Differences

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

Link to this comparison view

si:laboratoare:06 [2023/11/12 21:20]
florin.stancu
si:laboratoare:06 [2024/11/11 12:43] (current)
florin.stancu [Exerciții]
Line 1: Line 1:
-====== Laboratorul 06. Root filesystem și servicii de sistem ​======+====== Laboratorul 06. The embedded boot process ​======
  
-===== Root file system =====+Atunci când un microprocesor primește semnalul de reset (prin alimentare ori transmiterea unor comenzi / întreruperi interne etc.), acesta începe să ruleze un program inițial numit bootloader. 
 +Sistemele moderne folosesc un proces de boot multi-stagiu,​ însă primul este mereu încărcat dintr-un ROM (Read Only Memory) care, de cele mai multe ori, este integrat în același chip.
  
-Pentru ca sistemul să fie inițializat corect după pornirea kernel-ului,​ este necesar ca toate script-urile și executabilele necesare pentru a porni daemon-ul ​de inițializare,​ //init//, și restul proceselor //​user-space//,​ să existe în anumite locații în sistemul de fișiere. Acest sistem minimal de fișiere necesar la inițializare poartă numele de **root file system** sau **rootfs**. În cazul sistemelor Unix, //​rootfs//​-ul are ca rădăcină directorul ''/''​ și înglobează o serie de directoare ce contin restul de fișiere necesare. De obicei, în directorul ''/''​ nu se află niciun fișier, doar subdirectoare. Aceste subdirectoare sunt organizate în funcție de fișierele pe care le conțin:+==== Procesul ​de boot standard ARM ====
  
-  * ''/​bin''​ - conține programe și comenzi necesare la inițializare ce pot fi folosite apoi de către un utilizator neprivilegiat în timpul unei sesiuni +Arhitectura ARMv8 propune următoarea structură:
-  * ''/​sbin''​ - conține programe asemănătoare cu cele din /bin ca utilitate, dar care pot fi utilizate în general doar de utilizatorul privilegiat //root// +
-  * ''/​etc''​ - conține fișierele de configurare specifice sistemului +
-  * ''/​lib''​ - conține bibliotecile folosite de programele din //rootfs// (cele din ''/​bin''​ și ''/​sbin''​) +
-  * ''/​dev''​ - conține referințe la toate dispozitivele periferice; aceste referințe sunt în general fișiere speciale +
-  * ''/​boot''​ - conține fișierele necesare bootării și imaginea kernel-ului;​ este posibil ca acestea să fie păstrate pe un sistem de fișiere separat de //​rootfs//​ +
-  * ''/​tmp''​ - conține fișiere temporare și este de obicei curățat la repornirea sistemului +
-  * ''/​opt''​ - conține în principal aplicațiile third-party +
-  * ''/​proc'',​ ''/​usr'',​ ''/​mnt'',​ ''/​var'',​ ''/​home''​ - sunt directoare care, în general, reprezintă puncte în care se pot monta alte sisteme de fișiere pentru a stoca log-uri, biblioteci și alte aplicații și programe folosite de către utilizatori.+
  
