blob: 36828be9ba5409f54ea725f3a80d33d6d0407b46 [file] [log] [blame]
/*
* Copyright (c) 2010, NVIDIA
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of NVIDIA 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.
*/
/* needed for strdup() and asprintf() */
#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <fcntl.h>
#include <dirent.h>
#include <linux/hiddev.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include "seaboard.h"
static char *prog_name;
struct state {
char *hiddev_filename;
int fd;
int timestamps;
int no_names;
};
#define outerr(fmt, args...) fprintf(stderr, "%s: " fmt "\n", prog_name, ## args)
#define perr(fmt, args...) outerr(fmt ": %s", ## args, strerror(errno))
/* Constants to identify the HW */
#define NVIDIA_VENDOR_ID 0x955
#define USB_DAC_PRODUCT_ID 0x9
static int is_usage_current(int usage) {
/* Odd usages are current */
return !!(usage & 1);
}
static int probe_from_usage(int usage) {
/* Each probe covers two consecutive usages, so this gets the probe index */
return usage >> 1;
}
static double scale_reading(int input, int usage) {
if (is_usage_current(usage)) {
static const double scale_factor = 3.3 / 1023. / 100.;
double output = input * scale_factor;
output /= seaboard_probes[probe_from_usage(usage)].resistance;
return output;
} else {
static const double scale_factor = 3.3 / 1023. / 0.1708;
double output = input * scale_factor;
return output;
}
}
static void print_data(struct state *s, double value, int usage,
uint64_t time) {
const char *name = seaboard_probes[probe_from_usage(usage)].name;
const char *type = is_usage_current(usage) ? "CURR" : "VOLT";
if (s->timestamps) {
printf("%" PRIu64 ": ", time);
}
if (s->no_names) {
printf("%02u:%.16f\n", usage, value);
} else {
printf("%02u %8s %s %.16f\n", usage, name, type, value);
}
}
static uint64_t gettime64() {
struct timeval tv;
if (gettimeofday(&tv, NULL)) {
perr("gettimeofday failed");
return 0;
}
return (uint64_t) tv.tv_sec * 1000000 + tv.tv_usec;
}
#define USB_HID_ORDINAL_USAGE_PAGE 0xa
static int read_data(struct state *s) {
struct hiddev_event events[1024];
ssize_t num_read;
retry:
while ((num_read = read(s->fd, &events, sizeof(events))) > 0) {
int i;
uint64_t time = gettime64();
for (i = 0; i < num_read / sizeof(events[0]); i += 1) {
double scaled_value;
int usage_page = events[i].hid >> 16;
int usage_code = (events[i].hid & 0xffff) - 1;
/* All of the "data" reports are "ordinal", skip the rest */
if (usage_page != USB_HID_ORDINAL_USAGE_PAGE)
continue;
scaled_value = scale_reading(events[i].value, usage_code);
print_data(s, scaled_value, usage_code, time);
}
/* Without this, realtime graph may lag */
fflush(NULL);
}
if (num_read < 0) {
int err = errno;
if (err == EINTR) {
goto retry;
}
perr("Failed to read from USB device");
return err;
}
return 0;
}
static int try_open_device(struct state *s, const char *filename) {
int fd = -1;
struct hiddev_devinfo dev_info = { 0 };
int ret;
fd = open(filename, O_RDONLY);
if (fd < 0) {
ret = errno;
perr("failed to open file '%s'", filename);
goto fail;
}
/* Check if we're looking at a NVIDIA USB DAC device */
ret = ioctl(fd, HIDIOCGDEVINFO, &dev_info);
if (ret) {
perr("Failed to read device info for %s", filename);
goto fail;
}
if (dev_info.vendor != NVIDIA_VENDOR_ID ||
dev_info.product != USB_DAC_PRODUCT_ID) {
ret = EINVAL;
goto fail;
}
s->fd = fd;
return 0;
fail:
if (fd >= 0) {
close(fd);
}
return ret;
}
static int find_hiddev(struct state *s) {
static char *dev_usb_dir = "/dev/usb";
struct dirent *dirent;
DIR *dir;
/* If the user picked a filename, just use that */
if (s->hiddev_filename)
return try_open_device(s, s->hiddev_filename);
/* Look for files /dev/usb/hiddev* */
dir = opendir(dev_usb_dir);
if (!dir) {
int err = errno;
perr("Failed to open %s", dev_usb_dir);
return err;
}
while ((dirent = readdir(dir))) {
char *filename = NULL;
#if defined(_DIRENT_HAVE_D_TYPE)
if (dirent->d_type != DT_CHR)
continue;
#endif
if (asprintf(&filename, "%s/%s", dev_usb_dir, dirent->d_name) == -1)
return ENOMEM;
if (!try_open_device(s, filename)) {
fprintf(stderr, "Found USB device at %s\n", filename);
s->hiddev_filename = filename;
return 0;
}
free(filename);
filename = NULL;
}
return ENOENT;
}
static void print_help(void) {
fprintf(stderr, "Usage: %s <options>\n\n", prog_name);
fprintf(stderr, "Valid options:\n");
fprintf(stderr, "--help, -h\n");
fprintf(stderr, " Print this help text and exit\n");
fprintf(stderr, "--hiddev_file <path>, -f <path>\n");
fprintf(stderr, " Manually specify the hiddev device file to open\n");
fprintf(stderr, " Default: autodetect\n");
fprintf(stderr, "--timestamps, -t\n");
fprintf(stderr, " Output timestamps. Default: off\n");
fprintf(stderr, "--short, -s\n");
fprintf(stderr, " Don't print strings describing the readings (useful "
"for scripting)\n");
fprintf(stderr, " Default: print the strings\n");
}
static int parse_options(struct state *s, int argc, char **argv) {
const struct option longopts[] = {
{ .name = "help",
.val = 'h', },
{ .name = "hiddev_file",
.has_arg = 1,
.val = 'f', },
{ .name = "timestamps",
.val = 't', },
{ .name = "short",
.val = 's', },
};
const char *shortopts = "f:hts";
char opt;
while ((opt = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
switch (opt) {
case 'f':
s->hiddev_filename = strdup(optarg);
if (!s->hiddev_filename) {
return ENOMEM;
}
break;
case 't':
s->timestamps = 1;
break;
case 's':
s->no_names = 1;
break;
case 'h':
print_help();
exit(0);
case '?':
return EINVAL;
}
}
return 0;
}
int main(int argc, char **argv) {
static struct state s;
prog_name = argv[0];
if (parse_options(&s, argc, argv)) {
print_help();
return 1;
}
if (find_hiddev(&s)) {
fprintf(stderr, "%s: Failed to find USB device, aborting\n", prog_name);
return 1;
}
if (read_data(&s)) {
fprintf(stderr, "%s: Failed to read data, aborting\n", prog_name);
return 1;
}
close(s.fd);
return 0;
}