linux设备驱动开发之设备驱动软件架构思想

linux驱动的软件架构

  1. 我们知道linux驱动软件是为了挂接在cpu总线上的设备而出现的,这些设备有例如速度传感器、键盘输入、lcd显示等。我们的cpu通过总线访问这些设备,例如读、写 、控制等操作,访问的这些动作实现就是我们写的驱动。

  2. 从上面我们可以看出,不管访问哪种设备,我们几乎都要有读、写、控制等这些通用操作。所以为了追求代码上的复用性,我们可以把上面那些通用的逻辑操作封装成一个类似c++语法中类的对象,当调用具体的设备时,我们再把这些设备的具体信息融合到这个通用的封装好的对象中。

  3. 上面那个通用的封装对象我们内核中用input输入设备核心层这一层次的代码来实现。比如我们要写按键设备的驱动时,我们使用input核心层来写我们的驱动代码,这时我们只用实现接收按键值、按键中断处理函数等,前面那些读、写、控制啊等操作都由input核心层搞定了,这既简化了开发者编写驱动代码步骤,也提高了内核中驱动代码的复用性。
    下面是内核中输入设备的驱动分层示意图:
    在这里插入图片描述

  4. 随着内核的发展,内核中的驱动代码变得越来越具有可以移植性。 我们现在通常将设备驱动分为设备和驱动2个方面,为什么这样分呢,因为同一类的设备访问具有通用性,这种通用性操作就是驱动的表现,我们把这驱动分离出来,以后要是再加入一个类似的设备,那么我们只用修改这个设备的设备信息部分,驱动实现部分就不用再改了。
    下面是设备与驱动的分离示意图:
    在这里插入图片描述

  5. 把驱动与设备分离开来,那么我们该怎样把我们想要的设备与指定的驱动融合起来呢。我们内核中采用了platform总线这种层次的代码将二者融合起来使用。platform总线通过匹配注册到platform总线的设备和注册到platform总线的驱动,当二者匹配成功后,驱动就开始把设备信息通过指定的读取路径读入到驱动中。这样的话这个platform总线就相当于月老的工作了,匹配设备与驱动的,不过驱动是少量的,设备是大量的。

platform设备驱动

  1. platform总线
    这个总线是虚拟的,用于将总线上的设备与最小的基础设施连接起来。

  2. platform设备
    platform设备是系统中的一部分设备实例。它的结构体如下:

// include/linux/platform_device.h
struct platform_device {
	const char	* name;  // 这个名字用来绑定驱动的
	int		id;  // 设备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;
};
  1. platform驱动
    platform驱动也是驱动,遵循着标准驱动模型架构,不过多了像probe、remove这种接口。它的结构体如下:
// include/linux/platform_device.h
struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	// 下面3个接口是为了实现电源管理方面的功能接口,为低功耗而实现的
	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;
};

将globalfifo作为platform设备

现在我们要将前面写的globalfifo驱动挂接到platform总线上。

  1. 将globalfifo驱动移植为platform驱动
diff --git a/kernel/drivers/char/globalfifo/globalfifo.c b/kernel/drivers/char/globalfifo/globalfifo.c
index 85e007c..99a9578 100644
--- a/kernel/drivers/char/globalfifo/globalfifo.c
+++ b/kernel/drivers/char/globalfifo/globalfifo.c
@@ -21,6 +21,7 @@
 #include "linux/sched.h"
 #include "linux/types.h"
 #include "linux/poll.h"
+#include "linux/platform_device.h"
 
 #define  GLOBALFIFO_SIZE  (0X1000)
 #define  GLOBALFIFO_MAJOR  (231)
@@ -482,8 +483,97 @@ static void __exit globalfifo_exit(void)
     printk("globalfifo_exit success\n");
 }
 
-module_init(globalfifo_init)
-module_exit(globalfifo_exit)
+static int glboalfifo_probe(struct platform_device *pdev)
+{
+    int i = 0;
+    int ret = 0;
+    // 1. get the device id
+    dev_t devno = MKDEV(globalfifo_major, 0);
+#ifdef globalfifo_debug
+    printk(KERN_NOTICE "globalfifo_init\n");
+#endif
+    // 2. register the DEVICE_NUM of the char device
+    if (globalfifo_major)
+    {
+        ret = register_chrdev_region(devno, DEVICE_NUM, "globalfifo");
+    }
+    else
+    {
+        // automatic register char device
+        ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "globalfifo");
+    }
+    // check the error code
+    if (ret < 0)
+    {
+        return ret;
+    }
+
+    // 3. construct the globalfifo devices structure in the heap
+    globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev) * DEVICE_NUM, GFP_KERNEL);
+    if (!globalfifo_devp)
+    {
+        ret = -ENOMEM;
+#ifdef globalfifo_debug
+    printk(KERN_NOTICE "globalfifo_init = %d\n", __LINE__);
+#endif
+        goto fail_malloc;
+    }
+
+    // initialize the mutex
+    mutex_init(&globalfifo_devp->mutex);
+
+    // initialize the write and read wait queue head
+    init_waitqueue_head(&globalfifo_devp->r_wait);
+    init_waitqueue_head(&globalfifo_devp->w_wait);
+
+    // 4. add the globalfifo decices structure pointer to the kobjct map
+    for (i = 0; i < DEVICE_NUM; i++)
+    {
+        globalfifo_init_dev(globalfifo_devp + i, i);
+    }
+    printk("globalfifo_init success\n");
+    return 0;
+
+ fail_malloc:
+    unregister_chrdev_region(devno, DEVICE_NUM);
+    return ret;
+}
+
+static int glboalfifo_remove(struct platform_device *pdev)
+{
+    int i = 0;
+#ifdef globalfifo_debug
+    printk(KERN_NOTICE "globalfifo_exit\n");
+#endif
+    // 1. remove the globalfifo structure from teh kobject map
+    for (i = 0; i < DEVICE_NUM; i++)
+    {
+        cdev_del(&(globalfifo_devp + i)->chrdev);
+    }
+
+    // 2. free the glboalmem structure in the heap
+    kfree(globalfifo_devp);
+
+    // 3. unregister the device id
+    unregister_chrdev_region(MKDEV(globalfifo_major, 0), DEVICE_NUM);
+
+    // 4. remove the device id
+    printk("globalfifo_exit success\n");
+}
+
+static struct platform_driver globalfifo_driver = {
+    .driver = {
+        .name = "glboalfifo",  // used in devices binding
+        .owner = THIS_MODULE,
+    },
+    .probe = glboalfifo_probe,
+    .remove = glboalfifo_remove,
+};
+
+//module_init(globalfifo_init)
+//module_exit(globalfifo_exit)
+module_platform_driver(globalfifo_driver)
+
 // the declaration	of the author
 MODULE_AUTHOR("ZhongHuan Duan <15818411038@163.com>");
 // the declaration of the licence
  1. 在板文件中添加globalfifo这个platform设备
// arch/arm/mach-<soc名>/mach-<板名>.c
static struct platform_device globalfifo_device = {
	.name = "globalfifo",
	.id = -1,
};
static void __init globalfifo_machine_init(void)
{
	...
	platform_add_devices(globalfifo_device, ARRAY_SIZE(globalfifo_device));
	...
}
  • 上面会把定义的设备信息注册到系统中。如果一切顺利,我们会在/sys/devices/platform目录下看到一个名字叫globalfifo的子目录,/sys/devices/platform/globalfifo中会有一个driver文件,它是指向/sys/bus/platform/drivers/globalfifo的符号链接,这证明驱动和设备匹配上了。

platform设备资源和数据

资源结构体定义:

// include/linux/ioport.h
struct resource {
	resource_size_t start;  // 资源的开始值
	resource_size_t end;  // 资源的结束值
	const char *name;  // 资源名称
	unsigned long flags;  // 资源的标志,可以表示io、内存、中断等种类
	struct resource *parent, *sibling, *child;  // 资源的基类、同类、子类
};

我们通常关心start、end和flags这3个字段,它们分别标明了资源的开始值、结束值和类型,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资源。

资源的定义通常在板文件中,可以看一下网卡dm9000的资源定义:

#if defined(CONFIG_DM9000)
static struct resource dm9000_resource[] = {
	[0] = {
		.start	= AT91_CHIPSELECT_2,
		.end	= AT91_CHIPSELECT_2 + 3,
		.flags	= IORESOURCE_MEM
	},
	[1] = {
		.start	= AT91_CHIPSELECT_2 + 0x44,
		.end	= AT91_CHIPSELECT_2 + 0xFF,
		.flags	= IORESOURCE_MEM
	},
	[2] = {
		.flags	= IORESOURCE_IRQ
			| IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHEDGE,
	}
};

对于上面的网卡资源获取可以使用platform_get_resource获得,使用以下方法可以获得上面的3份资源:

db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

设备驱动的分层思想

输入设备驱动

  1. linux内核输入子系统框架
    在这里插入图片描述

  2. 分配、释放一个输入设备

struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);
  1. 注册、注销一个输入设备
int __must_check input_register_device(struct input_dev *);
void input_unregister_device(struct input_dev *);
  1. 报告输入事件
