Anda di halaman 1dari 24

DEVICE DRIVER

Introduction to Linux 
Linux is a 32­bit multitasking, multimedia operating system with complete source code, developed b y 
the free software community on the Internet. Linux is a clone of the Unix operating system that runs on 
Intel 80386/80486/Pentium computers. It supports a wide range of software, from TEX to the X 
Window System, to the GNU C/C++ compiler, to TCP/IP. The Linux system is mostly compatible at 
the source level with a number of Unix standards including IEEE POSIX.1, System V, and BSD. Linux 
also provides a complete Unix programming environment, including standard libraries, programming 
tools, compilers, and debuggers. 

A device driver consists of a set of routines that control a peripheral device attached to a workstation. 
The operating system normally provides a uniform interface to all peripheral devices. Linux and Unix 
present peripheral devices at a sufficiently high level of abstraction by observing that a large proportion 
of I/O devices can be represented as a sequence of bytes. Linux and Unix use the file­­which is a well 
understood data structure for handling byte sequences­­to represent I/O devices. 

Linux I/O Subsystem 

Figure 1 shows the Linux architecture in the most general terms. Here, the kernel is shown wrapped 
around the hardware to depict that it is the software component that has direct access to­­and control 
over­­the system hardware, including the processor, primary memory, and I/O devices. 

 Figure 2  [Bach86] 
   shows that user­level programs communicate with the kernel using system calls, for 
instance, open(), read(), write(), ioctl(), close() , and the like. 
                
Fig1.

           
Fig2
Linux System Calls 

The kernel is not a separate task under Linux. It is as if each process has a copy of the kernel. When a 
user process executes a system call, it does not transfer control to another process, but changes its 
execution mode from user to kernel mode. In kernel mode, while executing the system call, the process 
has access to the kernel address space, and through supporting functions , it has access to the address 
space of the user executing the call. 

Figure 3 depicts the I/O Subsystem. The Linux kernel implements a device­independent I/O system 
that serves all devices. A device driver provides the I/O system with a standard interface to the 
hardware, hiding the unique characteristics of the hardware device from the user to the greatest extent 
possible.

             
Fig 3.

Listing 1 illustrates a user program that employs some basic system calls to read characters from a 
device into a buffer. When a system call is requested, the kernel transfers control to the appropriate 
device driver routine that executes on behalf of the calling user process (as shown previously with 
Figure 3). 

All devices look like files on a Linux system. In fact, the user­level interface to a device is called a 
ýspecial file These special files (often called device nodes) reside in the /dev directory. For ex 
ample, invoking the command ls -l /dev/lp* can be used to yield the following status 
information: 

crw-rw-rw 1 root root 6, 0 April 23 1994 /dev/lp0


This example indicates that: ýlp0ý is a character type device (the first letter of the file mode field is 
ýcý), the major number is 6, and minor device number 0 is assigned to the device. 

Major device numbers are used by the Linux system to map I/O requests to the driver code, thereby 
deciding which device driver to execute, when a user reads from or writes to the special file. The minor 
numbers are entirely under the control of the driver writer, and usually refer to ýsub­devicesý of the 
device. These sub­devices may be separate units attached to a controller. Thus, a disk device driver 
may, for example, communicate with a hardware controller (the device) which has several disk drives 
(sub­devices) attached. 

Figure 4 outlines the flow of execution of a system call within th e Linux operating system. 

                                       

Fig 4.

Device Drivers 

A device driver is a collection of subroutines and data within the kernel that constitutes the software 
interface to an I/O device. When the kernel recognizes that a particular action is required from the 
device, it calls the appropriate driver routine, which passes control from the user process to the driver 
routine. Control is returned to the user process when the driver routine has completed. A device driver 
may be shared simultaneously by user applications and must be protected to ensure its own integrity. 

Figure 5 shows the relationship between device driver and the Linux system. 
                                             Fig 5.

A device driver provides the following features: 

• A set of routines that communicate with a hardware device and provide a uniform interface to 
the operating system kernel. 
• A self­contained component that can be added to, or removed from, the operating system 
dynamically. 
• Management of data flow and c ontrol between user programs and a peripheral device. 
• A user­defined section of the kernel that allows a program or a peripheral device to appear as a 
`` /dev '' device to the rest of the system's software. 

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 ). 

      

