diff --git a/tools/labs/skels/device_drivers/extra/char-driver-lin/Kbuild b/tools/labs/skels/device_drivers/extra/char-driver-lin/Kbuild new file mode 100644 index 00000000000000..9399052a035415 --- /dev/null +++ b/tools/labs/skels/device_drivers/extra/char-driver-lin/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = modul.o diff --git a/tools/labs/skels/device_drivers/extra/char-driver-lin/makenode b/tools/labs/skels/device_drivers/extra/char-driver-lin/makenode new file mode 100644 index 00000000000000..e66abd3c6a1cd0 --- /dev/null +++ b/tools/labs/skels/device_drivers/extra/char-driver-lin/makenode @@ -0,0 +1,9 @@ +#!/bin/sh +device="my_device" +major=42 + + +rm -f /dev/${device} +mknod /dev/${device} c $major 0 + + diff --git a/tools/labs/skels/device_drivers/extra/char-driver-lin/modul.c b/tools/labs/skels/device_drivers/extra/char-driver-lin/modul.c new file mode 100644 index 00000000000000..b92e74fabfded6 --- /dev/null +++ b/tools/labs/skels/device_drivers/extra/char-driver-lin/modul.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include + +#define MY_MAJOR 42 +#define MY_MAX_MINORS 2 +/* #define IOCTL_IN _IOC(_IOC_WRITE, 'k', 1, sizeof(my_ioctl_data)) */ +#define MY_IOCTL_IN _IOC(_IOC_WRITE, 'k', 1, 0) + +struct my_device_data { + struct cdev cdev; + /* my data starts here */ +}devs[MY_MAX_MINORS]; + +MODULE_DESCRIPTION("My kernel module"); +MODULE_AUTHOR("Me"); +MODULE_LICENSE("GPL"); + +static int my_open(struct inode *inode, struct file *file) { + struct my_device_data *my_data = + container_of(inode->i_cdev, struct my_device_data, cdev); + + printk( KERN_DEBUG "[my_open]\n" ); + /* validate access to device */ + file->private_data = my_data; + /* initialize device */ + + return 0; +} + +static int my_close(struct inode *inode, struct file *file) { + printk( KERN_DEBUG "[my_close]\n" ); + /* deinitialize device */ + return 0; +} + +static int my_read(struct file *file, char __user *user_buffer, size_t size, loff_t *offset) { + struct my_device_data *my_data = + (struct my_device_data*) file->private_data; + int sizeRead = 0; + + printk( KERN_DEBUG "[my_read]\n" ); + /* read data from device in my_data->buffer */ + /* if(copy_to_user(user_buffer, my_data->buffer, my_data->size)) + return -EFAULT; */ + + return sizeRead; +} + +static int my_write(struct file *file, const char __user *user_buffer, size_t size, loff_t *offset) { + struct my_device_data *my_data = + (struct my_device_data*) file->private_data; + int sizeWritten = 0; + + printk( KERN_DEBUG "[my_write]\n" ); + /* copy_from_user */ + /* write data to device from my_data->buffer */ + sizeWritten = size; //only if sizeWritten == size ! + + return sizeWritten; +} + +static long my_ioctl (struct file *file, unsigned int cmd, unsigned long arg) { + struct my_device_data *my_data = + (struct my_device_data*) file->private_data; + /* my_ioctl_data mid; */ + + printk( KERN_DEBUG "[my_ioctl]\n" ); + switch(cmd) { + case MY_IOCTL_IN: + /* if( copy_from_user(&mid, (my_ioctl_data *) arg, sizeof(my_ioctl_data)) ) + return -EFAULT; */ + + /* process data and execute command */ + break; + default: + return -ENOTTY; + } + return 0; +} + + +struct file_operations my_fops = { + .owner = THIS_MODULE, + .open = my_open, + .read = my_read, + .write = my_write, + .release = my_close, + .unlocked_ioctl = my_ioctl +}; + +int init_module(void) { + int i, err; + + printk( KERN_DEBUG "[init_module]\n" ); + err = register_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS,"my_device_driver"); + if (err != 0) { + /* report error */ + return err; + } + + for(i = 0; i < MY_MAX_MINORS; i++) { + /* initialize devs[i] fields */ + cdev_init(&devs[i].cdev, &my_fops); + cdev_add(&devs[i].cdev, MKDEV(MY_MAJOR, i), 1); + } + + return 0; +} + +void cleanup_module(void) { + int i; + + printk( KERN_DEBUG "[cleanup_module]\n" ); + for(i = 0; i < MY_MAX_MINORS; i++) { + /* release devs[i] fields */ + cdev_del(&devs[i].cdev); + } + unregister_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS); +} + diff --git a/tools/labs/skels/device_drivers/include/so2_cdev.h b/tools/labs/skels/device_drivers/include/so2_cdev.h new file mode 100644 index 00000000000000..a88a8809dc5ab0 --- /dev/null +++ b/tools/labs/skels/device_drivers/include/so2_cdev.h @@ -0,0 +1,14 @@ +#ifndef __PSOCDEV_H__ +#define __PSOCDEV_H__ 1 + +#include + +#define BUFFER_SIZE 256 + +#define MY_IOCTL_PRINT _IOC(_IOC_NONE, 'k', 1, 0) +#define MY_IOCTL_SET_BUFFER _IOC(_IOC_WRITE, 'k', 2, BUFFER_SIZE) +#define MY_IOCTL_GET_BUFFER _IOC(_IOC_READ, 'k', 3, BUFFER_SIZE) +#define MY_IOCTL_DOWN _IOC(_IOC_NONE, 'k', 4, 0) +#define MY_IOCTL_UP _IOC(_IOC_NONE, 'k', 5, 0) + +#endif diff --git a/tools/labs/skels/device_drivers/kernel/Kbuild b/tools/labs/skels/device_drivers/kernel/Kbuild new file mode 100644 index 00000000000000..462ca0f2a4d467 --- /dev/null +++ b/tools/labs/skels/device_drivers/kernel/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = so2_cdev.o diff --git a/tools/labs/skels/device_drivers/kernel/so2_cdev.c b/tools/labs/skels/device_drivers/kernel/so2_cdev.c new file mode 100644 index 00000000000000..9ffb9a99a32628 --- /dev/null +++ b/tools/labs/skels/device_drivers/kernel/so2_cdev.c @@ -0,0 +1,246 @@ +/* + * Character device drivers lab + * + * All tasks + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../include/so2_cdev.h" + +MODULE_DESCRIPTION("SO2 character device"); +MODULE_AUTHOR("SO2"); +MODULE_LICENSE("GPL"); + +#define LOG_LEVEL KERN_INFO + +#define MY_MAJOR 42 +#define MY_MINOR 0 +#define NUM_MINORS 1 +#define MODULE_NAME "so2_cdev" +#define MESSAGE "hello\n" +#define IOCTL_MESSAGE "Hello ioctl" + +#ifndef BUFSIZ +#define BUFSIZ 4096 +#endif + +size_t my_min(size_t a, size_t b) { + if (a < b) { + return a; + } + + return b; +} + +struct so2_device_data { + /* TODO 2: add cdev member */ + struct cdev cdev; + /* TODO 4: add buffer with BUFSIZ elements */ + char buf[BUFSIZ]; + size_t size; + /* TODO 7: extra members for home */ + wait_queue_head_t wq; + int flag; + /* TODO 3: add atomic_t access variable to keep track if file is opened */ + atomic_t is_opened; +}; + +struct so2_device_data devs[NUM_MINORS]; + +static int so2_cdev_open(struct inode *inode, struct file *file) +{ + /* TODO 2: print message when the device file is open. */ + pr_info("Device opened!\n"); + + /* TODO 3: inode->i_cdev contains our cdev struct, use container_of to obtain a pointer to so2_device_data */ + struct so2_device_data *data = + container_of(inode->i_cdev, struct so2_device_data, cdev); + file->private_data = data; + + /* TODO 3: return immediately if access is != 0, use atomic_cmpxchg */ + // if (atomic_cmpxchg(&data->is_opened, 0, 1) != 0) + // return -EBUSY; + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1000); + + return 0; +} + +static int +so2_cdev_release(struct inode *inode, struct file *file) +{ + /* TODO 2: print message when the device file is closed. */ + pr_info("Device closed!\n"); + +#ifndef EXTRA + struct so2_device_data *data = + (struct so2_device_data *) file->private_data; + + /* TODO 3: reset access variable to 0, use atomic_set */ + atomic_set(&data->is_opened, 0); +#endif + return 0; +} + +static ssize_t +so2_cdev_read(struct file *file, + char __user *user_buffer, + size_t size, loff_t *offset) +{ + struct so2_device_data *data = + (struct so2_device_data *) file->private_data; + size_t to_read; + +#ifdef EXTRA + /* TODO 7: extra tasks for home */ +#endif + + /* TODO 4: Copy data->buffer to user_buffer, use copy_to_user */ + to_read = my_min(data->size - *offset, size); + + if (to_read <= 0) + return 0; + + if (copy_to_user(user_buffer, data->buf + *offset, to_read)) + return -EFAULT; + + *offset += to_read; + + return to_read; +} + +static ssize_t +so2_cdev_write(struct file *file, + const char __user *user_buffer, + size_t size, loff_t *offset) +{ + struct so2_device_data *data = + (struct so2_device_data *) file->private_data; + + + /* TODO 5: copy user_buffer to data->buffer, use copy_from_user */ + size = my_min(BUFSIZ - *offset, size); + + if (copy_from_user(data->buf + *offset, user_buffer, size)) { + return -EFAULT; + } + + data->size = *offset + size; + *offset += size; + /* TODO 7: extra tasks for home */ + + return size; +} + +static long +so2_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct so2_device_data *data = + (struct so2_device_data *) file->private_data; + int ret = 0; + int remains; + + switch (cmd) { + /* TODO 6: if cmd = MY_IOCTL_PRINT, display IOCTL_MESSAGE */ + case MY_IOCTL_PRINT: + pr_info("%s\n", IOCTL_MESSAGE); + break; + /* TODO 7: extra tasks, for home */ + case MY_IOCTL_SET_BUFFER: + pr_info("Extra write\n"); + memset(data->buf, 0, BUFSIZ); + copy_from_user(data->buf, (char __user *)arg, strlen((char __user *)arg)); + data->size = strlen((char __user *)arg); + break; + case MY_IOCTL_GET_BUFFER: + pr_info("Extra read\n"); + copy_to_user((char __user *)arg, data->buf, data->size); + break; + case MY_IOCTL_DOWN: + data->flag = 0; + ret = wait_event_interruptible(data->wq, data->flag != 0); + break; + case MY_IOCTL_UP: + data->flag = 1; + wake_up_interruptible(&data->wq); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct file_operations so2_fops = { + .owner = THIS_MODULE, +/* TODO 2: add open and release functions */ + .open = so2_cdev_open, + .release = so2_cdev_release, +/* TODO 4: add read function */ + .read = so2_cdev_read, +/* TODO 5: add write function */ + .write = so2_cdev_write, +/* TODO 6: add ioctl function */ + .unlocked_ioctl = so2_cdev_ioctl +}; + +static int so2_cdev_init(void) +{ + int err; + int i; + + /* TODO 1: register char device region for MY_MAJOR and NUM_MINORS starting at MY_MINOR */ + err = register_chrdev_region(MKDEV(MY_MAJOR, MY_MINOR), NUM_MINORS, + "so2_cdev"); + if (err != 0) { + return err; + } + + pr_info("Hello!\n"); + + + for (i = 0; i < NUM_MINORS; i++) { +#ifdef EXTRA + /* TODO 7: extra tasks, for home */ + init_waitqueue_head(&devs[i].wq); + devs[i].flag = 0; +#else + /*TODO 4: initialize buffer with MESSAGE string */ + memcpy(devs[i].buf, MESSAGE, sizeof(MESSAGE)); + devs[i].size = sizeof(MESSAGE); +#endif + /* TODO 3: set access variable to 0, use atomic_set */ + atomic_set(&devs[i].is_opened, 0); + /* TODO 2: init and add cdev to kernel core */ + cdev_init(&devs[i].cdev, &so2_fops); + cdev_add(&devs[i].cdev, MKDEV(MY_MAJOR, i), 1); + } + + return 0; +} + +static void so2_cdev_exit(void) +{ + int i; + + for (i = 0; i < NUM_MINORS; i++) { + /* TODO 2: delete cdev from kernel core */ + cdev_del(&devs[i].cdev); + } + + /* TODO 1: unregister char device region, for MY_MAJOR and NUM_MINORS starting at MY_MINOR */ + unregister_chrdev_region(MKDEV(MY_MAJOR, MY_MINOR), NUM_MINORS); + pr_info("Goodbye!\n"); +} + +module_init(so2_cdev_init); +module_exit(so2_cdev_exit); diff --git a/tools/labs/skels/device_drivers/user/Makefile b/tools/labs/skels/device_drivers/user/Makefile new file mode 100644 index 00000000000000..8b4ace8e72fe9f --- /dev/null +++ b/tools/labs/skels/device_drivers/user/Makefile @@ -0,0 +1,7 @@ +all: so2_cdev_test.c + gcc -m32 -static -o so2_cdev_test so2_cdev_test.c + +.PHONY: clean + +clean: + -rm -f *~ *.o so2_cdev_test diff --git a/tools/labs/skels/device_drivers/user/so2_cdev_test b/tools/labs/skels/device_drivers/user/so2_cdev_test new file mode 100755 index 00000000000000..bc6e9f0c8b0664 Binary files /dev/null and b/tools/labs/skels/device_drivers/user/so2_cdev_test differ diff --git a/tools/labs/skels/device_drivers/user/so2_cdev_test.c b/tools/labs/skels/device_drivers/user/so2_cdev_test.c new file mode 100644 index 00000000000000..24a1f39d6898ea --- /dev/null +++ b/tools/labs/skels/device_drivers/user/so2_cdev_test.c @@ -0,0 +1,125 @@ +/* + * SO2 Lab - Linux device drivers (#4) + * User-space test file + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../include/so2_cdev.h" + +#define DEVICE_PATH "/dev/so2_cdev" + +/* + * prints error message and exits + */ + +static void error(const char *message) +{ + perror(message); + exit(EXIT_FAILURE); +} + +/* + * print use case + */ + +static void usage(const char *argv0) +{ + printf("Usage: %s \n options:\n" + "\tp - print\n" + "\ts string - set buffer\n" + "\tg - get buffer\n" + "\td - down\n" + "\tu - up\n" + "\tn - open with O_NONBLOCK and read data\n", argv0); + exit(EXIT_FAILURE); +} + +/* + * Sample run: + * ./so2_cdev_test p ; print ioctl message + * ./so2_cdev_test d ; wait on wait_queue + * ./so2_cdev_test u ; wait on wait_queue + */ + +int main(int argc, char **argv) +{ + int fd; + char buffer[BUFFER_SIZE]; + + if (argc < 2) + usage(argv[0]); + + if (strlen(argv[1]) != 1) + usage(argv[0]); + + fd = open(DEVICE_PATH, O_RDONLY); + if (fd < 0) { + perror("open"); + exit(EXIT_FAILURE); + } + + switch (argv[1][0]) { + case 'p': /* print */ + if (ioctl(fd, MY_IOCTL_PRINT, 0) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + + break; + case 's': /* set buffer */ + if (argc < 3) + usage(argv[0]); + memset(buffer, 0, BUFFER_SIZE); + strncpy(buffer, argv[2], BUFFER_SIZE); + if (ioctl(fd, MY_IOCTL_SET_BUFFER, buffer) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + break; + case 'g': /* get buffer */ + if (ioctl(fd, MY_IOCTL_GET_BUFFER, buffer) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + buffer[BUFFER_SIZE-1] = 0; + printf("IOCTL buffer contains %s\n", buffer); + break; + case 'd': /* down */ + if (ioctl(fd, MY_IOCTL_DOWN, 0) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + break; + case 'u': /* up */ + if (ioctl(fd, MY_IOCTL_UP, 0) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + break; + case 'n': + if (fcntl(fd, F_SETFL, O_RDONLY | O_NONBLOCK) < 0) { + perror("fcntl"); + exit(EXIT_FAILURE); + } + + if (read(fd, buffer, BUFFER_SIZE) < 0) { + perror("read"); + exit(EXIT_FAILURE); + } + buffer[BUFFER_SIZE-1] = 0; + printf("Device buffer contains %s\n", buffer); + break; + default: + error("Wrong parameter"); + } + + close(fd); + + return 0; +}