分类:
2010-06-01 14:33:41
本系列文章为业余编程爱好者而写,仅仅作为初学者的一个借鉴,真正的精华存在于参考资料*中。知识
的积累将经历从薄到厚,再从厚到薄的反复过程,为了打下牢固的基础,请读者务必在阅读本文的基础上花费必要的时间完成参考资料。
笔者的实践环境为:
硬件:P35 Motherboard & ICH9 chip,Pentium Dual Cpu E2160 1.8g, DDR2
1g
软件:Windows XP2、VS 2005、Visual AssistX、DriverStudio
3.2、MICROSOFT.WINDOWS.SERVER.V2003.IFS.DDK、Windbg
6.8.0004.0,请安装好用于调试的虚拟机并配置好调试环境。
参考资料*:
1.《Programming the Microsoft Windows driver
model》第一版(当前阶段主要阅读资料,在先前的基础上,本次需要完成前八章)
2.《Windows NT File System Internals - A Developers
Guide》(资料1理解后可阅读,为后续章节做准备)
3.《OSR White Papers》
4、《Intel 64 and IA-32 Architectures Software Developer s Manual
Volume 3A/B System Programming Guide》
5.WinXXX部分源代码…
6.《Kernel Debugging with WinDbg》(随WinDbg软件附带的文档)
阅读基础:了解计算机结构体系、掌握用户模式下常用API调用、了解常用汇编指令。请从现在开始的一年时间里阅读50万字以上的驱动编程外文资料,彻底突
破外文关。
本章目的:初步掌握硬件驱动编程的基础知识,学习使用Windbg调试。初次阅读代码量在一万行以上的驱动程序。
int main(int argc, char* argv[])
{
char result;
asm {
mov al, 0x10
out 0x70, al
in al, 0x71
mov result, al
}
printf("%X", result);
return 0;
}
这是早已预料的结果。笔者搜索了一下,找到了《Direct Port IO and Windows
NT》,欣欣然之下禁不住想让大家知道原来“不需要”写驱动也可以访问端口了。它究竟是何方神圣?原来这是一种称之为iopm的方法,它涉及了
CPU硬件理论里关于TS段的基础知识:
通过修改I/O映射表,用户模式下的进程就具有了存取端口的权限。问题是如何修改I/O映射表?阅读了porttalk和winio的源代码后,
发现所谓的iopm还是需要通过驱动代码修改映射表,此外你还需要修改当前进程的映射表偏移地址。当我们费力的写好近千行代码,换来的好处就是真的可以在
用户模式下以较快的速度存取端口了。欣欣然的你不妨用文初给出的测试程序来检查一下成果:
如果把驱动编程想得这么的简单,那你就有些过于乐观了。现在尝试另一个端口,比如串口的0x3f8,看看有什么奇怪的事情发生?这次读出了
0xff,那么0x3f9呢,还是0xff,一直读完串口的所有端口,结果都是0xff。假如我们首先用CreateFile来打开串口,怎么读出的值不
再是0xff了,如果关闭了串口再来读一读,结果又是0xff了。Google或baidu后,发现串口原理的资料很少,换个思路来查,查询串口的主要芯
片——从8250一直搜索到16550A,这下果然搜出了有价值的资料,如《Interfacing the Serial RS232
Port》,《串行输入输出接口》,《常用的输入输出接口芯片》,《UART
内部寄存器对应端口及用途》等。但这些资料只说明了一个问题——很久很久以前,在DOS下是如何发送串口指令的——却还是没有解决Windows下的问
题。
让我们再换一下思路。从DevView里看到串口驱动的基本情况,原来串口的PDO是ACPI。
阅读《Advanced Configuration and Power Interface Specification》,可知ACPI是微软和几个厂家制定的协议,不由得想起使用WINPE启动的时候选择了ACPI,结果机子一次次的重启,本 杂牌机无论从外观还是认证标签上看极象兼容ACPI,但假货毕竟是假货。折腾了半天后,因我们的目的不在于如何写ACPI驱动,故这个问题的答案即使和它 有关,也请暂时绕过(实践会证明,很多问题都是冤家路窄,避是避不过的^_^。对ACPI调用IoCallDriver后,激活了串口)。
ULONG
SerialDbgPrintEx(IN ULONG Level, PCHAR Format, ...) // 级别,格式,不定的参数
{
va_list arglist;
ULONG rval;
ULONG Mask = 0;
ULONG cb;
UCHAR buffer[SERIAL_DBGPRINT_BUFSIZE];
RTL_QUERY_REGISTRY_TABLE paramTable[4];
ULONG zero = 0;
if (KeGetCurrentIrql() == PASSIVE_LEVEL)
{
RtlZeroMemory (¶mTable[0], sizeof(paramTable));
paramTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
paramTable[0].Name = L"SerialDebugLevelBegin";
paramTable[0].EntryContext = &SerialDebugLevel; // 保存查询结果
paramTable[0].DefaultType = REG_DWORD;
paramTable[0].DefaultData = &zero;
paramTable[0].DefaultLength = sizeof(ULONG);
paramTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT;
paramTable[1].Name = L"SerialDebugLevelEnd";
paramTable[1].EntryContext = &SerialDebugLevelEnd; //
保存查询结果
paramTable[1].DefaultType = REG_DWORD;
paramTable[1].DefaultData = &zero;
paramTable[1].DefaultLength = sizeof(ULONG);
paramTable[2].Flags = RTL_QUERY_REGISTRY_DIRECT;
paramTable[2].Name = L"SerialDebugLevelCloseHigh";
paramTable[2].EntryContext = &SerialDebugLevelCloseHigh; //
保存查询结果
paramTable[2].DefaultType = REG_DWORD;
paramTable[2].DefaultData = &zero;
paramTable[2].DefaultLength = sizeof(ULONG);
RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE |
RTL_REGISTRY_OPTIONAL,
SerialGlobals.RegistryPath.Buffer, //
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Serial
¶mTable[0], // 以全0的结尾表结束,这样一次就可以查询多个值了
NULL,
NULL);
Mask = Level;
}
else if (SerialDebugLevelCloseHigh == 1) // 不显示高irql信息
{
return STATUS_SUCCESS;
}
if (Mask < SerialDebugLevel || Mask > SerialDebugLevelEnd) {
return STATUS_SUCCESS;
}
va_start(arglist, Format); // arglist越过了格式串,指向了参数
cb = _vsnprintf(buffer, sizeof(buffer), Format, arglist);
if (cb == -1) { // 串长于buffer
buffer[sizeof(buffer) - 2] = '\n';
}
DbgPrint("IRQL %d, SERIAL: %s", KeGetCurrentIrql(), buffer);
//rval = vDbgPrintEx(DPFLTR_SERIAL_ID, Level, Format, arglist);
va_end(arglist);
rval = STATUS_SUCCESS;
return rval;
}
往注册表里添加 SerialDebugLevelBegin,SerialDebugLevelEnd,SerialDebugLevelCloseHigh三键,我们 可以更灵活的动态控制调试信息。
编译好Checked模式的串口驱动程序后,接下来需要替换掉系统自带的串口驱动WINDOWS\system32\drivers\ serial.sys(大小为59KB)。希望你首先按照自己的思路来尝试替换,如果失败了再来尝试如下的方法:
为了方便阅读代码,简单说明一下串口寄存器。端口0x3f8-0x3fe 用于微机上COM1 串行口,0x2f8-0x2fe 对应COM2 端口。DLAB(Divisor Latch Access Bit)是除数锁存访问位,是指线路控制寄存器的最高位7。
表 UART 内部寄存器对应端口及用途
序号 | 端口 |
读/写 |
条件 |
用途 |
0 |
0x3f8 (0x2f8) |
写 |
DLAB=0 |
发送保持寄存器。含有将发送的字符。 TRANSMIT_HOLDING_REGISTER |
|
|
读 |
DLAB=0 |
接收缓存寄存器。含有收到的字符。 RECEIVE_BUFFER_REGISTER |
|
|
读/写 |
DLAB=1 |
波特率因子低字节(LSB)。DIVISOR_LATCH_LSB |
1 |
0x3f9 (0x2f9) |
读/写 |
DLAB=1 |
波特率因子高字节(MSB)。DIVISOR_LATCH_MSB |
|
|
读/写 |
DLAB=0 |
中断允许寄存器。INTERRUPT_ENABLE_REGISTER |
|
|
|
|
位7-4 全0 保留不用; |
|
|
|
|
位3=1 modem 状态中断允许; |
|
|
|
|
位2=1 接收器线路状态中断允许; |
|
|
|
|
位1=1 发送保持寄存器空中断允许; |
|
|
|
|
位0=1 已接收到数据中断允许。 |
2 |
0x3fa (0x2fa) |
读 |
|
中断标识寄存器。中断处理程序用以判断此次中断是4种中的那一种。 INTERRUPT_IDENT_REGISTER |
|
|
|
|
位7-3 全0(8250不用,16550:7-6=11 允许fifo, =00 不允许fifo); |
|
|
|
|
位2-1 确定中断的优先级; |
|
|
|
|
= 11 接收状态有错中断,优先级最高; |
|
|
|
|
= 10 已接收到数据中断,优先级第2; |
|
|
|
|
= 01 发送保持寄存器空中断,优先级第3; |
|
|
|
|
= 00 modem 状态改变中断,优先级第4。 |
|
|
|
|
位0=0 有待处理中断;=1 无中断。 |
3 |
0x3fb (0x2fb) |
写 |
|
线路控制寄存器。LINE_CONTROL_REGISTER |
|
|
|
|
位7=1 除数锁存访问位(DLAB)。 =0 访问发送保持寄存器、接收缓存寄存器器或中断允许寄存器; |
|
|
|
|
位6=1 强迫SOUT送出空闲状态;0 禁止间断; |
|
|
|
|
保持奇偶位 位5=1 偶校验时,校验位=0 奇校验时,校验位=1;0 无数 |
|
|
|
|
位4,3=11 偶校验;=01 奇校验;x0 不加校验位 |
|
|
|
|
位2=1 (5位数据位时)1.5位停止位,(6,7,8位数据位时)2位停止位;=0 1位停止位; |
|
|
|
|
位1-0 数据位长度:= 00 5位数据位;= 01 6位数据位;= 10 7位数据位;= 11 8位数据位。 |
4 |
0x3fc (0x2fc) |
写 |
|
modem 控制寄存器。MODEM_CONTROL_REGISTER |
|
|
|
|
位7-5 全0 保留; |
|
|
|
|
位4=1 芯片处于循环反馈诊断操作模式;(自测试循环)SERIAL_MCR_LOOP |
|
|
|
|
位3=1 辅助用户指定输出2,允许INTRPT 到系统;SERIAL_MCR_OUT2 |
|
|
|
|
位2=1 辅助用户指定输出1,PC 机未用;SERIAL_MCR_OUT1 |
|
|
|
|
位1=1 使请求发送RTS 有效;SERIAL_MCR_RTS |
|
|
|
|
位0=1 使数据终端就绪DTR 有效。SERIAL_MCR_DTR |
5 |
0x3fd (0x2fd) |
读 |
|
线路状态寄存器。LINE_STATUS_REGISTER |
|
|
|
|
位7=0 保留; |
|
|
|
|
位6=1 发送移位寄存器为空; |
|
|
|
|
位5=1 发送保持寄存器为空,可以取字符发送; |
|
|
|
|
位4=1 接收到满足间断条件的位序列; |
|
|
|
|
位3=1 帧格式错误; |
|
|
|
|
位2=1 奇偶校验错误; |
|
|
|
|
位1=1 超越覆盖错误; |
|
|
|
|
位0=1 接收器数据准备好,系统可读取。 |
6 |
0x3fe (0x2fe) |
读 |
|
modem 状态寄存器。δ 表示信号发生变化。MODEM_STATUS_REGISTER |
|
|
|
|
位7=1 载波检测(CD)有效; |
|
|
|
|
位6=1 响铃指示(RI)有效; |
|
|
|
|
位5=1 数据设备就绪(DSR)有效; |
|
|
|
|
位4=1 清除发送(CTS)有效; |
|
|
|
|
位3=1 检测到δ 载波; |
|
|
|
|
位2=1 检测到响铃信号边沿; |
|
|
|
|
位1=1 δ 数据设备就绪(DSR); |
|
|
|
|
位0=1 δ 清除发送(CTS)。 |
#define RECEIVE_BUFFER_REGISTER???
((ULONG)((0x00)*SERIAL_REGISTER_STRIDE))
// 本文转自 C++Builder研究 -
article.asp?i=1060&d=fu044r
#define TRANSMIT_HOLDING_REGISTER?
((ULONG)((0x00)*SERIAL_REGISTER_STRIDE))
#define INTERRUPT_ENABLE_REGISTER?
((ULONG)((0x01)*SERIAL_REGISTER_STRIDE))
#define INTERRUPT_IDENT_REGISTER??
((ULONG)((0x02)*SERIAL_REGISTER_STRIDE))
#define FIFO_CONTROL_REGISTER?????
((ULONG)((0x02)*SERIAL_REGISTER_STRIDE))
#define LINE_CONTROL_REGISTER?????
((ULONG)((0x03)*SERIAL_REGISTER_STRIDE))
#define MODEM_CONTROL_REGISTER????
((ULONG)((0x04)*SERIAL_REGISTER_STRIDE))
#define LINE_STATUS_REGISTER??????
((ULONG)((0x05)*SERIAL_REGISTER_STRIDE))
#define MODEM_STATUS_REGISTER?????
((ULONG)((0x06)*SERIAL_REGISTER_STRIDE))
前几章中涉及的代码量不过千行左右,此次阅读的个别子例程代码量已达到上千行,所以要特别注意阅读的技巧。首先大略了解各文件的基本功能,
从DriverEntry起,分步阅读各个派遣例程,适时记录流程中涉及的关键函数,至少读懂全部代码的45%并攻克本文提出的问题。
如果你已经开始厌烦没完没了的书写重复的代码,那么你就应该有所理解为何微软在KMDF模型下提供现成的PNP和电源管理代码的原因了。