Kernel: Add support for DualShock 3 controllers

This driver accepts any HID joystick devices but button and axis
mappings will only work on a PS3 controller
This commit is contained in:
Bananymous 2026-01-03 19:48:28 +02:00
parent 08bfa0971e
commit 65664b0d65
9 changed files with 240 additions and 20 deletions

View File

@ -108,6 +108,7 @@ set(KERNEL_SOURCES
kernel/USB/Controller.cpp kernel/USB/Controller.cpp
kernel/USB/Device.cpp kernel/USB/Device.cpp
kernel/USB/HID/HIDDriver.cpp kernel/USB/HID/HIDDriver.cpp
kernel/USB/HID/Joystick.cpp
kernel/USB/HID/Keyboard.cpp kernel/USB/HID/Keyboard.cpp
kernel/USB/HID/Mouse.cpp kernel/USB/HID/Mouse.cpp
kernel/USB/Hub/HubDriver.cpp kernel/USB/Hub/HubDriver.cpp

View File

@ -16,6 +16,7 @@ namespace Kernel
Debug, Debug,
Keyboard, Keyboard,
Mouse, Mouse,
Joystick,
SCSI, SCSI,
NVMeController, NVMeController,
NVMeNamespace, NVMeNamespace,

View File

@ -15,6 +15,7 @@ namespace Kernel
{ {
Mouse, Mouse,
Keyboard, Keyboard,
Joystick,
}; };
public: public:

View File

@ -55,6 +55,8 @@ namespace Kernel
{} {}
virtual ~USBHIDDevice() = default; virtual ~USBHIDDevice() = default;
virtual BAN::ErrorOr<void> initialize() { return {}; }
virtual void start_report() = 0; virtual void start_report() = 0;
virtual void stop_report() = 0; virtual void stop_report() = 0;

View File

@ -0,0 +1,43 @@
#pragma once
#include <kernel/USB/HID/HIDDriver.h>
#include <LibInput/Joystick.h>
namespace Kernel
{
class USBJoystick final : public USBHIDDevice
{
BAN_NON_COPYABLE(USBJoystick);
BAN_NON_MOVABLE(USBJoystick);
public:
BAN::ErrorOr<void> initialize() override;
void start_report() override;
void stop_report() override;
void handle_array(uint16_t usage_page, uint16_t usage) override;
void handle_variable(uint16_t usage_page, uint16_t usage, int64_t state) override;
void handle_variable_absolute(uint16_t usage_page, uint16_t usage, int64_t state, int64_t min, int64_t max) override;
protected:
BAN::ErrorOr<size_t> read_impl(off_t, BAN::ByteSpan) override;
bool can_read_impl() const override { return true; }
private:
USBJoystick(USBHIDDriver&);
~USBJoystick() = default;
private:
USBHIDDriver& m_driver;
SpinLock m_state_lock;
InterruptState m_interrupt_state;
LibInput::JoystickState m_state {};
friend class BAN::RefPtr<USBJoystick>;
};
}

View File

@ -3,6 +3,7 @@
#include <kernel/Input/InputDevice.h> #include <kernel/Input/InputDevice.h>
#include <kernel/Lock/SpinLockAsMutex.h> #include <kernel/Lock/SpinLockAsMutex.h>
#include <LibInput/Joystick.h>
#include <LibInput/KeyEvent.h> #include <LibInput/KeyEvent.h>
#include <LibInput/MouseEvent.h> #include <LibInput/MouseEvent.h>
@ -18,6 +19,8 @@ namespace Kernel
static BAN::Vector<BAN::WeakPtr<InputDevice>> s_mice; static BAN::Vector<BAN::WeakPtr<InputDevice>> s_mice;
static BAN::RefPtr<MouseDevice> s_mouse_device; static BAN::RefPtr<MouseDevice> s_mouse_device;
static BAN::Vector<BAN::WeakPtr<InputDevice>> s_joysticks;
static const char* get_name_format(InputDevice::Type type) static const char* get_name_format(InputDevice::Type type)
{ {
switch (type) switch (type)
@ -26,6 +29,8 @@ namespace Kernel
return "keyboard{}"; return "keyboard{}";
case InputDevice::Type::Mouse: case InputDevice::Type::Mouse:
return "mouse{}"; return "mouse{}";
case InputDevice::Type::Joystick:
return "joystick{}";
} }
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
@ -44,6 +49,11 @@ namespace Kernel
if (!s_mice[i].valid()) if (!s_mice[i].valid())
return makedev(DeviceNumber::Mouse, i + 1); return makedev(DeviceNumber::Mouse, i + 1);
return makedev(DeviceNumber::Mouse, s_mice.size() + 1); return makedev(DeviceNumber::Mouse, s_mice.size() + 1);
case InputDevice::Type::Joystick:
for (size_t i = 0; i < s_joysticks.size(); i++)
if (!s_joysticks[i].valid())
return makedev(DeviceNumber::Joystick, i + 1);
return makedev(DeviceNumber::Joystick, s_joysticks.size() + 1);
} }
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
@ -56,6 +66,8 @@ namespace Kernel
return sizeof(LibInput::RawKeyEvent); return sizeof(LibInput::RawKeyEvent);
case InputDevice::Type::Mouse: case InputDevice::Type::Mouse:
return sizeof(LibInput::MouseEvent); return sizeof(LibInput::MouseEvent);
case InputDevice::Type::Joystick:
return sizeof(LibInput::JoystickEvent);
} }
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
@ -69,18 +81,23 @@ namespace Kernel
{ {
MUST(m_event_buffer.resize(m_event_size * m_max_event_count, 0)); MUST(m_event_buffer.resize(m_event_size * m_max_event_count, 0));
if (m_type == Type::Keyboard) switch (m_type)
{ {
if (s_keyboards.size() < minor(m_rdev)) case Type::Keyboard:
MUST(s_keyboards.resize(minor(m_rdev))); if (s_keyboards.size() < minor(m_rdev))
s_keyboards[minor(m_rdev) - 1] = MUST(get_weak_ptr()); MUST(s_keyboards.resize(minor(m_rdev)));
} s_keyboards[minor(m_rdev) - 1] = MUST(get_weak_ptr());
break;
if (m_type == Type::Mouse) case Type::Mouse:
{ if (s_mice.size() < minor(m_rdev))
if (s_mice.size() < minor(m_rdev)) MUST(s_mice.resize(minor(m_rdev)));
MUST(s_mice.resize(minor(m_rdev))); s_mice[minor(m_rdev) - 1] = MUST(get_weak_ptr());
s_mice[minor(m_rdev) - 1] = MUST(get_weak_ptr()); break;
case Type::Joystick:
if (s_joysticks.size() < minor(m_rdev))
MUST(s_joysticks.resize(minor(m_rdev)));
s_joysticks[minor(m_rdev) - 1] = MUST(get_weak_ptr());
break;
} }
} }