/* 报告指定 type 、 code 的输入事件 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/* 报告键值 */
void input_report_key(struct input_dev *dev, unsigned int code, int value);
/* 报告相对坐标 */
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
/* 报告绝对坐标 */void input_report_abs(struct input_dev *dev, unsigned int code, int value);
/* 报告同步事件 */
void input_sync(struct input_dev *dev);
  1. gpio通用驱动的探测函数
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
	const struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
	struct gpio_keys_drvdata *ddata;
	struct device *dev = &pdev->dev;
	struct gpio_keys_platform_data alt_pdata;
	struct input_dev *input;
	int i, error;
	int wakeup = 0;

	if (!pdata) {
		error = gpio_keys_get_devtree_pdata(dev, &alt_pdata);
		if (error)
			return error;
		pdata = &alt_pdata;
	}

	ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +
			pdata->nbuttons * sizeof(struct gpio_button_data),
			GFP_KERNEL);
	input = input_allocate_device();
	if (!ddata || !input) {
		dev_err(dev, "failed to allocate state\n");
		error = -ENOMEM;
		goto fail1;
	}

	ddata->input = input;
	ddata->n_buttons = pdata->nbuttons;
	ddata->enable = pdata->enable;
	ddata->disable = pdata->disable;
	mutex_init(&ddata->disable_lock);

	platform_set_drvdata(pdev, ddata);
	input_set_drvdata(input, ddata);

	input->name = pdata->name ? : pdev->name;
	input->phys = "gpio-keys/input0";
	input->dev.parent = &pdev->dev;
	input->open = gpio_keys_open;
	input->close = gpio_keys_close;

	input->id.bustype = BUS_HOST;
	input->id.vendor = 0x0001;
	input->id.product = 0x0001;
	input->id.version = 0x0100;

	/* Enable auto repeat feature of Linux input subsystem */
	if (pdata->rep)
		__set_bit(EV_REP, input->evbit);

	for (i = 0; i < pdata->nbuttons; i++) {
		const struct gpio_keys_button *button = &pdata->buttons[i];
		struct gpio_button_data *bdata = &ddata->data[i];

		error = gpio_keys_setup_key(pdev, input, bdata, button);
		if (error)
			goto fail2;

		if (button->wakeup)
			wakeup = 1;
	}

	error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
	if (error) {
		dev_err(dev, "Unable to export keys/switches, error: %d\n",
			error);
		goto fail2;
	}

	error = input_register_device(input);
	if (error) {
		dev_err(dev, "Unable to register input device, error: %d\n",
			error);
		goto fail3;
	}

	/* get current state of buttons that are connected to GPIOs */
	for (i = 0; i < pdata->nbuttons; i++) {
		struct gpio_button_data *bdata = &ddata->data[i];
		if (gpio_is_valid(bdata->button->gpio))
			gpio_keys_gpio_report_event(bdata);
	}
	input_sync(input);

	device_init_wakeup(&pdev->dev, wakeup);

	return 0;

 fail3:
	sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
 fail2:
	while (--i >= 0)
		gpio_remove_key(&ddata->data[i]);

	platform_set_drvdata(pdev, NULL);
 fail1:
	input_free_device(input);
	kfree(ddata);
	/* If we have no platform_data, we allocated buttons dynamically. */
	if (!pdev->dev.platform_data)
		kfree(pdata->buttons);

	return error;
}
  • 主要注意使用input核心层的流程。
  • 注意学习内核中已有的代码例程。

RTC设备驱动

  1. RTC(实时钟)借助电池供电,在系统掉电的情况下依然可以正常计时。它通常还具有产生周期性中断以及闹钟(Alarm)中断的能力,是一种典型的字符设备。
  2. rivers/rtc/rtc-dev.c实现了RTC驱动通用的字符设备驱动层,它实现了file_opearations的成员函数以及一些通用的关于RTC的控制代码。
  3. s3c系列rtc驱动代码:
static const struct rtc_class_ops s3c_rtcops = {
	.read_time	= s3c_rtc_gettime,
	.set_time	= s3c_rtc_settime,
	.read_alarm	= s3c_rtc_getalarm,
	.set_alarm	= s3c_rtc_setalarm,
	.proc		= s3c_rtc_proc,
	.alarm_irq_enable = s3c_rtc_setaie,
};
  • drivers/rtc/rtc-dev.c以及其调用的drivers/rtc/interface.c等RTC核心层相当于把file_operations中的open()、release()、读取和设置时间等都间接“转发”给了底层的实例。
  • 有空可以研究下RTC核心层调用具体底层驱动callback的过程。

