root/drivers/gpu/drm/xe/xe_mocs.c
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2022 Intel Corporation
 */

#include "xe_mocs.h"

#include "regs/xe_gt_regs.h"
#include "xe_device.h"
#include "xe_exec_queue.h"
#include "xe_force_wake.h"
#include "xe_gt.h"
#include "xe_gt_mcr.h"
#include "xe_gt_printk.h"
#include "xe_mmio.h"
#include "xe_platform_types.h"
#include "xe_pm.h"
#include "xe_sriov.h"

#if IS_ENABLED(CONFIG_DRM_XE_DEBUG)
#define mocs_dbg xe_gt_dbg
#else
__printf(2, 3)
static inline void mocs_dbg(const struct xe_gt *gt,
                            const char *format, ...)
{ /* noop */ }
#endif

enum {
        HAS_GLOBAL_MOCS = BIT(0),
        HAS_LNCF_MOCS = BIT(1),
};

struct xe_mocs_entry {
        u32 control_value;
        u16 l3cc_value;
        u16 used;
};

struct xe_mocs_info;

struct xe_mocs_ops {
        void (*dump)(struct xe_mocs_info *mocs, unsigned int flags,
                     struct xe_gt *gt, struct drm_printer *p);
};

struct xe_mocs_info {
        /*
         * Size of the spec's suggested MOCS programming table.  The list of
         * table entries from the spec can potentially be smaller than the
         * number of hardware registers used to program the MOCS table; in such
         * cases the registers for the remaining indices will be programmed to
         * match unused_entries_index.
         */
        unsigned int table_size;
        /* Number of MOCS entries supported by the hardware */
        unsigned int num_mocs_regs;
        const struct xe_mocs_entry *table;
        const struct xe_mocs_ops *ops;
        u8 uc_index;
        u8 wb_index;
        u8 unused_entries_index;
};

/* Defines for the tables (GLOB_MOCS_0 - GLOB_MOCS_16) */
#define IG_PAT                          REG_BIT(8)
#define L3_CACHE_POLICY_MASK            REG_GENMASK(5, 4)
#define L4_CACHE_POLICY_MASK            REG_GENMASK(3, 2)

/* Helper defines */
#define XELP_NUM_MOCS_ENTRIES   64  /* 63-64 are reserved, but configured. */
#define PVC_NUM_MOCS_ENTRIES    3
#define MTL_NUM_MOCS_ENTRIES    16
#define XE2_NUM_MOCS_ENTRIES    16

/* (e)LLC caching options */
/*
 * Note: LE_0_PAGETABLE works only up to Gen11; for newer gens it means
 * the same as LE_UC
 */
#define LE_0_PAGETABLE          LE_CACHEABILITY(0)
#define LE_1_UC                 LE_CACHEABILITY(1)
#define LE_2_WT                 LE_CACHEABILITY(2)
#define LE_3_WB                 LE_CACHEABILITY(3)

/* Target cache */
#define LE_TC_0_PAGETABLE       LE_TGT_CACHE(0)
#define LE_TC_1_LLC             LE_TGT_CACHE(1)
#define LE_TC_2_LLC_ELLC        LE_TGT_CACHE(2)
#define LE_TC_3_LLC_ELLC_ALT    LE_TGT_CACHE(3)

/* L3 caching options */
#define L3_0_DIRECT             L3_CACHEABILITY(0)
#define L3_1_UC                 L3_CACHEABILITY(1)
#define L3_2_RESERVED           L3_CACHEABILITY(2)
#define L3_3_WB                 L3_CACHEABILITY(3)

/* L4 caching options */
#define L4_0_WB                 REG_FIELD_PREP(L4_CACHE_POLICY_MASK, 0)
#define L4_1_WT                 REG_FIELD_PREP(L4_CACHE_POLICY_MASK, 1)
#define L4_3_UC                 REG_FIELD_PREP(L4_CACHE_POLICY_MASK, 3)

#define XE2_L3_0_WB             REG_FIELD_PREP(L3_CACHE_POLICY_MASK, 0)
/* XD: WB Transient Display */
#define XE2_L3_1_XD             REG_FIELD_PREP(L3_CACHE_POLICY_MASK, 1)
#define XE2_L3_3_UC             REG_FIELD_PREP(L3_CACHE_POLICY_MASK, 3)

