/* * Copyright 2004-2009 Haiku, Inc. * Distributed under the terms of the MIT License. * * Authors (in chronological order): * Stefano Ceccherini (burton666@libero.it) * Axel Dörfler, axeld@pinc-software.de * Marcus Overhagen */ /*! PS/2 bus manager */ #include #include "ps2_common.h" #include "ps2_service.h" #include "ps2_dev.h" //#define TRACE_PS2_COMMON #ifdef TRACE_PS2_COMMON # define TRACE(x...) dprintf(x) # define TRACE_ONLY #else # define TRACE(x...) # define TRACE_ONLY __attribute__((unused)) #endif isa_module_info *gIsa = NULL; bool gActiveMultiplexingEnabled = false; bool gSetupComplete = false; sem_id gControllerSem; static int32 sIgnoreInterrupts = 0; uint8 ps2_read_ctrl(void) { return gIsa->read_io_8(PS2_PORT_CTRL); } uint8 ps2_read_data(void) { return gIsa->read_io_8(PS2_PORT_DATA); } void ps2_write_ctrl(uint8 ctrl) { TRACE("ps2: ps2_write_ctrl 0x%02x\n", ctrl); gIsa->write_io_8(PS2_PORT_CTRL, ctrl); } void ps2_write_data(uint8 data) { TRACE("ps2: ps2_write_data 0x%02x\n", data); gIsa->write_io_8(PS2_PORT_DATA, data); } status_t ps2_wait_read(void) { int i; for (i = 0; i < PS2_CTRL_WAIT_TIMEOUT / 50; i++) { if (ps2_read_ctrl() & PS2_STATUS_OUTPUT_BUFFER_FULL) return B_OK; snooze(50); } return B_ERROR; } status_t ps2_wait_write(void) { int i; for (i = 0; i < PS2_CTRL_WAIT_TIMEOUT / 50; i++) { if (!(ps2_read_ctrl() & PS2_STATUS_INPUT_BUFFER_FULL)) return B_OK; snooze(50); } return B_ERROR; } // #pragma mark - void ps2_flush(void) { int i; acquire_sem(gControllerSem); atomic_add(&sIgnoreInterrupts, 1); for (i = 0; i < 64; i++) { uint8 ctrl; uint8 data TRACE_ONLY; ctrl = ps2_read_ctrl(); if (!(ctrl & PS2_STATUS_OUTPUT_BUFFER_FULL)) break; data = ps2_read_data(); TRACE("ps2: ps2_flush: ctrl 0x%02x, data 0x%02x (%s)\n", ctrl, data, (ctrl & PS2_STATUS_AUX_DATA) ? "aux" : "keyb"); snooze(100); } atomic_add(&sIgnoreInterrupts, -1); release_sem(gControllerSem); } static status_t ps2_selftest() { status_t res; uint8 in; res = ps2_command(PS2_CTRL_SELF_TEST, NULL, 0, &in, 1); if (res != B_OK || in != 0x55) { INFO("ps2: controller self test failed, status 0x%08" B_PRIx32 ", data " "0x%02x\n", res, in); return B_ERROR; } return B_OK; } static status_t ps2_setup_command_byte(bool interruptsEnabled) { status_t res; uint8 cmdbyte; res = ps2_command(PS2_CTRL_READ_CMD, NULL, 0, &cmdbyte, 1); TRACE("ps2: get command byte: res 0x%08" B_PRIx32 ", cmdbyte 0x%02x\n", res, cmdbyte); if (res != B_OK) cmdbyte = 0x47; cmdbyte |= PS2_BITS_TRANSLATE_SCANCODES; cmdbyte &= ~(PS2_BITS_KEYBOARD_DISABLED | PS2_BITS_MOUSE_DISABLED); if (interruptsEnabled) cmdbyte |= PS2_BITS_KEYBOARD_INTERRUPT | PS2_BITS_AUX_INTERRUPT; else cmdbyte &= ~(PS2_BITS_KEYBOARD_INTERRUPT | PS2_BITS_AUX_INTERRUPT); res = ps2_command(PS2_CTRL_WRITE_CMD, &cmdbyte, 1, NULL, 0); TRACE("ps2: set command byte: res 0x%08" B_PRIx32 ", cmdbyte 0x%02x\n", res, cmdbyte); return res; } static status_t ps2_setup_active_multiplexing(bool *enabled) { status_t res; uint8 in, out; // Disable the keyboard port to avoid any interference with the keyboard ps2_command(PS2_CTRL_KEYBOARD_DISABLE, NULL, 0, NULL, 0); out = 0xf0; res = ps2_command(PS2_CTRL_AUX_LOOPBACK, &out, 1, &in, 1); if (res) goto fail; // Step 1, if controller is good, in does match out. // This test failes with MS Virtual PC. if (in != out) goto no_support; out = 0x56; res = ps2_command(PS2_CTRL_AUX_LOOPBACK, &out, 1, &in, 1); if (res) goto fail; // Step 2, if controller is good, in does match out. if (in != out) goto no_support; out = 0xa4; res = ps2_command(PS2_CTRL_AUX_LOOPBACK, &out, 1, &in, 1); if (res) goto fail; // Step 3, if the controller doesn't support active multiplexing, // then in data does match out data (0xa4), else it's version number. if (in == out) goto no_support; // With some broken USB legacy emulation, it's 0xac, and with // MS Virtual PC, it's 0xa6. Since current active multiplexing // specification version is 1.1 (0x11), we validate the data. if (in > 0x9f) { TRACE("ps2: active multiplexing v%d.%d detected, but ignored!\n", (in >> 4), in & 0xf); goto no_support; } INFO("ps2: active multiplexing v%d.%d enabled\n", (in >> 4), in & 0xf); *enabled = true; goto done; no_support: TRACE("ps2: active multiplexing not supported\n"); *enabled = false; done: // Some controllers get upset by the d3 command and will continue data // loopback, thus we need to send a harmless command (enable keyboard // interface) next. // This fixes bug report #1175 res = ps2_command(PS2_CTRL_KEYBOARD_ENABLE, NULL, 0, NULL, 0); if (res != B_OK) { INFO("ps2: active multiplexing d3 workaround failed, status 0x%08" B_PRIx32 "\n", res); } return B_OK; fail: TRACE("ps2: testing for active multiplexing failed\n"); *enabled = false; // this should revert the controller into legacy mode, // just in case it has switched to multiplexed mode return ps2_selftest(); } status_t ps2_command(uint8 cmd, const uint8 *out, int outCount, uint8 *in, int inCount) { status_t res; int i; acquire_sem(gControllerSem); atomic_add(&sIgnoreInterrupts, 1); #ifdef TRACE_PS2_COMMON TRACE("ps2: ps2_command cmd 0x%02x, out %d, in %d\n", cmd, outCount, inCount); for (i = 0; i < outCount; i++) TRACE("ps2: ps2_command out 0x%02x\n", out[i]); #endif res = ps2_wait_write(); if (res == B_OK) ps2_write_ctrl(cmd); for (i = 0; res == B_OK && i < outCount; i++) { res = ps2_wait_write(); if (res == B_OK) ps2_write_data(out[i]); else TRACE("ps2: ps2_command out byte %d failed\n", i); } for (i = 0; res == B_OK && i < inCount; i++) { res = ps2_wait_read(); if (res == B_OK) in[i] = ps2_read_data(); else TRACE("ps2: ps2_command in byte %d failed\n", i); } #ifdef TRACE_PS2_COMMON for (i = 0; i < inCount; i++) TRACE("ps2: ps2_command in 0x%02x\n", in[i]); TRACE("ps2: ps2_command result 0x%08" B_PRIx32 "\n", res); #endif atomic_add(&sIgnoreInterrupts, -1); release_sem(gControllerSem); return res; } // #pragma mark - static int32 ps2_interrupt(void* cookie) { uint8 ctrl; uint8 data; bool error; ps2_dev *dev; ctrl = ps2_read_ctrl(); if (!(ctrl & PS2_STATUS_OUTPUT_BUFFER_FULL)) { TRACE("ps2: ps2_interrupt unhandled, OBF bit unset, ctrl 0x%02x (%s)\n", ctrl, (ctrl & PS2_STATUS_AUX_DATA) ? "aux" : "keyb"); return B_UNHANDLED_INTERRUPT; } if (atomic_get(&sIgnoreInterrupts)) { TRACE("ps2: ps2_interrupt ignoring, ctrl 0x%02x (%s)\n", ctrl, (ctrl & PS2_STATUS_AUX_DATA) ? "aux" : "keyb"); return B_HANDLED_INTERRUPT; } data = ps2_read_data(); if ((ctrl & PS2_STATUS_AUX_DATA) != 0) { uint8 idx; if (gActiveMultiplexingEnabled) { idx = ctrl >> 6; error = (ctrl & 0x04) != 0; TRACE("ps2: ps2_interrupt ctrl 0x%02x, data 0x%02x (mouse %d)\n", ctrl, data, idx); } else { idx = 0; error = (ctrl & 0xC0) != 0; TRACE("ps2: ps2_interrupt ctrl 0x%02x, data 0x%02x (aux)\n", ctrl, data); } dev = &ps2_device[PS2_DEVICE_MOUSE + idx]; } else { TRACE("ps2: ps2_interrupt ctrl 0x%02x, data 0x%02x (keyb)\n", ctrl, data); dev = &ps2_device[PS2_DEVICE_KEYB]; error = (ctrl & 0xC0) != 0; } dev->history[1] = dev->history[0]; dev->history[0].time = system_time(); dev->history[0].data = data; dev->history[0].error = error; return ps2_dev_handle_int(dev); } // #pragma mark - driver interface status_t ps2_init(void) { status_t status; TRACE("ps2: init\n"); status = get_module(B_ISA_MODULE_NAME, (module_info **)&gIsa); if (status < B_OK) return status; gControllerSem = create_sem(1, "ps/2 keyb ctrl"); ps2_flush(); status = ps2_dev_init(); if (status < B_OK) goto err1; status = ps2_service_init(); if (status < B_OK) goto err2; status = install_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt, NULL, 0); if (status) goto err3; status = install_io_interrupt_handler(INT_PS2_MOUSE, &ps2_interrupt, NULL, 0); if (status) goto err4; // While this might have fixed bug #1185, we can't do this unconditionally // as it obviously messes up many controllers which couldn't reboot anymore // after that //ps2_selftest(); // Setup the command byte with disabled keyboard and AUX interrupts // to prevent interrupts storm on some KBCs during active multiplexing // activation procedure. Fixes #7635. status = ps2_setup_command_byte(false); if (status) { INFO("ps2: initial setup of command byte failed\n"); goto err5; } ps2_flush(); status = ps2_setup_active_multiplexing(&gActiveMultiplexingEnabled); if (status) { INFO("ps2: setting up active multiplexing failed\n"); goto err5; } status = ps2_setup_command_byte(true); if (status) { INFO("ps2: setting up command byte with enabled interrupts failed\n"); goto err5; } if (gActiveMultiplexingEnabled) { // The multiplexing spec recommends to leave device 0 unconnected because it saves some // confusion with the use of the D3 command which appears as if the replied data was // coming from device 0. So we enable it only if it really looks like there is a device // connected there. if (ps2_dev_command_timeout(&ps2_device[PS2_DEVICE_MOUSE], PS2_CMD_MOUSE_SET_SCALE11, NULL, 0, NULL, 0, 100000) == B_TIMED_OUT) { INFO("ps2: accessing multiplexed mouse port 0 timed out, ignoring it!\n"); } else { ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE]); } for (int idx = 1; idx <= 3; idx++) { ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE + idx]); } ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_KEYB]); } else { ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_MOUSE]); ps2_service_notify_device_added(&ps2_device[PS2_DEVICE_KEYB]); } gSetupComplete = true; TRACE("ps2: init done!\n"); return B_OK; err5: remove_io_interrupt_handler(INT_PS2_MOUSE, &ps2_interrupt, NULL); err4: remove_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt, NULL); err3: ps2_service_exit(); err2: ps2_dev_exit(); err1: delete_sem(gControllerSem); put_module(B_ISA_MODULE_NAME); TRACE("ps2: init failed!\n"); return B_ERROR; } void ps2_uninit(void) { TRACE("ps2: uninit\n"); remove_io_interrupt_handler(INT_PS2_MOUSE, &ps2_interrupt, NULL); remove_io_interrupt_handler(INT_PS2_KEYBOARD, &ps2_interrupt, NULL); ps2_service_exit(); ps2_dev_exit(); delete_sem(gControllerSem); put_module(B_ISA_MODULE_NAME); }