Fig­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() or irqaction()
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, while inb_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, while outb_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_wait queue. 
*sleep_on()
Sleeps on an event, puts a wait_queue entry 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/char directory. 
• Edit Makefile in the same directory so it will compile the source for the driver­­add 
xxx_drv.o to the OBJS list, which causes the make utility to automatically compi le 
xxx_drv.c and add the object code to the char.a library archive. 
• The last step step is the recompilation of the kernel. 

The following steps are required to recompile the Linux kernel: 

1. Log in as root 
2. Change to the /root/linux directory 
3. Carry out the following series of commands 
• make clean ; make config to configures the basic kernel 

• make dep to set­up the dependencies correctly 
• make to create the new kernel 
4. Wait for the kernel to compile and go to the /usr/src/linux directory. 
5. 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 xxx and 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. 
Conclusions 

In this article, we have detailed how to write a hardware character device driver for the Linux operating 
system. We have outlined how to access hardware memory. We have also presented the kernel 
programming environment, as well as the supporting functions available to write a device driver. A 
number of worked examples were also presented to aid the programmer in developing his/her own 
device driver(s). 

Bibliography 

[Bach86] Bach, M; The Design of the Unix Operating System ; Englewood Cliffs, NJ: Prentice Hall, 

1986. [John93] Michael K. Johnson; LINUX Kernel Hackers' Guide ; 201 Howell Street, Apt. 1C, 
Chapel Hill, North Carolina 27514­4818; 1993.

[Swit93] Robert Switzer, University of Gottingen, Germany; Operating Systems, A Practical Approach  
; Prentice Hall 1993. 

[YCI94] The Linux Bible, The GNU Testament­2nd Edition ; Yggdrasil Computing Incorporated, 
Version 2.1.1, 10 July 1994. 
UnixWorld Online: Tutorial Article No. 010 Listing

Listing 1. A user program that employs system calls.
#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "fcntl.h"

void
main() {

int fd; /* file descriptor for the device */


char buff[16]; /* string of 16 bytes */

if ((fd = open("/dev/xxx", O_RDWR)) < 0) {


fprintf(stderr, "Can't open: "); perror("/dev/xxx"); exit(1);
} else {
printf("/dev/xxx opened with fd = %d\n", fd);
}

printf("size of buff is %d\n", sizeof(buff));


printf("read returns %d\n", read(fd, buff, sizeof(buff)));

buff[15] = 0; /* null terminate the string */

/* display the data read into the buffer */


printf("buff is:\n%s\n", buff);
close(fd);
}

Listing 2. The file_operations structure and device 
initialization.
struct file_operations xxx_fops = {
NULL, /* lseek() */
xxx_read, /* read() */
xxx_write, /* write() */
NULL, /* readdir() */
NULL, /* select() */
xxx_ioctl, /* ioctl() */
NULL, /* mmap() */
xxx_open, /* open() */
xxx_close /* close() */
};

long
xxx_init(long kmem_start) {
printk("Sample Device Driver Initialization\n");

if (register_chrdev(22, "xxx", &xxx_fops))


printk("error--cannot register to major device 22!\n");
/* detect hardware and initialize it */
return kmem_start;
}

Listing 3. The xxx_write() routine from a character device 
driver.
/* system include files */
#include "linux/kernel.h"
#include "linux/sched.h"
#include "linux/tty.h"
#include "linux/signal.h"
#include "linux/errno.h"
#include "asm/io.h"
#include "asm/segment.h"
#include "asm/system.h"
#include "asm/irq.h"

static int
xxx_write(struct inode *inode, struct file *file, char *buffer, int count) {

unsigned int minor = MINOR(inode->i_rdev); /* minor number of device */


int offset = 0;
char ret;

if (count > 4095)


return(-ENOMEM);
if (count <= 0)
return(-EINVAL);

while (count > 0) {


ret = xxx_write_byte(minor);
if (ret < 0) {
xxx_handle_error(WRITE, ret, minor);
continue;
}
buff++ = ret; offset++;
}
return offset; /* return number of bytes written */
/* xxx_write_byte() and xxx_handle_error() are functions
defined elsewhere in xxx_drv.c */
}
Listing 4. The xxx_write() routine for an interrupt­driven 
character­device driver.
/* register definitions */
#define XXX_BUFFER_SIZE 1024
#define XXX_INTERRUPT_TIMEOUT 100

