Wie schreibe ich einen einfachen Linux-Gerätetreiber?

Lesezeit: 11 Minuten

Benutzeravatar von Sagar Jain
Sagar Jain

Ich muss einen SPI-Linux-Zeichengerätetreiber für Omap4 von Grund auf neu schreiben. Ich kenne einige Grundlagen zum Schreiben von Gerätetreibern. Aber ich weiß nicht, wie ich anfangen soll, plattformspezifische Gerätetreiber von Grund auf neu zu schreiben.

Ich habe einige grundlegende Zeichentreiber geschrieben und dachte, das Schreiben von SPI-Gerätetreibern wäre ähnlich. Char-Treiber haben eine Struktur file_operations die die im Treiber implementierten Funktionen enthält.

struct file_operations Fops = {
    .read = device_read,
    .write = device_write,
    .ioctl = device_ioctl,
    .open = device_open,
    .release = device_release,  /* a.k.a. close */
};

Jetzt gehe ich durch spi-omap2-mcspi.c Code als Referenz, um eine Idee zu bekommen, um mit der Entwicklung von SPI-Treibern von Grund auf neu zu beginnen.

Aber ich sehe keine Funktionen wie Öffnen, Lesen, Schreiben usw. Ich weiß nicht, wo das Programm beginnt.

  • Nur eine Frage: Warum willst du den SPI-Treiber umschreiben? Ich habe zuvor den OMAP4-SPI-Treiber verwendet und hatte keine Probleme damit.

    – Nils Pipenbrinck

    13. August 2014 um 9:45 Uhr

  • @NilsPipenbrinck: Der Hauptzweck beim Schreiben des Treibers ist das Lernen.

    – Sagar Jain

    13. August 2014 um 10:02 Uhr

  • mögliches Duplikat von stackoverflow.com/questions/22706570/… ?

    – tauseef_CuriousGuy

    3. August 2017 um 9:24 Uhr

  • Sie können die Zeiger und Verweise auf verschiedene Teile des Geräts sehen. Die Treiber „interagieren“ mit Hardware- und Kernel-Tools, sodass strenge „Öffnen“- oder „Schreiben“-Befehle nicht erforderlich sind.

    – ILMostro_7

    21. Oktober 2017 um 4:47 Uhr


  • s/commands/functions/

    – ILMostro_7

    21. Oktober 2017 um 4:56 Uhr

Benutzeravatar von Nenad Radulovic
Nenad Radulović

Beginnen Sie zunächst mit dem Schreiben eines generischen Kernelmoduls. Es gibt mehrere Orte, an denen Sie nach Informationen suchen können, aber ich habe sie gefunden dieser Link sehr nützlich sein. Nachdem Sie alle dort aufgeführten Beispiele durchgegangen sind, können Sie mit dem Schreiben Ihres eigenen Linux-Treibermoduls beginnen.

Bitte beachten Sie, dass Sie nicht damit davonkommen, den Beispielcode einfach zu kopieren und zu hoffen, dass es funktioniert, nein. Die Kernel-API kann sich manchmal ändern und Beispiele funktionieren nicht. Die dort bereitgestellten Beispiele sollten als Anleitung betrachtet werden, wie etwas zu tun ist. Je nach verwendeter Kernel-Version müssen Sie das Beispiel anpassen, damit es funktioniert.

Erwägen Sie, die von der TI-Plattform bereitgestellten Funktionen so oft wie möglich zu verwenden, da dies wirklich eine Menge Arbeit für Sie erledigen kann, wie z. B. das Anfordern und Aktivieren benötigter Takte, Busse und Netzteile. Wenn ich mich richtig erinnere, können Sie die Funktionen verwenden, um speicherabgebildete Adressbereiche für den direkten Zugriff auf Register zu erwerben. Ich muss erwähnen, dass ich schlechte Erfahrungen mit von TI bereitgestellten Funktionen gemacht habe, da sie nicht alle erworbenen Ressourcen ordnungsgemäß freigeben/bereinigen, sodass ich für einige Ressourcen andere Kerneldienste aufrufen musste, um sie während des Entladens des Moduls freizugeben.

Bearbeiten 1:

