zoukankan      html  css  js  c++  java
  • (转)eCos驱动分析 之 ISR是如何与硬件中断联系起来的?

    要想知道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相关的代码。
    完!
  • 相关阅读:
    时间复杂度理解
    elementUI表单校验汇总
    严选促销中心价格计算体系的建设之路
    sqlserver日志文件太大解决方法
    数据分析的 5 种细分方法
    批处理记录电脑磁盘剩余容量并输出到txt中
    关于sqlserver收缩数据库(引起的问题、可以半途停止吗)
    Sql Server 数据库总是显示“正在恢复、恢复挂起”的解决办法
    数据库“xxx”的事务日志已满,原因为“LOG_BACKUP”
    数据库分库分表策略的具体实现方案
  • 原文地址:https://www.cnblogs.com/kuainiao/p/2971597.html
Copyright © 2011-2022 走看看