Differences

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

Link to this comparison view

si:laboratoare:10 [2024/01/06 13:33]
laura.ruse [Exerciții]
si:laboratoare:10 [2024/12/09 16:38] (current)
jan.vaduva [Resurse]
Line 1: Line 1:
-====== Laboratorul 10. CoAP & MQTT ======+====== Laboratorul 10. Module de Kernel ​======
  
-Internet of Things (IoT) este definit ca o colecție de noduri (things) care pot aduna informații din mediul în care se află, pot trimite aceste informații către o locație remote/​locală și pot face anumite acțiuni. ​ 
  
-Nodurile acestea sunt de obicei alimentate de la baterie, ​cu putere de procesare, memorie reduse și lățime de bandă reduse. Aceste noduri sunt în general dispozitive embeddedidentificate în mod unicși care sunt conectate la Internet+Un sistem embedded poate funcționa doar cu perifericele pe care le-am folosit deja (rețeacard SDUSB), î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?
  
 +===== Memory Management Unit și importanța sa în dezvoltarea de module =====
  
-În acest laborator, vom utiliza două protocoale de comunicație frecvent utilizate în IoT.+==== Sisteme embedded fără MMU ====
  
 +În sistemele care nu au MMU scrierea unui //modul// este directă: fie că modulul este foarte strâns cuplat cu întreg sistemul, fie că este sub forma unei biblioteci de funcții, totul 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 sistemul, putând duce chiar la compromiterea acestuia. ​
  
-==== CoAP ====+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 PM, dacă o funcție configura seriala cu un anumit baud rate, apoi ceda controlul altei funcții care seta baud rate la altă valoarea, valoarea finală va fi a doua, iar codul asociat cu prima funcție nu va mai rula corect.
  
-CoAP (Constrained Application Protocol) este un protocol ​de comunicare conceput pentru utilizarea ​în medii cu resurse limitateîn special în contextul IoT (Internet of Things)Este folosit pe dispozitive cu resurse reduse (energiememorielățime de bandă), și în rețelele cu pierderi de pachete.+Deși pare greu de ajuns la o asemenea situație, ​în realitate este foarte ușor: unul din modurile de a implementa sisteme multitasking colaborative este folosind corutine, funcții ​cu mai multe puncte de intrare care cedează controlul. În astfel de sistemeo corutină joacă rolul unui procesAstfelavâ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.
  
-CoAP funcționează peste UDP/IP și oferă un model simplu de interacțiune bazat pe cerere/​răspuns,​ similar ​cu HTTP, ceea ce îl face potrivit pentru dispozitivele cu resurse limitate. ​+==== Sistem embedded ​cu MMU ====
  
-CoAP suportă cele 4 metode standard din HTTP, mai exact GET, POSTPUT și DELETE, care au aceeași semnificație cu cele din HTTP. De obicei nodul senzorial joacă rolul de server CoAP. +Sistemele cu MMU rezolvă această problemă. Accesul la hardware poate fi făcut doar de aplicațiile ce rulează într-un mod specialnumit și //​privilegiat//​. Astfelcodul care interfațează hardware-ul va fi pus într-o zonă protejată de memorie, accesibilă doar din modul privilegiatUn 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 arbitrare a accesului la resursa hardware ​pe care o gestionează.
-  * GET pentru a obține date de pe server  +
-  * POST pentru a scrie o resursă de pe server +
-  * PUT pentru a actualiza o resursă de pe server +
-  * DELETE pentru a șterge resursă de pe server+
  
