mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 12:13:51 -04:00
usb: gadget: f_fs: Introduce rw_proxy file descriptors
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 I/O, preventing buffer corruption or races even if userspace mixes transfers across both the rw_proxy and base files while allowing 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> Link: https://patch.msgid.link/20260619040609.4010746-5-nkapron@google.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
c3249c462b
commit
0f0ea552cd
@@ -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.
|
||||
|
||||
@@ -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,12 +221,13 @@ struct ffs_epfile {
|
||||
struct ffs_buffer *read_buffer;
|
||||
#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN))
|
||||
|
||||
char name[5];
|
||||
char name[8];
|
||||
|
||||
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;
|
||||
@@ -978,9 +981,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 +1009,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 +1122,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 +1175,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;
|
||||
@@ -1647,7 +1654,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 +1772,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 +1825,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 +2223,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 +2281,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 +2379,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;
|
||||
|
||||
@@ -2395,6 +2410,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 +3013,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 +3102,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;
|
||||
|
||||
@@ -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() */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user