#define XE2_L3_CLOS_MASK        REG_GENMASK(7, 6)

#define MOCS_ENTRY(__idx, __control_value, __l3cc_value) \
        [__idx] = { \
                .control_value = __control_value, \
                .l3cc_value = __l3cc_value, \
                .used = 1, \
        }

/*
 * MOCS tables
 *
 * These are the MOCS tables that are programmed across all the rings.
 * The control value is programmed to all the rings that support the
 * MOCS registers. While the l3cc_values are only programmed to the
 * LNCFCMOCS0 - LNCFCMOCS32 registers.
 *
 * These tables are intended to be kept reasonably consistent across
 * HW platforms, and for ICL+, be identical across OSes. To achieve
 * that, the list of entries is published as part of bspec.
 *
 * Entries not part of the following tables are undefined as far as userspace is
 * concerned and shouldn't be relied upon. The last few entries are reserved by
 * the hardware. They should be initialized according to bspec and never used.
 *
 * NOTE1: These tables are part of bspec and defined as part of the hardware
 * interface. It is expected that, for specific hardware platform, existing
 * entries will remain constant and the table will only be updated by adding new
 * entries, filling unused positions.
 *
 * NOTE2: Reserved and unspecified MOCS indices have been set to L3 WB. These
 * reserved entries should never be used. They may be changed to low performant
 * variants with better coherency in the future if more entries are needed.
 */

static const struct xe_mocs_entry gen12_mocs_desc[] = {
        /* Base - L3 + LLC */
        MOCS_ENTRY(2,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3),
                   L3_3_WB),
        /* Base - Uncached */
        MOCS_ENTRY(3,
                   LE_1_UC | LE_TC_1_LLC,
                   L3_1_UC),
        /* Base - L3 */
        MOCS_ENTRY(4,
                   LE_1_UC | LE_TC_1_LLC,
                   L3_3_WB),
        /* Base - LLC */
        MOCS_ENTRY(5,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3),
                   L3_1_UC),
        /* Age 0 - LLC */
        MOCS_ENTRY(6,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(1),
                   L3_1_UC),
        /* Age 0 - L3 + LLC */
        MOCS_ENTRY(7,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(1),
                   L3_3_WB),
        /* Age: Don't Chg. - LLC */
        MOCS_ENTRY(8,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(2),
                   L3_1_UC),
        /* Age: Don't Chg. - L3 + LLC */
        MOCS_ENTRY(9,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(2),
                   L3_3_WB),
        /* No AOM - LLC */
        MOCS_ENTRY(10,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3) | LE_AOM(1),
                   L3_1_UC),
        /* No AOM - L3 + LLC */
        MOCS_ENTRY(11,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3) | LE_AOM(1),
                   L3_3_WB),
        /* No AOM; Age 0 - LLC */
        MOCS_ENTRY(12,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(1) | LE_AOM(1),
                   L3_1_UC),
        /* No AOM; Age 0 - L3 + LLC */
        MOCS_ENTRY(13,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(1) | LE_AOM(1),
                   L3_3_WB),
        /* No AOM; Age:DC - LLC */
        MOCS_ENTRY(14,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(2) | LE_AOM(1),
                   L3_1_UC),
        /* No AOM; Age:DC - L3 + LLC */
        MOCS_ENTRY(15,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(2) | LE_AOM(1),
                   L3_3_WB),
        /* Self-Snoop - L3 + LLC */
        MOCS_ENTRY(18,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3) | LE_SSE(3),
                   L3_3_WB),
        /* Skip Caching - L3 + LLC(12.5%) */
        MOCS_ENTRY(19,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3) | LE_SCC(7),
                   L3_3_WB),
        /* Skip Caching - L3 + LLC(25%) */
        MOCS_ENTRY(20,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3) | LE_SCC(3),
                   L3_3_WB),
        /* Skip Caching - L3 + LLC(50%) */
        MOCS_ENTRY(21,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3) | LE_SCC(1),
                   L3_3_WB),
        /* Skip Caching - L3 + LLC(75%) */
        MOCS_ENTRY(22,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3) | LE_RSC(1) | LE_SCC(3),
                   L3_3_WB),
        /* Skip Caching - L3 + LLC(87.5%) */
        MOCS_ENTRY(23,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3) | LE_RSC(1) | LE_SCC(7),
                   L3_3_WB),
        /* Implicitly enable L1 - HDC:L1 + L3 + LLC */
        MOCS_ENTRY(48,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3),
                   L3_3_WB),
        /* Implicitly enable L1 - HDC:L1 + L3 */
        MOCS_ENTRY(49,
                   LE_1_UC | LE_TC_1_LLC,
                   L3_3_WB),
        /* Implicitly enable L1 - HDC:L1 + LLC */
        MOCS_ENTRY(50,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3),
                   L3_1_UC),
        /* Implicitly enable L1 - HDC:L1 */
        MOCS_ENTRY(51,
                   LE_1_UC | LE_TC_1_LLC,
                   L3_1_UC),
        /* HW Special Case (CCS) */
        MOCS_ENTRY(60,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3),
                   L3_1_UC),
        /* HW Special Case (Displayable) */
        MOCS_ENTRY(61,
                   LE_1_UC | LE_TC_1_LLC,
                   L3_3_WB),
        /* HW Reserved - SW program but never use */
        MOCS_ENTRY(62,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3),
                   L3_1_UC),
        /* HW Reserved - SW program but never use */
        MOCS_ENTRY(63,
                   LE_3_WB | LE_TC_1_LLC | LE_LRUM(3),
                   L3_1_UC)
};

