如何编写一个简单的Linux设备驱动程序?

时间:2021-10-21 16:54:02

I need to write an SPI Linux character device driver for omap4 from scratch. I know some basics of writing device drivers. But, I don't know how to start writing platform specific device driver from scratch.

我需要从头为omap4编写一个SPI Linux字符设备驱动程序。我知道一些编写设备驱动程序的基础知识。但是,我不知道如何从头开始编写平台特定的设备驱动程序。

I've written some basic char drivers, and I thought writing SPI device driver would be similar to it. Char drivers have a structure file_operations which contains the functions implemented in the driver.

我已经编写了一些基本的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 */
};

Now, I am going through spi-omap2-mcspi.c code as a reference to get an idea to start developing SPI driver from scratch.

现在,我要讲的是spiomap2 -mcspi。c代码作为参考,以获得从头开始开发SPI驱动程序的想法。

But, I don't see functions such as open, read, write etc. Don't know from where the program starts.

但是,我没有看到诸如打开、读取、写入等函数。不知道程序从哪里开始。

4 个解决方案

#1


47  

First start by writing a generic kernel module. There are multiple places to look up for information but I found this link to be very useful. After you have gone through all examples specified there you can start writing your own Linux Driver Module.

首先编写一个通用内核模块。有很多地方可以查找信息,但是我发现这个链接非常有用。在完成所有指定的示例之后,您可以开始编写自己的Linux驱动程序模块。

Please note, that you will not get away with just copy-pasting the example code and hope it will work, no. Kernel API can sometimes change and examples will not work. Examples provided there should be looked at as a guide how to do something. Depending on the kernel version you are using you have to modify the example in order to work.

请注意,您不会仅仅复制粘贴示例代码,并希望它能够工作,不会。内核API有时会改变,示例也不会工作。提供的示例应该被视为如何做某事的指南。根据使用的内核版本,您必须修改示例才能工作。

Consider using TI platform provided functions as much as you can, because that can really do a lot work for you, like requesting and enabling needed clocks, buses and power supplies. If I recall correctly you can use the functions to acquire memory mapped address ranges for direct access to registers. I have to mention that I have bad experience with TI provided functions because they do not properly release/clean-up all acquired resources, so for some resources I had to call other kernel services to release them during module unload.

考虑尽可能多地使用TI平台提供的功能,因为它可以为您做很多工作,比如请求和启用所需的时钟、总线和电源。如果我没记错的话,您可以使用这些函数获取内存映射地址范围,以便直接访问寄存器。我不得不指出,我对TI提供的函数有不好的经验,因为它们不能正确地释放/清理所有已获得的资源,所以对于某些资源,我不得不在模块卸载期间调用其他内核服务来释放它们。

Edit 1:

编辑1:

I'm not entirely familiar with Linux SPI implementation but I would start by looking at omap2_mcspi_probe() function in drivers/spi/spi-omap2-mcspi.c file. As you can see there, it registers it's methods to Linux master SPI driver using this API: Linux/include/linux/spi/spi.h. In contrast to char driver the main functions here are *_transfer() functions. Look up at the struct descriptions in spi.h file for further details. Also, have a look at this alternative device driver API, too.

我对Linux SPI实现不是很熟悉,但是我将从在驱动程序/ SPI /spi-omap -mcspi -mcspi -mcspi -mcspi中的omap2- probe()函数开始。c文件。正如您在那里看到的,它使用这个API向Linux主SPI驱动程序注册它的方法:Linux/include/ Linux/ SPI /spi.h。与char驱动相反,这里的主要函数是*_transfer()函数。查看spi中的结构描述。h文件以了解更多细节。此外,还可以看看这个可选的设备驱动程序API。

#2


18  

