| /* |
| * 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 <errno.h> |
| #include <stdarg.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #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", "<invalid>" |
| }; |
| static const char * const color_sym[] = { |
| "r", "g", "b", "<invalid>" |
| }; |
| |
| 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; |
| } |