xref: /openbmc/linux/drivers/firmware/arm_sdei.c (revision ad6eb31ef90355993eb55ff77e0e855ae7d91e4c)
1*ad6eb31eSJames Morse // SPDX-License-Identifier: GPL-2.0
2*ad6eb31eSJames Morse // Copyright (C) 2017 Arm Ltd.
3*ad6eb31eSJames Morse #define pr_fmt(fmt) "sdei: " fmt
4*ad6eb31eSJames Morse 
5*ad6eb31eSJames Morse #include <linux/acpi.h>
6*ad6eb31eSJames Morse #include <linux/arm_sdei.h>
7*ad6eb31eSJames Morse #include <linux/arm-smccc.h>
8*ad6eb31eSJames Morse #include <linux/bitops.h>
9*ad6eb31eSJames Morse #include <linux/compiler.h>
10*ad6eb31eSJames Morse #include <linux/errno.h>
11*ad6eb31eSJames Morse #include <linux/hardirq.h>
12*ad6eb31eSJames Morse #include <linux/kernel.h>
13*ad6eb31eSJames Morse #include <linux/kprobes.h>
14*ad6eb31eSJames Morse #include <linux/kvm_host.h>
15*ad6eb31eSJames Morse #include <linux/list.h>
16*ad6eb31eSJames Morse #include <linux/mutex.h>
17*ad6eb31eSJames Morse #include <linux/of.h>
18*ad6eb31eSJames Morse #include <linux/of_platform.h>
19*ad6eb31eSJames Morse #include <linux/percpu.h>
20*ad6eb31eSJames Morse #include <linux/platform_device.h>
21*ad6eb31eSJames Morse #include <linux/ptrace.h>
22*ad6eb31eSJames Morse #include <linux/preempt.h>
23*ad6eb31eSJames Morse #include <linux/slab.h>
24*ad6eb31eSJames Morse #include <linux/smp.h>
25*ad6eb31eSJames Morse #include <linux/spinlock.h>
26*ad6eb31eSJames Morse #include <linux/uaccess.h>
27*ad6eb31eSJames Morse 
28*ad6eb31eSJames Morse /*
29*ad6eb31eSJames Morse  * The call to use to reach the firmware.
30*ad6eb31eSJames Morse  */
31*ad6eb31eSJames Morse static asmlinkage void (*sdei_firmware_call)(unsigned long function_id,
32*ad6eb31eSJames Morse 		      unsigned long arg0, unsigned long arg1,
33*ad6eb31eSJames Morse 		      unsigned long arg2, unsigned long arg3,
34*ad6eb31eSJames Morse 		      unsigned long arg4, struct arm_smccc_res *res);
35*ad6eb31eSJames Morse 
36*ad6eb31eSJames Morse /* entry point from firmware to arch asm code */
37*ad6eb31eSJames Morse static unsigned long sdei_entry_point;
38*ad6eb31eSJames Morse 
39*ad6eb31eSJames Morse struct sdei_event {
40*ad6eb31eSJames Morse 	struct list_head	list;
41*ad6eb31eSJames Morse 	u32			event_num;
42*ad6eb31eSJames Morse 	u8			type;
43*ad6eb31eSJames Morse 	u8			priority;
44*ad6eb31eSJames Morse 
45*ad6eb31eSJames Morse 	/* This pointer is handed to firmware as the event argument. */
46*ad6eb31eSJames Morse 	struct sdei_registered_event *registered;
47*ad6eb31eSJames Morse };
48*ad6eb31eSJames Morse 
49*ad6eb31eSJames Morse /* Take the mutex for any API call or modification. Take the mutex first. */
50*ad6eb31eSJames Morse static DEFINE_MUTEX(sdei_events_lock);
51*ad6eb31eSJames Morse 
52*ad6eb31eSJames Morse /* and then hold this when modifying the list */
53*ad6eb31eSJames Morse static DEFINE_SPINLOCK(sdei_list_lock);
54*ad6eb31eSJames Morse static LIST_HEAD(sdei_list);
55*ad6eb31eSJames Morse 
56*ad6eb31eSJames Morse static int sdei_to_linux_errno(unsigned long sdei_err)
57*ad6eb31eSJames Morse {
58*ad6eb31eSJames Morse 	switch (sdei_err) {
59*ad6eb31eSJames Morse 	case SDEI_NOT_SUPPORTED:
60*ad6eb31eSJames Morse 		return -EOPNOTSUPP;
61*ad6eb31eSJames Morse 	case SDEI_INVALID_PARAMETERS:
62*ad6eb31eSJames Morse 		return -EINVAL;
63*ad6eb31eSJames Morse 	case SDEI_DENIED:
64*ad6eb31eSJames Morse 		return -EPERM;
65*ad6eb31eSJames Morse 	case SDEI_PENDING:
66*ad6eb31eSJames Morse 		return -EINPROGRESS;
67*ad6eb31eSJames Morse 	case SDEI_OUT_OF_RESOURCE:
68*ad6eb31eSJames Morse 		return -ENOMEM;
69*ad6eb31eSJames Morse 	}
70*ad6eb31eSJames Morse 
71*ad6eb31eSJames Morse 	/* Not an error value ... */
72*ad6eb31eSJames Morse 	return sdei_err;
73*ad6eb31eSJames Morse }
74*ad6eb31eSJames Morse 
75*ad6eb31eSJames Morse /*
76*ad6eb31eSJames Morse  * If x0 is any of these values, then the call failed, use sdei_to_linux_errno()
77*ad6eb31eSJames Morse  * to translate.
78*ad6eb31eSJames Morse  */
79*ad6eb31eSJames Morse static int sdei_is_err(struct arm_smccc_res *res)
80*ad6eb31eSJames Morse {
81*ad6eb31eSJames Morse 	switch (res->a0) {
82*ad6eb31eSJames Morse 	case SDEI_NOT_SUPPORTED:
83*ad6eb31eSJames Morse 	case SDEI_INVALID_PARAMETERS:
84*ad6eb31eSJames Morse 	case SDEI_DENIED:
85*ad6eb31eSJames Morse 	case SDEI_PENDING:
86*ad6eb31eSJames Morse 	case SDEI_OUT_OF_RESOURCE:
87*ad6eb31eSJames Morse 		return true;
88*ad6eb31eSJames Morse 	}
89*ad6eb31eSJames Morse 
90*ad6eb31eSJames Morse 	return false;
91*ad6eb31eSJames Morse }
92*ad6eb31eSJames Morse 
93*ad6eb31eSJames Morse static int invoke_sdei_fn(unsigned long function_id, unsigned long arg0,
94*ad6eb31eSJames Morse 			  unsigned long arg1, unsigned long arg2,
95*ad6eb31eSJames Morse 			  unsigned long arg3, unsigned long arg4,
96*ad6eb31eSJames Morse 			  u64 *result)
97*ad6eb31eSJames Morse {
98*ad6eb31eSJames Morse 	int err = 0;
99*ad6eb31eSJames Morse 	struct arm_smccc_res res;
100*ad6eb31eSJames Morse 
101*ad6eb31eSJames Morse 	if (sdei_firmware_call) {
102*ad6eb31eSJames Morse 		sdei_firmware_call(function_id, arg0, arg1, arg2, arg3, arg4,
103*ad6eb31eSJames Morse 				   &res);
104*ad6eb31eSJames Morse 		if (sdei_is_err(&res))
105*ad6eb31eSJames Morse 			err = sdei_to_linux_errno(res.a0);
106*ad6eb31eSJames Morse 	} else {
107*ad6eb31eSJames Morse 		/*
108*ad6eb31eSJames Morse 		 * !sdei_firmware_call means we failed to probe or called
109*ad6eb31eSJames Morse 		 * sdei_mark_interface_broken(). -EIO is not an error returned
110*ad6eb31eSJames Morse 		 * by sdei_to_linux_errno() and is used to suppress messages
111*ad6eb31eSJames Morse 		 * from this driver.
112*ad6eb31eSJames Morse 		 */
113*ad6eb31eSJames Morse 		err = -EIO;
114*ad6eb31eSJames Morse 		res.a0 = SDEI_NOT_SUPPORTED;
115*ad6eb31eSJames Morse 	}
116*ad6eb31eSJames Morse 
117*ad6eb31eSJames Morse 	if (result)
118*ad6eb31eSJames Morse 		*result = res.a0;
119*ad6eb31eSJames Morse 
120*ad6eb31eSJames Morse 	return err;
121*ad6eb31eSJames Morse }
122*ad6eb31eSJames Morse 
123*ad6eb31eSJames Morse static struct sdei_event *sdei_event_find(u32 event_num)
124*ad6eb31eSJames Morse {
125*ad6eb31eSJames Morse 	struct sdei_event *e, *found = NULL;
126*ad6eb31eSJames Morse 
127*ad6eb31eSJames Morse 	lockdep_assert_held(&sdei_events_lock);
128*ad6eb31eSJames Morse 
129*ad6eb31eSJames Morse 	spin_lock(&sdei_list_lock);
130*ad6eb31eSJames Morse 	list_for_each_entry(e, &sdei_list, list) {
131*ad6eb31eSJames Morse 		if (e->event_num == event_num) {
132*ad6eb31eSJames Morse 			found = e;
133*ad6eb31eSJames Morse 			break;
134*ad6eb31eSJames Morse 		}
135*ad6eb31eSJames Morse 	}
136*ad6eb31eSJames Morse 	spin_unlock(&sdei_list_lock);
137*ad6eb31eSJames Morse 
138*ad6eb31eSJames Morse 	return found;
139*ad6eb31eSJames Morse }
140*ad6eb31eSJames Morse 
141*ad6eb31eSJames Morse int sdei_api_event_context(u32 query, u64 *result)
142*ad6eb31eSJames Morse {
143*ad6eb31eSJames Morse 	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_CONTEXT, query, 0, 0, 0, 0,
144*ad6eb31eSJames Morse 			      result);
145*ad6eb31eSJames Morse }
146*ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_api_event_context);
147*ad6eb31eSJames Morse 
148*ad6eb31eSJames Morse static int sdei_api_event_get_info(u32 event, u32 info, u64 *result)
149*ad6eb31eSJames Morse {
150*ad6eb31eSJames Morse 	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_GET_INFO, event, info, 0,
151*ad6eb31eSJames Morse 			      0, 0, result);
152*ad6eb31eSJames Morse }
153*ad6eb31eSJames Morse 
154*ad6eb31eSJames Morse static struct sdei_event *sdei_event_create(u32 event_num,
155*ad6eb31eSJames Morse 					    sdei_event_callback *cb,
156*ad6eb31eSJames Morse 					    void *cb_arg)
157*ad6eb31eSJames Morse {
158*ad6eb31eSJames Morse 	int err;
159*ad6eb31eSJames Morse 	u64 result;
160*ad6eb31eSJames Morse 	struct sdei_event *event;
161*ad6eb31eSJames Morse 	struct sdei_registered_event *reg;
162*ad6eb31eSJames Morse 
163*ad6eb31eSJames Morse 	lockdep_assert_held(&sdei_events_lock);
164*ad6eb31eSJames Morse 
165*ad6eb31eSJames Morse 	event = kzalloc(sizeof(*event), GFP_KERNEL);
166*ad6eb31eSJames Morse 	if (!event)
167*ad6eb31eSJames Morse 		return ERR_PTR(-ENOMEM);
168*ad6eb31eSJames Morse 
169*ad6eb31eSJames Morse 	INIT_LIST_HEAD(&event->list);
170*ad6eb31eSJames Morse 	event->event_num = event_num;
171*ad6eb31eSJames Morse 
172*ad6eb31eSJames Morse 	err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_PRIORITY,
173*ad6eb31eSJames Morse 				      &result);
174*ad6eb31eSJames Morse 	if (err) {
175*ad6eb31eSJames Morse 		kfree(event);
176*ad6eb31eSJames Morse 		return ERR_PTR(err);
177*ad6eb31eSJames Morse 	}
178*ad6eb31eSJames Morse 	event->priority = result;
179*ad6eb31eSJames Morse 
180*ad6eb31eSJames Morse 	err = sdei_api_event_get_info(event_num, SDEI_EVENT_INFO_EV_TYPE,
181*ad6eb31eSJames Morse 				      &result);
182*ad6eb31eSJames Morse 	if (err) {
183*ad6eb31eSJames Morse 		kfree(event);
184*ad6eb31eSJames Morse 		return ERR_PTR(err);
185*ad6eb31eSJames Morse 	}
186*ad6eb31eSJames Morse 	event->type = result;
187*ad6eb31eSJames Morse 
188*ad6eb31eSJames Morse 	if (event->type == SDEI_EVENT_TYPE_SHARED) {
189*ad6eb31eSJames Morse 		reg = kzalloc(sizeof(*reg), GFP_KERNEL);
190*ad6eb31eSJames Morse 		if (!reg) {
191*ad6eb31eSJames Morse 			kfree(event);
192*ad6eb31eSJames Morse 			return ERR_PTR(-ENOMEM);
193*ad6eb31eSJames Morse 		}
194*ad6eb31eSJames Morse 
195*ad6eb31eSJames Morse 		reg->event_num = event_num;
196*ad6eb31eSJames Morse 		reg->priority = event->priority;
197*ad6eb31eSJames Morse 
198*ad6eb31eSJames Morse 		reg->callback = cb;
199*ad6eb31eSJames Morse 		reg->callback_arg = cb_arg;
200*ad6eb31eSJames Morse 		event->registered = reg;
201*ad6eb31eSJames Morse 	}
202*ad6eb31eSJames Morse 
203*ad6eb31eSJames Morse 	if (sdei_event_find(event_num)) {
204*ad6eb31eSJames Morse 		kfree(event->registered);
205*ad6eb31eSJames Morse 		kfree(event);
206*ad6eb31eSJames Morse 		event = ERR_PTR(-EBUSY);
207*ad6eb31eSJames Morse 	} else {
208*ad6eb31eSJames Morse 		spin_lock(&sdei_list_lock);
209*ad6eb31eSJames Morse 		list_add(&event->list, &sdei_list);
210*ad6eb31eSJames Morse 		spin_unlock(&sdei_list_lock);
211*ad6eb31eSJames Morse 	}
212*ad6eb31eSJames Morse 
213*ad6eb31eSJames Morse 	return event;
214*ad6eb31eSJames Morse }
215*ad6eb31eSJames Morse 
216*ad6eb31eSJames Morse static void sdei_event_destroy(struct sdei_event *event)
217*ad6eb31eSJames Morse {
218*ad6eb31eSJames Morse 	lockdep_assert_held(&sdei_events_lock);
219*ad6eb31eSJames Morse 
220*ad6eb31eSJames Morse 	spin_lock(&sdei_list_lock);
221*ad6eb31eSJames Morse 	list_del(&event->list);
222*ad6eb31eSJames Morse 	spin_unlock(&sdei_list_lock);
223*ad6eb31eSJames Morse 
224*ad6eb31eSJames Morse 	if (event->type == SDEI_EVENT_TYPE_SHARED)
225*ad6eb31eSJames Morse 		kfree(event->registered);
226*ad6eb31eSJames Morse 
227*ad6eb31eSJames Morse 	kfree(event);
228*ad6eb31eSJames Morse }
229*ad6eb31eSJames Morse 
230*ad6eb31eSJames Morse static int sdei_api_get_version(u64 *version)
231*ad6eb31eSJames Morse {
232*ad6eb31eSJames Morse 	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_VERSION, 0, 0, 0, 0, 0, version);
233*ad6eb31eSJames Morse }
234*ad6eb31eSJames Morse 
235*ad6eb31eSJames Morse int sdei_mask_local_cpu(void)
236*ad6eb31eSJames Morse {
237*ad6eb31eSJames Morse 	int err;
238*ad6eb31eSJames Morse 
239*ad6eb31eSJames Morse 	WARN_ON_ONCE(preemptible());
240*ad6eb31eSJames Morse 
241*ad6eb31eSJames Morse 	err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_MASK, 0, 0, 0, 0, 0, NULL);
242*ad6eb31eSJames Morse 	if (err && err != -EIO) {
243*ad6eb31eSJames Morse 		pr_warn_once("failed to mask CPU[%u]: %d\n",
244*ad6eb31eSJames Morse 			      smp_processor_id(), err);
245*ad6eb31eSJames Morse 		return err;
246*ad6eb31eSJames Morse 	}
247*ad6eb31eSJames Morse 
248*ad6eb31eSJames Morse 	return 0;
249*ad6eb31eSJames Morse }
250*ad6eb31eSJames Morse 
251*ad6eb31eSJames Morse static void _ipi_mask_cpu(void *ignored)
252*ad6eb31eSJames Morse {
253*ad6eb31eSJames Morse 	sdei_mask_local_cpu();
254*ad6eb31eSJames Morse }
255*ad6eb31eSJames Morse 
256*ad6eb31eSJames Morse int sdei_unmask_local_cpu(void)
257*ad6eb31eSJames Morse {
258*ad6eb31eSJames Morse 	int err;
259*ad6eb31eSJames Morse 
260*ad6eb31eSJames Morse 	WARN_ON_ONCE(preemptible());
261*ad6eb31eSJames Morse 
262*ad6eb31eSJames Morse 	err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PE_UNMASK, 0, 0, 0, 0, 0, NULL);
263*ad6eb31eSJames Morse 	if (err && err != -EIO) {
264*ad6eb31eSJames Morse 		pr_warn_once("failed to unmask CPU[%u]: %d\n",
265*ad6eb31eSJames Morse 			     smp_processor_id(), err);
266*ad6eb31eSJames Morse 		return err;
267*ad6eb31eSJames Morse 	}
268*ad6eb31eSJames Morse 
269*ad6eb31eSJames Morse 	return 0;
270*ad6eb31eSJames Morse }
271*ad6eb31eSJames Morse 
272*ad6eb31eSJames Morse static void _ipi_unmask_cpu(void *ignored)
273*ad6eb31eSJames Morse {
274*ad6eb31eSJames Morse 	sdei_unmask_local_cpu();
275*ad6eb31eSJames Morse }
276*ad6eb31eSJames Morse 
277*ad6eb31eSJames Morse static void _ipi_private_reset(void *ignored)
278*ad6eb31eSJames Morse {
279*ad6eb31eSJames Morse 	int err;
280*ad6eb31eSJames Morse 
281*ad6eb31eSJames Morse 	err = invoke_sdei_fn(SDEI_1_0_FN_SDEI_PRIVATE_RESET, 0, 0, 0, 0, 0,
282*ad6eb31eSJames Morse 			     NULL);
283*ad6eb31eSJames Morse 	if (err && err != -EIO)
284*ad6eb31eSJames Morse 		pr_warn_once("failed to reset CPU[%u]: %d\n",
285*ad6eb31eSJames Morse 			     smp_processor_id(), err);
286*ad6eb31eSJames Morse }
287*ad6eb31eSJames Morse 
288*ad6eb31eSJames Morse static int sdei_api_shared_reset(void)
289*ad6eb31eSJames Morse {
290*ad6eb31eSJames Morse 	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_SHARED_RESET, 0, 0, 0, 0, 0,
291*ad6eb31eSJames Morse 			      NULL);
292*ad6eb31eSJames Morse }
293*ad6eb31eSJames Morse 
294*ad6eb31eSJames Morse static void sdei_mark_interface_broken(void)
295*ad6eb31eSJames Morse {
296*ad6eb31eSJames Morse 	pr_err("disabling SDEI firmware interface\n");
297*ad6eb31eSJames Morse 	on_each_cpu(&_ipi_mask_cpu, NULL, true);
298*ad6eb31eSJames Morse 	sdei_firmware_call = NULL;
299*ad6eb31eSJames Morse }
300*ad6eb31eSJames Morse 
301*ad6eb31eSJames Morse static int sdei_platform_reset(void)
302*ad6eb31eSJames Morse {
303*ad6eb31eSJames Morse 	int err;
304*ad6eb31eSJames Morse 
305*ad6eb31eSJames Morse 	on_each_cpu(&_ipi_private_reset, NULL, true);
306*ad6eb31eSJames Morse 	err = sdei_api_shared_reset();
307*ad6eb31eSJames Morse 	if (err) {
308*ad6eb31eSJames Morse 		pr_err("Failed to reset platform: %d\n", err);
309*ad6eb31eSJames Morse 		sdei_mark_interface_broken();
310*ad6eb31eSJames Morse 	}
311*ad6eb31eSJames Morse 
312*ad6eb31eSJames Morse 	return err;
313*ad6eb31eSJames Morse }
314*ad6eb31eSJames Morse 
315*ad6eb31eSJames Morse static int sdei_api_event_enable(u32 event_num)
316*ad6eb31eSJames Morse {
317*ad6eb31eSJames Morse 	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_ENABLE, event_num, 0, 0, 0,
318*ad6eb31eSJames Morse 			      0, NULL);
319*ad6eb31eSJames Morse }
320*ad6eb31eSJames Morse 
321*ad6eb31eSJames Morse int sdei_event_enable(u32 event_num)
322*ad6eb31eSJames Morse {
323*ad6eb31eSJames Morse 	int err = -EINVAL;
324*ad6eb31eSJames Morse 	struct sdei_event *event;
325*ad6eb31eSJames Morse 
326*ad6eb31eSJames Morse 	mutex_lock(&sdei_events_lock);
327*ad6eb31eSJames Morse 	event = sdei_event_find(event_num);
328*ad6eb31eSJames Morse 	if (!event) {
329*ad6eb31eSJames Morse 		mutex_unlock(&sdei_events_lock);
330*ad6eb31eSJames Morse 		return -ENOENT;
331*ad6eb31eSJames Morse 	}
332*ad6eb31eSJames Morse 
333*ad6eb31eSJames Morse 	if (event->type == SDEI_EVENT_TYPE_SHARED)
334*ad6eb31eSJames Morse 		err = sdei_api_event_enable(event->event_num);
335*ad6eb31eSJames Morse 	mutex_unlock(&sdei_events_lock);
336*ad6eb31eSJames Morse 
337*ad6eb31eSJames Morse 	return err;
338*ad6eb31eSJames Morse }
339*ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_enable);
340*ad6eb31eSJames Morse 
341*ad6eb31eSJames Morse static int sdei_api_event_disable(u32 event_num)
342*ad6eb31eSJames Morse {
343*ad6eb31eSJames Morse 	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_DISABLE, event_num, 0, 0,
344*ad6eb31eSJames Morse 			      0, 0, NULL);
345*ad6eb31eSJames Morse }
346*ad6eb31eSJames Morse 
347*ad6eb31eSJames Morse int sdei_event_disable(u32 event_num)
348*ad6eb31eSJames Morse {
349*ad6eb31eSJames Morse 	int err = -EINVAL;
350*ad6eb31eSJames Morse 	struct sdei_event *event;
351*ad6eb31eSJames Morse 
352*ad6eb31eSJames Morse 	mutex_lock(&sdei_events_lock);
353*ad6eb31eSJames Morse 	event = sdei_event_find(event_num);
354*ad6eb31eSJames Morse 	if (!event) {
355*ad6eb31eSJames Morse 		mutex_unlock(&sdei_events_lock);
356*ad6eb31eSJames Morse 		return -ENOENT;
357*ad6eb31eSJames Morse 	}
358*ad6eb31eSJames Morse 
359*ad6eb31eSJames Morse 	if (event->type == SDEI_EVENT_TYPE_SHARED)
360*ad6eb31eSJames Morse 		err = sdei_api_event_disable(event->event_num);
361*ad6eb31eSJames Morse 	mutex_unlock(&sdei_events_lock);
362*ad6eb31eSJames Morse 
363*ad6eb31eSJames Morse 	return err;
364*ad6eb31eSJames Morse }
365*ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_disable);
366*ad6eb31eSJames Morse 
367*ad6eb31eSJames Morse static int sdei_api_event_unregister(u32 event_num)
368*ad6eb31eSJames Morse {
369*ad6eb31eSJames Morse 	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_UNREGISTER, event_num, 0,
370*ad6eb31eSJames Morse 			      0, 0, 0, NULL);
371*ad6eb31eSJames Morse }
372*ad6eb31eSJames Morse 
373*ad6eb31eSJames Morse static int _sdei_event_unregister(struct sdei_event *event)
374*ad6eb31eSJames Morse {
375*ad6eb31eSJames Morse 	lockdep_assert_held(&sdei_events_lock);
376*ad6eb31eSJames Morse 
377*ad6eb31eSJames Morse 	if (event->type == SDEI_EVENT_TYPE_SHARED)
378*ad6eb31eSJames Morse 		return sdei_api_event_unregister(event->event_num);
379*ad6eb31eSJames Morse 
380*ad6eb31eSJames Morse 	return -EINVAL;
381*ad6eb31eSJames Morse }
382*ad6eb31eSJames Morse 
383*ad6eb31eSJames Morse int sdei_event_unregister(u32 event_num)
384*ad6eb31eSJames Morse {
385*ad6eb31eSJames Morse 	int err;
386*ad6eb31eSJames Morse 	struct sdei_event *event;
387*ad6eb31eSJames Morse 
388*ad6eb31eSJames Morse 	WARN_ON(in_nmi());
389*ad6eb31eSJames Morse 
390*ad6eb31eSJames Morse 	mutex_lock(&sdei_events_lock);
391*ad6eb31eSJames Morse 	event = sdei_event_find(event_num);
392*ad6eb31eSJames Morse 	do {
393*ad6eb31eSJames Morse 		if (!event) {
394*ad6eb31eSJames Morse 			pr_warn("Event %u not registered\n", event_num);
395*ad6eb31eSJames Morse 			err = -ENOENT;
396*ad6eb31eSJames Morse 			break;
397*ad6eb31eSJames Morse 		}
398*ad6eb31eSJames Morse 
399*ad6eb31eSJames Morse 		err = _sdei_event_unregister(event);
400*ad6eb31eSJames Morse 		if (err)
401*ad6eb31eSJames Morse 			break;
402*ad6eb31eSJames Morse 
403*ad6eb31eSJames Morse 		sdei_event_destroy(event);
404*ad6eb31eSJames Morse 	} while (0);
405*ad6eb31eSJames Morse 	mutex_unlock(&sdei_events_lock);
406*ad6eb31eSJames Morse 
407*ad6eb31eSJames Morse 	return err;
408*ad6eb31eSJames Morse }
409*ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_unregister);
410*ad6eb31eSJames Morse 
411*ad6eb31eSJames Morse static int sdei_api_event_register(u32 event_num, unsigned long entry_point,
412*ad6eb31eSJames Morse 				   void *arg, u64 flags, u64 affinity)
413*ad6eb31eSJames Morse {
414*ad6eb31eSJames Morse 	return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_REGISTER, event_num,
415*ad6eb31eSJames Morse 			      (unsigned long)entry_point, (unsigned long)arg,
416*ad6eb31eSJames Morse 			      flags, affinity, NULL);
417*ad6eb31eSJames Morse }
418*ad6eb31eSJames Morse 
419*ad6eb31eSJames Morse static int _sdei_event_register(struct sdei_event *event)
420*ad6eb31eSJames Morse {
421*ad6eb31eSJames Morse 	lockdep_assert_held(&sdei_events_lock);
422*ad6eb31eSJames Morse 
423*ad6eb31eSJames Morse 	if (event->type == SDEI_EVENT_TYPE_SHARED)
424*ad6eb31eSJames Morse 		return sdei_api_event_register(event->event_num,
425*ad6eb31eSJames Morse 					       sdei_entry_point,
426*ad6eb31eSJames Morse 					       event->registered,
427*ad6eb31eSJames Morse 					       SDEI_EVENT_REGISTER_RM_ANY, 0);
428*ad6eb31eSJames Morse 
429*ad6eb31eSJames Morse 	return -EINVAL;
430*ad6eb31eSJames Morse }
431*ad6eb31eSJames Morse 
432*ad6eb31eSJames Morse int sdei_event_register(u32 event_num, sdei_event_callback *cb, void *arg)
433*ad6eb31eSJames Morse {
434*ad6eb31eSJames Morse 	int err;
435*ad6eb31eSJames Morse 	struct sdei_event *event;
436*ad6eb31eSJames Morse 
437*ad6eb31eSJames Morse 	WARN_ON(in_nmi());
438*ad6eb31eSJames Morse 
439*ad6eb31eSJames Morse 	mutex_lock(&sdei_events_lock);
440*ad6eb31eSJames Morse 	do {
441*ad6eb31eSJames Morse 		if (sdei_event_find(event_num)) {
442*ad6eb31eSJames Morse 			pr_warn("Event %u already registered\n", event_num);
443*ad6eb31eSJames Morse 			err = -EBUSY;
444*ad6eb31eSJames Morse 			break;
445*ad6eb31eSJames Morse 		}
446*ad6eb31eSJames Morse 
447*ad6eb31eSJames Morse 		event = sdei_event_create(event_num, cb, arg);
448*ad6eb31eSJames Morse 		if (IS_ERR(event)) {
449*ad6eb31eSJames Morse 			err = PTR_ERR(event);
450*ad6eb31eSJames Morse 			pr_warn("Failed to create event %u: %d\n", event_num,
451*ad6eb31eSJames Morse 				err);
452*ad6eb31eSJames Morse 			break;
453*ad6eb31eSJames Morse 		}
454*ad6eb31eSJames Morse 
455*ad6eb31eSJames Morse 		err = _sdei_event_register(event);
456*ad6eb31eSJames Morse 		if (err) {
457*ad6eb31eSJames Morse 			sdei_event_destroy(event);
458*ad6eb31eSJames Morse 			pr_warn("Failed to register event %u: %d\n", event_num,
459*ad6eb31eSJames Morse 				err);
460*ad6eb31eSJames Morse 		}
461*ad6eb31eSJames Morse 	} while (0);
462*ad6eb31eSJames Morse 	mutex_unlock(&sdei_events_lock);
463*ad6eb31eSJames Morse 
464*ad6eb31eSJames Morse 	return err;
465*ad6eb31eSJames Morse }
466*ad6eb31eSJames Morse EXPORT_SYMBOL(sdei_event_register);
467*ad6eb31eSJames Morse 
468*ad6eb31eSJames Morse static void sdei_smccc_smc(unsigned long function_id,
469*ad6eb31eSJames Morse 			   unsigned long arg0, unsigned long arg1,
470*ad6eb31eSJames Morse 			   unsigned long arg2, unsigned long arg3,
471*ad6eb31eSJames Morse 			   unsigned long arg4, struct arm_smccc_res *res)
472*ad6eb31eSJames Morse {
473*ad6eb31eSJames Morse 	arm_smccc_smc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res);
474*ad6eb31eSJames Morse }
475*ad6eb31eSJames Morse 
476*ad6eb31eSJames Morse static void sdei_smccc_hvc(unsigned long function_id,
477*ad6eb31eSJames Morse 			   unsigned long arg0, unsigned long arg1,
478*ad6eb31eSJames Morse 			   unsigned long arg2, unsigned long arg3,
479*ad6eb31eSJames Morse 			   unsigned long arg4, struct arm_smccc_res *res)
480*ad6eb31eSJames Morse {
481*ad6eb31eSJames Morse 	arm_smccc_hvc(function_id, arg0, arg1, arg2, arg3, arg4, 0, 0, res);
482*ad6eb31eSJames Morse }
483*ad6eb31eSJames Morse 
484*ad6eb31eSJames Morse static int sdei_get_conduit(struct platform_device *pdev)
485*ad6eb31eSJames Morse {
486*ad6eb31eSJames Morse 	const char *method;
487*ad6eb31eSJames Morse 	struct device_node *np = pdev->dev.of_node;
488*ad6eb31eSJames Morse 
489*ad6eb31eSJames Morse 	sdei_firmware_call = NULL;
490*ad6eb31eSJames Morse 	if (np) {
491*ad6eb31eSJames Morse 		if (of_property_read_string(np, "method", &method)) {
492*ad6eb31eSJames Morse 			pr_warn("missing \"method\" property\n");
493*ad6eb31eSJames Morse 			return CONDUIT_INVALID;
494*ad6eb31eSJames Morse 		}
495*ad6eb31eSJames Morse 
496*ad6eb31eSJames Morse 		if (!strcmp("hvc", method)) {
497*ad6eb31eSJames Morse 			sdei_firmware_call = &sdei_smccc_hvc;
498*ad6eb31eSJames Morse 			return CONDUIT_HVC;
499*ad6eb31eSJames Morse 		} else if (!strcmp("smc", method)) {
500*ad6eb31eSJames Morse 			sdei_firmware_call = &sdei_smccc_smc;
501*ad6eb31eSJames Morse 			return CONDUIT_SMC;
502*ad6eb31eSJames Morse 		}
503*ad6eb31eSJames Morse 
504*ad6eb31eSJames Morse 		pr_warn("invalid \"method\" property: %s\n", method);
505*ad6eb31eSJames Morse 	}
506*ad6eb31eSJames Morse 
507*ad6eb31eSJames Morse 	return CONDUIT_INVALID;
508*ad6eb31eSJames Morse }
509*ad6eb31eSJames Morse 
510*ad6eb31eSJames Morse static int sdei_probe(struct platform_device *pdev)
511*ad6eb31eSJames Morse {
512*ad6eb31eSJames Morse 	int err;
513*ad6eb31eSJames Morse 	u64 ver = 0;
514*ad6eb31eSJames Morse 	int conduit;
515*ad6eb31eSJames Morse 
516*ad6eb31eSJames Morse 	conduit = sdei_get_conduit(pdev);
517*ad6eb31eSJames Morse 	if (!sdei_firmware_call)
518*ad6eb31eSJames Morse 		return 0;
519*ad6eb31eSJames Morse 
520*ad6eb31eSJames Morse 	err = sdei_api_get_version(&ver);
521*ad6eb31eSJames Morse 	if (err == -EOPNOTSUPP)
522*ad6eb31eSJames Morse 		pr_err("advertised but not implemented in platform firmware\n");
523*ad6eb31eSJames Morse 	if (err) {
524*ad6eb31eSJames Morse 		pr_err("Failed to get SDEI version: %d\n", err);
525*ad6eb31eSJames Morse 		sdei_mark_interface_broken();
526*ad6eb31eSJames Morse 		return err;
527*ad6eb31eSJames Morse 	}
528*ad6eb31eSJames Morse 
529*ad6eb31eSJames Morse 	pr_info("SDEIv%d.%d (0x%x) detected in firmware.\n",
530*ad6eb31eSJames Morse 		(int)SDEI_VERSION_MAJOR(ver), (int)SDEI_VERSION_MINOR(ver),
531*ad6eb31eSJames Morse 		(int)SDEI_VERSION_VENDOR(ver));
532*ad6eb31eSJames Morse 
533*ad6eb31eSJames Morse 	if (SDEI_VERSION_MAJOR(ver) != 1) {
534*ad6eb31eSJames Morse 		pr_warn("Conflicting SDEI version detected.\n");
535*ad6eb31eSJames Morse 		sdei_mark_interface_broken();
536*ad6eb31eSJames Morse 		return -EINVAL;
537*ad6eb31eSJames Morse 	}
538*ad6eb31eSJames Morse 
539*ad6eb31eSJames Morse 	err = sdei_platform_reset();
540*ad6eb31eSJames Morse 	if (err)
541*ad6eb31eSJames Morse 		return err;
542*ad6eb31eSJames Morse 
543*ad6eb31eSJames Morse 	sdei_entry_point = sdei_arch_get_entry_point(conduit);
544*ad6eb31eSJames Morse 	if (!sdei_entry_point) {
545*ad6eb31eSJames Morse 		/* Not supported due to hardware or boot configuration */
546*ad6eb31eSJames Morse 		sdei_mark_interface_broken();
547*ad6eb31eSJames Morse 		return 0;
548*ad6eb31eSJames Morse 	}
549*ad6eb31eSJames Morse 
550*ad6eb31eSJames Morse 	on_each_cpu(&_ipi_unmask_cpu, NULL, false);
551*ad6eb31eSJames Morse 
552*ad6eb31eSJames Morse 	return 0;
553*ad6eb31eSJames Morse }
554*ad6eb31eSJames Morse 
555*ad6eb31eSJames Morse static const struct of_device_id sdei_of_match[] = {
556*ad6eb31eSJames Morse 	{ .compatible = "arm,sdei-1.0" },
557*ad6eb31eSJames Morse 	{}
558*ad6eb31eSJames Morse };
559*ad6eb31eSJames Morse 
560*ad6eb31eSJames Morse static struct platform_driver sdei_driver = {
561*ad6eb31eSJames Morse 	.driver		= {
562*ad6eb31eSJames Morse 		.name			= "sdei",
563*ad6eb31eSJames Morse 		.of_match_table		= sdei_of_match,
564*ad6eb31eSJames Morse 	},
565*ad6eb31eSJames Morse 	.probe		= sdei_probe,
566*ad6eb31eSJames Morse };
567*ad6eb31eSJames Morse 
568*ad6eb31eSJames Morse static bool __init sdei_present_dt(void)
569*ad6eb31eSJames Morse {
570*ad6eb31eSJames Morse 	struct platform_device *pdev;
571*ad6eb31eSJames Morse 	struct device_node *np, *fw_np;
572*ad6eb31eSJames Morse 
573*ad6eb31eSJames Morse 	fw_np = of_find_node_by_name(NULL, "firmware");
574*ad6eb31eSJames Morse 	if (!fw_np)
575*ad6eb31eSJames Morse 		return false;
576*ad6eb31eSJames Morse 
577*ad6eb31eSJames Morse 	np = of_find_matching_node(fw_np, sdei_of_match);
578*ad6eb31eSJames Morse 	of_node_put(fw_np);
579*ad6eb31eSJames Morse 	if (!np)
580*ad6eb31eSJames Morse 		return false;
581*ad6eb31eSJames Morse 
582*ad6eb31eSJames Morse 	pdev = of_platform_device_create(np, sdei_driver.driver.name, NULL);
583*ad6eb31eSJames Morse 	of_node_put(np);
584*ad6eb31eSJames Morse 	if (IS_ERR(pdev))
585*ad6eb31eSJames Morse 		return false;
586*ad6eb31eSJames Morse 
587*ad6eb31eSJames Morse 	return true;
588*ad6eb31eSJames Morse }
589*ad6eb31eSJames Morse 
590*ad6eb31eSJames Morse static int __init sdei_init(void)
591*ad6eb31eSJames Morse {
592*ad6eb31eSJames Morse 	if (sdei_present_dt())
593*ad6eb31eSJames Morse 		platform_driver_register(&sdei_driver);
594*ad6eb31eSJames Morse 
595*ad6eb31eSJames Morse 	return 0;
596*ad6eb31eSJames Morse }
597*ad6eb31eSJames Morse 
598*ad6eb31eSJames Morse subsys_initcall_sync(sdei_init);
599*ad6eb31eSJames Morse 
600*ad6eb31eSJames Morse int sdei_event_handler(struct pt_regs *regs,
601*ad6eb31eSJames Morse 		       struct sdei_registered_event *arg)
602*ad6eb31eSJames Morse {
603*ad6eb31eSJames Morse 	int err;
604*ad6eb31eSJames Morse 	mm_segment_t orig_addr_limit;
605*ad6eb31eSJames Morse 	u32 event_num = arg->event_num;
606*ad6eb31eSJames Morse 
607*ad6eb31eSJames Morse 	orig_addr_limit = get_fs();
608*ad6eb31eSJames Morse 	set_fs(USER_DS);
609*ad6eb31eSJames Morse 
610*ad6eb31eSJames Morse 	err = arg->callback(event_num, regs, arg->callback_arg);
611*ad6eb31eSJames Morse 	if (err)
612*ad6eb31eSJames Morse 		pr_err_ratelimited("event %u on CPU %u failed with error: %d\n",
613*ad6eb31eSJames Morse 				   event_num, smp_processor_id(), err);
614*ad6eb31eSJames Morse 
615*ad6eb31eSJames Morse 	set_fs(orig_addr_limit);
616*ad6eb31eSJames Morse 
617*ad6eb31eSJames Morse 	return err;
618*ad6eb31eSJames Morse }
619*ad6eb31eSJames Morse NOKPROBE_SYMBOL(sdei_event_handler);
620