/* chardev.c * Simple character device driver. * Derived extensively from the LKMPG device driver example. * * Keeps a counter of the number of dev_open calls that have been made * and the number of read calls on each open. * On device read, reports the current number of dev_open and * dev_read calls. * * When you load this module you will get instructions for * creating the appropriate device file with the kernel-assigned * major device number (probably 254). For example, * mknod ./dev/mydev c 254 0 (do man on mknod). * A nice way to upgrade this program would be to code the mknod * as a system call in init_module and code a system("rm") in * in cleanup_module. * * To invoke this module after creating the device file dev/mydev * do a command line "head dev/mydev" to see * the open and read counts. * * Note: if you try a cat on the device file then be ready to ^C * to end your stream of prints because this version of the program * never returns an end of file that cat is looking for to quit. */ /* kernel module programming */ #ifndef MODULE #define MODULE #endif #ifndef LINUX #define LINUX #endif #ifndef __KERNEL__ #define __KERNEL__ #endif #include /* needed by all modules */ #include /* for KERN_ALERT */ #include /* for struct file_operations */ #include /* for put_user */ #include /* for all those handy error constants */ /* Prototypes - this would normally go in a header file */ int init_module(void); void cleanup_module(void); /* These should be static because they should only be called through * the fops file_operations structure instance. */ static int device_open(struct inode *, struct file *); static int device_release(struct inode *, struct file *); static ssize_t device_read(struct file *, char *, size_t, loff_t *); static ssize_t device_write(struct file *, const char *, size_t, loff_t *); #define DEVICE_NAME "mydev" /* device name in /proc/devices */ #define MAX_BUF 80 /* max msg length from device */ /* Device driver global variables */ static int major; static int dev_open = 0; /* this does not seem to be concurrent safe */ static int open_cnt = 0; static int read_cnt; /* count how many reads after each open */ /* Gcc assigns the rest of the structure fields to NULL */ static struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release }; /* new character device file functions */ static int device_open(struct inode *inodp, struct file *filp) { /* This seems like it is not concurrent-safe locking */ if (dev_open) return -EBUSY; dev_open++; open_cnt++; read_cnt = 0; MOD_INC_USE_COUNT; return 0; } static int device_release(struct inode *inodp, struct file *filp) { dev_open--; MOD_DEC_USE_COUNT; return 0; } static ssize_t device_read(struct file *filp, char *buf, /* caller's buffer and length */ size_t length, loff_t *offset) /* Our offset in the file */ { static char msg[MAX_BUF]; int len; size_t cplen; /* Figure out when and how to send back an eof (use * a return 0 to signify no bytes read to the file I/O * driver). * Programs like cat won't quit until they see an eof. * Can I use offset pointer to do this? Say after a single read? * if (offset > 0) return 0; */ read_cnt++; /* On dev file read send back the open count and the read count. * Use safe nprintf. What would have been copied is len on truncation. */ len = snprintf(msg, MAX_BUF, "Open_cnt: %d, Read_cnt: %d\n", open_cnt, read_cnt); cplen = len < MAX_BUF ? len : MAX_BUF; /* Note: before this next copy_to_user, I need to make sure that the user * buffer length is big enough to hold a message of size len+1 * and if not, then just fill the user buffer and ignore the rest. */ /* len back from sprintf does not include the trailing 0 */ cplen = length < cplen+1 ? length : cplen+1; if ( copy_to_user(buf, msg, cplen) ) return -EFAULT; return cplen; } static ssize_t device_write(struct file *filp, const char *buf, /* caller's buffer and length */ size_t length, loff_t *offset) /* Our offset in the file */ { printk(KERN_ALERT "device_write: Operation not supported\n"); return -EINVAL; } /* Initialize the module at insmod */ int init_module(void) { printk(KERN_ALERT "chardev module loaded\n"); /* the first argument as 0 directs kernel to supply a major device number */ major = register_chrdev(0, DEVICE_NAME, &fops); if (major < 0) { printk (KERN_ALERT "init_module: register char dev failed with %d\n", major); return major; } printk(KERN_ALERT "init_module: Major device number assigned is: %d\n", major); printk(KERN_ALERT "init_module: Create device with mknod ./dev/mydev c %d 0\n", major); /* A non-zero return means init_module failed; module can't be loaded */ return 0; } /* Clean up the module at rmmod */ void cleanup_module(void) { int ret; ret = unregister_chrdev(major, DEVICE_NAME); if (ret < 0) printk(KERN_ALERT "cleanup_module: unregister failed with %d\n", ret); printk(KERN_ALERT "chardev module unloaded\n"); } MODULE_LICENSE("GPL");