#include <string.h>
#include <new>
#include <debug.h>
#include <debugger_keymaps.h>
#include <lock.h>
#include <util/AutoLock.h>
#include "ATKeymap.h"
#include "ps2_service.h"
#include "keyboard_mouse_driver.h"
#include "packet_buffer.h"
#define KEY_BUFFER_SIZE 100
#ifdef TRACE_PS2_KEYBOARD
# define TRACE(x...) dprintf(x)
#else
# define TRACE(x...)
#endif
enum {
LED_SCROLL = 1,
LED_NUM = 2,
LED_CAPS = 4
};
enum {
EXTENDED_KEY_0 = 0xe0,
EXTENDED_KEY_1 = 0xe1,
LEFT_ALT_KEY = 0x38,
RIGHT_ALT_KEY = 0xb8,
SYS_REQ_KEY = 0x54,
PRNT_SCRN_KEY = 0x80 | 0x37,
PAUSE_KEY = 0x80 | 0x46,
};
struct keyboard_cookie {
bool is_reader;
bool is_debugger;
};
static mutex sInitializeLock = MUTEX_INITIALIZER("keyboard init");
static int32 sKeyboardOpenCount = 0;
static bool sHasKeyboardReader = false;
static bool sHasDebugReader = false;
static sem_id sKeyboardSem;
static struct packet_buffer *sKeyBuffer;
static bool sIsExtended0 = false;
static bool sIsExtended1 = false;
static uint8 sPauseSequenceRead = 0;
static int32 sKeyboardRepeatRate;
static bigtime_t sKeyboardRepeatDelay;
static uint8 sKeyboardIds[2];
static status_t
set_leds(led_info *ledInfo)
{
uint8 leds = 0;
TRACE("ps2: 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 ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB],
PS2_CMD_KEYBOARD_SET_LEDS, &leds, 1, NULL, 0);
}
static status_t
set_typematic(int32 rate, bigtime_t delay)
{
uint8 value;
TRACE("ps2: set_typematic rate %" B_PRId32 ", delay %" B_PRId64 "\n",
rate, delay);
if (rate < 20)
rate = 20;
if (rate > 300)
rate = 300;
rate = ((rate - 20) * 31) / (300 - 20);
value = 31 - rate;
if (delay >= 875000)
value |= 3 << 5;
else if (delay >= 625000)
value |= 2 << 5;
else if (delay >= 375000)
value |= 1 << 5;
else
value |= 0 << 5;
return ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB],
PS2_CMD_SET_TYPEMATIC, &value, 1, NULL, 0);
}
static int32
keyboard_handle_int(ps2_dev *dev)
{
enum emergency_keys {
EMERGENCY_LEFT_ALT = 0x01,
EMERGENCY_RIGHT_ALT = 0x02,
EMERGENCY_SYS_REQ = 0x04,
};
static const uint8 pauseSequence[] = { 0x1D, 0x45 };
static int emergencyKeyStatus = 0;
raw_key_info keyInfo;
uint8 scancode = dev->history[0].data;
if (atomic_get(&sKeyboardOpenCount) == 0)
return B_HANDLED_INTERRUPT;
if (scancode == EXTENDED_KEY_0) {
sIsExtended0 = true;
return B_HANDLED_INTERRUPT;
}
if (scancode == EXTENDED_KEY_1) {
sIsExtended1 = true;
return B_HANDLED_INTERRUPT;
}
if ((scancode & 0x80) != 0) {
keyInfo.is_keydown = false;
scancode &= 0x7f;
} else
keyInfo.is_keydown = true;
if (sIsExtended1 && scancode == pauseSequence[sPauseSequenceRead]) {
sPauseSequenceRead++;
if (sPauseSequenceRead == 2) {
sIsExtended1 = false;
sPauseSequenceRead = 0;
scancode = PAUSE_KEY;
} else {
return B_HANDLED_INTERRUPT;
}
}
if (sIsExtended0) {
scancode |= 0x80;
sIsExtended0 = false;
}
if (scancode == LEFT_ALT_KEY || scancode == RIGHT_ALT_KEY) {
if (keyInfo.is_keydown) {
emergencyKeyStatus |= scancode == LEFT_ALT_KEY
? EMERGENCY_LEFT_ALT : EMERGENCY_RIGHT_ALT;
} else {
emergencyKeyStatus &= ~(scancode == LEFT_ALT_KEY
? EMERGENCY_LEFT_ALT : EMERGENCY_RIGHT_ALT);
}
} else if (scancode == SYS_REQ_KEY || scancode == PRNT_SCRN_KEY) {
if (keyInfo.is_keydown)
emergencyKeyStatus |= EMERGENCY_SYS_REQ;
else
emergencyKeyStatus &= ~EMERGENCY_SYS_REQ;
} else if (emergencyKeyStatus > EMERGENCY_SYS_REQ
&& debug_emergency_key_pressed(kUnshiftedKeymap[scancode])) {
static const int kKeys[] = {LEFT_ALT_KEY, RIGHT_ALT_KEY, SYS_REQ_KEY};
emergencyKeyStatus = 0;
keyInfo.timestamp = system_time();
keyInfo.is_keydown = false;
for (size_t i = 0; i < sizeof(kKeys) / sizeof(kKeys[0]); i++) {
keyInfo.keycode = kATKeycodeMap[kKeys[i] - 1];
if (packet_buffer_write(sKeyBuffer, (uint8 *)&keyInfo,
sizeof(keyInfo)) != 0)
release_sem_etc(sKeyboardSem, 1, B_DO_NOT_RESCHEDULE);
}
return B_HANDLED_INTERRUPT;
}
keyInfo.timestamp = dev->history[0].time;
keyInfo.keycode = kATKeycodeMap[scancode - 1];
if (packet_buffer_write(sKeyBuffer, (uint8 *)&keyInfo,
sizeof(keyInfo)) == 0) {
return B_HANDLED_INTERRUPT;
}
release_sem_etc(sKeyboardSem, 1, B_DO_NOT_RESCHEDULE);
return B_INVOKE_SCHEDULER;
}
static status_t
read_keyboard_packet(raw_key_info *packet, bool isDebugger)
{
status_t status;
TRACE("ps2: read_keyboard_packet: enter\n");
while (true) {
status = acquire_sem_etc(sKeyboardSem, 1, B_CAN_INTERRUPT, 0);
if (status != B_OK)
return status;
if (!ps2_device[PS2_DEVICE_KEYB].active) {
TRACE("ps2: read_keyboard_packet, Error device no longer active\n");
return B_ERROR;
}
if (isDebugger || !sHasDebugReader)
break;
release_sem(sKeyboardSem);
snooze(100000);
}
if (packet_buffer_read(sKeyBuffer, (uint8 *)packet, sizeof(*packet)) == 0) {
TRACE("ps2: read_keyboard_packet, Error reading packet: %s\n",
strerror(status));
return B_ERROR;
}
TRACE("ps2: read_keyboard_packet: keycode: %" B_PRIx32 ", keydown: %s\n",
packet->keycode, packet->is_keydown ? "true" : "false");
return B_OK;
}
static void
ps2_keyboard_disconnect(ps2_dev *dev)
{
INFO("ps2: ps2_keyboard_disconnect %s\n", dev->name);
if (atomic_get(&sKeyboardOpenCount) != 0)
release_sem(sKeyboardSem);
}
status_t
probe_keyboard(void)
{
uint8 data;
status_t status;
int ids_read = 0;
#if 0
status = ps2_command(PS2_CTRL_KEYBOARD_TEST, NULL, 0, &data, 1);
if (status != B_OK || data != 0x00) {
INFO("ps2: keyboard test failed, status 0x%08lx, data 0x%02x\n", status, data);
return B_ERROR;
}
#endif
status = ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB], PS2_CMD_RESET, NULL,
0, &data, 1);
if (status != B_OK || data != 0xaa) {
ERROR("ps2: keyboard reset failed, status 0x%08" B_PRIx32 ", data 0x%02x"
"\n", status, data);
ids_read = 1;
status = ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB],
PS2_CMD_GET_DEVICE_ID, NULL, 0, sKeyboardIds, sizeof(sKeyboardIds));
if ((status != B_OK) || (sKeyboardIds[0] != 0xab && sKeyboardIds[0] != 0xac &&
sKeyboardIds[0] != 0x2b && sKeyboardIds[0] != 0x5d &&
sKeyboardIds[0] != 0x60 && sKeyboardIds[0] != 0x47)) {
ERROR("ps2: keyboard getid failed, status 0x%08" B_PRIx32 ", data 0x%02x%02x."
" Assuming no keyboard\n", status, sKeyboardIds[0], sKeyboardIds[1]);
return B_ERROR;
}
}
sKeyboardRepeatRate = ((31 - 0x0b) * 280) / 31 + 20;
sKeyboardRepeatDelay = 500000;
#if 0
status = ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB], PS2_ENABLE_KEYBOARD, NULL, 0, NULL, 0);
#endif
#if 0
status = ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB], PS2_CMD_ECHO, NULL, 0, &data, 1);
if (status != B_OK || data != 0xee) {
INFO("ps2: keyboard echo test failed, status 0x%08lx, data 0x%02x\n", status, data);
return B_ERROR;
}
#endif
uint8 cmdbyte = 0;
status = ps2_command(PS2_CTRL_READ_CMD, NULL, 0, &cmdbyte, 1);
if (status != B_OK) {
ERROR("ps2: cannot read CMD byte on kbd probe:%#08" B_PRIx32 "\n",
status);
} else if ((cmdbyte & PS2_BITS_KEYBOARD_DISABLED) == PS2_BITS_KEYBOARD_DISABLED) {
cmdbyte &= ~PS2_BITS_KEYBOARD_DISABLED;
status = ps2_command(PS2_CTRL_WRITE_CMD, &cmdbyte, 1, NULL, 0);
if (status != B_OK) {
ERROR("ps2: cannot write 0x%02x to CMD byte on kbd probe:%#08"
B_PRIx32 "\n", cmdbyte, status);
}
}
if (!ids_read) {
status = ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB],
PS2_CMD_GET_DEVICE_ID, NULL, 0, sKeyboardIds, sizeof(sKeyboardIds));
if (status != B_OK)
ERROR("ps2: cannot read keyboard device id:%#08" B_PRIx32 "\n", status);
}
return B_OK;
}
static status_t
keyboard_open(const char *name, uint32 flags, void **_cookie)
{
TRACE("ps2: keyboard_open %s\n", name);
keyboard_cookie* cookie = new(std::nothrow) keyboard_cookie();
if (cookie == NULL)
return B_NO_MEMORY;
cookie->is_reader = false;
cookie->is_debugger = false;
MutexLocker locker(sInitializeLock);
if (atomic_get(&sKeyboardOpenCount) == 0) {
status_t status = probe_keyboard();
if (status != B_OK) {
INFO("ps2: keyboard probing failed\n");
ps2_service_notify_device_removed(&ps2_device[PS2_DEVICE_KEYB]);
delete cookie;
return status;
}
INFO("ps2: keyboard found\n");
sKeyboardSem = create_sem(0, "keyboard_sem");
if (sKeyboardSem < 0) {
delete cookie;
return sKeyboardSem;
}
sKeyBuffer
= create_packet_buffer(KEY_BUFFER_SIZE * sizeof(raw_key_info));
if (sKeyBuffer == NULL) {
delete_sem(sKeyboardSem);
delete cookie;
return B_NO_MEMORY;
}
ps2_device[PS2_DEVICE_KEYB].disconnect = &ps2_keyboard_disconnect;
ps2_device[PS2_DEVICE_KEYB].handle_int = &keyboard_handle_int;
atomic_or(&ps2_device[PS2_DEVICE_KEYB].flags, PS2_FLAG_ENABLED);
}
atomic_add(&sKeyboardOpenCount, 1);
*_cookie = cookie;
TRACE("ps2: keyboard_open %s success\n", name);
return B_OK;
}
static status_t
keyboard_close(void *_cookie)
{
keyboard_cookie *cookie = (keyboard_cookie *)_cookie;
TRACE("ps2: keyboard_close enter\n");
if (atomic_add(&sKeyboardOpenCount, -1) == 1) {
delete_packet_buffer(sKeyBuffer);
delete_sem(sKeyboardSem);
atomic_and(&ps2_device[PS2_DEVICE_KEYB].flags, ~PS2_FLAG_ENABLED);
sKeyboardIds[0] = sKeyboardIds[1] = 0;
}
if (cookie->is_reader)
sHasKeyboardReader = false;
if (cookie->is_debugger)
sHasDebugReader = false;
TRACE("ps2: keyboard_close done\n");
return B_OK;
}
static status_t
keyboard_freecookie(void *cookie)
{
delete (keyboard_cookie*)cookie;
return B_OK;
}
static status_t
keyboard_read(void *cookie, off_t pos, void *buffer, size_t *_length)
{
TRACE("ps2: keyboard read\n");
*_length = 0;
return B_NOT_ALLOWED;
}
static status_t
keyboard_write(void *cookie, off_t pos, const void *buffer, size_t *_length)
{
TRACE("ps2: keyboard write\n");
*_length = 0;
return B_NOT_ALLOWED;
}
static status_t
keyboard_ioctl(void *_cookie, uint32 op, void *buffer, size_t length)
{
keyboard_cookie *cookie = (keyboard_cookie *)_cookie;
switch (op) {
case KB_READ:
{
if (!sHasKeyboardReader && !cookie->is_debugger) {
cookie->is_reader = true;
sHasKeyboardReader = true;
} else if (!cookie->is_debugger && !cookie->is_reader)
return B_BUSY;
raw_key_info packet;
status_t status = read_keyboard_packet(&packet,
cookie->is_debugger);
TRACE("ps2: ioctl KB_READ: %s\n", strerror(status));
if (status != B_OK)
return status;
return user_memcpy(buffer, &packet, sizeof(packet));
}
case KB_SET_LEDS:
{
led_info info;
TRACE("ps2: ioctl KB_SET_LEDS\n");
if (user_memcpy(&info, buffer, sizeof(led_info)) < B_OK)
return B_BAD_ADDRESS;
return set_leds(&info);
}
case KB_SET_KEY_REPEATING:
{
TRACE("ps2: ioctl KB_SET_KEY_REPEATING\n");
return ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB], 0xfa, NULL, 0,
NULL, 0);
}
case KB_SET_KEY_NONREPEATING:
{
TRACE("ps2: ioctl KB_SET_KEY_NONREPEATING\n");
return ps2_dev_command(&ps2_device[PS2_DEVICE_KEYB], 0xf8, NULL, 0,
NULL, 0);
}
case KB_SET_KEY_REPEAT_RATE:
{
int32 key_repeat_rate;
TRACE("ps2: ioctl KB_SET_KEY_REPEAT_RATE\n");
if (user_memcpy(&key_repeat_rate, buffer, sizeof(key_repeat_rate))
!= B_OK)
return B_BAD_ADDRESS;
if (set_typematic(key_repeat_rate, sKeyboardRepeatDelay) != B_OK)
return B_ERROR;
sKeyboardRepeatRate = key_repeat_rate;
return B_OK;
}
case KB_GET_KEY_REPEAT_RATE:
{
TRACE("ps2: ioctl KB_GET_KEY_REPEAT_RATE\n");
return user_memcpy(buffer, &sKeyboardRepeatRate,
sizeof(sKeyboardRepeatRate));
}
case KB_SET_KEY_REPEAT_DELAY:
{
bigtime_t key_repeat_delay;
TRACE("ps2: ioctl KB_SET_KEY_REPEAT_DELAY\n");
if (user_memcpy(&key_repeat_delay, buffer, sizeof(key_repeat_delay))
!= B_OK)
return B_BAD_ADDRESS;
if (set_typematic(sKeyboardRepeatRate, key_repeat_delay) != B_OK)
return B_ERROR;
sKeyboardRepeatDelay = key_repeat_delay;
return B_OK;
}
case KB_GET_KEY_REPEAT_DELAY:
{
TRACE("ps2: ioctl KB_GET_KEY_REPEAT_DELAY\n");
return user_memcpy(buffer, &sKeyboardRepeatDelay,
sizeof(sKeyboardRepeatDelay));
}
case KB_GET_KEYBOARD_ID:
{
TRACE("ps2: ioctl KB_GET_KEYBOARD_ID\n");
uint16 keyboardId = sKeyboardIds[1] << 8 | sKeyboardIds[0];
return user_memcpy(buffer, &keyboardId, sizeof(keyboardId));
}
case KB_SET_CONTROL_ALT_DEL_TIMEOUT:
case KB_CANCEL_CONTROL_ALT_DEL:
case KB_DELAY_CONTROL_ALT_DEL:
INFO("ps2: ioctl 0x%" B_PRIx32 " not implemented yet, returning "
"B_OK\n", op);
return B_OK;
case KB_SET_DEBUG_READER:
if (sHasDebugReader)
return B_BUSY;
cookie->is_debugger = true;
sHasDebugReader = true;
return B_OK;
default:
INFO("ps2: invalid ioctl 0x%" B_PRIx32 "\n", op);
return B_DEV_INVALID_IOCTL;
}
}
device_hooks gKeyboardDeviceHooks = {
keyboard_open,
keyboard_close,
keyboard_freecookie,
keyboard_ioctl,
keyboard_read,
keyboard_write,
};