#include #include #include #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Example Author"); MODULE_DESCRIPTION("A simple char device driver with 4 minors"); MODULE_VERSION("1.0"); #define DEVICE_NAME "mychardev" #define NUM_MINORS 4 #define BUFFER_SIZE 1024 static int mychardev_open(struct inode *inode, struct file *file); static int mychardev_release(struct inode *inode, struct file *file); static ssize_t mychardev_read(struct file *file, char __user *buf, size_t len, loff_t *off); static ssize_t mychardev_write(struct file *file, const char __user *buf, size_t len, loff_t *off); /* File operations structure */ static struct file_operations fops = { .owner = THIS_MODULE, .open = mychardev_open, .release = mychardev_release, .read = mychardev_read, .write = mychardev_write, }; /* Our device data for each minor */ struct mychardev_data { char buffer[BUFFER_SIZE]; size_t data_size; /* Amount of data stored in buffer */ }; static struct cdev my_cdev[NUM_MINORS]; static struct mychardev_data dev_data[NUM_MINORS]; static dev_t dev_num; static struct class *mychardev_class; static int mychardev_open(struct inode *inode, struct file *file) { int minor = iminor(inode); if (minor < 0 || minor >= NUM_MINORS) { pr_err("mychardev: invalid minor %d\n", minor); return -ENODEV; } /* Associate the device-specific data with the file pointer */ file->private_data = &dev_data[minor]; pr_info("mychardev: open called on minor %d\n", minor); return 0; } static int mychardev_release(struct inode *inode, struct file *file) { int minor = iminor(inode); pr_info("mychardev: release called on minor %d\n", minor); return 0; } static ssize_t mychardev_read(struct file *file, char __user *buf, size_t len, loff_t *off) { struct mychardev_data *data = file->private_data; int minor = iminor(file_inode(file)); ssize_t to_read; /* If offset is beyond our stored data, return 0 */ if (*off >= data->data_size) return 0; to_read = min((size_t)(data->data_size - *off), len); if (copy_to_user(buf, data->buffer + *off, to_read)) { pr_err("mychardev: Failed to copy data to user\n"); return -EFAULT; } *off += to_read; pr_info("mychardev: read called on minor %d, read %zu bytes\n", minor, to_read); return to_read; } static ssize_t mychardev_write(struct file *file, const char __user *buf, size_t len, loff_t *off) { struct mychardev_data *data = file->private_data; int minor = iminor(file_inode(file)); ssize_t to_write; /* Limit to our buffer size */ to_write = min((size_t)(BUFFER_SIZE - *off), len); if (to_write == 0) return -ENOSPC; /* No space left in our buffer */ if (copy_from_user(data->buffer + *off, buf, to_write)) { pr_err("mychardev: Failed to copy data from user\n"); return -EFAULT; } *off += to_write; data->data_size = max(data->data_size, (size_t)(*off)); /* Print what we have written for demonstration */ pr_info("mychardev: write called on minor %d, wrote %zu bytes\n", minor, to_write); pr_info("mychardev: user wrote: \"%.*s\"\n", (int)to_write, data->buffer + (*off - to_write)); return to_write; } static int __init mychardev_init(void) { int ret, i; dev_t curr_dev; /* Allocate major and minor numbers for our devices */ ret = alloc_chrdev_region(&dev_num, 0, NUM_MINORS, DEVICE_NAME); if (ret < 0) { pr_err("mychardev: Failed to allocate chrdev region\n"); return ret; } pr_info("mychardev: allocated major=%d for %d minors\n", MAJOR(dev_num), NUM_MINORS); /* Create device class for automatic /dev creation */ mychardev_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(mychardev_class)) { pr_err("mychardev: Failed to create class\n"); unregister_chrdev_region(dev_num, NUM_MINORS); return PTR_ERR(mychardev_class); } /* Initialize and add each cdev, create device nodes */ for (i = 0; i < NUM_MINORS; i++) { cdev_init(&my_cdev[i], &fops); my_cdev[i].owner = THIS_MODULE; curr_dev = MKDEV(MAJOR(dev_num), MINOR(dev_num) + i); ret = cdev_add(&my_cdev[i], curr_dev, 1); if (ret) { pr_err("mychardev: Failed to add cdev for minor %d\n", i); /* Cleanup already added cdevs */ while (--i >= 0) cdev_del(&my_cdev[i]); class_destroy(mychardev_class); unregister_chrdev_region(dev_num, NUM_MINORS); return ret; } /* Create a device node, e.g. /dev/mychardev0, /dev/mychardev1, ... */ device_create(mychardev_class, NULL, curr_dev, NULL, DEVICE_NAME "%d", i); /* Clear device buffer */ memset(dev_data[i].buffer, 0, BUFFER_SIZE); dev_data[i].data_size = 0; } pr_info("mychardev: Module loaded successfully\n"); return 0; } static void __exit mychardev_exit(void) { int i; dev_t curr_dev; /* Remove devices, cdevs, and class */ for (i = 0; i < NUM_MINORS; i++) { curr_dev = MKDEV(MAJOR(dev_num), MINOR(dev_num) + i); device_destroy(mychardev_class, curr_dev); cdev_del(&my_cdev[i]); } class_destroy(mychardev_class); unregister_chrdev_region(dev_num, NUM_MINORS); pr_info("mychardev: Module unloaded\n"); } module_init(mychardev_init); module_exit(mychardev_exit);