Achieving Day 0 ROG Ally X Support in Bazzite

As in: making Windows handhelds behave in Linux.

Ally X

During this little postmortem 2-piece series, we will explore just one of the challenges of adding support to Linux: the controller, and reminisce about the 2 weeks we spent with Rich from FanTheDeck in July while adding support for the Ally X.

Some would say that Ally X is an iterative upgrade over its predecessor. Therefore, adding support for it should be trivial. How difficult was it to support the original Ally, anyway, if Valve just did it by backporting a kernel patch? (spoiler: they did not add support)

When using Bazzite, everything “just works” and it is just a controller and VRR screen after all, so how hard could it be?

Indeed, the magic we baked into Bazzite is so potent, it hides the little fact that the Legion Go controller shows up as 8 disparate devices and the Ally as 7, which have to be meticulously fused together into that pretty Dualsense Edge controller.

Achieving Day 0 support for Ally X is the culmination of a multi-month long effort by the Bazzite and Handheld Daemon projects into building cutting-edge tooling and infrastructure for rapid iteration and testing, in addition to thousands of hours of reverse engineering.

In the first part, we will explore the how and the why of Handheld Daemon, the open-source software that powers the controller (and more) in Bazzite. We will explore how controllers work in Windows, how they work in Linux, how they work under Steam, and delve into how Handheld Daemon meshes all of this together.

Controllers in Windows

With the introduction of Xbox 360 in 2005, a major success, Microsoft set the standard for how a controller should look and what buttons it should have. The humble Xbox 360 controller has become the de facto standard for PC gaming, with its 11 buttons, 2 analog sticks, and a D-pad.

Alongside this controller, Microsoft introduced the XInput API and protocols, which have become (and still are*) the standard API for controller support on Windows.Even as newer protocols have superseded it, XInput remains the only way hardwaremanufacturers can ensure their controller will always work with games in Windows.

XInput is so pervasive that all Windows handhelds up to this point use XInput!

*The XInput protocol (not API) is deprecated, with Ally X being the first handheld not to use it.

As manufacturers have continued to innovate, they started to add additional features to their controllers, such as gyroscopes, touchpads, paddles, and RGB lighting. Valve itself has spent years perfecting a standard for PC gaming controllers, first with the Steam Controller and now with the Steam Deck, which re-uses the proprietary firmware from the Steam Controller.

However, with limited interest in the PC gaming market, XInput remained the only viable protocol for manufacturers and games.

Third party applications such as DS4Windows are often required to use the DualShock and Dualsense controller in certain Windows games.

Valve’s Steam Input

As part of the Steam controller, Valve introduced Steam Input, a closed-source Input mangler that that can translate and customize their Steam controller into an XInput-like controller or Keyboard with state-of-the-art features. These include touchpad to Controller to Mouse, Gyro to Mouse, touchpad to D-pad and mouse, RGB, action layers, and many more.

Steam Input contains two modes: an Xbox 360/Keyboard mode that is game agnostic, and a proprietary mode that encodes Buttons into Actions (e.g., “A” becomes “Jump” when in-game and “Enter” in menus).

This system was first used for the Steam Controller, and is closed-source end-to-end when used with it or with the Steam Deck. Of course, Valve wanted to expand it to other controllers as well while incentivizing developers into having a soft vendor buy in into the protocol of Steam Input.

To kill two birds with one stone, Valve heavily invested into the open-source library SDL to make it support all available controllers in the market, and to also include a Steam Input backend for the Steam Input protocol. This means that when a game developer uses SDL for controller support, they get Steam Input support for free.

SDL is the main controller backend of Steam Input for all controllers other than the Steam Deck.

This means that any controller that works with SDL will work with Steam Input and vice versa, including with its advanced features (e.g., touchpad, gyro, paddles, RGB).

Through perfecting and polishing Steam Input, Valve is years ahead of the competition in terms of controller support, and this support is one of their major moats with SteamOS.

Steam Input remains the only way for a casual user to use gyroscope aiming or touchpads properly, as alternatives such as JoyShockMapper and AntiMicroX require manual configuration, and are hard to use.

In addition, Steam Input has a large library of community configurations (under Valve’s control) for most games in the market. With third party controllers getting support “for free”.

Handhelds in Windows

Meanwhile, other manufacturers wanted to join the handheld market and offer the same advanced controller features that consoles and the Steam Deck have, such as remapping, gyro, and touchpads. Having to use Windows, they were limited to using the XInput API as shown above. Therefore, remapping and any gyro integration and remapping would have to be done in firmware.

Let us take as an example the Legion Go controllers, which were the first to be supported in Handheld Daemon. When using Windows, the controllers work as shown below (where MCU is the controller embedded computer, and EC is the motherboard embedded computer):

