在Linux2.6 以后的设备驱动模型中,需要关心总线、设备和驱动3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反 ,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,在SoC系统中集成的独立外设控制器、挂接在SOC内存空间的外设等不依附于此类总线。在linux中实现了一种虚拟总线,称为platform总线,相应的设备称为platform_device ,而驱动称为 platform_driver。 所谓的platform_device 并不是与字符设备、块设备和网络设备并列的概念,而是linux 系统提供的一种附加手段,例如,我们通常把在SoC内部集成的I2C、RTC、LCD、看门狗等控制器都归纳为platform_device,而它们本身是字符设备。
platform_device结构体的定义如下
struct platform_device { // platform总线设备
const char * name; // 平台设备的名字
int id; // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
boo id_auto;
struct device dev; // 内置的device结构体
u32 num_resources; // 资源结构体数量
struct resource * resource; // 指向一个资源结构体数组
const struct platform_device_id *id_entry; // 用来进行与设备驱动匹配用的id_table表
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata; // 自留地 添加自己的东西
};
其中struct resource结构体的定义如下,描述了platform_device 的资源,本身由一个结构体表示
struct resource { // 资源结构体
resource_size_t start; // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址
resource_size_t end; // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址
const char *name; // 资源名
unsigned long flags; // 资源的标示,用来识别不同的资源
struct resource *parent, *sibling, *child; // 资源指针,可以构成链表
};
我们通常关心的是start,end,flags 三个参数,它们分别标明了资源的开始值、结束值和类型,flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORE-SOURCE_DMA等。start、end的 含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的 内存的开始地址和结束地址;当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的 中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。对于同种类型的资源而言,可 以有多份,例如说某设备占据了两个内存区域,则可以定义两个IORESOURCE_MEM资源。
对resource的定义也通常在BSP的板文件中进行,而在具体的设备驱动中通过platform_get_resource() 这样的API来获取,此API的原型为
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
platform_driver 结构体的定义如下
struct platform_driver {
int (*probe)(struct platform_device *); // 这个probe函数其实和 device_driver中的是一样的功能,但是一般是使用device_driver中的那个
int (*remove)(struct platform_device *); // 卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; // 内置的device_driver 结构体
const struct platform_device_id *id_table; // 该设备驱动支持的设备的列表 他是通过这个指针去指向 platform_device_id 类型的数组
bool prevent_deferred_probe;
};
其中struct device_driver结构体的定义为
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules *
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
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 (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
platform的接口函数
int platform_driver_register(struct platform_driver *); // 用来注册我们的设备驱动
void platform_driver_unregister(struct platform_driver *); // 用来卸载我们的设备驱动
int platform_device_register(struct platform_device *); // 用来注册我们的设备
void platform_device_unregister(struct platform_device *); // 用来卸载我们的设备
系统为platform总线定义一个bus_type的实例platform_bus_type
struct bus_type platform_bus_type = {
.name = "platform"
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
这里重点关注成员函数match(),其确定device和driver如何匹配。
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
可以看出,匹配platform_device和platform_driver有4种可能性,一是基于设备树风格的匹配;二是基于ACPI风格的匹配;三是匹配ID表(即platform_device设备名是否出现在platform_driver的ID 表内);第四种是匹配platform_device设备名和驱动的名字。匹配platform_device和platform_driver主要看二者的name字段是否相同。(name必须要相同才能匹配)
对于linuxARM平台而言,对设备platform_device的定义通常在BSP 的板级配置文件中实现,在板文件中,将platform_device归纳与一个数组,最终通过 platform_add_devices() 函数统一注册。
platform_add_devices() 函数可以将平台设备添加到系统中,这个函数的原型为:
int platform_add_devices(struct platform_device **devs,int num);
该函数的第一个参数为平台设备数组的指针,第二个参数为平台设备的数量,它内部调用了platform_device_register() 函数以注册单个的平台设备。在linux3以后,ARMlinux 不太喜欢人们以编码的形式去填写platform_device和注册,而倾向于根据设备数中的内容自动展开platform_device。
下面将之前写的简单字符设备驱动改为platform总线的字符设备
先写一个设备框架mymodule_device.c,只添加设备驱动匹配名称
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct platform_device mymodule_dev = {
.name = "mymodules",
.id = -1,
};
static int mymodule_dev_init(void)
{
int result = platform_device_register(&mymodele_dev);
return result;
}
static void mymodule_dev_exit(void)
{
platform_device_unregister(&mymodule_dev);
}
module_init(mymodule_dev_init);
module_exit(mymodule_dev_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");
在修改设备驱动文件mymodule_driver.c
//添加头文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int major=0,minor=0;
#define BUF_CLEAR 0x1
//定义cdev结构体
struct mymodule_dev{
struct cdev cdev;
unsigned char buf[512];
struct mutex mutex;
unsigned int current_len;//buf中实际数据长度
wait_queue_head_t r_wait;//定义读写等待队列头部
wait_queue_head_t w_wait;
struct fasync_struct *async_queue; //异步结构体
};
struct mymodule_dev *mydev;
struct class *my_class;
struct device *my_device;
static int mymodule_fasync(int fd,struct file *filp,int mode)
{
struct mymodule_dev *dev = filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
static int mymodule_open(struct inode *inode,struct file *filp)
{
filp->private_data = mydev;
return 0;
}
static int mymodule_release(struct inode *inode,struct file *filp)
{
mymodule_fasync(-1,filp,0);
return 0;
}
static ssize_t mymodule_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
int ret = 0;
struct mymodule_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait,current);//定义等待队列元素
mutex_lock(&dev->mutex);
add_wait_queue(&dev->r_wait,&wait);//等待队列头部添加到等待队列中
while(dev->current_len ==0){
if(filp->f_flags & O_NONBLOCK){ //非阻塞时
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改变进程状态
mutex_unlock(&dev->mutex);
schedule();
if(signal_pending(current)){ //如果是因为信号唤醒
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if(count > dev->current_len)
count = dev->current_len;
//内核空间->用户空间
if(copy_to_user(buf,dev->buf,count)){
ret = -EFAULT;
goto out;
}else{
memcpy(dev->buf,dev->buf+count,dev->current_len-count);
dev->current_len -= count;
printk(KERN_INFO"Read %d bytes,current_len %d\n",count,dev->current_len);
wake_up_interruptible(&dev->w_wait);//唤醒可能阻塞的写进程
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait,&wait); //将元素移除等待队列
set_current_state(TASK_RUNNING); //设置进程状态
return ret;
}
static ssize_t mymodule_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
int ret = 0;
struct mymodule_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait,current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait,&wait);
while(dev->current_len == 512){
if(filp->f_flags & O_NONBLOCK){
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if(count > 512 - dev->current_len)
count = 512 - dev->current_len;
//用户空间->内核空间
if(copy_from_user(dev->buf+dev->current_len,buf,count)){
ret = -EFAULT;
goto out;
}else{
dev->current_len += count;
printk(KERN_INFO"Writen %d bytes,current_len: %d\n",count,dev->current_len);
wake_up_interruptible(&dev->r_wait);
if(dev->async_queue){
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
printk(KERN_DEBUG"%s kill SIGIO\n",__func__);
}
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait,&wait);
set_current_state(TASK_RUNNING);
return ret;
}
static unsigned int mymodule_poll(struct file *filp,struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct mymodule_dev *dev = filp->private_data;
mutex_lock(&dev->mutex);
poll_wait(filp,&dev->r_wait,wait);
poll_wait(filp,&dev->w_wait,wait);
if(dev->current_len !=0)
mask |= POLLIN | POLLRDNORM;
if(dev->current_len != 512)
mask |= POLLOUT | POLLWRNORM;
mutex_unlock(&dev->mutex);
return mask;
}
static long mymodule_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
struct mymodule_dev *dev = filp->private_data;
switch(cmd)
{
case BUF_CLEAR:
mutex_lock(&dev->mutex);
memset(dev->buf,0,512);
mutex_unlock(&dev->mutex);
printk(KERN_INFO"globalmem is set to zero\n");
break;
default:
return - EINVAL;
}
return 0;
}
//file_operation设备驱动文件操作结构体
static struct file_operations mymodule_fops = {
.owner = THIS_MODULE,
.open = mymodule_open,
.release = mymodule_release,
.read = mymodule_read,
.write = mymodule_write,
.poll = mymodule_poll,
.compat_ioctl = mymodule_ioctl,
.fasync = mymodule_fasync,
};
//初始化并添加cdev结构体
static void mymodule_cdev_setup(struct mymodule_dev *dev)
{
int err,devno=MKDEV(major,minor);
//初始化
cdev_init(&dev->cdev,&mymodule_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &mymodule_fops;
//注册,添加
err = cdev_add(&dev->cdev,devno,1);
if(err)
printk(KERN_NOTICE"error %d adding mymodule",err);
}
static int mymodule_probe(void)
{
//申请设备号
int result;
dev_t devno = MKDEV(major,minor);
if(major)
result = register_chrdev_region(devno,1,"mymodule");
else{
result = alloc_chrdev_region(&devno,minor,1,"mymodule");
major = MAJOR(devno);
minor = MINOR(devno);
}
if(result<0)
return result;
//动态申请设备结构体内存
mydev = kmalloc(sizeof(struct mymodule_dev),GFP_KERNEL);
if(!mydev){
result=-ENOMEM;
goto fail_malloc;
}
memset(mydev,0,sizeof(struct mymodule_dev));
//初始化互斥体
mutex_init(&mydev->mutex);
//cdev字符设备的初始化和添加
mymodule_cdev_setup(mydev);
//初始化等待队列长度
init_waitqueue_head(&mydev->r_wait);
init_waitqueue_head(&mydev->w_wait);
//注册设备节点
my_class = class_create(THIS_MODULE,"mymodule_t");
my_device = device_create(my_class,NULL,MKDEV(major,minor),NULL,"mymodules");
return 0;
fail_malloc:unregister_chrdev_region(devno,1);
return result;
}
static int mymodule_remove(void)
{
device_destroy(my_class,MKDEV(major,minor));
class_destroy(my_class);
//删除cdev结构体
cdev_del(&mydev->cdev);
kfree(mydev);
//注销设备号
unregister_chrdev_region(MKDEV(major,minor),1);
}
static struct platform_driver mymodule_drv = {
.porbe = mymodule_probe,
.remove = mymodule_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mymodules",
},
};
//模块加载
int __init mymodule_init(void)
{
int result = platform_driver_register(&mymodule_drv);
return result;
}
//模块卸载
void __exit mymodule_exit(void)
{
platform_driver_unregister(&mymodule_drv);
}
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");
编译后两个.ko 都加载后,在/dev下才出现 mymodules的设备文件,并且在/sys/devices/platform 下会出现mymodules的文件夹。在mymodules文件夹下会有driver文件,它是指向/sys/bus/platform/devices/mymodules 的符号链接,这证明驱动和设备匹配上了。
上述的mymodule_device.c 的文件可以不写,只需要在内核/arch/arm/mach-vexpress/ct-ca9x4.c 中添加如下代码代码,然后重新编译内核与驱动。
static struct platform_device mymodule_device = {
.name = "mymodules",
.id = -1,
}