欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux驱动学习(三)平台总线设备驱动模型

程序员文章站 2022-07-14 10:01:28
...

前言

Linux驱动的核心是向内核注册我们实现的file_operation结构体。编写GPIO类如LED的驱动可以非常简单,我们只需要实现file_operation结构体中的函数,具体为在open函数中实现对GPIO的初始化,在write函数中实现对GPIO的操作。这种方式虽然简单,但是缺点非常明显,把驱动和硬件操作绑定在一起,当我们更换引脚甚至是板子时,就需要重新编写,这时就需要引入分离分层的思想,将驱动层和硬件资源层分离。Linux的platform总线-设备-驱动模型就为我们提供了这样的方法。

1、总线设备驱动模型

平台总线-设备-驱动驱动模型示意图:
Linux驱动学习(三)平台总线设备驱动模型
平台总线platform是虚拟的总线,通过这三条总线可以实现对设备资源和驱动层的分离,需要做的主要工作可以简单概括为:

  1. 向内核注册硬件platform_device结构体
  2. 向内核注册驱动platform_driver结构体
  3. 实现platform_driver的成员函数probe,在里面进行设备创建

board_A_led.c中定义我们的设备device结构体:

static struct platform_device board_A_led_dev = {
        .name = "100ask_led",
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
};

chip_demo_gpio.c中,定义我们的driver结构体:

static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,//drv-dev匹配函数
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",
    },
};

chip_demo_gpio_driverboard_A_led_dev结构体中的name成员值均为100ask_led,这个成员非常重要,在platform_deviceplatform_driver结构体中name的值相同说明这个设备和这个驱动的关系是两者匹配,后面会说明其他的设备-驱动匹配方式。

1.1 匹配流程

当我们调用设备的入口函数安装驱动时,会使用函数platform_device_register向内核注册我们的platform_device结构体,注册后内核就会根据匹配的规则调用函数层层查找符合条件的驱动,查找到匹配的驱动时,就会进行绑定,最后调用platform_driver的成员函数probe,进行设备的创建和初始化等工作。
设备-驱动匹配时的函数调用关系如下:
Linux驱动学习(三)平台总线设备驱动模型
注意:注册device和driver并没有先后顺序之分,最终都会调用platform_driver中的probe函数创建设备节点。也就是说,只有两个结构体都注册到内核中,设备-驱动才可以进行匹配,在/dev/目录下创建设备节点。

1.2 匹配规则

前面我们所采用的是name成员进行设备-驱动的匹配的,下面是一些详细的匹配规则。
首选我们来看一下platform_device结构体:

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

记住几个比较重要的成员:namedriver_override

然后就是platform_driver结构体:

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;
	bool prevent_deferred_probe;
};

platform_driver 的成员结构体driver的类型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 */
	enum probe_type probe_type;

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

记住上面两个结构体的几个重要成员:nameid_table

1.3 具体匹配项

总线中有一个platform_match函数,用于将平台设备和驱动进行绑定,函数内容如下:
Linux驱动学习(三)平台总线设备驱动模型

platform_match函数可以看出,设备和驱动匹配时,比较匹配流程为:

首先,判断设备driver_override是否为空,存在driver_override项,说明此设备需要强制选择一个对应驱动,这个驱动的名字由驱动drvname成员存放,直接返回driver_overridedrv->name这两项的比较结果,不再进行后续查找匹配。

然后,是设备树节点和ACPI的方法。

接着,查看驱动platform_driver的成员id_table是否为空,这个成员含有多项时,说明此驱动支持多个设备,return会返回platform_match_id的结果,这个函数会将platform_driver.id_table[i].name成员与platform_device.name进行比较,判断此驱动支持的设备中是否包含当前的设备。

static const struct platform_device_id *platform_match_id(
			const struct platform_device_id *id,
			struct platform_device *pdev)
{
	while (id->name[0]) {
		if (strcmp(pdev->name, id->name) == 0) {
			pdev->id_entry = id;
			return id;
		}
		id++;
	}
	return NULL;
}

