Подтвердить что ты не робот

Как написать простой драйвер устройства Linux?

Мне нужно написать драйвер символьного устройства SPI Linux для omap4 с нуля. Я знаю некоторые основы написания драйверов устройств. Но я не знаю, как начать писать драйвер устройства на платформе с нуля.

Я написал несколько базовых драйверов char, и я думал, что писать драйвер устройства SPI будет похож на него. char драйверы имеют структуру file_operations, которая содержит функции, реализованные в драйвере.

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

Теперь я перехожу через spi-omap2-mcspi.c код в качестве ссылки, чтобы получить представление о том, чтобы начать разработку SPI-драйвера с нуля.

Но я не вижу таких функций, как открытие, чтение, запись и т.д. Не знаю, с чего начинается программа.

4b9b3361

Ответ 1

Сначала запустите, написав общий модуль ядра. Есть несколько мест для поиска информации, но я нашел эту ссылку очень полезной. После того, как вы пройдете все указанные там примеры, вы можете начать писать свой собственный модуль драйверов Linux.

Обратите внимание, что вам не удастся просто скопировать код примера и надеяться, что он сработает, нет. API-интерфейс ядра иногда может меняться, и примеры не будут работать. Приведенные здесь примеры должны рассматриваться как руководство, как сделать что-то. В зависимости от используемой версии ядра вам нужно изменить пример, чтобы работать.

Рассмотрите возможность использования функций платформы TI, насколько это возможно, потому что это может действительно сделать для вас много работы, например, запрашивать и включать необходимые часы, шины и источники питания. Если я правильно помню, вы можете использовать функции для получения диапазонов адресов с памятью для прямого доступа к регистрам. Я должен упомянуть, что у меня плохой опыт работы с функциями, предоставляемыми TI, потому что они не должным образом выпускают/очищают все приобретенные ресурсы, поэтому для некоторых ресурсов мне приходилось вызывать другие службы ядра для их освобождения во время разгрузки модуля.

Изменить 1:

Я не совсем знаком с реализацией Linux SPI, но я бы начал с рассмотрения функции omap2_mcspi_probe() в файле drivers/spi/spi-omap2-mcspi.c. Как вы можете видеть там, он регистрирует его методы для Linux master SPI-драйвера, используя этот API: Linux/include/linux/spi/spi.h. В отличие от драйвера char основными функциями здесь являются функции * _transfer(). Для получения дополнительной информации просмотрите описания структуры в файле spi.h. Кроме того, посмотрите этот альтернативный драйвер драйверов устройств.

Ответ 2

Я предполагаю, что ваш OMAP4 linux использует одно из arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi}, поэтому он компилирует drivers/spi/spi-omap2-mcspi.c (если вы не знаете об устройстве-дереве, читайте это). Тогда:

  • мастер-драйвер SPI сделан,
  • он (скорее всего) регистрируется с базой ядра Linux SPI drivers/spi/spi.c,
  • он (возможно) отлично работает на вашем OMAP4.

На самом деле вам не нужно заботиться о том, чтобы главный драйвер записывал драйвер подчиненного устройства. Как я узнаю, что spi-omap2-mcspi.c является главным драйвером? Он вызывает spi_register_master().

Мастер SPI, ведомый SPI?

Обратитесь к Documentation/spi/spi_summary. Документ ссылается на драйвер драйвера (мастер) и драйверы протокола (ведомый). Из вашего описания я понимаю, что вы хотите написать драйвер протокола/устройства.

Протокол SPI?

Чтобы понять это, вам понадобится техническое описание вашего подчиненного устройства, оно должно сказать вам:

  • режим SPI, понятный вашему устройству,
  • протокол, который он ожидает на шине.

В отличие от i2c, SPI не определяет протокол или рукопожатие, производители SPI-чипов должны определять свои собственные. Поэтому проверьте техническое описание.

Режим SPI

От include/linux/spi/spi.h:

 * @mode: The spi mode defines how data is clocked out and in.
 *  This may be changed by the device 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).

Снова проверьте данные вашего устройства SPI.

Пример драйвера устройства SPI?

Чтобы дать вам соответствующий пример, мне нужно знать ваш тип устройства SPI. Вы понимаете, что драйвер SPI отличается от драйвера устройства FPGA. К сожалению, там не так много драйверов устройств SPI. Чтобы найти их:

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

Ответ 3

Я не знаю, правильно ли понял ваш вопрос. Как указывал m-ric, есть ведущие драйверы и ведомые драйверы.

Обычно главные драйверы более связаны с аппаратными средствами, я имею в виду, что они обычно манипулируют регистрами ввода-вывода или выполняют преобразование IO с памятью.

Для некоторых архитектур, уже поддерживаемых ядром linux (например, omap3 и omap4), уже реализованы мастер-драйверы (McSPI).

Итак, я предполагаю, что вы хотите использовать эти средства SPI для omap4 для внедрения драйвера ведомого устройства (ваш протокол для связи с вашим внешним устройством через SPI).

Я написал следующий пример для BeagleBoard-xM (omap3). Полный код находится в https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (стоит посмотреть, но имеет больше кода инициализации для ALSA, GPIO, параметров модуля). Я попытался установить код, который имеет дело с SPI (возможно, я что-то забыл, но в любом случае вы должны получить идею):

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

Чтобы записать данные на свое устройство:

spi_write( spi_device, &write_data, sizeof write_data );

Вышеприведенный код не зависит от реализации, то есть он может использовать McSPI, бит-бит GPIO или любую другую реализацию ведущего устройства SPI. Этот интерфейс описан в linux/spi/spi.h

Чтобы заставить его работать в BeagleBoard-XM, мне пришлось добавить следующее в командную строку ядра:

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

Итак, для устройства omap3 McSPI4 создается мастер-устройство McSPI.

Надеюсь, что это поможет.

Ответ 4

Минимальный runnable file_operations пример

Этот пример не взаимодействует с каким-либо оборудованием, но он иллюстрирует более простой API-интерфейс ядра file_operations с debugfs.

Модуль ядра 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/info/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:

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

Вы также должны написать программу на языке C, которая запускает эти тесты, если вам не ясно, какие системные вызовы вызывают для каждой из этих команд. (или вы также можете использовать strace и узнать: -)).

Другие file_operations немного более сложны, вот еще несколько примеров:

Начать с программных моделей упрощенного оборудования в эмуляторах

Фактическое развитие аппаратного обеспечения устройства "сложно", потому что:

  • вы не всегда можете легко получить свою руку на данном оборудовании.
  • аппаратные API могут быть сложными
  • трудно понять, что такое внутреннее состояние аппаратного обеспечения.

Эмуляторы, такие как QEMU, позволяют преодолеть все эти трудности, моделируя упрощенное аппаратное моделирование в программном обеспечении.

QEMU, например, имеет встроенное обучающее устройство PCI под названием edu, которое я объяснил далее: Как добавить новое устройство в исходный код QEMU? и это хороший способ начать работу с драйверами устройств. Я сделал для этого простой драйвер который можно найти здесь.

Затем вы можете поместить printf или использовать GDB на QEMU так же, как и для любой другой программы, и точно увидеть, что происходит.

Существует также модель OPAM SPI для конкретного варианта использования: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c