web-dev-qa-db-fra.com

Comment écrire un pilote de périphérique Linux simple?

Je dois écrire un SPI) pilote de périphérique de caractère Linux pour omap4. Je connais quelques notions de base pour l'écriture de pilotes de périphérique. Mais, je ne sais pas comment commencer à écrire un pilote de périphérique spécifique à une plateforme .

J'ai écrit quelques pilotes de caractères de base et je pensais qu'écrire SPI lui serait similaire. Les pilotes de caractères ont une structure file_operations qui contient les fonctions implémentées dans le pilote.

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

Maintenant, je traverse le code spi-omap2-mcspi.c comme référence pour se faire une idée de commencer à développer SPI le pilote à partir de rien.

Mais, je ne vois pas les fonctions telles que ouvrir, lire, écrire, etc. Je ne sais pas d'où le programme démarre.

49
Sagar Jain

Commencez par écrire un module de noyau générique. Il existe plusieurs endroits où rechercher des informations, mais j’ai trouvé ce lien très utile. Après avoir parcouru tous les exemples spécifiés ici, vous pouvez commencer à écrire votre propre module de pilote Linux.

Veuillez noter que vous ne pourrez pas vous contenter de copier-coller l'exemple de code et espérer que cela fonctionnera, non. L'API du noyau peut parfois changer et les exemples ne fonctionneront pas. Les exemples fournis devraient être considérés comme un guide pour faire quelque chose. Selon la version du noyau que vous utilisez, vous devez modifier l'exemple pour pouvoir fonctionner.

Envisagez d'utiliser autant que possible les fonctions fournies par la plate-forme TI, car cela peut vraiment vous être très utile, comme demander et activer les horloges, les bus et les alimentations nécessaires. Si je me souviens bien, vous pouvez utiliser les fonctions pour acquérir des plages d’adresses mappées en mémoire pour un accès direct aux registres. Je dois mentionner que j'ai une mauvaise expérience des fonctions fournies par TI, car elles ne libèrent pas/nettoient pas correctement toutes les ressources acquises. C'est pourquoi, pour certaines ressources, j'ai dû appeler d'autres services du noyau pour les libérer lors du déchargement du module.

Éditer 1:

Je ne connais pas parfaitement l'implémentation de Linux SPI), mais je commencerai par examiner la fonction omap2_mcspi_probe () dans le fichier drivers/spi/spi-spap-omc2-mcspi.c. Comme vous pouvez le voir, il enregistre ses méthodes auprès du maître Linux SPI, pilote utilisant cette API: Linux/include/linux/spi/spi.h. Contrairement au pilote char, les fonctions principales sont * _transfer (). Recherchez les descriptions de structure dans le fichier spi.h pour plus de détails. Consultez également this l'API du pilote de périphérique alternatif.

54
Nenad Radulovic

Je suppose que votre OMAP4 linux utilise l’un des arbres de périphériques Arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi}, Il compile donc drivers/spi/spi-omap2-mcspi.c (Si vous ne connaissez pas l’arborescence de périphériques, lisez ceci ). Ensuite:

  • le pilote SPI maître est terminé,
  • il s’inscrit (très probablement) avec Linux SPI cadre principal drivers/spi/spi.c,
  • cela fonctionne (probablement) bien sur votre OMAP4.

En fait, vous n'avez pas besoin de vous soucier du pilote principal pour écrire votre pilote de périphérique esclave. Comment savoir que spi-omap2-mcspi.c Est un pilote principal? Il appelle spi_register_master().

Maître SPI, SPI esclave?

Veuillez vous référer à Documentation/spi/spi_summary. La doc fait référence à Pilote de contrôleur (maître) et Pilotes de protocole (esclave). D'après votre description, vous voudrez peut-être écrire un pilote de protocole/périphérique.

Protocole SPI?

Pour comprendre cela, vous avez besoin de la fiche technique de votre appareil esclave. Elle vous indiquera:

  • le mode SPI compris par votre appareil,
  • le protocole qu'il attend sur le bus.

Contrairement à i2c, SPI ne définit pas de protocole ni de négociation, SPI). Les fabricants de puces doivent définir leurs propres. Vérifiez la fiche technique.

