From e260cf649de16aad3755fecf2fe586c60b420e5f Mon Sep 17 00:00:00 2001 From: Yevgen Kovalyov Date: Wed, 1 Dec 2021 00:13:52 +0200 Subject: [PATCH 01/17] Task05: Add tasks for lesson 5 Time Management Add descriptions of tasks for lesson 5. Signed-off-by: Yevgen Kovalyov --- 05_timers/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 05_timers/README.md 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. From a8bc3010b8552272a6d76d1e62754e0e778b7342 Mon Sep 17 00:00:00 2001 From: Yevgen Kovalyov Date: Thu, 2 Dec 2021 19:36:43 +0200 Subject: [PATCH 02/17] Task06: Add tasks for lesson 6 Memory Management --- 06_memory/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 06_memory/README.md 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. From 9d1495bac6172bba2727df813a46f9a7de96c8df Mon Sep 17 00:00:00 2001 From: Oleksandr Posukhov Date: Wed, 8 Dec 2021 16:50:33 +0200 Subject: [PATCH 03/17] Task07: Add procfs task for lesson 7 ProcFS and platform bootup Add task for implementation of procFS API Signed-off-by: Oleksandr Posukhov --- 07_procfs/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 07_procfs/README.md 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 + From b0c236f9a37f4bd8465d78fadb03c1810e8e28bb Mon Sep 17 00:00:00 2001 From: Oleksandr Posukhov Date: Tue, 21 Dec 2021 14:14:22 +0200 Subject: [PATCH 04/17] Lesson08: Add home task 08 Add example of polling mechanism, add hometask Signed-off-by: Oleksandr Posukhov --- 08_irq_handling/README.md | 5 ++ 08_irq_handling/led_mod.c | 154 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 08_irq_handling/README.md create mode 100644 08_irq_handling/led_mod.c 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/08_irq_handling/led_mod.c b/08_irq_handling/led_mod.c new file mode 100644 index 0000000..5444623 --- /dev/null +++ b/08_irq_handling/led_mod.c @@ -0,0 +1,154 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include +#include +#include +#include + +#define GPIO_NUMBER(port, bit) (32 * (port) + (bit)) + +/* https://linux-sunxi.org/Xunlong_Orange_Pi_PC#LEDs + * Board config for OPI-PC: + * LED GREEN (PL10): GPIO_11_10 + * LED RED (PA15): GPIO_0_15 + * BUTTON (PG7) : GPIO_6_7 + * + * 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 + * BUTTON (PG7) : GPIO_6_7 + * + */ + +#define LED_GREEN GPIO_NUMBER(11, 10) +#define LED_RED GPIO_NUMBER(0, 15) +#define BUTTON GPIO_NUMBER(6, 7) + +//#define TIMER_ENABLE 1 + +static int ledg_gpio = -1; +static int ledr_gpio = -1; +static int button_gpio = -1; +static int button_state = -1; +static int button_cnt = -1; + +#ifdef TIMER_ENABLE +static ktime_t timer_period; +struct hrtimer button_timer; + +static enum hrtimer_restart timer_callback(struct hrtimer *timer) +{ + int cur_button_state; + + cur_button_state = gpio_get_value(button_gpio); + button_cnt = (cur_button_state == button_state) ? (button_cnt + 1) : 0; + button_state = cur_button_state; + gpio_set_value(ledr_gpio, ((button_cnt == 20) ? 1 : 0)); + if (button_cnt >= 20) + gpio_set_value(ledg_gpio, !button_state); + hrtimer_forward(timer, timer->base->get_time(), timer_period); + return HRTIMER_RESTART; //restart timer +} +#endif + +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 button_gpio_init(int gpio) +{ + int res; + + res = gpio_request(gpio, "Onboard user button"); + if (res != 0) + return res; + + res = gpio_direction_input(gpio); + if (res != 0) + goto err_input; + + button_gpio = gpio; + pr_info("Init GPIO%d OK\n", button_gpio); + button_state = gpio_get_value(button_gpio); + button_cnt = 0; + + return 0; + +err_input: + gpio_free(gpio); + return res; +} + +static void button_gpio_deinit(void) +{ + if (button_gpio >= 0) { + gpio_free(button_gpio); + pr_info("Deinit GPIO%d\n", button_gpio); + } +} + +/* Module entry/exit points */ +static int __init gpio_poll_init(void) +{ + int res; + pr_info("GPIO Init\n"); + + res = button_gpio_init(BUTTON); + if (res != 0) { + pr_err("Can't set GPIO%d for button\n", BUTTON); + return res; + } +#ifdef TIMER_ENABLE + timer_period = ktime_set(0, 1000000); /*1 msec*/ + hrtimer_init(&button_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_start(&button_timer, timer_period, HRTIMER_MODE_REL); + button_timer.function = timer_callback; +#endif + res = led_gpio_init(LED_GREEN, &ledg_gpio); + if (res != 0) { + pr_err("Can't set GPIO%d for output\n", LED_GREEN); + goto err_led; + } + + gpio_set_value(ledg_gpio, 0); + + res = led_gpio_init(LED_RED, &ledr_gpio); + if (res != 0) { + pr_err("Can't set GPIO%d for output\n", LED_RED); + goto err_led; + } + gpio_set_value(ledr_gpio, 1); + + return 0; + +err_led: + button_gpio_deinit(); + return res; +} + +static void __exit gpio_poll_exit(void) +{ + gpio_set_value(ledg_gpio, 0); + gpio_set_value(ledr_gpio, 0); + button_gpio_deinit(); +#ifdef TIMER_ENABLE + hrtimer_cancel(&button_timer); +#endif +} + +module_init(gpio_poll_init); +module_exit(gpio_poll_exit); + +MODULE_AUTHOR("Oleksandr Posukhov oleksandr.posukhov@gmail.com>"); +MODULE_DESCRIPTION("LED Test"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); \ No newline at end of file From d552a3919932244fed0c4010b003bd39d5db0299 Mon Sep 17 00:00:00 2001 From: Oleksandr Posukhov Date: Tue, 21 Dec 2021 14:22:50 +0200 Subject: [PATCH 05/17] Lesson10: Home task for character device driver Add hometask for lection 10: Chardev Signed-off-by: Oleksandr Posukhov --- 10_chardev/REAMDE.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 10_chardev/REAMDE.md 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... + From c5fd31e07259bfb00acce98a1e582b63b63ffda5 Mon Sep 17 00:00:00 2001 From: volodymyrkniazkyi <94599590+volodymyrkniazkyi@users.noreply.github.com> Date: Fri, 26 Nov 2021 09:14:56 +0200 Subject: [PATCH 06/17] 01_git: Create rock-paper-scissors game (#57) Add initial revision of homework task --- 01_git/makefile | 17 +++++++ 01_git/scissors.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++ 01_git/scissors.h | 22 +++++++++ 3 files changed, 155 insertions(+) create mode 100644 01_git/makefile create mode 100644 01_git/scissors.c create mode 100644 01_git/scissors.h diff --git a/01_git/makefile b/01_git/makefile new file mode 100644 index 0000000..9e9408a --- /dev/null +++ b/01_git/makefile @@ -0,0 +1,17 @@ +# +# makefile of scissors-paper game project +# + +HEADERS = scissors.h +OBJECTS = scissors.o + +scissors: ${OBJECTS} + gcc -o scissors ${OBJECTS} + +scissors.o: scissors.c $(HEADERS) + gcc -c scissors.c + +.PHONY: clean + +clean: + rm $(OBJECTS) diff --git a/01_git/scissors.c b/01_git/scissors.c new file mode 100644 index 0000000..bc212c4 --- /dev/null +++ b/01_git/scissors.c @@ -0,0 +1,116 @@ +/* + * Main file of scissors-paper project + */ +#include +#include +#include +#include "scissors.h" +#include + +// +// Return name of material chose by gamer +// +char* getmaterial(char gamersymb) +{ + switch(gamersymb) + { + case SYMB_ROCK: + return STR_ROCK;; + case SYMB_PAPER: + return STR_PAPER; + case SYMB_SCISSORS: + return STR_SCISSORS; + } +} + +// +// Calculate who is winner of game +// +int getwinner(char gamer1, char gamer2) +{ + int ret = WINNER_NO; + + if(gamer1==gamer2) return WINNER_NO; + switch(gamer1) + { + case SYMB_PAPER: + if(SYMB_ROCK==gamer2) ret = WINNER_1; + else ret = WINNER_2; + break; + case SYMB_ROCK: + if(SYMB_PAPER==gamer2) ret = WINNER_2; + else ret = WINNER_1; + break; + case SYMB_SCISSORS: + if(SYMB_ROCK==gamer2) ret = WINNER_2; + else ret = WINNER_1; + break; + default: + ret = WINNER_ERR; + break; + } + return ret; +} + +int main() +{ + char sgamer[256],gamer,computer,winner; + + gamer = computer = winner = 0; + srand(time(NULL)); //Initialisation of random function + + for(;;){ + // Get user choise + for(;;){ + printf("\n\tPlease, choose rock (%c) - paper(%c) - scissors(%c) exit - (%c)\n\t",\ + SYMB_ROCK,SYMB_PAPER,SYMB_SCISSORS,SYMB_EXIT); + + if(NULL==fgets((char*)sgamer,sizeof(sgamer),stdin)){ + printf("\n\tInput error"); + return 1; + } + if(2 Date: Fri, 26 Nov 2021 00:13:24 +0200 Subject: [PATCH 07/17] 02_bash: Create script witch detects connected hardware Add initial revision of script --- 02_bash/hwdetect.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 02_bash/hwdetect.sh diff --git a/02_bash/hwdetect.sh b/02_bash/hwdetect.sh new file mode 100644 index 0000000..3996a84 --- /dev/null +++ b/02_bash/hwdetect.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# delete temporary files +cleanup() { + rm udev.txt + rm udev_new.txt + rm i2cdev.txt + rm i2cdev_new.txt + echo "cleaning up..." + exit +} + +echo "Device conecting monitor" +echo "For exit press Ctrl-C" + +# detect usb devices +lsusb > udev.txt + +# detect i2c devices +i2cdetect -l >i2cdev.txt + +while true +do +# create new list of usb devices + lsusb > udev_new.txt +# create new list of i2c devices + i2cdetect -l >i2cdev_new.txt + +# look for differences in usb devices list + diffresult=$(diff udev.txt udev_new.txt) + size=$(wc -c < udev.txt) + size_new=$(wc -c < udev_new.txt) +# show differences + if [ $size -ne $size_new ] + then + if [ $size -gt $size_new ] + then + echo "Disconnected USB devices:" + else + echo "Connected USB devices:" + fi + echo $diffresult + mv -f udev_new.txt udev.txt + fi + +# look for differences in i2c devices list + diffresult=$(diff i2cdev.txt i2cdev_new.txt) + size=$(wc -c < i2cdev.txt) + size_new=$(wc -c < i2cdev_new.txt) +# show differences + if [ $size -ne $size_new ] + then + if [ $size -gt $size_new ] + then + echo "Disconnected i2c devices:" + else + echo "Connected i2c devices:" + fi + echo $diffresult + mv -f i2cdev_new.txt i2cdev.txt + fi + + sleep 1 +# check if Ctrl-C pressed and exit + trap 'cleanup' INT +done From 4fc20bc7b2a47205909872f1f13364a0396b1dec Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Fri, 26 Nov 2021 00:23:41 +0200 Subject: [PATCH 08/17] 03_module: Create simple loadable kernel module with parameters Add first revision of kernel module with parameters Module outputs: - A sum of parameters upon driver load - A substraction of parameters upon unload --- 03_module/Makefile | 9 +++++++++ 03_module/dump.txt | 18 ++++++++++++++++++ 03_module/prmmod.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 03_module/Makefile create mode 100644 03_module/dump.txt create mode 100644 03_module/prmmod.c diff --git a/03_module/Makefile b/03_module/Makefile new file mode 100644 index 0000000..83f9467 --- /dev/null +++ b/03_module/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.7/ #WARNING relative path + +obj-m := prmmod.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/03_module/dump.txt b/03_module/dump.txt new file mode 100644 index 0000000..021d9fa --- /dev/null +++ b/03_module/dump.txt @@ -0,0 +1,18 @@ +# cd /home/user +# insmod prmmod.ko fprm=10 sprm=20 +prmmod: loading out-of-tree module taints kernel. +prmmod: module license 'unspecified' taints kernel. +Disabling lock debugging due to kernel taint +Module with parameters fprm=10 sprm=20 initialisation! +The sum of parameters fprm and sprm is 30 +# rmmod prmmod.ko +Module with parameters fprm=10 sprm=20 initialisation! +The substraction of parameters fprm and sprm is -10 +Bye Bye... +# insmod prmmod.ko +Module with parameters fprm=1 sprm=2 initialisation! +The sum of parameters fprm and sprm is 3 +# rmmod prmmod.ko +Module with parameters fprm=1 sprm=2 initialisation! +The substraction of parameters fprm and sprm is -1 +Bye Bye... diff --git a/03_module/prmmod.c b/03_module/prmmod.c new file mode 100644 index 0000000..42868d4 --- /dev/null +++ b/03_module/prmmod.c @@ -0,0 +1,33 @@ +/* +* Simple module as example of usage modules with parameters +*/ +#include +#include +#include +#include + +//first parameter initialisation +static int fprm = 1; +module_param(fprm,int,0660); + +//second parameter initialisation +static int sprm = 2; +module_param(sprm,int,0660); + + +int prmmod_init(void) +{ + printk(KERN_INFO "Module with parameters fprm=%d sprm=%d initialisation!\n",fprm,sprm); + printk(KERN_INFO "The sum of parameters fprm and sprm is %d\n",fprm+sprm); + return 0; +} + + +void prmmod_exit(void) { + printk(KERN_INFO "Module with parameters fprm=%d sprm=%d initialisation!\n",fprm,sprm); + printk(KERN_INFO "The substraction of parameters fprm and sprm is %d\n",fprm-sprm); + printk(KERN_INFO "Bye Bye...\n"); +} + +module_init(prmmod_init); +module_exit(prmmod_exit); From fee313bf329716595d2e9c702e47eb80ce6261bc Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Tue, 30 Nov 2021 00:05:58 +0200 Subject: [PATCH 09/17] 04_basic_struct: Create module with linked_list structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On read form attribute “list” show content of the objects linked list. On write to attribute “list” add new string to the objects linked list. During rmmod clear all received strings and free all resources. --- 04_basic_struct/Makefile | 9 +++ 04_basic_struct/dump.txt | 10 +++ 04_basic_struct/recv_msg.c | 122 +++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 04_basic_struct/Makefile create mode 100644 04_basic_struct/dump.txt create mode 100644 04_basic_struct/recv_msg.c diff --git a/04_basic_struct/Makefile b/04_basic_struct/Makefile new file mode 100644 index 0000000..37819bf --- /dev/null +++ b/04_basic_struct/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.7/ #WARNING relative path + +obj-m := recv_msg.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/04_basic_struct/dump.txt b/04_basic_struct/dump.txt new file mode 100644 index 0000000..abda0bf --- /dev/null +++ b/04_basic_struct/dump.txt @@ -0,0 +1,10 @@ +# insmod /home/user/recv_msg.ko +# echo message1 > /sys/kernel/recv_msg/list +# echo message2 > /sys/kernel/recv_msg/list +# echo message3 > /sys/kernel/recv_msg/list +# cat /sys/kernel/recv_msg/list +message1 +message2 +message3 +# rmmod /home/user/recv_msg.ko +# diff --git a/04_basic_struct/recv_msg.c b/04_basic_struct/recv_msg.c new file mode 100644 index 0000000..28bba91 --- /dev/null +++ b/04_basic_struct/recv_msg.c @@ -0,0 +1,122 @@ +/* + * Receiving messages module + */ +#define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME +#include // Core header for loading LKMs into the kernel +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Volodymyr Kniazkyi "); +MODULE_DESCRIPTION("Message receive module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +struct msg_list { + struct list_head list; + char *msg_data; +}; + +static struct list_head head; + +/* + * Read all data from message list + */ +static ssize_t message_show_all(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct msg_list *curr_msg; + struct list_head *listptr; + size_t wr_len = 0; + + list_for_each(listptr, &head) { + curr_msg = list_entry(listptr, struct msg_list, list); + + //pr_info("Read data: %s",curr_msg->msg_data); + + strcpy(buf+wr_len, curr_msg->msg_data); + wr_len += strlen(curr_msg->msg_data); + } + return wr_len; +} + +/* + * Add new entry to message list (FIFO or Stack) + */ +static ssize_t message_add_new(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + //allocate memory buffer for new entry of msg_list + struct msg_list *new_msg = kmalloc(sizeof(struct msg_list), GFP_KERNEL); + + if (!new_msg) + return -ENOMEM; + + //allocate memory buffer for message data + new_msg->msg_data = kmalloc(count, GFP_KERNEL); + + if (!new_msg->msg_data) + return -ENOMEM; + + strcpy(new_msg->msg_data, buf); + + //add new data to FIFO + list_add_tail(&new_msg->list, &head); + //add new data to Stack + //list_add(&new_msg->list, &head); + + //pr_info("New message received (length=%i): %s",(int)count,new_msg->msg_data); + return count; +} + +static struct kobj_attribute list_attribute = + __ATTR(list, 0664, message_show_all, message_add_new); + +static struct kobject *message_kobj; + +static int message_module_init(void) +{ + int res; + + message_kobj = kobject_create_and_add("recv_msg", kernel_kobj); + if (!message_kobj) + return -ENOMEM; + + res = sysfs_create_file(message_kobj, &list_attribute.attr); + if (res) + kobject_put(message_kobj); + + INIT_LIST_HEAD(&head); + //pr_info("module init\n"); + return res; +} + +static void message_module_exit(void) +{ + struct msg_list *curr_msg; + struct msg_list *tmp_msg; + + list_for_each_entry_safe(curr_msg, tmp_msg, &head, list) { + //delete message from list + list_del(&curr_msg->list); + + //pr_info("Clear message: %s",curr_msg->msg_data); + + //free memory for message buffer + kfree(curr_msg->msg_data); + //free memory for message + kfree(curr_msg); + } + if (!list_empty(&head)) + pr_info("Alarm!!! message list is not empty\n"); + + kobject_put(message_kobj); + //pr_info("module exit\n"); +} + +module_init(message_module_init); +module_exit(message_module_exit); From ae64e2225fda34f622c8c4eebc0463fd39133e29 Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Fri, 3 Dec 2021 01:58:13 +0200 Subject: [PATCH 10/17] 05_timers: Add homework for Linux Kernel Time Management lesson Implement program which return absolute time in user space Implement kernel module wich return relative and absolute time since previous read of it. --- 05_timers/App/get_time.c | 43 +++++++++++++ 05_timers/App/makefile | 16 +++++ 05_timers/Module/Makefile | 9 +++ 05_timers/Module/clc_time.c | 120 ++++++++++++++++++++++++++++++++++++ 05_timers/Module/dump.txt | 26 ++++++++ 5 files changed, 214 insertions(+) create mode 100644 05_timers/App/get_time.c create mode 100644 05_timers/App/makefile create mode 100644 05_timers/Module/Makefile create mode 100644 05_timers/Module/clc_time.c create mode 100644 05_timers/Module/dump.txt diff --git a/05_timers/App/get_time.c b/05_timers/App/get_time.c new file mode 100644 index 0000000..5b41523 --- /dev/null +++ b/05_timers/App/get_time.c @@ -0,0 +1,43 @@ +/* + * Getting absolute time application + */ +#include +#include +#include + +int main(int argc, char **argv) +{ + struct timespec ts, ts_loc; + struct tm *loctime; + + printf("\n Absolute time Local time\n"); + printf(" tv_nsec tv_sec date time\n"); + //switch off cursor + printf("\033[?25l"); + + while (1) { + clock_gettime(CLOCK_REALTIME, &ts); + //clock_gettime(CLOCK_MONOTONIC, &ts); + //clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); + //clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); + + printf("%10ld %10ld ", ts.tv_nsec, ts.tv_sec); + + clock_gettime(CLOCK_REALTIME, &ts_loc); + loctime = localtime((time_t *)&ts_loc.tv_sec); + + printf("%02d-%02d-%04d %02d:%02d:%02d", + loctime->tm_mday, + loctime->tm_mon+1, + loctime->tm_year+1900, + loctime->tm_hour, + loctime->tm_min, + loctime->tm_sec); + + usleep(1000); + printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); + printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); + } + printf("\n"); + return 0; +} diff --git a/05_timers/App/makefile b/05_timers/App/makefile new file mode 100644 index 0000000..1b76a99 --- /dev/null +++ b/05_timers/App/makefile @@ -0,0 +1,16 @@ +# +# makefile of get_time application +# + +OBJECTS = get_time.o + +get_time: ${OBJECTS} + gcc -o get_time ${OBJECTS} + +get_time.o: get_time.c $(HEADERS) + gcc -c get_time.c + +.PHONY: clean + +clean: + rm $(OBJECTS) diff --git a/05_timers/Module/Makefile b/05_timers/Module/Makefile new file mode 100644 index 0000000..8ec7426 --- /dev/null +++ b/05_timers/Module/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.7/ #WARNING relative path + +obj-m := clc_time.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/05_timers/Module/clc_time.c b/05_timers/Module/clc_time.c new file mode 100644 index 0000000..a209e7c --- /dev/null +++ b/05_timers/Module/clc_time.c @@ -0,0 +1,120 @@ +/* + * Time intervals calculating module + */ +#include // Core header for loading LKMs into the kernel +#include +#include + +MODULE_AUTHOR("Volodymyr Kniazkyi "); +MODULE_DESCRIPTION("Time intervals calculating module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + + +/* + * Show absolute time of previous reading + * Show relation time passed since previous reading + */ +static ssize_t timing_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + static cycles_t prev_tick; + static ktime_t prev_time; + + cycles_t curr_tick; + ktime_t curr_time; + + size_t wr_len; + struct timespec64 ts; + struct tm dt; + + + //current time stamp + curr_tick = get_cycles();///CLOCK_TICK_RATE; + curr_time = ktime_get_real(); + + if (prev_tick == 0) { + wr_len = sprintf(buf, "First reading\n"); + } else { + ts = ns_to_timespec64(prev_time); + //ktime_get_coarse_real_ts64(&ts); + time64_to_tm(ts.tv_sec, 0, &dt); + + wr_len = sprintf(buf, "Previous reading was at %02d-%02d-%02ld %02d:%02d:%02d.%09ld\n", + dt.tm_mday, + dt.tm_mon+1, + dt.tm_year+1900, + dt.tm_hour, + dt.tm_min, + dt.tm_sec, + ts.tv_nsec); + + + prev_time = curr_time - prev_time; + ts = ns_to_timespec64(prev_time); + + wr_len += sprintf(buf+wr_len, "Relative time %04lld.%09ld\n", + ts.tv_sec, + ts.tv_nsec); + + prev_tick = curr_tick - prev_tick; + wr_len += sprintf(buf+wr_len, "Relative cycles %09lld\n", prev_tick); + } + + //save current time stamp + prev_tick = curr_tick; + prev_time = curr_time; + + //output of current time stamp + ts = ns_to_timespec64(curr_time); + time64_to_tm(ts.tv_sec, 0, &dt); + + wr_len += sprintf(buf+wr_len, "Current time stamp %02d-%02d-%02ld %02d:%02d:%02d.%09ld\n", + dt.tm_mday, + dt.tm_mon+1, + dt.tm_year+1900, + dt.tm_hour, + dt.tm_min, + dt.tm_sec, + ts.tv_nsec); + + return wr_len; +} + +//static struct kobj_attribute getclk_attribute = { +// .attr = {.name = "getclk", 0444}, +// .show = absolute_time_show +//}; + +static struct kobj_attribute getclk_attribute = + __ATTR(getclk, 0444, timing_show, NULL); + +static struct kobject *calctime_kobj; + + +static int calctime_module_init(void) +{ + int res; + + calctime_kobj = kobject_create_and_add("clc_time", kernel_kobj); + if (!calctime_kobj) + return -ENOMEM; + + res = sysfs_create_file(calctime_kobj, &getclk_attribute.attr); + if (res) { + kobject_put(calctime_kobj); + return res; + } + + //pr_info("module init\n"); + return res; +} + +static void calctime_module_exit(void) +{ + kobject_put(calctime_kobj); + //pr_info("module exit\n"); +} + +module_init(calctime_module_init); +module_exit(calctime_module_exit); diff --git a/05_timers/Module/dump.txt b/05_timers/Module/dump.txt new file mode 100644 index 0000000..8c0c187 --- /dev/null +++ b/05_timers/Module/dump.txt @@ -0,0 +1,26 @@ +# buildroot login: root +# random: crng init done +# insmod /home/user/clc_time.ko +clc_time: loading out-of-tree module taints kernel. +# cat /sys/kernel/clc_time/getclk +First reading +Current time stamp 02-12-2021 23:35:54.237000775 +# cat /sys/kernel/clc_time/getclk +Previous reading was at 02-12-2021 23:35:54.237000775 +Relative time 0003.429394330 +Relative cycles 3753137428 +Current time stamp 02-12-2021 23:35:57.666395105 +# cat /sys/kernel/clc_time/getclk +Previous reading was at 02-12-2021 23:35:57.666395105 +Relative time 0002.989310592 +Relative cycles 3271507523 +Current time stamp 02-12-2021 23:36:00.655705697 +# cat /sys/kernel/clc_time/getclk +Previous reading was at 02-12-2021 23:36:00.655705697 +Relative time 0036.955127282 +Relative cycles 40443766361 +Current time stamp 02-12-2021 23:36:37.610832979 +# rmmod /home/user/clc_time.ko +# + + From 73affdefe45339e71518664f159d8ee375477677 Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Mon, 6 Dec 2021 22:48:30 +0200 Subject: [PATCH 11/17] 05_timers: Fix minor mistake in codestyle Make one line in code shorter by moving procedure parameter to next one. --- 05_timers/Module/clc_time.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/05_timers/Module/clc_time.c b/05_timers/Module/clc_time.c index a209e7c..2745f67 100644 --- a/05_timers/Module/clc_time.c +++ b/05_timers/Module/clc_time.c @@ -58,7 +58,8 @@ static ssize_t timing_show(struct kobject *kobj, struct kobj_attribute *attr, ts.tv_nsec); prev_tick = curr_tick - prev_tick; - wr_len += sprintf(buf+wr_len, "Relative cycles %09lld\n", prev_tick); + wr_len += sprintf(buf+wr_len, "Relative cycles %09lld\n", + prev_tick); } //save current time stamp From da631073683200db976544e55ea2f191c37c36b2 Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Thu, 9 Dec 2021 16:06:29 +0200 Subject: [PATCH 12/17] 06_memory: Add homework for Memory management lesson Create user-space program and test allcation/freeing time for functions malloc, calloc and alloca. Create kernel module and test allocation/freeing time for functions kmalloc, kzalloc, vmalloc, and get_free_pages. --- 06_memory/App/dump.txt | 105 +++++++++++++ 06_memory/App/makefile | 16 ++ 06_memory/App/memory_tst.c | 150 +++++++++++++++++++ 06_memory/Module/dump.txt | 109 ++++++++++++++ 06_memory/Module/makefile | 9 ++ 06_memory/Module/memory_tst_mod.c | 237 ++++++++++++++++++++++++++++++ 6 files changed, 626 insertions(+) create mode 100644 06_memory/App/dump.txt create mode 100644 06_memory/App/makefile create mode 100644 06_memory/App/memory_tst.c create mode 100644 06_memory/Module/dump.txt create mode 100644 06_memory/Module/makefile create mode 100644 06_memory/Module/memory_tst_mod.c diff --git a/06_memory/App/dump.txt b/06_memory/App/dump.txt new file mode 100644 index 0000000..6cb32fd --- /dev/null +++ b/06_memory/App/dump.txt @@ -0,0 +1,105 @@ + Memory allocation/deallocation test + +Size Function Alloc(ns) Dealloc(ns) +2^0 alloca 400 0 +2^0 calloc 611 238 +2^0 malloc 164 72 +2^1 alloca 54 0 +2^1 calloc 155 53 +2^1 malloc 47 54 +2^2 alloca 53 0 +2^2 calloc 106 56 +2^2 malloc 62 56 +2^3 alloca 52 0 +2^3 calloc 104 58 +2^3 malloc 49 55 +2^4 alloca 52 0 +2^4 calloc 162 56 +2^4 malloc 64 54 +2^5 alloca 52 0 +2^5 calloc 125 54 +2^5 malloc 48 54 +2^6 alloca 54 0 +2^6 calloc 111 55 +2^6 malloc 47 56 +2^7 alloca 52 0 +2^7 calloc 239 53 +2^7 malloc 50 56 +2^8 alloca 52 0 +2^8 calloc 162 58 +2^8 malloc 50 54 +2^9 alloca 53 0 +2^9 calloc 146 58 +2^9 malloc 48 54 +2^10 alloca 52 0 +2^10 calloc 241 58 +2^10 malloc 47 52 +2^11 alloca 54 0 +2^11 calloc 5840 415 +2^11 malloc 371 129 +2^12 alloca 102 0 +2^12 calloc 440 69 +2^12 malloc 158 65 +2^13 alloca 5405 0 +2^13 calloc 6218 327 +2^13 malloc 310 94 +2^14 alloca 17881 0 +2^14 calloc 12444 368 +2^14 malloc 361 99 +2^15 alloca 22088 0 +2^15 calloc 23112 382 +2^15 malloc 350 93 +2^16 alloca 38445 0 +2^16 calloc 50092 405 +2^16 malloc 359 94 +2^17 alloca 84172 0 +2^17 calloc 12702 10346 +2^17 malloc 9211 346 +2^18 alloca 201729 0 +2^18 calloc 264216 374 +2^18 malloc 373 91 +2^19 alloca 393075 0 +2^19 calloc 11581 9660 +2^19 malloc 8750 305 +2^20 alloca 799612 0 +2^20 calloc 11796 10163 +2^20 malloc 8642 313 +2^21 alloca error +2^21 calloc 12631 11374 +2^21 malloc 9904 307 +2^22 alloca error +2^22 calloc 12721 10158 +2^22 malloc 10035 305 +2^23 alloca error +2^23 calloc 13096 10197 +2^23 malloc 9698 305 +2^24 alloca error +2^24 calloc 13462 10141 +2^24 malloc 9805 308 +2^25 alloca error +2^25 calloc 12614 10372 +2^25 malloc 11600 10153 +2^26 alloca error +2^26 calloc 11735 10634 +2^26 malloc 12040 10568 +2^27 alloca error +2^27 calloc 11524 11401 +2^27 malloc 11522 11358 +2^28 alloca error +2^28 calloc 11573 13073 +2^28 malloc 11568 13055 +2^29 alloca error +2^29 calloc 24143 30025 +2^29 malloc 12747 17119 +2^30 alloca error +2^30 calloc 12704 23797 +2^30 malloc 12631 23852 +2^31 alloca error +2^31 calloc 12561 23796 +2^31 malloc 12523 23874 +2^32 alloca error +2^32 calloc 12544 23950 +2^32 malloc 12943 23929 +2^33 alloca error +2^33 calloc error +2^33 malloc error diff --git a/06_memory/App/makefile b/06_memory/App/makefile new file mode 100644 index 0000000..2852b33 --- /dev/null +++ b/06_memory/App/makefile @@ -0,0 +1,16 @@ +# +# makefile of memory_tst application +# + +OBJECTS = memory_tst.o + +get_time: ${OBJECTS} + gcc -o memory_tst ${OBJECTS} + +get_time.o: memory_tst.c $(HEADERS) + gcc -c memory_tst.c + +.PHONY: clean + +clean: + rm $(OBJECTS) diff --git a/06_memory/App/memory_tst.c b/06_memory/App/memory_tst.c new file mode 100644 index 0000000..6d2c934 --- /dev/null +++ b/06_memory/App/memory_tst.c @@ -0,0 +1,150 @@ +/* + * Application for test timing of memory + * allocation/deallocation functions + * + */ +#include +#include +#include +#include +#include +#include + + +#define MAX_POWER_2 34 + +#define FUNC_ALLOCA 0 +#define FUNC_CAALOC 1 +#define FUNC_MAALOC 2 + +#define FUNC_MAX_ID 3 + +#define FUNC_MAX_NAME 7 + +char func_name[FUNC_MAX_ID][FUNC_MAX_NAME] = { + "alloca", + "calloc", + "malloc"}; + +int64_t calc_delta_time(struct timespec *start, struct timespec *stop) +{ + return (int64_t)(stop->tv_sec - start->tv_sec) * (int64_t)1000000000UL + + (int64_t)(stop->tv_nsec - start->tv_nsec); +} + +void *get_memory(int8_t func_id, int64_t size, int64_t *time) +{ + char *buf = NULL; + + struct timespec start; + struct timespec stop; + + if (time != NULL) + *time = 0; + + //clock_gettime(CLOCK_MONOTONIC, &start); + + switch (func_id) { + case FUNC_ALLOCA: + //I am a beginner in Linux programming and + //I do not know how check maximum memory size + //that can be allocated safely by alloca() + if (size > (1ul<<20)) + break; + clock_gettime(CLOCK_MONOTONIC, &start); + buf = alloca(size); + clock_gettime(CLOCK_MONOTONIC, &stop); + break; + case FUNC_CAALOC: + clock_gettime(CLOCK_MONOTONIC, &start); + buf = calloc(size, sizeof(char)); + clock_gettime(CLOCK_MONOTONIC, &stop); + break; + case FUNC_MAALOC: + clock_gettime(CLOCK_MONOTONIC, &start); + buf = malloc(size); + clock_gettime(CLOCK_MONOTONIC, &stop); + break; + default: + return NULL; + } + + //clock_gettime(CLOCK_MONOTONIC, &stop); + + if (time != NULL) { + if (buf == NULL) + *time = -1;// error happened + else + *time = calc_delta_time(&start, &stop); + } + + return (void *)buf; +} + +void free_memory(void *buf, int8_t func_id, int64_t *time) +{ + struct timespec start = {0}; + struct timespec stop = {0}; + + if (time != NULL) + *time = 0; + + //clock_gettime(CLOCK_MONOTONIC, &start); + + switch (func_id) { + case FUNC_ALLOCA: + // memory was freed at the moment of exit from get_memory automatically + break; + case FUNC_CAALOC: + clock_gettime(CLOCK_MONOTONIC, &start); + free(buf); + clock_gettime(CLOCK_MONOTONIC, &stop); + break; + case FUNC_MAALOC: + clock_gettime(CLOCK_MONOTONIC, &start); + free(buf); + clock_gettime(CLOCK_MONOTONIC, &stop); + break; + default: + return; + } + + //clock_gettime(CLOCK_MONOTONIC, &stop); + + if (time != NULL) + *time = calc_delta_time(&start, &stop); +} + + +int main(void) +{ + int8_t func_id; + int8_t s = 0; + int64_t size = 0; + int64_t tim_alloc; + int64_t tim_dealloc; + + char *buf = NULL; + + printf("\n\tMemory allocation/deallocation test\n\n"); + + printf("Size\t\tFunction\tAlloc(ns)\tDealloc(ns)\n"); + + for (s = 0; s < MAX_POWER_2; s++) { + + size = 1ull << s; + + for (func_id = 0; func_id < FUNC_MAX_ID; func_id++) { + buf = get_memory(func_id, size, &tim_alloc); + if (buf != NULL) + free_memory(buf, func_id, &tim_dealloc); + if (tim_alloc == -1) + printf("2^%i\t\t%s\t\terror\n", + s, &func_name[func_id][0]); + else + printf("2^%i\t\t%s\t\t%lu\t\t%lu\n", + s, &func_name[func_id][0], tim_alloc, tim_dealloc); + + } + } +} diff --git a/06_memory/Module/dump.txt b/06_memory/Module/dump.txt new file mode 100644 index 0000000..62f9cc1 --- /dev/null +++ b/06_memory/Module/dump.txt @@ -0,0 +1,109 @@ +# insmod /home/user/memory_tst_mod.ko +# cat /sys/kernel/memory_tst_mod/getmemtst + + Memory allocation/deallocation test + +Size Function Alloc(ns) Dealloc(ns) +2^0 kmalloc 29830 33470 +2^0 kzalloc 33650 1600 +2^0 vmalloc 89010 50810 +2^0 get_free_pages 38530 23750 +2^1 kmalloc 2780 1570 +2^1 kzalloc 1880 1130 +2^1 vmalloc 16450 11620 +2^1 get_free_pages 2630 1830 +2^2 kmalloc 1710 1210 +2^2 kzalloc 1640 1090 +2^2 vmalloc 9640 7120 +2^2 get_free_pages 2270 1520 +2^3 kmalloc 1710 1100 +2^3 kzalloc 1610 1060 +2^3 vmalloc 9400 6580 +2^3 get_free_pages 2360 1470 +2^4 kmalloc 4370 1110 +2^4 kzalloc 1710 1040 +2^4 vmalloc 336620 108880 +2^4 get_free_pages 4110 2160 +2^5 kmalloc 2560 2740 +2^5 kzalloc 2340 1080 +2^5 vmalloc 16330 8280 +2^5 get_free_pages 2570 1610 +2^6 kmalloc 1740 1180 +2^6 kzalloc 1710 1030 +2^6 vmalloc 11700 6670 +2^6 get_free_pages 2160 1450 +2^7 kmalloc 3300 2140 +2^7 kzalloc 1890 990 +2^7 vmalloc 8850 6170 +2^7 get_free_pages 2340 1590 +2^8 kmalloc 3400 1300 +2^8 kzalloc 1840 1010 +2^8 vmalloc 9140 6350 +2^8 get_free_pages 2110 1420 +2^9 kmalloc 3170 1030 +2^9 kzalloc 1870 990 +2^9 vmalloc 8310 5990 +2^9 get_free_pages 2160 1480 +2^10 kmalloc 3000 3220 +2^10 kzalloc 2070 1030 +2^10 vmalloc 8440 6170 +2^10 get_free_pages 2090 1460 +2^11 kmalloc 3080 2220 +2^11 kzalloc 2340 980 +2^11 vmalloc 9180 7390 +2^11 get_free_pages 2120 1450 +2^12 kmalloc 3080 1270 +2^12 kzalloc 2890 1050 +2^12 vmalloc 11330 6190 +2^12 get_free_pages 2130 1450 +2^13 kmalloc 3300 2210 +2^13 kzalloc 5390 1060 +2^13 vmalloc 28320 7480 +2^13 get_free_pages 10190 8400 +2^14 kmalloc 10810 6270 +2^14 kzalloc 15250 3060 +2^14 vmalloc 16450 9020 +2^14 get_free_pages 3070 3030 +2^15 kmalloc 52380 4250 +2^15 kzalloc 23930 3040 +2^15 vmalloc 38050 12200 +2^15 get_free_pages 3400 3240 +2^16 kmalloc 4310 3290 +2^16 kzalloc 36150 3190 +2^16 vmalloc 20380 19590 +2^16 get_free_pages 3270 3210 +2^17 kmalloc 5200 5430 +2^17 kzalloc 68850 4780 +2^17 vmalloc 37670 27600 +2^17 get_free_pages 3930 5030 +2^18 kmalloc 5310 6000 +2^18 kzalloc 153060 5610 +2^18 vmalloc 77830 46190 +2^18 get_free_pages 4480 6020 +2^19 kmalloc 10010 6900 +2^19 kzalloc 1068410 11670 +2^19 vmalloc 172820 93110 +2^19 get_free_pages 7930 8630 +2^20 kmalloc 14860 8660 +2^20 kzalloc 552680 11950 +2^20 vmalloc 235970 186950 +2^20 get_free_pages 10250 9560 +2^21 kmalloc 27500 16920 +2^21 kzalloc 1291920 26230 +2^21 vmalloc 715850 366160 +2^21 get_free_pages 15300 14600 +2^22 kmalloc 47150 26910 +2^22 kzalloc 2406550 51710 +2^22 vmalloc 1176980 709480 +2^22 get_free_pages 27620 23490 +2^23 kmalloc error +2^23 kzalloc error +2^23 vmalloc 1699320 1453870 +2^23 get_free_pages error +2^24 kmalloc error +2^24 kzalloc error +2^24 vmalloc 3499200 3032510 +2^24 get_free_pages error +# rmmod /home/user/memory_tst_mod.ko +# + diff --git a/06_memory/Module/makefile b/06_memory/Module/makefile new file mode 100644 index 0000000..9ef0906 --- /dev/null +++ b/06_memory/Module/makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.7/ #WARNING relative path + +obj-m := memory_tst_mod.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/06_memory/Module/memory_tst_mod.c b/06_memory/Module/memory_tst_mod.c new file mode 100644 index 0000000..0824fa7 --- /dev/null +++ b/06_memory/Module/memory_tst_mod.c @@ -0,0 +1,237 @@ +/* + * Memory allocation/freeing time calculation module + */ +#include // Core header for loading LKMs into the kernel +#include +#include +#include +#include + + +MODULE_AUTHOR("Volodymyr Kniazkyi "); +MODULE_DESCRIPTION("Memory allocation/freeing time calculation module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +#define MEM_PAGE_SIZE 4096 +#define MAX_POWER_2 25 +#define MAX_MEM_ORDER 10 + +#define FUNC_KMALLOC 0 +#define FUNC_KZALLOC 1 +#define FUNC_VMALLOC 2 +#define FUNC_GFPAGES 3 + +#define FUNC_MAX_ID 4 + +#define FUNC_MAX_NAME 15 + +char func_name[FUNC_MAX_ID][FUNC_MAX_NAME] = { + "kmalloc ", + "kzalloc ", + "vmalloc ", + "get_free_pages"}; + +unsigned int get_page_order(int64_t size) +{ + unsigned int order; + int64_t npage; + + npage = size / MEM_PAGE_SIZE; + + if ((size % MEM_PAGE_SIZE) != 0) + npage++; + + for (order = 0; order < MAX_MEM_ORDER + 1; order++) { + if (npage <= (1ull << order)) + break; + } + return order; +} +/* + * Measure time for allocate memory + */ +static void *get_memory(int8_t func_id, int64_t size, int64_t *time) +{ + char *buf = NULL; + + int64_t start = 0; + int64_t stop = 0; + + if (time != NULL) + *time = 0; + + //start = ktime_get_ns(); + + switch (func_id) { + case FUNC_KMALLOC: + start = ktime_get_ns(); + buf = kmalloc(size, GFP_KERNEL); + stop = ktime_get_ns(); + break; + case FUNC_KZALLOC: + start = ktime_get_ns(); + buf = kzalloc(size, GFP_KERNEL); + stop = ktime_get_ns(); + break; + case FUNC_VMALLOC: + start = ktime_get_ns(); + buf = vmalloc(size); + stop = ktime_get_ns(); + break; + case FUNC_GFPAGES: + { + unsigned int order; + + order = get_page_order(size); + if (order > MAX_MEM_ORDER) + break; + + start = ktime_get_ns(); + buf = (char *)__get_free_pages(GFP_KERNEL, order); + stop = ktime_get_ns(); + } + break; + default: + return NULL; + } + + //stop = ktime_get_ns(); + + if (time != NULL) { + if (buf == NULL) + *time = -1;// error happened + else + *time = stop - start; + } + + return (void *)buf; +} + +/* + * Measure time for freeing memory + */ +static void free_memory(void *buf, int8_t func_id, int64_t size, int64_t *time) +{ + int64_t start = 0; + int64_t stop = 0; + + if (time != NULL) + *time = 0; + + if (buf == NULL) + return; + + //start = ktime_get_ns(); + + switch (func_id) { + case FUNC_KMALLOC: + start = ktime_get_ns(); + kfree(buf); + stop = ktime_get_ns(); + break; + case FUNC_KZALLOC: + start = ktime_get_ns(); + kfree(buf); + stop = ktime_get_ns(); + break; + case FUNC_VMALLOC: + start = ktime_get_ns(); + vfree(buf); + stop = ktime_get_ns(); + break; + case FUNC_GFPAGES: + { + unsigned int order; + + order = get_page_order(size); + + start = ktime_get_ns(); + free_pages((unsigned long)buf, order); + stop = ktime_get_ns(); + } + break; + default: + return; + } + + //stop = ktime_get_ns(); + + if (time != NULL) { + if (buf == NULL) + *time = -1;// error happened + else + *time = stop - start; + } +} + +/* + * Show test results + */ +static ssize_t memtst_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + size_t wrlen = 0; + int8_t func_id; + int8_t s = 0; + int64_t size = 0; + int64_t tim_alloc; + int64_t tim_dealloc; + + char *membuf = NULL; + + wrlen = sprintf(buf, "\n\tMemory allocation/deallocation test\n\n"); + + wrlen += sprintf(buf+wrlen, "Size\t\tFunction\t\tAlloc(ns)\tDealloc(ns)\n"); + + for (s = 0; s < MAX_POWER_2; s++) { + + size = 1ull << s; + + for (func_id = 0; func_id < FUNC_MAX_ID; func_id++) { + membuf = get_memory(func_id, size, &tim_alloc); + if (buf != NULL) + free_memory(membuf, func_id, size, &tim_dealloc); + if (tim_alloc == -1) + wrlen += sprintf(buf+wrlen, "2^%i\t\t%s\t\terror\n", + s, &func_name[func_id][0]); + else + wrlen += sprintf(buf+wrlen, "2^%i\t\t%s\t\t%llu\t\t%llu\n", + s, &func_name[func_id][0], tim_alloc, tim_dealloc); + } + } + return wrlen; +} + +static struct kobj_attribute memtst_attribute = + __ATTR(getmemtst, 0444, memtst_show, NULL); + +static struct kobject *memtst_kobj; + + +static int memtst_module_init(void) +{ + int res; + + memtst_kobj = kobject_create_and_add("memory_tst_mod", kernel_kobj); + if (!memtst_kobj) + return -ENOMEM; + + res = sysfs_create_file(memtst_kobj, &memtst_attribute.attr); + if (res) { + kobject_put(memtst_kobj); + return res; + } + + //pr_info("module init\n"); + return res; +} + +static void memtst_module_exit(void) +{ + kobject_put(memtst_kobj); + //pr_info("module exit\n"); +} + +module_init(memtst_module_init); +module_exit(memtst_module_exit); From 8fc8fea525485117f52e60902e66d48358158964 Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Sun, 12 Dec 2021 23:27:27 +0200 Subject: [PATCH 13/17] 07_procfs: Add homework for ProcFS and platform bootup lesson Create kernel module with procfs API wich: - 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 --- 07_procfs/Makefile | 9 ++ 07_procfs/orangepi_dump.txt | 41 +++++++++ 07_procfs/procfs_mod.c | 172 ++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 07_procfs/Makefile create mode 100644 07_procfs/orangepi_dump.txt create mode 100644 07_procfs/procfs_mod.c diff --git a/07_procfs/Makefile b/07_procfs/Makefile new file mode 100644 index 0000000..7f15532 --- /dev/null +++ b/07_procfs/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.10/ #WARNING relative path + +obj-m := procfs_mod.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/07_procfs/orangepi_dump.txt b/07_procfs/orangepi_dump.txt new file mode 100644 index 0000000..7b9e6e8 --- /dev/null +++ b/07_procfs/orangepi_dump.txt @@ -0,0 +1,41 @@ +Welcome to Buildroot for the Orange Pi Zero +OrangePi_Zero login: root +# uname +Linux +# mount -t nfs -o nolock 192.168.1.117:/export /mnt/mine +# cd /mnt/mine/ +# insmod procfs_mod.ko +[ 117.911129] module loaded +# ls /proc/my_dir +my_info rd_file wr_file +# cat /proc/my_dir/my_info +Module name procfs_mod version 1.0 +Module author Volodymyr Kniazkyi +# cat /proc/my_dir/rd_file +You tried to read 1 times +# cat /proc/my_dir/rd_file +You tried to read 2 times +# cat /proc/my_dir/rd_file +You tried to read 3 times +# cat /proc/my_dir/rd_file +You tried to read 4 times +# cat /proc/my_dir/rd_file +You tried to read 5 times +# echo 'hello 1' > /proc/my_dir/wr_file +# cat /proc/my_dir/wr_file +You tried to write 1 times +# cat /proc/my_dir/wr_file +You tried to write 1 times +# cat /proc/my_dir/wr_file +You tried to write 1 times +# echo 'hello 2' > /proc/my_dir/wr_file +# cat /proc/my_dir/wr_file +You tried to write 2 times +# cat /proc/my_dir/wr_file +You tried to write 2 times +# echo 'hello 3' > /proc/my_dir/wr_file +# cat /proc/my_dir/wr_file +You tried to write 3 times +# rmmod procfs_mod.ko +[ 247.722362] module exited + diff --git a/07_procfs/procfs_mod.c b/07_procfs/procfs_mod.c new file mode 100644 index 0000000..35c226b --- /dev/null +++ b/07_procfs/procfs_mod.c @@ -0,0 +1,172 @@ +/* + * Simple ProcFS module + */ +//#define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME +#include +#include // Core header for loading LKMs into the kernel +#include +#include +#include +#include + +#define MOD_AUTHOR "Volodymyr Kniazkyi " +MODULE_AUTHOR(MOD_AUTHOR); +MODULE_DESCRIPTION("Simple ProcFS module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +#define DIR_NAME "my_dir" +#define RD_FILE_NAME "rd_file" +#define WR_FILE_NAME "wr_file" +#define INF_FILE_NAME "my_info" + +static char msg[200]; + +size_t read_counter; +size_t write_counter; + +static ssize_t rd_readcounter(struct file *file, char __user *pbuf, + size_t count, loff_t *ppos) +{ + ssize_t num, not_copied; + static ssize_t msg_len = 1; + + if (msg_len == 1) { + read_counter++; + msg_len = sprintf(msg, "You tried to read %d times\n", read_counter); + } + + num = min_t(ssize_t, msg_len, count); + if (num) { + not_copied = copy_to_user((void __user *)pbuf, msg, num); + num -= not_copied; + } + + if (num != 0) + msg_len = 0; + else + msg_len = 1; + + return num; +} + +static ssize_t read_info(struct file *file, char __user *pbuf, + size_t count, loff_t *ppos) +{ + ssize_t num, not_copied; + static ssize_t msg_len = 1; + + if (msg_len == 1) { + msg_len = sprintf(msg, "Module name %s version %s\n", THIS_MODULE->name, THIS_MODULE->version); + msg_len += sprintf(msg+msg_len, "Module author %s\n", MOD_AUTHOR); + } + + num = min_t(ssize_t, msg_len, count); + if (num) { + not_copied = copy_to_user((void __user *)pbuf, msg, num); + num -= not_copied; + } + + if (num != 0) + msg_len = 0; + else + msg_len = 1; + + return num; +} + +static ssize_t rd_writecounter(struct file *file, char __user *pbuf, + size_t count, loff_t *ppos) +{ + ssize_t num, not_copied; + static ssize_t msg_len = 1; + + if (msg_len == 1) + msg_len = sprintf(msg, "You tried to write %d times\n", write_counter); + + num = min_t(ssize_t, msg_len, count); + if (num) { + not_copied = copy_to_user((void __user *)pbuf, msg, num); + num -= not_copied; + } + + if (num != 0) + msg_len = 0; + else + msg_len = 1; + + return num; +} + +static ssize_t inc_writecounter(struct file *file, const char __user *pbuf, + size_t count, loff_t *ppos) +{ + write_counter++; + return count; +} + +static struct proc_ops ops_read = { + .proc_read = rd_readcounter, +}; + +static struct proc_ops ops_info = { + .proc_read = read_info, +}; + +static struct proc_ops ops_write = { + .proc_read = rd_writecounter, + .proc_write = inc_writecounter, +}; + +static struct proc_dir_entry *dir; +static struct proc_dir_entry *read_f, *write_f, *info_f; + + +static int procfs_module_init(void) +{ + int res = 0; + + read_counter = 0; + write_counter = 0; + + dir = proc_mkdir(DIR_NAME, NULL); + if (dir == NULL) { + pr_info("procfs_module: Can not create procfs directory\n"); + return -ENOMEM; + } + read_f = proc_create(RD_FILE_NAME, 0444, dir, &ops_read); + if (read_f == NULL) { + pr_info("procfs_module: Can not create procfs entry %s\n", RD_FILE_NAME); + remove_proc_entry(DIR_NAME, NULL); + return -ENOMEM; + } + + info_f = proc_create(INF_FILE_NAME, 0444, dir, &ops_info); + if (read_f == NULL) { + pr_info("procfs_module: Can not create procfs entry %s\n", RD_FILE_NAME); + remove_proc_entry(RD_FILE_NAME, dir); + remove_proc_entry(DIR_NAME, NULL); + return -ENOMEM; + } + + write_f = proc_create(WR_FILE_NAME, 0666, dir, &ops_write); + if (read_f == NULL) { + pr_info("procfs_module: Can not create procfs entry %s\n", WR_FILE_NAME); + remove_proc_entry(RD_FILE_NAME, dir); + remove_proc_entry(INF_FILE_NAME, dir); + remove_proc_entry(DIR_NAME, NULL); + return -ENOMEM; + } + + pr_info("module loaded\n"); + return res; +} + +static void procfs_module_exit(void) +{ + proc_remove(dir); + pr_info("module exited\n"); +} + +module_init(procfs_module_init); +module_exit(procfs_module_exit); From 6149ec5a3d74c0f025b8b006bd6cfc08064ad15c Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Wed, 15 Dec 2021 09:30:26 +0200 Subject: [PATCH 14/17] 07_procfs: Fix copy-paste and codestyle mistakes - Make one line in code shorter by moving procedure parameters to next one; - Fix copy-paste mistakes in module initialization procedure. --- 07_procfs/procfs_mod.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/07_procfs/procfs_mod.c b/07_procfs/procfs_mod.c index 35c226b..0faeba4 100644 --- a/07_procfs/procfs_mod.c +++ b/07_procfs/procfs_mod.c @@ -57,7 +57,9 @@ static ssize_t read_info(struct file *file, char __user *pbuf, static ssize_t msg_len = 1; if (msg_len == 1) { - msg_len = sprintf(msg, "Module name %s version %s\n", THIS_MODULE->name, THIS_MODULE->version); + msg_len = sprintf(msg, "Module name %s version %s\n", + THIS_MODULE->name, + THIS_MODULE->version); msg_len += sprintf(msg+msg_len, "Module author %s\n", MOD_AUTHOR); } @@ -142,18 +144,18 @@ static int procfs_module_init(void) } info_f = proc_create(INF_FILE_NAME, 0444, dir, &ops_info); - if (read_f == NULL) { - pr_info("procfs_module: Can not create procfs entry %s\n", RD_FILE_NAME); + if (info_f == NULL) { + pr_info("procfs_module: Can not create procfs entry %s\n", INF_FILE_NAME); remove_proc_entry(RD_FILE_NAME, dir); remove_proc_entry(DIR_NAME, NULL); return -ENOMEM; } write_f = proc_create(WR_FILE_NAME, 0666, dir, &ops_write); - if (read_f == NULL) { + if (write_f == NULL) { pr_info("procfs_module: Can not create procfs entry %s\n", WR_FILE_NAME); - remove_proc_entry(RD_FILE_NAME, dir); remove_proc_entry(INF_FILE_NAME, dir); + remove_proc_entry(RD_FILE_NAME, dir); remove_proc_entry(DIR_NAME, NULL); return -ENOMEM; } From 045966d00981067ed0800443cd080ae122b7ecb8 Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Fri, 24 Dec 2021 22:58:08 +0200 Subject: [PATCH 15/17] 07_procfs: Fix checkpatch errors Delete trailing whitespace Delete whitespace before tabs --- 07_procfs/procfs_mod.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/07_procfs/procfs_mod.c b/07_procfs/procfs_mod.c index 0faeba4..de6a0df 100644 --- a/07_procfs/procfs_mod.c +++ b/07_procfs/procfs_mod.c @@ -58,7 +58,7 @@ static ssize_t read_info(struct file *file, char __user *pbuf, if (msg_len == 1) { msg_len = sprintf(msg, "Module name %s version %s\n", - THIS_MODULE->name, + THIS_MODULE->name, THIS_MODULE->version); msg_len += sprintf(msg+msg_len, "Module author %s\n", MOD_AUTHOR); } From 626ba084ad9113102947d72ddc5e811018a3b7f0 Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Sun, 26 Dec 2021 22:37:06 +0200 Subject: [PATCH 16/17] 08_irq_handling: Add homework for IRQ-handling lesson Modify and run polling example module on Orange Pi board Create driver with irq handling instead of pooling mechanism --- 08_irq_handling/led_irq/Makefile | 9 ++ 08_irq_handling/led_irq/ledirq_mod.c | 145 ++++++++++++++++++++ 08_irq_handling/led_pool/Makefile | 9 ++ 08_irq_handling/led_pool/ledpool_mod.c | 176 +++++++++++++++++++++++++ 4 files changed, 339 insertions(+) create mode 100644 08_irq_handling/led_irq/Makefile create mode 100644 08_irq_handling/led_irq/ledirq_mod.c create mode 100644 08_irq_handling/led_pool/Makefile create mode 100644 08_irq_handling/led_pool/ledpool_mod.c diff --git a/08_irq_handling/led_irq/Makefile b/08_irq_handling/led_irq/Makefile new file mode 100644 index 0000000..94b7986 --- /dev/null +++ b/08_irq_handling/led_irq/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.10/ #WARNING relative path + +obj-m := ledirq_mod.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/08_irq_handling/led_irq/ledirq_mod.c b/08_irq_handling/led_irq/ledirq_mod.c new file mode 100644 index 0000000..a0a6290 --- /dev/null +++ b/08_irq_handling/led_irq/ledirq_mod.c @@ -0,0 +1,145 @@ +/* + * Simple LED irq handling test module + */ + +#include +#include +#include + +MODULE_AUTHOR("Volodymyr Kniazkyi "); +MODULE_DESCRIPTION("LED irq handling test module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +#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 + * BUTTON (PG7) : GPIO_6_7 + */ + +#define LED_GREEN GPIO_NUMBER(11, 10) +#define LED_RED GPIO_NUMBER(0, 17) +#define BUTTON GPIO_NUMBER(6, 7) + +static int ledg_gpio; +static int ledr_gpio; +static int button_gpio; +static int sw_button_state; +static int button_irq; +static int dev_id; + + +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 button_gpio_init(int gpio) +{ + int res; + + res = gpio_request(gpio, "user_button"); + if (res != 0) + return res; + + res = gpio_direction_input(gpio); + if (res != 0) { + gpio_free(gpio); + return res; + } + + button_gpio = gpio; + sw_button_state = 0; + pr_info("Init GPIO%d OK\n", button_gpio); + return 0; +} + +static void button_gpio_deinit(void) +{ + if (button_gpio >= 0) { + gpio_free(button_gpio); + pr_info("Deinit GPIO%d\n", button_gpio); + } +} + +static irqreturn_t button_handler(int irq, void *devid) +{ + gpio_set_value(ledg_gpio, sw_button_state); + gpio_set_value(ledr_gpio, !sw_button_state); + sw_button_state = !sw_button_state; + return IRQ_NONE; +} + +/* + * Module entry/exit points + */ + +static int __init gpio_irq_init(void) +{ + int res; + + pr_info("GPIO Init\n"); + + dev_id = 0; + + res = button_gpio_init(BUTTON); + if (res != 0) { + pr_err("Can't set GPIO%d for button\n", BUTTON); + return res; + } + + res = led_gpio_init(LED_GREEN, &ledg_gpio); + if (res != 0) { + pr_err("Can't set GPIO%d for output\n", LED_GREEN); + button_gpio_deinit(); + return res; + } + pr_info("Set GPIO%d for output\n", ledg_gpio); + + gpio_set_value(ledg_gpio, 0); + + res = led_gpio_init(LED_RED, &ledr_gpio); + if (res != 0) { + pr_err("Can't set GPIO%d for output\n", LED_RED); + button_gpio_deinit(); + return res; + } + pr_info("Set GPIO%d for output\n", ledr_gpio); + + gpio_set_value(ledr_gpio, 0); + + button_irq = gpio_to_irq(BUTTON); + if (button_irq < 0) { + pr_err("Can't find GPIO%d IRQ\n", BUTTON); + button_gpio_deinit(); + } + + res = request_irq(button_irq, button_handler, IRQF_SHARED, "user_button_irq", &dev_id); + if (res < 0) { + pr_err("Request for IRQ %d failed\n", button_irq); + button_gpio_deinit(); + } + return 0; +} + +static void __exit gpio_irq_exit(void) +{ + free_irq(button_irq, &dev_id); + gpio_set_value(ledg_gpio, 0); + gpio_set_value(ledr_gpio, 0); + button_gpio_deinit(); +} + +module_init(gpio_irq_init); +module_exit(gpio_irq_exit); diff --git a/08_irq_handling/led_pool/Makefile b/08_irq_handling/led_pool/Makefile new file mode 100644 index 0000000..219d065 --- /dev/null +++ b/08_irq_handling/led_pool/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.10/ #WARNING relative path + +obj-m := ledpool_mod.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/08_irq_handling/led_pool/ledpool_mod.c b/08_irq_handling/led_pool/ledpool_mod.c new file mode 100644 index 0000000..e870796 --- /dev/null +++ b/08_irq_handling/led_pool/ledpool_mod.c @@ -0,0 +1,176 @@ +/* + * Simple LED handling test module with pooling mechanism + */ + +#include +#include +#include +#include + +MODULE_AUTHOR("Volodymyr Kniazkyi "); +MODULE_DESCRIPTION("LED test pool module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +#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 + * BUTTON (PG7) : GPIO_6_7 + */ + +#define LED_GREEN GPIO_NUMBER(11, 10) +#define LED_RED GPIO_NUMBER(0, 17) +#define BUTTON GPIO_NUMBER(6, 7) + +#define TIMER_ENABLE 1 + +static int ledg_gpio; +static int ledr_gpio; +static int button_gpio; +static int button_state; +static int sw_button_state; +static int was_pressed; +static int button_cnt; + +#ifdef TIMER_ENABLE +static ktime_t timer_period; +struct hrtimer button_timer; + +static enum hrtimer_restart timer_callback(struct hrtimer *timer) +{ + int cur_button_state; + + cur_button_state = gpio_get_value(button_gpio); + button_cnt = (cur_button_state == button_state) ? (button_cnt + 1) : 0; + button_state = cur_button_state; + + //Button has been in the same state for 20 msec + if (button_cnt >= 20) { + //Button has been pressed at least 20 msec + if (button_state == 1) { + //Button was pressed before + if (was_pressed == 0) { + //invert green led + gpio_set_value(ledg_gpio, sw_button_state); + /* invert red led */ + gpio_set_value(ledr_gpio, !sw_button_state); + // invert led's states + sw_button_state = !sw_button_state; + was_pressed = 1; + } + } else + was_pressed = 0; + } + + hrtimer_forward(timer, timer->base->get_time(), timer_period); + + return HRTIMER_RESTART; //restart timer +} + +#endif + +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 button_gpio_init(int gpio) +{ + int res; + + res = gpio_request(gpio, "User button"); + if (res != 0) + return res; + + res = gpio_direction_input(gpio); + if (res != 0) { + gpio_free(gpio); + return res; + } + + button_gpio = gpio; + pr_info("Init GPIO%d OK\n", button_gpio); + button_state = gpio_get_value(button_gpio); + button_cnt = 0; + was_pressed = 0; + sw_button_state = button_state; + return 0; +} + +static void button_gpio_deinit(void) +{ + if (button_gpio >= 0) { + gpio_free(button_gpio); + pr_info("Deinit GPIO%d\n", button_gpio); + } +} + +/* + * Module entry/exit points + */ + +static int __init gpio_poll_init(void) +{ + int res; + + pr_info("GPIO Init\n"); + + res = button_gpio_init(BUTTON); + if (res != 0) { + pr_err("Can't set GPIO%d for button\n", BUTTON); + return res; + } + +#ifdef TIMER_ENABLE + timer_period = ktime_set(0, 1000000); /*1 msec*/ + hrtimer_init(&button_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_start(&button_timer, timer_period, HRTIMER_MODE_REL); + button_timer.function = timer_callback; +#endif + + res = led_gpio_init(LED_GREEN, &ledg_gpio); + if (res != 0) { + pr_err("Can't set GPIO%d for output\n", LED_GREEN); + button_gpio_deinit(); + return res; + } + pr_info("Set GPIO%d for output\n", ledg_gpio); + + gpio_set_value(ledg_gpio, 0); + + res = led_gpio_init(LED_RED, &ledr_gpio); + if (res != 0) { + pr_err("Can't set GPIO%d for output\n", LED_RED); + button_gpio_deinit(); + return res; + } + pr_info("Set GPIO%d for output\n", ledr_gpio); + + gpio_set_value(ledr_gpio, 0); + + return 0; +} + +static void __exit gpio_poll_exit(void) +{ + gpio_set_value(ledg_gpio, 0); + gpio_set_value(ledr_gpio, 0); + button_gpio_deinit(); +#ifdef TIMER_ENABLE + hrtimer_cancel(&button_timer); +#endif +} + +module_init(gpio_poll_init); +module_exit(gpio_poll_exit); From 90e9dd3508662eccbd2acf9facc088c184992518 Mon Sep 17 00:00:00 2001 From: Volodymyr Kniazkyi Date: Wed, 12 Jan 2022 12:20:12 +0200 Subject: [PATCH 17/17] 10_chardev: Add homework for character devices lesson Create character device driver to control status led and user button Create userspace application to control status led and user button Modify and run simple web-server to control status led --- 10_chardev/Makefile | 16 +++ 10_chardev/dev.h | 12 ++ 10_chardev/index.html | 23 ++++ 10_chardev/ioctl_app.c | 114 ++++++++++++++++ 10_chardev/ioctl_mod.c | 265 ++++++++++++++++++++++++++++++++++++ 10_chardev/server.c | 296 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 726 insertions(+) create mode 100644 10_chardev/Makefile create mode 100644 10_chardev/dev.h create mode 100644 10_chardev/index.html create mode 100644 10_chardev/ioctl_app.c create mode 100644 10_chardev/ioctl_mod.c create mode 100644 10_chardev/server.c diff --git a/10_chardev/Makefile b/10_chardev/Makefile new file mode 100644 index 0000000..53ae68d --- /dev/null +++ b/10_chardev/Makefile @@ -0,0 +1,16 @@ +KERNELDIR ?= ../output/build/linux-5.10.10/ #WARNING relative path + +obj-m := ioctl_mod.o + +all: clean build ioctrl + +build: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean + rm -rf server + +ioctrl: + $(CROSS_COMPILE)gcc server.c -o server + $(CROSS_COMPILE)gcc ioctl_app.c -o ioctl_app \ No newline at end of file diff --git a/10_chardev/dev.h b/10_chardev/dev.h new file mode 100644 index 0000000..bebbf97 --- /dev/null +++ b/10_chardev/dev.h @@ -0,0 +1,12 @@ +#ifndef __DEVICE_H__ +#define __DEVICE_H__ + +#define IOC_MAGIC 'h' +#define IOCTL_LEDGSET_VAL _IOW(IOC_MAGIC, 1, unsigned int) +#define IOCTL_LEDRSET_VAL _IOW(IOC_MAGIC, 2, unsigned int) +#define IOCTL_BUTTGET_VAL _IOR(IOC_MAGIC, 3, unsigned int) + +#define DEVICE_NAME "ledioctl" +#define DEVPATH "/dev/ledioctl" + +#endif diff --git a/10_chardev/index.html b/10_chardev/index.html new file mode 100644 index 0000000..af6dc92 --- /dev/null +++ b/10_chardev/index.html @@ -0,0 +1,23 @@ + + +
+ +
+
+ +
+
+ +
+
+ +
+ \ No newline at end of file diff --git a/10_chardev/ioctl_app.c b/10_chardev/ioctl_app.c new file mode 100644 index 0000000..70d2c97 --- /dev/null +++ b/10_chardev/ioctl_app.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include +#include "dev.h" + + +#define SYMB_BUTTON 'b' +#define SYMB_EXIT 'e' +#define SYMB_GREENLED 'g' +#define SYMB_REDLED 'r' + +int main(void) +{ + char sdevice[256], device, action; + char bstate; + int fdev; + + device = action = 0; + bstate = 0; + + for (;;) { + // choise device + for (;;) { + printf("\nPlease, choose Green LED (%c) - Red LED(%c)"\ + " - Button(%c) exit - (%c)\n", + SYMB_GREENLED, SYMB_REDLED, SYMB_BUTTON, SYMB_EXIT); + + if (fgets((char *)sdevice, sizeof(sdevice), stdin) == NULL) { + printf("\nInput error"); + return 1; + } + + if (strlen((char *)sdevice) > 2) { + printf("\n too much symbols (%li) %s", + strlen((char *)sdevice)-1, sdevice); + continue; + } + + device = sdevice[0]; + + if (device == SYMB_EXIT) + return 0; + + if ((device == SYMB_GREENLED) || (device == SYMB_REDLED) + || (device == SYMB_BUTTON)) + break; + + printf("\n\tWrong choise - (%c)\n\t", device); + } + + if ((device == SYMB_GREENLED) || (device == SYMB_REDLED)) { + for (;;) { + printf( + "Please, choose action switch on/off (1/0), exit - (%c)\n", + SYMB_EXIT); + + if (fgets((char *)sdevice, sizeof(sdevice), stdin) == NULL) { + printf("\n\tInput error"); + return 1; + } + + if (strlen((char *)sdevice) > 2) { + printf("\n\t too much symbols (%li) %s", + strlen((char *)sdevice)-1, sdevice); + continue; + } + + action = sdevice[0]; + + if (action == SYMB_EXIT) + return 0; + if ((action == '1') || (action == '0')) + break; + printf("\n\tWrong choise - (%c)\n\t", device); + } + } + + fdev = open(DEVPATH, O_RDWR); + if (fdev >= 0) { + switch (device) { + default: + case SYMB_BUTTON: + ioctl(fdev, IOCTL_BUTTGET_VAL, &bstate); + printf("\n Button state %d", bstate); + break; + case SYMB_GREENLED: + if (action == '1') { + ioctl(fdev, IOCTL_LEDGSET_VAL, 1); + printf("\n Green led is switched on"); + } else { + ioctl(fdev, IOCTL_LEDGSET_VAL, 0); + printf("\n Green led is switched off"); + } + break; + case SYMB_REDLED: + if (action == '1') { + ioctl(fdev, IOCTL_LEDRSET_VAL, 1); + printf("\n Red led is switched on"); + } else { + ioctl(fdev, IOCTL_LEDRSET_VAL, 0); + printf("\n Red led is switched off"); + } + break; + } + close(fdev); + } else { + printf("\n Driver is not ready"); + } + } + return 0; +} diff --git a/10_chardev/ioctl_mod.c b/10_chardev/ioctl_mod.c new file mode 100644 index 0000000..89ad20a --- /dev/null +++ b/10_chardev/ioctl_mod.c @@ -0,0 +1,265 @@ +#include +#include +#include +#include +#include "dev.h" + +MODULE_AUTHOR("Volodymyr Kniazkyi "); +MODULE_DESCRIPTION("Character device example driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); + +#define CLASS_NAME "chrdev" +#define BUFFER_SIZE 1024 + +static struct class *pclass; +static struct device *pdev; + +static int major; + +#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 + * BUTTON (PG7) : GPIO_6_7 + */ + +#define LED_GREEN GPIO_NUMBER(11, 10) +#define LED_RED GPIO_NUMBER(0, 17) +#define BUTTON GPIO_NUMBER(6, 7) + +static int ledg_gpio; +static int ledr_gpio; +static int button_gpio; + +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 button_gpio_init(int gpio) +{ + int res; + + res = gpio_request(gpio, "User button"); + if (res != 0) + return res; + + res = gpio_direction_input(gpio); + if (res != 0) { + gpio_free(gpio); + return res; + } + + button_gpio = gpio; + pr_info("Init GPIO%d OK\n", button_gpio); + return 0; +} + +static void button_gpio_deinit(void) +{ + if (button_gpio >= 0) { + gpio_free(button_gpio); + pr_info("Deinit GPIO%d\n", button_gpio); + } +} + +static int is_open; +static int data_size; +static unsigned char data_buffer[BUFFER_SIZE]; + +static int dev_open(struct inode *inodep, struct file *filep) +{ + if (is_open) { + pr_err("chrdev: already open\n"); + return -EBUSY; + } + + is_open = 1; + //pr_info("chrdev: device opened\n"); + return 0; +} + +static int dev_release(struct inode *inodep, struct file *filep) +{ + is_open = 0; + //pr_info("chrdev: device closed\n"); + return 0; +} + + +static ssize_t dev_read(struct file *filep, char *buffer, size_t len, loff_t *offset) +{ + int ret; + + pr_info("chrdev: read from file %s\n", filep->f_path.dentry->d_iname); + pr_info("chrdev: read from device %d:%d\n", imajor(filep->f_inode), iminor(filep->f_inode)); + + if (len > data_size) + len = data_size; + + ret = copy_to_user(buffer, data_buffer, len); + if (ret) { + pr_err("chrdev: copy_to_user failed: %d\n", ret); + return -EFAULT; + } + data_size = 0; /* eof for cat */ + + pr_info("chrdev: %zu bytes read\n", len); + return len; +} + +static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) +{ + int ret; + + pr_info("chrdev: write to file %s\n", filep->f_path.dentry->d_iname); + pr_info("chrdev: write to device %d:%d\n", imajor(filep->f_inode), iminor(filep->f_inode)); + + data_size = len; + if (data_size > BUFFER_SIZE) + data_size = BUFFER_SIZE; + + ret = copy_from_user(data_buffer, buffer, data_size); + if (ret) { + pr_err("chrdev: copy_from_user failed: %d\n", ret); + return -EFAULT; + } + + pr_info("chrdev: %d bytes written\n", data_size); + return data_size; +} + +static long dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + if (_IOC_TYPE(cmd) != IOC_MAGIC) + return -ENOTTY; + switch (cmd) { + case IOCTL_BUTTGET_VAL: + { + char bstate; + + if (gpio_get_value(button_gpio) == 0) + bstate = 0; + else + bstate = 1; + if (copy_to_user((char *)arg, &bstate, sizeof(bstate))) + return -EFAULT; + //pr_info("IOCTL_BUTTGET_VAL: %d\n", bstate); + } + break; + case IOCTL_LEDGSET_VAL: + //pr_info("IOCTL_LEDGSET_VAL: %ld\n", arg); + if (arg == 0) + gpio_set_value(ledg_gpio, 0); + else + gpio_set_value(ledg_gpio, 1); + break; + case IOCTL_LEDRSET_VAL: + //pr_info("IOCTL_LEDRSET_VAL: %ld\n", arg); + if (arg == 0) + gpio_set_value(ledr_gpio, 0); + else + gpio_set_value(ledr_gpio, 1); + break; + default: + return -ENOTTY; + } + return 0; +} +static struct file_operations fops = { + .open = dev_open, + .release = dev_release, + .read = dev_read, + .write = dev_write, + .unlocked_ioctl = dev_ioctl +}; + +static int chrdev_init(void) +{ + int ret; + + is_open = 0; + data_size = 0; + + major = register_chrdev(0, DEVICE_NAME, &fops); + if (major < 0) { + pr_err("register_chrdev failed: %d\n", major); + return major; + } + pr_info("chrdev: register_chrdev ok, major = %d\n", major); + + pclass = class_create(THIS_MODULE, CLASS_NAME); + if (IS_ERR(pclass)) { + unregister_chrdev(major, DEVICE_NAME); + pr_err("chrdev: class_create failed\n"); + return PTR_ERR(pclass); + } + pr_info("chrdev: device class created successfully\n"); + + pdev = device_create(pclass, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); + if (IS_ERR(pdev)) { + class_destroy(pclass); + unregister_chrdev(major, DEVICE_NAME); + pr_err("chrdev: device_create failed\n"); + return PTR_ERR(pdev); + } + pr_info("chrdev: device node created successfully\n"); + + pr_info("GPIO Init\n"); + + ret = led_gpio_init(LED_GREEN, &ledg_gpio); + if (ret == 0) { + pr_info("Set GPIO%d for output\n", ledg_gpio); + gpio_set_value(ledg_gpio, 0); + + ret = led_gpio_init(LED_RED, &ledr_gpio); + if (ret == 0) { + pr_info("Set GPIO%d for output\n", ledr_gpio); + gpio_set_value(ledr_gpio, 0); + + ret = button_gpio_init(BUTTON); + if (ret != 0) + pr_err("Can't set GPIO%d for button\n", BUTTON); + } else + pr_err("Can't set GPIO%d for output\n", LED_RED); + } else + pr_err("Can't set GPIO%d for output\n", LED_GREEN); + + if (ret != 0) { + device_destroy(pclass, MKDEV(major, 0)); + class_destroy(pclass); + unregister_chrdev(major, DEVICE_NAME); + return ret; + } + + pr_info("chrdev: module loaded\n"); + return 0; +} + +static void chrdev_exit(void) +{ + gpio_set_value(ledg_gpio, 0); + gpio_set_value(ledr_gpio, 0); + + device_destroy(pclass, MKDEV(major, 0)); + class_destroy(pclass); + unregister_chrdev(major, DEVICE_NAME); + + button_gpio_deinit(); + + pr_info("chrdev: module exited\n"); +} + +module_init(chrdev_init); +module_exit(chrdev_exit); diff --git a/10_chardev/server.c b/10_chardev/server.c new file mode 100644 index 0000000..9a2b4bc --- /dev/null +++ b/10_chardev/server.c @@ -0,0 +1,296 @@ +/* + * This example has been taken from here: + * https://gist.github.com/bdahlia/7826649 + * + * and modified to return only index.html page + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dev.h" +#define BUFSIZE 1024 +#define MAXERRS 16 + +char **environ; /* the environment */ + +/* + * error - wrapper for perror used for bad syscalls + */ +void error(char *msg) +{ + perror(msg); + exit(1); +} + +/* + * cerror - returns an error message to the client + */ +void cerror(FILE *stream, char *cause, char *errno, + char *shortmsg, char *longmsg) +{ + fprintf(stream, "HTTP/1.1 %s %s\n", errno, shortmsg); + fprintf(stream, "Content-type: text/html\n"); + fprintf(stream, "\n"); + fprintf(stream, "Tiny Error"); + fprintf(stream, "\n"); + fprintf(stream, "%s: %s\n", errno, shortmsg); + fprintf(stream, "

