笔记

day_1

主题: 1。嵌入式概况
2。linux内核概况
3。linux内核的编译
4。linux下的模块

1。2

3。内核的编译
(1)配置
$>make menuconfig
通过图形化的配置界面,决定如何处理内核的各个功能部分。配置完成后,所有的配置选项包存在.config文件中。

(2)编译内核和模块
$>make
根据.config的内容,决定应该编译哪些asm/c文件。
通过不同的配置选项,可以决定代码是不编译,还是编译到zImage中,还是编译为.ko

(3)安装模块
$>make modules_install
将编译出来的.ko安装到/lib/modules/目录中

(4)安装内核
$>make install
将zImage安装到/boot目录下,并且修改/boot/grub/grub.conf文件

安装成功后,可以重新reset机器,在引导内核时,选择新安装的内核。如果有问题,可以重新make menuconfig。


4。模块
模块实际上是一个容器的概念,一个模块中可以包含驱动,也可以包含新的文件系统,协议栈等。

如果模块对应的c代码在内核源代码树,那么生成的模块称为内部模块;
如果模块对应的c代码在其他位置,那么模块称为外部模块。

今天先讲外部模块。

参考x86-drv/01mod/mod_test01.c和Makefile

day_2

主题: 1。模块的其他内容说明
2。模块的符号导出
3。模块的参数
4。从内核中导出信息:printk
5。用户态了解内核信息:/proc下的文件
6。list_head的使用

作业:
1。掌握module_param_array宏的使用,自己试着写个例子


1.模块的其他内容
(1)模块相关工具
$>insmod xxx.ko
手工加载一个模块

$>rmmod xxx
手工卸载一个模块

$>lsmod
查看内核已经加载的模块

$>modinfo
参看某个模块的信息

$>modprobe xxx
自动加载模块。只能加载已经安装到/lib/modules下的模块。
还可以用modprobe卸载模块:
$>modprobe -r xxx
如果xxx.ko依赖的模块计数为0,则一起卸载。


(2)查看printk的信息
$>dmesg
$>dmesg -c /* 查看信息后,将信息清空 */


2. 模块的符号导出
为了避免命名空间的污染,内核规定ko模块中的符号,无论加不加static,都是局部的。
如果要将符号明确导出为全局的,则需要用宏:
EXPORT_SYMBOL(xxx);
例如:
int abc(...)
{
...
}
EXPORT_SYMBOL(abc);

注意!如果c文件不是编译为模块,而是编译到zImage中,那么符号的权限仍然遵循普通c的要求,加上static的为局部,不加为全局。

还有一个类似的宏:
EXPORT_SYMBOL_GPL(xxx);
利用该宏导出的符号,只能被具有GPL许可证的模块使用。


3. 模块的参数
参考01mod/mod_test03.c


4. printk
printk在内核的任意地方都可以使用,将字符串添加优先级后,写入内核的一个缓冲区(该缓冲区称为log
buffer,默认128KB)

可以用dmesg观察;或者回到纯文本控制台,可以自动显示级别较高的printk信息


5. proc文件

day_3

主题: 1。完成list_head的例子
2。VFS的核心结构体
3。char/block/网络设备的特征
4。char驱动的设计
5。基于内存缓冲区的char驱动例子

作业:完成多缓冲区的char设备驱动

1.参考02proc/proc_test03.c


2. VFS的核心结构体
定义在<linux/fs.h>

(1)super_block
结构体的名字来源于ext2文件系统。VFS用super_block结构体来记录已挂载磁盘的分区信息。
也就是说,当用户态mount一个磁盘分区时,VFS会在内核分配并初始化一个super_block结构体,结构体中记录分区的信息。super_block将一直存在到用户执行umount

如果用户态mount的是procfs等非磁盘文件系统,则VFS也会分配一个super_block,但其中的分区信息由文件系统负责填充(一般没意义)

