blob: 10d55125bd074975eeead140418feb30f837dc23 [file] [log] [blame]
From 17d1ff57f1881d5b88a832d031813c23d7e39ef1 Mon Sep 17 00:00:00 2001
From: Dmitry Torokhov <dtor@chromium.org>
Date: Wed, 5 Jun 2019 10:40:56 -0700
Subject: [PATCH 08/11] CHROMIUM: linux_usbfs: wire up IOCTL_USBFS_CONNINFO_EX
This new ioctl provides important information, such as device's assigned
bus number, port connection information, and speed, when using usbfs
file descriptors. Such information is usually available via sysfs, but
when using file descriptors sysfs is often not accessible.
Signed-off-by: Dmitry Torokhov <dtor@chromium.org>
---
libusb/os/linux_usbfs.c | 50 +++++++++++++++++++++++++++++++++++++----
libusb/os/linux_usbfs.h | 29 ++++++++++++++++++++++++
2 files changed, 75 insertions(+), 4 deletions(-)
diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c
index 0ba38c3..a00d51a 100644
--- a/libusb/os/linux_usbfs.c
+++ b/libusb/os/linux_usbfs.c
@@ -1059,6 +1059,21 @@ static int device_speed_from_sysfs(struct libusb_context *ctx,
}
}
+static int device_speed_from_kernel(struct libusb_context *ctx, int speed)
+{
+ switch (speed) {
+ case KERNEL_SPEED_LOW: return LIBUSB_SPEED_LOW;
+ case KERNEL_SPEED_FULL: return LIBUSB_SPEED_FULL;
+ case KERNEL_SPEED_HIGH: return LIBUSB_SPEED_HIGH;
+ case KERNEL_SPEED_WIRELESS: return LIBUSB_SPEED_HIGH;
+ case KERNEL_SPEED_SUPER: return LIBUSB_SPEED_SUPER;
+ case KERNEL_SPEED_SUPER_PLUS: return LIBUSB_SPEED_SUPER_PLUS;
+ default:
+ usbi_warn(ctx, "Unknown device kernel speed: %d", speed);
+ return LIBUSB_SPEED_UNKNOWN;
+ }
+}
+
static int device_cache_active_config(struct libusb_device *dev, int wrapped_fd)
{
struct linux_device_priv *priv = _device_priv(dev);
@@ -1094,6 +1109,8 @@ static int initialize_from_usbfs(struct libusb_device *dev, int fd)
{
struct linux_device_priv *priv = _device_priv(dev);
struct libusb_context *ctx = DEVICE_CTX(dev);
+ struct usbfs_conninfo_ex ci = { 0 };
+ uint32_t caps;
int ret;
ret = lseek(fd, 0, SEEK_SET);
@@ -1102,7 +1119,24 @@ static int initialize_from_usbfs(struct libusb_device *dev, int fd)
return LIBUSB_ERROR_IO;
}
- return device_cache_descriptors(ctx, priv, fd);
+ ret = device_cache_descriptors(ctx, priv, fd);
+ if (ret != LIBUSB_SUCCESS)
+ return ret;
+
+ /* If FD does not allow write access this will fail even though
+ * we do not alter state of the device. That is why caller will
+ * try to open FD for writing first. */
+ if (ioctl(fd, IOCTL_USBFS_GET_CAPABILITIES, &caps) >= 0 &&
+ (caps & USBFS_CAP_CONNINFO_EX) &&
+ ioctl(fd, IOCTL_USBFS_CONNINFO_EX(sizeof(ci)), &ci) >= 0 &&
+ /* Kernel should support at least as much as we want */
+ ci.size >= sizeof(ci)) {
+ dev->bus_number = ci.busnum;
+ dev->device_address = ci.devnum;
+ dev->speed = device_speed_from_kernel(ctx, ci.speed);
+ }
+
+ return LIBUSB_SUCCESS;
}
static int initialize_device(struct libusb_device *dev, uint8_t busnum,
@@ -1139,9 +1173,17 @@ static int initialize_device(struct libusb_device *dev, uint8_t busnum,
}
if (!priv->descriptors) {
- usbfs_fd = wrapped_fd >= 0 ?
- dup(wrapped_fd) :
- _get_usbfs_fd(dev, O_RDONLY, 0 /* silent */);
+ if (wrapped_fd >= 0)
+ usbfs_fd = dup(wrapped_fd);
+ else {
+ /* Try opening for writing first to allow ioctls
+ * work. If this fails open for reading only so
+ * we at least can fetch descriptors. */
+ usbfs_fd = _get_usbfs_fd(dev, O_RDWR, 1 /* silent */);
+ if (usbfs_fd < 0)
+ usbfs_fd = _get_usbfs_fd(dev, O_RDONLY,
+ 0 /* silent */);
+ }
if (usbfs_fd < 0)
return usbfs_fd;
diff --git a/libusb/os/linux_usbfs.h b/libusb/os/linux_usbfs.h
index 2449632..227c4c7 100644
--- a/libusb/os/linux_usbfs.h
+++ b/libusb/os/linux_usbfs.h
@@ -110,6 +110,33 @@ struct usbfs_connectinfo {
unsigned char slow;
};
+#define KERNEL_SPEED_LOW 1
+#define KERNEL_SPEED_FULL 2
+#define KERNEL_SPEED_HIGH 3
+#define KERNEL_SPEED_WIRELESS 4
+#define KERNEL_SPEED_SUPER 5
+#define KERNEL_SPEED_SUPER_PLUS 6
+
+struct usbfs_conninfo_ex {
+ uint32_t size; /* Size of the structure from the kernel's */
+ /* point of view. Can be used by userspace */
+ /* to determine how much data can be */
+ /* used/trusted. */
+ uint32_t busnum; /* USB bus number, as enumerated by the */
+ /* kernel, the device is connected to. */
+ uint32_t devnum; /* Device address on the bus. */
+ uint32_t speed; /* KERNEL_SPEED_* */
+ uint8_t num_ports; /* Number of ports the device is connected */
+ /* to on the way to the root hub. It may */
+ /* be bigger than size of 'ports' array so */
+ /* userspace can detect overflows. */
+ uint8_t ports[7]; /* List of ports on the way from the root */
+ /* hub to the device. Current limit in */
+ /* USB specification is 7 tiers (root hub, */
+ /* 5 intermediate hubs, device), which */
+ /* gives at most 6 port entries. */
+};
+
struct usbfs_ioctl {
int ifno; /* interface 0..N ; negative numbers reserved */
int ioctl_code; /* MUST encode size + direction of data so the
@@ -127,6 +154,7 @@ struct usbfs_hub_portinfo {
#define USBFS_CAP_NO_PACKET_SIZE_LIM 0x04
#define USBFS_CAP_BULK_SCATTER_GATHER 0x08
#define USBFS_CAP_REAP_AFTER_DISCONNECT 0x10
+#define USBFS_CAP_CONNINFO_EX 0x80
#define USBFS_DISCONNECT_CLAIM_IF_DRIVER 0x01
#define USBFS_DISCONNECT_CLAIM_EXCEPT_DRIVER 0x02
@@ -168,6 +196,7 @@ struct usbfs_streams {
#define IOCTL_USBFS_DISCONNECT_CLAIM _IOR('U', 27, struct usbfs_disconnect_claim)
#define IOCTL_USBFS_ALLOC_STREAMS _IOR('U', 28, struct usbfs_streams)
#define IOCTL_USBFS_FREE_STREAMS _IOR('U', 29, struct usbfs_streams)
+#define IOCTL_USBFS_CONNINFO_EX(len) _IOC(_IOC_READ, 'U', 32, len)
extern usbi_mutex_static_t linux_hotplug_lock;
--
2.22.0.rc2.383.gf4fbbf30c2-goog