This is an old revision of the document!


Laborator 12 - Linux Device Model. Plug and Play.

Obiectivele laboratorului

  • Dobândirea de cunoștințe legate de Linux Device Model şi principalele componente ale acestuia: device-uri, drivere, clase şi magistrale.
  • Cunoștințe legate de modul de funcţionare al mecanismelor Hotplug/Plug and Play.

Cuvinte cheie

  • Magistrala
  • Device
  • Driver
  • Kobject
  • Hotplug
  • Plug and Play
  • Udev

Materiale ajutătoare

Noţiuni teoretice

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:

  • Detectarea automată a adaugării şi eliminării dispozitivelor din sistem (dispozitivul şi magistrala acestuia trebuie sa anunţe driver-ul corespunzător că a apărut o modificare în configuraţie).
  • Managementul resurselor (adrese, linii irq, canale DMA, zone de memorie), ce include alocarea resurselor către dispozitive şi rezolvarea conflictelor ce pot apărea.
  • Dispozitivele trebuie să permită configurarea software (resursele dispozitivului – porturi, întreruperi, resurse DMA – trebuie să permită atribuirea din partea driver-ului).
  • Driver-ele necesare pentru dispozitive noi trebuie să fie încarcate automat de sistemul de operare atunci când este nevoie.
  • Atunci când dispozitivul şi magistrala acestuia permit, sistemul ar trebui să poată adăuga sau elimina dispozitivul din sistem în timp ce acesta rulează, fără a fi necesară repornirea sistemului (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.

Linux Device Model

Î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:

  • dispozitiv - un dispozitiv fizic care este ataşat unei magistrale
  • driver – o entitate software care poate fi asociată unui dispozitiv si execută operaţii cu acesta
  • magistrală (bus) – un dispozitiv la care se pot ataşa alte dispozitive
  • clasă – un tip de dispozitive care au o comportare similară; există o clasă pentru discuri, partiţii, porturi seriale, etc.
  • subsistem – o vedere asupra structurii sistemului; subsistemele din kernel includ dispozitive (devices - o vedere ierarhică asupra tuturor dispozitivelor din sistem), magistrale (bus - o vedere a dispozitivelor în funcţie de cum sunt ataşate la magistrale), clase, etc.

Sysfs

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:

  • block - toate dispozitivele de tip bloc disponibile în sistem (discuri, partiţii)
  • bus - tipuri de magistrale la care se conectează dispozitivele fizice (pci, ide, usb)
  • class - clase de drivere care sunt disponibile în sistem (net, sound, usb)
  • devices - structura ierarhica a dispozitivelor conectate în sistem
  • firmware - informaţii obţinute de la firmware-ul sistemului (ACPI)
  • fs - informaţii legate de sistemele de fişiere montate
  • kernel - informaţii legate de starea kernel-ului (utilizatorii logaţi, hotplug)
  • module - lista modulelor încarcate la momentul curent
  • power - informaţii legate de subsistemul de power management

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 - identificatorul major şi minor al dispozitivului; acesta poate fi folosit pentru a crea automat intrările în directorul /dev
  • device - o legătură simbolică spre directorul ce conţine dispozitive; acesta poate fi folosit pentru a descoperi dispozitivele hardware care oferă un anumit serviciu (spre exemplu dispozitivul PCI al plăcii de reţea eth0)
  • driver - o legătură simbolică spre directorul ce conţine drivere (care se află în /sys/bus/*/drivers)

Sunt disponibile şi alte atribute, în funcţie de magistrala şi driverul folosit.

Structuri de bază în dispozitivele Linux

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.

Structura kobject

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.

Magistrale

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:

Afișați exemplu implementare

Ascundeți exemplu implementare

#include <linux/device.h>
/* mybus.c */
 
//bus type
struct bus_type my_bus_type = {
	.name	= "mybus",
	.match	= my_match,
	.uevent	= my_uevent,
};
 
static int __init my_bus_init (void)
{
	int err;
 
	//...
	err = bus_register (&my_bus_type);
	if (err)
		return err;
	//...
}
 
static void __exit my_bus_exit (void)
{
	//...
	bus_unregister (&my_bus_type);
	//...
}


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:

Afișați exemplu de definire a unui atribut

Ascundeți exemplu de definire a unui atribut

/* mybus.c */
 
#define MY_BUS_DESCR     "SO2 rules forever"
 
// export a simple bus attribute
static ssize_t my_show_bus_descr (struct bus_type *bus, char *buf)
{
        return snprintf (buf, PAGE_SIZE, "%s\n", MY_BUS_DESCR);
}
 
/*
 * define attribute - attribute name is descr;
 * full name is bus_attr_descr;
 * sysfs entry should be /sys/bus/mybus/descr
 */
BUS_ATTR (descr, 0444, my_show_bus_descr , NULL);
 
// specify attribute - in module init function
static int __init my_bus_init (void)
{
        int err;
        //...
        err = bus_create_file (&my_bus_type, &bus_attr_descr);
        if(err) {
                /* handle error */
        }
        //...
}
 
static void __exit my_bus_exit (void)
{
        //...
        bus_remove_file (&my_bus_type, &bus_attr_descr);
        //...
}


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).

Dispozitive

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:

Afișați cod

Ascundeți cod

/* mybus.c */
 
/* BUS DEVICE (parent) */
 
// parent device release
static void my_bus_device_release (struct device *dev)
{
}
 
// parent device
static struct device my_bus_device = {
	.init_name   = "mybus0",
	.release  = my_bus_device_release
};
 
/* DEVICE */
 
/*
 * as we are not using the reference count, we use a no-op
 * release function
 */
static void my_dev_release (struct device *dev)
{
}
 
int my_register_device (struct my_device *mydev)
{
	mydev->dev.bus = &my_bus_type;
	mydev->dev.parent = &my_bus_device;
	mydev->dev.release = my_dev_release;
	dev_set_name(&mydev->dev, mydev->name);
 
	return device_register (&mydev->dev);
}
 
void my_unregister_device (struct my_device * mydev)
{
	device_unregister (&mydev->dev);
}
 
/* export register/unregister device functions */
EXPORT_SYMBOL (my_register_device);
EXPORT_SYMBOL (my_unregister_device);


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ă:

Afișați exemplu înregistrare device

Ascundeți exemplu înregistrare device

/* mydriver.c */
 
static struct my_device mydev;
char devname[NAME_SIZE];
//...
 
//register
int err;
 
sprintf(devname, "mydev0");
mydev.name = devname;
mydev.driver = &mydriver;
dev_set_drvdata(&mydev.dev, &mydev);
err = my_register_device(&mydev);
if (err < 0) {
	/*handle error */
}
 
//..
 
//unregister
my_unregister_device(&mydev);


Drivere

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.):

Afișați cod

Ascundeți cod

/* mybus.c */
 
// my driver type
 struct my_driver {
	struct module *module;
	struct device_driver driver;
};
 
#define to_my_driver(drv) container_of(drv, struct my_driver, driver);
 
int my_register_driver (struct my_driver *driver)
{
	int err;
 
	driver->driver.bus = &my_bus_type;
	err= driver_register (&driver->driver);
	if (err)
		return err;
	return 0;
}
 
void my_unregister_driver (struct my_driver *driver)
{
	driver_unregister (&driver->driver);
}
 
/* export register/unregister driver functions */
EXPORT_SYMBOL (my_register_driver);
EXPORT_SYMBOL (my_unregister_driver);


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);

Clase

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:

Afișați cod

Ascundeți cod

static struct class my_class = {
        .name = "myclass",
};
 
static int __init my_init(void)
{
        int err;
        //...
        err = class_register(&my_class);
        if (err < 0) {
                /* handle error */
        }
        //...
}
 
static void __exit my_cleanup (void) 
{
        //...
        class_unregister(&my_class);
        //...
}


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

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.

Plug and Play

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.

Magistrala PNP

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:

  • PNPBIOS - folosite pentru sisteme cum ar fi porturile seriale şi paralele
  • ISAPNP - oferă suport pentru magistrala ISA
  • ACPI - care oferă, printre altele, informaţii despre dispozitivele la nivel de sistem

Î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);
}

Operaţii PNP

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.

Adăugare driver

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ă.

Eliminare driver

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.

Adăugare dispozitiv

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.

Eliminare dispozitiv

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.

Resurse utile

so2/laboratoare/lab12.1399806909.txt.gz · Last modified: 2014/05/11 14:15 by irina.presa
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