| /* SPDX-License-Identifier: GPL-2.0 */ | |
| /* | |
| * AMD Address Translation Library | |
| * | |
| * internal.h : Helper functions and common defines | |
| * | |
| * Copyright (c) 2023, Advanced Micro Devices, Inc. | |
| * All Rights Reserved. | |
| * | |
| * Author: Yazen Ghannam <Yazen.Ghannam@amd.com> | |
| */ | |
| #ifndef __AMD_ATL_INTERNAL_H__ | |
| #define __AMD_ATL_INTERNAL_H__ | |
| #include <linux/bitfield.h> | |
| #include <linux/bitops.h> | |
| #include <linux/ras.h> | |
| #include <asm/amd_nb.h> | |
| #include "reg_fields.h" | |
| #undef pr_fmt | |
| #define pr_fmt(fmt) "amd_atl: " fmt | |
| /* Maximum possible number of Coherent Stations within a single Data Fabric. */ | |
| #define MAX_COH_ST_CHANNELS 32 | |
| /* PCI ID for Zen4 Server DF Function 0. */ | |
| #define DF_FUNC0_ID_ZEN4_SERVER 0x14AD1022 | |
| /* PCI IDs for MI300 DF Function 0. */ | |
| #define DF_FUNC0_ID_MI300 0x15281022 | |
| /* Shift needed for adjusting register values to true values. */ | |
| #define DF_DRAM_BASE_LIMIT_LSB 28 | |
| #define MI300_DRAM_LIMIT_LSB 20 | |
| #define INVALID_SPA ~0ULL | |
| enum df_revisions { | |
| UNKNOWN, | |
| DF2, | |
| DF3, | |
| DF3p5, | |
| DF4, | |
| DF4p5, | |
| }; | |
| /* These are mapped 1:1 to the hardware values. Special cases are set at > 0x20. */ | |
| enum intlv_modes { | |
| NONE = 0x00, | |
| NOHASH_2CHAN = 0x01, | |
| NOHASH_4CHAN = 0x03, | |
| NOHASH_8CHAN = 0x05, | |
| DF3_6CHAN = 0x06, | |
| NOHASH_16CHAN = 0x07, | |
| NOHASH_32CHAN = 0x08, | |
| DF3_COD4_2CHAN_HASH = 0x0C, | |
| DF3_COD2_4CHAN_HASH = 0x0D, | |
| DF3_COD1_8CHAN_HASH = 0x0E, | |
| DF4_NPS4_2CHAN_HASH = 0x10, | |
| DF4_NPS2_4CHAN_HASH = 0x11, | |
| DF4_NPS1_8CHAN_HASH = 0x12, | |
| DF4_NPS4_3CHAN_HASH = 0x13, | |
| DF4_NPS2_6CHAN_HASH = 0x14, | |
| DF4_NPS1_12CHAN_HASH = 0x15, | |
| DF4_NPS2_5CHAN_HASH = 0x16, | |
| DF4_NPS1_10CHAN_HASH = 0x17, | |
| MI3_HASH_8CHAN = 0x18, | |
| MI3_HASH_16CHAN = 0x19, | |
| MI3_HASH_32CHAN = 0x1A, | |
| DF2_2CHAN_HASH = 0x21, | |
| /* DF4.5 modes are all IntLvNumChan + 0x20 */ | |
| DF4p5_NPS1_16CHAN_1K_HASH = 0x2C, | |
| DF4p5_NPS0_24CHAN_1K_HASH = 0x2E, | |
| DF4p5_NPS4_2CHAN_1K_HASH = 0x30, | |
| DF4p5_NPS2_4CHAN_1K_HASH = 0x31, | |
| DF4p5_NPS1_8CHAN_1K_HASH = 0x32, | |
| DF4p5_NPS4_3CHAN_1K_HASH = 0x33, | |
| DF4p5_NPS2_6CHAN_1K_HASH = 0x34, | |
| DF4p5_NPS1_12CHAN_1K_HASH = 0x35, | |
| DF4p5_NPS2_5CHAN_1K_HASH = 0x36, | |
| DF4p5_NPS1_10CHAN_1K_HASH = 0x37, | |
| DF4p5_NPS4_2CHAN_2K_HASH = 0x40, | |
| DF4p5_NPS2_4CHAN_2K_HASH = 0x41, | |
| DF4p5_NPS1_8CHAN_2K_HASH = 0x42, | |
| DF4p5_NPS1_16CHAN_2K_HASH = 0x43, | |
| DF4p5_NPS4_3CHAN_2K_HASH = 0x44, | |
| DF4p5_NPS2_6CHAN_2K_HASH = 0x45, | |
| DF4p5_NPS1_12CHAN_2K_HASH = 0x46, | |
| DF4p5_NPS0_24CHAN_2K_HASH = 0x47, | |
| DF4p5_NPS2_5CHAN_2K_HASH = 0x48, | |
| DF4p5_NPS1_10CHAN_2K_HASH = 0x49, | |
| }; | |
| struct df4p5_denorm_ctx { | |
| /* Indicates the number of "lost" bits. This will be 1, 2, or 3. */ | |
| u8 perm_shift; | |
| /* A mask indicating the bits that need to be rehashed. */ | |
| u16 rehash_vector; | |
| /* | |
| * Represents the value that the high bits of the normalized address | |
| * are divided by during normalization. This value will be 3 for | |
| * interleave modes with a number of channels divisible by 3 or the | |
| * value will be 5 for interleave modes with a number of channels | |
| * divisible by 5. Power-of-two interleave modes are handled | |
| * separately. | |
| */ | |
| u8 mod_value; | |
| /* | |
| * Represents the bits that can be directly pulled from the normalized | |
| * address. In each case, pass through bits [7:0] of the normalized | |
| * address. The other bits depend on the interleave bit position which | |
| * will be bit 10 for 1K interleave stripe cases and bit 11 for 2K | |
| * interleave stripe cases. | |
| */ | |
| u64 base_denorm_addr; | |
| /* | |
| * Represents the high bits of the physical address that have been | |
| * divided by the mod_value. | |
| */ | |
| u64 div_addr; | |
| u64 current_spa; | |
| u64 resolved_spa; | |
| u16 coh_st_fabric_id; | |
| }; | |
| struct df_flags { | |
| __u8 legacy_ficaa : 1, | |
| socket_id_shift_quirk : 1, | |
| heterogeneous : 1, | |
| __reserved_0 : 5; | |
| }; | |
| struct df_config { | |
| enum df_revisions rev; | |
| /* | |
| * These masks operate on the 16-bit Coherent Station IDs, | |
| * e.g. Instance, Fabric, Destination, etc. | |
| */ | |
| u16 component_id_mask; | |
| u16 die_id_mask; | |
| u16 node_id_mask; | |
| u16 socket_id_mask; | |
| /* | |
| * Least-significant bit of Node ID portion of the | |
| * system-wide Coherent Station Fabric ID. | |
| */ | |
| u8 node_id_shift; | |
| /* | |
| * Least-significant bit of Die portion of the Node ID. | |
| * Adjusted to include the Node ID shift in order to apply | |
| * to the Coherent Station Fabric ID. | |
| */ | |
| u8 die_id_shift; | |
| /* | |
| * Least-significant bit of Socket portion of the Node ID. | |
| * Adjusted to include the Node ID shift in order to apply | |
| * to the Coherent Station Fabric ID. | |
| */ | |
| u8 socket_id_shift; | |
| /* Number of DRAM Address maps visible in a Coherent Station. */ | |
| u8 num_coh_st_maps; | |
| u32 dram_hole_base; | |
| /* Global flags to handle special cases. */ | |
| struct df_flags flags; | |
| }; | |
| extern struct df_config df_cfg; | |
| struct dram_addr_map { | |
| /* | |
| * Each DRAM Address Map can operate independently | |
| * in different interleaving modes. | |
| */ | |
| enum intlv_modes intlv_mode; | |
| /* System-wide number for this address map. */ | |
| u8 num; | |
| /* Raw register values */ | |
| u32 base; | |
| u32 limit; | |
| u32 ctl; | |
| u32 intlv; | |
| /* | |
| * Logical to Physical Coherent Station Remapping array | |
| * | |
| * Index: Logical Coherent Station Instance ID | |
| * Value: Physical Coherent Station Instance ID | |
| * | |
| * phys_coh_st_inst_id = remap_array[log_coh_st_inst_id] | |
| */ | |
| u8 remap_array[MAX_COH_ST_CHANNELS]; | |
| /* | |
| * Number of bits covering DRAM Address map 0 | |
| * when interleaving is non-power-of-2. | |
| * | |
| * Used only for DF3_6CHAN. | |
| */ | |
| u8 np2_bits; | |
| /* Position of the 'interleave bit'. */ | |
| u8 intlv_bit_pos; | |
| /* Number of channels interleaved in this map. */ | |
| u8 num_intlv_chan; | |
| /* Number of dies interleaved in this map. */ | |
| u8 num_intlv_dies; | |
| /* Number of sockets interleaved in this map. */ | |
| u8 num_intlv_sockets; | |
| /* | |
| * Total number of channels interleaved accounting | |
| * for die and socket interleaving. | |
| */ | |
| u8 total_intlv_chan; | |
| /* Total bits needed to cover 'total_intlv_chan'. */ | |
| u8 total_intlv_bits; | |
| }; | |
| /* Original input values cached for debug printing. */ | |
| struct addr_ctx_inputs { | |
| u64 norm_addr; | |
| u8 socket_id; | |
| u8 die_id; | |
| u8 coh_st_inst_id; | |
| }; | |
| struct addr_ctx { | |
| u64 ret_addr; | |
| struct addr_ctx_inputs inputs; | |
| struct dram_addr_map map; | |
| /* AMD Node ID calculated from Socket and Die IDs. */ | |
| u8 node_id; | |
| /* | |
| * Coherent Station Instance ID | |
| * Local ID used within a 'node'. | |
| */ | |
| u16 inst_id; | |
| /* | |
| * Coherent Station Fabric ID | |
| * System-wide ID that includes 'node' bits. | |
| */ | |
| u16 coh_st_fabric_id; | |
| }; | |
| int df_indirect_read_instance(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo); | |
| int df_indirect_read_broadcast(u16 node, u8 func, u16 reg, u32 *lo); | |
| int get_df_system_info(void); | |
| int determine_node_id(struct addr_ctx *ctx, u8 socket_num, u8 die_num); | |
| int get_umc_info_mi300(void); | |
| int get_address_map(struct addr_ctx *ctx); | |
| int denormalize_address(struct addr_ctx *ctx); | |
| int dehash_address(struct addr_ctx *ctx); | |
| unsigned long norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id, unsigned long addr); | |
| unsigned long convert_umc_mca_addr_to_sys_addr(struct atl_err *err); | |
| u64 add_base_and_hole(struct addr_ctx *ctx, u64 addr); | |
| u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr); | |
| #ifdef CONFIG_AMD_ATL_PRM | |
| unsigned long prm_umc_norm_to_sys_addr(u8 socket_id, u64 umc_bank_inst_id, unsigned long addr); | |
| #else | |
| static inline unsigned long prm_umc_norm_to_sys_addr(u8 socket_id, u64 umc_bank_inst_id, | |
| unsigned long addr) | |
| { | |
| return -ENODEV; | |
| } | |
| #endif | |
| /* | |
| * Make a gap in @data that is @num_bits long starting at @bit_num. | |
| * e.g. data = 11111111'b | |
| * bit_num = 3 | |
| * num_bits = 2 | |
| * result = 1111100111'b | |
| */ | |
| static inline u64 expand_bits(u8 bit_num, u8 num_bits, u64 data) | |
| { | |
| u64 temp1, temp2; | |
| if (!num_bits) | |
| return data; | |
| if (!bit_num) { | |
| WARN_ON_ONCE(num_bits >= BITS_PER_LONG); | |
| return data << num_bits; | |
| } | |
| WARN_ON_ONCE(bit_num >= BITS_PER_LONG); | |
| temp1 = data & GENMASK_ULL(bit_num - 1, 0); | |
| temp2 = data & GENMASK_ULL(63, bit_num); | |
| temp2 <<= num_bits; | |
| return temp1 | temp2; | |
| } | |
| /* | |
| * Remove bits in @data between @low_bit and @high_bit inclusive. | |
| * e.g. data = XXXYYZZZ'b | |
| * low_bit = 3 | |
| * high_bit = 4 | |
| * result = XXXZZZ'b | |
| */ | |
| static inline u64 remove_bits(u8 low_bit, u8 high_bit, u64 data) | |
| { | |
| u64 temp1, temp2; | |
| WARN_ON_ONCE(high_bit >= BITS_PER_LONG); | |
| WARN_ON_ONCE(low_bit >= BITS_PER_LONG); | |
| WARN_ON_ONCE(low_bit > high_bit); | |
| if (!low_bit) | |
| return data >> (high_bit++); | |
| temp1 = GENMASK_ULL(low_bit - 1, 0) & data; | |
| temp2 = GENMASK_ULL(63, high_bit + 1) & data; | |
| temp2 >>= high_bit - low_bit + 1; | |
| return temp1 | temp2; | |
| } | |
| #define atl_debug(ctx, fmt, arg...) \ | |
| pr_debug("socket_id=%u die_id=%u coh_st_inst_id=%u norm_addr=0x%016llx: " fmt,\ | |
| (ctx)->inputs.socket_id, (ctx)->inputs.die_id,\ | |
| (ctx)->inputs.coh_st_inst_id, (ctx)->inputs.norm_addr, ##arg) | |
| static inline void atl_debug_on_bad_df_rev(void) | |
| { | |
| pr_debug("Unrecognized DF rev: %u", df_cfg.rev); | |
| } | |
| static inline void atl_debug_on_bad_intlv_mode(struct addr_ctx *ctx) | |
| { | |
| atl_debug(ctx, "Unrecognized interleave mode: %u", ctx->map.intlv_mode); | |
| } | |
| #endif /* __AMD_ATL_INTERNAL_H__ */ |