web-dev-qa-db-ja.com

簡単なLinuxデバイスドライバーを作成する方法

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

いくつかの基本的なcharドライバーを作成しましたが、SPIデバイスドライバーの作成もそれに似ていると思いました。文字ドライバは、ドライバに実装された機能を含む構造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 コードを確認します。

しかし、open、read、writeなどの関数は表示されません。プログラムの開始点がわかりません。

49
Sagar Jain

まず、一般的なカーネルモジュールを作成します。情報を検索する場所は複数ありますが、非常に役立つ このリンク が見つかりました。そこで指定されているすべての例を確認したら、独自のLinuxドライバーモジュールの作成を開始できます。

サンプルコードをコピーアンドペーストするだけでは済まないので注意してください。カーネルAPIは変更される場合があり、例は機能しません。そこに提供された例は、何かをする方法のガイドとして見られるべきです。使用しているカーネルのバージョンに応じて、動作するように例を変更する必要があります。

TIプラットフォームが提供する機能を可能な限り使用することを検討してください。必要なクロック、バス、電源の要求や有効化など、本当に多くの作業を行えるためです。正しく思い出せば、関数を使用して、レジスタに直接アクセスするためのメモリマップアドレス範囲を取得できます。 TIが提供する関数は、取得したすべてのリソースを適切に解放/クリーンアップしないため、悪い経験があることに言及する必要があります。

編集1:

Linux SPIの実装に完全には精通していませんが、drivers/spi/spi-omap2-mcspi.cファイルのomap2_mcspi_probe()関数を見ることから始めます。ご覧のように、Linux/include/linux/spi/spi.hというAPIを使用して、LinuxマスターSPIドライバーにメソッドを登録します。 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を参照してください。ドキュメントでは、Controller driver(master)およびProtocol drivers(slave)を参照しています。あなたの説明から、Protocol/Device driverを書きたいと思います。

SPIプロトコル?

それを理解するには、スレーブデバイスのデータシートが必要です。

  • デバイスが理解するSPIモード
  • protocolバスに期待します。

I2cとは異なり、SPIはプロトコルまたはハンドシェイクを定義しません。SPIチップメーカーは独自に定義する必要があります。そのため、データシートを確認してください。

SPIモード

include/linux/spi/spi.hから:

 * @mode:spiモードは、データのクロックアウトおよびクロックイン方法を定義します。
 *これはデバイスのドライバーによって変更される場合があります。
 *チップセレクトの「アクティブロー」デフォルトモードは
 *(SPI_CS_HIGHを指定することにより)オーバーライドすることができます。[MS_first]のデフォルトの
 *転送内の各ワード(SPI_LSB_FIRSTを指定することにより)。

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

例SPIデバイスドライバー?

関連する例を挙げるには、SPIデバイスタイプを知る必要があります。 SPIフラッシュデバイスドライバーSPI FPGAデバイスドライバー。残念ながら、SPIデバイスドライバはそれほど多くありません。それらを見つけるには:

$ cd linux 
$ git grep "spi_new_device\|spi_add_device"
20
m-ric

あなたの質問を正しく理解したかどうかわかりません。 m-ricが指摘したように、マスタードライバーとスレーブドライバーがあります。

通常、マスタードライバーはよりハードウェアにバインドされています。つまり、通常、IOレジスタを操作するか、メモリマップIOを実行します。

Linuxカーネル(omap3やomap4など)で既にサポートされている一部のアーキテクチャでは、マスタードライバーが既に実装されています(McSPI)。

したがって、omap4のこれらのSPI機能を使用して、スレーブデバイスドライバー(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マスターデバイスが作成されます。

お役に立てば幸いです。

11
rslemos

file_operations実行可能な最小限の例

この例はどのハードウェアとも相互作用しませんが、debugfsを使用した簡単なfile_operationsカーネルAPIを示しています。

カーネルモジュール 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");

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

#!/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を配置するか、QEMUでGDBを使用して、何が起こっているかを正確に確認できます。

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