static bool regs_are_mcr(struct xe_gt *gt)
{
        struct xe_device *xe = gt_to_xe(gt);

        if (xe_gt_is_media_type(gt))
                return MEDIA_VER(xe) >= 20;
        else
                return GRAPHICS_VERx100(xe) >= 1250;
}

static void xelp_lncf_dump(struct xe_mocs_info *info, struct xe_gt *gt, struct drm_printer *p)
{
        unsigned int i, j;
        u32 reg_val;

        drm_printf(p, "LNCFCMOCS[idx] = [ESC, SCC, L3CC] (value)\n\n");

        for (i = 0, j = 0; i < (info->num_mocs_regs + 1) / 2; i++, j++) {
                if (regs_are_mcr(gt))
                        reg_val = xe_gt_mcr_unicast_read_any(gt, XEHP_LNCFCMOCS(i));
                else
                        reg_val = xe_mmio_read32(&gt->mmio, XELP_LNCFCMOCS(i));

                drm_printf(p, "LNCFCMOCS[%2d] = [%u, %u, %u] (%#8x)\n",
                           j++,
                           !!(reg_val & L3_ESC_MASK),
                           REG_FIELD_GET(L3_SCC_MASK, reg_val),
                           REG_FIELD_GET(L3_CACHEABILITY_MASK, reg_val),
                           reg_val);

                drm_printf(p, "LNCFCMOCS[%2d] = [%u, %u, %u] (%#8x)\n",
                           j,
                           !!(reg_val & L3_UPPER_IDX_ESC_MASK),
                           REG_FIELD_GET(L3_UPPER_IDX_SCC_MASK, reg_val),
                           REG_FIELD_GET(L3_UPPER_IDX_CACHEABILITY_MASK, reg_val),
                           reg_val);
        }
}

static void xelp_mocs_dump(struct xe_mocs_info *info, unsigned int flags,
                           struct xe_gt *gt, struct drm_printer *p)
{
        unsigned int i;
        u32 reg_val;

        if (flags & HAS_GLOBAL_MOCS) {
                drm_printf(p, "Global mocs table configuration:\n");
                drm_printf(p, "GLOB_MOCS[idx] = [LeCC, TC, LRUM, AOM, RSC, SCC, PFM, SCF, CoS, SSE] (value)\n\n");

                for (i = 0; i < info->num_mocs_regs; i++) {
                        if (regs_are_mcr(gt))
                                reg_val = xe_gt_mcr_unicast_read_any(gt, XEHP_GLOBAL_MOCS(i));
                        else
                                reg_val = xe_mmio_read32(&gt->mmio, XELP_GLOBAL_MOCS(i));

                        drm_printf(p, "GLOB_MOCS[%2d] = [%u, %u, %u, %u, %u, %u, %u, %u, %u, %u ] (%#8x)\n",
                                   i,
                                   REG_FIELD_GET(LE_CACHEABILITY_MASK, reg_val),
                                   REG_FIELD_GET(LE_TGT_CACHE_MASK, reg_val),
                                   REG_FIELD_GET(LE_LRUM_MASK, reg_val),
                                   !!(reg_val & LE_AOM_MASK),
                                   !!(reg_val & LE_RSC_MASK),
                                   REG_FIELD_GET(LE_SCC_MASK, reg_val),
                                   REG_FIELD_GET(LE_PFM_MASK, reg_val),
                                   !!(reg_val & LE_SCF_MASK),
                                   REG_FIELD_GET(LE_COS_MASK, reg_val),
                                   REG_FIELD_GET(LE_SSE_MASK, reg_val),
                                   reg_val);
                }
        }

