X-Git-Url: https://sigrok.org/gitweb/?a=blobdiff_plain;f=src%2Fhardware%2Fdcttech-usbrelay%2Fapi.c;h=deeb234a95eb2e72e640e6a3d0bcc8e2b9e328a6;hb=77ee3168d0ae88c92a127999d890fff0511ca31e;hp=67de5d1e7dfac3ad6daf889ff6f67b2a96e31d4b;hpb=321f85fb07ea38d81abfde7769833838e8aef068;p=libsigrok.git diff --git a/src/hardware/dcttech-usbrelay/api.c b/src/hardware/dcttech-usbrelay/api.c index 67de5d1e..deeb234a 100644 --- a/src/hardware/dcttech-usbrelay/api.c +++ b/src/hardware/dcttech-usbrelay/api.c @@ -19,6 +19,7 @@ #include +#include #include #include @@ -43,8 +44,12 @@ static const uint32_t devopts_cg[] = { static struct sr_dev_driver dcttech_usbrelay_driver_info; -static struct sr_dev_inst *probe_device(const char *path, size_t relay_count) +static struct sr_dev_inst *probe_device_common(const char *path, + uint16_t vid, uint16_t pid, const char *want_serno, + const wchar_t *vendor, const wchar_t *product) { + char nonws[16], *s, *endp; + unsigned long relay_count; hid_device *hid; int ret; char serno[SERNO_LENGTH + 1]; @@ -58,11 +63,33 @@ static struct sr_dev_inst *probe_device(const char *path, size_t relay_count) struct channel_group_context *cgc; size_t idx, nr; struct sr_channel_group *cg; + char cg_name[24]; + + /* + * Get relay count from product string. Weak condition, + * accept any trailing number regardless of preceeding text. + */ + snprintf(nonws, sizeof(nonws), "%ls", product); + s = nonws; + s += strlen(s); + while (s > nonws && isdigit((int)s[-1])) + s--; + ret = sr_atoul_base(s, &relay_count, &endp, 10); + if (ret != SR_OK || !endp || *endp) + return NULL; + if (!relay_count) + return NULL; + sr_info("Relay count %lu from product string %s.", relay_count, nonws); /* Open device, need to communicate to identify. */ - hid = hid_open_path(path); - if (!hid) + if (vid && pid) + hid = hid_open(vid, pid, NULL); + else + hid = hid_open_path(path); + if (!hid) { + sr_err("Cannot open %s.", path); return NULL; + } /* Get an HID report. */ hid_set_nonblocking(hid, 0); @@ -72,11 +99,17 @@ static struct sr_dev_inst *probe_device(const char *path, size_t relay_count) hid_close(hid); if (sr_log_loglevel_get() >= SR_LOG_SPEW) { txt = sr_hexdump_new(report, sizeof(report)); - sr_spew("raw report, rc %d, bytes %s", ret, txt->str); + sr_spew("Got report bytes: %s, rc %d.", txt->str, ret); sr_hexdump_free(txt); } - if (ret != sizeof(report)) + if (ret < 0) { + sr_err("Cannot read %s: %ls.", path, hid_error(NULL)); return NULL; + } + if (ret != sizeof(report)) { + sr_err("Unexpected HID report length %d from %s.", ret, path); + return NULL; + } /* * Serial number must be all printable characters. Relay state @@ -87,46 +120,136 @@ static struct sr_dev_inst *probe_device(const char *path, size_t relay_count) for (snr_pos = 0; snr_pos < SERNO_LENGTH; snr_pos++) { c = report[1 + snr_pos]; serno[snr_pos] = c; - if (c < 0x20 || c > 0x7e) + if (c < 0x20 || c > 0x7e) { + sr_warn("Skipping %s, non-printable serial.", path); return NULL; + } } curr_state = report[1 + STATE_INDEX]; - sr_spew("report data, serno[%s], relays 0x%02x.", serno, curr_state); + sr_info("HID report data: serial number %s, relay state 0x%02x.", + serno, curr_state); - /* - * Create a device instance, create channels (groups). The - * caller fills in vendor, model, conn from USB enum details. - */ + /* Optionally filter by serial number. */ + if (want_serno && *want_serno && strcmp(serno, want_serno) != 0) { + sr_dbg("Serial number does not match user spec. Skipping."); + return NULL; + } + + /* Create a device instance. */ sdi = g_malloc0(sizeof(*sdi)); + sdi->vendor = g_strdup_printf("%ls", vendor); + sdi->model = g_strdup_printf("%ls", product); + sdi->serial_num = g_strdup(serno); + sdi->connection_id = g_strdup(path); + sdi->driver = &dcttech_usbrelay_driver_info; + sdi->inst_type = SR_INST_USB; + + /* Create channels (groups). */ devc = g_malloc0(sizeof(*devc)); sdi->priv = devc; devc->hid_path = g_strdup(path); + devc->usb_vid = vid; + devc->usb_pid = pid; devc->relay_count = relay_count; devc->relay_mask = (1U << relay_count) - 1; for (idx = 0; idx < devc->relay_count; idx++) { nr = idx + 1; - cg = g_malloc0(sizeof(*cg)); - cg->name = g_strdup_printf("R%zu", nr); + snprintf(cg_name, sizeof(cg_name), "R%zu", nr); cgc = g_malloc0(sizeof(*cgc)); - cg->priv = cgc; cgc->number = nr; - sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + cg = sr_channel_group_new(sdi, cg_name, cgc); + (void)cg; } return sdi; } +static struct sr_dev_inst *probe_device_enum(struct hid_device_info *dev, + const char *want_serno) +{ + return probe_device_common(dev->path, 0, 0, want_serno, + dev->manufacturer_string, dev->product_string); +} + +static struct sr_dev_inst *probe_device_conn(const char *path) +{ + char vid_pid[12]; + uint16_t vid, pid; + const char *s; + char *endp; + unsigned long num; + hid_device *dev; + gboolean ok; + int ret; + wchar_t vendor[32], product[32]; + + /* + * The hidapi(3) library's API strives for maximum portability, + * thus won't provide ways of getting a path from alternative + * presentations like VID:PID pairs, bus.addr specs, etc. The + * typical V-USB setup neither provides reliable serial numbers + * (that USB enumeration would cover). So this driver's support + * for conn= specs beyond Unix style path names is limited, too. + * This implementation tries "VID.PID" then assumes "path". The + * inability to even get the path for a successfully opened HID + * results in redundancy across the places which open devices. + */ + + /* Check for "." specs. */ + vid = pid = 0; + s = path; + ret = sr_atoul_base(s, &num, &endp, 16); + if (ret == SR_OK && endp && endp == s + 4 && *endp == '.' && num) { + vid = num; + s = ++endp; + } + ret = sr_atoul_base(s, &num, &endp, 16); + if (ret == SR_OK && endp && endp == s + 4 && *endp == '\0' && num) { + pid = num; + s = ++endp; + } + if (vid && pid) { + snprintf(vid_pid, sizeof(vid_pid), "%04x.%04x", vid, pid); + path = vid_pid; + sr_dbg("Using VID.PID %s.", path); + } + + /* Open the device, get vendor and product strings. */ + if (vid && pid) + dev = hid_open(vid, pid, NULL); + else + dev = hid_open_path(path); + if (!dev) { + sr_err("Cannot open %s.", path); + return NULL; + } + ok = TRUE; + ret = hid_get_manufacturer_string(dev, vendor, ARRAY_SIZE(vendor)); + if (ret != 0) + ok = FALSE; + if (!wcslen(vendor)) + ok = FALSE; + ret = hid_get_product_string(dev, product, ARRAY_SIZE(product)); + if (ret != 0) + ok = FALSE; + if (!wcslen(product)) + ok = FALSE; + hid_close(dev); + if (!ok) + return NULL; + + return probe_device_common(path, vid, pid, NULL, vendor, product); +} + static GSList *scan(struct sr_dev_driver *di, GSList *options) { const char *conn; GSList *devices; struct drv_context *drvc; + char want_serno[SERNO_LENGTH + 1]; struct hid_device_info *devs, *curdev; - int ret; wchar_t *ws; char nonws[32]; - char *s, *endp; - unsigned long relay_count; struct sr_dev_inst *sdi; /* Get optional conn= spec when provided. */ @@ -134,12 +257,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) (void)sr_serial_extract_options(options, &conn, NULL); if (conn && !*conn) conn = NULL; - /* - * TODO Accept different types of conn= specs? Either paths that - * hidapi(3) can open. Or bus.addr specs that we can check for - * during USB enumeration. Derive want_path, want_bus, want_addr - * here from the optional conn= spec. - */ devices = NULL; drvc = di->context; @@ -151,23 +268,41 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) * and product _strings_ to actually identify the device. * * The USB serial number need not be present nor reliable. The - * HID report contains a five character string which may serve - * as an identification for boards (is said to differ between - * boards). The last byte encodes the current relays state. + * HID report content will carry the board's serial number. + * When users specify "sn=..." connection strings, then run a + * regular USB enumation, and filter the result set by serial + * numbers which only become available with HID reports. + * + * When other connection strings were specified, then have + * HIDAPI open _this_ device and skip the enumeration. Which + * allows users to specify paths that need not match the + * enumeration's details. */ + memset(want_serno, 0, sizeof(want_serno)); + if (conn && g_str_has_prefix(conn, "sn=")) { + conn += strlen("sn="); + snprintf(want_serno, sizeof(want_serno), "%s", conn); + conn = NULL; + } + if (conn) { + sr_info("Checking HID path %s.", conn); + sdi = probe_device_conn(conn); + if (!sdi) + sr_warn("Failed to communicate to %s.", conn); + else + devices = g_slist_append(devices, sdi); + } devs = hid_enumerate(VENDOR_ID, PRODUCT_ID); for (curdev = devs; curdev; curdev = curdev->next) { + if (conn) + break; if (!curdev->vendor_id || !curdev->product_id) continue; if (!curdev->manufacturer_string || !curdev->product_string) continue; if (!*curdev->manufacturer_string || !*curdev->product_string) continue; - if (conn && strcmp(curdev->path, conn) != 0) { - sr_dbg("skipping %s, conn= mismatch", curdev->path); - continue; - } - sr_dbg("checking %04hx:%04hx, vendor %ls, product %ls.", + sr_dbg("Checking %04hx:%04hx, vendor %ls, product %ls.", curdev->vendor_id, curdev->product_id, curdev->manufacturer_string, curdev->product_string); @@ -182,30 +317,14 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) if (!ws || !wcslen(ws)) continue; snprintf(nonws, sizeof(nonws), "%ls", ws); - s = nonws; - if (!g_str_has_prefix(s, PRODUCT_STRING_PREFIX)) - continue; - s += strlen(PRODUCT_STRING_PREFIX); - ret = sr_atoul_base(s, &relay_count, &endp, 10); - if (ret != SR_OK || !endp || *endp) + if (!g_str_has_prefix(nonws, PRODUCT_STRING_PREFIX)) continue; - sr_info("Found: HID path %s, relay count %lu.", - curdev->path, relay_count); /* Identify device by communicating to it. */ - sdi = probe_device(curdev->path, relay_count); - if (!sdi) { - sr_warn("Failed to communicate to %s.", curdev->path); + sr_info("Checking HID path %s.", curdev->path); + sdi = probe_device_enum(curdev, want_serno); + if (!sdi) continue; - } - - /* Amend driver instance from USB enumeration details. */ - sdi->vendor = g_strdup_printf("%ls", curdev->manufacturer_string); - sdi->model = g_strdup_printf("%ls", curdev->product_string); - sdi->conn = g_strdup(curdev->path); - sdi->driver = &dcttech_usbrelay_driver_info; - sdi->inst_type = SR_INST_USB; - devices = g_slist_append(devices, sdi); } hid_free_enumeration(devs); @@ -224,7 +343,10 @@ static int dev_open(struct sr_dev_inst *sdi) devc->hid_dev = NULL; } - devc->hid_dev = hid_open_path(devc->hid_path); + if (devc->usb_vid && devc->usb_pid) + devc->hid_dev = hid_open(devc->usb_vid, devc->usb_pid, NULL); + else + devc->hid_dev = hid_open_path(devc->hid_path); if (!devc->hid_dev) return SR_ERR_IO; @@ -256,9 +378,9 @@ static int config_get(uint32_t key, GVariant **data, if (!cg) { switch (key) { case SR_CONF_CONN: - if (!sdi->conn) + if (!sdi->connection_id) return SR_ERR_NA; - *data = g_variant_new_string(sdi->conn); + *data = g_variant_new_string(sdi->connection_id); return SR_OK; default: return SR_ERR_NA;