The controllers work in Windows without the use of a proprietary driver, as all the interfaces shown are open standards. The same is true in Linux, where only a single patch was required to enable the xpad driver to autodetect the XInput controllers.

The exception is the Vendor interfaces, shown in green, which are proprietary and are used with Lenovo’s software, Legion Space, to provide remapping features, RGB, and power management features.

These interfaces use the WMI standard for TDP, power light controls, and HID for configuring the controllers. Neither WMI nor HID require a kernel driver in Windows, and in the case of Legion Go, they are handled with a vendor DLL called SapientiaUsb.dll, which is shipped with Legion Space.

The ROG Ally uses a similar approach.

For example, let us look into how Gyro Aiming is implemented in Windows. In the Legion Go, the MCU (controller microprocessor) can also do some local translation of the controller gyros and convert them into stick movement. From user feedback, this is not particularly good and up to this day the OEM gyro implementation of the Legion Go remains unusable without replacing Legion Space in Windows.

Asus takes a different approach: their Armory Crate software receives signals from the Screen’s Bosch Inertial Measurement Unit (IMU) and converts them into stick or mouse movement. This stick movement is then back fed through the vendor interface to the MCU, which pretends the joystick or mouse being moved.

Big boy manufacturers can afford the development cost to do this in hardware, albeit not to the quality Steam users are accustomed to. Smaller manufacturers such as GPD, Ayaneo, and OneXPlayer, use a third-party Windows kernel driver to hide the original XInput controller and either replace it with a virtual XInput controller that emulates gyro or a Dualshock 4 controller that contains the raw gyro data. The community Windows application Handheld Companion works similarly.

As part of their ecosystem, all manufacturers, excluding GPD, claim exclusive use of the extra vendor buttons of the device (e.g., the “Xbox” button), which are firmware limited to launching their software. Where that software is the only avenue for remapping in Windows.

Users are then locked into using the manufacturer’s software for remapping and other features, and the controller is not fully usable without it, whether that is Legion Space or Armory Crate.

This is not the fault of manufacturers: they are limited to what they can do by Microsoft when they ship Windows, in the presentation of the OS (see: bloatware), in the APIs and drivers they can use (e.g., XInput), and in what software they can distribute (e.g., Legion Go uses custom AMD Adrenaline drivers that break AMD GPU docks; thanks AMD).

In parallel, vendors do not have the software or validation capacity to maintain and ship a Linux based operating system, forcing them to use Windows, and are trying their best to create a good user experience and a moat through their vendor software.

Enter Handheld Daemon

Let us state the obvious: when handhelds are used in Windows as in the previous section the result is neither intuitive nor user-friendly.

In Linux, there is no Armoury Crate, or Legion Space, so we would have to find an alternative. To that end, it was clear from the beginning that replacing the “Legion Space” box in the previous diagram with e.g., a daemon named Lenovo Legion Daemon (LLD), would result in duplicative work per manufacturer, and it would not solve the fundamental problem users face.

The problem the users want to be fixed is that the controller output needs to be cleaned up and standardized, such that downstream games and remappers (e.g., Steam Input and AntiMicroX) can use the controller without having to know the quirks of each manufacturer.

This is what Handheld Daemon does: it takes all the little vendor devices the manufacturer bundled into their controller for Windows support, and it merges them into a unified target device, which is either an Xbox style controller or a Dualsense controller.

Of course, this sounds simpler than it is, with the Legion Go integration looking like the following:

(When the display motions unit was used for gyro, the figure above was more complicated)

When Handheld Daemon launches, it identifies each controller device using a rich set of criteria (e.g., VID, PID, HID Usages, Evdev Capabilities, name patterns) which are unique. Then, it initializes each device individually, by e.g., muting shortcut keyboards, setting the gyroscope sampling rate, initializing the IIO subsystem buffer, and setting the Ally gamepad to gamepad mode.

Afterwards, it enters a hot loop, where it uses select to wait for new events on every device. Here, custom implementations had to be made to ensure every device has a file descriptor to wait on, because e.g., libusb hidapi does not expose one by default.

Once an event is received, Handheld Daemon wakes up instantly to process it (with some event coalescing to limit the output to the max HZ of the controller), with an average delay of just 0.17ms.

A message passing architecture is used, where devices are classified into “producers” and “consumers”. For example, the Inertial Measurement Unit (IMU) is a producer as it only produces events, the RGB kernel driver is a consumer that receives RGB events, and the gamepad is both, as it sends key events and receives rumble events. Producers with a ready file descriptor are read, and their events are fed to the consumers, with each consumer filtering out the events they are interested in.

In-between the consumers and producers, a multiplexer (i.e., 1500 lines of spaghetti code) preprocesses the events, redirecting e.g., the right gyro to the main controller, making the QAM Xbox + A bind work, converting the d-pad from a hat to buttons, performing the Legion and ROG button swaps, and all the other settings found in the GUI, with settings that are custom per manufacturer.