I assume your OMAP4 linux uses one of arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi} device-tree, thus it compiles drivers/spi/spi-omap2-mcspi.c (if you don't know about device-tree, read this). Then:

我假设您的OMAP4 linux使用的是arch/arm/boot/dts/{OMAP4 .dtsi,am33xx。dtsi}设备树,因此它编译驱动程序/spi/spi-omap2-mcspi。c(如果您不了解设备树,请阅读本文)。然后:

  • the SPI master driver is done,
  • SPI主驱动完成了,
  • it (most probably) registers with Linux SPI core framework drivers/spi/spi.c,
  • 它(很可能)使用Linux SPI核心框架驱动程序/ SPI /spi.c。
  • it (probably) works fine on your OMAP4.
  • 它(很可能)在你的OMAP4上运行良好。

You actually don't need to care about the master driver to write your slave device driver. How do I know spi-omap2-mcspi.c is a master driver? It calls spi_register_master().

实际上,您不需要关心主驱动程序来编写从设备驱动程序。我怎么知道spi-omap -mcspi。c是一个熟练的司机?它调用spi_register_master()。

SPI master, SPI slave ?

Please refer to Documentation/spi/spi_summary. The doc refers to Controller driver (master) and Protocol drivers (slave). From your description, I understand you want to write a Protocol/Device driver.

请参考文档/ spi / spi_summary。doc指的是控制器驱动程序(master)和协议驱动程序(slave)。根据您的描述,我理解您想要编写一个协议/设备驱动程序。

SPI protocol ?

To understand that, you need your slave device datasheet, it shall tell you:

要理解这一点,你需要你的从设备数据表,它将告诉你:

  • the SPI mode understood by your device,
  • 你的设备能理解SPI模式,
  • the protocol it expects on the bus.
  • 它期望在总线上的协议。

Contrary to i2c, SPI does not define a protocol or handshake, SPI chips manufacturers have to define their own. So check the datasheet.

与i2c相反,SPI没有定义协议或握手,SPI芯片制造商必须定义他们自己的。所以检查数据表。

SPI mode

From include/linux/spi/spi.h:

从包括/ 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).

Again, check your SPI device datasheet.

再次,检查SPI设备数据表。

An example SPI device driver?

To give you a relevant example, I need to know your SPI device type. You would understand that a SPI flash device driver is different from a SPI FPGA device driver. Unfortunately there are not so many SPI device drivers out there. To find them:

为了给你一个相关的例子,我需要知道你的SPI设备类型。您将理解SPI闪存设备驱动程序与SPI FPGA设备驱动程序是不同的。不幸的是,这里没有那么多SPI设备驱动程序。找到他们。

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

#3


9  

I don't know if I understood your question correctly. As m-ric pointed out, there are master drivers and slave drivers.

我不知道我是否理解对了你的问题。m-ric指出,有主驱动和从驱动。

Usually master drivers are more hardware bound, I mean, they usually manipulate IO registers or do some memory mapped IO.

通常主驱动程序是硬件绑定的,我的意思是,它们通常操作IO寄存器或做一些内存映射IO。

For some architectures already supported by linux kernel (like omap3 and omap4) master drivers are already implemented (McSPI).

对于一些linux内核已经支持的体系结构(如omap3和omap4),主驱动程序已经实现(McSPI)。

So I assume you want to USE those SPI facilities of omap4 to implement a slave device driver (your protocol, to communicate with your external device through SPI).

因此,我假设您希望使用omap4的SPI设施来实现从设备驱动程序(您的协议,通过SPI与外部设备通信)。

I've written the following example for BeagleBoard-xM (omap3). The full code is at https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (worth a view, but have more initialisation code, for ALSA, GPIO, module parameters). I've tried to set apart code that deals with SPI (maybe I forgot something, but anyway you should get the idea):

我已经为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 );
}

To write data to your device:

将数据写入设备:

spi_write( spi_device, &write_data, sizeof write_data );

The above code is independent of implementation, that is, it could use McSPI, bit-banged GPIO, or any other implementation of an SPI master device. This interface is described in linux/spi/spi.h

上述代码独立于实现,即可以使用McSPI、位绑定GPIO或SPI主设备的任何其他实现。这个接口在linux/spi/spi.h中描述

To make it work in BeagleBoard-XM I had to add the following to the kernel command line:

