Differences

This shows you the differences between two versions of the page.

Link to this comparison view

si:laboratoare:09 [2023/12/03 18:17]
florin.stancu
si:laboratoare:09 [2024/10/27 09:23] (current)
florin.stancu created
Line 1: Line 1:
-====== Laboratorul 09. Module de Kernel & Drivere de dispozitive ​======+====== Laboratorul 09. RootFS Bootstrapping ​======
  
 +===== Crearea unei distribuții Linux =====
  
-Un sistem embedded poate funcționa doar cu perifericele pe care le-am folosit ​deja (rețea, card SD, USB), însă va fi strict limitat la hardware-ul pentru care exista deja suportCe se întâmplă atunci când dorim să folosim un hardware nou sau diferit de cel pentru care există suport?+Până acum am folosit ​Root Filesystem de-a gata pre-instalat.
  
-===== Memory Management Unit și importanța sa în dezvoltarea ​de module =====+La acest laborator, dorim să învățăm procesul de generare a unui sistem de bază și modalitățile de personalizare a acestuia, proces numit **rootfs bootstrapping**.
  
-==== Sisteme embedded fără MMU ====+Avem 2 variante: 
 +  * plecarea de la o distribuție existentă și alegerea pachetelor pe care să le includem + configurații specifice;​ 
 +  * compilarea (manuală sau automată) a tuturor componentelor unui astfel de sistem;
  
-În sistemele care nu au MMU scrierea unui //modul// este directă: fie că modulul este foarte strâns cuplat cu întreg sistemulfie că este sub forma unei biblioteci ​de funcțiitotul se află în același ​spațiu de memorie cu programul care rulează ​(sau sistemul ​de operare, în cazurile mai complexe). Dezavantajele principale al acestui tip de sistem sunt lipsa de securitate ​și de stabilitate. Orice vulnerabilitate sau bug afectează și periclitează întreg sistemulputând duce chiar la compromiterea acestuia+Prima soluție, deși mult mai facilă (atât rapidă, cât și ușor de înțeles)nu ne permite o optimizare a spațiului utilizat ​(sisteme bazate pe Debian ocupă ​de ordinul ''​~1GB''​) și nici fine-tuningul ulterior pentru îmbunătățirea performanțelor în cazul rulării pe anumite dispozitive (e.g.ARMv8 cu extensii NEON pentru SIMD).
  
-Mai mult, două programe (de exemplu într-un sistem de operare cu multi-tasking cooperativ) pot concura pentru aceeași resursă, chiar dacă nu rulează concomitent. Dacă luăm exemplul aplicațiilor ​de la PMdacă o funcție configura seriala cu un anumit baud rateapoi ceda controlul altei funcții ​care seta baud rate la altă valoareavaloarea finală va fi a douaiar codul asociat cu prima funcție nu va mai rula corect.+Pe de altă parte, dacă am dori să facem asta de la zero, ar trebui să plecăm de la un toolchain (pe carede cele mai multe ori, trebuie să-l compilăm tot noi)pe care îl vom folosi să compilăm din cod sursă toate componentele necesare într-un sistem Linux: biblioteci standard (LibC)sistem de init (SystemV / SystemD / OpenRC etc.), suita de programe de bază Unix (e.g.''​mount'',​ ''​cat'',​ ''​ls'',​ ''​cp'',​ ''​sh'',​ ''​awk''​ și multe altele!), device manager (''​udev''​) precum și multe alte servicii (e.g., de networking) și aplicații necesare scopului nostru final (e.g., Python, X11 + QT Toolkit pentru interfețe grafice etc.).
  
