用DOS32 / A替换平坦存储器模式下的HW中断

时间:2012-07-24 07:27:16

标签: dos interrupt isr watcom

我有一个关于如何在平面存储模式下替换硬件中断的问题......

  1. 关于我的申请......
    • 通过合并 Watcom C DOS32 / A 创建。
    • 为在DOS模式下运行而写(不是在OS模式下)
    • 使用DOS32 / A现在我可以访问> 1M内存并分配大内存来使用...(在平面内存模式下运行 !!!)
  2. 当前问题......
    • 我想为一张PCI卡写一个ISR(中断服务程序)。因此,我需要“替换”硬件中断。
    • 实施例。 DOS中PCI卡的中断线= 0xE。这意味着该设备将通过8259的IRQ 14发出中断。
  3. 但我不知道如何在平面模式下实现替换此中断的目标?

    @资源我找到了...... - 在watcom C的库中,有一个使用_dos_getvect,_dos_setvect和_chain_intr来连接INT 0x1C的示例... 我测试了这段代码,发现没问题。但是当我将它应用于我的情况时:INT76(其中IRQ 14是“INT 0x76”< - (14-8)+ 0x70)然后什么都没发生......

    • 我检查了HW中断是否已生成,但我自己的ISR没有被调用...

    我会丢失什么吗?或者我有什么功能可以用来实现我的目标?

    =============================================== ================

    [20120809] 我尝试使用 DPMI 调用 0x204和0x205 ,但仍未调用 MyISR()。我描述了我在下面做了什么,也许你们都可以给我一些建议!

    1)使用内联汇编实现DPMI调用0x204和0x205并测试OK ...

    实施例。使用DPMI 0x204 显示16个IRQ的中断向量,然后得到( selector:offset )结果: 8:1540 (INT8),的 8:1544 (INT9),...,的 8:1560 (INT70),的 8:1564 (INT71),.. 。,的 8:157℃(INT77)

    实施例。使用DPMI 0x205 设置 IRQ14 (INT76)的中断向量,并返回CF = 0,表示成功

    2)按如下方式创建我自己的ISR MyISR():

    volatile int tick=0;  // global and volatile...
    void MyISR(void)
    {
      tick = 5;  // simple code to change the value of tick...
    }
    

    3)通过DPMI调用0x205设置中断向量:

    selector = FP_SEG(MyISR);  // selector = 0x838 here
    offset = FP_OFF(MyISR);    // offset   = 0x30100963 here
    sts = DPMI_SetIntVector(0x76, selector, offset, &out_ax);
    

    然后sts = 0(CF = 0)表示成功!

    • 这里有一个奇怪的事情是:我的应用程序运行在平面内存模型,我认为选择器应为0,对于MyISR()...但如果选择器= 0,则DPMI调用0x205然后我得到CF = 1且AX = 0x8022,表示“选择器无效”!

    4)让HW中断产生,证据是:

    • PCI设备配置寄存器0x5 bit2(中断禁用)= 0
    • PCI设备配置寄存器0x6 bit3(中断状态)= 1
    • PCI设备配置寄存器0x3C / 0x3D(中断线)= 0xE / 0x2
    • 在DOS中,中断模式为PIC模式(8259模式)和基于引脚(MSIE = 0)

    5)显示tick的值,发现它仍然是“0” ......

    因此我认为没有正确调用MyISR()......

2 个答案:

答案 0 :(得分:0)

自从我摆弄中断以来已经有一段时间了,但是这个表是一个指针,用于设置处理器应该处理中断的位置。我可以给你这个过程,但不是代码,因为我只使用过8086代码。

伪代码:

Initialize:
    Get current vector - store value
    Set vector to point to the entry point of your routine

下:

Process Interrupt:
    Your code decides what to do with data
    If it's your data:
        process it, and return
    If not:
        jump to the stored vector that we got during initialize, 
        and let the chain of interrupts continue as they normally would

最后:

Program End:
    check to see if interrupt still points to your code
        if yes, set vector back to the saved value
        if no, set beginning of your code to long jump to vector address you saved, 
            or set a flag that lets your program not process anything

答案 1 :(得分:0)

尝试使用DPMI Function 0204h0205h代替' _dos_getvect'和' _dos_setvect'分别。

程序的运行时环境是DOS32A或DPMI服务器/主机。所以使用他们提供的api而不是使用DOS int21h工具。但是DOS32A确实拦截了int21h中断,因此就实模式而言,你的代码应该可以正常工作。

实际上你所做的是你只使用' _dos_getvect'为IRQ14安装实模式中断处理程序。和' _dos_setvect'功能

通过使用DPMI函数,您可以为IRQ14安装保护模式中断处理程序,DOS32a将自动为此保护模式处理程序启用IRQ14中断。

召回:当断言IRQ时,dos扩展器/ DPMI服务器可以处于保护模式或实模式。

这是becoz您的应用程序使用一些DOS或BIOS API,因此扩展程序需要切换到实模式执行它们并返回到保护模式以将控制转移到您的保护模式应用程序。

DOS32a通过分配实模式回调(至少对于硬件中断)来实现这一点,如果在Extender处于实模式时IRQ14被置位,则调用保护模式处理程序。