View File

@ -3,6 +3,7 @@
#include <kernel/FS/DevFS/FileSystem.h> #include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/USB/HID/HIDDriver.h> #include <kernel/USB/HID/HIDDriver.h>
#include <kernel/USB/HID/Joystick.h>
#include <kernel/USB/HID/Keyboard.h> #include <kernel/USB/HID/Keyboard.h>
#include <kernel/USB/HID/Mouse.h> #include <kernel/USB/HID/Mouse.h>
@ -208,6 +209,10 @@ namespace Kernel
return BAN::Error::from_errno(EINVAL); return BAN::Error::from_errno(EINVAL);
} }
for (auto& report : m_device_inputs)
if (report.device && report.device->initialize().is_error())
report.device.clear();
m_device.send_data_buffer(m_data_endpoint_id, m_data_buffer->paddr(), m_data_buffer->size()); m_device.send_data_buffer(m_data_endpoint_id, m_data_buffer->paddr(), m_data_buffer->size());
return {}; return {};
@ -256,6 +261,10 @@ namespace Kernel
report.device = TRY(BAN::RefPtr<USBMouse>::create()); report.device = TRY(BAN::RefPtr<USBMouse>::create());
dprintln("Initialized an USB Mouse"); dprintln("Initialized an USB Mouse");
break; break;
case 0x04:
report.device = TRY(BAN::RefPtr<USBJoystick>::create(*this));
dprintln("Initialized an USB Joystick");
break;
case 0x06: case 0x06:
report.device = TRY(BAN::RefPtr<USBKeyboard>::create(*this, BAN::move(outputs))); report.device = TRY(BAN::RefPtr<USBKeyboard>::create(*this, BAN::move(outputs)));
dprintln("Initialized an USB Keyboard"); dprintln("Initialized an USB Keyboard");
@ -376,13 +385,13 @@ namespace Kernel
continue; continue;
} }
const uint32_t usage_base = input.usage_id ? input.usage_id : input.usage_minimum;
const bool relative = !!(input.flags & 0x04); const bool relative = !!(input.flags & 0x04);
const bool variable = !!(input.flags & 0x02); const bool variable = !!(input.flags & 0x02);
const auto usage = input.usage_id ? input.usage_id : (input.usage_minimum + (variable ? i : logical));
if (!variable) if (!variable)
device_input.device->handle_array(input.usage_page, usage_base + logical); device_input.device->handle_array(input.usage_page, usage);
else else
{ {
const int64_t physical = const int64_t physical =
@ -392,9 +401,9 @@ namespace Kernel
input.physical_minimum; input.physical_minimum;
if (relative) if (relative)
device_input.device->handle_variable(input.usage_page, usage_base + i, physical); device_input.device->handle_variable(input.usage_page, usage, physical);
else else
device_input.device->handle_variable_absolute(input.usage_page, usage_base + i, physical, input.physical_minimum, input.physical_maximum); device_input.device->handle_variable_absolute(input.usage_page, usage, physical, input.physical_minimum, input.physical_maximum);
} }
bit_offset += input.report_size; bit_offset += input.report_size;
@ -595,15 +604,16 @@ namespace Kernel
break; break;
case 0b1010: // collection case 0b1010: // collection
{ {
if (local_state.usage_stack.size() != 1) if (local_state.usage_stack.size() > 1)
{ {
dwarnln("{} usages specified for collection", local_state.usage_stack.empty() ? "No" : "Multiple"); dwarnln("Multiple usages specified for a collection");
return BAN::Error::from_errno(EFAULT); return BAN::Error::from_errno(EFAULT);
} }
uint16_t usage_page = 0; uint16_t usage_page = 0;
if (global_state.usage_page.has_value()) if (global_state.usage_page.has_value())
usage_page = global_state.usage_page.value(); usage_page = global_state.usage_page.value();
if (local_state.usage_stack.front() >> 16) if (!local_state.usage_stack.empty() && local_state.usage_stack.front() >> 16)
usage_page = local_state.usage_stack.front() >> 16; usage_page = local_state.usage_stack.front() >> 16;
if (usage_page == 0) if (usage_page == 0)
{ {
@ -612,8 +622,11 @@ namespace Kernel
} }
TRY(collection_stack.emplace_back()); TRY(collection_stack.emplace_back());
collection_stack.back().type = report_data[1];
collection_stack.back().usage_page = usage_page; collection_stack.back().usage_page = usage_page;
collection_stack.back().usage_id = local_state.usage_stack.front(); if (!local_state.usage_stack.empty())
collection_stack.back().usage_id = local_state.usage_stack.front();
break; break;
} }
case 0b1100: // end collection case 0b1100: // end collection

