linux内核msi机制,linux kernel 之中断框架与机制解剖(s3c6410)

参考博文http://blog.chinaunix.net/uid-25845340-id-2983156.html

/**

* struct irq_desc - interrupt descriptor

* @irq:  interrupt number for this descriptor

* @handle_irq:  highlevel irq-events handler [if NULL, __do_IRQ()]

* @chip:  low level interrupt hardware access

* @msi_desc:  MSI descriptor

* @handler_data: per-IRQ data for the irq_chip methods

* @chip_data:  platform-specific per-chip private data for the chip

*   methods, to allow shared chip implementations

* @action:  the irq action chain

* @status:  status information

* @depth:  disable-depth, for nested irq_disable() calls

* @wake_depth:  enable depth, for multiple set_irq_wake() callers

* @irq_count:  stats field to detect stalled irqs

* @irqs_unhandled: stats field for spurious unhandled interrupts

* @last_unhandled: aging timer for unhandled count

* @lock:  locking for SMP

* @affinity:  IRQ affinity on SMP

* @cpu:  cpu index useful for balancing

* @pending_mask: pending rebalanced interrupts

* @dir:  /proc/irq/ procfs entry

* @name:  flow handler name for /proc/interrupts output

*/

struct irq_desc {

unsigned int  irq;

irq_flow_handler_t handle_irq;

struct irq_chip  *chip;

struct msi_desc  *msi_desc;

void   *handler_data;

void   *chip_data;

struct irqaction *action; /* IRQ action list */

unsigned int  status;  /* IRQ status */

unsigned int  depth;  /* nested irq disables */

unsigned int  wake_depth; /* nested wake enables */

unsigned int  irq_count; /* For detecting broken IRQs */

unsigned int  irqs_unhandled;

unsigned long  last_unhandled; /* Aging timer for unhandled count */

spinlock_t  lock;

#ifdef CONFIG_SMP

cpumask_t  affinity;

unsigned int  cpu;

#endif

#ifdef CONFIG_GENERIC_PENDING_IRQ

cpumask_t  pending_mask;

#endif

#ifdef CONFIG_PROC_FS

struct proc_dir_entry *dir;

#endif

const char  *name;

} ____cacheline_internodealigned_in_smp;

extern struct irq_desc irq_desc[NR_IRQS];

///

尤entry_armv.s 里面的中断底层汇编可知asm_do_IRQ函数式中断的C语言总入口函数。它在arch/arm/kernel/irq.c中定义

/**

*我们暂且可以认为,绝大部分中断是从汇编跳到本函数处理的。当然,IPI和local_timer不是。

*irq:产生中断的外部中断号。

*regs:被中断打断的寄存器现场。

*/

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

{

/**

*将当前正在处理的中断现场保存到每CPU变量__irq_regs中去。

*这样做的目的,是为了在其他代码中,直接读取__irq_regs中的值,找到中断前的现场。

*而不用将regs参数层层传递下去。

*/

struct pt_regs *old_regs = set_irq_regs(regs);

irq_enter();

/*

* Some hardware gives randomly wrong interrupts.  Rather

* than crashing, do something sensible.

*/

if (irq >= NR_IRQS) //中断号错误,超过最大中断号

handle_bad_irq(irq, &bad_irq_desc);

elsegeneric_handle_irq(irq);///*这里进行正常的中断处理*/

/* AT91 specific workaround */

irq_finish(irq);

irq_exit();

//恢复__irq_regs每CPU变量的内容。

set_irq_regs(old_regs);

}

include/asm-generic/irq_regs.h

//这个函数就是记录中断现场

static inline struct pt_regs *set_irq_regs(struct pt_regs *new_regs)

{

struct pt_regs *old_regs, **pp_regs = &__get_cpu_var(__irq_regs);

old_regs = *pp_regs;

*pp_regs = new_regs;

return old_regs;

}

///

/**

*中断退出过程,主要处理以下内容:

*1、调试钩子,记录退出中断的事实。

*2、在任务的抢占计数字段中,递减中断计数

*3、处理软中断

*4、调用rcu模块的函数,表示已经退出中断。

*/

/*

* Exit an interrupt context. Process softirqs if needed and possible:

*/

void irq_exit(void)

{

account_system_vtime(current);

trace_hardirq_exit();

sub_preempt_count(IRQ_EXIT_OFFSET);

if (!in_interrupt() && local_softirq_pending())

invoke_softirq();

#ifdef CONFIG_NO_HZ

/* Make sure that timer wheel updates are propagated */

if (!in_interrupt() && idle_cpu(smp_processor_id()) && !need_resched())

tick_nohz_stop_sched_tick(0);

rcu_irq_exit();

#endif

preempt_enable_no_resched();

}