        xelp_lncf_dump(info, gt, p);
}

static const struct xe_mocs_ops xelp_mocs_ops = {
        .dump = xelp_mocs_dump,
};

static const struct xe_mocs_entry dg1_mocs_desc[] = {
        /* UC */
        MOCS_ENTRY(1, 0, L3_1_UC),
        /* WB - L3 */
        MOCS_ENTRY(5, 0, L3_3_WB),
        /* WB - L3 50% */
        MOCS_ENTRY(6, 0, L3_ESC(1) | L3_SCC(1) | L3_3_WB),
        /* WB - L3 25% */
        MOCS_ENTRY(7, 0, L3_ESC(1) | L3_SCC(3) | L3_3_WB),
        /* WB - L3 12.5% */
        MOCS_ENTRY(8, 0, L3_ESC(1) | L3_SCC(7) | L3_3_WB),

        /* HDC:L1 + L3 */
        MOCS_ENTRY(48, 0, L3_3_WB),
        /* HDC:L1 */
        MOCS_ENTRY(49, 0, L3_1_UC),

        /* HW Reserved */
        MOCS_ENTRY(60, 0, L3_1_UC),
        MOCS_ENTRY(61, 0, L3_1_UC),
        MOCS_ENTRY(62, 0, L3_1_UC),
        MOCS_ENTRY(63, 0, L3_1_UC),
};

static const struct xe_mocs_entry dg2_mocs_desc[] = {
        /* UC - Coherent; GO:L3 */
        MOCS_ENTRY(0, 0, L3_1_UC | L3_LKUP(1)),
        /* UC - Coherent; GO:Memory */
        MOCS_ENTRY(1, 0, L3_1_UC | L3_GLBGO(1) | L3_LKUP(1)),
        /* UC - Non-Coherent; GO:Memory */
        MOCS_ENTRY(2, 0, L3_1_UC | L3_GLBGO(1)),

        /* WB - LC */
        MOCS_ENTRY(3, 0, L3_3_WB | L3_LKUP(1)),
};

static void xehp_lncf_dump(struct xe_mocs_info *info, unsigned int flags,
                           struct xe_gt *gt, struct drm_printer *p)
{
        unsigned int i, j;
        u32 reg_val;

        drm_printf(p, "LNCFCMOCS[idx] = [UCL3LOOKUP, GLBGO, L3CC] (value)\n\n");

        for (i = 0, j = 0; i < (info->num_mocs_regs + 1) / 2; i++, j++) {
                if (regs_are_mcr(gt))
                        reg_val = xe_gt_mcr_unicast_read_any(gt, XEHP_LNCFCMOCS(i));
                else
                        reg_val = xe_mmio_read32(&gt->mmio, XELP_LNCFCMOCS(i));

                drm_printf(p, "LNCFCMOCS[%2d] = [%u, %u, %u] (%#8x)\n",
                           j++,
                           !!(reg_val & L3_LKUP_MASK),
                           !!(reg_val & L3_GLBGO_MASK),
                           REG_FIELD_GET(L3_CACHEABILITY_MASK, reg_val),
                           reg_val);

                drm_printf(p, "LNCFCMOCS[%2d] = [%u, %u, %u] (%#8x)\n",
                           j,
                           !!(reg_val & L3_UPPER_LKUP_MASK),
                           !!(reg_val & L3_UPPER_GLBGO_MASK),
                           REG_FIELD_GET(L3_UPPER_IDX_CACHEABILITY_MASK, reg_val),
                           reg_val);
        }
}

static const struct xe_mocs_ops xehp_mocs_ops = {
        .dump = xehp_lncf_dump,
};

