* [PATCH 1/3] usb: gadget: f_fs: Initialize epfile->in early to fix endpoint direction checks
2026-06-14 18:09 [PATCH 0/3] usb: gadget: f_fs: Add R/W proxy EPs and ZLP support Neill Kapron
@ 2026-06-14 18:10 ` Neill Kapron
2026-06-15 2:30 ` Greg KH
2026-06-14 18:10 ` [PATCH 2/3] usb: gadget: f_fs: Add zero-length packet ioctl Neill Kapron
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Neill Kapron @ 2026-06-14 18:10 UTC (permalink / raw)
To: gregkh, corbet, skhan, Paul Cercueil, Christian König,
Simona Vetter
Cc: linux-usb, linux-doc, linux-kernel, kernel-team, Neill Kapron
When parsing endpoint descriptors, ffs_data_got_descs() generates the
eps_addrmap which contains the endpoint direction. However, epfile->in
was previously only populated in ffs_func_eps_enable() which executes
upon USB host connection. As a result, early userspace ioctls like
FUNCTIONFS_DMABUF_ATTACH that run before the host connects would see
epfile->in as 0, leading to incorrect DMA directions.
By moving the initialization to ffs_epfiles_create(), epfile->in is
accurate before userspace opens the endpoint files.
Fixes: 7b07a2a7ca02 ("usb: gadget: functionfs: Add DMABUF import interface")
Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Neill Kapron <nkapron@google.com>
---
drivers/usb/gadget/function/f_fs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 75912ce6ab55..38e36faefe92 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -2364,6 +2364,7 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]);
else
sprintf(epfile->name, "ep%u", i);
+ epfile->in = (ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) ? 1 : 0;
err = ffs_sb_create_file(ffs->sb, epfile->name,
epfile, &ffs_epfile_operations);
if (err) {
@@ -2453,7 +2454,6 @@ static int ffs_func_eps_enable(struct ffs_function *func)
ret = usb_ep_enable(ep->ep);
if (!ret) {
epfile->ep = ep;
- epfile->in = usb_endpoint_dir_in(ep->ep->desc);
epfile->isoc = usb_endpoint_xfer_isoc(ep->ep->desc);
} else {
break;
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH 1/3] usb: gadget: f_fs: Initialize epfile->in early to fix endpoint direction checks
2026-06-14 18:10 ` [PATCH 1/3] usb: gadget: f_fs: Initialize epfile->in early to fix endpoint direction checks Neill Kapron
@ 2026-06-15 2:30 ` Greg KH
0 siblings, 0 replies; 7+ messages in thread
From: Greg KH @ 2026-06-15 2:30 UTC (permalink / raw)
To: Neill Kapron
Cc: corbet, skhan, Paul Cercueil, Christian König, Simona Vetter,
linux-usb, linux-doc, linux-kernel, kernel-team
On Sun, Jun 14, 2026 at 06:10:00PM +0000, Neill Kapron wrote:
> When parsing endpoint descriptors, ffs_data_got_descs() generates the
> eps_addrmap which contains the endpoint direction. However, epfile->in
> was previously only populated in ffs_func_eps_enable() which executes
> upon USB host connection. As a result, early userspace ioctls like
> FUNCTIONFS_DMABUF_ATTACH that run before the host connects would see
> epfile->in as 0, leading to incorrect DMA directions.
>
> By moving the initialization to ffs_epfiles_create(), epfile->in is
> accurate before userspace opens the endpoint files.
>
> Fixes: 7b07a2a7ca02 ("usb: gadget: functionfs: Add DMABUF import interface")
> Assisted-by: Antigravity:gemini-3.1-pro
> Signed-off-by: Neill Kapron <nkapron@google.com>
> ---
> drivers/usb/gadget/function/f_fs.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
This should also go to stable, right?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 2/3] usb: gadget: f_fs: Add zero-length packet ioctl
2026-06-14 18:09 [PATCH 0/3] usb: gadget: f_fs: Add R/W proxy EPs and ZLP support Neill Kapron
2026-06-14 18:10 ` [PATCH 1/3] usb: gadget: f_fs: Initialize epfile->in early to fix endpoint direction checks Neill Kapron
@ 2026-06-14 18:10 ` Neill Kapron
2026-06-14 18:10 ` [PATCH 3/3] usb: gadget: f_fs: Introduce rw_proxy file descriptors Neill Kapron
2026-06-15 2:30 ` [PATCH 0/3] usb: gadget: f_fs: Add R/W proxy EPs and ZLP support Greg KH
3 siblings, 0 replies; 7+ messages in thread
From: Neill Kapron @ 2026-06-14 18:10 UTC (permalink / raw)
To: gregkh, corbet, skhan
Cc: linux-usb, linux-doc, linux-kernel, kernel-team, Neill Kapron
When transferring data from a USB gadget to a host, a transfer is
considered complete when the host receives a short packet (a packet
smaller than wMaxPacketSize). If the total transfer length is an
exact multiple of wMaxPacketSize, a Zero-Length Packet (ZLP) must
be appended to signal the end of the transfer.
FunctionFS currently provides no mechanism for userspace to instruct
the kernel to set the `req->zero` flag on transfers. Userspace
workarounds, such as manually submitting separate 0-byte requests,
may not be available for legacy protocols which must maintain write
behavior compatibility when moved to functionfs implementations.
To resolve this, introduce a new ioctl, FUNCTIONFS_ENDPOINT_ENABLE_ZLP,
which takes a pointer to a __u32 flag. When enabled, all subsequent
transfers on that endpoint will have the `req->zero` flag set, allowing
the underlying USB Device Controller (UDC) hardware to automatically
append a ZLP only when mathematically required. For logical transfers
chunked across multiple requests, userspace can dynamically toggle this
flag, enabling it only prior to submitting the final chunk.
The flag defaults to false to maintain backward compatibility. Once
enabled, the state is persistent for the lifetime of the endpoint and
will not be reset by opening or closing the endpoint file descriptors.
Assisted-by: Gemini-CLI:gemini-3.1-pro-preview
Signed-off-by: Neill Kapron <nkapron@google.com>
---
Documentation/usb/functionfs.rst | 24 ++++++++++++++++++++++++
drivers/usb/gadget/function/f_fs.c | 25 ++++++++++++++++++++++++-
include/uapi/linux/usb/functionfs.h | 23 +++++++++++++++++++++++
3 files changed, 71 insertions(+), 1 deletion(-)
diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst
index f7487b0d8057..582e53549d5b 100644
--- a/Documentation/usb/functionfs.rst
+++ b/Documentation/usb/functionfs.rst
@@ -72,6 +72,30 @@ have been written to their ep0's.
Conversely, the gadget is unregistered after the first USB function
closes its endpoints.
+Endpoint IOCTLs
+===============
+
+FunctionFS supports additional IOCTLs that can be performed on data endpoints
+(ie. not ep0). For a full list of these IOCTLs, please refer to the documentation
+in ``include/uapi/linux/usb/functionfs.h``.
+
+One such IOCTL is:
+
+ ``FUNCTIONFS_ENDPOINT_ENABLE_ZLP(__u32 *)``
+ Enable or disable automatic zero-length packet (ZLP) appending for the
+ endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to
+ enable. When enabled, the kernel will automatically append a ZLP at the end
+ of a transfer if the payload length is an exact multiple of the endpoint's
+ max packet size. This is useful for compatibility with legacy protocols
+ which require automatic ZLP appending to data written from userspace. This
+ IOCTL can only be used on IN endpoints. It can be called at any time after
+ the FunctionFS instance is active, even before the host has connected or
+ enabled the endpoint. Returns zero on success, or a negative errno value on
+ error:
+
+ * ``-ENODEV``: The FunctionFS instance is not active.
+ * ``-EINVAL``: The endpoint is not an IN endpoint.
+ * ``-EFAULT``: Invalid user space pointer for the argument.
DMABUF interface
================
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 38e36faefe92..4c1bafb3eef5 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -224,7 +224,7 @@ struct ffs_epfile {
unsigned char in; /* P: ffs->eps_lock */
unsigned char isoc; /* P: ffs->eps_lock */
- unsigned char _pad;
+ u8 zlp_enabled; /* P: ffs->eps_lock */
/* Protects dmabufs */
struct mutex dmabufs_mutex;
@@ -1114,6 +1114,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
req->buf = data;
req->num_sgs = 0;
}
+
+ req->zero = epfile->zlp_enabled;
req->length = data_len;
io_data->buf = data;
@@ -1165,6 +1167,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
req->buf = data;
req->num_sgs = 0;
}
+
+ req->zero = epfile->zlp_enabled;
req->length = data_len;
io_data->buf = data;
@@ -1708,6 +1712,7 @@ static int ffs_dmabuf_transfer(struct file *file,
/* Now that the dma_fence is in place, queue the transfer. */
+ usb_req->zero = epfile->zlp_enabled;
usb_req->length = req->length;
usb_req->buf = NULL;
usb_req->sg = priv->sgt->sgl;
@@ -1755,6 +1760,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
struct ffs_epfile *epfile = file->private_data;
struct ffs_ep *ep;
int ret;
+ __u32 enable_zlp = 0;
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
@@ -1787,6 +1793,23 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
return ffs_dmabuf_transfer(file, &req);
}
+ /*
+ * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace
+ * to configure ZLP behavior immediately without blocking indefinitely
+ * while waiting for the USB host to connect and enable the endpoint.
+ */
+ case FUNCTIONFS_ENDPOINT_ENABLE_ZLP:
+ if (!epfile->in)
+ return -EINVAL;
+
+ if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp)))
+ return -EFAULT;
+
+ spin_lock_irq(&epfile->ffs->eps_lock);
+ epfile->zlp_enabled = !!enable_zlp;
+ spin_unlock_irq(&epfile->ffs->eps_lock);
+
+ return 0;
default:
break;
}
diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h
index beef1752e36e..06134dca4e8a 100644
--- a/include/uapi/linux/usb/functionfs.h
+++ b/include/uapi/linux/usb/functionfs.h
@@ -414,4 +414,27 @@ struct usb_functionfs_event {
#define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \
struct usb_ffs_dmabuf_transfer_req)
+/*
+ * Enable or disable automatic zero-length packet (ZLP) appending for the
+ * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to
+ * enable.
+ *
+ * When enabled, the kernel will automatically append a ZLP at the end of
+ * a transfer if the payload length is an exact multiple of the endpoint's
+ * max packet size.
+ *
+ * This is useful for compatibility with legacy protocols which require
+ * automatic ZLP appending to data written from userspace.
+ *
+ * This ioctl can only be used on IN endpoints. It can be called at any time
+ * after the FunctionFS instance is active, even before the host has connected
+ * or enabled the endpoint.
+ *
+ * Returns zero on success, or a negative errno value on error:
+ * -ENODEV: The FunctionFS instance is not active.
+ * -EINVAL: The endpoint is not an IN endpoint.
+ * -EFAULT: Invalid user space pointer for the argument.
+ */
+#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32)
+
#endif /* _UAPI__LINUX_FUNCTIONFS_H__ */
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 3/3] usb: gadget: f_fs: Introduce rw_proxy file descriptors
2026-06-14 18:09 [PATCH 0/3] usb: gadget: f_fs: Add R/W proxy EPs and ZLP support Neill Kapron
2026-06-14 18:10 ` [PATCH 1/3] usb: gadget: f_fs: Initialize epfile->in early to fix endpoint direction checks Neill Kapron
2026-06-14 18:10 ` [PATCH 2/3] usb: gadget: f_fs: Add zero-length packet ioctl Neill Kapron
@ 2026-06-14 18:10 ` Neill Kapron
2026-06-15 2:35 ` Greg KH
2026-06-15 2:30 ` [PATCH 0/3] usb: gadget: f_fs: Add R/W proxy EPs and ZLP support Greg KH
3 siblings, 1 reply; 7+ messages in thread
From: Neill Kapron @ 2026-06-14 18:10 UTC (permalink / raw)
To: gregkh, corbet, skhan
Cc: linux-usb, linux-doc, linux-kernel, kernel-team, Neill Kapron
Currently, FunctionFS exposes each USB endpoint as a separate,
unidirectional file descriptor (e.g., `ep1` for IN, `ep2` for OUT).
While this mirrors the underlying hardware structure, it forces
userspace daemons implementing bidirectional protocols to manage
multiple file descriptors. When dealing with legacy protocols which
require exposing a single, bi-directional fd to userspace, this becomes
problematic.
This patch introduces the `FUNCTIONFS_RW_PROXY_EPS` UAPI flag. When
passed in the descriptor header during initialization, FunctionFS
provisions a "rw_proxy" bidirectional file descriptor (e.g., `ep1_rw`)
alongside every pair of IN/OUT endpoints.
Implementation details:
- RW proxy files act as a pure VFS alias, proxying operations
directly to the base ffs_epfile instances. A `read()` proxies to
the OUT endpoint's file, and a `write()` proxies to the IN file.
- Because operations are proxied natively, they reuse the underlying
base endpoint's lock (`epfile->mutex`) and tracking state. This
serializes concurrent read or write operations, preventing buffer
corruption and race conditions even if userspace mixes transfers across
both the rw_proxy and base files. This approach allows full-duplex
synchronous operations to occur concurrently without serializing on a
single lock.
- Control operations (like IOCTLs) and intentional stalls (via
reverse-direction I/O) must still be issued on the base endpoints, as the
rw_proxy returns `-ENOTTY` for IOCTLs and cannot trigger stalls.
Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Neill Kapron <nkapron@google.com>
---
Documentation/usb/functionfs.rst | 56 ++++++++++++++
drivers/usb/gadget/function/f_fs.c | 109 +++++++++++++++++++++++-----
drivers/usb/gadget/function/u_fs.h | 8 +-
include/uapi/linux/usb/functionfs.h | 1 +
4 files changed, 156 insertions(+), 18 deletions(-)
diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst
index 582e53549d5b..b189cf5626ba 100644
--- a/Documentation/usb/functionfs.rst
+++ b/Documentation/usb/functionfs.rst
@@ -96,6 +96,58 @@ One such IOCTL is:
* ``-ENODEV``: The FunctionFS instance is not active.
* ``-EINVAL``: The endpoint is not an IN endpoint.
* ``-EFAULT``: Invalid user space pointer for the argument.
+
+RW Proxy Endpoints
+==================
+
+If the ``FUNCTIONFS_RW_PROXY_EPS`` flag is passed in the descriptor header
+(requires ``FUNCTIONFS_DESCRIPTORS_MAGIC_V2``), FunctionFS will provision a
+bidirectional rw_proxy file descriptor (e.g., "ep1_rw") alongside each pair
+of IN and OUT endpoints. The rw_proxy file aliases the underlying hardware
+endpoints, allowing userspace to use a single file descriptor for both reading
+(OUT) and writing (IN).
+
+This flag requires the total number of hardware endpoints to be an even number.
+FunctionFS will automatically walk the provided endpoints and group them into
+adjacent pairs (e.g., ep1 and ep2 form the first pair, ep3 and ep4 form the
+second pair). Each pair must consist of exactly one IN endpoint and one OUT
+endpoint.
+
+For each valid pair, a rw_proxy file is created and named after the first
+endpoint in the pair with a "_rw" suffix. For example, if ep1 and ep2 are
+paired, a rw_proxy file named "ep1_rw" is created. If ep3 and ep4 are paired,
+"ep3_rw" is created.
+
+If the ``FUNCTIONFS_VIRTUAL_ADDR`` flag is also enabled, the endpoints will be
+named using their physical endpoint address in hexadecimal instead of their
+index. RW proxy files will inherit this naming convention. For example, if the
+first endpoint of a pair maps to address 0x02, the rw_proxy file will be
+named "ep02_rw".
+
+When this flag is enabled, userspace has the choice of performing data transfers
+via the single rw_proxy file descriptor or the two base file descriptors. The
+rw_proxy file descriptor acts as a pure VFS alias that proxies all operations
+directly to the underlying base file descriptors.
+
+Because it is a pure proxy, there are no data races or buffer corruptions if
+userspace uses both the rw_proxy endpoint and the base endpoints concurrently.
+The native mutexes of the base endpoints perfectly serialize all concurrent
+transfers. However, userspace should generally pick one method and stick to it
+to avoid interleaving its own data stream.
+
+- **IOCTLs (Clear Halt, etc.):** RW proxy endpoints do not support IOCTLs and
+ will return ``-ENOTTY``. To clear a host-initiated halt, userspace must issue
+ the ``FUNCTIONFS_CLEAR_HALT`` ioctl directly on the corresponding base
+ endpoint file descriptor.
+- **Intentional Stalls:** The traditional mechanism for intentionally halting an
+ endpoint by issuing a reverse-direction data operation (e.g., attempting to
+ read from an IN endpoint) continues to work, but it must be issued on the
+ base endpoint. RW proxy endpoints cannot be used to trigger a stall because
+ they are fully bidirectional.
+
+Note that DMABUF data transfers (``FUNCTIONFS_DMABUF_TRANSFER``) are unsupported
+via the rw_proxy endpoint because it does not support IOCTLs. If DMABUF
+transfers are required, users must use the standard base endpoints.
DMABUF interface
================
@@ -103,6 +155,10 @@ FunctionFS additionally supports a DMABUF based interface, where the
userspace can attach DMABUF objects (externally created) to an endpoint,
and subsequently use them for data transfers.
+Note: The DMABUF interface is unsupported on rw_proxy endpoints. See
+the RW Proxy Endpoints section for details on using DMABUF alongside
+the ``FUNCTIONFS_RW_PROXY_EPS`` flag.
+
A userspace application can then use this interface to share DMABUF
objects between several interfaces, allowing it to transfer data in a
zero-copy fashion, for instance between IIO and the USB stack.
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 4c1bafb3eef5..0ccfdcfb1810 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -159,7 +159,9 @@ struct ffs_epfile {
struct mutex mutex;
struct ffs_data *ffs;
- struct ffs_ep *ep; /* P: ffs->eps_lock */
+ struct ffs_ep *ep; /* P: ffs->eps_lock */
+ struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */
+ struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */
/*
* Buffer for holding data from partial reads which may happen since
@@ -219,17 +221,20 @@ struct ffs_epfile {
struct ffs_buffer *read_buffer;
#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN))
- char name[5];
+ char name[10];
unsigned char in; /* P: ffs->eps_lock */
unsigned char isoc; /* P: ffs->eps_lock */
u8 zlp_enabled; /* P: ffs->eps_lock */
+ bool is_rw_proxy;
/* Protects dmabufs */
struct mutex dmabufs_mutex;
struct list_head dmabufs; /* P: dmabufs_mutex */
atomic_t seqno;
+
+ int opened_count; /* P: ffs->eps_lock */
};
struct ffs_buffer {
@@ -978,9 +983,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile,
return ret;
}
-static struct ffs_ep *ffs_epfile_wait_ep(struct file *file)
+static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file)
{
- struct ffs_epfile *epfile = file->private_data;
struct ffs_ep *ep;
int ret;
@@ -1007,17 +1011,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
char *data = NULL;
ssize_t ret, data_len = -EINVAL;
int halt;
+ bool is_rw_proxy = epfile->is_rw_proxy;
/* Are we still active? */
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
- ep = ffs_epfile_wait_ep(file);
+ /* Proxy to base endpoint if rw_proxy */
+ if (is_rw_proxy)
+ epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in;
+
+ ep = ffs_epfile_wait_ep(epfile, file);
if (IS_ERR(ep))
return PTR_ERR(ep);
/* Do we halt? */
- halt = (!io_data->read == !epfile->in);
+ halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in);
if (halt && epfile->isoc)
return -EINVAL;
@@ -1115,7 +1124,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
req->num_sgs = 0;
}
- req->zero = epfile->zlp_enabled;
+ req->zero = !io_data->read ? epfile->zlp_enabled : 0;
req->length = data_len;
io_data->buf = data;
@@ -1168,7 +1177,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
req->num_sgs = 0;
}
- req->zero = epfile->zlp_enabled;
+ req->zero = !io_data->read ? epfile->zlp_enabled : 0;
req->length = data_len;
io_data->buf = data;
@@ -1225,6 +1234,12 @@ ffs_epfile_open(struct inode *inode, struct file *file)
spin_unlock_irq(&ffs->eps_lock);
return -ENODEV;
}
+ if (epfile->is_rw_proxy) {
+ epfile->epfile_in->opened_count++;
+ epfile->epfile_out->opened_count++;
+ } else {
+ epfile->opened_count++;
+ }
ffs->opened++;
spin_unlock_irq(&ffs->eps_lock);
@@ -1378,8 +1393,18 @@ ffs_epfile_release(struct inode *inode, struct file *file)
mutex_unlock(&epfile->dmabufs_mutex);
- __ffs_epfile_read_buffer_free(epfile);
- ffs_data_closed(epfile->ffs);
+ spin_lock_irq(&ffs->eps_lock);
+ if (epfile->is_rw_proxy) {
+ epfile->epfile_in->opened_count--;
+ if (--epfile->epfile_out->opened_count == 0)
+ __ffs_epfile_read_buffer_free(epfile->epfile_out);
+ } else {
+ if (--epfile->opened_count == 0)
+ __ffs_epfile_read_buffer_free(epfile);
+ }
+ spin_unlock_irq(&ffs->eps_lock);
+
+ ffs_data_closed(ffs);
return 0;
}
@@ -1647,7 +1672,7 @@ static int ffs_dmabuf_transfer(struct file *file,
priv = attach->importer_priv;
- ep = ffs_epfile_wait_ep(file);
+ ep = ffs_epfile_wait_ep(epfile, file);
if (IS_ERR(ep)) {
ret = PTR_ERR(ep);
goto err_attachment_put;
@@ -1765,6 +1790,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
+ if (epfile->is_rw_proxy)
+ return -ENOTTY;
+
switch (code) {
case FUNCTIONFS_DMABUF_ATTACH:
{
@@ -1815,7 +1843,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
}
/* Wait for endpoint to be enabled */
- ep = ffs_epfile_wait_ep(file);
+ ep = ffs_epfile_wait_ep(epfile, file);
if (IS_ERR(ep))
return PTR_ERR(ep);
@@ -2213,7 +2241,7 @@ static void ffs_data_closed(struct ffs_data *ffs)
if (epfiles)
ffs_epfiles_destroy(ffs->sb, epfiles,
- ffs->eps_count);
+ ffs->epfiles_count);
if (ffs->setup_state == FFS_SETUP_PENDING)
__ffs_ep0_stall(ffs);
@@ -2271,7 +2299,7 @@ static void ffs_data_clear(struct ffs_data *ffs)
* copy of epfile will save us from use-after-free.
*/
if (epfiles) {
- ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count);
+ ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count);
ffs->epfiles = NULL;
}
@@ -2369,11 +2397,16 @@ static void functionfs_unbind(struct ffs_data *ffs)
static int ffs_epfiles_create(struct ffs_data *ffs)
{
struct ffs_epfile *epfile, *epfiles;
- unsigned i, count;
+ unsigned int i, count, epfiles_count;
int err;
count = ffs->eps_count;
- epfiles = kzalloc_objs(*epfiles, count);
+ epfiles_count = count;
+ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS)
+ epfiles_count += count / 2;
+ ffs->epfiles_count = epfiles_count;
+
+ epfiles = kzalloc_objs(*epfiles, epfiles_count);
if (!epfiles)
return -ENOMEM;
@@ -2396,6 +2429,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
}
}
+ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) {
+ struct ffs_epfile *comp = epfiles + count;
+
+ for (i = 0; i < count; i += 2, ++comp) {
+ struct ffs_epfile *ep1 = &epfiles[i];
+ struct ffs_epfile *ep2 = &epfiles[i + 1];
+ bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK;
+
+ comp->ffs = ffs;
+ comp->is_rw_proxy = true;
+ comp->epfile_in = ep1_in ? ep1 : ep2;
+ comp->epfile_out = ep1_in ? ep2 : ep1;
+ mutex_init(&comp->mutex);
+ mutex_init(&comp->dmabufs_mutex);
+ INIT_LIST_HEAD(&comp->dmabufs);
+ snprintf(comp->name, sizeof(comp->name), "%s_rw",
+ epfiles[i].name);
+ err = ffs_sb_create_file(ffs->sb, comp->name,
+ comp, &ffs_epfile_operations);
+ if (err) {
+ ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2));
+ return err;
+ }
+ }
+ }
+
ffs->epfiles = epfiles;
return 0;
}
@@ -2972,7 +3031,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
FUNCTIONFS_VIRTUAL_ADDR |
FUNCTIONFS_EVENTFD |
FUNCTIONFS_ALL_CTRL_RECIP |
- FUNCTIONFS_CONFIG0_SETUP)) {
+ FUNCTIONFS_CONFIG0_SETUP |
+ FUNCTIONFS_RW_PROXY_EPS)) {
ret = -ENOSYS;
goto error;
}
@@ -3060,6 +3120,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
goto error;
}
+ if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) {
+ if (ffs->eps_count % 2) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ for (i = 1; i < ffs->eps_count; i += 2) {
+ if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) ==
+ (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) {
+ ret = -EINVAL;
+ goto error;
+ }
+ }
+ }
+
ffs->raw_descs_data = _data;
ffs->raw_descs = raw_descs;
ffs->raw_descs_length = data - raw_descs;
diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h
index 6a80182aadd7..c280c495fbd2 100644
--- a/drivers/usb/gadget/function/u_fs.h
+++ b/drivers/usb/gadget/function/u_fs.h
@@ -252,8 +252,14 @@ struct ffs_data {
unsigned short strings_count;
unsigned short interfaces_count;
+
+ /*
+ * eps_count tracks the number of underlying hardware endpoints.
+ * epfiles_count tracks the total number of VFS endpoint files.
+ * When companion endpoints are active, epfiles_count > eps_count.
+ */
unsigned short eps_count;
- unsigned short _pad1;
+ unsigned short epfiles_count;
/* filled by __ffs_data_got_strings() */
/* ids in stringtabs are set in functionfs_bind() */
diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h
index 06134dca4e8a..290308c9dd92 100644
--- a/include/uapi/linux/usb/functionfs.h
+++ b/include/uapi/linux/usb/functionfs.h
@@ -25,6 +25,7 @@ enum functionfs_flags {
FUNCTIONFS_EVENTFD = 32,
FUNCTIONFS_ALL_CTRL_RECIP = 64,
FUNCTIONFS_CONFIG0_SETUP = 128,
+ FUNCTIONFS_RW_PROXY_EPS = 256,
};
/* Descriptor of an non-audio endpoint */
--
2.54.0.1136.gdb2ca164c4-goog
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH 3/3] usb: gadget: f_fs: Introduce rw_proxy file descriptors
2026-06-14 18:10 ` [PATCH 3/3] usb: gadget: f_fs: Introduce rw_proxy file descriptors Neill Kapron
@ 2026-06-15 2:35 ` Greg KH
0 siblings, 0 replies; 7+ messages in thread
From: Greg KH @ 2026-06-15 2:35 UTC (permalink / raw)
To: Neill Kapron
Cc: corbet, skhan, linux-usb, linux-doc, linux-kernel, kernel-team
On Sun, Jun 14, 2026 at 06:10:02PM +0000, Neill Kapron wrote:
> Currently, FunctionFS exposes each USB endpoint as a separate,
> unidirectional file descriptor (e.g., `ep1` for IN, `ep2` for OUT).
> While this mirrors the underlying hardware structure, it forces
> userspace daemons implementing bidirectional protocols to manage
> multiple file descriptors. When dealing with legacy protocols which
> require exposing a single, bi-directional fd to userspace, this becomes
> problematic.
>
> This patch introduces the `FUNCTIONFS_RW_PROXY_EPS` UAPI flag. When
> passed in the descriptor header during initialization, FunctionFS
> provisions a "rw_proxy" bidirectional file descriptor (e.g., `ep1_rw`)
> alongside every pair of IN/OUT endpoints.
>
> Implementation details:
> - RW proxy files act as a pure VFS alias, proxying operations
> directly to the base ffs_epfile instances. A `read()` proxies to
> the OUT endpoint's file, and a `write()` proxies to the IN file.
> - Because operations are proxied natively, they reuse the underlying
> base endpoint's lock (`epfile->mutex`) and tracking state. This
> serializes concurrent read or write operations, preventing buffer
> corruption and race conditions even if userspace mixes transfers across
> both the rw_proxy and base files. This approach allows full-duplex
> synchronous operations to occur concurrently without serializing on a
> single lock.
> - Control operations (like IOCTLs) and intentional stalls (via
> reverse-direction I/O) must still be issued on the base endpoints, as the
> rw_proxy returns `-ENOTTY` for IOCTLs and cannot trigger stalls.
>
> Assisted-by: Antigravity:gemini-3.1-pro
> Signed-off-by: Neill Kapron <nkapron@google.com>
> ---
> Documentation/usb/functionfs.rst | 56 ++++++++++++++
> drivers/usb/gadget/function/f_fs.c | 109 +++++++++++++++++++++++-----
> drivers/usb/gadget/function/u_fs.h | 8 +-
> include/uapi/linux/usb/functionfs.h | 1 +
> 4 files changed, 156 insertions(+), 18 deletions(-)
>
> diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst
> index 582e53549d5b..b189cf5626ba 100644
> --- a/Documentation/usb/functionfs.rst
> +++ b/Documentation/usb/functionfs.rst
> @@ -96,6 +96,58 @@ One such IOCTL is:
> * ``-ENODEV``: The FunctionFS instance is not active.
> * ``-EINVAL``: The endpoint is not an IN endpoint.
> * ``-EFAULT``: Invalid user space pointer for the argument.
> +
> +RW Proxy Endpoints
> +==================
> +
> +If the ``FUNCTIONFS_RW_PROXY_EPS`` flag is passed in the descriptor header
> +(requires ``FUNCTIONFS_DESCRIPTORS_MAGIC_V2``), FunctionFS will provision a
> +bidirectional rw_proxy file descriptor (e.g., "ep1_rw") alongside each pair
> +of IN and OUT endpoints. The rw_proxy file aliases the underlying hardware
> +endpoints, allowing userspace to use a single file descriptor for both reading
> +(OUT) and writing (IN).
> +
> +This flag requires the total number of hardware endpoints to be an even number.
> +FunctionFS will automatically walk the provided endpoints and group them into
> +adjacent pairs (e.g., ep1 and ep2 form the first pair, ep3 and ep4 form the
> +second pair). Each pair must consist of exactly one IN endpoint and one OUT
> +endpoint.
> +
> +For each valid pair, a rw_proxy file is created and named after the first
> +endpoint in the pair with a "_rw" suffix. For example, if ep1 and ep2 are
> +paired, a rw_proxy file named "ep1_rw" is created. If ep3 and ep4 are paired,
> +"ep3_rw" is created.
> +
> +If the ``FUNCTIONFS_VIRTUAL_ADDR`` flag is also enabled, the endpoints will be
> +named using their physical endpoint address in hexadecimal instead of their
> +index. RW proxy files will inherit this naming convention. For example, if the
> +first endpoint of a pair maps to address 0x02, the rw_proxy file will be
> +named "ep02_rw".
> +
> +When this flag is enabled, userspace has the choice of performing data transfers
> +via the single rw_proxy file descriptor or the two base file descriptors. The
> +rw_proxy file descriptor acts as a pure VFS alias that proxies all operations
> +directly to the underlying base file descriptors.
> +
> +Because it is a pure proxy, there are no data races or buffer corruptions if
> +userspace uses both the rw_proxy endpoint and the base endpoints concurrently.
> +The native mutexes of the base endpoints perfectly serialize all concurrent
> +transfers. However, userspace should generally pick one method and stick to it
> +to avoid interleaving its own data stream.
> +
> +- **IOCTLs (Clear Halt, etc.):** RW proxy endpoints do not support IOCTLs and
> + will return ``-ENOTTY``. To clear a host-initiated halt, userspace must issue
> + the ``FUNCTIONFS_CLEAR_HALT`` ioctl directly on the corresponding base
> + endpoint file descriptor.
> +- **Intentional Stalls:** The traditional mechanism for intentionally halting an
> + endpoint by issuing a reverse-direction data operation (e.g., attempting to
> + read from an IN endpoint) continues to work, but it must be issued on the
> + base endpoint. RW proxy endpoints cannot be used to trigger a stall because
> + they are fully bidirectional.
> +
> +Note that DMABUF data transfers (``FUNCTIONFS_DMABUF_TRANSFER``) are unsupported
> +via the rw_proxy endpoint because it does not support IOCTLs. If DMABUF
> +transfers are required, users must use the standard base endpoints.
> DMABUF interface
> ================
>
> @@ -103,6 +155,10 @@ FunctionFS additionally supports a DMABUF based interface, where the
> userspace can attach DMABUF objects (externally created) to an endpoint,
> and subsequently use them for data transfers.
>
> +Note: The DMABUF interface is unsupported on rw_proxy endpoints. See
> +the RW Proxy Endpoints section for details on using DMABUF alongside
> +the ``FUNCTIONFS_RW_PROXY_EPS`` flag.
> +
> A userspace application can then use this interface to share DMABUF
> objects between several interfaces, allowing it to transfer data in a
> zero-copy fashion, for instance between IIO and the USB stack.
> diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
> index 4c1bafb3eef5..0ccfdcfb1810 100644
> --- a/drivers/usb/gadget/function/f_fs.c
> +++ b/drivers/usb/gadget/function/f_fs.c
> @@ -159,7 +159,9 @@ struct ffs_epfile {
> struct mutex mutex;
>
> struct ffs_data *ffs;
> - struct ffs_ep *ep; /* P: ffs->eps_lock */
> + struct ffs_ep *ep; /* P: ffs->eps_lock */
> + struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */
> + struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */
>
> /*
> * Buffer for holding data from partial reads which may happen since
> @@ -219,17 +221,20 @@ struct ffs_epfile {
> struct ffs_buffer *read_buffer;
> #define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN))
>
> - char name[5];
> + char name[10];
Why change the size? Shouldn't that be a separate patch?
>
> unsigned char in; /* P: ffs->eps_lock */
> unsigned char isoc; /* P: ffs->eps_lock */
>
> u8 zlp_enabled; /* P: ffs->eps_lock */
> + bool is_rw_proxy;
>
> /* Protects dmabufs */
> struct mutex dmabufs_mutex;
> struct list_head dmabufs; /* P: dmabufs_mutex */
> atomic_t seqno;
> +
> + int opened_count; /* P: ffs->eps_lock */
Attempting to track "is this file open or not" almost always fails
horribly. Think about file descriptors that can be dup() and passed
around, the kernel has no idea what is going on with them, nor does it
have to.
Yes, we do track if the file is opened or not already, but I'd argue
that too is broken and should probably be removed and just use the
normal file descriptor logic instead.
> @@ -1378,8 +1393,18 @@ ffs_epfile_release(struct inode *inode, struct file *file)
>
> mutex_unlock(&epfile->dmabufs_mutex);
>
> - __ffs_epfile_read_buffer_free(epfile);
> - ffs_data_closed(epfile->ffs);
> + spin_lock_irq(&ffs->eps_lock);
> + if (epfile->is_rw_proxy) {
> + epfile->epfile_in->opened_count--;
> + if (--epfile->epfile_out->opened_count == 0)
> + __ffs_epfile_read_buffer_free(epfile->epfile_out);
> + } else {
> + if (--epfile->opened_count == 0)
> + __ffs_epfile_read_buffer_free(epfile);
If you drop the opened_count, shouldn't these buffers just get freed
when the structure themselves get freed? You are treating the count as
a "reference counted structure" in a hand-rolled way that might not
really be right here as it's kind of hard to prove.
Either use a real reference count for the whole structure (i.e. kref)
because you need to, or just tie the lifetime of the buffer to the
larger structure itself. Otherwise these fake references are going to
be a pain to track that all is correct with them...
thanks,
greg k-h
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 0/3] usb: gadget: f_fs: Add R/W proxy EPs and ZLP support
2026-06-14 18:09 [PATCH 0/3] usb: gadget: f_fs: Add R/W proxy EPs and ZLP support Neill Kapron
` (2 preceding siblings ...)
2026-06-14 18:10 ` [PATCH 3/3] usb: gadget: f_fs: Introduce rw_proxy file descriptors Neill Kapron
@ 2026-06-15 2:30 ` Greg KH
3 siblings, 0 replies; 7+ messages in thread
From: Greg KH @ 2026-06-15 2:30 UTC (permalink / raw)
To: Neill Kapron
Cc: corbet, skhan, linux-usb, linux-doc, linux-kernel, kernel-team
On Sun, Jun 14, 2026 at 06:09:59PM +0000, Neill Kapron wrote:
> We are working to deprecate a widely used, out of tree gadget driver by
> moving the functionality to userspace via functionfs. To do so, we have to
> maintain strict compatibility with the legacy driver, as there are many
> third party applications which can’t be modified and are reliant on this
> interface. Specifically, the following requirements must be met:
>
> - The function must expose a single file descriptor to userspace for both
> reads and writes,
> - It must block on writes when it can not handle more data,
> - It must handle arbitrary write transaction sizes,
> - It must automatically append a zero length packet (ZLP) when the write
> transaction ends on a boundary of a multiple of the max packet size.
>
> Initially, we pursued a compatibility layer in userspace which implemented
> a socket pair to combine the OUT and IN endpoint files. This approach
> proved problematic for several reasons.
>
> To preserve the write transaction boundary for ZLP calculation, we
> attempted to use SOCK_SEQPACKET. This created problems as larger
> transactions required contiguous buffers to be allocated, and, even if we
> ignore the constraint to the arbitrary write size and limited it to 1MB,
> the socket would occasionally return -ENOBUFS to the end user if a write
> operation was attempted when other sockets on the system were consuming
> more than 7MB of the 8MB wmem_max limit.
>
> After significant investigation including switching to SOCK_STREAM and
> attempting a heuristic timeout approach, we decided the best path forward
> was to pursue a native proxy endpoint approach in the functionfs driver
> itself.
>
> This patchset introduces the `FUNCTIONFS_RW_PROXY_EPS` flag to functionfs
> which, when set, creates an additional proxy file for reading or writing
> to a pair of endpoints. In an attempt to limit the change to the UAPI
> surface, we added several constraints to this proxy file. We chose not to
> handle ioctls on this proxy file, as the current ioctls do not have a
> directionality associated with them, and would require essentially
> creating duplicate ioctls with a direction argument. To use this flag, an
> even number of in/out endpoints must be created, and a proxy ep is created
> for each pair of endpoints in the descriptors.
>
> With this new r/w proxy ep, we are able to transparently hand it to the
> end application. However, to match the legacy driver’s ZLP functionality,
> a new ioctl is added, `FUNCTIONFS_ENDPOINT_ENABLE_ZLP`. This allows the
> userspace functionfs daemon to write the necessary descriptors, configure
> the auto ZLP functionality on the IN EP, then handoff the proxy ep to the
> application. When enabled, functionfs sets the req->zero flag. The UDC
> driver then automatically adds the ZLP if the transaction length % max
> packet size is 0.
>
> An addition, a patch is included which fixes an issue if certain ioctls
> (like the new `FUNCTIONFS_ENDPOINT_ENABLE_ZLP` or
> `FUNCTIONFS_DMABUF_ATTACH`) are called prior to the host being connected.
>
> This patchset has been tested on a kernel based on 7.1-rc7, as well as a
> backported version tested on 6.1. Existing functionfs implementations
> continue to work without modification, and the new functionality passes
> tests designed for our legacy kernel driver implementation.
Very cool, thanks for these changes. I'll look at them after -rc1 is
out!
greg k-h
^ permalink raw reply [flat|nested] 7+ messages in thread