/

/**

*调用irq_enter表示进入中断处理过程。该函数进行如下处理:

*1、rcu模块记录内部计数,表示当前退出了NOHZ状态。关于rcu,需要整整一本书来描述,请从下载了解更多内容。

*2、处理NOHZ事件。

*3、将当前任务的抢占计数中的硬中断计数加1.该计数表示当前中断嵌套层次。

*4、调试信息,表示当前已经进入中断的事实。

*/

/*

* Enter an interrupt context.

*/

voidirq_enter(void)

{

int cpu = smp_processor_id();

if (idle_cpu(cpu) && !in_interrupt()) {

__irq_enter();

tick_check_idle(cpu);

} else

__irq_enter();

}

/

static void s3c_irq_demux_timer0(unsigned int irq, struct irq_desc *desc)

{

s3c_irq_demux_timer(irq, IRQ_TIMER0);

}

//

/* Timer interrupt handling */

static void s3c_irq_demux_timer(unsigned int base_irq, unsigned int sub_irq)

{

generic_handle_irq(sub_irq);

}

//中定义static inline voidgeneric_handle_irq(unsigned int irq)

{//*根据中断描述符中的信息,得到该中断ISR,并处理中断。

generic_handle_irq_desc(irq, irq_to_desc(irq));

}

/*

* Architectures call this to let the generic IRQ layer

* handle an interrupt. If the descriptor is attached to an

* irqchip-style controller then we call the ->handle_irq() handler,

* and it calls __do_IRQ() if it's attached to an irqtype-style controller.

*/

中定义static inline voidgeneric_handle_irq_desc(unsigned int irq, struct irq_desc *desc)

{

#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ

desc->handle_irq(irq, desc);

#else

if (likely(desc->handle_irq))desc->handle_irq(irq, desc);//此处调用s3c24xx_init_irq()中set_irq_chained_handler(IRQ_EINT8t23,s3c_irq_demux_extint8)的s3c_irq_demux_extint8()

else

__do_IRQ(irq);

#endif

}

///

handle.c

unsigned int __do_IRQ(unsigned int irq)

{

struct irq_desc *desc = irq_to_desc(irq);

struct irqaction *action;

unsigned int status;

spin_lock(&desc->lock);

/*

* REPLAY is when Linux resends an IRQ that was dropped earlier

* WAITING is used by probe to mark irqs that are being tested

*/

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

status |= IRQ_PENDING; /* we _want_ to handle it */

/*

* If the IRQ is disabled for whatever reason, we cannot

* use the action we have.

*/

action = NULL;

if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) {

action = desc->action;

status &= ~IRQ_PENDING; /* we commit to handling */

status |= IRQ_INPROGRESS; /* we are handling it */

}

desc->status = status;

/*

* If there is no IRQ handler or it was disabled, exit early.

* Since we set PENDING, if another processor is handling

* a different instance of this same irq, the other processor

* will take care of it.

*/

if (unlikely(!action))

goto out;

/*

* Edge triggered interrupts need to remember

* pending events.

* This applies to any hw interrupts that allow a second

* instance of the same irq to arrive while we are in do_IRQ

* or in the handler. But the code here only handles the _second_

* instance of the irq, not the third or fourth. So it is mostly

* useful for irq hardware that does not mask cleanly in an

* SMP environment.

*/

for (;;) {

irqreturn_t action_ret;

spin_unlock(&desc->lock);

action_ret = handle_IRQ_event(irq, action);//通过此函数逐个执行action链表中用户注册的中断处理函数if (!noirqdebug)

note_interrupt(irq, desc, action_ret);

spin_lock(&desc->lock);

if (likely(!(desc->status & IRQ_PENDING)))

break;

desc->status &= ~IRQ_PENDING;

}

desc->status &= ~IRQ_INPROGRESS;

out:

/*

* The ->end() handler has to deal with interrupts which got

* disabled while the handler was running.

*/

desc->chip->end(irq);

spin_unlock(&desc->lock);

return 1;

}

/

handle.c

/**

* handle_IRQ_event - irq action chain handler

* @irq: the interrupt number

* @action: the interrupt action chain for this irq

*

* Handles the action chain of an irq event

*/

irqreturn_thandle_IRQ_event(unsigned int irq, struct irqaction *action)

