博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【龙芯1c库】龙芯1c的中断分析
阅读量:2360 次
发布时间:2019-05-10

本文共 8730 字,大约阅读时间需要 29 分钟。

封装龙芯1c上常见外设接口,便于在裸机程序或实时操作系统中使用,类似于STM32库,正在不断添加中。Git地址是https://gitee.com/caogos/OpenLoongsonLib1c

龙芯1c中断结构分析

异常和中断的区别

在MIPS体系结构中,中断、自陷、系统调用以及其它打断程序正常执行流程的事件称为异常,

即异常包括中断,中断是一种特定类型的异常。
龙芯1c的异常分为四级
第一级: 各种情况下异常的总入口
第二级:各个异常的入口,其中ExcCode=0的异常为外设中断的总入口
第三级: 外设中断的每组的总入口(龙芯1c将所有外设分为五组)
第四级: 每个外设中断的入口

第一级——各种情况下异常的总入口

异常向量间的距离缺省为128字节(0x80)。

上表中,龙芯1CBASE0x80000000

异常地址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-25个中断分别对应龙芯1c的五组中断,这个才是我们真正关心的龙芯1C上外设的中断,包括串口中断,PWM定时器中断,GPIO中断等。如下

IP1和IP0为软件中断位,龙芯1c没有使用这个功能,忽略。

正如《see mips run》中所说——Cause(IP7--2)照抄CPU硬件的输入信号。即不同的CPU可能不同。比如龙芯1C和龙芯2E的就不一样。从这里开始就是龙芯1C特有的。

第四级——每个外设中断的入口

龙芯1C上很多外设都有对应的中断,比如串口0-串口1112个串口每个都有一个中断,106GPIO每个都有一个中断,龙芯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]

Cache

为什么在设置中断总入口后需要回写Dcache和作废Icache

前面已经提到,在重新设置中断总入口后需要回写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         对应   d
Desc        对应  dcache
Hitop       对应  Hit_Writeback_Inv_D
Prot        对应  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()”啊。

c函数内嵌汇编

来看看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”一起讨论

你可能感兴趣的文章
【转】免费进入学术数据库
查看>>
【转】matlab 使用的一点儿体会(for beginner)
查看>>
蚂蚁的哲学
查看>>
朴素的美丽、抑郁中的缕缕阳光——“红衣妹妹”于洋博文选粹
查看>>
最新的计算机方向的国际会议/期刊的排名
查看>>
《蚁群算法原理及其应用》(段海滨)附录Matlab源程序
查看>>
新的探索!
查看>>
小波图像分解与重构程序存在的问题与解决办法
查看>>
小波图像分解 Matlab 程序 - V2.0版
查看>>
小波图像重构 Matlab 程序 - V2.0版
查看>>
投到 ICIC 2008 的蚁群算法论文被录用了,喜忧参半!
查看>>
写科研论文的高级方法学 -- 小木虫上的精华(推荐阅读)
查看>>
Good News -- 投稿到《机器人》的论文也被录用了
查看>>
In this paper 与 In this study 的区别
查看>>
敏捷开发一千零一问系列之三十六:如何做小版本迭代的代码管理
查看>>
敏捷开发产品管理系列之九:划分产品子系统
查看>>
敏捷开发一千零一问系列之三十六:跨平台开发的人员和代码复用
查看>>
关于微软的VB和C#:为何Basic需要存在,为何VB如此像C#,为何两者不合并等
查看>>
度量分析之报告信息的四个层次:数据,信息,分析,措施
查看>>
如何将asp.net MVC2项目升级为MVC3项目(微软官方自动升级工具:ASP.NET MVC 3 Application Upgrader )
查看>>