diff --git a/kernel/include/kernel/USB/HID/Joystick.h b/kernel/include/kernel/USB/HID/Joystick.h index 755addc6..1fd2d498 100644 --- a/kernel/include/kernel/USB/HID/Joystick.h +++ b/kernel/include/kernel/USB/HID/Joystick.h @@ -12,6 +12,12 @@ namespace Kernel BAN_NON_COPYABLE(USBJoystick); BAN_NON_MOVABLE(USBJoystick); + enum class Type + { + Unknown, + DualShock3, + }; + public: BAN::ErrorOr initialize() override; @@ -22,20 +28,42 @@ namespace Kernel 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; + void update() override; + protected: BAN::ErrorOr read_impl(off_t, BAN::ByteSpan) override; bool can_read_impl() const override { return true; } + BAN::ErrorOr ioctl_impl(int request, void* arg) override; + private: USBJoystick(USBHIDDriver&); ~USBJoystick() = default; + private: + void initialize_type(); + + BAN::ErrorOr update_dualshock3_state(uint8_t led_bitmap, uint8_t rumble_strength); + private: USBHIDDriver& m_driver; + Type m_type { Type::Unknown }; + + BAN::UniqPtr m_send_buffer; + SpinLock m_state_lock; InterruptState m_interrupt_state; - LibInput::JoystickState m_state {}; + LibInput::JoystickState m_state; + size_t m_state_index { 0 }; + + BAN::Atomic m_has_got_report { false }; + + Mutex m_command_mutex; + + BAN::Atomic m_has_initialized_leds { false }; + uint8_t m_led_state { 0b0001 }; + uint8_t m_rumble_strength { 0x00 }; friend class BAN::RefPtr; }; diff --git a/kernel/kernel/USB/HID/Joystick.cpp b/kernel/kernel/USB/HID/Joystick.cpp index f8dd3f0e..c99519d0 100644 --- a/kernel/kernel/USB/HID/Joystick.cpp +++ b/kernel/kernel/USB/HID/Joystick.cpp @@ -1,6 +1,9 @@ #include +#include #include +#include + namespace Kernel { @@ -8,35 +11,64 @@ namespace Kernel : USBHIDDevice(InputDevice::Type::Joystick) , m_driver(driver) { + using namespace LibInput; + m_state.axis[JSA_TRIGGER_LEFT] = -32767; + m_state.axis[JSA_TRIGGER_RIGHT] = -32767; + } + + void USBJoystick::initialize_type() + { + m_type = Type::Unknown; + + const auto& device_descriptor = m_driver.device().device_descriptor(); + switch (device_descriptor.idVendor) + { + case 0x054C: // Sony + switch (device_descriptor.idProduct) + { + case 0x0268: // DualShock 3 + m_type = Type::DualShock3; + break; + } + break; + } } BAN::ErrorOr 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. + initialize_type(); - // linux hid-sony.c - - auto temp_region = TRY(DMARegion::create(17)); + m_send_buffer = TRY(DMARegion::create(PAGE_SIZE)); 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())); + switch (m_type) + { + case Type::Unknown: + break; + case Type::DualShock3: + { + // linux hid-sony.c - // 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())); + // 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, m_send_buffer->paddr())); + + // some compatible controllers need this too (we don't detect compatible controllers though) + 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, m_send_buffer->paddr())); + + break; + } + } return {}; } @@ -44,16 +76,13 @@ namespace Kernel 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; + m_state_index = 0; } void USBJoystick::stop_report() { m_state_lock.unlock(m_interrupt_state); + m_has_got_report = true; } void USBJoystick::handle_array(uint16_t usage_page, uint16_t usage) @@ -71,31 +100,76 @@ namespace Kernel void USBJoystick::handle_variable_absolute(uint16_t usage_page, uint16_t usage, int64_t state, int64_t min, int64_t max) { + using namespace LibInput; + + constexpr auto map_joystick_axis = + [](int64_t value, int64_t min, int64_t max) -> int16_t + { + if (min == max) + return 0; + return (value - min) * 65534 / (max - min) - 32767; + }; + + constexpr auto map_trigger_axis = + [](int64_t value, int64_t min, int64_t max) -> int16_t + { + if (min == max) + return -32767; + return (value - min) * 65534 / (max - min) - 32767; + }; + + static constexpr JoystickButton button_map[] { + [ 0] = JSB_SELECT, + [ 1] = JSB_STICK_LEFT, + [ 2] = JSB_STICK_RIGHT, + [ 3] = JSB_START, + [ 4] = JSB_DPAD_UP, + [ 5] = JSB_DPAD_RIGHT, + [ 6] = JSB_DPAD_DOWN, + [ 7] = JSB_DPAD_LEFT, + [ 8] = JSB_COUNT, // left trigger + [ 9] = JSB_COUNT, // right trigger + [10] = JSB_SHOULDER_LEFT, + [11] = JSB_SHOULDER_RIGHT, + [12] = JSB_FACE_UP, + [13] = JSB_FACE_RIGHT, + [14] = JSB_FACE_DOWN, + [15] = JSB_FACE_LEFT, + [16] = JSB_MENU, + }; + switch (usage_page) { case 0x01: + // TODO: These are probably only correct for dualshock 3 switch (usage) { case 0x01: - // TODO: PS3 controller sends some extra data with this usage + if (m_state_index == 8) + m_state.axis[JSA_TRIGGER_LEFT] = map_trigger_axis(state, min, max); + if (m_state_index == 9) + m_state.axis[JSA_TRIGGER_RIGHT] = map_trigger_axis(state, min, max); + m_state_index++; break; case 0x30: - m_state.axis[0] = { state, min, max }; + m_state.axis[JSA_STICK_LEFT_X] = map_joystick_axis(state, min, max); break; case 0x31: - m_state.axis[1] = { state, min, max }; + m_state.axis[JSA_STICK_LEFT_Y] = map_joystick_axis(state, min, max); break; case 0x32: - m_state.axis[2] = { state, min, max }; + m_state.axis[JSA_STICK_RIGHT_X] = map_joystick_axis(state, min, max); break; case 0x35: - m_state.axis[3] = { state, min, max }; + m_state.axis[JSA_STICK_RIGHT_Y] = map_joystick_axis(state, min, max); break; } break; case 0x09: - if (usage > 0 && usage <= sizeof(m_state.buttons)) - m_state.buttons[usage - 1] = state; + // TODO: These are probably only correct for dualshock 3 + if (usage > 0 && usage <= sizeof(button_map) / sizeof(*button_map)) + if (const auto button = button_map[usage - 1]; button != JSB_COUNT) + m_state.buttons[button] = state; break; default: dprintln("Unsupported absolute usage page {2H}", usage_page); @@ -111,4 +185,112 @@ namespace Kernel return to_copy; } + BAN::ErrorOr USBJoystick::ioctl_impl(int request, void* arg) + { + switch (request) + { + case JOYSTICK_GET_LEDS: + *static_cast(arg) = m_led_state; + return 0; + case JOYSTICK_SET_LEDS: + switch (m_type) + { + case Type::Unknown: + return BAN::Error::from_errno(ENOTSUP); + case Type::DualShock3: + TRY(update_dualshock3_state(*static_cast(arg), m_rumble_strength)); + return 0; + } + ASSERT_NOT_REACHED(); + case JOYSTICK_GET_RUMBLE: + *static_cast(arg) = m_rumble_strength; + return 0; + case JOYSTICK_SET_RUMBLE: + switch (m_type) + { + case Type::Unknown: + return BAN::Error::from_errno(ENOTSUP); + case Type::DualShock3: + TRY(update_dualshock3_state(m_led_state, *static_cast(arg))); + return 0; + } + ASSERT_NOT_REACHED(); + } + + return USBHIDDevice::ioctl(request, arg); + } + + void USBJoystick::update() + { + if (!m_has_got_report) + return; + + switch (m_type) + { + case Type::Unknown: + break; + case Type::DualShock3: + // DualShock 3 only accepts leds after it has started sending reports + // (when you press the PS button) + if (!m_has_initialized_leds) + (void)update_dualshock3_state(m_led_state, m_rumble_strength); + break; + } + } + + BAN::ErrorOr USBJoystick::update_dualshock3_state(uint8_t led_state, uint8_t rumble_strength) + { + led_state &= 0x0F; + + LockGuard _(m_command_mutex); + + // we cannot do anything until we have received an reports + if (!m_has_got_report) + { + m_led_state = led_state; + m_rumble_strength = rumble_strength; + return {}; + } + + auto* request_data = reinterpret_cast(m_send_buffer->vaddr()); + memset(request_data, 0, 35); + + // header + request_data[0] = 0x01; // report id (?) + request_data[1] = 0xFF; // no idea but linux sets this, doesn't seem to affect anything + + // first byte is maybe *enable rumble control*, it has to be non-zero for rumble to do anything + request_data[3] = 0xFF; + request_data[4] = rumble_strength; + + // LED bitmap (bit 1: led 1, bit 2: led 2, ...) + request_data[9] = led_state << 1; + + // No idea what these do but they need to be correct for the corresponding led to turn on. + // Also they are in reverse order, first entry corresponds to led 4, second to led 3, ... + for (size_t i = 0; i < 4; i++) + { + // values are the same as linux sends + request_data[10 + i * 5] = 0xFF; // has to be non zero + request_data[11 + i * 5] = 0x27; // ignored + request_data[12 + i * 5] = 0x10; // has to be non zero + request_data[13 + i * 5] = 0x00; // ignored + request_data[14 + i * 5] = 0x32; // has to be non zero + } + + USBDeviceRequest request; + request.bmRequestType = USB::RequestType::HostToDevice | USB::RequestType::Class | USB::RequestType::Interface; + request.bRequest = 0x09; + request.wValue = 0x0201; + request.wIndex = m_driver.interface().descriptor.bInterfaceNumber; + request.wLength = 35; + TRY(m_driver.device().send_request(request, m_send_buffer->paddr())); + + m_led_state = led_state; + m_rumble_strength = rumble_strength; + m_has_initialized_leds = true; + + return {}; + } + } diff --git a/userspace/libraries/LibC/include/sys/ioctl.h b/userspace/libraries/LibC/include/sys/ioctl.h index 30646a03..aa7d4b77 100644 --- a/userspace/libraries/LibC/include/sys/ioctl.h +++ b/userspace/libraries/LibC/include/sys/ioctl.h @@ -54,9 +54,14 @@ struct winsize #define SND_GET_SAMPLE_RATE 61 /* stores sample rate to uint32_t argument */ #define SND_RESET_BUFFER 62 /* stores the size of internal buffer to uint32_t argument and clears the buffer */ #define SND_GET_BUFFERSZ 63 /* stores the size of internal buffer to uint32_t argument */ -#define SND_GET_TOTAL_PINS 64 /* gets the number of pins on the current device */ -#define SND_GET_PIN 65 /* gets the currently active pin */ -#define SND_SET_PIN 66 /* sets the currently active pin */ +#define SND_GET_TOTAL_PINS 64 /* gets the number of pins on the current device as uint32_t */ +#define SND_GET_PIN 65 /* gets the currently active pin as uint32_t */ +#define SND_SET_PIN 66 /* sets the currently active pin to uint32_t */ + +#define JOYSTICK_GET_LEDS 80 /* get controller led bitmap to uint8_t argument */ +#define JOYSTICK_SET_LEDS 81 /* set controller leds to uint8_t bitmap */ +#define JOYSTICK_GET_RUMBLE 82 /* get controller rumble strength to uint8_t argument */ +#define JOYSTICK_SET_RUMBLE 83 /* set controller rumble strength to uint8_t argument */ int ioctl(int, int, ...); diff --git a/userspace/libraries/LibInput/include/LibInput/Joystick.h b/userspace/libraries/LibInput/include/LibInput/Joystick.h index 13ddfced..028827bb 100644 --- a/userspace/libraries/LibInput/include/LibInput/Joystick.h +++ b/userspace/libraries/LibInput/include/LibInput/Joystick.h @@ -5,7 +5,45 @@ namespace LibInput { - // TODO: not used but here if we ever make controller + enum JoystickButton + { + JSB_DPAD_UP, + JSB_DPAD_DOWN, + JSB_DPAD_LEFT, + JSB_DPAD_RIGHT, + + JSB_FACE_UP, + JSB_FACE_DOWN, + JSB_FACE_LEFT, + JSB_FACE_RIGHT, + + JSB_STICK_LEFT, + JSB_STICK_RIGHT, + + JSB_SHOULDER_LEFT, + JSB_SHOULDER_RIGHT, + + JSB_MENU, + JSB_START, + JSB_SELECT, + + JSB_COUNT, + }; + + enum JoystickAxis + { + JSA_STICK_LEFT_X, + JSA_STICK_LEFT_Y, + JSA_STICK_RIGHT_X, + JSA_STICK_RIGHT_Y, + + JSA_TRIGGER_LEFT, + JSA_TRIGGER_RIGHT, + + JSA_COUNT, + }; + + // TODO: not used but exists if we ever make controller // support generating events instead of being polled struct JoystickEvent { @@ -14,15 +52,9 @@ namespace LibInput struct JoystickState { - struct Axis - { - int64_t value; - int64_t min; - int64_t max; - }; - - Axis axis[4]; - bool buttons[32]; + // axis are mapped to range [-32767, +32767] + int16_t axis[JSA_COUNT]; + bool buttons[JSB_COUNT]; }; } diff --git a/userspace/tests/test-joystick/main.cpp b/userspace/tests/test-joystick/main.cpp index 6d1b0df4..610f3780 100644 --- a/userspace/tests/test-joystick/main.cpp +++ b/userspace/tests/test-joystick/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -47,13 +48,6 @@ void cleanup() tcsetattr(STDIN_FILENO, TCSANOW, &original_termios); } -int map_joystick(const LibInput::JoystickState::Axis& axis, float min, float max) -{ - if (axis.min == axis.max) - return (min + max) / 2; - return (axis.value - axis.min) * (max - min) / (axis.max - axis.min) + min; -} - int main(int argc, char** argv) { const char* fb_path = "/dev/fb0"; @@ -132,6 +126,12 @@ int main(int argc, char** argv) return 1; } + uint8_t led_bitmap { 0b0001 }; + ioctl(joystick_fd, JOYSTICK_SET_LEDS, &led_bitmap); + bool old_lshoulder { false }, old_rshoulder { false }; + + uint8_t rumble_strength { 0x00 }; + uint32_t color = 0xFF0000; int circle_x = fb_info.width / 2; int circle_y = fb_info.height / 2; @@ -142,9 +142,20 @@ int main(int argc, char** argv) draw_circle(circle_x, circle_y, radius, color); msync(fb_mmap, fb_bytes, MS_SYNC); + timespec last_led_update; + clock_gettime(CLOCK_MONOTONIC, &last_led_update); + + timespec prev_frame_ts; + clock_gettime(CLOCK_MONOTONIC, &prev_frame_ts); + while (true) { - LibInput::JoystickState state {}; + using namespace LibInput; + + timespec current_ts; + clock_gettime(CLOCK_MONOTONIC, ¤t_ts); + + JoystickState state {}; if (read(joystick_fd, &state, sizeof(state)) == -1) { fprintf(stderr, "read: "); @@ -152,9 +163,9 @@ int main(int argc, char** argv) return 1; } - const int dx = map_joystick(state.axis[0], -50, 50); - const int dy = map_joystick(state.axis[1], -50, 50); - const int dr = map_joystick(state.axis[3], 5, -5); + const int dx = state.axis[JSA_STICK_LEFT_X] / 655; + const int dy = state.axis[JSA_STICK_LEFT_Y] / 655; + const int dr = state.axis[JSA_STICK_RIGHT_Y] / 6553; draw_circle(circle_x, circle_y, radius, 0x000000); @@ -164,21 +175,59 @@ int main(int argc, char** argv) circle_y = BAN::Math::clamp(circle_y + dy, 0, fb_info.height); radius = BAN::Math::clamp(radius + dr, 1, 100); - if (state.buttons[12]) + if (state.buttons[JSB_FACE_UP]) color = 0xFF0000; - if (state.buttons[13]) + if (state.buttons[JSB_FACE_DOWN]) color = 0x00FF00; - if (state.buttons[14]) + if (state.buttons[JSB_FACE_LEFT]) color = 0x0000FF; - if (state.buttons[15]) + if (state.buttons[JSB_FACE_RIGHT]) color = 0xFFFFFF; + if ((state.buttons[JSB_SHOULDER_LEFT] && !old_lshoulder) != (state.buttons[JSB_SHOULDER_RIGHT] && !old_rshoulder)) + { + if (state.buttons[JSB_SHOULDER_LEFT] && !old_lshoulder) + led_bitmap = (led_bitmap - 1) & 0x0F; + if (state.buttons[JSB_SHOULDER_RIGHT] && !old_rshoulder) + led_bitmap = (led_bitmap + 1) & 0x0F; + ioctl(joystick_fd, JOYSTICK_SET_LEDS, &led_bitmap); + } + old_lshoulder = state.buttons[JSB_SHOULDER_LEFT]; + old_rshoulder = state.buttons[JSB_SHOULDER_RIGHT]; + + if ((state.axis[JSA_TRIGGER_LEFT] > 0) != (state.axis[JSA_TRIGGER_RIGHT] > 0)) + { + const auto old_rumble = rumble_strength; + if (state.axis[JSA_TRIGGER_LEFT] > 0) + rumble_strength = (rumble_strength <= 0x05) ? 0 : rumble_strength - 5; + if (state.axis[JSA_TRIGGER_RIGHT] > 0) + rumble_strength = (rumble_strength >= 0xFA) ? 0 : rumble_strength + 5; + if (rumble_strength != old_rumble) + ioctl(joystick_fd, JOYSTICK_SET_RUMBLE, &rumble_strength); + } + draw_circle(circle_x, circle_y, radius, color); msync(fb_mmap, fb_bytes, MS_SYNC); - usleep(16666); + const uint64_t current_us = + current_ts.tv_sec * 1'000'000 + + current_ts.tv_nsec / 1000; + const uint64_t last_frame_us = + prev_frame_ts.tv_sec * 1'000'000 + + prev_frame_ts.tv_nsec / 1000; - msync(fb_mmap, fb_bytes, MS_SYNC); + const uint64_t wakeup_us = last_frame_us + 16'666; + if (current_us < wakeup_us) + { + const uint32_t sleep_us = wakeup_us - current_us; + const timespec sleep_ts { + .tv_sec = 0, + .tv_nsec = static_cast(wakeup_us - current_us) * 1000, + }; + nanosleep(&sleep_ts, nullptr); + } + + prev_frame_ts = current_ts; } }