2 * drivers/s390/char/vmlogrdr.c
3 * character device driver for reading z/VM system service records
6 * Copyright (C) 2004 IBM Corporation
7 * character device driver for reading z/VM system service records,
9 * Author(s): Xenia Tkatschow <xenia@us.ibm.com>
10 * Stefan Weinhuber <wein@de.ibm.com>
13 #include <linux/module.h>
14 #include <linux/init.h>
15 #include <linux/errno.h>
16 #include <linux/types.h>
17 #include <linux/interrupt.h>
18 #include <linux/spinlock.h>
19 #include <asm/atomic.h>
20 #include <asm/uaccess.h>
21 #include <asm/cpcmd.h>
22 #include <asm/debug.h>
23 #include <asm/ebcdic.h>
24 #include "../net/iucv.h"
25 #include <linux/kmod.h>
26 #include <linux/cdev.h>
27 #include <linux/device.h>
28 #include <linux/string.h>
33 ("(C) 2004 IBM Corporation by Xenia Tkatschow (xenia@us.ibm.com)\n"
34 " Stefan Weinhuber (wein@de.ibm.com)");
35 MODULE_DESCRIPTION ("Character device driver for reading z/VM "
36 "system service records.");
37 MODULE_LICENSE("GPL");
41 * The size of the buffer for iucv data transfer is one page,
42 * but in addition to the data we read from iucv we also
43 * place an integer and some characters into that buffer,
44 * so the maximum size for record data is a little less then
47 #define NET_BUFFER_SIZE (PAGE_SIZE - sizeof(int) - sizeof(FENCE))
50 * The elements that are concurrently accessed by bottom halves are
51 * connection_established, iucv_path_severed, local_interrupt_buffer
52 * and receive_ready. The first three can be protected by
53 * priv_lock. receive_ready is atomic, so it can be incremented and
54 * decremented without holding a lock.
55 * The variable dev_in_use needs to be protected by the lock, since
56 * it's a flag used by open to make sure that the device is opened only
57 * by one user at the same time.
59 struct vmlogrdr_priv_t
{
60 char system_service
[8];
61 char internal_name
[8];
62 char recording_name
[8];
64 int connection_established
;
65 int iucv_path_severed
;
66 iucv_MessagePending local_interrupt_buffer
;
67 atomic_t receive_ready
;
68 iucv_handle_t iucv_handle
;
71 char * current_position
;
73 ulong residual_length
;
75 int dev_in_use
; /* 1: already opened, 0: not opened*/
77 struct device
*device
;
78 struct class_device
*class_device
;
85 * File operation structure for vmlogrdr devices
87 static int vmlogrdr_open(struct inode
*, struct file
*);
88 static int vmlogrdr_release(struct inode
*, struct file
*);
89 static ssize_t
vmlogrdr_read (struct file
*filp
, char *data
, size_t count
,
92 static struct file_operations vmlogrdr_fops
= {
94 .open
= vmlogrdr_open
,
95 .release
= vmlogrdr_release
,
96 .read
= vmlogrdr_read
,
100 static u8 iucvMagic
[16] = {
101 0xF0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
102 0xF0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40
107 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
108 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
109 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
110 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
114 static u8 iucv_host
[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
118 vmlogrdr_iucv_ConnectionComplete(iucv_ConnectionComplete
*eib
, void *pgm_data
);
120 vmlogrdr_iucv_ConnectionSevered(iucv_ConnectionSevered
*eib
, void *pgm_data
);
122 vmlogrdr_iucv_MessagePending(iucv_MessagePending
*eib
, void *pgm_data
);
125 static iucv_interrupt_ops_t vmlogrdr_iucvops
= {
126 .ConnectionComplete
= vmlogrdr_iucv_ConnectionComplete
,
127 .ConnectionSevered
= vmlogrdr_iucv_ConnectionSevered
,
128 .MessagePending
= vmlogrdr_iucv_MessagePending
,
132 DECLARE_WAIT_QUEUE_HEAD(conn_wait_queue
);
133 DECLARE_WAIT_QUEUE_HEAD(read_wait_queue
);
136 * pointer to system service private structure
137 * minor number 0 --> logrec
138 * minor number 1 --> account
139 * minor number 2 --> symptom
142 static struct vmlogrdr_priv_t sys_ser
[] = {
143 { .system_service
= "*LOGREC ",
144 .internal_name
= "logrec",
145 .recording_name
= "EREP",
148 .priv_lock
= SPIN_LOCK_UNLOCKED
,
152 { .system_service
= "*ACCOUNT",
153 .internal_name
= "account",
154 .recording_name
= "ACCOUNT",
157 .priv_lock
= SPIN_LOCK_UNLOCKED
,
161 { .system_service
= "*SYMPTOM",
162 .internal_name
= "symptom",
163 .recording_name
= "SYMPTOM",
166 .priv_lock
= SPIN_LOCK_UNLOCKED
,
172 #define MAXMINOR (sizeof(sys_ser)/sizeof(struct vmlogrdr_priv_t))
174 static char FENCE
[] = {"EOR"};
175 static int vmlogrdr_major
= 0;
176 static struct cdev
*vmlogrdr_cdev
= NULL
;
177 static int recording_class_AB
;
181 vmlogrdr_iucv_ConnectionComplete (iucv_ConnectionComplete
* eib
,
184 struct vmlogrdr_priv_t
* logptr
= pgm_data
;
185 spin_lock(&logptr
->priv_lock
);
186 logptr
->connection_established
= 1;
187 spin_unlock(&logptr
->priv_lock
);
188 wake_up(&conn_wait_queue
);
194 vmlogrdr_iucv_ConnectionSevered (iucv_ConnectionSevered
* eib
, void * pgm_data
)
196 u8 reason
= (u8
) eib
->ipuser
[8];
197 struct vmlogrdr_priv_t
* logptr
= pgm_data
;
199 printk (KERN_ERR
"vmlogrdr: connection severed with"
200 " reason %i\n", reason
);
202 spin_lock(&logptr
->priv_lock
);
203 logptr
->connection_established
= 0;
204 logptr
->iucv_path_severed
= 1;
205 spin_unlock(&logptr
->priv_lock
);
207 wake_up(&conn_wait_queue
);
208 /* just in case we're sleeping waiting for a record */
209 wake_up_interruptible(&read_wait_queue
);
214 vmlogrdr_iucv_MessagePending (iucv_MessagePending
* eib
, void * pgm_data
)
216 struct vmlogrdr_priv_t
* logptr
= pgm_data
;
219 * This function is the bottom half so it should be quick.
220 * Copy the external interrupt data into our local eib and increment
223 spin_lock(&logptr
->priv_lock
);
224 memcpy(&(logptr
->local_interrupt_buffer
), eib
, sizeof(*eib
));
225 atomic_inc(&logptr
->receive_ready
);
226 spin_unlock(&logptr
->priv_lock
);
227 wake_up_interruptible(&read_wait_queue
);
232 vmlogrdr_get_recording_class_AB(void) {
233 char cp_command
[]="QUERY COMMAND RECORDING ";
234 char cp_response
[80];
238 printk (KERN_DEBUG
"vmlogrdr: query command: %s\n", cp_command
);
239 cpcmd(cp_command
, cp_response
, sizeof(cp_response
), NULL
);
240 printk (KERN_DEBUG
"vmlogrdr: response: %s", cp_response
);
241 len
= strnlen(cp_response
,sizeof(cp_response
));
243 tail
=strnchr(cp_response
,len
,'=');
247 if (!strncmp("ANY",tail
,3))
249 if (!strncmp("NONE",tail
,4))
252 * expect comma separated list of classes here, if one of them
253 * is A or B return 1 otherwise 0
255 for (i
=tail
-cp_response
; i
<len
; i
++)
256 if ( cp_response
[i
]=='A' || cp_response
[i
]=='B' )
263 vmlogrdr_recording(struct vmlogrdr_priv_t
* logptr
, int action
, int purge
) {
266 char cp_response
[160];
267 char *onoff
, *qid_string
;
269 memset(cp_command
, 0x00, sizeof(cp_command
));
270 memset(cp_response
, 0x00, sizeof(cp_response
));
272 onoff
= ((action
== 1) ? "ON" : "OFF");
273 qid_string
= ((recording_class_AB
== 1) ? " QID * " : "");
276 * The recording commands needs to be called with option QID
277 * for guests that have previlege classes A or B.
278 * Purging has to be done as separate step, because recording
279 * can't be switched on as long as records are on the queue.
280 * Doing both at the same time doesn't work.
284 snprintf(cp_command
, sizeof(cp_command
),
285 "RECORDING %s PURGE %s",
286 logptr
->recording_name
,
289 printk (KERN_DEBUG
"vmlogrdr: recording command: %s\n",
291 cpcmd(cp_command
, cp_response
, sizeof(cp_response
), NULL
);
292 printk (KERN_DEBUG
"vmlogrdr: recording response: %s",
296 memset(cp_command
, 0x00, sizeof(cp_command
));
297 memset(cp_response
, 0x00, sizeof(cp_response
));
298 snprintf(cp_command
, sizeof(cp_command
), "RECORDING %s %s %s",
299 logptr
->recording_name
,
303 printk (KERN_DEBUG
"vmlogrdr: recording command: %s\n", cp_command
);
304 cpcmd(cp_command
, cp_response
, sizeof(cp_response
), NULL
);
305 printk (KERN_DEBUG
"vmlogrdr: recording response: %s",
307 /* The recording command will usually answer with 'Command complete'
308 * on success, but when the specific service was never connected
309 * before then there might be an additional informational message
310 * 'HCPCRC8072I Recording entry not found' before the
311 * 'Command complete'. So I use strstr rather then the strncmp.
313 if (strstr(cp_response
,"Command complete"))
322 vmlogrdr_open (struct inode
*inode
, struct file
*filp
)
325 struct vmlogrdr_priv_t
* logptr
= NULL
;
329 dev_num
= iminor(inode
);
330 if (dev_num
> MAXMINOR
)
333 logptr
= &sys_ser
[dev_num
];
338 * only allow for blocking reads to be open
340 if (filp
->f_flags
& O_NONBLOCK
)
343 /* Besure this device hasn't already been opened */
344 spin_lock_bh(&logptr
->priv_lock
);
345 if (logptr
->dev_in_use
) {
346 spin_unlock_bh(&logptr
->priv_lock
);
349 logptr
->dev_in_use
= 1;
350 spin_unlock_bh(&logptr
->priv_lock
);
353 atomic_set(&logptr
->receive_ready
, 0);
354 logptr
->buffer_free
= 1;
356 /* set the file options */
357 filp
->private_data
= logptr
;
358 filp
->f_op
= &vmlogrdr_fops
;
360 /* start recording for this service*/
362 if (logptr
->autorecording
)
363 ret
= vmlogrdr_recording(logptr
,1,logptr
->autopurge
);
365 printk (KERN_WARNING
"vmlogrdr: failed to start "
366 "recording automatically\n");
368 /* Register with iucv driver */
369 logptr
->iucv_handle
= iucv_register_program(iucvMagic
,
370 logptr
->system_service
, mask
, &vmlogrdr_iucvops
,
373 if (logptr
->iucv_handle
== NULL
) {
374 printk (KERN_ERR
"vmlogrdr: failed to register with"
379 /* create connection to the system service */
380 spin_lock_bh(&logptr
->priv_lock
);
381 logptr
->connection_established
= 0;
382 logptr
->iucv_path_severed
= 0;
383 spin_unlock_bh(&logptr
->priv_lock
);
385 connect_rc
= iucv_connect (&(logptr
->pathid
), 10, iucvMagic
,
386 logptr
->system_service
, iucv_host
, 0,
388 logptr
->iucv_handle
, NULL
);
390 printk (KERN_ERR
"vmlogrdr: iucv connection to %s "
391 "failed with rc %i \n", logptr
->system_service
,
396 /* We've issued the connect and now we must wait for a
397 * ConnectionComplete or ConnectinSevered Interrupt
398 * before we can continue to process.
400 wait_event(conn_wait_queue
, (logptr
->connection_established
)
401 || (logptr
->iucv_path_severed
));
402 if (logptr
->iucv_path_severed
) {
406 return nonseekable_open(inode
, filp
);
409 iucv_unregister_program(logptr
->iucv_handle
);
410 logptr
->iucv_handle
= NULL
;
412 if (logptr
->autorecording
)
413 vmlogrdr_recording(logptr
,0,logptr
->autopurge
);
414 logptr
->dev_in_use
= 0;
422 vmlogrdr_release (struct inode
*inode
, struct file
*filp
)
426 struct vmlogrdr_priv_t
* logptr
= filp
->private_data
;
428 iucv_unregister_program(logptr
->iucv_handle
);
429 logptr
->iucv_handle
= NULL
;
431 if (logptr
->autorecording
) {
432 ret
= vmlogrdr_recording(logptr
,0,logptr
->autopurge
);
434 printk (KERN_WARNING
"vmlogrdr: failed to stop "
435 "recording automatically\n");
437 logptr
->dev_in_use
= 0;
444 vmlogrdr_receive_data(struct vmlogrdr_priv_t
*priv
) {
446 /* we need to keep track of two data sizes here:
447 * The number of bytes we need to receive from iucv and
448 * the total number of bytes we actually write into the buffer.
450 int user_data_count
, iucv_data_count
;
453 if (atomic_read(&priv
->receive_ready
)) {
454 spin_lock_bh(&priv
->priv_lock
);
455 if (priv
->residual_length
){
456 /* receive second half of a record */
457 iucv_data_count
= priv
->residual_length
;
459 buffer
= priv
->buffer
;
461 /* receive a new record:
462 * We need to return the total length of the record
463 * + size of FENCE in the first 4 bytes of the buffer.
466 priv
->local_interrupt_buffer
.ln1msg2
.ipbfln1f
;
467 user_data_count
= sizeof(int);
468 temp
= (int*)priv
->buffer
;
469 *temp
= iucv_data_count
+ sizeof(FENCE
);
470 buffer
= priv
->buffer
+ sizeof(int);
473 * If the record is bigger then our buffer, we receive only
474 * a part of it. We can get the rest later.
476 if (iucv_data_count
> NET_BUFFER_SIZE
)
477 iucv_data_count
= NET_BUFFER_SIZE
;
478 rc
= iucv_receive(priv
->pathid
,
479 priv
->local_interrupt_buffer
.ipmsgid
,
480 priv
->local_interrupt_buffer
.iptrgcls
,
485 &priv
->residual_length
);
486 spin_unlock_bh(&priv
->priv_lock
);
487 /* An rc of 5 indicates that the record was bigger then
488 * the buffer, which is OK for us. A 9 indicates that the
489 * record was purged befor we could receive it.
494 atomic_set(&priv
->receive_ready
, 0);
499 priv
->buffer_free
= 0;
500 user_data_count
+= iucv_data_count
;
501 priv
->current_position
= priv
->buffer
;
502 if (priv
->residual_length
== 0){
503 /* the whole record has been captured,
504 * now add the fence */
505 atomic_dec(&priv
->receive_ready
);
506 buffer
= priv
->buffer
+ user_data_count
;
507 memcpy(buffer
, FENCE
, sizeof(FENCE
));
508 user_data_count
+= sizeof(FENCE
);
510 priv
->remaining
= user_data_count
;
518 vmlogrdr_read (struct file
*filp
, char *data
, size_t count
, loff_t
* ppos
)
521 struct vmlogrdr_priv_t
* priv
= filp
->private_data
;
523 while (priv
->buffer_free
) {
524 rc
= vmlogrdr_receive_data(priv
);
526 rc
= wait_event_interruptible(read_wait_queue
,
527 atomic_read(&priv
->receive_ready
));
532 /* copy only up to end of record */
533 if (count
> priv
->remaining
)
534 count
= priv
->remaining
;
536 if (copy_to_user(data
, priv
->current_position
, count
))
540 priv
->current_position
+= count
;
541 priv
->remaining
-= count
;
543 /* if all data has been transferred, set buffer free */
544 if (priv
->remaining
== 0)
545 priv
->buffer_free
= 1;
551 vmlogrdr_autopurge_store(struct device
* dev
, struct device_attribute
*attr
, const char * buf
, size_t count
) {
552 struct vmlogrdr_priv_t
*priv
= dev
->driver_data
;
570 vmlogrdr_autopurge_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
) {
571 struct vmlogrdr_priv_t
*priv
= dev
->driver_data
;
572 return sprintf(buf
, "%u\n", priv
->autopurge
);
576 static DEVICE_ATTR(autopurge
, 0644, vmlogrdr_autopurge_show
,
577 vmlogrdr_autopurge_store
);
581 vmlogrdr_purge_store(struct device
* dev
, struct device_attribute
*attr
, const char * buf
, size_t count
) {
584 char cp_response
[80];
585 struct vmlogrdr_priv_t
*priv
= dev
->driver_data
;
590 memset(cp_command
, 0x00, sizeof(cp_command
));
591 memset(cp_response
, 0x00, sizeof(cp_response
));
594 * The recording command needs to be called with option QID
595 * for guests that have previlege classes A or B.
596 * Other guests will not recognize the command and we have to
597 * issue the same command without the QID parameter.
600 if (recording_class_AB
)
601 snprintf(cp_command
, sizeof(cp_command
),
602 "RECORDING %s PURGE QID * ",
603 priv
->recording_name
);
605 snprintf(cp_command
, sizeof(cp_command
),
606 "RECORDING %s PURGE ",
607 priv
->recording_name
);
609 printk (KERN_DEBUG
"vmlogrdr: recording command: %s\n", cp_command
);
610 cpcmd(cp_command
, cp_response
, sizeof(cp_response
), NULL
);
611 printk (KERN_DEBUG
"vmlogrdr: recording response: %s",
618 static DEVICE_ATTR(purge
, 0200, NULL
, vmlogrdr_purge_store
);
622 vmlogrdr_autorecording_store(struct device
*dev
, struct device_attribute
*attr
, const char *buf
,
624 struct vmlogrdr_priv_t
*priv
= dev
->driver_data
;
629 priv
->autorecording
=0;
632 priv
->autorecording
=1;
642 vmlogrdr_autorecording_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
) {
643 struct vmlogrdr_priv_t
*priv
= dev
->driver_data
;
644 return sprintf(buf
, "%u\n", priv
->autorecording
);
648 static DEVICE_ATTR(autorecording
, 0644, vmlogrdr_autorecording_show
,
649 vmlogrdr_autorecording_store
);
653 vmlogrdr_recording_store(struct device
* dev
, struct device_attribute
*attr
, const char * buf
, size_t count
) {
655 struct vmlogrdr_priv_t
*priv
= dev
->driver_data
;
660 ret
= vmlogrdr_recording(priv
,0,0);
663 ret
= vmlogrdr_recording(priv
,1,0);
676 static DEVICE_ATTR(recording
, 0200, NULL
, vmlogrdr_recording_store
);
680 vmlogrdr_recording_status_show(struct device_driver
*driver
, char *buf
) {
682 char cp_command
[] = "QUERY RECORDING ";
685 cpcmd(cp_command
, buf
, 4096, NULL
);
691 static DRIVER_ATTR(recording_status
, 0444, vmlogrdr_recording_status_show
,
694 static struct attribute
*vmlogrdr_attrs
[] = {
695 &dev_attr_autopurge
.attr
,
696 &dev_attr_purge
.attr
,
697 &dev_attr_autorecording
.attr
,
698 &dev_attr_recording
.attr
,
702 static struct attribute_group vmlogrdr_attr_group
= {
703 .attrs
= vmlogrdr_attrs
,
706 static struct class *vmlogrdr_class
;
707 static struct device_driver vmlogrdr_driver
= {
714 vmlogrdr_register_driver(void) {
717 ret
= driver_register(&vmlogrdr_driver
);
719 printk(KERN_ERR
"vmlogrdr: failed to register driver.\n");
723 ret
= driver_create_file(&vmlogrdr_driver
,
724 &driver_attr_recording_status
);
726 printk(KERN_ERR
"vmlogrdr: failed to add driver attribute.\n");
730 vmlogrdr_class
= class_create(THIS_MODULE
, "vmlogrdr");
731 if (IS_ERR(vmlogrdr_class
)) {
732 printk(KERN_ERR
"vmlogrdr: failed to create class.\n");
733 ret
=PTR_ERR(vmlogrdr_class
);
740 driver_remove_file(&vmlogrdr_driver
, &driver_attr_recording_status
);
742 driver_unregister(&vmlogrdr_driver
);
748 vmlogrdr_unregister_driver(void) {
749 class_destroy(vmlogrdr_class
);
750 vmlogrdr_class
= NULL
;
751 driver_remove_file(&vmlogrdr_driver
, &driver_attr_recording_status
);
752 driver_unregister(&vmlogrdr_driver
);
758 vmlogrdr_register_device(struct vmlogrdr_priv_t
*priv
) {
762 dev
= kzalloc(sizeof(struct device
), GFP_KERNEL
);
764 snprintf(dev
->bus_id
, BUS_ID_SIZE
, "%s",
765 priv
->internal_name
);
766 dev
->bus
= &iucv_bus
;
767 dev
->parent
= iucv_root
;
768 dev
->driver
= &vmlogrdr_driver
;
770 * The release function could be called after the
771 * module has been unloaded. It's _only_ task is to
772 * free the struct. Therefore, we specify kfree()
773 * directly here. (Probably a little bit obfuscating
776 dev
->release
= (void (*)(struct device
*))kfree
;
779 ret
= device_register(dev
);
783 ret
= sysfs_create_group(&dev
->kobj
, &vmlogrdr_attr_group
);
785 device_unregister(dev
);
788 priv
->class_device
= class_device_create(
791 MKDEV(vmlogrdr_major
, priv
->minor_num
),
794 if (IS_ERR(priv
->class_device
)) {
795 ret
= PTR_ERR(priv
->class_device
);
796 priv
->class_device
=NULL
;
797 sysfs_remove_group(&dev
->kobj
, &vmlogrdr_attr_group
);
798 device_unregister(dev
);
801 dev
->driver_data
= priv
;
808 vmlogrdr_unregister_device(struct vmlogrdr_priv_t
*priv
) {
809 class_device_destroy(vmlogrdr_class
, MKDEV(vmlogrdr_major
, priv
->minor_num
));
810 if (priv
->device
!= NULL
) {
811 sysfs_remove_group(&priv
->device
->kobj
, &vmlogrdr_attr_group
);
812 device_unregister(priv
->device
);
820 vmlogrdr_register_cdev(dev_t dev
) {
822 vmlogrdr_cdev
= cdev_alloc();
823 if (!vmlogrdr_cdev
) {
826 vmlogrdr_cdev
->owner
= THIS_MODULE
;
827 vmlogrdr_cdev
->ops
= &vmlogrdr_fops
;
828 vmlogrdr_cdev
->dev
= dev
;
829 rc
= cdev_add(vmlogrdr_cdev
, vmlogrdr_cdev
->dev
, MAXMINOR
);
833 // cleanup: cdev is not fully registered, no cdev_del here!
834 kobject_put(&vmlogrdr_cdev
->kobj
);
841 vmlogrdr_cleanup(void) {
844 cdev_del(vmlogrdr_cdev
);
847 for (i
=0; i
< MAXMINOR
; ++i
) {
848 vmlogrdr_unregister_device(&sys_ser
[i
]);
849 free_page((unsigned long)sys_ser
[i
].buffer
);
851 vmlogrdr_unregister_driver();
852 if (vmlogrdr_major
) {
853 unregister_chrdev_region(MKDEV(vmlogrdr_major
, 0), MAXMINOR
);
866 if (! MACHINE_IS_VM
) {
867 printk (KERN_ERR
"vmlogrdr: not running under VM, "
868 "driver not loaded.\n");
872 recording_class_AB
= vmlogrdr_get_recording_class_AB();
874 rc
= alloc_chrdev_region(&dev
, 0, MAXMINOR
, "vmlogrdr");
877 vmlogrdr_major
= MAJOR(dev
);
879 rc
=vmlogrdr_register_driver();
883 for (i
=0; i
< MAXMINOR
; ++i
) {
884 sys_ser
[i
].buffer
= (char *) get_zeroed_page(GFP_KERNEL
);
885 if (!sys_ser
[i
].buffer
) {
889 sys_ser
[i
].current_position
= sys_ser
[i
].buffer
;
890 rc
=vmlogrdr_register_device(&sys_ser
[i
]);
897 rc
= vmlogrdr_register_cdev(dev
);
900 printk (KERN_INFO
"vmlogrdr: driver loaded\n");
905 printk (KERN_ERR
"vmlogrdr: driver not loaded.\n");
914 printk (KERN_INFO
"vmlogrdr: driver unloaded\n");
919 module_init(vmlogrdr_init
);
920 module_exit(vmlogrdr_exit
);