// SPDX-License-Identifier: GPL-2.0-or-later
/*
* PowerNV SCOM bus debugfs interface
*
* Copyright 2010 Benjamin Herrenschmidt, IBM Corp
* <[email protected]>
* and David Gibson, IBM Corporation.
* Copyright 2013 IBM Corp.
*/
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/bug.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <asm/machdep.h>
#include <asm/firmware.h>
#include <asm/opal.h>
#include <asm/prom.h>
static u64 opal_scom_unmangle(u64 addr)
{
u64 tmp;
/*
* XSCOM addresses use the top nibble to set indirect mode and
* its form. Bits 4-11 are always 0.
*
* Because the debugfs interface uses signed offsets and shifts
* the address left by 3, we basically cannot use the top 4 bits
* of the 64-bit address, and thus cannot use the indirect bit.
*
* To deal with that, we support the indirect bits being in
* bits 4-7 (IBM notation) instead of bit 0-3 in this API, we
* do the conversion here.
*
* For in-kernel use, we don't need to do this mangling. In
* kernel won't have bits 4-7 set.
*
* So:
* debugfs will always set 0-3 = 0 and clear 4-7
* kernel will always clear 0-3 = 0 and set 4-7
*/
tmp = addr;
tmp &= 0x0f00000000000000;
addr &= 0xf0ffffffffffffff;
addr |= tmp << 4;
return addr;
}
static int opal_scom_read(uint32_t chip, uint64_t addr, u64 reg, u64 *value)
{
int64_t rc;
__be64 v;
reg = opal_scom_unmangle(addr + reg);
rc = opal_xscom_read(chip, reg, (__be64 *)__pa(&v));
if (rc) {
*value = 0xfffffffffffffffful;
return -EIO;
}
*value = be64_to_cpu(v);
return 0;
}
static int opal_scom_write(uint32_t chip, uint64_t addr, u64 reg, u64 value)
{
int64_t rc;
reg = opal_scom_unmangle(addr + reg);
rc = opal_xscom_write(chip, reg, value);
if (rc)
return -EIO;
return 0;
}
struct scom_debug_entry {
u32 chip;
struct debugfs_blob_wrapper path;
char name[16];
};
static ssize_t scom_debug_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct scom_debug_entry *ent = filp->private_data;
u64 __user *ubuf64 = (u64 __user *)ubuf;
loff_t off = *ppos;
ssize_t done = 0;
u64 reg, reg_base, reg_cnt, val;
int rc;
if (off < 0 || (off & 7) || (count & 7))
return -EINVAL;
reg_base = off >> 3;
reg_cnt = count >> 3;
for (reg = 0; reg < reg_cnt; reg++) {
rc = opal_scom_read(ent->chip, reg_base, reg, &val);
if (!rc)
rc = put_user(val, ubuf64);
if (rc) {
if (!done)
done = rc;
break;
}
ubuf64++;
*ppos += 8;
done += 8;
}
return done;
}
static ssize_t scom_debug_write(struct file *filp, const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct scom_debug_entry *ent = filp->private_data;
u64 __user *ubuf64 = (u64 __user *)ubuf;
loff_t off = *ppos;
ssize_t done = 0;
u64 reg, reg_base, reg_cnt, val;
int rc;
if (off < 0 || (off & 7) || (count & 7))
return -EINVAL;
reg_base = off >> 3;
reg_cnt = count >> 3;
for (reg = 0; reg < reg_cnt; reg++) {
rc = get_user(val, ubuf64);
if (!rc)
rc = opal_scom_write(ent->chip, reg_base, reg, val);
if (rc) {
if (!done)
done = rc;
break;
}
ubuf64++;
done += 8;
}
return done;
}
static const struct file_operations scom_debug_fops = {
.read = scom_debug_read,
.write = scom_debug_write,
.open = simple_open,
.llseek = default_llseek,
};
static int scom_debug_init_one(struct dentry *root, struct device_node *dn,
int chip)
{
struct scom_debug_entry *ent;
struct dentry *dir;
ent = kzalloc(sizeof(*ent), GFP_KERNEL);
if (!ent)
return -ENOMEM;
ent->chip = chip;
snprintf(ent->name, 16, "%08x", chip);
ent->path.data = (void *)kasprintf(GFP_KERNEL, "%pOF", dn);
if (!ent->path.data) {
kfree(ent);
return -ENOMEM;
}
ent->path.size = strlen((char *)ent->path.data);
dir = debugfs_create_dir(ent->name, root);
if (IS_ERR(dir)) {
kfree(ent->path.data);
kfree(ent);
return -1;
}
debugfs_create_blob("devspec", 0400, dir, &ent->path);
debugfs_create_file("access", 0600, dir, ent, &scom_debug_fops);
return 0;
}
static int scom_debug_init(void)
{
struct device_node *dn;
struct dentry *root;
int chip, rc;
if (!firmware_has_feature(FW_FEATURE_OPAL))
return 0;
root = debugfs_create_dir("scom", arch_debugfs_dir);
if (IS_ERR(root))
return -1;
rc = 0;
for_each_node_with_property(dn, "scom-controller") {
chip = of_get_ibm_chip_id(dn);
WARN_ON(chip == -1);
rc |= scom_debug_init_one(root, dn, chip);
}
return rc;
}
device_initcall(scom_debug_init);