ldd3 read note Capter 10
时间:2009-08-06 来源:hylpro
2009.7.2 CHAPTER 10 Interrupt Handling 并口中断 并 口中断的作用是让打印机可以通知lp驱动, 已经做好接受下一个字符的准备.port2的bit 4 (0x37a,0x27a)是中断使能. 位. 一但中断使能后, 当pin10 (status port bit 6) (ACK bit) 从low变成high, 就会触发一个中断. 最简单的办法就是吧pin9 和 pin 10 链接起来. 然后可以向 /dev/short0写入二进制数据来触发中断(注意,ascii不成, ascii最高bit都是0). *<linux/interrupt.h> int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *dev_name, void *dev_id); //返回0代表成功, 负值代表失败 //dev_id是共享中断的时候使用的,建议无论共享与否,都把它指向device structure flag: SA_INTERRUPT : 中断过程中禁止其他中断 SA_SHIRQ : 中断可以被共享 SA_SAMPLE_RANDOM : 可以给/dev/random and /dev/urandom贡献熵.(中断时间不确定) void free_irq(unsigned int irq, void *dev_id); 如 果不能共享中断, 在模块初始化的时候安装中断并不是一个好注意, 这样有时候会浪费中断资源. 最好在第一次打开的时候(在中断产生前)挂接中断, 在最后一次释放后释放中断. 但是自己要有统计计数. (short呢,不在open内申请中断, 而是在初始化的时候) if (short_irq >= 0) { result = request_irq(short_irq, short_interrupt, SA_INTERRUPT, "short", NULL); if (result) { printk(KERN_INFO "short: can't get assigned irq %i\n",short_irq); short_irq = -1; } else { /* actually enable it -- assume this *is* a parallel port */ outb(0x10,short_base+2); } } int can_request_irq(unsigned int irq, unsigned long flags); //是否被占用 一般情况下kernel总是尝试cpu0处理中断(一般情况下是cpu0处理中断),保证cach locality. /proc/interrupts #体系结构无关 cat /proc/stat #特定体系结构 intr 5167833 5154006 2 0 2 4907 0 2 68 4 0 4406 9291 50 0 0 /proc/stat 这个intr的统计是统计所有中断历史次数. 而/proc/interrupts则没有当前未使用的中断的统计. Autodetecting the IRQ Number *short操控的LPT还是有默认值可以用的. if (short_irq < 0) /* not yet specified: force the default on */ switch(short_base) { case 0x378: short_irq = 7; break; case 0x278: short_irq = 2; break; case 0x3bc: short_irq = 5; break; } *有些设备知道自己的irq, 可以从寄存器或者PCI内读出来 (PCI是bios或者linux分配好的) *最后的自动检测就是让设备产生中断,看看那条线active了 Kernel-assisted probing 仅对非共享中断有效. <linux/interrupt.h> unsigned long probe_irq_on(void); //返回一个未分配的中断位图 产生一个中断,(记着要关闭.) int probe_irq_off(unsigned long); //传入未分配中断位图, 返回>0是探测到的irq, =0是没有中断, <0是多个中断 记着处理处于pending状态的设备 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); 建议就是模块加载的时候probe一下就成了. Do-it-yourself probing 也就3,5,7.9几个. 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; } Fast and Slow Handlers SA_INTERRUPT: 禁止其他中断. 建议快速处理的中断使用.(这时,其他cpu还是可以处理其他中断的). 除非理由充分,不要用这个标记. Implementing a Handler *不能访问user space *不能睡眠(不能lock, 不能kmalloc without GFP_ATOMIC) *不能调用schedule 中断任务,开始是处理..., 最后如果设备有 interrupt pending bit, 要clear掉,否则不会产生下一个中断. 但是并口是没有pending的不用ack设备. 一个中断的典型任务就是唤醒在队列上等待数据的进程. 如果需要大量计算, 还是推迟到tasklet或者workqueue吧. 没有barrier, 编译器会把new直接优化掉, 直接更新index,然后再根据条件调整index. 这样就有一个窗口, index的值是错误的. 这个也算是lock free了. short提供/dev/shortprint可以驱动一个真正的打印机. Handler Arguments and Return Value irq, reg就不提了. dev_id就是request的时候传入的. 如果共享中断的设备, 这个dev_id可以指向device结构. 中断里就可以直接访问这个个结构了. static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct sample_dev *dev = dev_id; /* now `dev' points to the right hardware item */ /* .... */ } static void sample_open(struct inode *inode, struct file *filp) { struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev); request_irq(dev->irq, sample_interrupt, 0 /* flags */, "sample", dev /* dev_id */); /*....*/ return 0; } 如果确实是设备发生中断, 需要返回IRQ_HANDLED,否则返回IRQ_NONE. 有个宏可以利用: IRQ_RETVAL(handled) 如果不知道是否是自己设备产生的,就返回IRQ_HANDLED. Enabling and Disabling Interrupts 禁止中断不是互斥手段,极少使用. driver用spinlock的时候一般要禁止中断,以免造成死锁(肯定是和进程/softirq用同一个spin了...). Disabling a single interrupt 禁止什么中断都极少用.特别是共享中断,这么做是不成的. *<asm/irq.h> void disable_irq(int irq); void disable_irq_nosync(int irq); void enable_irq(int irq); 可 以嵌套使用,两次disable需要两次enable...., 这些函数操作PIC/APIC来禁止单个中断pin. disable irq会等待当前正在运行的中断. 如果进程在持有资源的的情况下(如spin), 调用这个函数,可能引起死锁. disable_irq_nosync 不会等待, 但是仍有部分竞争窗口存在. plip 的例子 plip device 使用串口模拟网卡. 在接受报文的时候禁止中断. 有个模式的plip最多一次接受5bit(因为打印机电缆的原因),有一个bit作为handshake. 下图是这种NULL电缆. 接受数据和组合niblle参考plip_receive() (当然从status寄存器中读取数据了). 在NB0和NB1的数据有效bit是不同的,一切为了写程序方便. 发送的时候第一次触发对方的ack, 然后发送nb的时候中断被禁止, ack作为数据的一个bit. Disabling all interrupts void local_irq_save(unsigned long flags); void local_irq_disable(void); void local_irq_restore(unsigned long flags); void local_irq_enable(void); 在2.6中全系统的中断什么时候都不能关闭.... Top and Bottom Halves 典型的top half 将数据存入设备的buffer,然后调度后半部分就结束了. bottom部分做唤醒进程, 开始下次io的各种工作. bottom有两种技术, tasklet和workqueu. 前者必须全程atomic,后者容许睡眠. Tasklets *多次调度一次运行(在运行前) *在一个软中断环境中运行 *同一个tasklet不会同时运行 *可以和另一个tasklet同时运行 *和调度的cpu在同一个cpu运行: 中断不会被自己的tasklet打断, 但是tasklet可以被自己的中断打断 void short_do_tasklet(unsigned long); DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0); irqreturn_t short_tl_interrupt(int irq, void *dev_id, struct pt_regs *regs) { do_gettimeofday((struct timeval *) tv_head); /* cast to stop 'volatile' warning */ short_incr_tv(&tv_head); tasklet_schedule(&short_tasklet); short_wq_count++; /* record that an interrupt arrived */ return IRQ_HANDLED; } void short_do_tasklet (unsigned long unused) { int savecount = short_wq_count, written; short_wq_count = 0; /* we have already been removed from the queue */ /* * The bottom half reads the tv array, filled by the top half, * and prints it to the circular text buffer, which is then consumed * by reading processes */ /* First write the number of interrupts that occurred before this bh */ written = sprintf((char *)short_head,"bh after %6i\n",savecount); short_incr_bp(&short_head, written); /* * Then, write the time values. Write exactly 16 bytes at a time, * so it aligns with PAGE_SIZE */ do { written = sprintf((char *)short_head,"%08u.%06u\n", (int)(tv_tail->tv_sec % 100000000), (int)(tv_tail->tv_usec)); short_incr_bp(&short_head, written); short_incr_tv(&tv_tail); // } while (tv_tail != tv_head); wake_up_interruptible(&short_queue); /* awake any reading process */ } Workqueues *运行在一个特殊内核进程 *不能访问user space static struct work_struct short_wq; /* this line is in short_init( ) */ INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL); irqreturn_t short_wq_interrupt(int irq, void *dev_id, struct pt_regs *regs) { /* Grab the current time information. */ do_gettimeofday((struct timeval *) tv_head); short_incr_tv(&tv_head); /* Queue the bh. Don't worry about multiple enqueueing */ schedule_work(&short_wq); short_wq_count++; /* record that an interrupt arrived */ return IRQ_HANDLED; } Interrupt Sharing PCI需要共享中断, linux提供了在所有bus上共享中断的手段,在ISA上也提供了共享. *必须传递SA_SHIRQ *dev_id必须唯一, 在共享的情况下为NULL显然不合适 *在中断是无人使用或者大家都是共享的情况下可以注册成功 *需要快速判断是不是自己设备产生了中断, 不是则返回IRQ_NONE(每个共享的handler都得到调用) *共享中断不能使用内核提供的机制进行probe, 不过这种不能提供irq却要共享的硬件似乎没有 *不要禁止irq, 这个irq不是你自己的了 irqreturn_t short_sh_interrupt(int irq, void *dev_id, struct pt_regs *regs) { int value, written; struct timeval tv; /* If it wasn't short, return immediately */ value = inb(short_base); if (!(value & 0x80)) return IRQ_NONE; /* clear the interrupting bit */ outb(value & 0x7F, short_base); //假定9,10pin被跳线短接 /* the rest is unchanged */ do_gettimeofday(&tv); written = sprintf((char *)short_head,"%08u.%06u\n", (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec)); short_incr_bp(&short_head, written); wake_up_interruptible(&short_queue); /* awake any reading process */ return IRQ_HANDLED; } 注意: 真正的打印机不能辨识是否自己发出了中断, 不支持中断共享. Interrupt-Driven I/O driver buffer 可以隔离read/write和真正的数据接受.一个中断驱动的io,设备应该有如下能力: *在数据到达可以被cpu接受的时候发出中断, 然后使用io/内存映射或者DMA来进行数据传输. *发送成功或者可以接受新的数据的时候发出中断. shortprint driver *shortprint driver maintains a one-page circular output buffer *write 函数不进行任何I/O, 只是吧数据写入上述buffer shortp_write core: *有sem 保护这段代码 *使用workqueue向设备发送数据, 访问shortp_output_active 用spinlock来保护. static void shortp_start_output(void) { if (shortp_output_active) /* Should never happen */ return; /* Set up our 'missed interrupt' timer */ shortp_output_active = 1; shortp_timer.expires = jiffies + TIMEOUT; add_timer(&shortp_timer); /* And get the process going. */ queue_work(shortp_workqueue, &shortp_work); } *中断有可能丢失, 用了一个保活timer 下面代码是workqueue core: 下面是写一个字符到打印机 static irqreturn_t shortp_interrupt(int irq, void *dev_id, struct pt_regs *regs) { if (! shortp_output_active) return IRQ_NONE; /* Remember the time, and farm off the rest to the workqueue function */ do_gettimeofday(&shortp_tv); queue_work(shortp_workqueue, &shortp_work); return IRQ_HANDLED; } |
相关阅读 更多 +