Ich bin mit der Linux-SPI-Implementierung nicht ganz vertraut, aber ich würde damit beginnen, mir die Funktion omap2_mcspi_probe() in der Datei drivers/spi/spi-omap2-mcspi.c anzusehen. Wie Sie dort sehen können, registriert es seine Methoden beim Linux-Master-SPI-Treiber mit dieser API: Linux/include/linux/spi/spi.h. Im Gegensatz zum char-Treiber sind hier die Hauptfunktionen *_transfer()-Funktionen. Weitere Details finden Sie in den Strukturbeschreibungen in der Datei spi.h. Schauen Sie auch mal rein Dies auch alternative Gerätetreiber-API.

  • Ich habe neuere Versionen von “diesem Link” gefunden (Das Linux-Kernel-Modul-Programmierhandbuch ) hier

    – JohanPI

    17. September 2021 um 12:27 Uhr

Benutzeravatar von m-ric
m-ric

Ich nehme an, Ihr OMAP4-Linux verwendet eines von arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi} Gerätebaum, also kompiliert es drivers/spi/spi-omap2-mcspi.c (Wenn Sie nichts über den Gerätebaum wissen, lesen Sie dies). Dann:

  • der SPI-Master-Treiber ist fertig,
  • es registriert sich (höchstwahrscheinlich) beim Linux-SPI-Core-Framework drivers/spi/spi.c,
  • es funktioniert (wahrscheinlich) gut auf Ihrem OMAP4.

Um die braucht man sich eigentlich nicht zu kümmern Meisterfahrer Ihre zu schreiben Slave-Gerätetreiber. Wie soll ich wissen spi-omap2-mcspi.c ist ein Master-Fahrer? Es ruft spi_register_master().

SPI-Master, SPI-Slave?

Bitte beziehen Sie sich auf Documentation/spi/spi_summary. Das Dokument bezieht sich auf Controller-Treiber (Meister) und Protokolltreiber (Sklave). Ihrer Beschreibung entnehme ich, dass Sie a schreiben wollen Protokoll/Gerätetreiber.

SPI-Protokoll?

Um das zu verstehen, benötigen Sie das Datenblatt Ihres Slave-Geräts, das Ihnen sagen soll:

  • das SPI-Modus von Ihrem Gerät verstanden,
  • das Protokoll es erwartet auf dem Bus.

Im Gegensatz zu i2c definiert SPI kein Protokoll oder Handshake, SPI-Chiphersteller müssen ihre eigenen definieren. Also Datenblatt prüfen.

SPI-Modus

Aus include/linux/spi/spi.h:

 * @mode: The spi mode defines how data is clocked out and in.
 *  This may be changed by the device's driver.
 *  The "active low" default for chipselect mode can be overridden
 *  (by specifying SPI_CS_HIGH) as can the "MSB first" default for
 *  each word in a transfer (by specifying SPI_LSB_FIRST).

Überprüfen Sie erneut das Datenblatt Ihres SPI-Geräts.

Ein Beispiel für einen SPI-Gerätetreiber?

Um Ihnen ein relevantes Beispiel zu geben, muss ich Ihren SPI-Gerätetyp kennen. Sie würden verstehen, dass a SPI-Flash-Gerätetreiber unterscheidet sich von a SPI-FPGA-Gerätetreiber. Leider gibt es nicht so viele SPI-Gerätetreiber. Um sie zu finden:

$ cd linux 
$ git grep "spi_new_device\|spi_add_device"

Benutzeravatar von rslemos
rslemos

Ich weiß nicht, ob ich deine Frage richtig verstanden habe. Wie m-ric betonte, gibt es Master-Treiber und Slave-Treiber.

Normalerweise sind Master-Treiber mehr an die Hardware gebunden, ich meine, sie manipulieren normalerweise IO-Register oder führen einige speicherabgebildete IO durch.

Für einige Architekturen, die bereits vom Linux-Kernel unterstützt werden (wie omap3 und omap4), sind bereits Master-Treiber implementiert (McSPI).

Ich gehe also davon aus, dass Sie diese SPI-Einrichtungen von omap4 verwenden möchten, um einen Slave-Gerätetreiber zu implementieren (Ihr Protokoll, um mit Ihrem externen Gerät über SPI zu kommunizieren).

Ich habe das folgende Beispiel für BeagleBoard-xM (omap3) geschrieben. Der vollständige Code ist unter https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (ansehenswert, aber mehr Initialisierungscode, für ALSA, GPIO, Modulparameter). Ich habe versucht, Code zu unterscheiden, der sich mit SPI befasst (vielleicht habe ich etwas vergessen, aber Sie sollten trotzdem auf die Idee kommen):

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spi/spi.h>

