Linux驱动.之platform平台总线驱动层(一)

本文参考,https://blog.csdn.net/suifen_/article/details/135256555?spm=1001.2014.3001.5502

内核版本:Linux 2.6

设备驱动,分离与分层
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、 platform总线、设备与驱动概念
在Linux 2.6的设备驱动模型中,平台总线模型就是把原来的驱动C文件给分成两个C文件,一个是device.c,一个是driver.c,设备驱动分离思想。关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每 注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

把稳定不变的放在driver.c里面,需要变得放在devic.c里面。

一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2 C、SPI、uart、PCIE、APB、AHB等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总 线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,并不是一个物理的总线。

platform驱动框架模型

示例代码54.2.2.5 platform驱动框架
 	/* 设备结构体 */
1  	struct xxx_dev{
2   	struct cdev cdev;
3   	/* 设备结构体其他具体内容 */
4  	};
5  
6  	struct xxx_dev xxxdev;   /* 定义个设备结构体变量 */
7  
8  	static int xxx_open(struct inode *inode, struct file *filp)
9  	{    
10  	/* 函数具体内容 */
11  	return 0;
12 	}
13 
14 static ssize_t xxx_write(struct file *filp, const char __user *buf, 
size_t cnt, loff_t *offt)
15 	{
16  	/* 函数具体内容 */
17  	return 0;
18 	}
19 
20 /*
21  * 字符设备驱动操作集
22  */
23 	static struct file_operations xxx_fops = {
24  	.owner = THIS_MODULE,
25  	.open = xxx_open,
26  	.write = xxx_write,
27 	};
28 
29 /*
30  * platform驱动的probe函数
31  * 驱动与设备匹配成功以后此函数就会执行
32  */
33 	static int xxx_probe(struct platform_device *dev)
34 	{    
35  	......
36  	cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
37  	/* 函数具体内容 */
38  	return 0;
39 	}
40 
41 	static int xxx_remove(struct platform_device *dev)
42 	{
43  	......
44  	cdev_del(&xxxdev.cdev);/*  删除cdev */
45  	/* 函数具体内容 */
46  	return 0;
47 	}
48
49 /* 匹配列表 */
50 static const struct of_device_id xxx_of_match[] = {
51  	{ .compatible = "xxx-gpio" },
52 	 	{ /* Sentinel */ }
53 };
54
55 /* 
56  * platform平台驱动结构体
57  */
58 	static struct platform_driver xxx_driver = {
59  	.driver = {
60      	.name       = "xxx",
61      	.of_match_table = xxx_of_match,
62  	},
63  	.probe      = xxx_probe,
64  	.remove     = xxx_remove,
65 	};
66   
67 	/* 驱动模块加载 */
68 	static int __init xxxdriver_init(void)
69 	{
70  	return platform_driver_register(&xxx_driver);
71 }
72 
73 	/* 驱动模块卸载 */
74 	static void __exit xxxdriver_exit(void)
75 	{ 
76      	platform_driver_unregister(&xxx_driver);
77 	}
78 
79 	module_init(xxxdriver_init);
80 	module_exit(xxxdriver_exit);
81 	MODULE_LICENSE("GPL");
82 	MODULE_AUTHOR("zuozhongkai");

第1~27行,传统的字符设备驱动,所谓的platform驱动并不是独立于字符设备驱动、块设备驱动和网络设备驱动之外的其他种类的驱动。platform只是为了驱动的分离与分层而提出来的一种框架,其驱动的具体实现还是需要字符设备驱动、块设备驱动或网络设备驱动。

第33~39行,xxx_probe函数,当驱动和设备匹配成功以后此函数就会执行,以前在驱动入口init函数里面编写的字符设备驱动程序就全部放到此probe函数里面。比如注册字符设备驱动、添加cdev、创建类等等。
第41~47行,xxx_remove函数,platform_driver结构体中的remove成员变量,当关闭platfor备驱动的时候此函数就会执行,以前在驱动卸载exit函数里面要做的事情就放到此函数中来。比如,使用iounmap释放内存、删除cdev,注销设备号等等。
第50~53行,xxx_of_match匹配表,如果使用设备树的话将通过此匹配表进行驱动和设备的匹配。第51行设置了一个匹配项,此匹配项的compatible值为“xxx-gpio”,因此当设备树中设备节点的compatible属性值为“xxx-gpio”的时候此设备就会与此驱动匹配。第52行是一个标记,of_device_id表最后一个匹配项必须是空的。
第5865行,定义一个platform_driver结构体变量xxx_driver,表示platform驱动,第5962行设置paltform_driver中的device_driver成员变量的name和of_match_table这两个属性。其中name属性用于传统的驱动与设备匹配,也就是检查驱动和设备的name字段是不是相同。of_match_table属性就是用于设备树下的驱动与设备检查。对于一个完整的驱动程序,必须提供有设备树和无设备树两种匹配方法。最后63和64这两行设置probe和remove这两成员变量。
第68~71行,驱动入口函数,调用platform_driver_register函数向Linux内核注册一个platform驱动,也就是上面定义的xxx_driver结构体变量。
第74~77行,驱动出口函数,调用platform_driver_unregister函数卸载前面注册的platform驱动。
总体来说,platform驱动还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套上了一张“platform”的皮,目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分离与分层。

device.c里面写的是硬件资源,这里的硬件资源就是指寄存器的地址,中断号,时钟等硬件资源。在linux内核里面,我们是用一个结构体来描述硬件资源的。

资源的类型一般用下面的几个宏定义:

#define IORESOURCE_IO       0x00000100  //IO的内存
#define IORESOURCE_MEM      0x00000200  //表示一段物理内存
#define IORESOURCE_REG      0x00000300  
#define IORESOURCE_IRQ      0x00000400 //表示中断
#define IORESOURCE_DMA      0x00000800
#define IORESOURCE_BUS      0x00001000

在不支持设备树的 Linux 内核版本中需要在通过 platform_device 结构体来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:

extern int platform_device_register(struct platform_device *);

相应的设备称为platform_device,而驱动称为 platform_driver。

1、platform_device,它主要用于描述SOC上的片上资源,用于描述设备硬件信息的结构体,包括该硬件的所有资源(io,memory、中断、DMA等等)。platform 所描述的资源有一个共同点:在CPU 的总线上直接取址。平台设备会分到一个名称(用在驱动绑定中)以及一系列诸如地址和中断请求号(IRQ)之类的资源。

2、platform_driver,用于注册驱动到platform总线。

注意,所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,例如,在 S3C6410处理器中,把内部集成的I2 C、RTC、SPI、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。

