0%

芯片地址

通过ADDR[4:0]配置芯片的设备地址:
| ADDR[4] | ADDR[3] | ADDR[2] | ADDR[1] | ADDR[0] | 地址模式 |
| :-: | :-: | :-: | :-: | :-: | :-: |
| 1 | 1 | 1 | 1 | 1 | 单芯片模式,地址为0x00(ADDR[4:0]n值取反),可以直接访问芯片内部地址 |
| x | x | x | x | x | (ADDR[4:0]n != 0x1f), 多芯片模式,地址为(ADDR[4:0]n值取反),通过SMI寄存器访问芯片内部地址 |

单芯片模式下0x1B,0x1C,0x1D为三个global地址,0x10 - 0x16 为7个port设备地址,port设备通过SMI PHY Command and Data registers (Global 2, offsets 0x18 & 0x19)访问。
多芯片模式下地址由ADDR[4:0]n值取反决定,所有寄存器都通过SMI PHY Command and Data registers(offsets 0x00 & 0x01)访问。

kernel_timer

jiffies

1
2
3
4
5
6
7
8
9
10
11
unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */
/*************************************
具体的代码
************************************/
/* 判断有没有超时 */
if(time_before(jiffies, timeout)) {
/* 超时发生 */
} else {
/* 超时未发生 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int jiffies_to_msecs(const unsigned long j) 将 jiffies 类型的参数 j 分别转换为对应的毫秒、微秒、纳秒。
int jiffies_to_usecs(const unsigned long j)
u64 jiffies_to_nsecs(const unsigned long j)
long msecs_to_jiffies(const unsigned int m) 将毫秒、微秒、纳秒转换为 jiffies 类型。
long usecs_to_jiffies(const unsigned int u)
unsigned long nsecs_to_jiffies(u64 n)
```

## 内核定时器
```c
#include <linux/timer.h>
struct timer_list timer; /* 定义定时器 */
/* 定时器回调函数 */
void function(unsigned long arg)
{
/*
* 定时器处理代码
*/
/* 如果需要定时器周期性运行的话就使用 mod_timer
* 函数重新设置超时值并且启动定时器。
*/
mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2));
}

/* 初始化函数 */
void init(void)
{
init_timer(&timer); /* 初始化定时器 */

timer.function = function; /* 设置定时处理函数 */
timer.expires=jffies + msecs_to_jiffies(2);/* 超时时间 2 秒 */
timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */

add_timer(&timer); /* 启动定时器 */
}

/* 退出函数 */
void exit(void)
{
del_timer(&timer); /* 删除定时器 */
/* 或者使用 */
del_timer_sync(&timer);
}
```

## 内核短延时函数
```c
void ndelay(unsigned long nsecs) 纳秒、微秒和毫秒延时函数。
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)

concurrency&competition

原子操作

32位atomic_t 64位atomic64_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ATOMIC_INIT(int i)  定义原子变量的时候对其初始化。  
int atomic_read(atomic_t *v) 读取 v 的值,并且返回。
void atomic_set(atomic_t *v, int i) 向 v 写入 i 值。
void atomic_add(int i, atomic_t *v) 给 v 加上 i 值。
void atomic_sub(int i, atomic_t *v) 从 v 减去 i 值。
void atomic_inc(atomic_t *v) 给 v 加 1,也就是自增。
void atomic_dec(atomic_t *v) 从 v 减 1,也就是自减
int atomic_dec_return(atomic_t *v) 从 v 减 1,并且返回 v 的值。
int atomic_inc_return(atomic_t *v) 给 v 加 1,并且返回 v 的值。
int atomic_sub_and_test(int i, atomic_t *v) 从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v) 从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v) 给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v) 给 v 加 i,如果结果为负就返回真,否则返回假

void set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1。
void clear_bit(int nr,void *p) 将 p 地址的第 nr 位清零。
void change_bit(int nr, void *p) 将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p) 获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr, void *p) 将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr, void *p) 将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。

自旋锁

自旋锁适用于短时期的轻量级加锁
自旋锁API函数适用于SMP或支持抢占的单CPU下线程之间的并发访问,也就是用于线程与线程之间,被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数,否则的话会可能会导致死锁现象的发生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DEFINE_SPINLOCK(spinlock_t lock)  定义并初始化一个自选变量。  
int spin_lock_init(spinlock_t *lock) 初始化自旋锁。
void spin_lock(spinlock_t *lock) 获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t *lock) 释放指定的自旋锁。
int spin_trylock(spinlock_t *lock) 尝试获取指定的自旋锁,如果没有获取到就返回0。
int spin_is_locked(spinlock_t *lock) 检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0。

