18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * PowerNV SCOM bus debugfs interface 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2010 Benjamin Herrenschmidt, IBM Corp 68c2ecf20Sopenharmony_ci * <benh@kernel.crashing.org> 78c2ecf20Sopenharmony_ci * and David Gibson, IBM Corporation. 88c2ecf20Sopenharmony_ci * Copyright 2013 IBM Corp. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/of.h> 138c2ecf20Sopenharmony_ci#include <linux/bug.h> 148c2ecf20Sopenharmony_ci#include <linux/gfp.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <asm/machdep.h> 198c2ecf20Sopenharmony_ci#include <asm/firmware.h> 208c2ecf20Sopenharmony_ci#include <asm/opal.h> 218c2ecf20Sopenharmony_ci#include <asm/debugfs.h> 228c2ecf20Sopenharmony_ci#include <asm/prom.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic u64 opal_scom_unmangle(u64 addr) 258c2ecf20Sopenharmony_ci{ 268c2ecf20Sopenharmony_ci u64 tmp; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci /* 298c2ecf20Sopenharmony_ci * XSCOM addresses use the top nibble to set indirect mode and 308c2ecf20Sopenharmony_ci * its form. Bits 4-11 are always 0. 318c2ecf20Sopenharmony_ci * 328c2ecf20Sopenharmony_ci * Because the debugfs interface uses signed offsets and shifts 338c2ecf20Sopenharmony_ci * the address left by 3, we basically cannot use the top 4 bits 348c2ecf20Sopenharmony_ci * of the 64-bit address, and thus cannot use the indirect bit. 358c2ecf20Sopenharmony_ci * 368c2ecf20Sopenharmony_ci * To deal with that, we support the indirect bits being in 378c2ecf20Sopenharmony_ci * bits 4-7 (IBM notation) instead of bit 0-3 in this API, we 388c2ecf20Sopenharmony_ci * do the conversion here. 398c2ecf20Sopenharmony_ci * 408c2ecf20Sopenharmony_ci * For in-kernel use, we don't need to do this mangling. In 418c2ecf20Sopenharmony_ci * kernel won't have bits 4-7 set. 428c2ecf20Sopenharmony_ci * 438c2ecf20Sopenharmony_ci * So: 448c2ecf20Sopenharmony_ci * debugfs will always set 0-3 = 0 and clear 4-7 458c2ecf20Sopenharmony_ci * kernel will always clear 0-3 = 0 and set 4-7 468c2ecf20Sopenharmony_ci */ 478c2ecf20Sopenharmony_ci tmp = addr; 488c2ecf20Sopenharmony_ci tmp &= 0x0f00000000000000; 498c2ecf20Sopenharmony_ci addr &= 0xf0ffffffffffffff; 508c2ecf20Sopenharmony_ci addr |= tmp << 4; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci return addr; 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic int opal_scom_read(uint32_t chip, uint64_t addr, u64 reg, u64 *value) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci int64_t rc; 588c2ecf20Sopenharmony_ci __be64 v; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci reg = opal_scom_unmangle(addr + reg); 618c2ecf20Sopenharmony_ci rc = opal_xscom_read(chip, reg, (__be64 *)__pa(&v)); 628c2ecf20Sopenharmony_ci if (rc) { 638c2ecf20Sopenharmony_ci *value = 0xfffffffffffffffful; 648c2ecf20Sopenharmony_ci return -EIO; 658c2ecf20Sopenharmony_ci } 668c2ecf20Sopenharmony_ci *value = be64_to_cpu(v); 678c2ecf20Sopenharmony_ci return 0; 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic int opal_scom_write(uint32_t chip, uint64_t addr, u64 reg, u64 value) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci int64_t rc; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci reg = opal_scom_unmangle(addr + reg); 758c2ecf20Sopenharmony_ci rc = opal_xscom_write(chip, reg, value); 768c2ecf20Sopenharmony_ci if (rc) 778c2ecf20Sopenharmony_ci return -EIO; 788c2ecf20Sopenharmony_ci return 0; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistruct scom_debug_entry { 828c2ecf20Sopenharmony_ci u32 chip; 838c2ecf20Sopenharmony_ci struct debugfs_blob_wrapper path; 848c2ecf20Sopenharmony_ci char name[16]; 858c2ecf20Sopenharmony_ci}; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic ssize_t scom_debug_read(struct file *filp, char __user *ubuf, 888c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci struct scom_debug_entry *ent = filp->private_data; 918c2ecf20Sopenharmony_ci u64 __user *ubuf64 = (u64 __user *)ubuf; 928c2ecf20Sopenharmony_ci loff_t off = *ppos; 938c2ecf20Sopenharmony_ci ssize_t done = 0; 948c2ecf20Sopenharmony_ci u64 reg, reg_base, reg_cnt, val; 958c2ecf20Sopenharmony_ci int rc; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (off < 0 || (off & 7) || (count & 7)) 988c2ecf20Sopenharmony_ci return -EINVAL; 998c2ecf20Sopenharmony_ci reg_base = off >> 3; 1008c2ecf20Sopenharmony_ci reg_cnt = count >> 3; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci for (reg = 0; reg < reg_cnt; reg++) { 1038c2ecf20Sopenharmony_ci rc = opal_scom_read(ent->chip, reg_base, reg, &val); 1048c2ecf20Sopenharmony_ci if (!rc) 1058c2ecf20Sopenharmony_ci rc = put_user(val, ubuf64); 1068c2ecf20Sopenharmony_ci if (rc) { 1078c2ecf20Sopenharmony_ci if (!done) 1088c2ecf20Sopenharmony_ci done = rc; 1098c2ecf20Sopenharmony_ci break; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci ubuf64++; 1128c2ecf20Sopenharmony_ci *ppos += 8; 1138c2ecf20Sopenharmony_ci done += 8; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci return done; 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic ssize_t scom_debug_write(struct file *filp, const char __user *ubuf, 1198c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci struct scom_debug_entry *ent = filp->private_data; 1228c2ecf20Sopenharmony_ci u64 __user *ubuf64 = (u64 __user *)ubuf; 1238c2ecf20Sopenharmony_ci loff_t off = *ppos; 1248c2ecf20Sopenharmony_ci ssize_t done = 0; 1258c2ecf20Sopenharmony_ci u64 reg, reg_base, reg_cnt, val; 1268c2ecf20Sopenharmony_ci int rc; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (off < 0 || (off & 7) || (count & 7)) 1298c2ecf20Sopenharmony_ci return -EINVAL; 1308c2ecf20Sopenharmony_ci reg_base = off >> 3; 1318c2ecf20Sopenharmony_ci reg_cnt = count >> 3; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci for (reg = 0; reg < reg_cnt; reg++) { 1348c2ecf20Sopenharmony_ci rc = get_user(val, ubuf64); 1358c2ecf20Sopenharmony_ci if (!rc) 1368c2ecf20Sopenharmony_ci rc = opal_scom_write(ent->chip, reg_base, reg, val); 1378c2ecf20Sopenharmony_ci if (rc) { 1388c2ecf20Sopenharmony_ci if (!done) 1398c2ecf20Sopenharmony_ci done = rc; 1408c2ecf20Sopenharmony_ci break; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci ubuf64++; 1438c2ecf20Sopenharmony_ci done += 8; 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci return done; 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic const struct file_operations scom_debug_fops = { 1498c2ecf20Sopenharmony_ci .read = scom_debug_read, 1508c2ecf20Sopenharmony_ci .write = scom_debug_write, 1518c2ecf20Sopenharmony_ci .open = simple_open, 1528c2ecf20Sopenharmony_ci .llseek = default_llseek, 1538c2ecf20Sopenharmony_ci}; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cistatic int scom_debug_init_one(struct dentry *root, struct device_node *dn, 1568c2ecf20Sopenharmony_ci int chip) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci struct scom_debug_entry *ent; 1598c2ecf20Sopenharmony_ci struct dentry *dir; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci ent = kzalloc(sizeof(*ent), GFP_KERNEL); 1628c2ecf20Sopenharmony_ci if (!ent) 1638c2ecf20Sopenharmony_ci return -ENOMEM; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci ent->chip = chip; 1668c2ecf20Sopenharmony_ci snprintf(ent->name, 16, "%08x", chip); 1678c2ecf20Sopenharmony_ci ent->path.data = (void *)kasprintf(GFP_KERNEL, "%pOF", dn); 1688c2ecf20Sopenharmony_ci if (!ent->path.data) { 1698c2ecf20Sopenharmony_ci kfree(ent); 1708c2ecf20Sopenharmony_ci return -ENOMEM; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci ent->path.size = strlen((char *)ent->path.data); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci dir = debugfs_create_dir(ent->name, root); 1768c2ecf20Sopenharmony_ci if (!dir) { 1778c2ecf20Sopenharmony_ci kfree(ent->path.data); 1788c2ecf20Sopenharmony_ci kfree(ent); 1798c2ecf20Sopenharmony_ci return -1; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci debugfs_create_blob("devspec", 0400, dir, &ent->path); 1838c2ecf20Sopenharmony_ci debugfs_create_file("access", 0600, dir, ent, &scom_debug_fops); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci return 0; 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic int scom_debug_init(void) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci struct device_node *dn; 1918c2ecf20Sopenharmony_ci struct dentry *root; 1928c2ecf20Sopenharmony_ci int chip, rc; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci if (!firmware_has_feature(FW_FEATURE_OPAL)) 1958c2ecf20Sopenharmony_ci return 0; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci root = debugfs_create_dir("scom", powerpc_debugfs_root); 1988c2ecf20Sopenharmony_ci if (!root) 1998c2ecf20Sopenharmony_ci return -1; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci rc = 0; 2028c2ecf20Sopenharmony_ci for_each_node_with_property(dn, "scom-controller") { 2038c2ecf20Sopenharmony_ci chip = of_get_ibm_chip_id(dn); 2048c2ecf20Sopenharmony_ci WARN_ON(chip == -1); 2058c2ecf20Sopenharmony_ci rc |= scom_debug_init_one(root, dn, chip); 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci return rc; 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_cidevice_initcall(scom_debug_init); 211