summaryrefslogtreecommitdiff
path: root/lib/utils/irqchip/plic.c
blob: 8b2190f86ce1ca65ffd8e089ea8c8e6f68286a72 (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
/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2019 Western Digital Corporation or its affiliates.
 *
 * Authors:
 *   Anup Patel <anup.patel@wdc.com>
 *   Samuel Holland <samuel@sholland.org>
 */

#include <sbi/riscv_io.h>
#include <sbi/riscv_encoding.h>
#include <sbi/sbi_bitops.h>
#include <sbi/sbi_console.h>
#include <sbi/sbi_domain.h>
#include <sbi/sbi_error.h>
#include <sbi/sbi_heap.h>
#include <sbi/sbi_string.h>
#include <sbi_utils/irqchip/plic.h>

#define PLIC_PRIORITY_BASE 0x0
#define PLIC_PENDING_BASE 0x1000
#define PLIC_ENABLE_BASE 0x2000
#define PLIC_ENABLE_STRIDE 0x80
#define PLIC_CONTEXT_BASE 0x200000
#define PLIC_CONTEXT_STRIDE 0x1000

#define THEAD_PLIC_CTRL_REG 0x1ffffc

static unsigned long plic_ptr_offset;

#define plic_get_hart_data_ptr(__scratch)				\
	sbi_scratch_read_type((__scratch), void *, plic_ptr_offset)

#define plic_set_hart_data_ptr(__scratch, __plic)			\
	sbi_scratch_write_type((__scratch), void *, plic_ptr_offset, (__plic))

struct plic_data *plic_get(void)
{
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();

	return plic_get_hart_data_ptr(scratch);
}

static u32 plic_get_priority(const struct plic_data *plic, u32 source)
{
	volatile void *plic_priority = (char *)plic->addr +
			PLIC_PRIORITY_BASE + 4 * source;
	return readl(plic_priority);
}

static void plic_set_priority(const struct plic_data *plic, u32 source, u32 val)
{
	volatile void *plic_priority = (char *)plic->addr +
			PLIC_PRIORITY_BASE + 4 * source;
	writel(val, plic_priority);
}

static u32 plic_get_thresh(const struct plic_data *plic, u32 cntxid)
{
	volatile void *plic_thresh;

	plic_thresh = (char *)plic->addr +
		      PLIC_CONTEXT_BASE + PLIC_CONTEXT_STRIDE * cntxid;

	return readl(plic_thresh);
}

static void plic_set_thresh(const struct plic_data *plic, u32 cntxid, u32 val)
{
	volatile void *plic_thresh;

	plic_thresh = (char *)plic->addr +
		      PLIC_CONTEXT_BASE + PLIC_CONTEXT_STRIDE * cntxid;
	writel(val, plic_thresh);
}

static u32 plic_get_ie(const struct plic_data *plic, u32 cntxid,
		       u32 word_index)
{
	volatile void *plic_ie;

	plic_ie = (char *)plic->addr +
		   PLIC_ENABLE_BASE + PLIC_ENABLE_STRIDE * cntxid +
		   4 * word_index;

	return readl(plic_ie);
}

static void plic_set_ie(const struct plic_data *plic, u32 cntxid,
			u32 word_index, u32 val)
{
	volatile void *plic_ie;

	plic_ie = (char *)plic->addr +
		   PLIC_ENABLE_BASE + PLIC_ENABLE_STRIDE * cntxid +
		   4 * word_index;
	writel(val, plic_ie);
}

static void plic_delegate(const struct plic_data *plic)
{
	/* If this is a T-HEAD PLIC, delegate access to S-mode */
	if (plic->flags & PLIC_FLAG_THEAD_DELEGATION)
		writel_relaxed(BIT(0), (char *)plic->addr + THEAD_PLIC_CTRL_REG);
}

static int plic_context_init(const struct plic_data *plic, int context_id,
			     bool enable, u32 threshold)
{
	u32 ie_words, ie_value;

	if (!plic || context_id < 0)
		return SBI_EINVAL;

	ie_words = PLIC_IE_WORDS(plic);
	ie_value = enable ? 0xffffffffU : 0U;

	for (u32 i = 0; i < ie_words; i++)
		plic_set_ie(plic, context_id, i, ie_value);

	plic_set_thresh(plic, context_id, threshold);

	return 0;
}

void plic_suspend(void)
{
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
	const struct plic_data *plic = plic_get_hart_data_ptr(scratch);
	u32 ie_words = PLIC_IE_WORDS(plic);
	u32 *data_word = plic->pm_data;
	u8 *data_byte;

	if (!data_word)
		return;

	for (u32 h = 0; h <= sbi_scratch_last_hartindex(); h++) {
		u32 context_id = plic->context_map[h][PLIC_S_CONTEXT];

		if (context_id < 0)
			continue;

		/* Save the enable bits */
		for (u32 i = 0; i < ie_words; i++)
			*data_word++ = plic_get_ie(plic, context_id, i);

		/* Save the context threshold */
		*data_word++ = plic_get_thresh(plic, context_id);
	}

	/* Restore the input priorities */
	data_byte = (u8 *)data_word;
	for (u32 i = 1; i <= plic->num_src; i++)
		*data_byte++ = plic_get_priority(plic, i);
}

void plic_resume(void)
{
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
	const struct plic_data *plic = plic_get_hart_data_ptr(scratch);
	u32 ie_words = PLIC_IE_WORDS(plic);
	u32 *data_word = plic->pm_data;
	u8 *data_byte;

	if (!data_word)
		return;

	for (u32 h = 0; h <= sbi_scratch_last_hartindex(); h++) {
		u32 context_id = plic->context_map[h][PLIC_S_CONTEXT];

		if (context_id < 0)
			continue;

		/* Restore the enable bits */
		for (u32 i = 0; i < ie_words; i++)
			plic_set_ie(plic, context_id, i, *data_word++);

		/* Restore the context threshold */
		plic_set_thresh(plic, context_id, *data_word++);
	}

	/* Restore the input priorities */
	data_byte = (u8 *)data_word;
	for (u32 i = 1; i <= plic->num_src; i++)
		plic_set_priority(plic, i, *data_byte++);

	/* Restore the delegation */
	plic_delegate(plic);
}

static int plic_warm_irqchip_init(struct sbi_irqchip_device *dev)
{
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
	const struct plic_data *plic = plic_get_hart_data_ptr(scratch);
	u32 hartindex = current_hartindex();
	s16 m_cntx_id = plic->context_map[hartindex][PLIC_M_CONTEXT];
	s16 s_cntx_id = plic->context_map[hartindex][PLIC_S_CONTEXT];
	bool enable;
	int ret;

	/*
	 * By default, disable all IRQs for the target HART. Ariane
	 * has a bug which requires enabling all interrupts at boot.
	 */
	enable = plic->flags & PLIC_FLAG_ARIANE_BUG;

	if (m_cntx_id > -1) {
		ret = plic_context_init(plic, m_cntx_id, enable, 0x7);
		if (ret)
			return ret;
	}

	if (s_cntx_id > -1) {
		ret = plic_context_init(plic, s_cntx_id, enable, 0x7);
		if (ret)
			return ret;
	}

	return 0;
}

int plic_cold_irqchip_init(struct plic_data *plic)
{
	int i, ret;

	if (!plic)
		return SBI_EINVAL;

	if (!plic_ptr_offset) {
		plic_ptr_offset = sbi_scratch_alloc_type_offset(void *);
		if (!plic_ptr_offset)
			return SBI_ENOMEM;
	}

	if (plic->flags & PLIC_FLAG_ENABLE_PM) {
		unsigned long data_size = 0;

		for (u32 i = 0; i <= sbi_scratch_last_hartindex(); i++) {
			if (plic->context_map[i][PLIC_S_CONTEXT] < 0)
				continue;

			/* Allocate space for enable bits */
			data_size += (plic->num_src / 32 + 1) * sizeof(u32);

			/* Allocate space for the context threshold */
			data_size += sizeof(u32);
		}

		/*
		 * Allocate space for the input priorities. So far,
		 * priorities on all known implementations fit in 8 bits.
		 */
		data_size += plic->num_src * sizeof(u8);

		plic->pm_data = sbi_malloc(data_size);
		if (!plic->pm_data)
			return SBI_ENOMEM;
	}

	/* Configure default priorities of all IRQs */
	for (i = 1; i <= plic->num_src; i++)
		plic_set_priority(plic, i, 0);

	plic_delegate(plic);

	ret = sbi_domain_root_add_memrange(plic->addr, plic->size, BIT(20),
					(SBI_DOMAIN_MEMREGION_MMIO |
					 SBI_DOMAIN_MEMREGION_SHARED_SURW_MRW));
	if (ret)
		return ret;

	for (u32 i = 0; i <= sbi_scratch_last_hartindex(); i++) {
		if (plic->context_map[i][PLIC_M_CONTEXT] < 0 &&
		    plic->context_map[i][PLIC_S_CONTEXT] < 0)
			continue;

		plic_set_hart_data_ptr(sbi_hartindex_to_scratch(i), plic);
	}

	/* Register irqchip device */
	plic->irqchip.warm_init = plic_warm_irqchip_init;
	sbi_irqchip_add_device(&plic->irqchip);

	return 0;
}