i8042.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Note: this driver is only used to attach a keyboard when
* booting with ACPI enumeration turned off (acpi-enum=off).
*/
/*
* Unfortunately, soft interrupts are implemented poorly. Each additional
* soft interrupt user impacts the performance of all existing soft interrupt
* users.
*/
#define BUFSIZ 64
enum i8042_ports {
MAIN_PORT = 0,
};
#define NUM_PORTS 2
/*
* One of these for each port - main (keyboard) and aux (mouse).
*/
struct i8042_port {
int inumber;
#if defined(USE_SOFT_INTRS)
#else
#endif
struct i8042 *i8042_global;
/*
* wptr is next byte to write
*/
int wptr;
/*
* rptr is next byte to read, == wptr means empty
* NB: At full, one byte is unused.
*/
int rptr;
int overruns;
};
/*
* Describes entire 8042 device.
*/
struct i8042 {
};
/*
* i8042 hardware register definitions
*/
/*
* These are I/O registers, relative to the device's base (normally 0x60).
*/
/*
* These are bits in I8042_STAT.
*/
/*
* These are commands to the i8042 itself (as distinct from the devices
* attached to it).
*/
/*
* function prototypes for bus ops routines:
*/
/*
* function prototypes for dev ops routines:
*/
void *, dev_info_t **);
ddi_bus_config_op_t, void *);
/*
* bus ops and dev ops structures:
*/
static struct bus_ops i8042_bus_ops = {
NULL,
NULL,
NULL,
NULL, /* ddi_map_fault */
NULL, /* ddi_dma_map */
NULL, /* ddi_dma_allochdl */
NULL, /* ddi_dma_freehdl */
NULL, /* ddi_dma_bindhdl */
NULL, /* ddi_dma_unbindhdl */
NULL, /* ddi_dma_flush */
NULL, /* ddi_dma_win */
NULL, /* ddi_dma_mctl */
NULL, /* (*bus_get_eventcookie)(); */
NULL, /* (*bus_add_eventcall)(); */
NULL, /* (*bus_remove_eventcall)(); */
NULL, /* (*bus_post_event)(); */
NULL, /* bus_intr_ctl */
i8042_bus_config, /* bus_config */
i8042_bus_unconfig, /* bus_unconfig */
NULL, /* bus_fm_init */
NULL, /* bus_fm_fini */
NULL, /* bus_fm_access_enter */
NULL, /* bus_fm_access_exit */
NULL, /* bus_power */
i8042_intr_ops /* bus_intr_op */
};
0,
0,
(struct cb_ops *)0,
};
/*
* module definitions:
*/
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type of module. This one is a driver */
"i8042 nexus driver %I%", /* Name of module. */
&i8042_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
};
int
_init(void)
{
int e;
/*
* Install the module.
*/
e = mod_install(&modlinkage);
return (e);
}
int
_fini(void)
{
int e;
/*
* Remove the module.
*/
e = mod_remove(&modlinkage);
if (e != 0)
return (e);
return (e);
}
int
{
}
static void i8042_write_command_byte(struct i8042_port *, unsigned char);
unsigned int i8042_unclaimed_interrupts = 0;
static int
{
struct i8042_port *port;
enum i8042_ports which_port;
int rc;
unsigned char stat;
static ddi_device_acc_attr_t attr = {
};
if (cmd != DDI_ATTACH) {
/*
* We don't support anything but DDI_ATTACH. Eventually
* we probably should.
*/
return (DDI_FAILURE);
}
/*
* We're evil - we never release this.
*
* Well, we will when we have a detach routine.
*/
if (rc != DDI_SUCCESS)
goto fail_1;
if (rc != DDI_SUCCESS)
goto fail_2;
}
/*
* Disable input and interrupts from both the main and aux ports.
*
* It is difficult if not impossible to read the command byte in
* a completely clean way. Reading the command byte may cause
* an interrupt, and there is no way to suppress interrupts without
* writing the command byte. On a PC we might rely on the fact
* that IRQ 1 is disabled and guaranteed not shared, but on
* other platforms the interrupt line might be shared and so
* causing an interrupt could be bad.
*
* Since we can't read the command byte and update it, we
* just set it to static values:
*
* 0x80: 0 = Reserved, must be zero.
* 0x40: 1 = Translate to XT codes.
* 0x20: 1 = Disable aux (mouse) port.
* 0x10: 1 = Disable main (keyboard) port.
* 0x08: 0 = Reserved, must be zero.
* 0x04: 1 = System flag, 1 means passed self-test.
* Caution: setting this bit to zero causes some
* systems (HP Kayak XA) to fail to reboot without
* a hard reset.
* 0x02: 0 = Disable aux port interrupts.
* 0x01: 0 = Disable main port interrupts.
*/
if (rc != DDI_SUCCESS)
goto fail_2;
/*
* Some systems (SPARCengine-6) have both interrupts wired to
* a single interrupt line. We should try to detect this case
* and not call ddi_add_intr twice.
*/
if (rc != DDI_SUCCESS)
goto fail_3;
/* Discard any junk data that may have been left around */
for (;;) {
if (! (stat & I8042_STAT_OUTBF))
break;
}
/*
* As noted above, we simply set the command byte to the
* desired value. For normal operation, that value is:
*
* 0x80: 0 = Reserved, must be zero.
* 0x40: 1 = Translate to XT codes.
* 0x20: 0 = Enable aux (mouse) port.
* 0x10: 0 = Enable main (keyboard) port.
* 0x08: 0 = Reserved, must be zero.
* 0x04: 1 = System flag, 1 means passed self-test.
* Caution: setting this bit to zero causes some
* systems (HP Kayak XA) to fail to reboot without
* a hard reset.
* 0x02: 1 = Enable aux port interrupts.
* 0x01: 1 = Enable main port interrupts.
*/
return (rc);
return (rc);
}
/*ARGSUSED*/
static int
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
/*
* We do not support detach. Eventually we probably should, but
* (a) detach of nexus drivers is iffy at present, and (b)
* realistically, the keyboard never detaches. This assumption
* might come into question when USB keyboards are introduced.
*/
return (DDI_FAILURE);
}
/*
* The primary interface to us from our children is via virtual registers.
* This is the entry point that allows our children to "map" these
* virtual registers.
*/
static int
{
struct i8042_port *port;
enum i8042_ports which_port;
int *iprop;
unsigned int iprop_len;
int rnumber;
case DDI_MT_REGSPEC:
break;
case DDI_MT_RNUMBER:
DDI_SUCCESS) {
#if defined(DEBUG)
#endif
return (DDI_FAILURE);
}
#if defined(DEBUG)
if (iprop_len != 1) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
#endif
ddi_prop_free((void *)iprop);
#if defined(DEBUG)
"%s #%d: bad 'reg' value %d on %s@%s",
return (DDI_FAILURE);
}
#endif
break;
default:
#if defined(DEBUG)
#endif
return (DDI_FAILURE);
}
#if defined(DEBUG)
"%s #%d: partial mapping attempt for %s@%s ignored",
}
#endif
case DDI_MO_MAP_LOCKED:
#if defined(USE_SOFT_INTRS)
#else
#endif
/*
*/
*addrp = 0;
return (DDI_SUCCESS);
case DDI_MO_UNMAP:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/*
* i8042 hardware interrupt routine. Called for both main and aux port
* interrupts.
*/
static unsigned int
{
enum i8042_ports which_port;
unsigned char stat;
unsigned char byte;
int new_wptr;
struct i8042_port *port;
if (! (stat & I8042_STAT_OUTBF)) {
return (DDI_INTR_UNCLAIMED);
}
if (! port->initialized) {
return (DDI_INTR_CLAIMED);
}
#if defined(DEBUG)
}
#endif
return (DDI_INTR_CLAIMED);
}
#if defined(USE_SOFT_INTRS)
if (port->soft_intr_enabled)
#endif
#if !defined(USE_SOFT_INTRS)
#endif
return (DDI_INTR_CLAIMED);
}
static void
{
}
/*
* Send a byte to either the i8042 command or data register, depending on
* the argument.
*/
static void
{
/*
* First, wait for the i8042 to be ready to accept data.
*/
do {
} while (stat & I8042_STAT_INBF);
/*
* ... and then send the data.
*/
}
/*
* Here's the interface to the virtual registers on the device.
*
* Normal interrupt-driven I/O:
*
* I8042_INT_INPUT_AVAIL (r/o)
* Interrupt mode input bytes available? Zero = No.
* I8042_INT_INPUT_DATA (r/o)
* Fetch interrupt mode input byte.
* I8042_INT_OUTPUT_DATA (w/o)
* Interrupt mode output byte.
*
* Polled I/O, used by (e.g.) kmdb, when normal system services are
* unavailable:
*
* I8042_POLL_INPUT_AVAIL (r/o)
* Polled mode input bytes available? Zero = No.
* I8042_POLL_INPUT_DATA (r/o)
* Polled mode input byte.
* I8042_POLL_OUTPUT_DATA (w/o)
* Polled mode output byte.
*
* Note that in polled mode we cannot use cmn_err; only prom_printf is safe.
*/
static uint8_t
{
struct i8042_port *port;
ddi_acc_hdl_t *h;
h = (ddi_acc_hdl_t *)handlep;
case I8042_INT_INPUT_AVAIL:
return (ret);
case I8042_INT_INPUT_DATA:
} else {
#if defined(DEBUG)
"i8042: Tried to read from empty buffer");
#endif
ret = 0;
}
break;
#if defined(DEBUG)
case I8042_INT_OUTPUT_DATA:
case I8042_POLL_OUTPUT_DATA:
(void *)addr);
ret = 0;
break;
#endif
case I8042_POLL_INPUT_AVAIL:
return (B_TRUE);
for (;;) {
if ((stat & I8042_STAT_OUTBF) == 0)
return (B_FALSE);
case MAIN_PORT:
if ((stat & I8042_STAT_AUXBF) == 0)
return (B_TRUE);
break;
case AUX_PORT:
if ((stat & I8042_STAT_AUXBF) != 0)
return (B_TRUE);
break;
}
/*
* Data for wrong port pending; discard it.
*/
}
/* NOTREACHED */
case I8042_POLL_INPUT_DATA:
return (ret);
}
if ((stat & I8042_STAT_OUTBF) == 0) {
#if defined(DEBUG)
prom_printf("I8042_POLL_INPUT_DATA: no data!\n");
#endif
return (0);
}
case MAIN_PORT:
if ((stat & I8042_STAT_AUXBF) == 0)
return (ret);
break;
case AUX_PORT:
if ((stat & I8042_STAT_AUXBF) != 0)
return (ret);
break;
}
#if defined(DEBUG)
prom_printf("I8042_POLL_INPUT_DATA: data for wrong port!\n");
#endif
return (0);
default:
#if defined(DEBUG)
(void *)addr);
#endif
ret = 0;
break;
}
return (ret);
}
static void
{
struct i8042_port *port;
ddi_acc_hdl_t *h;
h = (ddi_acc_hdl_t *)handlep;
case I8042_INT_OUTPUT_DATA:
case I8042_POLL_OUTPUT_DATA:
break;
#if defined(DEBUG)
case I8042_INT_INPUT_AVAIL:
case I8042_INT_INPUT_DATA:
case I8042_POLL_INPUT_AVAIL:
case I8042_POLL_INPUT_DATA:
(void *)addr);
break;
default:
(void *)addr);
break;
#endif
}
}
/* ARGSUSED */
static int
{
struct i8042_port *port;
#if defined(USE_SOFT_INTRS)
int ret;
#endif
switch (intr_op) {
*(int *)result = DDI_INTR_TYPE_FIXED;
break;
case DDI_INTROP_GETCAP:
#if defined(__sparc)
/*
* For sparc, there is concern to pass to its parent,
* so just hard code it to 0
*/
*(int *)result = 0;
#else
== DDI_FAILURE)
*(int *)result = 0;
#endif /* defined(__sparc) */
break;
case DDI_INTROP_NINTRS:
*(int *)result = 1;
break;
case DDI_INTROP_ALLOC:
break;
case DDI_INTROP_FREE:
break;
case DDI_INTROP_GETPRI:
/* Hard coding it for x86 */
*(int *)result = 5;
break;
case DDI_INTROP_ADDISR:
#if defined(USE_SOFT_INTRS)
if (ret != DDI_SUCCESS) {
#if defined(DEBUG)
"Cannot add soft interrupt for %s #%d, ret=%d.",
#endif /* defined(DEBUG) */
return (ret);
}
#else /* defined(USE_SOFT_INTRS) */
#endif /* defined(USE_SOFT_INTRS) */
break;
case DDI_INTROP_REMISR:
#if defined(USE_SOFT_INTRS)
#else /* defined(USE_SOFT_INTRS) */
#endif /* defined(USE_SOFT_INTRS) */
break;
case DDI_INTROP_ENABLE:
#if defined(USE_SOFT_INTRS)
#else /* defined(USE_SOFT_INTRS) */
#endif /* defined(USE_SOFT_INTRS) */
break;
case DDI_INTROP_DISABLE:
#if defined(USE_SOFT_INTRS)
#endif /* defined(USE_SOFT_INTRS) */
break;
case DDI_INTROP_NAVAIL:
*(int *)result = 1;
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
{
int *iprop;
unsigned int iprop_len;
int which_port;
char name[16];
switch (op) {
case DDI_CTLOPS_INITCHILD:
DDI_SUCCESS) {
#if defined(DEBUG)
#endif
return (DDI_FAILURE);
}
which_port = iprop[0];
ddi_prop_free((void *)iprop);
return (DDI_SUCCESS);
case DDI_CTLOPS_UNINITCHILD:
return (DDI_SUCCESS);
case DDI_CTLOPS_REPORTDEV:
return (DDI_SUCCESS);
default:
}
/* NOTREACHED */
}
static void
{
static int alloced = 0;
char *acpi_prop;
if (alloced) { /* just in case we are multi-threaded */
return;
}
alloced = 1;
/* don't alloc unless acpi is off */
acpi_off = 1;
}
}
if (acpi_off == 0) {
return;
}
/* mouse */
"reg", 1);
"compatible", "pnpPNP,f03");
"device-type", "mouse");
(void) ndi_devi_bind_driver(xdip, 0);
/* keyboard */
"reg", 0);
"compatible", "pnpPNP,303");
"device-type", "keyboard");
(void) ndi_devi_bind_driver(xdip, 0);
}
static int
{
}
static int
{
}