static const struct xe_mocs_entry pvc_mocs_desc[] = {
        /* Error */
        MOCS_ENTRY(0, 0, L3_3_WB),

        /* UC */
        MOCS_ENTRY(1, 0, L3_1_UC),

        /* WB */
        MOCS_ENTRY(2, 0, L3_3_WB),
};

static void pvc_mocs_dump(struct xe_mocs_info *info, unsigned int flags, struct xe_gt *gt,
                          struct drm_printer *p)
{
        unsigned int i, j;
        u32 reg_val;

        drm_printf(p, "LNCFCMOCS[idx] = [ L3CC ] (value)\n\n");

        for (i = 0, j = 0; i < (info->num_mocs_regs + 1) / 2; i++, j++) {
                if (regs_are_mcr(gt))
                        reg_val = xe_gt_mcr_unicast_read_any(gt, XEHP_LNCFCMOCS(i));
                else
                        reg_val = xe_mmio_read32(&gt->mmio, XELP_LNCFCMOCS(i));

                drm_printf(p, "LNCFCMOCS[%2d] = [ %u ] (%#8x)\n",
                           j++,
                           REG_FIELD_GET(L3_CACHEABILITY_MASK, reg_val),
                           reg_val);

                drm_printf(p, "LNCFCMOCS[%2d] = [ %u ] (%#8x)\n",
                           j,
                           REG_FIELD_GET(L3_UPPER_IDX_CACHEABILITY_MASK, reg_val),
                           reg_val);
        }
}

static const struct xe_mocs_ops pvc_mocs_ops = {
        .dump = pvc_mocs_dump,
};

static const struct xe_mocs_entry mtl_mocs_desc[] = {
        /* Error - Reserved for Non-Use */
        MOCS_ENTRY(0,
                   0,
                   L3_LKUP(1) | L3_3_WB),
        /* Cached - L3 + L4 */
        MOCS_ENTRY(1,
                   IG_PAT,
                   L3_LKUP(1) | L3_3_WB),
        /* L4 - GO:L3 */
        MOCS_ENTRY(2,
                   IG_PAT,
                   L3_LKUP(1) | L3_1_UC),
        /* Uncached - GO:L3 */
        MOCS_ENTRY(3,
                   IG_PAT | L4_3_UC,
                   L3_LKUP(1) | L3_1_UC),
        /* L4 - GO:Mem */
        MOCS_ENTRY(4,
                   IG_PAT,
                   L3_LKUP(1) | L3_GLBGO(1) | L3_1_UC),
        /* Uncached - GO:Mem */
        MOCS_ENTRY(5,
                   IG_PAT | L4_3_UC,
                   L3_LKUP(1) | L3_GLBGO(1) | L3_1_UC),
        /* L4 - L3:NoLKUP; GO:L3 */
        MOCS_ENTRY(6,
                   IG_PAT,
                   L3_1_UC),
        /* Uncached - L3:NoLKUP; GO:L3 */
        MOCS_ENTRY(7,
                   IG_PAT | L4_3_UC,
                   L3_1_UC),
        /* L4 - L3:NoLKUP; GO:Mem */
        MOCS_ENTRY(8,
                   IG_PAT,
                   L3_GLBGO(1) | L3_1_UC),
        /* Uncached - L3:NoLKUP; GO:Mem */
        MOCS_ENTRY(9,
                   IG_PAT | L4_3_UC,
                   L3_GLBGO(1) | L3_1_UC),
        /* Display - L3; L4:WT */
        MOCS_ENTRY(14,
                   IG_PAT | L4_1_WT,
                   L3_LKUP(1) | L3_3_WB),
        /* CCS - Non-Displayable */
        MOCS_ENTRY(15,
                   IG_PAT,
                   L3_GLBGO(1) | L3_1_UC),
};

static void mtl_mocs_dump(struct xe_mocs_info *info, unsigned int flags,
                          struct xe_gt *gt, struct drm_printer *p)
{
        unsigned int i;
        u32 reg_val;

        drm_printf(p, "Global mocs table configuration:\n");
        drm_printf(p, "GLOB_MOCS[idx] = [IG_PAT, L4_CACHE_POLICY] (value)\n\n");

        for (i = 0; i < info->num_mocs_regs; i++) {
                if (regs_are_mcr(gt))
                        reg_val = xe_gt_mcr_unicast_read_any(gt, XEHP_GLOBAL_MOCS(i));
                else
                        reg_val = xe_mmio_read32(&gt->mmio, XELP_GLOBAL_MOCS(i));

                drm_printf(p, "GLOB_MOCS[%2d] = [%u, %u]  (%#8x)\n",
                           i,
                           !!(reg_val & IG_PAT),
                           REG_FIELD_GET(L4_CACHE_POLICY_MASK, reg_val),
                           reg_val);
        }

        /* MTL lncf mocs table pattern is similar to that of xehp */
        xehp_lncf_dump(info, flags, gt, p);
}

static const struct xe_mocs_ops mtl_mocs_ops = {
        .dump = mtl_mocs_dump,
};

static const struct xe_mocs_entry xe2_mocs_table[] = {
        /* Defer to PAT */
        MOCS_ENTRY(0, XE2_L3_0_WB | L4_3_UC, 0),
        /* Cached L3, Uncached L4 */
        MOCS_ENTRY(1, IG_PAT | XE2_L3_0_WB | L4_3_UC, 0),
        /* Uncached L3, Cached L4 */
        MOCS_ENTRY(2, IG_PAT | XE2_L3_3_UC | L4_0_WB, 0),
        /* Uncached L3 + L4 */
        MOCS_ENTRY(3, IG_PAT | XE2_L3_3_UC | L4_3_UC, 0),
        /* Cached L3 + L4 */
        MOCS_ENTRY(4, IG_PAT | XE2_L3_0_WB | L4_0_WB, 0),
};

static void xe2_mocs_dump(struct xe_mocs_info *info, unsigned int flags,
                          struct xe_gt *gt, struct drm_printer *p)
{
        unsigned int i;
        u32 reg_val;

        drm_printf(p, "Global mocs table configuration:\n");
        drm_printf(p, "GLOB_MOCS[idx] = [IG_PAT, L3_CLOS, L3_CACHE_POLICY, L4_CACHE_POLICY] (value)\n\n");

        for (i = 0; i < info->num_mocs_regs; i++) {
                if (regs_are_mcr(gt))
                        reg_val = xe_gt_mcr_unicast_read_any(gt, XEHP_GLOBAL_MOCS(i));
                else
                        reg_val = xe_mmio_read32(&gt->mmio, XELP_GLOBAL_MOCS(i));

                drm_printf(p, "GLOB_MOCS[%2d] = [%u, %u, %u]  (%#8x)\n",
                           i,
                           !!(reg_val & IG_PAT),
                           REG_FIELD_GET(XE2_L3_CLOS_MASK, reg_val),
                           REG_FIELD_GET(L4_CACHE_POLICY_MASK, reg_val),
                           reg_val);
        }
}

static const struct xe_mocs_ops xe2_mocs_ops = {
        .dump = xe2_mocs_dump,
};

/*
 * Note that the "L3" and "L4" register fields actually control the L2 and L3
 * caches respectively on this platform.
 */
static const struct xe_mocs_entry xe3p_xpc_mocs_table[] = {
        /* Defer to PAT */
        MOCS_ENTRY(0, XE2_L3_0_WB | L4_3_UC, 0),
        /* UC */
        MOCS_ENTRY(1, IG_PAT | XE2_L3_3_UC | L4_3_UC, 0),
        /* L2 */
        MOCS_ENTRY(2, IG_PAT | XE2_L3_0_WB | L4_3_UC, 0),
        /* L3 */
        MOCS_ENTRY(3, IG_PAT | XE2_L3_3_UC | L4_0_WB, 0),
        /* L2 + L3 */
        MOCS_ENTRY(4, IG_PAT | XE2_L3_0_WB | L4_0_WB, 0),
};

static unsigned int get_mocs_settings(struct xe_device *xe,
                                      struct xe_mocs_info *info)
{
        unsigned int flags = 0;

        memset(info, 0, sizeof(struct xe_mocs_info));