为了使它在BeagleBoard-XM中工作,我必须在内核命令行中添加以下内容:

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

So that an McSPI master device is created for omap3 McSPI4 hardware facility.

为omap3 McSPI4硬件设备创建一个McSPI主设备。

Hope that helps.

希望有帮助。

#4


3  

Minimal runnable file_operations example

最小的runnable file_operations例子

This example does not interact with any hardware, but it illustrates the simpler file_operations kernel API with debugfs.

这个示例不与任何硬件交互,但它演示了带有debugfs的更简单的file_operations内核API。

Kernel module fops.c:

内核模块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");

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

You should also write a C program that runs those tests if it is not clear to you what system calls are being called for each of those commands. (or you could also use strace and find out :-)).

您还应该编写一个运行这些测试的C程序,如果您不清楚每个命令调用的系统调用是什么。(或者你也可以使用strace来查找:-)

The other file_operations are a bit more involved, here are some further examples:

其他的file_operations则更复杂一些,下面是一些进一步的示例:

Start with software models of simplified hardware in emulators

从仿真器中简化硬件的软件模型开始

Actual device hardware development is "hard" because:

实际设备硬件开发是“硬”的,因为:

  • you can't always get your hand on a given hardware easily
  • 你不能总是轻易地把你的手放在一个给定的硬件上。
  • hardware APIs may be complicated
  • 硬件api可能很复杂。
  • it is hard to see what is the internal state of the hardware
  • 很难看出硬件的内部状态是什么

Emulators like QEMU allow us to overcome all those difficulties, by simulating simplified hardware simulation in software.

像QEMU这样的仿真器可以通过在软件中模拟简化的硬件模拟来克服所有这些困难。

QEMU for example, has a built-in educational PCI device called edu, which I explained further at: How to add a new device in QEMU source code? and is a good way to get started with device drivers. I've made a simple driver for it available here.

例如,QEMU有一个内置的教育PCI设备,名为edu,我在下面进一步解释:如何在QEMU源代码中添加新设备?这是开始使用设备驱动程序的好方法。我为它做了一个简单的驱动程序。

You can then put printf's or use GDB on QEMU just as for any other program, and see exactly what is going on.

然后,您可以在QEMU上放置printf或使用GDB,就像对任何其他程序一样,并确切地看到发生了什么。

There is also an OPAM SPI model for you specific use case: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c

这里还有一个OPAM SPI模型,用于特定的用例:https://github.com/qemu/qemu/v2.7.0/hw/ssi/omap_spi.c。

#1


47  

First start by writing a generic kernel module. There are multiple places to look up for information but I found this link to be very useful. After you have gone through all examples specified there you can start writing your own Linux Driver Module.

首先编写一个通用内核模块。有很多地方可以查找信息,但是我发现这个链接非常有用。在完成所有指定的示例之后,您可以开始编写自己的Linux驱动程序模块。

Please note, that you will not get away with just copy-pasting the example code and hope it will work, no. Kernel API can sometimes change and examples will not work. Examples provided there should be looked at as a guide how to do something. Depending on the kernel version you are using you have to modify the example in order to work.

请注意,您不会仅仅复制粘贴示例代码,并希望它能够工作,不会。内核API有时会改变,示例也不会工作。提供的示例应该被视为如何做某事的指南。根据使用的内核版本,您必须修改示例才能工作。

Consider using TI platform provided functions as much as you can, because that can really do a lot work for you, like requesting and enabling needed clocks, buses and power supplies. If I recall correctly you can use the functions to acquire memory mapped address ranges for direct access to registers. I have to mention that I have bad experience with TI provided functions because they do not properly release/clean-up all acquired resources, so for some resources I had to call other kernel services to release them during module unload.

考虑尽可能多地使用TI平台提供的功能,因为它可以为您做很多工作,比如请求和启用所需的时钟、总线和电源。如果我没记错的话,您可以使用这些函数获取内存映射地址范围,以便直接访问寄存器。我不得不指出,我对TI提供的函数有不好的经验,因为它们不能正确地释放/清理所有已获得的资源,所以对于某些资源,我不得不在模块卸载期间调用其他内核服务来释放它们。

