Linux驱动-platform总线设备驱动

前言

写文章的目的是想通过记录自己的学习过程,以便以后使用到相关的知识点可以回顾和参考。

一、简介

platform总线是一种虚拟的总线,相应的设备则为platform_device,而驱动则为platform_driver。platform总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

二、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 驱动

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 设备

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 设备。
返回值:无

五、用platform总线设备驱动来点亮led

一、平台设备驱动程序led_drv.c:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <cfg_type.h>


#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");

二、平台设备程序led_dev.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <cfg_type.h>


/* 释放 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");

三、应用程序ledApp.c

#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;
}

四、测试

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值