void spin_lock_irq(spinlock_t *lock) 禁止本地中断,并获取自旋锁。
void spin_unlock_irq(spinlock_t *lock) 激活本地中断,并释放自旋锁。
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags) 保存中断状态,禁止本地中断,并获取自旋锁。
void spin_unlock_irqrestore(spinlock_t*lock, unsigned long flags)将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。

void spin_lock_bh(spinlock_t *lock) 关闭下半部,并获取自旋锁。
void spin_unlock_bh(spinlock_t *lock) 打开下半部,并释放自旋锁。

一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock

DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */

/* 线程 A */
void functionA (){
    unsigned long flags; /* 中断状态 */
    spin_lock_irqsave(&lock, flags) /* 获取锁 */
    /* 临界区 */
    spin_unlock_irqrestore(&lock, flags)  /* 释放锁 */
}

/* 中断服务函数 */
void irq() {
    spin_lock(&lock) /* 获取锁 */
    /* 临界区 */
    spin_unlock(&lock) /* 释放锁 */
}

读写自旋锁

读写自旋锁为读和写操作提供了不同的锁,一次只能允许一个写操作,也就是只能一个线
程持有写锁,而且不能进行读操作。但是当没有写操作的时候允许一个或多个线程持有读锁,
可以进行并发的读操作。

DEFINE_RWLOCK(rwlock_t lock)  定义并初始化读写锁  
void rwlock_init(rwlock_t *lock) 初始化读写锁。  
void read_lock(rwlock_t *lock)  获取读锁。  
void read_unlock(rwlock_t *lock)  释放读锁。  
void read_lock_irq(rwlock_t *lock)  禁止本地中断,并且获取读锁。  
void read_unlock_irq(rwlock_t *lock)  打开本地中断,并且释放读锁。  
void read_lock_irqsave(rwlock_t *lock,unsigned long flags) 保存中断状态,禁止本地中断,并获取读锁。  
void read_unlock_irqrestore(rwlock_t *lock,unsigned long flags) 将中断状态恢复到以前的状态,并且激活本地
中断,释放读锁。  
void read_lock_bh(rwlock_t *lock)  关闭下半部,并获取读锁。  
void read_unlock_bh(rwlock_t *lock)  打开下半部,并释放读锁。  
void write_lock(rwlock_t *lock)  获取读锁。  
void write_unlock(rwlock_t *lock)  释放读锁。  
void write_lock_irq(rwlock_t *lock)  禁止本地中断,并且获取读锁。  
void write_unlock_irq(rwlock_t *lock)  打开本地中断,并且释放读锁。  
void write_lock_irqsave(rwlock_t *lock,unsigned long flags) 保存中断状态,禁止本地中断,并获取读锁。  
void write_unlock_irqrestore(rwlock_t *lock,unsigned long flags) 将中断状态恢复到以前的状态,并且激活本地
中断,释放读锁。  
void write_lock_bh(rwlock_t *lock)  关闭下半部,并获取读锁。  
void write_unlock_bh(rwlock_t *lock)  打开下半部,并释放读锁。  

顺序锁

使用顺序锁的话可以允许在写的时候进行读操作,也就是实现同时读写,但是不允许同时进行并发的写操作。

DEFINE_SEQLOCK(seqlock_t sl)  定义并初始化顺序锁  
void seqlock_init(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)读结束以后调用此函数检查在读的过程中有没有对资源进行写操作,如果有的话就要重读。  

信号量

  1. 因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
  2. 因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
  3. 如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
    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)  释放信号量  
    #include <linux/semaphore.h>  
    struct semaphore sem; /* 定义信号量 */
    sema_init(&sem, 1); /* 初始化信号量 */
    down(&sem); /* 申请信号量 */
    /* 临界区 */
    up(&sem); /* 释放信号量 */

互斥体

  1. mutex 可以导致休眠,因此不能在中断中使用 mutex,中断中只能使用自旋锁。
  2. 和信号量一样,mutex 保护的临界区可以调用引起阻塞的 API 函数。
  3. 因为一次只有一个线程可以持有 mutex,因此,必须由 mutex 的持有者释放 mutex。并且 mutex 不能递归上锁和解锁。
    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)使用此函数获取信号量失败进入休眠以后可以被信号打断。  
