尝试分别使用DPMI 函数 0204h和0205h而不是 '_dos_getvect' 和 '_dos_setvect'。
程序的运行环境是 DOS32A 或 DPMI 服务器/主机。所以使用他们提供的 api 而不是使用 DOS int21h 设施。但是 DOS32A 确实会拦截 int21h 中断,因此就实模式而言,您的代码应该可以正常工作。
实际上,您所做的只是使用“_dos_getvect”和“_dos_setvect”函数为 IRQ14安装实模式中断处理程序。
通过使用 DPMI 函数,您可以为 IRQ14 安装保护模式中断处理程序,并且 DOS32a 将自动将 IRQ14 中断传递给此保护模式处理程序。
回想一下:当 IRQ 被断言时,dos 扩展器/DPMI 服务器可以处于保护模式或实模式。
这是因为您的应用程序使用了一些 DOS 或 BIOS API,因此扩展程序需要切换到实模式来执行它们并返回到保护模式以将控制权转移给您的保护模式应用程序。
DOS32a 通过分配一个实模式回调(至少对于硬件中断)来做到这一点,如果在扩展器处于实模式时断言 IRQ14,它将调用您的保护模式处理程序。
如果扩展器处于保护模式,而 IRQ14 被断言,它将自动将控制权转移到您的 IRQ14 处理程序。
但是如果你没有为你的 IRQ 安装保护模式处理程序,那么 DOS32a 将不会分配任何实模式回调,并且你的实模式 irq 处理程序可能无法获得控制。但它应该接收控制 AFAIK。
无论如何试试上面的两个功能。正如肖恩所说,并链接到以前的 int76h 中断处理程序。
简而言之:
对于 DOS32a,您不需要使用 '_dos_getvect' 和 '_dos_setvect' 函数。而是使用 DPMI 函数 0204h 和 0205h 来安装您的保护模式 IRQ 处理程序。
一个建议:在您的中断处理程序中,第一步应该是检查您的设备是否实际产生了中断,或者它是共享此 irq 的其他设备(在您的情况下为 IRQ14)。您可以通过检查设备中的“中断挂起位”来执行此操作,如果已设置,请服务您的设备并链接到下一个处理程序。如果未设置为 1,则只需链接到下一个处理程序。
已编辑:
使用最新版本的 DOS32a,而不是 OW 附带的版本。
2012-08-14 更新:
是的,您可以使用 FP_SEG 和 FP_OFF 宏来分别获取选择器和偏移量,就像在实模式下使用这些宏来获取段和偏移量一样。
您还可以使用 MK_FP 宏从选择器和偏移量创建远指针。例如。MK_FP(选择器,偏移量)。
在 C 中编写处理程序时,您应该使用“__interrupt”关键字声明您的中断处理程序。
这是一个片段:
#include <i86.h> /* for FP_OFF, FP_SEG, and MK_FP in OW */
/* C Prototype for your IRQ handler */
void __interrupt __far irqHandler(void);
.
.
.
irq_selector = (unsigned short)FP_SEG( &irqHandler );
irq_offset = (unsigned long)FP_OFF( &irqHandler );
__dpmi_SetVect( intNum, irq_selector, irq_offset );
.
.
.
或者,试试这个:
extern void sendEOItoMaster(void);
# pragma aux sendEOItoMaster = \
"mov al, 0x20" \
"out 0x20, al" \
modify [eax] ;
extern void sendEOItoSlave(void);
# pragma aux sendEOItoSlave = \
"mov al, 0x20" \
"out 0xA0, al" \
modify [eax] ;
unsigned int old76_selector, new76_selector;
unsigned long old76_offset, new76_offset;
volatile int chain = 1; /* Chain to the old handler */
volatile int tick=0; // global and volatile...
void (__interrupt __far *old76Handler)(void) = NULL; // function pointer declaration
void __interrupt __far new76Handler(void) {
tick = 5; // simple code to change the value of tick...
.
.
.
if( chain ){
// disable irqs if enabled above.
_chain_intr( old76Handler ); // 'jumping' to the old handler
// ( *old76Handler )(); // 'calling' the old handler
}else{
sendEOItoMaster();
sendEOItoSlave();
}
}
__dpmi_GetVect( 0x76, &old76_selector, &old76_offset );
old76Handler = ( void (__interrupt __far *)(void) ) MK_FP (old76_selector, old76_offset)
new76_selector = (unsigned int)FP_SEG( &new76Handler );
new76_offset = (unsigned long)FP_OFF( &new76Handler );
__dpmi_SetVect( 0x76, new76_selector, new76_offset );
.
.
笔记:
您应该首先仔细检查您要挂接的 IRQ# 是否真的分配/映射到相关 PCI 设备的中断引脚。IOWs,首先从 PCI 配置空间读取“中断线寄存器”(不是中断引脚寄存器),然后只钩住那个 irq#。在您的情况下,此寄存器的有效值是:0x00 到 0x0F(含),0x00 表示 IRQ0,0x01 表示 IRQ1,依此类推。
POST/BIOS 代码在启动时在“中断线路寄存器”中写入一个值,并且您不得不惜一切代价修改此寄存器。(当然,除非您正在处理 OS 编写器将处理的中断路由问题)
您还应该使用 DPMI 调用 0204h 获取并保存旧处理程序的选择器和偏移量,以防您链接到旧处理程序。如果没有,请不要忘记将 EOI(中断结束)发送到主 PIC 和从 PIC,以防您连接了属于从 PIC(即 INT 70h 到 77h,包括 INT 0Ah)的 IRQ,并且只发送到主 PIC万一你挂了一个属于主 PIC 的 IRQ。
在平面模型中,BASE 地址为 0,Limit 为 0xFFFFF,其中 G 位(即粒度位)=1。
基数和限制(连同段的属性位(例如G位))驻留在对应于特定段的描述符中。描述符本身位于描述符表中。
描述符表是一个数组,每个条目为 8 个字节。
选择器只是描述符表(GDT 或 LDT)中 8 字节描述符条目的指针(或索引)。所以选择器不能为 0。
请注意,16 位选择器的最低 3 位具有特殊含义,只有高 13 位用于索引描述符表中的描述符条目。
GDT = 全局描述符表
LDT = 本地描述符表
一个系统只能有一个 GDT,但可以有多个 LDT。
作为 GDT 中的条目号 0,是保留的,不能使用。AFAIK,DOS32A 不会为其应用程序创建任何 LDT,而是在 GDT 本身中简单地分配和初始化与应用程序相对应的描述符条目。
选择器不得为 0,因为当您尝试使用该选择器访问内存时,x86 架构认为 0 选择器无效;尽管您可以成功地将 0 放在任何段寄存器中,但只有当您尝试访问(读/写/执行)该段时,cpu 才会生成异常。
在中断处理程序的情况下,基地址不必为 0,即使在平面模式下也是如此。DPMI 环境必须有这样做的正当理由。毕竟,您仍然需要在 x86 架构中的某个级别解决分段问题。
PCI device config register 0x5 bit2(Interrupt Disabled) = 0
PCI device config register 0x6 bit3(Interrupt status) = 1
我认为,您的意思分别是总线主命令和状态寄存器。它们实际上驻留在 I/O 空间或内存空间中,但不在 PCI 配置空间中。因此,您可以通过 IN/OUT 或 MOV 指令直接读取/写入它们。
对于读/写,PCI 配置寄存器必须使用配置红/写方法或 PCI BIOS 例程。
笔记:
许多 PCI 磁盘控制器都有一个称为“中断启用/禁用”位的位。包含该位的寄存器通常位于 PCI 配置空间中,可以从数据表中找到。
实际上,此设置是为了将连接到 PCI 控制器的设备产生的中断“转发”到 PCI 总线。
如果通过该位禁用中断,那么即使您的设备(连接到 PCI 控制器)正在生成中断,中断也不会转发到 PCI 总线(因此 cpu 永远不会知道是否发生了中断),但是中断PCI控制器中的位(该位与'中断启用/禁用'位不同)仍然设置为通知设备(连接到PCI控制器,例如硬盘)产生了中断,以便程序可以读取该位并采取适当的行动。从编程的角度来看,它类似于轮询。
这通常只适用于非总线主机传输。
但是,您似乎正在使用总线主机传输(即 DMA),所以它不应该适用于您的情况。
但无论如何,我建议你仔细阅读 PCI 控制器的数据表,特别是寻找与中断处理相关的位/寄存器
编辑:
好吧,就应用程序级编程而言,您不需要遇到/使用 _far 指针,因为您的程序不会访问代码之外的任何内容。
但这并不完全正确,当你进行系统级编程时,你需要访问内存映射的设备寄存器、外部 ROM,或者实现中断处理程序等。
故事在这里发生了变化。段的创建,即分配描述符并获取其关联的选择器,确保即使代码中存在错误,它也不会令人讨厌地更改当前代码正在执行的特定段之外的任何内容。如果它试图这样做,cpu 将产生一个故障。因此,当访问外部设备(尤其是内存映射设备的寄存器),或访问一些 rom 数据,例如 BIOS 等时,分配一个描述符并根据您需要执行的区域设置基数和段限制是个好主意/读/写并继续。但你不一定要这样做。
一些驻留在例如 rom 中的外部代码假定它们将通过远调用调用。
正如我之前所说,在 x86 架构中,在某个级别(越往下)你需要处理分段,因为没有办法完全禁用它。但是在平面模型中,正如我上面所说,在访问外部(对您的程序)事物时,分段是作为程序员的一种帮助而存在的。但是,如果您不想这样做,则无需使用。
当调用中断处理程序时,它不知道被中断程序的基本和限制。它不知道被中断程序的段属性、限制等,我们说除了 CS 和 EIP 之外的所有寄存器都处于未定义状态 wrt 中断处理程序。因此需要将其声明为 far 函数,以表明它位于当前正在执行的程序之外的某个地方。