ot PxL(22-02-2007)

reiting (51)   [ dobre ]  [ zle ]

Printer Friendly Variant za otpechatvane

.::Linux draivuri::.

.:: Koi? Kude? Zashto?
Predi izvestno vreme biah mnogo zapalen da napisha draivur za iadroto na Linux.Izvestno mi beshe gore-dolu kak se pishe draivur i kak raboti takuv, suotvetno imah i izvestni poznaniia po C.Obshto vzeto tova bi triabvalo da e iziskvaneto za pisane na draivuri za ustroistva. Kato tsialo podobni materiali ne lipsvat v internet, no ne sum sreshtal na bulgarski.

.:: Niva na komunikatsiia
Kakto predpolagam e izvestno na povecheto Linux potrebiteli, ustroistvata tam sa predstaveni kato failove, namirat se v /dev chasta...Otsustva portovata komunikatsiia...? Po-skoro komunikatsiiata s fizicheskite ustroistva se osushtestviava na dve niva. Realnata komunikatsiia s harduernoto ustroistvoto se sushtestviava ot t.nar. kernel space nivo, kudeto iadroto i modulite mu se grizhat da predavat informatsiiata ot i za fizicheskite ustroistva kum suotvetstvashtite im takiva predstaveni ot failove. Drugata chast se naricha user space, kudeto aplikatsiite komunikirat s tezi failove.

.:: Kernel space / User space
Kato nachalo mozhem da kazhem nakratko, che modulite sa prilozheniia, chiiato ideia e da se razshiri i lesno da se dobavia funktsionalnost kum iadroto. V zavisimost ot tipa na iadroto (v konkretniiat sluchai viziram Linuks iadro) modulite mogat da se dobaviat dinamichno, t.e. ne e nuzhna prekompilatsiia i restart na samoto iadro za da bude dobaven nov modul. Po-spetsialno ni interesuvat t.nar. monolitic tip iadra, tui kato Linux iadroto e takuv tip. Pri monolitic tipovete iadra (za razlika naprimer ot microkernel tipovete) modulite izpolzvat pametta zadelena za iadroto. Tova oznachava, che vsichki globalni danni sa vidimi za vsichki moduli. Imenno poradi tova pisaneto na moduli triabva da e mnogo strogo standartizirano i e za predpochitane da se polzvat static promenlivi..
Kakto spomenah po-gore tselta na draivurite v kernel space e da svurzhat failovete na ustroistvata v /dev sus samit eharduerni ustroistva.Modulite v kernel space (LKM - Loadable Kernel Modul) obmeniat danni s potrebitelskite aplikatsii v user space chrez callback funktsii v iadroto.Predstaveno vizualno tova bi izglezhdalo taka:

Za da ne oburkam niakogo ili sebe si napravo shte dam primer za prost kernel modul.

simple.c
#include <linux/module.h>
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Dimiter Todorov Dimitrov");
 MODULE_DESCRIPTION("Simple kernel-space module");
 MODULE_SUPPORTED_DEVICE("testdevice");

Kakto zabeliazvate izpolzvam niakolko makrosa, chrez koito opredeliame litsenz i informatsiia za modula. Definirani sa v module.h neobhodimost za vseki edin kernel modul (vim /usr/include/linux/module.h).

Za da kompilirame shte ni triabva elementaren Makefile
Makefile
KDIR:=/lib/modules/$(shell uname -r)/build
 
 obj-m:=simple.o
 
 default:
 $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
 clean:
 $(RM) .*.cmd *.mod.c *.o *.ko -r .tmp* *.symvers
Neobhodimo e za kompilatsiia da se polzva iadroto, v koeto shte zaredite modula. Dobavil sum i optsiia za clean sled kompilatsiia.

Zapazvame i kompilirame:
Primeren kod
jorko tut $ make; ls
 make -C /lib/modules/2.6.18/build SUBDIRS=/pxl/Devel/tut modules
 make[1]: Entering directory `/usr/src/linux-2.6.18'
 CC [M]  /pxl/Devel/tut/simple.o
 Building modules, stage 2.
 MODPOST
 CC      /pxl/Devel/tut/simple.mod.o
 LD [M]  /pxl/Devel/tut/simple.ko
 make[1]: Leaving directory `/usr/src/linux-2.6.18'
 Makefile        simple.c   simple.mod.c  simple.o
 Module.symvers  simple.ko  simple.mod.o
 jorko tut $

Kompiliraniiat modul simple.ko mozhem da zaredim ot user space v iadroto, chrez insmod, i da razgledame s lsmod.
Primeren kod
jorko tut # insmod simple.ko
 jorko tut #
 jorko tut # lsmod | grep simple
 simple                  2176  0
 jorko tut #      
Modula e zareden i raboti v kernel space. Mozhem da go premahnem s rmmod.
Primeren kod
jorko tut # rmmod simple
.:: Device failove
V gorniiat primer suzdadohme modul, koito defakto ne pravi nishto osven da se zaredi v kernel space. Pri zarezhdane na moduli v povecheto sluchai e neobhodimo da izvurshite operatsiia, naprimer initsializatsiia na ustroistvo, danni i t.n. Za tselta sa predostaveni dve funktsii: module_init() i module_exit().

Neka razshirim nashiiat primer:
Primeren kod
#include <linux/module.h>
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Dimiter Todorov Dimitrov");
 MODULE_DESCRIPTION("Simple kernel-space module");
 MODULE_SUPPORTED_DEVICE("testdevice");
 
 static int simple_init(void) {
         printk("Simple module sayes: Hello!\n");
         return 0;
 }
 
 static void simple_exit(void) {
         printk("Simple module unloaded\n");
 }
 
 module_init(simple_init);
 module_exit(simple_exit);

Izpolzvame callback funktsiite predostaveni ot iadroto, za da izpulnim operatsiia pri zarezhdane i premahvane na nashiiat modul. V sluchaia polzvam kernel funktsiiata printk, koiato zapazva sistemni suobshteniia v sistemniiat log. Za da gi vidim mozhem da polzvame dmesg. Po analogichen nachin kakto purviiat put zarezhdam modula i tursia za suobshteniiata v log-a:
Primeren kod
jorko tut # insmod simple.ko
 jorko tut # dmesg | grep Simple
 Simple module sayes: Hello!
 jorko tut # rmmod simple
 jorko tut # dmesg | grep Simple
 Simple module sayes: Hello!
 Simple module unloaded
 jorko tut #

.::Device failove
Kakto spomenah veche aplikatsiite ot user space komunikirat s modulite v iadroto chrez fail-ove namirashti se v /dev chastta. Za vsiako ustroistvo predostaveno v /dev otgovaria suotveten modul v iadroto. Za da se /naznachi/ daden modul kum daden fail se izpolzvat unikalni chisla (zhelatelno e te da sa unikalni, za da ne se oburkvat modulite, no e vuzmozhno da se dublirat, koeto bi dovelo do nai-razlichni /nepriiatni/ posledstviia). Tezi chisla sa narecheni major numbers, chrez tiah iadroto znae koi modul za koi fail otgovaria.Vsiako ustroistvo ima i minor number, koeto ne igrae rolia pri iadroto, to e neobhodimo na samiiat modul da razlichava svoite ustrostva. Vazhno e da znaem, che tezi ustroistva mogat da sa dva vida: character i block.Glavnata razlikata kakto mozhebi se doseshtate e, che block ustroistvata predavat dannite na otdelni fiksirani v zavisimost ot tipa na ustroistvoto blokove, dokato character ustroistvata niamat fiksiran razmer na predavanite danni. Eto edin primer:
Primeren kod
jorko tut $ ls -la /dev/hda*
 brw-rw---- 1 root disk 3, 0 Feb 18 13:18 /dev/hda
 brw-rw---- 1 root disk 3, 1 Feb 18 13:18 /dev/hda1
 brw-rw---- 1 root disk 3, 2 Feb 18 13:18 /dev/hda2
 brw-rw---- 1 root disk 3, 5 Feb 18 13:18 /dev/hda5
 brw-rw---- 1 root disk 3, 6 Feb 18 13:18 /dev/hda6
 brw-rw---- 1 root disk 3, 7 Feb 18 13:18 /dev/hda7
 brw-rw---- 1 root disk 3, 8 Feb 18 13:18 /dev/hda8

Vizhdate tipa na ustroistvoto [color=blue]b[/color]rw-rw----. Kakto i negovite major i minor nomera brw-rw---- 1 root disk [color=red]3[/color], [color=blue]8[/color] Feb 18 13:18 /dev/hda8
Mozhete da zabelezhite ot gorniiat primer, che za tezi ustroistva otgovaria edin modul, tui kato imat edin i sushti major nomer. Suotvetno minor nomerata sa razlichni, za da mozhe modulut da razlichava ustroistvata.
Za da znaem koga protses se opitva da chete ili pishe v dadeno ustroistvo e neobhodimo da polzvame struktura narechena file_operations, definirana v fs.h.
Primeren kod
struct file_operations {
         struct module *owner;
         loff_t (*llseek) (struct file *, loff_t, int);
         ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
         ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
         ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
         ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
         int (*readdir) (struct file *, void *, filldir_t);
         unsigned int (*poll) (struct file *, struct poll_table_struct *);
         int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
         long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
         int (*mmap) (struct file *, struct vm_area_struct *);
         int (*open) (struct inode *, struct file *);
         int (*flush) (struct file *);
         int (*release) (struct inode *, struct file *);
         int (*fsync) (struct file *, struct dentry *, int datasync);
         int (*aio_fsync) (struct kiocb *, int datasync);
         int (*fasync) (int, struct file *, int);
         int (*lock) (struct file *, int, struct file_lock *);
         ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
         ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
         ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
         ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
         unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
         int (*check_flags)(int);
         int (*dir_notify)(struct file *filp, unsigned long arg);
         int (*flock) (struct file *, int, struct file_lock *);
         ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
         ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
 };
Kakto sami zabeliazvate strukturata predostavia dosta ukazateli, kato povecheto ot tiah v sluchaia niama da ni interesuvat, taka che ne se striaskaite. Za tselta mozhem da polzvame GCC razshireniiat nachin za definirane na tazi struktura:
Primeren kod
struct file_operations fops = {
         read: device_read,
         write: device_write,
         open: device_open,
         release: device_release
 };

ili C99 standarta:
Primeren kod
struct file_operations fops = {
         .read = device_read,
         .write = device_write,
         .open = device_open,
         .release = device_release
 };

Nedefiniranite chlenove na strukturata shte se definirat avtomatichno ot gcc kato NULL.

V primera, shte izpolzvam komentari direktno, za da e po-razbiraemo:
Primeren kod
#include <linux/module.h> /* definicii na modul fukciite */
 #include <linux/kernel.h> /* Definiciq na printk() i konstanti za neq */
 #include <linux/fs.h> /* Definiciq na file_operations strukturata */
 #include <linux/uaccess.h> /* Za prenos ot user space kym kernel space */
 
 #define BUF_LEN 64 /* maksimalna dyljina na syobshteniqta */
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Dimiter Todorov Dimitrov");
 MODULE_DESCRIPTION("Simple kernel-space module");
 MODULE_SUPPORTED_DEVICE("testdevice");
 
 /* Prototipi na funkciite  */
 static int simple_init(void);
 static void simple_exit(void);
 
 static int simple_open(struct inode *inode, struct file *file);
 static int simple_release(struct inode *inode, struct file *file);
 static ssize_t simple_read(struct file *filp, char *buffer, size_t length, loff_t *offset);
 static ssize_t simple_write(struct file *filp, const char *buff, size_t len, loff_t *off);
 
 
 /* Deklaraciq na init i exit funkciite */
 module_init(simple_init);
 module_exit(simple_exit);
 
 
 /* Deklaraciq na strukturata za rabota s fajlove */
 struct file_operations fops = {
         read: simple_read,
         write: simple_write,
         open: simple_open,
         release: simple_release
 };
 
 /* Shte ni e neobhodim za da razberem kakyv Major nomer shte poluchi ustrojstvoto pri registraciq*/
 static int Major;      // Promenliva za Major nomera daden ni ot qdroto
 static int Device_Open = 0;  // Flag, koito shte polzvame dokato proces izpolzva ustrojstvoto
 static char msg[BUF_LEN]; // Bufer za syobshteniqta
 static char *msgPtr;      // Ukazatel kym bufera za syobshteniq
 
 
 
 /* Init i Exit funkcii */
 static int simple_init(void) {
         Major = register_chrdev(0, "simple", &fops); // Registrirane na ustrojstvoto
         
         if (Major < 0) {
                 printk ("Registering the character device failed with %d\n", Major);
                 return Major;
         }
         
         printk("Simple module loaded into kernel space!\n");
         printk("Simple module has been assigned to Major number %d\n", Major);
         printk("Create device file with: mknod /dev/simple c %d 0\n", Major);
         return 0;
 }
 
 static void simple_exit(void) {
         int ret = unregister_chrdev(Major, "simple");
         if (ret < 0)
         printk("Error in unregister_chrdev: %d\n", ret);
         
         printk("Simple module unloaded successfuly!\n");
 }
 
 
 /* Metodi ot file_operations */
 
 
 /*Izvikva se pri opit za otwarqne na fajlovoto ustrojstvo  */
 static int simple_open(struct inode *inode, struct file *file)
 {
         static int counter = 0;
         if (Device_Open)
         return -EBUSY; // Ustrojstvoto se izpolzva ot drug procesz
         
         Device_Open ++; // Vdigame flag, che ustrojstvoto e zaeto
         sprintf(msg,"You have accessed this device %d times!\n", counter++);
         msgPtr = msg;
         
         return 0;
 }
 
 static int simple_release(struct inode *inode, struct file *file)
 {
         Device_Open --; // Nulirame flaga, za da mojem da priemame i drugi procesi      
         return 0;
 }
 
 
 /* Izvikva se pri opit za chetene ot ustroistvoto */
 static ssize_t simple_read(struct file *filp, char *buffer, size_t length, loff_t *offset)
 {
         int bytes_read = 0; // Broqch
         
         if (*msgPtr == 0)
         return 0;
         
         while(length && *msgPtr)
         {
                 put_user(*(msgPtr++), buffer++); // Funkciqta kopira dannite ot kernel space kym user space
                 
                 length --;
                 bytes_read ++;
         }
         
         return bytes_read; // Vryshtame kato rezultat prochetenite baitove
 }
 
 static ssize_t simple_write(struct file *filp, const char *buff, size_t len, loff_t *off)
 {
         
         printk( "You don`t wanna write to me :)");
         return -EINVAL;
 }

Sled kato zaredim modula shte triabva da suzdadem fail za ustroistvoto v /dev s mknod kato mu zadadem Major number spored tova, kakuv Major number ni e opredelen ot iadroto
Primeren kod
jorko tut # insmod simple.ko
 jorko tut # dmesg| tail -n 1
 Create device file with: mknod /dev/simple c 253 0
 jorko tut # mknod /dev/simple c 253 0
 jorko tut # cat /dev/simple
 You have accessed this device 0 times!
 jorko tut # cat /dev/simple
 You have accessed this device 1 times!
 jorko tut # cat /dev/simple
 You have accessed this device 2 times!
 jorko tut # cat /dev/simple
 You have accessed this device 3 times!
 jorko tut # rmmod simple
 jorko tut # dmesg| tail -n 1
 Simple module unloaded successfuly
 jorko tut # rm /dev/simple

Veche znaem kak da suzdadem modul i da go asotsirame s user space ustroistvo.
Ostana edinstveno momenta, v koito realno shte komunikirame s harduer...

.:: Operatsii s harduer
Linuks predostavia niakolko funktsii za operatsii s harduera. Predi da se zaredi draivur, podoben na gorniiat toi triabva da /rezervira/ daden IO port za polzvane.Tova se osushtestviava chrez funktsiiata request_region iziskvashta nomer na port, obhvat ako sa poveche ot 1 vhodno-izhoden port i ime na draivura. Predi neia obache, za da se uveri, che niakoi drug modul ne polzva porta draivura triabva da izvika check_region, iziskvashta dva parametura: nomer na port i dulzhina (1 za konkreten port). Sled priklyuchvane na rabota s vunshnoto u-vo Draiuvura triabva da osvobodi port-a za da e vuzmozhno drugi draivuri da rabotiat s nego, za tselta se polzva release_region iziskvashta otnovo sushtite parametri. Za komunikatsiia s ustroistvata se polzvat funktsii ot bibliotekata io.h (asm/io.h). Dvete po-sushtestveni funktsii za pisane i chetene ot port sa: outb i inb

.:: Harduerni prekusvaniia
Za da raboti s harduerni prekusvaniia (IRQ - Interrupt Request), draivurut polzva request_irq i free_irq. Sushto mozhem da polzvame dve funktsii za vremenno prekratiavane na prekusvaniiata i suotvetno vuzstanoviavaneto im: cli i sti.

.:: DMA
DMA (Direct Memory Access) kanalite polzvat direktno RAM pametta, bez da nalagat prekusvane na protsesornata rabota. Za rabota s DMA se izpolzva bibliotekata asm/dma.h. Ustroistvata polzvashti DMA sa obiknoveno prikrepeni kum dunnata platka.

.:: KonkluzIoneto
Nai-dobriiat nachin da razberete neshto e da razgledate gotov primer i da testvate.

p.s.: Tazi statiia e provokirana ot osnovnoto solo v Sweet child o' mine na Guns 'n' Roses

Referentsii:
http://www.faqs.org/docs/kernel/
http://kernel.org/


Blagodarsko na @Angel ot http://forums.bgdev.org/ za suvetite otnosno kernel/user space grafikata.

Dimitur T. Dimitrov PxL 2007
http://insecurebg.org/
pxl at insecurebg


--EOF

Suzhaliavam ako imam netochnosti,pomagaite da gi opravim.


<< | Programirane grafichen interfeis (GUI) s Lazarus i freepascal >>