从零开始写一个基于Hi3516EV200平台的spi设备驱动框架demo

从零开始写一个基于Hi3516EV200平台的spi设备驱动框架demo Tony 2024-01-24 15:02:45 2509

文章目录

测试环境说明

环境参数
海思芯片型号Hi3516EV200
Kernel Version4.9.37
SDKHi3516EV200_SDK_V1.0.1.1

1.SPI设备和字符设备之间的关系

Linux的三大设备:字符设备、块设备和网络设备对知道Linux的人都不会陌生,在Linux内部,对这些基本的设备类型做了更进一步的封装,正如《Linux设备驱动开发详解》一书中所说,Linux虽然是用C语言写的,但是处处都有面向对象编程的思想。借由这一思想,Linux2.6之后提出了platform设备驱动模型,在此驱动模型中,有总线、设备和驱动三个实体,总线将设备和驱动绑定。

platform设备驱动模型+面向对象思想两者结合在一起会发生什么呢?那就是,对每一类相似的设备,都有一套独立的子系统,比如input子系统、RTC子系统、帧缓冲、终端、misc、MMC、USB、I2C以及本次要说的SPI等等。

所以从本质上说,SPI子系统同属于platform设备驱动模型,在SPI子系统中,涉及4个软件模块:(1)主机端驱动;(2)连接主机和外设的纽带;(3)外设驱动;(4)板级逻辑。

本博文测试环境使用的Kernel版本为4.9.37,并且打了海思官方提供的SDK内核patch,该版本内核使用设备树描述板级逻辑,板级逻辑即描述了SOC内部资源分布和分配。主机端驱动则使用海思默认配置中所选择的SSP-PL022。

PL022

上图为内核菜单配置界面,可以看到,我去掉了“User mode SPI device driver support”一项,该项勾选会在/dev目录下生成可以供应用层操作的spidev0.0、spidev1.0、spidev1.1节点,我没有测试勾选此项后后文的设备驱动挂载测试能否成功,此处声明。

连接主机和外设的纽带是内核的任务,所以我们本文要关注的主要是第三方面——外设驱动的大致架构。

2.字符设备驱动框架

前面说过,SPI设备模型是基于三大基本设备类型扩展的,所以如果想要实现一个字符型SPI设备驱动,首先要实现一个字符型的设备驱动框架。这一类的教程网上很多,此处借用了《Linux设备驱动开发详解》书中提供的架构逻辑,并增加了自动在/dev目录下生成节点的功能。

/* Copyright (c) 2020 Turix.
 *
 * 该程序用于Turix中心(虚拟)的程序学习和实验。
 * 并做程序参考模板。
 *
 */
#include <linux/module.h>//使用模块相关功能
#include <linux/kernel.h>//使用printk
#include <linux/cdev.h>  //提供cdev结构体
#include <linux/fs.h>    //提供字符设备注册函数
#include <linux/slab.h>  //提供kzalloc和kfree
#include <linux/device.h>//提供设备注册相关函数

#define DRIVER_MAJOR 0
#define DEVICE_NUMBER 1

static int device_number=DEVICE_NUMBER;

//****************************************
//[注意]这里要保证编译器将未初始化全局变量
//设置为0
//****************************************
static int driver_major=DRIVER_MAJOR;
//****************************************
//在载入模块的时候,指定参数值(主设备号):
//insmod module.ko driver_major=xxx
//否则自动分配
//****************************************
module_param(driver_major,int,S_IRUGO);

//****************************************
//该结构体用于设置每一个(子)设备的设备参量
//****************************************
struct turix_dev_t{
    struct cdev cdev;

};

struct turix_dev_t *pturix_dev_t;

//****************************************
//设置的类用于驱动程序自动生成设备节点
//设备类的结构体类型新旧版本系统不同。
//在旧版本系统为:struct class_device
//在新版本系统为:struct device
//****************************************
static struct class *turixdev_class;
static struct device *turixdev_class_device[DEVICE_NUMBER];

