root/drivers/iommu/amd/debugfs.c
// SPDX-License-Identifier: GPL-2.0
/*
 * AMD IOMMU driver
 *
 * Copyright (C) 2018 Advanced Micro Devices, Inc.
 *
 * Author: Gary R Hook <gary.hook@amd.com>
 */

#include <linux/debugfs.h>
#include <linux/pci.h>

#include "amd_iommu.h"
#include "../irq_remapping.h"

static struct dentry *amd_iommu_debugfs;

#define MAX_NAME_LEN    20
#define OFS_IN_SZ       8
#define DEVID_IN_SZ     16

static int sbdf = -1;

static ssize_t iommu_mmio_write(struct file *filp, const char __user *ubuf,
                                size_t cnt, loff_t *ppos)
{
        struct seq_file *m = filp->private_data;
        struct amd_iommu *iommu = m->private;
        int ret, dbg_mmio_offset = iommu->dbg_mmio_offset = -1;

        if (cnt > OFS_IN_SZ)
                return -EINVAL;

        ret = kstrtos32_from_user(ubuf, cnt, 0, &dbg_mmio_offset);
        if (ret)
                return ret;

        if (dbg_mmio_offset < 0 || dbg_mmio_offset >
                        iommu->mmio_phys_end - sizeof(u64))
                return -EINVAL;

        iommu->dbg_mmio_offset = dbg_mmio_offset;
        return cnt;
}

static int iommu_mmio_show(struct seq_file *m, void *unused)
{
        struct amd_iommu *iommu = m->private;
        u64 value;
        int dbg_mmio_offset = iommu->dbg_mmio_offset;

        if (dbg_mmio_offset < 0 || dbg_mmio_offset >
                        iommu->mmio_phys_end - sizeof(u64)) {
                seq_puts(m, "Please provide mmio register's offset\n");
                return 0;
        }

        value = readq(iommu->mmio_base + dbg_mmio_offset);
        seq_printf(m, "Offset:0x%x Value:0x%016llx\n", dbg_mmio_offset, value);

        return 0;
}
DEFINE_SHOW_STORE_ATTRIBUTE(iommu_mmio);

static ssize_t iommu_capability_write(struct file *filp, const char __user *ubuf,
                                      size_t cnt, loff_t *ppos)
{
        struct seq_file *m = filp->private_data;
        struct amd_iommu *iommu = m->private;
        int ret, dbg_cap_offset = iommu->dbg_cap_offset = -1;

        if (cnt > OFS_IN_SZ)
                return -EINVAL;

        ret = kstrtos32_from_user(ubuf, cnt, 0, &dbg_cap_offset);
        if (ret)
                return ret;

        /* Capability register at offset 0x14 is the last IOMMU capability register. */
        if (dbg_cap_offset < 0 || dbg_cap_offset > 0x14)
                return -EINVAL;

        iommu->dbg_cap_offset = dbg_cap_offset;
        return cnt;
}

static int iommu_capability_show(struct seq_file *m, void *unused)
{
        struct amd_iommu *iommu = m->private;
        u32 value;
        int err, dbg_cap_offset = iommu->dbg_cap_offset;

        if (dbg_cap_offset < 0 || dbg_cap_offset > 0x14) {
                seq_puts(m, "Please provide capability register's offset in the range [0x00 - 0x14]\n");
                return 0;
        }

        err = pci_read_config_dword(iommu->dev, iommu->cap_ptr + dbg_cap_offset, &value);
        if (err) {
                seq_printf(m, "Not able to read capability register at 0x%x\n",
                           dbg_cap_offset);
                return 0;
        }

        seq_printf(m, "Offset:0x%x Value:0x%08x\n", dbg_cap_offset, value);

        return 0;
}
DEFINE_SHOW_STORE_ATTRIBUTE(iommu_capability);

static int iommu_cmdbuf_show(struct seq_file *m, void *unused)
{
        struct amd_iommu *iommu = m->private;
        struct iommu_cmd *cmd;
        unsigned long flag;
        u32 head, tail;
        int i;

        raw_spin_lock_irqsave(&iommu->lock, flag);
        head = readl(iommu->mmio_base + MMIO_CMD_HEAD_OFFSET);
        tail = readl(iommu->mmio_base + MMIO_CMD_TAIL_OFFSET);
        seq_printf(m, "CMD Buffer Head Offset:%d Tail Offset:%d\n",
                   (head >> 4) & 0x7fff, (tail >> 4) & 0x7fff);
        for (i = 0; i < CMD_BUFFER_ENTRIES; i++) {
                cmd = (struct iommu_cmd *)(iommu->cmd_buf + i * sizeof(*cmd));
                seq_printf(m, "%3d: %08x %08x %08x %08x\n", i, cmd->data[0],
                           cmd->data[1], cmd->data[2], cmd->data[3]);
        }
        raw_spin_unlock_irqrestore(&iommu->lock, flag);

        return 0;
}
DEFINE_SHOW_ATTRIBUTE(iommu_cmdbuf);