-Există o multitudine de implementări software ale protocolului CoAP ([[https://​en.wikipedia.org/​wiki/​Constrained_Application_Protocol#​Implementations|Implementări]]),​ dar în acest laborator vom utiliza biblioteca [[https://​github.com/​hirotakaster/​CoAP-simple-library|CoAP-simple-library]] pentru Arduino IDE pentru a rula un server CoAP și implementarea Python [[https://​github.com/​Tanganelli/​CoAPthon|CoAPthon]] pentru a rula un client CoAP.+===== Arhitectura kernelului =====
  
-O captură Wireshark arată cum rulează protocolul CoAP peste protocolul UDP:+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.
  
-{{:​iothings:​laboratoare:​lab7-coap-capture.png?​600|}} +==== Monolitic ​====
-==== MQTT ====+
  
-MQTT (Message Queuing Telemetry Transport) este un protocol de comunicare eficient din punct de vedere al consumului de energie și fiabil. A fost dezvoltat pentru ​facilita schimbul ​de mesaje între dispozitive cu resurse limitateconectate ​în rețele cu lățime de bandă redusă, fiind ideal pentru IoT.+Arhitectura **monolitică** ​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.
  
-MQTT este un protocol pentru transportul mesajelor de la un producător (publisher) la un consumator (subscriber). Spre deosebire de CoAP, care are o arhitectură client-server,​ MQTT folosește un intermediar (broker) pentru a livra mesajele de la producător la consumator.+==== Microkernel ====
  
-MQTT funcționează pe baza conceptului ​de topic-uri. Un topic este un identificator unic care definește un subiect ​de interes ​(extemperatura).+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). 
  
-Un dispozitiv care dorește să publice un mesaj se conectează la un broker MQTT. Brokerul este un server care intermediază comunicarea dintre clienții care publică și clienții care se abonează la mesaje. 
  
-Când un client publică un mesajacesta specifică topicul mesajuluiBrokerul transmite apoi mesajul tuturor clienților abonați la acel topic.+După cum se poate observa, ​un mare dezavantaj al kernel-ului de tip monolitic îl reprezintă lipsa de modularitate și de extensibilitatelucruri care au fost adăugate ulteriorAstfel, 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.
  
-Un client care dorește să se aboneze la mesaje se conectează la broker și specifică topicurile la care dorește să se aboneze. Când brokerul primește un mesaj pentru un topic la care un client este abonat, acesta transmite mesajul clientului.+===== Linux Kernel =====
  
-{{:​iothings:​laboratoare:​lab7-mqtt-flow.png|}}+Î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 hardwareExistă î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]].
  
-==== CoAP vsMQTT ====+De altfel, din punct de vedere al arhitecurii kernel-ului,​ Linux este **monolitic** cu posibilitatea de extindere la runtime prin **module**.
  
-În această figură avem o comparație vizuală între protocoalele CoAP și MQTT. La MQTT avem un singur broker și mai mulți clienți care pot fi publisheri sau subscriberi. La CoAP putem avea mai multe servere care generează date (de obicei nodurile senzoriale) și mai mulți clienți care obțin acele date. +==== Programarea modulelor ​de kernel ====
  
-{{:si:​laboratoare:​mqtt_coap.png?​800|}}+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).
  
-==== Exerciții ====+=== Modulul Hello world ===
  
