diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d9aae3b..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "files.associations": { - "array": "cpp", - "atomic": "cpp", - "bit": "cpp", - "*.tcc": "cpp", - "cctype": "cpp", - "chrono": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "compare": "cpp", - "concepts": "cpp", - "condition_variable": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "string": "cpp", - "unordered_map": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "functional": "cpp", - "iterator": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "random": "cpp", - "ratio": "cpp", - "string_view": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "utility": "cpp", - "initializer_list": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "mutex": "cpp", - "new": "cpp", - "numbers": "cpp", - "ostream": "cpp", - "semaphore": "cpp", - "stdexcept": "cpp", - "stop_token": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "typeinfo": "cpp", - "syncstream": "cpp", - "csignal": "cpp", - "sstream": "cpp", - "fstream": "cpp" - }, - "C_Cpp.default.compilerPath": "/usr/bin/clang++" -} \ No newline at end of file diff --git a/lab6/.vscode/c_cpp_properties.json b/lab6/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..7a9b4b6 --- /dev/null +++ b/lab6/.vscode/c_cpp_properties.json @@ -0,0 +1,24 @@ +{ + "configurations": [ + { + "name": "Linux Kernel Module", + "includePath": [ + "/usr/lib/modules/6.14.3-arch1-1/build/include", + "/usr/lib/modules/6.14.3-arch1-1/build/arch/x86/include", + "/usr/lib/modules/6.14.3-arch1-1/build/include/uapi", + "/usr/lib/modules/6.14.3-arch1-1/build/include/generated/uapi", + "/usr/lib/modules/6.14.3-arch1-1/build/include/generated", + "/usr/lib/modules/6.14.3-arch1-1/build/arch/x86/include/generated" + ], + "defines": [ + "__KERNEL__", + "MODULE" + ], + "compilerPath": "/usr/bin/g++", + "cStandard": "c11", + "cppStandard": "c++20", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 + } \ No newline at end of file diff --git a/lab6/.vscode/settings.json b/lab6/.vscode/settings.json new file mode 100644 index 0000000..7d8d6ff --- /dev/null +++ b/lab6/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.associations": { + "cdev.h": "c", + "module.h": "c", + "types.h": "c", + "init.h": "c" + }, + "C_Cpp.default.compilerPath": "/usr/bin/clang++" +} \ No newline at end of file diff --git a/lab6/Makefile b/lab6/Makefile new file mode 100644 index 0000000..315c2ac --- /dev/null +++ b/lab6/Makefile @@ -0,0 +1,13 @@ +obj-m += mypipe.o +OUT_DIR := build + +.PHONY kern_mod writer reader clean +all: kern_mod writer reader + +kern_mod: mypipe.c + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + rm -rf $(OUT_DIR) + diff --git a/lab6/mypipe.c b/lab6/mypipe.c new file mode 100644 index 0000000..6a77c4d --- /dev/null +++ b/lab6/mypipe.c @@ -0,0 +1,230 @@ +#include +#include +#include +#include + +// Ref: +// https://linux-kernel-labs.github.io/refs/heads/master/labs/device_drivers.html +// https://olegkutkov.me/2018/03/14/simple-linux-character-device-driver/ + +#define MYPIPE_BUFFER_SIZE 4096 +#define MYPIPE_DEVICE_NAME "wendy" + +static dev_t dev_major = 0; + +enum MyPipeMinors { + MYPIPE_MINOR_IN = 0, + MYPIPE_MINOR_OUT, + MYPIPE_MINOR_COUNT, +}; + +struct mypipe_data { + struct cdev cdev; + u8 buffer[MYPIPE_BUFFER_SIZE]; + size_t write_pos; + size_t read_pos; + size_t data_len; +}; + +// This could be an array if we want multiple cdev +static struct mypipe_data pdata; + +static struct class *mypipedev_class = NULL; + +// Protects the buffer +static DEFINE_MUTEX(mypipe_lock); +// These are two wait_queue_head_t for storing tasks that are waiting for +// writing/ reading +static DECLARE_WAIT_QUEUE_HEAD(read_queue); +static DECLARE_WAIT_QUEUE_HEAD(write_queue); + +static int mypipe_open(struct inode *inode, struct file *file) { + printk("Mypipe opened."); + return 0; +} + +static int mypipe_release(struct inode *inode, struct file *file) { + printk("Mypipe released."); + return 0; +} + +static int mypipe_read(struct file *filep, char __user *buf, size_t size, + loff_t *offset) { + ssize_t ret = 0; + + if (iminor(file_iniode(filep)) != MYPIPE_MINOR_OUT) { + return -EINVAL; + } + + // Acquire the lock. If not acquired and interrupted by signal, let kernel + // to try restart the syscall + if (mutex_lock_interruptible(&mypipe_lock)) { + return -ERESTARTSYS; + } + + // We currently have no data, but we can wait for possible new to write in + while (pdata.data_len == 0) { + mutex_unlock(&mypipe_lock); + if (filep->f_flags & O_NONBLOCK) { + return -EAGAIN; + } + if (wait_event_interruptible(read_queue, pdata.data_len > 0)) { + return -ERESTARTSYS; + } + // Woken, relock for checking the condition + if (mutex_lock_interruptible(&mypipe_lock)) { + return -ERESTARTSYS; + } + } + + // data_len > 0 + size_t to_read = min(size, pdata.data_len); + size_t until_end = min(to_read, MYPIPE_BUFFER_SIZE - pdata.read_pos); + if (copy_to_user(buf, pdata.buffer + pdata.read_pos, until_end)) { + ret = -EFAULT; + goto out; + } + + if (to_read > until_end) { + if (copy_to_user(buf + until_end, pdata.buffer, to_read - until_end)) { + ret = -EFAULT; + goto out; + } + } + pdata.read_pos = (pdata.read_pos + to_read) % MYPIPE_BUFFER_SIZE; + pdata.data_len -= to_read; + ret = to_read; + + wake_up_interruptible(&write_queue); + +out: + mutex_unlock(&mypipe_lock); + return ret; +} + +static int mypipe_write(struct file *filep, char __user *buf, size_t size, + loff_t *offset) { + // This is very similar to read(), just write instead of read + + ssize_t ret = 0; + + if (iminor(file_indo(filep)) != MYPIPE_MINOR_IN) { + return -EINVAL; + } + + if (mutex_lock_interruptible(&mypipe_lock)) { + return -ERESTARTSYS; + } + + while (pdata.data_len == MYPIPE_BUFFER_SIZE) { + mutex_unlock(&mypipe_lock); + if (filep->f_flags & O_NONBLOCK) { + return -EAGAIN; + } + if (wait_event_interruptible(write_queue, + pdata.data_len < MYPIPE_BUFFER_SIZE)) { + return -ERESTARTSYS; + } + if (mutex_lock_interruptible(&mypipe_lock)) { + return -ERESTARTSYS; + } + } + + size_t space_left = MYPIPE_BUFFER_SIZE - pdata.data_len; + size_t to_write = min(size, space_left); + size_t until_end = min(to_write, MYPIPE_BUFFER_SIZE - pdata.write_pos); + + if (copy_from_user(pdata.buffer + pdata.write_pos, buf, until_end)) { + ret = -EFAULT; + goto out; + } + + if (to_write > until_end) { + if (copy_from_user(pdata.buffer, buf + until_end, + to_write - until_end)) { + ret = -EFAULT; + goto out; + } + } + + pdata.write_pos = (pdata.write_pos + to_write) % MYPIPE_BUFFER_SIZE; + pdata.data_len += to_write; + ret = to_write; + + wake_up_interruptible(&read_queue); + +out: + mutex_unlock(&mypipe_lock); + return ret; +} + +const struct file_operations mypipe_fops = {.owner = THIS_MODULE, + .open = mypipe_open, + .read = mypipe_read, + .write = mypipe_write, + .release = mypipe_release}; + +static int __init mypipe_init(void) { + int ret; + dev_t dev; + + // Acquire a region of major, minor for us to use. The name appears in + // /proc/devices (and potentially /sys ?). + ret = alloc_chrdev_region(&dev, 0, MYPIPE_MINOR_COUNT, MYPIPE_DEVICE_NAME); + if (ret) { + return ret; + } + + dev_major = MAJOR(dev); + + // Create the class in sysfs. The name appears in /sys/class/ + mypipedev_class = class_create(MYPIPE_DEVICE_NAME); + if (IS_ERR(mypipedev_class)) { + ret = PTR_ERR(mypipedev_class); + goto del_cdev; + } + + // Create a cdev in our pdata + cdev_init(&pdata.cdev, &mypipe_fops); + pdata.cdev.owner = THIS_MODULE; + + // Register this newly created cdev to kernel. We only have one dev, so we + // can MKDEV(dev_major, 0). + + // Also, we want to devices that one handles write and one read, so the last + // arg is not 1, but 2 (In this case, MYPIPE_MINOR_COUNT) + ret = cdev_add(&pdata.cdev, MKDEV(dev_major, 0), MYPIPE_MINOR_COUNT); + if (ret) + goto unregister; + + // Create device node /dev/mypipe_in and /dev/mypipe_out + device_create(mypipedev_class, NULL, MKDEV(dev_major, MYPIPE_MINOR_IN), + NULL, "%s_in", MYPIPE_DEVICE_NAME); + device_create(mypipedev_class, NULL, MKDEV(dev_major, MYPIPE_MINOR_OUT), + NULL, "%s_out", MYPIPE_DEVICE_NAME); + + printk(KERN_INFO "Wendy: module loaded\n"); + return 0; + +del_cdev: + cdev_del(&pdata.cdev); +unregister: + unregister_chrdev_region(MKDEV(dev_major, 0), MYPIPE_MINOR_COUNT); + return ret; +} + +static void __exit mypipe_destroy(void) { + // This is basically the reverse operation of mypipe_init + device_destroy(mypipedev_class, MKDEV(dev_major, MYPIPE_MINOR_IN)); + device_destroy(mypipedev_class, MKDEV(dev_major, MYPIPE_MINOR_OUT)); + class_unregister(mypipedev_class); + class_destory(mypipedev_class); + cdev_del(&pdata.cdev); + unregister_chrdev_region(MKDEV(dev_major, 0), MYPIPE_MINOR_COUNT); + printk(KERN_INFO "Wendy: module unloaded"); +} + +module_init(mypipe_init); +module_exit(mypipe_destroy); + +MODULE_LICENSE("GPL"); \ No newline at end of file