Hardware hotplug events on Linux, the gory details

(arcanenibble.github.io)

96 points | by todsacerdoti 3 days ago

4 comments

  • krasikra 1 hour ago
    Excellent deep dive. The uevent/netlink path is one of those things every Linux developer should understand but few actually trace through.

    One thing worth adding: on embedded systems (ARM SBCs, Jetson boards), hotplug behavior can be surprisingly different from x86. Some device tree overlays don't fire proper uevents, and you end up polling sysfs as a fallback. The kernel's device model assumes a level of firmware cooperation that embedded vendors don't always provide.

    The kobject lifecycle diagram alone makes this worth bookmarking.

  • philips 28 minutes ago
    Sort of relatedly: I bought an inexpensive DDR pad recently that worked on coldplug but not hotplug.

    It turns out the firmware on the pad isn’t quite ready to be polled for USB descriptors right when it is plugged in so you have to put in a little udev hack to suspend it then let it reconnect. At which point it comes back correctly.

    (The manufacturer included a little debugging flyer telling you to plug the device in slowly to work around this issue haha)

    For those interested: https://github.com/batocera-linux/batocera.linux/issues/1547...

  • WaitWaitWha 7 minutes ago
    Love it.

    Since I am a visual learner, here is a sequence diagram that helped me follow it a bit cleaner. (yes, I used the gAI dark magic)

    ```

      sequenceDiagram
          participant HW as Hardware
          participant Kernel as Linux Kernel<br>(USB / driver core / kobject)
          participant NetlinkK as Netlink<br>(NETLINK_KOBJECT_UEVENT<br>group 1 = MONITOR_GROUP_KERNEL)
          participant Udevd as udevd<br>(systemd-udevd)
          participant NetlinkU as Netlink<br>(NETLINK_KOBJECT_UEVENT<br>group 2 = MONITOR_GROUP_UDEV)
          participant App as Userspace Application<br>(libudev or direct netlink listener)
          participant Sysd as systemd<br>(device units, services)
          participant DevFS as /dev<br>(device nodes + symlinks)
      
          HW->>Kernel: Physical insertion (USB plug-in)
      
          Kernel->>Kernel: Detect change via bus/driver<br>(e.g. xhci-hcd → usbcore)
      
          Kernel->>Kernel: Register new device in device model<br>(kobject_add / device_add)
      
          Kernel->>NetlinkK: kobject_uevent_env(ACTION=add, ...)<br>multicast to group 1<br>(raw uevent: null-terminated key=value strings)
      
          NetlinkK->>Udevd: Receive kernel uevent<br>(ACTION=add, SUBSYSTEM=..., DEVPATH=..., etc.)
      
          Note over Udevd: udevd parses uevent
      
          Udevd->>Udevd: Match & apply udev rules<br>(/lib/udev/rules.d/, /etc/udev/rules.d/)
      
          Udevd->>Udevd: Perform actions:<br>• Load firmware<br>• usb_modeswitch<br>• Set permissions<br>• Run programs/scripts
      
          Udevd->>Udevd: Create device node(s)<br>e.g. /dev/bus/usb/001/002
      
          Udevd->>Udevd: Create symlinks<br>e.g. /dev/ttyACM0, /dev/disk/by-id/...
      
          alt Optional: triggers systemd .device unit
              Udevd->>Sysd: Triggers / influences device unit activation
              Sysd->>Sysd: May start dependent services / scopes
          end
      
          Udevd->>Udevd: Build enhanced udev packet:<br>• libudev header ("libudev\0", magic 0xfeedcafe, ...)<br>• MurmurHash2 subsystem/devtype<br>•   64-bit tag Bloom filter<br>• Original + added properties
      
          Udevd->>NetlinkU: Broadcast processed event<br>multicast to group 2<br>(binary format with header + properties)
      
          NetlinkU->>App: Receive udev event packet<br>(via libudev_monitor or raw netlink socket)
      
          App->>App: Parse header, validate magic/credentials<br>Extract properties
      
          App->>App: React to device<br>(open /dev/..., query sysfs, etc.)
      
          Note over DevFS: Device now usable via stable names / permissions
    
    ```
  • robinsonb5 1 hour ago
    I can't help feeling that the old XKCD cartoon [1] about life satisfaction being proportional to the time since last opening xorg.conf could equally apply to udev.

    For instance, I tinker with FPGA boards, and one board in particular presents both a JTAG and serial port over USB. Nothing unusual there, but while most such boards show up as /dev/ttyUSBn, but this one shows up as /dev/ttyACM0. I eventually figured out how to make the JTAG part accessible to the tools I was using, without having to be root, via a udev rule. The serial side was defeating me though - it turned out some kind of modem manager service was messing with the port, and needed to be disabled. OK, job done?

    Nope.

    A few days ago I updated the tools, and now access as a regular user wasn't working any more! It turns out the new version of one particular tool uses libusb, while the old version used rawhid (that last detail is no doubt why I had such trouble getting it to work in the first place) - and as such they require different entries in the udev rule. I'm getting too old for those kinds of side quest, especially now a certain search engine is much less use in solving them.

    (Not naming the tools because I'm not ranting against them - just venting about the frustration caused by the excessive and seemingly opaque complexity. Having got that off my chest, I'll go read the article, in the hope that the complexity becomes a little less opaque!)

    [1] https://xkcd.com/963/