三种Linux字符设备驱动写法-2:总线设备驱动框架

这篇文章中介绍Linux总线设备驱动框架,重点是体会整个框架,特别是感受一下分离的思想和抽象的方法,很多结构体中的成员不必深究到底如何实现或者有何作用,我将在今后的文章中深入探讨。

参考资料:

嵌入式Linux驱动学习-6.platform总线设备驱动模型

嵌入式Linux驱动学习-5.驱动的分层分离思想

1. 分离思想

回顾第一篇,最简单的驱动框架:

如下,在驱动程序中直接把资源(IO引脚)写死

static unsigned long gpio_va;
 
#define GPIO_OFT(x) ((x) - 0x56000000)
#define GPFCON  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000050)))
#define GPFDAT  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000054)))

有单片机基础,或者c程序写得多的朋友可能就发现这种写法的缺陷了:

当程序简单时还好,程序复杂了,又需要换硬件时,将会变得难以修改。所以需要将硬件相关部分的代码给抽离开,这就叫分离,硬件层一部分代码,驱动层一部分代码,在单片机中,常见的处理方法是利用单独的头文件定义引脚或者其他功能,比写在一个文件中好一些,但是如果有修改还是需要整个程序重新编译。

Linux系统中采用总线设备驱动模型,完美解决了这些问题。

引入platform_device/platform_driver,将“硬件资源”与“驱动”分离开

同样以led驱动为例,我们编写两个源文件,led_drv.c表示驱动,led_dev.c表示设备(资源),编译生成led_drv.ko和led_dev.ko两个文件,insmod后即可正常使用,当我们需要修改硬件时,只需要rmmod led_dev,然后重新加载即可,而不需要卸载修改led_drv。

2. platform_device

static int led_dev_init(void)
{
	platform_device_register(&led_dev);
	return 0;
}

static void led_dev_exit(void)
{
	platform_device_unregister(&led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);

先从入口看,和第一篇的基本驱动一样,只不过入口从register_chrdev变成了注册platform_device。

platform_device_register(&led_dev)函数中的led_dev是一个platform_device结构体

static struct platform_device led_dev = {
    .name         = "myled",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(led_resource),
    .resource     = led_resource,
    .dev = { 
    	.release = led_release, 
	},
};

结构体原型:

struct platform_device {
	const char	* name;
	u32		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;
};

第一项:name,取个名字

第二项:id,设备实例号,或者用“-1”表示只有一个实例。

第三项:dev,一个device结构体,这里实现一个空函数,不会报警告就行,其他成员用不到。之后会出文章细讲。

    .dev = { 
    	.release = led_release, 
	},
static void led_release(struct device * dev)
{
}

第四项:num_resources,资源的数目,ARRAY_SIZE(led_resource)返回资源数目

第五项:resource,资源数组

使用resource 结构体实现一个资源数组,数组内容就是我们的硬件资源

start为起始,end为结束,flags为资源类型,我的开发板GPF4、GPF5、GPF6为三个led灯

GPFCON为0x56000050,GPFDAT为0x56000054,定义如下资源,实际上有第一个就能控制所有灯,这里这样写是为了突出展示使用资源数组的作用和便捷性。

static struct resource led_resource[] = {
    {
        .start = 0x56000050,
        .end   = 0x56000050 + 8 - 1,
        .flags = IORESOURCE_MEM,
    },
    {
        .start = 4,
        .flags = IORESOURCE_IO,
    },
	{
        .start = 5,
        .flags = IORESOURCE_IO,
    },
	{
        .start = 6,
        .flags = IORESOURCE_IO,
    },

};

3. platform_driver

static int led_drv_init(void)
{
	platform_driver_register(&led_drv);
	return 0;
}

static void led_drv_exit(void)
{
	platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);

一样从入口看,注册platform_driver。

platform_driver_register(&led_drv)函数中的led_drv是一个platform_driver结构体

struct platform_driver led_drv = {
	.probe		= led_probe,
	.remove		= led_remove,
	.driver		= {
		.name	= "myled",
	}
};

结构体原型:

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 (*suspend_late)(struct platform_device *, pm_message_t state);
	int (*resume_early)(struct platform_device *);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
};

以后出文章细讲这个结构体,这里只需要几个基本成员:

1.probe,探针,在这个函数中要获取资源、注册字符设备,创建设备等

static int led_probe(struct platform_device *pdev)
{
	struct resource		*res;
	led_num=0;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpio_con = ioremap(res->start, res->end - res->start + 1);
	gpio_dat = gpio_con + 1;

	major = register_chrdev(0, "myled", &led_fops);
	cls = class_create(THIS_MODULE, "myled");

	while(1)
	{
		res = platform_get_resource(pdev, IORESOURCE_IO, led_num);
		if(res==NULL)
			break;
		led_num++;
		pin[led_num] = res->start;

		printk("led_probe, found led%d\n",led_num);

		class_device_create(cls, NULL, MKDEV(major, led_num), NULL, "led%d",led_num); /* /dev/led */
	}
	return 0;
}

使用platform_get_resource获取资源,pin[]数组中保存了引脚的值,然后循环创建设备节点,有几个led资源就创建几个led节点。

static int led_open(struct inode *inode, struct file *file)
{
	int minor;
	minor=iminor(inode);
	
	led_init(minor);

	return 0;	
}

static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
	
	int minor = iminor(file->f_path.dentry->d_inode);
	printk("write led%d\n",minor);

	copy_from_user(&val, buf, count); //	copy_to_user();

	led_ctl(minor,int);
	
	return 0;
}

file_operations结构体中的open和write和第一篇其实是差不多的,区别在于把初始化操作封装进了led_init()函数,把控制操作封装进了led_ctl()函数,并通过次设备号来区分对哪一个led进行操作。

从inode结构体中获取次设备号:

minor=iminor(inode);

从file结构体中获取次设备号:

int minor = iminor(file->f_path.dentry->d_inode);

2.remove,这个函数表示移除,销毁设备

static int led_remove(struct platform_device *pdev)
{
	int i;
	i=0;
	for(i=0;i<led_num;i++)
		{
			class_device_destroy(cls, MKDEV(major, i));
		}
	class_destroy(cls);
	unregister_chrdev(major, "myled");
	iounmap(gpio_con);
	
	return 0;
}

3.driver,这个结构体中的名字要与platform_device中的name对应,这样platform_dev与platform_drv才能对应起来。

insmod led_drv.ko没有反应很正常

insmod led_dev.ko后打印出了找到三个设备说明platform_dev和platform_drv对应上了

再通过命令看一下设备节点是否创建成功

接下来便可通过应用程序控制着三个led了。

./ledtest /dev/led1 on使led1亮,以此类推,应用程序源码和第一篇中的基本差不多。

这篇文章讲得不是很细致,因为重点其实就是感受一下分层思想,并且了解linux系统中一些抽象出来的结构体,实际上手工作,你在源码中搜索,便可以找到很多可以参考的代码,随着工作深入,自然会对这些结构体熟悉。今后我也将深入分析部分结构体。

源码https://download.csdn.net/download/freestep96/86731971

猜你喜欢

转载自blog.csdn.net/freestep96/article/details/127141555
今日推荐