Character and Block Device Drivers
Character and block device drivers are the two main types of peripheral drivers. A disk drive is an example of a block device, whereas, terminals and line printers are examples of character devices.
A block device driver is accessed by user programs through a system buffer that acts as a data cache. Specific allocation and memory management routines are not necessary as the system transfers the data to/from the device. Character device drivers communicate directly with the user program, as there is no buffering performed. Linux transfers control to the appropriate device driver when a user program requests a data transfer between a section of its memory and a device. The device driver is responsible for transferring the d ata. Within Linux, the source for character drivers is kept in the /usr/src/linux/drivers/char directory. This article only addresses the development of character device drivers.
Kernel Programming Environment
A Linux user process executes in a space isolated from critical system data and other user processes. This protected environment provides security to protect the process from mistakes in other processes. By contrast, a device driver executes in kernel mode, which places few limits on its freedom of action. The driver is assumed to be correct and responsible. A driver has to be part of the kernel in order to service interrupts and access device hardware. A driver should process interrupts efficiently to preserve the schedulerýs ability to balance the demands on the system. It should also use system buffers responsibly to avoid degrading system performance.
A device driver contains both interrupt and synchronous sections. The interrupt section deals with re al-time events and is driven by interrupts from devices. The synchronous section, which comprises the remainder of the driver, only executes when the process which it serves is also active. When a device requests some software service, it generates an ``interrupt.'' The interrupt handler must determine the cause of the interrupt and take appropriate action.
A Linux process might have to wait for an event to occur before it can proceed. For example, a process might wait for requested information to be written to a hardware device before continuing. One way that processes can coordinate their actions with events is through sleep() and wakeup() system calls. When a process goes to sleep, it specifies an event that must occur, that is, wakeup, before it can continue its task. For example: interruptible_sleep_on(&dev_wait_queue) causes the process to sleep and adds the process number to the list of processes sleeping on dev_wait_queue . When the devi ce is ready, it posts an interrupt, causing the interrupt service routine in the driver to be activated. The routine services the device and issue a corresponding wakeup call, for example, wake_up_interruptible(&dev_wait_queue) , which wakes up the process sleeping on dev_wait_queue .
Special care must be taken if two or more processes, such as the synchronous and interrupt portions of a device driver, share common data. The shared data area must be treated as a critical section. The critical section is protected by ensuring that processes only have mutually exclusive access to the shared data. Mutually exclusive access to a critical section can be implemented by using the Linux kernel routines cli() and sti() . Interrupts are disabled by cli() while the process is operating in the critical section and re-enabled by sti() upon exit from the critical section, as in:
cli()
Critical Section Operations
sti
()
Virtual File system Switch (VFS)
The principal interface between a device driver and the rest of the Linux kernel comprises a set of standard entry points and driver-specific data structures (see Figure 6 ).
Listing 2 illustrates how the entry points are registered with the Virtual File system Switch using the file_operations structure. This structure, which is defined in /usr/include/linux/fs.h , constitutes a list of the functions written for the driver. The initialization routine, xxx_init() registers the file_operations structure with the VFS and allocates a major number for the device.
Device Driver Development Supporting Functions
The table below contains most of the common supporting functions available for writing device drivers. See also the Kernel Hackers' Guide [John93] for a more detailed ex planation:
add_timer()- Causes a function to be executed when a given amount of time has passed
cli()- Prevents interrupts from being acknowledged
end_request()- Called when a request has been satisfied or aborted
free_irq()- Frees an IRQ previously acquired with
request_irq()orirqaction() get_fs*()- Allows a driver to access data in user space, a memory area distinct from the kernel
inb(), inb_p()- Reads a byte from a port. Here,
inb()goes as fast as it can, whileinb_p()pauses before returning. irqaction()- Registers an interrupt like a signal.
IS_*(inode)- Tests if inode is on a file system mounted with the corresponding flag.
kfree*()- Frees memory previously allocated with
kmalloc() kmalloc()- Allocates a chu nk of memory no larger than 4096 bytes.
MAJOR()- Reports the major device number for a device.
MINOR()- Reports the minor device number for a device.
memcpy_*fs()- Copies chunks of memory between user space and kernel space
outb(), outb_p()- Writes a byte to a port. Here,
outb()goes as fast as it can, whileoutb_p()pauses before returning. printk()- A version of
printf()for the kernel. put_fs*()- Allows a driver to write data in user space.
register_*dev()- Registers a device with the kernel.
request_irq()- Requests an IRQ from the kernel, and, if successful, installs an IRQ interrupt handler.
select_wait()- Adds a process to the proper
select_waitqueue. *sleep_on()- Sleeps on an event, puts a
wait_queueentry in the list so that the process can be awakened on that event. sti()- Allows interrupts to be acknowledged.
sys_get*()- System calls used to get information regarding the process, user, or group.
wake_up*()- Wakes up a process that has been put to sleep by the matching
*sleep_on()function.
Name space
The name of the driver should be a short string. Throughout this article we have used "xxx" as our device name. For instance, the parallel (printer) device is the ``lp'' device, the floppies are the ``fd'' devices, and the SCSI disks are the ``sd'' devices. To avoid name space confusion, the entry point names are formed by concatenating this unique driver prefix with a generic name that describes the routine. For instance, xxx_open() is the ``open'' routine for the ``xxx'' driver.
Accessing Hardware Memory
A Linux user process can not access physical memory directly. The memory management sc heme--which is a demand paged virtual memory system--means that each process has its own address space (user virtual address space) that begins at virtual location zero. The kernel has its own distinct address space known as the system virtual address space.
The device driver copies data between the kernel'ýs address space and the user program'ýs address space whenever the user makes a read() or write() system call. Several Linux routines--such as, memcpy_*fs() and put_fs*() --enable device drivers to transfer data across the user-system boundary. Data may be transferred in bytes, words, or in buffers of arbitrary sizes. For example, memcpy_fromfs() transfers an arbitrary number of bytes of data from user space to the device, while get_fs_byte() transfers a byte of data from user space. Similarly, memcpy_tofs() and put_fs_byte() write data to user space memory.
The transfer of data betwee n the memory accessible to the kernel and the device itself is machine-dependent. Some machines require that the CPU execute special I/O instructions to move data between a device register and addressable memory--often called direct memory access (DMA). Another scheme, known as memory mapped I/O, implements the device interface as one or more locations in the memory address space. The most common method uses I/O instructions, provided by the system to allow drivers access the data in a general way. Linux provides inb() to read a single byte from an I/O address (port) and outb() to write a single byte to an I/O address. The calling syntax is shown here:
unsigned char inb(int port)
outb(char data, int port)
Writing a Character Device Driver
Listing 3 shows a sample xxx_write() routine where the device driver would, typically, poll the hardware to determine if it is ready to transfer data. The xxx_writ e() routine transfers a character string of count bytes from the user-space memory to the device. Using interrupts, the hardware is able to interrupt when it is ready to transfer data and so there is no waiting. Listing 4 outlines an alternative xxx_write() routine for an interrupt-driven driver.
Here, xxx_table[] is an array of structures, each of which have several members. Some of the members include xxx_wait_queue and bytes_xfered , which are used for both reading and writing. The interrupt-handling code can use either request_irq() or irqaction() in the xxx_open() routine to call xxx_interrupt() .
Listing 5 presents an example of a complete device driver (for the bus mouse). The source listing contains the code for a typical bus mouse driver, such as the Logitec bus mouse or the Microsoft bus mouse.
Device Driver Initialization
In order that the device driver is correctly initialized when the operating system is booted, the xxx_init() routine must be executed. To ensure this happens, add the following line to the end of the chr_drv_init() function in the /usr/src/linux/driver/char/mem.c file:
mem_start = xxx_init(mem_start);
and resave the file back to disk.
Installing the Driver in the Kernel
A character device driver has to be archived into the /usr/src/linux/drivers/char/char.a library. The following steps are required to link the driver to the kernel:
- Put a copy of the source file (say
xxx_drv.c) in the/usr/src/linux/drivers/chardirectory. - Edit
Makefilein the same directory so it will compile the source for the driver--addxxx_drv.oto theOBJSlist, which causes themakeutility to automatically compi lexxx_drv.cand add the object code to thechar.alibrary archive. - The last step step is the recompilation of the kernel.
The following steps are required to recompile the Linux kernel:
- Log in as root
- Change to the
/root/linuxdirectory - Carry out the following series of commands
make clean ; make configto configures the basic kernelmake depto set-up the dependencies correctlymaketo create the new kernel
- Wait for the kernel to compile and go to the
/usr/src/linuxdirectory. - In order to boot the new kernel, copy the new kernel image (
/usr/src/linux/zImage) into the place where the regular bootable kernel is found.
Device File Creation
In order to access the device using system calls, a special file is created. The driver files are normally stored in the /dev directory of the system. The following commands create the special device file:
mknod /dev/xxx c 22 0- Creates a special character file named
xxxand gives it major number 22 and minor number 0. chmod 0666 /dev/xxx- Ensures that every user in the system has read/write access to the device.
No comments:
Post a Comment