[c] シンプルなLinuxデバイスドライバを書くには?



Answers

私はあなたのOMAP4 Linuxがarch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi}デバイスツリーの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'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).

再度、SPIデバイスのデータシートを確認してください。

SPIデバイスドライバの例

関連する例を示すために、私はあなたのSPIデバイスタイプを知る必要があります。 SPIフラッシュデバイスドライバは、 SPI FPGAデバイスドライバとは異なることが理解できます。 残念ながらそこにはあまり多くのSPIデバイスドライバはありません。 それらを見つけるには:

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

omap4用のSPI Linuxキャラクタデバイスドライバをゼロから作成する必要があります。 私はデバイスドライバを書くための基本を知っています。 しかし、プラットフォーム固有のデバイスドライバを一から書き始める方法はわかりません。

私はいくつかの基本的なcharドライバを書いてきましたが、私はSPIデバイスドライバを書くことはそれに似ていると思った。 charドライバは、ドライバに実装されている関数を含むfile_operations構造体を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ドライバの開発を最初から開始するアイディアを得るために、 spi-omap2-mcspi.cコードを参考にしています。

しかし、オープン、リード、ライトなどの機能はわかりません。プログラムの開始場所はわかりません。




最小限の実行可能な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://.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");

ユーザーランドシェルテストプログラム

#!/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にはeduという教育用PCIデバイスが組み込まれています。QEMUのソースコードに新しいデバイスを追加するにはどうすればいいですか? デバイスドライバを使い始めるには良い方法です。 私はここ入手できる簡単なドライバを作った。

他のプログラムと同じように、printfを置くかGDBをQEMUに使うことができ、何が起こっているかを正確に見ることができます。

特定のユースケースのためのOPAM SPIモデルもあります: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c : https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c




Links