:: [DNG] How to stop udev from re-orde…
Top Page
Delete this message
Reply to this message
Author: Rainer Weikusat
Date:  
To: dng
Subject: [DNG] How to stop udev from re-ordering devices
This is published here in the hope that it is useful for someting.

The basic design of udev is similar to that of a forking server: There's
a parent process listening for uevents from the kernel on a netlink
socket which passes these events to worker processes for actual
processing. In case no idle worker process is found, a new one is
forked, up to a configurable limit (=> udevd(8)).

    One could argue if using more than one worker process is
    actually sensible considering that uevent processing happens
    mostly during startup and isn't going to take much time,
    especially as this is a text book example of the simple, obvious
    design one shouldn't be using if good performance is to be
    achieved: The process which read the uevent from the socket is
    already running and the CPU executing it has all the data in its
    cache and kicking this to another CPU is a waste: The process/
    thread which received the event should process it and another
    should be listening for more uevents while this is happening.


A less-than-desirable side-effect of this model is that uevents causing
driver loads may end up loading the drivers in an order different from
the one the uevents came in. This is a problem with the Linux kernel
naming scheme for network devices as that's based on using a driver
prefix (most drivers use eth) plus a running number and a running number
is assigned to a certain driver when it calls register_netdev. This
means if more than one driver needs to be loaded to use all interfaces
in a machine, IOW, "it's a Dell box", any driver might end up claiming
the first eth-number[*].

As it turns out to be, it's actually fairly simple to prevent
this. Drivers are dynamically loaded by the rules in 80-driver-rules,
specifically, by these rules:

# check if the device has already been claimed by a driver
ENV{DRIVER}=="?*", SUBSYSTEM!="input", GOTO="hotplug_driver_loaded"

# load the drivers
ENV{MODALIAS}=="?*",                    RUN+="/sbin/modprobe -b $env{MODALIAS}"


A uevent without DRIVER property but a MODALIAS property will thus
cause a driver load request. Based on this knowledge, it's possible to
assign sequence numbers to driver load events which reflect their
original ordering, with the following, fairly simple code change:

-----------
static inline int drv_req(struct event *event)
{
    struct udev_device *u_dev;


    u_dev = event->dev;
    return
        !udev_device_get_driver(u_dev)
        && udev_device_get_property_value(u_dev, "MODALIAS");
}


static void add_drv_seqno(struct event *event)
{
    static unsigned drv_seqno;
    char buf[32];


    sprintf(buf, "%u", drv_seqno);
    ++drv_seqno;

    
    udev_device_add_property(event->dev, "DRV_SEQNO", buf);
}


static void event_run(struct event *event, bool force)
{
    struct udev_list_node *loop;


    if (drv_req(event)) add_drv_seqno(event);


    udev_list_node_foreach(loop, &worker_list) {
        struct worker *worker = node_to_worker(loop);


------------
(added to udevd.c)

These driver load event sequence numbers can then be used by a driver
loading program to serialize concucrrent incarnations of itself such
that the drivers are loaded in order (I'm using an 89-line C program for
that). Any other events (the overwhelming majority of them) are still
processed in parallell.

[*] Dell is also immensely fond of cross-connecting onboard interfaces,
    ie, in case of two 2-port NICs, connect the one which comes 2nd on
    the bus to the two left-most RJ-45 sockets. I haven't yet
    encountered a case where they also split the ports, ie, connect the
    2nd to sockets 0 and 2 and the first to sockets 1 and 3 but I can't
    imagine why some hardware guy wouldn't want to do that ...