linux中断处理汇编入口,Linux中断处理体系结构分析(一)

中断也是一种异常,之所以把它单独的列出来,是因为中断的处理与具体的开发板密切相关,除一些必须、共用的中断(比如系统时钟中断、片内外设UART中断)外,必须由驱动开发者提供处理函数。内核提炼出中断处理的共性,搭建一个非常容易扩充的中断处理体系。

init_IRQ函数(代码在arch/arm/kernel/irq.c中)被用来初始化中断和处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数asm_do_IRQ就可以调用这些函数进行下一步处理。

Linux中断处理体系结构分析(二)

(2010-06-10 21:08)

1.中断处理的体系结构

我们知道编写设备驱动程序一定要用到中断处理函数,这在驱动程序的编写中,占据很重要的一部分。在响应一个特定的中断的时候,内核会执行一个函数,该函数

叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine

,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以

产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部

(top half),和下半部(bottom

half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的

情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》的第七章的内容。

Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。

通过irq_desc结构数组就可以了解中断处理体系结构,irq_desc结构的数据类型include/linux/irq.h

中定义,

struct irq_desc {

unsigned int        irq;

struct timer_rand_state *timer_rand_state;

unsigned int *kstat_irqs;

#ifdef CONFIG_INTR_REMAP

struct irq_2_iommu *irq_2_iommu;

#endif

irq_flow_handler_t    handle_irq; // 当前中断的处理函数入口

struct irq_chip        *chip; //低层的硬件访问

struct msi_desc        *msi_desc;

void            *handler_data;

void            *chip_data;

struct irqaction    *action;    // 用户提供的中断处理函数链表

unsigned int        status;        //IRQ状态

........

const char        *name; //中断的名称

} ____cacheline_internodealigned_in_smp;

handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数asm_do_IRQ将根据中断号调用相应irq_desc数组项中

handle_irq.handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函

数。

irq_chip结构类型也是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。

struct irq_chip {

const char    *name;

unsigned int    (*startup)(unsigned int irq);//启动中断,如果不设置,缺省为“enable

void        (*shutdown)(unsigned int irq);/*关闭中断,如果不设置,缺省为"disable"*/

void        (*enable)(unsigned int irq);// 使用中断,如果不设置,缺省为"unmask"

void        (*disable)(unsigned int irq);//禁止中断,如果不设置,缺省为“mask”

void        (*ack)(unsigned int irq);/*响应中断,通常是清除当前中断使得可以接收下一个中断*/

void        (*mask)(unsigned int irq); //屏蔽中断源

void        (*mask_ack)(unsigned int irq);//屏蔽和响应中断

void        (*unmask)(unsigned int irq);//开启中断源

void        (*eoi)(unsigned int irq);

........

const char    *typename;

};irq_desc结构中的irqaction结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断

处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的irqaction结

构链接成一个链表,以action为表头。irqation结构定义如下:

struct irqaction {

irq_handler_t handler; //用户注册的中断处理函数

unsigned long flags; //中断标志,比如是否共享中断,电平触发还是边沿触发

const char *name; //用户注册的中断名字

void *dev_id; //用户传给上面的handler的参数,还可以用来区分共享中断

struct irqaction *next; //指向下一个用户注册函数的指针

int irq; //中断号

struct proc_dir_entry *dir;

irq_handler_t thread_fn;

struct task_struct *thread;

unsigned long thread_flags;

};   irq_desc结构数组、它的成员“struct irq_chip *chip” "struct irqaction *action",这3种数据结构构成了中断处理体系的框架。下图中描述了Linxu中断处理体系结构的关系图:

26196899_1.jpg

中断处理流程如下

(1)发生中断时,CPU执行异常向量vector_irq的代码

(2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ

(3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。

(4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等

(5)handle_irq逐个调用用户在aciton链表中注册的处理函数

中断体系结构的初始化就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip等成员;用户注册中断时就是构造action链表;用户卸载中断时就是从action链表中去除不需要的项。

2.中断处理体系结构的初始化

init_IRQ函数被用来初始化中断处理体系结构,代码在arch/arm/kernel/irq.c中

153 void __init init_IRQ(void)

154 {

155 int irq;

156

157 for (irq = 0; irq < NR_IRQS; irq++)

158 irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

159

160 init_arch_irq();

161 }157~~158行 初始化irq_desc结构数组中每一项的中断状态

160行调用架构相关的中断初始化函数。对于S3C2440开发板,这个函数就是s3c24xx_init_irq,移植machine_desc结构中

的init_irq成员就指向这个函数s3c24xx_init_irq函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有

中断设置了芯片相关的数据结构(irq_desc[irq].chip),设置了处理函数入口(irq_desc[irq].handle_irq)。以

外部中断EINT4-EINT23为例,用来设置它们的代码如下:

void __init s3c24xx_init_irq(void)

534 {

535 unsigned long pend;

536 unsigned long last;

537 int irqno;

538 int i;

........

637 for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {

638 irqdbf("registering irq %d (extended s3c irq)\n", irqno);

639 set_irq_chip(irqno, &s3c_irqext_chip);

640 set_irq_handler(irqno, handle_edge_irq);

641 set_irq_flags(irqno, IRQF_VALID);

...............655 for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {

656 irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);

657 set_irq_chip(irqno, &s3c_irq_uart1);

658 set_irq_handler(irqno, handle_level_irq);

659 set_irq_flags(irqno, IRQF_VALID);

660 }

..........

676 irqdbf("s3c2410: registered interrupt handlers\n");

677 }

678

在639行set_irq_chip函数的作用就是“irq_desc[irno].chip =

&s3c_irqext_chip”,以后就可能通过irq_desc[irqno].chip结构中的函数指针设置这些外部中断的触发方式(电

平触发,边沿触发),使能中断,禁止中断。

在640行设置这些中断的处理函数入口为handle_edge_irq,即“irq_desc[irqno].handle_irq

=handle_edge_irq”.发生中断时,handle_edge_irq函数会调用用户注册的具体处理函数; 在641行设置中断标志为

“IRQF_VALID”,表示可以使用它们。init_IRQ函数执行完后,irq_desc数组项的chip,handl_irq成员都被设置

2.2 用户注册中断处理函数的过程

用户驱动程序通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的

action链表添加一个表项。原先的内核中requset_irq函数在kernel/irq/manage.c中定义,而现在2.6.32版本中,进

行了改变,可以看这篇文章http://eeek.borgchat.net/lists/newbies/msg39146.html,这里解释了,在2.6.32内核中我们可以看到找不到了request_irq函数的实现,而是用request_threaded_irq()函数给替换了。我们可以在inclue/linux/interrupt.h中找到这个函数的原型。

110 #ifdef CONFIG_GENERIC_HARDIRQS

111 extern int __must_check

112 request_threaded_irq(unsigned int irq, irq_handler_t handler,

113 irq_handler_t thread_fn,

114 unsigned long flags, const char *name, void *dev);

115

116 static inline int __must_check

117 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

118 const char *name, void *dev)

119 {

120 return request_threaded_irq(irq, handler, NULL, flags, name, dev);

121 }

123 extern void exit_irq_thread(void);

124 #else

126 extern int __must_check

127 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

128 const char *name, void *dev);

136 static inline int __must_check

137 request_threaded_irq(unsigned int irq, irq_handler_t handler,

138 irq_handler_t thread_fn,

139 unsigned long flags, const char *name, void *dev)

140 {

141 return request_irq(irq, handler, flags, name, dev);

142 }

143

144 static inline void exit_irq_thread(void) { }

145 #endif其

实具体的实现在request_threaded_irq函数中,也是在/kernel/irq/manage.c中定

义,requset_threaded_irq函数首先使用这4个参数构造一个irqaction结构,然后调用setup_irq函数将它链入链表中,

1003 int request_threaded_irq(unsigned int irq, irq_handler_t handler,

1004 irq_handler_t thread_fn, unsigned long irqflags,

1005                          const char *devname, void *dev_id)

.............

1056 action->handler = handler;

1057 action->thread_fn = thread_fn;

1058 action->flags = irqflags;

1059 action->name = devname;

1060 action->dev_id = dev_id;

1061

1062 chip_bus_lock(irq, desc);

1084 local_irq_restore(flags);

1085 enable_irq(irq);

...........

1088 return retval;

1089 }

1090 EXPORT_SYMBOL(request_threaded_irq);

setup_irq函数也是在kernel/irq.manage.c中定义,它完成如下3个主要功能

(1)将新建的irqaction结构链入irq_desc[irq]结构的action链表中,这有两种可能。

果action链表为空,则直接链入,否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致,即是否都声明

为"可共享的"(IRQF_SHARED)、是否都使用相同的触发方式,如果一致,则将新建的irqation结构链入

(2)设置irq_desc[irq]结构中chip成员的还没设置的指针,让它们指向一些默认函数

chip成员在init_IRQ函数初始化中断体系结构的时候已经设置了,这里只是设置其中还没设置的指针这通过irq_chip_set_defaults函数来完成,它在kernel/irq/chip.c中定义

296 void irq_chip_set_defaults(struct irq_chip *chip)

297 {

298 if (!chip->enable)

299 chip->enable = default_enable;//调用chip->unmask

300 if (!chip->disable)

301 chip->disable = default_disable;//此函数为空

302 if (!chip->startup)

303 chip->startup = default_startup;//调用chip->enable

310 if (!chip->shutdown)

311 chip->shutdown = chip->disable != default_disable ?

312 chip->disable : default_shutdown;

313 if (!chip->name)

314 chip->name = chip->typename;

315 if (!chip->end)

316 chip->end = dummy_irq_chip.end;

317 }

(4)启动中断

如果irq_desc[irq]结构中status成员没有被指明IRQ_NOAUTOEN(表示注册中断时不要使用中

断),还要调用chip->startup或chip->enable来启动中断,所谓启动中断通常就是使用中断。一般情况下,只有那些“可

以自动使能的”中断对应的irq_desc[irq].status才会被指明为IRQ_NOAUTOEN,所以,无论哪种情况,执行

request_irq注册中断之后,这个中断就已经被使能了。

总结一下request_irq函数注册

(1)irq_des[irq]结构中的action链表中已经链入了用户注册的中断处理函数

(2)中断的触发方式已经被设好

(3)中断已经被使能

2.3 中断的处理过程

asm_do_IRQ是中断的C语言总入口函数,它在/arch/arm/kernel/irq.c中定义,

106 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

107 {

108 struct pt_regs *old_regs = set_irq_regs(regs);

109

110 irq_enter();

111

112 /*

113 * Some hardware gives randomly wrong interrupts. Rather

114 * than crashing, do something sensible.

115 */

116 if (unlikely(irq >= NR_IRQS)) {

117 if (printk_ratelimit())

118 printk(KERN_WARNING "Bad IRQ%u\n", irq);

119 ack_bad_irq(irq);

120 } else {

121 generic_handle_irq(irq);

122 }

123

124 /* AT91 specific workaround */

125 irq_finish(irq);

126

127 irq_exit();

128 set_irq_regs(old_regs);

129 }

desc_hand_irq函数直接调用desc结构中的hand_irq成员函数,它就是irq_desc[irq].handle.irq

asm_do_IRQ函数中参数irq的取值范围为IRQ_EINT0~(IRQ_EINT0 + 31),只有32个取值。它可能是一个实际的中断号,也可能是一组中断的中断号。这里有S3C2440的芯片特性决定的:发生中断时,INTPND寄存器的某一位被置1,INTOFFSET寄存器中记录了是哪一位(0--31),中断向量调用asm_do_IRQ之前要把INTOFFSET寄存器的值确定irq参数。每一个实际的中断在irq_desc数组中都有一项与它对应,它们的数目不止32.当asm_do_IRQ函数参数irq表示的是“一组”中断时,irq_desc[irq].handle_irq成员函数还需要先分辨出是哪一个中断,然后调用irq_desc[irqno].handle_irq来进一步处理。

以外部中断EINT8—EINT23为例,它们通常是边沿触发

(1)它们被触发里,INTOFFSET寄存器中的值都是5,asm_do_IRQ函数中参数irq的值为(IRQ_EINTO+5),即IRQ_EINT8t23,

(2)irq_desc[IRQ_EINT8t23].handle_irq在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irq_demux_extint8.

(3)s3c_irq_demux_extint8函数的代码在arch/arm/plat-s3c24xx/irq.c中,它首先读取EINTPEND、EINTMASK寄存器,确定发生了哪些中断,重新计算它们的中断号,然后调用irq_desc数组项中的handle_irq成员函数

453 s3c_irq_demux_extint8(unsigned int irq,

454 struct irq_desc *desc)

455 {

456 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); //EINT8-EINT23 发生时,相应位被置1

457 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);//屏蔽寄存器

458

459 eintpnd &= ~eintmsk; //清除被屏蔽的位

460 eintpnd &= ~0xff; /* 清除低8位(EINT8对应位8)ignore lower irqs */

461

462 /* 循环处理所有子中断*/

463

464 while (eintpnd) {

465 irq = __ffs(eintpnd); //确定eintpnd中为1的最高位

466 eintpnd &= ~(1<

467

468 irq += (IRQ_EINT4 - 4);//重新计算中断号,前面计算出irq等于8时,中断号为

IRQ_EINT8

469 generic_handle_irq(irq);//调用这中断的真正的处理函数

470 }

471

472 }

void

(4)IRQ_EINT8--IRQ_EINT23这几个中断的处理函数入口,在init_IRQ函数初始化中断体系结构的时候已经被设置为handle_edge_irq函数,desc_handle_irq(irq,irq_desc+irq)就是调用这个函数,它在kernel/irq/chip.c中定义,它用来处理边沿触发的中断,

中断发生的次数统计

531 handle_edge_irq(unsigned int irq, struct irq_desc *desc)

532 {

533 spin_lock(&desc->lock);

534

535 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

536

537 /*

538 * If we're currently running this IRQ, or its disabled,

539 * we shouldn't process the IRQ. Mark it pending, handle

540 * the necessary masking and go out

541 */

542 if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||

543 !desc->action)) {

544 desc->status |= (IRQ_PENDING | IRQ_MASKED);

545 mask_ack_irq(desc, irq);

546 goto out_unlock;

547 }

