| 1 | |
| 2 | |
| 3 | * AMD IOMMU driver
|
| 4 | *
|
| 5 | * Copyright (C) 2018 Advanced Micro Devices, Inc.
|
| 6 | *
|
| 7 | * Author: Gary R Hook <[email protected]>
|
| 8 |
|
| 9 |
|
| 10 | #include <linux/debugfs.h>
|
| 11 | #include <linux/pci.h>
|
| 12 |
|
| 13 | #include "amd_iommu.h"
|
| 14 | #include "../irq_remapping.h"
|
| 15 |
|
| 16 | static struct dentry *amd_iommu_debugfs;
|
| 17 |
|
| 18 | #define MAX_NAME_LEN 20
|
| 19 | #define OFS_IN_SZ 8
|
| 20 | #define DEVID_IN_SZ 16
|
| 21 |
|
| 22 | static int sbdf = -1;
|
| 23 |
|
| 24 | static ssize_t iommu_mmio_write(struct file *filp, const char __user *ubuf,
|
| 25 | size_t cnt, loff_t *ppos)
|
| 26 | {
|
| 27 | struct seq_file *m = filp->private_data;
|
| 28 | struct amd_iommu *iommu = m->private;
|
| 29 | int ret, dbg_mmio_offset = iommu->dbg_mmio_offset = -1;
|
| 30 |
|
| 31 | if (cnt > OFS_IN_SZ)
|
| 32 | return -EINVAL;
|
| 33 |
|
| 34 | ret = kstrtos32_from_user(ubuf, cnt, 0, &dbg_mmio_offset);
|
| 35 | if (ret)
|
| 36 | return ret;
|
| 37 |
|
| 38 | if (dbg_mmio_offset < 0 || dbg_mmio_offset >
|
| 39 | iommu->mmio_phys_end - sizeof(u64))
|
| 40 | return -EINVAL;
|
| 41 |
|
| 42 | iommu->dbg_mmio_offset = dbg_mmio_offset;
|
| 43 | return cnt;
|
| 44 | }
|
| 45 |
|
| 46 | static int iommu_mmio_show(struct seq_file *m, void *unused)
|
| 47 | {
|
| 48 | struct amd_iommu *iommu = m->private;
|
| 49 | u64 value;
|
| 50 | int dbg_mmio_offset = iommu->dbg_mmio_offset;
|
| 51 |
|
| 52 | if (dbg_mmio_offset < 0 || dbg_mmio_offset >
|
| 53 | iommu->mmio_phys_end - sizeof(u64)) {
|
| 54 | seq_puts(m, "Please provide mmio register's offset\n");
|
| 55 | return 0;
|
| 56 | }
|
| 57 |
|
| 58 | value = readq(iommu->mmio_base + dbg_mmio_offset);
|
| 59 | seq_printf(m, "Offset:0x%x Value:0x%016llx\n", dbg_mmio_offset, value);
|
| 60 |
|
| 61 | return 0;
|
| 62 | }
|
| 63 | DEFINE_SHOW_STORE_ATTRIBUTE(iommu_mmio);
|
| 64 |
|
| 65 | static ssize_t iommu_capability_write(struct file *filp, const char __user *ubuf,
|
| 66 | size_t cnt, loff_t *ppos)
|
| 67 | {
|
| 68 | struct seq_file *m = filp->private_data;
|
| 69 | struct amd_iommu *iommu = m->private;
|
| 70 | int ret, dbg_cap_offset = iommu->dbg_cap_offset = -1;
|
| 71 |
|
| 72 | if (cnt > OFS_IN_SZ)
|
| 73 | return -EINVAL;
|
| 74 |
|
| 75 | ret = kstrtos32_from_user(ubuf, cnt, 0, &dbg_cap_offset);
|
| 76 | if (ret)
|
| 77 | return ret;
|
| 78 |
|
| 79 |
|
| 80 | if (dbg_cap_offset < 0 || dbg_cap_offset > 0x14)
|
| 81 | return -EINVAL;
|
| 82 |
|
| 83 | iommu->dbg_cap_offset = dbg_cap_offset;
|
| 84 | return cnt;
|
| 85 | }
|
| 86 |
|
| 87 | static int iommu_capability_show(struct seq_file *m, void *unused)
|
| 88 | {
|
| 89 | struct amd_iommu *iommu = m->private;
|
| 90 | u32 value;
|
| 91 | int err, dbg_cap_offset = iommu->dbg_cap_offset;
|
| 92 |
|
| 93 | if (dbg_cap_offset < 0 || dbg_cap_offset > 0x14) {
|
| 94 | seq_puts(m, "Please provide capability register's offset in the range [0x00 - 0x14]\n");
|
| 95 | return 0;
|
| 96 | }
|
| 97 |
|
| 98 | err = pci_read_config_dword(iommu->dev, iommu->cap_ptr + dbg_cap_offset, &value);
|
| 99 | if (err) {
|
| 100 | seq_printf(m, "Not able to read capability register at 0x%x\n",
|
| 101 | dbg_cap_offset);
|
| 102 | return 0;
|
| 103 | }
|
| 104 |
|
| 105 | seq_printf(m, "Offset:0x%x Value:0x%08x\n", dbg_cap_offset, value);
|
| 106 |
|
| 107 | return 0;
|
| 108 | }
|
| 109 | DEFINE_SHOW_STORE_ATTRIBUTE(iommu_capability);
|
| 110 |
|
| 111 | static int iommu_cmdbuf_show(struct seq_file *m, void *unused)
|
| 112 | {
|
| 113 | struct amd_iommu *iommu = m->private;
|
| 114 | struct iommu_cmd *cmd;
|
| 115 | unsigned long flag;
|
| 116 | u32 head, tail;
|
| 117 | int i;
|
| 118 |
|
| 119 | raw_spin_lock_irqsave(&iommu->lock, flag);
|
| 120 | head = readl(iommu->mmio_base + MMIO_CMD_HEAD_OFFSET);
|
| 121 | tail = readl(iommu->mmio_base + MMIO_CMD_TAIL_OFFSET);
|
| 122 | seq_printf(m, "CMD Buffer Head Offset:%d Tail Offset:%d\n",
|
| 123 | (head >> 4) & 0x7fff, (tail >> 4) & 0x7fff);
|
| 124 | for (i = 0; i < CMD_BUFFER_ENTRIES; i++) {
|
| 125 | cmd = (struct iommu_cmd *)(iommu->cmd_buf + i * sizeof(*cmd));
|
| 126 | seq_printf(m, "%3d: %08x %08x %08x %08x\n", i, cmd->data[0],
|
| 127 | cmd->data[1], cmd->data[2], cmd->data[3]);
|
| 128 | }
|
| 129 | raw_spin_unlock_irqrestore(&iommu->lock, flag);
|
| 130 |
|
| 131 | return 0;
|
| 132 | }
|
| 133 | DEFINE_SHOW_ATTRIBUTE(iommu_cmdbuf);
|
| 134 |
|
| 135 | static ssize_t devid_write(struct file *filp, const char __user *ubuf,
|
| 136 | size_t cnt, loff_t *ppos)
|
| 137 | {
|
| 138 | struct amd_iommu_pci_seg *pci_seg;
|
| 139 | int seg, bus, slot, func;
|
| 140 | struct amd_iommu *iommu;
|
| 141 | char *srcid_ptr;
|
| 142 | u16 devid;
|
| 143 | int i;
|
| 144 |
|
| 145 | sbdf = -1;
|
| 146 |
|
| 147 | if (cnt >= DEVID_IN_SZ)
|
| 148 | return -EINVAL;
|
| 149 |
|
| 150 | srcid_ptr = memdup_user_nul(ubuf, cnt);
|
| 151 | if (IS_ERR(srcid_ptr))
|
| 152 | return PTR_ERR(srcid_ptr);
|
| 153 |
|
| 154 | i = sscanf(srcid_ptr, "%x:%x:%x.%x", &seg, &bus, &slot, &func);
|
| 155 | if (i != 4) {
|
| 156 | i = sscanf(srcid_ptr, "%x:%x.%x", &bus, &slot, &func);
|
| 157 | if (i != 3) {
|
| 158 | kfree(srcid_ptr);
|
| 159 | return -EINVAL;
|
| 160 | }
|
| 161 | seg = 0;
|
| 162 | }
|
| 163 |
|
| 164 | devid = PCI_DEVID(bus, PCI_DEVFN(slot, func));
|
| 165 |
|
| 166 |
|
| 167 | for_each_pci_segment(pci_seg) {
|
| 168 | if (pci_seg->id != seg)
|
| 169 | continue;
|
| 170 | if (devid > pci_seg->last_bdf) {
|
| 171 | kfree(srcid_ptr);
|
| 172 | return -EINVAL;
|
| 173 | }
|
| 174 | iommu = pci_seg->rlookup_table[devid];
|
| 175 | if (!iommu) {
|
| 176 | kfree(srcid_ptr);
|
| 177 | return -ENODEV;
|
| 178 | }
|
| 179 | sbdf = PCI_SEG_DEVID_TO_SBDF(seg, devid);
|
| 180 | kfree(srcid_ptr);
|
| 181 | return cnt;
|
| 182 | }
|
| 183 |
|
| 184 | kfree(srcid_ptr);
|
| 185 | return -EINVAL;
|
| 186 | }
|
| 187 |
|
| 188 | static int devid_show(struct seq_file *m, void *unused)
|
| 189 | {
|
| 190 | u16 devid;
|
| 191 | int sbdf_shadow = sbdf;
|
| 192 |
|
| 193 | if (sbdf_shadow >= 0) {
|
| 194 | devid = PCI_SBDF_TO_DEVID(sbdf_shadow);
|
| 195 | seq_printf(m, "%04x:%02x:%02x.%x\n", PCI_SBDF_TO_SEGID(sbdf_shadow),
|
| 196 | PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid));
|
| 197 | } else
|
| 198 | seq_puts(m, "No or Invalid input provided\n");
|
| 199 |
|
| 200 | return 0;
|
| 201 | }
|
| 202 | DEFINE_SHOW_STORE_ATTRIBUTE(devid);
|
| 203 |
|
| 204 | static void dump_dte(struct seq_file *m, struct amd_iommu_pci_seg *pci_seg, u16 devid)
|
| 205 | {
|
| 206 | struct dev_table_entry *dev_table;
|
| 207 | struct amd_iommu *iommu;
|
| 208 |
|
| 209 | iommu = pci_seg->rlookup_table[devid];
|
| 210 | if (!iommu)
|
| 211 | return;
|
| 212 |
|
| 213 | dev_table = get_dev_table(iommu);
|
| 214 | if (!dev_table) {
|
| 215 | seq_puts(m, "Device table not found");
|
| 216 | return;
|
| 217 | }
|
| 218 |
|
| 219 | seq_printf(m, "%-12s %16s %16s %16s %16s iommu\n", "DeviceId",
|
| 220 | "QWORD[3]", "QWORD[2]", "QWORD[1]", "QWORD[0]");
|
| 221 | seq_printf(m, "%04x:%02x:%02x.%x ", pci_seg->id, PCI_BUS_NUM(devid),
|
| 222 | PCI_SLOT(devid), PCI_FUNC(devid));
|
| 223 | for (int i = 3; i >= 0; --i)
|
| 224 | seq_printf(m, "%016llx ", dev_table[devid].data[i]);
|
| 225 | seq_printf(m, "iommu%d\n", iommu->index);
|
| 226 | }
|
| 227 |
|
| 228 | static int iommu_devtbl_show(struct seq_file *m, void *unused)
|
| 229 | {
|
| 230 | struct amd_iommu_pci_seg *pci_seg;
|
| 231 | u16 seg, devid;
|
| 232 | int sbdf_shadow = sbdf;
|
| 233 |
|
| 234 | if (sbdf_shadow < 0) {
|
| 235 | seq_puts(m, "Enter a valid device ID to 'devid' file\n");
|
| 236 | return 0;
|
| 237 | }
|
| 238 | seg = PCI_SBDF_TO_SEGID(sbdf_shadow);
|
| 239 | devid = PCI_SBDF_TO_DEVID(sbdf_shadow);
|
| 240 |
|
| 241 | for_each_pci_segment(pci_seg) {
|
| 242 | if (pci_seg->id != seg)
|
| 243 | continue;
|
| 244 | dump_dte(m, pci_seg, devid);
|
| 245 | break;
|
| 246 | }
|
| 247 |
|
| 248 | return 0;
|
| 249 | }
|
| 250 | DEFINE_SHOW_ATTRIBUTE(iommu_devtbl);
|
| 251 |
|
| 252 | static void dump_128_irte(struct seq_file *m, struct irq_remap_table *table, u16 int_tab_len)
|
| 253 | {
|
| 254 | struct irte_ga *ptr, *irte;
|
| 255 | int index;
|
| 256 |
|
| 257 | for (index = 0; index < int_tab_len; index++) {
|
| 258 | ptr = (struct irte_ga *)table->table;
|
| 259 | irte = &ptr[index];
|
| 260 |
|
| 261 | if (AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir) &&
|
| 262 | !irte->lo.fields_vapic.valid)
|
| 263 | continue;
|
| 264 | else if (!irte->lo.fields_remap.valid)
|
| 265 | continue;
|
| 266 | seq_printf(m, "IRT[%04d] %016llx %016llx\n", index, irte->hi.val, irte->lo.val);
|
| 267 | }
|
| 268 | }
|
| 269 |
|
| 270 | static void dump_32_irte(struct seq_file *m, struct irq_remap_table *table, u16 int_tab_len)
|
| 271 | {
|
| 272 | union irte *ptr, *irte;
|
| 273 | int index;
|
| 274 |
|
| 275 | for (index = 0; index < int_tab_len; index++) {
|
| 276 | ptr = (union irte *)table->table;
|
| 277 | irte = &ptr[index];
|
| 278 |
|
| 279 | if (!irte->fields.valid)
|
| 280 | continue;
|
| 281 | seq_printf(m, "IRT[%04d] %08x\n", index, irte->val);
|
| 282 | }
|
| 283 | }
|
| 284 |
|
| 285 | static void dump_irte(struct seq_file *m, u16 devid, struct amd_iommu_pci_seg *pci_seg)
|
| 286 | {
|
| 287 | struct dev_table_entry *dev_table;
|
| 288 | struct irq_remap_table *table;
|
| 289 | struct amd_iommu *iommu;
|
| 290 | unsigned long flags;
|
| 291 | u16 int_tab_len;
|
| 292 |
|
| 293 | table = pci_seg->irq_lookup_table[devid];
|
| 294 | if (!table) {
|
| 295 | seq_printf(m, "IRQ lookup table not set for %04x:%02x:%02x:%x\n",
|
| 296 | pci_seg->id, PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid));
|
| 297 | return;
|
| 298 | }
|
| 299 |
|
| 300 | iommu = pci_seg->rlookup_table[devid];
|
| 301 | if (!iommu)
|
| 302 | return;
|
| 303 |
|
| 304 | dev_table = get_dev_table(iommu);
|
| 305 | if (!dev_table) {
|
| 306 | seq_puts(m, "Device table not found");
|
| 307 | return;
|
| 308 | }
|
| 309 |
|
| 310 | int_tab_len = dev_table[devid].data[2] & DTE_INTTABLEN_MASK;
|
| 311 | if (int_tab_len != DTE_INTTABLEN_512 && int_tab_len != DTE_INTTABLEN_2K) {
|
| 312 | seq_puts(m, "The device's DTE contains an invalid IRT length value.");
|
| 313 | return;
|
| 314 | }
|
| 315 |
|
| 316 | seq_printf(m, "DeviceId %04x:%02x:%02x.%x\n", pci_seg->id, PCI_BUS_NUM(devid),
|
| 317 | PCI_SLOT(devid), PCI_FUNC(devid));
|
| 318 |
|
| 319 | raw_spin_lock_irqsave(&table->lock, flags);
|
| 320 | if (AMD_IOMMU_GUEST_IR_GA(amd_iommu_guest_ir))
|
| 321 | dump_128_irte(m, table, BIT(int_tab_len >> 1));
|
| 322 | else
|
| 323 | dump_32_irte(m, table, BIT(int_tab_len >> 1));
|
| 324 | seq_puts(m, "\n");
|
| 325 | raw_spin_unlock_irqrestore(&table->lock, flags);
|
| 326 | }
|
| 327 |
|
| 328 | static int iommu_irqtbl_show(struct seq_file *m, void *unused)
|
| 329 | {
|
| 330 | struct amd_iommu_pci_seg *pci_seg;
|
| 331 | u16 devid, seg;
|
| 332 | int sbdf_shadow = sbdf;
|
| 333 |
|
| 334 | if (!irq_remapping_enabled) {
|
| 335 | seq_puts(m, "Interrupt remapping is disabled\n");
|
| 336 | return 0;
|
| 337 | }
|
| 338 |
|
| 339 | if (sbdf_shadow < 0) {
|
| 340 | seq_puts(m, "Enter a valid device ID to 'devid' file\n");
|
| 341 | return 0;
|
| 342 | }
|
| 343 |
|
| 344 | seg = PCI_SBDF_TO_SEGID(sbdf_shadow);
|
| 345 | devid = PCI_SBDF_TO_DEVID(sbdf_shadow);
|
| 346 |
|
| 347 | for_each_pci_segment(pci_seg) {
|
| 348 | if (pci_seg->id != seg)
|
| 349 | continue;
|
| 350 | dump_irte(m, devid, pci_seg);
|
| 351 | break;
|
| 352 | }
|
| 353 |
|
| 354 | return 0;
|
| 355 | }
|
| 356 | DEFINE_SHOW_ATTRIBUTE(iommu_irqtbl);
|
| 357 |
|
| 358 | void amd_iommu_debugfs_setup(void)
|
| 359 | {
|
| 360 | struct amd_iommu *iommu;
|
| 361 | char name[MAX_NAME_LEN + 1];
|
| 362 |
|
| 363 | amd_iommu_debugfs = debugfs_create_dir("amd", iommu_debugfs_dir);
|
| 364 |
|
| 365 | for_each_iommu(iommu) {
|
| 366 | iommu->dbg_mmio_offset = -1;
|
| 367 | iommu->dbg_cap_offset = -1;
|
| 368 |
|
| 369 | snprintf(name, MAX_NAME_LEN, "iommu%02d", iommu->index);
|
| 370 | iommu->debugfs = debugfs_create_dir(name, amd_iommu_debugfs);
|
| 371 |
|
| 372 | debugfs_create_file("mmio", 0644, iommu->debugfs, iommu,
|
| 373 | &iommu_mmio_fops);
|
| 374 | debugfs_create_file("capability", 0644, iommu->debugfs, iommu,
|
| 375 | &iommu_capability_fops);
|
| 376 | debugfs_create_file("cmdbuf", 0444, iommu->debugfs, iommu,
|
| 377 | &iommu_cmdbuf_fops);
|
| 378 | }
|
| 379 |
|
| 380 | debugfs_create_file("devid", 0644, amd_iommu_debugfs, NULL,
|
| 381 | &devid_fops);
|
| 382 | debugfs_create_file("devtbl", 0444, amd_iommu_debugfs, NULL,
|
| 383 | &iommu_devtbl_fops);
|
| 384 | debugfs_create_file("irqtbl", 0444, amd_iommu_debugfs, NULL,
|
| 385 | &iommu_irqtbl_fops);
|
| 386 | }
|