git: d84b639a684e - main - libusb: implement zlp flag in libusb transfer

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

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

commit d84b639a684e7e0640e7c24a1a6dbc09da4bc3d4
Author:     ShengYi Hung <aokblast@FreeBSD.org>
AuthorDate: 2025-08-06 12:48:05 +0000
Commit:     ShengYi Hung <aokblast@FreeBSD.org>
CommitDate: 2026-06-22 13:36:48 +0000

    libusb: implement zlp flag in libusb transfer
    
    The USB protocol defines a Zero-Length Packet (ZLP) to signal the end of
    a transfer when the data size is an exact multiple of the Maximum Packet
    Size (MPS). Without a ZLP in such cases, the device may not be able to
    determine that the transfer has completed.
    
    This flag is added to libusb to allow the user send a ZLP in the end
    of libusb_xfer.
    
    Reviewed by:    adrian
    Sponsored by:   The FreeBSD Foundataion
    Differential Revision: https://reviews.freebsd.org/D51759
---
 lib/libusb/libusb.h   |  1 +
 lib/libusb/libusb10.c | 29 ++++++++++++++++++++++++++++-
 2 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/lib/libusb/libusb.h b/lib/libusb/libusb.h
index b6853879412c..95f7faf900f8 100644
--- a/lib/libusb/libusb.h
+++ b/lib/libusb/libusb.h
@@ -264,6 +264,7 @@ enum libusb_transfer_flags {
 	LIBUSB_TRANSFER_SHORT_NOT_OK = 1 << 0,
 	LIBUSB_TRANSFER_FREE_BUFFER = 1 << 1,
 	LIBUSB_TRANSFER_FREE_TRANSFER = 1 << 2,
+	LIBUSB_TRANSFER_ADD_ZERO_PACKET = 1 << 3,
 };
 
 enum libusb_log_level {
diff --git a/lib/libusb/libusb10.c b/lib/libusb/libusb10.c
index ab92c219409a..bd3c25c0f052 100644
--- a/lib/libusb/libusb10.c
+++ b/lib/libusb/libusb10.c
@@ -1419,6 +1419,7 @@ libusb10_bulk_intr_proxy(struct libusb20_transfer *pxfer)
 	uint32_t actlen;
 	uint8_t status;
 	uint8_t flags;
+	uint8_t tr_flags;
 
 	status = libusb20_tr_get_status(pxfer);
 	sxfer = libusb20_tr_get_priv_sc1(pxfer);
@@ -1471,6 +1472,20 @@ libusb10_bulk_intr_proxy(struct libusb20_transfer *pxfer)
 		sxfer->curr_data += max_bulk;
 		sxfer->rem_len -= max_bulk;
 
+		/*
+		 * When a zero length packet (ZLP) is requested, ask the
+		 * kernel to terminate the last frame of the transfer with
+		 * a short packet. This appends a ZLP when the data length
+		 * is an exact multiple of the maximum packet size.
+		 */
+		tr_flags = libusb20_tr_get_flags(pxfer);
+		if (sxfer->rem_len == 0 &&
+		    (flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET))
+			tr_flags |= LIBUSB20_TRANSFER_FORCE_SHORT;
+		else
+			tr_flags &= ~LIBUSB20_TRANSFER_FORCE_SHORT;
+		libusb20_tr_set_flags(pxfer, tr_flags);
+
 		libusb20_tr_submit(pxfer);
 
 		/* check if we can fork another USB transfer */
@@ -1710,7 +1725,7 @@ libusb_submit_transfer(struct libusb_transfer *uxfer)
 	struct libusb_super_transfer *sxfer;
 	struct libusb_device *dev;
 	uint8_t endpoint;
-	int err;
+	int err, mps;
 
 	if (uxfer == NULL)
 		return (LIBUSB_ERROR_INVALID_PARAM);
@@ -1731,6 +1746,18 @@ libusb_submit_transfer(struct libusb_transfer *uxfer)
 
 	pxfer0 = libusb10_get_transfer(uxfer->dev_handle, endpoint, 0);
 	pxfer1 = libusb10_get_transfer(uxfer->dev_handle, endpoint, 1);
+	mps = libusb_get_max_packet_size(dev, endpoint);
+
+	/*
+	 * The ADD_ZERO_PACKET flag only has an effect on host-to-device
+	 * (OUT) transfers whose length is an exact multiple of the maximum
+	 * packet size. Clear it otherwise so the kernel is not asked to
+	 * terminate the transfer with a short packet, see
+	 * libusb10_bulk_intr_proxy().
+	 */
+	if ((uxfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) &&
+	    ((endpoint & LIBUSB_ENDPOINT_IN) || uxfer->length % mps != 0))
+		uxfer->flags &= ~LIBUSB_TRANSFER_ADD_ZERO_PACKET;
 
 	if (pxfer0 == NULL || pxfer1 == NULL) {
 		err = LIBUSB_ERROR_OTHER;