1、platform_device,设备
platform_device结构体的定义

struct platform_device {
	const char * name;/* 设备名 /
	u32 id;
	struct device dev;
	u32 num_resources;/ 设备所使用各类资源数量 /
	struct resource * resource;/ 资源 */
};

实验注册一个device

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> 
//#include <linux/ioport.h> 
 
struct resource beep_res[] = {
	[0] = {
		.start = 0x20AC000,
		.end   = 0x20AC003,
		.flags = IORESOURCE_MEM,
		.name  = "GPIO5_DR"
	}
};
 
void beep_release(struct device *dev)
{
	printk("beep_release\n");
}
 
struct platform_device beep_device = {
	.name = "beep_test",//ls /sys/bus/platform/devices/可以看到
	.id   = -1,
	.resource = beep_res,
	.num_resources = ARRAY_SIZE(beep_res),
	.dev = {
		.release = beep_release
	}
};
 
static int device_init(void)
{
	printk("device_init \n");
	return platform_device_register(&beep_device);
}
 
static void  device_exit(void)
{
	platform_device_unregister(&beep_device);
	printk("device_exit \n");
}
 
module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");

在这里插入图片描述
在这里插入图片描述

2、platform_driver,驱动
platform_driver这个结构体中包含probe()、remove()、shutdown()、suspend()、 resume()函数,通常也需要由驱动实现
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 (*suspend_late)(struct platform_device *, pm_message_t state);
	int (*resume_early)(struct platform_device *);
	int (*resume)(struct platform_device *);
	struct pm_ext_ops *pm;
	struct device_driver driver;
};

写个代码测试注册驱动

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> int beep_probe(struct platform_device *pdev)
{
    pritnk("beep_probe \n");
    return 0;
}int beep_remove(struct platform_device *pdev)
{
    pritnk("beep_remove \n");
    return 0;
}
​
strcut platform_driver beep_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name  = "beep_test"
    }
};static int beep_driver_init(void)
{
    printk("beep_driver_init \n");
    return platform_driver_register(&beep_driver);
}static void  beep_driver_exit(void)
{
    platform_driver_unregister(&beep_driver);
    printk("beep_driver_exit \n");
}module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");

编译上面的驱动,加载驱动后发现没有进入probe函数。没有加name,所以没法匹配

可以发现这样写不会进入到probe函数。因为name不匹配
在这里插入图片描述
继续改
在这里插入图片描述
在这里插入图片描述
可以发现这次调用了probe函数。因为总算匹配上了。

总结
可以发现设备名称name要一样才能进入probe函数,id_table没有初始化值的时候,device name才会与结构体driver成员name匹配。否则会优先与id_table成员name进行匹配,匹配成功才会加载probe函数。
在这里插入图片描述
先加载driver.ko或先加载device.ko没有先后关系

二、字符设备中的两个重要结构体,struct device和struct device_driver

在include/linux/device.h中,Linux内核定义了设备模型中最重要的两个数据结构,struct device和struct device_driver。
内核管理的所有的驱动,都必须包含一个叫struct device_driver成员。描述的硬件设备,必须包含struct device结构体成员。
device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备,所以kernel就为设备和驱动它的driver定义了两个数据结构,分别是device和device_driver。Linux设备模型的核心逻辑,包括:
1、设备及设备驱动在kernel中的抽象、使用和维护;
2、设备及设备驱动的注册、加载、初始化原理;
3、设备模型在实际驱动开发过程中的使用方法。

上面的两个 platform结构体成员就分别有这两个结构体,如下。

struct platform_device {
	struct device dev;
};

struct platform_driver {
	struct device_driver driver;
};

1、struct device

// include/linux/device.h 
/**
 * struct device - The basic device structure
 * @parent:	The device's "parent" device, the device to which it is attached.
 * 		In most cases, a parent device is some sort of bus or host
 * 		controller. If parent is NULL, the device, is a top-level device,
 * 		which is not usually what you want.
 * @p:		Holds the private data of the driver core portions of the device.
 * 		See the comment of the struct device_private for detail.
 * @kobj:	A top-level, abstract class from which other classes are derived.
 * @init_name:	Initial name of the device.
 * @type:	The type of device.
 * 		This identifies the device type and carries type-specific
 * 		information.
 * @mutex:	Mutex to synchronize calls to its driver.
 * @lockdep_mutex: An optional debug lock that a subsystem can use as a
 * 		peer lock to gain localized lockdep coverage of the device_lock.
 * @bus:	Type of bus device is on.
 * @driver:	Which driver has allocated this
 * @platform_data: Platform data specific to the device.
 * 		Example: For devices on custom boards, as typical of embedded
 * 		and SOC based hardware, Linux often uses platform_data to point
 * 		to board-specific structures describing devices and how they
 * 		are wired.  That can include what ports are available, chip
 * 		variants, which GPIO pins act in what additional roles, and so
 * 		on.  This shrinks the "Board Support Packages" (BSPs) and
 * 		minimizes board-specific #ifdefs in drivers.
 * @driver_data: Private pointer for driver specific info.
 * @links:	Links to suppliers and consumers of this device.
 * @power:	For device power management.
 *		See Documentation/driver-api/pm/devices.rst for details.
 * @pm_domain:	Provide callbacks that are executed during system suspend,
 * 		hibernation, system resume and during runtime PM transitions
 * 		along with subsystem-level and driver-level callbacks.
 * @em_pd:	device's energy model performance domain
 * @pins:	For device pin management.
 *		See Documentation/driver-api/pin-control.rst for details.
 * @msi_lock:	Lock to protect MSI mask cache and mask register
 * @msi_list:	Hosts MSI descriptors
 * @msi_domain: The generic MSI domain this device is using.
 * @numa_node:	NUMA node this device is close to.
 * @dma_ops:    DMA mapping operations for this device.
 * @dma_mask:	Dma mask (if dma'ble device).
 * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all
 * 		hardware supports 64-bit addresses for consistent allocations
 * 		such descriptors.
 * @bus_dma_limit: Limit of an upstream bridge or bus which imposes a smaller
 *		DMA limit than the device itself supports.
 * @dma_range_map: map for DMA memory ranges relative to that of RAM
 * @dma_parms:	A low level driver may set these to teach IOMMU code about
 * 		segment limitations.
 * @dma_pools:	Dma pools (if dma'ble device).
 * @dma_mem:	Internal for coherent mem override.
 * @cma_area:	Contiguous memory area for dma allocations
 * @dma_io_tlb_mem: Pointer to the swiotlb pool used.  Not for driver use.
 * @archdata:	For arch-specific additions.
 * @of_node:	Associated device tree node.
 * @fwnode:	Associated device node supplied by platform firmware.
 * @devt:	For creating the sysfs "dev".
 * @id:		device instance
 * @devres_lock: Spinlock to protect the resource of the device.
 * @devres_head: The resources list of the device.
 * @knode_class: The node used to add the device to the class list.
 * @class:	The class of the device.
 * @groups:	Optional attribute groups.
 * @release:	Callback to free the device after all references have
 * 		gone away. This should be set by the allocator of the
 * 		device (i.e. the bus driver that discovered the device).
 * @iommu_group: IOMMU group the device belongs to.
 * @iommu:	Per device generic IOMMU runtime data
 * @removable:  Whether the device can be removed from the system. This
 *              should be set by the subsystem / bus driver that discovered
 *              the device.
 *
 * @offline_disabled: If set, the device is permanently online.
 * @offline:	Set after successful invocation of bus type's .offline().
 * @of_node_reused: Set if the device-tree node is shared with an ancestor
 *              device.
 * @state_synced: The hardware state of this device has been synced to match
 *		  the software state of this device by calling the driver/bus
 *		  sync_state() callback.
 * @can_match:	The device has matched with a driver at least once or it is in
 *		a bus (like AMBA) which can't check for matching drivers until
 *		other devices probe successfully.
 * @dma_coherent: this particular device is dma coherent, even if the
 *		architecture supports non-coherent devices.
 * @dma_ops_bypass: If set to %true then the dma_ops are bypassed for the
 *		streaming DMA operations (->map_* / ->unmap_* / ->sync_*),
 *		and optionall (if the coherent mask is large enough) also
 *		for dma allocations.  This flag is managed by the dma ops
 *		instance from ->dma_supported.
 *
 * At the lowest level, every device in a Linux system is represented by an
 * instance of struct device. The device structure contains the information
 * that the device model core needs to model the system. Most subsystems,
 * however, track additional information about the devices they host. As a
 * result, it is rare for devices to be represented by bare device structures;
 * instead, that structure, like kobject structures, is usually embedded within
 * a higher-level representation of the device.
 */
struct device {
	struct kobject kobj;
	struct device		*parent;

	struct device_private	*p;

	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set_drvdata/dev_get_drvdata */
#ifdef CONFIG_PROVE_LOCKING
	struct mutex		lockdep_mutex;
#endif
	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct dev_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_ENERGY_MODEL
	struct em_perf_domain	*em_pd;
#endif

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	raw_spinlock_t		msi_lock;
	struct list_head	msi_list;
#endif
#ifdef CONFIG_DMA_OPS
	const struct dma_map_ops *dma_ops;
#endif
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
	u64		bus_dma_limit;	/* upstream dma constraint */
	const struct bus_dma_region *dma_range_map;

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

#ifdef CONFIG_DMA_DECLARE_COHERENT
	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#endif
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
#ifdef CONFIG_SWIOTLB
	struct io_tlb_mem *dma_io_tlb_mem;
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */
	struct fwnode_handle	*fwnode; /* firmware device node */

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;
	struct dev_iommu	*iommu;

	enum device_removable	removable;

	bool			offline_disabled:1;
	bool			offline:1;
	bool			of_node_reused:1;
	bool			state_synced:1;
	bool			can_match:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
	bool			dma_coherent:1;
#endif
#ifdef CONFIG_DMA_OPS_BYPASS
	bool			dma_ops_bypass : 1;
#endif
};

device结构很复杂(不过linux内核的开发人员素质是很高的,该接口的注释写的非常详细,感兴趣的同学可以参考内核源代码),这里将会选一些对理解设备模型非常关键的字段进行说明。

parent //该设备的父设备,一般是该设备所从属的bus、controller等设备。
p //一个用于struct device的私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/prent等设备中的链表头等等,具体可查看源代码。

kobj //该数据结构对应的struct kobject。

init_name //该设备的名称。注:在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据“bus name + device ID”的方式创造。

type struct device_type //结构是新版本内核新引入的一个结构,它和struct device关系,非常类似stuct kobj_type和struct kobject之间的关系,后续会再详细说明。

bus //该device属于哪个总线(后续会详细描述)。

driver //该device对应的device driver。

platform_data //一个指针,用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存在这里,需要使用的时候,再拿出来,因此设备模型并不关心该指针得实际含义。

power、pm_domain //电源管理相关的逻辑,后续会由电源管理专题讲解。

pins,"PINCTRL”功能,暂不描述。

numa_node,"NUMA”功能,暂不描述。

dma_mask~archdata,DMA相关的功能,暂不描述。

devt,dev_t是一个32位的整数,它由两个部分(Major和Minor)组成,在需要以设备节点的形式(字符设备和块设备)向用户空间提供接口的设备中,当作设备号使用。在这里,该变量主要用于在sys文件系统中,为每个具有设备号的device,创建/sys/dev/* 下的对应目录,
如下:
ls /sys/dev/char/1:
1:1/ 1:11/ 1:13/ 1:14/ 1:2/ 1:3/ 1:5/ 1:7/ 1:8/ 1:9/

class //该设备属于哪个class。

groups //该设备的默认attribute集合。将会在设备注册时自动在sysfs中创建对应的文件。

2、struct device_driver

// include/linux/device/driver.h 
/**
 * struct device_driver - The basic device driver structure
 * @name:	Name of the device driver.
 * @bus:	The bus which the device of this driver belongs to.
 * @owner:	The module owner.
 * @mod_name:	Used for built-in modules.
 * @suppress_bind_attrs: Disables bind/unbind via sysfs.
 * @probe_type:	Type of the probe (synchronous or asynchronous) to use.
 * @of_match_table: The open firmware table.
 * @acpi_match_table: The ACPI match table.
 * @probe:	Called to query the existence of a specific device,
 *		whether this driver can work with it, and bind the driver
 *		to a specific device.
 * @sync_state:	Called to sync device state to software state after all the
 *		state tracking consumers linked to this device (present at
 *		the time of late_initcall) have successfully bound to a
 *		driver. If the device has no consumers, this function will
 *		be called at late_initcall_sync level. If the device has
 *		consumers that are never bound to a driver, this function
 *		will never get called until they do.
 * @remove:	Called when the device is removed from the system to
 *		unbind a device from this driver.
 * @shutdown:	Called at shut-down time to quiesce the device.
 * @suspend:	Called to put the device to sleep mode. Usually to a
 *		low power state.
 * @resume:	Called to bring a device from sleep mode.
 * @groups:	Default attributes that get created by the driver core
 *		automatically.
 * @dev_groups:	Additional attributes attached to device instance once
 *		it is bound to the driver.
 * @pm:		Power management operations of the device which matched
 *		this driver.
 * @coredump:	Called when sysfs entry is written to. The device driver
 *		is expected to call the dev_coredump API resulting in a
 *		uevent.
 * @p:		Driver core's private data, no one other than the driver
 *		core can touch this.
 *
 * The device driver-model tracks all of the drivers known to the system.
 * The main reason for this tracking is to enable the driver core to match
 * up drivers with new devices. Once drivers are known objects within the
 * system, however, a number of other things become possible. Device drivers
 * can export information and configuration variables that are independent
 * of any specific device.
 */
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);
	void (*sync_state)(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 attribute_group **dev_groups;

	const struct dev_pm_ops *pm;
	void (*coredump) (struct device *dev);

	struct driver_private *p;
};