最后,如果前面的匹配方法都没使用,
就会判断platform_device.nameplatform_driver.driver.name,这是最后的匹配规则,如果这个规则也没设定,则设备和驱动无法通过平台总线匹配。

所以主要的匹配项(忽略设备树和ACPI方法)为:

  1. platform_device.driver_overrideplatform_driver.driver.name
  2. platform_device.nameplatform_driver.id_table[i].name
  3. platform_device.nameplatform_driver.driver.name

2、使用总线驱动模型

这里以简单的LED字符设备驱动为例,使用platform_device.nameplatform_driver.driver.name匹配项进行匹配的方法,编写总线设备驱动,实现硬件资源和驱动核心的分离。

2.1 文件说明

涉及驱动的主要文件为:
Linux驱动学习(三)平台总线设备驱动模型

  1. leddrv.c中是file_operations结构体成员函数的实现,在入口函数中向内核注册file_operation结构体,向上层应用程序提供方法,是Linux驱动的核心所在;

  2. board_A_led.c中,对封装GPIO的引脚名称等硬件资源,定义board_A_led_devplatform_device类型,入口函数led_dev_init中,向内核注册platform_device平台设备结构体。

  3. chip_demo_gpio.c与上面的结构类似,在入口函数中注册platform_driver结构体,同时在probe中创建设备节点,当devicedriver匹配时,就会调用platform_driver结构体的probe函数。

使用platform_device.nameplatform_driver.driver.name匹配项进行匹配的方法,所以在我们的platform_deviceplatform_driver结构体的成员driver的name必须相等,结构体定义如下:

platform_device :

static struct platform_device board_A_led_dev = {
        .name = "100ask_led",
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
};

platform_driver :

static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",
    },
};

2.2 源码

leddrv.c:

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

#include "led_opr.h"


/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;


#define MIN(a, b) (a < b ? a : b)


void led_class_create_device(int minor)
{
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
}
void led_class_destroy_device(int minor)
{
	device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{
	p_led_opr = opr;
}

EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);



