This is an old revision of the document!
Pentru rezolvarea laboratorului, vom lucra în același director din care pornim mașina virtuală (~/so2/linux/tools/labs
).
Pașii de rezolvare sunt următorii:
Pentru rezolvarea laboratorului trebuie sa activam suportul de netfilter din kernel. In meniul deschis cu make menuconfig
activati optiunea Networking support/Networking options/Network packet filtering framework (Netfilter)
.
Scheletul de laborator este generat din sursele din directorul tools/labs/templates
. Putem genera scheletele pentru toate laboratoarele folosind următoarea comanda:
tools/labs $ make skels
Pentru a genera scheletul pentru un singur laborator, vom folosi variabila de mediu LABS
:
tools/labs $ make clean tools/labs $ LABS=<lab name> make skels
device_model
.
Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea <lab_name>/<task_name>
variabilei LABS
.
tools/labs/skels
.
Comanda make build
compilează toate modulele din directorul skels
.
student@eg106:~/so2/linux/tools/labs$ make build echo "# autogenerated, do not edit " > skels/Kbuild echo "ccflags-y += -Wno-unused-function -Wno-unused-label -Wno-unused-variable " >> skels/Kbuild for i in ./device_model; do echo "obj-m += $i/" >> skels/Kbuild; done ...
Putem copia modulele generate pe mașina virtuală folosind target-ul copy
al comenzii make, atunci când mașina virtuală este oprită.
student@eg106:~/so2/linux/tools/labs$ make copy student@eg106:~/so2/linux/tools/labs$ make boot
Alternativ, putem copia fișierele prin scp
, pentru e evita repornirea mașinii virtuale. Pentru detalii despre folosirea interacțiunea prin rețea cu mașina virtuală citiți Interacțiunea cu mașina virtuală.
Modulele generate sunt copiate pe mașina virtuală în directorul /home/root/skels/<lab_name>
.
root@qemux86:~/skels/device_model# ls bex.ko bex_misc.ko
După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU (sau în minicom
) pentru a încărca și descărca modulul de kernel:
root@qemux86:~# insmod skels/<lab_name>/<task_name>/<module_name>.ko root@qemux86:~# rmmod skels/<lab_name>/<task_name>/<module_name>.ko
Ctrl+Shift+t
. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:
~/so2/linux/tools/labs
.~/so2/linux/
cu sursele nucleului unde putem folosi Vim și cscope pentru parcurgerea codului sursă.student@eg106-pc:~$ netcat -lup 6666
git pull --rebase
in directorul ~/so2/linux
, pentru a obține ultima versiune a scheletului de laborator.
Vă mulțumim! Găsiți definițiile următoarelor simboluri în nucleul Linux:
dev_name
, dev_set_name
.pnp_device_probe
, pnp_bus_match
, pnp_register_driver
și variabila pnp_bus_type
.Apreciem opinia voastră legată de activitățile cursului de SO2. Ne ajută să îmbunătățim cursul și să facem materia cât mai accesibilă și interesantă. Pentru această vă rugăm să completați formularul de feedback de pe cs.curs.pub.ro (trebuie să fiți autentificați și înrolați în cadrul cursului). Vă mulțumim!
În prima parte ne propunem să ne familiarizăm cu modelul Linux Device Model
și reflectarea acestuia în userspace prin intermediul sistemului virtual de fișiere sysfs
. Vom analiza implementarea magistralelor și modul în care device driverele se conectează la o magistrală. Pentru început, vom lucra cu o magistrală virtuală, definită în modulul mybus.ko
din subdirectorul virtual_bus/mybus/
la care vom atașa driverul mydriver.ko
din subdirectorul virtual_bus/mydriver
.
my_bus_type
(mybus.c): variabilă globală pentru tipul de magistralămy_bus_device
(mybus.c): variabilă globală pentru dispozitivul magistraleimy_device
(virtual_bus.h): structură folosită pentru dispozitivele ce se conectează la magistralămy_driver
(virtual_bus.h): structură folosită pentru driverul care lucrează cu dispozitivele ce se conectează la magistralădev_data
(mydriver.c): variabilă globală ce menţine datele necesare driverului
Intrați în directorul virtual_bus/mybus
, unde găsiți implementarea unei magistrale mybus
, așa cum este descrisă în laborator. Analizați conținutul fișierelor mybus/mybus.c
și include/virtual_bus.h
.
Observaţi că la încărcarea modulului se înregistrează atât o structură bus_type, reprezentând tipul de magistrală, cât și o structură device, reprezentând dispozitivul efectiv al magistralei.
Compilați și inserați modulul. Verificaţi că tipul de magistrală apare în /sys/bus, iar dispozitivul în /sys/devices. Scoateți modulul şi observaţi că intrările din sysfs sunt înlăturate.
Modificați sursa astfel încât intrările pentru magistrală și dispozitivul asociat să fie virtualbus
, respectiv virtualbus0
.
Pentru verificare trebuie să se fi creat pe mașina virtuală în /sys
intrările aferente magistralei (virtualbus
) și dispozitivului părinte (virtualbus0
):
# ls /sys/bus/virtualbus/ # ls /sys/devices/virtualbus0/
Intrați în directorul virtual_bus/mydriver
, unde găsiți implementarea unui device driver de tip caracter.
Modificați sursa astfel încât să respecte modelul Linux Device Model
. Dispozitivul echo
se va conecta la magistrala virtualbus de la exercițiul anterior, având un driver asociat echo
. Numele driverului și al dispozitivului trebuie să fie identice.
Urmăriți comentariile TODO 2
din cod și exemplele de înregistrare dispozitiv/driver din laborator: înregistrare dispozitiv, înregistrare driver.
Pentru a vă conecta la magistrala virtualbus
, va trebui să folosiți funcțiile și tipurile de driver/device exportate de aceasta.
În structura struct my_device_data
adăugați un câmp de tipul struct my_device
(tip de date definit în include/virtual_bus.h
). Câmpurile structurii my_device
vor fi inițializate în funcția my_init
.
Definiți o variabilă de tipul struct my_driver
şi inițializați pentru aceasta modulul şi numele driver-ului. Înregistraţi driverul în funcţia my_init
.
struct my_device
și struct my_driver
în funcția de init / exit a modulului. Pentru aceasta folosiți funcțiile my_register_driver
/ my_unregister_driver
, respectiv my_register_device
/ my_unregister_device
, definite în fișierul mybus.c
.
bus_find_device
folosiți o expresie de forma mydriver.driver.bus
.
În cadrul funcţiei my_init
, iniţializaţi dispozitivul şi înregistraţi-l. Vor trebui completate cel puțin câmpurile name
și driver
.
De asemenea, puteți stoca un back pointer către struct my_device_data
în câmpul dev->p->driver_data
. Acest câmp e util pentru a putea accesa datele private (my_device_data
) ale dispozitivului și din locurile în care aveți acces doar la structura generică struct device. Fiind un câmp din datele private ale dispozitivului, pentru a accesa dev->p->driver_data
e recomandat să folosiți funcțiile (interfața) dev_set_drvdata, dev_get_drvdata.
Compilaţi modulul şi copiaţi-l pe maşina virtuală, împreună cu modulul de la exerciţiul anterior.
mybus.ko
) înaintea inserării modulului curent.
Pentru testare urmăriți intrările de dispozitiv (echo
) și de driver (echo
) în /sys
pe mașina virtuală:
# ls /sys/bus/virtualbus/devices/ # ls /sys/bus/virtualbus/drivers/ # ls /sys/devices/virtualbus0/
La sfârșitul inițilizării modulului mydriver
(TODO 2.1), verificați dacă device-ul a fost atașat la magistrală. Pornind de la magistrala asociată, parcurgeți device-urile atașate și căutați-l pe cel cu numele echo
. Afișați numele device-ului, dacă acesta a fost găsit.
NULL
ca al doilea argument al funcției.
Obțineți structura de tip magistrală (struct bus_type
) printr-o construcție de forma my_driver.driver.bus
.
Numele device-ului întors de funcția bus_find_device_by_name se poate obține cu funcția dev_name.
Pentru verificare la inserarea modulului se va afișa mesaj cu numele dispozitivului.
Extindeți driverul echo de la exercițiul anterior prin adăugarea unui atribut myattr
pentru dispozitivul creat, ce va conține majorul și minorul dispozitivului (major:minor
). Acest atribut va fi expus prin interfaţa sysfs
din directorul device-ului echo
.
Urmăriți comentariile TODO 3
din cod.
dev_attr_##_name
, unde ##_name
este numele atributului.
Pentru folosirea macro-ului va trebui să specificaţi, în această ordine:
0444
.NULL
.
În funcţia show, puteți folosi macrodefinițiile MAJOR și MINOR pentru aflarea majorului și minorului. Aceste funcții primesc ca argument câmpul dev
al structurii struct cdev
. Câmpul de tipul struct cdev
îl găsiți în structura de tipul struct my_device_data
. Pentru a obține structura de tipul struct my_device_data
, atunci când știți adresa unei variabile de tipul struct device
, puteți folosi funcția dev_get_drvdata.
Țineți cont să adăugați/eliminați atributul la inițializarea/dezactivarea modulului cu ajutorul funcțiilor device_create_file, device_remove_file.
Parcurgeți secțiunile Magistrale și Dispozitive din laborator.
Pentru testare rulați comanda:
# cat /sys/devices/virtualbus0/echo/myattr
Această comanda va conduce la rularea funcției my_show
.
Intrați în directorul parallel
, unde găsiți implementarea unui driver simplu pentru portul paralel.
Analizați conținutul fișierului parallel.c
. Modificați sursa astfel încât să respecte modelul Linux Device Model
și plug and play
. Dispozitivul se va conecta la magistrala PNP
.
Pentru a fi un driver plug and play
, inițializarea dispozitivelor trebuie făcută în momentul în care acestea apar în sistem (la execuția funcției parallel_pnp_probe
), iar deînregistrarea în momentul în care dispar din sistem (la execuția funcției parallel_pnp_remove
).
Recitiți secțiunile Magistrala PNP și Operații plug and play din laborator.
register_parallel_dev
respectiv unregister_parallel_dev
definite în cadrul scheletului de cod de laborator.
Pentru verificare, urmăriți conținutul din directorul aferent din sysfs:
# ls /sys/bus/pnp/drivers/parallel
Pornind de la modulul anterior, adăugați informațiile pentru o nouă clasă parclass
, de care aparține modulul paralel
.
Structura class trebuie inițializată odată cu resursele driver-ului (la execuția funcției parallel_init
) și eliminată la ieșirea driverului (parallel_exit
). Va trebui să folosiţi funcţiile class_register şi class_unregister.
În plus, pentru fiecare dispozitiv trebuie inițializată o structură device și înregistrată cu device_create (la execuția funcției parallel_pnp_probe
). La apelul funcţiei device_create, folosiți pentru al patrulea parametru construcția &dev->dev
. Este câmpul de tipul struct device al structurii struct pnp_dev.
La înlăturarea dispozitivului (în funcţia parallel_pnp_remove
) folosiți funcția device_destroy.
Recitiți secțiunea Clase din laborator.
Pentru verificare, urmăriți conținutul directorului aferent din sysfs:
# ls /sys/class/parclass/
În subdirectorul usb_extra
găsiți o implementare minimală a unui driver USB. Analizați sursa usb.c
și observați implementarea mecanismului de Hotplug
si conectarea la magistrala USB
. Observați asemănările cu interfața dintre magistralei PNP
studiată în laborator și driverele asociate: struct usb_driver
/ struct pnp_driver
, implementarea funcției probe
(skel_probe
), tabela skel_table
cu care se inițializează câmpul id_table
pentru a identifica device-urile compatibile, etc.
Conectați un device USB pe mașina fizică. Apelați dmesg
sau lsusb
pentru a identifica vendorId-ul și productId-ul device-ul atașat.
usb 3-2: New USB device found, idVendor=1e3d, idProduct=6025
Modificați codul din usb.c
pentru a crea un driver compatibil cu device-ul vostru. Compilați modulul și inserați-l pe mașina fizică (sau pe o mașină virtuală cu udev și acces la USB-ul gazdei). Reconectați device-ul USB. Ce observați la rularea comenzii dmesg?
Cel mai probabil device-ul va fi preluat de alt driver usb din sistem (e.x. usb_storage). În acest caz, puteți descărca temporar modulul concurent (rmmod usb_storage). Dacă device-ul se conectează la driverului usb.ko
, puteți observa mesajul “USB Skeleton device now attached to USBSkel-0”, mesaj afișat de funcția skel_probe
.
Descărcați modulul. Creați o regulă udev
, care la identificarea dispozitivului (după ATTRS{idVendor}
și ATTRS{idProduct}
), să încarce driverul usb.ko
. Recitiți secțiunea Hotplug din laborator. Cu modulul descărcat, reconectați device-ul USB. Ce observați la rularea comenzilor dmesg
și lsmod
?