/* * Copyright (c) 2015, 2017 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * x86-family/ps2.cpp * 8042 PS/2 Controller. */ #include #include #include #include #include #include #include #include #include "ps2.h" namespace Sortix { namespace PS2 { static const uint16_t REG_DATA = 0x0060; static const uint16_t REG_COMMAND = 0x0064; static const uint16_t REG_STATUS = 0x0064; static const uint8_t REG_COMMAND_READ_RAM = 0x20; static const uint8_t REG_COMMAND_WRITE_RAM = 0x60; static const uint8_t REG_COMMAND_DISABLE_SECOND_PORT = 0xA7; static const uint8_t REG_COMMAND_ENABLE_SECOND_PORT = 0xA8; static const uint8_t REG_COMMAND_TEST_SECOND_PORT = 0xA9; static const uint8_t REG_COMMAND_TEST_CONTROLLER = 0xAA; static const uint8_t REG_COMMAND_TEST_FIRST_PORT = 0xAB; static const uint8_t REG_COMMAND_DISABLE_FIRST_PORT = 0xAD; static const uint8_t REG_COMMAND_ENABLE_FIRST_PORT = 0xAE; static const uint8_t REG_COMMAND_SEND_TO_PORT_2 = 0xD4; static const uint8_t REG_STATUS_OUTPUT = 1 << 0; static const uint8_t REG_STATUS_INPUT = 1 << 1; static const uint8_t REG_STATUS_SYSTEM = 1 << 2; static const uint8_t REG_STATUS_COMMAND = 1 << 3; static const uint8_t REG_STATUS_UNKNOWN1 = 1 << 4; static const uint8_t REG_STATUS_UNKNOWN2 = 1 << 5; static const uint8_t REG_STATUS_TIMEOUT = 1 << 6; static const uint8_t REG_STATUS_PARITY = 1 << 7; static const uint8_t REG_CONFIG_FIRST_INTERRUPT = 1 << 0; static const uint8_t REG_CONFIG_SECOND_INTERRUPT = 1 << 1; static const uint8_t REG_CONFIG_SYSTEM = 1 << 2; static const uint8_t REG_CONFIG_ZERO1 = 1 << 3; static const uint8_t REG_CONFIG_FIRST_CLOCK = 1 << 4; static const uint8_t REG_CONFIG_SECOND_CLOCK = 1 << 5; static const uint8_t REG_CONFIG_FIRST_TRANSLATION = 1 << 6; static const uint8_t REG_CONFIG_ZERO2 = 1 << 7; static const uint8_t DEVICE_RESET_OK = 0xAA; static const uint8_t DEVICE_ECHO = 0xEE; static const uint8_t DEVICE_ACK = 0xFA; static const uint8_t DEVICE_RESEND = 0xFE; static const uint8_t DEVICE_ERROR = 0xFF; static const uint8_t DEVICE_CMD_ENABLE_SCAN = 0xF4; static const uint8_t DEVICE_CMD_DISABLE_SCAN = 0xF5; static const uint8_t DEVICE_CMD_IDENTIFY = 0xF2; static const uint8_t DEVICE_CMD_RESET = 0xFF; static const size_t DEVICE_RETRIES = 5; static const unsigned int TIMEOUT_MS = 20; static bool WaitInput() { return wait_inport8_clear(REG_STATUS, REG_STATUS_INPUT, false, TIMEOUT_MS); } static bool WaitOutput() { return wait_inport8_set(REG_STATUS, REG_STATUS_OUTPUT, false, TIMEOUT_MS); } static bool TryReadByte(uint8_t* result) { if ( !WaitOutput() ) return false; *result = inport8(REG_DATA); return true; } static bool TryWriteByte(uint8_t byte) { if ( !WaitInput() ) return false; outport8(REG_DATA, byte); return true; } static bool TryWriteCommand(uint8_t byte) { if ( !WaitInput() ) return false; outport8(REG_COMMAND, byte); return true; } static bool TryWriteToPort(uint8_t byte, uint8_t port) { if ( port == 2 && !TryWriteCommand(REG_COMMAND_SEND_TO_PORT_2) ) return false; return TryWriteByte(byte); } static bool TryWriteCommandToPort(uint8_t command, uint8_t port, uint8_t* answer) { *answer = DEVICE_ERROR; size_t unrelated = 0; for ( size_t i = 0; i < DEVICE_RETRIES; i++ ) { if ( !TryWriteToPort(command, port) ) return false; again: uint8_t byte; if ( !TryReadByte(&byte) ) return false; if ( byte == DEVICE_RESET_OK && !TryReadByte(&byte) ) return false; *answer = byte; if ( byte == DEVICE_ACK || byte == DEVICE_ECHO ) return true; if ( byte != DEVICE_RESEND ) { // We received a weird response, probably pending data, discard it // and hope we receive a real acknowledgement. if ( 1000 <= unrelated ) return false; unrelated++; goto again; } } return false; } static bool IsKeyboardResponse(uint8_t* response, size_t size) { if ( size == 0 ) return true; if ( size == 2 && response[0] == 0xAB && response[1] == 0x41 ) return true; if ( size == 2 && response[0] == 0xAB && response[1] == 0xC1 ) return true; if ( size == 2 && response[0] == 0xAB && response[1] == 0x83 ) return true; return false; } static bool IsMouseResponse(uint8_t* response, size_t size) { if ( size == 1 && response[0] == 0x00 ) return true; if ( size == 1 && response[0] == 0x03 ) return true; if ( size == 1 && response[0] == 0x04 ) return true; return false; } static void IRQ1Work(void* /*context*/); static void IRQ12Work(void* /*context*/); static struct interrupt_handler irq1_registration; static struct interrupt_handler irq12_registration; static struct interrupt_work irq1_work = { NULL, IRQ1Work, NULL }; static struct interrupt_work irq12_work = { NULL, IRQ12Work, NULL }; static PS2Device* irq1_device; static PS2Device* irq12_device; static unsigned char irq1_buffer[128]; static unsigned char irq12_buffer[128]; static size_t irq1_offset; static size_t irq12_offset; static size_t irq1_used; static size_t irq12_used; static bool irq1_working; static bool irq12_working; static void IRQ1Work(void* /*context*/) { Interrupt::Disable(); size_t todo = irq1_used; for ( size_t i = 0; i < todo; i++ ) { unsigned char byte = irq1_buffer[irq1_offset++]; if ( sizeof(irq1_buffer) <= irq1_offset ) irq1_offset = 0; irq1_used--; Interrupt::Enable(); if ( irq1_device ) irq1_device->PS2DeviceOnByte(byte); Interrupt::Disable(); } if ( irq1_used ) Interrupt::ScheduleWork(&irq1_work); else irq1_working = false; Interrupt::Enable(); } static void IRQ12Work(void* /*context*/) { Interrupt::Disable(); size_t todo = irq12_used; for ( size_t i = 0; i < todo; i++ ) { unsigned char byte = irq12_buffer[irq12_offset++]; if ( sizeof(irq12_buffer) <= irq12_offset ) irq12_offset = 0; irq12_used--; Interrupt::Enable(); if ( irq12_device ) irq12_device->PS2DeviceOnByte(byte); Interrupt::Disable(); } if ( irq12_used ) Interrupt::ScheduleWork(&irq12_work); else irq12_working = false; Interrupt::Enable(); } static void OnIRQ1(struct interrupt_context* /*intctx*/, void* /*user*/) { if ( inport8(REG_STATUS) & REG_STATUS_OUTPUT ) { uint8_t byte = inport8(REG_DATA); // TODO: This drops incoming bytes if the buffer is full. Instead make // the device not send further interrupts until we have available bytes. if ( irq1_used < sizeof(irq1_buffer) ) { size_t index = irq1_offset + irq1_used++; if ( sizeof(irq1_buffer) <= index ) index -= sizeof(irq1_buffer); irq1_buffer[index] = byte; if ( !irq1_working ) { Interrupt::ScheduleWork(&irq1_work); irq1_working = true; } } } } static void OnIRQ12(struct interrupt_context* intctx, void* user) { (void) intctx; (void) user; if ( inport8(REG_STATUS) & REG_STATUS_OUTPUT ) { uint8_t byte = inport8(REG_DATA); // TODO: This drops incoming bytes if the buffer is full. Instead make // the device not send further interrupts until we have available bytes. if ( irq12_used < sizeof(irq12_buffer) ) { size_t index = irq12_offset + irq12_used++; if ( sizeof(irq12_buffer) <= index ) index -= sizeof(irq12_buffer); irq12_buffer[index] = byte; if ( !irq12_working ) { Interrupt::ScheduleWork(&irq12_work); irq12_working = true; } } } } static kthread_mutex_t ps2_device_send_mutex = KTHREAD_MUTEX_INITIALIZER; static bool PS2DeviceSend(void* ctx, uint8_t byte) { ScopedLock lock(&ps2_device_send_mutex); uint8_t port = (uint8_t) (uintptr_t) ctx; return TryWriteToPort(byte, port); } static bool DetectDevice(uint8_t port, uint8_t* response, size_t* response_size); void Init(PS2Device* keyboard, PS2Device* mouse) { uint8_t byte; uint8_t config; if ( !TryWriteCommand(REG_COMMAND_DISABLE_FIRST_PORT) || !TryWriteCommand(REG_COMMAND_DISABLE_SECOND_PORT) ) return; while ( inport8(REG_STATUS) & REG_STATUS_OUTPUT ) inport8(REG_DATA); if ( !TryWriteCommand(REG_COMMAND_READ_RAM) || !TryReadByte(&config) ) return; config &= ~REG_CONFIG_FIRST_INTERRUPT; config &= ~REG_CONFIG_SECOND_INTERRUPT; //config &= ~REG_CONFIG_FIRST_TRANSLATION; // TODO: Not ready for this yet. if ( !TryWriteCommand(REG_COMMAND_WRITE_RAM) || !TryWriteByte(config) ) return; bool dual = config & REG_CONFIG_SECOND_CLOCK; if ( !TryWriteCommand(REG_COMMAND_TEST_CONTROLLER) || !TryReadByte(&byte) ) return; if ( byte == 0xFF ) return; if ( byte != 0x55 ) { Log::PrintF("[PS/2 controller] Self-test failure resulted in " "0x%02X instead of 0x55\n", byte); return; } if ( dual ) { uint8_t config; if ( !TryWriteCommand(REG_COMMAND_ENABLE_SECOND_PORT) || !TryWriteCommand(REG_COMMAND_READ_RAM) || !TryReadByte(&config) ) return; dual = !(config & REG_CONFIG_SECOND_CLOCK); if ( dual && !TryWriteCommand(REG_COMMAND_DISABLE_SECOND_PORT) ) return; } bool port_1 = true; bool port_2 = dual; #if 0 // Disabled due to some emulated PS/2 controllers not handling this well. if ( port_1 ) { if ( !TryWriteCommand(REG_COMMAND_TEST_FIRST_PORT) || !TryReadByte(&byte) ) return; port_1 = byte == 0x00; } if ( port_2 ) { if ( !TryWriteCommand(REG_COMMAND_TEST_SECOND_PORT) || !TryReadByte(&byte) ) return; port_2 = byte == 0x00; } #endif size_t port_1_resp_size = 0; uint8_t port_1_resp[2]; if ( port_1 && !DetectDevice(1, port_1_resp, &port_1_resp_size) ) port_1 = false; size_t port_2_resp_size = 0; uint8_t port_2_resp[2]; if ( port_2 && !DetectDevice(2, port_2_resp, &port_2_resp_size) ) port_2 = false; if ( port_1 && !irq1_device && IsKeyboardResponse(port_1_resp, port_1_resp_size) ) irq1_device = keyboard; if ( port_2 && !irq12_device && IsMouseResponse(port_2_resp, port_2_resp_size) ) irq12_device = mouse; if ( port_1 && !irq1_device && IsMouseResponse(port_1_resp, port_1_resp_size) ) irq1_device = mouse; if ( port_2 && !irq12_device && IsKeyboardResponse(port_2_resp, port_2_resp_size) ) irq12_device = keyboard; port_1 = port_1 && irq1_device; port_2 = port_2 && irq12_device; if ( !TryWriteCommand(REG_COMMAND_READ_RAM) || !TryReadByte(&config) ) return; irq1_registration.handler = OnIRQ1; irq1_registration.context = NULL; Interrupt::RegisterHandler(Interrupt::IRQ1, &irq1_registration); irq12_registration.handler = OnIRQ12; irq12_registration.context = NULL; Interrupt::RegisterHandler(Interrupt::IRQ12, &irq12_registration); config |= port_1 ? REG_CONFIG_FIRST_INTERRUPT : 0; config |= port_2 ? REG_CONFIG_SECOND_INTERRUPT : 0; if ( !TryWriteCommand(REG_COMMAND_WRITE_RAM) || !TryWriteByte(config) ) return; if ( port_1 && !TryWriteCommand(REG_COMMAND_ENABLE_FIRST_PORT) ) return; if ( port_2 && !TryWriteCommand(REG_COMMAND_ENABLE_SECOND_PORT) ) return; if ( irq1_device ) irq1_device->PS2DeviceInitialize((void*) 1, PS2DeviceSend, port_1_resp, port_1_resp_size); if ( irq12_device ) irq12_device->PS2DeviceInitialize((void*) 2, PS2DeviceSend, port_2_resp, port_2_resp_size); } static bool DetectDevice(uint8_t port, uint8_t* response, size_t* response_size) { uint8_t enable = port == 1 ? REG_COMMAND_ENABLE_FIRST_PORT : REG_COMMAND_ENABLE_SECOND_PORT; uint8_t disable = port == 1 ? REG_COMMAND_DISABLE_FIRST_PORT : REG_COMMAND_DISABLE_SECOND_PORT; uint8_t byte; if ( !TryWriteCommand(enable) ) return false; if ( !TryWriteCommandToPort(DEVICE_CMD_DISABLE_SCAN, port, &byte) ) { if ( byte == DEVICE_RESEND ) { // HARDWARE BUG: // This may be incomplete PS/2 emulation that simulates the // controller but the devices always responds with 0xFE to anything // they receive. This happens on sortie's pc1, but the keyboard // device still supplies IRQ1's and scancodes. Let's assume the // devices are stil there even though we can't control them. if ( port == 1 ) { *response_size = 2; response[0] = 0xAB; response[1] = 0x83; if ( !TryWriteCommand(disable) ) return false; return true; } if ( port == 2 ) { *response_size = 1; response[0] = 0x00; if ( !TryWriteCommand(disable) ) return false; return true; } } TryWriteCommand(disable); return false; } // Empty pending buffer. while ( TryReadByte(&byte) ) continue; if ( !TryWriteCommandToPort(DEVICE_CMD_IDENTIFY, port, &byte) ) { TryWriteCommand(disable); return false; } *response_size = 0; if ( TryReadByte(&byte) ) { response[(*response_size)++] = byte; if ( TryReadByte(&byte) ) response[(*response_size)++] = byte; } if ( !TryWriteCommand(disable) ) return false; return true; } } // namespace PS2 } // namespace Sortix