Intel 8259 PIC - 确认中断

Intel 8259 PIC - Acknowledge interrupt

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

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

这里有问题:

对不起我的英语。

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

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

在连接到 IRR ["interrupt request register"] 引脚的中断设备发出中断请求后,8259 将通过发出 INTR 然后将向量置于CPU.

生成的三个 INTA 周期内的总线

在给定设备声明 IRR 后,8259 的 IS ["in-service"] 寄存器与 IRR 引脚号的掩码进行或运算。 IS 是优先事项 select。设置 IS 位后,其他优先级较低的中断设备[或原始中断设备]将不会导致INTR/INTA到CPU的循环。必须首先清除 IS 位。这些中断保持 "pending".

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

EOI 操作就是允许其他设备在 ISR 处理当前设备时引起中断。 EOI 不会清除中断设备

清除中断设备必须由 ISR 使用设备为此目的而具有的任何设备特定寄存器来完成。通常,这是一个 "pending interrupt" 寄存器 [可以是 1 位宽]。大多数 H/W 使用两个与中断相关的寄存器,另一个是 "interrupt enable" 寄存器。

使用电平触发中断,如果 ISR 清除设备,当 ISR 向 8259 发出 EOI 命令时,8259 将[尝试]重新中断CPU 将矢量用于 same 设备用于 same 条件。 CPU 可能会在发出 stiiret 指令后立即重新中断。因此,ISR 例程必须注意以正确的顺序处理事情。

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

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

控制器在其自己的特殊中断源寄存器中将这些作为位掩码呈现,我们称之为 vidintr_pend。我们称中断使能寄存器为vidintr_enable.

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

只要 vidpend 为 non-zero,视频控制器就会断言其 IRR 引脚。由于我们是水平触发的,因此 CPU 可能是 re-interrupted.

这是一个 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),我们应该两个但不一定在同一个地方。它们看起来相似,但工作方式略有不同。

我们更改了视频控制器驱动程序中的视频控制器寄存器[因为它是 "understands" 视频控制器寄存器的唯一位置。

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

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

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

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

在这里,"containing OS" 可能是一个完整的 OS,就像 Linux [我最熟悉的]。或者,它可能只是一个 R/T 执行程序 [或引导 rom--我已经写了一些],它有一些通用的设备处理框架和 "helper" 设备驱动程序的功能。

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

也有可能设备连接到 "nested" 或 "cascaded" 中断控制器。 IIRC [咨询文件],可以有一个 "master" 8259 和 [最多] 8 个 "slave" 8259。每个从机 8259 连接到主机的一个 IRR 引脚。然后,将设备连接到从 IRR 引脚。对于满载系统,您可以有 256 个中断设备。而且,主机可以在某些 IRR 引脚上使用从机 8259,在其他引脚上使用真实设备 ["hybrid" 拓扑]。

通常,只有 OS 知道足以处理这个问题。在实际系统中,设备驱动程序可能根本不会接触 8259。 non-specific EOI 可能会在 进入设备的 ISR 之前发送到 8259 。而且,OS 将处理完整的 "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 处理可能会针对 next/upcoming field/frame 对设备进行编程,并且 必须 在垂直消隐间隔内完成此操作。他们必须这样做,即使这意味着 "excessive" 延迟其他 ISR。

请注意,ISR "racing" 在消隐间隔结束之前完成。不是最好的设计。我不得不编写这样一个 controller/device。对于版本 2,我们更改了设计,因此设备寄存器为 double-buffered.

这意味着我们可以在 [更长] 帧 0 显示期间随时为帧 1 设置寄存器。在第 1 帧的 VSTART 处,视频硬件会立即 clock-in/save double-buffered 值,然后 CPU 可以在第 1 帧显示期间随时为第 2 帧设置。依此类推。 .

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

处理的

在驱动程序示例中,我稍微调整了顺序以防止无限堆叠,并根据我的问题 (1) 的答案添加了一些额外信息。也就是说,它 [粗略地] 显示了使用或不使用 OS.

可以做什么
// 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

旁注:常见问题解答[和电子邮件地址]中的"where to get"信息不再有效。而且,直到我发布常见问题解答并开始收到 "spam" 的全部影响 ;-)

而且,irqtune 甚至激怒了 Linus。不是因为它 没有 起作用,而是因为它 起作用了 : https://lkml.org/lkml/1996/8/23/19 IMO,如果他阅读了常见问题解答,他就会明白为什么 [因为 irqtune 对 R/T 人来说是标准的东西。


更新#2

你的新问题:

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

回答问题 (3):不,不是真的 [即使看起来是这样]。代码片段是 "pseudo code" [不是纯 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。

软件无法访问 all 处的 IRR。 (即)8259 中没有 no 端口来读取或写入 IRR 值。 IRR 完全 8259 内部。

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

因为视频控制器物理上 连接到给定的 IRR 引脚,对于给定的 PC 板来说它总是相同的。 [在较旧的 non-PnP 系统上] 的软件无法对此进行探测。即使在较新的系统上,8259 也不知道 PnP,所以它仍然是硬连线的。视频控制器驱动程序程序员必须 "know" 正在使用的 IRR 引脚 [通过咨询 "spec sheet" 或控制器 "architecture reference manual"]。


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

初始化 8259 时,ICW2 ("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.

这只是一个向量 number [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 指令上进行物理地址转换。翻译是否是 one-to-one [MMU not enabled or page tables have one-to-one mapping] 是 "How has the OS set things up?"

的问题

严格来说,没有这回事 作为 "acknowledge an interrupt to device"。 ISR 应该做的事情是处理 中断条件。例如,如果 UART 请求中断,因为它 有传入数据,那么你应该阅读 传入的数据。在读取操作之后, UART不再有传入的数据,那么自然 它停止声明 IRQ 线。或者, 如果您的程序不再需要读取 数据并想停止通信,它 只会通过屏蔽接收器中断 UART 寄存器,再一次,UART 将停止断言 IRQ 线。如果设备 只是想给你一些状态变化的信号, 那么你应该阅读新的状态,以及 设备会知道你有一个最新的 状态并将释放 IRQ 线。

所以,简而言之:通常没有任何特定于设备的 承认程序。您需要做的就是 服务中断条件,之后, 这种情况将消失,使 中断请求。