diff options
author | Liav A <liavalb@gmail.com> | 2022-09-03 10:25:33 +0300 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-09-20 18:43:05 +0100 |
commit | c50a81e93e7cf5ea3df8afc6801ccdc4f6ec0c8c (patch) | |
tree | f58651c49b3243b0894d4b26fbabbf391f9fec0e /Kernel/Arch/x86 | |
parent | 948be9674a057658badceb469df14a5e92d818ea (diff) | |
download | serenity-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.cpp | 116 | ||||
-rw-r--r-- | Kernel/Arch/x86/ISABus/HID/PS2KeyboardDevice.h | 49 | ||||
-rw-r--r-- | Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.cpp | 223 | ||||
-rw-r--r-- | Kernel/Arch/x86/ISABus/HID/PS2MouseDevice.h | 63 | ||||
-rw-r--r-- | Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.cpp | 60 | ||||
-rw-r--r-- | Kernel/Arch/x86/ISABus/HID/VMWareMouseDevice.h | 31 | ||||
-rw-r--r-- | Kernel/Arch/x86/ISABus/I8042Controller.cpp | 387 | ||||
-rw-r--r-- | Kernel/Arch/x86/ISABus/I8042Controller.h | 164 |
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; +}; + +} |