aboutsummaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/hyp-debug.c
blob: da77afd1f1f6f44f8cdb4c29cfa6cb768e472c3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/slab.h>
#include <linux/io.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/debugfs.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <soc/qcom/subsystem_notif.h>
#include <soc/qcom/hvc.h>

#include "peripheral-loader.h"

#define hyp_dbg_err(fmt, ...)						\
	pr_err("%s: " fmt, "hyp-debug", ##__VA_ARGS__)
#define hyp_dbg_info(fmt, ...)						\
	pr_info("%s: " fmt, "hyp-debug", ##__VA_ARGS__)

#define HVC_FN_DBG_MAP_RANGE			HVC_FN_SIP(1)
#define HVC_FN_DBG_UNMAP_RANGE			HVC_FN_SIP(2)

#define MEM_PERM_EXECUTE			BIT(0)
#define MEM_PERM_WRITE				BIT(1)
#define MEM_PERM_READ				BIT(2)

#define MEM_CACHE_nGnRnE			0x0
#define MEM_CACHE_nGnRE				0x1
#define MEM_CACHE_nGRE				0x2
#define MEM_CACHE_GRE				0x3
#define MEM_CACHE_ONC_INC			0x5
#define MEM_CACHE_ONC_IWT			0x6
#define MEM_CACHE_ONC_IWB			0x7
#define MEM_CACHE_OWT_INC			0x9
#define MEM_CACHE_OWT_IWT			0xA
#define MEM_CACHE_OWT_IWB			0xB
#define MEM_CACHE_OWB_INC			0xD
#define MEM_CACHE_OWB_IWT			0xE
#define MEM_CACHE_OWB_IWB			0xF

#define MEM_SHARE_NS				0x0
#define MEM_SHARE_OS				0x2
#define MEM_SHARE_IS				0x3

static u64 mem_addr, mem_size, mem_perm_attr, mem_cache_attr, mem_share_attr;

static int hyp_debug_mem_addr_get(void *data, u64 *val)
{
	*val = mem_addr;
	return 0;
}

static int hyp_debug_mem_addr_set(void *data, u64 val)
{
	mem_addr = val;
	return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_addr_fops, hyp_debug_mem_addr_get,
			hyp_debug_mem_addr_set, "%llu\n");

static int hyp_debug_mem_size_get(void *data, u64 *val)
{
	*val = mem_size;
	return 0;
}

static int hyp_debug_mem_size_set(void *data, u64 val)
{
	mem_size = val;
	return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_size_fops, hyp_debug_mem_size_get,
			hyp_debug_mem_size_set, "%llu\n");

static int hyp_debug_mem_perm_attr_get(void *data, u64 *val)
{
	*val = mem_perm_attr;
	return 0;
}

static int hyp_debug_mem_perm_attr_set(void *data, u64 val)
{
	mem_perm_attr = val;
	return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_perm_attr_fops,
			hyp_debug_mem_perm_attr_get,
			hyp_debug_mem_perm_attr_set, "%llu\n");

static int hyp_debug_mem_cache_attr_get(void *data, u64 *val)
{
	*val = mem_cache_attr;
	return 0;
}

static int hyp_debug_mem_cache_attr_set(void *data, u64 val)
{
	mem_cache_attr = val;
	return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_cache_attr_fops,
			hyp_debug_mem_cache_attr_get,
			hyp_debug_mem_cache_attr_set, "%llu\n");

static int hyp_debug_mem_share_attr_get(void *data, u64 *val)
{
	*val = mem_share_attr;
	return 0;
}

static int hyp_debug_mem_share_attr_set(void *data, u64 val)
{
	mem_share_attr = val;
	return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_share_attr_fops,
			hyp_debug_mem_share_attr_get,
			hyp_debug_mem_share_attr_set, "%llu\n");

static int hyp_debug_mem_map_set(void *data, u64 val)
{
	struct hvc_desc desc = { {0}, {0} };
	int ret;

	desc.arg[0] = mem_addr;
	desc.arg[1] = mem_size;
	desc.arg[2] = mem_perm_attr;
	desc.arg[3] = mem_cache_attr;
	desc.arg[4] = mem_share_attr;
	ret = hvc(HVC_FN_DBG_MAP_RANGE, &desc);
	if (ret)
		hyp_dbg_err("user specified hvc map range failed: %d\n", ret);

	return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_map_fops, NULL, hyp_debug_mem_map_set,
			"%llu\n");

static int hyp_debug_mem_unmap_set(void *data, u64 val)
{
	struct hvc_desc desc = { {0}, {0} };
	int ret;

	desc.arg[0] = mem_addr;
	desc.arg[1] = mem_size;
	ret = hvc(HVC_FN_DBG_UNMAP_RANGE, &desc);
	if (ret)
		hyp_dbg_err("user specified hvc unmap range failed: %d\n", ret);

	return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(hyp_debug_mem_unmap_fops, NULL, hyp_debug_mem_unmap_set,
			"%llu\n");

struct restart_notifier_block {
	struct pil_image_info __iomem *pil_info;
	struct notifier_block nb;
};

static struct restart_notifier_block *restart_nbs;

static int restart_notifier_cb(struct notifier_block *this, unsigned long code,
			       void *data)
{
	struct restart_notifier_block *restart_nb;
	struct hvc_desc desc = { {0}, {0} };
	u64 addr;
	u32 size;
	int ret;

	restart_nb = container_of(this, struct restart_notifier_block, nb);

	switch (code) {
	case SUBSYS_AFTER_POWERUP:
		memcpy_fromio(&addr, &restart_nb->pil_info->start, sizeof(u64));
		size = readl_relaxed(&restart_nb->pil_info->size);
		desc.arg[0] = addr;
		desc.arg[1] = size;
		ret = hvc(HVC_FN_DBG_UNMAP_RANGE, &desc);
		if (ret)
			hyp_dbg_err("subsys hvc unmap range failed: %lu %d\n",
				    code, ret);
		break;
	case SUBSYS_BEFORE_SHUTDOWN:
		memcpy_fromio(&addr, &restart_nb->pil_info->start, sizeof(u64));
		size = readl_relaxed(&restart_nb->pil_info->size);
		desc.arg[0] = addr;
		desc.arg[1] = size;
		desc.arg[2] = MEM_PERM_EXECUTE | MEM_PERM_WRITE | MEM_PERM_READ;
		desc.arg[3] = MEM_CACHE_OWB_IWB;
		desc.arg[4] = MEM_SHARE_NS;
		ret = hvc(HVC_FN_DBG_MAP_RANGE, &desc);
		if (ret)
			hyp_dbg_err("subsys hvc map range failed: %lu %d\n",
				    code, ret);
		break;
	}

	return NOTIFY_DONE;
}

static int __init hyp_debug_init(void)
{
	struct device_node *np;
	struct resource res;
	struct dentry *debugfs_dir, *debugfs_file;
	char subsys_name[FIELD_SIZEOF(struct pil_image_info, name)];
	void __iomem *pil_info_base, __iomem *addr;
	void *handle;
	u32 nr_restart_nb;
	int i, ret;

	np = of_find_compatible_node(NULL, NULL, "qcom,msm-imem-pil");
	if (!np) {
		hyp_dbg_err("pil imem DT node does not exist\n");
		return -ENODEV;
	}

	ret = of_address_to_resource(np, 0, &res);
	if (ret)
		return ret;

	pil_info_base = ioremap(res.start, resource_size(&res));
	if (!pil_info_base) {
		hyp_dbg_err("pil info imem base offset mapping failed\n");
		return -ENOMEM;
	}

	nr_restart_nb = resource_size(&res) / sizeof(struct pil_image_info);
	restart_nbs = kzalloc(nr_restart_nb *
			     sizeof(struct restart_notifier_block), GFP_KERNEL);
	if (!restart_nbs) {
		ret = -ENOMEM;
		hyp_dbg_err("restart notifiers allocation failed\n");
		goto err0;
	}

	for (i = 0; i < nr_restart_nb; i++) {
		addr = pil_info_base + sizeof(struct pil_image_info) * i;
		restart_nbs[i].pil_info = (struct pil_image_info __iomem *)addr;

		memcpy_fromio(subsys_name, restart_nbs[i].pil_info->name,
			      sizeof(restart_nbs[i].pil_info->name));
		if (subsys_name[0] == '\0')
			break;

		restart_nbs[i].nb.notifier_call = restart_notifier_cb;
		handle = subsys_notif_register_notifier(subsys_name,
							&restart_nbs[i].nb);
		if (IS_ERR_OR_NULL(handle)) {
			ret = PTR_ERR(handle);
			hyp_dbg_err("subsys notif register %d failed: %d\n", i,
				    ret);
			goto err1;
		}
	}

	mem_perm_attr = MEM_PERM_EXECUTE | MEM_PERM_WRITE | MEM_PERM_READ;
	mem_cache_attr = MEM_CACHE_OWB_IWB;
	mem_share_attr = MEM_SHARE_NS;

	debugfs_dir = debugfs_create_dir("hyp_debug", NULL);
	if (IS_ERR_OR_NULL(debugfs_dir)) {
		ret = PTR_ERR(debugfs_dir);
		goto err1;
	}

	debugfs_file = debugfs_create_file("mem_addr", S_IRUGO, debugfs_dir,
					   NULL, &hyp_debug_mem_addr_fops);
	if (IS_ERR_OR_NULL(debugfs_file)) {
		ret = PTR_ERR(debugfs_file);
		goto err2;
	}

	debugfs_file = debugfs_create_file("mem_size", S_IRUGO, debugfs_dir,
					   NULL, &hyp_debug_mem_size_fops);
	if (IS_ERR_OR_NULL(debugfs_file)) {
		ret = PTR_ERR(debugfs_file);
		goto err2;
	}

	debugfs_file = debugfs_create_file("mem_perm_attr", S_IRUGO,
					   debugfs_dir, NULL,
					   &hyp_debug_mem_perm_attr_fops);
	if (IS_ERR_OR_NULL(debugfs_file)) {
		ret = PTR_ERR(debugfs_file);
		goto err2;
	}

	debugfs_file = debugfs_create_file("mem_cache_attr", S_IRUGO,
					   debugfs_dir, NULL,
					   &hyp_debug_mem_cache_attr_fops);
	if (IS_ERR_OR_NULL(debugfs_file)) {
		ret = PTR_ERR(debugfs_file);
		goto err2;
	}

	debugfs_file = debugfs_create_file("mem_share_attr", S_IRUGO,
					   debugfs_dir, NULL,
					   &hyp_debug_mem_share_attr_fops);
	if (IS_ERR_OR_NULL(debugfs_file)) {
		ret = PTR_ERR(debugfs_file);
		goto err2;
	}

	debugfs_file = debugfs_create_file("mem_map", S_IRUGO, debugfs_dir,
					   NULL, &hyp_debug_mem_map_fops);
	if (IS_ERR_OR_NULL(debugfs_file)) {
		ret = PTR_ERR(debugfs_file);
		goto err2;
	}

	debugfs_file = debugfs_create_file("mem_unmap", S_IRUGO, debugfs_dir,
					   NULL, &hyp_debug_mem_unmap_fops);
	if (IS_ERR_OR_NULL(debugfs_file)) {
		ret = PTR_ERR(debugfs_file);
		goto err2;
	}

	hyp_dbg_info("MSM Hyp Debug initialized\n");
	return 0;
err2:
	debugfs_remove_recursive(debugfs_dir);
err1:
	for (i--; i >= 0; i--)
		subsys_notif_unregister_notifier(handle, &restart_nbs[i].nb);
	kfree(restart_nbs);
err0:
	iounmap(pil_info_base);
	return ret;
}
late_initcall(hyp_debug_init);