From: Mark Hemment Date: Thu, 23 Apr 2009 13:33:53 +0000 (+0100) Subject: Kernel-side driver (pass2) for PS/2 pass-through for XenClient. X-Git-Url: http://xenbits.xensource.com/gitweb?a=commitdiff_plain;ds=inline;p=xenclient%2Fkernel.git Kernel-side driver (pass2) for PS/2 pass-through for XenClient. --- diff --git a/drivers/input/xen/pass2.c b/drivers/input/xen/pass2.c new file mode 100644 index 00000000..550b07f1 --- /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"); diff --git a/include/linux/pass2.h b/include/linux/pass2.h new file mode 100644 index 00000000..a06517e3 --- /dev/null +++ b/include/linux/pass2.h @@ -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 */