git: ae489991d1c0 - main - libusb: implement IAD parser

From: ShengYi Hung <aokblast_at_FreeBSD.org>
Date: Mon, 22 Jun 2026 13:36:58 UTC
The branch main has been updated by aokblast:

URL: https://cgit.FreeBSD.org/src/commit/?id=ae489991d1c0b550ead309abc1eade3b185e7499

commit ae489991d1c0b550ead309abc1eade3b185e7499
Author:     ShengYi Hung <aokblast@FreeBSD.org>
AuthorDate: 2025-06-11 17:24:04 +0000
Commit:     ShengYi Hung <aokblast@FreeBSD.org>
CommitDate: 2026-06-22 13:36:47 +0000

    libusb: implement IAD parser
    
    libusb provide functions to parse interface association descriptor. This
    descriptor indicates that a function is composed by multiple interface
    and which interfaces is associate to the target function. This
    descriptor is not a separate USB require, instead, it comes with the
    config descriptor.
    
    Reviewed by:    adrian
    Sponsored by:   The FreeBSD Foundation
    Differential Revision: https://reviews.freebsd.org/D50958
---
 lib/libusb/libusb.3        | 27 ++++++++++++++
 lib/libusb/libusb.h        | 21 +++++++++++
 lib/libusb/libusb10_desc.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/libusb/libusb20_desc.c | 30 +++++++++++++---
 lib/libusb/libusb20_desc.h | 23 ++++++++++++
 5 files changed, 185 insertions(+), 5 deletions(-)

diff --git a/lib/libusb/libusb.3 b/lib/libusb/libusb.3
index 90d9afdb5609..9b46470ed13d 100644
--- a/lib/libusb/libusb.3
+++ b/lib/libusb/libusb.3
@@ -561,6 +561,33 @@ libusb_free_platform_descriptor function.
 .Fn libusb_free_platform_descriptor "struct libusb_platform_descriptor *platform_descriptor"
 This function is NULL safe and frees a parsed platform descriptor given by
 .Fa platform_descriptor .
+.Pp
+.Ft int
+.Fn libusb_get_interface_association_descriptors "libusb_device *dev" "uint8_t config_index" "struct libusb_interface_association_descriptor_array **iad_arr"
+This function parses the interface association descriptor from the descriptor given by
+.Fa dev
+and
+.Fa config_index
+then stores a pointer to the parsed descriptor array into
+.Fa iad_arr .
+Return zero on success and a LIBUSB_ERROR code on failure.
+On success the parsed interface association descriptor must be freed using the
+libusb_free_interface_association_descriptors function.
+.Pp
+.Ft int
+.Fn libusb_get_active_interface_association_descriptors "libusb_device *dev" "struct libusb_interface_association_descriptor_array **iad_arr"
+This function parses the interface association descriptor from the descriptor given by
+.Fa dev
+and active config then stores a pointer to the parsed descriptor array into
+.Fa iad_arr .
+Return zero on success and a LIBUSB_ERROR code on failure.
+On success the parsed interface association descriptor must be freed using the
+libusb_free_interface_association_descriptors function.
+.Pp
+.Ft void
+.Fn libusb_free_interface_association_descriptors "struct libusb_interface_association_descriptor_array *iad_arr"
+This function is NULL safe and frees a parsed interface association descriptor array given by
+.Fa iad_arr .
 .Sh USB ASYNCHRONOUS I/O
 .Ft struct libusb_transfer *
 .Fn libusb_alloc_transfer "int iso_packets"
