Jak napisać prosty sterownik urządzenia Linux?

Muszę napisać sterownik urządzenia znakowego SPI Linux dla omap4 od podstaw. Znam kilka podstaw pisania sterowników urządzeń. Ale nie wiem, jak zacząć pisać sterownik urządzenia od zera.

Napisałem kilka podstawowych sterowników char i pomyślałem, że pisanie sterownika urządzenia SPI będzie podobne. Drivery Char mają strukturę file_operations, która zawiera funkcje zaimplementowane w driverze.

struct file_operations Fops = {
    .read = device_read,
    .write = device_write,
    .ioctl = device_ioctl,
    .open = device_open,
    .release = device_release,  /* a.k.a. close */
};
Teraz przechodzę przez spi-omap2-mcspi.c kod jako odniesienie, aby uzyskać pomysł, aby rozpocząć rozwój sterownika SPI od zera.

Ale nie widzę takich funkcji jak open, read, write itp. Nie wiem od czego zaczyna się program.

Author: Sagar Jain, 2014-03-25

4 answers

Najpierw zacznij od napisania generycznego modułu jądra. Jest wiele miejsc do szukania informacji, ale znalazłem ten link za bardzo przydatny. Po przejrzeniu wszystkich podanych tam przykładów możesz zacząć pisać swój własny moduł sterownika Linuksa.

Zauważ, że nie ujdzie ci to na sucho po prostu skopiuj-wklej przykładowy kod i miej nadzieję, że zadziała, nie. API jądra może czasami ulec zmianie i przykłady nie będą działać. Podane tam przykłady należy rozpatrywać jako poradnik jak coś zrobić. W zależności od używanej wersji jądra musisz zmodyfikować przykład, aby działał.

Rozważ korzystanie z funkcji platformy ti jak najwięcej, ponieważ może to naprawdę zrobić wiele pracy dla Ciebie, jak żądanie i włączenie potrzebnych zegarów, autobusów i zasilaczy. Jeśli dobrze pamiętam, możesz użyć funkcji do uzyskania zakresów adresów mapowanych w pamięci, aby uzyskać bezpośredni dostęp do rejestrów. Muszę wspomnieć, że mam złe doświadczenia z TI pod warunkiem funkcje, ponieważ nie zwalniają/nie oczyszczają wszystkich nabytych zasobów, więc dla niektórych zasobów musiałem wywołać inne usługi jądra, aby je zwolnić podczas rozładowywania modułu.

Edytuj 1:

Nie jestem do końca zaznajomiony z implementacją SPI Linuksa, ale zacznę od funkcji omap2_mcspi_probe () w drivers/spi/spi-omap2-mcspi.plik C. Jak widać, rejestruje metody do sterownika Linux master SPI za pomocą tego API: Linux / include/linux/spi / spi.h. W w przeciwieństwie do sterownika char główne funkcje są tutaj funkcje *_transfer (). Spójrz na opisy struktur w spi.plik h W celu uzyskania dalszych informacji. Zobacz też ten alternatywny interfejs API sterownika urządzenia.

 50
Author: Nenad Radulovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-27 21:30:18

Zakładam, że Twój Linux OMAP4 używa jednego z arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi} device-tree, więc kompiluje drivers/spi/spi-omap2-mcspi.c (Jeśli nie wiesz o device-tree, przeczytaj to). Następnie:

  • Sterownik SPI master jest gotowy,
  • to (najprawdopodobniej) rejestruje się z Linux SPI core framework drivers/spi/spi.c,
  • to (prawdopodobnie) działa dobrze na Twoim OMAP4.

Nie musisz przejmować się sterownikiem master driver aby napisać sterownik slave device driver. Skąd mam wiedzieć, że spi-omap2-mcspi.c jest główny kierowca? Nazywa spi_register_master().

SPI master, SPI slave ?

Proszę odnieść się do Documentation/spi/spi_summary. Doc odnosi się do sterownika kontrolera (master) i sterowników protokołu (slave). Z twojego opisu rozumiem, że chcesz napisać sterownik protokołu / urządzenia .

Protokół SPI ?

Aby to zrozumieć, potrzebujesz arkusza danych urządzenia slave, to ci powie:

  • tryb SPI rozumiany przez Twoje urządzenie,
  • the protokół oczekuje w autobusie.