/****************************************
函数名称:turix_open
功能描述:
    设备打开函数。
其它:
****************************************/
static int turix_open(struct inode *inode,struct file *filp)
{
    struct turix_dev_t *dev=container_of(inode->i_cdev,struct turix_dev_t,cdev);
    filp->private_data=dev;
    return 0;
}

/****************************************
函数名称:turix_release
功能描述:
    设备释放(关闭)函数。
其它:
****************************************/
static int turix_release(struct inode *inode,struct file *filp)
{
    return 0;
}

/****************************************
函数名称:turix_read
功能描述:
    设备读函数。
其它:
****************************************/
static ssize_t turix_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
{
    //struct turix_dev_t *dev=filp->private_data;

    return 0;
}

/****************************************
函数名称:turix_write
功能描述:
    设备写函数。
其它:
****************************************/
static ssize_t turix_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos)
{
    //struct turix_dev_t *dev=filp->private_data;

    return 0;
}

/****************************************
函数名称:turix_ioctl
功能描述:
    设备控制函数。
其它:
****************************************/
static long turix_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
    //struct turix_dev_t *dev=filp->private_data;
    switch(cmd)
    {
        default:return -EINVAL;
    }

    return 0;
}

static const struct file_operations turix_fops=
{
    .owner=THIS_MODULE,
    .read=turix_read,
    .write=turix_write,
    .unlocked_ioctl=turix_ioctl,
    .open=turix_open,
    .release=turix_release,
};

/****************************************
函数名称:turix_setup
功能描述:
    该函数用于子设备注册。
其它:
****************************************/
static int turix_setup(struct turix_dev_t *dev,int index)
{
    //****************************************
    //为每个子设备生成设备号
    //****************************************
    int err,devno=MKDEV(driver_major,index);

    cdev_init(&dev->cdev,&turix_fops);
    dev->cdev.owner=THIS_MODULE;

    err=cdev_add(&dev->cdev,devno,1);
    if(err<0)
    {
        printk(KERN_NOTICE "Turix:Add turixdev%d failed with %d!\n",index,err);
        return err;
    }

    return 0;
}

/****************************************
函数名称:turix_init
功能描述:
    该函数用于模块整体的初始化。
其它:
****************************************/
static int __init turix_init(void)
{
    int i,ret;
    //****************************************
    //MKDEV(dev_t dev)通过主次设备号生成设备号
    //****************************************
    dev_t devno=MKDEV(driver_major,0);

    printk(KERN_INFO "Turix: module init.\n");

    if(driver_major)
    {
        //****************************************
        //register_chrdev_region用于向系统注册设备号
        //这里注册的为主设备号,对不同数量的设备,
        //在配置函数turix_setup中设置次设备号。
        //****************************************
        ret=register_chrdev_region(devno,device_number,"turixdev");
    }
    else
    {
        //****************************************
        //alloc_chrdev_region用于向系统申请设备号
        //****************************************
        ret=alloc_chrdev_region(&devno,0,device_number,"turixdev");
        //****************************************
        //MAJOR(dev_t dev)宏用于提取主设备号
        //****************************************
        driver_major=MAJOR(devno);
    }
    if(ret<0)
    {
        goto FAIL;
    }
    printk(KERN_INFO "Turix: turixdev devno_major:%d.\n",driver_major);
    printk(KERN_INFO "Turix: The devices number is %d.\n",device_number);

    //****************************************
    //此处为每个设备申请的结构体会分别被每个
    //设备调用,其占用的内存在模块存在期间始终
    //存在,模块卸载时被释放。
    //****************************************
    pturix_dev_t=kzalloc(sizeof(struct turix_dev_t)*device_number,GFP_KERNEL);
    if(!pturix_dev_t)
    {
        ret=-ENOMEM;
        //****************************************
        //当内存申请不足时,异常退出。
        //****************************************
        unregister_chrdev_region(devno,device_number);
        goto FAIL;
    }

    //****************************************
    //创建类设备,会在sys/class/turixdev_class
    //类下创建设备,udev通过这个自动创建
    ///dev/turixdev这个设备节点。
    //****************************************
    turixdev_class=class_create(THIS_MODULE,"turixclass");

    /*运行子设备注册函数*/
    for(i=0;i<device_number;i++)
    {
        if(turix_setup(pturix_dev_t+i,i)==0)
        {
            turixdev_class_device[i]=device_create(turixdev_class,NULL,MKDEV(driver_major,i),NULL,"turixdev%d",i);
            printk(KERN_INFO "Turix: Add turixdev%d for major:%d-sub:%d.\n",i,driver_major,i);
        }
    }

    return 0;
FAIL:
    return ret;
}
module_init(turix_init);

