Comment écrire un pilote de périphérique Linux simple?
je dois écrire un pilote de périphérique Linux SPI pour omap4 à partir de zéro. Je connais quelques notions de base sur l'écriture de pilotes de périphériques. Mais, je ne sais pas comment commencer à écrire des pilotes de périphériques spécifiques à une plate-forme à partir de zéro.
j'ai écrit quelques pilotes de base de char, et j'ai pensé que l'écriture du pilote de périphérique SPI serait similaire à celle-ci. Les pilotes Char 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 passe par spi-omap2-mcspi.c code comme référence pour obtenir une idée de commencer à développer pilote SPI à partir de zéro.
mais, je ne vois pas de fonctions comme ouvrir, lire, écrire, etc. Ne sais pas d'où le programme commence.
4 réponses
commencez par écrire un module générique du noyau. Il y a plusieurs endroits pour chercher de l'information, mais j'ai trouvé ce lien très utile. Après avoir passé en revue tous les exemples spécifiés, Vous pouvez commencer à écrire votre propre module de pilote Linux.
s'il vous Plaît noter que vous ne serez pas sortir avec juste copier-coller le code d'exemple et espère que cela va fonctionner, non. L'API du noyau peut parfois changer et les exemples ne fonctionneront pas. Exemple la condition doit être considérée comme un guide sur la façon de faire quelque chose. Selon la version du noyau que vous utilisez, vous devez modifier l'exemple pour travailler.
envisagez D'utiliser autant que vous le pouvez les fonctions fournies par la plate-forme TI, car cela peut vraiment faire beaucoup de travail pour vous, comme demander et activer les horloges, les bus et les alimentations nécessaires. Si je me souviens correctement, vous pouvez utiliser les fonctions pour acquérir des plages d'adresses mappées en mémoire pour un accès direct aux registres. J'ai pour mentionner que j'ai une mauvaise expérience avec les fonctions fournies par TI parce qu'elles ne libèrent pas/nettoient pas correctement toutes les ressources acquises, donc pour certaines ressources j'ai dû appeler d'autres services du noyau pour les libérer pendant le déchargement du module.
Edit 1:
Je ne suis pas tout à fait familier avec L'implémentation de Linux SPI, mais je commencerais par regarder la fonction omap2_mcspi_probe() dans drivers/spi/spi-omap2-mcspi.c fichier. Comme vous pouvez le voir il y, il enregistre ses méthodes sur le pilote Linux master SPI en utilisant cette API: Linux/include/linux/spi / spi.H. Contrairement à char driver, Les principales fonctions sont *_transfer (). Regardez les descriptions des structures dans spi.h fichier pour plus de détails. Aussi, jetez un oeil à ce API de pilote de périphérique alternative, aussi.
je suppose que votre OMAP4 linux utilise l'un des arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi}
device-tree, donc il compile drivers/spi/spi-omap2-mcspi.c
(si vous ne connaissez pas device-tree, lire ce ). Puis:
- la SPI maître pilote est terminé,
- il enregistre (très probablement) avec Linux SPI core framework
drivers/spi/spi.c
, - (probablement) fonctionne très bien sur votre OMAP4.
en fait Vous n'avez pas besoin de soins à propos de la maître pilote pour écrire votre esclave "pilote de périphérique 1519230920" . Comment savoir si spi-omap2-mcspi.c
est un maître conducteur? Il s'appelle spi_register_master()
.
maître SPI, esclave SPI ?
consultez Documentation/spi/spi_summary
. Le doc se réfère à Contrôleur Pilote (maître) et protocole pilotes (esclave). D'après votre description, je comprends que vous voulez écrire un Protocole/pilote de Périphérique .
protocole SPI ?
pour comprendre que, vous avez besoin de votre feuille de données de périphérique d'esclave, il vous dira:
- le en mode SPI , entendu par votre appareil,
- le protocole il attend le bus.
contrairement à i2c, SPI ne définit pas un protocole ou poignée de main, les fabricants de puces SPI doivent définir leur propre. Vérifiez la fiche technique.
SPI mode
de 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).
encore une fois, vérifiez votre feuille de données de périphérique SPI.
un exemple de pilote de périphérique SPI?
pour vous donner un exemple pertinent, je dois connaître votre type de périphérique SPI. Vous comprendriez qu'un pilote SPI flash device driver est différent d'un SPI FPGA pilote de périphérique . Malheureusement, il n'y a pas tant de pilotes SPI que ça. Pour les trouver:
$ cd linux
$ git grep "spi_new_device\|spi_add_device"
je ne sais pas si j'ai bien compris votre question. Comme m-ric l'a fait remarquer, il y a des conducteurs maîtres et des conducteurs esclaves.
habituellement, les pilotes maîtres sont plus liés au matériel, je veux dire, ils manipulent habituellement les registres D'entrées-sorties ou font 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 d'omap4 pour mettre en œuvre 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 est à https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (mérite une vue, mais ont plus de code d'initialisation, pour ALSA, GPIO, paramètres du module). J'ai essayé de mettre à part le code qui traite avec SPI (peut-être que j'ai oublié quelque chose, mais de toute façon, vous devriez obtenir 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 à 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 peut utiliser McSPI, GPIO bit-banged, 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
de sorte qu'un dispositif McSPI master soit créé pour l'installation matérielle omap3 McSPI4.
Espère que ça aide.
Minimal runnable file_operations
exemple
cet exemple n'interagit avec aucun matériel, mais il illustre l'API du noyau plus simple file_operations
avec debugfs.
module du 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:
/q/why-is-the-close-function-is-called-release-in-struct-file-operations-in-the-linux-kernel-38221/"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 de programme de test :
#!/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 devriez aussi écrire un programme C qui s'exécute ces tests s'il n'est pas clair pour vous ce que les appels système sont appelés pour chacune de ces commandes. (ou vous pouvez aussi utiliser strace
et savoir :-)).
L'autre file_operations
sont un peu plus impliqué, voici quelques autres exemples:
démarrer avec des modèles logiciels de matériel simplifié dans les émulateurs
développement matériel réel de l'appareil est "difficile" parce que:
- vous ne pouvez pas toujours obtenir votre main sur un matériel facilement
- matériel Api peut être compliqué
- il est difficile de voir quel est l'état interne du matériel
comme QEMU nous permettent de surmonter toutes ces difficultés, en simulant la simulation matérielle simplifiée en logiciel.
QEMU par exemple , a un dispositif PCI éducatif intégré appelé edu
, que j'ai expliqué plus loin à: comment ajouter un nouveau dispositif dans le code source QEMU? et est un bon moyen de commencer avec les pilotes de périphériques. J'ai fait un pilote simple pour elle disponible ici .
vous pouvez alors mettre des printf ou utiliser GDB sur QEMU comme pour tout autre programme, et voir exactement ce qui se passe.
Il ya aussi un modèle OPAM SPI pour Vous cas d'utilisation spécifique: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c