device_driver就简单多了(在早期的内核版本中driver的数据结构为"struct driver”,不知道从哪个版本开始,就改成device_driver了):

name //该driver的名称。和device结构一样,该名称非常重要,后面会再详细说明。

bus // 该driver所驱动设备的总线设备。为什么driver需要记录总线设备的指针呢?因为内核要保证在driver运行前,设备所依赖的总线能够正确初始化。

owner、mod_name //內核module相关的变量,暂不描述。

suppress_bind_attrs //是不在sysfs中启用bind和unbind attribute,如下:
ls /sys/bus/platform/drivers/switch-gpio/
bind uevent unbind
在kernel中,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制。这种机制是在bus.c中完成的,后面会详细解释。

probe、remove //这两个接口函数用于实现driver逻辑的开始和结束。Driver是一段软件code,因此会有开始和结束两个代码逻辑,就像PC程序,会有一个main函数,main函数的开始就是开始,return的地方就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备(就是为热拔插起的!)。

shutdown、suspend、resume、pm //电源管理相关的内容,会在电源管理专题中详细说明。

groups //和struct device 结构中的同名变量类似,driver也可以定义一些默认attribute,这样在将driver注册到内核中时,内核设备模型部分的代码(driver/base/driver.c)会自动将这些attribute添加到sysfs中。

p //driver core的私有数据指针,其它模块不能访问。

设备模型框架下驱动开发的基本步骤
在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:
步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。
步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。
这两步完成后,内核会在合适的时机(后面会讲),调用struct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。而所有的驱动程序逻辑,都会由这些回调函数实现,此时,驱动开发者眼中便不再有“设备模型”,转而只关心驱动本身的实现。

一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用(也正是因为这个原因,本文并不会过多涉及device、device_driver等模块的实现细节)。
内核提供很多struct device结构的操作接口(具体可以参考include/linux/device.h和drivers/base/core.c的代码),主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。
device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。
device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。
driver开发者可以在struct device变量中,保存描述设备特征的信息,如寻址空间、依赖的GPIOs等,因为device指针会在执行probe等接口时传入,这时driver就可以根据这些信息,执行相应的逻辑操作了。

设备驱动probe的时机
所谓的"probe”,是指在Linux内核中,如果存在相同名称的device和device_driver(注:还存在其它方式,我们先不关注了),内核就会执行device_driver中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作("remove”是它的反操作,发生在device或者device_driver任何一方从内核注销时,其原理类似,就不再单独说明了)。

设备驱动probe的时机有如下几种(分为自动触发和手动触发):
将struct device类型的变量注册到内核中时自动触发(device_register,device_add,device_create_vargs,device_create)
将struct device_driver类型的变量注册到内核中时自动触发(driver_register)
手动查找同一bus下的所有device_driver,如果有和指定device同名的driver,执行probe操作(device_attach)
手动查找同一bus下的所有device,如果有和指定driver同名的device,执行probe操作(driver_attach)
自行调用driver的probe接口,并在该接口中将该driver绑定到某个device结构中----即设置dev->driver(device_bind_drive

注2:probe动作实际是由bus模块(在下面文章讲解)实现的,这不难理解:device和device_driver都是挂载在bus这根线上,因此只有bus最清楚应该为哪些device、哪些driver配对。

注3:每个bus都有一个drivers_autoprobe变量,用于控制是否在device或者driver注册时,自动probe。该变量默认为1(即自动probe),bus模块将它开放到sysfs中了,因而可在用户空间修改,进而控制probe行为。

3、platform bus总线
系统中为platform总线定义了一个bus_type的实例platform_bus_type

 struct bus_type platform_bus_type = {
	 .name = “platform”,
	 .dev_attrs = platform_dev_attrs,
	 .match = platform_match,
	.uevent = platform_uevent,
	.pm = PLATFORM_PM_OPS_PTR,
};

EXPORT_SYMBOL_GPL(platform_bus_type);

这里要重点关注其match()成员函数,正是此成员表明了platform_device和platform_driver之间如何匹配

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev;
	pdev = container_of(dev, struct platform_device, dev);
	return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

在这里插入图片描述

三、实验,将自己写的globalfifo字符设备驱动,修改成挂接到platform总线上的驱动

globalfifo字符设备驱动原代码
把globalmem中的全局内存变成一个FIFO,只有当FIFO中有数据的时候(即有进程把数据写到这个FIFO而且没有被读进程读空),读进程才能把数据读出,而且读取后的数据会从globalmem的全局内存中被拿掉;只有当fifo非满时(即还有一些空间未被写,或写满后被读进程读出了数据),写进程才能写入数据。现在将globalmem重命名为“globalfifo",在globalfifo中,读fifo将唤醒写fifo,写fifo也将唤醒读fifo.
支持阻塞操作的globalfifo设备驱动,在globalfifo设备结构体上需要添加两个等待队列头,分别对应于读和写。

#include<linux/module.h>  
#include<linux/types.h>  
#include<linux/fs.h>  
#include<linux/errno.h>  
#include<linux/mm.h>  
#include<linux/sched.h>  
#include<linux/init.h>  
#include<linux/cdev.h>  
#include<asm/io.h>  
#include<asm/uaccess.h>  
#include<linux/poll.h>  
#include <linux/slab.h>
#include <linux/ioctl.h>
  
#define GLOBALFIFO_SIZE  10  /*全局fifo最大10字节*/  
#define FIFO_CLEAR 0X1       /*清0全局内存的长度*/  
#define GLOBALFIFO_MAJOR 100 /*主设备号*/  
  
static int globalfifo_major = GLOBALFIFO_MAJOR;  
  
/*globalfifo设备结构体*/  
struct globalfifo_dev{  
	struct cdev cdev;                   /*cdev结构体*/  
	unsigned int current_len;           /*fifo有效数据长度*/  
    unsigned char mem[GLOBALFIFO_SIZE]; /*全局内存*/  //内核的共享数据区
    struct semaphore sem;               /*并发控制用的信号量*/  
    wait_queue_head_t r_wait;           /*阻塞读用的等待队列,内核双向循环链表*/  
    wait_queue_head_t w_wait;           /*阻塞写用的等待队列头*/  
};  
  
struct globalfifo_dev *globalfifo_devp; /*设备结构体指针*/ 
/*globalfifo读函数*/  //cat /dev/xxx
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)  
{  
    int ret;  
    struct globalfifo_dev *dev = filp->private_data;  //private_data全局变量
    DECLARE_WAITQUEUE(wait, current);  //定义当前进程的等待队列wait,current指针指向当前在运行的进程
  
    down(&dev->sem);                     /*获得信号量*/  
    add_wait_queue(&dev->r_wait, &wait); /*加入读等待队列头 到内核*/ //把wait添加到等待队列头r_wait指向的等待队列链表中,并不代表已经睡眠了,还需要调度函数的调度
  
    /*等待FIFO 非空*/ //如果共享数据区mem的数据长度为0,就应该阻塞该进程
    if(dev->current_len == 0){  
        if(filp->f_flags & O_NONBLOCK){   /*如果进程为 非阻塞打开 设备文件*/  
            ret = -EAGAIN;  //再进行一次读操作
            goto out;  
        }  
        __set_current_state(TASK_INTERRUPTIBLE); /*改变进程状态为睡眠*/  
        up(&dev->sem);                           /*释放信号量*/  
  
        schedule();                              /*调度其他进程执行*/  //此时读进程才会真正的睡眠,直至被写进程唤醒。在睡眠途中,如果用户给读进程发送了信号,那么也会唤醒睡眠的进程
        if(signal_pending(current)){             /*如果是因为信号唤醒*/ //因为是调度出去,进程状态是浅度睡眠,唤醒它的有可能是信号 
            ret = -ERESTARTSYS;  //表示信号函数处理完毕后重新执行信号处理函数前的某个系统调用
            goto out2;  
        }  
        down(&dev->sem);  //加入信号量down和up防止多个进程同时访问共享数据mem
    }  
  
    /*拷贝到用户空间*/  
    if(count > dev->current_len)  //如果当前读的数据大于fifo的有效数据长度
        count = dev->current_len;  
    if(copy_to_user(buf, dev->mem, count)){  //参数:to from count,成功返回0,失败是返回还没有拷贝到用户空间的字节数
        ret = -EFAULT;  
        goto out;  
    }else{  
        memcpy(dev->mem, dev->mem + count, dev->current_len - count);/*数据前移*/  //memcpy将(dev->mem + count)开始的(dev->current_len - count)字节的数据移动到缓冲区最开始的地方
        dev->current_len -= count; /*有效数据长度减少*/  
        printk(KERN_INFO"read %ld bytes(s),current_len:%d\n",count, dev->current_len);  
  
        wake_up_interruptible(&dev->w_wait); /*唤醒写等待队列*/	//已经读完数据,就要唤醒写队列来进行写数据
        ret = count;  
    }  
out:  
    up(&dev->sem); /*释放信号量*/  
out2:  
    remove_wait_queue(&dev->w_wait, &wait); /*从属的等待队列头移除*/  
    set_current_state(TASK_RUNNING);  
    return ret;  
}  
  
/*globalfifo 写操作*/  //echo " " > /dev/xxx
static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)  
{  
    struct globalfifo_dev *dev = filp->private_data;  
    int ret;  
    DECLARE_WAITQUEUE(wait, current);    /*定义等待队列*/    
    down(&dev->sem);                     /*获得信号量*/  
    add_wait_queue(&dev->w_wait, &wait); /*进入写等待队列头*/  
  
    /*等待FIFO非满*/  //如果共享数据区的数据长度等于fifo的大小,表示已经满了,就应该阻塞
    if(dev->current_len == GLOBALFIFO_SIZE){  
        if(filp->f_flags & O_NONBLOCK){   
			/*如果进程非阻塞打开的文件*/  
            ret = -EAGAIN;  
            goto out;  
        }  
  
        __set_current_state(TASK_INTERRUPTIBLE); /*改变进程状态为睡眠*/  
        up(&dev->sem);                     /*释放信号量*/  
  
        schedule();                         /*调度其他进程执行*/  
        if(signal_pending(current)){  
                                            /*如果是因为信号唤醒*/  
            ret = -ERESTARTSYS;  
            goto out2;  
        }  
        down(&dev->sem);                    /*获得信号量*/  
    }  
  
    /*从用户空间拷贝数据到内核空间*/  
    if(count > GLOBALFIFO_SIZE - dev->current_len){	//如果fifo的大小大于有效内存的长度,则下次再写  
        /*如果要拷贝的数据大于 剩余有效内存长度   
         *则 只拷贝最大 能装下的长度*/  
        count = GLOBALFIFO_SIZE - dev->current_len;  //count保留下次再写的数据大小
    }  
    if(copy_from_user(dev->mem + dev->current_len, buf, count)){  //参数to,from,count,to:(dev->mem) + (dev->current_len),因为写入数据了,所以当前的共享数据区,要移位
        ret = -EFAULT;  
        goto out;  
    }else {  
        dev->current_len += count;  //有效数据有加当前的数据的长度
        printk(KERN_INFO"written %ld bytes(s), current_len: %d\n",count, dev->current_len);  
  
        wake_up_interruptible(&dev->r_wait); /*唤醒读等待队列*/ //写完数据,那肯定要唤醒读队列进行读啦 
        ret = count;  
    }  
    out:  
        up(&dev->sem); /*释放信号量*/  //释放信号量,读进程会因信号量被释放而唤醒
    out2:  
        remove_wait_queue(&dev->w_wait, &wait); /*从附属的等待队列头移除*/  
        set_current_state(TASK_RUNNING);  //进程处于可运行状态
        return ret;  
}  
   
   
/*ioctl 设备控制函数*/  
static long globalfifo_ioctl(struct file *filp,unsigned int cmd, unsigned long arg)
{  
    struct globalfifo_dev *dev = filp->private_data;/*获得设备结构体指针*/  
  
    switch(cmd){  
        case FIFO_CLEAR:  
            down(&dev->sem);                        /*获得信号量*/  
            dev->current_len = 0;  
            memset(dev->mem, 0, GLOBALFIFO_SIZE);  
            up(&dev->sem);                          /*释放信号量*/  
  
            printk(KERN_INFO"globalfifo is set to zero\n");  
            break;  
  
        default:  
            return -EINVAL;  
    }  
    return 0;  
}  
/*在驱动中的增加轮询操作*/  
static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)  
{  
    unsigned int mask = 0;  
    struct globalfifo_dev *dev = filp->private_data;/*获得设备结构体指针*/  
  
    down(&dev->sem);  
    poll_wait(filp, &dev->r_wait, wait);  
    poll_wait(filp, &dev->w_wait, wait);  
  
    /*fifo非空*/  
    if(dev->current_len != 0){  
        mask |= POLLIN | POLLRDNORM; /*标示数据可以获得*/  
    }  
  
    /*fifo 非满*/  
    if(dev->current_len != GLOBALFIFO_SIZE){  
        mask |= POLLOUT | POLLWRNORM ; /*标示数据可以写入*/  
    }  
  
    up(&dev->sem);  
    return mask; /*返回驱动是否可读 或可写的 状态*/  
}  
/*文件打开函数*/  
int globalfifo_open(struct inode *inode, struct file *filp)  
{  
    /*让设备结构体作为设备的私有信息*/  
    filp->private_data = globalfifo_devp;  
    return 0;  
}  
  