%s: %s\n", longmsg, cause); + fprintf(stream, "


The Tiny Web server\n"); +} + +int main(int argc, char **argv) +{ + /* variables for connection management */ + int parentfd; /* parent socket */ + int childfd; /* child socket */ + int portno; /* port to listen on */ + int clientlen; /* byte size of client's address */ + char *hostaddrp; /* dotted decimal host addr string */ + int optval; /* flag value for setsockopt */ + struct sockaddr_in serveraddr; /* server's addr */ + struct sockaddr_in clientaddr; /* client addr */ + + /* variables for connection I/O */ + FILE *stream; /* stream version of childfd */ + char buf[BUFSIZE]; /* message buffer */ + char method[BUFSIZE]; /* request method */ + char uri[BUFSIZE]; /* request uri */ + char version[BUFSIZE]; /* request method */ + char filename[BUFSIZE];/* path derived from uri */ + char filetype[BUFSIZE];/* path derived from uri */ + char cgiargs[BUFSIZE]; /* cgi argument list */ + char *p; /* temporary pointer */ + int is_static; /* static request? */ + struct stat sbuf; /* file status */ + int fd; /* static content filedes */ + int pid; /* process id from fork */ + int wait_status; /* status from wait */ + + /* variable for connction with chardev */ + int fdev; + + /* check command line args */ + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(1); + } + portno = atoi(argv[1]); + + /* open socket descriptor */ + parentfd = socket(AF_INET, SOCK_STREAM, 0); + if (parentfd < 0) + error("ERROR opening socket"); + + /* allows us to restart server immediately */ + optval = 1; + setsockopt(parentfd, SOL_SOCKET, SO_REUSEADDR, + (const void *)&optval, sizeof(int)); + + /* bind port to socket */ + bzero((char *) &serveraddr, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); + serveraddr.sin_port = htons((unsigned short)portno); + if (bind(parentfd, (struct sockaddr *) &serveraddr, + sizeof(serveraddr)) < 0) + error("ERROR on binding"); + + /* get us ready to accept connection requests */ + if (listen(parentfd, 5) < 0) /* allow 5 requests to queue up */ + error("ERROR on listen"); + /* + * main loop: wait for a connection request, parse HTTP, + * serve requested content, close connection. + */ + clientlen = sizeof(clientaddr); + while (1) { + /* wait for a connection request */ + childfd = accept(parentfd, (struct sockaddr *) &clientaddr, &clientlen); + if (childfd < 0) + error("ERROR on accept"); + + /* determine who sent the message */ +// printf("clientaddr %d.%d.%d.%d\n", +// clientaddr.sin_addr.s_addr&0xFF, +// (clientaddr.sin_addr.s_addr>>8)&0xFF, +// (clientaddr.sin_addr.s_addr>>16)&0xFF, +// (clientaddr.sin_addr.s_addr>>24)&0xFF +// ); + + hostaddrp = inet_ntoa(clientaddr.sin_addr); + if (hostaddrp == NULL) + error("ERROR on inet_ntoa\n"); + + /* open the child socket descriptor as a stream */ + stream = fdopen(childfd, "r+"); + if (stream == NULL) + error("ERROR on fdopen"); + + /* get the HTTP request line */ + fgets(buf, BUFSIZE, stream); + printf("%s", buf); + if (sscanf(buf, "%s %s %s\n", method, uri, version) == EOF) + error("ERROR on sscanf"); + + /* tiny only supports the GET method */ + if (strcasecmp(method, "GET")) { + cerror(stream, method, "501", "Not Implemented", + "Tiny does not implement this method"); + fclose(stream); + close(childfd); + continue; + } + + /* read (and ignore) the HTTP headers */ + fgets(buf, BUFSIZE, stream); + printf("%s", buf); + while (strcmp(buf, "\r\n")) { + fgets(buf, BUFSIZE, stream); + printf("%s", buf); + } + + /* parse the uri [crufty] */ + if (!strstr(uri, "cgi-bin")) { + /* static content */ + printf(" static content: %s\n", uri); + if (strstr(uri, "red=on") != NULL) { + /* static content */ + fdev = open(DEVPATH, O_RDWR); + if (fdev >= 0) { + ioctl(fdev, IOCTL_LEDRSET_VAL, 1); + close(fdev); + } + printf("!!red=on\n"); + } else if (strstr(uri, "red=off") != NULL) { + /* static content */ + fdev = open(DEVPATH, O_RDWR); + if (fdev >= 0) { + ioctl(fdev, IOCTL_LEDRSET_VAL, 0); + close(fdev); + } + printf("!!red=off\n"); + } else if (strstr(uri, "green=on") != NULL) { + /* static content */ + fdev = open(DEVPATH, O_RDWR); + if (fdev >= 0) { + ioctl(fdev, IOCTL_LEDGSET_VAL, 1); + close(fdev); + } + printf("!!!green=on\n"); + } else if (strstr(uri, "green=off") != NULL) { + /* static content */ + fdev = open(DEVPATH, O_RDWR); + if (fdev >= 0) { + ioctl(fdev, IOCTL_LEDGSET_VAL, 0); + close(fdev); + } + printf("!!!green=off\n"); + } + is_static = 1; + strcpy(cgiargs, ""); + strcpy(filename, "."); + strcat(filename, uri); + if (uri[strlen(uri)-1] == '/') + strcat(filename, "index.html"); + } else { + /* dynamic content */ + printf("Not static %s\n", uri); + is_static = 1; + strcpy(cgiargs, ""); + strcpy(filename, "."); + strcat(filename, uri); + if (uri[strlen(uri)-1] == '/') + strcat(filename, "index.html"); + } + + /* make sure the file exists */ + if (stat(filename, &sbuf) < 0) { + is_static = 1; + strcpy(cgiargs, ""); + strcpy(filename, "."); + strcat(filename, uri); + if (uri[strlen(uri)-1] == '/') + strcat(filename, "index.html"); + continue; + } + + /* serve static content */ + if (is_static) { + if (strstr(filename, ".html")) + strcpy(filetype, "text/html"); + else if (strstr(filename, ".gif")) + strcpy(filetype, "image/gif"); + else if (strstr(filename, ".jpg")) + strcpy(filetype, "image/jpg"); + else + strcpy(filetype, "text/plain"); + + /* print response header */ + fprintf(stream, "HTTP/1.1 200 OK\n"); + fprintf(stream, "Server: Tiny Web Server\n"); + fprintf(stream, "Content-length: %d\n", (int)sbuf.st_size); + fprintf(stream, "Content-type: %s\n", filetype); + fprintf(stream, "\r\n"); + fflush(stream); + + /* Use mmap to return arbitrary-sized response body */ + fd = open(filename, O_RDONLY); + p = mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + fwrite(p, 1, sbuf.st_size, stream); + munmap(p, sbuf.st_size); + } else { + /* serve dynamic content */ + /* make sure file is a regular executable file */ + if (!(S_IFREG & sbuf.st_mode) || !(/*S_IXUSR*/ 0100 & sbuf.st_mode)) { + cerror(stream, filename, "403", "Forbidden", + "You are not allow to access this item"); + fclose(stream); + close(childfd); + continue; + } + + /* a real server would set other CGI environ vars as well*/ + setenv("QUERY_STRING", cgiargs, 1); + + /* print first part of response header */ + sprintf(buf, "HTTP/1.1 200 OK\n"); + write(childfd, buf, strlen(buf)); + sprintf(buf, "Server: Tiny Web Server\n"); + write(childfd, buf, strlen(buf)); + /* + * create and run the child CGI process so that all child + * output to stdout and stderr goes back to the client via the + * childfd socket descriptor + */ + pid = fork(); + if (pid < 0) { + perror("ERROR in fork"); + exit(1); + } else if (pid > 0) { /* parent process */ + wait(&wait_status); + } else { /* child process*/ + close(0); /* close stdin */ + dup2(childfd, 1); /* map socket to stdout */ + dup2(childfd, 2); /* map socket to stderr */ + if (execve(filename, NULL, environ) < 0) + perror("ERROR in execve"); + } + } + + /* clean up */ + fclose(stream); + close(childfd); + } +}