All of this logic is then wrapped using a graceful error handler. When you detach the controllers of the Legion Go, or turn on mouse mode in GPD devices, or suspend the device, an error is raised, causing Handheld Daemon to clean up after itself and patiently wait for the device to come back. When it sees the device again, it will reconnect to it within 100ms and wrap it, so you do not notice any delay or see the initial controller anywhere.

Since a lot of games break when controllers disconnect, Handheld Daemon also implements controller caching, where it will keep the previous virtual controller for up to 20 seconds in stasis, and re-use it when it wraps the controller again. As part of this, Handheld Daemon spins a thread which keeps sending fake events, as Steam freezes if it does not see Dualsense events for a few seconds, and fake IMU accelerometer data, to block emulators and Steam from calibrating if auto-calibration is enabled.

Of course, this is a brief overview of just the controller feature in Handheld Daemon, but it should give the reader an idea of the problem.

This complexity is hidden from users, with them seeing following:

Then, the users are free to game, while being able to change every controller and power setting from a console-like interface. For custom settings, we provide an overlay. This overlay includes a desktop mode too, unlike SteamOS, as a lot of our users use their devices as their PCs :wink:

What could “official” SteamOS Support look like?

Nevertheless, Handheld Daemon adds a step of unnecessary complexity. It is a compatibility tool first and foremost, much like Proton, trying to bridge the gap between Windows and Steam. With large parts of the functionality being tailored to Steam, not Linux itself. Which brings us to the final part of this article: how could SteamOS support look in these handhelds, and how long will it take?

Valve and manufacturers could collaborate on a controller standard that is in addition to XInput, ensuring that their device operates properly in Linux and in Windows. This firmware could look a lot like a Dualsense controller, which would be my suggestion, but it would nevertheless be custom.

You can guess that developing and validating this firmware would take a minimum of 1-2 years to implement with both parties collaborating heavily, which is why this author doubts SteamOS general release is right around the corner. In fact, given amount of work required for this, it is doubtful that ANY Windows device that exists right now will ever get proper a properly designed firmware for SteamOS/Linux.

Lenovo stated a couple of months ago, for example, that the Legion Go will not have its BIOS officially validated and released for Linux, which is not much effort compared to remaking the firmware, and they seem to have stopped updating the firmware feature-wise. Asus barely made any firmware changes for the Ally X. The Steam Deck firmware itself is an iteration of the Steam Controller firmware, being 6 years in the making.

Valve could drop their standards for what SteamOS support would look like, and omit features such as RGB, paddles, and gyro. However, given that this would lead to a very unfavorable narrative for them (“if Bazzite can do it, why can’t you?”), this is doubtful.

Finally, since Handheld Daemon and Bazzite are open-source, Valve could repurpose them for SteamOS, and we would be happy to help them with that. In which case, the Valve specific quirks would be fixed (needing an extra overlay, glyphs, GPU slider not working), but the experience would largely remain the same.

Given the above, it is safe to say that you will be stuck with us for a while :wink:

Who would have thought that emulating a controller is that difficult anyway? We hope you will join us again in the next article of this series, where we will explore the challenges of adding support for the Ally X, and the tools and infrastructure that made it happen on day 0.

What’s next for Bazzite

Also, dont think there haven’t been any updates for the Ally since the Bazzite 3.6 release. In fact, there have been almost 20 updates to Handheld Daemon since then, and 10 updates in Adjustor (our TDP plugin) which included redesigning the Ally TDP logic completely and sched_ext support, bringing LAVD support right into Steam UI. Even more in Bazzite, including fixing VRR. We are just not that good with changelogs :face_with_hand_over_mouth: (but that will change; see below).

As for what you can look forward to, we are working on adding fingerprint support on the Ally units with libfprint maintainers (the Ally units have 2 manufacturers so Egis only for now, Focal boys like @nicknamenamenick be damned; check with lsusb). After that, we will need to figure out where to shove this support as Steam does not know what a fingerprint is.

In addition, we are working on a big kernel patch that fixes Ally Extreme powersave (on sleep) and makes the Ally wake up instantly from sleep. And from testing, let me tell you, it is very impressive how fast it wakes up. If you do choose powersave, you lose some of that wake-up speed, but you instead gain 2% battery loss over night (wow) and you make that mean suspend light to go away (when not plugged in). Every time I look at that light I swear it is angry at me.

Oh, and that is just for the Ally units of course. We have a bunch of other tricks lined up for everyone else, including auto-generated changelogs and a real update progress bar.

18 Likes

Great writeup and great work! Thanks!

3 Likes

This topic was automatically closed after 13 days. New replies are no longer allowed.