mirror of
https://github.com/EnderIce2/Fennix.git
synced 2025-07-03 03:19:16 +00:00
feat(kernel/drivers): migrate drivers to the kernel
make the drivers builtin Signed-off-by: EnderIce2 <enderice2@protonmail.com>
This commit is contained in:
43
Kernel/drivers/misc/aip/aip.hpp
Normal file
43
Kernel/drivers/misc/aip/aip.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
This file is part of Fennix Kernel.
|
||||
|
||||
Fennix Kernel 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.
|
||||
|
||||
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <types.h>
|
||||
#include <cpu.hpp>
|
||||
|
||||
namespace Driver::AdvancedIntegratedPeripheral
|
||||
{
|
||||
extern uint8_t Device1ID[];
|
||||
extern uint8_t Device2ID[];
|
||||
|
||||
void PS2KbdInterruptHandler(CPU::TrapFrame *);
|
||||
int InitializeKeyboard();
|
||||
int FinalizeKeyboard();
|
||||
int DetectPS2Keyboard();
|
||||
|
||||
void PS2MouseInterruptHandler(CPU::TrapFrame *);
|
||||
int InitializeMouse();
|
||||
int FinalizeMouse();
|
||||
int DetectPS2Mouse();
|
||||
int DetectUART();
|
||||
|
||||
void MasterInterruptHandler(CPU::TrapFrame *);
|
||||
void SlaveInterruptHandler(CPU::TrapFrame *);
|
||||
int InitializeATA();
|
||||
int FinalizeATA();
|
||||
}
|
70
Kernel/drivers/misc/aip/ata.cpp
Normal file
70
Kernel/drivers/misc/aip/ata.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
This file is part of Fennix Kernel.
|
||||
|
||||
Fennix Kernel 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.
|
||||
|
||||
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <driver.hpp>
|
||||
#include <cpu.hpp>
|
||||
#include <pci.hpp>
|
||||
#include <io.h>
|
||||
|
||||
extern Driver::Manager *DriverManager;
|
||||
extern PCI::Manager *PCIManager;
|
||||
namespace Driver::AdvancedIntegratedPeripheral
|
||||
{
|
||||
extern dev_t DriverID;
|
||||
|
||||
const struct InodeOperations opsATA = {
|
||||
.Lookup = nullptr,
|
||||
.Create = nullptr,
|
||||
.Remove = nullptr,
|
||||
.Rename = nullptr,
|
||||
.Read = nullptr,
|
||||
.Write = nullptr,
|
||||
.Truncate = nullptr,
|
||||
.Open = nullptr,
|
||||
.Close = nullptr,
|
||||
.Ioctl = nullptr,
|
||||
.ReadDir = nullptr,
|
||||
.MkDir = nullptr,
|
||||
.RmDir = nullptr,
|
||||
.SymLink = nullptr,
|
||||
.ReadLink = nullptr,
|
||||
.Seek = nullptr,
|
||||
.Stat = nullptr,
|
||||
};
|
||||
|
||||
void MasterInterruptHandler(CPU::TrapFrame *)
|
||||
{
|
||||
}
|
||||
|
||||
void SlaveInterruptHandler(CPU::TrapFrame *)
|
||||
{
|
||||
}
|
||||
|
||||
int InitializeATA()
|
||||
{
|
||||
v0::RegisterInterruptHandler(DriverID, 14, (void *)MasterInterruptHandler);
|
||||
v0::RegisterInterruptHandler(DriverID, 15, (void *)SlaveInterruptHandler);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FinalizeATA()
|
||||
{
|
||||
v0::UnregisterInterruptHandler(DriverID, 14, (void *)MasterInterruptHandler);
|
||||
v0::UnregisterInterruptHandler(DriverID, 15, (void *)SlaveInterruptHandler);
|
||||
return 0;
|
||||
}
|
||||
}
|
224
Kernel/drivers/misc/aip/keyboard.cpp
Normal file
224
Kernel/drivers/misc/aip/keyboard.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
This file is part of Fennix Drivers.
|
||||
|
||||
Fennix Drivers 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.
|
||||
|
||||
Fennix Drivers 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 Fennix Drivers. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "aip.hpp"
|
||||
|
||||
#include <driver.hpp>
|
||||
#include <interface/input.h>
|
||||
#include <interface/aip.h>
|
||||
#include <cpu.hpp>
|
||||
#include <io.h>
|
||||
|
||||
extern const unsigned short ScanCodeSet1[];
|
||||
extern const unsigned short ScanCodeSet1mm[];
|
||||
extern const unsigned short ScanCodeSet3[];
|
||||
|
||||
namespace Driver::AdvancedIntegratedPeripheral
|
||||
{
|
||||
extern dev_t DriverID;
|
||||
uint8_t KeyboardScanCodeSet = 0;
|
||||
dev_t KeyboardDevID = -1;
|
||||
|
||||
InputReport kir = {};
|
||||
int ReportKeyboardEvent(dev_t Device, const unsigned short ScanCode, uint8_t Pressed)
|
||||
{
|
||||
kir.Type = INPUT_TYPE_KEYBOARD;
|
||||
kir.Device = Device;
|
||||
kir.Keyboard.Key = (KeyScanCodes)(ScanCode);
|
||||
kir.Keyboard.Key = (KeyScanCodes)((int)kir.Keyboard.Key | (Pressed ? KEY_PRESSED : 0));
|
||||
// kir.Keyboard.Key |= Pressed ? KEY_PRESSED : 0;
|
||||
v0::ReportInputEvent(DriverID, &kir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IsE0 = false;
|
||||
bool IsE1 = false;
|
||||
void PS2KbdInterruptHandler(CPU::TrapFrame *)
|
||||
{
|
||||
uint8_t sc = inb(PS2_DATA);
|
||||
if (sc == PS2_KBD_RESP_ACK ||
|
||||
sc == PS2_KBD_RESP_ECHO ||
|
||||
sc == PS2_KBD_RESP_RESEND)
|
||||
return;
|
||||
|
||||
if (sc == 0xE0)
|
||||
{
|
||||
IsE0 = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sc == 0xE1)
|
||||
{
|
||||
IsE1 = true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (KeyboardScanCodeSet)
|
||||
{
|
||||
case PS2_KBD_SC_SET_1:
|
||||
case PS2_KBD_SC_SET_2:
|
||||
{
|
||||
if (IsE0)
|
||||
{
|
||||
IsE0 = false;
|
||||
ReportKeyboardEvent(KeyboardDevID, ScanCodeSet1mm[sc], sc < 0x90);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool released = sc & 0x80;
|
||||
uint8_t scFinal = released ? sc & 0x7F : sc;
|
||||
ReportKeyboardEvent(KeyboardDevID, ScanCodeSet1[scFinal], !released);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* FIXME: https://wiki.osdev.org/PS/2_Keyboard */
|
||||
// case PS2_KBD_SC_SET_2:
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
case PS2_KBD_SC_SET_3:
|
||||
{
|
||||
ReportKeyboardEvent(KeyboardDevID, ScanCodeSet3[sc], true);
|
||||
ReportKeyboardEvent(KeyboardDevID, ScanCodeSet3[sc], false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (IsE0)
|
||||
IsE0 = false;
|
||||
trace("Unknown PS/2 Keyboard Scan Code Set: %#x", KeyboardScanCodeSet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int __fs_kb_Ioctl(struct Inode *, unsigned long, void *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct InodeOperations KbdOps = {
|
||||
.Lookup = nullptr,
|
||||
.Create = nullptr,
|
||||
.Remove = nullptr,
|
||||
.Rename = nullptr,
|
||||
.Read = nullptr,
|
||||
.Write = nullptr,
|
||||
.Truncate = nullptr,
|
||||
.Open = nullptr,
|
||||
.Close = nullptr,
|
||||
.Ioctl = __fs_kb_Ioctl,
|
||||
.ReadDir = nullptr,
|
||||
.MkDir = nullptr,
|
||||
.RmDir = nullptr,
|
||||
.SymLink = nullptr,
|
||||
.ReadLink = nullptr,
|
||||
.Seek = nullptr,
|
||||
.Stat = nullptr,
|
||||
};
|
||||
|
||||
int InitializeKeyboard()
|
||||
{
|
||||
// v0::PS2WriteData(DriverID, PS2_KBD_CMD_RESET);
|
||||
// uint8_t test = v0::PS2ReadData(DriverID);
|
||||
// if (test != PS2_KBD_RESP_TEST_PASSED &&
|
||||
// test != PS2_KBD_RESP_ACK)
|
||||
// {
|
||||
// trace("PS/2 keyboard reset failed (%#x)", test);
|
||||
// return -EFAULT;
|
||||
// }
|
||||
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_CMD_DEFAULTS);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 keyboard failed to set defaults");
|
||||
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_CMD_SCAN_CODE_SET);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 keyboard failed to set scan code set");
|
||||
|
||||
/* We want Scan Code Set 1 */
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_SCAN_CODE_SET_2); /* It will set to 1 but with translation? */
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 keyboard failed to set scan code set 2");
|
||||
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_CMD_SCAN_CODE_SET);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 keyboard failed to set scan code set");
|
||||
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_SCAN_CODE_GET_CURRENT);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 keyboard failed to get current scan code set");
|
||||
|
||||
KeyboardScanCodeSet = v0::PS2ReadAfterACK(DriverID);
|
||||
trace("PS/2 Keyboard Scan Code Set: 0x%X", KeyboardScanCodeSet);
|
||||
v0::PS2ClearOutputBuffer(DriverID);
|
||||
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_CMD_ENABLE_SCANNING);
|
||||
|
||||
v0::RegisterInterruptHandler(DriverID, 1, (void *)PS2KbdInterruptHandler);
|
||||
|
||||
KeyboardDevID = v0::RegisterDevice(DriverID, INPUT_TYPE_KEYBOARD, &KbdOps);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FinalizeKeyboard()
|
||||
{
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_CMD_DISABLE_SCANNING);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 keyboard failed to disable scanning");
|
||||
|
||||
v0::UnregisterDevice(DriverID, KeyboardDevID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DetectPS2Keyboard()
|
||||
{
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_CMD_DISABLE_SCANNING);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 keyboard failed to disable scanning");
|
||||
|
||||
v0::PS2WriteData(DriverID, PS2_KBD_CMD_IDENTIFY);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 keyboard failed to identify");
|
||||
|
||||
uint8_t recByte;
|
||||
int timeout = 1000000;
|
||||
while (timeout--)
|
||||
{
|
||||
recByte = v0::PS2ReadData(DriverID);
|
||||
if (recByte != PS2_ACK)
|
||||
break;
|
||||
}
|
||||
Device1ID[0] = recByte;
|
||||
|
||||
timeout = 1000000;
|
||||
while (timeout--)
|
||||
{
|
||||
recByte = v0::PS2ReadData(DriverID);
|
||||
if (recByte != PS2_ACK)
|
||||
break;
|
||||
}
|
||||
if (timeout == 0)
|
||||
trace("PS/2 keyboard second byte timed out");
|
||||
else
|
||||
Device1ID[1] = recByte;
|
||||
|
||||
trace("PS2 Keyboard Device: 0x%X 0x%X", Device1ID[0], Device1ID[1]);
|
||||
return 0;
|
||||
}
|
||||
}
|
260
Kernel/drivers/misc/aip/main.cpp
Normal file
260
Kernel/drivers/misc/aip/main.cpp
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
This file is part of Fennix Kernel.
|
||||
|
||||
Fennix Kernel 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.
|
||||
|
||||
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <driver.hpp>
|
||||
#include <interface/aip.h>
|
||||
#include <cpu.hpp>
|
||||
#include <pci.hpp>
|
||||
|
||||
#include "aip.hpp"
|
||||
|
||||
extern Driver::Manager *DriverManager;
|
||||
extern PCI::Manager *PCIManager;
|
||||
EXTERNC void KPrint(const char *Format, ...);
|
||||
namespace Driver::AdvancedIntegratedPeripheral
|
||||
{
|
||||
dev_t DriverID;
|
||||
|
||||
bool IsATAPresent()
|
||||
{
|
||||
outb(0x1F0 + 2, 0);
|
||||
outb(0x1F0 + 3, 0);
|
||||
outb(0x1F0 + 4, 0);
|
||||
outb(0x1F0 + 5, 0);
|
||||
outb(0x1F0 + 7, 0xEC);
|
||||
if (inb(0x1F0 + 7) == 0 || inb(0x1F0 + 1) != 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsKeyboard(uint8_t ID)
|
||||
{
|
||||
/* Common keyboard IDs */
|
||||
return ID == 0xAB || ID == 0xAC || ID == 0x5D ||
|
||||
ID == 0x2B || ID == 0x47 || ID == 0x60;
|
||||
}
|
||||
|
||||
bool IsMouse(uint8_t ID)
|
||||
{
|
||||
/* Common mouse IDs */
|
||||
return ID == 0x00 || ID == 0x03 || ID == 0x04;
|
||||
}
|
||||
|
||||
const char *GetPS2DeviceName(uint8_t ID, uint8_t SubID)
|
||||
{
|
||||
switch (ID)
|
||||
{
|
||||
case 0x00:
|
||||
return "Standard PS/2 Mouse";
|
||||
case 0x03:
|
||||
return "Mouse with scroll wheel";
|
||||
case 0x04:
|
||||
return "Mouse 5 buttons";
|
||||
case 0xAB:
|
||||
{
|
||||
switch (SubID)
|
||||
{
|
||||
case 0x83: /* Normal */
|
||||
case 0x41: /* Translated */
|
||||
case 0xC1: /* Normal + Translated */
|
||||
return "Standard PS/2 Keyboard";
|
||||
case 0x84:
|
||||
case 0x54:
|
||||
return "IBM Thinkpad/Spacesaver Keyboard";
|
||||
case 0x85:
|
||||
return "NCD N-97/122-Key Host Connect(ed) Keyboard";
|
||||
case 0x86:
|
||||
return "122-Key Keyboard";
|
||||
case 0x90:
|
||||
return "Japanese \"G\" Keyboard";
|
||||
case 0x91:
|
||||
return "Japanese \"P\" Keyboard";
|
||||
case 0x92:
|
||||
return "Japanese \"A\" Keyboard";
|
||||
default:
|
||||
return "Unknown PS/2 Keyboard";
|
||||
}
|
||||
}
|
||||
case 0xAC:
|
||||
{
|
||||
switch (SubID)
|
||||
{
|
||||
case 0xA1:
|
||||
return "NCD Sun Keyboard";
|
||||
default:
|
||||
return "Unknown NCD Sun Keyboard";
|
||||
}
|
||||
}
|
||||
case 0x5D:
|
||||
case 0x2B:
|
||||
return "Trust Keyboard";
|
||||
case 0x47:
|
||||
case 0x60:
|
||||
return "NMB SGI Keyboard";
|
||||
default:
|
||||
return "Unknown PS/2 Device";
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Device1ID[2] = {0x00, 0x00};
|
||||
uint8_t Device2ID[2] = {0x00, 0x00};
|
||||
bool DualChannel = false;
|
||||
bool ATAPresent = false;
|
||||
|
||||
int Entry()
|
||||
{
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_DISABLE_PORT_1);
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_DISABLE_PORT_2);
|
||||
v0::PS2ClearOutputBuffer(DriverID);
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_READ_CONFIG);
|
||||
PS2_CONFIGURATION cfg = {.Raw = v0::PS2ReadData(DriverID)};
|
||||
|
||||
DualChannel = cfg.Port2Clock;
|
||||
if (DualChannel)
|
||||
trace("Dual channel PS/2 controller detected");
|
||||
cfg.Port1Interrupt = 1;
|
||||
cfg.Port2Interrupt = 1;
|
||||
cfg.Port1Translation = 1;
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_CONFIG);
|
||||
v0::PS2WriteData(DriverID, cfg.Raw);
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_TEST_CONTROLLER);
|
||||
uint8_t test = v0::PS2ReadData(DriverID);
|
||||
if (test != PS2_TEST_PASSED)
|
||||
{
|
||||
trace("PS/2 controller self test failed (%#x)", test);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_CONFIG);
|
||||
v0::PS2WriteData(DriverID, cfg.Raw);
|
||||
|
||||
// bool port2avail = false;
|
||||
// if (DualChannel)
|
||||
// {
|
||||
// v0::PS2WriteCommand(DriverID, PS2_CMD_ENABLE_PORT_1);
|
||||
// v0::PS2WriteCommand(DriverID, PS2_CMD_READ_CONFIG);
|
||||
// cfg.Raw = v0::PS2ReadData(DriverID);
|
||||
// port2avail = cfg.Port2Clock;
|
||||
// v0::PS2WriteCommand(DriverID, PS2_CMD_DISABLE_PORT_1);
|
||||
// }
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_TEST_PORT_1);
|
||||
test = v0::PS2ReadData(DriverID);
|
||||
if (test != 0x00)
|
||||
{
|
||||
trace("PS/2 Port 1 self test failed (%#x)", test);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (DualChannel)
|
||||
{
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_TEST_PORT_2);
|
||||
test = v0::PS2ReadData(DriverID);
|
||||
if (test != 0x00)
|
||||
{
|
||||
trace("PS/2 Port 2 self test failed (%#x)", test);
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_ENABLE_PORT_1);
|
||||
if (DualChannel)
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_ENABLE_PORT_2);
|
||||
|
||||
int errK = InitializeKeyboard();
|
||||
|
||||
int errM = 0;
|
||||
if (DualChannel)
|
||||
errM = InitializeMouse();
|
||||
|
||||
ATAPresent = IsATAPresent();
|
||||
|
||||
if (errK != 0 && errM != 0 && ATAPresent == false)
|
||||
return -ENODEV;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Final()
|
||||
{
|
||||
FinalizeKeyboard();
|
||||
FinalizeMouse();
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_DISABLE_PORT_1);
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_DISABLE_PORT_2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Panic()
|
||||
{
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_DISABLE_PORT_1);
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_DISABLE_PORT_2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __intStub() {}
|
||||
int Probe()
|
||||
{
|
||||
v0::RegisterInterruptHandler(DriverID, 1, (void *)__intStub);
|
||||
v0::RegisterInterruptHandler(DriverID, 12, (void *)__intStub);
|
||||
|
||||
int kbd = DetectPS2Keyboard();
|
||||
int mouse = DetectPS2Mouse();
|
||||
int uart = DetectUART();
|
||||
|
||||
v0::UnregisterAllInterruptHandlers(DriverID, (void *)__intStub);
|
||||
|
||||
if (kbd != 0 && mouse != 0 && uart != 0)
|
||||
return -ENODEV;
|
||||
|
||||
if (kbd == 0)
|
||||
{
|
||||
if (!IsKeyboard(Device1ID[0]))
|
||||
{
|
||||
trace("PS/2 Port 1 is not a keyboard");
|
||||
// return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse == 0)
|
||||
{
|
||||
if (!IsMouse(Device2ID[0]))
|
||||
{
|
||||
trace("PS/2 Port 2 is not a mouse");
|
||||
// return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
KPrint("PS/2 Port 1: %s (0x%X 0x%X)",
|
||||
GetPS2DeviceName(Device1ID[0], Device1ID[1]),
|
||||
Device1ID[0], Device1ID[1]);
|
||||
KPrint("PS/2 Port 2: %s (0x%X 0x%X)",
|
||||
GetPS2DeviceName(Device2ID[0], Device2ID[1]),
|
||||
Device2ID[0], Device2ID[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
REGISTER_BUILTIN_DRIVER(aip,
|
||||
"Advanced Integrated Peripheral Driver",
|
||||
"enderice2",
|
||||
1, 0, 0,
|
||||
Entry,
|
||||
Final,
|
||||
Panic,
|
||||
Probe);
|
||||
}
|
263
Kernel/drivers/misc/aip/mouse.cpp
Normal file
263
Kernel/drivers/misc/aip/mouse.cpp
Normal file
@ -0,0 +1,263 @@
|
||||
/*
|
||||
This file is part of Fennix Drivers.
|
||||
|
||||
Fennix Drivers 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.
|
||||
|
||||
Fennix Drivers 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 Fennix Drivers. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "aip.hpp"
|
||||
|
||||
#include <driver.hpp>
|
||||
#include <interface/aip.h>
|
||||
#include <interface/input.h>
|
||||
|
||||
namespace Driver::AdvancedIntegratedPeripheral
|
||||
{
|
||||
extern dev_t DriverID;
|
||||
dev_t MouseDevID = -1;
|
||||
bool PacketReady = false;
|
||||
bool FourPackets = false;
|
||||
bool MouseButton45 = false;
|
||||
uint8_t Cycle = 0;
|
||||
PS2_MOUSE_PACKET Packet = {};
|
||||
|
||||
InputReport mir = {};
|
||||
void PS2MouseInterruptHandler(CPU::TrapFrame *)
|
||||
{
|
||||
uint8_t data = v0::PS2ReadData(DriverID);
|
||||
if (data == PS2_MOUSE_RESP_ACK ||
|
||||
data == PS2_MOUSE_RESP_RESEND)
|
||||
return;
|
||||
|
||||
if (!PacketReady)
|
||||
{
|
||||
switch (Cycle)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if ((data & 0b00001000 /* Always 1 */) == 0)
|
||||
return;
|
||||
|
||||
Packet.Base.Raw = data;
|
||||
Cycle++;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
Packet.XMovement = data;
|
||||
Cycle++;
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
Packet.YMovement = data;
|
||||
if (FourPackets)
|
||||
Cycle++;
|
||||
else
|
||||
{
|
||||
Cycle = 0;
|
||||
PacketReady = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
Packet.ZMovement.Raw = data;
|
||||
Cycle = 0;
|
||||
PacketReady = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/a/3208376/9352057 */
|
||||
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
|
||||
#define BYTE_TO_BINARY(byte) \
|
||||
((byte) & 0x80 ? '1' : '0'), \
|
||||
((byte) & 0x40 ? '1' : '0'), \
|
||||
((byte) & 0x20 ? '1' : '0'), \
|
||||
((byte) & 0x10 ? '1' : '0'), \
|
||||
((byte) & 0x08 ? '1' : '0'), \
|
||||
((byte) & 0x04 ? '1' : '0'), \
|
||||
((byte) & 0x02 ? '1' : '0'), \
|
||||
((byte) & 0x01 ? '1' : '0')
|
||||
|
||||
debug("PS/2 Mouse Packet: [" BYTE_TO_BINARY_PATTERN ":" BYTE_TO_BINARY_PATTERN ":" BYTE_TO_BINARY_PATTERN ":" BYTE_TO_BINARY_PATTERN "] LB:%d RB:%d MB:%d A1:%d XS:%d YS:%d XO:%d YO:%d | X:%03d Y:%03d | Z:%d B4:%d B5:%d A0:%d A0:%d",
|
||||
BYTE_TO_BINARY(Packet.Base.Raw),
|
||||
BYTE_TO_BINARY(Packet.XMovement),
|
||||
BYTE_TO_BINARY(Packet.YMovement),
|
||||
BYTE_TO_BINARY(Packet.ZMovement.Raw),
|
||||
Packet.Base.LeftButton, Packet.Base.RightButton, Packet.Base.MiddleButton,
|
||||
Packet.Base.Always1,
|
||||
Packet.Base.XSign, Packet.Base.YSign,
|
||||
Packet.Base.XOverflow, Packet.Base.YOverflow,
|
||||
Packet.XMovement, Packet.YMovement,
|
||||
Packet.ZMovement.Z, Packet.ZMovement.Button4, Packet.ZMovement.Button5,
|
||||
Packet.ZMovement.Always0, Packet.ZMovement.Always0_2);
|
||||
|
||||
int X, Y;
|
||||
X = Packet.XMovement - (Packet.Base.XSign ? 256 : 0);
|
||||
Y = Packet.YMovement - (Packet.Base.YSign ? 256 : 0);
|
||||
|
||||
if (Packet.Base.XOverflow)
|
||||
X = 0;
|
||||
|
||||
if (Packet.Base.YOverflow)
|
||||
Y = 0;
|
||||
|
||||
mir.Type = INPUT_TYPE_MOUSE;
|
||||
mir.Device = MouseDevID;
|
||||
mir.Mouse.LeftButton = Packet.Base.LeftButton;
|
||||
mir.Mouse.RightButton = Packet.Base.RightButton;
|
||||
mir.Mouse.MiddleButton = Packet.Base.MiddleButton;
|
||||
mir.Mouse.Button4 = Packet.ZMovement.Button4;
|
||||
mir.Mouse.Button5 = Packet.ZMovement.Button5;
|
||||
mir.Mouse.X = X;
|
||||
mir.Mouse.Y = -Y;
|
||||
mir.Mouse.Z = Packet.ZMovement.Z;
|
||||
v0::ReportInputEvent(DriverID, &mir);
|
||||
PacketReady = false;
|
||||
}
|
||||
|
||||
void MouseSampleRate(uint8_t SampleRate)
|
||||
{
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_SET_SAMPLE_RATE);
|
||||
v0::PS2ReadData(DriverID);
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, SampleRate);
|
||||
v0::PS2ReadData(DriverID);
|
||||
}
|
||||
|
||||
int __fs_ms_Ioctl(struct Inode *, unsigned long, void *)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct InodeOperations MouseOps = {
|
||||
.Lookup = nullptr,
|
||||
.Create = nullptr,
|
||||
.Remove = nullptr,
|
||||
.Rename = nullptr,
|
||||
.Read = nullptr,
|
||||
.Write = nullptr,
|
||||
.Truncate = nullptr,
|
||||
.Open = nullptr,
|
||||
.Close = nullptr,
|
||||
.Ioctl = __fs_ms_Ioctl,
|
||||
.ReadDir = nullptr,
|
||||
.MkDir = nullptr,
|
||||
.RmDir = nullptr,
|
||||
.SymLink = nullptr,
|
||||
.ReadLink = nullptr,
|
||||
.Seek = nullptr,
|
||||
.Stat = nullptr,
|
||||
};
|
||||
|
||||
int InitializeMouse()
|
||||
{
|
||||
v0::PS2WriteData(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_RESET);
|
||||
uint8_t test = v0::PS2ReadData(DriverID);
|
||||
if (test != PS2_MOUSE_RESP_TEST_PASSED &&
|
||||
test != PS2_MOUSE_RESP_ACK)
|
||||
{
|
||||
trace("PS/2 mouse reset failed! (%#x)", test);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
v0::RegisterInterruptHandler(DriverID, 12, (void *)PS2MouseInterruptHandler);
|
||||
|
||||
MouseDevID = v0::RegisterDevice(DriverID, INPUT_TYPE_MOUSE, &MouseOps);
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_SET_DEFAULTS);
|
||||
v0::PS2ReadData(DriverID);
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_ENABLE_DATA_REPORTING);
|
||||
|
||||
MouseSampleRate(200);
|
||||
MouseSampleRate(100);
|
||||
MouseSampleRate(80);
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_READ_ID);
|
||||
uint8_t Device2ID = v0::PS2ReadData(DriverID);
|
||||
trace("PS/2 Mouse ID: %#x", Device2ID);
|
||||
|
||||
MouseSampleRate(200);
|
||||
MouseSampleRate(200);
|
||||
MouseSampleRate(80);
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_READ_ID);
|
||||
Device2ID = v0::PS2ReadData(DriverID);
|
||||
trace("PS/2 Mouse ID: %#x", Device2ID);
|
||||
|
||||
if (Device2ID >= 3 && Device2ID <= 4)
|
||||
FourPackets = true;
|
||||
if (Device2ID == 4)
|
||||
MouseButton45 = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FinalizeMouse()
|
||||
{
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_DISABLE_DATA_REPORTING);
|
||||
|
||||
v0::UnregisterDevice(DriverID, MouseDevID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DetectPS2Mouse()
|
||||
{
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_DISABLE_DATA_REPORTING);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 mouse failed to disable data reporting!");
|
||||
|
||||
v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
|
||||
v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_READ_ID);
|
||||
if (v0::PS2ACKTimeout(DriverID) != 0)
|
||||
trace("PS/2 mouse failed to read ID!");
|
||||
|
||||
uint8_t recByte;
|
||||
int timeout = 1000000;
|
||||
while (timeout--)
|
||||
{
|
||||
recByte = v0::PS2ReadData(DriverID);
|
||||
if (recByte != PS2_ACK)
|
||||
break;
|
||||
}
|
||||
Device2ID[0] = recByte;
|
||||
|
||||
timeout = 1000000;
|
||||
while (timeout--)
|
||||
{
|
||||
recByte = v0::PS2ReadData(DriverID);
|
||||
if (recByte != PS2_ACK)
|
||||
break;
|
||||
}
|
||||
Device2ID[1] = recByte;
|
||||
|
||||
trace("PS2 Mouse Device: 0x%X 0x%X", Device2ID[0], Device2ID[1]);
|
||||
return 0;
|
||||
}
|
||||
}
|
154
Kernel/drivers/misc/aip/scancodes.c
Normal file
154
Kernel/drivers/misc/aip/scancodes.c
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
This file is part of Fennix Kernel.
|
||||
|
||||
Fennix Kernel 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.
|
||||
|
||||
Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <interface/driver.h>
|
||||
#include <interface/input.h>
|
||||
|
||||
const unsigned short ScanCodeSet1[] =
|
||||
{KEY_NULL, KEY_ESCAPE,
|
||||
KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0,
|
||||
KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_TAB,
|
||||
KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P,
|
||||
KEY_LEFT_BRACKET, KEY_RIGHT_BRACKET, KEY_RETURN, KEY_LEFT_CTRL,
|
||||
KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L,
|
||||
KEY_SEMICOLON, KEY_APOSTROPHE, KEY_BACK_TICK, KEY_LEFT_SHIFT, KEY_BACKSLASH,
|
||||
KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M,
|
||||
KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_RIGHT_SHIFT,
|
||||
KEYPAD_ASTERISK, KEY_LEFT_ALT, KEY_SPACE, KEY_CAPS_LOCK,
|
||||
KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10,
|
||||
KEY_NUM_LOCK, KEY_SCROLL_LOCK,
|
||||
KEYPAD_7, KEYPAD_8, KEYPAD_9, KEYPAD_MINUS,
|
||||
KEYPAD_4, KEYPAD_5, KEYPAD_6, KEYPAD_PLUS,
|
||||
KEYPAD_1, KEYPAD_2, KEYPAD_3, KEYPAD_0, KEYPAD_PERIOD,
|
||||
KEY_NULL, KEY_NULL, KEY_NULL,
|
||||
KEY_F11, KEY_F12};
|
||||
|
||||
const unsigned short ScanCodeSet1mm[] = {
|
||||
[0x10] = KEY_MULTIMEDIA_PREV_TRACK,
|
||||
[0x19] = KEY_MULTIMEDIA_NEXT_TRACK,
|
||||
[0x1C] = KEYPAD_RETURN,
|
||||
[0x1D] = KEY_RIGHT_CTRL,
|
||||
[0x20] = KEY_MULTIMEDIA_MUTE,
|
||||
[0x21] = KEY_MULTIMEDIA_CALCULATOR,
|
||||
[0x22] = KEY_MULTIMEDIA_PLAY,
|
||||
[0x24] = KEY_MULTIMEDIA_STOP,
|
||||
[0x2A] = KEY_PRINT_SCREEN,
|
||||
[0x2E] = KEY_MULTIMEDIA_VOL_DOWN,
|
||||
[0x30] = KEY_MULTIMEDIA_VOL_UP,
|
||||
[0x32] = KEY_MULTIMEDIA_WWW_HOME,
|
||||
[0x35] = KEYPAD_SLASH,
|
||||
[0x37] = KEY_PRINT_SCREEN,
|
||||
[0x38] = KEY_RIGHT_ALT,
|
||||
[0x47] = KEY_HOME,
|
||||
[0x48] = KEY_UP_ARROW,
|
||||
[0x49] = KEY_PAGE_UP,
|
||||
[0x4B] = KEY_LEFT_ARROW,
|
||||
[0x4D] = KEY_RIGHT_ARROW,
|
||||
[0x4F] = KEY_END,
|
||||
[0x50] = KEY_DOWN_ARROW,
|
||||
[0x51] = KEY_PAGE_DOWN,
|
||||
[0x52] = KEY_INSERT,
|
||||
[0x53] = KEY_DELETE,
|
||||
[0x5B] = KEY_LEFT_GUI,
|
||||
[0x5C] = KEY_RIGHT_GUI,
|
||||
[0x5D] = KEY_APPS,
|
||||
[0x5E] = KEY_ACPI_POWER,
|
||||
[0x5F] = KEY_ACPI_SLEEP,
|
||||
[0x63] = KEY_ACPI_WAKE,
|
||||
[0x65] = KEY_MULTIMEDIA_WWW_SEARCH,
|
||||
[0x66] = KEY_MULTIMEDIA_WWW_FAVORITES,
|
||||
[0x67] = KEY_MULTIMEDIA_WWW_REFRESH,
|
||||
[0x68] = KEY_MULTIMEDIA_WWW_STOP,
|
||||
[0x69] = KEY_MULTIMEDIA_WWW_FORWARD,
|
||||
[0x6A] = KEY_MULTIMEDIA_WWW_BACK,
|
||||
[0x6B] = KEY_MULTIMEDIA_MY_COMPUTER,
|
||||
[0x6C] = KEY_MULTIMEDIA_EMAIL,
|
||||
[0x6D] = KEY_MULTIMEDIA_MEDIA_SELECT,
|
||||
|
||||
/* RELEASED */
|
||||
|
||||
[0x90] = KEY_MULTIMEDIA_PREV_TRACK,
|
||||
[0x99] = KEY_MULTIMEDIA_NEXT_TRACK,
|
||||
[0x9C] = KEYPAD_RETURN,
|
||||
[0x9D] = KEY_RIGHT_CTRL,
|
||||
[0xA0] = KEY_MULTIMEDIA_MUTE,
|
||||
[0xA1] = KEY_MULTIMEDIA_CALCULATOR,
|
||||
[0xA2] = KEY_MULTIMEDIA_PLAY,
|
||||
[0xA4] = KEY_MULTIMEDIA_STOP,
|
||||
[0xAA] = KEY_PRINT_SCREEN,
|
||||
[0xAE] = KEY_MULTIMEDIA_VOL_DOWN,
|
||||
[0xB0] = KEY_MULTIMEDIA_VOL_UP,
|
||||
[0xB2] = KEY_MULTIMEDIA_WWW_HOME,
|
||||
[0xB5] = KEYPAD_SLASH,
|
||||
[0xB7] = KEY_PRINT_SCREEN,
|
||||
[0xB8] = KEY_RIGHT_ALT,
|
||||
[0xC7] = KEY_HOME,
|
||||
[0xC8] = KEY_UP_ARROW,
|
||||
[0xC9] = KEY_PAGE_UP,
|
||||
[0xCB] = KEY_LEFT_ARROW,
|
||||
[0xCD] = KEY_RIGHT_ARROW,
|
||||
[0xCF] = KEY_END,
|
||||
[0xD0] = KEY_DOWN_ARROW,
|
||||
[0xD1] = KEY_PAGE_DOWN,
|
||||
[0xD2] = KEY_INSERT,
|
||||
[0xD3] = KEY_DELETE,
|
||||
[0xDB] = KEY_LEFT_GUI,
|
||||
[0xDC] = KEY_RIGHT_GUI,
|
||||
[0xDD] = KEY_APPS,
|
||||
[0xDE] = KEY_ACPI_POWER,
|
||||
[0xDF] = KEY_ACPI_SLEEP,
|
||||
[0xE3] = KEY_ACPI_WAKE,
|
||||
[0xE5] = KEY_MULTIMEDIA_WWW_SEARCH,
|
||||
[0xE6] = KEY_MULTIMEDIA_WWW_FAVORITES,
|
||||
[0xE7] = KEY_MULTIMEDIA_WWW_REFRESH,
|
||||
[0xE8] = KEY_MULTIMEDIA_WWW_STOP,
|
||||
[0xE9] = KEY_MULTIMEDIA_WWW_FORWARD,
|
||||
[0xEA] = KEY_MULTIMEDIA_WWW_BACK,
|
||||
[0xEB] = KEY_MULTIMEDIA_MY_COMPUTER,
|
||||
[0xEC] = KEY_MULTIMEDIA_EMAIL,
|
||||
[0xED] = KEY_MULTIMEDIA_MEDIA_SELECT};
|
||||
|
||||
const unsigned short ScanCodeSet3[] = {
|
||||
[0x15] = KEY_Q,
|
||||
[0x1A] = KEY_Z,
|
||||
[0x1B] = KEY_S,
|
||||
[0x1C] = KEY_A,
|
||||
[0x1D] = KEY_W,
|
||||
|
||||
[0x21] = KEY_C,
|
||||
[0x22] = KEY_X,
|
||||
[0x23] = KEY_D,
|
||||
[0x24] = KEY_E,
|
||||
[0x2A] = KEY_V,
|
||||
[0x2B] = KEY_F,
|
||||
[0x2C] = KEY_T,
|
||||
[0x2D] = KEY_R,
|
||||
|
||||
[0x31] = KEY_N,
|
||||
[0x32] = KEY_B,
|
||||
[0x33] = KEY_H,
|
||||
[0x34] = KEY_G,
|
||||
[0x35] = KEY_Y,
|
||||
[0x3A] = KEY_M,
|
||||
[0x3B] = KEY_J,
|
||||
[0x3C] = KEY_U,
|
||||
|
||||
[0x42] = KEY_K,
|
||||
[0x43] = KEY_I,
|
||||
[0x44] = KEY_O,
|
||||
[0x4B] = KEY_L,
|
||||
[0x4D] = KEY_P};
|
647
Kernel/drivers/misc/aip/uart.cpp
Normal file
647
Kernel/drivers/misc/aip/uart.cpp
Normal file
@ -0,0 +1,647 @@
|
||||
/*
|
||||
This file is part of Fennix Drivers.
|
||||
|
||||
Fennix Drivers 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.
|
||||
|
||||
Fennix Drivers 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 Fennix Drivers. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <driver.hpp>
|
||||
#include <io.h>
|
||||
|
||||
#include "aip.hpp"
|
||||
|
||||
namespace Driver::AdvancedIntegratedPeripheral
|
||||
{
|
||||
extern dev_t DriverID;
|
||||
|
||||
#define SERIAL_ENABLE_DLAB 0x80
|
||||
#define SERIAL_BUFFER_EMPTY 0x20
|
||||
|
||||
enum Ports
|
||||
{
|
||||
COM1 = 0x3F8,
|
||||
COM2 = 0x2F8,
|
||||
COM3 = 0x3E8,
|
||||
COM4 = 0x2E8,
|
||||
COM5 = 0x5F8,
|
||||
COM6 = 0x4F8,
|
||||
COM7 = 0x5E8,
|
||||
COM8 = 0x4E8,
|
||||
|
||||
LPT1 = 0x378,
|
||||
LPT2 = 0x278,
|
||||
LPT3 = 0x3BC
|
||||
};
|
||||
|
||||
enum SerialSpeed
|
||||
{
|
||||
RATE_50_HI = 0x09,
|
||||
RATE_50_LO = 0x00,
|
||||
|
||||
RATE_300_HI = 0x01,
|
||||
RATE_300_LO = 0x80,
|
||||
|
||||
RATE_600_HI = 0x00,
|
||||
RATE_600_LO = 0xC0,
|
||||
|
||||
RATE_2400_HI = 0x00,
|
||||
RATE_2400_LO = 0x30,
|
||||
|
||||
RATE_4800_HI = 0x00,
|
||||
RATE_4800_LO = 0x18,
|
||||
|
||||
RATE_9600_HI = 0x00,
|
||||
RATE_9600_LO = 0x0C,
|
||||
|
||||
RATE_19200_HI = 0x00,
|
||||
RATE_19200_LO = 0x06,
|
||||
|
||||
RATE_38400_HI = 0x00,
|
||||
RATE_38400_LO = 0x03,
|
||||
|
||||
RATE_57600_HI = 0x00,
|
||||
RATE_57600_LO = 0x02,
|
||||
|
||||
RATE_115200_HI = 0x00,
|
||||
RATE_115200_LO = 0x01
|
||||
};
|
||||
|
||||
/*
|
||||
. Table of Registers .
|
||||
/---------------------------------------------------------------------\
|
||||
| Base Address | DLAB | R/W | Abr | Register Name |
|
||||
|---------------------------------------------------------------------|
|
||||
| +0 | =0 | W | - | Transmitter Holding Buffer |
|
||||
| | =0 | R | - | Receiver Buffer |
|
||||
| | =1 | R/W | - | Divisor Latch Low Byte |
|
||||
| +1 | =0 | R/W | IER | Interrupt Enable Register |
|
||||
| | =1 | R/W | - | Divisor Latch High Byte |
|
||||
| +2 | - | R | IIR | Interrupt Identification Register |
|
||||
| | - | W | FCR | FIFO Control Register |
|
||||
| +3 | - | R/W | LCR | Line Control Register |
|
||||
| +4 | - | R/W | MCR | Modem Control Register |
|
||||
| +5 | - | R | LSR | Line Status Register |
|
||||
| +6 | - | R | MSR | Modem Status Register |
|
||||
| +7 | - | R/W | - | Scratch Register |
|
||||
\---------------------------------------------------------------------/
|
||||
|
||||
Source:
|
||||
Interfacing the Serial / RS232 Port V5.0
|
||||
Table 5 : Table of Registers
|
||||
*/
|
||||
|
||||
/** Interrupt Enable Register */
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/* Enable Received Data Available Interrupt */
|
||||
uint8_t InterruptOnReceive : 1;
|
||||
|
||||
/* Enable Transmitter Holding Register Empty Interrupt */
|
||||
uint8_t InterruptOnTransmitter : 1;
|
||||
|
||||
/* Enable Receiver Line Status Interrupt */
|
||||
uint8_t LineStatusInterrupt : 1;
|
||||
|
||||
/* Enable Modem Status Interrupt */
|
||||
uint8_t ModemStatusInterrupt : 1;
|
||||
|
||||
/* Enables Sleep Mode (16750) */
|
||||
uint8_t SleepMode : 1;
|
||||
|
||||
/* Enables Low Power Mode (16750) */
|
||||
uint8_t LowPowerMode : 1;
|
||||
|
||||
/* Reserved */
|
||||
uint8_t __reserved : 2;
|
||||
};
|
||||
uint8_t raw;
|
||||
} IER;
|
||||
|
||||
/** Interrupt Identification Register */
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/* Interrupt pending */
|
||||
uint8_t InterruptPending : 1;
|
||||
|
||||
/**
|
||||
* Interrupt Status
|
||||
*
|
||||
* 00b = Modem Status Interrupt
|
||||
* 01b = Transmitter Holding Register Empty Interrupt
|
||||
* 10b = Received Data Available Interrupt
|
||||
* 11b = Receiver Line Status Interrupt
|
||||
*/
|
||||
uint8_t InterruptStatus : 2;
|
||||
|
||||
/**
|
||||
* 16550 Time-out Interrupt Pending
|
||||
*
|
||||
* @note Reserved on 8250, 16450
|
||||
*/
|
||||
uint8_t TimeOutIP : 1;
|
||||
|
||||
/** Reserved */
|
||||
uint8_t __reserved : 1;
|
||||
|
||||
/** 64 Byte Fifo Enabled (16750 only) */
|
||||
uint8_t FIFO64 : 1;
|
||||
|
||||
/**
|
||||
* Enable FIFO
|
||||
*
|
||||
* 00b = No FIFO
|
||||
* 01b = FIFO Enabled but Unusable
|
||||
* 11b = FIFO Enabled
|
||||
*/
|
||||
uint8_t FIFO : 2;
|
||||
};
|
||||
uint8_t raw;
|
||||
} IIR;
|
||||
|
||||
/** First In / First Out Control Register */
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/** Enable FIFO's */
|
||||
uint8_t FIFO : 1;
|
||||
|
||||
/** Clear Receive FIFO */
|
||||
uint8_t ClearRX : 1;
|
||||
|
||||
/** Clear Transmit FIFO */
|
||||
uint8_t ClearTX : 1;
|
||||
|
||||
/** DMA Mode Select.
|
||||
*
|
||||
* Change status of RXRDY & TXRDY pins from mode 1 to mode 2.
|
||||
*/
|
||||
uint8_t DMAMode : 1;
|
||||
|
||||
/** Reserved */
|
||||
uint8_t __reserved : 1;
|
||||
|
||||
/** Enable 64 Byte FIFO (16750 only) */
|
||||
uint8_t FIFO64 : 1;
|
||||
|
||||
/** Interrupt Trigger Level
|
||||
*
|
||||
* 00b = 1 Byte
|
||||
* 01b = 4 Bytes
|
||||
* 10b = 8 Bytes
|
||||
* 11b = 14 Bytes
|
||||
*/
|
||||
uint8_t TriggerLevel : 2;
|
||||
};
|
||||
uint8_t raw;
|
||||
} FCR;
|
||||
|
||||
/** Line Control Register */
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/** Word Length
|
||||
*
|
||||
* 00b = 5 bits
|
||||
* 01b = 6 bits
|
||||
* 10b = 7 bits
|
||||
* 11b = 8 bits
|
||||
*/
|
||||
uint8_t WordLength : 2;
|
||||
|
||||
/** Length of Stop Bit
|
||||
*
|
||||
* 0b = One Stop Bit
|
||||
* 1b = 2 Stop bits for words of length 6,7 or 8 bits or 1.5 Stop Bits for Word lengths of 5 bits.
|
||||
*/
|
||||
uint8_t StopBit : 1;
|
||||
|
||||
/** Parity Select
|
||||
*
|
||||
* 0b = No Parity
|
||||
* 001b = Odd Parity
|
||||
* 011b = Even Parity
|
||||
* 101b = High Parity (Sticky)
|
||||
* 111b = Low Parity (Sticky)
|
||||
*/
|
||||
uint8_t Parity : 3;
|
||||
|
||||
/** Set Break Enable */
|
||||
uint8_t SetBreak : 1;
|
||||
|
||||
/**
|
||||
* Divisor Latch Access
|
||||
*
|
||||
* 0b = Access to Receiver buffer, Transmitter buffer & Interrupt Enable Register
|
||||
* 1b = Divisor Latch Access Bit
|
||||
*/
|
||||
uint8_t DLAB : 1;
|
||||
};
|
||||
uint8_t raw;
|
||||
} LCR;
|
||||
|
||||
/** Modem Control Register */
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/** Force Data Terminal Ready */
|
||||
uint8_t DataTerminalReady : 1;
|
||||
|
||||
/** Force Request to Send */
|
||||
uint8_t RequestToSend : 1;
|
||||
|
||||
/** Auxiliary Output 1 */
|
||||
uint8_t Out1 : 1;
|
||||
|
||||
/** Auxiliary Output 2 */
|
||||
uint8_t Out2 : 1;
|
||||
|
||||
/** Loopback Mode */
|
||||
uint8_t Loopback : 1;
|
||||
|
||||
/** Autoflow Control Enabled (16750 only) */
|
||||
uint8_t Autoflow : 1;
|
||||
|
||||
/** Reserved */
|
||||
uint8_t __reserved : 2;
|
||||
};
|
||||
uint8_t raw;
|
||||
} MCR;
|
||||
|
||||
/** Line Status Register */
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/** Data Ready */
|
||||
uint8_t DataReady : 1;
|
||||
|
||||
/** Overrun Error */
|
||||
uint8_t OverrunError : 1;
|
||||
|
||||
/** Parity Error */
|
||||
uint8_t ParityError : 1;
|
||||
|
||||
/** Framing Error */
|
||||
uint8_t FramingError : 1;
|
||||
|
||||
/** Break Interrupt */
|
||||
uint8_t BreakInterrupt : 1;
|
||||
|
||||
/** Empty Transmitter Holding Register */
|
||||
uint8_t EmptyTransmitterHolding : 1;
|
||||
|
||||
/** Empty Data Holding Registers */
|
||||
uint8_t EmptyDataHolding : 1;
|
||||
|
||||
/** Error in Received FIFO */
|
||||
uint8_t ErrorReceivedFIFO : 1;
|
||||
};
|
||||
uint8_t raw;
|
||||
} LSR;
|
||||
|
||||
/** Modem Status Register */
|
||||
typedef union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/** Delta Clear to Send */
|
||||
uint8_t DeltaClearToSend : 1;
|
||||
|
||||
/** Delta Data Set Ready */
|
||||
uint8_t DeltaDataSetReady : 1;
|
||||
|
||||
/** Trailing Edge Ring Indicator */
|
||||
uint8_t TrailingEdgeRingIndicator : 1;
|
||||
|
||||
/** Delta Data Carrier Detect */
|
||||
uint8_t DeltaDataCarrierDetect : 1;
|
||||
|
||||
/** Clear To Send */
|
||||
uint8_t ClearToSend : 1;
|
||||
|
||||
/** Data Set Ready */
|
||||
uint8_t DataSetReady : 1;
|
||||
|
||||
/** Ring Indicator */
|
||||
uint8_t RingIndicator : 1;
|
||||
|
||||
/** Carrier Detect */
|
||||
uint8_t CarrierDetect : 1;
|
||||
};
|
||||
uint8_t raw;
|
||||
} MSR;
|
||||
|
||||
union UARTs
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t com1 : 1;
|
||||
uint8_t com2 : 1;
|
||||
uint8_t com3 : 1;
|
||||
uint8_t com4 : 1;
|
||||
uint8_t com5 : 1;
|
||||
uint8_t com6 : 1;
|
||||
uint8_t com7 : 1;
|
||||
uint8_t com8 : 1;
|
||||
|
||||
uint8_t lpt1 : 1;
|
||||
uint8_t lpt2 : 1;
|
||||
uint8_t lpt3 : 1;
|
||||
|
||||
uint8_t __reserved : 5;
|
||||
};
|
||||
uint16_t raw;
|
||||
} uart;
|
||||
|
||||
bool IsDataReady(uint16_t Port)
|
||||
{
|
||||
LSR lsr;
|
||||
lsr.raw = inb(Port + 5);
|
||||
return lsr.DataReady;
|
||||
}
|
||||
|
||||
bool IsTransmitEmpty(uint16_t Port)
|
||||
{
|
||||
LSR lsr;
|
||||
lsr.raw = inb(Port + 5);
|
||||
return lsr.EmptyTransmitterHolding;
|
||||
}
|
||||
|
||||
char ReadSerial(uint16_t Port)
|
||||
{
|
||||
while (!IsDataReady(Port))
|
||||
v0::Yield(DriverID);
|
||||
return inb(Port);
|
||||
}
|
||||
|
||||
void WriteSerial(uint16_t Port, char Character)
|
||||
{
|
||||
while (!IsTransmitEmpty(Port))
|
||||
v0::Yield(DriverID);
|
||||
outb(Port, Character);
|
||||
}
|
||||
|
||||
void ReportSerialReceived(uint8_t Data)
|
||||
{
|
||||
debug("%c", Data);
|
||||
}
|
||||
|
||||
void UartCOM24(CPU::TrapFrame *)
|
||||
{
|
||||
LSR lsr2, lsr4;
|
||||
do
|
||||
{
|
||||
lsr2.raw = inb(COM2 + 5);
|
||||
if (lsr2.DataReady)
|
||||
ReportSerialReceived(inb(COM2));
|
||||
lsr4.raw = inb(COM4 + 5);
|
||||
if (lsr4.DataReady)
|
||||
ReportSerialReceived(inb(COM4));
|
||||
} while (lsr2.DataReady || lsr4.DataReady);
|
||||
}
|
||||
|
||||
void UartCOM13(CPU::TrapFrame *)
|
||||
{
|
||||
LSR lsr1, lsr3;
|
||||
do
|
||||
{
|
||||
lsr1.raw = inb(COM1 + 5);
|
||||
if (lsr1.DataReady)
|
||||
ReportSerialReceived(inb(COM1));
|
||||
lsr3.raw = inb(COM3 + 5);
|
||||
if (lsr3.DataReady)
|
||||
ReportSerialReceived(inb(COM3));
|
||||
} while (lsr1.DataReady || lsr3.DataReady);
|
||||
}
|
||||
|
||||
bool InitializePort(uint16_t Port)
|
||||
{
|
||||
v0::CriticalState cs = v0::EnterCriticalSection(DriverID);
|
||||
LCR lcr = {};
|
||||
IER ier = {};
|
||||
FCR fcr = {};
|
||||
MCR mcr = {};
|
||||
|
||||
outb(Port + 3, lcr.raw);
|
||||
outb(Port + 1, ier.raw);
|
||||
|
||||
lcr.DLAB = 1;
|
||||
outb(Port + 3, lcr.raw);
|
||||
|
||||
outb(Port + 0, RATE_115200_LO);
|
||||
outb(Port + 1, RATE_115200_HI);
|
||||
|
||||
lcr.DLAB = 0;
|
||||
lcr.WordLength = 0b11;
|
||||
outb(Port + 3, lcr.raw);
|
||||
|
||||
fcr.FIFO = 1;
|
||||
fcr.ClearRX = 1;
|
||||
fcr.ClearTX = 1;
|
||||
fcr.TriggerLevel = 0b11;
|
||||
outb(Port + 2, fcr.raw);
|
||||
|
||||
mcr.DataTerminalReady = 1;
|
||||
mcr.RequestToSend = 1;
|
||||
mcr.Out2 = 1;
|
||||
mcr.Loopback = 1;
|
||||
outb(Port + 4, mcr.raw);
|
||||
|
||||
/* Test the serial port */
|
||||
outb(Port + 0, 0x48);
|
||||
uint8_t result = inb(Port + 0);
|
||||
if (result != 0x48)
|
||||
{
|
||||
/* FIXME: DETECT BAUD RATE
|
||||
Do multiple test to check if the output is garbage.
|
||||
If so, reduce the baud rate until it works. */
|
||||
|
||||
v0::LeaveCriticalSection(DriverID, cs);
|
||||
trace("Port %#X test failed!", Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set normal operation mode */
|
||||
mcr.DataTerminalReady = 1;
|
||||
mcr.RequestToSend = 1;
|
||||
mcr.Out1 = 1;
|
||||
mcr.Out2 = 1;
|
||||
mcr.Loopback = 0;
|
||||
outb(Port + 4, mcr.raw);
|
||||
|
||||
/* Enable interrupts on receive */
|
||||
ier.InterruptOnReceive = 1;
|
||||
outb(Port + 1, ier.raw);
|
||||
v0::RegisterInterruptHandler(DriverID, 3, (void *)UartCOM24);
|
||||
v0::RegisterInterruptHandler(DriverID, 4, (void *)UartCOM13);
|
||||
|
||||
v0::LeaveCriticalSection(DriverID, cs);
|
||||
trace("Port %#X initialized", Port);
|
||||
return true;
|
||||
}
|
||||
|
||||
int DetectUART()
|
||||
{
|
||||
uart.com1 = inb(COM1) != 0xFF ? true : false;
|
||||
uart.com2 = inb(COM2) != 0xFF ? true : false;
|
||||
uart.com3 = inb(COM3) != 0xFF ? true : false;
|
||||
uart.com4 = inb(COM4) != 0xFF ? true : false;
|
||||
uart.com5 = inb(COM5) != 0xFF ? true : false;
|
||||
uart.com6 = inb(COM6) != 0xFF ? true : false;
|
||||
uart.com7 = inb(COM7) != 0xFF ? true : false;
|
||||
uart.com8 = inb(COM8) != 0xFF ? true : false;
|
||||
|
||||
uart.lpt1 = inb(LPT1) != 0xFF ? true : false;
|
||||
uart.lpt2 = inb(LPT2) != 0xFF ? true : false;
|
||||
uart.lpt3 = inb(LPT3) != 0xFF ? true : false;
|
||||
|
||||
if (uart.com1 == true)
|
||||
if (InitializePort(COM1) == false)
|
||||
uart.com1 = false;
|
||||
|
||||
if (uart.com2 == true)
|
||||
if (InitializePort(COM2) == false)
|
||||
uart.com1 = false;
|
||||
|
||||
if (uart.com3 == true)
|
||||
if (InitializePort(COM3) == false)
|
||||
uart.com1 = false;
|
||||
|
||||
if (uart.com4 == true)
|
||||
if (InitializePort(COM4) == false)
|
||||
uart.com1 = false;
|
||||
|
||||
if (uart.com5 == true)
|
||||
if (InitializePort(COM5) == false)
|
||||
uart.com1 = false;
|
||||
|
||||
if (uart.com6 == true)
|
||||
if (InitializePort(COM6) == false)
|
||||
uart.com1 = false;
|
||||
|
||||
if (uart.com7 == true)
|
||||
if (InitializePort(COM7) == false)
|
||||
uart.com1 = false;
|
||||
|
||||
if (uart.com8 == true)
|
||||
if (InitializePort(COM8) == false)
|
||||
uart.com1 = false;
|
||||
|
||||
if (uart.lpt1 == true)
|
||||
trace("LPT1 is present");
|
||||
|
||||
if (uart.lpt2 == true)
|
||||
trace("LPT2 is present");
|
||||
|
||||
if (uart.lpt3 == true)
|
||||
trace("LPT3 is present");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// static int once = 0;
|
||||
// static uint8_t com4 = 0xFF;
|
||||
// if (!once++)
|
||||
// com4 = inb(0x2E8);
|
||||
// if (com4 == 0xFF)
|
||||
// CPU::Halt(true);
|
||||
// char UserInputBuffer[256]{'\0'};
|
||||
// int BackSpaceLimit = 0;
|
||||
// while (true)
|
||||
// {
|
||||
// while ((inb(0x2E8 + 5) & 1) == 0)
|
||||
// CPU::Pause();
|
||||
// char key = inb(0x2E8);
|
||||
// // debug("key: %d", key);
|
||||
// if (key == '\x7f') /* Backspace (DEL) */
|
||||
// {
|
||||
// if (BackSpaceLimit <= 0)
|
||||
// continue;
|
||||
// char keyBuf[5] = {'\b', '\x1b', '[', 'K', '\0'};
|
||||
// ExPrint(keyBuf);
|
||||
// backspace(UserInputBuffer);
|
||||
// BackSpaceLimit--;
|
||||
// continue;
|
||||
// }
|
||||
// else if (key == '\x0d') /* Enter (CR) */
|
||||
// {
|
||||
// UserInput(UserInputBuffer);
|
||||
// BackSpaceLimit = 0;
|
||||
// UserInputBuffer[0] = '\0';
|
||||
// continue;
|
||||
// }
|
||||
// else if (key == '\x1b') /* Escape */
|
||||
// {
|
||||
// char tmp[16]{'\0'};
|
||||
// append(tmp, key);
|
||||
// while ((inb(0x2E8 + 5) & 1) == 0)
|
||||
// CPU::Pause();
|
||||
// char key = inb(0x2E8);
|
||||
// append(tmp, key);
|
||||
// if (key == '[')
|
||||
// {
|
||||
// // 27 91
|
||||
// // < 68
|
||||
// // > 67
|
||||
// // down 66
|
||||
// // up 65
|
||||
// while ((inb(0x2E8 + 5) & 1) == 0)
|
||||
// CPU::Pause();
|
||||
// key = inb(0x2E8);
|
||||
// append(tmp, key);
|
||||
// switch (key)
|
||||
// {
|
||||
// case 'A':
|
||||
// key = KEY_D_UP;
|
||||
// break;
|
||||
// case 'B':
|
||||
// key = KEY_D_DOWN;
|
||||
// break;
|
||||
// case 'C':
|
||||
// key = KEY_D_RIGHT;
|
||||
// break;
|
||||
// case 'D':
|
||||
// key = KEY_D_LEFT;
|
||||
// break;
|
||||
// default:
|
||||
// {
|
||||
// for (size_t i = 0; i < strlen(tmp); i++)
|
||||
// {
|
||||
// if ((int)sizeof(UserInputBuffer) <= BackSpaceLimit)
|
||||
// continue;
|
||||
// append(UserInputBuffer, tmp[i]);
|
||||
// BackSpaceLimit++;
|
||||
// char keyBuf[2] = {(char)tmp[i], '\0'};
|
||||
// ExPrint(keyBuf);
|
||||
// }
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// ArrowInput(key);
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
// if ((int)sizeof(UserInputBuffer) <= BackSpaceLimit)
|
||||
// continue;
|
||||
// append(UserInputBuffer, key);
|
||||
// BackSpaceLimit++;
|
||||
// char keyBuf[2] = {(char)key, '\0'};
|
||||
// ExPrint(keyBuf);
|
||||
// }
|
||||
}
|
Reference in New Issue
Block a user