diff options
Diffstat (limited to 'drivers/media/v4l2-core')
| -rw-r--r-- | drivers/media/v4l2-core/Kconfig | 4 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/Makefile | 1 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-common.c | 29 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-ctrls-core.c | 131 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-ioctl.c | 2 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-isp.c | 132 | ||||
| -rw-r--r-- | drivers/media/v4l2-core/v4l2-mem2mem.c | 21 |
7 files changed, 277 insertions, 43 deletions
diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig index 331b8e535e5b..d50ccac9733c 100644 --- a/drivers/media/v4l2-core/Kconfig +++ b/drivers/media/v4l2-core/Kconfig @@ -82,3 +82,7 @@ config V4L2_CCI_I2C depends on I2C select REGMAP_I2C select V4L2_CCI + +config V4L2_ISP + tristate + depends on VIDEOBUF2_CORE diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile index 2177b9d63a8f..329f0eadce99 100644 --- a/drivers/media/v4l2-core/Makefile +++ b/drivers/media/v4l2-core/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_V4L2_CCI) += v4l2-cci.o obj-$(CONFIG_V4L2_FLASH_LED_CLASS) += v4l2-flash-led-class.o obj-$(CONFIG_V4L2_FWNODE) += v4l2-fwnode.o obj-$(CONFIG_V4L2_H264) += v4l2-h264.o +obj-$(CONFIG_V4L2_ISP) += v4l2-isp.o obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o obj-$(CONFIG_V4L2_VP9) += v4l2-vp9.o diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index b367d479d6b3..554c591e1113 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -573,6 +573,35 @@ s64 v4l2_get_link_freq(const struct media_pad *pad, unsigned int mul, return v4l2_get_link_freq_ctrl(sd->ctrl_handler, mul, div); } EXPORT_SYMBOL_GPL(v4l2_get_link_freq); + +int v4l2_get_active_data_lanes(const struct media_pad *pad, + unsigned int max_data_lanes) +{ + struct v4l2_mbus_config mbus_config = {}; + struct v4l2_subdev *sd; + unsigned int lanes; + int ret; + + sd = media_entity_to_v4l2_subdev(pad->entity); + ret = v4l2_subdev_call(sd, pad, get_mbus_config, pad->index, + &mbus_config); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + /* This relies on the mbus_config being zeroed at init time */ + lanes = mbus_config.bus.mipi_csi2.num_data_lanes; + if (!lanes) + return max_data_lanes; + + if (lanes > max_data_lanes) { + dev_dbg(sd->dev, "Active data lanes (%u) exceeds max (%u)\n", + lanes, max_data_lanes); + return -EINVAL; + } + + return lanes; +} +EXPORT_SYMBOL_GPL(v4l2_get_active_data_lanes); #endif /* diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 85d07ef44f62..209bc05883bb 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -160,7 +160,13 @@ static void std_init_compound(const struct v4l2_ctrl *ctrl, u32 idx, break; case V4L2_CTRL_TYPE_AV1_SEQUENCE: p_av1_sequence = p; + /* + * The initial profile is 0 which only allows YUV 420 subsampled + * data. Set the subsampling flags accordingly. + */ p_av1_sequence->bit_depth = 8; + p_av1_sequence->flags |= V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_X | + V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_Y; break; case V4L2_CTRL_TYPE_FWHT_PARAMS: p_fwht_params = p; @@ -827,39 +833,114 @@ static int validate_av1_frame(struct v4l2_ctrl_av1_frame *f) return 0; } +/** + * validate_av1_sequence - validate AV1 sequence header fields + * @s: control struct from userspace + * + * Implements AV1 spec §5.5.2 color_config() checks that are + * possible with the current v4l2_ctrl_av1_sequence definition. + * + * TODO: extend validation once additional fields such as + * color_primaries, transfer_characteristics, + * matrix_coefficients, and chroma_sample_position + * are added to the uAPI. + * + * Returns 0 if valid, -EINVAL otherwise. + */ static int validate_av1_sequence(struct v4l2_ctrl_av1_sequence *s) { - if (s->flags & - ~(V4L2_AV1_SEQUENCE_FLAG_STILL_PICTURE | - V4L2_AV1_SEQUENCE_FLAG_USE_128X128_SUPERBLOCK | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_FILTER_INTRA | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_INTRA_EDGE_FILTER | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_INTERINTRA_COMPOUND | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_MASKED_COMPOUND | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_WARPED_MOTION | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_DUAL_FILTER | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_ORDER_HINT | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_JNT_COMP | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_REF_FRAME_MVS | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_SUPERRES | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_CDEF | - V4L2_AV1_SEQUENCE_FLAG_ENABLE_RESTORATION | - V4L2_AV1_SEQUENCE_FLAG_MONO_CHROME | - V4L2_AV1_SEQUENCE_FLAG_COLOR_RANGE | - V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_X | - V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_Y | - V4L2_AV1_SEQUENCE_FLAG_FILM_GRAIN_PARAMS_PRESENT | - V4L2_AV1_SEQUENCE_FLAG_SEPARATE_UV_DELTA_Q)) - return -EINVAL; + const bool mono = s->flags & V4L2_AV1_SEQUENCE_FLAG_MONO_CHROME; + const bool sx = s->flags & V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_X; + const bool sy = s->flags & V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_Y; + const bool uv_dq = s->flags & V4L2_AV1_SEQUENCE_FLAG_SEPARATE_UV_DELTA_Q; - if (s->seq_profile == 1 && s->flags & V4L2_AV1_SEQUENCE_FLAG_MONO_CHROME) + /* 1. Reject unknown flags */ + if (s->flags & + ~(V4L2_AV1_SEQUENCE_FLAG_STILL_PICTURE | + V4L2_AV1_SEQUENCE_FLAG_USE_128X128_SUPERBLOCK | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_FILTER_INTRA | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_INTRA_EDGE_FILTER | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_INTERINTRA_COMPOUND | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_MASKED_COMPOUND | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_WARPED_MOTION | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_DUAL_FILTER | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_ORDER_HINT | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_JNT_COMP | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_REF_FRAME_MVS | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_SUPERRES | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_CDEF | + V4L2_AV1_SEQUENCE_FLAG_ENABLE_RESTORATION | + V4L2_AV1_SEQUENCE_FLAG_MONO_CHROME | + V4L2_AV1_SEQUENCE_FLAG_COLOR_RANGE | + V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_X | + V4L2_AV1_SEQUENCE_FLAG_SUBSAMPLING_Y | + V4L2_AV1_SEQUENCE_FLAG_FILM_GRAIN_PARAMS_PRESENT | + V4L2_AV1_SEQUENCE_FLAG_SEPARATE_UV_DELTA_Q)) return -EINVAL; - /* reserved */ + /* 2. Profile range */ if (s->seq_profile > 2) return -EINVAL; - /* TODO: PROFILES */ + /* 3. Monochrome shortcut */ + if (mono) { + /* Profile 1 forbids monochrome */ + if (s->seq_profile == 1) + return -EINVAL; + + /* Mono → subsampling must look like 4:0:0: sx=1, sy=1 */ + if (!sx || !sy) + return -EINVAL; + + /* separate_uv_delta_q must be 0 */ + if (uv_dq) + return -EINVAL; + + return 0; + } + + /* 4. Profile-specific rules */ + switch (s->seq_profile) { + case 0: + /* Profile 0: only 8/10-bit, subsampling=4:2:0 (sx=1, sy=1) */ + if (s->bit_depth != 8 && s->bit_depth != 10) + return -EINVAL; + if (!(sx && sy)) + return -EINVAL; + break; + + case 1: + /* Profile 1: only 8/10-bit, subsampling=4:4:4 (sx=0, sy=0) */ + if (s->bit_depth != 8 && s->bit_depth != 10) + return -EINVAL; + if (sx || sy) + return -EINVAL; + break; + + case 2: + /* Profile 2: 8/10/12-bit allowed */ + if (s->bit_depth != 8 && s->bit_depth != 10 && + s->bit_depth != 12) + return -EINVAL; + + if (s->bit_depth == 12) { + if (!sx) { + /* 4:4:4 → sy must be 0 */ + if (sy) + return -EINVAL; + } else { + /* sx=1 → sy=0 (4:2:2) or sy=1 (4:2:0) */ + if (sy != 0 && sy != 1) + return -EINVAL; + } + } else { + /* 8/10-bit → only 4:2:2 allowed (sx=1, sy=0) */ + if (!(sx && !sy)) + return -EINVAL; + } + break; + } + return 0; } diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 01cf52c3ea33..98512ea4cc5b 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1469,6 +1469,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_RK_ISP1_EXT_PARAMS: descr = "Rockchip ISP1 Ext 3A Params"; break; case V4L2_META_FMT_C3ISP_PARAMS: descr = "Amlogic C3 ISP Parameters"; break; case V4L2_META_FMT_C3ISP_STATS: descr = "Amlogic C3 ISP Statistics"; break; + case V4L2_META_FMT_MALI_C55_PARAMS: descr = "ARM Mali-C55 ISP Parameters"; break; + case V4L2_META_FMT_MALI_C55_STATS: descr = "ARM Mali-C55 ISP 3A Statistics"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; diff --git a/drivers/media/v4l2-core/v4l2-isp.c b/drivers/media/v4l2-core/v4l2-isp.c new file mode 100644 index 000000000000..29831f7032e9 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-isp.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Video4Linux2 generic ISP parameters and statistics support + * + * Copyright (C) 2025 Ideas On Board Oy + * Author: Jacopo Mondi <jacopo.mondi@ideasonboard.com> + */ + +#include <media/v4l2-isp.h> + +#include <linux/bitops.h> +#include <linux/device.h> + +#include <media/videobuf2-core.h> + +int v4l2_isp_params_validate_buffer_size(struct device *dev, + struct vb2_buffer *vb, + size_t max_size) +{ + size_t header_size = offsetof(struct v4l2_isp_params_buffer, data); + size_t payload_size = vb2_get_plane_payload(vb, 0); + + /* Payload size can't be greater than the destination buffer size */ + if (payload_size > max_size) { + dev_dbg(dev, "Payload size is too large: %zu\n", payload_size); + return -EINVAL; + } + + /* Payload size can't be smaller than the header size */ + if (payload_size < header_size) { + dev_dbg(dev, "Payload size is too small: %zu\n", payload_size); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_isp_params_validate_buffer_size); + +int v4l2_isp_params_validate_buffer(struct device *dev, struct vb2_buffer *vb, + const struct v4l2_isp_params_buffer *buffer, + const struct v4l2_isp_params_block_type_info *type_info, + size_t num_block_types) +{ + size_t header_size = offsetof(struct v4l2_isp_params_buffer, data); + size_t payload_size = vb2_get_plane_payload(vb, 0); + size_t block_offset = 0; + size_t buffer_size; + + /* + * Currently only the first version of the V4L2 ISP parameters format is + * supported. We accept both V0 and V1 to support existing drivers + * compatible with V4L2 ISP that use either 0 or 1 as their "first + * version" identifiers. + */ + if (buffer->version != V4L2_ISP_PARAMS_VERSION_V0 && + buffer->version != V4L2_ISP_PARAMS_VERSION_V1) { + dev_dbg(dev, + "Unsupported V4L2 ISP parameters format version: %u\n", + buffer->version); + return -EINVAL; + } + + /* Validate the size reported in the header */ + buffer_size = header_size + buffer->data_size; + if (buffer_size != payload_size) { + dev_dbg(dev, "Data size %zu and payload size %zu are different\n", + buffer_size, payload_size); + return -EINVAL; + } + + /* Walk the list of ISP configuration blocks and validate them. */ + buffer_size = buffer->data_size; + while (buffer_size >= sizeof(struct v4l2_isp_params_block_header)) { + const struct v4l2_isp_params_block_type_info *info; + const struct v4l2_isp_params_block_header *block; + + block = (const struct v4l2_isp_params_block_header *) + (buffer->data + block_offset); + + if (block->type >= num_block_types) { + dev_dbg(dev, + "Invalid block type %u at offset %zu\n", + block->type, block_offset); + return -EINVAL; + } + + if (block->size > buffer_size) { + dev_dbg(dev, "Premature end of parameters data\n"); + return -EINVAL; + } + + /* It's invalid to specify both ENABLE and DISABLE. */ + if ((block->flags & (V4L2_ISP_PARAMS_FL_BLOCK_ENABLE | + V4L2_ISP_PARAMS_FL_BLOCK_DISABLE)) == + (V4L2_ISP_PARAMS_FL_BLOCK_ENABLE | + V4L2_ISP_PARAMS_FL_BLOCK_DISABLE)) { + dev_dbg(dev, "Invalid block flags %x at offset %zu\n", + block->flags, block_offset); + return -EINVAL; + } + + /* + * Match the block reported size against the type info provided + * one, but allow the block to only contain the header in + * case it is going to be disabled. + */ + info = &type_info[block->type]; + if (block->size != info->size && + (!(block->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) || + block->size != sizeof(*block))) { + dev_dbg(dev, + "Invalid block size %u (expected %zu) at offset %zu\n", + block->size, info->size, block_offset); + return -EINVAL; + } + + block_offset += block->size; + buffer_size -= block->size; + } + + if (buffer_size) { + dev_dbg(dev, "Unexpected data after the parameters buffer end\n"); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_isp_params_validate_buffer); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jacopo Mondi <jacopo.mondi@ideasonboard.com"); +MODULE_DESCRIPTION("V4L2 generic ISP parameters and statistics helpers"); diff --git a/drivers/media/v4l2-core/v4l2-mem2mem.c b/drivers/media/v4l2-core/v4l2-mem2mem.c index 21acd9bc8607..fec93c1a9231 100644 --- a/drivers/media/v4l2-core/v4l2-mem2mem.c +++ b/drivers/media/v4l2-core/v4l2-mem2mem.c @@ -123,13 +123,7 @@ static struct v4l2_m2m_queue_ctx *get_queue_ctx(struct v4l2_m2m_ctx *m2m_ctx, struct vb2_queue *v4l2_m2m_get_vq(struct v4l2_m2m_ctx *m2m_ctx, enum v4l2_buf_type type) { - struct v4l2_m2m_queue_ctx *q_ctx; - - q_ctx = get_queue_ctx(m2m_ctx, type); - if (!q_ctx) - return NULL; - - return &q_ctx->q; + return &get_queue_ctx(m2m_ctx, type)->q; } EXPORT_SYMBOL(v4l2_m2m_get_vq); @@ -1285,8 +1279,6 @@ void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx, unsigned long flags; q_ctx = get_queue_ctx(m2m_ctx, vbuf->vb2_buf.vb2_queue->type); - if (!q_ctx) - return; spin_lock_irqsave(&q_ctx->rdy_spinlock, flags); list_add_tail(&b->list, &q_ctx->rdy_queue); @@ -1296,14 +1288,9 @@ void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx, EXPORT_SYMBOL_GPL(v4l2_m2m_buf_queue); void v4l2_m2m_buf_copy_metadata(const struct vb2_v4l2_buffer *out_vb, - struct vb2_v4l2_buffer *cap_vb, - bool copy_frame_flags) + struct vb2_v4l2_buffer *cap_vb) { - u32 mask = V4L2_BUF_FLAG_TIMECODE | V4L2_BUF_FLAG_TSTAMP_SRC_MASK; - - if (copy_frame_flags) - mask |= V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_PFRAME | - V4L2_BUF_FLAG_BFRAME; + const u32 mask = V4L2_BUF_FLAG_TIMECODE | V4L2_BUF_FLAG_TSTAMP_SRC_MASK; cap_vb->vb2_buf.timestamp = out_vb->vb2_buf.timestamp; @@ -1388,8 +1375,6 @@ int v4l2_m2m_ioctl_remove_bufs(struct file *file, void *priv, struct v4l2_fh *fh = file_to_v4l2_fh(file); struct vb2_queue *q = v4l2_m2m_get_vq(fh->m2m_ctx, remove->type); - if (!q) - return -EINVAL; if (q->type != remove->type) return -EINVAL; |