static int
xxx_write(struct inode *inode, struct file *file, char *buffer, int count) {

unsigned int minor = MINOR(inode->i_rdev); /* minor number of device */


unsigned long copy_size, bytes_written, total_bytes_written = 0;

struct xxx_struct *xxx = &xxx_table[minor];

do {
copy_size = (count <= XXX_BUFFER_SIZE ? count : XXX_BUFFER_SIZE);
memcpy_fromfs(xxx->xxx_buffer, buf, copy_size); /* move the data */

while (copy_size) { /* while there is data in the buffer */

/* initiate interrupts */
if (error_occurred) { /* handle error condition */ }
current_timeout = jiffies + XXX_INTERRUPT_TIMEOUT;

/ * set timeout in case interrupt is missed */


interruptible_sleep_on(&xxx_wait_queue);

/* defined in , xxx_wait_queue is a FIFO queue of


processes that are sleeping; once initialized (to NULL) the kernel
handles the queue and to access it, then supply a pointer */

bytes_written = xxx->bytes_xfered;
xxx->bytes_written = 0;
if (current->signal & ~current->blocked) {
if (total_bytes_written + bytes_written)
return(total_bytes_written + bytes_written);
else return(-EINTR); /* system call interrupted, try again */
}
}
total_bytes_written += bytes_written;
buf += bytes_written;
count -= bytes_written;
} while (count > 0);

return(total_bytes_written); /* bytes written on a call */


}

static void xxx_interrupt(int irq) {

struct xxx_struct *xxx = &xxx_table[xxx_irq[irq]];

/* execute interrupt actions, check flag in xxx_table to see


if reading or writing */

/* increment xxx->bytes_xfered by however many characters transferred */


if (buffer too full/empty)
wake_up_interruptible(&xxx->xxx_wait_queue);
}

Listing 5. A Bus Mouse Device Driver
Notes: The file mouse.c contains the code to call mouse­dependent routines, which can be found, for 
example, in file busmouse.c. The main points to extract from review of this driver are an 
understanding of basic driver structure.

Line 23 (of file mouse.c) declares the head of the doubly linked mouse_list structure. Line 26 is 
the start of the generic routine to open all mouse devices. Line 30 retrieves the minor number of the 
device. Line 39 calls the mouse­specific open routine. Line 81 calls the mouse­specific initialization 
routine. Line 88 registers the device and itýs entry points with the VFS.

1 /* Filename: linux/drivers/char/mouse.c
2 * Description: generic mouse open routine
3 * the independent mouse drivers are independent
4 */
5
6 #ifdef MODULE
7 #include
8 #include
9 #else
10 #define MOD_INC_USE_COUNT
11 #define MOD_DEC_USE_COUNT
12 #endif
13
14 #include
15 #include
16 #include
17 #include
18 #include
19 #include
20 #include
21
22 /* head entry for the doubly linked mouse list */
23 static struct mouse mouse_list = {0, "head", NULL, &mouse_list, &mouse_list};
24
25 /* include declarations of the various mouse initialization routines */
26 extern int bus_mouse_init(void);
27
28 /* generic routine to open all mouse devices */
29 static int mouse_open(struct inode * inode, struct file * file) {
30 int minor = MINOR(inode->i_rdev);
31 struct mouse *c = mouse_list.next;
32 file->f_op = NULL;
33 while (c != &mouse_list) {
34 if (c->minor == minor) {
35 file->f_op = c->fops; break;
36 }
37 c = c->next;
38 }
39 if (file->f_op == NULL) return(-ENODEV); /* no device found */
40
41 /* call the mouse-dependent open routine */
42 return file->f_op->open(inode, file);
43 }
44
45 static struct file_operations mouse_fops = {
46 NULL, /* seek */
47 NULL, /* read */
48 NULL, /* write */
49 NULL, /* readdir */
50 NULL, /* select */
51 NULL, /* ioctl */
52 NULL, /* mmap */
53 mouse_open,
54 NULL /* release */
55 };
56
57 int mouse_register(struct mouse * mouse)
58 {
59 if (mouse->next || mouse->prev)
60 return(-EBUSY);
61 MOD_INC_USE_COUNT;
62 mouse->next = &mouse_list;
63 mouse->prev = mouse_list.prev;
64 mouse->prev->next = mouse;
65 mouse->next->prev = mouse;
66 return 0;
67 }
68
69 int mouse_deregister(struct mouse * mouse)
70 {
71 if (!mouse->next || !mouse->prev)
72 return (-EINVAL);
73 MOD_DEC_USE_COUNT;
74 mouse->prev->next = mouse->next; /* removing mouse from list */
75 mouse->next->prev = mouse->prev;
76 mouse->next = NULL;
77 mouse->prev = NULL;
78 return 0;
79 }
80
81 int mouse_init(void)
82 {
83
84 #ifdef CONFIG_BUSMOUSE
85 bus_mouse_init(); /* called to initialize the proper mouse device */
86 #endif
87
88 if (register_chrdev(MOUSE_MAJOR, "mouse", &mouse_fops)) {
89 printk("unable to get major number %d for mouse devices\n",
90 MOUSE_MAJOR);
91 return(-EIO);
92 }
93 return 0;
94 }
95
96 void cleanup_module(void)
97 {
98 if (MOD_IN_USE) { /* mouse is being used */
99 printk("mouse: in use, remove delayed \n");
100 return;
101 }
102 unregister_chrdev(MOUSE_MAJOR, "mouse");
103 }

