bh处理
1.三种旧式的bottom half 处理类型
IMMEDIATE_BH: driver注册入tq_immediate队列,等待调度
TQUEUE_BH: 执行tq_timer,到系统tick产生时触发
TIMER_BH: 直接绑定到do_timer()处理函数
2.mark_bh()
将激活32个BH中的某一个,触发软中断来进入被调度状态
此种软中断优先级为 HI_SOFTIRQ
3.tasklet
此方法将触发软中断,使tasklet t被进入调度状态
此方式触发的软中断优先级为TASKLET_SOFTIRQ
使用方式:
1.
声明tasklet:
DECLARE_TASKLET
2.
执行tasklet
tasklet_schedule(t)
init_bh(TIMER_BH,
timer_bh); 定时器中断将执行 timer_bh任务队列
init_bh(TQUEUE_BH,
tqueue_bh);
init_bh(IMMEDIATE_BH,
immediate_bh);
BH后半部处理类型
interrupts.h
enum {
TIMER_BH
= 0, /*时钟*/
TQUEUE_BH, 定时器队列
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
AURORA_BH,
ESP_BH,
SCSI_BH,
IMMEDIATE_BH,
CYCLADES_BH,
CM206_BH,
JS_BH,
MACSERIAL_BH,
ISICOM_BH
};
bonfs
1.bonfs 是一种简易的只读文件系统,在2440项目中,bonfs是一个可选的块设备。
bonfs是一种设备,他位于mtd管理层的上端,所以其可以 register_mtd_user()注册一个mtd_user.
此刻如果已经存在mtd设备或者mtd分区设备,在register_mtd_user()时,每个mtd设备都会通过 notifier_add()通知bonfs,然后bonfs便可以进行初始化操作。
vivi在进行分区时可以将分区格式化为若干bon分区(
read_partition_info() )
在bon的notifier_add()处理中,调用read_partition_info()读取分区开头8字节是否是bon分区的MAGIC标志
**读bon.c代码,发现bon是将整个mtd设备(也就是整个nand flash)作为操作对象,而并不是一个mtd分区一个bon分区。
似乎有点好理解了,(bon设备不依赖mtd分区),vivi 在用bon分区时,将分区信息写入nand
flash 头部的512字节区域内,bon视整个flash是一个mtd设备,
然后通过read_partition_info()将bon分区信息读出,在内存中构建分区信息,所以在mtdblock.c代码中没有使用到mtd设备的分区信息,所以也就是不能用 root =/dev/mtdblock/0方式加载.
**在vivi的代码中已经写死了整个nand
flash的分区使用规则,如果不使用bonfs的话,就没有必要进行bon分区,分区只是搽除数据和写入标志,还有其他一些信息
bon设备支持 add_mtd_device(s3c2440_mtd) 方式添加的mtd设备
如何读取和校验bon分区的:
read_partition_info(){
mtd->read_oob((
mtd->size - mtd->erasesize),8) 从erasesize开始读8字节oob数据
if(
oob[5] == 0xff){
buf
= mtd->read( ( mtd->size - mtd->erasesize),512) 读512字节,bon分区信息存储在这512字节区间内,比如分区偏移,大小,标志等等
if(
buf[0~7]!=BON_MAGIC){
非法bon分区
}
}
devfs_mk_dir("bon") 创建 /dev/bon
从mtd(nand flash)中读取bon分区表信息,构建分区数组
devfs_register()
注册子设备文件 /dev/bon/0,1,2,3 (子设备号根据vivi对nand flash的分区)
统计bad_block信息
}
bon_sizes[] 分区大小 ,以k为计算单位
bon_blksizes[]
分区块大小
.bon分区作为启动root分区
cmd
: char linux_cmd[] = "noinitrd root=/dev/bon/2 init=/linuxrc
console=ttyS0";
察看 main.c中 root_dev_names[]列表中并不存在bon的设备类型,原因在于bon是注册进入devfs,是fs加载之前便已经初始化完毕了,
所以传递以上内核参数是可以找到对应的设备的.
root_dev_names[]定义的是固定的设备名称和设备编号,此时就需要实现此设备的驱动程序必须与设备编号进行匹配对应了,也就是靠devfs_register(MAJOR)注册的设备主编号
.bon分区启动
start_kernel(){
1.parse_command()
分析命令行参数
root=/dev/bon/2
2.查找启动设备的主设备编号
初始化所有设备驱动__initcall
在设备驱动中,bon注册一个devfs的块设备名称 /dev/bon,然后注册为mtd user ,register_mtd_user()
当mtd设备驱动被初始话时,启动nand_chip驱动去搜索flash设备,通过一系列的寄存器的交互,发现了mtd设备,
如果当前没有把分区开关打开的话则将整个flash当作一个mtd设备加入mtd_table[],同时通知mtd user,也就是告知
bon,当前已经有mtd设备挂入,bon得到控制权了,他就读取mtd设备的开头512字节的数据,构造分区表信息,和校验分区是否合法,
bon将每个分区注册进入devfs作为子设备(/dev/bon/0)
3.回到步骤2,由于bon没有存在root_dev_names[]静态表中,所以内核不知道其major编号,所以在跳过了nfs,floppy启动判别之后,
内核拿着bon名字到devfs进行查询此设备的主次设备编号,如果找到了则读取超级块,然后进行文件系统的识别
}
devfs
devfs 文件系统在内存中建立,而不是存储在块设备或者字符设备之上。
但是为了符合vfs文件系统的接口规范,以 devfs_entry来包装 dentry,以devfs_inode来包装 inode。
devfs文件系统中的devfs_entry存放在一个叫做 fs_info的结构变量中,以inode->ino节点编号作为索引可以查询fs_info中的devfs_entry
数据结构
===============
1. devfs_entry : 描述devfs中的目录项
typedef
struct devfs_entry * devfs_handle_t;
struct
fcb_type : 此结构类型概括了 字符设备/块设备/常规文件的信息,包括了设备的特性,安全管理和最重要的驱动函数入口
devfs_entry存放在fs_info->table[]中
2.devfsd
此为一服务进程,观察devfs 目录项条目的运行状态,比如加入新的entry, devfs_register()会触发DEVFSD_NOTIFY_REGISTERED,devfsd便可以进行一些新增设备的处理工作 。
函数
==============
1.get_devfs_entry_from_vfs_inode()
根据inode->ino,在fs_info->table中搜寻devfs_entry对象
+++++++++++
1.访问devfs块设备文件
命令: cat
/dev/mtdblock/0
mtdblock/0
的主设备号31,次设备号 12 ,之前是由mtd驱动调用 devfs_register()注册进入内核,并在devfs的fs_info->table[]中占有一席之地。
用户在打开设备文件进入内核空间,在内核空间中,fs层通过path_walk()等函数找到/dev/mtdblock/0对应的dentry信息,
然后构建出file结构,将dentry->inode 和file作为参数,传入devfs文件系统的
dev_open()函数。
devfs文件系统根据inode->ino编号在fs_info->table[]中找出mtd设备的devfs_entry对象,在devfs_entry->fcb->ops中存放着
设备驱动入口函数,驱动入口函数原型可以是file_operations或者block_device_operations. 然后将 devfs_entry->fcb->ops传给inode->i_bdev->bd_op作为块设备驱动入口。
在devfs_open()中,如果当前打开的是块设备,就把缺省的块设备访问接口(def_blk_fops)付给file->f_op,然后调用file->f_open->open(inode,file)打开块设备.
**所以对比字符设备和块设备,块设备的访问比字符设备多了一层
block_device_operation. 字符设备可以将驱动写在file_operations中,但块设备必须写在block_device_operation, 但要访问设备的话都是通过 file_operation访问,块设备利用def_blk_fops进行了file_operation到block_device_operation的转换。
2.块设备的读写
ll_rw_block
devfs_register
===================
embedded_arch
1. bootloader : vivi/u-boot
2. kernel_image
3. root_fs
bootloader通过jtag烧写到flash rom
bootloader通过rs232将kernel_image写入flash,或者bootloader从rs232/ethernet动态加载kernel_image
kernel_image加载fs,可以采取nfs/mtd方式加载
kernel支持mtd/nand文件系统驱动
bootloader通过参数决定从何处挂载root-fs
fs
1. 新的文件系统注册
register_filesystem()
find_filesystem()
kern_mount()
数据结构:
1.file_system_type
struct
file_system_type {
const
char *name;
int
fs_flags;
struct
super_block *(*read_super) (struct super_block *, void *, int);
struct
module *owner;/*动态加载的模块,编译进内核则为0*/
struct
vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */
struct
file_system_type * next;
};
2.file_systems(super.c)定义了文件系统类型的链表
IO访问
1.外设访问的方式
*IO端口访问 : 提供特殊的端口访问指令,比如inb,outb
*端口映射: 将外设register映射到cpu内存空间,用普通操作指令便可访问外设register 。非常方便,cpu开销比较小,指令简单
2.防止优化
对于register映射到内存的方式,将碰到两种麻烦的可能。其一,硬件采用cache的方式来加快对内存地址的访问;其二,编译器会优化
操作指令,比如写入内存数据可能会被放入寄存器。以上两种情况都将导致访问外射IO内存地址空间失败。
所以为了避免这种情况,linux初始化时禁用访问io地址空间时硬件cache功能;对于编译器的解决方法就是采用barrier()
3.io端口地址的请求和释放
release_region
request_region
4. io读写
inb(),insb(),outb(),outsb()
inb_p
暂停读
io资源分配
1. request_region()
请求io映射端口内存地址范围.内存区域用resource数据结构进行表示。
resource表现一段内存地址,以树型展示不同的区域。也就是简单的理解为一段内存地址可以又分隔为许多内存地址,所以resource具有了parent,sibling,child属性
ipc
source/ipc目录内的ipc机制都是system V 新增的通信机制,包括 共享内存,消息队列和信号量
sys_pipe的实现在arch/i386/kernel中,似乎跟具体的平台有某种关系,这是何故呢?
*pipe的实现比较简单,但是具有所有文件具有的特征,区别在于pipe的inode节点中存在一个 pipe_operation的操作跳转,两个进程一头写一头读,数据靠内核中申请的一页内存进行中转。
夫进程在创建pipe之后,可以将pipe文件句柄传递到两个子进程去,使其子进程之间可以进行单向的数据通信。
**fifo 命名管道
支持在无进程关系的不同进程之间通过打开命名管道名称进行通信
==管道都是基于流,效率和使用方式都不是很好,比如不能提供有效的消息分割,导致要到应用层进行数据的解析,类似于tcp流的处理.
system-V 的ipc在 sys_ipc()函数内分流处理,类型有 semophera/message/share-memory
?sys_ipc()存在于 /arch/i386/kernel中,又不知何故难道不可移植的吗?
kernel运行参数加载
include/asm-arm/mach/arch.h
arch/arm/mach-s3c2440/smdk.c
定义平台信息结构
MACHINE_START(SMDK2440,
"Samsung-SMDK2440")
BOOT_MEM(0x30000000,
0x48000000, 0xe8000000)
BOOT_PARAMS(0x30000100) //定义linux启示参数存放区域
FIXUP(fixup_smdk)
MAPIO(smdk_map_io)
INITIRQ(s3c2440_init_irq)
MACHINE_END
start_kernel(){
setup_arch(){
setup_machine()
//获取平台配置信息结构
struct machine_desc
{
检索__arch_info_begin开始的位置的平台信息是否匹配平台类型
vmlinux-armv.lds.in
包含__arch_info_begin
定义*(.arch.info)
}
}
}
Linux启动过程中硬件模块的加载
设备驱动程序都用module_init(xx)进行登记
module_init()函数就是将 xxx函数地址负值到
initcall(void*) 的函数指针变量,并将其连接如.initcall.init节中. __attribute__
((unused,__section__ (".initcall.init")))
kernel启动时,在函数do_initcalls()将 initcall.init节中的所有函数地址都执行一便,这样就完成了静态连接到kernel的驱动的初始化调用
mtd_nand
1.nand支持结构层次
fs
(user mode)
mtd
nand(driver)
** mtd/0
字符设备 driver/mtd/mtdchar.c
** mtdblock/0 块设备 driver/mtd/mtdblock.c
分3层结构,nand驱动提供访问flash 的底层接口函数,mtd抽象访问接口,最上层就是文件系统层,目前可以使用的是jiffs,yaffs2等等
2. 基本数据结构
struct
mtd_info
定义一堆的访问nand驱动的接口和数据
add_mtd_device() 将一个nand 设备注册为一个mtd设备
add_mtd_partitions() 将分区注册为mtd设备 (这种方式不能用于bon块设备,因为这样bon无法读取正确的设备分区信息,bon是面向整个flash设备的)
struct
mtd_part 代表一个nand分区 , mtd_part::mtd指向 mtd_info
struct
mtd_notifier {
void (*add)(struct mtd_info *mtd);
注册时回调fs的函数
void (*remove)(struct mtd_info *mtd);
struct mtd_notifier *next;
};
user-mode 的fs要提供mtd_notifier,当nand设备插拔时将进行通知(remove)到fs
register_mtd_user
(struct mtd_notifier *new);
unregister_mtd_user
(struct mtd_notifier *old);
以上这两个方法供具体的fs调用
CONFIG_MTD_SMC_S3C2440_SMDK_PARTITION 决定了是否将一个mtd分区注册为一个mtd设备
1. s3c2440 nand驱动
driver\mtd\nand\smc_sc2440.c
smc_insert()登记 nand partition信息
smc_scan() 扫描nand类型,并安装nand处理函数到 mtd_info中去
add_mtd_partitions()
注册mtd_info,支持partition
add_mtd_device(){
把每一个分区注册为一个mtd设备
把每个mtd分区注册进每一个mtd_notifier中去
}
2.
mtd_partition 定义mtd设备分区表
mtd_part 定义分区信息结构
每个mtd_part分区都视为mtd设备,所以都具有 mtd_info数据结构。并将mtd_part通过add_mtd_device()注册进入mtd_table[]数组
mtd_part
拥有mtd_info的理由:
mtd_info定义了访问mtd设备的接口,比如 mtd.read(). mtd_part作为mtd设备的一个区间,但也被看作是一个mtd设备,所以读取mtd的起始位置与直接访问mtd主设备不一致,多了一个偏移量。mtd_part.mtd.read()函数中计算出mtd主设备中自己的绝对位置,然后调用主mtd设备的read()接口函数,所以在访问带有分区的mtd设备时调用绕了一个小圈子。
mtd_info中的诸多函数在主设备中定义为 nand_xxx(),而对应着在mtd_part.mtd中也对应定义了 part_xxx(),但调用还是最终要进入 nand_xxx(). part_xxx()接口只是在为真正调用nand_xxx()而计算自己在主设备中的偏移量而已
。
所以对mtd来讲,一个分区就是一个mtd设备,如果没有划分分区则此设备就是一个mtd设备
smc_insert
mtdblock
1. 初始化
init_mtdblock(){
devfs_register_blkdev() 注册mtd块设备到devfs文件系统
register_mtd_user() 注册mtd设备插拔事件接收者
blk_init_queue() 初始化数据队列
mtdblock_thread() 启动工作线程
}
/drivers/mtd/mtdcore.c 代码中通过add_mtd_device()将mtd/nand/smc_s3c2440.c中定义的分区mtd_part作为mtd_info设备注册进系统mtd设备链表
修改分区信息:
smc_s3c2440.c::smc_partitions[],
开放 宏开关 CONFIG_MTD_SMC_S3C2440_SMDK_PARTITION
,系统将所有定义的分区作为mtd设备登记入mtd_table[]数组.(通过/dev/mtdblock/n便穿越mtdblock驱动到达mtd驱动层获取mtd分区信息)
64M K9F1208U0M Flash
15bit宽
,32k pages = 16M
nand flash = 16M*4Plane = 64M
column Address 用于选择 A,B,C区地址
出厂时如果是坏块,在第一或者第二页的C区的第6字节不为0xff
Flash由于在使用时会产生新的坏块,所以必须采用replacement策略,即在写入检测为坏块则跳跃到下一个块进行处理。
在写/读/擦出操作时产生失败,则有必要将此块标示为坏块
检测到操作失败
定位到页所在块的第一个页位置
写入C区517位置的值为非0xff
在读写之前判别好/坏块的方法:
从块编号换算到页编号
NF_CMD(CMD_READ2);
// 0x50
NF_ADDR(VALIDADDR) //column 第6个字节,517位置, #define VALIDADDR 0x05
NF_ADDR() //PAGE raws
NF_RDDATA() 如果是0xff,表示为好块,否则为坏块
ECC校验:
Samsung
2440的nandflash
controller在读写flash之后将自动产生ecc校验码存放在ECC寄存器,所以在执行写页操作之后,必须把ECC校验码写入到512~527这个C区间内,一般都放置在512+8的位置;在读操作时,将存储的校验码读出并与ECC寄存器的值进行比对即可
Flash 读写代码:
BOOL
FMD_WriteSector(SECTOR_ADDR startSectorAddr, LPBYTE pSectorBuff, PSectorInfo
pSectorInfoBuff, DWORD dwNumSectors)
{
BYTE Status;
ULONG SectorAddr = (ULONG)startSectorAddr;
ULONG MECC;
if (!pSectorBuff &&
!pSectorInfoBuff)
{
RETAILMSG(1,(TEXT("FMD_WriteSector: Failed sector write 01.
\r\n")));
return(FALSE);
}
NF_RSTECC(); // Initialize ECC.
NF_nFCE_L(); // Select the
flash chip.
NF_CMD(CMD_RESET); // Send reset command.
NF_WAITRB(); // Wait for flash to complete
command.
while (dwNumSectors--)
{
ULONG blockPage = (((SectorAddr /
NAND_PAGE_CNT) * NAND_PAGE_CNT) | (SectorAddr % NAND_PAGE_CNT));
if (!pSectorBuff) //如果只是准备写入page信息(512~517)
{
// If we are asked just to write
the SectorInfo, we will do that separately
NF_CMD(CMD_READ2); // Send read command. 读第C区命令
NF_CMD(CMD_WRITE); // Send write command.
NF_ADDR(0); // Column = 0. 第一个字节开始(512)
NF_ADDR(blockPage & 0xff); // Page address. 页行地址,共15位宽,所以共 2^15=32K pages = 16M
NF_ADDR((blockPage >> 8) & 0xff);
NF_ADDR((blockPage >> 16)
& 0xff);
WrPageInfo((PBYTE)pSectorInfoBuff); //写入信息
pSectorInfoBuff++;
}
else
{
NF_RSTECC();
NF_MECC_UnLock();
NF_CMD(CMD_READ); // Send read command. 0x00
NF_CMD(CMD_WRITE); // Send write command.
NF_ADDR(0); // Column = 0.
NF_ADDR(blockPage & 0xff); // Page address.
NF_ADDR((blockPage >> 8) & 0xff);
NF_ADDR((blockPage >> 16)
& 0xff);
//
Special case to handle un-aligned buffer pointer.
if( ((DWORD) pSectorBuff) &
0x3)
{
WrPage512Unalign (pSectorBuff);
}
else
{
WrPage512(pSectorBuff); // Write page/sector data.
}
NF_MECC_Lock();
// Write the SectorInfo data to the
media.
//
if(pSectorInfoBuff)
{
WrPageInfo((PBYTE)pSectorInfoBuff); //写入尾部信息 512~519
pSectorInfoBuff++;
}
else // Make sure we advance the Flash's write
pointer (even though we aren't writing the SectorInfo data)
{
BYTE TempInfo[] = {0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
WrPageInfo(TempInfo);
}
pSectorBuff += NAND_PAGE_SIZE;
MECC = NF_RDMECC0();
NF_WRDATA((MECC ) & 0xff);
NF_WRDATA((MECC >> 8) & 0xff);
NF_WRDATA((MECC >> 16) & 0xff);
NF_WRDATA((MECC >> 24) &
0xff);
}