Path: blob/master/arch/powerpc/platforms/powernv/opal-irqchip.c
26481 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* This file implements an irqchip for OPAL events. Whenever there is3* an interrupt that is handled by OPAL we get passed a list of events4* that Linux needs to do something about. These basically look like5* interrupts to Linux so we implement an irqchip to handle them.6*7* Copyright Alistair Popple, IBM Corporation 2014.8*/9#include <linux/bitops.h>10#include <linux/irq.h>11#include <linux/irqchip.h>12#include <linux/irqdomain.h>13#include <linux/interrupt.h>14#include <linux/module.h>15#include <linux/of.h>16#include <linux/platform_device.h>17#include <linux/kthread.h>18#include <linux/delay.h>19#include <linux/slab.h>20#include <linux/of_irq.h>2122#include <asm/machdep.h>23#include <asm/opal.h>2425#include "powernv.h"2627/* Maximum number of events supported by OPAL firmware */28#define MAX_NUM_EVENTS 642930struct opal_event_irqchip {31struct irq_chip irqchip;32struct irq_domain *domain;33unsigned long mask;34};35static struct opal_event_irqchip opal_event_irqchip;36static u64 last_outstanding_events;37static int opal_irq_count;38static struct resource *opal_irqs;3940void opal_handle_events(void)41{42__be64 events = 0;43u64 e;4445e = READ_ONCE(last_outstanding_events) & opal_event_irqchip.mask;46again:47while (e) {48int hwirq;4950hwirq = fls64(e) - 1;51e &= ~BIT_ULL(hwirq);5253local_irq_disable();54irq_enter();55generic_handle_domain_irq(opal_event_irqchip.domain, hwirq);56irq_exit();57local_irq_enable();5859cond_resched();60}61WRITE_ONCE(last_outstanding_events, 0);62if (opal_poll_events(&events) != OPAL_SUCCESS)63return;64e = be64_to_cpu(events) & opal_event_irqchip.mask;65if (e)66goto again;67}6869bool opal_have_pending_events(void)70{71if (READ_ONCE(last_outstanding_events) & opal_event_irqchip.mask)72return true;73return false;74}7576static void opal_event_mask(struct irq_data *d)77{78clear_bit(d->hwirq, &opal_event_irqchip.mask);79}8081static void opal_event_unmask(struct irq_data *d)82{83set_bit(d->hwirq, &opal_event_irqchip.mask);84if (opal_have_pending_events())85opal_wake_poller();86}8788static int opal_event_set_type(struct irq_data *d, unsigned int flow_type)89{90/*91* For now we only support level triggered events. The irq92* handler will be called continuously until the event has93* been cleared in OPAL.94*/95if (flow_type != IRQ_TYPE_LEVEL_HIGH)96return -EINVAL;9798return 0;99}100101static struct opal_event_irqchip opal_event_irqchip = {102.irqchip = {103.name = "OPAL EVT",104.irq_mask = opal_event_mask,105.irq_unmask = opal_event_unmask,106.irq_set_type = opal_event_set_type,107},108.mask = 0,109};110111static int opal_event_map(struct irq_domain *d, unsigned int irq,112irq_hw_number_t hwirq)113{114irq_set_chip_data(irq, &opal_event_irqchip);115irq_set_chip_and_handler(irq, &opal_event_irqchip.irqchip,116handle_level_irq);117118return 0;119}120121static irqreturn_t opal_interrupt(int irq, void *data)122{123__be64 events;124125opal_handle_interrupt(virq_to_hw(irq), &events);126WRITE_ONCE(last_outstanding_events, be64_to_cpu(events));127if (opal_have_pending_events())128opal_wake_poller();129130return IRQ_HANDLED;131}132133static int opal_event_match(struct irq_domain *h, struct device_node *node,134enum irq_domain_bus_token bus_token)135{136return irq_domain_get_of_node(h) == node;137}138139static int opal_event_xlate(struct irq_domain *h, struct device_node *np,140const u32 *intspec, unsigned int intsize,141irq_hw_number_t *out_hwirq, unsigned int *out_flags)142{143*out_hwirq = intspec[0];144*out_flags = IRQ_TYPE_LEVEL_HIGH;145146return 0;147}148149static const struct irq_domain_ops opal_event_domain_ops = {150.match = opal_event_match,151.map = opal_event_map,152.xlate = opal_event_xlate,153};154155void opal_event_shutdown(void)156{157unsigned int i;158159/* First free interrupts, which will also mask them */160for (i = 0; i < opal_irq_count; i++) {161if (!opal_irqs || !opal_irqs[i].start)162continue;163164if (in_interrupt() || irqs_disabled())165disable_irq_nosync(opal_irqs[i].start);166else167free_irq(opal_irqs[i].start, NULL);168169opal_irqs[i].start = 0;170}171}172173int __init opal_event_init(void)174{175struct device_node *dn, *opal_node;176bool old_style = false;177int i, rc = 0;178179opal_node = of_find_node_by_path("/ibm,opal");180if (!opal_node) {181pr_warn("opal: Node not found\n");182return -ENODEV;183}184185/* If dn is NULL it means the domain won't be linked to a DT186* node so therefore irq_of_parse_and_map(...) wont work. But187* that shouldn't be problem because if we're running a188* version of skiboot that doesn't have the dn then the189* devices won't have the correct properties and will have to190* fall back to the legacy method (opal_event_request(...))191* anyway. */192dn = of_find_compatible_node(NULL, NULL, "ibm,opal-event");193opal_event_irqchip.domain = irq_domain_create_linear(of_fwnode_handle(dn),194MAX_NUM_EVENTS,195&opal_event_domain_ops, &opal_event_irqchip);196of_node_put(dn);197if (!opal_event_irqchip.domain) {198pr_warn("opal: Unable to create irq domain\n");199rc = -ENOMEM;200goto out;201}202203/* Look for new-style (standard) "interrupts" property */204opal_irq_count = of_irq_count(opal_node);205206/* Absent ? Look for the old one */207if (opal_irq_count < 1) {208/* Get opal-interrupts property and names if present */209rc = of_property_count_u32_elems(opal_node, "opal-interrupts");210if (rc > 0)211opal_irq_count = rc;212old_style = true;213}214215/* No interrupts ? Bail out */216if (!opal_irq_count)217goto out;218219pr_debug("OPAL: Found %d interrupts reserved for OPAL using %s scheme\n",220opal_irq_count, old_style ? "old" : "new");221222/* Allocate an IRQ resources array */223opal_irqs = kcalloc(opal_irq_count, sizeof(struct resource), GFP_KERNEL);224if (WARN_ON(!opal_irqs)) {225rc = -ENOMEM;226goto out;227}228229/* Build the resources array */230if (old_style) {231/* Old style "opal-interrupts" property */232for (i = 0; i < opal_irq_count; i++) {233struct resource *r = &opal_irqs[i];234const char *name = NULL;235u32 hw_irq;236int virq;237238rc = of_property_read_u32_index(opal_node, "opal-interrupts",239i, &hw_irq);240if (WARN_ON(rc < 0)) {241opal_irq_count = i;242break;243}244of_property_read_string_index(opal_node, "opal-interrupts-names",245i, &name);246virq = irq_create_mapping(NULL, hw_irq);247if (!virq) {248pr_warn("Failed to map OPAL irq 0x%x\n", hw_irq);249continue;250}251r->start = r->end = virq;252r->flags = IORESOURCE_IRQ | IRQ_TYPE_LEVEL_LOW;253r->name = name;254}255} else {256/* new style standard "interrupts" property */257rc = of_irq_to_resource_table(opal_node, opal_irqs, opal_irq_count);258if (WARN_ON(rc < 0)) {259opal_irq_count = 0;260kfree(opal_irqs);261goto out;262}263if (WARN_ON(rc < opal_irq_count))264opal_irq_count = rc;265}266267/* Install interrupt handlers */268for (i = 0; i < opal_irq_count; i++) {269struct resource *r = &opal_irqs[i];270const char *name;271272/* Prefix name */273if (r->name && strlen(r->name))274name = kasprintf(GFP_KERNEL, "opal-%s", r->name);275else276name = kasprintf(GFP_KERNEL, "opal");277278if (!name)279continue;280/* Install interrupt handler */281rc = request_irq(r->start, opal_interrupt, r->flags & IRQD_TRIGGER_MASK,282name, NULL);283if (rc) {284pr_warn("Error %d requesting OPAL irq %d\n", rc, (int)r->start);285kfree(name);286continue;287}288}289rc = 0;290out:291of_node_put(opal_node);292return rc;293}294machine_arch_initcall(powernv, opal_event_init);295296/**297* opal_event_request(unsigned int opal_event_nr) - Request an event298* @opal_event_nr: the opal event number to request299*300* This routine can be used to find the linux virq number which can301* then be passed to request_irq to assign a handler for a particular302* opal event. This should only be used by legacy devices which don't303* have proper device tree bindings. Most devices should use304* irq_of_parse_and_map() instead.305*/306int opal_event_request(unsigned int opal_event_nr)307{308if (WARN_ON_ONCE(!opal_event_irqchip.domain))309return 0;310311return irq_create_mapping(opal_event_irqchip.domain, opal_event_nr);312}313EXPORT_SYMBOL(opal_event_request);314315316