平台总线专题

设备驱动模型的由来

一个驱动代码是由下面6部分实现的。
1,实现入口函数 xxx_init()和卸载函数 xxx_exit()
2,申请设备号 register_chrdev (与内核相关)
3,利用udev/mdev机制创建设备文件(节点) class_create, device_create (与内核相关)
4,硬件部分初始化
io资源映射 ioremap,内核提供gpio库函数 (与硬件相关)
注册中断(与硬件相关)
5,构建 file_operation结构 (与内核相关)
6,实现操作硬件方法 xxx_open,xxx_read,xxxx_write

其中除了第四部分由所不同外,其余部分基本相同,为了节约时间,提高开发效率,提出了代码复用的概念。
通过代码复用概念实现的代码,具备了可移植性。
即:驱动代码框架只写一次,但其中涵盖了绝大多数可能会发生的情况,在实际应用中,不论是何种设备的驱动,直接拿来用,
只需要修改第四步,硬件初始化部分即可。

sysfs文件系统的介绍


举个例子,usb2是一个鼠标,该设备文件存储在/sys/Devices下,同时软连接到/sys/Bus下用于与drvicers配对,从而实现代码复用。
同时也会软连接到/sys/Classes/Inputdevs目录下,因为它同时也是一个输入型的usb设备,软连接到Classes目录下,便于形成拓扑图,便于查阅管理。

关于 /sys 目录:
    block:用于管理块设备,系统中的每一个块设备会在该目录下对应一个子目录。
    bus:用于管理总线,每注册一条总线,在该目录下有一个对应的子目录。
    其中,每个总线子目录下会有两个子目录:devices和drivers。
    devices包含系统中所有属于该总线的的设备。
    class:将系统中的设备按功能分类。
    dev:该目录包含已注册的设备号(设备节点)的视图,包括char和block
    kernel:内核中的相关参数。
    module:内核中的模块信息。
    fireware:内核中的固件信息。
    fs:描述内核中的文件系统。

梳理event0的文件存储脉络:

linux@ubuntu:/dev/input$ ls -l event*
crw-rw---- 1 root input 13, 64 Dec 28 01:02 event0  //就拿这个event0 做实验吧 通过event0没法推断出设备的名字 试试其他渠道
crw-rw---- 1 root input 13, 65 Dec 28 01:02 event1
crw-rw---- 1 root input 13, 66 Dec 28 01:02 event2
crw-rw---- 1 root input 13, 67 Dec 28 01:02 event3
crw-rw---- 1 root input 13, 68 Dec 28 01:02 event4

linux@ubuntu:/sys/class/input$ ls
event0  event1  event2  event3  event4  input0  input1  input3  input4  input5  mice  mouse0  mouse1  mouse2
linux@ubuntu:/sys/class/input/event0$ ls
dev  device  power  subsystem  uevent
linux@ubuntu:/sys/class/input/event0$ cd device
linux@ubuntu:/sys/class/input/event0/device$ ls
capabilities  device  event0  id  modalias  name  phys  power  properties  subsystem  uevent  uniq
linux@ubuntu:/sys/class/input/event0/device$ cat name
Power Button        //这里获取到了event0 的设备名 是一个电源开关

linux@ubuntu:/sys/class/input/event0$ cat uevent
MAJOR=13    //查询到主次设备号 与/devinput 下对比,都是13,64 是同一个设备
MINOR=64
DEVNAME=input/event0    

创建bus的代码编程


平台总线文件夹

linux@ubuntu:/sys/bus/platform$ ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent

设备驱动模型:bus, driver, device

struct bus_type :总线对象,描述一个总线,管理device和driver,完成匹配
struct bus_type {
	const char		*name;
	int (*match)(struct device *dev, struct device_driver *drv);
}

注册和注销:

	int bus_register(struct bus_type *bus)
	void bus_unregister(struct bus_type *bus)
