mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
Add PS/2 controller driver.
This commit is contained in:
parent
3d48c7f658
commit
306709fc4a
8 changed files with 658 additions and 128 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
44
kernel/include/sortix/kernel/ps2.h
Normal file
44
kernel/include/sortix/kernel/ps2.h
Normal 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
|
|
@ -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,84 +66,129 @@ 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);
|
||||
}
|
||||
PS2KeyboardWork work;
|
||||
work.scancode = scancode;
|
||||
Interrupt::ScheduleWork(PS2Keyboard__InterruptWork, this, &work, sizeof(work));
|
||||
state = STATE_RESET_LED;
|
||||
tries = DEVICE_RETRIES;
|
||||
byte = DEVICE_RESEND;
|
||||
}
|
||||
|
||||
void PS2Keyboard::InterruptWork(uint8_t scancode)
|
||||
if ( state == STATE_RESET_LED )
|
||||
{
|
||||
kthread_mutex_lock(&kblock);
|
||||
int kbkey = DecodeScancode(scancode);
|
||||
if ( !kbkey )
|
||||
if ( byte == DEVICE_RESEND && tries-- )
|
||||
{
|
||||
kthread_mutex_unlock(&kblock);
|
||||
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()
|
||||
|
@ -158,37 +198,10 @@ void PS2Keyboard::NotifyOwner()
|
|||
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,7 +219,9 @@ 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;
|
||||
|
@ -214,7 +229,7 @@ bool PS2Keyboard::PushKey(int key)
|
|||
{
|
||||
size_t elemsize = sizeof(*queue);
|
||||
size_t leadingavai = queuelength - queueoffset;
|
||||
size_t leading = (leadingavai < queueused) ? leadingavai : queueused;
|
||||
size_t leading = leadingavai < queueused ? leadingavai : queueused;
|
||||
size_t trailing = queueused - leading;
|
||||
memcpy(newqueue, queue + queueoffset, leading * elemsize);
|
||||
memcpy(newqueue + leading, queue, trailing * elemsize);
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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
410
kernel/x86-family/ps2.cpp
Normal 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
40
kernel/x86-family/ps2.h
Normal 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
|
Loading…
Reference in a new issue