mbox series

[v5,00/16] Add Arm Mali-C55 Image Signal Processor Driver

Message ID 20240529152858.183799-1-dan.scally@ideasonboard.com
Headers show
Series Add Arm Mali-C55 Image Signal Processor Driver | expand

Message

Dan Scally May 29, 2024, 3:28 p.m. UTC
Hello all

This patchset introduces a driver for Arm's Mali-C55 Image Signal Processor.
The driver uses the V4L2 / media controller API and implements both of the ISP's
capture pipelines allowing a range of output formats plus downscaling and
cropping. The capture pipelines are named "Full resolution" and "Downscale" and
so abbreviated FR and DS throughout the driver.

The driver exposes 4 V4L2 subdevices:

- mali-c55 isp: input data formatting
- mali-c55 tpg: test pattern generator (modeled as a camera sensor entity)
- mali-c55 resizer fr: downscale / crop and format setting for the FR pipe
- mali-c55 resizer ds: downscale / crop and format setting for the DS pipe

Along with 4 V4L2 Video devices:

- mali-c55 fr: Capture device for the full resolution pipe
- mali-c55 ds: Capture device for the downscale pipe
- mali-c55 3a stats: Capture device for statistics to support 3A algorithms
- mali-c55 3a params: Output device for parameter buffers to configure the ISP

Support is implemented in the parameters video device code for many of the ISP'S
hardware blocks, but not yet all of them. The buffer format is (as far as I am
aware anyway) a novel design that we intend to be extensible so that support for
the C55's remaining hardware blocks can be added later.

Patches 1, 4, 5, 6 and 7 have already had 4 versions on the mailing list...I
decided to post the additional work on the driver as extra patches rather than
merge them all into the existing series as it's already a lot of code to review
and I hoped that that might make it a little easier...if I'm wrong and that's
not liked I can just squash them into a much smaller series.

Thanks
Dan

Daniel Scally (15):
  media: uapi: Add MEDIA_BUS_FMT_RGB202020_1X60 format code
  media: uapi: Add 20-bit bayer formats
  media: v4l2-common: Add RAW16 format info
  dt-bindings: media: Add bindings for ARM mali-c55
  media: mali-c55: Add Mali-C55 ISP driver
  media: Documentation: Add Mali-C55 ISP Documentation
  MAINTAINERS: Add entry for mali-c55 driver
  media: Add MALI_C55_3A_STATS meta format
  media: uapi: Add 3a stats buffer for mali-c55
  media: platform: Add mali-c55 3a stats devnode
  media: platform: Fill stats buffer on ISP_START
  Documentation: mali-c55: Add Statistics documentation
  media: uapi: Add parameters structs to mali-c55-config.h
  media: platform: Add mali-c55 parameters video node
  Documentation: mali-c55: Document the mali-c55 parameter setting

Jacopo Mondi (1):
  media: mali-c55: Add image formats for Mali-C55 parameters buffer

 .../admin-guide/media/mali-c55-graph.dot      |  19 +
 Documentation/admin-guide/media/mali-c55.rst  | 406 ++++++++
 .../admin-guide/media/v4l-drivers.rst         |   1 +
 .../bindings/media/arm,mali-c55.yaml          |  66 ++
 .../userspace-api/media/v4l/meta-formats.rst  |   1 +
 .../media/v4l/metafmt-arm-mali-c55.rst        |  87 ++
 .../media/v4l/subdev-formats.rst              | 268 +++++
 MAINTAINERS                                   |  10 +
 drivers/media/platform/Kconfig                |   1 +
 drivers/media/platform/Makefile               |   1 +
 drivers/media/platform/arm/Kconfig            |   5 +
 drivers/media/platform/arm/Makefile           |   2 +
 drivers/media/platform/arm/mali-c55/Kconfig   |  18 +
 drivers/media/platform/arm/mali-c55/Makefile  |  11 +
 .../platform/arm/mali-c55/mali-c55-capture.c  | 951 ++++++++++++++++++
 .../platform/arm/mali-c55/mali-c55-common.h   | 312 ++++++
 .../platform/arm/mali-c55/mali-c55-core.c     | 825 +++++++++++++++
 .../platform/arm/mali-c55/mali-c55-isp.c      | 626 ++++++++++++
 .../platform/arm/mali-c55/mali-c55-params.c   | 615 +++++++++++
 .../arm/mali-c55/mali-c55-registers.h         | 365 +++++++
 .../arm/mali-c55/mali-c55-resizer-coefs.h     | 382 +++++++
 .../platform/arm/mali-c55/mali-c55-resizer.c  | 779 ++++++++++++++
 .../platform/arm/mali-c55/mali-c55-stats.c    | 350 +++++++
 .../platform/arm/mali-c55/mali-c55-tpg.c      | 402 ++++++++
 drivers/media/v4l2-core/v4l2-common.c         |   4 +
 drivers/media/v4l2-core/v4l2-ioctl.c          |   2 +
 include/uapi/linux/media-bus-format.h         |   9 +-
 .../uapi/linux/media/arm/mali-c55-config.h    | 851 ++++++++++++++++
 include/uapi/linux/videodev2.h                |   3 +
 29 files changed, 7370 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/admin-guide/media/mali-c55-graph.dot
 create mode 100644 Documentation/admin-guide/media/mali-c55.rst
 create mode 100644 Documentation/devicetree/bindings/media/arm,mali-c55.yaml
 create mode 100644 Documentation/userspace-api/media/v4l/metafmt-arm-mali-c55.rst
 create mode 100644 drivers/media/platform/arm/Kconfig
 create mode 100644 drivers/media/platform/arm/Makefile
 create mode 100644 drivers/media/platform/arm/mali-c55/Kconfig
 create mode 100644 drivers/media/platform/arm/mali-c55/Makefile
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-capture.c
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-common.h
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-core.c
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-isp.c
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-params.c
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-registers.h
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-resizer-coefs.h
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-stats.c
 create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-tpg.c
 create mode 100644 include/uapi/linux/media/arm/mali-c55-config.h

Comments

Laurent Pinchart May 30, 2024, 10:24 p.m. UTC | #1
Hi Dan,

Thank you for the patch.

On Wed, May 29, 2024 at 04:28:51PM +0100, Daniel Scally wrote:
> Add a header that describes the format of the 3A statistics buffers
> for the mali-c55 ISP.
> 
> Acked-by: Nayden Kanchev  <nayden.kanchev@arm.com>
> Co-developed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
> Changes in v5:
> 
> 	- New patch
> 
>  .../uapi/linux/media/arm/mali-c55-config.h    | 182 ++++++++++++++++++
>  1 file changed, 182 insertions(+)
>  create mode 100644 include/uapi/linux/media/arm/mali-c55-config.h
> 
> diff --git a/include/uapi/linux/media/arm/mali-c55-config.h b/include/uapi/linux/media/arm/mali-c55-config.h
> new file mode 100644
> index 000000000000..8fb89af6c874
> --- /dev/null
> +++ b/include/uapi/linux/media/arm/mali-c55-config.h
> @@ -0,0 +1,182 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * ARM Mali-C55 ISP Driver - Userspace API
> + *
> + * Copyright (C) 2023 Ideas on Board Oy
> + */
> +
> +#ifndef __UAPI_MALI_C55_CONFIG_H
> +#define __UAPI_MALI_C55_CONFIG_H
> +
> +#include <linux/types.h>
> +
> +/*
> + * Frames are split into zones of almost equal width and height - a zone is a
> + * rectangular tile of a frame. The metering blocks within the ISP collect
> + * aggregated statistics per zone. A maximum of 15x15 zones can be configured,
> + * and so the statistics buffer within the hardware is sized to accommodate
> + * that.
> + *
> + * The utilised number of zones is runtime configurable.
> + */
> +#define MALI_C55_MAX_ZONES	225

Maybe

#define MALI_C55_MAX_ZONES	(15 * 15)

to match the comment above ? Or do you think the result of the
multiplication is more important to see at a quick glance ?

> +
> +/**
> + * struct mali_c55_ae_1024bin_hist - Auto Exposure 1024-bin histogram statistics
> + *
> + * @bins:	1024 element array of 16-bit pixel counts.
> + *
> + * The 1024-bin histogram module collects image-global but zone-weighted
> + * intensity distributions of pixels in fixed-width bins. The modules can be
> + * configured into different "plane modes" which affect the contents of the
> + * collected statistics. In plane mode 0, pixel intensities are taken regardless
> + * of colour plane into a single 1024-bin histogram with a bin width of 4. In
> + * plane mode 1, four 256-bin histograms with a bin width of 16 are collected -
> + * one for each CFA colour plane. In plane modes 4, 5, 6 and 7 two 512-bin
> + * histograms with a bin width of 8 are collected - in each mode one of the
> + * colour planes is collected into the first histogram and all the others are
> + * combined into the second. The histograms are stored consecutively in the bins
> + * array.
> + *
> + * The 16-bit pixel counts are stored as a 4-bit exponent in the most
> + * significant bits followed by a 12-bit mantissa. Conversion to a usable
> + * format can be done according to the following pseudo-code::
> + *
> + *	if (e == 0) {
> + *		bin = m * 2;
> + *	} else {
> + *		bin = (m + 4096) x 2^e

x or *, up to you, but pick one :-) Same below.

> + *	}
> + *
> + * where
> + *	e is the exponent value in range 0..15
> + *	m is the mantissa value in range 0..4095
> + *
> + * The pixels used in calculating the statistics can be masked using three
> + * methods:
> + *
> + * 1. Pixels can be skipped in X and Y directions independently.
> + * 2. Minimum/Maximum intensities can be configured
> + * 3. Zones can be differentially weighted, including 0 weighted to mask them
> + *
> + * The data for this histogram can be collected from different tap points in the
> + * ISP depending on configuration - after the white balance or digital gain
> + * blocks, or immediately after the input crossbar.
> + */
> +struct mali_c55_ae_1024bin_hist {
> +	__u16 bins[1024];
> +} __attribute__((packed));
> +
> +/**
> + * struct mali_c55_ae_5bin_hist - Auto Exposure 5-bin histogram statistics
> + *
> + * @hist0:	16-bit normalised pixel count for the 0th intensity bin
> + * @hist1:	16-bit normalised pixel count for the 1st intensity bin
> + * @hist3:	16-bit normalised pixel count for the 3rd intensity bin
> + * @hist4:	16-bit normalised pixel count for the 4th intensity bin
> + *
> + * The ISP generates a 5-bin histogram of normalised pixel counts within bins of
> + * pixel intensity for each of 225 possible zones within a frame. The centre bin
> + * of the histogram for each zone is not available from the hardware and must be
> + * calculated by subtracting the values of hist0, hist1, hist3 and hist4 from
> + * 0xffff as in the following equation:
> + *
> + *	hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4)
> + */
> +struct mali_c55_ae_5bin_hist {
> +	__u16 hist0;
> +	__u16 hist1;
> +	__u16 hist3;
> +	__u16 hist4;
> +} __attribute__((packed));
> +
> +/**
> + * struct mali_c55_awb_average_ratios - Auto White Balance colour ratios
> + *
> + * @avg_rg_gr:	Average R/G or G/R ratio in Q4.8 format.
> + * @avg_bg_br:	Average B/G or B/R ratio in Q4.8 format.
> + * @num_pixels:	The number of pixels used in the AWB calculation
> + *
> + * The ISP calculates and collects average colour ratios for each zone in an
> + * image and stores them in Q4.8 format (the lowest 8 bits are fractional, with
> + * bits [11:8] representing the integer). The exact ratios collected (either
> + * R/G, B/G or G/R, B/R) are configurable through the parameters buffer. The
> + * value of the 4 high bits is undefined.
> + */
> +struct mali_c55_awb_average_ratios {
> +	__u16 avg_rg_gr;
> +	__u16 avg_bg_br;
> +	__u32 num_pixels;
> +} __attribute__((packed));
> +
> +/**
> + * struct mali_c55_af_statistics - Auto Focus edge and intensity statistics
> + *
> + * @intensity_stats:	Packed mantissa and exponent value for pixel intensity
> + * @edge_stats:		Packed mantissa and exponent values for edge intensity
> + *
> + * The ISP collects the squared sum of pixel intensities for each zone within a
> + * configurable Region of Interest on the frame. Additionally, the same data are
> + * collected after being passed through a bandpass filter which removes high and
> + * low frequency components - these are referred to as the edge statistics.
> + *
> + * The intensity and edge statistics for a zone can be used to calculate the
> + * contrast information for a zone
> + *
> + *	C = E2 / I2
> + *
> + * Where I2 is the intensity statistic for a zone and E2 is the edge statistic
> + * for that zone. Optimum focus is reached when C is at its maximum.
> + *
> + * The intensity and edge statistics are stored packed into a non-standard 16
> + * bit floating point format, where the 7 most significant bits represent the
> + * exponent and the 9 least significant bits the mantissa. This format can be
> + * unpacked with the following pseudocode::
> + *
> + *	if (e == 0) {
> + *		x = m;
> + *	} else {
> + *		x = 2^e-1 x (m + 2^9)
> + *	}
> + *
> + * where
> + *	e is the exponent value in range 0..128

If the exponent is stored on 7 bits, isn't this 0..127 ?

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> + *	m is the mantissa value in range 0..511
> + */
> +struct mali_c55_af_statistics {
> +	__u16 intensity_stats;
> +	__u16 edge_stats;
> +} __attribute__((packed));
> +
> +/**
> + * struct mali_c55_stats_buffer - 3A statistics for the mali-c55 ISP
> + *
> + * @ae_1024bin_hist:		1024-bin frame-global pixel intensity histogram
> + * @iridix_1024bin_hist:	Post-Iridix block 1024-bin histogram
> + * @ae_5bin_hists:		5-bin pixel intensity histograms for AEC
> + * @reserved1:			Undefined buffer space
> + * @awb_ratios:			Color balance ratios for Auto White Balance
> + * @reserved2:			Undefined buffer space
> + * @af_statistics:		Pixel intensity statistics for Auto Focus
> + * @reserved3:			Undefined buffer space
> + *
> + * This struct describes the metering statistics space in the Mali-C55 ISP's
> + * hardware in its entirety. The space between each defined area is marked as
> + * "unknown" and may not be 0, but should not be used. The @ae_5bin_hists,
> + * @awb_ratios and @af_statistics members are arrays of statistics per-zone.
> + * The zones are arranged in the array in raster order starting from the top
> + * left corner of the image.
> + */
> +
> +struct mali_c55_stats_buffer {
> +	struct mali_c55_ae_1024bin_hist ae_1024bin_hist;
> +	struct mali_c55_ae_1024bin_hist iridix_1024bin_hist;
> +	struct mali_c55_ae_5bin_hist ae_5bin_hists[MALI_C55_MAX_ZONES];
> +	__u32 reserved1[14];
> +	struct mali_c55_awb_average_ratios awb_ratios[MALI_C55_MAX_ZONES];
> +	__u32 reserved2[14];
> +	struct mali_c55_af_statistics af_statistics[MALI_C55_MAX_ZONES];
> +	__u32 reserved3[15];
> +} __attribute__((packed));
> +
> +#endif /* __UAPI_MALI_C55_CONFIG_H */
Laurent Pinchart May 30, 2024, 10:44 p.m. UTC | #2
Hi Dan,

Thank you for the patch.

On Wed, May 29, 2024 at 04:28:55PM +0100, Daniel Scally wrote:
> From: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> 
> Add a new V4L2 meta format code for the Mali-C55 parameters

s/$/./

> Acked-by: Nayden Kanchev  <nayden.kanchev@arm.com>
> Co-developed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> ---
> Changes in v5:
> 
> 	- New patch
> 
>  drivers/media/v4l2-core/v4l2-ioctl.c | 1 +
>  include/uapi/linux/videodev2.h       | 1 +
>  2 files changed, 2 insertions(+)
> 
> diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
> index 0d00b0476762..4e3b5e16571c 100644
> --- a/drivers/media/v4l2-core/v4l2-ioctl.c
> +++ b/drivers/media/v4l2-core/v4l2-ioctl.c
> @@ -1456,6 +1456,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
>  	case V4L2_META_FMT_VIVID:       descr = "Vivid Metadata"; break;
>  	case V4L2_META_FMT_RK_ISP1_PARAMS:	descr = "Rockchip ISP1 3A Parameters"; break;
>  	case V4L2_META_FMT_RK_ISP1_STAT_3A:	descr = "Rockchip ISP1 3A Statistics"; break;
> +	case V4L2_META_FMT_MALI_C55_PARAMS:	descr = "ARM Mali-C55 ISP Parameters"; break;
>  	case V4L2_META_FMT_MALI_C55_3A_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;
> diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
> index 021424fdaf68..e63d5e76d21e 100644
> --- a/include/uapi/linux/videodev2.h
> +++ b/include/uapi/linux/videodev2.h
> @@ -841,6 +841,7 @@ struct v4l2_pix_format {
>  #define V4L2_META_FMT_RK_ISP1_PARAMS	v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 3A Parameters */
>  #define V4L2_META_FMT_RK_ISP1_STAT_3A	v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */
>  
> +#define V4L2_META_FMT_MALI_C55_PARAMS	v4l2_fourcc('C', '5', '5', 'P') /* ARM Mali-C55 Parameters */
>  #define V4L2_META_FMT_MALI_C55_3A_STATS	v4l2_fourcc('C', '5', '5', 'S') /* ARM Mali-C55 3A Statistics */
>  
>  #ifdef __KERNEL__
Sakari Ailus June 14, 2024, 6:53 p.m. UTC | #3
Hi Jacopo, Dan,

Thanks for the patch. Please see my comments below.

On Wed, May 29, 2024 at 04:28:57PM +0100, Daniel Scally wrote:
> Add a new code file to the mali-c55 driver that registers an output
> video node for userspace to queue buffers of parameters to. Handlers
> are included to program the statistics generation plus the white
> balance, black level correction and mesh shading correction blocks.
> 
> Update the rest of the driver to register and link the new video node
> 
> Acked-by: Nayden Kanchev  <nayden.kanchev@arm.com>
> Co-developed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
> Changes in v5:
> 
> 	- New patch
> 
>  drivers/media/platform/arm/mali-c55/Makefile  |   1 +
>  .../platform/arm/mali-c55/mali-c55-common.h   |  18 +
>  .../platform/arm/mali-c55/mali-c55-core.c     |  24 +
>  .../platform/arm/mali-c55/mali-c55-isp.c      |  16 +-
>  .../platform/arm/mali-c55/mali-c55-params.c   | 615 ++++++++++++++++++
>  .../arm/mali-c55/mali-c55-registers.h         | 104 +++
>  6 files changed, 777 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-params.c
> 
> diff --git a/drivers/media/platform/arm/mali-c55/Makefile b/drivers/media/platform/arm/mali-c55/Makefile
> index cd5a64bf0c62..b2443f2d416a 100644
> --- a/drivers/media/platform/arm/mali-c55/Makefile
> +++ b/drivers/media/platform/arm/mali-c55/Makefile
> @@ -5,6 +5,7 @@ mali-c55-y := mali-c55-capture.o \
>  	      mali-c55-isp.o \
>  	      mali-c55-tpg.o \
>  	      mali-c55-resizer.o \
> +	      mali-c55-params.o \
>  	      mali-c55-stats.o
>  
>  obj-$(CONFIG_VIDEO_MALI_C55) += mali-c55.o
> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-common.h b/drivers/media/platform/arm/mali-c55/mali-c55-common.h
> index 44119e04009b..565d98acfcdd 100644
> --- a/drivers/media/platform/arm/mali-c55/mali-c55-common.h
> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-common.h
> @@ -80,6 +80,7 @@ enum mali_c55_isp_pads {
>  	MALI_C55_ISP_PAD_SOURCE,
>  	MALI_C55_ISP_PAD_SOURCE_BYPASS,
>  	MALI_C55_ISP_PAD_SOURCE_3A,
> +	MALI_C55_ISP_PAD_SINK_PARAMS,
>  	MALI_C55_ISP_NUM_PADS,
>  };
>  
> @@ -217,6 +218,19 @@ struct mali_c55_stats {
>  	} buffers;
>  };
>  
> +struct mali_c55_params {
> +	struct mali_c55 *mali_c55;
> +	struct video_device vdev;
> +	struct vb2_queue queue;
> +	struct media_pad pad;
> +	struct mutex lock;
> +
> +	struct {
> +		spinlock_t lock;
> +		struct list_head queue;
> +	} buffers;
> +};
> +
>  enum mali_c55_config_spaces {
>  	MALI_C55_CONFIG_PING,
>  	MALI_C55_CONFIG_PONG,
> @@ -247,6 +261,7 @@ struct mali_c55 {
>  	struct mali_c55_isp isp;
>  	struct mali_c55_resizer resizers[MALI_C55_NUM_RZRS];
>  	struct mali_c55_cap_dev cap_devs[MALI_C55_NUM_CAP_DEVS];
> +	struct mali_c55_params params;
>  	struct mali_c55_stats stats;
>  
>  	struct list_head contexts;
> @@ -271,6 +286,8 @@ int mali_c55_register_capture_devs(struct mali_c55 *mali_c55);
>  void mali_c55_unregister_capture_devs(struct mali_c55 *mali_c55);
>  int mali_c55_register_stats(struct mali_c55 *mali_c55);
>  void mali_c55_unregister_stats(struct mali_c55 *mali_c55);
> +int mali_c55_register_params(struct mali_c55 *mali_c55);
> +void mali_c55_unregister_params(struct mali_c55 *mali_c55);
>  struct mali_c55_ctx *mali_c55_get_active_context(struct mali_c55 *mali_c55);
>  void mali_c55_set_plane_done(struct mali_c55_cap_dev *cap_dev,
>  			     enum mali_c55_planes plane);
> @@ -290,5 +307,6 @@ bool mali_c55_isp_is_format_supported(unsigned int mbus_code);
>  	for ((fmt) = NULL; ((fmt) = mali_c55_isp_fmt_next((fmt)));)
>  void mali_c55_stats_fill_buffer(struct mali_c55 *mali_c55,
>  				enum mali_c55_config_spaces cfg_space);
> +void mali_c55_params_write_config(struct mali_c55 *mali_c55);
>  
>  #endif /* _MALI_C55_COMMON_H */
> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-core.c b/drivers/media/platform/arm/mali-c55/mali-c55-core.c
> index 2cf8b1169604..6acee3edd03f 100644
> --- a/drivers/media/platform/arm/mali-c55/mali-c55-core.c
> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-core.c
> @@ -347,6 +347,17 @@ static int mali_c55_create_links(struct mali_c55 *mali_c55)
>  		goto err_remove_links;
>  	}
>  
> +	ret = media_create_pad_link(&mali_c55->params.vdev.entity, 0,
> +				    &mali_c55->isp.sd.entity,
> +				    MALI_C55_ISP_PAD_SINK_PARAMS,
> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret) {
> +		dev_err(mali_c55->dev,
> +			"failed to link ISP and parameters video node\n");
> +		goto err_remove_links;
> +	}
> +
>  	return 0;
>  
>  err_remove_links:
> @@ -360,6 +371,7 @@ static void mali_c55_unregister_entities(struct mali_c55 *mali_c55)
>  	mali_c55_unregister_isp(mali_c55);
>  	mali_c55_unregister_resizers(mali_c55);
>  	mali_c55_unregister_capture_devs(mali_c55);
> +	mali_c55_unregister_params(mali_c55);
>  	mali_c55_unregister_stats(mali_c55);
>  }
>  
> @@ -383,6 +395,10 @@ static int mali_c55_register_entities(struct mali_c55 *mali_c55)
>  	if (ret)
>  		goto err_unregister_entities;
>  
> +	ret = mali_c55_register_params(mali_c55);
> +	if (ret)
> +		goto err_unregister_entities;
> +
>  	ret = mali_c55_register_stats(mali_c55);
>  	if (ret)
>  		goto err_unregister_entities;
> @@ -474,6 +490,14 @@ static irqreturn_t mali_c55_isr(int irq, void *context)
>  			curr_config >>= ffs(MALI_C55_REG_PING_PONG_READ_MASK) - 1;
>  			next_config = curr_config ^ 1;
>  
> +			/*
> +			 * Write the configuration parameters received from
> +			 * userspace into the configuration buffer, which will
> +			 * be transferred to the 'next' active config space at
> +			 * by mali_c55_swap_next_config().
> +			 */
> +			mali_c55_params_write_config(mali_c55);
> +
>  			/*
>  			 * The ordering of these two is currently important as
>  			 * mali_c55_stats_fill_buffer() is asynchronous whereas
> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-isp.c b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
> index 94876fba3353..8c2b45bfd82d 100644
> --- a/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
> @@ -146,6 +146,7 @@ static int mali_c55_isp_start(struct mali_c55 *mali_c55)
>  			     cfg->encoding == V4L2_PIXEL_ENC_RGB ?
>  			     MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK : 0x00);
>  
> +	mali_c55_params_write_config(mali_c55);
>  	ret = mali_c55_config_write(ctx, MALI_C55_CONFIG_PING);
>  	if (ret) {
>  		dev_err(mali_c55->dev, "failed to DMA config\n");
> @@ -455,8 +456,20 @@ static const struct v4l2_subdev_internal_ops mali_c55_isp_internal_ops = {
>  	.init_state = mali_c55_isp_init_state,
>  };
>  
> +static int mali_c55_subdev_link_validate(struct media_link *link)
> +{
> +	/*
> +	 * Skip validation for the parameters sink pad, as the source is not
> +	 * a subdevice.
> +	 */
> +	if (link->sink->index == MALI_C55_ISP_PAD_SINK_PARAMS)
> +		return 0;
> +
> +	return v4l2_subdev_link_validate(link);
> +}
> +
>  static const struct media_entity_operations mali_c55_isp_media_ops = {
> -	.link_validate		= v4l2_subdev_link_validate,
> +	.link_validate		= mali_c55_subdev_link_validate,
>  };
>  
>  static int mali_c55_isp_notifier_bound(struct v4l2_async_notifier *notifier,
> @@ -565,6 +578,7 @@ int mali_c55_register_isp(struct mali_c55 *mali_c55)
>  	isp->pads[MALI_C55_ISP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>  	isp->pads[MALI_C55_ISP_PAD_SOURCE_BYPASS].flags = MEDIA_PAD_FL_SOURCE;
>  	isp->pads[MALI_C55_ISP_PAD_SOURCE_3A].flags = MEDIA_PAD_FL_SOURCE;
> +	isp->pads[MALI_C55_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>  
>  	ret = media_entity_pads_init(&sd->entity, MALI_C55_ISP_NUM_PADS,
>  				     isp->pads);
> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-params.c b/drivers/media/platform/arm/mali-c55/mali-c55-params.c
> new file mode 100644
> index 000000000000..049a7b8e4861
> --- /dev/null
> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-params.c
> @@ -0,0 +1,615 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ARM Mali-C55 ISP Driver - Configuration parameters output device
> + *
> + * Copyright (C) 2024 Ideas on Board Oy
> + */
> +#include <linux/media/arm/mali-c55-config.h>
> +
> +#include <media/media-entity.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-fh.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "mali-c55-common.h"
> +#include "mali-c55-registers.h"
> +
> +typedef void (*mali_c55_block_handler)(struct mali_c55 *mali_c55,

You can wrap after the return type (including typedef). Same elsewhere.

> +				       struct mali_c55_params_block_header *block);
> +
> +struct mali_c55_block_handler {
> +	size_t size;
> +	mali_c55_block_handler handler;
> +};
> +
> +static void mali_c55_params_sensor_offs(struct mali_c55 *mali_c55,
> +					struct mali_c55_params_block_header *block)
> +{
> +	struct mali_c55_params_sensor_off_preshading *p =
> +		(struct mali_c55_params_sensor_off_preshading *)block;

I wonder if an union could be used to make this a bit cleaner. You're doing
a lot of casting that I think could be avoided.

> +	__u32 global_offset;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	if (!(p->chan00 || p->chan01 || p->chan10 || p->chan11))
> +		return;
> +
> +	mali_c55_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_00,
> +		       p->chan00 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK);
> +	mali_c55_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_01,
> +		       p->chan01 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK);
> +	mali_c55_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_10,
> +		       p->chan10 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK);
> +	mali_c55_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_11,
> +		       p->chan11 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK);
> +
> +	/*
> +	 * The average offset is applied as a global offset for the digital
> +	 * gain block
> +	 */
> +	global_offset = (p->chan00 + p->chan01 + p->chan10 + p->chan11) >> 2;
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_DIGITAL_GAIN_OFFSET,
> +			     MALI_C55_DIGITAL_GAIN_OFFSET_MASK, global_offset);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_BYPASS_3,
> +			     MALI_C55_REG_BYPASS_3_SENSOR_OFFSET_PRE_SH, 0x00);
> +}
> +
> +static void mali_c55_params_aexp_hist(struct mali_c55 *mali_c55,
> +				struct mali_c55_params_block_header *block)
> +{
> +	u32 disable_mask = block->type == MALI_C55_PARAM_BLOCK_AEXP_HIST ?
> +					  MALI_C55_AEXP_HIST_DISABLE_MASK :
> +					  MALI_C55_AEXP_IHIST_DISABLE_MASK;
> +	u32 base = block->type == MALI_C55_PARAM_BLOCK_AEXP_HIST ?
> +				  MALI_C55_REG_AEXP_HIST_BASE :
> +				  MALI_C55_REG_AEXP_IHIST_BASE;
> +	struct mali_c55_params_aexp_hist *params =
> +		(struct mali_c55_params_aexp_hist *)block;
> +
> +	if (!block->enabled) {
> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
> +				     disable_mask, true);
> +		return;
> +	}
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
> +			     disable_mask, false);
> +
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET,
> +			     MALI_C55_AEXP_HIST_SKIP_X_MASK, params->skip_x);
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET,
> +			     MALI_C55_AEXP_HIST_OFFSET_X_MASK, params->offset_x);
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET,
> +			     MALI_C55_AEXP_HIST_SKIP_Y_MASK, params->skip_y);
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET,
> +			     MALI_C55_AEXP_HIST_OFFSET_Y_MASK, params->offset_y);
> +
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SCALE_OFFSET,
> +			     MALI_C55_AEXP_HIST_SCALE_BOTTOM_MASK, params->scale_bottom);
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SCALE_OFFSET,
> +			     MALI_C55_AEXP_HIST_SCALE_TOP_MASK, params->scale_top);
> +
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_PLANE_MODE_OFFSET,
> +			     MALI_C55_AEXP_HIST_PLANE_MODE_MASK, params->plane_mode);
> +
> +	if (block->type == MALI_C55_PARAM_BLOCK_AEXP_HIST)
> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
> +				     MALI_C55_AEXP_HIST_SWITCH_MASK,
> +				     params->tap_point);
> +}
> +
> +static void
> +mali_c55_params_aexp_hist_weights(struct mali_c55 *mali_c55,
> +				  struct mali_c55_params_block_header *block)
> +{
> +	struct mali_c55_params_aexp_weights *params =
> +		(struct mali_c55_params_aexp_weights *)block;
> +	u32 base;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	base = block->type == MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS ?
> +			      MALI_C55_REG_AEXP_HIST_BASE :
> +			      MALI_C55_REG_AEXP_IHIST_BASE;
> +
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_NODES_USED_OFFSET,
> +			     MALI_C55_AEXP_HIST_NODES_USED_HORIZ_MASK, params->nodes_used_horiz);
> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_NODES_USED_OFFSET,
> +			     MALI_C55_AEXP_HIST_NODES_USED_VERT_MASK, params->nodes_used_vert);
> +
> +	/*
> +	 * The zone weights array is a 225-element array of u8 values, but that
> +	 * is a bit annoying to handle given the ISP expects 32-bit writes. We
> +	 * just reinterpret it as a 57-element array of 32-bit values for the
> +	 * purposes of this transaction (the 3 bytes of additional space at the
> +	 * end of the write is just padding for the array of weights in the ISP
> +	 * memory space anyway, so there's no risk of overwriting other
> +	 * registers).
> +	 */
> +	for (unsigned int i = 0; i < 57; i++) {
> +		u32 val = ((u32 *)params->zone_weights)[i]
> +			    & MALI_C55_AEXP_HIST_ZONE_WEIGHT_MASK;
> +		u32 addr = base + MALI_C55_AEXP_HIST_ZONE_WEIGHTS_OFFSET + (4 * i);
> +
> +		mali_c55_write(mali_c55, addr, val);
> +	}
> +}
> +
> +static void mali_c55_params_digital_gain(struct mali_c55 *mali_c55,
> +					 struct mali_c55_params_block_header *block)
> +{
> +	struct mali_c55_params_digital_gain *dgain =
> +		(struct mali_c55_params_digital_gain *)block;
> +
> +	/*
> +	 * If the block is flagged as disabled we write a gain of 1.0, which in
> +	 * Q5.8 format is 256.
> +	 */
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_DIGITAL_GAIN,
> +			     MALI_C55_DIGITAL_GAIN_MASK,
> +			     block->enabled ? dgain->gain : 256);
> +}
> +
> +static void mali_c55_params_awb_gains(struct mali_c55 *mali_c55,
> +				      struct mali_c55_params_block_header *block)
> +{
> +	struct mali_c55_params_awb_gains *gains =
> +		(struct mali_c55_params_awb_gains *)block;
> +
> +	/*
> +	 * There are two places AWB gains can be set in the ISP; one affects the
> +	 * image output data and the other affects the statistics for the
> +	 * AEXP-0 tap point.
> +	 */
> +	u32 addr1 = block->type = MALI_C55_PARAM_BLOCK_AWB_GAINS ?
> +				  MALI_C55_REG_AWB_GAINS1 :
> +				  MALI_C55_REG_AWB_GAINS1_AEXP;
> +	u32 addr2 = block->type = MALI_C55_PARAM_BLOCK_AWB_GAINS ?
> +				  MALI_C55_REG_AWB_GAINS2 :
> +				  MALI_C55_REG_AWB_GAINS2_AEXP;
> +
> +	mali_c55_update_bits(mali_c55, addr1, MALI_C55_AWB_GAIN00_MASK,
> +			     gains->gain00);
> +	mali_c55_update_bits(mali_c55, addr1, MALI_C55_AWB_GAIN01_MASK,
> +			     gains->gain01);
> +	mali_c55_update_bits(mali_c55, addr2, MALI_C55_AWB_GAIN10_MASK,
> +			     gains->gain10);
> +	mali_c55_update_bits(mali_c55, addr2, MALI_C55_AWB_GAIN11_MASK,
> +			     gains->gain11);
> +}
> +
> +static void mali_c55_params_awb_config(struct mali_c55 *mali_c55,
> +				      struct mali_c55_params_block_header *block)
> +{
> +	struct mali_c55_params_awb_config *params =
> +		(struct mali_c55_params_awb_config *)block;
> +
> +	if (!block->enabled) {
> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
> +				     MALI_C55_AWB_DISABLE_MASK, true);
> +		return;
> +	}
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
> +			     MALI_C55_AWB_DISABLE_MASK, false);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_STATS_MODE,
> +			     MALI_C55_AWB_STATS_MODE_MASK, params->stats_mode);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_WHITE_LEVEL,
> +			     MALI_C55_AWB_WHITE_LEVEL_MASK, params->white_level);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_BLACK_LEVEL,
> +			     MALI_C55_AWB_BLACK_LEVEL_MASK, params->black_level);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CR_MAX,
> +			     MALI_C55_AWB_CR_MAX_MASK, params->cr_max);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CR_MIN,
> +			     MALI_C55_AWB_CR_MIN_MASK, params->cr_min);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CB_MAX,
> +			     MALI_C55_AWB_CB_MAX_MASK, params->cb_max);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CB_MIN,
> +			     MALI_C55_AWB_CB_MIN_MASK, params->cb_min);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_NODES_USED,
> +			     MALI_C55_AWB_NODES_USED_HORIZ_MASK,
> +			     params->nodes_used_horiz);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_NODES_USED,
> +			     MALI_C55_AWB_NODES_USED_VERT_MASK,
> +			     params->nodes_used_vert);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CR_HIGH,
> +			     MALI_C55_AWB_CR_HIGH_MASK, params->cr_high);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CR_LOW,
> +			     MALI_C55_AWB_CR_LOW_MASK, params->cr_low);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CB_HIGH,
> +			     MALI_C55_AWB_CB_HIGH_MASK, params->cb_high);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CB_LOW,
> +			     MALI_C55_AWB_CB_LOW_MASK, params->cb_low);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
> +			     MALI_C55_AWB_SWITCH_MASK, params->tap_point);
> +}
> +
> +static void mali_c55_params_lsc_config(struct mali_c55 *mali_c55,
> +				       struct mali_c55_params_block_header *block)
> +{
> +	struct mali_c55_params_mesh_shading_config *params =
> +		(struct mali_c55_params_mesh_shading_config *)block;
> +	unsigned int i;
> +	u32 addr;
> +
> +	if (!block->enabled) {
> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +				     MALI_C55_MESH_SHADING_ENABLE_MASK, false);
> +		return;
> +	}
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +			     MALI_C55_MESH_SHADING_ENABLE_MASK, true);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +			     MALI_C55_MESH_SHADING_MESH_SHOW, params->mesh_show);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +			     MALI_C55_MESH_SHADING_SCALE_MASK,
> +			     params->mesh_scale);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +			     MALI_C55_MESH_SHADING_PAGE_R_MASK,
> +			     params->mesh_page_r);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +			     MALI_C55_MESH_SHADING_PAGE_G_MASK,
> +			     params->mesh_page_g);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +			     MALI_C55_MESH_SHADING_PAGE_B_MASK,
> +			     params->mesh_page_b);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +			     MALI_C55_MESH_SHADING_MESH_WIDTH_MASK,
> +			     params->mesh_width);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
> +			     MALI_C55_MESH_SHADING_MESH_HEIGHT_MASK,
> +			     params->mesh_height);
> +
> +	for (i = 0; i < MALI_C55_NUM_MESH_SHADING_ELEMENTS; i++) {
> +		addr = MALI_C55_REG_MESH_SHADING_TABLES + (i * 4);
> +		mali_c55_write(mali_c55, addr, params->mesh[i]);
> +	}
> +}
> +
> +static void mali_c55_params_lsc_selection(struct mali_c55 *mali_c55,
> +					  struct mali_c55_params_block_header *block)
> +{
> +	struct mali_c55_params_mesh_shading_selection *params =
> +		(struct mali_c55_params_mesh_shading_selection *)block;
> +
> +	if (!block->enabled)
> +		return;
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK,
> +			     MALI_C55_MESH_SHADING_ALPHA_BANK_R_MASK,
> +			     params->mesh_alpha_bank_r);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK,
> +			     MALI_C55_MESH_SHADING_ALPHA_BANK_G_MASK,
> +			     params->mesh_alpha_bank_g);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK,
> +			     MALI_C55_MESH_SHADING_ALPHA_BANK_B_MASK,
> +			     params->mesh_alpha_bank_b);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA,
> +			     MALI_C55_MESH_SHADING_ALPHA_R_MASK,
> +			     params->mesh_alpha_r);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA,
> +			     MALI_C55_MESH_SHADING_ALPHA_G_MASK,
> +			     params->mesh_alpha_g);
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA,
> +			     MALI_C55_MESH_SHADING_ALPHA_B_MASK,
> +			     params->mesh_alpha_b);
> +
> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_MESH_STRENGTH,
> +			     MALI_c55_MESH_STRENGTH_MASK,
> +			     params->mesh_strength);
> +}
> +
> +static const struct mali_c55_block_handler mali_c55_block_handlers[] = {
> +	[MALI_C55_PARAM_BLOCK_SENSOR_OFFS] = {
> +		.size = sizeof(struct mali_c55_params_sensor_off_preshading),
> +		.handler = &mali_c55_params_sensor_offs,
> +	},
> +	[MALI_C55_PARAM_BLOCK_AEXP_HIST] = {
> +		.size = sizeof(struct mali_c55_params_aexp_hist),
> +		.handler = &mali_c55_params_aexp_hist,
> +	},
> +	[MALI_C55_PARAM_BLOCK_AEXP_IHIST] = {
> +		.size = sizeof(struct mali_c55_params_aexp_hist),
> +		.handler = &mali_c55_params_aexp_hist,
> +	},
> +	[MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS] = {
> +		.size = sizeof(struct mali_c55_params_aexp_weights),
> +		.handler = &mali_c55_params_aexp_hist_weights,
> +	},
> +	[MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS] = {
> +		.size = sizeof(struct mali_c55_params_aexp_weights),
> +		.handler = &mali_c55_params_aexp_hist_weights,
> +	},
> +	[MALI_C55_PARAM_BLOCK_DIGITAL_GAIN] = {
> +		.size = sizeof(struct mali_c55_params_digital_gain),
> +		.handler = &mali_c55_params_digital_gain,
> +	},
> +	[MALI_C55_PARAM_BLOCK_AWB_GAINS] = {
> +		.size = sizeof(struct mali_c55_params_awb_gains),
> +		.handler = &mali_c55_params_awb_gains,
> +	},
> +	[MALI_C55_PARAM_BLOCK_AWB_CONFIG] = {
> +		.size = sizeof(struct mali_c55_params_awb_config),
> +		.handler = &mali_c55_params_awb_config,
> +	},
> +	[MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP] = {
> +		.size = sizeof(struct mali_c55_params_awb_gains),
> +		.handler = &mali_c55_params_awb_gains,
> +	},
> +	[MALI_C55_PARAM_MESH_SHADING_CONFIG] = {
> +		.size = sizeof(struct mali_c55_params_mesh_shading_config),
> +		.handler = &mali_c55_params_lsc_config,
> +	},
> +	[MALI_C55_PARAM_MESH_SHADING_SELECTION] = {
> +		.size = sizeof(struct mali_c55_params_mesh_shading_selection),
> +		.handler = &mali_c55_params_lsc_selection,
> +	},
> +};
> +
> +static int mali_c55_params_enum_fmt_meta_out(struct file *file, void *fh,
> +					    struct v4l2_fmtdesc *f)
> +{
> +	if (f->index || f->type != V4L2_BUF_TYPE_META_OUTPUT)

The buffer type check has been done by the caller already.

> +		return -EINVAL;
> +
> +	f->pixelformat = V4L2_META_FMT_MALI_C55_PARAMS;
> +
> +	return 0;
> +}
> +
> +static int mali_c55_params_g_fmt_meta_out(struct file *file, void *fh,
> +					 struct v4l2_format *f)
> +{
> +	static const struct v4l2_meta_format mfmt = {
> +		.dataformat = V4L2_META_FMT_MALI_C55_PARAMS,
> +		.buffersize = sizeof(struct mali_c55_params_buffer),
> +	};
> +
> +	if (f->type != V4L2_BUF_TYPE_META_OUTPUT)
> +		return -EINVAL;

Ditto.

Maybe check the other instances of format access functions in the driver,
too?

> +
> +	f->fmt.meta = mfmt;
> +
> +	return 0;
> +}
> +
> +static int mali_c55_params_querycap(struct file *file,
> +				   void *priv, struct v4l2_capability *cap)
> +{
> +	strscpy(cap->driver, MALI_C55_DRIVER_NAME, sizeof(cap->driver));
> +	strscpy(cap->card, "ARM Mali-C55 ISP", sizeof(cap->card));
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops mali_c55_params_v4l2_ioctl_ops = {
> +	.vidioc_reqbufs = vb2_ioctl_reqbufs,
> +	.vidioc_querybuf = vb2_ioctl_querybuf,
> +	.vidioc_create_bufs = vb2_ioctl_create_bufs,
> +	.vidioc_qbuf = vb2_ioctl_qbuf,
> +	.vidioc_expbuf = vb2_ioctl_expbuf,
> +	.vidioc_dqbuf = vb2_ioctl_dqbuf,
> +	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> +	.vidioc_streamon = vb2_ioctl_streamon,
> +	.vidioc_streamoff = vb2_ioctl_streamoff,
> +	.vidioc_enum_fmt_meta_out = mali_c55_params_enum_fmt_meta_out,
> +	.vidioc_g_fmt_meta_out = mali_c55_params_g_fmt_meta_out,
> +	.vidioc_s_fmt_meta_out = mali_c55_params_g_fmt_meta_out,
> +	.vidioc_try_fmt_meta_out = mali_c55_params_g_fmt_meta_out,
> +	.vidioc_querycap = mali_c55_params_querycap,
> +	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations mali_c55_params_v4l2_fops = {
> +	.owner = THIS_MODULE,
> +	.unlocked_ioctl = video_ioctl2,
> +	.open = v4l2_fh_open,
> +	.release = vb2_fop_release,
> +	.poll = vb2_fop_poll,
> +	.mmap = vb2_fop_mmap,
> +};
> +
> +static int
> +mali_c55_params_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
> +			   unsigned int *num_planes, unsigned int sizes[],
> +			   struct device *alloc_devs[])
> +{
> +	if (*num_planes && *num_planes > 1)
> +		return -EINVAL;
> +
> +	if (sizes[0] && sizes[0] != sizeof(struct mali_c55_params_buffer))
> +		return -EINVAL;
> +
> +	*num_planes = 1;
> +	sizes[0] = sizeof(struct mali_c55_params_buffer);
> +
> +	return 0;
> +}
> +
> +static void mali_c55_params_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct mali_c55_params *params = vb2_get_drv_priv(vb->vb2_queue);
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +	struct mali_c55_buffer *buf = container_of(vbuf,
> +						   struct mali_c55_buffer, vb);
> +
> +	vb2_set_plane_payload(vb, 0, sizeof(struct mali_c55_params_buffer));
> +
> +	spin_lock(&params->buffers.lock);
> +	list_add_tail(&buf->queue, &params->buffers.queue);
> +	spin_unlock(&params->buffers.lock);
> +}
> +
> +static void mali_c55_params_stop_streaming(struct vb2_queue *q)
> +{
> +	struct mali_c55_params *params = vb2_get_drv_priv(q);
> +	struct mali_c55_buffer *buf, *tmp;
> +
> +	spin_lock(&params->buffers.lock);
> +
> +	list_for_each_entry_safe(buf, tmp, &params->buffers.queue, queue) {
> +		list_del(&buf->queue);
> +		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +	}
> +
> +	spin_unlock(&params->buffers.lock);
> +}
> +
> +static const struct vb2_ops mali_c55_params_vb2_ops = {
> +	.queue_setup = mali_c55_params_queue_setup,
> +	.buf_queue = mali_c55_params_buf_queue,
> +	.wait_prepare = vb2_ops_wait_prepare,
> +	.wait_finish = vb2_ops_wait_finish,
> +	.stop_streaming = mali_c55_params_stop_streaming,
> +};
> +
> +void mali_c55_params_write_config(struct mali_c55 *mali_c55)
> +{
> +	struct mali_c55_params *params = &mali_c55->params;
> +	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
> +	struct mali_c55_params_buffer *config;
> +	struct mali_c55_buffer *buf;
> +	size_t block_offset = 0;
> +
> +	spin_lock(&params->buffers.lock);
> +
> +	buf = list_first_entry_or_null(&params->buffers.queue,
> +				       struct mali_c55_buffer, queue);
> +	if (buf)
> +		list_del(&buf->queue);
> +	spin_unlock(&params->buffers.lock);
> +
> +	if (!buf)
> +		return;
> +
> +	buf->vb.sequence = mali_c55->isp.frame_sequence;
> +	config = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
> +
> +	if (config->total_size > MALI_C55_PARAMS_MAX_SIZE) {
> +		dev_dbg(mali_c55->dev, "Invalid parameters buffer size %lu\n",
> +			config->total_size);
> +		state = VB2_BUF_STATE_ERROR;
> +		goto err_buffer_done;
> +	}
> +
> +	/* Walk the list of parameter blocks and process them. */
> +	while (block_offset < config->total_size) {
> +		const struct mali_c55_block_handler *block_handler;
> +		struct mali_c55_params_block_header *block;
> +
> +		block = (struct mali_c55_params_block_header *)
> +			 &config->data[block_offset];

How do you ensure config->data does hold a full struct
mali_c33_params_block_header at block_offset (i.e. that the struct does not
exceed the memory available for config->data)?

> +
> +		if (block->type >= MALI_C55_PARAM_BLOCK_SENTINEL) {
> +			dev_dbg(mali_c55->dev, "Invalid parameters block type\n");
> +			state = VB2_BUF_STATE_ERROR;
> +			goto err_buffer_done;
> +		}
> +
> +		block_handler = &mali_c55_block_handlers[block->type];
> +		if (block->size != block_handler->size) {

How do you ensure config->data has room for the block?

> +			dev_dbg(mali_c55->dev, "Invalid parameters block size\n");
> +			state = VB2_BUF_STATE_ERROR;
> +			goto err_buffer_done;
> +		}
> +
> +		block_handler->handler(mali_c55, block);
> +
> +		block_offset += block->size;
> +	}
> +
> +err_buffer_done:
> +	vb2_buffer_done(&buf->vb.vb2_buf, state);
> +}
> +
> +void mali_c55_unregister_params(struct mali_c55 *mali_c55)
> +{
> +	struct mali_c55_params *params = &mali_c55->params;
> +
> +	if (!video_is_registered(&params->vdev))
> +		return;
> +
> +	vb2_video_unregister_device(&params->vdev);
> +	media_entity_cleanup(&params->vdev.entity);
> +	mutex_destroy(&params->lock);
> +}
> +
> +int mali_c55_register_params(struct mali_c55 *mali_c55)
> +{
> +	struct mali_c55_params *params = &mali_c55->params;
> +	struct video_device *vdev = &params->vdev;
> +	struct vb2_queue *vb2q = &params->queue;
> +	int ret;
> +
> +	mutex_init(&params->lock);
> +	INIT_LIST_HEAD(&params->buffers.queue);
> +
> +	params->pad.flags = MEDIA_PAD_FL_SOURCE;
> +	ret = media_entity_pads_init(&params->vdev.entity, 1, &params->pad);
> +	if (ret)
> +		goto err_destroy_mutex;
> +
> +	vb2q->type = V4L2_BUF_TYPE_META_OUTPUT;
> +	vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
> +	vb2q->drv_priv = params;
> +	vb2q->mem_ops = &vb2_dma_contig_memops;
> +	vb2q->ops = &mali_c55_params_vb2_ops;
> +	vb2q->buf_struct_size = sizeof(struct mali_c55_buffer);
> +	vb2q->min_queued_buffers = 1;
> +	vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	vb2q->lock = &params->lock;
> +	vb2q->dev = mali_c55->dev;
> +
> +	ret = vb2_queue_init(vb2q);
> +	if (ret) {
> +		dev_err(mali_c55->dev, "params vb2 queue init failed\n");
> +		goto err_cleanup_entity;
> +	}
> +
> +	strscpy(params->vdev.name, "mali-c55 3a params",
> +		sizeof(params->vdev.name));
> +	vdev->release = video_device_release_empty;
> +	vdev->fops = &mali_c55_params_v4l2_fops;
> +	vdev->ioctl_ops = &mali_c55_params_v4l2_ioctl_ops;
> +	vdev->lock = &params->lock;
> +	vdev->v4l2_dev = &mali_c55->v4l2_dev;
> +	vdev->queue = &params->queue;
> +	vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
> +	vdev->vfl_dir = VFL_DIR_TX;
> +	video_set_drvdata(vdev, params);
> +
> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> +	if (ret) {
> +		dev_err(mali_c55->dev,
> +			"failed to register params video device\n");
> +		goto err_release_vb2q;
> +	}
> +
> +	params->mali_c55 = mali_c55;
> +
> +	return 0;
> +
> +err_release_vb2q:
> +	vb2_queue_release(vb2q);
> +err_cleanup_entity:
> +	media_entity_cleanup(&params->vdev.entity);
> +err_destroy_mutex:
> +	mutex_destroy(&params->lock);
> +
> +	return ret;
> +}
> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-registers.h b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
> index eb3719245ec3..8e6a801077ed 100644
> --- a/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
> @@ -119,6 +119,19 @@
>  #define MALI_C55_REG_ACTIVE_HEIGHT_MASK			0xffff0000
>  #define MALI_C55_REG_BAYER_ORDER			0x18e8c
>  #define MALI_C55_BAYER_ORDER_MASK			GENMASK(1, 0)
> +
> +#define MALI_C55_REG_METERING_CONFIG			0x18ed0
> +#define MALI_C55_5BIN_HIST_DISABLE_MASK			BIT(0)
> +#define MALI_C55_5BIN_HIST_SWITCH_MASK			GENMASK(2, 1)
> +#define MALI_C55_AF_DISABLE_MASK			BIT(4)
> +#define MALI_C55_AF_SWITCH_MASK				BIT(5)
> +#define MALI_C55_AWB_DISABLE_MASK			BIT(8)
> +#define MALI_C55_AWB_SWITCH_MASK			BIT(9)
> +#define MALI_C55_AEXP_HIST_DISABLE_MASK			BIT(12)
> +#define MALI_C55_AEXP_HIST_SWITCH_MASK			GENMASK(14, 13)
> +#define MALI_C55_AEXP_IHIST_DISABLE_MASK		BIT(16)
> +#define MALI_C55_AEXP_SRC_MASK				BIT(24)
> +
>  #define MALI_C55_REG_TPG_CH0				0x18ed8
>  #define MALI_C55_TEST_PATTERN_ON_OFF			BIT(0)
>  #define MALI_C55_TEST_PATTERN_RGB_MASK			BIT(1)
> @@ -138,6 +151,11 @@
>  #define MALI_C55_REG_CONFIG_SPACES_OFFSET		0x0ab6c
>  #define MALI_C55_CONFIG_SPACE_SIZE			0x1231c
>  
> +#define MALI_C55_REG_DIGITAL_GAIN			0x1926c
> +#define MALI_C55_DIGITAL_GAIN_MASK			GENMASK(12, 0)
> +#define MALI_C55_REG_DIGITAL_GAIN_OFFSET		0x19270
> +#define MALI_C55_DIGITAL_GAIN_OFFSET_MASK		GENMASK(19, 0)
> +
>  #define MALI_C55_REG_SINTER_CONFIG			0x19348
>  #define MALI_C55_SINTER_VIEW_FILTER_MASK		GENMASK(1, 0)
>  #define MALI_C55_SINTER_SCALE_MODE_MASK			GENMASK(3, 2)
> @@ -146,6 +164,46 @@
>  #define MALI_C55_SINTER_INT_SELECT_MASK			BIT(6)
>  #define MALI_C55_SINTER_RM_ENABLE_MASK			BIT(7)
>  
> +/* Black Level Correction Configuration */
> +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_00		0x1abcc
> +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_01		0x1abd0
> +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_10		0x1abd4
> +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_11		0x1abd8
> +#define MALI_C55_SENSOR_OFF_PRE_SHA_MASK		0xfffff
> +
> +/* Lens Mesh Shading Configuration */
> +#define MALI_C55_REG_MESH_SHADING_TABLES		0x13074
> +#define MALI_C55_REG_MESH_SHADING_CONFIG		0x1abfc
> +#define MALI_C55_MESH_SHADING_ENABLE_MASK		BIT(0)
> +#define MALI_C55_MESH_SHADING_MESH_SHOW			BIT(1)
> +#define MALI_C55_MESH_SHADING_SCALE_MASK		GENMASK(4, 2)
> +#define MALI_C55_MESH_SHADING_PAGE_R_MASK		GENMASK(9, 8)
> +#define MALI_C55_MESH_SHADING_PAGE_G_MASK		GENMASK(11, 10)
> +#define MALI_C55_MESH_SHADING_PAGE_B_MASK		GENMASK(13, 12)
> +#define MALI_C55_MESH_SHADING_MESH_WIDTH_MASK		GENMASK(21, 16)
> +#define MALI_C55_MESH_SHADING_MESH_HEIGHT_MASK		GENMASK(29, 24)
> +
> +#define MALI_C55_REG_MESH_SHADING_ALPHA_BANK		0x1ac04
> +#define MALI_C55_MESH_SHADING_ALPHA_BANK_R_MASK		GENMASK(2, 0)
> +#define MALI_C55_MESH_SHADING_ALPHA_BANK_G_MASK		GENMASK(5, 3)
> +#define MALI_C55_MESH_SHADING_ALPHA_BANK_B_MASK		GENMASK(8, 6)
> +#define MALI_C55_REG_MESH_SHADING_ALPHA			0x1ac08
> +#define MALI_C55_MESH_SHADING_ALPHA_R_MASK		GENMASK(7, 0)
> +#define MALI_C55_MESH_SHADING_ALPHA_G_MASK		GENMASK(15, 8)
> +#define MALI_C55_MESH_SHADING_ALPHA_B_MASK		GENMASK(23, 16)
> +#define MALI_C55_REG_MESH_SHADING_MESH_STRENGTH		0x1ac0c
> +#define MALI_c55_MESH_STRENGTH_MASK			GENMASK(15, 0)
> +
> +/* AWB Gains Configuration */
> +#define MALI_C55_REG_AWB_GAINS1				0x1ac10
> +#define MALI_C55_AWB_GAIN00_MASK			GENMASK(11, 0)
> +#define MALI_C55_AWB_GAIN01_MASK			GENMASK(27, 16)
> +#define MALI_C55_REG_AWB_GAINS2				0x1ac14
> +#define MALI_C55_AWB_GAIN10_MASK			GENMASK(11, 0)
> +#define MALI_C55_AWB_GAIN11_MASK			GENMASK(27, 16)
> +#define MALI_C55_REG_AWB_GAINS1_AEXP			0x1ac18
> +#define MALI_C55_REG_AWB_GAINS2_AEXP			0x1ac1c
> +
>  /* Colour Correction Matrix Configuration */
>  #define MALI_C55_REG_CCM_ENABLE				0x1b07c
>  #define MALI_C55_CCM_ENABLE_MASK			BIT(0)
> @@ -168,6 +226,52 @@
>  #define MALI_C55_REG_CCM_ANTIFOG_OFFSET_B		0x1b0c8
>  #define MALI_C55_CCM_ANTIFOG_OFFSET_MASK		GENMASK(11, 0)
>  
> +/* AWB Statistics Configuration */
> +#define MALI_C55_REG_AWB_STATS_MODE			0x1b29c
> +#define MALI_C55_AWB_STATS_MODE_MASK			BIT(0)
> +#define MALI_C55_REG_AWB_WHITE_LEVEL			0x1b2a0
> +#define MALI_C55_AWB_WHITE_LEVEL_MASK			GENMASK(9, 0)
> +#define MALI_C55_REG_AWB_BLACK_LEVEL			0x1b2a4
> +#define MALI_C55_AWB_BLACK_LEVEL_MASK			GENMASK(9, 0)
> +#define MALI_C55_REG_AWB_CR_MAX				0x1b2a8
> +#define MALI_C55_AWB_CR_MAX_MASK			GENMASK(11, 0)
> +#define MALI_C55_REG_AWB_CR_MIN				0x1b2ac
> +#define MALI_C55_AWB_CR_MIN_MASK			GENMASK(11, 0)
> +#define MALI_C55_REG_AWB_CB_MAX				0x1b2b0
> +#define MALI_C55_REG_AWB_CB_MIN				0x1b2b4
> +#define MALI_C55_AWB_CB_MIN_MASK			GENMASK(11, 0)
> +#define MALI_C55_REG_AWB_NODES_USED			0x1b2c4
> +#define MALI_C55_AWB_NODES_USED_HORIZ_MASK		GENMASK(7, 0)
> +#define MALI_C55_AWB_NODES_USED_VERT_MASK		GENMASK(15, 8)
> +#define MALI_C55_REG_AWB_CR_HIGH			0x1b2c8
> +#define MALI_C55_AWB_CR_HIGH_MASK			GENMASK(11, 0)
> +#define MALI_C55_REG_AWB_CR_LOW				0x1b2cc
> +#define MALI_C55_AWB_CR_LOW_MASK			GENMASK(11, 0)
> +#define MALI_C55_REG_AWB_CB_HIGH			0x1b2d0
> +#define MALI_C55_AWB_CB_HIGH_MASK			GENMASK(11, 0)
> +#define MALI_C55_REG_AWB_CB_LOW				0x1b2d4
> +#define MALI_C55_AWB_CB_LOW_MASK			GENMASK(11, 0)
> +
> +/* AEXP Metering Histogram Configuration */
> +#define MALI_C55_REG_AEXP_HIST_BASE			0x1b730
> +#define MALI_C55_REG_AEXP_IHIST_BASE			0x1bbac
> +#define MALI_C55_AEXP_HIST_SKIP_OFFSET			0
> +#define MALI_C55_AEXP_HIST_SKIP_X_MASK			GENMASK(2, 0)
> +#define MALI_C55_AEXP_HIST_OFFSET_X_MASK		BIT(3)
> +#define MALI_C55_AEXP_HIST_SKIP_Y_MASK			GENMASK(6, 4)
> +#define MALI_C55_AEXP_HIST_OFFSET_Y_MASK		BIT(7)
> +#define MALI_C55_AEXP_HIST_SCALE_OFFSET			4
> +#define MALI_C55_AEXP_HIST_SCALE_BOTTOM_MASK		GENMASK(3, 0)
> +#define MALI_C55_AEXP_HIST_SCALE_TOP_MASK		GENMASK(7, 4)
> +#define MALI_C55_AEXP_HIST_PLANE_MODE_OFFSET		16
> +#define MALI_C55_AEXP_HIST_PLANE_MODE_MASK		GENMASK(2, 0)
> +#define MALI_C55_AEXP_HIST_NODES_USED_OFFSET		52
> +#define MALI_C55_AEXP_HIST_NODES_USED_HORIZ_MASK	GENMASK(7, 0)
> +#define MALI_C55_AEXP_HIST_NODES_USED_VERT_MASK		GENMASK(15, 8)
> +#define MALI_C55_AEXP_HIST_ZONE_WEIGHTS_OFFSET		56
> +#define MALI_C55_AEXP_HIST_ZONE_WEIGHT_MASK		0x0f0f0f0f
> +
>  /*
>   * The Mali-C55 ISP has up to two output pipes; known as full resolution and
>   * down scaled. The register space for these is laid out identically, but offset
Dan Scally June 14, 2024, 8:15 p.m. UTC | #4
Hi Sakari - thanks for the comments

On 14/06/2024 19:53, Sakari Ailus wrote:
> Hi Jacopo, Dan,
>
> Thanks for the patch. Please see my comments below.
>
> On Wed, May 29, 2024 at 04:28:57PM +0100, Daniel Scally wrote:
>> Add a new code file to the mali-c55 driver that registers an output
>> video node for userspace to queue buffers of parameters to. Handlers
>> are included to program the statistics generation plus the white
>> balance, black level correction and mesh shading correction blocks.
>>
>> Update the rest of the driver to register and link the new video node
>>
>> Acked-by: Nayden Kanchev  <nayden.kanchev@arm.com>
>> Co-developed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
>> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
>> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
>> ---
>> Changes in v5:
>>
>> 	- New patch
>>
>>   drivers/media/platform/arm/mali-c55/Makefile  |   1 +
>>   .../platform/arm/mali-c55/mali-c55-common.h   |  18 +
>>   .../platform/arm/mali-c55/mali-c55-core.c     |  24 +
>>   .../platform/arm/mali-c55/mali-c55-isp.c      |  16 +-
>>   .../platform/arm/mali-c55/mali-c55-params.c   | 615 ++++++++++++++++++
>>   .../arm/mali-c55/mali-c55-registers.h         | 104 +++
>>   6 files changed, 777 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-params.c
>>
>> diff --git a/drivers/media/platform/arm/mali-c55/Makefile b/drivers/media/platform/arm/mali-c55/Makefile
>> index cd5a64bf0c62..b2443f2d416a 100644
>> --- a/drivers/media/platform/arm/mali-c55/Makefile
>> +++ b/drivers/media/platform/arm/mali-c55/Makefile
>> @@ -5,6 +5,7 @@ mali-c55-y := mali-c55-capture.o \
>>   	      mali-c55-isp.o \
>>   	      mali-c55-tpg.o \
>>   	      mali-c55-resizer.o \
>> +	      mali-c55-params.o \
>>   	      mali-c55-stats.o
>>   
>>   obj-$(CONFIG_VIDEO_MALI_C55) += mali-c55.o
>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-common.h b/drivers/media/platform/arm/mali-c55/mali-c55-common.h
>> index 44119e04009b..565d98acfcdd 100644
>> --- a/drivers/media/platform/arm/mali-c55/mali-c55-common.h
>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-common.h
>> @@ -80,6 +80,7 @@ enum mali_c55_isp_pads {
>>   	MALI_C55_ISP_PAD_SOURCE,
>>   	MALI_C55_ISP_PAD_SOURCE_BYPASS,
>>   	MALI_C55_ISP_PAD_SOURCE_3A,
>> +	MALI_C55_ISP_PAD_SINK_PARAMS,
>>   	MALI_C55_ISP_NUM_PADS,
>>   };
>>   
>> @@ -217,6 +218,19 @@ struct mali_c55_stats {
>>   	} buffers;
>>   };
>>   
>> +struct mali_c55_params {
>> +	struct mali_c55 *mali_c55;
>> +	struct video_device vdev;
>> +	struct vb2_queue queue;
>> +	struct media_pad pad;
>> +	struct mutex lock;
>> +
>> +	struct {
>> +		spinlock_t lock;
>> +		struct list_head queue;
>> +	} buffers;
>> +};
>> +
>>   enum mali_c55_config_spaces {
>>   	MALI_C55_CONFIG_PING,
>>   	MALI_C55_CONFIG_PONG,
>> @@ -247,6 +261,7 @@ struct mali_c55 {
>>   	struct mali_c55_isp isp;
>>   	struct mali_c55_resizer resizers[MALI_C55_NUM_RZRS];
>>   	struct mali_c55_cap_dev cap_devs[MALI_C55_NUM_CAP_DEVS];
>> +	struct mali_c55_params params;
>>   	struct mali_c55_stats stats;
>>   
>>   	struct list_head contexts;
>> @@ -271,6 +286,8 @@ int mali_c55_register_capture_devs(struct mali_c55 *mali_c55);
>>   void mali_c55_unregister_capture_devs(struct mali_c55 *mali_c55);
>>   int mali_c55_register_stats(struct mali_c55 *mali_c55);
>>   void mali_c55_unregister_stats(struct mali_c55 *mali_c55);
>> +int mali_c55_register_params(struct mali_c55 *mali_c55);
>> +void mali_c55_unregister_params(struct mali_c55 *mali_c55);
>>   struct mali_c55_ctx *mali_c55_get_active_context(struct mali_c55 *mali_c55);
>>   void mali_c55_set_plane_done(struct mali_c55_cap_dev *cap_dev,
>>   			     enum mali_c55_planes plane);
>> @@ -290,5 +307,6 @@ bool mali_c55_isp_is_format_supported(unsigned int mbus_code);
>>   	for ((fmt) = NULL; ((fmt) = mali_c55_isp_fmt_next((fmt)));)
>>   void mali_c55_stats_fill_buffer(struct mali_c55 *mali_c55,
>>   				enum mali_c55_config_spaces cfg_space);
>> +void mali_c55_params_write_config(struct mali_c55 *mali_c55);
>>   
>>   #endif /* _MALI_C55_COMMON_H */
>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-core.c b/drivers/media/platform/arm/mali-c55/mali-c55-core.c
>> index 2cf8b1169604..6acee3edd03f 100644
>> --- a/drivers/media/platform/arm/mali-c55/mali-c55-core.c
>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-core.c
>> @@ -347,6 +347,17 @@ static int mali_c55_create_links(struct mali_c55 *mali_c55)
>>   		goto err_remove_links;
>>   	}
>>   
>> +	ret = media_create_pad_link(&mali_c55->params.vdev.entity, 0,
>> +				    &mali_c55->isp.sd.entity,
>> +				    MALI_C55_ISP_PAD_SINK_PARAMS,
>> +				    MEDIA_LNK_FL_ENABLED |
>> +				    MEDIA_LNK_FL_IMMUTABLE);
>> +	if (ret) {
>> +		dev_err(mali_c55->dev,
>> +			"failed to link ISP and parameters video node\n");
>> +		goto err_remove_links;
>> +	}
>> +
>>   	return 0;
>>   
>>   err_remove_links:
>> @@ -360,6 +371,7 @@ static void mali_c55_unregister_entities(struct mali_c55 *mali_c55)
>>   	mali_c55_unregister_isp(mali_c55);
>>   	mali_c55_unregister_resizers(mali_c55);
>>   	mali_c55_unregister_capture_devs(mali_c55);
>> +	mali_c55_unregister_params(mali_c55);
>>   	mali_c55_unregister_stats(mali_c55);
>>   }
>>   
>> @@ -383,6 +395,10 @@ static int mali_c55_register_entities(struct mali_c55 *mali_c55)
>>   	if (ret)
>>   		goto err_unregister_entities;
>>   
>> +	ret = mali_c55_register_params(mali_c55);
>> +	if (ret)
>> +		goto err_unregister_entities;
>> +
>>   	ret = mali_c55_register_stats(mali_c55);
>>   	if (ret)
>>   		goto err_unregister_entities;
>> @@ -474,6 +490,14 @@ static irqreturn_t mali_c55_isr(int irq, void *context)
>>   			curr_config >>= ffs(MALI_C55_REG_PING_PONG_READ_MASK) - 1;
>>   			next_config = curr_config ^ 1;
>>   
>> +			/*
>> +			 * Write the configuration parameters received from
>> +			 * userspace into the configuration buffer, which will
>> +			 * be transferred to the 'next' active config space at
>> +			 * by mali_c55_swap_next_config().
>> +			 */
>> +			mali_c55_params_write_config(mali_c55);
>> +
>>   			/*
>>   			 * The ordering of these two is currently important as
>>   			 * mali_c55_stats_fill_buffer() is asynchronous whereas
>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-isp.c b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
>> index 94876fba3353..8c2b45bfd82d 100644
>> --- a/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
>> @@ -146,6 +146,7 @@ static int mali_c55_isp_start(struct mali_c55 *mali_c55)
>>   			     cfg->encoding == V4L2_PIXEL_ENC_RGB ?
>>   			     MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK : 0x00);
>>   
>> +	mali_c55_params_write_config(mali_c55);
>>   	ret = mali_c55_config_write(ctx, MALI_C55_CONFIG_PING);
>>   	if (ret) {
>>   		dev_err(mali_c55->dev, "failed to DMA config\n");
>> @@ -455,8 +456,20 @@ static const struct v4l2_subdev_internal_ops mali_c55_isp_internal_ops = {
>>   	.init_state = mali_c55_isp_init_state,
>>   };
>>   
>> +static int mali_c55_subdev_link_validate(struct media_link *link)
>> +{
>> +	/*
>> +	 * Skip validation for the parameters sink pad, as the source is not
>> +	 * a subdevice.
>> +	 */
>> +	if (link->sink->index == MALI_C55_ISP_PAD_SINK_PARAMS)
>> +		return 0;
>> +
>> +	return v4l2_subdev_link_validate(link);
>> +}
>> +
>>   static const struct media_entity_operations mali_c55_isp_media_ops = {
>> -	.link_validate		= v4l2_subdev_link_validate,
>> +	.link_validate		= mali_c55_subdev_link_validate,
>>   };
>>   
>>   static int mali_c55_isp_notifier_bound(struct v4l2_async_notifier *notifier,
>> @@ -565,6 +578,7 @@ int mali_c55_register_isp(struct mali_c55 *mali_c55)
>>   	isp->pads[MALI_C55_ISP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
>>   	isp->pads[MALI_C55_ISP_PAD_SOURCE_BYPASS].flags = MEDIA_PAD_FL_SOURCE;
>>   	isp->pads[MALI_C55_ISP_PAD_SOURCE_3A].flags = MEDIA_PAD_FL_SOURCE;
>> +	isp->pads[MALI_C55_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
>>   
>>   	ret = media_entity_pads_init(&sd->entity, MALI_C55_ISP_NUM_PADS,
>>   				     isp->pads);
>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-params.c b/drivers/media/platform/arm/mali-c55/mali-c55-params.c
>> new file mode 100644
>> index 000000000000..049a7b8e4861
>> --- /dev/null
>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-params.c
>> @@ -0,0 +1,615 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * ARM Mali-C55 ISP Driver - Configuration parameters output device
>> + *
>> + * Copyright (C) 2024 Ideas on Board Oy
>> + */
>> +#include <linux/media/arm/mali-c55-config.h>
>> +
>> +#include <media/media-entity.h>
>> +#include <media/v4l2-dev.h>
>> +#include <media/v4l2-event.h>
>> +#include <media/v4l2-fh.h>
>> +#include <media/v4l2-ioctl.h>
>> +#include <media/videobuf2-core.h>
>> +#include <media/videobuf2-dma-contig.h>
>> +
>> +#include "mali-c55-common.h"
>> +#include "mali-c55-registers.h"
>> +
>> +typedef void (*mali_c55_block_handler)(struct mali_c55 *mali_c55,
> You can wrap after the return type (including typedef). Same elsewhere.
>
>> +				       struct mali_c55_params_block_header *block);
>> +
>> +struct mali_c55_block_handler {
>> +	size_t size;
>> +	mali_c55_block_handler handler;
>> +};
>> +
>> +static void mali_c55_params_sensor_offs(struct mali_c55 *mali_c55,
>> +					struct mali_c55_params_block_header *block)
>> +{
>> +	struct mali_c55_params_sensor_off_preshading *p =
>> +		(struct mali_c55_params_sensor_off_preshading *)block;
> I wonder if an union could be used to make this a bit cleaner. You're doing
> a lot of casting that I think could be avoided.


Interesting idea, thank you - let me give that a try and see how it looks

>
>> +	__u32 global_offset;
>> +
>> +	if (!block->enabled)
>> +		return;
>> +
>> +	if (!(p->chan00 || p->chan01 || p->chan10 || p->chan11))
>> +		return;
>> +
>> +	mali_c55_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_00,
>> +		       p->chan00 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK);
>> +	mali_c55_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_01,
>> +		       p->chan01 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK);
>> +	mali_c55_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_10,
>> +		       p->chan10 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK);
>> +	mali_c55_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_11,
>> +		       p->chan11 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK);
>> +
>> +	/*
>> +	 * The average offset is applied as a global offset for the digital
>> +	 * gain block
>> +	 */
>> +	global_offset = (p->chan00 + p->chan01 + p->chan10 + p->chan11) >> 2;
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_DIGITAL_GAIN_OFFSET,
>> +			     MALI_C55_DIGITAL_GAIN_OFFSET_MASK, global_offset);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_BYPASS_3,
>> +			     MALI_C55_REG_BYPASS_3_SENSOR_OFFSET_PRE_SH, 0x00);
>> +}
>> +
>> +static void mali_c55_params_aexp_hist(struct mali_c55 *mali_c55,
>> +				struct mali_c55_params_block_header *block)
>> +{
>> +	u32 disable_mask = block->type == MALI_C55_PARAM_BLOCK_AEXP_HIST ?
>> +					  MALI_C55_AEXP_HIST_DISABLE_MASK :
>> +					  MALI_C55_AEXP_IHIST_DISABLE_MASK;
>> +	u32 base = block->type == MALI_C55_PARAM_BLOCK_AEXP_HIST ?
>> +				  MALI_C55_REG_AEXP_HIST_BASE :
>> +				  MALI_C55_REG_AEXP_IHIST_BASE;
>> +	struct mali_c55_params_aexp_hist *params =
>> +		(struct mali_c55_params_aexp_hist *)block;
>> +
>> +	if (!block->enabled) {
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
>> +				     disable_mask, true);
>> +		return;
>> +	}
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
>> +			     disable_mask, false);
>> +
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET,
>> +			     MALI_C55_AEXP_HIST_SKIP_X_MASK, params->skip_x);
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET,
>> +			     MALI_C55_AEXP_HIST_OFFSET_X_MASK, params->offset_x);
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET,
>> +			     MALI_C55_AEXP_HIST_SKIP_Y_MASK, params->skip_y);
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET,
>> +			     MALI_C55_AEXP_HIST_OFFSET_Y_MASK, params->offset_y);
>> +
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SCALE_OFFSET,
>> +			     MALI_C55_AEXP_HIST_SCALE_BOTTOM_MASK, params->scale_bottom);
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SCALE_OFFSET,
>> +			     MALI_C55_AEXP_HIST_SCALE_TOP_MASK, params->scale_top);
>> +
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_PLANE_MODE_OFFSET,
>> +			     MALI_C55_AEXP_HIST_PLANE_MODE_MASK, params->plane_mode);
>> +
>> +	if (block->type == MALI_C55_PARAM_BLOCK_AEXP_HIST)
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
>> +				     MALI_C55_AEXP_HIST_SWITCH_MASK,
>> +				     params->tap_point);
>> +}
>> +
>> +static void
>> +mali_c55_params_aexp_hist_weights(struct mali_c55 *mali_c55,
>> +				  struct mali_c55_params_block_header *block)
>> +{
>> +	struct mali_c55_params_aexp_weights *params =
>> +		(struct mali_c55_params_aexp_weights *)block;
>> +	u32 base;
>> +
>> +	if (!block->enabled)
>> +		return;
>> +
>> +	base = block->type == MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS ?
>> +			      MALI_C55_REG_AEXP_HIST_BASE :
>> +			      MALI_C55_REG_AEXP_IHIST_BASE;
>> +
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_NODES_USED_OFFSET,
>> +			     MALI_C55_AEXP_HIST_NODES_USED_HORIZ_MASK, params->nodes_used_horiz);
>> +	mali_c55_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_NODES_USED_OFFSET,
>> +			     MALI_C55_AEXP_HIST_NODES_USED_VERT_MASK, params->nodes_used_vert);
>> +
>> +	/*
>> +	 * The zone weights array is a 225-element array of u8 values, but that
>> +	 * is a bit annoying to handle given the ISP expects 32-bit writes. We
>> +	 * just reinterpret it as a 57-element array of 32-bit values for the
>> +	 * purposes of this transaction (the 3 bytes of additional space at the
>> +	 * end of the write is just padding for the array of weights in the ISP
>> +	 * memory space anyway, so there's no risk of overwriting other
>> +	 * registers).
>> +	 */
>> +	for (unsigned int i = 0; i < 57; i++) {
>> +		u32 val = ((u32 *)params->zone_weights)[i]
>> +			    & MALI_C55_AEXP_HIST_ZONE_WEIGHT_MASK;
>> +		u32 addr = base + MALI_C55_AEXP_HIST_ZONE_WEIGHTS_OFFSET + (4 * i);
>> +
>> +		mali_c55_write(mali_c55, addr, val);
>> +	}
>> +}
>> +
>> +static void mali_c55_params_digital_gain(struct mali_c55 *mali_c55,
>> +					 struct mali_c55_params_block_header *block)
>> +{
>> +	struct mali_c55_params_digital_gain *dgain =
>> +		(struct mali_c55_params_digital_gain *)block;
>> +
>> +	/*
>> +	 * If the block is flagged as disabled we write a gain of 1.0, which in
>> +	 * Q5.8 format is 256.
>> +	 */
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_DIGITAL_GAIN,
>> +			     MALI_C55_DIGITAL_GAIN_MASK,
>> +			     block->enabled ? dgain->gain : 256);
>> +}
>> +
>> +static void mali_c55_params_awb_gains(struct mali_c55 *mali_c55,
>> +				      struct mali_c55_params_block_header *block)
>> +{
>> +	struct mali_c55_params_awb_gains *gains =
>> +		(struct mali_c55_params_awb_gains *)block;
>> +
>> +	/*
>> +	 * There are two places AWB gains can be set in the ISP; one affects the
>> +	 * image output data and the other affects the statistics for the
>> +	 * AEXP-0 tap point.
>> +	 */
>> +	u32 addr1 = block->type = MALI_C55_PARAM_BLOCK_AWB_GAINS ?
>> +				  MALI_C55_REG_AWB_GAINS1 :
>> +				  MALI_C55_REG_AWB_GAINS1_AEXP;
>> +	u32 addr2 = block->type = MALI_C55_PARAM_BLOCK_AWB_GAINS ?
>> +				  MALI_C55_REG_AWB_GAINS2 :
>> +				  MALI_C55_REG_AWB_GAINS2_AEXP;
>> +
>> +	mali_c55_update_bits(mali_c55, addr1, MALI_C55_AWB_GAIN00_MASK,
>> +			     gains->gain00);
>> +	mali_c55_update_bits(mali_c55, addr1, MALI_C55_AWB_GAIN01_MASK,
>> +			     gains->gain01);
>> +	mali_c55_update_bits(mali_c55, addr2, MALI_C55_AWB_GAIN10_MASK,
>> +			     gains->gain10);
>> +	mali_c55_update_bits(mali_c55, addr2, MALI_C55_AWB_GAIN11_MASK,
>> +			     gains->gain11);
>> +}
>> +
>> +static void mali_c55_params_awb_config(struct mali_c55 *mali_c55,
>> +				      struct mali_c55_params_block_header *block)
>> +{
>> +	struct mali_c55_params_awb_config *params =
>> +		(struct mali_c55_params_awb_config *)block;
>> +
>> +	if (!block->enabled) {
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
>> +				     MALI_C55_AWB_DISABLE_MASK, true);
>> +		return;
>> +	}
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
>> +			     MALI_C55_AWB_DISABLE_MASK, false);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_STATS_MODE,
>> +			     MALI_C55_AWB_STATS_MODE_MASK, params->stats_mode);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_WHITE_LEVEL,
>> +			     MALI_C55_AWB_WHITE_LEVEL_MASK, params->white_level);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_BLACK_LEVEL,
>> +			     MALI_C55_AWB_BLACK_LEVEL_MASK, params->black_level);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CR_MAX,
>> +			     MALI_C55_AWB_CR_MAX_MASK, params->cr_max);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CR_MIN,
>> +			     MALI_C55_AWB_CR_MIN_MASK, params->cr_min);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CB_MAX,
>> +			     MALI_C55_AWB_CB_MAX_MASK, params->cb_max);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CB_MIN,
>> +			     MALI_C55_AWB_CB_MIN_MASK, params->cb_min);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_NODES_USED,
>> +			     MALI_C55_AWB_NODES_USED_HORIZ_MASK,
>> +			     params->nodes_used_horiz);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_NODES_USED,
>> +			     MALI_C55_AWB_NODES_USED_VERT_MASK,
>> +			     params->nodes_used_vert);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CR_HIGH,
>> +			     MALI_C55_AWB_CR_HIGH_MASK, params->cr_high);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CR_LOW,
>> +			     MALI_C55_AWB_CR_LOW_MASK, params->cr_low);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CB_HIGH,
>> +			     MALI_C55_AWB_CB_HIGH_MASK, params->cb_high);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_AWB_CB_LOW,
>> +			     MALI_C55_AWB_CB_LOW_MASK, params->cb_low);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
>> +			     MALI_C55_AWB_SWITCH_MASK, params->tap_point);
>> +}
>> +
>> +static void mali_c55_params_lsc_config(struct mali_c55 *mali_c55,
>> +				       struct mali_c55_params_block_header *block)
>> +{
>> +	struct mali_c55_params_mesh_shading_config *params =
>> +		(struct mali_c55_params_mesh_shading_config *)block;
>> +	unsigned int i;
>> +	u32 addr;
>> +
>> +	if (!block->enabled) {
>> +		mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +				     MALI_C55_MESH_SHADING_ENABLE_MASK, false);
>> +		return;
>> +	}
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +			     MALI_C55_MESH_SHADING_ENABLE_MASK, true);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +			     MALI_C55_MESH_SHADING_MESH_SHOW, params->mesh_show);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +			     MALI_C55_MESH_SHADING_SCALE_MASK,
>> +			     params->mesh_scale);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +			     MALI_C55_MESH_SHADING_PAGE_R_MASK,
>> +			     params->mesh_page_r);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +			     MALI_C55_MESH_SHADING_PAGE_G_MASK,
>> +			     params->mesh_page_g);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +			     MALI_C55_MESH_SHADING_PAGE_B_MASK,
>> +			     params->mesh_page_b);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +			     MALI_C55_MESH_SHADING_MESH_WIDTH_MASK,
>> +			     params->mesh_width);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
>> +			     MALI_C55_MESH_SHADING_MESH_HEIGHT_MASK,
>> +			     params->mesh_height);
>> +
>> +	for (i = 0; i < MALI_C55_NUM_MESH_SHADING_ELEMENTS; i++) {
>> +		addr = MALI_C55_REG_MESH_SHADING_TABLES + (i * 4);
>> +		mali_c55_write(mali_c55, addr, params->mesh[i]);
>> +	}
>> +}
>> +
>> +static void mali_c55_params_lsc_selection(struct mali_c55 *mali_c55,
>> +					  struct mali_c55_params_block_header *block)
>> +{
>> +	struct mali_c55_params_mesh_shading_selection *params =
>> +		(struct mali_c55_params_mesh_shading_selection *)block;
>> +
>> +	if (!block->enabled)
>> +		return;
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK,
>> +			     MALI_C55_MESH_SHADING_ALPHA_BANK_R_MASK,
>> +			     params->mesh_alpha_bank_r);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK,
>> +			     MALI_C55_MESH_SHADING_ALPHA_BANK_G_MASK,
>> +			     params->mesh_alpha_bank_g);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK,
>> +			     MALI_C55_MESH_SHADING_ALPHA_BANK_B_MASK,
>> +			     params->mesh_alpha_bank_b);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA,
>> +			     MALI_C55_MESH_SHADING_ALPHA_R_MASK,
>> +			     params->mesh_alpha_r);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA,
>> +			     MALI_C55_MESH_SHADING_ALPHA_G_MASK,
>> +			     params->mesh_alpha_g);
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA,
>> +			     MALI_C55_MESH_SHADING_ALPHA_B_MASK,
>> +			     params->mesh_alpha_b);
>> +
>> +	mali_c55_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_MESH_STRENGTH,
>> +			     MALI_c55_MESH_STRENGTH_MASK,
>> +			     params->mesh_strength);
>> +}
>> +
>> +static const struct mali_c55_block_handler mali_c55_block_handlers[] = {
>> +	[MALI_C55_PARAM_BLOCK_SENSOR_OFFS] = {
>> +		.size = sizeof(struct mali_c55_params_sensor_off_preshading),
>> +		.handler = &mali_c55_params_sensor_offs,
>> +	},
>> +	[MALI_C55_PARAM_BLOCK_AEXP_HIST] = {
>> +		.size = sizeof(struct mali_c55_params_aexp_hist),
>> +		.handler = &mali_c55_params_aexp_hist,
>> +	},
>> +	[MALI_C55_PARAM_BLOCK_AEXP_IHIST] = {
>> +		.size = sizeof(struct mali_c55_params_aexp_hist),
>> +		.handler = &mali_c55_params_aexp_hist,
>> +	},
>> +	[MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS] = {
>> +		.size = sizeof(struct mali_c55_params_aexp_weights),
>> +		.handler = &mali_c55_params_aexp_hist_weights,
>> +	},
>> +	[MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS] = {
>> +		.size = sizeof(struct mali_c55_params_aexp_weights),
>> +		.handler = &mali_c55_params_aexp_hist_weights,
>> +	},
>> +	[MALI_C55_PARAM_BLOCK_DIGITAL_GAIN] = {
>> +		.size = sizeof(struct mali_c55_params_digital_gain),
>> +		.handler = &mali_c55_params_digital_gain,
>> +	},
>> +	[MALI_C55_PARAM_BLOCK_AWB_GAINS] = {
>> +		.size = sizeof(struct mali_c55_params_awb_gains),
>> +		.handler = &mali_c55_params_awb_gains,
>> +	},
>> +	[MALI_C55_PARAM_BLOCK_AWB_CONFIG] = {
>> +		.size = sizeof(struct mali_c55_params_awb_config),
>> +		.handler = &mali_c55_params_awb_config,
>> +	},
>> +	[MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP] = {
>> +		.size = sizeof(struct mali_c55_params_awb_gains),
>> +		.handler = &mali_c55_params_awb_gains,
>> +	},
>> +	[MALI_C55_PARAM_MESH_SHADING_CONFIG] = {
>> +		.size = sizeof(struct mali_c55_params_mesh_shading_config),
>> +		.handler = &mali_c55_params_lsc_config,
>> +	},
>> +	[MALI_C55_PARAM_MESH_SHADING_SELECTION] = {
>> +		.size = sizeof(struct mali_c55_params_mesh_shading_selection),
>> +		.handler = &mali_c55_params_lsc_selection,
>> +	},
>> +};
>> +
>> +static int mali_c55_params_enum_fmt_meta_out(struct file *file, void *fh,
>> +					    struct v4l2_fmtdesc *f)
>> +{
>> +	if (f->index || f->type != V4L2_BUF_TYPE_META_OUTPUT)
> The buffer type check has been done by the caller already.
>
>> +		return -EINVAL;
>> +
>> +	f->pixelformat = V4L2_META_FMT_MALI_C55_PARAMS;
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_params_g_fmt_meta_out(struct file *file, void *fh,
>> +					 struct v4l2_format *f)
>> +{
>> +	static const struct v4l2_meta_format mfmt = {
>> +		.dataformat = V4L2_META_FMT_MALI_C55_PARAMS,
>> +		.buffersize = sizeof(struct mali_c55_params_buffer),
>> +	};
>> +
>> +	if (f->type != V4L2_BUF_TYPE_META_OUTPUT)
>> +		return -EINVAL;
> Ditto.
>
> Maybe check the other instances of format access functions in the driver,
> too?

Ack
>
>> +
>> +	f->fmt.meta = mfmt;
>> +
>> +	return 0;
>> +}
>> +
>> +static int mali_c55_params_querycap(struct file *file,
>> +				   void *priv, struct v4l2_capability *cap)
>> +{
>> +	strscpy(cap->driver, MALI_C55_DRIVER_NAME, sizeof(cap->driver));
>> +	strscpy(cap->card, "ARM Mali-C55 ISP", sizeof(cap->card));
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_ioctl_ops mali_c55_params_v4l2_ioctl_ops = {
>> +	.vidioc_reqbufs = vb2_ioctl_reqbufs,
>> +	.vidioc_querybuf = vb2_ioctl_querybuf,
>> +	.vidioc_create_bufs = vb2_ioctl_create_bufs,
>> +	.vidioc_qbuf = vb2_ioctl_qbuf,
>> +	.vidioc_expbuf = vb2_ioctl_expbuf,
>> +	.vidioc_dqbuf = vb2_ioctl_dqbuf,
>> +	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
>> +	.vidioc_streamon = vb2_ioctl_streamon,
>> +	.vidioc_streamoff = vb2_ioctl_streamoff,
>> +	.vidioc_enum_fmt_meta_out = mali_c55_params_enum_fmt_meta_out,
>> +	.vidioc_g_fmt_meta_out = mali_c55_params_g_fmt_meta_out,
>> +	.vidioc_s_fmt_meta_out = mali_c55_params_g_fmt_meta_out,
>> +	.vidioc_try_fmt_meta_out = mali_c55_params_g_fmt_meta_out,
>> +	.vidioc_querycap = mali_c55_params_querycap,
>> +	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
>> +	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
>> +};
>> +
>> +static const struct v4l2_file_operations mali_c55_params_v4l2_fops = {
>> +	.owner = THIS_MODULE,
>> +	.unlocked_ioctl = video_ioctl2,
>> +	.open = v4l2_fh_open,
>> +	.release = vb2_fop_release,
>> +	.poll = vb2_fop_poll,
>> +	.mmap = vb2_fop_mmap,
>> +};
>> +
>> +static int
>> +mali_c55_params_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
>> +			   unsigned int *num_planes, unsigned int sizes[],
>> +			   struct device *alloc_devs[])
>> +{
>> +	if (*num_planes && *num_planes > 1)
>> +		return -EINVAL;
>> +
>> +	if (sizes[0] && sizes[0] != sizeof(struct mali_c55_params_buffer))
>> +		return -EINVAL;
>> +
>> +	*num_planes = 1;
>> +	sizes[0] = sizeof(struct mali_c55_params_buffer);
>> +
>> +	return 0;
>> +}
>> +
>> +static void mali_c55_params_buf_queue(struct vb2_buffer *vb)
>> +{
>> +	struct mali_c55_params *params = vb2_get_drv_priv(vb->vb2_queue);
>> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
>> +	struct mali_c55_buffer *buf = container_of(vbuf,
>> +						   struct mali_c55_buffer, vb);
>> +
>> +	vb2_set_plane_payload(vb, 0, sizeof(struct mali_c55_params_buffer));
>> +
>> +	spin_lock(&params->buffers.lock);
>> +	list_add_tail(&buf->queue, &params->buffers.queue);
>> +	spin_unlock(&params->buffers.lock);
>> +}
>> +
>> +static void mali_c55_params_stop_streaming(struct vb2_queue *q)
>> +{
>> +	struct mali_c55_params *params = vb2_get_drv_priv(q);
>> +	struct mali_c55_buffer *buf, *tmp;
>> +
>> +	spin_lock(&params->buffers.lock);
>> +
>> +	list_for_each_entry_safe(buf, tmp, &params->buffers.queue, queue) {
>> +		list_del(&buf->queue);
>> +		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
>> +	}
>> +
>> +	spin_unlock(&params->buffers.lock);
>> +}
>> +
>> +static const struct vb2_ops mali_c55_params_vb2_ops = {
>> +	.queue_setup = mali_c55_params_queue_setup,
>> +	.buf_queue = mali_c55_params_buf_queue,
>> +	.wait_prepare = vb2_ops_wait_prepare,
>> +	.wait_finish = vb2_ops_wait_finish,
>> +	.stop_streaming = mali_c55_params_stop_streaming,
>> +};
>> +
>> +void mali_c55_params_write_config(struct mali_c55 *mali_c55)
>> +{
>> +	struct mali_c55_params *params = &mali_c55->params;
>> +	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>> +	struct mali_c55_params_buffer *config;
>> +	struct mali_c55_buffer *buf;
>> +	size_t block_offset = 0;
>> +
>> +	spin_lock(&params->buffers.lock);
>> +
>> +	buf = list_first_entry_or_null(&params->buffers.queue,
>> +				       struct mali_c55_buffer, queue);
>> +	if (buf)
>> +		list_del(&buf->queue);
>> +	spin_unlock(&params->buffers.lock);
>> +
>> +	if (!buf)
>> +		return;
>> +
>> +	buf->vb.sequence = mali_c55->isp.frame_sequence;
>> +	config = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
>> +
>> +	if (config->total_size > MALI_C55_PARAMS_MAX_SIZE) {
>> +		dev_dbg(mali_c55->dev, "Invalid parameters buffer size %lu\n",
>> +			config->total_size);
>> +		state = VB2_BUF_STATE_ERROR;
>> +		goto err_buffer_done;
>> +	}
>> +
>> +	/* Walk the list of parameter blocks and process them. */
>> +	while (block_offset < config->total_size) {
>> +		const struct mali_c55_block_handler *block_handler;
>> +		struct mali_c55_params_block_header *block;
>> +
>> +		block = (struct mali_c55_params_block_header *)
>> +			 &config->data[block_offset];
> How do you ensure config->data does hold a full struct
> mali_c33_params_block_header at block_offset (i.e. that the struct does not
> exceed the memory available for config->data)?


We don't currently...the data buffer is sized specifically to be large enough to accept a single 
instance of each possible struct that could be included, we could keep track of the blocks that we 
have seen already and ensure that none are seen twice...and that should guarantee that the remaining 
space is sufficient to hold whatever the last block is. Does that sound ok?

>
>> +
>> +		if (block->type >= MALI_C55_PARAM_BLOCK_SENTINEL) {
>> +			dev_dbg(mali_c55->dev, "Invalid parameters block type\n");
>> +			state = VB2_BUF_STATE_ERROR;
>> +			goto err_buffer_done;
>> +		}
>> +
>> +		block_handler = &mali_c55_block_handlers[block->type];
>> +		if (block->size != block_handler->size) {
> How do you ensure config->data has room for the block?
I think through the same proposal as above.
>
>> +			dev_dbg(mali_c55->dev, "Invalid parameters block size\n");
>> +			state = VB2_BUF_STATE_ERROR;
>> +			goto err_buffer_done;
>> +		}
>> +
>> +		block_handler->handler(mali_c55, block);
>> +
>> +		block_offset += block->size;
>> +	}
>> +
>> +err_buffer_done:
>> +	vb2_buffer_done(&buf->vb.vb2_buf, state);
>> +}
>> +
>> +void mali_c55_unregister_params(struct mali_c55 *mali_c55)
>> +{
>> +	struct mali_c55_params *params = &mali_c55->params;
>> +
>> +	if (!video_is_registered(&params->vdev))
>> +		return;
>> +
>> +	vb2_video_unregister_device(&params->vdev);
>> +	media_entity_cleanup(&params->vdev.entity);
>> +	mutex_destroy(&params->lock);
>> +}
>> +
>> +int mali_c55_register_params(struct mali_c55 *mali_c55)
>> +{
>> +	struct mali_c55_params *params = &mali_c55->params;
>> +	struct video_device *vdev = &params->vdev;
>> +	struct vb2_queue *vb2q = &params->queue;
>> +	int ret;
>> +
>> +	mutex_init(&params->lock);
>> +	INIT_LIST_HEAD(&params->buffers.queue);
>> +
>> +	params->pad.flags = MEDIA_PAD_FL_SOURCE;
>> +	ret = media_entity_pads_init(&params->vdev.entity, 1, &params->pad);
>> +	if (ret)
>> +		goto err_destroy_mutex;
>> +
>> +	vb2q->type = V4L2_BUF_TYPE_META_OUTPUT;
>> +	vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
>> +	vb2q->drv_priv = params;
>> +	vb2q->mem_ops = &vb2_dma_contig_memops;
>> +	vb2q->ops = &mali_c55_params_vb2_ops;
>> +	vb2q->buf_struct_size = sizeof(struct mali_c55_buffer);
>> +	vb2q->min_queued_buffers = 1;
>> +	vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
>> +	vb2q->lock = &params->lock;
>> +	vb2q->dev = mali_c55->dev;
>> +
>> +	ret = vb2_queue_init(vb2q);
>> +	if (ret) {
>> +		dev_err(mali_c55->dev, "params vb2 queue init failed\n");
>> +		goto err_cleanup_entity;
>> +	}
>> +
>> +	strscpy(params->vdev.name, "mali-c55 3a params",
>> +		sizeof(params->vdev.name));
>> +	vdev->release = video_device_release_empty;
>> +	vdev->fops = &mali_c55_params_v4l2_fops;
>> +	vdev->ioctl_ops = &mali_c55_params_v4l2_ioctl_ops;
>> +	vdev->lock = &params->lock;
>> +	vdev->v4l2_dev = &mali_c55->v4l2_dev;
>> +	vdev->queue = &params->queue;
>> +	vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
>> +	vdev->vfl_dir = VFL_DIR_TX;
>> +	video_set_drvdata(vdev, params);
>> +
>> +	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
>> +	if (ret) {
>> +		dev_err(mali_c55->dev,
>> +			"failed to register params video device\n");
>> +		goto err_release_vb2q;
>> +	}
>> +
>> +	params->mali_c55 = mali_c55;
>> +
>> +	return 0;
>> +
>> +err_release_vb2q:
>> +	vb2_queue_release(vb2q);
>> +err_cleanup_entity:
>> +	media_entity_cleanup(&params->vdev.entity);
>> +err_destroy_mutex:
>> +	mutex_destroy(&params->lock);
>> +
>> +	return ret;
>> +}
>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-registers.h b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
>> index eb3719245ec3..8e6a801077ed 100644
>> --- a/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
>> @@ -119,6 +119,19 @@
>>   #define MALI_C55_REG_ACTIVE_HEIGHT_MASK			0xffff0000
>>   #define MALI_C55_REG_BAYER_ORDER			0x18e8c
>>   #define MALI_C55_BAYER_ORDER_MASK			GENMASK(1, 0)
>> +
>> +#define MALI_C55_REG_METERING_CONFIG			0x18ed0
>> +#define MALI_C55_5BIN_HIST_DISABLE_MASK			BIT(0)
>> +#define MALI_C55_5BIN_HIST_SWITCH_MASK			GENMASK(2, 1)
>> +#define MALI_C55_AF_DISABLE_MASK			BIT(4)
>> +#define MALI_C55_AF_SWITCH_MASK				BIT(5)
>> +#define MALI_C55_AWB_DISABLE_MASK			BIT(8)
>> +#define MALI_C55_AWB_SWITCH_MASK			BIT(9)
>> +#define MALI_C55_AEXP_HIST_DISABLE_MASK			BIT(12)
>> +#define MALI_C55_AEXP_HIST_SWITCH_MASK			GENMASK(14, 13)
>> +#define MALI_C55_AEXP_IHIST_DISABLE_MASK		BIT(16)
>> +#define MALI_C55_AEXP_SRC_MASK				BIT(24)
>> +
>>   #define MALI_C55_REG_TPG_CH0				0x18ed8
>>   #define MALI_C55_TEST_PATTERN_ON_OFF			BIT(0)
>>   #define MALI_C55_TEST_PATTERN_RGB_MASK			BIT(1)
>> @@ -138,6 +151,11 @@
>>   #define MALI_C55_REG_CONFIG_SPACES_OFFSET		0x0ab6c
>>   #define MALI_C55_CONFIG_SPACE_SIZE			0x1231c
>>   
>> +#define MALI_C55_REG_DIGITAL_GAIN			0x1926c
>> +#define MALI_C55_DIGITAL_GAIN_MASK			GENMASK(12, 0)
>> +#define MALI_C55_REG_DIGITAL_GAIN_OFFSET		0x19270
>> +#define MALI_C55_DIGITAL_GAIN_OFFSET_MASK		GENMASK(19, 0)
>> +
>>   #define MALI_C55_REG_SINTER_CONFIG			0x19348
>>   #define MALI_C55_SINTER_VIEW_FILTER_MASK		GENMASK(1, 0)
>>   #define MALI_C55_SINTER_SCALE_MODE_MASK			GENMASK(3, 2)
>> @@ -146,6 +164,46 @@
>>   #define MALI_C55_SINTER_INT_SELECT_MASK			BIT(6)
>>   #define MALI_C55_SINTER_RM_ENABLE_MASK			BIT(7)
>>   
>> +/* Black Level Correction Configuration */
>> +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_00		0x1abcc
>> +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_01		0x1abd0
>> +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_10		0x1abd4
>> +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_11		0x1abd8
>> +#define MALI_C55_SENSOR_OFF_PRE_SHA_MASK		0xfffff
>> +
>> +/* Lens Mesh Shading Configuration */
>> +#define MALI_C55_REG_MESH_SHADING_TABLES		0x13074
>> +#define MALI_C55_REG_MESH_SHADING_CONFIG		0x1abfc
>> +#define MALI_C55_MESH_SHADING_ENABLE_MASK		BIT(0)
>> +#define MALI_C55_MESH_SHADING_MESH_SHOW			BIT(1)
>> +#define MALI_C55_MESH_SHADING_SCALE_MASK		GENMASK(4, 2)
>> +#define MALI_C55_MESH_SHADING_PAGE_R_MASK		GENMASK(9, 8)
>> +#define MALI_C55_MESH_SHADING_PAGE_G_MASK		GENMASK(11, 10)
>> +#define MALI_C55_MESH_SHADING_PAGE_B_MASK		GENMASK(13, 12)
>> +#define MALI_C55_MESH_SHADING_MESH_WIDTH_MASK		GENMASK(21, 16)
>> +#define MALI_C55_MESH_SHADING_MESH_HEIGHT_MASK		GENMASK(29, 24)
>> +
>> +#define MALI_C55_REG_MESH_SHADING_ALPHA_BANK		0x1ac04
>> +#define MALI_C55_MESH_SHADING_ALPHA_BANK_R_MASK		GENMASK(2, 0)
>> +#define MALI_C55_MESH_SHADING_ALPHA_BANK_G_MASK		GENMASK(5, 3)
>> +#define MALI_C55_MESH_SHADING_ALPHA_BANK_B_MASK		GENMASK(8, 6)
>> +#define MALI_C55_REG_MESH_SHADING_ALPHA			0x1ac08
>> +#define MALI_C55_MESH_SHADING_ALPHA_R_MASK		GENMASK(7, 0)
>> +#define MALI_C55_MESH_SHADING_ALPHA_G_MASK		GENMASK(15, 8)
>> +#define MALI_C55_MESH_SHADING_ALPHA_B_MASK		GENMASK(23, 16)
>> +#define MALI_C55_REG_MESH_SHADING_MESH_STRENGTH		0x1ac0c
>> +#define MALI_c55_MESH_STRENGTH_MASK			GENMASK(15, 0)
>> +
>> +/* AWB Gains Configuration */
>> +#define MALI_C55_REG_AWB_GAINS1				0x1ac10
>> +#define MALI_C55_AWB_GAIN00_MASK			GENMASK(11, 0)
>> +#define MALI_C55_AWB_GAIN01_MASK			GENMASK(27, 16)
>> +#define MALI_C55_REG_AWB_GAINS2				0x1ac14
>> +#define MALI_C55_AWB_GAIN10_MASK			GENMASK(11, 0)
>> +#define MALI_C55_AWB_GAIN11_MASK			GENMASK(27, 16)
>> +#define MALI_C55_REG_AWB_GAINS1_AEXP			0x1ac18
>> +#define MALI_C55_REG_AWB_GAINS2_AEXP			0x1ac1c
>> +
>>   /* Colour Correction Matrix Configuration */
>>   #define MALI_C55_REG_CCM_ENABLE				0x1b07c
>>   #define MALI_C55_CCM_ENABLE_MASK			BIT(0)
>> @@ -168,6 +226,52 @@
>>   #define MALI_C55_REG_CCM_ANTIFOG_OFFSET_B		0x1b0c8
>>   #define MALI_C55_CCM_ANTIFOG_OFFSET_MASK		GENMASK(11, 0)
>>   
>> +/* AWB Statistics Configuration */
>> +#define MALI_C55_REG_AWB_STATS_MODE			0x1b29c
>> +#define MALI_C55_AWB_STATS_MODE_MASK			BIT(0)
>> +#define MALI_C55_REG_AWB_WHITE_LEVEL			0x1b2a0
>> +#define MALI_C55_AWB_WHITE_LEVEL_MASK			GENMASK(9, 0)
>> +#define MALI_C55_REG_AWB_BLACK_LEVEL			0x1b2a4
>> +#define MALI_C55_AWB_BLACK_LEVEL_MASK			GENMASK(9, 0)
>> +#define MALI_C55_REG_AWB_CR_MAX				0x1b2a8
>> +#define MALI_C55_AWB_CR_MAX_MASK			GENMASK(11, 0)
>> +#define MALI_C55_REG_AWB_CR_MIN				0x1b2ac
>> +#define MALI_C55_AWB_CR_MIN_MASK			GENMASK(11, 0)
>> +#define MALI_C55_REG_AWB_CB_MAX				0x1b2b0
>> +#define MALI_C55_REG_AWB_CB_MIN				0x1b2b4
>> +#define MALI_C55_AWB_CB_MIN_MASK			GENMASK(11, 0)
>> +#define MALI_C55_REG_AWB_NODES_USED			0x1b2c4
>> +#define MALI_C55_AWB_NODES_USED_HORIZ_MASK		GENMASK(7, 0)
>> +#define MALI_C55_AWB_NODES_USED_VERT_MASK		GENMASK(15, 8)
>> +#define MALI_C55_REG_AWB_CR_HIGH			0x1b2c8
>> +#define MALI_C55_AWB_CR_HIGH_MASK			GENMASK(11, 0)
>> +#define MALI_C55_REG_AWB_CR_LOW				0x1b2cc
>> +#define MALI_C55_AWB_CR_LOW_MASK			GENMASK(11, 0)
>> +#define MALI_C55_REG_AWB_CB_HIGH			0x1b2d0
>> +#define MALI_C55_AWB_CB_HIGH_MASK			GENMASK(11, 0)
>> +#define MALI_C55_REG_AWB_CB_LOW				0x1b2d4
>> +#define MALI_C55_AWB_CB_LOW_MASK			GENMASK(11, 0)
>> +
>> +/* AEXP Metering Histogram Configuration */
>> +#define MALI_C55_REG_AEXP_HIST_BASE			0x1b730
>> +#define MALI_C55_REG_AEXP_IHIST_BASE			0x1bbac
>> +#define MALI_C55_AEXP_HIST_SKIP_OFFSET			0
>> +#define MALI_C55_AEXP_HIST_SKIP_X_MASK			GENMASK(2, 0)
>> +#define MALI_C55_AEXP_HIST_OFFSET_X_MASK		BIT(3)
>> +#define MALI_C55_AEXP_HIST_SKIP_Y_MASK			GENMASK(6, 4)
>> +#define MALI_C55_AEXP_HIST_OFFSET_Y_MASK		BIT(7)
>> +#define MALI_C55_AEXP_HIST_SCALE_OFFSET			4
>> +#define MALI_C55_AEXP_HIST_SCALE_BOTTOM_MASK		GENMASK(3, 0)
>> +#define MALI_C55_AEXP_HIST_SCALE_TOP_MASK		GENMASK(7, 4)
>> +#define MALI_C55_AEXP_HIST_PLANE_MODE_OFFSET		16
>> +#define MALI_C55_AEXP_HIST_PLANE_MODE_MASK		GENMASK(2, 0)
>> +#define MALI_C55_AEXP_HIST_NODES_USED_OFFSET		52
>> +#define MALI_C55_AEXP_HIST_NODES_USED_HORIZ_MASK	GENMASK(7, 0)
>> +#define MALI_C55_AEXP_HIST_NODES_USED_VERT_MASK		GENMASK(15, 8)
>> +#define MALI_C55_AEXP_HIST_ZONE_WEIGHTS_OFFSET		56
>> +#define MALI_C55_AEXP_HIST_ZONE_WEIGHT_MASK		0x0f0f0f0f
>> +
>>   /*
>>    * The Mali-C55 ISP has up to two output pipes; known as full resolution and
>>    * down scaled. The register space for these is laid out identically, but offset
Sakari Ailus June 14, 2024, 9:11 p.m. UTC | #5
Hi Dan,

On Fri, Jun 14, 2024 at 09:15:07PM +0100, Dan Scally wrote:
> > > +void mali_c55_params_write_config(struct mali_c55 *mali_c55)
> > > +{
> > > +	struct mali_c55_params *params = &mali_c55->params;
> > > +	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
> > > +	struct mali_c55_params_buffer *config;
> > > +	struct mali_c55_buffer *buf;
> > > +	size_t block_offset = 0;
> > > +
> > > +	spin_lock(&params->buffers.lock);
> > > +
> > > +	buf = list_first_entry_or_null(&params->buffers.queue,
> > > +				       struct mali_c55_buffer, queue);
> > > +	if (buf)
> > > +		list_del(&buf->queue);
> > > +	spin_unlock(&params->buffers.lock);
> > > +
> > > +	if (!buf)
> > > +		return;
> > > +
> > > +	buf->vb.sequence = mali_c55->isp.frame_sequence;
> > > +	config = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
> > > +
> > > +	if (config->total_size > MALI_C55_PARAMS_MAX_SIZE) {
> > > +		dev_dbg(mali_c55->dev, "Invalid parameters buffer size %lu\n",
> > > +			config->total_size);
> > > +		state = VB2_BUF_STATE_ERROR;
> > > +		goto err_buffer_done;
> > > +	}
> > > +
> > > +	/* Walk the list of parameter blocks and process them. */
> > > +	while (block_offset < config->total_size) {
> > > +		const struct mali_c55_block_handler *block_handler;
> > > +		struct mali_c55_params_block_header *block;
> > > +
> > > +		block = (struct mali_c55_params_block_header *)
> > > +			 &config->data[block_offset];
> > How do you ensure config->data does hold a full struct
> > mali_c33_params_block_header at block_offset (i.e. that the struct does not
> > exceed the memory available for config->data)?
> 
> 
> We don't currently...the data buffer is sized specifically to be large
> enough to accept a single instance of each possible struct that could be
> included, we could keep track of the blocks that we have seen already and
> ensure that none are seen twice...and that should guarantee that the
> remaining space is sufficient to hold whatever the last block is. Does that
> sound ok?

Ḯ'd add an explicit check here. It's more simple way to ensure memory
safety here: relying on a complex machinery that can't be trivially
validated does risk having grave bugs, not only now but later on as well as
modifications to the code are done.

> 
> > 
> > > +
> > > +		if (block->type >= MALI_C55_PARAM_BLOCK_SENTINEL) {
> > > +			dev_dbg(mali_c55->dev, "Invalid parameters block type\n");
> > > +			state = VB2_BUF_STATE_ERROR;
> > > +			goto err_buffer_done;
> > > +		}
> > > +
> > > +		block_handler = &mali_c55_block_handlers[block->type];
> > > +		if (block->size != block_handler->size) {
> > How do you ensure config->data has room for the block?
> I think through the same proposal as above.

Similarly here. You already even have the size of the blocks available
here.
Dan Scally June 14, 2024, 9:49 p.m. UTC | #6
Hi Sakari

On 14/06/2024 22:11, Sakari Ailus wrote:
> Hi Dan,
>
> On Fri, Jun 14, 2024 at 09:15:07PM +0100, Dan Scally wrote:
>>>> +void mali_c55_params_write_config(struct mali_c55 *mali_c55)
>>>> +{
>>>> +	struct mali_c55_params *params = &mali_c55->params;
>>>> +	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>>>> +	struct mali_c55_params_buffer *config;
>>>> +	struct mali_c55_buffer *buf;
>>>> +	size_t block_offset = 0;
>>>> +
>>>> +	spin_lock(&params->buffers.lock);
>>>> +
>>>> +	buf = list_first_entry_or_null(&params->buffers.queue,
>>>> +				       struct mali_c55_buffer, queue);
>>>> +	if (buf)
>>>> +		list_del(&buf->queue);
>>>> +	spin_unlock(&params->buffers.lock);
>>>> +
>>>> +	if (!buf)
>>>> +		return;
>>>> +
>>>> +	buf->vb.sequence = mali_c55->isp.frame_sequence;
>>>> +	config = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
>>>> +
>>>> +	if (config->total_size > MALI_C55_PARAMS_MAX_SIZE) {
>>>> +		dev_dbg(mali_c55->dev, "Invalid parameters buffer size %lu\n",
>>>> +			config->total_size);
>>>> +		state = VB2_BUF_STATE_ERROR;
>>>> +		goto err_buffer_done;
>>>> +	}
>>>> +
>>>> +	/* Walk the list of parameter blocks and process them. */
>>>> +	while (block_offset < config->total_size) {
>>>> +		const struct mali_c55_block_handler *block_handler;
>>>> +		struct mali_c55_params_block_header *block;
>>>> +
>>>> +		block = (struct mali_c55_params_block_header *)
>>>> +			 &config->data[block_offset];
>>> How do you ensure config->data does hold a full struct
>>> mali_c33_params_block_header at block_offset (i.e. that the struct does not
>>> exceed the memory available for config->data)?
>>
>> We don't currently...the data buffer is sized specifically to be large
>> enough to accept a single instance of each possible struct that could be
>> included, we could keep track of the blocks that we have seen already and
>> ensure that none are seen twice...and that should guarantee that the
>> remaining space is sufficient to hold whatever the last block is. Does that
>> sound ok?
> Ḯ'd add an explicit check here.


How would you do the check, sorry?

> It's more simple way to ensure memory
> safety here: relying on a complex machinery that can't be trivially
> validated does risk having grave bugs, not only now but later on as well as
> modifications to the code are done.
>
>>>> +
>>>> +		if (block->type >= MALI_C55_PARAM_BLOCK_SENTINEL) {
>>>> +			dev_dbg(mali_c55->dev, "Invalid parameters block type\n");
>>>> +			state = VB2_BUF_STATE_ERROR;
>>>> +			goto err_buffer_done;
>>>> +		}
>>>> +
>>>> +		block_handler = &mali_c55_block_handlers[block->type];
>>>> +		if (block->size != block_handler->size) {
>>> How do you ensure config->data has room for the block?
>> I think through the same proposal as above.
> Similarly here. You already even have the size of the blocks available
> here.
>
Laurent Pinchart June 16, 2024, 9:32 p.m. UTC | #7
On Fri, Jun 14, 2024 at 10:49:37PM +0100, Daniel Scally wrote:
> On 14/06/2024 22:11, Sakari Ailus wrote:
> > On Fri, Jun 14, 2024 at 09:15:07PM +0100, Dan Scally wrote:
> >>>> +void mali_c55_params_write_config(struct mali_c55 *mali_c55)
> >>>> +{
> >>>> +	struct mali_c55_params *params = &mali_c55->params;
> >>>> +	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
> >>>> +	struct mali_c55_params_buffer *config;
> >>>> +	struct mali_c55_buffer *buf;
> >>>> +	size_t block_offset = 0;
> >>>> +
> >>>> +	spin_lock(&params->buffers.lock);
> >>>> +
> >>>> +	buf = list_first_entry_or_null(&params->buffers.queue,
> >>>> +				       struct mali_c55_buffer, queue);
> >>>> +	if (buf)
> >>>> +		list_del(&buf->queue);
> >>>> +	spin_unlock(&params->buffers.lock);
> >>>> +
> >>>> +	if (!buf)
> >>>> +		return;
> >>>> +
> >>>> +	buf->vb.sequence = mali_c55->isp.frame_sequence;
> >>>> +	config = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
> >>>> +
> >>>> +	if (config->total_size > MALI_C55_PARAMS_MAX_SIZE) {
> >>>> +		dev_dbg(mali_c55->dev, "Invalid parameters buffer size %lu\n",
> >>>> +			config->total_size);
> >>>> +		state = VB2_BUF_STATE_ERROR;
> >>>> +		goto err_buffer_done;
> >>>> +	}
> >>>> +
> >>>> +	/* Walk the list of parameter blocks and process them. */
> >>>> +	while (block_offset < config->total_size) {
> >>>> +		const struct mali_c55_block_handler *block_handler;
> >>>> +		struct mali_c55_params_block_header *block;
> >>>> +
> >>>> +		block = (struct mali_c55_params_block_header *)
> >>>> +			 &config->data[block_offset];
> >>>
> >>> How do you ensure config->data does hold a full struct
> >>> mali_c33_params_block_header at block_offset (i.e. that the struct does not
> >>> exceed the memory available for config->data)?
> >>
> >> We don't currently...the data buffer is sized specifically to be large
> >> enough to accept a single instance of each possible struct that could be
> >> included, we could keep track of the blocks that we have seen already and
> >> ensure that none are seen twice...and that should guarantee that the
> >> remaining space is sufficient to hold whatever the last block is. Does that
> >> sound ok?
> >
> > Ḯ'd add an explicit check here.
> 
> How would you do the check, sorry?

You could simply change the while() loop to

	max_offset = config->total_size - sizeof(mali_c55_params_block_header);
	while (block_offset <= max_offset) {

That would ensure that you always have enough space left for a header.
Within the loop, you will need to check that block->size doesn't go past
the end of the remaining space. Please also check the code for integer
overflows.

> > It's more simple way to ensure memory
> > safety here: relying on a complex machinery that can't be trivially
> > validated does risk having grave bugs, not only now but later on as well as
> > modifications to the code are done.
> >
> >>>> +
> >>>> +		if (block->type >= MALI_C55_PARAM_BLOCK_SENTINEL) {
> >>>> +			dev_dbg(mali_c55->dev, "Invalid parameters block type\n");
> >>>> +			state = VB2_BUF_STATE_ERROR;
> >>>> +			goto err_buffer_done;
> >>>> +		}
> >>>> +
> >>>> +		block_handler = &mali_c55_block_handlers[block->type];
> >>>> +		if (block->size != block_handler->size) {
> >>>
> >>> How do you ensure config->data has room for the block?
> >>
> >> I think through the same proposal as above.
> >
> > Similarly here. You already even have the size of the blocks available
> > here.