// SPDX-License-Identifier: GPL-2.0-or-later /* * PowerNV SCOM bus debugfs interface * * Copyright 2010 Benjamin Herrenschmidt, IBM Corp * * and David Gibson, IBM Corporation. * Copyright 2013 IBM Corp. */ #include #include #include #include #include #include #include #include #include #include #include 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);