/*文件释放函数*/  
int globalfifo_release(struct inode *inode, struct file *filp)  
{  
    return 0;  
}  
/*文件操作结构体*/  
static const struct file_operations globalfifo_fops = {  
    .owner = THIS_MODULE,  
    .read = globalfifo_read,  
    .write = globalfifo_write,  
    .unlocked_ioctl = globalfifo_ioctl,  
    .poll = globalfifo_poll,  
    .open = globalfifo_open,  
    .release = globalfifo_release,  
};  
  
/*初始化并注册cdev*/  
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)  
{  
    int err, devno = MKDEV(globalfifo_major, index);  
  
    cdev_init(&dev->cdev, &globalfifo_fops);  
    dev->cdev.owner = THIS_MODULE;  
    err = cdev_add(&dev->cdev, devno, 1);  
    if(err)  
        printk(KERN_NOTICE "Error %d adding LED %d", err, index);  
}  
   
/*设备驱动模块加载函数*/  
int globalfifo_init(void)  
{  
    int ret;  
    dev_t devno = MKDEV(globalfifo_major, 0);  
  
    /*申请设备号*/  
    if(globalfifo_major)  
        ret = register_chrdev_region(devno, 1, "globalfifo");  
    else{/*动态申请设备号*/  
        ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");  
        globalfifo_major = MAJOR(devno);  
    }  
  
    if(ret < 0)  
        return ret;  
  
    /*动态申请设备结构体的内存*/  
    globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);  
    if(!globalfifo_devp){  
        ret = - ENOMEM;  
		goto fail_malloc;  
    }  
  
    memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));  
  
    globalfifo_setup_cdev(globalfifo_devp, 0);  
  
	sema_init(&globalfifo_devp->sem,1);             /*初始化信号量*/  
    init_waitqueue_head(&globalfifo_devp->r_wait);  /*初始化读等待队列头*/  
    init_waitqueue_head(&globalfifo_devp->w_wait);  /*初始化写等待队列头*/  
  
    return 0;  
  
fail_malloc: unregister_chrdev_region(devno, 1);  
             return ret;  
}  
void globalfifo_exit(void)  
{  
    cdev_del(&globalfifo_devp->cdev); /*注销cdev*/  
    kfree(globalfifo_devp); /*释放设备结构体内存*/  
    unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*释放设备号*/  
}  
MODULE_AUTHOR("54geeker");  
MODULE_LICENSE("Dual BSD/GPL");    
module_init(globalfifo_init);  
module_exit(globalfifo_exit);  

4、编写probe函数的思路
(1)从device.c里面获得硬件资源
方法一:直接获得,不推荐
方法二:只用函数获得,在include/linux/platform_device.h
extern struct resource *platform_get_resource(struct platform_device *,unsigned int, unsigned int);
(2)注册杂项设备/字符设备,完善file_operation结构体,并生成设备节点。

注册字符设备驱动框架图:
在这里插入图片描述

在这里插入图片描述

一个简单字符设备probe

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> 
#include <linux/miscdevice.h> 
#include <linux/fs.h> 
#include <linux/uaccess.h> 
#include <linux/io.h> struct resource *beep_mem;
struct resource *beep_mem_tmp;
unsigned int *vir_gpio5_dr;ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *off_t)
{
    char kbuf[64] = {0};
    
    printk("misc_write \n");
    if(copy_from_user(kbuf,ubuf,size) != 0) {
        printk("copy_from_user error\n");
        return -1;
    }
    printk("kbuf is %s \n",kbuf);
    if(kbuf[0] == 1)
        *vir_gpio5_dr |= (1<<1);
    else if(kbuf[1] == 0)
        *vir_gpio5_dr &= ~(1<<1);
    
    return 0;
}
ssize_t misc_read(struct file *file, char __user *user, size_t size, loff_t *loff_t)
{
    printk("misc_read \n");
    return 0;
}int misc_open(struct inode *inode, struct file *file)
{
    printk("misc_release \n");
    return 0;
}int misc_release (struct inode *inode, struct file *file)
{
    printk("misc_release \n");
    return 0;
}struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .read = misc_read,
    .write = misc_write,
    .release = misc_release
};struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,//动态分配设备号
    .name  = "hello_misc",
    .fops  = &misc_fops
};
​
​
int beep_probe(struct platform_device *pdev)
{
    int ret = 0;
    printk("beep_probe \n");
    //方法1
    //printk("beep_res is %s\n".pdev->resource[0].name);//方法2
    beep_mem  = platform_get_resource(pdev,IORESOURCE_MEM,0);
    if(beep_mem == NULL) {
        printk("platform_get_resource is error\n");
        return -EBUSY;
    }
​
    vir_gpio5_dr = ioremap(beep_mem->start,4);
    if(vir_gpio5_dr == NULL) {
        printk("GPIO5_DR ioremap is error\n");
        return -EBUSY;
    }
    printk("GPIO5_DR ioremap is ok\n");
​
    ret = misc_register(&misc_dev);
    if(ret < 0) {
        printk("misc_register is error\n");
        return -1;
    }
    printk("misc_register is ok\n");
    return 0;
}int beep_remove(struct platform_device *pdev)
{
    printk("beep_remove \n");
    return 0;
}struct platform_driver beep_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name  = "beep_test"
    }
};static int beep_driver_init(void)
{
    printk("beep_driver_init \n");
    return platform_driver_register(&beep_driver);
}static void  beep_driver_exit(void)
{
    platform_driver_unregister(&beep_driver);
    misc_deregister(&misc_dev);
    iounmap(vir_gpio5_dr);
    printk("beep_driver_exit \n");
}module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");