-<​note>​ +{{:si:laboratoare:​arm_booting_process.png?700}}
-Organizarea unui rootfs Linux în termeni de directoare este bine definită de standardul ierarhiei sistemului de fișiere[[https://​refspecs.linuxfoundation.org/​fhs.shtml|Filesystem Hierarchy Standard]], ultimul standard FHS 3.0 fiind definit in 2015. +
-</​note>​+
  
-Standardul Unix recomandă ca //rootfs//-ul să aibe dimensiuni relativ mici, deoarece orice problemă sau corupere a acestuia poate împiedica inițializarea corectă a sistemului. ​Dacă, totuși, rootfs-ul devine corupt, există modalitățprin care poate fi remediat. O soluție des folosită este de a monta acest sistem de fișiere într-un alt sistem, funcțional,​ pentru a putea fi explorat și verificat.+Se observă numărul mare de pași decât metoda naivă pe care sistemul trebuie să-i realizeze până ​să pornească kernelul ​sistemului ​de operare. 
 +Acest lucru se datorează punerii accentului pe securitatea soluțiilor incorporate (prin tehnologia ARM TrustZone, de care doar vom menționa scurt, pe Raspberry PI nefiind implementată în totalitate),​ însă necesitatea a 3 stagii diferite ​este dată de un motiv simplu: memoria disponibilă în diferitele momente ale procesului.
  
-În cazul sistemelor embeddedde multe ori spațiul de stocare pe care îl avem la dispoziție este deja limitat: < 32MBDe cele mai multe ori, o bună parte din acest spațiu este ocupat ​de imaginea kernel-uluiDe aceeaîn majoritatea cazurilor, //rootfs//-ul pentru aceste sisteme ​este fie compus doar din minimul de programe necesar pentru ​funcționare, fie este stocat pe un server accesibil prin rețea ​și montat ​de către kernel la inițializare ​(e.g.folosind protocolul NFS).+Așadarprima dată se începe prin rularea primului stagiu, ''​BL1'',​ stocat în memoria ROMAcesta va încărcade pe memorie flash externă (de obicei, prin SPI: eMMC sau SD), următoarele stagiiDintre acestea''​BL2''​ estede obicei, un firmware foarte mic (//10-100KB//) ce este încărcat în memoria SRAM a SOC-ului (care, uneori, ​funcționează ​și pe post de cache) și care, mai departe, ​inițializează toate perifericele chip-ului ​(printre carefoare important este DRAM-ul -- folosit de următoarele stagii cu consum ridicat de RAM!).
  
-===== Pseudo Filesystems =====+Stagiile ''​BL3x''​ devin opționale, totul depinzând dacă ''​BL2''​ conține funcționalitatea de a porni și Kernel-ul de Linux și este suficient de configurabil pentru a putea acoperi o mare parte a cazurilor de utilizare ale sistemului (ceea ce este adevărat pentru RPI, însă deseori nu pentru celelalte SoC-uri) sau dezvoltatorul software dorește să beneficieze de funcționalități avansate de boot (aici, ''​BL31''​ poate oferi partiționare A/B cu toleranță la defecte, actualizări la distanță etc.) sau încărcarea unui sistem de operare securizat (Trusted OS, la pasul ''​BL32''​).
  
-Un kernel ​Linux expune mai multe pseudo-sisteme ​de fișiereprecum ''/​dev''​ (unde există noduri pentru ​toate dispozitivele detectate), ''/​proc''​ sau ''/​sys''​. Unele aplicații ce gestionează sistemul ​de operare ​au nevoie ca aceste mountpointuri ​să existe, ele fiind folosite pentru a interfața cu kernelul (mai ales în sisteme incorporate,​unde programele trebuie ​să comunice cu hardware-ul).+În final, ultimul pas al bootloaderului va fi să citească configurația (de pe o partiție de boot sau dintr-o altă memorie ROM re-programabilă) și componentele de rulare (kernel, initrd, device tree blob -- de cele mai multe ori, toate 3 fiind necesareale sistemului ​de operaresă încarce ​în RAM și apoi să paseze execuția CPU-ului către kernel.
  
-==== Sistemul virtual proc ====+==== Procesul de boot al Raspberry PI ====
  
-Acest sistem de fișiere există ​de când există și Linux-ul și permite expunerea de statistici despre procesele rulate în sistem dar și ajustarea la runtime a diverșilor parametrii ce implică managementul proceselor, ale memoriei etc. Este utilizat de majoritatea aplicațiilor standard. Aplicații precum ​**ps** și **top** nu pot funcționa fără acest filesystem.+Deși procesul ​de boot diferă între //Raspberry PI// versiuni mai vechi sau egale cu **3** (ce folosește SoC-ul ''​BCM2837''​) ​și cele **după 4** (cu ''​BCM2711'', ​și, pe viitor, v5), stagiile se încadrează în arhitectura propusă de ARM.
  
-Pentru ​monta **proc** se poate folosi comanda: <​code>​ mount -t proc nodev /proc </​code>​+Toate versiunile procesoarelor Broadcom folosite la RPI încep prin rularea stagiului ''​BL1''​ pe microcontrollerul de gestionare ​GPU-ul integrat (da, pe bune!), care, mai departe, inițializează memoria cache, interfața SPI (pentru accesarea memoriei flash din eMMC card SD) și, folosind o bibliotecă incorporată de citire de pe partiții FAT32, scanează după existența firmware-ului pentru următorul stagiu, ''​BL2'',​ pe care îl va încărca în cache-ul L2 al procesorului (DRAM-ul încă nu a fost inițializat).
  
-Printre conținutul aflat în **proc** putem găsi: +Ordinea de scanare a perifericelor pentru continuarea procesului de boot (e.g.SD Card eMMCextenal SPIUSB Mass StorageLAN boot) diferă în funcție de starea unor GPIO-uri sau unor regiștri OTP (One Time Programmable).
-  * ///​proc/​interrupts, /proc/iomem/​proc/​cpuinfo//:​ ce conțin informații specifice de device +
-  * ///​proc/<​pid>​/​proc/​3840/​cmdline//:​ conține detalii despre fișierele deschise de procesutilizarea memoriei, procesorului etc. +
-  * ///​proc/​cmdline//:​ conține linia de comandă cu care fost pornită imaginea kernel-ului +
-  * ///​proc/​sys//:​ conține ​ fișiere care pot fi scrise pentru a ajusta parametrii ai kernel-ului. Poartă numele de **sysctl**. Mai multe detalii despre asta aici: [[https://​www.kernel.org/​doc/​html/​latest/​admin-guide/​sysctl/​|documentație sysctl]].+
  
-==== Sistemul virtual sys ====+Așadar, un Raspberry PI are nevoie ca dispozitivul de pe care se efectuează bootarea să conțină o primă partiție FAT32 cu cel puțin firmware-urile de inițializare a platformei (''​bootcode.bin'',​ ''​start*.elf'',​ ''​config.txt''​ și altele câteva ce depind de modelul efectiv).
  
-Permite reprezentarea în userspace a viziunii pe care o are kernel-ul asupra magistralelor,​ dispozitivelor și driverelor din sistemEste util pentru diverse aplicații din userspace care trebuie ​să enumere ​și să interogheze hardware-ul disponibil, ​de exemplu **udev** sau **mdev**:+<note important>​ 
 +Fiecare microprocesor ​are propriile convenții de stabilire a adreselor de încărcare a stagiului secundar. 
 +Cei de la Broadcom au ales să folosească partiții FAT32, însă majoritatea producătorilor de SoC-ul incorporate cu ARM (e.g., Allwinner, NXP) preferă ​să încarce o imagine specială de la un anumit offset al disk-ului (e.g., la adresa ''​32KB''​ de la începutul cardului SD). De aici vine și recomandarea de a crea prima partiție abia începând cu offset-ul de ''​1MB''​. 
 +</​note>​
  
-<​code>​ls ​/sys/ +Pentru [[https://www.raspberrypi.com/documentation/​computers/​raspberry-pi.html#​boot-sequence|Raspberry PI 3]], se va încărca fișierul ''​bootcode.bin''​ ca stagiu secundar, care, după inițializarea RAM-ului, va citi și încărca următorul stagiu (care este, de obicei, sistemul de operare propriu-zis,​ însă se poate interpune un alt bootloader -- ''​BL31'',​ e.g., [[https://​www.denx.de/​project/​u-boot/​|U-Boot]]).
-block bus class dev devices firmware fs kernel module power +
-</code>+
  
-===== Utilitare folosite pentru crearea ​inspecția unui RootFS =====+La versiunile de Raspberry PI ''>​=4'',​ ''​BL2''​ poate fi încărcat doar dintr-un EEPROM prin interfață SPI (și NU de pe SD eMMC-ul extern -- însă există procedură de recovery în caz că se strică ceva), fapt ce ușurează aplicațiile care făceau network boot. De asemenea, firmware-ul este open-source (ceea ce nu era adevărat până acum).
  
-Uneori vom fi în situația în care nu avem acces la un server și imaginea de care dispunem ​pentru ​sistemul nostru nu este satisfăcătoare. În aceste condiții va trebui să ne creăm propriul ​//rootfs// pe care să îl scriem în memoria sistemului embedded.+Mai departe, //Secondary Program Loader//-ul va încărca firmware-uri adiționale ​pentru ​GPU (pentru a putea afișa text prin HDMI), va analiza conținutul fișierului ''​config.txt''​ și va pune în aplicare procedurile configurate (încărcarea în memorie a fișierelor de kernel ​device tree initramfs).
  
-Imaginea recomandată pentru RaspberryPi,​ Raspbian, conține două partiții: o partiție ''​FAT''​ folosită ​pentru boot și o partiție ''​ext4''​ pentru //rootfs//. Fiecare dintre aceste partiții începe de la un anumit offset în cadrul imaginii. Atunci când dorim să creăm o imagine nouă pentru RaspberryPi,​ trebuie să ne asigurăm că respectăm aceste partiții și formatele lor. Pașii pe care trebuie să îi urmăm sunt:+==== Componentele ​pentru boot ale Linux ====
  
-  * Stabilirea dimensiunii imaginii și inițializarea acesteia cu zero-uri +După cum fost menționat mai suspentru a porni un sistem de operare pe bază de Linux se folosesc, de cele mai multe ori, 3 componente:
-  * Crearea tabelei de partiții și celor două partiții necesare +
-  * Formatarea partițiilor cu formatul corespunzător +
-  * Pentru popularea rootfs-uluiputem fie să montam partiția și să copiem manual directoarele și fișierelefie putem să copiem o partiție întreagă ​de pe o altă imagine+
  
-Pentru fiecare dintre acești pași există utilitare ce ne ajută să realizăm operațiile necesare.+  - **Imaginea Kernel-ului** (''​kernel*.img''​),​ ce conține codul executabil al nucleului Linux; 
 +  - **Device Tree Blob-ul** (''​*.dtb''​):​ conține descrierea ​și configurația tuturor componentelor specifice platformei hardware (i.e., pentru un anumit model + versiune a unei plăci de bază), însă se pot codifica ​și anumite setări și meta-informații pentru software (e.g., kernel command line, partiția rădăcină etc.); 
 +  - **RamDisk inițial** (''​initrd*.img''​):​ opțional, poate conține o imagine a unui sistem de fișiere minimalist (max. câțiva zeci de MB) cu module și scripturi ​necesare ​pentru a monta sistemul de fișiere rădăcină (e.g., dacă se dorește montarea unui sistem la distanță, trebuie mai întâi să se conecteze la rețea și să primească IP prin DHCP).
  
-==== dd ====+Bootloaderul (ori ''​BL2''​-ul integrat, ori ''​BL31''​ -- dacă a fost inclus) va încărca aceste fișiere în DRAM-ul sistemului și va completa anumiți regiștri (cu pointerii la locația de încărcare a tuturor componentelor necesare) și va executa o instrucțiune de //branch// pentru a lansa kernelul în execuție, toate în condordanță cu [[https://​www.kernel.org/​doc/​html//​v5.8/​arm64/​booting.html|protocolul definit de kernel pentru arhitectura dată]].
  
-Pentru copierea, ​și eventual convertirea,​ unui fișier, la nivel de byte, se poate folosit utilitarul //dd//.. Folosind //dd//, putem de asemenea genera ​fișiere ​de anumite dimensiuni ​și le putem stabili conținutul. În cazul nostru, ​//dd// este util pentru a inițializa o imagine ​și pentru a copia în acea imagine conținutul care ne interesează. Dintre parametrii lui //dd//, cei mai des utilizați sunt:+Obiectivul final al componentelor de boot Linux va fi să caute și să monteze sistemul ​de fișiere ​rădăcină (**roofs**-ul) ce conține toată configurația ​și programele din //user-space//, de unde se va lansa procesul ''​init''​ care va continua prin pornirea serviciilor predefinite (care pot, la rândul lor, să inițializeze dispozitive hardware noi și să ruleze procese de automatizare).
  
-  * ''​if'' ​fișierul de intrare; dacă nu se specifică acest parametru, se va citi de la //standard input// +==== U-Boot ====
-  * ''​of''​ - fișierul de ieșire; ca și la ''​if'',​ dacă nu este specificat, se scrie la //standard output// +
-  * ''​count''​ - numărul de blocuri de input ce vor fi copiate +
-  * ''​bs''​ - numărul de bytes dintr-un bloc+
  
-<note tip> +U-boot este probabil cel mai popular bootloader open-source folosit atât în lumea embedded / IoT / industrial, cât și în platformele mobile (majoritatea telefoanelor pe Android).
-Un exemplu de utilizare a lui ''​dd''​ pentru a inițializa cu zerouri un fișier de o dimensiune exactă:+
  
-<code shell> +Acesta este scris în C și folosește KConfig (as expected) pentru personalizarea funcționalităților incluse pentru a se putea încadra în cerințele restrictive de memorie a anumitor sisteme. 
-$ dd if=/dev/zero of=<​file>​ bs=1M count=2048 +De asemenea, U-Boot are implementată o cantitate vastă de drivere necesare pentru a interfața cu cât mai multe periferice de pe care să se încarce sistemul de operare (SPI, MMC, USB devices, SATA M.2 PCI-E, chiar și Rețea, prin PXE + TFTP) și conține chiar și un mini-limbaj de scripting (similar bash, însă compilat în binar) ce permite implementarea de proceduri avansate de boot cu redundanță și failover (necesare sistemelor realtime).
-</​code>​+
  
-Observați valorile lui ''​count''​ si ''​bs''​. Se vor copia în total 2048 de blocuri ​de câte 1MB fiecarerezultând ​dimensiune ​de 2GB. Fișierul de intrare ''/​dev/​zero''​ este un fișier special din care se pot citi oricâte caractere ASCII NUL (0x00). +Noi vom folosi un U-Boot la laboratoare pentru a putea scrie SD card-ul direct ​de pe PC, prin conectarea Raspberry PI4-ului direct la PC prin USB OTG (fără a utiliza un cititor extern ​de carduri SD!)cât și pentru a avea vizibilitate mult mai bună a procesului ​de boot Linux.
-</​note>​+
  
-==== parted ​====+===== Exerciții =====
  
-Pentru manipularea tabelei de partiții (crearea sau ștergerea partițiilor) se poate folosi ​utilitarul //parted//. Acesta recunoaște ​și poate manipula multiple formate de partiții: DOS / SUN / GNU / GPT etc. Utilitarul primește ca parametru device-ul a cărui tabelă de partiții dorim să o modificămUn exemplu de apel este:+<​note>​ 
 +**În laborator, vom folosi ​echipamente Raspberry PI 4!** conectate prin USB Type-C ​și un adaptor UART la USB pentru vizualizarea consolei dispozitivului (din păcate, nu dispunem ​de suficiente monitoare HDMI în laborator + cabluri adaptoare).
  
-<code shell> +Înainte de a începe exercițiile,​ asigurați-vă că aveți cel puțin 10GB de storage disponibili în mașină virtuala de laborator. 
-$ parted /​dev/<​device>​ +</note>
-</code>+
  
-Rezultatul acestei comenzi este un prompt nou (similar cu //fdisk//) în care putem folosi tasta ''​m''​ pentru a afișa un meniu cu opțiunile disponibileOpțiuni utile pentru crearea șștergerea de partiții sunt: +**0.** Descărcați [[https://​github.com/​cs-pub-ro/​SI-rpi-debian-scripts/​releases|de aici arhivă unui sistem ​de fișiere folosit ca referință laborator]] pentru RPI4 + imaginea ​partiției ''​rpi-boot.img''​ (utilă în caz că se strică bootloaderul).
-  +
-  * ''​print''​ (sau simplu, ''​p''​) ​afișează tabela curentă; +
-  * ''​mklabel <​type>'' ​șterge tot și crează ​tabelă nouă de partiții (type poate fi ''​msdos'',​ ''​gpt''​ etc.); +
-  * ''​mkpart [type[fstype<​START>​ <​END>''​ - crează o partiție nouă (type poate fi ''​primary''​ / ''​logical''​ / ''​extended''​);​ singurii parametrii obligatorii sunt cele 2 offseturi;​ +
-  * ''​set <​partition>​ <​flag>​ <​state>​''​ - setează un flag unei partiții;​ +
-  * ''​rm <​NUMBER>''​ - șterge o partiție;​ +
-  * ''​quit''​ - iese din program ​(toate comenzile salvează automat).+
  
-Trebuie reținut că, în mod normal //parted// operează cu sectoare ​și nu cu octeți. Așadareste de preferat ​să punem unitatea ​de măsură (KM, G etc.) ca sufix la orice offset cerut de comenzi ​(e.g.''​120MB''​).+  * Dezarhivați arhiva într-un subdirector (prin ''​tar'',​ folosiți argumentul ''​-C''​ pentru a preciza directorul destinație,​ însă va trebui să îl creați înainte). **Atenție**:​ folosiți contul de ''​root''​deoarece dorim să dezarhivăm fișierele ​și să păstrăm permisiunile originale (imaginea este un root filesystem de Linux pre-instalat!);​ 
 +  * Inspectați căile ''/​boot''​ (rețineți / copiați într-un fișier text output-ule util de comparat mai încolo). 
 +  * Apoi, ne vom pregăti ​să pornim Raspberry PI 4! 
 +  * Asigurați-vă că firele ce conectează Raspberry PI-ul la adaptorul ​de serială nu sunt ieșite. Dacă da[[https://​pinout.xyz/​|conectați-le corespunzător]] (UART RX (alb) / TX (verde) la TX (pin 8)/RX (pin 10) al RPI-ului!) + chemați asistentul să verifice! 
 +  * TLDR: începeți cu negru (GND) la pin 6 (al treilea ​de pe margine), apoi imediat lângă ​(pe același rând)firele alb + verde (în această ordine); **firul roșu SĂ RĂMÂNĂ NECONECTAT**! ​
  
-<​note>​ +<​note ​warning
-  Dimensiunea standard a unui sector de disc este ''​512 bytes''​. Aceasta poate fi schimbată folosind opțiunea ''​-b''​. +Pentru orice eventualitate, ​**vă rugăm să chemați asistentul înainte ​de a alimenta dispozitivul ​la laptop!**
-  ​Există două tipuri de partiții: ''​primary''​ ș''​extended''​. Un device ce folosește sistemul ​de partiții ''​DOS''​ nu poate avea mai mult de 4 partiții primare (la GPT s-au eliminat aceste restricții). +
-  ​Exemplu de creare a celor 2 partiții necesare pentru o imagine bootabilă Raspberry PI:<​code>​ +
-(parted) mklabel msdos +
-(parted) mkpart primary fat32 1MiB 120MiB +
-(parted) mkpart primary ext4 120MiB 100% +
-(parted) print +
-(parted) quit +
-</​code>​ +
-  ​Observați, în exemplu anterior, la ultima partiție s-a folosit o unitate specială, procentul, ceea ce înseamnă că ultimei partiții i s-a repartizat tot spațiul rămas.+
 </​note>​ </​note>​
  
-==== losetup ====+  * În final, înainte de a-l alimenta, conectați-vă adaptorul serial la USB în laptop, faceți USB passthrough la mașina virtuală (dacă este cazul) și porniți programul preferat de consolă serială (e.g., ''​picocom''​),​ folosind baud rate-ul ''​115200'':​ <​code>​ 
 +sudo dmesg | tail -20 && ls -l /​dev/​ttyUSB* 
 +picocom -b 115200 /​dev/​ttyUSB0 ​ # înlocuiți cu ce dispozitiv aveți 
 +</​code>​
  
-Dacă se lucrează cu imagini din fișiereeste util să le putem //monta// pentru a vedea ce conțin. Însă acest lucru nu se poate realiza pe Linux fără a avea un dispozitiv de tip blocDin fericireutilitarul ''​losetup''​ ne poate salva în aceste situații, el permițând conectarea unui fișier imagine la un dispozitiv bloc virtual ​de forma ''/​dev/​loop<​N>''​unde //N// este un număr din intervalul //[0-9]//.+<note important>​ 
 +Din păcate, în laborator avem ca adaptoare Serial to USB niște clone chinezești de Prolific PL2303pe care driverele oficiale Microsoft [[http://wp.brodzinski.net/2014/10/​01/​fake-pl2303-how-to-install/​|le dezactivează automat ca fiind "​counterfeit"​ (găsiți o soluție aici)]]De regulă, în mașina virtuală cu Linux nu ar trebui să contezedoar că mecanismul driverelor de a dezactiva automat dispozitivele "​copiate"​ poate interfera cu passthrough-ul USB care se face în mod normal:(( problema apare frecvent pe VirtualBox (pe VMware nu au fost raportate probleme). ​   
 +</​note>​
  
-**Atenție**''​losetup'' ​nu poate fi utilizat decât cu privilegii de **root** (e.g.folosiți **sudo** la toate comenzile de manipulare a dispozitivelor de sistem)!+**1.** Alimentați RPI-ul și urmăriți mesajele din consolă. Ar trebui să vă intre în U-Boot (preinstalat pe cardurile SD ca ''​BL31''​)apoi:
  
-Exemplu folosire: <code bash> +  * Dacă apare că rulează un proces de network boot, apăsați Ctrl+C de foarte multe ori (//yep... scriptul ​de boot face un for cu vreo 10 iterații//) pentru a-l întrerupe; 
-# conectăm fișierul la un dispozitiv loop +  * După ce vă apare prompt-ul ''​U-Boot>​ '',​ rulați comanda ''​mmc list''​. O să vă apară o listă de dispozitive MMC (ar trebui să vedeți ​cardul SD ca fiind detectat); încercați-le pe fiecare cu ''​mmc dev <​N>'' ​și apoi ''​mmc info''​ până găsiți cardul SD; 
-losetup ./path/to/your-image.bin /​dev/​loop0 +  ​* Apoi rulați comanda ''​ums mmc <N>'',​ unde ''<​N>''​ este numărul perifericului MMC. Acesta va lansa în execuție un program care emulează un Mass Storage Device pe interfața USB Type-C folosită la conectarea la laptop; 
-# scanăm dispozitivul bloc de partiții+  * Inspectați noul dispozitiv montat (nu uitați să faceți pass-through la noul device USB în mașina virtuală, dacă este cazul!). Mai precis, montați prima partiție (posibil să fie și singura) pe mașina voastră virtuală cu Linux, e.g., în /mnt. Ce fișiere există? Comparați cu partiția ''/​boot''​ a rootfs-ului.
-partprobe ​/dev/loop0 +
-ls -l /dev/loop0* +
-# ^ ar trebui să vedeți ​cele 2 partiții ale imaginii RPI: loop0p1 ​și loop0p2 +
-  +
-#  +
-</code>+
  
-**Important / NU uitați:** la finaltrebuie ​să deconectăm dispozitivul pentru a sincroniza cele scrise înapoi în fișier: ''​losetup -d /dev/​loop<​N>​''​!+<note important>​ 
 +Comanda ''​ums''​ este blocantă (poate fi întreruptă cu Ctrl+C). 
 +Dacă folosiți un soft de virtualizare (VirtualBox / VMWare)va trebui ​să faceți passthrough la acest dispozitiv (se numește "​Netchip USB Download Gadget"​). Apoi verificați prin ''​lsblk''​ / ''​blkid''​ care este dispozitivul nou apărut în VM.
  
-==== mkfs ====+Cât timp rulează, vă puteți conecta cu laptopul (prin interfața USB Type-C) la cardul SD introdus în Raspberry pentru a-l inspecta / [re]scrie (cu grijă să nu suprascrieți bootloaderul u-boot). 
 +</​note>​
  
-Crearea unei partiții nu este suficient ​pentru a putea stoca fișiere. Avem nevoie ca partiția să conțină șun sistem de fișiere. Utilitarul folosit pentru a crea (formatasisteme de fișiere Linux este //mkfs//. Parametrii pe care îi primește //mkfs// sunt device-ul ce trebuie formatat ​și tipul sistemului ​de fișiere dorit, specificat cu parametrul ''​-t''​.+**2.** Dorim să compilăm **U-Boot** local (pentru a învăța cum se face și explora ce funcționalități are) și să construim imaginea ​de boot de la zero.
  
-Spre exemplu, comanda ​<​code ​shell+  * Aveți nevoie de toolchain-ul pentru Aarch64 să fie în ''​$PATH''​ (lucru adevărat pe VM-ul de la laborator);​ 
-$ mkfs -t ext4 -L MyRootFS /dev/fd0+  * Urmați pașii: ​<​code>​ 
 +# pe Lab VM 2023 lipsește acest pachet, instalați-l: 
 +sudo apt install libssl-dev 
 +git clone --branch=v2023.07.02 https://​github.com/​u-boot/​u-boot.git 
 +cd u-boot 
 +# ne pregatim de compilare:​ 
 +export CROSS_COMPILE="​aarch64-linux-gnu-"​ 
 +# initializam configul default pentru RPI4 
 +make rpi_4_defconfig 
 +make menuconfig 
 +# aici, căutați și activați următoarele opțiuni (ambele sunt necesare!):​ 
 +# CONFIG_USB_FUNCTION_MASS_STORAGE=y 
 +# CONFIG_CMD_USB_MASS_STORAGE=y 
 +make -j4
 </​code>​ </​code>​
  
-Va crea pe device-ul //fd0// un sistem ​de fișiere ​//ext4// cu label-ul (denumire logică) ​//MyRootFS//. În Linux, utilitarul ​//mkfs// este împărțit în câte un executabil pentru fiecare tip de sistem de fișiere suportat. Pentru a le vedea pe cele disponibiledeschideți un terminal, scrieți ''​mkfs.''​ (punct la coadă!) și apasați tab.+  * O dată terminată compilarea, copiați fișierele ''​u-boot.bin''​ și ''​arch/arm/dts/bcm2711-rpi-4-b.dtb''​ din directorul lui U-Boot către ​un director nou creat (să zicem, ''​~/​rpi-boot-firmware''​). 
 +  * De asemenea, stagiul secundar (''​BL2''​) al RPI4 are nevoie ​de firmware-ul GPU-ului, ce poate fi descărcat de pe GitHub de la adresele: 
 +<​code>​ 
 +cd ~/rpi-boot-firmware/ 
 +wget "​https:​//github.com/raspberrypi/firmware/raw/master/​boot/​start4.elf" 
 +wget "​https:​//github.com/raspberrypi/firmware/​raw/​master/​boot/​fixup4.dat"​ 
 +# însă le găsiți și pe sistemul ​rădăcină referință, la calea /​boot/​firmware 
 +</​code>​ 
 +  * Ultimul lucru care lipsește este fișierul ''​config.txt''​. Strictul necesar este următorul: <​code>​ 
 +# config.txt contents to load U-Boot as BL31: 
 +arm_64bit=1 
 +kernel=u-boot.bin 
 +enable_uart=1 
 +</​code>​ 
 +  * Acum directorul ''​rpi-boot-firmware''​ ar trebui să fie identic ca imaginea referință din laborator! 
 +  * //​Notă: ​Pentru a boota Linuxeste necesar și ''​cmdline.txt''​ (folosiți-l pe cel din imaginea referință de bootloader);//​
  
-<​note>​ +<​note ​important
-Dacă etichetați sistemele de fișiere, le puteți accesa ulterior după căi speciale din ''/​dev/disk/by-label/''​! ​+Dacă doriți să testați noua imagine, ar trebui să copiați aceste ​fișiere ​pe partiția FAT32 a Raspberry PI-ului. 
 +Însă faceți asta doar dacă aveți încredere ​că nu stricațbootloaderul existent! 
 +Dacă ați pățit totuși asta și doriți să refaceți, există mai multe tehnici de recuperat, cea mai simplă fiind folosirea unui SD card reader extern și scrierea imaginii ​''​rpi-boot.img''​ descărcate la ex. 0. Alternativ, puteți scrie imaginea pe un stick USB și introduce în RPI, apoi folosiți comanda ''​ums''​ pentru a scrie cardul SD introdus în dispozitiv și apoi ''​dd''​ pentru a scrie bootloaderul referință:<​code>​ 
 +sudo dd if=~/Downloads/rpi-boot.img.bin of=/dev/​sd<​x>​ bs=4k status=progress && sudo sync 
 +</​code>​
 </​note>​ </​note>​
  
-==== mount ====+**3.** Ne dorim să instalăm sistemul de fișiere Linux referință pe cardul SD din Raspberry PI (folosind USB Mass Storage-ul prin USB Type-C).
  
-În sistemele Unix, sistemul de fișiere este unul arborescent unde directoarele pot fi considerate noduriiar fișierele frunze. Utilitarul ​//mount// ne permite ​să atașăm unui arbore existent un alt subarbore (sistem ​de fișiere). Apelată fără niciun parametruaceastă comandă va afișa toate device-urile montatelocația în care sunt montate șopțiunile cu care au fost montate.+  * Folosiți comanda ''​lsblk''​ pentru a descoperi cum se numesc device-urile (șidesigur, aveți grijă la capacitate: să nu ștergeți partiția rădăcină a OS-ului real din VM fizic)! 
 +  * Primul lucru, va trebui ​să creați o partiție ''​ext4''​ pe cardul SD, **după cea de boot, FAT32 (NU O ȘTERGEȚI)!**Putem folosi ''​fdisk''​ sau utilitarul mai modern, ''​parted''​ (tot în linia de comandă :D ): 
 +<​code>​ 
 +lsblk  # NOTAȚI CU ATENȚIE CARE E CARDUL SD DE ~16GB !!!! 
 +# probabil SDB/SDC...poate să fie sda dacă aveți ssd pe NVME și rulați Linux nativ... 
 +sudo parted /​dev/​sd<​X> ​ # înlocuiți <X>-ul 
 +# în parteddați comenzile '​print'​ pentru a vedea partițiile curente, '​help'​ pentru comenzi 
 +# creați o partiție nouă (primary, ext2 sunt setări bune) pe restul de spațiu liber (putețscrie ''​100%''​),​ apoi quit 
 +# apoi, pentru a încărca tabela nouă de partiții, folosiți comanda: 
 +sudo partprobe /​dev/​sd<​X>​ 
 +</​code>​
  
-Formatul general al comenzii este ''​mount -t <type<​device>​ <mount path>'',​ unde //<​type>//​ este acelașca la //mkfs//. De cele mai multe ori, argumentul ''​-t <​type>''​ poate fi omis, acesta fiind autodetectat ​de către kernel.+<note warning> 
 +**Mare atenție aici**: suntețpe cale de a rula operații cu potențial destructiv!
  
-===== Exerciții =====+Dacă folosiți mașina virtuală, este recomandat să faceți snapshot (riscați să ștergeți partițiile sistemului de operare real dacă nu aveți grijă!). 
 + 
 +Dacă sunteți pe un Linux în mașină fizică și nu sunteți siguri de ce faceți, întrebați un asistent dacă dați comenzile bune **ÎNAINTE DE A LE DA**! 
 +</​note>​ 
 +  * Formatați noua partiție (cea de-a doua, e.g. ''/​dev/​sdX2''​) folosind utilitarul ''​mkfs.ext4''​. Folosiți și argumentul ''​-L RPI_ROOTFS''​ pentru a-i da un label (nume). 
 +  * Copiați conținutul arhivei ''​rootfs.tar.xz''​ (descărcată de pe GitHub la începutul laboratorului) pe partiția ''​ext4''​ nou-creată:​ 
 +<​code>​ 
 +# montăm partiția în /​media/​rootfs (pe care trebuie să-l cream) 
 +sudo mkdir /​media/​rootfs 
 +sudo mount /dev/sdX2 /​media/​rootfs 
 +# TODO: mount sdX1 undeva (veți avea nevoie mai jos), creați un dir nou sau folosiți /mnt 
 +sudo tar xf rootfs.tar.xz -C /​media/​rootfs 
 +sudo sync  # sincronizează datele cacheuite în RAM către dispozitivele fizice 
 +# dacă comanda sync durează mult, NU O ÎNTRERUPEȚI!!! citiți notița 
 +# când a terminat, nu dați unmount, mai avem de copiat ceva de pe partiție!! 
 +</​code>​
  
 <​note>​ <​note>​
-**În laboratorvom folosi echipamente Raspberry PI 4!** conectate prin USB Type-și un adaptor UART la USB pentru vizualizarea consolei dispozitivului (din păcatenu dispunem ​de suficiente monitoare HDMI în laborator + cabluri adaptoare).+Cardurile SD sunt extrem de lenteiar Linux are un obicei prost de a cache-ui fișierele copate în RAM-ul local și a le scrie în background după ce returnează comenzile de dezarhivare / copiere! Rulați comanda ''​sudo sync''​care este blocantă până toate operațiile ​de scriere pe disk se vor completa.
  
-Înainte de începe exercițiileasigurați-vă că aveți cel puțin 10GB de storage disponibili în mașină virtuala ​de laborator.+Pentru ​monitoriza progresulutilizați one-liner-ul ''​watch grep -e Dirty: -e Writeback: /​proc/​meminfo''​ (așteptați până Writeback devine aproape ​de zero, iar Dirty sub 1MB).
 </​note>​ </​note>​
  
-**0.** Descărcați [[https://github.com/​cs-pub-ro/​SI-rpi-debian-scripts/​releases|de aici arhiva ​rootfs ​referință pentru laborator (v2)]] + imaginea ​partiției de boot ''​rpi-boot.img'' ​(pe care o vom clona mai târziu ​și îi vom adăuga partiție nouă pentru Linux).+  ​Așa cum a fost menționat, avem nevoie de câteva fișiere din sistemul rădăcină pentru a boota linux-ul (pe care le găsiți în ''​/media/rootfs/​boot''​ pe partiția a doua, sau în arhiva rootfs.tar.xz în directorul ​de boot), anume: 
 +    * ''​vmlinuz--*'';​ 
 +    * ''​initrd.img-*''​
 +    * //device tree blob-ul ar trebui să fie bun cel de la BL2, încărcat automat ​și de către u-boot, nu-l preluați pe acesta!//;
  
-  * Pentru cei cu Windows, descărcați ​și instalați utilitarul [[https://​www.raspberrypi.com/​software/​|Raspberry Pi Imager]]pe care îl vom folosi să scriem imaginea ​pe dispozitivul fizic.+  * NU UITAȚI: după ce procesul de copiere s-a terminat (și ''​sync''​ nu mai blochează!),​ demontați toate device-urile implicate (i.e., cele 2 partiții ale cardului SD). 
 +  * După copiere, puteți vizualiza fișierele de pe partiția FAT32 din U-Boot: ''​fatls mmc 1''​ ;)
  
-1Dorim să creăm propria imagine pe care, ulterior, s-o urcăm pe Raspberry PI și să bootăm Linux-ul (prin U-Boot șmodul ''​ums''​ -- USB Mass Storage Device):+**4.** Pornim Raspberry PI-ul, din nou (avem mare grijă la firele de la serială, să nu facă contact cu alte păale dispozitivului!).
  
-  * Imaginea de //​rpi-boot.img//​ a RaspberryPi are dimensiunea de ~150MB. Folosiți ''​dd''​ și creați o imagine de 2GB (să zicem, ''​rpi-full.img''​).  +  * Încercați, mai întâi, ​fără initramfs, apoi dați și comenzile care îl încarcă și îl dau argument la ''​booti'':​
-  * Dorim să copiem de-a întregul prima partiție din imaginea ''​rpi-boot.img''​ în cea de 2GB proaspăt creată. Putem folosi ''​dd''​ pentru asta:<​code>​ +
-dd if=rpi-boot.img of=rpi-full.img conv=notrunc +
-</​code>​ +
-  * Folosind ''​parted'',​ inspectați dacă există partiția de boot în noua imagine (cea //full//). Apoi creați pe cea de-a doua partiție (ce va conține un sistem de tip ext4). Vedeți mai sus în laborator pentru exemple de folosire a utilitarului;​ +
-  * Formatați sistemul de fișiere a celei de-a doua partiții în ext4. Pentru aceasta, va trebui, mai întâi, ​să conectați imaginea într-un dispozitiv de tip bloc prin ''​losetup''​, apoi ''​mkfs''​ pentru a o formata (urmați documentația din laborator a acestor utilitare);  +
-  * Montați noua partiție ''​ext4''​ (puteți folosi ''/​mnt''​ ca mountpoint) ​și copiați conținutul arhivei rootfs descărcate mai sus; folosiți argumentul ''​-C''​ al ''​tar''​ pentru a preciza directorul destinație,​ adică mountpointul partiției). **Atenție**:​ folosiți contul de ''​root'',​ deoarece dorim să dezarhivăm fișierele ​și să păstrăm permisiunile originale (imaginea este un root filesystem de Linux pre-instalat!):​ <code bash> +
-tar xf <​ARCHIVE_FILE>​ -C <​DEST_MOUNTPOINT>​ +
-# inspectați calea dezarhivată:​ +
-ls -l <​DEST_MOUNTPOINT>​ +
-# ar trebui să vedeți toate directoarele unui rootfs clasic: /bin, /usr, /dev, /opt etc. +
-</​code>​ +
-  * Încă nu am terminat. Montați partiția de boot (prim partiție din loop device) într-o cale (puteți crea directorul ​''​/​mnt/​boot/​rpi'' ​și să-l folosiți); apoi copiați fișierele ''/​boot/​vmlinuz-<​versiune>''​ ''/​mnt/​boot/​initrd.img-<​versiune>''​ pe prima partiție (în mountpointul acesteia, ofc); acestea sunt necesare deoarece bootloaderul RPI nu știe să citească partiții ''​ext4''​ (ci doar FAT32). +
-  * Tot pe partiția de boot, creați fișierul ''​cmdline.txt''​ și scrieți argumentele kernelului Linux:<​code>​ +
-earlycon=pl011,​mmio32,​0xfe201000 console=tty1 console=serial0,​115200 root=/​dev/​mmcblk0p2 rw rootwait fsck.repair=yes +
-</​code>​ +
-  * **Acum (atenție!):​** demontați partițiile (mai întâi pe cea de boot, montată ultima oară, apoi pe cea ext4) și deconectați dispozitivul ''​loop''​!+
  
-2. Acum că imaginea noastră este gatavrem să vedem că funcționează. Pentru aceasta, va trebui să pornim u-boot în modul ''​ums''​ (vedeți [[:si:​lab|laboratorul anterior]]) și folosim un utilitar de Raw Disk Imager (precum cel descărcat mai sus) sau ''​dd''​ (pentru cei cu Linux nativ sau cărora le funcționează USB Device Passthrough în mașina virtuală) pentru a scrie imaginea obținută anterior. +<​code>​ 
- +# inițialfără initramfs
-<note important+fatload mmc <N>:1 ${kernel_addr_r} vmlinuz-6.1.61-rpi+ 
-  * **Atenție**:​ Va trebui, mai întâi, să 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''​)! +#fatload mmc <N>:1 ${ramdisk_addr_r} initrd.img-6.1.61-rpi+ ​ # e degeabamomentan ​:D 
-  * **Notă pentru VirtualBox**:​ va trebui să folosiți argumentul ''​-P 2023''​ și ''​student@localhost:<​cale>''​ ca sursă. +# astea sunt setate deja în cmdline.txtapoi sunt încărcate în DTB (FDT) mai departe de către BL2 
-  * **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 background, această operațiune fiind mult mai lentă). +# putem explora device tree-ul din memorie ​în felul următor: 
-</note> +fdt addr ${fdt_addr} 
- +fdt print /chosen 
-3. Reporniți Raspberry PI-ul. Vom încerca ​să bootăm kernelul și rootfs-ul din prompt-ul ''​u-boot''​+# verificați valoarea bootargs ​să conțină cel puțin
- +# bootargs = "... earlycon=pl011,mmio32,0xfe201000 console=serial0,​115200 root=/​dev/​mmcblk0p2 rw rootwait"​ 
-  * Deoarece kernelul este compresatoperațiunea este un pic mai complicatătrebuind să-spunem o zonă de memorie ​folosibilă pentru extracția imaginii<​code>​ +booti ${kernel_addr_r} - ${fdt_addr} 
-fdt addr ${fdt_addr} && fdt get value bootargs /chosen bootargs+# se va plânge că nu aveți zonă de memorie ​de decompresie alocată! facem asta:
 # set decompression zone in RAM at 400MB, 64MB in size # set decompression zone in RAM at 400MB, 64MB in size
 setenv kernel_comp_addr_r 0x19000000 setenv kernel_comp_addr_r 0x19000000
 setenv kernel_comp_size 0x04000000 setenv kernel_comp_size 0x04000000
-fatload mmc 0:1 ${kernel_addr_r} vmlinuz-6.1.61-rpi++# try again:
 booti ${kernel_addr_r} - ${fdt_addr} booti ${kernel_addr_r} - ${fdt_addr}
 +# ramdisk-ul momentan nu merge folosit, dar e opțional la debian
 </​code>​ </​code>​
 +
 +<​note>​
 +Pentru a avea vizibilitate la ce face kernelul când bootează pe serială, [[https://​www.raspberrypi.com/​documentation/​computers/​configuration.html#​enabling-early-console-for-linux|am urmat pașii de aici]] să activăm earlycon pe RPI.
 +</​note>​
 +
 +  * Este posibil ca ''​root=<​device>''​ să nu fie bun, încercați să îi dați ''​root=LABEL=RPI_ROOTFS''​ sau prin UUID (depinde cum ați creat partiția și dacă ați dat label la ext4).
 +
 +<​note>​
 +Login-ul pentru rootfs este ''​pi''​ (fără parolă)!
 +</​note>​
  
 ===== Resurse ===== ===== Resurse =====
  
 +  * [[https://​www.raspberrypi.com/​documentation/​computers/​raspberry-pi.html#​boot-sequence|Procesul de boot al Raspberry PI]]
 +  * [[https://​hechao.li/​2021/​12/​20/​Boot-Raspberry-Pi-4-Using-uboot-and-Initramfs/​|Mod utilizare U-Boot pentru Raspberry PI 4]]
 +  * [[https://​github-wiki-see.page/​m/​lulu98/​projects-with-sel4/​wiki/​RPi4-Boot-Files|Explicație proces de boot al RPI 4 și fișierele firmware implicate]]
   * [[https://​github.com/​cs-pub-ro/​SI-rpi-debian-scripts|Cod sursă scripturi compilare bootloader / kernel / generare rootfs]]   * [[https://​github.com/​cs-pub-ro/​SI-rpi-debian-scripts|Cod sursă scripturi compilare bootloader / kernel / generare rootfs]]
  
  
si/laboratoare/06.1699816810.txt.gz · Last modified: 2023/11/12 21:20 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