345 lines
11 KiB
C
345 lines
11 KiB
C
// 16550-compatible serial input driver. This file routes COM1 receive bytes into the canonical input core.
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <intrin.h>
|
|
|
|
#include <ntlite/status.h>
|
|
#include <ntlite/ddk/ddk.h>
|
|
#include <ntlite/ddi/input_device.h>
|
|
#include <ntlite/ddk/debug.h>
|
|
#include <ntlite/ddk/dpc.h>
|
|
#include <ntlite/ddk/interrupt.h>
|
|
|
|
#define SERIAL_COM1_BASE 0x3F8u
|
|
#define SERIAL_COM1_IRQ 4u
|
|
|
|
#define SERIAL_REG_DATA 0u
|
|
#define SERIAL_REG_IER 1u
|
|
#define SERIAL_REG_IIR 2u
|
|
#define SERIAL_REG_FCR 2u
|
|
#define SERIAL_REG_LCR 3u
|
|
#define SERIAL_REG_MCR 4u
|
|
#define SERIAL_REG_LSR 5u
|
|
|
|
#define SERIAL_LSR_DATA_READY 0x01u
|
|
#define SERIAL_LSR_TX_EMPTY 0x20u
|
|
#define SERIAL_LCR_DLAB 0x80u
|
|
#define SERIAL_MCR_DTR 0x01u
|
|
#define SERIAL_MCR_RTS 0x02u
|
|
#define SERIAL_MCR_OUT2 0x08u
|
|
|
|
#define SERIAL_IER_RX_READY 0x01u
|
|
#define SERIAL_FCR_ENABLE 0x01u
|
|
#define SERIAL_FCR_CLEAR_RX 0x02u
|
|
#define SERIAL_FCR_CLEAR_TX 0x04u
|
|
#define SERIAL_FCR_TRIGGER_14 0xC0u
|
|
|
|
#define SERIAL_RX_QUEUE_SIZE 1024u
|
|
#define SERIAL_RX_QUEUE_MASK (SERIAL_RX_QUEUE_SIZE - 1u)
|
|
|
|
typedef enum SERIAL_ESCAPE_STATE {
|
|
SerialEscapeNone = 0,
|
|
SerialEscapeEsc,
|
|
SerialEscapeCsi,
|
|
SerialEscapeCsiNumber,
|
|
} SERIAL_ESCAPE_STATE;
|
|
|
|
static KDPC gSerialRxDpc;
|
|
static volatile SERIAL_ESCAPE_STATE gSerialEscapeState;
|
|
static volatile uint8_t gSerialCsiNumber;
|
|
static volatile bool gSerialDropNextLf;
|
|
static uint8_t gSerialRxQueue[SERIAL_RX_QUEUE_SIZE];
|
|
static volatile uint32_t gSerialRxHead;
|
|
static volatile uint32_t gSerialRxTail;
|
|
static volatile uint32_t gSerialRxDropped;
|
|
|
|
// Reads one byte from a COM register.
|
|
static uint8_t SerialRead(uint16_t reg) {
|
|
return __inbyte((unsigned short)(SERIAL_COM1_BASE + reg));
|
|
}
|
|
|
|
// Writes one byte to a COM register.
|
|
static void SerialWrite(uint16_t reg, uint8_t value) {
|
|
__outbyte((unsigned short)(SERIAL_COM1_BASE + reg), value);
|
|
}
|
|
|
|
// Submits one input key transition.
|
|
static void SerialSubmitTransition(INPUT_KEY key, bool pressed) {
|
|
INPUT_KEY_EVENT event;
|
|
event.Key = key;
|
|
event.Pressed = pressed;
|
|
KeInputSubmitKey(&event);
|
|
}
|
|
|
|
// Submits one logical key press and release.
|
|
static void SerialSubmitKey(INPUT_KEY key) {
|
|
if (key == INPUT_KEY_NONE) return;
|
|
SerialSubmitTransition(key, true);
|
|
SerialSubmitTransition(key, false);
|
|
}
|
|
|
|
// Submits one shifted key press and release.
|
|
static void SerialSubmitShiftedKey(INPUT_KEY key) {
|
|
if (key == INPUT_KEY_NONE) return;
|
|
SerialSubmitTransition(INPUT_KEY_LEFT_SHIFT, true);
|
|
SerialSubmitKey(key);
|
|
SerialSubmitTransition(INPUT_KEY_LEFT_SHIFT, false);
|
|
}
|
|
|
|
// Submits one control-modified key press and release.
|
|
static void SerialSubmitCtrlKey(INPUT_KEY key) {
|
|
if (key == INPUT_KEY_NONE) return;
|
|
SerialSubmitTransition(INPUT_KEY_LEFT_CTRL, true);
|
|
SerialSubmitKey(key);
|
|
SerialSubmitTransition(INPUT_KEY_LEFT_CTRL, false);
|
|
}
|
|
|
|
// Converts an ASCII printable byte into input events.
|
|
static void SerialSubmitPrintable(uint8_t ch) {
|
|
if (ch >= 'a' && ch <= 'z') {
|
|
SerialSubmitKey((INPUT_KEY)(INPUT_KEY_A + (ch - 'a')));
|
|
return;
|
|
}
|
|
if (ch >= 'A' && ch <= 'Z') {
|
|
SerialSubmitShiftedKey((INPUT_KEY)(INPUT_KEY_A + (ch - 'A')));
|
|
return;
|
|
}
|
|
if (ch >= '1' && ch <= '9') {
|
|
SerialSubmitKey((INPUT_KEY)(INPUT_KEY_1 + (ch - '1')));
|
|
return;
|
|
}
|
|
if (ch == '0') {
|
|
SerialSubmitKey(INPUT_KEY_0);
|
|
return;
|
|
}
|
|
|
|
switch (ch) {
|
|
case ' ': SerialSubmitKey(INPUT_KEY_SPACE); return;
|
|
case '-': SerialSubmitKey(INPUT_KEY_MINUS); return;
|
|
case '=': SerialSubmitKey(INPUT_KEY_EQUAL); return;
|
|
case '[': SerialSubmitKey(INPUT_KEY_LEFT_BRACKET); return;
|
|
case ']': SerialSubmitKey(INPUT_KEY_RIGHT_BRACKET); return;
|
|
case '\\': SerialSubmitKey(INPUT_KEY_BACKSLASH); return;
|
|
case ';': SerialSubmitKey(INPUT_KEY_SEMICOLON); return;
|
|
case '\'': SerialSubmitKey(INPUT_KEY_APOSTROPHE); return;
|
|
case '`': SerialSubmitKey(INPUT_KEY_GRAVE); return;
|
|
case ',': SerialSubmitKey(INPUT_KEY_COMMA); return;
|
|
case '.': SerialSubmitKey(INPUT_KEY_PERIOD); return;
|
|
case '/': SerialSubmitKey(INPUT_KEY_SLASH); return;
|
|
|
|
case '!': SerialSubmitShiftedKey(INPUT_KEY_1); return;
|
|
case '@': SerialSubmitShiftedKey(INPUT_KEY_2); return;
|
|
case '#': SerialSubmitShiftedKey(INPUT_KEY_3); return;
|
|
case '$': SerialSubmitShiftedKey(INPUT_KEY_4); return;
|
|
case '%': SerialSubmitShiftedKey(INPUT_KEY_5); return;
|
|
case '^': SerialSubmitShiftedKey(INPUT_KEY_6); return;
|
|
case '&': SerialSubmitShiftedKey(INPUT_KEY_7); return;
|
|
case '*': SerialSubmitShiftedKey(INPUT_KEY_8); return;
|
|
case '(': SerialSubmitShiftedKey(INPUT_KEY_9); return;
|
|
case ')': SerialSubmitShiftedKey(INPUT_KEY_0); return;
|
|
case '_': SerialSubmitShiftedKey(INPUT_KEY_MINUS); return;
|
|
case '+': SerialSubmitShiftedKey(INPUT_KEY_EQUAL); return;
|
|
case '{': SerialSubmitShiftedKey(INPUT_KEY_LEFT_BRACKET); return;
|
|
case '}': SerialSubmitShiftedKey(INPUT_KEY_RIGHT_BRACKET); return;
|
|
case '|': SerialSubmitShiftedKey(INPUT_KEY_BACKSLASH); return;
|
|
case ':': SerialSubmitShiftedKey(INPUT_KEY_SEMICOLON); return;
|
|
case '"': SerialSubmitShiftedKey(INPUT_KEY_APOSTROPHE); return;
|
|
case '~': SerialSubmitShiftedKey(INPUT_KEY_GRAVE); return;
|
|
case '<': SerialSubmitShiftedKey(INPUT_KEY_COMMA); return;
|
|
case '>': SerialSubmitShiftedKey(INPUT_KEY_PERIOD); return;
|
|
case '?': SerialSubmitShiftedKey(INPUT_KEY_SLASH); return;
|
|
default: return;
|
|
}
|
|
}
|
|
|
|
// Submits a completed CSI escape sequence.
|
|
static void SerialSubmitCsi(uint8_t finalByte) {
|
|
uint8_t number = gSerialCsiNumber;
|
|
gSerialEscapeState = SerialEscapeNone;
|
|
gSerialCsiNumber = 0;
|
|
|
|
switch (finalByte) {
|
|
case 'A': SerialSubmitKey(INPUT_KEY_UP); return;
|
|
case 'B': SerialSubmitKey(INPUT_KEY_DOWN); return;
|
|
case 'C': SerialSubmitKey(INPUT_KEY_RIGHT); return;
|
|
case 'D': SerialSubmitKey(INPUT_KEY_LEFT); return;
|
|
case 'H': SerialSubmitKey(INPUT_KEY_HOME); return;
|
|
case 'F': SerialSubmitKey(INPUT_KEY_END); return;
|
|
case '~':
|
|
switch (number) {
|
|
case 1: SerialSubmitKey(INPUT_KEY_HOME); return;
|
|
case 3: SerialSubmitKey(INPUT_KEY_DELETE); return;
|
|
case 4: SerialSubmitKey(INPUT_KEY_END); return;
|
|
case 5: SerialSubmitKey(INPUT_KEY_PAGE_UP); return;
|
|
case 6: SerialSubmitKey(INPUT_KEY_PAGE_DOWN); return;
|
|
default: return;
|
|
}
|
|
default: return;
|
|
}
|
|
}
|
|
|
|
// Handles one byte after an ESC prefix.
|
|
static bool SerialHandleEscapeByte(uint8_t ch) {
|
|
if (gSerialEscapeState == SerialEscapeNone) return false;
|
|
|
|
if (gSerialEscapeState == SerialEscapeEsc) {
|
|
if (ch == '[') {
|
|
gSerialEscapeState = SerialEscapeCsi;
|
|
gSerialCsiNumber = 0;
|
|
return true;
|
|
}
|
|
|
|
gSerialEscapeState = SerialEscapeNone;
|
|
gSerialCsiNumber = 0;
|
|
SerialSubmitKey(INPUT_KEY_ESCAPE);
|
|
return false;
|
|
}
|
|
|
|
if (gSerialEscapeState == SerialEscapeCsi || gSerialEscapeState == SerialEscapeCsiNumber) {
|
|
if (ch >= '0' && ch <= '9') {
|
|
gSerialEscapeState = SerialEscapeCsiNumber;
|
|
if (gSerialCsiNumber < 25)
|
|
gSerialCsiNumber = (uint8_t)(gSerialCsiNumber * 10u + (ch - '0'));
|
|
return true;
|
|
}
|
|
|
|
if (ch == ';')
|
|
return true;
|
|
|
|
SerialSubmitCsi(ch);
|
|
return true;
|
|
}
|
|
|
|
gSerialEscapeState = SerialEscapeNone;
|
|
gSerialCsiNumber = 0;
|
|
return false;
|
|
}
|
|
|
|
// Routes one received serial byte into the input core.
|
|
static void SerialReceiveByte(uint8_t ch) {
|
|
if (SerialHandleEscapeByte(ch)) return;
|
|
|
|
if (ch == 0x1Bu) {
|
|
gSerialEscapeState = SerialEscapeEsc;
|
|
gSerialCsiNumber = 0;
|
|
return;
|
|
}
|
|
|
|
if (ch == '\r') {
|
|
SerialSubmitKey(INPUT_KEY_ENTER);
|
|
gSerialDropNextLf = true;
|
|
return;
|
|
}
|
|
|
|
if (ch == '\n') {
|
|
if (gSerialDropNextLf) {
|
|
gSerialDropNextLf = false;
|
|
return;
|
|
}
|
|
SerialSubmitKey(INPUT_KEY_ENTER);
|
|
return;
|
|
}
|
|
|
|
gSerialDropNextLf = false;
|
|
|
|
if (ch == '\t') {
|
|
SerialSubmitKey(INPUT_KEY_TAB);
|
|
return;
|
|
}
|
|
|
|
if (ch == '\b' || ch == 0x7Fu) {
|
|
SerialSubmitKey(INPUT_KEY_BACKSPACE);
|
|
return;
|
|
}
|
|
|
|
if (ch == 0x03u) {
|
|
SerialSubmitCtrlKey(INPUT_KEY_C);
|
|
return;
|
|
}
|
|
|
|
if (ch >= 0x20u && ch <= 0x7Eu)
|
|
SerialSubmitPrintable(ch);
|
|
}
|
|
|
|
// Handles queued receive bytes at DISPATCH_LEVEL.
|
|
static void SerialRxDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) {
|
|
(void)dpc;
|
|
(void)context;
|
|
(void)systemArgument1;
|
|
(void)systemArgument2;
|
|
|
|
while (gSerialRxHead != gSerialRxTail) {
|
|
uint32_t head = gSerialRxHead;
|
|
uint8_t ch = gSerialRxQueue[head];
|
|
gSerialRxHead = (head + 1u) & SERIAL_RX_QUEUE_MASK;
|
|
SerialReceiveByte(ch);
|
|
}
|
|
}
|
|
|
|
// Queues a received byte for bottom-half processing.
|
|
static void SerialQueueRxByte(uint8_t ch) {
|
|
uint32_t tail = gSerialRxTail;
|
|
uint32_t next = (tail + 1u) & SERIAL_RX_QUEUE_MASK;
|
|
if (next == gSerialRxHead) {
|
|
gSerialRxDropped++;
|
|
return;
|
|
}
|
|
|
|
gSerialRxQueue[tail] = ch;
|
|
gSerialRxTail = next;
|
|
KeInsertQueueDpc(&gSerialRxDpc, 0, 0);
|
|
}
|
|
|
|
// Handles receive interrupts for COM1.
|
|
static void SerialIsr(void) {
|
|
for (uint32_t i = 0; i < 256u; i++) {
|
|
if ((SerialRead(SERIAL_REG_LSR) & SERIAL_LSR_DATA_READY) == 0)
|
|
break;
|
|
SerialQueueRxByte(SerialRead(SERIAL_REG_DATA));
|
|
}
|
|
}
|
|
|
|
// Initializes COM1 as an 8N1 115200 baud input device.
|
|
static void SerialInitializeCom1(void) {
|
|
SerialWrite(SERIAL_REG_IER, 0x00u);
|
|
SerialWrite(SERIAL_REG_LCR, SERIAL_LCR_DLAB);
|
|
SerialWrite(SERIAL_REG_DATA, 0x01u);
|
|
SerialWrite(SERIAL_REG_IER, 0x00u);
|
|
SerialWrite(SERIAL_REG_LCR, 0x03u);
|
|
SerialWrite(SERIAL_REG_FCR, SERIAL_FCR_ENABLE | SERIAL_FCR_CLEAR_RX |
|
|
SERIAL_FCR_CLEAR_TX | SERIAL_FCR_TRIGGER_14);
|
|
SerialWrite(SERIAL_REG_MCR, SERIAL_MCR_DTR | SERIAL_MCR_RTS | SERIAL_MCR_OUT2);
|
|
|
|
while (SerialRead(SERIAL_REG_LSR) & SERIAL_LSR_DATA_READY)
|
|
(void)SerialRead(SERIAL_REG_DATA);
|
|
|
|
SerialWrite(SERIAL_REG_IER, SERIAL_IER_RX_READY);
|
|
}
|
|
|
|
// DriverEntry registers COM1 as an input source.
|
|
KSTATUS DriverEntry(const NTL_DDK* ddk, NTL_DRIVER_INIT* init) {
|
|
(void)ddk;
|
|
(void)init;
|
|
|
|
uint32_t deviceId = 0;
|
|
KSTATUS st = KeInputRegisterDevice("serial-com1", 0, &deviceId);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
KeInitializeDpc(&gSerialRxDpc, SerialRxDpcRoutine, 0);
|
|
gSerialEscapeState = SerialEscapeNone;
|
|
gSerialCsiNumber = 0;
|
|
gSerialDropNextLf = false;
|
|
gSerialRxHead = 0;
|
|
gSerialRxTail = 0;
|
|
gSerialRxDropped = 0;
|
|
|
|
SerialInitializeCom1();
|
|
KeIrqRegister(SERIAL_COM1_IRQ, SerialIsr);
|
|
|
|
DbgPrint("serial: initialized COM1 input device=%u irq=%u\n", deviceId, SERIAL_COM1_IRQ);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|