struct mutex lock; /* 定义一个互斥体 */
mutex_init(&lock); /* 初始化互斥体 */
mutex_lock(&lock); /* 上锁 */
/* 临界区 */
mutex_unlock(&lock); /* 解锁 */

device_tree

属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#address-cells /*决定了子节点 reg 属性中地址信息所占用的字长*/  
#size-cells /*属性值决定了子节点 reg 属性中长度信息所占的字长*/
reg = <address1 length1 address2 length2 address3 length3……>
/*reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息*/

ranges = <0x0 0xe0000000 0x00100000>; /*子地址、父地址和地址空间长度*/
/*此属性值指定了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。*/
ranges; /*子空间和父空间地址范围相同*/

aliases {can0 = &flexcan1;}; /*定义别名*/
chosen {stdout-path = &uart1;}; /*uboot 向 Linux 内核传递数据*/
```

## OF函数
### 查找节点
```c
//直接获取当前设备根节点
const struct device_node *np = priv->dev->dev.of_node;
//通过节点名字查找指定的节点
struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
//通过 device_type 属性查找指定的节点
struct device_node *of_find_node_by_type(struct device_node *from,
const char *type);
//根据 device_type 和 compatible 这两个属性查找指定的节点
struct device_node *of_find_compatible_node(struct device_node *from,const char *type,
const char compatible);
//通过 of_device_id 匹配表来查找指定的节点
struct device_node *of_find_matching_node_and_match(struct device_node *from,
const struct of_device_id *matches,const struct of_device_id **match);
//通过路径来查找指定的节点
struct device_node *of_find_node_by_path(const char *path);

查找父/子节点

1
2
3
4
5
//获取指定节点的父节点
struct device_node *of_get_parent(const struct device_node *node);
//迭代的查找子节点
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);

