【龙芯1c库】在裸机编程环境中常用的中断接口简介

来源:互联网 发布:添加windows凭据 编辑:程序博客网 时间:2024/05/20 17:23

本文所说的裸机编程是基于“龙芯1c库”的。“龙芯1c库”类似于STM32库,“龙芯1c库”的git地址是https://gitee.com/caogos/OpenLoongsonLib1c

中断对于任何一款CPU来说都是非常重要的,对龙芯1c也是如此。另一篇博文以linux中的中断作为实例,详细分析了龙芯1c的中断。龙芯1c的中断与arm的中断有些不同,如果对龙芯1c的中断还没有概念,请移步到《【龙芯1c库】龙芯1c的中断分析》http://blog.csdn.net/caogos/article/details/69948579

除了linux中有龙芯1c的中断实现以外,RT-Thread中也有,事实上RT-Thread的中断移植是参考了linux的,而本文提到的裸机程序的中断则主要参考了RT-Thread,所以裸机程序的和RT-Thread的中断相关接口类似,用法也类似。RT-Thread的git地址是https://github.com/RT-Thread/rt-thread

常用的中断接口简介

这里只介绍几个对“龙芯1c库”的“用户”(嵌入式软件工程师,单片机工程师)可见的,还有一些具体实现的函数接口在这里就不阐述了,感兴趣的可以移步到git查看。

函数exception_init()

龙芯1c(mips系列的cpu)中,对于异常和中断分得很清楚,中断常指外设的中断,可能外设已经集成在芯片内了,比如定时器等。中断是一种“特殊的异常”,所有中断共用一个异常入口。所以中断的初始化,就涉及异常的初始化。函数exception_init()就是对龙芯1c的整个异常进行初始化,包括初始化中断。要是用中断,函数exception_init()必须被调用,因此在函数bsp_init()中默认调用了exception_init()。

函数irq_enable()和irq_disable()

简介

/* * 使能指定中断 * @IRQn 中断号 */void irq_enable(int IRQn);/* * 禁止指定中断 * @IRQn 中断号 */void irq_disable(int IRQn);

这两个函数的入参都是中断号。所有中断的中断号都可以在INTx_SR(INT0_SR、INT1_SR、……、INT4_SR)中找到,如下图所示

来看看代码中的中断号

// 中断号#define LS1C_ACPI_IRQ0#define LS1C_HPET_IRQ1//#define LS1C_UART0_IRQ3  // linux中是3,v1.4版本的1c手册中是2,暂屏蔽,待确认#define LS1C_UART1_IRQ4#define LS1C_UART2_IRQ5#define LS1C_CAN0_IRQ6#define LS1C_CAN1_IRQ7#define LS1C_SPI0_IRQ8#define LS1C_SPI1_IRQ9#define LS1C_AC97_IRQ10#define LS1C_MS_IRQ11#define LS1C_KB_IRQ12#define LS1C_DMA0_IRQ13#define LS1C_DMA1_IRQ14#define LS1C_DMA2_IRQ   15#define LS1C_NAND_IRQ16#define LS1C_PWM0_IRQ17#define LS1C_PWM1_IRQ18#define LS1C_PWM2_IRQ19#define LS1C_PWM3_IRQ20#define LS1C_RTC_INT0_IRQ  21#define LS1C_RTC_INT1_IRQ  22#define LS1C_RTC_INT2_IRQ  23#define LS1C_UART3_IRQ  29#define LS1C_ADC_IRQ    30#define LS1C_SDIO_IRQ   31#define LS1C_EHCI_IRQ(32+0)#define LS1C_OHCI_IRQ(32+1)#define LS1C_OTG_IRQ    (32+2)#define LS1C_MAC_IRQ    (32+3)#define LS1C_CAM_IRQ    (32+4)#define LS1C_UART4_IRQ  (32+5)#define LS1C_UART5_IRQ  (32+6)#define LS1C_UART6_IRQ  (32+7)#define LS1C_UART7_IRQ  (32+8)#define LS1C_UART8_IRQ  (32+9)#define LS1C_UART9_IRQ  (32+13)#define LS1C_UART10_IRQ (32+14)#define LS1C_UART11_IRQ (32+15)#define LS1C_I2C2_IRQ   (32+17)#define LS1C_I2C1_IRQ   (32+18)#define LS1C_I2C0_IRQ   (32+19)#define LS1C_GPIO_IRQ 64#define LS1C_GPIO_FIRST_IRQ 64#define LS1C_GPIO_IRQ_COUNT 96#define LS1C_GPIO_LAST_IRQ  (LS1C_GPIO_FIRST_IRQ + LS1C_GPIO_IRQ_COUNT-1)#define LS1C_LAST_IRQ 159// 龙芯1c的中断分为五组,每组32个#define LS1C_NR_IRQS    (32*5)// GPIO编号和中断号之间的互相转换#define LS1C_GPIO_TO_IRQ(GPIOn)     (LS1C_GPIO_FIRST_IRQ + (GPIOn))#define LS1C_IRQ_TO_GPIO(IRQn)      ((IRQn) - LS1C_GPIO_FIRST_IRQ)