View File

@ -0,0 +1,114 @@
#include <kernel/Input/InputDevice.h>
#include <kernel/USB/HID/Joystick.h>
namespace Kernel
{
USBJoystick::USBJoystick(USBHIDDriver& driver)
: USBHIDDevice(InputDevice::Type::Joystick)
, m_driver(driver)
{
}
BAN::ErrorOr<void> USBJoystick::initialize()
{
// TODO: this is not a generic USB HID joystick driver but one for PS3 controller.
// this may still work with other HID joysticks so i won't limit this only
// based on the device.
// linux hid-sony.c
auto temp_region = TRY(DMARegion::create(17));
USBDeviceRequest request;
// move ps3 controller to "operational" state
request.bmRequestType = USB::RequestType::DeviceToHost | USB::RequestType::Class | USB::RequestType::Interface;
request.bRequest = 0x01;
request.wValue = 0x03F2;
request.wIndex = m_driver.interface().descriptor.bInterfaceNumber;
request.wLength = 17;
TRY(m_driver.device().send_request(request, temp_region->paddr()));
// some compatible controllers need this too
request.bmRequestType = USB::RequestType::DeviceToHost | USB::RequestType::Class | USB::RequestType::Interface;
request.bRequest = 0x01;
request.wValue = 0x03F5;
request.wIndex = m_driver.interface().descriptor.bInterfaceNumber;
request.wLength = 8;
TRY(m_driver.device().send_request(request, temp_region->paddr()));
return {};
}
void USBJoystick::start_report()
{
m_interrupt_state = m_state_lock.lock();
for (auto& axis : m_state.axis)
axis = {};
for (auto& button : m_state.buttons)
button = false;
}
void USBJoystick::stop_report()
{
m_state_lock.unlock(m_interrupt_state);
}
void USBJoystick::handle_array(uint16_t usage_page, uint16_t usage)
{
(void)usage;
dprintln("Unsupported array {2H}", usage_page);
}
void USBJoystick::handle_variable(uint16_t usage_page, uint16_t usage, int64_t state)
{
(void)usage;
(void)state;
dprintln("Unsupported relative usage page {2H}", usage_page);
}
void USBJoystick::handle_variable_absolute(uint16_t usage_page, uint16_t usage, int64_t state, int64_t min, int64_t max)
{
switch (usage_page)
{
case 0x01:
switch (usage)
{
case 0x01:
// TODO: PS3 controller sends some extra data with this usage
break;
case 0x30:
m_state.axis[0] = { state, min, max };
break;
case 0x31:
m_state.axis[1] = { state, min, max };
break;
case 0x32:
m_state.axis[2] = { state, min, max };
break;
case 0x35:
m_state.axis[3] = { state, min, max };
break;
}
break;
case 0x09:
if (usage > 0 && usage <= sizeof(m_state.buttons))
m_state.buttons[usage - 1] = state;
break;
default:
dprintln("Unsupported absolute usage page {2H}", usage_page);
break;
}
}
BAN::ErrorOr<size_t> USBJoystick::read_impl(off_t, BAN::ByteSpan buffer)
{
SpinLockGuard _(m_state_lock);
const size_t to_copy = BAN::Math::min(buffer.size(), sizeof(m_state));
memcpy(buffer.data(), &m_state, to_copy);
return to_copy;
}
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <stdint.h>
namespace LibInput
{
// TODO: not used but here if we ever make controller
// support generating events instead of being polled
struct JoystickEvent
{
};
struct JoystickState
{
struct Axis
{
int64_t value;
int64_t min;
int64_t max;
};
Axis axis[4];
bool buttons[32];
};
}