root/arch/powerpc/platforms/powernv/opal-xscom.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * PowerNV SCOM bus debugfs interface
 *
 * Copyright 2010 Benjamin Herrenschmidt, IBM Corp
 *                <benh@kernel.crashing.org>
 *     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_obj(*ent);
        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);