root/drivers/ras/amd/atl/access.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * AMD Address Translation Library
 *
 * access.c : DF Indirect Access functions
 *
 * Copyright (c) 2023, Advanced Micro Devices, Inc.
 * All Rights Reserved.
 *
 * Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
 */

#include "internal.h"

/* Protect the PCI config register pairs used for DF indirect access. */
static DEFINE_MUTEX(df_indirect_mutex);

/*
 * Data Fabric Indirect Access uses FICAA/FICAD.
 *
 * Fabric Indirect Configuration Access Address (FICAA): constructed based
 * on the device's Instance Id and the PCI function and register offset of
 * the desired register.
 *
 * Fabric Indirect Configuration Access Data (FICAD): there are FICAD
 * low and high registers but so far only the low register is needed.
 *
 * Use Instance Id 0xFF to indicate a broadcast read.
 */
#define DF_BROADCAST            0xFF

#define DF_FICAA_INST_EN        BIT(0)
#define DF_FICAA_REG_NUM        GENMASK(10, 1)
#define DF_FICAA_FUNC_NUM       GENMASK(13, 11)
#define DF_FICAA_INST_ID        GENMASK(23, 16)

#define DF_FICAA_REG_NUM_LEGACY GENMASK(10, 2)

static u16 get_accessible_node(u16 node)
{
        /*
         * On heterogeneous systems, not all AMD Nodes are accessible
         * through software-visible registers. The Node ID needs to be
         * adjusted for register accesses. But its value should not be
         * changed for the translation methods.
         */
        if (df_cfg.flags.heterogeneous) {
                /* Only Node 0 is accessible on DF3.5 systems. */
                if (df_cfg.rev == DF3p5)
                        node = 0;

                /*
                 * Only the first Node in each Socket is accessible on
                 * DF4.5 systems, and this is visible to software as one
                 * Fabric per Socket.  The Socket ID can be derived from
                 * the Node ID and global shift values.
                 */
                if (df_cfg.rev == DF4p5)
                        node >>= df_cfg.socket_id_shift - df_cfg.node_id_shift;
        }

        return node;
}

static int __df_indirect_read(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo)
{
        u32 ficaa_addr = 0x8C, ficad_addr = 0xB8;
        struct pci_dev *F4;
        int err = -ENODEV;
        u32 ficaa = 0;

        node = get_accessible_node(node);
        if (node >= amd_nb_num()) {
                pr_debug("Node %u is out of bounds\n", node);
                goto out;
        }

        F4 = node_to_amd_nb(node)->link;
        if (!F4) {
                pr_debug("DF function 4 not found\n");
                goto out;
        }

        /* Enable instance-specific access. */
        if (instance_id != DF_BROADCAST) {
                ficaa |= FIELD_PREP(DF_FICAA_INST_EN, 1);
                ficaa |= FIELD_PREP(DF_FICAA_INST_ID, instance_id);
        }

        /*
         * The two least-significant bits are masked when inputing the
         * register offset to FICAA.
         */
        reg >>= 2;

        if (df_cfg.flags.legacy_ficaa) {
                ficaa_addr = 0x5C;
                ficad_addr = 0x98;

                ficaa |= FIELD_PREP(DF_FICAA_REG_NUM_LEGACY, reg);
        } else {
                ficaa |= FIELD_PREP(DF_FICAA_REG_NUM, reg);
        }

        ficaa |= FIELD_PREP(DF_FICAA_FUNC_NUM, func);

        mutex_lock(&df_indirect_mutex);

        err = pci_write_config_dword(F4, ficaa_addr, ficaa);
        if (err) {
                pr_warn("Error writing DF Indirect FICAA, FICAA=0x%x\n", ficaa);
                goto out_unlock;
        }

        err = pci_read_config_dword(F4, ficad_addr, lo);
        if (err)
                pr_warn("Error reading DF Indirect FICAD LO, FICAA=0x%x.\n", ficaa);

        pr_debug("node=%u inst=0x%x func=0x%x reg=0x%x val=0x%x",
                 node, instance_id, func, reg << 2, *lo);

out_unlock:
        mutex_unlock(&df_indirect_mutex);

out:
        return err;
}

int df_indirect_read_instance(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo)
{
        return __df_indirect_read(node, func, reg, instance_id, lo);
}

int df_indirect_read_broadcast(u16 node, u8 func, u16 reg, u32 *lo)
{
        return __df_indirect_read(node, func, reg, DF_BROADCAST, lo);
}