本文参考,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个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每 注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
一个现实的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()函数。
四、总线相关的其他的知识点
- 内核总线相关结构体变量
内核维护的所有的总线都需要用以下结构体注册一个变量。
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函数,初始化驱动的所有资源,让该驱动生效。
注册位置
五、注册代码流程详解
捋架构的好处,就是可以帮助我们定位问题
- match函数何时被调用到?
- 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);
可见方法一就是方法二的展开形式。