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];
};
struct assembler_context {
- int arch;
+ /* The architecture version (802.11 core revision) */
+ unsigned int arch;
+
struct label *start_label;
/* Tracking stuff */
if (have_arch)
asm_error(ctx, "Multiple %%arch definitions");
ctx->arch = ad->u.arch;
+ 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_arch)
asm_error(ctx, "No %%arch defined");
- if (ctx->arch != NEWWORLD)
- asm_error(ctx, "TODO: Only NEWWORLD arch supported, yet");
if (!have_start_label)
asm_info(ctx, "Using start address 0");
}
-static int is_possible_imm(unsigned int imm)
+static bool is_possible_imm(unsigned int imm)
{
unsigned int mask;
return 1;
}
-static int is_valid_imm(unsigned int imm)
+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.
*
- * The value itself is 10bit long, signed.
- * We also honor sign-extension, so we allow values
- * of 0xFFFF, for example.
+ * 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;
- /* assert sign extension */
- mask = 0xFC00;
- if (imm & (1 << 9)) {
- /* sign-extended */
+ if (ctx->arch == 5) {
+ immediate_size = 10; /* 10bit */
+ } else if (ctx->arch == 15) {
+ immediate_size = 11; /* 11bit */
+ } else {
+ asm_error(ctx, "Unknown immediate size for arch %u",
+ ctx->arch);
+ }
+
+ /* 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;
}
-static int is_contiguous_bitmask(unsigned int mask)
+/* This checks if the value is nonzero and a power of two. */
+static bool is_power_of_two(unsigned int value)
{
- int bit;
- int only_zero_now = 0;
-
- /* This checks if the mask is contiguous.
- * A contiguous mask is:
- * 0b0001111110000
- * A non-contiguous mask is:
- * 0b0001101110000
- */
+ return (value && ((value & (value - 1)) == 0));
+}
- bit = ffs(mask);
- if (!bit)
- return 1;
- if (bit > 16)
- return 1;
- bit--;
- for ( ; bit < 16; bit++) {
- if (mask & (1 << bit)) {
- if (only_zero_now)
- return 0;
- } else
- only_zero_now = 1;
- }
+/* 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;
- return 1;
+ 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,
{
unsigned int val, tmp;
unsigned int mask;
- int too_long = 0;
/* format: 0b11ii iiii iiii */
val = 0xC00;
+ if (ctx->arch == 15)
+ val <<= 1;
tmp = imm->imm;
- if (!is_valid_imm(tmp)) {
+ if (!is_valid_imm(ctx, tmp)) {
asm_warn(ctx, "IMMEDIATE 0x%X (%d) too long "
"(> 9 bits + sign). Did you intend to "
"use implicit sign extension?",
tmp, (int)tmp);
}
- tmp &= 0x3FF;
+ if (ctx->arch == 15)
+ tmp &= 0x7FF;
+ else
+ tmp &= 0x3FF;
val |= tmp;
return val;
case GPR:
/* format: 0b1011 11rr rrrr */
val |= 0xBC0;
- if (reg->nr & ~0x3F)
+ if (ctx->arch == 15)
+ val <<= 1;
+ if (reg->nr & ~0x3F) //FIXME 128 regs for v15 arch possible?
asm_error(ctx, "GPR-nr too big");
val |= reg->nr;
break;
case SPR:
/* format: 0b100. .... .... */
val |= 0x800;
+ if (ctx->arch == 15)
+ val <<= 1;
if (reg->nr & ~0x1FF)
asm_error(ctx, "SPR-nr too big");
val |= reg->nr;
case OFFR:
/* format: 0b1000 0110 0rrr */
val |= 0x860;
+ if (ctx->arch == 15)
+ val <<= 1;
if (reg->nr & ~0x7)
asm_error(ctx, "OFFR-nr too big");
val |= reg->nr;
case MEM_DIRECT:
/* format: 0b0mmm mmmm mmmm */
off = mem->offset;
- if (off & ~0x7FF) {
+ if (off & ~0x7FF) { //FIXME 4096 words for v15 arch possible?
asm_warn(ctx, "DIRECT memoffset 0x%X too long (> 11 bits)", off);
off &= 0x7FF;
}
off = mem->offset;
reg = mem->offr_nr;
val |= 0xA00;
+ //FIXME what about v15 arch?
if (off & ~0x3F) {
asm_warn(ctx, "INDIRECT memoffset 0x%X too long (> 6 bits)", off);
off &= 0x3F;
}
- if (reg & ~0x7)
+ 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 << 6);
break;
}
}
-static void do_assemble_insn(struct assembler_context *ctx,
- struct instruction *insn,
- unsigned int opcode)
+static struct code_output * do_assemble_insn(struct assembler_context *ctx,
+ struct instruction *insn,
+ unsigned int opcode)
{
int i;
struct operlist *ol;
"lowlevel do_assemble_insn");
list_add_tail(&out->list, &ctx->output);
+
+ return out;
}
static unsigned int merge_ext_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.raw;
+ cond = ol->oper[0]->u.imm->imm;
if (cond & ~0xFF)
asm_error(ctx, "External jump condition value too big (> 0xFF)");
opcode |= cond;
- ol->oper[0] = ol->oper[1];
- ol->oper[1] = ol->oper[2];
- ol->oper[2] = ol->oper[3];
+ 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;
}
tmp = in->u.imm->imm;
if (!is_possible_imm(tmp))
asm_error(ctx, "MOV operand 0x%X > 16bit", tmp);
- if (!is_valid_imm(tmp)) {
+ if (!is_valid_imm(ctx, tmp)) {
/* Immediate too big for plain OR */
em_insn.op = OP_ORX;
{
struct instruction em_insn;
struct operlist em_ol;
- struct operand em_op;
- struct immediate em_imm;
-
- /* This is a pseudo-OP. We emulate it by JE */
-
- em_insn.op = OP_JE;
- em_imm.imm = 1;
- em_op.type = OPER_IMM;
- em_op.u.imm = &em_imm;
- em_ol.oper[0] = &em_op;
- em_ol.oper[1] = &em_op;
- em_ol.oper[2] = insn->operands->oper[0];
+ 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 */
}
struct instruction *insn,
int inverted)
{
+ struct code_output *out;
struct instruction em_insn;
struct operlist em_ol;
struct operand em_op_shift;
* Check if it's representable by a normal JAND insn.
*/
tmp = imm_oper->u.imm->imm;
- if (!is_valid_imm(tmp)) {
+ 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",
/* Do a normal JAND/JNAND instruction */
if (inverted)
- do_assemble_insn(ctx, insn, 0x040 | 0x1);
+ out = do_assemble_insn(ctx, insn, 0x040 | 0x1);
else
- do_assemble_insn(ctx, insn, 0x040);
+ 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) {
emulate_jand_insn(ctx, insn, 1);
return;
case OP_JS:
- do_assemble_insn(ctx, insn, 0x050);
+ out = do_assemble_insn(ctx, insn, 0x050);
+ out->is_jump_insn = 1;
break;
case OP_JNS:
- do_assemble_insn(ctx, insn, 0x050 | 0x1);
+ out = do_assemble_insn(ctx, insn, 0x050 | 0x1);
+ out->is_jump_insn = 1;
break;
case OP_JE:
- do_assemble_insn(ctx, insn, 0x0D0);
+ out = do_assemble_insn(ctx, insn, 0x0D0);
+ out->is_jump_insn = 1;
break;
case OP_JNE:
- do_assemble_insn(ctx, insn, 0x0D0 | 0x1);
+ out = do_assemble_insn(ctx, insn, 0x0D0 | 0x1);
+ out->is_jump_insn = 1;
break;
case OP_JLS:
- do_assemble_insn(ctx, insn, 0x0D2);
+ out = do_assemble_insn(ctx, insn, 0x0D2);
+ out->is_jump_insn = 1;
break;
case OP_JGES:
- do_assemble_insn(ctx, insn, 0x0D2 | 0x1);
+ out = do_assemble_insn(ctx, insn, 0x0D2 | 0x1);
+ out->is_jump_insn = 1;
break;
case OP_JGS:
- do_assemble_insn(ctx, insn, 0x0D4);
+ out = do_assemble_insn(ctx, insn, 0x0D4);
+ out->is_jump_insn = 1;
break;
case OP_JLES:
- do_assemble_insn(ctx, insn, 0x0D4 | 0x1);
+ out = do_assemble_insn(ctx, insn, 0x0D4 | 0x1);
+ out->is_jump_insn = 1;
break;
case OP_JL:
- do_assemble_insn(ctx, insn, 0x0DA);
+ out = do_assemble_insn(ctx, insn, 0x0DA);
+ out->is_jump_insn = 1;
break;
case OP_JGE:
- do_assemble_insn(ctx, insn, 0x0DA | 0x1);
+ out = do_assemble_insn(ctx, insn, 0x0DA | 0x1);
+ out->is_jump_insn = 1;
break;
case OP_JG:
- do_assemble_insn(ctx, insn, 0x0DC);
+ out = do_assemble_insn(ctx, insn, 0x0DC);
break;
case OP_JLE:
- do_assemble_insn(ctx, insn, 0x0DC | 0x1);
+ out = do_assemble_insn(ctx, insn, 0x0DC | 0x1);
+ out->is_jump_insn = 1;
break;
case OP_JZX:
opcode = merge_ext_into_opcode(ctx, 0x400, insn);
- do_assemble_insn(ctx, insn, opcode);
+ out = do_assemble_insn(ctx, insn, opcode);
+ out->is_jump_insn = 1;
break;
case OP_JNZX:
opcode = merge_ext_into_opcode(ctx, 0x500, insn);
- do_assemble_insn(ctx, insn, opcode);
+ 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);
- do_assemble_insn(ctx, insn, opcode);
+ 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);
- do_assemble_insn(ctx, insn, opcode);
+ out = do_assemble_insn(ctx, insn, opcode);
+ out->is_jump_insn = 1;
break;
case OP_CALL:
do_assemble_insn(ctx, insn, 0x002);
break;
case OP_RET:
+ /* 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, 0x003);
break;
case OP_TKIPH:
if (addr < 0)
goto does_not_exist;
c->operands[i].u.operand = addr;
- if (i != 2) /* Is not a jump target */
- c->operands[i].u.operand |= 0xC00; /* Make it be an immediate */
+ 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:
static void emit_code(struct assembler_context *ctx)
{
FILE *fd;
- char *fn;
- size_t fn_len;
+ const char *fn;
struct code_output *c;
uint64_t code;
unsigned char outbuf[8];
unsigned int insn_count = 0;
struct fw_header hdr;
- fn_len = strlen(outfile_name) + 20;
- fn = xmalloc(fn_len);
- snprintf(fn, fn_len, "%s.ucode", outfile_name);
+ fn = outfile_name;
fd = fopen(fn, "w+");
if (!fd) {
fprintf(stderr, "Could not open microcode output file \"%s\"\n", fn);
- free(fn);
exit(1);
}
if (IS_VERBOSE_DEBUG)
}
code = 0;
- /* Instruction binary format is: xxyy yzzz 0000 oooX
- * Big-Endian, X is the most significant part of Xxx.
- */
- code |= (c->opcode << 4);
-
- code |= (((uint64_t)c->operands[0].u.operand & 0xF00) >> 8);
- code |= (((uint64_t)c->operands[0].u.operand & 0x0FF) << 56);
-
- code |= ((uint64_t)c->operands[1].u.operand << 44);
-
- code |= ((uint64_t)c->operands[2].u.operand << 32);
-
- outbuf[7] = (code & 0x00000000000000FFULL);
- outbuf[6] = (code & 0x000000000000FF00ULL) >> 8;
- outbuf[5] = (code & 0x0000000000FF0000ULL) >> 16;
- outbuf[4] = (code & 0x00000000FF000000ULL) >> 24;
- outbuf[3] = (code & 0x000000FF00000000ULL) >> 32;
- outbuf[2] = (code & 0x0000FF0000000000ULL) >> 40;
- outbuf[1] = (code & 0x00FF000000000000ULL) >> 48;
- outbuf[0] = (code & 0xFF00000000000000ULL) >> 56;
+ if (ctx->arch == 5) {
+ /* Instruction binary format is: xxyyyzzz0000oooX
+ * byte-0-^ byte-7-^
+ * ooo is the opcode
+ * Xxx is the first operand
+ * yyy is the second operand
+ * zzz is the third operand
+ */
+ 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;
+ code = ((code & (uint64_t)0xFFFFFFFF00000000ULL) >> 32) |
+ ((code & (uint64_t)0x00000000FFFFFFFFULL) << 32);
+ } else if (ctx->arch == 15) {
+ 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;
+ code = ((code & (uint64_t)0xFFFFFFFF00000000ULL) >> 32) |
+ ((code & (uint64_t)0x00000000FFFFFFFFULL) << 32);
+ } else {
+ asm_error(ctx, "No emit format for arch %u",
+ ctx->arch);
+ }
+ 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;
if (fwrite(&outbuf, ARRAY_SIZE(outbuf), 1, fd) != 1) {
fprintf(stderr, "Could not write microcode outfile\n");
break;
}
}
+
+ if (arg_print_sizes) {
+ printf("%s: text = %u instructions (%u bytes)\n",
+ fn, insn_count,
+ (unsigned int)(insn_count * sizeof(uint64_t)));
+ }
+
fclose(fd);
- free(fn);
}
static void assemble(void)
int err, res = 1;
err = parse_args(argc, argv);
- if (err)
+ if (err < 0)
goto out;
+ if (err > 0) {
+ res = 0;
+ goto out;
+ }
err = open_input_file();
if (err)
goto out;