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 } ---------------------------------------------------------------------------- Copyright © 1995 The McGraw-Hill Companies, Inc. All Rights Reserved. Edited by Becca Thomas / Online Editor / UnixWorld Online / editor@unixworld.com [Go to Contents] [Search Editorial] Technical review and some commentary kindly provided by Stephen Degler. Last Modified: Sunday, 21-Jan-96 06:05:09 PST