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 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 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); 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); 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..2745f67 --- /dev/null +++ b/05_timers/Module/clc_time.c @@ -0,0 +1,121 @@ +/* + * 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 +# + + 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/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); 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/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/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/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..de6a0df --- /dev/null +++ b/07_procfs/procfs_mod.c @@ -0,0 +1,174 @@ +/* + * 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 (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 (write_f == NULL) { + pr_info("procfs_module: Can not create procfs entry %s\n", WR_FILE_NAME); + remove_proc_entry(INF_FILE_NAME, dir); + remove_proc_entry(RD_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); 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_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_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 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); 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/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/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); + } +}