/* chardev.c * Neal Nelson 2010.02.10 * * Simple fifo character device driver. * * Keeps a circular buffer of characters that is initialized on module load, * queued on device write calls, and dequeued on device read calls. * * Maintains a synchronization lock on device open, so that only * one process can be accessing the device at any time. * * 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). This feature * could be programmed into the init_module by doing a mknod * system call. Similarly, on cleanup_module, a system("rm") * call could be made to remove the device. Or we could just * leave the device if we keep our own experimental directory * of devices. * * To write to the device just do a cat myfile >./dev/mydev * or an echo 123456789abcde > ./dev/mydev * and do a cat ./dev/mydev to read from the device. Note * that writing to the device with cat appends characters and * reading from the device with cat empties the device. * * Notes * * 2010.02.16 * Added atomic locking on the dev_open variable. Still needs to * be stress tested to demonstrate the lock performs when needed. * Hmmmm. It seems to me that what this program really needs mutual * exclusion around the circular buffer read and write! That will * require some more locking. But for now, the lock on open will * suffice to insure mutual exclusion on buffer access since no * process can read or write unless they have opened the device. * * 2010.02.12 * I fixed two bugs. First, I accidently typed an index i in qbuf[i] * in the put_user instead of the proper index qbuf[rp]. * Second, I fixed an infinite write loop that occurred when the * buffer to write had a length greater than the circular buffer. * This caused the caller to persistently call back waiting to * hear that all of the buffer length of characters had been * written to the device. To fix this I just lied and returned * the caller's buffer length on write instead of the actual * number of characters put into the device circular buffer. * That fix is consistent with just throwing the characters * away, although I should probably have returned * a -EOVERFLOW error or something like that. * * The initialization of the circular buffer pointers has to * occur in init_module not open_device because each system * call may separately open the file and we want the state * of the file to live beyond the lifetime of any process * accessing the file. * * I believe you just have to quit thinking about using 0 EOS * terminated strings and always use buffer addresses and * lengths. The following types are relevant for that. I suspect the * signed size is used to return negative numbers as error codes. * You can always return an unsigned int as an int (but not the reverse). * * size_t is unsigned int * ssize_t (signed size) is int * * Static functions in C just mean private to the file, * which is what I think we want for these kernel device driver * functions. The driver functions should only be called through * the file_operations fops structure instance. * Note that init_module and cleanup_module are not private. * * This code is intended to allow only one process to be using * the device at a time. The locking in this program does not seem * to be SMP safe. Does the file system somewhere above do * some locking on device opens? * */ /* 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 */ #include /* for atomic locking on device open */ /* prototypes - this would normally go in a header file */ int init_module(void); void cleanup_module(void); 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 *); static size_t enQ(const char *, size_t); static size_t deQ(char *, size_t); #define DEVICE_NAME "mydev" /* device name in /proc/devices */ #define MAX_BUF 10 /* max msg length from device */ /* Device driver global variables */ static int major; static atomic_t dev_open = ATOMIC_INIT(1); /* Device locking */ static char qbuf[MAX_BUF]; /* circular buffer of char for fifo */ static int rp, wp; /* 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) { /* Device locking * Currently I want to just report a failed attempt to lock * just so I can see if I can get a contention to show up * when I'm stress testing this. * To see what this does, look at the definition in * (it returns true if the result is zero). */ if (!atomic_dec_and_test(&dev_open) ) return -EBUSY; MOD_INC_USE_COUNT; return 0; } static int device_release(struct inode *inodp, struct file *filp) { /* Unlock the device */ atomic_inc(&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 */ { int n; printk(KERN_ALERT "device_read: Entering\n"); /* Use deQ to copy the qbuf into user buffer. If the * user buffer is smaller than the circular buffer, * then just leave it up to the caller * to keep asking for more until we send back a 0 byte * read meaning EOF. That's what the file read system calls do * when interacting with file drivers. * * I think this code simply solves this problem below by * returning 0 for EOF just when when 0 bytes are finally * read from the circular buffer. */ printk(KERN_ALERT "device_read: rp: %d, wp: %d\n",rp,wp); n = deQ(buf, length); printk(KERN_ALERT "device_read: rp: %d, wp: %d\n",rp,wp); printk(KERN_ALERT "device_read: %d chars dequeued\n", n); return n; } static ssize_t device_write(struct file *filp, const char *buf, /* caller's buffer; const so it doesn't change */ size_t length, /* buffer size, size_t is unsigned int */ loff_t *offset) /* Our offset in the file */ { ssize_t n; printk(KERN_ALERT "device_write: Entering\n"); /* Write the entire caller's buffer into the qbuf. We * can't assume that the buffer has a 0 terminated * string in it. */ /* use enQ to copy the user buffer into the qbuf. If * the qbuf is not big enough, then just truncate the * user message. Return the number of characters * entered into the queue. */ printk(KERN_ALERT "device_write rp: %d, wp: %d\n",rp,wp); n = enQ(buf, length); printk(KERN_ALERT "device_write rp: %d, wp: %d\n",rp,wp); printk(KERN_ALERT "device_write: %d chars enqueued\n", n); printk(KERN_ALERT "device_write: %d chars discarded\n", length-n); /* We have to lie about how many characters were written because * the caller may keep calling back until all the characters up * to the length of the buffer are reported to have been written. * Another way to do this is to go ahead and let the caller * call back, but throw the characters away up to length and * report back length-n the on the second call. That'll get the * caller off our back. Thirdly, we could just overwrite the * buffer, letting it go ahead and wrap around. Finally, I * really probably should just return -EOVERFLOW from * But for this experiment I'm just going to lie. */ /* return n; */ return length; /* lie to make the caller happy */ } /* Circular Queue functions */ static size_t enQ(const char *s, size_t length) { size_t i; /* Put characters from the user buffer into the * circular buffer. When the queue fills, ignore the * rest of the input string sent to the queue. * Return number of characters put in queue, so * return 0 if the queue is already full. */ /* put the argument string into the buffer, no 0 EOS */ for (i = 0; i < length && ((wp+1) % MAX_BUF != rp); i++) { qbuf[wp] = s[i]; wp = (wp+1) % MAX_BUF; } return i; } static size_t deQ(char *buf, size_t length) { size_t i; /* Fill the caller's buffer with characters taken from queue * until either the queue is empty or the caller buffer is full. * Return the number of bytes read from the queue, so 0 if * queue is empty. */ for (i = 0; i < length && rp != wp; i++) { /* buf[i] = qbuf[rp]; */ put_user(qbuf[rp], &buf[i] ); rp = (rp+1) % MAX_BUF; } return i; } /* 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; } /* init circular buffer pointers */ rp = 0; wp = 0; 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");