Added buffer length to proc entry.
[cascardo/kernel/samples/02.char/.git] / helloc.c
index 0a7cf40..b0271df 100644 (file)
--- a/helloc.c
+++ b/helloc.c
 #include <linux/module.h>
+/* Needed for struct file_operations and others */
 #include <linux/fs.h>
+/* Needed for struct cdev */
 #include <linux/cdev.h>
+/* Needed for copying to/from user space */
 #include <asm/uaccess.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com>");
 MODULE_DESCRIPTION("A hello world char device");
 MODULE_VERSION("1.0.0");
 
-static char helloc_message[] = "hello, world\n";
+#define BUFFER_SIZE 4096
+#define DEVICE_NUMBER 2
 
+struct message {
+       char *text;
+       size_t len;
+};
+
+/* Message buffer we send to upstream */
+static struct message *hello_message[DEVICE_NUMBER];
+
+static int helloc_open(struct inode *ino, struct file *filp)
+{
+       struct message *msg;
+       printk(KERN_INFO "Opened file with minor %d\n", iminor(ino));
+       if (iminor(ino) >= DEVICE_NUMBER)
+               return -ENODEV;
+       msg = hello_message[iminor(ino)];
+       filp->private_data = msg;
+       if (filp->f_flags & O_TRUNC) {
+               memset(msg->text, 0, msg->len);
+               msg->len = 0;
+               printk(KERN_INFO "File opened with truncate flags\n");
+       }
+       if (filp->f_flags & O_APPEND) {
+               filp->f_pos = msg->len;
+       }
+       return 0;
+}
+
+static int helloc_release(struct inode *ino, struct file *filp)
+{
+       return 0;
+}
+
+/* our read function writes our message to the user buffer */
 static ssize_t helloc_read(struct file *filp, char __user *buf, size_t len,
                           loff_t *pos)
 {
        int r;
-       if (*pos >= sizeof(helloc_message))
+       struct message *msg = filp->private_data;
+       /* do not read pass through the size of the message */
+       if (*pos >= msg->len)
+       /* return end of file */
                return 0;
-       if (len > sizeof(helloc_message) - *pos)
-               len = sizeof(helloc_message) - *pos;
-       r = copy_to_user(buf, helloc_message + *pos, len);
+       /* if len is bigger than the rest of the message, clamp it */
+       if (len > msg->len - *pos)
+               len = msg->len - *pos;
+       /* copy message to user space and return error if it fails */
+       r = copy_to_user(buf, msg->text + *pos, len);
        if (r)
                return -EFAULT;
+       /* update the file position */
        *pos += len;
        return len;
 }
 
+static ssize_t helloc_write(struct file *filp, const char __user *buf,
+                           size_t len, loff_t *pos)
+{
+       int r;
+       struct message *msg = filp->private_data;
+       /* do not read pass through the size of the message */
+       if (*pos >= BUFFER_SIZE)
+       /* return end of file */
+               return 0;
+       /* if len is bigger than the rest of the message, clamp it */
+       if (len > BUFFER_SIZE - *pos)
+               len = BUFFER_SIZE - *pos;
+
+       /* copy message to user space and return error if it fails */
+       r = copy_from_user(msg->text + *pos, buf, len);
+       if (r) {
+               if (*pos + len > msg->len)
+                       memset(msg->text + msg->len, 0, *pos + len - msg->len);
+               return -EFAULT;
+       }
+       /* update the file position */
+       *pos += len;
+       if (msg->len < *pos) {
+               printk(KERN_DEBUG "Updating size from %d to %lld\n", msg->len, *pos);
+               msg->len = *pos;
+       }
+
+       return len;
+}
+
+/* we only implement read */
 static struct file_operations helloc_fops = {
        .owner = THIS_MODULE,
+       .open = helloc_open,
+       .release = helloc_release,
        .read = helloc_read,
+       .write = helloc_write,
 };
 
+static int hello_len_show(struct seq_file *seq, void *data)
+{
+       struct message *msg = seq->private;
+       seq_printf(seq, "%d\n", msg->len);
+       return 0;
+}
+
+static int hello_proc_open(struct inode *ino, struct file *filp)
+{
+       return single_open(filp, hello_len_show, hello_message[0]);
+}
+
+static struct file_operations hello_proc_fops = {
+       .owner = THIS_MODULE,
+       .open = hello_proc_open,
+       .llseek = seq_lseek,
+       .read = seq_read,
+       .release = seq_release,
+};
+
+
+/* the device number and the char device struct */
 static dev_t dev;
 static struct cdev *cdev;
 
+static void helloc_free(void)
+{
+       int i;
+       for (i = 0; i < DEVICE_NUMBER; i++) {
+               if (hello_message[i])
+                       kfree(hello_message[i]->text);
+               kfree(hello_message[i]);
+       }
+}
+
+static int __init helloc_alloc(void)
+{
+       int i;
+       for (i = 0; i < DEVICE_NUMBER; i++) {
+               hello_message[i] = kzalloc(sizeof(struct message), GFP_KERNEL);
+               if (!hello_message[i])
+                       goto out;
+               hello_message[i]->text = kzalloc(BUFFER_SIZE, GFP_KERNEL);
+               if (!hello_message[i]->text)
+                       goto out;
+       }
+       return 0;
+out:
+       for (; i >= 0; i--) {
+               if (hello_message[i])
+                       kfree(hello_message[i]->text);
+               kfree(hello_message[i]);
+       }
+       return -ENOMEM;
+}
+
 static int __init helloc_init(void)
 {
+       struct proc_dir_entry *hello_pde;
        int r;
-       r = alloc_chrdev_region(&dev, 0, 1, "helloc");
+       r = helloc_alloc();
+       if (r)
+               goto out_alloc2;
+       /* allocate any major number with only one minor */
+       r = alloc_chrdev_region(&dev, 0, DEVICE_NUMBER, "helloc");
        if (r)
                goto out_region;
        r = -ENOMEM;
+       /* print the major number allocated so we can create our device node */
        printk(KERN_INFO "Allocated major number %d\n", MAJOR(dev));
+       /* allocate the character device struct */
        cdev = cdev_alloc();
        if (!cdev)
                goto out_alloc;
+       /* set the module owner and the file operations of our chardev */
        cdev->owner = THIS_MODULE;
        cdev->ops = &helloc_fops;
-       r = cdev_add(cdev, dev, 1);
+       /* register the chardev to the system */
+       r = cdev_add(cdev, dev, DEVICE_NUMBER);
        if (r)
                goto out_add;
+       hello_pde = proc_create("hello_len", 0666, NULL, &hello_proc_fops);
+       if (!hello_pde)
+               goto out_pde;
        return 0;
+out_pde:
+       cdev_del(cdev);
 out_add:
+       /* release memory allocated to the cdev device */
        kfree(cdev);
 out_alloc:
-       unregister_chrdev_region(dev, 1);
+       /* release the device number allocated */
+       unregister_chrdev_region(dev, DEVICE_NUMBER);
 out_region:
+out_alloc2:
        return r;
 }
 
 static void __exit helloc_exit(void)
 {
+       remove_proc_entry("hello_len", NULL);
+       /* remove the chardev from the system */
        cdev_del(cdev);
-       unregister_chrdev_region(dev, 1);
+       /* release the device number allocated */
+       unregister_chrdev_region(dev, DEVICE_NUMBER);
+       helloc_free();
 }
 
 module_init(helloc_init);