提取属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//查找指定的属性
property *of_find_property(const struct device_node *np, const char *name, int *lenp);
//获取属性中元素的数量
int of_property_count_elems_of_size(const struct device_node *np,const char *propname,int elem_size);
//从属性中获取指定标号的 u32 类型数据值,如某个属性有多个 u32 类型的值,那么就可以使用此函数来获取指定标号的数据值
int of_property_read_u32_index(const struct device_node *np,const char *propname,
u32 index,u32 *out_value);
//连续读取属性中 u8、u16、u32 和 u64
int of_property_read_u8_array(const struct device_node *np,const char *propname,u8 *out_values,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_string(struct device_node *np,const char *propname,const char **out_string);
//获取#address-cells 属性值
int of_n_addr_cells(struct device_node *np);
//获取#size-cells 属性值
int of_n_size_cells(struct device_node *np);

其他常用

1
2
3
4
5
6
7
8
9
10
//于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性
int of_device_is_compatible(const struct device_node *device,const char *compat);
//用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值
const __be32 *of_get_address(struct device_node *dev,int index,u64 *size,unsigned int *flags);
//将从设备树读取到的地址转换为物理地址
u64 of_translate_address(struct device_node *dev,const __be32 *in_addr);
//将reg 属性值转换为 resource 结构体类型
int of_address_to_resource(struct device_node *dev,int index,struct resource *r);
//获取内存地址所对应的虚拟地址
void __iomem *of_iomap(struct device_node *np,int index);

gpio相关

1
2
3
4
5
6
//获取设备树某个属性里面定义了几个 GPIO 信息,要注意的是空的 GPIO 信息也会被统计到
int of_gpio_named_count(struct device_node *np, const char *propname);
//同上 此函数统计的是“gpios”这个属性的 GPIO 数量
int of_gpio_count(struct device_node *np);
//函数获取 GPIO 编号
int of_get_named_gpio(struct device_node *np,const char *propname,int index);

printk

消息级别

include/linux/kern_levels.h

#define KERN_SOH  "\001" 
#define KERN_EMERG KERN_SOH "0"  /* 紧急事件,一般是内核崩溃 */
#define KERN_ALERT KERN_SOH "1"  /* 必须立即采取行动 */
#define KERN_CRIT  KERN_SOH "2"  /* 临界条件,比如严重的软件或硬件错误*/
#define KERN_ERR  KERN_SOH "3"  /* 错误状态,一般设备驱动程序中使用
KERN_ERR 报告硬件错误 */
#define KERN_WARNING KERN_SOH "4"  /* 警告信息,不会对系统造成严重影响 */
#define KERN_NOTICE  KERN_SOH "5"  /* 有必要进行提示的一些信息 */
#define KERN_INFO  KERN_SOH "6"  /* 提示性的信息 */
#define KERN_DEBUG KERN_SOH "7"  /* 调试信息 */
 include/linux/printk.h
 #define CONSOLE_LOGLEVEL_DEFAULT 7 /*优先级高于 7 的消息才能显示在控制台上*/

Kconfig&Makefile

功能

Kconfig Makefile
决定menuconfig中的选项并最终影响Makefile的行为 根据menuconfig的配置决定实际编译的obj文件

语法

Kconfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
menu "TEST driver"      #menuconfig中增加子菜单 TEST driver
comment "TEST driver" #可选 在子菜单选项中增加一行注释

config  TEST #决定Makefile中的宏定义值,这里为 CONFIG_TEST

    tristate  " TEST support " #menuconfig中的选项名称, tristate可选为m

default    y #该选项的默认值 n(不选择) y(选择) m(编译为ko)

    depends    on FOO #FOO被选择后TEST才可以被选择

    select     BAR #如果TEST被选择则同时选中BAR

    help  
XXX. # 对选项的详细描述

endmenu #匹配 menu

Makefile

1
2
3
4
5
6
7
obj-$(CONFIG_TEST) += test.o    #若TEST support被选择为y(m),则将test.c编译为test.o(test.ko)

obj-$(CONFIG_DIR)  += dir/ #若DIR相关项被选择为y,则编译时进入子目录dir寻找子目录Makefile

obj-$(CONFIG_FOO)  += foo.o #若FOO相关项被选择为y(m),则将foo1.c foo2.c 编译为 foo.o(foo.ko)
foo-objs := foo1.o \
foo2.o \

在子目录中增加驱动

假设目录结构如下:

  • drivers/
    • dir1/
      • Kconfig(1)
      • Makefile(1)
      • dir2/
        • Kconfig(2)
        • Makefile(2)

在 drivers/dir1 下:
修改Kconfig(1)

1
2
3
4
source "drivers/dir1/dir2/Kconfig"    #解析dir1下Kconfig时引入dir2下的Kconfig
```

修改 Makefile(1) 下:

obj-$(CONFIG_TEST) += dir2/

1
2
3

在 drivers/dir1/dir2 下:
修改Kconfig(2)

menu “TEST driver”
comment “TEST driver”

config TEST
tristate “test support”

endmenu

1
2

修改Makefile(2)

obj-$(CONFIG_TEST) += test.o

ioctrl

CMD定义

NRBITS TYPEBITS SIZEBITS DIRBITS
8 8 13 3
0~7 8~15 16~28 29~31
ioctrl.h
/*
* The original linux ioctl numbering scheme was just a general
* "anything goes" setup, where more or less random numbers were
* assigned.  Sorry, I was clueless when I started out on this.
*
* On the alpha, we'll try to clean it up a bit, using a more sane
* ioctl numbering, and also trying to be compatible with OSF/1 in
* the process. I'd like to clean it up for the i386 as well, but
* it's so painful recognizing both the new and the old numbers..
*/

#define _IOC_NRBITS    8
#define _IOC_TYPEBITS    8
#define _IOC_SIZEBITS    13
#define _IOC_DIRBITS    3

#define _IOC_NRMASK    ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK    ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK    ((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK    ((1 << _IOC_DIRBITS)-1)

#define _IOC_NRSHIFT    0
#define _IOC_TYPESHIFT    (_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT    (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT    (_IOC_SIZESHIFT+_IOC_SIZEBITS)

/*
* Direction bits _IOC_NONE could be 0, but OSF/1 gives it a bit.
* And this turns out useful to catch old ioctl numbers in header
* files for us.
*/
#define _IOC_NONE    1U
#define _IOC_READ    2U
#define _IOC_WRITE    4U

#define _IOC(dir,type,nr,size)            \
    ((unsigned int)                \
    (((dir)  << _IOC_DIRSHIFT) |        \
    ((type) << _IOC_TYPESHIFT) |        \
    ((nr)   << _IOC_NRSHIFT) |        \
    ((size) << _IOC_SIZESHIFT)))

/* used to create numbers */
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)    _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size)    _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

/* used to decode them.. */
#define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)        (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)        (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)        (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)