{

irqreturn_t ret, retval = IRQ_NONE;

unsigned int status = 0;

if (!(action->flags & IRQF_DISABLED))

local_irq_enable_in_hardirq();

do {

ret =action->handler(irq, action->dev_id);

if (ret == IRQ_HANDLED)

status |= action->flags;

retval |= ret;

action = action->next;

} while (action);

if (status & IRQF_SAMPLE_RANDOM)

add_interrupt_randomness(irq);

local_irq_disable();

return retval;

}/

/init/main.c asmlinkage void __init start_kernel(void) --->调用 arch/arm/kernel/irq.c     void __init init_IRQ(void)

void __init init_IRQ(void)

{

int irq;

for (irq = 0; irq < NR_IRQS; irq++)irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

#ifdef CONFIG_SMP

bad_irq_desc.affinity = CPU_MASK_ALL;

bad_irq_desc.cpu = smp_processor_id();

#endif

init_arch_irq();//即为 s3c6410_init_irq

//以下为注释

//set_up.c 中有setup_arch()中有init_arch_irq=mdesc->init_irq;对于S3C2410,S3C2440开发板,中有

MACHINE_START(SMDK6410, "SMDK6410")

/* Maintainer: Ben Dooks */

.phys_io    = S3C_PA_UART & 0xfff00000,

.io_pg_offst    = (((u32)S3C_VA_UART) >> 18) & 0xfffc,

.boot_params    = S3C64XX_PA_SDRAM + 0x100,

.init_irq    = s3c6410_init_irq,

.map_io        = smdk6410_map_io,

.init_machine    = smdk6410_machine_init,

.timer        = &s3c64xx_timer,

MACHINE_END//以上为注释

}

//void __init s3c6410_init_irq(void)

{

/* VIC0 is missing IRQ7, VIC1 is fully populated. */

s3c64xx_init_irq(~0 & ~(1 << 7), ~0);

}

//

void __init s3c64xx_init_irq(u32 vic0_valid, u32 vic1_valid)

{

int uart, irq;

printk(KERN_DEBUG "%s: initialising interrupts\n", __func__);

/* initialise the pair of VICs */

vic_init(S3C_VA_VIC0, S3C_VIC0_BASE, vic0_valid);

vic_init(S3C_VA_VIC1, S3C_VIC1_BASE, vic1_valid);

/* add the timer sub-irqs */

set_irq_chained_handler(IRQ_TIMER0_VIC, s3c_irq_demux_timer0);

set_irq_chained_handler(IRQ_TIMER1_VIC, s3c_irq_demux_timer1);

set_irq_chained_handler(IRQ_TIMER2_VIC, s3c_irq_demux_timer2);

set_irq_chained_handler(IRQ_TIMER3_VIC, s3c_irq_demux_timer3);

set_irq_chained_handler(IRQ_TIMER4_VIC, s3c_irq_demux_timer4);

for (irq = IRQ_TIMER0; irq <= IRQ_TIMER4; irq++) {

set_irq_chip(irq, &s3c_irq_timer);

set_irq_handler(irq, handle_level_irq);

set_irq_flags(irq, IRQF_VALID);

}

for (uart = 0; uart < ARRAY_SIZE(uart_irqs); uart++)

s3c64xx_uart_irq(&uart_irqs[uart]);

}

///

/* Note, we make use of the fact that the parent IRQs, IRQ_UART[0..3]

* are consecutive when looking up the interrupt in the demux routines.

*/

static struct uart_irq uart_irqs[] = {

[0] = {

.regs        = S3C_VA_UART0,

.base_irq    = IRQ_S3CUART_BASE0,

.parent_irq    = IRQ_UART0,

},

[1] = {

.regs        = S3C_VA_UART1,

.base_irq    = IRQ_S3CUART_BASE1,

.parent_irq    = IRQ_UART1,

},

[2] = {

.regs        = S3C_VA_UART2,

.base_irq    = IRQ_S3CUART_BASE2,

.parent_irq    = IRQ_UART2,

},

[3] = {

.regs        = S3C_VA_UART3,

.base_irq    = IRQ_S3CUART_BASE3,

.parent_irq    = IRQ_UART3,

},

};

//

static void __init s3c64xx_uart_irq(struct uart_irq *uirq)

{

void *reg_base = uirq->regs;

unsigned int irq;

int offs;

/* mask all interrupts at the start. */

__raw_writel(0xf, reg_base + S3C64XX_UINTM);

for (offs = 0; offs < 3; offs++) {

irq = uirq->base_irq + offs;

set_irq_chip(irq, &s3c_irq_uart);

set_irq_chip_data(irq, uirq);

set_irq_handler(irq, handle_level_irq);

set_irq_flags(irq, IRQF_VALID);

}

set_irq_chained_handler(uirq->parent_irq, s3c_irq_demux_uart);

}include/linux/irq.h中

