/* * Copyright (c) 2014 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include #include #include #include #include "compile_time_macros.h" #include "ec_commands.h" #include "lb_common.h" #include "lightbar.h" static const char usage[] = "\n" "Usage: %s [OPTIONS] [INFILE [OUTFILE]]\n" "\n" "This compiles or decompiles the lightbar programmable bytecode.\n" "\n" "Options:\n" " -d Decode binary to ascii\n" " -v Decode output should be verbose\n" "\n"; /* globals */ static int hit_errors; static int opt_verbose; static int is_jump_target[EC_LB_PROG_LEN]; /* does program jump here? */ static int is_instruction[EC_LB_PROG_LEN]; /* instruction or operand? */ static char *label[EC_LB_PROG_LEN]; /* labels we've seen */ static char *reloc_label[EC_LB_PROG_LEN]; /* put label target here */ static void Error(const char *format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "ERROR: "); vfprintf(stderr, format, ap); va_end(ap); hit_errors++; } static void Warning(const char *format, ...) { va_list ap; va_start(ap, format); fprintf(stderr, "Warning: "); vfprintf(stderr, format, ap); va_end(ap); } /* The longest line should have a label, an opcode, and the max operands */ #define LB_PROG_MAX_OPERANDS 4 #define MAX_WORDS (2 + LB_PROG_MAX_OPERANDS) struct safe_lightbar_program { struct lightbar_program p; uint8_t zeros[LB_PROG_MAX_OPERANDS]; } __packed; #define OP(NAME, BYTES, MNEMONIC) NAME, #include "lightbar_opcode_list.h" enum lightbyte_opcode { LIGHTBAR_OPCODE_TABLE MAX_OPCODE }; #undef OP #define OP(NAME, BYTES, MNEMONIC) BYTES, #include "lightbar_opcode_list.h" static const int num_operands[] = { LIGHTBAR_OPCODE_TABLE }; #undef OP #define OP(NAME, BYTES, MNEMONIC) MNEMONIC, #include "lightbar_opcode_list.h" static const char * const opcode_sym[] = { LIGHTBAR_OPCODE_TABLE }; #undef OP static const char * const control_sym[] = { "beg", "end", "phase", "" }; static const char * const color_sym[] = { "r", "g", "b", "" }; static void read_binary(FILE *fp, struct safe_lightbar_program *prog) { int got; memset(prog, 0, sizeof(*prog)); /* Read up to one more byte than we need, so we know if it's too big */ got = fread(prog->p.data, 1, EC_LB_PROG_LEN + 1, fp); if (got < 1) { Error("Unable to read any input: "); if (feof(fp)) fprintf(stderr, "EOF\n"); else if (ferror(fp)) fprintf(stderr, "%s\n", strerror(errno)); else fprintf(stderr, "no idea why.\n"); } else if (got > EC_LB_PROG_LEN) { Warning("Truncating input at %d bytes\n", EC_LB_PROG_LEN); prog->zeros[0] = 0; got = EC_LB_PROG_LEN; } else { prog->p.size = got; } } static uint32_t val32(uint8_t *ptr) { uint32_t val; val = (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; return val; } static int is_jump(uint8_t op) { /* TODO: probably should be a field in the opcode list */ return op >= JUMP && op <= JUMP_IF_CHARGING; } static void print_led_set(FILE *fp, uint8_t led) { int i, first = 1; fprintf(fp, "{"); for (i = 0; i < NUM_LEDS; i++) if (led & (1 << i)) { if (!first) fprintf(fp, ","); fprintf(fp, "%d", i); first = 0; } fprintf(fp, "}"); } /* returns number of operands consumed */ static int print_op(FILE *fp, uint8_t addr, uint8_t cmd, uint8_t *arg) { uint8_t led, color, control; int i, operands; operands = num_operands[cmd]; /* assume valid instruction for now */ is_instruction[addr] = 1; if (opt_verbose) { fprintf(fp, "%02x: %02x", addr, cmd); for (i = 0; i < LB_PROG_MAX_OPERANDS; i++) if (i < operands) fprintf(fp, " %02x", arg[i]); else fprintf(fp, " "); fprintf(fp, "\t"); } if (is_jump_target[addr]) fprintf(fp, "L00%02x:", addr); fprintf(fp, "\t"); if (cmd < MAX_OPCODE) fprintf(fp, "%s", opcode_sym[cmd]); switch (cmd) { case JUMP: case JUMP_IF_CHARGING: fprintf(fp, "\tL00%02x\n", arg[0]); break; case JUMP_BATTERY: fprintf(fp, "\tL00%02x L00%02x\n", arg[0], arg[1]); break; case SET_WAIT_DELAY: case SET_RAMP_DELAY: fprintf(fp, "\t%d\n", val32(arg)); break; case SET_BRIGHTNESS: fprintf(fp, "\t%d\n", arg[0]); break; case SET_COLOR_SINGLE: led = arg[0] >> 4; control = (arg[0] >> 2) & 0x03; color = arg[0] & 0x03; fprintf(fp, "\t"); print_led_set(fp, led); fprintf(fp, ".%s", control_sym[control]); fprintf(fp, ".%s", color_sym[color]); fprintf(fp, "\t0x%02x\n", arg[1]); break; case SET_COLOR_RGB: led = arg[0] >> 4; control = (arg[0] >> 2) & 0x03; fprintf(fp, "\t"); print_led_set(fp, led); fprintf(fp, ".%s", control_sym[control]); fprintf(fp, "\t0x%02x 0x%02x 0x%02x\n", arg[1], arg[2], arg[3]); break; case ON: case OFF: case WAIT: case GET_COLORS: case SWAP_COLORS: case RAMP_ONCE: case CYCLE_ONCE: case CYCLE: case HALT: fprintf(fp, "\n"); break; default: fprintf(fp, "-- invalid opcode 0x%02x --\n", cmd); is_instruction[addr] = 0; hit_errors++; } return operands; } static void set_jump_target(uint8_t targ) { if (targ >= EC_LB_PROG_LEN) { Warning("program jumps to 0x%02x, " "which out of bounds\n", targ); return; } is_jump_target[targ] = 1; } static void disassemble_prog(FILE *fp, struct safe_lightbar_program *prog) { int i; uint8_t *ptr, op; /* Scan the program once to identify all the jump targets, * so we can print the labels when we encounter them. */ for (i = 0; i < prog->p.size; i++) { ptr = &prog->p.data[i]; op = *ptr; if (is_jump(op)) set_jump_target(ptr[1]); if (op == JUMP_BATTERY) set_jump_target(ptr[2]); i += num_operands[op]; } /* Now disassemble */ for (i = 0; i < prog->p.size; i++) { ptr = &prog->p.data[i]; i += print_op(fp, i, *ptr, ptr + 1); } /* Finally, make sure the program doesn't jump to any location other * than a valid instruction */ for (i = 0; i < EC_LB_PROG_LEN; i++) if (is_jump_target[i] && !is_instruction[i]) { Warning("program jumps to 0x%02x, " "which is not a valid instruction\n", i); } } /* We'll split each line into an array of these. */ struct parse_s { char *word; int is_num; uint32_t val; }; /* Fills in struct, returns number of words found. Note that pointers are only * copied. The strings they point to are not duplicated. */ static int split_line(char *buf, char *delim, struct parse_s *elt, int max) { char *w, *ptr, *buf_savetok; int i; char *e = 0; memset(elt, 0, max * sizeof(*elt)); for (ptr = buf, i = 0; i < max && (w = strtok_r(ptr, delim, &buf_savetok)) != 0; ptr = 0, i++) { elt[i].word = w; elt[i].val = (uint32_t)strtoul(w, &e, 0); if (!e || !*e) elt[i].is_num = 1; } return i; } /* Decode led set. Return 0 if bogus, 1 if okay. */ static int is_led_set(char *buf, uint8_t *valp) { uint8_t led = 0; unsigned long int next_led; char *ptr; if (!buf) return 0; if (*buf != '{') return 0; buf++; for (;;) { next_led = strtoul(buf, &ptr, 0); if (buf == ptr) { if (buf[0] == '}' && buf[1] == 0) { *valp = led; return 1; } else return 0; } if (next_led >= NUM_LEDS) return 0; led |= 1 << next_led; buf = ptr; if (*buf == ',') buf++; } } /* Decode color arg based on expected control param sections. * Return 0 if bogus, 1 if okay. */ static int is_color_arg(char *buf, int expected, uint32_t *valp) { struct parse_s token[MAX_WORDS]; uint8_t led, control, color; int i; if (!buf) return 0; /* There should be three terms, separated with '.' */ i = split_line(buf, ".", token, MAX_WORDS); if (i != expected) return 0; if (!is_led_set(token[0].word, &led)) { Error("Invalid LED set \"%s\"\n", token[0].word); return 0; } for (i = 0; i < LB_CONT_MAX; i++) if (!strcmp(token[1].word, control_sym[i])) { control = i; break; } if (i >= LB_CONT_MAX) return 0; if (expected == 3) { for (i = 0; i < ARRAY_SIZE(color_sym); i++) if (!strcmp(token[2].word, color_sym[i])) { color = i; break; } if (i >= ARRAY_SIZE(color_sym)) return 0; } else color = 0; *valp = ((led & 0xF) << 4) | ((control & 0x3) << 2) | (color & 0x3); return 1; } static void fixup_symbols(struct safe_lightbar_program *prog) { int i, j; for (i = 0; i < EC_LB_PROG_LEN; i++) { if (reloc_label[i]) { /* Looking for reloc label */ for (j = 0; j < EC_LB_PROG_LEN; j++) { if (label[j] && !strcmp(label[j], reloc_label[i])) { prog->p.data[i] = j; break; } } if (j >= EC_LB_PROG_LEN) Error("Can't find label %s from line %d\n", j); } } } static void compile(FILE *fp, struct safe_lightbar_program *prog) { char buf[128]; struct parse_s token[MAX_WORDS]; char *s; int line = 0, chopping = 0; uint8_t addr = 0; int opcode; int wnum, wordcnt; int i; while (fgets(buf, sizeof(buf), fp)) { /* We truncate lines that are too long */ s = strchr(buf, '\n'); if (chopping) { if (s) chopping = 0; continue; } /* Got something to look at */ line++; if (!s) { chopping = 1; Warning("truncating line %d\n", line); } /* Ignore comments */ s = strchr(buf, '#'); if (s) *s = '\0'; wordcnt = split_line(buf, " \t\n", token, MAX_WORDS); if (!wordcnt) continue; wnum = 0; /* A label must be the first word, ends with a ':' (no spaces * before it), and doesn't start with a ':' */ s = strchr(token[0].word, ':'); if (s && s[1] == '\0' && s != token[0].word) { *s = '\0'; label[addr] = strdup(token[0].word); wnum++; } /* How about an opcode? */ for (opcode = 0; opcode < MAX_OPCODE; opcode++) if (!strcasecmp(token[wnum].word, opcode_sym[opcode])) break; if (opcode >= MAX_OPCODE) { Error("Unrecognized opcode \"%s\"" " at line %d\n", token[wnum].word, line); continue; } /* Do we even have a place to write this opcode? */ if (addr >= EC_LB_PROG_LEN) { Error("out of program space at line %d\n", line); break; } /* Got an opcode. Save it! */ prog->p.data[addr++] = opcode; wnum++; /* Now we need operands. */ switch (opcode) { case JUMP: case JUMP_IF_CHARGING: /* a label */ if (token[wnum].word) reloc_label[addr++] = strdup(token[wnum].word); else Error("Missing jump target at line %d\n", line); break; case JUMP_BATTERY: /* two labels*/ if (token[wnum].word) reloc_label[addr++] = strdup(token[wnum].word); else { Error("Missing first jump target " "at line %d\n", line); break; } wnum++; if (token[wnum].word) reloc_label[addr++] = strdup(token[wnum].word); else Error("Missing second jump target " "at line %d\n", line); break; case SET_BRIGHTNESS: /* one 8-bit arg */ if (token[wnum].is_num) prog->p.data[addr++] = token[wnum].val; else Error("Missing/invalid arg at line %d\n", line); break; case SET_WAIT_DELAY: case SET_RAMP_DELAY: /* one 32-bit arg */ if (token[wnum].is_num) { prog->p.data[addr++] = (token[wnum].val >> 24) & 0xff; prog->p.data[addr++] = (token[wnum].val >> 16) & 0xff; prog->p.data[addr++] = (token[wnum].val >> 8) & 0xff; prog->p.data[addr++] = token[wnum].val & 0xff; } else { Error("Missing/invalid arg at line %d\n", line); } break; case SET_COLOR_SINGLE: /* one magic word, then one more 8-bit arg */ i = is_color_arg(token[wnum].word, 3, &token[wnum].val); if (!i) { Error("Missing/invalid arg at line %d\n", line); break; } /* save the magic number */ prog->p.data[addr++] = token[wnum++].val; /* and the color immediate */ if (token[wnum].is_num) { prog->p.data[addr++] = token[wnum++].val; } else { Error("Missing/Invalid arg " "at line %d\n", line); break; } break; case SET_COLOR_RGB: /* one magic word, then three more 8-bit args */ i = is_color_arg(token[wnum].word, 2, &token[wnum].val); if (!i) { Error("Missing/invalid arg at line %d\n", line); break; } /* save the magic number */ prog->p.data[addr++] = token[wnum++].val; /* and the color immediates */ for (i = 0; i < 3; i++) { if (token[wnum].is_num) { prog->p.data[addr++] = token[wnum++].val; } else { Error("Missing/Invalid arg " "at line %d\n", line); break; } } break; default: /* No args needed */ break; } /* Did we run past the end? */ if (addr > EC_LB_PROG_LEN) { Error("out of program space at line %d\n", line); break; } } if (ferror(fp)) Error("problem while reading input: %s\n", strerror(errno)); if (!hit_errors) fixup_symbols(prog); if (!hit_errors) prog->p.size = addr; if (!prog->p.size) Error("input file produced no output bytes\n"); } int main(int argc, char *argv[]) { struct safe_lightbar_program safe_prog; int opt_decode = 0; int c; int errorcnt = 0; char *infile, *outfile; FILE *ifp, *ofp; char *progname = strrchr(argv[0], '/'); if (progname) progname++; else progname = argv[0]; opterr = 0; /* quiet, you */ while ((c = getopt(argc, argv, ":dv")) != -1) { switch (c) { case 'd': opt_decode = 1; break; case 'v': opt_verbose = 1; break; case '?': fprintf(stderr, "%s: unrecognized switch: -%c\n", progname, optopt); errorcnt++; break; case ':': fprintf(stderr, "%s: missing argument to -%c\n", progname, optopt); errorcnt++; break; default: errorcnt++; break; } } if (errorcnt) { fprintf(stderr, usage, progname); exit(1); } if (argc - optind > 0) { infile = argv[optind]; ifp = fopen(infile, "rb"); if (!ifp) { fprintf(stderr, "%s: Unable to open %s for reading: %s\n", progname, infile, strerror(errno)); exit(1); } } else { infile = "stdin"; ifp = stdin; } if (argc - optind > 1) { outfile = argv[optind + 1]; ofp = fopen(outfile, "wb"); if (!ofp) { fprintf(stderr, "%s: Unable to open %s for writing: %s\n", progname, outfile, strerror(errno)); exit(1); } } else { outfile = "stdout"; ofp = stdout; } if (opt_decode) { read_binary(ifp, &safe_prog); fclose(ifp); if (hit_errors) return 1; fprintf(ofp, "# %s\n", infile); disassemble_prog(ofp, &safe_prog); fclose(ofp); } else { memset(&safe_prog, 0, sizeof(safe_prog)); compile(ifp, &safe_prog); fclose(ifp); if (!hit_errors) { if (1 != fwrite(safe_prog.p.data, safe_prog.p.size, 1, ofp)) Error("%s: Unable to write to %s: %s\n", progname, outfile, strerror(errno)); else fprintf(stderr, "0x%02x bytes written to %s\n", safe_prog.p.size, outfile); } fclose(ofp); } return hit_errors; }