注意,gpio的中断号是通过宏LS1C_GPIO_TO_IRQ()转换得到的。
函数irq_enable()和irq_disable()只是把寄存器INTx_EN中相应的位置一或清零了,寄存器INTx_EN是龙芯1c特有的,其它MIPS系列的CPU可能没有。除了寄存器INTx_EN控制着中断的使能外,还有协处理器0的状态(SR)寄存器中的一些位域(比如:IM7-IM0、IE、EXL等)。其中IE和IM7-IM0是我们关心的,IE为全局中断使能位,IM7为滴答定时器的屏蔽位,IM6到IM2共5个位分别控制龙芯1c的五组中断。即龙芯1c的中断使能分为三级:IE全局中断使能位、IM6-IM2为分组中断使能位、龙芯1c特有的寄存器INTx_EN为每个外设中断的使能位。默认情况,我已经把IE和IM6-IM2都设置为允许中断,而由龙芯1c特有的寄存器INTx_EN来控制每个外设中断是否使能。

滴答定时器是个例外

为什么说滴答定时器是个例外呢?首先从滴答定时器的由来说起,滴答定时器是为了给操作系统提供时钟,也就是说基本上只有在初始化的时候设置一下定时时间之外,很少看见有禁止滴答定时器的。虽然是可以禁止滴答定时器,甚至作为普通定时器一样使用都是可以的,但实际上很少这样做。
前面提到普通的外设中断使能有三级,而滴答定时器只有两级。即滴答定时器只受协处理器0的SR寄存器中的IE和IM7控制,INTx_EN寄存器中没有控制滴答定时器的位。
在调试时,我遇到一种情况,协处理器0的compare寄存器被设为0了(不知道是上电后默认的,还是pmon设置的),这会产生什么效果呢?这会造成不断触发滴答定时器中断(如果使能了的话),造成根本没法运行函数main()中的代码,可以在中断处理函数中加打印看出来。为避免这种情况,在异常初始化之前,先初始化滴答定时器。

函数irq_install()

/* * 设置中断处理函数 * @IRQn 中断号 * @new_handler 新设置的中断处理函数 * @param 传递给中断处理函数的参数 * @ret 之前的中断处理函数 */irq_handler_t irq_install(int IRQn, irq_handler_t new_handler, void *param);

函数irq_install()用于设置中断处理函数。来看看中断处理函数的类型irq_handler_t

typedef void (*irq_handler_t)(int IRQn, void *param);

比如,设置定时器0的中断处理函数

irq_install(LS1C_PWM0_IRQ, test_timer_pwm0_irqhandler, NULL);

中断处理函数test_timer_pwm0_irqhandler()的实现为

void test_timer_pwm0_irqhandler(int IRQn, void *param){Return ;}

源码清单

ls1c_irq.h

#ifndef __IRQ_H#define __IRQ_H// 中断号#define LS1C_ACPI_IRQ0#define LS1C_HPET_IRQ1//#define LS1C_UART0_IRQ3  // linux中是3,v1.4版本的1c手册中是2,暂屏蔽,待确认#define LS1C_UART1_IRQ4#define LS1C_UART2_IRQ5#define LS1C_CAN0_IRQ6#define LS1C_CAN1_IRQ7#define LS1C_SPI0_IRQ8#define LS1C_SPI1_IRQ9#define LS1C_AC97_IRQ10#define LS1C_MS_IRQ11#define LS1C_KB_IRQ12#define LS1C_DMA0_IRQ13#define LS1C_DMA1_IRQ14#define LS1C_DMA2_IRQ   15#define LS1C_NAND_IRQ16#define LS1C_PWM0_IRQ17#define LS1C_PWM1_IRQ18#define LS1C_PWM2_IRQ19#define LS1C_PWM3_IRQ20#define LS1C_RTC_INT0_IRQ  21#define LS1C_RTC_INT1_IRQ  22#define LS1C_RTC_INT2_IRQ  23#define LS1C_UART3_IRQ  29#define LS1C_ADC_IRQ    30#define LS1C_SDIO_IRQ   31#define LS1C_EHCI_IRQ(32+0)#define LS1C_OHCI_IRQ(32+1)#define LS1C_OTG_IRQ    (32+2)#define LS1C_MAC_IRQ    (32+3)#define LS1C_CAM_IRQ    (32+4)#define LS1C_UART4_IRQ  (32+5)#define LS1C_UART5_IRQ  (32+6)#define LS1C_UART6_IRQ  (32+7)#define LS1C_UART7_IRQ  (32+8)#define LS1C_UART8_IRQ  (32+9)#define LS1C_UART9_IRQ  (32+13)#define LS1C_UART10_IRQ (32+14)#define LS1C_UART11_IRQ (32+15)#define LS1C_I2C2_IRQ   (32+17)#define LS1C_I2C1_IRQ   (32+18)#define LS1C_I2C0_IRQ   (32+19)#define LS1C_GPIO_IRQ 64#define LS1C_GPIO_FIRST_IRQ 64#define LS1C_GPIO_IRQ_COUNT 96#define LS1C_GPIO_LAST_IRQ  (LS1C_GPIO_FIRST_IRQ + LS1C_GPIO_IRQ_COUNT-1)#define LS1C_LAST_IRQ 159// 龙芯1c的中断分为五组,每组32个#define LS1C_NR_IRQS    (32*5)// GPIO编号和中断号之间的互相转换#define LS1C_GPIO_TO_IRQ(GPIOn)     (LS1C_GPIO_FIRST_IRQ + (GPIOn))#define LS1C_IRQ_TO_GPIO(IRQn)      ((IRQn) - LS1C_GPIO_FIRST_IRQ)// 定义中断处理函数typedef void (*irq_handler_t)(int IRQn, void *param);typedef struct irq_desc{    irq_handler_t   handler;    void            *param;}irq_desc_t;// 初始化异常void exception_init(void);/* * 使能指定中断 * @IRQn 中断号 */void irq_enable(int IRQn);/* * 禁止指定中断 * @IRQn 中断号 */void irq_disable(int IRQn);/* * 设置中断处理函数 * @IRQn 中断号 * @new_handler 新设置的中断处理函数 * @param 传递给中断处理函数的参数 * @ret 之前的中断处理函数 */irq_handler_t irq_install(int IRQn, irq_handler_t new_handler, void *param);#endif


ls1c_irq.c