所以,super_block是和用户态的mount动作一一对应。


2.inode
inode是VFS创建的结构体,用于记录文件信息,比如文件的大小,属主,atime/ctime/mtime。

当打开一个文件时,VFS会创建inode,inode的成员用从磁盘上读取的文件信息来赋值。

所以,inode应该和实际打开的文件对应。如果有两个人同时打开一个文件,那么VFS只会维护一个inode。

inode中有两个和char设备文件相关的成员:
i_rdev: 记录设备的设备号
i_cdev: 指向一个cdev结构体,该结构体记录char设备的信息


3. file
和用户态的open操作一一对应。file中记录文件打开已经使用过程中的一些标志信息。
如果对同一文件open多次,那么VFS会创建多个file。


4. file_operations
VFS要求char驱动实现若干设备的访问函数,然后将这些函数的指针集中到file_operations中。

通常char驱动应该实现的操作有:
open
release
read
write
unlocked_ioctl
(课堂讲)

可能需要实现的操作有:
lseek
poll
mmap
fasync
(参考LDD3书)


3. 三种设备类型的特征


4. char驱动的设计
(1)了解硬件
可能一个驱动会支持多个类似的硬件

(2)设计和硬件对应的私有结构体
私有结构体完全由驱动人员自行设计,原则是将使用设备时涉及的各类信息都集中到私有结构体中。比如设备号,设备寄存器的地址,中断号,锁,等待队列,计数等。

内核不会规定私有结构体如何设计。如果一个驱动要支持多个设备,那么一般要求定义私有结构体。

(3)初始化私有结构体以及硬件

(4)为设备分配设备号
原则是设备号必须唯一。如果一个驱动支持多个设备,那么应该为这些设备采用相同的主设备号,次设备号任意。

(5)准备file_operations
根据设备需求,提供xxx_open, xxx_release等操作函数

(6)将设备号和file_operations集中到cdev结构体中,注册到VFS

(7)如果用户态要访问硬件,还有通过mknod在/dev/下创建设备节点


5. 基于缓冲区的char驱动例子
参考03char/char_test01.c

day_4

主题: 1。完成支持多设备的char驱动
2。构建在开发板上运行的linux内核
3。测试开发板上的内核和文件系统是否可运行
4。将写好的驱动放置到内核代码树中,编译进zImage运行

作业:1. 使用MEM_RESIZE命令
2. 仔细看看make menuconfig的各个选项,尤其是和6410相关的。另外,再看看内核中6410实际的驱动文件都是哪些。
(不一定今天完成,但最好抽时间做一下)


1. 多设备驱动
参考03char/char_test03.c


2.开发板上内核编译
(1)使用默认的开发板配置文件
$>cd /home/zhang/kernel/linux-2.6.28_smdk6410
$>cp smdk6410_config .config
注意,各类型开发板默认配置文件的路径一般应该在arch/arm/configs/目录下。从其中挑一个和当前开发板最接近的就可以。

(2)利用menuconfig对选项进行调整
$>make menuconfig
必须确保选项和开发板的情况一致。
修改部分:
a.取消模块的支持,这样,所有代码全部编译进zImage
b.进入Device Drivers-->Graphics support-->support framebuffer。。。进去将LCD屏从800*480修改为480*272

(3)编译
$>make
make之前,先修改Makefile第194行,将CROSS_COMPILE修改为:
CROSS_COMPILE := arm-linux-


3. 测试


4。将自己的驱动编译到zimage中
(1)创建目录.../drivers/arm-drv/
$>mkdir drivers/arm-drv/
以后课程中自己写的在开发板上运行的驱动,统一放在该目录下

(2)将自己写的缓冲区的驱动拷贝到arm-drv/目录下
$>cp .../03char/char_test0*.c .../drivers/arm-drv/

(3)在arm-drv/中创建Kconfig
参考drivers/arm-drv/Kconfig

(4)修改上层Kconfig
修改arch/arm/Kconfig,在1224行加入:
source "drivers/arm-drv/Kconfig"

(5)利用图形化的配置界面选择将新的驱动编译进内核
$>make menuconfig
选择完后,在.config文件中应该见到新的CONFIG_UP6410_DRV等选项。

(6)在arm-drv/中创建Makefile
参考arm-drv/Makefile

(7)修改上层的Makefile
修改drivers/Makefile,在第75行加入:
obj-$(CONFIG_UP6410_DRV) += arm-drv/

(8)重新make内核
$>make
生成的zImage在arch/arm/boot目录下


4. 新内核的测试
(1)将zImage拷贝到tftp共享目录
$>cp arch/arm/boot/zImage /tftpboot

(2)准备好文件系统,用于NFS登录
$>tar xzvf /home/zhang/rootfs/nfsboot.tar.gz -C /
然后修改/etc/exports,增加/nfsboot为NFS共享目录

(3)启动开发板
$>minicom -s

$>tftp 50008000 zImage
$>bootm
$>setenv bootargs noinitrd root=/dev/nfs nfsroot=192.168.1.254:/nfsboot/qt_root ip=192.168.1.20 init=/linuxrc console=ttySAC0,115200 mem=128M

如果登录成功,可以运行一下/usr/local/multimedia_test应用程序
还可以验证char_test01.c/char_test02.c是否可运行
自己mknod..还可以查看/proc/mem_test

day_5

主题: 1。驱动如何访问寄存器以及6410的物理地址空间
2。linux下如何访问有物理地址的寄存器
3。为led灯设计char驱动

1。如何访问寄存器

2。物理寄存器的访问
(1)将手册中找到的物理地址转换为虚拟地址
例子:
#include <linux/ioport.h>
#define PHY_BASE 0x7E005000 /* RTC */
#define PHY_SIZE 0x7E005090

static void __iomem *vir_base;

/* 将物理地址映射到虚拟地址
* 如果返回NULL,则映射失败 */
vir_base = ioremap(PHY_BASE, /* 物理基地址 */
PHY_SIZE); /* 物理地址范围 */
if (!vir_base)
return -EIO;


(2)寄存器的访问
寄存器的访问应该使用虚拟基地址+偏移的模式

例子:
#include <linux/io.h>

8位寄存器
char value;
value = readb(vir_base + offset);
writeb(value, (vir_base+offset));

16位寄存器
short value;
value = readw(vir_base+offset);
writew(value, (vir_base+offset));

32位寄存器
int value;
value = readl(vir_base+offset);
writel(value, (vir_base+offset));

64位寄存器
long long value;
value = readq(vir_base+offset);
writeq(value, (vir_base+offset));


(3)释放虚拟地址的映射
如果驱动退出,则必须将映射到的虚拟地址释放
例子:
#include <linux/ioport.h>
iounmap(vir_base);

3。char驱动
要求:
1。开发板上有4个LED灯,完成标准的char驱动,在用户态创建

#include <linux/xxx.h>
#include <asm/xxx.h>


对于6410开发板来说,arch/arm下的重要目录是:
1. arch/arm/mach-s3c6410/
其中的mach-smdk6410.c为开发板的初始化文件

2. arch/arm/plat-s3c/
三星各类型处理器公用的一些代码

3. arch/arm/plat-s3c64xx/
三星64系列处理器得到公用代码

根据目录的分布,三星为中断设置中断号所准备的头文件,应该在plat-s3c64xx目录中。具体为:
plat-s3c64xx/include/plat/irqs.h

6410的中断号的分布:
0~15: 保留给老式的ISA设备
16~31: 分配给UART0到UART3
32~95: 为VIC支持的每个中断源各分配一个中断号
96~100: 为5个定时器准备的中断号。对于6410非常重要的是timer4对应的100号中断
101~128: 外部中断组0的中断号(group0中共28个中断)
比如开发板上的按键以及DM9000连接的中断都是属于group0的
129~227: 外部中断组1到9的中断号(共99个)

NR_IRQS: 228

 day_6

主题: 1。讲评LED的例子
2。中断处理架构
3。中断处理函数
4。内核中的时间
5。基于开发板的按键中断控制LED的例子

1.led的例子
参考arm-drv/led_test02.c


2. 中断架构
核心结构体:
(1)irq_desc
定义在<linux/irq.h>
和中断号一一对应。记录中断的状态,以及对应的中断处理函数。
内核在开机时负责分配一个irq_desc结构体的数组,数组共有NR_IRQS个成员。

(2)irqaction
定义在<linux/interrupt.h>
是中断处理函数的封装结构体。每个irqaction对应一个中断处理函数。
在中断处理函数的注册函数中自动分配。
由于中断支持共享,所以一个中断号可能有多个处理函数。
hander: 中断处理函数
flags: 中断的标志


(3)irq_chip
定义在<linux/irq.h>
irq_chip中包含enable/disable/ack/mask等函数,用于为给定的中断号提供关闭,掩码等服务。对于6410来说,这些服务需要访问VIC或GPIO中对应的寄存器。

理论上来说,一个irq_chip就可以为所有中断号提供服务。但为了加快中断的处理,通常,会为一组服务方式类似的中断号提供对应的irq_chip

三星在移植6410时,构建的irq_chip以及对应的函数在arch/arm/plat-s3c64xx/目录,如irq.c和irq-eint.c


3。中断处理函数
类型在<linux/interrupt.h>中定义:
typedef irqreturn_t (*irq_handler_t)(int, void *);
中断处理函数的返回值只有IRQ_NONE和IRQ_HANDLED两个。如果处理了中断则返回IRQ_HANDLED,否则返回IRQ_NONE.

中断处理函数调用时遵循原则:
a. 可嵌套不可重入
b. 不可睡眠(不能调用任何可能引起睡眠的函数)
kmalloc(size, GFP_KERNEL);


(1)中断的注册
例子:
#include <linux/interrupt.h>

/* 中断处理函数 */
irqreturn_t my_handler(int irq, void *dev_id)
{
...
}

/* 中断的注册 */
int ret;
unsigned long flags = IRQF_SHARED |
IRQF_SAMPLE_RANDOM |
IRQF_TRIGGER_FALLING;

ret = request_irq(irq, /* 中断号 */
my_handler, /* 中断处理函数 */
flags, /* 中断的标志 */
"abc", /* 中断处理函数的名字,出现在/proc/interrupts文件中 */
dev_id); /* 非0的参数,会传递给中断处理函数,一般应该把驱动的私有结构体传进来 */

(2)中断的注销
#include <linux/interrupt.h>
free_irq(irq, dev_id);


4. 按键的例子
自己回去写。。。


5. 时间
tick:每个定时器中断间的间隔
HZ:每秒钟硬件定时器中断的发生次数
1 tick = 1/HZ秒
内核很多定时和延迟的操作,都是基于tick的

linux内核的运行队列

day_7

主题: 1。讲评按键中断的例子
2。linux时间的其他内容
3。内核的延迟
4。内核中的定时

作业:
1。看wait_event宏的实现
2. 利用timer_list实现按键驱动例子的去抖


1. 按键中断例子
参考drivers/arm-drv/key_test02.c

2. 时间的其他内容
tick/HZ
内核有一个全局变量jiffies,记录开机以后经过的tick的数量。
用jiffies/HZ,可以得到开机后到现在经过的秒数。这个时间称为相对时间。

jiffies的类型为unsigned long.如果HZ为1000,那么肯能49。7天会溢出一次。
为了避免这一情况,内核还提供了64位的jiffies:
jiffies_64

