diff options
Diffstat (limited to 'sys/dev/atkbdc')
-rw-r--r-- | sys/dev/atkbdc/atkbd.c | 1409 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbd_atkbdc.c | 178 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbd_isa.c | 178 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdc.c | 1039 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdc_isa.c | 369 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdc_subr.c | 369 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdcreg.h | 263 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdreg.h | 47 | ||||
-rw-r--r-- | sys/dev/atkbdc/psm.c | 2972 |
9 files changed, 6824 insertions, 0 deletions
diff --git a/sys/dev/atkbdc/atkbd.c b/sys/dev/atkbdc/atkbd.c new file mode 100644 index 0000000..dcc6ae8 --- /dev/null +++ b/sys/dev/atkbdc/atkbd.c @@ -0,0 +1,1409 @@ +/*- + * Copyright (c) 1999 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer as + * the first lines of this file unmodified. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_kbd.h" +#include "opt_atkbd.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/proc.h> +#include <sys/malloc.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#ifdef __i386__ +#include <machine/md_var.h> +#include <machine/psl.h> +#include <machine/vm86.h> +#include <machine/pc/bios.h> + +#include <vm/vm.h> +#include <vm/pmap.h> +#endif /* __i386__ */ + +#include <sys/kbio.h> +#include <dev/kbd/kbdreg.h> +#include <dev/kbd/atkbdreg.h> +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> + +static timeout_t atkbd_timeout; + +int +atkbd_probe_unit(int unit, int ctlr, int irq, int flags) +{ + keyboard_switch_t *sw; + int args[2]; + int error; + + sw = kbd_get_switch(ATKBD_DRIVER_NAME); + if (sw == NULL) + return ENXIO; + + args[0] = ctlr; + args[1] = irq; + error = (*sw->probe)(unit, args, flags); + if (error) + return error; + return 0; +} + +int +atkbd_attach_unit(int unit, keyboard_t **kbd, int ctlr, int irq, int flags) +{ + keyboard_switch_t *sw; + int args[2]; + int error; + + sw = kbd_get_switch(ATKBD_DRIVER_NAME); + if (sw == NULL) + return ENXIO; + + /* reset, initialize and enable the device */ + args[0] = ctlr; + args[1] = irq; + *kbd = NULL; + error = (*sw->probe)(unit, args, flags); + if (error) + return error; + error = (*sw->init)(unit, kbd, args, flags); + if (error) + return error; + (*sw->enable)(*kbd); + +#ifdef KBD_INSTALL_CDEV + /* attach a virtual keyboard cdev */ + error = kbd_attach(*kbd); + if (error) + return error; +#endif + + /* + * This is a kludge to compensate for lost keyboard interrupts. + * A similar code used to be in syscons. See below. XXX + */ + atkbd_timeout(*kbd); + + if (bootverbose) + (*sw->diag)(*kbd, bootverbose); + return 0; +} + +static void +atkbd_timeout(void *arg) +{ + keyboard_t *kbd; + int s; + + /* + * The original text of the following comments are extracted + * from syscons.c (1.287) + * + * With release 2.1 of the Xaccel server, the keyboard is left + * hanging pretty often. Apparently an interrupt from the + * keyboard is lost, and I don't know why (yet). + * This ugly hack calls the low-level interrupt routine if input + * is ready for the keyboard and conveniently hides the problem. XXX + * + * Try removing anything stuck in the keyboard controller; whether + * it's a keyboard scan code or mouse data. The low-level + * interrupt routine doesn't read the mouse data directly, + * but the keyboard controller driver will, as a side effect. + */ + /* + * And here is bde's original comment about this: + * + * This is necessary to handle edge triggered interrupts - if we + * returned when our IRQ is high due to unserviced input, then there + * would be no more keyboard IRQs until the keyboard is reset by + * external powers. + * + * The keyboard apparently unwedges the irq in most cases. + */ + s = spltty(); + kbd = (keyboard_t *)arg; + if ((*kbdsw[kbd->kb_index]->lock)(kbd, TRUE)) { + /* + * We have seen the lock flag is not set. Let's reset + * the flag early, otherwise the LED update routine fails + * which may want the lock during the interrupt routine. + */ + (*kbdsw[kbd->kb_index]->lock)(kbd, FALSE); + if ((*kbdsw[kbd->kb_index]->check_char)(kbd)) + (*kbdsw[kbd->kb_index]->intr)(kbd, NULL); + } + splx(s); + timeout(atkbd_timeout, arg, hz/10); +} + +/* LOW-LEVEL */ + +#include <machine/limits.h> + +#define ATKBD_DEFAULT 0 + +typedef struct atkbd_state { + KBDC kbdc; /* keyboard controller */ + /* XXX: don't move this field; pcvt + * expects `kbdc' to be the first + * field in this structure. */ + int ks_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */ + int ks_flags; /* flags */ +#define COMPOSE (1 << 0) + int ks_polling; + int ks_state; /* shift/lock key state */ + int ks_accents; /* accent key index (> 0) */ + u_int ks_composed_char; /* composed char code (> 0) */ + u_char ks_prefix; /* AT scan code prefix */ +} atkbd_state_t; + +/* keyboard driver declaration */ +static int atkbd_configure(int flags); +static kbd_probe_t atkbd_probe; +static kbd_init_t atkbd_init; +static kbd_term_t atkbd_term; +static kbd_intr_t atkbd_intr; +static kbd_test_if_t atkbd_test_if; +static kbd_enable_t atkbd_enable; +static kbd_disable_t atkbd_disable; +static kbd_read_t atkbd_read; +static kbd_check_t atkbd_check; +static kbd_read_char_t atkbd_read_char; +static kbd_check_char_t atkbd_check_char; +static kbd_ioctl_t atkbd_ioctl; +static kbd_lock_t atkbd_lock; +static kbd_clear_state_t atkbd_clear_state; +static kbd_get_state_t atkbd_get_state; +static kbd_set_state_t atkbd_set_state; +static kbd_poll_mode_t atkbd_poll; + +keyboard_switch_t atkbdsw = { + atkbd_probe, + atkbd_init, + atkbd_term, + atkbd_intr, + atkbd_test_if, + atkbd_enable, + atkbd_disable, + atkbd_read, + atkbd_check, + atkbd_read_char, + atkbd_check_char, + atkbd_ioctl, + atkbd_lock, + atkbd_clear_state, + atkbd_get_state, + atkbd_set_state, + genkbd_get_fkeystr, + atkbd_poll, + genkbd_diag, +}; + +KEYBOARD_DRIVER(atkbd, atkbdsw, atkbd_configure); + +/* local functions */ +static int get_typematic(keyboard_t *kbd); +static int setup_kbd_port(KBDC kbdc, int port, int intr); +static int get_kbd_echo(KBDC kbdc); +static int probe_keyboard(KBDC kbdc, int flags); +static int init_keyboard(KBDC kbdc, int *type, int flags); +static int write_kbd(KBDC kbdc, int command, int data); +static int get_kbd_id(KBDC kbdc); +static int typematic(int delay, int rate); +static int typematic_delay(int delay); +static int typematic_rate(int rate); + +/* local variables */ + +/* the initial key map, accent map and fkey strings */ +#ifdef ATKBD_DFLT_KEYMAP +#define KBD_DFLT_KEYMAP +#include "atkbdmap.h" +#endif +#include <dev/kbd/kbdtables.h> + +/* structures for the default keyboard */ +static keyboard_t default_kbd; +static atkbd_state_t default_kbd_state; +static keymap_t default_keymap; +static accentmap_t default_accentmap; +static fkeytab_t default_fkeytab[NUM_FKEYS]; + +/* + * The back door to the keyboard driver! + * This function is called by the console driver, via the kbdio module, + * to tickle keyboard drivers when the low-level console is being initialized. + * Almost nothing in the kernel has been initialied yet. Try to probe + * keyboards if possible. + * NOTE: because of the way the low-level console is initialized, this routine + * may be called more than once!! + */ +static int +atkbd_configure(int flags) +{ + keyboard_t *kbd; + int arg[2]; + int i; + + /* probe the keyboard controller */ + atkbdc_configure(); + + /* if the driver is disabled, unregister the keyboard if any */ + if ((resource_int_value("atkbd", ATKBD_DEFAULT, "disabled", &i) == 0) + && i != 0) { + i = kbd_find_keyboard(ATKBD_DRIVER_NAME, ATKBD_DEFAULT); + if (i >= 0) { + kbd = kbd_get_keyboard(i); + kbd_unregister(kbd); + kbd->kb_flags &= ~KB_REGISTERED; + } + return 0; + } + + /* XXX: a kludge to obtain the device configuration flags */ + if (resource_int_value("atkbd", ATKBD_DEFAULT, "flags", &i) == 0) + flags |= i; + + /* probe the default keyboard */ + arg[0] = -1; + arg[1] = -1; + kbd = NULL; + if (atkbd_probe(ATKBD_DEFAULT, arg, flags)) + return 0; + if (atkbd_init(ATKBD_DEFAULT, &kbd, arg, flags)) + return 0; + + /* return the number of found keyboards */ + return 1; +} + +/* low-level functions */ + +/* detect a keyboard */ +static int +atkbd_probe(int unit, void *arg, int flags) +{ + KBDC kbdc; + int *data = (int *)arg; /* data[0]: controller, data[1]: irq */ + + /* XXX */ + if (unit == ATKBD_DEFAULT) { + if (KBD_IS_PROBED(&default_kbd)) + return 0; + } + + kbdc = atkbdc_open(data[0]); + if (kbdc == NULL) + return ENXIO; + if (probe_keyboard(kbdc, flags)) { + if (flags & KB_CONF_FAIL_IF_NO_KBD) + return ENXIO; + } + return 0; +} + +/* reset and initialize the device */ +static int +atkbd_init(int unit, keyboard_t **kbdp, void *arg, int flags) +{ + keyboard_t *kbd; + atkbd_state_t *state; + keymap_t *keymap; + accentmap_t *accmap; + fkeytab_t *fkeymap; + int fkeymap_size; + int delay[2]; + int *data = (int *)arg; /* data[0]: controller, data[1]: irq */ + + /* XXX */ + if (unit == ATKBD_DEFAULT) { + *kbdp = kbd = &default_kbd; + if (KBD_IS_INITIALIZED(kbd) && KBD_IS_CONFIGURED(kbd)) + return 0; + state = &default_kbd_state; + keymap = &default_keymap; + accmap = &default_accentmap; + fkeymap = default_fkeytab; + fkeymap_size = + sizeof(default_fkeytab)/sizeof(default_fkeytab[0]); + } else if (*kbdp == NULL) { + *kbdp = kbd = malloc(sizeof(*kbd), M_DEVBUF, M_NOWAIT | M_ZERO); + state = malloc(sizeof(*state), M_DEVBUF, M_NOWAIT | M_ZERO); + keymap = malloc(sizeof(key_map), M_DEVBUF, M_NOWAIT); + accmap = malloc(sizeof(accent_map), M_DEVBUF, M_NOWAIT); + fkeymap = malloc(sizeof(fkey_tab), M_DEVBUF, M_NOWAIT); + fkeymap_size = sizeof(fkey_tab)/sizeof(fkey_tab[0]); + if ((kbd == NULL) || (state == NULL) || (keymap == NULL) + || (accmap == NULL) || (fkeymap == NULL)) { + if (state != NULL) + free(state, M_DEVBUF); + if (keymap != NULL) + free(keymap, M_DEVBUF); + if (accmap != NULL) + free(accmap, M_DEVBUF); + if (fkeymap != NULL) + free(fkeymap, M_DEVBUF); + if (kbd != NULL) + free(kbd, M_DEVBUF); + return ENOMEM; + } + } else if (KBD_IS_INITIALIZED(*kbdp) && KBD_IS_CONFIGURED(*kbdp)) { + return 0; + } else { + kbd = *kbdp; + state = (atkbd_state_t *)kbd->kb_data; + bzero(state, sizeof(*state)); + keymap = kbd->kb_keymap; + accmap = kbd->kb_accentmap; + fkeymap = kbd->kb_fkeytab; + fkeymap_size = kbd->kb_fkeytab_size; + } + + if (!KBD_IS_PROBED(kbd)) { + state->kbdc = atkbdc_open(data[0]); + if (state->kbdc == NULL) + return ENXIO; + kbd_init_struct(kbd, ATKBD_DRIVER_NAME, KB_OTHER, unit, flags, + 0, 0); + bcopy(&key_map, keymap, sizeof(key_map)); + bcopy(&accent_map, accmap, sizeof(accent_map)); + bcopy(fkey_tab, fkeymap, + imin(fkeymap_size*sizeof(fkeymap[0]), sizeof(fkey_tab))); + kbd_set_maps(kbd, keymap, accmap, fkeymap, fkeymap_size); + kbd->kb_data = (void *)state; + + if (probe_keyboard(state->kbdc, flags)) { /* shouldn't happen */ + if (flags & KB_CONF_FAIL_IF_NO_KBD) + return ENXIO; + } else { + KBD_FOUND_DEVICE(kbd); + } + atkbd_clear_state(kbd); + state->ks_mode = K_XLATE; + /* + * FIXME: set the initial value for lock keys in ks_state + * according to the BIOS data? + */ + KBD_PROBE_DONE(kbd); + } + if (!KBD_IS_INITIALIZED(kbd) && !(flags & KB_CONF_PROBE_ONLY)) { + kbd->kb_config = flags & ~KB_CONF_PROBE_ONLY; + if (KBD_HAS_DEVICE(kbd) + && init_keyboard(state->kbdc, &kbd->kb_type, kbd->kb_config) + && (kbd->kb_config & KB_CONF_FAIL_IF_NO_KBD)) + return ENXIO; + atkbd_ioctl(kbd, KDSETLED, (caddr_t)&state->ks_state); + get_typematic(kbd); + delay[0] = kbd->kb_delay1; + delay[1] = kbd->kb_delay2; + atkbd_ioctl(kbd, KDSETREPEAT, (caddr_t)delay); + KBD_INIT_DONE(kbd); + } + if (!KBD_IS_CONFIGURED(kbd)) { + if (kbd_register(kbd) < 0) + return ENXIO; + KBD_CONFIG_DONE(kbd); + } + + return 0; +} + +/* finish using this keyboard */ +static int +atkbd_term(keyboard_t *kbd) +{ + kbd_unregister(kbd); + return 0; +} + +/* keyboard interrupt routine */ +static int +atkbd_intr(keyboard_t *kbd, void *arg) +{ + atkbd_state_t *state; + int delay[2]; + int c; + + if (KBD_IS_ACTIVE(kbd) && KBD_IS_BUSY(kbd)) { + /* let the callback function to process the input */ + (*kbd->kb_callback.kc_func)(kbd, KBDIO_KEYINPUT, + kbd->kb_callback.kc_arg); + } else { + /* read and discard the input; no one is waiting for input */ + do { + c = atkbd_read_char(kbd, FALSE); + } while (c != NOKEY); + + if (!KBD_HAS_DEVICE(kbd)) { + /* + * The keyboard was not detected before; + * it must have been reconnected! + */ + state = (atkbd_state_t *)kbd->kb_data; + init_keyboard(state->kbdc, &kbd->kb_type, + kbd->kb_config); + atkbd_ioctl(kbd, KDSETLED, (caddr_t)&state->ks_state); + get_typematic(kbd); + delay[0] = kbd->kb_delay1; + delay[1] = kbd->kb_delay2; + atkbd_ioctl(kbd, KDSETREPEAT, (caddr_t)delay); + KBD_FOUND_DEVICE(kbd); + } + } + return 0; +} + +/* test the interface to the device */ +static int +atkbd_test_if(keyboard_t *kbd) +{ + int error; + int s; + + error = 0; + empty_both_buffers(((atkbd_state_t *)kbd->kb_data)->kbdc, 10); + s = spltty(); + if (!test_controller(((atkbd_state_t *)kbd->kb_data)->kbdc)) + error = EIO; + else if (test_kbd_port(((atkbd_state_t *)kbd->kb_data)->kbdc) != 0) + error = EIO; + splx(s); + + return error; +} + +/* + * Enable the access to the device; until this function is called, + * the client cannot read from the keyboard. + */ +static int +atkbd_enable(keyboard_t *kbd) +{ + int s; + + s = spltty(); + KBD_ACTIVATE(kbd); + splx(s); + return 0; +} + +/* disallow the access to the device */ +static int +atkbd_disable(keyboard_t *kbd) +{ + int s; + + s = spltty(); + KBD_DEACTIVATE(kbd); + splx(s); + return 0; +} + +/* read one byte from the keyboard if it's allowed */ +static int +atkbd_read(keyboard_t *kbd, int wait) +{ + int c; + + if (wait) + c = read_kbd_data(((atkbd_state_t *)kbd->kb_data)->kbdc); + else + c = read_kbd_data_no_wait(((atkbd_state_t *)kbd->kb_data)->kbdc); + if (c != -1) + ++kbd->kb_count; + return (KBD_IS_ACTIVE(kbd) ? c : -1); +} + +/* check if data is waiting */ +static int +atkbd_check(keyboard_t *kbd) +{ + if (!KBD_IS_ACTIVE(kbd)) + return FALSE; + return kbdc_data_ready(((atkbd_state_t *)kbd->kb_data)->kbdc); +} + +/* read char from the keyboard */ +static u_int +atkbd_read_char(keyboard_t *kbd, int wait) +{ + atkbd_state_t *state; + u_int action; + int scancode; + int keycode; + + state = (atkbd_state_t *)kbd->kb_data; +next_code: + /* do we have a composed char to return? */ + if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char > 0)) { + action = state->ks_composed_char; + state->ks_composed_char = 0; + if (action > UCHAR_MAX) + return ERRKEY; + return action; + } + + /* see if there is something in the keyboard port */ + if (wait) { + do { + scancode = read_kbd_data(state->kbdc); + } while (scancode == -1); + } else { + scancode = read_kbd_data_no_wait(state->kbdc); + if (scancode == -1) + return NOKEY; + } + ++kbd->kb_count; + +#if KBDIO_DEBUG >= 10 + printf("atkbd_read_char(): scancode:0x%x\n", scancode); +#endif + + /* return the byte as is for the K_RAW mode */ + if (state->ks_mode == K_RAW) + return scancode; + + /* translate the scan code into a keycode */ + keycode = scancode & 0x7F; + switch (state->ks_prefix) { + case 0x00: /* normal scancode */ + switch(scancode) { + case 0xB8: /* left alt (compose key) released */ + if (state->ks_flags & COMPOSE) { + state->ks_flags &= ~COMPOSE; + if (state->ks_composed_char > UCHAR_MAX) + state->ks_composed_char = 0; + } + break; + case 0x38: /* left alt (compose key) pressed */ + if (!(state->ks_flags & COMPOSE)) { + state->ks_flags |= COMPOSE; + state->ks_composed_char = 0; + } + break; + case 0xE0: + case 0xE1: + state->ks_prefix = scancode; + goto next_code; + } + break; + case 0xE0: /* 0xE0 prefix */ + state->ks_prefix = 0; + switch (keycode) { + case 0x1C: /* right enter key */ + keycode = 0x59; + break; + case 0x1D: /* right ctrl key */ + keycode = 0x5A; + break; + case 0x35: /* keypad divide key */ + keycode = 0x5B; + break; + case 0x37: /* print scrn key */ + keycode = 0x5C; + break; + case 0x38: /* right alt key (alt gr) */ + keycode = 0x5D; + break; + case 0x46: /* ctrl-pause/break on AT 101 (see below) */ + keycode = 0x68; + break; + case 0x47: /* grey home key */ + keycode = 0x5E; + break; + case 0x48: /* grey up arrow key */ + keycode = 0x5F; + break; + case 0x49: /* grey page up key */ + keycode = 0x60; + break; + case 0x4B: /* grey left arrow key */ + keycode = 0x61; + break; + case 0x4D: /* grey right arrow key */ + keycode = 0x62; + break; + case 0x4F: /* grey end key */ + keycode = 0x63; + break; + case 0x50: /* grey down arrow key */ + keycode = 0x64; + break; + case 0x51: /* grey page down key */ + keycode = 0x65; + break; + case 0x52: /* grey insert key */ + keycode = 0x66; + break; + case 0x53: /* grey delete key */ + keycode = 0x67; + break; + /* the following 3 are only used on the MS "Natural" keyboard */ + case 0x5b: /* left Window key */ + keycode = 0x69; + break; + case 0x5c: /* right Window key */ + keycode = 0x6a; + break; + case 0x5d: /* menu key */ + keycode = 0x6b; + break; + default: /* ignore everything else */ + goto next_code; + } + break; + case 0xE1: /* 0xE1 prefix */ + /* + * The pause/break key on the 101 keyboard produces: + * E1-1D-45 E1-9D-C5 + * Ctrl-pause/break produces: + * E0-46 E0-C6 (See above.) + */ + state->ks_prefix = 0; + if (keycode == 0x1D) + state->ks_prefix = 0x1D; + goto next_code; + /* NOT REACHED */ + case 0x1D: /* pause / break */ + state->ks_prefix = 0; + if (keycode != 0x45) + goto next_code; + keycode = 0x68; + break; + } + + if (kbd->kb_type == KB_84) { + switch (keycode) { + case 0x37: /* *(numpad)/print screen */ + if (state->ks_flags & SHIFTS) + keycode = 0x5c; /* print screen */ + break; + case 0x45: /* num lock/pause */ + if (state->ks_flags & CTLS) + keycode = 0x68; /* pause */ + break; + case 0x46: /* scroll lock/break */ + if (state->ks_flags & CTLS) + keycode = 0x6c; /* break */ + break; + } + } else if (kbd->kb_type == KB_101) { + switch (keycode) { + case 0x5c: /* print screen */ + if (state->ks_flags & ALTS) + keycode = 0x54; /* sysrq */ + break; + case 0x68: /* pause/break */ + if (state->ks_flags & CTLS) + keycode = 0x6c; /* break */ + break; + } + } + + /* return the key code in the K_CODE mode */ + if (state->ks_mode == K_CODE) + return (keycode | (scancode & 0x80)); + + /* compose a character code */ + if (state->ks_flags & COMPOSE) { + switch (keycode | (scancode & 0x80)) { + /* key pressed, process it */ + case 0x47: case 0x48: case 0x49: /* keypad 7,8,9 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x40; + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + case 0x4B: case 0x4C: case 0x4D: /* keypad 4,5,6 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x47; + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + case 0x4F: case 0x50: case 0x51: /* keypad 1,2,3 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x4E; + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + case 0x52: /* keypad 0 */ + state->ks_composed_char *= 10; + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + + /* key released, no interest here */ + case 0xC7: case 0xC8: case 0xC9: /* keypad 7,8,9 */ + case 0xCB: case 0xCC: case 0xCD: /* keypad 4,5,6 */ + case 0xCF: case 0xD0: case 0xD1: /* keypad 1,2,3 */ + case 0xD2: /* keypad 0 */ + goto next_code; + + case 0x38: /* left alt key */ + break; + + default: + if (state->ks_composed_char > 0) { + state->ks_flags &= ~COMPOSE; + state->ks_composed_char = 0; + return ERRKEY; + } + break; + } + } + + /* keycode to key action */ + action = genkbd_keyaction(kbd, keycode, scancode & 0x80, + &state->ks_state, &state->ks_accents); + if (action == NOKEY) + goto next_code; + else + return action; +} + +/* check if char is waiting */ +static int +atkbd_check_char(keyboard_t *kbd) +{ + atkbd_state_t *state; + + if (!KBD_IS_ACTIVE(kbd)) + return FALSE; + state = (atkbd_state_t *)kbd->kb_data; + if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char > 0)) + return TRUE; + return kbdc_data_ready(state->kbdc); +} + +/* some useful control functions */ +static int +atkbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) +{ + /* trasnlate LED_XXX bits into the device specific bits */ + static u_char ledmap[8] = { + 0, 4, 2, 6, 1, 5, 3, 7, + }; + atkbd_state_t *state = kbd->kb_data; + int error; + int s; + int i; + + s = spltty(); + switch (cmd) { + + case KDGKBMODE: /* get keyboard mode */ + *(int *)arg = state->ks_mode; + break; + case KDSKBMODE: /* set keyboard mode */ + switch (*(int *)arg) { + case K_XLATE: + if (state->ks_mode != K_XLATE) { + /* make lock key state and LED state match */ + state->ks_state &= ~LOCK_MASK; + state->ks_state |= KBD_LED_VAL(kbd); + } + /* FALLTHROUGH */ + case K_RAW: + case K_CODE: + if (state->ks_mode != *(int *)arg) { + atkbd_clear_state(kbd); + state->ks_mode = *(int *)arg; + } + break; + default: + splx(s); + return EINVAL; + } + break; + + case KDGETLED: /* get keyboard LED */ + *(int *)arg = KBD_LED_VAL(kbd); + break; + case KDSETLED: /* set keyboard LED */ + /* NOTE: lock key state in ks_state won't be changed */ + if (*(int *)arg & ~LOCK_MASK) { + splx(s); + return EINVAL; + } + i = *(int *)arg; + /* replace CAPS LED with ALTGR LED for ALTGR keyboards */ + if (state->ks_mode == K_XLATE && + kbd->kb_keymap->n_keys > ALTGR_OFFSET) { + if (i & ALKED) + i |= CLKED; + else + i &= ~CLKED; + } + if (KBD_HAS_DEVICE(kbd)) { + error = write_kbd(state->kbdc, KBDC_SET_LEDS, + ledmap[i & LED_MASK]); + if (error) { + splx(s); + return error; + } + } + KBD_LED_VAL(kbd) = *(int *)arg; + break; + + case KDGKBSTATE: /* get lock key state */ + *(int *)arg = state->ks_state & LOCK_MASK; + break; + case KDSKBSTATE: /* set lock key state */ + if (*(int *)arg & ~LOCK_MASK) { + splx(s); + return EINVAL; + } + state->ks_state &= ~LOCK_MASK; + state->ks_state |= *(int *)arg; + splx(s); + /* set LEDs and quit */ + return atkbd_ioctl(kbd, KDSETLED, arg); + + case KDSETREPEAT: /* set keyboard repeat rate (new interface) */ + splx(s); + if (!KBD_HAS_DEVICE(kbd)) + return 0; + i = typematic(((int *)arg)[0], ((int *)arg)[1]); + error = write_kbd(state->kbdc, KBDC_SET_TYPEMATIC, i); + if (error == 0) { + kbd->kb_delay1 = typematic_delay(i); + kbd->kb_delay2 = typematic_rate(i); + } + return error; + + case KDSETRAD: /* set keyboard repeat rate (old interface) */ + splx(s); + if (!KBD_HAS_DEVICE(kbd)) + return 0; + error = write_kbd(state->kbdc, KBDC_SET_TYPEMATIC, *(int *)arg); + if (error == 0) { + kbd->kb_delay1 = typematic_delay(*(int *)arg); + kbd->kb_delay2 = typematic_rate(*(int *)arg); + } + return error; + + case PIO_KEYMAP: /* set keyboard translation table */ + case PIO_KEYMAPENT: /* set keyboard translation table entry */ + case PIO_DEADKEYMAP: /* set accent key translation table */ + state->ks_accents = 0; + /* FALLTHROUGH */ + default: + splx(s); + return genkbd_commonioctl(kbd, cmd, arg); + } + + splx(s); + return 0; +} + +/* lock the access to the keyboard */ +static int +atkbd_lock(keyboard_t *kbd, int lock) +{ + return kbdc_lock(((atkbd_state_t *)kbd->kb_data)->kbdc, lock); +} + +/* clear the internal state of the keyboard */ +static void +atkbd_clear_state(keyboard_t *kbd) +{ + atkbd_state_t *state; + + state = (atkbd_state_t *)kbd->kb_data; + state->ks_flags = 0; + state->ks_polling = 0; + state->ks_state &= LOCK_MASK; /* preserve locking key state */ + state->ks_accents = 0; + state->ks_composed_char = 0; +#if 0 + state->ks_prefix = 0; /* XXX */ +#endif +} + +/* save the internal state */ +static int +atkbd_get_state(keyboard_t *kbd, void *buf, size_t len) +{ + if (len == 0) + return sizeof(atkbd_state_t); + if (len < sizeof(atkbd_state_t)) + return -1; + bcopy(kbd->kb_data, buf, sizeof(atkbd_state_t)); + return 0; +} + +/* set the internal state */ +static int +atkbd_set_state(keyboard_t *kbd, void *buf, size_t len) +{ + if (len < sizeof(atkbd_state_t)) + return ENOMEM; + if (((atkbd_state_t *)kbd->kb_data)->kbdc + != ((atkbd_state_t *)buf)->kbdc) + return ENOMEM; + bcopy(buf, kbd->kb_data, sizeof(atkbd_state_t)); + return 0; +} + +static int +atkbd_poll(keyboard_t *kbd, int on) +{ + atkbd_state_t *state; + int s; + + state = (atkbd_state_t *)kbd->kb_data; + s = spltty(); + if (on) + ++state->ks_polling; + else + --state->ks_polling; + splx(s); + return 0; +} + +/* local functions */ + +static int +get_typematic(keyboard_t *kbd) +{ +#ifdef __i386__ + /* + * Only some systems allow us to retrieve the keyboard repeat + * rate previously set via the BIOS... + */ + struct vm86frame vmf; + u_int32_t p; + + bzero(&vmf, sizeof(vmf)); + vmf.vmf_ax = 0xc000; + vm86_intcall(0x15, &vmf); + if ((vmf.vmf_eflags & PSL_C) || vmf.vmf_ah) + return ENODEV; + p = BIOS_PADDRTOVADDR(((u_int32_t)vmf.vmf_es << 4) + vmf.vmf_bx); + if ((readb(p + 6) & 0x40) == 0) /* int 16, function 0x09 supported? */ + return ENODEV; + vmf.vmf_ax = 0x0900; + vm86_intcall(0x16, &vmf); + if ((vmf.vmf_al & 0x08) == 0) /* int 16, function 0x0306 supported? */ + return ENODEV; + vmf.vmf_ax = 0x0306; + vm86_intcall(0x16, &vmf); + kbd->kb_delay1 = typematic_delay(vmf.vmf_bh << 5); + kbd->kb_delay2 = typematic_rate(vmf.vmf_bl); + return 0; +#else + return ENODEV; +#endif /* __i386__ */ +} + +static int +setup_kbd_port(KBDC kbdc, int port, int intr) +{ + if (!set_controller_command_byte(kbdc, + KBD_KBD_CONTROL_BITS, + ((port) ? KBD_ENABLE_KBD_PORT : KBD_DISABLE_KBD_PORT) + | ((intr) ? KBD_ENABLE_KBD_INT : KBD_DISABLE_KBD_INT))) + return 1; + return 0; +} + +static int +get_kbd_echo(KBDC kbdc) +{ + /* enable the keyboard port, but disable the keyboard intr. */ + if (setup_kbd_port(kbdc, TRUE, FALSE)) + /* CONTROLLER ERROR: there is very little we can do... */ + return ENXIO; + + /* see if something is present */ + write_kbd_command(kbdc, KBDC_ECHO); + if (read_kbd_data(kbdc) != KBD_ECHO) { + empty_both_buffers(kbdc, 10); + test_controller(kbdc); + test_kbd_port(kbdc); + return ENXIO; + } + + /* enable the keyboard port and intr. */ + if (setup_kbd_port(kbdc, TRUE, TRUE)) { + /* + * CONTROLLER ERROR + * This is serious; the keyboard intr is left disabled! + */ + return ENXIO; + } + + return 0; +} + +static int +probe_keyboard(KBDC kbdc, int flags) +{ + /* + * Don't try to print anything in this function. The low-level + * console may not have been initialized yet... + */ + int err; + int c; + int m; + + if (!kbdc_lock(kbdc, TRUE)) { + /* driver error? */ + return ENXIO; + } + + /* temporarily block data transmission from the keyboard */ + write_controller_command(kbdc, KBDC_DISABLE_KBD_PORT); + + /* flush any noise in the buffer */ + empty_both_buffers(kbdc, 100); + + /* save the current keyboard controller command byte */ + m = kbdc_get_device_mask(kbdc) & ~KBD_KBD_CONTROL_BITS; + c = get_controller_command_byte(kbdc); + if (c == -1) { + /* CONTROLLER ERROR */ + kbdc_set_device_mask(kbdc, m); + kbdc_lock(kbdc, FALSE); + return ENXIO; + } + + /* + * The keyboard may have been screwed up by the boot block. + * We may just be able to recover from error by testing the controller + * and the keyboard port. The controller command byte needs to be + * saved before this recovery operation, as some controllers seem + * to set the command byte to particular values. + */ + test_controller(kbdc); + test_kbd_port(kbdc); + + err = get_kbd_echo(kbdc); + + /* + * Even if the keyboard doesn't seem to be present (err != 0), + * we shall enable the keyboard port and interrupt so that + * the driver will be operable when the keyboard is attached + * to the system later. It is NOT recommended to hot-plug + * the AT keyboard, but many people do so... + */ + kbdc_set_device_mask(kbdc, m | KBD_KBD_CONTROL_BITS); + setup_kbd_port(kbdc, TRUE, TRUE); +#if 0 + if (err == 0) { + kbdc_set_device_mask(kbdc, m | KBD_KBD_CONTROL_BITS); + } else { + /* try to restore the command byte as before */ + set_controller_command_byte(kbdc, 0xff, c); + kbdc_set_device_mask(kbdc, m); + } +#endif + + kbdc_lock(kbdc, FALSE); + return err; +} + +static int +init_keyboard(KBDC kbdc, int *type, int flags) +{ + int codeset; + int id; + int c; + + if (!kbdc_lock(kbdc, TRUE)) { + /* driver error? */ + return EIO; + } + + /* temporarily block data transmission from the keyboard */ + write_controller_command(kbdc, KBDC_DISABLE_KBD_PORT); + + /* save the current controller command byte */ + empty_both_buffers(kbdc, 200); + c = get_controller_command_byte(kbdc); + if (c == -1) { + /* CONTROLLER ERROR */ + kbdc_lock(kbdc, FALSE); + printf("atkbd: unable to get the current command byte value.\n"); + return EIO; + } + if (bootverbose) + printf("atkbd: the current kbd controller command byte %04x\n", + c); +#if 0 + /* override the keyboard lock switch */ + c |= KBD_OVERRIDE_KBD_LOCK; +#endif + + /* enable the keyboard port, but disable the keyboard intr. */ + if (setup_kbd_port(kbdc, TRUE, FALSE)) { + /* CONTROLLER ERROR: there is very little we can do... */ + printf("atkbd: unable to set the command byte.\n"); + kbdc_lock(kbdc, FALSE); + return EIO; + } + + /* + * Check if we have an XT keyboard before we attempt to reset it. + * The procedure assumes that the keyboard and the controller have + * been set up properly by BIOS and have not been messed up + * during the boot process. + */ + codeset = -1; + if (flags & KB_CONF_ALT_SCANCODESET) + /* the user says there is a XT keyboard */ + codeset = 1; +#ifdef KBD_DETECT_XT_KEYBOARD + else if ((c & KBD_TRANSLATION) == 0) { + /* SET_SCANCODE_SET is not always supported; ignore error */ + if (send_kbd_command_and_data(kbdc, KBDC_SET_SCANCODE_SET, 0) + == KBD_ACK) + codeset = read_kbd_data(kbdc); + } + if (bootverbose) + printf("atkbd: scancode set %d\n", codeset); +#endif /* KBD_DETECT_XT_KEYBOARD */ + + *type = KB_OTHER; + id = get_kbd_id(kbdc); + switch(id) { + case 0x41ab: /* 101/102/... Enhanced */ + case 0x83ab: /* ditto */ + case 0x54ab: /* SpaceSaver */ + case 0x84ab: /* ditto */ +#if 0 + case 0x90ab: /* 'G' */ + case 0x91ab: /* 'P' */ + case 0x92ab: /* 'A' */ +#endif + *type = KB_101; + break; + case -1: /* AT 84 keyboard doesn't return ID */ + *type = KB_84; + break; + default: + break; + } + if (bootverbose) + printf("atkbd: keyboard ID 0x%x (%d)\n", id, *type); + + /* reset keyboard hardware */ + if (!(flags & KB_CONF_NO_RESET) && !reset_kbd(kbdc)) { + /* + * KEYBOARD ERROR + * Keyboard reset may fail either because the keyboard + * doen't exist, or because the keyboard doesn't pass + * the self-test, or the keyboard controller on the + * motherboard and the keyboard somehow fail to shake hands. + * It is just possible, particularly in the last case, + * that the keyoard controller may be left in a hung state. + * test_controller() and test_kbd_port() appear to bring + * the keyboard controller back (I don't know why and how, + * though.) + */ + empty_both_buffers(kbdc, 10); + test_controller(kbdc); + test_kbd_port(kbdc); + /* + * We could disable the keyboard port and interrupt... but, + * the keyboard may still exist (see above). + */ + set_controller_command_byte(kbdc, 0xff, c); + kbdc_lock(kbdc, FALSE); + if (bootverbose) + printf("atkbd: failed to reset the keyboard.\n"); + return EIO; + } + + /* + * Allow us to set the XT_KEYBD flag so that keyboards + * such as those on the IBM ThinkPad laptop computers can be used + * with the standard console driver. + */ + if (codeset == 1) { + if (send_kbd_command_and_data(kbdc, + KBDC_SET_SCANCODE_SET, codeset) == KBD_ACK) { + /* XT kbd doesn't need scan code translation */ + c &= ~KBD_TRANSLATION; + } else { + /* + * KEYBOARD ERROR + * The XT kbd isn't usable unless the proper scan + * code set is selected. + */ + set_controller_command_byte(kbdc, 0xff, c); + kbdc_lock(kbdc, FALSE); + printf("atkbd: unable to set the XT keyboard mode.\n"); + return EIO; + } + } + +#ifdef __alpha__ + if (send_kbd_command_and_data( + kbdc, KBDC_SET_SCANCODE_SET, 2) != KBD_ACK) { + printf("atkbd: can't set translation.\n"); + + } + c |= KBD_TRANSLATION; +#endif + + /* enable the keyboard port and intr. */ + if (!set_controller_command_byte(kbdc, + KBD_KBD_CONTROL_BITS | KBD_TRANSLATION | KBD_OVERRIDE_KBD_LOCK, + (c & (KBD_TRANSLATION | KBD_OVERRIDE_KBD_LOCK)) + | KBD_ENABLE_KBD_PORT | KBD_ENABLE_KBD_INT)) { + /* + * CONTROLLER ERROR + * This is serious; we are left with the disabled + * keyboard intr. + */ + set_controller_command_byte(kbdc, 0xff, c); + kbdc_lock(kbdc, FALSE); + printf("atkbd: unable to enable the keyboard port and intr.\n"); + return EIO; + } + + kbdc_lock(kbdc, FALSE); + return 0; +} + +static int +write_kbd(KBDC kbdc, int command, int data) +{ + int s; + + /* prevent the timeout routine from polling the keyboard */ + if (!kbdc_lock(kbdc, TRUE)) + return EBUSY; + + /* disable the keyboard and mouse interrupt */ + s = spltty(); +#if 0 + c = get_controller_command_byte(kbdc); + if ((c == -1) + || !set_controller_command_byte(kbdc, + kbdc_get_device_mask(kbdc), + KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT + | KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + /* CONTROLLER ERROR */ + kbdc_lock(kbdc, FALSE); + splx(s); + return EIO; + } + /* + * Now that the keyboard controller is told not to generate + * the keyboard and mouse interrupts, call `splx()' to allow + * the other tty interrupts. The clock interrupt may also occur, + * but the timeout routine (`scrn_timer()') will be blocked + * by the lock flag set via `kbdc_lock()' + */ + splx(s); +#endif + + if (send_kbd_command_and_data(kbdc, command, data) != KBD_ACK) + send_kbd_command(kbdc, KBDC_ENABLE_KBD); + +#if 0 + /* restore the interrupts */ + if (!set_controller_command_byte(kbdc, + kbdc_get_device_mask(kbdc), + c & (KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS))) { + /* CONTROLLER ERROR */ + } +#else + splx(s); +#endif + kbdc_lock(kbdc, FALSE); + + return 0; +} + +static int +get_kbd_id(KBDC kbdc) +{ + int id1, id2; + + empty_both_buffers(kbdc, 10); + id1 = id2 = -1; + if (send_kbd_command(kbdc, KBDC_SEND_DEV_ID) != KBD_ACK) + return -1; + + DELAY(10000); /* 10 msec delay */ + id1 = read_kbd_data(kbdc); + if (id1 != -1) + id2 = read_kbd_data(kbdc); + + if ((id1 == -1) || (id2 == -1)) { + empty_both_buffers(kbdc, 10); + test_controller(kbdc); + test_kbd_port(kbdc); + return -1; + } + return ((id2 << 8) | id1); +} + +static int delays[] = { 250, 500, 750, 1000 }; +static int rates[] = { 34, 38, 42, 46, 50, 55, 59, 63, + 68, 76, 84, 92, 100, 110, 118, 126, + 136, 152, 168, 184, 200, 220, 236, 252, + 272, 304, 336, 368, 400, 440, 472, 504 }; + +static int +typematic_delay(int i) +{ + return delays[(i >> 5) & 3]; +} + +static int +typematic_rate(int i) +{ + return rates[i & 0x1f]; +} + +static int +typematic(int delay, int rate) +{ + int value; + int i; + + for (i = sizeof(delays)/sizeof(delays[0]) - 1; i > 0; --i) { + if (delay >= delays[i]) + break; + } + value = i << 5; + for (i = sizeof(rates)/sizeof(rates[0]) - 1; i > 0; --i) { + if (rate >= rates[i]) + break; + } + value |= i; + return value; +} diff --git a/sys/dev/atkbdc/atkbd_atkbdc.c b/sys/dev/atkbdc/atkbd_atkbdc.c new file mode 100644 index 0000000..0e505dad --- /dev/null +++ b/sys/dev/atkbdc/atkbd_atkbdc.c @@ -0,0 +1,178 @@ +/*- + * Copyright (c) 1999 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer as + * the first lines of this file unmodified. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_kbd.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <sys/kbio.h> +#include <dev/kbd/kbdreg.h> +#include <dev/kbd/atkbdreg.h> +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> +#include <isa/isavar.h> + +typedef struct { + struct resource *intr; + void *ih; +} atkbd_softc_t; + +devclass_t atkbd_devclass; + +static void atkbdidentify(driver_t *driver, device_t dev); +static int atkbdprobe(device_t dev); +static int atkbdattach(device_t dev); +static int atkbdresume(device_t dev); +static void atkbd_isa_intr(void *arg); + +static device_method_t atkbd_methods[] = { + DEVMETHOD(device_identify, atkbdidentify), + DEVMETHOD(device_probe, atkbdprobe), + DEVMETHOD(device_attach, atkbdattach), + DEVMETHOD(device_resume, atkbdresume), + { 0, 0 } +}; + +static driver_t atkbd_driver = { + ATKBD_DRIVER_NAME, + atkbd_methods, + sizeof(atkbd_softc_t), +}; + +static void +atkbdidentify(driver_t *driver, device_t parent) +{ + + /* always add at least one child */ + BUS_ADD_CHILD(parent, KBDC_RID_KBD, driver->name, device_get_unit(parent)); +} + +static int +atkbdprobe(device_t dev) +{ + struct resource *res; + u_long irq; + int flags; + int rid; + + device_set_desc(dev, "AT Keyboard"); + + /* obtain parameters */ + flags = device_get_flags(dev); + + /* see if IRQ is available */ + rid = KBDC_RID_KBD; + res = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE); + if (res == NULL) { + if (bootverbose) + device_printf(dev, "unable to allocate IRQ\n"); + return ENXIO; + } + irq = rman_get_start(res); + bus_release_resource(dev, SYS_RES_IRQ, rid, res); + + /* probe the device */ + return atkbd_probe_unit(device_get_unit(dev), + device_get_unit(device_get_parent(dev)), + irq, flags); +} + +static int +atkbdattach(device_t dev) +{ + atkbd_softc_t *sc; + keyboard_t *kbd; + u_long irq; + int flags; + int rid; + int error; + + sc = device_get_softc(dev); + + rid = KBDC_RID_KBD; + irq = bus_get_resource_start(dev, SYS_RES_IRQ, rid); + flags = device_get_flags(dev); + error = atkbd_attach_unit(device_get_unit(dev), &kbd, + device_get_unit(device_get_parent(dev)), + irq, flags); + if (error) + return error; + + /* declare our interrupt handler */ + sc->intr = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE); + if (sc->intr == NULL) + return ENXIO; + error = bus_setup_intr(dev, sc->intr, INTR_TYPE_TTY, atkbd_isa_intr, + kbd, &sc->ih); + if (error) + bus_release_resource(dev, SYS_RES_IRQ, rid, sc->intr); + + return error; +} + +static int +atkbdresume(device_t dev) +{ + atkbd_softc_t *sc; + keyboard_t *kbd; + int args[2]; + + sc = device_get_softc(dev); + kbd = kbd_get_keyboard(kbd_find_keyboard(ATKBD_DRIVER_NAME, + device_get_unit(dev))); + if (kbd) { + kbd->kb_flags &= ~KB_INITIALIZED; + args[0] = device_get_unit(device_get_parent(dev)); + args[1] = rman_get_start(sc->intr); + (*kbdsw[kbd->kb_index]->init)(device_get_unit(dev), &kbd, + args, device_get_flags(dev)); + (*kbdsw[kbd->kb_index]->clear_state)(kbd); + } + return 0; +} + +static void +atkbd_isa_intr(void *arg) +{ + keyboard_t *kbd; + + kbd = (keyboard_t *)arg; + (*kbdsw[kbd->kb_index]->intr)(kbd, NULL); +} + +DRIVER_MODULE(atkbd, atkbdc, atkbd_driver, atkbd_devclass, 0, 0); diff --git a/sys/dev/atkbdc/atkbd_isa.c b/sys/dev/atkbdc/atkbd_isa.c new file mode 100644 index 0000000..0e505dad --- /dev/null +++ b/sys/dev/atkbdc/atkbd_isa.c @@ -0,0 +1,178 @@ +/*- + * Copyright (c) 1999 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer as + * the first lines of this file unmodified. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_kbd.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <sys/kbio.h> +#include <dev/kbd/kbdreg.h> +#include <dev/kbd/atkbdreg.h> +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> +#include <isa/isavar.h> + +typedef struct { + struct resource *intr; + void *ih; +} atkbd_softc_t; + +devclass_t atkbd_devclass; + +static void atkbdidentify(driver_t *driver, device_t dev); +static int atkbdprobe(device_t dev); +static int atkbdattach(device_t dev); +static int atkbdresume(device_t dev); +static void atkbd_isa_intr(void *arg); + +static device_method_t atkbd_methods[] = { + DEVMETHOD(device_identify, atkbdidentify), + DEVMETHOD(device_probe, atkbdprobe), + DEVMETHOD(device_attach, atkbdattach), + DEVMETHOD(device_resume, atkbdresume), + { 0, 0 } +}; + +static driver_t atkbd_driver = { + ATKBD_DRIVER_NAME, + atkbd_methods, + sizeof(atkbd_softc_t), +}; + +static void +atkbdidentify(driver_t *driver, device_t parent) +{ + + /* always add at least one child */ + BUS_ADD_CHILD(parent, KBDC_RID_KBD, driver->name, device_get_unit(parent)); +} + +static int +atkbdprobe(device_t dev) +{ + struct resource *res; + u_long irq; + int flags; + int rid; + + device_set_desc(dev, "AT Keyboard"); + + /* obtain parameters */ + flags = device_get_flags(dev); + + /* see if IRQ is available */ + rid = KBDC_RID_KBD; + res = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE); + if (res == NULL) { + if (bootverbose) + device_printf(dev, "unable to allocate IRQ\n"); + return ENXIO; + } + irq = rman_get_start(res); + bus_release_resource(dev, SYS_RES_IRQ, rid, res); + + /* probe the device */ + return atkbd_probe_unit(device_get_unit(dev), + device_get_unit(device_get_parent(dev)), + irq, flags); +} + +static int +atkbdattach(device_t dev) +{ + atkbd_softc_t *sc; + keyboard_t *kbd; + u_long irq; + int flags; + int rid; + int error; + + sc = device_get_softc(dev); + + rid = KBDC_RID_KBD; + irq = bus_get_resource_start(dev, SYS_RES_IRQ, rid); + flags = device_get_flags(dev); + error = atkbd_attach_unit(device_get_unit(dev), &kbd, + device_get_unit(device_get_parent(dev)), + irq, flags); + if (error) + return error; + + /* declare our interrupt handler */ + sc->intr = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE); + if (sc->intr == NULL) + return ENXIO; + error = bus_setup_intr(dev, sc->intr, INTR_TYPE_TTY, atkbd_isa_intr, + kbd, &sc->ih); + if (error) + bus_release_resource(dev, SYS_RES_IRQ, rid, sc->intr); + + return error; +} + +static int +atkbdresume(device_t dev) +{ + atkbd_softc_t *sc; + keyboard_t *kbd; + int args[2]; + + sc = device_get_softc(dev); + kbd = kbd_get_keyboard(kbd_find_keyboard(ATKBD_DRIVER_NAME, + device_get_unit(dev))); + if (kbd) { + kbd->kb_flags &= ~KB_INITIALIZED; + args[0] = device_get_unit(device_get_parent(dev)); + args[1] = rman_get_start(sc->intr); + (*kbdsw[kbd->kb_index]->init)(device_get_unit(dev), &kbd, + args, device_get_flags(dev)); + (*kbdsw[kbd->kb_index]->clear_state)(kbd); + } + return 0; +} + +static void +atkbd_isa_intr(void *arg) +{ + keyboard_t *kbd; + + kbd = (keyboard_t *)arg; + (*kbdsw[kbd->kb_index]->intr)(kbd, NULL); +} + +DRIVER_MODULE(atkbd, atkbdc, atkbd_driver, atkbd_devclass, 0, 0); diff --git a/sys/dev/atkbdc/atkbdc.c b/sys/dev/atkbdc/atkbdc.c new file mode 100644 index 0000000..8593368 --- /dev/null +++ b/sys/dev/atkbdc/atkbdc.c @@ -0,0 +1,1039 @@ +/*- + * Copyright (c) 1996-1999 + * Kazutaka YOKOTA (yokota@zodiac.mech.utsunomiya-u.ac.jp) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * from kbdio.c,v 1.13 1998/09/25 11:55:46 yokota Exp + */ + +#include "opt_kbd.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/syslog.h> +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + + +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> + +/* constants */ + +#define MAXKBDC 1 /* XXX */ + +/* macros */ + +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#define kbdcp(p) ((atkbdc_softc_t *)(p)) +#define nextq(i) (((i) + 1) % KBDQ_BUFSIZE) +#define availq(q) ((q)->head != (q)->tail) +#if KBDIO_DEBUG >= 2 +#define emptyq(q) ((q)->tail = (q)->head = (q)->qcount = 0) +#else +#define emptyq(q) ((q)->tail = (q)->head = 0) +#endif + +#define read_data(k) (bus_space_read_1((k)->iot, (k)->ioh0, 0)) +#define read_status(k) (bus_space_read_1((k)->iot, (k)->ioh1, 0)) +#define write_data(k, d) \ + (bus_space_write_1((k)->iot, (k)->ioh0, 0, (d))) +#define write_command(k, d) \ + (bus_space_write_1((k)->iot, (k)->ioh1, 0, (d))) + +/* local variables */ + +/* + * We always need at least one copy of the kbdc_softc struct for the + * low-level console. As the low-level console accesses the keyboard + * controller before kbdc, and all other devices, is probed, we + * statically allocate one entry. XXX + */ +static atkbdc_softc_t default_kbdc; +static atkbdc_softc_t *atkbdc_softc[MAXKBDC] = { &default_kbdc }; + +static int verbose = KBDIO_DEBUG; + +/* function prototypes */ + +static int atkbdc_setup(atkbdc_softc_t *sc, bus_space_tag_t tag, + bus_space_handle_t h0, bus_space_handle_t h1); +static int addq(kqueue *q, int c); +static int removeq(kqueue *q); +static int wait_while_controller_busy(atkbdc_softc_t *kbdc); +static int wait_for_data(atkbdc_softc_t *kbdc); +static int wait_for_kbd_data(atkbdc_softc_t *kbdc); +static int wait_for_kbd_ack(atkbdc_softc_t *kbdc); +static int wait_for_aux_data(atkbdc_softc_t *kbdc); +static int wait_for_aux_ack(atkbdc_softc_t *kbdc); + +atkbdc_softc_t +*atkbdc_get_softc(int unit) +{ + atkbdc_softc_t *sc; + + if (unit >= sizeof(atkbdc_softc)/sizeof(atkbdc_softc[0])) + return NULL; + sc = atkbdc_softc[unit]; + if (sc == NULL) { + sc = atkbdc_softc[unit] + = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT | M_ZERO); + if (sc == NULL) + return NULL; + } + return sc; +} + +int +atkbdc_probe_unit(int unit, struct resource *port0, struct resource *port1) +{ + if (rman_get_start(port0) <= 0) + return ENXIO; + if (rman_get_start(port1) <= 0) + return ENXIO; + return 0; +} + +int +atkbdc_attach_unit(int unit, atkbdc_softc_t *sc, struct resource *port0, + struct resource *port1) +{ + return atkbdc_setup(sc, rman_get_bustag(port0), + rman_get_bushandle(port0), + rman_get_bushandle(port1)); +} + +/* the backdoor to the keyboard controller! XXX */ +int +atkbdc_configure(void) +{ + bus_space_tag_t tag; + bus_space_handle_t h0; + bus_space_handle_t h1; + int port0; + int port1; + + port0 = IO_KBD; + resource_int_value("atkbdc", 0, "port", &port0); + port1 = IO_KBD + KBD_STATUS_PORT; +#if 0 + resource_int_value("atkbdc", 0, "port", &port0); +#endif + + /* XXX: tag should be passed from the caller */ +#if defined(__i386__) + tag = I386_BUS_SPACE_IO; +#elif defined(__alpha__) + tag = busspace_isa_io; +#elif defined(__ia64__) + tag = IA64_BUS_SPACE_IO; +#else +#error "define tag!" +#endif + +#if notyet + bus_space_map(tag, port0, IO_KBDSIZE, 0, &h0); + bus_space_map(tag, port1, IO_KBDSIZE, 0, &h1); +#else + h0 = (bus_space_handle_t)port0; + h1 = (bus_space_handle_t)port1; +#endif + return atkbdc_setup(atkbdc_softc[0], tag, h0, h1); +} + +static int +atkbdc_setup(atkbdc_softc_t *sc, bus_space_tag_t tag, bus_space_handle_t h0, + bus_space_handle_t h1) +{ + if (sc->ioh0 == 0) { /* XXX */ + sc->command_byte = -1; + sc->command_mask = 0; + sc->lock = FALSE; + sc->kbd.head = sc->kbd.tail = 0; + sc->aux.head = sc->aux.tail = 0; +#if KBDIO_DEBUG >= 2 + sc->kbd.call_count = 0; + sc->kbd.qcount = sc->kbd.max_qcount = 0; + sc->aux.call_count = 0; + sc->aux.qcount = sc->aux.max_qcount = 0; +#endif + } + sc->iot = tag; + sc->ioh0 = h0; + sc->ioh1 = h1; + return 0; +} + +/* open a keyboard controller */ +KBDC +atkbdc_open(int unit) +{ + if (unit <= 0) + unit = 0; + if (unit >= MAXKBDC) + return NULL; + if ((atkbdc_softc[unit]->port0 != NULL) + || (atkbdc_softc[unit]->ioh0 != 0)) /* XXX */ + return (KBDC)atkbdc_softc[unit]; + return NULL; +} + +/* + * I/O access arbitration in `kbdio' + * + * The `kbdio' module uses a simplistic convention to arbitrate + * I/O access to the controller/keyboard/mouse. The convention requires + * close cooperation of the calling device driver. + * + * The device drivers which utilize the `kbdio' module are assumed to + * have the following set of routines. + * a. An interrupt handler (the bottom half of the driver). + * b. Timeout routines which may briefly poll the keyboard controller. + * c. Routines outside interrupt context (the top half of the driver). + * They should follow the rules below: + * 1. The interrupt handler may assume that it always has full access + * to the controller/keyboard/mouse. + * 2. The other routines must issue `spltty()' if they wish to + * prevent the interrupt handler from accessing + * the controller/keyboard/mouse. + * 3. The timeout routines and the top half routines of the device driver + * arbitrate I/O access by observing the lock flag in `kbdio'. + * The flag is manipulated via `kbdc_lock()'; when one wants to + * perform I/O, call `kbdc_lock(kbdc, TRUE)' and proceed only if + * the call returns with TRUE. Otherwise the caller must back off. + * Call `kbdc_lock(kbdc, FALSE)' when necessary I/O operaion + * is finished. This mechanism does not prevent the interrupt + * handler from being invoked at any time and carrying out I/O. + * Therefore, `spltty()' must be strategically placed in the device + * driver code. Also note that the timeout routine may interrupt + * `kbdc_lock()' called by the top half of the driver, but this + * interruption is OK so long as the timeout routine observes + * rule 4 below. + * 4. The interrupt and timeout routines should not extend I/O operation + * across more than one interrupt or timeout; they must complete any + * necessary I/O operation within one invocation of the routine. + * This means that if the timeout routine acquires the lock flag, + * it must reset the flag to FALSE before it returns. + */ + +/* set/reset polling lock */ +int +kbdc_lock(KBDC p, int lock) +{ + int prevlock; + + prevlock = kbdcp(p)->lock; + kbdcp(p)->lock = lock; + + return (prevlock != lock); +} + +/* check if any data is waiting to be processed */ +int +kbdc_data_ready(KBDC p) +{ + return (availq(&kbdcp(p)->kbd) || availq(&kbdcp(p)->aux) + || (read_status(kbdcp(p)) & KBDS_ANY_BUFFER_FULL)); +} + +/* queuing functions */ + +static int +addq(kqueue *q, int c) +{ + if (nextq(q->tail) != q->head) { + q->q[q->tail] = c; + q->tail = nextq(q->tail); +#if KBDIO_DEBUG >= 2 + ++q->call_count; + ++q->qcount; + if (q->qcount > q->max_qcount) + q->max_qcount = q->qcount; +#endif + return TRUE; + } + return FALSE; +} + +static int +removeq(kqueue *q) +{ + int c; + + if (q->tail != q->head) { + c = q->q[q->head]; + q->head = nextq(q->head); +#if KBDIO_DEBUG >= 2 + --q->qcount; +#endif + return c; + } + return -1; +} + +/* + * device I/O routines + */ +static int +wait_while_controller_busy(struct atkbdc_softc *kbdc) +{ + /* CPU will stay inside the loop for 100msec at most */ + int retry = 5000; + int f; + + while ((f = read_status(kbdc)) & KBDS_INPUT_BUFFER_FULL) { + if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdc->kbd, read_data(kbdc)); + } else if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdc->aux, read_data(kbdc)); + } + DELAY(KBDC_DELAYTIME); + if (--retry < 0) + return FALSE; + } + return TRUE; +} + +/* + * wait for any data; whether it's from the controller, + * the keyboard, or the aux device. + */ +static int +wait_for_data(struct atkbdc_softc *kbdc) +{ + /* CPU will stay inside the loop for 200msec at most */ + int retry = 10000; + int f; + + while ((f = read_status(kbdc) & KBDS_ANY_BUFFER_FULL) == 0) { + DELAY(KBDC_DELAYTIME); + if (--retry < 0) + return 0; + } + DELAY(KBDD_DELAYTIME); + return f; +} + +/* wait for data from the keyboard */ +static int +wait_for_kbd_data(struct atkbdc_softc *kbdc) +{ + /* CPU will stay inside the loop for 200msec at most */ + int retry = 10000; + int f; + + while ((f = read_status(kbdc) & KBDS_BUFFER_FULL) + != KBDS_KBD_BUFFER_FULL) { + if (f == KBDS_AUX_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdc->aux, read_data(kbdc)); + } + DELAY(KBDC_DELAYTIME); + if (--retry < 0) + return 0; + } + DELAY(KBDD_DELAYTIME); + return f; +} + +/* + * wait for an ACK(FAh), RESEND(FEh), or RESET_FAIL(FCh) from the keyboard. + * queue anything else. + */ +static int +wait_for_kbd_ack(struct atkbdc_softc *kbdc) +{ + /* CPU will stay inside the loop for 200msec at most */ + int retry = 10000; + int f; + int b; + + while (retry-- > 0) { + if ((f = read_status(kbdc)) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + b = read_data(kbdc); + if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) { + if ((b == KBD_ACK) || (b == KBD_RESEND) + || (b == KBD_RESET_FAIL)) + return b; + addq(&kbdc->kbd, b); + } else if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) { + addq(&kbdc->aux, b); + } + } + DELAY(KBDC_DELAYTIME); + } + return -1; +} + +/* wait for data from the aux device */ +static int +wait_for_aux_data(struct atkbdc_softc *kbdc) +{ + /* CPU will stay inside the loop for 200msec at most */ + int retry = 10000; + int f; + + while ((f = read_status(kbdc) & KBDS_BUFFER_FULL) + != KBDS_AUX_BUFFER_FULL) { + if (f == KBDS_KBD_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdc->kbd, read_data(kbdc)); + } + DELAY(KBDC_DELAYTIME); + if (--retry < 0) + return 0; + } + DELAY(KBDD_DELAYTIME); + return f; +} + +/* + * wait for an ACK(FAh), RESEND(FEh), or RESET_FAIL(FCh) from the aux device. + * queue anything else. + */ +static int +wait_for_aux_ack(struct atkbdc_softc *kbdc) +{ + /* CPU will stay inside the loop for 200msec at most */ + int retry = 10000; + int f; + int b; + + while (retry-- > 0) { + if ((f = read_status(kbdc)) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + b = read_data(kbdc); + if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) { + if ((b == PSM_ACK) || (b == PSM_RESEND) + || (b == PSM_RESET_FAIL)) + return b; + addq(&kbdc->aux, b); + } else if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) { + addq(&kbdc->kbd, b); + } + } + DELAY(KBDC_DELAYTIME); + } + return -1; +} + +/* write a one byte command to the controller */ +int +write_controller_command(KBDC p, int c) +{ + if (!wait_while_controller_busy(kbdcp(p))) + return FALSE; + write_command(kbdcp(p), c); + return TRUE; +} + +/* write a one byte data to the controller */ +int +write_controller_data(KBDC p, int c) +{ + if (!wait_while_controller_busy(kbdcp(p))) + return FALSE; + write_data(kbdcp(p), c); + return TRUE; +} + +/* write a one byte keyboard command */ +int +write_kbd_command(KBDC p, int c) +{ + if (!wait_while_controller_busy(kbdcp(p))) + return FALSE; + write_data(kbdcp(p), c); + return TRUE; +} + +/* write a one byte auxiliary device command */ +int +write_aux_command(KBDC p, int c) +{ + if (!write_controller_command(p, KBDC_WRITE_TO_AUX)) + return FALSE; + return write_controller_data(p, c); +} + +/* send a command to the keyboard and wait for ACK */ +int +send_kbd_command(KBDC p, int c) +{ + int retry = KBD_MAXRETRY; + int res = -1; + + while (retry-- > 0) { + if (!write_kbd_command(p, c)) + continue; + res = wait_for_kbd_ack(kbdcp(p)); + if (res == KBD_ACK) + break; + } + return res; +} + +/* send a command to the auxiliary device and wait for ACK */ +int +send_aux_command(KBDC p, int c) +{ + int retry = KBD_MAXRETRY; + int res = -1; + + while (retry-- > 0) { + if (!write_aux_command(p, c)) + continue; + /* + * FIXME: XXX + * The aux device may have already sent one or two bytes of + * status data, when a command is received. It will immediately + * stop data transmission, thus, leaving an incomplete data + * packet in our buffer. We have to discard any unprocessed + * data in order to remove such packets. Well, we may remove + * unprocessed, but necessary data byte as well... + */ + emptyq(&kbdcp(p)->aux); + res = wait_for_aux_ack(kbdcp(p)); + if (res == PSM_ACK) + break; + } + return res; +} + +/* send a command and a data to the keyboard, wait for ACKs */ +int +send_kbd_command_and_data(KBDC p, int c, int d) +{ + int retry; + int res = -1; + + for (retry = KBD_MAXRETRY; retry > 0; --retry) { + if (!write_kbd_command(p, c)) + continue; + res = wait_for_kbd_ack(kbdcp(p)); + if (res == KBD_ACK) + break; + else if (res != KBD_RESEND) + return res; + } + if (retry <= 0) + return res; + + for (retry = KBD_MAXRETRY, res = -1; retry > 0; --retry) { + if (!write_kbd_command(p, d)) + continue; + res = wait_for_kbd_ack(kbdcp(p)); + if (res != KBD_RESEND) + break; + } + return res; +} + +/* send a command and a data to the auxiliary device, wait for ACKs */ +int +send_aux_command_and_data(KBDC p, int c, int d) +{ + int retry; + int res = -1; + + for (retry = KBD_MAXRETRY; retry > 0; --retry) { + if (!write_aux_command(p, c)) + continue; + emptyq(&kbdcp(p)->aux); + res = wait_for_aux_ack(kbdcp(p)); + if (res == PSM_ACK) + break; + else if (res != PSM_RESEND) + return res; + } + if (retry <= 0) + return res; + + for (retry = KBD_MAXRETRY, res = -1; retry > 0; --retry) { + if (!write_aux_command(p, d)) + continue; + res = wait_for_aux_ack(kbdcp(p)); + if (res != PSM_RESEND) + break; + } + return res; +} + +/* + * read one byte from any source; whether from the controller, + * the keyboard, or the aux device + */ +int +read_controller_data(KBDC p) +{ + if (availq(&kbdcp(p)->kbd)) + return removeq(&kbdcp(p)->kbd); + if (availq(&kbdcp(p)->aux)) + return removeq(&kbdcp(p)->aux); + if (!wait_for_data(kbdcp(p))) + return -1; /* timeout */ + return read_data(kbdcp(p)); +} + +#if KBDIO_DEBUG >= 2 +static int call = 0; +#endif + +/* read one byte from the keyboard */ +int +read_kbd_data(KBDC p) +{ +#if KBDIO_DEBUG >= 2 + if (++call > 2000) { + call = 0; + log(LOG_DEBUG, "kbdc: kbd q: %d calls, max %d chars, " + "aux q: %d calls, max %d chars\n", + kbdcp(p)->kbd.call_count, kbdcp(p)->kbd.max_qcount, + kbdcp(p)->aux.call_count, kbdcp(p)->aux.max_qcount); + } +#endif + + if (availq(&kbdcp(p)->kbd)) + return removeq(&kbdcp(p)->kbd); + if (!wait_for_kbd_data(kbdcp(p))) + return -1; /* timeout */ + return read_data(kbdcp(p)); +} + +/* read one byte from the keyboard, but return immediately if + * no data is waiting + */ +int +read_kbd_data_no_wait(KBDC p) +{ + int f; + +#if KBDIO_DEBUG >= 2 + if (++call > 2000) { + call = 0; + log(LOG_DEBUG, "kbdc: kbd q: %d calls, max %d chars, " + "aux q: %d calls, max %d chars\n", + kbdcp(p)->kbd.call_count, kbdcp(p)->kbd.max_qcount, + kbdcp(p)->aux.call_count, kbdcp(p)->aux.max_qcount); + } +#endif + + if (availq(&kbdcp(p)->kbd)) + return removeq(&kbdcp(p)->kbd); + f = read_status(kbdcp(p)) & KBDS_BUFFER_FULL; + if (f == KBDS_AUX_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdcp(p)->aux, read_data(kbdcp(p))); + f = read_status(kbdcp(p)) & KBDS_BUFFER_FULL; + } + if (f == KBDS_KBD_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + return read_data(kbdcp(p)); + } + return -1; /* no data */ +} + +/* read one byte from the aux device */ +int +read_aux_data(KBDC p) +{ + if (availq(&kbdcp(p)->aux)) + return removeq(&kbdcp(p)->aux); + if (!wait_for_aux_data(kbdcp(p))) + return -1; /* timeout */ + return read_data(kbdcp(p)); +} + +/* read one byte from the aux device, but return immediately if + * no data is waiting + */ +int +read_aux_data_no_wait(KBDC p) +{ + int f; + + if (availq(&kbdcp(p)->aux)) + return removeq(&kbdcp(p)->aux); + f = read_status(kbdcp(p)) & KBDS_BUFFER_FULL; + if (f == KBDS_KBD_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdcp(p)->kbd, read_data(kbdcp(p))); + f = read_status(kbdcp(p)) & KBDS_BUFFER_FULL; + } + if (f == KBDS_AUX_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + return read_data(kbdcp(p)); + } + return -1; /* no data */ +} + +/* discard data from the keyboard */ +void +empty_kbd_buffer(KBDC p, int wait) +{ + int t; + int b; + int f; +#if KBDIO_DEBUG >= 2 + int c1 = 0; + int c2 = 0; +#endif + int delta = 2; + + for (t = wait; t > 0; ) { + if ((f = read_status(kbdcp(p))) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + b = read_data(kbdcp(p)); + if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) { + addq(&kbdcp(p)->aux, b); +#if KBDIO_DEBUG >= 2 + ++c2; + } else { + ++c1; +#endif + } + t = wait; + } else { + t -= delta; + } + DELAY(delta*1000); + } +#if KBDIO_DEBUG >= 2 + if ((c1 > 0) || (c2 > 0)) + log(LOG_DEBUG, "kbdc: %d:%d char read (empty_kbd_buffer)\n", c1, c2); +#endif + + emptyq(&kbdcp(p)->kbd); +} + +/* discard data from the aux device */ +void +empty_aux_buffer(KBDC p, int wait) +{ + int t; + int b; + int f; +#if KBDIO_DEBUG >= 2 + int c1 = 0; + int c2 = 0; +#endif + int delta = 2; + + for (t = wait; t > 0; ) { + if ((f = read_status(kbdcp(p))) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + b = read_data(kbdcp(p)); + if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) { + addq(&kbdcp(p)->kbd, b); +#if KBDIO_DEBUG >= 2 + ++c1; + } else { + ++c2; +#endif + } + t = wait; + } else { + t -= delta; + } + DELAY(delta*1000); + } +#if KBDIO_DEBUG >= 2 + if ((c1 > 0) || (c2 > 0)) + log(LOG_DEBUG, "kbdc: %d:%d char read (empty_aux_buffer)\n", c1, c2); +#endif + + emptyq(&kbdcp(p)->aux); +} + +/* discard any data from the keyboard or the aux device */ +void +empty_both_buffers(KBDC p, int wait) +{ + int t; + int f; +#if KBDIO_DEBUG >= 2 + int c1 = 0; + int c2 = 0; +#endif + int delta = 2; + + for (t = wait; t > 0; ) { + if ((f = read_status(kbdcp(p))) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + (void)read_data(kbdcp(p)); +#if KBDIO_DEBUG >= 2 + if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) + ++c1; + else + ++c2; +#endif + t = wait; + } else { + t -= delta; + } + DELAY(delta*1000); + } +#if KBDIO_DEBUG >= 2 + if ((c1 > 0) || (c2 > 0)) + log(LOG_DEBUG, "kbdc: %d:%d char read (empty_both_buffers)\n", c1, c2); +#endif + + emptyq(&kbdcp(p)->kbd); + emptyq(&kbdcp(p)->aux); +} + +/* keyboard and mouse device control */ + +/* NOTE: enable the keyboard port but disable the keyboard + * interrupt before calling "reset_kbd()". + */ +int +reset_kbd(KBDC p) +{ + int retry = KBD_MAXRETRY; + int again = KBD_MAXWAIT; + int c = KBD_RESEND; /* keep the compiler happy */ + + while (retry-- > 0) { + empty_both_buffers(p, 10); + if (!write_kbd_command(p, KBDC_RESET_KBD)) + continue; + emptyq(&kbdcp(p)->kbd); + c = read_controller_data(p); + if (verbose || bootverbose) + log(LOG_DEBUG, "kbdc: RESET_KBD return code:%04x\n", c); + if (c == KBD_ACK) /* keyboard has agreed to reset itself... */ + break; + } + if (retry < 0) + return FALSE; + + while (again-- > 0) { + /* wait awhile, well, in fact we must wait quite loooooooooooong */ + DELAY(KBD_RESETDELAY*1000); + c = read_controller_data(p); /* RESET_DONE/RESET_FAIL */ + if (c != -1) /* wait again if the controller is not ready */ + break; + } + if (verbose || bootverbose) + log(LOG_DEBUG, "kbdc: RESET_KBD status:%04x\n", c); + if (c != KBD_RESET_DONE) + return FALSE; + return TRUE; +} + +/* NOTE: enable the aux port but disable the aux interrupt + * before calling `reset_aux_dev()'. + */ +int +reset_aux_dev(KBDC p) +{ + int retry = KBD_MAXRETRY; + int again = KBD_MAXWAIT; + int c = PSM_RESEND; /* keep the compiler happy */ + + while (retry-- > 0) { + empty_both_buffers(p, 10); + if (!write_aux_command(p, PSMC_RESET_DEV)) + continue; + emptyq(&kbdcp(p)->aux); + /* NOTE: Compaq Armada laptops require extra delay here. XXX */ + for (again = KBD_MAXWAIT; again > 0; --again) { + DELAY(KBD_RESETDELAY*1000); + c = read_aux_data_no_wait(p); + if (c != -1) + break; + } + if (verbose || bootverbose) + log(LOG_DEBUG, "kbdc: RESET_AUX return code:%04x\n", c); + if (c == PSM_ACK) /* aux dev is about to reset... */ + break; + } + if (retry < 0) + return FALSE; + + for (again = KBD_MAXWAIT; again > 0; --again) { + /* wait awhile, well, quite looooooooooooong */ + DELAY(KBD_RESETDELAY*1000); + c = read_aux_data_no_wait(p); /* RESET_DONE/RESET_FAIL */ + if (c != -1) /* wait again if the controller is not ready */ + break; + } + if (verbose || bootverbose) + log(LOG_DEBUG, "kbdc: RESET_AUX status:%04x\n", c); + if (c != PSM_RESET_DONE) /* reset status */ + return FALSE; + + c = read_aux_data(p); /* device ID */ + if (verbose || bootverbose) + log(LOG_DEBUG, "kbdc: RESET_AUX ID:%04x\n", c); + /* NOTE: we could check the device ID now, but leave it later... */ + return TRUE; +} + +/* controller diagnostics and setup */ + +int +test_controller(KBDC p) +{ + int retry = KBD_MAXRETRY; + int again = KBD_MAXWAIT; + int c = KBD_DIAG_FAIL; + + while (retry-- > 0) { + empty_both_buffers(p, 10); + if (write_controller_command(p, KBDC_DIAGNOSE)) + break; + } + if (retry < 0) + return FALSE; + + emptyq(&kbdcp(p)->kbd); + while (again-- > 0) { + /* wait awhile */ + DELAY(KBD_RESETDELAY*1000); + c = read_controller_data(p); /* DIAG_DONE/DIAG_FAIL */ + if (c != -1) /* wait again if the controller is not ready */ + break; + } + if (verbose || bootverbose) + log(LOG_DEBUG, "kbdc: DIAGNOSE status:%04x\n", c); + return (c == KBD_DIAG_DONE); +} + +int +test_kbd_port(KBDC p) +{ + int retry = KBD_MAXRETRY; + int again = KBD_MAXWAIT; + int c = -1; + + while (retry-- > 0) { + empty_both_buffers(p, 10); + if (write_controller_command(p, KBDC_TEST_KBD_PORT)) + break; + } + if (retry < 0) + return FALSE; + + emptyq(&kbdcp(p)->kbd); + while (again-- > 0) { + c = read_controller_data(p); + if (c != -1) /* try again if the controller is not ready */ + break; + } + if (verbose || bootverbose) + log(LOG_DEBUG, "kbdc: TEST_KBD_PORT status:%04x\n", c); + return c; +} + +int +test_aux_port(KBDC p) +{ + int retry = KBD_MAXRETRY; + int again = KBD_MAXWAIT; + int c = -1; + + while (retry-- > 0) { + empty_both_buffers(p, 10); + if (write_controller_command(p, KBDC_TEST_AUX_PORT)) + break; + } + if (retry < 0) + return FALSE; + + emptyq(&kbdcp(p)->kbd); + while (again-- > 0) { + c = read_controller_data(p); + if (c != -1) /* try again if the controller is not ready */ + break; + } + if (verbose || bootverbose) + log(LOG_DEBUG, "kbdc: TEST_AUX_PORT status:%04x\n", c); + return c; +} + +int +kbdc_get_device_mask(KBDC p) +{ + return kbdcp(p)->command_mask; +} + +void +kbdc_set_device_mask(KBDC p, int mask) +{ + kbdcp(p)->command_mask = + mask & (KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS); +} + +int +get_controller_command_byte(KBDC p) +{ + if (kbdcp(p)->command_byte != -1) + return kbdcp(p)->command_byte; + if (!write_controller_command(p, KBDC_GET_COMMAND_BYTE)) + return -1; + emptyq(&kbdcp(p)->kbd); + kbdcp(p)->command_byte = read_controller_data(p); + return kbdcp(p)->command_byte; +} + +int +set_controller_command_byte(KBDC p, int mask, int command) +{ + if (get_controller_command_byte(p) == -1) + return FALSE; + + command = (kbdcp(p)->command_byte & ~mask) | (command & mask); + if (command & KBD_DISABLE_KBD_PORT) { + if (!write_controller_command(p, KBDC_DISABLE_KBD_PORT)) + return FALSE; + } + if (!write_controller_command(p, KBDC_SET_COMMAND_BYTE)) + return FALSE; + if (!write_controller_data(p, command)) + return FALSE; + kbdcp(p)->command_byte = command; + + if (verbose) + log(LOG_DEBUG, "kbdc: new command byte:%04x (set_controller...)\n", + command); + + return TRUE; +} diff --git a/sys/dev/atkbdc/atkbdc_isa.c b/sys/dev/atkbdc/atkbdc_isa.c new file mode 100644 index 0000000..5619fff --- /dev/null +++ b/sys/dev/atkbdc/atkbdc_isa.c @@ -0,0 +1,369 @@ +/*- + * Copyright (c) 1999 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer as + * the first lines of this file unmodified. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_kbd.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> +#include <isa/isavar.h> + +static MALLOC_DEFINE(M_ATKBDDEV, "atkbddev", "AT Keyboard device"); + +/* children */ +typedef struct atkbdc_device { + struct resource_list resources; + int rid; + u_int32_t vendorid; + u_int32_t serial; + u_int32_t logicalid; + u_int32_t compatid; +} atkbdc_device_t; + +/* kbdc */ +devclass_t atkbdc_devclass; + +static int atkbdc_probe(device_t dev); +static int atkbdc_attach(device_t dev); +static device_t atkbdc_add_child(device_t bus, int order, char *name, + int unit); +static int atkbdc_print_child(device_t bus, device_t dev); +static int atkbdc_read_ivar(device_t bus, device_t dev, int index, + uintptr_t *val); +static int atkbdc_write_ivar(device_t bus, device_t dev, int index, + uintptr_t val); +static struct resource_list + *atkbdc_get_resource_list (device_t bus, device_t dev); +static struct resource + *atkbdc_alloc_resource(device_t bus, device_t dev, int type, + int *rid, u_long start, u_long end, + u_long count, u_int flags); +static int atkbdc_release_resource(device_t bus, device_t dev, int type, + int rid, struct resource *res); + +static device_method_t atkbdc_methods[] = { + DEVMETHOD(device_probe, atkbdc_probe), + DEVMETHOD(device_attach, atkbdc_attach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + + DEVMETHOD(bus_add_child, atkbdc_add_child), + DEVMETHOD(bus_print_child, atkbdc_print_child), + DEVMETHOD(bus_read_ivar, atkbdc_read_ivar), + DEVMETHOD(bus_write_ivar, atkbdc_write_ivar), + DEVMETHOD(bus_get_resource_list,atkbdc_get_resource_list), + DEVMETHOD(bus_alloc_resource, atkbdc_alloc_resource), + DEVMETHOD(bus_release_resource, atkbdc_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), + DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + + { 0, 0 } +}; + +static driver_t atkbdc_driver = { + ATKBDC_DRIVER_NAME, + atkbdc_methods, + sizeof(atkbdc_softc_t *), +}; + +static struct isa_pnp_id atkbdc_ids[] = { + { 0x0303d041, "Keyboard controller (i8042)" }, /* PNP0303 */ + { 0 } +}; + +static int +atkbdc_probe(device_t dev) +{ + struct resource *port0; + struct resource *port1; + u_long start; + u_long count; + int error; + int rid; + + /* check PnP IDs */ + if (ISA_PNP_PROBE(device_get_parent(dev), dev, atkbdc_ids) == ENXIO) + return ENXIO; + + device_set_desc(dev, "Keyboard controller (i8042)"); + + /* + * Adjust I/O port resources. + * The AT keyboard controller uses two ports (a command/data port + * 0x60 and a status port 0x64), which may be given to us in + * one resource (0x60 through 0x64) or as two separate resources + * (0x60 and 0x64). Furthermore, /boot/device.hints may contain + * just one port, 0x60. We shall adjust resource settings + * so that these two ports are available as two separate resources. + */ + device_quiet(dev); + rid = 0; + if (bus_get_resource(dev, SYS_RES_IOPORT, rid, &start, &count) != 0) + return ENXIO; + if (count > 1) /* adjust the count */ + bus_set_resource(dev, SYS_RES_IOPORT, rid, start, 1); + port0 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (port0 == NULL) + return ENXIO; + rid = 1; + if (bus_get_resource(dev, SYS_RES_IOPORT, rid, NULL, NULL) != 0) + bus_set_resource(dev, SYS_RES_IOPORT, 1, + start + KBD_STATUS_PORT, 1); + port1 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (port1 == NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, port0); + return ENXIO; + } + device_verbose(dev); + + error = atkbdc_probe_unit(device_get_unit(dev), port0, port1); + if (error == 0) + bus_generic_probe(dev); + + bus_release_resource(dev, SYS_RES_IOPORT, 0, port0); + bus_release_resource(dev, SYS_RES_IOPORT, 1, port1); + + return error; +} + +static int +atkbdc_attach(device_t dev) +{ + atkbdc_softc_t *sc; + int unit; + int error; + int rid; + + unit = device_get_unit(dev); + sc = *(atkbdc_softc_t **)device_get_softc(dev); + if (sc == NULL) { + /* + * We have to maintain two copies of the kbdc_softc struct, + * as the low-level console needs to have access to the + * keyboard controller before kbdc is probed and attached. + * kbdc_soft[] contains the default entry for that purpose. + * See atkbdc.c. XXX + */ + sc = atkbdc_get_softc(unit); + if (sc == NULL) + return ENOMEM; + } + + rid = 0; + sc->port0 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (sc->port0 == NULL) + return ENXIO; + rid = 1; + sc->port1 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (sc->port1 == NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->port0); + return ENXIO; + } + + error = atkbdc_attach_unit(unit, sc, sc->port0, sc->port1); + if (error) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->port0); + bus_release_resource(dev, SYS_RES_IOPORT, 1, sc->port1); + return error; + } + *(atkbdc_softc_t **)device_get_softc(dev) = sc; + + bus_generic_attach(dev); + + return 0; +} + +static device_t +atkbdc_add_child(device_t bus, int order, char *name, int unit) +{ + atkbdc_device_t *ivar; + device_t child; + int t; + + ivar = malloc(sizeof(struct atkbdc_device), M_ATKBDDEV, + M_NOWAIT | M_ZERO); + if (!ivar) + return NULL; + + child = device_add_child_ordered(bus, order, name, unit); + if (child == NULL) { + free(ivar, M_ATKBDDEV); + return child; + } + + resource_list_init(&ivar->resources); + ivar->rid = order; + + /* + * If the device is not created by the PnP BIOS or ACPI, + * refer to device hints for IRQ. + */ + if (ISA_PNP_PROBE(device_get_parent(bus), bus, atkbdc_ids) != 0) { + if (resource_int_value(name, unit, "irq", &t) != 0) + t = -1; + } else { + t = bus_get_resource_start(bus, SYS_RES_IRQ, ivar->rid); + } + if (t > 0) + resource_list_add(&ivar->resources, SYS_RES_IRQ, ivar->rid, + t, t, 1); + + if (resource_int_value(name, unit, "flags", &t) == 0) + device_set_flags(child, t); + if (resource_int_value(name, unit, "disabled", &t) == 0 && t != 0) + device_disable(child); + + device_set_ivars(child, ivar); + + return child; +} + +static int +atkbdc_print_child(device_t bus, device_t dev) +{ + atkbdc_device_t *kbdcdev; + u_long irq; + int flags; + int retval = 0; + + kbdcdev = (atkbdc_device_t *)device_get_ivars(dev); + + retval += bus_print_child_header(bus, dev); + flags = device_get_flags(dev); + if (flags != 0) + retval += printf(" flags 0x%x", flags); + irq = bus_get_resource_start(dev, SYS_RES_IRQ, kbdcdev->rid); + if (irq != 0) + retval += printf(" irq %ld", irq); + retval += bus_print_child_footer(bus, dev); + + return (retval); +} + +static int +atkbdc_read_ivar(device_t bus, device_t dev, int index, uintptr_t *val) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + switch (index) { + case KBDC_IVAR_VENDORID: + *val = (u_long)ivar->vendorid; + break; + case KBDC_IVAR_SERIAL: + *val = (u_long)ivar->serial; + break; + case KBDC_IVAR_LOGICALID: + *val = (u_long)ivar->logicalid; + break; + case KBDC_IVAR_COMPATID: + *val = (u_long)ivar->compatid; + break; + default: + return ENOENT; + } + return 0; +} + +static int +atkbdc_write_ivar(device_t bus, device_t dev, int index, uintptr_t val) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + switch (index) { + case KBDC_IVAR_VENDORID: + ivar->vendorid = (u_int32_t)val; + break; + case KBDC_IVAR_SERIAL: + ivar->serial = (u_int32_t)val; + break; + case KBDC_IVAR_LOGICALID: + ivar->logicalid = (u_int32_t)val; + break; + case KBDC_IVAR_COMPATID: + ivar->compatid = (u_int32_t)val; + break; + default: + return ENOENT; + } + return 0; +} + +static struct resource_list +*atkbdc_get_resource_list (device_t bus, device_t dev) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + return &ivar->resources; +} + +static struct resource +*atkbdc_alloc_resource(device_t bus, device_t dev, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + return resource_list_alloc(&ivar->resources, bus, dev, type, rid, + start, end, count, flags); +} + +static int +atkbdc_release_resource(device_t bus, device_t dev, int type, int rid, + struct resource *res) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + return resource_list_release(&ivar->resources, bus, dev, type, rid, + res); +} + +DRIVER_MODULE(atkbdc, isa, atkbdc_driver, atkbdc_devclass, 0, 0); +DRIVER_MODULE(atkbdc, acpi, atkbdc_driver, atkbdc_devclass, 0, 0); diff --git a/sys/dev/atkbdc/atkbdc_subr.c b/sys/dev/atkbdc/atkbdc_subr.c new file mode 100644 index 0000000..5619fff --- /dev/null +++ b/sys/dev/atkbdc/atkbdc_subr.c @@ -0,0 +1,369 @@ +/*- + * Copyright (c) 1999 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer as + * the first lines of this file unmodified. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_kbd.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> +#include <isa/isavar.h> + +static MALLOC_DEFINE(M_ATKBDDEV, "atkbddev", "AT Keyboard device"); + +/* children */ +typedef struct atkbdc_device { + struct resource_list resources; + int rid; + u_int32_t vendorid; + u_int32_t serial; + u_int32_t logicalid; + u_int32_t compatid; +} atkbdc_device_t; + +/* kbdc */ +devclass_t atkbdc_devclass; + +static int atkbdc_probe(device_t dev); +static int atkbdc_attach(device_t dev); +static device_t atkbdc_add_child(device_t bus, int order, char *name, + int unit); +static int atkbdc_print_child(device_t bus, device_t dev); +static int atkbdc_read_ivar(device_t bus, device_t dev, int index, + uintptr_t *val); +static int atkbdc_write_ivar(device_t bus, device_t dev, int index, + uintptr_t val); +static struct resource_list + *atkbdc_get_resource_list (device_t bus, device_t dev); +static struct resource + *atkbdc_alloc_resource(device_t bus, device_t dev, int type, + int *rid, u_long start, u_long end, + u_long count, u_int flags); +static int atkbdc_release_resource(device_t bus, device_t dev, int type, + int rid, struct resource *res); + +static device_method_t atkbdc_methods[] = { + DEVMETHOD(device_probe, atkbdc_probe), + DEVMETHOD(device_attach, atkbdc_attach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + + DEVMETHOD(bus_add_child, atkbdc_add_child), + DEVMETHOD(bus_print_child, atkbdc_print_child), + DEVMETHOD(bus_read_ivar, atkbdc_read_ivar), + DEVMETHOD(bus_write_ivar, atkbdc_write_ivar), + DEVMETHOD(bus_get_resource_list,atkbdc_get_resource_list), + DEVMETHOD(bus_alloc_resource, atkbdc_alloc_resource), + DEVMETHOD(bus_release_resource, atkbdc_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), + DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + + { 0, 0 } +}; + +static driver_t atkbdc_driver = { + ATKBDC_DRIVER_NAME, + atkbdc_methods, + sizeof(atkbdc_softc_t *), +}; + +static struct isa_pnp_id atkbdc_ids[] = { + { 0x0303d041, "Keyboard controller (i8042)" }, /* PNP0303 */ + { 0 } +}; + +static int +atkbdc_probe(device_t dev) +{ + struct resource *port0; + struct resource *port1; + u_long start; + u_long count; + int error; + int rid; + + /* check PnP IDs */ + if (ISA_PNP_PROBE(device_get_parent(dev), dev, atkbdc_ids) == ENXIO) + return ENXIO; + + device_set_desc(dev, "Keyboard controller (i8042)"); + + /* + * Adjust I/O port resources. + * The AT keyboard controller uses two ports (a command/data port + * 0x60 and a status port 0x64), which may be given to us in + * one resource (0x60 through 0x64) or as two separate resources + * (0x60 and 0x64). Furthermore, /boot/device.hints may contain + * just one port, 0x60. We shall adjust resource settings + * so that these two ports are available as two separate resources. + */ + device_quiet(dev); + rid = 0; + if (bus_get_resource(dev, SYS_RES_IOPORT, rid, &start, &count) != 0) + return ENXIO; + if (count > 1) /* adjust the count */ + bus_set_resource(dev, SYS_RES_IOPORT, rid, start, 1); + port0 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (port0 == NULL) + return ENXIO; + rid = 1; + if (bus_get_resource(dev, SYS_RES_IOPORT, rid, NULL, NULL) != 0) + bus_set_resource(dev, SYS_RES_IOPORT, 1, + start + KBD_STATUS_PORT, 1); + port1 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (port1 == NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, port0); + return ENXIO; + } + device_verbose(dev); + + error = atkbdc_probe_unit(device_get_unit(dev), port0, port1); + if (error == 0) + bus_generic_probe(dev); + + bus_release_resource(dev, SYS_RES_IOPORT, 0, port0); + bus_release_resource(dev, SYS_RES_IOPORT, 1, port1); + + return error; +} + +static int +atkbdc_attach(device_t dev) +{ + atkbdc_softc_t *sc; + int unit; + int error; + int rid; + + unit = device_get_unit(dev); + sc = *(atkbdc_softc_t **)device_get_softc(dev); + if (sc == NULL) { + /* + * We have to maintain two copies of the kbdc_softc struct, + * as the low-level console needs to have access to the + * keyboard controller before kbdc is probed and attached. + * kbdc_soft[] contains the default entry for that purpose. + * See atkbdc.c. XXX + */ + sc = atkbdc_get_softc(unit); + if (sc == NULL) + return ENOMEM; + } + + rid = 0; + sc->port0 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (sc->port0 == NULL) + return ENXIO; + rid = 1; + sc->port1 = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + if (sc->port1 == NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->port0); + return ENXIO; + } + + error = atkbdc_attach_unit(unit, sc, sc->port0, sc->port1); + if (error) { + bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->port0); + bus_release_resource(dev, SYS_RES_IOPORT, 1, sc->port1); + return error; + } + *(atkbdc_softc_t **)device_get_softc(dev) = sc; + + bus_generic_attach(dev); + + return 0; +} + +static device_t +atkbdc_add_child(device_t bus, int order, char *name, int unit) +{ + atkbdc_device_t *ivar; + device_t child; + int t; + + ivar = malloc(sizeof(struct atkbdc_device), M_ATKBDDEV, + M_NOWAIT | M_ZERO); + if (!ivar) + return NULL; + + child = device_add_child_ordered(bus, order, name, unit); + if (child == NULL) { + free(ivar, M_ATKBDDEV); + return child; + } + + resource_list_init(&ivar->resources); + ivar->rid = order; + + /* + * If the device is not created by the PnP BIOS or ACPI, + * refer to device hints for IRQ. + */ + if (ISA_PNP_PROBE(device_get_parent(bus), bus, atkbdc_ids) != 0) { + if (resource_int_value(name, unit, "irq", &t) != 0) + t = -1; + } else { + t = bus_get_resource_start(bus, SYS_RES_IRQ, ivar->rid); + } + if (t > 0) + resource_list_add(&ivar->resources, SYS_RES_IRQ, ivar->rid, + t, t, 1); + + if (resource_int_value(name, unit, "flags", &t) == 0) + device_set_flags(child, t); + if (resource_int_value(name, unit, "disabled", &t) == 0 && t != 0) + device_disable(child); + + device_set_ivars(child, ivar); + + return child; +} + +static int +atkbdc_print_child(device_t bus, device_t dev) +{ + atkbdc_device_t *kbdcdev; + u_long irq; + int flags; + int retval = 0; + + kbdcdev = (atkbdc_device_t *)device_get_ivars(dev); + + retval += bus_print_child_header(bus, dev); + flags = device_get_flags(dev); + if (flags != 0) + retval += printf(" flags 0x%x", flags); + irq = bus_get_resource_start(dev, SYS_RES_IRQ, kbdcdev->rid); + if (irq != 0) + retval += printf(" irq %ld", irq); + retval += bus_print_child_footer(bus, dev); + + return (retval); +} + +static int +atkbdc_read_ivar(device_t bus, device_t dev, int index, uintptr_t *val) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + switch (index) { + case KBDC_IVAR_VENDORID: + *val = (u_long)ivar->vendorid; + break; + case KBDC_IVAR_SERIAL: + *val = (u_long)ivar->serial; + break; + case KBDC_IVAR_LOGICALID: + *val = (u_long)ivar->logicalid; + break; + case KBDC_IVAR_COMPATID: + *val = (u_long)ivar->compatid; + break; + default: + return ENOENT; + } + return 0; +} + +static int +atkbdc_write_ivar(device_t bus, device_t dev, int index, uintptr_t val) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + switch (index) { + case KBDC_IVAR_VENDORID: + ivar->vendorid = (u_int32_t)val; + break; + case KBDC_IVAR_SERIAL: + ivar->serial = (u_int32_t)val; + break; + case KBDC_IVAR_LOGICALID: + ivar->logicalid = (u_int32_t)val; + break; + case KBDC_IVAR_COMPATID: + ivar->compatid = (u_int32_t)val; + break; + default: + return ENOENT; + } + return 0; +} + +static struct resource_list +*atkbdc_get_resource_list (device_t bus, device_t dev) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + return &ivar->resources; +} + +static struct resource +*atkbdc_alloc_resource(device_t bus, device_t dev, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + return resource_list_alloc(&ivar->resources, bus, dev, type, rid, + start, end, count, flags); +} + +static int +atkbdc_release_resource(device_t bus, device_t dev, int type, int rid, + struct resource *res) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + return resource_list_release(&ivar->resources, bus, dev, type, rid, + res); +} + +DRIVER_MODULE(atkbdc, isa, atkbdc_driver, atkbdc_devclass, 0, 0); +DRIVER_MODULE(atkbdc, acpi, atkbdc_driver, atkbdc_devclass, 0, 0); diff --git a/sys/dev/atkbdc/atkbdcreg.h b/sys/dev/atkbdc/atkbdcreg.h new file mode 100644 index 0000000..f2ec844 --- /dev/null +++ b/sys/dev/atkbdc/atkbdcreg.h @@ -0,0 +1,263 @@ +/*- + * Copyright (c) 1996-1999 + * Kazutaka YOKOTA (yokota@zodiac.mech.utsunomiya-u.ac.jp) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * from kbdio.h,v 1.8 1998/09/25 11:55:46 yokota Exp + */ + +#ifndef _DEV_KBD_ATKBDCREG_H_ +#define _DEV_KBD_ATKBDCREG_H_ + +#include "opt_kbd.h" /* Structures depend on the value if KBDIO_DEBUG */ + +/* constants */ + +/* I/O ports */ +#define KBD_STATUS_PORT 4 /* status port, read */ +#define KBD_COMMAND_PORT 4 /* controller command port, write */ +#define KBD_DATA_PORT 0 /* data port, read/write + * also used as keyboard command + * and mouse command port + */ + +/* controller commands (sent to KBD_COMMAND_PORT) */ +#define KBDC_SET_COMMAND_BYTE 0x0060 +#define KBDC_GET_COMMAND_BYTE 0x0020 +#define KBDC_WRITE_TO_AUX 0x00d4 +#define KBDC_DISABLE_AUX_PORT 0x00a7 +#define KBDC_ENABLE_AUX_PORT 0x00a8 +#define KBDC_TEST_AUX_PORT 0x00a9 +#define KBDC_DIAGNOSE 0x00aa +#define KBDC_TEST_KBD_PORT 0x00ab +#define KBDC_DISABLE_KBD_PORT 0x00ad +#define KBDC_ENABLE_KBD_PORT 0x00ae + +/* controller command byte (set by KBDC_SET_COMMAND_BYTE) */ +#define KBD_TRANSLATION 0x0040 +#define KBD_RESERVED_BITS 0x0004 +#define KBD_OVERRIDE_KBD_LOCK 0x0008 +#define KBD_ENABLE_KBD_PORT 0x0000 +#define KBD_DISABLE_KBD_PORT 0x0010 +#define KBD_ENABLE_AUX_PORT 0x0000 +#define KBD_DISABLE_AUX_PORT 0x0020 +#define KBD_ENABLE_AUX_INT 0x0002 +#define KBD_DISABLE_AUX_INT 0x0000 +#define KBD_ENABLE_KBD_INT 0x0001 +#define KBD_DISABLE_KBD_INT 0x0000 +#define KBD_KBD_CONTROL_BITS (KBD_DISABLE_KBD_PORT | KBD_ENABLE_KBD_INT) +#define KBD_AUX_CONTROL_BITS (KBD_DISABLE_AUX_PORT | KBD_ENABLE_AUX_INT) + +/* keyboard device commands (sent to KBD_DATA_PORT) */ +#define KBDC_RESET_KBD 0x00ff +#define KBDC_ENABLE_KBD 0x00f4 +#define KBDC_DISABLE_KBD 0x00f5 +#define KBDC_SET_DEFAULTS 0x00f6 +#define KBDC_SEND_DEV_ID 0x00f2 +#define KBDC_SET_LEDS 0x00ed +#define KBDC_ECHO 0x00ee +#define KBDC_SET_SCANCODE_SET 0x00f0 +#define KBDC_SET_TYPEMATIC 0x00f3 + +/* aux device commands (sent to KBD_DATA_PORT) */ +#define PSMC_RESET_DEV 0x00ff +#define PSMC_ENABLE_DEV 0x00f4 +#define PSMC_DISABLE_DEV 0x00f5 +#define PSMC_SET_DEFAULTS 0x00f6 +#define PSMC_SEND_DEV_ID 0x00f2 +#define PSMC_SEND_DEV_STATUS 0x00e9 +#define PSMC_SEND_DEV_DATA 0x00eb +#define PSMC_SET_SCALING11 0x00e6 +#define PSMC_SET_SCALING21 0x00e7 +#define PSMC_SET_RESOLUTION 0x00e8 +#define PSMC_SET_STREAM_MODE 0x00ea +#define PSMC_SET_REMOTE_MODE 0x00f0 +#define PSMC_SET_SAMPLING_RATE 0x00f3 + +/* PSMC_SET_RESOLUTION argument */ +#define PSMD_RES_LOW 0 /* typically 25ppi */ +#define PSMD_RES_MEDIUM_LOW 1 /* typically 50ppi */ +#define PSMD_RES_MEDIUM_HIGH 2 /* typically 100ppi (default) */ +#define PSMD_RES_HIGH 3 /* typically 200ppi */ +#define PSMD_MAX_RESOLUTION PSMD_RES_HIGH + +/* PSMC_SET_SAMPLING_RATE */ +#define PSMD_MAX_RATE 255 /* FIXME: not sure if it's possible */ + +/* status bits (KBD_STATUS_PORT) */ +#define KBDS_BUFFER_FULL 0x0021 +#define KBDS_ANY_BUFFER_FULL 0x0001 +#define KBDS_KBD_BUFFER_FULL 0x0001 +#define KBDS_AUX_BUFFER_FULL 0x0021 +#define KBDS_INPUT_BUFFER_FULL 0x0002 + +/* return code */ +#define KBD_ACK 0x00fa +#define KBD_RESEND 0x00fe +#define KBD_RESET_DONE 0x00aa +#define KBD_RESET_FAIL 0x00fc +#define KBD_DIAG_DONE 0x0055 +#define KBD_DIAG_FAIL 0x00fd +#define KBD_ECHO 0x00ee + +#define PSM_ACK 0x00fa +#define PSM_RESEND 0x00fe +#define PSM_RESET_DONE 0x00aa +#define PSM_RESET_FAIL 0x00fc + +/* aux device ID */ +#define PSM_MOUSE_ID 0 +#define PSM_BALLPOINT_ID 2 +#define PSM_INTELLI_ID 3 +#define PSM_EXPLORER_ID 4 +#define PSM_4DMOUSE_ID 6 +#define PSM_4DPLUS_ID 8 + +#ifdef _KERNEL + +#define ATKBDC_DRIVER_NAME "atkbdc" + +/* + * driver specific options: the following options may be set by + * `options' statements in the kernel configuration file. + */ + +/* retry count */ +#ifndef KBD_MAXRETRY +#define KBD_MAXRETRY 3 +#endif + +/* timing parameters */ +#ifndef KBD_RESETDELAY +#define KBD_RESETDELAY 200 /* wait 200msec after kbd/mouse reset */ +#endif +#ifndef KBD_MAXWAIT +#define KBD_MAXWAIT 5 /* wait 5 times at most after reset */ +#endif + +/* I/O recovery time */ +#define KBDC_DELAYTIME 20 +#define KBDD_DELAYTIME 7 + +/* debug option */ +#ifndef KBDIO_DEBUG +#define KBDIO_DEBUG 0 +#endif + +/* end of driver specific options */ + +/* types/structures */ + +#define KBDQ_BUFSIZE 32 + +typedef struct _kqueue { + int head; + int tail; + unsigned char q[KBDQ_BUFSIZE]; +#if KBDIO_DEBUG >= 2 + int call_count; + int qcount; + int max_qcount; +#endif +} kqueue; + +struct resource; + +typedef struct atkbdc_softc { + struct resource *port0; /* data port */ + struct resource *port1; /* status port */ + bus_space_tag_t iot; + bus_space_handle_t ioh0; + bus_space_handle_t ioh1; + int command_byte; /* current command byte value */ + int command_mask; /* command byte mask bits for kbd/aux devices */ + int lock; /* FIXME: XXX not quite a semaphore... */ + kqueue kbd; /* keyboard data queue */ + kqueue aux; /* auxiliary data queue */ +} atkbdc_softc_t; + +enum kbdc_device_ivar { + KBDC_IVAR_VENDORID, + KBDC_IVAR_SERIAL, + KBDC_IVAR_LOGICALID, + KBDC_IVAR_COMPATID, +}; + +typedef caddr_t KBDC; + +#define KBDC_RID_KBD 0 +#define KBDC_RID_AUX 1 + +/* function prototypes */ + +atkbdc_softc_t *atkbdc_get_softc(int unit); +int atkbdc_probe_unit(int unit, struct resource *port0, struct resource *port1); +int atkbdc_attach_unit(int unit, atkbdc_softc_t *sc, struct resource *port0, + struct resource *port1); +int atkbdc_configure(void); + +KBDC atkbdc_open(int unit); +int kbdc_lock(KBDC kbdc, int lock); +int kbdc_data_ready(KBDC kbdc); + +int write_controller_command(KBDC kbdc,int c); +int write_controller_data(KBDC kbdc,int c); + +int write_kbd_command(KBDC kbdc,int c); +int write_aux_command(KBDC kbdc,int c); +int send_kbd_command(KBDC kbdc,int c); +int send_aux_command(KBDC kbdc,int c); +int send_kbd_command_and_data(KBDC kbdc,int c,int d); +int send_aux_command_and_data(KBDC kbdc,int c,int d); + +int read_controller_data(KBDC kbdc); +int read_kbd_data(KBDC kbdc); +int read_kbd_data_no_wait(KBDC kbdc); +int read_aux_data(KBDC kbdc); +int read_aux_data_no_wait(KBDC kbdc); + +void empty_kbd_buffer(KBDC kbdc, int t); +void empty_aux_buffer(KBDC kbdc, int t); +void empty_both_buffers(KBDC kbdc, int t); + +int reset_kbd(KBDC kbdc); +int reset_aux_dev(KBDC kbdc); + +int test_controller(KBDC kbdc); +int test_kbd_port(KBDC kbdc); +int test_aux_port(KBDC kbdc); + +int kbdc_get_device_mask(KBDC kbdc); +void kbdc_set_device_mask(KBDC kbdc, int mask); + +int get_controller_command_byte(KBDC kbdc); +int set_controller_command_byte(KBDC kbdc, int command, int flag); + +#endif /* _KERNEL */ + +#endif /* !_DEV_KBD_ATKBDCREG_H_ */ diff --git a/sys/dev/atkbdc/atkbdreg.h b/sys/dev/atkbdc/atkbdreg.h new file mode 100644 index 0000000..4ae93e3 --- /dev/null +++ b/sys/dev/atkbdc/atkbdreg.h @@ -0,0 +1,47 @@ +/*- + * Copyright (c) 1999 Kazutaka YOKOTA <yokota@zodiac.mech.utsunomiya-u.ac.jp> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer as + * the first lines of this file unmodified. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _DEV_KBD_ATKBDREG_H_ +#define _DEV_KBD_ATKBDREG_H_ + +#define ATKBD_DRIVER_NAME "atkbd" + +/* device configuration flags (atkbdprobe, atkbdattach) */ +#define KB_CONF_FAIL_IF_NO_KBD (1 << 0) /* don't install if no kbd is found */ +#define KB_CONF_NO_RESET (1 << 1) /* don't reset the keyboard */ +#define KB_CONF_ALT_SCANCODESET (1 << 2) /* assume the XT type keyboard */ + +#ifdef _KERNEL + +int atkbd_probe_unit(int unit, int ctlr, int irq, int flags); +int atkbd_attach_unit(int unit, keyboard_t **kbd, + int ctlr, int irq, int flags); + +#endif + +#endif /* !_DEV_KBD_ATKBDREG_H_ */ diff --git a/sys/dev/atkbdc/psm.c b/sys/dev/atkbdc/psm.c new file mode 100644 index 0000000..5e95cb5 --- /dev/null +++ b/sys/dev/atkbdc/psm.c @@ -0,0 +1,2972 @@ +/*- + * Copyright (c) 1992, 1993 Erik Forsberg. + * Copyright (c) 1996, 1997 Kazutaka YOKOTA. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL I BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Ported to 386bsd Oct 17, 1992 + * Sandi Donno, Computer Science, University of Cape Town, South Africa + * Please send bug reports to sandi@cs.uct.ac.za + * + * Thanks are also due to Rick Macklem, rick@snowhite.cis.uoguelph.ca - + * although I was only partially successful in getting the alpha release + * of his "driver for the Logitech and ATI Inport Bus mice for use with + * 386bsd and the X386 port" to work with my Microsoft mouse, I nevertheless + * found his code to be an invaluable reference when porting this driver + * to 386bsd. + * + * Further modifications for latest 386BSD+patchkit and port to NetBSD, + * Andrew Herbert <andrew@werple.apana.org.au> - 8 June 1993 + * + * Cloned from the Microsoft Bus Mouse driver, also by Erik Forsberg, by + * Andrew Herbert - 12 June 1993 + * + * Modified for PS/2 mouse by Charles Hannum <mycroft@ai.mit.edu> + * - 13 June 1993 + * + * Modified for PS/2 AUX mouse by Shoji Yuen <yuen@nuie.nagoya-u.ac.jp> + * - 24 October 1993 + * + * Hardware access routines and probe logic rewritten by + * Kazutaka Yokota <yokota@zodiac.mech.utsunomiya-u.ac.jp> + * - 3, 14, 22 October 1996. + * - 12 November 1996. IOCTLs and rearranging `psmread', `psmioctl'... + * - 14, 30 November 1996. Uses `kbdio.c'. + * - 13 December 1996. Uses queuing version of `kbdio.c'. + * - January/February 1997. Tweaked probe logic for + * HiNote UltraII/Latitude/Armada laptops. + * - 30 July 1997. Added APM support. + * - 5 March 1997. Defined driver configuration flags (PSM_CONFIG_XXX). + * Improved sync check logic. + * Vendor specific support routines. + */ + +#include "opt_psm.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/poll.h> +#include <sys/syslog.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <sys/selinfo.h> +#include <sys/time.h> +#include <sys/uio.h> + +#include <machine/limits.h> +#include <sys/mouse.h> +#include <machine/resource.h> + +#include <isa/isavar.h> +#include <dev/kbd/atkbdcreg.h> + +/* + * Driver specific options: the following options may be set by + * `options' statements in the kernel configuration file. + */ + +/* debugging */ +#ifndef PSM_DEBUG +#define PSM_DEBUG 0 /* logging: 0: none, 1: brief, 2: verbose */ +#endif + +#ifndef PSM_SYNCERR_THRESHOLD1 +#define PSM_SYNCERR_THRESHOLD1 20 +#endif + +#ifndef PSM_INPUT_TIMEOUT +#define PSM_INPUT_TIMEOUT 2000000 /* 2 sec */ +#endif + +/* end of driver specific options */ + +#define PSM_DRIVER_NAME "psm" +#define PSMCPNP_DRIVER_NAME "psmcpnp" + +/* input queue */ +#define PSM_BUFSIZE 960 +#define PSM_SMALLBUFSIZE 240 + +/* operation levels */ +#define PSM_LEVEL_BASE 0 +#define PSM_LEVEL_STANDARD 1 +#define PSM_LEVEL_NATIVE 2 +#define PSM_LEVEL_MIN PSM_LEVEL_BASE +#define PSM_LEVEL_MAX PSM_LEVEL_NATIVE + +/* Logitech PS2++ protocol */ +#define MOUSE_PS2PLUS_CHECKBITS(b) \ + ((((b[2] & 0x03) << 2) | 0x02) == (b[1] & 0x0f)) +#define MOUSE_PS2PLUS_PACKET_TYPE(b) \ + (((b[0] & 0x30) >> 2) | ((b[1] & 0x30) >> 4)) + +/* some macros */ +#define PSM_UNIT(dev) (minor(dev) >> 1) +#define PSM_NBLOCKIO(dev) (minor(dev) & 1) +#define PSM_MKMINOR(unit,block) (((unit) << 1) | ((block) ? 0:1)) + +#ifndef max +#define max(x,y) ((x) > (y) ? (x) : (y)) +#endif +#ifndef min +#define min(x,y) ((x) < (y) ? (x) : (y)) +#endif + +#define abs(x) (((x) < 0) ? -(x) : (x)) + +/* ring buffer */ +typedef struct ringbuf { + int count; /* # of valid elements in the buffer */ + int head; /* head pointer */ + int tail; /* tail poiner */ + unsigned char buf[PSM_BUFSIZE]; +} ringbuf_t; + +/* driver control block */ +struct psm_softc { /* Driver status information */ + int unit; + struct selinfo rsel; /* Process selecting for Input */ + unsigned char state; /* Mouse driver state */ + int config; /* driver configuration flags */ + int flags; /* other flags */ + KBDC kbdc; /* handle to access the keyboard controller */ + struct resource *intr; /* IRQ resource */ + void *ih; /* interrupt handle */ + mousehw_t hw; /* hardware information */ + mousemode_t mode; /* operation mode */ + mousemode_t dflt_mode; /* default operation mode */ + mousestatus_t status; /* accumulated mouse movement */ + ringbuf_t queue; /* mouse status queue */ + unsigned char ipacket[16]; /* interim input buffer */ + int inputbytes; /* # of bytes in the input buffer */ + int button; /* the latest button state */ + int xold; /* previous absolute X position */ + int yold; /* previous absolute Y position */ + int syncerrors; + struct timeval inputtimeout; + int watchdog; /* watchdog timer flag */ + struct callout_handle callout; /* watchdog timer call out */ + dev_t dev; + dev_t bdev; +}; +devclass_t psm_devclass; +#define PSM_SOFTC(unit) ((struct psm_softc*)devclass_get_softc(psm_devclass, unit)) + +/* driver state flags (state) */ +#define PSM_VALID 0x80 +#define PSM_OPEN 1 /* Device is open */ +#define PSM_ASLP 2 /* Waiting for mouse data */ + +/* driver configuration flags (config) */ +#define PSM_CONFIG_RESOLUTION 0x000f /* resolution */ +#define PSM_CONFIG_ACCEL 0x00f0 /* acceleration factor */ +#define PSM_CONFIG_NOCHECKSYNC 0x0100 /* disable sync. test */ +#define PSM_CONFIG_NOIDPROBE 0x0200 /* disable mouse model probe */ +#define PSM_CONFIG_NORESET 0x0400 /* don't reset the mouse */ +#define PSM_CONFIG_FORCETAP 0x0800 /* assume `tap' action exists */ +#define PSM_CONFIG_IGNPORTERROR 0x1000 /* ignore error in aux port test */ +#define PSM_CONFIG_HOOKRESUME 0x2000 /* hook the system resume event */ +#define PSM_CONFIG_INITAFTERSUSPEND 0x4000 /* init the device at the resume event */ +#define PSM_CONFIG_SYNCHACK 0x8000 /* enable `out-of-sync' hack */ + +#define PSM_CONFIG_FLAGS (PSM_CONFIG_RESOLUTION \ + | PSM_CONFIG_ACCEL \ + | PSM_CONFIG_NOCHECKSYNC \ + | PSM_CONFIG_SYNCHACK \ + | PSM_CONFIG_NOIDPROBE \ + | PSM_CONFIG_NORESET \ + | PSM_CONFIG_FORCETAP \ + | PSM_CONFIG_IGNPORTERROR \ + | PSM_CONFIG_HOOKRESUME \ + | PSM_CONFIG_INITAFTERSUSPEND) + +/* other flags (flags) */ +#define PSM_FLAGS_FINGERDOWN 0x0001 /* VersaPad finger down */ + +/* for backward compatibility */ +#define OLD_MOUSE_GETHWINFO _IOR('M', 1, old_mousehw_t) +#define OLD_MOUSE_GETMODE _IOR('M', 2, old_mousemode_t) +#define OLD_MOUSE_SETMODE _IOW('M', 3, old_mousemode_t) + +typedef struct old_mousehw { + int buttons; + int iftype; + int type; + int hwid; +} old_mousehw_t; + +typedef struct old_mousemode { + int protocol; + int rate; + int resolution; + int accelfactor; +} old_mousemode_t; + +/* packet formatting function */ +typedef int packetfunc_t(struct psm_softc *, unsigned char *, + int *, int, mousestatus_t *); + +/* function prototypes */ +static void psmidentify(driver_t *, device_t); +static int psmprobe(device_t); +static int psmattach(device_t); +static int psmdetach(device_t); +static int psmresume(device_t); + +static d_open_t psmopen; +static d_close_t psmclose; +static d_read_t psmread; +static d_ioctl_t psmioctl; +static d_poll_t psmpoll; + +static int enable_aux_dev(KBDC); +static int disable_aux_dev(KBDC); +static int get_mouse_status(KBDC, int *, int, int); +static int get_aux_id(KBDC); +static int set_mouse_sampling_rate(KBDC, int); +static int set_mouse_scaling(KBDC, int); +static int set_mouse_resolution(KBDC, int); +static int set_mouse_mode(KBDC); +static int get_mouse_buttons(KBDC); +static int is_a_mouse(int); +static void recover_from_error(KBDC); +static int restore_controller(KBDC, int); +static int doinitialize(struct psm_softc *, mousemode_t *); +static int doopen(struct psm_softc *, int); +static int reinitialize(struct psm_softc *, int); +static char *model_name(int); +static void psmintr(void *); +static void psmtimeout(void *); + +/* vendor specific features */ +typedef int probefunc_t(struct psm_softc *); + +static int mouse_id_proc1(KBDC, int, int, int *); +static int mouse_ext_command(KBDC, int); +static probefunc_t enable_groller; +static probefunc_t enable_gmouse; +static probefunc_t enable_aglide; +static probefunc_t enable_kmouse; +static probefunc_t enable_msexplorer; +static probefunc_t enable_msintelli; +static probefunc_t enable_4dmouse; +static probefunc_t enable_4dplus; +static probefunc_t enable_mmanplus; +static probefunc_t enable_versapad; +static int tame_mouse(struct psm_softc *, mousestatus_t *, unsigned char *); + +static struct { + int model; + unsigned char syncmask; + int packetsize; + probefunc_t *probefunc; +} vendortype[] = { + /* + * WARNING: the order of probe is very important. Don't mess it + * unless you know what you are doing. + */ + { MOUSE_MODEL_NET, /* Genius NetMouse */ + 0x08, MOUSE_PS2INTELLI_PACKETSIZE, enable_gmouse, }, + { MOUSE_MODEL_NETSCROLL, /* Genius NetScroll */ + 0xc8, 6, enable_groller, }, + { MOUSE_MODEL_MOUSEMANPLUS, /* Logitech MouseMan+ */ + 0x08, MOUSE_PS2_PACKETSIZE, enable_mmanplus, }, + { MOUSE_MODEL_EXPLORER, /* Microsoft IntelliMouse Explorer */ + 0x08, MOUSE_PS2INTELLI_PACKETSIZE, enable_msexplorer, }, + { MOUSE_MODEL_4D, /* A4 Tech 4D Mouse */ + 0x08, MOUSE_4D_PACKETSIZE, enable_4dmouse, }, + { MOUSE_MODEL_4DPLUS, /* A4 Tech 4D+ Mouse */ + 0xc8, MOUSE_4DPLUS_PACKETSIZE, enable_4dplus, }, + { MOUSE_MODEL_INTELLI, /* Microsoft IntelliMouse */ + 0x08, MOUSE_PS2INTELLI_PACKETSIZE, enable_msintelli, }, + { MOUSE_MODEL_GLIDEPOINT, /* ALPS GlidePoint */ + 0xc0, MOUSE_PS2_PACKETSIZE, enable_aglide, }, + { MOUSE_MODEL_THINK, /* Kensignton ThinkingMouse */ + 0x80, MOUSE_PS2_PACKETSIZE, enable_kmouse, }, + { MOUSE_MODEL_VERSAPAD, /* Interlink electronics VersaPad */ + 0xe8, MOUSE_PS2VERSA_PACKETSIZE, enable_versapad, }, + { MOUSE_MODEL_GENERIC, + 0xc0, MOUSE_PS2_PACKETSIZE, NULL, }, +}; +#define GENERIC_MOUSE_ENTRY ((sizeof(vendortype) / sizeof(*vendortype)) - 1) + +/* device driver declarateion */ +static device_method_t psm_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, psmidentify), + DEVMETHOD(device_probe, psmprobe), + DEVMETHOD(device_attach, psmattach), + DEVMETHOD(device_detach, psmdetach), + DEVMETHOD(device_resume, psmresume), + + { 0, 0 } +}; + +static driver_t psm_driver = { + PSM_DRIVER_NAME, + psm_methods, + sizeof(struct psm_softc), +}; + +#define CDEV_MAJOR 21 + +static struct cdevsw psm_cdevsw = { + /* open */ psmopen, + /* close */ psmclose, + /* read */ psmread, + /* write */ nowrite, + /* ioctl */ psmioctl, + /* poll */ psmpoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ PSM_DRIVER_NAME, + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +/* debug message level */ +static int verbose = PSM_DEBUG; + +/* device I/O routines */ +static int +enable_aux_dev(KBDC kbdc) +{ + int res; + + res = send_aux_command(kbdc, PSMC_ENABLE_DEV); + if (verbose >= 2) + log(LOG_DEBUG, "psm: ENABLE_DEV return code:%04x\n", res); + + return (res == PSM_ACK); +} + +static int +disable_aux_dev(KBDC kbdc) +{ + int res; + + res = send_aux_command(kbdc, PSMC_DISABLE_DEV); + if (verbose >= 2) + log(LOG_DEBUG, "psm: DISABLE_DEV return code:%04x\n", res); + + return (res == PSM_ACK); +} + +static int +get_mouse_status(KBDC kbdc, int *status, int flag, int len) +{ + int cmd; + int res; + int i; + + switch (flag) { + case 0: + default: + cmd = PSMC_SEND_DEV_STATUS; + break; + case 1: + cmd = PSMC_SEND_DEV_DATA; + break; + } + empty_aux_buffer(kbdc, 5); + res = send_aux_command(kbdc, cmd); + if (verbose >= 2) + log(LOG_DEBUG, "psm: SEND_AUX_DEV_%s return code:%04x\n", + (flag == 1) ? "DATA" : "STATUS", res); + if (res != PSM_ACK) + return 0; + + for (i = 0; i < len; ++i) { + status[i] = read_aux_data(kbdc); + if (status[i] < 0) + break; + } + + if (verbose) { + log(LOG_DEBUG, "psm: %s %02x %02x %02x\n", + (flag == 1) ? "data" : "status", status[0], status[1], status[2]); + } + + return i; +} + +static int +get_aux_id(KBDC kbdc) +{ + int res; + int id; + + empty_aux_buffer(kbdc, 5); + res = send_aux_command(kbdc, PSMC_SEND_DEV_ID); + if (verbose >= 2) + log(LOG_DEBUG, "psm: SEND_DEV_ID return code:%04x\n", res); + if (res != PSM_ACK) + return (-1); + + /* 10ms delay */ + DELAY(10000); + + id = read_aux_data(kbdc); + if (verbose >= 2) + log(LOG_DEBUG, "psm: device ID: %04x\n", id); + + return id; +} + +static int +set_mouse_sampling_rate(KBDC kbdc, int rate) +{ + int res; + + res = send_aux_command_and_data(kbdc, PSMC_SET_SAMPLING_RATE, rate); + if (verbose >= 2) + log(LOG_DEBUG, "psm: SET_SAMPLING_RATE (%d) %04x\n", rate, res); + + return ((res == PSM_ACK) ? rate : -1); +} + +static int +set_mouse_scaling(KBDC kbdc, int scale) +{ + int res; + + switch (scale) { + case 1: + default: + scale = PSMC_SET_SCALING11; + break; + case 2: + scale = PSMC_SET_SCALING21; + break; + } + res = send_aux_command(kbdc, scale); + if (verbose >= 2) + log(LOG_DEBUG, "psm: SET_SCALING%s return code:%04x\n", + (scale == PSMC_SET_SCALING21) ? "21" : "11", res); + + return (res == PSM_ACK); +} + +/* `val' must be 0 through PSMD_MAX_RESOLUTION */ +static int +set_mouse_resolution(KBDC kbdc, int val) +{ + int res; + + res = send_aux_command_and_data(kbdc, PSMC_SET_RESOLUTION, val); + if (verbose >= 2) + log(LOG_DEBUG, "psm: SET_RESOLUTION (%d) %04x\n", val, res); + + return ((res == PSM_ACK) ? val : -1); +} + +/* + * NOTE: once `set_mouse_mode()' is called, the mouse device must be + * re-enabled by calling `enable_aux_dev()' + */ +static int +set_mouse_mode(KBDC kbdc) +{ + int res; + + res = send_aux_command(kbdc, PSMC_SET_STREAM_MODE); + if (verbose >= 2) + log(LOG_DEBUG, "psm: SET_STREAM_MODE return code:%04x\n", res); + + return (res == PSM_ACK); +} + +static int +get_mouse_buttons(KBDC kbdc) +{ + int c = 2; /* assume two buttons by default */ + int status[3]; + + /* + * NOTE: a special sequence to obtain Logitech Mouse specific + * information: set resolution to 25 ppi, set scaling to 1:1, set + * scaling to 1:1, set scaling to 1:1. Then the second byte of the + * mouse status bytes is the number of available buttons. + * Some manufactures also support this sequence. + */ + if (set_mouse_resolution(kbdc, PSMD_RES_LOW) != PSMD_RES_LOW) + return c; + if (set_mouse_scaling(kbdc, 1) && set_mouse_scaling(kbdc, 1) + && set_mouse_scaling(kbdc, 1) + && (get_mouse_status(kbdc, status, 0, 3) >= 3)) { + if (status[1] != 0) + return status[1]; + } + return c; +} + +/* misc subroutines */ +/* + * Someday, I will get the complete list of valid pointing devices and + * their IDs... XXX + */ +static int +is_a_mouse(int id) +{ +#if 0 + static int valid_ids[] = { + PSM_MOUSE_ID, /* mouse */ + PSM_BALLPOINT_ID, /* ballpoint device */ + PSM_INTELLI_ID, /* Intellimouse */ + PSM_EXPLORER_ID, /* Intellimouse Explorer */ + -1 /* end of table */ + }; + int i; + + for (i = 0; valid_ids[i] >= 0; ++i) + if (valid_ids[i] == id) + return TRUE; + return FALSE; +#else + return TRUE; +#endif +} + +static char * +model_name(int model) +{ + static struct { + int model_code; + char *model_name; + } models[] = { + { MOUSE_MODEL_NETSCROLL, "NetScroll" }, + { MOUSE_MODEL_NET, "NetMouse/NetScroll Optical" }, + { MOUSE_MODEL_GLIDEPOINT, "GlidePoint" }, + { MOUSE_MODEL_THINK, "ThinkingMouse" }, + { MOUSE_MODEL_INTELLI, "IntelliMouse" }, + { MOUSE_MODEL_MOUSEMANPLUS, "MouseMan+" }, + { MOUSE_MODEL_VERSAPAD, "VersaPad" }, + { MOUSE_MODEL_EXPLORER, "IntelliMouse Explorer" }, + { MOUSE_MODEL_4D, "4D Mouse" }, + { MOUSE_MODEL_4DPLUS, "4D+ Mouse" }, + { MOUSE_MODEL_GENERIC, "Generic PS/2 mouse" }, + { MOUSE_MODEL_UNKNOWN, NULL }, + }; + int i; + + for (i = 0; models[i].model_code != MOUSE_MODEL_UNKNOWN; ++i) { + if (models[i].model_code == model) + return models[i].model_name; + } + return "Unknown"; +} + +static void +recover_from_error(KBDC kbdc) +{ + /* discard anything left in the output buffer */ + empty_both_buffers(kbdc, 10); + +#if 0 + /* + * NOTE: KBDC_RESET_KBD may not restore the communication between the + * keyboard and the controller. + */ + reset_kbd(kbdc); +#else + /* + * NOTE: somehow diagnostic and keyboard port test commands bring the + * keyboard back. + */ + if (!test_controller(kbdc)) + log(LOG_ERR, "psm: keyboard controller failed.\n"); + /* if there isn't a keyboard in the system, the following error is OK */ + if (test_kbd_port(kbdc) != 0) { + if (verbose) + log(LOG_ERR, "psm: keyboard port failed.\n"); + } +#endif +} + +static int +restore_controller(KBDC kbdc, int command_byte) +{ + empty_both_buffers(kbdc, 10); + + if (!set_controller_command_byte(kbdc, 0xff, command_byte)) { + log(LOG_ERR, "psm: failed to restore the keyboard controller " + "command byte.\n"); + empty_both_buffers(kbdc, 10); + return FALSE; + } else { + empty_both_buffers(kbdc, 10); + return TRUE; + } +} + +/* + * Re-initialize the aux port and device. The aux port must be enabled + * and its interrupt must be disabled before calling this routine. + * The aux device will be disabled before returning. + * The keyboard controller must be locked via `kbdc_lock()' before + * calling this routine. + */ +static int +doinitialize(struct psm_softc *sc, mousemode_t *mode) +{ + KBDC kbdc = sc->kbdc; + int stat[3]; + int i; + + switch((i = test_aux_port(kbdc))) { + case 1: /* ignore this error */ + case PSM_ACK: + if (verbose) + log(LOG_DEBUG, "psm%d: strange result for test aux port (%d).\n", + sc->unit, i); + /* FALLTHROUGH */ + case 0: /* no error */ + break; + case -1: /* time out */ + default: /* error */ + recover_from_error(kbdc); + if (sc->config & PSM_CONFIG_IGNPORTERROR) + break; + log(LOG_ERR, "psm%d: the aux port is not functioning (%d).\n", + sc->unit, i); + return FALSE; + } + + if (sc->config & PSM_CONFIG_NORESET) { + /* + * Don't try to reset the pointing device. It may possibly be + * left in the unknown state, though... + */ + } else { + /* + * NOTE: some controllers appears to hang the `keyboard' when + * the aux port doesn't exist and `PSMC_RESET_DEV' is issued. + */ + if (!reset_aux_dev(kbdc)) { + recover_from_error(kbdc); + log(LOG_ERR, "psm%d: failed to reset the aux device.\n", sc->unit); + return FALSE; + } + } + + /* + * both the aux port and the aux device is functioning, see + * if the device can be enabled. + */ + if (!enable_aux_dev(kbdc) || !disable_aux_dev(kbdc)) { + log(LOG_ERR, "psm%d: failed to enable the aux device.\n", sc->unit); + return FALSE; + } + empty_both_buffers(kbdc, 10); /* remove stray data if any */ + + if (sc->config & PSM_CONFIG_NOIDPROBE) { + i = GENERIC_MOUSE_ENTRY; + } else { + /* FIXME: hardware ID, mouse buttons? */ + + /* other parameters */ + for (i = 0; vendortype[i].probefunc != NULL; ++i) { + if ((*vendortype[i].probefunc)(sc)) { + if (verbose >= 2) + log(LOG_ERR, "psm%d: found %s\n", + sc->unit, model_name(vendortype[i].model)); + break; + } + } + } + + sc->hw.model = vendortype[i].model; + sc->mode.packetsize = vendortype[i].packetsize; + + /* set mouse parameters */ + if (mode != (mousemode_t *)NULL) { + if (mode->rate > 0) + mode->rate = set_mouse_sampling_rate(kbdc, mode->rate); + if (mode->resolution >= 0) + mode->resolution = set_mouse_resolution(kbdc, mode->resolution); + set_mouse_scaling(kbdc, 1); + set_mouse_mode(kbdc); + } + + /* request a data packet and extract sync. bits */ + if (get_mouse_status(kbdc, stat, 1, 3) < 3) { + log(LOG_DEBUG, "psm%d: failed to get data (doinitialize).\n", + sc->unit); + sc->mode.syncmask[0] = 0; + } else { + sc->mode.syncmask[1] = stat[0] & sc->mode.syncmask[0]; /* syncbits */ + /* the NetScroll Mouse will send three more bytes... Ignore them */ + empty_aux_buffer(kbdc, 5); + } + + /* just check the status of the mouse */ + if (get_mouse_status(kbdc, stat, 0, 3) < 3) + log(LOG_DEBUG, "psm%d: failed to get status (doinitialize).\n", + sc->unit); + + return TRUE; +} + +static int +doopen(struct psm_softc *sc, int command_byte) +{ + int stat[3]; + + /* enable the mouse device */ + if (!enable_aux_dev(sc->kbdc)) { + /* MOUSE ERROR: failed to enable the mouse because: + * 1) the mouse is faulty, + * 2) the mouse has been removed(!?) + * In the latter case, the keyboard may have hung, and need + * recovery procedure... + */ + recover_from_error(sc->kbdc); +#if 0 + /* FIXME: we could reset the mouse here and try to enable + * it again. But it will take long time and it's not a good + * idea to disable the keyboard that long... + */ + if (!doinitialize(sc, &sc->mode) || !enable_aux_dev(sc->kbdc)) { + recover_from_error(sc->kbdc); +#else + { +#endif + restore_controller(sc->kbdc, command_byte); + /* mark this device is no longer available */ + sc->state &= ~PSM_VALID; + log(LOG_ERR, "psm%d: failed to enable the device (doopen).\n", + sc->unit); + return (EIO); + } + } + + if (get_mouse_status(sc->kbdc, stat, 0, 3) < 3) + log(LOG_DEBUG, "psm%d: failed to get status (doopen).\n", sc->unit); + + /* enable the aux port and interrupt */ + if (!set_controller_command_byte(sc->kbdc, + kbdc_get_device_mask(sc->kbdc), + (command_byte & KBD_KBD_CONTROL_BITS) + | KBD_ENABLE_AUX_PORT | KBD_ENABLE_AUX_INT)) { + /* CONTROLLER ERROR */ + disable_aux_dev(sc->kbdc); + restore_controller(sc->kbdc, command_byte); + log(LOG_ERR, "psm%d: failed to enable the aux interrupt (doopen).\n", + sc->unit); + return (EIO); + } + + /* start the watchdog timer */ + sc->watchdog = FALSE; + sc->callout = timeout(psmtimeout, (void *)(uintptr_t)sc, hz*2); + + return (0); +} + +static int +reinitialize(struct psm_softc *sc, int doinit) +{ + int err; + int c; + int s; + + /* don't let anybody mess with the aux device */ + if (!kbdc_lock(sc->kbdc, TRUE)) + return (EIO); + s = spltty(); + + /* block our watchdog timer */ + sc->watchdog = FALSE; + untimeout(psmtimeout, (void *)(uintptr_t)sc, sc->callout); + callout_handle_init(&sc->callout); + + /* save the current controller command byte */ + empty_both_buffers(sc->kbdc, 10); + c = get_controller_command_byte(sc->kbdc); + if (verbose >= 2) + log(LOG_DEBUG, "psm%d: current command byte: %04x (reinitialize).\n", + sc->unit, c); + + /* enable the aux port but disable the aux interrupt and the keyboard */ + if ((c == -1) || !set_controller_command_byte(sc->kbdc, + kbdc_get_device_mask(sc->kbdc), + KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT + | KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + /* CONTROLLER ERROR */ + splx(s); + kbdc_lock(sc->kbdc, FALSE); + log(LOG_ERR, "psm%d: unable to set the command byte (reinitialize).\n", + sc->unit); + return (EIO); + } + + /* flush any data */ + if (sc->state & PSM_VALID) { + disable_aux_dev(sc->kbdc); /* this may fail; but never mind... */ + empty_aux_buffer(sc->kbdc, 10); + } + sc->inputbytes = 0; + sc->syncerrors = 0; + + /* try to detect the aux device; are you still there? */ + err = 0; + if (doinit) { + if (doinitialize(sc, &sc->mode)) { + /* yes */ + sc->state |= PSM_VALID; + } else { + /* the device has gone! */ + restore_controller(sc->kbdc, c); + sc->state &= ~PSM_VALID; + log(LOG_ERR, "psm%d: the aux device has gone! (reinitialize).\n", + sc->unit); + err = ENXIO; + } + } + splx(s); + + /* restore the driver state */ + if ((sc->state & PSM_OPEN) && (err == 0)) { + /* enable the aux device and the port again */ + err = doopen(sc, c); + if (err != 0) + log(LOG_ERR, "psm%d: failed to enable the device (reinitialize).\n", + sc->unit); + } else { + /* restore the keyboard port and disable the aux port */ + if (!set_controller_command_byte(sc->kbdc, + kbdc_get_device_mask(sc->kbdc), + (c & KBD_KBD_CONTROL_BITS) + | KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + /* CONTROLLER ERROR */ + log(LOG_ERR, "psm%d: failed to disable the aux port (reinitialize).\n", + sc->unit); + err = EIO; + } + } + + kbdc_lock(sc->kbdc, FALSE); + return (err); +} + +/* psm driver entry points */ + +static void +psmidentify(driver_t *driver, device_t parent) +{ + device_t psmc; + device_t psm; + u_long irq; + int unit; + + unit = device_get_unit(parent); + + /* always add at least one child */ + psm = BUS_ADD_CHILD(parent, KBDC_RID_AUX, driver->name, unit); + if (psm == NULL) + return; + + irq = bus_get_resource_start(psm, SYS_RES_IRQ, KBDC_RID_AUX); + if (irq > 0) + return; + + /* + * If the PS/2 mouse device has already been reported by ACPI or + * PnP BIOS, obtain the IRQ resource from it. + * (See psmcpnp_attach() below.) + */ + psmc = device_find_child(device_get_parent(parent), + PSMCPNP_DRIVER_NAME, unit); + if (psmc == NULL) + return; + irq = bus_get_resource_start(psmc, SYS_RES_IRQ, 0); + if (irq <= 0) + return; + bus_set_resource(psm, SYS_RES_IRQ, KBDC_RID_AUX, irq, 1); +} + +#define endprobe(v) { if (bootverbose) \ + --verbose; \ + kbdc_set_device_mask(sc->kbdc, mask); \ + kbdc_lock(sc->kbdc, FALSE); \ + return (v); \ + } + +static int +psmprobe(device_t dev) +{ + int unit = device_get_unit(dev); + struct psm_softc *sc = device_get_softc(dev); + int stat[3]; + int command_byte; + int mask; + int rid; + int i; + +#if 0 + kbdc_debug(TRUE); +#endif + + /* see if IRQ is available */ + rid = KBDC_RID_AUX; + sc->intr = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE); + if (sc->intr == NULL) { + if (bootverbose) + device_printf(dev, "unable to allocate IRQ\n"); + return (ENXIO); + } + bus_release_resource(dev, SYS_RES_IRQ, rid, sc->intr); + + sc->unit = unit; + sc->kbdc = atkbdc_open(device_get_unit(device_get_parent(dev))); + sc->config = device_get_flags(dev) & PSM_CONFIG_FLAGS; + /* XXX: for backward compatibility */ +#if defined(PSM_HOOKRESUME) || defined(PSM_HOOKAPM) + sc->config |= +#ifdef PSM_RESETAFTERSUSPEND + PSM_CONFIG_HOOKRESUME | PSM_CONFIG_INITAFTERSUSPEND; +#else + PSM_CONFIG_HOOKRESUME; +#endif +#endif /* PSM_HOOKRESUME | PSM_HOOKAPM */ + sc->flags = 0; + if (bootverbose) + ++verbose; + + device_set_desc(dev, "PS/2 Mouse"); + + if (!kbdc_lock(sc->kbdc, TRUE)) { + printf("psm%d: unable to lock the controller.\n", unit); + if (bootverbose) + --verbose; + return (ENXIO); + } + + /* + * NOTE: two bits in the command byte controls the operation of the + * aux port (mouse port): the aux port disable bit (bit 5) and the aux + * port interrupt (IRQ 12) enable bit (bit 2). + */ + + /* discard anything left after the keyboard initialization */ + empty_both_buffers(sc->kbdc, 10); + + /* save the current command byte; it will be used later */ + mask = kbdc_get_device_mask(sc->kbdc) & ~KBD_AUX_CONTROL_BITS; + command_byte = get_controller_command_byte(sc->kbdc); + if (verbose) + printf("psm%d: current command byte:%04x\n", unit, command_byte); + if (command_byte == -1) { + /* CONTROLLER ERROR */ + printf("psm%d: unable to get the current command byte value.\n", + unit); + endprobe(ENXIO); + } + + /* + * disable the keyboard port while probing the aux port, which must be + * enabled during this routine + */ + if (!set_controller_command_byte(sc->kbdc, + KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS, + KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT + | KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + /* + * this is CONTROLLER ERROR; I don't know how to recover + * from this error... + */ + restore_controller(sc->kbdc, command_byte); + printf("psm%d: unable to set the command byte.\n", unit); + endprobe(ENXIO); + } + write_controller_command(sc->kbdc, KBDC_ENABLE_AUX_PORT); + + /* + * NOTE: `test_aux_port()' is designed to return with zero if the aux + * port exists and is functioning. However, some controllers appears + * to respond with zero even when the aux port doesn't exist. (It may + * be that this is only the case when the controller DOES have the aux + * port but the port is not wired on the motherboard.) The keyboard + * controllers without the port, such as the original AT, are + * supporsed to return with an error code or simply time out. In any + * case, we have to continue probing the port even when the controller + * passes this test. + * + * XXX: some controllers erroneously return the error code 1 when + * it has the perfectly functional aux port. We have to ignore this + * error code. Even if the controller HAS error with the aux port, + * it will be detected later... + * XXX: another incompatible controller returns PSM_ACK (0xfa)... + */ + switch ((i = test_aux_port(sc->kbdc))) { + case 1: /* ignore this error */ + case PSM_ACK: + if (verbose) + printf("psm%d: strange result for test aux port (%d).\n", + unit, i); + /* FALLTHROUGH */ + case 0: /* no error */ + break; + case -1: /* time out */ + default: /* error */ + recover_from_error(sc->kbdc); + if (sc->config & PSM_CONFIG_IGNPORTERROR) + break; + restore_controller(sc->kbdc, command_byte); + if (verbose) + printf("psm%d: the aux port is not functioning (%d).\n", + unit, i); + endprobe(ENXIO); + } + + if (sc->config & PSM_CONFIG_NORESET) { + /* + * Don't try to reset the pointing device. It may possibly be + * left in the unknown state, though... + */ + } else { + /* + * NOTE: some controllers appears to hang the `keyboard' when the aux + * port doesn't exist and `PSMC_RESET_DEV' is issued. + */ + if (!reset_aux_dev(sc->kbdc)) { + recover_from_error(sc->kbdc); + restore_controller(sc->kbdc, command_byte); + if (verbose) + printf("psm%d: failed to reset the aux device.\n", unit); + endprobe(ENXIO); + } + } + + /* + * both the aux port and the aux device is functioning, see if the + * device can be enabled. NOTE: when enabled, the device will start + * sending data; we shall immediately disable the device once we know + * the device can be enabled. + */ + if (!enable_aux_dev(sc->kbdc) || !disable_aux_dev(sc->kbdc)) { + /* MOUSE ERROR */ + recover_from_error(sc->kbdc); + restore_controller(sc->kbdc, command_byte); + if (verbose) + printf("psm%d: failed to enable the aux device.\n", unit); + endprobe(ENXIO); + } + + /* save the default values after reset */ + if (get_mouse_status(sc->kbdc, stat, 0, 3) >= 3) { + sc->dflt_mode.rate = sc->mode.rate = stat[2]; + sc->dflt_mode.resolution = sc->mode.resolution = stat[1]; + } else { + sc->dflt_mode.rate = sc->mode.rate = -1; + sc->dflt_mode.resolution = sc->mode.resolution = -1; + } + + /* hardware information */ + sc->hw.iftype = MOUSE_IF_PS2; + + /* verify the device is a mouse */ + sc->hw.hwid = get_aux_id(sc->kbdc); + if (!is_a_mouse(sc->hw.hwid)) { + restore_controller(sc->kbdc, command_byte); + if (verbose) + printf("psm%d: unknown device type (%d).\n", unit, sc->hw.hwid); + endprobe(ENXIO); + } + switch (sc->hw.hwid) { + case PSM_BALLPOINT_ID: + sc->hw.type = MOUSE_TRACKBALL; + break; + case PSM_MOUSE_ID: + case PSM_INTELLI_ID: + case PSM_EXPLORER_ID: + case PSM_4DMOUSE_ID: + case PSM_4DPLUS_ID: + sc->hw.type = MOUSE_MOUSE; + break; + default: + sc->hw.type = MOUSE_UNKNOWN; + break; + } + + if (sc->config & PSM_CONFIG_NOIDPROBE) { + sc->hw.buttons = 2; + i = GENERIC_MOUSE_ENTRY; + } else { + /* # of buttons */ + sc->hw.buttons = get_mouse_buttons(sc->kbdc); + + /* other parameters */ + for (i = 0; vendortype[i].probefunc != NULL; ++i) { + if ((*vendortype[i].probefunc)(sc)) { + if (verbose >= 2) + printf("psm%d: found %s\n", + unit, model_name(vendortype[i].model)); + break; + } + } + } + + sc->hw.model = vendortype[i].model; + + sc->dflt_mode.level = PSM_LEVEL_BASE; + sc->dflt_mode.packetsize = MOUSE_PS2_PACKETSIZE; + sc->dflt_mode.accelfactor = (sc->config & PSM_CONFIG_ACCEL) >> 4; + if (sc->config & PSM_CONFIG_NOCHECKSYNC) + sc->dflt_mode.syncmask[0] = 0; + else + sc->dflt_mode.syncmask[0] = vendortype[i].syncmask; + if (sc->config & PSM_CONFIG_FORCETAP) + sc->mode.syncmask[0] &= ~MOUSE_PS2_TAP; + sc->dflt_mode.syncmask[1] = 0; /* syncbits */ + sc->mode = sc->dflt_mode; + sc->mode.packetsize = vendortype[i].packetsize; + + /* set mouse parameters */ +#if 0 + /* + * A version of Logitech FirstMouse+ won't report wheel movement, + * if SET_DEFAULTS is sent... Don't use this command. + * This fix was found by Takashi Nishida. + */ + i = send_aux_command(sc->kbdc, PSMC_SET_DEFAULTS); + if (verbose >= 2) + printf("psm%d: SET_DEFAULTS return code:%04x\n", unit, i); +#endif + if (sc->config & PSM_CONFIG_RESOLUTION) { + sc->mode.resolution + = set_mouse_resolution(sc->kbdc, + (sc->config & PSM_CONFIG_RESOLUTION) - 1); + } else if (sc->mode.resolution >= 0) { + sc->mode.resolution + = set_mouse_resolution(sc->kbdc, sc->dflt_mode.resolution); + } + if (sc->mode.rate > 0) { + sc->mode.rate = set_mouse_sampling_rate(sc->kbdc, sc->dflt_mode.rate); + } + set_mouse_scaling(sc->kbdc, 1); + + /* request a data packet and extract sync. bits */ + if (get_mouse_status(sc->kbdc, stat, 1, 3) < 3) { + printf("psm%d: failed to get data.\n", unit); + sc->mode.syncmask[0] = 0; + } else { + sc->mode.syncmask[1] = stat[0] & sc->mode.syncmask[0]; /* syncbits */ + /* the NetScroll Mouse will send three more bytes... Ignore them */ + empty_aux_buffer(sc->kbdc, 5); + } + + /* just check the status of the mouse */ + /* + * NOTE: XXX there are some arcane controller/mouse combinations out + * there, which hung the controller unless there is data transmission + * after ACK from the mouse. + */ + if (get_mouse_status(sc->kbdc, stat, 0, 3) < 3) { + printf("psm%d: failed to get status.\n", unit); + } else { + /* + * When in its native mode, some mice operate with different + * default parameters than in the PS/2 compatible mode. + */ + sc->dflt_mode.rate = sc->mode.rate = stat[2]; + sc->dflt_mode.resolution = sc->mode.resolution = stat[1]; + } + + /* disable the aux port for now... */ + if (!set_controller_command_byte(sc->kbdc, + KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS, + (command_byte & KBD_KBD_CONTROL_BITS) + | KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + /* + * this is CONTROLLER ERROR; I don't know the proper way to + * recover from this error... + */ + restore_controller(sc->kbdc, command_byte); + printf("psm%d: unable to set the command byte.\n", unit); + endprobe(ENXIO); + } + + /* done */ + kbdc_set_device_mask(sc->kbdc, mask | KBD_AUX_CONTROL_BITS); + kbdc_lock(sc->kbdc, FALSE); + return (0); +} + +static int +psmattach(device_t dev) +{ + int unit = device_get_unit(dev); + struct psm_softc *sc = device_get_softc(dev); + int error; + int rid; + + if (sc == NULL) /* shouldn't happen */ + return (ENXIO); + + /* Setup initial state */ + sc->state = PSM_VALID; + callout_handle_init(&sc->callout); + + /* Setup our interrupt handler */ + rid = KBDC_RID_AUX; + sc->intr = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE); + if (sc->intr == NULL) + return (ENXIO); + error = bus_setup_intr(dev, sc->intr, INTR_TYPE_TTY, psmintr, sc, &sc->ih); + if (error) { + bus_release_resource(dev, SYS_RES_IRQ, rid, sc->intr); + return (error); + } + + /* Done */ + sc->dev = make_dev(&psm_cdevsw, PSM_MKMINOR(unit, FALSE), 0, 0, 0666, + "psm%d", unit); + sc->bdev = make_dev(&psm_cdevsw, PSM_MKMINOR(unit, TRUE), 0, 0, 0666, + "bpsm%d", unit); + + if (!verbose) { + printf("psm%d: model %s, device ID %d\n", + unit, model_name(sc->hw.model), sc->hw.hwid & 0x00ff); + } else { + printf("psm%d: model %s, device ID %d-%02x, %d buttons\n", + unit, model_name(sc->hw.model), + sc->hw.hwid & 0x00ff, sc->hw.hwid >> 8, sc->hw.buttons); + printf("psm%d: config:%08x, flags:%08x, packet size:%d\n", + unit, sc->config, sc->flags, sc->mode.packetsize); + printf("psm%d: syncmask:%02x, syncbits:%02x\n", + unit, sc->mode.syncmask[0], sc->mode.syncmask[1]); + } + + if (bootverbose) + --verbose; + + return (0); +} + +static int +psmdetach(device_t dev) +{ + struct psm_softc *sc; + int rid; + + sc = device_get_softc(dev); + if (sc->state & PSM_OPEN) + return EBUSY; + + rid = KBDC_RID_AUX; + bus_teardown_intr(dev, sc->intr, sc->ih); + bus_release_resource(dev, SYS_RES_IRQ, rid, sc->intr); + + destroy_dev(sc->dev); + destroy_dev(sc->bdev); + + return 0; +} + +static int +psmopen(dev_t dev, int flag, int fmt, struct thread *td) +{ + int unit = PSM_UNIT(dev); + struct psm_softc *sc; + int command_byte; + int err; + int s; + + /* Get device data */ + sc = PSM_SOFTC(unit); + if ((sc == NULL) || (sc->state & PSM_VALID) == 0) + /* the device is no longer valid/functioning */ + return (ENXIO); + + /* Disallow multiple opens */ + if (sc->state & PSM_OPEN) + return (EBUSY); + + device_busy(devclass_get_device(psm_devclass, unit)); + + /* Initialize state */ + sc->mode.level = sc->dflt_mode.level; + sc->mode.protocol = sc->dflt_mode.protocol; + sc->watchdog = FALSE; + + /* flush the event queue */ + sc->queue.count = 0; + sc->queue.head = 0; + sc->queue.tail = 0; + sc->status.flags = 0; + sc->status.button = 0; + sc->status.obutton = 0; + sc->status.dx = 0; + sc->status.dy = 0; + sc->status.dz = 0; + sc->button = 0; + + /* empty input buffer */ + bzero(sc->ipacket, sizeof(sc->ipacket)); + sc->inputbytes = 0; + sc->syncerrors = 0; + + /* don't let timeout routines in the keyboard driver to poll the kbdc */ + if (!kbdc_lock(sc->kbdc, TRUE)) + return (EIO); + + /* save the current controller command byte */ + s = spltty(); + command_byte = get_controller_command_byte(sc->kbdc); + + /* enable the aux port and temporalily disable the keyboard */ + if ((command_byte == -1) + || !set_controller_command_byte(sc->kbdc, + kbdc_get_device_mask(sc->kbdc), + KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT + | KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + /* CONTROLLER ERROR; do you know how to get out of this? */ + kbdc_lock(sc->kbdc, FALSE); + splx(s); + log(LOG_ERR, "psm%d: unable to set the command byte (psmopen).\n", + unit); + return (EIO); + } + /* + * Now that the keyboard controller is told not to generate + * the keyboard and mouse interrupts, call `splx()' to allow + * the other tty interrupts. The clock interrupt may also occur, + * but timeout routines will be blocked by the poll flag set + * via `kbdc_lock()' + */ + splx(s); + + /* enable the mouse device */ + err = doopen(sc, command_byte); + + /* done */ + if (err == 0) + sc->state |= PSM_OPEN; + kbdc_lock(sc->kbdc, FALSE); + return (err); +} + +static int +psmclose(dev_t dev, int flag, int fmt, struct thread *td) +{ + int unit = PSM_UNIT(dev); + struct psm_softc *sc = PSM_SOFTC(unit); + int stat[3]; + int command_byte; + int s; + + /* don't let timeout routines in the keyboard driver to poll the kbdc */ + if (!kbdc_lock(sc->kbdc, TRUE)) + return (EIO); + + /* save the current controller command byte */ + s = spltty(); + command_byte = get_controller_command_byte(sc->kbdc); + if (command_byte == -1) { + kbdc_lock(sc->kbdc, FALSE); + splx(s); + return (EIO); + } + + /* disable the aux interrupt and temporalily disable the keyboard */ + if (!set_controller_command_byte(sc->kbdc, + kbdc_get_device_mask(sc->kbdc), + KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT + | KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + log(LOG_ERR, "psm%d: failed to disable the aux int (psmclose).\n", + unit); + /* CONTROLLER ERROR; + * NOTE: we shall force our way through. Because the only + * ill effect we shall see is that we may not be able + * to read ACK from the mouse, and it doesn't matter much + * so long as the mouse will accept the DISABLE command. + */ + } + splx(s); + + /* stop the watchdog timer */ + untimeout(psmtimeout, (void *)(uintptr_t)sc, sc->callout); + callout_handle_init(&sc->callout); + + /* remove anything left in the output buffer */ + empty_aux_buffer(sc->kbdc, 10); + + /* disable the aux device, port and interrupt */ + if (sc->state & PSM_VALID) { + if (!disable_aux_dev(sc->kbdc)) { + /* MOUSE ERROR; + * NOTE: we don't return error and continue, pretending + * we have successfully disabled the device. It's OK because + * the interrupt routine will discard any data from the mouse + * hereafter. + */ + log(LOG_ERR, "psm%d: failed to disable the device (psmclose).\n", + unit); + } + + if (get_mouse_status(sc->kbdc, stat, 0, 3) < 3) + log(LOG_DEBUG, "psm%d: failed to get status (psmclose).\n", + unit); + } + + if (!set_controller_command_byte(sc->kbdc, + kbdc_get_device_mask(sc->kbdc), + (command_byte & KBD_KBD_CONTROL_BITS) + | KBD_DISABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + /* CONTROLLER ERROR; + * we shall ignore this error; see the above comment. + */ + log(LOG_ERR, "psm%d: failed to disable the aux port (psmclose).\n", + unit); + } + + /* remove anything left in the output buffer */ + empty_aux_buffer(sc->kbdc, 10); + + /* close is almost always successful */ + sc->state &= ~PSM_OPEN; + kbdc_lock(sc->kbdc, FALSE); + device_unbusy(devclass_get_device(psm_devclass, unit)); + return (0); +} + +static int +tame_mouse(struct psm_softc *sc, mousestatus_t *status, unsigned char *buf) +{ + static unsigned char butmapps2[8] = { + 0, + MOUSE_PS2_BUTTON1DOWN, + MOUSE_PS2_BUTTON2DOWN, + MOUSE_PS2_BUTTON1DOWN | MOUSE_PS2_BUTTON2DOWN, + MOUSE_PS2_BUTTON3DOWN, + MOUSE_PS2_BUTTON1DOWN | MOUSE_PS2_BUTTON3DOWN, + MOUSE_PS2_BUTTON2DOWN | MOUSE_PS2_BUTTON3DOWN, + MOUSE_PS2_BUTTON1DOWN | MOUSE_PS2_BUTTON2DOWN | MOUSE_PS2_BUTTON3DOWN, + }; + static unsigned char butmapmsc[8] = { + MOUSE_MSC_BUTTON1UP | MOUSE_MSC_BUTTON2UP | MOUSE_MSC_BUTTON3UP, + MOUSE_MSC_BUTTON2UP | MOUSE_MSC_BUTTON3UP, + MOUSE_MSC_BUTTON1UP | MOUSE_MSC_BUTTON3UP, + MOUSE_MSC_BUTTON3UP, + MOUSE_MSC_BUTTON1UP | MOUSE_MSC_BUTTON2UP, + MOUSE_MSC_BUTTON2UP, + MOUSE_MSC_BUTTON1UP, + 0, + }; + int mapped; + int i; + + if (sc->mode.level == PSM_LEVEL_BASE) { + mapped = status->button & ~MOUSE_BUTTON4DOWN; + if (status->button & MOUSE_BUTTON4DOWN) + mapped |= MOUSE_BUTTON1DOWN; + status->button = mapped; + buf[0] = MOUSE_PS2_SYNC | butmapps2[mapped & MOUSE_STDBUTTONS]; + i = max(min(status->dx, 255), -256); + if (i < 0) + buf[0] |= MOUSE_PS2_XNEG; + buf[1] = i; + i = max(min(status->dy, 255), -256); + if (i < 0) + buf[0] |= MOUSE_PS2_YNEG; + buf[2] = i; + return MOUSE_PS2_PACKETSIZE; + } else if (sc->mode.level == PSM_LEVEL_STANDARD) { + buf[0] = MOUSE_MSC_SYNC | butmapmsc[status->button & MOUSE_STDBUTTONS]; + i = max(min(status->dx, 255), -256); + buf[1] = i >> 1; + buf[3] = i - buf[1]; + i = max(min(status->dy, 255), -256); + buf[2] = i >> 1; + buf[4] = i - buf[2]; + i = max(min(status->dz, 127), -128); + buf[5] = (i >> 1) & 0x7f; + buf[6] = (i - (i >> 1)) & 0x7f; + buf[7] = (~status->button >> 3) & 0x7f; + return MOUSE_SYS_PACKETSIZE; + } + return sc->inputbytes;; +} + +static int +psmread(dev_t dev, struct uio *uio, int flag) +{ + register struct psm_softc *sc = PSM_SOFTC(PSM_UNIT(dev)); + unsigned char buf[PSM_SMALLBUFSIZE]; + int error = 0; + int s; + int l; + + if ((sc->state & PSM_VALID) == 0) + return EIO; + + /* block until mouse activity occured */ + s = spltty(); + while (sc->queue.count <= 0) { + if (PSM_NBLOCKIO(dev)) { + splx(s); + return EWOULDBLOCK; + } + sc->state |= PSM_ASLP; + error = tsleep((caddr_t) sc, PZERO | PCATCH, "psmrea", 0); + sc->state &= ~PSM_ASLP; + if (error) { + splx(s); + return error; + } else if ((sc->state & PSM_VALID) == 0) { + /* the device disappeared! */ + splx(s); + return EIO; + } + } + splx(s); + + /* copy data to the user land */ + while ((sc->queue.count > 0) && (uio->uio_resid > 0)) { + s = spltty(); + l = min(sc->queue.count, uio->uio_resid); + if (l > sizeof(buf)) + l = sizeof(buf); + if (l > sizeof(sc->queue.buf) - sc->queue.head) { + bcopy(&sc->queue.buf[sc->queue.head], &buf[0], + sizeof(sc->queue.buf) - sc->queue.head); + bcopy(&sc->queue.buf[0], + &buf[sizeof(sc->queue.buf) - sc->queue.head], + l - (sizeof(sc->queue.buf) - sc->queue.head)); + } else { + bcopy(&sc->queue.buf[sc->queue.head], &buf[0], l); + } + sc->queue.count -= l; + sc->queue.head = (sc->queue.head + l) % sizeof(sc->queue.buf); + splx(s); + error = uiomove(buf, l, uio); + if (error) + break; + } + + return error; +} + +static int +block_mouse_data(struct psm_softc *sc, int *c) +{ + int s; + + if (!kbdc_lock(sc->kbdc, TRUE)) + return EIO; + + s = spltty(); + *c = get_controller_command_byte(sc->kbdc); + if ((*c == -1) + || !set_controller_command_byte(sc->kbdc, + kbdc_get_device_mask(sc->kbdc), + KBD_DISABLE_KBD_PORT | KBD_DISABLE_KBD_INT + | KBD_ENABLE_AUX_PORT | KBD_DISABLE_AUX_INT)) { + /* this is CONTROLLER ERROR */ + splx(s); + kbdc_lock(sc->kbdc, FALSE); + return EIO; + } + + /* + * The device may be in the middle of status data transmission. + * The transmission will be interrupted, thus, incomplete status + * data must be discarded. Although the aux interrupt is disabled + * at the keyboard controller level, at most one aux interrupt + * may have already been pending and a data byte is in the + * output buffer; throw it away. Note that the second argument + * to `empty_aux_buffer()' is zero, so that the call will just + * flush the internal queue. + * `psmintr()' will be invoked after `splx()' if an interrupt is + * pending; it will see no data and returns immediately. + */ + empty_aux_buffer(sc->kbdc, 0); /* flush the queue */ + read_aux_data_no_wait(sc->kbdc); /* throw away data if any */ + sc->inputbytes = 0; + splx(s); + + return 0; +} + +static int +unblock_mouse_data(struct psm_softc *sc, int c) +{ + int error = 0; + + /* + * We may have seen a part of status data during `set_mouse_XXX()'. + * they have been queued; flush it. + */ + empty_aux_buffer(sc->kbdc, 0); + + /* restore ports and interrupt */ + if (!set_controller_command_byte(sc->kbdc, + kbdc_get_device_mask(sc->kbdc), + c & (KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS))) { + /* CONTROLLER ERROR; this is serious, we may have + * been left with the inaccessible keyboard and + * the disabled mouse interrupt. + */ + error = EIO; + } + + kbdc_lock(sc->kbdc, FALSE); + return error; +} + +static int +psmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td) +{ + struct psm_softc *sc = PSM_SOFTC(PSM_UNIT(dev)); + mousemode_t mode; + mousestatus_t status; +#if (defined(MOUSE_GETVARS)) + mousevar_t *var; +#endif + mousedata_t *data; + int stat[3]; + int command_byte; + int error = 0; + int s; + + /* Perform IOCTL command */ + switch (cmd) { + + case OLD_MOUSE_GETHWINFO: + s = spltty(); + ((old_mousehw_t *)addr)->buttons = sc->hw.buttons; + ((old_mousehw_t *)addr)->iftype = sc->hw.iftype; + ((old_mousehw_t *)addr)->type = sc->hw.type; + ((old_mousehw_t *)addr)->hwid = sc->hw.hwid & 0x00ff; + splx(s); + break; + + case MOUSE_GETHWINFO: + s = spltty(); + *(mousehw_t *)addr = sc->hw; + if (sc->mode.level == PSM_LEVEL_BASE) + ((mousehw_t *)addr)->model = MOUSE_MODEL_GENERIC; + splx(s); + break; + + case OLD_MOUSE_GETMODE: + s = spltty(); + switch (sc->mode.level) { + case PSM_LEVEL_BASE: + ((old_mousemode_t *)addr)->protocol = MOUSE_PROTO_PS2; + break; + case PSM_LEVEL_STANDARD: + ((old_mousemode_t *)addr)->protocol = MOUSE_PROTO_SYSMOUSE; + break; + case PSM_LEVEL_NATIVE: + ((old_mousemode_t *)addr)->protocol = MOUSE_PROTO_PS2; + break; + } + ((old_mousemode_t *)addr)->rate = sc->mode.rate; + ((old_mousemode_t *)addr)->resolution = sc->mode.resolution; + ((old_mousemode_t *)addr)->accelfactor = sc->mode.accelfactor; + splx(s); + break; + + case MOUSE_GETMODE: + s = spltty(); + *(mousemode_t *)addr = sc->mode; + ((mousemode_t *)addr)->resolution = + MOUSE_RES_LOW - sc->mode.resolution; + switch (sc->mode.level) { + case PSM_LEVEL_BASE: + ((mousemode_t *)addr)->protocol = MOUSE_PROTO_PS2; + ((mousemode_t *)addr)->packetsize = MOUSE_PS2_PACKETSIZE; + break; + case PSM_LEVEL_STANDARD: + ((mousemode_t *)addr)->protocol = MOUSE_PROTO_SYSMOUSE; + ((mousemode_t *)addr)->packetsize = MOUSE_SYS_PACKETSIZE; + ((mousemode_t *)addr)->syncmask[0] = MOUSE_SYS_SYNCMASK; + ((mousemode_t *)addr)->syncmask[1] = MOUSE_SYS_SYNC; + break; + case PSM_LEVEL_NATIVE: + /* FIXME: this isn't quite correct... XXX */ + ((mousemode_t *)addr)->protocol = MOUSE_PROTO_PS2; + break; + } + splx(s); + break; + + case OLD_MOUSE_SETMODE: + case MOUSE_SETMODE: + if (cmd == OLD_MOUSE_SETMODE) { + mode.rate = ((old_mousemode_t *)addr)->rate; + /* + * resolution old I/F new I/F + * default 0 0 + * low 1 -2 + * medium low 2 -3 + * medium high 3 -4 + * high 4 -5 + */ + if (((old_mousemode_t *)addr)->resolution > 0) + mode.resolution = -((old_mousemode_t *)addr)->resolution - 1; + mode.accelfactor = ((old_mousemode_t *)addr)->accelfactor; + mode.level = -1; + } else { + mode = *(mousemode_t *)addr; + } + + /* adjust and validate parameters. */ + if (mode.rate > UCHAR_MAX) + return EINVAL; + if (mode.rate == 0) + mode.rate = sc->dflt_mode.rate; + else if (mode.rate == -1) + /* don't change the current setting */ + ; + else if (mode.rate < 0) + return EINVAL; + if (mode.resolution >= UCHAR_MAX) + return EINVAL; + if (mode.resolution >= 200) + mode.resolution = MOUSE_RES_HIGH; + else if (mode.resolution >= 100) + mode.resolution = MOUSE_RES_MEDIUMHIGH; + else if (mode.resolution >= 50) + mode.resolution = MOUSE_RES_MEDIUMLOW; + else if (mode.resolution > 0) + mode.resolution = MOUSE_RES_LOW; + if (mode.resolution == MOUSE_RES_DEFAULT) + mode.resolution = sc->dflt_mode.resolution; + else if (mode.resolution == -1) + /* don't change the current setting */ + ; + else if (mode.resolution < 0) /* MOUSE_RES_LOW/MEDIUM/HIGH */ + mode.resolution = MOUSE_RES_LOW - mode.resolution; + if (mode.level == -1) + /* don't change the current setting */ + mode.level = sc->mode.level; + else if ((mode.level < PSM_LEVEL_MIN) || (mode.level > PSM_LEVEL_MAX)) + return EINVAL; + if (mode.accelfactor == -1) + /* don't change the current setting */ + mode.accelfactor = sc->mode.accelfactor; + else if (mode.accelfactor < 0) + return EINVAL; + + /* don't allow anybody to poll the keyboard controller */ + error = block_mouse_data(sc, &command_byte); + if (error) + return error; + + /* set mouse parameters */ + if (mode.rate > 0) + mode.rate = set_mouse_sampling_rate(sc->kbdc, mode.rate); + if (mode.resolution >= 0) + mode.resolution = set_mouse_resolution(sc->kbdc, mode.resolution); + set_mouse_scaling(sc->kbdc, 1); + get_mouse_status(sc->kbdc, stat, 0, 3); + + s = spltty(); + sc->mode.rate = mode.rate; + sc->mode.resolution = mode.resolution; + sc->mode.accelfactor = mode.accelfactor; + sc->mode.level = mode.level; + splx(s); + + unblock_mouse_data(sc, command_byte); + break; + + case MOUSE_GETLEVEL: + *(int *)addr = sc->mode.level; + break; + + case MOUSE_SETLEVEL: + if ((*(int *)addr < PSM_LEVEL_MIN) || (*(int *)addr > PSM_LEVEL_MAX)) + return EINVAL; + sc->mode.level = *(int *)addr; + break; + + case MOUSE_GETSTATUS: + s = spltty(); + status = sc->status; + sc->status.flags = 0; + sc->status.obutton = sc->status.button; + sc->status.button = 0; + sc->status.dx = 0; + sc->status.dy = 0; + sc->status.dz = 0; + splx(s); + *(mousestatus_t *)addr = status; + break; + +#if (defined(MOUSE_GETVARS)) + case MOUSE_GETVARS: + var = (mousevar_t *)addr; + bzero(var, sizeof(*var)); + s = spltty(); + var->var[0] = MOUSE_VARS_PS2_SIG; + var->var[1] = sc->config; + var->var[2] = sc->flags; + splx(s); + break; + + case MOUSE_SETVARS: + return ENODEV; +#endif /* MOUSE_GETVARS */ + + case MOUSE_READSTATE: + case MOUSE_READDATA: + data = (mousedata_t *)addr; + if (data->len > sizeof(data->buf)/sizeof(data->buf[0])) + return EINVAL; + + error = block_mouse_data(sc, &command_byte); + if (error) + return error; + if ((data->len = get_mouse_status(sc->kbdc, data->buf, + (cmd == MOUSE_READDATA) ? 1 : 0, data->len)) <= 0) + error = EIO; + unblock_mouse_data(sc, command_byte); + break; + +#if (defined(MOUSE_SETRESOLUTION)) + case MOUSE_SETRESOLUTION: + mode.resolution = *(int *)addr; + if (mode.resolution >= UCHAR_MAX) + return EINVAL; + else if (mode.resolution >= 200) + mode.resolution = MOUSE_RES_HIGH; + else if (mode.resolution >= 100) + mode.resolution = MOUSE_RES_MEDIUMHIGH; + else if (mode.resolution >= 50) + mode.resolution = MOUSE_RES_MEDIUMLOW; + else if (mode.resolution > 0) + mode.resolution = MOUSE_RES_LOW; + if (mode.resolution == MOUSE_RES_DEFAULT) + mode.resolution = sc->dflt_mode.resolution; + else if (mode.resolution == -1) + mode.resolution = sc->mode.resolution; + else if (mode.resolution < 0) /* MOUSE_RES_LOW/MEDIUM/HIGH */ + mode.resolution = MOUSE_RES_LOW - mode.resolution; + + error = block_mouse_data(sc, &command_byte); + if (error) + return error; + sc->mode.resolution = set_mouse_resolution(sc->kbdc, mode.resolution); + if (sc->mode.resolution != mode.resolution) + error = EIO; + unblock_mouse_data(sc, command_byte); + break; +#endif /* MOUSE_SETRESOLUTION */ + +#if (defined(MOUSE_SETRATE)) + case MOUSE_SETRATE: + mode.rate = *(int *)addr; + if (mode.rate > UCHAR_MAX) + return EINVAL; + if (mode.rate == 0) + mode.rate = sc->dflt_mode.rate; + else if (mode.rate < 0) + mode.rate = sc->mode.rate; + + error = block_mouse_data(sc, &command_byte); + if (error) + return error; + sc->mode.rate = set_mouse_sampling_rate(sc->kbdc, mode.rate); + if (sc->mode.rate != mode.rate) + error = EIO; + unblock_mouse_data(sc, command_byte); + break; +#endif /* MOUSE_SETRATE */ + +#if (defined(MOUSE_SETSCALING)) + case MOUSE_SETSCALING: + if ((*(int *)addr <= 0) || (*(int *)addr > 2)) + return EINVAL; + + error = block_mouse_data(sc, &command_byte); + if (error) + return error; + if (!set_mouse_scaling(sc->kbdc, *(int *)addr)) + error = EIO; + unblock_mouse_data(sc, command_byte); + break; +#endif /* MOUSE_SETSCALING */ + +#if (defined(MOUSE_GETHWID)) + case MOUSE_GETHWID: + error = block_mouse_data(sc, &command_byte); + if (error) + return error; + sc->hw.hwid &= ~0x00ff; + sc->hw.hwid |= get_aux_id(sc->kbdc); + *(int *)addr = sc->hw.hwid & 0x00ff; + unblock_mouse_data(sc, command_byte); + break; +#endif /* MOUSE_GETHWID */ + + default: + return ENOTTY; + } + + return error; +} + +static void +psmtimeout(void *arg) +{ + struct psm_softc *sc; + int s; + + sc = (struct psm_softc *)arg; + s = spltty(); + if (sc->watchdog && kbdc_lock(sc->kbdc, TRUE)) { + if (verbose >= 4) + log(LOG_DEBUG, "psm%d: lost interrupt?\n", sc->unit); + psmintr(sc); + kbdc_lock(sc->kbdc, FALSE); + } + sc->watchdog = TRUE; + splx(s); + sc->callout = timeout(psmtimeout, (void *)(uintptr_t)sc, hz); +} + +static void +psmintr(void *arg) +{ + /* + * the table to turn PS/2 mouse button bits (MOUSE_PS2_BUTTON?DOWN) + * into `mousestatus' button bits (MOUSE_BUTTON?DOWN). + */ + static int butmap[8] = { + 0, + MOUSE_BUTTON1DOWN, + MOUSE_BUTTON3DOWN, + MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN, + MOUSE_BUTTON2DOWN, + MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN, + MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN, + MOUSE_BUTTON1DOWN | MOUSE_BUTTON2DOWN | MOUSE_BUTTON3DOWN + }; + static int butmap_versapad[8] = { + 0, + MOUSE_BUTTON3DOWN, + 0, + MOUSE_BUTTON3DOWN, + MOUSE_BUTTON1DOWN, + MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN, + MOUSE_BUTTON1DOWN, + MOUSE_BUTTON1DOWN | MOUSE_BUTTON3DOWN + }; + register struct psm_softc *sc = arg; + mousestatus_t ms; + struct timeval tv; + int x, y, z; + int c; + int l; + int x0, y0; + + /* read until there is nothing to read */ + while((c = read_aux_data_no_wait(sc->kbdc)) != -1) { + + /* discard the byte if the device is not open */ + if ((sc->state & PSM_OPEN) == 0) + continue; + + getmicrouptime(&tv); + if ((sc->inputbytes > 0) && timevalcmp(&tv, &sc->inputtimeout, >)) { + log(LOG_DEBUG, "psmintr: delay too long; resetting byte count\n"); + sc->inputbytes = 0; + sc->syncerrors = 0; + } + sc->inputtimeout.tv_sec = PSM_INPUT_TIMEOUT/1000000; + sc->inputtimeout.tv_usec = PSM_INPUT_TIMEOUT%1000000; + timevaladd(&sc->inputtimeout, &tv); + + sc->ipacket[sc->inputbytes++] = c; + if (sc->inputbytes < sc->mode.packetsize) + continue; + +#if 0 + log(LOG_DEBUG, "psmintr: %02x %02x %02x %02x %02x %02x\n", + sc->ipacket[0], sc->ipacket[1], sc->ipacket[2], + sc->ipacket[3], sc->ipacket[4], sc->ipacket[5]); +#endif + + c = sc->ipacket[0]; + + if ((c & sc->mode.syncmask[0]) != sc->mode.syncmask[1]) { + log(LOG_DEBUG, "psmintr: out of sync (%04x != %04x).\n", + c & sc->mode.syncmask[0], sc->mode.syncmask[1]); + ++sc->syncerrors; + if (sc->syncerrors < sc->mode.packetsize) { + log(LOG_DEBUG, "psmintr: discard a byte (%d).\n", sc->syncerrors); + --sc->inputbytes; + bcopy(&sc->ipacket[1], &sc->ipacket[0], sc->inputbytes); + } else if (sc->syncerrors == sc->mode.packetsize) { + log(LOG_DEBUG, "psmintr: re-enable the mouse.\n"); + sc->inputbytes = 0; + disable_aux_dev(sc->kbdc); + enable_aux_dev(sc->kbdc); + } else if (sc->syncerrors < PSM_SYNCERR_THRESHOLD1) { + log(LOG_DEBUG, "psmintr: discard a byte (%d).\n", sc->syncerrors); + --sc->inputbytes; + bcopy(&sc->ipacket[1], &sc->ipacket[0], sc->inputbytes); + } else if (sc->syncerrors >= PSM_SYNCERR_THRESHOLD1) { + log(LOG_DEBUG, "psmintr: reset the mouse.\n"); + reinitialize(sc, TRUE); + } + continue; + } + + /* + * A kludge for Kensington device! + * The MSB of the horizontal count appears to be stored in + * a strange place. + */ + if (sc->hw.model == MOUSE_MODEL_THINK) + sc->ipacket[1] |= (c & MOUSE_PS2_XOVERFLOW) ? 0x80 : 0; + + /* ignore the overflow bits... */ + x = (c & MOUSE_PS2_XNEG) ? sc->ipacket[1] - 256 : sc->ipacket[1]; + y = (c & MOUSE_PS2_YNEG) ? sc->ipacket[2] - 256 : sc->ipacket[2]; + z = 0; + ms.obutton = sc->button; /* previous button state */ + ms.button = butmap[c & MOUSE_PS2_BUTTONS]; + /* `tapping' action */ + if (sc->config & PSM_CONFIG_FORCETAP) + ms.button |= ((c & MOUSE_PS2_TAP)) ? 0 : MOUSE_BUTTON4DOWN; + + switch (sc->hw.model) { + + case MOUSE_MODEL_EXPLORER: + /* + * b7 b6 b5 b4 b3 b2 b1 b0 + * byte 1: oy ox sy sx 1 M R L + * byte 2: x x x x x x x x + * byte 3: y y y y y y y y + * byte 4: * * S2 S1 s d2 d1 d0 + * + * L, M, R, S1, S2: left, middle, right and side buttons + * s: wheel data sign bit + * d2-d0: wheel data + */ + z = (sc->ipacket[3] & MOUSE_EXPLORER_ZNEG) + ? (sc->ipacket[3] & 0x0f) - 16 : (sc->ipacket[3] & 0x0f); + ms.button |= (sc->ipacket[3] & MOUSE_EXPLORER_BUTTON4DOWN) + ? MOUSE_BUTTON4DOWN : 0; + ms.button |= (sc->ipacket[3] & MOUSE_EXPLORER_BUTTON5DOWN) + ? MOUSE_BUTTON5DOWN : 0; + break; + + case MOUSE_MODEL_INTELLI: + case MOUSE_MODEL_NET: + /* wheel data is in the fourth byte */ + z = (char)sc->ipacket[3]; + /* some mice may send 7 when there is no Z movement?! XXX */ + if ((z >= 7) || (z <= -7)) + z = 0; + /* some compatible mice have additional buttons */ + ms.button |= (c & MOUSE_PS2INTELLI_BUTTON4DOWN) + ? MOUSE_BUTTON4DOWN : 0; + ms.button |= (c & MOUSE_PS2INTELLI_BUTTON5DOWN) + ? MOUSE_BUTTON5DOWN : 0; + break; + + case MOUSE_MODEL_MOUSEMANPLUS: + /* + * PS2++ protocl packet + * + * b7 b6 b5 b4 b3 b2 b1 b0 + * byte 1: * 1 p3 p2 1 * * * + * byte 2: c1 c2 p1 p0 d1 d0 1 0 + * + * p3-p0: packet type + * c1, c2: c1 & c2 == 1, if p2 == 0 + * c1 & c2 == 0, if p2 == 1 + * + * packet type: 0 (device type) + * See comments in enable_mmanplus() below. + * + * packet type: 1 (wheel data) + * + * b7 b6 b5 b4 b3 b2 b1 b0 + * byte 3: h * B5 B4 s d2 d1 d0 + * + * h: 1, if horizontal roller data + * 0, if vertical roller data + * B4, B5: button 4 and 5 + * s: sign bit + * d2-d0: roller data + * + * packet type: 2 (reserved) + */ + if (((c & MOUSE_PS2PLUS_SYNCMASK) == MOUSE_PS2PLUS_SYNC) + && (abs(x) > 191) + && MOUSE_PS2PLUS_CHECKBITS(sc->ipacket)) { + /* the extended data packet encodes button and wheel events */ + switch (MOUSE_PS2PLUS_PACKET_TYPE(sc->ipacket)) { + case 1: + /* wheel data packet */ + x = y = 0; + if (sc->ipacket[2] & 0x80) { + /* horizontal roller count - ignore it XXX*/ + } else { + /* vertical roller count */ + z = (sc->ipacket[2] & MOUSE_PS2PLUS_ZNEG) + ? (sc->ipacket[2] & 0x0f) - 16 + : (sc->ipacket[2] & 0x0f); + } + ms.button |= (sc->ipacket[2] & MOUSE_PS2PLUS_BUTTON4DOWN) + ? MOUSE_BUTTON4DOWN : 0; + ms.button |= (sc->ipacket[2] & MOUSE_PS2PLUS_BUTTON5DOWN) + ? MOUSE_BUTTON5DOWN : 0; + break; + case 2: + /* this packet type is reserved by Logitech... */ + /* + * IBM ScrollPoint Mouse uses this packet type to + * encode both vertical and horizontal scroll movement. + */ + x = y = 0; + /* horizontal count */ + if (sc->ipacket[2] & 0x0f) + z = (sc->ipacket[2] & MOUSE_SPOINT_WNEG) ? -2 : 2; + /* vertical count */ + if (sc->ipacket[2] & 0xf0) + z = (sc->ipacket[2] & MOUSE_SPOINT_ZNEG) ? -1 : 1; +#if 0 + /* vertical count */ + z = (sc->ipacket[2] & MOUSE_SPOINT_ZNEG) + ? ((sc->ipacket[2] >> 4) & 0x0f) - 16 + : ((sc->ipacket[2] >> 4) & 0x0f); + /* horizontal count */ + w = (sc->ipacket[2] & MOUSE_SPOINT_WNEG) + ? (sc->ipacket[2] & 0x0f) - 16 + : (sc->ipacket[2] & 0x0f); +#endif + break; + case 0: + /* device type packet - shouldn't happen */ + /* FALLTHROUGH */ + default: + x = y = 0; + ms.button = ms.obutton; + if (bootverbose) + log(LOG_DEBUG, "psmintr: unknown PS2++ packet type %d: " + "0x%02x 0x%02x 0x%02x\n", + MOUSE_PS2PLUS_PACKET_TYPE(sc->ipacket), + sc->ipacket[0], sc->ipacket[1], sc->ipacket[2]); + break; + } + } else { + /* preserve button states */ + ms.button |= ms.obutton & MOUSE_EXTBUTTONS; + } + break; + + case MOUSE_MODEL_GLIDEPOINT: + /* `tapping' action */ + ms.button |= ((c & MOUSE_PS2_TAP)) ? 0 : MOUSE_BUTTON4DOWN; + break; + + case MOUSE_MODEL_NETSCROLL: + /* three addtional bytes encode buttons and wheel events */ + ms.button |= (sc->ipacket[3] & MOUSE_PS2_BUTTON3DOWN) + ? MOUSE_BUTTON4DOWN : 0; + ms.button |= (sc->ipacket[3] & MOUSE_PS2_BUTTON1DOWN) + ? MOUSE_BUTTON5DOWN : 0; + z = (sc->ipacket[3] & MOUSE_PS2_XNEG) + ? sc->ipacket[4] - 256 : sc->ipacket[4]; + break; + + case MOUSE_MODEL_THINK: + /* the fourth button state in the first byte */ + ms.button |= (c & MOUSE_PS2_TAP) ? MOUSE_BUTTON4DOWN : 0; + break; + + case MOUSE_MODEL_VERSAPAD: + /* VersaPad PS/2 absolute mode message format + * + * [packet1] 7 6 5 4 3 2 1 0(LSB) + * ipacket[0]: 1 1 0 A 1 L T R + * ipacket[1]: H7 H6 H5 H4 H3 H2 H1 H0 + * ipacket[2]: V7 V6 V5 V4 V3 V2 V1 V0 + * ipacket[3]: 1 1 1 A 1 L T R + * ipacket[4]:V11 V10 V9 V8 H11 H10 H9 H8 + * ipacket[5]: 0 P6 P5 P4 P3 P2 P1 P0 + * + * [note] + * R: right physical mouse button (1=on) + * T: touch pad virtual button (1=tapping) + * L: left physical mouse button (1=on) + * A: position data is valid (1=valid) + * H: horizontal data (12bit signed integer. H11 is sign bit.) + * V: vertical data (12bit signed integer. V11 is sign bit.) + * P: pressure data + * + * Tapping is mapped to MOUSE_BUTTON4. + */ + ms.button = butmap_versapad[c & MOUSE_PS2VERSA_BUTTONS]; + ms.button |= (c & MOUSE_PS2VERSA_TAP) ? MOUSE_BUTTON4DOWN : 0; + x = y = 0; + if (c & MOUSE_PS2VERSA_IN_USE) { + x0 = sc->ipacket[1] | (((sc->ipacket[4]) & 0x0f) << 8); + y0 = sc->ipacket[2] | (((sc->ipacket[4]) & 0xf0) << 4); + if (x0 & 0x800) + x0 -= 0x1000; + if (y0 & 0x800) + y0 -= 0x1000; + if (sc->flags & PSM_FLAGS_FINGERDOWN) { + x = sc->xold - x0; + y = y0 - sc->yold; + if (x < 0) /* XXX */ + x++; + else if (x) + x--; + if (y < 0) + y++; + else if (y) + y--; + } else { + sc->flags |= PSM_FLAGS_FINGERDOWN; + } + sc->xold = x0; + sc->yold = y0; + } else { + sc->flags &= ~PSM_FLAGS_FINGERDOWN; + } + c = ((x < 0) ? MOUSE_PS2_XNEG : 0) + | ((y < 0) ? MOUSE_PS2_YNEG : 0); + break; + + case MOUSE_MODEL_4D: + /* + * b7 b6 b5 b4 b3 b2 b1 b0 + * byte 1: s2 d2 s1 d1 1 M R L + * byte 2: sx x x x x x x x + * byte 3: sy y y y y y y y + * + * s1: wheel 1 direction + * d1: wheel 1 data + * s2: wheel 2 direction + * d2: wheel 2 data + */ + x = (sc->ipacket[1] & 0x80) ? sc->ipacket[1] - 256 : sc->ipacket[1]; + y = (sc->ipacket[2] & 0x80) ? sc->ipacket[2] - 256 : sc->ipacket[2]; + switch (c & MOUSE_4D_WHEELBITS) { + case 0x10: + z = 1; + break; + case 0x30: + z = -1; + break; + case 0x40: /* 2nd wheel turning right XXX */ + z = 2; + break; + case 0xc0: /* 2nd wheel turning left XXX */ + z = -2; + break; + } + break; + + case MOUSE_MODEL_4DPLUS: + if ((x < 16 - 256) && (y < 16 - 256)) { + /* + * b7 b6 b5 b4 b3 b2 b1 b0 + * byte 1: 0 0 1 1 1 M R L + * byte 2: 0 0 0 0 1 0 0 0 + * byte 3: 0 0 0 0 S s d1 d0 + * + * L, M, R, S: left, middle, right and side buttons + * s: wheel data sign bit + * d1-d0: wheel data + */ + x = y = 0; + if (sc->ipacket[2] & MOUSE_4DPLUS_BUTTON4DOWN) + ms.button |= MOUSE_BUTTON4DOWN; + z = (sc->ipacket[2] & MOUSE_4DPLUS_ZNEG) + ? ((sc->ipacket[2] & 0x07) - 8) + : (sc->ipacket[2] & 0x07) ; + } else { + /* preserve previous button states */ + ms.button |= ms.obutton & MOUSE_EXTBUTTONS; + } + break; + + case MOUSE_MODEL_GENERIC: + default: + break; + } + + /* scale values */ + if (sc->mode.accelfactor >= 1) { + if (x != 0) { + x = x * x / sc->mode.accelfactor; + if (x == 0) + x = 1; + if (c & MOUSE_PS2_XNEG) + x = -x; + } + if (y != 0) { + y = y * y / sc->mode.accelfactor; + if (y == 0) + y = 1; + if (c & MOUSE_PS2_YNEG) + y = -y; + } + } + + ms.dx = x; + ms.dy = y; + ms.dz = z; + ms.flags = ((x || y || z) ? MOUSE_POSCHANGED : 0) + | (ms.obutton ^ ms.button); + + if (sc->mode.level < PSM_LEVEL_NATIVE) + sc->inputbytes = tame_mouse(sc, &ms, sc->ipacket); + + sc->status.flags |= ms.flags; + sc->status.dx += ms.dx; + sc->status.dy += ms.dy; + sc->status.dz += ms.dz; + sc->status.button = ms.button; + sc->button = ms.button; + + sc->watchdog = FALSE; + + /* queue data */ + if (sc->queue.count + sc->inputbytes < sizeof(sc->queue.buf)) { + l = min(sc->inputbytes, sizeof(sc->queue.buf) - sc->queue.tail); + bcopy(&sc->ipacket[0], &sc->queue.buf[sc->queue.tail], l); + if (sc->inputbytes > l) + bcopy(&sc->ipacket[l], &sc->queue.buf[0], sc->inputbytes - l); + sc->queue.tail = + (sc->queue.tail + sc->inputbytes) % sizeof(sc->queue.buf); + sc->queue.count += sc->inputbytes; + } + sc->inputbytes = 0; + + if (sc->state & PSM_ASLP) { + sc->state &= ~PSM_ASLP; + wakeup((caddr_t) sc); + } + selwakeup(&sc->rsel); + } +} + +static int +psmpoll(dev_t dev, int events, struct thread *td) +{ + struct psm_softc *sc = PSM_SOFTC(PSM_UNIT(dev)); + int s; + int revents = 0; + + /* Return true if a mouse event available */ + s = spltty(); + if (events & (POLLIN | POLLRDNORM)) { + if (sc->queue.count > 0) + revents |= events & (POLLIN | POLLRDNORM); + else + selrecord(td, &sc->rsel); + } + splx(s); + + return (revents); +} + +/* vendor/model specific routines */ + +static int mouse_id_proc1(KBDC kbdc, int res, int scale, int *status) +{ + if (set_mouse_resolution(kbdc, res) != res) + return FALSE; + if (set_mouse_scaling(kbdc, scale) + && set_mouse_scaling(kbdc, scale) + && set_mouse_scaling(kbdc, scale) + && (get_mouse_status(kbdc, status, 0, 3) >= 3)) + return TRUE; + return FALSE; +} + +static int +mouse_ext_command(KBDC kbdc, int command) +{ + int c; + + c = (command >> 6) & 0x03; + if (set_mouse_resolution(kbdc, c) != c) + return FALSE; + c = (command >> 4) & 0x03; + if (set_mouse_resolution(kbdc, c) != c) + return FALSE; + c = (command >> 2) & 0x03; + if (set_mouse_resolution(kbdc, c) != c) + return FALSE; + c = (command >> 0) & 0x03; + if (set_mouse_resolution(kbdc, c) != c) + return FALSE; + return TRUE; +} + +#if notyet +/* Logitech MouseMan Cordless II */ +static int +enable_lcordless(struct psm_softc *sc) +{ + int status[3]; + int ch; + + if (!mouse_id_proc1(sc->kbdc, PSMD_RES_HIGH, 2, status)) + return FALSE; + if (status[1] == PSMD_RES_HIGH) + return FALSE; + ch = (status[0] & 0x07) - 1; /* channel # */ + if ((ch <= 0) || (ch > 4)) + return FALSE; + /* + * status[1]: always one? + * status[2]: battery status? (0-100) + */ + return TRUE; +} +#endif /* notyet */ + +/* Genius NetScroll Mouse, MouseSystems SmartScroll Mouse */ +static int +enable_groller(struct psm_softc *sc) +{ + int status[3]; + + /* + * The special sequence to enable the fourth button and the + * roller. Immediately after this sequence check status bytes. + * if the mouse is NetScroll, the second and the third bytes are + * '3' and 'D'. + */ + + /* + * If the mouse is an ordinary PS/2 mouse, the status bytes should + * look like the following. + * + * byte 1 bit 7 always 0 + * bit 6 stream mode (0) + * bit 5 disabled (0) + * bit 4 1:1 scaling (0) + * bit 3 always 0 + * bit 0-2 button status + * byte 2 resolution (PSMD_RES_HIGH) + * byte 3 report rate (?) + */ + + if (!mouse_id_proc1(sc->kbdc, PSMD_RES_HIGH, 1, status)) + return FALSE; + if ((status[1] != '3') || (status[2] != 'D')) + return FALSE; + /* FIXME: SmartScroll Mouse has 5 buttons! XXX */ + sc->hw.buttons = 4; + return TRUE; +} + +/* Genius NetMouse/NetMouse Pro, ASCII Mie Mouse, NetScroll Optical */ +static int +enable_gmouse(struct psm_softc *sc) +{ + int status[3]; + + /* + * The special sequence to enable the middle, "rubber" button. + * Immediately after this sequence check status bytes. + * if the mouse is NetMouse, NetMouse Pro, or ASCII MIE Mouse, + * the second and the third bytes are '3' and 'U'. + * NOTE: NetMouse reports that it has three buttons although it has + * two buttons and a rubber button. NetMouse Pro and MIE Mouse + * say they have three buttons too and they do have a button on the + * side... + */ + if (!mouse_id_proc1(sc->kbdc, PSMD_RES_HIGH, 1, status)) + return FALSE; + if ((status[1] != '3') || (status[2] != 'U')) + return FALSE; + return TRUE; +} + +/* ALPS GlidePoint */ +static int +enable_aglide(struct psm_softc *sc) +{ + int status[3]; + + /* + * The special sequence to obtain ALPS GlidePoint specific + * information. Immediately after this sequence, status bytes will + * contain something interesting. + * NOTE: ALPS produces several models of GlidePoint. Some of those + * do not respond to this sequence, thus, cannot be detected this way. + */ + if (set_mouse_sampling_rate(sc->kbdc, 100) != 100) + return FALSE; + if (!mouse_id_proc1(sc->kbdc, PSMD_RES_LOW, 2, status)) + return FALSE; + if ((status[1] == PSMD_RES_LOW) || (status[2] == 100)) + return FALSE; + return TRUE; +} + +/* Kensington ThinkingMouse/Trackball */ +static int +enable_kmouse(struct psm_softc *sc) +{ + static unsigned char rate[] = { 20, 60, 40, 20, 20, 60, 40, 20, 20 }; + KBDC kbdc = sc->kbdc; + int status[3]; + int id1; + int id2; + int i; + + id1 = get_aux_id(kbdc); + if (set_mouse_sampling_rate(kbdc, 10) != 10) + return FALSE; + /* + * The device is now in the native mode? It returns a different + * ID value... + */ + id2 = get_aux_id(kbdc); + if ((id1 == id2) || (id2 != 2)) + return FALSE; + + if (set_mouse_resolution(kbdc, PSMD_RES_LOW) != PSMD_RES_LOW) + return FALSE; +#if PSM_DEBUG >= 2 + /* at this point, resolution is LOW, sampling rate is 10/sec */ + if (get_mouse_status(kbdc, status, 0, 3) < 3) + return FALSE; +#endif + + /* + * The special sequence to enable the third and fourth buttons. + * Otherwise they behave like the first and second buttons. + */ + for (i = 0; i < sizeof(rate)/sizeof(rate[0]); ++i) { + if (set_mouse_sampling_rate(kbdc, rate[i]) != rate[i]) + return FALSE; + } + + /* + * At this point, the device is using default resolution and + * sampling rate for the native mode. + */ + if (get_mouse_status(kbdc, status, 0, 3) < 3) + return FALSE; + if ((status[1] == PSMD_RES_LOW) || (status[2] == rate[i - 1])) + return FALSE; + + /* the device appears be enabled by this sequence, diable it for now */ + disable_aux_dev(kbdc); + empty_aux_buffer(kbdc, 5); + + return TRUE; +} + +/* Logitech MouseMan+/FirstMouse+, IBM ScrollPoint Mouse */ +static int +enable_mmanplus(struct psm_softc *sc) +{ + KBDC kbdc = sc->kbdc; + int data[3]; + + /* the special sequence to enable the fourth button and the roller. */ + /* + * NOTE: for ScrollPoint to respond correctly, the SET_RESOLUTION + * must be called exactly three times since the last RESET command + * before this sequence. XXX + */ + if (!set_mouse_scaling(kbdc, 1)) + return FALSE; + if (!mouse_ext_command(kbdc, 0x39) || !mouse_ext_command(kbdc, 0xdb)) + return FALSE; + if (get_mouse_status(kbdc, data, 1, 3) < 3) + return FALSE; + + /* + * PS2++ protocl, packet type 0 + * + * b7 b6 b5 b4 b3 b2 b1 b0 + * byte 1: * 1 p3 p2 1 * * * + * byte 2: 1 1 p1 p0 m1 m0 1 0 + * byte 3: m7 m6 m5 m4 m3 m2 m1 m0 + * + * p3-p0: packet type: 0 + * m7-m0: model ID: MouseMan+:0x50, FirstMouse+:0x51, ScrollPoint:0x58... + */ + /* check constant bits */ + if ((data[0] & MOUSE_PS2PLUS_SYNCMASK) != MOUSE_PS2PLUS_SYNC) + return FALSE; + if ((data[1] & 0xc3) != 0xc2) + return FALSE; + /* check d3-d0 in byte 2 */ + if (!MOUSE_PS2PLUS_CHECKBITS(data)) + return FALSE; + /* check p3-p0 */ + if (MOUSE_PS2PLUS_PACKET_TYPE(data) != 0) + return FALSE; + + sc->hw.hwid &= 0x00ff; + sc->hw.hwid |= data[2] << 8; /* save model ID */ + + /* + * MouseMan+ (or FirstMouse+) is now in its native mode, in which + * the wheel and the fourth button events are encoded in the + * special data packet. The mouse may be put in the IntelliMouse mode + * if it is initialized by the IntelliMouse's method. + */ + return TRUE; +} + +/* MS IntelliMouse Explorer */ +static int +enable_msexplorer(struct psm_softc *sc) +{ + static unsigned char rate0[] = { 200, 100, 80, }; + static unsigned char rate1[] = { 200, 200, 80, }; + KBDC kbdc = sc->kbdc; + int id; + int i; + + /* the special sequence to enable the extra buttons and the roller. */ + for (i = 0; i < sizeof(rate1)/sizeof(rate1[0]); ++i) { + if (set_mouse_sampling_rate(kbdc, rate1[i]) != rate1[i]) + return FALSE; + } + /* the device will give the genuine ID only after the above sequence */ + id = get_aux_id(kbdc); + if (id != PSM_EXPLORER_ID) + return FALSE; + + sc->hw.hwid = id; + sc->hw.buttons = 5; /* IntelliMouse Explorer XXX */ + + /* + * XXX: this is a kludge to fool some KVM switch products + * which think they are clever enough to know the 4-byte IntelliMouse + * protocol, and assume any other protocols use 3-byte packets. + * They don't convey 4-byte data packets from the IntelliMouse Explorer + * correctly to the host computer because of this! + * The following sequence is actually IntelliMouse's "wake up" + * sequence; it will make the KVM think the mouse is IntelliMouse + * when it is in fact IntelliMouse Explorer. + */ + for (i = 0; i < sizeof(rate0)/sizeof(rate0[0]); ++i) { + if (set_mouse_sampling_rate(kbdc, rate0[i]) != rate0[i]) + break; + } + id = get_aux_id(kbdc); + + return TRUE; +} + +/* MS IntelliMouse */ +static int +enable_msintelli(struct psm_softc *sc) +{ + /* + * Logitech MouseMan+ and FirstMouse+ will also respond to this + * probe routine and act like IntelliMouse. + */ + + static unsigned char rate[] = { 200, 100, 80, }; + KBDC kbdc = sc->kbdc; + int id; + int i; + + /* the special sequence to enable the third button and the roller. */ + for (i = 0; i < sizeof(rate)/sizeof(rate[0]); ++i) { + if (set_mouse_sampling_rate(kbdc, rate[i]) != rate[i]) + return FALSE; + } + /* the device will give the genuine ID only after the above sequence */ + id = get_aux_id(kbdc); + if (id != PSM_INTELLI_ID) + return FALSE; + + sc->hw.hwid = id; + sc->hw.buttons = 3; + + return TRUE; +} + +/* A4 Tech 4D Mouse */ +static int +enable_4dmouse(struct psm_softc *sc) +{ + /* + * Newer wheel mice from A4 Tech may use the 4D+ protocol. + */ + + static unsigned char rate[] = { 200, 100, 80, 60, 40, 20 }; + KBDC kbdc = sc->kbdc; + int id; + int i; + + for (i = 0; i < sizeof(rate)/sizeof(rate[0]); ++i) { + if (set_mouse_sampling_rate(kbdc, rate[i]) != rate[i]) + return FALSE; + } + id = get_aux_id(kbdc); + /* + * WinEasy 4D, 4 Way Scroll 4D: 6 + * Cable-Free 4D: 8 (4DPLUS) + * WinBest 4D+, 4 Way Scroll 4D+: 8 (4DPLUS) + */ + if (id != PSM_4DMOUSE_ID) + return FALSE; + + sc->hw.hwid = id; + sc->hw.buttons = 3; /* XXX some 4D mice have 4? */ + + return TRUE; +} + +/* A4 Tech 4D+ Mouse */ +static int +enable_4dplus(struct psm_softc *sc) +{ + /* + * Newer wheel mice from A4 Tech seem to use this protocol. + * Older models are recognized as either 4D Mouse or IntelliMouse. + */ + KBDC kbdc = sc->kbdc; + int id; + + /* + * enable_4dmouse() already issued the following ID sequence... + static unsigned char rate[] = { 200, 100, 80, 60, 40, 20 }; + int i; + + for (i = 0; i < sizeof(rate)/sizeof(rate[0]); ++i) { + if (set_mouse_sampling_rate(kbdc, rate[i]) != rate[i]) + return FALSE; + } + */ + + id = get_aux_id(kbdc); + if (id != PSM_4DPLUS_ID) + return FALSE; + + sc->hw.hwid = id; + sc->hw.buttons = 4; /* XXX */ + + return TRUE; +} + +/* Interlink electronics VersaPad */ +static int +enable_versapad(struct psm_softc *sc) +{ + KBDC kbdc = sc->kbdc; + int data[3]; + + set_mouse_resolution(kbdc, PSMD_RES_MEDIUM_HIGH); /* set res. 2 */ + set_mouse_sampling_rate(kbdc, 100); /* set rate 100 */ + set_mouse_scaling(kbdc, 1); /* set scale 1:1 */ + set_mouse_scaling(kbdc, 1); /* set scale 1:1 */ + set_mouse_scaling(kbdc, 1); /* set scale 1:1 */ + set_mouse_scaling(kbdc, 1); /* set scale 1:1 */ + if (get_mouse_status(kbdc, data, 0, 3) < 3) /* get status */ + return FALSE; + if (data[2] != 0xa || data[1] != 0 ) /* rate == 0xa && res. == 0 */ + return FALSE; + set_mouse_scaling(kbdc, 1); /* set scale 1:1 */ + + sc->config |= PSM_CONFIG_HOOKRESUME | PSM_CONFIG_INITAFTERSUSPEND; + + return TRUE; /* PS/2 absolute mode */ +} + +static int +psmresume(device_t dev) +{ + struct psm_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int err; + + if (verbose >= 2) + log(LOG_NOTICE, "psm%d: system resume hook called.\n", unit); + + if (!(sc->config & PSM_CONFIG_HOOKRESUME)) + return (0); + + err = reinitialize(sc, sc->config & PSM_CONFIG_INITAFTERSUSPEND); + + if ((sc->state & PSM_ASLP) && !(sc->state & PSM_VALID)) { + /* + * Release the blocked process; it must be notified that the device + * cannot be accessed anymore. + */ + sc->state &= ~PSM_ASLP; + wakeup((caddr_t)sc); + } + + if (verbose >= 2) + log(LOG_DEBUG, "psm%d: system resume hook exiting.\n", unit); + + return (err); +} + +DRIVER_MODULE(psm, atkbdc, psm_driver, psm_devclass, 0, 0); + +/* + * This sucks up assignments from PNPBIOS and ACPI. + */ + +/* + * When the PS/2 mouse device is reported by ACPI or PnP BIOS, it may + * appear BEFORE the AT keyboard controller. As the PS/2 mouse device + * can be probed and attached only after the AT keyboard controller is + * attached, we shall quietly reserve the IRQ resource for later use. + * If the PS/2 mouse device is reported to us AFTER the keyboard controller, + * copy the IRQ resource to the PS/2 mouse device instance hanging + * under the keyboard controller, then probe and attach it. + */ + +static devclass_t psmcpnp_devclass; + +static device_probe_t psmcpnp_probe; +static device_attach_t psmcpnp_attach; + +static device_method_t psmcpnp_methods[] = { + DEVMETHOD(device_probe, psmcpnp_probe), + DEVMETHOD(device_attach, psmcpnp_attach), + + { 0, 0 } +}; + +static driver_t psmcpnp_driver = { + PSMCPNP_DRIVER_NAME, + psmcpnp_methods, + 1, /* no softc */ +}; + +static struct isa_pnp_id psmcpnp_ids[] = { + { 0x030fd041, "PS/2 mouse port" }, /* PNP0F03 */ + { 0x130fd041, "PS/2 mouse port" }, /* PNP0F13 */ + { 0x1303d041, "PS/2 port" }, /* PNP0313, XXX */ + { 0x80374d24, "IBM PS/2 mouse port" }, /* IBM3780, ThinkPad */ + { 0x81374d24, "IBM PS/2 mouse port" }, /* IBM3781, ThinkPad */ + { 0x0490d94d, "SONY VAIO PS/2 mouse port"}, /* SNY9004, Vaio*/ + { 0 } +}; + +static int +create_a_copy(device_t atkbdc, device_t me) +{ + device_t psm; + u_long irq; + + /* find the PS/2 mouse device instance under the keyboard controller */ + psm = device_find_child(atkbdc, PSM_DRIVER_NAME, + device_get_unit(atkbdc)); + if (psm == NULL) + return ENXIO; + if (device_get_state(psm) != DS_NOTPRESENT) + return 0; + + /* move our resource to the found device */ + irq = bus_get_resource_start(me, SYS_RES_IRQ, 0); + bus_set_resource(psm, SYS_RES_IRQ, KBDC_RID_AUX, irq, 1); + + /* ...then probe and attach it */ + return device_probe_and_attach(psm); +} + +static int +psmcpnp_probe(device_t dev) +{ + struct resource *res; + u_long irq; + int rid; + + if (ISA_PNP_PROBE(device_get_parent(dev), dev, psmcpnp_ids)) + return ENXIO; + + /* + * The PnP BIOS and ACPI are supposed to assign an IRQ (12) + * to the PS/2 mouse device node. But, some buggy PnP BIOS + * declares the PS/2 mouse device node without an IRQ resource! + * If this happens, we shall refer to device hints. + * If we still don't find it there, use a hardcoded value... XXX + */ + rid = 0; + irq = bus_get_resource_start(dev, SYS_RES_IRQ, rid); + if (irq <= 0) { + if (resource_long_value(PSM_DRIVER_NAME, + device_get_unit(dev), "irq", &irq) != 0) + irq = 12; /* XXX */ + device_printf(dev, "irq resource info is missing; " + "assuming irq %ld\n", irq); + bus_set_resource(dev, SYS_RES_IRQ, rid, irq, 1); + } + res = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE); + bus_release_resource(dev, SYS_RES_IRQ, rid, res); + + /* keep quiet */ + if (!bootverbose) + device_quiet(dev); + + return ((res == NULL) ? ENXIO : 0); +} + +static int +psmcpnp_attach(device_t dev) +{ + device_t atkbdc; + int rid; + + /* find the keyboard controller, which may be on acpi* or isa* bus */ + atkbdc = devclass_get_device(devclass_find(ATKBDC_DRIVER_NAME), + device_get_unit(dev)); + if ((atkbdc != NULL) && (device_get_state(atkbdc) == DS_ATTACHED)) { + create_a_copy(atkbdc, dev); + } else { + /* + * If we don't have the AT keyboard controller yet, + * just reserve the IRQ for later use... + * (See psmidentify() above.) + */ + rid = 0; + bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, + RF_SHAREABLE); + } + + return 0; +} + +DRIVER_MODULE(psmcpnp, isa, psmcpnp_driver, psmcpnp_devclass, 0, 0); +DRIVER_MODULE(psmcpnp, acpi, psmcpnp_driver, psmcpnp_devclass, 0, 0); |