Notes: Files named busmouse.h and busmouse.c contain code that is spscific to a particular 
busmouse, for example the Logitech bus mouse. Lines 29 ­39 (of file busmouse.h) define a structure to 
keep track of the mouse. Line 37 defines the wait queue structure that the device uses to sleep on.

1 /*
2 * Filename: linux/drivers/char/busmouse.h
3 */
4
5 #define MOUSE_IRQ 5
6 #define LOGITECH_BUSMOUSE 0 /* Minor device num for Logitech */
7 #define MICROSOFT_BUSMOUSE 2 /* Minor device num for Microsoft */
8 #define MSE_DATA_PORT 0x23c
9 #define MSE_SIGNATURE_PORT 0x23d
10 #define MSE_CONTROL_PORT 0x23e
11 #define MSE_INTERRUPT_PORT 0x23e
12 #define MSE_CONFIG_PORT 0x23f
13 #define MSE_ENABLE_INTERRUPTS 0x00
14 #define MSE_DISABLE_INTERRUPTS 0x10
15 #define MSE_READ_X_LOW 0x80
16 #define MSE_READ_X_HIGH 0xa0
17 #define MSE_READ_Y_LOW 0xc0
18 #define MSE_READ_Y_HIGH 0xe0
19
20 /* magic number used to check if the mouse exists */
21 #define MSE_CONFIG_BYTE 0x91
22 #define MSE_DEFAULT_MODE 0x90
23 #define MSE_SIGNATURE_BYTE 0xa5
24
25 /* useful Logitech Mouse macros */
26 #define MSE_INT_OFF() outb(MSE_DISABLE_INTERRUPTS, MSE_CONTROL_PORT)
27 #define MSE_INT_ON() outb(MSE_ENABLE_INTERRUPTS, MSE_CONTROL_PORT)
28
29 struct mouse_status {
30 unsigned char buttons;
31 unsigned char latch_buttons;
32 int dx;
33 int dy;
34 int present;
35 int ready;
36 int active;
37 struct wait_queue *wait;
38 struct fasync_struct *fasyncptr;
39 };

Busmouse.c Listing

Notes: This file contains the actual hardware dependent routines to detect, initialize, and manage a 
busmouse. At system startup time, mem_init() calls mouse_init(), which in turn calls bus_mouse_init(). 
Lines 178 to 186 verify that the mouse actually exists, then lines 187 to 198 initializes the mouse's 
state, but leaves interrupts disabled. This routine is only called once.

Interrupts for the mouse are not enabled until the open_mouse() routine is invoked. This routine is 
accessed when a user process opens /dev/mouse. Line 88 checks to see if the mouse is present, and 
exits if bus_mouse_init() did not find it during system startup. Line 89 checks to see if the driver is 
already open, and if so the call returns and the driver exits. However, if the device is present and is not 
open, lines 90 to 100 register the interrupt handler and enable interrupts on the mouse device.

The interrupt service routine starts on line 29. First, line 34 disables other mouse interrupts. Then the 
mouse position and mouse button status is obtained from the mouse device. These values are adjusted 
for mouse randomness and then used to update the driver's notion of the mouse position. Line 52 wakes 
up any processes sleeping on the wait queue. Lines 56­59 cap the driver's mouse­position values. Line 
60 generate SIGIO signals for processes that have requested them. Line 62 enables mouse interrupts 
before the interrupt service routine returns.

