diff --git a/README b/README index 8a4d84c..8a1824e 100644 --- a/README +++ b/README @@ -18,7 +18,7 @@ from sandbox/cdba/cdba-server. Available devices are read from $HOME/.cdba = Client side The client is invoked as: - cdba -b -h [-c ] boot.img + cdba -b -h [-c ] [-s ] boot.img will be connected to using ssh and will be selected for operation. As the board's fastboot interface shows up the given boot.img will @@ -31,9 +31,23 @@ If the optional -c is given, the board will upon receiving the tilde sequence restart the board the given number of times. Each time booting the given boot.img. +The optional -s argument can be used to specify that a fifo should be created +and opened. cdba will request the server to start sending status/measurement +updates, which will be written to this fifo. + += Server side + == Device configuration The list of attached devices is read from $HOME/.cdba and is YAML formatted. +== Status command + +The "status-cmd" property for a board specifies a command line that should be +executed to perform measurements and report status updates to the client. The +command is expected to run for the duration of the board session and should +produce a continuous stream of json-formatted lines of status updates according +to the format defined in this document. + === Example devices: - board: db2k @@ -110,3 +124,38 @@ devices: fastboot: cacafada fastboot_set_active: true fastboot_key_timeout: 2 + += Status messages + +The status messages that are used by the client fifo and the server's status +command should be json-formatted, with one status update per line. + +Each message should contain one timestamp member "ts", and one or more +measurement members. Like so: + + {"ts":%d.%03d, "name": {["mv"|"ma"]: %u}(, "name2": {["mv"|"ma"]: %u})*} + +The timestamp member ("ts"), should provide the time since first measurement in +decimal form with millisecond accuracy. + +The key for the measurement members should be an identifier of the measured +resources, and the value should be an object with members for each unit +measured for the given resource and the measured value. + +Valid units to report are "mv", "ma", and "mw". + +Note that the cadence of measurement might differ between different items to be +measured, so not all status messages contains data for all items that can be +measured. + +== Examples + +Single resource "dc" measured at 20.271s, with voltage and current reported: + + {"ts":20.271, "dc":{ "mv": 12165, "ma": 114}} + +Multiple resources measured in a single status message, followed by single +resource measurement, all with voltage and current reported: + + {"ts":38.341, "battery":{"mv":8023, "ma":725}, "vdd_cx":{"mv":750, "ma":466}} + {"ts":44.339, "battery":{"mv":8023, "ma":733}} diff --git a/cdb_assist.c b/cdb_assist.c index d62ef22..0cc801f 100644 --- a/cdb_assist.c +++ b/cdb_assist.c @@ -43,6 +43,7 @@ #include "cdba-server.h" #include "device.h" +#include "status.h" struct cdb_assist { char serial[9]; @@ -338,23 +339,37 @@ static void cdb_gpio(struct cdb_assist *cdb, int gpio, bool on) cdb_ctrl_write(cdb, &cmd[gpio][on], 1); } -static void cdb_assist_print_status(struct device *dev) +static void cdb_assist_print_status(void *data) +{ + struct cdb_assist *cdb = data; + struct status_value vbat[] = { + { + .unit = STATUS_MV, + .value = cdb->voltage_set, + }, + { + .unit = STATUS_MA, + .value = cdb->current_actual, + }, + {} + }; + struct status_value vref[] = { + { + .unit = STATUS_MV, + .value = cdb->vref, + }, + {} + }; + + status_send_values("vbat", vbat); + status_send_values("vref", vref); +} + +static void cdb_assist_status_enable(struct device *dev) { struct cdb_assist *cdb = dev->cdb; - char buf[128]; - int n; - n = sprintf(buf, "%umV %umA%s%s%s%s%s ref: %umV", - cdb->voltage_set, - cdb->current_actual, - cdb->vbat ? " vbat" : "", - cdb->vbus ? " vbus" : "", - cdb->btn[0] ? " btn1" : "", - cdb->btn[1] ? " btn2" : "", - cdb->btn[2] ? " btn3" : "", - cdb->vref); - - cdba_send_buf(MSG_STATUS_UPDATE, n, buf); + watch_timer_add(1000, cdb_assist_print_status, cdb); } static void cdb_set_voltage(struct cdb_assist *cdb, unsigned mV) @@ -384,7 +399,7 @@ const struct control_ops cdb_assist_ops = { .open = cdb_assist_open, .close = cdb_assist_close, .power = cdb_assist_power, - .print_status = cdb_assist_print_status, + .status_enable = cdb_assist_status_enable, .usb = cdb_assist_usb, .key = cdb_assist_key, }; diff --git a/cdba-server.c b/cdba-server.c index f1a7157..c65f606 100644 --- a/cdba-server.c +++ b/cdba-server.c @@ -210,7 +210,7 @@ static int handle_stdin(int fd, void *buf) // fprintf(stderr, "fastboot boot\n"); break; case MSG_STATUS_UPDATE: - device_print_status(selected_device); + device_status_enable(selected_device); break; case MSG_VBUS_ON: device_usb(selected_device, true); diff --git a/cdba.c b/cdba.c index 00c02b1..a16069a 100644 --- a/cdba.c +++ b/cdba.c @@ -52,6 +52,8 @@ static bool quit; static bool fastboot_repeat; static bool fastboot_done; +static int status_fd = -1; + static const char *fastboot_file; static struct termios *tty_unbuffer(void) @@ -398,12 +400,40 @@ static void request_fastboot_files(void) static void handle_status_update(const void *data, size_t len) { - char *str = alloca(len + 1); + if (status_fd < 0) + return; + + write(status_fd, data, len); +} + +static void status_enable_fn(struct work *work, int ssh_stdin) +{ + cdba_send(ssh_stdin, MSG_STATUS_UPDATE); + + free(work); +} + +static void status_pipe_open(const char *path) +{ + struct work *work; + int ret; + int fd; + + ret = mkfifo(path, 0600); + if (ret < 0 && errno != EEXIST) + err(1, "failed to create fifo %s", path); + + fd = open(path, O_RDWR | O_NONBLOCK); + if (fd < 0) + err(1, "failed to open fifo %s", path); - memcpy(str, data, len); - str[len] = '\n'; + status_fd = fd; - write(STDOUT_FILENO, str, len + 1); + /* Queue a MSG_STATUS_UPDATE request */ + work = malloc(sizeof(*work)); + work->fn = status_enable_fn; + + list_add(&work_items, &work->node); } static void handle_list_devices(const void *data, size_t len) @@ -577,6 +607,7 @@ int main(int argc, char **argv) struct timeval timeout_total_tv; struct termios *orig_tios; const char *server_binary = "cdba-server"; + const char *status_pipe = NULL; int timeout_inactivity = 0; int timeout_total = 600; struct work *next; @@ -597,7 +628,7 @@ int main(int argc, char **argv) int opt; int ret; - while ((opt = getopt(argc, argv, "b:c:C:h:ilRt:S:T:")) != -1) { + while ((opt = getopt(argc, argv, "b:c:C:h:ilRt:S:s:T:")) != -1) { switch (opt) { case 'b': board = optarg; @@ -623,6 +654,9 @@ int main(int argc, char **argv) case 'S': server_binary = optarg; break; + case 's': + status_pipe = optarg; + break; case 't': timeout_total = atoi(optarg); break; @@ -661,6 +695,9 @@ int main(int argc, char **argv) break; } + if (status_pipe) + status_pipe_open(status_pipe); + ret = fork_ssh(host, server_binary, ssh_fds); if (ret) err(1, "failed to connect to \"%s\"", host); diff --git a/config-samples/sample11.yaml b/config-samples/sample11.yaml new file mode 100644 index 0000000..9496b39 --- /dev/null +++ b/config-samples/sample11.yaml @@ -0,0 +1,8 @@ +--- +devices: + - board: myboard + name: "My Board" + alpaca: /dev/ttyACM0 + console: /dev/ttyUSB0 + fastboot: cacafada + status-cmd: /usr/bin/sample-measure-app --sample-rate 100 /dev/measure0 diff --git a/device.c b/device.c index 01f369c..95cded8 100644 --- a/device.c +++ b/device.c @@ -45,6 +45,7 @@ #include "fastboot.h" #include "list.h" #include "ppps.h" +#include "status-cmd.h" #define ARRAY_SIZE(x) ((sizeof(x)/sizeof((x)[0]))) @@ -260,10 +261,18 @@ int device_power(struct device *device, bool on) return device_power_off(device); } -void device_print_status(struct device *device) +void device_status_enable(struct device *device) { - if (device_has_control(device, print_status)) - device_control(device, print_status); + if (device->status_enabled) + return; + + if (device_has_control(device, status_enable)) + device_control(device, status_enable); + + if (device->status_cmd) + status_cmd_open(device); + + device->status_enabled = true; } void device_usb(struct device *device, bool on) @@ -300,6 +309,11 @@ void device_boot(struct device *device, const void *data, size_t len) fastboot_set_active(device->fastboot, device->set_active); fastboot_download(device->fastboot, data, len); device->boot(device); + + if (device->status_enabled && !device->usb_always_on) { + warnx("disabling USB, use ^A V to enable"); + device_usb(device, false); + } } void device_send_break(struct device *device) diff --git a/device.h b/device.h index 5fdd47b..600cbe5 100644 --- a/device.h +++ b/device.h @@ -17,7 +17,7 @@ struct control_ops { int (*power)(struct device *dev, bool on); void (*usb)(struct device *dev, bool on); void (*key)(struct device *device, int key, bool asserted); - void (*print_status)(struct device *dev); + void (*status_enable)(struct device *dev); }; struct console_ops { @@ -46,6 +46,8 @@ struct device { int state; bool has_power_key; + bool status_enabled; + void (*boot)(struct device *); const struct control_ops *control_ops; @@ -56,6 +58,8 @@ struct device { void *cdb; void *console; + char *status_cmd; + struct list_head node; }; @@ -73,7 +77,7 @@ struct device *device_open(const char *board, void device_close(struct device *dev); int device_power(struct device *device, bool on); -void device_print_status(struct device *device); +void device_status_enable(struct device *device); void device_usb(struct device *device, bool on); int device_write(struct device *device, const void *buf, size_t len); diff --git a/device_parser.c b/device_parser.c index a137628..fd65816 100644 --- a/device_parser.c +++ b/device_parser.c @@ -193,6 +193,8 @@ static void parse_board(struct device_parser *dp) dev->ppps_path = strdup(value); } else if (!strcmp(key, "ppps3_path")) { dev->ppps3_path = strdup(value); + } else if (!strcmp(key, "status-cmd")) { + dev->status_cmd = strdup(value); } else { fprintf(stderr, "device parser: unknown key \"%s\"\n", key); exit(1); diff --git a/meson.build b/meson.build index 63cb838..f1d12bd 100644 --- a/meson.build +++ b/meson.build @@ -58,6 +58,13 @@ server_deps = [dependency('libudev', required: server_opt), dependency('yaml-0.1', required: server_opt), gpiod_dep, ftdi_dep] + +# E.g. Debian reuires -lutil for forkpty +if not compiler.has_function('forkpty') + util_dep = compiler.find_library('util') + server_deps += util_dep +endif + server_srcs = ['cdba-server.c', 'cdb_assist.c', 'circ_buf.c', @@ -71,7 +78,9 @@ server_srcs = ['cdba-server.c', 'local-gpio.c', 'console.c', 'qcomlt_dbg.c', - 'ppps.c'] + 'ppps.c', + 'status.c', + 'status-cmd.c'] if gpiod_dep.version().version_compare('>=2.0') server_srcs += ['local-gpio-v2.c'] diff --git a/qcomlt_dbg.c b/qcomlt_dbg.c index bda7e61..64cfc2c 100644 --- a/qcomlt_dbg.c +++ b/qcomlt_dbg.c @@ -44,10 +44,25 @@ #include "cdba-server.h" #include "device.h" +#include "status.h" + +enum qcomlt_parse_state { + STATE_, + STATE_num, + STATE_num_m, + STATE_num_mV, + STATE_num_mV_num, + STATE_num_mV_num_m, + STATE_err, +}; struct qcomlt_dbg { int fd; struct termios orig_tios; + + enum qcomlt_parse_state parse_state; + unsigned long mv; + unsigned long ma; }; static void *qcomlt_dbg_open(struct device *dev) @@ -100,9 +115,124 @@ static void qcomlt_dbg_key(struct device *dev, int key, bool asserted) } } +static int qcomlt_dbg_ctrl_data(int fd, void *data) +{ + struct qcomlt_dbg *dbg = data; + struct status_value dc[] = { + { + .unit = STATUS_MV, + }, + { + .unit = STATUS_MA, + }, + {} + }; + char buf[64]; + ssize_t i; + ssize_t n; + char ch; + + n = read(fd, buf, sizeof(buf)); + if (n < 0) + return n; + + for (i = 0; i < n; i++) { + ch = buf[i]; + + /* + * The control data consists of a stream in the format: + * mV mA + * + * The stream might be split in arbitrary ways across reads, so + * a parser is used instead of sscanf(). + * In the initial state any non-digits are ignored, if a parse + * error occurs thereafter all characters until 'A' are + * dropped, with the result that any unexpected control + * messages are ignored. + */ + switch (dbg->parse_state) { + case STATE_: + if (isdigit(ch)) { + dbg->mv = ch - '0'; + dbg->parse_state = STATE_num; + } + break; + case STATE_num: + if (isdigit(ch)) { + dbg->mv *= 10; + dbg->mv += ch - '0'; + } else if (ch == 'm') { + dbg->parse_state = STATE_num_m; + } else { + dbg->parse_state = STATE_err; + } + break; + case STATE_num_m: + if (ch == 'V') + dbg->parse_state = STATE_num_mV; + else + dbg->parse_state = STATE_err; + break; + case STATE_num_mV: + if (isdigit(ch)) { + dbg->ma = ch - '0'; + dbg->parse_state = STATE_num_mV_num; + } else if (!isspace(ch)) { + dbg->parse_state = STATE_err; + } + break; + case STATE_num_mV_num: + if (isdigit(ch)) { + dbg->ma *= 10; + dbg->ma += ch - '0'; + } else if (ch == 'm') { + dbg->parse_state = STATE_num_mV_num_m; + } else { + dbg->parse_state = STATE_err; + } + break; + case STATE_num_mV_num_m: + if (ch == 'A') { + /* Parser found a match, report it */ + dc[0].value = dbg->mv; + dc[1].value = dbg->ma; + + status_send_values("dc", dc); + } else { + dbg->parse_state = STATE_err; + } + break; + case STATE_err: + if (ch == 'A') + dbg->parse_state = STATE_; + break; + } + } + + return 0; +} + +static void qcomlt_dbg_request_status(void *data) +{ + struct qcomlt_dbg *dbg = data; + + write(dbg->fd, "s", 1); + + watch_timer_add(200, qcomlt_dbg_request_status, dbg); +} + +static void qcomlt_dbg_status_enable(struct device *dev) +{ + struct qcomlt_dbg *dbg = dev->cdb; + + watch_add_readfd(dbg->fd, qcomlt_dbg_ctrl_data, dbg); + watch_timer_add(200, qcomlt_dbg_request_status, dbg); +} + const struct control_ops qcomlt_dbg_ops = { .open = qcomlt_dbg_open, .power = qcomlt_dbg_power, .usb = qcomlt_dbg_usb, .key = qcomlt_dbg_key, + .status_enable = qcomlt_dbg_status_enable, }; diff --git a/schema.yaml b/schema.yaml index caf0860..d4c0338 100644 --- a/schema.yaml +++ b/schema.yaml @@ -73,6 +73,10 @@ properties: description: USB device name, like 2-2:1.0/2-2-port2 type: string + status-cmd: + description: Command to execute for generating status updates + type: string + qcomlt_debug_board: description: Qlt Debug Board control tty device path $ref: "#/$defs/device_path" diff --git a/status-cmd.c b/status-cmd.c new file mode 100644 index 0000000..3529a63 --- /dev/null +++ b/status-cmd.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023, Qualcomm Innovaction Center, Inc + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cdba-server.h" +#include "device.h" +#include "status.h" +#include "status-cmd.h" + +static void launch_status_cmd(struct device *dev) +{ + char *tokens[100]; + char *p; + int t = 0; + + p = strtok(dev->status_cmd, " "); + while (p) { + tokens[t++] = p; + p = strtok(NULL, " "); + if (t == 100) + exit(1); + } + tokens[t] = NULL; + + execvp(tokens[0], tokens); + exit(1); +} + +static int status_data(int fd, void *data) +{ + char buf[128]; + ssize_t n; + + n = read(fd, buf, sizeof(buf)); + if (n <= 0) + return n; + + status_send_raw(buf, n); + return 0; +} + +int status_cmd_open(struct device *dev) +{ + pid_t status_pid; + int fd; + + status_pid = forkpty(&fd, NULL, NULL, NULL); + if (status_pid < 0) + err(1, "failed to fork"); + + if(status_pid == 0) { + launch_status_cmd(dev); + /* Notreached */ + } + + watch_add_readfd(fd, status_data, dev); + + return 0; +} diff --git a/status-cmd.h b/status-cmd.h new file mode 100644 index 0000000..5dc9c24 --- /dev/null +++ b/status-cmd.h @@ -0,0 +1,8 @@ +#ifndef __STATUS_CMD_H__ +#define __STATUS_CMD_H__ + +struct device; + +int status_cmd_open(struct device *dev); + +#endif diff --git a/status.c b/status.c new file mode 100644 index 0000000..431c04f --- /dev/null +++ b/status.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include + +#include "cdba-server.h" +#include "status.h" + +static const char *sz_units[] = { + [STATUS_MV] = "mv", + [STATUS_MA] = "ma", + [STATUS_GPIO] = "gpio", +}; + +static void status_get_ts(struct timespec *ts) +{ + static struct timespec t0; + struct timespec t; + + if (!t0.tv_sec && !t0.tv_nsec) + clock_gettime(CLOCK_MONOTONIC, &t0); + + clock_gettime(CLOCK_MONOTONIC, &t); + + if (t.tv_nsec < t0.tv_nsec) { + ts->tv_sec = t.tv_sec - t0.tv_sec - 1; + ts->tv_nsec = 1000000000 + (t.tv_nsec - t0.tv_nsec); + } else { + ts->tv_sec = t.tv_sec - t0.tv_sec; + ts->tv_nsec = t.tv_nsec - t0.tv_nsec; + } +} + +void status_send_values(const char *id, struct status_value *values) +{ + struct status_value *value; + struct timespec ts; + char chunk[32]; + char buf[256]; + size_t len; + size_t n; + + status_get_ts(&ts); + + len = snprintf(buf, sizeof(buf), "{\"ts\":%ld.%03ld, \"%s\":{ ", ts.tv_sec, ts.tv_nsec / 1000000, id); + + for (value = values; value->unit; value++) { + if (value != values) { + if (len + 3 >= sizeof(buf)) { + warnx("status message overflow"); + return; + } + + strcpy(buf + len, ", "); + len += 2; + } + + n = snprintf(chunk, sizeof(chunk), "\"%s\": %u", sz_units[value->unit], value->value); + + if (len + n + 1>= sizeof(buf)) { + warnx("status message overflow"); + return; + } + + strcpy(buf + len, chunk); + len += n; + } + + if (len + 4 >= sizeof(buf)) { + warnx("status message overflow"); + return; + } + + strcpy(buf + len, "}}\n"); + len += 3; + + cdba_send_buf(MSG_STATUS_UPDATE, len, buf); +} + +void status_send_raw(const char *data, size_t len) +{ + cdba_send_buf(MSG_STATUS_UPDATE, len, data); +} diff --git a/status.h b/status.h new file mode 100644 index 0000000..4838b51 --- /dev/null +++ b/status.h @@ -0,0 +1,21 @@ +#ifndef __STATUS_H__ +#define __STATUS_H__ + +#include + +enum status_unit { + STATUS_EOF, + STATUS_MV, + STATUS_MA, + STATUS_GPIO, +}; + +struct status_value { + enum status_unit unit; + unsigned int value; +}; + +void status_send_values(const char *id, struct status_value *values); +void status_send_raw(const char *data, size_t len); + +#endif