summaryrefslogtreecommitdiff
path: root/Kernel/Arch/x86
diff options
context:
space:
mode:
authorLiav A <liavalb@gmail.com>2022-09-03 10:25:33 +0300
committerLinus Groh <mail@linusgroh.de>2022-09-20 18:43:05 +0100
commitc50a81e93e7cf5ea3df8afc6801ccdc4f6ec0c8c (patch)
treef58651c49b3243b0894d4b26fbabbf391f9fec0e /Kernel/Arch/x86
parent948be9674a057658badceb469df14a5e92d818ea (diff)
downloadserenity-c50a81e93e7cf5ea3df8afc6801ccdc4f6ec0c8c.zip
Kernel: Move x86-specific HID code to the Arch/x86 directory
The i8042 controller with its attached devices, the PS2 keyboard and mouse, rely on x86-specific IO instructions to work. Therefore, move them to the Arch/x86 directory to make it easier to omit the handling code of these devices.
Diffstat (limited to 'Kernel/Arch/x86')
-rw-r--r--Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.cpp116
-rw-r--r--Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.h49
-rw-r--r--Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.cpp223
-rw-r--r--Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.h63
-rw-r--r--Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.cpp60
-rw-r--r--Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.h31
-rw-r--r--Kernel/Arch/x86/ISABus/I8042Controller.cpp387
-rw-r--r--Kernel/Arch/x86/ISABus/I8042Controller.h164
8 files changed, 1093 insertions, 0 deletions
diff --git a/Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.cpp b/Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.cpp
new file mode 100644
index 0000000000..b569f16e48
--- /dev/null
+++ b/Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Types.h>
+#include <Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.h>
+#include <Kernel/Debug.h>
+#include <Kernel/Devices/DeviceManagement.h>
+#include <Kernel/Devices/HID/HIDManagement.h>
+#include <Kernel/Scheduler.h>
+#include <Kernel/Sections.h>
+#include <Kernel/TTY/ConsoleManagement.h>
+#include <Kernel/WorkQueue.h>
+
+namespace Kernel {
+
+#define IRQ_KEYBOARD 1
+
+void PS2KeyboardDevice::irq_handle_byte_read(u8 byte)
+{
+ u8 ch = byte & 0x7f;
+ bool pressed = !(byte & 0x80);
+
+ m_entropy_source.add_random_event(byte);
+
+ if (byte == 0xe0) {
+ m_has_e0_prefix = true;
+ return;
+ }
+
+ if ((m_modifiers == (Mod_Alt | Mod_Shift) || m_modifiers == (Mod_Ctrl | Mod_Alt | Mod_Shift)) && byte == 0x58) {
+ // Alt+Shift+F12 pressed, dump some kernel state to the debug console.
+ ConsoleManagement::the().switch_to_debug();
+ Scheduler::dump_scheduler_state(m_modifiers == (Mod_Ctrl | Mod_Alt | Mod_Shift));
+ }
+
+ dbgln_if(KEYBOARD_DEBUG, "Keyboard::irq_handle_byte_read: {:#02x} {}", ch, (pressed ? "down" : "up"));
+ switch (ch) {
+ case 0x38:
+ if (m_has_e0_prefix)
+ update_modifier(Mod_AltGr, pressed);
+ else
+ update_modifier(Mod_Alt, pressed);
+ break;
+ case 0x1d:
+ update_modifier(Mod_Ctrl, pressed);
+ break;
+ case 0x5b:
+ m_left_super_pressed = pressed;
+ update_modifier(Mod_Super, m_left_super_pressed || m_right_super_pressed);
+ break;
+ case 0x5c:
+ m_right_super_pressed = pressed;
+ update_modifier(Mod_Super, m_left_super_pressed || m_right_super_pressed);
+ break;
+ case 0x2a:
+ m_left_shift_pressed = pressed;
+ update_modifier(Mod_Shift, m_left_shift_pressed || m_right_shift_pressed);
+ break;
+ case 0x36:
+ m_right_shift_pressed = pressed;
+ update_modifier(Mod_Shift, m_left_shift_pressed || m_right_shift_pressed);
+ break;
+ }
+ switch (ch) {
+ case I8042Response::Acknowledge:
+ break;
+ default:
+ if ((m_modifiers & Mod_Alt) != 0 && ch >= 2 && ch <= ConsoleManagement::s_max_virtual_consoles + 1) {
+ // FIXME: Do something sanely here if we can't allocate a work queue?
+ MUST(g_io_work->try_queue([ch]() {
+ ConsoleManagement::the().switch_to(ch - 0x02);
+ }));
+ }
+ key_state_changed(ch, pressed);
+ }
+}
+
+bool PS2KeyboardDevice::handle_irq(RegisterState const&)
+{
+ // The controller will read the data and call irq_handle_byte_read
+ // for the appropriate device
+ return m_i8042_controller->irq_process_input_buffer(HIDDevice::Type::Keyboard);
+}
+
+UNMAP_AFTER_INIT ErrorOr<NonnullLockRefPtr<PS2KeyboardDevice>> PS2KeyboardDevice::try_to_initialize(I8042Controller const& ps2_controller)
+{
+ auto keyboard_device = TRY(DeviceManagement::try_create_device<PS2KeyboardDevice>(ps2_controller));
+
+ TRY(keyboard_device->initialize());
+
+ return keyboard_device;
+}
+
+UNMAP_AFTER_INIT ErrorOr<void> PS2KeyboardDevice::initialize()
+{
+ return m_i8042_controller->reset_device(HIDDevice::Type::Keyboard);
+}
+
+// FIXME: UNMAP_AFTER_INIT might not be correct, because in practice PS/2 devices
+// are hot pluggable.
+UNMAP_AFTER_INIT PS2KeyboardDevice::PS2KeyboardDevice(I8042Controller const& ps2_controller)
+ : IRQHandler(IRQ_KEYBOARD)
+ , KeyboardDevice()
+ , I8042Device(ps2_controller)
+{
+}
+
+// FIXME: UNMAP_AFTER_INIT might not be correct, because in practice PS/2 devices
+// are hot pluggable.
+UNMAP_AFTER_INIT PS2KeyboardDevice::~PS2KeyboardDevice() = default;
+
+}
diff --git a/Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.h b/Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.h
new file mode 100644
index 0000000000..d3c63984ea
--- /dev/null
+++ b/Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/CircularQueue.h>
+#include <AK/DoublyLinkedList.h>
+#include <AK/Types.h>
+#include <Kernel/API/KeyCode.h>
+#include <Kernel/Arch/x86/ISABus/I8042Controller.h>
+#include <Kernel/Devices/HID/KeyboardDevice.h>
+#include <Kernel/Interrupts/IRQHandler.h>
+#include <Kernel/Random.h>
+
+namespace Kernel {
+
+class PS2KeyboardDevice final : public IRQHandler
+ , public KeyboardDevice
+ , public I8042Device {
+ friend class DeviceManagement;
+
+public:
+ static ErrorOr<NonnullLockRefPtr<PS2KeyboardDevice>> try_to_initialize(I8042Controller const&);
+ virtual ~PS2KeyboardDevice() override;
+ ErrorOr<void> initialize();
+
+ virtual StringView purpose() const override { return class_name(); }
+
+ // ^I8042Device
+ virtual void irq_handle_byte_read(u8 byte) override;
+ virtual void enable_interrupts() override
+ {
+ enable_irq();
+ }
+
+private:
+ explicit PS2KeyboardDevice(I8042Controller const&);
+
+ // ^IRQHandler
+ virtual bool handle_irq(RegisterState const&) override;
+
+ // ^CharacterDevice
+ virtual StringView class_name() const override { return "KeyboardDevice"sv; }
+};
+
+}
diff --git a/Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.cpp b/Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.cpp
new file mode 100644
index 0000000000..e1bc3370c2
--- /dev/null
+++ b/Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Memory.h>
+#include <Kernel/Arch/x86/Hypervisor/VMWareBackdoor.h>
+#include <Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.h>
+#include <Kernel/Debug.h>
+#include <Kernel/Devices/DeviceManagement.h>
+#include <Kernel/Sections.h>
+
+namespace Kernel {
+
+#define IRQ_MOUSE 12
+
+#define PS2MOUSE_INTELLIMOUSE_ID 0x03
+#define PS2MOUSE_INTELLIMOUSE_EXPLORER_ID 0x04
+
+UNMAP_AFTER_INIT PS2MouseDevice::PS2MouseDevice(I8042Controller const& ps2_controller)
+ : IRQHandler(IRQ_MOUSE)
+ , MouseDevice()
+ , I8042Device(ps2_controller)
+{
+}
+
+UNMAP_AFTER_INIT PS2MouseDevice::~PS2MouseDevice() = default;
+
+bool PS2MouseDevice::handle_irq(RegisterState const&)
+{
+ // The controller will read the data and call irq_handle_byte_read
+ // for the appropriate device
+ return m_i8042_controller->irq_process_input_buffer(instrument_type());
+}
+
+void PS2MouseDevice::irq_handle_byte_read(u8 byte)
+{
+ auto commit_packet = [&] {
+ m_data_state = 0;
+ dbgln_if(PS2MOUSE_DEBUG, "PS2Mouse: {}, {} {} {}",
+ m_data.bytes[1],
+ m_data.bytes[2],
+ (m_data.bytes[0] & 1) ? "Left" : "",
+ (m_data.bytes[0] & 2) ? "Right" : "");
+
+ m_entropy_source.add_random_event(m_data.dword);
+
+ {
+ SpinlockLocker lock(m_queue_lock);
+ m_queue.enqueue(parse_data_packet(m_data));
+ }
+ evaluate_block_conditions();
+ };
+
+ VERIFY(m_data_state < sizeof(m_data.bytes) / sizeof(m_data.bytes[0]));
+ m_data.bytes[m_data_state] = byte;
+
+ switch (m_data_state) {
+ case 0:
+ if (!(byte & 0x08)) {
+ dbgln("PS2Mouse: Stream out of sync.");
+ break;
+ }
+ ++m_data_state;
+ break;
+ case 1:
+ ++m_data_state;
+ break;
+ case 2:
+ if (m_has_wheel) {
+ ++m_data_state;
+ break;
+ }
+ commit_packet();
+ break;
+ case 3:
+ VERIFY(m_has_wheel);
+ commit_packet();
+ break;
+ }
+}
+
+MousePacket PS2MouseDevice::parse_data_packet(RawPacket const& raw_packet)
+{
+ int x = raw_packet.bytes[1];
+ int y = raw_packet.bytes[2];
+ int z = 0;
+ int w = 0;
+ if (m_has_wheel) {
+ // FIXME: For non-Intellimouse, this is a full byte.
+ // However, for now, m_has_wheel is only set for Intellimouse.
+ z = (char)(raw_packet.bytes[3] & 0x0f);
+
+ // -1 in 4 bits
+ if (z == 15)
+ z = -1;
+
+ if ((raw_packet.bytes[3] & 0xc0) == 0x40) {
+ // FIXME: Scroll only functions correctly when the sign is flipped there
+ w = -z;
+ z = 0;
+ } else {
+ w = 0;
+ }
+ }
+ bool x_overflow = raw_packet.bytes[0] & 0x40;
+ bool y_overflow = raw_packet.bytes[0] & 0x80;
+ bool x_sign = raw_packet.bytes[0] & 0x10;
+ bool y_sign = raw_packet.bytes[0] & 0x20;
+ if (x && x_sign)
+ x -= 0x100;
+ if (y && y_sign)
+ y -= 0x100;
+ if (x_overflow || y_overflow) {
+ x = 0;
+ y = 0;
+ }
+ MousePacket packet;
+ packet.x = x;
+ packet.y = y;
+ packet.z = z;
+ packet.w = w;
+ packet.buttons = raw_packet.bytes[0] & 0x07;
+
+ if (m_has_five_buttons) {
+ if (raw_packet.bytes[3] & 0x10)
+ packet.buttons |= MousePacket::BackwardButton;
+ if (raw_packet.bytes[3] & 0x20)
+ packet.buttons |= MousePacket::ForwardButton;
+ }
+
+ packet.is_relative = true;
+ dbgln_if(PS2MOUSE_DEBUG, "PS2 Relative Mouse: Buttons {:x}", packet.buttons);
+ dbgln_if(PS2MOUSE_DEBUG, "Mouse: X {}, Y {}, Z {}, W {}", packet.x, packet.y, packet.z, packet.w);
+ return packet;
+}
+
+ErrorOr<u8> PS2MouseDevice::get_device_id()
+{
+ TRY(send_command(I8042Command::GetDeviceID));
+ return read_from_device();
+}
+
+ErrorOr<u8> PS2MouseDevice::read_from_device()
+{
+ return m_i8042_controller->read_from_device(instrument_type());
+}
+
+ErrorOr<u8> PS2MouseDevice::send_command(u8 command)
+{
+ u8 response = TRY(m_i8042_controller->send_command(instrument_type(), command));
+
+ if (response != I8042Response::Acknowledge) {
+ dbgln("PS2MouseDevice: Command {} got {} but expected ack: {}", command, response, static_cast<u8>(I8042Response::Acknowledge));
+ return Error::from_errno(EIO);
+ }
+ return response;
+}
+
+ErrorOr<u8> PS2MouseDevice::send_command(u8 command, u8 data)
+{
+ u8 response = TRY(m_i8042_controller->send_command(instrument_type(), command, data));
+ if (response != I8042Response::Acknowledge) {
+ dbgln("PS2MouseDevice: Command {} got {} but expected ack: {}", command, response, static_cast<u8>(I8042Response::Acknowledge));
+ return Error::from_errno(EIO);
+ }
+ return response;
+}
+
+ErrorOr<void> PS2MouseDevice::set_sample_rate(u8 rate)
+{
+ TRY(send_command(I8042Command::SetSampleRate, rate));
+ return {};
+}
+
+UNMAP_AFTER_INIT ErrorOr<NonnullLockRefPtr<PS2MouseDevice>> PS2MouseDevice::try_to_initialize(I8042Controller const& ps2_controller)
+{
+ auto mouse_device = TRY(DeviceManagement::try_create_device<PS2MouseDevice>(ps2_controller));
+ TRY(mouse_device->initialize());
+ return mouse_device;
+}
+
+UNMAP_AFTER_INIT ErrorOr<void> PS2MouseDevice::initialize()
+{
+ TRY(m_i8042_controller->reset_device(instrument_type()));
+
+ u8 device_id = TRY(read_from_device());
+
+ TRY(send_command(I8042Command::SetDefaults));
+
+ TRY(send_command(I8042Command::EnablePacketStreaming));
+
+ if (device_id != PS2MOUSE_INTELLIMOUSE_ID) {
+ // Send magical wheel initiation sequence.
+ TRY(set_sample_rate(200));
+ TRY(set_sample_rate(100));
+ TRY(set_sample_rate(80));
+ device_id = TRY(get_device_id());
+ }
+ if (device_id == PS2MOUSE_INTELLIMOUSE_ID) {
+ m_has_wheel = true;
+ dmesgln("PS2MouseDevice: Mouse wheel enabled!");
+ } else {
+ dmesgln("PS2MouseDevice: No mouse wheel detected!");
+ }
+
+ if (device_id == PS2MOUSE_INTELLIMOUSE_ID) {
+ // Try to enable 5 buttons as well!
+ TRY(set_sample_rate(200));
+ TRY(set_sample_rate(200));
+ TRY(set_sample_rate(80));
+ device_id = TRY(get_device_id());
+ }
+
+ if (device_id == PS2MOUSE_INTELLIMOUSE_EXPLORER_ID) {
+ m_has_five_buttons = true;
+ dmesgln("PS2MouseDevice: 5 buttons enabled!");
+ }
+ return {};
+}
+
+}
diff --git a/Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.h b/Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.h
new file mode 100644
index 0000000000..e874ae42f4
--- /dev/null
+++ b/Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/CircularQueue.h>
+#include <Kernel/API/MousePacket.h>
+#include <Kernel/Arch/x86/ISABus/I8042Controller.h>
+#include <Kernel/Devices/HID/MouseDevice.h>
+#include <Kernel/Interrupts/IRQHandler.h>
+#include <Kernel/Random.h>
+
+namespace Kernel {
+class PS2MouseDevice : public IRQHandler
+ , public MouseDevice
+ , public I8042Device {
+ friend class DeviceManagement;
+
+public:
+ static ErrorOr<NonnullLockRefPtr<PS2MouseDevice>> try_to_initialize(I8042Controller const&);
+ ErrorOr<void> initialize();
+
+ virtual ~PS2MouseDevice() override;
+
+ virtual StringView purpose() const override { return class_name(); }
+
+ // ^I8042Device
+ virtual void irq_handle_byte_read(u8 byte) override;
+ virtual void enable_interrupts() override
+ {
+ enable_irq();
+ }
+
+protected:
+ explicit PS2MouseDevice(I8042Controller const&);
+
+ // ^IRQHandler
+ virtual bool handle_irq(RegisterState const&) override;
+
+ struct RawPacket {
+ union {
+ u32 dword;
+ u8 bytes[4];
+ };
+ };
+
+ ErrorOr<u8> read_from_device();
+ ErrorOr<u8> send_command(u8 command);
+ ErrorOr<u8> send_command(u8 command, u8 data);
+ MousePacket parse_data_packet(RawPacket const&);
+ ErrorOr<void> set_sample_rate(u8);
+ ErrorOr<u8> get_device_id();
+
+ u8 m_data_state { 0 };
+ RawPacket m_data;
+ bool m_has_wheel { false };
+ bool m_has_five_buttons { false };
+};
+
+}
diff --git a/Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.cpp b/Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.cpp
new file mode 100644
index 0000000000..b2e9bc046a
--- /dev/null
+++ b/Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/Arch/x86/Hypervisor/VMWareBackdoor.h>
+#include <Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.h>
+#include <Kernel/Devices/DeviceManagement.h>
+#include <Kernel/Sections.h>
+
+namespace Kernel {
+
+UNMAP_AFTER_INIT ErrorOr<NonnullLockRefPtr<VMWareMouseDevice>> VMWareMouseDevice::try_to_initialize(I8042Controller const& ps2_controller)
+{
+ // FIXME: return the correct error
+ if (!VMWareBackdoor::the())
+ return Error::from_errno(EIO);
+ if (!VMWareBackdoor::the()->vmmouse_is_absolute())
+ return Error::from_errno(EIO);
+ auto mouse_device = TRY(DeviceManagement::try_create_device<VMWareMouseDevice>(ps2_controller));
+ TRY(mouse_device->initialize());
+ return mouse_device;
+}
+
+void VMWareMouseDevice::irq_handle_byte_read(u8)
+{
+ auto backdoor = VMWareBackdoor::the();
+ VERIFY(backdoor);
+ VERIFY(backdoor->vmmouse_is_absolute());
+
+ // We will receive 4 bytes from the I8042 controller that we are going to
+ // ignore. Instead, we will check with VMWareBackdoor to see how many bytes
+ // of mouse event data are waiting for us. For each multiple of 4, we
+ // produce a mouse packet.
+ constexpr u8 max_iterations = 128;
+ u8 current_iteration = 0;
+ while (++current_iteration < max_iterations) {
+ auto number_of_mouse_event_bytes = backdoor->read_mouse_status_queue_size();
+ if (number_of_mouse_event_bytes == 0)
+ break;
+ VERIFY(number_of_mouse_event_bytes % 4 == 0);
+
+ auto mouse_packet = backdoor->receive_mouse_packet();
+ m_entropy_source.add_random_event(mouse_packet);
+ {
+ SpinlockLocker lock(m_queue_lock);
+ m_queue.enqueue(mouse_packet);
+ }
+ }
+ evaluate_block_conditions();
+}
+
+VMWareMouseDevice::VMWareMouseDevice(I8042Controller const& ps2_controller)
+ : PS2MouseDevice(ps2_controller)
+{
+}
+VMWareMouseDevice::~VMWareMouseDevice() = default;
+
+}
diff --git a/Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.h b/Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.h
new file mode 100644
index 0000000000..63711ab1e7
--- /dev/null
+++ b/Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/CircularQueue.h>
+#include <Kernel/API/MousePacket.h>
+#include <Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.h>
+#include <Kernel/Arch/x86/ISABus/I8042Controller.h>
+#include <Kernel/Interrupts/IRQHandler.h>
+#include <Kernel/Random.h>
+
+namespace Kernel {
+
+class VMWareMouseDevice final : public PS2MouseDevice {
+public:
+ friend class DeviceManagement;
+ static ErrorOr<NonnullLockRefPtr<VMWareMouseDevice>> try_to_initialize(I8042Controller const&);
+ virtual ~VMWareMouseDevice() override;
+
+ // ^I8042Device
+ virtual void irq_handle_byte_read(u8 byte) override;
+
+private:
+ explicit VMWareMouseDevice(I8042Controller const&);
+};
+
+}
diff --git a/Kernel/Arch/x86/ISABus/I8042Controller.cpp b/Kernel/Arch/x86/ISABus/I8042Controller.cpp
new file mode 100644
index 0000000000..c4ce6b62f1
--- /dev/null
+++ b/Kernel/Arch/x86/ISABus/I8042Controller.cpp
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/Arch/Delay.h>
+#include <Kernel/Arch/x86/IO.h>
+#include <Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.h>
+#include <Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.h>
+#include <Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.h>
+#include <Kernel/Arch/x86/ISABus/I8042Controller.h>
+#include <Kernel/Sections.h>
+
+namespace Kernel {
+
+UNMAP_AFTER_INIT NonnullLockRefPtr<I8042Controller> I8042Controller::initialize()
+{
+ return adopt_lock_ref(*new I8042Controller());
+}
+
+LockRefPtr<MouseDevice> I8042Controller::mouse() const
+{
+ return m_mouse_device;
+}
+LockRefPtr<KeyboardDevice> I8042Controller::keyboard() const
+{
+ return m_keyboard_device;
+}
+
+UNMAP_AFTER_INIT I8042Controller::I8042Controller()
+{
+}
+
+UNMAP_AFTER_INIT bool I8042Controller::check_existence_via_probing(Badge<HIDManagement>)
+{
+ {
+ u8 configuration = 0;
+ SpinlockLocker lock(m_lock);
+
+ // This drains the output buffer and serves as an existence test.
+ if (auto result = drain_output_buffer(); result.is_error()) {
+ dbgln("I8042: Trying to flush output buffer as an existence test failed, error {}", result.error());
+ return false;
+ }
+
+ // Note: Perform controller self-test before touching the controller
+ // Try to probe the controller for 10 times and give up if nothing
+ // responded.
+ // Some controllers will reset and behave abnormally on this, so let's ensure
+ // we keep the configuration before initiating this command.
+
+ if (auto result = do_wait_then_write(I8042Port::Command, I8042Command::ReadConfiguration); result.is_error()) {
+ dbgln("I8042: Trying to read configuration failed during the existence test, error {}", result.error());
+ return false;
+ }
+
+ {
+ auto result = do_wait_then_read(I8042Port::Buffer);
+ if (result.is_error()) {
+ dbgln("I8042: Trying to read configuration failed during the existence test, error {}", result.error());
+ return false;
+ }
+ configuration = result.release_value();
+ }
+
+ bool successful_self_test = false;
+ for (int attempt = 0; attempt < 20; attempt++) {
+ do_write(I8042Port::Command, I8042Command::TestPS2Controller);
+ if (do_read(I8042Port::Buffer) == I8042Response::ControllerTestPassed) {
+ successful_self_test = true;
+ break;
+ }
+ // Note: Wait 500 microseconds in case the controller couldn't respond
+ microseconds_delay(500);
+ }
+ if (!successful_self_test) {
+ dbgln("I8042: Trying to probe for existence of controller failed");
+ return false;
+ }
+
+ if (auto result = do_wait_then_write(I8042Port::Command, I8042Command::WriteConfiguration); result.is_error()) {
+ dbgln("I8042: Trying to restore configuration after self-test failed with error {}", result.error());
+ return false;
+ }
+
+ if (auto result = do_wait_then_write(I8042Port::Buffer, configuration); result.is_error()) {
+ dbgln("I8042: Trying to write restored configuration after self-test failed with error {}", result.error());
+ return false;
+ }
+
+ return true;
+ }
+}
+
+UNMAP_AFTER_INIT ErrorOr<void> I8042Controller::detect_devices()
+{
+
+ u8 configuration;
+ {
+ SpinlockLocker lock(m_lock);
+ // Note: This flushes all the garbage left in the controller registers
+ TRY(drain_output_buffer());
+
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::DisableFirstPS2Port));
+ 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));
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::WriteConfiguration));
+ configuration &= ~I8042ConfigurationFlag::FirstPS2PortInterrupt;
+ configuration &= ~I8042ConfigurationFlag::SecondPS2PortInterrupt;
+
+ // Note: The default BIOS on the QEMU microvm machine type (qboot) doesn't
+ // behave like SeaBIOS, which means it doesn't set first port scan code translation.
+ // However we rely on compatibility feature of the i8042 to send scan codes of set 1.
+ // To ensure that the controller is always outputting correct scan codes, set it
+ // to scan code 2 (because SeaBIOS on regular QEMU machine does this for us) and enable
+ // first port translation to ensure all scan codes are translated to scan code set 1.
+ configuration |= I8042ConfigurationFlag::FirstPS2PortTranslation;
+ TRY(do_wait_then_write(I8042Port::Buffer, configuration));
+ TRY(do_wait_then_write(I8042Port::Buffer, I8042Command::SetScanCodeSet));
+ TRY(do_wait_then_write(I8042Port::Buffer, 0x2));
+
+ m_is_dual_channel = (configuration & I8042ConfigurationFlag::SecondPS2PortClock) != 0;
+ dbgln("I8042: {} channel controller", m_is_dual_channel ? "Dual" : "Single");
+
+ // Perform controller self-test
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::TestPS2Controller));
+ auto self_test_result = TRY(do_wait_then_read(I8042Port::Buffer));
+ if (self_test_result == I8042Response::ControllerTestPassed) {
+ // Restore configuration in case the controller reset
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::WriteConfiguration));
+ TRY(do_wait_then_write(I8042Port::Buffer, configuration));
+ } else {
+ dbgln("I8042: Controller self test failed");
+ }
+
+ // 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));
+ m_first_port_available = (first_port_test_result == 0);
+
+ if (m_first_port_available) {
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::EnableFirstPS2Port));
+ configuration |= I8042ConfigurationFlag::FirstPS2PortInterrupt;
+ configuration &= ~I8042ConfigurationFlag::FirstPS2PortClock;
+ } else {
+ dbgln("I8042: Keyboard port not available");
+ }
+
+ TRY(drain_output_buffer());
+
+ 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));
+ m_second_port_available = (test_second_port_result == 0);
+ if (m_second_port_available) {
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::EnableSecondPS2Port));
+ configuration |= I8042ConfigurationFlag::SecondPS2PortInterrupt;
+ configuration &= ~I8042ConfigurationFlag::SecondPS2PortClock;
+ } else {
+ dbgln("I8042: Mouse port not available");
+ }
+ }
+
+ // Enable IRQs for the ports that are usable
+ if (m_first_port_available || m_second_port_available) {
+ configuration &= ~I8042ConfigurationFlag::FirstPS2PortClock;
+ configuration &= ~I8042ConfigurationFlag::SecondPS2PortClock;
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::WriteConfiguration));
+ TRY(do_wait_then_write(I8042Port::Buffer, configuration));
+ }
+ }
+
+ // Try to detect and initialize the devices
+ if (m_first_port_available) {
+ auto error_or_device = PS2KeyboardDevice::try_to_initialize(*this);
+ if (error_or_device.is_error()) {
+ dbgln("I8042: Keyboard device failed to initialize, disable");
+ m_first_port_available = false;
+ configuration &= ~I8042ConfigurationFlag::FirstPS2PortInterrupt;
+ configuration |= I8042ConfigurationFlag::FirstPS2PortClock;
+ m_keyboard_device = nullptr;
+ SpinlockLocker lock(m_lock);
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::WriteConfiguration));
+ TRY(do_wait_then_write(I8042Port::Buffer, configuration));
+ } else {
+ m_keyboard_device = error_or_device.release_value();
+ }
+ }
+ if (m_second_port_available) {
+ auto vmmouse_device_or_error = VMWareMouseDevice::try_to_initialize(*this);
+ 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);
+ if (mouse_device_or_error.is_error()) {
+ dbgln("I8042: Mouse device failed to initialize, disable");
+ m_second_port_available = false;
+ configuration |= I8042ConfigurationFlag::SecondPS2PortClock;
+ m_mouse_device = nullptr;
+ SpinlockLocker lock(m_lock);
+ TRY(do_wait_then_write(I8042Port::Command, I8042Command::WriteConfiguration));
+ TRY(do_wait_then_write(I8042Port::Buffer, configuration));
+ } else {
+ m_mouse_device = mouse_device_or_error.release_value();
+ }
+ } else {
+ m_mouse_device = vmmouse_device_or_error.release_value();
+ }
+ }
+
+ // Enable IRQs after both are detected and initialized
+ if (m_keyboard_device)
+ m_keyboard_device->enable_interrupts();
+ if (m_mouse_device)
+ m_mouse_device->enable_interrupts();
+ return {};
+}
+
+bool I8042Controller::irq_process_input_buffer(HIDDevice::Type instrument_type)
+{
+ VERIFY(Processor::current_in_irq());
+
+ u8 status = IO::in8(I8042Port::Status);
+ if (!(status & I8042StatusFlag::OutputBuffer))
+ return false;
+ u8 byte = IO::in8(I8042Port::Buffer);
+ if (instrument_type == HIDDevice::Type::Mouse) {
+ VERIFY(m_mouse_device);
+ static_cast<PS2MouseDevice&>(*m_mouse_device).irq_handle_byte_read(byte);
+ return true;
+ }
+ if (instrument_type == HIDDevice::Type::Keyboard) {
+ VERIFY(m_keyboard_device);
+ static_cast<PS2KeyboardDevice&>(*m_keyboard_device).irq_handle_byte_read(byte);
+ return true;
+ }
+ return false;
+}
+
+ErrorOr<void> I8042Controller::drain_output_buffer()
+{
+ for (int attempt = 0; attempt < 50; attempt++) {
+ u8 status = IO::in8(I8042Port::Status);
+ if (!(status & I8042StatusFlag::OutputBuffer))
+ return {};
+ IO::in8(I8042Port::Buffer);
+
+ microseconds_delay(100);
+ }
+ return Error::from_errno(EBUSY);
+}
+
+ErrorOr<void> I8042Controller::do_reset_device(HIDDevice::Type device)
+{
+ VERIFY(device != HIDDevice::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);
+ // Wait until we get the self-test result
+ auto self_test_result = TRY(do_wait_then_read(I8042Port::Buffer));
+
+ // FIXME: Is this the correct errno value for this?
+ if (self_test_result != I8042Response::Success)
+ return Error::from_errno(EIO);
+ return {};
+}
+
+ErrorOr<u8> I8042Controller::do_send_command(HIDDevice::Type device, u8 command)
+{
+ VERIFY(device != HIDDevice::Type::Unknown);
+ VERIFY(m_lock.is_locked());
+
+ VERIFY(!Processor::current_in_irq());
+
+ return do_write_to_device(device, command);
+}
+
+ErrorOr<u8> I8042Controller::do_send_command(HIDDevice::Type device, u8 command, u8 data)
+{
+ VERIFY(device != HIDDevice::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(HIDDevice::Type device, u8 data)
+{
+ VERIFY(device != HIDDevice::Type::Unknown);
+ VERIFY(m_lock.is_locked());
+
+ VERIFY(!Processor::current_in_irq());
+
+ int attempts = 0;
+ u8 response;
+ do {
+ if (device != HIDDevice::Type::Keyboard) {
+ TRY(prepare_for_output());
+ IO::out8(I8042Port::Command, I8042Command::WriteSecondPS2PortInputBuffer);
+ }
+ TRY(prepare_for_output());
+ IO::out8(I8042Port::Buffer, data);
+
+ response = TRY(do_wait_then_read(I8042Port::Buffer));
+ } while (response == I8042Response::Resend && ++attempts < 250);
+ if (attempts >= 250)
+ dbgln("Failed to write byte to device, gave up");
+ return response;
+}
+
+ErrorOr<u8> I8042Controller::do_read_from_device(HIDDevice::Type device)
+{
+ VERIFY(device != HIDDevice::Type::Unknown);
+
+ TRY(prepare_for_input(device));
+ return IO::in8(I8042Port::Buffer);
+}
+
+ErrorOr<void> I8042Controller::prepare_for_input(HIDDevice::Type device)
+{
+ VERIFY(m_lock.is_locked());
+ u8 const second_port_flag = device == HIDDevice::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 == HIDDevice::Type::Unknown)
+ return {};
+ if ((status & I8042StatusFlag::SecondPS2PortOutputBuffer) == second_port_flag)
+ return {};
+ microseconds_delay(1000);
+ }
+ return Error::from_errno(EBUSY);
+}
+
+ErrorOr<void> I8042Controller::prepare_for_output()
+{
+ VERIFY(m_lock.is_locked());
+ for (int attempt = 0; attempt < 250; attempt++) {
+ u8 status = IO::in8(I8042Port::Status);
+ if (!(status & I8042StatusFlag::InputBuffer))
+ return {};
+ microseconds_delay(1000);
+ }
+ return Error::from_errno(EBUSY);
+}
+
+UNMAP_AFTER_INIT void I8042Controller::do_write(u8 port, u8 data)
+{
+ VERIFY(m_lock.is_locked());
+ IO::out8(port, data);
+}
+
+UNMAP_AFTER_INIT u8 I8042Controller::do_read(u8 port)
+{
+ VERIFY(m_lock.is_locked());
+ return IO::in8(port);
+}
+
+ErrorOr<void> I8042Controller::do_wait_then_write(u8 port, u8 data)
+{
+ VERIFY(m_lock.is_locked());
+ TRY(prepare_for_output());
+ IO::out8(port, data);
+ return {};
+}
+
+ErrorOr<u8> I8042Controller::do_wait_then_read(u8 port)
+{
+ VERIFY(m_lock.is_locked());
+ TRY(prepare_for_input(HIDDevice::Type::Unknown));
+ return IO::in8(port);
+}
+
+}
diff --git a/Kernel/Arch/x86/ISABus/I8042Controller.h b/Kernel/Arch/x86/ISABus/I8042Controller.h
new file mode 100644
index 0000000000..997cf4517d
--- /dev/null
+++ b/Kernel/Arch/x86/ISABus/I8042Controller.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2020, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/AtomicRefCounted.h>
+#include <Kernel/Devices/HID/KeyboardDevice.h>
+#include <Kernel/Devices/HID/MouseDevice.h>
+#include <Kernel/Locking/Spinlock.h>
+
+namespace Kernel {
+
+enum I8042Port : u8 {
+ Buffer = 0x60,
+ Command = 0x64,
+ Status = 0x64,
+};
+
+enum I8042Command : u8 {
+ ReadConfiguration = 0x20,
+ WriteConfiguration = 0x60,
+ DisableSecondPS2Port = 0xA7,
+ EnableSecondPS2Port = 0xA8,
+ TestSecondPS2Port = 0xA9,
+ TestPS2Controller = 0xAA,
+ TestFirstPS2Port = 0xAB,
+ DisableFirstPS2Port = 0xAD,
+ EnableFirstPS2Port = 0xAE,
+ WriteSecondPS2PortInputBuffer = 0xD4,
+ SetScanCodeSet = 0xF0,
+ GetDeviceID = 0xF2,
+ SetSampleRate = 0xF3,
+ EnablePacketStreaming = 0xF4,
+ SetDefaults = 0xF6,
+ Reset = 0xFF,
+};
+
+enum I8042ConfigurationFlag : u8 {
+ FirstPS2PortInterrupt = 1 << 0,
+ SecondPS2PortInterrupt = 1 << 1,
+ SystemFlag = 1 << 2,
+ FirstPS2PortClock = 1 << 4,
+ SecondPS2PortClock = 1 << 5,
+ FirstPS2PortTranslation = 1 << 6,
+};
+
+enum I8042StatusFlag : u8 {
+ OutputBuffer = 1 << 0,
+ InputBuffer = 1 << 1,
+ System = 1 << 2,
+ InputType = 1 << 3,
+ SecondPS2PortOutputBuffer = 1 << 5,
+ TimeoutError = 1 << 6,
+ ParityError = 1 << 7,
+};
+
+enum I8042Response : u8 {
+ ControllerTestPassed = 0x55,
+ Success = 0xAA,
+ Acknowledge = 0xFA,
+ Resend = 0xFE,
+};
+
+class I8042Controller;
+class I8042Device {
+public:
+ virtual ~I8042Device() = default;
+
+ virtual void irq_handle_byte_read(u8 byte) = 0;
+
+protected:
+ explicit I8042Device(I8042Controller const& ps2_controller)
+ : m_i8042_controller(ps2_controller)
+ {
+ }
+
+ NonnullLockRefPtr<I8042Controller> m_i8042_controller;
+};
+
+class PS2KeyboardDevice;
+class PS2MouseDevice;
+class HIDManagement;
+class I8042Controller final : public AtomicRefCounted<I8042Controller> {
+ friend class PS2KeyboardDevice;
+ friend class PS2MouseDevice;
+
+public:
+ static NonnullLockRefPtr<I8042Controller> initialize();
+
+ ErrorOr<void> detect_devices();
+
+ ErrorOr<void> reset_device(HIDDevice::Type device)
+ {
+ SpinlockLocker lock(m_lock);
+ return do_reset_device(device);
+ }
+
+ ErrorOr<u8> send_command(HIDDevice::Type device, u8 command)
+ {
+ SpinlockLocker lock(m_lock);
+ return do_send_command(device, command);
+ }
+ ErrorOr<u8> send_command(HIDDevice::Type device, u8 command, u8 data)
+ {
+ SpinlockLocker lock(m_lock);
+ return do_send_command(device, command, data);
+ }
+
+ ErrorOr<u8> read_from_device(HIDDevice::Type device)
+ {
+ SpinlockLocker lock(m_lock);
+ return do_read_from_device(device);
+ }
+
+ ErrorOr<void> wait_then_write(u8 port, u8 data)
+ {
+ SpinlockLocker lock(m_lock);
+ return do_wait_then_write(port, data);
+ }
+
+ ErrorOr<u8> wait_then_read(u8 port)
+ {
+ SpinlockLocker lock(m_lock);
+ return do_wait_then_read(port);
+ }
+
+ ErrorOr<void> prepare_for_output();
+ ErrorOr<void> prepare_for_input(HIDDevice::Type);
+
+ bool irq_process_input_buffer(HIDDevice::Type);
+
+ LockRefPtr<MouseDevice> mouse() const;
+ LockRefPtr<KeyboardDevice> keyboard() const;
+
+ // Note: This function exists only for the initialization process of the controller
+ bool check_existence_via_probing(Badge<HIDManagement>);
+
+private:
+ I8042Controller();
+ ErrorOr<void> do_reset_device(HIDDevice::Type);
+ ErrorOr<u8> do_send_command(HIDDevice::Type type, u8 data);
+ ErrorOr<u8> do_send_command(HIDDevice::Type device, u8 command, u8 data);
+ ErrorOr<u8> do_write_to_device(HIDDevice::Type device, u8 data);
+ ErrorOr<u8> do_read_from_device(HIDDevice::Type device);
+ ErrorOr<void> do_wait_then_write(u8 port, u8 data);
+ ErrorOr<u8> do_wait_then_read(u8 port);
+ ErrorOr<void> drain_output_buffer();
+
+ // Note: These functions exist only for the initialization process of the controller
+ void do_write(u8 port, u8 data);
+ u8 do_read(u8 port);
+
+ Spinlock m_lock { LockRank::None };
+ bool m_first_port_available { false };
+ bool m_second_port_available { false };
+ bool m_is_dual_channel { false };
+ LockRefPtr<MouseDevice> m_mouse_device;
+ LockRefPtr<KeyboardDevice> m_keyboard_device;
+};
+
+}