【龙芯1c库】龙芯1c的中断分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/caogos/article/details/69948579

封装龙芯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  5
BUILD_ROLLBACK_PROLOGUE handle_int
NESTED(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
#endif
1:
	.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”一起讨论


没有更多推荐了,返回首页