/****************************************
函数名称:turix_exit
功能描述:
    该函数用于模块整体的退出操作。
其它:
****************************************/
static void __exit turix_exit(void)
{
    int i;

    for(i=0;i<device_number;i++)
    {
        cdev_del(&(pturix_dev_t+i)->cdev);
        device_unregister(turixdev_class_device[i]);
        printk(KERN_INFO "Turix: Remove turixdev%d for major:%d-sub:%d.\n",i,driver_major,i);
    }
    class_destroy(turixdev_class);

    kfree(pturix_dev_t);
    unregister_chrdev_region(MKDEV(driver_major,0),1);

    printk(KERN_INFO "Turix: module unload.\n");
}
module_exit(turix_exit);

MODULE_AUTHOR("Turix");
MODULE_DESCRIPTION("Turix test driver.");
MODULE_LICENSE("GPL");

这个框架看起来如此简单,字符设备驱动模型理解起来的话,除了设备的注册和注销、注册设备号以及节点生成之外,就只剩下下面的文件操作结构体了:

static const struct file_operations turix_fops=
{
.owner=THIS_MODULE,
.read=turix_read,
.write=turix_write,
.unlocked_ioctl=turix_ioctl,
.open=turix_open,
.release=turix_release,
};

它们的大致实现函数在上面的程序中已经有了,但是并没有对读写做更多的操作,因为这些读写我们并不打算使用GPIO进行模拟。即便如此,上面的程序也可以进行编译和挂载,Makefile为:

CC = arm-himix100-linux-gcc
obj-m:=test.o

CURRENT_PATH:=$(shell pwd)

LINUX_KERNEL_PATH:=/home/wind/Documents/osdrv/opensource/kernel/linux-4.9.y
#complie object
default:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
endif

其中,CC为交叉编译工具,test.o为对应于测试文件名的输出文件,LINUX_KERNEL_PATH为目标内核的源码目录,需要根据需要修改。挂载之后的现象是,在/dev下出现turixdev0的设备。

字符设备示例

3.SPI字符设备驱动框架

3.1 SPI设备的struct driver结构体

该结构体在文件 linux源码/include/linux/spi/spi.h 文件中定义:

/**
 * struct spi_driver-主机端“协议”驱动程序
 * @id_table:该驱动程序支持的SPI设备列表
 * @probe: 将此驱动程序绑定到spi设备。 
 * 驱动程序可以验证设备是否确实存在,并且可能需要配置
 * 系统设置过程中进行初始配置时不需要的特征(例如bits_per_word)。
 * @remove:将此驱动程序与spi设备解除绑定
 * @shutdown:在系统状态转换期间使用的标准关闭回调,例如powerdown / halt和kexec
 * @suspend:系统状态转换期间使用的标准暂停回调
 * @resume:系统状态转换期间使用的标准恢复回调
 * @driver:SPI设备驱动程序应初始化此结构的名称和所有者字段。
 *
 * 这表示使用SPI消息与SPI链路另一端的硬件进行交互的设备驱动程序。 
 * 之所以称为“协议”驱动程序,是因为它通过消息工作,
 * 而不是直接与SPI硬件通信(这是底层SPI控制器
 * 驱动程序用来传递这些消息的方式)。
 * 这些协议在驱动程序支持的设备的规范中定义。
 *
 * 通常,这些设备协议代表驱动程序支持的最低级别的接口,
 * 它也将支持较高级别的接口。这种高层的示例包括诸如MTD,
 * 网络,MMC,RTC,文件系统字符设备节点和硬件监视之类的框架。
 */
