【问题标题】:Intel 8259 PIC - Acknowledge interruptIntel 8259 PIC - 确认中断
【发布时间】:2015-12-16 21:05:57
【问题描述】:

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

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

这里有问题:

  • 导致中断的整个设备如何知道他的中断 请求被接受并且可以拉断中断请求?我阅读了大约 8259,但没有找到。
  • 是否在 ISR 中执行了接受其中断的确认设备?

对不起我的英语。

【问题讨论】:

    标签: device interrupt


    【解决方案1】:

    最好的参考是原始英特尔文档,可在此处获得:https://pdos.csail.mit.edu/6.828/2012/readings/hardware/8259A.pdf 它包含这些模式、设备如何工作以及如何对设备进行编程的完整详细信息。

    警告:我有点生疏,因为我已经很多年没有对 8259 进行编程了,但我会根据你的要求来解释一下。

    在连接到 IRR [“中断请求寄存器”] 引脚的中断设备发出中断请求后,8259 将通过断言 INTR 将其传送给 CPU,然后在三个 INTA 期间将向量放在总线上CPU 产生的周期。

    在给定设备断言 IRR 后,8259 的 IS [“in-service”] 寄存器与 IRR 引脚号的掩码进行或运算。 IS是优先选择。当 IS 位被设置时,其他较低优先级的中断设备 [或原始设备] 将不会对 CPU 造成 INTR/INTA 周期。必须先清除 IS 位。这些中断仍处于“未决”状态。

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

    EOI 操作就是允许其他设备在 ISR 处理当前设备时产生中断。 EOI 确实清除中断设备

    清除中断设备必须由 ISR 使用设备为此目的而拥有的任何设备特定寄存器来完成。通常,这是一个“挂起的中断”寄存器[可以是 1 位宽]。大多数硬件使用两个与中断相关的寄存器,另一个是“中断使能”寄存器。

    对于电平触发中断,如果 ISR 确实清除设备,当 ISR 确实向 8259 发出 EOI 命令时,8259 将 [尝试] 使用向量重新中断 CPU for same 设备用于 same 条件。 CPU 可能会在发出stiiret 指令后立即重新中断。因此,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();
    }
    

    更新:

    您的后续问题:

    1. 为什么在视频控制器寄存器上使用中断禁用而不是屏蔽 8259 的中断启用位?
    2. 当您执行 vidisr_nested(void) 函数时,它将启用嵌套相同的中断。这是真的吗?这就是你想要的吗?

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

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

    视频控制器实际上断言 8259 的 IRR 引脚来自:IRR = ((vidintr_enable & vidintr_pend) != 0)。如果我们从不设置vidintr_enable(即全为零),那么我们可以在“轮询”[非中断]模式下操作设备。

    8259 中断启用寄存器的工作方式类似,但它会屏蔽哪些 IRR [断言或未断言] 可能会中断 CPU。设备vidintr_enable 控制它是否断言 IRR。

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

    更改8259中断启用掩码应该在了解整个系统中断拓扑的地方进行。这通常由包含操作系统完成。那是因为操作系统知道 其他 设备并且可以做出最佳选择。

    这里,“包含操作系统”可能是一个完整的操作系统,如 Linux [我最熟悉的]。或者,它可能只是一个 R/T 执行程序 [或引导 rom——我已经写了一些],它具有一些通用的设备处理框架,为设备驱动程序提供“帮助”功能。

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

    设备也有可能连接到“嵌套”或“级联”中断控制器。 IIRC [查阅文档],可能有一个“主”8259 和 [最多]8 个“从”8259。每个从机 8259 连接到主机的 IRR 引脚。然后,将设备连接到从 IRR 引脚。对于一个满载的系统,您可以有 256 个中断设备。而且,主机可以在某些 IRR 引脚上拥有从属 8259,而在其他引脚上拥有真实设备 [“混合”拓扑]。

    通常,只有操作系统知道足以处理这个问题。在实际系统中,设备驱动程序可能根本不会接触 8259。 进入设备的 ISR 之前,非特定 EOI 可能已发送到 8259。而且,操作系统会处理完整的“保存状态”和“恢复状态”,而驱动程序只处理特定于设备的操作。

    另外,在操作系统下,操作系统会调用“init”和“stop”例程。用于此的通用操作系统例程将处理 8259 并调用设备特定的例程。

    例如,在 Linux [或几乎任何其他操作系统或 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 的时间过长。

    但是,一些时间要求严格的设备(如视频控制器)需要使用简单模型,防止被其他设备中断,以保证它们可以在有限的确定时间内完成。 p>

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

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

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

    使用修改后的设计,视频驱动程序完全从 ISR 中删除了设备设置。它现在是从操作系统任务级别处理的


    在驱动程序示例中,我稍微调整了顺序以防止无限堆叠,并根据我的问题 (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 年代中期写的。它现在使用较少,并且可能不适用于现代系统,但我写的常见问题解答中有大量关于中断设备优先级的信息。程序本身做了一个简单的 8259 操作。

    在线副本可在此处获得:http://archive.debian.org/debian/dists/Debian-1.1/main/disks-i386/SpecialKernels/irqtune/README.html 此存档中的某处可能有源代码。

    这是 0.2 版本的文档。网上找不到0.6版本有更好的解释,所以在这里放了文字版:http://pastebin.com/Ut6nCgL6

    旁注:FAQ [和电子邮件地址] 中的“从哪里获取”信息不再有效。而且,直到我发布了常见问题解答并开始收到 [吨] 它,我才了解“垃圾邮件”的全部影响 ;-)

    而且,irqtune 甚至引起了 Linus 的愤怒。不是因为它没有工作,而是因为它确实https://lkml.org/lkml/1996/8/23/19IMO,如果他阅读了常见问题解答,他就会明白为什么[因为 irqtune 所做的是标准的R / T家伙的东西]。


    更新 #2

    您的新问题:

    1. 我认为您在write_port(8259_interrupt_enable &= ~VIDEO_IRR_PIN) 中缺少目标地址。不是吗?
    2. IRR 寄存器是只读的还是可读写的?如果是第二种情况,写进去的目的是什么?
    3. 中断向量存储为逻辑地址还是物理地址?

    回答问题(3):不,不是真的[即使看起来如此]。代码 sn-p 是“伪代码”[不是纯 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_。如上面代码 cmets 中所述,我们正在写入 8259 中断启用寄存器,它实际上是文档中的“中断屏蔽寄存器”或 IMR。可以使用 OCW1 读取和写入 IMR(参见 doc)。

    所有软件都无法访问 IRR。 (即)8259 中有 no 端口可以读取或写入 IRR 值。 IRR 完全在 8259 内部。

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

    由于视频控制器物理上连接到给定的 IRR 引脚,因此对于给定的 PC 板,它总是相同的。该软件 [在较旧的非 PnP 系统上] 无法对此进行探测。即使在较新的系统上,8259 对 PnP 也一无所知,所以它仍然是硬连线的。视频控制器驱动程序程序员必须“知道”正在使用的 IRR 引脚 [通过查阅“规格表”或控制器“架构参考手册”]。


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

    当 8259 初始化时,ICW2(“初始化命令字 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 的“中断向量表”[IVT],使用向量号作为 IVT 的索引,以正确地将中断向量化到 ISR 例程。

    在 80386 及更高版本的体系结构中,IVT 实际上称为 IDT(“中断描述符表”)。详见《系统编程指南》第六章:http://download.intel.com/design/processor/manuals/253668.pdf

    因为,来自 IVT/IDT 的 ISR 地址是物理的还是逻辑的取决于处理器模式(例如实模式、保护模式、启用虚拟寻址的保护)。

    在某种意义上,所有这些地址总是合乎逻辑。并且,所有逻辑地址在每条 CPU 指令上都经过转换为物理地址。翻译是否是一对一的 [未启用 MMU 或页表具有一对一映射] 是“操作系统如何设置?”的问题

    【讨论】:

    • 感谢您的回答。如果我没看错,这意味着以任何一种方式在 ISR 结束时都会关闭中断请求(通过某种形式的 EOI 确认中断)?
    • 但我还有一个问题。如何处理来自PIC(自动EOI)的EOI?使用什么引脚? PIC 如何直接告诉设备它可以取消中断请求。
    • @NikNovák 我最初的解释有点离题。我已经更新并扩展了我的答案,所以,希望事情会更清楚
    • 哦,非常感谢。它对我帮助很大,但我还有两个问题。为什么在视频控制器寄存器上使用中断禁用而不是掩码 8259 的中断启用位。第二个是当你执行vidisr_nested(void)函数时,它会启用嵌套相同的中断。这是真的吗?这就是你想要的吗?
    • 非常感谢!!即使对非英语母语人士来说,你的答案也很清楚,至少对我来说是这样。要编码,我认为您在函数write_port(8259_interrupt_enable &amp;= ~VIDEO_IRR_PIN); 中缺少一个目标地址,该地址包含在STANDALONE 定义中。不是这样吗?此外,如果您回答我另外两个问题,我会很高兴。 IRR 寄存器是只读的还是可读写的?如果是第二种情况,写进去的目的是什么?第二,中断向量存储为逻辑地址还是物理地址?这是我最后的问题,我保证。
    【解决方案2】:

    严格来说,没有这种东西 作为“确认设备中断”。 ISR 应该做的事情是处理 中断条件。例如,如果 UART 请求中断,因为它 有一个传入的数据,那么你应该阅读 传入的数据。在那次读取操作之后, UART 不再有传入的数据,所以很自然 它停止断言 IRQ 线。或者, 如果您的程序不再需要阅读 数据并想停止通信,它 只会通过以下方式屏蔽接收器中断 UART寄存器,再一次,UART 将停止断言 IRQ 线。如果设备 只是想告诉你一些状态变化, 那么你应该阅读新的状态,并且 设备会知道您有最新的 状态并将释放一条 IRQ 行。

    因此,简而言之:通常没有任何特定于设备的 确认程序。你需要做的就是 服务一个中断条件,之后, 这种情况将消失,无效 中断请求。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-30
      • 1970-01-01
      • 1970-01-01
      • 2011-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多