[PATCH AUTOSEL 6.19-5.10] media: dvb-core: dmxdevfilter must always flush bufs

Sasha Levin
2026-02-14T01:07:02Z

From: Hans Verkuil <hverkuil@xs4all.nl>

[ Upstream commit c4e620eccbef76aa5564ebb295e23d6540e27215 ]

Currently the buffers are being filled until full, which works fine
for the transport stream, but not when reading sections, those have
to be returned to userspace immediately, otherwise dvbv5-scan will
just wait forever.

Add a 'flush' argument to dvb_vb2_fill_buffer to indicate whether
the buffer must be flushed or wait until it is full.

Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## Analysis

### 1. Commit Message Analysis

The commit clearly describes a functional bug: DVB section data is
buffered until the VB2 buffer is completely full, but sections are
discrete units that must be delivered immediately. The result is that
`dvbv5-scan` (a standard DVB channel scanning utility from v4l-utils)
**hangs forever** when using the VB2 mmap path.

### 2. Code Change Analysis

The bug is in `dvb_vb2_fill_buffer()` at line 309 of `dvb_vb2.c`:

```309:309:drivers/media/dvb-core/dvb_vb2.c
if (ctx->nonblocking && ctx->buf) {
```

This condition flushes partially-filled buffers only when the file
descriptor was opened with `O_NONBLOCK`. The `nonblocking` field is set
during `dvb_vb2_init()` from `file->f_flags & O_NONBLOCK`:

```819:820:drivers/media/dvb-core/dmxdev.c
dvb_vb2_init(&dmxdevfilter->vb2_ctx, "demux_filter",
file->f_flags & O_NONBLOCK);
```

**The fundamental problem**: The decision to flush a partially-filled
buffer should depend on the **type of data** (section vs. transport
stream), not on the **blocking mode** of the file descriptor. DVB
sections are discrete protocol data units that must be returned to
userspace immediately. Transport streams are continuous, so waiting
until the buffer is full is correct.

**Bug mechanism**:
1. Application opens demux device in blocking mode (normal, no
`O_NONBLOCK`)
2. `ctx->nonblocking` is set to 0
3. Section callback delivers a complete section to
`dvb_vb2_fill_buffer()`
4. Section data is smaller than the full VB2 buffer, so `ctx->remain >
0`
5. The `if (ctx->nonblocking && ctx->buf)` check fails (nonblocking=0)
6. Buffer is NOT flushed to userspace
7. Application blocks forever waiting for data that's already been
received

**The fix** adds a `bool flush` parameter to `dvb_vb2_fill_buffer()`:
- `dvb_dmxdev_section_callback()` passes `flush=true` (sections: flush
immediately)
- `dvb_dmxdev_ts_callback()` passes `flush=false` (transport stream:
wait until full)
- The condition becomes `if (flush && ctx->buf)` - semantically correct

The `nonblocking` field is NOT removed - it's still used in
`dvb_vb2_dqbuf()` (line 415) for its legitimate purpose of controlling
blocking dequeue behavior.

### 3. Classification

This is a **clear bug fix** - it fixes a condition where a standard
userspace application (`dvbv5-scan`) hangs indefinitely. The existing
behavior was simply wrong: flushing decisions were tied to an unrelated
flag.

### 4. Scope and Risk Assessment

- **Files changed**: 3 (dmxdev.c, dvb_vb2.c, dvb_vb2.h)
- **Lines changed**: ~15 (very small)
- **Only 2 callers** of `dvb_vb2_fill_buffer()`, both updated
- **Internal API only** - no userspace-visible changes
- **TS path unchanged** - `flush=false` preserves existing behavior for
transport streams
- **Section path fixed** - `flush=true` ensures proper delivery
- **CONFIG_DVB_MMAP only** - behind experimental Kconfig (default N),
limiting blast radius
- **Risk**: Very low - the change cannot regress TS behavior, and
section behavior goes from "broken" to "working"

### 5. User Impact

For users who have `CONFIG_DVB_MMAP` enabled and use `dvbv5-scan` or
similar section-reading applications via the VB2 path in blocking mode,
this is a **complete showstopper** - the application hangs forever.
Without this fix, the VB2 mmap path is fundamentally broken for section
data.

### 6. Stability Indicators

- **Author**: Hans Verkuil - V4L2/DVB subsystem maintainer, extremely
well-known and trusted
- **Sign-off**: Mauro Carvalho Chehab - overall media subsystem
maintainer
- **Self-contained**: No dependencies on other patches
- **Bug exists since**: The DVB VB2 feature was introduced in kernel
4.20 (commit 57868acc369a), and this bug has existed since then

### 7. Dependency Check

The patch applies cleanly to the current tree. There are no dependencies
on other uncommitted patches. All callers are updated within the same
patch.