/************************************************************************* * * 中断相关的函数 * 龙芯1c中中断是一种类型的异常, * 可以理解为cpu中有几种异常,而中断是其中一种异常下面的一个子类型 * 龙芯1c的异常分为四级 * 第一级: 各种情况下异常向量的总入口 * 第二级: 各个异常的入口,其中ExcCode=0的异常为外设中断的总入口 * 第三级: 外设中断的每组的总入口(龙芯1c将所有外设分为五组) * 第四级: 每个外设中断的入口 * *************************************************************************/#include <stdio.h>#include <string.h>#include "ls1c_public.h"#include "ls1c_mipsregs.h"#include "ls1c_clock.h"#include "ls1c_gpio.h"#include "ls1c_cache.h"#include "ls1c_sys_tick.h"#include "ls1c_irq.h"#include "ls1c_regs.h"// 每个异常有0x80字节的空间#define EXCEPTION_HANDER_MAX_SIZE               (0x80)// 正常运行时,异常的入口基地址// 正常运行时,STATUS寄存器的BEV=0,cpu采用RAM空间的异常入口// 0x80000000地址处不经TLB映射,但缓存#define EXCEPTION_RAM_EBASE                     (0x80000000)// 异常的最大个数#define EXCEPTION_MAX_NUM                       (32)// 中断的最大个数#define IRQ_MAX_NUM                             (LS1C_NR_IRQS)// 中断配置寄存器#define LS1C_INTREG_BASE                    (0xbfd01040)struct ls1c_intc_regs{volatile unsigned int int_isr;volatile unsigned int int_en;volatile unsigned int int_set;volatile unsigned int int_clr;/* offset 0x10*/volatile unsigned int int_pol;   volatile unsigned int int_edge;/* offset 0 */}; struct ls1c_intc_regs volatile *ls1c_hw0_icregs     = (struct ls1c_intc_regs volatile *)LS1C_INTREG_BASE;// 异常处理函数unsigned long exception_handlers[EXCEPTION_MAX_NUM];// 中断处理函数irq_desc_t irq_handlers[IRQ_MAX_NUM];// 汇编实现的函数extern void irq_disable_all(void);extern void irq_enable_all(void);extern void general_exception(void);extern void handle_int(void);extern void handle_reserved(void);// 设置整个异常向量的处理函数// @offset 异常向量总入口的偏移// @src_addr 异常向量总入口处理函数的首地址void irq_set_exception_vector_handler(unsigned long offset, void *src_addr, unsigned long size){    unsigned long dst_addr;   // 异常入口    dst_addr = EXCEPTION_RAM_EBASE+offset;    memcpy((void *)dst_addr, src_addr, size);    // 先回写dcache,再作废icache    // memcpy之后,现在异常向量总入口的指令位于dcache中,需要回写到内存,    // 并作废相应icache,作废后当有中断发生时,才会从内存重新加载新代码到icache,这样新代码就生效了    dcache_writeback_invalidate_range(dst_addr, dst_addr + size);    icache_invalidate_range(dst_addr, dst_addr + size);    return ;}// 设置一个异常的处理函数// @n 协处理器0的cause寄存器的[2,6]位,即ExcCode// @addr 异常处理函数的首地址void irq_set_one_exception_handler(int n, void *addr){    unsigned long handler = (unsigned long)addr;    exception_handlers[n] = handler;    return ;}/* * 默认的中断处理函数 * @IRQn 中断号 * @param 参数 */void irq_default_handler(int IRQn, void *param){    myprintf("unhandled irq %d occured!\r\n", IRQn);    return ;}/* * 使能指定中断 * @IRQn 中断号 */void irq_enable(int IRQn){    (ls1c_hw0_icregs + (IRQn >> 5))->int_en |= (1 << (IRQn & 0x1f));    return ;}/* * 禁止指定中断 * @IRQn 中断号 */void irq_disable(int IRQn){    (ls1c_hw0_icregs + (IRQn >> 5))->int_en &= ~(1 << (IRQn & 0x1f));    return ;}/* * 设置中断处理函数 * @IRQn 中断号 * @new_handler 新设置的中断处理函数 * @param 传递给中断处理函数的参数 * @ret 之前的中断处理函数 */irq_handler_t irq_install(int IRQn, irq_handler_t new_handler, void *param){    irq_handler_t old_handler = NULL;    if((0 <= IRQn) && (IRQ_MAX_NUM > IRQn))    {        old_handler = irq_handlers[IRQn].handler;                irq_handlers[IRQn].handler  = new_handler;        irq_handlers[IRQn].param    = param;    }    return old_handler;}// 初始化中断void irq_init(void){    volatile struct ls1c_intc_regs *intc_regs = NULL;    int i;    int IRQn;    // 禁止中断:设置龙芯1C里面的中断配置寄存器    for (i=0; i<5; i++)     // 龙芯1c的中断分为五组    {        intc_regs = ls1c_hw0_icregs+i;        intc_regs->int_en   = 0x0;          // disable        intc_regs->int_pol  = -1;           // must be done here        intc_regs->int_edge = 0x00000000;   // 电平触发        intc_regs->int_clr  = 0xffffffff;   // 清中断    }    // 设置默认的中断处理函数    for (IRQn = 0; IRQn < IRQ_MAX_NUM; IRQn++)    {        irq_handlers[IRQn].handler  = irq_default_handler;        irq_handlers[IRQn].param    = 0;    }    return ;}// 初始化异常void exception_init(void){    int i;        // 禁止中断:设置协处理器0    irq_disable_all();    // 初始化高速缓存    cache_init();    // 初始化中断    irq_init();        // 设置整个异常向量的处理函数    irq_set_exception_vector_handler(0x180, &general_exception, EXCEPTION_HANDER_MAX_SIZE);    irq_set_exception_vector_handler(0x200, &general_exception, EXCEPTION_HANDER_MAX_SIZE);    // 设置各个异常的处理函数    for (i=0; i<EXCEPTION_MAX_NUM; i++)    {        irq_set_one_exception_handler(i, handle_reserved);    }    irq_set_one_exception_handler(0, handle_int);    // 先回写整个dcache,再作废整个icache    dcache_writeback_invalidate_all();    icache_invalidate_all();    // 使能中断    irq_enable_all();    return ;}/* * 执行中断处理流程 * @IRQn 中断号 */void ls1c_do_IRQ(int IRQn){    irq_handler_t irq_handler = NULL;    void *param = NULL;    irq_handler = irq_handlers[IRQn].handler;    param       = irq_handlers[IRQn].param;    // 执行中断处理函数    irq_handler(IRQn, param);    return ;}void ls1c_irq_dispatch(int n){    unsigned int intstatus, irq;    /* Receive interrupt signal, compute the irq */    intstatus = (ls1c_hw0_icregs+n)->int_isr & (ls1c_hw0_icregs+n)->int_en;    if (0 == intstatus)        return ;    // 执行中断处理函数    irq = ffs(intstatus) - 1;    ls1c_do_IRQ((n<<5) + irq);    /* ack interrupt */    (ls1c_hw0_icregs+n)->int_clr |= (1 << irq);        return ;}// 中断分发函数void plat_irq_dispatch(void){    unsigned int pending;    pending = read_c0_cause() & read_c0_status() & ST0_IM;    if (pending & CAUSEF_IP7)    {        sys_tick_handler();    }    else if (pending & CAUSEF_IP2)    {        ls1c_irq_dispatch(0);    }    else if (pending & CAUSEF_IP3)    {        ls1c_irq_dispatch(1);    }    else if (pending & CAUSEF_IP4)    {        ls1c_irq_dispatch(2);    }    else if (pending & CAUSEF_IP5)    {        ls1c_irq_dispatch(3);    }    else if (pending & CAUSEF_IP6)    {        ls1c_irq_dispatch(4);    }    else    {        // 其它情况不处理    }    return ;}

