设备树语法和多任务处理

Dts语法:
    头文件包含方法:
        #include<xxx.h>
        #include"xxx.dtsi"
        #include"xx.dts"
        dtsi主要用于描述soc硬件信息;
        设备节点表示: label :node_name@unit_addr
    dts属性:
        1、compatible属性:compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,
                        "manufacturer,model"
                        manufacturer 表示厂商,
                        model 一般是模块对应的驱动名字。
        2、model属性:描述设备模块信息
        3、status 属性看名字就知道是和设备状态有关的
            okay”      表明设备是可操作的。
            “disabled” 表明设备当前是不可操作的
            “fail” 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。
            “fail-sss” 含义和“fail”相同,后面的 sss 部分是检测到的错误内容
        4、#address-cells 和#size-cells 属性
            #address-cells 属性值决定了子节点 reg 属地址信息所占用的字长(32 位),
            #size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。     
        5、reg 属性是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,    
        6、 ranges 属性ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
            child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
            parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
            length: 子地址空间的长度    
        7、aliases 节点的主要功能就是定义别名    
        8、chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。    
            
OF函数:
    通过节点名查找指定节点:struct device_node *of_finde_node_by_name(struct device_node *from,const char *name);
    通过设备类型查找节点名:struct device_node *of_find_node_by_type(struct device_node *from ,const char type);
    通多device_type和compatible查找节点: struct device_node *of_node_by_compatible_node(struct device_node *from,const char *type,const char *compatible)
    通过路径查找节点:struct device_node* of_find_node_by_path(char *path)    
    
    获取子节点的父节点:struct device_node* of_get_parent(struct device_node *node)
    迭代子节点:struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev)
    
    struct property {
         char *name; /* 属性名字 */
         int length; /* 属性长度 */
         void *value; /* 属性值 */
         struct property *next; /* 下一个属性 */
         unsigned long _flags;
         unsigned int unique_id;
         struct bin_attribute attr;
         };
    查找指定属性:property* of_find_property(struct device_node *node ,const char *name,int *lenp);
    获取属性中的元素数量 int of_property_elenms_of_size(const struct device *node,const char *property_name);
    从属性中获取指定标号的 u32 类型数据值 int of_property_read_u32_index(const struct *np,    const char *property_name,const int index,int *value);
    
    读取属性中 u8、 u16、 u32 和 u64 类型的数组数据                                        
        int of_property_read_u8(const struct device_node *np,
                                const char *propname,
                                u8 *out_value
                                size_t sz)
        int of_property_read_u16(const struct device_node *np,
                                const char *propname,
                                u16 *out_value
                                size_t sz)
        int of_property_read_u32(const struct device_node *np,
                                const char *propname,
                                u32 *out_value
                                size_t sz)
        int of_property_read_u64(const struct device_node *np,
                                const char *propname,
                                u64 *out_value
                                size_t sz)
    -----------------------------------------------------------
    读取 u8、 u16、 u32 和 u64 类型属性值,函数原型如下:
        int of_property_read_u8(const struct device_node *np,
                                const char *propname,
                                u8 *out_value)
        int of_property_read_u16(const struct device_node *np,
                                const char *propname,
                                u16 *out_value)
        int of_property_read_u32(const struct device_node *np,
                                const char *propname,
                                u32 *out_value)
        int of_property_read_u64(const struct device_node *np,
                                const char *propname,
                                u64 *out_value)    
    -------------------------------------------------------------    
    读取属性中字符串值,函数原型如下:
        int of_property_read_string(struct device_node *np,
                                    const char *propname,
                                    const char **out_string)    
    用于获取#address-cells 属性值,
            int of_n_addr_cells(struct device_node *np)
    
    of_size_cells 函数用于获取#size-cells 属性值,
        int of_n_size_cells(struct device_node *np)
        
    将从设备树读取到的地址转换为物理地址,函数原型如下:
        u64 of_translate_address(struct device_node *dev,
                                const __be32 *in_addr)
    用于直接内存映射,以前我们会通过 ioremap 函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址,不需要使用 ioremap 函数了。当然了,你也可以使用 ioremap 函数来完成物理地址到虚拟地址的内存映射,只是在采用设备树以后,大部分的驱动都使用 of_iomap 函数了。 of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是那一段, of_iomap 函数原型如下:
        void __iomem *of_iomap(struct device_node *np,
        int index)    
        
    