struct spi_driver {
    const struct spi_device_id *id_table;
    int            (*probe)   (struct spi_device *spi);
    int            (*remove)  (struct spi_device *spi);
    void        (*shutdown)(struct spi_device *spi);
    int            (*suspend) (struct spi_device *spi, pm_message_t mesg);
    int            (*resume)  (struct spi_device *spi);
    struct device_driver    driver;
};

probe为主机端和外设匹配成功之后执行的操作,remove则是设备移除后的操作,其它的回调函数和PM有关,暂不考虑。

这样一来,对字符设备的改动应该在以下几个方面:

1.设备的注册和节点的生成应该放在probe中,因为只有外设和主机都存在且匹配的时候,设备节点的操作才有意义且合法。

2.因为所要编写的SPI设备(其实我本意是想写2401的驱动的,基于spi而非gpio模拟的,但是未完成之前先整理一下才写的博文)并不具备热插拔检测能力,所以匹配的操作在模块挂载时完成,而移除的操作由模块卸载函数进行。

3.为了指定设备绑在那个SPI上,需要修改设备树。

3.2设备树的修改

设备树的修改

修改对应于板的dts/dtsi文件,(dtsi被包含在dts中,只修改dts即可,重复设置的项会被新设置覆盖),在compatible中添加一项,这个项目将在后续驱动尝试匹配时发挥作用。

当然,SPI设备的match并不只有这一种操作。比如在参考2中,作者使用spi_new_device动态增加一个SPI设备(但是我在使用的时候,他的操作没有复现成功,device和ssp-pl022冲突,并且dts中删除spibus1之后也不成,不排除我 操作不对,但还是…心累)。此处把修改该作者使用的device端板信息注册模块代码放上,有兴趣测试的自取:

#include <linux/kernel.h> //提供printk
#include <linux/module.h> //使用模块相关功能
#include <linux/spi/spi.h>//提供SPI设备模型API

#define DEVICENAME "turixSPIdev"
#define SPIBUS 1

static struct spi_board_info dev_info =
{
    .modalias        = DEVICENAME,  //初始化设备的名称
    .platform_data   = NULL,        //平台文件信息
    .max_speed_hz    = 10*1000*1000,//初始化传输速率
    .bus_num         = SPIBUS,      //控制器编号
    .chip_select     = 1,           //控制器片选的编号
    .mode            = SPI_MODE_0,  //spi的模式
    .controller_data = NULL,
};
static struct spi_device* turix_spi_device; 

static int __init turix_init(void)
{
    struct spi_master* turix_spi_master;
    turix_spi_master=spi_busnum_to_master(dev_info.bus_num);
    turix_spi_device=spi_new_device(turix_spi_master,&dev_info);
    return 0;
}

static void __exit turix_exit(void)
{ 
    spi_unregister_device(turix_spi_device);
}

module_init(turix_init);
module_exit(turix_exit);

MODULE_AUTHOR("Turix");
MODULE_DESCRIPTION("Turix test driver.");
MODULE_LICENSE("GPL");

设备树的编译跟随内核进行,会生成dtb二进制文件并写入uImage,在内核启动时加载。

编译设备树

上图中的内核是经过少量再裁剪的,官方默认deconfig配置生成的大小约3.4MB,所以有差池正常。

3.3 SPI设备的注册

和字符设备填充struct file_operations结构体相似,SPI设备要填充上面说到的struct spi_driver结构体:

#define DEVICENAME "turixSPIdev"
//****************************************
//此处的匹配要配套设备树中进行相应的修改
//****************************************
static const struct of_device_id spi_id[] = {
    { .compatible = "arm,turixSPIdev", .data = NULL },
    {}
};

