summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKyle K <kylek389@gmail.com>2013-01-24 17:56:32 -0600
committerKyle Kaminski <kyle@kkaminsk.com>2013-01-24 17:56:32 -0600
commit20c1a6d14c9447b87394221fa42a0e85aca1b060 (patch)
tree1356fa87585bc2d4cee97cbb83a40f62c10cdd12
parent8fdd43b49af70fd8712116b31010c032301a96b4 (diff)
downloadkernelhello-20c1a6d14c9447b87394221fa42a0e85aca1b060.tar.gz
kernelhello-20c1a6d14c9447b87394221fa42a0e85aca1b060.tar.bz2
kernelhello-20c1a6d14c9447b87394221fa42a0e85aca1b060.zip
use a linked list to hold data, utilize Linux api
-rw-r--r--.gitignore15
-rw-r--r--debug.c9
-rw-r--r--main.c202
-rw-r--r--main.h30
4 files changed, 225 insertions, 31 deletions
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 <linux/cdev.h>
+#include <linux/list.h>
+
+/*
+ * 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 */