/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode *inode = file_inode(file);
	int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	p_led_opr->ctl(minor, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	p_led_opr->init(minor);
	
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
	int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */


	led_class = class_create(THIS_MODULE, "100ask_led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		return -1;
	}
	
	return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	class_destroy(led_class);
	unregister_chrdev(major, "100ask_led");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

board_A_led.c:


#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include "led_resource.h"


static void led_dev_release(struct device *dev)
{
}

static struct resource resources[] = {
        {
                .start = GROUP_PIN(3,1),
                .flags = IORESOURCE_IRQ,
                .name = "100ask_led_pin",
        },
        {
                .start = GROUP_PIN(5,8),
                .flags = IORESOURCE_IRQ,
                .name = "100ask_led_pin",
        },
};


static struct platform_device board_A_led_dev = {
        .name = "100ask_led",
        .num_resources = ARRAY_SIZE(resources),
        .resource = resources,
        .dev = {
                .release = led_dev_release,
         },
};

static int __init led_dev_init(void)
{
    int err;
    
    err = platform_device_register(&board_A_led_dev);   
    
    return 0;
}

static void __exit led_dev_exit(void)
{
    platform_device_unregister(&board_A_led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);

MODULE_LICENSE("GPL");

chip_demo_gpio.c:

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>

#include "led_opr.h"
#include "leddrv.h"
#include "led_resource.h"

static int g_ledpins[100];
static int g_ledcnt = 0;

static char clk_en_flag;
static char pin_offset;
/*初始化引脚*/
static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */       
{   
    //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
    if(!CCM_CCGR5)//还未进行过映射
    {
			CCM_CCGR5								= ioremap(0x020C406C, 4);
			IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290014, 4);
			GPIO5_GDIR								= ioremap(0x020AC000+0x4, 4);
			GPIO5_DR								= ioremap(0x020AC000 + 0, 4);
    }

    printk("init gpio: group %d, pin %d\n", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
    switch(GROUP(g_ledpins[which]))//获取当前引脚所在的IO组
    {
        // case 0:
        // {
        //     printk("init pin of group 0 ...\n");
        //     break;
        // }
        case 1:
        {
            printk("init pin of group 1 ...\n");
            break;
        }
        case 2:
        {
            printk("init pin of group 2 ...\n");
            break;
        }
        case 3:
        {
            printk("init pin of group 3 ...\n");
            break;
        }
        case 4:
        {
            printk("init pin of group 3 ...\n");
            break;
        }
        case 5:
        {
            *CCM_CCGR5 |= (3<<30);//全程使能GPIO5时钟
            clk_en_flag = 1;
            printk("init pin of group 3 ...\n");
            break;
        } 
    }

    if(clk_en_flag)
    {
        switch(PIN(g_ledpins[which]))//根据具体的引脚进行初始化
        {
            case 0:
            {

                break;
            }
            case 1:
            {
                
                break;
            }
            case 2:
            {
                
                break;
            }
            case 3:
            {
                *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf);//清除低4位
                *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= (0x5);//MUX_MODE设置ALT5 0101复用为GPIO

                /* 
                *  设置IO方向
                *  GPIO5_GDIR      
                *  地址:0x020AC000+0x4
                *  bit3设置为1输出模式
                */
                *GPIO5_GDIR |= (1<<3);
                break;
            }
        }
    }
    
    return 0;
}

static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
    //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
    printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
    pin_offset = PIN(g_ledpins[which]);
    switch(GROUP(g_ledpins[which]))
    {
        case 0:
        {
            printk("set pin of group 0 ...\n");
            break;
        }
        case 1:
        {
            printk("set pin of group 1 ...\n");
            break;
        }
        case 2:
        {
            printk("set pin of group 2 ...\n");
            break;
        }
        case 5://GPIO
        {
            if(status){
                *GPIO5_DR &= ~(1 << pin_offset);//清零输出低电平,点灯on
            }else{
                *GPIO5_DR |=  (1 << pin_offset);//关灯off
            }
            
            printk("set pin of group 5 ...\n");
            break;
        }
    }

    return 0;


static struct led_operations board_demo_led_opr = {
    .init = board_demo_led_init,
    .ctl  = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_demo_led_opr;
}

static int chip_demo_gpio_probe(struct platform_device *pdev)
{
    struct resource *res;
    int i = 0;

    while (1)
    {
        res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
        if (!res)
            break;
        
        g_ledpins[g_ledcnt] = res->start;
        led_class_create_device(g_ledcnt);
        g_ledcnt++;
    }
    return 0;
    
}

static int chip_demo_gpio_remove(struct platform_device *pdev)
{
    struct resource *res;
    int i = 0;

    while (1)
    {
        res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
        if (!res)
            break;
        
        led_class_destroy_device(i);
        i++;
        g_ledcnt--;
    }
    return 0;
}


static struct platform_driver chip_demo_gpio_driver = {
    .probe      = chip_demo_gpio_probe,
    .remove     = chip_demo_gpio_remove,
    .driver     = {
        .name   = "100ask_led",
    },
};

static int __init chip_demo_gpio_drv_init(void)
{
    int err;
    
    err = platform_driver_register(&chip_demo_gpio_driver); 
    register_led_operations(&board_demo_led_opr);
    
    return 0;
}

static void __exit lchip_demo_gpio_drv_exit(void)
{
    platform_driver_unregister(&chip_demo_gpio_driver);
}

module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");

2.3 安装验证

Linux驱动学习(三)平台总线设备驱动模型

总结

使用平台总线设备驱动模型编写驱动程序,加入了分离分层的思想,可以编写出更容易移植修改的驱动程序。Linux驱动的框架还是挺大的,现在只学习了一点点皮毛,对内核的东西我也没有进行深入的了解,驱动的执行流程也只能暂时根据教程上面的来理解。但起码得先学会用,所以需要记录一下,方便后续学习,还得继续努力呀~~

相关标签: # Linux驱动