419 lines
10 KiB
C
419 lines
10 KiB
C
/*
|
|
* Copyright (c) 2018 NVIDIA Corporation. All rights reserved.
|
|
*
|
|
* NVIDIA Corporation and its licensors retain all intellectual property
|
|
* and proprietary rights in and to this software and related documentation
|
|
* and any modifications thereto. Any use, reproduction, disclosure or
|
|
* distribution of this software and related documentation without an express
|
|
* license agreement from NVIDIA Corporation is strictly prohibited.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/io.h>
|
|
#include <linux/device.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/version.h>
|
|
|
|
#include "arm-smmu-regs.h"
|
|
#include "arm-smmu-debug.h"
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0)
|
|
|
|
/* TODO: Restructure code to remove handle global variable */
|
|
static struct smmu_debugfs_info *smmu_handle;
|
|
|
|
#define defreg(_name) \
|
|
{ \
|
|
.name = __stringify(_name), \
|
|
.offset = ARM_SMMU_ ## _name, \
|
|
}
|
|
#define defreg_gr0(_name) defreg(GR0_ ## _name)
|
|
|
|
static const struct debugfs_reg32 arm_smmu_gr0_regs[] = {
|
|
defreg_gr0(sCR0),
|
|
defreg_gr0(ID0),
|
|
defreg_gr0(ID1),
|
|
defreg_gr0(ID2),
|
|
defreg_gr0(sGFSR),
|
|
defreg_gr0(sGFSYNR0),
|
|
defreg_gr0(sGFSYNR1),
|
|
defreg_gr0(sTLBGSTATUS),
|
|
defreg_gr0(nsCR0),
|
|
defreg_gr0(nsGFSR),
|
|
defreg_gr0(nsGFSYNR0),
|
|
defreg_gr0(nsGFSYNR1),
|
|
defreg_gr0(nsTLBGSTATUS),
|
|
defreg_gr0(PIDR2),
|
|
};
|
|
|
|
#define defreg_gnsr0(_name) defreg(GNSR0_ ## _name)
|
|
|
|
static const struct debugfs_reg32 arm_smmu_gnsr0_regs[] = {
|
|
defreg_gnsr0(PMCNTENSET_0),
|
|
defreg_gnsr0(PMCNTENCLR_0),
|
|
defreg_gnsr0(PMINTENSET_0),
|
|
defreg_gnsr0(PMINTENCLR_0),
|
|
defreg_gnsr0(PMOVSCLR_0),
|
|
defreg_gnsr0(PMOVSSET_0),
|
|
defreg_gnsr0(PMCFGR_0),
|
|
defreg_gnsr0(PMCR_0),
|
|
defreg_gnsr0(PMCEID0_0),
|
|
defreg_gnsr0(PMAUTHSTATUS_0),
|
|
defreg_gnsr0(PMDEVTYPE_0)
|
|
};
|
|
|
|
#define defreg_cb(_name) \
|
|
{ \
|
|
.name = __stringify(_name), \
|
|
.offset = ARM_SMMU_CB_ ## _name,\
|
|
}
|
|
|
|
static const struct debugfs_reg32 arm_smmu_cb_regs[] = {
|
|
defreg_cb(SCTLR),
|
|
defreg_cb(TTBCR2),
|
|
defreg_cb(TTBR0_LO),
|
|
defreg_cb(TTBR0_HI),
|
|
defreg_cb(TTBCR),
|
|
defreg_cb(S1_MAIR0),
|
|
defreg_cb(FSR),
|
|
defreg_cb(FAR_LO),
|
|
defreg_cb(FAR_HI),
|
|
defreg_cb(FSYNR0),
|
|
};
|
|
|
|
static int smmu_master_show(struct seq_file *s, void *unused)
|
|
{
|
|
int i;
|
|
struct smmu_debugfs_master *master = s->private;
|
|
struct iommu_fwspec *fwspec = master->dev->iommu_fwspec;
|
|
|
|
for (i = 0; i < fwspec->num_ids; i++) {
|
|
seq_printf(s, "streamids: % 3d ",
|
|
fwspec->ids[i] & smmu_handle->streamid_mask);
|
|
}
|
|
seq_printf(s, "\n");
|
|
for (i = 0; i < fwspec->num_ids; i++) {
|
|
seq_printf(s, "smrs: % 3d ", master->smendx[i]);
|
|
}
|
|
seq_printf(s, "\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smmu_master_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, smmu_master_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations smmu_master_fops = {
|
|
.open = smmu_master_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
void arm_smmu_debugfs_add_master(struct device *dev, u8 *cbndx, u16 smendx[])
|
|
{
|
|
struct smmu_debugfs_master *master;
|
|
struct dentry *dent;
|
|
char name[] = "cb000";
|
|
char target[] = "../../cb000";
|
|
|
|
dent = debugfs_create_dir(dev_name(dev), smmu_handle->masters_root);
|
|
if (!dent)
|
|
return;
|
|
|
|
master = kmalloc(sizeof(*master), GFP_KERNEL);
|
|
master->dev = dev;
|
|
master->smendx = smendx;
|
|
master->dent = dent;
|
|
|
|
// TODO create the streamids file
|
|
debugfs_create_file("streamids", 0444, dent, master,
|
|
&smmu_master_fops);
|
|
debugfs_create_u8("cbndx", 0444, dent, cbndx);
|
|
sprintf(name, "cb%03d", *cbndx);
|
|
sprintf(target, "../../cb%03d", *cbndx);
|
|
debugfs_create_symlink(name, dent, target);
|
|
|
|
list_add_tail(&master->node, &smmu_handle->masters_list);
|
|
}
|
|
|
|
void arm_smmu_debugfs_remove_master(struct device *dev)
|
|
{
|
|
struct smmu_debugfs_master *master = NULL;
|
|
|
|
list_for_each_entry(master, &smmu_handle->masters_list, node) {
|
|
if (master->dev == dev)
|
|
break;
|
|
}
|
|
|
|
if (master != NULL) {
|
|
debugfs_remove_recursive(master->dent);
|
|
list_del(&master->node);
|
|
kfree(master);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TODO: Cbs downstream have the ability to print out iova-to-phys
|
|
* and a dump of their page tables
|
|
*/
|
|
static void debugfs_create_smmu_cb(struct smmu_debugfs_info *smmu, u8 cbndx)
|
|
{
|
|
struct dentry *dent;
|
|
char name[] = "cb000";
|
|
struct debugfs_regset32 *cb;
|
|
|
|
sprintf(name, "cb%03d", cbndx);
|
|
dent = debugfs_create_dir(name, smmu->debugfs_root);
|
|
if (!dent)
|
|
return;
|
|
|
|
cb = smmu->regset + 1 + cbndx;
|
|
cb->regs = arm_smmu_cb_regs;
|
|
cb->nregs = ARRAY_SIZE(arm_smmu_cb_regs);
|
|
cb->base = smmu->base + (smmu->size >> 1) +
|
|
cbndx * (1 << smmu->pgshift);
|
|
debugfs_create_regset32("regdump", S_IRUGO, dent, cb);
|
|
}
|
|
|
|
static int smmu_reg32_debugfs_set(void *data, u64 val)
|
|
{
|
|
struct debugfs_reg32 *regs = (struct debugfs_reg32 *)data;
|
|
|
|
writel(val, (smmu_handle->base + regs->offset));
|
|
return 0;
|
|
}
|
|
|
|
static int smmu_reg32_debugfs_get(void *data, u64 *val)
|
|
{
|
|
struct debugfs_reg32 *regs = (struct debugfs_reg32 *)data;
|
|
|
|
*val = readl(smmu_handle->base + regs->offset);
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(smmu_reg32_debugfs_fops,
|
|
smmu_reg32_debugfs_get,
|
|
smmu_reg32_debugfs_set, "%08llx\n");
|
|
|
|
static int smmu_perf_regset_debugfs_set(void *data, u64 val)
|
|
{
|
|
struct debugfs_reg32 *regs = (struct debugfs_reg32 *)data;
|
|
|
|
writel(val, (smmu_handle->perf_regset->base + regs->offset));
|
|
return 0;
|
|
}
|
|
|
|
static int smmu_perf_regset_debugfs_get(void *data, u64 *val)
|
|
{
|
|
struct debugfs_reg32 *regs = (struct debugfs_reg32 *)data;
|
|
|
|
*val = readl(smmu_handle->perf_regset->base + regs->offset);
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(smmu_perf_regset_debugfs_fops,
|
|
smmu_perf_regset_debugfs_get,
|
|
smmu_perf_regset_debugfs_set, "%08llx\n");
|
|
|
|
void arm_smmu_regs_debugfs_delete(struct smmu_debugfs_info *smmu)
|
|
{
|
|
int i;
|
|
|
|
if (smmu->regset) {
|
|
const struct debugfs_reg32 *regs = smmu->regset->regs;
|
|
|
|
regs += ARRAY_SIZE(arm_smmu_gr0_regs);
|
|
for (i = 0; i < 4 * smmu->num_context_banks; i++)
|
|
kfree(regs[i].name);
|
|
|
|
kfree(smmu->regset);
|
|
}
|
|
|
|
if (smmu->perf_regset) {
|
|
const struct debugfs_reg32 *regs = smmu->perf_regset->regs;
|
|
|
|
i = ARRAY_SIZE(arm_smmu_gnsr0_regs);
|
|
for (; i < smmu->perf_regset->nregs ; i++)
|
|
kfree(regs[i].name);
|
|
|
|
kfree(smmu->perf_regset);
|
|
smmu->perf_regset = NULL;
|
|
}
|
|
|
|
debugfs_remove_recursive(smmu->debugfs_root);
|
|
}
|
|
|
|
int arm_smmu_regs_debugfs_create(struct smmu_debugfs_info *smmu)
|
|
{
|
|
int i;
|
|
struct debugfs_reg32 *regs;
|
|
size_t bytes;
|
|
struct dentry *dent_gr, *dent_gnsr;
|
|
|
|
smmu_handle = smmu;
|
|
|
|
if (!smmu->debugfs_root)
|
|
return -1;
|
|
|
|
dent_gr = debugfs_create_dir("gr", smmu->debugfs_root);
|
|
if (!dent_gr)
|
|
goto err_out;
|
|
|
|
dent_gnsr = debugfs_create_dir("gnsr", smmu->debugfs_root);
|
|
if (!dent_gnsr)
|
|
goto err_out;
|
|
|
|
smmu->masters_root = debugfs_create_dir("masters", smmu->debugfs_root);
|
|
if (!smmu->masters_root)
|
|
goto err_out;
|
|
|
|
smmu->cb_root = debugfs_create_dir("context_banks", smmu->debugfs_root);
|
|
if (!smmu->cb_root)
|
|
goto err_out;
|
|
|
|
bytes = (smmu->num_context_banks + 1) * sizeof(*smmu->regset);
|
|
bytes += ARRAY_SIZE(arm_smmu_gr0_regs) * sizeof(*regs);
|
|
bytes += 4 * smmu->num_context_banks * sizeof(*regs);
|
|
smmu->regset = kzalloc(bytes, GFP_KERNEL);
|
|
if (!smmu->regset)
|
|
goto err_out;
|
|
|
|
smmu->regset->base = smmu->base;
|
|
smmu->regset->nregs = ARRAY_SIZE(arm_smmu_gr0_regs) +
|
|
4 * smmu->num_context_banks;
|
|
smmu->regset->regs = (struct debugfs_reg32 *)(smmu->regset +
|
|
smmu->num_context_banks + 1);
|
|
regs = (struct debugfs_reg32 *)smmu->regset->regs;
|
|
for (i = 0; i < ARRAY_SIZE(arm_smmu_gr0_regs); i++) {
|
|
regs->name = arm_smmu_gr0_regs[i].name;
|
|
regs->offset = arm_smmu_gr0_regs[i].offset;
|
|
regs++;
|
|
}
|
|
|
|
for (i = 0; i < smmu->num_context_banks; i++) {
|
|
regs->name = kasprintf(GFP_KERNEL, "GR0_SMR%03d", i);
|
|
if (!regs->name)
|
|
goto err_out;
|
|
regs->offset = ARM_SMMU_GR0_SMR(i);
|
|
regs++;
|
|
|
|
regs->name = kasprintf(GFP_KERNEL, "GR0_S2CR%03d", i);
|
|
if (!regs->name)
|
|
goto err_out;
|
|
regs->offset = ARM_SMMU_GR0_S2CR(i);
|
|
regs++;
|
|
|
|
regs->name = kasprintf(GFP_KERNEL, "GR1_CBAR%03d", i);
|
|
if (!regs->name)
|
|
goto err_out;
|
|
regs->offset = (1 << smmu->pgshift) + ARM_SMMU_GR1_CBAR(i);
|
|
regs++;
|
|
|
|
regs->name = kasprintf(GFP_KERNEL, "GR1_CBA2R%03d", i);
|
|
if (!regs->name)
|
|
goto err_out;
|
|
regs->offset = (1 << smmu->pgshift) + ARM_SMMU_GR1_CBA2R(i);
|
|
regs++;
|
|
}
|
|
|
|
regs = (struct debugfs_reg32 *)smmu->regset->regs;
|
|
for (i = 0; i < smmu->regset->nregs; i++) {
|
|
debugfs_create_file(regs->name, S_IRUGO | S_IWUSR,
|
|
dent_gr, regs, &smmu_reg32_debugfs_fops);
|
|
regs++;
|
|
}
|
|
|
|
debugfs_create_regset32("regdump", S_IRUGO, smmu->debugfs_root,
|
|
smmu->regset);
|
|
|
|
bytes = sizeof(*smmu->perf_regset);
|
|
bytes += ARRAY_SIZE(arm_smmu_gnsr0_regs) * sizeof(*regs);
|
|
/*
|
|
* Account the number of bytes for two sets of
|
|
* counter group registers
|
|
*/
|
|
bytes += 2 * PMCG_SIZE * sizeof(*regs);
|
|
/*
|
|
* Account the number of bytes for two sets of
|
|
* event counter registers
|
|
*/
|
|
bytes += 2 * PMEV_SIZE * sizeof(*regs);
|
|
|
|
/* Allocate memory for Perf Monitor registers */
|
|
smmu->perf_regset = kzalloc(bytes, GFP_KERNEL);
|
|
if (!smmu->perf_regset)
|
|
goto err_out;
|
|
|
|
/*
|
|
* perf_regset base address is placed at offset (3 * smmu_pagesize)
|
|
* from smmu->base address
|
|
*/
|
|
smmu->perf_regset->base = smmu->base + 3 * (1 << smmu->pgshift);
|
|
smmu->perf_regset->nregs = ARRAY_SIZE(arm_smmu_gnsr0_regs) +
|
|
2 * PMCG_SIZE + 2 * PMEV_SIZE;
|
|
smmu->perf_regset->regs =
|
|
(struct debugfs_reg32 *)(smmu->perf_regset + 1);
|
|
|
|
regs = (struct debugfs_reg32 *)smmu->perf_regset->regs;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(arm_smmu_gnsr0_regs); i++) {
|
|
regs->name = arm_smmu_gnsr0_regs[i].name;
|
|
regs->offset = arm_smmu_gnsr0_regs[i].offset;
|
|
regs++;
|
|
}
|
|
|
|
for (i = 0; i < PMEV_SIZE; i++) {
|
|
regs->name = kasprintf(GFP_KERNEL, "GNSR0_PMEVTYPER%d_0", i);
|
|
if (!regs->name)
|
|
goto err_out;
|
|
regs->offset = ARM_SMMU_GNSR0_PMEVTYPER(i);
|
|
regs++;
|
|
|
|
regs->name = kasprintf(GFP_KERNEL, "GNSR0_PMEVCNTR%d_0", i);
|
|
if (!regs->name)
|
|
goto err_out;
|
|
regs->offset = ARM_SMMU_GNSR0_PMEVCNTR(i);
|
|
regs++;
|
|
}
|
|
|
|
for (i = 0; i < PMCG_SIZE; i++) {
|
|
regs->name = kasprintf(GFP_KERNEL, "GNSR0_PMCGCR%d_0", i);
|
|
if (!regs->name)
|
|
goto err_out;
|
|
regs->offset = ARM_SMMU_GNSR0_PMCGCR(i);
|
|
regs++;
|
|
|
|
regs->name = kasprintf(GFP_KERNEL, "GNSR0_PMCGSMR%d_0", i);
|
|
if (!regs->name)
|
|
goto err_out;
|
|
regs->offset = ARM_SMMU_GNSR0_PMCGSMR(i);
|
|
regs++;
|
|
}
|
|
|
|
regs = (struct debugfs_reg32 *)smmu->perf_regset->regs;
|
|
for (i = 0; i < smmu->perf_regset->nregs; i++) {
|
|
debugfs_create_file(regs->name, S_IRUGO | S_IWUSR,
|
|
dent_gnsr, regs, &smmu_perf_regset_debugfs_fops);
|
|
regs++;
|
|
}
|
|
|
|
for (i = 0; i < smmu->num_context_banks; i++)
|
|
debugfs_create_smmu_cb(smmu, i);
|
|
|
|
INIT_LIST_HEAD(&smmu_handle->masters_list);
|
|
|
|
return 0;
|
|
|
|
err_out:
|
|
arm_smmu_regs_debugfs_delete(smmu);
|
|
return -1;
|
|
}
|
|
|
|
#endif
|