ls1c_start.S

/************************************************************************* * * 用汇编实现的启动相关函数 * *************************************************************************//* * 汇编文件的扩展名必须是大写的S,不能用小写的s。否则不会执行预处理, * 即把#include, #define等这些当作以'#'开头的注释,而忽略掉,最终导致编译错误 */#include "ls1c_regdef.h"#include "ls1c_mipsregs.h"#include "ls1c_asm.h"#include "ls1c_stackframe.h"#include "ls1c_cacheops.h"    .section ".text", "ax"    .set noreorder/* * 禁止中断 * void irq_disable_all(void)*/    .globl irq_disable_allirq_disable_all:    mfc0    t0, CP0_STATUS    and     t0, 0xfffffffe    mtc0    t0, CP0_STATUS    jr      ra    nop/* * 使能中断 * void irq_enable_all(void) */    .globl irq_enable_allirq_enable_all:    mfc0    t0, CP0_STATUS    or      t0, 0xFC01    mtc0    t0, CP0_STATUS    jr      ra    nop    /** General exception vector for all other CPUs.** Be careful when changing this, it has to be at most 128 bytes* to fit into space reserved for the exception handler.* * 整个异常向量的入口处理函数* void general_exception(void)*/    .globl general_exceptiongeneral_exception:    mfc0    k1, CP0_CAUSE    andi    k1, k1, 0x7c    # 注意,这里exception_handlers不是函数,而是地址    # 即exception_handlers+k1 = exception_handlers+ExcCode*4    # exception_handlers[]中保存了各个异常处理函数的首地址,每个占4字节    # 这里看上去很巧妙,实际上个人认为很不好理解,很容易混淆    PTR_L   k0, exception_handlers(k1)    jr  k0    .globl handle_inthandle_int:    SAVE_ALL    jal     plat_irq_dispatch    nop        RESTORE_ALL_AND_RET    .globl handle_reservedhandle_reserved:    .set    mips3    eret    .set    mips0    .set reorder