/* MODULE PARAMETERS */
static uint spi_bus = 4;
static uint spi_cs = 0;
static uint spi_speed_hz = 1500000;
static uint spi_bits_per_word = 16;

/* THIS IS WHERE YOUR DEVICE IS CREATED; THROUGH THIS YOU INTERACT WITH YOUR EXTERNAL DEVICE */
static struct spi_device *spi_device;


/* SETUP SPI */

static inline __init int spi_init(void) {
    struct spi_board_info spi_device_info = {
        .modalias = "module name",
        .max_speed_hz = spi_speed_hz,
        .bus_num = spi_bus,
        .chip_select = spi_cs,
        .mode = 0,
    };

    struct spi_master *master;

    int ret;

    // get the master device, given SPI the bus number
    master = spi_busnum_to_master( spi_device_info.bus_num );
    if( !master )
        return -ENODEV;

    // create a new slave device, given the master and device info
    spi_device = spi_new_device( master, &spi_device_info );
    if( !spi_device )
        return -ENODEV;

    spi_device->bits_per_word = spi_bits_per_word;

    ret = spi_setup( spi_device );
    if( ret )
        spi_unregister_device( spi_device );

    return ret;
}

static inline void spi_exit(void) {
    spi_unregister_device( spi_device );
}

So schreiben Sie Daten auf Ihr Gerät:

spi_write( spi_device, &write_data, sizeof write_data );

Der obige Code ist unabhängig von der Implementierung, das heißt, er könnte McSPI, Bit-Bang-GPIO oder jede andere Implementierung eines SPI-Master-Geräts verwenden. Diese Schnittstelle ist beschrieben in linux/spi/spi.h

Damit es in BeagleBoard-XM funktioniert, musste ich Folgendes zur Kernel-Befehlszeile hinzufügen:

omap_mux=mcbsp1_clkr.mcspi4_clk=0x0000,mcbsp1_dx.mcspi4_simo=0x0000,mcbsp1_dr.mcspi4_somi=0x0118,mcbsp1_fsx.mcspi4_cs0=0x0000

Damit wird ein McSPI-Master-Gerät für die omap3-McSPI4-Hardware-Einrichtung erstellt.

Ich hoffe, das hilft.

Ciro Santilli Benutzeravatar von OurBigBook.com
Ciro Santilli OurBigBook.com

file_operations Minimales lauffähiges Beispiel

Dieses Beispiel interagiert nicht mit irgendeiner Hardware, aber es veranschaulicht das einfachere file_operations Kernel-API mit debugfs.

Kernel-Modul fops.c:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h> /* file_operations */
#include <linux/kernel.h> /* min */
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include <uapi/linux/stat.h> /* S_IRUSR */

static struct dentry *debugfs_file;
static char data[] = {'a', 'b', 'c', 'd'};

static int open(struct inode *inode, struct file *filp)
{
    pr_info("open\n");
    return 0;
}

/* @param[in,out] off: gives the initial position into the buffer.
 *      We must increment this by the ammount of bytes read.
 *      Then when userland reads the same file descriptor again,
 *      we start from that point instead.
 * */
static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;

    pr_info("read\n");
    pr_info("len = %zu\n", len);
    pr_info("off = %lld\n", (long long)*off);
    if (sizeof(data) <= *off) {
        ret = 0;
    } else {
        ret = min(len, sizeof(data) - (size_t)*off);
        if (copy_to_user(buf, data + *off, ret)) {
            ret = -EFAULT;
        } else {
            *off += ret;
        }
    }
    pr_info("buf = %.*s\n", (int)len, buf);
    pr_info("ret = %lld\n", (long long)ret);
    return ret;
}

/* Similar to read, but with one notable difference:
 * we must return ENOSPC if the user tries to write more
 * than the size of our buffer. Otherwise, Bash > just
 * keeps trying to write to it infinitely. */
static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;

    pr_info("write\n");
    pr_info("len = %zu\n", len);
    pr_info("off = %lld\n", (long long)*off);
    if (sizeof(data) <= *off) {
        ret = 0;
    } else {
        if (sizeof(data) - (size_t)*off < len) {
            ret = -ENOSPC;
        } else {
            if (copy_from_user(data + *off, buf, len)) {
                ret = -EFAULT;
            } else {
                ret = len;
                pr_info("buf = %.*s\n", (int)len, data + *off);
                *off += ret;
            }
        }
    }
    pr_info("ret = %lld\n", (long long)ret);
    return ret;
}