五、为完成将上面的写的globalfifo字符设备,移植改造到platform驱动的工作,要完成2个工作:

1、将globalfifo移植为platform驱动。。
2、在板文件中添加globalfifo这个platform设备

为完成将globalfifo移植到platform驱动的工作,需要在原始的globalfifo字符设备驱动中套一层 platform_driver的外壳,注意进行这一工作后,并没有改变globalfifo是字符设备的本质,只是将其挂接到了 platform总线。

globalfifo添加platform_driver
将globalfifo_init改成globalfifo_probe

static int __devinit globalfifo_probe(struct platform_device pdev)
 {
	int ret;
	dev_t devno = MKDEV(globalfifo_major, 0);
	
	 / 申请设备号*/
	 if (globalfifo_major)
	 {
	  ret = register_chrdev_region(devno, 1, “globalfifo”);
	 }
	 else { /* 动态申请设备号 /
		ret = alloc_chrdev_region(&devno, 0, 1, “globalfifo”);
		 globalfifo_major = MAJOR(devno);
	 }
	 if (ret < 0)
	 	return ret;
	 	
	/ 动态申请设备结构体的内存*/
	globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
	if (!globalfifo_devp) { /申请失败/
		ret = – ENOMEM;
		goto fail_malloc;
	}
	memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
	
	globalfifo_setup_cdev(globalfifo_devp, 0);//)注册杂项设备/字符设备,完善file_operation结构体,并生成设备节点
	
	init_MUTEX(&globalfifo_devp->sem); /初始化信号量/
	init_waitqueue_head(&globalfifo_devp->r_wait); /初始化读等待队列头/
	init_waitqueue_head(&globalfifo_devp->w_wait); /初始化写等待队列头/
	return 0;

	fail_malloc: unregister_chrdev_region(devno, 1);
 	return ret;
}
static int __devexit globalfifo_remove(struct platform_device *pdev)
 {
	cdev_del(&globalfifo_devp->cdev); /注销cdev/
	kfree(globalfifo_devp); /释放设备结构体内存/
	unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /释放设备号/
	return 0;
 }
static struct platform_driver globalfifo_device_driver = {
	 .probe = globalfifo_probe,
	 .remove = __devexit_p(globalfifo_remove),
	 .driver = {
		 .name = “globalfifo”,
		 .owner = THIS_MODULE,
		}
 };
static int __init globalfifo_init(void)
{
	return platform_driver_register(&globalfifo_device_driver);
}

static void __exit globalfifo_exit(void)
{
	platform_driver_unregister(&globalfifo_device_driver);
}

module_init(globalfifo_init);
module_exit(globalfifo_exit);

模块加载和卸载函数仅仅通过platform_driver_register()、 platform_driver_unregister()函数进行platform_driver的注册与注销,而原先注册和注销字符设备的工作已经被 移交到platform_driver的probe()和remove()成员函数中。

代码未列出的部分与原始的globalfifo驱动相同,都是实现作为字符设备驱动核心的file_operations的成员函数。

为了完成在板文件中添加globalfifo这个platform设备的工作,需要在板文件(对于LDD6410而言,为arch/arm /mach-s3c6410/ mach-ldd6410.c)中添加相应的代码。
globalfifo对应的platform_device

 static struct platform_device globalfifo_device = {
	 .name = “globalfifo”,
	 .id = -1,
 };

对于LDD6410开发板而言,为了完成上述globalfifo_device这一platform_device的注册,只需要将其地址放 入 arch/arm/mach-s3c6410/ mach-ldd6410.c中定义的ldd6410_devices数组,如:

static struct platform_device *ldd6410_devices[] __initdata = {
	& globalfifo_device,
	&s3c_device_fb,
	&s3c_device_hsmmc0,}

测试一下结果。
在这里插入图片描述

在加载LDD6410驱动后,在sysfs中会发现如下结点:
/sys/bus/platform/devices/globalfifo/
/sys/devices/platform/globalfifo/

platform_device和platform_driver的name一致,这是二 者得以匹配的前提。

platform设备资源和数据resouce
留意一下代码中platform_device结构体定义的struct resource * resource,描述了platform_device的资源,资源本身由 resource结构体描述,其定义如下。
resouce结构体定义

struct resource {
	resource_size_t start;
	esource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

我们通常关心start、end和flags这3个字段,分别标明资源的开始值、结束值和类型,flags可以为IORESOURCE_IO、 IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。start、end的含义会随着flags而变更,如当 flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;当 flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了 1个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,譬如说某设备占据了2个内存区域,则可以定义2个IORESOURCE_MEM资 源。

对resource的定义也通常在BSP的板文件中进行,而在具体的设备驱动中透过platform_get_resource()这样的 API来获取,此API的原型为:

struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);

譬如在LDD6410开发板的板文件中为DM9000网卡定义了如下resouce:

static struct resource ldd6410_dm9000_resource[] = {
	[0] = {
		.start = 0×18000000,
		.end = 0×18000000 + 3,
		.flags = IORESOURCE_MEM
	},
	[1] = {
		.start = 0×18000000 + 0×4,
		.end = 0×18000000 + 0×7,
		.flags = IORESOURCE_MEM
	},
	[2] = {
		.start = IRQ_EINT(7),
		.end = IRQ_EINT(7),
		.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
	}
};

在DM9000网卡的驱动中则是通过如下办法拿到这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);

对于IRQ而言,platform_get_resource()还有一个进行了封装的变体platform_get_irq(),其原型为:

int platform_get_irq(struct platform_device *dev, unsigned int num);

它实际上调用了“platform_get_resource(dev, IORESOURCE_IRQ, num);”。

platform_data
设备除了可以在BSP中定义资源以外,还可以附加一些数据信息,因为对设备的硬件描述除了中断、内存、DMA通道以外,可能还会有一些配置信 息,而 这些配置信息也依赖于板,不适宜直接放置在设备驱动本身,因此**,platform也提供了platform_data的支持**。platform_data 的形式是自定义的,如对于DM9000网卡而言,platform_data为一个dm9000_plat_data结构体,我们就可以将MAC地址、总 线宽度、有无EEPROM信息放入platform_data:

