mips的tlbrefill和tlb load exception处理函数,都是用自己的一套编译器来实现的。
这么做官方的解释是为了兼容,想想也是,只需要修改一下指令表就可以做到了。
编译器的核心在arch/mips/mm/uasm.c
包括关键指令码生成辅助函数build_insn,以及关键数据insn_table
build_insn的作用是根据传入的op操作指示,以及op附带的参数,比照模板insn_table,去生成一个实际的指令码。
以addu为例,预备数据结构:
enum opcode {
insn_invalid,
insn_addu,
struct insn {
enum opcode opcode;
u32 match;
enum fields fields;
};
/* This macro sets the non-variable bits of an instruction. */
#define M(a, b, c, d, e, f) \
((a) << OP_SH \
| (b) << RS_SH \
| (c) << RT_SH \
| (d) << RD_SH \
| (e) << RE_SH \
| (f) << FUNC_SH)
static struct insn insn_table[]
{ insn_addiu, M(addiu_op, 0, 0, 0, 0, 0), RS | RT | SIMM },
{ insn_addu, M(spec_op, 0, 0, 0, 0, addu_op), RS | RT | RD },
首先,I_u3u1u2(_addu)定义了一个函数
uasm_i_addu(u32 **buf, unsigned int a, unsigned int b, unsigned int c)
{
build_insn(buf, insn_addu, b, c, a);
}
来看build_insn的实现
首先,是从insn_table里找出数组结构体成员的第一个分量等于insn_addu的元素ip;
接着,取ip的第二个分量的初始值op = ip->match,这个值的保存最终的指令码,先填充初始值的意思是,
先把addu这条指令需要的辨别码写入,而其余的RS,RT,RD位则根据传入b,c,a来填充;
具体的填充流程即一个分支判断,
由于addu所在的数组元素具有RS | RT | RD属性,则会走下面的流程
if (ip->fields & RS)
op |= build_rs(va_arg(ap, u32));
if (ip->fields & RT)
op |= build_rt(va_arg(ap, u32));
if (ip->fields & RD)
op |= build_rd(va_arg(ap, u32));
build_rd等函数则将参数写入具体的指令码位置,最后保存在编译器缓冲里
**buf = op;
(*buf)++;
使用的时候,则调用uasm_i_addu,按照格式填写即可。
假如我们希望在tlb refill里添加每cpu统计计数,则可以在
build_r4000_tlb_refill_handler添加:
unsigned long cpu_tlb_miss[NR_CPU];
uasm_i_dmfc0(p, K0 C0_CONTEXT);
uasm_i_dsrl(p, K0, K0, 23);
UASM_i_LA(p, K1, cpu_tlb_miss);
uasm_i_daddu(p, K1, K0, K1);
uasm_i_ld(p, K0, 0, K1);
uasm_i_daddiu(p, K0, K0, 1);
uasm_i_sd(p, K0, 0, K1);