        switch (xe->info.platform) {
        case XE_CRESCENTISLAND:
                info->ops = &xe2_mocs_ops;
                info->table_size = ARRAY_SIZE(xe3p_xpc_mocs_table);
                info->table = xe3p_xpc_mocs_table;
                info->num_mocs_regs = XE2_NUM_MOCS_ENTRIES;
                info->uc_index = 1;
                info->wb_index = 4;
                info->unused_entries_index = 4;
                break;
        case XE_NOVALAKE_S:
        case XE_PANTHERLAKE:
        case XE_LUNARLAKE:
        case XE_BATTLEMAGE:
                info->ops = &xe2_mocs_ops;
                info->table_size = ARRAY_SIZE(xe2_mocs_table);
                info->table = xe2_mocs_table;
                info->num_mocs_regs = XE2_NUM_MOCS_ENTRIES;
                info->uc_index = 3;
                info->wb_index = 4;
                info->unused_entries_index = 4;
                break;
        case XE_PVC:
                info->ops = &pvc_mocs_ops;
                info->table_size = ARRAY_SIZE(pvc_mocs_desc);
                info->table = pvc_mocs_desc;
                info->num_mocs_regs = PVC_NUM_MOCS_ENTRIES;
                info->uc_index = 1;
                info->wb_index = 2;
                info->unused_entries_index = 2;
                break;
        case XE_METEORLAKE:
                info->ops = &mtl_mocs_ops;
                info->table_size = ARRAY_SIZE(mtl_mocs_desc);
                info->table = mtl_mocs_desc;
                info->num_mocs_regs = MTL_NUM_MOCS_ENTRIES;
                info->uc_index = 9;
                info->unused_entries_index = 1;
                break;
        case XE_DG2:
                info->ops = &xehp_mocs_ops;
                info->table_size = ARRAY_SIZE(dg2_mocs_desc);
                info->table = dg2_mocs_desc;
                info->uc_index = 1;
                /*
                 * Last entry is RO on hardware, don't bother with what was
                 * written when checking later
                 */
                info->num_mocs_regs = XELP_NUM_MOCS_ENTRIES - 1;
                info->unused_entries_index = 3;
                break;
        case XE_DG1:
                info->ops = &xelp_mocs_ops;
                info->table_size = ARRAY_SIZE(dg1_mocs_desc);
                info->table = dg1_mocs_desc;
                info->uc_index = 1;
                info->num_mocs_regs = XELP_NUM_MOCS_ENTRIES;
                info->unused_entries_index = 5;
                break;
        case XE_TIGERLAKE:
        case XE_ROCKETLAKE:
        case XE_ALDERLAKE_S:
        case XE_ALDERLAKE_P:
        case XE_ALDERLAKE_N:
                info->ops = &xelp_mocs_ops;
                info->table_size  = ARRAY_SIZE(gen12_mocs_desc);
                info->table = gen12_mocs_desc;
                info->num_mocs_regs = XELP_NUM_MOCS_ENTRIES;
                info->uc_index = 3;
                info->unused_entries_index = 2;
                break;
        default:
                drm_err(&xe->drm, "Platform that should have a MOCS table does not.\n");
                return 0;
        }

        /*
         * Index 0 is a reserved/unused table entry on most platforms, but
         * even on those where it does represent a legitimate MOCS entry, it
         * never represents the "most cached, least coherent" behavior we want
         * to populate undefined table rows with.  So if unused_entries_index
         * is still 0 at this point, we'll assume that it was omitted by
         * mistake in the switch statement above.
         */
        xe_assert(xe, info->unused_entries_index != 0);

        xe_assert(xe, info->ops && info->ops->dump);
        xe_assert(xe, info->table_size <= info->num_mocs_regs);

        if (!IS_DGFX(xe) || GRAPHICS_VER(xe) >= 20)
                flags |= HAS_GLOBAL_MOCS;
        if (GRAPHICS_VER(xe) < 20)
                flags |= HAS_LNCF_MOCS;

        return flags;
}

/*
 * Get control_value from MOCS entry.  If the table entry is not defined, the
 * settings from unused_entries_index will be returned.
 */
static u32 get_entry_control(const struct xe_mocs_info *info,
                             unsigned int index)
{
        if (index < info->table_size && info->table[index].used)
                return info->table[index].control_value;
        return info->table[info->unused_entries_index].control_value;
}