diff --git a/lib/libusb/libusb.h b/lib/libusb/libusb.h
index 55982be2ead3..5a33dd39d85e 100644
--- a/lib/libusb/libusb.h
+++ b/lib/libusb/libusb.h
@@ -82,6 +82,7 @@ enum libusb_descriptor_type {
 	LIBUSB_DT_STRING = 0x03,
 	LIBUSB_DT_INTERFACE = 0x04,
 	LIBUSB_DT_ENDPOINT = 0x05,
+	LIBUSB_DT_INTERFACE_ASSOCIATION = 0x0b,
 	LIBUSB_DT_HID = 0x21,
 	LIBUSB_DT_REPORT = 0x22,
 	LIBUSB_DT_PHYSICAL = 0x23,
@@ -106,6 +107,7 @@ enum libusb_device_capability_type {
 #define	LIBUSB_DT_HUB_NONVAR_SIZE	7
 #define	LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE	6
 #define	LIBUSB_DT_BOS_SIZE		5
+#define	LIBUSB_DT_INTERFACE_ASSOCIATION_SIZE		8
 #define	LIBUSB_USB_2_0_EXTENSION_DEVICE_CAPABILITY_SIZE	7
 #define	LIBUSB_SS_USB_DEVICE_CAPABILITY_SIZE	10
 
@@ -367,6 +369,22 @@ typedef struct libusb_ss_endpoint_companion_descriptor {
 	uint16_t wBytesPerInterval;
 }	libusb_ss_endpoint_companion_descriptor __aligned(sizeof(void *));
 
+typedef struct libusb_interface_association_descriptor {
+	uint8_t bLength;
+	uint8_t bDescriptorType;
+	uint8_t bFirstInterface;
+	uint8_t bInterfaceCount;
+	uint8_t bFunctionClass;
+	uint8_t bFunctionSubClass;
+	uint8_t bFunctionProtocol;
+	uint8_t iFunction;
+} libusb_interface_association_descriptor __aligned(sizeof(void *));
+
+typedef struct libusb_interface_association_descriptor_array {
+	const struct libusb_interface_association_descriptor *iad;
+	int length;
+} libusb_interface_association_descriptor_array;
+
 typedef struct libusb_interface_descriptor {
 	uint8_t	bLength;
 	uint8_t	bDescriptorType;
@@ -579,6 +597,9 @@ int	libusb_get_container_id_descriptor(struct libusb_context *ctx, struct libusb
 void	libusb_free_container_id_descriptor(struct libusb_container_id_descriptor *container_id);
 int	libusb_get_platform_descriptor(libusb_context *ctx, struct libusb_bos_dev_capability_descriptor *dev_cap, struct libusb_platform_descriptor **platform_descriptor);
 void	libusb_free_platform_descriptor(struct libusb_platform_descriptor *platform_descriptor);
+int	libusb_get_interface_association_descriptors(libusb_device *dev, uint8_t config_index, struct libusb_interface_association_descriptor_array **iad_arr);
+int	libusb_get_active_interface_association_descriptors(libusb_device *dev, struct libusb_interface_association_descriptor_array **iad_arr);
+void	libusb_free_interface_association_descriptors(struct libusb_interface_association_descriptor_array *iad_arr);
 
 /* Asynchronous device I/O */
 
diff --git a/lib/libusb/libusb10_desc.c b/lib/libusb/libusb10_desc.c
index 7da5c84f4ad2..592b0cb7a4cd 100644
--- a/lib/libusb/libusb10_desc.c
+++ b/lib/libusb/libusb10_desc.c
@@ -763,3 +763,92 @@ libusb_free_platform_descriptor(
 {
 	free(platform_descriptor);
 }
+
+int
+libusb_get_interface_association_descriptors(libusb_device *dev,
+    uint8_t config_index,
+    struct libusb_interface_association_descriptor_array **iad_arr)
+{
+	struct libusb_interface_association_descriptor_array *iads;
+	struct libusb_interface_association_descriptor *iad;
+	struct libusb20_config *config;
+	struct LIBUSB20_INTERFACE_ASSOCIATION_DESC_DECODED *iad_desc;
+
+	int idx;
+	int niad;
+
+	if (!iad_arr)
+		return (LIBUSB_ERROR_INVALID_PARAM);
+
+	if (config_index >= dev->os_priv->ddesc.bNumConfigurations)
+		return (LIBUSB_ERROR_NOT_FOUND);
+
+	config = libusb20_dev_alloc_config(dev->os_priv, config_index);
+	if (config == NULL)
+		return (LIBUSB_ERROR_NO_MEM);
+	niad = config->niad;
+	iads = calloc(1,
+	    sizeof(struct libusb_interface_association_descriptor_array));
+	if (iads == NULL) {
+		free(config);
+		return (LIBUSB_ERROR_NO_MEM);
+	}
+	*iad_arr = iads;
+	if (niad == 0) {
+		iads->iad = NULL;
+		iads->length = 0;
+		free(config);
+		return (LIBUSB_SUCCESS);
+	}
+
+	iad = malloc(
+	    sizeof(struct libusb_interface_association_descriptor) * niad);
+	if (iad == NULL) {
+		*iad_arr = NULL;
+		free(iads);
+		free(config);
+		return (LIBUSB_ERROR_NO_MEM);
+	}
+
+	for (idx = 0; idx < niad; ++idx) {
+		iad_desc = &config->iad_desc[idx].desc;
+		iad[idx].bLength = iad_desc->bLength;
+		iad[idx].bDescriptorType = iad_desc->bDescriptorType;
+		iad[idx].bFirstInterface = iad_desc->bFirstInterface;
+		iad[idx].bInterfaceCount = iad_desc->bInterfaceCount;
+		iad[idx].bFunctionClass = iad_desc->bFunctionClass;
+		iad[idx].bFunctionSubClass = iad_desc->bFunctionSubClass;
+		iad[idx].bFunctionProtocol = iad_desc->bFunctionProtocol;
+		iad[idx].iFunction = iad_desc->iFunction;
+	}
+	iads->length = niad;
+	iads->iad = iad;
+
+	free(config);
+	return (LIBUSB_SUCCESS);
+}
+
+int
+libusb_get_active_interface_association_descriptors(libusb_device *dev,
+    struct libusb_interface_association_descriptor_array **iad_arr)
+{
+	struct libusb20_device *pdev;
+	uint8_t config_index;
+
+	pdev = dev->os_priv;
+	config_index = libusb20_dev_get_config_index(pdev);
+
+	return (libusb_get_interface_association_descriptors(dev, config_index,
+	    iad_arr));
+}
+
+void
+libusb_free_interface_association_descriptors(
+    struct libusb_interface_association_descriptor_array *iad_arr)
+{
+	if (iad_arr == NULL)
+		return;
+	if (iad_arr->iad)
+		free((void *)iad_arr->iad);
+	free(iad_arr);
+}
diff --git a/lib/libusb/libusb20_desc.c b/lib/libusb/libusb20_desc.c
index 89c01d5afeff..a0e3561ebb1b 100644
--- a/lib/libusb/libusb20_desc.c
+++ b/lib/libusb/libusb20_desc.c
@@ -44,6 +44,7 @@ static const uint32_t libusb20_me_encode_empty[2];	/* dummy */
 LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_DEVICE_DESC);
 LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_ENDPOINT_DESC);
 LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_INTERFACE_DESC);
+LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_INTERFACE_ASSOCIATION_DESC);
 LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_CONFIG_DESC);
 LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_CONTROL_SETUP);
 LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_SS_ENDPT_COMP_DESC);
