it-swarm.com.ru

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

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

Я написал несколько базовых драйверов символов и подумал, что написать 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) с нуля.

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

49
Sagar Jain

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

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

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

Изменить 1:

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

54
Nenad Radulovic

Я предполагаю, что ваш 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: режим spi определяет, как данные синхронизируются и поступают. 
 * Это может быть изменено драйвером устройства. 
 * Значение по умолчанию "активный низкий уровень" для выбора микросхемы режим можно переопределить 
 * (указав SPI_CS_HIGH), так же как и "MSB first" по умолчанию для 
 * каждого слова в передаче (указав SPI_LSB_FIRST). 

Опять же, проверьте вашу SPI таблицу данных устройства).

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

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

$ cd linux 
$ git grep "spi_new_device\|spi_add_device"
20
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

Таким образом, главное устройство McSPI создано для аппаратного обеспечения omap3 McSPI4.

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

11
rslemos

file_operations минимальный исполняемый пример

Этот пример не взаимодействует с каким-либо оборудованием, но он иллюстрирует более простое file_operations API ядра с 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/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 :

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