diff options
author | Kyle K <kylek389@gmail.com> | 2013-01-24 17:56:32 -0600 |
---|---|---|
committer | Kyle Kaminski <kyle@kkaminsk.com> | 2013-01-24 17:56:32 -0600 |
commit | 20c1a6d14c9447b87394221fa42a0e85aca1b060 (patch) | |
tree | 1356fa87585bc2d4cee97cbb83a40f62c10cdd12 /main.c | |
parent | 8fdd43b49af70fd8712116b31010c032301a96b4 (diff) | |
download | kernelhello-20c1a6d14c9447b87394221fa42a0e85aca1b060.tar.gz kernelhello-20c1a6d14c9447b87394221fa42a0e85aca1b060.tar.bz2 kernelhello-20c1a6d14c9447b87394221fa42a0e85aca1b060.zip |
use a linked list to hold data, utilize Linux api
Diffstat (limited to 'main.c')
-rw-r--r-- | main.c | 202 |
1 files changed, 177 insertions, 25 deletions
@@ -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"); - |