Dislikes Dislikes:  0
Ergebnis 1 bis 5 von 5

Thema: OpenBSD Treiberentwicklung Beispiel anhand meines PCF8574 GPIO Treibers

  1. #1

    Registriert seit
    03.07.15
    Danke (erhalten)
    2
    Gefällt mir (erhalten)
    7

    Standard OpenBSD Treiberentwicklung Beispiel anhand meines PCF8574 GPIO Treibers

    Anzeige
    Hallo Community!

    Ich habe vor kurzer Zeit einen GPIO Treiber für den I2C Baustein PCF8574(A) von NXP geschrieben
    und möchte gerne dieses Wissen anhand dieses Treiber weitergeben. Dieses Tutorial soll also eine
    kleine Hilfe für eure eigenen Treiber sein.

    Was Ihr benötigt:
    - Optimal wären zwei Systeme (Ein System zum entwickeln und ein System zum testen)
    - Dokumentation für den Baustein (Falls ein öffentliches Datenblatt nicht verfügbar ist, dann
    könnt Ihr auch die Informationen im Quellcode von anderen freien Betriebssystemen erhalten, was
    natürlich meiner Meinung nach schwerer ist)
    - Quellcode von OpenBSD

    In diesem Beispiel nehme ich meinen Treiber her. Also wir schreiben einen GPIO Treiber. Codefragmente
    davon sind jeweils bei anderen Treibern immer dieselben.

    Die Treiber befinden sich im Quellcode von OpenBSD unter /usr/src/sys/dev. Schaut in diese Treiber
    auch mal rein. Hier findet Ihr auch nützliche Informationen für eure eigenen Treiber. Eine gute Anlaufstelle
    ist folgendes Dokument:

    https://www.openbsd.org/papers/eurob...ce-drivers.pdf

    Wir schreiben einen Treiber für den guten alten I2C Baustein PCF8574. Da dieser Baustein über das I2C Interface
    angesprochen wird, platzieren wir den Treiber unter /usr/src/sys/dev/i2c. Ich habe die Datei vom Treiber
    hier pcf8574.c benannt.

    Das Grundkonstrukt einer Treibers in OpenBSD Treibers, damit er vom Betriebssystem eingebunden werden kann,
    besteht aus folgenden Codefragmenten:

    Code:
    struct pcfgpio_softc {
        struct device             sc_dev;
        i2c_tag_t                sc_tag;
        i2c_addr_t                sc_addr;
        int                        sc_node;
    
        u_int8_t                sc_npins;
    
        struct gpio_chipset_tag    sc_gpio_gc;
        gpio_pin_t                sc_gpio_pins[PCFGPIO_NPINS];
    };
    Das ist die Struktur die wir nachfolgend in unseren Funktionen verwenden. sc_dev (struct device) und
    sc_node ist das Minimum (für den Kernel) was enthalten sein muss. Die anderen Variablen und Strukturen
    sind dementsprechend anzupassen. Das es sich um einen I2C Treiber handelt, definieren wir in dieser Struktur noch
    sc_tag (i2c_tag_t) und die I2C Adresse sc_addr (i2c_addr_t) des Bausteines. Jetzt kommen wir zu den
    nötigen Definitionen für das GPIO Interface. Diese besteht aus den Variablen sc_npins (die Anzahl der PINs die
    angesteuert werden können), sc_gpio_gc ist der gpio controller (struct gpio_chipset_tag) und noch
    sc_gpio_pins (gpio_pin_t). Dieses Array enthält die PIN Information (Status, Input oder Output und
    sonstige Information).

    Code:
    struct cfattach pcfgpio_ca = {
        sizeof(struct pcfgpio_softc), pcfgpio_match, pcfgpio_attach
    };
    Eine Struktur mit der der Kernel den Treiber ansteuert. Es enthält die Größe der vorigen Struktur und dann kommen
    die Funktionszeiger mit der das Gerät erkannt wird (match) und der Funktionszeiger mit der das Gerät eingebunden
    wird (attach).

    Code:
    struct cfdriver pcfgpio_cd = {
        NULL, "pcfgpio", DV_DULL
    };
    Hier wird der Name des Treibers definiert.

    Code:
    int
    pcfgpio_match(struct device *parent, void *match, void *aux)
    {
        struct i2c_attach_args *ia = aux;
    
        if ((strcmp(ia->ia_name, "nxp,pcf8574") == 0) ||
            (strcmp(ia->ia_name, "nxp,pcf8574a") == 0))
            return (1);
        return (0);
    }
    Die Match Funktion. Wie Ihr sehen könnt werden zwei Typen vom PCF8574 unterstützt.
    Beide haben unterschiedliche I2C Adressen.

    Code:
    void
    pcfgpio_attach(struct device *parent, struct device *self, void *aux)
    {
        struct pcfgpio_softc *sc = (struct pcfgpio_softc *)self;
        struct i2c_attach_args *ia = aux;
        struct gpiobus_attach_args gba;
        int i;
    
        sc->sc_tag = ia->ia_tag;
        sc->sc_addr = ia->ia_addr;
        sc->sc_npins = PCFGPIO_NPINS;
        sc->sc_node = *(int *)ia->ia_cookie;
    
        printf("\n");
    
        for (i = 0; i < sc->sc_npins; i++) {
            sc->sc_gpio_pins[i].pin_num = i;
            sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INOUT | 
                GPIO_PIN_OPENDRAIN | GPIO_PIN_INVOUT;
    
            sc->sc_gpio_pins[i].pin_flags = GPIO_PIN_INPUT;
            sc->sc_gpio_pins[i].pin_state = 0;
        }
    
        pcfgpio_write(sc, 0xFF);
    
        sc->sc_gpio_gc.gp_cookie = sc;
        sc->sc_gpio_gc.gp_pin_read = pcfgpio_pin_read;
        sc->sc_gpio_gc.gp_pin_write = pcfgpio_pin_write;
        sc->sc_gpio_gc.gp_pin_ctl = pcfgpio_pin_ctl;
    
        gba.gba_name = "gpio";
        gba.gba_gc = &sc->sc_gpio_gc;
        gba.gba_pins = sc->sc_gpio_pins;
        gba.gba_npins = sc->sc_npins;
        config_found(&sc->sc_dev, &gba, gpiobus_print);
    }
    Die Attach Funktion. Diese Funktion wird für die Initialisierung benötigt. Die for Schleife setzt die Eigenschaften
    der PINs vom GPIO device. Das Statement pcfgpio_write(sc, 0xFF) sorgt dafür, dass im Anfangsstadium die PINs
    als Eingänge definiert sind. Danach werden die Funktionszeiger für den GPIO controller zugewiesen. Anschließend
    werden noch die jeweiligen Variablen gesetzt, damit der Treiber im Userland angesprochen werden kann.

    Die restlichen Funktionen sollten eigentlich selbsterklärend sein. Ich habe den Treiber zwar in einem anderen Thread
    schon gepostet, aber hier nochmal komplett:
    Code:
    /* Driver for the NXP PCF8574(A) Remote 8-bit I/O expander */
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/device.h>
    #include <sys/gpio.h>
    
    #include <dev/i2c/i2cvar.h>
    #include <dev/gpio/gpiovar.h>
    
    #define PCFGPIO_NPINS        8
    
    struct pcfgpio_softc {
        struct device             sc_dev;
        i2c_tag_t                sc_tag;
        i2c_addr_t                sc_addr;
        int                        sc_node;
    
        u_int8_t                sc_npins;
    
        struct gpio_chipset_tag    sc_gpio_gc;
        gpio_pin_t                sc_gpio_pins[PCFGPIO_NPINS];
    };
    
    int pcfgpio_match(struct device *, void *, void *);
    void pcfgpio_attach(struct device *, struct device *, void *);
    uint8_t pcfgpio_read(struct pcfgpio_softc *);
    void pcfgpio_write(struct pcfgpio_softc *, uint8_t);
    int pcfgpio_pin_read(void *, int);
    void pcfgpio_pin_write(void *, int, int);
    void pcfgpio_pin_ctl(void *, int, int);
    
    struct cfattach pcfgpio_ca = {
        sizeof(struct pcfgpio_softc), pcfgpio_match, pcfgpio_attach
    };
    
    struct cfdriver pcfgpio_cd = {
        NULL, "pcfgpio", DV_DULL
    };
    
    int
    pcfgpio_match(struct device *parent, void *match, void *aux)
    {
        struct i2c_attach_args *ia = aux;
    
        if ((strcmp(ia->ia_name, "nxp,pcf8574") == 0) ||
            (strcmp(ia->ia_name, "nxp,pcf8574a") == 0))
            return (1);
        return (0);
    }
    
    void
    pcfgpio_attach(struct device *parent, struct device *self, void *aux)
    {
        struct pcfgpio_softc *sc = (struct pcfgpio_softc *)self;
        struct i2c_attach_args *ia = aux;
        struct gpiobus_attach_args gba;
        int i;
    
        sc->sc_tag = ia->ia_tag;
        sc->sc_addr = ia->ia_addr;
        sc->sc_npins = PCFGPIO_NPINS;
        sc->sc_node = *(int *)ia->ia_cookie;
    
        printf("\n");
    
        for (i = 0; i < sc->sc_npins; i++) {
            sc->sc_gpio_pins[i].pin_num = i;
            sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INOUT | 
                GPIO_PIN_OPENDRAIN | GPIO_PIN_INVOUT;
    
            sc->sc_gpio_pins[i].pin_flags = GPIO_PIN_INPUT;
            sc->sc_gpio_pins[i].pin_state = 0;
        }
    
        pcfgpio_write(sc, 0xFF);
    
        sc->sc_gpio_gc.gp_cookie = sc;
        sc->sc_gpio_gc.gp_pin_read = pcfgpio_pin_read;
        sc->sc_gpio_gc.gp_pin_write = pcfgpio_pin_write;
        sc->sc_gpio_gc.gp_pin_ctl = pcfgpio_pin_ctl;
    
        gba.gba_name = "gpio";
        gba.gba_gc = &sc->sc_gpio_gc;
        gba.gba_pins = sc->sc_gpio_pins;
        gba.gba_npins = sc->sc_npins;
        config_found(&sc->sc_dev, &gba, gpiobus_print);
    }
    
    uint8_t
    pcfgpio_read(struct pcfgpio_softc *sc)
    {
        uint8_t val;
    
        iic_acquire_bus(sc->sc_tag, 0);
        if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
            NULL, 0, &val, sizeof(val), 0)) {
            printf("%s: pcfgpio_read: failed to read\n",
                sc->sc_dev.dv_xname);
            iic_release_bus(sc->sc_tag, 0);
            return (0);
        }
        iic_release_bus(sc->sc_tag, 0);
        return val;
    }
    
    void
    pcfgpio_write(struct pcfgpio_softc *sc, uint8_t val)
    {
        iic_acquire_bus(sc->sc_tag, 0);
        if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
            &val, sizeof val, NULL, 0, 0)) {
            printf("%s: pcfgpio_write: failed to write\n",
                sc->sc_dev.dv_xname);
            iic_release_bus(sc->sc_tag, 0);
            return;
        }
        iic_release_bus(sc->sc_tag, 0);
    }
    
    int
    pcfgpio_pin_read(void *arg, int pin)
    {
        struct pcfgpio_softc *sc = arg;
        uint8_t tmp;
    
        if (pin >= sc->sc_npins)
            return 0;
        if (!ISSET(sc->sc_gpio_pins[pin].pin_flags, GPIO_PIN_INPUT))
            pcfgpio_pin_write(sc, pin, GPIO_PIN_HIGH);
        tmp = pcfgpio_read(sc);
        if (tmp & (1 << pin))
            sc->sc_gpio_pins[pin].pin_state = GPIO_PIN_HIGH;
        else
            sc->sc_gpio_pins[pin].pin_state = GPIO_PIN_LOW;
        return sc->sc_gpio_pins[pin].pin_state;
    }
    
    void
    pcfgpio_pin_write(void *arg, int pin, int val)
    {
        struct pcfgpio_softc *sc = arg;
        int i;
        uint8_t tmp = 0x00;
    
        if (pin >= sc->sc_npins)
            return;
        for (i = 0; i < sc->sc_npins; i++)
            if (sc->sc_gpio_pins[i].pin_state == GPIO_PIN_LOW)
                tmp |= (1 << i);
        if (val == GPIO_PIN_HIGH) {
            tmp &= ~(1 << pin);
            pcfgpio_write(sc, tmp);
            sc->sc_gpio_pins[pin].pin_state = GPIO_PIN_HIGH;
        } else {
            tmp |= 1 << pin;
            pcfgpio_write(sc, tmp);
            sc->sc_gpio_pins[pin].pin_state = GPIO_PIN_LOW;
        }
    }
    
    void
    pcfgpio_pin_ctl(void *arg, int pin, int flags)
    {
        struct pcfgpio_softc *sc = arg;
    
        if (ISSET(flags, GPIO_PIN_INPUT)) {
            pcfgpio_pin_write(sc, pin, GPIO_PIN_HIGH);
            sc->sc_gpio_pins[pin].pin_flags = GPIO_PIN_INPUT;
        } else
            sc->sc_gpio_pins[pin].pin_flags = GPIO_PIN_OUTPUT;
    }
    Damit der Treiber vom Config System vom Kernel erkannt wird und kompiliert werden kann, folgendes in
    der Datei /usr/src/sys/dev/i2c/files.i2c hinzufügen:
    Code:
    device    pcfgpio: gpiobus
    attach    pcfgpio at i2c
    file    dev/i2c/pcf8574.c            pcfgpio
    Da dies mein erster OpenBSD Treiber ist und ich somit noch ein Grünschnabel bin, können in den Erklärungen
    noch falsche Annahmen (bezüglich Kernel) von mir enthalten sein. Sollte jemand diese finden, dann könnt Ihr
    mich gerne darauf hinweisen und korrigieren.

    Ich hoffe euch gefällt mein Tutorial. Freue mich über jedes Feedback!

    Edit:
    Habe gerade bemerkt, dass es ein eigenes Unterforum mit Tutorials gibt. Bitte diesen wenn möglich Thread verschieben.
    Geändert von fork(Agent Smith) (08.08.18 um 21:25 Uhr)
    /* Beware of bugs in the above code;
    I have only proved it correct, not tried it. */


    Original quote by Donald E. Knuth

  2. Danke bitmuncher thanked for this post
    Gefällt mir bitmuncher, Chromatin liked this post
  3. #2
    Moderator Avatar von bitmuncher
    Registriert seit
    30.09.06
    Danke (erhalten)
    159
    Gefällt mir (erhalten)
    1667

    Standard

    Habe gerade bemerkt, dass es ein eigenes Unterforum mit Tutorials gibt. Bitte diesen wenn möglich Thread verschieben.
    Dein Wunsch ist mir Befehl.

    Vielen Dank für deine Mühe.

  4. Danke fork(Agent Smith) thanked for this post
  5. #3

    Registriert seit
    03.07.15
    Danke (erhalten)
    2
    Gefällt mir (erhalten)
    7

    Standard

    Zitat Zitat von bitmuncher Beitrag anzeigen
    Dein Wunsch ist mir Befehl.
    Vielen Dank! Sollte eigentlich heißen: "Bitte diesen Thread in das jeweilige Unterforum verschieben." Hab ich vergessen zu korrigieren.
    Vielen Dank für deine Mühe.
    Kein Problem.
    /* Beware of bugs in the above code;
    I have only proved it correct, not tried it. */


    Original quote by Donald E. Knuth

  6. #4
    Moderator Avatar von Chromatin
    Registriert seit
    30.06.08
    Danke (erhalten)
    33
    Gefällt mir (erhalten)
    847

    Standard

    Super Text, Danke Dir!

    Zur Praktischen Anwendung vielleicht noch:

    Wie stellt sich das Device denn dem OS/User/Coder dar und wie kann es angesprochen werden?
    Wenn ein Gesetz nicht gerecht ist, dann geht die Gerechtigkeit vor dem Gesetz!

    Habo Blog - http://blog.hackerboard.de/

  7. #5

    Registriert seit
    03.07.15
    Danke (erhalten)
    2
    Gefällt mir (erhalten)
    7

    Standard

    Anzeige
    Zitat Zitat von Chromatin Beitrag anzeigen
    [...] Zur Praktischen Anwendung vielleicht noch:

    Wie stellt sich das Device denn dem OS/User/Coder dar und wie kann es angesprochen werden?
    Device Tree:
    Damit der Baustein erkannt wird muss dieser zusätliche Eintrag in den Device Trees stehen:
    Code:
    /dts-v1/;
    /include/ "rk3328-rock64.dts"
    
    &i2c0 {
        status = "okay";
    
        pcf8574: gpio@20 {
            compatible = "nxp,pcf8574";
            status = "okay";
            reg = <0x20>;
            #gpio-cells = <2>;
            gpio-controller;
        };
    };
    Das ist ein Beispiel von meinem Testboard. Die Adresse des Bausteines (reg) und des I2C Interface (&i2c0) dementsprechend Eures
    Boards und Schaltung anpassen.

    Nützliche Erklärung bezüglich Device Trees:
    https://events.static.linuxfound.org...ee-dummies.pdf

    Kernel Konfiguration und Kompilieren:
    Code:
    $ cd /sys/arch/$(machine)/conf
    $ cp GENERIC PCF8574
    Damit der Treiber vom Kernel eingebunden wird, folgendes in der Datei PCF8574, die wir erstellt haben
    hinzufügen (Ihr könnt auch wie Ihr wollt einen anderen Namen für eure Kernel Konfiguration verwenden):
    Code:
    pcfgpio*    at iic?
    gpio*       at pcfgpio?
    Denn Kernel kompilieren:
    Code:
    $ config PCF8574
    $ cd ../compile/PCF8574
    $ make -j4
    Details zum Kompilieren entnehmt Ihr von der man-page von OpenBSD:
    OpenBSD FAQ: Building the System from Source

    Hier ist die dmesg Ausgabe vom Treiber:
    Code:
    pcfgpio0 at iic0 addr 0x20
    gpio0 at pcfgpio0: 8 pins
    Der Treiber wurde erfolgreich eingebunden und kann bentutzt werden.

    Sicherheitslevel
    Damit der Treiber im Userland verwendet werden kann, müsst Ihr den Standard Sicherheitslevel (securelevel) von OpenBSD anpassen.
    In der Standardeinstellung erlaubt der Kernel nicht die Kommunikation mit GPIO Treibern. Auch nicht wenn Ihr als root eingeloggt seid.

    Dazu eine Datei mit den Namen rc.securlevel unter /etc anlegen und folgenden Inhalt hinzufügen:
    Code:
    sysctl kern.securelevel=-1
    Der Sicherheitslevel wird hier auf -1 gesetzt, weil der Kernel beim Booten den Sicherheitslevel um einen Schritt erhöht. Deswegen
    ist nach dem Booten der Sicherheitslevel auf 0 eingestellt. Mit diesem Level kann der GPIO Treiber angesprochen werden.

    Details zum Securelevel entnehmt Ihr von der man-page von OpenBSD:
    securelevel(7) - OpenBSD manual pages

    Ansteuerung mit dem Progamm gpioctl
    FolgenderBefehl schaltet einen Pin ein:
    Code:
    gpioctl /dev/gpio0 0 on
    Hier wird die Gerätedatei /dev/gpio0 angegeben, die wir mit dmesg ermittelt haben. Danach folgt die Pinnummer die gesetzt werden
    soll und der Status.

    Details zu gpioctl entnehmt Ihr von der man-page von OpenBSD:
    gpioctl( - OpenBSD manual pages

    Ansteuerung mit ioctls (natives Progamm):
    Wenn wer ein natives Programm lieber mag, hier ein simples Lauflicht:
    Code:
    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/gpio.h>
    #include <sys/ioctl.h>
    
    int main(int argc, char *argv[])
    {
        int i, fd;
        int ret;
        struct gpio_info ginfo;
        struct gpio_pin_op gop;
        
        if (argc < 2) {
            printf("usage: gpio [DEV]\n");
            return 1;
        }
    
        fd = open(argv[1], O_RDWR);
    
        if (fd == -1) {
            printf("error: couldn't open file (%s)\n", strerror(errno));
            return 1;
        }
    
        ret = ioctl(fd, GPIOINFO, &ginfo);
    
        if (ret == -1) {
            printf("error: ioctl GPIOINFO failed (%s)\n", strerror(errno));
            close(fd);
            return 1;
        }
    
        printf("gpio: Number of pins: %d\n", ginfo.gpio_npins);
    
        while (1) {
            for (i = 0; i < ginfo.gpio_npins; i++) {
                gop.gp_pin = i;
                gop.gp_value = GPIO_PIN_HIGH;
                ret = ioctl(fd, GPIOPINWRITE, &gop);
    
                if (ret == -1) {
                    printf("error: ioctl GPIOPINWRITE failed (%s)\n", strerror(errno));
                    close(fd);
                    return 1;
                }
    
                sleep(1);
                gop.gp_value = GPIO_PIN_LOW;
                ret = ioctl(fd, GPIOPINWRITE, &gop);
    
                if (ret == -1) {
                    printf("error: ioctl GPIOPINWRITE failed (%s)\n", strerror(errno));
                    close(fd);
                    return 1;
                }
            }
        }
    
        close(fd);
        return 0;
    }
    Details zu den ioctls entnehmt Ihr von der man-page von OpenBSD:
    gpio(4) - OpenBSD manual pages
    Geändert von fork(Agent Smith) (09.08.18 um 14:03 Uhr)
    /* Beware of bugs in the above code;
    I have only proved it correct, not tried it. */


    Original quote by Donald E. Knuth

  8. Danke Chromatin thanked for this post
    Gefällt mir Chromatin liked this post

Ähnliche Themen

  1. GPIO Interrupts und poll()
    Von Nimda05 im Forum Code Kitchen
    Antworten: 1
    Letzter Beitrag: 25.02.13, 01:24
  2. Raspberry Pi GPIO - Relay schalten
    Von Joroe im Forum Hardware Probleme
    Antworten: 21
    Letzter Beitrag: 06.02.13, 09:19
  3. Probelm beim Erstellen eines Treibers
    Von Shlyakh im Forum Code Kitchen
    Antworten: 4
    Letzter Beitrag: 01.09.12, 21:49
  4. Aufbau eines Windows Treibers
    Von Cracker im Forum Windows
    Antworten: 2
    Letzter Beitrag: 17.06.08, 22:12
  5. Antworten: 2
    Letzter Beitrag: 08.08.06, 13:28

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •