Path: blob/master/arch/powerpc/platforms/ps3/interrupt.c
10818 views
/*1* PS3 interrupt routines.2*3* Copyright (C) 2006 Sony Computer Entertainment Inc.4* Copyright 2006 Sony Corp.5*6* This program is free software; you can redistribute it and/or modify7* it under the terms of the GNU General Public License as published by8* the Free Software Foundation; version 2 of the License.9*10* This program is distributed in the hope that it will be useful,11* but WITHOUT ANY WARRANTY; without even the implied warranty of12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13* GNU General Public License for more details.14*15* You should have received a copy of the GNU General Public License16* along with this program; if not, write to the Free Software17* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA18*/1920#include <linux/kernel.h>21#include <linux/module.h>22#include <linux/irq.h>2324#include <asm/machdep.h>25#include <asm/udbg.h>26#include <asm/lv1call.h>27#include <asm/smp.h>2829#include "platform.h"3031#if defined(DEBUG)32#define DBG udbg_printf33#else34#define DBG pr_debug35#endif3637/**38* struct ps3_bmp - a per cpu irq status and mask bitmap structure39* @status: 256 bit status bitmap indexed by plug40* @unused_1:41* @mask: 256 bit mask bitmap indexed by plug42* @unused_2:43* @lock:44* @ipi_debug_brk_mask:45*46* The HV maintains per SMT thread mappings of HV outlet to HV plug on47* behalf of the guest. These mappings are implemented as 256 bit guest48* supplied bitmaps indexed by plug number. The addresses of the bitmaps49* are registered with the HV through lv1_configure_irq_state_bitmap().50* The HV requires that the 512 bits of status + mask not cross a page51* boundary. PS3_BMP_MINALIGN is used to define this minimal 64 byte52* alignment.53*54* The HV supports 256 plugs per thread, assigned as {0..255}, for a total55* of 512 plugs supported on a processor. To simplify the logic this56* implementation equates HV plug value to Linux virq value, constrains each57* interrupt to have a system wide unique plug number, and limits the range58* of the plug values to map into the first dword of the bitmaps. This59* gives a usable range of plug values of {NUM_ISA_INTERRUPTS..63}. Note60* that there is no constraint on how many in this set an individual thread61* can acquire.62*63* The mask is declared as unsigned long so we can use set/clear_bit on it.64*/6566#define PS3_BMP_MINALIGN 646768struct ps3_bmp {69struct {70u64 status;71u64 unused_1[3];72unsigned long mask;73u64 unused_2[3];74};75u64 ipi_debug_brk_mask;76spinlock_t lock;77};7879/**80* struct ps3_private - a per cpu data structure81* @bmp: ps3_bmp structure82* @ppe_id: HV logical_ppe_id83* @thread_id: HV thread_id84*/8586struct ps3_private {87struct ps3_bmp bmp __attribute__ ((aligned (PS3_BMP_MINALIGN)));88u64 ppe_id;89u64 thread_id;90};9192static DEFINE_PER_CPU(struct ps3_private, ps3_private);9394/**95* ps3_chip_mask - Set an interrupt mask bit in ps3_bmp.96* @virq: The assigned Linux virq.97*98* Sets ps3_bmp.mask and calls lv1_did_update_interrupt_mask().99*/100101static void ps3_chip_mask(struct irq_data *d)102{103struct ps3_private *pd = irq_data_get_irq_chip_data(d);104unsigned long flags;105106pr_debug("%s:%d: thread_id %llu, virq %d\n", __func__, __LINE__,107pd->thread_id, d->irq);108109local_irq_save(flags);110clear_bit(63 - d->irq, &pd->bmp.mask);111lv1_did_update_interrupt_mask(pd->ppe_id, pd->thread_id);112local_irq_restore(flags);113}114115/**116* ps3_chip_unmask - Clear an interrupt mask bit in ps3_bmp.117* @virq: The assigned Linux virq.118*119* Clears ps3_bmp.mask and calls lv1_did_update_interrupt_mask().120*/121122static void ps3_chip_unmask(struct irq_data *d)123{124struct ps3_private *pd = irq_data_get_irq_chip_data(d);125unsigned long flags;126127pr_debug("%s:%d: thread_id %llu, virq %d\n", __func__, __LINE__,128pd->thread_id, d->irq);129130local_irq_save(flags);131set_bit(63 - d->irq, &pd->bmp.mask);132lv1_did_update_interrupt_mask(pd->ppe_id, pd->thread_id);133local_irq_restore(flags);134}135136/**137* ps3_chip_eoi - HV end-of-interrupt.138* @virq: The assigned Linux virq.139*140* Calls lv1_end_of_interrupt_ext().141*/142143static void ps3_chip_eoi(struct irq_data *d)144{145const struct ps3_private *pd = irq_data_get_irq_chip_data(d);146lv1_end_of_interrupt_ext(pd->ppe_id, pd->thread_id, d->irq);147}148149/**150* ps3_irq_chip - Represents the ps3_bmp as a Linux struct irq_chip.151*/152153static struct irq_chip ps3_irq_chip = {154.name = "ps3",155.irq_mask = ps3_chip_mask,156.irq_unmask = ps3_chip_unmask,157.irq_eoi = ps3_chip_eoi,158};159160/**161* ps3_virq_setup - virq related setup.162* @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be163* serviced on.164* @outlet: The HV outlet from the various create outlet routines.165* @virq: The assigned Linux virq.166*167* Calls irq_create_mapping() to get a virq and sets the chip data to168* ps3_private data.169*/170171static int ps3_virq_setup(enum ps3_cpu_binding cpu, unsigned long outlet,172unsigned int *virq)173{174int result;175struct ps3_private *pd;176177/* This defines the default interrupt distribution policy. */178179if (cpu == PS3_BINDING_CPU_ANY)180cpu = 0;181182pd = &per_cpu(ps3_private, cpu);183184*virq = irq_create_mapping(NULL, outlet);185186if (*virq == NO_IRQ) {187pr_debug("%s:%d: irq_create_mapping failed: outlet %lu\n",188__func__, __LINE__, outlet);189result = -ENOMEM;190goto fail_create;191}192193pr_debug("%s:%d: outlet %lu => cpu %u, virq %u\n", __func__, __LINE__,194outlet, cpu, *virq);195196result = irq_set_chip_data(*virq, pd);197198if (result) {199pr_debug("%s:%d: irq_set_chip_data failed\n",200__func__, __LINE__);201goto fail_set;202}203204ps3_chip_mask(irq_get_irq_data(*virq));205206return result;207208fail_set:209irq_dispose_mapping(*virq);210fail_create:211return result;212}213214/**215* ps3_virq_destroy - virq related teardown.216* @virq: The assigned Linux virq.217*218* Clears chip data and calls irq_dispose_mapping() for the virq.219*/220221static int ps3_virq_destroy(unsigned int virq)222{223const struct ps3_private *pd = irq_get_chip_data(virq);224225pr_debug("%s:%d: ppe_id %llu, thread_id %llu, virq %u\n", __func__,226__LINE__, pd->ppe_id, pd->thread_id, virq);227228irq_set_chip_data(virq, NULL);229irq_dispose_mapping(virq);230231pr_debug("%s:%d <-\n", __func__, __LINE__);232return 0;233}234235/**236* ps3_irq_plug_setup - Generic outlet and virq related setup.237* @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be238* serviced on.239* @outlet: The HV outlet from the various create outlet routines.240* @virq: The assigned Linux virq.241*242* Sets up virq and connects the irq plug.243*/244245int ps3_irq_plug_setup(enum ps3_cpu_binding cpu, unsigned long outlet,246unsigned int *virq)247{248int result;249struct ps3_private *pd;250251result = ps3_virq_setup(cpu, outlet, virq);252253if (result) {254pr_debug("%s:%d: ps3_virq_setup failed\n", __func__, __LINE__);255goto fail_setup;256}257258pd = irq_get_chip_data(*virq);259260/* Binds outlet to cpu + virq. */261262result = lv1_connect_irq_plug_ext(pd->ppe_id, pd->thread_id, *virq,263outlet, 0);264265if (result) {266pr_info("%s:%d: lv1_connect_irq_plug_ext failed: %s\n",267__func__, __LINE__, ps3_result(result));268result = -EPERM;269goto fail_connect;270}271272return result;273274fail_connect:275ps3_virq_destroy(*virq);276fail_setup:277return result;278}279EXPORT_SYMBOL_GPL(ps3_irq_plug_setup);280281/**282* ps3_irq_plug_destroy - Generic outlet and virq related teardown.283* @virq: The assigned Linux virq.284*285* Disconnects the irq plug and tears down virq.286* Do not call for system bus event interrupts setup with287* ps3_sb_event_receive_port_setup().288*/289290int ps3_irq_plug_destroy(unsigned int virq)291{292int result;293const struct ps3_private *pd = irq_get_chip_data(virq);294295pr_debug("%s:%d: ppe_id %llu, thread_id %llu, virq %u\n", __func__,296__LINE__, pd->ppe_id, pd->thread_id, virq);297298ps3_chip_mask(irq_get_irq_data(virq));299300result = lv1_disconnect_irq_plug_ext(pd->ppe_id, pd->thread_id, virq);301302if (result)303pr_info("%s:%d: lv1_disconnect_irq_plug_ext failed: %s\n",304__func__, __LINE__, ps3_result(result));305306ps3_virq_destroy(virq);307308return result;309}310EXPORT_SYMBOL_GPL(ps3_irq_plug_destroy);311312/**313* ps3_event_receive_port_setup - Setup an event receive port.314* @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be315* serviced on.316* @virq: The assigned Linux virq.317*318* The virq can be used with lv1_connect_interrupt_event_receive_port() to319* arrange to receive interrupts from system-bus devices, or with320* ps3_send_event_locally() to signal events.321*/322323int ps3_event_receive_port_setup(enum ps3_cpu_binding cpu, unsigned int *virq)324{325int result;326u64 outlet;327328result = lv1_construct_event_receive_port(&outlet);329330if (result) {331pr_debug("%s:%d: lv1_construct_event_receive_port failed: %s\n",332__func__, __LINE__, ps3_result(result));333*virq = NO_IRQ;334return result;335}336337result = ps3_irq_plug_setup(cpu, outlet, virq);338BUG_ON(result);339340return result;341}342EXPORT_SYMBOL_GPL(ps3_event_receive_port_setup);343344/**345* ps3_event_receive_port_destroy - Destroy an event receive port.346* @virq: The assigned Linux virq.347*348* Since ps3_event_receive_port_destroy destroys the receive port outlet,349* SB devices need to call disconnect_interrupt_event_receive_port() before350* this.351*/352353int ps3_event_receive_port_destroy(unsigned int virq)354{355int result;356357pr_debug(" -> %s:%d virq %u\n", __func__, __LINE__, virq);358359ps3_chip_mask(irq_get_irq_data(virq));360361result = lv1_destruct_event_receive_port(virq_to_hw(virq));362363if (result)364pr_debug("%s:%d: lv1_destruct_event_receive_port failed: %s\n",365__func__, __LINE__, ps3_result(result));366367/*368* Don't call ps3_virq_destroy() here since ps3_smp_cleanup_cpu()369* calls from interrupt context (smp_call_function) when kexecing.370*/371372pr_debug(" <- %s:%d\n", __func__, __LINE__);373return result;374}375376int ps3_send_event_locally(unsigned int virq)377{378return lv1_send_event_locally(virq_to_hw(virq));379}380381/**382* ps3_sb_event_receive_port_setup - Setup a system bus event receive port.383* @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be384* serviced on.385* @dev: The system bus device instance.386* @virq: The assigned Linux virq.387*388* An event irq represents a virtual device interrupt. The interrupt_id389* coresponds to the software interrupt number.390*/391392int ps3_sb_event_receive_port_setup(struct ps3_system_bus_device *dev,393enum ps3_cpu_binding cpu, unsigned int *virq)394{395/* this should go in system-bus.c */396397int result;398399result = ps3_event_receive_port_setup(cpu, virq);400401if (result)402return result;403404result = lv1_connect_interrupt_event_receive_port(dev->bus_id,405dev->dev_id, virq_to_hw(*virq), dev->interrupt_id);406407if (result) {408pr_debug("%s:%d: lv1_connect_interrupt_event_receive_port"409" failed: %s\n", __func__, __LINE__,410ps3_result(result));411ps3_event_receive_port_destroy(*virq);412*virq = NO_IRQ;413return result;414}415416pr_debug("%s:%d: interrupt_id %u, virq %u\n", __func__, __LINE__,417dev->interrupt_id, *virq);418419return 0;420}421EXPORT_SYMBOL(ps3_sb_event_receive_port_setup);422423int ps3_sb_event_receive_port_destroy(struct ps3_system_bus_device *dev,424unsigned int virq)425{426/* this should go in system-bus.c */427428int result;429430pr_debug(" -> %s:%d: interrupt_id %u, virq %u\n", __func__, __LINE__,431dev->interrupt_id, virq);432433result = lv1_disconnect_interrupt_event_receive_port(dev->bus_id,434dev->dev_id, virq_to_hw(virq), dev->interrupt_id);435436if (result)437pr_debug("%s:%d: lv1_disconnect_interrupt_event_receive_port"438" failed: %s\n", __func__, __LINE__,439ps3_result(result));440441result = ps3_event_receive_port_destroy(virq);442BUG_ON(result);443444/*445* ps3_event_receive_port_destroy() destroys the IRQ plug,446* so don't call ps3_irq_plug_destroy() here.447*/448449result = ps3_virq_destroy(virq);450BUG_ON(result);451452pr_debug(" <- %s:%d\n", __func__, __LINE__);453return result;454}455EXPORT_SYMBOL(ps3_sb_event_receive_port_destroy);456457/**458* ps3_io_irq_setup - Setup a system bus io irq.459* @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be460* serviced on.461* @interrupt_id: The device interrupt id read from the system repository.462* @virq: The assigned Linux virq.463*464* An io irq represents a non-virtualized device interrupt. interrupt_id465* coresponds to the interrupt number of the interrupt controller.466*/467468int ps3_io_irq_setup(enum ps3_cpu_binding cpu, unsigned int interrupt_id,469unsigned int *virq)470{471int result;472u64 outlet;473474result = lv1_construct_io_irq_outlet(interrupt_id, &outlet);475476if (result) {477pr_debug("%s:%d: lv1_construct_io_irq_outlet failed: %s\n",478__func__, __LINE__, ps3_result(result));479return result;480}481482result = ps3_irq_plug_setup(cpu, outlet, virq);483BUG_ON(result);484485return result;486}487EXPORT_SYMBOL_GPL(ps3_io_irq_setup);488489int ps3_io_irq_destroy(unsigned int virq)490{491int result;492unsigned long outlet = virq_to_hw(virq);493494ps3_chip_mask(irq_get_irq_data(virq));495496/*497* lv1_destruct_io_irq_outlet() will destroy the IRQ plug,498* so call ps3_irq_plug_destroy() first.499*/500501result = ps3_irq_plug_destroy(virq);502BUG_ON(result);503504result = lv1_destruct_io_irq_outlet(outlet);505506if (result)507pr_debug("%s:%d: lv1_destruct_io_irq_outlet failed: %s\n",508__func__, __LINE__, ps3_result(result));509510return result;511}512EXPORT_SYMBOL_GPL(ps3_io_irq_destroy);513514/**515* ps3_vuart_irq_setup - Setup the system virtual uart virq.516* @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be517* serviced on.518* @virt_addr_bmp: The caller supplied virtual uart interrupt bitmap.519* @virq: The assigned Linux virq.520*521* The system supports only a single virtual uart, so multiple calls without522* freeing the interrupt will return a wrong state error.523*/524525int ps3_vuart_irq_setup(enum ps3_cpu_binding cpu, void* virt_addr_bmp,526unsigned int *virq)527{528int result;529u64 outlet;530u64 lpar_addr;531532BUG_ON(!is_kernel_addr((u64)virt_addr_bmp));533534lpar_addr = ps3_mm_phys_to_lpar(__pa(virt_addr_bmp));535536result = lv1_configure_virtual_uart_irq(lpar_addr, &outlet);537538if (result) {539pr_debug("%s:%d: lv1_configure_virtual_uart_irq failed: %s\n",540__func__, __LINE__, ps3_result(result));541return result;542}543544result = ps3_irq_plug_setup(cpu, outlet, virq);545BUG_ON(result);546547return result;548}549EXPORT_SYMBOL_GPL(ps3_vuart_irq_setup);550551int ps3_vuart_irq_destroy(unsigned int virq)552{553int result;554555ps3_chip_mask(irq_get_irq_data(virq));556result = lv1_deconfigure_virtual_uart_irq();557558if (result) {559pr_debug("%s:%d: lv1_configure_virtual_uart_irq failed: %s\n",560__func__, __LINE__, ps3_result(result));561return result;562}563564result = ps3_irq_plug_destroy(virq);565BUG_ON(result);566567return result;568}569EXPORT_SYMBOL_GPL(ps3_vuart_irq_destroy);570571/**572* ps3_spe_irq_setup - Setup an spe virq.573* @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be574* serviced on.575* @spe_id: The spe_id returned from lv1_construct_logical_spe().576* @class: The spe interrupt class {0,1,2}.577* @virq: The assigned Linux virq.578*579*/580581int ps3_spe_irq_setup(enum ps3_cpu_binding cpu, unsigned long spe_id,582unsigned int class, unsigned int *virq)583{584int result;585u64 outlet;586587BUG_ON(class > 2);588589result = lv1_get_spe_irq_outlet(spe_id, class, &outlet);590591if (result) {592pr_debug("%s:%d: lv1_get_spe_irq_outlet failed: %s\n",593__func__, __LINE__, ps3_result(result));594return result;595}596597result = ps3_irq_plug_setup(cpu, outlet, virq);598BUG_ON(result);599600return result;601}602603int ps3_spe_irq_destroy(unsigned int virq)604{605int result;606607ps3_chip_mask(irq_get_irq_data(virq));608609result = ps3_irq_plug_destroy(virq);610BUG_ON(result);611612return result;613}614615616#define PS3_INVALID_OUTLET ((irq_hw_number_t)-1)617#define PS3_PLUG_MAX 63618619#if defined(DEBUG)620static void _dump_64_bmp(const char *header, const u64 *p, unsigned cpu,621const char* func, int line)622{623pr_debug("%s:%d: %s %u {%04lx_%04lx_%04lx_%04lx}\n",624func, line, header, cpu,625*p >> 48, (*p >> 32) & 0xffff, (*p >> 16) & 0xffff,626*p & 0xffff);627}628629static void __maybe_unused _dump_256_bmp(const char *header,630const u64 *p, unsigned cpu, const char* func, int line)631{632pr_debug("%s:%d: %s %u {%016lx:%016lx:%016lx:%016lx}\n",633func, line, header, cpu, p[0], p[1], p[2], p[3]);634}635636#define dump_bmp(_x) _dump_bmp(_x, __func__, __LINE__)637static void _dump_bmp(struct ps3_private* pd, const char* func, int line)638{639unsigned long flags;640641spin_lock_irqsave(&pd->bmp.lock, flags);642_dump_64_bmp("stat", &pd->bmp.status, pd->thread_id, func, line);643_dump_64_bmp("mask", &pd->bmp.mask, pd->thread_id, func, line);644spin_unlock_irqrestore(&pd->bmp.lock, flags);645}646647#define dump_mask(_x) _dump_mask(_x, __func__, __LINE__)648static void __maybe_unused _dump_mask(struct ps3_private *pd,649const char* func, int line)650{651unsigned long flags;652653spin_lock_irqsave(&pd->bmp.lock, flags);654_dump_64_bmp("mask", &pd->bmp.mask, pd->thread_id, func, line);655spin_unlock_irqrestore(&pd->bmp.lock, flags);656}657#else658static void dump_bmp(struct ps3_private* pd) {};659#endif /* defined(DEBUG) */660661static int ps3_host_map(struct irq_host *h, unsigned int virq,662irq_hw_number_t hwirq)663{664pr_debug("%s:%d: hwirq %lu, virq %u\n", __func__, __LINE__, hwirq,665virq);666667irq_set_chip_and_handler(virq, &ps3_irq_chip, handle_fasteoi_irq);668669return 0;670}671672static int ps3_host_match(struct irq_host *h, struct device_node *np)673{674/* Match all */675return 1;676}677678static struct irq_host_ops ps3_host_ops = {679.map = ps3_host_map,680.match = ps3_host_match,681};682683void __init ps3_register_ipi_debug_brk(unsigned int cpu, unsigned int virq)684{685struct ps3_private *pd = &per_cpu(ps3_private, cpu);686687pd->bmp.ipi_debug_brk_mask = 0x8000000000000000UL >> virq;688689pr_debug("%s:%d: cpu %u, virq %u, mask %llxh\n", __func__, __LINE__,690cpu, virq, pd->bmp.ipi_debug_brk_mask);691}692693static unsigned int ps3_get_irq(void)694{695struct ps3_private *pd = &__get_cpu_var(ps3_private);696u64 x = (pd->bmp.status & pd->bmp.mask);697unsigned int plug;698699/* check for ipi break first to stop this cpu ASAP */700701if (x & pd->bmp.ipi_debug_brk_mask)702x &= pd->bmp.ipi_debug_brk_mask;703704asm volatile("cntlzd %0,%1" : "=r" (plug) : "r" (x));705plug &= 0x3f;706707if (unlikely(plug == NO_IRQ)) {708pr_debug("%s:%d: no plug found: thread_id %llu\n", __func__,709__LINE__, pd->thread_id);710dump_bmp(&per_cpu(ps3_private, 0));711dump_bmp(&per_cpu(ps3_private, 1));712return NO_IRQ;713}714715#if defined(DEBUG)716if (unlikely(plug < NUM_ISA_INTERRUPTS || plug > PS3_PLUG_MAX)) {717dump_bmp(&per_cpu(ps3_private, 0));718dump_bmp(&per_cpu(ps3_private, 1));719BUG();720}721#endif722return plug;723}724725void __init ps3_init_IRQ(void)726{727int result;728unsigned cpu;729struct irq_host *host;730731host = irq_alloc_host(NULL, IRQ_HOST_MAP_NOMAP, 0, &ps3_host_ops,732PS3_INVALID_OUTLET);733irq_set_default_host(host);734irq_set_virq_count(PS3_PLUG_MAX + 1);735736for_each_possible_cpu(cpu) {737struct ps3_private *pd = &per_cpu(ps3_private, cpu);738739lv1_get_logical_ppe_id(&pd->ppe_id);740pd->thread_id = get_hard_smp_processor_id(cpu);741spin_lock_init(&pd->bmp.lock);742743pr_debug("%s:%d: ppe_id %llu, thread_id %llu, bmp %lxh\n",744__func__, __LINE__, pd->ppe_id, pd->thread_id,745ps3_mm_phys_to_lpar(__pa(&pd->bmp)));746747result = lv1_configure_irq_state_bitmap(pd->ppe_id,748pd->thread_id, ps3_mm_phys_to_lpar(__pa(&pd->bmp)));749750if (result)751pr_debug("%s:%d: lv1_configure_irq_state_bitmap failed:"752" %s\n", __func__, __LINE__,753ps3_result(result));754}755756ppc_md.get_irq = ps3_get_irq;757}758759void ps3_shutdown_IRQ(int cpu)760{761int result;762u64 ppe_id;763u64 thread_id = get_hard_smp_processor_id(cpu);764765lv1_get_logical_ppe_id(&ppe_id);766result = lv1_configure_irq_state_bitmap(ppe_id, thread_id, 0);767768DBG("%s:%d: lv1_configure_irq_state_bitmap (%llu:%llu/%d) %s\n", __func__,769__LINE__, ppe_id, thread_id, cpu, ps3_result(result));770}771772773