-Deși pare greu de ajuns la o asemenea situațieîn realitate este foarte ușorunul din modurile de a implementa sisteme multitasking colaborative este folosind corutinefuncții cu mai multe puncte de intrare ​care cedează controlulÎn astfel de sisteme, o corutină joacă rolul unui proces. Astfel, având mai mult de un dezvoltator putem fi siguri cămai devreme sau mai târziu, va apărea o situație ca cea menționată în scenariul anterior.+Din fericireexistă suite software ([[https://​www.yoctoproject.org/​|Yocto Linux]][[https://​buildroot.org/​|Buildroot]]) ​care pot automatiza repetabil acest procesÎnsă un dezavantaj tot rămâne: timpul de compilare este de ordinul oreloriar spațiul pe disk necesar între ''​20GB -- 50GB''​.
  
-==== Sistem embedded cu MMU ==== 
- 
-Sistemele cu MMU rezolvă această problemă. Accesul la hardware poate fi făcut doar de aplicațiile ce rulează într-un mod special, numit și //​privilegiat//​. Astfel, codul care interfațează hardware-ul va fi pus într-o zonă protejată de memorie, accesibilă doar din modul privilegiat. Un layer adițional va face comunicația între acest cod și codul neprivilegiat care îl folosește. Astfel, modulul va fi protejat de vulnerabilitățile programelor care îl utilizează,​ nu va avea probleme în urma crash-urilor lor și va putea face o arbitrare a accesului la resursa hardware pe care o gestionează. 
- 
-===== Arhitectura kernelului ===== 
- 
-Din punct de vedere al modului în care este organizată,​ arhitectura kernelului poate fi de două tipuri: **monolitică** și **microkernel**,​ însă pot exista și implementări hibride. 
- 
-==== Monolitic ==== 
- 
-Arhitectura **monolitică** a unui kernel însemnă că toate serviciile oferite de acesta rulează într-un singur proces, în același spațiu de adrese (**kernel-space**) și cu aceleași privilegii. Toate facilitățile pe care le pune la dispoziție sunt înglobate într-un singur binar. 
- 
-==== Microkernel ==== 
- 
-Spre deosebire de cel monolitic, **microkernel-ul** implementează un pachet minimal de servicii și mecanisme necesare pentru implementarea unui sistem de operare precum: alocarea memoriei, planificarea proceselor, mecanismele de comunicare între procese (IPC). Restul serviciilor (networking,​ filesystem, etc.) rulează ca daemoni în user-space (modul neprivilegiat). ​ 
- 
- 
-După cum se poate observa, un mare dezavantaj al kernel-ului de tip monolitic îl reprezintă lipsa de modularitate și de extensibilitate,​ lucruri care au fost adăugate ulterior. Astfel, actual, kernel-urile monolitice au posibilitatea de a fi **extinse la runtime** cu noi funcționalități,​ prin inserarea de **module**. Aceste module vor rula în spațiu de adrese al kernelului. 
- 
-===== Linux Kernel ===== 
- 
-În general, kernel-ul Linux rulează pe arhitecturi cu suport de MMU, implementarea tuturor funcționalităților bazându-se în mare măsură pe existența acestei componente hardware. Există însă un fork al kernel-ului existent de Linux pentru arhitecuri fără MMU, numit //​uClinux//​. Mai multe detalii despre acest subiect găsiți [[http://​www.uclinux.org/​| aici]]. 
- 
-De altfel, din punct de vedere al arhitecurii kernel-ului,​ Linux este **monolitic** cu posibilitatea de extindere la runtime prin **module**. 
- 
-==== Programarea modulelor de kernel ==== 
- 
-Având în vedere că modulele rulează în kernel-space,​ în mod privilegiat,​ codul **trebuie** scris cu mare grijă. Astfel, programarea modulelor de kernel este guvernată de anumite reguli: 
- 
-  * Kernelul nu este link-at cu biblioteca standard C, nu se pot folosi niciunele dintre apelurile de bibliotecă cunoscute! 
-  * Se pot folosi însă funcțiile, macro-urile și variabilele exportate de kernel. Acestea se găsesc în header-ele kernel-ului. 
-  * Nu se accesează direct zona de memorie care poate fi accesată și din modul neprivilegiat (a.k.a. user-space). Tot ce provine din user-space trebuie privit cu suspiciune. Există macro-uri speciale pentru transferul dintre cele două zone de memorie. 
-  * Accesele invalide la memorie trebuiesc evitate, deoarece sunt mult mai grave decăt cele din user-space: pe Windows se generează BSOD (Blue Screen of Death), iar pe Linux se dă mesajul //kernel oops// (sau în cazuri mai grave //kernel panic//), sistemul devenind instabil (de cele mai multe ori fiind necesar un restart). 
- 
-=== Modulul Hello world === 
- 
-Orice modul de kernel are nevoie de o funcție de inițializare și o funcție de cleanup. Prima se apelează atunci când modulul este încărcat, a doua este apelată când modulul este descărcat. De asemenea, modulele au nevoie de autor, licență și descriere, utilizarea acestor macro-uri fiind obligatorie. 
- 
- 
-<file c hello.c> 
-#include <​linux/​init.h> ​  /* for __init and __exit */ 
-#include <​linux/​module.h>​ /* for module_init and module_exit */ 
-#include <​linux/​printk.h>​ /* for printk call */ 
- 
-MODULE_AUTHOR("​SI"​);​ 
-MODULE_LICENSE("​GPL"​);​ 
-MODULE_DESCRIPTION("​Hello world module"​);​ 
- 
-static int __init my_init(void) 
-{ 
-        printk(KERN_DEBUG "​Hello,​ world!\n"​);​ 
- 
-        return 0; 
-} 
- 
-static void __exit my_exit(void) 
-{ 
-        printk(KERN_DEBUG "​Goodbye,​ world!\n"​);​ 
-} 
- 
-module_init(my_init);​ 
-module_exit(my_exit);​ 
-</​file>​ 
- 
-Modulul Hello World conține: 
-  - Header-ele necesare 
-  - Definirea autorului, licenței și descrierii 
-  - Funcția my_init 
-    * Specificatorul //​%%__init%%//​ este un macro pentru //​%%__section(.init.text) __cold notrace%%// care specifică gcc-ului în ce segment al executabilului să pună această funcție. În cazul modulelor care se încarcă odată cu kernelul (builtin), funcția init se va apela o singură dată, după care va rămâne în memoria sistemului (kernel-space) până la închiderea sistemului. .init.text este un segment care este eliberat după inițializarea kernel-ului (se poate observa în timp ce pornește sistemul mesajul %%Freeing unused kernel memory: 108k freed%%). 
-    * Funcția printk este echivalentul în kernel al funcției printf. Ieșirea este însă direcționată către un fișier log, %%/​var/​log/​messages%%. Diferența în folosire este dată și de specificarea priorității cu macro-ul KERN_ALERT, care de fapt se traduce în șirul de caractere <1>. Sistemul va putea fi configurat astfel să ignore anumite mesaje. 
-  - Funcția my_exit 
-    *  Specificatorul //​%%__exit%%//​ este un macro pentru //​%%__section(.exit.text) __exitused __cold notrace%%//​. În cazul modulelor care se încarcă odată cu kernelul (builtin), funcția exit **nu** se va apela niciodată, deci va fi ignorată de compilator. 
-  - Înregistrarea celor două funcții ca init și exit pentru modulul respectiv. 
- 
-=== Module de kernel In-Tree === 
- 
-Am văzut in laboratorul 9 cum putem configura (personaliza) build-ul de kernel, prin selectarea componentelor care vrem să fie parte din imaginea de kernel (build-in) sau compilate ca module de kernel. Aceste module de kernel (fie ele drivere video, module de criptografie sau suportul de sunet) se numesc "​in-tree",​ deoarece ele fac parte din codul sursă care vine odata cu tot kernel-ul de Linux. Fișierul de configurație al kernel-ului (.config) păstrează aceaste optiuni: 
- 
-<file bash .config> 
-# 
-# Serial drivers 
-# 
-CONFIG_SERIAL_EARLYCON=y 
-CONFIG_SERIAL_8250=m ​  # Compiled as kernel module 
-CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y 
-CONFIG_SERIAL_8250_16550A_VARIANTS=y 
-</​file>​ 
- 
-Avantajul în a avea aceste componente compilate ca module de kernel este că ele pot fi inserate in kernel la cerere (on-demand),​ în funcție de ce device-uri sunt prezente in sistem și ce nevoi are utilizatorul. Spre exemplu, folosirea NAT-ului în rețea, pentru a folosi sistemul ca un router de Layer 3, va moment in care modulul de kernel nf_nat.ko este incărcat). 
- 
-=== Module de kernel Out-of-Tree === 
- 
-Dezavantajul modulelor de kernel In-Tree este că vin împreună cu acea versiune de kernel pentru care au fost compilate (deoarece au codul in același repository). Dacă spre exemplu utilizatorul dorește o versiune mai nouă a unui driver de placă wireless, el trebuie sa aștepte un nou update al distribuției de Linux (ex. Ubuntu), sau trebuie sa recompileze tot kernel-ul. 
- 
-Majoritatea producătorilor de componente hardware (ex. Nvidia, Intel, Realtek) fac disponibile online atât codul sursă al driverele cât și firmware-ul pe care driver-ul îl încarcă pentru device-ul respectiv. Pentru a face codul modulelor de kernel compatibil cu cat mai multe versiuni de kernel, se apelează la construcții precum: 
- 
-<file c some_kernel_module.c>​ 
-#if ( LINUX_VERSION_CODE < KERNEL_VERSION(3,​1,​0) ) 
-    // Use specific structures before kernel 3.1 
-#else 
-    // Use the new structures, after kernel 3.1 
-#endif 
-</​file>​ 
- 
-În exemplul de mai sus, dacă compilăm modulul de kernel pentru un kernel mai nou (ex. 5.1), atunci codul va folosi al doilea branch de "​if"​. 
- 
-Pentru a compila un modul de kernel Out-of-Tree,​ utilizatorul are nevoie doar de header-ele de kernel cu care a fost compilat kernel-ul care rulează. Majoritatea distribuțiilor de Linux au aceste headere disponibile:​ 
- 
-<​code>​sudo apt install linux-headers-`uname -r`</​code>​ 
- 
-Comanda "uname -r" returnează versiune actuală de kernel care rulează (ex. 5.11.0). 
- 
-Raspberry PI pune la dispozitia utilizatorilor un alt pachet ''​raspberrypi-kernel-headers''​. 
-==== Utilitare pentru lucrul cu module ==== 
- 
-''​insmod''​ inserează module în kernel: 
- 
-<​code>​~#​insmod hello_mod.ko</​code>​ 
- 
-Modprobe face același lucru, dar cu modulele puse deja în sistemul de fișiere în locul corespunzător (/​lib/​modules/​`uname -r`/... - unde //uname -r// este versiunea nucleului). În plus, //​modprobe//​ va citi și lista de dependențe ale modulului de încarcat și o va rezolva (va insera și alte module, dacă e nevoie). 
- 
-:!: Observați că doar insmod necesită calea modulului (cu tot cu extensie) 
- 
-<​code>​~#​modprobe hello_mod</​code>​ 
- 
-Pentru descărcare,​ se folosește fie: 
- 
-<​code>​~#​modprobe -r hello_mod</​code>​ 
- 
-fie: 
- 
-<​code>​~#​rmmod hello_mod</​code>​ 
- 
-Afișarea modulelor încărcate se face cu lsmod 
- 
-<​code>​~#​lsmod</​code>​ 
- 
-Afișarea mesajelor date cu printk din modul se găsesc în /​var/​log/​messages,​ se afișează cu: 
- 
-<​code>​~#​dmesg | tail </​code>​ 
- 
-sau cu 
- 
-<​code>​~#​tail -f /​var/​log/​messages </​code>​ 
- 
-===== GPIO ===== 
- 
-Placile Raspberry PI dispun de mai multi pini, numiti pini GPIO (General Purpose I/O) pentru a comunica cu dispozitive exterioare. Acestia pot fi folositi direct din nucleul de Linux, folosind un modul de nucleu, sau folosind o biblioteca user-level, in Python, C, sau alte limbaje. 
-Fiecare pin GPIO are un anumit numar fix, dupa cum indica imaginea de mai jos 
- 
-{{:​si:​laboratoare:​gpio-pinout-diagram-2.png?​200|}}. 
- 
-Unii pini pot avea functii specializate,​ precum PWM sau I2C. 
  
 ===== Exerciții ===== ===== Exerciții =====
  
-0. **Pregătirea** imaginii de kernel si a header-elor de kernel+Pentru acest laborator, vom încerca ambele abordări!
  
-În cadrul laboratorul ​de astăzi, vom lucra cu module de kernel Out-of-Tree, ​pe un Raspberry PI fizic, ideal (se poate si in QEMU). Pentru aceasta avem nevoie de header-ele de kernel. Pentru asta, putem instala pachetul `raspberrypi-kernel-headers`+==== 1. Crearea ​de distribuții bazate ​pe Debian ​(debootstrap====
  
-<note important>​ +Pentru a genera distribuții personalizate bazate pe Debian (și derivatee.g., Ubuntu)se poate folosi utilitarul ​''​debootstrap''​.
-Pentru a va conecta la Raspberry PI-ul din laboratortrebuie sa va conectati la reteaua WiFi ''​LabSI-PR703'',​ cu parola ''​Il0v3-rpi''​Utilizatorul de pe RPi este ''​root''​iar parola este ''​Il0v3-rpi''​.  +
-</​note>​ +
-  +
-1. Compilați modulul de kernel **Hello World** cu header-ele de kernel corespunzătoare.+
  
-În directorul unde ați copiat hello.c-ul de mai suscopiați și acest Makefile:+Acesta descarcă automat din repository-uri pachetele ''​.dpkg'',​ pe care le instalează într-un prefix (director) precizat ​de utilizatore.g.: 
 +<code shell> 
 +# Sfat: încă nu rulați (veți aștepta mult și vă va da o eroare la final). Vedeți mai jos cauza + soluția! 
 +$ sudo debootstrap --verbose --arch="​arm64"​ --include="​netbase,​vim,​bash,​curl"​ \ 
 +        --variant=minbase "​bookworm"​ "/​root/​mydebian/"​ "http://​ftp.de.debian.org/​debian"​ 
 +</​code>​
  
-<file bash Makefile>​ +Puteți alege [[https://www.debian.org/​mirror/​list|alt mirror din lista oficială de aici]] (**recomandat!**). Alegeți la întâmplare ​țară din Europa (să nu fiți toți pe același server că s-ar putea să facă throttling!).
-obj-m := hello.o+
  
-all: +<note tip> 
-    make -C /​lib/​modules/​$(shell uname -r)/build M=$(PWD) modules +Aproape toate distribuțiile de Linux au câte un utilitar ​(de multe ori, chiar package managerul distribuțieicare permite bootstrappingul RootFS-urilor din pachete ​(tot a sunt generate ​și imaginile ISO live!)
- +Spre exemplu, Arch Linux are ''​pacstrap'',​ Alpine Linux are ''​apk.static''​ and so on...
-clean: +
-    make -C /​lib/​modules/​$(shell uname -r)/build M=$(PWD) clean +
-</​file>​ +
- +
-Pentru ​compila modulul de kernel, invocați comanda "​make":​ +
- +
-<​code>​make CROSS_COMPILE=... ARCH=... </​code>​ +
- +
-<​note>​Revizitați [[https://​ocw.cs.pub.ro/​courses/​si/​laboratoare/​09|laboratorul 09]] pentru detalii.</​note>​ +
- +
-Copiați ​și incărcați modulul hello.ko pe mașina de Qemu/​RPi ​și verificați că mesajele sunt afișate+
- +
-<note important>​ +
-[[https://​elixir.bootlin.com/​linux/​latest/​source| Elixir Cross Reference]] este un site care indexează sursele de LinuxÎl puteți folosi pentru a vedea definițiile funcțiilor și macro-urilor pe care le veți folosi.+
 </​note>​ </​note>​
  
-2. Afisati mesajul de "Hello World" cu mai multe niveluri de afisareinfo, warning, error, critical. +De menționat ar fi că instalarea unui pachet are loc în etape
-Incercati si cu nivelul de debug. Puteti urmari discutia de [[https://stackoverflow.com/questions/​28936199/​why-is-pr-debug-of-the-linux-kernel-not-giving-any-output | aici]].+  * decompresia arhivei (extragerea fișierelor din pachet în directorul rădăcină destinație);​ 
 +  * rularea unor scripturi post-install pentru crearea configurațiilor implicite ​upgrade fișiere ​integrarea cu alte aplicații ​instalarea serviciilor etc.;
  
-<note tip>​Puteti utiliza 2 functii ​pentru a afisa mesaje de kernelprintk si pr_<​nivel>​.</​note>​+Acest aspect devine important când încercăm să generăm un rootfs ​pentru ​o arhitectură străină celei gazdă (cross-bootstrapping)! 
 +Pentru ​realiza acest lucru, va trebui să distingem cele două etapedecompresia va putea avea loc direct pe host (avem utilitarele necesare instalate cu scriptul de bootstrapping),​ pe când etapa de execuție a programelor va trebui să fie virtualizată cumva (chroot + ''​qemu-user-static''​ FTW).
  
-<note important>​ +Astfel, pentru a realiza ''​debootstrap'' ​de pe o arhitectură Intel/AMD ''​x86_64''​ pentru ​țintă AArch64 ​(ARM 64-bit): 
-In continuarepentru nota maxima, puteti alege una dintre cele 2 cai: software, care aprofundeaza modulele de nucleu, sau hardware, care face cehstii sa lumineze / miste. Puteti sa faceti toate exercitiile si sa primiti puncte bonus. +<​code ​shell
-</​note>​ +a fost adăugat flag-ul `--foreign` în plus la comanda de debootstrap normală: 
- +$ sudo debootstrap --foreign --verbose --arch="arm64" ... /root/​mydebian ​... # restul ​de argumente normale 
-==== Calea Software ==== +# apoi copiem emulatorul în noua rădăcină ​(va fi rulat de binfmt_misc ca în Lab 04
- +$ sudo cp -af "$(which qemu-aarch64-static)" /root/mydebian/usr/bin
-3. Folosiți modulul de kernel de {{.:​rename_process.c.txt | aici}} ​pentru a **redenumi procesul "​Init"​** +# Acum va trebui ​să rulăm stagiul 2 al debootstrapînsă într-un container / chroot! 
- +# Folosim systemd-nspawn ​pentru a rula binarul ​de second-stage instalat pe rootfs ​de la primul: 
-<note important>​În kernel, un proces este reprezentat ​de structura [[https://​elixir.bootlin.com/​linux/​latest/​source/​include/​linux/​sched.h#​L723| struct task_struct]]</​note>​ +$ sudo systemd-nspawn ​--as-pid2 --resolv-conf=copy-host -D /root/mydebian \ 
-<note tip>​Folosiți structura //%%struct task_struct%%//,​ cu accent ​pe cămpurile ​//pid//, și //​parent//</​note>​ +    /debootstrap/debootstrap ​--second-stage --verbose
-<note tip>​Există ​variabilă, **current**,​ de tip //%%struct task_struct *%%//, care reprezintă procesul curent, și care este exportată de kernel tuturor modulelor.</​note>​ +
- +
-Inainte de inserarea modulului de kernel, citiți numele procesului Init (PID 1cu următoarea comanda: +
- +
-<code>cat /​proc/​1/​status</​code+
- +
-<​note>​Nu uitați sa faceți modificarile necesare in fișierul Makefile de la exercitiul anterior</​note>​ +
- +
-Verificați că numele procesului Init a fost modificat. +
- +
-4. Dezactivați intreruperea de timp cu ajutorul următorului {{.:​disable_irqs.c.txt | modul de kernel}} +
- +
-Pentru a afla numărul alocat intreruperii de timp, folosiți comanda: +
- +
-<​code>​cat /​proc/​interrupts</​code>​ +
- +
-Odată ce ați aflat numărul înteruperii,​ completați numărul interuperii in codul C si recompilați. După inserarea modulului de kernel rulați comanda: +
- +
-<​code>​sleep 1</​code>​ +
- +
-Comanda "sleep 1" ar trebui să returneze după o secundă, dar va rămâne blocată deoarece timer-ul HW (de pe placă de bază) sau SW (din Qemu) nu mai poate genera interuperi. După descărcarea modulului de kernel, ​comanda ​sleep va relua execuția. +
- +
-Listați sursa de ceas pe care o emulează Qemu cu ajutorul comenzii+
- +
-<​code>​cat /​sys/​devices/​system/​clocksource/​clocksource0</​code>​ +
- +
-==== Calea  Hardware ==== +
- +
-<note important>​ +
-La laborator aveti disponibile urmatoarele ​"jucarii"+
-  * 5 Raspberry Pi-uri 4 +
-  * 2 drivere de motoare L298N, care pot comanda cate 2 motoare, prin pinii ENA, IN1, IN2, respectiv ENB, IN3 si IN4ENA si ENB pot fi folositi ca pini PWM, pentru a controla vitezaExemplu pentru motorul conectat la ENA, IN1 si IN2: {{:​si:​laboratoare:​8ebfe7a64c9108a7b032c8a985412ffd.png?​200|}} +
-  * LED-uri normale - au nevoie de o rezistenta de 150 de ohmi +
-  * fire +
-  * breadboard-uri +
-  * baterii de 9V pentru driverele de motoare +
-Puteti lucra cu oricare din piesele de mai sus, daca sunt disponibile,​ pentru exercitiile de mai jos. +
- +
-</note> +
- +
-5Scrieti un modul de nucleu care aprinde un LED sau care porneste un motor, in functie de ce este disponibil fizic in laboratorTestati folosind materialele din laboratorModulul trebuie sa execute cel putin urmatorii pasi: +
-  * Validarea portului GPIO, folosind ''​bool gpio_is_valid(int gpio_number);''​ +
-  * obtinerea portului GPIO de la nucleu, folosind ''​int gpio_request(unsigned gpio, const char *label)''​. ''​label''​ poate fi obtinut din ''/​sys/​kernel/​debug/​gpio''​. +
-  * exportarea portului GPIO, folosind ''​int gpio_export(unsigned int gpio, bool direction_may_change)'',​ cu variabila ''​direction_may_change''​ pe ''​false''​. In urma acestui pas, portul GPIO va fi vizibil in ''/​sys/​class/​gpio/''​. +
-  * setarea directiei portului GPIO, folosind ''​int ​ gpio_direction_output(unsigned gpio, int value)''​ sau ''​int ​ gpio_direction_input(unsigned gpio, int value)''​. +
-  * setarea valorii portului GPIO, folosind ''​gpio_set_value(unsigned int gpio, int value)''​. ''​value''​ poate fi 0 (LOW) sau 1 (HIGH). +
-  * eliberarea portului GPIO, folosind ''​void gpio_unexport(unsigned int gpio)''​ si ''​void gpio_free(unsigned int gpio)''​. +
- +
-6. Modificati modulul de nucleu pentru a comuta starea LED-ului sau directia de miscare a motorului, la un anumit interval de timp. +
- +
-<note tip>​pentru a astepta X milisecunde,​ puteti folosi functia ''​msleep''​ din nucleu.</​note>​ +
- +
-<note warning> Nu rulati bucle de tip "while(1)" ​in module de nucleu. Pentru a porni o actiune care se desfasoara pana cand este eliminat modulul, trebuie folosite thread-uri de nucleu (kthread). Un exemplu de utilizare e [[https://github.com/muratdemirtas/Linux-Kernel-Examples/blob/​master/​kernel-threads.c|aici]].</​note>​ +
- +
-7. Faceti acelasi lucru ca la exercitiul anterior, folosind biblioteca de Python RPi.GPIO. +
-<note tip> Urmatoarele functii sunt utile: +
-  * RPi.GPIO.setmode +
-  * RPi.GPIO.setup +
-  * RPi.GPIO.output +
-</​note>​ +
- +
-8. Folosind ''​RPi.GPIO.PWM'',​ schimbati viteza mortorului sau intensitatea cu care lumineaza LED-ul. +
- +
-<​hidden>​===== Device drivers ===== +
- +
-Un //device driver// reprezintă o bucată de cod al cărui scop este de a controla o anumită componentă hardware. Avantajele aduse de către un driver provin în principal din oferirea unei interfețe mai simple de comunicare cu hardware-ul,​ scutind aplicațiile de complexitatea comunicării directe cu acesta. Se pot defini diferite operații de nivel înalt necesare aplicațiilor,​ care apoi sunt implementate de către driver. Centralizarea acestor operații în driver permite ca ele să fie implementate o singură dată pentru toate aplicațiile,​ reducând astfel dimensiunea totală a codului și implicit numărul de bug-uri. Interfața de nivel înalt poate fi de asemenea extinsă și la nivelul mai multor componente hardware diferitefăcând posibilă astfel accesarea acestora printr-o interfață comună, oferind astfel o decuplare între aplicații și componentele hardware. +
- +
-Pe lângă simplificarea comunicării cu dispozitivele hardware un driver poate oferi și o arbitrare a accesului la resursele dispozitivul,​ un lucru util atunci când mai multe aplicații concurente doresc accesul la același dispozitiv. Fiind plasat într-un punct central între aplicații și hardware, un driver este cel mai în măsură să medieze accesul aplicațiilor la dispozitiv, ​pentru a preveni conflictele. +
- +
-În sistemele care oferă separare între codul privilegiat și cel neprivilegiat,​ un device driver va rula în general în modul privilegiat. Acest lucru este necesar deoarece ​de obicei comunicarea directă cu dispozitivele hardware necesită execuția ​de instrucțiuni privilegiate sau accesarea registrelor hardware-ului care, în general, sunt mapate ​la adrese restricționate pentru codul neprivilegiat. +
- +
-În kernel-ul Linux implementarea unui device driver se face sub forma unui //modul//. Astfel, driverul va avea accesul necesar la modul privilegiat și, ca un bonus, el va putea fi încărcat în kernel la cerere, ca orice modul, atunci când serviciile oferite sunt necesare (ex: un dispozitiv nou a fost conectat la sistem). Bineînțeles,​ tot ca orice modul din Linux, driverul poate fi integrat și în imaginea kernel-ului, dacă se cunoaște că serviciile oferite sunt necesare oricând, sau atunci când sistemul nu poate porni fără componenta controlată de driver. +
- +
-Din punctul de vedere al Linux driverele se împart în mai multe categorii, în funcție de operațiile care pot fi efectuate de dispozitivul hardware: drivere pentru dispozitive de tip caracter (//​character devices//), drivere pentru dispozitive de tip bloc (//block devices//), drivere dispozitive de rețea (//network devices//) etc. De exemplu, deoarece accesarea directă a unui hard-disk se face la nivel de sectoare, vom folosi un driver pentru un dispozitiv de tip bloc (//block driver//) pentru a expune funcțiile hard-disk-ului pentru restul sistemului. Interfața oferită de acest tip de driver se mapează mai bine pe funcțiile hard-disk-ului decât interfața oferită de un driver pentru un dispozitiv de tip caracter (//​character driver//). Un character driver oferă o interfață generală, pentru accesul nestructurat la hardware, folositoare atunci când dispozitivul nu se încadrează într-una din celelalte categorii (ex: port serial, tastatură, mouse, dispozitive de sunet, controllere I2C etc.) +
- +
-===== Character driver ===== +
- +
-Implementarea unui character driver pentru Linux presupune scrierea unui modul care urmărește aceeași [[https://ocw.cs.pub.ro/​courses/​si/​laboratoare/​09#​modulul_hello_world| structură]] folosită și în laboratorul 9. Pentru a putea folosi însă interfața de comunicare specifică unui character driver este necesar ca modulul să se înregistreze ca un character driver și să implementeze anumite operații. +
- +
-==== Înregistrare ==== +
- +
-Acest lucru se face de obicei în funcția de inițializare a modulului printr-un apel la funcția ''​[[http:​//lxr.free-electrons.com/​source/​include/​linux/​fs.h?​v=3.12#​L2141| register_chrdev]]'':​ +
- +
-<code c> +
-int register_chrdev(unsigned int major, const char *name, +
-                    const struct file_operations *fops);+
 </​code>​ </​code>​
  
-Aceasta primește ​ca argumente:​ +<​note>​Este posibil ​ca descărcarea să dureze ceva, în funcție ​de viteza ​de conectare la Internet + mirror-ul ales.</note>
-  * ''​major''​ - un număr care identifică tipul de dispozitiv; se poate trimite ''​0''​ pentru alocarea dinamică a unui //​major// ​de către kernel +
-  * ''​name'' ​un șir de caractere prin care tipul de dispozitiv poate fi mai ușor identificat +
-  * ''​fops''​ - o structură cu pointeri către funcțiile care implementează interfața driver-ului +
-și returnează 0, dacă //majorul// a fost specificat static, sau valoarea alocată pentru //major//, dacă acesta a fost alocat dinamic. În caz de eroare funcția întoarce un număr negativ care identifică tipul erorii.+
  
-==== Deînregistrare ==== +Tehnica cu systemd-nspawn + qemu-user-static (și binfmt_misc) este utilă pentru a intra într-un chroot pe rootfs-ul nostru pentru ​a-l personaliza (putem chiar și instala programe noi sau configura servicii etc.)Însă va trebui configurat programul de gestionat de pachetele, ''​apt''​: de adăugat mirror-uri la repo-urile ​de bază (și cele extinse, de preferat) ​în ''/​etc/apt/sources.list'' ​-- [[https://wiki.debian.org/SourcesList#​Example_sources.list|găsiți exemplu aici]].
- +
-În mod asemănător, deînregistrarea unui character driver se face în funcția de clean-up modulului cu funcția ''​[[http://​lxr.free-electrons.com/​source/​include/​linux/​fs.h?​v=3.12#​L2147| unregister_chrdev]]'':​ +
- +
-<code c> +
-void unregister_chrdev(unsigned int majorconst char *name); +
-</​code>​ +
- +
-Argumentele acestei funcții sunt: +
-  * ''​major'' ​- majorul asociat cu driverul care este înlăturat +
-  * ''​name'' ​șirul ​de caractere înregistrat pentru acest driver +
-iar apelarea ei este necesară pentru ca modulul să poată fi descărcat. +
- +
-==== Accesare ==== +
- +
-Într-un sistem Linux orice dispozitiv este reprezentat în general printr-un fișier disponibil ​în directorul ​''/​dev''​. Operațiile obișnuite asupra fișierelor: ​//open//, //read//, //write//, //close// făcute pe fișierele speciale din ''/​dev''​ sunt mapate în operații asupra dispozitivelorCrearea fișierului asociat unui dispozitiv nu se face însă automat. Acesta trebuie creat cu ajutorul utilitarului ​''​[[http://linux.die.net/man/1/mknodmknod]]'':​+
  
 +**Atenție:​** momentan, rootfs-ul NU ARE parolă la contul ''​root''​ (și nu are nici un alt cont) și nu vă veți putea autentifica din niciun //tty//!
 +Se recomandă accesarea unui chroot (rulați ''/​usr/​bin/​bash''​ ca ultim argument al lui ''​systemd-nspawn''​) și setarea uneia prin ''​passwd'':​
 <code shell> <code shell>
-mknod <​path>​ <​type>​ <​major>​ <​minor>​+host$ sudo systemd-nspawn --as-pid2 --resolv-conf=copy-host -D /​root/​mydebian bash 
 +root@debian$ passwd root 
 +root@debian$ apt update && apt install -y wget curl # nu le vom folosi, dar exemplu :P
 </​code>​ </​code>​
  
-unde argumentele comenzii reprezintă+O dată generat rootfs-ul, acesta trebuie copiat (atenție să nu se piardă permisiunile ​și atributele unor fișiere speciale ​-- mai ales symlink-urilepe partiție și bootat! ​Se recomandă arhivarea acestuia folosind utilitarul ​''​tar'' ​(acesta salvând automat metadatele necesare în arhivă):<​code ​shell
-  * ''<​path>'' ​calea fișierului care va fi creat +$ tar czf mydebian.tar.gz -C /​root/​mydebian .
-  * ''<​type>''​ - tipul dispozitivului;​ se folosește ''​c''​ pentru dispozitive de tip caracter +
-  * ''<​major>'' ​//majorul// înregistrat de către driver pentru tipul de dispozitiv +
-  * ''<​minor>'' ​identifică dispozitivul (dacă există ​mai multe); se folosește 0 pentru primul dispozitiv +
- +
-Aflarea //​majorului//​ pentru un anumit tip de dispozitiv se poate face prin afișarea conținutului fișiserului ''/​proc/​devices''​. Acest fișier conține toate tipurile de dispozitive înregistrate de driverele încărcate în sistem. +
- +
-==== Operații ==== +
- +
-Structura ''​[[http://​lxr.free-electrons.com/​source/​include/​linux/​fs.h?​v=3.12#​L1526| file_operations]]''​ necesară pentru înregistrarea unui character driver conține operațiile prin care un driver poate comunica cu restul sistemului. Nu este necesar ca driverul să implementeze toate operațiile,​ ci doar cele care au semnificație pentru dispozitivul controlat. Cele mai folosite operații sunt: +
- +
-=== open === +
- +
-Se execută în momentul în care un proces userspace deschide, cu funcția ​''​open'' ​sau echivalentă, fișierul special asociat dispozitivului. În cadrul driver-ului această operație este implementată de o funcție cu prototipul: +
- +
-<​code ​c+
-static int mydevice_open(struct inode *, struct file *);+
 </​code>​ </​code>​
  
-Argumentele ''​inode''​ și ''​file''​ conțin informații despre fișierul accesat de userspace și pot fi folosite pentru a extrageprintre altele, care dispozitiv a fost accesat în cazul în care un driver deservește mai multe dispozitive. Dacă driverul deservește un singur dispozitiv ele pot fi ignorate. Valoarea de return a funcției va fi întoarsă userspace-ului și poate semnala erori în deschiderea dispozitivului. +În finaldorim să vedem cât ocupă rootfs-ul nostru
- +<​code ​shell
-Pentru a efectua maparea între un apel ''​open''​ din userspace pe dispozitiv și funcția care trateaza operația //open// în driver, kernel-ul are nevoie ca adresa acesteia să fie atribuită câmpului ''​.open''​ al structurii ''​fops''​ +$ du -hs /​root/​mydebian
- +
-=== release === +
- +
-Se execută în momentul în care un proces userspace închide un fișier de dispozitiv, cu funcția ''​close''​ sau echivalentă. În cadrul driver-ului această operație este implementată de o funcție cu prototipul: +
- +
-<​code ​c+
-static int mydevice_release(struct inode *, struct file *);+
 </​code>​ </​code>​
  
-Asemănător cu operația //open/argumentele ''​inode''​ și ''​file''​ conțin informații despre dispozitivul accesat și sunt folosite atunci când driverul deservește mai multe dispozitive,​ iar valoarea de return poate transmite informații userspace-ului despre succesul operației.+<note tip> 
 +Vom trece să instalăm + rulăm sistemul pe un Raspberry PI 4 fizic imediat ce pornim compilarea unui sistem Buildroot ;) 
 +</note>
  
-Pentru a efectua maparea între un apel ''​close''​ din userspace pe dispozitiv și funcția care trateaza operația //release// în driver, kernel-ul are nevoie ca adresa acesteia să fie atribuită câmpului ''​.release''​ al structurii ''​fops''​.+==== 2. Compilarea programelor de bază (buildroot) ====
  
-=== read ===+Mai departe, descriem procesul de utilizare al unui utilitar destul de popular (cel mai important fiind ușurința de învățare față de Yocto Linux).
  
-Se execută în momentul în care un proces userspace citește dintr-un fișier ​de dispozitiv, cu funcția ​''​read'' ​sau echivalentă. În cadrul driver-ului această operație este implementată de o funcție cu prototipul:+<note warning>​ 
 +Din păcate, vom avea nevoie ​de aproximativ ​''​~20-30GB'' ​de spațiu disponibil (în mașina virtuală).
  
-<code c> +Cel mai simplu este să creați un Virtual HDD nou de ''​30GB'' ​(**nu îi pre-alocați** spațiuldeoarece nu va ajunge să ocupe atât azi -- poate la temă :P ) și atașați-l proiectului VirtualBox / VMWare ca dispozitiv de stocare suplimentar (din setări / storageadd [hard] disk pe ambele hipervizoare). 
-static int mydevice_read(struct file *file, char __user ​*user_buffersize_t sizeloff_t *offset); +Apoi va trebui formatat + montat din VM: ''​mkdir /media/big && mkfs.ext4 /dev/sd<X> && mount /dev/sd<X/​media/​big''​. 
-</code>+O idee bună ar mai fă să creați director de lucru pentru ''​student'':​ ''​mkdir /​media/​big/​work && chown student:​student /​media/​big/​work -R''​.
  
-Argumentele importante ale acestei funcții sunt: +De asemenea, tot acum ar fi momentul să alocațmemorie ​RAM mai multă (3-6GB, dacă aveți ​de unde) aproape câte nuclee aveți procesorului virtualizat (să se paralelizeze procesul)
-  * ''​user_buffer''​ - pointerul către zona de memorie ​unde procesul userspace așteaptă să fie copiate datele citite +Și să conectați laptop-ul la 230V ;) 
-  * ''​size'' ​dimensiunea zonei de memorie alocată de procesul userspace pentru primirea datelor; cantitatea datelor returnate de driver nu trebuie să depășească această valoare +</note>
-  * ''​offset''​ - un offset pentru memorarea poziției în stream-ul de date returnate de driver; trebuie incrementat de către driver cu cantitatea de date returnată șeste folosit de userspace pentru a putea citi un buffer prin mai multe apeluri ''​read''​ +
-Valoarea de return este folosită pentru a transmite procesului userspace dimensiunea citită efectiv+
- +
-Pentru a efectua maparea între un apel ''​read''​ din userspace pe dispozitiv șfuncția care trateaza operația //read// în driver, kernel-ul are nevoie ca adresa acesteia ​să fie atribuită câmpului ''​.read''​ al structurii ''​fops''​. +
- +
-Un lucru important care trebuie avut în vedere la implementarea operației ''​read''​ este că pointerul șdimensiunea transmise de userspace s-ar putea să fie invalide. Dacă ele sunt folosite fără a fi verificate, driver-ul ar putea accesa involuntar zone inaccesibile de memorie, ceea ce va duce la un //kernel panic//. Pentru a rezolva această problemă și pentru a copia datele în zona de memorie a userspace-ului se pot folosi funcțiile ''​[[http://​lxr.free-electrons.com/​source/​include/​asm-generic/​uaccess.h?​v=3.12#​L265| copy_to_user]]''​ sau ''​[[http://​lxr.free-electrons.com/​source/​include/​asm-generic/​uaccess.h?​v=3.12#​L164| put_user]]''​. +
- +
-=== write ===+
  
-Se execută în momentul în care un proces userspace scrie într-un fișier ​de dispozitivcu funcția ​''​write'' ​sau echivalentă. În cadrul driver-ului această operație este implementată de o funcție cu prototipul:+Spre deosebire de ''​debootstrap'', ​care avea nevoie ​de drepturi de ''​root''​aceste utilitare folosesc principiul de ''​fake root'' ​(metadatele sistemului de fișiere sunt virtualizate într-o bază de date externă kernelului),​ fiind utilizabile (uneori obligatoriu) și de către conturi non-superuser. Aproape toți pașii va trebui să-i urmați autentificați ca ''​student''​!
  
-<​code ​c+Primul pas, ar fi, desigur descărcarea lui ''​buildroot''​. Alegeți o versiune de la [[https://​buildroot.org/​downloads/​]] și descărcați-o + dezarhivați-o (sub contul student și pe HDD-ul mare!)<​code ​shell
-static int mydevice_write(struct file *file, const char __user *user_buffer,​ size_t size, loff_t *offset);+$ wget https://​buildroot.org/​downloads/​buildroot-2023.08.tar.xz 
 +$ tar xf ... 
 +$ cd buildroot-*
 </​code>​ </​code>​
  
-Argumentele importante ale acestei funcții sunt: +Utilitarul Buildroot folosește mecanismul Kconfig (la fel ca U-Boot și kernel-ul Linux), deci: ''​make menuconfig''​. 
-  * ''​user_buffer'' ​- pointerul către zona de memorie unde procesul userspace a pus datele pentru scriere +Navigați prin meniuri ​și schimbați următoarele opțiuni:
-  * ''​size''​ - dimensiunea zonei de memorie care conține datele ce vor fi scrise; driverul nu este obligat să scrie toată cantitatea +
-  * ''​offset''​ - un offset pentru memorarea poziției în stream-ul de date; trebuie incrementat de către driver cu cantitatea de date scrisă ​și este folosit de userspace pentru a putea efectua o scriere prin mai multe apeluri ''​write''​ +
-Valoarea de return este folosită pentru a transmite procesului userspace dimensiunea scrisă efectiv.+
  
-Pentru a efectua maparea între un apel ''​write'' ​din userspace pe dispozitiv și funcția care trateaza operația //write// în driver, kernel-ul are nevoie ca adresa acesteia să fie atribuită câmpului ​''​.write'' ​al structurii ​''​fops''​.+  * ''​Target Options'' ​=> ''​Target Architecture''​ => ''​AArch64 (little endian)'';​ 
 +  * ''​Target Options''​ => ''​Target Architecture Variant''​ => ''​cortex-A53''​ (pentru compatibilitate sporită cu mai multe modele Raspberry PI + Qemu); 
 +  * ''​Target Options''​ => ''​Floating point strategy''​ => ''​VFPv4''​ (la fel, compatibilitate sporită); 
 +  * ''​Toolchain''​ => ''​Enable compatibility shims ...''​
 +  * ''​System Configuration'' ​=> ''​Root password''​ => ''​student''​ -- pentru a ne putea autentifica în shell! 
 +  * ''​Filesystem images''​ => ''​Compression method''​ => ''​gzip''​ -- opțional, imaginea ocupă extrem de puțin oricum.
  
-La fel ca la operația //read//un lucru important care trebuie avut în vedere este că pointerul și dimensiunea transmise ​de userspace s-ar putea să fie invalide. Dacă ele sunt folosite fără a fi verificate driver-ul ar putea accesa involuntar zone inaccesibile ​de memorieceea ce va duce la un //kernel panic//. Pentru a rezolva această problemă și pentru a copia datele din zona de memorie a userspace-ului se pot folosi funcțiile ''​[[http://​lxr.free-electrons.com/​source/​include/​asm-generic/​uaccess.h?​v=3.12#​L255| copy_from_user]]''​ sau ''​[[http://​lxr.free-electrons.com/​source/​include/​asm-generic/​uaccess.h?​v=3.12#​L226| get_user]]''​.+Apoi rulați ''​make''​! Procesul poate dura între 10 minute și 1h, în funcție ​de numărul de nucleefrecvența lor (ce poate fi scăzută dacă sunteți pe baterie) ​și memoria RAM disponibilă.
  
-===== Sistemul VersatilePB emulat în Qemu =====+<note tip> 
 +Distribuția de bază Buildroot include doar utilitarele ce vin cu [[https://​busybox.net/​|busybox]]. 
 +Aveți, însă, o mulțime de pachete de aplicații pe care le puteți bifa la secțiunea ''​Target packages''​!
  
-==== Overview ====+Însă acest lucru va duce la o mărire drastică a timpului de compilare + consum de spațiu pe disk (în funcție de ce / câte pachete + câte dependințe au)! 
 +</​note>​
  
-Am mai lucrat in laboratoarele trecute cu sistemul VersatilePB de la ARM, emulat într-o masină virtuală de Qemu. Dacă sistemul fizic pune la dispoziție o placă de dezvoltare cu procesor ARM, conector pentru display, mouse, tastatura, șamd, la fel încearcă șemulatorul Qemu să emuleze același hardware.+**Nu așteptați! lăsați-l să ruleze ​și treceți la task-ul următor ;)**
  
-Pentru a fi cât mai aproape de sistemul fizicQemu are nevoie de harta de memorie a sistemului fizic:+La final, să inspectați sistemul de fișiere rezultatîl găsiți la calea ''<​buildroot-dir>/​output/​images/''​. Cât ocupă?
  
-<code text hw/​arm/​versatilepb.c>​ +==== 3. Instalarea rootfs-ului în imaginea RPI 4 ====
-    /* Memory map for Versatile/​PB: ​ */ +
-    /* 0x10000000 System registers. ​ */ +
-    /* 0x10001000 PCI controller config registers. ​ */ +
-    /* 0x10002000 Serial bus interface. ​ */ +
-    /* 0x10003000 Secondary interrupt controller. ​ */ +
-    /* 0x10004000 AACI (audio). ​ */ +
-    /* 0x10005000 MMCI0. ​ */ +
-    /* 0x10006000 KMI0 (keyboard). ​ */ +
-    /* 0x10007000 KMI1 (mouse). ​ */ +
-    /* 0x10008000 Character LCD Interface. ​ */ +
-    /* 0x10009000 UART3. ​ */ +
-    /* 0x1000a000 Smart card 1.  */ +
-    /* 0x1000b000 MMCI1. ​ */ +
-    /* 0x10010000 Ethernet. ​ */ +
-    /* 0x10020000 USB.  */ +
-    /* 0x10100000 SSMC.  */ +
-    /* 0x10110000 MPMC.  */ +
-    /* 0x10120000 CLCD Controller. ​ */ +
-    /* 0x10130000 DMA Controller. ​ */ +
-    /* 0x10140000 Vectored interrupt controller. ​ */ +
-    /* 0x101d0000 AHB Monitor Interface. ​ */ +
-    /* 0x101e0000 System Controller. ​ */ +
-    /* 0x101e1000 Watchdog Interface. ​ */ +
-    /* 0x101e2000 Timer 0/1.  */ +
-    /* 0x101e3000 Timer 2/3.  */ +
-    /* 0x101e4000 GPIO port 0.  */ +
-    /* 0x101e5000 GPIO port 1.  */ +
-    /* 0x101e6000 GPIO port 2.  */ +
-    /* 0x101e7000 GPIO port 3.  */ +
-    /* 0x101e8000 RTC.  */ +
-    /* 0x101f0000 Smart card 0.  */ +
-    /* 0x101f1000 UART0. ​ */ +
-    /* 0x101f2000 UART1. ​ */ +
-    /* 0x101f3000 UART2. ​ */ +
-    /* 0x101f4000 SSPI.  */ +
-    /* 0x34000000 NOR Flash */ +
-</​code>​+
  
-De interes pentru acest laborator vor fi adresele fizice ale celor 4 controllere GPIOQemu emuleaza controller-ul GPIO prin modelul PL061 fabricat de ARM PrimeCell. Modalitatea prin care Qemu adaugă aceste controllere emulate la mașina virtuală este cu ajutorul acestor construcții:+**0.** Descărcați [[https://​github.com/​cs-pub-ro/​SI-rpi-debian-scripts/​releases|de aici imaginea rpi-full.img.tar.xz]].
  
-<code text hw/​arm/​versatilepb.c> +**1.** Trebuie să montăm imaginea și să copiem noul RootFs pe cea de a doua partiție ​(pe care o vom formata):
-    sysbus_create_simple("​pl061",​ 0x101e4000, pic[6])+
-    sysbus_create_simple("​pl061",​ 0x101e5000, pic[7]); +
-    sysbus_create_simple("​pl061",​ 0x101e6000, pic[8]); +
-    sysbus_create_simple("​pl061",​ 0x101e7000, pic[9]); +
-</​code>​+
  
-==== Porturile GPIO in mașina fizică vs mașina virtuală ==== +  * Dezarhivați fișierul imaginii ''​rpi-full.img'';​ ar trebui să obțineți imaginea de ''​2GB'';​ 
- +  * Verificați că există ambele partiții''​parted rpi-full.img print'';​ 
-Deoarece un controller PL061 poate furniza 8 conexiuni GPIO, o mașină fizică ar avea cei 32 de PINI expuși pe placa de dezvoltare: +  * Conectați imaginea la un dispozitiv ​de tip bloc folosind ''​losetup ​/dev/loop0 rpi-full.img'' ​și ''​partprobe ​/dev/loop0''​ (verificați cu ''​losetup -L''​ ce aveți conectat dacă întâmpinați probleme); 
- +  * Formatați sistemul ​de fișiere ​celei de-doua partiții în ext4: ''​mkfs.ext4 ​-L RPI_ROOTFS ​/dev/loop0p2''​ (confirmați!); 
-{{:​si:​laboratoare:​physical-machine-gpio.png?700|}} +  * Montați ​noua partiție goală ''​ext4''​ (puteți folosi ''/​mnt''​ ca mountpoint) și copiați conținutul arhivei rootfs generate anterior (ori cea de Debianori cea obținută prin Buildroot): <​code ​shell> 
- +tar xf <​ARCHIVE_FILE>​ -C <​DEST_MOUNTPOINT
-Driver-ul PL061 din instanțde Linux expune utilizatorilor 4 intrări în /dev, precum ​/dev/​gpiochip0. +inspectați calea dezarhivată:​ 
- +ls -l <​DEST_MOUNTPOINT>​
-{{:​si:​laboratoare:​qemu-machine-gpio.png?​700|}} +
- +
-Când device-ul de GPIO este emulat in Qemu, el nu mai are tranzistoare ​și bistabili astfel încât sa poată pune tensiunea pe PINI la 0V (0 logic) sau 5V (1 logic). De aceea, Qemu emulează valorile de intrare/ieșire în structuri de C/C++ pe care le reține in memoria sa. +
- +
-==== Folosirea interfeței /​dev/​gpiochip ==== +
- +
-Incepând cu versiunea de kernel 4.8, o noua interfață de acces porturilor ​de GPIO fost creată prin folosirea char device-ului /dev/gpiochip, iar vechea interfață expusa in /​sys/​class/​gpio nu mai este compilată in mod implicit. +
- +
-Pentru a folosi ​noua interfață, un set de tool-uri noi au fost adăugate. Unul dintre ele este "​gpiod"​ si poate fi instalat pe distribuția de Rasbperry Pi pe care o folosim la aceste laboratoare: +
- +
-<​code>​ +
-sudo apt install gpiod Tools for interacting with Linux GPIO character device+
 </​code>​ </​code>​
 +  * Demontați partiția ext4 și deconectați dispozitivul ''​loop''​ folosite!
  
-Dintre binarele acestui pachet menționăm:+**2.** Porniți Raspberry PI-ul în u-boot, apoi intrați în modul ''​ums mmc X''​ (vedeți [[:​si:​laboratoare/​05|laboratorul 5]]); folosiți ori ''​dd'',​ ori un utilitar de Raw Disk Imager pentru Windows (recomandatRaspberry PI Imager) ca la laboratoarele anterioare pentru a scrie noua imagine pe RPI.
  
-  * gpiodetect - scanează device-urile de GPIO din sistem +<note important>​ 
-  * gpioget ​   - citeste valoarea unui PIN de GPIO, și setează direcția de INPUT în caz că nu era deja setată +  * **Atenție**: Va trebui, mai întâisă aduceți imaginea pe Windows ​(folosiți ''​scp''​ cu ''​username@<​ip_sursa>​:/cale/catre/fisier.img''​ ca prim argument, iar, ca destinație,​ o cale de pe PC-ul fizic, e.g., ''​C:​\Users\..etc''​)! 
-  ​gpioset ​   - seteaza valoarea PIN-ului pe 0/1 și setează direcția de OUTPUT în caz că nu era deja setată +  * **Notă pentru ​VirtualBox**: va trebui să folosiți argumentul ''​-P 2023''​ ș''​student@localhost:​<cale>''​ ca sursă. 
-  ​gpioinfo ​  - listează valorile fiecărui PIN +  * **Notă pentru ​cei cu gazda pe Linux**: dacă utilizați ''​dd'',​ va trebui ​să rulați comanda ''​sync''​ și să așteptați să se termine! Asta e important deoarece kernelul modern cache-uiește în memoria RAM și scrierea se termină, aproape instant (însă rămâne să se copieze pe SD în backgroundaceastă operațiune fiind mult mai lentă).
- +
-===== Exerciții ===== +
- +
-În laboratorul de astăzi vom folosi driver-ul PL061 (emulat ca device in Qemu) pentru a seta modul OUTPUT pe PIN-ul 5 al device-ului de GPIO numărul 2. +
- +
-**Ex. 0** - **Pregătirea** imaginii de kernel și a header-elor de kernel +
- +
-Ca și în laboratorul trecutavem nevoie de o imagine de kernel și de headere-le asociate. Spre deosebire de laboratorul trecut, noua imagine de kernel are scos suportul de PL061 din imagine. Driver-ul PL061 nu mai este compilat built-inci îl vom compila separat. +
- +
- * Imaginea de kernel o găsiți [[https://​drive.google.com/​file/​d/​1_VljZ3y0E1225sco119aPyMe1Kif_WLK/​view?​usp=sharing|aici]] +
- * Header-ele de kernel ​(identice cu cele de la laboratorul 9) le găsiți [[https://drive.google.com/file/​d/​1SSSs4UAVU74yyW5j3t2m7OpS0un5Mby5/​view?​usp=sharing|aici]] +
-  +
-Porniți noul kernel cu vechea imagine (.img-ul) de RaspberryPi de la [[https://​ocw.cs.pub.ro/​courses/​si/​laboratoare/​03|laboratorul 03]]+
- +
-**Ex. 1** - Compilați modulul de kernel ​pentru **driver-ul de GPIO PL061** de {{:si:​laboratoare:​pl061.tgz|aici}} +
- +
-Înainte de a insera driver-ul pentru PL061, listați device-urile de GPIO detectate în sistem cu "​gpiodetect"​. +
- +
-<​code>​ +
-gpiodetect +
-</​code>​ +
- +
-Inserațdriver-ul si relistați device-urile de GPIO +
- +
-<code> +
-sudo insmod gpio-pl061.ko +
-</​code>​ +
- +
-Verificați că toți PIN-ii de GPIO ai controller-ului 2 sunt pe modul INPUT+
- +
-<​code>​ +
-gpioinfo gpiochip2 +
-</​code>​ +
- +
-Setați PIN-ul 5 pe modul OUTPUT si o valoare de 5V cu comanda: +
- +
-<​code>​ +
-gpioset gpiochip2 5=1 +
-</​code>​ +
- +
-Treceți PIN-ul 5 înapoi pe modul INPUT: +
- +
-<​code>​ +
-gpioget gpiochip2 5 +
-</​code>​ +
- +
-**Ex. 2** - Compilați modulul de kernel ​pentru **driver-ul de GPIO al echipei de SI** de {{:si:​laboratoare:​si-gpio-driver.tgz|aici}}. Driver-ul este unul foarte rudimentar și poate doar să seteze direcția de OUTPUT a pinilor de GPIO. Ca exercițiu dorim să setăm PIN-ul 5 al device-ului de GPIO numărul 2 în modul OUTPUT. +
- +
-Pentru a-l compila este nevoie să completați codul sursă cu două adrese. Prima adresă este cea device-ului de GPIO numarul 2, pe care o găsiți fie in harta memorie de la VersatilePB (mai sus în laborator), sau în output-ul comenzii "​gpiodetect"​.  +
- +
-A doua adresă este adresa registrului de direcție al device-ului PL061. Găsiți offset-ul registrului (față de adresa de start, numită ​și base) în datasheet-ul producătoruluiîn tabelul 3.1. Puteți să-l download-ați de [[http://​access.ee.ntu.edu.tw/​course/​SOC%E5%AF%A6%E9%A9%97%E6%95%99%E6%9D%90/​Version%203/​Lab05_External%20IO%20Control/​Doc/​Ref/​ddi0190_gpio_trm.pdf|aici]] sau de [[primecell-pl061.pdf|aici]] +
- +
-Înainte de a trimite comenzi către device, este nevoie ​să creăm o intrare in /dev a noului device driver. Inspectați codul sursa pentru a afla Major-ul driver-uluiiar apoi executați comanda: +
- +
-<​code>​ +
-sudo mknod /​dev/​si_gpio c $MAJOR 0 +
-</​code>​ +
- +
-Folosiți următoarea comandă pentru a seta un PIN in modul OUTPUT: +
- +
-<​code>​ +
-sudo bash -c 'echo $PIN > /​dev/​si_gpio'​ +
-</​code>​ +
- +
-Listați starea PIN-ilor cu comanda: +
- +
-<​code>​ +
-cat /​dev/​si_gpio +
-</​code>​ +
- +
-<​note>​ +
-De asemenea, puteți lista starea PIN-ilor cu tool-ul "​gpioinfo",​ dar este nevoie de reîncărcarea driver-ului PL061 care face cache la starea PIN-ilor: +
-<​code>​ +
-sudo rmmod gpio_pl061; sudo insmod gpio-pl061.ko; gpioinfo gpiochip2 +
-</​code>​+
 </​note>​ </​note>​
  
-**Ex3** Folosiți **suportul de Qemu-Monitor** pentru a citi/scrie direct memoria fizică emulată în Qemu. Qemu-Monitor este un "​backdoor"​ ce permite interogarea structurilor folosite de Qemuprecum memoria, procesoare, device-uri, șamd. Documentația completă o găsiți [[https://​qemu.readthedocs.io/​en/​latest/​system/​monitor.html|aici]]. Qemu-Monitor este similar ​cu JTAG-ul folosit pentru a depana o plăcuță de dezvoltare fizică.+3Reporniți Raspberry PI-ul. Lăsați-l să booteze normalar trebui să pornească kernel-ul implicit ​cu rootfs-ul vostru!
  
-{{:​si:​laboratoare:​qemu-monitor.png?700|Qemu Monitor}}+  * Inspectați-l! Cât ocupă pe disk (''​df''​)? 
 +  * Dacă vă printează mesajul cu ''​waiting for device /​dev/​mmcblk0p2''​ și nu se termină procesul, verificați dacă partiția a doua există din u-boot (''​part list mmc 0''​).
  
-Pentru a folosi Qemu-Monitor este nevoie să pornim Qemu cu următoarele argumente:+==== 4. [BONUS] Rulați imaginea voastră în QEmu (system) ====
  
-<​code>​ +Observăm că procesul de testare a unei imagini embedded consumă ceva timp (mai ales la scrierea pe cardul SD)
--monitor telnet:​127.0.0.1:​7777,​server,​nowait +Dorim să optimizăm!
-</​code>​+
  
-În acest moment Qemu așteaptă conexiuni de Telnet pe portul 7777. Conectați-vă la Qemu prin comanda:+Rulați imaginea obținută anterior (va trebui să exrageți kernel-ul din imagine!) ​prin ''​qemu-system-aarch64''​. 
 +Re-vedeți instrucțiunile din [[:si:​laboratoare/​04|laboratorul 4]].
  
-<​code>​ +===== Resurse =====
-telnet localhost 7777 +
-</​code>​+
  
-Afișați porțiunea de memorie unde se află registrul de direcție și verificați daca registrul are valoarea 0x20 (PIN-ul 5 este bit-ul 6 din registru - 00100000 in binar). Folosiți comanda "​xp"​ a cărei descriere este aici: +  * [[https://github.com/cs-pub-ro/SI-rpi-debian-scripts|Cod sursă scripturi compilare bootloader ​kernel ​generare rootfs]] 
- +  * [[https://buildroot.org/downloads/manual/manual.html#​_getting_started|Buildroot Manual - Getting started]]
-<​note>​ +
-xp ///fmt// addr +
-Physical memory dump starting at //addr//. +
- +
-//fmt// is a format which tells the command how to format the data. Its syntax is: /​{count}{format}{size} +
-</​note>​ +
- +
-Folosind comenzile "info mtree" si "info qtree" puteți afla informații despre memorie și device-uri, printre care și PL061</​hidden>​ +
-===== Resurse ===== +
-  * [[https://elixir.bootlin.com/linux/latest/sourceLinux Cross Reference]]+
  
-<​hidden>​ 
-  * [[https://​cs.unibo.it/​~davide.berardi6/​post/​20201204-1.html| Simulating Raspberry PI GPIO interaction with QEMU]] 
-  * [[https://​qemu.readthedocs.io/​en/​latest/​system/​arm/​versatile.html| Arm Versatile boards]] 
-  * [[https://​qemu.readthedocs.io/​en/​latest/​system/​monitor.html| QEMU Monitor]] 
-</​hidden>​ 
-<​hidden>​ 
-===== Referințe ===== 
  
-  * [[http://​www.cs.rutgers.edu/​~pxk/​416/​notes/​11-devices.html| Rutgers University CS416 (Operating Systems) - Devices]] 
-  * [[http://​ocw.cs.pub.ro/​courses/​so2/​laboratoare/​lab04| UPB SO2 (Sisteme de Operare 2) - Device drivere în Linux]] 
-</​hidden>​ 
si/laboratoare/09.1701620222.txt.gz · Last modified: 2023/12/03 18:17 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0