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

本文所说的裸机编程是基于“龙芯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查看。

主要有以下几个函数会

// 初始化异常
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);


其中,函数exception_init()默认已被调用,对于具体的中断来说,只需要先调用irq_install()设置中断处理函数,然后调用irq_enable()使能即可。

下面以按键中断和硬件定时器中断为例,演示如何使用这几个函数。感兴趣的可以跳到相应也没查看。

用龙芯1c库在裸机编程中实现外部中断(GPIO中断、按键中断)
http://blog.csdn.net/caogos/article/details/78166271
用龙芯1c库在裸机编程环境中实现硬件定时器中断
http://blog.csdn.net/caogos/article/details/78166348

函数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_IRQ	0
#define LS1C_HPET_IRQ	1
//#define LS1C_UART0_IRQ	3  // linux中是3,v1.4版本的1c手册中是2,暂屏蔽,待确认
#define LS1C_UART1_IRQ	4
#define LS1C_UART2_IRQ	5
#define LS1C_CAN0_IRQ	6
#define LS1C_CAN1_IRQ	7
#define LS1C_SPI0_IRQ	8
#define LS1C_SPI1_IRQ	9
#define LS1C_AC97_IRQ	10
#define LS1C_MS_IRQ		11
#define LS1C_KB_IRQ		12
#define LS1C_DMA0_IRQ	13
#define LS1C_DMA1_IRQ	14
#define LS1C_DMA2_IRQ   15
#define LS1C_NAND_IRQ	16
#define LS1C_PWM0_IRQ	17
#define LS1C_PWM1_IRQ	18
#define LS1C_PWM2_IRQ	19
#define LS1C_PWM3_IRQ	20
#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寄存器中没有控制滴答定时器的位。

最新的代码中默认禁止了IM7,即禁止滴答定时器中断。单独封装了函数sys_tick_enable()和sys_tick_disable()来使能和禁止IM7,即使能和禁止滴答定时器中断。

函数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_IRQ	0
#define LS1C_HPET_IRQ	1
//#define LS1C_UART0_IRQ	3  // linux中是3,v1.4版本的1c手册中是2,暂屏蔽,待确认
#define LS1C_UART1_IRQ	4
#define LS1C_UART2_IRQ	5
#define LS1C_CAN0_IRQ	6
#define LS1C_CAN1_IRQ	7
#define LS1C_SPI0_IRQ	8
#define LS1C_SPI1_IRQ	9
#define LS1C_AC97_IRQ	10
#define LS1C_MS_IRQ		11
#define LS1C_KB_IRQ		12
#define LS1C_DMA0_IRQ	13
#define LS1C_DMA1_IRQ	14
#define LS1C_DMA2_IRQ   15
#define LS1C_NAND_IRQ	16
#define LS1C_PWM0_IRQ	17
#define LS1C_PWM1_IRQ	18
#define LS1C_PWM2_IRQ	19
#define LS1C_PWM3_IRQ	20
#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_all
irq_disable_all:
    mfc0    t0, CP0_STATUS
    and     t0, 0xfffffffe
    mtc0    t0, CP0_STATUS
    jr      ra
    nop



/*
 * 使能中断
 * void irq_enable_all(void)
 */
    .globl irq_enable_all
irq_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_exception
general_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_int
handle_int:
    SAVE_ALL

    jal     plat_irq_dispatch
    nop
    
    RESTORE_ALL_AND_RET




    .globl handle_reserved
handle_reserved:
    .set    mips3
    eret
    .set    mips0



    .set reorder









猜你喜欢

转载自blog.csdn.net/caogos/article/details/78162218