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 *) §->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