@@ -67,12 +68,14 @@ libusb20_parse_config_desc(const void *config_desc)
 	struct libusb20_interface *last_if;
 	struct libusb20_endpoint *lub_endpoint;
 	struct libusb20_endpoint *last_ep;
+	struct libusb20_iad_desc *lub_iad;
 
 	struct libusb20_me_struct pcdesc;
 	const uint8_t *ptr;
 	uint32_t size;
 	uint16_t niface_no_alt;
 	uint16_t niface;
+	uint16_t niad;
 	uint16_t nendpoint;
 	uint16_t iface_no;
 
@@ -89,6 +92,7 @@ libusb20_parse_config_desc(const void *config_desc)
 	nendpoint = 0;
 	niface = 0;
 	iface_no = 0xFFFF;
+	niad = 0;
 	ptr = NULL;
 
 	/* get "wTotalLength" and setup "pcdesc" */
@@ -109,6 +113,8 @@ libusb20_parse_config_desc(const void *config_desc)
 				iface_no = ptr[2];
 				niface_no_alt++;
 			}
+		} else if (ptr[1] == LIBUSB20_DT_INTERFACE_ASSOCIATION) {
+			niad++;
 		}
 	}
 
@@ -119,10 +125,12 @@ libusb20_parse_config_desc(const void *config_desc)
 	if (nendpoint >= 256) {
 		return (NULL);		/* corrupt */
 	}
-	size = sizeof(*lub_config) +
-	    (niface * sizeof(*lub_interface)) +
+	if (niad >= 256)
+		return (NULL);
+
+	size = sizeof(*lub_config) + (niface * sizeof(*lub_interface)) +
 	    (nendpoint * sizeof(*lub_endpoint)) +