Edit 1:

编辑1:

I'm not entirely familiar with Linux SPI implementation but I would start by looking at omap2_mcspi_probe() function in drivers/spi/spi-omap2-mcspi.c file. As you can see there, it registers it's methods to Linux master SPI driver using this API: Linux/include/linux/spi/spi.h. In contrast to char driver the main functions here are *_transfer() functions. Look up at the struct descriptions in spi.h file for further details. Also, have a look at this alternative device driver API, too.

我对Linux SPI实现不是很熟悉,但是我将从在驱动程序/ SPI /spi-omap -mcspi -mcspi -mcspi -mcspi中的omap2- probe()函数开始。c文件。正如您在那里看到的,它使用这个API向Linux主SPI驱动程序注册它的方法:Linux/include/ Linux/ SPI /spi.h。与char驱动相反,这里的主要函数是*_transfer()函数。查看spi中的结构描述。h文件以了解更多细节。此外,还可以看看这个可选的设备驱动程序API。

#2


18  

I assume your OMAP4 linux uses one of arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi} device-tree, thus it compiles drivers/spi/spi-omap2-mcspi.c (if you don't know about device-tree, read this). Then:

我假设您的OMAP4 linux使用的是arch/arm/boot/dts/{OMAP4 .dtsi,am33xx。dtsi}设备树,因此它编译驱动程序/spi/spi-omap2-mcspi。c(如果您不了解设备树,请阅读本文)。然后:

  • the SPI master driver is done,
  • SPI主驱动完成了,
  • it (most probably) registers with Linux SPI core framework drivers/spi/spi.c,
  • 它(很可能)使用Linux SPI核心框架驱动程序/ SPI /spi.c。
  • it (probably) works fine on your OMAP4.
  • 它(很可能)在你的OMAP4上运行良好。

You actually don't need to care about the master driver to write your slave device driver. How do I know spi-omap2-mcspi.c is a master driver? It calls spi_register_master().

实际上,您不需要关心主驱动程序来编写从设备驱动程序。我怎么知道spi-omap -mcspi。c是一个熟练的司机?它调用spi_register_master()。

SPI master, SPI slave ?

Please refer to Documentation/spi/spi_summary. The doc refers to Controller driver (master) and Protocol drivers (slave). From your description, I understand you want to write a Protocol/Device driver.

请参考文档/ spi / spi_summary。doc指的是控制器驱动程序(master)和协议驱动程序(slave)。根据您的描述,我理解您想要编写一个协议/设备驱动程序。

SPI protocol ?

To understand that, you need your slave device datasheet, it shall tell you:

要理解这一点,你需要你的从设备数据表,它将告诉你:

  • the SPI mode understood by your device,
  • 你的设备能理解SPI模式,
  • the protocol it expects on the bus.
  • 它期望在总线上的协议。

Contrary to i2c, SPI does not define a protocol or handshake, SPI chips manufacturers have to define their own. So check the datasheet.

与i2c相反,SPI没有定义协议或握手,SPI芯片制造商必须定义他们自己的。所以检查数据表。

SPI mode

From include/linux/spi/spi.h:

从包括/ 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).

Again, check your SPI device datasheet.

再次,检查SPI设备数据表。

An example SPI device driver?

To give you a relevant example, I need to know your SPI device type. You would understand that a SPI flash device driver is different from a SPI FPGA device driver. Unfortunately there are not so many SPI device drivers out there. To find them:

为了给你一个相关的例子,我需要知道你的SPI设备类型。您将理解SPI闪存设备驱动程序与SPI FPGA设备驱动程序是不同的。不幸的是,这里没有那么多SPI设备驱动程序。找到他们。

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

#3


9  

I don't know if I understood your question correctly. As m-ric pointed out, there are master drivers and slave drivers.

我不知道我是否理解对了你的问题。m-ric指出,有主驱动和从驱动。

