diff options
Diffstat (limited to 'sys/dev/atkbdc')
-rw-r--r-- | sys/dev/atkbdc/atkbd.c | 1335 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbd_atkbdc.c | 128 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbd_isa.c | 128 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdc.c | 1021 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdc_isa.c | 269 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdc_subr.c | 269 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdcreg.h | 247 | ||||
-rw-r--r-- | sys/dev/atkbdc/atkbdreg.h | 47 | ||||
-rw-r--r-- | sys/dev/atkbdc/psm.c | 2454 |
9 files changed, 5898 insertions, 0 deletions
diff --git a/sys/dev/atkbdc/atkbd.c b/sys/dev/atkbdc/atkbd.c new file mode 100644 index 0000000..7072734 --- /dev/null +++ b/sys/dev/atkbdc/atkbd.c @@ -0,0 +1,1335 @@ +/*- + * 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 "atkbd.h" +#include "opt_kbd.h" +#include "opt_atkbd.h" + +#if NATKBD > 0 + +#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 <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 port, 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] = port; + 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 port, 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] = port; + 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 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 scintr 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. `scintr()' doesn't + * read the mouse data directly, but `kbdio' routines will, as a + * side effect. + */ + 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); +} + +/* cdev driver functions */ + + +/* LOW-LEVEL */ + +#include <machine/limits.h> +#include <machine/console.h> +#include <machine/clock.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 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 conole 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; + + /* XXX */ + if (unit == ATKBD_DEFAULT) { + if (KBD_IS_PROBED(&default_kbd)) + return 0; + } + + kbdc = kbdc_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 *data = (int *)arg; + + /* 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); + if (kbd == NULL) + return ENOMEM; + bzero(kbd, sizeof(*kbd)); + state = malloc(sizeof(*state), M_DEVBUF, M_NOWAIT); + 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 ((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); + free(kbd, M_DEVBUF); + return ENOMEM; + } + bzero(state, sizeof(*state)); + } 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 = kbdc_open(data[0]); + if (state->kbdc == NULL) + return ENXIO; + kbd_init_struct(kbd, ATKBD_DRIVER_NAME, KB_OTHER, unit, flags, + data[0], IO_KBDSIZE); + 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); + 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 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); + 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; + kbd->kb_prev_key = keycode | (scancode & 0x80); + 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; + kbd->kb_prev_key = keycode | (scancode & 0x80); + 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; + kbd->kb_prev_key = keycode | (scancode & 0x80); + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + case 0x52: /* keypad 0 */ + state->ks_composed_char *= 10; + kbd->kb_prev_key = keycode | (scancode & 0x80); + 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 */ + kbd->kb_prev_key = keycode | (scancode & 0x80); + 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; + kbd->kb_prev_key = keycode | (scancode & 0x80); + return ERRKEY; + } + break; + } + } + + /* keycode to key action */ + action = genkbd_keyaction(kbd, keycode, scancode & 0x80, + &state->ks_state, &state->ks_accents); + kbd->kb_prev_key = keycode | (scancode & 0x80); + 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); + } + /* FALL THROUGH */ + 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 (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; + /* FALL THROUGH */ + 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 +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; + } + + /* flush any noise in the buffer */ + empty_both_buffers(kbdc, 10); + + /* 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); + if (err == 0) { + kbdc_set_device_mask(kbdc, m | KBD_KBD_CONTROL_BITS); + } else { + if (c != -1) + /* try to restore the command byte as before */ + set_controller_command_byte(kbdc, 0xff, c); + kbdc_set_device_mask(kbdc, m); + } + + 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; + } + + /* 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: + case 0x83ab: + *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 in UserConfig 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; +} + +#endif /* NATKBD > 0 */ diff --git a/sys/dev/atkbdc/atkbd_atkbdc.c b/sys/dev/atkbdc/atkbd_atkbdc.c new file mode 100644 index 0000000..f686170 --- /dev/null +++ b/sys/dev/atkbdc/atkbd_atkbdc.c @@ -0,0 +1,128 @@ +/*- + * 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 "atkbd.h" +#include "opt_kbd.h" + +#if NATKBD > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/bus.h> +#include <machine/bus.h> +#include <sys/rman.h> + +#include <machine/resource.h> + +#include <dev/kbd/kbdreg.h> +#include <dev/kbd/atkbdreg.h> +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> +#include <isa/isavar.h> + +devclass_t atkbd_devclass; + +static int atkbdprobe(device_t dev); +static int atkbdattach(device_t dev); +static void atkbd_isa_intr(void *arg); + +static device_method_t atkbd_methods[] = { + DEVMETHOD(device_probe, atkbdprobe), + DEVMETHOD(device_attach, atkbdattach), + { 0, 0 } +}; + +static driver_t atkbd_driver = { + ATKBD_DRIVER_NAME, + atkbd_methods, + 1, +}; + +static int +atkbdprobe(device_t dev) +{ + uintptr_t port; + uintptr_t irq; + uintptr_t flags; + + device_set_desc(dev, "AT Keyboard"); + + /* obtain parameters */ + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_PORT, &port); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_IRQ, &irq); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_FLAGS, &flags); + + /* probe the device */ + return atkbd_probe_unit(device_get_unit(dev), port, irq, flags); +} + +static int +atkbdattach(device_t dev) +{ + keyboard_t *kbd; + uintptr_t port; + uintptr_t irq; + uintptr_t flags; + struct resource *res; + void *ih; + int zero = 0; + int error; + + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_PORT, &port); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_IRQ, &irq); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_FLAGS, &flags); + + error = atkbd_attach_unit(device_get_unit(dev), &kbd, port, irq, flags); + if (error) + return error; + + /* declare our interrupt handler */ + res = bus_alloc_resource(dev, SYS_RES_IRQ, &zero, irq, irq, 1, + RF_SHAREABLE | RF_ACTIVE); + BUS_SETUP_INTR(device_get_parent(dev), dev, res, INTR_TYPE_TTY, + atkbd_isa_intr, kbd, &ih); + + 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); + +#endif /* NATKBD > 0 */ diff --git a/sys/dev/atkbdc/atkbd_isa.c b/sys/dev/atkbdc/atkbd_isa.c new file mode 100644 index 0000000..f686170 --- /dev/null +++ b/sys/dev/atkbdc/atkbd_isa.c @@ -0,0 +1,128 @@ +/*- + * 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 "atkbd.h" +#include "opt_kbd.h" + +#if NATKBD > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/bus.h> +#include <machine/bus.h> +#include <sys/rman.h> + +#include <machine/resource.h> + +#include <dev/kbd/kbdreg.h> +#include <dev/kbd/atkbdreg.h> +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> +#include <isa/isavar.h> + +devclass_t atkbd_devclass; + +static int atkbdprobe(device_t dev); +static int atkbdattach(device_t dev); +static void atkbd_isa_intr(void *arg); + +static device_method_t atkbd_methods[] = { + DEVMETHOD(device_probe, atkbdprobe), + DEVMETHOD(device_attach, atkbdattach), + { 0, 0 } +}; + +static driver_t atkbd_driver = { + ATKBD_DRIVER_NAME, + atkbd_methods, + 1, +}; + +static int +atkbdprobe(device_t dev) +{ + uintptr_t port; + uintptr_t irq; + uintptr_t flags; + + device_set_desc(dev, "AT Keyboard"); + + /* obtain parameters */ + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_PORT, &port); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_IRQ, &irq); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_FLAGS, &flags); + + /* probe the device */ + return atkbd_probe_unit(device_get_unit(dev), port, irq, flags); +} + +static int +atkbdattach(device_t dev) +{ + keyboard_t *kbd; + uintptr_t port; + uintptr_t irq; + uintptr_t flags; + struct resource *res; + void *ih; + int zero = 0; + int error; + + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_PORT, &port); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_IRQ, &irq); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_FLAGS, &flags); + + error = atkbd_attach_unit(device_get_unit(dev), &kbd, port, irq, flags); + if (error) + return error; + + /* declare our interrupt handler */ + res = bus_alloc_resource(dev, SYS_RES_IRQ, &zero, irq, irq, 1, + RF_SHAREABLE | RF_ACTIVE); + BUS_SETUP_INTR(device_get_parent(dev), dev, res, INTR_TYPE_TTY, + atkbd_isa_intr, kbd, &ih); + + 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); + +#endif /* NATKBD > 0 */ diff --git a/sys/dev/atkbdc/atkbdc.c b/sys/dev/atkbdc/atkbdc.c new file mode 100644 index 0000000..68fcffc --- /dev/null +++ b/sys/dev/atkbdc/atkbdc.c @@ -0,0 +1,1021 @@ +/*- + * 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 "atkbdc.h" +#include "opt_kbd.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/syslog.h> + +#include <machine/clock.h> + +#include <dev/kbd/atkbdcreg.h> + +#include <isa/isareg.h> + +/* constants */ + +#define MAXKBDC MAX(NATKBDC, 1) + +/* 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 + +/* 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, int port); +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); + +#if NATKBDC > 0 + +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); + if (sc == NULL) + return NULL; + bzero(sc, sizeof(*sc)); + sc->port = -1; /* XXX */ + } + return sc; +} + +int +atkbdc_probe_unit(int unit, int port) +{ + if (port <= 0) + return ENXIO; + return 0; +} + +int +atkbdc_attach_unit(int unit, atkbdc_softc_t *sc, int port) +{ + return atkbdc_setup(sc, port); +} + +#endif /* NATKBDC > 0 */ + +/* the backdoor to the keyboard controller! XXX */ +int +atkbdc_configure(void) +{ + return atkbdc_setup(atkbdc_softc[0], -1); +} + +static int +atkbdc_setup(atkbdc_softc_t *sc, int port) +{ + if (port <= 0) + port = IO_KBD; + + if (sc->port <= 0) { + 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->port = port; /* may override the previous value */ + return 0; +} + +/* associate a port number with a KBDC */ + +KBDC +kbdc_open(int port) +{ + int s; + int i; + + if (port <= 0) + port = IO_KBD; + + s = spltty(); + for (i = 0; i < sizeof(atkbdc_softc)/sizeof(atkbdc_softc[0]); ++i) { + if (atkbdc_softc[i] == NULL) + continue; + if (atkbdc_softc[i]->port == port) { + splx(s); + return (KBDC)atkbdc_softc[i]; + } + if (atkbdc_softc[i]->port <= 0) { + if (atkbdc_setup(atkbdc_softc[i], port)) + break; + splx(s); + return (KBDC)atkbdc_softc[i]; + } + } + splx(s); + 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 driver which utilizes 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 polls 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 the + * the 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 + * necessary I/O operation within one invokation of the routine. + * This measns 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) + || (inb(kbdcp(p)->port + KBD_STATUS_PORT) & 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 port = kbdc->port; + int f; + + while ((f = inb(port + KBD_STATUS_PORT)) & KBDS_INPUT_BUFFER_FULL) { + if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdc->kbd, inb(port + KBD_DATA_PORT)); + } else if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdc->aux, inb(port + KBD_DATA_PORT)); + } + 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 port = kbdc->port; + int f; + + while ((f = inb(port + KBD_STATUS_PORT) & 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 port = kbdc->port; + int f; + + while ((f = inb(port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL) + != KBDS_KBD_BUFFER_FULL) { + if (f == KBDS_AUX_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdc->aux, inb(port + KBD_DATA_PORT)); + } + 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 port = kbdc->port; + int f; + int b; + + while (retry-- > 0) { + if ((f = inb(port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + b = inb(port + KBD_DATA_PORT); + 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 port = kbdc->port; + int f; + + while ((f = inb(port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL) + != KBDS_AUX_BUFFER_FULL) { + if (f == KBDS_KBD_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdc->kbd, inb(port + KBD_DATA_PORT)); + } + 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 port = kbdc->port; + int f; + int b; + + while (retry-- > 0) { + if ((f = inb(port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + b = inb(port + KBD_DATA_PORT); + 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; + outb(kbdcp(p)->port + KBD_COMMAND_PORT, 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; + outb(kbdcp(p)->port + KBD_DATA_PORT, 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; + outb(kbdcp(p)->port + KBD_DATA_PORT, 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 inb(kbdcp(p)->port + KBD_DATA_PORT); +} + +#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 inb(kbdcp(p)->port + KBD_DATA_PORT); +} + +/* 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 = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL; + if (f == KBDS_AUX_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdcp(p)->aux, inb(kbdcp(p)->port + KBD_DATA_PORT)); + f = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL; + } + if (f == KBDS_KBD_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + return inb(kbdcp(p)->port + KBD_DATA_PORT); + } + 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 inb(kbdcp(p)->port + KBD_DATA_PORT); +} + +/* 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 = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL; + if (f == KBDS_KBD_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + addq(&kbdcp(p)->kbd, inb(kbdcp(p)->port + KBD_DATA_PORT)); + f = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL; + } + if (f == KBDS_AUX_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + return inb(kbdcp(p)->port + KBD_DATA_PORT); + } + 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 = inb(kbdcp(p)->port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + b = inb(kbdcp(p)->port + KBD_DATA_PORT); + 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 = inb(kbdcp(p)->port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + b = inb(kbdcp(p)->port + KBD_DATA_PORT); + 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 = inb(kbdcp(p)->port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) { + DELAY(KBDD_DELAYTIME); + (void)inb(kbdcp(p)->port + KBD_DATA_PORT); +#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..9829f2b --- /dev/null +++ b/sys/dev/atkbdc/atkbdc_isa.c @@ -0,0 +1,269 @@ +/*- + * 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 "atkbdc.h" +#include "opt_kbd.h" + +#if NATKBDC > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/malloc.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> + +MALLOC_DEFINE(M_ATKBDDEV, "atkbddev", "AT Keyboard device"); + +/* children */ +typedef struct atkbdc_device { + int flags; /* configuration flags */ + int port; /* port number (same as the controller's) */ + int irq; /* ISA IRQ mask */ +} atkbdc_device_t; + +/* kbdc */ +devclass_t atkbdc_devclass; + +static int atkbdc_probe(device_t dev); +static int atkbdc_attach(device_t dev); +static int atkbdc_print_child(device_t bus, device_t dev); +static int atkbdc_read_ivar(device_t bus, device_t dev, int index, + u_long *val); +static int atkbdc_write_ivar(device_t bus, device_t dev, int index, + u_long val); + +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_print_child, atkbdc_print_child), + DEVMETHOD(bus_read_ivar, atkbdc_read_ivar), + DEVMETHOD(bus_write_ivar, atkbdc_write_ivar), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_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 int +atkbdc_probe(device_t dev) +{ + int error; + int rid; + struct resource *port; + + /* Check isapnp ids */ + if (isa_get_vendorid(dev)) + return (ENXIO); + + device_set_desc(dev, "keyboard controller (i8042)"); + rid = 0; + port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, + 0, ~0, IO_KBDSIZE, RF_ACTIVE); + if (!port) + return ENXIO; + error = atkbdc_probe_unit(device_get_unit(dev), rman_get_start(port)); + bus_release_resource(dev, SYS_RES_IOPORT, rid, port); + return error; +} + +static void +atkbdc_add_device(device_t dev, const char *name, int unit) +{ + atkbdc_softc_t *sc = *(atkbdc_softc_t **)device_get_softc(dev); + atkbdc_device_t *kdev; + device_t child; + int t; + + kdev = malloc(sizeof(struct atkbdc_device), M_ATKBDDEV, M_NOWAIT); + if (!kdev) + return; + bzero(kdev, sizeof *kdev); + + kdev->port = sc->port; + + if (resource_int_value(name, unit, "irq", &t) == 0) + kdev->irq = t; + else + kdev->irq = -1; + + if (resource_int_value(name, unit, "flags", &t) == 0) + kdev->flags = t; + else + kdev->flags = 0; + + child = device_add_child(dev, name, unit); + device_set_ivars(child, kdev); +} + +static int +atkbdc_attach(device_t dev) +{ + atkbdc_softc_t *sc; + struct resource *port; + int unit; + int error; + int rid; + int i; + + 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; + } + + /* XXX should track resource in softc */ + rid = 0; + port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, + 0, ~0, IO_KBDSIZE, RF_ACTIVE); + if (!port) + return ENXIO; + error = atkbdc_attach_unit(unit, sc, rman_get_start(port)); + if (error) + return error; + *(atkbdc_softc_t **)device_get_softc(dev) = sc; + + /* + * Add all devices configured to be attached to atkbdc0. + */ + for (i = resource_query_string(-1, "at", "atkbdc0"); + i != -1; + i = resource_query_string(i, "at", "atkbdc0")) { + atkbdc_add_device(dev, resource_query_name(i), + resource_query_unit(i)); + } + + /* + * and atkbdc? + */ + for (i = resource_query_string(-1, "at", "atkbdc"); + i != -1; + i = resource_query_string(i, "at", "atkbdc")) { + atkbdc_add_device(dev, resource_query_name(i), + resource_query_unit(i)); + } + + bus_generic_attach(dev); + + return 0; +} + +static int +atkbdc_print_child(device_t bus, device_t dev) +{ + atkbdc_device_t *kbdcdev; + int retval = 0; + + kbdcdev = (atkbdc_device_t *)device_get_ivars(dev); + + retval += bus_print_child_header(bus, dev); + if (kbdcdev->flags != 0) + retval += printf(" flags 0x%x", kbdcdev->flags); + if (kbdcdev->irq != -1) + retval += printf(" irq %d", kbdcdev->irq); + retval += bus_print_child_footer(bus, dev); + + return (retval); +} + +static int +atkbdc_read_ivar(device_t bus, device_t dev, int index, u_long *val) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + switch (index) { + case KBDC_IVAR_PORT: + *val = (u_long)ivar->port; + break; + case KBDC_IVAR_IRQ: + *val = (u_long)ivar->irq; + break; + case KBDC_IVAR_FLAGS: + *val = (u_long)ivar->flags; + break; + default: + return ENOENT; + } + return 0; +} + +static int +atkbdc_write_ivar(device_t bus, device_t dev, int index, u_long val) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + switch (index) { + case KBDC_IVAR_PORT: + ivar->port = (int)val; + break; + case KBDC_IVAR_IRQ: + ivar->irq = (int)val; + break; + case KBDC_IVAR_FLAGS: + ivar->flags = (int)val; + break; + default: + return ENOENT; + } + return 0; +} + +DRIVER_MODULE(atkbdc, isa, atkbdc_driver, atkbdc_devclass, 0, 0); + +#endif /* NATKBDC > 0 */ diff --git a/sys/dev/atkbdc/atkbdc_subr.c b/sys/dev/atkbdc/atkbdc_subr.c new file mode 100644 index 0000000..9829f2b --- /dev/null +++ b/sys/dev/atkbdc/atkbdc_subr.c @@ -0,0 +1,269 @@ +/*- + * 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 "atkbdc.h" +#include "opt_kbd.h" + +#if NATKBDC > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/malloc.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> + +MALLOC_DEFINE(M_ATKBDDEV, "atkbddev", "AT Keyboard device"); + +/* children */ +typedef struct atkbdc_device { + int flags; /* configuration flags */ + int port; /* port number (same as the controller's) */ + int irq; /* ISA IRQ mask */ +} atkbdc_device_t; + +/* kbdc */ +devclass_t atkbdc_devclass; + +static int atkbdc_probe(device_t dev); +static int atkbdc_attach(device_t dev); +static int atkbdc_print_child(device_t bus, device_t dev); +static int atkbdc_read_ivar(device_t bus, device_t dev, int index, + u_long *val); +static int atkbdc_write_ivar(device_t bus, device_t dev, int index, + u_long val); + +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_print_child, atkbdc_print_child), + DEVMETHOD(bus_read_ivar, atkbdc_read_ivar), + DEVMETHOD(bus_write_ivar, atkbdc_write_ivar), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_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 int +atkbdc_probe(device_t dev) +{ + int error; + int rid; + struct resource *port; + + /* Check isapnp ids */ + if (isa_get_vendorid(dev)) + return (ENXIO); + + device_set_desc(dev, "keyboard controller (i8042)"); + rid = 0; + port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, + 0, ~0, IO_KBDSIZE, RF_ACTIVE); + if (!port) + return ENXIO; + error = atkbdc_probe_unit(device_get_unit(dev), rman_get_start(port)); + bus_release_resource(dev, SYS_RES_IOPORT, rid, port); + return error; +} + +static void +atkbdc_add_device(device_t dev, const char *name, int unit) +{ + atkbdc_softc_t *sc = *(atkbdc_softc_t **)device_get_softc(dev); + atkbdc_device_t *kdev; + device_t child; + int t; + + kdev = malloc(sizeof(struct atkbdc_device), M_ATKBDDEV, M_NOWAIT); + if (!kdev) + return; + bzero(kdev, sizeof *kdev); + + kdev->port = sc->port; + + if (resource_int_value(name, unit, "irq", &t) == 0) + kdev->irq = t; + else + kdev->irq = -1; + + if (resource_int_value(name, unit, "flags", &t) == 0) + kdev->flags = t; + else + kdev->flags = 0; + + child = device_add_child(dev, name, unit); + device_set_ivars(child, kdev); +} + +static int +atkbdc_attach(device_t dev) +{ + atkbdc_softc_t *sc; + struct resource *port; + int unit; + int error; + int rid; + int i; + + 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; + } + + /* XXX should track resource in softc */ + rid = 0; + port = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, + 0, ~0, IO_KBDSIZE, RF_ACTIVE); + if (!port) + return ENXIO; + error = atkbdc_attach_unit(unit, sc, rman_get_start(port)); + if (error) + return error; + *(atkbdc_softc_t **)device_get_softc(dev) = sc; + + /* + * Add all devices configured to be attached to atkbdc0. + */ + for (i = resource_query_string(-1, "at", "atkbdc0"); + i != -1; + i = resource_query_string(i, "at", "atkbdc0")) { + atkbdc_add_device(dev, resource_query_name(i), + resource_query_unit(i)); + } + + /* + * and atkbdc? + */ + for (i = resource_query_string(-1, "at", "atkbdc"); + i != -1; + i = resource_query_string(i, "at", "atkbdc")) { + atkbdc_add_device(dev, resource_query_name(i), + resource_query_unit(i)); + } + + bus_generic_attach(dev); + + return 0; +} + +static int +atkbdc_print_child(device_t bus, device_t dev) +{ + atkbdc_device_t *kbdcdev; + int retval = 0; + + kbdcdev = (atkbdc_device_t *)device_get_ivars(dev); + + retval += bus_print_child_header(bus, dev); + if (kbdcdev->flags != 0) + retval += printf(" flags 0x%x", kbdcdev->flags); + if (kbdcdev->irq != -1) + retval += printf(" irq %d", kbdcdev->irq); + retval += bus_print_child_footer(bus, dev); + + return (retval); +} + +static int +atkbdc_read_ivar(device_t bus, device_t dev, int index, u_long *val) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + switch (index) { + case KBDC_IVAR_PORT: + *val = (u_long)ivar->port; + break; + case KBDC_IVAR_IRQ: + *val = (u_long)ivar->irq; + break; + case KBDC_IVAR_FLAGS: + *val = (u_long)ivar->flags; + break; + default: + return ENOENT; + } + return 0; +} + +static int +atkbdc_write_ivar(device_t bus, device_t dev, int index, u_long val) +{ + atkbdc_device_t *ivar; + + ivar = (atkbdc_device_t *)device_get_ivars(dev); + switch (index) { + case KBDC_IVAR_PORT: + ivar->port = (int)val; + break; + case KBDC_IVAR_IRQ: + ivar->irq = (int)val; + break; + case KBDC_IVAR_FLAGS: + ivar->flags = (int)val; + break; + default: + return ENOENT; + } + return 0; +} + +DRIVER_MODULE(atkbdc, isa, atkbdc_driver, atkbdc_devclass, 0, 0); + +#endif /* NATKBDC > 0 */ diff --git a/sys/dev/atkbdc/atkbdcreg.h b/sys/dev/atkbdc/atkbdcreg.h new file mode 100644 index 0000000..2085bb6 --- /dev/null +++ b/sys/dev/atkbdc/atkbdcreg.h @@ -0,0 +1,247 @@ +/*- + * 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_ + +/* 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 + +#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; + +typedef struct atkbdc_softc { + int port; /* base port address */ + 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_PORT, + KBDC_IVAR_IRQ, + KBDC_IVAR_FLAGS, +}; + +typedef caddr_t KBDC; + +/* function prototypes */ + +atkbdc_softc_t *atkbdc_get_softc(int unit); +int atkbdc_probe_unit(int unit, int port); +int atkbdc_attach_unit(int unit, atkbdc_softc_t *sc, int port); +int atkbdc_configure(void); + +KBDC kbdc_open(int port); +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..bda6afb --- /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 port, int irq, int flags); +int atkbd_attach_unit(int unit, keyboard_t **kbd, + int port, int irq, int flags); + +#endif /* KERNEL */ + +#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..928e920 --- /dev/null +++ b/sys/dev/atkbdc/psm.c @@ -0,0 +1,2454 @@ +/*- + * 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 "psm.h" +#include "opt_psm.h" + +#if NPSM > 0 + +#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 <sys/malloc.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <sys/select.h> +#include <sys/uio.h> + +#include <machine/clock.h> +#include <machine/limits.h> +#include <machine/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 + +/* features */ + +/* #define PSM_HOOKRESUME hook the system resume event */ +/* #define PSM_RESETAFTERSUSPEND reset the device at the resume event */ + +#ifdef PSM_HOOKAPM +#undef PSM_HOOKRESUME +#define PSM_HOOKRESUME 1 +#endif /* PSM_HOOKAPM */ + +#ifndef PSM_HOOKRESUME +#undef PSM_RESETAFTERSUSPEND +#endif /* PSM_HOOKRESUME */ + +/* end of driver specific options */ + +/* 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 */ + 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 */ + int addr; /* I/O port address */ + 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 */ +}; +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_FLAGS (PSM_CONFIG_RESOLUTION \ + | PSM_CONFIG_ACCEL \ + | PSM_CONFIG_NOCHECKSYNC \ + | PSM_CONFIG_NOIDPROBE \ + | PSM_CONFIG_NORESET \ + | PSM_CONFIG_FORCETAP \ + | PSM_CONFIG_IGNPORTERROR) + +/* 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 __P((struct psm_softc *, unsigned char *, + int *, int, mousestatus_t *)); + +/* function prototypes */ +static int psmprobe __P((device_t)); +static int psmattach __P((device_t)); +static int psmresume __P((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 __P((KBDC)); +static int disable_aux_dev __P((KBDC)); +static int get_mouse_status __P((KBDC, int *, int, int)); +static int get_aux_id __P((KBDC)); +static int set_mouse_sampling_rate __P((KBDC, int)); +static int set_mouse_scaling __P((KBDC, int)); +static int set_mouse_resolution __P((KBDC, int)); +#ifdef PSM_RESETAFTERSUSPEND +static int set_mouse_mode __P((KBDC)); +#endif /* PSM_RESETAFTERSUSPEND */ +static int get_mouse_buttons __P((KBDC)); +static int is_a_mouse __P((int)); +static void recover_from_error __P((KBDC)); +static int restore_controller __P((KBDC, int)); +#ifdef PSM_RESETAFTERSUSPEND +static int reinitialize __P((int, mousemode_t *)); +#endif +static int doopen __P((int, int)); +static char *model_name(int); +static void psmintr(void*); + +/* vendor specific features */ +typedef int probefunc_t __P((struct psm_softc *)); + +static int mouse_id_proc1 __P((KBDC, int, int, 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_msintelli; +static probefunc_t enable_mmanplus; +static probefunc_t enable_versapad; +static int tame_mouse __P((struct psm_softc *, mousestatus_t *, unsigned char *)); + +static struct { + int model; + unsigned char syncmask; + int packetsize; + probefunc_t *probefunc; +} vendortype[] = { + { MOUSE_MODEL_NET, /* Genius NetMouse */ + 0xc8, MOUSE_INTELLI_PACKETSIZE, enable_gmouse, }, + { MOUSE_MODEL_NETSCROLL, /* Genius NetScroll */ + 0xc8, 6, enable_groller, }, + { MOUSE_MODEL_GLIDEPOINT, /* ALPS GlidePoint */ + 0xc0, MOUSE_PS2_PACKETSIZE, enable_aglide, }, + { MOUSE_MODEL_MOUSEMANPLUS, /* Logitech MouseMan+ */ + 0x08, MOUSE_PS2_PACKETSIZE, enable_mmanplus, }, + { MOUSE_MODEL_THINK, /* Kensignton ThinkingMouse */ + 0x80, MOUSE_PS2_PACKETSIZE, enable_kmouse, }, + { MOUSE_MODEL_INTELLI, /* Microsoft IntelliMouse */ + 0xc8, MOUSE_INTELLI_PACKETSIZE, enable_msintelli, }, + { MOUSE_MODEL_VERSAPAD, /* Interlink electronics VersaPad */ + 0xe8, MOUSE_PS2VERSA_PACKETSIZE, enable_versapad, }, + { MOUSE_MODEL_GENERIC, + 0xc0, MOUSE_PS2_PACKETSIZE, NULL, }, +}; +#define GENERIC_MOUSE_ENTRY 7 + +/* device driver declarateion */ +static device_method_t psm_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, psmprobe), + DEVMETHOD(device_attach, psmattach), + DEVMETHOD(device_resume, psmresume), + + { 0, 0 } +}; + +static driver_t psm_driver = { + "psm", + 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", + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* bmaj */ -1 +}; + +/* 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); +} + +#ifdef PSM_RESETAFTERSUSPEND +/* + * 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); +} +#endif /* PSM_RESETAFTERSUSPEND */ + + +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 */ + -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" }, + { MOUSE_MODEL_NET, "NetMouse" }, + { MOUSE_MODEL_GLIDEPOINT, "GlidePoint" }, + { MOUSE_MODEL_THINK, "ThinkingMouse" }, + { MOUSE_MODEL_INTELLI, "IntelliMouse" }, + { MOUSE_MODEL_MOUSEMANPLUS, "MouseMan+" }, + { MOUSE_MODEL_VERSAPAD, "VersaPad" }, + { 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"); + return FALSE; + } else { + return TRUE; + } +} + +#ifdef PSM_RESETAFTERSUSPEND +/* + * 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 +reinitialize(int unit, mousemode_t *mode) +{ + struct psm_softc *sc = PSM_SOFTC(unit); + 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", + unit, i); + /* fall though */ + 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", + 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", 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", 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", + 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 (reinitialize).\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(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 (reinitialize).\n", unit); + + return TRUE; +} +#endif /* PSM_RESETAFTERSUSPEND */ + +static int +doopen(int unit, int command_byte) +{ + struct psm_softc *sc = PSM_SOFTC(unit); + 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 (!reinitialize(unit, &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", + unit); + return (EIO); + } + } + + if (get_mouse_status(sc->kbdc, stat, 0, 3) < 3) + log(LOG_DEBUG, "psm%d: failed to get status (doopen).\n", 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", + unit); + return (EIO); + } + + return (0); +} + +/* psm driver entry points */ + +#define endprobe(v) { if (bootverbose) \ + --verbose; \ + kbdc_set_device_mask(sc->kbdc, mask); \ + kbdc_lock(sc->kbdc, FALSE); \ + free(sc, M_DEVBUF); \ + return (v); \ + } + +static int +psmprobe(device_t dev) +{ + int unit = device_get_unit(dev); + struct psm_softc *sc = device_get_softc(dev); + uintptr_t port; + uintptr_t flags; + int stat[3]; + int command_byte; + int mask; + int i; + +#if 0 + kbdc_debug(TRUE); +#endif + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_PORT, &port); + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_FLAGS, &flags); + + sc->addr = port; + sc->kbdc = kbdc_open(sc->addr); + sc->config = flags & PSM_CONFIG_FLAGS; + 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); + /* fall though */ + 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: + 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); + void *ih; + struct resource *res; + uintptr_t irq; + int zero = 0; + + if (sc == NULL) /* shouldn't happen */ + return (ENXIO); + + /* Setup initial state */ + sc->state = PSM_VALID; + + /* Done */ + make_dev(&psm_cdevsw, PSM_MKMINOR(unit, FALSE), 0, 0, 0666, "psm%d", unit); + 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; + + BUS_READ_IVAR(device_get_parent(dev), dev, KBDC_IVAR_IRQ, &irq); + res = bus_alloc_resource(dev, SYS_RES_IRQ, &zero, irq, irq, 1, + RF_SHAREABLE | RF_ACTIVE); + BUS_SETUP_INTR(device_get_parent(dev), dev, res, INTR_TYPE_TTY, + psmintr, sc, &ih); + + return (0); +} + +static int +psmopen(dev_t dev, int flag, int fmt, struct proc *p) +{ + int unit = PSM_UNIT(dev); + struct psm_softc *sc; + int command_byte; + int err; + int s; + + /* Validate unit number */ + if (unit >= NPSM) + return (ENXIO); + + /* 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->rsel.si_flags = 0; + sc->rsel.si_pid = 0; + sc->mode.level = sc->dflt_mode.level; + sc->mode.protocol = sc->dflt_mode.protocol; + + /* 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; + + /* 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(unit, 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 proc *p) +{ + 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", + PSM_UNIT(dev)); + /* 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); + + /* 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", + PSM_UNIT(dev)); + } + + if (get_mouse_status(sc->kbdc, stat, 0, 3) < 3) + log(LOG_DEBUG, "psm%d: failed to get status (psmclose).\n", + PSM_UNIT(dev)); + } + + 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", + PSM_UNIT(dev)); + } + + /* 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 proc *p) +{ + 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 +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; + 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; + + /* + * Check sync bits. We check for overflow bits and the bit 3 + * for most mice. True, the code doesn't work if overflow + * condition occurs. But we expect it rarely happens... + */ + if ((sc->inputbytes == 0) + && ((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]); + continue; + } + + 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]; + + /* + * A kludge for Kensington device! + * The MSB of the horizontal count appears to be stored in + * a strange place. This kludge doesn't affect other mice + * because the bit is the overflow bit which is, in most cases, + * expected to be zero when we reach here. XXX + */ + if (sc->hw.model != MOUSE_MODEL_VERSAPAD) + 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_INTELLI: + case MOUSE_MODEL_NET: + /* wheel data is in the fourth byte */ + z = (char)sc->ipacket[3]; + 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, and currently ignored */ + /* FALL THROUGH */ + case 0: + /* device type packet - shouldn't happen */ + /* FALL THROUGH */ + default: + x = y = 0; + ms.button = ms.obutton; + 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 button and wheel events */ + ms.button |= (sc->ipacket[3] & MOUSE_PS2_BUTTON3DOWN) + ? MOUSE_BUTTON4DOWN : 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_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; + + /* 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 proc *p) +{ + 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(p, &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; +} + +#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 */ +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!! */ + sc->hw.buttons = get_mouse_buttons(sc->kbdc); + sc->hw.buttons = 4; + return TRUE; +} + +/* Genius NetMouse/NetMouse Pro */ +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+ */ +static int +enable_mmanplus(struct psm_softc *sc) +{ + static char res[] = { + -1, PSMD_RES_LOW, PSMD_RES_HIGH, PSMD_RES_MEDIUM_HIGH, + PSMD_RES_MEDIUM_LOW, -1, PSMD_RES_HIGH, PSMD_RES_MEDIUM_LOW, + PSMD_RES_MEDIUM_HIGH, PSMD_RES_HIGH, + }; + KBDC kbdc = sc->kbdc; + int data[3]; + int i; + + /* the special sequence to enable the fourth button and the roller. */ + for (i = 0; i < sizeof(res)/sizeof(res[0]); ++i) { + if (res[i] < 0) { + if (!set_mouse_scaling(kbdc, 1)) + return FALSE; + } else { + if (set_mouse_resolution(kbdc, res[i]) != res[i]) + 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,... + */ + /* 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 */ +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; +} + +/* 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 */ + + return TRUE; /* PS/2 absolute mode */ +} + +static int +psmresume(device_t dev) +{ +#ifdef PSM_HOOKRESUME + struct psm_softc *sc = device_get_softc(dev); + int unit = device_get_unit(dev); + int err = 0; + int s; + int c; + + if (verbose >= 2) + log(LOG_NOTICE, "psm%d: system resume hook called.\n", unit); + + /* don't let anybody mess with the aux device */ + if (!kbdc_lock(sc->kbdc, TRUE)) + return (EIO); + s = spltty(); + + /* 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 (psmresume).\n", + 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 (psmresume).\n", + 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; + +#ifdef PSM_RESETAFTERSUSPEND + /* try to detect the aux device; are you still there? */ + if (reinitialize(unit, &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! (psmresume).\n", + unit); + err = ENXIO; + } +#endif /* PSM_RESETAFTERSUSPEND */ + splx(s); + + /* restore the driver state */ + if ((sc->state & PSM_OPEN) && (err == 0)) { + /* enable the aux device and the port again */ + err = doopen(unit, c); + if (err != 0) + log(LOG_ERR, "psm%d: failed to enable the device (psmresume).\n", + 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 (psmresume).\n", + unit); + err = EIO; + } + } + + /* done */ + kbdc_lock(sc->kbdc, FALSE); + 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); +#else /* !PSM_HOOKRESUME */ + return (0); +#endif /* PSM_HOOKRESUME */ +} + +DRIVER_MODULE(psm, atkbdc, psm_driver, psm_devclass, 0, 0); + +#endif /* NPSM > 0 */ |