From a59ef9a4e9b4248f9c5c836f4fcbd46f1f1d508f Mon Sep 17 00:00:00 2001 From: Mark Hemment Date: Fri, 29 May 2009 14:41:27 +0100 Subject: [PATCH] Kernel-side driver (pass2) for PS/2 pass-through for XenClient. --- master/pass2-driver | 1228 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1228 insertions(+) create mode 100644 master/pass2-driver diff --git a/master/pass2-driver b/master/pass2-driver new file mode 100644 index 0000000..85359df --- /dev/null +++ b/master/pass2-driver @@ -0,0 +1,1228 @@ +diff --git a/drivers/input/Makefile b/drivers/input/Makefile +index 98c4f9a..414082b 100644 +--- a/drivers/input/Makefile ++++ b/drivers/input/Makefile +@@ -21,6 +21,7 @@ obj-$(CONFIG_INPUT_JOYSTICK) += joystick/ + obj-$(CONFIG_INPUT_TABLET) += tablet/ + obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/ + obj-$(CONFIG_INPUT_MISC) += misc/ ++obj-$(CONFIG_INPUT_XEN) += xen/ + + obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o + +diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig +index 27d70d3..b2f4ff7 100644 +--- a/drivers/input/serio/Kconfig ++++ b/drivers/input/serio/Kconfig +@@ -19,7 +19,7 @@ config SERIO + if SERIO + + config SERIO_I8042 +- tristate "i8042 PC Keyboard controller" if EMBEDDED || !X86 ++ tristate "i8042 PC Keyboard controller" if EMBEDDED || X86 + default y + depends on !PARISC && (!ARM || ARCH_SHARK || FOOTBRIDGE_HOST) && !M68K && !BLACKFIN + ---help--- +diff --git a/drivers/input/xen/Kconfig b/drivers/input/xen/Kconfig +new file mode 100644 +index 0000000..c45f063 +--- /dev/null ++++ b/drivers/input/xen/Kconfig +@@ -0,0 +1,25 @@ ++# ++# Mouse driver configuration ++# ++menuconfig INPUT_XEN ++ bool "XEN" ++ default n ++ help ++ Say Y here for input devices specific to Xen. ++ This option doesn't affect the kernel. ++ ++ If unsure, say N. ++ ++if INPUT_XEN ++ ++config XEN_8042 ++ tristate "8042 KBD and Mouse" ++ default y ++ ---help--- ++ Say Y here if you have a PS/2 keyboard and/or mouse connected to ++ your system that you wish to virtualize between Xen Client guests. ++ If unsure, say Y. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called pass2 (for pass-through PS/2). ++endif +diff --git a/drivers/input/xen/Makefile b/drivers/input/xen/Makefile +new file mode 100644 +index 0000000..25e4bce +--- /dev/null ++++ b/drivers/input/xen/Makefile +@@ -0,0 +1,7 @@ ++# ++# Makefile for Xen Client input devices. ++# ++ ++obj-$(CONFIG_XEN_8042) += pass2.o ++ ++xeninput-objs := pass2.o +diff --git a/drivers/input/xen/pass2.c b/drivers/input/xen/pass2.c +new file mode 100644 +index 0000000..550b07f +--- /dev/null ++++ b/drivers/input/xen/pass2.c +@@ -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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++/** ++ ** 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"); -- 2.39.5