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