Mode SPI

De include/linux/spi/spi.h:

 * @mode: Le mode spi définit le mode de synchronisation des données. 
 * Cela peut être modifié par le pilote du périphérique. 
 * La valeur par défaut "active low" pour les puces Le mode peut être remplacé par 
 * (en spécifiant SPI_CS_HIGH), de même que la valeur par défaut "MSB en premier" pour 
 * pour chaque mot d'un transfert (en spécifiant SPI_LSB_FIRST). 

Encore une fois, vérifiez votre fiche technique de l'appareil SPI).

Un exemple SPI pilote de périphérique?

Pour vous donner un exemple pertinent, j'ai besoin de connaître votre type de périphérique SPI. Vous comprendriez qu'un pilote de périphérique flash SPI est différent d'un pilote de périphérique SPI FPGA . Malheureusement, il n'y en a pas beaucoup SPI) pilotes de périphérique. Pour les trouver:

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

Je ne sais pas si j'ai bien compris votre question. Comme le souligne m-ric, il existe des pilotes maîtres et des pilotes esclaves.

Habituellement, les pilotes maîtres sont davantage liés au matériel, je veux dire, ils manipulent habituellement IO) enregistre ou effectue des entrées/sorties mappées en mémoire.

Pour certaines architectures déjà supportées par le noyau Linux (comme omap3 et omap4), les pilotes maîtres sont déjà implémentés (McSPI).

Donc, je suppose que vous voulez UTILISER ces installations SPI de omap4 pour implémenter un pilote de périphérique esclave (votre protocole, pour communiquer avec votre périphérique externe via SPI).

J'ai écrit l'exemple suivant pour BeagleBoard-xM (omap3). Le code complet se trouve à https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (mérite une vue, mais comporte davantage de code d'initialisation, pour ALSA, GPIO, module paramètres). J'ai essayé de mettre de côté le code qui traite SPI (j'ai peut-être oublié quelque chose, mais de toute façon, vous devriez avoir l'idée):

#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 );
}

Pour écrire des données sur votre appareil:

spi_write( spi_device, &write_data, sizeof write_data );

Le code ci-dessus est indépendant de l’implémentation, c’est-à-dire qu’il pourrait utiliser McSPI, une interface GPIO bangée, ou toute autre implémentation d’un périphérique maître SPI). Cette interface est décrite dans linux/spi/spi.h

Pour que cela fonctionne dans BeagleBoard-XM, j'ai dû ajouter ce qui suit à la ligne de commande du noyau:

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

Ainsi, un périphérique maître McSPI est créé pour l'installation matérielle omap3 McSPI4.

J'espère que ça t'as aidé.

11
rslemos

file_operations exemple minimal exécutable

Cet exemple n'interagit avec aucun matériel, mais il illustre le file_operations API du noyau avec debugfs.

Module de noyau 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");

programme de test Userland Shell :

#!/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

Vous devez également écrire un programme C qui exécute ces tests si vous ne savez pas exactement quels appels système sont appelés pour chacune de ces commandes. (ou vous pouvez aussi utiliser strace et trouver :-)).

L'autre file_operations sont un peu plus impliqués, voici quelques exemples supplémentaires:

Commencez par les modèles logiciels du matériel simplifié dans les émulateurs

Le développement matériel du périphérique est "difficile" car:

  • vous ne pouvez pas toujours mettre la main sur un matériel donné facilement
  • les API matérielles peuvent être compliquées
  • il est difficile de voir quel est l'état interne du matériel

Des émulateurs comme QEMU nous permettent de surmonter toutes ces difficultés en simulant une simulation matérielle simplifiée dans un logiciel.

QEMU, par exemple, a un périphérique PCI éducatif intégré appelé edu, ce que j’ai expliqué plus loin: Comment ajouter un nouveau périphérique dans le code source QEMU? et est un bon moyen de démarrer avec les pilotes de périphériques. J'ai fait un pilote simple pour cela disponible ici .

Vous pouvez ensuite mettre printf ou utiliser GDB sur QEMU, comme pour tout autre programme, et voir exactement ce qui se passe.

Il existe également un modèle OPAM SPI pour votre cas d'utilisation spécifique: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi. c