summaryrefslogtreecommitdiff
path: root/util/lbcc.c
diff options
context:
space:
mode:
authorBill Richardson <wfrichar@chromium.org>2014-09-26 15:50:23 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-09-30 01:53:53 +0000
commit87762fa6997d3deb55dec777d5fa42c3cabdf7ca (patch)
treefc057b0aedbb8c26417f0d83705d869ee7e7fed3 /util/lbcc.c
parentaf3d103dbc3ddbb6004240186a946efe1cc8b8f4 (diff)
downloadchrome-ec-87762fa6997d3deb55dec777d5fa42c3cabdf7ca.tar.gz
Add compiler/decompiler for lightbar bytecode programs.
BUG=none BRANCH=ToT TEST=manual make BOARD=samus for i in extra/lightbar/programs/[g-z]*.bin; do ./build/samus/util/lbcc -d $i /tmp/x.lbs ./build/samus/util/lbcc /tmp/x.lbs /tmp/x.bin cmp $i /tmp/x.bin done Change-Id: I86c014c425e917ecafadd1c6845fcf2e5b4edbb7 Signed-off-by: Bill Richardson <wfrichar@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/220244
Diffstat (limited to 'util/lbcc.c')
-rw-r--r--util/lbcc.c602
1 files changed, 602 insertions, 0 deletions
diff --git a/util/lbcc.c b/util/lbcc.c
new file mode 100644
index 0000000000..6fdefed927
--- /dev/null
+++ b/util/lbcc.c
@@ -0,0 +1,602 @@
+/*
+ * 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[LB_PROG_LEN]; /* does program jump here? */
+static int is_instruction[LB_PROG_LEN]; /* instruction or operand? */
+static char *label[LB_PROG_LEN]; /* labels we've seen */
+static char *reloc_label[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 MAX_WORDS (2 + LB_PROG_MAX_OPERANDS)
+
+struct safe_lb_program {
+ struct lb_program p;
+ uint8_t zeros[LB_PROG_MAX_OPERANDS];
+} __packed;
+
+#define OP(X) X
+#include "lightbar_opcode_list.h"
+enum lightbyte_opcode {
+ LIGHTBAR_OPCODE_TABLE
+ HALT,
+ MAX_OPCODE
+};
+#undef OP
+
+static const char const *opcode_sym[] = {
+ "jump", "wait", "bright", "color",
+ "step", "ramp.1", "cycle.1", "cycle",
+ "halt",
+};
+BUILD_ASSERT(ARRAY_SIZE(opcode_sym) == MAX_OPCODE);
+
+static const char const *control_sym[] = {
+ "beg", "end", "phase", "<invalid>"
+};
+static const char const *color_sym[] = {
+ "r", "g", "b", "rgb"
+};
+
+static void read_binary(FILE *fp, struct safe_lb_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, 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 > LB_PROG_LEN) {
+ Warning("Truncating input at %d bytes\n", LB_PROG_LEN);
+ prog->zeros[0] = 0;
+ got = LB_PROG_LEN;
+ } else {
+ prog->p.size = got;
+ }
+}
+
+/* Returns number of operands required by an opcode */
+static int num_operands(uint8_t cmd, uint8_t *arg)
+{
+ int operands = 0;
+
+ switch (cmd) {
+ case JUMP:
+ case SET_BRIGHTNESS:
+ operands = 1;
+ break;
+
+ case DELAY:
+ case SET_DELAY_TIME:
+ operands = 4;
+ break;
+
+ case SET_COLOR:
+ if ((arg[0] & 0x03) == LB_COL_ALL)
+ operands = 4;
+ else
+ operands = 2;
+ break;
+
+ default:
+ break;
+ }
+ return operands;
+}
+
+static uint32_t val32(uint8_t *ptr)
+{
+ uint32_t val;
+ val = (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
+ return val;
+}
+
+/* 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, arg);
+
+ /* 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:
+ fprintf(fp, "\tL00%02x\n", arg[0]);
+ break;
+ case DELAY:
+ case SET_DELAY_TIME:
+ fprintf(fp, "\t%d\n", val32(arg));
+ break;
+ case SET_BRIGHTNESS:
+ fprintf(fp, "\t%d\n", arg[0]);
+ break;
+ case SET_COLOR:
+ led = arg[0] >> 4;
+ control = (arg[0] >> 2) & 0x03;
+ color = arg[0] & 0x03;
+ fprintf(fp, "\t");
+ if (led >= NUM_LEDS)
+ fprintf(fp, "all");
+ else
+ fprintf(fp, "%d", led);
+ fprintf(fp, ".%s", control_sym[control]);
+ fprintf(fp, ".%s", color_sym[color]);
+ if (color == LB_COL_ALL)
+ fprintf(fp, "\t0x%02x 0x%02x 0x%02x\n",
+ arg[1], arg[2], arg[3]);
+ else
+ fprintf(fp, "\t0x%02x\n", arg[1]);
+ break;
+ 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 disassemble_prog(FILE *fp, struct safe_lb_program *prog)
+{
+ int i;
+ uint8_t *ptr, targ;
+
+ /* 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];
+ if (ptr[0] == JUMP) {
+ targ = ptr[1];
+ is_jump_target[targ] = 1;
+ }
+ i += num_operands(*ptr, ptr + 1);
+ }
+
+ /* 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 < 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 color arg. Return 0 if bogus, number of additional args if okay. */
+static int is_color_arg(char *buf, uint32_t *valp)
+{
+ struct parse_s token[MAX_WORDS];
+ uint8_t led, control, color, val;
+ int i;
+ int rv = 1;
+
+ if (!buf)
+ return 0;
+
+ /* There should be three terms, separated with '.' */
+ i = split_line(buf, ".,", token, MAX_WORDS);
+ if (i != 3)
+ return 0;
+
+ if (!strcmp("all", token[0].word)) {
+ led = NUM_LEDS;
+ } else if (token[0].is_num) {
+ led = token[0].val;
+ } else {
+ Error("Invalid LED \"%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;
+
+ 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;
+
+
+ val = ((led & 0xF) << 4) | ((control & 0x3) << 2) | (color & 0x3);
+
+ *valp = val;
+ if (color == LB_COL_ALL)
+ rv = 3;
+
+ return rv;
+}
+
+static void fixup_symbols(struct safe_lb_program *prog)
+{
+ int i, j;
+
+ for (i = 0; i < LB_PROG_LEN; i++) {
+ if (reloc_label[i]) {
+ /* Looking for reloc label */
+ for (j = 0; j < LB_PROG_LEN; j++) {
+ if (label[j] && !strcmp(label[j],
+ reloc_label[i])) {
+ prog->p.data[i] = j;
+ break;
+ }
+ }
+ if (j >= LB_PROG_LEN)
+ Error("Can't find label %s from line %d\n", j);
+ }
+ }
+}
+
+
+static void compile(FILE *fp, struct safe_lb_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 >= 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:
+ /* 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 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 DELAY:
+ case SET_DELAY_TIME:
+ /* 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:
+ /* one magic word, then one or three more 8-bit args */
+ i = is_color_arg(token[wnum].word, &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;
+ while (i--) {
+ /* and the others */
+ 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 > 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_lb_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, "\nUsage: %s [options] ...\n\n", 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;
+}