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

#include <linux/module.h>
#include <asm/cpu_device_id.h>

#include "internal.h"

struct df_config df_cfg __read_mostly;

static int addr_over_limit(struct addr_ctx *ctx)
{
        u64 dram_limit_addr;

        if (df_cfg.rev >= DF4)
                dram_limit_addr = FIELD_GET(DF4_DRAM_LIMIT_ADDR, ctx->map.limit);
        else
                dram_limit_addr = FIELD_GET(DF2_DRAM_LIMIT_ADDR, ctx->map.limit);

        dram_limit_addr <<= DF_DRAM_BASE_LIMIT_LSB;
        dram_limit_addr |= GENMASK(DF_DRAM_BASE_LIMIT_LSB - 1, 0);

        /* Is calculated system address above DRAM limit address? */
        if (ctx->ret_addr > dram_limit_addr) {
                atl_debug(ctx, "Calculated address (0x%016llx) > DRAM limit (0x%016llx)",
                          ctx->ret_addr, dram_limit_addr);
                return -EINVAL;
        }

        return 0;
}

static bool legacy_hole_en(struct addr_ctx *ctx)
{
        u32 reg = ctx->map.base;

        if (df_cfg.rev >= DF4)
                reg = ctx->map.ctl;

        return FIELD_GET(DF_LEGACY_MMIO_HOLE_EN, reg);
}

static u64 add_legacy_hole(struct addr_ctx *ctx, u64 addr)
{
        if (!legacy_hole_en(ctx))
                return addr;

        if (addr >= df_cfg.dram_hole_base)
                addr += (BIT_ULL(32) - df_cfg.dram_hole_base);

        return addr;
}

static u64 remove_legacy_hole(struct addr_ctx *ctx, u64 addr)
{
        if (!legacy_hole_en(ctx))
                return addr;

        if (addr >= df_cfg.dram_hole_base)
                addr -= (BIT_ULL(32) - df_cfg.dram_hole_base);

        return addr;
}

static u64 get_base_addr(struct addr_ctx *ctx)
{
        u64 base_addr;

        if (df_cfg.rev >= DF4)
                base_addr = FIELD_GET(DF4_BASE_ADDR, ctx->map.base);
        else
                base_addr = FIELD_GET(DF2_BASE_ADDR, ctx->map.base);

        return base_addr << DF_DRAM_BASE_LIMIT_LSB;
}

u64 add_base_and_hole(struct addr_ctx *ctx, u64 addr)
{
        return add_legacy_hole(ctx, addr + get_base_addr(ctx));
}

u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr)
{
        return remove_legacy_hole(ctx, addr) - get_base_addr(ctx);
}

static bool late_hole_remove(struct addr_ctx *ctx)
{
        if (df_cfg.rev == DF3p5)
                return true;

        if (df_cfg.rev == DF4)
                return true;

        if (ctx->map.intlv_mode == DF3_6CHAN)
                return true;

        return false;
}

unsigned long norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id, unsigned long addr)
{
        struct addr_ctx ctx;

        if (df_cfg.rev == UNKNOWN)
                return -EINVAL;

        memset(&ctx, 0, sizeof(ctx));

        /* Start from the normalized address */
        ctx.ret_addr = addr;
        ctx.inst_id = coh_st_inst_id;

        ctx.inputs.norm_addr = addr;
        ctx.inputs.socket_id = socket_id;
        ctx.inputs.die_id = die_id;
        ctx.inputs.coh_st_inst_id = coh_st_inst_id;

        if (legacy_hole_en(&ctx) && !df_cfg.dram_hole_base)
                return -EINVAL;

        if (determine_node_id(&ctx, socket_id, die_id))
                return -EINVAL;

        if (get_address_map(&ctx))
                return -EINVAL;

        if (denormalize_address(&ctx))
                return -EINVAL;

        if (!late_hole_remove(&ctx))
                ctx.ret_addr = add_base_and_hole(&ctx, ctx.ret_addr);

        if (dehash_address(&ctx))
                return -EINVAL;

        if (late_hole_remove(&ctx))
                ctx.ret_addr = add_base_and_hole(&ctx, ctx.ret_addr);

        if (addr_over_limit(&ctx))
                return -EINVAL;

        return ctx.ret_addr;
}

static void check_for_legacy_df_access(void)
{
        /*
         * All Zen-based systems before Family 19h use the legacy
         * DF Indirect Access (FICAA/FICAD) offsets.
         */
        if (boot_cpu_data.x86 < 0x19) {
                df_cfg.flags.legacy_ficaa = true;
                return;
        }

        /* All systems after Family 19h use the current offsets. */
        if (boot_cpu_data.x86 > 0x19)
                return;

        /* Some Family 19h systems use the legacy offsets. */
        switch (boot_cpu_data.x86_model) {
        case 0x00 ... 0x0f:
        case 0x20 ... 0x5f:
               df_cfg.flags.legacy_ficaa = true;
        }
}

/*
 * This library provides functionality for AMD-based systems with a Data Fabric.
 * The set of systems with a Data Fabric is equivalent to the set of Zen-based systems
 * and the set of systems with the Scalable MCA feature at this time. However, these
 * are technically independent things.
 *
 * It's possible to match on the PCI IDs of the Data Fabric devices, but this will be
 * an ever expanding list. Instead, match on the SMCA and Zen features to cover all
 * relevant systems.
 */
static const struct x86_cpu_id amd_atl_cpuids[] = {
        X86_MATCH_FEATURE(X86_FEATURE_SMCA, NULL),
        X86_MATCH_FEATURE(X86_FEATURE_ZEN, NULL),
        { }
};
MODULE_DEVICE_TABLE(x86cpu, amd_atl_cpuids);

static int __init amd_atl_init(void)
{
        int ret;

        if (!x86_match_cpu(amd_atl_cpuids))
                return -ENODEV;

        if (!amd_nb_num())
                return -ENODEV;

        check_for_legacy_df_access();

        ret = get_df_system_info();
        if (ret)
                return ret;

        /* Increment this module's recount so that it can't be easily unloaded. */
        __module_get(THIS_MODULE);
        amd_atl_register_decoder(convert_umc_mca_addr_to_sys_addr);

        pr_info("AMD Address Translation Library initialized\n");
        return 0;
}

/*
 * Exit function is only needed for testing and debug. Module unload must be
 * forced to override refcount check.
 */
static void __exit amd_atl_exit(void)
{
        amd_atl_unregister_decoder();
}

module_init(amd_atl_init);
module_exit(amd_atl_exit);

MODULE_DESCRIPTION("AMD Address Translation Library");
MODULE_LICENSE("GPL");