static ssize_t devid_write(struct file *filp, const char __user *ubuf,
                           size_t cnt, loff_t *ppos)
{
        struct amd_iommu_pci_seg *pci_seg;
        int seg, bus, slot, func;
        struct amd_iommu *iommu;
        char *srcid_ptr;
        u16 devid;
        int i;

        sbdf = -1;

        if (cnt >= DEVID_IN_SZ)
                return -EINVAL;

        srcid_ptr = memdup_user_nul(ubuf, cnt);
        if (IS_ERR(srcid_ptr))
                return PTR_ERR(srcid_ptr);

        i = sscanf(srcid_ptr, "%x:%x:%x.%x", &seg, &bus, &slot, &func);
        if (i != 4) {
                i = sscanf(srcid_ptr, "%x:%x.%x", &bus, &slot, &func);
                if (i != 3) {
                        kfree(srcid_ptr);
                        return -EINVAL;
                }
                seg = 0;
        }

        devid = PCI_DEVID(bus, PCI_DEVFN(slot, func));

        /* Check if user device id input is a valid input */
        for_each_pci_segment(pci_seg) {
                if (pci_seg->id != seg)
                        continue;
                if (devid > pci_seg->last_bdf) {
                        kfree(srcid_ptr);
                        return -EINVAL;
                }
                iommu = pci_seg->rlookup_table[devid];
                if (!iommu) {
                        kfree(srcid_ptr);
                        return -ENODEV;
                }
                break;
        }

        if (pci_seg->id != seg) {
                kfree(srcid_ptr);
                return -EINVAL;
        }

        sbdf = PCI_SEG_DEVID_TO_SBDF(seg, devid);

        kfree(srcid_ptr);

        return cnt;
}

static int devid_show(struct seq_file *m, void *unused)
{
        u16 devid;
        int sbdf_shadow = sbdf;

        if (sbdf_shadow >= 0) {
                devid = PCI_SBDF_TO_DEVID(sbdf_shadow);
                seq_printf(m, "%04x:%02x:%02x.%x\n", PCI_SBDF_TO_SEGID(sbdf_shadow),
                           PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid));
        } else
                seq_puts(m, "No or Invalid input provided\n");

        return 0;
}
DEFINE_SHOW_STORE_ATTRIBUTE(devid);

static void dump_dte(struct seq_file *m, struct amd_iommu_pci_seg *pci_seg, u16 devid)
{
        struct dev_table_entry *dev_table;
        struct amd_iommu *iommu;

        iommu = pci_seg->rlookup_table[devid];
        if (!iommu)
                return;

        dev_table = get_dev_table(iommu);
        if (!dev_table) {
                seq_puts(m, "Device table not found");
                return;
        }

        seq_printf(m, "%-12s %16s %16s %16s %16s iommu\n", "DeviceId",
                   "QWORD[3]", "QWORD[2]", "QWORD[1]", "QWORD[0]");
        seq_printf(m, "%04x:%02x:%02x.%x ", pci_seg->id, PCI_BUS_NUM(devid),
                   PCI_SLOT(devid), PCI_FUNC(devid));
        for (int i = 3; i >= 0; --i)
                seq_printf(m, "%016llx ", dev_table[devid].data[i]);
        seq_printf(m, "iommu%d\n", iommu->index);
}

static int iommu_devtbl_show(struct seq_file *m, void *unused)
{
        struct amd_iommu_pci_seg *pci_seg;
        u16 seg, devid;
        int sbdf_shadow = sbdf;

        if (sbdf_shadow < 0) {
                seq_puts(m, "Enter a valid device ID to 'devid' file\n");
                return 0;
        }
        seg = PCI_SBDF_TO_SEGID(sbdf_shadow);
        devid = PCI_SBDF_TO_DEVID(sbdf_shadow);

        for_each_pci_segment(pci_seg) {
                if (pci_seg->id != seg)
                        continue;
                dump_dte(m, pci_seg, devid);
                break;
        }

        return 0;
}
DEFINE_SHOW_ATTRIBUTE(iommu_devtbl);