static struct spi_driver turix_spi_driver =
{
    .driver =
    {
        .name  = DEVICENAME,
        .owner = THIS_MODULE,
        .of_match_table = spi_id,
    },
    .probe     = turix_spi_probe,
    .remove    = turix_spi_remove,
};

可以看到,struct of_device_id结构体中的信息正是之前我们在设备树中添加的项,这样两者一致就可以成功匹配进而执行probe函数注册设备了。按照3.1节所说,设备的注册和节点的生成应该放在probe中,这样一来模块挂载中要进行的操作就剩下spi设备的注册了,具体的匹配交给SPI子系统去做,挂载卸载函数为:

static int __init turix_init(void)
{
    spi_register_driver(&turix_spi_driver);
    return 0;
}
module_init(turix_init);

static void __exit turix_exit(void)
{
    /*先进行设备的注销、节点的删除操作*/
    spi_unregister_driver(&turix_spi_driver);
}
module_exit(turix_exit);

其中turix_spi_driver结构体即上面的struct spi_driver结构体。

3.4 驱动框架demo

整个驱动程序如下, 其中,write/read函数未实现 (因为作者写此博文的时候作为当天工作总结,剩下的坑来日再踩):

/* Copyright (c) 2020 Turix.
 *
 * 该程序用于Turix中心(虚拟)的SPI设备驱动程序学习和实验。
 * 并做程序参考模板。
 *
 */

#include <linux/module.h>//使用模块相关功能
#include <linux/kernel.h>//使用printk
#include <linux/cdev.h>  //提供cdev结构体
#include <linux/fs.h>    //提供字符设备注册函数
#include <linux/slab.h>  //提供kzalloc和kfree
#include <linux/device.h>//提供设备注册相关函数
#include <linux/spi/spi.h>//提供SPI设备模型API

#define DEVICENAME "turixSPIdev"
#define DEVICE_NUMBER 1
static int device_number=DEVICE_NUMBER;

#define DRIVER_MAJOR 0
//****************************************
//[注意]这里要保证编译器将未初始化全局变量
//设置为0
//****************************************
static int driver_major=DRIVER_MAJOR;
//****************************************
//在载入模块的时候,指定参数值(主设备号):
//insmod module.ko driver_major=xxx
//否则自动分配
//****************************************
module_param(driver_major,int,S_IRUGO);

//****************************************
//该结构体用于设置每一个(子)设备的设备参量
//****************************************
struct turix_dev_t{
    struct cdev cdev;

};
struct turix_dev_t *pturix_dev_t;

//****************************************
//设置的类用于驱动程序自动生成设备节点
//设备类的结构体类型新旧版本系统不同。
//在旧版本系统为:struct class_device
//在新版本系统为:struct device
//****************************************
static struct class *turixdev_class;
static struct device *turixdev_class_device[DEVICE_NUMBER];

/****************************************
函数名称:turix_open
功能描述:
    设备打开函数。
其它:
****************************************/
static int turix_open(struct inode *inode,struct file *filp)
{
    struct turix_dev_t *dev=container_of(inode->i_cdev,struct turix_dev_t,cdev);
    filp->private_data=dev;
    printk(KERN_INFO "Turix: SPI Device open.\n");
    return 0;
}

/****************************************
函数名称:turix_release
功能描述:
    设备释放(关闭)函数。
其它:
****************************************/
static int turix_release(struct inode *inode,struct file *filp)
{
    printk(KERN_INFO "Turix: SPI Device release.\n");
    return 0;
}

/****************************************
函数名称:turix_read
功能描述:
    设备读函数。
其它:
****************************************/
static ssize_t turix_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
{
    //struct turix_dev_t *dev=filp->private_data;
    printk(KERN_INFO "Turix: Perform a read operation.\n");
    return 0;
}

