本文共 8730 字,大约阅读时间需要 29 分钟。
封装龙芯1c上常见外设接口,便于在裸机程序或实时操作系统中使用,类似于STM32库,正在不断添加中。Git地址是https://gitee.com/caogos/OpenLoongsonLib1c
在MIPS体系结构中,中断、自陷、系统调用以及其它打断程序正常执行流程的事件称为异常,
即异常包括中断,中断是一种特定类型的异常。龙芯1c的异常分为四级第一级: 各种情况下异常的总入口第二级:各个异常的入口,其中ExcCode=0的异常为外设中断的总入口第三级: 外设中断的每组的总入口(龙芯1c将所有外设分为五组)第四级: 每个外设中断的入口异常向量间的距离缺省为128字节(0x80)。
上表中,龙芯1C的BASE为0x80000000。
异常地址BASE+0x180对应的所有其它异常(包括我们关心的中断),在linux中的源码为
void __init trap_init(void){……set_handler(0x180, &except_vec3_generic, 0x80);……}函数set_handler()的源码为
/* Install CPU exception handler */void __init set_handler(unsigned long offset, void *addr, unsigned long size){ memcpy((void *)(ebase + offset), addr, size); local_flush_icache_range(ebase + offset, ebase + offset + size);}简单来说,就是把函数except_vec3_generic()复制到BASE+0x180开始的长度为0x80的空间内,并且还有个刷cache的操作。这和其它单片机的中断处理函数名是在汇编中指定不太一样,但实现的功能是一样的。 函数except_vec3_generic()位于genex.S中,使用汇编实现的,如下
/* * 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. */NESTED(except_vec3_generic, 0, sp) .set push .set noat#if R5432_CP0_INTERRUPT_WAR mfc0 k0, CP0_INDEX#endif mfc0 k1, CP0_CAUSE andi k1, k1, 0x7c#ifdef CONFIG_64BIT dsll k1, k1, 1#endif PTR_L k0, exception_handlers(k1) jr k0 .set pop END(except_vec3_generic)其中,命令 mfc0 k1, CP0_CAUSE andi k1, k1, 0x7c取协处理器0的cause寄存器中[2,6]位,即ExcCode,如下
并将ExcCode作为索引,命令
PTR_L k0, exception_handlers(k1) jr k0跳转到对应的异常处理函数中。
exception_handlers的注册在traps.c中用函数set_except_vector()实现
void __init *set_except_vector(int n, void *addr){ unsigned long handler = (unsigned long) addr; unsigned long old_handler = exception_handlers[n]; exception_handlers[n] = handler; if (n == 0 && cpu_has_divec) { unsigned long jump_mask = ~((1 << 28) - 1); u32 *buf = (u32 *)(ebase + 0x200); unsigned int k0 = 26; if ((handler & jump_mask) == ((ebase + 0x200) & jump_mask)) { uasm_i_j(&buf, handler & ~jump_mask); uasm_i_nop(&buf); } else { UASM_i_LA(&buf, k0, handler); uasm_i_jr(&buf, k0); uasm_i_nop(&buf); } local_flush_icache_range(ebase + 0x200, (unsigned long)buf); } return (void *)old_handler;}
其中ExcCode=0的异常为外设中断的总入口,ExcCode=0的异常才是我们关心的中断。
…… set_except_vector(0, rollback ? rollback_handle_int : handle_int); set_except_vector(1, handle_tlbm); set_except_vector(2, handle_tlbl); set_except_vector(3, handle_tlbs); set_except_vector(4, handle_adel); set_except_vector(5, handle_ades); set_except_vector(6, handle_ibe);……}函数handle_int()位于genex.S中,用汇编实现的,如下
.align 5BUILD_ROLLBACK_PROLOGUE handle_intNESTED(handle_int, PT_SIZE, sp)#ifdef CONFIG_TRACE_IRQFLAGS /* * Check to see if the interrupted code has just disabled * interrupts and ignore this interrupt for now if so. * * local_irq_disable() disables interrupts and then calls * trace_hardirqs_off() to track the state. If an interrupt is taken * after interrupts are disabled but before the state is updated * it will appear to restore_all that it is incorrectly returning with * interrupts disabled */ .set push .set noat mfc0 k0, CP0_STATUS#if defined(CONFIG_CPU_R3000) || defined(CONFIG_CPU_TX39XX) and k0, ST0_IEP bnez k0, 1f mfc0 k0, CP0_EPC .set noreorder j k0 rfe#else and k0, ST0_IE bnez k0, 1f eret#endif1: .set pop#endif SAVE_ALL CLI TRACE_IRQS_OFF LONG_L s0, TI_REGS($28) LONG_S sp, TI_REGS($28) PTR_LA ra, ret_from_irq j plat_irq_dispatch END(handle_int)
龙芯1c将所有外设分为五组
再来看下函数plat_irq_dispatch(),位于arch\mips\loongson\ls1x\irq.c中asmlinkage void plat_irq_dispatch(struct pt_regs *regs){ unsigned int pending; pending = read_c0_cause() & read_c0_status() & ST0_IM; if (pending & CAUSEF_IP7) { do_IRQ(TIMER_IRQ); } else if (pending & CAUSEF_IP2) { ls1x_irq_dispatch(0); } else if (pending & CAUSEF_IP3) { ls1x_irq_dispatch(1); } else if (pending & CAUSEF_IP4) { ls1x_irq_dispatch(2); } else if (pending & CAUSEF_IP5) { ls1x_irq_dispatch(3); } else if (pending & CAUSEF_IP6) { ls1x_irq_dispatch(4); } else { spurious_interrupt(); }}这个函数的作用是根据协处理器0的状态寄存器的IM7--0和原因寄存器的IP7--0来判断当前发生中断的是IP7--0中的那一个。 其中IP7为时钟中断,不是常说的定时器中断哦。
IP6-2共5个中断分别对应龙芯1c的五组中断,这个才是我们真正关心的龙芯1C上外设的中断,包括串口中断,PWM定时器中断,GPIO中断等。如下
IP1和IP0为软件中断位,龙芯1c没有使用这个功能,忽略。
正如《see mips run》中所说——Cause(IP7--2)照抄CPU硬件的输入信号。即不同的CPU可能不同。比如龙芯1C和龙芯2E的就不一样。从这里开始就是龙芯1C特有的。
龙芯1C上很多外设都有对应的中断,比如串口0-串口11共12个串口每个都有一个中断,106个GPIO每个都有一个中断,龙芯1C将这么多中断分为五组,每组32个。详细间龙芯1C的用户手册。
以上可见,第三四五组都用作GPIO中断了。
再来看看函数ls1x_irq_dispatch(),如下
static void ls1x_irq_dispatch(int n){ u32 intstatus, irq; /* Receive interrupt signal, compute the irq */ intstatus = (ls1x_icregs+n)->int_isr & (ls1x_icregs+n)->int_en; if (intstatus) { irq = ffs(intstatus); do_IRQ((n<<5) + irq - 1); }}根据每组中断寄存器中的中断状态寄存器(INTn_SR)和中断使能寄存器(INTn_EN)判断是否有中断发生,如果有,则执行对应的中断处理函数。 intstatus = (ls1x_icregs+n)->int_isr & (ls1x_icregs+n)->int_en; 这条语句就是判断是否有中断发生; irq = ffs(intstatus); 这条语句找出发生是谁发生了中断,即中断在该组中的编号; do_IRQ((n<<5) + irq - 1); 这条语句先将中断在该组的编号转换为全局的编号。 linux源码中arch\mips\include\asm\mach-loongson\ls1x\irq.h有龙芯1C支持的所有中断对应的中断号,如下
注意:GPIO[105:96]的中断号为[63:54],GPIO[95:0]的中断号为[159:64]
前面已经提到,在重新设置中断总入口后需要回写Dcache和作废Icache。这是是为什么呢?来看看《see mips run》中的说明
如果还不清楚,可以把《see mips run》中第4章——mips的高速缓存机制多看几遍。
再来看看,设置异常处理函数就好理解了/* Install CPU exception handler */void __init set_handler(unsigned long offset, void *addr, unsigned long size){ memcpy((void *)(ebase + offset), addr, size); local_flush_icache_range(ebase + offset, ebase + offset + size);}
在阅读linux代码时,经常会碰到函数指针,比如设置异常处理函数时用到的local_flush_icache_range。可以用
printk(“...%pf\n”, ...pFunc)这种形式将函数指针pFun指向的函数名打印出来。比如要打印函数指针local_flush_icache_range指向的函数名printk(KERN_NOTICE "[%s] local_flush_icache_range=%pf\n", __FUNCTION__, local_flush_icache_range);利用这个小技巧,可以跟踪到local_flush_icache_range指向local_r4k_flush_icache_range(),可是在函数local_r4k_flush_icache_range()中的protected_blast_icache_range()和protected_blast_dcache_range()却没找到定义和实现的地方。后来一位网友提供了一个path链接(https://www.linux-mips.org/archives/linux-mips/2006-02/msg00115.html),里面详细说明了blast_xxx_range(), protected_blast_xxx_range() etc. They are built by __BUILD_BLAST_CACHE_RANGE()。如下
由此可知,函数local_r4k_flush_icache_range()中的protected_blast_icache_range()和protected_blast_dcache_range()是通过宏__BUILD_BLAST_CACHE_RANGE()生成的。
宏__BUILD_BLAST_CACHE_RANGE()中,连接符“##”的功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串 (token)就是指编译器能够识别的最小语法单元。具体的定义在编译原理里有详尽的解释。比如__BUILD_BLAST_CACHE_RANGE(d, dcache, Hit_Writeback_Inv_D, protected_)中,四个宏参数的对应关系分别是Pfx 对应 dDesc 对应 dcacheHitop 对应 Hit_Writeback_Inv_DProt 对应 protected_代入“prot##blast_##pfx##cache##_range”中得到函数名”protected_blast_dcache_range”,函数protected_blast_icache_range()也是类似的。再来看看patch中的内容,如下注意每行前面的“-”和“+”。减号所在的行是不是和宏protected_blast_icache_range()展开后的内容相似啊。
函数protected_flush_icache_line()的实现在linux中也能找到,如下static inline void protected_flush_icache_line(unsigned long addr){ protected_cache_op(Hit_Invalidate_I, addr);}函数protected_cache_op()的函数名是不是宏__BUILD_BLAST_CACHE_RANGE()中的“prot##cache_op()”啊。
来看看protected_cache_op()的源码
#define protected_cache_op(op,addr) \ __asm__ __volatile__( \ " .set push \n" \ " .set noreorder \n" \ " .set mips3 \n" \ "1: cache %0, (%1) \n" \ "2: .set pop \n" \ " .section __ex_table,\"a\" \n" \ " "STR(PTR)" 1b, 2b \n" \ " .previous" \ : \ : "i" (op), "r" (addr))看上去一大堆,实际上就一条cache指令。”.set”,”.section”和”.previous”都不是汇编指令的助记符,不会被翻译为机器指令,而仅仅是给汇编器一些特殊指示,称为汇编指示或伪操作。简化后为
#define protected_cache_op(op,addr) \ __asm__ __volatile__( \ " cache %0, (%1) \n" \ : \ : "i" (op), "r" (addr))这个就很清晰了,入参op和addr分别对应%0和%1。
同理可以得到:函数指针r4k_blast_dcache()指向blast_dcache32(),函数blast_dcache32()由宏__BUILD_BLAST_CACHE()生成,如下
/* build blast_xxx, blast_xxx_page, blast_xxx_page_indexed */#define __BUILD_BLAST_CACHE(pfx, desc, indexop, hitop, lsize) \static inline void blast_##pfx##cache##lsize(void) \{ \ unsigned long start = INDEX_BASE; \ unsigned long end = start + current_cpu_data.desc.waysize; \ unsigned long ws_inc = 1UL << current_cpu_data.desc.waybit; \ unsigned long ws_end = current_cpu_data.desc.ways << \ current_cpu_data.desc.waybit; \ unsigned long ws, addr; \ \ __##pfx##flush_prologue \ \ for (ws = 0; ws < ws_end; ws += ws_inc) \ for (addr = start; addr < end; addr += lsize * 32) \ cache##lsize##_unroll32(addr|ws, indexop); \ \ __##pfx##flush_epilogue \}
《龙芯1C300处理器用户手册》 V1.4
《see mips run 中文版》第二版《mips linux异常中断代码分析》《mips cache arch.pdf》Linux源码http://blog.csdn.net/pbymw8iwm/article/details/8227839 GCC在C语言中内嵌汇编 asm __volatile__参考文档可以在百度或者龙芯1c库的git(http://git.oschina.net/caogos/OpenLoongsonLib1c)下载欢迎加qq群“633262684”一起讨论