[PATCH v3 07/17] drm/imagination: Add GPU ID parsing and firmware loading

Adam Ford aford173 at gmail.com
Mon Jun 26 15:38:17 UTC 2023


On Mon, Jun 26, 2023 at 8:22 AM Frank Binns <Frank.Binns at imgtec.com> wrote:
>
> Hi Adam,
>
> On Sat, 2023-06-17 at 07:48 -0500, Adam Ford wrote:
> > On Tue, Jun 13, 2023 at 10:20 AM Sarah Walker <sarah.walker at imgtec.com> wrote:
> > > Read the GPU ID register at probe time and select the correct
> > > features/quirks/enhancements. Use the GPU ID to form the firmware
> > > file name and load the firmware.
> >
> > I have a Rogue 6250 variant, but the BVNC is returning a slightly
> > different revision than the firmware that's currently available, and
> > the older firmware for the vendor driver doesn't work with this new
> > code.
> >
> > Linux responds with Unsupported BVNC: 4.45.2.58.  From what I can
> > tell, the closest available firmware is 4.40.2.51.
> >
> > Will there be more firmware variants in the future or will there be
> > some options to build the firmware somehow?
>
> We don't plan to support the SoC vendor provided firmware binaries as this will
> mean having to deal with many different versions of the firmware and its
> interface. Specifically, the interface can change in backwards incompatible ways
> between releases, it changes based on the hardware feature set & bugs and it's
> split across userspace & the kernel. This makes these SoC provided firmware
> binaries very difficult to support in this new driver.

Thanks for the response.

That makes sense.  I would hope that various SoC vendors would jump on
the bandwagon to work with your group to get their hardware supported.
>
> As an alternative, we'll be releasing firmware binaries as we add support for
> more Rogue GPUs. We'll also release binaries upon request, in case others in the
> community want to work on support in the meantime - we're just getting things
> set up for this at the moment.

The Mesa side of things appears to be missing some documentation, and
the power VR stuff still appears listed as experimental.  Is there
some documentation somewhere that would explain to someone how to go
about porting the Rogue 6250 to a different hardware variant of the
6250?  I don't really know the difference between BVNC of 4.45.2.58
and 4.40.2.51, but I can't imagine they are drastically different.