/****************************************
函数名称:turix_write
功能描述:
    设备写函数。
其它:
****************************************/
static ssize_t turix_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos)
{
    //struct turix_dev_t *dev=filp->private_data;
    printk(KERN_INFO "Turix: Perform a write operation.\n");
    return 0;
}

/****************************************
函数名称:turix_ioctl
功能描述:
    设备控制函数。
其它:
****************************************/
static long turix_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
    //struct turix_dev_t *dev=filp->private_data;
    switch(cmd)
    {
        default:return -EINVAL;
    }

    return 0;
}

//****************************************
//字符设备操作
//****************************************
static const struct file_operations turix_fops=
{
    .owner         =THIS_MODULE,
    .read          =turix_read,
    .write         =turix_write,
    .unlocked_ioctl=turix_ioctl,
    .open          =turix_open,
    .release       =turix_release,
};

/****************************************
函数名称:turix_setup
功能描述:
    该函数用于子设备注册。
其它:
****************************************/
static int turix_setup(struct turix_dev_t *dev,int index)
{
    //****************************************
    //为每个子设备生成设备号
    //****************************************
    int err,devno=MKDEV(driver_major,index);

    cdev_init(&dev->cdev,&turix_fops);
    dev->cdev.owner=THIS_MODULE;

    err=cdev_add(&dev->cdev,devno,1);
    if(err<0)
    {
        printk(KERN_NOTICE "Turix:Add node turixdev%d failed with %d!\n",index,err);
        return err;
    }

    return 0;
}

//****************************************
//probe执行标志,用于执行失败时模块可以正常卸载
//****************************************
static int flag_devprobed=0;
/****************************************
函数名称:turix_spi_probe
功能描述:
    设备挂载函数。
其它:
****************************************/
int turix_spi_probe(struct spi_device *spi)
{
    int i,ret;
    //****************************************
    //MKDEV(dev_t dev)通过主次设备号生成设备号
    //****************************************
    dev_t devno=MKDEV(driver_major,0);

    printk(KERN_INFO "Turix: SPI Device probe.\n");

    if(driver_major)
    {
        //****************************************
        //register_chrdev_region用于向系统注册设备号
        //这里注册的为主设备号,对不同数量的设备,
        //在配置函数turix_setup中设置次设备号。
        //****************************************
        ret=register_chrdev_region(devno,device_number,"turixdev");
    }
    else
    {
        //****************************************
        //alloc_chrdev_region用于向系统申请设备号
        //****************************************
        ret=alloc_chrdev_region(&devno,0,device_number,"turixdev");
        //****************************************
        //MAJOR(dev_t dev)宏用于提取主设备号
        //****************************************
        driver_major=MAJOR(devno);
    }
    if(ret<0)
    {
        goto FAIL;
    }
    printk(KERN_INFO "Turix: turixdev devno_major:%d.\n",driver_major);
    printk(KERN_INFO "Turix: The devices number is %d.\n",device_number);

    //****************************************
    //此处为每个设备申请的结构体会分别被每个
    //设备调用,其占用的内存在模块存在期间始终
    //存在,模块卸载时被释放。
    //****************************************
    pturix_dev_t=kzalloc(sizeof(struct turix_dev_t)*device_number,GFP_KERNEL);
    if(!pturix_dev_t)
    {
        ret=-ENOMEM;
        //****************************************
        //当内存申请不足时,异常退出。
        //****************************************
        unregister_chrdev_region(devno,device_number);
        goto FAIL;
    }

    //****************************************
    //创建类设备,会在sys/class/turixdev_class
    //类下创建设备,udev通过这个自动创建
    ///dev/turixdev这个设备节点。
    //****************************************
    turixdev_class=class_create(THIS_MODULE,"turixclass");

    /*运行子设备注册函数*/
    for(i=0;i<device_number;i++)
    {
        if(turix_setup(pturix_dev_t+i,i)==0)
        {
            turixdev_class_device[i]=device_create(turixdev_class,NULL,MKDEV(driver_major,i),NULL,"turixdev%d",i);
            printk(KERN_INFO "Turix: Add node turixdev%d for major:%d-sub:%d.\n",i,driver_major,i);
        }
    }

    flag_devprobed=1;
    return 0;
FAIL:
    printk(KERN_INFO "Turix: SPI Device probe failed.\n");
    return ret;
}