-Instalați Arduino IDE și adăugați board-ul ESP32 conform acestui [[https://​randomnerdtutorials.com/​installing-the-esp32-board-in-arduino-ide-windows-instructions/​|tutorial]]doar că tipul board-ului să fie ESP32 WROVER pentru Sparrow ESP32.+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ță șdescriereutilizarea acestor macro-uri fiind obligatorie.
  
-=== Ex. 1 CoAP === 
  
-Dorim să folosim CoAP pentru a stinge și aprinde un LED pe placa ESP32Pentru asta trebuie ca placa ESP32 să fie configurată ca server CoAP și să răspundă la cererile primite de la clienți CoAP (laptop și dispozitiv mobil).+<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 */
  
-== Ex. 1.1. Serverul CoAP pe ESP32 ==+MODULE_AUTHOR("​SI"​);​ 
 +MODULE_LICENSE("​GPL"​);​ 
 +MODULE_DESCRIPTION("​Hello world module"​);​
  
-Descărcați biblioteca [[https://​github.com/​hirotakaster/​CoAP-simple-library|CoAP-simple-library]] și copiați-o în directorul cu biblioteci din Arduino IDE (de obicei în ''​Documents/​Arduino/​libraries''​). Este necesară repornirea Arduino IDE după instalarea unei biblioteci direct în acel directorîn loc să utilizați Library Manager.+static int __init my_init(void) 
 +
 +        printk(KERN_DEBUG "Helloworld!\n"​);​
  
-{{:​iothings:​laboratoare:​lab7-coap-server-png.png|}}+        return 0; 
 +}
  
-Deschideți exemplul numit ''​esp32''​ din biblioteca ''​CoAP_simple_library''​ și ajustați codul astfel încât LED-ul conectat la GPIO25 să fie aprins prin CoAP. +static void __exit my_exit(void) 
 +
 +        printk(KERN_DEBUG "​Goodbye,​ world!\n"​);​ 
 +}
  
-Conectați placa ESP32 la rețeaua WiFi (recomandăm să faceți un hotspot pe dispozitivul mobil, deoarece nu va merge cu rețeaua facultății). Completați în cod SSID-ul și parola rețelei.+module_init(my_init)
 +module_exit(my_exit);​ 
 +</​file>​
  
-Rețineți ​adresa IP obținută de ESP32 pentru ​fi utilizată în clientul CoAP.+Modulul Hello World conține
 +  - Header-ele necesare 
 +  - Definirea autorului, licenței ș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.
  
-<​note>​ +=== Module de kernel In-Tree === 
-Portul implicit definit ​de CoAP este portul ​UDP 5683În biblioteca ''​CoAP_simple_library''​ din Arduino IDEportul ​este definit ​în fișierul ''​libraries/CoAP_simple_library/coap-simple.h''​+ 
 +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). 
 + 
 +==== 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>​ 
 + 
 +===== Exerciții ===== 
 + 
 +0. **Pregătirea** imaginii de kernel si a header-elor de kernel 
 + 
 +În cadrul laboratorul de astăzi, vom lucra cu module de kernel Out-of-Tree,​ pe un Raspberry PI fizic. Compilarea modulelor se va face in afara RPi-ului. Va fi nevoie de kernel-ul compilat in [[https://​ocw.cs.pub.ro/​courses/​si/​laboratoare/​08|laboratorul 8]], care trebuie sa fie instalat pe Raspberry PI. 
 + 
 +Codul modulelor de kernel se afla [[https://​github.com/​cs-pub-ro/​embedded-systems/​tree/​master/​lab09|aici]]. 
 +  
 +1. Compilați modulul de kernel **Hello World** cu header-ele de kernel corespunzătoare. 
 + 
 +În directorul ''​hello'',​ rulati comanda: 
 + 
 +<​code>​KDIR=<​locatia_kernel-ului_linux>​ CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 make</​code>​ 
 + 
 +<note>​Revizitați [[https://​ocw.cs.pub.ro/​courses/​si/​laboratoare/​05|laboratorul 05]] pentru a va reaminti despre cross-compiling.</​note> 
 + 
 +Copiați și incărcați modulul hello.ko pe 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>​ 
 + 
 +2. Afisati mesajul de "Hello World" cu mai multe niveluri de afisare: info, warning, error, critical. 
 +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]]. 
 + 
 +<note tip>​Puteti utiliza 2 functii pentru a afisa mesaje de kernel: printk si pr_<​nivel>​.</​note>​ 
 + 
 +3. Folosiți modulul de kernel din folder-ul ''​rename-init''​ pentru a **redenumi procesul "​Init"​** 
 + 
 +<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>​ 
 +<note tip>​Folosiți structura //%%struct task_struct%%//,​ cu accent pe cămpurile //pid//, și //​parent//</​note>​ 
 +<note tip>​Există o 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 1) cu următoarea comanda: 
 + 
 +<​code>​cat /​proc/​1/​status</​code>​ 
 + 
 +Verificați că numele procesului Init a fost modificat. 
 + 
 +4. Dezactivați intreruperea de timp cu ajutorul modulului din folder-ul ''​disable-interrupt''​. 
 + 
 +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ă) nu mai poate genera interuperi. După descărcarea modulului de kernel, comanda sleep va relua execuția. 
 + 
 +<​hidden>​ 
 + 
 +5. Scrieti un modul de nucleu care aprinde un LED sau care porneste un motor, in functie de ce este disponibil fizic in laborator. Testati folosind materialele din laborator. Modulul 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. 
 + 
 +===== 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-ulscutind 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 diferite, fă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>​ 
 + 
 +Aceasta primește ca argumente:​ 
 +  * ''​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 ==== 
 + 
 +În mod asemănător,​ deînregistrarea unui character driver se face în funcția de clean-up a 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 major, const 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 dispozitivelor. Crearea fișierului asociat unui dispozitiv nu se face însă automat. Acesta trebuie creat cu ajutorul utilitarului ''​[[http://​linux.die.net/​man/​1/​mknod| mknod]]'':​ 
 + 
 +<code shell> 
 +mknod <​path>​ <​type>​ <​major>​ <​minor>​ 
 +</​code>​ 
 + 
 +unde argumentele comenzii reprezintă:​ 
 +  * ''<​path>''​ - calea fișierului care va fi creat 
 +  * ''<​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 o 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>​ 
 + 
 +Argumentele ​''​inode''​ și ''​file''​ conțin informații despre fișierul accesat de userspace și pot fi folosite pentru a extrage, printre 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. 
 + 
 +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''​ 
 + 
 +=== 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>​ 
 + 
 +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. 
 + 
 +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''​. 
 + 
 +=== read === 
 + 
 +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:​ 
 + 
 +<code c> 
 +static int mydevice_read(struct file *file, char __user *user_buffer,​ size_t size, loff_t *offset); 
 +</​code>​ 
 + 
 +Argumentele importante ale acestei funcții sunt: 
 +  * ''​user_buffer''​ - pointerul către zona de memorie unde procesul userspace așteaptă să fie copiate datele citite 
 +  * ''​size''​ - dimensiunea zonei de memorie alocată de procesul userspace pentru primirea datelor; cantitatea datelor returnate de driver nu trebuie să depășească această valoare 
 +  * ''​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ă și 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 și 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 ș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 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 dispozitiv, cu funcția ''​write''​ sau echivalentă. În cadrul driver-ului această operație este implementată de o funcție cu prototipul:​ 
 + 
 +<code c> 
 +static int mydevice_write(struct file *file, const char __user *user_buffer,​ size_t size, loff_t *offset); 
 +</​code>​ 
 + 
 +Argumentele importante ale acestei funcții sunt: 
 +  * ''​user_buffer''​ - pointerul către zona de memorie unde procesul userspace a pus datele pentru scriere 
 +  * ''​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''​. 
 + 
 +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 memorie, ceea 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]]''​. 
 + 
 +===== Sistemul VersatilePB emulat în Qemu ===== 
 + 
 +==== Overview ==== 
 + 
 +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ă și emulatorul Qemu să emuleze același hardware. 
 + 
 +Pentru a fi cât mai aproape de sistemul fizic, Qemu are nevoie de harta de memorie a sistemului fizic: 
 + 
 +<code text hw/​arm/​versatilepb.c>​ 
 +    /* 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 GPIO. Qemu 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:​ 
 + 
 +<code text hw/​arm/​versatilepb.c>​ 
 +    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ă ==== 
 + 
 +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:​ 
 + 
 +{{:​si:​laboratoare:​physical-machine-gpio.png?​700|}} 
 + 
 +Driver-ul PL061 din instanța de Linux expune utilizatorilor 4 intrări în /dev, precum /​dev/​gpiochip0. 
 + 
 +{{:​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 a porturilor de GPIO a 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>​ <​code>​
-#define COAP_DEFAULT_PORT 5683+sudo apt install gpiod Tools for interacting with Linux GPIO character device
 </​code>​ </​code>​
-</​note>​ 
  
 +Dintre binarele acestui pachet menționăm:​
  
-== Ex. 1.2. Clientul CoAP ==+  * gpiodetect - scanează device-urile de GPIO din sistem 
 +  * gpioget ​   - citeste valoarea unui PIN de GPIO, și setează direcția de INPUT în caz că nu era deja setată 
 +  * gpioset ​   - seteaza valoarea PIN-ului pe 0/și setează direcția de OUTPUT în caz că nu era deja setată 
 +  * gpioinfo ​  - listează valorile fiecărui PIN
  
-Instalațpe laptop biblioteca ''​CoAPthon'' ​din Python folosind utilitarul ''​pip''​:+===== 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 trecut, avem 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-in, ci î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>​ <​code>​
-pip install CoAPthon+gpiodetect
 </​code>​ </​code>​
  
-<note important>​Dacă folosiți Python3 instalați CoAPthon3</​note>​+Inserați driver-ul si relistați device-urile de GPIO
  
-Folosiți următoarea comandă pentru a aprinde șstinge LED-ul:+<​code>​ 
 +sudo insmod gpio-pl061.ko 
 +</​code>​ 
 + 
 +Verificați că toțPIN-ii de GPIO ai controller-ului 2 sunt pe modul INPUT.
  
 <​code>​ <​code>​
-/​usr/​local/​bin/​coapclient.py -o PUT -p "​coap://​192.168.1.151/​light"​ -P "​1"​+gpioinfo gpiochip2
 </​code>​ </​code>​
  
-<note important>​Folosiți adresa IP a plăcii ESP32.  +Setați PIN-ul 5 pe modul OUTPUT si o valoare de 5V cu comanda:
-Laptop-ul și placa ESP32 trebuie să fie conectate în aceeași rețea WiFi.  +
-</​note>​+
  
-== Ex. 1.3. Aplicația Android ''​IoT CoAP''​ ==+<​code>​ 
 +gpioset gpiochip2 5=1 
 +</​code>​
  
-Descărcați și instalați aplicația ''​IoT CoAP''​ din Google Play Store. Folosiți aplicația pentru a trimite mesaje către placa ESP32 pentru a aprinde și stinge LED-ul+Treceți PIN-ul 5 înapoi pe modul INPUT:
  
-{{:​iothings:​laboratoare:​lab7-iot-coap-app.png|}}+<​code>​ 
 +gpioget gpiochip2 5 
 +</​code>​
  
-=== Ex. 2 MQTT ===+**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.
  
-Dorim să controlăm LED-ul plăcii ESP32 prin protocolul MQTTPlaca ESP32 va avea rol de subscriber pe un anumit topiciar laptopul va avea rol de publisher ​pe acelaștopic. Astfel, prin mesajul MQTT trimis ​de la laptop vom aprinde și vom stinge LED-ul plăcii.+Pentru a-l compila este nevoie ​să completați codul sursă cu două adresePrima adresă este cea a device-ului ​de GPIO numarul 2, pe care o găsițfie in harta memorie ​de la VersatilePB (mai sus în laborator), sau în output-ul comenzii "​gpiodetect"​
  
-Instalați biblioteca ''​PubSubClient''​ din Library Manager ​al Arduino IDE și deschideți exemplul ''​mqtt_esp8266''​.+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]]
  
-{{:​iothings:​laboratoare:​lab7-mqtt-esp8266.png|}}+Î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-ului, iar apoi executați comanda:
  
-Deși este scris pentru ESP8266, puteți rula programul pe ESP32 prin modificarea ​următoarei linii:+<​code>​ 
 +sudo mknod /​dev/​si_gpio c $MAJOR 0 
 +</​code>​ 
 + 
 +Folosiți următoarea comandă pentru a seta un PIN in modul OUTPUT:
  
 <​code>​ <​code>​
-#include <​ESP8266WiFi.h>​ +sudo bash -c 'echo $PIN /​dev/​si_gpio'​
-+ #include <WiFi.h>+
 </​code>​ </​code>​
  
-Placa ESP32 va folosi clientul MQTT pentru a se înregistra ca subscriber la topic-ul ''​inTopic''​ șpe laptop vom instala biblioteca ''​paho-mqtt''​ pentru a instanția un client MQTT în modul publisher. Pentru a instala ''​paho-mqtt'',​ utilizați următoarea comandă:+Listațstarea PIN-ilor cu comanda:
  
 <​code>​ <​code>​
-pip install paho-mqtt+cat /​dev/​si_gpio
 </​code>​ </​code>​
  
-Folosiți {{:iothings:​laboratoare:​mypythonmqttclient.py.txt|acest}} program Python pentru a aprinde și stinge LED-ul pe placa ESP32.+<​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>​
  
-Veți utiliza broker-ul MQTT public ​de la adresa „test.mosquitto.org”atât pentru clientul MQTT al ESP32cât și pentru clientul PythonDin acest motiv, este important să schimbați numele topicului ​pentru a nu primi mesaje ​de la alte dispozitive din Internet care utilizează același server public.+**Ex. 3** - Folosiți **suportul ​de Qemu-Monitor** pentru a citi/scrie direct memoria fizică emulată în QemuQemu-Monitor este un "​backdoor"​ ce permite interogarea structurilor folosite de Qemuprecum memoria, procesoare, device-uri, șamdDocumentaț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ă.
  
-<note important+{{:​si:​laboratoare:​qemu-monitor.png?​700|Qemu Monitor}} 
-Modificați topic-ul „inTopic” în „si-lab10-myLED-inTopic”, iar topicul „outTopic” în „si-lab10-myTemperature-outTopic”."+ 
 +Pentru a folosi Qemu-Monitor este nevoie să pornim Qemu cu următoarele argumente:​ 
 + 
 +<code
 +-monitor telnet:​127.0.0.1:​7777,​server,​nowait 
 +</​code>​ 
 + 
 +În acest moment Qemu așteaptă conexiuni de Telnet pe portul 7777. Conectați-vă la Qemu prin comanda: 
 + 
 +<​code>​ 
 +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: 
 + 
 +<​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>​ </​note>​
-===== Resources ===== 
  
-  ​* https://github.com/Tanganelli/CoAPthon +Folosind comenzile "info mtree" si "info qtree" puteți afla informații despre memorie și device-uri, printre care și PL061</​hidden>​ 
-  * https://github.com/hirotakaster/CoAP-simple-library +===== Resurse ===== 
-  * https://en.wikipedia.org/wiki/Constrained_Application_Protocol +  ​[[https://elixir.bootlin.com/linux/​latest/source| Linux Cross Reference]] 
-  * https://play.google.com/store/apps/details?​id=ch.buedev.iot_coap&​hl=en&gl=US +  * [[https://www.kernel.org/doc/html/​latest/​dev-tools/​kgdb.html| Kernel Debugger Internals]] 
-  * https://www.digikey.de/en/maker/blogs/2019/how-to-use-mqtt-with-the-raspberry-pi+ 
 +<​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/10.1704540781.txt.gz · Last modified: 2024/01/06 13:33 by laura.ruse
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