diff --git a/04_basic_struct/README.md b/04_basic_struct/README.md new file mode 100644 index 0000000..445d6dc --- /dev/null +++ b/04_basic_struct/README.md @@ -0,0 +1,7 @@ +## Basic structure homework +Implement object with name “MyObject” which is parent of kernel_kobj. +Object should include linked_list structure. +This object should contain sysfs attribute with name “list”. +On read form attribute “list” it should show content of the objects linked list. +On write to attribute “list” it should add new string to the objects linked list. +!! Do not forget properly free all the resources during rmmod. diff --git a/05_timers/README.md b/05_timers/README.md new file mode 100644 index 0000000..55b41f2 --- /dev/null +++ b/05_timers/README.md @@ -0,0 +1,10 @@ +## Homework: Linux Kernel Time Management + +1. Implement program which return absolute time in user space. +Use clock_gettime() from time.h. Try different clock id. +Find the difference. Show possible clock resolution provided by clock_getres(). + +2. Implement kernel module with API in sysfs, which returns relative +time in maximum possible resolution passed since previous read of it. +Implement kernel module with API in sysfs which returns absolute time +of previous reading with maximum resolution like ‘400.123567’ seconds. diff --git a/06_memory/README.md b/06_memory/README.md new file mode 100644 index 0000000..4c22613 --- /dev/null +++ b/06_memory/README.md @@ -0,0 +1,22 @@ +# Memory management + +## Homework +1. Create user-space C or C++ program which tries to allocate buffers + with sizes 2^x for x in range from 0 to maximium possible value + using functions: + **malloc, calloc, alloca, (optional for C++) new **. + Measure time of each allocation/freeing. + 2^x means x power of 2 in this task. +Pull request should contains program source code and program output +in text format. + +2. Create kernel module and test allocation/freeing time for functions: + **kmalloc, kzmalloc, vmalloc, get_free_pages, + (optional and only for drivers integrated to kernel)alloc_bootmem**. + Measure the time of each allocation/freeing except alloc_bootmem. + The results should be presented in text file table with followed columns: + Buffer size, allocation time, freeing time. + Size unit is 1 byte, time unit is 1 ns. + +Pull request should contains source code of developed driver, Makefile +and program output from system log in text format. diff --git a/07_procfs/README.md b/07_procfs/README.md new file mode 100644 index 0000000..7fa3528 --- /dev/null +++ b/07_procfs/README.md @@ -0,0 +1,10 @@ +## Lesson 07 - ProcFS interface and orange pi bootup + +* Update your existing sysfs kernel module with procfs API: +* Create folder in procfs file system; +* Create entry that returns module author name; +* Create entry that returns amount of "store" callback calls; +* Create entry that returns amount of "show" callback calls. +* Build image for orange pi zero +* Attach console output from your development board + diff --git a/08_irq_handling/README.md b/08_irq_handling/README.md new file mode 100644 index 0000000..20360a8 --- /dev/null +++ b/08_irq_handling/README.md @@ -0,0 +1,5 @@ +## Lesson 08 - IRQ handling + +Run polling example on your board. +Modify you driver to enable irq handling instead of polling mechanism + diff --git a/10_chardev/Makefile b/10_chardev/Makefile new file mode 100644 index 0000000..bdc3501 --- /dev/null +++ b/10_chardev/Makefile @@ -0,0 +1,18 @@ +KERNELDIR ?= ../../output/build/linux-5.10.10/ #WARNING relative path + +TARGET = chrdev_mod + +obj-m := $(TARGET).o +CFLAGS_$(TARGET).o := -std=gnu11 -DDEBUG -Wno-declaration-after-statement + +all: clean build ioctrl + +build: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean + rm -rf ioctrl_usrspace + +ioctrl: + $(CROSS_COMPILE)gcc ioctrl_usrspace.c -o ioctrl_usrspace diff --git a/10_chardev/REAMDE.md b/10_chardev/REAMDE.md new file mode 100644 index 0000000..a4fbf99 --- /dev/null +++ b/10_chardev/REAMDE.md @@ -0,0 +1,4 @@ +## Task10: Character device + +Create character device driver and userspace application to control status led and user led using gpio:w... + diff --git a/10_chardev/chrdev_mod.c b/10_chardev/chrdev_mod.c new file mode 100644 index 0000000..6edd2f5 --- /dev/null +++ b/10_chardev/chrdev_mod.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dev.h" + +#define CHRDEV_MODULE_AUTHOR "Sergey Dubyna " + +#define GPIO_NUMBER(port, bit) (32 * (port) + (bit)) + +/* + * https://linux-sunxi.org/Xunlong_Orange_Pi_Zero#LEDs + * Board config for OPI-Zero: + * LED GREEN (PL10): GPIO_11_10 + * LED RED (PA17): GPIO_0_17 + */ + +#define LED_GREEN GPIO_NUMBER(11, 10) +#define LED_RED GPIO_NUMBER(0, 17) + +#define CHRDEV_DEV_NAME "chrdev_led" +#define CHRDEV_CLASS_NAME "chrdev" + +#define BUFFER_SIZE 1024u + +static int data_size; +static u8 data_buffer[BUFFER_SIZE]; + +static int ledg_gpio = -1; +static int ledr_gpio = -1; + +static struct class *pclass; +static struct device *pdev; +static int major; +static int is_open; + +static int led_gpio_init(int gpio, int *led_gpio) +{ + int res; + + res = gpio_direction_output(gpio, 0); + if (res != 0) + return res; + + *led_gpio = gpio; + return 0; +} + +static int chrdev_open(struct inode *pinode, struct file *pfile) +{ + (void)pinode; + (void)pfile; + + if (is_open) { + pr_err("device is already opened\n"); + return -EBUSY; + } + + is_open = 1; + pr_info("device opened\n"); + return 0; +} + +static int chrdev_release(struct inode *pinode, struct file *pfile) +{ + (void)pinode; + (void)pfile; + + is_open = 0; + pr_info("device closed\n"); + return 0; +} + +static ssize_t chrdev_read(struct file *pfile, char *buf, size_t len, loff_t *offset) +{ + int ret; + + pr_info("read from file %s\n", pfile->f_path.dentry->d_iname); + pr_info("read from device %d:%d\n", imajor(pfile->f_inode), iminor(pfile->f_inode)); + + if (len > data_size) + len = data_size; + + ret = copy_to_user(buf, data_buffer, len); + if (ret) { + pr_err("copy_to_user failed: %d\n", ret); + return -EFAULT; + } + + data_size = 0; + pr_info("%zu bytes read\n", len); + return len; +} + +static ssize_t chrdev_write(struct file *pfile, const char *buf, size_t len, loff_t *offset) +{ + int ret; + + pr_info("write to file %s\n", pfile->f_path.dentry->d_iname); + pr_info("write to device %d:%d\n", imajor(pfile->f_inode), iminor(pfile->f_inode)); + + data_size = len; + if (data_size > BUFFER_SIZE) + data_size = BUFFER_SIZE; + + ret = copy_from_user(data_buffer, buf, data_size); + if (ret) { + pr_err("copy_from_user failed: %d\n", ret); + return -EFAULT; + } + + pr_info("%d bytes written\n", data_size); + return data_size; +} + +static long chrdev_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + struct led_control led_cmd = {}; + + (void)f; + + if (_IOC_TYPE(cmd) != CHRDEV_IOCTRL_MAGIC) + return -ENOTTY; + + if (copy_from_user(&led_cmd, (const void *)arg, sizeof(struct led_control))) + return -EFAULT; + + if (!chrdev_led_cmd_is_ok(&led_cmd)) { + BUG(); + return -EINVAL; + } + + const int gpio_num = + (led_cmd.e_type == e_led_type_green) ? ledg_gpio : ledr_gpio; + + switch (cmd) { + case CHRDEV_IOCTRL_SET_VAL: + gpio_set_value(gpio_num, led_cmd.e_state); + break; + + case CHRDEV_IOCTRL_GET_VAL: + led_cmd.e_state = + gpio_get_value(gpio_num) ? e_led_state_on : e_led_state_off; + if (copy_to_user((void *)arg, &led_cmd, sizeof(struct led_control))) + return -EFAULT; + break; + + default: + BUG(); + return -EINVAL; + } + + return 0; +} + +static const struct file_operations chrdev_fops = { + .open = chrdev_open, + .write = chrdev_write, + .release = chrdev_release, + .read = chrdev_read, + .unlocked_ioctl = chrdev_ioctl, +}; + +static int __init chrdev_mod_init(void) +{ + int ret; + + is_open = 0; + data_size = 0; + + major = register_chrdev(0, CHRDEV_DEV_NAME, &chrdev_fops); + if (major < 0) { + pr_err("Error %d. Cannot register char device\n", major); + return major; + } + pr_info("Chrdev registered, major = %d\n", major); + + pclass = class_create(THIS_MODULE, CHRDEV_CLASS_NAME); + if (IS_ERR(pclass)) { + unregister_chrdev(major, CHRDEV_DEV_NAME); + pr_err("Cannot create class\n"); + return PTR_ERR(pclass); + } + pr_info("Class successfully created\n"); + + pdev = device_create(pclass, NULL, MKDEV(major, 0), NULL, CHRDEV_CLASS_NAME "0"); + if (IS_ERR(pdev)) { + class_destroy(pclass); + unregister_chrdev(major, CHRDEV_DEV_NAME); + pr_err("Cannot create device\n"); + return PTR_ERR(pdev); + } + pr_info("Device node created successfully\n"); + + ret = led_gpio_init(LED_GREEN, &ledg_gpio); + if (ret != 0) { + pr_err("Cannot set GPIO%d for output\n", LED_GREEN); + return ret; + } + + ret = led_gpio_init(LED_RED, &ledr_gpio); + if (ret != 0) { + pr_err("Can't set GPIO%d for output\n", LED_RED); + return ret; + } + + gpio_set_value(ledg_gpio, 0); + gpio_set_value(ledr_gpio, 0); + + pr_info("module loaded\n"); + + return 0; +} + +static void __exit chrdev_mod_exit(void) +{ + device_destroy(pclass, MKDEV(major, 0)); + class_destroy(pclass); + unregister_chrdev(major, CHRDEV_DEV_NAME); + + gpio_set_value(ledg_gpio, 0); + gpio_set_value(ledr_gpio, 0); + gpio_free(ledg_gpio); + gpio_free(ledr_gpio); + + pr_info("module exited\n"); +} + +module_init(chrdev_mod_init); +module_exit(chrdev_mod_exit); + +MODULE_AUTHOR(CHRDEV_MODULE_AUTHOR); +MODULE_DESCRIPTION("Char device module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); diff --git a/10_chardev/dev.h b/10_chardev/dev.h new file mode 100644 index 0000000..176a0d8 --- /dev/null +++ b/10_chardev/dev.h @@ -0,0 +1,35 @@ +#ifndef DEV_H_ +#define DEV_H_ + +enum e_led_state +{ + e_led_state_off = 0, + e_led_state_on, +}; + +enum e_led_type +{ + e_led_type_green = 0, + e_led_type_red, +}; + +struct led_control +{ + enum e_led_type e_type; + enum e_led_state e_state; +}; + +#define CHRDEV_IOCTRL_MAGIC 's' +/** Set LED state */ +#define CHRDEV_IOCTRL_SET_VAL _IOW(CHRDEV_IOCTRL_MAGIC, 1, const struct led_control *) +/** Get LED state */ +#define CHRDEV_IOCTRL_GET_VAL _IOR(CHRDEV_IOCTRL_MAGIC, 2, struct led_control *) +#define CHRDEV_PATH "/dev/chrdev0" + +static inline int chrdev_led_cmd_is_ok(const struct led_control *pled_cmd) +{ + return (pled_cmd->e_type == e_led_type_green || pled_cmd->e_type == e_led_type_red) + && (pled_cmd->e_state == e_led_state_off || pled_cmd->e_state == e_led_state_on); +} + +#endif // DEV_H_ diff --git a/10_chardev/ioctrl_usrspace.c b/10_chardev/ioctrl_usrspace.c new file mode 100644 index 0000000..f1deb90 --- /dev/null +++ b/10_chardev/ioctrl_usrspace.c @@ -0,0 +1,78 @@ +/** + * @file ioctrl_usrspace.c + * @brief User space appliation to control builtin Orange PI leds + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dev.h" + +static int cmd_run = 1; + +static int blink(int dfile, enum e_led_type type); +static void signal_handler(int signal); + +int main(void) +{ + int dfd; + + signal(SIGINT, signal_handler); + + if ((dfd = open(CHRDEV_PATH, O_RDWR)) < 0) { + fprintf(stderr, "Error %d while opening the device\n", dfd); + exit(EXIT_FAILURE); + } + + while (cmd_run) { + if (blink(dfd, e_led_type_green)) + break; + + if (blink(dfd, e_led_type_red)) + break; + + usleep(100000); + } + + close(dfd); + signal(SIGINT, SIG_DFL); + + return EXIT_SUCCESS; +} + +static int blink(int dfile, enum e_led_type type) +{ + struct led_control led_ctrl = { .e_type = type }; + + if (ioctl(dfile, CHRDEV_IOCTRL_GET_VAL, &led_ctrl)) { + fprintf(stderr, "Error %d while ioctl get operation\n", dfile); + return dfile; + } + + if (!chrdev_led_cmd_is_ok(&led_ctrl)) { + assert(0); + return -1; + } + + /* Invert LED state */ + led_ctrl.e_state = + (led_ctrl.e_state == e_led_state_off) ? e_led_state_on : e_led_state_off; + + if (ioctl(dfile, CHRDEV_IOCTRL_SET_VAL, &led_ctrl)) { + fprintf(stderr, "Error %d while ioctl set operation\n", dfile); + return dfile; + } + + return 0; +} + +static void signal_handler(int signal) +{ + cmd_run = 0; +}