diff --git a/doc/cross-development b/doc/cross-development index 3dbe392e..bba96a4c 100644 --- a/doc/cross-development +++ b/doc/cross-development @@ -216,7 +216,7 @@ can build a bootable .iso by typing: This will produce a sortix.iso file that is bootable on real hardware and virtual machines. This works by first building Sortix system and packaging up an initrd, then it create a cdrom image with a bootloader configured to load the -kernel and initrd stored on the cdrom. +kernel and initrd stored on the cdrom. If the command fails, see below. You can clean the source directory fully: @@ -267,3 +267,13 @@ Building some ports may require additional tools to be installed on your system and other unforeseen problems may arise that means the port doesn't compile properly on your system. Should a port fail to compile, then the `tix-build` command will offer you a chance to investigate the situation in a shell. + +Troubleshooting +--------------- + +If producing a bootable cdrom with grub-mkrescue gives the error + + xorriso : FAILURE : Cannot find path '/efi.img' in loaded ISO image + +then your GRUB grub installation is defective. You need to install mformat(1) to +use grub-mkrescue. diff --git a/kernel/Makefile b/kernel/Makefile index 9b6dc364..013a9949 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -61,6 +61,7 @@ ifdef X86FAMILY x86-family/mtrr.o \ x86-family/pat.o \ x86-family/float.o \ + x86-family/ps2.o \ x86-family/x86-family.o endif diff --git a/kernel/include/sortix/kernel/ps2.h b/kernel/include/sortix/kernel/ps2.h new file mode 100644 index 00000000..1615261a --- /dev/null +++ b/kernel/include/sortix/kernel/ps2.h @@ -0,0 +1,44 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2015. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + sortix/kernel/ps2.h + Various interfaces for keyboard devices and layouts. + +*******************************************************************************/ + +#ifndef INCLUDE_SORTIX_KERNEL_PS2_H +#define INCLUDE_SORTIX_KERNEL_PS2_H + +#include + +namespace Sortix { + +class PS2Device +{ +public: + virtual ~PS2Device() { } + virtual void PS2DeviceInitialize(void* send_ctx, bool (*send)(void*, uint8_t), + uint8_t* id, size_t id_size) = 0; + virtual void PS2DeviceOnByte(uint8_t byte) = 0; + +}; + +} // namespace Sortix + +#endif diff --git a/kernel/kb/ps2.cpp b/kernel/kb/ps2.cpp index ea3e0a2d..58a3739f 100644 --- a/kernel/kb/ps2.cpp +++ b/kernel/kb/ps2.cpp @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2014. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2014, 2015. This file is part of Sortix. @@ -18,52 +18,47 @@ Sortix. If not, see . kb/ps2.cpp - A driver for the PS2 Keyboard. + PS2 Keyboard. *******************************************************************************/ -#include #include #include #include -#if defined(__x86_64__) -#include -#endif - #include -#include -#include -#include -#include -#include #include -#include -#include - -#if defined(__i386__) -#include "../x86-family/gdt.h" -#endif +#include +#include #include "ps2.h" +// TODO: This driver doesn't deal with keyboard scancode sets yet. + namespace Sortix { -const uint16_t DATA = 0x0; -const uint16_t COMMAND = 0x0; -const uint16_t STATUS = 0x4; -const uint8_t CMD_SETLED = 0xED; -const uint8_t LED_SCRLCK = 1 << 0; -const uint8_t LED_NUMLCK = 1 << 1; -const uint8_t LED_CAPSLCK = 1 << 2; +static const uint8_t DEVICE_RESET_OK = 0xAA; +static const uint8_t DEVICE_SCANCODE_ESCAPE = 0xE0; +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; -void PS2Keyboard__OnInterrupt(struct interrupt_context* intctx, void* user) -{ - ((PS2Keyboard*) user)->OnInterrupt(intctx); -} +static const uint8_t DEVICE_CMD_SET_LED = 0xED; +static const uint8_t DEVICE_CMD_SET_TYPEMATIC = 0xF3; +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; -PS2Keyboard::PS2Keyboard(uint16_t iobase, uint8_t interrupt) +static const uint8_t DEVICE_LED_SCRLCK = 1 << 0; +static const uint8_t DEVICE_LED_NUMLCK = 1 << 1; +static const uint8_t DEVICE_LED_CAPSLCK = 1 << 2; + +static const size_t DEVICE_RETRIES = 5; + +PS2Keyboard::PS2Keyboard() { this->queue = NULL; this->queuelength = 0; @@ -71,124 +66,142 @@ PS2Keyboard::PS2Keyboard(uint16_t iobase, uint8_t interrupt) this->queueused = 0; this->owner = NULL; this->ownerptr = NULL; - this->iobase = iobase; - this->interrupt = interrupt; + // TODO: Initial LED status can be read from the BIOS data area. If so, we + // need to emulate fake presses of the modifier keys to keep the + // keyboard layout in sync. this->leds = 0; - this->scancodeescaped = false; this->kblock = KTHREAD_MUTEX_INITIALIZER; - interrupt_registration.handler = PS2Keyboard__OnInterrupt; - interrupt_registration.context = this; - Interrupt::RegisterHandler(interrupt, &interrupt_registration); - - // If any scancodes were already pending, our interrupt handler will - // never be called. Let's just discard anything pending. - PopScancode(); } PS2Keyboard::~PS2Keyboard() { - Interrupt::RegisterHandler(interrupt, &interrupt_registration); delete[] queue; } -struct PS2KeyboardWork +void PS2Keyboard::PS2DeviceInitialize(void* send_ctx, bool (*send)(void*, uint8_t), + uint8_t* id, size_t id_size) { - uint8_t scancode; -}; - -static void PS2Keyboard__InterruptWork(void* kb_ptr, void* payload, size_t size) -{ - assert(size == sizeof(PS2KeyboardWork)); - PS2KeyboardWork* work = (PS2KeyboardWork*) payload; - ((PS2Keyboard*) kb_ptr)->InterruptWork(work->scancode); + if ( sizeof(this->id) < id_size ) + id_size = sizeof(this->id); + this->send_ctx = send_ctx; + this->send = send; + this->state = STATE_INIT; + this->tries = 0; + memcpy(this->id, id, id_size); + this->id_size = id_size; + PS2DeviceOnByte(DEVICE_RESEND); } -void PS2Keyboard::OnInterrupt(struct interrupt_context* intctx) +void PS2Keyboard::PS2DeviceOnByte(uint8_t byte) { - uint8_t scancode = PopScancode(); - if ( scancode == KBKEY_F10 ) + ScopedLock lock(&kblock); + + if ( state == STATE_INIT ) { - Scheduler::SaveInterruptedContext(intctx, &CurrentThread()->registers); - Debugger::Run(true); + state = STATE_RESET_LED; + tries = DEVICE_RETRIES; + byte = DEVICE_RESEND; } - PS2KeyboardWork work; - work.scancode = scancode; - Interrupt::ScheduleWork(PS2Keyboard__InterruptWork, this, &work, sizeof(work)); -} -void PS2Keyboard::InterruptWork(uint8_t scancode) -{ - kthread_mutex_lock(&kblock); - int kbkey = DecodeScancode(scancode); - if ( !kbkey ) + if ( state == STATE_RESET_LED ) { - kthread_mutex_unlock(&kblock); + if ( byte == DEVICE_RESEND && tries-- ) + { + if ( send(send_ctx, DEVICE_CMD_SET_LED) && + send(send_ctx, leds & 0x07) ) + return; + } + state = STATE_RESET_TYPEMATIC; + tries = DEVICE_RETRIES; + byte = DEVICE_RESEND; + } + + if ( state == STATE_RESET_TYPEMATIC ) + { + if ( byte == DEVICE_RESEND && tries-- ) + { + uint8_t rate = 0b00000; // 33.36 ms/repeat. + uint8_t delay = 0b01; // 500 ms. + uint8_t typematic = delay << 3 | rate << 0; + if ( send(send_ctx, DEVICE_CMD_SET_TYPEMATIC) && + send(send_ctx, typematic) ) + return; + } + state = STATE_ENABLE_SCAN; + tries = DEVICE_RETRIES; + byte = DEVICE_RESEND; + } + + if ( state == STATE_ENABLE_SCAN ) + { + if ( byte == DEVICE_RESEND && tries-- ) + { + if ( send(send_ctx, DEVICE_CMD_ENABLE_SCAN) ) + return; + } + state = STATE_NORMAL; + tries = DEVICE_RETRIES; + byte = DEVICE_RESEND; + } + + if ( byte == DEVICE_RESEND || byte == DEVICE_ACK ) + return; + + if ( byte == DEVICE_SCANCODE_ESCAPE ) + { + state = STATE_NORMAL_ESCAPED; return; } + if ( state == STATE_NORMAL ) + { + int kbkey = byte & 0x7F; + OnKeyboardKey(byte & 0x80 ? -kbkey : kbkey); + lock.Reset(); + NotifyOwner(); + return; + } + + if ( state == STATE_NORMAL_ESCAPED ) + { + state = STATE_NORMAL; + int kbkey = (byte & 0x7F) + 0x80; + OnKeyboardKey(byte & 0x80 ? -kbkey : kbkey); + lock.Reset(); + NotifyOwner(); + return; + } +} + +void PS2Keyboard::OnKeyboardKey(int kbkey) +{ if ( !PushKey(kbkey) ) - { - Log::PrintF("Warning: dropping keystroke due to insufficient " - "storage space in PS2 keyboard driver.\n"); - kthread_mutex_unlock(&kblock); return; - } uint8_t newleds = leds; if ( kbkey == KBKEY_CAPSLOCK ) - newleds ^= LED_CAPSLCK; + newleds ^= DEVICE_LED_CAPSLCK; if ( kbkey == KBKEY_SCROLLLOCK ) - newleds ^= LED_SCRLCK; + newleds ^= DEVICE_LED_SCRLCK; if ( kbkey == KBKEY_NUMLOCK ) - newleds ^= LED_NUMLCK; + newleds ^= DEVICE_LED_NUMLCK; if ( newleds != leds ) UpdateLEDs(leds = newleds); - - kthread_mutex_unlock(&kblock); - - NotifyOwner(); } void PS2Keyboard::NotifyOwner() { - if ( !owner) + if ( !owner ) return; owner->OnKeystroke(this, ownerptr); } -int PS2Keyboard::DecodeScancode(uint8_t scancode) -{ - const uint8_t SCANCODE_ESCAPE = 0xE0; - if ( scancode == SCANCODE_ESCAPE ) - { - scancodeescaped = true; - return 0; - } - - int offset = scancodeescaped ? 0x80 : 0; - int kbkey = scancode & 0x7F; - kbkey = scancode & 0x80 ? -kbkey - offset : kbkey + offset; - - scancodeescaped = false; - - // kbkey is now in the format specified in . - - return kbkey; -} - -uint8_t PS2Keyboard::PopScancode() -{ - return inport8(iobase + DATA); -} - void PS2Keyboard::UpdateLEDs(int ledval) { - while ( (inport8(iobase + STATUS) & (1<<1)) ); - outport8(iobase + COMMAND, CMD_SETLED); - while ( (inport8(iobase + STATUS) & (1<<1)) ); - outport8(iobase + COMMAND, ledval); + send(send_ctx, DEVICE_CMD_SET_LED) && + send(send_ctx, ledval); } void PS2Keyboard::SetOwner(KeyboardOwner* owner, void* user) @@ -206,15 +219,17 @@ bool PS2Keyboard::PushKey(int key) // Check if we need to allocate or resize the circular queue. if ( queueused == queuelength ) { - size_t newqueuelength = (queuelength) ? queuelength * 2 : 32UL; + size_t newqueuelength = queuelength ? 2 * queuelength : 32UL; + if ( 16 * 1024 < newqueuelength ) + return false; int* newqueue = new int[newqueuelength]; if ( !newqueue ) return false; if ( queue ) { size_t elemsize = sizeof(*queue); - size_t leadingavai = queuelength-queueoffset; - size_t leading = (leadingavai < queueused) ? leadingavai : queueused; + size_t leadingavai = queuelength - queueoffset; + size_t leading = leadingavai < queueused ? leadingavai : queueused; size_t trailing = queueused - leading; memcpy(newqueue, queue + queueoffset, leading * elemsize); memcpy(newqueue + leading, queue, trailing * elemsize); diff --git a/kernel/kb/ps2.h b/kernel/kb/ps2.h index 9d426677..6d99496c 100644 --- a/kernel/kb/ps2.h +++ b/kernel/kb/ps2.h @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2014. + Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2014, 2015. This file is part of Sortix. @@ -18,7 +18,7 @@ Sortix. If not, see . kb/ps2.h - A driver for the PS2 Keyboard. + PS2 Keyboard. *******************************************************************************/ @@ -28,47 +28,55 @@ #include #include -#include #include #include +#include namespace Sortix { -class PS2Keyboard : public Keyboard +class PS2Keyboard : public Keyboard, public PS2Device { public: - PS2Keyboard(uint16_t iobase, uint8_t interrupt); + PS2Keyboard(); virtual ~PS2Keyboard(); virtual int Read(); virtual size_t GetPending() const; virtual bool HasPending() const; virtual void SetOwner(KeyboardOwner* owner, void* user); - -public: - void OnInterrupt(struct interrupt_context* intctx); - void InterruptWork(uint8_t scancode); + virtual void PS2DeviceInitialize(void* send_ctx, bool (*send)(void*, uint8_t), + uint8_t* id, size_t id_size); + virtual void PS2DeviceOnByte(uint8_t byte); private: - uint8_t PopScancode(); - int DecodeScancode(uint8_t scancode); + void OnKeyboardKey(int kbkey); void UpdateLEDs(int ledval); bool PushKey(int key); int PopKey(); void NotifyOwner(); private: - struct interrupt_handler interrupt_registration; + mutable kthread_mutex_t kblock; int* queue; size_t queuelength; size_t queueoffset; size_t queueused; KeyboardOwner* owner; void* ownerptr; - uint16_t iobase; - uint8_t interrupt; - bool scancodeescaped; + void* send_ctx; + bool (*send)(void*, uint8_t); + enum + { + STATE_INIT = 0, + STATE_RESET_LED, + STATE_RESET_TYPEMATIC, + STATE_ENABLE_SCAN, + STATE_NORMAL, + STATE_NORMAL_ESCAPED, + } state; + size_t tries; uint8_t leds; - mutable kthread_mutex_t kblock; + uint8_t id[2]; + size_t id_size; }; diff --git a/kernel/kernel.cpp b/kernel/kernel.cpp index 670fbb96..4d2d029c 100644 --- a/kernel/kernel.cpp +++ b/kernel/kernel.cpp @@ -95,6 +95,7 @@ #include "x86-family/cmos.h" #include "x86-family/float.h" #include "x86-family/gdt.h" +#include "x86-family/ps2.h" #endif // Keep the stack size aligned with $CPU/boot.s @@ -562,7 +563,7 @@ static void BootThread(void* /*user*/) Panic("Unable to create descriptor for RAM filesystem /dev directory."); // Initialize the keyboard. - Keyboard* keyboard = new PS2Keyboard(0x60, Interrupt::IRQ1); + PS2Keyboard* keyboard = new PS2Keyboard(); if ( !keyboard ) Panic("Could not allocate PS2 Keyboard driver"); KeyboardLayoutExecutor* kblayout = new KeyboardLayoutExecutor; @@ -570,6 +571,7 @@ static void BootThread(void* /*user*/) Panic("Could not allocate keyboard layout executor"); if ( !kblayout->Upload(default_kblayout, sizeof(default_kblayout)) ) Panic("Could not load the default keyboard layout into the executor"); + PS2::Init(keyboard, NULL); // Register the kernel terminal as /dev/tty. Ref tty(new LogTerminal(slashdev->dev, 0666, 0, 0, keyboard, kblayout)); diff --git a/kernel/x86-family/ps2.cpp b/kernel/x86-family/ps2.cpp new file mode 100644 index 00000000..5b1eaab7 --- /dev/null +++ b/kernel/x86-family/ps2.cpp @@ -0,0 +1,410 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2015. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + 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; + +// TODO: This is entirely a guess. I don't actually know what timeout is +// suitable. GRUB seems to use 20 ms. I'll pick 50 ms to be safe. +static const unsigned int TIMEOUT_MS = 50; + +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 struct interrupt_handler irq1_registration; +static struct interrupt_handler irq12_registration; +static PS2Device* irq1_device; +static PS2Device* irq12_device; + +static void IRQ1Work(void* ctx, void* payload, size_t size) +{ + (void) ctx; + assert(size == sizeof(unsigned char)); + unsigned char* byteptr = (unsigned char*) payload; + unsigned char byte = *byteptr; + if ( irq1_device ) + irq1_device->PS2DeviceOnByte(byte); +} + +static void IRQ12Work(void* ctx, void* payload, size_t size) +{ + (void) ctx; + assert(size == sizeof(unsigned char)); + unsigned char* byteptr = (unsigned char*) payload; + unsigned char byte = *byteptr; + if ( irq12_device ) + irq12_device->PS2DeviceOnByte(byte); +} + +static void OnIRQ1(struct interrupt_context* intctx, void* user) +{ + (void) intctx; + (void) user; + if ( inport8(REG_STATUS) & REG_STATUS_OUTPUT ) + { + uint8_t byte = inport8(REG_DATA); + Interrupt::ScheduleWork(IRQ1Work, NULL, &byte, sizeof(byte)); + } +} + +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); + Interrupt::ScheduleWork(IRQ12Work, NULL, &byte, sizeof(byte)); + } +} + +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 0xAA\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 ( 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; + } + 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 diff --git a/kernel/x86-family/ps2.h b/kernel/x86-family/ps2.h new file mode 100644 index 00000000..7c443c93 --- /dev/null +++ b/kernel/x86-family/ps2.h @@ -0,0 +1,40 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2015. + + This file is part of Sortix. + + Sortix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Sortix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Sortix. If not, see . + + x86-family/ps2.h + 8042 PS/2 Controller. + +*******************************************************************************/ + +#ifndef SORTIX_X86_FAMILY_PS2_H +#define SORTIX_X86_FAMILY_PS2_H + +#include + +#include + +namespace Sortix { +namespace PS2 { + +void Init(PS2Device* keyboard, PS2Device* mouse); + +} // namespace PS2 +} // namespace Sortix + +#endif