X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fkernel%2Fsamples%2F02.char%2F.git;a=blobdiff_plain;f=helloc.c;h=b0271df7e9617bde9988b002aef81411e1200f7e;hp=1a2c23846f00b49ca4d597694f46b077d6531358;hb=67446b4c4a1ee4df194203d80ed3077d08c52c88;hpb=66945cb744a5259446eb796a007121125c9e4701 diff --git a/helloc.c b/helloc.c index 1a2c238..b0271df 100644 --- a/helloc.c +++ b/helloc.c @@ -1,67 +1,221 @@ #include +/* Needed for struct file_operations and others */ #include +/* Needed for struct cdev */ #include +/* Needed for copying to/from user space */ #include +#include +#include +#include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Thadeu Lima de Souza Cascardo "); 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: - kfree(&cdev->kobj); + /* 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);