From c96a5d201692dda0496852bb2a8dd699f3cf8a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20S=C3=B6ntgen?= Date: Thu, 29 Dec 2016 12:59:15 +0100 Subject: [PATCH] usb_gamepad_input: add minimal USB gamepad driver This minimal USB gamepad driver uses the Usb session to access the USB device and provides Genode's Input service to its client. There is no support for any fancy features like rumble support or, if available, battery state checking. Furthermore there is currently no way to calibrate the analog input sources, which leads to unexpected motion events due to input jitter. For a list of supported devices and more information please look at the README. Fixes #58. --- run/usb_gamepad_input.run | 151 +++++ src/drivers/usb_gamepad_input/README | 187 ++++++ src/drivers/usb_gamepad_input/buffalo_snes.h | 177 ++++++ src/drivers/usb_gamepad_input/hid_device.h | 92 +++ .../usb_gamepad_input/logitech_precision.h | 175 ++++++ src/drivers/usb_gamepad_input/main.cc | 557 ++++++++++++++++++ .../usb_gamepad_input/microsoft_xbox360.h | 186 ++++++ .../usb_gamepad_input/microsoft_xboxone.h | 197 +++++++ src/drivers/usb_gamepad_input/retrolink_n64.h | 193 ++++++ src/drivers/usb_gamepad_input/sony_ds3.h | 225 +++++++ src/drivers/usb_gamepad_input/sony_ds4.h | 245 ++++++++ src/drivers/usb_gamepad_input/target.mk | 4 + src/drivers/usb_gamepad_input/utils.h | 190 ++++++ 13 files changed, 2579 insertions(+) create mode 100644 run/usb_gamepad_input.run create mode 100644 src/drivers/usb_gamepad_input/README create mode 100644 src/drivers/usb_gamepad_input/buffalo_snes.h create mode 100644 src/drivers/usb_gamepad_input/hid_device.h create mode 100644 src/drivers/usb_gamepad_input/logitech_precision.h create mode 100644 src/drivers/usb_gamepad_input/main.cc create mode 100644 src/drivers/usb_gamepad_input/microsoft_xbox360.h create mode 100644 src/drivers/usb_gamepad_input/microsoft_xboxone.h create mode 100644 src/drivers/usb_gamepad_input/retrolink_n64.h create mode 100644 src/drivers/usb_gamepad_input/sony_ds3.h create mode 100644 src/drivers/usb_gamepad_input/sony_ds4.h create mode 100644 src/drivers/usb_gamepad_input/target.mk create mode 100644 src/drivers/usb_gamepad_input/utils.h diff --git a/run/usb_gamepad_input.run b/run/usb_gamepad_input.run new file mode 100644 index 0000000..757c021 --- /dev/null +++ b/run/usb_gamepad_input.run @@ -0,0 +1,151 @@ +set usb_raw_device "x.x" + +if {[have_include power_on/qemu]} { + if {![info exists ::env(USB_RAW_DEVICE)]} { + puts "\nPlease define USB_RAW_DEVICE environment variable and set it to your USB device \n" + exit 0 + } + set usb_raw_device $::env(USB_RAW_DEVICE) +} + +set use_qemu [have_include "power_on/qemu"] + +# +# Build +# + +set build_components { + core init + drivers/timer + drivers/usb + drivers/usb_gamepad_input + server/report_rom + test/input +} + +lappend_if [have_spec gpio] build_components drivers/gpio + +source ${genode_dir}/repos/base/run/platform_drv.inc +append_platform_drv_build_components + +build $build_components + +create_boot_directory + +# +# Generate config +# + +set config { + + + + + + + + + + + + + + + } + +append_platform_drv_config + +append_if [have_spec gpio] config { + + + + + } + +append config { + + + + + + + + + + + + + + + + + } +append_if [expr !$use_qemu] config { + + + + + + + +} +append_if $use_qemu config { + } +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +# +# Boot modules +# + +# generic modules +set boot_modules { + core ld.lib.so init + timer report_rom usb_drv usb_gamepad_input_drv + test-input +} + +append_platform_drv_boot_modules + +build_boot_image $boot_modules + +# +# Qemu opts for EHCI +# +append qemu_args " -m 128 -nographic " +append qemu_args " -usb -usbdevice host:$usb_raw_device " +append qemu_args " -device usb-ehci,id=ehci " + +run_genode_until forever diff --git a/src/drivers/usb_gamepad_input/README b/src/drivers/usb_gamepad_input/README new file mode 100644 index 0000000..309d574 --- /dev/null +++ b/src/drivers/usb_gamepad_input/README @@ -0,0 +1,187 @@ +This directory contains an minimal USB gamepad driver. It uses the Usb session +interface to access the USB device and provides Genode's Input service to its +client. There is no support for any fancy features like rumble support or, if +available, battery state checking. Furthermore there is currently no way to +calibrate the analog input sources, which leads to unexpected motion events +due to input jitter. + + +Usage +----- + +Please take a look at the run script _repos/world/run/usb_gamepad_input.run_. + + +Support gamepads and mappings +----------------------------- + +* iBuffalo SNES replica (0583:2060) fully supported: + + dpad top: BTN_FORWARD + dpad bottom: BTN_BACK + dpad left: BTN_LEFT + dpad right: BTN_RIGHT + start: BTN_START + select: BTN_SELECT + A: BTN_A + B: BTN_B + X: BTN_X + Y: BTN_Y + L: BTN_TL + R: BTN_RL + + +* Logitech Precision Gamepad (046d:c21a) fully supported: + + dpad top: BTN_FORWARD + dpad bottom: BTN_BACK + dpad left: BTN_LEFT + dpad right: BTN_RIGHT + start: BTN_START + select: BTN_SELECT + 1: BTN_A + 2: BTN_B + 3: BTN_X + 4: BTN_Y + L1: BTN_TL + R1: BTN_RL + L2: BTN_TL2 + R2: BTN_RL2 + + +* Microsoft XBox 360 (045e:028e) / One (045e:02d1) partially supported: + + LED support and battery information checking is missing as well as + is rumble support. + + analog X axis: MOTION 0 ax + analog Y axis: MOTION 0 ay + analog Z axis: MOTION 1 ax + analog Rz axis: MOTION 1 ay + analog L2 axis: MOTION 2 ax + analog R2 axis: MOTION 3 ax + dpad up: BTN_FORWARD + dpad right: BTN_RIGHT + dpad down: BTN_BACK + dpad left: BTN_LEFT + start: BTN_START + select: BTN_SELECT + guide: BTN_MODE + A: BTN_A + B: BTN_B + X: BTN_X + Y: BTN_Y + L1: BTN_TL + R1: BTN_RL + L3: BTN_THUMBL + R3: BTN_THUMBR + + +* Retrolink N64 replica (0079:0006) fully supported: + + The dpad is actually a 8-way hat that is mapped to 4-way dpad. + + analog X axis: MOTION ax + analog Y axis: MOTION ay + dpad up: BTN_FORWARD + dpad right: BTN_RIGHT + dpad down: BTN_BACK + dpad left: BTN_LEFT + start: BTN_START + select: BTN_SELECT + A: BTN_A + B: BTN_B + CUP: BTN_0are + CRIGHT: BTN_1 + CDOWN: BTN_2 + CLEFT: BTN_3 + L: BTN_TL + R: BTN_RL + Z: BTN_Z + + +* Sony DualShock3 Sixaxis (054c:0268) partially supported: + + Analog support for all buttons as well as sixaxis support and battery + information checking and rumble support is missing. The PS button is + not usable for now. + + analog X axis: MOTION 0 ax + analog Y axis: MOTION 0 ay + analog Z axis: MOTION 1 ax + analog Rz axis: MOTION 1 ay + analog L2 axis: MOTION 2 ax + analog R2 axis: MOTION 3 ax + R2: BTN_RL2 + dpad up: BTN_FORWARD + dpad right: BTN_RIGHT + dpad down: BTN_BACK + dpad left: BTN_LEFT + start: BTN_START + select: BTN_SELECT + X: BTN_A + O: BTN_B + SQUARE: BTN_X + CIRCLE: BTN_Y + L1: BTN_TL + R1: BTN_RL + L2: BTN_TL2 + R2: BTN_RL2 + L3: BTN_THUMB + R3: BTN_THUMB2 + + +* Sony DualShock4 Sixaxis (054c:05c4) partially supported: + + Gyro/touchpad support, battery information checking and rumble support + as well as LED support is missing. The dpad is actually a 8-way hat that + is mapped to 4-way dpad. + + analog X axis: MOTION 0 ax + analog Y axis: MOTION 0 ay + analog Z axis: MOTION 1 ax + analog Rz axis: MOTION 1 ay + analog L2 axis: MOTION 2 ax + analog R2 axis: MOTION 3 ax + R2: BTN_RL2 + dpad up: BTN_FORWARD + dpad right: BTN_RIGHT + dpad down: BTN_BACK + dpad left: BTN_LEFT + start: BTN_START + select: BTN_SELECT + X: BTN_A + O: BTN_B + SQUARE: BTN_X + CIRCLE: BTN_Y + L1: BTN_TL + R1: BTN_RL + L2: BTN_TL2 + R2: BTN_RL2 + L3: BTN_THUMB + R3: BTN_THUMB2 + + +Adding support for additional devices +------------------------------------- + +There is a variety of 3rd party devices that are compatible to the +Microsoft and Sony gamepads. In their case it should be enough to +add their vendor and product id to the proper driver for them to be +recognized. + +Adding support for other completely different devices is mostly just +a matter of picking a fitting existing driver as blueprint and implementing +the 'parse' method. If the device needs a quirk to work, like the DS3, +it may be hacked in the like it was done for the DS4 and XBone controller. + + +Todo +---- + +* add proper configuration handling, e.g. enable_left_analog_stick='yes' + and calibration knobs +* generate device Report, i.e., how many buttons, axis and so on +* rework quirk mechanism and thereby turn the drivers inside out and make + use of a proper state-machine +* support for fancy features diff --git a/src/drivers/usb_gamepad_input/buffalo_snes.h b/src/drivers/usb_gamepad_input/buffalo_snes.h new file mode 100644 index 0000000..2124770 --- /dev/null +++ b/src/drivers/usb_gamepad_input/buffalo_snes.h @@ -0,0 +1,177 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _BUFFALO_SNES_H_ +#define _BUFFALO_SNES_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include + +struct Buffalo_snes : Hid_device +{ + /* + * Supported devices + */ + struct { + uint16_t vendor_id; + uint16_t product_id; + } devices[1] = { + {0x0583, 0x2060} + }; + + enum { + IFACE_NUM = 0, + ALT_NUM = 0, + EP_NUM = 0, + + DATA_LENGTH = 8, + + X = 0, + Y = 1, + B = 2, + + ORIGIN = 0x80, + LEFT_PRESSED = 0x00, + RIGHT_PRESSED = 0xff, + UP_PRESSED = 0x00, + DOWN_PRESSED = 0xff, + }; + + Input::Keycode button_mapping[8] = { + Input::Keycode::BTN_A, /* 0x01 */ + Input::Keycode::BTN_B, /* 0x02 */ + Input::Keycode::BTN_X, /* 0x03 */ + Input::Keycode::BTN_Y, /* 0x04 */ + Input::Keycode::BTN_TL, /* 0x05 */ + Input::Keycode::BTN_TR, /* 0x06 */ + Input::Keycode::BTN_SELECT, /* 0x07 */ + Input::Keycode::BTN_START, /* 0x08 */ + /* clear / turbo buttons omitted */ + }; + + uint8_t last[DATA_LENGTH] = {}; + + /* XXX instead checking for false positives check absolute values */ + bool false_positive(uint8_t o, uint8_t n) + { + return (o == 0x7f && n == 0x80) || (o == 0x80 && n == 0x7f); + } + + Buffalo_snes(Input::Session_component &input_session) + : Hid_device(input_session, "iBuffalo classic USB gamepad (SNES)") + { + /* initial values */ + last[X] = ORIGIN; + last[Y] = ORIGIN; + } + + /************************** + ** HID device interface ** + **************************/ + + bool probe(uint16_t vendor_id, uint16_t product_id) const override + { + for (size_t i = 0; i < sizeof(devices)/sizeof(devices[0]); i++) { + if ( vendor_id == devices[i].vendor_id + && product_id == devices[i].product_id) { + return true; + } + } + return false; + } + + void parse(uint8_t const *new_data, size_t len) override + { + using namespace Genode; + + if (len != DATA_LENGTH) { + error("new data invalid"); + throw -1; + } + + bool const changed = memcmp(last, new_data, len) != 0; + + if (!changed) { return; } + + /* x-axis */ + if (last[X] != new_data[X]) { + if (false_positive(last[X], new_data[X])) { return; } + + /* now pressed if last was origin */ + bool const press = last[X] == ORIGIN; + + bool left = true; + + if (press && !(new_data[X] < last[X])) { + left = false; + } + + if (last[X] == RIGHT_PRESSED) { left = false; } + + Input::Event ev(press ? Input::Event::PRESS : Input::Event::RELEASE, + left ? Input::Keycode::BTN_LEFT : Input::Keycode::BTN_RIGHT, + 0, 0, 0, 0); + input_session.submit(ev); + } + + /* y-axis*/ + if (last[Y] != new_data[Y]) { + if (false_positive(last[Y], new_data[Y])) { return; } + + /* now pressed if last was origin */ + bool const press = last[Y] == ORIGIN; + + bool up = true; + + if (press && !(new_data[Y] < last[Y])) { + up = false; + } + + if (last[Y] == DOWN_PRESSED) { up = false; } + + Input::Event ev(press ? Input::Event::PRESS : Input::Event::RELEASE, + up ? Input::Keycode::BTN_FORWARD : Input::Keycode::BTN_BACK, + 0, 0, 0, 0); + input_session.submit(ev); + } + + if (last[B] != new_data[B]) { + uint8_t const prev = last[B]; + uint8_t const curr = new_data[B]; + + for (int i = 0; i < 8; i++) { + uint8_t const idx = 1u << i; + + if ((prev & idx) == (curr & idx)) { continue; } + + bool const press = !(prev & idx) && (curr & idx); + + Input::Event ev(press ? Input::Event::PRESS : Input::Event::RELEASE, + button_mapping[i], 0, 0, 0, 0); + input_session.submit(ev); + } + } + + /* save for next poll */ + Genode::memcpy(last, new_data, len); + } + + uint8_t iface() const { return IFACE_NUM; } + uint8_t ep() const { return EP_NUM; } + uint8_t alt() const { return ALT_NUM; } +}; + +#endif /* _BUFFALO_SNES_H_ */ diff --git a/src/drivers/usb_gamepad_input/hid_device.h b/src/drivers/usb_gamepad_input/hid_device.h new file mode 100644 index 0000000..0bf914f --- /dev/null +++ b/src/drivers/usb_gamepad_input/hid_device.h @@ -0,0 +1,92 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _HID_DEVICE_H_ +#define _HID_DEVICE_H_ + +/* Genode includes */ +#include +#include + + +struct Hid_device +{ + typedef Genode::uint8_t uint8_t; + typedef Genode::uint16_t uint16_t; + typedef Genode::size_t size_t; + typedef signed short int16_t; + + enum { MAX_DATA = 256, }; + uint8_t data[MAX_DATA] = {}; + + typedef Genode::String<64> Name; + Name name { "" }; + + Input::Session_component &input_session; + + Hid_device(Input::Session_component &input_session, Name const &name) + : name(name), input_session(input_session) { } + + Hid_device(Input::Session_component &input_session) + : input_session(input_session) { } + + virtual ~Hid_device() { } + + /************************** + ** HID device interface ** + **************************/ + + virtual bool probe(uint16_t vendor_id, uint16_t product_id) const + { + Genode::warning(__func__, "(): not implemented"); + return false; + } + + virtual void parse(uint8_t const *new_data, size_t len) + { + using namespace Genode; + + if (MAX_DATA < len) { + warning("limit data len: ", len, " to: ", (int)MAX_DATA); + len = MAX_DATA; + } + + log("generic USB HID dump data:"); + for (size_t i = 0; i < len; i++) { + log(Hex(i), ": ", Hex(new_data[i]), " (", Hex(data[i]), ")"); + } + + /* save for next poll */ + Genode::memcpy(data, new_data, len); + } + + virtual uint8_t iface() const + { + Genode::warning(__func__, "(): not implemented, returning 0"); + return 0; + } + + virtual uint8_t ep() const + { + Genode::warning(__func__, "(): not implemented, returning 0"); + return 0; + } + + virtual uint8_t alt() const + { + Genode::warning(__func__, "(): not implemented, returning 0"); + return 0; + } +}; + +#endif /* _HID_DEVICE_H_ */ diff --git a/src/drivers/usb_gamepad_input/logitech_precision.h b/src/drivers/usb_gamepad_input/logitech_precision.h new file mode 100644 index 0000000..ac9b03b --- /dev/null +++ b/src/drivers/usb_gamepad_input/logitech_precision.h @@ -0,0 +1,175 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _LOGITECH_PRECISION_H_ +#define _LOGITECH_PRECISION_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include + + +struct Logitech_precision : Hid_device +{ + /* + * Supported devices + */ + struct { + uint16_t vendor_id; + uint16_t product_id; + } devices[1] = { + {0x046d, 0xc21a} + }; + + struct Report + { + uint8_t x; /* x-axis [1,255] */ + uint8_t y; /* y-axis [1,255] */ + uint16_t b; /* 8 buttons */ + }; __attribute__((packed)); + + enum { + IFACE_NUM = 0, + ALT_NUM = 0, + EP_NUM = 0, + + DATA_LENGTH = 4, + + X = 0, + Y = 1, + + ORIGIN = 0x80, + + BUTTONS = 10, + + RIGHT_PRESSED = 0xff, + DOWN_PRESSED = 0xff, + }; + + Input::Keycode button_mapping[BUTTONS] = { + Input::Keycode::BTN_X, /* 0x01 */ + Input::Keycode::BTN_A, /* 0x02 */ + Input::Keycode::BTN_B, /* 0x04 */ + Input::Keycode::BTN_Y, /* 0x08 */ + Input::Keycode::BTN_TL, /* 0x10 */ + Input::Keycode::BTN_TR, /* 0x20 */ + Input::Keycode::BTN_TL2, /* 0x40 */ + Input::Keycode::BTN_TR2, /* 0x80 */ + Input::Keycode::BTN_SELECT, /* 0x100 */ + Input::Keycode::BTN_START, /* 0x200 */ + }; + + uint8_t last[DATA_LENGTH] = {}; + + Logitech_precision(Input::Session_component &input_session) + : Hid_device(input_session, "Logitech, Inc. Precision Gamepad") + { + /* initial values */ + last[X] = ORIGIN; + last[Y] = ORIGIN; + } + + /************************** + ** HID device interface ** + **************************/ + + bool probe(uint16_t vendor_id, uint16_t product_id) const override + { + for (size_t i = 0; i < sizeof(devices)/sizeof(devices[0]); i++) { + if ( vendor_id == devices[i].vendor_id + && product_id == devices[i].product_id) { + return true; + } + } + return false; + } + + void parse(uint8_t const *data, size_t len) override + { + using namespace Genode; + + if (len != DATA_LENGTH) { + error("new data invalid"); + throw -1; + } + + bool const changed = memcpy(last, data, len); + if (!changed) { return; } + + Report const * const o = reinterpret_cast(last); + Report const * const n = reinterpret_cast(data); + + /* x-axis */ + uint8_t const ox = last[X]; + uint8_t const nx = data[X]; + bool const x = (ox != nx); + if (x) { + /* now pressed if last was origin */ + bool const press = (ox == ORIGIN); + + bool left = true; + + if (press && !(nx < ox)) { + left = false; + } + + if (ox == RIGHT_PRESSED) { left = false; } + + Input::Event ev(press ? Input::Event::PRESS : Input::Event::RELEASE, + left ? Input::Keycode::BTN_LEFT : Input::Keycode::BTN_RIGHT, + 0, 0, 0, 0); + input_session.submit(ev); + } + + /* y-axis*/ + uint8_t const oy = last[Y]; + uint8_t const ny = data[Y]; + bool const y = (oy != ny); + if (y) { + /* now pressed if last was origin */ + bool const press = oy == ORIGIN; + + bool up = true; + + if (press && !(ny < oy)) { + up = false; + } + + if (oy == DOWN_PRESSED) { up = false; } + + Input::Event ev(press ? Input::Event::PRESS : Input::Event::RELEASE, + up ? Input::Keycode::BTN_FORWARD : Input::Keycode::BTN_BACK, + 0, 0, 0, 0); + input_session.submit(ev); + } + + /* check buttons */ + uint16_t const ob = o->b; + uint16_t const nb = n->b; + bool const b = (ob != nb); + if (b) { + Utils::check_buttons(input_session, ob, nb, BUTTONS, button_mapping); + } + + /* save for next poll */ + Genode::memcpy(last, data, len); + } + + uint8_t iface() const { return IFACE_NUM; } + uint8_t ep() const { return EP_NUM; } + uint8_t alt() const { return ALT_NUM; } +}; + +#endif /* _LOGITECH_PRECISION_H_ */ diff --git a/src/drivers/usb_gamepad_input/main.cc b/src/drivers/usb_gamepad_input/main.cc new file mode 100644 index 0000000..dcf81f5 --- /dev/null +++ b/src/drivers/usb_gamepad_input/main.cc @@ -0,0 +1,557 @@ +/* + * \brief USB HID gamepad to Input session translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local includes */ +#include +#include + +/* include known gamepads */ +#include +#include +#include +#include +#include +#include +#include + + +static bool const verbose_intr = false; +static bool const verbose = false; +static bool const debug = false; +static bool const dump_dt = false; + + +namespace Usb { + using namespace Genode; + + struct Hid; + struct Main; +} + + +/********************************** + ** USB HID Input implementation ** + **********************************/ + +/** + * USB HID + */ +struct Usb::Hid +{ + Env &env; + Input::Session_component &input_session; + + /* + * Supported USB HID gamepads + */ + Hid_device generic { input_session }; + Buffalo_snes buffalo_snes { input_session }; + Logitech_precision logitech_precision { input_session }; + Microsoft_xbox360 xbox360 { input_session }; + Microsoft_xboxone xboxone { input_session }; + Retrolink_n64 retrolink_n64 { input_session }; + Sony_ds3 sony_ds3 { input_session }; + Sony_ds4 sony_ds4 { input_session }; + + enum { MAX_DEVICES = 8, }; + Hid_device *devices[MAX_DEVICES] { + &generic, + &buffalo_snes, + &logitech_precision, + &retrolink_n64, + &xbox360, + &xboxone, + &sony_ds3, + &sony_ds4, + }; + + Hid_device *device = &generic; + + Timer::Connection timer { env }; + + unsigned polling_us = 0; + + void state_change() + { + if (usb.plugged()) { + log("Gamepad plugged in"); + probe_device(); + return; + } + + log("Gamepad unplugged"); + } + + /* construct before Usb::Connection so the dispatcher is valid */ + Signal_handler state_dispatcher { env.ep(), *this, &Hid::state_change }; + + Allocator_avl usb_alloc; + Usb::Connection usb { env, &usb_alloc, "usb_gamepad", 32*1024, state_dispatcher }; + + Usb::Config_descriptor config_descr; + Usb::Device_descriptor device_descr; + Usb::Interface_descriptor iface_descr; + Usb::Endpoint_descriptor ep_descr; + + void handle_alt_setting(Packet_descriptor &p) { warning(__func__, ": not implemented"); } + + void handle_config_packet(Packet_descriptor &p) { _claim_device(); } + + struct Hid_report + { + struct Invalid_report : Genode::Exception { }; + }; + + Hid_report hid_report; + + Hid_report parse_hid_report(uint8_t const *r, size_t len) + { + if (dump_dt) { + log("HID report dump:"); + /* using i+=4 is save since we use a 256 byte buffer */ + for (size_t i = 0; i < len; i+=4) { + log(Hex(r[i], Hex::PREFIX, Hex::PAD), + " ", Hex(r[i+1], Hex::PREFIX, Hex::PAD), + " ", Hex(r[i+2], Hex::PREFIX, Hex::PAD), + " ", Hex(r[i+3], Hex::PREFIX, Hex::PAD)); + } + } + + return Hid_report(); + } + + struct Hid_report_descriptor + { + Usb::Hid &hid; + + Hid_report_descriptor(Usb::Hid &hid) : hid(hid) { } + + void request() + { + enum { + USB_REQUEST_TO_HOST = 0x80, + USB_REQUEST_RCPT_IFACE = 0x01, + USB_REQUEST_GET_DESCRIPTOR = 0x06, + USB_REQUEST_DT = 0x22, + + REQUEST = USB_REQUEST_TO_HOST | USB_REQUEST_RCPT_IFACE, + }; + + /* XXX read size from interface */ + Usb::Packet_descriptor p = hid.alloc_packet(256); + + p.type = Usb::Packet_descriptor::CTRL; + p.control.request = USB_REQUEST_GET_DESCRIPTOR; + p.control.request_type = REQUEST; + p.control.value = USB_REQUEST_DT<<8; + p.control.index = 0; + p.control.timeout = 1000; + + /* alloc_packet checks readiness */ + hid.usb.source()->submit_packet(p); + } + }; + + Hid_report_descriptor hid_report_descr { *this }; + + void handle_ctrl(Packet_descriptor &p) + { + uint8_t const * const data = (uint8_t*)usb.source()->packet_content(p); + size_t const len = p.control.actual_size > 0 + ? p.control.actual_size : 0; + + try { + /* XXX only parse HID report if not already known */ + hid_report = parse_hid_report(data, len); + } catch (Hid_report::Invalid_report) { + error("HID report is invalid"); + return; + } + + /* kick-off polling */ + timer.trigger_once(polling_us); + } + + void handle_irq_packet(Packet_descriptor &p) + { + if (!p.read_transfer()) { return; } + + uint8_t const * const data = (uint8_t*)usb.source()->packet_content(p); + size_t const len = p.transfer.actual_size > 0 + ? p.transfer.actual_size : 0; + + try { + device->parse(data, len); + } + catch (...) { + error("input data is invalid, reconnect device"); + return; + } + + /* keep on r^Wpolling */ + if (polling_us) { timer.trigger_once(polling_us); } + } + + struct String_descr + { + Usb::Hid &hid; + + char const * const name; + + enum { MAX_STRING_LENGTH = 128, }; + char string[MAX_STRING_LENGTH] {}; + + uint8_t index = 0xff; /* hopefully invalid */ + + String_descr(Usb::Hid &hid, char const *name) : hid(hid), name(name) { } + + void request(uint8_t i) + { + index = i; + + Usb::Packet_descriptor p = hid.alloc_packet(MAX_STRING_LENGTH); + + p.type = Usb::Packet_descriptor::STRING; + p.string.index = index; + p.string.length = MAX_STRING_LENGTH; + + hid.usb.source()->submit_packet(p); + } + }; + + void handle_string_packet(Usb::Packet_descriptor &p) + { + String_descr *s = nullptr; + if (p.string.index == manufactorer_string.index) { + s = &manufactorer_string; + } else if (p.string.index == product_string.index) { + s = &product_string; + } else if (p.string.index == serial_number_string.index) { + s = &serial_number_string; + } + + if (!s) { return; } + + uint16_t const * const u = (uint16_t*)usb.source()->packet_content(p); + + if (p.string.length < 0) { p.string.length = 0; } + int const len = min((unsigned)p.string.length, + (unsigned)String_descr::MAX_STRING_LENGTH - 1); + + for (int i = 0; i < len; i++) { s->string[i] = u[i] & 0xff; } + s->string[len] = 0; + + log(s->name, ": ", (char const*)s->string); + } + + String_descr manufactorer_string { *this, "Manufactorer" }; + String_descr product_string { *this, "Product" }; + String_descr serial_number_string { *this, "Serial_number" }; + + struct Completion : Usb::Completion + { + enum State { VALID, FREE, CANCELED }; + State state = FREE; + + void complete(Usb::Packet_descriptor &p) override { } + + void complete(Usb::Hid &hid, Usb::Packet_descriptor &p) + { + if (state != VALID) + return; + + if (!p.succeded) { + /* + * We might end up here b/c the generic driver was used and a vendor + * did not fill the queried string index. It is, however, more likely + * that a IRQ or CTRL packet failed. For better or worse that is quite + * often the case when a gamepad is unplugged. Therefore we never print + * any error in case of a failure. If something goes wrong, one has to + * debug anyway... + */ + return; + } + + switch (p.type) { + case Usb::Packet_descriptor::IRQ: hid.handle_irq_packet(p); break; + case Usb::Packet_descriptor::CTRL: hid.handle_ctrl(p); break; + case Usb::Packet_descriptor::STRING: hid.handle_string_packet(p); break; + case Usb::Packet_descriptor::CONFIG: hid.handle_config_packet(p); break; + case Usb::Packet_descriptor::ALT_SETTING: hid.handle_alt_setting(p); break; + /* ignore other packets */ + case Usb::Packet_descriptor::BULK: + default: break; + } + } + } completions[Usb::Session::TX_QUEUE_SIZE]; + + void ack_avail() + { + while (usb.source()->ack_avail()) { + Usb::Packet_descriptor p = usb.source()->get_acked_packet(); + dynamic_cast(p.completion)->complete(*this, p); + free_packet(p); + } + } + + Signal_handler ack_avail_dispatcher { env.ep(), *this, &Hid::ack_avail }; + + void probe_device() + { + try { + usb.config_descriptor(&device_descr, &config_descr); + } catch (Usb::Session::Device_not_found) { + error("cound not read config descriptor"); + throw -1; + } + + for (int i = 1; i < MAX_DEVICES; i++) { + bool const found = devices[i]->probe(device_descr.vendor_id, + device_descr.product_id); + if (found) { + device = devices[i]; + log("Driver found for device: ", device->name); + break; + } + } + + if (device == &generic) { + warning("no matching driver found, falling back to generic driver"); + } + + Usb::Packet_descriptor p = alloc_packet(0); + + p.type = Packet_descriptor::CONFIG; + p.number = 1; /* XXX read from device */ + + usb.source()->submit_packet(p); + } + + bool _claim_device() + { + try { + usb.config_descriptor(&device_descr, &config_descr); + if (verbose) { Utils::Dump::device(device_descr); } + } catch (Usb::Session::Device_not_found) { + error("cound not read config descriptor"); + return false; + } + + uint8_t const iface = device->iface(); + uint8_t const alt = device->alt(); + uint8_t const ep = device->ep(); + + try { usb.claim_interface(iface); } + catch (Usb::Session::Interface_already_claimed) { + error("could not claim device"); + return false; + } + + try { + usb.interface_descriptor(iface, alt, &iface_descr); + if (verbose) { Utils::Dump::iface(iface_descr); } + } catch (Usb::Session::Interface_not_found) { + error("could not read interface descriptor"); + return false; + } + + try { + usb.endpoint_descriptor(iface, alt, ep, &ep_descr); + if (verbose) { Utils::Dump::ep(ep_descr); } + } catch (Usb::Session::Interface_not_found) { + error("could not read endpoint descriptor"); + return false; + } + + polling_us = 1000 * ep_descr.polling_interval; + + if (debug) { polling_us = 1000 * 1000; } + + /* + * Request HID report descriptor here because certain devices, + * e.g., XBox 360 controller, will not respond otherwise. + */ + hid_report_descr.request(); + + /* + * Execute device specific quirk method which in most cases simply + * sends an USB packet to get the device in working order. + */ + if (device == &xboxone) { + log("Enable XBox One quirk"); + + Usb::Endpoint_descriptor ep_out_descr; + usb.endpoint_descriptor(0, 0, 0, &ep_out_descr); + + Usb::Packet_descriptor p = alloc_packet(5); + + p.type = Usb::Packet_descriptor::IRQ; + p.transfer.ep = ep_out_descr.address; + p.transfer.polling_interval = 100; + + uint8_t *data = reinterpret_cast(usb.source()->packet_content(p)); + data[0] = 0x05; + data[1] = 0x20; + data[2] = 0x00; /* serial */ + data[3] = 0x00; + data[4] = 0x00; + + usb.source()->submit_packet(p); + } else if (device == &sony_ds3) { + log("Enable DS3 quirk"); + + enum { + USB_REQUEST_TO_DEVICE = 0x00, + USB_REQUEST_TYPE_CLASS = 0x20, + USB_REQUEST_RCPT_IFACE = 0x01, + USB_REQUEST_GET_DESCRIPTOR = 0x06, + + USB_HID_REQUEST_GET_REPORT = 0x01, + USB_HID_REQUEST_SET_REPORT = 0x09, + USB_HID_FEATURE_REPORT = 0x02, + + DS3_REPORT = 0xf4, + + REQUEST = USB_REQUEST_TO_DEVICE | USB_REQUEST_TYPE_CLASS | USB_REQUEST_RCPT_IFACE, + }; + + uint8_t const cmd[4] = { 0x42, 0x0C, 0x00, 0x00 }; + + Usb::Packet_descriptor p = alloc_packet(sizeof(cmd)); + + uint8_t *data = reinterpret_cast(usb.source()->packet_content(p)); + Genode::memcpy(data, cmd, sizeof(cmd)); + + p.type = Usb::Packet_descriptor::CTRL; + p.control.request = USB_HID_REQUEST_SET_REPORT; + p.control.request_type = REQUEST; + p.control.value = ((USB_HID_FEATURE_REPORT + 1) << 8) | DS3_REPORT; + p.control.index = 0; + p.control.timeout = 1000; + + usb.source()->submit_packet(p); + } + + /* if we do not know the device, at least query vendor information */ + if (device == &generic) { + manufactorer_string.request(device_descr.manufactorer_index); + product_string.request(device_descr.product_index); + serial_number_string.request(device_descr.serial_number_index); + } + + return true; + } + + void handle_polling() + { + Usb::Packet_descriptor p = alloc_packet(ep_descr.max_packet_size); + + p.type = Usb::Packet_descriptor::IRQ; + p.succeded = false; + p.transfer.ep = ep_descr.address; + p.transfer.polling_interval = 10; + + usb.source()->submit_packet(p); + } + + Signal_handler polling_dispatcher = { + env.ep(), *this, &Hid::handle_polling }; + + Completion *_alloc_completion() + { + for (unsigned i = 0; i < Usb::Session::TX_QUEUE_SIZE; i++) + if (completions[i].state == Completion::FREE) { + completions[i].state = Completion::VALID; + return &completions[i]; + } + + return nullptr; + } + + struct Queue_full : Genode::Exception { }; + struct No_completion_free : Genode::Exception { }; + + Usb::Packet_descriptor alloc_packet(int length) + { + if (!usb.source()->ready_to_submit()) { throw Queue_full(); } + + Usb::Packet_descriptor p = usb.source()->alloc_packet(length); + + p.completion = _alloc_completion(); + if (!p.completion) { + usb.source()->release_packet(p); + throw No_completion_free(); + } + + return p; + } + + void free_packet(Usb::Packet_descriptor &packet) + { + dynamic_cast(packet.completion)->state = Completion::FREE; + usb.source()->release_packet(packet); + } + + /** + * Constructor + * + * \param env environment + * \param alloc allocator used by Usb::Connection + * \param input Input session + */ + Hid(Env &env, Genode::Allocator &alloc, Input::Session_component &input) + : env(env), input_session(input), usb_alloc(&alloc) + { + usb.tx_channel()->sigh_ack_avail(ack_avail_dispatcher); + + timer.sigh(polling_dispatcher); + + /* HID gets initialized by state_change() */ + } +}; + + +struct Usb::Main +{ + Env &env; + Heap heap { env.ram(), env.rm() }; + + Input::Session_component input_session; + Static_root input_root { env.ep().manage(input_session) }; + + Usb::Hid hid { env, heap, input_session }; + + Main(Env &env) : env(env) + { + input_session.event_queue().enabled(true); + env.parent().announce(env.ep().manage(input_root)); + } +}; + + +void Component::construct(Genode::Env &env) { static Usb::Main main(env); } diff --git a/src/drivers/usb_gamepad_input/microsoft_xbox360.h b/src/drivers/usb_gamepad_input/microsoft_xbox360.h new file mode 100644 index 0000000..d78ff08 --- /dev/null +++ b/src/drivers/usb_gamepad_input/microsoft_xbox360.h @@ -0,0 +1,186 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _MICROSOFT_XBOX_360_H_ +#define _MICROSOFT_XBOX_360_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include + + +struct Microsoft_xbox360 : Hid_device +{ + /* + * Supported devices + */ + struct { + uint16_t vendor_id; + uint16_t product_id; + } devices[1] = { + {0x045e, 0x028e} /* orignal Microsoft XBox 360 wired controller */ + }; + + struct Report + { + uint8_t cmd; + uint8_t size; + uint16_t buttons; + uint8_t lt; + uint8_t rt; + int16_t x; + int16_t y; + int16_t z; + int16_t rz; + uint8_t reserved[6]; + }; __attribute__((packed)); + + enum { + IFACE_NUM = 0, + ALT_NUM = 0, + EP_NUM = 0, + + DATA_LENGTH = 20, + + B_NUM = 16, + + DATA_CMD = 0x00, + + AXIS_XY = 0, + AXIS_ZRZ = 1, + AXIS_LT = 2, + AXIS_RT = 3, + }; + + Input::Keycode b_mapping[B_NUM] = { + Input::Keycode::BTN_FORWARD, /* 0x0001 */ + Input::Keycode::BTN_BACK, /* 0x0002 */ + Input::Keycode::BTN_LEFT, /* 0x0004 */ + Input::Keycode::BTN_RIGHT, /* 0x0008 */ + Input::Keycode::BTN_START, /* 0x0010 */ + Input::Keycode::BTN_SELECT, /* 0x0020 */ + Input::Keycode::BTN_THUMBL, /* 0x0040 */ + Input::Keycode::BTN_THUMBR, /* 0x0080 */ + Input::Keycode::BTN_TL, /* 0x0100 */ /* LB */ + Input::Keycode::BTN_TR, /* 0x0200 */ /* RB */ + Input::Keycode::BTN_MODE, /* 0x0400 */ /* Xbox/guide button */ + Input::Keycode::KEY_UNKNOWN, /* 0x0800 */ /* unused */ + Input::Keycode::BTN_A, /* 0x1000 */ + Input::Keycode::BTN_B, /* 0x2000 */ + Input::Keycode::BTN_X, /* 0x4000 */ + Input::Keycode::BTN_Y, /* 0x8000 */ + }; + + typedef signed short int16_t; + + uint8_t last[DATA_LENGTH] = {}; + + bool left_stick_enabled = true; + bool right_stick_enabled = true; + + bool verbose = false; + + void dump_state(Report const * const o, Report const * const n) + { + using namespace Genode; + + log("dump state:"); + log("cmd: ", Hex(n->cmd), " (", Hex(o->cmd), ")"); + log("size: ", Hex(n->size), " (", Hex(o->size), ")"); + log("buttons: ", Hex(n->buttons), " (", Hex(o->buttons), ")"); + log("tl: ", Hex(n->lt), " (", Hex(o->lt), ")"); + log("tr: ", Hex(n->rt), " (", Hex(o->rt), ")"); + log("x: ", Hex(n->x), " (", Hex(o->x), ")"); + log("y: ", Hex(n->y), " (", Hex(o->y), ")"); + log("z: ", Hex(n->z), " (", Hex(o->z), ")"); + log("rz: ", Hex(n->rz), " (", Hex(o->rz), ")"); + } + + Microsoft_xbox360(Input::Session_component &input_session) + : Hid_device(input_session, "Microsoft Corp. Xbox360 Controller") { } + + + /************************** + ** HID device interface ** + **************************/ + + bool probe(uint16_t vendor_id, uint16_t product_id) const override + { + for (size_t i = 0; i < sizeof(devices)/sizeof(devices[0]); i++) { + if ( vendor_id == devices[i].vendor_id + && product_id == devices[i].product_id) { + return true; + } + } + return false; + } + + void parse(uint8_t const *data, size_t len) override + { + using namespace Genode; + + Report const * const n = reinterpret_cast(data); + + /* for now ignore the rest */ + if (n->cmd != DATA_CMD) { return; } + + Report const * const o = reinterpret_cast(last); + + bool const changed = memcmp(data, last, len); + if (!changed) { return; } + + if (verbose) { dump_state(o, n); } + + /* check analog sticks */ + if (left_stick_enabled) { + Utils::check_axis(input_session, o->x, n->x, o->y, n->y, AXIS_XY); + } + if (right_stick_enabled) { + Utils::check_axis(input_session, o->z, n->z, o->rz, n->rz, AXIS_ZRZ); + } + + /* check analog triggers */ + uint8_t const olt = o->lt; + uint8_t const nlt = n->lt; + if (olt != nlt) { + int16_t const oltv = Utils::convert_u8_to_s16(olt); + int16_t const nltv = Utils::convert_u8_to_s16(nlt); + Utils::check_axis(input_session, oltv, nltv, 0, 0, AXIS_LT); + } + + uint8_t const ort = o->rt; + uint8_t const nrt = n->rt; + if (ort != nrt) { + int16_t const ortv = Utils::convert_u8_to_s16(ort); + int16_t const nrtv = Utils::convert_u8_to_s16(nrt); + Utils::check_axis(input_session, ortv, nrtv, 0, 0, AXIS_RT); + } + + /* check buttons */ + uint16_t const ob = o->buttons; + uint16_t const nb = n->buttons; + if (ob != nb) { Utils::check_buttons(input_session, ob, nb, B_NUM, b_mapping); } + + /* save for next poll */ + Genode::memcpy(last, data, len); + } + + uint8_t iface() const { return IFACE_NUM; } + uint8_t ep() const { return EP_NUM; } + uint8_t alt() const { return ALT_NUM; } +}; + +#endif /* _MICROSOFT_XBOX_360_H_ */ diff --git a/src/drivers/usb_gamepad_input/microsoft_xboxone.h b/src/drivers/usb_gamepad_input/microsoft_xboxone.h new file mode 100644 index 0000000..7182ce1 --- /dev/null +++ b/src/drivers/usb_gamepad_input/microsoft_xboxone.h @@ -0,0 +1,197 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _MICROSOFT_XBOX_ONE_H_ +#define _MICROSOFT_XBOX_ONE_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include + + +struct Microsoft_xboxone : Hid_device +{ + /* + * Supported devices + */ + struct { + uint16_t vendor_id; + uint16_t product_id; + } devices[2] = { + {0x045e, 0x02d1}, /* Microsoft XBox One wired controller */ + {0x045e, 0x02dd} /* Microsoft XBox One wired controller (new FW) */ + }; + + struct Report + { + uint8_t cmd; + uint8_t reserved1; /* always 0x00 */ + uint8_t packet_nr; + uint8_t reserved2; /* always 0x0e */ + uint16_t buttons; + uint16_t lt; + uint16_t rt; + int16_t x; + int16_t y; + int16_t z; + int16_t rz; + }; __attribute__((packed)); + + enum { + IFACE_NUM = 0, + ALT_NUM = 0, + EP_NUM = 1, + + DATA_LENGTH = 18, + DATA_OFFSET = 4, /* ignore cmd + packet_nr */ + + B_NUM = 16, + + DATA_CMD = 0x20, + + AXIS_XY = 0, + AXIS_ZRZ = 1, + AXIS_LT = 2, + AXIS_RT = 3, + }; + + Input::Keycode b_mapping[B_NUM] = { + Input::Keycode::KEY_UNKNOWN, /* 0x0001 */ /* unused */ + Input::Keycode::KEY_UNKNOWN, /* 0x0002 */ /* unused */ + Input::Keycode::BTN_START, /* 0x0004 */ + Input::Keycode::BTN_SELECT, /* 0x0008 */ + Input::Keycode::BTN_A, /* 0x0010 */ + Input::Keycode::BTN_B, /* 0x0020 */ + Input::Keycode::BTN_X, /* 0x0040 */ + Input::Keycode::BTN_Y, /* 0x0080 */ + Input::Keycode::BTN_FORWARD, /* 0x0100 */ + Input::Keycode::BTN_BACK, /* 0x0200 */ + Input::Keycode::BTN_LEFT, /* 0x0400 */ + Input::Keycode::BTN_RIGHT, /* 0x0800 */ + Input::Keycode::BTN_TL, /* 0x1000 */ /* LB */ + Input::Keycode::BTN_TR, /* 0x2000 */ /* RB */ + Input::Keycode::BTN_THUMBL, /* 0x4000 */ + Input::Keycode::BTN_THUMBR, /* 0x8000 */ + }; + + typedef signed short int16_t; + + uint8_t last[DATA_LENGTH] = {}; + + bool left_stick_enabled = true; + bool right_stick_enabled = true; + + bool verbose = false; + + void dump_state(Report const * const o, Report const * const n) + { + using namespace Genode; + + log("dump state:"); + log("cmd: ", Hex(n->cmd), " (", Hex(o->cmd), ")"); + log("packet_nr: ", Hex(n->packet_nr), " (", Hex(o->packet_nr), ")"); + log("buttons: ", Hex(n->buttons), " (", Hex(o->buttons), ")"); + log("lt: ", Hex(n->lt), " (", Hex(o->lt), ")"); + log("rt: ", Hex(n->rt), " (", Hex(o->rt), ")"); + log("x: ", Hex(n->x), " (", Hex(o->x), ")"); + log("y: ", Hex(n->y), " (", Hex(o->y), ")"); + log("z: ", Hex(n->z), " (", Hex(o->z), ")"); + log("rz: ", Hex(n->rz), " (", Hex(o->rz), ")"); + } + + Microsoft_xboxone(Input::Session_component &input_session) + : Hid_device(input_session, "Microsoft Corp. Xbox One Controller") { } + + + /************************** + ** HID device interface ** + **************************/ + + bool probe(uint16_t vendor_id, uint16_t product_id) const override + { + for (size_t i = 0; i < sizeof(devices)/sizeof(devices[0]); i++) { + if ( vendor_id == devices[i].vendor_id + && product_id == devices[i].product_id) { + return true; + } + } + return false; + } + + void parse(uint8_t const *data, size_t len) override + { + using namespace Genode; + + Report const * const n = reinterpret_cast(data); + + /* ignore the rest */ + if (n->cmd != DATA_CMD) { return; } + + if (len != DATA_LENGTH) { + warning("drop invalid packet ", n->packet_nr); + return; + } + + Report const * const o = reinterpret_cast(last); + + bool const changed = memcmp(data+DATA_OFFSET, last+DATA_OFFSET, len-DATA_OFFSET); + if (!changed) { return; } + + if (verbose) { dump_state(o, n); } + /* check analog sticks */ + if (left_stick_enabled) { + Utils::check_axis(input_session, o->x, n->x, o->y, n->y, AXIS_XY); + } + if (right_stick_enabled) { + Utils::check_axis(input_session, o->z, n->z, o->rz, n->rz, AXIS_ZRZ); + } + + /* + * check analog triggers + * + * XXX not sure if 1024 -> [-2^15,2^15] is a good idea + */ + uint8_t const olt = o->lt; + uint8_t const nlt = n->lt; + if (olt != nlt) { + int16_t const oltv = Utils::convert_u8_to_s16(olt); + int16_t const nltv = Utils::convert_u8_to_s16(nlt); + Utils::check_axis(input_session, oltv, nltv, 0, 0, AXIS_LT); + } + + uint16_t const ort = o->rt; + uint16_t const nrt = n->rt; + if (ort != nrt) { + int16_t const ortv = Utils::convert_u8_to_s16(ort); + int16_t const nrtv = Utils::convert_u8_to_s16(nrt); + Utils::check_axis(input_session, ortv, nrtv, 0, 0, AXIS_RT); + } + + /* check buttons */ + uint16_t const ob = o->buttons; + uint16_t const nb = n->buttons; + if (ob != nb) { Utils::check_buttons(input_session, ob, nb, B_NUM, b_mapping); } + + /* save for next poll */ + Genode::memcpy(last, data, len); + } + + uint8_t iface() const { return IFACE_NUM; } + uint8_t ep() const { return EP_NUM; } + uint8_t alt() const { return ALT_NUM; } +}; + +#endif /* _MICROSOFT_XBOX_ONE_H_ */ diff --git a/src/drivers/usb_gamepad_input/retrolink_n64.h b/src/drivers/usb_gamepad_input/retrolink_n64.h new file mode 100644 index 0000000..df7093f --- /dev/null +++ b/src/drivers/usb_gamepad_input/retrolink_n64.h @@ -0,0 +1,193 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _RETROLINK_N64_H_ +#define _RETROLINK_N64_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include + + +struct Retrolink_n64 : Hid_device +{ + /* + * Supported devices + */ + struct { + uint16_t vendor_id; + uint16_t product_id; + } devices[1] = { + {0x0079, 0x0006} + }; + + /* + * The actual report data coming from the device does not correspond + * to the HID report, unless I missed something. The digipad and + * the camera buttons are in fact reported in byte 6, 4Bit each. + */ + struct Report + { + uint8_t x; /* analog x-axis [0,255] */ + uint8_t y; /* analog y-axis [0,255] */ + uint8_t z; /* - */ + uint8_t w; /* - */ + uint8_t v; /* - */ + uint8_t h; /* high nibble pad, low nibble camera */ + uint8_t b; /* 6 buttons (l, r, z, a, b, start) */ + uint8_t r; /* - */ + }; __attribute__((packed)); + + enum { + IFACE_NUM = 0, + ALT_NUM = 0, + EP_NUM = 0, + + DATA_LENGTH = 8, + + X = 0, + Y = 1, + H = 5, + B = 6, + + AXIS_XY = 0, + + ORIGIN = 0x80, + HAT_ORIGIN = 0x0f, + + B_NUM = 6, + C_NUM = 4, + H_NUM = 8, + }; + + Input::Keycode b_mapping[B_NUM] = { + Input::Keycode::BTN_TL, /* 0x01 */ + Input::Keycode::BTN_TR, /* 0x02 */ + Input::Keycode::BTN_A, /* 0x04 */ + Input::Keycode::BTN_Z, /* 0x08 */ + Input::Keycode::BTN_B, /* 0x10 */ + Input::Keycode::BTN_START, /* 0x20 */ + }; + + Input::Keycode c_mapping[C_NUM] = { + Input::Keycode::BTN_0, /* 0x01 */ + Input::Keycode::BTN_1, /* 0x02 */ + Input::Keycode::BTN_2, /* 0x04 */ + Input::Keycode::BTN_3, /* 0x08 */ + }; + + char const *h_name(uint8_t v) + { + switch (v) { + case 0: return "BTN_FORWARD"; + case 2: return "BTN_RIGHT"; + case 4: return "BTN_BACK"; + case 6: return "BTN_LEFT"; + } + + return ""; + } + + uint8_t last[DATA_LENGTH] = {}; + + Retrolink_n64(Input::Session_component &input_session) + : Hid_device(input_session, "Retrolink N64 gamepad") + { + + /* initial values */ + last[X] = ORIGIN; + last[Y] = ORIGIN; + last[H] = HAT_ORIGIN; + } + + + /************************** + ** HID device interface ** + **************************/ + + bool probe(uint16_t vendor_id, uint16_t product_id) const override + { + for (size_t i = 0; i < sizeof(devices)/sizeof(devices[0]); i++) { + if ( vendor_id == devices[i].vendor_id + && product_id == devices[i].product_id) { + return true; + } + } + return false; + } + + void parse(uint8_t const *data, size_t len) override + { + using namespace Genode; + + if (len != DATA_LENGTH) { + error("new data invalid"); + throw -1; + } + + Report const * const o = reinterpret_cast(last); + Report const * const n = reinterpret_cast(data); + + bool const changed = (n->x != o->x) + ||(n->y != o->y) + ||(n->h != o->h) + ||(n->b != o->b); + if (!changed) { return; } + + // log("dump state:"); + // log("x: ", Hex(n->x), " (", Hex(o->x), ")"); + // log("y: ", Hex(n->y), " (", Hex(o->y), ")"); + // log("h: ", Hex(n->h), " (", Hex(o->h), ")"); + // log("b: ", Hex(n->b), " (", Hex(o->b), ")"); + + /* check analog */ + uint8_t const ox = o->x; + uint8_t const nx = n->x; + uint8_t const oy = o->y; + uint8_t const ny = n->y; + + int16_t const oxv = Utils::convert_u8_to_s16(ox); + int16_t const nxv = Utils::convert_u8_to_s16(nx); + int16_t const oyv = Utils::convert_u8_to_s16(oy); + int16_t const nyv = Utils::convert_u8_to_s16(ny); + + Utils::check_axis(input_session, oxv, nxv, oyv, nyv, AXIS_XY); + + /* check digipad */ + uint8_t const od = o->h & 0x0f; + uint8_t const nd = n->h & 0x0f; + if (od != nd) { Utils::check_hat(input_session, od, nd); } + + /* check camera buttons */ + uint8_t const oc = (o->h >> 4) & 0x0f; + uint8_t const nc = (n->h >> 4) & 0x0f; + if (oc != nc) { Utils::check_buttons(input_session, oc, nc, C_NUM, c_mapping); } + + /* check buttons */ + uint8_t const ob = o->b; + uint8_t const nb = n->b; + if (o->b != n->b) { Utils::check_buttons(input_session, ob, nb, B_NUM, b_mapping); } + + /* save for next poll */ + Genode::memcpy(last, data, len); + } + + uint8_t iface() const { return IFACE_NUM; } + uint8_t ep() const { return EP_NUM; } + uint8_t alt() const { return ALT_NUM; } +}; + +#endif /* _RETROLINK_N64_H_ */ diff --git a/src/drivers/usb_gamepad_input/sony_ds3.h b/src/drivers/usb_gamepad_input/sony_ds3.h new file mode 100644 index 0000000..57c75bb --- /dev/null +++ b/src/drivers/usb_gamepad_input/sony_ds3.h @@ -0,0 +1,225 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _SONY_DS3_H_ +#define _SONY_DS3_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include + + +struct Sony_ds3 : Hid_device +{ + /* + * Supported devices + */ + struct { + uint16_t vendor_id; + uint16_t product_id; + } devices[2] = { + {0x054c, 0x0268}, /* Sony Corp. Batoh Device / PlayStation 3 Controller */ + }; + + struct Report + { + uint8_t rid; + uint8_t unused1; + uint16_t buttons; + uint8_t ps_button; /* currently unused */ + uint8_t unused2; + uint8_t x; + uint8_t y; + uint8_t z; + uint8_t rz; + uint8_t unused3[8]; + uint8_t lt; + uint8_t rt; + }; __attribute__((packed)); + + enum { + IFACE_NUM = 0, + ALT_NUM = 0, + EP_NUM = 1, + + DATA_LENGTH = 49, + + B_NUM = 16, + + DATA_CMD = 0x20, + + AXIS_XY = 0, + AXIS_ZRZ = 1, + AXIS_LT = 2, + AXIS_RT = 3, + }; + +#if 0 + data [0] rid + data [1] 0x0 + data [2] = 0x01 select, 0x08 start buttons, 0x10 DU, 0x20 DR, 0x40 DD, 0x80 DL + data [3] = 0x01 L2, 0x02 R2, 0x04 L1, 0x08 R1, 0x10 t, 0x20 o, 0x40 x, 0x80 s buttons + data [4] = 0x01 PS button + data [6] = x + data [7] = y + data [8] = z + data [9] = rz + data [18] = LT + data [19] = RT +#endif + + Input::Keycode b_mapping[B_NUM] = { + Input::Keycode::BTN_SELECT, /* 0x0001 */ + Input::Keycode::BTN_THUMBL, /* 0x0002 */ + Input::Keycode::BTN_THUMBR, /* 0x0004 */ + Input::Keycode::BTN_START, /* 0x0008 */ + Input::Keycode::BTN_FORWARD, /* 0x0010 */ + Input::Keycode::BTN_RIGHT, /* 0x0020 */ + Input::Keycode::BTN_BACK, /* 0x0040 */ + Input::Keycode::BTN_LEFT, /* 0x0080 */ + Input::Keycode::BTN_TL2, /* 0x0100 */ + Input::Keycode::BTN_TR2, /* 0x0200 */ + Input::Keycode::BTN_TL, /* 0x0400 */ + Input::Keycode::BTN_TR, /* 0x0800 */ + Input::Keycode::BTN_Y, /* 0x1000 */ + Input::Keycode::BTN_B, /* 0x2000 */ + Input::Keycode::BTN_A, /* 0x4000 */ + Input::Keycode::BTN_X, /* 0x8000 */ + }; + + typedef signed short int16_t; + + uint8_t last[DATA_LENGTH] = {}; + + bool left_stick_enabled = true; + bool right_stick_enabled = true; + + bool verbose = false; + + void dump_state(Report const * const o, Report const * const n) + { + using namespace Genode; + + log("dump state:"); + log("buttons: ", Hex(n->buttons), " (", Hex(o->buttons), ")"); + log("ps_button: ", Hex(n->ps_button), " (", Hex(o->ps_button), ")"); + log("x: ", Hex(n->x), " (", Hex(o->x), ")"); + log("y: ", Hex(n->y), " (", Hex(o->y), ")"); + log("z: ", Hex(n->z), " (", Hex(o->z), ")"); + log("rz: ", Hex(n->rz), " (", Hex(o->rz), ")"); + log("lt: ", Hex(n->lt), " (", Hex(o->lt), ")"); + log("rt: ", Hex(n->rt), " (", Hex(o->rt), ")"); + } + + Sony_ds3(Input::Session_component &input_session) + : Hid_device(input_session, "Sony Corp. PlayStation(R) 3 Controller") { } + + + /************************** + ** HID device interface ** + **************************/ + + bool probe(uint16_t vendor_id, uint16_t product_id) const override + { + for (size_t i = 0; i < sizeof(devices)/sizeof(devices[0]); i++) { + if ( vendor_id == devices[i].vendor_id + && product_id == devices[i].product_id) { + return true; + } + } + return false; + } + + void parse(uint8_t const *data, size_t len) override + { + using namespace Genode; + + Report const * const n = reinterpret_cast(data); + + if (len != DATA_LENGTH) { return; } + + Report const * const o = reinterpret_cast(last); + + bool const changed = memcmp(data, last, len); + if (!changed) { return; } + + if (verbose) { dump_state(o, n); } + + /* check analog sticks */ + if (left_stick_enabled) { + uint8_t const ox = o->x; + uint8_t const nx = n->x; + uint8_t const oy = o->y; + uint8_t const ny = n->y; + + int16_t const oxv = Utils::convert_u8_to_s16(ox); + int16_t const nxv = Utils::convert_u8_to_s16(nx); + int16_t const oyv = Utils::convert_u8_to_s16(oy); + int16_t const nyv = Utils::convert_u8_to_s16(ny); + + Utils::check_axis(input_session, oxv, nxv, oyv, nyv, AXIS_XY); + } + + if (right_stick_enabled) { + uint8_t const oz = o->z; + uint8_t const nz = n->z; + uint8_t const orz = o->rz; + uint8_t const nrz = n->rz; + + int16_t const ozv = Utils::convert_u8_to_s16(oz); + int16_t const nzv = Utils::convert_u8_to_s16(nz); + int16_t const orzv = Utils::convert_u8_to_s16(orz); + int16_t const nrzv = Utils::convert_u8_to_s16(nrz); + + Utils::check_axis(input_session, ozv, nzv, orzv, nrzv, AXIS_ZRZ); + } + + /* + * check analog triggers + * + * XXX not sure if 1024 -> [-2^15,2^15] is a good idea + */ + uint8_t const olt = o->lt; + uint8_t const nlt = n->lt; + if (olt != nlt) { + int16_t const oltv = Utils::convert_u8_to_s16(olt); + int16_t const nltv = Utils::convert_u8_to_s16(nlt); + Utils::check_axis(input_session, oltv, nltv, 0, 0, AXIS_LT); + } + + uint16_t const ort = o->rt; + uint16_t const nrt = n->rt; + if (ort != nrt) { + int16_t const ortv = Utils::convert_u8_to_s16(ort); + int16_t const nrtv = Utils::convert_u8_to_s16(nrt); + Utils::check_axis(input_session, ortv, nrtv, 0, 0, AXIS_RT); + } + + /* check buttons */ + uint16_t const ob = o->buttons; + uint16_t const nb = n->buttons; + if (ob != nb) { Utils::check_buttons(input_session, ob, nb, B_NUM, b_mapping); } + + /* save for next poll */ + Genode::memcpy(last, data, len); + } + + uint8_t iface() const { return IFACE_NUM; } + uint8_t ep() const { return EP_NUM; } + uint8_t alt() const { return ALT_NUM; } +}; + +#endif /* _SONY_DS3_H_ */ diff --git a/src/drivers/usb_gamepad_input/sony_ds4.h b/src/drivers/usb_gamepad_input/sony_ds4.h new file mode 100644 index 0000000..2a77f39 --- /dev/null +++ b/src/drivers/usb_gamepad_input/sony_ds4.h @@ -0,0 +1,245 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _SONY_DS4_H_ +#define _SONY_DS4_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include + + +struct Sony_ds4 : Hid_device +{ + /* + * Supported devices + */ + struct { + uint16_t vendor_id; + uint16_t product_id; + } devices[2] = { + {0x054c, 0x05c4}, /* Sony Corp. */ + }; + + struct Report + { + uint8_t rid; + uint8_t x; + uint8_t y; + uint8_t z; + uint8_t rz; + uint8_t dbuttons; + uint8_t buttons; + uint8_t ps_button; + uint8_t lt; + uint8_t rt; + /* + uint8_t unused2[3]; + uint8_t bat_level; + */ + }; __attribute__((packed)); + + enum { + IFACE_NUM = 0, + ALT_NUM = 0, + EP_NUM = 0, /* first IRQ EP IN */ + + DATA_LENGTH = 64, + + B_NUM = 16, + + DATA_CMD = 0x20, + + AXIS_XY = 0, + AXIS_ZRZ = 1, + AXIS_LT = 2, + AXIS_RT = 3, + }; + +#if 0 + data [0] rid + data [1] x + data [2] y + data [3] z + data [4] rz + data [5] 0x10 square, 0x20 cross, 0x40 circle, 0x80 triangle + dpad: 7=NW 6=W 5=SW 4=S 3=SE 2=E 1=NE 0=N + data [6] 0x01 L1, 0x02 R1, 0x04 L2, 0x08 R2, 0x10 SH, 0x20 OPT, 0x40 L3, 0x80 R3 + data [7] 0x01 PS button + data [8] L2 trigger + data [9] R2 trigger + data [12] BAT level +#endif + + /* + * The buttons are ordered after the reshuffling of data: + * v = (dbuttons & 0xf0) | (ps_button & 0x01) | (buttons << 8) + */ + Input::Keycode b_mapping[B_NUM] = { + Input::Keycode::BTN_MODE, /* 0x0001 */ + Input::Keycode::KEY_UNKNOWN, /* 0x0002 */ + Input::Keycode::KEY_UNKNOWN, /* 0x0004 */ + Input::Keycode::KEY_UNKNOWN, /* 0x0008 */ + Input::Keycode::BTN_X, /* 0x0010 */ + Input::Keycode::BTN_A, /* 0x0020 */ + Input::Keycode::BTN_B, /* 0x0040 */ + Input::Keycode::BTN_Y, /* 0x0080 */ + Input::Keycode::BTN_TL, /* 0x0100 */ + Input::Keycode::BTN_TR, /* 0x0200 */ + Input::Keycode::BTN_TL2, /* 0x0400 */ + Input::Keycode::BTN_TR2, /* 0x0800 */ + Input::Keycode::BTN_SELECT, /* 0x1000 */ + Input::Keycode::BTN_START, /* 0x2000 */ + Input::Keycode::BTN_THUMBL, /* 0x4000 */ + Input::Keycode::BTN_THUMBR, /* 0x8000 */ + }; + + typedef signed short int16_t; + + uint8_t last[DATA_LENGTH] = {}; + + bool left_stick_enabled = false; + bool right_stick_enabled = false; + + bool verbose = false; + + void dump_state(Report const * const o, Report const * const n) + { + using namespace Genode; + + log("dump state:"); + log("dbuttons: ", Hex(n->dbuttons), " (", Hex(o->dbuttons), ")"); + log("buttons: ", Hex(n->buttons), " (", Hex(o->buttons), ")"); + log("ps_button: ", Hex(n->ps_button), " (", Hex(o->ps_button), ")"); + log("x: ", Hex(n->x), " (", Hex(o->x), ")"); + log("y: ", Hex(n->y), " (", Hex(o->y), ")"); + log("z: ", Hex(n->z), " (", Hex(o->z), ")"); + log("rz: ", Hex(n->rz), " (", Hex(o->rz), ")"); + log("lt: ", Hex(n->lt), " (", Hex(o->lt), ")"); + log("rt: ", Hex(n->rt), " (", Hex(o->rt), ")"); + } + + Sony_ds4(Input::Session_component &input_session) + : Hid_device(input_session, "Sony Corp. PlayStation(R) 4 Controller") + { + last[5] = 0x08; + } + + + /************************** + ** HID device interface ** + **************************/ + + bool probe(uint16_t vendor_id, uint16_t product_id) const override + { + for (size_t i = 0; i < sizeof(devices)/sizeof(devices[0]); i++) { + if ( vendor_id == devices[i].vendor_id + && product_id == devices[i].product_id) { + return true; + } + } + return false; + } + + void parse(uint8_t const *data, size_t len) override + { + using namespace Genode; + + Report const * const n = reinterpret_cast(data); + + if (len != DATA_LENGTH) { return; } + + Report const * const o = reinterpret_cast(last); + + /* ignore ever changing report counter */ + Report * const hack = reinterpret_cast(const_cast(data)); + hack->ps_button &= 0x01; + + /* we only care about the actual input data */ + bool const changed = memcmp(data, last, sizeof(Report)); + if (!changed) { return; } + + if (verbose) { dump_state(o, n); } + + /* check analog sticks */ + if (left_stick_enabled) { + uint8_t const ox = o->x; + uint8_t const nx = n->x; + uint8_t const oy = o->y; + uint8_t const ny = n->y; + + int16_t const oxv = Utils::convert_u8_to_s16(ox); + int16_t const nxv = Utils::convert_u8_to_s16(nx); + int16_t const oyv = Utils::convert_u8_to_s16(oy); + int16_t const nyv = Utils::convert_u8_to_s16(ny); + + Utils::check_axis(input_session, oxv, nxv, oyv, nyv, AXIS_XY); + } + + if (right_stick_enabled) { + uint8_t const oz = o->z; + uint8_t const nz = n->z; + uint8_t const orz = o->rz; + uint8_t const nrz = n->rz; + + int16_t const ozv = Utils::convert_u8_to_s16(oz); + int16_t const nzv = Utils::convert_u8_to_s16(nz); + int16_t const orzv = Utils::convert_u8_to_s16(orz); + int16_t const nrzv = Utils::convert_u8_to_s16(nrz); + + Utils::check_axis(input_session, ozv, nzv, orzv, nrzv, AXIS_ZRZ); + } + + /* + * check analog triggers + * + * XXX not sure if 1024 -> [-2^15,2^15] is a good idea + */ + uint8_t const olt = o->lt; + uint8_t const nlt = n->lt; + if (olt != nlt) { + int16_t const oltv = Utils::convert_u8_to_s16(olt); + int16_t const nltv = Utils::convert_u8_to_s16(nlt); + Utils::check_axis(input_session, oltv, nltv, 0, 0, AXIS_LT); + } + + uint16_t const ort = o->rt; + uint16_t const nrt = n->rt; + if (ort != nrt) { + int16_t const ortv = Utils::convert_u8_to_s16(ort); + int16_t const nrtv = Utils::convert_u8_to_s16(nrt); + Utils::check_axis(input_session, ortv, nrtv, 0, 0, AXIS_RT); + } + + uint8_t const od = o->dbuttons & 0x0f; + uint8_t const nd = n->dbuttons & 0x0f; + if (od != nd) { Utils::check_hat(input_session, od, nd); } + + /* check buttons */ + uint16_t const ob = (o->dbuttons & 0xf0) | (o->ps_button & 0x01) | (o->buttons << 8); + uint16_t const nb = (n->dbuttons & 0xf0) | (n->ps_button & 0x01) | (n->buttons << 8); + if (ob != nb) { Utils::check_buttons(input_session, ob, nb, B_NUM, b_mapping); } + + /* save for next poll */ + Genode::memcpy(last, data, len); + } + + uint8_t iface() const { return IFACE_NUM; } + uint8_t ep() const { return EP_NUM; } + uint8_t alt() const { return ALT_NUM; } +}; + +#endif /* _SONY_DS4_H_ */ diff --git a/src/drivers/usb_gamepad_input/target.mk b/src/drivers/usb_gamepad_input/target.mk new file mode 100644 index 0000000..5f572c1 --- /dev/null +++ b/src/drivers/usb_gamepad_input/target.mk @@ -0,0 +1,4 @@ +TARGET = usb_gamepad_input_drv +SRC_CC = main.cc +INC_DIR = $(PRG_DIR) +LIBS = base diff --git a/src/drivers/usb_gamepad_input/utils.h b/src/drivers/usb_gamepad_input/utils.h new file mode 100644 index 0000000..5985f12 --- /dev/null +++ b/src/drivers/usb_gamepad_input/utils.h @@ -0,0 +1,190 @@ +/* + * \brief USB HID to Input translator + * \author Josef Soentgen + * \date 2016-10-12 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +/* Genode includes */ +#include +#include +#include +#include + + +namespace Utils { + + using namespace Genode; + using namespace Usb; + + namespace Dump { + + void device(Device_descriptor &); + void iface(Interface_descriptor &); + void ep(Endpoint_descriptor &); + } + + typedef signed short int16_t; + int16_t convert_u8_to_s16(uint8_t); + + template void check_buttons(Input::Session_component&, + T const, T const, uint8_t const, Input::Keycode[]); + + void check_axis(Input::Session_component&, + int16_t const, int16_t const, int16_t const, int16_t const, int const); + + void check_hat(Input::Session_component &, uint8_t const, uint8_t const); +} + +void Utils::Dump::device(Device_descriptor &descr) +{ + log("Device: " + "len: ", Hex(descr.length), " " + "type: " , Hex(descr.type), " " + "class: ", Hex(descr.dclass), " " + "sub-class: ", Hex(descr.dsubclass), " " + "proto: ", Hex(descr.dprotocol), " " + "max_packet_size: ", Hex(descr.max_packet_size)); + log(" " + "vendor: ", Hex(descr.vendor_id), " " + "product: ", Hex(descr.product_id), " " + "num_configs: ", Hex(descr.num_configs)); +} + + +void Utils::Dump::iface(Interface_descriptor &descr) +{ + log("Interface: ", + "len: ", Hex(descr.length), " " + "type: ", Hex(descr.type), " " + "number: ", Hex(descr.number), " " + "alt_settings: ", Hex(descr.alt_settings), " " + "num_endpoints: ", Hex(descr.num_endpoints), " " + "iclass: ", Hex(descr.iclass), " " + "isubclass: ", Hex(descr.isubclass), " " + "iprotocol: ", Hex(descr.iprotocol), " " + "str_index: ", Hex(descr.interface_index)); +} + + +void Utils::Dump::ep(Endpoint_descriptor &descr) +{ + log("Endpoint: ", + "len: ", Hex(descr.length), " " + "type: ", Hex(descr.type), " " + "address: ", Hex(descr.address), " " + "attributes: ", Hex(descr.attributes), " " + "max_packet_size: ", Hex(descr.max_packet_size), " " + "polling_interval: ", descr.polling_interval); +} + + +Utils::int16_t Utils::convert_u8_to_s16(uint8_t val) +{ + if (val == 0) { return -0x7fff; } + + return val * 0x0101 - 0x8000; +} + + +template +void Utils::check_buttons(Input::Session_component &input_session, + T const prev, T const curr, + uint8_t const count, Input::Keycode mapping[]) +{ + for (uint8_t i = 0; i < count; i++) { + uint16_t const idx = 1u << i; + + if ((prev & idx) == (curr & idx)) { continue; } + + bool const press = !(prev & idx) && (curr & idx); + + Input::Event ev(press ? Input::Event::PRESS : Input::Event::RELEASE, + mapping[i], 0, 0, 0, 0); + input_session.submit(ev); + } +} + + +void Utils::check_axis(Input::Session_component &input_session, + int16_t const ox, int16_t const nx, + int16_t const oy, int16_t const ny, + int const axis) +{ + bool const x = (ox != nx); + bool const y = (oy != ny); + + if (!x && !y) { return; } + + Input::Event ev(Input::Event::MOTION, axis, x ? nx : ox, y ? ny : oy , 0, 0); + input_session.submit(ev); +} + + +void Utils::check_hat(Input::Session_component &input_session, uint8_t const o, uint8_t const n) +{ + static struct Axis_mapping + { + signed char x; + signed char y; + } hat_to_dpad[9] = { + { 0, -1 }, /* N */ + { 1, -1 }, /* NE */ + { 1, 0 }, /* E */ + { 1, 1 }, /* SE */ + { 0, 1 }, /* S */ + { -1, 1 }, /* SW */ + { -1, 0 }, /* W */ + { -1, -1 }, /* NW */ + { 0, 0 }, /* O */ + }; + + static Input::Keycode axis_keymap_x[3] = { + Input::Keycode::BTN_LEFT, + Input::Keycode::KEY_UNKNOWN, + Input::Keycode::BTN_RIGHT, + }; + + static Input::Keycode axis_keymap_y[3] = { + Input::Keycode::BTN_FORWARD, + Input::Keycode::KEY_UNKNOWN, + Input::Keycode::BTN_BACK, + }; + + Axis_mapping ao = hat_to_dpad[o]; + Axis_mapping an = hat_to_dpad[n]; + + if (ao.x != an.x) { + if (an.x != 0) { + Input::Event ev(Input::Event::PRESS, axis_keymap_x[an.x+1], 0, 0, 0, 0); + input_session.submit(ev); + } + if (ao.x != 0) { + Input::Event ev(Input::Event::RELEASE, axis_keymap_x[ao.x+1], 0, 0, 0, 0); + input_session.submit(ev); + } + } + + if (ao.y != an.y) { + if (an.y != 0) { + Input::Event ev(Input::Event::PRESS, axis_keymap_y[an.y+1], 0, 0, 0, 0); + input_session.submit(ev); + } + + if (ao.y != 0) { + Input::Event ev(Input::Event::RELEASE, axis_keymap_y[ao.y+1], 0, 0, 0, 0); + input_session.submit(ev); + } + } +} + +#endif /* _UTILS_H_ */