static inline voidset_irq_chained_handler(unsigned int irq,

irq_flow_handler_t handle)

{

__set_irq_handler(irq, handle, 1, NULL);

}

//

kernel/irq/chip.c

void  __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,

const char *name)

{struct irq_desc *desc = irq_to_desc(irq);

unsigned long flags;

desc->handle_irq = handle;

desc->name = name;

if (handle != handle_bad_irq && is_chained) {

desc->status &= ~IRQ_DISABLED;

desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;

desc->depth = 0;

desc->chip->startup(irq);

}

spin_unlock_irqrestore(&desc->lock, flags);

}

//

static inline struct irq_desc *irq_to_desc(unsigned int irq)

{

return (irq < nr_irqs) ? irq_desc + irq : NULL;

}

///

irq.h中

static inline struct irq_desc*irq_to_desc(unsigned int irq)

{

return (irq < nr_irqs) ? irq_desc + irq : NULL;

}/用户注册中断处理函数:manage.c/**

* request_irq - allocate an interrupt line

* @irq: Interrupt line to allocate

* @handler: Function to be called when the IRQ occurs

* @irqflags: Interrupt type flags

* @devname: An ascii name for the claiming device 传递给request_irq的字符串,用来在/proc/interrupts中显示中断的拥有者

* @dev_id: A cookie passed back to the handler function     用于共享的中断信号线

*

* This call allocates interrupt resources and enables the

* interrupt line and IRQ handling. From the point this

* call is made your handler function may be invoked. Since

* your handler function must clear any interrupt the board

* raises, you must take care both to initialise your hardware

* and to set up the interrupt handler in the right order.

*

* Dev_id must be globally unique. Normally the address of the

* device data structure is used as the cookie. Since the handler

* receives this value it makes sense to use it.

*

* If your interrupt is shared you must pass a non NULL dev_id

* as this is required when freeing the interrupt.

*

* Flags:

*

* IRQF_SHARED  Interrupt is shared

* IRQF_DISABLED Disable local interrupts while processing

* IRQF_SAMPLE_RANDOM The interrupt can be used for entropy

* IRQF_TRIGGER_*  Specify active edge(s) or level

*

*/在内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义:

int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)

irq是要申请的硬件中断号。

handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。

irqflags是中断处理的属性,若

设置了IRQF_DISABLED

(老版本中的SA_INTERRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程

序不屏蔽;若设置了IRQF_SHARED

(老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的

SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)

devname设置中断名称,通常是设备驱动程序的名称在cat /proc/interrupts中可以看到此名称。

dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。

request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

intrequest_irq(unsigned int irq, irq_handler_t handler,

unsigned long irqflags, const char *devname, void *dev_id)

{

struct irqaction *action;

struct irq_desc *desc;

int retval;

#ifdef CONFIG_LOCKDEP

/*

* Lockdep wants atomic interrupt handlers:

*/

irqflags |= IRQF_DISABLED;

#endif

/*

* Sanity-check: shared interrupts must pass in a real dev-ID,

* otherwise we'll have trouble later trying to figure out

* which interrupt is which (messes up the interrupt freeing

* logic etc).

*/

if ((irqflags & IRQF_SHARED) && !dev_id)

return -EINVAL;

desc = irq_to_desc(irq);          //根据中断号来获得关于中断资源的描述

if (desc->status & IRQ_NOREQUEST)  //如果中断资源被设置为无法获得。

return -EINVAL;

if (!handler)                 //--中断必须要有中断处理函数

return -EINVAL;

action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);

if (!action)

return -ENOMEM;

action->handler = handler;

action->flags = irqflags;

cpus_clear(action->mask);

action->name = devname;

action->next = NULL;

action->dev_id = dev_id;__setup_irq函数,该函数才是真正的将中断注册

retval = __setup_irq(irq, desc, action);if (retval)

kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ

if (irqflags & IRQF_SHARED) {

/*

* It's a shared IRQ -- the driver ought to be prepared for it

* to happen immediately, so let's make sure....

* We disable the irq to make sure that a 'real' IRQ doesn't

* run in parallel with our fake.

*/

unsigned long flags;

disable_irq(irq);

local_irq_save(flags);

handler(irq, dev_id);        //???为啥在这里立即调用了中断的handler函数(这是一个参数) 看看宏CONFIG_DEBUG_SHIRQ

local_irq_restore(flags);

enable_irq(irq);

}

#endif

return retval;

}

