+static struct sr_dev_inst *probe_device_common(const char *path,
+ uint16_t vid, uint16_t pid,
+ 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];
+ uint8_t curr_state;
+ uint8_t report[1 + REPORT_BYTECOUNT];
+ GString *txt;
+ size_t snr_pos;
+ char c;
+ struct sr_dev_inst *sdi;
+ struct dev_context *devc;
+ struct channel_group_context *cgc;
+ size_t idx, nr;
+ struct sr_channel_group *cg;
+
+ /*
+ * 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. */
+ 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);
+ memset(&report, 0, sizeof(report));
+ report[0] = REPORT_NUMBER;
+ ret = hid_get_feature_report(hid, report, sizeof(report));
+ hid_close(hid);
+ if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+ txt = sr_hexdump_new(report, sizeof(report));
+ sr_spew("Got report bytes: %s, rc %d.", txt->str, ret);
+ sr_hexdump_free(txt);
+ }
+ 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
+ * is for information only, gets re-retrieved before configure
+ * API calls (get/set).
+ */
+ memset(serno, 0, sizeof(serno));
+ for (snr_pos = 0; snr_pos < SERNO_LENGTH; snr_pos++) {
+ c = report[1 + snr_pos];
+ serno[snr_pos] = c;
+ if (c < 0x20 || c > 0x7e) {
+ sr_warn("Skipping %s, non-printable serial.", path);
+ return NULL;
+ }
+ }
+ curr_state = report[1 + STATE_INDEX];
+ sr_info("HID report data: serial number %s, relay state 0x%02x.",
+ serno, curr_state);
+
+ /* 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);
+ cgc = g_malloc0(sizeof(*cgc));
+ cg->priv = cgc;
+ cgc->number = nr;
+ sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+ }
+
+ return sdi;
+}
+
+static struct sr_dev_inst *probe_device_enum(struct hid_device_info *dev)
+{
+ return probe_device_common(dev->path, 0, 0,
+ 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 "<vid>.<pid>" 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, vendor, product);
+}
+