diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/usb/osmo_libusb.c | 378 | 
1 files changed, 377 insertions, 1 deletions
| diff --git a/src/usb/osmo_libusb.c b/src/usb/osmo_libusb.c index fda95739..5b012b86 100644 --- a/src/usb/osmo_libusb.c +++ b/src/usb/osmo_libusb.c @@ -1,6 +1,6 @@  /* libosmocore integration with libusb-1.0   * - * (C) 2019 by Harald Welte <laforge@gnumonks.org> + * (C) 2019-2019 by Harald Welte <laforge@gnumonks.org>   * All Rights Reserved.   *   * SPDX-License-Identifier: GPL-2.0+ @@ -33,6 +33,8 @@  #include <libusb.h> +#include <osmocom/usb/libusb.h> +  /***********************************************************************   * logging integration   ***********************************************************************/ @@ -104,7 +106,381 @@ static void osmo_usb_removed_cb(int fd, void *user_data)  	talloc_free(ofd);  } +/*********************************************************************** + * utility functions + ***********************************************************************/ + +/*! obtain the string representation of the USB device path of given device. + *  \param[out] buf Output string buffer + *  \param[in] bufsize Size of output string buffer in bytes + *  \param[in] dev USB device whose bus path we want to obtain + *  \returns pointer to 'buf' in case of success; NULL in case of error */ +char *osmo_libusb_dev_get_path_buf(char *buf, size_t bufsize, libusb_device *dev) +{ +#if (defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000102) || \ +    (defined(LIBUSBX_API_VERSION) && LIBUSBX_API_VERSION >= 0x01000102) +	struct osmo_strbuf sb = { .buf = buf, .len = bufsize }; +	uint8_t path[8]; +	int r,j; +	r = libusb_get_port_numbers(dev, path, sizeof(path)); +	if (r > 0) { +		OSMO_STRBUF_PRINTF(sb, "%d-%d", libusb_get_bus_number(dev), path[0]); +		for (j = 1; j < r; j++){ +			OSMO_STRBUF_PRINTF(sb, ".%d", path[j]); +		} +	} +	return buf; +#else +# warning "libusb too old - building without USB path support!" +	return NULL; +#endif +} + +/*! obtain the string representation of the USB device path of given device. + *  \param[in] talloc context from which to dynamically allocate output string buffer + *  \param[in] dev USB device whose bus path we want to obtain + *  \returns pointer to 'buf' in case of success; NULL in case of error */ +char *osmo_libusb_dev_get_path_c(void *ctx, libusb_device *dev) +{ +	char *buf = talloc_zero_size(ctx, USB_MAX_PATH_LEN); +	if (!buf) +		return NULL; +	return osmo_libusb_dev_get_path_buf(buf, USB_MAX_PATH_LEN, dev); +} + +static int match_dev_id(const struct libusb_device_descriptor *desc, const struct dev_id *id) +{ +	if ((desc->idVendor == id->vendor_id) && (desc->idProduct == id->product_id)) +		return 1; +	return 0; +} + +static int match_dev_ids(const struct libusb_device_descriptor *desc, const struct dev_id *ids) +{ +	const struct dev_id *id; + +	for (id = ids; id->vendor_id || id->product_id; id++) { +		if (match_dev_id(desc, id)) +			return 1; +	} +	return 0; +} + +/*! Find USB devices matching the specified list of USB VendorID/ProductIDs + *  \param[in] ctx talloc context from which to allocate output data + *  \param[in] luctx libusb context on which to operate + *  \param[in] dev_ids zero-terminated array of VendorId/ProductId tuples + *  \returns array of up to 256 libusb_device pointers; NULL in case of error */ +libusb_device **osmo_libusb_find_matching_usb_devs(void *ctx, struct libusb_context *luctx, +						   const struct dev_id *dev_ids) +{ +	libusb_device **list; +	libusb_device **out = talloc_zero_array(ctx, libusb_device *, 256); +	libusb_device **cur = out; +	unsigned int i; +	int rc; + +	if (!out) +		return NULL; + +	rc = libusb_get_device_list(luctx, &list); +	if (rc <= 0) { +		perror("No USB devices found"); +		talloc_free(out); +		return NULL; +	} + +	for (i = 0; list[i] != NULL; i++) { +		struct libusb_device_descriptor dev_desc; +		libusb_device *dev = list[i]; + +		rc = libusb_get_device_descriptor(dev, &dev_desc); +		if (rc < 0) { +			perror("Couldn't get device descriptor\n"); +			libusb_unref_device(dev); +			continue; +		} + +		if (match_dev_ids(&dev_desc, dev_ids)) { +			*cur = dev; +			cur++; +			/* overflow check */ +			if (cur >= out + 256) +				break; +		} else +			libusb_unref_device(dev); +	} +	if (cur == out) { +		libusb_free_device_list(list, 1); +		talloc_free(out); +		return NULL; +	} + +	libusb_free_device_list(list, 0); +	return out; +} + +/*! find a matching interface among all interfaces of the given USB device. + *  \param[in] dev USB device in which we shall search + *  \param[in] class USB Interface Class to look for + *  \param[in] sub_class USB Interface Subclass to look for + *  \param[in] protocol USB Interface Protocol to look for + *  \param[out] out User-allocated array for storing matches + *  \param[in] out_len Length of out array + *  \returns number of matching interfaces; negative in case of error */ +int osmo_libusb_dev_find_matching_interfaces(libusb_device *dev, int class, int sub_class, +					     int protocol, struct usb_interface_match *out, +					     unsigned int out_len) +{ +	struct libusb_device_descriptor dev_desc; +	int rc, i, out_idx = 0; +	uint8_t addr; +	char pathbuf[USB_MAX_PATH_LEN]; +	char *path; + +	rc = libusb_get_device_descriptor(dev, &dev_desc); +	if (rc < 0) { +		perror("Couldn't get device descriptor\n"); +		return -EIO; +	} + +	addr = libusb_get_device_address(dev); +	path = osmo_libusb_dev_get_path_buf(pathbuf, sizeof(pathbuf), dev); +	/* iterate over all configurations */ +	for (i = 0; i < dev_desc.bNumConfigurations; i++) { +		struct libusb_config_descriptor *conf_desc; +		int j; + +		rc = libusb_get_config_descriptor(dev, i, &conf_desc); +		if (rc < 0) { +			fprintf(stderr, "Couldn't get config descriptor %u\n", i); +			continue; +		} +		/* iterate over all interfaces */ +		for (j = 0; j < conf_desc->bNumInterfaces; j++) { +			const struct libusb_interface *intf = &conf_desc->interface[j]; +			int k; +			/* iterate over all alternate settings */ +			for (k = 0; k < intf->num_altsetting; k++) { +				const struct libusb_interface_descriptor *if_desc; +				if_desc = &intf->altsetting[k]; +				if (class >= 0 && if_desc->bInterfaceClass != class) +					continue; +				if (sub_class >= 0 && if_desc->bInterfaceSubClass != sub_class) +					continue; +				if (protocol >= 0 && if_desc->bInterfaceProtocol != protocol) +					continue; +				/* MATCH! */ +				out[out_idx].usb_dev = dev; +				out[out_idx].vendor = dev_desc.idVendor; +				out[out_idx].product = dev_desc.idProduct; +				out[out_idx].addr = addr; +				strncpy(out[out_idx].path, path, sizeof(out[out_idx].path)-1); +				out[out_idx].path[sizeof(out[out_idx].path)-1] = '\0'; +				out[out_idx].configuration = conf_desc->bConfigurationValue; +				out[out_idx].interface = if_desc->bInterfaceNumber; +				out[out_idx].altsetting = if_desc->bAlternateSetting; +				out[out_idx].class = if_desc->bInterfaceClass; +				out[out_idx].sub_class = if_desc->bInterfaceSubClass; +				out[out_idx].protocol = if_desc->bInterfaceProtocol; +				out[out_idx].string_idx = if_desc->iInterface; +				out_idx++; +				if (out_idx >= out_len) +					return out_idx; +			} +		} +	} +	return out_idx; +} + +/*! find matching interfaces among a list devices of specified VendorId/ProductID tuples. + *  \param[in] luctx libusb context on which to operate + *  \param[in] dev_ids zero-terminated array of VendorId/ProductId tuples + *  \param[in] class USB Interface Class to look for + *  \param[in] sub_class USB Interface Subclass to look for + *  \param[in] protocol USB Interface Protocol to look for + *  \param[out] out User-allocated array for storing matches + *  \param[in] out_len Length of out array + *  \returns number of matching interfaces; negative in case of error */ +int osmo_libusb_find_matching_interfaces(libusb_context *luctx, const struct dev_id *dev_ids, +					 int class, int sub_class, int protocol, +					 struct usb_interface_match *out, unsigned int out_len) +{ +	struct usb_interface_match *out_cur = out; +	unsigned int out_len_remain = out_len; +	libusb_device **list; +	libusb_device **dev; + +	list = osmo_libusb_find_matching_usb_devs(NULL, luctx, dev_ids); +	if (!list) +		return 0; + +	for (dev = list; *dev; dev++) { +		int rc; + +#if 0 +		struct libusb_device_descriptor dev_desc; +		uint8_t ports[8]; +		uint8_t addr; +		rc = libusb_get_device_descriptor(*dev, &dev_desc); +		if (rc < 0) { +			perror("Cannot get device descriptor"); +			continue; +		} + +		addr = libusb_get_device_address(*dev); + +		rc = libusb_get_port_numbers(*dev, ports, sizeof(ports)); +		if (rc < 0) { +			perror("Cannot get device path"); +			continue; +		} + +		printf("Found USB Device %04x:%04x at address %d\n", +			dev_desc.idVendor, dev_desc.idProduct, addr); +#endif + +		rc = osmo_libusb_dev_find_matching_interfaces(*dev, class, sub_class, +							      protocol, out_cur, out_len_remain); +		if (rc < 0) +			continue; +		out_cur += rc; +		out_len_remain -= rc; + +	} + +	/* unref / free list */ +	for (dev = list; *dev; dev++) +		libusb_unref_device(*dev); +	talloc_free(list); + +	return out_len - out_len_remain; +} + +/*! open matching USB device and claim interface + *  \param[in] ctx talloc context to use for related allocations + *  \param[in] luctx libusb context on which to operate + *  \param[in] ifm interface match describing interface to claim + *  \returns libusb device chandle on success; NULL on error */ +libusb_device_handle *osmo_libusb_open_claim_interface(void *ctx, libusb_context *luctx, +							const struct usb_interface_match *ifm) +{ +	int rc, config; +	struct dev_id dev_ids[] = { { ifm->vendor, ifm->product }, { 0, 0 } }; +	libusb_device **list; +	libusb_device **dev; +	libusb_device_handle *usb_devh = NULL; + +	list = osmo_libusb_find_matching_usb_devs(ctx, luctx, dev_ids); +	if (!list) { +		perror("No USB device with matching VID/PID"); +		return NULL; +	} + +	for (dev = list; *dev; dev++) { +		int addr; +		char pathbuf[USB_MAX_PATH_LEN]; +		char *path; + +		addr = libusb_get_device_address(*dev); +		path = osmo_libusb_dev_get_path_buf(pathbuf, sizeof(pathbuf), *dev); +		if ((ifm->addr && addr == ifm->addr) || +		    (strlen(ifm->path) && !strcmp(path, ifm->path))) { +			rc = libusb_open(*dev, &usb_devh); +			if (rc < 0) { +				fprintf(stderr, "Cannot open device: %s\n", libusb_error_name(rc)); +				usb_devh = NULL; +				break; +			} +			rc = libusb_get_configuration(usb_devh, &config); +			if (rc < 0) { +				fprintf(stderr, "Cannot get current configuration: %s\n", libusb_error_name(rc)); +				libusb_close(usb_devh); +				usb_devh = NULL; +				break; +			} +			if (config != ifm->configuration) { +				rc = libusb_set_configuration(usb_devh, ifm->configuration); +				if (rc < 0) { +					fprintf(stderr, "Cannot set configuration: %s\n", libusb_error_name(rc)); +					libusb_close(usb_devh); +					usb_devh = NULL; +					break; +				} +			} +			rc = libusb_claim_interface(usb_devh, ifm->interface); +			if (rc < 0) { +				fprintf(stderr, "Cannot claim interface: %s\n", libusb_error_name(rc)); +				libusb_close(usb_devh); +				usb_devh = NULL; +				break; +			} +			rc = libusb_set_interface_alt_setting(usb_devh, ifm->interface, ifm->altsetting); +			if (rc < 0) { +				fprintf(stderr, "Cannot set interface altsetting: %s\n", libusb_error_name(rc)); +				libusb_release_interface(usb_devh, ifm->interface); +				libusb_close(usb_devh); +				usb_devh = NULL; +				break; +			} +		} +	} + +	/* unref / free list */ +	for (dev = list; *dev; dev++) +		libusb_unref_device(*dev); +	talloc_free(list); + +	return usb_devh; +} + +/*! obtain the endpoint addresses for a given USB interface. + *  \param[in] devh USB device handle on which to operate + *  \param[in] if_num USB Interface number on which to operate + *  \param[out] out user-provided storage for OUT endpoint number + *  \param[out] in user-provided storage for IN endpoint number + *  \param[out] irq user-provided storage for IRQ endpoint number + *  \returns 0 in case of success; negative in case of error */ +int osmo_libusb_get_ep_addrs(libusb_device_handle *devh, unsigned int if_num, +			     uint8_t *out, uint8_t *in, uint8_t *irq) +{ +	libusb_device *dev = libusb_get_device(devh); +	struct libusb_config_descriptor *cdesc; +	const struct libusb_interface_descriptor *idesc; +	const struct libusb_interface *iface; +	int rc, l; + +	rc = libusb_get_active_config_descriptor(dev, &cdesc); +	if (rc < 0) +		return rc; + +	iface = &cdesc->interface[if_num]; +	/* FIXME: we assume there's no altsetting */ +	idesc = &iface->altsetting[0]; + +	for (l = 0; l < idesc->bNumEndpoints; l++) { +		const struct libusb_endpoint_descriptor *edesc = &idesc->endpoint[l]; +		switch (edesc->bmAttributes & 3) { +		case LIBUSB_TRANSFER_TYPE_BULK: +			if (edesc->bEndpointAddress & 0x80) { +				if (in) +					*in = edesc->bEndpointAddress; +			} else { +				if (out) +					*out = edesc->bEndpointAddress; +			} +			break; +		case LIBUSB_TRANSFER_TYPE_INTERRUPT: +			if (irq) +				*irq = edesc->bEndpointAddress; +			break; +		default: +			break; +		} +	} +	return 0; +}  /***********************************************************************   * initialization   ***********************************************************************/ | 