The read_mouse() routine starts on line 110 and continues to line 143. Line 116 returns an error to the 
user process if a read of less than 3 bytes is attempted. Attempts to read more than 3 bytes result in 
zero padding. An interesting feature of this routine is that the user process never sleeps on this device. 
Line 119 of the mouse driver returns EAGAIN to the user if the mouse has no data. Most drivers 
typically would call sleep_on() until data became available, causing the process to sleep. On line 117, 
the verify_area() kernel routine is called to ensure that the user process has provided a valid pointer 
into its address space. It is very important that the driver perform checking of its parameters, as failing 
to do so can cause security holes or system crashes. Lines 137 to 141 utilize the put_user() kernel 
function to copy the mouse data into the user­ process address space.

Writing to the mouse driver is meaningless, so lines 104 to 107 return an error to the user process if the 
write_mouse() routine was called.

The mouse_select() routine on lines 147 to 169 allows user processes to use the select(2) system call on 
mouse devices. In contrast to the mouse_read() routine, a user process can sleep on the mouse device 
becoming ready during this call, although this occurrence is pretty infrequent. The only time the mouse 
driver is not ready is between the time the first mouse_open() is called and the time the first interrupt 
actually occurs.

The fasync_mouse() routine on lines 65 to 71 allow user processes to indicate that they wish to receive 
SIGIO signals when data is available.

The close_mouse() routine on lines 74 to 82 is called when a user process closes the mouse device or 
the process exits. Line 77 cancels the reception of SIGIO signals for the calling process. Line 78 
returns immediately if the driver is still active. If the driver is not active (in use), lines 79 and 80 
disable mouse interrupts and unregister the mouse interrupt handler.

