Embedded Systems Programming Writing Device Drivers Writing a
Embedded Systems Programming Writing Device Drivers
Writing a new device driver • When writing a device driver you can either – Create a new, bespoke driver from scratch • Or – Port an existing driver • Generally pick option 2 unless – The hardware is so new to make porting impossible – You are really experienced in device driver writing – The driver has to do something really special or diffferent
Linux Execution Environment
Linux Execution Paths • Execution paths
Kernel Modules • Kernel modules are inserted and unloaded dynamically – Kernel code extensibility at run time – insmod / lsmod/ rmmod commands. Look at /proc/modules – Kernel and servers can detect and install them automatically, for example, cardmgr (pc card services manager) • Modules execute in kernel space – Access to kernel resources (memory, I/O ports) and global variables ( look at /proc/ksyms) – Export their own visible variables, register_symtab (); – Can implement new kernel services (new system calls, policies) or low level drivers (new devices, mechanisms) – Use internal kernel basic interface and can interact with other modules (pcmcia memory_cs uses generic card services module) – Need to implement init_module and cleanup_module entry points, and specific subsystem functions (open, read, write, close, ioctl …)
#include <linux/delay. h> #include <linux/module. h> #include <linux/ioport. h> #include <asm/io. h> #include <asm/arch/hardware. h> #include <asm/uaccess. h> #define DRIVER_AUTHOR "craig duffy craig. duffy@uwe. ac. uk" #define DRIVER_DESC "FPGA DIO driver" #define LED_ADDRESS #define RGGG 0 xf 000 0 xf 2400680 static int fpga_dio_init(void) { static int fpga_j; static short unsigned pattern; printk(KERN_ALERT "fpga dio loadedn"); pattern=RGGG; pattern = pattern >> 4; for ( fpga_j=0; fpga_j != 16 ; fpga_j++) { printk("pattern %xn", pattern); udelay(400); writew(pattern, LED_ADDRESS); pattern = pattern >> 8; pattern--; pattern = pattern << 8; }*/ return 0; } static void fpga_dio_exit(void) { printk(KERN_ALERT "fpga dio unloadedn"); } module_init(fpga_dio_init); module_exit(fpga_dio_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_SUPPORTED_DEVICE("fpga_dio");
Linking a module to the kernel (from Rubini’s book)
Module programming • Be careful: a kernel fault is fatal to the current process and sometimes the whole system • Modules should support concurrency (support calls by different processes). Distinct data structures for each process (since the same code is executed) to ensure data is not corrupted. • Driver code must be reentrant: keep status in local (stack allocated) variables or dynamic memory allocation: kmalloc / kfree • This allows the process executing to suspend (e. g. , wait for pcmcia card interrupt) and other processes to execute the same code. • It is not a good idea to assume your code won’t be interrupted. • Sleep_on(wait_queue) or interruptible_sleep_on(wait_queue) to yield the cpu to another process • /proc/ioports contains information about registered ports. /proc/iomem contains info about I/O memory
Register Capability • You can register a new device driver from the kernel: – int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); – A negative return value indicates an error, 0 or positive indicates success. – major: the major number being requested (a number < 128 or 256). – name: the name of the device (which appears in /proc/devices). – fops: a pointer to a global jump table used to invoke driver functions. • Then give to the programs a name by which they can request the driver through a device node in /dev – To create a char device node with major 254 and minor 0, use: • mknod /dev/memory_common c 254 0 – Minor numbers should be in the range of 0 to 255.
Finding a device driver • Obviously you need to be able to categorise the device – Some Linux categories are not clear • You can look at other open systems – for example Net. BSD – This is useful as they may have ported the driver themselves • Get as much reliable information about the device – Data sheets, tested, stand alone code
Example of a Parallel port • It is quite often useful to use a parallel port as an interface to some specialised hardware. • There a number of approaches you could take to controlling such hardware from Linux – A simple, low level kernel device driver – Porting the parallel port package, ppdev, – Writing a full blown device driver through the file system – Writing a driver through the /procfs interface
A simple low level kernel driver • Under Linux it is always possible to write directly to the hardware • This uses some low level calls to read and write data – inb(), outb(), inw(), outw() • The areas of memory are maped by the kernel and ioremap/iounmap shopuld be used • Data has to be copied to and from user space unto kernel space
Problems with low level driver • It is a good idea to use to test the hardware but isn’t a long term solution for driver development – Requires root access to run the kernel • No file system interface – Will be very limited functionality – Can’t use the kernel very effectively – Isn’t portable – Can crash the kernel
Porting an existing device • With the parallel port one can use ppdev • This give a high level interface into the file system through /dev/parport 0 -n – Don’t need root access • Can have well structured driver • Should be ported (in-time) with new kernel releases • Less likely to cause kernel panics
Problems with ppdev • Unless it is a very standard parallel port application it is likely to be difficult to get the correct level of functionality • Will require understanding a lot of details of parallel port and the driver which won’t be useful for your app
Writing a device driver • This allows you to create a new device in the system • Uses VFS calls to access the drive – Can open, read, write and close – Uses ioctl calls • Driver can be a module – Only loaded when needed – Easier development
Problems
- Slides: 17