目录
0.测试环境说明
环境 | 参数 |
---|---|
海思芯片型号 | Hi3516EV200 |
Kernel Version | 4.9.37 |
SDK | Hi3516EV200_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。
上图为内核菜单配置界面,可以看到,我去掉了“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.1SPI设备的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.2SPI设备的注册
和字符设备填充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驱动框架验证
模块加载、节点查看、读取及卸载如图所示:
在上图中,首先进行加载操作,可以看到,模块匹配后执行到probe,从系统申请的字符设备主设备号为252,设备数量为1null那里是我程序中有点瑕疵,传到博文的版本没有此问题。
使用 lsmod 可列出加载的驱动,同时在 /dev 目录下可以找到生成的设备节点 turixdev0 ,在 cd /sys/bus/spi/drivers/ 目录中有驱动匹配到的驱动名。
cat 操作可以对设备进行一次读操作,从打印信息来看,其依次执行了open、read、release操作。
最后模块卸载时会自动删除节点。
再次说明, 驱动中的write/read函数未实现 ,这是由于作者 太累了 想偷懒所致。
如果博文对您起到了哪怕一点点的帮助,可以给个赞嘛,ღ( ´・ᴗ・` )比心。
参考
1.《Linux设备驱动开发详解》宋宝华 著
————2020-5-13 @燕卫博————