From 20c1a6d14c9447b87394221fa42a0e85aca1b060 Mon Sep 17 00:00:00 2001 From: Kyle K Date: Thu, 24 Jan 2013 17:56:32 -0600 Subject: use a linked list to hold data, utilize Linux api --- .gitignore | 15 +++++ debug.c | 9 ++- main.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- main.h | 30 ++++++++- 4 files changed, 225 insertions(+), 31 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b33b9b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.* + +# do not ignore file below +!.gitignore + +*.o +core +.depend +.*.cmd +*.ko +*.mod.c +.tmp_versions +Module.symvers +modules.order + diff --git a/debug.c b/debug.c index c1d4529..c359046 100644 --- a/debug.c +++ b/debug.c @@ -115,9 +115,12 @@ int hello_read_procmem(char *page, char **start, off_t off, int count, int *eof, struct hello_dev *dev = (struct hello_dev *) data; - len = sprintf(page, "hello module internal buffer is at: %p, with index of: %lu, " - "buffer starts with: %c\n", dev->hello_buffer, dev->buff_index, - dev->hello_buffer[0]); + if (down_interruptible(&dev->sem)) /* protects page buffer from multiple writes */ + return -ERESTARTSYS; + len = sprintf(page, "hello module internal buffer is at: %p, with size of: %lu, " + "buffer starts with: %c\n", dev->mylist, dev->ll_size, + dev->mylist ? dev->mylist->chunk[0] : '0'); + up(&dev->sem); *eof = 1; /* signify that we're done with dumping our internal structure */ diff --git a/main.c b/main.c index 0ea47c8..7036144 100644 --- a/main.c +++ b/main.c @@ -1,25 +1,24 @@ /* * main.c * - * r/w /dev/helloc0 character device + * Character Device + * r/w /dev/helloc0 char device that stores its date using a linked list * - * to insert module and call mknod run: + * To insert module and call mknod run: * # ./hello_load.sh * - * to remove module and cleanup run: + * To remove module and cleanup run: * # ./hello_unload.sh * * # cat /dev/helloc0 * # echo -n foobar > /dev/helloc0 * - * notes: + * Notes: * - most notes are handwritten in my book, Device Drivers 3rd * - ssize_t is an int, size_t is unsigned int, be careful * - compile kernel with frame pointers to get better stack readout in case of an oops - * - * coding standars: - * - kernel official doc calls for tabs of width of 8, I'm using 4 spaced tab - * - ToDo: run Lindent + * - use strace to trace read() and write() syscalls to your driver, take note + * of the number of bytes requested to be r/w and what they got back * */ @@ -46,6 +45,34 @@ char *hellomsg = "photon is the energy carrier particle for EM waves within ligh struct dentry *debugfs_dir = NULL; struct dentry *debugfs_file = NULL; +/* + * Size of a buffer that each node of a linked list holds, this list resides in + * hello_device struct + */ +int hello_node_chunk_sz = HELLO_NODE_CHUNK_SIZE; + +/* allow arguments to be passed with insmod or modprobe to modfiy globals */ +module_param(hello_major, int, S_IRUGO); +module_param(hello_minor, int, S_IRUGO); +module_param(hello_node_chunk_sz, int, S_IRUGO); + +MODULE_AUTHOR("Kyle K"); +MODULE_LICENSE("GPL"); + +/* + * Be careful with this shared variable, it can be accessed and modified + * concurrently, e.g in case multiple reads or writes on /dev/helloc0 coming + * from userspace, and if we're not careful it can result in a race condition, + * where the thread that was the last to make a modification wins by overwriting + * previous state. + * + * Since to access to this variable will not be made from IRQ handler or other + * async context, a sempahore will work well. + * + * Operations should also be atomic, meaning that the entire operations happens + * at once as far as other threads of execution are concerned. So if we had a + * ptr that needed memory to be allocated we would like to do that only once. + */ struct hello_dev *hello_device; /* @@ -88,31 +115,119 @@ static int hello_create_debugfs(void) return ret; } +static int hello_trim(struct hello_dev *dev) +{ + struct hello_ll *pnode = NULL; + struct list_head *pos, *n /* tmp storage */; + PDEBUG("trim() called, dev->mylist is at: %p\n", dev->mylist); + + if (dev->mylist) { + list_for_each_safe(pos, n, &dev->mylist->list) { + /* computes offset of data member list within struct hello_ll and casts + * pos ptr to point lower address by amount of offset + */ + pnode = list_entry(pos, struct hello_ll, list); + list_del(pos); + kfree(pnode->chunk); + } + } + + dev->ll_size = 0; + dev->chunk_sz = hello_node_chunk_sz; + dev->mylist = NULL; + + return 0; +} + +/* + * Returns address to the nth node, it also allocates memory for nodes if they + * don't exist at such position + */ +static struct hello_ll *hello_follow(struct hello_dev *dev, int n) +{ + struct hello_ll *nptr = dev->mylist, *tmp; + PDEBUG("follow() called with n: %i\n", n); + + /* allocate first node explicitly if it needs to be */ + if (!nptr) { + PDEBUG("allocating first node\n"); + nptr = dev->mylist = kmalloc(sizeof(struct hello_ll), GFP_KERNEL); + if (!nptr) + return NULL; + memset(nptr, 0, sizeof(struct hello_ll)); + INIT_LIST_HEAD(&nptr->list); + /* segfault strcpy(nptr->chunk, "write to me\n"); */ + } + + /* follow the list */ + while (n--) { + /* almost like testing for NULL, remember this is circular list */ + if (nptr->list.next == &nptr->list) { + PDEBUG("allocating additional node"); + tmp = kmalloc(sizeof(struct hello_ll), GFP_KERNEL); + if (!tmp) + return NULL; + memset(tmp, 0, sizeof(struct hello_ll)); + list_add(&tmp->list, &nptr->list); + } + nptr = list_entry(nptr->list.next, struct hello_ll, list); + } + + return nptr; +} + +/* + * Reads at most dev->chunk_sz bytes + */ static ssize_t hello_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos) { struct hello_dev *dev = filp->private_data; ssize_t ret = 0; /* end of file, also in case of out of bound */ + struct hello_ll *cptr = NULL; /* node where right chunk is */ + int chunk_pos = 0; + PDEBUG("read() called\n"); if (down_interruptible(&dev->sem)) return -ERESTARTSYS; - if (*f_pos >= HELLO_KERNEL_BUFF_LEN) + if (*f_pos >= dev->ll_size) goto out; /* out of bound read */ - if (*f_pos + count > HELLO_KERNEL_BUFF_LEN) - count = HELLO_KERNEL_BUFF_LEN - *f_pos; /* truncate the read */ + if (*f_pos + count > dev->ll_size) + count = dev->ll_size - *f_pos; /* truncate the read */ + + /* offset to char array of a chunk */ + chunk_pos = *f_pos % dev->chunk_sz; - if (copy_to_user(buff, dev->hello_buffer + *f_pos, count)) /* write to userspace memory */ + /* seek within hello_ll to the right node */ + cptr = hello_follow(dev, *f_pos / dev->chunk_sz); + + if (!cptr || !cptr->chunk) + goto out; /* don't read nothingness */ + + /* + * I had a bug here because I didn't modify value of count and all the hell + * broke loose since I was reading past the chunk's memory since programs + * such as cat by default try to read 65536 bytes. At least count at this + * point was at most the amount of dev->ll_size - *f_pos + * + * We can write to user starting from chunk_pos all the way to the end of + * that chunk + */ + if (count > dev->chunk_sz - chunk_pos) + count = dev->chunk_sz - chunk_pos; + + /* write to userspace memory by reading only up to the end of chunk */ + if (copy_to_user(buff, cptr->chunk + (*f_pos % dev->chunk_sz), count)) { ret = -EFAULT; goto out; } *f_pos += count; /* yes we have to keep track */ - dev->buff_index += count; - ret= count; + ret = count; out: up(&dev->sem); @@ -122,43 +237,76 @@ static ssize_t hello_read(struct file *filp, char __user *buff, size_t count, lo static ssize_t hello_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos) { struct hello_dev *dev = filp->private_data; - ssize_t ret = 0; + ssize_t ret = -ENOMEM; + struct hello_ll *cptr = NULL; /* node where right chunk is */ + int chunk_pos = 0; + PDEBUG("write() called\n"); if (down_interruptible(&dev->sem)) return -ERESTARTSYS; - if (count > HELLO_KERNEL_BUFF_LEN) + chunk_pos = *f_pos / dev->chunk_sz; + + cptr = hello_follow(dev, *f_pos / dev->chunk_sz); + if (!cptr) goto out; - if (*f_pos + count > HELLO_KERNEL_BUFF_LEN) - count = HELLO_KERNEL_BUFF_LEN - *f_pos; + if (!cptr->chunk) { + cptr->chunk = kmalloc(dev->chunk_sz * sizeof(char *), GFP_KERNEL); + if (!cptr->chunk) + goto out; + memset(cptr->chunk, 0, dev->chunk_sz * sizeof(char *)); + } + + if (count > dev->chunk_sz - chunk_pos) + count = dev->chunk_sz - chunk_pos; - if (copy_from_user(dev->hello_buffer + *f_pos, buff, count)) + if (copy_from_user(cptr->chunk + chunk_pos, buff, count)) { ret = -EFAULT; goto out; } + *f_pos += count; ret = count; + /* update the size */ + if (dev->ll_size < *f_pos) + dev->ll_size = *f_pos; + out: up(&dev->sem); return ret; } -/* there's a count in filp that gets incremented every time our char device gets opened, but note that - * this function does not get called multiple times, instead copies are made, think fork(), or just simple ptr? +/* + * There's a count in filp that gets incremented every time our char device gets + * opened, but note that this function does not get called multiple times, + * instead copies are made, think fork(), or just simple ptr? */ static int hello_open(struct inode *inode, struct file *filp) { struct hello_dev *dev; + PDEBUG("open() called\n"); + dev = container_of(inode->i_cdev, struct hello_dev, cdev); filp->private_data = dev; /* our only chance to do that is here in open(), place to save our dev struct */ + /* now trim to 0 the length of the device if open was write-only */ + if ((filp->f_flags & O_ACCMODE) == O_WRONLY) { + if (down_interruptible(&dev->sem)) + return -ERESTARTSYS; + PDEBUG("write-only open, about to call trim()"); + hello_trim(dev); /* ignore errors */ + up(&dev->sem); + } + + PDEBUG("open() left\n"); return 0; } static int hello_release(struct inode *inode, struct file *filp) { + PDEBUG("release() called\n"); return 0; } @@ -170,7 +318,8 @@ struct file_operations hello_fops = { .release = hello_release }; -/* __init is a hint that the func is only used at initialization time, then module +/* + * __init is a hint that the func is only used at initialization time, then module * loader drops the function after module is loaded making its memory available for other uses */ static int __init hello_init(void) @@ -207,9 +356,10 @@ static int __init hello_init(void) goto fail; } memset(hello_device, 0, sizeof(struct hello_dev)); + hello_device->devnum = devnum; sema_init(&hello_device->sem, 1); - strcpy(hello_device->hello_buffer, "write to me\n"); + hello_device->chunk_sz = hello_node_chunk_sz; /* setup char_dev structure */ cdev_init(&hello_device->cdev, &hello_fops); @@ -234,6 +384,10 @@ static int __init hello_init(void) static void __exit hello_exit(void) { + if (hello_device) { + hello_trim(hello_device); + kfree(hello_device); + } unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1); hello_debugfs_destroy(debugfs_dir, debugfs_file); #ifdef HELLO_DEBUG @@ -245,5 +399,3 @@ static void __exit hello_exit(void) module_init(hello_init); module_exit(hello_exit); -MODULE_LICENSE("GPL"); - diff --git a/main.h b/main.h index db38aa8..cb751d0 100644 --- a/main.h +++ b/main.h @@ -2,26 +2,50 @@ #define _HELLOMAIN_H_ #include +#include + +/* + * Macros to aid with debugging + */ +#ifdef HELLO_DEBUG + #define PDEBUG(fmt, args...) printk( KERN_DEBUG "[hello] " fmt, ##args) +#else + #define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif #ifndef HELLO_MAJOR #define HELLO_MAJOR 0 /* let kernel choose, unless user really defined it */ #endif -#define HELLO_KERNEL_BUFF_LEN 1024 +/* + * Allow user to define size of node using a #define, or passing -D compiler + * flag + */ +#ifndef HELLO_NODE_CHUNK_SIZE +#define HELLO_NODE_CHUNK_SIZE 256 +#endif extern int hello_major; extern int hello_minor; extern char *magicstr; +/* node to be used with Linux api for circular linked lists */ +struct hello_ll +{ + char *chunk; + struct list_head list; +}; + struct hello_dev { dev_t devnum; struct semaphore sem; struct cdev cdev; /* char device */ /* for r/w operations */ - char hello_buffer[HELLO_KERNEL_BUFF_LEN]; - size_t buff_index; + struct hello_ll *mylist; + int chunk_sz; + size_t ll_size; }; /* function prototypes */ -- cgit v1.2.3