1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * PowerNV SCOM bus debugfs interface
4 *
5 * Copyright 2010 Benjamin Herrenschmidt, IBM Corp
6 *                <benh@kernel.crashing.org>
7 *     and        David Gibson, IBM Corporation.
8 * Copyright 2013 IBM Corp.
9 */
10
11#include <linux/kernel.h>
12#include <linux/of.h>
13#include <linux/bug.h>
14#include <linux/gfp.h>
15#include <linux/slab.h>
16#include <linux/uaccess.h>
17#include <linux/debugfs.h>
18
19#include <asm/machdep.h>
20#include <asm/firmware.h>
21#include <asm/opal.h>
22#include <asm/prom.h>
23
24static u64 opal_scom_unmangle(u64 addr)
25{
26	u64 tmp;
27
28	/*
29	 * XSCOM addresses use the top nibble to set indirect mode and
30	 * its form.  Bits 4-11 are always 0.
31	 *
32	 * Because the debugfs interface uses signed offsets and shifts
33	 * the address left by 3, we basically cannot use the top 4 bits
34	 * of the 64-bit address, and thus cannot use the indirect bit.
35	 *
36	 * To deal with that, we support the indirect bits being in
37	 * bits 4-7 (IBM notation) instead of bit 0-3 in this API, we
38	 * do the conversion here.
39	 *
40	 * For in-kernel use, we don't need to do this mangling.  In
41	 * kernel won't have bits 4-7 set.
42	 *
43	 * So:
44	 *   debugfs will always   set 0-3 = 0 and clear 4-7
45	 *    kernel will always clear 0-3 = 0 and   set 4-7
46	 */
47	tmp = addr;
48	tmp  &= 0x0f00000000000000;
49	addr &= 0xf0ffffffffffffff;
50	addr |= tmp << 4;
51
52	return addr;
53}
54
55static int opal_scom_read(uint32_t chip, uint64_t addr, u64 reg, u64 *value)
56{
57	int64_t rc;
58	__be64 v;
59
60	reg = opal_scom_unmangle(addr + reg);
61	rc = opal_xscom_read(chip, reg, (__be64 *)__pa(&v));
62	if (rc) {
63		*value = 0xfffffffffffffffful;
64		return -EIO;
65	}
66	*value = be64_to_cpu(v);
67	return 0;
68}
69
70static int opal_scom_write(uint32_t chip, uint64_t addr, u64 reg, u64 value)
71{
72	int64_t rc;
73
74	reg = opal_scom_unmangle(addr + reg);
75	rc = opal_xscom_write(chip, reg, value);
76	if (rc)
77		return -EIO;
78	return 0;
79}
80
81struct scom_debug_entry {
82	u32 chip;
83	struct debugfs_blob_wrapper path;
84	char name[16];
85};
86
87static ssize_t scom_debug_read(struct file *filp, char __user *ubuf,
88			       size_t count, loff_t *ppos)
89{
90	struct scom_debug_entry *ent = filp->private_data;
91	u64 __user *ubuf64 = (u64 __user *)ubuf;
92	loff_t off = *ppos;
93	ssize_t done = 0;
94	u64 reg, reg_base, reg_cnt, val;
95	int rc;
96
97	if (off < 0 || (off & 7) || (count & 7))
98		return -EINVAL;
99	reg_base = off >> 3;
100	reg_cnt = count >> 3;
101
102	for (reg = 0; reg < reg_cnt; reg++) {
103		rc = opal_scom_read(ent->chip, reg_base, reg, &val);
104		if (!rc)
105			rc = put_user(val, ubuf64);
106		if (rc) {
107			if (!done)
108				done = rc;
109			break;
110		}
111		ubuf64++;
112		*ppos += 8;
113		done += 8;
114	}
115	return done;
116}
117
118static ssize_t scom_debug_write(struct file *filp, const char __user *ubuf,
119				size_t count, loff_t *ppos)
120{
121	struct scom_debug_entry *ent = filp->private_data;
122	u64 __user *ubuf64 = (u64 __user *)ubuf;
123	loff_t off = *ppos;
124	ssize_t done = 0;
125	u64 reg, reg_base, reg_cnt, val;
126	int rc;
127
128	if (off < 0 || (off & 7) || (count & 7))
129		return -EINVAL;
130	reg_base = off >> 3;
131	reg_cnt = count >> 3;
132
133	for (reg = 0; reg < reg_cnt; reg++) {
134		rc = get_user(val, ubuf64);
135		if (!rc)
136			rc = opal_scom_write(ent->chip, reg_base, reg,  val);
137		if (rc) {
138			if (!done)
139				done = rc;
140			break;
141		}
142		ubuf64++;
143		done += 8;
144	}
145	return done;
146}
147
148static const struct file_operations scom_debug_fops = {
149	.read =		scom_debug_read,
150	.write =	scom_debug_write,
151	.open =		simple_open,
152	.llseek =	default_llseek,
153};
154
155static int scom_debug_init_one(struct dentry *root, struct device_node *dn,
156			       int chip)
157{
158	struct scom_debug_entry *ent;
159	struct dentry *dir;
160
161	ent = kzalloc(sizeof(*ent), GFP_KERNEL);
162	if (!ent)
163		return -ENOMEM;
164
165	ent->chip = chip;
166	snprintf(ent->name, 16, "%08x", chip);
167	ent->path.data = (void *)kasprintf(GFP_KERNEL, "%pOF", dn);
168	if (!ent->path.data) {
169		kfree(ent);
170		return -ENOMEM;
171	}
172
173	ent->path.size = strlen((char *)ent->path.data);
174
175	dir = debugfs_create_dir(ent->name, root);
176	if (IS_ERR(dir)) {
177		kfree(ent->path.data);
178		kfree(ent);
179		return -1;
180	}
181
182	debugfs_create_blob("devspec", 0400, dir, &ent->path);
183	debugfs_create_file("access", 0600, dir, ent, &scom_debug_fops);
184
185	return 0;
186}
187
188static int scom_debug_init(void)
189{
190	struct device_node *dn;
191	struct dentry *root;
192	int chip, rc;
193
194	if (!firmware_has_feature(FW_FEATURE_OPAL))
195		return 0;
196
197	root = debugfs_create_dir("scom", arch_debugfs_dir);
198	if (IS_ERR(root))
199		return -1;
200
201	rc = 0;
202	for_each_node_with_property(dn, "scom-controller") {
203		chip = of_get_ibm_chip_id(dn);
204		WARN_ON(chip == -1);
205		rc |= scom_debug_init_one(root, dn, chip);
206	}
207
208	return rc;
209}
210device_initcall(scom_debug_init);
211