如果扩展器处于保护模式,当IRQ14被置位时,它将自动将控制转移到IRQ14处理程序。

但如果您没有为IRQ安装保护模式处理程序,那么DOS32a将不会分配任何实模式回调,并且您的实模式irq处理程序可能无法获得控制权。 但它应该接受控制AFAIK。

无论如何,尝试上述两个功能。并且像Sean所说的那样将连接到之前的int76h中断处理程序。

简而言之:

如果是DOS32a,则无需使用' _dos_getvect'和' _dos_setvect'功能。而是使用DPMI功能0204h和0205h来安装保护模式IRQ处理程序。

建议:在您的中断处理程序中,第一步应该是检查您的设备是否实际生成了中断,或者是其他设备共享此irq(在您的情况下为IRQ14)。您可以通过检查'中断挂起位'来执行此操作。在您的设备中,如果已设置,请为您的设备提供服务并链接到下一个处理程序。如果未设置为1,则只需链接到下一个处理程序。

<强>编辑: 使用DOS32a的latest版本,而不是OW附带的版本。

2012-08-14更新:

是的,您可以使用FP_SEG和FP_OFF宏分别获取选择器和偏移量,就像在实模式下使用这些宏来获取段和偏移一样。

您还可以使用MK_FP宏从选择器和偏移量创建远指针。例如。 MK_FP(选择器,偏移)。

你应该用&#39;声明你的中断处理程序。 __interrupt&#39;,在C中编写处理程序时的关键字。

这是一个片段:

             #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设备的中断引脚。 IOW,首先从PCI配置空间读取&#39;中断线寄存器&#39; (非中断引脚寄存器),并仅挂起irq#。在这种情况下,该寄存器的有效值为:0x00至0x0F,包括0x00表示IRQ0,0x01表示IRQ1,依此类推。

POST / BIOS代码在启动时在“中断线路寄存器”中写入一个值,并且您不能不惜任何代价修改该寄存器。(当然,除非您正在处理中断路由问题,否则OS编写者将处理)

如果要链接到旧处理程序,还应使用DPMI调用0204h获取并保存旧处理程序的选择器和偏移量。如果没有,请不要忘记将EOI(中断结束)发送给BOTH主从PIC,以防您挂钩属于从属PIC的IRQ(即INT 70h到77h,包括INT 0Ah),并且只有如果您挂接属于主PIC的IRQ,则为主PIC。

在平面模型中,BASE地址为0,Limit为0xFFFFF,G位(即粒度位)= 1。

基数和限制(以及段的属性位(例如G位))驻留在对应于特定段的描述符中。描述符本身位于描述符表中。

描述符表是一个数组,每个条目都是8字节。

选择器只是Descriptor表(GDT或LDT)中8字节描述符条目的指针(或索引)。因此选择器CAN&#39; T为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配置空间中,可以从数据表中找到。

实际上,此设置适用于&#34;转发&#34;连接到PCI控制器的设备产生的中断到PCI总线。

如果通过该位禁止中断,那么即使您的设备(连接到PCI控制器)正在产生中断,中断也不会被转发到PCI总线(因此cpu永远不会知道是否发生了中断),但是PCI控制器中的中断位(该位与“中断使能/禁止位”不同)仍设置为通知设备(连接到PCI控制器,例如硬盘)产生中断,所以程序可以读取此位并采取适当的操作。从编程角度来看,它类似于轮询。

这通常仅适用于非总线主传输。

但是,您似乎正在使用总线主传输(即DMA),所以它不适用于您的情况。

但无论如何,我建议你仔细阅读PCI控制器的数据表,特别是寻找与中断处理相关的位/寄存器

<强>编辑:

嗯,就应用程序级编程而言,您不需要遇到/使用_far指针,因为您的程序不会访问代码之外的任何内容。

但这并不完全正确,当你进行系统级编程时,你需要访问内存映射设备寄存器,外部ROM或实现中断处理程序等。

这里的故事发生了变化。创建一个段即分配描述符并获取其关联的选择器,确保即使代码中存在错误,也不会烦恼地改变当前代码执行的特定段外部的任何内容。如果它试图这样做,cpu将产生一个错误。因此,当访问外部设备(尤其是内存映射设备的寄存器)或访问某些rom数据(例如BIOS等)时,最好根据您所在的区域分配描述符并设置基本和段限制。需要执行/读/写并继续。但你并不一定会这样做。

例如在rom中驻留的一些外部代码假设它们将通过远程调用来调用。

正如我之前所说,在x86架构中,在某种程度上(你走得越远),你需要处理分段,因为没有办法完全禁用它。 但是在平面模型中,正如我上面所说,当访问外部(与你的程序相关)时,分段作为程序员的辅助。但如果你不想这样做,你就不用了。

当调用中断处理程序时,它不知道被中断的程序的基数和限制。它不知道被中断程序的段属性,限制等,我们说除了CS和EIP之外所有寄存器都处于未定义状态和中断处理程序。因此,需要将其声明为远程函数,以指示它位于当前正在执行的程序外部的某个位置。