adam
>
> Thanks
> Frank
>
> >
> > adam
> >
> >
> >
> >
> > > The features/quirks/enhancements arrays are currently hardcoded in
> > > the driver for the supported GPUs. We are looking at moving this
> > > information to the firmware image.
> > >
> > > Signed-off-by: Sarah Walker <sarah.walker at imgtec.com>
> > > ---
> > >  drivers/gpu/drm/imagination/Makefile          |   1 +
> > >  drivers/gpu/drm/imagination/pvr_device.c      | 359 ++++++++++++
> > >  drivers/gpu/drm/imagination/pvr_device.h      | 221 +++++++
> > >  drivers/gpu/drm/imagination/pvr_device_info.c | 223 +++++++
> > >  drivers/gpu/drm/imagination/pvr_device_info.h | 133 +++++
> > >  drivers/gpu/drm/imagination/pvr_drv.c         | 553 +++++++++++++++++-
> > >  drivers/gpu/drm/imagination/pvr_drv.h         | 108 ++++
> > >  drivers/gpu/drm/imagination/pvr_fw.h          |  20 +
> > >  8 files changed, 1617 insertions(+), 1 deletion(-)
> > >  create mode 100644 drivers/gpu/drm/imagination/pvr_device_info.c
> > >  create mode 100644 drivers/gpu/drm/imagination/pvr_device_info.h
> > >  create mode 100644 drivers/gpu/drm/imagination/pvr_fw.h
> > >
> > > diff --git a/drivers/gpu/drm/imagination/Makefile b/drivers/gpu/drm/imagination/Makefile
> > > index 186f920d615b..d713b1280776 100644
> > > --- a/drivers/gpu/drm/imagination/Makefile
> > > +++ b/drivers/gpu/drm/imagination/Makefile
> > > @@ -5,6 +5,7 @@ subdir-ccflags-y := -I$(srctree)/$(src)
> > >
> > >  powervr-y := \
> > >         pvr_device.o \
> > > +       pvr_device_info.o \
> > >         pvr_drv.o \
> > >
> > >  obj-$(CONFIG_DRM_POWERVR) += powervr.o
> > > diff --git a/drivers/gpu/drm/imagination/pvr_device.c b/drivers/gpu/drm/imagination/pvr_device.c
> > > index 790c36cebec1..2e03763f2eb7 100644
> > > --- a/drivers/gpu/drm/imagination/pvr_device.c
> > > +++ b/drivers/gpu/drm/imagination/pvr_device.c
> > > @@ -2,20 +2,32 @@
> > >  /* Copyright (c) 2022 Imagination Technologies Ltd. */
> > >
> > >  #include "pvr_device.h"
> > > +#include "pvr_device_info.h"
> > > +
> > > +#include "pvr_fw.h"
> > > +#include "pvr_rogue_cr_defs.h"
> > >
> > >  #include <drm/drm_print.h>
> > >
> > > +#include <linux/bitfield.h>
> > >  #include <linux/clk.h>
> > >  #include <linux/compiler_attributes.h>
> > >  #include <linux/compiler_types.h>
> > >  #include <linux/dma-mapping.h>
> > >  #include <linux/err.h>
> > > +#include <linux/firmware.h>
> > >  #include <linux/gfp.h>
> > > +#include <linux/interrupt.h>
> > >  #include <linux/platform_device.h>
> > > +#include <linux/pm_runtime.h>
> > >  #include <linux/regulator/consumer.h>
> > >  #include <linux/slab.h>
> > >  #include <linux/stddef.h>
> > >  #include <linux/types.h>
> > > +#include <linux/workqueue.h>
> > > +
> > > +/* Major number for the supported version of the firmware. */
> > > +#define PVR_FW_VERSION_MAJOR 1
> > >
> > >  /**
> > >   * pvr_device_reg_init() - Initialize kernel access to a PowerVR device's
> > > @@ -205,6 +217,246 @@ pvr_device_regulator_init(struct pvr_device *pvr_dev)
> > >         return err;
> > >  }
> > >
> > > +/**
> > > + * pvr_device_clk_core_get_freq - Get current PowerVR device core clock frequency
> > > + * @pvr_dev: Target PowerVR device.
> > > + * @freq_out: Pointer to location to store core clock frequency in Hz.
> > > + *
> > > + * Returns:
> > > + *  * 0 on success, or
> > > + *  * -%EINVAL if frequency can not be determined.
> > > + */
> > > +int
> > > +pvr_device_clk_core_get_freq(struct pvr_device *pvr_dev, u32 *freq_out)
> > > +{
> > > +       u32 freq = clk_get_rate(pvr_dev->core_clk);
> > > +
> > > +       if (!freq)
> > > +               return -EINVAL;
> > > +
> > > +       *freq_out = freq;
> > > +       return 0;
> > > +}
> > > +
> > > +/**
> > > + * pvr_build_firmware_filename() - Construct a PowerVR firmware filename
> > > + * @pvr_dev: Target PowerVR device.
> > > + * @base: First part of the filename.
> > > + * @major: Major version number.
> > > + *
> > > + * A PowerVR firmware filename consists of three parts separated by underscores
> > > + * (``'_'``) along with a '.fw' file suffix. The first part is the exact value
> > > + * of @base, the second part is the hardware version string derived from @pvr_fw
> > > + * and the final part is the firmware version number constructed from @major with
> > > + * a 'v' prefix, e.g. powervr/rogue_4.40.2.51_v1.fw.
> > > + *
> > > + * The returned string will have been slab allocated and must be freed with
> > > + * kfree().
> > > + *
> > > + * Return:
> > > + *  * The constructed filename on success, or
> > > + *  * Any error returned by kasprintf().
> > > + */
> > > +static char *
> > > +pvr_build_firmware_filename(struct pvr_device *pvr_dev, const char *base,
> > > +                           u8 major)
> > > +{
> > > +       struct pvr_gpu_id *gpu_id = &pvr_dev->gpu_id;
> > > +
> > > +       return kasprintf(GFP_KERNEL, "%s_%d.%d.%d.%d_v%d.fw", base, gpu_id->b,
> > > +                        gpu_id->v, gpu_id->n, gpu_id->c, major);
> > > +}
> > > +
> > > +/**
> > > + * pvr_request_firmware() - Load firmware for a PowerVR device
> > > + * @pvr_dev: Target PowerVR device.
> > > + *
> > > + * See pvr_build_firmware_filename() for details on firmware file naming.
> > > + *
> > > + * Return:
> > > + *  * 0 on success,
> > > + *  * Any error returned by pvr_build_firmware_filename(), or
> > > + *  * Any error returned by request_firmware().
> > > + */
> > > +static int
> > > +pvr_request_firmware(struct pvr_device *pvr_dev)
> > > +{
> > > +       struct drm_device *drm_dev = &pvr_dev->base;
> > > +       char *filename;
> > > +       const struct firmware *fw;
> > > +       int err;
> > > +
> > > +       filename = pvr_build_firmware_filename(pvr_dev, "powervr/rogue",
> > > +                                              PVR_FW_VERSION_MAJOR);
> > > +       if (IS_ERR(filename))
> > > +               return PTR_ERR(filename);
> > > +
> > > +       /*
> > > +        * This function takes a copy of &filename, meaning we can free our
> > > +        * instance before returning.
> > > +        */
> > > +       err = request_firmware(&fw, filename, pvr_dev->base.dev);
> > > +       if (err) {
> > > +               drm_err(drm_dev, "failed to load firmware %s (err=%d)\n",
> > > +                       filename, err);
> > > +               goto err_free_filename;
> > > +       }
> > > +
> > > +       drm_info(drm_dev, "loaded firmware %s\n", filename);
> > > +       kfree(filename);
> > > +
> > > +       pvr_dev->fw_dev.firmware = fw;
> > > +
> > > +       return 0;
> > > +
> > > +err_free_filename:
> > > +       kfree(filename);
> > > +
> > > +       return err;
> > > +}
> > > +
> > > +/**
> > > + * pvr_load_gpu_id() - Load a PowerVR device's GPU ID (BVNC) from control registers.
> > > + *
> > > + * Sets struct pvr_dev.gpu_id.
> > > + *
> > > + * @pvr_dev: Target PowerVR device.
> > > + */
> > > +static void
> > > +pvr_load_gpu_id(struct pvr_device *pvr_dev)
> > > +{
> > > +       struct pvr_gpu_id *gpu_id = &pvr_dev->gpu_id;
> > > +       u64 bvnc;
> > > +
> > > +       /*
> > > +        * Try reading the BVNC using the newer (cleaner) method first. If the
> > > +        * B value is zero, fall back to the older method.
> > > +        */
> > > +       bvnc = PVR_CR_READ64(pvr_dev, CORE_ID__PBVNC);
> > > +
> > > +       gpu_id->b = PVR_CR_FIELD_GET(bvnc, CORE_ID__PBVNC__BRANCH_ID);
> > > +       if (gpu_id->b != 0) {
> > > +               gpu_id->v = PVR_CR_FIELD_GET(bvnc, CORE_ID__PBVNC__VERSION_ID);
> > > +               gpu_id->n = PVR_CR_FIELD_GET(bvnc, CORE_ID__PBVNC__NUMBER_OF_SCALABLE_UNITS);
> > > +               gpu_id->c = PVR_CR_FIELD_GET(bvnc, CORE_ID__PBVNC__CONFIG_ID);
> > > +       } else {
> > > +               u32 core_rev = PVR_CR_READ32(pvr_dev, CORE_REVISION);
> > > +               u32 core_id = PVR_CR_READ32(pvr_dev, CORE_ID);
> > > +               u16 core_id_config = PVR_CR_FIELD_GET(core_id, CORE_ID_CONFIG);
> > > +
> > > +               gpu_id->b = PVR_CR_FIELD_GET(core_rev, CORE_REVISION_MAJOR);
> > > +               gpu_id->v = PVR_CR_FIELD_GET(core_rev, CORE_REVISION_MINOR);
> > > +               gpu_id->n = FIELD_GET(0xFF00, core_id_config);
> > > +               gpu_id->c = FIELD_GET(0x00FF, core_id_config);
> > > +       }
> > > +}
> > > +
> > > +/**
> > > + * pvr_set_dma_info() - Set PowerVR device DMA information
> > > + * @pvr_dev: Target PowerVR device.
> > > + *
> > > + * Sets the DMA mask and max segment size for the PowerVR device.
> > > + *
> > > + * Return:
> > > + *  * 0 on success,
> > > + *  * Any error returned by PVR_FEATURE_VALUE(), or
> > > + *  * Any error returned by dma_set_mask().
> > > + */
> > > +
> > > +static int
> > > +pvr_set_dma_info(struct pvr_device *pvr_dev)
> > > +{
> > > +       struct drm_device *drm_dev = from_pvr_device(pvr_dev);
> > > +       u16 phys_bus_width;
> > > +       int err;
> > > +
> > > +       err = PVR_FEATURE_VALUE(pvr_dev, phys_bus_width, &phys_bus_width);
> > > +       if (err) {
> > > +               drm_err(drm_dev, "Failed to get device physical bus width\n");
> > > +               return err;
> > > +       }
> > > +
> > > +       /*
> > > +        * See the comment on &pvr_drm_driver.prime_fd_to_handle for an
> > > +        * explanation of the dma_set_mask function and dma_set_max_seg_size
> > > +        * calls below.
> > > +        */
> > > +       err = dma_set_mask(drm_dev->dev, DMA_BIT_MASK(phys_bus_width));
> > > +       if (err) {
> > > +               drm_err(drm_dev, "Failed to set DMA mask (err=%d)\n", err);
> > > +               return err;
> > > +       }
> > > +
> > > +       dma_set_max_seg_size(drm_dev->dev, UINT_MAX);
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +/**
> > > + * pvr_device_gpu_init() - GPU-specific initialization for a PowerVR device
> > > + * @pvr_dev: Target PowerVR device.
> > > + *
> > > + * The following steps are taken to ensure the device is ready:
> > > + *
> > > + *  1. Read the hardware version information from control registers,
> > > + *  2. Initialise the hardware feature information,
> > > + *  3. Setup the device DMA information,
> > > + *  4. Setup the device-scoped memory context, and
> > > + *  5. Load firmware into the device.
> > > + *
> > > + * Return:
> > > + *  * 0 on success,
> > > + *  * -%ENODEV if the GPU is not supported,
> > > + *  * Any error returned by pvr_set_dma_info(),
> > > + *  * Any error returned by pvr_memory_context_init(), or
> > > + *  * Any error returned by pvr_request_firmware().
> > > + */
> > > +static int
> > > +pvr_device_gpu_init(struct pvr_device *pvr_dev)
> > > +{
> > > +       int err;
> > > +
> > > +       pvr_load_gpu_id(pvr_dev);
> > > +
> > > +       err = pvr_device_info_init(pvr_dev);
> > > +       if (err)
> > > +               goto err_out;
> > > +
> > > +       if (PVR_HAS_FEATURE(pvr_dev, meta)) {
> > > +               pvr_dev->fw_dev.processor_type = PVR_FW_PROCESSOR_TYPE_META;
> > > +       } else if (PVR_HAS_FEATURE(pvr_dev, mips)) {
> > > +               pvr_dev->fw_dev.processor_type = PVR_FW_PROCESSOR_TYPE_MIPS;
> > > +       } else if (PVR_HAS_FEATURE(pvr_dev, riscv_fw_processor)) {
> > > +               pvr_dev->fw_dev.processor_type = PVR_FW_PROCESSOR_TYPE_RISCV;
> > > +       } else {
> > > +               err = -EINVAL;
> > > +               goto err_out;
> > > +       }
> > > +
> > > +       err = pvr_set_dma_info(pvr_dev);
> > > +       if (err)
> > > +               goto err_out;
> > > +
> > > +       err = pvr_request_firmware(pvr_dev);
> > > +       if (err)
> > > +               goto err_out;
> > > +
> > > +       return 0;
> > > +
> > > +err_out:
> > > +       return err;
> > > +}
> > > +
> > > +/**
> > > + * pvr_device_gpu_fini() - GPU-specific deinitialization for a PowerVR device
> > > + * @pvr_dev: Target PowerVR device.
> > > + */
> > > +static void
> > > +pvr_device_gpu_fini(struct pvr_device *pvr_dev)
> > > +{
> > > +       release_firmware(pvr_dev->fw_dev.firmware);
> > > +}
> > > +
> > >  /**
> > >   * pvr_device_init() - Initialize a PowerVR device
> > >   * @pvr_dev: Target PowerVR device.
> > > @@ -240,6 +492,11 @@ pvr_device_init(struct pvr_device *pvr_dev)
> > >
> > >         /* Map the control registers into memory. */
> > >         err = pvr_device_reg_init(pvr_dev);
> > > +       if (err)
> > > +               goto err_device_clk_fini;
> > > +
> > > +       /* Perform GPU-specific initialization steps. */
> > > +       err = pvr_device_gpu_init(pvr_dev);
> > >         if (err)
> > >                 goto err_device_reg_fini;
> > >
> > > @@ -266,6 +523,108 @@ pvr_device_fini(struct pvr_device *pvr_dev)
> > >          * Deinitialization stages are performed in reverse order compared to
> > >          * the initialization stages in pvr_device_init().
> > >          */
> > > +       pvr_device_gpu_fini(pvr_dev);
> > >         pvr_device_reg_fini(pvr_dev);
> > >         pvr_device_clk_fini(pvr_dev);
> > >  }
> > > +
> > > +bool
> > > +pvr_device_has_uapi_quirk(struct pvr_device *pvr_dev, u32 quirk)
> > > +{
> > > +       switch (quirk) {
> > > +       case 47217:
> > > +               return PVR_HAS_QUIRK(pvr_dev, 47217);
> > > +       case 48545:
> > > +               return PVR_HAS_QUIRK(pvr_dev, 48545);
> > > +       case 49927:
> > > +               return PVR_HAS_QUIRK(pvr_dev, 49927);
> > > +       case 51764:
> > > +               return PVR_HAS_QUIRK(pvr_dev, 51764);
> > > +       case 62269:
> > > +               return PVR_HAS_QUIRK(pvr_dev, 62269);
> > > +       default:
> > > +               return false;
> > > +       };
> > > +}
> > > +
> > > +bool
> > > +pvr_device_has_uapi_enhancement(struct pvr_device *pvr_dev, u32 enhancement)
> > > +{
> > > +       switch (enhancement) {
> > > +       case 35421:
> > > +               return PVR_HAS_ENHANCEMENT(pvr_dev, 35421);
> > > +       case 42064:
> > > +               return PVR_HAS_ENHANCEMENT(pvr_dev, 42064);
> > > +       default:
> > > +               return false;
> > > +       };
> > > +}
> > > +
> > > +/**
> > > + * pvr_device_has_feature() - Look up device feature based on feature definition
> > > + * @pvr_dev: Device pointer.
> > > + * @feature: Feature to look up. Should be one of %PVR_FEATURE_*.
> > > + *
> > > + * Returns:
> > > + *  * %true if feature is present on device, or
> > > + *  * %false if feature is not present on device.
> > > + */
> > > +bool
> > > +pvr_device_has_feature(struct pvr_device *pvr_dev, u32 feature)
> > > +{
> > > +       switch (feature) {
> > > +       case PVR_FEATURE_CLUSTER_GROUPING:
> > > +               return PVR_HAS_FEATURE(pvr_dev, cluster_grouping);
> > > +
> > > +       case PVR_FEATURE_COMPUTE_MORTON_CAPABLE:
> > > +               return PVR_HAS_FEATURE(pvr_dev, compute_morton_capable);
> > > +
> > > +       case PVR_FEATURE_FB_CDC_V4:
> > > +               return PVR_HAS_FEATURE(pvr_dev, fb_cdc_v4);
> > > +
> > > +       case PVR_FEATURE_GPU_MULTICORE_SUPPORT:
> > > +               return PVR_HAS_FEATURE(pvr_dev, gpu_multicore_support);
> > > +
> > > +       case PVR_FEATURE_ISP_ZLS_D24_S8_PACKING_OGL_MODE:
> > > +               return PVR_HAS_FEATURE(pvr_dev, isp_zls_d24_s8_packing_ogl_mode);
> > > +
> > > +       case PVR_FEATURE_S7_TOP_INFRASTRUCTURE:
> > > +               return PVR_HAS_FEATURE(pvr_dev, s7_top_infrastructure);
> > > +
> > > +       case PVR_FEATURE_TESSELLATION:
> > > +               return PVR_HAS_FEATURE(pvr_dev, tessellation);
> > > +
> > > +       case PVR_FEATURE_TPU_DM_GLOBAL_REGISTERS:
> > > +               return PVR_HAS_FEATURE(pvr_dev, tpu_dm_global_registers);
> > > +
> > > +       case PVR_FEATURE_VDM_DRAWINDIRECT:
> > > +               return PVR_HAS_FEATURE(pvr_dev, vdm_drawindirect);
> > > +
> > > +       case PVR_FEATURE_VDM_OBJECT_LEVEL_LLS:
> > > +               return PVR_HAS_FEATURE(pvr_dev, vdm_object_level_lls);
> > > +
> > > +       case PVR_FEATURE_ZLS_SUBTILE:
> > > +               return PVR_HAS_FEATURE(pvr_dev, zls_subtile);
> > > +
> > > +       /* Derived features. */
> > > +       case PVR_FEATURE_CDM_USER_MODE_QUEUE: {
> > > +               u8 cdm_control_stream_format = 0;
> > > +
> > > +               PVR_FEATURE_VALUE(pvr_dev, cdm_control_stream_format, &cdm_control_stream_format);
> > > +               return (cdm_control_stream_format >= 2 && cdm_control_stream_format <= 4);
> > > +       }
> > > +
> > > +       case PVR_FEATURE_REQUIRES_FB_CDC_ZLS_SETUP:
> > > +               if (PVR_HAS_FEATURE(pvr_dev, fbcdc_algorithm)) {
> > > +                       u8 fbcdc_algorithm = 0;
> > > +
> > > +                       PVR_FEATURE_VALUE(pvr_dev, fbcdc_algorithm, &fbcdc_algorithm);
> > > +                       return (fbcdc_algorithm < 3 || PVR_HAS_FEATURE(pvr_dev, fb_cdc_v4));
> > > +               }
> > > +               return false;
> > > +
> > > +       default:
> > > +               WARN(true, "Looking up undefined feature %u\n", feature);
> > > +               return false;
> > > +       }
> > > +}
> > > diff --git a/drivers/gpu/drm/imagination/pvr_device.h b/drivers/gpu/drm/imagination/pvr_device.h
> > > index c0dd0562844c..d9d0cf5a3820 100644
> > > --- a/drivers/gpu/drm/imagination/pvr_device.h
> > > +++ b/drivers/gpu/drm/imagination/pvr_device.h
> > > @@ -4,6 +4,9 @@
> > >  #ifndef PVR_DEVICE_H
> > >  #define PVR_DEVICE_H
> > >
> > > +#include "pvr_device_info.h"
> > > +#include "pvr_fw.h"
> > > +
> > >  #include <drm/drm_device.h>
> > >  #include <drm/drm_file.h>
> > >  #include <drm/drm_mm.h>
> > > @@ -31,6 +34,26 @@ struct firmware;
> > >  /* Forward declaration from <linux/regulator/consumer.h>. */
> > >  struct regulator;
> > >
> > > +/**
> > > + * struct pvr_gpu_id - Hardware GPU ID information for a PowerVR device
> > > + * @b: Branch ID.
> > > + * @v: Version ID.
> > > + * @n: Number of scalable units.
> > > + * @c: Config ID.
> > > + */
> > > +struct pvr_gpu_id {
> > > +       u16 b, v, n, c;
> > > +};
> > > +
> > > +/**
> > > + * struct pvr_fw_version - Firmware version information
> > > + * @major: Major version number.
> > > + * @minor: Minor version number.
> > > + */
> > > +struct pvr_fw_version {
> > > +       u16 major, minor;
> > > +};
> > > +
> > >  /**
> > >   * struct pvr_device - powervr-specific wrapper for &struct drm_device
> > >   */
> > > @@ -43,6 +66,35 @@ struct pvr_device {
> > >          */
> > >         struct drm_device base;
> > >
> > > +       /** @gpu_id: GPU ID detected at runtime. */
> > > +       struct pvr_gpu_id gpu_id;
> > > +
> > > +       /**
> > > +        * @features: Hardware feature information.
> > > +        *
> > > +        * Do not access this member directly, instead use PVR_HAS_FEATURE()
> > > +        * or PVR_FEATURE_VALUE() macros.
> > > +        */
> > > +       struct pvr_device_features features;
> > > +
> > > +       /**
> > > +        * @quirks: Hardware quirk information.
> > > +        *
> > > +        * Do not access this member directly, instead use PVR_HAS_QUIRK().
> > > +        */
> > > +       struct pvr_device_quirks quirks;
> > > +
> > > +       /**
> > > +        * @enhancements: Hardware enhancement information.
> > > +        *
> > > +        * Do not access this member directly, instead use
> > > +        * PVR_HAS_ENHANCEMENT().
> > > +        */
> > > +       struct pvr_device_enhancements enhancements;
> > > +
> > > +       /** @fw_version: Firmware version detected at runtime. */
> > > +       struct pvr_fw_version fw_version;
> > > +
> > >         /** @regs_resource: Resource representing device control registers. */
> > >         struct resource *regs_resource;
> > >
> > > @@ -65,6 +117,9 @@ struct pvr_device {
> > >
> > >         /** @regulator: Power regulator. */
> > >         struct regulator *regulator;
> > > +
> > > +       /** @fw_dev: Firmware related data. */
> > > +       struct pvr_fw_device fw_dev;
> > >  };
> > >
> > >  /**
> > > @@ -85,8 +140,79 @@ struct pvr_file {
> > >          *           to_pvr_device().
> > >          */
> > >         struct pvr_device *pvr_dev;
> > > +
> > >  };
> > >
> > > +/**
> > > + * PVR_HAS_FEATURE() - Tests whether a PowerVR device has a given feature
> > > + * @pvr_dev: [IN] Target PowerVR device.
> > > + * @feature: [IN] Hardware feature name.
> > > + *
> > > + * Feature names are derived from those found in &struct pvr_device_features by
> > > + * dropping the 'has_' prefix, which is applied by this macro.
> > > + *
> > > + * Return:
> > > + *  * true if the named feature is present in the hardware
> > > + *  * false if the named feature is not present in the hardware
> > > + */
> > > +#define PVR_HAS_FEATURE(pvr_dev, feature) ((pvr_dev)->features.has_##feature)
> > > +
> > > +/**
> > > + * PVR_FEATURE_VALUE() - Gets a PowerVR device feature value
> > > + * @pvr_dev: [IN] Target PowerVR device.
> > > + * @feature: [IN] Feature name.
> > > + * @value_out: [OUT] Feature value.
> > > + *
> > > + * This macro will get a feature value for those features that have values.
> > > + * If the feature is not present, nothing will be stored to @value_out.
> > > + *
> > > + * Feature names are derived from those found in &struct pvr_device_features by
> > > + * dropping the 'has_' prefix.
> > > + *
> > > + * Return:
> > > + *  * 0 on success, or
> > > + *  * -%EINVAL if the named feature is not present in the hardware
> > > + */
> > > +#define PVR_FEATURE_VALUE(pvr_dev, feature, value_out)             \
> > > +       ({                                                         \
> > > +               struct pvr_device *_pvr_dev = pvr_dev;             \
> > > +               int _ret = -EINVAL;                                \
> > > +               if (_pvr_dev->features.has_##feature) {            \
> > > +                       *(value_out) = _pvr_dev->features.feature; \
> > > +                       _ret = 0;                                  \
> > > +               }                                                  \
> > > +               _ret;                                              \
> > > +       })
> > > +
> > > +/**
> > > + * PVR_HAS_QUIRK() - Tests whether a physical device has a given quirk
> > > + * @pvr_dev: [IN] Target PowerVR device.
> > > + * @quirk: [IN] Hardware quirk name.
> > > + *
> > > + * Quirk numbers are derived from those found in #pvr_device_quirks by
> > > + * dropping the 'has_brn' prefix, which is applied by this macro.
> > > + *
> > > + * Returns
> > > + *  * true if the quirk is present in the hardware, or
> > > + *  * false if the quirk is not present in the hardware.
> > > + */
> > > +#define PVR_HAS_QUIRK(pvr_dev, quirk) ((pvr_dev)->quirks.has_brn##quirk)
> > > +
> > > +/**
> > > + * PVR_HAS_ENHANCEMENT() - Tests whether a physical device has a given
> > > + *                         enhancement
> > > + * @pvr_dev: [IN] Target PowerVR device.
> > > + * @enhancement: [IN] Hardware enhancement name.
> > > + *
> > > + * Enhancement numbers are derived from those found in #pvr_device_enhancements
> > > + * by dropping the 'has_ern' prefix, which is applied by this macro.
> > > + *
> > > + * Returns
> > > + *  * true if the enhancement is present in the hardware, or
> > > + *  * false if the enhancement is not present in the hardware.
> > > + */
> > > +#define PVR_HAS_ENHANCEMENT(pvr_dev, enhancement) ((pvr_dev)->enhancements.has_ern##enhancement)
> > > +
> > >  static __always_inline struct drm_device *
> > >  from_pvr_device(struct pvr_device *pvr_dev)
> > >  {
> > > @@ -111,12 +237,80 @@ to_pvr_file(struct drm_file *file)
> > >         return file->driver_priv;
> > >  }
> > >
> > > +/**
> > > + * PVR_PACKED_BVNC() - Packs B, V, N and C values into a 64-bit unsigned integer
> > > + * @b: Branch ID.
> > > + * @v: Version ID.
> > > + * @n: Number of scalable units.
> > > + * @c: Config ID.
> > > + *
> > > + * The packed layout is as follows:
> > > + *
> > > + *    +--------+--------+--------+-------+
> > > + *    | 63..48 | 47..32 | 31..16 | 15..0 |
> > > + *    +========+========+========+=======+
> > > + *    | B      | V      | N      | C     |
> > > + *    +--------+--------+--------+-------+
> > > + *
> > > + * pvr_gpu_id_to_packed_bvnc() should be used instead of this macro when a
> > > + * &struct pvr_gpu_id is available in order to ensure proper type checking.
> > > + *
> > > + * Return: Packed BVNC.
> > > + */
> > > +/* clang-format off */
> > > +#define PVR_PACKED_BVNC(b, v, n, c) \
> > > +       ((((u64)(b) & GENMASK_ULL(15, 0)) << 48) | \
> > > +        (((u64)(v) & GENMASK_ULL(15, 0)) << 32) | \
> > > +        (((u64)(n) & GENMASK_ULL(15, 0)) << 16) | \
> > > +        (((u64)(c) & GENMASK_ULL(15, 0)) <<  0))
> > > +/* clang-format on */
> > > +
> > > +/**
> > > + * pvr_gpu_id_to_packed_bvnc() - Packs B, V, N and C values into a 64-bit
> > > + * unsigned integer
> > > + * @gpu_id: GPU ID.
> > > + *
> > > + * The packed layout is as follows:
> > > + *
> > > + *    +--------+--------+--------+-------+
> > > + *    | 63..48 | 47..32 | 31..16 | 15..0 |
> > > + *    +========+========+========+=======+
> > > + *    | B      | V      | N      | C     |
> > > + *    +--------+--------+--------+-------+
> > > + *
> > > + * This should be used in preference to PVR_PACKED_BVNC() when a &struct
> > > + * pvr_gpu_id is available in order to ensure proper type checking.
> > > + *
> > > + * Return: Packed BVNC.
> > > + */
> > > +static __always_inline u64
> > > +pvr_gpu_id_to_packed_bvnc(struct pvr_gpu_id *gpu_id)
> > > +{
> > > +       return PVR_PACKED_BVNC(gpu_id->b, gpu_id->v, gpu_id->n, gpu_id->c);
> > > +}
> > > +
> > > +static __always_inline void
> > > +packed_bvnc_to_pvr_gpu_id(u64 bvnc, struct pvr_gpu_id *gpu_id)
> > > +{
> > > +       gpu_id->b = (bvnc & GENMASK_ULL(63, 48)) >> 48;
> > > +       gpu_id->v = (bvnc & GENMASK_ULL(47, 32)) >> 32;
> > > +       gpu_id->n = (bvnc & GENMASK_ULL(31, 16)) >> 16;
> > > +       gpu_id->c = bvnc & GENMASK_ULL(15, 0);
> > > +}
> > > +
> > >  int pvr_device_init(struct pvr_device *pvr_dev);
> > >  void pvr_device_fini(struct pvr_device *pvr_dev);
> > >
> > >  int
> > >  pvr_device_clk_core_get_freq(struct pvr_device *pvr_dev, u32 *freq_out);
> > >
> > > +bool
> > > +pvr_device_has_uapi_quirk(struct pvr_device *pvr_dev, u32 quirk);
> > > +bool
> > > +pvr_device_has_uapi_enhancement(struct pvr_device *pvr_dev, u32 enhancement);
> > > +bool
> > > +pvr_device_has_feature(struct pvr_device *pvr_dev, u32 feature);
> > > +
> > >  /**
> > >   * PVR_CR_READ32() - Read a 32-bit register from a PowerVR device
> > >   * @pvr_dev: Target PowerVR device.
> > > @@ -286,6 +480,29 @@ pvr_cr_poll_reg64(struct pvr_device *pvr_dev, u32 reg_addr, u64 reg_value,
> > >                 (value & reg_mask) == reg_value, 0, timeout_usec);
> > >  }
> > >
> > > +/**
> > > + * pvr_round_up_to_cacheline_size() - Round up a provided size to be cacheline
> > > + *                                    aligned
> > > + * @pvr_dev: Target PowerVR device.
> > > + * @size: Initial size, in bytes.
> > > + *
> > > + * Returns:
> > > + *  * Size aligned to cacheline size.
> > > + */
> > > +static __always_inline size_t
> > > +pvr_round_up_to_cacheline_size(struct pvr_device *pvr_dev, size_t size)
> > > +{
> > > +       u16 slc_cacheline_size_in_bits = 0;
> > > +       u16 slc_cacheline_size_in_bytes;
> > > +
> > > +       WARN_ON(!PVR_HAS_FEATURE(pvr_dev, slc_cache_line_size_in_bits));
> > > +       PVR_FEATURE_VALUE(pvr_dev, slc_cache_line_size_in_bits,
> > > +                         &slc_cacheline_size_in_bits);
> > > +       slc_cacheline_size_in_bytes = slc_cacheline_size_in_bits / 8;
> > > +
> > > +       return round_up(size, slc_cacheline_size_in_bytes);
> > > +}
> > > +
> > >  /**
> > >   * DOC: IOCTL validation helpers
> > >   *
> > > @@ -380,4 +597,8 @@ pvr_ioctl_union_padding_check(void *instance, size_t union_offset,
> > >                                               __union_size, __member_size);  \
> > >         })
> > >
> > > +#define PVR_FW_PROCESSOR_TYPE_META  0
> > > +#define PVR_FW_PROCESSOR_TYPE_MIPS  1
> > > +#define PVR_FW_PROCESSOR_TYPE_RISCV 2
> > > +
> > >  #endif /* PVR_DEVICE_H */
> > > diff --git a/drivers/gpu/drm/imagination/pvr_device_info.c b/drivers/gpu/drm/imagination/pvr_device_info.c
> > > new file mode 100644
> > > index 000000000000..660324e7c3a3
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/imagination/pvr_device_info.c
> > > @@ -0,0 +1,223 @@
> > > +// SPDX-License-Identifier: GPL-2.0 OR MIT
> > > +/* Copyright (c) 2022 Imagination Technologies Ltd. */
> > > +
> > > +#include "pvr_device.h"
> > > +#include "pvr_device_info.h"
> > > +
> > > +#include <drm/drm_print.h>
> > > +
> > > +#include <linux/types.h>
> > > +
> > > +static const struct pvr_device_features pvr_device_4_V_2_51 = {
> > > +       .has_cdm_control_stream_format = true,
> > > +       .has_cluster_grouping = true,
> > > +       .has_common_store_size_in_dwords = true,
> > > +       .has_compute = true,
> > > +       .has_compute_morton_capable = true,
> > > +       .has_compute_overlap = true,
> > > +       .has_fbcdc_algorithm = true,
> > > +       .has_isp_max_tiles_in_flight = true,
> > > +       .has_isp_samples_per_pixel = true,
> > > +       .has_max_partitions = true,
> > > +       .has_meta = true,
> > > +       .has_meta_coremem_size = true,
> > > +       .has_num_clusters = true,
> > > +       .has_num_isp_ipp_pipes = true,
> > > +       .has_num_raster_pipes = true,
> > > +       .has_phys_bus_width = true,
> > > +       .has_slc_cache_line_size_in_bits = true,
> > > +       .has_tile_size_x = true,
> > > +       .has_tile_size_y = true,
> > > +       .has_usc_min_output_registers_per_pix = true,
> > > +       .has_virtual_address_space_bits = true,
> > > +       .has_xt_top_infrastructure = true,
> > > +       .has_zls_subtile = true,
> > > +
> > > +       .cdm_control_stream_format = 1,
> > > +       .common_store_size_in_dwords = 1280U * 4U * 4U,
> > > +       .fbcdc_algorithm = 2,
> > > +       .isp_max_tiles_in_flight = 4,
> > > +       .isp_samples_per_pixel = 2,
> > > +       .max_partitions = 8,
> > > +       .meta = true,
> > > +       .meta_coremem_size = 32,
> > > +       .num_clusters = 2,
> > > +       .num_isp_ipp_pipes = 8,
> > > +       .num_raster_pipes = 1,
> > > +       .phys_bus_width = 40,
> > > +       .slc_cache_line_size_in_bits = 512,
> > > +       .tile_size_x = 32,
> > > +       .tile_size_y = 32,
> > > +       .usc_min_output_registers_per_pix = 2,
> > > +       .virtual_address_space_bits = 40,
> > > +};
> > > +
> > > +static const struct pvr_device_quirks pvr_device_quirks_4_40_2_51 = {
> > > +       .has_brn44079 = true,
> > > +       .has_brn48492 = true,
> > > +       .has_brn48545 = true,
> > > +       .has_brn49927 = true,
> > > +       .has_brn51764 = true,
> > > +       .has_brn52354 = true,
> > > +       .has_brn62269 = true,
> > > +       .has_brn63142 = true,
> > > +       .has_brn66011 = true,
> > > +};
> > > +
> > > +static const struct pvr_device_enhancements pvr_device_enhancements_4_40_2_51 = {
> > > +       .has_ern35421 = true,
> > > +       .has_ern38020 = true,
> > > +       .has_ern38748 = true,
> > > +       .has_ern42064 = true,
> > > +};
> > > +
> > > +static const struct pvr_device_features pvr_device_33_V_11_3 = {
> > > +       .has_cdm_control_stream_format = true,
> > > +       .has_common_store_size_in_dwords = true,
> > > +       .has_compute = true,
> > > +       .has_isp_max_tiles_in_flight = true,
> > > +       .has_isp_samples_per_pixel = true,
> > > +       .has_max_partitions = true,
> > > +       .has_mips = true,
> > > +       .has_num_clusters = true,
> > > +       .has_num_isp_ipp_pipes = true,
> > > +       .has_num_raster_pipes = true,
> > > +       .has_phys_bus_width = true,
> > > +       .has_roguexe = true,
> > > +       .has_simple_internal_parameter_format = true,
> > > +       .has_slc_cache_line_size_in_bits = true,
> > > +       .has_sys_bus_secure_reset = true,
> > > +       .has_tile_size_x = true,
> > > +       .has_tile_size_y = true,
> > > +       .has_usc_min_output_registers_per_pix = true,
> > > +       .has_virtual_address_space_bits = true,
> > > +       .has_xe_memory_hierarchy = true,
> > > +
> > > +       .cdm_control_stream_format = 1,
> > > +       .common_store_size_in_dwords = 512U * 4U * 4U,
> > > +       .isp_max_tiles_in_flight = 1,
> > > +       .isp_samples_per_pixel = 1,
> > > +       .max_partitions = 4,
> > > +       .mips = true,
> > > +       .num_clusters = 1,
> > > +       .num_isp_ipp_pipes = 1,
> > > +       .num_raster_pipes = 1,
> > > +       .phys_bus_width = 36,
> > > +       .simple_internal_parameter_format = 2,
> > > +       .slc_cache_line_size_in_bits = 512,
> > > +       .tile_size_x = 16,
> > > +       .tile_size_y = 16,
> > > +       .usc_min_output_registers_per_pix = 1,
> > > +       .virtual_address_space_bits = 40,
> > > +};
> > > +
> > > +static const struct pvr_device_quirks pvr_device_quirks_33_15_11_3 = {
> > > +       .has_brn63553 = true,
> > > +};
> > > +
> > > +static const struct pvr_device_enhancements pvr_device_enhancements_33_15_11_3 = {
> > > +       .has_ern35421 = true,
> > > +       .has_ern38748 = true,
> > > +};
> > > +
> > > +static const struct pvr_device_features pvr_device_36_V_104_796 = {
> > > +       .has_cdm_control_stream_format = true,
> > > +       .has_common_store_size_in_dwords = true,
> > > +       .has_compute = true,
> > > +       .has_compute_overlap = true,
> > > +       .has_fbcdc_algorithm = true,
> > > +       .has_gpu_multicore_support = true,
> > > +       .has_isp_max_tiles_in_flight = true,
> > > +       .has_isp_samples_per_pixel = true,
> > > +       .has_max_partitions = true,
> > > +       .has_num_clusters = true,
> > > +       .has_num_isp_ipp_pipes = true,
> > > +       .has_num_raster_pipes = true,
> > > +       .has_phys_bus_width = true,
> > > +       .has_riscv_fw_processor = true,
> > > +       .has_roguexe = true,
> > > +       .has_simple_internal_parameter_format = true,
> > > +       .has_slc_cache_line_size_in_bits = true,
> > > +       .has_sys_bus_secure_reset = true,
> > > +       .has_tile_size_x = true,
> > > +       .has_tile_size_y = true,
> > > +       .has_tpu_dm_global_registers = true,
> > > +       .has_usc_min_output_registers_per_pix = true,
> > > +       .has_virtual_address_space_bits = true,
> > > +       .has_xe_memory_hierarchy = true,
> > > +       .has_xpu_max_slaves = true,
> > > +
> > > +       .cdm_control_stream_format = 1,
> > > +       .common_store_size_in_dwords = 1344U * 4U * 4U,
> > > +       .fbcdc_algorithm = 50,
> > > +       .isp_max_tiles_in_flight = 6,
> > > +       .isp_samples_per_pixel = 4,
> > > +       .max_partitions = 16,
> > > +       .num_clusters = 1,
> > > +       .num_isp_ipp_pipes = 6,
> > > +       .num_raster_pipes = 1,
> > > +       .phys_bus_width = 36,
> > > +       .riscv_fw_processor = true,
> > > +       .simple_internal_parameter_format = 2,
> > > +       .slc_cache_line_size_in_bits = 512,
> > > +       .tile_size_x = 16,
> > > +       .tile_size_y = 16,
> > > +       .usc_min_output_registers_per_pix = 2,
> > > +       .virtual_address_space_bits = 40,
> > > +       .xpu_max_slaves = 3,
> > > +};
> > > +
> > > +static const struct pvr_device_quirks pvr_device_quirks_36_53_104_796 = {
> > > +       .has_brn44079 = true,
> > > +};
> > > +
> > > +static const struct pvr_device_enhancements pvr_device_enhancements_36_53_104_796 = {
> > > +       .has_ern35421 = true,
> > > +       .has_ern38748 = true,
> > > +};
> > > +
> > > +/**
> > > + * pvr_device_info_init() - Initialize a PowerVR device's hardware features and quirks
> > > + * @pvr_dev: Target PowerVR device.
> > > + *
> > > + * This function relies on &pvr_dev.gpu_id having already been initialized. If
> > > + * PowerVR device version is supported then sets &pvr_dev.features and &pvr_dev.quirks.
> > > + *
> > > + * Return:
> > > + *  * 0 on success, or
> > > + *  * -%ENODEV if the device is not supported.
> > > + */
> > > +int
> > > +pvr_device_info_init(struct pvr_device *pvr_dev)
> > > +{
> > > +       struct drm_device *drm_dev = from_pvr_device(pvr_dev);
> > > +       struct pvr_gpu_id *gpu_id = &pvr_dev->gpu_id;
> > > +       const u64 bvnc = pvr_gpu_id_to_packed_bvnc(gpu_id);
> > > +
> > > +       /*
> > > +        * This macro results in a "Macros with multiple statements should be
> > > +        * enclosed in a do - while loop" checkpatch error. However, following
> > > +        * this advice would make the macro look a bit odd and isn't necessary
> > > +        * in this particular case, as the macro has a very specific use and a
> > > +        * very limited lifetime. The error can therefore be ignored.
> > > +        */
> > > +#define CASE_PACKED_BVNC_DEVICE_INFO(b, v, n, c)                  \
> > > +       case PVR_PACKED_BVNC(b, v, n, c):                         \
> > > +               pvr_dev->features = pvr_device_##b##_V_##n##_##c; \
> > > +               pvr_dev->quirks = pvr_device_quirks_##b##_##v##_##n##_##c; \
> > > +               pvr_dev->enhancements = pvr_device_enhancements_##b##_##v##_##n##_##c; \
> > > +               return 0
> > > +
> > > +       switch (bvnc) {
> > > +               CASE_PACKED_BVNC_DEVICE_INFO(4, 40, 2, 51);
> > > +               CASE_PACKED_BVNC_DEVICE_INFO(33, 15, 11, 3);
> > > +               CASE_PACKED_BVNC_DEVICE_INFO(36, 53, 104, 796);
> > > +       }
> > > +
> > > +#undef CASE_PACKED_BVNC_DEVICE_INFO
> > > +
> > > +       drm_warn(drm_dev, "Unsupported BVNC: %u.%u.%u.%u\n", gpu_id->b,
> > > +                gpu_id->v, gpu_id->n, gpu_id->c);
> > > +
> > > +       return -ENODEV;
> > > +}
> > > diff --git a/drivers/gpu/drm/imagination/pvr_device_info.h b/drivers/gpu/drm/imagination/pvr_device_info.h
> > > new file mode 100644
> > > index 000000000000..36ad09dea420
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/imagination/pvr_device_info.h
> > > @@ -0,0 +1,133 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> > > +/* Copyright (c) 2022 Imagination Technologies Ltd. */
> > > +
> > > +#ifndef PVR_DEVICE_INFO_H
> > > +#define PVR_DEVICE_INFO_H
> > > +
> > > +#include <linux/types.h>
> > > +
> > > +struct pvr_device;
> > > +
> > > +/*
> > > + * struct pvr_device_features - Hardware feature information
> > > + */
> > > +struct pvr_device_features {
> > > +       bool has_cdm_control_stream_format : 1;
> > > +       bool has_cluster_grouping : 1;
> > > +       bool has_common_store_size_in_dwords : 1;
> > > +       bool has_compute : 1;
> > > +       bool has_compute_morton_capable : 1;
> > > +       bool has_compute_overlap : 1;
> > > +       bool has_fb_cdc_v4 : 1;
> > > +       bool has_fbcdc_algorithm : 1;
> > > +       bool has_gpu_multicore_support : 1;
> > > +       bool has_isp_max_tiles_in_flight : 1;
> > > +       bool has_isp_samples_per_pixel : 1;
> > > +       bool has_isp_zls_d24_s8_packing_ogl_mode : 1;
> > > +       bool has_max_partitions : 1;
> > > +       bool has_meta : 1;
> > > +       bool has_meta_coremem_size : 1;
> > > +       bool has_mips : 1;
> > > +       bool has_num_clusters : 1;
> > > +       bool has_num_isp_ipp_pipes : 1;
> > > +       bool has_num_raster_pipes : 1;
> > > +       bool has_phys_bus_width : 1;
> > > +       bool has_riscv_fw_processor : 1;
> > > +       bool has_roguexe : 1;
> > > +       bool has_s7_top_infrastructure : 1;
> > > +       bool has_simple_internal_parameter_format : 1;
> > > +       bool has_slc_cache_line_size_in_bits : 1;
> > > +       bool has_sys_bus_secure_reset : 1;
> > > +       bool has_tessellation : 1;
> > > +       bool has_tile_size_x : 1;
> > > +       bool has_tile_size_y : 1;
> > > +       bool has_tpu_dm_global_registers : 1;
> > > +       bool has_usc_min_output_registers_per_pix : 1;
> > > +       bool has_vdm_drawindirect : 1;
> > > +       bool has_vdm_object_level_lls : 1;
> > > +       bool has_virtual_address_space_bits : 1;
> > > +       bool has_xe_memory_hierarchy : 1;
> > > +       bool has_xpu_max_slaves : 1;
> > > +       bool has_xt_top_infrastructure : 1;
> > > +       bool has_zls_subtile : 1;
> > > +
> > > +       u8 cdm_control_stream_format;
> > > +       u32 common_store_size_in_dwords;
> > > +       u8 fbcdc_algorithm;
> > > +       u16 isp_max_tiles_in_flight;
> > > +       bool isp_samples_per_pixel;
> > > +       u16 max_partitions;
> > > +       bool meta;
> > > +       u32 meta_coremem_size;
> > > +       bool mips;
> > > +       u16 num_clusters;
> > > +       u8 num_isp_ipp_pipes;
> > > +       u8 num_raster_pipes;
> > > +       u16 phys_bus_width;
> > > +       bool riscv_fw_processor;
> > > +       u32 simple_internal_parameter_format;
> > > +       u16 slc_cache_line_size_in_bits;
> > > +       u16 tile_size_x;
> > > +       u16 tile_size_y;
> > > +       u16 usc_min_output_registers_per_pix;
> > > +       u16 virtual_address_space_bits;
> > > +       u8 xpu_max_slaves;
> > > +};
> > > +
> > > +/*
> > > + * struct pvr_device_quirks - Hardware quirk information
> > > + */
> > > +struct pvr_device_quirks {
> > > +       bool has_brn44079 : 1;
> > > +       bool has_brn47217 : 1;
> > > +       bool has_brn48492 : 1;
> > > +       bool has_brn48545 : 1;
> > > +       bool has_brn49927 : 1;
> > > +       bool has_brn51764 : 1;
> > > +       bool has_brn52354 : 1;
> > > +       bool has_brn62269 : 1;
> > > +       bool has_brn63142 : 1;
> > > +       bool has_brn63553 : 1;
> > > +       bool has_brn66011 : 1;
> > > +};
> > > +
> > > +/*
> > > + * struct pvr_device_enhancements - Hardware enhancement information
> > > + */
> > > +struct pvr_device_enhancements {
> > > +       bool has_ern35421 : 1;
> > > +       bool has_ern38020 : 1;
> > > +       bool has_ern38748 : 1;
> > > +       bool has_ern42064 : 1;
> > > +};
> > > +
> > > +int pvr_device_info_init(struct pvr_device *pvr_dev);
> > > +
> > > +/*
> > > + * Meta cores
> > > + *
> > > + * These are the values for the 'meta' feature when the feature is present
> > > + * (as per &struct pvr_device_features)/
> > > + */
> > > +#define PVR_META_MTP218 (1)
> > > +#define PVR_META_MTP219 (2)
> > > +#define PVR_META_LTP218 (3)
> > > +#define PVR_META_LTP217 (4)
> > > +
> > > +enum {
> > > +       PVR_FEATURE_CDM_USER_MODE_QUEUE,
> > > +       PVR_FEATURE_CLUSTER_GROUPING,
> > > +       PVR_FEATURE_COMPUTE_MORTON_CAPABLE,
> > > +       PVR_FEATURE_FB_CDC_V4,
> > > +       PVR_FEATURE_GPU_MULTICORE_SUPPORT,
> > > +       PVR_FEATURE_ISP_ZLS_D24_S8_PACKING_OGL_MODE,
> > > +       PVR_FEATURE_REQUIRES_FB_CDC_ZLS_SETUP,
> > > +       PVR_FEATURE_S7_TOP_INFRASTRUCTURE,
> > > +       PVR_FEATURE_TESSELLATION,
> > > +       PVR_FEATURE_TPU_DM_GLOBAL_REGISTERS,
> > > +       PVR_FEATURE_VDM_DRAWINDIRECT,
> > > +       PVR_FEATURE_VDM_OBJECT_LEVEL_LLS,
> > > +       PVR_FEATURE_ZLS_SUBTILE,
> > > +};
> > > +
> > > +#endif /* PVR_DEVICE_INFO_H */
> > > diff --git a/drivers/gpu/drm/imagination/pvr_drv.c b/drivers/gpu/drm/imagination/pvr_drv.c
> > > index 48a870715426..5ebdea49c3be 100644
> > > --- a/drivers/gpu/drm/imagination/pvr_drv.c
> > > +++ b/drivers/gpu/drm/imagination/pvr_drv.c
> > > @@ -3,6 +3,9 @@
> > >
> > >  #include "pvr_device.h"
> > >  #include "pvr_drv.h"
> > > +#include "pvr_rogue_defs.h"
> > > +#include "pvr_rogue_fwif_client.h"
> > > +#include "pvr_rogue_fwif_shared.h"
> > >
> > >  #include <uapi/drm/pvr_drm.h>
> > >
> > > @@ -88,6 +91,422 @@ pvr_ioctl_get_bo_mmap_offset(__always_unused struct drm_device *drm_dev,
> > >         return -ENOTTY;
> > >  }
> > >
> > > +static __always_inline u64
> > > +pvr_fw_version_packed(u32 major, u32 minor)
> > > +{
> > > +       return ((u64)major << 32) | minor;
> > > +}
> > > +
> > > +static u32
> > > +rogue_get_common_store_partition_space_size(struct pvr_device *pvr_dev)
> > > +{
> > > +       u32 max_partitions = 0;
> > > +       u32 tile_size_x = 0;
> > > +       u32 tile_size_y = 0;
> > > +
> > > +       PVR_FEATURE_VALUE(pvr_dev, tile_size_x, &tile_size_x);
> > > +       PVR_FEATURE_VALUE(pvr_dev, tile_size_y, &tile_size_y);
> > > +       PVR_FEATURE_VALUE(pvr_dev, max_partitions, &max_partitions);
> > > +
> > > +       if (tile_size_x == 16 && tile_size_y == 16) {
> > > +               u32 usc_min_output_registers_per_pix = 0;
> > > +
> > > +               PVR_FEATURE_VALUE(pvr_dev, usc_min_output_registers_per_pix,
> > > +                                 &usc_min_output_registers_per_pix);
> > > +
> > > +               return tile_size_x * tile_size_y * max_partitions *
> > > +                      usc_min_output_registers_per_pix;
> > > +       }
> > > +
> > > +       return max_partitions * 1024;
> > > +}
> > > +
> > > +static u32
> > > +rogue_get_common_store_alloc_region_size(struct pvr_device *pvr_dev)
> > > +{
> > > +       u32 common_store_size_in_dwords = 512 * 4 * 4;
> > > +       u32 alloc_region_size;
> > > +
> > > +       PVR_FEATURE_VALUE(pvr_dev, common_store_size_in_dwords, &common_store_size_in_dwords);
> > > +
> > > +       alloc_region_size = common_store_size_in_dwords - (256U * 4U) -
> > > +                           rogue_get_common_store_partition_space_size(pvr_dev);
> > > +
> > > +       if (PVR_HAS_QUIRK(pvr_dev, 44079)) {
> > > +               u32 common_store_split_point = (768U * 4U * 4U);
> > > +
> > > +               return min(common_store_split_point - (256U * 4U), alloc_region_size);
> > > +       }
> > > +
> > > +       return alloc_region_size;
> > > +}
> > > +
> > > +static inline u32
> > > +rogue_get_num_phantoms(struct pvr_device *pvr_dev)
> > > +{
> > > +       u32 num_clusters = 1;
> > > +
> > > +       PVR_FEATURE_VALUE(pvr_dev, num_clusters, &num_clusters);
> > > +
> > > +       return ROGUE_REQ_NUM_PHANTOMS(num_clusters);
> > > +}
> > > +
> > > +static inline u32
> > > +rogue_get_max_coeffs(struct pvr_device *pvr_dev)
> > > +{
> > > +       u32 max_coeff_additional_portion = ROGUE_MAX_VERTEX_SHARED_REGISTERS;
> > > +       u32 pending_allocation_shared_regs = 2U * 1024U;
> > > +       u32 pending_allocation_coeff_regs = 0U;
> > > +       u32 num_phantoms = rogue_get_num_phantoms(pvr_dev);
> > > +       u32 tiles_in_flight = 0;
> > > +       u32 max_coeff_pixel_portion;
> > > +
> > > +       PVR_FEATURE_VALUE(pvr_dev, isp_max_tiles_in_flight, &tiles_in_flight);
> > > +       max_coeff_pixel_portion = DIV_ROUND_UP(tiles_in_flight, num_phantoms);
> > > +       max_coeff_pixel_portion *= ROGUE_MAX_PIXEL_SHARED_REGISTERS;
> > > +
> > > +       /*
> > > +        * Compute tasks on cores with BRN48492 and without compute overlap may lock
> > > +        * up without two additional lines of coeffs.
> > > +        */
> > > +       if (PVR_HAS_QUIRK(pvr_dev, 48492) && !PVR_HAS_FEATURE(pvr_dev, compute_overlap))
> > > +               pending_allocation_coeff_regs = 2U * 1024U;
> > > +
> > > +       if (PVR_HAS_ENHANCEMENT(pvr_dev, 38748))
> > > +               pending_allocation_shared_regs = 0;
> > > +
> > > +       if (PVR_HAS_ENHANCEMENT(pvr_dev, 38020))
> > > +               max_coeff_additional_portion += ROGUE_MAX_COMPUTE_SHARED_REGISTERS;
> > > +
> > > +       return rogue_get_common_store_alloc_region_size(pvr_dev) + pending_allocation_coeff_regs -
> > > +               (max_coeff_pixel_portion + max_coeff_additional_portion +
> > > +                pending_allocation_shared_regs);
> > > +}
> > > +
> > > +static inline u32
> > > +rogue_get_cdm_max_local_mem_size_regs(struct pvr_device *pvr_dev)
> > > +{
> > > +       u32 available_coeffs_in_dwords = rogue_get_max_coeffs(pvr_dev);
> > > +
> > > +       if (PVR_HAS_QUIRK(pvr_dev, 48492) && PVR_HAS_FEATURE(pvr_dev, roguexe) &&
> > > +           !PVR_HAS_FEATURE(pvr_dev, compute_overlap)) {
> > > +               /* Driver must not use the 2 reserved lines. */
> > > +               available_coeffs_in_dwords -= ROGUE_CSRM_LINE_SIZE_IN_DWORDS * 2;
> > > +       }
> > > +
> > > +       /*
> > > +        * The maximum amount of local memory available to a kernel is the minimum
> > > +        * of the total number of coefficient registers available and the max common
> > > +        * store allocation size which can be made by the CDM.
> > > +        *
> > > +        * If any coeff lines are reserved for tessellation or pixel then we need to
> > > +        * subtract those too.
> > > +        */
> > > +       return min(available_coeffs_in_dwords, (u32)ROGUE_MAX_PER_KERNEL_LOCAL_MEM_SIZE_REGS);
> > > +}
> > > +
> > > +/**
> > > + * pvr_dev_query_gpu_info_get()
> > > + * @pvr_dev: Device pointer.
> > > + * @args: [IN] Device query arguments containing a pointer to a userspace
> > > + *        struct drm_pvr_dev_query_gpu_info.
> > > + *
> > > + * If the query object pointer is NULL, the size field is updated with the
> > > + * expected size of the query object.
> > > + *
> > > + * Returns:
> > > + *  * 0 on success, or if size is requested using a NULL pointer, or
> > > + *  * -%E2BIG if the indicated length of the allocation is less than is
> > > + *    required to contain the copied data, or
> > > + *  * -%EFAULT if local memory could not be copied to userspace.
> > > + */
> > > +static int
> > > +pvr_dev_query_gpu_info_get(struct pvr_device *pvr_dev,
> > > +                          struct drm_pvr_ioctl_dev_query_args *args)
> > > +{
> > > +       struct drm_pvr_dev_query_gpu_info gpu_info = {0};
> > > +       int err;
> > > +
> > > +       if (!args->pointer) {
> > > +               args->size = sizeof(struct drm_pvr_dev_query_gpu_info);
> > > +               return 0;
> > > +       }
> > > +
> > > +       gpu_info.gpu_id =
> > > +               pvr_gpu_id_to_packed_bvnc(&pvr_dev->gpu_id);
> > > +       gpu_info.num_phantoms = rogue_get_num_phantoms(pvr_dev);
> > > +
> > > +       err = PVR_UOBJ_SET(args->pointer, args->size, gpu_info);
> > > +       if (err < 0)
> > > +               return err;
> > > +
> > > +       if (args->size > sizeof(gpu_info))
> > > +               args->size = sizeof(gpu_info);
> > > +       return 0;
> > > +}
> > > +
> > > +/**
> > > + * pvr_dev_query_runtime_info_get()
> > > + * @pvr_dev: Device pointer.
> > > + * @args: [IN] Device query arguments containing a pointer to a userspace
> > > + *        struct drm_pvr_dev_query_runtime_info.
> > > + *
> > > + * If the query object pointer is NULL, the size field is updated with the
> > > + * expected size of the query object.
> > > + *
> > > + * Returns:
> > > + *  * 0 on success, or if size is requested using a NULL pointer, or
> > > + *  * -%E2BIG if the indicated length of the allocation is less than is
> > > + *    required to contain the copied data, or
> > > + *  * -%EFAULT if local memory could not be copied to userspace.
> > > + */
> > > +static int
> > > +pvr_dev_query_runtime_info_get(struct pvr_device *pvr_dev,
> > > +                              struct drm_pvr_ioctl_dev_query_args *args)
> > > +{
> > > +       struct drm_pvr_dev_query_runtime_info runtime_info = {0};
> > > +       int err;
> > > +
> > > +       if (!args->pointer) {
> > > +               args->size = sizeof(struct drm_pvr_dev_query_runtime_info);
> > > +               return 0;
> > > +       }
> > > +
> > > +       runtime_info.free_list_min_pages = 0; /* FIXME */
> > > +       runtime_info.free_list_max_pages =
> > > +               ROGUE_PM_MAX_FREELIST_SIZE / ROGUE_PM_PAGE_SIZE;
> > > +       runtime_info.common_store_alloc_region_size =
> > > +               rogue_get_common_store_alloc_region_size(pvr_dev);
> > > +       runtime_info.common_store_partition_space_size =
> > > +               rogue_get_common_store_partition_space_size(pvr_dev);
> > > +       runtime_info.max_coeffs = rogue_get_max_coeffs(pvr_dev);
> > > +       runtime_info.cdm_max_local_mem_size_regs =
> > > +               rogue_get_cdm_max_local_mem_size_regs(pvr_dev);
> > > +
> > > +       err = PVR_UOBJ_SET(args->pointer, args->size, runtime_info);
> > > +       if (err < 0)
> > > +               return err;
> > > +
> > > +       if (args->size > sizeof(runtime_info))
> > > +               args->size = sizeof(runtime_info);
> > > +       return 0;
> > > +}
> > > +
> > > +/**
> > > + * pvr_dev_query_hwrt_info_get()
> > > + * @pvr_dev: Device pointer.
> > > + * @args: [IN] Device query arguments containing a pointer to a userspace
> > > + *        struct drm_pvr_dev_query_hwrt_info.
> > > + *
> > > + * If the query object pointer is NULL, the size field is updated with the
> > > + * expected size of the query object.
> > > + *
> > > + * Returns:
> > > + *  * 0 on success, or if size is requested using a NULL pointer, or
> > > + *  * -%E2BIG if the indicated length of the allocation is less than is
> > > + *    required to contain the copied data, or
> > > + *  * -%EFAULT if local memory could not be copied to userspace.
> > > + */
> > > +static int
> > > +pvr_dev_query_hwrt_info_get(struct pvr_device *pvr_dev,
> > > +                           struct drm_pvr_ioctl_dev_query_args *args)
> > > +{
> > > +       struct drm_pvr_dev_query_hwrt_info hwrt_info = {0};
> > > +       int err;
> > > +
> > > +       if (!args->pointer) {
> > > +               args->size = sizeof(struct drm_pvr_dev_query_hwrt_info);
> > > +               return 0;
> > > +       }
> > > +
> > > +       hwrt_info.num_geomdatas = ROGUE_FWIF_NUM_GEOMDATAS;
> > > +       hwrt_info.num_rtdatas = ROGUE_FWIF_NUM_RTDATAS;
> > > +       hwrt_info.num_freelists = ROGUE_FWIF_NUM_RTDATA_FREELISTS;
> > > +
> > > +       err = PVR_UOBJ_SET(args->pointer, args->size, hwrt_info);
> > > +       if (err < 0)
> > > +               return err;
> > > +
> > > +       if (args->size > sizeof(hwrt_info))
> > > +               args->size = sizeof(hwrt_info);
> > > +       return 0;
> > > +}
> > > +
> > > +/**
> > > + * pvr_dev_query_quirks_get() - Unpack array of quirks at the address given
> > > + * in a struct drm_pvr_dev_query_quirks, or gets the amount of space required
> > > + * for it.
> > > + * @pvr_dev: Device pointer.
> > > + * @args: [IN] Device query arguments containing a pointer to a userspace
> > > + *        struct drm_pvr_dev_query_query_quirks.
> > > + *
> > > + * If the query object pointer is NULL, the size field is updated with the
> > > + * expected size of the query object.
> > > + * If the userspace pointer in the query object is NULL, or the count is
> > > + * short, no data is copied.
> > > + * The count field will be updated to that copied, or if either pointer is
> > > + * NULL, that which would have been copied.
> > > + * The size field in the query object will be updated to the size copied.
> > > + *
> > > + * Returns:
> > > + *  * 0 on success, or if size/count is requested using a NULL pointer, or
> > > + *  * -%EINVAL if args contained non-zero reserved fields, or
> > > + *  * -%E2BIG if the indicated length of the allocation is less than is
> > > + *    required to contain the copied data, or
> > > + *  * -%EFAULT if local memory could not be copied to userspace.
> > > + */
> > > +static int
> > > +pvr_dev_query_quirks_get(struct pvr_device *pvr_dev,
> > > +                        struct drm_pvr_ioctl_dev_query_args *args)
> > > +{
> > > +       /*
> > > +        * @FIXME - hardcoding of numbers here is intended as an
> > > +        * intermediate step so the UAPI can be fixed, but requires a
> > > +        * a refactor in the future to store them in a more appropriate
> > > +        * location
> > > +        */
> > > +       static const u32 umd_quirks_musthave[] = {
> > > +               47217,
> > > +               49927,
> > > +               62269,
> > > +       };
> > > +       static const u32 umd_quirks[] = {
> > > +               48545,
> > > +               51764,
> > > +       };
> > > +       struct drm_pvr_dev_query_quirks query;
> > > +       u32 out[ARRAY_SIZE(umd_quirks_musthave) + ARRAY_SIZE(umd_quirks)];
> > > +       size_t out_musthave_count = 0;
> > > +       size_t out_count = 0;
> > > +       int err;
> > > +
> > > +       if (!args->pointer) {
> > > +               args->size = sizeof(struct drm_pvr_dev_query_quirks);
> > > +               return 0;
> > > +       }
> > > +
> > > +       err = PVR_UOBJ_GET(query, args->size, args->pointer);
> > > +
> > > +       if (err < 0)
> > > +               return err;
> > > +       if (query._padding_c)
> > > +               return -EINVAL;
> > > +
> > > +       for (int i = 0; i < ARRAY_SIZE(umd_quirks_musthave); i++) {
> > > +               if (pvr_device_has_uapi_quirk(pvr_dev, umd_quirks_musthave[i])) {
> > > +                       out[out_count++] = umd_quirks_musthave[i];
> > > +                       out_musthave_count++;
> > > +               }
> > > +       }
> > > +
> > > +       for (int i = 0; i < ARRAY_SIZE(umd_quirks); i++) {
> > > +               if (pvr_device_has_uapi_quirk(pvr_dev, umd_quirks[i]))
> > > +                       out[out_count++] = umd_quirks[i];
> > > +       }
> > > +
> > > +       if (!query.quirks)
> > > +               goto copy_out;
> > > +       if (query.count < out_count)
> > > +               return -E2BIG;
> > > +
> > > +       if (copy_to_user(u64_to_user_ptr(query.quirks), out,
> > > +                        out_count * sizeof(u32))) {
> > > +               return -EFAULT;
> > > +       }
> > > +
> > > +       query.musthave_count = out_musthave_count;
> > > +
> > > +copy_out:
> > > +       query.count = out_count;
> > > +       err = PVR_UOBJ_SET(args->pointer, args->size, query);
> > > +       if (err < 0)
> > > +               return err;
> > > +
> > > +       args->size = sizeof(query);
> > > +       return 0;
> > > +}
> > > +
> > > +/**
> > > + * pvr_dev_query_enhancements_get() - Unpack array of enhancements at the
> > > + * address given in a struct drm_pvr_dev_query_enhancements, or gets the amount
> > > + * of space required for it.
> > > + * @pvr_dev: Device pointer.
> > > + * @args: [IN] Device query arguments containing a pointer to a userspace
> > > + *        struct drm_pvr_dev_query_enhancements.
> > > + *
> > > + * If the query object pointer is NULL, the size field is updated with the
> > > + * expected size of the query object.
> > > + * If the userspace pointer in the query object is NULL, or the count is
> > > + * short, no data is copied.
> > > + * The count field will be updated to that copied, or if either pointer is
> > > + * NULL, that which would have been copied.
> > > + * The size field in the query object will be updated to the size copied.
> > > + *
> > > + * Returns:
> > > + *  * 0 on success, or if size/count is requested using a NULL pointer, or
> > > + *  * -%EINVAL if args contained non-zero reserved fields, or
> > > + *  * -%E2BIG if the indicated length of the allocation is less than is
> > > + *    required to contain the copied data, or
> > > + *  * -%EFAULT if local memory could not be copied to userspace.
> > > + */
> > > +static int
> > > +pvr_dev_query_enhancements_get(struct pvr_device *pvr_dev,
> > > +                              struct drm_pvr_ioctl_dev_query_args *args)
> > > +{
> > > +       /*
> > > +        * @FIXME - hardcoding of numbers here is intended as an
> > > +        * intermediate step so the UAPI can be fixed, but requires a
> > > +        * a refactor in the future to store them in a more appropriate
> > > +        * location
> > > +        */
> > > +       const u32 umd_enhancements[] = {
> > > +               35421,
> > > +               42064,
> > > +       };
> > > +       struct drm_pvr_dev_query_enhancements query;
> > > +       u32 out[ARRAY_SIZE(umd_enhancements)];
> > > +       size_t out_idx = 0;
> > > +       int err;
> > > +
> > > +       if (!args->pointer) {
> > > +               args->size = sizeof(struct drm_pvr_dev_query_enhancements);
> > > +               return 0;
> > > +       }
> > > +
> > > +       err = PVR_UOBJ_GET(query, args->size, args->pointer);
> > > +
> > > +       if (err < 0)
> > > +               return err;
> > > +       if (query._padding_a)
> > > +               return -EINVAL;
> > > +       if (query._padding_c)
> > > +               return -EINVAL;
> > > +
> > > +       for (int i = 0; i < ARRAY_SIZE(umd_enhancements); i++) {
> > > +               if (pvr_device_has_uapi_enhancement(pvr_dev, umd_enhancements[i]))
> > > +                       out[out_idx++] = umd_enhancements[i];
> > > +       }
> > > +
> > > +       if (!query.enhancements)
> > > +               goto copy_out;
> > > +       if (query.count < out_idx)
> > > +               return -E2BIG;
> > > +
> > > +       if (copy_to_user(u64_to_user_ptr(query.enhancements), out,
> > > +                        out_idx * sizeof(u32))) {
> > > +               return -EFAULT;
> > > +       }
> > > +
> > > +copy_out:
> > > +       query.count = out_idx;
> > > +       err = PVR_UOBJ_SET(args->pointer, args->size, query);
> > > +       if (err < 0)
> > > +               return err;
> > > +
> > > +       args->size = sizeof(query);
> > > +       return 0;
> > > +}
> > > +
> > >  /**
> > >   * pvr_ioctl_dev_query() - IOCTL to copy information about a device
> > >   * @drm_dev: [IN] DRM device.
> > > @@ -112,7 +531,33 @@ static int
> > >  pvr_ioctl_dev_query(struct drm_device *drm_dev, void *raw_args,
> > >                     struct drm_file *file)
> > >  {
> > > -       return -ENOTTY;
> > > +       struct pvr_device *pvr_dev = to_pvr_device(drm_dev);
> > > +       struct drm_pvr_ioctl_dev_query_args *args = raw_args;
> > > +
> > > +       switch ((enum drm_pvr_dev_query)args->type) {
> > > +       case DRM_PVR_DEV_QUERY_GPU_INFO_GET:
> > > +               return pvr_dev_query_gpu_info_get(pvr_dev, args);
> > > +
> > > +       case DRM_PVR_DEV_QUERY_RUNTIME_INFO_GET:
> > > +               return pvr_dev_query_runtime_info_get(pvr_dev, args);
> > > +
> > > +       case DRM_PVR_DEV_QUERY_HWRT_INFO_GET:
> > > +               return pvr_dev_query_hwrt_info_get(pvr_dev, args);
> > > +
> > > +       case DRM_PVR_DEV_QUERY_QUIRKS_GET:
> > > +               return pvr_dev_query_quirks_get(pvr_dev, args);
> > > +
> > > +       case DRM_PVR_DEV_QUERY_ENHANCEMENTS_GET:
> > > +               return pvr_dev_query_enhancements_get(pvr_dev, args);
> > > +
> > > +       case DRM_PVR_DEV_QUERY_HEAP_INFO_GET:
> > > +               return -EINVAL;
> > > +
> > > +       case DRM_PVR_DEV_QUERY_STATIC_DATA_AREAS_GET:
> > > +               return -EINVAL;
> > > +       }
> > > +
> > > +       return -EINVAL;
> > >  }
> > >
> > >  /**
> > > @@ -350,6 +795,112 @@ pvr_ioctl_submit_jobs(struct drm_device *drm_dev, void *raw_args,
> > >         return -ENOTTY;
> > >  }
> > >
> > > +int
> > > +pvr_get_uobj(u64 usr_ptr, u32 usr_stride, u32 min_stride, u32 obj_size, void *out)
> > > +{
> > > +       if (usr_stride < min_stride)
> > > +               return -EINVAL;
> > > +
> > > +       return copy_struct_from_user(out, obj_size, u64_to_user_ptr(usr_ptr), usr_stride);
> > > +}
> > > +
> > > +int
> > > +pvr_set_uobj(u64 usr_ptr, u32 usr_stride, u32 min_stride, u32 obj_size, const void *in)
> > > +{
> > > +       if (usr_stride < min_stride)
> > > +               return -EINVAL;
> > > +
> > > +       if (copy_to_user(u64_to_user_ptr(usr_ptr), in, min_t(u32, usr_stride, obj_size)))
> > > +               return -EFAULT;
> > > +
> > > +       if (usr_stride > obj_size &&
> > > +           clear_user(u64_to_user_ptr(usr_ptr + obj_size), usr_stride - obj_size)) {
> > > +               return -EFAULT;
> > > +       }
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +int
> > > +pvr_get_uobj_array(const struct drm_pvr_obj_array *in, u32 min_stride, u32 obj_size, void **out)
> > > +{
> > > +       int ret = 0;
> > > +       void *out_alloc;
> > > +
> > > +       if (in->stride < min_stride)
> > > +               return -EINVAL;
> > > +
> > > +       if (!in->count)
> > > +               return 0;
> > > +
> > > +       out_alloc = kvmalloc_array(in->count, obj_size, GFP_KERNEL);
> > > +       if (!out_alloc)
> > > +               return -ENOMEM;
> > > +
> > > +       if (obj_size == in->stride) {
> > > +               if (copy_from_user(out_alloc, u64_to_user_ptr(in->array),
> > > +                                  (unsigned long)obj_size * in->count))
> > > +                       ret = -EFAULT;
> > > +       } else {
> > > +               void __user *in_ptr = u64_to_user_ptr(in->array);
> > > +               void *out_ptr = out_alloc;
> > > +
> > > +               for (u32 i = 0; i < in->count; i++) {
> > > +                       ret = copy_struct_from_user(out_ptr, obj_size, in_ptr, in->stride);
> > > +                       if (ret)
> > > +                               break;
> > > +
> > > +                       out_ptr += obj_size;
> > > +                       in_ptr += in->stride;
> > > +               }
> > > +       }
> > > +
> > > +       if (ret) {
> > > +               kvfree(out_alloc);
> > > +               return ret;
> > > +       }
> > > +
> > > +       *out = out_alloc;
> > > +       return 0;
> > > +}
> > > +
> > > +int
> > > +pvr_set_uobj_array(const struct drm_pvr_obj_array *out, u32 min_stride, u32 obj_size,
> > > +                  const void *in)
> > > +{
> > > +       if (out->stride < min_stride)
> > > +               return -EINVAL;
> > > +
> > > +       if (!out->count)
> > > +               return 0;
> > > +
> > > +       if (obj_size == out->stride) {
> > > +               if (copy_to_user(u64_to_user_ptr(out->array), in,
> > > +                                (unsigned long)obj_size * out->count))
> > > +                       return -EFAULT;
> > > +       } else {
> > > +               u32 cpy_elem_size = min_t(u32, out->stride, obj_size);
> > > +               void __user *out_ptr = u64_to_user_ptr(out->array);
> > > +               const void *in_ptr = in;
> > > +
> > > +               for (u32 i = 0; i < out->count; i++) {
> > > +                       if (copy_to_user(out_ptr, in_ptr, cpy_elem_size))
> > > +                               return -EFAULT;
> > > +
> > > +                       out_ptr += obj_size;
> > > +                       in_ptr += out->stride;
> > > +               }
> > > +
> > > +               if (out->stride > obj_size &&
> > > +                   clear_user(u64_to_user_ptr(out->array + obj_size),
> > > +                              out->stride - obj_size)) {
> > > +                       return -EFAULT;
> > > +               }
> > > +       }
> > > +
> > > +       return 0;
> > > +}
> > > +
> > >  #define DRM_PVR_IOCTL(_name, _func, _flags) \
> > >         DRM_IOCTL_DEF_DRV(PVR_##_name, pvr_ioctl_##_func, _flags)
> > >
> > > diff --git a/drivers/gpu/drm/imagination/pvr_drv.h b/drivers/gpu/drm/imagination/pvr_drv.h
> > > index 8e6f4a4dde3f..febdf1f09571 100644
> > > --- a/drivers/gpu/drm/imagination/pvr_drv.h
> > > +++ b/drivers/gpu/drm/imagination/pvr_drv.h
> > > @@ -19,4 +19,112 @@
> > >  #define PVR_DRIVER_MINOR 0
> > >  #define PVR_DRIVER_PATCHLEVEL 0
> > >
> > > +int pvr_get_uobj(u64 usr_ptr, u32 usr_size, u32 min_size, u32 obj_size, void *out);
> > > +int pvr_set_uobj(u64 usr_ptr, u32 usr_size, u32 min_size, u32 obj_size, const void *in);
> > > +int pvr_get_uobj_array(const struct drm_pvr_obj_array *in, u32 min_stride, u32 obj_size,
> > > +                      void **out);
> > > +int pvr_set_uobj_array(const struct drm_pvr_obj_array *out, u32 min_stride, u32 obj_size,
> > > +                      const void *in);
> > > +
> > > +#define PVR_UOBJ_MIN_SIZE_INTERNAL(_typename, _last_mandatory_field) \
> > > +       (offsetof(_typename, _last_mandatory_field) + \
> > > +        sizeof(((_typename *)NULL)->_last_mandatory_field))
> > > +
> > > +/* NOLINTBEGIN(bugprone-macro-parentheses) */
> > > +#define PVR_UOBJ_DECL(_typename, _last_mandatory_field) \
> > > +       , _typename : PVR_UOBJ_MIN_SIZE_INTERNAL(_typename, _last_mandatory_field)
> > > +/* NOLINTEND(bugprone-macro-parentheses) */
> > > +
> > > +/**
> > > + * DOC: PVR user objects.
> > > + *
> > > + * Macros used to aid copying structured and array data to and from
> > > + * userspace. Objects can differ in size, provided the minimum size
> > > + * allowed is specified (using the last mandatory field in the struct).
> > > + * All types used with PVR_UOBJ_GET/SET macros must be listed here under
> > > + * PVR_UOBJ_MIN_SIZE, with the last mandatory struct field specified.
> > > + */
> > > +
> > > +/**
> > > + * PVR_UOBJ_MIN_SIZE() - Fetch the minimum copy size of a compatible type object.
> > > + * @_obj_name: The name of the object. Cannot be a typename - this is deduced.
> > > + *
> > > + * This cannot fail. Using the macro with an incompatible type will result in a
> > > + * compiler error.
> > > + *
> > > + * To add compatibility for a type, list it within the macro in an orderly
> > > + * fashion. The second argument is the name of the last mandatory field of the
> > > + * struct type, which is used to calculate the size. See also PVR_UOBJ_DECL().
> > > + *
> > > + * Return: The minimum copy size.
> > > + */
> > > +#define PVR_UOBJ_MIN_SIZE(_obj_name) _Generic(_obj_name \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_job, hwrt) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_sync_op, value) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_dev_query_gpu_info, num_phantoms) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_dev_query_runtime_info, cdm_max_local_mem_size_regs) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_dev_query_hwrt_info, _padding_4) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_dev_query_quirks, _padding_c) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_dev_query_enhancements, _padding_c) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_heap, page_size_log2) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_dev_query_heap_info, heaps) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_static_data_area, offset) \
> > > +       PVR_UOBJ_DECL(struct drm_pvr_dev_query_static_data_areas, static_data_areas) \
> > > +       )
> > > +
> > > +/**
> > > + * PVR_UOBJ_GET() - Copies from _src_usr_ptr to &_dest_obj.
> > > + * @_dest_obj: The destination container object in kernel space.
> > > + * @_usr_size: The size of the source container in user space.
> > > + * @_src_usr_ptr: __u64 raw pointer to the source container in user space.
> > > + *
> > > + * Return: Error code. See pvr_get_uobj().
> > > + */
> > > +#define PVR_UOBJ_GET(_dest_obj, _usr_size, _src_usr_ptr) \
> > > +       pvr_get_uobj(_src_usr_ptr, _usr_size, \
> > > +                    PVR_UOBJ_MIN_SIZE(_dest_obj), \
> > > +                    sizeof(_dest_obj), &(_dest_obj))
> > > +
> > > +/**
> > > + * PVR_UOBJ_SET() - Copies from &_src_obj to _dest_usr_ptr.
> > > + * @_dest_usr_ptr: __u64 raw pointer to the destination container in user space.
> > > + * @_usr_size: The size of the destination container in user space.
> > > + * @_src_obj: The source container object in kernel space.
> > > + *
> > > + * Return: Error code. See pvr_set_uobj().
> > > + */
> > > +#define PVR_UOBJ_SET(_dest_usr_ptr, _usr_size, _src_obj) \
> > > +       pvr_set_uobj(_dest_usr_ptr, _usr_size, \
> > > +                    PVR_UOBJ_MIN_SIZE(_src_obj), \
> > > +                    sizeof(_src_obj), &(_src_obj))
> > > +
> > > +/**
> > > + * PVR_UOBJ_GET_ARRAY() - Copies from @_src_drm_pvr_obj_array.array to
> > > + * alloced memory and returns a pointer in _dest_array.
> > > + * @_dest_array: The destination C array object in kernel space.
> > > + * @_src_drm_pvr_obj_array: The &struct drm_pvr_obj_array containing a __u64 raw
> > > + * pointer to the source C array in user space and the size of each array
> > > + * element in user space (the 'stride').
> > > + *
> > > + * Return: Error code. See pvr_get_uobj_array().
> > > + */
> > > +#define PVR_UOBJ_GET_ARRAY(_dest_array, _src_drm_pvr_obj_array) \
> > > +       pvr_get_uobj_array(_src_drm_pvr_obj_array, \
> > > +                          PVR_UOBJ_MIN_SIZE((_dest_array)[0]), \
> > > +                          sizeof((_dest_array)[0]), (void **)&(_dest_array))
> > > +
> > > +/**
> > > + * PVR_UOBJ_SET_ARRAY() - Copies from _src_array to @_dest_drm_pvr_obj_array.array.
> > > + * @_dest_drm_pvr_obj_array: The &struct drm_pvr_obj_array containing a __u64 raw
> > > + * pointer to the destination C array in user space and the size of each array
> > > + * element in user space (the 'stride').
> > > + * @_src_array: The source C array object in kernel space.
> > > + *
> > > + * Return: Error code. See pvr_set_uobj_array().
> > > + */
> > > +#define PVR_UOBJ_SET_ARRAY(_dest_drm_pvr_obj_array, _src_array) \
> > > +       pvr_set_uobj_array(_dest_drm_pvr_obj_array, \
> > > +                          PVR_UOBJ_MIN_SIZE((_src_array)[0]), \
> > > +                          sizeof((_src_array)[0]), _src_array)
> > > +
> > >  #endif /* PVR_DRV_H */
> > > diff --git a/drivers/gpu/drm/imagination/pvr_fw.h b/drivers/gpu/drm/imagination/pvr_fw.h
> > > new file mode 100644
> > > index 000000000000..ba3bfd41867f
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/imagination/pvr_fw.h
> > > @@ -0,0 +1,20 @@
> > > +/* SPDX-License-Identifier: GPL-2.0 OR MIT */
> > > +/* Copyright (c) 2022 Imagination Technologies Ltd. */
> > > +
> > > +#ifndef PVR_FW_H
> > > +#define PVR_FW_H
> > > +
> > > +#include <linux/firmware.h>
> > > +
> > > +struct pvr_fw_device {
> > > +       /** @firmware: Handle to the firmware loaded into the device. */
> > > +       const struct firmware *firmware;
> > > +
> > > +       /*
> > > +        * @processor_type: FW processor type for this device. Must be one of
> > > +        *                  %PVR_FW_PROCESSOR_TYPE_*.
> > > +        */
> > > +       u16 processor_type;
> > > +};
> > > +
> > > +#endif /* PVR_FW_H */
> > > --
> > > 2.40.1
> > >


More information about the dri-devel mailing list