1 /*
2 * Filename: linux/drivers/char/busmouse.c
3 * Description: Logitech Bus Mouse Driver for Linux
4 */
5
6 #define MOD_INC_USE_COUNT
7 #define MOD_DEC_USE_COUNT
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #include
15 #include
16 #include
17 #include
18 #include
19 #include
20
21 static struct mouse_status mouse;
22 static int mouse_irq = MOUSE_IRQ;
23
24 void bmouse_setup(char *str, int *ints)
25 {
26 if (ints[0] > 0) mouse_irq=ints[1];
27 }
28
29 static void mouse_interrupt(int irq, struct pt_regs *regs)
30 {
31 char dx, dy;
32 unsigned char buttons;
33
34 MSE_INT_OFF();
35 outb(MSE_READ_X_LOW, MSE_CONTROL_PORT);
36 dx = (inb(MSE_DATA_PORT) & 0xf);
37 outb(MSE_READ_X_HIGH, MSE_CONTROL_PORT);
38 dx |= (inb(MSE_DATA_PORT) & 0xf) << 4;
39 outb(MSE_READ_Y_LOW, MSE_CONTROL_PORT );
40 dy = (inb(MSE_DATA_PORT) & 0xf);
41 outb(MSE_READ_Y_HIGH, MSE_CONTROL_PORT);
42 buttons = inb(MSE_DATA_PORT);
43 dy |= (buttons & 0xf) << 4;
44 buttons = ((buttons >> 5) & 0x07);
45
46 if (dx != 0 || dy != 0 || buttons != mouse.buttons) {
47 add_mouse_randomness((buttons << 16) + (dy << 8) + dx);
48 mouse.buttons = buttons;
49 mouse.dx += dx;
50 mouse.dy -= dy;
51 mouse.ready = 1;
52 wake_up_interruptible(&mouse.wait);
53
54 /* keep dx/dy reasonable, but still able to track when
55 X pages or is busy (long waits between reads) */
56 if (mouse.dx < -2048) mouse.dx = -2048;
57 if (mouse.dx > 2048) mouse.dx = 2048;
58 if (mouse.dy < -2048) mouse.dy = -2048;
59 if (mouse.dy > 2048) mouse.dy = 2048;
60 if (mouse.fasyncptr) kill_fasync(mouse.fasyncptr, SIGIO);
61 }
62 MSE_INT_ON();
63 }
64
65 static int
66 fasync_mouse(struct inode *inode, struct file *filp, int on)
67 {
68 int retval = fasync_helper(inode, filp, on, &mouse.fasyncptr);
69 if (retval < 0) return retval;
70 return 0;
71 }
72
73 /* close access to the mouse */
74 static void
75 close_mouse(struct inode *inode, struct file *file)
76 {
77 fasync_mouse(inode, file, 0);
78 if (--mouse.active) return;
79 MSE_INT_OFF();
80 free_irq(mouse_irq);
81 MOD_DEC_USE_COUNT;
82 }
83
84 /* open access to the mouse */
85 static int
86 open_mouse(struct inode *inode, struct file *file)
87 {
88 if (!mouse.present) return(-EINVAL);
89 if (mouse.active++) return(0);
90 if (request_irq(mouse_irq, mouse_interrupt, 0, "Busmouse")) {
91 mouse.active--;
92 return(-EBUSY);
93 }
94 mouse.ready = 0;
95 mouse.dx = mouse.dy = 0;
96 mouse.buttons = 0x87;
97 MOD_INC_USE_COUNT;
98 MSE_INT_ON();
99 return 0;
100 }
101
102 /* writes are disallowed */
103 static int
104 write_mouse(struct inode *inode, struct file *file, const char *buffer, int
count)
105 {
106 return(-EINVAL);
107 }
108
109 /* read mouse data - currently it never blocks */
110 static int
111 read_mouse(struct inode *inode, struct file *file, char *buffer, int count)
112 {
113 int r, dx, dy;
114 unsigned char buttons;
115
116 if (count < 3) return(-EINVAL);
117 if ((r = verify_area(VERIFY_WRITE, buffer, count)))
118 return r;
119 if (!mouse.ready) return(-EAGAIN);
120
121 /* obtain the current mouse params and limit as appropriate
122 for the return data format. Interrupts are disabled while
123 obtaining the params, NOT during the puts_fs_byte() calls,
124 so paging in put_user() not effect mouse tracking
125 */
126 MSE_INT_OFF();
127 dx = mouse.dx; dy = mouse.dy;
128 if (dx < -127) dx = -127;
129 if (dx > 127) dx = 127;
130 if (dy < -127) dy = -127;
131 if (dy > 127) dy = 127;
132 buttons = mouse.buttons;
133 mouse.dx -= dx;
134 mouse.dy -= dy;
135 mouse.ready = 0;
136 MSE_INT_ON();
137 put_user(buttons | 0x80, buffer);
138 put_user((char)dx, buffer + 1);
139 put_user((char)dy, buffer + 2);
140 for (r = 3; r < count; r++)
141 put_user(0x00, buffer + r);
142 return(r);
143 }
144
145 /* select for mouse input */
146 static int
147 mouse_select(struct inode *inode, struct file *file, int sel_type,
select_table * wait)
148 {
149 if (sel_type == SEL_IN) {
150 if (mouse.ready)
151 return(1);
152 select_wait(&mouse.wait, wait);
153 }
154 return(0);
155 }
156
157 struct file_operations bus_mouse_fops = {
158 NULL, /* mouse_seek */
159 read_mouse,
160 write_mouse,
161 NULL, /* mouse_readdir */
162 mouse_select, /* mouse_select */
163 NULL, /* mouse_ioctl */
164 NULL, /* mouse_mmap */
165 open_mouse,
166 close_mouse,
167 NULL,
168 fasync_mouse,
169 };
170
171 static struct mouse bus_mouse = {
172 LOGITECH_BUSMOUSE, "busmouse", &bus_mouse_fops
173 };
174
175 int bus_mouse_init(void)
176 {
177 int i;
178 outb(MSE_CONFIG_BYTE, MSE_CONFIG_PORT);
179 outb(MSE_SIGNATURE_BYTE, MSE_SIGNATURE_PORT);
180
181 for (i = 0; i < 100000; i++) /* busy loop */;
182
183 if (inb(MSE_SIGNATURE_PORT) != MSE_SIGNATURE_BYTE) {
184 mouse.present = 0;
185 return(-EIO);
186 }
187 outb(MSE_DEFAULT_MODE, MSE_CONFIG_PORT);
188 MSE_INT_OFF();
189 mouse.present = 1;
190 mouse.active = mouse.ready = 0;
191 mouse.buttons = 0x87;
192 mouse.dx = mouse.dy = 0;
193 mouse.wait = NULL;
194 printk("Logitech Bus mouse detected and installed with IRQ %d.\n",
195 mouse_irq);
196 mouse_register(&bus_mouse);
197 return 0;
198 }
199
200 void cleanup_module(void)
201 {
202 if (MOD_IN_USE)
203 printk("busmouse: in use - remove delayed\n");
204 mouse_deregister(&bus_mouse);
205 }

Anda mungkin juga menyukai