Usually master drivers are more hardware bound, I mean, they usually manipulate IO registers or do some memory mapped IO.

通常主驱动程序是硬件绑定的,我的意思是,它们通常操作IO寄存器或做一些内存映射IO。

For some architectures already supported by linux kernel (like omap3 and omap4) master drivers are already implemented (McSPI).

对于一些linux内核已经支持的体系结构(如omap3和omap4),主驱动程序已经实现(McSPI)。

So I assume you want to USE those SPI facilities of omap4 to implement a slave device driver (your protocol, to communicate with your external device through SPI).

因此,我假设您希望使用omap4的SPI设施来实现从设备驱动程序(您的协议,通过SPI与外部设备通信)。

I've written the following example for BeagleBoard-xM (omap3). The full code is at https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c (worth a view, but have more initialisation code, for ALSA, GPIO, module parameters). I've tried to set apart code that deals with SPI (maybe I forgot something, but anyway you should get the idea):

我已经为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 );
}

To write data to your device:

将数据写入设备:

spi_write( spi_device, &write_data, sizeof write_data );

The above code is independent of implementation, that is, it could use McSPI, bit-banged GPIO, or any other implementation of an SPI master device. This interface is described in linux/spi/spi.h

上述代码独立于实现,即可以使用McSPI、位绑定GPIO或SPI主设备的任何其他实现。这个接口在linux/spi/spi.h中描述

To make it work in BeagleBoard-XM I had to add the following to the kernel command line:

为了使它在BeagleBoard-XM中工作,我必须在内核命令行中添加以下内容:

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

So that an McSPI master device is created for omap3 McSPI4 hardware facility.

为omap3 McSPI4硬件设备创建一个McSPI主设备。

Hope that helps.

希望有帮助。

#4


3  

Minimal runnable file_operations example

最小的runnable file_operations例子

This example does not interact with any hardware, but it illustrates the simpler file_operations kernel API with debugfs.

这个示例不与任何硬件交互,但它演示了带有debugfs的更简单的file_operations内核API。

Kernel module fops.c:

内核模块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");

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

You should also write a C program that runs those tests if it is not clear to you what system calls are being called for each of those commands. (or you could also use strace and find out :-)).

您还应该编写一个运行这些测试的C程序,如果您不清楚每个命令调用的系统调用是什么。(或者你也可以使用strace来查找:-)

The other file_operations are a bit more involved, here are some further examples:

其他的file_operations则更复杂一些,下面是一些进一步的示例:

Start with software models of simplified hardware in emulators

从仿真器中简化硬件的软件模型开始

Actual device hardware development is "hard" because:

实际设备硬件开发是“硬”的,因为:

  • you can't always get your hand on a given hardware easily
  • 你不能总是轻易地把你的手放在一个给定的硬件上。
  • hardware APIs may be complicated
  • 硬件api可能很复杂。
  • it is hard to see what is the internal state of the hardware
  • 很难看出硬件的内部状态是什么

Emulators like QEMU allow us to overcome all those difficulties, by simulating simplified hardware simulation in software.

像QEMU这样的仿真器可以通过在软件中模拟简化的硬件模拟来克服所有这些困难。

QEMU for example, has a built-in educational PCI device called edu, which I explained further at: How to add a new device in QEMU source code? and is a good way to get started with device drivers. I've made a simple driver for it available here.

例如,QEMU有一个内置的教育PCI设备,名为edu,我在下面进一步解释:如何在QEMU源代码中添加新设备?这是开始使用设备驱动程序的好方法。我为它做了一个简单的驱动程序。

You can then put printf's or use GDB on QEMU just as for any other program, and see exactly what is going on.

然后,您可以在QEMU上放置printf或使用GDB,就像对任何其他程序一样,并确切地看到发生了什么。

There is also an OPAM SPI model for you specific use case: https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c

这里还有一个OPAM SPI模型,用于特定的用例:https://github.com/qemu/qemu/v2.7.0/hw/ssi/omap_spi.c。