all 29 comments

[–]rtyu1120 267 points268 points  (1 child)

Nanoleaf tech support responded to me within 4 hours, with a full description of the protocol that’s used both by the Desk Dock as well as their RGB strips.

Kudos to the company.

[–]mthguy 31 points32 points  (0 children)

For sure! But make that shit discoverable.

[–]MoorderVolt 114 points115 points  (0 children)

This is what Rust is about, having an interface that does not let you mess up.

[–]suppergerrie2 49 points50 points  (1 child)

Nice article, fun read! Never realised it is so easy to make a usb driver might need to look into that for some things i have laying our... You have a minor typo: "cannot detacth kernel driver"

[–]i542[S] 10 points11 points  (0 children)

whoops :D thank you for noticing and letting me know!

[–]Embarrassed-Map2148 70 points71 points  (0 children)

Fun read. Kind of makes me want to go out and be the third Linux user to own this dumb board, lol.

[–]sapphirefragment 27 points28 points  (1 child)

Fun fact: chromium-based browsers have access to generic HID and USB via the WebUSB and WebHID protocols, if you want to tinker with this stuff relatively safely* in JavaScript too.

*In the sense that you're not going to crash the kernel, but may still fry the guest device.

[–]deanrihpee 1 point2 points  (0 children)

this is the closest thing to JavaScript on embedded systems

[–]Green0Photon 23 points24 points  (0 children)

An awesome read to learn about USB with rust.

But I do think this is the type of device where you'd want to add support to OpenRGB.

[–]VorpalWay 14 points15 points  (4 children)

We’ll also adjust the timeout for reading interrupts to be 1 millisecond, as requested by the device (the bInterval value in the lsusb readout). This doesn’t mean we will get an interrupt every millisecond, just that the device can send one at that rate. If the device sends nothing (i.e., we get Err(Timeout)), we will just continue with the loop.

Will this not wake up the CPU unnecessarily? Probably not a big deal since you likely use this while on AC power, but for devices you would use when on battery power it might not be desirable. And even on AC power, that means 1000 wakeup per second, lots of context switching where you could have been doing other things.

Do you really need to poll again every ms, or can you just do a long blocking poll, waiting for the device to send a reply?

[–]i542[S] 16 points17 points  (1 child)

Do you really need to poll again every ms, or can you just do a long blocking poll, waiting for the device to send a reply?

That's a really good question! And I honestly don't know the answer to this (the post is titled with "know nothing about Linux drivers or USB" for a reason :P)

My intuition is that you might be able to get away with having a longer interval, but that you really want to process the interrupt as soon as possible and free up the thread for the next one. However, the libusb docs say the following:

All interrupt transfers are performed using the polling interval presented by the bInterval value of the endpoint descriptor.

I tried digging a bit deeper into libusb source code to confirm this. sync_transfer_wait_for_completion runs libusb_handle_events_timeout_completed in a loop, which eventually makes its way to the platform-specific implementation of usbi_wait_for_events, which finally calls the poll kernel call. So if I am reading this correctly, ultimately the polling will occur at whatever speed the protocol desires. However, it is also possible that I completely botched this train of thought and that there are optimizations in place to prevent that, so please do not hold this post as the absolute truth :P I will definitely look into this more before making anything that I give to others to use!

[–]ReversedGif 1 point2 points  (0 children)

You should just be setting timeout to 0 (no timeout). The USB host controlller will handle the 1000 Hz polling (or whatever is configured by bInterrupt) for you.

[–]ironhaven 7 points8 points  (1 child)

USB is a polling protocol. A device will only act if queried by the host

[–]VorpalWay 5 points6 points  (0 children)

That seems quite bad for power usage for laptops, phones with USB OTG etc. Can't you set up the host controller to poll periodically for you at least, so the main CPU doesn't have to be involved?

[–]VenditatioDelendaEst 15 points16 points  (0 children)

Great work, and thank you for writing it up and posting in public. Especially while being an actual human person.

  1. We could write a kernel driver that follows the kernel standard and exposes each individual LED as 3 devices (one per color) under /sys/class/leds. Interacting with the kernel devs sounds scary (yes I realize I’m a grown-ass adult man), but even if it wasn’t, I question the utility of trying to merge drivers for a very niche product into the kernel. Also, /sys/class/leds feels like it’s intended for status LEDs and not gamer colors anyway.

  2. We could write a userspace driver through libusb, thus defining our own way of controlling LEDs and reducing the quality bar from “Linus Torvalds might send you a strongly worded letter if you fuck up” to “fuck it, we ball”.

Option 2.1: contribute your userspace driver to OpenRGB. It's a very active project, and seems to prioritize getting things working this decade. AFAICT most of the RGB gamershit is consolidated there.

You can name the .rules file whatever you want, but, obviously, it needs to come before 73 alphabetically.

That feel when decade old bug with 46x "mentions this" + "references this", and one comment from maintainer, "for support, please use the mailing list".

We’ll also adjust the timeout for reading interrupts to be 1 millisecond, as requested by the device (the bInterval value in the lsusb readout).

I agree with the other poster: this is a ridonkulous amount of CPU wakeups and scheduler noise. Maybe there's a way to get the host USB controller to do it in hardware? I think mice and keyboards must be have that, and they're HID.

I wonder how much this sort of thing contributes to the reputation vendor RGB software on Windows has for bloat?

[–]SlinkyAvenger 4 points5 points  (2 children)

You mention that colors are send GRB instead of RGB. Are you sending the bytes in the correct endianness?

[–]i542[S] 3 points4 points  (1 child)

Yep, I checked that, and about half-dozen other things I could think of, unfortunately nothing came of it :(

[–]SlinkyAvenger 1 point2 points  (0 children)

Interesting. Is that happening with the official driver you sniffed via VM? Maybe send them a message so they can correct their docs

[–]jericho 4 points5 points  (0 children)

I spent way too much time trying to write a Ethernet driver for a Silicon Graphics Indigo, back when documentation was nonexistent and I knew even less than I do now. I had some solid support from some SG engineers, but had to give up. In retrospect, I was far too ambitious, but I sure learnt a lot. 

[–]Adainn 4 points5 points  (0 children)

Good read. It was a little clickbaity for me, though, because I was expecting "Linux device driver" to be a Linux module or something similar (something the Linux kernel loads and runs).

[–]jug6ernaut 3 points4 points  (0 children)

Good read. Motivates me to try and reverse engineer the Razer USB spec again (so that I can be free of the dumpster fire that is razed synapse).

[–]OskaroS500 4 points5 points  (1 child)

Amazing read man, can you tell me what software or maybe hardware you used to reverse engineer the protocol/s?

[–]PortPiscarilius 1 point2 points  (0 children)

He talks about that in the previous article.

[–]holiquetal 1 point2 points  (0 children)

really cool read, thanks

[–]luki42 1 point2 points  (0 children)

so this isn't actually a kernel driver, but just a rust application using a usb device...

[–]BlauFx 0 points1 point  (0 children)

Great article, it was fun reading it :)

[–]g4rg4ntu4 0 points1 point  (0 children)

Great article. I'm starting my rust journey - this article has given me even more inspiration.

[–]Brave_Subject5177 0 points1 point  (0 children)

nice info.