Linux Device Model
și principalele componente ale acestuia: dispozitive, drivere, clase și magistrale.Hotplug
/ Plug and Play
.Plug and Play este o tehnologie care oferă suport pentru adăugarea și eliminarea automată a dispozitivelor în sistem. În acest mod se reduc conflictele legate de resursele folosite de acestea prin configurare automată la pornirea sistemului. Pentru a îndeplini aceste scopuri, sunt necesare următoarele caracteristici:
hotplug
).
Pentru ca un sistem să suporte plug and play trebuie ca BIOS
-ul, sistemul de operare și dispozitivul să suporte această tehnologie. Dispozitivul trebuie să dețină un ID
pe care să îl ofere driver-ului pentru identificare, iar sistemul de operare trebuie să poată să identifice aceste modificări în configurație pe măsură ce ele apar.
Dispozitive care suportă plug and play sunt: dispozitivele PCI
(plăci de rețea), USB
(tastatură, mouse, imprimantă), etc.
Înainte de versiunea 2.6, kernel-ul nu dispunea de un model unificat prin care să se obțină informații despre acesta. Din acest motiv s-a realizat un model pentru dispozitivele din Linux, Linux Device Model
.
Scopul principal al acestui model este de a menține structuri de date interne care să reflecte starea și structura sistemului. Astfel de infomații includ ce dispozitive există în sistem, în ce stare se află din punct de vedere al managementului consumului (power management), la ce magistrală sunt atașate, ce drivere au asociate, alături de structura magistralelor, dispozitivelor, driverelor din sistem.
Pentru a menține aceste informații, kernel-ul folosește următoarele entități:
Kernel-ul oferă o reprezentare a modelului său în userspace prin intermediul sistemului virtual de fișiere sysfs. Acesta este de obicei montat în directorul /sys
și conține următoarele subdirectoare:
După cum se poate observa, există o corespondență între structurile de date din kernel în cadrul modelului descris și subdirectoarele din sistemul virtual de fișiere sysfs
. Deși această asemănare poate duce la confundarea celor două concepte, ele sunt diferite. Modelul pentru dispozitive în kernel poate funcționa și fără sistemul de fișiere sysfs
, dar reciproca nu este adevărată.
Informația din sysfs
se găsește în fișiere ce conțin câte un atribut. Câteva atribute standard (reprezentate de fișiere sau directoare cu același nume) sunt următoarele:
/dev
/sys/bus/*/drivers
)Sunt disponibile și alte atribute, în funcție de magistrala și driverul folosit.
Linux Device Model
oferă o serie de structuri care să asigure interacțiunea între un dispozitiv hardware și un device driver. Întreg modelul are la bază structuri struct kobject. Cu ajutorul acestei structuri sunt construite ierarhii și sunt implementate următoarele structuri: struct bus_type, struct device și struct device_driver.
O structură kobject nu îndeplinește o funcție de una singură. O astfel de structură este de obicei integrată într-o structură mai mare. O structură kobject incorporează de fapt un set de facilități care vor fi oferite unui obiect de nivel de abstracție mai înalt în ierarhia Linux Device Model
.
Spre exemplu, structura cdev are următoarea definiție:
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
Se observă că această structură include un câmp de tipul struct kobject.
O structură struct kobject este definită astfel:
struct kobject { const char *name; struct list_head entry; struct kobject *parent; struct kset *kset; struct kobj_type *ktype; struct sysfs_dirent *sd; struct kref kref; unsigned int state_initialized:1; unsigned int state_in_sysfs:1; unsigned int state_add_uevent_sent:1; unsigned int state_remove_uevent_sent:1; unsigned int uevent_suppress:1; };
După cum se observă, structurile kobject sunt într-o ierarhie: un obiect are un părinte și deține un membru kset, care cuprinde obiecte de pe același nivel.
Lucrul cu structura presupune inițializarea ei, cu ajutorul funcției kobject_init. Tot în procesul de inițializare este necesară stabilirea numelui structurii kobject, care va apărea în sysfs, cu ajutorul funcției kobject_set_name.
Orice operație asupra unui kobject se realizează prin incrementarea contorului intern al acestuia cu kobject_get, sau decrementarea în cazul în care nu se mai folosește cu kobject_put. Astfel, un obiect de tip kobject nu va fi eliberat decât în momentul în care contorul intern al acestuia ajunge la valoarea 0. Este nevoie de o metodă de notificare a acestui lucru, pentru ca, apoi, să fie eliberate resursele asociate structurii de dispozitiv care includea struct kobject (spre exemplu cdev). Metoda se numește release și este asociată obiectului prin intermediul câmpului ktype (de tip struct kobj_type).
Structura struct kobject este structura de bază din Linux Device Model. Structurile din nivelurile mai înalte ale modelului sunt struct bus_type, struct device și struct device_driver.
O magistrală este un canal de comunicație între procesor și un dispozitiv de intrare/ieșire. Pentru asigurarea genericității modelului, toate dispozitivele de intrare/ieșire sunt conectate la procesor prin intermediul unei astfel de magistrale (chiar dacă aceasta poate fi una virtuală fără un corespondent fizic hardware).
La adăugarea unei magistrale în sistem, aceasta va apărea în sistemul de fișiere sysfs
, în /sys/bus
. Ca și la structurile kobjects, magistralele pot fi organizate în ierarhii și vor fi reprezentate astfel și în sysfs
.
În Linux Device Model, o magistrală este reprezentată de structura struct bus_type:
struct bus_type { const char *name; const char *dev_name; struct device *dev_root; struct bus_attribute *bus_attrs; struct device_attribute *dev_attrs; struct driver_attribute *drv_attrs; struct subsys_private *p; int (*match)(struct device * dev, struct device_driver * drv); int (*uevent)(struct device *dev, struct kobj_uevent_env *env); int (*probe)(struct device * dev); int (*remove)(struct device * dev); //... };
Se observă că unei magistrale îi este asociat un nume, liste de atribute default, o serie de funcții specifice și datele private ale driverului. Funcția uevent
(fostă hotplug
), este folosită în cazul dispozitivelor hotplug
.
Operațiile cu magistrala sunt operații de înregistrare, implementarea operațiilor descrise în structura struct bus_type și operații de parcurgere și inspecție a dispozitivelor conectate la magistrală.
Înregistrarea unei magistrale se realizează folosind bus_register, iar deînregistrarea folosind bus_unregister:
Funcțiile care vor fi inițializate uzual în cadrul unei structuri struct bus_type sunt match
și uevent
:
#include <linux/device.h> #include <linux/string.h> /* mybus.c */ // match devices to drivers; just do a simple name test static int my_match (struct device *dev, struct device_driver *driver) { return strncmp (dev_name(dev), driver->name, strlen (driver->name)); } // respond to hotplug user events; add environment variable DEV_NAME static int my_uevent (struct device *dev, struct kobj_uevent_env *env) { add_uevent_var(env, "DEV_NAME=%s", dev_name(dev)); return 0; }
Funcția match
este utilizată în momentul în care se adaugă la magistrală un nou dispozitiv sau un nou driver. Rolul ei este de a realiza comparația între identificatorul dispozitivului și cel al driverului. Funcția uevent
este apelată înainte de generarea unui eveniment de tip hotplug
în user-space și are drept rol adăugarea de variabile de mediu.
Alte operații posibile în cazul unei magistrale sunt parcurgerea driverelor sau dispozitivelor atașate acesteia. Deși nu le putem accesa în mod direct (listele cu driverele și dispozitive fiind stocate în datele private ale driverului, câmpul struct subsys_private *p), acestea pot fi parcurse cu ajutorul macrodefinițiilor bus_for_each_dev și bus_for_each_drv.
Interfața Linux Device Model
permite crearea de atribute pentru obiectele asociate. Aceste atribute vor avea drept corespondent un fișier în subdirectorul magistralei din sysfs
. Atributele asociate unei magistrale sunt descrise de structura bus_attribute:
struct bus_attribute { struct attribute attr; ssize_t (*show)(struct bus_type *, char * buf); ssize_t (*store)(struct bus_type *, const char * buf, size_t count); };
De obicei, un atribut se definește cu ajutorul macrodefiniției BUS_ATTR. Pentru adăugarea/ștergerea unui atribut din cadrul structurii de magistrală se folosesc funcțiile bus_create_file și bus_remove_file.
Un exemplu de definire a unui atribut pentru magistrala my_bus
este prezentat în continuare:
Magistrala este reprezentată atât de un obiect bus_type, cât și de un obiect device, așa cum vom vedea în continuare (magistrala este și ea un dispozitiv).
Orice dispozitiv din sistem are asociată o structură struct device. Dispozitivele sunt descoperite prin diferite metode de kernel (hotplug, de către device drivere, la inițializarea sistemului) și sunt înregistrate în sistem. Toate dispozitivele prezente în kernel au câte o intrare în /sys/devices
.
La cel mai jos nivel, un dispozitiv în Linux Device Model
este reprezentat de o structură struct device:
struct device { //... struct device *parent; struct device_private *p; struct kobject kobj; const char *init_name; /* initial name of the device */ //... struct bus_type *bus; /* type of bus device is on */ struct device_driver *driver; /* which driver has allocated this device */ //... void (*release)(struct device * dev); };
Printre câmpurile structurii se numără dispozitivul părinte care este de obicei un controller, obiectul kobject asociat, magistrala pe care se găsește, driverul care se ocupă de dispozitiv și o funcție apelată în momentul în care contorul dispozitivului ajunge la 0.
Ca de obicei, avem funcții de înregistrare/deînregistrare device_register și device_unregister.
Pentru lucrul cu atributele avem structura struct device_attribute, macrodefiniția DEVICE_ATTR pentru definire și funcțiile device_create_file și device_remove_file pentru adăugarea atributului la dispozitiv.
Un lucru important de notat este faptul că, de obicei, nu se lucrează direct cu o structură de tip struct device, ci cu o structură care o conține pe aceasta, de forma:
// my device type struct my_device { char *name; struct my_driver *driver; struct device dev; };
De obicei, un modul va exporta funcții de înregistrare/deînregistrare ale unui astfel de dispozitiv, după modelul prezentat mai jos:
După cum se poate observa, funcțiile my_register_device
și my_unregister_device
de adăugare, respectiv scoatere a unui device de pe o magistrală sunt definite în fișierul în care este definită magistrala. Nu se inițializează obiecte de tip dispozitiv; acestea se vor ințializa în momentul în care vor fi descoperite în sistem (prin hotplug sau înregistrare directă din driver) și se va apela funcția my_register_device
pentru adăugarea la magistrală.
Pentru utilizare (în implementarea driver-ului), trebuie declarată o structură de tipul celei exportate, inițializată și înregistrată cu metoda exportata de magistrală:
Linux Device Model
este folosit pentru a permite asocierea foarte ușoară între dispozitivele sistemului și drivere. Driverele pot exporta informații independente de dispozitivul fizic din spate.
În sysfs
informațiile despre drivere nu au asociate un singur subdirector; ele se pot găsi în structura de directoare, în diferite locuri: în /sys/module
se găsește modulul încărcat, în devices
se poate găsi driverul asociat fiecărui device, în classes
driverele care aparțin unei clase, în /sys/bus
driverele asociate fiecărei magistrale.
Un device driver este identificat prin structura struct device_driver:
struct device_driver { const char *name; struct bus_type *bus; struct driver_private *p; struct module *owner; const char *mod_name; /* used for built-in modules */ int (*probe) (struct device * dev); int (*remove) (struct device * dev); void (*shutdown) (struct device * dev); int (*suspend) (struct device * dev, pm_message_t state); int (*resume) (struct device * dev); };
Printre câmpurile structurii regăsim numele driver-ului (apare în sysfs
), magistrala cu care driverul lucrează și funcții apelate în diverse momente din funcționarea unui dispozitiv.
Ca și până acum, avem funcțiile de înregistrare/deînregistrare driver_register și driver_unregister.
Pentru lucrul cu atributele avem structura struct driver_attribute, macrodefiniția DRIVER_ATTR pentru definire și funcțiile driver_create_file și driver_remove_file pentru adăugarea atributului la dispozitiv.
Ca și în cazul dispozitivelor, structura struct device_driver este, de obicei, incorporată în cadrul unei alte structuri specifice unei anumite magistrale (PCI
, USB
, etc.):
Se observă că se exportă operațiile de înregistrare/deînregistrare de driver pentru folosire în cadrul altor module.
Ca și în cazul dispozitivelor, și operațiile cu drivere sunt definite la inițializarea magistralei, și sunt exportate pentru a putea fi folosite de drivere. În momentul în care se implementează un driver ce lucrează cu dispozitive atașate la această magistrală, acesta va apela funcțiile my_register_driver
și my_unregister_driver
pentru a se asocia cu aceasta.
Pentru utilizare (în implementarea driver-ului), trebuie declarată o structură de tipul celei exportate, inițializată și înregistrată cu metoda exportata de magistrală:
/* mydriver.c */ static struct my_driver mydriver = { .module = THIS_MODULE, .driver = { .name = "mydriver", }, }; //... //register int err; err = my_register_driver(&mydriver); if (err < 0) { /*handle error */ } //.. //unregister my_unregister_driver(&mydriver);
O clasă este o vedere de nivel înalt asupra Linux Device Model
, care abstractizează detaliile de implementare. Spre exemplu, există drivere pentru discurile SCSI
și drivere pentru discurile ATA
, dar toate aparțin clasei discuri. Clasele asigură o grupare a dispozitivelor bazată pe funcționalitate, nu pe modul în care sunt conectate sau cum funcționează. Clasele au corespondent în /sys/classes
.
Există doua structuri principale care descriu clasele: struct class și struct device. Structura class descrie o clasa generică, în timp ce structura struct device descrie o clasă asociată unui dispozitiv. Există funcții pentru inițializare/deinițializare și adăugare de atribute pentru fiecare dintre acestea, descrise în include/linux/device.h
.
Avantajul folosirii claselor este că programul udev din userspace, despre care vom discuta în continuare, permite crearea automată a dispozitivelor în directorul /dev
pe baza informațiilor clasei.
Din acest motiv, vom prezenta în continuare un set restrâns de funcții care lucrează cu clasele pentru a simplifica folosirea mecanismului de plug and play.
O clasă generică este descrisă de structura struct class:
struct class { const char *name; struct module *owner; struct kobject *dev_kobj; struct subsys_private *p; struct class_attribute *class_attrs; struct class_device_attribute *class_dev_attrs; struct device_attribute *dev_attrs; int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); void (*class_release)(struct class *class); void (*dev_release)(struct device *dev); //... };
Pentru inițializare/deinițializare există funcțiile class_register și class_unregister:
O clasă asociată unui dispozitiv este descrisă de structura device. Pentru inițializare/deinițializare există funcțiile device_create și device_destroy. Funcția device_create inițializează stuctura device, îi asociază structura generică class și dispozitivul primit ca parametru; în plus, va crea un atribut al clasei, dev
, care conține minorul și majorul dispozitivului (minor:major
). Astfel, utilitarul udev
din usermode poate citi datele necesare din acest fișier atribut pentru a crea un nod în directorul /dev
apelând makenod
.
Un exemplu de inițializare:
struct device* my_classdev; struct cdev cdev; struct device dev; //init class for device cdev.dev my_classdev = device_create(&my_class, NULL, cdev.dev, &dev, "myclass0"); //destroy class for device cdev.dev device_destroy(&my_class, cdev.dev);
În momentul în care un nou dispozitiv va fi descoperit, i se va asocia o clasă și un nod în directorul /dev
. Pentru exemplul de mai sus, se va genera un nod /dev/myclass0
.
Hotplug
descrie mecanismul de adăugare sau eliminare a unui dispozitiv din sistem în timp ce acesta rulează, fără a fi necesară repornirea sistemului.
Un eveniment hotplug
este o notificare din partea kernel-ului către user-space atunci când se modifică ceva în configurația sistemului. Aceste evenimente sunt generate la crearea sau eliminarea unui obiect kobject din kernel. Întrucât aceste obiecte stau la baza Linux Device Model
, fiind incluse în toate structurile ( struct bus_type, struct device, struct device_driver, struct class, etc.), se va genera un eveniment hotplug
la crearea sau eliminarea oricăreia dintre aceste structuri (uevent
).
În momentul în care un dispozitiv este descoperit în sistem, se generează un eveniment. În funcție de punctul în care se află în Linux Device Model
, se vor apela funcțiile corespunzătoare apariției unui eveniment (de obicei, este cazul funcției uevent
a magistralei sau clasei). Driverul are posibilitatea, prin intermediul acestor funcții, să seteze variabile de sistem pentru user-space. Evenimentul generat ajunge în user-space apoi. Aici există utilitarul udev
, care capturează aceste evenimente. În directorul /etc/udev/
există fișierele de configurare pentru acest utilitar. Se pot specifica diferite reguli pentru a captura numai anumite evenimente și a executa anumite acțiuni, în funcție de variabilele de sistem setate din kernel sau în funcțiile uevent
.
O consecință importantă este că în acest mod se poate realiza mecanismul plug and play
; cu ajutorul lui udev
si a claselor, descrise anterior, se pot crea automat intrările în directorul /dev
pentru dispozitive, iar cu ajutorul udev
se pot încărca automat driverele necesare pentru un dispozitiv. În acest fel, întreg procesul este automatizat.
Regulile pentru udev
sunt localizate în /etc/udev/rules.d
. Orice fișier de aici care se termină cu .conf va fi parsat la apariția unui eveniment. Pentru mai multe detalii despre cum se scriu regulile în aceste fișiere consultați Writing udev rules. Pentru testare, există utilitarele udevmonitor
, udevinfo
și udevtest
.
Pentru un scurt exemplu, sa consideram situația în care dorim sa încarcăm automat un driver pentru un dispozitiv în momentul apariției unui eveniment. Putem crea un nou fișier /etc/udev/rules.d/myrules.rules
, în care vom avea următoarea linie:
SUBSYSTEM=="pnp", ATTRS{id}=="PNP0400", RUN+="/sbin/insmod /root/mydriver.ko"
Astfel, se vor alege dintre evenimentele generate doar cele care aparțin subsistemului pnp
(sunt conectate la magistrala PNP) și au un atribut id cu valoarea PNP0400
. În momentul în care se va găsi această regulă, se va executa comanda ce inserează driverul corespunzător în kernel.
După cum s-a specificat mai sus, în Linux Device Model
toate dispozitivele sunt conectate printr-o magistrală, chiar dacă are corespondent fizic hardware sau este virtuală.
În kernel, există deja implementate majoritatea magistralelor prin definirea unei structuri bus_type și a funcțiilor de înregistrare/deînregistrare a driverelor și dispozitivelor asociate. Pentru implementarea unui driver trebuie determinată magistrala la care se atașează dispozitivele suportate și trebuie folosite funcțiile și structurile acesteia. Principalele magistrale sunt PCI, USB, PNP, IDE, SCSI, platform, ACPI, etc.
Mecanismul plug and play oferă un mijloc de detectarea și setare a resurselor pentru drivere legacy sau care nu pot fi configurate în alt mod. Toate driverele plug and play, protocoalele, serviciile au la bază nivelul Plug and Play
. Acesta este responsabil cu schimbul de informație între drivere și protocoale. Următoarele protocoale sunt disponibile:
În kernel există o magistrala pnp_bus
, care este folosită pentru conectare de multe drivere. Implementarea și modul de lucru cu această magistrală respectă modelul Linux Device Model
și este foarte asemănatoare cu ce s-a prezentat până acum.
Principalele funcții și structuri exportate de această magistrală, și care pot fi folosite de către drivere sunt:
După cum am observat în secțiunile anterioare, magistrala are o funcție match
cu ajutorul căreia asociaza dispozitive cu driverele corespunzătoare. Spre exemplu, în cazul descoperirii unui dispozitiv, se va căuta driver-ul care îndeplinește condiția dată de această funcție relativă la dispozitiv. De obicei, această condiție reprezintă o comparație de id-uri ale driverului și dispozitivului. Un mecanism răspândit este folosirea unei tabele statice în fiecare driver, ce conține informații despre dispozitivele suportate de driver și vor fi folosite de magistrală la comparație. Spre exemplu, pentru un driver de port paralel vom avea tabela parport_pc_pnp_tbl
:
static const struct pnp_device_id parport_pc_pnp_tbl[] = { /* Standard LPT Printer Port */ {.id = "PNP0400", .driver_data = 0}, /* ECP Printer Port */ {.id = "PNP0401", .driver_data = 0}, }; MODULE_DEVICE_TABLE(pnp,parport_pc_pnp_tbl);
Se declară și inițializează o structură pnp_driver, cum ar fi parport_pc_pnp_driver
:
static int parport_pc_pnp_probe (struct pnp_dev * dev, const struct pnp_id *card_id, const struct pnp_id *dev_id); static void parport_pc_pnp_remove(struct pnp_dev * dev); static struct pnp_driver parport_pc_pnp_driver = { .name = "parport_pc", .id_table = parport_pc_pnp_tbl, .probe = parport_pc_pnp_probe, .remove = parport_pc_pnp_remove, };
După cum se poate observa, structura are ca parametri un pointer către tabela declarată mai sus două funcții care se apelează la detectarea unui dispozitiv, respectiv la eliminarea lui din sistem. La fel ca toate structurile prezentate, driver-ul trebuie înregistrat în sistem:
static int __init parport_pc_init(void) { err = pnp_register_driver (&parport_pc_pnp_driver); if (err < 0) { /* handle error */ } } static void __exit parport_pc_exit(void) { pnp_unregister_driver (&parport_pc_pnp_driver); }
Până acum am discutat despre modelul Linux Device Model
și API-ul folosit. Pentru a implementa un driver plug and play, trebuie respectat modelul Linux Device Model
.
De cele mai multe ori, adăugarea unei magistrale în kernel nu este necesară (bus), întrucât deja sunt implementate majoritatea magistralelor (PCI
, USB
, etc.). Astfel, mai întâi trebuie identificată magistrala la care se atașează dispozitivul. În exemplele de mai jos, vom considera că această magistrală este magistrala PNP
. Astfel, se vor folosi structurile și funcțiile prezentate mai sus.
Pe lângă operațiile uzuale, un driver trebuie să respecte modelul Linux Device Model
. Astfel, se va înregistra în sistem folosind funcțiile puse la dispoziție de magistrală în acest scop. De obicei, magistrala pune la dispoziție o structură particulară de driver, ce conține o structură device_driver, pe care driverul trebuie să o inițializeze și să o înregistreze cu o funcție *_register_driver
. Spre exemplu, pentru magistrala PNP
, driverul trebuie să declare și să inițializeze o structură de tipul pnp_driver, pe care să o înregistreze cu pnp_register_driver:
static struct pnp_driver my_pnp_driver = { .name = "mydriver", .id_table = my_pnp_tbl, .probe = my_pnp_probe, .remove = my_pnp_remove, }; static int __init my_init(void) { err = pnp_register_driver (&my_pnp_driver); }
Spre deosebire de driverele legacy, driverele plug and play nu înregistrează dispozitivele la inițializare, în funcția my_init
(register_device
).
După cum s-a descris mai sus, fiecare magistrală are o funcție match
care se apelează la detectarea unui dispozitv pentru a determina driverul asociat acestuia. Prin urmare, trebuie să existe o modalitate ca fiecare driver să exporte informații despre ce dispozitive suportă, pentru a putea trece de această comparație și pentru a fi apelate funcțiile sale. În exemplele prezentate în laborator, se face o simplă comparație între numele dispozitivului și numele driver-ului. Cele mai multe drivere folosesc o tabelă cu informații despre dispozitiv, pentru care au un pointer în structura driver-ului. Spre exemplu, un driver asociat cu o magistrală PNP
, declară o tabelă de tipul pnp_device_id, și inițializează câmpul id_table
din structura pnp_driver cu un pointer către aceasta:
static const struct pnp_device_id my_pnp_tbl[] = { /* Standard LPT Printer Port */ {.id = "PNP0400", .driver_data = 0}, /* ECP Printer Port */ {.id = "PNP0401", .driver_data = 0}, { } }; MODULE_DEVICE_TABLE(pnp,my_pnp_tbl); static struct pnp_driver my_pnp_driver = { //... .id_table = my_pnp_tbl, //... };
În exemplul de mai sus driver-ul suportă operații pentru portul paralel. Aceaste informații sunt folosite de magistrală, în funcția match_device
. La adăugarea unui driver, se va asocia driverul magistralei și se vor crea intrări în sysfs
bazate pe numele driver-ului. Apoi se va apela funcția match
a magistralei pentru toate dispozitivele asociate, pentru a asocia driver-ul cu orice dispozitiv conectat pe care îl suportă.
Pentru a elimina un driver din kernel, pe lângă operațiile necesare unui driver legacy, trebuie deînregistrată structura device_driver. În cazul unui driver pentru un dispozitiv asociat magistralei PNP
, trebuie deînregistrată structura pnp_driver cu ajutorul funcției pnp_unregister_driver:
static struct pnp_driver my_pnp_driver; static void __exit my_exit(void) { pnp_unregister_driver (&my_pnp_driver); }
Spre deosebire de driverele legacy, driverele plug and play nu deînregistrează dispozitivele la deînregistrarea driver-ului, în funcția my_exit
(unregister_device). La eliminarea unui driver, se vor elimina toate referințele către el pentru toate dispozitivele pe care le suportă și se vor șterge intrările din sysfs
.
Dupa cum am văzut mai sus, driverele plug and play nu înregistrează dispozitivele la inițializare. Această operație se va realiza în funcția probe
, care se va apela la detectarea unui dispozitiv. În cazul unui driver pentru un dispozitiv atașat la magistrala PNP
, adăugarea se va realiza în funcția probe
din structura pnp_driver:
static int my_pnp_probe (struct pnp_dev * dev, const struct pnp_id *card_id, const struct pnp_id *dev_id) { int err, iobase, nr_ports, irq; //get irq & ports if (pnp_irq_valid(dev, 0)) irq = pnp_irq(dev, 0); if (pnp_port_valid(dev, 0)) { iobase = pnp_port_start(dev, 0); } else return -ENODEV; nr_ports = pnp_port_len(dev, 0); /* register device dev */ } static struct pnp_driver my_pnp_driver = { //... .probe = my_pnp_probe, //... };
La detectarea unui dispozitiv în kernel (în procesul de boot sau la adăugarea dispozitivului prin hotplug
), se transmite o întrerupere în sistem care ajunge la magistrală. Dispozitivul este înregistrat cu ajutorul funcției device_register și este atașat magistralei (și se va genera un apel în userspace, care poate fi detectat de udev
). Apoi se va parcurge lista de drivere a magistralei și se va apela funcția match
pentru fiecare dintre ele. Funcția match
încearcă să asocieze un driver cu un dispozitiv. După ce a fost determinat driverul asociat dispozitivului, se va apela funcția probe
a driver-ului. Dacă funcția se termină cu succes, dispozitivul este adăugat în lista de dispozitive a driver-ului și se creează intrările corespunzătoare în sysfs
bazate pe numele dispozitivului.
Dupa cum am văzut mai sus, driverele plug and play nu deînregistrează dispozitivele la deînregistrarea driver-ului. Această operație se va realiza în funcția remove
, care se va apela la detectarea eliminării unui dispozitiv din kernel. În cazul unui driver pentru un dispozitiv atașat la magistrala PNP
, adăugarea se va realiza în funcția remove
din structura pnp_driver:
static void my_pnp_remove(struct pnp_dev * dev) { /* unregister device dev */ } static struct pnp_driver my_pnp_driver = { //... .remove = my_pnp_remove, };
După cum se poate observa, la detectarea eliminării unui dispozitiv din sistem, se va apela funcția remove a driver-ului, se va genera un apel în user-space, ce poate fi detectat de udev
și se vor elimina intrările din sysfs
.