]> xenbits.xen.org Git - xenclient/kernel.git/commitdiff
Kernel-side driver (pass2) for PS/2 pass-through for XenClient. master
authorMark Hemment <markhem@isildur.uk.xensource.com>
Thu, 23 Apr 2009 13:33:53 +0000 (14:33 +0100)
committerMark Hemment <markhem@isildur.uk.xensource.com>
Thu, 23 Apr 2009 13:33:53 +0000 (14:33 +0100)
drivers/input/xen/pass2.c [new file with mode: 0644]
include/linux/pass2.h [new file with mode: 0644]

diff --git a/drivers/input/xen/pass2.c b/drivers/input/xen/pass2.c
new file mode 100644 (file)
index 0000000..550b07f
--- /dev/null
@@ -0,0 +1,1153 @@
+/*
+ * Kernel-side driver of XenClient's pass-through PS/2 solution.
+ * Most the intelligence is in ioemu.  This driver talks to the h/w (i8042),
+ * handles interrupts, arbitrates on which guest has focus on the devices
+ * (KBD and AUX), and passes constant data from the primary VM to the
+ * secondaries.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/errno.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/poll.h>
+#include <linux/pass2.h>
+#include <linux/irq.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/cpu.h>
+
+#include <asm/io.h>
+
+/**
+ ** Compile control.
+ **/
+
+/* #define     PASS2_DEBUG     1 */
+
+/*
+ * Constants.
+ */
+
+#define        PA2_KBD_IRQ     1
+#define        PA2_AUX_IRQ     12
+
+#define        PA2_DATA_PORT           0x60
+#define        PA2_CMD_PORT            0x64
+#define        PA2_STATUS_PORT         0x64
+
+/*
+ * Status Register bits.
+ */
+
+#define PA2_CTL_STAT_OBF               0x01    /* KBD output buffer full */
+#define PA2_CTL_STAT_IBF               0x02    /* KBD input buffer full */
+#define PA2_CTL_STAT_SELFTEST          0x04    /* Self test successful */
+#define PA2_STAT_CMD                   0x08    /* Set when last write was a
+                                                * cmd */
+#define PA2_CTL_STAT_UNLOCKED          0x10    /* Zero if keyboard locked */
+#define PA2_CTL_STAT_AUX_OBF           0x20    /* AUX output buffer full */
+#define PA2_CTL_STAT_GTO               0x40    /* receive/xmit timeout */
+#define PA2_CTL_STAT_PERR              0x80    /* Parity error */
+
+/*
+ * Controller Mode Register Bits.
+ */
+
+#define PA2_CTL_MODE_INT_KBD           0x01    /* KBD data generate IRQ1 */
+#define PA2_CTL_MODE_INT_AUX           0x02    /* AUX data generate IRQ12 */
+#define PA2_CTL_MODE_SYS               0x04    /* The system flag (?) */
+#define PA2_CTL_MODE_NO_KEYLOCK                0x08    /* keylock doesn't affect the
+                                                * KBD */
+#define PA2_CTL_MODE_DISABLE_KBD       0x10    /* Disable keyboard interface */
+#define PA2_CTL_MODE_DISABLE_AUX       0x20    /* Disable aux interface */
+#define PA2_CTL_MODE_KCC               0x40    /* Scan code conversion to PC
+                                                * format */
+#define PA2_CTL_MODE_RFU               0x80
+
+/*
+ * Keyboard Controller Commands
+ */
+
+#define        PA2_CTL_CMD_MODE_READ           0x20    /* read mode bits */
+#define        PA2_CTL_CMD_MODE_WRITE          0x60    /* write mode bits */
+
+/**
+ ** Debug
+ **/
+
+#if    defined(PASS2_DEBUG)
+#define        ASSERT(cond)                    \
+       do {                                                            \
+               if ((cond) != 0) {                                      \
+                       break;                                          \
+               }                                                       \
+               printk(KERN_ERR "pass2: BUG on line:%d \"%s\"\n",       \
+                               __LINE__, #cond);                       \
+               panic("ASSERT\n");                                      \
+       } while (0)
+
+#define        PRINT_DEBUG(format, args...)    \
+       do {                                                            \
+               printk(KERN_ERR "pass2: " format, ## args);             \
+       } while (0)
+#else
+#define        ASSERT(cond)                    do { } while (0)
+#define        PRINT_DEBUG(format, args...)    do { } while (0)
+#endif
+
+/*
+ * Names of pseudo files.
+ */
+
+static const char      *pa2_path[] = { "8042-kbd", "8042-aux", "8042-ctl" };
+
+#define        PA2_INDEX_KBD   0
+#define        PA2_INDEX_AUX   1
+#define        PA2_INDEX_CTL   2
+
+/**
+ ** Queues.
+ ** Data is stored on two queues, one for KDB and one for AUX, until read
+ ** by the user-space driver.
+ ** The AUX device, when it is a Synaptic touchpad, can generate up to 80
+ ** sample packets per second with each packet being 6 bytes (480 bytes per
+ ** second).  Queue is sized to store just over a seconds worth (512 entries).
+ **/
+
+#define        PA2_BUF_SHIFT   9
+#define        PA2_BUF_SZ      (1 << PA2_BUF_SHIFT)
+
+/*
+ * Received data queue structure;
+ *  pp_entry           - data/status
+ *  pp_start           - first unused position in pp_entry
+ *  pp_end             - oldest, unread, data in pp_entry
+ *  pp_overflow                - an overflow occured since last user-space read
+ */
+
+typedef struct pa2_ps_s {
+       pa2_entry_t     pp_entry[PA2_BUF_SZ];
+       unsigned int    pp_start;
+       unsigned int    pp_end;
+       unsigned int    pp_overflow;
+} pa2_ps_t;
+
+/*
+ * The state of each device.  There are two of these, one for KBD other for
+ * AUX.
+ * Only the file structure which has grabbed (PA2_IOCTL_GRAB) the device can
+ * read data from it (this should be extended to sending cmds).
+ *  ps_lock            - guard for data on device's queue (ps_queue), and
+ *                       for grabbed status (ps_grabbed).
+ *  ps_index           - PA2_INDEX_{AUX,KBD}
+ *  ps_waiting_grab    - XXX not fully implemented....
+ *  ps_grabbed         - pointer to file structure that owns (grabbed) the
+ *                       device (PA2_IOCTL_GRAB).
+ *  ps_queue           - pointer to received data queue
+ *  ps_wait_grab       - XXX not fully implemented...
+ *  ps_wait_poll       - waiting for incoming data..XXX
+ */
+
+typedef struct pa2_state_s {
+       spinlock_t              ps_lock;
+       unsigned int            ps_index;
+       unsigned int            ps_waiting_grab;
+       struct file             *ps_grabbed;
+       pa2_ps_t                *ps_queue;
+       wait_queue_head_t       ps_wait_grab;
+       wait_queue_head_t       ps_wait_poll;
+} pa2_state_t;
+
+/*
+ * For the ctl file.
+ * The members must match the start of pa2_state_t.
+ */
+
+typedef struct pa2_ctl_s {
+       spinlock_t              ps_lock;
+       unsigned int            ps_index;
+} pa2_ctl_t;
+
+/**
+ ** Globals
+ **/
+
+static pa2_ps_t                pa2_queue[PA2_INDEX_AUX + 1];
+static pa2_state_t     pa2_state[PA2_INDEX_CTL + 1];   /* surely AUX? XXX */
+static pa2_ctl_t       pa2_ctl;
+
+/*
+ *  pa2_i8042_lock     - guards reading from the data port, and some globals
+ *                       (pa2_open_count, XXX)
+ *  pa2_open_count     - number of file opens
+ *  pa2_initialized    - flag indicating if one time initialization has
+ *                       been performed by user-space
+ *  pa2_init_taskp     - task performing the initialization
+ *  pa2_init_buffer    - copy of initialization data
+ *  pa2_init_buffer_tmp        - temp copy of initialization data, copied from user-
+ *                       space.  Not taken on stack due to size.
+ *  pa2_proc_dev       - /proc files
+ *  pa2_init_sem       - semaphore guard held during initilization
+ *  pa2_cmd_sem                - controls access to cmd register
+ *  pa2_cmd_filp       - file structure that holds the cmd_sem
+ */
+
+static DEFINE_SPINLOCK(pa2_i8042_lock);        /* cache aligned XXX */
+static unsigned int    pa2_open_count;
+static int             pa2_initialized;
+static struct task_struct      *pa2_init_taskp;
+static pa2_data_init_t         pa2_init_buffer;
+static pa2_data_init_t         pa2_init_buffer_tmp;
+static struct proc_dir_entry   *pa2_proc_dev[PA2_INDEX_CTL + 1];
+
+static DECLARE_MUTEX(pa2_init_sem);
+static DECLARE_MUTEX(pa2_cmd_sem);
+static struct file     *pa2_cmd_filp;
+
+/*
+ * Functions for accessing status, data, and cmd ports.
+ */
+
+static inline u8
+pa2_status_read(
+       void)
+{
+       return inb(PA2_STATUS_PORT);
+}
+
+static inline u8
+pa2_data_read(
+       void)
+{
+       return inb(PA2_DATA_PORT);
+}
+
+static inline void
+pa2_cmd_write(
+       u8      byte)
+{
+       outb(byte, PA2_CMD_PORT);
+       return;
+}
+
+/**
+ ** Support functions.
+ **/
+
+static __attribute__ ((format (printf, 1, 2))) void
+pa2_error(
+       const char      *fmt,
+       ...)
+{
+       va_list         ap;
+
+       printk(KERN_ERR "pa2: ");
+       va_start(ap, fmt);
+       vprintk(fmt, ap);
+       va_end(ap);
+       return;
+}
+
+/*
+ * Wait for input buffer to become empty.
+ */
+
+static int
+pa2_wait_on_input_buffer(
+       void)
+{
+       int     i;
+       int     ret;
+       u8      status;
+
+       ret = 1;
+       for (i = 0; i < 10000; i++) {
+               status = pa2_status_read();
+               if (status & PA2_CTL_STAT_IBF) {
+                       udelay(50);
+                       continue;
+               }
+               ret = 0;
+               break;
+       }
+       return ret;
+}
+
+static int
+pa2_wait_on_output_buf(
+       void)
+{
+       int     i;
+       int     ret;
+       u8      status;
+
+       ret = 1;
+       for (i = 0; i < 10000; i++) {
+               status = pa2_status_read();
+               if (!(status & PA2_CTL_STAT_OBF)) {
+                       udelay(50);
+                       continue;
+               }
+               ret = 0;
+               break;
+       }
+       return ret;
+}
+
+static int
+pa2_wait_on_aux_output_buf(
+       void)
+{
+       int     i;
+       int     ret;
+       u8      status;
+
+       ret = 1;
+       for (i = 0; i < 10000; i++) {
+               status = pa2_status_read();
+               if ((status & (PA2_CTL_STAT_AUX_OBF | PA2_CTL_STAT_OBF)) !=
+                             (PA2_CTL_STAT_AUX_OBF | PA2_CTL_STAT_OBF)) {
+                       udelay(50);
+                       continue;
+               }
+               ret = 0;
+               break;
+       }
+       return ret;
+}
+
+static int
+pa2_cmd_write_do(
+       u8      cmd)
+{
+       int     ret;
+
+       ret = pa2_wait_on_input_buffer();
+       pa2_cmd_write(cmd);
+       ret |= pa2_wait_on_input_buffer();              /* XXX */
+       return ret;
+}
+
+static int
+pa2_data_write_do(
+       u8      data)
+{
+       int     ret;
+
+       ret = pa2_wait_on_input_buffer();
+       pa2_cmd_write(data);
+       ret |= pa2_wait_on_input_buffer();              /* XXX */
+       return ret;
+}
+
+static int
+pa2_ctl_mode_write_do(
+       u8      data)
+{
+       int     ret;
+
+       ret = pa2_cmd_write_do(PA2_CTL_CMD_MODE_WRITE);
+       ret |= pa2_data_write_do(data);
+       return ret;
+}
+
+static long
+pa2_grab(
+       struct file     *filp,
+       void __user     *p)
+{
+       DECLARE_WAITQUEUE(wait, current);
+       pa2_state_t     *sp;
+       int             ret;
+       unsigned long   expire;
+       unsigned char   byte;
+
+       sp = filp->private_data;
+       ASSERT(sp->ps_index == PA2_INDEX_KBD ||
+              sp->ps_index == PA2_INDEX_AUX);
+       ret = 0;
+       if (copy_from_user(&byte, p, 1)) {
+               ret = -EFAULT;
+               goto out;
+       }
+       PRINT_DEBUG("%s %s\n", byte == 0 ? "Relasing" : "Grabbing",
+                   sp->ps_index == PA2_INDEX_KBD ? "KBD" : "AUX");
+
+       /*
+        * Do not allow a grab unless we're seen an initialisation.
+        * Need both sem and spinlock to modify pa2_initialized, so can
+        * test with just one of these held.
+        * If currently being initialised, then wait.
+        */
+
+       spin_lock_irq(&pa2_i8042_lock);
+       if (byte == 1) {
+               /*
+                * If someone is initializing, then delay the grab until the
+                * initialization is complete.
+                * If it is us that is initializing, then this is an error (qemu
+                * is single threaded, so if we wait for the init to end we'll
+                * deadlock).
+                */
+
+               while (pa2_init_taskp != NULL) {
+                       if (pa2_init_taskp == current) {
+                               pa2_error("Trying to grab while initing\n");
+                               ret = -EAGAIN;  /* XXX */
+                               break;
+                       }
+                       PRINT_DEBUG("Waiting for initialization to complete\n");
+                       spin_unlock_irq(&pa2_i8042_lock);
+                       expire = schedule_timeout_interruptible(HZ / 10);
+                       spin_lock_irq(&pa2_i8042_lock);
+                       if (expire == 0) {
+                               continue;
+                       }
+                       ret = -EINTR;   /* XXX */
+                       break;
+               }
+               if (ret == 0 && pa2_initialized == 0) {
+                       ret = -EINVAL;
+               }
+       }
+       spin_unlock_irq(&pa2_i8042_lock);
+       if (ret != 0) {
+               goto out;
+       }
+
+       /*
+        * Once initialized, cannot be intialized again.  As we have an
+        * open, cannot become uninitialized, so safe to continue without lock.
+        */
+
+       spin_lock_irq(&sp->ps_lock);
+       if (byte == 0) {
+               if (sp->ps_grabbed != filp) {
+                       ret = -EINVAL;
+               } else {
+                       sp->ps_grabbed = NULL;
+                       if (sp->ps_waiting_grab != 0) {
+                               sp->ps_waiting_grab = 0;
+                               wake_up(&sp->ps_wait_grab);
+                       }
+               }
+               spin_unlock_irq(&sp->ps_lock);
+               goto out;
+       }
+
+
+       /*
+        * Set that we want focus, then wait for it to be given.
+        */
+
+       add_wait_queue(&sp->ps_wait_grab, &wait);
+       while (sp->ps_grabbed != NULL) {
+               sp->ps_waiting_grab = 1;
+               wake_up(&sp->ps_wait_poll);
+               set_current_state(TASK_INTERRUPTIBLE);
+               spin_unlock_irq(&sp->ps_lock);
+               PRINT_DEBUG("Waiting for device to come available...\n");
+               expire = schedule_timeout(HZ / 10);
+               spin_lock_irq(&sp->ps_lock);
+               if (expire == 0) {
+                       continue;
+               }
+               ret = -EINTR;   /* XXX */
+               break;
+       }
+       remove_wait_queue(&sp->ps_wait_grab, &wait);
+       if (ret == 0) {
+               sp->ps_grabbed = filp;
+               PRINT_DEBUG("Grabbed by %p\n", current);
+       }
+       spin_unlock_irq(&sp->ps_lock);
+
+       /*
+        * After opening the device, user-space driver should query
+        * us to see if the device has been initialized.
+        * On the last close, the device is considered to have been
+        * uninitialized.
+        */
+
+out:
+       return ret;
+}
+
+
+/*
+ * Write to the data port.
+ * If requested, clear the data queued for the given device.  This is needed
+ * as the write could be KBD or AUX cmd, and these clear any data queued for
+ * the device.  As there could be a pending data, clear this before sending
+ * the cmd.  Not 100% this last part is needed....XXX
+ */
+
+static inline int
+pa2_data_write(
+       struct file     *filp,
+       pa2_data_t      *arg,
+       int             index)
+{
+       pa2_state_t     *sp;
+       pa2_ps_t        *pp;
+       unsigned char   status;
+       unsigned char   data;
+       unsigned long   flags;
+       int             ret;
+
+       ASSERT(index == PA2_INDEX_KBD || index == PA2_INDEX_AUX);
+       sp = &pa2_state[index];
+
+       /*
+        * Debug only XXX
+        */
+
+       ret = 0;
+       spin_lock_irqsave(&sp->ps_lock, flags);
+       if (sp->ps_grabbed != filp && pa2_init_taskp != current) {
+               spin_unlock_irqrestore(&sp->ps_lock, flags);
+               pa2_error("Trying to write without perm: %p %p %p %p\n",
+                       sp->ps_grabbed, filp, pa2_init_taskp, current);
+               ret = -EACCES;
+               goto out;
+       }
+       spin_unlock_irqrestore(&sp->ps_lock, flags);
+
+       if (arg->pd_clear != 0) {
+               spin_lock_irqsave(&pa2_i8042_lock, flags);
+               status = pa2_status_read();
+               if (unlikely(status & PA2_CTL_STAT_OBF)) {
+                       data = pa2_data_read();
+               }
+       }
+       outb(arg->pd_data, PA2_DATA_PORT);
+       if (arg->pd_clear != 0) {
+               pp = sp->ps_queue;
+               spin_lock(&sp->ps_lock);
+               pp->pp_start = pp->pp_end;
+               spin_unlock(&sp->ps_lock);
+               spin_unlock_irqrestore(&pa2_i8042_lock, flags);
+       }
+
+out:
+       return ret;
+}
+
+static irqreturn_t
+pa2_irq(
+       int             irq,
+       void            *dev_id,
+       struct pt_regs  *regs)
+{
+       pa2_state_t     *sp;
+       pa2_ps_t        *pp;
+       unsigned long   flags;
+       unsigned int    next;
+       unsigned char   status;
+       unsigned char   data;
+       int             wake;
+       int             ret;
+
+       ASSERT(irq == PA2_KBD_IRQ || irq == PA2_AUX_IRQ);
+       PRINT_DEBUG("irq: %d\n", irq);
+       ret = 0;
+       spin_lock_irqsave(&pa2_i8042_lock, flags);
+       status = pa2_status_read();
+       if (unlikely(~status & PA2_CTL_STAT_OBF)) {
+               spin_unlock_irqrestore(&pa2_i8042_lock, flags);
+               PRINT_DEBUG("Interrupt %d without any data\n", irq);
+                goto out;
+       }
+       data = pa2_data_read();
+       spin_unlock_irqrestore(&pa2_i8042_lock, flags);
+
+       /*
+        * Can't rely on the IRQ to determine where the data is from (could
+        * be a data record read by the app has raced with this interrupt
+        * and read the data it was raised for).
+        */
+
+       sp = &pa2_state[PA2_INDEX_KBD];
+       if (status & PA2_CTL_STAT_AUX_OBF) {
+               sp = &pa2_state[PA2_INDEX_AUX];
+       }
+       pp = sp->ps_queue;
+       spin_lock_irqsave(&sp->ps_lock, flags);
+       next = (pp->pp_start + 1) & (PA2_BUF_SZ - 1);
+       if (next == pp->pp_end) {
+               pp->pp_overflow++;
+               spin_unlock_irqrestore(&sp->ps_lock, flags);
+               goto out;
+       }
+       pp->pp_entry[pp->pp_start].pe_data = data;
+       pp->pp_entry[pp->pp_start].pe_status = status;
+       pp->pp_start = next;
+       wake = 0;
+       if (waitqueue_active(&sp->ps_wait_poll)) {
+               wake = 1;
+       }
+       spin_unlock_irqrestore(&sp->ps_lock, flags);
+
+       if (wake) {
+               wake_up(&sp->ps_wait_poll);
+       }
+       ret = 1;
+
+out:
+       return IRQ_RETVAL(ret);
+}
+
+
+static int
+pa2_release(
+       struct inode    *inode,
+       struct file     *filp)
+{
+       pa2_state_t     *sp;
+       unsigned int    i;
+
+       ASSERT(filp->private_data != NULL);
+
+       PRINT_DEBUG("release\n");
+       sp = filp->private_data;
+       if (sp->ps_index != PA2_INDEX_CTL) {
+               ASSERT(sp->ps_index == PA2_INDEX_KBD ||
+                      sp->ps_index == PA2_INDEX_AUX);
+               spin_lock_irq(&sp->ps_lock);
+       PRINT_DEBUG("Release: %p %p...\n", sp->ps_grabbed, filp);
+               if (sp->ps_grabbed == filp) {
+               PRINT_DEBUG("Releasing grab...\n");
+                       sp->ps_grabbed = NULL;
+                       if (sp->ps_waiting_grab) {
+                               sp->ps_waiting_grab = 0;
+                               wake_up(&sp->ps_wait_grab);
+                       }
+               }
+               spin_unlock_irq(&sp->ps_lock);
+       }
+
+       spin_lock_irq(&pa2_i8042_lock);
+       if (sp->ps_index == PA2_INDEX_CTL) {
+
+               /*
+                * pa2_init_taskp needs all spinlocks to be modified (and,
+                * hence, only one to be read.
+                */
+
+               PRINT_DEBUG("release CTL\n");
+               sp = &pa2_state[0];
+               for (i = 0; i < 2; i++, sp++) {
+                       spin_lock(&sp->ps_lock);
+               }
+               if (pa2_init_taskp == current) {
+                       /* assert sem held XXX */
+                       pa2_init_taskp = NULL;
+                       up(&pa2_init_sem);
+               }
+               sp = &pa2_state[0];
+               for (i = 0; i < 2; i++, sp++) {
+                       spin_unlock(&sp->ps_lock);
+               }
+       }
+       ASSERT(pa2_open_count != 0);
+       pa2_open_count--;
+       if (pa2_open_count == 0) {
+               pa2_initialized = 0;
+               memset(&pa2_init_buffer, 0, sizeof (pa2_init_buffer));
+               /* XXX */
+               (void) pa2_ctl_mode_write_do(PA2_CTL_MODE_KCC);
+       }
+       spin_unlock_irq(&pa2_i8042_lock);
+
+       if (pa2_cmd_filp == filp) {
+               ASSERT(sp->ps_index == PA2_INDEX_CTL);
+               pa2_cmd_filp = NULL;
+               up(&pa2_cmd_sem);
+       }
+       filp->private_data = NULL;
+       return 0;
+}
+
+static int
+pa2_open(
+       struct inode    *inode,
+       struct file     *filp)
+{
+       struct proc_dir_entry   *dp;
+       int                     ret;
+       int             i;
+
+       ret = 0;
+       dp = PROC_I(inode)->pde;
+       for (i = 0; i < 3; i++) {
+               if (dp != pa2_proc_dev[i]) {
+                       continue;
+               }
+               if (i == PA2_INDEX_CTL) {
+                       filp->private_data = &pa2_ctl;
+               } else {
+                       filp->private_data = &pa2_state[i];
+               }
+               break;
+       }
+       ASSERT(i != 3);
+       ASSERT(filp->private_data != NULL);
+       spin_lock_irq(&pa2_i8042_lock);
+       pa2_open_count++;
+       spin_unlock_irq(&pa2_i8042_lock);
+       return ret;
+}
+
+/*
+ * Read records into given user-space buffer.
+ */
+
+static long
+pa2_record_read(
+       struct file             *filp,
+       void __user             *p)
+{
+       pa2_state_t             *sp;
+       pa2_ps_t                *pp;
+       pa2_rec_hd_t            phd;
+       pa2_entry_t             prec;
+       pa2_entry_t __user      *routp;
+       long                    ret;
+
+       ret = 0;
+       sp = filp->private_data;
+       ASSERT(sp->ps_index == PA2_INDEX_KBD ||
+              sp->ps_index == PA2_INDEX_AUX);
+       if (copy_from_user(&phd, p, sizeof (phd))) {
+               ret = -EFAULT;
+               goto out;
+       }
+       phd.ph_rec_num = 0;
+       phd.ph_focus = 0;
+       phd.ph_overflow = 0;
+       phd.ph_forced = 0;
+       routp = phd.ph_records;
+
+       pp = sp->ps_queue;
+       spin_lock_irq(&sp->ps_lock);
+       if (sp->ps_grabbed != filp && pa2_init_taskp != current) {
+               spin_unlock_irq(&sp->ps_lock);
+               pa2_error("Trying to access ports without perm: %p %p %p %p\n",
+                       sp->ps_grabbed, filp, pa2_init_taskp, current);
+               ret = -EACCES;
+               goto out;
+       }
+
+       /*
+        * While not out of records, and user-space buffer not full, copy
+        * out record.
+        */
+
+       while (pp->pp_end != pp->pp_start &&
+              phd.ph_rec_num != phd.ph_rec_size) {
+               prec.pe_data = pp->pp_entry[pp->pp_end].pe_data;
+               prec.pe_status = pp->pp_entry[pp->pp_end].pe_status;
+               pp->pp_end = (pp->pp_end + 1) & (PA2_BUF_SZ - 1);
+               spin_unlock_irq(&sp->ps_lock);
+
+               if (copy_to_user(routp, &prec, sizeof(prec))) {
+                       ret = -EFAULT;
+                       break;
+               }
+               routp++;
+               phd.ph_rec_num++;
+               spin_lock_irq(&sp->ps_lock);
+       }
+       if (sp->ps_waiting_grab) {
+               phd.ph_focus = 1;
+       }
+       if (pp->pp_overflow) {
+               pp->pp_overflow = 0;
+               phd.ph_overflow = 1;
+       }
+
+       /*
+        * If no records found, and space in buffer for at least one record,
+        * then add a force record.
+        * If the status indicates there is data available, and it matches the
+        * device we are reading from, then read data.
+        */
+
+       if (phd.ph_rec_num == 0 && phd.ph_rec_size != 0 && ret == 0) {
+               /* fetch a forced record */
+               phd.ph_forced = 1;
+               prec.pe_data = 0;
+               prec.pe_status = pa2_status_read();
+               if (prec.pe_status & PA2_CTL_STAT_OBF) {
+                       if (prec.pe_status & PA2_CTL_STAT_AUX_OBF) {
+                               if (sp->ps_index == PA2_INDEX_KBD) {
+                                       prec.pe_status &= ~PA2_CTL_STAT_AUX_OBF;
+                                       prec.pe_status &= ~PA2_CTL_STAT_OBF;
+                               } else {
+                                       prec.pe_data = pa2_data_read();
+                                       phd.ph_forced = 0;
+                               }
+                       } else {
+                               if (sp->ps_index == PA2_INDEX_AUX) {
+                                       prec.pe_status &= ~PA2_CTL_STAT_OBF;
+                               } else {
+                                       prec.pe_data = pa2_data_read();
+                                       phd.ph_forced = 0;
+                               }
+                       }
+               }
+               spin_unlock_irq(&sp->ps_lock);
+               if (copy_to_user(routp, &prec, sizeof(prec))) {
+                       ret = -EFAULT;
+               } else {
+                       phd.ph_rec_num++;
+               }
+       } else {
+               spin_unlock_irq(&sp->ps_lock);
+       }
+       if (copy_to_user(p, &phd, sizeof (phd))) {
+               ret = -EFAULT;
+       }
+
+out:
+       return ret;
+}
+
+static long
+pa2_ioctl_aux(
+       struct file     *filp,
+       unsigned int    cmd,
+       void __user     *p)
+{
+       long            ret;
+       pa2_data_t      arg;
+
+       ret = 0;
+       switch (cmd) {
+       case PA2_IOCTL_WR_DATA:
+               /* open for writing XXX */
+
+               /* grabbed or initializing XXX */
+               if (copy_from_user(&arg, p, sizeof (arg))) {
+                       return -EFAULT;
+               }
+               ret = pa2_data_write(filp, &arg, PA2_INDEX_AUX);
+               break;
+       
+       case PA2_IOCTL_RD_RECORD:
+               ret = pa2_record_read(filp, p);
+               break;
+
+       case PA2_IOCTL_GRAB:
+               ret = pa2_grab(filp, p);
+               break;
+
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static long
+pa2_ioctl_kbd(
+       struct file     *filp,
+       unsigned int    cmd,
+       void __user     *p)
+{
+       long            ret;
+       pa2_data_t      arg;
+
+       ret = 0;
+       switch (cmd) {
+       case PA2_IOCTL_WR_DATA:
+               /* write directly from the port */
+               /* mask interrupts/spinlock? XXX */
+               if (copy_from_user(&arg, p, sizeof (arg))) {
+                       return -EFAULT;
+               }
+               ret = pa2_data_write(filp, &arg, PA2_INDEX_KBD);
+               break;
+       
+       case PA2_IOCTL_RD_RECORD:
+               ret = pa2_record_read(filp, p);
+               break;
+
+       case PA2_IOCTL_GRAB:
+               ret = pa2_grab(filp, p);
+               break;
+
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static long
+pa2_ioctl_ctl(
+       struct file     *filp,
+       unsigned int    cmd,
+       void __user     *p)
+{
+       pa2_state_t     *sp;
+       long            ret;
+       u8              byte;
+
+       sp = filp->private_data;
+       ASSERT(filp->private_data == &pa2_ctl);
+       ret = 0;
+       switch (cmd) {
+       case PA2_IOCTL_WR_CMD:
+               /* write access? XXX */
+               if (copy_from_user(&byte, p, 1)) {
+                       return -EFAULT;
+               }
+               pa2_cmd_write(byte);
+               break;
+
+       case PA2_IOCTL_GRAB:
+               ret = pa2_grab(filp, p);
+               break;
+
+       case PA2_IOCTL_INIT_LOCK:
+               /*
+                * As the device cannot be grabbed until it has been
+                * initialized, XXX
+                */
+               if (copy_from_user(&byte, p, 1)) {
+                       return -EFAULT;
+               }
+               if (byte == 0) {
+                       if (pa2_init_taskp != current) {
+                               pa2_error("bad unlock\n");
+                               return -EINVAL;
+                       }
+                       pa2_init_taskp = NULL;
+                       up(&pa2_init_sem);
+                       return 0;
+               }
+               down(&pa2_init_sem);
+               /* take all spinlocks XXX */
+               pa2_init_taskp = current;
+               break;
+
+       case PA2_IOCTL_INIT_QUERY:
+               if (pa2_init_taskp != current) {
+                       pa2_error("bad query\n");
+                       ret = -EINVAL;
+                       break;
+               }
+               if (copy_to_user(p, &pa2_init_buffer,
+                                sizeof (pa2_init_buffer))) {
+                       ret = -EFAULT;
+                       break;
+               }
+               break;
+
+       case PA2_IOCTL_INIT_SET:
+               if (pa2_init_taskp != current) {
+                       pa2_error("bad set\n");
+                       ret = -EPERM;
+                       break;
+               }
+               /* check if already initialized XXX */
+               if (copy_from_user(&pa2_init_buffer_tmp, p,
+                                  sizeof (pa2_init_buffer_tmp))) {
+                       ret = -EFAULT;
+                       break;
+               }
+               if (pa2_init_buffer_tmp.di_len > PA2_DATA_INIT_MAX) {
+                       ret = -EINVAL;
+                       break;
+               }
+               spin_lock_irq(&pa2_i8042_lock);
+               memcpy(&pa2_init_buffer, &pa2_init_buffer_tmp,
+                      sizeof (pa2_init_buffer_tmp));
+               pa2_initialized = 1;
+               spin_unlock_irq(&pa2_i8042_lock);
+               break;
+
+       case PA2_IOCTL_INIT_CMD:
+               if (copy_from_user(&byte, p, 1)) {
+                       ret = -EFAULT;
+                       break;
+               }
+               if (byte == 1) {
+                       PRINT_DEBUG("Taking init cmd: %p\n", current);
+                       if (down_interruptible(&pa2_cmd_sem)) {
+                               ret = -ERESTARTSYS;
+                               break;
+                       }
+                       pa2_cmd_filp = filp;
+                       break;
+               }
+               PRINT_DEBUG("Releasing init_cmd: %p\n", current);
+               pa2_cmd_filp = NULL;
+               up(&pa2_cmd_sem);
+               break;
+
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static long
+pa2_ioctl(
+       struct file     *filp,
+       unsigned int    cmd,
+       unsigned long   arg)
+{
+       pa2_state_t     *sp;
+       long            ret;
+
+       sp = filp->private_data;
+       switch (sp->ps_index) {
+       case PA2_INDEX_KBD:
+               ret = pa2_ioctl_kbd(filp, cmd, (void __user *)arg);
+               break;
+
+       case PA2_INDEX_AUX:
+               ret = pa2_ioctl_aux(filp, cmd, (void __user *)arg);
+               break;
+
+       case PA2_INDEX_CTL:
+               ret = pa2_ioctl_ctl(filp, cmd, (void __user *)arg);
+               break;
+
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static unsigned int
+pa2_poll(
+       struct file     *filp,
+       poll_table      *wait)
+{
+       pa2_state_t     *sp;
+       pa2_ps_t        *pp;
+       unsigned int    mask;
+
+       /*
+        * Should never poll the ctl.
+        */
+
+       sp = filp->private_data;
+       if (sp->ps_index != PA2_INDEX_KBD && sp->ps_index != PA2_INDEX_AUX) {
+               ASSERT(0);
+               return POLLOUT | POLLWRNORM | POLLIN | POLLRDNORM;
+       }
+       poll_wait(filp, &sp->ps_wait_poll, wait);
+       pp = sp->ps_queue;
+       mask = POLLOUT | POLLWRNORM;
+       spin_lock_irq(&sp->ps_lock);
+       if (pp->pp_start != pp->pp_end || sp->ps_waiting_grab) {
+               mask |= POLLIN | POLLRDNORM;
+       }
+       spin_unlock_irq(&sp->ps_lock);
+       return mask;
+}
+
+static struct file_operations pa2_file_ops = {
+       .owner          = THIS_MODULE,
+       .open           = pa2_open,
+       .unlocked_ioctl = pa2_ioctl,
+       .poll           = pa2_poll,
+       .release        = pa2_release,
+};
+
+static void __exit
+pa2_deinit(
+       void)
+{
+       unsigned int    i;
+
+       /*
+        * Should place 8042 into a safe state; KBD and AUX devices disabled,
+        * and interrupts disabled too.
+        */
+
+       (void) pa2_ctl_mode_write_do(PA2_CTL_MODE_KCC);
+       for (i = 0; i < 3; i++) {
+               if (pa2_proc_dev[i] != NULL) {
+                       remove_proc_entry(pa2_path[i], NULL);
+                       pa2_proc_dev[i] = NULL;
+               }
+       }
+       free_irq(PA2_KBD_IRQ, NULL);
+       free_irq(PA2_AUX_IRQ, NULL);
+       return;
+}
+
+static int __init
+pa2_init(
+       void)
+{
+       pa2_state_t     *sp;
+       unsigned int    i;
+       int             ret;
+
+       /*
+        * Grab keyboard and aux interrupts.
+        */
+
+       init_MUTEX(&pa2_init_sem);
+       for (i = 0; i < 3; i++) {
+               pa2_proc_dev[i] = create_proc_entry(pa2_path[i], 0644, NULL);
+               if (pa2_proc_dev[i] != NULL) {
+                       pa2_proc_dev[i]->proc_fops = &pa2_file_ops;
+               }
+       }
+
+       sp = &pa2_state[0];
+       for (i = 0; i < 2; i++, sp++) {
+               spin_lock_init(&sp->ps_lock);
+               sp->ps_queue = &pa2_queue[i];
+               sp->ps_index = i;
+               init_waitqueue_head(&sp->ps_wait_grab);
+               init_waitqueue_head(&sp->ps_wait_poll);
+       }
+       spin_lock_init(&pa2_ctl.ps_lock);
+       pa2_ctl.ps_index = PA2_INDEX_CTL;
+
+       PRINT_DEBUG("Requesting interrupts...\n");
+       ret = request_irq(PA2_KBD_IRQ, pa2_irq, 0, "kbd", NULL);
+       if (ret != 0) {
+               pa2_error("unable to get keyboard interrupt\n");
+               /* remove procs XXX */
+               goto out;
+       }
+       ret = request_irq(PA2_AUX_IRQ, pa2_irq, 0, "aux", NULL);
+       if (ret != 0) {
+               pa2_error("unable to get aux interrupt\n");
+               /* remove procs XXX */
+               free_irq(PA2_KBD_IRQ, NULL);    /* ugh, NULL!!! */
+               goto out;
+       }
+
+       (void) pa2_ctl_mode_write_do(PA2_CTL_MODE_KCC);
+
+out:
+       return ret;
+}
+
+module_init(pa2_init);
+module_exit(pa2_deinit);
+
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/include/linux/pass2.h b/include/linux/pass2.h
new file mode 100644 (file)
index 0000000..a06517e
--- /dev/null
@@ -0,0 +1,69 @@
+#ifndef _K_PASS2_H
+#define _K_PASS2_H
+
+#define        PA2_IOCTL_GRAB          _IO('p', 0x02)
+#define        PA2_IOCTL_WR_DATA       _IO('p', 0x04)
+#define        PA2_IOCTL_WR_CMD        _IO('p', 0x06)
+#define        PA2_IOCTL_RD_RECORD     _IO('p', 0x07)
+
+#define        PA2_IOCTL_INIT_LOCK     _IO('p', 0x09)
+#define        PA2_IOCTL_INIT_QUERY    _IO('p', 0x10)
+#define        PA2_IOCTL_INIT_SET      _IO('p', 0x11)
+
+#define        PA2_IOCTL_INIT_CMD      _IO('p', 0x12)
+
+typedef struct pa2_entry_s {
+       u8      pe_status;
+       u8      pe_data;
+} pa2_entry_t;
+
+/*
+ * Data payload for PA2_IOCTL_WR_DATA.
+ */
+
+typedef struct pa2_data_s {
+       u8              pd_clear;
+       u8              pd_data;
+} pa2_data_t;
+
+/*
+ * Record header, used by PA2_IOCTL_RD_RECORD ioctl().
+ */
+
+typedef struct pa2_rec_hd_s {
+       u32             ph_rec_size;    /* in records (input) */
+       u32             ph_rec_num;     /* in records (output) */
+       u8              ph_focus;
+       u8              ph_overflow;
+       u8              ph_forced;
+       pa2_entry_t     *ph_records;
+} pa2_rec_hd_t;
+
+/*
+ *  pg_grab            - 0 = release, 1 = grab device.
+ */
+
+typedef struct pa2_grab_s {
+       u8              pg_grab;
+       u8              pg_flags;
+       u8              pg_aux_sample;
+       u8              pg_aux_res;
+       u8              pg_syn_mode_byte;
+} pa2_grab_t;
+
+/* pg_flags */
+#define        PA2_GRAB_SYNAPTIC       0x01
+
+/*
+ * Init data payload.  Used to pass discovered static data from primary
+ * VMs to secondaries.
+ */
+
+#define        PA2_DATA_INIT_MAX       2048
+
+typedef struct pa2_data_init_s {
+       u32             di_len;
+       u8              di_data[PA2_DATA_INIT_MAX];
+} pa2_data_init_t;
+
+#endif /* _K_PASS2_H */