HID: ayaneo: Add AYANEO 3 detachable controller driver - #3
Conversation
| /* Input reports are not delivered during probe by default */ | ||
| hid_device_io_start(hdev); | ||
|
|
||
| mutex_lock(&aya->lock); |
There was a problem hiding this comment.
here I would use scoped_guard to spare the line of mutex_unlock
| if (ret) | ||
| hid_warn(hdev, "controller did not answer status check: %d\n", | ||
| ret); | ||
| else |
There was a problem hiding this comment.
kernel practice is not to print anything when things go as planned
| F: drivers/spi/spi-axiado.c | ||
| F: drivers/spi/spi-axiado.h | ||
|
|
||
| AYANEO 3 CONTROLLER HID DRIVER |
There was a problem hiding this comment.
I have rarely seen hid devices requiring an entry in MAINTAINERS, are you sure?
| return 0; | ||
| } | ||
|
|
||
| /* Send the command in aya->xfer and wait for the echoing reply. */ |
There was a problem hiding this comment.
I think this function deserves a kernel-doc also explaining arguments and the locking
|
|
||
| static void aya3_checksum(u8 *buf) | ||
| { | ||
| unsigned int sum = 0; |
There was a problem hiding this comment.
personally I would use a fixed-width type here, like u32 or u64 depending on if the result would fit the u32.... Maybe a u16 can also work? Especially since you then use put_unaligned_le16
| return ret; | ||
| } | ||
|
|
||
| static void aya3_remove(struct hid_device *hdev) |
There was a problem hiding this comment.
I can guarantee you sahiko-bot is going to cry over this with a bunch of "what if user uses sysfs attributes while a remove is started?"
| static int aya3_send_config(struct aya3 *aya, u8 eject) | ||
| { | ||
| u8 *buf = aya->xfer; | ||
| u8 mode = AYA3_RGB_SOLID; |
There was a problem hiding this comment.
I would do here u8 mode = (led_on_condition) ? AYA3_RGB_SOLID : AYA3_RGB_OFF; and spare the next two lines.
| if (!aya->rgb[0] && !aya->rgb[1] && !aya->rgb[2]) | ||
| mode = AYA3_RGB_OFF; | ||
|
|
||
| memset(buf, 0, AYA3_REPORT_SIZE); |
There was a problem hiding this comment.
I think I would do it the other way around: create a const u8 buf[SIZE] = {}; that will be zero-filled automatically on unspecified elements and then copy that to the dma buffer.
| for (i = 0; i < 3; i++) | ||
| aya->rgb[i] = min_t(unsigned int, aya->subleds[i].brightness, 255); | ||
|
|
||
| ret = aya3_send_config(aya, 0); |
There was a problem hiding this comment.
Since on error IDK what happens I would use a hid_err here in case ret has unexpected values
| aya->mcled.subled_info = aya->subleds; | ||
| aya->mcled.num_colors = 3; | ||
|
|
||
| cdev->name = "ayaneo:rgb:joystick_rings"; |
There was a problem hiding this comment.
sahiko-bot is going to complain about the name with a "what if an aya3 spoofed device is being emulated?". I would suggest doing what hid-asus does and compose this name with a dynamic part.
|
Thanks for the thorough review @NeroReflex! All addressed, I pushed each point as a separate commit for easy re-review (I'll squash everything back into the single patch before this goes to LKML):
I think the current code works well in two spots, let me know if you see it differently:
checkpatch --strict is clean on all the new commits, and I re-tested the updated driver on my AYANEO 3: probe is now silent, module type reads work, and the renamed LED sets/clears the joystick rings correctly (which also exercises the template-built config command end-to-end, since the device has to ACK it). |
I'm not sure adding the static is good idea: first time it will get populated and later on not touched... Are you sure driver still works? Beside it adds to the .bss without any real reason. I thing const is enough here.
sashiko-bot will tell you: what if dev_name(...) is NULL?
Maybe it can be moved above so that the kernel doesn't even try reading the descriptor if it's not a USB?
|
|
You should merge every modification into the single patch, then do a [NOT-FOR-UPSTREAM] patch that adds to the fragment in this repo the CONFIG_AYANEO required to build the driver so that the github workflow can compile the driver. |
The AYANEO 3 handheld has a detachable controller with swappable
modules ("Magic Modules"). The controller exposes three USB HID
interfaces behind 1c4f:0002 (a generic SigmaMicro VID/PID, hence the
DMI gate): a gamepad, a keyboard for the extra buttons, and a vendor
interface accepting 65-byte commands.
Add a driver for the vendor interface providing module identification
(module_left/module_right sysfs attributes), software eject of the
modules (eject sysfs attribute, blocking until the firmware confirms
the release handshake), and RGB control of the joystick rings as a
multicolor LED class device named ayaneo:rgb:joystick_rings, matching
the name InputPlumber already expects for this device.
This complements the ayaneo-ec platform driver, which exposes module
attach state and controller power. A full physical eject is performed
by writing to eject and then cutting power through ayaneo-ec's
controller_power attribute; that orchestration is deliberately left
to userspace.
The protocol was reverse engineered in the Handheld Daemon project by
Antheas Kapenekakis. Tested on an AYANEO 3 (7.2.0-ogc4.1): module
identification, RGB, and a full eject/reinsert/repower cycle.
Signed-off-by: Matías Martínez <hello@matias.me>
Lets the build workflow compile the new driver. The real OGC config change is OpenGamingCollective/kernel-packages#35, which lands once the driver merges. Signed-off-by: Matías Martínez <hello@matias.me>
55e0f67 to
501d2cc
Compare
|
@NeroReflex Thank you! Done on both process points: everything is squashed back into the single On the three code points:
Current state: I think the earlier CI failure was indeed the config gate flagging the missing |
|
Perfect, thank you. As soon as CI compiles the driver I will merge |
|
I asked claude to review one of my drivers. It has said this, I will paste it because I think it's useful to you too. Feel free to start from a HEAD prior to my merge and reopen another PR. I will take care of the rest. Good find — and this one's subtler than a simple missing check. hid_is_usb(hdev) only inspects hdev->bus: static inline bool hid_is_usb(const struct hid_device *hdev)
{
return hdev->bus == BUS_USB;
}That field is attacker-controlled: any unprivileged process with access to /dev/uhid can issue UHID_CREATE and set bus = BUS_USB while the actual hdev->dev.parent is the uhid virtual device, not a struct usb_interface. So hid_is_usb(hdev) returning true does not guarantee hdev->dev.parent is safe to cast with to_usb_interface()/interface_to_usbdev(). This affects every USB-cast site in the driver gated only by hid_is_usb(), not just the one hunk the bot flagged in hid_asus_ally_probe() — ally_get_endpoint_address(), asus_kbd_register_leds(), and the QUIRK_T100_KEYBOARD/QUIRK_MEDION_E1239T branches in asus_probe() all have the same gap. The fix: verify the parent device is actually attached to the USB bus (dev->bus == &usb_bus_type) before trusting the cast, not just the spoofable hdev->bus field. +/*
+ * hid_is_usb() only checks hdev->bus, which is attacker-controlled by any
+ * process with access to /dev/uhid: UHID_CREATE lets userspace claim an
+ * arbitrary bus id, including BUS_USB, while hdev->dev.parent is the uhid
+ * virtual device, not a struct usb_interface. Casting dev.parent based on
+ * hid_is_usb() alone lets such a spoofed "USB" HID device redirect the
+ * cast at unrelated memory. Confirm the parent is actually on the USB bus
+ * before trusting the cast.
+ */
+static bool asus_hdev_is_usb(struct hid_device *hdev)
+{
+ return hid_is_usb(hdev) && hdev->dev.parent &&
+ hdev->dev.parent->bus == &usb_bus_type;
+}
+
static int ally_get_endpoint_address(struct hid_device *hdev)
{
struct usb_host_endpoint *ep;
struct usb_interface *intf;
- if (!hid_is_usb(hdev))
+ if (!asus_hdev_is_usb(hdev))
return -ENODEV;
intf = to_usb_interface(hdev->dev.parent); |
|
@NeroReflex Thanks for the merge and the fast review cycle! For completeness I checked hid-ayaneo against the underlying concern anyway: the driver never casts Next on my side; the InputPlumber |
|
When you send it upstream please include And send to me too please. |
|
Done! Submitted to linux-input/LKML with your https://lore.kernel.org/linux-input/20260824215041.79892-1-hello@matias.me/ Rebased onto hid.git Thanks again for the review and the merge <3 I'll follow up here if the upstream review produces changes worth backporting to the OGC tree. |
Now we wait for sashiko-bot XD |
Submitting here for review prior to LKML, per @pastaq in ShadowBlip/OpenGamepadUI#528.
Same commit as OpenGamingCollective/linux#101 (rebased onto this master; happy to close whichever of the two is redundant — guidance welcome on how these flow together).
What it does
Driver for the AYANEO 3 detachable controller ("Magic Modules") vendor HID interface (
1c4f:0002, application usage0xff000001; DMI-gated to the AYANEO 3 since the VID/PID is a generic SigmaMicro ID):module_left/module_rightsysfs attrs — raw firmware module-type IDs (bits 0–5 type, bit 6 rotated)ejectsysfs attr (left/right/both) — blocks until the firmware confirms the release handshakereset— quick controller config resetayaneo:rgb:joystick_rings(the name InputPlumber's50-ayaneo_3.yamlalready expects)EC power-off is deliberately left to userspace (write
0to ayaneo-ec'scontroller_powerafterejectreturns) so orchestration/UX stays in the OpenGamepadUI layer. Protocol reverse engineered in Handheld Daemon by Antheas Kapenekakis (he'll be CC'd on the LKML series). IncludesDocumentation/ABI/and MAINTAINERS entries; checkpatch --strict clean except the standard-ENOSYSoutput-report-fallback false positive.Testing
On an AYANEO 3 / Bazzite 44 (OGC 7.2.0-ogc4.1): probe identifies modules (
left 0x04 right 0x50), RGB via LED class verified, and full physical eject → power-off → release → reinsert → repower → re-enumeration → rebind cycles, both from the shell and driven by a working OpenGamepadUI quick-bar plugin (see ShadowBlip/OpenGamepadUI#528). Community testing guide: https://github.com/matmartinez/ayaneo-3-bazzite-compat/blob/main/TESTING.md