📄 ch10s02.html
字号:
<div class="variablelist"><dl><dt><span class="term"><span>unsigned long probe_irq_on(void);</span></span></dt><dd><p>这个函数返回一个未安排的中断的位掩码. 驱动必须保留返回的位掩码, 并且在后面传递给 probe_irq_off. 在这个调用之后, 驱动应当安排它的设备产生至少一次中断.</p></dd><dt><span class="term"><span>int probe_irq_off(unsigned long);</span></span></dt><dd><p>在设备已请求一个中断后, 驱动调用这个函数, 作为参数传递之前由 probe_irq_on 返回的位掩码. probe_irq_off 返回在"probe_on"之后发出的中断号. 如果没有中断发生, 返回 0 (因此, IRQ 0 不能探测, 但是没有用户设备能够在任何支持的体系上使用它). 如果多于一个中断发生( 模糊的探测 ), probe_irq_off 返回一个负值.</p></dd></dl></div><p>程序员应当小心使能设备上的中断, 在调用 probe_irq_on 之后以及在调用 probe_irq_off 后禁止它们. 另外, 你必须记住服务你的设备中挂起的中断, 在 probe_irq_off 之后.</p><p>short 模块演示了如何使用这样的探测. 如果你加载模块使用 probe=1, 下列代码被执行来探测你的中断线, 如果并口连接器的管脚 9 和 10 连接在一起:</p><pre class="programlisting">int count = 0;do{ unsigned long mask; mask = probe_irq_on(); outb_p(0x10,short_base+2); /* enable reporting */ outb_p(0x00,short_base); /* clear the bit */ outb_p(0xFF,short_base); /* set the bit: interrupt! */ outb_p(0x00,short_base+2); /* disable reporting */ udelay(5); /* give it some time */ short_irq = probe_irq_off(mask); if (short_irq == 0) { /* none of them? */ printk(KERN_INFO "short: no irq reported by probe\n"); short_irq = -1; } /* * if more than one line has been activated, the result is * negative. We should service the interrupt (no need for lpt port) * and loop over again. Loop at most five times, then give up */} while (short_irq < 0 && count++ < 5);if (short_irq < 0) printk("short: probe failed %i times, giving up\n", count);</pre><p>注意 udelay 的使用, 在调用 probe_irq_off 之前. 依赖你的处理器的速度, 你可能不得不等待一小段时间来给中断时间来真正被递交.</p><p>探测可能是一个长时间的任务. 虽然对于 short 这不是真的, 例如, 探测一个帧抓取器, 需要一个至少 20 ms 的延时( 对处理器是一个时代 ), 并且其他的设备可能要更长. 因此, 最好只探测中断线一次, 在模块初始化时, 独立于你是否在设备打开时安装处理(如同你应当做的), 或者在初始化函数当中(这个不推荐).</p><p>有趣的是注意在一些平台上(PoweerPC, M68K, 大部分 MIPS 实现, 以及 2 个 SPARC 版本)探测是不必要的, 并且, 因此, 之前的函数只是空的占位者, 有时称为"无用的 ISA 废话". 在其他平台上, 探测只为 ISA 设备实现. 无论如何, 大部分体系定义了函数( 即便它们是空的 )来简化移植现存的设备驱动.</p></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Doityourselfprobing.sect"></a>10.2.2.2. Do-it-yourself 探测</h4></div></div></div><p>探测也可以在驱动自身实现没有太大麻烦. 它是一个少有的驱动必须实现它自己的探测, 但是看它是如何工作的能够给出对这个过程的内部认识. 为此目的, short 模块进行 do-it-yourself 的 IRQ 线探测, 如果它使用 probe=2 加载.</p><p>这个机制与前面描述的相同: 使能所有未使用的中断, 接着等待并观察发生什么. 我们能够, 然而, 利用我们对设备的知识. 常常地一个设备能够配置为使用一个 IRQ 号从 3 个或者 4 个一套; 只探测这些 IRQ 使我们能够探测正确的一个, 不必测试所有的可能中断.</p><p>short 实现假定 3, 5, 7, 和 9 是唯一可能的 IRQ 值. 这些数实际上是一些并口设备允许你选择的数.</p><p>下面的代码通过测试所有"可能的"中断并且查看发生的事情来探测中断. trials 数组列出要尝试的中断, 以 0 作为结尾标志; tried 数组用来跟踪哪个处理实际上被这个驱动注册.</p><pre class="programlisting">int trials[] = { 3, 5, 7, 9, 0 };int tried[] = {0, 0, 0, 0, 0};int i, count = 0;/* * install the probing handler for all possible lines. Remember * the result (0 for success, or -EBUSY) in order to only free * what has been acquired */for (i = 0; trials[i]; i++) tried[i] = request_irq(trials[i], short_probing, SA_INTERRUPT, "short probe", NULL);do{ short_irq = 0; /* none got, yet */ outb_p(0x10,short_base+2); /* enable */ outb_p(0x00,short_base); outb_p(0xFF,short_base); /* toggle the bit */ outb_p(0x00,short_base+2); /* disable */ udelay(5); /* give it some time */ /* the value has been set by the handler */ if (short_irq == 0) { /* none of them? */ printk(KERN_INFO "short: no irq reported by probe\n"); } /* * If more than one line has been activated, the result is * negative. We should service the interrupt (but the lpt port * doesn't need it) and loop over again. Do it at most 5 times */} while (short_irq <=0 && count++ < 5);/* end of loop, uninstall the handler */for (i = 0; trials[i]; i++) if (tried[i] == 0) free_irq(trials[i], NULL);if (short_irq < 0) printk("short: probe failed %i times, giving up\n", count);</pre><p>你可能事先不知道"可能的" IRQ 值是什么. 在这个情况, 你需要探测所有空闲的中断, 不是限制你自己在几个 trials[]. 为探测所有的中断, 你不得不从 IRQ 0 到 IRQ NR_IRQS-1 探测, 这里 NR_IRQS 在 <asm/irq.h> 中定义并且是独立于平台的.</p><p>现在我们只缺少探测处理自己了. 处理者的角色是更新 short_irq, 根据实际收到哪个中断. short_irq 中的 0 值意味着"什么没有", 而一个负值意味着"模糊的". 这些值选择来和 probe_irq_off 相一致并且允许同样的代码来调用任一种 short.c 中的探测.</p><pre class="programlisting">irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs){ if (short_irq == 0) short_irq = irq; /* found */ if (short_irq != irq) short_irq = -irq; /* ambiguous */ return IRQ_HANDLED;}</pre><p>处理的参数在后面描述. 知道 irq 是在处理的中断应当是足够的来理解刚刚展示的函数.</p></div></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="FastandSlowHandlers.sect"></a>10.2.3. 快速和慢速处理</h3></div></div></div><p>老版本的 Linux 内核尽了很大努力来区分"快速"和"慢速"中断. 快速中断是那些能够很快处理的, 而处理慢速中断要特别地长一些. 慢速中断可能十分苛求处理器, 并且它值得在处理的时候重新使能中断. 否则, 需要快速注意的任务可能被延时太长.</p><p>在现代内核中, 快速和慢速中断的大部分不同已经消失. 剩下的仅仅是一个: 快速中断(那些使用 SA_INTERRUPT 被请求的)执行时禁止所有在当前处理器上的其他中断. 注意其他的处理器仍然能够处理中断, 尽管你从不会看到 2 个处理器同时处理同一个 IRQ.</p><p>这样, 你的驱动应当使用哪个类型的中断? 在现代系统上, SA_INTERRUPT 只是打算用在几个, 特殊的情况例如时钟中断. 除非你有一个充足的理由来运行你的中断处理在禁止其他中断情况下, 你不应当使用 SA_INTERRUPT.</p><p>这个描述应当满足大部分读者, 尽管有人喜好硬件并且对她的计算机有经验可能有兴趣深入一些. 如果你不关心内部的细节, 你可跳到下一节.</p><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Theinteranlsofinterrupthandlingonthex86.sect"></a>10.2.3.1. x86上中断处理的内幕</h4></div></div></div><p>这个描述是从 arch/i386/kernel/irq.c, arch/i386/kernel/ apic.c, arch/i386/kernel/entry.S, arch/i386/kernel/i8259.c, 和 include/asm-i386/hw_irq.h 它们出现于 2.6 内核而推知的; 尽管一般的概念保持一致, 硬件细节在其他平台上不同.</p><p>中断处理的最低级是在 entry.S, 一个汇编语言文件处理很多机器级别的工作. 通过一点汇编器的技巧和一些宏定义, 一点代码被安排到每个可能的中断. 在每个情况下, 这个代码将中断号压栈并且跳转到一个通用段, 称为 do_IRQ, 在 irq.c 中定义.</p><p>do_IRQ 做的第一件事是确认中断以便中断控制器能够继续其他事情. 它接着获取给定 IRQ 号的一个自旋锁, 因此阻止任何其他 CPU 处理这个 IRQ. 它清除几个状态位(包括称为 IRQ_WAITING 的一个, 我们很快会看到它)并且接着查看这个特殊 IRQ 的处理者. 如果没有处理者, 什么不作; 自旋锁释放, 任何挂起的软件中断被处理, 最后 do_IRQ 返回.</p><p>常常, 但是, 如果一个设备在中断, 至少也有一个处理者注册给它的 IRQ. 函数 handle_IRQ_event 被调用来实际调用处理者. 如果处理者是慢速的( SA_INTERRUPT 没有设置 ), 中断在硬件中被重新使能, 并且调用处理者. 接着仅仅是清理, 运行软件中断, 以及回到正常的工作. "常规工作"很可能已经由于中断而改变了(处理者可能唤醒一个进程, 例如), 因此从中断中返回的最后的事情是一个处理器的可能的重新调度.</p><p>探测 IRQ 通过设置 IRQ_WAITING 状态位给每个当前缺乏处理者的 IRQ 来完成. 当中断发生, do_IRQ 清除这个位并且接着返回, 因为没有注册处理者. probe_irq_off, 当被一个函数调用, 需要只搜索不再有 IRQ_WAITING 设置的 IRQ.</p></div></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="ImplementingaHandler.sect"></a>10.2.4. 实现一个处理</h3></div></div></div><p>至今, 我们已学习了注册一个中断处理, 但是没有编写一个. 实际上, 对于一个处理者, 没什么不寻常的 -- 它是普通的 C 代码.</p><p>唯一的特别之处是一个处理者在中断时运行, 因此, 它能做的事情遭受一些限制. 这些限制与我们在内核定时器上看到的相同. 一个处理者不能传递数据到或者从用户空间, 因为它不在进程上下文执行. 处理者也不能做任何可能睡眠的事情, 例如调用 wait_event, 使用除 GFP_ATOMIC 之外任何东西来分配内存, 或者加锁一个旗标. 最后, 处理者不能调用调度.</p><p>一个中断处理的角色是给它的设备关于中断接收的回应并且读或写数据, 根据被服务的中断的含义. 第一步常常包括清除接口板上的一位; 大部分硬件设备不产生别的中断直到它们的"中断挂起"位被清除. 根据你的硬件如何工作的, 这一步可能需要在最后做而不是开始; 这里没有通吃的规则. 一些设备不需要这步, 因为它们没有一个"中断挂起"位; 这样的设备是一少数, 尽管并口是其中之一. 由于这个理由, short 不必清除这样一个位.</p><p>一个中断处理的典型任务是唤醒睡眠在设备上的进程, 如果中断指示它们在等待的事件, 例如新数据的到达.</p><p>为坚持帧抓取者的例子, 一个进程可能请求一个图像序列通过连续读设备; 读调用阻塞在读取每个帧之前, 而中断处理唤醒进程一旦每个新帧到达. 这个假定抓取器中断处理器来指示每个新帧的成功到达.</p><p>程序员应当小心编写一个函数在最小量的时间内执行, 不管是一个快速或慢速处理者. 如果需要进行长时间计算, 最好的方法是使用一个 tasklet 或者 workqueue 来调度计算在一个更安全的时间(我们将在"上和下半部"一节中见到工作如何被延迟.).</p><p>我们在 short 中的例子代码响应中断通过调用 do_gettimeofday 和 打印当前时间到一个页大小的环形缓存. 它接着唤醒任何读进程, 因为现在有数据可用来读取.</p><pre class="programlisting">irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct timeval tv; int written; do_gettimeofday(&tv); /* Write a 16 byte record. Assume PAGE_SIZE is a multiple of 16 */ written = sprintf((char *)short_head,"%08u.%06u\n", (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec)); BUG_ON(written != 16); short_incr_bp(&short_head, written); wake_up_interruptible(&short_queue); /* awake any reading process */ return IRQ_HANDLED; }</pre><p>这个代码, 尽管简单, 代表了一个中断处理的典型工作. 依次地, 它称为 short_incr_bp, 定义如下:</p><pre class="programlisting">static inline void short_incr_bp(volatile unsigned long *index, int delta){ unsigned long new = *index + delta; barrier(); /* Don't optimize these two together */ *index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;}</pre>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -