参考国嵌教程以及网络资料学习。
https://blog.csdn.net/lidaqiang99/article/details/6599117
https://blog.csdn.net/zqixiao_09/article/details/50888795
一、总线设备驱动模型
总线是处理器和设备之间的通道,在设备模型中,所有的设备都通过总线相连,甚至是内部的虚拟“platform”总线。在linux设备模型中,总线由bus_type结构表示,定义在linux/device.h
中。
struct bus_type {
const char *name;总线名称
struct bus_attribute *bus_attrs;总线属性
struct device_attribute *dev_attrs;设备属性
struct driver_attribute *drv_attrs;驱动属性
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*suspend_late)(struct device *dev, pm_message_t state);
int (*resume_early)(struct device *dev);
int (*resume)(struct device *dev);
struct dev_pm_ops *pm;
struct bus_type_private *p;
};
总线操作函数
总线注册
bus_register(struct bus_type *bus)
若成功,新的总线将被添加进系统,并可在sysfs的/sys/bus下看到。
总线删除
void bus_unregister(struct bus_type *bus)
总线match方法
int (*match)(struct device *dev,struct device_driver *drv)
当一个新设备或者驱动添加到这个总线时,该方法调用。用于判断指定的驱动程序是否能处理指定的设备。若可以,则返回非零值。
总线uevent方法
int (*uevent)(struct device *dev,char **envp,int num_envp,char *buffer,int buffer_size)
在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量。
总线属性
struct bus_attribute
{
struct attribute attr;
ssize_t (*show)(struct bus_type *,char *buf); //读
ssize_t (*store)(struct bus_type *,const char *buf,size_t conunt);//写
}
创建属性
int bus_create_file(struct bus_type *bus,struct bus_attribute *attr)
删除属性
void bus_remove_file(struct bus_type *bus,struct bus_attribute *attr)
代码示例
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
//MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("pp"); // 描述模块的作者
MODULE_VERSION("0.1"); // 描述模块的版本
MODULE_DESCRIPTION("module bus_basic"); // 描述模块的介绍信息
MODULE_ALIAS("alias bus_basic"); // 描述模块的别名信息
static char *Version = "$Reversion: 1.0 $";
static int my_match(struct device *dev,struct device_driver *driver)
{
//return !strncmp(dev->bus_id,driver->name,strlen(driver->name));
return !strncmp(dev_name(dev),driver->name,strlen(driver->name));
}
struct bus_type my_bus_type = {
.name = "my_bus",
.match = my_match,
};
static ssize_t show_bus_version(struct bus_type *bus,char *buf)
{
return snprintf(buf,PAGE_SIZE,"%s\n",Version);
}
static BUS_ATTR(version,S_IRUGO,show_bus_version,NULL);
static int __init my_bus_init(void)
{
int ret;
/*注册总线*/
ret = bus_register(&my_bus_type);
if(ret)
return ret;
/*创建属性文件*/
if(bus_create_file(&my_bus_type,&bus_attr_version))
printk(KERN_NOTICE "failed to create version attribute!\n");
return ret;
}
static void my_bus_exit(void)
{
bus_unregister(&my_bus_type);
}
module_init(my_bus_init);
module_exit(my_bus_exit);
这里由于我是在本机测试,采用Linux4.x内核,因此改动:
在新版本的内核中struct device 已经没有bus_id成员,取而代之的是通过dev_name和dev_set_name对设备的名字进行操作。
struct device - replace bus_id with dev_name(),以后只要使用dev->bus_id的时候,改成dev_name(dev)就可以了。
验证:
二、bus驱动学习
总线
bus.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
//MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("pp"); // 描述模块的作者
MODULE_VERSION("0.1"); // 描述模块的版本
MODULE_DESCRIPTION("module bus_basic"); // 描述模块的介绍信息
MODULE_ALIAS("alias bus_basic"); // 描述模块的别名信息
static char *Version = "$Reversion: 1.0 $";
////////////////////////////////////////////////////////
static void my_bus_release(struct device *dev)
{
printk(KERN_DEBUG "my bus release\n");
}
struct device my_bus = {
//.bus_id = "my_bus0",
.release = my_bus_release,
};
////////////////////////////////////////////////////////
static int my_match(struct device *dev,struct device_driver *driver)
{
//return !strncmp(dev->bus_id,driver->name,strlen(driver->name));
return !strncmp(dev_name(dev),driver->name,strlen(driver->name));
}
struct bus_type my_bus_type = {
.name = "my_bus",
.match = my_match,
};
////////////////////////////////////////////////////////
EXPORT_SYMBOL(my_bus);
EXPORT_SYMBOL(my_bus_type);
static ssize_t show_bus_version(struct bus_type *bus,char *buf)
{
return snprintf(buf,PAGE_SIZE,"%s\n",Version);
}
static BUS_ATTR(version,S_IRUGO,show_bus_version,NULL);
static int __init my_bus_init(void)
{
int ret;
/*注册总线*/
ret = bus_register(&my_bus_type);
if(ret)
return ret;
/*创建属性文件*/
if(bus_create_file(&my_bus_type,&bus_attr_version))
printk(KERN_NOTICE "failed to create version attribute!\n");
//.bus_id
dev_set_name(&my_bus,"my_bus0");
//注册总线设备
ret = device_register(&my_bus);
if(ret)
printk(KERN_NOTICE "failed to register device:my_bus!\n");
return ret;
}
static void my_bus_exit(void)
{
//卸载总线设备
device_unregister(&my_bus);
bus_unregister(&my_bus_type);
}
module_init(my_bus_init);
module_exit(my_bus_exit);
设备
device.c
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");
extern struct device my_bus;
extern struct bus_type my_bus_type;
/* Why need this ?*/
static void my_dev_release(struct device *dev)
{
}
struct device my_dev = {
.bus = &my_bus_type,
.parent = &my_bus,
.release = my_dev_release,
};
/*
* Export a simple attribute.
*/
static ssize_t mydev_show(struct device *dev, char *buf)
{
return sprintf(buf, "%s\n", "This is my device!");
}
static DEVICE_ATTR(dev, S_IRUGO, mydev_show, NULL);
static int __init my_device_init(void)
{
int ret = 0;
/* 初始化设备 */
//strncpy(my_dev.bus_id, "my_dev", BUS_ID_SIZE);
dev_set_name(&my_dev,"my_dev");
/*注册设备*/
device_register(&my_dev);
/*创建属性文件*/
device_create_file(&my_dev, &dev_attr_dev);
return ret;
}
static void my_device_exit(void)
{
device_unregister(&my_dev);
}
module_init(my_device_init);
module_exit(my_device_exit);
驱动
driver.c
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");
extern struct bus_type my_bus_type;
static int my_probe(struct device *dev)
{
printk("Driver found device which my driver can handle!\n");
return 0;
}
static int my_remove(struct device *dev)
{
printk("Driver found device unpluged!\n");
return 0;
}
struct device_driver my_driver = {
.name = "my_dev",
.bus = &my_bus_type,
.probe = my_probe,
.remove = my_remove,
};
/*
* Export a simple attribute.
*/
static ssize_t mydriver_show(struct device_driver *driver, char *buf)
{
return sprintf(buf, "%s\n", "This is my driver!");
}
static DRIVER_ATTR(drv, S_IRUGO, mydriver_show, NULL);
static int __init my_driver_init(void)
{
int ret = 0;
/*注册驱动*/
driver_register(&my_driver);
/*创建属性文件*/
driver_create_file(&my_driver, &driver_attr_drv);
return ret;
}
static void my_driver_exit(void)
{
driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
Makefile
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build
obj-m += bus.o device.o driver.o
all:
make -C $(KERN_DIR) M=`pwd` modules
.PHONY: clean
clean:
# make -C $(KERN_DIR) M=`pwd` clean
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
rm -f Module.markers Module.symvers modules.order
rm -rf .tmp_versions Modules.symvers
make之后得到bus.ko, dev.ko, drv.ko文件
验证
使用root用户依次调用如下命令添加3个模块:
insmod bus.ko
insmod dev.ko
insmod.drv.ko
输出:
pp@pp:~/learndriver/bustest$ ls /sys/bus/my_bus/drivers/my_dev/
bind drv my_dev uevent unbind
pp@pp:~/learndriver/bustest$ ls /sys/bus/my_bus/devices/my_dev/
dev driver power subsystem uevent
可以看到
/sys/bus/my_bus/drivers/目录下有my_dev设备了
/sys/bus/my_bus/drivers/目录下有my_dev驱动了
三、Platform驱动学习
driver.c
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/platform_device.h>
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");
static int my_probe(struct device *dev)
{
printk("Driver found device which my driver can handle!\n");
return 0;
}
static int my_remove(struct device *dev)
{
printk("Driver found device unpluged!\n");
return 0;
}
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.owner = THIS_MODULE,
.name = "my_dev",
},
};
static int __init my_driver_init(void)
{
/*注册平台驱动*/
return platform_driver_register(&my_driver);
}
static void my_driver_exit(void)
{
platform_driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
device.c
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/platform_device.h>
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");
static struct platform_device *my_device;
static int __init my_device_init(void)
{
int ret = 0;
/* 分配结构 */
my_device = platform_device_alloc("my_dev", -1);
/*注册设备*/
ret = platform_device_add(my_device);
/*注册失败,释放相关内存*/
if (ret)
platform_device_put(my_device);
return ret;
}
static void my_device_exit(void)
{
platform_device_unregister(my_device);
}
module_init(my_device_init);
module_exit(my_device_exit);
我们要记住,platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳。
可以看到,模块加载和卸载函数仅仅通过paltform_driver_register()、paltform_driver_unregister() 函数进行 platform_driver 的注册和注销,而原先注册和注销字符设备的工作已经被移交到 platform_driver 的 probe() 和 remove() 成员函数中。
引入platform模型符合Linux 设备模型 —— 总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利;
设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息,而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。