/*
Called on the last close:
http://stackoverflow.com/questions/11393674/why-is-the-close-function-is-called-release-in-struct-file-operations-in-the-l
*/
static int release(struct inode *inode, struct file *filp)
{
    pr_info("release\n");
    return 0;
}

static loff_t llseek(struct file *filp, loff_t off, int whence)
{
    loff_t newpos;

    pr_info("llseek\n");
    pr_info("off = %lld\n", (long long)off);
    pr_info("whence = %lld\n", (long long)whence);
    switch(whence) {
        case SEEK_SET:
            newpos = off;
            break;
        case SEEK_CUR:
            newpos = filp->f_pos + off;
            break;
        case SEEK_END:
            newpos = sizeof(data) + off;
            break;
        default:
            return -EINVAL;
    }
    if (newpos < 0) return -EINVAL;
    filp->f_pos = newpos;
    pr_info("newpos = %lld\n", (long long)newpos);
    return newpos;
}

static const struct file_operations fops = {
    /* Prevents rmmod while fops are running.
     * Try removing this for poll, which waits a lot. */
    .owner = THIS_MODULE,
    .llseek = llseek,
    .open = open,
    .read = read,
    .release = release,
    .write = write,
};

static int myinit(void)
{
    debugfs_file = debugfs_create_file("lkmc_fops", S_IRUSR | S_IWUSR, NULL, NULL, &fops);
    return 0;
}

static void myexit(void)
{
    debugfs_remove_recursive(debugfs_file);
}

module_init(myinit)
module_exit(myexit)
MODULE_LICENSE("GPL");

Userland-Shell-Testprogramm:

#!/bin/sh

mount -t debugfs none /sys/kernel/debug

insmod /fops.ko
cd /sys/kernel/debug/lkmc_fops

## Basic read.
cat f
# => abcd
# dmesg => open
# dmesg => read
# dmesg => len = [0-9]+
# dmesg => close

## Basic write

printf '01' >f
# dmesg => open
# dmesg => write
# dmesg => len = 1
# dmesg => buf = a
# dmesg => close

cat f
# => 01cd
# dmesg => open
# dmesg => read
# dmesg => len = [0-9]+
# dmesg => close

## ENOSPC
printf '1234' >f
printf '12345' >f
echo "$?"
# => 8
cat f
# => 1234

## seek
printf '1234' >f
printf 'z' | dd bs=1 of=f seek=2
cat f
# => 12z4

Sie sollten auch ein C-Programm schreiben, das diese Tests durchführt, wenn Ihnen nicht klar ist, welche Systemaufrufe für jeden dieser Befehle aufgerufen werden. (oder Sie könnten auch verwenden strace und herausfinden :-)).

Das andere file_operations etwas aufwändiger sind, hier noch ein paar Beispiele:

Beginnen Sie mit Softwaremodellen vereinfachter Hardware in Emulatoren

Die Entwicklung der eigentlichen Gerätehardware ist „schwierig“, weil:

  • Sie können eine bestimmte Hardware nicht immer leicht in die Hand bekommen
  • Hardware-APIs können kompliziert sein
  • Es ist schwer zu erkennen, wie der interne Zustand der Hardware ist

Emulatoren wie QEMU ermöglichen es uns, all diese Schwierigkeiten zu überwinden, indem wir eine vereinfachte Hardwaresimulation in Software simulieren.

QEMU zum Beispiel hat ein eingebautes pädagogisches PCI-Gerät namens edu, die ich weiter erklärt habe unter: Wie füge ich ein neues Gerät im QEMU-Quellcode hinzu? und ist eine gute Möglichkeit, mit Gerätetreibern zu beginnen. Ich habe einen einfachen Treiber dafür gemacht hier verfügbar.

Sie können dann wie bei jedem anderen Programm printfs oder GDB auf QEMU setzen und genau sehen, was vor sich geht.

Es gibt auch ein OPAM-SPI-Modell für Ihren spezifischen Anwendungsfall: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c

1411230cookie-checkWie schreibe ich einen einfachen Linux-Gerätetreiber?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy