得到一个句柄hDevice后,用IOCTL把句柄bind到一个队列上
IoctlDispacher
Irp得到stack,得到FILE_OBJECT,根据参数(x)把FILE_OBJECT bind到IrpRead[x]
具体用一个LIST_ENTRY记录这个FILE_OBJECT和x
pdx->listBind 类型为LIST_ENTRY
struct _BIND
{
LIST_ENTRY link;
PFILE_OBJECT fop;
ULONG id;
}
ReadFile(hDevice...)
ReadDispacher
Irp得到stack,得到FILE_OBJECT,查询pdx->listBind
其实ddk文档说的很清楚了。不过我还是要记录一下几个要点,暂时可能又用不到了,我的记忆力是越来越不行了
以前认为凡事只要掌握规律就行,现在发现这太极端。其实有些事不用关心那么多,记住就行,真是越来越懒了。。
1.包驱动
IOM_Packet是GIO,PIO,SIO class driver和mini-driver通讯用的数据结构,通过内部一些成员,class driver和mini-driver之间可以共享一些ram.
2.函数指针表
IOM_Fxn 是一个表,这个表是GIO,PIO,SIO 接口和mini-driver间的一个对应,如GIO_write 会调用mdSubmitChan
3.chan
其实chan到底是什么很难用一句话来描述,chan是自己设计的,根据不同的需要可以设计不同的chan. 在内部一般chan的object的handle通常就是class driver中的handle。这个handle可以是mini-driver MEM_alloc出的,也可以是static的。
4.完成IO
完成IO很简单,就是一个callback.这个calback是class driver提供的。如果不打算开发新的class driver,那么只需要在想要完成的时候callback就行了,一般这个callback可以放在chan的object里。不同的chan可以callback不同的。比如ISR里callback,于是class可以在callack里发出信号,而此时一个block中的class 接口函数会及时地resume.
5.排队
排队机制不是必须的,c64xx_pci.c很好地做出了一种通用的排队机制
6.相关的接口
6.1 CSL chip support library(i2c,cache,edma,irq,......)
6.2 BIOS(sem,mbx,que,tsk,hwi)
忽略了一个IOM。运行中IOM没有一个实体,它只是一个接口规范,定义IOM_Packet Fxn等。
设计中应当将实际的设备,on-chip 或者 off-chip 的抽象成device,而一个device其实只是一个IOM_Fxn 在和class driver通讯。对于同一个设备的不同用法,可以抽象出chan,再某个chan上对应一种操作,一切都取决于实际的用途。
isr的问题。1.event要map到cpu(c64xx是4-15)2.event类型要看对应寄存器,文档写interrupt flag,有的写 interrupt src.大伤脑筋,以后一定要用interrupt flag,有时后flag居然在status register里。src是只要访问就自动清,没什么用。可以在刚进isr的时候判断一下,0就return.
用bios时千万不要isr前加interrupt词,不用bios时一定要加。这个保留词和栈操作有关,具体不研究,反正不乱用。
头文件一个要加。这个鬼编译器没有头文件也可以引用一个函数,即使参数不一样,也不报个错,等待自己的就是崩溃。另外像刚进一个函数参数都对的,进去了就变了的情况也多半是没加头文件。
另外还有一些代码无法调试,无汇编的情况,一般是启用的优化导致。一些static过程,比如只用了一次的static过程,会直接优化到caller内部。在static 过程 源代码无法下断点。真是伤害脑筋啊,因为我以前也不知道,接到一个老的工程他自己也不知道就优化。搞的我很郁闷,其实release就是优化的,为什么debug还搞呢
mdBindDevice时内部可能不能用assert.这个我还不清楚原因。用了也没什么大的影响,就是无法go main。
拿到一块YC2440(s3c2440)的开发板,经过几天的学习,我对arm-linux系统开发步骤有了一些认识。就以开发这个开发板为例,arm-linux开发工作大概分4个部分
1. 硬件(hardware)
2. 引导加载器(bootloader)
3. 内核(kernel)
4. 文件系统(file system)
硬件
我并不是硬件工程师,但我知道硬件的设计基本上是从性能,结构,可靠性等方面的需求方面来考虑。比如串口调试很常用,那么硬件就需要设计串口。在比如硬件需要接LCD,就必须提供LCD接口。
bootloader
bootloader是什么?
bootloader是一个引导程序,它最主要的功能是加载内核,所谓加载内核就是让内核代码常驻内存,并且得到执行。
bootloader因为什么而存在?
每一种CPU都有自己的启动方式
a) CPU上电后从某个地址开始取指令运行,这样的指令往往是已经固化的,因为RAM刚上电时,里面的内容是没有意义的,很多单片机是这样方式。可以说这根本不是boot
b) CPU上电后从ROM读代码到RAM,然后跳到RAM里开始执行,这种方式的CPU一般会拷贝固定长度的二进制代码到RAM,因为它不知道有效代码有长,只有一个固定的长度拷贝完成后,CPU才知道拷贝工作完成,以便从RAM执行。这就是boot
c) 方式二只能拷贝固定长度的代码到RAM运行,为了拷贝更多的代码到RAM运行,方式二就做了改进,首先进入RAM的代码不是一个功能固件,而是另一个功能代码的加载器(loader),这就是bootloader了
arm的bootloader
arm会拷贝4K长度的代码运行。显然arm不是为4K的固件设计的。拷贝代码到RAM并不需要很多指令,因为ARM对RAM的管理需要一个MMU控制器(可以让CPU访问更多的RAM或许)而这个控制器需要配置相关寄存器,所以代码可能要多一点,另外可能还有许多别的功能,所以代码可能会更多。当然都不超过4K时都没问题,但往往还是要过4K的。所以真正arm-linux的bootloader一般有两步骤:
a) 拷贝4K代码到RAM,开始执行
b) 拷贝另一段代码到RAM并初始化一些必须的硬件设置,开始执行
u-boot
u-boot是一种很流行的bootloader,除了加载内核,它还提供了许多其他功能。基本上u-boot是一个精简的linux,它提供人机交换的,一般现在linux开发都采用串口方式使用u-boot。
关于u-boot的说明有很多,我简单说明一下
a) u-boot可以被打断,通过串口向u-boot输入命令后,u-boot中断,可以执行各种命令,这些命令有专门的手册可以查询。串口其实就是u-boot的一个远程终端。
b) u-boot可以设置网络,通过tftp服务,u-boot可以下载代码到RAM然后执行,也可以烧写到flash
c) u-boot之所以有这么多功能是因为里面集成了许多驱动,如果要让u-boot有更多的功能可以在u-boot源代码里添加,如果要用硬件就需要添加驱动。
d) 如果要修改u-boot需要。。编译。。。。。。。。。。。。
内核
Linux内核部分是工作量比较多的部分
1. 交叉编译
2. BSP
3. Kconfig以及内核裁减
4. 镜像制作和烧写
交叉编译
关于交叉编译,网络上有很多文章。以ubuntu 8.10说明一下:
a) 下载编译器,比如arm-linux-gcc 3.4.1
b) sudo tar vxjf arm-linux-gcc 3.4.1.bz2 –C /
c) 命令行编译需要设置环境变量
sudo gedit /etc/bash.bashrc
在文件最后添加 export PATH=$PATH:/usr/local/arm/3.4.1/bin
重新登录
d) arm-linux-gcc –v 查看版本便知道交叉编译器是否安装成功
BSP
Linux的BSP其实就是外设驱动集合。比如扩了一个串口,需要编写设备驱动。关于设备驱动编写是一个很大的话题,我想这是另外需要一本书的《Linux Device Driver.3rd Edition》。看不懂可以看看参考http://www.deansys.com/doc/ldd3/index.html。
Kconfig以及内核裁减
Kconfig是用于定制内核的,有了交叉环境、BSP以及内核源码后,就可以做Kconfig.源码包的Makefile需要从.config得到信息以便把需要的东西编译到内核,不需要的东西不放进来,这样的内核是最精简有效的。问题是这些信息是庞大的,正如管理一个大的工程用Makefile一样,管理一个越来越复杂的内核用Kconfig。
以下引用自互联网
Kconfig文档的作用
内核源码树的目录下都有两个文档Kconfig(2.4版本是Config.in)和Makefile。分布到各目录的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文档相关的内核配置菜单。在内核配置make menuconfig(或xconfig等)时,从Kconfig中读出菜单,用户选择后保存到.config的内核配置文档中。在内核编译时,主Makefile调用这个.config,就知道了用户的选择。
*上面的内容说明了,Kconfig就是对应着内核的配置菜单。假如要想添加新的驱动到内核的源码中,能够修改Kconfig,这样就能够选择这个驱动,假如想使这个驱动被编译,要修改Makefile
so添加新的驱动时需要修改的文档有两种(注意不只是两个)
*Kconfig
*Makefile
要想知道怎么修改这两种文档,就要知道两种文档的语法结构
Kconfig
每个菜单都有一个关键字标识,最常见的就是config
语法:
config
symbol是个新的标记的菜单项,options是在这个新的菜单项下的属性和选项
其中options部分有:
1、类型定义:
每个config菜单项都要有类型定义,bool布尔类型、 tristate三态:内建、模块、移除 string字符串、 hex十六进制、 integer整型
例如config HELLO_MODULE
bool "hello test module"
bool类型的只能选中或不选中,tristate类型的菜单项多了编译成内核模块的选项,假如选择编译成内核模块,则会在.config中生成一个CONFIG_HELLO_MODULE=m的配置,假如选择内建,就是直接编译成内核影响,就会在.config中生成一个CONFIG_HELLO_MODULE=y的配置.
2、依赖型定义depends on或requires
指此菜单的出现和否依赖于另一个定义
config HELLO_MODULE
bool "hello test module"
depends on ARCH_PXA
这个例子表明HELLO_MODULE这个菜单项只对XScale处理器有效。
3、帮助性定义
只是增加帮助用关键字help或---help---
内核的Makefile
在linux2.6.x/Documentation/kbuild目录下有周详的介绍有关kernel makefile的知识。
内核的Makefile分为5个组成部分:
Makefile 最顶层的Makefile
.config 内核的当前配置文档,编译时成为定层Makefile的一部分
arch/$(ARCH)/Makefile 和体系结构相关的Makefile
s/ Makefile.* 一些Makefile的通用规则
kbuild Makefile 各级目录下的大概约500个文档,编译时根据上层Makefile传下来的宏定义和其他编译规则,将源代码编译成模块或编入内核
顶层的Makefile文档读取 .config文档的内容,并总体上负责build内核和模块。Arch Makefile则提供补充体系结构相关的信息。 s目录下的Makefile文档包含了任何用来根据kbuild Makefile 构建内核所需的定义和规则。
(其中.config的内容是在make menuconfig的时候,通过Kconfig文档配置的结果。
举个例子:
假设想把自己写的一个flash的驱动程式加载到工程中,而且能够通过menuconfig配置内核时选择该驱动该怎么办呢?能够分三步:
第一:将您写的flashtest.c 文档添加到/driver/mtd/maps/ 目录下。
第二:修改/driver/mtd/maps目录下的kconfig文档:
config MTD_flashtest
tristate “ap71 flash"
这样当make menuconfig时 ,将会出现 ap71 flash选项。
第三:修改该目录下makefile文档。
ü 添加如下内容:obj-$(CONFIG_MTD_flashtest) += flashtest.o
这样,当您运行make menucofnig时,您将发现ap71 flash选项,假如您选择了此项。该选择就会保存在.config文档中。当您编译内核时,将会读取.config文档,当发现ap71 flash 选项为yes 时,系统在调用/driver/mtd/maps/下的makefile 时,将会把 flashtest.o 加入到内核中。即可达到您的目的
make menuconfig后这个蓝色的终端里,有许多复杂的配置,根据需要配置好后保存就可以了。
ubuntu 8.10出现make menuconfig失败,一堆错误,这个需要
sudo apt-get install libncurses5-dev
镜像制作和烧写
内核镜像是被bootloader加载的,比如u-boot可以把内核镜像加载到RAM并执行。制作u-boot可加载的镜像需要使用mkimage工具,所以
sudo cp ~/tools/mkimage /usr/bin
make uImage
需要注意的是如果mkimage权限不对make uImage是会出错的,可以设置一下权限
sudo chmod 777 /usr/bin/mkimage
如果一切成功那么在linux-xxxx/arch/arm/boot下就有uImage文件了。
如果要更清楚里面的细节,需要对mkimage做进一步了解。
烧写的工作还是交给u-boot吧,大概工作是这样的:
a) 中断u-boot
b) 需要一个tftp服务器,比如Windows XP下安装tftpwin就可以了
c) 调用u-boot命令启动下载烧写
文件系统
待续
09.5.25 最后一次更新
0.这是我在论坛问的问题
DispacherSend(....)
{
.....
WRITE_PORT_UCHAR(TRANSMIT,0x1);//这里就启动发送,在dispacher没返回pending时,isr就以及发生,dpc已经完成irp,怎么办???同步isr?
return STATUS_PENDING;
}
BOOLEAN OnInterrupt(..)
{
if(TRANSMITINT)
{
IoRequestDpc(...)
}
}
DpcForIsr()
{
IoCompleteRequeset(..)
}
////////////////////////////////////////////////////////////////////////////
1.关于dispacher的irql
wdm书上irql说是dispach_level,但其他章节的叙述看来是passive_level...
2.关于dispacher的返回
返回值到底给了谁?
a)iomgr创建irp
b)iocalldriver本身是同步调用
c)如果iomgr直接iocalldriver,那么返回值给了iomgr,iomgr将做后期处理
d)DPC里iocompleterequest ,将排队dpc obj,最终可能是在apc_level做真正的返回处理
分析:假设dispacher在dispach_level调用
a)在dispacher里return pending前,可能被isr打断,
b)isr里iorequestdpc时,一个dpc入队
c)irql回到dispach_level,假设dpc rountie先于dispacher调用
d)dpc rountie 里面complete irp但所谓完成也是iocomplete obj入队。之后,dispacher应该被resume,即return pending
e)irql到apc_level,排在队列里的iocomplete被完成
f)irql回到passive_level,线程调度恢复,此时win32里的同步deviceiocontrol操作才会真正resume,而得到的值应该是iocomplete obj完成时的,具体应该在一个XXXblock的数据结构里。
这样的分析有两个条件,1)dispacher在dispach_level调用,2)从isr到dispach_level,dpcer可能先于dispacher调用。
如果条件2改为dispacher先调用,也一样,iomgr知道如何处理,我想dispacher里iomarkpending就是起这个作用的,isr来临前dispacher 其实已经放弃了irp,返回值pending到底有什么用不知。或许最终apc complete iocomplete obj时,做判断?
如果条件1改为dispacher在passive_level调用,那么win32 resume和dispacher resume应该也有抢占问题,但这个不重要,只要iomgr把markpending的irp的dispacher的返回值忽略就行。。。。。
没有符号——大陆行货(阉割版)
////////--------------------------------------------------------------------------2009.4.11
翻了翻wrk代码。实际上dispacher是回调函数,对readfile,writefile等win32 api ioctl,其真正调用者应该是iomgr-iocalldriver. 在iomgr的某段代码里面调用了iocalldriver,dispacher的返回值只关系iomgr如何处理。简单的说,iocalldriver返回pending可能会导致iomgr 阻塞。进而阻塞win32线程,实际上中间有一个细节,我想应该说是阻塞一个文件对象(驱动?)所在的上下文,最终才阻塞win32. 当然iomarkirppending可能会让iomgr选择不阻塞。
这样的话,事情就清楚了,对于一个设备——文件句柄。只能有一个IRP pending.,即使在不同的线程中,IRP的一个中间受体是文件,一旦pending就必须完成。即使是non-block IRP,我想也不行!这样的话,必须有一个完成机制,打开另个文件句柄,发个IRP停止是一种做法,walter 的例子也是这么做的,我不知道是不是标准做法,我想应该不是!
我翻阅了src/kernel/serial。它的做法是分时。我想双工driver可以仿照文件行为
1.read行为应该类似普通文件
a) 如果下面有数据,立即返回,如果没有,启动一个timer,以便read返回,这和non-block block IOCTL关系不大。
b) 下面是否有数据,完全决定于驱动设计。对于一个中断设备,可以选择在pending期间才处理接收中断。也可以选择缓冲数据。
c) serial也是用的这种分时方式。
2.write
a)普通文件write也是会超时的。所以write也要提供一个timer
3.显然一个文件不应该同时写和读,即使技术上能实现。
至于排队IRP,书上说,排队是提高吞吐量。我认为排队揭示了一个重要原则,必须提供取消例程。应该说实际上对于一个设备驱动,只能串行处理irp,所谓排队应该有一个隐含条件,另一个设备句柄被打开了,有一个新的irp要处理,但当前的还没处理完,显然直接返回失败不是最好的办法,至少我认为这时排队irp是更好的方法,第二个实体不需要知道错误,它只需要同步挂起,或者overlap.它会在需要时被唤醒。
到目前为止,双工driver可以利用分时方法。双句柄当然可以不用分时,但双句柄有取消IRP.的问题。
总结一下:只为一个实体提供输入输出服务的设备。1.排队不是必须的(只能一个pending,)。2.分时是比较理想的方式3.overlaped是不需要的,因为对于同一个设备句柄,即使两个线程也不能overlap.第二个调用的会返回失败 重叠io正在请求中。我认为没必要花精力处理这种错误
#include "stdafx.h"
const char* check()
{
char c=0xff;
unsigned char val=0xff;
return c==val?"yes":"no";
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%s\n",check());
return 0;
}
区别在 一个是movsx一个是movzx...........
00411AB6 movsx eax,byte ptr [c]
00411ABA movzx ecx,byte ptr [val]
00411ABE cmp eax,ecx
传统的ISA/EISA总线设备,虽然不支持PnP Power管理,但仍然可以用WDM结构来写,显然,中文翻译的《Programming the Microsoft Windows Driver Model》多少是有点误导了我,英文的没看不知道。
WDM或者说KMD本身是设备驱动程序编写的一个框架,或者说规矩,或者说抽象,或者说风格
如何为遗留设备编写驱动,可以参考C:\WINDDK\3790\src\general\portio,这个例子是用inf配置资源的。LogConfig段用法是关键
root是一个好东西,可以虚拟设备
另外,要对设备枚举有更好的理解,可以看C:\WINDDK\3790\src\general\toaster,这里虚拟了一个总线,好好理解总线驱动如何工作,有帮理解其他总线设备如何工作,驱动如何编写
C:\WINDDK\3790\src\general\event是driver通知app的一个例子,里面介绍了事件方式和IRP Pending方式。我写过VxD,事件方式差不多,IRP Pending是VxD没有的。应该好好理解理解!VxD的User Mode APC方式我感觉很糟糕,以后不会用了
C:\WINDDK\3790\src\general\ioctl是IRP的一个例子,IRP是个复杂的东西,我认为,我想过些日子完全弄懂了在说吧。