写文章的目的是想通过记录自己的学习过程,以便以后使用到相关的知识点可以回顾和参考。
platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver。platform总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
Linux系统内核使用bus_type结构体表示总线,此结构体定义在文件include/linux/device.h,bus_type 结构体内容如下:
struct bus_type {
const char *name; //总线名字
const char *dev_name;
struct device *dev_root;
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 (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct iommu_ops *iommu_ops;
struct subsys_private *p;
};
platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c,platform 总线定义如下:
struct bus_type platform_bus_type = {
.name = "platform", //总线名字
.dev_attrs = platform_dev_attrs,
.match = platform_match, //设备与驱动的匹配函数
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
里面有一个必要重要的函数platform_match,函数的作用是把驱动和设备匹配起来,使设备能够找到对应的驱动,驱动能够找到对应的设备,platform_match函数定义在文件drivers/base/platform.c 中,函数内容如下所示:
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 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); //第三种匹配方式
}
驱动和设备的匹配方式有三种,其中用得最多就是第三种,因为老版本的Linux不支持设备树,基本都是使用这种方式,直接比较驱动和设备的 name 字段,等下的实操也是采用这种方式。
而第一种是设备树采用的匹配方式,of_driver_match_device 函数定义在文include/linux/of_device.h 中。device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。
platform_driver 结 构 体 表 示 platform 驱动,此结构体定义在文件include/linux/platform_device.h 中,内容如下:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
probe函数,相当于驱动的入口函数,当驱动与设备匹配成功以后 probe 函数就会执行,非常重要的函数,一般驱动的提供者会编写,如果自己要编写一个全新的驱动,那么 probe 就需要自行实现。
remove函数,相当于驱动的出口函数。
driver成员,为 device_driver 结构体变量,Linux 内核里面大量使用到了面向对象的思维,device_driver 相当于基类,提供了最基础的驱动框架。plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。
device_driver 结构体定义在 include/linux/device.h,device_driver 结构体的内容如下:
struct device_driver {
const char *name; //name字段,platform_match函数就是把它与设备的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; //采用设备树的时候驱动使用的匹配表
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;
};
name字段,驱动的名字,platform_match函数就是把它与设备的name字段进行匹配的。
of_match_table成员,是一个数组,每个元素的类型为 of_device_id 结构体类型,of_device_id 结构体里面有一个 compatible 成员,对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。
当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,platform_driver_register 函数原型如下所示:
int platform_driver_register (struct platform_driver driver)
函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。
还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动,
platform_driver_unregister 函数原型如下:
void platform_driver_unregister(struct platform_driver drv)
函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。
返回值:无
platform_device 这个结构体表示 platform 设备,platform_device 结构体定义在文件include/linux/platform_device.h 中,结构体内容如下:
struct platform_device {
const char * name; //name字段,platform_match函数就是把它与驱动的name字段进行匹配的
int id;
struct device dev;
u32 num_resources; //资源数量
struct resource * resource; //资源
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
name字段:跟上面驱动部分提到的name字段对应,platform_match函数就是把它与驱动的name字段进行匹配的。
resource成员,是一个数组,它的元素的类型为 resource 结构体类型,里面存放资源,也就是设备信息,比如外设寄存器,中断号,GPIO号等。
resource 结构体内容如下:
struct resource {
resource_size_t start; //资源的起始
resource_size_t end; //资源的结束
const char *name; //资源的名字
unsigned long flags; //资源的类型
struct resource *parent, *sibling, *child;
};
start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止
地址。
name 表示资源名字。
flags 表示资源类型:
#define IORESOURCE_IO 0x00000100 ---->GPIO口号
#define IORESOURCE_MEM 0x00000200 ---->功能寄存器的物理地址内存区
#define IORESOURCE_IRQ 0x00000400 ---->中断号
#define IORESOURCE_DMA 0x00000800 ---->DMA
#define IORESOURCE_BUS 0x00001000 ---->BUS
使用platform_device结构体来描述设备信息后,还需要用到 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:
int platform_device_register(struct platform_device pdev)
函数参数和返回值含义如下:
pdev:要注册的 platform 设备。
返回值:负数,失败;0,成功。
如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform
设备,platform_device_unregister 函数原型如下:
void platform_device_unregister(struct platform_device pdev)
函数参数和返回值含义如下:
pdev:要注销的 platform 设备。
返回值:无
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LEDDEV_CNT 1 /* 设备号个数 */
#define LEDDEV_NAME "led" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 定义一个资源数组 */
static struct resource *ledresource[4];
/* 定义一个设备信息的结构体 */
struct leddev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct leddev leddev;
static int led_open(struct inode *inode, struct file *filp)
{
int i;
filp->private_data = &leddev; /* 设置私有数据 */
//配置GPIOE13、GPIOC17、GPIOC7、GPIOC8为输出模式
for(i=0; i<4; i++)
gpio_direction_output(ledresource[i]->start, 1);
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
unsigned char databuf[2];
unsigned char ledstat;
unsigned char lednum;
ret = copy_from_user(databuf, buf, cnt);
if(ret < 0)
{
printk(KERN_EMERG "kernel write failed!\r\n");
return -EFAULT;
}
lednum = databuf[0];
ledstat = databuf[1];
if(lednum >= 4){
printk(KERN_EMERG "kernel: lednum don't specification!\r\n");
return -EFAULT;
}
if(ledstat == LEDON){
gpio_set_value(ledresource[lednum]->start, 0);
printk(KERN_EMERG "LED %d open!\r\n",lednum);
}else if(ledstat == LEDOFF){
gpio_set_value(ledresource[lednum]->start, 1);
printk(KERN_EMERG "LED %d close!\r\n",lednum);
}
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.release = led_release,
};
/* 相当于入口函数 */
static int led_probe(struct platform_device *dev)
{
int i,ret;
printk(KERN_EMERG "kernel: led driver and device has matched!\r\n");
/* 获取资源 */
for(i=0;i<4;i++){
ledresource[i] = platform_get_resource(dev, IORESOURCE_IO, i);
if(ledresource[i] == NULL){
dev_err(&dev->dev, "No IO resource for always on\n");
return -ENXIO;
}
}
/* 申请GPIO */
for(i=0;i<4;i++){
ret = gpio_request(ledresource[i]->start, ledresource[i]->name);
if(ret != 0){
printk(KERN_EMERG "kernel: gpio_request failed!\r\n");
return -EIO;
}
}
/* 申请设备号 */
if(leddev.major){
/* 如果自定义了设备号 */
leddev.devid = MKDEV(leddev.major, 0);
ret = register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
}else{
/* 否则,动态申请设备号 */
ret = alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
leddev.major = MAJOR(leddev.devid);
leddev.minor = MINOR(leddev.devid);
}
printk(KERN_EMERG "led_major = %d\r\n",leddev.major);
printk(KERN_EMERG "led_minor = %d\r\n",leddev.minor);
if(ret < 0){
printk(KERN_EMERG "kernel register chrdev failed!\r\n");
return -EIO;
}
/* 字符设备初始化 */
leddev.cdev.owner = THIS_MODULE;
cdev_init(&leddev.cdev, &led_fops);
/* 把字符设备添加到内核 */
cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
/* 创建类 */
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
/* 创建设备 */
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
return 0;
}
/* 出口函数 */
static int led_remove(struct platform_device *dev)
{
int i;
/* 释放gpio */
for(i=0;i<4;i++)
gpio_free(ledresource[i]->start);
/* 删除设备 */
device_destroy(leddev.class, leddev.devid);
/* 删除类 */
class_destroy(leddev.class);
/* 把字符设备从内核中删除 */
cdev_del(&leddev.cdev);
/* 注销字符设备驱动 */
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
return 0;
}
/* platform 驱动结构体 */
static struct platform_driver led_drv = {
.driver = {
.owner = THIS_MODULE,
.name = "led-gpio",
},
.probe = led_probe,
.remove = led_remove,
};
/* 驱动模块加载 */
static int __init led_drv_init(void)
{
return platform_driver_register(&led_drv);
}
/* 驱动模块卸载 */
static void __exit led_drv_exit(void)
{
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzj");
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 释放 platform 设备 */
static void led_release(struct device *dev)
{
printk(KERN_EMERG "led device release!\r\n");
}
/* 设备资源信息 */
static struct resource led_resource[] = {
[0] = {
.start = PAD_GPIO_E+13,
.end = PAD_GPIO_E+13,
.name = "gpioe13",
.flags = IORESOURCE_IO //资源的类型
},
[1] = {
.start = PAD_GPIO_C+17,
.end = PAD_GPIO_C+17,
.name = "gpioc17",
.flags = IORESOURCE_IO //资源的类型
},
[2] = {
.start = PAD_GPIO_C+8,
.end = PAD_GPIO_C+8,
.name = "gpioc8",
.flags = IORESOURCE_IO //资源的类型
},
[3] = {
.start = PAD_GPIO_C+7,
.end = PAD_GPIO_C+7,
.name = "gpioc7",
.flags = IORESOURCE_IO //资源的类型
},
};
/* platform 设备结构体 */
static struct platform_device led_dev = {
.name = "led-gpio", //设备的名字,必须跟driver的名字一样
.id = -1,
.dev = {
.release = &led_release,
},
.num_resources = ARRAY_SIZE(led_resource), //资源的数目
.resource = led_resource, //资源的来源
};
/* 设备模块加载 */
static int __init led_dev_init(void)
{
return platform_device_register(&led_dev);
}
/* 设备模块注销 */
static void __exit led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzj");
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc , char *argv[])
{
int fd, ret;
char *filename;
unsigned char databuf[2];
if(argc != 4){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]); //需要控制哪个led
databuf[1] = atoi(argv[3]); //点亮还是关闭
/* 向设备驱动写数据 */
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0){
printf("LED Control failed!\r\n");
close(fd);
return -1;
}
/* 关闭设备 */
ret = close(fd);
if(ret < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}