--- linux/drivers/char/pc_keyb.c.orig Wed Dec 13 10:21:43 2000 +++ linux/drivers/char/pc_keyb.c Wed Dec 13 10:38:23 2000 @@ -13,6 +13,15 @@ * Code fixes to handle mouse ACKs properly. * C. Scott Ananian 1999-01-29. * + * More work to handle mouse ACKs properly. (Modified version of + * patch by Julian Bradfield .) + * Chris Hanson 2000-12-11. + * + * Implement exclusive access mechanism for aux device. + * This permits grabbing the mouse away from the X server, + * which is needed by fancy mice that have configurable features. + * Chris Hanson 2000-10-30. + * */ #include @@ -57,7 +66,9 @@ static void kbd_write_command_w(int data); static void kbd_write_output_w(int data); #ifdef CONFIG_PSMOUSE -static void aux_write_ack(int val); +static void __aux_write_ack(int val); +static void aux_kill_fasync(struct fasync_struct *fasync, int s); +static int aux_release_ioctl(struct file *file); #endif spinlock_t kbd_controller_lock = SPIN_LOCK_UNLOCKED; @@ -81,13 +92,30 @@ static struct aux_queue *queue; /* Mouse data buffer. */ static int aux_count = 0; -/* used when we send commands to the mouse that expect an ACK. */ +/* Used when we (as opposed to user programs using the aux device) + send commands to the mouse. */ static unsigned char mouse_reply_expected = 0; +/* Used to make sure we have received an ACK from the byte last + written to the mouse before writing another. */ +static unsigned long mouse_ack_pending = 0; +static struct wait_queue *mouse_ack_wait = NULL; +/* How many jiffies to wait for the mouse to respond to a command with + an ACK. 25 msec is the maximum allowed delay for the hardware to + respond. We round that up to the nearest jiffy. */ +#define MOUSE_ACK_TIMEOUT ((25 * HZ + 999) / 1000) + #define AUX_INTS_OFF (KBD_MODE_KCC | KBD_MODE_DISABLE_MOUSE | KBD_MODE_SYS | KBD_MODE_KBD_INT) #define AUX_INTS_ON (KBD_MODE_KCC | KBD_MODE_SYS | KBD_MODE_MOUSE_INT | KBD_MODE_KBD_INT) #define MAX_RETRIES 60 /* some aux operations take long time*/ + +/* Support for exclusive access to the AUX device. */ +static struct file *aux_exclusive = 0; +static struct wait_queue *aux_exclusive_wait = NULL; +static unsigned long aux_last_write = 0; +#define AUX_GRAB _IO('M', 1) +#define AUX_RELEASE _IO('M', 2) #endif /* CONFIG_PSMOUSE */ /* @@ -392,6 +420,13 @@ static inline void handle_mouse_event(unsigned char scancode) { #ifdef CONFIG_PSMOUSE + if (mouse_ack_pending) { + /* It needn't actually be an ack, it could be an echo; + but every byte sent to the mouse results in a byte + back. */ + wake_up_interruptible(&mouse_ack_wait); + mouse_ack_pending = 0; + } if (mouse_reply_expected) { if (scancode == AUX_ACK) { mouse_reply_expected--; @@ -401,14 +436,7 @@ } else if(scancode == AUX_RECONNECT){ queue->head = queue->tail = 0; /* Flush input queue */ - /* ping the mouse :) */ - kb_wait(); - kbd_write_command(KBD_CCMD_WRITE_MOUSE); - kb_wait(); - kbd_write_output(AUX_ENABLE_DEV); - /* we expect an ACK in response. */ - mouse_reply_expected++; - kb_wait(); + __aux_write_ack(AUX_ENABLE_DEV); /* ping the mouse :) */ return; } @@ -421,7 +449,7 @@ if (head != queue->tail) { queue->head = head; if (queue->fasync) - kill_fasync(queue->fasync, SIGIO); + aux_kill_fasync(queue->fasync, SIGIO); wake_up_interruptible(&queue->proc_list); } } @@ -429,6 +457,7 @@ } static unsigned char kbd_exists = 1; +static unsigned char status_mask = 0; /* At probe time we want all */ /* * This reads the keyboard status port, and does the @@ -446,18 +475,21 @@ unsigned char scancode; scancode = kbd_read_input(); -# ifdef CHECK_RECONNECT_SCANCODE - printk(KERN_INFO "-=db=-: kbd_read_input() : scancode == %d\n",scancode); -# endif - if (status & KBD_STAT_MOUSE_OBF) { - handle_mouse_event(scancode); - } else { - kbd_exists = 1; - if (do_acknowledge(scancode)) - handle_scancode(scancode, !(scancode & 0x80)); - mark_bh(KEYBOARD_BH); + + /* Check for errors. Shouldnt ever happen but it does on Compaq + Presario 16[89]. */ + + if(!(status& status_mask)) + { + if (status & KBD_STAT_MOUSE_OBF) { + handle_mouse_event(scancode); + } else { + kbd_exists = 1; + if (do_acknowledge(scancode)) + handle_scancode(scancode, !(scancode & 0x80)); + mark_bh(KEYBOARD_BH); + } } - status = kbd_read_status(); if(!work--) @@ -741,7 +773,8 @@ #if defined CONFIG_PSMOUSE psaux_init(); #endif - + /* Switch keyboard processing to checking error bits */ + status_mask = KBD_STAT_GTO|KBD_STAT_PERR; /* Ok, finally allocate the IRQ, and off we go.. */ kbd_request_irq(keyboard_interrupt); } @@ -790,38 +823,128 @@ return retval; } +static void mouse_ack_timeout(unsigned long data) +{ + wake_up_interruptible(&mouse_ack_wait); +} + /* * Send a byte to the mouse. */ -static void aux_write_dev(int val) +static int aux_write_dev(int val, int dont_block, int handle_ack) { unsigned long flags; spin_lock_irqsave(&kbd_controller_lock, flags); kb_wait(); + /* If we haven't yet received the ACK from the previous write, + we must wait for it. */ + if (mouse_ack_pending) { + unsigned long expires; + struct timer_list timer; + struct wait_queue wait = { current, NULL }; + + if (dont_block) { + spin_unlock_irqrestore(&kbd_controller_lock, flags); + return -EAGAIN; + } + expires = mouse_ack_pending + MOUSE_ACK_TIMEOUT; + if (jiffies >= expires) + goto timed_out; + + add_wait_queue(&mouse_ack_wait, &wait); + init_timer (&timer); + timer.expires = expires; + timer.data = 0; + timer.function = mouse_ack_timeout; + add_timer(&timer); + + do { + current->state = TASK_INTERRUPTIBLE; + spin_unlock_irqrestore(&kbd_controller_lock, flags); + schedule(); + spin_lock_irqsave(&kbd_controller_lock, flags); + } while (mouse_ack_pending + && jiffies < expires + && !signal_pending(current)); + + del_timer(&timer); + remove_wait_queue(&mouse_ack_wait, &wait); + + if (mouse_ack_pending && jiffies >= expires) { + timed_out: + printk(KERN_WARNING "mouse ack timeout\n"); + mouse_ack_pending = 0; + spin_unlock_irqrestore(&kbd_controller_lock, flags); + return -EIO; + } + if (signal_pending(current)) { + spin_unlock_irqrestore(&kbd_controller_lock, flags); + return -ERESTARTSYS; + } + } kbd_write_command(KBD_CCMD_WRITE_MOUSE); kb_wait(); kbd_write_output(val); + mouse_ack_pending = jiffies; + if (handle_ack) + /* We will deal with the ACK ourselves. */ + mouse_reply_expected++; spin_unlock_irqrestore(&kbd_controller_lock, flags); + return 0; } /* * Send a byte to the mouse & handle returned ack */ -static void aux_write_ack(int val) +static void __aux_write_ack(int val) { - unsigned long flags; - - spin_lock_irqsave(&kbd_controller_lock, flags); kb_wait(); kbd_write_command(KBD_CCMD_WRITE_MOUSE); kb_wait(); kbd_write_output(val); - /* we expect an ACK in response. */ + mouse_ack_pending = jiffies; + /* We will deal with the ACK ourselves. */ mouse_reply_expected++; kb_wait(); +} + +#ifdef INITIALIZE_MOUSE +static void aux_write_ack(int val) +{ + unsigned long flags; + + spin_lock_irqsave(&kbd_controller_lock, flags); + __aux_write_ack(val); spin_unlock_irqrestore(&kbd_controller_lock, flags); } +#endif /* INITIALIZE_MOUSE */ + +static void aux_kill_fasync(struct fasync_struct *fasync, int s) +{ + struct fasync_struct * fp; + struct fasync_struct fa; + + fp = fasync; + /* If someone has grabbed the AUX device, send signal only to + them and not to other processes. We could do this directly + if send_sigio was exported, but since it isn't we must + synthesize a "struct fasync_struct" to pass to + kill_fasync. */ + if (aux_exclusive) { + while (1) { + if (!fp) + return; + if (fp->fa_file == aux_exclusive) + break; + fp = fp->fa_next; + } + fa = (*fp); + fa.fa_next = NULL; + fp = &fa; + } + kill_fasync(fp, s); +} static unsigned char get_from_queue(void) { @@ -860,6 +983,8 @@ static int release_aux(struct inode * inode, struct file * file) { fasync_aux(-1, file, 0); + if (aux_exclusive == file) + aux_release_ioctl(file); if (--aux_count) return 0; kbd_write_cmd(AUX_INTS_OFF); /* Disable controller ints */ @@ -886,13 +1011,40 @@ kbd_write_command_w(KBD_CCMD_MOUSE_ENABLE); /* Enable the auxiliary port on controller. */ - aux_write_ack(AUX_ENABLE_DEV); /* Enable aux device */ + aux_write_dev(AUX_ENABLE_DEV, 0, 1); /* Enable aux device */ kbd_write_cmd(AUX_INTS_ON); /* Enable controller ints */ return 0; } /* + * Implement exclusive access mechanism. + */ + +#define AUX_ACCESS_ALLOWED(file) (!aux_exclusive || aux_exclusive == (file)) + +static ssize_t aux_wait_for_access(struct file * file) +{ + struct wait_queue wait = { current, NULL }; + + if (!AUX_ACCESS_ALLOWED(file)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + add_wait_queue(&aux_exclusive_wait, &wait); + do { + current->state = TASK_INTERRUPTIBLE; + schedule(); + } + while (!AUX_ACCESS_ALLOWED(file) + && !signal_pending(current)); + remove_wait_queue(&aux_exclusive_wait, &wait); + } + if (!AUX_ACCESS_ALLOWED(file)) + return -ERESTARTSYS; + return 0; +} + +/* * Put bytes from input queue to buffer. */ @@ -902,7 +1054,11 @@ struct wait_queue wait = { current, NULL }; ssize_t i = count; unsigned char c; + ssize_t retval; + retval = aux_wait_for_access(file); + if (retval < 0) + return retval; if (queue_empty()) { if (file->f_flags & O_NONBLOCK) return -EAGAIN; @@ -937,23 +1093,32 @@ static ssize_t write_aux(struct file * file, const char * buffer, size_t count, loff_t *ppos) { - ssize_t retval = 0; - + ssize_t retval; + + retval = aux_wait_for_access(file); + if (retval < 0) + return retval; if (count) { ssize_t written = 0; + retval = -EIO; if (count > 32) count = 32; /* Limit to 32 bytes. */ do { char c; + int write_result; get_user(c, buffer++); - aux_write_dev(c); + write_result = aux_write_dev(c, file->f_flags & O_NONBLOCK, 0); + if (write_result) { + retval = write_result; + break; + } written++; } while (--count); - retval = -EIO; if (written) { retval = written; file->f_dentry->d_inode->i_mtime = CURRENT_TIME; + aux_last_write = jiffies; } } @@ -963,18 +1128,82 @@ static unsigned int aux_poll(struct file *file, poll_table * wait) { poll_wait(file, &queue->proc_list, wait); - if (!queue_empty()) + if (AUX_ACCESS_ALLOWED(file) && !queue_empty()) return POLLIN | POLLRDNORM; return 0; } +/* Wait this long after last write to mouse before allowing AUX_GRAB + to happen. This ensures that any outstanding mouse command is + completed. The ACK from the command is supposed to arrive in 25 + msec, and each subsequent status bytes are supposed to arrive + within 20 msec, so a command with 5 status bytes (I don't know any + this long) might take 125 msec. Fudge this up a bit to account for + additional delay introduced by bus locking. */ +#define AUX_GRAB_MIN_TIME (aux_last_write + (((200 * HZ) + 500) / 1000)) +#define AUX_GRAB_ALLOWED (aux_exclusive == 0 && (jiffies >= AUX_GRAB_MIN_TIME)) + +static void aux_grab_timeout(unsigned long data) +{ + wake_up_interruptible(&aux_exclusive_wait); +} + +static int aux_grab_ioctl(struct file *file) +{ + struct wait_queue wait = { current, NULL }; + struct timer_list timer; + + if (aux_exclusive == file) + return -EINVAL; + if (!AUX_GRAB_ALLOWED) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + init_timer (&timer); + timer.expires = AUX_GRAB_MIN_TIME; + timer.data = 0; + timer.function = aux_grab_timeout; + add_wait_queue(&aux_exclusive_wait, &wait); + add_timer(&timer); + do { + current->state = TASK_INTERRUPTIBLE; + schedule(); + } + while (!AUX_GRAB_ALLOWED && !signal_pending(current)); + del_timer(&timer); + remove_wait_queue(&aux_exclusive_wait, &wait); + } + if (!AUX_GRAB_ALLOWED) + return -ERESTARTSYS; + aux_exclusive = file; + return 0; +} + +static int aux_release_ioctl(struct file *file) +{ + if (aux_exclusive != file) + return -ENOENT; + aux_exclusive = 0; + wake_up_interruptible(&aux_exclusive_wait); + return 0; +} + +static int aux_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case AUX_GRAB: return aux_grab_ioctl(file); + case AUX_RELEASE: return aux_release_ioctl(file); + default: return -EINVAL; + } +} + struct file_operations psaux_fops = { NULL, /* seek */ read_aux, write_aux, NULL, /* readdir */ aux_poll, - NULL, /* ioctl */ + aux_ioctl, NULL, /* mmap */ open_aux, NULL, /* flush */ @@ -1011,6 +1240,8 @@ #endif /* INITIALIZE_MOUSE */ kbd_write_command(KBD_CCMD_MOUSE_DISABLE); /* Disable aux device. */ kbd_write_cmd(AUX_INTS_OFF); /* Disable controller ints. */ + + aux_last_write = jiffies; return 0; }