Intel 8259 PIC -确认中断



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

当8个中断中的一个发生时,PIC仅断言连接到CPU的INTR线。现在PIC等待CPU直到INTA被断言。此时,PIC选择具有最高优先级的中断(取决于引脚数),然后将其中断向量发送到数据总线。我遗漏了一些时间,但我想现在已经不重要了。

问题如下:

  • 引起中断的整个设备如何知道他的中断请求已被接受,是否可以完成中断请求?我读了8259,但是没有找到。
  • 接收中断的确认设备是否在ISR中执行?

对不起,我的英语不好。

最好的参考是原始英特尔文档,可在这里:https://pdos.csail.mit.edu/6.828/2012/readings/hardware/8259A.pdf它有这些模式的全部细节,设备如何操作,以及如何对设备进行编程。

警告:我有点生疏了,因为我已经很多年没有给8259编程了,但我将根据你的要求试着解释一下。

在中断设备连接到IRR["中断请求寄存器"]引脚后,断言了中断请求,8259将通过断言INTR将其传递给CPU,然后在CPU生成的三个INTA周期内将向量放置在总线上。

在给定设备断言IRR后,8259的IS["在用"]寄存器被IRR引脚编号的掩码激活。IS是一个优先选择。当IS位被设置时,其他优先级较低的中断设备[或原来的中断设备]将引起CPU的INTR/INTA周期。必须先清除IS位。这些中断保持"pending"状态。

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

EOI动作是关于允许其他设备在ISR处理当前中断时引起中断。EOI doesnot清除中断设备

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

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

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

HSTART—水平线开始
HEND—水平线结束[水平空白间隔开始]
VSTART—新视频字段/帧开始
VEND—视频字段/帧结束[垂直空白间隔开始]

控制器在它自己的特殊中断源寄存器(我们称之为vidintr_pend)中将这些表示为位掩码。我们将调用中断使能寄存器vidintr_enable

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

只要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执行器[或启动器——我已经写了一些],它有一些通用的设备处理框架,为设备驱动程序提供"助手"功能。

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

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

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

同样,在操作系统下,操作系统将调用"init"one_answers"stop"例程。通用的操作系统例程将处理8259并调用特定于设备的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的时间过长。

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

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

请注意,ISR正在"赛跑",以在落料间隔结束之前完成。不是最好的设计。我必须编写这样一个控制器/设备的程序。对于rev 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
}

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

我在90年代中期写的。它现在很少使用,并且可能在现代系统上不起作用,但是我写的FAQ中有大量关于中断设备优先级的信息。程序本身做了一个简单的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/19在我看来,如果他读过FAQ,他就会明白为什么[因为irqtune所做的是R/T家伙的标准东西]。


更新# 2

你的新问题:

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

回答问题(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中断启用寄存器,这实际上是文档中的"中断掩码寄存器"或IMR。可以使用OCW1对IMR进行读写(参见文档)。

软件无法访问所有的IRR。(即)8259中没有端口来读写IRR值。IRR完全位于8259内部。

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

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


回答问题(5):首先考虑8259是做什么的。

当8259初始化时,ICW2("初始化命令字2")由操作系统驱动程序设置。这定义了在INTR/INTA周期中8259号中断向量的一部分。在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(中断描述符表)。详细信息请参见《系统编程指南》第6章:http://download.intel.com/design/processor/manuals/253668.pdf

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

在某种意义上,所有这些地址都是总是逻辑的。并且,在每个CPU指令上,所有的逻辑地址都要经过转换为物理地址。转换是否为一对一(MMU未启用或页表有一对一映射)是"操作系统如何设置的问题?">

严格来说,没有这样的事情如"确认对设备的中断"。ISR应该做的就是处理中断条件。例如,如果UART请求中断,因为它有传入数据,那么应该读取吗输入的数据。在这个读操作之后,UART不再有传入数据,所以很自然它停止断言IRQ线。另外,如果您的程序不再需要读取数据和想要停止的通讯,它会屏蔽接收器的干扰吗UART寄存器,还是UART将停止维护伊拉克伊斯兰共和国的边界。如果设备我只是想通知你州政府有变动,那你应该看看新的状态,还有设备会知道你有一个最新的状态,并将释放IRQ线。

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

最新更新