LDD - newer2/Blog GitHub Wiki
- 源文件 头文件+模块参数+模块功能函数+其他+模块加载函数+模块卸载函数+模块许可声明
#include <linux/init.h>
#include <linux/module.h>
#include <linux/>
#include "add_sub.h"
static long a=1; //模块参数
static int b=1;
long add_integer(int a, int b) //模块功能函数
{
return a+b;
}
long sub_integer(int a, int b)
{
return a-b;
}
static int hello_init(void) //模块加载函数,insmod时自动调用
{
printk(KERN_ALERT "Hello World\n");
return 0;
}
static void hello_exit(void) //模块卸载函数,rmmod时自动调用
{
printk("KERN_ALTER "Hello World Exit\n");
}
module_param(a, long, S_IRUGO); //module_param(参数名, 参数数据类型, 参数读写权限)
module_param(b, int, S_IRUGO); //导出参数没有浮点类型,内核并不能完美的支持浮点类
//型,printk()也不支持浮点类型
/*+++++导出功能函数以供其他模块调用+++++*/
/*----------
1. 在insmod module.1时,内核发现以ELF格式组织的module.1的.symtab数据结构中有函数可以导出,内核便将其中导出的功能函数的内存地址记录在“内核符号表”;insmd module.2时,在以ELF格式组织的module.2的symtab中发现未解析的函数引用,通过查询“内核符号表”,将对应函数地址写入module.2的symtab中
2. 模块中定义的函数和内核中的函数冲突,怎么办?编译器认为模块中的函数都是私有的,除非使用导出宏
+++++++++++*/
EXPORT_SYMBOL(add_integer); //导出宏
EXPORT_SYMBOL(sub_integer);
module_init(hello_init); //模块加载函数和卸载函数的宏定义
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL"); //模块许可声明
- Makefile 正确编译内核模块条件:
- 不同版本的内核需要对应版本的编译工具(eg.gcc binutil)
- 编译的内核模块应该和测试系统对应同一内核源码
- 内核源码应该至少编译过一次
ifeq ($(KERNELRELEASE),) # 检查变量KERNELRELEASE是否为空
KERNELDIR ?= /linux-2.6.34.14/linux-2.6.34.14
PWD := $(shell pwd)
modules: # Makefile的一个功能选项
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules # -C $(KERNELDIR)使编译器进入内核源码目录,读取其Makefile,变量KERNELRELEASE将在这里被赋值(跟踪的makefile文件可知,类似于KERNELRELEASE的许多变量,KERNELVERSION..等等将在这里被赋值并导出)
# 编译器根据M=$(PWD)第二次进入模块所在目录,并再一次执行Makefile,此时KERNELRELEASE将是内核发布版本信息,
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions # 删除中间文件
else
obj-m := hello.o # 开始编译模块
endif
- 模块操作
- insmod 模块.ko 参数1=value1 参数2=value2 #加载模块,加载后自动调用hello_init
- rmmod 模块.ko #卸载模块,自动调用hello_exit
- modprobe 可以解决模块之间依赖性的加载和删除模块命令
- lsmod 列出已经加载模块相关信息
- modinfo 查询模块作者、版权等信息
实例
追踪流程 ismod-->globalmem_init-->globalmem_setup_cdev-->cdev_init-->cdev_add globalmem.c部分代码
struct globalmem_dev{
struct cdev cdev;
unsigned char mem[MAXSIZE];
};
int globalmem_open(struct inode *nodep, struct file *filp)
{
...
}
int globalmem_release(struct inode *nodep, struct file *filp)
{
...
}
static ssize_t globalmem_read(struct file * filp, char __user *buf, size_t size, loff_t *ppos)
{
...
}
static ssize_t globalmem_write(struct file * filp, const char __user *buf, size_t size, loff_t *ppos)
{
...
}
static int globalmem_ioctl(struct inode * nodep, struct file *filp, unsigned int cmd, unsigned long arg)
{
...
}
static off_t globalmem_lseek(struct file * filp, loff_t offset, int orig)
{
...
}
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.open = globalmem_open,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl,
.llseek = globalmem_lseek,
.release = globalmem_release,
};
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err, devno = MKDEV(globalmajor, index);
cdev_init(&dev->cdev, &globalmem_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if(err)
printk(KERN_NOTICE "Error %d adding globalmem %d",err, index);
}
static int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmajor, 0);
if(globalmajor)
result = register_chrdev_region(devno, 1, "globalmem");
else{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmajor = MAJOR(devno);
}
if(result < 0)
return result;
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if(!globalmem_devp){
result = -ENOMEM;
goto fail_malloc;
}
globalclass = class_create(THIS_MODULE, DEVICE_NAME);
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
globalmem_setup_cdev(globalmem_devp, 0);
printk("globalmajor is %d\n",(int)globalmajor);
device_create(globalclass, NULL, devno, NULL, DEVICE_NAME);
printk("/dev/globalmem created!\n");
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return result;
}
module_init(globalmem_init);
module_exit(globalmem_exit);
cdev_init cdev_init-->kobject_init-->kobject_init_internal
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
- kobject_init调用kobjectinit_internal,并将kobj->ktype与默认ktype:ktype_cdev_default绑定
- kobjct_init_internal完成kobject中各个成员初始化
cdev_add cdev_add-->kobj_map
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
kobj_map函数定义
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
- kobj_map 封装好一个probe结构并将它的地址放入probes数组进而封装进cdev_map。根据传入的设备号的个数,将设备号和cdev依次封装到kmalloc_array()分配的n个probe结构中。遍历probs数组,直到找到一个值为NULL的元素,再将probe的地址存入probes, 将设备号对255取余后与probes的下标对应。至此,我们就将我们的cdev放入的内核的数据结构
kobj_map相关数据结构
fs/char_dev.c:static struct kobj_map *cdev_map;
drivers/base/map.c
struct kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
- data:指向cdev结构体
- next:连接所有的probe
- get、lock两个函数接口
当设备对象被放入内核时,如何找到特定的cdev。即用户空间通过设备文件如何首次找到特定cdev结构体,从而获取struct file_operations
等数据结构。不过要先了解下面关于struct inode
和struct file
的描述。
字符设备访问流程:
文件路径-->inode-->chrdev_open-->kobj_lookup-->cdev-->cdev.fops.my_chr_open()
- 虽然我们有了字符设备的设备文件,inode也被构造并初始化了, 但是在第一次调用chrdev_open()之前,这个inode和具体的chr_dev对象并没有直接关系,而只是通过设备号建立的"间接"关系。在第一次调用chrdev_open()之后, inode->i_cdev才被根据设备号找到的cdev对象赋值,此后inode才和具体的cdev对象直接联系在了一起.
Linux中一切皆文件,当我们在Linux中创建一个文件时,就会在相应的文件系统创建一个inode与之对应,文件实体和文件的inode是一一对应的,创建好一个inode会存在存储器中,第一次open就会将inode在内存中有一个备份,同一个文件被多次打开并不会产生多个inode,当所有被打开的文件都被close之后,inode在内存中的实例才会被释放。既然如此,当我们使用mknod(或其他方法)创建一个设备文件时,也会在文件系统中创建一个inode,这个inode和其他的inode一样,用来存储关于这个文件的静态信息(不变的信息),包括这个设备文件对应的设备号,文件的路径以及对应的驱动对象etc。inode作为VFS四大对象之一,在驱动开发中很少需要自己进行填充,更多的是在open()方法中进行查看并根据需要填充我们的file结构。 对于不同的文件类型,inode被填充的成员内容也会有所不同,以创建字符设备为例,我们知道,add_chrdev_region其实是把一个驱动对象和一个(一组)设备号联系到一起。而创建设备文件,其实是把设备文件和设备号联系到一起。至此,这三者就被绑定在一起了。这样,内核就有能力创建一个struct inode实例了,下面是4.8.5内核中的inode。这个inode是VFS的inode,是最具体文件系统的inode的进一步封装,也是驱动开发中关心的inode,针对具体的文件系统,还有struct ext2_inode_info 等结构。 strcut inode //include/linux/fs.h
struct inode {
/* RCU path lookup touches following: */
umode_t i_mode;
uid_t i_uid;
gid_t i_gid;
const struct inode_operations *i_op;
struct super_block *i_sb;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned int i_flags;
unsigned long i_state;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
struct hlist_node i_hash;
struct list_head i_wb_list; /* backing dev IO list */
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct list_head i_dentry;
struct rcu_head i_rcu;
};
unsigned long i_ino;
atomic_t i_count;
unsigned int i_nlink;
dev_t i_rdev;
unsigned int i_blkbits;
u64 i_version;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
blkcnt_t i_blocks;
unsigned short i_bytes;
struct rw_semaphore i_alloc_sem;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
atomic_t i_writecount;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
void *i_private; /* fs or device private pointer */
};
针对我们的问题,从中主要抽取出i_fop
const struct file_operations def_chr_fops = {
.open = chrdev_open,
.llseek = noop_llseek,
};
- 字符设备作为一种特殊设备,设备文件被创建时,会构造“相应的inode”,拿def_chr_fop来初始化inode的file_operation结构,从而inode调用默认的
open
函数接口chardev_open
chrdev_open //fs/char_dev.c
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
we dropped the lock. */
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
filp->f_op = fops_get(p->ops);
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) {
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
- chrdev_open通过设备号、strcut kobj_map *cdev_map并调用kobj_lookup查找到设备号对应chr_dev的kobject,再通过container_of,找到cdev结构。然后用cdev.fops替换filp->f_op,即填充了一个空的struct file的f_op成员。回调替换之后的filp->f_op->open,由于替换,这个其实就是cdev.fops
Linux内核会为每一个进程维护一个文件描述符表,这个表其实就是struct file[]的索引。open()的过程其实就是根据传入的路径填充好一个file结构并将其赋值到数组中并返回其索引。下面是file的主要内容
struct file //include/linux/fs.h
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
- file结构中f_op结构的填充将在设备文件第一次被打开后,调用默认的chrdev_open接口-->kobj_lookup-->cotainer_of-->cdev-->file->f_op = cdev.fops-->回调fop->open
设备驱动模型(DDM)完成任务
- 电源管理和系统关机 设备驱动模型使得电源管理系统能够以正确的顺序遍历系统硬件。例如一个USB宿主适配器,在处理完所有与其连接的设备之前是不能关闭的,这就要求系统提供一个USB宿主适配器之下所连接设备的视图。
- 与用户空间通信 sysfs虚拟文件系统和设备驱动模型密切相关,通过其,向用户空间提供内核信息。
- 热插拔设备 设备驱动模型自动捕捉插拔信号,加载驱动程序。
kobject kset subsystem使设备驱动模型组成一个层次结构。该层次结构将驱动、设备和总线联系起来。
正因为内核中设备驱动模型合理管理系统中各种各样的驱动,使得驱动工程师并不需要了解设备驱动模型细节就可进行驱动开发。
kobject相关数据结构 最初作为设备对象仅仅提供一个引用计数,随着系统功能增加,其任务也越来越多。提供基本的设备对象管理功能,内核中注册的每一个kobject对应sysfs中的一个目录。
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
- name:对应于sysfs中的目录名
- entry:连接下一个kobject结构,形成以kset设备对象中list开始的环形链表
- parent:指向其父kobject,形成层次结构,如果存在的话
- kset:指向kset集合
- ktype:指向kobj类型描述符
- sd:对应sysfs的文件目录
- kref:引用计数,由kobject_get增加引用计数和kobject_put减小引用计数,为零时释放相关设备资源
- state_initialized:kobj是否初始化
- state_in_sysfs:是否已加入sysfs
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
- release:当kobject引用计数为0时,调用过程:kref_put--->kobject_release--->release
- sysfs_ops:对属性的操作函数,读、写操作,对于sysfs中普通属性文件的读写都是通过kobject->ktype->sysfs_ops完成
- 设备对象的属性,可能不止一个属性,每个属性对应一个文本文件,放在kobject对应目录下
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *,char *);
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
};
- 属性读写函数
struct attribute {
const char *name;
mode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
- name:属性名即属性文件名
- mode:属性读写权限,即sysfs中文件读写权限,eg.S_IRUGO S_IWUGO
kset
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
- list:属于该kset的所有kobject对象被list_head结构组织成以list开头的双向循环链表
- list_lock:从list中添加、删除kobject的自旋锁
- kobj:内嵌kobject对象,同样对应sysfs中一个目录。所有属于该kset的kobjetc对象的parent指针指向这个内嵌的kobject,kset的引用计数即内嵌的kobjetc对象的引用计数
- uevent_ops:支持热拔插的函数集
struct kset_uevent_ops {
int (* const filter)(struct kset *kset, struct kobject *kobj);
const char *(* const name)(struct kset *kset, struct kobject *kobj);
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
- filter:过滤函数,决定热插拔事件发生时,内核是否向用户空间发送事件产生信号
- name:该函数在用户空间的热插拔程序需要知道子系统名字时被调用
- uevent:热插拔程序执行前项环境变量中写入值,因为任何热插拔程序需要的信息可以通过环境变量来传递(热插拔程序属于用户空间,当设备插入、拔掉时产生热插拔事件,这时候内核中会有设备的注册或者删除,同时必然也对应这sysfs中相应设备对象目录的改变。信号向用户空间发送后将会执行热插拔程序,udev设备文件系统通过sysfs,完成设备的自动注册或注销)
kobject初始化
/*
* This function will properly initialize a kobject such that it can then
* be passed to the kobject_add() call.
*
* After this function is called, the kobject MUST be cleaned up by a call
* to kobject_put(), not by a call to kfree directly to ensure that all of
* the memory is cleaned up properly.
*/
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) {
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) {
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
dump_stack();
}
kobject_init_internal(kobj); /*初始化kobject内部成员变量*/
kobj->ktype = ktype; /*weikobject绑定一个ktype属性*/
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1;
}
注册kobject到sysfs中的实例
简介 sysfs:sysfs是 Linux 2.6 内核里的一个虚拟文件系统 (/sys)。它把设备和驱动的信息从内核的设备模块导出到用户空间 (userspace)。从该文件系统中,Linux 用户可以获取很多设备的属性。 sysfs目录结构
- block:过时的接口,按照sysfs设计理念,所有的设备都应该在/sys/device,这个目录只是为了提高兼容性设计,其中文件已全部替换为了符号链接
- sysfs中保证了系统中设备都只存在一个实例,所以class/ bus/等目录下都是符号链接
Reference:https://www.ibm.com/developerworks/cn/linux/l-cn-udev/ udev文件系统在用户空间工作,它可以根据sysfs文件系统导出的信息(设备号(dev)等),动态建立和删除设备文件,而不再需要使用mknod来手动建立设备文件,也不必为查找设备号(尤其是驱动中动态申请产生的设备号)而头疼。
- udev完全在用户态工作,利用设备加入或移除时内核所发送的热插拔事件。在热插拔时,设备的详细信息会由内核输出到位于/sys的sysfs文件系统。udev的设备命名策略权限控制都在用户态完成的,它利用sysfs信息来进行创建设备文件节点。
- udev根据系统中的硬件设备的状态动态更新设备文件,进行设备文件的创建和删除等。
- 使用udev,/dev目录下就会只包含系统中真正存在的设备。所有在 sysfs 中显示的设备都可以由 udev 来创建节点。如果内核中增加了其它设备的支持,udev 也就自动地可以为它们工作了。
- 结合设备驱动模型 设备节点<--udev<--sysfs<--DDM<--kset+kobject-->构成内核DDM结构 而总线、设备、驱动属于上层,驱动工程师不用知道底层 设备对象之间层次关系如何构建??设备和驱动信息如何由内核导出到sysfs??等等问题,这些都将由内核中的设备驱动模型处理
- DT基本概念
- ARM Linux中DT代码分析
设备树的基本单元是节点node,这些节点被组织成树状结构,除了root节点外,其他节点只有唯一的父节点。 基本单元结构
[label]: node-name[@unit-address]{
[properties define]
[child node]
}
- '[]'代表可选,所以可以定义一些空节点(下面的实例中会讲到)
- ‘label’即为节点取的别名,方便引用(否则,只能使用从根节点开始的绝对路径,随着设备的增多,这显然不可取),由DEVICE TREE Compile解析翻译。
- @unit-address 设备对应地址,与具体设备挂在哪个总线下相关。节点名字格式:node-name@unit-addr。若该节点没有reg属性,节点名就不能包括“@unit-addr”
- "properties define" 属性键值对,标识设备特性,其值有多种,为空、字符串、u32或u64的数值
- "child node" 类似于父节点的结构,从而嵌套成“树”型结构
linux3.5/arch/arm/boot/dts/skeleton.dtsi
/ {
#address-cells = <1>;
#size-cells = <1>;
chosen { };
aliases { };
memory { device_type = "memory"; reg = <0 0>; };
-
先讨论这个ske;eton.dtsi 设备树源文件dts,为了提高设备树代码的利用,避免重复,在arm--samsung--chip分别定义对应的.dtsi(‘i’即“include”)文件,这样再一层层的include就行 eg. skeleton.dtsi--s3c24xx.dtsi(samsung定义的属于s3c24xx系列的芯片通用的dtsi)--s3c2416(针对s3c2416芯片使用的dtsi)
-
前两条是属性定义,'#'含义:number,这两条定义是来约束其子节点的。若当前节点包含了,定义有寻址需求(定义reg property)的子节点,那么必须要定义这两个属性。#address-cells这个属性是用来描述sub node中的reg属性的地址域特性的,也就是说需要用多少个u32的cell来描述该地址域。同理可以推断#size-cells的含义, 关于属性: Linux设备树语法中定义了一些具有规范意义的属性,包括:compatible, address, interrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有 "mac_addr","gpio","clock","power"。"regulator" 等等
-
后三条都是三个子节点
-
chosen子节点主要用来传递参数,其必须挂接在根节点之下。这样原来通过tag list传递的一些运行时参数就可以通过Device Tree来传递。例如command line可以通过bootargs这个property这个属性传递;initrd的开始地址也可以通过linux,initrd-start这个property这个属性传递。在本例中,chosen节点是空的,在实际中,建议增加一个bootargs的属性。
-
aliases子节点用来定义别名,避免使用很长的绝对路径
-
memory device node是所有设备树文件的必备节点,它定义了系统物理内存的layout。device_type属性定义了该node的设备类型,例如cpu、serial等。对于memory node,其device_type必须等于memory。reg属性定义了访问该device node的地址信息,该属性的值被解析成任意长度的(address,size)数组,具体用多长的数据来表示address和size是在其parent node中定义(#address-cells和#size-cells)。对于device node,reg描述了memory-mapped IO register的offset和length。对于memory node,定义了该memory的起始地址和长度。
linux3.5/arch/arm/boot/dts/exynos4210.dtsi
/ {
compatible = "samsung,exynos4210";
interrupt-parent = <&gic>;
gic:interrupt-controller@10490000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
cpu-offset = <0x8000>;
reg = <0x10490000 0x1000>, <0x10480000 0x100>;
};
watchdog@10060000 {
compatible = "samsung,s3c2410-wdt";
reg = <0x10060000 0x100>;
interrupts = <0 43 0>;
};
.......
gpio-controllers {
#address-cells = <1>;
#size-cells = <1>;
gpio-controller;
ranges;
gpa0: gpio-controller@11400000 {
compatible = "samsung,exynos4-gpio";
reg = <0x11400000 0x20>;
#gpio-cells = <4>;
};
gpa1: gpio-controller@11400020 {
compatible = "samsung,exynos4-gpio";
reg = <0x11400020 0x20>;
#gpio-cells = <4>;
};
.......
-
compatible 要将compatible属性就要先说一下,model属性:指明该设备属于哪一个生产厂商的哪一个model。例如:manufacturer,model”。例如model = "samsung,s3c24xx"。samsung是生产商,s3c24xx是model类型,指明了具体的是哪一个系列的SOC。compatible属性对于根节点是匹配machine type的;而对与普通的HW block是来匹配驱动的。例如,compatible = “aaa”,“bbb” 会以“aaa”来匹配合适的驱动,若匹配不到,再使用“bbb”进行驱动查找匹配
-
interrupt-parent 对于各个HW block的中断源是如何连接到对应的interrupt control的呢?通过上面代码可以看出,定义interrupt-parent属性的是root node,难道root node会产生中断到control吗?no,因为如果一个能够产生中断的子节点没有定义interrupt-parent属性的话,那么它继承父节点的。所以,为了避免root node下需要定义此属性的子节点都定义一个,所以了
-
interrupt-parent = <&gic> gic是终端控制器定义的一个label,这里通过&来对其引用
-
中断树 在设备树中中断也是“树”型结构,在interrupt root node下会挂一些device也会挂interrupt controller(术语叫interrupt nexus:中断关系节点?),在中断控制器下也会挂一些设备、中断控制器。interrupt-parent建立起了这样的中断树结构。而没有定义interrupt-parent的interrupt-controller就是root 参考 http://www.cnblogs.com/xiaojiang1025/p/6131381.html http://www.wowotech.net/device_model/dt_basic_concept.html/comment-page-2#comments