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_ */