mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 20:37:34 +00:00
Kernel: Untie PS2 mouse and keyboard devices from i8042 implementation
To ensure actual PS2 code is not tied to the i8042 code, we make them separated in the following ways: - PS2KeyboardDevice and PS2MouseDevice classes are no longer inheriting from the IRQHandler class. Instead we have specific IRQHandler derived class for the i8042 controller implementation, which is used to ensure that we don't end up mixing PS2 code with low-level interrupt handling functionality. In the future this means that we could add a driver for other PS2 controllers that might have only one interrupt handler but multiple PS2 devices are attached, therefore, making it easier to put the right propagation flow from the controller driver all the way to the HID core code. - A simple abstraction layer is added between the PS2 command set which devices could use and the actual implementation low-level commands. This means that the code in PS2MouseDevice and PS2KeyboardDevice classes is no longer tied to i8042 implementation-specific commands, so now these objects could send PS2 commands to their PS2 controller and get a PS2Response which abstracts the given response too.
This commit is contained in:
parent
d276cac82c
commit
89a8920764
13 changed files with 392 additions and 272 deletions
|
@ -6,17 +6,37 @@
|
|||
|
||||
#include <Kernel/Arch/Delay.h>
|
||||
#include <Kernel/Arch/x86_64/IO.h>
|
||||
#include <Kernel/Arch/x86_64/ISABus/HID/PS2KeyboardDevice.h>
|
||||
#include <Kernel/Arch/x86_64/ISABus/HID/PS2MouseDevice.h>
|
||||
#include <Kernel/Arch/x86_64/ISABus/HID/VMWareMouseDevice.h>
|
||||
#include <Kernel/Arch/x86_64/ISABus/I8042Controller.h>
|
||||
#include <Kernel/Bus/SerialIO/Device.h>
|
||||
#include <Kernel/Devices/HID/KeyboardDevice.h>
|
||||
#include <Kernel/Devices/HID/MouseDevice.h>
|
||||
#include <Kernel/Devices/HID/PS2/KeyboardDevice.h>
|
||||
#include <Kernel/Devices/HID/PS2/MouseDevice.h>
|
||||
#include <Kernel/Devices/HID/ScanCodeEvent.h>
|
||||
#include <Kernel/Sections.h>
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
#define IRQ_FIRST_PORT 1
|
||||
#define IRQ_SECOND_PORT 12
|
||||
|
||||
UNMAP_AFTER_INIT ErrorOr<NonnullOwnPtr<I8042ControllerIRQHandler>> I8042ControllerIRQHandler::try_create(I8042Controller const& controller, u8 irq_number)
|
||||
{
|
||||
return adopt_nonnull_own_or_enomem(new I8042ControllerIRQHandler(controller, irq_number));
|
||||
}
|
||||
|
||||
bool I8042ControllerIRQHandler::handle_irq(RegisterState const&)
|
||||
{
|
||||
return m_controller->handle_irq({}, interrupt_number());
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT I8042ControllerIRQHandler::I8042ControllerIRQHandler(I8042Controller const& controller, u8 irq_number)
|
||||
: IRQHandler(irq_number)
|
||||
, m_controller(controller)
|
||||
{
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT ErrorOr<NonnullRefPtr<I8042Controller>> I8042Controller::create()
|
||||
{
|
||||
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) I8042Controller()));
|
||||
|
@ -26,6 +46,18 @@ UNMAP_AFTER_INIT I8042Controller::I8042Controller()
|
|||
{
|
||||
}
|
||||
|
||||
bool I8042Controller::handle_irq(Badge<I8042ControllerIRQHandler>, u8 irq_number)
|
||||
{
|
||||
// NOTE: The controller will read the data and call handle_byte_read_from_serial_input
|
||||
// for the appropriate device.
|
||||
VERIFY(irq_number == IRQ_FIRST_PORT || irq_number == IRQ_SECOND_PORT);
|
||||
PortIndex port_index = I8042PortIndex::FirstPort;
|
||||
if (irq_number == IRQ_SECOND_PORT) {
|
||||
port_index = I8042PortIndex::SecondPort;
|
||||
}
|
||||
return irq_process_input_buffer(port_index);
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT bool I8042Controller::check_existence_via_probing(Badge<HIDManagement>)
|
||||
{
|
||||
{
|
||||
|
@ -50,7 +82,7 @@ UNMAP_AFTER_INIT bool I8042Controller::check_existence_via_probing(Badge<HIDMana
|
|||
}
|
||||
|
||||
{
|
||||
auto result = do_wait_then_read(I8042Port::Buffer);
|
||||
auto result = do_wait_then_read_any_input(I8042Port::Buffer);
|
||||
if (result.is_error()) {
|
||||
dbgln("I8042: Trying to read configuration failed during the existence test, error {}", result.error());
|
||||
return false;
|
||||
|
@ -89,7 +121,6 @@ UNMAP_AFTER_INIT bool I8042Controller::check_existence_via_probing(Badge<HIDMana
|
|||
|
||||
UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
||||
{
|
||||
|
||||
u8 configuration;
|
||||
{
|
||||
SpinlockLocker lock(m_lock);
|
||||
|
@ -100,7 +131,7 @@ UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
|||
TRY(do_wait_then_write(I8042Port::Command, I8042Command::DisableSecondPS2Port)); // ignored if it doesn't exist
|
||||
|
||||
TRY(do_wait_then_write(I8042Port::Command, I8042Command::ReadConfiguration));
|
||||
configuration = TRY(do_wait_then_read(I8042Port::Buffer));
|
||||
configuration = TRY(do_wait_then_read_any_input(I8042Port::Buffer));
|
||||
configuration &= ~I8042ConfigurationFlag::FirstPS2PortInterrupt;
|
||||
configuration &= ~I8042ConfigurationFlag::SecondPS2PortInterrupt;
|
||||
configuration |= I8042ConfigurationFlag::FirstPS2PortTranslation;
|
||||
|
@ -110,7 +141,7 @@ UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
|||
|
||||
// Perform controller self-test
|
||||
TRY(do_wait_then_write(I8042Port::Command, I8042Command::TestPS2Controller));
|
||||
auto self_test_result = TRY(do_wait_then_read(I8042Port::Buffer));
|
||||
auto self_test_result = TRY(do_wait_then_read_any_input(I8042Port::Buffer));
|
||||
if (self_test_result == I8042Response::ControllerTestPassed) {
|
||||
// Restore configuration in case the controller reset
|
||||
TRY(do_wait_then_write(I8042Port::Command, I8042Command::WriteConfiguration));
|
||||
|
@ -125,7 +156,7 @@ UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
|||
|
||||
// Test ports and enable them if available
|
||||
TRY(do_wait_then_write(I8042Port::Command, I8042Command::TestFirstPS2Port));
|
||||
auto first_port_test_result = TRY(do_wait_then_read(I8042Port::Buffer));
|
||||
auto first_port_test_result = TRY(do_wait_then_read_any_input(I8042Port::Buffer));
|
||||
m_first_port_available = (first_port_test_result == 0);
|
||||
|
||||
if (m_first_port_available) {
|
||||
|
@ -140,7 +171,7 @@ UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
|||
|
||||
if (m_is_dual_channel) {
|
||||
TRY(do_wait_then_write(I8042Port::Command, I8042Command::TestSecondPS2Port));
|
||||
auto test_second_port_result = TRY(do_wait_then_read(I8042Port::Buffer));
|
||||
auto test_second_port_result = TRY(do_wait_then_read_any_input(I8042Port::Buffer));
|
||||
m_second_port_available = (test_second_port_result == 0);
|
||||
if (m_second_port_available) {
|
||||
TRY(do_wait_then_write(I8042Port::Command, I8042Command::EnableSecondPS2Port));
|
||||
|
@ -163,9 +194,9 @@ UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
|||
// Try to detect and initialize the devices
|
||||
if (m_first_port_available) {
|
||||
// FIXME: Actually figure out the connected PS2 device type
|
||||
m_first_ps2_port.device_type = PS2Device::Type::Keyboard;
|
||||
m_first_ps2_port.device_type = PS2DeviceType::StandardKeyboard;
|
||||
auto keyboard_device = TRY(KeyboardDevice::try_to_initialize());
|
||||
auto error_or_device = PS2KeyboardDevice::try_to_initialize(*this, *keyboard_device);
|
||||
auto error_or_device = PS2KeyboardDevice::try_to_initialize(*this, I8042PortIndex::FirstPort, *keyboard_device);
|
||||
if (error_or_device.is_error()) {
|
||||
dbgln("I8042: Keyboard device failed to initialize, disable");
|
||||
m_first_port_available = false;
|
||||
|
@ -175,7 +206,7 @@ UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
|||
m_first_ps2_port.device_type = {};
|
||||
SpinlockLocker lock(m_lock);
|
||||
// NOTE: Before setting the actual scan code set, stop packet streaming entirely.
|
||||
TRY(send_command(PS2Device::Type::Keyboard, I8042Command::DisablePacketStreaming));
|
||||
TRY(do_send_command(I8042PortIndex::FirstPort, I8042Command::DisablePacketStreaming));
|
||||
TRY(do_wait_then_write(I8042Port::Buffer, I8042Command::SetScanCodeSet));
|
||||
TRY(do_wait_then_write(I8042Port::Buffer, 0x2));
|
||||
|
||||
|
@ -187,12 +218,12 @@ UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
|||
}
|
||||
if (m_second_port_available) {
|
||||
// FIXME: Actually figure out the connected PS2 device type
|
||||
m_second_ps2_port.device_type = PS2Device::Type::Mouse;
|
||||
m_second_ps2_port.device_type = PS2DeviceType::StandardMouse;
|
||||
auto mouse_device = TRY(MouseDevice::try_to_initialize());
|
||||
auto vmmouse_device_or_error = VMWareMouseDevice::try_to_initialize(*this, *mouse_device);
|
||||
auto vmmouse_device_or_error = VMWareMouseDevice::try_to_initialize(*this, I8042PortIndex::SecondPort, *mouse_device);
|
||||
if (vmmouse_device_or_error.is_error()) {
|
||||
// FIXME: is there something to do with the VMWare errors?
|
||||
auto mouse_device_or_error = PS2MouseDevice::try_to_initialize(*this, *mouse_device);
|
||||
auto mouse_device_or_error = PS2MouseDevice::try_to_initialize(*this, I8042PortIndex::SecondPort, *mouse_device);
|
||||
if (mouse_device_or_error.is_error()) {
|
||||
dbgln("I8042: Mouse device failed to initialize, disable");
|
||||
m_second_port_available = false;
|
||||
|
@ -210,15 +241,70 @@ UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
|
|||
}
|
||||
}
|
||||
|
||||
m_irq_handlers[0] = TRY(I8042ControllerIRQHandler::try_create(*this, IRQ_FIRST_PORT));
|
||||
m_irq_handlers[1] = TRY(I8042ControllerIRQHandler::try_create(*this, IRQ_SECOND_PORT));
|
||||
|
||||
// Enable IRQs after both are detected and initialized
|
||||
if (m_first_ps2_port.device)
|
||||
m_first_ps2_port.device->enable_interrupts();
|
||||
m_irq_handlers[0]->enable_irq();
|
||||
if (m_second_ps2_port.device)
|
||||
m_second_ps2_port.device->enable_interrupts();
|
||||
m_irq_handlers[1]->enable_irq();
|
||||
return {};
|
||||
}
|
||||
|
||||
bool I8042Controller::irq_process_input_buffer(PS2Device::Type instrument_type)
|
||||
ErrorOr<void> I8042Controller::send_command(PortIndex port_index, DeviceCommand command)
|
||||
{
|
||||
switch (command) {
|
||||
case DeviceCommand::GetDeviceID: {
|
||||
SpinlockLocker lock(m_lock);
|
||||
return do_send_command(port_index, I8042Command::GetDeviceID);
|
||||
}
|
||||
case DeviceCommand::EnablePacketStreaming: {
|
||||
SpinlockLocker lock(m_lock);
|
||||
return do_send_command(port_index, I8042Command::EnablePacketStreaming);
|
||||
}
|
||||
case DeviceCommand::DisablePacketStreaming: {
|
||||
SpinlockLocker lock(m_lock);
|
||||
return do_send_command(port_index, I8042Command::DisablePacketStreaming);
|
||||
}
|
||||
case DeviceCommand::SetDefaults: {
|
||||
SpinlockLocker lock(m_lock);
|
||||
return do_send_command(port_index, I8042Command::SetDefaults);
|
||||
}
|
||||
|
||||
// NOTE: The sample rate command is supported only with sending data byte with it!
|
||||
case DeviceCommand::SetSampleRate:
|
||||
return EOPNOTSUPP;
|
||||
|
||||
default:
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorOr<void> I8042Controller::send_command(PortIndex port_index, DeviceCommand command, u8 data)
|
||||
{
|
||||
switch (command) {
|
||||
// NOTE: Only the sample rate command supports sending data byte with it!
|
||||
case DeviceCommand::SetSampleRate: {
|
||||
SpinlockLocker lock(m_lock);
|
||||
return do_send_command(port_index, I8042Command::SetSampleRate, data);
|
||||
}
|
||||
|
||||
case DeviceCommand::GetDeviceID:
|
||||
return EOPNOTSUPP;
|
||||
case DeviceCommand::EnablePacketStreaming:
|
||||
return EOPNOTSUPP;
|
||||
case DeviceCommand::DisablePacketStreaming:
|
||||
return EOPNOTSUPP;
|
||||
case DeviceCommand::SetDefaults:
|
||||
return EOPNOTSUPP;
|
||||
|
||||
default:
|
||||
return EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
bool I8042Controller::irq_process_input_buffer(PortIndex port_index)
|
||||
{
|
||||
VERIFY(Processor::current_in_irq());
|
||||
|
||||
|
@ -228,17 +314,16 @@ bool I8042Controller::irq_process_input_buffer(PS2Device::Type instrument_type)
|
|||
u8 byte = IO::in8(I8042Port::Buffer);
|
||||
|
||||
PS2Port* selected_port = nullptr;
|
||||
if (m_first_ps2_port.device_type.has_value() && m_first_ps2_port.device_type.value() == instrument_type) {
|
||||
VERIFY(m_first_ps2_port.device);
|
||||
if (port_index == I8042PortIndex::FirstPort)
|
||||
selected_port = &m_first_ps2_port;
|
||||
} else if (m_second_ps2_port.device_type.has_value() && m_second_ps2_port.device_type.value() == instrument_type) {
|
||||
VERIFY(m_second_ps2_port.device);
|
||||
else if (port_index == I8042PortIndex::SecondPort)
|
||||
selected_port = &m_second_ps2_port;
|
||||
} else {
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
selected_port->device->irq_handle_byte_read(byte);
|
||||
if (!selected_port->device)
|
||||
return false;
|
||||
selected_port->device->handle_byte_read_from_serial_input(byte);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -255,18 +340,14 @@ ErrorOr<void> I8042Controller::drain_output_buffer()
|
|||
return Error::from_errno(EBUSY);
|
||||
}
|
||||
|
||||
ErrorOr<void> I8042Controller::do_reset_device(PS2Device::Type device)
|
||||
ErrorOr<void> I8042Controller::do_reset_device(PortIndex port_index)
|
||||
{
|
||||
VERIFY(device != PS2Device::Type::Unknown);
|
||||
VERIFY(m_lock.is_locked());
|
||||
|
||||
VERIFY(!Processor::current_in_irq());
|
||||
auto reset_result = TRY(do_send_command(device, I8042Command::Reset));
|
||||
// FIXME: Is this the correct errno value for this?
|
||||
if (reset_result != I8042Response::Acknowledge)
|
||||
return Error::from_errno(EIO);
|
||||
TRY(do_send_command(port_index, I8042Command::Reset));
|
||||
// Wait until we get the self-test result
|
||||
auto self_test_result = TRY(do_wait_then_read(I8042Port::Buffer));
|
||||
auto self_test_result = TRY(do_wait_then_read_any_input(I8042Port::Buffer));
|
||||
|
||||
// FIXME: Is this the correct errno value for this?
|
||||
if (self_test_result != I8042Response::Success)
|
||||
|
@ -274,32 +355,28 @@ ErrorOr<void> I8042Controller::do_reset_device(PS2Device::Type device)
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<u8> I8042Controller::do_send_command(PS2Device::Type device, u8 command)
|
||||
ErrorOr<void> I8042Controller::do_send_command(PortIndex port_index, u8 command)
|
||||
{
|
||||
VERIFY(m_lock.is_locked());
|
||||
|
||||
VERIFY(!Processor::current_in_irq());
|
||||
TRY(do_write_to_device(port_index, command));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> I8042Controller::do_send_command(PortIndex port_index, u8 command, u8 data)
|
||||
{
|
||||
VERIFY(device != PS2Device::Type::Unknown);
|
||||
VERIFY(m_lock.is_locked());
|
||||
|
||||
VERIFY(!Processor::current_in_irq());
|
||||
|
||||
return do_write_to_device(device, command);
|
||||
TRY(do_write_to_device(port_index, command));
|
||||
TRY(do_write_to_device(port_index, data));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<u8> I8042Controller::do_send_command(PS2Device::Type device, u8 command, u8 data)
|
||||
ErrorOr<void> I8042Controller::do_write_to_device(PortIndex port_index, u8 data)
|
||||
{
|
||||
VERIFY(device != PS2Device::Type::Unknown);
|
||||
VERIFY(m_lock.is_locked());
|
||||
|
||||
VERIFY(!Processor::current_in_irq());
|
||||
|
||||
u8 response = TRY(do_write_to_device(device, command));
|
||||
if (response == I8042Response::Acknowledge)
|
||||
response = TRY(do_write_to_device(device, data));
|
||||
return response;
|
||||
}
|
||||
|
||||
ErrorOr<u8> I8042Controller::do_write_to_device(PS2Device::Type device, u8 data)
|
||||
{
|
||||
VERIFY(device != PS2Device::Type::Unknown);
|
||||
VERIFY(m_lock.is_locked());
|
||||
|
||||
VERIFY(!Processor::current_in_irq());
|
||||
|
@ -307,39 +384,61 @@ ErrorOr<u8> I8042Controller::do_write_to_device(PS2Device::Type device, u8 data)
|
|||
int attempts = 0;
|
||||
u8 response;
|
||||
do {
|
||||
if (device != PS2Device::Type::Keyboard) {
|
||||
TRY(prepare_for_output());
|
||||
if (port_index != I8042PortIndex::FirstPort) {
|
||||
VERIFY(port_index == I8042PortIndex::SecondPort);
|
||||
TRY(prepare_for_any_output());
|
||||
IO::out8(I8042Port::Command, I8042Command::WriteSecondPS2PortInputBuffer);
|
||||
}
|
||||
TRY(prepare_for_output());
|
||||
TRY(prepare_for_any_output());
|
||||
IO::out8(I8042Port::Buffer, data);
|
||||
|
||||
response = TRY(do_wait_then_read(I8042Port::Buffer));
|
||||
response = TRY(do_wait_then_read_any_input(I8042Port::Buffer));
|
||||
} while (response == I8042Response::Resend && ++attempts < 250);
|
||||
if (attempts >= 250)
|
||||
dbgln("Failed to write byte to device, gave up");
|
||||
return response;
|
||||
if (attempts >= 250 || response == I8042Response::Resend) {
|
||||
dbgln("I8042: Failed to write byte to device, gave up");
|
||||
return Error::from_errno(EBUSY);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<u8> I8042Controller::do_read_from_device(PS2Device::Type device)
|
||||
ErrorOr<u8> I8042Controller::do_read_from_device(PortIndex port_index)
|
||||
{
|
||||
VERIFY(device != PS2Device::Type::Unknown);
|
||||
|
||||
TRY(prepare_for_input(device));
|
||||
TRY(prepare_for_input(port_index));
|
||||
return IO::in8(I8042Port::Buffer);
|
||||
}
|
||||
|
||||
ErrorOr<void> I8042Controller::prepare_for_input(PS2Device::Type device)
|
||||
ErrorOr<void> I8042Controller::prepare_for_any_input()
|
||||
{
|
||||
VERIFY(m_lock.is_locked());
|
||||
u8 const second_port_flag = device == PS2Device::Type::Keyboard ? 0 : I8042StatusFlag::SecondPS2PortOutputBuffer;
|
||||
for (int attempt = 0; attempt < 1000; attempt++) {
|
||||
u8 status = IO::in8(I8042Port::Status);
|
||||
if (!(status & I8042StatusFlag::OutputBuffer)) {
|
||||
microseconds_delay(1000);
|
||||
continue;
|
||||
}
|
||||
if (device == PS2Device::Type::Unknown)
|
||||
return {};
|
||||
}
|
||||
return Error::from_errno(EBUSY);
|
||||
}
|
||||
|
||||
ErrorOr<void> I8042Controller::prepare_for_input(PortIndex port_index)
|
||||
{
|
||||
VERIFY(m_lock.is_locked());
|
||||
u8 const second_port_flag = port_index == I8042PortIndex::FirstPort ? 0 : I8042StatusFlag::SecondPS2PortOutputBuffer;
|
||||
PS2Port* port = nullptr;
|
||||
if (port_index == I8042PortIndex::FirstPort)
|
||||
port = &m_first_ps2_port;
|
||||
else if (port_index == I8042PortIndex::SecondPort)
|
||||
port = &m_second_ps2_port;
|
||||
else
|
||||
return Error::from_errno(ENODEV);
|
||||
for (int attempt = 0; attempt < 1000; attempt++) {
|
||||
u8 status = IO::in8(I8042Port::Status);
|
||||
if (!(status & I8042StatusFlag::OutputBuffer)) {
|
||||
microseconds_delay(1000);
|
||||
continue;
|
||||
}
|
||||
if (!port->device_type.has_value() || port->device_type.value() == PS2DeviceType::Unknown)
|
||||
return {};
|
||||
if ((status & I8042StatusFlag::SecondPS2PortOutputBuffer) == second_port_flag)
|
||||
return {};
|
||||
|
@ -348,7 +447,7 @@ ErrorOr<void> I8042Controller::prepare_for_input(PS2Device::Type device)
|
|||
return Error::from_errno(EBUSY);
|
||||
}
|
||||
|
||||
ErrorOr<void> I8042Controller::prepare_for_output()
|
||||
ErrorOr<void> I8042Controller::prepare_for_any_output()
|
||||
{
|
||||
VERIFY(m_lock.is_locked());
|
||||
for (int attempt = 0; attempt < 250; attempt++) {
|
||||
|
@ -375,16 +474,15 @@ UNMAP_AFTER_INIT u8 I8042Controller::do_read(u8 port)
|
|||
ErrorOr<void> I8042Controller::do_wait_then_write(u8 port, u8 data)
|
||||
{
|
||||
VERIFY(m_lock.is_locked());
|
||||
TRY(prepare_for_output());
|
||||
TRY(prepare_for_any_output());
|
||||
IO::out8(port, data);
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<u8> I8042Controller::do_wait_then_read(u8 port)
|
||||
ErrorOr<u8> I8042Controller::do_wait_then_read_any_input(u8 port)
|
||||
{
|
||||
VERIFY(m_lock.is_locked());
|
||||
TRY(prepare_for_input(PS2Device::Type::Unknown));
|
||||
TRY(prepare_for_any_input());
|
||||
return IO::in8(port);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue