From 20c1a6d14c9447b87394221fa42a0e85aca1b060 Mon Sep 17 00:00:00 2001
From: Kyle K <kylek389@gmail.com>
Date: Thu, 24 Jan 2013 17:56:32 -0600
Subject: use a linked list to hold data, utilize Linux api

---
 main.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 177 insertions(+), 25 deletions(-)

(limited to 'main.c')

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");
-
-- 
cgit v1.2.3