Suchen

Anatomie eines Plattformtreibers unter Linux

| Autor / Redakteur: Enrico Weigelt * / Sebastian Gerstl

Wie entsteht ein generischer GPIO-Treiber unter Linux? Um einen eigenen Plattformtreiber entwickeln zu können, sollte man sich einmal deren grundsätzlichen Aufbau näher betrachten.

Firma zum Thema

Ausschnitt eines GPL-Treibercodes für eine NDAS-Plattform unter Linux.
Ausschnitt eines GPL-Treibercodes für eine NDAS-Plattform unter Linux.
(Bild: Linux Driver Project Code Sample / Linux Driver Project Code Sample / Muudahweed / CC BY-SA 3.0 / CC BY-SA 3.0)

Der Linux-Kernel unterstützt Geräte für die verschiedensten Bus-Systeme, wie z.B. PCI, USB, I2C und viele mehr. Hierzu gibt es jeweils separate Subsysteme, welche neben den generellen Aufgaben wie z.B. Probing, Power-Management, diverse Highlevel-Funktionen/-Protokolle, etc, auch APIs für Backend- (z.B. PCI-Bridges, USB-HCDs, etc) und Frontend-Treiber (Geräte, die an einem bestimmten Bus angeschlossen sind) bieten.

Innerhalb des Linux-Treibermodells (LDM) sind alle Geräte an einem Bus zugeordnet. Nicht alle Geräte sind aber tatsächlich an einem generischen Bus wie z.B. PCI angeschlossen (z.B. in einem System-on-Chip integrierte PCI-Bridges) - die Treiber können hier auf kein generisches Subsystem zurückgreifen. Hierfür gibt es den virtuellen Plattform-Bus, über den diese Geräte verwaltet werden können. Die zugehörigen Treiber werden platform drivers genannt.

Embedded-Linux-Woche: Programm und Anmeldung Vom 18.-21. März 2019 findet in Würzburg wieder die Embedded-Linux-Woche statt. Prämierte Referenten geben dort Seminare für Einsteiger, Fortgeschrittene und Experten zu verschiedenen Themen der Embedded Linux-Programmierung. Mehr Informationen zu Kursen und Anmeldung finden Sie auf www.linux4embedded.de.

Eben jene wollen wir uns in diesem Artikel genauer anschauen. Als Beispiel dient uns hier das PCEngines APUv2-Board - hier benötigen wir Unterstützung für die Front-LEDs und einen Taster.

Bestandsaufnahme

Auf dem APUv2 sitzt ein AMD GX-412TC SoC, der eine Reihe GPIOs mitbringt. An einigen dieser hängen die drei Front-LEDs und der Taster.

Für GPIO-basierte LEDs und Tastaturen bringt Linux bereits generische Treiber mit - allerdings müssen diese zunächst gerätespezifisch konfiguriert bzw. initialisiert werden. Am GPIO-Treiber für den GX-412TC mangelt es jedoch noch.

Teil 1: GPIO-Treiber

Zunächst muss ein Treiber registriert werden, damit er von anderen Komponenten angefordert werden kann:

static struct platform_driver amd_fch_gpio_driver = {
    driver = {
    .    .name = „gpio_amd_fch",
    .},
    ..probe = amd_fch_gpio_probe,
};
...
module_platform_driver(amd_fch_gpio_driver);
MODULE_ALIAS("platform:gpio_amd_fch");

Mittels module_platform_driver() erzeugen wir Modul-Metadaten, anhand derer der Kernel den Treiber automatisch registriert, ohne dass wir noch eigene init-Routine schreiben müssten. MODULE_ALIAS() hilft modprobe

Schauen wir uns nun die probing-Routine an. Diese wird dann vom Kernel aufgerufen, wenn der Treiber initialisiert werden soll. Hier sollte normalerweise auch geprüft werden, ob die entsprechende Hawrdware auch vorhanden ist. In unserem Beispiel haben wir dazu keine Möglichkeit, da die Hawrdware derartiges nicht anbietet - die Konfiguration wird aber ohnehin von einem separaten Board-Treiber (siehe Teil 2) übernommen.

static int amd_fch_gpio_probe(struct platform_device *pdev)
{
    struct amd_fch_gpio_priv *priv;
    struct amd_fch_gpio_pdata *pdata = pdev->dev.platform_data;

    int err;

    if (!pdata) {
        dev_err(&pdev->dev, „no platform_data\n");
        return -ENOENT;
    }

Der Kernel hat bereits eine 'struct platform_device'-Struktur allokiert und einige Felder ausgefüllt.

In pdev->dev.platform_data wird uns ein Zeiger auf eine treiberspezifische Konfiguration durchgereicht, die der Board-Treiber aus Teil 2 mitgibt.

    if (!(priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL))) {
        dev_err(&pdev->dev, „failed to allocate priv struct\n");
        return -ENOMEM;
    }
    priv->pdata = pdata;
    priv->pdev = pdev;

Hier allokieren wir etwas Speicher für private Daten. Das schöne an den devm_*()-Funktionen ist, dass alle zugewiesenen Ressourcen automatisch freigegeben werden, wenn das zugehörige Device wieder freigegeben wird - die früheren Aufräum-Orgien (z.B. wenn beim probe etwas schief geht) erübrigen sich.

    if (IS_ERR(priv->base = devm_ioremap_resource(&pdev->dev, &priv->pdata->res))) {
        dev_err(&pdev->dev, „failed to map iomem\n");
        return -ENXIO;
    }

Mittels devm_ioremap_resource() werden die Hardware-Register in den virtuellen Adressraum des Kernels eingeblendet. Die Parameter hierzu wurden vom Board-Treiber mitgegeben.

Anschließend folgen noch allerlei gerätespezifische Initialisierungen und die Registrierung von Devices in den jeweiligen Subsystemen (wie z.B. gpio, irq, etc). Dies verdient aber eigene Artikel, und soll hier nicht genauer besprochen werden.

Zum Schluss speichern wir noch den Zeiger auf obige Treiber-private Daten und sind damit fertig.

    platform_set_drvdata(pdev, priv);
    return err;
}

Nun haben wir unseren generischen GPIO-Treiber, der dann vom Board-Treiber im Teil 2 konfiguriert wird.

(ID:45724613)