/etc/sysfs.conf ist obsolet

(2011-07-28)

Unter Linux kann man einige Treiber-Einstellungen über sysfs, ein vom Kernel bereitgestelltes Pseudo-Dateisystem, erledigen. Auf meinem ThinkPad kann ich darüber beispielsweise einstellen, wie schnell mein Trackpoint reagieren soll oder ab welchem Füllstand mein Akku geladen werden soll (damit er nicht gleich wieder einen Ladezyklus anfängt, wenn ich ihn 5 Minuten nicht am Strom habe).

Diese Einstellungen nimmt man zur Laufzeit z.B. folgendermaßen vor:

# echo 75 > /sys/devices/platform/smapi/BAT0/start_charge_thresh

Nach einem Neustart sind sie natürlich verloren, denn der Kernel hält sie nur im Speicher. Daher gibt es unter Debian das Paket sysfsutils, welches die Möglichkeit bot, diese Einstellungen in /etc/sysfs.conf zu konfigurieren. Beim Systemstart wurden diese über ein Init-Script dann nach /sys übertragen.

Probleme

Mit diesem Ansatz stimmen mehrere Sachen nicht:

  • Die Einstellungen werden an einem „beliebigen“ Zeitpunkt beim Systemstart gesetzt, nämlich dann, wenn das Initscript an der Reihe ist. Für die oben genannte Batterie-Einstellung ist das möglicherweise schon zu spät, das heißt, der Ladezyklus wurde bereits angefangen.
  • Dadurch, dass es ein Script ist, verzögert es den Systemstart mit systemd (welcher ansonsten keine Shellscripts mehr benutzt). Die Verzögerung ist zwar minimal, aber es geht ums Prinzip… :-)
  • Der Mechanismus ist distributions-spezifisch, was schlecht ist. Generell gibt es auf allen Linux-Distributionen /sys und es ist unnötig, mehrere verschiedene Mechanismen zu haben.
  • Der wichtigste Punkt ist aber, dass die Einstellungen nicht greifen, wenn man neue Geräte anschließt oder angeschlossene Geräte entfernt und wieder anschließt. Das ist bei USB-Geräten beispielsweise relativ üblich, kann aber auch mit Akkus passieren (ich habe zwei).

Die Lösung: udev-Regeln

Besser wäre es also, die Einstellungen dann zu setzen, wenn das Gerät tatsächlich im System auftaucht – und was könnte dafür besser geeignet sein als udev?

Nehmen wir also das Beispiel von oben, mit der Akku-Einstellung. Zunächst suchen wir uns das passende udev-Gerät. Dazu fangen wir mit dem Pfad an, unter dem die entsprechende sysfs-Datei liegt:

# udevadm info -p /sys/devices/platform/smapi/BAT0 -a
device path not found                                         

Das hat nicht geklappt, also probieren wir es eine Ebene höher. Es gilt die Faustregel: Jeder Ordner, der eine uevent-Datei enthält, repräsentiert ein Gerät.

# udevadm info -p /sys/devices/platform/smapi/ -a     
  looking at device '/devices/platform/smapi':
    KERNEL=="smapi"
    SUBSYSTEM=="platform"
    DRIVER=="smapi"
    ATTR{ac_connected}=="1"

  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""

Hierbei werden alle Attribute angezeigt, die man in einer udev-Regel matchen kann. Hierbei werden also nicht alle Attribute angezeigt, es fehlen diejenigen, die man nur beschreiben kann.

Wir legen nun die Datei /etc/udev/rules.d/11-battery.rules mit folgendem Inhalt an:

# start charging as soon as the battery is below 75% capacity
ACTION!="remove",SUBSYSTEM=="platform",DRIVER=="smapi", \
ATTR{BAT0/start_charge_thresh}="75"

Das bedeutet, dass für den smapi-Treiber bei allen Aktionen außer dem Entfernen die Einstellung BAT0/start_charge_thresh auf 75 gesetzt wird.

Wir testen diese Regel nun folgendermaßen:

# udevadm test /sys/devices/platform/smapi

udev_rules_new: rules use 216324 bytes tokens (18027 * 12 bytes), 28809 bytes buffer
udev_rules_new: temporary index used 52580 bytes (2629 * 20 bytes)
udev_device_new_from_syspath: device 0x7f4c810df170 has devpath '/devices/platform/smapi'
udev_device_new_from_syspath: device 0x7f4c810eebe0 has devpath '/devices/platform/smapi'
udev_device_read_db: no db file to read /run/udev/data/+platform:smapi: No such file or directory
udev_rules_apply_to_event: ATTR '/sys/devices/platform/smapi/BAT0/start_charge_thresh'
writing '75' /etc/udev/rules.d/11-battery.rules:6
udev_device_new_from_syspath: device 0x7f4c810ec4e0 has devpath '/devices/platform'
udev_rules_apply_to_event: RUN 'socket:@/org/freedesktop/hal/udev_event' /lib/udev/rules.d/90-hal.rules:2

Dabei achten wir auf die Zeile, die mit udev_rules_apply_to_event: ATTR beginnt: Sie zeigt uns an, dass unsere Regel wohlgeformt ist und die Einstellung gesetzt wurde. Anschließend kann man mit cat /sys/devices/platform/smapi/BAT0/start_charge_thresh nochmal nachschauen und wird feststellen, dass der Wert korrekt gesetzt wurde.

Nun deinstalliert man sysfsutils und freut sich über einen schnelleren Systemstart ;-).

Meine udev-Regeln

$ cat /etc/udev/rules.d/10-trackpoint.rules
ACTION!="remove",SUBSYSTEM=="serio",DRIVER=="psmouse",ATTR{sensitivity}="150",ATTR{speed}="150"

$ cat /etc/udev/rules.d/11-battery.rules   
# start charging as soon as the battery is below 75% capacity
# wait 2 minutes before charging to make battery changes easy

ACTION!="remove",SUBSYSTEM=="platform",DRIVER=="smapi", \
ATTR{BAT0/start_charge_thresh}="75", \
ATTR{BAT0/inhibit_charge_minutes}="2"