diff --git a/src/add-ons/kernel/drivers/input/ps2_hid_new/Jamfile b/src/add-ons/kernel/drivers/input/ps2_hid_new/Jamfile new file mode 100644 index 0000000000..56c3ba6f5e --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid_new/Jamfile @@ -0,0 +1,29 @@ +SubDir HAIKU_TOP src add-ons kernel drivers input ps2_hid ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +UsePrivateHeaders input ; +UsePrivateHeaders kernel ; + +if ! $(HAIKU_COMPATIBLE) { +SubDirCcFlags -DCOMPILE_FOR_R5 ; + +KernelAddon ps2_hid : kernel drivers bin : + common.c + keyboard.c + mouse.c +; +} else { +KernelAddon ps2_hid : kernel drivers bin : + common.c + keyboard.c + mouse.c + packet_buffer.cpp +; +} + +Package haiku-inputkit-cvs : + ps2_hid : + boot home config add-ons kernel drivers bin ; + +PackageDriverSymLink haiku-inputkit-cvs : input ps2_hid ; diff --git a/src/add-ons/kernel/drivers/input/ps2_hid_new/common.c b/src/add-ons/kernel/drivers/input/ps2_hid_new/common.c new file mode 100644 index 0000000000..49a737be60 --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid_new/common.c @@ -0,0 +1,376 @@ +/* + * Copyright 2004-2005 Haiku, Inc. + * Distributed under the terms of the Haiku License. + * + * common.c: + * PS/2 hid device driver + * Authors (in chronological order): + * Stefano Ceccherini (burton666@libero.it) + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include +#include + +#include "common.h" + + +#define DEVICE_MOUSE_NAME "input/mouse/ps2/0" +#define DEVICE_KEYBOARD_NAME "input/keyboard/at/0" + +int32 api_version = B_CUR_DRIVER_API_VERSION; + +static device_hooks sKeyboardDeviceHooks = { + keyboard_open, + keyboard_close, + keyboard_freecookie, + keyboard_ioctl, + keyboard_read, + keyboard_write, + NULL, + NULL, + NULL, + NULL +}; + +static device_hooks sMouseDeviceHooks = { + mouse_open, + mouse_close, + mouse_freecookie, + mouse_ioctl, + mouse_read, + mouse_write, + NULL, + NULL, + NULL, + NULL +}; + + +isa_module_info *gIsa = NULL; +sem_id gDeviceOpenSemaphore; + +static int32 sInitialized = 0; +static uint8 sCommandByte = 0; +static bool sKeyboardDetected = false; +static bool sMouseDetected = false; + +static sem_id sResultSemaphore; +static sem_id sResultOwnerSemaphore; +static uint8 *sResultBuffer; +static int32 sResultBytes; + + +/** Wait until the specified status bits are cleared or set, depending on the + * second parameter. + * This currently busy waits, but should nonetheless be avoided in interrupt + * handlers. + */ + +static status_t +wait_for_status(int32 bits, bool set) +{ + int8 read; + int32 tries = 100; + + TRACE(("wait_write_ctrl(bits = %lx, %s)\n", bits, set ? "set" : "cleared")); + + while (tries-- > 0) { + read = gIsa->read_io_8(PS2_PORT_CTRL); + if (((read & bits) == bits) == set) + return B_OK; + + spin(100); + } + + return B_ERROR; +} + + +static inline status_t +wait_for_bits_cleared(int32 bits) +{ + return wait_for_status(bits, false); +} + + +static inline status_t +wait_for_bits_set(int32 bits) +{ + return wait_for_status(bits, true); +} + + +/** Reads the command byte from the 8042 controller. + * Since the read goes through the data port, this function must not be + * called when the keyboard driver is up and running. + */ + +static status_t +get_command_byte(uint8 *data) +{ + TRACE(("get_command_byte()\n")); + + if (ps2_write_ctrl(PS2_CTRL_READ_CMD) == B_OK) + return ps2_read_data(data); + + return B_ERROR; +} + + +// #pragma mark - + + +/** Wait until the control port is ready to be written. This requires that + * the "Input buffer full" and "Output buffer full" bits will both be set + * to 0. Returns true if the control port is ready to be written, false + * if 10ms have passed since the function has been called, and the control + * port is still busy. + */ + +status_t +ps2_write_ctrl(uint8 data) +{ + if (wait_for_bits_cleared(PS2_STATUS_INPUT_BUFFER_FULL | PS2_STATUS_OUTPUT_BUFFER_FULL) != B_OK) + return B_ERROR; + + gIsa->write_io_8(PS2_PORT_CTRL, data); + return B_OK; +} + + +/** Wait until the data port is ready to be written, and then writes \a data to it. + */ + +status_t +ps2_write_data(uint8 data) +{ + if (wait_for_bits_cleared(PS2_STATUS_INPUT_BUFFER_FULL) != B_OK) + return B_ERROR; + + gIsa->write_io_8(PS2_PORT_DATA, data); + return B_OK; +} + + +/** Wait until the data port can be read from, and then transfers the byte read + * to /a data. + */ + +status_t +ps2_read_data(uint8 *data) +{ + if (wait_for_bits_set(PS2_STATUS_OUTPUT_BUFFER_FULL) != B_OK) + return B_ERROR; + + *data = gIsa->read_io_8(PS2_PORT_DATA); + TRACE(("ps2_read_data(): read %u\n", *data)); + return B_OK; +} + + +/** Get the PS/2 command byte. This cannot fail, since we're using our buffered + * data, read out in init_driver(). + */ + +uint8 +ps2_get_command_byte(void) +{ + TRACE(("ps2_get_command_byte(): command byte = %x\n", sCommandByte)); + + return sCommandByte; +} + + +/** Set the ps2 command byte. + */ + +status_t +ps2_set_command_byte(uint8 command) +{ + TRACE(("set_command_byte(command = %x)\n", command)); + + if (ps2_write_ctrl(PS2_CTRL_WRITE_CMD) == B_OK + && ps2_write_data(command) == B_OK) { + sCommandByte = command; + return B_OK; + } + + return B_ERROR; +} + + +bool +ps2_handle_result(uint8 data) +{ + int32 bytesLeft; + + if (sResultBuffer == NULL + || (bytesLeft = atomic_add(&sResultBytes, -1)) <= 0) + return false; + + *(sResultBuffer++) = data; + if (bytesLeft == 1) + release_sem_etc(sResultSemaphore, 1, B_DO_NOT_RESCHEDULE); + return true; +} + + +void +ps2_claim_result(uint8 *buffer, size_t bytes) +{ + acquire_sem(sResultOwnerSemaphore); + + sResultBuffer = buffer; + sResultBytes = bytes; +} + + +void +ps2_unclaim_result(void) +{ + sResultBytes = 0; + sResultBuffer = NULL; + + release_sem(sResultOwnerSemaphore); +} + + +status_t +ps2_wait_for_result(void) +{ + status_t status = acquire_sem_etc(sResultSemaphore, 1, B_RELATIVE_TIMEOUT, 100000); + // 0.1 secs for now + + ps2_unclaim_result(); + return status; +} + + +void +ps2_common_uninitialize(void) +{ + // do we still need the resources? + if (atomic_add(&sInitialized, -1) > 1) + return; + + delete_sem(sResultSemaphore); + delete_sem(sResultOwnerSemaphore); +} + + +status_t +ps2_common_initialize(void) +{ + if (atomic_add(&sInitialized, 1) > 0) { + // we're already initialized + return B_OK; + } + + sResultSemaphore = create_sem(0, "ps/2 result"); + if (sResultSemaphore < B_OK) + return sResultSemaphore; + + sResultOwnerSemaphore = create_sem(1, "ps/2 result owner"); + if (sResultOwnerSemaphore < B_OK) { + delete_sem(sResultSemaphore); + return sResultOwnerSemaphore; + } + + sResultBytes = 0; + sResultBuffer = NULL; + + return B_OK; +} + + +// #pragma mark - +// driver interface + + +status_t +init_hardware(void) +{ + return B_OK; +} + + +const char ** +publish_devices(void) +{ + static char *kDevices[3]; + int index = 0; + + if (sMouseDetected) + kDevices[index++] = DEVICE_MOUSE_NAME; + + if (sKeyboardDetected) + kDevices[index++] = DEVICE_KEYBOARD_NAME; + + kDevices[index++] = NULL; + + return (const char **)kDevices; +} + + +device_hooks * +find_device(const char *name) +{ + if (!strcmp(name, DEVICE_MOUSE_NAME)) + return &sMouseDeviceHooks; + else if (!strcmp(name, DEVICE_KEYBOARD_NAME)) + return &sKeyboardDeviceHooks; + + return NULL; +} + + +status_t +init_driver(void) +{ + status_t status; + + status = get_module(B_ISA_MODULE_NAME, (module_info **)&gIsa); + if (status < B_OK) { + TRACE(("Failed getting isa module: %s\n", strerror(status))); + return status; + } + + // Try to probe for the mouse first, as this can hang the keyboard if no + // mouse is found. + // Probing the mouse first and initializing the keyboard later appearantly + // clears the keyboard stall. + + if (probe_mouse(NULL) == B_OK) + sMouseDetected = true; + else + dprintf("ps2_hid: no mouse detected!\n"); + + if (probe_keyboard() == B_OK) + sKeyboardDetected = true; + else + dprintf("ps2_hid: no keyboard detected!\n"); + + // If there is no keyboard or mouse, we don't need to publish ourselves + if (!sKeyboardDetected && !sMouseDetected) { + put_module(B_ISA_MODULE_NAME); + return B_ERROR; + } + + // A semaphore to synchronize open keyboard and mouse devices + + gDeviceOpenSemaphore = create_sem(1, "ps/2 open"); + if (gDeviceOpenSemaphore < B_OK) + return gDeviceOpenSemaphore; + + return get_command_byte(&sCommandByte); +} + + +void +uninit_driver(void) +{ + delete_sem(gDeviceOpenSemaphore); + put_module(B_ISA_MODULE_NAME); +} diff --git a/src/add-ons/kernel/drivers/input/ps2_hid_new/common.h b/src/add-ons/kernel/drivers/input/ps2_hid_new/common.h new file mode 100644 index 0000000000..ba873c904a --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid_new/common.h @@ -0,0 +1,69 @@ +/* + * Copyright 2004-2005 Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * PS/2 hid device driver + * + * Authors (in chronological order): + * Elad Lahav (elad@eldarshany.com) + * Stefano Ceccherini (burton666@libero.it) + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef __PS2_COMMON_H +#define __PS2_COMMON_H + + +#include +#include +#include + +#include "ps2.h" + + +// debug defines +#ifdef DEBUG +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + +// global variables +extern isa_module_info *gIsa; +extern sem_id gDeviceOpenSemaphore; + +// prototypes from common.c +extern status_t ps2_write_ctrl(uint8 data); +extern status_t ps2_write_data(uint8 data); +extern status_t ps2_read_data(uint8 *data); + +extern status_t ps2_write_aux_byte(uint8 data); +extern uint8 ps2_get_command_byte(void); +extern status_t ps2_set_command_byte(uint8 command); + +extern void ps2_claim_result(uint8 *buffer, size_t bytes); +extern void ps2_unclaim_result(void); +extern status_t ps2_wait_for_result(void); +extern bool ps2_handle_result(uint8 data); + +extern void ps2_common_uninitialize(void); +extern status_t ps2_common_initialize(void); + +// prototypes from keyboard.c & mouse.c +extern status_t probe_keyboard(void); +extern status_t probe_mouse(size_t *probed_packet_size); + +extern status_t keyboard_open(const char *name, uint32 flags, void **cookie); +extern status_t keyboard_close(void *cookie); +extern status_t keyboard_freecookie(void *cookie); +extern status_t keyboard_read(void *cookie, off_t pos, void *buf, size_t *len); +extern status_t keyboard_write(void * cookie, off_t pos, const void *buf, size_t *len); +extern status_t keyboard_ioctl(void *cookie, uint32 op, void *buf, size_t len); + +extern status_t mouse_open(const char *name, uint32 flags, void **cookie); +extern status_t mouse_close(void *cookie); +extern status_t mouse_freecookie(void *cookie); +extern status_t mouse_read(void *cookie, off_t pos, void *buf, size_t *len); +extern status_t mouse_write(void * cookie, off_t pos, const void *buf, size_t *len); +extern status_t mouse_ioctl(void *cookie, uint32 op, void *buf, size_t len); + +#endif /* __PS2_COMMON_H */ diff --git a/src/add-ons/kernel/drivers/input/ps2_hid_new/keyboard.c b/src/add-ons/kernel/drivers/input/ps2_hid_new/keyboard.c new file mode 100644 index 0000000000..30294936e2 --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid_new/keyboard.c @@ -0,0 +1,438 @@ +/* + * Copyright 2004-2005 Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * PS/2 keyboard device driver + * + * Authors (in chronological order): + * Stefano Ceccherini (burton666@libero.it) + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "common.h" +#include "kb_mouse_driver.h" +#include "packet_buffer.h" + +#include + + +#define KEY_BUFFER_SIZE 100 + // we will buffer 100 key strokes before we start dropping them + +enum { + LED_SCROLL = 1, + LED_NUM = 2, + LED_CAPS = 4 +} leds_status; + +enum { + PS2_DATA_SET_LEDS = 0xed, + PS2_ENABLE_KEYBOARD = 0xf4, + PS2_DISABLE_KEYBOARD = 0xf5, + + EXTENDED_KEY = 0xe0, +}; + + +static int32 sKeyboardOpenMask; +static sem_id sKeyboardSem; +static struct packet_buffer *sKeyBuffer; +static bool sIsExtended = false; +static bool sInterruptHandlerInstalled = false; + + +static status_t +keyboard_write_byte(uint8 byte) +{ + uint8 acknowledged = 0; + + TRACE(("keyboard_write_byte(byte = %u)\n", byte)); + + if (sInterruptHandlerInstalled) { + ps2_claim_result(&acknowledged, 1); + + if (ps2_write_data(byte) != B_OK) { + ps2_unclaim_result(); + return B_TIMED_OUT; + } + + ps2_wait_for_result(); + } else { + status_t status = ps2_write_data(byte); + if (status == B_OK) + status = ps2_read_data(&acknowledged); + + if (status != B_OK) + return status; + } + + return acknowledged == PS2_REPLY_ACK ? B_OK : B_ERROR; +} + + +static status_t +keyboard_read_bytes(uint8 *buffer, size_t bufferSize) +{ + uint32 i; + + TRACE(("keyboard_read_bytes(bufferSize = %lu)\n", bufferSize)); + + if (sInterruptHandlerInstalled) { + ps2_claim_result(buffer, bufferSize); + return ps2_wait_for_result(); + } + + for (i = 0; i < bufferSize; i++) { + status_t status = ps2_read_data(&buffer[i]); + if (status != B_OK) + return status; + } + + return B_OK; +} + + +static void +keyboard_empty_data(void) +{ + uint8 data; + + TRACE(("keyboard_empty_data()\n")); + + while (ps2_read_data(&data) == B_OK) { + // empty keyboard output buffer + } +} + + +static status_t +keyboard_command(uint8 command, uint8 *buffer, size_t bufferSize) +{ + status_t status; + uint32 i; + + TRACE(("keyboard_command(command = %u, bufferSize = %lu)\n", command, bufferSize)); + + status = keyboard_write_byte(command); + if (status != B_OK) + return status; + + for (i = 0; i < bufferSize; i++) { + status = keyboard_write_byte(buffer[i]); + if (status != B_OK) + return status; + } + + return B_OK; +} + + +static status_t +set_leds(led_info *ledInfo) +{ + uint8 leds = 0; + + TRACE(("ps2_hid: set keyboard LEDs\n")); + + if (ledInfo->scroll_lock) + leds |= LED_SCROLL; + if (ledInfo->num_lock) + leds |= LED_NUM; + if (ledInfo->caps_lock) + leds |= LED_CAPS; + + return keyboard_command(PS2_DATA_SET_LEDS, &leds, sizeof(leds)); +} + + +static int32 +handle_keyboard_interrupt(void *data) +{ + uint8 read = gIsa->read_io_8(PS2_PORT_CTRL); + TRACE(("handle_keyboard_interrupt: read = 0x%x\n", read)); + + if (read & PS2_STATUS_OUTPUT_BUFFER_FULL) { + at_kbd_io keyInfo; + uint8 scancode; + + read = gIsa->read_io_8(PS2_PORT_DATA); + + // someone else might wait for a result from the keyboard controller + if (ps2_handle_result(read)) + return B_INVOKE_SCHEDULER; + + // TODO: Handle braindead "pause" key special case + + if (read == EXTENDED_KEY) { + sIsExtended = true; + TRACE(("Extended key\n")); + return B_HANDLED_INTERRUPT; + } + + scancode = read; + + TRACE(("scancode: %x\n", scancode)); + + // For now, F12 enters the kernel debugger + // ToDo: remove me later :-) + if (scancode == 88) + panic("keyboard requested halt.\n"); + + if (scancode & 0x80) { + keyInfo.is_keydown = false; + scancode -= 0x80; + } else + keyInfo.is_keydown = true; + + if (sIsExtended) { + scancode |= 0x80; + sIsExtended = false; + } + + keyInfo.timestamp = system_time(); + keyInfo.scancode = scancode; + + if (packet_buffer_write(sKeyBuffer, (uint8 *)&keyInfo, sizeof(keyInfo)) == 0) { + // If there is no space left in the buffer, we drop this key stroke. We avoid + // dropping old key strokes, to not destroy what already was typed. + return B_HANDLED_INTERRUPT; + } + + release_sem_etc(sKeyboardSem, 1, B_DO_NOT_RESCHEDULE); + } else { + // ToDo: the buffer is not yet available, we should come back soon... + // (depending on how often we see the message below... :-) + dprintf("ps2_hid: keyboard: buffer not available!\n"); + } + + return B_INVOKE_SCHEDULER; +} + + +static status_t +read_keyboard_packet(at_kbd_io *userBuffer) +{ + at_kbd_io packet; + status_t status; + + TRACE(("read_keyboard_packet()\n")); + + status = acquire_sem_etc(sKeyboardSem, 1, B_CAN_INTERRUPT, 0); + if (status < B_OK) + return status; + + if (packet_buffer_read(sKeyBuffer, (uint8 *)&packet, sizeof(at_kbd_io)) == 0) { + TRACE(("read_keyboard_packet(): error reading packet: %s\n", strerror(status))); + return B_ERROR; + } + + TRACE(("scancode: %x, keydown: %s\n", packet.scancode, packet.is_keydown ? "true" : "false")); + + return user_memcpy(userBuffer, &packet, sizeof(at_kbd_io)); +} + + +static status_t +enable_keyboard(void) +{ + uint32 tries = 3; + + while (tries-- > 0) { + keyboard_empty_data(); + + if (keyboard_command(PS2_ENABLE_KEYBOARD, NULL, 0) == B_OK) + return B_OK; + } + + dprintf("enable_keyboard() failed\n"); + return B_ERROR; +} + + +// #pragma mark - + + +status_t +probe_keyboard(void) +{ + int32 tries; + + // ToDo: for now there just is a keyboard ready to be used... + + keyboard_empty_data(); + + // Keyboard detection does not seem to be working always correctly +#if 0 + // Keyboard self-test + + if (ps2_write_ctrl(PS2_CTRL_KEYBOARD_SELF_TEST) != B_OK) + return B_ERROR; + + tries = 3; + while (tries-- > 0) { + uint8 acknowledged; + if (ps2_read_data(&acknowledged) == B_OK && acknowledged == PS2_REPLY_ACK) + break; + } + + // This selftest appears to fail quite frequently we'll just disable it + /*if (tries < 0) + return B_ERROR;*/ + + // Activate keyboard + + if (ps2_write_ctrl(PS2_CTRL_KEYBOARD_ACTIVATE) != B_OK) + return B_ERROR; + + // Enable keyboard + + return enable_keyboard(); +#endif + return B_OK; +} + + +// #pragma mark - + + +status_t +keyboard_open(const char *name, uint32 flags, void **_cookie) +{ + uint8 commandByte; + status_t status; + + TRACE(("keyboard open()\n")); + + if (atomic_or(&sKeyboardOpenMask, 1) != 0) + return B_BUSY; + + acquire_sem(gDeviceOpenSemaphore); + + status = ps2_common_initialize(); + if (status != B_OK) + goto err1; + + sKeyboardSem = create_sem(0, "keyboard_sem"); + if (sKeyboardSem < 0) { + status = sKeyboardSem; + goto err2; + } + + sKeyBuffer = create_packet_buffer(KEY_BUFFER_SIZE * sizeof(at_kbd_io)); + if (sKeyBuffer == NULL) { + status = B_NO_MEMORY; + goto err3; + } + + keyboard_empty_data(); + + commandByte = ps2_get_command_byte() + | PS2_BITS_KEYBOARD_INTERRUPT | PS2_BITS_TRANSLATE_SCANCODES; + commandByte &= ~PS2_BITS_KEYBOARD_DISABLED; + + status = ps2_set_command_byte(commandByte); + if (status < B_OK) { + TRACE(("keyboard_open(): cannot set command byte\n")); + goto err4; + } + + status = install_io_interrupt_handler(INT_PS2_KEYBOARD, + &handle_keyboard_interrupt, NULL, 0); + if (status < B_OK) + goto err4; + + sInterruptHandlerInstalled = true; + release_sem(gDeviceOpenSemaphore); + + *_cookie = NULL; + + TRACE(("keyboard_open(): done.\n")); + return B_OK; + +err4: + delete_packet_buffer(sKeyBuffer); +err3: + delete_sem(sKeyboardSem); +err2: + ps2_common_uninitialize(); +err1: + atomic_and(&sKeyboardOpenMask, 0); + release_sem(gDeviceOpenSemaphore); + + return status; +} + + +status_t +keyboard_close(void *cookie) +{ + TRACE(("keyboard_close()\n")); + + remove_io_interrupt_handler(INT_PS2_KEYBOARD, &handle_keyboard_interrupt, NULL); + sInterruptHandlerInstalled = false; + + delete_packet_buffer(sKeyBuffer); + delete_sem(sKeyboardSem); + + ps2_common_uninitialize(); + atomic_and(&sKeyboardOpenMask, 0); + + return B_OK; +} + + +status_t +keyboard_freecookie(void *cookie) +{ + return B_OK; +} + + +status_t +keyboard_read(void *cookie, off_t pos, void *buffer, size_t *_length) +{ + TRACE(("keyboard read()\n")); + *_length = 0; + return B_NOT_ALLOWED; +} + + +status_t +keyboard_write(void *cookie, off_t pos, const void *buffer, size_t *_length) +{ + TRACE(("keyboard write()\n")); + *_length = 0; + return B_NOT_ALLOWED; +} + + +status_t +keyboard_ioctl(void *cookie, uint32 op, void *buffer, size_t length) +{ + TRACE(("keyboard ioctl()\n")); + switch (op) { + case KB_READ: + TRACE(("KB_READ\n")); + return read_keyboard_packet((at_kbd_io *)buffer); + + case KB_SET_LEDS: + { + led_info info; + if (user_memcpy(&info, buffer, sizeof(led_info)) < B_OK) + return B_BAD_ADDRESS; + + TRACE(("KB_SET_LEDS\n")); + return set_leds(&info); + } + + case KB_SET_KEY_REPEATING: + case KB_SET_KEY_NONREPEATING: + TRACE(("ps2_hid: ioctl 0x%lx not implemented yet, returning B_OK\n", op)); + return B_OK; + + default: + TRACE(("ps2_hid: invalid ioctl 0x%lx\n", op)); + return EINVAL; + } +} diff --git a/src/add-ons/kernel/drivers/input/ps2_hid_new/mouse.c b/src/add-ons/kernel/drivers/input/ps2_hid_new/mouse.c new file mode 100644 index 0000000000..db0699200c --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid_new/mouse.c @@ -0,0 +1,506 @@ +/* + * Copyright 2001-2005 Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * PS/2 mouse device driver + * + * Authors (in chronological order): + * Elad Lahav (elad@eldarshany.com) + * Stefano Ceccherini (burton666@libero.it) + * Axel Dörfler, axeld@pinc-software.de + */ + +/* + * A PS/2 mouse is connected to the IBM 8042 controller, and gets its + * name from the IBM PS/2 personal computer, which was the first to + * use this device. All resources are shared between the keyboard, and + * the mouse, referred to as the "Auxiliary Device". + * + * I/O: + * ~~~ + * The controller has 3 I/O registers: + * 1. Status (input), mapped to port 64h + * 2. Control (output), mapped to port 64h + * 3. Data (input/output), mapped to port 60h + * + * Data: + * ~~~~ + * A packet read from the mouse data port is composed of + * three bytes: + * byte 0: status byte, where + * - bit 7: Y overflow (1 = true) + * - bit 6: X overflow (1 = true) + * - bit 5: MSB of Y offset + * - bit 4: MSB of X offset + * - bit 3: Syncronization bit (always 1) + * - bit 2: Middle button (1 = down) + * - bit 1: Right button (1 = down) + * - bit 0: Left button (1 = down) + * byte 1: X position change, since last probed (-127 to +127) + * byte 2: Y position change, since last probed (-127 to +127) + * + * Intellimouse mice send a four byte packet, where the first three + * bytes are the same as standard mice, and the last one reports the + * Z position, which is, usually, the wheel movement. + * + * Interrupts: + * ~~~~~~~~~~ + * The PS/2 mouse device is connected to interrupt 12, which means that + * it uses the second interrupt controller (handles INT8 to INT15). In + * order for this interrupt to be enabled, both the 5th interrupt of + * the second controller AND the 3rd interrupt of the first controller + * (cascade mode) should be unmasked. + * This is all done inside install_io_interrupt_handler(), no need to + * worry about it anymore + * The controller uses 3 consecutive interrupts to inform the computer + * that it has new data. On the first the data register holds the status + * byte, on the second the X offset, and on the 3rd the Y offset. + */ + + +#include +#include + +#include "kb_mouse_driver.h" +#include "common.h" +#include "packet_buffer.h" + + +#define MOUSE_HISTORY_SIZE 256 + // we record that many mouse packets before we start to drop them + +static sem_id sMouseSem; +static struct packet_buffer *sMouseBuffer; +static int32 sOpenMask; + +static bigtime_t sLastClickTime; +static bigtime_t sClickSpeed; +static int32 sClickCount; +static int sButtonsState; + +static size_t sPacketSize; +static size_t sPacketIndex; +static uint8 sPacketBuffer[PS2_MAX_PACKET_SIZE]; + + +/** Writes a byte to the mouse device. Uses the control port to indicate + * that the byte is sent to the auxiliary device (mouse), instead of the + * keyboard. + */ + +status_t +ps2_write_aux_byte(uint8 data) +{ + TRACE(("ps2_write_aux_byte(data = %u)\n", data)); + + if (ps2_write_ctrl(PS2_CTRL_WRITE_AUX) == B_OK + && ps2_write_data(data) == B_OK + && ps2_read_data(&data) == B_OK) + return data == PS2_REPLY_ACK ? B_OK : B_TIMED_OUT; + + return B_ERROR; +} + + +/* +static status_t +ps2_reset_mouse() +{ + int8 read; + + TRACE(("ps2_reset_mouse()\n")); + + write_aux_byte(PS2_CMD_RESET_MOUSE); + read = read_data_byte(); + + TRACE(("reset mouse: %2x\n", read)); + return B_OK; +} +*/ + + +/** Set sampling rate of the ps2 port. + */ + +static status_t +ps2_set_sample_rate(uint32 rate) +{ + int32 tries = 5; + + while (--tries > 0) { + status_t status = ps2_write_aux_byte(PS2_CMD_SET_SAMPLE_RATE); + if (status == B_OK) + status = ps2_write_aux_byte(rate); + + if (status == B_OK) + return B_OK; + } + + return B_ERROR; +} + + +/** Converts a packet received by the mouse to a "movement". + */ + +static void +ps2_packet_to_movement(uint8 packet[], mouse_movement *pos) +{ + int buttons = packet[0] & 7; + int xDelta = ((packet[0] & 0x10) ? 0xFFFFFF00 : 0) | packet[1]; + int yDelta = ((packet[0] & 0x20) ? 0xFFFFFF00 : 0) | packet[2]; + bigtime_t currentTime = system_time(); + int8 wheel_ydelta = 0; + int8 wheel_xdelta = 0; + + if (buttons != 0 && sButtonsState == 0) { + if (sLastClickTime + sClickSpeed > currentTime) + sClickCount++; + else + sClickCount = 1; + } + + sLastClickTime = currentTime; + sButtonsState = buttons; + + if (sPacketSize == PS2_PACKET_INTELLIMOUSE) { + switch (packet[3] & 0x0F) { + case 0x01: wheel_ydelta = +1; break; // wheel 1 down + case 0x0F: wheel_ydelta = -1; break; // wheel 1 up + case 0x02: wheel_xdelta = +1; break; // wheel 2 down + case 0x0E: wheel_xdelta = -1; break; // wheel 2 up + } + } + +// dprintf("packet: %02x %02x %02x %02x: xd %d, yd %d, 0x%x (%d), w-xd %d, w-yd %d\n", +// packet[0], packet[1], packet[2], packet[3], +// xDelta, yDelta, buttons, sClickCount, wheel_xdelta, wheel_ydelta); + + if (pos) { + pos->xdelta = xDelta; + pos->ydelta = yDelta; + pos->buttons = buttons; + pos->clicks = sClickCount; + pos->modifiers = 0; + pos->timestamp = currentTime; + pos->wheel_ydelta = (int)wheel_ydelta; + pos->wheel_xdelta = (int)wheel_xdelta; + + TRACE(("xdelta: %d, ydelta: %d, buttons %x, clicks: %ld, timestamp %Ld\n", + xDelta, yDelta, buttons, sClickCount, currentTime)); + } +} + + +/** Read a mouse event from the mouse events chain buffer. + */ + +static status_t +ps2_mouse_read(mouse_movement *userMovement) +{ + uint8 packet[PS2_MAX_PACKET_SIZE]; + mouse_movement movement; + status_t status; + + TRACE(("ps2_mouse_read()\n")); + status = acquire_sem_etc(sMouseSem, 1, B_CAN_INTERRUPT, 0); + if (status < B_OK) + return status; + + if (packet_buffer_read(sMouseBuffer, packet, sPacketSize) != sPacketSize) { + TRACE(("error copying buffer\n")); + return B_ERROR; + } + + if (!(packet[0] & 8)) + panic("ps2_hid: got broken data from packet_buffer_read\n"); + + ps2_packet_to_movement(packet, &movement); + + return user_memcpy(userMovement, &movement, sizeof(mouse_movement)); +} + + +/** Enables or disables mouse reporting for the PS/2 port. + */ + +static status_t +set_mouse_enabled(bool enable) +{ + int32 tries = 5; + + while (true) { + if (ps2_write_aux_byte(enable ? + PS2_CMD_ENABLE_MOUSE : PS2_CMD_DISABLE_MOUSE) == B_OK) + return B_OK; + + if (--tries <= 0) + break; + } + + return B_ERROR; +} + + +/** Interrupt handler for the mouse device. Called whenever the I/O + * controller generates an interrupt for the PS/2 mouse. Reads mouse + * information from the data port, and stores it, so it can be accessed + * by read() operations. The full data is obtained using 3 consecutive + * calls to the handler, each holds a different byte on the data port. + */ + +static int32 +handle_mouse_interrupt(void* cookie) +{ + uint8 data = gIsa->read_io_8(PS2_PORT_CTRL); + if (!(data & PS2_STATUS_OUTPUT_BUFFER_FULL)) { + TRACE(("no ps2 mouse data available\n")); + return B_UNHANDLED_INTERRUPT; + } + + data = gIsa->read_io_8(PS2_PORT_DATA); + TRACE(("mouse interrupt: %d byte: 0x%02x\n", sPacketIndex, data)); + + if (sPacketIndex == 0 && !(data & 8)) { + TRACE(("bad mouse data, trying resync\n")); + return B_HANDLED_INTERRUPT; + } + + sPacketBuffer[sPacketIndex++] = data; + + if (sPacketIndex != sPacketSize) { + // packet not yet complete + return B_HANDLED_INTERRUPT; + } + + // complete packet is assembled + + sPacketIndex = 0; + + if (packet_buffer_write(sMouseBuffer, sPacketBuffer, sPacketSize) != sPacketSize) { + // buffer is full, drop new data + return B_HANDLED_INTERRUPT; + } + + release_sem_etc(sMouseSem, 1, B_DO_NOT_RESCHEDULE); + return B_INVOKE_SCHEDULER; +} + + +// #pragma mark - + + +status_t +probe_mouse(size_t *probed_packet_size) +{ + int8 deviceId = -1; + + // get device id + if (ps2_write_aux_byte(PS2_CMD_GET_DEVICE_ID) == B_OK) + ps2_read_data(&deviceId); + + TRACE(("probe_mouse(): device id: %2x\n", deviceId)); + + if (deviceId == 0) { + int32 tries = 5; + + while (--tries > 0) { + // try to switch to intellimouse mode + if (ps2_set_sample_rate(200) == B_OK + && ps2_set_sample_rate(100) == B_OK + && ps2_set_sample_rate(80) == B_OK) { + // get device id, again + if (ps2_write_aux_byte(PS2_CMD_GET_DEVICE_ID) == B_OK) + ps2_read_data(&deviceId); + break; + } + } + } + + if (deviceId == PS2_DEV_ID_STANDARD) { + TRACE(("Standard PS/2 mouse found\n")); + if (probed_packet_size) + *probed_packet_size = PS2_PACKET_STANDARD; + } else if (deviceId == PS2_DEV_ID_INTELLIMOUSE) { + TRACE(("Extended PS/2 mouse found\n")); + if (probed_packet_size) + *probed_packet_size = PS2_PACKET_INTELLIMOUSE; + } else { + // Something's wrong. Better quit + return B_ERROR; + } + + return B_OK; +} + + +// #pragma mark - +// Device functions + + +status_t +mouse_open(const char *name, uint32 flags, void **_cookie) +{ + status_t status; + int8 commandByte; + + TRACE(("mouse_open()\n")); + + if (atomic_or(&sOpenMask, 1) != 0) + return B_BUSY; + + acquire_sem(gDeviceOpenSemaphore); + + status = probe_mouse(&sPacketSize); + if (status != B_OK) + goto err1; + + sPacketIndex = 0; + + status = ps2_common_initialize(); + if (status != B_OK) + goto err1; + + sMouseBuffer = create_packet_buffer(MOUSE_HISTORY_SIZE * sPacketSize); + if (sMouseBuffer == NULL) { + TRACE(("can't allocate mouse actions buffer\n")); + status = B_NO_MEMORY; + goto err2; + } + + // create the mouse semaphore, used for synchronization between + // the interrupt handler and the read operation + sMouseSem = create_sem(0, "ps2_mouse_sem"); + if (sMouseSem < 0) { + TRACE(("failed creating PS/2 mouse semaphore!\n")); + status = sMouseSem; + goto err3; + } + + *_cookie = NULL; + + commandByte = ps2_get_command_byte() | PS2_BITS_AUX_INTERRUPT; + commandByte &= ~PS2_BITS_MOUSE_DISABLED; + + status = ps2_set_command_byte(commandByte); + if (status < B_OK) { + TRACE(("mouse_open(): sending command byte failed\n")); + goto err4; + } + + status = set_mouse_enabled(true); + if (status < B_OK) { + TRACE(("mouse_open(): cannot enable PS/2 mouse\n")); + goto err4; + } + + status = install_io_interrupt_handler(INT_PS2_MOUSE, + handle_mouse_interrupt, NULL, 0); + if (status < B_OK) { + TRACE(("mouse_open(): cannot install interrupt handler\n")); + goto err4; + } + + release_sem(gDeviceOpenSemaphore); + + TRACE(("mouse_open(): mouse succesfully enabled\n")); + return B_OK; + +err4: + delete_sem(sMouseSem); +err3: + delete_packet_buffer(sMouseBuffer); +err2: + ps2_common_uninitialize(); +err1: + atomic_and(&sOpenMask, 0); + release_sem(gDeviceOpenSemaphore); + + return status; +} + + +status_t +mouse_close(void *cookie) +{ + TRACE(("mouse_close()\n")); + + set_mouse_enabled(false); + + delete_packet_buffer(sMouseBuffer); + delete_sem(sMouseSem); + + remove_io_interrupt_handler(INT_PS2_MOUSE, handle_mouse_interrupt, NULL); + + ps2_common_uninitialize(); + atomic_and(&sOpenMask, 0); + + return B_OK; +} + + +status_t +mouse_freecookie(void *cookie) +{ + return B_OK; +} + + +status_t +mouse_read(void *cookie, off_t pos, void *buffer, size_t *_length) +{ + *_length = 0; + return B_NOT_ALLOWED; +} + + +status_t +mouse_write(void *cookie, off_t pos, const void *buffer, size_t *_length) +{ + *_length = 0; + return B_NOT_ALLOWED; +} + + +status_t +mouse_ioctl(void *cookie, uint32 op, void *buffer, size_t length) +{ + switch (op) { + case MS_NUM_EVENTS: + { + int32 count; + TRACE(("MS_NUM_EVENTS\n")); + get_sem_count(sMouseSem, &count); + return count; + } + + case MS_READ: + TRACE(("MS_READ\n")); + return ps2_mouse_read((mouse_movement *)buffer); + + case MS_SET_TYPE: + TRACE(("MS_SET_TYPE not implemented\n")); + return B_BAD_VALUE; + + case MS_SET_MAP: + TRACE(("MS_SET_MAP (set mouse mapping) not implemented\n")); + return B_BAD_VALUE; + + case MS_GET_ACCEL: + TRACE(("MS_GET_ACCEL (get mouse acceleration) not implemented\n")); + return B_BAD_VALUE; + + case MS_SET_ACCEL: + TRACE(("MS_SET_ACCEL (set mouse acceleration) not implemented\n")); + return B_BAD_VALUE; + + case MS_SET_CLICKSPEED: + TRACE(("MS_SETCLICK (set click speed)\n")); + return user_memcpy(&sClickSpeed, buffer, sizeof(bigtime_t)); + + default: + TRACE(("unknown opcode: %ld\n", op)); + return B_BAD_VALUE; + } +} + diff --git a/src/add-ons/kernel/drivers/input/ps2_hid_new/packet_buffer.cpp b/src/add-ons/kernel/drivers/input/ps2_hid_new/packet_buffer.cpp new file mode 100644 index 0000000000..6bf52e08d2 --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid_new/packet_buffer.cpp @@ -0,0 +1,141 @@ +/* + * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include "packet_buffer.h" + +#include +#include + +#include +#include + + +/** The idea behind this packet buffer is to have multi-threading safe + * implementation that can be used in interrupts on top of the + * ring_buffer implementation provided by the kernel. + * It uses a spinlock for synchronization. + * + * IOW if you don't have such high restrictions in your environment, + * you better don't want to use it at all. + */ + +struct packet_buffer { + struct ring_buffer *buffer; + spinlock lock; +}; + + +struct packet_buffer * +create_packet_buffer(size_t size) +{ + struct packet_buffer *buffer = (packet_buffer *)malloc(sizeof(packet_buffer)); + if (buffer == NULL) + return NULL; + + buffer->buffer = create_ring_buffer(size); + if (buffer->buffer == NULL) { + free(buffer); + return NULL; + } + buffer->lock = 0; + + return buffer; +} + + +void +delete_packet_buffer(struct packet_buffer *buffer) +{ + delete_ring_buffer(buffer->buffer); + free(buffer); +} + + +void +packet_buffer_clear(struct packet_buffer *buffer) +{ + cpu_status state = disable_interrupts(); + acquire_spinlock(&buffer->lock); + + ring_buffer_clear(buffer->buffer); + + release_spinlock(&buffer->lock); + restore_interrupts(state); +} + + +size_t +packet_buffer_readable(struct packet_buffer *buffer) +{ + cpu_status state = disable_interrupts(); + acquire_spinlock(&buffer->lock); + + size_t available = ring_buffer_readable(buffer->buffer); + + release_spinlock(&buffer->lock); + restore_interrupts(state); + + return available; +} + + +size_t +packet_buffer_writable(struct packet_buffer *buffer) +{ + cpu_status state = disable_interrupts(); + acquire_spinlock(&buffer->lock); + + size_t left = ring_buffer_writable(buffer->buffer); + + release_spinlock(&buffer->lock); + restore_interrupts(state); + + return left; +} + + +void +packet_buffer_flush(struct packet_buffer *buffer, size_t length) +{ + cpu_status state = disable_interrupts(); + acquire_spinlock(&buffer->lock); + + ring_buffer_flush(buffer->buffer, length); + + release_spinlock(&buffer->lock); + restore_interrupts(state); +} + + +size_t +packet_buffer_read(struct packet_buffer *buffer, uint8 *data, size_t length) +{ + cpu_status state = disable_interrupts(); + acquire_spinlock(&buffer->lock); + + size_t bytesRead = ring_buffer_read(buffer->buffer, data, length); + + release_spinlock(&buffer->lock); + restore_interrupts(state); + + return bytesRead; +} + + +size_t +packet_buffer_write(struct packet_buffer *buffer, const uint8 *data, size_t length) +{ + cpu_status state = disable_interrupts(); + acquire_spinlock(&buffer->lock); + + size_t bytesWritten = ring_buffer_write(buffer->buffer, data, length); + + release_spinlock(&buffer->lock); + restore_interrupts(state); + + return bytesWritten; +} + diff --git a/src/add-ons/kernel/drivers/input/ps2_hid_new/packet_buffer.h b/src/add-ons/kernel/drivers/input/ps2_hid_new/packet_buffer.h new file mode 100644 index 0000000000..97a3f6b6ec --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid_new/packet_buffer.h @@ -0,0 +1,32 @@ +/* + * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef PACKET_BUFFER_H +#define PACKET_BUFFER_H + + +#include + + +struct packet_buffer; + +#ifdef __cplusplus +extern "C" { +#endif + +struct packet_buffer *create_packet_buffer(size_t size); +void delete_packet_buffer(struct packet_buffer *buffer); + +void packet_buffer_clear(struct packet_buffer *buffer); +size_t packet_buffer_readable(struct packet_buffer *buffer); +size_t packet_buffer_writable(struct packet_buffer *buffer); +void packet_buffer_flush(struct packet_buffer *buffer, size_t bytes); +size_t packet_buffer_read(struct packet_buffer *buffer, uint8 *data, size_t length); +size_t packet_buffer_write(struct packet_buffer *buffer, const uint8 *data, size_t length); + +#ifdef __cplusplus +} +#endif + +#endif /* PACKET_BUFFER_H */ diff --git a/src/add-ons/kernel/drivers/input/ps2_hid_new/ps2.h b/src/add-ons/kernel/drivers/input/ps2_hid_new/ps2.h new file mode 100644 index 0000000000..f5d5c20b35 --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid_new/ps2.h @@ -0,0 +1,77 @@ +/* + * Copyright 2004-2005 Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * PS/2 interface definitions + * + * Authors (in chronological order): + * Elad Lahav (elad@eldarshany.com) + * Stefano Ceccherini (burton666@libero.it) + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef _PS2_H +#define _PS2_H + + +/** Interface definitions for the Intel 8042, 8741, or 8742 (PS/2) */ + +// I/O addresses +#define PS2_PORT_DATA 0x60 +#define PS2_PORT_CTRL 0x64 + +// data port bits +#define PS2_STATUS_OUTPUT_BUFFER_FULL 0x01 +#define PS2_STATUS_INPUT_BUFFER_FULL 0x02 +#define PS2_STATUS_MOUSE_DATA 0x20 +#define PS2_STATUS_TIMEOUT 0x40 + +// control words +#define PS2_CTRL_READ_CMD 0x20 +#define PS2_CTRL_WRITE_CMD 0x60 +#define PS2_CTRL_WRITE_AUX 0xd4 +#define PS2_CTRL_MOUSE_DISABLE 0xa7 +#define PS2_CTRL_MOUSE_ENABLE 0xa8 +#define PS2_CTRL_MOUSE_TEST 0xa9 +#define PS2_CTRL_KEYBOARD_SELF_TEST 0xaa +#define PS2_CTRL_KEYBOARD_ACTIVATE 0xae +#define PS2_CTRL_KEYBOARD_DEACTIVATE 0xad + +// command bytes +#define PS2_CMD_DEV_INIT 0x43 + +// command bits +#define PS2_BITS_KEYBOARD_INTERRUPT 0x01 +#define PS2_BITS_AUX_INTERRUPT 0x02 +#define PS2_BITS_KEYBOARD_DISABLED 0x10 +#define PS2_BITS_MOUSE_DISABLED 0x20 +#define PS2_BITS_TRANSLATE_SCANCODES 0x40 + +// data words +#define PS2_CMD_TEST_PASSED 0xaa +#define PS2_CMD_GET_DEVICE_ID 0xf2 +#define PS2_CMD_SET_SAMPLE_RATE 0xf3 +#define PS2_CMD_ENABLE_MOUSE 0xf4 +#define PS2_CMD_DISABLE_MOUSE 0xf5 +#define PS2_CMD_RESET_MOUSE 0xff + +// reply codes +#define PS2_REPLY_TEST_PASSED 0x55 +#define PS2_REPLY_ACK 0xfa +#define PS2_REPLY_RESEND 0xfe +#define PS2_REPLY_ERROR 0xfc + +// interrupts +#define INT_PS2_MOUSE 0x0c +#define INT_PS2_KEYBOARD 0x01 + +// mouse device IDs +#define PS2_DEV_ID_STANDARD 0 +#define PS2_DEV_ID_INTELLIMOUSE 3 + +// packet sizes +#define PS2_PACKET_STANDARD 3 +#define PS2_PACKET_INTELLIMOUSE 4 +#define PS2_MAX_PACKET_SIZE 4 + // Should be equal to the biggest packet size + +#endif /* _PS2_H */