首页 文章

Intel 8259 PIC - 确认中断

提问于
浏览
0

假设我们的CPU系统与Intel 8259可编程中断控制器完全兼容 . 所以,这个CPU当然使用向量中断 .

当发生八个中断之一时,PIC只会断言连接到CPU的INTR线 . 现在PIC等待CPU,直到INTA被断言 . 这样,PIC选择具有最高优先级的中断(取决于引脚号),然后将其中断向量发送到数据总线 . 我省略了一些时间,但我认为现在没关系 .

Here are questions:

  • 导致中断的整个设备如何知道他的中断请求被接受并且它可以解除中断请求?我读了大约8259,但我没有找到它 .

  • 是否在ISR中执行了接受中断的确认设备?

对不起我的英语不好 .

2 回答

  • 3

    最佳参考资料是原始的intel文档,可在此处获取:https://pdos.csail.mit.edu/6.828/2012/readings/hardware/8259A.pdf它包含这些模式的完整详细信息,设备如何操作以及如何对设备进行编程 .

    警告:我多年来编写了8259编程,但我会根据您的要求解释一下 .

    在连接到IRR [“中断请求寄存器”]引脚的中断设备发出中断请求后,8259将通过置位INTR将其传送给CPU,然后在由INTR生成的三个INTA周期内将向量置于总线上 . CPU .

    在给定设备断言IRR之后,8259 's IS [ 2360172 ] register is or' ed带有IRR引脚编号的掩码 . IS是优先选择 . 当IS位置1时,其他优先级较低的中断设备[或原始设备]不会导致CPU进行INTR / INTA循环 . 必须首先清除IS位 . 这些中断仍然是"pending" .

    可以通过EOI(中断结束)操作清除IS . 有多种EOI模式可以编程 . EOI可由8259在AEOI模式下生成 . 在其他模式中,EOI由ISR通过向8259发送命令手动生成 .

    EOI操作是关于允许其他设备在ISR处理当前设备时导致中断 . EOI清除了中断设备 not .

    必须由ISR使用设备为此目的设置的任何特定寄存器来清除中断设备 . 通常,这是一个“挂起中断”寄存器[可以是1位宽] . 大多数H / W使用两个与中断相关的寄存器,另一个是“中断使能”寄存器 .

    对于电平触发中断,如果ISR未清除设备,当ISR向8259发出EOI命令时,8259将[尝试]使用同一设备的向量在相同条件下重新中断CPU . 一旦发出 stiiret 指令,CPU可能会被重新中断 . 因此,ISR例程必须注意以适当的顺序处理事物 .

    考虑一个例子 . 我们有一个视频控制器,有四个中断源:

    HSTART - 水平线的开始
    HEND - 水平线的终点[水平消隐间隔的开始]
    VSTART - 开始新的视频场/帧
    VEND - 视频场/帧结束[垂直消隐间隔开始]

    控制器在它自己的特殊中断源寄存器中将它们作为位掩码提供,我们称之为 vidintr_pend . 我们将调用中断使能寄存器 vidintr_enable .

    视频控制器仅使用一个8259 IRR引脚 . CPU的视频ISR负责查询vidpend寄存器并决定做什么 .

    只要vidpend不为零,视频控制器就会置位其IRR引脚 . 由于我们处于电平触发状态,因此可能会重新中断CPU .

    以下是一个示例ISR例程:

    // video_init -- initialize controller
    void
    video_init(void)
    {
    
        write_port(...);
        write_port(...);
        write_port(...);
        ...
    
        // we only care about the vertical interrupts, not the horizontal ones
        write_port(vidintr_enable,VSTART | VEND);
    }
    
    // video_stop -- stop controller
    void
    video_stop(void)
    {
    
        // stop all interrupt sources
        write_port(vidintr_enable,0);
    
        write_port(...);
        write_port(...);
        write_port(...);
        ...
    }
    
    // vidisr_process -- process video interrupts
    void
    vidisr_process(void)
    {
        u32 pendmsk;
    
        // NOTE: we loop because controller may assert a new, different interrupt
        // while we're processing a given one -- we don't want to exit if we _know_
        // we'll be [almost] immediately re-entered
        while (1) {
            pendmsk = port_read(vidintr_pend);
            if (pendmsk == 0)
                break;
    
            // the normal way to clear on most H/W is a writeback
            // writing a 1 to a given bit clears the interrupt source
            // writing a 0 does nothing
            // NOTE: with this method, we can _never_ have a race condition where
            // we lose an interrupt
            port_write(vidintr_pend,pendmsk);
    
            if (pendmsk & HSTART)
                ...
    
            if (pendmsk & HEND)
                ...
    
            if (pendmsk & VSTART)
                ...
    
            if (pendmsk & VEND)
                ...
        }
    }
    
    // vidisr_simple -- simple video ISR routine
    void
    vidisr_simple(void)
    {
    
        // NOTE: interrupt state has been pre-saved for us ...
    
        // process our interrupt sources
        vidisr_process();
    
        // allow other devices to cause interrupts
        port_write(8259,SEND_NON_SPECIFIC_EOI)
    
        // return from interrupt by popping interrupt state
        iret();
    }
    
    // vidisr_nested -- video ISR routine that allows nested interrupts
    void
    vidisr_nested(void)
    {
    
        // NOTE: interrupt state has been pre-saved for us ...
    
        // allow other devices to cause interrupts
        port_write(8259,SEND_NON_SPECIFIC_EOI)
    
        // allow us to receive them
        sti();
    
        // process our interrupt sources
        // this can be interrupted by another source or another device
        vidisr_process();
    
        // return from interrupt by popping interrupt state
        iret();
    }
    

    UPDATE:

    你的后续问题:

    • 为什么在视频控制器寄存器上使用中断禁用而不是掩码8259的中断使能位?

    • 执行vidisr_nested(void)函数时,它将启用嵌套相同的中断 . 这是真的吗?这就是你想要的吗?


    要回答(1),我们应该做两个但不一定在同一个地方 . 它们看似相似,但工作方式略有不同 .

    我们改变了视频控制器驱动程序中的视频控制器寄存器[因为它是“理解”视频控制器寄存器的唯一地方] .

    视频控制器实际上通过以下方式断言8259的IRR引脚: IRR = ((vidintr_enable & vidintr_pend) != 0) . 如果我们从未设置 vidintr_enable (即它全为零),那么我们可以在"polled" [非中断]中操作设备模式 .

    8259中断使能寄存器的工作方式类似,但它掩盖了IRR [断言与否]可能会中断CPU . 设备 vidintr_enable 控制是否断言IRR .

    在示例视频驱动程序中,init例程启用垂直中断,但不启用水平中断 . 只有垂直中断才会产生对ISR的调用,但ISR可以/也将处理水平的[作为轮询位] .

    更改8259中断使能掩码应在了解整个系统的中断拓扑的位置进行 . 这通常由包含OS完成 . 这是因为操作系统了解其他设备并可以做出最佳选择 .

    在这里,“包含OS”可能是一个完整的操作系统,如Linux [我最熟悉] . 或者,它可能只是一个R / T执行程序[或启动rom - 我写了一些],它有一些常见的设备处理框架,其中包含设备驱动程序的“帮助”功能 .

    例如,虽然通常所有设备都有自己的IRR引脚 . 但是,通过电平触发,两个不同的设备可以共享IRR . (例如) IRR[0] = devA_IRROUT | devB_IRROUT . 通过OR门[或有线OR(?)] .

    设备也可能连接到“嵌套”或“级联”中断控制器 . IIRC [咨询文件],有可能有一个“主”8259和[最多] 8“奴隶”8259s . 每个从器件8259连接到主器件的IRR引脚 . 然后,将器件连接到从IRR引脚 . 对于满载系统,您可以拥有256个中断设备 . 并且,主设备可以在某些IRR引脚上具有从设备8259,在其他设备上具有实际设备[“混合”拓扑结构] .

    通常,只有操作系统知道足以解决这个问题 . 在实际系统中,设备驱动程序可能根本不会触及8259 . 在进入设备的ISR之前,非特定的EOI可能已被发送到8259 . 并且,操作系统将处理完整的"save state"和"restore state",并且驱动程序只处理特定于设备的操作 .

    此外,在OS下,OS将调用“init”和“stop”例程 . 一般的OS例程将处理8259并调用特定于设备的 .

    例如,在Linux [或几乎任何其他OS或R / T执行程序]下,中断序列如下所示:

    - CPU hardware actions [atomic]:
      - push %esp and flags register [has CPU interrupt enable flag] to stack
      - clear CPU interrupt enable flag (e.g. implied cli)
      - jump within interrupt vector table
    
    - OS general ISR (preset within IVT):
      - push all remaining registers to stack
      - send non-specific EOI to 8259(s)
      - call device-specific ISR (NOTE: CPU interrupt flag still clear)
      - pop regs
      - iret
    

    回答(2),是的,你是对的 . 它可能会立即中断,并可能嵌套(无限地:-) .

    如果在ISR中采取的动作简短,快速和简单(例如只输出到几个数据端口),则简单的ISR版本更有效且更可取 .

    如果所需的动作花费相对长的时间(例如,进行密集计算,或写入大量端口或存储器位置),则优选嵌套版本以防止其他设备过度延迟进入其ISR .

    但是,一些时间关键设备[如视频控制器]需要使用简单模型,防止其他设备中断,以保证它们可以在有限的确定时间内完成 .

    例如,VEND的视频ISR处理可能会为下一个/即将到来的场/帧编程设备,并且必须在垂直消隐间隔内完成此操作 . 他们必须这样做,即使这意味着其他ISR的延迟 .

    请注意,ISR在消隐间隔结束之前“竞赛”完成 . 不是最好的设计 . 我不得不编程这样的控制器/设备 . 对于rev 2,我们更改了设计,因此设备寄存器是双缓冲的 .

    这意味着我们可以在[更长]帧0显示周期内随时设置帧1的寄存器 . 在第1帧的VSTART,视频硬件将立即输入/保存双缓冲值,然后CPU可以在帧1的显示期间随时设置第2帧 . 依此类推......

    通过修改后的设计,视频驱动程序完全从ISR中删除了设备设置 . 它现在从OS任务级别处理


    在驱动程序示例中,我已经调整了一些顺序以防止无限堆叠,并根据我的问题(1)回答添加了一些额外的信息 . 也就是说,它粗略地显示了操作系统有无操作 .

    // video controller driver
    //
    // for illustration purposes, STANDALONE means a very simple software system
    //
    // if it's _not_ defined, we assume the ISR is called from an OS general ISR
    // that handles 8259 interactions
    //
    // if it's _defined_, we're showing [crudely] what needs to be done
    //
    // NOTE: although this is largely C code, it's also pseudo-code in places
    
    // video_init -- initialize controller
    void
    video_init(void)
    {
    
        write_port(...);
        write_port(...);
        write_port(...);
        ...
    
    #ifdef STANDALONE
        write_port(8259_interrupt_enable |= VIDEO_IRR_PIN);
    #endif
    
        // we only care about the vertical interrupts, not the horizontal ones
        write_port(vidintr_enable,VSTART | VEND);
    }
    
    // video_stop -- stop controller
    void
    video_stop(void)
    {
    
        // stop all interrupt sources
        write_port(vidintr_enable,0);
    
    #ifdef STANDALONE
        write_port(8259_interrupt_enable &= ~VIDEO_IRR_PIN);
    #endif
    
        write_port(...);
        write_port(...);
        write_port(...);
        ...
    }
    
    // vidisr_pendmsk -- get video controller pending mask (and clear it)
    u32
    vidisr_pendmsk(void)
    {
        u32 pendmsk;
    
        pendmsk = port_read(vidintr_pend);
    
        // the normal way to clear on most H/W is a writeback
        // writing a 1 to a given bit clears the interrupt source
        // writing a 0 does nothing
        // NOTE: with this method, we can _never_ have a race condition where
        // we lose an interrupt
        port_write(vidintr_pend,pendmsk);
    
        return pendmsk;
    }
    
    // vidisr_process -- process video interrupts
    void
    vidisr_process(u32 pendmsk)
    {
    
        // NOTE: we loop because controller may assert a new, different interrupt
        // while we're processing a given one -- we don't want to exit if we _know_
        // we'll be [almost] immediately re-entered
        while (1) {
            if (pendmsk == 0)
                break;
    
            if (pendmsk & HSTART)
                ...
    
            if (pendmsk & HEND)
                ...
    
            if (pendmsk & VSTART)
                ...
    
            if (pendmsk & VEND)
                ...
    
            pendmsk = port_read(vidintr_pend);
        }
    }
    
    // vidisr_simple -- simple video ISR routine
    void
    vidisr_simple(void)
    {
        u32 pendmsk;
    
        // NOTE: interrupt state has been pre-saved for us ...
    
        pendmsk = vidisr_pendmsk();
    
        // process our interrupt sources
        vidisr_process(pendmsk);
    
        // allow other devices to cause interrupts
    #ifdef STANDALONE
        port_write(8259,SEND_NON_SPECIFIC_EOI)
    #endif
    
        // return from interrupt by popping interrupt state
    #ifdef STANDALONE
        pop_regs();
        iret();
    #endif
    }
    
    // vidisr_nested -- video ISR routine that allows nested interrupts
    void
    vidisr_nested(void)
    {
        u32 pendmsk;
    
        // NOTE: interrupt state has been pre-saved for us ...
    
        // get device pending mask -- do this _before_ [optional] EOI and the sti
        // to prevent immediate stacked interrupts
        pendmsk = vidisr_pendmsk();
    
        // allow other devices to cause interrupts
    #ifdef STANDALONE
        port_write(8259,SEND_NON_SPECIFIC_EOI)
    #endif
    
        // allow us to receive them
        // NOTE: with or without OS, we can't stack until _after_ this
        sti();
    
        // process our interrupt sources
        // this can be interrupted by another source or another device
        vidisr_process(pendmsk);
    
        // return from interrupt by popping interrupt state
    #ifdef STANDALONE
        pop_regs();
        iret();
    #endif
    }
    

    顺便说一下,我是linux irqtune 程序的作者

    我在90年代中期写回来了 . 它现在使用较少,可能不适用于现代系统,但我编写的FAQ中包含大量有关中断设备优先级的信息 . 程序本身做了一个简单的8259操作 .

    此处提供在线副本:http://archive.debian.org/debian/dists/Debian-1.1/main/disks-i386/SpecialKernels/irqtune/README.html这个档案中的某个地方可能存在源代码 .

    那个's the version 0.2 doc. I haven' t发现版本0.6的在线副本有更好的解释,所以我在这里提出了一个文本版本:http://pastebin.com/Ut6nCgL6

    附注:FAQ [和电子邮件地址]中的"where to get"信息不再有效 . 并且,在我发布常见问题并开始获得[吨]之前,我不明白"spam"的全部影响;-)

    并且, irqtune 甚至引起了Linus的愤怒 . 不是因为它不起作用,而是因为它确实如此:https://lkml.org/lkml/1996/8/23/19 IMO,如果他已经阅读了常见问题解答,他就会理解为什么[正如irqtune所做的那样对R / T家伙来说是标准的东西] .


    UPDATE #2

    你的新问题:

    • 我认为您在 write_port(8259_interrupt_enable &= ~VIDEO_IRR_PIN) 中缺少目的地地址 . 不是吗?

    • IRR寄存器是只读还是r / w?如果是第二种情况,写入它的目的是什么?

    • 中断向量是作为逻辑地址还是物理地址存储的?


    回答问题(3):不,不是真的[即使看起来如此] . 代码片段是“伪代码”[不是纯粹的C代码],正如我在顶部的代码注释中提到的那样,从技术上讲,我已经被覆盖了 . 但是,为了更清楚,这里是[更接近]真正的C代码的样子:

    // the system must know _which_ IRR H/W pin the video controller is connected to
    // so we _hardwire_ it here
    #define VIDEO_IRR_PIN_NUMBER    3       // just an example
    #define VIDEO_IMR_MASK          (1 << VIDEO_IRR_PIN_NUMBER)
    
    // video_enable -- enable/disable video controller in 8259
    void
    video_enable(int enable)
    {
        u32 val;
    
        // NOTE: we're reading/writing the _enable_ register, not the IRR [which
        // software can _not_ modify or read]
    
        val = read_port(8259_interrupt_enable);
    
        if (enable)
            val |= VIDEO_IMR_MASK;
        else
            val &= ~VIDEO_IMR_MASK;
    
        write_port(8259_interrupt_enable,val);
    }
    

    现在,在 video_init 中,将 STANDALONE 中的代码替换为 video_enable(1) ,并将 video_stop 替换为 video_enable(0)


    关于问题(4):我们并没有真正写入IRR,即使符号中有 _IRR_ . 正如上面的代码注释中所提到的,我们正在写入8259中断使能寄存器,它实际上是文档中的"interrupt mask register"或IMR . 可以使用OCW1读取和写入IMR(参见doc) .

    软件根本无法访问IRR . (即)8259中没有端口可读取或写入IRR值 . IRR完全是8259的内部 .

    IRR引脚编号[0-7]和IMR位编号之间存在一对一的对应关系(例如,启用IRR(0),将IMR位设置为0),但软件必须知道要设置哪个位 .

    由于视频控制器物理连接到给定的IRR引脚,因此对于给定的PC板,它始终是相同的 . [旧的非PnP系统]上的软件仍然可以硬连线 . 视频控制器驱动程序编程器必须"know"正在使用什么IRR引脚[通过咨询"spec sheet"或控制器"architecture reference manual"] .


    回答问题(5):首先考虑8259的作用 .

    当初始化8259时,OSW2("initialization command word 2")由OS驱动程序设置 . 这定义了8259在INTR / INTA周期期间将出现的中断向量编号的一部分 . 在ICW2中,最重要的5位标记为 T7-T3 .

    发生中断时,这些位与中断器件的IRR引脚编号[3位宽]组合在一起形成8位中断向量编号: T7,T6,T5,T4,T3|I2,I1,I0

    例如,如果我们将0xD0放入ICW2,我们的视频控制器使用IRR引脚3,我们将 1,1,0,1,0|0,1,1 或0xD3作为8259将发送到CPU的中断向量号 .

    这只是一个向量编号[0x00-0xFF],因为8259对内存地址一无所知 . CPU采用此向量编号,并使用CPU的"interrupt vector table" [IVT],使用向量编号作为IVT的索引,将中断正确地传送到ISR例程 .

    在80386及更高版本的体系结构中,IVT实际上称为IDT("interrupt descriptor table") . 有关详细信息,请参阅"System Programming Guide",第6章:http://download.intel.com/design/processor/manuals/253668.pdf

    至于来自IVT / IDT的结果ISR地址是物理的还是逻辑的取决于处理器模式(例如,实模式,受保护模式,受启用虚拟寻址保护) .

    从某种意义上说,所有这些地址都是合乎逻辑的 . 并且,所有逻辑地址都在每个CPU指令上进行物理转换 . 翻译是一对一[MMU未启用还是页面表具有一对一映射]是"How has the OS set things up?"的问题

  • 1

    严格来说,没有“确认设备中断”这样的事情 . ISR应该做的事情是处理中断条件 . 例如,如果UART请求中断,因为它有传入数据,那么您应该读取传入数据 . 在该读操作之后,UART不再具有输入数据,因此它自然会停止断言IRQ线 . 或者,如果您的程序不再需要读取数据并想要停止通信,它只会通过UART寄存器屏蔽接收器中断,并且UART将再次停止声明IRQ线 . 如果设备只是想要通知您一些状态更改,那么您应该读取新状态,并且设备将知道您具有最新状态并将释放IRQ线 .

    因此,简而言之:通常没有任何特定于设备的确认程序 . 您需要做的就是服务中断条件,之后,该条件将消失,中断请求 .

相关问题