/manage.c中

__setup_irq函数,该函数才是真正的将中断注册/*

* Internal function to register an irqaction - typically used to

* allocate special interrupts that are part of the architecture.

*/

static int__setup_irq(unsigned int irq, struct irq_desc * desc, struct irqaction *new)

{

struct irqaction *old, **p;

const char *old_name = NULL;

unsigned long flags;

int shared = 0;

int ret;

/*

* The following block of code has to be executed atomically

*/

spin_lock_irqsave(&desc->lock, flags);

p = &desc->action;

old = *p;

if (old) {

/* add new interrupt at end of irq queue */ ????????????????????????????、

do {

p = &old->next;

old = *p;

} while (old);shared = 1;

}

if (!shared) {

irq_chip_set_defaults(desc->chip);

/* Setup the type (level, edge polarity) if configured: */

if (new->flags & IRQF_TRIGGER_MASK) {

ret = __irq_set_trigger(desc, irq, new->flags);

if (ret) {

spin_unlock_irqrestore(&desc->lock, flags);

return ret;

}

} else

compat_irq_chip_set_default_handler(desc);

desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |

IRQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);

if (!(desc->status & IRQ_NOAUTOEN)) {

desc->depth = 0;

desc->status &= ~IRQ_DISABLED;

desc->chip->startup(irq);

} else

/* Undo nested disables: */

desc->depth = 1;

/* Exclude IRQ from balancing if requested */

if (new->flags & IRQF_NOBALANCING)

desc->status |= IRQ_NO_BALANCING;

/* Set default affinity mask once everything is setup */

do_irq_select_affinity(irq, desc);

} else if ((new->flags & IRQF_TRIGGER_MASK)

&& (new->flags & IRQF_TRIGGER_MASK)

!= (desc->status & IRQ_TYPE_SENSE_MASK)) {

/* hope the handler works with the actual trigger mode... */

pr_warning("IRQ %d uses trigger mode %d; requested %d\n",

irq, (int)(desc->status & IRQ_TYPE_SENSE_MASK),

(int)(new->flags & IRQF_TRIGGER_MASK));

}

*p = new;

/* Reset broken irq detection when installing new handler */

desc->irq_count = 0;

desc->irqs_unhandled = 0;

/*

* Check whether we disabled the irq via the spurious handler

* before. Reenable it and give it another chance.

*/

if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {

desc->status &= ~IRQ_SPURIOUS_DISABLED;

__enable_irq(desc, irq);

}

spin_unlock_irqrestore(&desc->lock, flags);

new->irq = irq;

register_irq_proc(irq, desc);

new->dir = NULL;

register_handler_proc(irq, new);

return 0;

mismatch:

spin_unlock_irqrestore(&desc->lock, flags);

return -EBUSY;

}

/arch/arm/kernel/setup.c      void __init setup_arch(char **cmdline_p)  调用

early_trap_init(void)arch/arm/kernel/traps.c

void __init early_trap_init(void)

{

// include/linux/autoconf.h       #define CONFIG_VECTORS_BASE 0xffff0000

unsigned long vectors = CONFIG_VECTORS_BASE;

extern char __stubs_start[], __stubs_end[];

extern char __vectors_start[], __vectors_end[];

extern char __kuser_helper_start[], __kuser_helper_end[];

int kuser_sz = __kuser_helper_end - __kuser_helper_start;

/*

* Copy the vectors, stubs and kuser helpers (in entry-armv.S)

* into the vector page, mapped at 0xffff0000, and ensure these

* are visible to the instruction stream.

*/

//将_vectors_start到__vectors_start之间的代码拷贝到0xffff0000  memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);

memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

/*

* Copy signal return handlers into the vector page, and

* set sigreturn to be a pointer to these.

*/

memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,

sizeof(sigreturn_codes));

flush_icache_range(vectors, vectors + PAGE_SIZE);

modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

}

中断共享

如果开发板上按键的中断已经被另外的驱动程序注册中断了,而我现在又想再注册一次这个中断,这就出现了一个中断号不止对应一个中断函数的情况。注意,这里与硬件上的共享中断不一样,这里是指,当一个中断信号来了,基于,一个中断的到来可以调用多个中断处理程序,与硬件无关。

SA_SHIRQ:这个标志表明多个中断处理程序可以共享一个中断号。

dAYBAAAAAAAA&bo=gAJVA7AEQAYFAAU%21

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值