### Conclusion

This commit fixes a real, user-impacting bug where DVB section data is
never delivered to userspace through the VB2 mmap path when the file
descriptor is opened in blocking mode. The fix is small, self-contained,
obviously correct, and authored by the subsystem maintainer. The risk of
regression is minimal since the TS path behavior is preserved unchanged
and the section path goes from "broken" to "correct." While it only
affects `CONFIG_DVB_MMAP` (experimental), for users who enable it, the
bug is a complete showstopper.

**YES**

drivers/media/dvb-core/dmxdev.c | 8 ++++----
drivers/media/dvb-core/dvb_vb2.c | 5 +++--
include/media/dvb_vb2.h | 6 ++++--
3 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/drivers/media/dvb-core/dmxdev.c b/drivers/media/dvb-core/dmxdev.c
index 8c6f5aafda1d6..17184b3674904 100644
--- a/drivers/media/dvb-core/dmxdev.c
+++ b/drivers/media/dvb-core/dmxdev.c
@@ -397,11 +397,11 @@ static int dvb_dmxdev_section_callback(const u8 *buffer1, size_t buffer1_len,
if (dvb_vb2_is_streaming(&dmxdevfilter->vb2_ctx)) {
ret = dvb_vb2_fill_buffer(&dmxdevfilter->vb2_ctx,
buffer1, buffer1_len,
- buffer_flags);
+ buffer_flags, true);
if (ret == buffer1_len)
ret = dvb_vb2_fill_buffer(&dmxdevfilter->vb2_ctx,
buffer2, buffer2_len,
- buffer_flags);
+ buffer_flags, true);
} else {
ret = dvb_dmxdev_buffer_write(&dmxdevfilter->buffer,
buffer1, buffer1_len);
@@ -452,10 +452,10 @@ static int dvb_dmxdev_ts_callback(const u8 *buffer1, size_t buffer1_len,

if (dvb_vb2_is_streaming(ctx)) {
ret = dvb_vb2_fill_buffer(ctx, buffer1, buffer1_len,
- buffer_flags);
+ buffer_flags, false);
if (ret == buffer1_len)
ret = dvb_vb2_fill_buffer(ctx, buffer2, buffer2_len,
- buffer_flags);
+ buffer_flags, false);
} else {
if (buffer->error) {
spin_unlock(&dmxdevfilter->dev->lock);
diff --git a/drivers/media/dvb-core/dvb_vb2.c b/drivers/media/dvb-core/dvb_vb2.c
index 29edaaff7a5c9..7444bbc2f24d9 100644
--- a/drivers/media/dvb-core/dvb_vb2.c
+++ b/drivers/media/dvb-core/dvb_vb2.c
@@ -249,7 +249,8 @@ int dvb_vb2_is_streaming(struct dvb_vb2_ctx *ctx)

int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx,
const unsigned char *src, int len,
- enum dmx_buffer_flags *buffer_flags)
+ enum dmx_buffer_flags *buffer_flags,
+ bool flush)
{
unsigned long flags = 0;
void *vbuf = NULL;
@@ -306,7 +307,7 @@ int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx,
}
}

- if (ctx->nonblocking && ctx->buf) {
+ if (flush && ctx->buf) {
vb2_set_plane_payload(&ctx->buf->vb, 0, ll);
vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_DONE);
list_del(&ctx->buf->list);
diff --git a/include/media/dvb_vb2.h b/include/media/dvb_vb2.h
index 8cb88452cd6c2..0fbbfc65157e6 100644
--- a/include/media/dvb_vb2.h
+++ b/include/media/dvb_vb2.h
@@ -124,7 +124,7 @@ static inline int dvb_vb2_release(struct dvb_vb2_ctx *ctx)
return 0;
};
#define dvb_vb2_is_streaming(ctx) (0)
-#define dvb_vb2_fill_buffer(ctx, file, wait, flags) (0)
+#define dvb_vb2_fill_buffer(ctx, file, wait, flags, flush) (0)

static inline __poll_t dvb_vb2_poll(struct dvb_vb2_ctx *ctx,
struct file *file,
@@ -166,10 +166,12 @@ int dvb_vb2_is_streaming(struct dvb_vb2_ctx *ctx);
* @buffer_flags:
* pointer to buffer flags as defined by &enum dmx_buffer_flags.
* can be NULL.
+ * @flush: flush the buffer, even if it isn't full.
*/
int dvb_vb2_fill_buffer(struct dvb_vb2_ctx *ctx,
const unsigned char *src, int len,
- enum dmx_buffer_flags *buffer_flags);
+ enum dmx_buffer_flags *buffer_flags,
+ bool flush);

/**
* dvb_vb2_poll - Wrapper to vb2_core_streamon() for Digital TV
--
2.51.0