xref: /linux/drivers/soc/xilinx/xlnx_event_manager.c (revision a4eb44a6435d6d8f9e642407a4a06f65eb90ca04)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Xilinx Event Management Driver
4  *
5  *  Copyright (C) 2021 Xilinx, Inc.
6  *
7  *  Abhyuday Godhasara <abhyuday.godhasara@xilinx.com>
8  */
9 
10 #include <linux/cpuhotplug.h>
11 #include <linux/firmware/xlnx-event-manager.h>
12 #include <linux/firmware/xlnx-zynqmp.h>
13 #include <linux/hashtable.h>
14 #include <linux/interrupt.h>
15 #include <linux/irq.h>
16 #include <linux/irqdomain.h>
17 #include <linux/module.h>
18 #include <linux/of_irq.h>
19 #include <linux/platform_device.h>
20 #include <linux/slab.h>
21 
22 static DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number1);
23 
24 static int virq_sgi;
25 static int event_manager_availability = -EACCES;
26 
27 /* SGI number used for Event management driver */
28 #define XLNX_EVENT_SGI_NUM	(15)
29 
30 /* Max number of driver can register for same event */
31 #define MAX_DRIVER_PER_EVENT	(10U)
32 
33 /* Max HashMap Order for PM API feature check (1<<7 = 128) */
34 #define REGISTERED_DRIVER_MAX_ORDER	(7)
35 
36 #define MAX_BITS	(32U) /* Number of bits available for error mask */
37 
38 #define FIRMWARE_VERSION_MASK			(0xFFFFU)
39 #define REGISTER_NOTIFIER_FIRMWARE_VERSION	(2U)
40 
41 static DEFINE_HASHTABLE(reg_driver_map, REGISTERED_DRIVER_MAX_ORDER);
42 static int sgi_num = XLNX_EVENT_SGI_NUM;
43 
44 /**
45  * struct registered_event_data - Registered Event Data.
46  * @key:		key is the combine id(Node-Id | Event-Id) of type u64
47  *			where upper u32 for Node-Id and lower u32 for Event-Id,
48  *			And this used as key to index into hashmap.
49  * @agent_data:		Data passed back to handler function.
50  * @cb_type:		Type of Api callback, like PM_NOTIFY_CB, etc.
51  * @eve_cb:		Function pointer to store the callback function.
52  * @wake:		If this flag set, firmware will wakeup processor if is
53  *			in sleep or power down state.
54  * @hentry:		hlist_node that hooks this entry into hashtable.
55  */
56 struct registered_event_data {
57 	u64 key;
58 	enum pm_api_cb_id cb_type;
59 	void *agent_data;
60 
61 	event_cb_func_t eve_cb;
62 	bool wake;
63 	struct hlist_node hentry;
64 };
65 
66 static bool xlnx_is_error_event(const u32 node_id)
67 {
68 	if (node_id == EVENT_ERROR_PMC_ERR1 ||
69 	    node_id == EVENT_ERROR_PMC_ERR2 ||
70 	    node_id == EVENT_ERROR_PSM_ERR1 ||
71 	    node_id == EVENT_ERROR_PSM_ERR2)
72 		return true;
73 
74 	return false;
75 }
76 
77 static int xlnx_add_cb_for_notify_event(const u32 node_id, const u32 event, const bool wake,
78 					event_cb_func_t cb_fun,	void *data)
79 {
80 	u64 key = 0;
81 	struct registered_event_data *eve_data;
82 
83 	key = ((u64)node_id << 32U) | (u64)event;
84 	/* Check for existing entry in hash table for given key id */
85 	hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
86 		if (eve_data->key == key) {
87 			pr_err("Found as already registered\n");
88 			return -EINVAL;
89 		}
90 	}
91 
92 	/* Add new entry if not present */
93 	eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL);
94 	if (!eve_data)
95 		return -ENOMEM;
96 
97 	eve_data->key = key;
98 	eve_data->cb_type = PM_NOTIFY_CB;
99 	eve_data->eve_cb = cb_fun;
100 	eve_data->wake = wake;
101 	eve_data->agent_data = data;
102 
103 	hash_add(reg_driver_map, &eve_data->hentry, key);
104 
105 	return 0;
106 }
107 
108 static int xlnx_add_cb_for_suspend(event_cb_func_t cb_fun, void *data)
109 {
110 	struct registered_event_data *eve_data;
111 
112 	/* Check for existing entry in hash table for given cb_type */
113 	hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) {
114 		if (eve_data->cb_type == PM_INIT_SUSPEND_CB) {
115 			pr_err("Found as already registered\n");
116 			return -EINVAL;
117 		}
118 	}
119 
120 	/* Add new entry if not present */
121 	eve_data = kmalloc(sizeof(*eve_data), GFP_KERNEL);
122 	if (!eve_data)
123 		return -ENOMEM;
124 
125 	eve_data->key = 0;
126 	eve_data->cb_type = PM_INIT_SUSPEND_CB;
127 	eve_data->eve_cb = cb_fun;
128 	eve_data->agent_data = data;
129 
130 	hash_add(reg_driver_map, &eve_data->hentry, PM_INIT_SUSPEND_CB);
131 
132 	return 0;
133 }
134 
135 static int xlnx_remove_cb_for_suspend(event_cb_func_t cb_fun)
136 {
137 	bool is_callback_found = false;
138 	struct registered_event_data *eve_data;
139 
140 	/* Check for existing entry in hash table for given cb_type */
141 	hash_for_each_possible(reg_driver_map, eve_data, hentry, PM_INIT_SUSPEND_CB) {
142 		if (eve_data->cb_type == PM_INIT_SUSPEND_CB &&
143 		    eve_data->eve_cb == cb_fun) {
144 			is_callback_found = true;
145 			/* remove an object from a hashtable */
146 			hash_del(&eve_data->hentry);
147 			kfree(eve_data);
148 		}
149 	}
150 	if (!is_callback_found) {
151 		pr_warn("Didn't find any registered callback for suspend event\n");
152 		return -EINVAL;
153 	}
154 
155 	return 0;
156 }
157 
158 static int xlnx_remove_cb_for_notify_event(const u32 node_id, const u32 event,
159 					   event_cb_func_t cb_fun)
160 {
161 	bool is_callback_found = false;
162 	struct registered_event_data *eve_data;
163 	u64 key = ((u64)node_id << 32U) | (u64)event;
164 
165 	/* Check for existing entry in hash table for given key id */
166 	hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
167 		if (eve_data->key == key &&
168 		    eve_data->eve_cb == cb_fun) {
169 			is_callback_found = true;
170 			/* remove an object from a hashtable */
171 			hash_del(&eve_data->hentry);
172 			kfree(eve_data);
173 		}
174 	}
175 	if (!is_callback_found) {
176 		pr_warn("Didn't find any registered callback for 0x%x 0x%x\n",
177 			node_id, event);
178 		return -EINVAL;
179 	}
180 
181 	return 0;
182 }
183 
184 /**
185  * xlnx_register_event() - Register for the event.
186  * @cb_type:	Type of callback from pm_api_cb_id,
187  *			PM_NOTIFY_CB - for Error Events,
188  *			PM_INIT_SUSPEND_CB - for suspend callback.
189  * @node_id:	Node-Id related to event.
190  * @event:	Event Mask for the Error Event.
191  * @wake:	Flag specifying whether the subsystem should be woken upon
192  *		event notification.
193  * @cb_fun:	Function pointer to store the callback function.
194  * @data:	Pointer for the driver instance.
195  *
196  * Return:	Returns 0 on successful registration else error code.
197  */
198 int xlnx_register_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event,
199 			const bool wake, event_cb_func_t cb_fun, void *data)
200 {
201 	int ret = 0;
202 	u32 eve;
203 	int pos;
204 
205 	if (event_manager_availability)
206 		return event_manager_availability;
207 
208 	if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) {
209 		pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type);
210 		return -EINVAL;
211 	}
212 
213 	if (!cb_fun)
214 		return -EFAULT;
215 
216 	if (cb_type == PM_INIT_SUSPEND_CB) {
217 		ret = xlnx_add_cb_for_suspend(cb_fun, data);
218 	} else {
219 		if (!xlnx_is_error_event(node_id)) {
220 			/* Add entry for Node-Id/Event in hash table */
221 			ret = xlnx_add_cb_for_notify_event(node_id, event, wake, cb_fun, data);
222 		} else {
223 			/* Add into Hash table */
224 			for (pos = 0; pos < MAX_BITS; pos++) {
225 				eve = event & (1 << pos);
226 				if (!eve)
227 					continue;
228 
229 				/* Add entry for Node-Id/Eve in hash table */
230 				ret = xlnx_add_cb_for_notify_event(node_id, eve, wake, cb_fun,
231 								   data);
232 				/* Break the loop if got error */
233 				if (ret)
234 					break;
235 			}
236 			if (ret) {
237 				/* Skip the Event for which got the error */
238 				pos--;
239 				/* Remove registered(during this call) event from hash table */
240 				for ( ; pos >= 0; pos--) {
241 					eve = event & (1 << pos);
242 					if (!eve)
243 						continue;
244 					xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun);
245 				}
246 			}
247 		}
248 
249 		if (ret) {
250 			pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id,
251 			       event, ret);
252 			return ret;
253 		}
254 
255 		/* Register for Node-Id/Event combination in firmware */
256 		ret = zynqmp_pm_register_notifier(node_id, event, wake, true);
257 		if (ret) {
258 			pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__, node_id,
259 			       event, ret);
260 			/* Remove already registered event from hash table */
261 			if (xlnx_is_error_event(node_id)) {
262 				for (pos = 0; pos < MAX_BITS; pos++) {
263 					eve = event & (1 << pos);
264 					if (!eve)
265 						continue;
266 					xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun);
267 				}
268 			} else {
269 				xlnx_remove_cb_for_notify_event(node_id, event, cb_fun);
270 			}
271 			return ret;
272 		}
273 	}
274 
275 	return ret;
276 }
277 EXPORT_SYMBOL_GPL(xlnx_register_event);
278 
279 /**
280  * xlnx_unregister_event() - Unregister for the event.
281  * @cb_type:	Type of callback from pm_api_cb_id,
282  *			PM_NOTIFY_CB - for Error Events,
283  *			PM_INIT_SUSPEND_CB - for suspend callback.
284  * @node_id:	Node-Id related to event.
285  * @event:	Event Mask for the Error Event.
286  * @cb_fun:	Function pointer of callback function.
287  *
288  * Return:	Returns 0 on successful unregistration else error code.
289  */
290 int xlnx_unregister_event(const enum pm_api_cb_id cb_type, const u32 node_id, const u32 event,
291 			  event_cb_func_t cb_fun)
292 {
293 	int ret;
294 	u32 eve, pos;
295 
296 	if (event_manager_availability)
297 		return event_manager_availability;
298 
299 	if (cb_type != PM_NOTIFY_CB && cb_type != PM_INIT_SUSPEND_CB) {
300 		pr_err("%s() Unsupported Callback 0x%x\n", __func__, cb_type);
301 		return -EINVAL;
302 	}
303 
304 	if (!cb_fun)
305 		return -EFAULT;
306 
307 	if (cb_type == PM_INIT_SUSPEND_CB) {
308 		ret = xlnx_remove_cb_for_suspend(cb_fun);
309 	} else {
310 		/* Remove Node-Id/Event from hash table */
311 		if (!xlnx_is_error_event(node_id)) {
312 			xlnx_remove_cb_for_notify_event(node_id, event, cb_fun);
313 		} else {
314 			for (pos = 0; pos < MAX_BITS; pos++) {
315 				eve = event & (1 << pos);
316 				if (!eve)
317 					continue;
318 
319 				xlnx_remove_cb_for_notify_event(node_id, eve, cb_fun);
320 			}
321 		}
322 
323 		/* Un-register for Node-Id/Event combination */
324 		ret = zynqmp_pm_register_notifier(node_id, event, false, false);
325 		if (ret) {
326 			pr_err("%s() failed for 0x%x and 0x%x: %d\n",
327 			       __func__, node_id, event, ret);
328 			return ret;
329 		}
330 	}
331 
332 	return ret;
333 }
334 EXPORT_SYMBOL_GPL(xlnx_unregister_event);
335 
336 static void xlnx_call_suspend_cb_handler(const u32 *payload)
337 {
338 	bool is_callback_found = false;
339 	struct registered_event_data *eve_data;
340 	u32 cb_type = payload[0];
341 
342 	/* Check for existing entry in hash table for given cb_type */
343 	hash_for_each_possible(reg_driver_map, eve_data, hentry, cb_type) {
344 		if (eve_data->cb_type == cb_type) {
345 			eve_data->eve_cb(&payload[0], eve_data->agent_data);
346 			is_callback_found = true;
347 		}
348 	}
349 	if (!is_callback_found)
350 		pr_warn("Didn't find any registered callback for suspend event\n");
351 }
352 
353 static void xlnx_call_notify_cb_handler(const u32 *payload)
354 {
355 	bool is_callback_found = false;
356 	struct registered_event_data *eve_data;
357 	u64 key = ((u64)payload[1] << 32U) | (u64)payload[2];
358 	int ret;
359 
360 	/* Check for existing entry in hash table for given key id */
361 	hash_for_each_possible(reg_driver_map, eve_data, hentry, key) {
362 		if (eve_data->key == key) {
363 			eve_data->eve_cb(&payload[0], eve_data->agent_data);
364 			is_callback_found = true;
365 
366 			/* re register with firmware to get future events */
367 			ret = zynqmp_pm_register_notifier(payload[1], payload[2],
368 							  eve_data->wake, true);
369 			if (ret) {
370 				pr_err("%s() failed for 0x%x and 0x%x: %d\r\n", __func__,
371 				       payload[1], payload[2], ret);
372 				/* Remove already registered event from hash table */
373 				xlnx_remove_cb_for_notify_event(payload[1], payload[2],
374 								eve_data->eve_cb);
375 			}
376 		}
377 	}
378 	if (!is_callback_found)
379 		pr_warn("Didn't find any registered callback for 0x%x 0x%x\n",
380 			payload[1], payload[2]);
381 }
382 
383 static void xlnx_get_event_callback_data(u32 *buf)
384 {
385 	zynqmp_pm_invoke_fn(GET_CALLBACK_DATA, 0, 0, 0, 0, buf);
386 }
387 
388 static irqreturn_t xlnx_event_handler(int irq, void *dev_id)
389 {
390 	u32 cb_type, node_id, event, pos;
391 	u32 payload[CB_MAX_PAYLOAD_SIZE] = {0};
392 	u32 event_data[CB_MAX_PAYLOAD_SIZE] = {0};
393 
394 	/* Get event data */
395 	xlnx_get_event_callback_data(payload);
396 
397 	/* First element is callback type, others are callback arguments */
398 	cb_type = payload[0];
399 
400 	if (cb_type == PM_NOTIFY_CB) {
401 		node_id = payload[1];
402 		event = payload[2];
403 		if (!xlnx_is_error_event(node_id)) {
404 			xlnx_call_notify_cb_handler(payload);
405 		} else {
406 			/*
407 			 * Each call back function expecting payload as an input arguments.
408 			 * We can get multiple error events as in one call back through error
409 			 * mask. So payload[2] may can contain multiple error events.
410 			 * In reg_driver_map database we store data in the combination of single
411 			 * node_id-error combination.
412 			 * So coping the payload message into event_data and update the
413 			 * event_data[2] with Error Mask for single error event and use
414 			 * event_data as input argument for registered call back function.
415 			 *
416 			 */
417 			memcpy(event_data, payload, (4 * CB_MAX_PAYLOAD_SIZE));
418 			/* Support Multiple Error Event */
419 			for (pos = 0; pos < MAX_BITS; pos++) {
420 				if ((0 == (event & (1 << pos))))
421 					continue;
422 				event_data[2] = (event & (1 << pos));
423 				xlnx_call_notify_cb_handler(event_data);
424 			}
425 		}
426 	} else if (cb_type == PM_INIT_SUSPEND_CB) {
427 		xlnx_call_suspend_cb_handler(payload);
428 	} else {
429 		pr_err("%s() Unsupported Callback %d\n", __func__, cb_type);
430 	}
431 
432 	return IRQ_HANDLED;
433 }
434 
435 static int xlnx_event_cpuhp_start(unsigned int cpu)
436 {
437 	enable_percpu_irq(virq_sgi, IRQ_TYPE_NONE);
438 
439 	return 0;
440 }
441 
442 static int xlnx_event_cpuhp_down(unsigned int cpu)
443 {
444 	disable_percpu_irq(virq_sgi);
445 
446 	return 0;
447 }
448 
449 static void xlnx_disable_percpu_irq(void *data)
450 {
451 	disable_percpu_irq(virq_sgi);
452 }
453 
454 static int xlnx_event_init_sgi(struct platform_device *pdev)
455 {
456 	int ret = 0;
457 	int cpu = smp_processor_id();
458 	/*
459 	 * IRQ related structures are used for the following:
460 	 * for each SGI interrupt ensure its mapped by GIC IRQ domain
461 	 * and that each corresponding linux IRQ for the HW IRQ has
462 	 * a handler for when receiving an interrupt from the remote
463 	 * processor.
464 	 */
465 	struct irq_domain *domain;
466 	struct irq_fwspec sgi_fwspec;
467 	struct device_node *interrupt_parent = NULL;
468 	struct device *parent = pdev->dev.parent;
469 
470 	/* Find GIC controller to map SGIs. */
471 	interrupt_parent = of_irq_find_parent(parent->of_node);
472 	if (!interrupt_parent) {
473 		dev_err(&pdev->dev, "Failed to find property for Interrupt parent\n");
474 		return -EINVAL;
475 	}
476 
477 	/* Each SGI needs to be associated with GIC's IRQ domain. */
478 	domain = irq_find_host(interrupt_parent);
479 	of_node_put(interrupt_parent);
480 
481 	/* Each mapping needs GIC domain when finding IRQ mapping. */
482 	sgi_fwspec.fwnode = domain->fwnode;
483 
484 	/*
485 	 * When irq domain looks at mapping each arg is as follows:
486 	 * 3 args for: interrupt type (SGI), interrupt # (set later), type
487 	 */
488 	sgi_fwspec.param_count = 1;
489 
490 	/* Set SGI's hwirq */
491 	sgi_fwspec.param[0] = sgi_num;
492 	virq_sgi = irq_create_fwspec_mapping(&sgi_fwspec);
493 
494 	per_cpu(cpu_number1, cpu) = cpu;
495 	ret = request_percpu_irq(virq_sgi, xlnx_event_handler, "xlnx_event_mgmt",
496 				 &cpu_number1);
497 	WARN_ON(ret);
498 	if (ret) {
499 		irq_dispose_mapping(virq_sgi);
500 		return ret;
501 	}
502 
503 	irq_to_desc(virq_sgi);
504 	irq_set_status_flags(virq_sgi, IRQ_PER_CPU);
505 
506 	return ret;
507 }
508 
509 static void xlnx_event_cleanup_sgi(struct platform_device *pdev)
510 {
511 	int cpu = smp_processor_id();
512 
513 	per_cpu(cpu_number1, cpu) = cpu;
514 
515 	cpuhp_remove_state(CPUHP_AP_ONLINE_DYN);
516 
517 	on_each_cpu(xlnx_disable_percpu_irq, NULL, 1);
518 
519 	irq_clear_status_flags(virq_sgi, IRQ_PER_CPU);
520 	free_percpu_irq(virq_sgi, &cpu_number1);
521 	irq_dispose_mapping(virq_sgi);
522 }
523 
524 static int xlnx_event_manager_probe(struct platform_device *pdev)
525 {
526 	int ret;
527 
528 	ret = zynqmp_pm_feature(PM_REGISTER_NOTIFIER);
529 	if (ret < 0) {
530 		dev_err(&pdev->dev, "Feature check failed with %d\n", ret);
531 		return ret;
532 	}
533 
534 	if ((ret & FIRMWARE_VERSION_MASK) <
535 	    REGISTER_NOTIFIER_FIRMWARE_VERSION) {
536 		dev_err(&pdev->dev, "Register notifier version error. Expected Firmware: v%d - Found: v%d\n",
537 			REGISTER_NOTIFIER_FIRMWARE_VERSION,
538 			ret & FIRMWARE_VERSION_MASK);
539 		return -EOPNOTSUPP;
540 	}
541 
542 	/* Initialize the SGI */
543 	ret = xlnx_event_init_sgi(pdev);
544 	if (ret) {
545 		dev_err(&pdev->dev, "SGI Init has been failed with %d\n", ret);
546 		return ret;
547 	}
548 
549 	/* Setup function for the CPU hot-plug cases */
550 	cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "soc/event:starting",
551 			  xlnx_event_cpuhp_start, xlnx_event_cpuhp_down);
552 
553 	ret = zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_REGISTER_SGI, sgi_num,
554 				  0, NULL);
555 	if (ret) {
556 		dev_err(&pdev->dev, "SGI %d Registration over TF-A failed with %d\n", sgi_num, ret);
557 		xlnx_event_cleanup_sgi(pdev);
558 		return ret;
559 	}
560 
561 	event_manager_availability = 0;
562 
563 	dev_info(&pdev->dev, "SGI %d Registered over TF-A\n", sgi_num);
564 	dev_info(&pdev->dev, "Xilinx Event Management driver probed\n");
565 
566 	return ret;
567 }
568 
569 static int xlnx_event_manager_remove(struct platform_device *pdev)
570 {
571 	int i;
572 	struct registered_event_data *eve_data;
573 	struct hlist_node *tmp;
574 	int ret;
575 
576 	hash_for_each_safe(reg_driver_map, i, tmp, eve_data, hentry) {
577 		hash_del(&eve_data->hentry);
578 		kfree(eve_data);
579 	}
580 
581 	ret = zynqmp_pm_invoke_fn(PM_IOCTL, 0, IOCTL_REGISTER_SGI, 0, 1, NULL);
582 	if (ret)
583 		dev_err(&pdev->dev, "SGI unregistration over TF-A failed with %d\n", ret);
584 
585 	xlnx_event_cleanup_sgi(pdev);
586 
587 	event_manager_availability = -EACCES;
588 
589 	return ret;
590 }
591 
592 static struct platform_driver xlnx_event_manager_driver = {
593 	.probe = xlnx_event_manager_probe,
594 	.remove = xlnx_event_manager_remove,
595 	.driver = {
596 		.name = "xlnx_event_manager",
597 	},
598 };
599 module_param(sgi_num, uint, 0);
600 module_platform_driver(xlnx_event_manager_driver);
601