-	    pcdesc.len;
+	    (niad * sizeof(*lub_iad)) + pcdesc.len;
 
 	lub_config = malloc(size);
 	if (lub_config == NULL) {
@@ -134,14 +142,15 @@ libusb20_parse_config_desc(const void *config_desc)
 	lub_interface = (void *)(lub_config + 1);
 	lub_alt_interface = (void *)(lub_interface + niface_no_alt);
 	lub_endpoint = (void *)(lub_interface + niface);
+	lub_iad = (void *)(lub_endpoint + nendpoint);
 
 	/*
 	 * Make a copy of the config descriptor, so that the caller can free
 	 * the initial config descriptor pointer!
 	 */
-	memcpy((void *)(lub_endpoint + nendpoint), config_desc, pcdesc.len);
+	memcpy((void *)(lub_iad + niad), config_desc, pcdesc.len);
 
-	ptr = (const void *)(lub_endpoint + nendpoint);
+	ptr = (const void *)(lub_iad + niad);
 	pcdesc.ptr = LIBUSB20_ADD_BYTES(ptr, 0);
 
 	/* init config structure */
@@ -156,6 +165,8 @@ libusb20_parse_config_desc(const void *config_desc)
 	lub_config->extra.ptr = LIBUSB20_ADD_BYTES(ptr, ptr[0]);
 	lub_config->extra.len = -ptr[0];
 	lub_config->extra.type = LIBUSB20_ME_IS_RAW;
+	lub_config->niad = niad;
+	lub_config->iad_desc = lub_iad;
 
 	/* reset states */
 	niface = 0;
@@ -163,6 +174,7 @@ libusb20_parse_config_desc(const void *config_desc)
 	ptr = NULL;
 	lub_interface--;
 	lub_endpoint--;
+	lub_iad--;
 	last_if = NULL;
 	last_ep = NULL;
 
@@ -223,6 +235,14 @@ libusb20_parse_config_desc(const void *config_desc)
 			last_if->num_altsetting = 0;
 			last_if->num_endpoints = 0;
 			last_ep = NULL;
+		} else if (ptr[1] == LIBUSB20_DT_INTERFACE_ASSOCIATION) {
+			lub_iad++;
+			LIBUSB20_INIT(LIBUSB20_INTERFACE_ASSOCIATION_DESC,
+			    &lub_iad->desc);
+			libusb20_me_decode(ptr, ptr[0], &lub_iad->desc);
+			lub_iad->extra.ptr = LIBUSB20_ADD_BYTES(ptr, ptr[0]);
+			lub_iad->extra.len = 0;
+			lub_iad->extra.type = LIBUSB20_ME_IS_RAW;
 		} else {
 			/* unknown descriptor */
 			if (last_if) {
diff --git a/lib/libusb/libusb20_desc.h b/lib/libusb/libusb20_desc.h
index 0f7c9294ebc8..f19a9dc00a24 100644
--- a/lib/libusb/libusb20_desc.h
+++ b/lib/libusb/libusb20_desc.h
@@ -248,6 +248,18 @@ LIBUSB20_MAKE_STRUCT(LIBUSB20_ENDPOINT_DESC);
 
 LIBUSB20_MAKE_STRUCT(LIBUSB20_INTERFACE_DESC);
 
+#define	LIBUSB20_INTERFACE_ASSOCIATION_DESC(m,n) \
+  m(n, UINT8_T,  bLength, ) \
+  m(n, UINT8_T,  bDescriptorType, ) \
+  m(n, UINT8_T,  bFirstInterface, ) \
+  m(n, UINT8_T,  bInterfaceCount, ) \
+  m(n, UINT8_T,  bFunctionClass, ) \
+  m(n, UINT8_T,  bFunctionSubClass, ) \
+  m(n, UINT8_T,  bFunctionProtocol, ) \
+  m(n, UINT8_T,  iFunction, ) \
+
+LIBUSB20_MAKE_STRUCT(LIBUSB20_INTERFACE_ASSOCIATION_DESC);
+
 #define	LIBUSB20_CONFIG_DESC(m,n) \
   m(n, UINT8_T,  bLength, ) \
   m(n, UINT8_T,  bDescriptorType, ) \
@@ -368,6 +380,10 @@ enum libusb20_descriptor_type {
 	/** Endpoint descriptor. See LIBUSB20_ENDPOINT_DESC. */
 	LIBUSB20_DT_ENDPOINT = 0x05,
 
+	/** Interface Association descriptor. See
+	   LIBUSB20_INTERFACE_ASSOCIATION_DESC. */
+	LIBUSB20_DT_INTERFACE_ASSOCIATION = 0x0b,
+
 	/** HID descriptor */
 	LIBUSB20_DT_HID = 0x21,
 
@@ -583,11 +599,18 @@ struct libusb20_interface {
 	uint8_t	num_endpoints;
 } __aligned(sizeof(void *));
 
+struct libusb20_iad_desc {
+	struct LIBUSB20_INTERFACE_ASSOCIATION_DESC_DECODED desc;
+	struct libusb20_me_struct extra;
+} __aligned(sizeof(void *));
+
 struct libusb20_config {
 	struct LIBUSB20_CONFIG_DESC_DECODED desc;
 	struct libusb20_me_struct extra;
 	struct libusb20_interface *interface;
 	uint8_t	num_interface;
+	struct libusb20_iad_desc *iad_desc;
+	int niad;
 } __aligned(sizeof(void *));
 
 uint8_t	libusb20_me_get_1(const struct libusb20_me_struct *ie, uint16_t offset);