summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Caruso <ejcaruso@chromium.org>2014-09-17 16:52:31 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-09-25 07:59:16 +0000
commitfb5ff7b1bb59174ea140d18cb84ce1d6599337c1 (patch)
tree9e78996ca8d6fbe6c51cf272b94e5b5e00727f2b
parent2939b986cfc4fc3f8b46b37b4d345bec270418ca (diff)
downloadchrome-ec-fb5ff7b1bb59174ea140d18cb84ce1d6599337c1.tar.gz
lightbar: add seq type PROGRAM for user-programmable sequences
This diff allows the user to send small programs to the EC and gain control of the lightbar. Right now, this is only exposed through ectool, and sysfs support will come later. To send a program to the EC, use $ ectool lightbar program /path/to/program.bin and then start running the program with $ ectool lightbar seq program BUG=None BRANCH=ToT TEST=Using the above steps with hand-assembled programs. Checked that infinite bytecode loops do not hang the EC. Checked that bad opcodes exit with an error. Stress tested pushing programs and changing sequences. Signed-off-by: Eric Caruso <ejcaruso@chromium.org> Change-Id: I635fb041a5dc5c403f7c26fb9a41b5563be9b6b7 Reviewed-on: https://chromium-review.googlesource.com/219558 Reviewed-by: Bill Richardson <wfrichar@chromium.org> Reviewed-by: Randall Spangler <rspangler@chromium.org>
-rw-r--r--common/lightbar.c374
-rw-r--r--extra/lightbar/main.c37
-rw-r--r--extra/lightbar/simulation.h1
-rw-r--r--include/ec_commands.h12
-rw-r--r--include/lightbar_msg_list.h3
-rw-r--r--util/ectool.c44
6 files changed, 469 insertions, 2 deletions
diff --git a/common/lightbar.c b/common/lightbar.c
index efd7d797a7..6684949c8a 100644
--- a/common/lightbar.c
+++ b/common/lightbar.c
@@ -973,6 +973,365 @@ static uint32_t sequence_TAP(void)
}
/****************************************************************************/
+/* Lightbar bytecode interpreter: Lightbyte. */
+/****************************************************************************/
+
+static struct lb_program cur_prog;
+static struct lb_program next_prog;
+static uint8_t pc;
+
+enum lb_color {
+ LB_COL_RED,
+ LB_COL_GREEN,
+ LB_COL_BLUE,
+ LB_COL_ALL
+};
+
+enum lb_control {
+ LB_CONT_COLOR0,
+ LB_CONT_COLOR1,
+ LB_CONT_PHASE,
+ LB_CONT_MAX
+};
+
+static uint8_t led_desc[NUM_LEDS][LB_CONT_MAX][3];
+static uint32_t lb_ramp_delay;
+
+/* Get one byte of data pointed to by the pc and advance
+ * the pc forward.
+ */
+static inline uint32_t decode_8(uint8_t *dest)
+{
+ if (pc >= cur_prog.size) {
+ CPRINTS("pc 0x%02x out of bounds", pc);
+ return EC_RES_INVALID_PARAM;
+ }
+ *dest = cur_prog.data[pc++];
+ return EC_SUCCESS;
+}
+
+/* Get four bytes of data pointed to by the pc and advance
+ * the pc forward that amount.
+ */
+static inline uint32_t decode_32(uint32_t *dest)
+{
+ if (pc >= cur_prog.size - 3) {
+ CPRINTS("pc 0x%02x near or out of bounds", pc);
+ return EC_RES_INVALID_PARAM;
+ }
+ *dest = cur_prog.data[pc++] << 24;
+ *dest |= cur_prog.data[pc++] << 16;
+ *dest |= cur_prog.data[pc++] << 8;
+ *dest |= cur_prog.data[pc++];
+ return EC_SUCCESS;
+}
+
+/* JUMP xx - jump to immediate location
+ * Changes the pc to the one-byte immediate argument.
+ */
+static uint32_t lightbyte_JUMP(void)
+{
+ uint8_t new_pc;
+ if (decode_8(&new_pc) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ pc = new_pc;
+ return EC_SUCCESS;
+}
+
+/* DELAY xx xx xx xx - yield processor for some time
+ * Performs an interruptible wait for a number of microseconds
+ * given in the four-byte immediate.
+ */
+static uint32_t lightbyte_DELAY(void)
+{
+ uint32_t delay_us;
+ if (decode_32(&delay_us) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ WAIT_OR_RET(delay_us);
+ return EC_SUCCESS;
+}
+
+/* SET_BRIGHTNESS xx
+ * Sets the current brightness to the given one-byte
+ * immediate argument.
+ */
+static uint32_t lightbyte_SET_BRIGHTNESS(void)
+{
+ uint8_t val;
+ if (decode_8(&val) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ lb_set_brightness(val);
+ return EC_SUCCESS;
+}
+
+/* SET_COLOR cc xx
+ * SET_COLOR cc rr gg bb
+ * Stores a color value in the led_desc structure.
+ * cc is a bit-packed location to perform the action on.
+ *
+ * The high four bits are used to describe an LED. If the
+ * value is less than NUM_LEDS, it describes a particular LED,
+ * and if it is greater than or equal to that value, it
+ * will perform the action on all LEDs.
+ *
+ * The next two bits are the control bits. This should be a value
+ * in lb_control that is not LB_CONT_MAX, and the corresponding
+ * color will be the one the action is performed on.
+ *
+ * The last two bits are the color bits. If this is LB_COL_RED,
+ * LB_COL_GREEN, or LB_COL_BLUE, then there is only one more byte
+ * to decode and this is a color value for that specific color
+ * channel. If it is LB_COL_ALL, then there are three more bytes,
+ * and it reads like a standard 24-bit color value.
+ */
+static uint32_t lightbyte_SET_COLOR(void)
+{
+
+ uint8_t packed_loc, led, control, color, value;
+ int start_led, end_led, color_mask, i, j;
+ if (decode_8(&packed_loc) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ led = packed_loc >> 4;
+ control = (packed_loc >> 2) & 0x3;
+ color = packed_loc & 0x3;
+
+ if (control >= LB_CONT_MAX)
+ return EC_RES_INVALID_PARAM;
+
+ if (led >= NUM_LEDS) {
+ start_led = 0;
+ end_led = NUM_LEDS - 1;
+ } else
+ start_led = end_led = led;
+
+ color_mask = color == LB_COL_ALL ? 7 : (1 << color);
+
+ for (i = 0; i < 3; i++) {
+ if (color_mask & (1 << i)) {
+ if (decode_8(&value) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+ for (j = start_led; j <= end_led; j++)
+ led_desc[j][control][i] = value;
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+/* SET_DELAY_TIME xx xx xx xx - change ramp speed
+ * This sets the length of time between ramp/cycle steps to
+ * the four-byte immediate argument, which represents a duration
+ * in milliseconds.
+ */
+static uint32_t lightbyte_SET_DELAY_TIME(void)
+{
+ uint32_t delay_us;
+ if (decode_32(&delay_us) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ lb_ramp_delay = delay_us;
+ return EC_SUCCESS;
+}
+
+static inline int get_interp_value(int led, int color, int interp)
+{
+ int base = led_desc[led][LB_CONT_COLOR0][color];
+ int delta = led_desc[led][LB_CONT_COLOR1][color] - base;
+ return base + (delta * interp / FP_SCALE);
+}
+
+/* RAMP_ONCE - simple gradient or color set
+ * If the ramp delay is set to zero, then this sets the color of
+ * all LEDs to their respective COLOR1.
+ * If the ramp delay is nonzero, then this sets their color to
+ * their respective COLOR0, and takes them via interpolation to
+ * COLOR1, with the delay time passing in between each step.
+ */
+static uint32_t lightbyte_RAMP_ONCE(void)
+{
+ int w, i, r, g, b;
+ float f;
+
+ /* special case for instantaneous set */
+ if (lb_ramp_delay == 0) {
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = led_desc[i][LB_CONT_COLOR1][LB_COL_RED];
+ g = led_desc[i][LB_CONT_COLOR1][LB_COL_GREEN];
+ b = led_desc[i][LB_CONT_COLOR1][LB_COL_BLUE];
+ lb_set_rgb(i, r, g, b);
+ }
+ return EC_SUCCESS;
+ }
+
+ for (w = 0; w < 128; w++) {
+ f = cycle_010(w);
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = get_interp_value(i, LB_COL_RED, f);
+ g = get_interp_value(i, LB_COL_GREEN, f);
+ b = get_interp_value(i, LB_COL_BLUE, f);
+ lb_set_rgb(i, r, g, b);
+ }
+ WAIT_OR_RET(lb_ramp_delay);
+ }
+ return EC_SUCCESS;
+}
+
+/* CYCLE_ONCE - simple cycle or color set
+ * If the ramp delay is zero, then this sets the color of all LEDs
+ * to their respective COLOR0.
+ * If the ramp delay is nonzero, this sets the color of all LEDs
+ * to COLOR0, then performs a ramp (as in RAMP_ONCE) to COLOR1,
+ * and finally back to COLOR0.
+ */
+static uint32_t lightbyte_CYCLE_ONCE(void)
+{
+ int w, i, r, g, b;
+ float f;
+
+ /* special case for instantaneous set */
+ if (lb_ramp_delay == 0) {
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = led_desc[i][LB_CONT_COLOR0][LB_COL_RED];
+ g = led_desc[i][LB_CONT_COLOR0][LB_COL_GREEN];
+ b = led_desc[i][LB_CONT_COLOR0][LB_COL_BLUE];
+ lb_set_rgb(i, r, g, b);
+ }
+ return EC_SUCCESS;
+ }
+
+ for (w = 0; w < 256; w++) {
+ f = cycle_010(w);
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = get_interp_value(i, LB_COL_RED, f);
+ g = get_interp_value(i, LB_COL_GREEN, f);
+ b = get_interp_value(i, LB_COL_BLUE, f);
+ lb_set_rgb(i, r, g, b);
+ }
+ WAIT_OR_RET(lb_ramp_delay);
+ }
+ return EC_SUCCESS;
+}
+
+/* CYCLE - repeating cycle
+ * Indefinitely ramps from COLOR0 to COLOR1, taking into
+ * account the PHASE of each component of each color when
+ * interpolating. (Different LEDs and different color channels
+ * on a single LED can start at different places in the cycle,
+ * though they will advance at the same rate.)
+ *
+ * If the ramp delay is zero, this instruction will error out.
+ */
+static uint32_t lightbyte_CYCLE(void)
+{
+ int w, i, r, g, b;
+
+ /* what does it mean to cycle indefinitely with 0 delay? */
+ if (lb_ramp_delay == 0)
+ return EC_RES_INVALID_PARAM;
+
+ for (w = 0;; w++) {
+ for (i = 0; i < NUM_LEDS; i++) {
+ r = get_interp_value(i, LB_COL_RED,
+ cycle_010((w & 0xff) +
+ led_desc[i][LB_CONT_PHASE][LB_COL_RED]));
+ g = get_interp_value(i, LB_COL_GREEN,
+ cycle_010((w & 0xff) +
+ led_desc[i][LB_CONT_PHASE][LB_COL_GREEN]));
+ b = get_interp_value(i, LB_COL_BLUE,
+ cycle_010((w & 0xff) +
+ led_desc[i][LB_CONT_PHASE][LB_COL_BLUE]));
+ lb_set_rgb(i, r, g, b);
+ }
+ WAIT_OR_RET(lb_ramp_delay);
+ }
+ return EC_SUCCESS;
+}
+
+#undef GET_INTERP_VALUE
+
+#define OPCODE_TABLE \
+ OP(JUMP), \
+ OP(DELAY), \
+ OP(SET_BRIGHTNESS), \
+ OP(SET_COLOR), \
+ OP(SET_DELAY_TIME), \
+ OP(RAMP_ONCE), \
+ OP(CYCLE_ONCE), \
+ OP(CYCLE),
+
+#define OP(X) X
+enum lightbyte_opcode {
+ OPCODE_TABLE
+ HALT,
+ MAX_OPCODE
+};
+#undef OP
+
+#define OP(X) lightbyte_ ## X
+static uint32_t (*lightbyte_dispatch[])(void) = {
+ OPCODE_TABLE
+};
+#undef OP
+
+#define OP(X) # X
+static const char * const lightbyte_names[] = {
+ OPCODE_TABLE
+ "HALT"
+};
+#undef OP
+
+static uint32_t sequence_PROGRAM(void)
+{
+ uint8_t saved_brightness;
+ uint8_t next_inst;
+ uint32_t rc;
+ uint8_t old_pc;
+
+ /* load next program */
+ memcpy(&cur_prog, &next_prog, sizeof(struct lb_program));
+
+ /* reset program state */
+ saved_brightness = lb_get_brightness();
+ pc = 0;
+ memset(led_desc, 0, sizeof(led_desc));
+ lb_ramp_delay = 0;
+
+ /* decode-execute loop */
+ for (;;) {
+ old_pc = pc;
+ if (decode_8(&next_inst) != EC_SUCCESS)
+ return EC_RES_INVALID_PARAM;
+
+ if (next_inst == HALT) {
+ CPRINTS("LB PROGRAM pc: 0x%02x, halting", old_pc);
+ lb_set_brightness(saved_brightness);
+ return 0;
+ } else if (next_inst >= MAX_OPCODE) {
+ CPRINTS("LB PROGRAM pc: 0x%02x, "
+ "found invalid opcode 0x%02x",
+ old_pc, next_inst);
+ lb_set_brightness(saved_brightness);
+ return EC_RES_INVALID_PARAM;
+ } else {
+ CPRINTS("LB PROGRAM pc: 0x%02x, opcode 0x%02x -> %s",
+ old_pc, next_inst, lightbyte_names[next_inst]);
+ rc = lightbyte_dispatch[next_inst]();
+ if (rc) {
+ lb_set_brightness(saved_brightness);
+ return rc;
+ }
+ }
+
+ /* yield processor in case we are stuck in a tight loop */
+ WAIT_OR_RET(100);
+ }
+}
+
+/****************************************************************************/
/* The main lightbar task. It just cycles between various pretty patterns. */
/****************************************************************************/
@@ -1036,6 +1395,7 @@ void lightbar_task(void)
case LIGHTBAR_ERROR:
case LIGHTBAR_KONAMI:
case LIGHTBAR_TAP:
+ case LIGHTBAR_PROGRAM:
st.cur_seq = st.prev_seq;
default:
break;
@@ -1165,6 +1525,10 @@ static int lpc_cmd_lightbar(struct host_cmd_handler_args *args)
CPRINTS("LB_set_params_v1");
memcpy(&st.p, &in->set_params_v1, sizeof(st.p));
break;
+ case LIGHTBAR_CMD_SET_PROGRAM:
+ CPRINTS("LB_set_program");
+ memcpy(&next_prog, &in->set_program, sizeof(struct lb_program));
+ break;
case LIGHTBAR_CMD_VERSION:
CPRINTS("LB_version");
out->version.num = LIGHTBAR_IMPLEMENTATION_VERSION;
@@ -1204,6 +1568,7 @@ static int help(const char *cmd)
ccprintf(" %s LED - get current LED color\n", cmd);
ccprintf(" %s demo [0|1] - turn demo mode on & off\n", cmd);
ccprintf(" %s params - show current params\n", cmd);
+ ccprintf(" %s program filename - load lightbyte program\n", cmd);
ccprintf(" %s version - show current version\n", cmd);
return EC_SUCCESS;
}
@@ -1375,6 +1740,15 @@ static int command_lightbar(int argc, char **argv)
return EC_SUCCESS;
}
+ if (argc >= 3 && !strcasecmp(argv[1], "program")) {
+#ifdef LIGHTBAR_SIMULATION
+ return lb_load_program(argv[2], &next_prog);
+#else
+ ccprintf("can't load program from console\n");
+ return EC_ERROR_INVAL;
+#endif
+ }
+
if (argc == 4) {
struct ec_params_lightbar in;
in.reg.ctrl = strtoi(argv[1], &e, 16);
diff --git a/extra/lightbar/main.c b/extra/lightbar/main.c
index 38234e743c..6014719f0b 100644
--- a/extra/lightbar/main.c
+++ b/extra/lightbar/main.c
@@ -290,3 +290,40 @@ done:
fclose(fp);
return r;
}
+
+int lb_load_program(const char *filename, struct lb_program *prog)
+{
+ FILE *fp;
+ size_t got;
+ int rc;
+
+ fp = fopen(filename, "rb");
+ if (!fp) {
+ fprintf(stderr, "Can't open %s: %s\n",
+ filename, strerror(errno));
+ return 1;
+ }
+
+ rc = fseek(fp, 0, SEEK_END);
+ if (rc) {
+ fprintf(stderr, "Couldn't find end of file %s",
+ filename);
+ fclose(fp);
+ return 1;
+ }
+ rc = (int) ftell(fp);
+ if (rc > LB_PROG_LEN) {
+ fprintf(stderr, "File %s is too long, aborting\n", filename);
+ fclose(fp);
+ return 1;
+ }
+ rewind(fp);
+
+ memset(prog->data, 0, LB_PROG_LEN);
+ got = fread(prog->data, 1, LB_PROG_LEN, fp);
+ if (rc != got)
+ fprintf(stderr, "Warning: did not read entire file\n");
+ prog->size = got;
+ fclose(fp);
+ return 0;
+}
diff --git a/extra/lightbar/simulation.h b/extra/lightbar/simulation.h
index 569107023b..2abfae230b 100644
--- a/extra/lightbar/simulation.h
+++ b/extra/lightbar/simulation.h
@@ -23,6 +23,7 @@ void *entry_lightbar(void *);
void init_windows(void);
int lb_read_params_from_file(const char *filename,
struct lightbar_params_v1 *p);
+int lb_load_program(const char *filename, struct lb_program *prog);
/* Interfaces to the EC code that we're encapsulating */
void lightbar_task(void);
int fake_consolecmd_lightbar(int argc, char *argv[]);
diff --git a/include/ec_commands.h b/include/ec_commands.h
index 552fc5dfc0..e148660644 100644
--- a/include/ec_commands.h
+++ b/include/ec_commands.h
@@ -1032,6 +1032,13 @@ struct lightbar_params_v1 {
struct rgb_s color[8]; /* 0-3 are Google colors */
} __packed;
+/* Lightbyte program. */
+#define LB_PROG_LEN 192
+struct lb_program {
+ uint8_t size;
+ uint8_t data[LB_PROG_LEN];
+};
+
struct ec_params_lightbar {
uint8_t cmd; /* Command (see enum lightbar_command) */
union {
@@ -1058,6 +1065,7 @@ struct ec_params_lightbar {
struct lightbar_params_v0 set_params_v0;
struct lightbar_params_v1 set_params_v1;
+ struct lb_program set_program;
};
} __packed;
@@ -1090,7 +1098,8 @@ struct ec_response_lightbar {
struct {
/* no return params */
} off, on, init, set_brightness, seq, reg, set_rgb,
- demo, set_params_v0, set_params_v1;
+ demo, set_params_v0, set_params_v1,
+ set_program;
};
} __packed;
@@ -1114,6 +1123,7 @@ enum lightbar_command {
LIGHTBAR_CMD_GET_DEMO = 15,
LIGHTBAR_CMD_GET_PARAMS_V1 = 16,
LIGHTBAR_CMD_SET_PARAMS_V1 = 17,
+ LIGHTBAR_CMD_SET_PROGRAM = 18,
LIGHTBAR_NUM_CMDS
};
diff --git a/include/lightbar_msg_list.h b/include/lightbar_msg_list.h
index fe75ccc021..77b8c60ad2 100644
--- a/include/lightbar_msg_list.h
+++ b/include/lightbar_msg_list.h
@@ -20,4 +20,5 @@
LBMSG(PULSE), /* A */ \
LBMSG(TEST), /* B */ \
LBMSG(KONAMI), /* C */ \
- LBMSG(TAP), /* D */
+ LBMSG(TAP), /* D */ \
+ LBMSG(PROGRAM), /* E */
diff --git a/util/ectool.c b/util/ectool.c
index 8cf5408a0f..c33ebd7a74 100644
--- a/util/ectool.c
+++ b/util/ectool.c
@@ -1581,6 +1581,7 @@ static const struct {
LB_SIZES(get_demo),
LB_SIZES(get_params_v1),
LB_SIZES(set_params_v1),
+ LB_SIZES(set_program),
};
#undef LB_SIZES
@@ -1601,6 +1602,7 @@ static int lb_help(const char *cmd)
printf(" %s demo [0|1] - turn demo mode on & off\n", cmd);
printf(" %s params [setfile] - get params"
" (or set from file)\n", cmd);
+ printf(" %s program file - load program from file\n", cmd);
return 0;
}
@@ -1999,6 +2001,43 @@ static void lb_show_params_v1(const struct lightbar_params_v1 *p)
p->color[i].b, i);
}
+static int lb_load_program(const char *filename, struct lb_program *prog)
+{
+ FILE *fp;
+ size_t got;
+ int rc;
+
+ fp = fopen(filename, "rb");
+ if (!fp) {
+ fprintf(stderr, "Can't open %s: %s\n",
+ filename, strerror(errno));
+ return 1;
+ }
+
+ rc = fseek(fp, 0, SEEK_END);
+ if (rc) {
+ fprintf(stderr, "Couldn't find end of file %s",
+ filename);
+ fclose(fp);
+ return 1;
+ }
+ rc = (int) ftell(fp);
+ if (rc > LB_PROG_LEN) {
+ fprintf(stderr, "File %s is too long, aborting\n", filename);
+ fclose(fp);
+ return 1;
+ }
+ rewind(fp);
+
+ memset(prog->data, 0, LB_PROG_LEN);
+ got = fread(prog->data, 1, LB_PROG_LEN, fp);
+ if (rc != got)
+ fprintf(stderr, "Warning: did not read entire file\n");
+ prog->size = got;
+ fclose(fp);
+ return 0;
+}
+
static int cmd_lightbar_params_v0(int argc, char **argv)
{
struct ec_params_lightbar param;
@@ -2145,6 +2184,11 @@ static int cmd_lightbar(int argc, char **argv)
return lb_do_cmd(LIGHTBAR_CMD_SEQ, &param, &resp);
}
+ if (argc >= 3 && !strcasecmp(argv[1], "program")) {
+ lb_load_program(argv[2], &param.set_program);
+ return lb_do_cmd(LIGHTBAR_CMD_SET_PROGRAM, &param, &resp);
+ }
+
if (argc == 4) {
char *e;
param.reg.ctrl = 0xff & strtoul(argv[1], &e, 16);