FrameBuffer设备驱动

  1. Framebuffer(帧缓冲)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域内写入颜色值,对应的颜色会自动在屏幕上显示。
  2. framebuffer程序结构示意图:
    在这里插入图片描述
  • 上图所示为Linux帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间的file_operations结构体由drivers/video/fbdev/core/fbmem.c中的file_operations提供,而特定帧缓冲设备fb_info结构体的注册、注销以及其中成员的维护,尤其是fb_ops中成员函数的实现则由对应的xxxfb.c文件实现,fb_ops中的成员函数最终会操作LCD控制其硬件寄存器。

终端设备驱动

  1. 在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty(Teletype)来简称各种类型的终端设备。对于嵌入式系统而言,最普遍采用的是UART(Universal Asynchronous Receiver/Transmitter)串行端口,日常生活中简称串口。
  2. tty的层次结构图:
    在这里插入图片描述
  • ty_io.c本身是一个标准的字符设备驱动,它对上有字符设备的职责,实现file_operations成员函数。但是tty核心层对下又定义了tty_driver的架构,这样tty设备驱动的主体工作就变成了填充tty_driver结构体中的成员,实现其中的tty_operations的成员函数,而不再是去实现file_operations这一级的工作。

misc设备驱动

  1. 有部分类似globalmem、globalfifo的字符设备,确实不知道它属于什么类型,我们一般推荐大家采用miscdevice框架结构。miscdevice本质上也是字符设备,只是在miscdevice核心层的misc_init()函数中,通过register_chrdev(MISC_MAJOR,“misc”,
    &misc_fops)注册了字符设备,而具体miscdevice实例调用misc_register()的时候又自动完成了device_create()、获取动态次设备号的动作。

  2. miscdevice的主设备号是固定的,MISC_MAJOR定义为10。

  3. miscdevice结构体:

struct miscdevice  {
	// 次设备号,MISC_DYNAMIC_MINOR,miscdevice核心层会自动找一个空闲的次设备号,
	// 否则用minor指定的次设备号
	int minor;
	const char *name;  // 设备名称
	const struct file_operations *fops;
	struct list_head list;
	struct device *parent;
	struct device *this_device;
	const char *nodename;
	umode_t mode;
};
  1. miscdevice驱动的注册和注销
int misc_register(struct miscdevice * misc);
int misc_deregister(struct miscdevice *misc);
  1. miscdevice驱动一般结构(摘抄自宋宝华老师的设备驱动开发书籍)
static const struct file_operations xxx_fops = {
	.unlocked_ioctl = xxx_ioctl,
	.mmap
	= xxx_mmap,
	...
};
static struct miscdevice xxx_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name
	= "xxx",
	.fops
	= &xxx_fops
};
static int __init xxx_init(void){
	pr_info("ARC Hostlink driver mmap at 0x%p\n", __HOSTLINK__);
	return misc_register(&xxx_dev);
}
  • 在调用misc_register(&xxx_dev)时,该函数内部会自动调用device_create(),而device_create()会以xxx_dev作为drvdata参数。其次,在miscdevice核心层misc_open()函数的帮助下,在file_operations的成员函数中,xxx_dev会自动成为file的private_data(misc_open会完成file->private_data的赋值操作).

主机驱动与外设驱动分离

Linux中的SPI、I 2 C、USB等子系统都利用了典型的把主机驱动和外设驱动分离的想法,让主机端只负责产生总线上的传输波形,而外设端只是通过标准的API来让主机端以适当的波形访问自身。(摘抄自宋宝华老师的设备驱动书籍)

  • 主机端的驱动。根据具体的I 2 C、SPI、USB等控制器的硬件手册,操作具体的I 2 C、SPI、USB等控制器,产生总线的各种波形。
  • 连接主机和外设的纽带。外设不直接调用主机端的驱动来产生波形,而是调一个标准的API。由这个标准的API把这个波形的传输请求间接“转发”给了具体的主机端驱动。当然,在这里,最好把关于波形的描述也以某种数据结构标准化。
  • 外设端的驱动。外设接在I 2 C、SPI、USB这样的总线上,但是它们本身可以是触摸屏、网卡、声卡或者任意一种类型的设备。我们在相关的i2c_driver、spi_driver、usb_driver这种xxx_driver的probe()函数中去注册它具体的类型。当这些外设要求I 2 C、SPI、USB等去访问它的时候,它调用“连接主机和外设的纽带”模块的标准API。
  • 板级逻辑。板级逻辑用来描述主机和外设是如何互联的,它相当于一个“路由表”。假设板子上有多个SPI控制器和多个SPI外设,那究竟谁接在谁上面管理互联关系,既不是主机端的责任,也不是外设端的责任,这属于板级逻辑的责任。这部分通常出现在arch/arm/mach-xxx下面或者arch/arm/boot/dts下面.

总结

  1. 有空分析下内核中spi驱动代码,感受编码分层和复用的魅力。
  2. 有空分析下linux下的dma编程,熟悉应用技能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值