This is an old revision of the document!
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 fara sistemul de fişiere sysfs
, dar reciproca nu este adevărată.
Informaţia din sysfs
se găseşte in 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 primite 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 trebuiesc 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
.