From 33a26ad1829e33e168fed497a8c5ebefa9b6a033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Wed, 17 Nov 2004 23:38:32 +0000 Subject: [PATCH] Added ps2_hid driver from Stefano Ceccherini Added in the input kit package git-svn-id: file:///srv/svn/repos/haiku/trunk/current@9993 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/add-ons/kernel/drivers/input/Jamfile | 1 + .../kernel/drivers/input/ps2_hid/Jamfile | 30 ++ .../kernel/drivers/input/ps2_hid/common.c | 246 ++++++++++ .../kernel/drivers/input/ps2_hid/common.h | 152 +++++++ .../kernel/drivers/input/ps2_hid/keyboard.c | 209 +++++++++ .../kernel/drivers/input/ps2_hid/mouse.c | 425 ++++++++++++++++++ 6 files changed, 1063 insertions(+) create mode 100644 src/add-ons/kernel/drivers/input/ps2_hid/Jamfile create mode 100644 src/add-ons/kernel/drivers/input/ps2_hid/common.c create mode 100644 src/add-ons/kernel/drivers/input/ps2_hid/common.h create mode 100644 src/add-ons/kernel/drivers/input/ps2_hid/keyboard.c create mode 100644 src/add-ons/kernel/drivers/input/ps2_hid/mouse.c diff --git a/src/add-ons/kernel/drivers/input/Jamfile b/src/add-ons/kernel/drivers/input/Jamfile index d191db3357..af0efc2a01 100644 --- a/src/add-ons/kernel/drivers/input/Jamfile +++ b/src/add-ons/kernel/drivers/input/Jamfile @@ -1,3 +1,4 @@ SubDir OBOS_TOP src add-ons kernel drivers input ; +SubInclude OBOS_TOP src add-ons kernel drivers input ps2_hid ; SubInclude OBOS_TOP src add-ons kernel drivers input hid ; diff --git a/src/add-ons/kernel/drivers/input/ps2_hid/Jamfile b/src/add-ons/kernel/drivers/input/ps2_hid/Jamfile new file mode 100644 index 0000000000..5297de1ad7 --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid/Jamfile @@ -0,0 +1,30 @@ +SubDir OBOS_TOP src add-ons kernel drivers input ps2_hid ; + +UsePrivateHeaders input ; +UsePrivateHeaders kernel ; + +if ! $(HAIKU_COMPATIBLE) { + SubDirCcFlags -DCOMPILE_FOR_R5 ; +} + +R5KernelAddon ps2_hid : kernel drivers bin : + common.c + keyboard.c + mouse.c +; + +# Link to kernel/drivers/dev/input/ +{ + local dir = [ FDirName $(OBOS_ADDON_DIR) kernel drivers dev input ] ; + local instDriver = ps2_hid ; + MakeLocate $(instDriver) : $(dir) ; + RelSymLink $(instDriver) : ps2_hid ; +} + +Package haiku-inputkit-cvs : + ps2_hid : + boot home config add-ons kernel drivers bin ; + +Package haiku-inputkit-cvs : + ps2_hid : + boot home config add-ons kernel drivers dev input ; diff --git a/src/add-ons/kernel/drivers/input/ps2_hid/common.c b/src/add-ons/kernel/drivers/input/ps2_hid/common.c new file mode 100644 index 0000000000..9e6173625e --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid/common.c @@ -0,0 +1,246 @@ +/* + * Copyright 2004 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) + */ + +#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; +isa_module_info *gIsa = NULL; +int32 gMouseOpenMask; +int32 gKeyboardOpenMask; + +device_hooks +keyboard_hooks = { + keyboard_open, + keyboard_close, + keyboard_freecookie, + keyboard_ioctl, + keyboard_read, + keyboard_write, + NULL, + NULL, + NULL, + NULL +}; + + +device_hooks +mouse_hooks = { + mouse_open, + mouse_close, + mouse_freecookie, + mouse_ioctl, + mouse_read, + mouse_write, + NULL, + NULL, + NULL, + NULL +}; + + +/** 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. + */ + +bool +wait_write_ctrl(void) +{ + int8 read; + int32 tries = 100; + TRACE(("wait_write_ctrl()\n")); + do { + read = gIsa->read_io_8(PS2_PORT_CTRL); + spin(100); + } while ((read & (PS2_IBUF_FULL | PS2_OBUF_FULL)) && tries-- > 0); + + return tries > 0; +} + + +/** Wait until the data port is ready to be written. This requires that + * the "Input buffer full" bit will be set to 0. + */ + +bool +wait_write_data(void) +{ + int8 read; + int32 tries = 100; + TRACE(("wait_write_data()\n")); + do { + read = gIsa->read_io_8(PS2_PORT_CTRL); + spin(100); + } while ((read & PS2_IBUF_FULL) && tries-- > 0); + + return tries > 0; +} + + +/** Wait until the data port can be read from. This requires that the + * "Output buffer full" bit will be set to 1. + */ + +bool +wait_read_data(void) +{ + int8 read; + int32 tries = 100; + TRACE(("wait_read_data()\n")); + do { + read = gIsa->read_io_8(PS2_PORT_CTRL); + spin(100); + } while (!(read & PS2_OBUF_FULL) && tries-- > 0); + + return tries > 0; +} + +/** Get the ps2 command byte. + */ + +int8 +get_command_byte(void) +{ + int8 read = 0; + + TRACE(("set_command_byte()\n")); + if (wait_write_ctrl()) { + gIsa->write_io_8(PS2_PORT_CTRL, PS2_CTRL_READ_CMD); + if (wait_read_data()) + read = gIsa->read_io_8(PS2_PORT_DATA); + } + + return read; +} + + +/** Set the ps2 command byte. + * Parameters: + * unsigned char, byte to write + */ + +void +set_command_byte(unsigned char cmd) +{ + TRACE(("set_command_byte()\n")); + if (wait_write_ctrl()) { + gIsa->write_io_8(PS2_PORT_CTRL, PS2_CTRL_WRITE_CMD); + if (wait_write_data()) + gIsa->write_io_8(PS2_PORT_DATA, cmd); + } +} + + +/** Reads a single byte from the data port. + * Return value: + * unsigned char, byte read + */ + +uint8 +read_data_byte(void) +{ + TRACE(("read_data_byte()\n")); + if (wait_read_data()) { + TRACE(("read_data_byte(): ok\n")); + return gIsa->read_io_8(PS2_PORT_DATA); + } + + TRACE(("read_data_byte(): timeout\n")); + + return PS2_ERROR; +} + + +/** 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. + * Parameters: + * unsigned char, byte to write + */ + +void +write_aux_byte(unsigned char cmd) +{ + TRACE(("write_aux_byte()\n")); + if (wait_write_ctrl()) { + gIsa->write_io_8(PS2_PORT_CTRL, PS2_CTRL_WRITE_AUX); + + if (wait_write_data()) + gIsa->write_io_8(PS2_PORT_DATA, cmd); + } +} + + +// driver stuff + +status_t +init_hardware() +{ + return B_OK; +} + + +static const char * +kDevices[] = { + DEVICE_KEYBOARD_NAME, + DEVICE_MOUSE_NAME, + NULL +}; + + +const char ** +publish_devices(void) +{ + return kDevices; +} + + +device_hooks * +find_device(const char *name) +{ + if (!strcmp(name, DEVICE_MOUSE_NAME)) + return &mouse_hooks; + else if (!strcmp(name, DEVICE_KEYBOARD_NAME)) + return &keyboard_hooks; + + return NULL; +} + + +status_t +init_driver() +{ + 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; + } + + gMouseOpenMask = 0; + gKeyboardOpenMask = 0; + + return B_OK; +} + + +void +uninit_driver() +{ + put_module(B_ISA_MODULE_NAME); +} diff --git a/src/add-ons/kernel/drivers/input/ps2_hid/common.h b/src/add-ons/kernel/drivers/input/ps2_hid/common.h new file mode 100644 index 0000000000..a1d8b2b400 --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid/common.h @@ -0,0 +1,152 @@ +/* + * Copyright 2004 Haiku, Inc. + * Distributed under the terms of the Haiku License. + * + * common.h: + * PS/2 hid device driver + * Authors (in chronological order): + * Elad Lahav (elad@eldarshany.com) + * Stefano Ceccherini (burton666@libero.it) + */ + +/* + * 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: + * ~~~~ + * Since a mouse is an input only device, data can only be read, and + * not written. A packet read from the mouse data port is composed of + * three bytes: + * byte 0: status byte, where + * - bit 0: Y overflow (1 = true) + * - bit 1: X overflow (1 = true) + * - bit 2: MSB of Y offset + * - bit 3: MSB of X offset + * - bit 4: Syncronization bit (always 1) + * - bit 5: Middle button (1 = down) + * - bit 6: Right button (1 = down) + * - bit 7: 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) + * 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. + * 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. + */ + +#ifndef __PS2COMMON_H +#define __PS2COMMON_H + +#include +#include +#include + +///////////////////////////////////////////////////////////////////////// +// definitions + +// I/O addresses +#define PS2_PORT_DATA 0x60 +#define PS2_PORT_CTRL 0x64 + +// data port bits +#define PS2_OBUF_FULL 0x01 +#define PS2_IBUF_FULL 0x02 +#define PS2_MOUSE_OBUF_FULL 0x20 +#define PS2_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 + +// command bytes +#define PS2_CMD_DEV_INIT 0x43 + +// command bits +#define PS2_BITS_AUX_INTERRUPT 0x02 +#define PS2_BITS_MOUSE_DISABLED 0x20 + +// 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_RES_TEST_PASSED 0x55 +#define PS2_RES_ACK 0xFA +#define PS2_RES_RESEND 0xFE +#define PS2_ERROR 0xFC + +// interrupts +#define INT_PS2_MOUSE 0x0C +#define INT_PS2_KEYBOARD 0x01 + +// other stuff +#define MOUSE_HISTORY_SIZE 256 + +// 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 + +// debug defines +#ifdef DEBUG + #define TRACE(x) dprintf x +#else + #define TRACE(x) +#endif + +// global variables +extern isa_module_info *gIsa; +extern int32 gMouseOpenMask; +extern int32 gKeyboardOpenMask; + +// prototypes +bool wait_write_ctrl(void); +bool wait_write_data(void); +bool wait_read_data(void); +void write_aux_byte(unsigned char cmd); +uint8 read_data_byte(void); + +int8 get_command_byte(void); +void set_command_byte(unsigned char cmd); + +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 /* __PS2COMMON_H */ diff --git a/src/add-ons/kernel/drivers/input/ps2_hid/keyboard.c b/src/add-ons/kernel/drivers/input/ps2_hid/keyboard.c new file mode 100644 index 0000000000..a2f552ed03 --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid/keyboard.c @@ -0,0 +1,209 @@ +/* + * Copyright 2004 Haiku, Inc. + * Distributed under the terms of the Haiku License. + * + * keyboard.c: + * PS/2 keyboard device driver + * Authors (in chronological order): + * Stefano Ceccherini (burton666@libero.it) + */ + +// TODO: Uncomment locking code +// We probably need to lock to avoid misbehaving on SMP machines, +// although the kb_mouse replacement driver never locks. + +#include "common.h" +#include "cbuf_adapter.h" + +#include +//#include + +#include "kb_mouse_driver.h" + + +enum { + LED_SCROLL = 1, + LED_NUM = 2, + LED_CAPS = 4 +} leds_status; + + +enum { + PS2_SET_LEDS = 0xed +} commands; + + +static sem_id sKeyboardSem; +//static mutex keyboard_read_mutex; +static cbuf *keyboard_buf; +static bool sIsExtended = false; + + +static void +set_leds(led_info *ledInfo) +{ + int leds = 0; + if (ledInfo->scroll_lock) + leds |= LED_SCROLL; + if (ledInfo->num_lock) + leds |= LED_NUM; + if (ledInfo->caps_lock) + leds |= LED_CAPS; + + wait_write_data(); + gIsa->write_io_8(PS2_PORT_DATA, PS2_SET_LEDS); + wait_write_data(); + gIsa->write_io_8(PS2_PORT_DATA, leds); +} + + +static int32 +handle_keyboard_interrupt(void *data) +{ + // TODO: Handle braindead "pause" key special case + at_kbd_io keyInfo; + unsigned char read, scancode; + + read = gIsa->read_io_8(PS2_PORT_DATA); + TRACE(("handle_keyboard_interrupt: read = 0x%x\n", read)); + + if (read == 0xE0) { + sIsExtended = true; + TRACE(("Extended key\n")); + return B_HANDLED_INTERRUPT; + } + + scancode = read; + + TRACE(("scancode: %x\n", scancode)); + if (scancode & 0x80) { + keyInfo.is_keydown = false; + scancode -= 0x80; + } else + keyInfo.is_keydown = true; + + if (sIsExtended) + scancode |= 0x80; + + keyInfo.timestamp = system_time(); + keyInfo.scancode = scancode; + + // TODO: Check return value + cbuf_memcpy_to_chain(keyboard_buf, 0, (void *)&keyInfo, sizeof(keyInfo)); + release_sem_etc(sKeyboardSem, 1, B_DO_NOT_RESCHEDULE); + + sIsExtended = false; + + return B_INVOKE_SCHEDULER; +} + + +static status_t +read_keyboard_packet(at_kbd_io *buffer) +{ + status_t status; + status = acquire_sem_etc(sKeyboardSem, 1, B_CAN_INTERRUPT, 0); + if (status < B_OK) + return status; + + status = cbuf_memcpy_from_chain((void *)buffer, keyboard_buf, 0, sizeof(at_kbd_io)); + if (status < B_OK) + TRACE(("read_keyboard_packet(): error reading packet: %s\n", strerror(status))); + + TRACE(("scancode: %x, keydown: %s\n", buffer->scancode, buffer->is_keydown ? "true" : "false")); + return status; +} + + +status_t +keyboard_open(const char *name, uint32 flags, void **cookie) +{ + status_t status; + + TRACE(("keyboard open()\n")); + if (atomic_or(&gKeyboardOpenMask, 1) != 0) + return B_BUSY; + + sKeyboardSem = create_sem(0, "keyboard_sem"); + if (sKeyboardSem < 0) { + atomic_and(&gKeyboardOpenMask, 0); + status = sKeyboardSem; + return status; + } + + //if (mutex_init(&keyboard_read_mutex, "keyboard_read_mutex") < 0) + // panic("could not create keyboard read mutex!\n"); + + keyboard_buf = cbuf_get_chain(1024); + if (keyboard_buf == NULL) + panic("could not create keyboard cbuf chain!\n"); + + status = install_io_interrupt_handler(1, &handle_keyboard_interrupt, NULL, 0); + + *cookie = NULL; + + return status; +} + + +status_t +keyboard_close(void *cookie) +{ + remove_io_interrupt_handler(1, &handle_keyboard_interrupt, NULL); + + cbuf_free_chain(keyboard_buf); + delete_sem(sKeyboardSem); + //mutex_destroy(&keyboard_read_mutex); + + atomic_and(&gKeyboardOpenMask, 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 EROFS; +} + + +status_t +keyboard_write(void *cookie, off_t pos, const void *buf, size_t *len) +{ + TRACE(("keyboard write()\n")); + *len = 0; + return EROFS; +} + + +status_t +keyboard_ioctl(void *cookie, uint32 op, void *buf, size_t len) +{ + TRACE(("keyboard ioctl()\n")); + switch (op) { + case KB_READ: + TRACE(("KB_READ\n")); + return read_keyboard_packet((at_kbd_io *)buf); + case KB_SET_LEDS: + set_leds((led_info *)buf); + TRACE(("KB_SET_LEDS\n")); + return B_OK; + case KB_SET_KEY_REPEATING: + case KB_SET_KEY_NONREPEATING: + TRACE(("ioctl 0x%x not implemented yet, returning B_OK\n", op)); + return B_OK; + default: + TRACE(("invalid ioctl 0x%x\n", op)); + return EINVAL; + } +} diff --git a/src/add-ons/kernel/drivers/input/ps2_hid/mouse.c b/src/add-ons/kernel/drivers/input/ps2_hid/mouse.c new file mode 100644 index 0000000000..60d1c02e3d --- /dev/null +++ b/src/add-ons/kernel/drivers/input/ps2_hid/mouse.c @@ -0,0 +1,425 @@ +/* + * Copyright 2001-2004 Haiku, Inc. + * Distributed under the terms of the Haiku License. + * + * ps2mouse.c: + * PS/2 mouse device driver + * Authors (in chronological order): + * Elad Lahav (elad@eldarshany.com) + * Stefano Ceccherini (burton666@libero.it) + */ + +/* + * 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 0: Y overflow (1 = true) + * - bit 1: X overflow (1 = true) + * - bit 2: MSB of Y offset + * - bit 3: MSB of X offset + * - bit 4: Syncronization bit (always 1) + * - bit 5: Middle button (1 = down) + * - bit 6: Right button (1 = down) + * - bit 7: 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 + +#include "kb_mouse_driver.h" + +#include "common.h" + +#ifdef COMPILE_FOR_R5 + #include "cbuf_adapter.h" +#else + #include +#endif + +static sem_id sMouseSem; +static int32 sSync; +static cbuf *sMouseChain; +static int32 sOpenMask; + +static bigtime_t sLastClickTime; +static bigtime_t sClickSpeed; +static int32 sClickCount; +static int sButtonsState; + +static uint32 sPacketSize; + +///////////////////////////////////////////////////////////////////////// +// mouse functions + +/* +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; +} +*/ + + +/** Enables or disables mouse reporting for the ps2 port. + */ + +static status_t +ps2_enable_mouse(bool enable) +{ + int32 tries = 2; + uint8 read; + + do { + write_aux_byte(enable ? PS2_CMD_ENABLE_MOUSE : PS2_CMD_DISABLE_MOUSE); + read = read_data_byte(); + if (read == PS2_RES_ACK) + break; + spin(100); + } while (read == PS2_RES_RESEND && tries-- > 0); + + if (read != PS2_RES_ACK) + return B_ERROR; + + return B_OK; +} + + +/** Set sampling rate of the ps2 port. + */ + +static status_t +ps2_set_sample_rate(uint32 rate) +{ + status_t status = B_ERROR; + + write_aux_byte(PS2_CMD_SET_SAMPLE_RATE); + if (read_data_byte() == PS2_RES_ACK) { + write_aux_byte(rate); + if (read_data_byte() == PS2_RES_ACK) + status = B_OK; + } + + return status; +} + + +/** 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) { + if (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 + } + } + + 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; + } +} + + +/** Read a mouse event from the mouse events chain buffer. + */ +static status_t +ps2_mouse_read(mouse_movement *pos) +{ + status_t status; + uint8 packet[PS2_MAX_PACKET_SIZE]; + + TRACE(("ps2_mouse_read()\n")); + status = acquire_sem_etc(sMouseSem, 1, B_CAN_INTERRUPT, 0); + if (status < B_OK) + return status; + + status = cbuf_memcpy_from_chain(packet, sMouseChain, 0, sPacketSize); + if (status < B_OK) { + TRACE(("error copying buffer\n")); + return status; + } + + ps2_packet_to_movement(packet, pos); + + return B_OK; +} + + +///////////////////////////////////////////////////////////////////////// +// interrupt + + +/** 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* data) +{ + int8 read; + TRACE(("mouse interrupt occurred!!!\n")); + + read = gIsa->read_io_8(PS2_PORT_CTRL); + + if (read < 0) { + TRACE(("Interrupt was not generated by the ps2 mouse\n")); + return B_UNHANDLED_INTERRUPT; + } + + read = read_data_byte(); + cbuf_memcpy_to_chain(sMouseChain, 0, &read, sizeof(read)); + + if (sSync == 0 && !(read & 8)) { + TRACE(("mouse resynched, bad data\n")); + return B_HANDLED_INTERRUPT; + } + + if (++sSync == sPacketSize) { + TRACE(("mouse synched\n")); + sSync = 0; + release_sem_etc(sMouseSem, 1, B_DO_NOT_RESCHEDULE); + + return B_INVOKE_SCHEDULER; + } + + return B_HANDLED_INTERRUPT; +} + + +///////////////////////////////////////////////////////////////////////// +// file operations + + +status_t +mouse_open(const char *name, uint32 flags, void **cookie) +{ + status_t status; + int8 commandByte; + int8 deviceId; + + TRACE(("mouse_open()\n")); + + if (atomic_or(&sOpenMask, 1) != 0) + return B_BUSY; + + // get device id + write_aux_byte(PS2_CMD_GET_DEVICE_ID); + if (read_data_byte() == PS2_RES_ACK) + deviceId = read_data_byte(); + + TRACE(("init_driver: device id: %2x\n", deviceId)); + if (deviceId == 0) { + // try to switch to intellimouse mode + ps2_set_sample_rate(200); + ps2_set_sample_rate(100); + ps2_set_sample_rate(80); + } + + // get device id, again + write_aux_byte(PS2_CMD_GET_DEVICE_ID); + if (read_data_byte() == PS2_RES_ACK) + deviceId = read_data_byte(); + + if (deviceId == PS2_DEV_ID_STANDARD) { + sPacketSize = PS2_PACKET_STANDARD; + TRACE(("Standard ps2 mouse found\n")); + } else if (deviceId == PS2_DEV_ID_INTELLIMOUSE) { + sPacketSize = PS2_PACKET_INTELLIMOUSE; + TRACE(("Extended ps2 mouse found\n")); + } else { + TRACE(("No mouse found\n")); + put_module(B_ISA_MODULE_NAME); + return B_ERROR; // Something's wrong. Better quit + } + + sMouseChain = cbuf_get_chain(MOUSE_HISTORY_SIZE); + if (sMouseChain == NULL) { + TRACE(("can't allocate cbuf chain\n")); + put_module(B_ISA_MODULE_NAME); + return B_ERROR; + } + + // 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")); + cbuf_free_chain(sMouseChain); + put_module(B_ISA_MODULE_NAME); + return sMouseSem; + } + + set_sem_owner(sMouseSem, B_SYSTEM_TEAM); + + *cookie = NULL; + + commandByte = get_command_byte(); + commandByte |= PS2_BITS_AUX_INTERRUPT; + commandByte &= ~PS2_BITS_MOUSE_DISABLED; + set_command_byte(commandByte); + + status = ps2_enable_mouse(true); + if (status < B_OK) { + TRACE(("mouse_open(): cannot enable PS/2 mouse\n")); + return B_ERROR; + } + + TRACE(("mouse_open(): mouse succesfully enabled\n")); + + // register interrupt handler + status = install_io_interrupt_handler(INT_PS2_MOUSE, handle_mouse_interrupt, NULL, 0); + + return status; +} + + +status_t +mouse_close(void * cookie) +{ + TRACE(("mouse_close()\n")); + ps2_enable_mouse(false); + + cbuf_free_chain(sMouseChain); + delete_sem(sMouseSem); + + remove_io_interrupt_handler(INT_PS2_MOUSE, handle_mouse_interrupt, NULL); + + 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 *buf, size_t *len) +{ + *len = 0; + return EROFS; +} + + +status_t +mouse_write(void * cookie, off_t pos, const void *buf, size_t *len) +{ + *len = 0; + return EROFS; +} + + +status_t +mouse_ioctl(void *cookie, uint32 op, void *buf, size_t len) +{ + mouse_movement *pos = (mouse_movement *)buf; + 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(pos); + case MS_SET_TYPE: + TRACE(("MS_SET_TYPE not implemented\n")); + return EINVAL; + case MS_SET_MAP: + TRACE(("MS_SET_MAP (set mouse mapping) not implemented\n")); + return EINVAL; + case MS_GET_ACCEL: + TRACE(("MS_GET_ACCEL (get mouse acceleration) not implemented\n")); + return EINVAL; + case MS_SET_ACCEL: + TRACE(("MS_SET_ACCEL (set mouse acceleration) not implemented\n")); + return EINVAL; + case MS_SET_CLICKSPEED: + TRACE(("MS_SETCLICK (set click speed)\n")); + sClickSpeed = *(bigtime_t *)buf; + return B_OK; + default: + TRACE(("unknown opcode: %ld\n", op)); + return EINVAL; + } +} +