登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

keendawn的博客

一个有着AI野心的电工+码农

 
 
 

日志

 
 

(原创)eCos驱动分析 之 ISR是如何与硬件中断联系起来的?  

2011-07-02 12:58:33|  分类: about coding and |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
要想知道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相关的代码。
完!

  评论这张
 
阅读(3803)| 评论(0)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018