xref: /linux/arch/powerpc/platforms/pseries/io_event_irq.c (revision 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
277eafe10STseng-Hui (Frank) Lin /*
377eafe10STseng-Hui (Frank) Lin  * Copyright 2010 2011 Mark Nelson and Tseng-Hui (Frank) Lin, IBM Corporation
477eafe10STseng-Hui (Frank) Lin  */
577eafe10STseng-Hui (Frank) Lin 
677eafe10STseng-Hui (Frank) Lin #include <linux/errno.h>
777eafe10STseng-Hui (Frank) Lin #include <linux/slab.h>
84b16f8e2SPaul Gortmaker #include <linux/export.h>
977eafe10STseng-Hui (Frank) Lin #include <linux/irq.h>
1077eafe10STseng-Hui (Frank) Lin #include <linux/interrupt.h>
1177eafe10STseng-Hui (Frank) Lin #include <linux/of.h>
1277eafe10STseng-Hui (Frank) Lin #include <linux/list.h>
1377eafe10STseng-Hui (Frank) Lin #include <linux/notifier.h>
1477eafe10STseng-Hui (Frank) Lin 
1577eafe10STseng-Hui (Frank) Lin #include <asm/machdep.h>
1677eafe10STseng-Hui (Frank) Lin #include <asm/rtas.h>
1777eafe10STseng-Hui (Frank) Lin #include <asm/irq.h>
1877eafe10STseng-Hui (Frank) Lin #include <asm/io_event_irq.h>
1977eafe10STseng-Hui (Frank) Lin 
2077eafe10STseng-Hui (Frank) Lin #include "pseries.h"
2177eafe10STseng-Hui (Frank) Lin 
2277eafe10STseng-Hui (Frank) Lin /*
2377eafe10STseng-Hui (Frank) Lin  * IO event interrupt is a mechanism provided by RTAS to return
2477eafe10STseng-Hui (Frank) Lin  * information about hardware error and non-error events. Device
2577eafe10STseng-Hui (Frank) Lin  * drivers can register their event handlers to receive events.
2677eafe10STseng-Hui (Frank) Lin  * Device drivers are expected to use atomic_notifier_chain_register()
2777eafe10STseng-Hui (Frank) Lin  * and atomic_notifier_chain_unregister() to register and unregister
2877eafe10STseng-Hui (Frank) Lin  * their event handlers. Since multiple IO event types and scopes
2977eafe10STseng-Hui (Frank) Lin  * share an IO event interrupt, the event handlers are called one
3077eafe10STseng-Hui (Frank) Lin  * by one until the IO event is claimed by one of the handlers.
3177eafe10STseng-Hui (Frank) Lin  * The event handlers are expected to return NOTIFY_OK if the
3277eafe10STseng-Hui (Frank) Lin  * event is handled by the event handler or NOTIFY_DONE if the
3377eafe10STseng-Hui (Frank) Lin  * event does not belong to the handler.
3477eafe10STseng-Hui (Frank) Lin  *
3577eafe10STseng-Hui (Frank) Lin  * Usage:
3677eafe10STseng-Hui (Frank) Lin  *
3777eafe10STseng-Hui (Frank) Lin  * Notifier function:
3877eafe10STseng-Hui (Frank) Lin  * #include <asm/io_event_irq.h>
3977eafe10STseng-Hui (Frank) Lin  * int event_handler(struct notifier_block *nb, unsigned long val, void *data) {
4077eafe10STseng-Hui (Frank) Lin  * 	p = (struct pseries_io_event_sect_data *) data;
4177eafe10STseng-Hui (Frank) Lin  * 	if (! is_my_event(p->scope, p->event_type)) return NOTIFY_DONE;
4277eafe10STseng-Hui (Frank) Lin  * 		:
4377eafe10STseng-Hui (Frank) Lin  * 		:
4477eafe10STseng-Hui (Frank) Lin  * 	return NOTIFY_OK;
4577eafe10STseng-Hui (Frank) Lin  * }
4677eafe10STseng-Hui (Frank) Lin  * struct notifier_block event_nb = {
4777eafe10STseng-Hui (Frank) Lin  * 	.notifier_call = event_handler,
4877eafe10STseng-Hui (Frank) Lin  * }
4977eafe10STseng-Hui (Frank) Lin  *
5077eafe10STseng-Hui (Frank) Lin  * Registration:
5177eafe10STseng-Hui (Frank) Lin  * atomic_notifier_chain_register(&pseries_ioei_notifier_list, &event_nb);
5277eafe10STseng-Hui (Frank) Lin  *
5377eafe10STseng-Hui (Frank) Lin  * Unregistration:
5477eafe10STseng-Hui (Frank) Lin  * atomic_notifier_chain_unregister(&pseries_ioei_notifier_list, &event_nb);
5577eafe10STseng-Hui (Frank) Lin  */
5677eafe10STseng-Hui (Frank) Lin 
5777eafe10STseng-Hui (Frank) Lin ATOMIC_NOTIFIER_HEAD(pseries_ioei_notifier_list);
5877eafe10STseng-Hui (Frank) Lin EXPORT_SYMBOL_GPL(pseries_ioei_notifier_list);
5977eafe10STseng-Hui (Frank) Lin 
6077eafe10STseng-Hui (Frank) Lin static int ioei_check_exception_token;
6177eafe10STseng-Hui (Frank) Lin 
6277eafe10STseng-Hui (Frank) Lin static char ioei_rtas_buf[RTAS_DATA_BUF_SIZE] __cacheline_aligned;
6377eafe10STseng-Hui (Frank) Lin 
6477eafe10STseng-Hui (Frank) Lin /**
6577eafe10STseng-Hui (Frank) Lin  * Find the data portion of an IO Event section from event log.
6677eafe10STseng-Hui (Frank) Lin  * @elog: RTAS error/event log.
6777eafe10STseng-Hui (Frank) Lin  *
6877eafe10STseng-Hui (Frank) Lin  * Return:
6977eafe10STseng-Hui (Frank) Lin  * 	pointer to a valid IO event section data. NULL if not found.
7077eafe10STseng-Hui (Frank) Lin  */
ioei_find_event(struct rtas_error_log * elog)7177eafe10STseng-Hui (Frank) Lin static struct pseries_io_event * ioei_find_event(struct rtas_error_log *elog)
7277eafe10STseng-Hui (Frank) Lin {
736431f208SAnton Blanchard 	struct pseries_errorlog *sect;
7477eafe10STseng-Hui (Frank) Lin 
7577eafe10STseng-Hui (Frank) Lin 	/* We should only ever get called for io-event interrupts, but if
7677eafe10STseng-Hui (Frank) Lin 	 * we do get called for another type then something went wrong so
7777eafe10STseng-Hui (Frank) Lin 	 * make some noise about it.
7877eafe10STseng-Hui (Frank) Lin 	 * RTAS_TYPE_IO only exists in extended event log version 6 or later.
7977eafe10STseng-Hui (Frank) Lin 	 * No need to check event log version.
8077eafe10STseng-Hui (Frank) Lin 	 */
81a08a53eaSGreg Kurz 	if (unlikely(rtas_error_type(elog) != RTAS_TYPE_IO)) {
8277eafe10STseng-Hui (Frank) Lin 		printk_once(KERN_WARNING"io_event_irq: Unexpected event type %d",
83a08a53eaSGreg Kurz 			    rtas_error_type(elog));
8477eafe10STseng-Hui (Frank) Lin 		return NULL;
8577eafe10STseng-Hui (Frank) Lin 	}
8677eafe10STseng-Hui (Frank) Lin 
876431f208SAnton Blanchard 	sect = get_pseries_errorlog(elog, PSERIES_ELOG_SECT_ID_IO_EVENT);
8877eafe10STseng-Hui (Frank) Lin 	if (unlikely(!sect)) {
8977eafe10STseng-Hui (Frank) Lin 		printk_once(KERN_WARNING "io_event_irq: RTAS extended event "
9077eafe10STseng-Hui (Frank) Lin 			    "log does not contain an IO Event section. "
9177eafe10STseng-Hui (Frank) Lin 			    "Could be a bug in system firmware!\n");
9277eafe10STseng-Hui (Frank) Lin 		return NULL;
9377eafe10STseng-Hui (Frank) Lin 	}
9477eafe10STseng-Hui (Frank) Lin 	return (struct pseries_io_event *) &sect->data;
9577eafe10STseng-Hui (Frank) Lin }
9677eafe10STseng-Hui (Frank) Lin 
9777eafe10STseng-Hui (Frank) Lin /*
9877eafe10STseng-Hui (Frank) Lin  * PAPR:
9977eafe10STseng-Hui (Frank) Lin  * - check-exception returns the first found error or event and clear that
10077eafe10STseng-Hui (Frank) Lin  *   error or event so it is reported once.
10177eafe10STseng-Hui (Frank) Lin  * - Each interrupt returns one event. If a plateform chooses to report
10277eafe10STseng-Hui (Frank) Lin  *   multiple events through a single interrupt, it must ensure that the
10377eafe10STseng-Hui (Frank) Lin  *   interrupt remains asserted until check-exception has been used to
10477eafe10STseng-Hui (Frank) Lin  *   process all out-standing events for that interrupt.
10577eafe10STseng-Hui (Frank) Lin  *
10677eafe10STseng-Hui (Frank) Lin  * Implementation notes:
10777eafe10STseng-Hui (Frank) Lin  * - Events must be processed in the order they are returned. Hence,
10877eafe10STseng-Hui (Frank) Lin  *   sequential in nature.
10977eafe10STseng-Hui (Frank) Lin  * - The owner of an event is determined by combinations of scope,
11077eafe10STseng-Hui (Frank) Lin  *   event type, and sub-type. There is no easy way to pre-sort clients
11177eafe10STseng-Hui (Frank) Lin  *   by scope or event type alone. For example, Torrent ISR route change
112027dfac6SMichael Ellerman  *   event is reported with scope 0x00 (Not Applicable) rather than
11377eafe10STseng-Hui (Frank) Lin  *   0x3B (Torrent-hub). It is better to let the clients to identify
114e1b85c17SSebastien Bessiere  *   who owns the event.
11577eafe10STseng-Hui (Frank) Lin  */
11677eafe10STseng-Hui (Frank) Lin 
ioei_interrupt(int irq,void * dev_id)11777eafe10STseng-Hui (Frank) Lin static irqreturn_t ioei_interrupt(int irq, void *dev_id)
11877eafe10STseng-Hui (Frank) Lin {
11977eafe10STseng-Hui (Frank) Lin 	struct pseries_io_event *event;
12077eafe10STseng-Hui (Frank) Lin 	int rtas_rc;
12177eafe10STseng-Hui (Frank) Lin 
12277eafe10STseng-Hui (Frank) Lin 	for (;;) {
12377eafe10STseng-Hui (Frank) Lin 		rtas_rc = rtas_call(ioei_check_exception_token, 6, 1, NULL,
12477eafe10STseng-Hui (Frank) Lin 				    RTAS_VECTOR_EXTERNAL_INTERRUPT,
12577eafe10STseng-Hui (Frank) Lin 				    virq_to_hw(irq),
12677eafe10STseng-Hui (Frank) Lin 				    RTAS_IO_EVENTS, 1 /* Time Critical */,
12777eafe10STseng-Hui (Frank) Lin 				    __pa(ioei_rtas_buf),
12877eafe10STseng-Hui (Frank) Lin 				    RTAS_DATA_BUF_SIZE);
12977eafe10STseng-Hui (Frank) Lin 		if (rtas_rc != 0)
13077eafe10STseng-Hui (Frank) Lin 			break;
13177eafe10STseng-Hui (Frank) Lin 
13277eafe10STseng-Hui (Frank) Lin 		event = ioei_find_event((struct rtas_error_log *)ioei_rtas_buf);
13377eafe10STseng-Hui (Frank) Lin 		if (!event)
13477eafe10STseng-Hui (Frank) Lin 			continue;
13577eafe10STseng-Hui (Frank) Lin 
13677eafe10STseng-Hui (Frank) Lin 		atomic_notifier_call_chain(&pseries_ioei_notifier_list,
13777eafe10STseng-Hui (Frank) Lin 					   0, event);
13877eafe10STseng-Hui (Frank) Lin 	}
13977eafe10STseng-Hui (Frank) Lin 	return IRQ_HANDLED;
14077eafe10STseng-Hui (Frank) Lin }
14177eafe10STseng-Hui (Frank) Lin 
ioei_init(void)14277eafe10STseng-Hui (Frank) Lin static int __init ioei_init(void)
14377eafe10STseng-Hui (Frank) Lin {
14477eafe10STseng-Hui (Frank) Lin 	struct device_node *np;
14577eafe10STseng-Hui (Frank) Lin 
146*08273c9fSNathan Lynch 	ioei_check_exception_token = rtas_function_token(RTAS_FN_CHECK_EXCEPTION);
14753876e38SAnton Blanchard 	if (ioei_check_exception_token == RTAS_UNKNOWN_SERVICE)
14877eafe10STseng-Hui (Frank) Lin 		return -ENODEV;
14953876e38SAnton Blanchard 
15077eafe10STseng-Hui (Frank) Lin 	np = of_find_node_by_path("/event-sources/ibm,io-events");
15177eafe10STseng-Hui (Frank) Lin 	if (np) {
15277eafe10STseng-Hui (Frank) Lin 		request_event_sources_irqs(np, ioei_interrupt, "IO_EVENT");
15353876e38SAnton Blanchard 		pr_info("IBM I/O event interrupts enabled\n");
15477eafe10STseng-Hui (Frank) Lin 		of_node_put(np);
15577eafe10STseng-Hui (Frank) Lin 	} else {
15677eafe10STseng-Hui (Frank) Lin 		return -ENODEV;
15777eafe10STseng-Hui (Frank) Lin 	}
15877eafe10STseng-Hui (Frank) Lin 	return 0;
15977eafe10STseng-Hui (Frank) Lin }
16077eafe10STseng-Hui (Frank) Lin machine_subsys_initcall(pseries, ioei_init);
16177eafe10STseng-Hui (Frank) Lin 
162