//================================================================================================================================= 涉及结构体/函数如下: struct bus_type strncmp(); printk(); bus_register(); bus_unregister(); struct device device_register(); device_unregister(); struct device_driver ioremap() readl() writel() driver_register() driver_unregister() //=============================================================================================================================== [1] >> 总线bus? -->>总线结构如下 -------------------------------------------------------------------------------- ↓ ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ←↑ [driver1.ko] --> [driver2.ko] --> [driver3.ko] --> [driver4.ko] 逻辑注册链表 -------------------------------------------------------------------------------- bus总线 -------------------------------------------------------------------------------- [device1.ko] --> [device2.ko] --> [device3.ko] --> [device4.ko] 硬件注册链表
↑ ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ↓ -------------------------------------------------------------------------------- //总线 = 高(速率)速总线 + 低(速率)速总线 ↙ ↓ ↘
I2C总线 SPI总线 ... //================================================================================================================================= [2] >> /sys目录结构 -------------------------------------------------------------------------------- { /class /class1 /class2 /class3 /class4 { | \ / \ \ / { | \ / \ \ / { | /\ \ \/ { | / \ \ / \ /sys { /device /device1 /device2 /device3 /device4 { { { { /driver1 /driver2 /driver3 { { /i2c{ { { { /device1 /device2 /device3 { { { { { /driver1 /driver2 /driver3 { /bus{ /spi{ { { /device1 /device2 /device3 { { { /driver1 /driver2 /driver3 { /usb{ { { /device1 /device2 /device3 //class目录是对设备的分类 //device目录是存放所有设备 //bus目录是存放各种总线目录,总线目录中包含总线驱动与总线设备 //bus目录中的device与device目录中的device通过软连接ln -s连接 --------------------------------------------------------------------------------
实验1:bus实验 ; 一个driver驱动两个device
bus.c文件
#include<linux/init.h> #include<linux/module.h> #include<linux/device.h> #define FUNC_IN(FILE,FUNCTION) (printk("\n>>kerner:=======%s:%s IN========\n",(FILE),(FUNCTION))) #define FUNC_OUT(FILE,FUNCTION) (printk("\n>>kerner:=======%s:%s OUT========\n",(FILE),(FUNCTION))) MODULE_LICENSE("GPL"); //没有这GPL声明,模块不能加载在内核 //bus总线比对设备对象与设备驱动对象的name,匹配成功调用diver模块的probe接口 int cbus_match(struct device *dev, struct device_driver *drv); //驱动与总线的remove接口;卸载驱动模块时会断开与总线的连接;此时bus与diver都会调用各自的remove接口 int cbus_remove(struct device *dev); //总线对象 struct bus_type cbus ={ .name = "cbus",//总线的名字 .match = cbus_match, //总线match接口 .remove = cbus_remove //总线remove接口 }; //任何数据,无论是函数/变量/结构体,想要在其他模块使用必须导出符号 EXPORT_SYMBOL(cbus); //bus总线比对设备对象与设备驱动对象的name,匹配成功(返回非0)调用diver模块的probe接口 int cbus_match(struct device *dev, struct device_driver *drv) { FUNC_IN(__FILE__,__FUNCTION__); //设备对象的name会自动存储在struct kobject这个父类对象的name里面 if(strncmp(dev->kobj.name, drv->name,sizeof(drv->name))==0) { printk("cbus_match success"); } else { printk("cbus_match error"); return 0; } FUNC_OUT(__FILE__,__FUNCTION__); return 1; } //驱动与总线的remove接口;卸载驱动模块时会断开与总线的连接;此时调用remove接口 int cbus_remove(struct device *dev) { FUNC_IN(__FILE__,__FUNCTION__); FUNC_OUT(__FILE__,__FUNCTION__); return 0; } static int bus_init(void) { FUNC_IN(__FILE__,__FUNCTION__); bus_register(&cbus); //注册总线cbus FUNC_OUT(__FILE__,__FUNCTION__); return 0; } static void bus_exit(void) { FUNC_IN(__FILE__,__FUNCTION__); bus_unregister(&cbus); //注销总线cbus FUNC_OUT(__FILE__,__FUNCTION__); } module_init(bus_init); module_exit(bus_exit);
dev1.c文件 (dev1 = led1)
#include<linux/init.h> #include<linux/module.h> #include<linux/device.h> #include "dev.h" #define FUNC_IN(FILE,FUNCTION) (printk("\n>>kerner:=======%s:%s IN========\n",(FILE),(FUNCTION))) #define FUNC_OUT(FILE,FUNCTION) (printk("\n>>kerner:=======%s:%s OUT========\n",(FILE),(FUNCTION))) MODULE_LICENSE("GPL"); //没有这GPL声明,模块不能加载在内核 void devrelease(struct device *dev); //硬件设备led1的私有数据 struct cdev_data cdev_data1 = { .name = "cdev1", //硬件1的名字 .id = 01, .GPxCON = 0x11000100,//硬件1的控制寄存器物理地址 .GPxDAT = 0x11000104,//硬件1的数据寄存器物理地址 .gpio_num = 0, //硬件1是gpio0 }; //调用bus.c的bus对象 extern struct bus_type cbus; //硬件设备1对象 struct device cdev1 = { .init_name = "cdev", //设备对象的name;匹配用 .bus = &cbus, //设备对象挂载到cbus总线 .platform_data = &cdev_data1, //设备对象的私有结构体 .release = devrelease, //卸载设备对象的释放内存接口 }; static int dev_init(void) { FUNC_IN(__FILE__,__FUNCTION__); device_register(&cdev1);//注册设备2 FUNC_OUT(__FILE__,__FUNCTION__); return 0; } static void dev_exit(void) { FUNC_IN(__FILE__,__FUNCTION__); device_unregister(&cdev1);//注销设备2 FUNC_OUT(__FILE__,__FUNCTION__); } //卸载设备对象的释放内存接口 void devrelease(struct device *dev) { } module_init(dev_init); module_exit(dev_exit);
dev2.c文件(dev2 = led2 ; dev2与dev1硬件寄存器地址不同,其他都一样)
#include<linux/init.h> #include<linux/module.h> #include<linux/device.h> #include "dev.h" #define FUNC_IN(FILE,FUNCTION) (printk("\n>>kerner:=======%s:%s IN========\n",(FILE),(FUNCTION))) #define FUNC_OUT(FILE,FUNCTION) (printk("\n>>kerner:=======%s:%s OUT========\n",(FILE),(FUNCTION))) MODULE_LICENSE("GPL"); //没有这GPL声明,模块不能加载在内核 void devrelease(struct device *dev); //硬件设备led2的私有数据 struct cdev_data cdev_data2 = { .name = "cdev2", .id = 02, .GPxCON = 0x11000060, .GPxDAT = 0x11000064, .gpio_num = 1, }; //调用bus.c的bus对象 extern struct bus_type cbus; //硬件设备2对象 struct device cdev2 = { .init_name = "cdev", //设备对象的name;匹配用 .bus = &cbus, //设备对象挂载到cbus总线 .platform_data = &cdev_data2,//设备对象的私有结构体 .release = devrelease, //卸载设备对象的释放内存接口 }; static int dev_init(void) { FUNC_IN(__FILE__,__FUNCTION__); device_register(&cdev2); //注册设备1 FUNC_OUT(__FILE__,__FUNCTION__); return 0; } static void dev_exit(void) { FUNC_IN(__FILE__,__FUNCTION__); device_unregister(&cdev2); //注销设备1 FUNC_OUT(__FILE__,__FUNCTION__); } void devrelease(struct device *dev) { } module_init(dev_init); module_exit(dev_exit);
drv.c文件
#include<linux/init.h> #include<linux/module.h> #include<linux/device.h> #include<linux/io.h> #include "dev.h" //打印进入函数与退出函数的调试信息 #define FUNC_IN(FILE,FUNCTION) (printk("\n>>kerner:=======%s:%s IN========\n",(FILE),(FUNCTION))) #define FUNC_OUT(FILE,FUNCTION) (printk("\n>>kerner:=======%s:%s OUT========\n",(FILE),(FUNCTION))) //LED打开函数 static int LED_ON(unsigned long *,unsigned long *,unsigned int); //LED关闭函数 static int LED_OFF(unsigned long *,unsigned long *,unsigned int); //驱动与总线的probe接口;在match匹配后总线会将匹配到的设备对象信息发送给驱动模块 int c_probe (struct device *dev); //驱动与总线的remove接口;卸载驱动模块时会断开与总线的连接;此时调用remove接口 int c_remove (struct device *dev); MODULE_LICENSE("GPL"); //没有这GPL声明,模块不能加载在内核 extern struct bus_type cbus; //调用bus.c的bus总线对象 struct device_driver cdrv = { //驱动对象 .name = "cdev", //驱动name .bus = &cbus, //挂载在cbus总线 .probe = c_probe, //probe接口 .remove = c_remove //remove接口 }; int c_probe (struct device *dev) { FUNC_IN(__FILE__,__FUNCTION__); printk("========c_probe in========"); //第一步:接收device对象传过来的私有数据(使用结构体封装的设备信息) struct cdev_data * cdrv_cdev_data = (struct cdev_data*)dev->platform_data; //打印device对象的私有数据 printk("\n cdrv_cdev_data->name is %s\n",cdrv_cdev_data->name); printk("\n cdrv_cdev_data->id is %d\n",cdrv_cdev_data->id); printk("\n cdrv_cdev_data->con is %lx\n",cdrv_cdev_data->GPxCON); printk("\n cdrv_cdev_data->dat is %lx\n",cdrv_cdev_data->GPxDAT); printk("\n cdrv_cdev_data->gpio_num is %d\n",cdrv_cdev_data->gpio_num); //第二步:将寄存器物理地址映射 unsigned long *gpxcon; unsigned long *gpxdat; gpxcon = ioremap(cdrv_cdev_data->GPxCON,8); gpxdat = gpxcon+1; //第三步:LED的操作 LED_ON(gpxcon,gpxdat,cdrv_cdev_data->gpio_num); LED_OFF(gpxcon,gpxdat,cdrv_cdev_data->gpio_num); FUNC_OUT(__FILE__,__FUNCTION__); return 0; } int c_remove (struct device *dev) { FUNC_IN(__FILE__,__FUNCTION__); printk("========c_probe out========"); FUNC_OUT(__FILE__,__FUNCTION__); return 0; } /*gpxcon = 控制寄存器虚拟地址 *gpxdat = 数据寄存器虚拟地址 *gpio_num = 控制寄存器第几位*/ static int LED_ON(unsigned long *gpxcon,unsigned long *gpxdat,unsigned int gpio_num) { FUNC_IN(__FILE__,__FUNCTION__); unsigned int reg_vlu = 0; reg_vlu = readl(gpxcon); //读取映射地址数据 reg_vlu |= (0x1 << gpio_num*4); writel(reg_vlu,gpxcon); //向映射地址写数据 reg_vlu = readl(gpxdat); //读取映射地址数据 reg_vlu |= (0x1<<gpio_num); writel(reg_vlu,gpxdat); //向映射地址写数据 FUNC_OUT(__FILE__,__FUNCTION__); } /*gpxcon = 控制寄存器虚拟地址 *gpxdat = 数据寄存器虚拟地址 *gpio_num = 控制寄存器第几位*/ static int LED_OFF(unsigned long *gpxcon,unsigned long *gpxdat,unsigned int gpio_num) { FUNC_IN(__FILE__,__FUNCTION__); unsigned int reg_vlu = 0; reg_vlu = readl(gpxcon); //读取映射地址数据 reg_vlu |= (0x1 << gpio_num*4); writel(reg_vlu,gpxcon); //向映射地址写数据 reg_vlu = readl(gpxdat); //读取映射地址数据 reg_vlu &= ~(0x1<<gpio_num); writel(reg_vlu,gpxdat); //向映射地址写数据 FUNC_OUT(__FILE__,__FUNCTION__); } static int drv_init(void) { FUNC_IN(__FILE__,__FUNCTION__); driver_register(&cdrv); //注册设备驱动 FUNC_OUT(__FILE__,__FUNCTION__); return 0; } static void drv_exit(void) { FUNC_IN(__FILE__,__FUNCTION__); driver_unregister(&cdrv); //注销设备驱动 FUNC_OUT(__FILE__,__FUNCTION__); } module_init(drv_init); module_exit(drv_exit);
dev.h
#ifndef __DEV_H__ #define __DEV_H__ //设备私有信息 struct cdev_data { char*name; //设备名 unsigned int id; //设备编号 unsigned long GPxCON; //设备控制寄存器物理地址 unsigned long GPxDAT; //设备数据寄存器物理地址 unsigned int gpio_num; //操作寄存器的第N位用 }; #endif
Makefile文件
obj-m += bus.o
obj-m += dev1.o
obj-m += dev2.o
obj-m += drv.o
KERNER = /home/topeet/iTop4412_Kernel_3.0 PWD = $(shell pwd) name1 = bus
name2 = dev1
name3 = dev2
name4 = drv
all: make -C $(KERNER) M=$(PWD) modules
install: cp $(name1).ko $(name2).ko $(name3).ko $(name4).ko /home/topeet/minilinux/yqj_file/modul_pro3/
运行结构如下
//注意先加载总线,在挂载设备,最后挂载驱动 ; 先卸载驱动 ,在卸载设备,最后卸载总线 ======================下面是驱动与设备1的测试============== [root@iTOP-4412]# insmod bus.ko //第一步:加载bus总线 [ 5401.859589] [ 5401.859596] >>kerner:=======/home/topeet/share/bus_pro/bus.c:bus_init IN======== [ 5401.872737] [ 5401.872744] >>kerner:=======/home/topeet/share/bus_pro/bus.c:bus_init OUT======== [root@iTOP-4412]# insmod dev1.ko //第二步:加载设备1 [ 5411.541101] [ 5411.541105] >>kerner:=======/home/topeet/share/bus_pro/dev1.c:dev_init IN======== [ 5411.551605] [ 5411.551609] >>kerner:=======/home/topeet/share/bus_pro/dev1.c:dev_init OUT======== [root@iTOP-4412]# insmod drv.ko //第三步:加载设备驱动 [ 5422.863723] [ 5422.863726] >>kerner:=======/home/topeet/share/bus_pro/drv.c:drv_init IN======== [ 5422.871594] Driver 'cdev' needs updating - please use bus_type methods [ 5422.877899] [ 5422.877902] >>kerner:=======/home/topeet/share/bus_pro/bus.c:cbus_match IN======== [ 5422.887740] cbus_match success //匹配成功 [ 5422.889514] >>kerner:=======/home/topeet/share/bus_pro/bus.c:cbus_match OUT======== [ 5422.897358] [ 5422.897360] >>kerner:=======/home/topeet/share/bus_pro/drv.c:c_probe IN======== [ 5422.906154] ========c_probe in======== //跳转到设备驱动对象的probe接口 [ 5422.909686] cdrv_cdev_data->name is cdev1 //打印设备1信息 [ 5422.913935] [ 5422.913937] cdrv_cdev_data->id is 1 [ 5422.918952] [ 5422.918954] cdrv_cdev_data->con is 11000100 [ 5422.924692] [ 5422.924694] cdrv_cdev_data->dat is 11000104 [ 5422.930411] [ 5422.930413] cdrv_cdev_data->gpio_num is 0 [ 5422.935982] [ 5422.935984] >>kerner:=======/home/topeet/share/bus_pro/drv.c:LED_ON IN======== [ 5422.944672] //这里LED1亮起来 [ 5422.944674] >>kerner:=======/home/topeet/share/bus_pro/drv.c:LED_ON OUT======== [ 5422.953410] [ 5422.953412] >>kerner:=======/home/topeet/share/bus_pro/drv.c:LED_OFF IN======== [ 5422.962177] //这里LED1关掉 [ 5422.962179] >>kerner:=======/home/topeet/share/bus_pro/drv.c:LED_OFF OUT======== [ 5422.971030] [ 5422.971032] >>kerner:=======/home/topeet/share/bus_pro/drv.c:c_probe OUT======== [ 5422.981694] [ 5422.981701] >>kerner:=======/home/topeet/share/bus_pro/drv.c:drv_init OUT======== [root@iTOP-4412]# ======================下面是卸载驱动与卸载设备1,重新加载设备2与驱动============== [root@iTOP-4412]# rmmod drv //卸载驱动 [ 5675.152872] [ 5675.152889] >>kerner:=======/home/topeet/share/bus_pro/drv.c:drv_exit IN======== [ 5675.185881] [ 5675.185899] >>kerner:=======/home/topeet/share/bus_pro/bus.c:cbus_remove IN======== [ 5675.214468] [ 5675.214485] >>kerner:=======/home/topeet/share/bus_pro/bus.c:cbus_remove OUT======== [ 5675.279325] [ 5675.279342] >>kerner:=======/home/topeet/share/bus_pro/drv.c:drv_exit OUT======== [root@iTOP-4412]# rmmod dev1 //卸载设备1 [ 5684.265934] [ 5684.265936] >>kerner:=======/home/topeet/share/bus_pro/dev1.c:dev_exit IN======== [ 5684.274836] [ 5684.274838] >>kerner:=======/home/topeet/share/bus_pro/dev1.c:dev_exit OUT======== ======================下面是驱动与设备1的测试============== [root@iTOP-4412]# insmod dev2.ko //加载设备2 [ 5799.792706] [ 5799.792719] >>kerner:=======/home/topeet/share/bus_pro/dev2.c:dev_init IN======== [ 5799.809223] [ 5799.809235] >>kerner:=======/home/topeet/share/bus_pro/dev2.c:dev_init OUT======== [root@iTOP-4412]# insmod drv.ko //加载驱动 [ 5807.934793] [ 5807.934809] >>kerner:=======/home/topeet/share/bus_pro/drv.c:drv_init IN======== [ 5807.944957] Driver 'cdev' needs updating - please use bus_type methods [ 5807.951247] [ 5807.951263] >>kerner:=======/home/topeet/share/bus_pro/bus.c:cbus_match IN======== [ 5807.965785] cbus_match success //匹配成功 [ 5807.967303] >>kerner:=======/home/topeet/share/bus_pro/bus.c:cbus_match OUT======== [ 5807.975499] [ 5807.975514] >>kerner:=======/home/topeet/share/bus_pro/drv.c:c_probe IN======== [ 5807.983895] ========c_probe in======== //匹配成功接入驱动的probe函数 [ 5807.987414] cdrv_cdev_data->name is cdev2 [ 5807.991669] [ 5807.991681] cdrv_cdev_data->id is 2 [ 5807.996695] [ 5807.996708] cdrv_cdev_data->con is 11000060 [ 5808.002639] [ 5808.002652] cdrv_cdev_data->dat is 11000064 [ 5808.008164] [ 5808.008176] cdrv_cdev_data->gpio_num is 1 [ 5808.013783] [ 5808.013797] >>kerner:=======/home/topeet/share/bus_pro/drv.c:LED_ON IN======== [ 5808.022405] //这里设备2这个led亮起来 [ 5808.022418] >>kerner:=======/home/topeet/share/bus_pro/drv.c:LED_ON OUT======== [ 5808.031298] [ 5808.031312] >>kerner:=======/home/topeet/share/bus_pro/drv.c:LED_OFF IN======== [ 5808.039932] //这里设备2这个led熄灭 [ 5808.039945] >>kerner:=======/home/topeet/share/bus_pro/drv.c:LED_OFF OUT======== [ 5808.048782] [ 5808.048794] >>kerner:=======/home/topeet/share/bus_pro/drv.c:c_probe OUT======== [ 5808.060242] [ 5808.060259] >>kerner:=======/home/topeet/share/bus_pro/drv.c:drv_init OUT======== >>结论:一个驱动程序驱动两个类似设备,实现软硬件分离,代码复用率提高