static void dump_128_irte(struct seq_file *m, struct irq_remap_table *table, u16 int_tab_len)
{
        struct irte_ga *ptr, *irte;
        int index;

        for (index = 0; index < int_tab_len; index++) {
                ptr = (struct irte_ga *)table->table;
                irte = &ptr[index];

                if (AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir) &&
                    !irte->lo.fields_vapic.valid)
                        continue;
                else if (!irte->lo.fields_remap.valid)
                        continue;
                seq_printf(m, "IRT[%04d] %016llx %016llx\n", index, irte->hi.val, irte->lo.val);
        }
}

static void dump_32_irte(struct seq_file *m, struct irq_remap_table *table, u16 int_tab_len)
{
        union irte *ptr, *irte;
        int index;

        for (index = 0; index < int_tab_len; index++) {
                ptr = (union irte *)table->table;
                irte = &ptr[index];

                if (!irte->fields.valid)
                        continue;
                seq_printf(m, "IRT[%04d] %08x\n", index, irte->val);
        }
}

static void dump_irte(struct seq_file *m, u16 devid, struct amd_iommu_pci_seg *pci_seg)
{
        struct dev_table_entry *dev_table;
        struct irq_remap_table *table;
        struct amd_iommu *iommu;
        unsigned long flags;
        u16 int_tab_len;

        table = pci_seg->irq_lookup_table[devid];
        if (!table) {
                seq_printf(m, "IRQ lookup table not set for %04x:%02x:%02x:%x\n",
                           pci_seg->id, PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid));
                return;
        }

        iommu = pci_seg->rlookup_table[devid];
        if (!iommu)
                return;

        dev_table = get_dev_table(iommu);
        if (!dev_table) {
                seq_puts(m, "Device table not found");
                return;
        }

        int_tab_len = dev_table[devid].data[2] & DTE_INTTABLEN_MASK;
        if (int_tab_len != DTE_INTTABLEN_512 && int_tab_len != DTE_INTTABLEN_2K) {
                seq_puts(m, "The device's DTE contains an invalid IRT length value.");
                return;
        }

        seq_printf(m, "DeviceId %04x:%02x:%02x.%x\n", pci_seg->id, PCI_BUS_NUM(devid),
                   PCI_SLOT(devid), PCI_FUNC(devid));

        raw_spin_lock_irqsave(&table->lock, flags);
        if (AMD_IOMMU_GUEST_IR_GA(amd_iommu_guest_ir))
                dump_128_irte(m, table, BIT(int_tab_len >> 1));
        else
                dump_32_irte(m, table, BIT(int_tab_len >> 1));
        seq_puts(m, "\n");
        raw_spin_unlock_irqrestore(&table->lock, flags);
}

static int iommu_irqtbl_show(struct seq_file *m, void *unused)
{
        struct amd_iommu_pci_seg *pci_seg;
        u16 devid, seg;
        int sbdf_shadow = sbdf;

        if (!irq_remapping_enabled) {
                seq_puts(m, "Interrupt remapping is disabled\n");
                return 0;
        }

        if (sbdf_shadow < 0) {
                seq_puts(m, "Enter a valid device ID to 'devid' file\n");
                return 0;
        }

        seg = PCI_SBDF_TO_SEGID(sbdf_shadow);
        devid = PCI_SBDF_TO_DEVID(sbdf_shadow);

        for_each_pci_segment(pci_seg) {
                if (pci_seg->id != seg)
                        continue;
                dump_irte(m, devid, pci_seg);
                break;
        }

        return 0;
}
DEFINE_SHOW_ATTRIBUTE(iommu_irqtbl);

void amd_iommu_debugfs_setup(void)
{
        struct amd_iommu *iommu;
        char name[MAX_NAME_LEN + 1];

        amd_iommu_debugfs = debugfs_create_dir("amd", iommu_debugfs_dir);

        for_each_iommu(iommu) {
                iommu->dbg_mmio_offset = -1;
                iommu->dbg_cap_offset = -1;

                snprintf(name, MAX_NAME_LEN, "iommu%02d", iommu->index);
                iommu->debugfs = debugfs_create_dir(name, amd_iommu_debugfs);

                debugfs_create_file("mmio", 0644, iommu->debugfs, iommu,
                                    &iommu_mmio_fops);
                debugfs_create_file("capability", 0644, iommu->debugfs, iommu,
                                    &iommu_capability_fops);
                debugfs_create_file("cmdbuf", 0444, iommu->debugfs, iommu,
                                    &iommu_cmdbuf_fops);
        }

        debugfs_create_file("devid", 0644, amd_iommu_debugfs, NULL,
                            &devid_fops);
        debugfs_create_file("devtbl", 0444, amd_iommu_debugfs, NULL,
                            &iommu_devtbl_fops);
        debugfs_create_file("irqtbl", 0444, amd_iommu_debugfs, NULL,
                            &iommu_irqtbl_fops);
}