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?
There was a problem hiding this comment.
It would only be used for people to send patches, and the module author is most likely to be the one to test it. I see no issues here.
| 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 <[email protected]>
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 <[email protected]>
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/[email protected]/ 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 |
pastaq
left a comment
There was a problem hiding this comment.
I have a few nuts, and some suggestions. Some of it is negotiable.
I briefly mentioned it below but wanted to expound more here regarding debounce. Have you fully stress tested the write speed of the RGB interface? In my experience it is best to do a write queue using mod_delayed_work() with a timeout that is approximately what the return time for a write to the interface is in ms. Some userspace applications (like steam) write once per increment of a slider in a single threaded operation. When sliding over the entire color spectrum (255^3 options) that can significantly delay a system even if the return time is only a few ms, added up it becomes seconds. mod_delayed_work() will ensure that the sysfs returns immediately and only the most recent write is sent to the device.
Since this protocol uses a single command buffer to write all attributes, that means you can protect everything with the same mod_delayed_work() which should reduce the complexity if this approach compared to other drivers. It will need special handling in suspend/resume to make sashiko happy, but you can gate re-arming with a bool on drvdata.
| else | ||
| return -EINVAL; | ||
|
|
||
| ret = mutex_lock_interruptible(&aya->lock); |
There was a problem hiding this comment.
It's better to use guard or scoped_guard from cleanup.h for new code in the kernel. They will unlock themselves immediately as they go out of scope and it prevents mistaken drops of the unlock in future revisions.
Applies to all instances
|
|
||
| static struct hid_driver aya3_driver = { | ||
| .name = "hid-ayaneo", | ||
| .id_table = aya3_devices, |
There was a problem hiding this comment.
I would make these function titles more generic (hid_ayaneo_*). If a future device uses this protocol it won't be confusing, and if the protocol updates later for a new generation it produces less churn turning the entry points into branching probes/resumes.
| cdev->brightness = 0; | ||
| cdev->max_brightness = 255; | ||
| cdev->brightness_set_blocking = aya3_led_set; | ||
|
|
There was a problem hiding this comment.
Please give the parent led_cdev the color index LED_COLOR_ID_RGB. That will allow userspace to detect the interface and an RGB interface an plumb up things like KDE's chameleon service automatically.
| #define AYA3_RGB_SOLID 0x01 | ||
| #define AYA3_RGB_OFF 0xff | ||
|
|
||
| #define AYA3_VIBRATION_DEFAULT 0x02 /* medium */ |
There was a problem hiding this comment.
This implies that it is variable. I would make this an enum with all values and expose a rumble_intensity attribute as well, with a rumble_intensity_index to expose to userspace the options.
| Reading these attributes queries the controller and can | ||
| take up to a second. | ||
|
|
||
| What: /sys/bus/hid/drivers/hid-ayaneo/<dev>/eject |
There was a problem hiding this comment.
Please add a RO eject_index attr so that userspace can detect options automatically without the need to consult the kernel docs
| F: drivers/spi/spi-axiado.c | ||
| F: drivers/spi/spi-axiado.h | ||
|
|
||
| AYANEO 3 CONTROLLER HID DRIVER |
There was a problem hiding this comment.
It would only be used for people to send patches, and the module author is most likely to be the one to test it. I see no issues here.
| dev_name(&aya->hdev->dev)); | ||
| if (!cdev->name) | ||
| return -ENOMEM; | ||
| cdev->brightness = 0; |
There was a problem hiding this comment.
Is there no way to prove the device for its current state? It would be preferable that the interface reflects the status of the hardware at all times rather than when it's written to from userspace. Other programs (like HHD or huesync) write to the hid interface directly, which can desync the sysfs from the hardware state
| static ssize_t aya3_module_show(struct device *dev, char *buf, int offset) | ||
| { | ||
| struct aya3 *aya = dev_get_drvdata(dev); | ||
| u8 resp[AYA3_RESP_SIZE]; |
There was a problem hiding this comment.
It would be better IMO to have an ayaneo_resp struct that you can cast the response into to improve readability and prevent mistakes with offsets.
| */ | ||
| static int aya3_send_config(struct aya3 *aya, u8 eject) | ||
| { | ||
| static const u8 template[AYA3_REPORT_SIZE] = { |
There was a problem hiding this comment.
I would turn this into a struct and then cast it into a byte buffer before sending. It improves readability significantly. See hid-lenovo-go or hid-oxp for examples.
| [3] = AYA3_CMD_CONFIG, | ||
| [4] = AYA3_SUBCMD_CONFIG, | ||
| [22] = 0x33, | ||
| [23] = 0x22, /* joystick sensitivity 100%/100% */ |
There was a problem hiding this comment.
Consider adding this attribute as configurable as well
|
Thank you @pastaq! Great, exactly the input I was holding v3 for. Here's what I've adopted (staged for the LKML v3; I'll bring the OGC tree in sync once the upstream scope discussion settles):
On the rest:
|
|
Great. Please CC me on the LKML as well. Derek J. Clark [email protected] |
|
@matmartinez I noticed you didn't mention anything about the debounce issue I brought up, did you see that portion as well? |
|
@pastaq apologies, I worked through the inline comments and missed the review body entirely! I went and traced the path in the tree, and I believe the scenario you describe is already covered by the LED core, because the driver deliberately registers only
I stress-tested while chasing the teardown race: tight-loop brightness hammer sustained over several rmmod cycles, minutes at a time results in no lag buildup, no protocol errors, and the rings track the most recent value (it's the same work item What |
|
I'm not confident that is accurate, my concern comes from real world experience with this issue. I experienced significant userspace hitching with the go, OXP, and MSI drivers. The results are amplified depending on the round trip time for a urb in the device, the wait_for_completion timeout becomes a bottleneck and the queued calls pile up. I haven't mitigated this in go_s drivers because it has a sub 4ms round trip and it's not really possible to over queue the buffer in the same way |
|
@pastaq I went and traced all three drivers, and I think the difference is which LED-core op they register. hid-lenovo-go, hid-oxp and hid-msi all register hid-ayaneo registers only I just measured on my hardware:
The residual thing your 50 ms settle would still buy is a lower device-command rate during a continuous drag (~190/s at this round trip → ~20/s). The rings held up fine under sustained tight-loop hammering in the teardown stress tests, so I'd lean toward keeping the stock core plumbing but if you've seen a |
|
I suppose it depends on if you're going to implement the additional attribute for effect using the same ABI I did for the other drivers. It's probably worth me investigating if I can get brightness_set_blocking() working in those contexts as well. I did try using that but my implementation wasn't successful. I don't recall why precisely. I wouldn't call this a blocker for v3 if you're getting reasonable performance. |
|
Yeah, no custom effect attribute planned... the AYA3's only effect is exposed through the stock One tip in case you retry Thanks for helping me on this @pastaq! |
Ido Schimmel says: ==================== Mitigate a side channel in routing exception caches When an ICMP error that quotes a UDP packet is locally delivered, the kernel only creates a routing exception if the quoted packet matches a socket. This allows an off-path attacker to conduct a side-channel attack on the routing exception caches in order to discover the ephemeral ports used by connected UDP sockets. Previous mitigations tried to make it harder for attackers to find hash collisions in these caches and make the eviction of exceptions less predictable. Amit Klein and Noam Caspi demonstrated that both of these mitigations can be bypassed. This patchset tries to mitigate such attacks by always creating an exception, even before trying to find a matching socket. The exception is created by the same helpers that are used when the quoted packet did not originate from a socket, so that guesses (right or wrong) from an off-path attacker always result in an exception being created or updated in the cache that the attacker can observe. Note that this mitigation does not make it easier for attackers to fill these caches, since they can already create exceptions with little to no validation. For example, by sending an ICMP error that quotes an ICMP Echo Reply or one that quotes a UDP source port that matches a wildcard socket. In the good case (matched socket) this comes at the cost of an extra route lookup, as the exception is created before the one performed by the socket path. When the two lookups resolve to different nexthops, an exception is created in the cache of each. Patch #1 fixes a pre-existing bug in the handling of ICMPv6 Redirect Message packets. Discovered while writing the selftest. Patch #2 creates an exception from the IPv4 UDP code even before socket matching. Other socket types do not need this: raw sockets have no ports, and for TCP the ICMP error is discarded unless the quoted sequence number is in window. Patch #3 does the same for IPv6. Patch #4 adds a selftest. v1: https://lore.kernel.org/netdev/[email protected]/ ==================== Link: https://patch.msgid.link/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
…d-type' Ihor Solodrai says: ==================== bpftool, selftests: Add tests for C dump and fix a dropped type "bpftool btf dump format c" generates the vmlinux.h that BPF programs are built against, and it has no direct test coverage. Building against the header is not a substitute for testing it, nothing a compiler accepts would break the build. Add selftests targeting C dump in particular. While we are here, refactor btf_dump selftests to use a new compare_text_to_expected() helper and become more efficient. Fix a bug in bpftool dump that dropped a type when sorting. --- v1->v2: * new patches (#1-#3) introducing compare_text_to_expected() helper and refactoring btf_dump selftests * bpftool dump selftest compares the output to the expected files exactly (Eduard) * use VALIDATE_RAW_BTF macro in the new test (Eduard) * drop v1 patch #4 ("selftests/bpf: Check that sorting preserves types in bpftool dump") as it became unnecessary * nit in the bpftool fix (Andrii) v1: https://lore.kernel.org/bpf/[email protected]/ ==================== Link: https://patch.msgid.link/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
The hid-ayaneo driver merged in OpenGamingCollective/linux-unstable#3 registers the joystick rings LED with a per-device name ("<hid dev name>:rgb:joystick_rings", e.g. "0003:1C4F:0002.0003:rgb:joystick_rings") rather than the fixed "ayaneo:rgb:joystick_rings", following review feedback there. sys_name is matched with glob_match(), so a wildcard prefix covers both the old fixed name and the new per-device one.
# [0.79.0](v0.78.1...v0.79.0) (2026-09-03) ### Bug Fixes * **Ayaneo 3:** match the joystick rings LED sys_name by glob ([535c920](535c920)), closes [OpenGamingCollective/linux-unstable#3](OpenGamingCollective/linux-unstable#3) * **Capability Map:** Correct Xbox Ally X mappings ([b5df052](b5df052)) * **Hardware Support:** Fix SuiPlay 0X1 system buttons ([edba05e](edba05e)) * **Hardware Support:** Use WMI event for MSI Claw series guide button ([3c37456](3c37456)) * **Target Test:** Correctly display Accel and Gyro ([45a8562](45a8562)) * **Target:** Only emulate a real deck with deck target ([38050c8](38050c8)) ### Features * **Hardware Support:** Add AYN Odin 2 ([6f64b97](6f64b97)) * **Hardware Support:** Add AYN Odin 2 Mini ([de77fa2](de77fa2)) * **Hardware Support:** Add Ayn Odin 2 Portal ([594c76d](594c76d)) * **Hardware Support:** Add AYN Odin 3 ([1e74434](1e74434)) * **Hardware Support:** Add AYN Thor ([21d37c0](21d37c0)) * **Hardware Support:** Add Retroid Pocket 5 ([a1232a1](a1232a1)) * **Hardware Support:** Add Retroid Pocket 6 ([8223a04](8223a04)) * **Hardware Support:** Add Retroid Pocket Flip 2 ([c6a875a](c6a875a)) * **Hardware Support:** Add Retroid Pocket Mini ([cf7d259](cf7d259)) * **Hardware Support:** Add Retroid Pocket Mini V2 ([3f42523](3f42523)) * **Hardware Support:** Add Retroid Pocket Nova ([880f512](880f512)) * **input:** add KONKR Pocket FIT controller support ([#670](#670)) ([39c8571](39c8571))
A write command whose data is sent entirely as immediate data is not registered. iser_reg_mem_fastreg() takes the DMA key path and leaves rdma_reg[ISER_DIR_OUT].desc at NULL, while iser_dma_map_task_data() has already set dir[ISER_DIR_OUT]. iser_check_remote_inv() looks at dir[] alone and hands the descriptor to iser_inv_desc(), which reads desc->sig_protected. A target that answers such a command with IB_WR_SEND_WITH_INV faults the initiator. Leaving those commands unregistered is deliberate. The same function already terminates the connection when a target sends a remote invalidation the initiator did not ask for. A target that invalidates a direction that was never registered is in the same class, so give it the same answer. Oops: general protection fault, probably for non-canonical address 0xdffffc0000000004: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027] CPU: 0 UID: 0 PID: 40 Comm: kworker/u8:2 Not tainted 7.2.0-rc5-ISERHOST-gf5098b6bae76-dirty #3 PREEMPT(lazy) Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Workqueue: rxe_wq do_work RIP: 0010:iser_task_rsp+0x6d6/0xec0 Code: 48 c1 ea 03 80 3c 02 00 0f 85 ba 06 00 00 48 8b 9b 78 01 00 00 48 b8 00 00 00 00 00 fc ff df 48 8d 7b 20 48 89 fa 48 c1 ea 03 <0f> b6 04 02 84 c0 74 06 0f 8e 76 06 00 00 80 7b 20 00 0f 84 3d 04 RSP: 0018:ffff88811b008db8 EFLAGS: 00010202 RAX: dffffc0000000000 RBX: 0000000000000000 RCX: 0000000000001848 RDX: 0000000000000004 RSI: 1ffff11021587b12 RDI: 0000000000000020 RBP: ffff88810adc1ae4 R08: ffff888109b7f860 R09: ffffffff90a922c0 R10: ffff88810adc1a1c R11: 000000000000003c R12: ffff888109b7f800 R13: ffff88810adc1acc R14: ffff888109b7f820 R15: 0000000000000000 FS: 0000000000000000(0000) GS:ffff88818a676000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00000000005afe2b CR3: 000000010af23005 CR4: 0000000000770ef0 PKRU: 55555554 Call Trace: <IRQ> __ib_process_cq+0xe1/0x390 ib_poll_handler+0x6e/0x200 irq_poll_softirq+0x1df/0x480 ? clockevents_program_event+0x2ba/0x860 ? __pfx_irq_poll_softirq+0x10/0x10 handle_softirqs+0x18e/0x590 ? __pfx_handle_softirqs+0x10/0x10 ? __hrtimer_rearm_deferred+0x156/0x450 do_softirq+0x3b/0x60 </IRQ> <TASK> __local_bh_enable_ip+0x61/0x70 __alloc_skb+0x732/0x890 ? _raw_spin_lock_irqsave+0x85/0xe0 ? __pfx___alloc_skb+0x10/0x10 ? _raw_read_unlock_irqrestore+0x16/0x50 rxe_init_packet+0x16b/0x4f0 prepare_ack_packet+0xb8/0x830 rxe_receiver+0x499/0x9980 ? __pfx_rxe_receiver+0x10/0x10 ? rxe_completer+0x29e5/0x38c0 ? hrtimer_start_range_ns_common+0x75f/0x1730 ? hrtimer_start_range_ns+0xa6/0x2c0 ? __pfx__raw_spin_lock_irqsave+0x10/0x10 ? __pfx_rxe_receiver+0x10/0x10 do_work+0x144/0x470 process_one_work+0x633/0x1030 ? assign_work+0x11d/0x370 worker_thread+0x45b/0xd10 ? __pfx_worker_thread+0x10/0x10 kthread+0x2c6/0x3b0 ? recalc_sigpending+0x15c/0x1e0 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x36e/0x5a0 ? __pfx_ret_from_fork+0x10/0x10 ? __switch_to+0x572/0xdd0 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 </TASK> Modules linked in: ---[ end trace 0000000000000000 ]--- Fixes: 59caaed ("IB/iser: Support the remote invalidation exception") Signed-off-by: Yehyeong Lee <[email protected]> Link: https://patch.msgid.link/[email protected] Reviewed-by: Max Gurtovoy <[email protected]> Signed-off-by: Leon Romanovsky <[email protected]>
Patch series "mm: avoid large folio splits when swap is unavailable", v7. This is v7 of Barry's original RFC patch, "mm: Avoiding split large folios if swap has no space": https://lore.kernel.org/r/[email protected] Barry's RFC showed the no-swap case with MADV_PAGEOUT on 16KB mTHP: the large-folio split counter increased by 1024 even though no swapout progress was possible. Skipping the split in that case kept the counter at 0. This series makes folio_alloc_swap() classify failures according to whether splitting a large folio might allow swapout to make progress. Callers can then avoid destroying the large folio when neither global swap availability nor the folio's memcg swap hierarchy has capacity for even a smaller folio. Patch #1 adds page_counter_margin(), a small helper that computes the minimum remaining chargeable space across a page_counter hierarchy. Patch #2 establishes the folio_alloc_swap() return-value contract: - -E2BIG: splitting may let smaller folios make progress - -ENOSPC: no global swap space is available - -ENOMEM: splitting is not expected to help, including when the folio's memcg swap hierarchy has no remaining capacity Patch #3 makes vmscan split a large folio only when folio_alloc_swap() returns -E2BIG. Other failures keep the existing activation path and avoid destroying the large folio when no smaller part can be backed by swap either. Patch #4 applies the same contract to shmem_writeout(), which currently splits a large folio on every folio_alloc_swap() failure. It now enters the split fallback only on -E2BIG; other failures redirty and reactivate the folio as before. Testing: With a 1GB anonymous mapping backed by 16KB mTHPs and memory.swap.max=0, the patch reduced the median latency of 30 process_madvise(MADV_PAGEOUT) runs from 743.8 ms to 181.7 ms, while the number of large-folio splits per run dropped from 65536 to 0. Neither kernel swapped out any pages. I also ran DaCapo h2 under swap pressure and found no statistically significant change in wall time or CPU time. The overall benefit appears minor and workload-dependent. This patch (of 4): mem_cgroup_get_nr_swap_pages() open-codes the remaining capacity across the memcg swap counter hierarchy. Add page_counter_margin() to return the minimum usable space from a page counter to the root, and use it in mem_cgroup_get_nr_swap_pages(). This is a pure refactoring with no intended behavior change. Link: https://lore.kernel.org/[email protected] Link: https://lore.kernel.org/[email protected] Signed-off-by: Johannes Weiner <[email protected]> Signed-off-by: Xueyuan Chen <[email protected]> Reviewed-by: David Hildenbrand (Arm) <[email protected]> Reviewed-by: Barry Song <[email protected]> Cc: Baolin Wang <[email protected]> Cc: Baoquan He <[email protected]> Cc: Chris Li <[email protected]> Cc: Hugh Dickins <[email protected]> Cc: Kairui Song <[email protected]> Cc: Kemeng Shi <[email protected]> Cc: Lorenzo Stoakes <[email protected]> Cc: Michal Hocko <[email protected]> Cc: Muchun Song <[email protected]> Cc: Nhat Pham <[email protected]> Cc: Roman Gushchin <[email protected]> Cc: Shakeel Butt <[email protected]> Cc: Nanzhe Zhao <[email protected]> Cc: Youngjun Park <[email protected]> Signed-off-by: Andrew Morton <[email protected]>
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