wake-up-neo.com

Wie schreibe ich einen einfachen Linux-Gerätetreiber?

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

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

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 spi-omap2-mcspi.c als Referenz durch, um eine Idee zu bekommen, mit der Entwicklung des SPI Treiber von Grund auf neu zu beginnen.

Ich sehe jedoch keine Funktionen wie Öffnen, Lesen, Schreiben usw. Ich weiß nicht, wo das Programm startet.

49
Sagar Jain

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

Bitte beachten Sie, dass Sie nicht mit dem Kopieren und Einfügen des Beispielcodes davonkommen und hoffen, dass es funktioniert. Die Kernel-API kann sich manchmal ändern und Beispiele funktionieren nicht. Die dort bereitgestellten Beispiele sollten als Leitfaden für die Vorgehensweise angesehen werden. Abhängig von der verwendeten Kernel-Version müssen Sie das Beispiel ändern, um zu funktionieren.

Ziehen Sie in Betracht, die von der TI-Plattform bereitgestellten Funktionen so weit wie möglich zu verwenden, da dies sehr viel Arbeit für Sie bedeuten kann, z. B. das Anfordern und Aktivieren der erforderlichen Uhren, Busse und Netzteile. Wenn ich mich richtig erinnere, können Sie die Funktionen verwenden, um Adressbereiche mit Speicherzuordnung für den direkten Zugriff auf Register abzurufen. Ich muss erwähnen, dass ich schlechte Erfahrungen mit von TI bereitgestellten Funktionen habe, weil sie nicht alle erworbenen Ressourcen ordnungsgemäß freigeben/bereinigen. Daher musste ich für einige Ressourcen andere Kerneldienste anrufen, um sie während des Modulentladens freizugeben.

Edit 1:

Ich bin mit der Implementierung von Linux SPI nicht ganz vertraut, beginne jedoch mit der Funktion omap2_mcspi_probe () in der Datei drivers/spi/spi-omap2-mcspi.c. Wie Sie dort sehen können, registriert es seine Methoden für den Linux-Master-Treiber SPI unter Verwendung dieser API: Linux/include/linux/spi/spi.h. Im Gegensatz zum char-Treiber sind die Hauptfunktionen hier * _transfer () -Funktionen. Weitere Informationen finden Sie in den Strukturbeschreibungen in der Datei spi.h. Schauen Sie sich auch this alternative Device Driver API an.

54
Nenad Radulovic

Ich gehe davon aus, dass Ihr OMAP4-Linux einen Arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi} - Gerätebaum verwendet, daher kompiliert es drivers/spi/spi-omap2-mcspi.c (Wenn Sie nichts über Gerätebaum wissen, lesen Sie dies ). Dann:

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

Sie müssen sich eigentlich nicht um den Master-Treiber kümmern, um Ihren Slave-Gerätetreiber zu schreiben. Woher weiß ich, dass spi-omap2-mcspi.c Ein Master-Treiber ist? Es ruft spi_register_master() auf.

SPI-Master, SPI Slave?

Bitte beziehen Sie sich auf Documentation/spi/spi_summary. Das Dokument bezieht sich auf Controller-Treiber (Master) und Protokoll-Treiber (Slave). Aus Ihrer Beschreibung geht hervor, dass Sie einen Protokoll-/Gerätetreiber schreiben möchten.

SPI-Protokoll?

Um dies zu verstehen, benötigen Sie Ihr Slave-Gerätedatenblatt, das Ihnen Folgendes mitteilen soll:

  • den SPI-Modus , den Ihr Gerät versteht,
  • das Protokoll , das es auf dem Bus erwartet.

Im Gegensatz zu i2c definiert SPI kein Protokoll oder Handshake, SPI Chips-Hersteller müssen ihre eigenen definieren. Überprüfen Sie daher das Datenblatt.

SPI-Modus

Von include/linux/spi/spi.h:

 * @mode: Der SPI-Modus legt fest, wie Daten aus- und eingelesen werden. 
 * Dies kann vom Treiber des Geräts geändert werden Modus kann außer Kraft gesetzt werden 
 * (durch Angabe von SPI_CS_HIGH), ebenso wie der "MSB first" -Standard für 
 * jedes Wort in einer Übertragung (durch Angabe von SPI_LSB_FIRST). 

Überprüfen Sie erneut Ihr SPI Gerätedatenblatt.

Ein Beispiel SPI Gerätetreiber?

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

$ cd linux 
$ git grep "spi_new_device\|spi_add_device"
20
m-ric

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

In der Regel sind Master-Treiber stärker an die Hardware gebunden. Ich meine, sie manipulieren normalerweise die IO) - Register oder führen einige speicherabgebildete E/A-Vorgänge durch.

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

Ich gehe also davon aus, dass Sie diese SPI) - Funktionen 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 befindet sich unter https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (eine Ansicht wert, aber mehr Initialisierungscode für ALSA, GPIO, Modul Parameter). Ich habe versucht, einen Code zu erstellen, der sich mit SPI) befasst (vielleicht habe ich etwas vergessen, aber du solltest 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, dh er kann McSPI, Bit-Banged-GPIO oder eine andere Implementierung eines SPI= Master-Geräts verwenden. Diese Schnittstelle wird in linux/spi/spi.h

Damit es in BeagleBoard-XM funktioniert, musste ich der Kernel-Befehlszeile Folgendes 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.

Hoffentlich hilft das.

11
rslemos

file_operations minimal ausführbares Beispiel

Dieses Beispiel interagiert nicht mit Hardware, zeigt jedoch 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");

serland 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 ausführt, wenn Ihnen nicht klar ist, welche Systemaufrufe für jeden dieser Befehle aufgerufen werden. (oder Sie könnten auch strace verwenden und herausfinden :-)).

Das andere file_operations sind etwas komplizierter, hier einige weitere Beispiele:

Beginnen Sie mit Softwaremodellen vereinfachter Hardware in Emulatoren

Die tatsächliche Entwicklung der Gerätehardware ist "schwierig", weil:

  • eine bestimmte Hardware ist nicht immer leicht zu handhaben
  • hardware-APIs können kompliziert sein
  • es ist schwer zu erkennen, wie der interne Zustand der Hardware ist

Mit Emulatoren wie QEMU können wir all diese Schwierigkeiten überwinden, indem wir eine vereinfachte Hardwaresimulation in Software simulieren.

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

Sie können dann printfs oder GDB auf QEMU wie für jedes andere Programm setzen und genau sehen, was los ist.

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