Linux Device Driver 2009/04/08 Reference Book Another Reference Book Embedded Linux Primer: A Practical, Real-World Approach By Christopher Hallinan Publisher: Prentice Hall Pub Date: September 18, 2006 Print ISBN-10: 0-13167984-8 Print ISBN-13: 978-0-13167984-9 Pages: 576 Website • • • • http://wiki.openwrt.org http://www.linuxsir.org http://www-128.ibm.com/developerworks/ http://lwn.net Task of Device Driver • Device initialization • Hardware operation and management • Data transfer between kernel space and user space • Data exchange between hardware and kernel space Function of device driver User space Application Kernel Space Buffer Device Driver Hardware User space vs Kernel Space program ——From the point of view of CPU Execution core INT mode reg. data address memory access surveillance memory address mapping Memory User space vs Kernel Space program ——From the point of view of CPU Soft interrupt 用户应用程序 用户应用程序 User space 用户应用程序 program Kernel schedule/ Kernel API Scheduler, change mode reg. to enter different mode Mode reg. 内核程序 内核程序 Kernel space 内核程序 program Address Mapping and accessing of the physical address User processs1 User processs2 User process 3 Virtual address mapping Virtual address mapping Virtual address mapping Physical Address space user program access virtual using pointers physical address of IO device cannot be accessed by user program directly Basic operation of device driver • Device controller is often mapped into memory address Data bus CPU Address bus D Q D Q D Q Address matching circuit Device circuits Basic operation of device driver Data bus CPU D Q D Q D Q Address bus Address matching circuit Device circuits User space vs Kernel Space program User space program • Limited priority • Virtual run environment – Logic address – Key resource access is difficult • User invoke function directly Kernel Space program • Highest priority • Physical run environment – Virtual address – Access all resource • Kernel invoke function Direct memory access(/dev/kmem) kmfd = open("/dev/kmem", O_RDONLY ); lseek( kmfd, offset, SEEK_SET ); read( kmfd, byteArray, byteArrayLen ); close(kmfd); • memory is mapped into device file, and can be memory accessed by file read/write • Can access kernel address(virtual address of kernel) • Most started from 0xC0000000 offset Access Physical address directly(/dev/mem) mem_fd = open("/dev/mem", O_RDONLY ); b=mmap(0, 0x10000, PROT_READ|PROT_WRITE,MAP_SHARED, mem_fd,0xA0000) … May not bye close(memfd); memory device file Pointer b 0xA0000 mmap mapping data in file into array Physical memory(accessed by special file /dev/mem) s mapped into array pointed by b 0xB0000 Note that value of B may not be 0xA0000, its value is a virtual address coressponding to the physical address 0xA0000 under Linux, /dev/mem is for special memory access, such as video memory Directly access IO port(/dev/port) port_fd = open("/dev/port", O_RDWR); lseek(port_fd, port_addr, SEEK_SET); read(port_fd, …); write(port_fd, …); close(port_fd); • Note, not use fopen/fread/fwrite/fclose, because data operation by these function is not fulfilled immediately (buffered) outb()/outw()/inb()/inw() Function #include <stdio.h> • outb(value, port); inb(port); // 8-bit • outw(value, port); inw(port); // 16-bit #include <unistd.h> • access time is about 1us #include <asm/io.h> #define BASEPORT 0x378 // printer int main() { ioperm(BASEPORT, 3, 1)); // get access permission outb(0, BASEPORT); usleep(100000); printf("status: %d\n", inb(BASEPORT + 1)); ioperm(BASEPORT, 3, 0)); // give up exit(0); • ioperm(from,num,turn_on) • port address that can be obtained from ioperm is } 0x000~0x3FF,use iopl() can obtain all port address • must run as root • use “gcc -02 –o xxx.elf xxx.c” to compile Access memory directly by user space program ——why we need device driver • Share devices • INT management Safe device access method —— Using Linux device driver • Device driver can access device address by pointer • device driver also use virtual address but more ‘real’ than user application(device address mapping can be found in transplanting Linux Kernel) Device Driver Virtual address mapping Device address mapping Device Driver Virtual address mapping Device address mapping Physical address space Device address Space Direct access IO port vs Using device driver • • • • Direct IO Access User space simple poll mode, slow (response time) difficult to share devices Access by device driver • Kernel space • difficult to debug and programming • can use fast INT mode, realtime • easy to implement device sharing (managed by OS) Device Classification in Linux • Character device – Mouse、Serial port、joystick • Block device – Printer , hard disk • Network device – Access by BSD Socket Character device vs Block device Character device Block device • Read/write operation is fulfilled immediately • data buffer is optional • need data buffer to reduce device write/read operation • For slow device such as hard disk • ADC/DAC、button、 LED、sensor Dynamically installable device vs static linked device driver • Static linked device driver – Change configuration file, re-compile and install kernel • Dynamically instable device driver – insmod – rmmod – lsmod install removal query Abstraction of devices under Linux Device file • Open/Close/Read/Write • Example – /dev/mouse – /dev/lp0 universal IF Device Driver and File Access by open/read/write/close API Similar method Device File Application Create by command mknod Find real device river by major device number Device driver installed by command insmod (or statically compiled into kernel) Device Driver and File User space User application Name of device file Device File device ID read() name of vice file write() open() close() device ID Deevice Driver Kernel Space read() write() open() close() Register and unregister Structure of Device Driver device file operation function (API) (*open)() (*write)() (*flush)() (*llseek)() … ISR Example of LED device driver CPU struct file_operations LED_fops = { read: LED_read, write: LED_write, open: LED_open, release: LED_release, pointers of }; Program list (1) functions int LED_init_module(void) { SET_MODULE_OWNER(&LED_fops); LED_major = register_chrdev(0, "LED", &LED_fops); LED_off(); invoked when device LED_status=0; is installed return 0; } void LED_cleanup_module(void) invoked when device is uninstalled { unregister_chrdev(LED_major, "LED"); } Tell kernel the name of function to b module_init(LED_init_module); invoked when install or uninstall dev module_exit(LED_cleanup_module); driver program(2) int LED_open(struct inode *inode, struct file *filp) { printk("LED_open()\n"); MOD_INC_USE_COUNT; new vision Linux doesn’t use this Macro return 0; } int LED_release(struct inode *inode, struct file *filp) { printk(“LED_release()\n“); new vision Linux doesn’t MOD_DEC_USE_COUNT; use this Macro return 0; } Program List(3) ssize_t LED_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) { int i; for (i=0; i<count; i++) *((char*)(buf+i)) = LED_Status; return count; } ssize_t LED_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { int i; for (i=0; i<count; i++) if (*((char*)(buf+i))) LED_on(); else LED_off(); return count; } (*((volatile unsigned int *)(0xXXXXXXXX))) |= MASK; (*((volatile unsigned int *)(0xXXXXXXXX))) &=~MASK; #ifndef __KERNEL__ #define __KERNEL__ #endif #ifndef MODULE #define MODULE #endif #include <linux/config.h> #include <linux/module.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/malloc.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/interrupt.h> #include <linux/in.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/skbuff.h> #include <sysdep.h> #include <linux/ioctl.h> #include <linux/in6.h> #include <asm/checksum.h> MODULE_AUTHOR("Rendong Ying"); int LED_major, LED_status; Program List(4) Header Device ID, uer can manually write the number or use automatically assigned number by kernel Compile(Makefile) CC = arm-elf-linux-gcc LD = arm-elf-linux-ld INCLUDE = /usr/local/src/bspLinux/include LIB_INC = /usr/local/lib/gcc-lib/arm-elf-linux/2.95.3/include CFLAGS = -O6 -Wall -DCONFIG_KERNELD -DMODULE -D__KERNEL__ -DLinux -nostdinc -I- -I . -I$(INCLUDE) -idirafter $(LIB_INC) LED.o: LED.c $(CC) $(CFLAGS) -c LED.c clean: rm -f LED.o generate *.o install device driver and create device file • chmod +x /tmp/LED.o • /sbin/insmod -f ./LED.o • cat /proc/devices get device ID instralled into the memory • mknod /dev/Lamp c Num1 Num2 Num1 major device ID Num2 Minor device IDforce install, ignore version checking can be any value assigned in the program: LED_major install device driver User space /sbin/insmod -f ./LED.o Kernel Space Device Driver read() write() open() close() Create device file User space cat /proc/devices mknod /dev/Lamp c Num1 Major device Num2 Minor device obtain major device ID Num1 Num2 ID ID device ID Device File Device ID Device Driver Kernel Space read() write() open() close() Test and use device driver • command open printk,you may read log information in /var/log/messages echo 8 > /proc/sys/kernel/printk cat /dev/Lamp cat > /dev/Lamp • program void main() { int fd=open(“/dev/Lamp, O_RDWR); write(fd, &data, 1); close(fd); } Use of device driver User space void main() { int fd=open(“/dev/Lamp, O_RDWR); write(fd, &data, 1); close(fd); } Device File Device Driver Kernel Space read() write() open() close() Uninstall device /sbin/rmmod LED rm -f /dev/Lamp remove device driver delete device file Function of MOD_INC_USE_COUNT; MOD_DEC_USE_COUNT; Uninstall device User space rm -f /dev/Lamp Device File /sbin/rmmod LED Device Driver Kernel Space read() write() open() close() Complex Device Driver Data in user space Kernel space buffer register and unregister (device and INT) Divice operation API(function) (*open)() (*write)() (*flush)() (*llseek)() … ISR Complex Device Driver (USB Device) Application of IRQ INT number, determined by hardware, kernel find INT number from INT status reg. Then call registed function on that number • if (request_irq(USB_INTR_SOURCE1, ISR pointer usb_ep1_int, INT ID Name point to ISR SA_INTERRUPT, mask same "USB EP1", INT 0) < 0) printk("Int. req. failed !\n"); • free_irq(USB_INTR_SOURCE0, 0); cat /proc/interrupts check register INT ISR • No return value • Fats and small Specific format for function declaration void usb_ep1_int(int irq, void *dev_id, struct pt_regs *regs) { //… } ISR that receive data void usb_ep1_int(int irq, void *dev_id, struct pt_regs *regs) { read_data_from_hardware_FIFO(); send_data_to_buffer(); } ISR that send data void usb_ep2_int(int irq, void *dev_id, struct pt_regs *regs) { read_data_from_buffer(); send_data_to_hardware_FIFO (); } read function ssize_t usb_ep1_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) { if (data_buffer_empty()) return 0; else copy_data_to_user_space(); return data_copyed; } copy_to_user(user_buf, device_driver_buf, size); read function (blocking mode) ssize_t usb_ep1_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) { while(device_driver_buf_empty()) { if (wait_event_interruptible(q_ep2, device_driver_buf_not_empty)) return -ERESTARTSYS; } copy_data_to_user_space(); return data_copyed; } wait_queue_head_t rq_EP2; queue when multiple user application init_waitqueue_head(&rq_EP2); access this device write function ssize_t usb_ep2_write (struct file *filp, char *buf, size_t count, loff_t *f_pos) { if (data_buffer_full()) return 0; else copy_data_to_device_driver_buf(); if (no_transmission_now) send_1st_data(); return data_copyed; } copy_from_user(device_driver_buf, user_buf, size); Memory allocation by device driver • malloc ? • kmalloc • kfree • vmalloc • vfree Prevent open device several times int LED_flag; int LED_init_module(void) { LED_flag=0; … } int LED_open(struct inode *inode, struct file *filp) { if (LED_flag=0) need semphora { LED_flag=1; MOD_INC_USE_COUNT; to avoid access conflict return 0; } else } return -ENODEV; int LED_release(struct inode *inode, struct file *filp) { LED_flag=0; MOD_DEC_USE_COUNT; return 0; } Manage several device by one device driver Application Serial Port Device Driver UART 0 UART 0 Create device file User space cat /proc/devices mknod /dev/Lamp c Num1 Major device Num2 Minor device Device File device Device file File Obtain device ID Num1 Num2 ID ID • Minor dev. ID is used to identify devices that is opened Device Driver read() write() open() close() Kernel Space Manage several device by one device driver int dev_open(struct inode *inode, struct file *filp) { int minor = MINOR(inode->i_rdev); filp->private_data=sub_dev_dat[minor]; … Allocate data space for each device } ssize_t dev_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) { switch(*(filp->private_data)) { … } use data space allocated for the opened } device Debug of Character device driver Application (using device) 设备文件 1 debug and monitor Virtual Serial port, canbe access by minicom 设备文件 2 Device Read Debug Read Device Write Debug Write Device Open Debug Open Device Close Debug Close Device Driver Kernel