blob: f6f8258772e8c3aa477a5360659c96873e8a785d [file] [log] [blame]
/*
* Copyright 2017 Limes Audio AB. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "diagnose.h"
#include "device.h"
#include <argp.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libudev.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <unistd.h>
struct arguments {
uint8_t report_id;
uint16_t diag_id;
};
struct hidraw_dev {
char *path;
char *serial;
struct hidraw_dev *next;
};
struct diag_command {
uint16_t id;
const char *str;
};
static struct diag_command commands[] = {{0x1230, "SERIAL_MASTER"},
{0x1231, "SERIAL_SLAVE_UP0"},
{0x1232, "SERIAL_SLAVE_UP1"},
{0x1233, "SERIAL_SLAVE_UP2"},
{0x1234, "SERIAL_SLAVE_UP3"},
{0x1235, "SERIAL_SLAVE_DOWN0"},
{0x1236, "SERIAL_SLAVE_DOWN1"},
{0x1237, "SERIAL_SLAVE_DOWN2"},
{0x1238, "SERIAL_SLAVE_DOWN3"},
{0x1300, "VERSION_MASTER"},
{0x1310, "VERSION_SLAVE_UP0"},
{0x1311, "VERSION_SLAVE_UP1"},
{0x1312, "VERSION_SLAVE_UP2"},
{0x1313, "VERSION_SLAVE_UP3"},
{0x1314, "VERSION_SLAVE_DOWN0"},
{0x1315, "VERSION_SLAVE_DOWN1"},
{0x1316, "VERSION_SLAVE_DOWN2"},
{0x1317, "VERSION_SLAVE_DOWN3"},
{0x1350, "DAISY_MIN_VERSION"},
{0x1351, "DAISY_NUM_SLAVES"},
{0x1500, "DAISY_DFU_STATUS"},
{0x1600, "STATE_MUTED"},
{0x1601, "STATE_HOOK_OFF"},
{0x1602, "STATE_VOLUME"},
{0x1603, "STATE_LED"},
{0x1604, "STATE_UPTIME"},
{0x1605, "STATE_SHARC_ALIVE"},
{0x5001, "SPI_QUEUE_STATUS"},
{0}};
struct report {
uint8_t report_id;
uint16_t diag_id;
char *message;
};
static const size_t report_header_length = 3;
static const size_t report_length = 64;
static void
report_pack(struct report *report, char *buf)
{
buf[0] = report->report_id;
buf[1] = report->diag_id;
buf[2] = report->diag_id >> 8;
}
static void
report_unpack(struct report *report, char *buf)
{
report->report_id = buf[0];
report->diag_id = (buf[2] << 8) | buf[1];
report->message = &buf[3];
}
static int
get_report(int fd, struct report *report)
{
char buf[report_length];
report_pack(report, buf);
ssize_t retval = write(fd, buf, report_header_length);
if (retval < 0) {
return -1;
}
struct report read_report;
while (true) {
fd_set readset;
struct timeval tv;
do {
FD_ZERO(&readset);
FD_SET(fd, &readset);
tv.tv_sec = 0;
tv.tv_usec = 100000;
retval = select(fd + 1, &readset, NULL, NULL, &tv);
if ((retval <= 0) && (errno != EINTR)) {
return -1;
}
} while ((retval < 0) && (errno == EINTR));
retval = read(fd, buf, report_length);
if (retval < 0) {
return -1;
}
report_unpack(&read_report, buf);
if (read_report.diag_id == report->diag_id) {
break;
}
if (read_report.diag_id == 0x7FF0) {
// Unknown command response
break;
}
}
strncpy(report->message, read_report.message,
report_length - report_header_length);
return 0;
}
static int
str_to_uint16(const char *str, uint16_t *val)
{
int retval = sscanf(str, "%" SCNx16, val);
return (retval == EOF || retval < 0) ? -1 : 0;
}
static int
get_hidraw_devices(struct hidraw_dev **head)
{
*head = NULL;
struct udev *udev = udev_new();
if (!udev) {
return -1;
}
struct udev_enumerate *enumerate = udev_enumerate_new(udev);
if (!enumerate) {
return -1;
}
udev_enumerate_add_match_subsystem(enumerate, "hidraw");
udev_enumerate_scan_devices(enumerate);
struct hidraw_dev *current = NULL;
struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
struct udev_list_entry *dev_list_entry;
udev_list_entry_foreach(dev_list_entry, devices)
{
const char *path = udev_list_entry_get_name(dev_list_entry);
struct udev_device *dev = udev_device_new_from_syspath(udev, path);
const char *dev_node_path = udev_device_get_devnode(dev);
struct udev_device *parent;
parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb",
"usb_device");
if (!parent) {
continue;
}
uint16_t idVendor;
const char *attr = udev_device_get_sysattr_value(parent, "idVendor");
int retval = str_to_uint16(attr, &idVendor);
if (retval < 0) {
goto unref;
}
uint16_t idProduct;
attr = udev_device_get_sysattr_value(parent, "idProduct");
retval = str_to_uint16(attr, &idProduct);
if (retval < 0) {
goto unref;
}
if ((idVendor != USB_VID) || (idProduct != USB_PID)) {
goto unref;
}
struct hidraw_dev *prev = current;
current = calloc(1, sizeof(*current));
if (*head == NULL) {
*head = current;
}
size_t dev_node_length = strlen(dev_node_path) + 1;
current->path = malloc(sizeof(*current->path) * dev_node_length);
strncpy(current->path, dev_node_path, dev_node_length);
const char *serial = udev_device_get_sysattr_value(parent, "serial");
size_t serial_length = strlen(serial) + 1;
current->serial = malloc(sizeof(*current->serial) * serial_length);
strncpy(current->serial, serial, serial_length);
if (prev) {
prev->next = current;
}
unref:
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate);
udev_unref(udev);
return 0;
}
static void
get_and_print_cmd(int fd, struct diag_command *cmd, struct report *report)
{
const char *message = "Error reading report";
int retval = get_report(fd, report);
if (retval == 0) {
message = report->message;
}
printf("0x%04X %s: %s\n", cmd->id, cmd->str, message);
}
static error_t
parser(int key, char *arg, struct argp_state *state)
{
struct arguments *arguments = state->input;
switch (key) {
case 'd':
arguments->diag_id = (uint16_t)strtol(arg, NULL, 0);
break;
case 'r':
arguments->report_id = (uint8_t)strtol(arg, NULL, 0);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
int
diagnose(int argc, char **argv)
{
char doc[] =
"\n"
"Options:";
struct argp_option options[] = {
{"diag", 'd', "id", 0, "Diagnostic ID", 0},
{"report", 'r', "id", 0, "HID report ID", 0},
{0}};
struct argp argp = {options, parser, NULL, doc, NULL, NULL, NULL};
struct arguments arguments = {
.report_id = 0x07, .diag_id = 0,
};
int retval = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, &arguments);
if (retval < 0) {
return -1;
}
struct hidraw_dev *devs;
get_hidraw_devices(&devs);
struct hidraw_dev *current = devs;
while (current) {
int fd = open(current->path, O_RDWR);
if (fd < 0) {
perror(current->path);
current = current->next;
continue;
}
printf("Diagnostics from %s\n", current->serial);
struct report report;
report.report_id = arguments.report_id;
char buf[report_length - report_header_length];
report.message = buf;
struct diag_command *cmd;
if (arguments.diag_id != 0) {
struct diag_command custom_cmd = {.id = arguments.diag_id,
.str = "UNKNOWN"};
for (cmd = commands; (*cmd).id; ++cmd) {
if (cmd->id == custom_cmd.id) {
custom_cmd.str = cmd->str;
}
}
report.diag_id = custom_cmd.id;
get_and_print_cmd(fd, &custom_cmd, &report);
} else {
for (cmd = commands; (*cmd).id; ++cmd) {
report.diag_id = cmd->id;
get_and_print_cmd(fd, cmd, &report);
};
}
close(fd);
current = current->next;
}
current = devs;
while (current) {
struct hidraw_dev *next = current->next;
free(current->path);
free(current->serial);
free(current);
current = next;
}
return 0;
}