Anatomie eines Plattformtreibers unter Linux

Seite: 2/2

Anbieter zum Thema

Teil 2: Board-Treiber

Der Board-Treiber muss zunächst das Mainboard anhand der vom BIOS gelieferten DMI-Informatioenn erkennen. Wir bedienen uns hier der bereits eingebaute Mechanismen, die auch das Modul automatisch laden. Hierzu definieren wir eine entsprechende match-table:

/* note: matching works on string prefix, so „apu2" must come before „apu" */
static const struct dmi_system_id apu_gpio_dmi_table[] __initconst = {
    {
        .ident = „apu2",
        .matches = {
            DMI_MATCH(DMI_SYS_VENDOR, „PC Engines"),
            DMI_MATCH(DMI_BOARD_NAME, „APU2")
        },
        .driver_data = (void*)&board_apu2,
    },
    {
        .ident = „apu2",
        .matches = {
            DMI_MATCH(DMI_SYS_VENDOR, „PC Engines"),
            DMI_MATCH(DMI_BOARD_NAME, „apu2")
        },
        .driver_data = (void*)&board_apu2,
    }
    ...
};
MODULE_DEVICE_TABLE(dmi, apu_gpio_dmi_table);

Im Feld 'driver_data' können wir einen Zeiger auf Board-spezifische Daten mitgeben. Das ist z.B. hilfreich, wenn das Modul verschiedene Board-Varianten unterstützt, die unterschiedliche Konfiguration benötigen. Das Macro MODULE_DEVICE_TABLE erzeugt die Metadaten für den Module-Loader, damit das Modul automatisch geladen werden kann, wenn das BIOS die passenden DMI-Informationen meldet.

In der Init-Route des Moduls testen wir, ob das Board wirklich vorhanden ist und holen uns die DMI-Daten:

static int __init apu_gpio_init(void)
{
    int rc;
    const struct dmi_system_id *dmi = dmi_first_match(apu_gpio_dmi_table);

    if (!dmi) {
        pr_err(KBUILD_MODNAME „: failed to detect apu board via dmi\n");
        return -ENODEV; }

    ...

Nun initialisieren wir unseren GPIO-Treiber aus Teil 1 mit der Konfiguration aus obiger DMI-Struktur:

    if (IS_ERR(apu_gpio_pdev = platform_device_register_resndata(
            NULL, „**gpio_amd_fch**", -1, NULL, 0, **dmi->driver_data**, **sizeof(struct amd_fch_gpio_pdata)**))) {
        rc = PTR_ERR(apu_gpio_pdev);
        goto fail;
    }

Anschließend können wir die LED- und Key-Treiber initialisieren und mit den GPIOs verbinden:

    static const struct gpio_led apu2_leds[] = {
        { .name = „apu:green:1", .gpio = GPIO_LED1, .active_low = 1, },
        { .name = „apu:green:2", .gpio = GPIO_LED2, .active_low = 1, },
        { .name = „apu:green:3", .gpio = GPIO_LED3, .active_low = 1, }
    };
    static const struct gpio_led_platform_data apu2_leds_pdata = {
        .num_leds = ARRAY_SIZE(apu2_leds),
        .leds = apu2_leds,
    };

    ...

    if (IS_ERR(apu_leds_pdev = platform_device_register_resndata(
            NULL, „leds-gpio", -1, NULL, 0, &apu2_leds_pdata, sizeof(apu2_leds_pdata)))) {
        rc = PTR_ERR(apu_leds_pdev);
        goto fail;
    }

    ...

    static struct gpio_keys_button apu2_keys_buttons[] = {
        {
            .code = KEY_A,
            .gpio = GPIO_MODESW,
            .active_low = 1,
            .desc = „modeswitch",
            .type = EV_KEY, /* or EV_SW ? */
            .debounce_interval = 10,
            .value = 1,
        }
    };
    static const struct gpio_keys_platform_data apu2_keys_pdata = {
        .buttons = apu2_keys_buttons,
        .nbuttons = ARRAY_SIZE(apu2_keys_buttons),
    .poll_interval = 100,
        .rep = 0,
        .name = „apu2-keys",
    };

    ...

    if (IS_ERR(apu_keys_pdev = platform_device_register_resndata(
            NULL, „gpio-keys-polled", -1, NULL, 0, &apu2_keys_pdata, sizeof(apu2_keys_pdata)))) {
        rc = PTR_ERR(apu_keys_pdev);
        goto fail;
    }
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.

Fazit: So entsteht ein generischer GPIO-Treiber

Wir haben nun mit einfachen Mitteln zunächst einen generischen GPIO-Treiber für den AMD GX-412TC SoC gebaut und anschließend die verschiedenen generischen Treiber board-spezfisch verdrahtet. Das Patchset fügt hier weniger als 500 Codezeilen ein – das meiste davon sind Konfigurations-Daten.

Der Treiber lässt sich zukünftig in den Mainline-Kernel aufnehmen, so dass für zukünftige Projekte hier keine Anpassungen mehr nötig sind, und auch Standard-Distributionen wie z.B. Debian das APU-Board auch out-of-the-Box unterstützen können. Dies entspricht auch der Philosopie von GNU/Linux und gewährleistet langfristige Wartbarkeit mit geringem Aufwand.

*Enrico Weigelt ist Gründer und Inhaber des IT-Servicebüros meTUX.

(ID:45724613)