diff -ruN linux-2.4.12-orig/drivers/char/pc_keyb.c linux-2.4.12/drivers/char/pc_keyb.c --- linux-2.4.12-orig/drivers/char/pc_keyb.c Tue Sep 18 01:52:35 2001 +++ linux-2.4.12/drivers/char/pc_keyb.c Thu Oct 11 13:10:03 2001 @@ -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 @@ -66,6 +75,8 @@ static void aux_write_ack(int val); static void __aux_write_ack(int val); static int aux_reconnect = 0; +static void aux_kill_fasync(struct fasync_struct **fasync, int sig, int band); +static int aux_release_ioctl(struct file *file); #endif static spinlock_t kbd_controller_lock = SPIN_LOCK_UNLOCKED; @@ -89,13 +100,30 @@ static struct aux_queue *queue; /* Mouse data buffer. */ static int aux_count; -/* 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; +/* 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; +static wait_queue_head_t mouse_ack_wait; +/* 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; +static wait_queue_head_t aux_exclusive_wait; +static unsigned long aux_last_write; +#define AUX_GRAB _IO('M', 1) +#define AUX_RELEASE _IO('M', 2) #endif /* CONFIG_PSMOUSE */ /* @@ -427,6 +455,13 @@ { #ifdef CONFIG_PSMOUSE static unsigned char prev_code; + 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--; @@ -451,7 +486,7 @@ head = (head + 1) & (AUX_BUF_SIZE-1); if (head != queue->tail) { queue->head = head; - kill_fasync(&queue->fasync, SIGIO, POLL_IN); + aux_kill_fasync(&queue->fasync, SIGIO, POLL_IN); wake_up_interruptible(&queue->proc_list); } } @@ -968,19 +1003,75 @@ 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; + DECLARE_WAITQUEUE(wait, current); + + 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; } /* @@ -992,11 +1083,13 @@ 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; @@ -1005,6 +1098,33 @@ __aux_write_ack(val); spin_unlock_irqrestore(&kbd_controller_lock, flags); } +#endif /* INITIALIZE_MOUSE */ + +static void aux_kill_fasync(struct fasync_struct **fasync, int sig, int band) +{ + 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, sig, band); +} static unsigned char get_from_queue(void) { @@ -1044,6 +1164,8 @@ { lock_kernel(); fasync_aux(-1, file, 0); + if (aux_exclusive == file) + aux_release_ioctl(file); if (--aux_count) { unlock_kernel(); return 0; @@ -1073,7 +1195,7 @@ 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 */ mdelay(2); /* Ensure we follow the kbc access delay rules.. */ @@ -1084,6 +1206,33 @@ } /* + * Implement exclusive access mechanism. + */ + +#define AUX_ACCESS_ALLOWED(file) (!aux_exclusive || aux_exclusive == (file)) + +static ssize_t aux_wait_for_access(struct file * file) +{ + DECLARE_WAITQUEUE(wait, current); + + 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. */ @@ -1093,7 +1242,11 @@ DECLARE_WAITQUEUE(wait, current); 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; @@ -1128,23 +1281,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; } } @@ -1155,15 +1317,80 @@ 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) +{ + DECLARE_WAITQUEUE(wait, current); + 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 = { read: read_aux, write: write_aux, poll: aux_poll, + ioctl: aux_ioctl, open: open_aux, release: release_aux, fasync: fasync_aux, @@ -1195,6 +1422,8 @@ memset(queue, 0, sizeof(*queue)); queue->head = queue->tail = 0; init_waitqueue_head(&queue->proc_list); + init_waitqueue_head(&mouse_ack_wait); + init_waitqueue_head(&aux_exclusive_wait); #ifdef INITIALIZE_MOUSE kbd_write_command_w(KBD_CCMD_MOUSE_ENABLE); /* Enable Aux. */ @@ -1206,6 +1435,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; }