548 kstat_incr_irqs_this_cpu(irq, desc);

549

550 /* Start handling the irq */

551 if (desc->chip->ack)

552 desc->chip->ack(irq);

553

554 /* Mark the IRQ currently in progress.*/

555 desc->status |= IRQ_INPROGRESS;

556

557 do {

558 struct irqaction *action = desc->action;

559 irqreturn_t action_ret;

560

561 if (unlikely(!action)) {

562 desc->chip->mask(irq);

563 goto out_unlock;

564 }

565

566 /*

567 * When another irq arrived while we were handling

568 * one, we could have masked the irq.

569 * Renable it, if it was not disabled in meantime.

570 */

571 if (unlikely((desc->status &

572 (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==

573 (IRQ_PENDING | IRQ_MASKED))) {

574 desc->chip->unmask(irq);

575 desc->status &= ~IRQ_MASKED;

576 }

577

578 desc->status &= ~IRQ_PENDING;

579 spin_unlock(&desc->lock);

580 action_ret = handle_IRQ_event(irq, action);

581 if (!noirqdebug)

582 note_interrupt(irq, desc, action_ret);

583 spin_lock(&desc->lock);

584

585 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

586

587 desc->status &= ~IRQ_INPROGRESS;

588 out_unlock:

589 spin_unlock(&desc->lock);

590 }

591

响应中断,通常是清除当前中断使得可以接收下一个中断,对于IRQ_EINT8~IRQ_EINT23这几个中断,desc->chip在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irqext_chip.desc->chip->ack就是s3c_irqext_ack函数,(arch/armplat-s3c24xx/irq.c)它用来清除中断

handle_IRQ_event函数来逐个执行action链表中用户注册的中断处理函数,它在kernel/irq/handle.c中定义。

do {

379 trace_irq_handler_entry(irq, action);

380 ret = action->handler(irq, action->dev_id);//执行用户注册的中断处理函数

381 trace_irq_handler_exit(irq, action, ret);

382

383 switch (ret) {

384 case IRQ_WAKE_THREAD:

385 /*

386 * Set result to handled so the spurious check

387 * does not trigger.

388 */

389 ret = IRQ_HANDLED;

390

391 /*

392 * Catch drivers which return WAKE_THREAD but

393 * did not set up a thread function

394 */

395 if (unlikely(!action->thread_fn)) {

396 warn_no_thread(irq, action);

397 break;

398 }

399

400 /*

408 if (likely(!test_bit(IRQTF_DIED,

409 &action->thread_flags))) {

410 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);

411 wake_up_process(action->thread);

412 }

413

414 /* Fall through to add to randomness */

415 case IRQ_HANDLED:

416 status |= action->flags;

417 break;

418

419 default:

420 break;

421 }

422

423 retval |= ret;

424 action = action->next; //下一个

425 } while (action);

用户注册的中断处理函数的参数为中断号irq,action->dev_id。后一个参数是通过request_irq函数注册中断时传入的dev_id参数,它由用户自己指定、自己使用,可以为空,当这个中断是“共享中断”时除外。

对于电平触发的中断,它们的irq_desc[irq].handle_irq通常是handle_level_irq函数。它也是在kernel/irq/chip.c中定义,其功能与上述handle_edge_irq函数相似,

对于handle_level_irq函数已经清除了中断,但是它只限于清除SoC内部的的信号,如果外设输入到SoC的中断信号仍然有效,这就会导致当前中断处理完成后,会误认为再次发生了中断,对于这种情况,需要用户注册的中断处理函数中清除中断,先清除外设的中断,然后再清除SoC内部的中断号。

中断的处理流程可以总结如下

(1)中断向量调用总入口函数asm_do_IRQ,传入根据中断号irq

(2)asm_do_IRQ函数根据中断号irq调用irq_desc[irq].handle_irq,它是这个中断的处理函数入口,对于电平触发的中断,这个入口函数通常为handle_level_irq,对于边沿触发的中断,这个入口通常为handle_edge_irq

(3)入口函数首先清除中断,入口函数是handle_level_irq时还要屏蔽中断

(4)逐个调用用户在irq_desc[irq].aciton链表中注册的中断处理函数

(5)入口函数是handle_level_irq时还要重新开启中断

卸载中断处理函数这通过free_irq函数来实现,它与request_irq一样,也是在kernel/irq/mangage.c中定义。

它需要用到两个参数:irq和dev_id,它们与通过request_irq注册中断函数时使用的参数一样,使用中断号irq定位action链表,再使用dev_id在action链表中找到要卸载的表项。同一个中断的不同中断处理函数必须使用不同的dev_id来区分,在注册共享中断时参数dev_id必惟一。

free_irq函数的处理过程与request_irq函数相反

(1)根据中断号irq,dev_id从action链表中找到表项,将它移除

(2)如果它是惟一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN或IRQ_DESC[IRQ].CHIP->DISABLW来关闭中断。

在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top

half),和下半部(bottom

half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》

这里主要是仿照《嵌入式Linux开发完全手册》上的例子写的,只是增加了别外两个按按键。在我的mini2440开发板上有6个按键。在上两篇文章中,主要分析了驱动中的整体的流程,现在来看一个具体的例子,是如何使用中断的。

1. 模块的初始化函数和卸载函数

/* 执行"insmod mini2440_buttons.ko"命令时就会调用这个函数*/

static int __init mini2440_buttons_init (void)

{

int ret;

/*

这里主要是注册设备驱动程序,参数为主设备号,如果BUTTON_MAJOR设为0,表示由内核自动分配主设备号,设备的名

字,file_operations结构,操作主调和号为BUTTON_MAJOR的设备文件时,就会调用mini2440_buttons_fops中

的相关成员函数*/

ret = register_chrdev(BUTTON_MAJOR,DEVICE_NAME,&mini2440_buttons_fops);

if(ret < 0)

{

printk(DEVICE_NAME "can't register major number\n");

return ret ;

}

printk(DEVICE_NAME"initialized\n");

return 0;

}

/* 执行 rmmod mini2440_buttons.ko0 命令时就会调用这个函数 */

static void __exit mini2440_buttons_exit(void)

{//卸载驱动程序

unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME);

}

//指定驱动程序的初始化函数和卸载函数

module_init(mini2440_buttons_init);

module_exit(mini2440_buttons_exit);下面这个结构体是每一个字符驱动程序都是要用到的。这里定义了应用程序可以使用的设备操作函数,只有在这个结构体中的函数,在应用程序中才可以使用,在下面的驱动程序中要实现下面的函数。

/* 这个结构是字符设备驱动程序的核心,当应用程序操作设备文件时所调用的open,read,write等函数,最终会调用这个结构中的对应函数*/

static struct file_operations mini2440_buttons_fops =

{

.owner = THIS_MODULE, //这是 个宏,指向编译模块时自动创建的_this_module变量

.open = mini2440_buttons_open,

.release = mini2440_buttons_close,

.read = mini2440_buttons_read,

};2. mini2440_buttons_open函数

在应用程序执行“open("/dev/buttons",..)"系统调用时,mini2440_buttons_open函数将被调用。这用来注册6个按键的中断处理程序

static int mini2440_buttons_open(struct inode *inode,struct file *file)

{

int i;

int err;

for (i=0;i

{ //注册中断处理函数 一共六个

err = request_irq(button_irqs[i].irq,buttons_interrupt,button_irqs[i].flags,button_irqs[i].name,(void *)&press_cnt[i]);

if (err)

break;

}

if(err) //出错处理函数,如果出错释放已经注册的中断

{

i--;

for(;i>=0;i--)

free_irq(button_irqs[i].irq,(void *)&press_cnt[i]);

return -EBUSY;

}

return 0;

}requst_irq

函数执行成功后,这6个按键所用的GPIO引脚的功能被设为外部中断,触发方式为下降沿触发,中断处理函数为buttons_interrupt.最后一

个参数“(void *)&press_cnt[i]”将在buttons_interrupt函数中用到,它用来

存储按键被按下的次数。参数button_irqs的定义如下:

struct button_irq_desc

{

int irq;//中断号

unsigned long flags; //中断标志,用来定义中断的触发方式

char *name; //中断名称

};

static struct button_irq_desc button_irqs[] =

{  //下面是按键对应的外部的中断号,触发方式,名称

{IRQ_EINT8,IRQF_TRIGGER_FALLING,"KEY0"},

{IRQ_EINT11,IRQF_TRIGGER_FALLING,"KEY1"},

{IRQ_EINT13,IRQF_TRIGGER_FALLING,"KEY2"},

{IRQ_EINT14,IRQF_TRIGGER_FALLING,"KEY3"},

{IRQ_EINT15,IRQF_TRIGGER_FALLING,"KEY4"},

{IRQ_EINT19,IRQF_TRIGGER_FALLING,"KEY5"},

};3. mini2440_buttons_close函数

mini2440_buttons_close函数的作用是用来卸载6个按键的中断处理函数代码如下:

/* 应用程序对设备文件/dev/buttons执行close(...)时。就会调用mini2440_buttons_close函数*/

static int mini2440_buttons_close(struct inode *inode,struct file *file)

{

int i;

for(i=0;i

{ //释放已注册的函数

free_irq(button_irqs[i].irq,(void *)&press_cnt[i]);

}

return 0;

}4. mini2440_buttons_read函数

中断处理函数会在press_cnt数组中记录按键被按下的次数。mini_buttons_read函数,首先判断是否按键再次按下,如果没有则休眠;否则读取press_cnt数组的数据,

/*等待队列:

当没有按键被按下时,如果有进程调用mini2440_buttons_read函数,它将休眠*/

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/*中断事件标志,中断服务程序将它置1,mini2440_buttons_read将它清0*/

static volatile int ev_press = 0;

/*应用程序对设备文件/dev/buttons执行read(...)时,就会调用mini2440_buttons_read函数*/

static int mini2440_buttons_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)

{

unsigned long err;

//如果ev_press等于0,休眠

wait_event_interruptible(button_waitq,ev_press);

ev_press = 0;// 执行到这里是ev_press肯定是1,将它清0

//将按键状态复制给用户,并清0

err = copy_to_user(buff,(const void *)press_cnt,min(sizeof(press_cnt),count));

memset((void *)press_cnt,0,sizeof(press_cnt));

return err ? -EFAULT:0;

}   wait_event_interruptible首先会判断ev_press是否为0,如果为0才会令当前进程进入休眠,否则向下继续执行,它的第一个

参数,button_waitq是一个等待的队列,在前面定义过,第二个参数ev_press用来表示中断是否已经发生,中断服务程序将它置1,如果

ev_press为0,当前进程进入休眠,中断发生时中断处理函数buttons_interrupt会把它唤醒。将press_cnt数组的内容复制到

用户空间,buff参数表示的缓冲区位于用户空间,使用copy_to_user向它赋值。

5.中断处理函数buttons_interrupt

static irqreturn_t buttons_interrupt(int irq,void *dev_id)

{

volatile int *press_cnt = (volatile int *)dev_id;

*press_cnt = *press_cnt + 1; //按键计数加1

ev_press = 1; //表示中断发生

wake_up_interruptible(&button_waitq); //唤醒休眠的进程

return IRQ_RETVAL (IRQ_HANDLED);

}

buttons_interrupt函数被调用时,第一个参数irq表示发生的中断号,第二个参数dev_id就是前面使用request_irq注册中断时传入的“&pres_cnt[i]”.

将mini2440_buttons.c放到内核源码目录drivers/char下,在drivers/char目录下生成可加载模块

mini2440_buttons.ko,把它放开开发板根文件系统的/lib/modules/2.6.22.6目录下,就可以使用"insmod

mini2440_buttons"、“rmmod mini2440_buttons.ko”命令进行加载,卸载了。

6.测试程序

编写的测试程序buttons_test.c,编译后生成可执行文件,然后把它放到开发板根文件系统/usr/bin目录下。在开发板根文件系统中建立设备文件。

#mknod /dev/buttons c 232 0然后使用“insmod mini2440_buttons”命令加载模块。执行完这条命令后可以看到在控制台中执行“cat /proc/devices”

[root@Frankzfz 2.6.32.2-FriendlyARM]$cat /proc/devices

Character devices:

1 mem

2 pty

3 ttyp

4 /dev/vc/0

4 tty

5 /dev/tty

5 /dev/console

5 /dev/ptmx

7 vcs

10 misc

13 input

14 sound

29 fb

89 i2c

90 mtd

116 alsa

128 ptm

136 pts

153 spi

180 usb

189 usb_device

204 s3c2410_serial

232 buttons  //主设备号 刚注册的设备名

252 hidraw

253 ttySDIO

254 rtc

Block devices:

1 ramdisk

256 rfd

259 blkext

31 mtdblock

44 ftl

93 nftl

96 inftl

179 mmc运行测试程序button_test后,/dev/buttons设备就会被打开,可以使用“cat /proc/interrupts”命令看到注册的6个中断了。

[root@Frankzfz /mnt]$cat /proc/interrupts

CPU0

30: 157894 s3c S3C2410 Timer Tick

42: 0 s3c ohci_hcd:usb1

43: 8 s3c s3c2440-i2c

51: 9751 s3c-ext eth0

52: 3 s3c-ext KEY0

55: 1 s3c-ext KEY1

57: 2 s3c-ext KEY2

58: 15 s3c-ext KEY3

59: 75 s3c-ext KEY4

63: 10 s3c-ext KEY5 70: 848 s3c-uart0 s3c2440-uart

71: 1476 s3c-uart0 s3c2440-uart

83: 0 - s3c2410-wdt

Err: 0第一列表示中断号,第二列表示这个中断发生的次数,第三列的文字表示这个中断的硬件访问结构“struct irq_chip *chip”的名字。它在初始化中断体系结构时指定,第四列文字表示中断的名称,它在request_irq中指定。

测试程序buttons_test.c如下

#include

#include

#include

int main(int argc, char **argv)

{

int i;

int ret;

int fd;

int press_cnt[4];

fd = open("/dev/buttons",0); // 打开设备

if (fd < 0) {

printf("Can't open /dev/buttons\n");

return -1;

}

// 这是个无限循环,进程有可能在read函数中休眠,当有按键被按下时,它才返回

while (1) {

// 读出按键被按下的次数

ret = read(fd, press_cnt, sizeof(press_cnt));

if (ret < 0) {

printf("read err!\n");

continue;

}

for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) {

// 如果被按下的次数不为0,打印出来

if (press_cnt[i])

}

}

close(fd);

return 0;

}在运行button_test时,可以以后台运行在button_test &。在开发板上按下不同的按键可以在串口上看到以下的信息。

$K1 has been pressed 1

K2 has been pressed 1

K2 has been pressed 1

K3 has been pressed 1

K3 has been pressed 5

K3 has been pressed 2

K6 has been pressed 1

K4 has been pressed 1

K4 has been pressed 1

K4 has been pressed 1

K4 has been pressed 3

K4 has been pressed 7

K4 has been pressed 2

K4 has been pressed 5

K4 has been pressed 10

K4 has been pressed 5

K5 has been pressed 1

K5 has been pressed 1

K5 has been pressed 1

K5 has been pressed 1

[3] + Done(255) ./button_test   这只是一处简单的测试程序,当有时按下一次时,也可能出现说是按下了10次,没有很精确。如果以前没有按键被按下则进行休眠状态。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/298689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

没有违反GPL,他们真的给了源码

文 | Travis出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013&#xff09;【前情回顾】国内智能设备制造商 UMIDIGI 因违反 GPLv2 协议引发争议&#xff0c;并告知开发者“想要源码上门自取”。而后&#xff0c;知名科技博主 Naomi Wu&#xff08;机械妖姬&#xff09…

有朋友问我为什么这么帅

1 2 重庆的朋友告诉我这是微辣3 我就说我只是虚胖&#xff0c;这下你相信了吧&#xff1f;4 这明明就是打情骂俏好嘛5 这还是那个平常瓶盖都拧不开的女孩子吗6 被一个机器人撩到了&#xff01;7 这是什么原理&#xff1f;你点的每个赞&#xff0c;我都认真当成了喜欢

python安装详细步骤mac_Mac安装python3的方法步骤

Python有两个版本&#xff0c;一个是2.x版&#xff0c;一个是3.x版&#xff0c;这两个版本是不兼容的。 现在 Mac 上默认安装的 python 版本为 2.7 版本&#xff0c;若 安装 新版本需要 通过 该地址进行下载&#xff1a; https://www.python.org/ftp/python/3.5.0/python-3.5.0…

在Excel中实现下拉列表选择录入

我们在用Excel录入表格数据时&#xff0c;常常会遇到某列数据的值只在几个固定值中选择一个的情况&#xff0c;比如&#xff1a;人的性别列只可能录入男或女&#xff0c;对学历列只可能录入高中、大专、本科、研究生之一等。遇到这类数据&#xff0c;如果我们手工录入&#xff…

你吃的瓜子仁,真是老奶奶磕出来的?!

全世界只有3.14 % 的人关注了爆炸吧知识本文转载自微信公众号一只学霸&#xff08;ID&#xff1a;bajie203&#xff09;萌萌不是挺爱吃瓜子仁吗有次他吃的时候大毛在旁边看着看着突然想到一个问题吓得我反思了一下自己是怎么和他们考上同个学校的没思考出来吓得我给大家写了这篇…

持续20年,一场威胁Linux存亡的诉讼终结束

文 | 局长出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013&#xff09;一场持续将近 20 年、曾被认为会威胁 Linux 存亡的诉讼终于迎来了尾声。这场诉讼开始于 2003 年&#xff0c;不过其背后的事件最早可追溯到 1998 年。当时 IBM 和 Santa Cruz Operation&#xff…

shell grep 变量_老司机给出的关于 shell 脚本的8个建议,必收!

这八个建议&#xff0c;来源于键者几年来编写 shell 脚本的一些经验和教训。事实上开始写的时候还不止这几条&#xff0c;后来思索再三&#xff0c;去掉几条无关痛痒的&#xff0c;最后剩下八条。毫不夸张地说&#xff0c;每条都是精挑细选的&#xff0c;虽然有几点算是老生常谈…

不是说好一起长大的吗?

1 仿佛闻到了嫉妒的味道&#xff01;2 阿拉&#xff1a;不是说好一起长大的吗&#xff1f;3 还有这么小的菠萝蜜&#xff1f;&#xff1f;4 你以为它是个橘子其实它并不是5 当我吃到自己喜欢吃的东西时……6 以后吃完小龙虾&#xff0c;千万别扔&#xff0c;有妙用7 这是啥玩意…

javascript:设置URL参数的方法,适合多条件查询

适用场景&#xff1a;多条件查询情况&#xff0c;如下图所示&#xff1a; 通过设置URL参数&#xff0c;再结合数据源控件设置的RUL参数&#xff0c;就能进行简单的多条件查询了。 javascript函数&#xff1a; <mce:script type"text/javascript"><!-- //设置…

SQL点滴19—T-SQL中的透视和逆透视

原文:SQL点滴19—T-SQL中的透视和逆透视透视 今天抽一点时间来看看透视和逆透视语句&#xff0c;简单的说就是行列转换。假设一个销售表中存放着产品号&#xff0c;产品折扣&#xff0c;产品价格三个列&#xff0c;每一种产品号可能有多种折扣&#xff0c;每一种折扣只对应一个…

Magicodes.IE 2.5.5.3发布

2.5.5.32021.08.27修复Append方式导出多个sheet时&#xff0c;发生“Tablename is not unique”错误&#xff0c;具体见#299。2.5.5.22021.08.24添加对Abp模块的包装&#xff0c;具体见#318。Magicodes.IE.Excel.Abp&#xff08;MagicodesIEExcelModule&#xff09;注册IExcelE…

C语言阿斯码,木叶四位上忍设定各不相同,网红负责秀操作,她只需要美就够了...

原标题&#xff1a;木叶四位上忍设定各不相同&#xff0c;网红负责秀操作&#xff0c;她只需要美就够了木叶四位上忍设定各不相同&#xff0c;网红负责秀操作&#xff0c;她只需要美就够了说道忍界网红&#xff0c;那一定就是卡卡西了。卡卡西在《火影》当中的表现俘获了大批小…

80岁COBOL码农:扶我起来,这个bug我会修!

95&#xff05;的 ATM 交易通过 COBOL 程序&#xff0c;80&#xff05;的现场交易依赖于它们&#xff0c;超过 40&#xff05;的银行仍然使用 COBOL 作为其系统的基础。由于年轻人懂 COBOL 的比较少&#xff0c;美国康涅狄格州劳工部正在召回经验丰富的退休 COBOL 人员。来源&a…

小心使用宏

开发过程中&#xff0c;会经常使用宏定义&#xff0c;偶尔还会碰到重复定义的宏&#xff0c;有些时候会造成不良影响。 见如下例子&#xff1a; Test.h #ifndef GUARD_TEST_H #define GUARD_TEST_H class CTest { public: CTest(); virtual ~CTest(); void Display(void); publ…

数据资产纳入国资保值增值考核

首先是国资云近期横空出世&#xff0c;国资云的推广预示着党政及国企未来将坚持私有云技术路线。从天津、四川等省市国资云平台的建设方式来看&#xff0c;未来党政及国企部门的业务系统上云将坚持私有云的技术路线&#xff0c;由此可能对未来国内云计算市场带来深远影响。国资…

iPhone5:4G是否进入主流的风向标?

当业内的目光不约而同地集中到苹果即将发布的iPad2的时候&#xff0c;按照惯例&#xff0c;苹果的另一款重量级产品iPhone5也会在今年登场。近日&#xff0c;国外有预测称&#xff0c;苹果的iPhone5可能会不支持 4G网络&#xff0c;这多少令业内感到意外和失望&#xff0c;并由…

一滴水从高处落下来,会不会砸死人?

全世界只有3.14 % 的人关注了爆炸吧知识有一个相当古老的段子是这么说的&#xff1a;为了涨姿势&#xff0c;我加入一个物理博士群&#xff0c;见到有人问&#xff1a;一滴水从很高的地方落下来&#xff0c;会不会弄死人&#xff1f;群里一下就热闹起来&#xff0c;各种公式&am…

给ubuntu换个图标主题(icon theme)

2019独角兽企业重金招聘Python工程师标准>>> 对于linux mint&#xff0c; http://linuxmint-art.org/?xcontentmode8325给出了很多icon主题。 安装方法有下面几种&#xff1a; 1、使用PPA 2、将解压后的icon主题目录放到 /usr/share/icons目录下。 3、将解压后的i…

贴图程序进展

从2.28号到现在&#xff0c;在休息&#xff0c;在此先整理下之前的情况&#xff0c;对问题和要做的内容进行一下梳理。 首先&#xff0c;把之前的问题稍做下总结&#xff0c;上次提到OpenGL版本这块&#xff0c;我的是1.1版本&#xff0c;所以就只能用顶点数组来实现了&#xf…

get到一个生气后的牵手方式!太可爱了

1 公司新聘的出纳&#xff0c;大家看看尽不尽职&#xff1f;2 柯基&#xff1a;腿长有啥用啊3 男友力爆表的小螃蟹4 一滴水的下落&#xff0c;原来如此有条有理5 内容太过真实6 get到一个生气后的牵手方式7 你们那里的这条街叫什么&#xff1f;你点的每个赞&#xff0c;我都认真…