// 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 */ #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); }