--- /dev/null
+/*
+ * 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");