static struct dm9000_plat_data ldd6410_dm9000_platdata = {
	.flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM,
	.dev_addr = { 0×0, 0×16, 0xd4, 0×9f, 0xed, 0xa4 },
};

static struct platform_device ldd6410_dm9000 = {
	.name = “dm9000″,
	.id = 0,
	.num_resources = ARRAY_SIZE(ldd6410_dm9000_resource),
	.resource = ldd6410_dm9000_resource,
	.dev = {
		.platform_data = &ldd6410_dm9000_platdata,
	}
};

而在DM9000网卡的驱动中,通过如下方式就拿到了platform_data:

struct dm9000_plat_data *pdata = pdev->dev.platform_data;

其中,pdev为platform_device的指针。

由以上分析可知,设备驱动中引入platform的概念至少有如下2大好处:
使得设备被挂接在一个总线上,因此,符合Linux 2.6的设备模型。其结果是,配套的sysfs结点、设备电源管理都成为可能。
隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用API去获取资源和数据,做到 了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

六、测试实验二
本例比较简单,只用于测试platform_driver 和platform_device是否可以匹配成功。左边是platform_device结构体注册的代码,右边是platform_driver结构体注册的代码。
在这里插入图片描述
Makefile

obj-m:=device.o driver.o 
KDIR :=/lib/modules/$(shell uname -r)/build 
PWD :=$(shell pwd) 

all: 
	make -C $(KDIR) M=$(PWD) modules 
clean: 
	rm -f *.ko *.o *.mod.o *.symvers *.cmd *.mod.c *.order 

编译后测试
在这里插入图片描述
在这里插入图片描述
先清空log信息 sudo dmesg -c
在这里插入图片描述
给结构体platform_device 增加硬件信息,并在内核中能够读取出来。 本例向结构体hello_device 增加信息如下:

基址寄存器地址0x139d0000,该地址的空间是0x4
中断号199 【注意】 实际的内核中会把外设的中断号根据HW id(通常soc厂商设备soc的时候会给每一个中断源定义好唯一的ID)计算出一个新的中断号,该中断号会被cpu所识别。

device.c

struct resource res[]=
{ 
	[0] ={ 
		.start = 0x139d0000, 
		.end = 0x139d0000 + 0x3, 
		.flags = IORESOURCE_MEM, 
	}, 
	[1] ={
	 .start = 199, 
	 .end = 199, 
	 .flags = IORESOURCE_IRQ, 
	 }, 
}; 
在上面代码基础,将device改一下,添加resource 
static struct platform_device hello_device = 
{ 
	.name = "duang", 
	.id = -1, 
	.dev
	.release = hello_release, 
	.num_resources = ARRAY_SIZE(res), 
	.resource = res, 
};

driver.c

static int hello_probe(struct platform_device *pdev)
{ 
	printk("match ok \n"); 
	printk("mem = %x \n",pdev->resource[0].start);
	printk("irq = %d \n",pdev->resource[1].start); //注册中断、申请内存 
	return 0; 
 }

重新编译,卸载第一个例子的模块,并清除log:

make sudo rmmod device sudo rmmod driver sudo dmesg -c
执行
在这里插入图片描述

四、platform_device是如何管理的?
1、没有设备树,在没有设备树的时候,以三星Cortex-A8 s5pc100为例,硬件信息放在以下位置
arch\arm\mach-s5pc100\Mach-smdkc100.c arch\arm\plat-samsung\

在这里插入图片描述
在这里插入图片描述
该数组存放了,内核启动需要初始化的硬件的信息。

2、如果有设备树
内核会有设备初始化的完整代码,会在内核启动的时候把设备树信息解析初始化,把硬件信息初始化到对应的链表中。 在总线匹配成功后,会把硬件的信息传递给probe()函数。

四、总线相关的其他的知识点

  1. 内核总线相关结构体变量
    内核维护的所有的总线都需要用以下结构体注册一个变量。

struct bus_type

struct bus_type platform_bus_type = 
{ 
	.name = "platform",
	.dev_groups = platform_dev_groups, 
	.match = platform_match,
	.uevent = platform_uevent,
	.pm = &platform_dev_pm_ops,
};

其中最重要的成员是**.match**。

当有设备的硬件信息注册到platform_bus_type 总线的时候,会遍历所有platform总线维护的驱动, 通过名字来匹配,如果相同,就说明硬件信息和驱动匹配,就会调用驱动的platform_driver ->probe函数,初始化驱动的所有资源,让该驱动生效。

当有设备的驱动注册到platform_bus_type 总线的时候,会遍历所有platform总线维护的硬件信息, 通过名字来匹配,如果相同,就说明硬件信息和驱动匹配,就会调用驱动的platform_driver ->probe函数,初始化驱动的所有资源,让该驱动生效。

注册位置
在这里插入图片描述

五、注册代码流程详解
捋架构的好处,就是可以帮助我们定位问题

  1. match函数何时被调用到?
  2. probe函数何时被调用到
    以下是上述两个问题代码的调用流程:
    在这里插入图片描述

七、注册一个 platform 驱动的两种方法
方法一:就是本文所介绍的传统方法
就是本文所介绍的传统方法,如下:

static int __init xxx_init(void)
{
    return platform_driver_register(&xxx_driver);
}
module_init(xxx_init);
// 驱动模块卸载 
static void __exit xxx_exit(void)
{
    platform_driver_unregister(&xxx_driver);
}
module_exit(xxx_exit);

方法二:使用采用 module_platform_driver 来完成向 Linux 内核注册 platform 驱动的操作
module_platform_driver 定义在 include/linux/platform_device.h 文件中,如下:

/* module_platform_driver() - Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates a lot of
 * boilerplate.  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 */
#define module_platform_driver(__platform_driver) \
	module_driver(__platform_driver, platform_driver_register, \
			platform_driver_unregister)

可以看出,module_platform_driver 依赖 module_driver,module_driver 也是一个宏,定义在include/linux/device.h 文件中,内容如下:

#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
	return __register(&(__driver) , ##__VA_ARGS__); \
} \

module_init(__driver##_init); \

static void __exit __driver##_exit(void) \
{ \
	__unregister(&(__driver) , ##__VA_ARGS__); \
} \

module_exit(__driver##_exit);

具体驱动代码中使用如下所示:module_platform_driver(xxx_driver);

可见方法一就是方法二的展开形式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值