static void __init_mocs_table(struct xe_gt *gt,
                              const struct xe_mocs_info *info)
{
        unsigned int i;
        u32 mocs;

        mocs_dbg(gt, "mocs entries: %d\n", info->num_mocs_regs);

        for (i = 0; i < info->num_mocs_regs; i++) {
                mocs = get_entry_control(info, i);

                mocs_dbg(gt, "GLOB_MOCS[%d] 0x%x 0x%x\n", i,
                         XELP_GLOBAL_MOCS(i).addr, mocs);

                if (regs_are_mcr(gt))
                        xe_gt_mcr_multicast_write(gt, XEHP_GLOBAL_MOCS(i), mocs);
                else
                        xe_mmio_write32(&gt->mmio, XELP_GLOBAL_MOCS(i), mocs);
        }
}

/*
 * Get l3cc_value from MOCS entry taking into account when it's not used
 * then if unused_entries_index is not zero then its value will be returned
 * otherwise I915_MOCS_PTE's value is returned in this case.
 */
static u16 get_entry_l3cc(const struct xe_mocs_info *info,
                          unsigned int index)
{
        if (index < info->table_size && info->table[index].used)
                return info->table[index].l3cc_value;
        return info->table[info->unused_entries_index].l3cc_value;
}

static u32 l3cc_combine(u16 low, u16 high)
{
        return low | (u32)high << 16;
}

static void init_l3cc_table(struct xe_gt *gt,
                            const struct xe_mocs_info *info)
{
        unsigned int i;
        u32 l3cc;

        mocs_dbg(gt, "l3cc entries: %d\n", info->num_mocs_regs);

        for (i = 0; i < (info->num_mocs_regs + 1) / 2; i++) {
                l3cc = l3cc_combine(get_entry_l3cc(info, 2 * i),
                                    get_entry_l3cc(info, 2 * i + 1));

                mocs_dbg(gt, "LNCFCMOCS[%d] 0x%x 0x%x\n", i,
                         XELP_LNCFCMOCS(i).addr, l3cc);

                if (regs_are_mcr(gt))
                        xe_gt_mcr_multicast_write(gt, XEHP_LNCFCMOCS(i), l3cc);
                else
                        xe_mmio_write32(&gt->mmio, XELP_LNCFCMOCS(i), l3cc);
        }
}

void xe_mocs_init_early(struct xe_gt *gt)
{
        struct xe_mocs_info table;

        get_mocs_settings(gt_to_xe(gt), &table);
        gt->mocs.uc_index = table.uc_index;
        gt->mocs.wb_index = table.wb_index;
}

void xe_mocs_init(struct xe_gt *gt)
{
        struct xe_mocs_info table;
        unsigned int flags;

        if (IS_SRIOV_VF(gt_to_xe(gt)))
                return;

        /*
         * MOCS settings are split between "GLOB_MOCS" and/or "LNCFCMOCS"
         * registers depending on platform.
         *
         * These registers should be programmed before GuC initialization
         * since their values will affect some of the memory transactions
         * performed by the GuC.
         */
        flags = get_mocs_settings(gt_to_xe(gt), &table);
        mocs_dbg(gt, "flag:0x%x\n", flags);

        if (IS_SRIOV_VF(gt_to_xe(gt)))
                return;

        if (flags & HAS_GLOBAL_MOCS)
                __init_mocs_table(gt, &table);
        if (flags & HAS_LNCF_MOCS)
                init_l3cc_table(gt, &table);
}

/**
 * xe_mocs_dump() - Dump MOCS table.
 * @gt: the &xe_gt with MOCS table
 * @p: the &drm_printer to dump info to
 *
 * Return: 0 on success or a negative error code on failure.
 */
int xe_mocs_dump(struct xe_gt *gt, struct drm_printer *p)
{
        struct xe_device *xe = gt_to_xe(gt);
        enum xe_force_wake_domains domain;
        struct xe_mocs_info table;
        unsigned int flags;

        flags = get_mocs_settings(xe, &table);

        domain = flags & HAS_LNCF_MOCS ? XE_FORCEWAKE_ALL : XE_FW_GT;

        guard(xe_pm_runtime_noresume)(xe);
        CLASS(xe_force_wake, fw_ref)(gt_to_fw(gt), domain);
        if (!xe_force_wake_ref_has_domain(fw_ref.domains, domain))
                return -ETIMEDOUT;

        table.ops->dump(&table, flags, gt, p);

        return 0;
}

#if IS_ENABLED(CONFIG_DRM_XE_KUNIT_TEST)
#include "tests/xe_mocs.c"
#endif