|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | +/* |
| 3 | + * AMD MP1 Smart Trace Buffer (STB) Layer |
| 4 | + * |
| 5 | + * Copyright (c) 2024, Advanced Micro Devices, Inc. |
| 6 | + * All Rights Reserved. |
| 7 | + * |
| 8 | + * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com> |
| 9 | + * Sanket Goswami <Sanket.Goswami@amd.com> |
| 10 | + */ |
| 11 | + |
| 12 | +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 13 | + |
| 14 | +#include <asm/amd_nb.h> |
| 15 | +#include <linux/debugfs.h> |
| 16 | +#include <linux/seq_file.h> |
| 17 | +#include <linux/uaccess.h> |
| 18 | + |
| 19 | +#include "pmc.h" |
| 20 | + |
| 21 | +/* STB Spill to DRAM Parameters */ |
| 22 | +#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000 |
| 23 | +#define S2D_TELEMETRY_BYTES_MAX 0x100000U |
| 24 | +#define S2D_RSVD_RAM_SPACE 0x100000 |
| 25 | + |
| 26 | +/* STB Registers */ |
| 27 | +#define AMD_STB_PMI_0 0x03E30600 |
| 28 | +#define AMD_PMC_STB_DUMMY_PC 0xC6000007 |
| 29 | + |
| 30 | +/* STB Spill to DRAM Message Definition */ |
| 31 | +#define STB_FORCE_FLUSH_DATA 0xCF |
| 32 | +#define FIFO_SIZE 4096 |
| 33 | + |
| 34 | +/* STB S2D(Spill to DRAM) has different message port offset */ |
| 35 | +#define AMD_S2D_REGISTER_MESSAGE 0xA20 |
| 36 | +#define AMD_S2D_REGISTER_RESPONSE 0xA80 |
| 37 | +#define AMD_S2D_REGISTER_ARGUMENT 0xA88 |
| 38 | + |
| 39 | +/* STB S2D (Spill to DRAM) message port offset for 44h model */ |
| 40 | +#define AMD_GNR_REGISTER_MESSAGE 0x524 |
| 41 | +#define AMD_GNR_REGISTER_RESPONSE 0x570 |
| 42 | +#define AMD_GNR_REGISTER_ARGUMENT 0xA40 |
| 43 | + |
| 44 | +static bool enable_stb; |
| 45 | +module_param(enable_stb, bool, 0644); |
| 46 | +MODULE_PARM_DESC(enable_stb, "Enable the STB debug mechanism"); |
| 47 | + |
| 48 | +static bool dump_custom_stb; |
| 49 | +module_param(dump_custom_stb, bool, 0644); |
| 50 | +MODULE_PARM_DESC(dump_custom_stb, "Enable to dump full STB buffer"); |
| 51 | + |
| 52 | +enum s2d_arg { |
| 53 | + S2D_TELEMETRY_SIZE = 0x01, |
| 54 | + S2D_PHYS_ADDR_LOW, |
| 55 | + S2D_PHYS_ADDR_HIGH, |
| 56 | + S2D_NUM_SAMPLES, |
| 57 | + S2D_DRAM_SIZE, |
| 58 | +}; |
| 59 | + |
| 60 | +struct amd_stb_v2_data { |
| 61 | + size_t size; |
| 62 | + u8 data[] __counted_by(size); |
| 63 | +}; |
| 64 | + |
| 65 | +int amd_stb_write(struct amd_pmc_dev *dev, u32 data) |
| 66 | +{ |
| 67 | + int err; |
| 68 | + |
| 69 | + err = amd_smn_write(0, AMD_STB_PMI_0, data); |
| 70 | + if (err) { |
| 71 | + dev_err(dev->dev, "failed to write data in stb: 0x%X\n", AMD_STB_PMI_0); |
| 72 | + return pcibios_err_to_errno(err); |
| 73 | + } |
| 74 | + |
| 75 | + return 0; |
| 76 | +} |
| 77 | + |
| 78 | +int amd_stb_read(struct amd_pmc_dev *dev, u32 *buf) |
| 79 | +{ |
| 80 | + int i, err; |
| 81 | + |
| 82 | + for (i = 0; i < FIFO_SIZE; i++) { |
| 83 | + err = amd_smn_read(0, AMD_STB_PMI_0, buf++); |
| 84 | + if (err) { |
| 85 | + dev_err(dev->dev, "error reading data from stb: 0x%X\n", AMD_STB_PMI_0); |
| 86 | + return pcibios_err_to_errno(err); |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + return 0; |
| 91 | +} |
| 92 | + |
| 93 | +static int amd_stb_debugfs_open(struct inode *inode, struct file *filp) |
| 94 | +{ |
| 95 | + struct amd_pmc_dev *dev = filp->f_inode->i_private; |
| 96 | + u32 size = FIFO_SIZE * sizeof(u32); |
| 97 | + u32 *buf; |
| 98 | + int rc; |
| 99 | + |
| 100 | + buf = kzalloc(size, GFP_KERNEL); |
| 101 | + if (!buf) |
| 102 | + return -ENOMEM; |
| 103 | + |
| 104 | + rc = amd_stb_read(dev, buf); |
| 105 | + if (rc) { |
| 106 | + kfree(buf); |
| 107 | + return rc; |
| 108 | + } |
| 109 | + |
| 110 | + filp->private_data = buf; |
| 111 | + return rc; |
| 112 | +} |
| 113 | + |
| 114 | +static ssize_t amd_stb_debugfs_read(struct file *filp, char __user *buf, size_t size, loff_t *pos) |
| 115 | +{ |
| 116 | + if (!filp->private_data) |
| 117 | + return -EINVAL; |
| 118 | + |
| 119 | + return simple_read_from_buffer(buf, size, pos, filp->private_data, |
| 120 | + FIFO_SIZE * sizeof(u32)); |
| 121 | +} |
| 122 | + |
| 123 | +static int amd_stb_debugfs_release(struct inode *inode, struct file *filp) |
| 124 | +{ |
| 125 | + kfree(filp->private_data); |
| 126 | + return 0; |
| 127 | +} |
| 128 | + |
| 129 | +static const struct file_operations amd_stb_debugfs_fops = { |
| 130 | + .owner = THIS_MODULE, |
| 131 | + .open = amd_stb_debugfs_open, |
| 132 | + .read = amd_stb_debugfs_read, |
| 133 | + .release = amd_stb_debugfs_release, |
| 134 | +}; |
| 135 | + |
| 136 | +/* Enhanced STB Firmware Reporting Mechanism */ |
| 137 | +static int amd_stb_handle_efr(struct file *filp) |
| 138 | +{ |
| 139 | + struct amd_pmc_dev *dev = filp->f_inode->i_private; |
| 140 | + struct amd_stb_v2_data *stb_data_arr; |
| 141 | + u32 fsize; |
| 142 | + |
| 143 | + fsize = dev->dram_size - S2D_RSVD_RAM_SPACE; |
| 144 | + stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL); |
| 145 | + if (!stb_data_arr) |
| 146 | + return -ENOMEM; |
| 147 | + |
| 148 | + stb_data_arr->size = fsize; |
| 149 | + memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize); |
| 150 | + filp->private_data = stb_data_arr; |
| 151 | + |
| 152 | + return 0; |
| 153 | +} |
| 154 | + |
| 155 | +static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp) |
| 156 | +{ |
| 157 | + struct amd_pmc_dev *dev = filp->f_inode->i_private; |
| 158 | + u32 fsize, num_samples, val, stb_rdptr_offset = 0; |
| 159 | + struct amd_stb_v2_data *stb_data_arr; |
| 160 | + int ret; |
| 161 | + |
| 162 | + /* Write dummy postcode while reading the STB buffer */ |
| 163 | + ret = amd_stb_write(dev, AMD_PMC_STB_DUMMY_PC); |
| 164 | + if (ret) |
| 165 | + dev_err(dev->dev, "error writing to STB: %d\n", ret); |
| 166 | + |
| 167 | + /* Spill to DRAM num_samples uses separate SMU message port */ |
| 168 | + dev->msg_port = MSG_PORT_S2D; |
| 169 | + |
| 170 | + ret = amd_pmc_send_cmd(dev, 0, &val, STB_FORCE_FLUSH_DATA, 1); |
| 171 | + if (ret) |
| 172 | + dev_dbg_once(dev->dev, "S2D force flush not supported: %d\n", ret); |
| 173 | + |
| 174 | + /* |
| 175 | + * We have a custom stb size and the PMFW is supposed to give |
| 176 | + * the enhanced dram size. Note that we land here only for the |
| 177 | + * platforms that support enhanced dram size reporting. |
| 178 | + */ |
| 179 | + if (dump_custom_stb) |
| 180 | + return amd_stb_handle_efr(filp); |
| 181 | + |
| 182 | + /* Get the num_samples to calculate the last push location */ |
| 183 | + ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->stb_arg.s2d_msg_id, true); |
| 184 | + /* Clear msg_port for other SMU operation */ |
| 185 | + dev->msg_port = MSG_PORT_PMC; |
| 186 | + if (ret) { |
| 187 | + dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret); |
| 188 | + return ret; |
| 189 | + } |
| 190 | + |
| 191 | + fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX); |
| 192 | + stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL); |
| 193 | + if (!stb_data_arr) |
| 194 | + return -ENOMEM; |
| 195 | + |
| 196 | + stb_data_arr->size = fsize; |
| 197 | + |
| 198 | + /* |
| 199 | + * Start capturing data from the last push location. |
| 200 | + * This is for general cases, where the stb limits |
| 201 | + * are meant for standard usage. |
| 202 | + */ |
| 203 | + if (num_samples > S2D_TELEMETRY_BYTES_MAX) { |
| 204 | + /* First read oldest data starting 1 behind last write till end of ringbuffer */ |
| 205 | + stb_rdptr_offset = num_samples % S2D_TELEMETRY_BYTES_MAX; |
| 206 | + fsize = S2D_TELEMETRY_BYTES_MAX - stb_rdptr_offset; |
| 207 | + |
| 208 | + memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr + stb_rdptr_offset, fsize); |
| 209 | + /* Second copy the newer samples from offset 0 - last write */ |
| 210 | + memcpy_fromio(stb_data_arr->data + fsize, dev->stb_virt_addr, stb_rdptr_offset); |
| 211 | + } else { |
| 212 | + memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize); |
| 213 | + } |
| 214 | + |
| 215 | + filp->private_data = stb_data_arr; |
| 216 | + |
| 217 | + return 0; |
| 218 | +} |
| 219 | + |
| 220 | +static ssize_t amd_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size, |
| 221 | + loff_t *pos) |
| 222 | +{ |
| 223 | + struct amd_stb_v2_data *data = filp->private_data; |
| 224 | + |
| 225 | + return simple_read_from_buffer(buf, size, pos, data->data, data->size); |
| 226 | +} |
| 227 | + |
| 228 | +static int amd_stb_debugfs_release_v2(struct inode *inode, struct file *filp) |
| 229 | +{ |
| 230 | + kfree(filp->private_data); |
| 231 | + return 0; |
| 232 | +} |
| 233 | + |
| 234 | +static const struct file_operations amd_stb_debugfs_fops_v2 = { |
| 235 | + .owner = THIS_MODULE, |
| 236 | + .open = amd_stb_debugfs_open_v2, |
| 237 | + .read = amd_stb_debugfs_read_v2, |
| 238 | + .release = amd_stb_debugfs_release_v2, |
| 239 | +}; |
| 240 | + |
| 241 | +static void amd_stb_update_args(struct amd_pmc_dev *dev) |
| 242 | +{ |
| 243 | + if (cpu_feature_enabled(X86_FEATURE_ZEN5)) |
| 244 | + switch (boot_cpu_data.x86_model) { |
| 245 | + case 0x44: |
| 246 | + dev->stb_arg.msg = AMD_GNR_REGISTER_MESSAGE; |
| 247 | + dev->stb_arg.arg = AMD_GNR_REGISTER_ARGUMENT; |
| 248 | + dev->stb_arg.resp = AMD_GNR_REGISTER_RESPONSE; |
| 249 | + return; |
| 250 | + default: |
| 251 | + break; |
| 252 | + } |
| 253 | + |
| 254 | + dev->stb_arg.msg = AMD_S2D_REGISTER_MESSAGE; |
| 255 | + dev->stb_arg.arg = AMD_S2D_REGISTER_ARGUMENT; |
| 256 | + dev->stb_arg.resp = AMD_S2D_REGISTER_RESPONSE; |
| 257 | +} |
| 258 | + |
| 259 | +static bool amd_is_stb_supported(struct amd_pmc_dev *dev) |
| 260 | +{ |
| 261 | + switch (dev->cpu_id) { |
| 262 | + case AMD_CPU_ID_YC: |
| 263 | + case AMD_CPU_ID_CB: |
| 264 | + if (boot_cpu_data.x86_model == 0x44) |
| 265 | + dev->stb_arg.s2d_msg_id = 0x9B; |
| 266 | + else |
| 267 | + dev->stb_arg.s2d_msg_id = 0xBE; |
| 268 | + break; |
| 269 | + case AMD_CPU_ID_PS: |
| 270 | + dev->stb_arg.s2d_msg_id = 0x85; |
| 271 | + break; |
| 272 | + case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT: |
| 273 | + case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT: |
| 274 | + if (boot_cpu_data.x86_model == 0x70) |
| 275 | + dev->stb_arg.s2d_msg_id = 0xF1; |
| 276 | + else |
| 277 | + dev->stb_arg.s2d_msg_id = 0xDE; |
| 278 | + break; |
| 279 | + default: |
| 280 | + return false; |
| 281 | + } |
| 282 | + |
| 283 | + amd_stb_update_args(dev); |
| 284 | + return true; |
| 285 | +} |
| 286 | + |
| 287 | +int amd_stb_s2d_init(struct amd_pmc_dev *dev) |
| 288 | +{ |
| 289 | + u32 phys_addr_low, phys_addr_hi; |
| 290 | + u64 stb_phys_addr; |
| 291 | + u32 size = 0; |
| 292 | + int ret; |
| 293 | + |
| 294 | + if (!enable_stb) |
| 295 | + return 0; |
| 296 | + |
| 297 | + if (amd_is_stb_supported(dev)) { |
| 298 | + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, |
| 299 | + &amd_stb_debugfs_fops_v2); |
| 300 | + } else { |
| 301 | + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, |
| 302 | + &amd_stb_debugfs_fops); |
| 303 | + return 0; |
| 304 | + } |
| 305 | + |
| 306 | + /* Spill to DRAM feature uses separate SMU message port */ |
| 307 | + dev->msg_port = MSG_PORT_S2D; |
| 308 | + |
| 309 | + amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true); |
| 310 | + if (size != S2D_TELEMETRY_BYTES_MAX) |
| 311 | + return -EIO; |
| 312 | + |
| 313 | + /* Get DRAM size */ |
| 314 | + ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true); |
| 315 | + if (ret || !dev->dram_size) |
| 316 | + dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX; |
| 317 | + |
| 318 | + /* Get STB DRAM address */ |
| 319 | + amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->stb_arg.s2d_msg_id, true); |
| 320 | + amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->stb_arg.s2d_msg_id, true); |
| 321 | + |
| 322 | + stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); |
| 323 | + |
| 324 | + /* Clear msg_port for other SMU operation */ |
| 325 | + dev->msg_port = MSG_PORT_PMC; |
| 326 | + |
| 327 | + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size); |
| 328 | + if (!dev->stb_virt_addr) |
| 329 | + return -ENOMEM; |
| 330 | + |
| 331 | + return 0; |
| 332 | +} |
0 commit comments