12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
28adae0c8SBenjamin Herrenschmidt /*
3bfd2f0d4SAndrew Donnellan  * PowerNV SCOM bus debugfs interface
48adae0c8SBenjamin Herrenschmidt  *
5bfd2f0d4SAndrew Donnellan  * Copyright 2010 Benjamin Herrenschmidt, IBM Corp
6bfd2f0d4SAndrew Donnellan  *                <benh@kernel.crashing.org>
7bfd2f0d4SAndrew Donnellan  *     and        David Gibson, IBM Corporation.
88adae0c8SBenjamin Herrenschmidt  * Copyright 2013 IBM Corp.
98adae0c8SBenjamin Herrenschmidt  */
108adae0c8SBenjamin Herrenschmidt 
118adae0c8SBenjamin Herrenschmidt #include <linux/kernel.h>
128adae0c8SBenjamin Herrenschmidt #include <linux/of.h>
138adae0c8SBenjamin Herrenschmidt #include <linux/bug.h>
148adae0c8SBenjamin Herrenschmidt #include <linux/gfp.h>
158adae0c8SBenjamin Herrenschmidt #include <linux/slab.h>
16bfd2f0d4SAndrew Donnellan #include <linux/uaccess.h>
17dbf77fedSAneesh Kumar K.V #include <linux/debugfs.h>
188adae0c8SBenjamin Herrenschmidt 
198adae0c8SBenjamin Herrenschmidt #include <asm/machdep.h>
208adae0c8SBenjamin Herrenschmidt #include <asm/firmware.h>
218adae0c8SBenjamin Herrenschmidt #include <asm/opal.h>
22bfd2f0d4SAndrew Donnellan #include <asm/prom.h>
238adae0c8SBenjamin Herrenschmidt 
opal_scom_unmangle(u64 addr)24e0cf9576SBenjamin Herrenschmidt static u64 opal_scom_unmangle(u64 addr)
2580546ac5SBenjamin Herrenschmidt {
26517c2757SMichael Neuling 	u64 tmp;
27517c2757SMichael Neuling 
2880546ac5SBenjamin Herrenschmidt 	/*
29517c2757SMichael Neuling 	 * XSCOM addresses use the top nibble to set indirect mode and
30517c2757SMichael Neuling 	 * its form.  Bits 4-11 are always 0.
3180546ac5SBenjamin Herrenschmidt 	 *
3280546ac5SBenjamin Herrenschmidt 	 * Because the debugfs interface uses signed offsets and shifts
3380546ac5SBenjamin Herrenschmidt 	 * the address left by 3, we basically cannot use the top 4 bits
3480546ac5SBenjamin Herrenschmidt 	 * of the 64-bit address, and thus cannot use the indirect bit.
3580546ac5SBenjamin Herrenschmidt 	 *
36517c2757SMichael Neuling 	 * To deal with that, we support the indirect bits being in
37517c2757SMichael Neuling 	 * bits 4-7 (IBM notation) instead of bit 0-3 in this API, we
38517c2757SMichael Neuling 	 * do the conversion here.
3980546ac5SBenjamin Herrenschmidt 	 *
40517c2757SMichael Neuling 	 * For in-kernel use, we don't need to do this mangling.  In
41517c2757SMichael Neuling 	 * kernel won't have bits 4-7 set.
42e0cf9576SBenjamin Herrenschmidt 	 *
43517c2757SMichael Neuling 	 * So:
44517c2757SMichael Neuling 	 *   debugfs will always   set 0-3 = 0 and clear 4-7
45517c2757SMichael Neuling 	 *    kernel will always clear 0-3 = 0 and   set 4-7
4680546ac5SBenjamin Herrenschmidt 	 */
47517c2757SMichael Neuling 	tmp = addr;
48517c2757SMichael Neuling 	tmp  &= 0x0f00000000000000;
49517c2757SMichael Neuling 	addr &= 0xf0ffffffffffffff;
50517c2757SMichael Neuling 	addr |= tmp << 4;
51517c2757SMichael Neuling 
52e0cf9576SBenjamin Herrenschmidt 	return addr;
5380546ac5SBenjamin Herrenschmidt }
5480546ac5SBenjamin Herrenschmidt 
opal_scom_read(uint32_t chip,uint64_t addr,u64 reg,u64 * value)55bfd2f0d4SAndrew Donnellan static int opal_scom_read(uint32_t chip, uint64_t addr, u64 reg, u64 *value)
568adae0c8SBenjamin Herrenschmidt {
578adae0c8SBenjamin Herrenschmidt 	int64_t rc;
5801a9dbccSAnton Blanchard 	__be64 v;
598adae0c8SBenjamin Herrenschmidt 
60bfd2f0d4SAndrew Donnellan 	reg = opal_scom_unmangle(addr + reg);
61bfd2f0d4SAndrew Donnellan 	rc = opal_xscom_read(chip, reg, (__be64 *)__pa(&v));
62bfd2f0d4SAndrew Donnellan 	if (rc) {
63bfd2f0d4SAndrew Donnellan 		*value = 0xfffffffffffffffful;
64bfd2f0d4SAndrew Donnellan 		return -EIO;
65bfd2f0d4SAndrew Donnellan 	}
6601a9dbccSAnton Blanchard 	*value = be64_to_cpu(v);
678adae0c8SBenjamin Herrenschmidt 	return 0;
688adae0c8SBenjamin Herrenschmidt }
69bfd2f0d4SAndrew Donnellan 
opal_scom_write(uint32_t chip,uint64_t addr,u64 reg,u64 value)70bfd2f0d4SAndrew Donnellan static int opal_scom_write(uint32_t chip, uint64_t addr, u64 reg, u64 value)
71bfd2f0d4SAndrew Donnellan {
72bfd2f0d4SAndrew Donnellan 	int64_t rc;
73bfd2f0d4SAndrew Donnellan 
74bfd2f0d4SAndrew Donnellan 	reg = opal_scom_unmangle(addr + reg);
75bfd2f0d4SAndrew Donnellan 	rc = opal_xscom_write(chip, reg, value);
76bfd2f0d4SAndrew Donnellan 	if (rc)
77bfd2f0d4SAndrew Donnellan 		return -EIO;
78bfd2f0d4SAndrew Donnellan 	return 0;
79bfd2f0d4SAndrew Donnellan }
80bfd2f0d4SAndrew Donnellan 
81bfd2f0d4SAndrew Donnellan struct scom_debug_entry {
82bfd2f0d4SAndrew Donnellan 	u32 chip;
83bfd2f0d4SAndrew Donnellan 	struct debugfs_blob_wrapper path;
84bfd2f0d4SAndrew Donnellan 	char name[16];
85bfd2f0d4SAndrew Donnellan };
86bfd2f0d4SAndrew Donnellan 
scom_debug_read(struct file * filp,char __user * ubuf,size_t count,loff_t * ppos)87bfd2f0d4SAndrew Donnellan static ssize_t scom_debug_read(struct file *filp, char __user *ubuf,
88bfd2f0d4SAndrew Donnellan 			       size_t count, loff_t *ppos)
89bfd2f0d4SAndrew Donnellan {
90bfd2f0d4SAndrew Donnellan 	struct scom_debug_entry *ent = filp->private_data;
91bfd2f0d4SAndrew Donnellan 	u64 __user *ubuf64 = (u64 __user *)ubuf;
92bfd2f0d4SAndrew Donnellan 	loff_t off = *ppos;
93bfd2f0d4SAndrew Donnellan 	ssize_t done = 0;
94bfd2f0d4SAndrew Donnellan 	u64 reg, reg_base, reg_cnt, val;
95bfd2f0d4SAndrew Donnellan 	int rc;
96bfd2f0d4SAndrew Donnellan 
97bfd2f0d4SAndrew Donnellan 	if (off < 0 || (off & 7) || (count & 7))
98bfd2f0d4SAndrew Donnellan 		return -EINVAL;
99bfd2f0d4SAndrew Donnellan 	reg_base = off >> 3;
100bfd2f0d4SAndrew Donnellan 	reg_cnt = count >> 3;
101bfd2f0d4SAndrew Donnellan 
102bfd2f0d4SAndrew Donnellan 	for (reg = 0; reg < reg_cnt; reg++) {
103bfd2f0d4SAndrew Donnellan 		rc = opal_scom_read(ent->chip, reg_base, reg, &val);
104bfd2f0d4SAndrew Donnellan 		if (!rc)
105bfd2f0d4SAndrew Donnellan 			rc = put_user(val, ubuf64);
106bfd2f0d4SAndrew Donnellan 		if (rc) {
107bfd2f0d4SAndrew Donnellan 			if (!done)
108bfd2f0d4SAndrew Donnellan 				done = rc;
109bfd2f0d4SAndrew Donnellan 			break;
110bfd2f0d4SAndrew Donnellan 		}
111bfd2f0d4SAndrew Donnellan 		ubuf64++;
112bfd2f0d4SAndrew Donnellan 		*ppos += 8;
113bfd2f0d4SAndrew Donnellan 		done += 8;
114bfd2f0d4SAndrew Donnellan 	}
115bfd2f0d4SAndrew Donnellan 	return done;
116bfd2f0d4SAndrew Donnellan }
117bfd2f0d4SAndrew Donnellan 
scom_debug_write(struct file * filp,const char __user * ubuf,size_t count,loff_t * ppos)118bfd2f0d4SAndrew Donnellan static ssize_t scom_debug_write(struct file *filp, const char __user *ubuf,
119bfd2f0d4SAndrew Donnellan 				size_t count, loff_t *ppos)
120bfd2f0d4SAndrew Donnellan {
121bfd2f0d4SAndrew Donnellan 	struct scom_debug_entry *ent = filp->private_data;
122bfd2f0d4SAndrew Donnellan 	u64 __user *ubuf64 = (u64 __user *)ubuf;
123bfd2f0d4SAndrew Donnellan 	loff_t off = *ppos;
124bfd2f0d4SAndrew Donnellan 	ssize_t done = 0;
125bfd2f0d4SAndrew Donnellan 	u64 reg, reg_base, reg_cnt, val;
126bfd2f0d4SAndrew Donnellan 	int rc;
127bfd2f0d4SAndrew Donnellan 
128bfd2f0d4SAndrew Donnellan 	if (off < 0 || (off & 7) || (count & 7))
129bfd2f0d4SAndrew Donnellan 		return -EINVAL;
130bfd2f0d4SAndrew Donnellan 	reg_base = off >> 3;
131bfd2f0d4SAndrew Donnellan 	reg_cnt = count >> 3;
132bfd2f0d4SAndrew Donnellan 
133bfd2f0d4SAndrew Donnellan 	for (reg = 0; reg < reg_cnt; reg++) {
134bfd2f0d4SAndrew Donnellan 		rc = get_user(val, ubuf64);
135bfd2f0d4SAndrew Donnellan 		if (!rc)
136bfd2f0d4SAndrew Donnellan 			rc = opal_scom_write(ent->chip, reg_base, reg,  val);
137bfd2f0d4SAndrew Donnellan 		if (rc) {
138bfd2f0d4SAndrew Donnellan 			if (!done)
139bfd2f0d4SAndrew Donnellan 				done = rc;
140bfd2f0d4SAndrew Donnellan 			break;
141bfd2f0d4SAndrew Donnellan 		}
142bfd2f0d4SAndrew Donnellan 		ubuf64++;
143bfd2f0d4SAndrew Donnellan 		done += 8;
144bfd2f0d4SAndrew Donnellan 	}
145bfd2f0d4SAndrew Donnellan 	return done;
146bfd2f0d4SAndrew Donnellan }
147bfd2f0d4SAndrew Donnellan 
148bfd2f0d4SAndrew Donnellan static const struct file_operations scom_debug_fops = {
149bfd2f0d4SAndrew Donnellan 	.read =		scom_debug_read,
150bfd2f0d4SAndrew Donnellan 	.write =	scom_debug_write,
151bfd2f0d4SAndrew Donnellan 	.open =		simple_open,
152bfd2f0d4SAndrew Donnellan 	.llseek =	default_llseek,
153bfd2f0d4SAndrew Donnellan };
154bfd2f0d4SAndrew Donnellan 
scom_debug_init_one(struct dentry * root,struct device_node * dn,int chip)155bfd2f0d4SAndrew Donnellan static int scom_debug_init_one(struct dentry *root, struct device_node *dn,
156bfd2f0d4SAndrew Donnellan 			       int chip)
157bfd2f0d4SAndrew Donnellan {
158bfd2f0d4SAndrew Donnellan 	struct scom_debug_entry *ent;
159bfd2f0d4SAndrew Donnellan 	struct dentry *dir;
160bfd2f0d4SAndrew Donnellan 
161bfd2f0d4SAndrew Donnellan 	ent = kzalloc(sizeof(*ent), GFP_KERNEL);
162bfd2f0d4SAndrew Donnellan 	if (!ent)
163bfd2f0d4SAndrew Donnellan 		return -ENOMEM;
164bfd2f0d4SAndrew Donnellan 
165bfd2f0d4SAndrew Donnellan 	ent->chip = chip;
166bfd2f0d4SAndrew Donnellan 	snprintf(ent->name, 16, "%08x", chip);
167bfd2f0d4SAndrew Donnellan 	ent->path.data = (void *)kasprintf(GFP_KERNEL, "%pOF", dn);
168*dd8422ffSKunwu Chan 	if (!ent->path.data) {
169*dd8422ffSKunwu Chan 		kfree(ent);
170*dd8422ffSKunwu Chan 		return -ENOMEM;
171*dd8422ffSKunwu Chan 	}
172*dd8422ffSKunwu Chan 
173bfd2f0d4SAndrew Donnellan 	ent->path.size = strlen((char *)ent->path.data);
174bfd2f0d4SAndrew Donnellan 
175bfd2f0d4SAndrew Donnellan 	dir = debugfs_create_dir(ent->name, root);
176429356faSImmad Mir 	if (IS_ERR(dir)) {
177bfd2f0d4SAndrew Donnellan 		kfree(ent->path.data);
178bfd2f0d4SAndrew Donnellan 		kfree(ent);
179bfd2f0d4SAndrew Donnellan 		return -1;
180bfd2f0d4SAndrew Donnellan 	}
181bfd2f0d4SAndrew Donnellan 
182bfd2f0d4SAndrew Donnellan 	debugfs_create_blob("devspec", 0400, dir, &ent->path);
183bfd2f0d4SAndrew Donnellan 	debugfs_create_file("access", 0600, dir, ent, &scom_debug_fops);
184bfd2f0d4SAndrew Donnellan 
185bfd2f0d4SAndrew Donnellan 	return 0;
186bfd2f0d4SAndrew Donnellan }
187bfd2f0d4SAndrew Donnellan 
scom_debug_init(void)188bfd2f0d4SAndrew Donnellan static int scom_debug_init(void)
189bfd2f0d4SAndrew Donnellan {
190bfd2f0d4SAndrew Donnellan 	struct device_node *dn;
191bfd2f0d4SAndrew Donnellan 	struct dentry *root;
192bfd2f0d4SAndrew Donnellan 	int chip, rc;
193bfd2f0d4SAndrew Donnellan 
194bfd2f0d4SAndrew Donnellan 	if (!firmware_has_feature(FW_FEATURE_OPAL))
195bfd2f0d4SAndrew Donnellan 		return 0;
196bfd2f0d4SAndrew Donnellan 
197dbf77fedSAneesh Kumar K.V 	root = debugfs_create_dir("scom", arch_debugfs_dir);
198429356faSImmad Mir 	if (IS_ERR(root))
199bfd2f0d4SAndrew Donnellan 		return -1;
200bfd2f0d4SAndrew Donnellan 
201bfd2f0d4SAndrew Donnellan 	rc = 0;
202bfd2f0d4SAndrew Donnellan 	for_each_node_with_property(dn, "scom-controller") {
203bfd2f0d4SAndrew Donnellan 		chip = of_get_ibm_chip_id(dn);
204bfd2f0d4SAndrew Donnellan 		WARN_ON(chip == -1);
205bfd2f0d4SAndrew Donnellan 		rc |= scom_debug_init_one(root, dn, chip);
206bfd2f0d4SAndrew Donnellan 	}
207bfd2f0d4SAndrew Donnellan 
208bfd2f0d4SAndrew Donnellan 	return rc;
209bfd2f0d4SAndrew Donnellan }
210bfd2f0d4SAndrew Donnellan device_initcall(scom_debug_init);
211