最好的参考是原始英特尔文档,可在此处获得: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 可能会在发出sti 或iret 指令后立即重新中断。因此,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();
}
更新:
您的后续问题:
- 为什么在视频控制器寄存器上使用中断禁用而不是屏蔽 8259 的中断启用位?
- 当您执行 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
您的新问题:
- 我认为您在
write_port(8259_interrupt_enable &= ~VIDEO_IRR_PIN) 中缺少目标地址。不是吗?
- IRR 寄存器是只读的还是可读写的?如果是第二种情况,写进去的目的是什么?
- 中断向量存储为逻辑地址还是物理地址?
回答问题(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 或页表具有一对一映射] 是“操作系统如何设置?”的问题