Merge pull request #51 from CodeLinaro/for-linux-msm/status-support

Programmatic access to remote power measurements
This commit is contained in:
Dmitry Baryshkov
2023-11-28 05:34:11 +02:00
committed by GitHub
15 changed files with 507 additions and 28 deletions

51
README
View File

@@ -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 <board> -h <host> [-c <power-cylce-count>] boot.img
cdba -b <board> -h <host> [-c <power-cylce-count>] [-s <status-fifo>] boot.img
<host> will be connected to using ssh and <board> 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}}

View File

@@ -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,
};

View File

@@ -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);

47
cdba.c
View File

@@ -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;
memcpy(str, data, len);
str[len] = '\n';
write(status_fd, data, len);
}
write(STDOUT_FILENO, str, len + 1);
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);
status_fd = fd;
/* 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);

View File

@@ -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

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);

View File

@@ -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']

View File

@@ -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:
* <number>mV <number>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,
};

View File

@@ -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"

94
status-cmd.c Normal file
View File

@@ -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 <err.h>
#include <pty.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}

8
status-cmd.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __STATUS_CMD_H__
#define __STATUS_CMD_H__
struct device;
int status_cmd_open(struct device *dev);
#endif

84
status.c Normal file
View File

@@ -0,0 +1,84 @@
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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);
}

21
status.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef __STATUS_H__
#define __STATUS_H__
#include <stdlib.h>
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