Add PS/2 controller driver.

This commit is contained in:
Jonas 'Sortie' Termansen 2015-05-01 00:07:06 +02:00
parent 3d48c7f658
commit 306709fc4a
8 changed files with 658 additions and 128 deletions

View File

@ -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.

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
sortix/kernel/ps2.h
Various interfaces for keyboard devices and layouts.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_KERNEL_PS2_H
#define INCLUDE_SORTIX_KERNEL_PS2_H
#include <stdint.h>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
kb/ps2.cpp
A driver for the PS2 Keyboard.
PS2 Keyboard.
*******************************************************************************/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#if defined(__x86_64__)
#include <msr.h>
#endif
#include <sortix/keycodes.h>
#include <sortix/kernel/cpu.h>
#include <sortix/kernel/debugger.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/ioport.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/keyboard.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/thread.h>
#if defined(__i386__)
#include "../x86-family/gdt.h"
#endif
#include <sortix/kernel/ps2.h>
#include <sortix/kernel/kthread.h>
#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 <sortix/keycodes.h>.
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);

View File

@ -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 <http://www.gnu.org/licenses/>.
kb/ps2.h
A driver for the PS2 Keyboard.
PS2 Keyboard.
*******************************************************************************/
@ -28,47 +28,55 @@
#include <stddef.h>
#include <stdint.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/keyboard.h>
#include <sortix/kernel/ps2.h>
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;
};

View File

@ -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<Inode> tty(new LogTerminal(slashdev->dev, 0666, 0, 0, keyboard, kblayout));

410
kernel/x86-family/ps2.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
x86-family/ps2.cpp
8042 PS/2 Controller.
*******************************************************************************/
#include <assert.h>
#include <stdint.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/ioport.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/log.h>
#include <sortix/kernel/panic.h>
#include <sortix/kernel/ps2.h>
#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

40
kernel/x86-family/ps2.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
x86-family/ps2.h
8042 PS/2 Controller.
*******************************************************************************/
#ifndef SORTIX_X86_FAMILY_PS2_H
#define SORTIX_X86_FAMILY_PS2_H
#include <stdint.h>
#include <sortix/kernel/ps2.h>
namespace Sortix {
namespace PS2 {
void Init(PS2Device* keyboard, PS2Device* mouse);
} // namespace PS2
} // namespace Sortix
#endif