/****************************************
函数名称:turix_spi_remove
功能描述:
    设备卸载函数。
其它:
****************************************/
int turix_spi_remove(struct spi_device *spi)
{
    printk(KERN_INFO "Turix: SPI Device remove.\n");
    return 0;
}

//****************************************
//此处的匹配要配套设备树中进行相应的修改
//****************************************
static const struct of_device_id spi_id[] = {
    { .compatible = "arm,turixSPIdev", .data = NULL },
    {}
};

static struct spi_driver turix_spi_driver =
{
    .driver =
    {
        .name  = DEVICENAME,
        .owner = THIS_MODULE,
        .of_match_table = spi_id,
    },
    .probe     = turix_spi_probe,
    .remove    = turix_spi_remove,
};

/****************************************
函数名称:turix_init
功能描述:
    该函数用于模块整体的初始化。
其它:
****************************************/
static int __init turix_init(void)
{
    printk(KERN_INFO "Turix: module init.\n");
    spi_register_driver(&turix_spi_driver);

    return 0;
}
module_init(turix_init);

/****************************************
函数名称:turix_exit
功能描述:
    该函数用于模块整体的退出操作。
其它:
****************************************/
static void __exit turix_exit(void)
{
    int i;

    if(flag_devprobed==1)
    {
        for(i=0;i<device_number;i++)
        {
            cdev_del(&(pturix_dev_t+i)->cdev);
            device_unregister(turixdev_class_device[i]);
            printk(KERN_INFO "Turix: Remove turixdev%d for major:%d-sub:%d.\n",i,driver_major,i);
        }
        class_destroy(turixdev_class);

        kfree(pturix_dev_t);
        unregister_chrdev_region(MKDEV(driver_major,0),1);

    }
    spi_unregister_driver(&turix_spi_driver);

    printk(KERN_INFO "Turix: module unload.\n");
}
module_exit(turix_exit);

MODULE_AUTHOR("Turix");
MODULE_DESCRIPTION("Turix SPI test driver.");
MODULE_LICENSE("GPL");

模块编译Makefile为:

CC = arm-himix100-linux-gcc

obj-m:=spidriver.o
CURRENT_PATH:=$(shell pwd)

LINUX_KERNEL_PATH:=/home/wind/Documents/osdrv/opensource/kernel/linux-4.9.y
#complie object
default:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
endif

其中,CC为交叉编译工具,test.o为对应于测试文件名的输出文件,LINUX_KERNEL_PATH为目标内核的源码目录,需要根据需要修改。

3.5 驱动框架验证

模块加载、节点查看、读取及卸载如图所示:

SPI设备驱动框架验证

在上图中,首先进行加载操作,可以看到,模块匹配后执行到probe,从系统申请的字符设备主设备号为252,设备数量为1null那里是我程序中有点瑕疵,传到博文的版本没有此问题。

使用 lsmod 可列出加载的驱动,同时在 /dev 目录下可以找到生成的设备节点 turixdev0 ,在 cd /sys/bus/spi/drivers/ 目录中有驱动匹配到的驱动名。

cat 操作可以对设备进行一次读操作,从打印信息来看,其依次执行了open、read、release操作。

最后模块卸载时会自动删除节点。

再次说明, 驱动中的write/read函数未实现 ,这是由于作者 太累了 想偷懒所致。

如果博文对您起到了哪怕一点点的帮助,可以给个赞嘛,ღ( ´・ᴗ・` )比心。

参考

1.《Linux设备驱动开发详解》宋宝华 著

2.Linux SPI 字符设备 驱动例子

3.AM335X——SPI设备驱动

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Tony
红包 点赞 2 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Tony
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区