usb: implement attach kernel driver feature

FreeBSD's USB framework supports detaching kernel drivers to allow
user space applications to exclusively claim USB interfaces. However,
it lacked support for reattaching the kernel driver afterward.

This commit adds the missing functionality, enabling user space
to return control of the device back to the kernel.

Reviewed by:    lwhsu
Sponsored by:   The FreeBSD Foundation
Differential Revision: https://reviews.freebsd.org/D52122
This commit is contained in:
ShengYi Hung
2025-08-22 10:24:19 -04:00
parent f056e84e5b
commit ff46acfd52
15 changed files with 116 additions and 7 deletions
+1
View File
@@ -225,6 +225,7 @@ MLINKS += libusb20.3 libusb20_dev_get_desc.3
MLINKS += libusb20.3 libusb20_dev_get_stats.3
MLINKS += libusb20.3 libusb20_dev_close.3
MLINKS += libusb20.3 libusb20_dev_detach_kernel_driver.3
MLINKS += libusb20.3 libusb20_dev_attach_kernel_driver.3
MLINKS += libusb20.3 libusb20_dev_set_config_index.3
MLINKS += libusb20.3 libusb20_dev_get_debug.3
MLINKS += libusb20.3 libusb20_dev_get_fd.3
+5 -2
View File
@@ -95,8 +95,10 @@ supports hotplug notifications.
can access HID devices without requiring user intervention.
.It Va LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER
.Nm
supports detaching of the default USB driver with
.Fn libusb_detach_kernel_driver .
supports detaching and attaching of the default USB driver with
.Fn libusb_detach_kernel_driver
and
.Fn libusb_attach_kernel_driver .
.El
.Pp
.Ft const char *
@@ -869,6 +871,7 @@ The library is also compliant with LibUSB version 0.1.12.
.Fn usb_check_connected
.Fn usb_get_driver_np
.Fn usb_detach_kernel_driver_np
.Fn usb_attach_kernel_driver_np
.Sh SEE ALSO
.Xr libusb20 3 ,
.Xr usb 4 ,
+2 -2
View File
@@ -209,8 +209,8 @@ enum libusb_capability {
LIBUSB_CAP_HAS_HID_ACCESS,
/*
* Supports detaching of the default USB driver with
* libusb_detach_kernel_driver().
* Supports detaching and attaching of the default USB driver with
* libusb_detach_kernel_driver() and libusb_attach_kernel_driver().
*/
LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER,
};
+18
View File
@@ -1023,3 +1023,21 @@ usb_detach_kernel_driver_np(usb_dev_handle * dev, int interface)
return (0);
}
int
usb_attach_kernel_driver_np(usb_dev_handle *dev, int interface)
{
struct libusb20_device *pdev;
int err;
pdev = (void *)dev;
if (pdev == NULL)
return (-1);
err = libusb20_dev_attach_kernel_driver(pdev, interface);
if (err != 0)
return (-1);
return (0);
}
+6 -2
View File
@@ -1052,10 +1052,14 @@ libusb_detach_kernel_driver(struct libusb20_device *pdev, int interface)
int
libusb_attach_kernel_driver(struct libusb20_device *pdev, int interface)
{
int err;
if (pdev == NULL)
return (LIBUSB_ERROR_INVALID_PARAM);
/* stub - currently not supported by libusb20 */
return (0);
err = libusb20_dev_attach_kernel_driver(pdev, interface);
return (err ? LIBUSB_ERROR_OTHER : 0);
}
int
+11
View File
@@ -126,6 +126,8 @@ USB access library (libusb -lusb)
.Ft int
.Fn libusb20_dev_detach_kernel_driver "struct libusb20_device *pdev" "uint8_t iface_index"
.Ft int
.Fn libusb20_dev_attach_kernel_driver "struct libusb20_device *pdev" "uint8_t iface_index"
.Ft int
.Fn libusb20_dev_set_config_index "struct libusb20_device *pdev" "uint8_t configIndex"
.Ft int
.Fn libusb20_dev_get_debug "struct libusb20_device *pdev"
@@ -623,6 +625,15 @@ returned.
.
.Pp
.
.Fn libusb20_dev_attach_kernel_driver
will try to attach the kernel driver for the USB interface given by
.Fa iface_index .
.
This function returns zero on success else a LIBUSB20_ERROR value is
returned.
.
.Pp
.
.Fn libusb20_dev_set_config_index
will try to set the configuration index on an USB
device.
+11
View File
@@ -80,6 +80,7 @@ dummy_callback(struct libusb20_transfer *xfer)
#define dummy_get_stats (void *)dummy_int
#define dummy_kernel_driver_active (void *)dummy_int
#define dummy_detach_kernel_driver (void *)dummy_int
#define dummy_attach_kernel_driver (void *)dummy_int
#define dummy_do_request_sync (void *)dummy_int
#define dummy_tr_open (void *)dummy_int
#define dummy_tr_close (void *)dummy_int
@@ -636,6 +637,16 @@ libusb20_dev_detach_kernel_driver(struct libusb20_device *pdev, uint8_t ifaceInd
return (error);
}
int
libusb20_dev_attach_kernel_driver(struct libusb20_device *pdev,
uint8_t ifaceIndex)
{
int error;
error = pdev->methods->attach_kernel_driver(pdev, ifaceIndex);
return (error);
}
struct LIBUSB20_DEVICE_DESC_DECODED *
libusb20_dev_get_device_desc(struct libusb20_device *pdev)
{
+2
View File
@@ -246,6 +246,8 @@ const char *libusb20_dev_get_backend_name(struct libusb20_device *pdev);
const char *libusb20_dev_get_desc(struct libusb20_device *pdev);
int libusb20_dev_close(struct libusb20_device *pdev);
int libusb20_dev_detach_kernel_driver(struct libusb20_device *pdev, uint8_t iface_index);
int libusb20_dev_attach_kernel_driver(struct libusb20_device *pdev,
uint8_t iface_index);
int libusb20_dev_set_config_index(struct libusb20_device *pdev, uint8_t configIndex);
int libusb20_dev_get_debug(struct libusb20_device *pdev);
int libusb20_dev_get_fd(struct libusb20_device *pdev);
+2
View File
@@ -92,6 +92,7 @@ typedef void (libusb20_dummy_void_t)(void);
/* USB device specific */
typedef int (libusb20_detach_kernel_driver_t)(struct libusb20_device *pdev, uint8_t iface_index);
typedef int (libusb20_attach_kernel_driver_t)(struct libusb20_device *pdev, uint8_t iface_index);
typedef int (libusb20_do_request_sync_t)(struct libusb20_device *pdev, struct LIBUSB20_CONTROL_SETUP_DECODED *setup, void *data, uint16_t *pactlen, uint32_t timeout, uint8_t flags);
typedef int (libusb20_get_config_desc_full_t)(struct libusb20_device *pdev, uint8_t **ppbuf, uint16_t *plen, uint8_t index);
typedef int (libusb20_get_config_index_t)(struct libusb20_device *pdev, uint8_t *pindex);
@@ -116,6 +117,7 @@ typedef void (libusb20_tr_cancel_async_t)(struct libusb20_transfer *xfer);
#define LIBUSB20_DEVICE(m,n) \
m(n, detach_kernel_driver) \
m(n, attach_kernel_driver) \
m(n, do_request_sync) \
m(n, get_config_desc_full) \
m(n, get_config_index) \
+12
View File
@@ -82,6 +82,7 @@ static libusb20_get_power_usage_t ugen20_get_power_usage;
static libusb20_get_stats_t ugen20_get_stats;
static libusb20_kernel_driver_active_t ugen20_kernel_driver_active;
static libusb20_detach_kernel_driver_t ugen20_detach_kernel_driver;
static libusb20_attach_kernel_driver_t ugen20_attach_kernel_driver;
static libusb20_do_request_sync_t ugen20_do_request_sync;
static libusb20_process_t ugen20_process;
@@ -716,6 +717,17 @@ ugen20_detach_kernel_driver(struct libusb20_device *pdev,
return (0); /* kernel driver is detached */
}
static int
ugen20_attach_kernel_driver(struct libusb20_device *pdev, uint8_t iface_index)
{
int temp = iface_index;
if (ioctl(pdev->file_ctrl, IOUSB(USB_IFACE_DRIVER_ATTACH), &temp)) {
return (LIBUSB20_ERROR_OTHER);
}
return (0); /* kernel driver is attached */
}
static int
ugen20_do_request_sync(struct libusb20_device *pdev,
struct LIBUSB20_CONTROL_SETUP_DECODED *setup,
+1
View File
@@ -303,6 +303,7 @@ struct usb_device *usb_device(usb_dev_handle * dev);
struct usb_bus *usb_get_busses(void);
int usb_get_driver_np(usb_dev_handle * dev, int interface, char *name, int namelen);
int usb_detach_kernel_driver_np(usb_dev_handle * dev, int interface);
int usb_attach_kernel_driver_np(usb_dev_handle *dev, int interface);
#if 0
{ /* style */
+22
View File
@@ -2376,6 +2376,28 @@ ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
*/
usbd_set_parent_iface(f->udev, n, n);
break;
case USB_IFACE_DRIVER_ATTACH:
error = priv_check(curthread, PRIV_DRIVER);
if (error)
break;
n = *u.pint & 0xFF;
if (n == USB_IFACE_INDEX_ANY) {
error = EINVAL;
break;
}
/*
* Attach the currently detached driver.
*/
usbd_set_parent_iface(f->udev, n, USB_IFACE_INDEX_ANY);
usb_probe_and_attach(f->udev, n);
break;
case USB_SET_POWER_MODE:
error = ugen_set_power_mode(f, *u.pint);
+2 -1
View File
@@ -273,7 +273,8 @@ struct usb_gen_quirk {
#define USB_IFACE_DRIVER_DETACH _IOW ('U', 125, int)
#define USB_GET_PLUGTIME _IOR ('U', 126, uint32_t)
#define USB_READ_DIR _IOW ('U', 127, struct usb_read_dir)
/* 128 - 133 unused */
#define USB_IFACE_DRIVER_ATTACH _IOW('U', 128, int)
/* 129 - 133 unused */
#define USB_GET_DEV_PORT_PATH _IOR ('U', 134, struct usb_device_port_path)
#define USB_GET_POWER_USAGE _IOR ('U', 135, int)
#define USB_SET_TX_FORCE_SHORT _IOW ('U', 136, int)
+5
View File
@@ -159,6 +159,11 @@ Detach kernel driver for the selected interface and USB device.
This command uses the
.Fl i Ar interface_index
option.
.It Cm attach_kernel_driver
Attach kernel driver for the selected interface and USB device.
This command uses the
.Fl i Ar interface_index
option.
.It Cm suspend
Force the device to suspend.
.It Cm resume
+16
View File
@@ -93,6 +93,7 @@ struct options {
uint8_t got_do_request:1;
uint8_t got_detach_kernel_driver:1;
uint8_t opt_dump_in_list_mode:1;
uint8_t got_attach_kernel_driver:1;
};
struct token {
@@ -129,6 +130,7 @@ enum {
T_RESET,
T_LIST,
T_DO_REQUEST,
T_ATTACH_KERNEL_DRIVER,
};
static struct options options;
@@ -143,6 +145,7 @@ static const struct token token[] = {
{"add_quirk", T_ADD_QUIRK, 1},
{"remove_quirk", T_REMOVE_QUIRK, 1},
{"detach_kernel_driver", T_DETACH_KERNEL_DRIVER, 0},
{"attach_kernel_driver", T_ATTACH_KERNEL_DRIVER, 0},
{"dump_quirk_names", T_DUMP_QUIRK_NAMES, 0},
{"dump_device_quirks", T_DUMP_DEVICE_QUIRKS, 0},
{"dump_all_desc", T_DUMP_ALL_DESC, 0},
@@ -283,6 +286,7 @@ usage(int exitcode)
" add_quirk <quirk>" "\n"
" remove_quirk <quirk>" "\n"
" detach_kernel_driver" "\n"
" attach_kernel_driver" "\n"
" dump_quirk_names" "\n"
" dump_device_quirks" "\n"
" dump_all_desc" "\n"
@@ -496,6 +500,11 @@ flush_command(struct libusb20_backend *pbe, struct options *opt)
err(1, "could not detach kernel driver");
}
}
if (opt->got_attach_kernel_driver) {
if (libusb20_dev_attach_kernel_driver(pdev, opt->iface)) {
err(1, "could not attach kernel driver");
}
}
dump_any =
(opt->got_dump_all_desc ||
opt->got_dump_device_desc ||
@@ -688,6 +697,13 @@ main(int argc, char **argv)
opt->got_any++;
break;
case T_ATTACH_KERNEL_DRIVER:
if (opt->got_attach_kernel_driver)
duplicate_option(argv[n]);
opt->got_attach_kernel_driver = 1;
opt->got_any++;
break;
case T_DUMP_QUIRK_NAMES:
if (opt->got_dump_quirk_names)
duplicate_option(argv[n]);