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