diff --git a/Group-3-main/Group-3-main/.gitignore b/Group-3-main/Group-3-main/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..144e9b1da081b8cfdd14bddf46eeb9772929a293 --- /dev/null +++ b/Group-3-main/Group-3-main/.gitignore @@ -0,0 +1,97 @@ +.gitignore +# +# NOTE! Don't add files that are generated in specific +# subdirectories here. Add them in the ".gitignore" file +# in that subdirectory instead. +# +# NOTE! Please use 'git ls-files -i --exclude-standard' +# command after changing this file, to see if there are +# any tracked files which get ignored after the change. +# +# Normal rules +# +.* +*.o +*.o.* +*.a +*.s +*.ko +*.so +*.so.dbg +*.mod.c +*.mod +*.i +*.lst +*.symtypes +*.order +modules.builtin +*.elf +*.bin +*.gz +*.bz2 +*.lzma +*.xz +*.lzo +*.patch +*.gcno +*.dtbo + +# +# Top-level generic files +# +/tags +/TAGS +/linux +/vmlinux +/vmlinuz +/System.map +/Module.markers +Module.symvers + +# +# Debian directory (make deb-pkg) +# +/debian/ + +# +# git files that we don't want to ignore even it they are dot-files +# +!.gitignore +!.mailmap + +# +# Generated include files +# +include/config +include/linux/version.h +include/generated +arch/*/include/generated + +# stgit generated dirs +patches-* + +# quilt's files +patches +series + +# cscope files +cscope.* +ncscope.* + +# gnu global files +GPATH +GRTAGS +GSYMS +GTAGS + +*.orig +*~ +\#*# + +# +# Leavings from module signing +# +extra_certificates +signing_key.priv +signing_key.x509 +x509.genkey diff --git a/Group-3-main/Group-3-main/Lektion3/Makefile b/Group-3-main/Group-3-main/Lektion3/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..26bce463c3627aef3f28802ac83cc2b817e3613a --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion3/Makefile @@ -0,0 +1,7 @@ +obj-m:=hello.o +ccflags-y:=-std=gnu99 -Wno-declaration-after-statement -Werror +KERNELDIR?=~/sources/rpi-5.4.83/ +all default:modules +install:modules_install +modules modules_install help clean: + $(MAKE) ARCH=arm CROSS_COMPILE=arm-poky-linux-gnueabi- -C $(KERNELDIR) M=$(shell pwd) $@ diff --git a/Group-3-main/Group-3-main/Lektion3/hello.c b/Group-3-main/Group-3-main/Lektion3/hello.c new file mode 100644 index 0000000000000000000000000000000000000000..af4fbcd487bb0537c9b4fe8b0842786c1ea61acc --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion3/hello.c @@ -0,0 +1,15 @@ +#include <linux/init.h> +#include <linux/module.h> +MODULE_LICENSE("Dual BSD/GPL"); +static int hello_init(void) +{ + printk(KERN_ALERT "Hello, world\n"); + return 0; +} +static void hello_exit(void) +{ + printk(KERN_ALERT "Goodbye, cruel world\n"); +} +module_init(hello_init); +module_exit(hello_exit); + diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/Group3 - Aflevering 1 - HAL.pdf b/Group-3-main/Group-3-main/Lektion4(Afl1)/Group3 - Aflevering 1 - HAL.pdf new file mode 100644 index 0000000000000000000000000000000000000000..06acd94d5297c9d60ca5d8e1ad3098734da083df Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/Group3 - Aflevering 1 - HAL.pdf differ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/LED3/Makefile b/Group-3-main/Group-3-main/Lektion4(Afl1)/LED3/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9b407374c629f120708bed1887014547aa4d0669 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion4(Afl1)/LED3/Makefile @@ -0,0 +1,7 @@ +obj-m:= led3.o +ccflags-y:= -std=gnu99 -Wno-declaration-after-statement -Werror +KERNELDIR?= ~/sources/rpi-5.4.83 +all default: modules +install: modules_install +modules modules_install help clean: + $(MAKE) ARCH=arm CROSS_COMPILE=arm-poky-linux-gnueabi- -C $(KERNELDIR) M=$(shell pwd) $@ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/LED3/led3.c b/Group-3-main/Group-3-main/Lektion4(Afl1)/LED3/led3.c new file mode 100644 index 0000000000000000000000000000000000000000..ffae3aa9bb91e99c94a3418ff0b20a4c2c74778d --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion4(Afl1)/LED3/led3.c @@ -0,0 +1,149 @@ +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/module.h> + +#define LED3 21 // GPIO pin number for LED3 (make sure it's correct) +static dev_t devno; +static struct class *led_class; +static struct cdev led_cdev; +struct file_operations led_fops; + +const int first_minor = 0; +const int max_devices = 255; + +static int mygpio_init(void) { + int err = 0; + + // Request GPIO for LED3 + err = gpio_request(LED3, "LED3"); + if (err) { + pr_err("Failed to request GPIO for LED3\n"); + return err; + } + + // Set GPIO direction (output) + err = gpio_direction_output(LED3, 0); // Initialize to 0 (off) + if (err) { + pr_err("Failed to set GPIO direction for LED3\n"); + goto err_free_gpio; + } + + // Allocate Major/Minor numbers + err = alloc_chrdev_region(&devno, first_minor, max_devices, "led-driver"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + goto err_free_gpio; + } + + pr_info("LED driver got Major %i\n", MAJOR(devno)); + + // Create class + led_class = class_create(THIS_MODULE, "led_class"); + if (IS_ERR(led_class)) { + pr_err("Failed to create class for LED3\n"); + err = PTR_ERR(led_class); + goto err_unregister_chrdev; + } + + // Initialize and add cdev + cdev_init(&led_cdev, &led_fops); + err = cdev_add(&led_cdev, devno, 1); // Register one device only + if (err < 0) { + pr_err("Failed to add cdev for LED3\n"); + goto err_destroy_class; + } + + return 0; + +err_destroy_class: + class_destroy(led_class); +err_unregister_chrdev: + unregister_chrdev_region(devno, max_devices); +err_free_gpio: + gpio_free(LED3); + + return err; +} + +static void mygpio_exit(void) { + cdev_del(&led_cdev); + unregister_chrdev_region(devno, max_devices); + class_destroy(led_class); + gpio_free(LED3); +} + +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; // Null-terminate the string + + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(LED3, value); // Set GPIO value (output) + + pr_info("Wrote %i to LED3\n", value); + *f_pos += len; + return len; +} + +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + + value = gpio_get_value(LED3); // Read GPIO value (output) + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (*f_pos > 0) { + return 0; // EOF + } + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; + return len; +} + +struct file_operations led_fops = { + .open = mygpio_open, + .release = mygpio_release, + .read = mygpio_read, + .write = mygpio_write, +}; + +module_init(mygpio_init); +module_exit(mygpio_exit); + +MODULE_AUTHOR("Khaled <Khaled.uni.au@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/PXL_20241001_110151752.jpg b/Group-3-main/Group-3-main/Lektion4(Afl1)/PXL_20241001_110151752.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cdc643319df1d46c3a30d37b4ef34695ba9642e9 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/PXL_20241001_110151752.jpg differ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/README.md b/Group-3-main/Group-3-main/Lektion4(Afl1)/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cf8f31140811a20a84c0ecc16519e325cd466360 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion4(Afl1)/README.md @@ -0,0 +1,202 @@ +# Group 3 +# Members +* John Nguyen +* Ibrahem Merhi +* Khaled Rami Omar + +# Link to repository +https://gitlab.au.dk/au-ece-sw3hal/e2024/Group-3.git + +# Øvelse: Linux Device Driver med GPIO (Rpi) + +## Formål + +Denne øvelse giver erfaring med at skrive, kompilere og indsætte en basal Linux Device Driver. + +Du forventes med øvelsen at opfylde følgende mål: +| Mål | Delmål 1 | Delmål 2 | Delmål 3 | +|-------------------------------------------|---------------------------------------|---------------------------------------|-----------------------------| +|Initialisere af driver|init gpio interfaces | Init major/minor numre og cdev| Lave fejlhåndtering | +|Implementere filoperationer |Impl. og test open/release | Impl. read |Impl.write| +|Anvende driver|Indsætte driver og oprette major/minor numre |Anvende file operations |Anvende kernel log til debugging/test| + +## Øvelsen + +Lav 2 drivere, der hhv. håndterer læsning og skrivning. + +* Driver 1: Læsning fra devicet (/dev/sw2) returnerer hvorvidt SW2 er trykket ned eller ej. +* Driver 2: Skrivning til devicet (/dev/led3) tænder og slukker LED3. + +NB! 2 Drivere medfører 2x makefile + 2x driver .c fil, navnene er desuden givet ovenfor. Placér hver driver i sin egen folder. + +Vi anvender SW2 og LED3. Du kan finde deres GPIO numre i diagrammet, ligesom i tidligere øvelse. + +### GPIO LDD driverne i steps + +Nedenfor er vist en overfladisk skabelon som kan bruges som inspiration når I skal lave de 2 drivere. Bemærk at driver 1 ikke har en ```write()``` funktion, da den kun bør kunne læse fra SW2. Den gpio som svarer til SW2 skal altså være sat til input, som er default. Derimod skal driver 2 have både en ```write()``` og en ```read()``` funktion. ```write()``` funktionen er naturligvis for at kunne tænde/slukke for LED3, mens ```read()``` er til for at kunne få at vide om den er tændt eller slukket. I det sidste tilfælde (LED3 driveren) skal gpio'en være et output. + +**HUSK!** At udfylde ```module_init```, ```module_exit```, samt ```licens```, ```author``` og ```description```. + +a) Implementer "```init```" og "```exit```" funktionerne. Det er her I skal requeste og free gpio’s, samt sætte gpio_direction til at styre pin’ens retning. Samtidigt er det afgørende at du/I laver en ordentlig fejlhåndtering i init(). + +```c +static int mygpio_init(void) { + int err = 0; + + // Request GPIO for SW2 + err = gpio_request(SW2, "SW2"); + if (err) { + pr_err("Failed to request GPIO for SW2\n"); + return err; + } + + // Set GPIO direction (input) + err = gpio_direction_input(SW2); + if (err) { + pr_err("Failed to set GPIO direction for SW2\n"); + goto err_free_gpio; + } + + // Allocate Major/Minor numbers + err = alloc_chrdev_region(&devno, first_minor, max_devices, "switch-driver"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + goto err_free_gpio; + } + + pr_info("Switch driver got Major %i\n", MAJOR(devno)); + + // Create class + switch_class = class_create(THIS_MODULE, "switch_class"); + if (IS_ERR(switch_class)) { + pr_err("Failed to create class for SW2\n"); + err = PTR_ERR(switch_class); + goto err_unregister_chrdev; + } + + // Initialize and add cdev + cdev_init(&switch_cdev, &switch_fops); + err = cdev_add(&switch_cdev, devno, 1); // Register one device only + if (err < 0) { + pr_err("Failed to add cdev for SW2\n"); + goto err_destroy_class; + } + + return 0; + +err_destroy_class: + class_destroy(switch_class); +err_unregister_chrdev: + unregister_chrdev_region(devno, max_devices); +err_free_gpio: + gpio_free(SW2); + + return err; +} +``` +Funktionen mygpio_init initialiserer en driver til GPIO-enheden SW2. Den anmoder først om adgang til GPIO-pinen ved hjælp af gpio_request; hvis dette mislykkes, logges en fejl og funktionen returnerer. Derefter indstilles GPIO-pinen til input-tilstand med gpio_direction_input. Hvis dette også fejler, frigives GPIO-ressourcen. Funktionen allokerer herefter major og minor numre via alloc_chrdev_region, som logges, hvis det lykkes. En klasse oprettes med class_create, og ved fejl frigives tidligere ressourcer. Endelig initialiseres og tilføjes den karakterbaserede enhed (cdev) med cdev_add. Fejl under nogen af disse processer håndteres for at sikre korrekt frigivelse af ressourcer. Ved succesfuld initialisering returneres 0. +```c +static void mygpio_exit(void) { + cdev_del(&switch_cdev); + unregister_chrdev_region(devno, max_devices); + class_destroy(switch_class); + gpio_free(SW2); +} +``` + +Funktionen mygpio_exit rydder op ved afinstallation af GPIO-driveren. Den fjerner den karakterbaserede enhed med cdev_del, unregisterer major og minor numre, destruerer klassen med class_destroy, og frigiver GPIO-pinen SW2 med gpio_free. Dette sikrer, at alle ressourcer ryddes korrekt op og undgår hukommelseslækager. + +b) Implementer ```open``` og ```release```. Dvs den kode som eksekveres når en applikation forsøger at åbne/lukke et device (fil). Brug følgende funktioner som de er (De skriver en besked til kernel loggen om hhv open/release med hvilket major og minor nummer): + +```c +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening Switch Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing Switch Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} +``` + +Test ved at bygge-, indsætte driveren, oprette en node. Ex her med major = 239 og minor = 0: +```bash +mknod /dev/mygpio c 239 0 +``` + +c) Implementer ```read``` (Driver 1 - læsning af SW1 + Driver 2 - Er LED3 tændt eller?). Dvs den kode som eksekveres i kernel-space når en user-space applikation forsøger at læse fra et device (fil). + +```c +ssize_t mygpio_read(struct file *filep, char __user *buf, + size_t count, loff_t *f_pos) + { + int value; +char kbuf[12]; +int len; + +// Read GPIO value (input) +value = gpio_get_value(SW2); +len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + +if (*f_pos > 0) { + return 0; // EOF +} + +if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; +} + +*f_pos += len; +return len; + } +``` +Funktionen mygpio_read læser værdien fra GPIO-pinden (SW2) og gemmer den i value. Den formaterer derefter værdien som en streng i kbuf. Hvis læsepositionen (*f_pos) er større end 0, returneres 0 for at angive EOF. Hvis ikke, forsøger den at kopiere dataene til brugerens buffer (buf) med copy_to_user. Hvis kopieringen mislykkes, returneres -EFAULT. Hvis den er succesfuld, opdateres læsepositionen, og længden af de læste bytes returneres. + +Test ved at bygge, scp og indsætte device driveren, samt oprette node. Prøv nu at læse fra noden, den læste værdi skal gerne afspejle tilstanden af trykknapperne. I kan bruge "cat" eller udvide jeres lille testapplikation. Du kan blive inspireret [her](https://redweb.ece.au.dk/devs/projects/devkit8000/wiki/PosixFileRead) og kompilere den med "arm-rpizw-gcc -o rd rd.c". + +Bemærk at programmer som f.eks "cat" læser indtil de møder en End of File karakter (EOF). "cat" vil derfor læse uendeligt fra driveren og læsse bunkevis af data ud på terminalen. + + + +d) Implementer ```write``` (Driver 2 - Skrivning til LED3 - altså om den er tændt eller ej.) + +```c +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; // Null-terminate the string + + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(LED3, value); // Set GPIO value (output) + + pr_info("Wrote %i to LED3\n", value); + *f_pos += len; + return len; +} +``` + +Funktionen mygpio_write håndterer skrivning til en enhed ved at modtage data fra brugeren. Den kopierer data fra brugerens buffer (ubuf) til en intern buffer (kbuf), og sikrer, at den ikke overskrider størrelsen. Hvis kopieringen mislykkes, returneres en fejlkode. Herefter konverteres indholdet af kbuf til et heltal med kstrtoint. Ved fejl i konverteringen logges en fejlmeddelelse, og en fejlkode returneres. Hvis konverteringen lykkes, indstilles GPIO-pinden (LED3) til den ønskede værdi, og der logges en succesmeddelelse. Funktionen afsluttes ved at opdatere læsepositionen og returnere antallet af skrevne bytes. + +Test ved at bygge, scp og indsætte device driveren, samt oprette node. Skriv til noden for at tænde og slukke led'en. +```bash +echo 1 > /dev/led3 +``` + \ No newline at end of file diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/Makefile b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..eb62eff4b980fda45b77eacb26e97d35df0fa3fe --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/Makefile @@ -0,0 +1,7 @@ +obj-m:= sw2.o +ccflags-y:= -std=gnu99 -Wno-declaration-after-statement -Werror +KERNELDIR?= ~/sources/rpi-5.4.83 +all default: modules +install: modules_install +modules modules_install help clean: + $(MAKE) ARCH=arm CROSS_COMPILE=arm-poky-linux-gnueabi- -C $(KERNELDIR) M=$(shell pwd) $@ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/root@10.9.8.2 b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/root@10.9.8.2 new file mode 100644 index 0000000000000000000000000000000000000000..86db5426cb8d97d5f232b31be930f4789d5a1feb Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/root@10.9.8.2 differ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/sw2.c b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/sw2.c new file mode 100644 index 0000000000000000000000000000000000000000..aee2dcb373b0b2f3c5b304554b11125a5f520a48 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2/sw2.c @@ -0,0 +1,125 @@ +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/module.h> + +#define SW2 16 // GPIO pin number for SW2 +static dev_t devno; +static struct class *switch_class; +static struct cdev switch_cdev; +struct file_operations switch_fops; + +const int first_minor = 0; +const int max_devices = 255; + +static int mygpio_init(void) { + int err = 0; + + // Request GPIO for SW2 + err = gpio_request(SW2, "SW2"); + if (err) { + pr_err("Failed to request GPIO for SW2\n"); + return err; + } + + // Set GPIO direction (input) + err = gpio_direction_input(SW2); + if (err) { + pr_err("Failed to set GPIO direction for SW2\n"); + goto err_free_gpio; + } + + // Allocate Major/Minor numbers + err = alloc_chrdev_region(&devno, first_minor, max_devices, "switch-driver"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + goto err_free_gpio; + } + + pr_info("Switch driver got Major %i\n", MAJOR(devno)); + + // Create class + switch_class = class_create(THIS_MODULE, "switch_class"); + if (IS_ERR(switch_class)) { + pr_err("Failed to create class for SW2\n"); + err = PTR_ERR(switch_class); + goto err_unregister_chrdev; + } + + // Initialize and add cdev + cdev_init(&switch_cdev, &switch_fops); + err = cdev_add(&switch_cdev, devno, 1); // Register one device only + if (err < 0) { + pr_err("Failed to add cdev for SW2\n"); + goto err_destroy_class; + } + + return 0; + +err_destroy_class: + class_destroy(switch_class); +err_unregister_chrdev: + unregister_chrdev_region(devno, max_devices); +err_free_gpio: + gpio_free(SW2); + + return err; +} + +static void mygpio_exit(void) { + cdev_del(&switch_cdev); + unregister_chrdev_region(devno, max_devices); + class_destroy(switch_class); + gpio_free(SW2); +} + +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening Switch Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing Switch Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + + // Read GPIO value (input) + value = gpio_get_value(SW2); + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (*f_pos > 0) { + return 0; // EOF + } + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; + return len; +} + +struct file_operations switch_fops = { + .open = mygpio_open, + .release = mygpio_release, + .read = mygpio_read, +}; + +module_init(mygpio_init); +module_exit(mygpio_exit); + +MODULE_AUTHOR("Khaled <Khaled.uni.au@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2_test1_2-1.png b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2_test1_2-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d7ad2205bc0a95da350dc655fa76b0dfc4d979bf Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2_test1_2-1.png differ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2_test1_2.png b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2_test1_2.png new file mode 100644 index 0000000000000000000000000000000000000000..d7ad2205bc0a95da350dc655fa76b0dfc4d979bf Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2_test1_2.png differ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2test1_1-1.png b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2test1_1-1.png new file mode 100644 index 0000000000000000000000000000000000000000..8a3084305c8483fb98a23eeee2ff43e4f5dfc827 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2test1_1-1.png differ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2test1_1.png b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2test1_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8a3084305c8483fb98a23eeee2ff43e4f5dfc827 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/SW2test1_1.png differ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/image-1.png b/Group-3-main/Group-3-main/Lektion4(Afl1)/image-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e421ef6114d14201eca0fd46f6b8e5c1338522e6 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/image-1.png differ diff --git a/Group-3-main/Group-3-main/Lektion4(Afl1)/image.png b/Group-3-main/Group-3-main/Lektion4(Afl1)/image.png new file mode 100644 index 0000000000000000000000000000000000000000..fe313562a989a6c7616872377fd5396095ec9838 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion4(Afl1)/image.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/Makefile b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..db932f6832a316ed9f33e742215f3e2af4eb435b --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/Makefile @@ -0,0 +1,40 @@ +# Kernel Module +KMODULE:=plat_drv +DTB_FILE:=$(KMODULE)-overlay.dtb +DTBO_FILE:=$(KMODULE).dtbo +KERNELDIR=~/sources/rpi-5.4.83 +CCPREFIX=arm-poky-linux-gnueabi- + +# To build modules outside of the kernel tree, we run "make" +# in the kernel source tree; the Makefile these then includes this +# Makefile once again. +# This conditional selects whether we are being included from the +# kernel Makefile or not. +ifeq ($(KERNELRELEASE),) +# The current directory is passed to sub-makes as argument + PWD:=$(shell pwd) + +modules: + $(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C ${KERNELDIR} M=$(PWD) +# Rename .dtb to .dtbo, required by dtoverlay + mv $(DTB_FILE) $(DTBO_FILE) + +modules_install:modules + scp *.ko *.dtbo root@10.9.8.2: + +clean: + rm -rf *.o *.dtb *.dtbo *~ core .depend *.mod *.a .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers .*.tmp + +.PHONY:default clean + +else +# called from kernel build system: just declare what our modules are +# Ignore C90 decl after statement warning + ccflags-y:=-DDEBUG -g -std=gnu99 -Wno-declaration-after-statement +# Device Tree Blobs to build + always:=$(DTB_FILE) +# Kernel Object target file(s) + obj-m += $(KMODULE).o +# If object must be linked from multiple parts +#xxxxmod-objs := part1.o part2.o +endif diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/plat_drv-overlay.dts b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/plat_drv-overlay.dts new file mode 100644 index 0000000000000000000000000000000000000000..5813a60661b15be1822c10c7182d8dc65be7cd1e --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/plat_drv-overlay.dts @@ -0,0 +1,21 @@ +// Definitions for plat_drv module +// Adapted from w1-gpio-pullup-overlay.dts +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + /* Add device to base */ + target-path = "/"; + __overlay__ { + /* instance:type */ + plat_drv: plat_drv@0 { + /* Label to match in driver */ + compatible = "au-ece, plat_drv"; + status = "okay"; + }; + }; + }; +}; diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/plat_drv.c b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/plat_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..5a554b1b8604fc57d818fd1a024fe605fa10da49 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL1/plat_drv.c @@ -0,0 +1,191 @@ +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#define LED3 21 // GPIO pin number for LED3 +static dev_t devno; +static struct class *led_class; +static struct cdev led_cdev; +struct file_operations led_fops; + +const int first_minor = 0; +const int max_devices = 255; + +// Platform driver match table +static const struct of_device_id led_of_match[] = { + { .compatible = "au-ece, plat_drv" }, // Compatible name used in Device Tree + {} +}; +MODULE_DEVICE_TABLE(of, led_of_match); + +// Probe function +static int plat_drv_probe(struct platform_device *pdev) { + int err; + + // Request GPIO for LED3 + err = gpio_request(LED3,"LED3"); + if (err) { + pr_err("Failed to request GPIO for LED3\n"); + return err; + } + + // Set GPIO direction (output) + err = gpio_direction_output(LED3, 0); // Initialize to 0 (off) + if (err) { + pr_err("Failed to set GPIO direction for LED3\n"); + gpio_free(LED3); + return err; + } + + // Allocate Major/Minor numbers + err = alloc_chrdev_region(&devno, first_minor, max_devices, "led-driver"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + gpio_free(LED3); + return err; + } + + pr_info("LED driver got Major %i\n", MAJOR(devno)); + + // Create class + led_class = class_create(THIS_MODULE,"led_class"); + if (IS_ERR(led_class)) { + pr_err("Failed to create class for LED3\n"); + err = PTR_ERR(led_class); + unregister_chrdev_region(devno, max_devices); + gpio_free(LED3); + return err; + } + + // Initialize and add cdev + cdev_init(&led_cdev, &led_fops); + err = cdev_add(&led_cdev, devno, 1); // Register one device only + if (err < 0) { + pr_err("Failed to add cdev for LED3\n"); + class_destroy(led_class); + unregister_chrdev_region(devno, max_devices); + gpio_free(LED3); + return err; + } + + // Create device node in /dev/ + device_create(led_class, NULL, devno, NULL, "plat_drv"); + + pr_info("hello from probe\n"); + + return 0; +} + +// Remove function +static int plat_drv_remove(struct platform_device *pdev) { + pr_info("goodbye from remove\n"); + + device_destroy(led_class, devno); + cdev_del(&led_cdev); + class_destroy(led_class); + unregister_chrdev_region(devno, max_devices); + gpio_free(LED3); + + return 0; +} + +// Platform driver structure +static struct platform_driver led_platform_driver = { + .probe = plat_drv_probe, + .remove = plat_drv_remove, + .driver = { + .name = "plat_drv", + .of_match_table = led_of_match, + .owner = THIS_MODULE, + }, +}; + +// Init function (register platform driver) +static int __init mygpio_init(void) { + pr_info("Initializing LED Platform Driver\n"); + return platform_driver_register(&led_platform_driver); +} + +// Exit function (unregister platform driver) +static void __exit mygpio_exit(void) { + platform_driver_unregister(&led_platform_driver); +} + +// File operations functions +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; // Null-terminate the string + + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(LED3, value); // Set GPIO value (output) + + pr_info("Wrote %i to LED3\n", value); + *f_pos += len; + return len; +} + +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + + value = gpio_get_value(LED3); // Read GPIO value (output) + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (*f_pos > 0) { + return 0; // EOF + } + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; + return len; +} + +struct file_operations led_fops = { + .open = mygpio_open, + .release = mygpio_release, + .read = mygpio_read, + .write = mygpio_write, +}; + +module_init(mygpio_init); +module_exit(mygpio_exit); + +MODULE_AUTHOR("Khaled <Khaled.uni.au@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/Makefile b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3377ab9b20fbe82276fd5f8348425ccad554f40b --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/Makefile @@ -0,0 +1,40 @@ +# Kernel Module +KMODULE:=plat_drv +DTB_FILE:=$(KMODULE)-overlay.dtb +DTBO_FILE:=$(KMODULE).dtbo +KERNELDIR=~/sources/rpi-5.4.83 +CCPREFIX=arm-poky-linux-gnueabi- + +# To build modules outside of the kernel tree, we run "make" +# in the kernel source tree; the Makefile these then includes this +# Makefile once again. +# This conditional selects whether we are being included from the +# kernel Makefile or not. +ifeq ($(KERNELRELEASE),) +# The current directory is passed to sub-makes as argument + PWD:=$(shell pwd) + +modules: + $(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C ${KERNELDIR} M=$(PWD) +# Rename .dtb to .dtbo, required by dtoverlay + mv $(DTB_FILE) $(DTBO_FILE) + +modules_install:modules + scp *.ko *.dtbo root@10.9.8.2: + +clean: + rm -rf *.o *.dtb *.dtbo *~ core .depend *.a *.mod .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers .*.tmp + +.PHONY:default clean + +else +# called from kernel build system: just declare what our modules are +# Ignore C90 decl after statement warning + ccflags-y:=-DDEBUG -g -std=gnu99 -Wno-declaration-after-statement +# Device Tree Blobs to build + always:=$(DTB_FILE) +# Kernel Object target file(s) + obj-m += $(KMODULE).o +# If object must be linked from multiple parts +#xxxxmod-objs := part1.o part2.o +endif diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/plat_drv-overlay.dts b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/plat_drv-overlay.dts new file mode 100644 index 0000000000000000000000000000000000000000..5813a60661b15be1822c10c7182d8dc65be7cd1e --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/plat_drv-overlay.dts @@ -0,0 +1,21 @@ +// Definitions for plat_drv module +// Adapted from w1-gpio-pullup-overlay.dts +/dts-v1/; +/plugin/; + +/{ + compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709"; + + fragment@0 { + /* Add device to base */ + target-path = "/"; + __overlay__ { + /* instance:type */ + plat_drv: plat_drv@0 { + /* Label to match in driver */ + compatible = "au-ece, plat_drv"; + status = "okay"; + }; + }; + }; +}; diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/plat_drv.c b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/plat_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..7e7fe39648615218dbdaada9057503ea10d67822 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL2/plat_drv.c @@ -0,0 +1,258 @@ +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#define NUM_GPIOS 2 // Antal GPIO'er + +// Struktur til at gemme GPIO-oplysninger +struct gpio_dev { + int no; // GPIO nummer + int dir; // 0: input, 1: output +}; + +// Array der definerer GPIO numre og retninger +static struct gpio_dev gpio_devs[NUM_GPIOS] = { + {12, 0}, // GPIO12, input + {21, 1} // GPIO21, output +}; +static int gpios_len = 2; + +static dev_t devno; +static struct class *led_class; +static struct cdev led_cdev; +struct file_operations led_fops; + +const int first_minor = 0; +const int max_devices = 255; + +// Platform driver match table +static const struct of_device_id led_of_match[] = { + { .compatible = "au-ece, plat_drv" }, + {} +}; +MODULE_DEVICE_TABLE(of, led_of_match); + +// Probe function +static int plat_drv_probe(struct platform_device *pdev) { + int err, i; + dev_t curr_devno; + + // Allocate Major/Minor numbers + err = alloc_chrdev_region(&devno, first_minor, max_devices, "led-driver"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + return err; + } + pr_info("LED driver got Major %i\n", MAJOR(devno)); + + // Create class + led_class = class_create(THIS_MODULE, "led_class"); + if (IS_ERR(led_class)) { + pr_err("Failed to create class\n"); + err = PTR_ERR(led_class); + unregister_chrdev_region(devno, max_devices); + return err; + } + + // Initialize and add cdev + cdev_init(&led_cdev, &led_fops); + err = cdev_add(&led_cdev, devno, NUM_GPIOS); // Registrer to devices + if (err < 0) { + pr_err("Failed to add cdev\n"); + class_destroy(led_class); + unregister_chrdev_region(devno, max_devices); + return err; + } + + // Loop over gpio_devs array og anmod om GPIO'er samt sæt deres retning + for (i = 0; i < NUM_GPIOS; i++) { + struct gpio_dev *dev = &gpio_devs[i]; + + // Anmod om GPIO + err = gpio_request(dev->no, "gpio_dev"); + if (err) { + pr_err("Failed to request GPIO %d\n", dev->no); + goto cleanup_gpio; + } + + // Sæt GPIO retning + if (dev->dir == 1) { // Hvis det er output + err = gpio_direction_output(dev->no, 0); // Initialiser til 0 + } else { // Hvis det er input + err = gpio_direction_input(dev->no); + } + if (err) { + pr_err("Failed to set GPIO direction for GPIO %d\n", dev->no); + gpio_free(dev->no); + goto cleanup_gpio; + } + + // Opret device node for hver GPIO + curr_devno = MKDEV(MAJOR(devno), MINOR(devno) + i); + device_create(led_class, NULL, curr_devno, NULL, "plat_drv%d", i); + pr_info("Created device for GPIO %d with minor %d\n", dev->no, MINOR(curr_devno)); + } + + pr_info("hello from probe\n"); + return 0; + + cleanup_gpio: + while (i--) { + gpio_free(gpio_devs[i].no); + device_destroy(led_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + cdev_del(&led_cdev); + class_destroy(led_class); + unregister_chrdev_region(devno, max_devices); + return err; +} + +// Remove function +static int plat_drv_remove(struct platform_device *pdev) { + int i; + pr_info("goodbye from remove\n"); + + // Fjern devices, cdev og class + for (i = 0; i < NUM_GPIOS; i++) { + gpio_free(gpio_devs[i].no); + device_destroy(led_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + cdev_del(&led_cdev); + class_destroy(led_class); + unregister_chrdev_region(devno, max_devices); + + return 0; +} + +// Platform driver structure +static struct platform_driver led_platform_driver = { + .probe = plat_drv_probe, + .remove = plat_drv_remove, + .driver = { + .name = "plat_drv", + .of_match_table = led_of_match, + .owner = THIS_MODULE, + }, +}; + +// Init function (register platform driver) +static int __init mygpio_init(void) { + pr_info("Initializing LED Platform Driver\n"); + return platform_driver_register(&led_platform_driver); +} + +// Exit function (unregister platform driver) +static void __exit mygpio_exit(void) { + platform_driver_unregister(&led_platform_driver); +} + +// File operations functions +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + int minor = MINOR(filep->f_inode->i_rdev); // Få minor nummer + struct gpio_dev *dev; + + // Tjek om minor nummeret er gyldigt + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; // Få den korrekte gpio_dev struct baseret på minor nummeret + + // Kontroller, om GPIO'en er sat til output + if (dev->dir != 1) { + pr_err("GPIO %d is not configured as output\n", dev->no); + return -EINVAL; + } + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; // Null-terminér strengen + + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(dev->no, value); // Sæt GPIO-værdien (output) + + pr_info("Wrote %i to GPIO %d\n", value, dev->no); + *f_pos += len; + return len; +} + +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + int minor = MINOR(filep->f_inode->i_rdev); // Få minor nummer + struct gpio_dev *dev; + + // Tjek om minor nummeret er gyldigt + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; // Få den korrekte gpio_dev struct baseret på minor nummeret + + // Kontroller, om GPIO'en er sat til input + if (dev->dir != 0) { + pr_err("GPIO %d is not configured as input\n", dev->no); + return -EINVAL; + } + + value = gpio_get_value(dev->no); // Læs GPIO-værdien (input) + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (*f_pos > 0) { + return 0; // EOF + } + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; + return len; +} + +struct file_operations led_fops = { + .open = mygpio_open, + .release = mygpio_release, + .read = mygpio_read, + .write = mygpio_write, +}; + +module_init(mygpio_init); +module_exit(mygpio_exit); + +MODULE_AUTHOR("Khaled <Khaled.uni.au@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/Makefile b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3377ab9b20fbe82276fd5f8348425ccad554f40b --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/Makefile @@ -0,0 +1,40 @@ +# Kernel Module +KMODULE:=plat_drv +DTB_FILE:=$(KMODULE)-overlay.dtb +DTBO_FILE:=$(KMODULE).dtbo +KERNELDIR=~/sources/rpi-5.4.83 +CCPREFIX=arm-poky-linux-gnueabi- + +# To build modules outside of the kernel tree, we run "make" +# in the kernel source tree; the Makefile these then includes this +# Makefile once again. +# This conditional selects whether we are being included from the +# kernel Makefile or not. +ifeq ($(KERNELRELEASE),) +# The current directory is passed to sub-makes as argument + PWD:=$(shell pwd) + +modules: + $(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C ${KERNELDIR} M=$(PWD) +# Rename .dtb to .dtbo, required by dtoverlay + mv $(DTB_FILE) $(DTBO_FILE) + +modules_install:modules + scp *.ko *.dtbo root@10.9.8.2: + +clean: + rm -rf *.o *.dtb *.dtbo *~ core .depend *.a *.mod .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers .*.tmp + +.PHONY:default clean + +else +# called from kernel build system: just declare what our modules are +# Ignore C90 decl after statement warning + ccflags-y:=-DDEBUG -g -std=gnu99 -Wno-declaration-after-statement +# Device Tree Blobs to build + always:=$(DTB_FILE) +# Kernel Object target file(s) + obj-m += $(KMODULE).o +# If object must be linked from multiple parts +#xxxxmod-objs := part1.o part2.o +endif diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/plat_drv-overlay.dts b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/plat_drv-overlay.dts new file mode 100644 index 0000000000000000000000000000000000000000..a12083d60fec21bb97c41636c31589bb8b44f526 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/plat_drv-overlay.dts @@ -0,0 +1,23 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + plat_drv: plat_drv@0 { + compatible = "mygpio, plat_drv"; + status = "okay"; + + /* Definer GPIO'er til driveren */ + gpios = <&gpio 12 0>, /* GPIO 12 som input */ + <&gpio 21 1>; /* GPIO 21 som output */ + + /* Tilføj evt. en brugerdefineret værdi */ + mydevt-custom = <0x12345678>; + }; + }; + }; +}; diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/plat_drv.c b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/plat_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..17b9e5c244e87ee3d5bb34eaadab6853629d4fd1 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL3/plat_drv.c @@ -0,0 +1,271 @@ +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/of_gpio.h> +#include <linux/err.h> + +#define NUM_GPIOS 2 // Antal GPIO'er (tilpasses til dynamisk senere) + +// Struktur til at gemme GPIO-oplysninger +struct gpio_dev { + int no; // GPIO nummer + int dir; // 0: input, 1: output +}; + +static struct gpio_dev gpio_devs[NUM_GPIOS]; +static int gpios_len; + +static dev_t devno; +static struct class *led_class; +static struct cdev led_cdev; +struct file_operations led_fops; + +// Match-table for Device Tree +static const struct of_device_id led_of_match[] = { + { .compatible = "mygpio, plat_drv" }, + {}, +}; +MODULE_DEVICE_TABLE(of, led_of_match); + +// Probe function +static int plat_drv_probe(struct platform_device *pdev) { + int err, i; + dev_t curr_devno; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + enum of_gpio_flags flag; + + // Hent antal GPIO'er fra Device Tree + gpios_len = of_gpio_count(np); + if (gpios_len <= 0) { + pr_err("No GPIOs defined in Device Tree\n"); + return -EINVAL; + } + + pr_info("Found %d GPIOs in Device Tree\n", gpios_len); + + // Loop gennem GPIO'er i Device Tree og hent konfiguration + for (i = 0; i < gpios_len; i++) { + gpio_devs[i].no = of_get_gpio(np, i); + if (gpio_devs[i].no < 0) { + pr_err("Failed to get GPIO %d\n", i); + return gpio_devs[i].no; + } + + if (of_get_gpio_flags(np, i, &flag) < 0) { + pr_err("Failed to get GPIO flags for GPIO %d\n", gpio_devs[i].no); + return -EINVAL; + } + + gpio_devs[i].dir = (flag == OF_GPIO_ACTIVE_LOW) ? 0 : 1; // 0 = input, 1 = output + + pr_info("Configured GPIO %d with direction %d\n", gpio_devs[i].no, gpio_devs[i].dir); + } + + // Opret karakterenheder og GPIO-konfigurationer baseret på Device Tree-data + for (i = 0; i < gpios_len; i++) { + struct gpio_dev *dev = &gpio_devs[i]; + + // Anmod om GPIO + err = gpio_request(dev->no, "gpio_dev"); + if (err) { + pr_err("Failed to request GPIO %d\n", dev->no); + goto cleanup_gpio; + } + + // Sæt GPIO-retning + if (dev->dir == 1) { // output + err = gpio_direction_output(dev->no, 0); + } else { // input + err = gpio_direction_input(dev->no); + } + if (err) { + pr_err("Failed to set direction for GPIO %d\n", dev->no); + gpio_free(dev->no); + goto cleanup_gpio; + } + + // Opret device node for hver GPIO + curr_devno = MKDEV(MAJOR(devno), MINOR(devno) + i); + device_create(led_class, NULL, curr_devno, NULL, "plat_drv%d", i); + pr_info("Created device for GPIO %d with minor %d\n", dev->no, MINOR(curr_devno)); + } + + pr_info("Driver successfully probed with GPIO configuration from Device Tree\n"); + return 0; + + cleanup_gpio: + while (i--) { + gpio_free(gpio_devs[i].no); + device_destroy(led_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + return err; +} + +// Remove function +static int plat_drv_remove(struct platform_device *pdev) { + int i; + pr_info("goodbye from remove\n"); + + // Fjern devices, cdev og class + for (i = 0; i < gpios_len; i++) { // Brug gpios_len her + gpio_free(gpio_devs[i].no); + device_destroy(led_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + cdev_del(&led_cdev); + class_destroy(led_class); + unregister_chrdev_region(devno, gpios_len); // Brug gpios_len her + + return 0; +} + +// Platform driver structure +static struct platform_driver led_platform_driver = { + .probe = plat_drv_probe, + .remove = plat_drv_remove, + .driver = { + .name = "plat_drv", + .of_match_table = led_of_match, // Matcher med Device Tree + .owner = THIS_MODULE, + }, +}; + +// Init function (register platform driver) +static int __init mygpio_init(void) { + int err; + + pr_info("Initializing LED Platform Driver\n"); + + // Alloker chrdev region + err = alloc_chrdev_region(&devno, 0, NUM_GPIOS, "plat_drv"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + return err; + } + + // Opret class og cdev + led_class = class_create(THIS_MODULE, "plat_drv_class"); + if (IS_ERR(led_class)) { + unregister_chrdev_region(devno, NUM_GPIOS); + return PTR_ERR(led_class); + } + + cdev_init(&led_cdev, &led_fops); + led_cdev.owner = THIS_MODULE; + err = cdev_add(&led_cdev, devno, NUM_GPIOS); + if (err) { + class_destroy(led_class); + unregister_chrdev_region(devno, NUM_GPIOS); + return err; + } + + return platform_driver_register(&led_platform_driver); +} + +// Exit function (unregister platform driver) +static void __exit mygpio_exit(void) { + platform_driver_unregister(&led_platform_driver); + cdev_del(&led_cdev); + class_destroy(led_class); + unregister_chrdev_region(devno, NUM_GPIOS); +} + +// File operations functions +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + int minor = MINOR(filep->f_inode->i_rdev); + struct gpio_dev *dev; + + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; + if (dev->dir != 1) { + pr_err("GPIO %d is not configured as output\n", dev->no); + return -EINVAL; + } + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(dev->no, value); + pr_info("Wrote %i to GPIO %d\n", value, dev->no); + *f_pos += len; + return len; +} + +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + int minor = MINOR(filep->f_inode->i_rdev); + struct gpio_dev *dev; + + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; + value = gpio_get_value(dev->no); + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (len < 0 || len > count) { + return -EINVAL; + } + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; + return len; +} + +// File operations +struct file_operations led_fops = { + .owner = THIS_MODULE, + .open = mygpio_open, + .release = mygpio_release, + .write = mygpio_write, + .read = mygpio_read, +}; + +module_init(mygpio_init); +module_exit(mygpio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dit Navn"); +MODULE_DESCRIPTION("GPIO Platform Driver"); diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/Makefile b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3377ab9b20fbe82276fd5f8348425ccad554f40b --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/Makefile @@ -0,0 +1,40 @@ +# Kernel Module +KMODULE:=plat_drv +DTB_FILE:=$(KMODULE)-overlay.dtb +DTBO_FILE:=$(KMODULE).dtbo +KERNELDIR=~/sources/rpi-5.4.83 +CCPREFIX=arm-poky-linux-gnueabi- + +# To build modules outside of the kernel tree, we run "make" +# in the kernel source tree; the Makefile these then includes this +# Makefile once again. +# This conditional selects whether we are being included from the +# kernel Makefile or not. +ifeq ($(KERNELRELEASE),) +# The current directory is passed to sub-makes as argument + PWD:=$(shell pwd) + +modules: + $(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C ${KERNELDIR} M=$(PWD) +# Rename .dtb to .dtbo, required by dtoverlay + mv $(DTB_FILE) $(DTBO_FILE) + +modules_install:modules + scp *.ko *.dtbo root@10.9.8.2: + +clean: + rm -rf *.o *.dtb *.dtbo *~ core .depend *.a *.mod .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers .*.tmp + +.PHONY:default clean + +else +# called from kernel build system: just declare what our modules are +# Ignore C90 decl after statement warning + ccflags-y:=-DDEBUG -g -std=gnu99 -Wno-declaration-after-statement +# Device Tree Blobs to build + always:=$(DTB_FILE) +# Kernel Object target file(s) + obj-m += $(KMODULE).o +# If object must be linked from multiple parts +#xxxxmod-objs := part1.o part2.o +endif diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/plat_drv-overlay.dts b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/plat_drv-overlay.dts new file mode 100644 index 0000000000000000000000000000000000000000..a12083d60fec21bb97c41636c31589bb8b44f526 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/plat_drv-overlay.dts @@ -0,0 +1,23 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + plat_drv: plat_drv@0 { + compatible = "mygpio, plat_drv"; + status = "okay"; + + /* Definer GPIO'er til driveren */ + gpios = <&gpio 12 0>, /* GPIO 12 som input */ + <&gpio 21 1>; /* GPIO 21 som output */ + + /* Tilføj evt. en brugerdefineret værdi */ + mydevt-custom = <0x12345678>; + }; + }; + }; +}; diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/plat_drv.c b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/plat_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..17b9e5c244e87ee3d5bb34eaadab6853629d4fd1 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/DEL4/plat_drv.c @@ -0,0 +1,271 @@ +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/of_gpio.h> +#include <linux/err.h> + +#define NUM_GPIOS 2 // Antal GPIO'er (tilpasses til dynamisk senere) + +// Struktur til at gemme GPIO-oplysninger +struct gpio_dev { + int no; // GPIO nummer + int dir; // 0: input, 1: output +}; + +static struct gpio_dev gpio_devs[NUM_GPIOS]; +static int gpios_len; + +static dev_t devno; +static struct class *led_class; +static struct cdev led_cdev; +struct file_operations led_fops; + +// Match-table for Device Tree +static const struct of_device_id led_of_match[] = { + { .compatible = "mygpio, plat_drv" }, + {}, +}; +MODULE_DEVICE_TABLE(of, led_of_match); + +// Probe function +static int plat_drv_probe(struct platform_device *pdev) { + int err, i; + dev_t curr_devno; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + enum of_gpio_flags flag; + + // Hent antal GPIO'er fra Device Tree + gpios_len = of_gpio_count(np); + if (gpios_len <= 0) { + pr_err("No GPIOs defined in Device Tree\n"); + return -EINVAL; + } + + pr_info("Found %d GPIOs in Device Tree\n", gpios_len); + + // Loop gennem GPIO'er i Device Tree og hent konfiguration + for (i = 0; i < gpios_len; i++) { + gpio_devs[i].no = of_get_gpio(np, i); + if (gpio_devs[i].no < 0) { + pr_err("Failed to get GPIO %d\n", i); + return gpio_devs[i].no; + } + + if (of_get_gpio_flags(np, i, &flag) < 0) { + pr_err("Failed to get GPIO flags for GPIO %d\n", gpio_devs[i].no); + return -EINVAL; + } + + gpio_devs[i].dir = (flag == OF_GPIO_ACTIVE_LOW) ? 0 : 1; // 0 = input, 1 = output + + pr_info("Configured GPIO %d with direction %d\n", gpio_devs[i].no, gpio_devs[i].dir); + } + + // Opret karakterenheder og GPIO-konfigurationer baseret på Device Tree-data + for (i = 0; i < gpios_len; i++) { + struct gpio_dev *dev = &gpio_devs[i]; + + // Anmod om GPIO + err = gpio_request(dev->no, "gpio_dev"); + if (err) { + pr_err("Failed to request GPIO %d\n", dev->no); + goto cleanup_gpio; + } + + // Sæt GPIO-retning + if (dev->dir == 1) { // output + err = gpio_direction_output(dev->no, 0); + } else { // input + err = gpio_direction_input(dev->no); + } + if (err) { + pr_err("Failed to set direction for GPIO %d\n", dev->no); + gpio_free(dev->no); + goto cleanup_gpio; + } + + // Opret device node for hver GPIO + curr_devno = MKDEV(MAJOR(devno), MINOR(devno) + i); + device_create(led_class, NULL, curr_devno, NULL, "plat_drv%d", i); + pr_info("Created device for GPIO %d with minor %d\n", dev->no, MINOR(curr_devno)); + } + + pr_info("Driver successfully probed with GPIO configuration from Device Tree\n"); + return 0; + + cleanup_gpio: + while (i--) { + gpio_free(gpio_devs[i].no); + device_destroy(led_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + return err; +} + +// Remove function +static int plat_drv_remove(struct platform_device *pdev) { + int i; + pr_info("goodbye from remove\n"); + + // Fjern devices, cdev og class + for (i = 0; i < gpios_len; i++) { // Brug gpios_len her + gpio_free(gpio_devs[i].no); + device_destroy(led_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + cdev_del(&led_cdev); + class_destroy(led_class); + unregister_chrdev_region(devno, gpios_len); // Brug gpios_len her + + return 0; +} + +// Platform driver structure +static struct platform_driver led_platform_driver = { + .probe = plat_drv_probe, + .remove = plat_drv_remove, + .driver = { + .name = "plat_drv", + .of_match_table = led_of_match, // Matcher med Device Tree + .owner = THIS_MODULE, + }, +}; + +// Init function (register platform driver) +static int __init mygpio_init(void) { + int err; + + pr_info("Initializing LED Platform Driver\n"); + + // Alloker chrdev region + err = alloc_chrdev_region(&devno, 0, NUM_GPIOS, "plat_drv"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + return err; + } + + // Opret class og cdev + led_class = class_create(THIS_MODULE, "plat_drv_class"); + if (IS_ERR(led_class)) { + unregister_chrdev_region(devno, NUM_GPIOS); + return PTR_ERR(led_class); + } + + cdev_init(&led_cdev, &led_fops); + led_cdev.owner = THIS_MODULE; + err = cdev_add(&led_cdev, devno, NUM_GPIOS); + if (err) { + class_destroy(led_class); + unregister_chrdev_region(devno, NUM_GPIOS); + return err; + } + + return platform_driver_register(&led_platform_driver); +} + +// Exit function (unregister platform driver) +static void __exit mygpio_exit(void) { + platform_driver_unregister(&led_platform_driver); + cdev_del(&led_cdev); + class_destroy(led_class); + unregister_chrdev_region(devno, NUM_GPIOS); +} + +// File operations functions +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing LED Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + int minor = MINOR(filep->f_inode->i_rdev); + struct gpio_dev *dev; + + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; + if (dev->dir != 1) { + pr_err("GPIO %d is not configured as output\n", dev->no); + return -EINVAL; + } + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(dev->no, value); + pr_info("Wrote %i to GPIO %d\n", value, dev->no); + *f_pos += len; + return len; +} + +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + int minor = MINOR(filep->f_inode->i_rdev); + struct gpio_dev *dev; + + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; + value = gpio_get_value(dev->no); + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (len < 0 || len > count) { + return -EINVAL; + } + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; + return len; +} + +// File operations +struct file_operations led_fops = { + .owner = THIS_MODULE, + .open = mygpio_open, + .release = mygpio_release, + .write = mygpio_write, + .read = mygpio_read, +}; + +module_init(mygpio_init); +module_exit(mygpio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dit Navn"); +MODULE_DESCRIPTION("GPIO Platform Driver"); diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/README.md b/Group-3-main/Group-3-main/Lektion6_Afl2/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1b2df4944f816cc83df19c39daf8d115ea4fe10c --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/README.md @@ -0,0 +1,634 @@ +# Group 3 +## Members +* John Nguyen +* Ibrahem Merhi +* Khaled Rami Omar + +## Link to repository +https://gitlab.au.dk/au-ece-sw3hal/e2024/Group-3.git + +# Øvelse: Hot-pluggable Device Driver (Raspberry Pi) + +Med udgangspunkt i kodeskabelonen til en platform driver, `plat_drv`, skal du undersøge hvordan hot-plugging virker i Linux kontekst og bagefter udvide skabelonen til en GPIO driver. + +## Formål + +Formålet med denne øvelse er at opnå erfaring med at anvende Linux' device model. Herunder at opnå forståelse for anvendelsen af Hot- og Cold-plugging og hvordan dette implementeres i en device driver. + +Følgende Rubric beskriver forskellig opfyldelse af øvelsens mål: + +| Mål | Mangelfuld opfyldelse af mål | Delvis opfyldelse af mål 2 | Fuld opfyldelse af mål | +|-------------------------------------------|---------------------------------------|---------------------------------------|------------------------------| +| Undersøge Hot-plugging | <ul><li> Driverens init/probe er implementeret </li><li> Driver kan indsættes og device treee loades.</li><li>Der ses IKKE en besked fra probe() i dmesg.</li></ul> | <ul><li> Driverens init/probe er implementeret </li><li> Driver kan indsættes og device treee loades </li><li> Der ses en besked fra probe() i dmesg. </li><li> Der er vist output fra test</li></ul> | <ul><li> Driverens init/probe er implementeret </li><li> Driver kan indsættes og device treee loades </li><li> Der ses en besked fra probe() i dmesg. </li><li> Der er vist output fra test </li><li> Driver/device binding er diskuteret </li></ul> | +| Implementere gpio hot-plug driver | <ul><li> Der er implementeret basal oprettelse og konfiguration af gpios i probe </li><li> Device oprettes i probe().</li></ul> | <ul><li> Der er implementeret basal oprettelse og konfiguration af gpios i probe </li><li> Device oprettes i probe(). </li><li> Read og write funktioner er opdateret til at kunne tilgå de korrekte gpios.</li></ul> | <ul><li> Der er implementeret basal oprettelse og konfiguration af gpios i probe </li><li> Device oprettes i probe(). </li><li> Read og write funktioner er opdateret til at kunne tilgå de korrekte gpios. </li><li> Der er lavet korrekt cleanup i hhv. exit og remove. </li></ul>| +| Anvende Device Tree | <ul><li> Overlay (.dts) er opdateret med GPIOs | <ul><li> Overlay er opdateret med GPIOs </li><li> overlay data bliver læst i probe() </li></ul> | <ul><li> Overlay er opdateret med GPIOs </li><li> overlay data bliver læst i probe() </li><li> gpios oprettes korrekt i /dev </li><li> gpios fungerer korrekt </li><li> tests er dokumentet </li></ul>| + +## Delopgave 1: Hot-Plugging af Platform Driver og Device +Målet med denne delopgave er at du opnår forståelse af principperne bag Linux' device model, i dette tilfælde hvordan hot-plug virker og hvilke funktioner som kaldes hvor og hvornår. + + +### a) Kopier din GPIO driver fra øvelse 4 til en ny folder, kopiér desuden Makefile og plat_drv_overlay.dts fra repositoriet hertil. Omdøb din driver .c fil til plat_drv.c. Byg koden vha. den tilhørende Makefile. Bemærk at resultatet er to filer: kernemodulet plat_drv.ko og et device tree blob overlay, plat_drv.dtbo. Den sidste beskriver hardwaren og er noget vi vil dykke ned i senere. + + + +makefilen køres og vi får filerne: "plat_drv.dtbo""plat_drv.ko": + + + + +### b) Tilføj hot-plugging funktionalitet ved at oprette probe() og remove() funktionerne i driveren (frie funktioner ligesom init(), read() mm.), lav en enkelt printk() i hver af dem ("hello from probe\n"...). Tilføj hot-plugging identificering ved at oprette of_device_id og platform_driver struct'ene. Sæt compatible = "au-ece, plat_drv", som er navnet brugt i device tree filen, plat_drv-overlay.dts. Registrer platform driveren i init (som det sidste) vha. platform_driver_register(). Byg og kopiér .ko og .dtbo filer til target. + +Der tilføres følgene elementer i plat_drv.c. Der vises kun Det tilføjet element og ikke hele funktionen. +probe(): +```c +static int plat_drv_probe(struct platform_device *pdev) { + {...} + pr_info("hello from probe\n"); + {...} +} +``` +probe() kaldes automatisk, når en driver finder en enhed, der matcher driverens compatible specifikation fra Device Tree. + +Remove(): +```c +static int plat_drv_remove(struct platform_device *pdev) { + pr_info("goodbye from remove\n"); + {...} +} +``` +remove() kaldes, når driveren fjernes. + +of_device_id strukturen: +```c +static const struct of_device_id led_of_match[] = { + { .compatible = "au-ece,plat_drv" }, // Compatible name used in Device Tree +}; +MODULE_DEVICE_TABLE(of, led_of_match); +``` +Dette array bruges til at matche driveren med en compatible værdi fra Device Tree. + +platform_driver strukturen: +```c +static struct platform_driver led_platform_driver = { + .probe = plat_drv_probe, + .remove = plat_drv_remove, + .driver = { + .name = "plat_drv", + .of_match_table = led_of_match, + .owner = THIS_MODULE, + }, +}; +``` +platform_driver-strukturen samler driverens funktioner som probe og remove samt opsætter driverens of_match_table for Device Tree-kompatibilitet. + +Registrer platformdriveren i init(): +```c +static int __init mygpio_init(void) { + {...} + return platform_driver_register(&led_platform_driver); +} +``` +Hvilken headerfil skal du inkludere for at benytte platform_driver_register? For at benytte platform_driver_register skal du inkludere følgende headerfil: +```c +#include <linux/platform_device.h> +``` +Makefilen køres igen, for at bygge programmen med de nye emplementering. Derefter kopires og overføres .ko og .dtbo til RPi'en: + + +### c) Du skal nu registrere device't i Linux og indsætte driveren. Du registrerer device't ved at indlæse .dtbo filen på følgende vis: + +```c +dtoverlay -d . plat_drv.dtbo +``` + +Dette svarer til når man laver hot-plugging med USB. Det overordnede RaspberryPi ZW device tree udvides herved til også at indeholde vores device. -d . gør at den loader .dtbo filen fra vores lokale sti, ellers ville den forsøge at loade den fra /boot/overlays. Du skal loade driveren på sædvanlig vis. + +Der køres også: +```c +insmod plat_drv.ko +``` +Der fåes beskeden, der er skrevet ind i funktion, ved at skrive "dmesg" ("Hello from probe"): + + + +Er driveren tildelt major nummer (se /proc/devices)? +Når "dmesg" køres ses at major er 239, derfor burde man kunne se i device nummer 239 devicet for led: + + + +Det er er tildelt en major nummer, som findes i /proc/devices. + +Er der lavet en Sys FS klasse (se /sys/class)? + + + +Der er lavet en led klasse, som ses under /sys/class. + +Hvilke metoder er blevet kaldt i device driveren (plat_drv.c)? +probe() kaldes ved tilføjelse af overlayet, og remove() kaldes, når overlayet fjernes. + +Hvad sker der hvis vi fjerner overlayet igen (dtoverlay -R plat_drv)? +Vis resultatet af dmesg og forklar sammenhængen til det observerede med koden i plat_drv.c og sekvensdiagrammet i lektionens slides. Benyt gerne dit eget sekvensdiagram med rigtige funktionsnavne. + + + +Beskeden i remove() vises. Det gøres fordi, ved at lavelse af overlay, laves en device og en class, og det er probe der gør det, mens ved fjernelse af overlay, er det remove der stræder i kræft og hermed vil beskeden. + + +### d) I probe() skal nu oprettes et device vha device_create(). I remove() tilføjes device_destroy(). Byg og test på target. +Der tilføjes det nødvændige til de to funktioner: +probe(): +```c +static int plat_drv_probe(struct platform_device *pdev) { + {...} + device_create(led_class, NULL, devno, NULL, "plat_drv"); + {...} +} +``` +Remove(): +```c +static int plat_drv_remove(struct platform_device *pdev) { + {...} + device_destroy(led_class, devno); + {...} +} +``` +Koden overføres til target of testes: + + + +Er der oprettet Sys FS devices (se /sys/class/<my_class>/)? + + + +Der ses at der er oprettet en klasse "led_class" + +Er der oprettet noder (/dev)? + + + +Ja der oprettes noder + +virker read/write metoderne? + + + +Der kan godt tændes for lampen vha at ændre på plat_drv værdien. Herud over kan man aflæse i tidligere billeder at beskederne fra de forskellige funktioner printes ud efter "dmesg". + + + +## Delopgave 2: Konstruktion af egen hot-pluggable GPIO device driver +Nu hvor du har prøvet at anvende den device modellens overordnede mekanik, er det på tide at du anvender den til at forbedre din GPIO driver. Din driver skal automatisk oprette 2 noder under /dev/ når device tree filen loades og fjerne dem igen, når den fjernes. +Før vi går løs kan man med fordel lave en lille struct i driveren til at gemme info om GPIO port numre og retninger, samt lave et array af denne som vi senere kan bruge til at mappe minor numre og GPIOs, ex: GPIO12 er input og skal styres vha minor 0: +```c +struct gpio_dev { + int no; // GPIO number + int dir; // 0: in, 1: out +}; +static struct gpio_dev gpio_devs[2] = {{12,0}, {21, 1}}; +static int gpios_len = 2; +``` +Vi har tidligere anvendt request_gpio(), gpio_direction...() i init(). Vi ønsker at allokere GPIO'er dynamisk og bruge GPIO numre og retninger givet i device tree. request_gpio() mm skal derfor flyttes til probe(), da denne kaldes når der er detekteret et gyldigt device (ex. plat_drv i device tree). + +### a) Flyt request_gpio(), gpio_direction...() kaldende til probe(). Loop igennem gpios_devs arrayet og brug informationerne til at sætte parametre (GPIO nummer) og funktionskald (gpio_direction_in / out). Lav et kald til device_create() for hver GPIO og brug loop variablen til at sætte minor numret med. Dvs hver GPIO får hver sit minor nummer: +``` +[request_gpio() -loop antal] +[gpio_set_direction() -loop antal] +[opret devices med device_create -loop antal] +``` +Byg og test på target. + +Request og direction() er allerede lavet inde i probe, derfor behøves de ikke at flyttes, men der skal ændres på koden fra del 1. Følgene ændringer/tilføjelser er lavet: + +Struct: +```c +#define NUM_GPIOS 2 // Antal GPIO'er + +struct gpio_dev { + int no; // GPIO nummer + int dir; // 0: input, 1: output +}; + +static struct gpio_dev gpio_devs[NUM_GPIOS] = { + {12, 0}, // GPIO12, input + {21, 1} // GPIO21, output +}; +``` +Der tilføjes struct GPIO_dev som der bliver spurgt om i opgaven. + +Probe(): +```c +static int plat_drv_probe(struct platform_device *pdev) { + int err, i; + dev_t curr_devno; + + // Allocate Major/Minor numbers + err = alloc_chrdev_region(&devno, first_minor, max_devices, "led-driver"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + return err; + } + pr_info("LED driver got Major %i\n", MAJOR(devno)); + + // Create class + led_class = class_create(THIS_MODULE, "led_class"); + if (IS_ERR(led_class)) { + pr_err("Failed to create class\n"); + err = PTR_ERR(led_class); + unregister_chrdev_region(devno, max_devices); + return err; + } + + // Initialize and add cdev + cdev_init(&led_cdev, &led_fops); + err = cdev_add(&led_cdev, devno, NUM_GPIOS); // Registrer to devices + if (err < 0) { + pr_err("Failed to add cdev\n"); + class_destroy(led_class); + unregister_chrdev_region(devno, max_devices); + return err; + } + + // Loop over gpio_devs array og anmod om GPIO'er samt sæt deres retning + for (i = 0; i < NUM_GPIOS; i++) { + struct gpio_dev *dev = &gpio_devs[i]; + + // Anmod om GPIO + err = gpio_request(dev->no, "gpio_dev"); + if (err) { + pr_err("Failed to request GPIO %d\n", dev->no); + goto cleanup_gpio; + } + + // Sæt GPIO retning + if (dev->dir == 1) { // Hvis det er output + err = gpio_direction_output(dev->no, 0); // Initialiser til 0 + } else { // Hvis det er input + err = gpio_direction_input(dev->no); + } + if (err) { + pr_err("Failed to set GPIO direction for GPIO %d\n", dev->no); + gpio_free(dev->no); + goto cleanup_gpio; + } + + // Opret device node for hver GPIO + curr_devno = MKDEV(MAJOR(devno), MINOR(devno) + i); + device_create(led_class, NULL, curr_devno, NULL, "plat_drv%d", i); + pr_info("Created device for GPIO %d with minor %d\n", dev->no, MINOR(curr_devno)); + } + + pr_info("hello from probe\n"); + return 0; + + cleanup_gpio: + while (i--) { + gpio_free(gpio_devs[i].no); + device_destroy(led_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + cdev_del(&led_cdev); + class_destroy(led_class); + unregister_chrdev_region(devno, max_devices); + return err; +} +``` +I den opdaterede plat_drv_probe-funktion er der tilføjet logik til korrekt initialisering af en LED-driver. Funktionen allokerer først major og minor numre og opretter en klasse for driveren. Derefter initialiserer den en karakterenhed og registrerer flere GPIO-enheder. Hver GPIO anmodes, og dens retning indstilles til enten input eller output. Hvis der opstår fejl under disse trin, udføres der oprydning ved at frigive GPIO'er og destruere enheder. + +remove(): +```c +static int plat_drv_remove(struct platform_device *pdev) { + int i; + // Fjern devices, cdev og class + for (i = 0; i < NUM_GPIOS; i++) { + gpio_free(gpio_devs[i].no); + device_destroy(led_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + {...} +} +``` +I den opdaterede plat_drv_remove-funktion er der tilføjet logik til at frigive GPIO-enheder og destruere tilknyttede enheder. Funktionen itererer gennem GPIO'enhederne, frigiver dem med gpio_free og destruerer enhederne med device_destroy. + +Test: +"dmesg" køres efter overføresel af filerne til target: + + + +Der ses at der laves to devices og at probe kører som den skal. + +Oprettes der Sys FS devices? + + + +Ja det gøres der for hvrt element i array'et. + +Oprettes der devices i /dev/? + + + +Ja man kan se de to også inde i /dev. + +Hvilke GPIO'er virker read/write funktionerne på +GPIO 12 og 21, siden det er dem der er indtastet i array'et. + +### b) Vi skal have gjort read/write funktionerne generelle, så de duer på de GPIO'er vi har i vores struct. Dvs. opdatér read/write funktioner til at anvende gpio_devs struct'en. Husk at vi har adgang til minor nummeret i read/write vha: +```c +size_t plat_drv_read(struct file *filep, char __user *ubuf, size_t count, loff_t *f_pos) +{ + int minor; + minor = iminor(filep->f_inode); + + [gpio_get_value(gpio_devs[.......]) ] +} +``` +Byg og test at det virker på de GPIO'er som du har angivet i gpios_devs. Dokumentér med output af dmesg og resultater af dine tests og forklar hvordan de afspejler din implementering. + +Write(): +```c +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + int minor = MINOR(filep->f_inode->i_rdev); // Få minor nummer + struct gpio_dev *dev; + + // Tjek om minor nummeret er gyldigt + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; // Få den korrekte gpio_dev struct baseret på minor nummeret + + // Kontroller, om GPIO'en er sat til output + if (dev->dir != 1) { + pr_err("GPIO %d is not configured as output\n", dev->no); + return -EINVAL; + } + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; // Null-terminér strengen + + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(dev->no, value); // Sæt GPIO-værdien (output) + + pr_info("Wrote %i to GPIO %d\n", value, dev->no); + *f_pos += len; + return len; +} +``` +I mygpio_write bruges minor nummeret til at finde den specifikke GPIO-enhed i gpio_devs arrayet, så vi skriver til den korrekte GPIO. Funktionen sikrer, at GPIO’en er konfigureret som output, da kun output-GPIO’er kan få en værdi skrevet til dem. Hvis ikke, returneres fejlen -EINVAL. Funktionen læser derefter data fra ubuf, konverterer det til 0 eller 1, og skriver denne værdi til GPIO’en, hvilket sætter spændingen høj eller lav afhængigt af input. + +Read(): +```c +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + int minor = MINOR(filep->f_inode->i_rdev); // Få minor nummer + struct gpio_dev *dev; + + // Tjek om minor nummeret er gyldigt + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; // Få den korrekte gpio_dev struct baseret på minor nummeret + + // Kontroller, om GPIO'en er sat til input + if (dev->dir != 0) { + pr_err("GPIO %d is not configured as input\n", dev->no); + return -EINVAL; + } + + value = gpio_get_value(dev->no); // Læs GPIO-værdien (input) + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (*f_pos > 0) { + return 0; // EOF + } + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; + return len; +} +``` +I mygpio_read bruges minor nummeret til at finde og læse fra den korrekte GPIO-enhed. Funktionen tjekker, at GPIO’en er konfigureret som input; ellers returneres fejlen -EINVAL. Derefter læser den GPIO’ens værdi med gpio_get_value(), som returnerer 0 eller 1 afhængigt af spændingen. Denne værdi konverteres til en streng og kopieres til buf, så brugeren kan aflæse GPIO’ens status. + +Test: + + + + + +Man kan ved hjælp af echo tænde og slukke for en lampe. fx "echo 1 > /dev/plat_drv1". + + + +Denne opdatering gør read og write funktionerne generelle, så de kan bruges på de GPIO'er, der er defineret i gpio_devs structen. Ved at bruge minor nummeret vælges der automatisk den korrekte GPIO, og implementeringen håndterer dynamisk tildeling af input og output baseret på konfigurationen i gpio_devs. Dette gør koden fleksibel og nem at udvide med flere GPIO'er, hvis det ønskes. + + +## Delopgave 3: Anvendelse af Device Tree i driver + +Vi vil nu udvide vores device tree fil, .dts, til at indeholde en beskrivelse af hvilke GPIO'er vores driver skal bruge. Man kan på denne måde ændre GPIO'erne uden at skulle ændre driveren efterfølgende. Du bør have set lektionen og eksempler på how device tree overlays virker. Der er nogle WIKI sider og værktøjer til at oprette Device Tree Overlays som du kan bruge som hjælp. + +### a) Opdater plat_drv-overlay.dts til at inkludere GPIO pins. Brug de korrekte gpio numre og hhv '0' for input og '1' for output. I nedenstående eksempel er anvendt GPIO 55 som input og GPIO 56 som output: +``` +/dts-v1/; +/plugin/; +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; /* Add new virtual device to base path */ + __overlay__ { + mydevt: mydevt {/* devtree label:instance name */ + /* Label to match in driver */ + compatible = "my_device_name"; + + /* Configure GPIO module */ + /* <ressource pinno dir pinno dir pinno dir ....*/ + gpios = <&gpio 55 0>, <&gpio 56 1>; + + /* Custom Property */ + /* <32-bit int value*/ + mydevt-custom = <0x12345678>; + status = "okay"; /* Device is enabled */ + }; + }; + }; +}; +``` +Dts filen bygges sammen med kernemodulet, når du bruger Makefile'n med make. Bemærk at source filen plat_drv-overlay.dts bygges til filen plat_drv.dtbo. Omdøbningen sker i Makefile'n og skyldes navnekonventioner som anvendes ifb. med Device Tree Overlays. + +Der ændres på plat_drv-overlay.dts, så den følger skabelonen, der er udgivet, men med de rigtige GPIO numre samt in og out osv.: +``` +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + plat_drv: plat_drv@0 { + compatible = "au-ece, plat_drv"; + status = "okay"; + + /* Definer GPIO'er til driveren */ + gpios = <&gpio 12 0>, /* GPIO 12 som input */ + <&gpio 21 1>; /* GPIO 21 som output */ + + /* Tilføj evt. en brugerdefineret værdi */ + mydevt-custom = <0x12345678>; + }; + }; + }; +}; +``` +I ovenstående: +- gpio 12 0 betyder, at GPIO 12 er konfigureret som input (0 betyder input). +- gpio 21 1 betyder, at GPIO 21 er konfigureret som output (1 betyder output). + +Når .dts-filen bygges, vil Makefile generere en .dtbo-fil (Device Tree Overlay Binary) ved at omdøbe .dtb-filen til .dtbo som beskrevet i Makefilen. + + +### b) Anvend Device Tree parametre i device driveren + +Anvend of (Open Firmware) funktionerne i probe() til at udlæse GPIO numre og retning (in/out) fra Device Tree. Værdierne kan med fordel tilskrives elementerne i gpio_devs struct'en fra tidligere og efterfølgende bruges til initialisering af GPIO'er og oprettelse af devices (som I sådan set allerede er implementeret...). + +Vigtige funktioner fra of_gpio.h: +```c + static inline int of_gpio_count(struct device_node *np) + static inline int of_get_gpio(struct device_node *np, int index) + static inline int of_get_gpio_flags(struct device_node *np, int index, enum of_gpio_flags *flags) +``` +of_get_gpio() returnerer GPIO nummer eller en værdi <0 ved fejl. of_get_gpio_flags() returnerer flagets værdi by reference via flags parameteren. Returværdien er det tilhørende GPIO nummer eller en værdi <0 ved fejl. + +I driveren skal du opdatere probe() noget i stil med: + +```c +#include <linux/err.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> + +/* GPIO Device Data Struct */ +struct gpio_dev { + int gpio; + int flag; +}; + +static struct gpio_dev gpio_devs[255]; +int gpio_devs_cnt = 0; + +static int plat_drv_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *dev = &pdev->dev; // Device ptr derived from current platform_device + struct device_node *np = dev->of_node; // Device tree node ptr + enum of_gpio_flags flag; + int gpios_in_dt = 0; + + /* Retrieve number of GPIOs */ + [hent antal af gpioer (of_gpio_count)] + + /* Loop through gpios in Device Tree */ + for (int i=0;i<gpios_in_dt ; i++) { + /* hent gpio nummer (of_get_gpio) og skriv i struct */ + /* Hent gpio flag, dvs retning (of_get_gpio_flags) og skriv i struct */ + } + + for (int i=0;i<gpios_in_dt ; i++) { + /* request_gpio[gpio_devs[i].no] */ + /* Sæt gpio direction iht .dir i struct */ + /* Opret devices med device_create() */ + printk(KERN_ALERT "Using GPIO[%i], flag:%i on major:%i, minor:%i\n", + gpio_devs[gpio_devs_cnt].gpio, gpio_devs[gpio_devs_cnt].flag, + MY_DRIVERS_major_number, gt_devs_cnt); + } +} +``` +Du kan finde inspiration i spi-oc-tiny.c driveren som findes på jeres VMware image under /home/stud/sources/rpi-5.4.51/drivers/spi/spi-oc-tiny.c. Se funktionerne tiny_spi_probe() og tiny_spi_of_probe() (som kaldes i tiny_spi_probe()) + +```c +static int plat_drv_probe(struct platform_device *pdev) { + int err, i; + dev_t curr_devno; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + enum of_gpio_flags flag; + + // Hent antal GPIO'er fra Device Tree + gpios_len = of_gpio_count(np); + if (gpios_len <= 0) {} + + pr_info("Found %d GPIOs in Device Tree\n", gpios_len); + + // Loop gennem GPIO'er i Device Tree og hent konfiguration + for (i = 0; i < gpios_len; i++) {} + + // Opret karakterenheder og GPIO-konfigurationer baseret på Device Tree-data + for (i = 0; i < gpios_len; i++) { + // Anmod om GPIO + // Sæt GPIO-retning + // Opret device node for hver GPIO + } + + pr_info("Driver successfully probed with GPIO configuration from Device Tree\n"); + return 0; + + cleanup_gpio: +} +``` +Ændringerne i koden gør platformdriveren mere dynamisk og fleksibel ved at hente GPIO-konfigurationer fra Device Tree i stedet for at være afhængig af faste værdier. NUM_GPIOS er blevet erstattet af en variabel, gpios_len, der automatisk tilpasses efter det antal GPIO'er, der er defineret i Device Tree. Desuden er der tilføjet en of_match_table, hvilket gør det muligt for driveren at matche enheder baseret på deres compatible-strenge. Samlet set forbedrer disse ændringer driverens modularitet og genanvendelighed på tværs af forskellige hardwarekonfigurationer. + +## Delopgave 4: Automatisk load af Device Tree Overlay og driver modul + +For at loade device og driver automatisk under boot skal du gøre følgende 5 ting (ex: mit_modul.ko, mit_overlay.dtbo): + +1. Kopiere mit_modul.ko til /lib/modules/<linux-version>/ +2. Registrere modulet med depmod -a +3. Tilføje en entry i /etc/modules_load.d/ for at loade det automatisk under boot +4. Kopiere mit_overlay.dtbo til /boot/overlays/ +5. Lave entry i /boot/config.txt for at loade device definition under boot +-------------------- +1. Kernemoduler ligger som standard under /lib/modules/den_version_af_linux_som_er_i_brug/ (Version kan udlæses med uname -r). Vil du gøre dit kernemodul tilgængelig for systemet, skal du kopiere din .ko fil hertil. +2. Kør depmod -a, som undersøger modul dependencies og laver et load script. Modulet vil herefter kunne loades med modprobe mit_modul. +3. Linux skanner filerne under /etc/modules-load.d/ under upstart for at finde ud af hvilke moduler som skal loades. Du skal derfor oprette en fil her, som indeholder navnet på modulet som skal loades: +```sh +echo mit_modul /etc/modules-load.d/mit_modul.conf +``` +4. Kopiér dit overlay, mit_overlay.dtbo, til /boot/overlays på din Rpi. Dette er default søgesti for overlays. +5. Lav en backup af din config.txt fil ved at kopiere den til et nyt navn: +```sh +cp /boot/config.txt /boot/config.txt.backup +``` +6. Lav en entry i /boot/config.txt med navnet på overlayet, herved vil Linux søge efter det i /boot/overlays og indlæse det under boot. Brug en editor, f.eks. nano til at editere /boot/config.txt: +```sh +## Angiv overlay (uden .dtbo og ingen mellemrum omkring =) +dtoverlay=mit_overlay +``` +BRUG IKKE echo bla bla /boot/config.txt da det overskriver den eksisterende config.txt og din RPI vil ikke længere kunne boote. Du skal I så fald have hentet en config.txt ned fra en medstuderende og kopieret den ind på kortet eller kopiere din backup over i din config.txt. Du skal i begge tilfælde sætte dit SD kort i en kortlæser og mounte dem i Linux. config.txt ligger på FAT partitionen. + +Nu er både kernemodul og device tree blob registreret og disse vil automatisk blive loadet når Linux booter. Hvis der er match i "compatible" navnene i hhv. device tree og driver, vil der blive oprettet devices under /dev/ og entries i sysfs. Du kan checke om dit overlay bruger det rigtige navn ved at udlæse /sys/firmware/devicetree/instans_navn_i_mit_overlay/compatible. + +Test: +De følgene trin køres og laves: + + + +Efter genstart tjekkes der om der bliver oprettet noder i /dev. + + \ No newline at end of file diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1a1.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1a1.png new file mode 100644 index 0000000000000000000000000000000000000000..6d668be531fa6891475c7b6f90d50dd296e58a83 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1a1.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1a2.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1a2.png new file mode 100644 index 0000000000000000000000000000000000000000..bdab1547759126e8182362a0e82a1d6b7a4471cc Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1a2.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1b1.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1b1.png new file mode 100644 index 0000000000000000000000000000000000000000..c21192ea191ec5cdb93df7179f0ead271cf80680 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1b1.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1c1.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1c1.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f64357b7147b0443cd3ba4fb3f8386a113b28b Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1c1.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1c2.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1c2.png new file mode 100644 index 0000000000000000000000000000000000000000..ea7bcfbe45ea4ac9f6dcd5086a2df669f444a9bf Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1c2.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1c3.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1c3.png new file mode 100644 index 0000000000000000000000000000000000000000..be52df70e7c9d03566e3b94644111274c2ee4b68 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1c3.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1c4.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1c4.png new file mode 100644 index 0000000000000000000000000000000000000000..5bb33c0b84c714a447e110415f26dfe38979c58f Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1c4.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1d1.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1d1.png new file mode 100644 index 0000000000000000000000000000000000000000..58adb45f1c4f7735aadfb18ced56a36567c38515 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1d1.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1d2.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1d2.png new file mode 100644 index 0000000000000000000000000000000000000000..046f06a5b9ccb5f1a98c61820181c7f19c42950f Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1d2.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1d3.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1d3.png new file mode 100644 index 0000000000000000000000000000000000000000..5ef41cabf38d12f123baf8db7748120553bd9215 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1d3.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X1d4.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X1d4.png new file mode 100644 index 0000000000000000000000000000000000000000..2f7cd9da593c0c09ebe671b4b0ec1a915d2c17e6 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X1d4.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X2a1.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X2a1.png new file mode 100644 index 0000000000000000000000000000000000000000..a9885c3ddc9f98d6aaff54c25d64423f7a70a5d4 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X2a1.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X2a2.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X2a2.png new file mode 100644 index 0000000000000000000000000000000000000000..96e9f5c6349092823ce27fed5d5bbb1f9a02241a Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X2a2.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X2a3.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X2a3.png new file mode 100644 index 0000000000000000000000000000000000000000..603a043f960470a82dd375fdd9fbee38c3e420f8 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X2a3.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X2b1.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X2b1.png new file mode 100644 index 0000000000000000000000000000000000000000..4106ea3b63c99d7b87c3cd402a53d3371bd0b2ea Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X2b1.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X2b2.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X2b2.png new file mode 100644 index 0000000000000000000000000000000000000000..2a8d48e729a51ffdf4c89d665240e3ea90c0e5d4 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X2b2.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X2b3.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X2b3.png new file mode 100644 index 0000000000000000000000000000000000000000..1139eab9a975d9891f4a8e29bc585291cea633e1 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X2b3.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X41.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X41.png new file mode 100644 index 0000000000000000000000000000000000000000..1734000d793980a14da33c3b25d95cc407cd03d4 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X41.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/X42.png b/Group-3-main/Group-3-main/Lektion6_Afl2/X42.png new file mode 100644 index 0000000000000000000000000000000000000000..365bb8064cd055ff470d8e5a7dba88355f88fd74 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion6_Afl2/X42.png differ diff --git a/Group-3-main/Group-3-main/Lektion6_Afl2/gitignore b/Group-3-main/Group-3-main/Lektion6_Afl2/gitignore new file mode 100644 index 0000000000000000000000000000000000000000..144e9b1da081b8cfdd14bddf46eeb9772929a293 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion6_Afl2/gitignore @@ -0,0 +1,97 @@ +.gitignore +# +# NOTE! Don't add files that are generated in specific +# subdirectories here. Add them in the ".gitignore" file +# in that subdirectory instead. +# +# NOTE! Please use 'git ls-files -i --exclude-standard' +# command after changing this file, to see if there are +# any tracked files which get ignored after the change. +# +# Normal rules +# +.* +*.o +*.o.* +*.a +*.s +*.ko +*.so +*.so.dbg +*.mod.c +*.mod +*.i +*.lst +*.symtypes +*.order +modules.builtin +*.elf +*.bin +*.gz +*.bz2 +*.lzma +*.xz +*.lzo +*.patch +*.gcno +*.dtbo + +# +# Top-level generic files +# +/tags +/TAGS +/linux +/vmlinux +/vmlinuz +/System.map +/Module.markers +Module.symvers + +# +# Debian directory (make deb-pkg) +# +/debian/ + +# +# git files that we don't want to ignore even it they are dot-files +# +!.gitignore +!.mailmap + +# +# Generated include files +# +include/config +include/linux/version.h +include/generated +arch/*/include/generated + +# stgit generated dirs +patches-* + +# quilt's files +patches +series + +# cscope files +cscope.* +ncscope.* + +# gnu global files +GPATH +GRTAGS +GSYMS +GTAGS + +*.orig +*~ +\#*# + +# +# Leavings from module signing +# +extra_certificates +signing_key.priv +signing_key.x509 +x509.genkey diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/Attributter_X1.png b/Group-3-main/Group-3-main/Lektion8_Afl3/Attributter_X1.png new file mode 100644 index 0000000000000000000000000000000000000000..bf10fb617b09f2411a3933aa27117a835189fecf Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion8_Afl3/Attributter_X1.png differ diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/Makefile b/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..97f55f5d49372d5b39fa44e45bf00c8b033eeaf9 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/Makefile @@ -0,0 +1,43 @@ +# Kernel Module +KMODULE:=plat_drv +DTB_FILE:=$(KMODULE)-overlay.dtb +DTBO_FILE:=$(KMODULE).dtbo +KERNELDIR=~/sources/rpi-5.4.83 +CCPREFIX=arm-poky-linux-gnueabi- + +# To build modules outside of the kernel tree, we run "make" +# in the kernel source tree; the Makefile these then includes this +# Makefile once again. +# This conditional selects whether we are being included from the +# kernel Makefile or not. +ifeq ($(KERNELRELEASE),) +# The current directory is passed to sub-makes as argument + PWD:=$(shell pwd) + +modules: + $(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C ${KERNELDIR} M=$(PWD) + +# Compile the .dts file into a .dtbo file +# dtc -@ -I dts -O dtb -o $(DTB_FILE) $(KMODULE)-overlay.dts +# Rename .dtb to .dtbo, required by dtoverlay + mv $(DTB_FILE) $(DTBO_FILE) + +modules_install:modules + scp *.ko *.dtbo root@10.9.8.2: + +clean: + rm -rf *.o *.dtb *.dtbo *~ core .depend *.a *.mod .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers .*.tmp + +.PHONY:default clean + +else +# called from kernel build system: just declare what our modules are +# Ignore C90 decl after statement warning + ccflags-y:=-DDEBUG -g -std=gnu99 -Wno-declaration-after-statement +# Device Tree Blobs to build + always:=$(DTB_FILE) +# Kernel Object target file(s) + obj-m += $(KMODULE).o +# If object must be linked from multiple parts +#xxxxmod-objs := part1.o part2.o +endif diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/plat_drv-overlay.dts b/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/plat_drv-overlay.dts new file mode 100644 index 0000000000000000000000000000000000000000..b5b0aa97ac4f7349ad6cc25c11d602341352114e --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/plat_drv-overlay.dts @@ -0,0 +1,23 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + plat_drv: plat_drv@0 { + compatible = "mygpio, plat_drv"; + status = "okay"; + + /* Definer GPIO'er til driveren */ + gpios = <&gpio 12 0>, /* GPIO 12 som input */ + <&gpio 20 1>; /* GPIO 20 som output */ + + /* Tilføj evt. en brugerdefineret værdi */ + mydevt-custom = <0x12345678>; + }; + }; + }; +}; diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/plat_drv.c b/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/plat_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..aced933e374590e8e135e76cadd719a3e86f1c3e --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion8_Afl3/DeviceAttributer/plat_drv.c @@ -0,0 +1,305 @@ +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/of_gpio.h> +#include <linux/err.h> + +#define NUM_GPIOS 20 // Antal GPIO'er (tilpasses til dynamisk senere) + +// Struktur til at gemme GPIO-oplysninger +struct gpio_dev { + int no; // GPIO nummer + int dir; // 0: input, 1: output +}; +static struct gpio_dev gpio_devs[NUM_GPIOS]; +static int gpios_len; + +static dev_t devno; +static struct class *gpio_class; // Omdøbt fra 'led_class' til 'gpio_class' +static struct cdev gpio_cdev; // Omdøbt fra 'led_cdev' til 'gpio_cdev' +struct file_operations gpio_fops; // Omdøbt fra 'led_fops' til 'gpio_fops' +static int toggle_state; + +// Match-table for Device Tree +static const struct of_device_id gpio_of_match[] = { + { .compatible = "mygpio, plat_drv" }, + {}, +}; +MODULE_DEVICE_TABLE(of, gpio_of_match); + + +/* Sysfs "read" method prototype */ +static ssize_t gpio_state_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + printk(KERN_INFO "gpio_state_show called, state: %d\n", toggle_state); // Debug besked + return sprintf(buf, "%d\n", toggle_state); // Returnér længden af string +} + +/* Sysfs "write" method prototype */ +static ssize_t gpio_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + int state; + if (kstrtoint(buf, 10, &state)) { + printk(KERN_ERR "Invalid input for gpio_state_store\n"); + return -EINVAL; + } + toggle_state = state; + printk(KERN_INFO "gpio_toggle_state_store called, state: %d\n", toggle_state); // Debug besked + return size; // Returnér antallet af læste bytes +} + +DEVICE_ATTR_RW( gpio_state ); + +static struct attribute *led_attrs[] = { + &dev_attr_gpio_state.attr, + // More attributes could go in here, + NULL, + } ; + +ATTRIBUTE_GROUPS( led ); // Creates led_groups + +// Probe function +static int plat_drv_probe(struct platform_device *pdev) { + int err, i; + dev_t curr_devno; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + enum of_gpio_flags flag; + + // Hent antal GPIO'er fra Device Tree + gpios_len = of_gpio_count(np); + if (gpios_len <= 0) { + pr_err("No GPIOs defined in Device Tree\n"); + return -EINVAL; + } + + pr_info("Found %d GPIOs in Device Tree\n", gpios_len); + + // Loop gennem GPIO'er i Device Tree og hent konfiguration + for (i = 0; i < gpios_len; i++) { + gpio_devs[i].no = of_get_gpio(np, i); + if (gpio_devs[i].no < 0) { + pr_err("Failed to get GPIO %d\n", i); + return gpio_devs[i].no; + } + + if (of_get_gpio_flags(np, i, &flag) < 0) { + pr_err("Failed to get GPIO flags for GPIO %d\n", gpio_devs[i].no); + return -EINVAL; + } + + gpio_devs[i].dir = (flag == OF_GPIO_ACTIVE_LOW) ? 0 : 1; // 0 = input, 1 = output + + pr_info("Configured GPIO %d with direction %d\n", gpio_devs[i].no, gpio_devs[i].dir); + } + + // Opret karakterenheder og GPIO-konfigurationer baseret på Device Tree-data + for (i = 0; i < gpios_len; i++) { + struct gpio_dev *dev = &gpio_devs[i]; + + // Anmod om GPIO + err = gpio_request(dev->no, "gpio_dev"); + if (err) { + pr_err("Failed to request GPIO %d\n", dev->no); + goto cleanup_gpio; + } + + // Sæt GPIO-retning + if (dev->dir == 1) { // output + err = gpio_direction_output(dev->no, 0); + } else { // input + err = gpio_direction_input(dev->no); + } + if (err) { + pr_err("Failed to set direction for GPIO %d\n", dev->no); + gpio_free(dev->no); + goto cleanup_gpio; + } + + // Opret device node for hver GPIO + curr_devno = MKDEV(MAJOR(devno), MINOR(devno) + i); + device_create(gpio_class, NULL, curr_devno, NULL, "plat_drv%d", i); + pr_info("Created device for GPIO %d with minor %d\n", dev->no, MINOR(curr_devno)); + } + + pr_info("Driver successfully probed with GPIO configuration from Device Tree\n"); + return 0; + +cleanup_gpio: + while (i--) { + gpio_free(gpio_devs[i].no); + device_destroy(gpio_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + return err; +} + +// Remove function +static int plat_drv_remove(struct platform_device *pdev) { + int i; + pr_info("goodbye from remove\n"); + + // Fjern devices, cdev og class + for (i = 0; i < gpios_len; i++) { // Brug gpios_len her + gpio_free(gpio_devs[i].no); + device_destroy(gpio_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + cdev_del(&gpio_cdev); + class_destroy(gpio_class); + unregister_chrdev_region(devno, gpios_len); // Brug gpios_len her + + return 0; +} + +// Platform driver structure +static struct platform_driver gpio_platform_driver = { + .probe = plat_drv_probe, + .remove = plat_drv_remove, + .driver = { + .name = "plat_drv", + .of_match_table = gpio_of_match, // Matcher med Device Tree + .owner = THIS_MODULE, + }, +}; + +// Init function (register platform driver) +static int __init mygpio_init(void) { + int err; + + pr_info("Initializing GPIO Platform Driver\n"); + + // Alloker chrdev region + err = alloc_chrdev_region(&devno, 0, NUM_GPIOS, "plat_drv"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + return err; + } + + // Opret class og cdev + gpio_class = class_create(THIS_MODULE, "plat_drv_class"); + if (IS_ERR(gpio_class)) { + unregister_chrdev_region(devno, NUM_GPIOS); + return PTR_ERR(gpio_class); + } + gpio_class->dev_groups = led_groups; + + cdev_init(&gpio_cdev, &gpio_fops); + gpio_cdev.owner = THIS_MODULE; + err = cdev_add(&gpio_cdev, devno, NUM_GPIOS); + if (err) { + class_destroy(gpio_class); + unregister_chrdev_region(devno, NUM_GPIOS); + return err; + } + + return platform_driver_register(&gpio_platform_driver); +} + +// Exit function (unregister platform driver) +static void __exit mygpio_exit(void) { + platform_driver_unregister(&gpio_platform_driver); + cdev_del(&gpio_cdev); + class_destroy(gpio_class); + unregister_chrdev_region(devno, NUM_GPIOS); +} + +// File operations functions +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening GPIO Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing GPIO Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + int minor = MINOR(filep->f_inode->i_rdev); + struct gpio_dev *dev; + + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; + if (dev->dir != 1) { + pr_err("GPIO %d is not configured as output\n", dev->no); + return -EINVAL; + } + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(dev->no, value); + pr_info("Wrote %i to GPIO %d\n", value, dev->no); + *f_pos += len; + return len; +} + +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + int minor = MINOR(filep->f_inode->i_rdev); + struct gpio_dev *dev; + + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; + value = gpio_get_value(dev->no); + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (len < 0 || len > count) { + return -EINVAL; + } + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; + return len; +} + + + +// File operations +struct file_operations gpio_fops = { + .owner = THIS_MODULE, + .open = mygpio_open, + .release = mygpio_release, + .write = mygpio_write, + .read = mygpio_read, +}; + +module_init(mygpio_init); +module_exit(mygpio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dit Navn"); +MODULE_DESCRIPTION("GPIO Platform Driver"); diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/Dmesg_X1.png b/Group-3-main/Group-3-main/Lektion8_Afl3/Dmesg_X1.png new file mode 100644 index 0000000000000000000000000000000000000000..24d8e750b778e3d0265d8a1ff4b37de8c14eb214 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion8_Afl3/Dmesg_X1.png differ diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/Klasse_X1.png b/Group-3-main/Group-3-main/Lektion8_Afl3/Klasse_X1.png new file mode 100644 index 0000000000000000000000000000000000000000..6e8768f551063e7ef1eb7d00689a93e284f3574e Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion8_Afl3/Klasse_X1.png differ diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/README.md b/Group-3-main/Group-3-main/Lektion8_Afl3/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d02fa86be849695c4b5dfbbcbf0fa9be87a01685 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion8_Afl3/README.md @@ -0,0 +1,494 @@ +# Group 3 +# Members +* John Nguyen +* Ibrahem Merhi +* Khaled Rami Omar + +# Link to repository +https://gitlab.au.dk/au-ece-sw3hal/e2024/Group-3.git + + +# Øvelse: LDD med Timers og Attributes (Rpi) + +## Formål + +Formålet med denne øvelse opnår erfaring med at SysFS attributter og +timers ved er at opdatere en GPIO device driver således at den anvender +SysFS attributter til at styre en ny attribute som lader en GPIO toggle. + +Du forventes med øvelsen at opfylde følgende mål: +| **Mål** | **Delmål 1** | **Delmål 2** | **Delmål 3** | +|------------------------------|-------------------------------------|----------------------------------------|----------------------------------| +| Oprettelse af attributter | show/store funktioner | Deklarere dem og gøre dem til default attributter | Bygge til- og teste på target | +| Lave attribute funktionalitet | Implementer timer state | Implementer timer delay | Test med printk | +| Lave timer funktion | Implementer timer callback | Test på board | Mål timers præcision og jitter | + +## Godkendelse + +I bestemmer selv om I vil aflevere øvelse 7 eller 8. Deadline er som +angivet i lektionsplanen på Brightspace.\ +Såfremt I aflverer denne, skal Besvarelsen indeholde relevante +kodeudsnit som der henvises til i forklarende tekst, tests og diskussion +af resultater heraf. Komplet kode inklusiv Makefiles lægges i +repository. + +## Forberedelse + +- Du skal have læst de relevante sider i kapitel 4+5 i Essential Linux + Device Drivers +- Se på source koden i + \~/sources/rpi-5.4.83/drivers/leds/led-class.c + leds-gpio.c +- Se detaljer omkring "Reading/Writing Attribute Data" i sysfs.txt. Se + linje 165 og frem og eksempler linje 229 og frem +- Kopier platform driveren fra øvelse 6 til en ny folder, vi arbejder + videre med denne. +- Du skal have en Raspberry Pi Zero, samt en ASE fHAT med komponenter + monteret som vist på figuren herunder: + + + +## Øvelsen + +Du skal i øvelsen udvide din platform driver til at kunne toggle GPIO +output portene ved hjælp af en timer funktion. Toggle delay og hvorvidt +gpio'en toggler (toggle_state) skal kunne sættes og udlæses ved hjælp af +attributes. + +Den overordnede struktur for driveren er således (**eksempel!!! +attribute, *my_led_state***): + +```C +#include <linux/gpio.h> +... +static ssize_t my_led_state_show(struct device *dev, struct device_attribute *attr, char *buf) +{ ... } + +static ssize_t my_led_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ ... } +... +DEVICE_ATTR_RW(my_led_state); +static struct attribute *my_led_attrs[] = { ... } +ATTRIBUTE_GROUPS(my_led); // Creates led_groups +... +static int __init my_platform_driver_init(void) +{ + ... + gpio_class = class_create(THIS_MODULE, "my_led"); + gpio_class->dev_groups = my_led_groups; + + err = platform_driver_register(&my_led_platform_driver); + ... +} +``` + +Bemærk ifht. eksemplerne i bogen, at funktionen *class_device_create()* +er erstattet med *device_create()* og I derfor ikke skal spekulere på +manipulation af kobjekter. **Bemærk desuden at *my_led_state* kun er et +eksempel, ikke navnet på den attribute som I selv skal lave!!** + +### Oprettelse af device attributer + +Opret show + store funktioner for attributten *gpio_toggle_state*. +Funktionerne har prototyperne: + +```C +/* Global Variable */ +static int toggle_state; +/* Sysfs "read" method prototype */ +static ssize_t gpio_toggle_state_show(struct device *dev, struct device_attribute *attr, char *buf) +/* Sysfs "write" method prototype */ +static ssize_t gpio_toggle_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +``` + +Indtil videre skal *_store()* funktionen alene gemme en værdi i en global variabel, mens *_show()* udlæser den. Lav gerne en printk i de to funktioner til debugging, husk "\\n" i tekststrengen. + +Attributten skal nu deklareres og angives som en default attribute for vores class. Benyt nedenstående makroer og struct til at deklarere variable. + +Fra *device.h* +[\~/sources/rpi-5.4.83/include/linux/device.h](http://git.kernel.org/?p=linux/kernel/git/stable/linux-stable.git;a=blob;f=include/linux/device.h); +): + +```C +// Example of creating an r/w attribute "state" for the class "led" and adding it to the attribute group "led" + +DEVICE_ATTR_RW(led_state); // Creates dev_attr_led_state + +static struct attribute *led_attrs[] = { + &dev_attr_led_state.attr, + // More attributes could go in here, + NULL, + } ; + +ATTRIBUTE_GROUPS(led); // Creates led_groups +``` + +Herefter skal du i driverens init funktion sætte dev_groups på den nyoprettede class pointer. Det er vigtigt at du gør det umiddelbart efter *class_create()* og inden *platform_register()*. Kaldet til *platform_register()* gør at *probe()* kaldes med det samme, hvis der allerede er loaded et device tree. + +Metoden der er brugt i bogen giver mulighed for at lave specifikke attributter for et givet device. Det står så derfor i kontrast til det I laver her, hvor alle "devices" af samme "class" har/får samme adfærd ("nedarver"). + +#### Løsning: + +Der startes med at tilføje, de nøsvændige funktioner ind i platform driver fra lektion 6: +```c + +static ssize_t mygpio_state_show(struct device *dev, struct device_attribute *attr, char *buf) +{ ... } + +static ssize_t mygpio_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ ... } +... +DEVICE_ATTR_RW(mygpio_state); +static struct attribute *mygpio_attrs[] = { ... } +ATTRIBUTE_GROUPS(mygpio); // Creates led_groups +... +static int __init my_platform_driver_init(void) +{ + ... + gpio_class = class_create(THIS_MODULE, "my_led"); + gpio_class->dev_groups = my_led_groups; + + err = platform_driver_register(&my_led_platform_driver); + ... +} +``` +Derefter tilføjes de givne globale variabler: +```c +/* Global Variable */ +static int toggle_state; +/* Sysfs "read" method prototype */ +static ssize_t gpio_toggle_state_show(struct device *dev, struct device_attribute *attr, char *buf) +/* Sysfs "write" method prototype */ +static ssize_t gpio_toggle_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +``` + +Derefter skal my_led_attrs[] udfyldes, med de givne kode: +```c +static struct attribute *my_led_attrs[] = { + &dev_attr_led_state.attr, + // More attributes could go in here, + NULL, + } +``` + +Herefter skrives debug beskederne i _show og _stor funktionerne: +```c +/* Sysfs "read" method prototype */ +static ssize_t gpio_toggle_state_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + printk(KERN_INFO "gpio_toggle_state_show called, state: %d\n", gpio_toggle_state); // Debug besked +} +/* Sysfs "write" method prototype */ +static ssize_t gpio_toggle_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + printk(KERN_INFO "gpio_toggle_state_store called, state: %d\n", gpio_toggle_state); // Debug besked +} +``` + +Der sættes herefter dev_groups ind det korrekte sted og der videreudvikles på _init funktionen: +```c +static int __init mygpio_init(void) { + int err; + + pr_info("Initializing GPIO Platform Driver\n"); + + // Alloker chrdev region + err = alloc_chrdev_region(&devno, 0, NUM_GPIOS, "plat_drv"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + return err; + } + + // Opret klassen + gpio_class = class_create(THIS_MODULE, "my_led"); + if (IS_ERR(gpio_class)) { + unregister_chrdev_region(devno, NUM_GPIOS); + return PTR_ERR(gpio_class); + } + + // Sæt dev_groups til klassen + gpio_class->dev_groups = my_led_groups; + + // Opret cdev og tilføj den + cdev_init(&led_cdev, &led_fops); + led_cdev.owner = THIS_MODULE; + err = cdev_add(&led_cdev, devno, NUM_GPIOS); + if (err) { + pr_err("Failed to add cdev\n"); + class_destroy(gpio_class); + unregister_chrdev_region(devno, NUM_GPIOS); + return err; + } + + // Registrer platformdriveren + err = platform_driver_register(&my_led_platform_driver); + if (err) { + pr_err("Failed to register platform driver\n"); + cdev_del(&led_cdev); + class_destroy(gpio_class); + unregister_chrdev_region(devno, NUM_GPIOS); + return err; + } + + return 0; +} +``` + +#### Test af atttributter på target + +Indsæt modul og load devicetree. Bemærk at der i /sys/ under dit/dine nye device(s) er kommet en attribute. Skriv og læs på attributten og check i dmesg at det lykkedes. + +Der startes med at registrerer device't ved at indlæse .dtbo filen: +``` +dtoverlay -d . plat_drv.dtbo +``` + +Derefter indsættes modulet for .ko filen: +``` +insmod plat_drv.ko +``` +Der køres "dmesg" for at se om device tree er kørt: + + + +Der testes om klassen for plat_drv er oprettet: + + + +Der bude være oprettet to atributter under klassen (drv0 og drv1): + + + +Inde i hvert atribut burde der være en en gpio_state fil, som man kan ændre værdien på til at udskrive en besked med "dmesg". + + + +Nu testes der om funktionerne store() og show() fungere som de skal vha. cat og echo komand, der aflæses på dmesg om de fungere: +``` +echo 1 > gpio_state +cat > gpio_state +``` + + + +Ud fra billederne af testen finder vi ud af at show() og store() fungere og kører som den skal, samt printer forventede beskeder når "dmesg" køres. + + +### Implementering af toggler attributter + +Vi går nu videre med den allerede oprettet attribute og udvider med funktionalitet og en attribute mere: + +* gpio_toggle_state - Sætter/udlæser vores GPIO toggle tilstand (1=toggling / 0=not toggling), dvs den skal oprette/starte- og slette timeren alt efter om værdien er 0 eller 1. +* gpio_toggle_delay - Sætter/udlæser 1/toggle hastigheden, dvs den skal sætte en variabel som indeholder hvor meget der skal lægges til expiration time i timerfunktionen. + +Der skal bruges følgende funktioner (fra linux/timer.h): + +* "timer_setup(timer, callback, flags)":https://elixir.bootlin.com/linux/latest/source/include/linux/timer.h#L126\ +* "extern int mod_timer(struct timer_list \*timer, unsigned long expires)":https://elixir.bootlin.com/linux/latest/source/include/linux/timer.h#L157\ +* "int del_timer_sync(struct timer_list \*timer)": https://elixir.bootlin.com/linux/latest/source/include/linux/timer.h#L183 + +og en timer callback funktion på formen: + +* ```static void my_timer_callback(struct timer_list *t)``` + +Toggler funktionaliten skal implementeres vha en kernel timer. Se bog og slides for detaljer herom. N.B. For at stoppe/slette en timer kaldes ```del_timer_sync(&timer)```. + +Det er muligt at lave lave toggler funktionaliteten på to måder: + +* Toggler funktionaliteten virker på alle gpio'er på en gang. +* Toggler funktionaliteten virker individuelt på de enkelte gpio'er, dvs. delay og state gælder per gpio. (Mere avanceret) + +I begge tilfælde har attributterne tilhørende show og store metoder. Det følgende er lidt hjælp til implementeringen af disse. + +#### Show metoden + +Denne metode skal blot udlæse tilstanden af den pågældende attribute. Bruger man globale variabler for hhv. *state* og *delay* kan man nøjes med at læse værdien af den respektive tilbage og se bort fra det følgende omkring drvdata. + +Har man *state* og *delay* for hver GPIO skal man have disse data tilknyttet det pågældende device (som er oprettet med device \_create...). Hvert oprettet device er af typen struct device, og denne indeholder et element, drvdata af typen void\*.\ Når man opretter et device med device_create kan man initialisere denne pointer med data som tilhører det pågældende device. Disse data kan siden tilgås i hhv. show()/store() funktionerne vha funktionen *dev_get_data()*. I det følgende eksempel initialiseres drvdata med en pointer til en int med gpio nummeret og siden hentes gpio nummeret ud i *show()* funktionen: + +```C +static int gpio_no = 164; /* Global variabel with GPIO number */ + +chr_drv_probe(..) { + ... + device_create(my_class, NULL, MKDEV(MAJOR(devno), its_minor), + &gpio_no, "mygpiodevicelabel"); +...} + +static ssize_t gpio_toggle_delay_show(struct device *dev, struct device_attribute *attr, char *buf) { + + int *gpio_in_use_ptr = dev_get_drvdata(dev); + printk("Show accessed for device with GPIO %i\n", *gpio_in_use_ptr); + ... +} +``` + +Skal man bruge dette i vores sammenhæng, ville det være smart at kunne få adgang til det rigtige element i vores gpio_devs struct.... ;-) + +Desuden kunne det være smart at udvide vores gpio_devs struct til også at indeholde togge_state, toggle_delay og evt. en struct timer. Herved kan vi gemme toggle data for hver gpio port. + +#### Store metoden + +Store metoden er meget lig det vi kender fra *write* metoden. Vi behøver dog ikke kopiere fra user space. Ex: + +```C +static ssize_t gpio_toggle_delay_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + ssize_t ret = -EINVAL; + unsigned long value; + + // Force zero termination to end of string + buf[size-1] = 0; + + // Convert string to unsigned long (s to ul) + if((ret = kstrtoul(buf, 10, &value))<0) + return ret; + + /* Do something with value */ + + ret = size; // Always read the full content of buf + return ret; +} +``` + +I den simple udgave skal vi blot opdatere de respektive globale variable (hhv. toggle_delay og toggle_state) og ved *toggle_state_store()* skal vi desuden initialisere timeren og starte den, hvis værdien er '1' eller delete den (del_timer_sync()), hvis værdien er '0'. + +I udgaven med en timer per gpio, er det data'ene som kan tilgås via dev_get_drvdata() som skal opdateres, og timeren som skal startes. Bemærk!! timerens .data element kan indeholde en unsigned long int. Dette er netop hvad en pointer fylder, og man kan derfor angive en pointer, som kan derefereres og anvendes i timerens callback (handler) funktion. + +**NOTE!** Det er vigtigt at få lukket timeren ned hvis modulet tages ud eller device tree unloades. Timeren lukkes ned med hjælp af *del_timer_sync()* find funktionsprototypen mm. med en søgning på <https://elixir.bootlin.com> bemærk at der i resultatet både hvor funktionen er defineret som prototype, hvor den er defineret som funktion (kode) og hvor man kan finde dokumentation. Hvad gør funktionen ud over at deaktivere timeren? + +### Implementering af Timerfunktion + +I alle tilfælde opdateres expiration time og gpio toggles. + +I den simple udgave kan man vælge enten blot at toggle en enkelt gpio eller alle gpioer. Man kan også løbe alle gpio'er igennem og kun toggle de, for hvem "toggle_state" er sat til '1'. + +Benytter man en timer per gpio, caster man unsigned long parameteren til en pointer af den rigtige struct type og tilgår via denne device's gpio nummer, timer og delay. + + +### Implementering af toggle, delay og timer funktioner mm.: + +I denne opgave blev en platformdriver udvidet til at håndtere GPIO-toggling ved hjælp af timerfunktioner. Fokus var på at give hver GPIO en individuel toggling tilstand og forsinkelse, så de kunne styres uafhængigt af hinanden. Dette blev opnået ved at bruge en timer for hver GPIO, som kontrollerede toggling af tilstanden. + +Den overordnede struktur består af flere komponenter: + +GPIO-enhedens Struktur (gpio_dev): En struktur blev oprettet for at holde styr på oplysninger for hver GPIO-enhed. Denne struktur indeholder: +- gpio_number: Identifikationen af GPIO-porten. +- toggle_state: En variabel, der angiver, om GPIO'en er aktiv (1) eller inaktiv (0). +- toggle_delay: Den forsinkelse, der bruges ved toggling. +- timer: En struktur, der repræsenterer timeren for GPIO'en. + +Strukturen gør det muligt at holde styr på hver enkelt GPIO’s tilstand og forsinkelse. +```c +struct gpio_dev { + int no; // GPIO nummer + int dir; // Retning + int toggle_state; // 1=toggling, 0=ikke toggling + int toggle_delay; // Delay i ms + struct timer_list timer; // Kernel-timer +}; +``` + +Timer Callback Funktion: Timeren blev implementeret ved at bruge en callback-funktion, som bliver kaldt, når timeren udløber. Denne funktion toggler GPIO'ens tilstand, og hvis toggle_state er sat til 1, starter den timeren for at fortsætte togglingen. + +Funktionens operation: +- Først hentes den relevante gpio_dev struktur ved hjælp af from_timer(). +- Hvis toggle_state er 1 (aktivt), toggles GPIO’ens tilstand, og timeren opdateres med den forsinkelse, der er angivet i toggle_delay. +- Hvis toggle_state er 0, stopper toggling af GPIO'en + +```c +static void gpio_timer_callback(struct timer_list *t) { + struct gpio_dev *dev = from_timer(dev, t, timer); + if (dev->toggle_state) { + gpio_set_value(dev->no, !gpio_get_value(dev->no)); + mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->toggle_delay)); + } +} +``` +Show og Store Funktioner: +Sysfs-attributter blev brugt til at styre tilstanden og forsinkelsen for hver GPIO. For hver GPIO er der oprettet show og store funktioner for at kunne læse og skrive til toggle_state og toggle_delay. + +- gpio_toggle_delay_show(): Denne funktion returnerer den aktuelle forsinkelse (delay) mellem toggling for en given GPIO. Værdien returneres som en streng, som kan læses af brugerland. +- gpio_toggle_delay_store(): Denne funktion opdaterer toggle_delay og justerer forsinkelsen mellem toggling. Når værdien ændres, justeres timerens expiration tid for at reflektere den nye forsinkelse. + +Når toggle_delay opdateres, sørger store-funktionen for at justere timerens timing for at matche den nye forsinkelse. + +- gpio_toggle_state_show(): Denne funktion returnerer den aktuelle tilstand af toggling (1 for aktiveret, 0 for deaktiveret) for en given GPIO. +- gpio_toggle_state_store(): Denne funktion opdaterer toggle_state og starter eller stopper timeren afhængigt af den værdi, der skrives til sysfs. Hvis toggle_state sættes til 1, startes timeren, mens den stoppes, hvis den sættes til 0. + +Når toggle_state opdateres, sørger store-funktionen for enten at starte eller stoppe timeren baseret på den nye værdi. +```c +static ssize_t gpio_toggle_state_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct gpio_dev *gpio_dev = dev_get_drvdata(dev); + if (!gpio_dev) return -EINVAL; + + return sprintf(buf, "%d\n", gpio_dev->toggle_state); +} + +static ssize_t gpio_toggle_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + struct gpio_dev *gpio_dev = dev_get_drvdata(dev); + int state; + + if (!gpio_dev) return -EINVAL; + + if (kstrtoint(buf, 10, &state)) return -EINVAL; + + gpio_dev->toggle_state = state; + + if (state) { + mod_timer(&gpio_dev->timer, jiffies + msecs_to_jiffies(gpio_dev->toggle_delay)); + } else { + del_timer_sync(&gpio_dev->timer); + } + + return size; +} + +static ssize_t gpio_toggle_delay_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct gpio_dev *gpio_dev = dev_get_drvdata(dev); + if (!gpio_dev) return -EINVAL; + + return sprintf(buf, "%d\n", gpio_dev->toggle_delay); +} + +static ssize_t gpio_toggle_delay_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + struct gpio_dev *gpio_dev = dev_get_drvdata(dev); + int delay; + + if (!gpio_dev) return -EINVAL; + + if (kstrtoint(buf, 10, &delay)) return -EINVAL; + + gpio_dev->toggle_delay = delay; + return size; +} +``` + +Initialization af GPIO Devices: Når driveren initialiseres, oprettes der en GPIO-enhed for hver GPIO, som driveren skal håndtere. Hver enhed får tildelt en gpio_dev struktur, som knyttes til enheden via dev_set_drvdata(). Dette gør det muligt at tilgå de relevante data i show og store funktionerne samt i timer callbacken. + +```c +DEVICE_ATTR_RW( gpio_toggle_delay ); +DEVICE_ATTR_RW( gpio_toggle_state ); + +// Attributter til SysFS: +static struct attribute *gpio_attrs[] = { + &dev_attr_gpio_toggle_state.attr, + &dev_attr_gpio_toggle_delay.attr, + NULL, +}; +ATTRIBUTE_GROUPS( gpio ); +``` + + +### Test af Toggler på Target + +Gør som i tidligere tests. Anvend echo til at sætte et toggle delay og starte din toggler på target. Check om LED'en lyser. Lyser den svagt kan det være fordi at den blinker hurtigt. Anvend evt printk/dmesg beskeder til at debugge og verificere dit arbejde. Check desuden gerne med Analog Discovery hvor godt toggle frekvensen passer, hvor meget [jitter](https://e2e.ti.com/blogs_/archives/b/energyzarr/archive/2012/10/26/the-truth-about-jitter) der er på den og om den ændrer sig over tid. + +Først overføres .ko- og .dtbo-filerne til Raspberry Pi'en ved hjælp af scp, som beskrevet i den tidligere del. Efter filerne er overført, indlæses .ko-modulet med kommandoen insmod, og .dtbo-overlayen aktiveres med dtoverlay -d. Derefter anvendes echo-kommandoen til at konfigurere både toggle_delay og toggle_state. For at tænde LED'en med en forsinkelse og derefter slukke den, kan echo og cat anvendes som følger: + + + + + +Implementeringen af GPIO-toggling ved hjælp af en kernel-timer blev gennemført med succes, og de ønskede funktioner blev opnået. Systemet fungerer som forventet, og det er nu muligt at tænde og slukke for toggling ved at skrive værdien 1 eller 0 til gpio_toggle_state via sysfs og echo. Hastigheden på togglingen kan også justeres ved at skrive en forsinkelsesværdi i millisekunder til gpio_toggle_delay. Den aktuelle tilstand af både gpio_toggle_state og gpio_toggle_delay kan observeres ved hjælp af cat. + diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/Makefile b/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..97f55f5d49372d5b39fa44e45bf00c8b033eeaf9 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/Makefile @@ -0,0 +1,43 @@ +# Kernel Module +KMODULE:=plat_drv +DTB_FILE:=$(KMODULE)-overlay.dtb +DTBO_FILE:=$(KMODULE).dtbo +KERNELDIR=~/sources/rpi-5.4.83 +CCPREFIX=arm-poky-linux-gnueabi- + +# To build modules outside of the kernel tree, we run "make" +# in the kernel source tree; the Makefile these then includes this +# Makefile once again. +# This conditional selects whether we are being included from the +# kernel Makefile or not. +ifeq ($(KERNELRELEASE),) +# The current directory is passed to sub-makes as argument + PWD:=$(shell pwd) + +modules: + $(MAKE) ARCH=arm CROSS_COMPILE=${CCPREFIX} -C ${KERNELDIR} M=$(PWD) + +# Compile the .dts file into a .dtbo file +# dtc -@ -I dts -O dtb -o $(DTB_FILE) $(KMODULE)-overlay.dts +# Rename .dtb to .dtbo, required by dtoverlay + mv $(DTB_FILE) $(DTBO_FILE) + +modules_install:modules + scp *.ko *.dtbo root@10.9.8.2: + +clean: + rm -rf *.o *.dtb *.dtbo *~ core .depend *.a *.mod .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers .*.tmp + +.PHONY:default clean + +else +# called from kernel build system: just declare what our modules are +# Ignore C90 decl after statement warning + ccflags-y:=-DDEBUG -g -std=gnu99 -Wno-declaration-after-statement +# Device Tree Blobs to build + always:=$(DTB_FILE) +# Kernel Object target file(s) + obj-m += $(KMODULE).o +# If object must be linked from multiple parts +#xxxxmod-objs := part1.o part2.o +endif diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/plat_drv-overlay.dts b/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/plat_drv-overlay.dts new file mode 100644 index 0000000000000000000000000000000000000000..c431a8810f55f60d7d2f83739535ed1cdc366f05 --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/plat_drv-overlay.dts @@ -0,0 +1,23 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + __overlay__ { + plat_drv: plat_drv@0 { + compatible = "mygpio, plat_drv"; + status = "okay"; + + /* Definer GPIO'er til driveren */ + gpios = <&gpio 12 0>, /* GPIO 12 som input */ + <&gpio 21 1>; /* GPIO 20 som output */ + + /* Tilføj evt. en brugerdefineret værdi */ + mydevt-custom = <0x12345678>; + }; + }; + }; +}; diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/plat_drv.c b/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/plat_drv.c new file mode 100644 index 0000000000000000000000000000000000000000..9468bb216457306fe0332bd9612bd7ac03e9f4fa --- /dev/null +++ b/Group-3-main/Group-3-main/Lektion8_Afl3/Timerfunktion/plat_drv.c @@ -0,0 +1,368 @@ +#include <linux/gpio.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/of_gpio.h> +#include <linux/err.h> + +#define NUM_GPIOS 21 // Antal GPIO'er (tilpasses til dynamisk senere) + +// Struktur til at gemme GPIO-oplysninger +struct gpio_dev { + int no; // GPIO nummer + int dir; // Retning + int toggle_state; // 1=toggling, 0=ikke toggling + int toggle_delay; // Delay i ms + struct timer_list timer; // Kernel-timer +}; + +static struct gpio_dev gpio_devs[NUM_GPIOS]; +static int gpios_len; +static dev_t devno; +static struct class *gpio_class; // Omdøbt fra 'led_class' til 'gpio_class' +static struct cdev gpio_cdev; // Omdøbt fra 'led_cdev' til 'gpio_cdev' +struct file_operations gpio_fops; // Omdøbt fra 'led_fops' til 'gpio_fops' + +// Match-table for Device Tree +static const struct of_device_id gpio_of_match[] = { + { .compatible = "mygpio, plat_drv" }, + {}, +}; +MODULE_DEVICE_TABLE(of, gpio_of_match); + +// Timer callback +static void gpio_timer_callback(struct timer_list *t) { + struct gpio_dev *dev = from_timer(dev, t, timer); + + // Hvis toggling er aktiv for denne GPIO + if (dev->toggle_state) { + // Skift GPIO-tilstand + gpio_set_value(dev->no, !gpio_get_value(dev->no)); + + // Planlæg næste toggling baseret på delay + mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->toggle_delay)); + } +} + + +// Probe function for the platform driver +static int plat_drv_probe(struct platform_device *pdev) { + int err, i; + dev_t curr_devno; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + enum of_gpio_flags flag; + + pr_info("Probing platform driver\n"); + + // Retrieve the number of GPIOs from the Device Tree + gpios_len = of_gpio_count(np); + if (gpios_len <= 0) { + pr_err("No GPIOs defined in Device Tree\n"); + return -EINVAL; + } + + pr_info("Found %d GPIOs in Device Tree\n", gpios_len); + + // Loop through GPIOs in the Device Tree and retrieve configurations + for (i = 0; i < gpios_len; i++) { + gpio_devs[i].no = of_get_gpio(np, i); + if (gpio_devs[i].no < 0) { + pr_err("Failed to get GPIO %d\n", i); + return gpio_devs[i].no; + } + + if (of_get_gpio_flags(np, i, &flag) < 0) { + pr_err("Failed to get GPIO flags for GPIO %d\n", gpio_devs[i].no); + return -EINVAL; + } + + // Set GPIO direction: 0 = input, 1 = output + gpio_devs[i].dir = (flag == OF_GPIO_ACTIVE_LOW) ? 0 : 1; + + pr_info("Configured GPIO %d with direction %d\n", gpio_devs[i].no, gpio_devs[i].dir); + } + + // Create character devices and set up GPIO configurations + for (i = 0; i < gpios_len; i++) { + struct gpio_dev *dev = &gpio_devs[i]; + + // Initialize toggling parameters + dev->toggle_state = 0; // Default: not toggling + dev->toggle_delay = 1000; // Default delay in ms + timer_setup(&dev->timer, gpio_timer_callback, 0); // Initialize kernel timer + + // Request the GPIO + err = gpio_request(dev->no, "gpio_dev"); + if (err) { + pr_err("Failed to request GPIO %d\n", dev->no); + goto cleanup_gpio; + } + + // Set GPIO direction + if (dev->dir == 1) { // output + err = gpio_direction_output(dev->no, 0); + } else { // input + err = gpio_direction_input(dev->no); + } + if (err) { + pr_err("Failed to set direction for GPIO %d\n", dev->no); + gpio_free(dev->no); + goto cleanup_gpio; + } + + // Create a device node for each GPIO + curr_devno = MKDEV(MAJOR(devno), MINOR(devno) + i); + device_create(gpio_class, NULL, curr_devno, &gpio_devs[i], "plat_drv%d", i); + pr_info("Created device for GPIO %d with minor %d\n", dev->no, MINOR(curr_devno)); + } + + pr_info("Driver successfully probed with GPIO configuration from Device Tree\n"); + return 0; + +cleanup_gpio: + // Cleanup in case of failure + while (i--) { + del_timer_sync(&gpio_devs[i].timer); // Remove the timer + gpio_free(gpio_devs[i].no); // Free the GPIO + device_destroy(gpio_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); // Destroy the device node + } + return err; +} + + + +// Remove function +static int plat_drv_remove(struct platform_device *pdev) { + int i; + + for (i = 0; i < gpios_len; i++) { + struct gpio_dev *dev = &gpio_devs[i]; + + del_timer_sync(&dev->timer); // Stop timer + gpio_free(dev->no); // Frigiv GPIO + device_destroy(gpio_class, MKDEV(MAJOR(devno), MINOR(devno) + i)); + } + + cdev_del(&gpio_cdev); + class_destroy(gpio_class); + unregister_chrdev_region(devno, gpios_len); + + return 0; +} + + +// Platform driver structure +static struct platform_driver gpio_platform_driver = { + .probe = plat_drv_probe, + .remove = plat_drv_remove, + .driver = { + .name = "plat_drv", + .of_match_table = gpio_of_match, // Matcher med Device Tree + .owner = THIS_MODULE, + }, +}; + +// gpio_toggle_state show: +static ssize_t gpio_toggle_state_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct gpio_dev *gpio_dev = dev_get_drvdata(dev); + if (!gpio_dev) return -EINVAL; + + return sprintf(buf, "%d\n", gpio_dev->toggle_state); +} + +// gpio_toggle_state store: +static ssize_t gpio_toggle_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + struct gpio_dev *gpio_dev = dev_get_drvdata(dev); + int state; + + if (!gpio_dev) return -EINVAL; + + if (kstrtoint(buf, 10, &state)) return -EINVAL; + + gpio_dev->toggle_state = state; + + if (state) { + // Start timer with the current delay + mod_timer(&gpio_dev->timer, jiffies + msecs_to_jiffies(gpio_dev->toggle_delay)); + } else { + // Stop timer + del_timer_sync(&gpio_dev->timer); + } + + return size; +} + + + +// gpio_toggle_delay show +static ssize_t gpio_toggle_delay_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct gpio_dev *gpio_dev = dev_get_drvdata(dev); + if (!gpio_dev) return -EINVAL; + + return sprintf(buf, "%d\n", gpio_dev->toggle_delay); +} + +// gpio_toggle_delay store +static ssize_t gpio_toggle_delay_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + struct gpio_dev *gpio_dev = dev_get_drvdata(dev); + int delay; + + if (!gpio_dev) return -EINVAL; + + if (kstrtoint(buf, 10, &delay)) return -EINVAL; + + gpio_dev->toggle_delay = delay; + return size; +} + + +DEVICE_ATTR_RW( gpio_toggle_delay ); +DEVICE_ATTR_RW( gpio_toggle_state ); + +// Attributter til SysFS: +static struct attribute *gpio_attrs[] = { + &dev_attr_gpio_toggle_state.attr, + &dev_attr_gpio_toggle_delay.attr, + NULL, +}; +ATTRIBUTE_GROUPS( gpio ); + + +// Init function (register platform driver) +static int __init mygpio_init(void) { + int err; + + pr_info("Initializing GPIO Platform Driver\n"); + + // Alloker chrdev region + err = alloc_chrdev_region(&devno, 0, NUM_GPIOS, "plat_drv"); + if (err < 0) { + pr_err("Failed to allocate char dev region\n"); + return err; + } + + // Opret class og cdev + gpio_class = class_create(THIS_MODULE, "plat_drv_class"); + if (IS_ERR(gpio_class)) { + unregister_chrdev_region(devno, NUM_GPIOS); + return PTR_ERR(gpio_class); + } + gpio_class->dev_groups = gpio_groups; + + cdev_init(&gpio_cdev, &gpio_fops); + gpio_cdev.owner = THIS_MODULE; + err = cdev_add(&gpio_cdev, devno, NUM_GPIOS); + if (err) { + class_destroy(gpio_class); + unregister_chrdev_region(devno, NUM_GPIOS); + return err; + } + + return platform_driver_register(&gpio_platform_driver); +} + +// Exit function (unregister platform driver) +static void __exit mygpio_exit(void) { + platform_driver_unregister(&gpio_platform_driver); + cdev_del(&gpio_cdev); + class_destroy(gpio_class); + unregister_chrdev_region(devno, NUM_GPIOS); +} + +// File operations functions +int mygpio_open(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Opening GPIO Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +int mygpio_release(struct inode *inode, struct file *filep) { + int major, minor; + major = MAJOR(inode->i_rdev); + minor = MINOR(inode->i_rdev); + pr_info("Closing/Releasing GPIO Device [major], [minor]: %i, %i\n", major, minor); + return 0; +} + +ssize_t mygpio_write(struct file *filep, const char __user *ubuf, size_t count, loff_t *f_pos) { + int len, value; + char kbuf[12]; + int minor = MINOR(filep->f_inode->i_rdev); + struct gpio_dev *dev; + + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; + if (dev->dir != 1) { + pr_err("GPIO %d is not configured as output\n", dev->no); + return -EINVAL; + } + + len = count < sizeof(kbuf) ? count : sizeof(kbuf) - 1; + if (copy_from_user(kbuf, ubuf, len)) { + return -EFAULT; + } + + kbuf[len] = '\0'; + if (kstrtoint(kbuf, 10, &value)) { + pr_err("Error converting string to int\n"); + return -EINVAL; + } + + gpio_set_value(dev->no, value); + pr_info("Wrote %i to GPIO %d\n", value, dev->no); + *f_pos += len; + return len; +} +ssize_t mygpio_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { + int value; + char kbuf[12]; + int len; + int minor = MINOR(filep->f_inode->i_rdev); + struct gpio_dev *dev; + + if (minor >= gpios_len) { + pr_err("Invalid minor number\n"); + return -ENODEV; + } + + dev = &gpio_devs[minor]; + value = gpio_get_value(dev->no); + len = snprintf(kbuf, sizeof(kbuf), "%d\n", value); + + if (*f_pos >= len) // Handle EOF + return 0; + + if (copy_to_user(buf, kbuf, len)) { + return -EFAULT; + } + + *f_pos += len; // Advance position + return len; +} + +// File operations +struct file_operations gpio_fops = { + .owner = THIS_MODULE, + .open = mygpio_open, + .release = mygpio_release, + .write = mygpio_write, + .read = mygpio_read, +}; + +module_init(mygpio_init); +module_exit(mygpio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dit Navn"); +MODULE_DESCRIPTION("GPIO Platform Driver"); diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/catecho_x2.png b/Group-3-main/Group-3-main/Lektion8_Afl3/catecho_x2.png new file mode 100644 index 0000000000000000000000000000000000000000..6582dbe109ea1e30d1fca380e9f366608cdfaa73 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion8_Afl3/catecho_x2.png differ diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/dmesg_X2.png b/Group-3-main/Group-3-main/Lektion8_Afl3/dmesg_X2.png new file mode 100644 index 0000000000000000000000000000000000000000..e69d4f5bacf03b1b781c532a20c4e8b3953ba2c8 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion8_Afl3/dmesg_X2.png differ diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/gpio_state_X1.png b/Group-3-main/Group-3-main/Lektion8_Afl3/gpio_state_X1.png new file mode 100644 index 0000000000000000000000000000000000000000..e3adb9ed44340861d2a5ec89de4582a9510ade3d Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion8_Afl3/gpio_state_X1.png differ diff --git a/Group-3-main/Group-3-main/Lektion8_Afl3/skrive_X1.png b/Group-3-main/Group-3-main/Lektion8_Afl3/skrive_X1.png new file mode 100644 index 0000000000000000000000000000000000000000..ab917f021f2c2bf0f5b446bf0c5ed527ac811481 Binary files /dev/null and b/Group-3-main/Group-3-main/Lektion8_Afl3/skrive_X1.png differ diff --git a/Group-3-main/Group-3-main/README.md b/Group-3-main/Group-3-main/README.md new file mode 100644 index 0000000000000000000000000000000000000000..52c431775b3ef354fcd702d1054ccd44a8860e28 --- /dev/null +++ b/Group-3-main/Group-3-main/README.md @@ -0,0 +1 @@ +# Group-3