chrome-ec/util/lbcc.c

691 lines
14 KiB
C

/*
* Copyright 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 & BIT(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)strtoull(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 = strtoull(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\n", reloc_label[i]);
}
}
}
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;
}