/* * Copyright (C) 2006-2010 Michael Buesch * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "main.h" #include "list.h" #include "util.h" #include "parser.h" #include "args.h" #include "initvals.h" #include #include #include extern int yyparse(void); extern int yydebug; struct file infile; const char *infile_name; const char *outfile_name; struct out_operand { enum { OUTOPER_NORMAL, OUTOPER_LABELREF, } type; union { unsigned int operand; /* For NORMAL */ struct label *label; /* For LABELREF */ } u; }; struct code_output { enum { OUT_INSN, OUT_LABEL, } type; /* Set to true, if this is a jump instruction. * This is only used when assembling RET to check * whether the previous instruction was a jump or not. */ bool is_jump_insn; unsigned int opcode; struct out_operand operands[3]; /* The absolute address of this instruction. * Only used in resolve_labels(). */ unsigned int address; const char *labelname; /* only for OUT_LABEL */ /* Set to 1, if this is the %start instruction. */ int is_start_insn; struct list_head list; }; struct assembler_context { /* The architecture version (802.11 core revision) */ unsigned int arch; struct label *start_label; /* Tracking stuff */ struct statement *cur_stmt; struct list_head output; }; #define for_each_statement(ctx, s) \ list_for_each_entry(s, &infile.sl, list) { \ ctx->cur_stmt = s; #define for_each_statement_end(ctx, s) \ } do { ctx->cur_stmt = NULL; } while (0) #define _msg_helper(type, stmt, msg, x...) do { \ fprintf(stderr, "Assembler " type); \ if (stmt) { \ fprintf(stderr, " (file \"%s\", line %u)", \ stmt->info.file, \ stmt->info.lineno); \ } \ fprintf(stderr, ":\n " msg "\n" ,##x); \ } while (0) #define asm_error(ctx, msg, x...) do { \ _msg_helper("ERROR", (ctx)->cur_stmt, msg ,##x); \ exit(1); \ } while (0) #define asm_warn(ctx, msg, x...) \ _msg_helper("warning", (ctx)->cur_stmt, msg ,##x) #define asm_info(ctx, msg, x...) \ _msg_helper("info", (ctx)->cur_stmt, msg ,##x) static void eval_directives(struct assembler_context *ctx) { struct statement *s; struct asmdir *ad; struct label *l; int have_start_label = 0; int have_arch = 0; unsigned int arch_fallback = 0; for_each_statement(ctx, s) { if (s->type == STMT_ASMDIR) { ad = s->u.asmdir; switch (ad->type) { case ADIR_ARCH: if (have_arch) asm_error(ctx, "Multiple %%arch definitions"); ctx->arch = ad->u.arch; if (ctx->arch > 5 && ctx->arch < 15) arch_fallback = 5; if (ctx->arch > 15) arch_fallback = 15; if (arch_fallback) { asm_warn(ctx, "Using %%arch %d is incorrect. " "The wireless core revision %d uses the " "firmware architecture %d. So use %%arch %d", ctx->arch, ctx->arch, arch_fallback, arch_fallback); ctx->arch = arch_fallback; } if (ctx->arch != 5 && ctx->arch != 15) { asm_error(ctx, "Architecture version %u unsupported", ctx->arch); } have_arch = 1; break; case ADIR_START: if (have_start_label) asm_error(ctx, "Multiple %%start definitions"); ctx->start_label = ad->u.start; have_start_label = 1; break; default: asm_error(ctx, "Unknown ASM directive"); } } } for_each_statement_end(ctx, s); if (!have_arch) asm_error(ctx, "No %%arch defined"); if (!have_start_label) asm_info(ctx, "Using start address 0"); } static bool is_possible_imm(unsigned int imm) { unsigned int mask; /* Immediates are only possible up to 16bit (wordsize). */ mask = ~0; mask <<= 16; if (imm & (1 << 15)) { if ((imm & mask) != mask && (imm & mask) != 0) return 0; } else { if ((imm & mask) != 0) return 0; } return 1; } static unsigned int immediate_nr_bits(struct assembler_context *ctx) { switch (ctx->arch) { case 5: return 10; /* 10 bits */ case 15: return 11; /* 11 bits */ } asm_error(ctx, "Internal error: immediate_nr_bits unknown arch\n"); } static bool is_valid_imm(struct assembler_context *ctx, unsigned int imm) { unsigned int mask; unsigned int immediate_size; /* This function checks if the immediate value is representable * as a native immediate operand. * * For v5 architecture the immediate can be 10bit long. * For v15 architecture the immediate can be 11bit long. * * The value is sign-extended, so we allow values * of 0xFFFA, for example. */ if (!is_possible_imm(imm)) return 0; imm &= 0xFFFF; immediate_size = immediate_nr_bits(ctx); /* First create a mask with all possible bits for * an immediate value unset. */ mask = (~0 << immediate_size) & 0xFFFF; /* Is the sign bit of the immediate set? */ if (imm & (1 << (immediate_size - 1))) { /* Yes, so all bits above that must also * be set, otherwise we can't represent this * value in an operand. */ if ((imm & mask) != mask) return 0; } else { /* All bits above the immediate's size must * be unset. */ if (imm & mask) return 0; } return 1; } /* This checks if the value is nonzero and a power of two. */ static bool is_power_of_two(unsigned int value) { return (value && ((value & (value - 1)) == 0)); } /* This checks if all bits set in the mask are contiguous. * Zero is also considered a contiguous mask. */ static bool is_contiguous_bitmask(unsigned int mask) { unsigned int low_zeros_mask; bool is_contiguous; if (mask == 0) return 1; /* Turn the lowest zeros of the mask into a bitmask. * Example: 0b00011000 -> 0b00000111 */ low_zeros_mask = (mask - 1) & ~mask; /* Adding the low_zeros_mask to the original mask * basically is a bitwise OR operation. * If the original mask was contiguous, we end up with a * contiguous bitmask from bit 0 to the highest bit * set in the original mask. Adding 1 will result in a single * bit set, which is a power of two. */ is_contiguous = is_power_of_two(mask + low_zeros_mask + 1); return is_contiguous; } static unsigned int generate_imm_operand(struct assembler_context *ctx, const struct immediate *imm) { unsigned int val, tmp; unsigned int mask; val = 0xC00; if (ctx->arch == 15) val <<= 1; tmp = imm->imm; if (!is_valid_imm(ctx, tmp)) { asm_warn(ctx, "IMMEDIATE 0x%X (%d) too long " "(> %u bits + sign). Did you intend to " "use implicit sign extension?", tmp, (int)tmp, immediate_nr_bits(ctx) - 1); } if (ctx->arch == 15) tmp &= 0x7FF; else tmp &= 0x3FF; val |= tmp; return val; } static unsigned int generate_reg_operand(struct assembler_context *ctx, const struct registr *reg) { unsigned int val = 0; switch (reg->type) { case GPR: val |= 0xBC0; if (ctx->arch == 15) val <<= 1; if (reg->nr & ~0x3F) /* REVISIT: 128 regs for v15 arch possible? Probably not... */ asm_error(ctx, "GPR-nr too big"); val |= reg->nr; break; case SPR: val |= 0x800; if (ctx->arch == 15) val <<= 1; if (reg->nr & ~0x1FF) asm_error(ctx, "SPR-nr too big"); val |= reg->nr; break; case OFFR: val |= 0x860; if (ctx->arch == 15) val <<= 1; if (reg->nr & ~0x7) asm_error(ctx, "OFFR-nr too big"); val |= reg->nr; break; default: asm_error(ctx, "generate_reg_operand() regtype"); } return val; } static unsigned int generate_mem_operand(struct assembler_context *ctx, const struct memory *mem) { unsigned int val = 0, off, reg, off_mask, reg_shift; switch (mem->type) { case MEM_DIRECT: off = mem->offset; switch (ctx->arch) { case 5: if (off & ~0x7FF) { asm_warn(ctx, "DIRECT memoffset 0x%X too long (> 11 bits)", off); off &= 0x7FF; } break; case 15: if (off & ~0xFFF) { asm_warn(ctx, "DIRECT memoffset 0x%X too long (> 12 bits)", off); off &= 0xFFF; } break; default: asm_error(ctx, "Internal error: generate_mem_operand invalid arch"); } val |= off; break; case MEM_INDIRECT: switch (ctx->arch) { case 5: val = 0xA00; off_mask = 0x3F; reg_shift = 6; break; case 15: val = 0x1400; off_mask = 0x7F; reg_shift = 7; break; default: asm_error(ctx, "Internal error: MEM_INDIRECT invalid arch\n"); } off = mem->offset; reg = mem->offr_nr; if (off & ~off_mask) { asm_warn(ctx, "INDIRECT memoffset 0x%X too long (> %u bits)", off, reg_shift); off &= off_mask; } if (reg > 6) { /* Assembler bug. The parser shouldn't pass this value. */ asm_error(ctx, "OFFR-nr too big"); } if (reg == 6) { asm_warn(ctx, "Using offset register 6. This register is broken " "on certain devices. Use off0 to off5 only."); } val |= off; val |= (reg << reg_shift); break; default: asm_error(ctx, "generate_mem_operand() memtype"); } return val; } static void generate_operand(struct assembler_context *ctx, const struct operand *oper, struct out_operand *out) { out->type = OUTOPER_NORMAL; switch (oper->type) { case OPER_IMM: out->u.operand = generate_imm_operand(ctx, oper->u.imm); break; case OPER_REG: out->u.operand = generate_reg_operand(ctx, oper->u.reg); break; case OPER_MEM: out->u.operand = generate_mem_operand(ctx, oper->u.mem); break; case OPER_LABEL: out->type = OUTOPER_LABELREF; out->u.label = oper->u.label; break; case OPER_ADDR: out->u.operand = oper->u.addr->addr; break; case OPER_RAW: out->u.operand = oper->u.raw; break; default: asm_error(ctx, "generate_operand() operstate"); } } static struct code_output * do_assemble_insn(struct assembler_context *ctx, struct instruction *insn, unsigned int opcode) { unsigned int i; struct operlist *ol; int nr_oper = 0; uint64_t code = 0; struct code_output *out; struct label *labelref = NULL; struct operand *oper; int have_spr_operand = 0; int have_mem_operand = 0; out = xmalloc(sizeof(*out)); INIT_LIST_HEAD(&out->list); out->opcode = opcode; ol = insn->operands; if (ARRAY_SIZE(out->operands) > ARRAY_SIZE(ol->oper)) asm_error(ctx, "Internal operand array confusion"); for (i = 0; i < ARRAY_SIZE(out->operands); i++) { oper = ol->oper[i]; if (!oper) continue; /* If this is an INPUT operand (first or second), we must * make sure that not both are accessing SPR or MEMORY. * The device only supports one SPR or MEMORY operand in * the input operands. */ if ((i == 0) || (i == 1)) { if ((oper->type == OPER_REG) && (oper->u.reg->type == SPR)) { if (have_spr_operand) asm_error(ctx, "Multiple SPR input operands in one instruction"); have_spr_operand = 1; } if (oper->type == OPER_MEM) { if (have_mem_operand) asm_error(ctx, "Multiple MEMORY input operands in on instruction"); have_mem_operand = 1; } } generate_operand(ctx, oper, &out->operands[i]); nr_oper++; } if (nr_oper != 3) asm_error(ctx, "Internal error: nr_oper at " "lowlevel do_assemble_insn"); list_add_tail(&out->list, &ctx->output); return out; } static void do_assemble_ret(struct assembler_context *ctx, struct instruction *insn, unsigned int opcode) { struct code_output *out; /* Get the previous instruction and check whether it * is a jump instruction. */ list_for_each_entry_reverse(out, &ctx->output, list) { /* Search the last insn. */ if (out->type == OUT_INSN) { if (out->is_jump_insn) { asm_warn(ctx, "RET instruction directly after " "jump instruction. The hardware won't like this."); } break; } } do_assemble_insn(ctx, insn, opcode); } static unsigned int merge_ext_into_opcode(struct assembler_context *ctx, unsigned int opbase, struct instruction *insn) { struct operlist *ol; unsigned int opcode; unsigned int mask, shift; ol = insn->operands; opcode = opbase; mask = ol->oper[0]->u.raw; if (mask & ~0xF) asm_error(ctx, "opcode MASK extension too big (> 0xF)"); shift = ol->oper[1]->u.raw; if (shift & ~0xF) asm_error(ctx, "opcode SHIFT extension too big (> 0xF)"); opcode |= (mask << 4); opcode |= shift; ol->oper[0] = ol->oper[2]; ol->oper[1] = ol->oper[3]; ol->oper[2] = ol->oper[4]; return opcode; } static unsigned int merge_external_jmp_into_opcode(struct assembler_context *ctx, unsigned int opbase, struct instruction *insn) { struct operand *fake; struct registr *fake_reg; struct operand *target; struct operlist *ol; unsigned int cond; unsigned int opcode; ol = insn->operands; opcode = opbase; cond = ol->oper[0]->u.imm->imm; if (cond & ~0xFF) asm_error(ctx, "External jump condition value too big (> 0xFF)"); opcode |= cond; target = ol->oper[1]; memset(ol->oper, 0, sizeof(ol->oper)); /* This instruction has two fake r0 operands * at position 0 and 1. */ fake = xmalloc(sizeof(*fake)); fake_reg = xmalloc(sizeof(*fake_reg)); fake->type = OPER_REG; fake->u.reg = fake_reg; fake_reg->type = GPR; fake_reg->nr = 0; ol->oper[0] = fake; ol->oper[1] = fake; ol->oper[2] = target; return opcode; } static void assemble_instruction(struct assembler_context *ctx, struct instruction *insn); static void emulate_mov_insn(struct assembler_context *ctx, struct instruction *insn) { struct instruction em_insn; struct operlist em_ol; struct operand em_op_shift; struct operand em_op_mask; struct operand em_op_x; struct operand em_op_y; struct immediate em_imm_x; struct immediate em_imm_y; struct operand *in, *out; unsigned int tmp; /* This is a pseudo-OP. We emulate it by OR or ORX */ in = insn->operands->oper[0]; out = insn->operands->oper[1]; em_insn.op = OP_OR; em_ol.oper[0] = in; em_imm_x.imm = 0; em_op_x.type = OPER_IMM; em_op_x.u.imm = &em_imm_x; em_ol.oper[1] = &em_op_x; em_ol.oper[2] = out; if (in->type == OPER_IMM) { tmp = in->u.imm->imm; if (!is_possible_imm(tmp)) asm_error(ctx, "MOV operand 0x%X > 16bit", tmp); if (!is_valid_imm(ctx, tmp)) { /* Immediate too big for plain OR */ em_insn.op = OP_ORX; em_op_mask.type = OPER_RAW; em_op_mask.u.raw = 0x7; em_op_shift.type = OPER_RAW; em_op_shift.u.raw = 0x8; em_imm_x.imm = (tmp & 0xFF00) >> 8; em_op_x.type = OPER_IMM; em_op_x.u.imm = &em_imm_x; em_imm_y.imm = (tmp & 0x00FF); em_op_y.type = OPER_IMM; em_op_y.u.imm = &em_imm_y; em_ol.oper[0] = &em_op_mask; em_ol.oper[1] = &em_op_shift; em_ol.oper[2] = &em_op_x; em_ol.oper[3] = &em_op_y; em_ol.oper[4] = out; } } em_insn.operands = &em_ol; assemble_instruction(ctx, &em_insn); /* recurse */ } static void emulate_jmp_insn(struct assembler_context *ctx, struct instruction *insn) { struct instruction em_insn; struct operlist em_ol; struct immediate em_condition; struct operand em_cond_op; /* This is a pseudo-OP. We emulate it with * JEXT 0x7F, target */ em_insn.op = OP_JEXT; em_condition.imm = 0x7F; /* Ext cond: Always true */ em_cond_op.type = OPER_IMM; em_cond_op.u.imm = &em_condition; em_ol.oper[0] = &em_cond_op; em_ol.oper[1] = insn->operands->oper[0]; /* Target */ em_insn.operands = &em_ol; assemble_instruction(ctx, &em_insn); /* recurse */ } static void emulate_jand_insn(struct assembler_context *ctx, struct instruction *insn, int inverted) { struct code_output *out; struct instruction em_insn; struct operlist em_ol; struct operand em_op_shift; struct operand em_op_mask; struct operand em_op_y; struct immediate em_imm; struct operand *oper0, *oper1, *oper2; struct operand *imm_oper = NULL; unsigned int tmp; int first_bit, last_bit; oper0 = insn->operands->oper[0]; oper1 = insn->operands->oper[1]; oper2 = insn->operands->oper[2]; if (oper0->type == OPER_IMM) imm_oper = oper0; if (oper1->type == OPER_IMM) imm_oper = oper1; if (oper0->type == OPER_IMM && oper1->type == OPER_IMM) imm_oper = NULL; if (imm_oper) { /* We have a single immediate operand. * Check if it's representable by a normal JAND insn. */ tmp = imm_oper->u.imm->imm; if (!is_valid_imm(ctx, tmp)) { /* Nope, this must be emulated by JZX/JNZX */ if (!is_contiguous_bitmask(tmp)) { asm_error(ctx, "Long bitmask 0x%X is not contiguous", tmp); } first_bit = ffs(tmp); last_bit = ffs(~(tmp >> (first_bit - 1))) - 1 + first_bit - 1; if (inverted) em_insn.op = OP_JZX; else em_insn.op = OP_JNZX; em_op_shift.type = OPER_RAW; em_op_shift.u.raw = first_bit - 1; em_op_mask.type = OPER_RAW; em_op_mask.u.raw = last_bit - first_bit; em_imm.imm = 0; em_op_y.type = OPER_IMM; em_op_y.u.imm = &em_imm; em_ol.oper[0] = &em_op_mask; em_ol.oper[1] = &em_op_shift; if (oper0->type != OPER_IMM) em_ol.oper[2] = oper0; else em_ol.oper[2] = oper1; em_ol.oper[3] = &em_op_y; em_ol.oper[4] = oper2; em_insn.operands = &em_ol; assemble_instruction(ctx, &em_insn); /* recurse */ return; } } /* Do a normal JAND/JNAND instruction */ if (inverted) out = do_assemble_insn(ctx, insn, 0x040 | 0x1); else out = do_assemble_insn(ctx, insn, 0x040); out->is_jump_insn = 1; } static void assemble_instruction(struct assembler_context *ctx, struct instruction *insn) { struct code_output *out; unsigned int opcode; switch (insn->op) { case OP_MUL: do_assemble_insn(ctx, insn, 0x101); break; case OP_ADD: do_assemble_insn(ctx, insn, 0x1C0); break; case OP_ADDSC: do_assemble_insn(ctx, insn, 0x1C2); break; case OP_ADDC: do_assemble_insn(ctx, insn, 0x1C1); break; case OP_ADDSCC: do_assemble_insn(ctx, insn, 0x1C3); break; case OP_SUB: do_assemble_insn(ctx, insn, 0x1D0); break; case OP_SUBSC: do_assemble_insn(ctx, insn, 0x1D2); break; case OP_SUBC: do_assemble_insn(ctx, insn, 0x1D1); break; case OP_SUBSCC: do_assemble_insn(ctx, insn, 0x1D3); break; case OP_SRA: do_assemble_insn(ctx, insn, 0x130); break; case OP_OR: do_assemble_insn(ctx, insn, 0x160); break; case OP_AND: do_assemble_insn(ctx, insn, 0x140); break; case OP_XOR: do_assemble_insn(ctx, insn, 0x170); break; case OP_SR: do_assemble_insn(ctx, insn, 0x120); break; case OP_SRX: opcode = merge_ext_into_opcode(ctx, 0x200, insn); do_assemble_insn(ctx, insn, opcode); break; case OP_SL: do_assemble_insn(ctx, insn, 0x110); break; case OP_RL: do_assemble_insn(ctx, insn, 0x1A0); break; case OP_RR: do_assemble_insn(ctx, insn, 0x1B0); break; case OP_NAND: do_assemble_insn(ctx, insn, 0x150); break; case OP_ORX: opcode = merge_ext_into_opcode(ctx, 0x300, insn); do_assemble_insn(ctx, insn, opcode); break; case OP_MOV: emulate_mov_insn(ctx, insn); return; case OP_JMP: emulate_jmp_insn(ctx, insn); return; case OP_JAND: emulate_jand_insn(ctx, insn, 0); return; case OP_JNAND: emulate_jand_insn(ctx, insn, 1); return; case OP_JS: out = do_assemble_insn(ctx, insn, 0x050); out->is_jump_insn = 1; break; case OP_JNS: out = do_assemble_insn(ctx, insn, 0x050 | 0x1); out->is_jump_insn = 1; break; case OP_JE: out = do_assemble_insn(ctx, insn, 0x0D0); out->is_jump_insn = 1; break; case OP_JNE: out = do_assemble_insn(ctx, insn, 0x0D0 | 0x1); out->is_jump_insn = 1; break; case OP_JLS: out = do_assemble_insn(ctx, insn, 0x0D2); out->is_jump_insn = 1; break; case OP_JGES: out = do_assemble_insn(ctx, insn, 0x0D2 | 0x1); out->is_jump_insn = 1; break; case OP_JGS: out = do_assemble_insn(ctx, insn, 0x0D4); out->is_jump_insn = 1; break; case OP_JLES: out = do_assemble_insn(ctx, insn, 0x0D4 | 0x1); out->is_jump_insn = 1; break; case OP_JL: out = do_assemble_insn(ctx, insn, 0x0DA); out->is_jump_insn = 1; break; case OP_JGE: out = do_assemble_insn(ctx, insn, 0x0DA | 0x1); out->is_jump_insn = 1; break; case OP_JG: out = do_assemble_insn(ctx, insn, 0x0DC); break; case OP_JLE: out = do_assemble_insn(ctx, insn, 0x0DC | 0x1); out->is_jump_insn = 1; break; case OP_JDN: out = do_assemble_insn(ctx, insn, 0x0D6); out->is_jump_insn = 1; break; case OP_JDPZ: out = do_assemble_insn(ctx, insn, 0x0D6 | 0x1); out->is_jump_insn = 1; break; case OP_JDP: out = do_assemble_insn(ctx, insn, 0x0D8); out->is_jump_insn = 1; break; case OP_JDNZ: out = do_assemble_insn(ctx, insn, 0x0D8 | 0x1); out->is_jump_insn = 1; break; case OP_JZX: opcode = merge_ext_into_opcode(ctx, 0x400, insn); out = do_assemble_insn(ctx, insn, opcode); out->is_jump_insn = 1; break; case OP_JNZX: opcode = merge_ext_into_opcode(ctx, 0x500, insn); out = do_assemble_insn(ctx, insn, opcode); out->is_jump_insn = 1; break; case OP_JEXT: opcode = merge_external_jmp_into_opcode(ctx, 0x700, insn); out = do_assemble_insn(ctx, insn, opcode); out->is_jump_insn = 1; break; case OP_JNEXT: opcode = merge_external_jmp_into_opcode(ctx, 0x600, insn); out = do_assemble_insn(ctx, insn, opcode); out->is_jump_insn = 1; break; case OP_CALL: if (ctx->arch != 5) asm_error(ctx, "'call' instruction is only supported on arch 5"); do_assemble_insn(ctx, insn, 0x002); break; case OP_CALLS: if (ctx->arch != 15) asm_error(ctx, "'calls' instruction is only supported on arch 15"); do_assemble_insn(ctx, insn, 0x004); break; case OP_RET: if (ctx->arch != 5) asm_error(ctx, "'ret' instruction is only supported on arch 5"); do_assemble_ret(ctx, insn, 0x003); break; case OP_RETS: if (ctx->arch != 15) asm_error(ctx, "'rets' instruction is only supported on arch 15"); do_assemble_insn(ctx, insn, 0x005); break; case OP_TKIPH: case OP_TKIPHS: case OP_TKIPL: case OP_TKIPLS: do_assemble_insn(ctx, insn, 0x1E0); break; case OP_NAP: do_assemble_insn(ctx, insn, 0x001); break; case RAW_CODE: do_assemble_insn(ctx, insn, insn->opcode); break; default: asm_error(ctx, "Unknown op"); } } static void assemble_instructions(struct assembler_context *ctx) { struct statement *s; struct instruction *insn; struct code_output *out; if (ctx->start_label) { /* Generate a jump instruction at offset 0 to * jump to the code start. */ struct instruction sjmp; struct operlist ol; struct operand oper; oper.type = OPER_LABEL; oper.u.label = ctx->start_label; ol.oper[0] = &oper; sjmp.op = OP_JMP; sjmp.operands = &ol; assemble_instruction(ctx, &sjmp); out = list_entry(ctx->output.next, struct code_output, list); out->is_start_insn = 1; } for_each_statement(ctx, s) { switch (s->type) { case STMT_INSN: ctx->cur_stmt = s; insn = s->u.insn; assemble_instruction(ctx, insn); break; case STMT_LABEL: out = xmalloc(sizeof(*out)); INIT_LIST_HEAD(&out->list); out->type = OUT_LABEL; out->labelname = s->u.label->name; list_add_tail(&out->list, &ctx->output); break; case STMT_ASMDIR: break; } } for_each_statement_end(ctx, s); } /* Resolve a label reference to the address it points to. */ static int get_labeladdress(struct assembler_context *ctx, struct code_output *this_insn, struct label *labelref) { struct code_output *c; bool found = 0; int address = -1; switch (labelref->direction) { case LABELREF_ABSOLUTE: list_for_each_entry(c, &ctx->output, list) { if (c->type != OUT_LABEL) continue; if (strcmp(c->labelname, labelref->name) != 0) continue; if (found) { asm_error(ctx, "Ambiguous label reference \"%s\"", labelref->name); } found = 1; address = c->address; } break; case LABELREF_RELATIVE_BACK: for (c = list_entry(this_insn->list.prev, typeof(*c), list); &c->list != &ctx->output; c = list_entry(c->list.prev, typeof(*c), list)) { if (c->type != OUT_LABEL) continue; if (strcmp(c->labelname, labelref->name) == 0) { /* Found */ address = c->address; break; } } break; case LABELREF_RELATIVE_FORWARD: for (c = list_entry(this_insn->list.next, typeof(*c), list); &c->list != &ctx->output; c = list_entry(c->list.next, typeof(*c), list)) { if (c->type != OUT_LABEL) continue; if (strcmp(c->labelname, labelref->name) == 0) { /* Found */ address = c->address; break; } } break; } return address; } static void resolve_labels(struct assembler_context *ctx) { struct code_output *c; int addr; unsigned int i; unsigned int current_address; /* Calculate the absolute addresses for each instruction. */ recalculate_addresses: current_address = 0; list_for_each_entry(c, &ctx->output, list) { switch (c->type) { case OUT_INSN: c->address = current_address; current_address++; break; case OUT_LABEL: c->address = current_address; break; } } /* Resolve the symbolic label references. */ list_for_each_entry(c, &ctx->output, list) { switch (c->type) { case OUT_INSN: if (c->is_start_insn) { /* If the first %start-jump jumps to 001, we can * optimize it away, as it's unneeded. */ i = 2; if (c->operands[i].type != OUTOPER_LABELREF) asm_error(ctx, "Internal error, %%start insn oper 2 not labelref"); if (c->operands[i].u.label->direction != LABELREF_ABSOLUTE) asm_error(ctx, "%%start label reference not absolute"); addr = get_labeladdress(ctx, c, c->operands[i].u.label); if (addr < 0) goto does_not_exist; if (addr == 1) { list_del(&c->list); /* Kill it */ goto recalculate_addresses; } } for (i = 0; i < ARRAY_SIZE(c->operands); i++) { if (c->operands[i].type != OUTOPER_LABELREF) continue; addr = get_labeladdress(ctx, c, c->operands[i].u.label); if (addr < 0) goto does_not_exist; c->operands[i].u.operand = addr; if (i != 2) { /* Is not a jump target. * Make it be an immediate */ if (ctx->arch == 5) c->operands[i].u.operand |= 0xC00; else if (ctx->arch == 15) c->operands[i].u.operand |= 0xC00 << 1; else asm_error(ctx, "Internal error: label res imm"); } } break; case OUT_LABEL: break; } } return; does_not_exist: asm_error(ctx, "Label \"%s\" does not exist", c->operands[i].u.label->name); } static void emit_code(struct assembler_context *ctx) { FILE *fd; const char *fn; struct code_output *c; uint64_t code; unsigned char outbuf[8]; unsigned int insn_count = 0, insn_count_limit; struct fw_header hdr; fn = outfile_name; fd = fopen(fn, "w+"); if (!fd) { fprintf(stderr, "Could not open microcode output file \"%s\"\n", fn); exit(1); } if (IS_VERBOSE_DEBUG) printf("\nCode:\n"); list_for_each_entry(c, &ctx->output, list) { switch (c->type) { case OUT_INSN: insn_count++; break; default: break; } } switch (cmdargs.outformat) { case FMT_RAW_LE32: case FMT_RAW_BE32: /* Nothing */ break; case FMT_B43: memset(&hdr, 0, sizeof(hdr)); hdr.type = FW_TYPE_UCODE; hdr.ver = FW_HDR_VER; hdr.size = cpu_to_be32(8 * insn_count); if (fwrite(&hdr, sizeof(hdr), 1, fd) != 1) { fprintf(stderr, "Could not write microcode outfile\n"); exit(1); } break; } switch (ctx->arch) { case 5: insn_count_limit = NUM_INSN_LIMIT_R5; break; case 15: insn_count_limit = ~0; //FIXME limit currently unknown. break; default: asm_error(ctx, "Internal error: emit_code unknown arch\n"); } if (insn_count > insn_count_limit) asm_warn(ctx, "Generating more than %u instructions. This " "will overflow the device microcode memory.", insn_count_limit); list_for_each_entry(c, &ctx->output, list) { switch (c->type) { case OUT_INSN: if (IS_VERBOSE_DEBUG) { printf("%03X %04X,%04X,%04X\n", c->opcode, c->operands[0].u.operand, c->operands[1].u.operand, c->operands[2].u.operand); } switch (ctx->arch) { case 5: code = 0; code |= ((uint64_t)c->operands[2].u.operand); code |= ((uint64_t)c->operands[1].u.operand) << 12; code |= ((uint64_t)c->operands[0].u.operand) << 24; code |= ((uint64_t)c->opcode) << 36; break; case 15: code = 0; code |= ((uint64_t)c->operands[2].u.operand); code |= ((uint64_t)c->operands[1].u.operand) << 13; code |= ((uint64_t)c->operands[0].u.operand) << 26; code |= ((uint64_t)c->opcode) << 39; break; default: asm_error(ctx, "No emit format for arch %u", ctx->arch); } switch (cmdargs.outformat) { case FMT_B43: case FMT_RAW_BE32: code = ((code & (uint64_t)0xFFFFFFFF00000000ULL) >> 32) | ((code & (uint64_t)0x00000000FFFFFFFFULL) << 32); outbuf[0] = (code & (uint64_t)0xFF00000000000000ULL) >> 56; outbuf[1] = (code & (uint64_t)0x00FF000000000000ULL) >> 48; outbuf[2] = (code & (uint64_t)0x0000FF0000000000ULL) >> 40; outbuf[3] = (code & (uint64_t)0x000000FF00000000ULL) >> 32; outbuf[4] = (code & (uint64_t)0x00000000FF000000ULL) >> 24; outbuf[5] = (code & (uint64_t)0x0000000000FF0000ULL) >> 16; outbuf[6] = (code & (uint64_t)0x000000000000FF00ULL) >> 8; outbuf[7] = (code & (uint64_t)0x00000000000000FFULL) >> 0; break; case FMT_RAW_LE32: outbuf[7] = (code & (uint64_t)0xFF00000000000000ULL) >> 56; outbuf[6] = (code & (uint64_t)0x00FF000000000000ULL) >> 48; outbuf[5] = (code & (uint64_t)0x0000FF0000000000ULL) >> 40; outbuf[4] = (code & (uint64_t)0x000000FF00000000ULL) >> 32; outbuf[3] = (code & (uint64_t)0x00000000FF000000ULL) >> 24; outbuf[2] = (code & (uint64_t)0x0000000000FF0000ULL) >> 16; outbuf[1] = (code & (uint64_t)0x000000000000FF00ULL) >> 8; outbuf[0] = (code & (uint64_t)0x00000000000000FFULL) >> 0; break; } if (fwrite(&outbuf, ARRAY_SIZE(outbuf), 1, fd) != 1) { fprintf(stderr, "Could not write microcode outfile\n"); exit(1); } break; case OUT_LABEL: break; } } if (cmdargs.print_sizes) { printf("%s: text = %u instructions (%u bytes)\n", fn, insn_count, (unsigned int)(insn_count * sizeof(uint64_t))); } fclose(fd); } static void assemble(void) { struct assembler_context ctx; memset(&ctx, 0, sizeof(ctx)); INIT_LIST_HEAD(&ctx.output); eval_directives(&ctx); assemble_instructions(&ctx); resolve_labels(&ctx); emit_code(&ctx); } static void initialize(void) { INIT_LIST_HEAD(&infile.sl); INIT_LIST_HEAD(&infile.ivals); #if YYDEBUG if (IS_INSANE_DEBUG) yydebug = 1; else yydebug = 0; #endif /* YYDEBUG */ } int main(int argc, char **argv) { int err, res = 1; err = parse_args(argc, argv); if (err < 0) goto out; if (err > 0) { res = 0; goto out; } err = open_input_file(); if (err) goto out; initialize(); yyparse(); assemble(); assemble_initvals(); close_input_file(); res = 0; out: /* Lazyman simply leaks all allocated memory. */ return res; }