/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 * or http://www.opensolaris.org/os/licensing. * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include /* for prom_printf */ #include /* * Note: For x86, this driver is used to create keyboard/mouse nodes 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. This is not the case on SPARC, however. */ #ifdef __sparc #define USE_SOFT_INTRS #else #undef USE_SOFT_INTRS #endif /* * The command bytes are different for x86 and for SPARC because on x86, * all modern 8042s can properly translate scan code set 2 codes to * scan code set 1. On SPARC systems that have 8042s (e.g. Tadpole laptops), * setting the "translation" bit in the command byte has no effect. * This is potentially dangerous if, in the future, new SPARC systems uses 8042s * that implement the scan code translation when the translation bit is set. * * On SPARC, kb8042 will attempt to detect which scan code set the keyboard * is using. In order for that code to work, the real scan code set must be the * set that is returned by the keyboard (and not a different set that is * translated by the 8042). (e.g. If the translation bit were enabled here, * and the keyboard returned scan code set 2 when kb8042 queried it, kb8042 * would not be able to know with certainty that the scan codes it will receive * are set 2 scancodes, or set 1 translations made by the 8042). */ /* * 8042 Command Byte Layout: * * 0x80: 0 = Reserved, must be zero. * 0x40: 1 = Translate to XT codes. (0=No translation) * 0x20: 1 = Disable aux (mouse) port. (0=Enable port) * 0x10: 1 = Disable main (keyboard) port. (0=Enable 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. (1=Enable aux port interrupts) * 0x01: 0 = Disable main port interrupts. (1=Enable main port interrupts) * */ #if defined(__sparc) #define I8042_CMD_DISABLE_ALL 0x34 #define I8042_CMD_ENABLE_ALL 0x07 #elif defined(__i386) || defined(__amd64) #define I8042_CMD_DISABLE_ALL 0x74 #define I8042_CMD_ENABLE_ALL 0x47 #endif #define BUFSIZ 64 /* * Child nodes, used to determine which to create at bus_config time */ #define I8042_KEYBOARD 2 #define I8042_MOUSE 1 enum i8042_ports { MAIN_PORT = 0, AUX_PORT }; #define NUM_PORTS 2 /* * Only register at most MAX_INTERRUPTS interrupt handlers, * regardless of the number of interrupts in the prom node. * This is important, as registering for all interrupts on * some systems (e.g. Tadpole laptops) results in a flood * of spurious interrupts (for Tadpole, the first 2 interrupts * are for the keyboard and mouse, respectively, and the * third is for a proprietary device that is also accessed * via the same I/O addresses.) */ #define MAX_INTERRUPTS 2 /* * One of these for each port - main (keyboard) and aux (mouse). */ struct i8042_port { boolean_t initialized; dev_info_t *dip; int inumber; enum i8042_ports which; /* main or aux port */ #if defined(USE_SOFT_INTRS) ddi_softint_handle_t soft_hdl; boolean_t soft_intr_enabled; #else kmutex_t intr_mutex; #endif uint_t (*intr_func)(caddr_t arg1, caddr_t arg2); caddr_t intr_arg1; caddr_t intr_arg2; 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; unsigned char buf[BUFSIZ]; }; /* * Describes entire 8042 device. */ struct i8042 { dev_info_t *dip; struct i8042_port i8042_ports[NUM_PORTS]; kmutex_t i8042_mutex; kmutex_t i8042_out_mutex; boolean_t initialized; ddi_acc_handle_t io_handle; uint8_t *io_addr; int nintrs; ddi_iblock_cookie_t *iblock_cookies; uint_t init_state; /* Initialization states: */ #define I8042_INIT_BASIC 0x00000001 #define I8042_INIT_REGS_MAPPED 0x00000002 #define I8042_INIT_MUTEXES 0x00000004 #define I8042_INIT_INTRS_ENABLED 0x00000010 uint_t intrs_added; #ifdef __sparc timeout_id_t timeout_id; #endif }; /* * i8042 hardware register definitions */ /* * These are I/O registers, relative to the device's base (normally 0x60). */ #define I8042_DATA 0x00 /* read/write data here */ #define I8042_STAT 0x04 /* read status here */ #define I8042_CMD 0x04 /* write commands here */ /* * These are bits in I8042_STAT. */ #define I8042_STAT_OUTBF 0x01 /* Output (to host) buffer full */ #define I8042_STAT_INBF 0x02 /* Input (from host) buffer full */ #define I8042_STAT_AUXBF 0x20 /* Output buffer data is from aux */ /* * These are commands to the i8042 itself (as distinct from the devices * attached to it). */ #define I8042_CMD_RCB 0x20 /* Read command byte (we don't use) */ #define I8042_CMD_WCB 0x60 /* Write command byte */ #define I8042_CMD_WRITE_AUX 0xD4 /* Send next data byte to aux port */ /* * Maximum number of times to loop while clearing pending data from the * keyboard controller. */ #define MAX_JUNK_ITERATIONS 1000 /* * Maximum time to wait for the keyboard to become ready to accept data * (maximum time = MAX_WAIT_ITERATIONS * USECS_PER_WAIT (default is 250ms)) */ #define MAX_WAIT_ITERATIONS 25000 #define USECS_PER_WAIT 10 #ifdef __sparc #define PLATFORM_MATCH(s) (strncmp(ddi_get_name(ddi_root_node()), \ (s), strlen(s)) == 0) /* * On some older SPARC platforms that have problems with the * interrupt line attached to the PS/2 keyboard/mouse, it * may be necessary to change the operating mode of the nexus * to a polling-based (instead of interrupt-based) method. * this variable is present to enable a worst-case workaround so * owners of these systems can still retain a working keyboard. * * The `i8042_polled_mode' variable can be used to force polled * mode for platforms that have this issue, but for which * automatic relief is not implemented. * * In the off chance that one of the platforms is misidentified * as requiried polling mode, `i8042_force_interrupt_mode' can * be set to force the nexus to use interrupts. */ #define I8042_MIN_POLL_INTERVAL 1000 /* usecs */ int i8042_poll_interval = 8000; /* usecs */ int i8042_fast_poll_interval; /* usecs */ int i8042_slow_poll_interval; /* usecs */ boolean_t i8042_polled_mode = B_FALSE; boolean_t i8042_force_interrupt_mode = B_FALSE; #endif /* __sparc */ int max_wait_iterations = MAX_WAIT_ITERATIONS; /* * function prototypes for bus ops routines: */ static int i8042_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t offset, off_t len, caddr_t *addrp); static int i8042_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, void *result); /* * function prototypes for dev ops routines: */ static int i8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int i8042_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int i8042_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result); static int i8042_bus_config(dev_info_t *, uint_t, ddi_bus_config_op_t, void *, dev_info_t **); static int i8042_bus_unconfig(dev_info_t *, uint_t, ddi_bus_config_op_t, void *); #ifdef __sparc static int i8042_build_interrupts_property(dev_info_t *dip); static boolean_t i8042_is_polling_platform(void); #endif /* * bus ops and dev ops structures: */ static struct bus_ops i8042_bus_ops = { BUSO_REV, i8042_map, 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 */ i8042_ctlops, ddi_bus_prop_op, 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 */ }; static struct dev_ops i8042_ops = { DEVO_REV, 0, ddi_no_info, nulldev, 0, i8042_attach, i8042_detach, nodev, (struct cb_ops *)0, &i8042_bus_ops }; /* * module definitions: */ #include extern struct mod_ops mod_driverops; static struct modldrv modldrv = { &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 = { MODREV_1, (void *)&modldrv, NULL }; 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 _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } #define DRIVER_NAME(dip) ddi_driver_name(dip) static void i8042_timeout(void *arg); static unsigned int i8042_intr(caddr_t arg); static void i8042_write_command_byte(struct i8042 *, unsigned char); static uint8_t i8042_get8(ddi_acc_impl_t *handlep, uint8_t *addr); static void i8042_put8(ddi_acc_impl_t *handlep, uint8_t *addr, uint8_t value); static void i8042_send(struct i8042 *global, int reg, unsigned char cmd); unsigned int i8042_unclaimed_interrupts = 0; static void i8042_discard_junk_data(struct i8042 *global) { /* Discard any junk data that may have been left around */ for (;;) { unsigned char stat; stat = ddi_get8(global->io_handle, global->io_addr + I8042_STAT); if (! (stat & I8042_STAT_OUTBF)) break; (void) ddi_get8(global->io_handle, global->io_addr + I8042_DATA); } } static int i8042_cleanup(struct i8042 *global) { int which_port, i; struct i8042_port *port; ASSERT(global != NULL); if (global->initialized == B_TRUE) { /* * If any children still have regs mapped or interrupts * registered, return immediate failure (and do nothing). */ mutex_enter(&global->i8042_mutex); for (which_port = 0; which_port < NUM_PORTS; which_port++) { port = &global->i8042_ports[which_port]; if (port->initialized == B_TRUE) { mutex_exit(&global->i8042_mutex); return (DDI_FAILURE); } #if defined(USE_SOFT_INTRS) if (port->soft_hdl != 0) { mutex_exit(&global->i8042_mutex); return (DDI_FAILURE); } #else mutex_enter(&port->intr_mutex); if (port->intr_func != NULL) { mutex_exit(&port->intr_mutex); mutex_exit(&global->i8042_mutex); return (DDI_FAILURE); } mutex_exit(&port->intr_mutex); #endif } global->initialized = B_FALSE; mutex_exit(&global->i8042_mutex); } #ifdef __sparc /* If there may be an outstanding timeout, cancel it */ if (global->timeout_id != 0) { (void) untimeout(global->timeout_id); } #endif /* Stop the controller from generating interrupts */ if (global->init_state & I8042_INIT_INTRS_ENABLED) i8042_write_command_byte(global, I8042_CMD_DISABLE_ALL); if (global->intrs_added) { /* * Remove the interrupts in the reverse order in * which they were added */ for (i = global->nintrs - 1; i >= 0; i--) { if (global->intrs_added & (1 << i)) ddi_remove_intr(global->dip, i, global->iblock_cookies[i]); } } if (global->init_state & I8042_INIT_MUTEXES) { #ifndef USE_SOFT_INTRS for (which_port = 0; which_port < NUM_PORTS; which_port++) { port = &global->i8042_ports[which_port]; mutex_destroy(&port->intr_mutex); } #endif mutex_destroy(&global->i8042_out_mutex); mutex_destroy(&global->i8042_mutex); } if (global->init_state & I8042_INIT_REGS_MAPPED) ddi_regs_map_free(&global->io_handle); if (global->init_state & I8042_INIT_BASIC) { ddi_set_driver_private(global->dip, (caddr_t)NULL); if (global->nintrs > 0) { kmem_free(global->iblock_cookies, global->nintrs * sizeof (ddi_iblock_cookie_t)); } kmem_free(global, sizeof (struct i8042)); } return (DDI_SUCCESS); } #define OBF_WAIT_COUNT 1000 /* in granules of 10uS */ /* * Wait for the 8042 to fill the 'output' (from 8042 to host) * buffer. If 8042 fails to fill the output buffer within an * allowed time, return 1 (which means there is no data available), * otherwise return 0 */ static int i8042_wait_obf(struct i8042 *global) { int timer = 0; while (!(ddi_get8(global->io_handle, global->io_addr + I8042_STAT) & I8042_STAT_OUTBF)) { if (++timer > OBF_WAIT_COUNT) return (1); drv_usecwait(10); } return (0); } /* * Drain all queued bytes from the 8042. * Return 0 for no error, <> 0 if there was an error. */ static int i8042_purge_outbuf(struct i8042 *global) { int i; for (i = 0; i < MAX_JUNK_ITERATIONS; i++) { if (i8042_wait_obf(global)) break; (void) ddi_get8(global->io_handle, global->io_addr + I8042_DATA); } /* * If we hit the maximum number of iterations, then there * was a serious problem (e.g. our hardware may not be * present or working properly). */ return (i == MAX_JUNK_ITERATIONS); } static int i8042_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct i8042_port *port; enum i8042_ports which_port; int i; static ddi_device_acc_attr_t attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC, }; struct i8042 *global; #ifdef __sparc int interval; #endif switch (cmd) { case DDI_RESUME: global = (struct i8042 *)ddi_get_driver_private(dip); i8042_discard_junk_data(global); i8042_write_command_byte(global, I8042_CMD_ENABLE_ALL); return (DDI_SUCCESS); case DDI_ATTACH: /* Handled in the main function block */ break; default: return (DDI_FAILURE); } /* * DDI_ATTACH processing */ global = (struct i8042 *)kmem_zalloc(sizeof (struct i8042), KM_SLEEP); ddi_set_driver_private(dip, (caddr_t)global); global->dip = dip; global->initialized = B_FALSE; global->init_state |= I8042_INIT_BASIC; if (ddi_regs_map_setup(dip, 0, (caddr_t *)&global->io_addr, (offset_t)0, (offset_t)0, &attr, &global->io_handle) != DDI_SUCCESS) goto fail; global->init_state |= I8042_INIT_REGS_MAPPED; /* * Get the number of interrupts for this nexus */ if (ddi_dev_nintrs(dip, &global->nintrs) == DDI_FAILURE) goto fail; #ifdef __sparc if ((i8042_polled_mode || i8042_is_polling_platform()) && !i8042_force_interrupt_mode) { /* * If we're on a platform that has known * interrupt issues with the keyboard/mouse, * use polled mode. */ i8042_polled_mode = B_TRUE; global->nintrs = 0; } else if (global->nintrs == 0) { /* * If there are no interrupts on the i8042 node, * we may be on a brain-dead platform that only * has interrupts properties on i8042's children * (e.g. some UltraII-based boards) * In this case, scan first-level children, and * build a list of interrupts that each child uses, * then create an `interrupts' property on the nexus node * that contains the interrupts used by all children */ if (i8042_build_interrupts_property(dip) == DDI_FAILURE || ddi_dev_nintrs(dip, &global->nintrs) == DDI_FAILURE || global->nintrs == 0) { cmn_err(CE_WARN, "i8042#%d: No interrupts defined!", ddi_get_instance(global->dip)); goto fail; } } #else if (global->nintrs == 0) { cmn_err(CE_WARN, "i8042#%d: No interrupts defined!", ddi_get_instance(global->dip)); goto fail; } #endif if (global->nintrs > MAX_INTERRUPTS) global->nintrs = MAX_INTERRUPTS; if (global->nintrs > 0) { global->iblock_cookies = kmem_zalloc(global->nintrs * sizeof (ddi_iblock_cookie_t), KM_NOSLEEP); for (i = 0; i < global->nintrs; i++) { if (ddi_get_iblock_cookie(dip, i, &global->iblock_cookies[i]) != DDI_SUCCESS) goto fail; } } else global->iblock_cookies = NULL; mutex_init(&global->i8042_mutex, NULL, MUTEX_DRIVER, (global->nintrs > 0) ? global->iblock_cookies[0] : NULL); mutex_init(&global->i8042_out_mutex, NULL, MUTEX_DRIVER, NULL); for (which_port = 0; which_port < NUM_PORTS; ++which_port) { port = &global->i8042_ports[which_port]; port->initialized = B_FALSE; port->i8042_global = global; port->which = which_port; #if defined(USE_SOFT_INTRS) port->soft_hdl = 0; #else /* * Assume that the interrupt block cookie for port * is iblock_cookies[] (a 1:1 mapping). If there are not * enough interrupts to cover the number of ports, use * the cookie from interrupt 0. */ if (global->nintrs > 0) mutex_init(&port->intr_mutex, NULL, MUTEX_DRIVER, global->iblock_cookies[(which_port < global->nintrs) ? which_port : 0]); else mutex_init(&port->intr_mutex, NULL, MUTEX_DRIVER, NULL); #endif } global->init_state |= I8042_INIT_MUTEXES; /* * 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. */ i8042_write_command_byte(global, I8042_CMD_DISABLE_ALL); global->init_state &= ~I8042_INIT_INTRS_ENABLED; /* Discard any junk data that may have been left around */ if (i8042_purge_outbuf(global) != 0) goto fail; /* * Assume the number of interrupts is less that the number of * bits in the variable used to keep track of which interrupt * was added. */ ASSERT(global->nintrs <= (sizeof (global->intrs_added) * NBBY)); for (i = 0; i < global->nintrs; i++) { /* * The 8042 handles all interrupts, because all * device access goes through the same I/O addresses. */ if (ddi_add_intr(dip, i, (ddi_iblock_cookie_t *)NULL, (ddi_idevice_cookie_t *)NULL, i8042_intr, (caddr_t)global) != DDI_SUCCESS) goto fail; global->intrs_added |= (1 << i); } global->initialized = B_TRUE; /* * Enable the main and aux data ports and interrupts */ i8042_write_command_byte(global, I8042_CMD_ENABLE_ALL); global->init_state |= I8042_INIT_INTRS_ENABLED; #ifdef __sparc if (i8042_polled_mode) { /* * Do not allow anyone to set the polling interval * to an interval more frequent than I8042_MIN_POLL_INTERVAL -- * it could hose the system. */ interval = i8042_poll_interval; if (interval < I8042_MIN_POLL_INTERVAL) interval = I8042_MIN_POLL_INTERVAL; i8042_fast_poll_interval = interval; i8042_slow_poll_interval = interval << 3; global->timeout_id = timeout(i8042_timeout, global, drv_usectohz(i8042_slow_poll_interval)); } #endif return (DDI_SUCCESS); fail: /* cleanup will succeed because no children have attached yet */ (void) i8042_cleanup(global); return (DDI_FAILURE); } /*ARGSUSED*/ static int i8042_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { struct i8042 *global = (struct i8042 *)ddi_get_driver_private(dip); ASSERT(global != NULL); switch (cmd) { case DDI_SUSPEND: /* * Do not disable the keyboard controller for x86 suspend, as * the keyboard can be used to bring the system out of * suspend. */ #ifdef __sparc /* Disable interrupts and controller devices before suspend */ i8042_write_command_byte(global, I8042_CMD_DISABLE_ALL); #endif return (DDI_SUCCESS); case DDI_DETACH: /* DETACH can only succeed if cleanup succeeds */ return (i8042_cleanup(global)); default: 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 i8042_map( dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t offset, off_t len, caddr_t *addrp) { struct i8042_port *port; struct i8042 *global; enum i8042_ports which_port; int *iprop; unsigned int iprop_len; int rnumber; ddi_acc_hdl_t *handle; ddi_acc_impl_t *ap; global = ddi_get_driver_private(dip); switch (mp->map_type) { case DDI_MT_REGSPEC: which_port = *(int *)mp->map_obj.rp; break; case DDI_MT_RNUMBER: rnumber = mp->map_obj.rnumber; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS, "reg", &iprop, &iprop_len) != DDI_SUCCESS) { #if defined(DEBUG) cmn_err(CE_WARN, "%s #%d: Missing 'reg' on %s@%s", DRIVER_NAME(dip), ddi_get_instance(dip), ddi_node_name(rdip), ddi_get_name_addr(rdip)); #endif return (DDI_FAILURE); } #if defined(DEBUG) if (iprop_len != 1) { cmn_err(CE_WARN, "%s #%d: Malformed 'reg' on %s@%s", DRIVER_NAME(dip), ddi_get_instance(dip), ddi_node_name(rdip), ddi_get_name_addr(rdip)); return (DDI_FAILURE); } if (rnumber < 0 || rnumber >= iprop_len) { cmn_err(CE_WARN, "%s #%d: bad map request for %s@%s", DRIVER_NAME(dip), ddi_get_instance(dip), ddi_node_name(rdip), ddi_get_name_addr(rdip)); return (DDI_FAILURE); } #endif which_port = iprop[rnumber]; ddi_prop_free((void *)iprop); #if defined(DEBUG) if (which_port != MAIN_PORT && which_port != AUX_PORT) { cmn_err(CE_WARN, "%s #%d: bad 'reg' value %d on %s@%s", DRIVER_NAME(dip), ddi_get_instance(dip), which_port, ddi_node_name(rdip), ddi_get_name_addr(rdip)); return (DDI_FAILURE); } #endif break; default: #if defined(DEBUG) cmn_err(CE_WARN, "%s #%d: unknown map type %d for %s@%s", DRIVER_NAME(dip), ddi_get_instance(dip), mp->map_type, ddi_node_name(rdip), ddi_get_name_addr(rdip)); #endif return (DDI_FAILURE); } #if defined(DEBUG) if (offset != 0 || len != 0) { cmn_err(CE_WARN, "%s #%d: partial mapping attempt for %s@%s ignored", DRIVER_NAME(dip), ddi_get_instance(dip), ddi_node_name(rdip), ddi_get_name_addr(rdip)); } #endif port = &global->i8042_ports[which_port]; switch (mp->map_op) { case DDI_MO_MAP_LOCKED: #if defined(USE_SOFT_INTRS) port->soft_intr_enabled = B_FALSE; #else port->intr_func = NULL; #endif port->wptr = 0; port->rptr = 0; port->dip = dip; port->inumber = 0; port->initialized = B_TRUE; handle = mp->map_handlep; handle->ah_bus_private = port; handle->ah_addr = 0; ap = (ddi_acc_impl_t *)handle->ah_platform_private; /* * Only single get/put 8 is supported on this "bus". */ ap->ahi_put8 = i8042_put8; ap->ahi_get8 = i8042_get8; ap->ahi_put16 = NULL; ap->ahi_get16 = NULL; ap->ahi_put32 = NULL; ap->ahi_get32 = NULL; ap->ahi_put64 = NULL; ap->ahi_get64 = NULL; ap->ahi_rep_put8 = NULL; ap->ahi_rep_get8 = NULL; ap->ahi_rep_put16 = NULL; ap->ahi_rep_get16 = NULL; ap->ahi_rep_put32 = NULL; ap->ahi_rep_get32 = NULL; ap->ahi_rep_put64 = NULL; ap->ahi_rep_get64 = NULL; *addrp = 0; return (DDI_SUCCESS); case DDI_MO_UNMAP: port->initialized = B_FALSE; return (DDI_SUCCESS); default: cmn_err(CE_WARN, "%s: map operation %d not supported", DRIVER_NAME(dip), mp->map_op); return (DDI_FAILURE); } } #ifdef __sparc static void i8042_timeout(void *arg) { struct i8042 *i8042_p = (struct i8042 *)arg; int interval; /* * Allow the polling speed to be changed on the fly -- * catch it here and update the intervals used. */ if (i8042_fast_poll_interval != i8042_poll_interval) { interval = i8042_poll_interval; if (interval < I8042_MIN_POLL_INTERVAL) interval = I8042_MIN_POLL_INTERVAL; i8042_fast_poll_interval = interval; i8042_slow_poll_interval = interval << 3; } /* * If the ISR returned true, start polling at a faster rate to * increate responsiveness. Once the keyboard or mouse go idle, * the ISR will return UNCLAIMED, and we'll go back to the slower * polling rate. This gives some positive hysteresis (but not * negative, since we go back to the slower polling interval after * only one UNCLAIMED). This has shown to be responsive enough, * even for fast typers. */ interval = (i8042_intr((caddr_t)i8042_p) == DDI_INTR_CLAIMED) ? i8042_fast_poll_interval : i8042_slow_poll_interval; if (i8042_polled_mode) i8042_p->timeout_id = timeout(i8042_timeout, arg, drv_usectohz(interval)); else i8042_p->timeout_id = 0; } #endif /* * i8042 hardware interrupt routine. Called for both main and aux port * interrupts. */ static unsigned int i8042_intr(caddr_t arg) { struct i8042 *global = (struct i8042 *)arg; enum i8042_ports which_port; unsigned char stat; unsigned char byte; int new_wptr; struct i8042_port *port; mutex_enter(&global->i8042_mutex); stat = ddi_get8(global->io_handle, global->io_addr + I8042_STAT); if (! (stat & I8042_STAT_OUTBF)) { ++i8042_unclaimed_interrupts; mutex_exit(&global->i8042_mutex); return (DDI_INTR_UNCLAIMED); } byte = ddi_get8(global->io_handle, global->io_addr + I8042_DATA); which_port = (stat & I8042_STAT_AUXBF) ? AUX_PORT : MAIN_PORT; port = &global->i8042_ports[which_port]; if (! port->initialized) { mutex_exit(&global->i8042_mutex); return (DDI_INTR_CLAIMED); } new_wptr = (port->wptr + 1) % BUFSIZ; if (new_wptr == port->rptr) { port->overruns++; #if defined(DEBUG) if (port->overruns % 50 == 1) { cmn_err(CE_WARN, "i8042/%d: %d overruns\n", which_port, port->overruns); } #endif mutex_exit(&global->i8042_mutex); return (DDI_INTR_CLAIMED); } port->buf[port->wptr] = byte; port->wptr = new_wptr; #if defined(USE_SOFT_INTRS) if (port->soft_intr_enabled) (void) ddi_intr_trigger_softint(port->soft_hdl, port->intr_arg2); #endif mutex_exit(&global->i8042_mutex); #if !defined(USE_SOFT_INTRS) mutex_enter(&port->intr_mutex); if (port->intr_func != NULL) port->intr_func(port->intr_arg1, NULL); mutex_exit(&port->intr_mutex); #endif return (DDI_INTR_CLAIMED); } static void i8042_write_command_byte(struct i8042 *global, unsigned char cb) { mutex_enter(&global->i8042_out_mutex); i8042_send(global, I8042_CMD, I8042_CMD_WCB); i8042_send(global, I8042_DATA, cb); mutex_exit(&global->i8042_out_mutex); } /* * Send a byte to either the i8042 command or data register, depending on * the argument. */ static void i8042_send(struct i8042 *global, int reg, unsigned char val) { uint8_t stat; int tries = 0; /* * First, wait for the i8042 to be ready to accept data. */ /*CONSTANTCONDITION*/ while (1) { stat = ddi_get8(global->io_handle, global->io_addr + I8042_STAT); if ((stat & I8042_STAT_INBF) == 0) { ddi_put8(global->io_handle, global->io_addr+reg, val); break; } /* Don't wait unless we're going to check again */ if (++tries >= max_wait_iterations) break; else drv_usecwait(USECS_PER_WAIT); } #ifdef DEBUG if (tries >= MAX_WAIT_ITERATIONS) cmn_err(CE_WARN, "i8042_send: timeout!"); #endif } /* * 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 i8042_get8(ddi_acc_impl_t *handlep, uint8_t *addr) { struct i8042_port *port; struct i8042 *global; uint8_t ret; ddi_acc_hdl_t *h; uint8_t stat; h = (ddi_acc_hdl_t *)handlep; port = (struct i8042_port *)h->ah_bus_private; global = port->i8042_global; switch ((uintptr_t)addr) { case I8042_INT_INPUT_AVAIL: mutex_enter(&global->i8042_mutex); ret = port->rptr != port->wptr; mutex_exit(&global->i8042_mutex); return (ret); case I8042_INT_INPUT_DATA: mutex_enter(&global->i8042_mutex); if (port->rptr != port->wptr) { ret = port->buf[port->rptr]; port->rptr = (port->rptr + 1) % BUFSIZ; } else { #if defined(DEBUG) cmn_err(CE_WARN, "i8042: Tried to read from empty buffer"); #endif ret = 0; } mutex_exit(&global->i8042_mutex); break; #if defined(DEBUG) case I8042_INT_OUTPUT_DATA: case I8042_POLL_OUTPUT_DATA: cmn_err(CE_WARN, "i8042: read of write-only register 0x%p", (void *)addr); ret = 0; break; #endif case I8042_POLL_INPUT_AVAIL: if (port->rptr != port->wptr) return (B_TRUE); for (;;) { stat = ddi_get8(global->io_handle, global->io_addr + I8042_STAT); if ((stat & I8042_STAT_OUTBF) == 0) return (B_FALSE); switch (port->which) { 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; default: cmn_err(CE_WARN, "data from unknown port: %d", port->which); } /* * Data for wrong port pending; discard it. */ (void) ddi_get8(global->io_handle, global->io_addr + I8042_DATA); } /* NOTREACHED */ case I8042_POLL_INPUT_DATA: if (port->rptr != port->wptr) { ret = port->buf[port->rptr]; port->rptr = (port->rptr + 1) % BUFSIZ; return (ret); } stat = ddi_get8(global->io_handle, global->io_addr + I8042_STAT); if ((stat & I8042_STAT_OUTBF) == 0) { #if defined(DEBUG) prom_printf("I8042_POLL_INPUT_DATA: no data!\n"); #endif return (0); } ret = ddi_get8(global->io_handle, global->io_addr + I8042_DATA); switch (port->which) { 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) cmn_err(CE_WARN, "i8042: read of undefined register 0x%p", (void *)addr); #endif ret = 0; break; } return (ret); } static void i8042_put8(ddi_acc_impl_t *handlep, uint8_t *addr, uint8_t value) { struct i8042_port *port; struct i8042 *global; ddi_acc_hdl_t *h; h = (ddi_acc_hdl_t *)handlep; port = (struct i8042_port *)h->ah_bus_private; global = port->i8042_global; switch ((uintptr_t)addr) { case I8042_INT_OUTPUT_DATA: case I8042_POLL_OUTPUT_DATA: if ((uintptr_t)addr == I8042_INT_OUTPUT_DATA) mutex_enter(&global->i8042_out_mutex); if (port->which == AUX_PORT) i8042_send(global, I8042_CMD, I8042_CMD_WRITE_AUX); i8042_send(global, I8042_DATA, value); if ((uintptr_t)addr == I8042_INT_OUTPUT_DATA) mutex_exit(&global->i8042_out_mutex); break; #if defined(DEBUG) case I8042_INT_INPUT_AVAIL: case I8042_INT_INPUT_DATA: case I8042_POLL_INPUT_AVAIL: case I8042_POLL_INPUT_DATA: cmn_err(CE_WARN, "i8042: write of read-only register 0x%p", (void *)addr); break; default: cmn_err(CE_WARN, "i8042: read of undefined register 0x%p", (void *)addr); break; #endif } } /* ARGSUSED */ static int i8042_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result) { struct i8042_port *port; #if defined(USE_SOFT_INTRS) struct i8042 *global; int ret; #endif switch (intr_op) { case DDI_INTROP_SUPPORTED_TYPES: *(int *)result = DDI_INTR_TYPE_FIXED; break; case DDI_INTROP_GETCAP: if (i_ddi_intr_ops(dip, rdip, intr_op, hdlp, result) == DDI_FAILURE) *(int *)result = 0; break; case DDI_INTROP_NINTRS: case DDI_INTROP_NAVAIL: *(int *)result = 1; break; case DDI_INTROP_ALLOC: *(int *)result = hdlp->ih_scratch1; break; case DDI_INTROP_FREE: break; case DDI_INTROP_GETPRI: /* Hard coding it for x86 */ *(int *)result = 5; break; case DDI_INTROP_ADDISR: port = ddi_get_parent_data(rdip); #if defined(USE_SOFT_INTRS) global = port->i8042_global; ret = ddi_intr_add_softint(rdip, &port->soft_hdl, I8042_SOFTINT_PRI, hdlp->ih_cb_func, hdlp->ih_cb_arg1); if (ret != DDI_SUCCESS) { #if defined(DEBUG) cmn_err(CE_WARN, "%s #%d: " "Cannot add soft interrupt for %s #%d, ret=%d.", DRIVER_NAME(dip), ddi_get_instance(dip), DRIVER_NAME(rdip), ddi_get_instance(rdip), ret); #endif /* defined(DEBUG) */ return (ret); } #else /* defined(USE_SOFT_INTRS) */ mutex_enter(&port->intr_mutex); port->intr_func = hdlp->ih_cb_func; port->intr_arg1 = hdlp->ih_cb_arg1; port->intr_arg2 = hdlp->ih_cb_arg2; mutex_exit(&port->intr_mutex); #endif /* defined(USE_SOFT_INTRS) */ break; case DDI_INTROP_REMISR: port = ddi_get_parent_data(rdip); #if defined(USE_SOFT_INTRS) global = port->i8042_global; mutex_enter(&global->i8042_mutex); port->soft_hdl = 0; mutex_exit(&global->i8042_mutex); #else /* defined(USE_SOFT_INTRS) */ mutex_enter(&port->intr_mutex); port->intr_func = NULL; mutex_exit(&port->intr_mutex); #endif /* defined(USE_SOFT_INTRS) */ break; case DDI_INTROP_ENABLE: port = ddi_get_parent_data(rdip); #if defined(USE_SOFT_INTRS) global = port->i8042_global; mutex_enter(&global->i8042_mutex); port->soft_intr_enabled = B_TRUE; if (port->wptr != port->rptr) (void) ddi_intr_trigger_softint(port->soft_hdl, port->intr_arg2); mutex_exit(&global->i8042_mutex); #else /* defined(USE_SOFT_INTRS) */ mutex_enter(&port->intr_mutex); if (port->wptr != port->rptr) port->intr_func(port->intr_arg1, port->intr_arg2); mutex_exit(&port->intr_mutex); #endif /* defined(USE_SOFT_INTRS) */ break; case DDI_INTROP_DISABLE: #if defined(USE_SOFT_INTRS) port = ddi_get_parent_data(rdip); global = port->i8042_global; mutex_enter(&global->i8042_mutex); port->soft_intr_enabled = B_FALSE; (void) ddi_intr_remove_softint(port->soft_hdl); mutex_exit(&global->i8042_mutex); #endif /* defined(USE_SOFT_INTRS) */ break; default: return (DDI_FAILURE); } return (DDI_SUCCESS); } static int i8042_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, void *result) { int *iprop; unsigned int iprop_len; int which_port; char name[16]; struct i8042 *global; dev_info_t *child; global = ddi_get_driver_private(dip); switch (op) { case DDI_CTLOPS_INITCHILD: child = (dev_info_t *)arg; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "reg", &iprop, &iprop_len) != DDI_SUCCESS) { #if defined(DEBUG) cmn_err(CE_WARN, "%s #%d: Missing 'reg' on %s@???", DRIVER_NAME(dip), ddi_get_instance(dip), ddi_node_name(child)); #endif return (DDI_FAILURE); } which_port = iprop[0]; ddi_prop_free((void *)iprop); (void) sprintf(name, "%d", which_port); ddi_set_name_addr(child, name); ddi_set_parent_data(child, (caddr_t)&global->i8042_ports[which_port]); return (DDI_SUCCESS); case DDI_CTLOPS_UNINITCHILD: child = (dev_info_t *)arg; ddi_set_name_addr(child, NULL); ddi_set_parent_data(child, NULL); return (DDI_SUCCESS); case DDI_CTLOPS_REPORTDEV: cmn_err(CE_CONT, "?8042 device: %s@%s, %s # %d\n", ddi_node_name(rdip), ddi_get_name_addr(rdip), DRIVER_NAME(rdip), ddi_get_instance(rdip)); return (DDI_SUCCESS); default: return (ddi_ctlops(dip, rdip, op, arg, result)); } /* NOTREACHED */ } #if defined(__i386) || defined(__amd64) static dev_info_t * i8042_devi_findchild_by_node_name(dev_info_t *pdip, char *nodename) { dev_info_t *child; ASSERT(DEVI_BUSY_OWNED(pdip)); if (nodename == NULL) { return ((dev_info_t *)NULL); } for (child = ddi_get_child(pdip); child != NULL; child = ddi_get_next_sibling(child)) { if (strcmp(ddi_node_name(child), nodename) == 0) break; } return (child); } static void alloc_kb_mouse(dev_info_t *i8042_dip, int nodes_needed) { dev_info_t *xdip; int acpi_off = 0; char *acpi_prop; /* don't alloc unless acpi is off */ if (ddi_prop_lookup_string(DDI_DEV_T_ANY, ddi_root_node(), DDI_PROP_DONTPASS, "acpi-enum", &acpi_prop) == DDI_PROP_SUCCESS) { if (strcmp("off", acpi_prop) == 0) { acpi_off = 1; } ddi_prop_free(acpi_prop); } if (acpi_off == 0) { return; } if (nodes_needed & I8042_MOUSE) { /* mouse */ ndi_devi_alloc_sleep(i8042_dip, "mouse", (pnode_t)DEVI_SID_NODEID, &xdip); (void) ndi_prop_update_int(DDI_DEV_T_NONE, xdip, "reg", 1); (void) ndi_prop_update_int(DDI_DEV_T_NONE, xdip, "interrupts", 2); (void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip, "compatible", "pnpPNP,f03"); /* * The device_type property does not matter on SPARC. Retain it * on x86 for compatibility with the previous pseudo-prom. */ (void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip, "device_type", "mouse"); (void) ndi_devi_bind_driver(xdip, 0); } if (nodes_needed & I8042_KEYBOARD) { /* keyboard */ ndi_devi_alloc_sleep(i8042_dip, "keyboard", (pnode_t)DEVI_SID_NODEID, &xdip); (void) ndi_prop_update_int(DDI_DEV_T_NONE, xdip, "reg", 0); (void) ndi_prop_update_int(DDI_DEV_T_NONE, xdip, "interrupts", 1); (void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip, "compatible", "pnpPNP,303"); (void) ndi_prop_update_string(DDI_DEV_T_NONE, xdip, "device_type", "keyboard"); (void) ndi_devi_bind_driver(xdip, 0); } } #endif static int i8042_bus_config(dev_info_t *parent, uint_t flags, ddi_bus_config_op_t op, void *arg, dev_info_t **childp) { #if defined(__i386) || defined(__amd64) int nodes_needed = 0; int circ; /* * On x86 systems, if ACPI is disabled, the only way the * keyboard and mouse can be enumerated is by creating them * manually. The following code searches for the existence of * the keyboard and mouse nodes and creates them if they are not * found. */ ndi_devi_enter(parent, &circ); if (i8042_devi_findchild_by_node_name(parent, "keyboard") == NULL) nodes_needed |= I8042_KEYBOARD; if (i8042_devi_findchild_by_node_name(parent, "mouse") == NULL) nodes_needed |= I8042_MOUSE; /* If the mouse and keyboard nodes do not already exist, create them */ if (nodes_needed) alloc_kb_mouse(parent, nodes_needed); ndi_devi_exit(parent, circ); #endif return (ndi_busop_bus_config(parent, flags, op, arg, childp, 0)); } static int i8042_bus_unconfig(dev_info_t *parent, uint_t flags, ddi_bus_config_op_t op, void *arg) { /* * The NDI_UNCONFIG flag allows the reference count on this nexus to be * decremented when children's drivers are unloaded, enabling the nexus * itself to be unloaded. */ return (ndi_busop_bus_unconfig(parent, flags | NDI_UNCONFIG, op, arg)); } #ifdef __sparc static int i8042_build_interrupts_property(dev_info_t *dip) { dev_info_t *child = ddi_get_child(dip); uint_t nintr; int *intrs = NULL; int interrupts[MAX_INTERRUPTS]; int i = 0; /* Walk the children of this node, scanning for interrupts properties */ while (child != NULL && i < MAX_INTERRUPTS) { if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "interrupts", &intrs, &nintr) == DDI_PROP_SUCCESS && intrs != NULL) { while (nintr > 0 && i < MAX_INTERRUPTS) { interrupts[i++] = intrs[--nintr]; } ddi_prop_free(intrs); } child = ddi_get_next_sibling(child); } if (ddi_prop_update_int_array(DDI_DEV_T_NONE, dip, "interrupts", interrupts, i) != DDI_PROP_SUCCESS) { return (DDI_FAILURE); } /* * Oh, the humanity. On the platforms on which we need to * synthesize an interrupts property, we ALSO need to update the * device_type property, and set it to "serial" in order for the * correct interrupt PIL to be chosen by the framework. */ if (ddi_prop_update_string(DDI_DEV_T_NONE, dip, "device_type", "serial") != DDI_PROP_SUCCESS) { return (DDI_FAILURE); } return (DDI_SUCCESS); } static boolean_t i8042_is_polling_platform(void) { /* * Returns true if this platform is one of the platforms * that has interrupt issues with the PS/2 keyboard/mouse. */ if (PLATFORM_MATCH("SUNW,UltraAX-")) return (B_TRUE); else return (B_FALSE); } #endif