pigctrl子系统;主要是负责io的复用和io的电气特性{}
    <mux_reg conf_reg input_reg mux_mode input_val>     cof_val
        mux_reg 寄存器偏移地址
        conf_reg 寄存器偏移地址
        input_reg 寄存器偏移地址
        mux_reg 寄 存 器 值
        input_reg 寄存器值
        
GPIO子系统:主要是提供io的输入输出;
    申请gpio :int gpio_request(unsigned gpio,const char *gpio_lable)
    释放gpio : vid gpio_free(unsigned gpio);
    设置输入: int gpio_direction_input(unsigned gpio)
    设置输出: int gpio_direcrion_output(unsigned gpio,int default_value)
    获取gpio值:int gpio_get_value(unsigned gpio);
    设置gpio值:int gpio_set_value(unsigned gpio);
    
和gpioi相关的of函数:

    int of_gpio_named_count(struct device_node *np, const char *propname)
    函数参数和返回值含义如下:
    nd:设备节点。
    propname:要统计的 GPIO 属性。
    返回值: 正值,统计到的 GPIO 数量;负值,失败。
    
    int of_gpio_count(struct device_node *np)
    统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下
    函数参数和返回值含义如下:
    nd:设备节点。
    返回值: 正值,统计到的 GPIO 数量;负值,失败。
    
    
    int of_get_named_gpio(struct device_node *np,const char *propname,int index)
    此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,
    此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编
    号,此函数在驱动中使用很频繁!函数原型如下:
    函数参数和返回值含义如下:
    nd:设备节点。
    propname:包含要获取 GPIO 信息的属性名。
    index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO
    的编号,如果只有一个 GPIO 信息的话此参数为 0。
    返回值: 正值,获取到的 GPIO 编号;负值,失败。
    
    
原子操作:
----------------------------------------------------------------------------
自旋锁:一次只能允许一个写操作,也就是只能一个线程持有写锁,而且不能进行读操作。但是当没有写操作的时候允许一个或多个线程持有读锁,可以进行并发的读操作
    
----------------------------------------------------------------------------
读写自旋锁:顺序锁在读写锁的基础上衍生而来的,使用读写锁的时候读操作和写操作不能同时进行。使用顺序锁的话可以允许在写的时候进行读操作,也就是实现同时读写,但是不允许同时进行并发的写操作

    DEFINE_SEQLOCK(seqlock_t sl) 定义并初始化顺序锁
    void seqlock_ini seqlock_t *sl) 初始化顺序锁。
            顺序锁写操作
    void write_seqlock(seqlock_t *sl) 获取写顺序锁。
    void write_sequnlock(seqlock_t *sl) 释放写顺序锁。
    void write_seqlock_irq(seqlock_t *sl) 禁止本地中断,并且获取写顺序锁
    void write_sequnlock_irq(seqlock_t *sl) 打开本地中断,并且释放写顺序锁。
    void write_seqlock_irqsave(seqlock_t *sl,unsigned long flags)保存中断状态,禁止本地中断,并获取写顺序锁。
    void write_sequnlock_irqrestore(seqlock_t *sl,unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放写顺序锁。
    void write_seqlock_bh(seqlock_t *sl) 关闭下半部,并获取写读锁。
    void write_sequnlock_bh(seqlock_t *sl) 打开下半部,并释放写读锁。
            顺序锁读操作
    unsigned read_seqbegin(const seqlock_t *sl)读单元访问共享资源的时候调用此函数,此函数会返回顺序锁的顺序号。
    unsigned read_seqretry(const seqlock_t *sl,unsigned start)读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读
----------------------------------------------------------------------------
自旋锁使用注意事项
综合前面关于自旋锁的信息,我们需要在使用自旋锁的时候要注意一下几点:
①、因为在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要
短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处
理方式,比如稍后要讲的信号量和互斥体。
②、自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数,否则的话可能
导致死锁。原子哥在线教学: www.yuanzige.com 论坛:www.openedv.com
1147
I.MX6U 嵌入式 Linux 驱动开发指南
③、不能递归申请自旋锁,因为一旦通过递归的方式申请一个你正在持有的锁,那么你就
必须“自旋”,等待锁被释放,然而你正处于“自旋”状态,根本没法释放锁。结果就是自己
把自己锁死了!
④、在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管你用的是单核的还
是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。

----------------------------------------------------------------------------
信号量:
DEFINE_SEAMPHORE(name) 定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore *sem, int val) 初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem)获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore *sem);尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(struct semaphore *sem)获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem) 释放信号量
----------------------------------------------------------------------------
互斥体:
DEFINE_MUTEX(name) 定义并初始化一个 mutex 变量。
void mutex_init(mutex *lock) 初始化 mutex。
void mutex_lock(struct mutex *lock)获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠。
void mutex_unlock(struct mutex *lock) 释放 mutex,也就给 mutex 解锁。
int mutex_trylock(struct mutex *lock)尝试获取 mutex,如果成功就返回 1,如果失败就返回 0。
int mutex_is_locked(struct mutex *lock)判断 mutex 是否被获取,如果是的话就返回1,否则返回 0。
int mutex_lock_interruptible(struct mutex *lock)使用此函数获取信号量失败进入休眠以后可以被信号打断。
----------------------------------------------------------------------------

Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数
----------------------------------------------------------------------------
内核定时器:
    struct timer_list {
    struct list_head entry;
    unsigned long expires; /* 定时器超时时间,单位是节拍数 */
    struct tvec_base *base;
    void (*function)(unsigned long); /* 定时处理函数 */
    unsigned long data; /* 要传递给 function 函数的参数 */
    int slack;
    };
    
    void init_timer(struct timer_list *timer)
    void add_timer(struct timer_list *timer)//添加之后就开始运行
    int del_timer(struct timer_list * timer)
    int del_timer_sync(struct timer_list *timer)//del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除
    int mod_timer(struct timer_list *timer, unsigned long expires)//修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!
内核延时函数:
    void ndelay(unsigned long nsecs)
    void udelay(unsigned long usecs)  //纳秒、微秒和毫秒延时函数。
    void mdelay(unsigned long mseces)

内核中断:
    int request_irq(unsigned int irq,
                    irq_handler_t handler,
                    unsigned long flags,
                    const char *name,
                    void *dev)
    函数参数和返回值含义如下:
    irq:要申请中断的中断号。
    handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
    flags:中断标志,
        IRQF_SHARED多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话, request_irq 函数的 dev 参数就是唯一区分他们的标志。
        IRQF_ONESHOT 单次中断,中断执行一次就接触。
        IRQF_TRIGGER_NONE 无触发。
        IRQF_TRIGGER_RISING 上升沿触发。
        IRQF_TRIGGER_FALLING 下降沿触发。
        IRQF_TRIGGER_HIGH 高电平触发。
        IRQF_TRIGGER_LOW 低电平触发。
    name:中断名字,设置要以后可以在/proc/interrupts 文件中看到对应的中断名字。
    dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
    返回值: 0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经
    被申请了。
    
    void free_irq(unsigned int irq,void *dev)
    irqreturn_t (*irq_handler_t) (int, void *)
    
    enable_irq 和 disable_irq 用于使能和禁止指定的中断, irq 就是要禁止的中断号
        void enable_irq(unsigned int irq)
        void disable_irq(unsigned int irq)
    
    关闭全局中断,这个时候可以使用如下两个函数:
        local_irq_enable()
        local_irq_disable()
        local_irq_save(flags)//local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
        local_irq_restore(flags)//local_irq_restore 用于恢复中断,将中断到 flags 状态。
        
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可
以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部
去执行,这样中断处理函数就会快进快出。

tasklet: 是利用软中断来实现的另外一种下半部机制
    初始化:DECLARE_TASKLET(name, func, data) <=> void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
    void tasklet_schedule(struct tasklet_struct *t)//一般在中断处理函数内部调用
    
工作队列:
    DECLARE_WORK(n, f)//初始化工作
    bool schedule_work(struct work_struct *work)//一般在中断处理函数内部调用
    
中断有关的设备树属性信息:
①、 #interrupt-cells,指定中断源的信息 cells 个数。
②、 interrupt-controller,表示当前节点为中断控制器。
③、 interrupts,指定中断号,触发方式等。
④、 interrupt-parent,指定父中断,也就是中断控制器。
----------------------------------------------------------------------------

发布了136 篇原创文章 · 获赞 22 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/u010261063/article/details/104518794