device和目录 linux_linux device目录 - CSDN
精华内容
参与话题
  • Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备(device),所以kernel就为设备驱动它的driver定义了两个数据结构,分别是device和device_driver。因此本文将会围绕这两个...

    1. 前言

    device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备(device),所以kernel就为设备和驱动它的driver定义了两个数据结构,分别是device和device_driver。因此本文将会围绕这两个数据结构,介绍Linux设备模型的核心逻辑,包括:

    设备及设备驱动在kernel中的抽象、使用和维护;

    设备及设备驱动的注册、加载、初始化原理;

    设备模型在实际驱动开发过程中的使用方法。

    注:在介绍device和device_driver的过程中,会遇到很多额外的知识点,如Class、Bus、DMA、电源管理等等,这些知识点都很复杂,任何一个都可以作为一个单独的专题区阐述,因此本文不会深入解析它们,而会在后续的文章中专门描述。

    2. struct device和struct device_driver

    在阅读Linux内核源代码时,通过核心数据结构,即可理解某个模块60%以上的逻辑,设备模型部分尤为明显。

    在include/linux/device.h中,Linux内核定义了设备模型中最重要的两个数据结构,struct device和struct device_driver。

    
    /**
     * 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.
     * @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.
     * @power:	For device power management.
     * 		See Documentation/power/devices.txt 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.
     * @pins:	For device pin management.
     *		See Documentation/pinctrl.txt for details.
     * @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_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.
     * @dma_pfn_offset: offset of DMA memory range relatively 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
     * @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_fwspec: IOMMU-specific properties supplied by firmware.
     *
     * @offline_disabled: If set, the device is permanently online.
     * @offline:	Set after successful invocation of bus type's .offline().
     *
     * 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 device		*parent;
    
    	struct device_private	*p;
    
    	struct kobject kobj;
    	const char		*init_name; /* initial name of the device */
    	const struct device_type *type;
    
    	struct mutex		mutex;	/* mutex to synchronize calls to
    					 * its driver.
    					 */
    
    	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/get_drvdata */
    	struct dev_pm_info	power;
    	struct dev_pm_domain	*pm_domain;
    
    #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
    	struct list_head	msi_list;
    #endif
    
    #ifdef CONFIG_NUMA
    	int		numa_node;	/* NUMA node this device is close to */
    #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. */
    	unsigned long	dma_pfn_offset;
    
    	struct device_dma_parameters *dma_parms;
    
    	struct list_head	dma_pools;	/* dma pools (if dma'ble) */
    
    	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
    					     override */
    #ifdef CONFIG_DMA_CMA
    	struct cma *cma_area;		/* contiguous memory area for dma
    					   allocations */
    #endif
    	/* arch specific additions */
    	struct dev_archdata	archdata;
    
    	struct device_node	*of_node; /* associated device tree node */
    	struct fwnode_handle	*fwnode; /* firmware device node */
    
    	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
    	u32			id;	/* device instance */
    
    	spinlock_t		devres_lock;
    	struct list_head	devres_head;
    
    	struct klist_node	knode_class;
    	struct class		*class;
    	const struct attribute_group **groups;	/* optional groups */
    
    	void	(*release)(struct device *dev);
    	struct iommu_group	*iommu_group;
    	struct iommu_fwspec	*iommu_fwspec;
    
    	bool			offline_disabled:1;
    	bool			offline:1;
    };
    

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

    parent,该设备的父设备,一般是该设备所从属的bus、controller等设备。

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

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

    init_name,该设备的名称。

           注1:在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据“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/* 下的对应目录,如下:

    1|root@android:/storage/sdcard0 #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/ 
    1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:1                                                                    
    1:1/  1:11/ 1:13/ 1:14/
    1|root@android:/storage/sdcard0 # ls /sys/dev/char/1\:1 
    /sys/dev/char/1:1  

    class,该设备属于哪个class。

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

    iommu_fwspec,固件提供的IOMMU特定属性

    offline_disabled,如果设置,则设备永久在线

    offline,成功调用总线类型的.offline()后设置

    在最低级别,Linux系统中的每个设备都由一个结构设备的实例。 设备结构包含信息 设备模型核心需要为系统建模。 ‘

    大多数子系统, 但是,请跟踪有关其托管设备的其他信息。 作为一个  结果,设备很少用裸设备结构表示;相反,该

    结构,如kobject结构,通常嵌入其中 设备的更高级别表示。

    • struct device_driver
    
    /**
     * 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.
     * @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.
     * @pm:		Power management operations of the device which matched
     *		this driver.
     * @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);
    	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 dev_pm_ops *pm;
    
    	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,如下:root@android:/storage/sdcard0 # 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的私有数据指针,其它模块不能访问。

    3. 设备模型框架下驱动开发的基本步骤

    在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:

    步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。

    步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。

    这两步完成后,内核会在合适的时机(后面会讲),调用struct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。而所有的驱动程序逻辑,都会由这些回调函数实现,此时,驱动开发者眼中便不再有“设备模型”,转而只关心驱动本身的实现。

    以上两个步骤的补充说明:

    1. 一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用(也正是因为这个原因,本文并不会过多涉及device、device_driver等模块的实现细节)。

    2. 内核提供很多struct device结构的操作接口(具体可以参考include/linux/device.h和drivers/base/core.c的代码),主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。

    3. device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。

    4. device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。

    5. driver开发者可以在struct device变量中,保存描述设备特征的信息,如寻址空间、依赖的GPIOs等,因为device指针会在执行probe等接口时传入,这时driver就可以根据这些信息,执行相应的逻辑操作了。

    device部分的实现细节:

    https://blog.csdn.net/qq_16777851/article/details/81437352

    device_driver部分的实现细节

    https://mp.csdn.net/postedit/81459931

     

    4. 设备驱动probe的时机

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

    设备驱动prove的时机有如下几种(分为自动触发和手动触发):

    • 将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_driver)

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

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

    注4:上面的手动触发和自动触发以及probe机制都在设备模型六和七有分析,要看细节请看那两篇博客。

     

    5. 其它杂项

    5.1 device_attribute和driver_attribute

    Linux设备模型四(attribure中,我们有讲到,大多数时候,attribute文件的读写数据流为:vfs---->sysfs---->kobject---->attibute---->kobj_type---->sysfs_ops---->xxx_attribute,其中kobj_type、sysfs_ops和xxx_attribute都是由包含kobject的上层数据结构实现。

    Linux内核中关于该内容的例证到处都是,device也不无例外的提供了这种例子,如下

    
    #define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
    
    static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
    			     char *buf)
    {
    	struct device_attribute *dev_attr = to_dev_attr(attr);
    	struct device *dev = kobj_to_dev(kobj);
    	ssize_t ret = -EIO;
    
    	if (dev_attr->show)
    		ret = dev_attr->show(dev, dev_attr, buf);
    	if (ret >= (ssize_t)PAGE_SIZE) {
    		print_symbol("dev_attr_show: %s returned bad count\n",
    				(unsigned long)dev_attr->show);
    	}
    	return ret;
    }
    
    static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
    			      const char *buf, size_t count)
    {
    	struct device_attribute *dev_attr = to_dev_attr(attr);
    	struct device *dev = kobj_to_dev(kobj);
    	ssize_t ret = -EIO;
    
    	if (dev_attr->store)
    		ret = dev_attr->store(dev, dev_attr, buf, count);
    	return ret;
    }
    
    static const struct sysfs_ops dev_sysfs_ops = {
    	.show	= dev_attr_show,
    	.store	= dev_attr_store,
    };
    
    
    static struct kobj_type device_ktype = {
    	.release	= device_release,
    	.sysfs_ops	= &dev_sysfs_ops,
    	.namespace	= device_namespace,
    };
    
    
    struct sysfs_ops {
    	ssize_t	(*show)(struct kobject *, struct attribute *,char *);
    	ssize_t	(*store)(struct kobject *,struct attribute *,const char *, size_t);
    };
    
    
    /* interface for exporting device attributes */
    struct device_attribute {
    	struct attribute	attr;
    	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
    			char *buf);
    	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
    			 const char *buf, size_t count);
    };

    至于driver的attribute,则要简单的多,其数据流为:vfs---->sysfs---->kobject---->attribute---->driver_attribute,如下:

    
    /* sysfs interface for exporting driver attributes */
    
    struct driver_attribute {
    	struct attribute attr;
    	ssize_t (*show)(struct device_driver *driver, char *buf);
    	ssize_t (*store)(struct device_driver *driver, const char *buf,
    			 size_t count);
    };
    
    #define DRIVER_ATTR(_name, _mode, _show, _store) \
    	struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
    

    5.2 device_type

    device_type是内嵌在struct device结构中的一个数据结构,用于指明设备的类型,并提供一些额外的辅助功能。它的的形式如下:

    /*
     * The type of device, "struct device" is embedded in. A class
     * or bus can contain devices of different types
     * like "partitions" and "disks", "mouse" and "event".
     * This identifies the device type and carries type-specific
     * information, equivalent to the kobj_type of a kobject.
     * If "name" is specified, the uevent will contain it in
     * the DEVTYPE variable.
     */
    struct device_type {
    	const char *name;
    	const struct attribute_group **groups;
    	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    	char *(*devnode)(struct device *dev, umode_t *mode,
    			 kuid_t *uid, kgid_t *gid);
    	void (*release)(struct device *dev);
    
    	const struct dev_pm_ops *pm;
    };
    

    device_type的功能包括:

    • name表示该类型的名称,当该类型的设备添加到内核时,内核会发出"DEVTYPE=‘name’”类型的uevent,告知用户空间某个类型的设备available了
    • groups,该类型设备的公共attribute集合。设备注册时,会同时注册这些attribute。这就是面向对象中“继承”的概念
    • uevent,同理,所有相同类型的设备,会有一些共有的uevent需要发送,由该接口实现
    • devnode,devtmpfs有关的内容,暂不说明
    • release,如果device结构没有提供release接口,就要查询它所属的type是否提供。用于释放device变量所占的空间

    5.3 root device

    在sysfs中有这样一个目录:/sys/devices,系统中所有的设备,都归集在该目录下。有些设备,是通过device_register注册到Kernel并体现在/sys/devices/xxx/下。但有时候我们仅仅需要在/sys/devices/下注册一个目录,该目录不代表任何的实体设备,这时可以使用下面的接口:

    /*
     * Root device objects for grouping under /sys/devices
     */
    extern struct device *__root_device_register(const char *name,
    					     struct module *owner);
    
    /* This is a macro to avoid include problems with THIS_MODULE */
    #define root_device_register(name) \
    	__root_device_register(name, THIS_MODULE)
    
    extern void root_device_unregister(struct device *root);
    

    该接口会调用device_register函数,向内核中注册一个设备,但是(你也想到了),没必要注册与之对应的driver(顺便提一下,内核中有很多不需要driver的设备,这是之一)。

     

    参考文章:

    http://www.wowotech.net/device_model/device_and_driver.html

    展开全文
  • Linux设备模型(5)_device和device driver

    千次阅读 2017-08-10 16:31:30
    device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备,所以kernel就为设备驱动它的driver定义了两个数据

    原文地址:http://www.wowotech.net/device_model/device_and_driver.html


    1. 前言

    device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备,所以kernel就为设备和驱动它的driver定义了两个数据结构,分别是device和device_driver。因此本文将会围绕这两个数据结构,介绍Linux设备模型的核心逻辑,包括:

    设备及设备驱动在kernel中的抽象、使用和维护;

    设备及设备驱动的注册、加载、初始化原理;

    设备模型在实际驱动开发过程中的使用方法。

    注:在介绍device和device_driver的过程中,会遇到很多额外的知识点,如Class、Bus、DMA、电源管理等等,这些知识点都很复杂,任何一个都可以作为一个单独的专题区阐述,因此本文不会深入解析它们,而会在后续的文章中专门描述。

    2. struct device和struct device_driver

    在阅读Linux内核源代码时,通过核心数据结构,即可理解某个模块60%以上的逻辑,设备模型部分尤为明显。

    在include/linux/device.h中,Linux内核定义了设备模型中最重要的两个数据结构,struct device和struct device_driver。

    • struct device
     1: /* include/linux/device.h, line 660 */
     2: struct device {
     3:     struct device       *parent;
     4:  
     5:     struct device_private   *p;
     6:  
     7:     struct kobject kobj;
     8:     const char *init_name; /* initial name of the device */
     9:     const struct device_type *type;
     10:  
     11:    struct mutex        mutex; /* mutex to synchronize calls to
     12:                             * its driver.
     13:                             */
     14:  
     15:    struct bus_type *bus; /* type of bus device is on */
     16:    struct device_driver *driver; /* which driver has allocated this
     17:                                 device */
     18:    void *platform_data; /* Platform specific data, device
     19:                         core doesn't touch it */
     20:    struct dev_pm_info  power;
     21:    struct dev_pm_domain    *pm_domain;
     22:  
     23: #ifdef CONFIG_PINCTRL
     24:    struct dev_pin_info *pins;
     25: #endif
     26:  
     27: #ifdef CONFIG_NUMA
     28:    int numa_node; /* NUMA node this device is close to */
     29: #endif
     30:    u64     *dma_mask; /* dma mask (if dma'able device) */
     31:    u64     coherent_dma_mask;/* Like dma_mask, but for
     32:                             alloc_coherent mappings as
     33:                             not all hardware supports
     34:                             64 bit addresses for consistent
     35:                             allocations such descriptors. */
     36:  
     37:    struct device_dma_parameters *dma_parms;
     38:  
     39:    struct list_head    dma_pools; /* dma pools (if dma'ble) */
     40:  
     41:    struct dma_coherent_mem *dma_mem; /* internal for coherent mem
     42:                            override */
     43: #ifdef CONFIG_CMA
     44:    struct cma *cma_area; /* contiguous memory area for dma
     45:                            allocations */
     46: #endif
     47:    /* arch specific additions */
     48:    struct dev_archdata archdata;
     49:  
     50:    struct device_node  *of_node; /* associated device tree node */
     51:    struct acpi_dev_node    acpi_node; /* associated ACPI device node */
     52:  
     53:    dev_t           devt; /* dev_t, creates the sysfs "dev" */
     54:    u32         id; /* device instance */
     55:  
     56:    spinlock_t      devres_lock;
     57:    struct list_head    devres_head;
     58:  
     59:    struct klist_node   knode_class;
     60:    struct class *class;
     61:    const struct attribute_group **groups; /* optional groups */
     62:  
     63:    void (*release)(struct device *dev);
     64:    struct iommu_group  *iommu_group;
     65: };

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

    parent,该设备的父设备,一般是该设备所从属的bus、controller等设备。

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

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

    init_name,该设备的名称。

           注1:在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据“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/* 下的对应目录,如下:

    1|root@android:/storage/sdcard0 #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/  
    1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:1                                                                     
    1:1/  1:11/ 1:13/ 1:14/ 
    1|root@android:/storage/sdcard0 # ls /sys/dev/char/1\:1  
    /sys/dev/char/1:1  

    class,该设备属于哪个class。

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

     

    • struct device_driver
     1: /* include/linux/device.h, line 213 */
     2: struct device_driver {  
     3:     const char *name;  
     4:     struct bus_type     *bus;
     5:  
     6:     struct module       *owner;
     7:     const char *mod_name; /* used for built-in modules */
     8:  
     9:     bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
     10:  
     11:    const struct of_device_id   *of_match_table;
     12:    const struct acpi_device_id *acpi_match_table;
     13:  
     14:    int (*probe) (struct device *dev);
     15:    int (*remove) (struct device *dev);
     16:    void (*shutdown) (struct device *dev);
     17:    int (*suspend) (struct device *dev, pm_message_t state);
     18:    int (*resume) (struct device *dev);
     19:    const struct attribute_group **groups;
     20:  
     21:    const struct dev_pm_ops *pm;
     22:  
     23:    struct driver_private *p;
     24: };

    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,如下:root@android:/storage/sdcard0 # 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代码可以把任何需要的内容放在这里,反正设备模型代码不关心。

    3. 设备模型框架下驱动开发的基本步骤

    在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:

    步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。

    步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。

    这两步完成后,内核会在合适的时机(后面会讲),调用struct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。而所有的驱动程序逻辑,都会由这些回调函数实现,此时,驱动开发者眼中便不再有“设备模型”,转而只关心驱动本身的实现。

    以上两个步骤的补充说明:

    1. 一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用(也正是因为这个原因,本文并不会过多涉及device、device_driver等模块的实现细节)。

    2. 内核提供很多struct device结构的操作接口(具体可以参考include/linux/device.h和drivers/base/core.c的代码),主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。

    3. device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver

    4. device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。

    5. driver开发者可以在struct device变量中,保存描述设备特征的信息,如寻址空间、依赖的GPIOs等,因为device指针会在执行probe等接口时传入,这时driver就可以根据这些信息,执行相应的逻辑操作了。

    4. 设备驱动probe的时机

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

    设备驱动prove的时机有如下几种(分为自动触发和手动触发):

    • 将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_driver)

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

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

    5. 其它杂项

    5.1 device_attribute和driver_attribute

    在"Linux设备模型(4)_sysfs”中,我们有讲到,大多数时候,attribute文件的读写数据流为:vfs---->sysfs---->kobject---->attibute---->kobj_type---->sysfs_ops---->xxx_attribute,其中kobj_type、sysfs_ops和xxx_attribute都是由包含kobject的上层数据结构实现。

    Linux内核中关于该内容的例证到处都是,device也不无例外的提供了这种例子,如下:

     1: /* driver/base/core.c, line 118 */
     2: static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
     3: char *buf) 
     4: {   
     5:     struct device_attribute *dev_attr = to_dev_attr(attr);
     6:     struct device *dev = kobj_to_dev(kobj);
     7:     ssize_t ret = -EIO;
     8: 
     9:     if (dev_attr->show)
     10:        ret = dev_attr->show(dev, dev_attr, buf);
     11:        if (ret >= (ssize_t)PAGE_SIZE) {
     12:            print_symbol("dev_attr_show: %s returned bad count\n",
     13:                        (unsigned long)dev_attr->show);
     14:    }
     15:    return ret;
     16: }
     17:  
     18: static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
     19: const char *buf, size_t count)
     20: {
     21:    struct device_attribute *dev_attr = to_dev_attr(attr);
     22:    struct device *dev = kobj_to_dev(kobj);
     23:    ssize_t ret = -EIO;
     24: 
     25:    if (dev_attr->store)
     26:        ret = dev_attr->store(dev, dev_attr, buf, count);
     27:    return ret;
     28: }
     29:  
     30: static const struct sysfs_ops dev_sysfs_ops = {
     31:    .show   = dev_attr_show,
     32:    .store  = dev_attr_store,
     33: };
     34:  
     35: /* driver/base/core.c, line 243 */
     36: static struct kobj_type device_ktype = {
     37:    .release    = device_release,
     38:    .sysfs_ops  = &dev_sysfs_ops,
     39:    .namespace = device_namespace,
     40: };
     41:  
     42: /* include/linux/device.h, line 478 */
     43: /* interface for exporting device attributes */
     44: struct device_attribute {
     45:    struct attribute    attr;
     46:    ssize_t (*show)(struct device *dev, struct device_attribute *attr,
     47:                    char *buf);
     48:    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
     49:                    const char *buf, size_t count);
     50: };

     

    至于driver的attribute,则要简单的多,其数据流为:vfs---->sysfs---->kobject---->attribute---->driver_attribute,如下:

     1: /* include/linux/device.h, line 247 */
     2: /* sysfs interface for exporting driver attributes */
     3:  
     4: struct driver_attribute {
     5:     struct attribute attr;
     6:     ssize_t (*show)(struct device_driver *driver, char *buf);
     7:     ssize_t (*store)(struct device_driver *driver, const char *buf,
     8:                     size_t count);
     9: };
     10:  
     11: #define DRIVER_ATTR(_name, _mode, _show, _store)    \
     12: struct driver_attribute driver_attr_##_name =       \
     13:    __ATTR(_name, _mode, _show, _store)

    5.2 device_type

    device_type是内嵌在struct device结构中的一个数据结构,用于指明设备的类型,并提供一些额外的辅助功能。它的的形式如下:

     1: /* include/linux/device.h, line 467 */
     2: struct device_type {
     3:     const char *name;
     4:     const struct attribute_group **groups;
     5:     int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
     6:     char *(*devnode)(struct device *dev, umode_t *mode,
     7:                     kuid_t *uid, kgid_t *gid);
     8:     void (*release)(struct device *dev);
     9:  
     10:    const struct dev_pm_ops *pm;
     11: };

    device_type的功能包括:

    • name表示该类型的名称,当该类型的设备添加到内核时,内核会发出"DEVTYPE=‘name’”类型的uevent,告知用户空间某个类型的设备available了
    • groups,该类型设备的公共attribute集合。设备注册时,会同时注册这些attribute。这就是面向对象中“继承”的概念
    • uevent,同理,所有相同类型的设备,会有一些共有的uevent需要发送,由该接口实现
    • devnode,devtmpfs有关的内容,暂不说明
    • release,如果device结构没有提供release接口,就要查询它所属的type是否提供。用于释放device变量所占的空间

    5.3 root device

    在sysfs中有这样一个目录:/sys/devices,系统中所有的设备,都归集在该目录下。有些设备,是通过device_register注册到Kernel并体现在/sys/devices/xxx/下。但有时候我们仅仅需要在/sys/devices/下注册一个目录,该目录不代表任何的实体设备,这时可以使用下面的接口:

     1: /* include/linux/device.h, line 859 */
     2: /*
     3:  * Root device objects for grouping under /sys/devices
     4:  */
     5: extern struct device *__root_device_register(const char *name,
     6: struct module *owner);
     7:  
     8: /*
     9:  * This is a macro to avoid include problems with THIS_MODULE,
     10:  * just as per what is done for device_schedule_callback() above.
     11:  */
     12: #define root_device_register(name) \
     13: __root_device_register(name, THIS_MODULE)
     14:  
     15: extern void root_device_unregister(struct device *root);

    该接口会调用device_register函数,向内核中注册一个设备,但是(你也想到了),没必要注册与之对应的driver(顺便提一下,内核中有很多不需要driver的设备,这是之一)。


    展开全文
  • ARM Linux 3.x的设备树(Device Tree)

    万次阅读 多人点赞 2019-07-08 16:28:33
    本文部分案例文字英文原版来源于http://devicetree.org/Device_Tree_Usage 更多精华文章请扫描下方二维码关注Linux阅码场 1. ARM Device Tree起源 Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称...

    本文部分案例和文字英文原版来源于 http://devicetree.org/Device_Tree_Usage

    更多精华文章请扫描下方二维码关注Linux阅码场

    1.    ARM Device Tree起源

    Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a f*cking pain in the ass”,引发ARM Linux社区的地震,随后ARM社区进行了一系列的重大修正。在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data。读者有兴趣可以统计下常见的s3c2410、s3c6410等板级目录,代码量在数万行。
    社区必须改变这种局面,于是PowerPC等其他体系架构下已经使用的Flattened Device Tree(FDT)进入ARM社区的视野。Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
    Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):

    • CPU的数量和类别
    • 内存基地址和大小
    • 总线和桥
    • 外设连接
    • 中断控制器和中断使用情况
    • GPIO控制器和GPIO使用情况
    • Clock控制器和Clock使用情况

    它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

    2.    Device Tree组成和结构

    整个Device Tree牵涉面比较广,即增加了新的用于描述设备硬件信息的文本格式,又增加了编译这一文本的工具,同时Bootloader也需要支持将编译后的Device Tree传递给Linux内核。

    DTS (device tree source)

    .dts文件是一种ASCII 文本格式的Device Tree描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM Linux在,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录。由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的machine对应的.dts就include这个.dtsi。譬如,对于VEXPRESS而言,vexpress-v2m.dtsi就被vexpress-v2p-ca9.dts所引用, vexpress-v2p-ca9.dts有如下一行:
    /include/ "vexpress-v2m.dtsi"
    当然,和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi。
    .dts(或者其include的.dtsi)基本元素即为前文所述的结点和属性:

    / {
        node1 {
            a-string-property = "A string";
            a-string-list-property = "first string", "second string";
            a-byte-data-property = [0x01 0x23 0x34 0x56];
            child-node1 {
                first-child-property;
                second-child-property = <1>;
                a-string-property = "Hello, world";
            };
            child-node2 {
            };
        };
        node2 {
            an-empty-property;
            a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
            child-node1 {
            };
        };
    };

    上述.dts文件并没有什么真实的用途,但它基本表征了一个Device Tree源文件的结构:
    1个root结点"/";
    root结点下面含一系列子结点,本例中为"node1" 和 "node2";
    结点"node1"下又含有一系列子结点,本例中为"child-node1" 和 "child-node2";
    各结点都有一系列属性。这些属性可能为空,如" an-empty-property";可能为字符串,如"a-string-property";可能为字符串数组,如"a-string-list-property";可能为Cells(由u32整数组成),如"second-child-property",可能为二进制数,如"a-byte-data-property"。
    下面以一个最简单的machine为例来看如何写一个.dts文件。假设此machine的配置如下:
    1个双核ARM Cortex-A9 32位处理器;
    ARM的local bus上的内存映射区域分布了2个串口(分别位于0x101F1000 和 0x101F2000)、GPIO控制器(位于0x101F3000)、SPI控制器(位于0x10170000)、中断控制器(位于0x10140000)和一个external bus桥;
    External bus桥上又连接了SMC SMC91111 Ethernet(位于0x10100000)、I2C控制器(位于0x10160000)、64MB NOR Flash(位于0x30000000);
    External bus桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338实时钟(I2C地址为0x58)。
    其对应的.dts文件为:

    / {
        compatible = "acme,coyotes-revenge";
        #address-cells = <1>;
        #size-cells = <1>;
        interrupt-parent = <&intc>;
    
        cpus {
            #address-cells = <1>;
            #size-cells = <0>;
            cpu@0 {
                compatible = "arm,cortex-a9";
                reg = <0>;
            };
            cpu@1 {
                compatible = "arm,cortex-a9";
                reg = <1>;
            };
        };
    
        serial@101f0000 {
            compatible = "arm,pl011";
            reg = <0x101f0000 0x1000 >;
            interrupts = < 1 0 >;
        };
    
        serial@101f2000 {
            compatible = "arm,pl011";
            reg = <0x101f2000 0x1000 >;
            interrupts = < 2 0 >;
        };
    
        gpio@101f3000 {
            compatible = "arm,pl061";
            reg = <0x101f3000 0x1000
                   0x101f4000 0x0010>;
            interrupts = < 3 0 >;
        };
    
        intc: interrupt-controller@10140000 {
            compatible = "arm,pl190";
            reg = <0x10140000 0x1000 >;
            interrupt-controller;
            #interrupt-cells = <2>;
        };
    
        spi@10115000 {
            compatible = "arm,pl022";
            reg = <0x10115000 0x1000 >;
            interrupts = < 4 0 >;
        };
    
        external-bus {
            #address-cells = <2>
            #size-cells = <1>;
            ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
                      1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
                      2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash
    
            ethernet@0,0 {
                compatible = "smc,smc91c111";
                reg = <0 0 0x1000>;
                interrupts = < 5 2 >;
            };
    
            i2c@1,0 {
                compatible = "acme,a1234-i2c-bus";
                #address-cells = <1>;
                #size-cells = <0>;
                reg = <1 0 0x1000>;
                interrupts = < 6 2 >;
                rtc@58 {
                    compatible = "maxim,ds1338";
                    reg = <58>;
                    interrupts = < 7 3 >;
                };
            };
    
            flash@2,0 {
                compatible = "samsung,k8f1315ebm", "cfi-flash";
                reg = <2 0 0x4000000>;
            };
        };
    };

    上述.dts文件中,root结点"/"的compatible 属性compatible = "acme,coyotes-revenge";定义了系统的名称,它的组织形式为:<manufacturer>,<model>。Linux内核透过root结点"/"的compatible 属性即可判断它启动的是什么machine。
    在.dts文件的每个设备,都有一个compatible 属性,compatible属性用户驱动和设备的绑定。compatible 属性是一个字符串的列表,列表中的第一个字符串表征了结点代表的确切设备,形式为"<manufacturer>,<model>",其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。如在arch/arm/boot/dts/vexpress-v2m.dtsi中的Flash结点:

    flash@0,00000000 {
         compatible = "arm,vexpress-flash", "cfi-flash";
         reg = <0 0x00000000 0x04000000>,
         <1 0x00000000 0x04000000>;
         bank-width = <4>;
     };

    compatible属性的第2个字符串"cfi-flash"明显比第1个字符串"arm,vexpress-flash"涵盖的范围更广。
    再比如,Freescale MPC8349 SoC含一个串口设备,它实现了国家半导体(National Semiconductor)的ns16550 寄存器接口。则MPC8349串口设备的compatible属性为compatible = "fsl,mpc8349-uart", "ns16550"。其中,fsl,mpc8349-uart指代了确切的设备, ns16550代表该设备与National Semiconductor 的16550 UART保持了寄存器兼容。
    接下来root结点"/"的cpus子结点下面又包含2个cpu子结点,描述了此machine上的2个CPU,并且二者的compatible 属性为"arm,cortex-a9"。
    注意cpus和cpus的2个cpu子结点的命名,它们遵循的组织形式为:<name>[@<unit-address>],<>中的内容是必选项,[]中的则为可选项。name是一个ASCII字符串,用于描述结点对应的设备类型,如3com Ethernet适配器对应的结点name宜为ethernet,而不是3com509。如果一个结点描述的设备有地址,则应该给出@unit-address。多个相同类型设备结点的name可以一样,只要unit-address不同即可,如本例中含有cpu@0、cpu@1以及serial@101f0000与serial@101f2000这样的同名结点。设备的unit-address地址也经常在其对应结点的reg属性中给出。ePAPR标准给出了结点命名的规范。
    可寻址的设备使用如下信息来在Device Tree中编码地址信息:

    •     reg
    •     #address-cells
    •     #size-cells

    其中reg的组织形式为reg = <address1 length1 [address2 length2] [address3 length3] ... >,其中的每一组address length表明了设备使用的一个地址范围。address为1个或多个32位的整型(即cell),而length则为cell的列表或者为空(若#size-cells = 0)。address 和 length 字段是可变长的,父结点的#address-cells和#size-cells分别决定了子结点的reg属性的address和length字段的长度。在本例中,root结点的#address-cells = <1>;和#size-cells = <1>;决定了serial、gpio、spi等结点的address和length字段的长度分别为1。cpus 结点的#address-cells = <1>;和#size-cells = <0>;决定了2个cpu子结点的address为1,而length为空,于是形成了2个cpu的reg = <0>;和reg = <1>;。external-bus结点的#address-cells = <2>和#size-cells = <1>;决定了其下的ethernet、i2c、flash的reg字段形如reg = <0 0 0x1000>;、reg = <1 0 0x1000>;和reg = <2 0 0x4000000>;。其中,address字段长度为0,开始的第一个cell(0、1、2)是对应的片选,第2个cell(0,0,0)是相对该片选的基地址,第3个cell(0x1000、0x1000、0x4000000)为length。特别要留意的是i2c结点中定义的 #address-cells = <1>;和#size-cells = <0>;又作用到了I2C总线上连接的RTC,它的address字段为0x58,是设备的I2C地址。
    root结点的子结点描述的是CPU的视图,因此root子结点的address区域就直接位于CPU的memory区域。但是,经过总线桥后的address往往需要经过转换才能对应的CPU的memory映射。external-bus的ranges属性定义了经过external-bus桥后的地址范围如何映射到CPU的memory区域。

            ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
                      1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
                      2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash

    ranges是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射。映射表中的子地址、父地址分别采用子地址空间的#address-cells和父地址空间的#address-cells大小。对于本例而言,子地址空间的#address-cells为2,父地址空间的#address-cells值为1,因此0 0  0x10100000   0x10000的前2个cell为external-bus后片选0上偏移0,第3个cell表示external-bus后片选0上偏移0的地址空间被映射到CPU的0x10100000位置,第4个cell表示映射的大小为0x10000。ranges的后面2个项目的含义可以类推。
    Device Tree中还可以中断连接信息,对于中断控制器而言,它提供如下属性:
    interrupt-controller – 这个属性为空,中断控制器应该加上此属性表明自己的身份;
    #interrupt-cells – 与#address-cells 和 #size-cells相似,它表明连接此中断控制器的设备的interrupts属性的cell大小。
    在整个Device Tree中,与中断相关的属性还包括:
    interrupt-parent – 设备结点透过它来指定它所依附的中断控制器的phandle,当结点没有指定interrupt-parent 时,则从父级结点继承。对于本例而言,root结点指定了interrupt-parent = <&intc>;其对应于intc: interrupt-controller@10140000,而root结点的子结点并未指定interrupt-parent,因此它们都继承了intc,即位于0x10140000的中断控制器。
    interrupts – 用到了中断的设备结点透过它指定中断号、触发方法等,具体这个属性含有多少个cell,由它依附的中断控制器结点的#interrupt-cells属性决定。而具体每个cell又是什么含义,一般由驱动的实现决定,而且也会在Device Tree的binding文档中说明。譬如,对于ARM GIC中断控制器而言,#interrupt-cells为3,它3个cell的具体含义Documentation/devicetree/bindings/arm/gic.txt就有如下文字说明:

    01   The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
    02   interrupts.
    03
    04   The 2nd cell contains the interrupt number for the interrupt type.
    05   SPI interrupts are in the range [0-987].  PPI interrupts are in the
    06   range [0-15].
    07
    08   The 3rd cell is the flags, encoded as follows:
    09         bits[3:0] trigger type and level flags.
    10                 1 = low-to-high edge triggered
    11                 2 = high-to-low edge triggered
    12                 4 = active high level-sensitive
    13                 8 = active low level-sensitive
    14         bits[15:8] PPI interrupt cpu mask.  Each bit corresponds to each of
    15         the 8 possible cpus attached to the GIC.  A bit set to '1' indicated
    16         the interrupt is wired to that CPU.  Only valid for PPI interrupts.

    另外,值得注意的是,一个设备还可能用到多个中断号。对于ARM GIC而言,若某设备使用了SPI的168、169号2个中断,而言都是高电平触发,则该设备结点的interrupts属性可定义为:interrupts = <0 168 4>, <0 169 4>;
    除了中断以外,在ARM Linux中clock、GPIO、pinmux都可以透过.dts中的结点和属性进行描述。

    DTC (device tree compiler)

    将.dts编译为.dtb的工具。DTC的源代码位于内核的scripts/dtc目录,在Linux内核使能了Device Tree的情况下,编译内核的时候主机工具dtc会被编译出来,对应scripts/dtc/Makefile中的“hostprogs-y := dtc”这一hostprogs编译target。
    在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来,如与VEXPRESS对应的.dtb包括:

    dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \
            vexpress-v2p-ca9.dtb \
            vexpress-v2p-ca15-tc1.dtb \
            vexpress-v2p-ca15_a7.dtb \
            xenvm-4.2.dtb

    在Linux下,我们可以单独编译Device Tree文件。当我们在Linux内核下运行make dtbs时,若我们之前选择了ARCH_VEXPRESS,上述.dtb都会由对应的.dts编译出来。因为arch/arm/Makefile中含有一个dtbs编译target项目。

    Device Tree Blob (.dtb)

    .dtb是.dts被DTC编译后的二进制格式的Device Tree描述,可由Linux内核解析。通常在我们为电路板制作NAND、SD启动image时,会为.dtb文件单独留下一个很小的区域以存放之,之后bootloader在引导kernel的过程中,会先读取该.dtb到内存。

    Binding

    对于Device Tree中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为.txt。这些文档位于内核的Documentation/devicetree/bindings目录,其下又分为很多子目录。

    Bootloader

    Uboot mainline 从 v1.1.3开始支持Device Tree,其对ARM的支持则是和ARM内核支持Device Tree同期完成。
    为了使能Device Tree,需要编译Uboot的时候在config文件中加入
    #define CONFIG_OF_LIBFDT
    在Uboot中,可以从NAND、SD或者TFTP等任意介质将.dtb读入内存,假设.dtb放入的内存地址为0x71000000,之后可在Uboot运行命令fdt addr命令设置.dtb的地址,如:
    U-Boot> fdt addr 0x71000000
    fdt的其他命令就变地可以使用,如fdt resize、fdt print等。
    对于ARM来讲,可以透过bootz kernel_addr initrd_address dtb_address的命令来启动内核,即dtb_address作为bootz或者bootm的最后一次参数,第一个参数为内核映像的地址,第二个参数为initrd的地址,若不存在initrd,可以用 -代替。

    3.    Device Tree引发的BSP和驱动变更

    有了Device Tree后,大量的板级信息都不再需要,譬如过去经常在arch/arm/plat-xxx和arch/arm/mach-xxx实施的如下事情:
    1.    注册platform_device,绑定resource,即内存、IRQ等板级信息。

    透过Device Tree后,形如

     

    90 static struct resource xxx_resources[] = {
    91         [0] = {
    92                 .start  = …,
    93                 .end    = …,
    94                 .flags  = IORESOURCE_MEM,
    95         },
    96         [1] = {
    97                 .start  = …,
    98                 .end    = …,
    99                 .flags  = IORESOURCE_IRQ,
    100         },
    101 };
    102
    103 static struct platform_device xxx_device = {
    104         .name           = "xxx",
    105         .id             = -1,
    106         .dev            = {
    107                                 .platform_data          = &xxx_data,
    108         },
    109         .resource       = xxx_resources,
    110         .num_resources  = ARRAY_SIZE(xxx_resources),
    111 };

    之类的platform_device代码都不再需要,其中platform_device会由kernel自动展开。而这些resource实际来源于.dts中设备结点的reg、interrupts属性。典型地,大多数总线都与“simple_bus”兼容,而在SoC对应的machine的.init_machine成员函数中,调用of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);即可自动展开所有的platform_device。譬如,假设我们有个XXX SoC,则可在arch/arm/mach-xxx/的板文件中透过如下方式展开.dts中的设备结点对应的platform_device:

     

     

    18 static struct of_device_id xxx_of_bus_ids[] __initdata = {
    19         { .compatible = "simple-bus", },
    20         {},
    21 };
    22
    23 void __init xxx_mach_init(void)
    24 {
    25         of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);
    26 }
    32
    33 #ifdef CONFIG_ARCH_XXX
    38
    39 DT_MACHINE_START(XXX_DT, "Generic XXX (Flattened Device Tree)")
    41         …
    45         .init_machine   = xxx_mach_init,
    46         …
    49 MACHINE_END
    50 #endif

     

     

     

    2.    注册i2c_board_info,指定IRQ等板级信息。

    形如

     

    145 static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
    146         {
    147                 I2C_BOARD_INFO("tlv320aic23", 0x1a),
    148         }, {
    149                 I2C_BOARD_INFO("fm3130", 0x68),
    150         }, {
    151                 I2C_BOARD_INFO("24c64", 0x50),
    152         },
    153 };

    之类的i2c_board_info代码,目前不再需要出现,现在只需要把tlv320aic23、fm3130、24c64这些设备结点填充作为相应的I2C controller结点的子结点即可,类似于前面的

     

     

          i2c@1,0 {
                compatible = "acme,a1234-i2c-bus";
                …
                rtc@58 {
                    compatible = "maxim,ds1338";
                    reg = <58>;
                    interrupts = < 7 3 >;
                };
            };

    Device Tree中的I2C client会透过I2C host驱动的probe()函数中调用of_i2c_register_devices(&i2c_dev->adapter);被自动展开。

     

    3.    注册spi_board_info,指定IRQ等板级信息。

    形如

     

    79 static struct spi_board_info afeb9260_spi_devices[] = {
    80         {       /* DataFlash chip */
    81                 .modalias       = "mtd_dataflash",
    82                 .chip_select    = 1,
    83                 .max_speed_hz   = 15 * 1000 * 1000,
    84                 .bus_num        = 0,
    85         },
    86 };

    之类的spi_board_info代码,目前不再需要出现,与I2C类似,现在只需要把mtd_dataflash之类的结点,作为SPI控制器的子结点即可,SPI host驱动的probe函数透过spi_register_master()注册master的时候,会自动展开依附于它的slave。

     

    4.    多个针对不同电路板的machine,以及相关的callback。

    过去,ARM Linux针对不同的电路板会建立由MACHINE_START和MACHINE_END包围起来的针对这个machine的一系列callback,譬如:

     

    373 MACHINE_START(VEXPRESS, "ARM-Versatile Express")
    374         .atag_offset    = 0x100,
    375         .smp            = smp_ops(vexpress_smp_ops),
    376         .map_io         = v2m_map_io,
    377         .init_early     = v2m_init_early,
    378         .init_irq       = v2m_init_irq,
    379         .timer          = &v2m_timer,
    380         .handle_irq     = gic_handle_irq,
    381         .init_machine   = v2m_init,
    382         .restart        = vexpress_restart,
    383 MACHINE_END

    这些不同的machine会有不同的MACHINE ID,Uboot在启动Linux内核时会将MACHINE ID存放在r1寄存器,Linux启动时会匹配Bootloader传递的MACHINE ID和MACHINE_START声明的MACHINE ID,然后执行相应machine的一系列初始化函数。

     

    引入Device Tree之后,MACHINE_START变更为DT_MACHINE_START,其中含有一个.dt_compat成员,用于表明相关的machine与.dts中root结点的compatible属性兼容关系。如果Bootloader传递给内核的Device Tree中root结点的compatible属性出现在某machine的.dt_compat表中,相关的machine就与对应的Device Tree匹配,从而引发这一machine的一系列初始化函数被执行。

     

    489 static const char * const v2m_dt_match[] __initconst = {
    490         "arm,vexpress",
    491         "xen,xenvm",
    492         NULL,
    493 };
    495 DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
    496         .dt_compat      = v2m_dt_match,
    497         .smp            = smp_ops(vexpress_smp_ops),
    498         .map_io         = v2m_dt_map_io,
    499         .init_early     = v2m_dt_init_early,
    500         .init_irq       = v2m_dt_init_irq,
    501         .timer          = &v2m_dt_timer,
    502         .init_machine   = v2m_dt_init,
    503         .handle_irq     = gic_handle_irq,
    504         .restart        = vexpress_restart,
    505 MACHINE_END

    Linux倡导针对多个SoC、多个电路板的通用DT machine,即一个DT machine的.dt_compat表含多个电路板.dts文件的root结点compatible属性字符串。之后,如果的电路板的初始化序列不一样,可以透过int of_machine_is_compatible(const char *compat) API判断具体的电路板是什么。

     

        譬如arch/arm/mach-exynos/mach-exynos5-dt.c的EXYNOS5_DT machine同时兼容"samsung,exynos5250"和"samsung,exynos5440":

     

    158 static char const *exynos5_dt_compat[] __initdata = {
    159         "samsung,exynos5250",
    160         "samsung,exynos5440",
    161         NULL
    162 };
    163
    177 DT_MACHINE_START(EXYNOS5_DT, "SAMSUNG EXYNOS5 (Flattened Device Tree)")
    178         /* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
    179         .init_irq       = exynos5_init_irq,
    180         .smp            = smp_ops(exynos_smp_ops),
    181         .map_io         = exynos5_dt_map_io,
    182         .handle_irq     = gic_handle_irq,
    183         .init_machine   = exynos5_dt_machine_init,
    184         .init_late      = exynos_init_late,
    185         .timer          = &exynos4_timer,
    186         .dt_compat      = exynos5_dt_compat,
    187         .restart        = exynos5_restart,
    188         .reserve        = exynos5_reserve,
    189 MACHINE_END

         它的.init_machine成员函数就针对不同的machine进行了不同的分支处理:

     

    126 static void __init exynos5_dt_machine_init(void)
    127 {
    128         …
    149
    150         if (of_machine_is_compatible("samsung,exynos5250"))
    151                 of_platform_populate(NULL, of_default_bus_match_table,
    152                                      exynos5250_auxdata_lookup, NULL);
    153         else if (of_machine_is_compatible("samsung,exynos5440"))
    154                 of_platform_populate(NULL, of_default_bus_match_table,
    155                                      exynos5440_auxdata_lookup, NULL);
    156 }

     

     

     

    使用Device Tree后,驱动需要与.dts中描述的设备结点进行匹配,从而引发驱动的probe()函数执行。对于platform_driver而言,需要添加一个OF匹配表,如前文的.dts文件的"acme,a1234-i2c-bus"兼容I2C控制器结点的OF匹配表可以是:

     

    436 static const struct of_device_id a1234_i2c_of_match[] = {
    437         { .compatible = "acme,a1234-i2c-bus ", },
    438         {},
    439 };
    440 MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);
    441
    442 static struct platform_driver i2c_a1234_driver = {
    443         .driver = {
    444                 .name = "a1234-i2c-bus ",
    445                 .owner = THIS_MODULE,
    449                 .of_match_table = a1234_i2c_of_match,
    450         },
    451         .probe = i2c_a1234_probe,
    452         .remove = i2c_a1234_remove,
    453 };
    454 module_platform_driver(i2c_a1234_driver);

     

    对于I2C和SPI从设备而言,同样也可以透过of_match_table添加匹配的.dts中的相关结点的compatible属性,如sound/soc/codecs/wm8753.c中的:

     

    1533 static const struct of_device_id wm8753_of_match[] = {
    1534         { .compatible = "wlf,wm8753", },
    1535         { }
    1536 };
    1537 MODULE_DEVICE_TABLE(of, wm8753_of_match);
    1587 static struct spi_driver wm8753_spi_driver = {
    1588         .driver = {
    1589                 .name   = "wm8753",
    1590                 .owner  = THIS_MODULE,
    1591                 .of_match_table = wm8753_of_match,
    1592         },
    1593         .probe          = wm8753_spi_probe,
    1594         .remove         = wm8753_spi_remove,
    1595 };
    1640 static struct i2c_driver wm8753_i2c_driver = {
    1641         .driver = {
    1642                 .name = "wm8753",
    1643                 .owner = THIS_MODULE,
    1644                 .of_match_table = wm8753_of_match,
    1645         },
    1646         .probe =    wm8753_i2c_probe,
    1647         .remove =   wm8753_i2c_remove,
    1648         .id_table = wm8753_i2c_id,
    1649 };

    不过这边有一点需要提醒的是,I2C和SPI外设驱动和Device Tree中设备结点的compatible 属性还有一种弱式匹配方法,就是别名匹配。compatible 属性的组织形式为<manufacturer>,<model>,别名其实就是去掉compatible 属性中逗号前的manufacturer前缀。关于这一点,可查看drivers/spi/spi.c的源代码,函数spi_match_device()暴露了更多的细节,如果别名出现在设备spi_driver的id_table里面,或者别名与spi_driver的name字段相同,SPI设备和驱动都可以匹配上:

     

     

     

    90 static int spi_match_device(struct device *dev, struct device_driver *drv)
    91 {
    92         const struct spi_device *spi = to_spi_device(dev);
    93         const struct spi_driver *sdrv = to_spi_driver(drv);
    94
    95         /* Attempt an OF style match */
    96         if (of_driver_match_device(dev, drv))
    97                 return 1;
    98
    99         /* Then try ACPI */
    100         if (acpi_driver_match_device(dev, drv))
    101                 return 1;
    102
    103         if (sdrv->id_table)
    104                 return !!spi_match_id(sdrv->id_table, spi);
    105
    106         return strcmp(spi->modalias, drv->name) == 0;
    107 }
    71 static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
    72                                                 const struct spi_device *sdev)
    73 {
    74         while (id->name[0]) {
    75                 if (!strcmp(sdev->modalias, id->name))
    76                         return id;
    77                 id++;
    78         }
    79         return NULL;
    80 }

     

    4.    常用OF API

    在Linux的BSP和驱动代码中,还经常会使用到Linux中一组Device Tree的API,这些API通常被冠以of_前缀,它们的实现代码位于内核的drivers/of目录。这些常用的API包括:

    int of_device_is_compatible(const struct device_node *device,const char *compat);

    判断设备结点的compatible 属性是否包含compat指定的字符串。当一个驱动支持2个或多个设备的时候,这些不同.dts文件中设备的compatible 属性都会进入驱动 OF匹配表。因此驱动可以透过Bootloader传递给内核的Device Tree中的真正结点的compatible 属性以确定究竟是哪一种设备,从而根据不同的设备类型进行不同的处理。如drivers/pinctrl/pinctrl-sirf.c即兼容于"sirf,prima2-pinctrl",又兼容于"sirf,prima2-pinctrl",在驱动中就有相应分支处理:

     

    1682 if (of_device_is_compatible(np, "sirf,marco-pinctrl"))
    1683      is_marco = 1;

    struct device_node *of_find_compatible_node(struct device_node *from,

     

             const char *type, const char *compatible);

     

    根据compatible属性,获得设备结点。遍历Device Tree中所有的设备结点,看看哪个结点的类型、compatible属性与本函数的输入参数匹配,大多数情况下,from、type为NULL。

    int of_property_read_u8_array(const struct device_node *np,

                         const char *propname, u8 *out_values, size_t sz);

    int of_property_read_u16_array(const struct device_node *np,

                          const char *propname, u16 *out_values, size_t sz);

    int of_property_read_u32_array(const struct device_node *np,

                          const char *propname, u32 *out_values, size_t sz);

    int of_property_read_u64(const struct device_node *np, const char

    *propname, u64 *out_value);

    读取设备结点np的属性名为propname,类型为8、16、32、64位整型数组的属性。对于32位处理器来讲,最常用的是of_property_read_u32_array()。如在arch/arm/mm/cache-l2x0.c中,透过如下语句读取L2 cache的"arm,data-latency"属性:

     

    534         of_property_read_u32_array(np, "arm,data-latency",
    535                                    data, ARRAY_SIZE(data));

    在arch/arm/boot/dts/vexpress-v2p-ca9.dts中,含有"arm,data-latency"属性的L2 cache结点如下:

     

    137         L2: cache-controller@1e00a000 {
    138                 compatible = "arm,pl310-cache";
    139                 reg = <0x1e00a000 0x1000>;
    140                 interrupts = <0 43 4>;
    141                 cache-level = <2>;
    142                 arm,data-latency = <1 1 1>;
    143                 arm,tag-latency = <1 1 1>;
    144         }


    有些情况下,整形属性的长度可能为1,于是内核为了方便调用者,又在上述API的基础上封装出了更加简单的读单一整形属性的API,它们为int of_property_read_u8()、of_property_read_u16()等,实现于include/linux/of.h:

     

     

    513 static inline int of_property_read_u8(const struct device_node *np,
    514                                        const char *propname,
    515                                        u8 *out_value)
    516 {
    517         return of_property_read_u8_array(np, propname, out_value, 1);
    518 }
    519
    520 static inline int of_property_read_u16(const struct device_node *np,
    521                                        const char *propname,
    522                                        u16 *out_value)
    523 {
    524         return of_property_read_u16_array(np, propname, out_value, 1);
    525 }
    526
    527 static inline int of_property_read_u32(const struct device_node *np,
    528                                        const char *propname,
    529                                        u32 *out_value)
    530 {
    531         return of_property_read_u32_array(np, propname, out_value, 1);
    532 }


    int of_property_read_string(struct device_node *np, const char

     

    *propname, const char **out_string);

    int of_property_read_string_index(struct device_node *np, const char

        *propname, int index, const char **output);

    前者读取字符串属性,后者读取字符串数组属性中的第index个字符串。如drivers/clk/clk.c中的of_clk_get_parent_name()透过of_property_read_string_index()遍历clkspec结点的所有"clock-output-names"字符串数组属性。

     

    1759 const char *of_clk_get_parent_name(struct device_node *np, int index)
    1760 {
    1761         struct of_phandle_args clkspec;
    1762         const char *clk_name;
    1763         int rc;
    1764
    1765         if (index < 0)
    1766                 return NULL;
    1767
    1768         rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
    1769                                         &clkspec);
    1770         if (rc)
    1771                 return NULL;
    1772
    1773         if (of_property_read_string_index(clkspec.np, "clock-output-names",
    1774                                   clkspec.args_count ? clkspec.args[0] : 0,
    1775                                           &clk_name) < 0)
    1776                 clk_name = clkspec.np->name;
    1777
    1778         of_node_put(clkspec.np);
    1779         return clk_name;
    1780 }
    1781 EXPORT_SYMBOL_GPL(of_clk_get_parent_name);


    static inline bool of_property_read_bool(const struct device_node *np,

     

                                             const char *propname);

     

    如果设备结点np含有propname属性,则返回true,否则返回false。一般用于检查空属性是否存在。

     

    void __iomem *of_iomap(struct device_node *node, int index);

     

    通过设备结点直接进行设备内存区间的 ioremap(),index是内存段的索引。若设备结点的reg属性有多段,可通过index标示要ioremap的是哪一段,只有1段的情况,index为0。采用Device Tree后,大量的设备驱动通过of_iomap()进行映射,而不再通过传统的ioremap。

     

    unsigned int irq_of_parse_and_map(struct device_node *dev, int index);

    透过Device Tree或者设备的中断号,实际上是从.dts中的interrupts属性解析出中断号。若设备使用了多个中断,index指定中断的索引号。

     

    还有一些OF API,这里不一一列举,具体可参考include/linux/of.h头文件。

    5.    总结

    ARM社区一贯充斥的大量垃圾代码导致Linus盛怒,因此社区在2011年到2012年进行了大量的工作。ARM Linux开始围绕Device Tree展开,Device Tree有自己的独立的语法,它的源文件为.dts,编译后得到.dtb,Bootloader在引导Linux内核的时候会将.dtb地址告知内核。之后内核会展开Device Tree并创建和注册相关的设备,因此arch/arm/mach-xxx和arch/arm/plat-xxx中大量的用于注册platform、I2C、SPI板级信息的代码被删除,而驱动也以新的方式和.dts中定义的设备结点进行匹配。

    更多精华文章请扫描下方二维码关注Linux阅码场

    展开全文
  • Linux Device和Driver注册及配对过程

    千次阅读 2018-06-29 10:31:42
    *** 原文来自...device_register() / driver_register()执行时通过枚举BUS上的Driver/Device来实现绑定,本文详解这一过程。这是整个LINUX设备驱动的基础,PLATF...

     *** 原文来自http://blog.csdn.net/thl789/article/details/6723350 ***

    Linux 2.6的设备驱动模型中,所有的device都是通过Bus相连。device_register() / driver_register()执行时通过枚举BUS上的Driver/Device来实现绑定,本文详解这一过程。这是整个LINUX设备驱动的基础,PLATFORM设备,I2C上的设备等诸设备的注册最终也是调用本文讲述的注册函数来实现的。

    Linux Device的注册最终都是通过device_register()实现,Driver的注册最终都是通过driver_register()实现。下图对照说明了Device和Driver的注册过程。



     

    上面的图解一目了然,详细过程不再赘述。注意以下几点说明:

      • BUS的p->drivers_autoprobe;1默认是true。
      • bus_for_each_drv()是对BUS上所有的Driver都进行__device_attach()操作;同样的,bus_for_each_dev()是对BUS上所有的Device都进行__driver_attach()操作。
      • BUS上实现的.match()函数,定义了Device和Driver绑定时的规则。比如Platform实现的就是先比较id_table,然后比较name的规则。如果BUS的match()函数没实现,认为BUS上的所有的Device和Driver都是match的,具体后续过程要看probe()的实现了。
      • Probe的规则是:如果BUS上实现了probe就用BUS的probe;否则才会用driver的probe。

     

    Device一般是先于Driver注册,但也不全是这样的顺序。Linux的Device和Driver的注册过程分别枚举挂在该BUS上所有的Driver和Device实现了这种时序无关性。



    展开全文
  • Linux /dev目录详解和Linux系统各个目录的作用

    万次阅读 多人点赞 2012-01-11 23:21:27
    Linux /dev目录详解  在linux下,/dev目录是很重要的,各种设备都在下面。下面简单总结一下:  dev是设备(device)的英文缩写。/dev这个目录对所有的用户都十分重要。因为在这个目录中包含了所有Linux系统中...
  • Linux设备文件(Device file)

    千次阅读 2018-03-31 17:30:09
    Linux设备文件(Device file) 设备文件概述在类Unix操作系统中,设备文件或特殊文件是设备驱动程序的接口,出现在文件系统中就好像它是普通文件一样。在MS-DOS,OS / 2Microsoft Windows中也有特殊文件。 这些...
  • https://www.cnblogs.com/lifan3a/articles/5045447.html ... linux平台设备驱动架构详解 Linux Platform Device and Driver 从Linux 2.6起引入了一套新的驱动管理注册机制:Platform
  • Linux DEVICE_ATTR 介绍及使用示例

    千次阅读 2018-10-17 17:04:29
    使用DEVICE_ATTR,可以实现驱动在sys目录自动创建文件,我们只需要实现showstore函数即可. 然后在应用层就能通过catecho命令来对sys创建出来的文件进行读写驱动设备,实现交互. 2.DEVICE_ATTR()宏定义 DEVICE_...
  • linux设备模型六(device细节)

    千次阅读 2019-09-03 09:31:31
    device相关的操作接口: struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...); struct device *device_create_vargs(struct class ...
  • Linux为什么使用DeviceTree

    千次阅读 2016-03-24 12:21:43
    介绍为什么使用DeviceTree介绍DeviceTree之前先看一下Linux内核中设备注册到系统的方式有哪些
  • 新浪微博 @宋宝华Barry 在@微盘 分享了 Prentice.Hall出版社《Essential.Linux.Device.Drivers》中文版高清电子版 "宋宝华_精通LINUX设备驱动开发.pdf"http://t.cn/zYjS7sh 目 录 第1章 引言 1 1.1 演进 1 1.2...
  • From: 全面解析Linux 内核 3.10.x - 本文章完全基于MIPS架构 如果你觉得累,那么就继续吧 - 佚名一、Device Tree简介Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。在PPC 平台,已使用很长时间...
  • linux设备模型七(device_driver细节)

    千次阅读 2018-08-07 23:37:38
    阅读这篇之前,建议先阅读我的下面这篇博客了解device_driver的数据结构大概作用。 https://blog.csdn.net/qq_16777851/article/details/81429257 了解我的下面这篇博客可以对device部分了解清晰。同时,下面...
  • Linux多播问题(No such device)解决方法: 这主要当前的网络配置有关,因为多播IP地址没有加入到路由表中。解决方法:把需要用到的多播地址(如224.0.0.88)加入到路由表中,命令如下: sudo route add -...
  • Linux 设备树(Device Tree)简介

    千次阅读 2016-11-16 11:47:41
    DTS (device tree ...基本上,在ARM Linux在,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录。由于一个SoC可能对应多个machine(一个SoC可以对应多个产品电路板),势必这些.dts文
  • Linux umount 报 device is busy 的处理方法

    万次阅读 2017-12-05 14:59:14
    Linux 挂盘这块也小学了两招。   一. umout 移动硬盘  开始用sftp 将安装文件copy到服务器的时候,速度太慢了,500k/s。几个G的东西,copy 这些就要半个多小时,扛不住,拿移动硬盘来copy了。...
  • device_create和device_add区别

    千次阅读 2018-08-08 00:19:13
    在学习linux驱动的时候经常遇到device_create和device_add两个函数,因为这两个函数都是属于驱动底层的,所以平时很少关注。但最近准备写一个简单驱动框架练手,所以到底用那个来创建出相应的设备,所以准备学习一下...
  • Linux Kernel设备驱动模型之 struct device

    千次阅读 2017-03-12 12:25:49
    设备模型之设备描述: /**  * struct device - The basic device ... * @parent: The device's "parent" device, the device to which it is attached.  * In most cases, a parent device is some sort of bus
  • Linux设备模型之device_add

    千次阅读 2017-05-23 11:16:58
    linux设备类型的内容还是比较多的,这里就重点说明device_add函数的流程。 1.总体框架 linux设备模型:设备device,驱动driver,总线bus。设备代表物理设备,驱动代表了设备操作方法,bus则是用来管理匹配它们...
  • Linux中用platform_device结构体来描述

    千次阅读 2014-08-01 10:19:57
    二、相关概念 1、平台设备: ...在Linux中用platform_device结构体来描述一个平台设备,在2.6.30.4内核中定义在:include/linux/platform_device.h中,如下: struct platform_device {
1 2 3 4 5 ... 20
收藏数 293,188
精华内容 117,275
关键字:

device和目录 linux