[root@farsight ]# insmod *.ko
[  585.132692] mybus: loading out-of-tree module taints kernel.
[root@farsight ]# ls /sys/bus
amba          event_source  mdio_bus      platform      usb
cec           genpd         mipi-dsi      scsi          workqueue
clockevents   gpio          mmc           sdio
clocksource   hid           mmc_rpmb      serio
container     i2c           mybus         soc
cpu           iio           nvmem         spi
[root@farsight ]# ls /sys/bus/mybus/
devices            drivers_autoprobe  uevent
drivers            drivers_probe

可以在 /sys/bus 目录下看到mybus文件夹了,

设备驱动模型:bus, driver, device

bus总线对象

struct bus_type :总线对象,描述一个总线,管理device和driver,完成匹配
struct bus_type {
const char *name;
int (*match)(struct device *dev, struct device_driver *drv);
}
注册和注销
int bus_register(struct bus_type *bus)
void bus_unregister(struct bus_type *bus)

device对象

device对象:设备对象,描述设备信息,包括地址,中断号,甚至其他自定义的数据
struct device {
struct kobject kobj;  //所有对象的父类
const char *init_name;
// 在总线中会有一个名字,用于做匹配,在/sys/bus/mybus/devices/名字
struct bus_type *bus; //指向该device对象依附于总线的对象
void *platform_data; // 自定义的数据,指向任何类型数据

注册和注销的方法:
int device_register(struct device *dev)
void device_unregister(struct device *dev)

river对象

driver对象:描述设备驱动的方法(代码逻辑)
struct device_driver {
const char *name;
// 在总线中会有一个名字,用于做匹配,在/sys/bus/mybus/drivers/名字
struct bus_type *bus;//指向该driver对象依附于总线的对象
int (*probe) (struct device *dev); // 如果device和driver匹配之后,driver要做的事情
int (*remove) (struct device *dev); // 如果device和driver从总线移除之后,driver要做的事情
}
注册和注销:
int driver_register(struct device_driver *drv)
void driver_unregister(struct device_driver *drv)

match总线匹配

如何实现总线匹配,匹配成功之后会自动调用driver的probe方法:
1, 实现bus对象中 match方法
2, 保证driver和device中名字要一样

配对函数(match)、探测函数(probe)和卸载函数

1,int (*match)(struct device *dev, struct device_driver *drv);--总线bus
当总线上添加了新设备或者新驱动函数的时候,内核会调用一次或者多次这个函数。
如果现在添加了一个新的驱动(driver),内核就会调用所属总线(bus)的match函数,
配对总线上所有的设备(device),如果驱动能够对应处理其中一个设备,函数返回1,
告诉内核配对成功。一般的,match函数是判断设备的结构体成员device->bus_id
和驱动函数的结构体成员device_driver->name是否一致,如果一致,
那就表明配对成功。

2,int (*probe)(struct device *dev);---- 驱动driver
当配对(match)成功后,内核就会调用指定驱动中的probe函数来查询设备能否被该
驱动操作,如果可以,驱动就会对该设备进行相应的操作,如初始化。所以说,真正
的驱动函数入口是在probe函数中。

3, int (*remove) (struct device *dev); ---驱动driver
当设备从总线中移除时,内核会调用驱动函数中的remove函数调用,进行一些设备
卸载相应的操作

平台总线模型:

为什么会有平台总线:
	用于平台升级:三星: 2410, 2440, 6410, s5pc100  s5pv210  4412
		硬件平台升级的时候,部分的模块的控制方式,基本上是类似的
		但是模块的地址是不一样

		gpio控制逻辑: 1, 配置gpio的输入输出功能: gpxxconf
					   2, 给gpio的数据寄存器设置高低电平: gpxxdata
					逻辑操作基本上是一样的
					但是地址不一样
		
		uart控制:1,设置8n1,115200, no AFC
					UCON,ULCON, UMODOEN, UDIV
				
				逻辑基本上是一样的
				但是地址不一样

问题:
	当soc升级的时候, 对于相似的设备驱动,需要编写很多次(如果不用平台总线)
	但是会有大部分重复代码

解决:引入平台总线	
		device(中断/地址)和driver(操作逻辑) 分离
	在升级的时候,只需要修改device中信息即可(中断/地址)
	实现一个driver代码能够驱动多个平台相似的模块,并且修改的代码量很少

平台总线中的三元素:

1, bus

platform_bus:不需要自己创建,开机的时候自动创建
	struct bus_type platform_bus_type = {
		.name		= "platform",
		.dev_groups	= platform_dev_groups,
		.match		= platform_match,
		.uevent		= platform_uevent,
		.pm		= &platform_dev_pm_ops,
	};
匹配方法:
	1,优先匹配pdriver中的id_table,里面包含了支持不同的平台的名字
	2,直接匹配driver中名字和device中名字
		struct platform_device *pdev = to_platform_device(dev);
		struct platform_driver *pdrv = to_platform_driver(drv);
		if (pdrv->id_table)// 如果pdrv中有idtable,平台列表名字和pdev中的名字
			return platform_match_id(pdrv->id_table, pdev) != NULL;

		/* fall-back to driver name match */
		return (strcmp(pdev->name, drv->name) == 0);

2,device对象:

	struct platform_device {
		const char	*name;  //用于做匹配
		int		id;  // 一般都是直接给-1
		struct device	dev; // 继承了device父类
		u32		num_resources; // 资源的个数
		struct resource	*resource; // 资源:包括了一个设备的地址和中断
	}
	//注册和注销
		int  platform_device_register(struct platform_device * pdev)void  platform_device_unregister(struct platform_device * pdev);

3,driver对象

	struct platform_driver {
			int (*probe)(struct platform_device *); //匹配成功之后被调用的函数
			int (*remove)(struct platform_device *);//device移除的时候调用的函数
			struct device_driver driver; //继承了driver父类
					const char		*name;//driver父类中的元素name
			const struct platform_device_id *id_table; //如果driver支持多个平台,在列表中写出来
	}
//注册和注销
		int platform_driver_register(struct platform_driver *drv);
		void platform_driver_unregister(struct platform_driver *drv);

编写代码: 编写一个能在多个平台下使用的led驱动

1,注册一个platform_device,定义资源:地址和中断

	struct resource {
		resource_size_t start; // 开始
		resource_size_t end; //结束
		const char *name; //描述,自定义
		unsigned long flags; //区分当前资源描述的是中断(IORESOURCE_IRQ)还是内存(IORESOURCE_MEM)
		struct resource *parent, *sibling, *child;
	};

2,注册一个platform_driver,实现操作设备的代码

	注册完毕,同时如果和pdev匹配成功,自动调用probe方法:
			probe方法: 对硬件进行操作
					a,注册设备号,并且注册fops--为用户提供一个设备标示,同时提供文件操作io接口
					b, 创建设备节点
					c, 初始化硬件
								ioremap(地址);  //地址从pdev需要获取
								readl/writle();
					d,实现各种io接口: xxx_open, xxx_read, ..
		获取资源的方式:		
		//获取资源
		// 参数1: 从哪个pdev中获取资源
		// 参数2:  资源类型
		// 参数3: 表示获取同种资源的第几个
				struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

[root@farsight ]# ./led_app
[ 26.829250] --------fops_open----------
[ 26.832025] --------fops_write----------
[ 27.854882] --------fops_write----------
[ 28.857548] --------fops_write----------
[ 29.860138] --------fops_write----------
[ 30.862723] --------fops_write----------
[ 31.865313] --------fops_write----------
[ 32.867971] --------fops_write----------
[ 33.757312] VMEM_VDD_2.8V: disabling
[ 33.870564] --------fops_write----------
[ 34.873153] --------fops_write----------
[ 35.875739] --------fops_write----------
^C[ 36.473797] --------fops_release----------