W przeciwieństwie do i2c, SPI nie definiuje protokołu ani uścisku dłoni, producenci układów SPI muszą zdefiniować własne. Sprawdź Arkusz danych.

Tryb SPI

From 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).

Ponownie sprawdź Arkusz danych urządzenia SPI.

Przykładowy sterownik urządzenia SPI?

Aby podać odpowiedni przykład, muszę znać typ urządzenia SPI. Zrozumiałbyś, że sterownik SPI flash device driver jest różni się od sterownika urządzenia SPI FPGA . Niestety nie ma tak wielu sterowników urządzeń SPI. Aby je znaleźć:

$ cd linux 
$ git grep "spi_new_device\|spi_add_device"
 19
Author: m-ric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:32

Nie wiem, czy dobrze zrozumiałem twoje pytanie. Jak zauważył m-ric, istnieją sterowniki master i Slave.

Zazwyczaj sterowniki główne są bardziej związane sprzętowo, to znaczy, Zwykle manipulują rejestrami IO lub robią mapowanie pamięci IO.

Dla niektórych architektur już obsługiwanych przez jądro Linuksa (jak omap3 i omap4) sterowniki master są już zaimplementowane (McSPI).

Więc zakładam, że chcesz użyć tych urządzeń SPI z omap4 do zaimplementowania urządzenia slave sterownik (Twój protokół, do komunikacji z urządzeniem zewnętrznym za pośrednictwem SPI).

Napisałem następujący przykład dla BeagleBoard-xM (omap3). Pełny kod znajduje się pod adresem https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (warto zobaczyć, ale mieć więcej kodu inicjalizacji, dla ALSA, GPIO, parametrów modułu). Starałem się wyodrębnić kod, który dotyczy SPI (może czegoś zapomniałem, ale w każdym razie powinieneś mieć pomysł): {]}

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

Aby zapisać dane do twojego urządzenie:

spi_write( spi_device, &write_data, sizeof write_data );

Powyższy kod jest niezależny od implementacji, to znaczy może używać McSPI, bit-banged GPIO, lub jakiejkolwiek innej implementacji urządzenia głównego SPI. Interfejs ten jest opisany w linux/spi/spi.h

Aby to działało w beagleboard-XM musiałem dodać do wiersza poleceń jądra:

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

Tak, że urządzenie główne McSPI jest tworzone dla obiektu sprzętowego omap3 McSPI4.

Mam nadzieję, że to pomoże.
 11
Author: rslemos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-24 01:32:14

Minimal runnable file_operations przykład

Ten przykład nie wchodzi w interakcję z żadnym sprzętem, ale ilustruje prostsze API jądra file_operations z debugfs.

Moduł jądra 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 test program :

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

Powinieneś również napisać program w języku C, który uruchamia te testy, jeśli nie jest dla Ciebie jasne, jakie wywołania systemowe są wywoływane dla każdego z tych poleceń. (lub możesz również użyć strace i dowiedzieć się :-)).

Inne file_operations są nieco bardziej zaangażowane, oto kilka dalszych przykładów:

Zacznij od modeli oprogramowania uproszczonego sprzętu w emulatorach

[[10]}rzeczywisty rozwój sprzętu jest "trudny", ponieważ:
  • nie zawsze można łatwo zdobyć rękę na danym sprzęcie
  • [[29]}sprzętowe interfejsy API mogą być skomplikowane]}
  • it is hard aby zobaczyć, jaki jest wewnętrzny stan Sprzętu

Emulatory takie jak QEMU pozwalają nam przezwyciężyć wszystkie te trudności, symulując uproszczoną symulację sprzętu w oprogramowaniu.

QEMU na przykład ma wbudowane edukacyjne urządzenie PCI o nazwie edu , co wyjaśniłem dalej na stronie: Jak dodać nowe urządzenie w kodzie źródłowym QEMU?{[18] } i jest dobrym sposobem na rozpoczęcie pracy ze sterownikami urządzeń. Zrobiłem dla niego prosty sterownik dostępny tutaj .

Możesz następnie umieść printf ' y lub użyj GDB na QEMU tak jak w każdym innym programie i zobacz dokładnie, co się dzieje.

Istnieje również model OPAM SPI dla konkretnego przypadku użycia: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c

 4
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-06 13:02:01