绝对时间:从1970年1月1日0时0分0秒到现在经过的时间。
记录绝对时间要使用timeval或timespec结构体
内核获得绝对时间的方式:
例子:
#include <linux/time.h>
struct timeval tval;
struct timespec tspec;

/* 从内核获取绝对时间 */
do_gettimeofday(&tval);
getnstimeofday(&tspec);

参考x86-drv/04time/time_test.c


3. 内核的延迟
延迟的原因是当前程序由于某些条件不满足,无法继续执行。此时需要延迟。

(1)等待给定时间的延迟
如果进程的条件只要等待足够的时间就可以满足,那么这种延迟可以称为等待给定时间的延迟。

a.短延迟
例子:
#include <linux/delay.h>
ndelay(xxx); /* xxx为延迟的ns数 */
udelay(xxx);
mdelay(xxx);
基于us或ms的延迟,常常用于连续操作寄存器间的间隔。上述几个延迟函数,都是基于忙循环延迟。

b.长延迟
如果延迟时间比较长,比如秒级延迟,应该基于tick进行延迟。而且应该睡眠。
例子:
#include <linux/sched.h>

/* 睡眠3秒钟 */
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(3*HZ);


(2)等待不定时间(直到条件满足为止)的延迟
这种等待需要使用内核中的等待队列进行延迟。

等待队列定义在<linux/wait.h>,和要等待的条件一一对应。也就是说,有一个条件,就应该有一个等待队列。

等待队列要有一个等待队列头:
wait_queue_head_t xxx;

例子:
#include <linux/fs.h>
#include <linux/wait.h>

/* 1. 等待队列头 */
wait_queue_head_t mywait;

ssize_t mem_write(struct file *filp...)
{
/* 如果缓冲区满,则睡眠 */

//2.不可打断睡眠
wait_event(mywait, wp!=buf_len);

//可打断睡眠,如果返回非0,说明是被信号唤醒
if (wait_event_interruptible(mywait, wp!=buf_len))
return -ERESTARTSYS;
...
}


int mem_ioctl(xxx)
{
...
case MEM_RESET:
memset(xxx);
wp = 0;
/* 3.唤醒整个队列中的进程 */
wake_up(&mywait);
或wake_up_interruptible(&mywait);
...
}

static int __init my_init()
{
/* 4.初始化等待队列头 */
init_waitqueue_head(&mywait);
...
}

如果不希望唤醒时把整个队列都唤醒,还可以使用exclusive版本的睡眠/唤醒函数。


4. 内核的定时
内核的定时器称为timer_list,定义在<linux/timer.h>

例子:
struct timer_list mytimer;

/* 定时器的执行函数 */
void timer_func(unsigned long data)
{
...
}


/* 定时器的初始化 */
setup_timer(&mytimer, timer_func, data);

/* 定时器的启动,再未来的给定时间运行 */
mod_timer(&mytimer, jiffies+xxx);
也可以用add_timer();

/* 定时器的删除:一般在模块退出时执行 */
del_timer(&mytimer);

day_8

主题: 1。为什么要用锁以及锁相关的信息
2。加锁的原则
3。内核的哪些机制导致必须要加锁
4。atomic_t(原子变量)
5。spinlock_t(自旋锁)
6。mutex(互斥锁)

1。为啥用锁
如果内核代码中出现对全局变量的同时访问,那么通常要利用锁来保护。
临界区(critical region):访问全局变量的代码
竞争(race):有多个人同时进入临界区,就会发生竞争
同步(synchronization):如何避免竞争(如何加锁)

临界区可能分散在多个函数中,大家要共用一把锁。
临界区的每个部分都应该分别加锁,如果有个别部分忘记加锁,那么锁保护就没意义了。

锁的唯一作用,就是对数据访问提供保护,不要用锁来完成功能。

死锁
自死锁:在获得锁以后,再次要求获得锁
ABBA死锁:进程需要两把锁时,由于获得顺序问题导致的死锁
解决方式就是一旦要持有两把锁,必须按给定顺序


2。加锁原则
(1)锁是一种自愿的行为,是不得已的,为了保护数据
(2)锁一定要在程序设计的一开始就考虑,不要程序设计完成后在考虑加锁
(3)一开始设计锁时可以比较粗糙(加锁范围较大),随着设计深入,可以不断细化临界区(缩小加锁范围)。
(4)同一临界区的代码可能分散在不同的函数中,用同一把锁来保护,但是临界区每个部分的加锁/解锁函数可能不同。


3。导致加锁的机制
(1)SMP
(2)中断
(3)内核抢占
(4)schedule()
上述内核机制都可能造成同步或者伪同步(虽然只有一个cpu,但利用调度器的调度切换不同进程)

对临界区的每个部分加锁时,都应该考虑上述4种机制。


4. atomic_t
可用atomic_t来替代int型,实现计数的++等功能。由atomic_t的相关函数保证原子性
例子:
#include <linux/atomic.h>
static atomic_t i = ATOMIC_INIT(0);

/* 计数++ */
atomic_inc(&i);

参考arm-drv/atomic_test01.c


5. spinlock_t
特征:
(1)临界区1个人
(2)等待锁的人通过忙循环等待
(3)持有锁的人不能睡眠
spinlock锁是内核中最常用的锁,持有时间通常是ns级的。

结合内核的机制,spinlock锁的适用范围:
SMP,中断以及内核抢占。

如果临界区中必须睡眠(调用schedule),则该临界区不能用spinlock提供保护。

参考arm-drv/spin_test01.c


6. mutex / semaphore
特征:
(1)临界区里一个人
(2)等待锁的人睡眠等
(3)持有锁时可以睡眠(必须能被唤醒)
mutex适用于临界区中可能睡眠的情况,在内核中用的比较多,持有锁的时间常常是ms级。

mutex可以针对的内核机制有:
SMP,内核抢占,schedule()。
如果临界区中包含中断处理,则无法用mutex进行保护(因为获取mutex时可能睡眠,而中断中不能睡)

例子:
#include <linux/mutex.h>

//声明锁
struct mutex mylock;

ssize_t xxx_write()
{
int ret;
//加锁
mutex_lock(&mylock);
或可被信号打断的版本:
ret = mutex_lock_interruptible(&mylock);
if (ret)
return -ERESTARTSYS;
...
//解锁
mutex_unlock(&mylock);

}

xxx_init(void)
{
...
//锁的初始化
mutex_init(&mylock);
...
}

day_9

主题: 1。基于mutex实现PIPE的例子
2。spinlock/mutex的一些相关变种锁
3。设备模型的概述
4。设备模型的核心结构体
5。基于设备模型改造写好的char驱动
6。基于设备模型实现设备文件的自动创建(自动mknod)

1.pipe的例子
参考x86-drv/06mutex/mutex_test01.c

2.变种锁
(1)rwlock_t
例子:
#include <linux/spinlock.h>

rwlock_t mylock;

/* 在只读的临界区,用读函数加锁 */
read_lock(&mylock);
...
read_unlock(&mylock);

/* 如果临界区可读写,则加写锁 */
write_lock(&mylock);
...
write_unlock(&mylock);


(2)seqlock_t
顺序锁。可以避免写者饥饿。但是,要求临界区中不能涉及指针操作


(3)semaphore(信号量)
semaphore的出现时间早于mutex。如果semaphore中的计数为1,则使用方式同mutex。
目前的内核已经不再使用semaphore保护临界区,只用semaphore限制对有限资源的访问人数。


3. 设备模型概述
最早期的设备模型用于解决电源管理方面的问题。其实就是在内核中创建了若干个和实际设备一一对应的结构体。结构体中记录设备的信息。
随着设备模型的发展,内核为用户态观察设备的信息提供了接口。
即/sys目录


4. 核心结构体
内核在设计设备模型时,引入了面向对象的思想。所以,相关结构体按照对象设计。一般会有3层。


(1)<linux/kobject.h>
kobject.h中是最核心的结构体,包括:
kobject:对应一个对象
kset: 对应一个容器

(2)<linux/device.h>
device.h中的类是kobject/kset的子类。用于描述设备,驱动等。
a.device
继承于kobject,对设备信息的抽象,和实际的设备一一对应
b.device_driver
继承于kobject,和设备驱动一一对应
c.class
继承于kset,内核中通常将同一类型的多个设备/驱动等放到给定的class中
d.bus_type
继承于kset,内核将同一中总线下的设备/驱动放置到给定的bus_type中


(3)<linux/platform_device.h>
对于6410内部的大量设备来说,采用的总线为platform总线。因此,每个实际存在的设备应该创建一个platform_device结构体。
驱动人员为设备开发的驱动,应该用platform_driver封装。

platform_device
对应一个硬件。以6410为例,驱动开发人员或者移植人员应该为6410中每个实际存在的硬件创建一个platform_device。用硬件的信息,比如物理地址,中断号等来初始化platform_device
比如6410有4个UART,那么需要创建4个platform_device

由于开发板上实际用到的设备通常少于6410中存在的设备,因此,内核要求将实际用到设备的pdev注册到内核。

对于6410来说,已经准备好的platform_device分步在如下目录:
(1).../arch/arm/plat-s3c/
其中的dev-ts.c等
(2).../arch/arm/plat-s3c64xx/
其中的devs.c等

对于使用的开发板来说,需要把真正用到硬件的platform_device注册到内核,SMDK6410开发板将这些pdev的指针集中到一起,即:
文件arch/arm/mach-s3c6410/mach-smdk6410.c
中的数组smdk6410_devices。
当内核启动时,会自动将数组中的每个platform_device注册到内核中。


platform_driver


kobject --> device --> platform_device
--> device_driver --> platform_driver

kset --> class
--> bus_type

day_10

主题: 1。platform_device/platform_driver的例子
2。自动创建设备文件的例子
3。linux驱动中的子系统
4。input子系统

1.
参考x86-drv/06model/plat_dev1.c, plat_dev2.c, plat_drv1.c

2.自动创建设备文件
参考plat_drv2.c


3. 子系统
子系统的优点:
(1)简化驱动的设计
(2)给用户态一个统一的接口
(3)提高代码的复用率
在现在的内核中,基本上每一类相似的硬件,都会有对应的子系统。比如支持所有flash设备的子系统在drivers/mtd目录下;支持所有输入设备(鼠标,键盘等)的子系统在drivers/input目录下;

子系统的缺点:
(1)由于每类设备都有自己的子系统,所以要写驱动,必须先了解对应的子系统
由于子系统相关资料比较缺乏,所以,详细的子系统设计需要以代码为准。

4. input子系统
实现文件可参考drivers/input/目录下的input.c以及evdev.c
input子系统的核心头文件是include/linux/input.h

 day_11

主题: 1。input子系统的核心结构体
2。input硬件之触摸屏
3。input驱动的platform_device和platform_driver
4。input驱动的设计

作业:
1。看完input驱动,重点看两个中断处理函数和一个定时器函数
2。看DM9000手册的P13~27

1.核心结构体
定义在<linux/input.h>
基本上,每个子系统都会在include/linux目录下有一个核心的头文件,其中定义了子系统的核心结构体。

要查看当前系统中已经存在的input设备,可以参考文件:
/proc/bus/input/devices

(1)input_event
结构体占16个字节,每当设备产生按键,坐标等信息时,input驱动将设备的事件封装到input_event中,然后通报给input子系统

(2)input_dev
在input子系统中定义,用于代表一个实际的input设备。
input_dev由input驱动分配并初始化,然后注册到input子系统中。input驱动应该为每个实际存在的设备都分配并注册一个对应的input_dev。


2。TS硬件


3。ADC的platform_device和platform_driver
和触屏相关的platform_device应该已经由三星准备好了,放在arch/arm/plat-s3c/dev-ts.c中

由于platform_device只能记录物理地址,中断号等通用信息,很多驱动在准备pdev时还会分配一个设备独有的结构体,用于记录设备的独特信息。比如,三星就为触摸屏设备设定了结构体s3c_ts_mach_info。

该结构体和platform_device一起准备,只会由三星触摸屏的驱动使用。

该结构体通常在arch/arm/mach-s3c6410/mach-smdk6410.c中准备,里面记录触摸屏独有的信息。


platform_driver来自于drivers/input/touchscreen/s3c-ts.c
将其拷贝到drivers/arm-drv/目录下

三星的移植人员还要为6410中的每个硬件准备一个clk结构体,记录该硬件的时钟信息。
这些clk结构体集中在arch/arm/plat-s3c64xx/clock.c中

驱动用clk_get()找到给定设备的clk结构体;
然后用clk_enable()使能该设备的时钟

 day_12

主题: 1。input驱动的补完
2。input驱动的测试
3。DM9000网卡的platform_device
4。DM9000网卡的platform_driver
5。网卡驱动的核心结构体net_device

作业:1。基于开发板的按键写出标准的input驱动


1.
参考ts_test01.c


2.测试


3。DM9000的platform_device
自行实现platform_device
参考arm-drv/dm9000_dev.c

4. DM9000的platform_driver
MII: Media Indepedent Interface(媒体无关接口)
MII是MAC和PHY之间的接口


5. net_device
定义在<linux/netdevice.h>
是网络驱动的核心结构体。

net_device->name: 设备名字,如eth0等
net_device->dev_addr: 设备的MAC地址

net_device必须提供的3个函数是:
hard_start_xmit: 完成数据包的发送
open: 和用户态用ifconfig配置IP地址的行为对应
$>ifup eth0
stop: 用$>ifdown eth0对应

day_13

主题: 1。DM9000驱动的platform_driver
2。net_device->open|stop|hard_start_xmit
3。网卡驱动的中断处理

作业:
1。复习DM9000驱动
2。看6410手册的Display Controller一章

1。platform_driver
参考arm-drv/dm9000_test01.c

day_14

主题: 1。6410的Display Controller(FIMD)和LCD屏
2。framebuffer驱动的架构
3。针对6410 FIMD的fb驱动
4。驱动的测试

day_15

主题: 1。块设备的特征
2。块设备驱动和块调度层
3。块设备的核心结构体
4。在x86上的一个块设备驱动例子

1/2

3。核心结构体
(1)gendisk
定义在<linux/genhd.h>
和一个实际存在的磁盘对应。由驱动分配并初始化,和磁盘一一对应。

(2)hd_struct
定义在<linux/genhd.h>
和一个磁盘上的分区对应。记录分区的起始扇区,扇区数,分区的一些统计信息等

(3)request
定义在<linux/blkdev.h>
和一组连续扇区的访问对应。request由块调度层产生,提交给块设备驱动,每个request要么读,要么写。驱动得到request后,应该将磁盘上对应扇区的信息拷贝到request对应的缓冲区或相反。


(4)request_queue
定义在<linux/blkdev.h>
request的队列。块调度层生成的请求首先要挂载到request队列中,然后在提交给块驱动。

(5)block_device_operations
定义在<linux/blkdev.h>
一组访问函数集,由驱动提供,当用户态需要对/dev下的设备文件进行mount,fdisk等访问时,需要调用其中的函数。
一旦分区mount成功,对分区文件的读写不会调用block_device_operations中的函数。

原文地址:https://www.cnblogs.com/Huluwa-Vs-Aoteman/p/3453054.html