要想知道ecos的中断ISR是怎么与硬件中断向量联系起来的,是怎么被调用的?
那就要看下面这两个关键的函数:
cyg_drv_interrupt_create()
cyg_drv_interrupt_attach()
这两个函数都声明在cyg/kernel/kapi.h中,其形式如下:
void cyg_interrupt_create(
cyg_vector_t vector, /* Vector to attach to */
cyg_priority_t priority, /* Queue priority */
cyg_addrword_t data, /* Data pointer */
cyg_ISR_t *isr, /* Interrupt Service Routine */
cyg_DSR_t *dsr, /* Deferred Service Routine */
cyg_handle_t *handle, /* returned handle */
cyg_interrupt *intr /* put interrupt here */
) __THROW;
void cyg_interrupt_attach( cyg_handle_t interrupt ) __THROW;
(注: __THROW是在C++中用的,是用来抛出异常的,详见我的博文http://keendawn.blog.163.com/blog/static/888807432011611193510/
这里可以视而不见.)
其中文意义对照如下:
cyg_interrupt_create(
中断号,
中断优先级,
传递的中断参数,
ISR函数,
DSR函数,
被返回的中断句柄,
存放与此中断相关的内核数据的变量空间);
cyg_interrupt_attach(中断句柄);
这样实际上去研究一下cyg_interrupt_create函数的定义内容,应该就能搞明白我们的问题了!
由于其函数声明在kapi.h中,很自然的就想到其定义应在kapi.c文件中,
找到
....\ecos\ecos-current\packages\kernel\current\src\common\kapi.cxx
文件,
找到这两个函数的定义如下:
/*---------------------------------------------------------------------------*/
/* Interrupt handling */
externC void cyg_interrupt_create(
cyg_vector_t vector, /* Vector to attach to */
cyg_priority_t priority, /* Queue priority */
cyg_addrword_t data, /* Data pointer */
cyg_ISR_t *isr, /* Interrupt Service Routine */
cyg_DSR_t *dsr, /* Deferred Service Routine */
cyg_handle_t *handle, /* returned handle */
cyg_interrupt *intr /* put interrupt here */
) __THROW
{
CYG_ASSERT_SIZES( cyg_interrupt, Cyg_Interrupt );
Cyg_Interrupt *t = new((void *)intr) Cyg_Interrupt (
(cyg_vector)vector,
(cyg_priority)priority,
(CYG_ADDRWORD)data,
(cyg_ISR *)isr,
(cyg_DSR *)dsr );
t=t;
CYG_CHECK_DATA_PTR( handle, "Bad handle pointer" );
*handle = (cyg_handle_t)intr;
}
void cyg_interrupt_attach( cyg_handle_t interrupt ) __THROW
{
((Cyg_Interrupt *)interrupt)->attach();
}
函数内容比想象中的简单,所有的操作又都传给了Cyg_Interrupt这个类来完成,
那就来对Cyg_Interrupt探个究竟吧:
(注意Cyg_Interrupt是个C++类,
可不要找成了struct cyg_interrupt,注意哟,cyg_interrupt_create函数的最后一个参数就是这个cyg_interrupt struct类型的,
在cyg/kernel/kapidata.h中有个struct cyg_interrupt定义,虽然名字和内容都很相似,但实际上不是.)
真正的class Cyg_Interrupt定义在cyg/kernel/intr.hxx中,这个头文件没干别的,就是声明这个class了,
可见这是一个很大的class,如下:
// -------------------------------------------------------------------------
// Interrupt class. This both represents each interrupt and provides a static
// interface for controlling the interrupt hardware.
class Cyg_Interrupt
{
friend class Cyg_Scheduler;
friend void interrupt_end( cyg_uint32,
Cyg_Interrupt *,
HAL_SavedRegisters *);
friend void cyg_interrupt_post_dsr( CYG_ADDRWORD intr_obj );
friend void cyg_interrupt_call_pending_DSRs( void );
cyg_vector vector; // Interrupt vector
cyg_priority priority; // Queuing priority
cyg_ISR *isr; // Pointer to ISR
cyg_DSR *dsr; // Pointer to DSR
CYG_ADDRWORD data; // Data pointer
// DSR handling interface called by the scheduler
// Check for pending DSRs
static cyg_bool DSRs_pending();
// Call any pending DSRs
static void call_pending_DSRs();
static void call_pending_DSRs_inner();
// DSR handling interface called by the scheduler and HAL
// interrupt arbiters.
void post_dsr(); // Post the DSR for this interrupt
// Data structures for handling DSR calls. We implement two DSR
// handling mechanisms, a list based one and a table based
// one. The list based mechanism is safe with respect to temporary
// overloads and will not run out of resource. However it requires
// extra data per interrupt object, and interrupts must be turned
// off briefly when delivering the DSR. The table based mechanism
// does not need unnecessary interrupt switching, but may be prone
// to overflow on overload. However, since a correctly programmed
// real time application should not experience such a condition,
// the table based mechanism is more efficient for real use. The
// list based mechainsm is enabled by default since it is safer to
// use during development.
#ifdef CYGIMP_KERNEL_INTERRUPTS_DSRS_TABLE
static Cyg_Interrupt *dsr_table[CYGNUM_KERNEL_CPU_MAX]
[CYGNUM_KERNEL_INTERRUPTS_DSRS_TABLE_SIZE]
CYGBLD_ANNOTATE_VARIABLE_INTR;
static cyg_ucount32 dsr_table_head[CYGNUM_KERNEL_CPU_MAX]
CYGBLD_ANNOTATE_VARIABLE_INTR;
static volatile cyg_ucount32 dsr_table_tail[CYGNUM_KERNEL_CPU_MAX]
CYGBLD_ANNOTATE_VARIABLE_INTR;
#endif
#ifdef CYGIMP_KERNEL_INTERRUPTS_DSRS_LIST
// Number of DSR posts made
volatile cyg_ucount32 dsr_count CYGBLD_ANNOTATE_VARIABLE_INTR;
// next DSR in list
Cyg_Interrupt* volatile next_dsr CYGBLD_ANNOTATE_VARIABLE_INTR;
// static list of pending DSRs
static Cyg_Interrupt* volatile dsr_list[CYGNUM_KERNEL_CPU_MAX]
CYGBLD_ANNOTATE_VARIABLE_INTR;
#endif
#ifdef CYGIMP_KERNEL_INTERRUPTS_CHAIN
// The default mechanism for handling interrupts is to attach just
// one Interrupt object to each vector. In some cases, and on some
// hardware, this is not possible, and each vector must carry a chain
// of interrupts.
Cyg_Interrupt *next; // Next Interrupt in list
// Chaining ISR inserted in HAL vector
static cyg_uint32 chain_isr(cyg_vector vector, CYG_ADDRWORD data);
// Table of interrupt chains
static Cyg_Interrupt *chain_list[CYGNUM_HAL_ISR_TABLE_SIZE];
#endif
// Interrupt disable data. Interrupt disable can be nested. On
// each CPU this is controlled by disable_counter[cpu]. When the
// counter is first incremented from zero to one, the
// interrupt_disable_spinlock is claimed using spin_intsave(), the
// original interrupt enable state being saved in
// interrupt_disable_state[cpu]. When the counter is decremented
// back to zero the spinlock is cleared using clear_intsave().
// The spinlock is necessary in SMP systems since a thread
// accessing data shared with an ISR may be scheduled on a
// different CPU to the one that handles the interrupt. So, merely
// blocking local interrupts would be ineffective. SMP aware
// device drivers should either use their own spinlocks to protect
// data, or use the API supported by this class, via
// cyg_drv_isr_lock()/_unlock(). Note that it now becomes
// essential that ISRs do this if they are to be SMP-compatible.
// In a single CPU system, this mechanism reduces to just
// disabling/enabling interrupts.
// Disable level counter. This counts the number of times
// interrupts have been disabled.
static volatile cyg_int32 disable_counter[CYGNUM_KERNEL_CPU_MAX]
CYGBLD_ANNOTATE_VARIABLE_INTR;
// Interrupt disable spinlock. This is claimed by any CPU that has
// disabled interrupts via the Cyg_Interrupt API.
static Cyg_SpinLock interrupt_disable_spinlock CYGBLD_ANNOTATE_VARIABLE_INTR;
// Saved interrupt state. When each CPU first disables interrupts
// the original state of the interrupts are saved here to be
// restored later.
static CYG_INTERRUPT_STATE interrupt_disable_state[CYGNUM_KERNEL_CPU_MAX]
CYGBLD_ANNOTATE_VARIABLE_INTR;
public:
Cyg_Interrupt // Initialize interrupt
(
cyg_vector vector, // Vector to attach to
cyg_priority priority, // Queue priority
CYG_ADDRWORD data, // Data pointer
cyg_ISR *isr, // Interrupt Service Routine
cyg_DSR *dsr // Deferred Service Routine
);
~Cyg_Interrupt();
// ISR return values
enum {
HANDLED = 1, // Interrupt was handled
CALL_DSR = 2 // Schedule DSR
};
// Interrupt management
void attach(); // Attach to vector
void detach(); // Detach from vector
// Static Interrupt management functions
// Get the current service routine
static void get_vsr(cyg_vector vector, cyg_VSR **vsr);
// Install a vector service routine
static void set_vsr(
cyg_vector vector, // hardware vector to replace
cyg_VSR *vsr, // my new service routine
cyg_VSR **old = NULL // pointer to old vsr, if required
);
// Static interrupt masking functions
// Disable interrupts at the CPU
static void disable_interrupts();
// Re-enable CPU interrupts
static void enable_interrupts();
// Are interrupts enabled at the CPU?
static inline cyg_bool interrupts_enabled()
{
return (0 == disable_counter[CYG_KERNEL_CPU_THIS()]);
}
// Get the vector for the following calls
inline cyg_vector get_vector()
{
return vector;
}
// Static PIC control functions
// Mask a specific interrupt in a PIC
static void mask_interrupt(cyg_vector vector);
// The same but not interrupt safe
static void mask_interrupt_intunsafe(cyg_vector vector);
// Clear PIC mask
static void unmask_interrupt(cyg_vector vector);
// The same but not interrupt safe
static void unmask_interrupt_intunsafe(cyg_vector vector);
// Acknowledge interrupt at PIC
static void acknowledge_interrupt(cyg_vector vector);
// Change interrupt detection at PIC
static void configure_interrupt(
cyg_vector vector, // vector to control
cyg_bool level, // level or edge triggered
cyg_bool up // hi/lo level, rising/falling edge
);
#ifdef CYGPKG_KERNEL_SMP_SUPPORT
// SMP support for associating an interrupt with a specific CPU.
static void set_cpu( cyg_vector, HAL_SMP_CPU_TYPE cpu );
static HAL_SMP_CPU_TYPE get_cpu( cyg_vector );
#endif
};
这只是声明了这个class,这个class的构造/析构函数和成员函数的实现,还要找cyg/kernel/intr.hxx头文件相对应的C文件,
在这里....\packages\kernel\current\src\intr\intr.cxx找到了intr.cxx文件,
因为cyg_interrupt_create函数实际上调用了class Cyg_Interrupt的构造函数,我们就来看看这个构造函数:
Cyg_Interrupt::Cyg_Interrupt(
cyg_vector vec, // Vector to attach to
cyg_priority pri, // Queue priority
CYG_ADDRWORD d, // Data pointer
cyg_ISR *ir, // Interrupt Service Routine
cyg_DSR *dr // Deferred Service Routine
)
{
CYG_REPORT_FUNCTION();
CYG_REPORT_FUNCARG5("vector=%d, priority=%d, data=%08x, isr=%08x, "
"dsr=%08x", vec, pri, d, ir, dr);
vector = vec;
priority = pri;
isr = ir;
dsr = dr;
data = d;
#ifdef CYGIMP_KERNEL_INTERRUPTS_DSRS_LIST
dsr_count = 0;
next_dsr = NULL;
#endif
#ifdef CYGIMP_KERNEL_INTERRUPTS_CHAIN
next = NULL;
#endif
CYG_REPORT_RETURN();
};
也就是分配了一下成员变量,把cyg_interrupt_create函数传进来的 中断号、ISR、DSR等 分配给类的成员变量,好像也没什么特别的。
看来整个cyg_interrupt_create函数也就是在构造这个类对象了。
这样重要的好戏是在cyg_interrupt_attach函数里完成了,看cyg_interrupt_attach的源代码,只一行,再次列出如下:
void cyg_interrupt_attach( cyg_handle_t interrupt ) __THROW
{
((Cyg_Interrupt *)interrupt)->attach();
}
它也就是调用了class Cyg_Interrupt的attach成员函数,那我们到intr.cxx中看看attach这个成员函数都干了些啥?
// -------------------------------------------------------------------------
// Attach an ISR to an interrupt vector.
void
Cyg_Interrupt::attach(void)
{
CYG_REPORT_FUNCTION();
CYG_ASSERT( vector >= CYGNUM_HAL_ISR_MIN, "Invalid vector");
CYG_ASSERT( vector <= CYGNUM_HAL_ISR_MAX, "Invalid vector");
CYG_INSTRUMENT_INTR(ATTACH, vector, 0);
HAL_INTERRUPT_SET_LEVEL( vector, priority );
#ifdef CYGIMP_KERNEL_INTERRUPTS_CHAIN
CYG_ASSERT( next == NULL , "Cyg_Interrupt already on a list");
cyg_uint32 index;
HAL_TRANSLATE_VECTOR( vector, index );
if( chain_list[index] == NULL )
{
int in_use;
// First Interrupt on this chain, just assign it and register
// the chain_isr with the HAL.
chain_list[index] = this;
HAL_INTERRUPT_IN_USE( vector, in_use );
CYG_ASSERT( 0 == in_use, "Interrupt vector not free.");
HAL_INTERRUPT_ATTACH( vector, chain_isr, &chain_list[index], NULL );
}
else
{
// There are already interrupts chained, add this one into the
// chain in priority order.
Cyg_Interrupt **p = &chain_list[index];
while( *p != NULL )
{
Cyg_Interrupt *n = *p;
if( n->priority < priority ) break;
p = &n->next;
}
next = *p;
*p = this;
}
#else
{
int in_use;
HAL_INTERRUPT_IN_USE( vector, in_use );
CYG_ASSERT( 0 == in_use, "Interrupt vector not free.");
HAL_INTERRUPT_ATTACH( vector, isr, data, this );
}
#endif
CYG_REPORT_RETURN();
}
attach成员函数又调用了 HAL_INTERRUPT_ATTACH 这个HAL层的宏,那就继续追踪这个宏吧,
它定义在<cyg/hal/hal_intr.h>里,如下:
#define HAL_INTERRUPT_ATTACH( _vector_, _isr_, _data_, _object_ ) \
CYG_MACRO_START \
cyg_uint32 _index_; \
HAL_TRANSLATE_VECTOR( _vector_, _index_ ); \
\
if( hal_interrupt_handlers[_index_] == (CYG_ADDRESS)HAL_DEFAULT_ISR ) \
{ \
hal_interrupt_handlers[_index_] = (CYG_ADDRESS)_isr_; \
hal_interrupt_data[_index_] = (CYG_ADDRWORD)_data_; \
hal_interrupt_objects[_index_] = (CYG_ADDRESS)_object_; \
} \
CYG_MACRO_END
(注: CYG_MACRO_START 和 CYG_MACRO_END 宏定义在<cyg/infra/cyg_type.h>中
L176 #define CYG_MACRO_START do {
L177 #define CYG_MACRO_END } while (0)
)
这个宏的主要干了3件事,
以中断号作为索引,将 isr地址、data 和 0bject地址(也就是class Cyg_Interrupt的对象) 3者
分别存入hal_interrupt_handlers、hal_interrupt_data and hal_interrupt_objects 3个数组中。
这3个数组定义在hal_intr.h头文件相对应的hal_intr.c 文件中,
这个C文件也没干其他事,就只定义了这3个全局数组,如下:
// Create the interrupt handler table, with all handlers set to the 'safe'
// default.
volatile CYG_ADDRESS hal_interrupt_handlers[CYGNUM_HAL_ISR_COUNT] =
{(CYG_ADDRESS)HAL_DEFAULT_ISR,
(CYG_ADDRESS)HAL_DEFAULT_ISR,
.
.
.
.
(CYG_ADDRESS)HAL_DEFAULT_ISR,
(CYG_ADDRESS)HAL_DEFAULT_ISR};
volatile CYG_ADDRWORD hal_interrupt_data[CYGNUM_HAL_ISR_COUNT];
volatile CYG_ADDRESS hal_interrupt_objects[CYGNUM_HAL_ISR_COUNT];
最关键的就是hal_interrupt_handlers数组,它放的就是所有终端ISR的地址。
但是,追踪到这个数组,好像也就追不下去了,
这样的一个ISR table,又是怎样被索引调用的呢?
我们只顾忙着追踪,应该回想一下,我们知道HAL层是ecos中和硬件结构相关的地址,
我们上面分析的部分HAL层代码实际上是具体于某一CPU架构的。
而我上面看的HAL代码就是NIOS II的HAL层,只是上面的一点HAL代码好像还看不出与CPU结构相关的特殊性。
那如果我们还要继续trace, 看来就必须要和CPU结构密切相关了,也能感觉的到是和硬件中断向量有关的东东,
那我们就以NIOS II为例继续追踪,
你会发现,和上面hal_intr.c文件同一目录下,
....\packages\hal\nios2\arch\current\src\
有一个vector.S汇编文件,一看到它的名字,就有感觉,打开它,你会发现,
它分了4大块,分别定义了4种不同情况下,CPU发生异常or接收到中断时,要执行的代码,
/*
* ========================================================================
* _hardware_reset
*
* This is the reset entry point for Nios II.
*
* At reset, only the cache line which contain the reset vector is
* initialized. Therefore the code within the first cache line is required
* to initialize the instruction cache for the remainder of the code.
*
* Note that the automatically generated linker script requires that
* the .init section is less than 0x20 bytes long.
*/
L106 _hardware_reset:
/*
* ========================================================================
* _exception_vector
*
* This is the exception entry point. It is responsible for determing if the
* exception was caused by an hardware interrupt, or a software exception.
* It then vectors accordingly using the VSR table to handle the exception.
*/
L283 _exception_vector:
/*
* ========================================================================
* _interrupt_handler
*
* This is the default handler for hardware interrupts.
*/
L342 _interrupt_handler::
/*
* ========================================================================
* _software_exception_handler
*
* This is the default handler for software exceptions.
*/
.globl _software_exception_handler
.type _software_exception_handler, @function
L613 _software_exception_handler:
顾名思义,我们要找的应该在_interrupt_handler中,果不其然,其中有如下一段代码:
L465 /*
* Having located the interrupt source, r4 contains the index of the
* interrupt to be handled.
*
* This is converted into an offset into the handler table,
* and stored in r15.
*/
slli r15, r4, 2
L475 /*
* Load into r1 the address of the handler for this interrupt. This is
* obtained from the interrupt handler table: hal_interrupt_handlers.
*/
movhi r1, %hiadj(hal_interrupt_handlers)
addi r1, r1, %lo(hal_interrupt_handlers)
add r1, r1, r15
ldw r1, (r1)
/*
* Load into r5 the data associated with the interrupt handler. This is
* obtained from the table: hal_interrupt_data.
*/
movhi r5, %hiadj(hal_interrupt_data)
addi r5, r5, %lo(hal_interrupt_data)
add r5, r5, r15
ldw r5, (r5)
/*
* Call the interrupt handler The input arguments are the interrupt number
* and the associated data obtained above. Save off r15 explicitly since
* its a caller-saved register and is used below.
*/
addi sp, sp, -4
stw r15, (sp)
L502 callr r1
ldw r15, (sp)
L504 addi sp, sp, 4
你看,r4中存有要相应的中断号,它又存入r15中,根据r15的值,
在hal_interrupt_handlers数组中得到相应的ISR地址存入r1中,
在L502行callr r1,就调用了中断ISR。
至此,层层追踪结束,大功告成。
终于搞明白了ecos中ISR是怎么响应到硬件中断事件的。
其实,剥茧抽丝,层层追踪,兜兜转转,你会发现class Cyg_Interrupt这个C++类,只是做了一个高层的包装,
真正起作用的还是底层HAL相关的代码。
完!