This shows you the differences between two versions of the page.
so2:laboratoare:lab05 [2015/03/15 13:34] irina.presa [Locking] |
so2:laboratoare:lab05 [2018/03/18 19:00] (current) anda.nicolae [Controller tastatură] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Laborator 5 - Întreruperi ====== | ====== Laborator 5 - Întreruperi ====== | ||
+ | |||
===== Obiectivele laboratorului ===== | ===== Obiectivele laboratorului ===== | ||
- | *înțelegerea modului de comunicare cu dispozitivele periferice | + | * înțelegerea modului de comunicare cu dispozitivele periferice |
- | *deprinderea de cunoștințe de implementare a rutinelor de tratare a întreruperilor | + | * deprinderea de cunoștințe de implementare a rutinelor de tratare a întreruperilor |
- | *înțelegerea particularităților în sincronizarea cu rutinele de tratarea a întreruperilor | + | * înțelegerea particularităților în sincronizarea cu rutinele de tratarea a întreruperilor |
===== Cuvinte cheie ===== | ===== Cuvinte cheie ===== | ||
- | *IRQ | + | * IRQ |
- | *port I/O | + | * port I/O |
- | *adresă I/O | + | * adresă I/O |
- | *adresă de bază | + | * adresă de bază |
- | *UART | + | * UART |
- | *request_region / release_region | + | * request_region / release_region |
- | *inb / outb | + | * inb / outb |
===== Materiale ajutătoare ===== | ===== Materiale ajutătoare ===== | ||
- | *[[http://elf.cs.pub.ro/so2/res/laboratoare/lab05-slides.pdf|Slide-uri de suport pentru laborator]] | + | * [[http://elf.cs.pub.ro/so2/res/laboratoare/lab05-slides.pdf|Slide-uri de suport pentru laborator]] |
- | *[[http://elf.cs.pub.ro/so2/res/extra/so2_reference.pdf|SO2 Reference Card]] | + | * [[http://elf.cs.pub.ro/so2/res/extra/so2_reference.pdf|SO2 Reference Card]] |
===== Noțiuni generale ===== | ===== Noțiuni generale ===== | ||
Line 25: | Line 26: | ||
==== Comunicația cu hardware-ul ==== | ==== Comunicația cu hardware-ul ==== | ||
- | Un dispozitiv periferic este controlat prin scrierea și citirea registrelor sale. De cele mai multe ori, un dispozitiv are mai multe registre, care pot fi accesate la adrese consecutive, fie în spațiul de **adrese din memorie**, fie în spațiul de **adrese I/O**. Fiecare dispozitiv conectat la magistrala I/O are un set de adrese I/O, numite porturi I/O. Porturile I/O pot fi mapate la adrese din memoria fizică, astfel încât procesorul va putea realiza comunicarea cu dispozitivul prin instrucțiuni care operează direct cu memoria. Din motive de simplitate, vom folosi porturile I/O pentru comunicarea cu dispozitivele fizice. | + | Un dispozitiv periferic este controlat prin scrierea și citirea registrelor sale. De cele mai multe ori, un dispozitiv are mai multe registre, care pot fi accesate la adrese consecutive, fie în **spațiul de adrese din memorie**, fie în **spațiul de adrese I/O**. Fiecare dispozitiv conectat la magistrala I/O are un set de adrese I/O, numite **porturi I/O**. Porturile I/O pot fi mapate la adrese din memoria fizică, astfel încât procesorul va putea realiza comunicarea cu dispozitivul prin instrucțiuni care operează direct cu memoria. Din motive de simplitate, vom folosi în mod direct porturile I/O (fără mapare la adrese din memoria fizică) pentru comunicarea cu dispozitivele fizice. |
- | Porturile I/O ale fiecărui dispozitiv sunt structurate într-un set de registre specializate, pentru a oferi o interfață uniformă pentru programare. Astfel, majoritatea dispozitivelor vor avea următoarele registre: | + | Porturile I/O ale fiecărui dispozitiv sunt structurate într-un set de registre specializate, pentru a oferi o interfață uniformă pentru programare. Astfel, majoritatea dispozitivelor vor avea următoarele tipuri de registre: |
- | *registrul de **control**, care primește comenzi pentru dispozitiv | + | * registre de **control**, care primesc comenzi pentru dispozitiv |
- | *registrul de **stare**, care conține starea interna a dispozitivului | + | * registre de **stare**, care conțin informații despre starea internă a dispozitivului |
- | *registrul de **intrare**, din care se preiau datele de la dispozitiv | + | * registre de **intrare**, din care se preiau datele de la dispozitiv |
- | *registrul de **ieșire**, în care se scriu datele pentru a le transmite către dispozitiv | + | * registre de **ieșire**, în care se scriu datele pentru a le transmite către dispozitiv |
În majoritatea cazurilor, porturile fizice sunt diferențiate după numărul de biți: pot fi porturi pe 8, 16 sau 32 de biți. | În majoritatea cazurilor, porturile fizice sunt diferențiate după numărul de biți: pot fi porturi pe 8, 16 sau 32 de biți. | ||
Line 39: | Line 40: | ||
==== Tratarea întreruperilor ==== | ==== Tratarea întreruperilor ==== | ||
- | Deși există echipamente care pot fi controlate în întregime folosind porturi I/O sau zone de memorie speciale, există și situații în care acest lucru este insuficient. Principala problemă care mai trebuie tratată e faptul că anumite evenimente au loc la intervale de timp nedeterminate și este ineficient ca procesorul (CPU) să interogheze repetat echipamentele despre starea lor. Modalitatea de rezolvare a acestei probleme o reprezintă **IRQ** (Interrupt ReQuest) prin care procesorul este anunțat de apariția unui anumit eveniment extern. | + | Deși există echipamente care pot fi controlate în întregime folosind porturi I/O sau zone de memorie speciale, există și situații în care acest lucru este insuficient. Principala problemă care mai trebuie tratată e faptul că anumite evenimente au loc la intervale de timp nedeterminate și este ineficient ca procesorul (CPU) să interogheze repetat echipamentele despre starea lor (//polling//). Modalitatea de rezolvare a acestei probleme o reprezintă **IRQ** (//Interrupt ReQuest//), notificări hardware prin care procesorul este anunțat de apariția unui anumit eveniment extern. |
- | Pentru ca IRQ-urile să fie utile, trebuie să existe secvențe de cod care să le trateze. Deoarece, în multe situații, numărul de întreruperi disponibile este limitat, un device driver trebuie să aibă un comportament cât mai "civilizat" relativ la ele: întreruperile trebuie să fie cerute înainte de a fi utilizate și eliberate atunci când nu mai este nevoie de ele. În plus, în anumite situații, device driver-ele trebuie să folosească în comun o întrerupere sau să se sincronizeze cu întreruperile. Despre toate acestea vom discuta în continuare. | + | Pentru ca IRQ-urile să fie utile, trebuie să existe secvențe de cod care să le trateze. Deoarece, în multe situații, numărul de întreruperi disponibile este limitat, un device driver trebuie să aibă un comportament cât mai "civilizat" cu întreruperile: adică întreruperile trebuie să fie cerute înainte de a fi utilizate și eliberate atunci când nu mai este nevoie de ele. În plus, în anumite situații, device driver-ele trebuie să folosească în comun o întrerupere sau să se sincronizeze cu întreruperile. Despre toate acestea vom discuta în continuare. |
==== Locking ==== | ==== Locking ==== | ||
- | Atunci când trebuie să accesăm resurse partajate între o rutină de tratare a întreruperii (A) și cod ce rulează în context proces sau într-o rutină de tratare a unei acțiuni amânabile (B), trebuie să folosim un mod special de sincronizare. În (A) trebuie sa folosim o primitivă de tip spinlock, iar în (B) trebuie să dezactivăm întreruperile **ȘI** să folosim o primitivă de tip spinlock. Dezactivarea întreruperilor nu este suficientă pentru că rutina de întrerupere poate rula pe un alt procesor decât cel pe care rulează (B). | + | Atunci când trebuie să accesăm resurse partajate între o rutină de tratare a întreruperii (A) și cod ce rulează în context proces sau într-o rutină de tratare a unei acțiuni amânabile (B), trebuie să folosim un mod special de sincronizare. În (A) trebuie să folosim o primitivă de tip spinlock, iar în (B) trebuie să dezactivăm întreruperile **ȘI** să folosim o primitivă de tip spinlock. Dezactivarea întreruperilor nu este suficientă pentru că rutina de întrerupere poate rula pe un alt procesor decât cel pe care rulează (B). |
Folosirea doar a unui spinlock poate duce la deadlock. Exemplul clasic de deadlock în acest caz este: | Folosirea doar a unui spinlock poate duce la deadlock. Exemplul clasic de deadlock în acest caz este: | ||
- | *rulăm în context proces, pe procesorul X, și luăm lock-ul | + | - rulăm în context proces, pe procesorul X, și achiziționăm lock-ul |
- | *înainte de a elibera lock-ul, se generează o întrerupere pe procesorul X | + | - înainte de a elibera lock-ul, se generează o întrerupere pe procesorul X |
- | *rutina de tratare a întreruperii va încerca să ia și ea lock-ul | + | - rutina de tratare a întreruperii va încerca să achiziționeze și ea lock-ul și va intra într-o buclă infinită |
- | ===== Comunicația cu hardware-ul în Linux ===== | + | ===== Comunicarea cu hardware-ul în Linux ===== |
- | În Linux, conceptul de porturi I/O este implementat pe toate platformele, chiar și pe cele care folosesc un singur spațiu de adresă. | + | În Linux, conceptul de porturi I/O este implementat pe toate platformele. |
==== Alocarea porturilor I/O ==== | ==== Alocarea porturilor I/O ==== | ||
- | Înainte de a putea lucra cu porturile I/O, trebuie să ne asigurăm că avem acces exclusiv la ele. Pentru a obține porturile dorite, se folosește funcția ''[[http://lxr.free-electrons.com/source/include/linux/ioport.h?v=3.13#L174|request_region]]'': | + | Înainte de a putea lucra cu porturile I/O, trebuie să ne asigurăm că avem acces exclusiv la ele. Pentru a obține porturile dorite, se folosește funcția [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/ioport.h#L220|request_region]]: |
<code c> | <code c> | ||
Line 66: | Line 67: | ||
</code> | </code> | ||
- | Parametrul ''first'' specifica adresa de bază pentru dispozitiv, iar ''n'' numărul de porturi dorite. Adresa de bază este începutul zonei contigue de adrese (sau de porturi I/O) asociate dispozitivului și trebuie să fie unică pentru fiecare dispozitiv. Parametrul ''name'' reprezintă numele dispozitivului. | + | Parametrul ''first'' specifică adresa de bază pentru dispozitiv, iar ''n'' numărul de porturi dorite. Adresa de bază este începutul zonei contigue de adrese (sau de porturi I/O) asociate dispozitivului și trebuie să fie unică pentru fiecare dispozitiv. Parametrul ''name'' reprezintă numele dispozitivului. |
- | Pentru eliberarea porturilor rezervate, se folosește funcția ''[[http://lxr.free-electrons.com/source/include/linux/ioport.h?v=3.13#L188|release_region]]'': | + | Pentru eliberarea porturilor rezervate, se folosește funcția [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/ioport.h#L234|release_region]]: |
<code c> | <code c> | ||
Line 74: | Line 75: | ||
</code> | </code> | ||
- | Spre exemplu, portul serial ''COM1'' are adresa de bază 0x3F8 și deține 8 porturi. Secvența de cod pentru alocarea porturilor asociate acestuia este următoarea: | + | Spre exemplu, portul serial ''COM1'' are adresa de bază ''0x3F8'' și deține 8 porturi. Secvența de cod pentru alocarea porturilor asociate acestuia este următoarea: |
<code c> | <code c> | ||
Line 118: | Line 119: | ||
... | ... | ||
</code> | </code> | ||
+ | |||
==== Operații de scriere și citire a porturilor I/O ==== | ==== Operații de scriere și citire a porturilor I/O ==== | ||
- | După ce un driver a obținut intervalul de porturi I/O dorite, trebuie să realizeze operații de citire sau scriere pe aceste porturi. Întrucât porturile fizice sunt diferențiate după numărul de biți (8, 16 sau 32 de biți), există diferite funcții de acces a porturilor în funcție de dimensiunea lor. În ''[[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13|asm/io.h]]'' sunt definite următoarele funcții de acces a porturilor: | + | După ce un driver a obținut intervalul de porturi I/O dorite, trebuie să realizeze operații de citire sau scriere pe aceste porturi. Întrucât porturile fizice sunt diferențiate după numărul de biți (8, 16 sau 32 de biți), există diferite funcții de acces a porturilor în funcție de dimensiunea lor. În ''[[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h|asm/io.h]]'' sunt definite următoarele funcții de acces a porturilor: |
- | * ''unsigned [[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L132|inb]](int port)'', citește porturi de dimensiune un octet (8 biți) | + | * ''u8 [[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L360|inb]](unsigned long addr)'', citește porturi de dimensiune un octet (8 biți) |
- | * ''void [[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L142|outb]](unsigned char byte, int port)'', scrie porturi de dimensiune un octet (8 biți) | + | * ''void [[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L384|outb]](u8 value, unsigned long addr)'', scrie porturi de dimensiune un octet (8 biți) |
- | * ''unsigned [[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L132|inw]](int port)'', citește porturi de dimensiune doi octeți (16 biți) | + | * ''u16 [[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L368|inw]](unsigned long addr)'', citește porturi de dimensiune doi octeți (16 biți) |
- | * ''void [[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L147|outw]](unsigned short word, int port)'', scrie porturi de dimensiune doi octeți (16 biți) | + | * ''void [[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L392|outw]](u16 value, unsigned long addr)'', scrie porturi de dimensiune doi octeți (16 biți) |
- | * ''unsigned [[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L137|inl]](int port)'', citește porturi de dimensiune patru octeți (32 biți) | + | * ''u32 [[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L376|inl]](unsigned long addr)'', citește porturi de dimensiune patru octeți (32 biți) |
- | * ''void [[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L152|outl]](unsigned long word, int port)'', scrie porturi de dimensiune patru octeți (32 biți) | + | * ''void [[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L400|outl]](u32 value, unsigned long addr)'', scrie porturi de dimensiune patru octeți (32 biți) |
Argumentul port specifică adresa portului de unde se citește sau se scrie, iar tipul său este dependent de platformă (poate fi ''unsigned long'' sau ''unsigned short''). | Argumentul port specifică adresa portului de unde se citește sau se scrie, iar tipul său este dependent de platformă (poate fi ''unsigned long'' sau ''unsigned short''). | ||
- | Anumite platforme pot avea probleme atunci când procesorul încearcă să transfere date prea rapid către și de la dispozitiv. Soluția este inserarea unei întârzieri după fiecare instrucțiune de I/O, în cazul în care urmează o altă instrucțiune de același tip. În cazul în care dispozitivul pierde date, se pot folosi funcții care introduc această întârziere; numele acestora este similar cu cele descrise mai sus, cu deosebirea că se termină în ''_p'': ''[[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L157|inb_p]]'', ''[[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L160|outb_p]]'' etc. | + | Anumite platforme pot avea probleme atunci când procesorul încearcă să transfere date prea rapid către și de la dispozitiv. Soluția este inserarea unei întârzieri după fiecare instrucțiune de I/O, în cazul în care urmează o altă instrucțiune de același tip. În cazul în care dispozitivul pierde date, se pot folosi funcții care introduc această întârziere; numele acestora este similar cu cele descrise mai sus, cu deosebirea că se termină în ''_p'': ''[[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L408|inb_p]]'', ''[[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L432|outb_p]]'' etc. |
Spre exemplu, următoarea secvență scrie un octet pe portul serial ''COM1'' și apoi îl citește: | Spre exemplu, următoarea secvență scrie un octet pe portul serial ''COM1'' și apoi îl citește: | ||
+ | |||
<code c> | <code c> | ||
#include <asm/io.h> | #include <asm/io.h> | ||
Line 143: | Line 146: | ||
</code> | </code> | ||
- | Deși funcțiile descrise mai sus sunt definite pentru device drivere, ele pot fi folosite și din user-space, prin includerea header-ului ''<sys/io.h>''. Pentru a putea fi folosite, vor trebui apelate mai întâi funcțiile ''[[http://www.kernel.org/doc/man-pages/online/pages/man2/ioperm.2.html|ioperm]]'' sau ''[[http://www.kernel.org/doc/man-pages/online/pages/man2/iopl.2.html|iopl]]'' pentru obținerea permisiunii de a realiza operații cu porturile. Funcția ''ioperm'' obține permisiunea pentru porturi individuale, în timp ce ''iopl'' pentru întregul spațiu de adrese I/O. Pentru a putea folosi aceste funcții utilizatorul trebuie să fie ''root''. | + | <spoiler Operații I/O în user space> |
- | Următoarea secvență obține permisiunea pentru primele 3 porturi ale portului serial, și apoi le eliberează: | + | Deși funcțiile descrise mai sus sunt definite pentru device drivere, ele pot fi folosite și din user space, prin includerea header-ului ''<sys/io.h>''. Pentru a putea fi folosite, vor trebui apelate mai întâi funcțiile ''[[http://www.kernel.org/doc/man-pages/online/pages/man2/ioperm.2.html|ioperm]]'' sau ''[[http://www.kernel.org/doc/man-pages/online/pages/man2/iopl.2.html|iopl]]'' pentru obținerea permisiunii de a realiza operații cu porturile. Funcția ''ioperm'' obține permisiunea pentru porturi individuale, în timp ce ''iopl'' pentru întregul spațiu de adrese I/O. Pentru a putea folosi aceste funcții utilizatorul trebuie să fie ''root''. |
+ | |||
+ | Următoarea secvență folosită în user space obține permisiunea pentru primele 3 porturi ale portului serial, și apoi le eliberează: | ||
<code c> | <code c> | ||
Line 160: | Line 165: | ||
Al treilea parametru al funcției ''ioperm'' este 1 pentru obținerea permisiunii și 0 pentru eliberare. | Al treilea parametru al funcției ''ioperm'' este 1 pentru obținerea permisiunii și 0 pentru eliberare. | ||
+ | |||
+ | </spoiler> | ||
===== Întreruperi în Linux ===== | ===== Întreruperi în Linux ===== | ||
+ | |||
==== Obținerea unei întreruperi ==== | ==== Obținerea unei întreruperi ==== | ||
La fel ca și în cazul celorlalte resurse, un driver trebuie să obțină accesul la o linie de întreruperi înainte de a o putea utiliza și să o elibereze la finalul execuției. | La fel ca și în cazul celorlalte resurse, un driver trebuie să obțină accesul la o linie de întreruperi înainte de a o putea utiliza și să o elibereze la finalul execuției. | ||
- | În Linux, cererea de obținere și respectiv eliberare a unei întreruperi se face cu ajutorul funcțiilor ''[[http://lxr.free-electrons.com/source/include/linux/interrupt.h?v=3.13#L128|request_irq]]'' și ''[[http://lxr.free-electrons.com/source/kernel/irq/manage.c?v=3.13#L1336|free_irq]]'': | + | În Linux, cererea de obținere și respectiv eliberare a unei întreruperi se face cu ajutorul funcțiilor ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]'' și ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L169|free_irq]]'': |
<code c> | <code c> | ||
#include <linux/interrupt.h> | #include <linux/interrupt.h> | ||
- | int request_irq(unsigned int irq_no, | + | typedef irqreturn_t (*irq_handler_t)(int, void *); |
- | irqreturn_t (*handler)(int irq_no, void *dev_id), | + | |
- | unsigned long flags, const char *dev_name, void *dev_id); | + | int request_irq(unsigned int irq, irq_handler_t handler, |
+ | unsigned long flags, const char *name, void *dev); | ||
+ | |||
+ | const void *free_irq(unsigned int irq, void *dev_id) | ||
+ | </code> | ||
+ | |||
+ | Se observă că pentru obținerea unei întreruperi dezvoltatorul apelează funcția ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]''. În cadrul funcției ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]'' trebuie să specifice **numărul întreruperii** (''irq''), un **handler** ce va fi chemat în momentul generării întreruperii (''handler''), **flag**-uri ce vor instrui kernelul despre comportamentul dorit (''flags''), **numele dispozitivului** ce folosește această întrerupere (''name''), și un pointer ce poate fi configurat de către utilizator la orice valoare, și care nu are semnificație globală (''dev''). De cele mai multe ori, ''dev'' va fi configurat la pointerul către datele private ale dispozitivului. În schimb, la eliberarea întreruperii, folosind funcția ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L169|free_irq]]'' dezvoltatorul trebuie să transmită aceeași valoare a pointerului (''dev''), împreună cu numărul întreruperii (''irq''). Numele dispozitivului (''name'') este folosit pentru afișarea de statistici în ''/proc/interrupts''. | ||
+ | |||
+ | Valoarea pe care o întoarce ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]'' este 0 în cazul în care înregistrarea s-a efectuat cu succes sau un cod de eroare negativ care indică motivul eșecului. O valoare uzuală este ''-EBUSY'' care este întoarsă atunci când întreruperea este ocupată deja de un alt echipament. | ||
+ | |||
+ | Există situații în care deși un dispozitiv folosește întreruperi nu putem citi regiștrii dispozitivului într-un mod non-blocant (de exemplu un senzor conectat la un bus I2C sau SPI al cărui driver nu garantează că operațiile de read / write pe bus sunt non-blocante). În această situație în întrerupere trebuie să planificăm o acțiune amânabilă ce rulează în context proces (work queue, kernel thread) pentru a putea accesa regiștrii dispozitivului. Pentru că o astfel de situație este relativ comună, kernel-ul pune la dispoziție funcția ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L140|request_threaded_irq]]'' pentru întregistra rutine de tratare a întreruperilor ce rulează în două faze: o fază în context proces și o fază în context întrerupere: | ||
+ | |||
+ | <code c> | ||
+ | #include <linux/interrupt.h> | ||
- | void free_irq(unsigned int irq_no, void *dev_id); | + | int request_threaded_irq(unsigned int irq, irq_handler_t handler, |
+ | irq_handler_t thread_fn, | ||
+ | unsigned long flags, const char *name, void *dev); | ||
</code> | </code> | ||
- | Se observă că pentru obținerea unei întreruperi utilizatorul trebuie să specifice **numărul întreruperii** (''irq_no''), un **handler** ce va fi chemat în momentul generării întreruperii (''handler''), **flag**-uri ce vor instrui kernelul despre comportamentul dorit (''flags''), **numele dispozitivului** ce folosește această întrerupere (''dev_name''), și un pointer ce poate fi setat de către utilizator la orice valoare, și care nu are semnificație globală (''dev_id''). De cele mai multe ori, ''dev_id'' va fi setat la pointerul către datele private ale dispozitivului. În schimb, la eliberarea întreruperii, utilizatorul trebuie să paseze aceeași valoare a pointerului (''dev_id''), împreună cu numărul întreruperii (''irq_no''). Numele dispozitivului (''dev_name'') este folosit pentru afișarea de statistici în ''/proc/interrupts''. | + | ''handler'' este funcția ce rulează în context întrerupere și va implementa operațiile critice și care pot fi executate din context întrerupere, în timp ce funcția ''thread_fn'' rulează în context proces și va implementa restul operațiilor. |
- | Valoarea pe care o întoarce ''request_irq'' este 0 în cazul în care înregistrarea s-a efectuat cu succes sau un cod de eroare negativ care indică motivul eșecului. O valoare uzuală este ''-EBUSY'' care este întoarsă atunci când întreruperea este ocupată deja de un alt echipament. | ||
- | [[http://lxr.free-electrons.com/source/include/linux/interrupt.h?v=3.13#L39|Flag-urile]] ce pot fi pasate la obținerea unei întreruperi sunt ''IRQF_SHARED'' și ''IRQF_DISABLED'': | + | [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L40|Flag-urile]] ce pot fi transmise la obținerea unei întreruperi sunt: |
- | *''[[http://lxr.free-electrons.com/source/include/linux/interrupt.h?v=3.13#L62|IRQF_SHARED]]'' anunță kernelul că întreruperea poate fi partajată cu alte dispozitive. Dacă acest flag nu este setat, atunci, dacă există deja un handler asociat cu întreruperea cerută, cererea de obținere a unei întreruperi va eșua. O întrerupere partajată este tratată prin execuția tuturor rutinelor înregistrate. Această abordare duce la o situație interesantă: cum își poate da seama un device driver dacă rutina de tratare a întreruperii a fost activată de o întrerupere generată de dispozitivul pe care îl gestionează? Răspunsul este simplu: toate dispozitivele care oferă suport pentru întreruperi au asociate un registru de stare, care poate fi interogat în rutina de tratare pentru a afla dacă întreruperea a fost sau nu generată de dispozitiv (în cazul portului serial, acest registru de stare este ''IIR''). La cererea unei întreruperi partajate cu ''request_irq'', argumentul ''dev_id'' trebuie să fie unic în kernel; poate fi setat la un pointer către datele private ale modulului, dar nu poate fi ''NULL''. | + | *''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L86|IRQF_SHARED]]'' anunță kernelul că întreruperea poate fi partajată cu alte dispozitive. Dacă acest flag nu este setat, atunci, dacă există deja un handler asociat cu întreruperea cerută, cererea de obținere a unei întreruperi va eșua. O întrerupere partajată este tratată prin execuția tuturor rutinelor înregistrate. Această abordare duce la o situație interesantă: cum își poate da seama un device driver dacă rutina de tratare a întreruperii a fost activată de o întrerupere generată de dispozitivul pe care îl gestionează? Toate dispozitivele care oferă suport pentru întreruperi au asociate un registru de stare, care poate fi interogat în rutina de tratare pentru a afla dacă întreruperea a fost sau nu generată de dispozitiv (în cazul portului serial, acest registru de stare este ''IIR'' - //Interrupt Information Register//). La cererea unei întreruperi partajate cu ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]'', argumentul ''dev'' trebuie să fie unic în kernel; poate fi setat la un pointer către datele private ale modulului, dar nu poate fi ''NULL''. |
- | *''[[http://lxr.free-electrons.com/source/include/linux/interrupt.h?v=3.13#L61|IRQF_DISABLED]]'' anunță kernelul că handler-ul se va executa cu toate întreruperile dezactivate pe procesorul local; un device driver nu trebuie să folosească acest flag, el este folosit doar în cazuri speciale - de exemplu la tratarea întreruperii de ceas. | + | *''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L73|IRQF_ONESHOT]]'' întreruperea va fi reactivată după rularea rutinei de tratare din context proces; fără acest flag, întreruperea va fi reactivată dupa rularea rutinei de tratare din context întrerupere. |
Obținerea întreruperii se poate realiza fie la inițializarea driver-ului, în funcția ''init_module'', fie atunci când dispozitivul este deschis prima dată, în funcția ''open''. | Obținerea întreruperii se poate realiza fie la inițializarea driver-ului, în funcția ''init_module'', fie atunci când dispozitivul este deschis prima dată, în funcția ''open''. | ||
- | Următorul exemplu realizează aceste operații pentru portul serial ''COM1'': | + | Următorul exemplu realizează obținerea întreruperii pentru portul serial ''COM1'': |
<code c> | <code c> | ||
Line 196: | Line 218: | ||
#define MY_IRQ 4 | #define MY_IRQ 4 | ||
- | struct my_device_data *my_data; | + | static my_init(void) |
- | int err; | + | { |
+ | [...] | ||
+ | struct my_device_data *my_data; | ||
+ | int err; | ||
+ | |||
+ | err = request_irq(MY_IRQ, my_handler, IRQF_SHARED, | ||
+ | "com1", my_data); | ||
+ | if (err < 0) { | ||
+ | /* handle error*/ | ||
+ | return err; | ||
+ | } | ||
- | if ((err = request_irq(MY_IRQ, my_handler, IRQF_SHARED, | + | [...] |
- | "com1", my_data))) { | + | |
- | /* handle error*/ | + | |
- | return err; | + | |
} | } | ||
</code> | </code> | ||
Line 208: | Line 237: | ||
După cum se poate observa, IRQ-ul pentru portul serial ''COM1'' este 4, care este folosit în mod partajat (''IRQF_SHARED''). | După cum se poate observa, IRQ-ul pentru portul serial ''COM1'' este 4, care este folosit în mod partajat (''IRQF_SHARED''). | ||
- | ** Atenție!** La cererea unei întreruperi partajate (''IRQF_SHARED'') cu ''request_irq'', argumentul ''dev_id'' nu poate fi ''NULL''. | + | <note warning> |
+ | La cererea unei întreruperi partajate (''IRQF_SHARED'') cu ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]'', argumentul ''dev'' **nu** poate fi ''NULL''. | ||
+ | </note> | ||
Pentru eliberarea întreruperii asociate portului serial se va executa următoarea secvență: | Pentru eliberarea întreruperii asociate portului serial se va executa următoarea secvență: | ||
+ | |||
<code c> | <code c> | ||
free_irq(MY_IRQ, my_data); | free_irq(MY_IRQ, my_data); | ||
Line 218: | Line 250: | ||
Pentru portul serial trebuie realizate două operații pentru activarea întreruperilor: | Pentru portul serial trebuie realizate două operații pentru activarea întreruperilor: | ||
- | *se activează toate întreruperile prin setarea bitului 3 (''Aux Output 2'') în registrul ''MCR'' | + | - se activează toate întreruperile prin setarea bitului 3 (''Aux Output 2'') în registrul ''MCR'' - //Modem Control Register// |
- | *se activează întreruperea dorită (''RDAI'' - Receive Data Available Interrupt, ''THREI'' - Transmit Holding Register Empty Interrupt) prin setarea bitului corespunzător în registrul ''IER''. | + | - se activează întreruperea dorită (''RDAI'' - //Receive Data Available Interrupt//, ''THREI'' - //Transmit Holding Register Empty Interrupt//) prin setarea bitului corespunzător în registrul ''IER'' - //Interrupt Enable Register//. |
În exemplul de mai jos se activează întreruperea ''RDAI'' pentru portul ''COM1'': | În exemplul de mai jos se activează întreruperea ''RDAI'' pentru portul ''COM1'': | ||
+ | |||
<code c> | <code c> | ||
#include <asm/io.h> | #include <asm/io.h> | ||
Line 231: | Line 265: | ||
==== Implementarea rutinei de tratare a întreruperii ==== | ==== Implementarea rutinei de tratare a întreruperii ==== | ||
- | Să examinăm acum [[http://lxr.free-electrons.com/source/include/linux/interrupt.h?v=3.13#L88|signatura funcției de tratare a întreruperii]]: | + | Să examinăm acum [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L94|signatura funcției de tratare a întreruperii]]: |
<code c> | <code c> | ||
- | irqreturn_t (*handler)(int irq_no, void *dev_id); | + | irqreturn_t (*handler)(int irq, void *dev_id); |
</code> | </code> | ||
- | Funcția primește ca parametri numărul întreruperii pe care rutina o tratează și pointer-ul trimis la cererea de obținere a întreruperii. Rutina de tratare a întreruperii trebuie să întoarcă o valoare cu tipul ''[[http://lxr.free-electrons.com/source/include/linux/irqreturn.h?v=3.13#L16|irqreturn_t]]''. Pentru versiunea curentă de kernel, există doar două valori valide: ''[[http://lxr.free-electrons.com/source/include/linux/irqreturn.h?v=3.13#L11|IRQ_NONE]]'' și ''[[http://lxr.free-electrons.com/source/include/linux/irqreturn.h?v=3.13#L12|IRQ_HANDLED]]''. Device driverul trebuie să întoarcă ''IRQ_NONE'' dacă observă că întreruperea nu a fost generată de dispozitivul pe care îl comandă. În caz contrar, device driverul trebuie să întoarcă ''IRQ_HANDLED''. | + | Funcția primește ca parametri numărul întreruperii pe care rutina o tratează și pointer-ul trimis la cererea de obținere a întreruperii. Rutina de tratare a întreruperii trebuie să întoarcă o valoare cu tipul ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/irqreturn.h#L17|irqreturn_t]]''. Pentru versiunea curentă de kernel, există trei valori valide: ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/irqreturn.h#L12|IRQ_NONE]]'', ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/irqreturn.h#L13|IRQ_HANDLED]]'' și [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/irqreturn.h#L14|IRQ_WAKE_THREAD]]. Device driverul trebuie să întoarcă ''IRQ_NONE'' dacă observă că întreruperea nu a fost generată de dispozitivul pe care îl comandă. În caz contrar, device driverul trebuie să întoarcă ''IRQ_HANDLED'' dacă întreruperea poate fi tratată direct din context înterupere sau ''IRQ_WAKE_THREAD'' pentru a planifica rularea funcției de tratare din context proces. |
Un handler de întrerupere va avea următoarea structură: | Un handler de întrerupere va avea următoarea structură: | ||
Line 244: | Line 278: | ||
irqreturn_t my_handler(int irq_no, void *dev_id) | irqreturn_t my_handler(int irq_no, void *dev_id) | ||
{ | { | ||
- | struct my_device_data *my_data = (struct my_device_data *) dev_id; | + | struct my_device_data *my_data = (struct my_device_data *) dev_id; |
- | /* if interrupt is not for this device (shared interrupts) */ | + | /* if interrupt is not for this device (shared interrupts) */ |
- | /* return IRQ_NONE;*/ | + | /* return IRQ_NONE;*/ |
- | /* clear interrupt-pending bit */ | + | /* clear interrupt-pending bit */ |
- | /* read from device or write to device*/ | + | /* read from device or write to device*/ |
- | return IRQ_HANDLED; | + | return IRQ_HANDLED; |
} | } | ||
</code> | </code> | ||
De obicei, primul lucru executat în rutina de tratare a întreruperii este să se determine dacă întreruperea a fost generată de dispozitivul comandat de driver. Pentru aceasta, de obicei se citesc informații din registrul de control al dispozitivului, care indică daca acesta a generat întreruperea. Al doilea lucru constă în resetarea bitului ''interrupt-pending'' pe dispozitivul fizic; cele mai multe dispozitive nu vor mai genera întreruperi până când acest bit nu a fost resetat. Acest pas depinde doar de dispozitiv, și poate și lipsi (cum e cazul portului paralel, care nu are un astfel de bit). Portul serial are un astfel de bit: bitul 0 din registrul ''IIR''. | De obicei, primul lucru executat în rutina de tratare a întreruperii este să se determine dacă întreruperea a fost generată de dispozitivul comandat de driver. Pentru aceasta, de obicei se citesc informații din registrul de control al dispozitivului, care indică daca acesta a generat întreruperea. Al doilea lucru constă în resetarea bitului ''interrupt-pending'' pe dispozitivul fizic; cele mai multe dispozitive nu vor mai genera întreruperi până când acest bit nu a fost resetat. Acest pas depinde doar de dispozitiv, și poate și lipsi (cum e cazul portului paralel, care nu are un astfel de bit). Portul serial are un astfel de bit: bitul 0 din registrul ''IIR''. | ||
+ | |||
==== Locking ==== | ==== Locking ==== | ||
- | Deoarece rutinele de tratare pentru întreruperi se execută în [[http://elf.cs.pub.ro/so2/wiki/laboratoare/lab03#contexte-de-executie|context întrerupere]], acțiunile care se pot efectua sunt limitate: nu se poate accesa memoria din user-space, nu se pot apela funcții blocante, nu se poate face sincronizare doar cu spinlock-uri deoarece acest lucru ar duce la deadlock în cazul în care spinlock-ul este deja obținut de către un proces care a fost întrerupt. | + | Deoarece rutinele de tratare pentru întreruperi se execută în [[http://elf.cs.pub.ro/so2/wiki/laboratoare/lab03#contexte-de-executie|context întrerupere]], acțiunile care se pot efectua sunt limitate: nu se poate accesa memoria din user space, nu se pot apela funcții blocante, nu se poate face sincronizare doar cu spinlock-uri deoarece acest lucru ar duce la deadlock în cazul în care spinlock-ul este deja obținut de către un proces care a fost întrerupt. |
- | Totuși, există cazuri în care device driverele trebuie să se sincronizeze cu întreruperile. În aceste situații este necesară dezactivarea întreruperii cu care dorim să ne sincronizăm (nu se pot dezactiva întreruperile partajate) cu ajutorul funcțiilor ''[[http://lxr.free-electrons.com/source/kernel/irq/manage.c?v=3.13#L413|disable_irq]]'', ''[[http://lxr.free-electrons.com/source/kernel/irq/manage.c?v=3.13#L395|disable_irq_nosync]]'' și ''[[http://lxr.free-electrons.com/source/kernel/irq/manage.c?v=3.13#L454|enable_irq]]''. Dezactivarea are loc la nivelul tuturor procesoarelor din sistem și apelurile pot să fie imbricate: dacă se apelează de două ori ''disable_irq'' vor fi necesare tot atâtea apeluri ''enable_irq'' pentru activarea ei. Diferența dintre ''disable_irq'' și ''disable_irq_nosync'' e că prima din ele va aștepta terminarea handler-elor aflate în execuție. Din această cauză ''disable_irq_nosync'' este în general mai rapidă. | + | Totuși, există cazuri în care device driverele trebuie să se sincronizeze cu întreruperile. În aceste situații este necesară dezactivarea întreruperilor și folosirea spinlock-urilor. Există două modalitați de dezactivarea a întreruperilor: dezactivarea tututor întreruperilor, la nivel de procesor, sau dezactivarea la nivel de întrerupere. Dezactivarea la nivel de procesor este mai rapidă și de asemenea previne deadlock-urile mai complexe și de accea este preferată. În acest scop, există funcții de locking care dezactivează, respectiv reactivează întreruperile: ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L338|spin_lock_irqsave]]'', ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L363|spin_unlock_irqrestore]]'', ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L333|spin_lock_irq]]'' și ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L358#L355|spin_unlock_irq]]''. |
- | Spre exemplu, următoarea secvență dezactivează și apoi activează întreruperea pentru portul serial ''COM1'': | ||
- | <code c> | ||
- | #define MY_IRQ 4 | ||
- | |||
- | disable_irq(MY_IRQ); | ||
- | enable_irq(MY_IRQ); | ||
- | </code> | ||
- | |||
- | Este posibilă și dezactivarea tuturor întreruperilor pentru procesorul curent. Dezactivarea tuturor întreruperilor de către device drivere pentru sincronizare este total neadecvată; nu trebuie folosită niciodată această abordare. Funcțiile care dezactivează / reactivează întreruperile pe procesorul local sunt ''[[http://lxr.free-electrons.com/source/include/linux/irqflags.h?v=3.13#L91|local_irq_disable]]'' și ''[[http://lxr.free-electrons.com/source/include/linux/irqflags.h?v=3.13#L89|local_irq_enable]]''. În general, nu puteți folosi aceste funcții pentru sincronizare datorită problemelor ce apar în sisteme multiprocesor. | ||
- | |||
- | Pentru sincronizare, uneori este necesar să se folosească un spinlock și să se dezactiveze și întreruperile. În acest scop, există funcții de locking care dezactivează, respectiv reactivează întreruperile: ''[[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L321|spin_lock_irqsave]]'', ''[[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L234|spin_unlock_irqrestore]]'', ''[[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L316|spin_lock_irq]]'' și ''[[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L341|spin_unlock_irq]]''. | ||
<code c> | <code c> | ||
#include <linux/spinlock.h> | #include <linux/spinlock.h> | ||
Line 282: | Line 306: | ||
void spin_unlock_irq(spinlock_t *lock); | void spin_unlock_irq(spinlock_t *lock); | ||
</code> | </code> | ||
- | Funcția ''[[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L321|spin_lock_irqsave]]'' dezactivează întreruperile pentru procesorul local înainte de a obține spinlock-ul; starea anterioara a întreruperilor este salvată în ''flags''. În cazul în care sunteți siguri că întreruperile pe procesorul curent nu au fost deja dezactivate de altcineva (deci sunteți siguri că trebuie să activați întreruperile când eliberați spinlock-ul), puteți folosi funcția ''[[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L316|spin_lock_irq]]''. | + | |
+ | Funcția ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L338|spin_lock_irqsave]]'' dezactivează întreruperile pentru procesorul local înainte de a obține spinlock-ul; starea anterioara a întreruperilor este salvată în ''flags''. În cazul în care sunteți siguri că întreruperile pe procesorul curent nu au fost deja dezactivate de altcineva (deci sunteți siguri că trebuie să activați întreruperile când eliberați spinlock-ul), puteți folosi funcția ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L333|spin_lock_irq]]''. | ||
Pentru spinlock-urile de tip read / write există funcții similare: | Pentru spinlock-urile de tip read / write există funcții similare: | ||
- | * ''[[http://lxr.free-electrons.com/source/include/linux/rwlock.h?v=3.13#L69|read_lock_irqsave]]'' | + | * ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/rwlock.h#L75|read_lock_irqsave]]'' |
- | * ''[[http://lxr.free-electrons.com/source/include/linux/rwlock.h?v=3.13#L104|read_unlock_irqrestore]]'' | + | * ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/rwlock.h#L110|read_unlock_irqrestore]]'' |
- | * ''[[http://lxr.free-electrons.com/source/include/linux/rwlock.h?v=3.13#L95|read_lock_irq]]'' | + | * ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/rwlock.h#L101|read_lock_irq]]'' |
- | * ''[[http://lxr.free-electrons.com/source/include/linux/rwlock.h?v=3.13#L101|read_unlock_irq]]'' | + | * ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/rwlock.h#L107|read_unlock_irq]]'' |
- | * ''[[http://lxr.free-electrons.com/source/include/linux/rwlock.h?v=3.13#L74|write_lock_irqsave]]'' | + | * ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/rwlock.h#L80|write_lock_irqsave]]'' |
- | * ''[[http://lxr.free-electrons.com/source/include/linux/rwlock.h?v=3.13#L111|write_unlock_irqrestore]]'' | + | * ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/rwlock.h#L117#L111|write_unlock_irqrestore]]'' |
- | * ''[[http://lxr.free-electrons.com/source/include/linux/rwlock.h?v=3.13#L97|write_lock_irq]]'' | + | * ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/rwlock.h#L103|write_lock_irq]]'' |
- | * ''[[http://lxr.free-electrons.com/source/include/linux/rwlock.h?v=3.13#L102|write_unlock_irq]]''. | + | * ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/rwlock.h#L108|write_unlock_irq]]''. |
+ | |||
- | Astfel, pentru a folosi o resursă partajată atât în context proces cât și în rutina de tratare a întreruperii, se vor folosi funcțiile descrise mai sus în context proces astfel: | + | Dacă dorim să ne sincronizăm la nivel de întrerupere (nerecomandat pentru că este dezactivarea unei întreruperi este mai lentă, nu se pot dezactiva întreruperile partajate, pot apărea deadlock-uri în situații în care lucrăm cu mai multe întreruperi) o putem face cu ajutorul funcțiilor ''[[https://elixir.bootlin.com/linux/v4.15/source/kernel/irq/manage.c#L475|disable_irq]]'', ''[[https://elixir.bootlin.com/linux/v4.15/source/kernel/irq/manage.c#L458|disable_irq_nosync]]'' și ''[[https://elixir.bootlin.com/linux/v4.15/source/kernel/irq/manage.c#L548|enable_irq]]''. Dezactivarea are loc la nivelul tuturor procesoarelor din sistem și apelurile pot să fie imbricate: dacă se apelează de două ori ''disable_irq'' vor fi necesare tot atâtea apeluri ''enable_irq'' pentru activarea ei. Diferența dintre ''disable_irq'' și ''disable_irq_nosync'' e că prima din ele va aștepta terminarea handler-elor aflate în execuție. Din această cauză ''disable_irq_nosync'' este în general mai rapidă. |
+ | |||
+ | Spre exemplu, următoarea secvență dezactivează și apoi activează întreruperea pentru portul serial ''COM1'': | ||
<code c> | <code c> | ||
- | unsigned long flags; | + | #define MY_IRQ 4 |
- | DEFINE_SPINLOCK(lock); | + | |
- | spin_lock_irqsave(&lock, flags); | + | disable_irq(MY_IRQ); |
- | /* critical region – access shared resource */ | + | enable_irq(MY_IRQ); |
- | spin_unlock_irqrestore(&lock, flags); | + | |
</code> | </code> | ||
- | În rutina de tratare a întreruperii, se pot folosi funcțiile ''spin_lock'' și ''spin_unlock'' pentru acces la resursa partajată. | + | Este posibilă și dezactivarea tuturor întreruperilor pentru procesorul curent. Dezactivarea tuturor întreruperilor de către device drivere pentru sincronizare este neadecvată. Funcțiile care dezactivează / reactivează întreruperile pe procesorul local sunt ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/irqflags.h#L104|local_irq_disable]]'' și ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/irqflags.h#L102|local_irq_enable]]''. Nu puteți folosi aceste funcții în mod singular pentru sincronizare din cauza problemelor ce apar în sisteme multiprocesor. |
+ | |||
+ | |||
+ | === Utilizare spinlock-uri === | ||
+ | |||
+ | Pentru a folosi o resursă partajată atât în context proces cât și în rutina de tratare a întreruperii, se vor folosi funcțiile descrise mai sus astfel: | ||
+ | |||
+ | <code c> | ||
+ | static spinlock_t lock; | ||
+ | |||
+ | /* IRQ handling routine: interrupt context */ | ||
+ | irqreturn_t so2_kbd_interrupt_handle(int irq_no, void *dev_id) | ||
+ | { | ||
+ | [...] | ||
+ | spin_lock(&lock); | ||
+ | /* critical region - access shared resource */ | ||
+ | [...] | ||
+ | spin_unlock(&lock); | ||
+ | [...] | ||
+ | } | ||
+ | |||
+ | /* Process context: disable interrupts when locking */ | ||
+ | static void my_access(void) | ||
+ | { | ||
+ | unsigned long flags; | ||
+ | |||
+ | spin_lock_irqsave(&lock, flags); | ||
+ | /* critical region - access shared resource */ | ||
+ | spin_unlock_irqrestore(&lock, flags); | ||
+ | |||
+ | [...] | ||
+ | } | ||
+ | |||
+ | void my_init(void) | ||
+ | { | ||
+ | [...] | ||
+ | spin_lock_init(&lock); | ||
+ | [...] | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Funcția ''my_access'' de mai sus rulează în context proces. Atunci când facem sincronizare dezactivăm întreruperile și folosim spinlock-ul ''lock'', adică funcțiile ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L338|spin_lock_irqsave]]'' și ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L363|spin_unlock_irqrestore]]''. | ||
+ | |||
+ | În rutina de tratare a întreruperii, folosim funcțiile ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L308|spin_lock]]'' și ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L348|spin_unlock]]'' pentru acces la resursa partajată. | ||
+ | |||
+ | <note important> | ||
+ | Atunci când folosim argumentul ''flags'' în cazul funcțiilor ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L338|spin_lock_irqsave]]'' și ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L363|spin_unlock_irqrestore]]'', acesta este trimis prin valoare. Funcția ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L338|spin_lock_irqsave]]'' modifică valoarea flag-ului, dar această funcție este de fapt un macro și poate folosi flag-ul trimis prin valoare. | ||
+ | </note> | ||
==== Statistici despre întreruperi ==== | ==== Statistici despre întreruperi ==== | ||
- | Statistici despre întreruperile din sistem pot fi găsite în ''/proc/interrupts'' sau ''/proc/stat''. În fișierul ''/proc/interrupts'' apar numai întreruperile din sistem care au câte un handler asociat: | + | Informații și statistici despre întreruperile din sistem pot fi găsite în ''/proc/interrupts'' sau ''/proc/stat''. În fișierul ''/proc/interrupts'' apar numai întreruperile din sistem care au câte un handler asociat: |
<code> | <code> | ||
Line 344: | Line 417: | ||
Fiecare linie din fișierul ''/proc/stat'' începe cu un text care specifică semnificația informațiilor de pe linie; pentru informații despre întreruperi, acest text este ''intr''. Primul număr de pe linie reprezintă numărul total de întreruperi, iar celelalte numere reprezintă numărul de întreruperi pentru fiecare IRQ, începând de la 0. Valoarea contorului reprezintă numărul de întreruperi pentru toate procesoarele din sistem. | Fiecare linie din fișierul ''/proc/stat'' începe cu un text care specifică semnificația informațiilor de pe linie; pentru informații despre întreruperi, acest text este ''intr''. Primul număr de pe linie reprezintă numărul total de întreruperi, iar celelalte numere reprezintă numărul de întreruperi pentru fiecare IRQ, începând de la 0. Valoarea contorului reprezintă numărul de întreruperi pentru toate procesoarele din sistem. | ||
- | |||
- | |||
- | |||
- | |||
- | |||
- | |||
- | |||
===== Resurse utile ===== | ===== Resurse utile ===== | ||
Line 361: | Line 427: | ||
==== Port paralel ==== | ==== Port paralel ==== | ||
- | * [[http://elf.cs.pub.ro/so2/res/lab06/Port_paralel.pdf|Documentație port paralel]] | ||
- | * [[http://elf.cs.pub.ro/so2/res/lab06/Par_control_lin.zip|Program de test pentru portul paralel (Linux)]] | ||
* [[http://www.beyondlogic.org/spp/parallel.htm|Interfacing the Standard Parallel Port]] | * [[http://www.beyondlogic.org/spp/parallel.htm|Interfacing the Standard Parallel Port]] | ||
- | * [[http://www.lvr.com/parport.htm|Parallel Port Central]] | ||
- | * [[ftp://ftp.armory.com/pub/user/rstevew/LPT/|Resurse suplimentare port paralel]] | ||
==== Controller tastatură ==== | ==== Controller tastatură ==== | ||
* [[http://en.wikipedia.org/wiki/Intel_8042|Intel 8042]] | * [[http://en.wikipedia.org/wiki/Intel_8042|Intel 8042]] | ||
- | * [[http://gunnarwrobel.de/wiki/Linux-and-the-keyboard.html|Linux and the keyboard]] | + | * [[https://elixir.bootlin.com/linux/v4.15/source/drivers/input/serio/i8042.c|drivers/input/serio/i8042.c]] |
- | * [[http://lxr.linux.no/linux+v2.6.32/drivers/input/serio/i8042.c|drivers/input/serio/i8042.c]] | + | * [[https://elixir.bootlin.com/linux/v4.15/source/drivers/input/keyboard/atkbd.c|drivers/input/keyboard/atkbd.c]] |
- | * [[http://lxr.linux.no/linux+v2.6.32/drivers/input/keyboard/atkbd.c|drivers/input/keyboard/atkbd.c]] | + | |
==== Linux ==== | ==== Linux ==== | ||
Line 379: | Line 440: | ||
* [[http://lwn.net/images/pdf/LDD3/ch10.pdf|Linux Device Drivers, 3rd ed., Ch. 10 - Interrupt Handling]] | * [[http://lwn.net/images/pdf/LDD3/ch10.pdf|Linux Device Drivers, 3rd ed., Ch. 10 - Interrupt Handling]] | ||
* [[http://tldp.org/LDP/lkmpg/2.6/html/x1256.html|Interrupt Handlers]] | * [[http://tldp.org/LDP/lkmpg/2.6/html/x1256.html|Interrupt Handlers]] | ||
- | |||
- |