Merge master.kernel.org:/pub/scm/linux/kernel/git/herbert/crypto-2.6
[linux-drm-fsl-dcu.git] / arch / powerpc / platforms / ps3 / interrupt.c
index 056c1e4141ba4bc937a03c2e72c71c7cf6619e7d..9da82c266ba9ccc45110a5b2daa9f32004253001 100644 (file)
@@ -24,7 +24,6 @@
 
 #include <asm/machdep.h>
 #include <asm/udbg.h>
-#include <asm/ps3.h>
 #include <asm/lv1call.h>
 
 #include "platform.h"
 #endif
 
 /**
- * ps3_alloc_io_irq - Assign a virq to a system bus device.
- * interrupt_id: The device interrupt id read from the system repository.
+ * struct ps3_bmp - a per cpu irq status and mask bitmap structure
+ * @status: 256 bit status bitmap indexed by plug
+ * @unused_1:
+ * @mask: 256 bit mask bitmap indexed by plug
+ * @unused_2:
+ * @lock:
+ * @ipi_debug_brk_mask:
+ *
+ * The HV mantains per SMT thread mappings of HV outlet to HV plug on
+ * behalf of the guest.  These mappings are implemented as 256 bit guest
+ * supplied bitmaps indexed by plug number.  The addresses of the bitmaps
+ * are registered with the HV through lv1_configure_irq_state_bitmap().
+ * The HV requires that the 512 bits of status + mask not cross a page
+ * boundary.  PS3_BMP_MINALIGN is used to define this minimal 64 byte
+ * alignment.
+ *
+ * The HV supports 256 plugs per thread, assigned as {0..255}, for a total
+ * of 512 plugs supported on a processor.  To simplify the logic this
+ * implementation equates HV plug value to Linux virq value, constrains each
+ * interrupt to have a system wide unique plug number, and limits the range
+ * of the plug values to map into the first dword of the bitmaps.  This
+ * gives a usable range of plug values of  {NUM_ISA_INTERRUPTS..63}.  Note
+ * that there is no constraint on how many in this set an individual thread
+ * can acquire.
+ */
+
+#define PS3_BMP_MINALIGN 64
+
+struct ps3_bmp {
+       struct {
+               u64 status;
+               u64 unused_1[3];
+               u64 mask;
+               u64 unused_2[3];
+       };
+       u64 ipi_debug_brk_mask;
+       spinlock_t lock;
+};
+
+/**
+ * struct ps3_private - a per cpu data structure
+ * @bmp: ps3_bmp structure
+ * @node: HV logical_ppe_id
+ * @cpu: HV thread_id
+ */
+
+struct ps3_private {
+       struct ps3_bmp bmp __attribute__ ((aligned (PS3_BMP_MINALIGN)));
+       u64 node;
+       unsigned int cpu;
+};
+
+static DEFINE_PER_CPU(struct ps3_private, ps3_private);
+
+/**
+ * ps3_virq_setup - virq related setup.
+ * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be
+ * serviced on.
+ * @outlet: The HV outlet from the various create outlet routines.
  * @virq: The assigned Linux virq.
  *
- * An io irq represents a non-virtualized device interrupt.  interrupt_id
- * coresponds to the interrupt number of the interrupt controller.
+ * Calls irq_create_mapping() to get a virq and sets the chip data to
+ * ps3_private data.
  */
 
-int ps3_alloc_io_irq(unsigned int interrupt_id, unsigned int *virq)
+int ps3_virq_setup(enum ps3_cpu_binding cpu, unsigned long outlet,
+       unsigned int *virq)
 {
        int result;
-       unsigned long outlet;
+       struct ps3_private *pd;
 
-       result = lv1_construct_io_irq_outlet(interrupt_id, &outlet);
+       /* This defines the default interrupt distribution policy. */
+
+       if (cpu == PS3_BINDING_CPU_ANY)
+               cpu = 0;
+
+       pd = &per_cpu(ps3_private, cpu);
+
+       *virq = irq_create_mapping(NULL, outlet);
+
+       if (*virq == NO_IRQ) {
+               pr_debug("%s:%d: irq_create_mapping failed: outlet %lu\n",
+                       __func__, __LINE__, outlet);
+               result = -ENOMEM;
+               goto fail_create;
+       }
+
+       pr_debug("%s:%d: outlet %lu => cpu %u, virq %u\n", __func__, __LINE__,
+               outlet, cpu, *virq);
+
+       result = set_irq_chip_data(*virq, pd);
 
        if (result) {
-               pr_debug("%s:%d: lv1_construct_io_irq_outlet failed: %s\n",
-                       __func__, __LINE__, ps3_result(result));
-               return result;
+               pr_debug("%s:%d: set_irq_chip_data failed\n",
+                       __func__, __LINE__);
+               goto fail_set;
        }
 
-       *virq = irq_create_mapping(NULL, outlet);
+       return result;
+
+fail_set:
+       irq_dispose_mapping(*virq);
+fail_create:
+       return result;
+}
+
+/**
+ * ps3_virq_destroy - virq related teardown.
+ * @virq: The assigned Linux virq.
+ *
+ * Clears chip data and calls irq_dispose_mapping() for the virq.
+ */
+
+int ps3_virq_destroy(unsigned int virq)
+{
+       const struct ps3_private *pd = get_irq_chip_data(virq);
+
+       pr_debug("%s:%d: node %lu, cpu %d, virq %u\n", __func__, __LINE__,
+               pd->node, pd->cpu, virq);
 
-       pr_debug("%s:%d: interrupt_id %u => outlet %lu, virq %u\n",
-               __func__, __LINE__, interrupt_id, outlet, *virq);
+       set_irq_chip_data(virq, NULL);
+       irq_dispose_mapping(virq);
 
+       pr_debug("%s:%d <-\n", __func__, __LINE__);
        return 0;
 }
 
-int ps3_free_io_irq(unsigned int virq)
+/**
+ * ps3_irq_plug_setup - Generic outlet and virq related setup.
+ * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be
+ * serviced on.
+ * @outlet: The HV outlet from the various create outlet routines.
+ * @virq: The assigned Linux virq.
+ *
+ * Sets up virq and connects the irq plug.
+ */
+
+int ps3_irq_plug_setup(enum ps3_cpu_binding cpu, unsigned long outlet,
+       unsigned int *virq)
 {
        int result;
+       struct ps3_private *pd;
 
-       result = lv1_destruct_io_irq_outlet(virq_to_hw(virq));
+       result = ps3_virq_setup(cpu, outlet, virq);
 
-       if (!result)
-               pr_debug("%s:%d: lv1_destruct_io_irq_outlet failed: %s\n",
-                       __func__, __LINE__, ps3_result(result));
+       if (result) {
+               pr_debug("%s:%d: ps3_virq_setup failed\n", __func__, __LINE__);
+               goto fail_setup;
+       }
 
-       irq_dispose_mapping(virq);
+       pd = get_irq_chip_data(*virq);
+
+       /* Binds outlet to cpu + virq. */
+
+       result = lv1_connect_irq_plug_ext(pd->node, pd->cpu, *virq, outlet, 0);
+
+       if (result) {
+               pr_info("%s:%d: lv1_connect_irq_plug_ext failed: %s\n",
+               __func__, __LINE__, ps3_result(result));
+               result = -EPERM;
+               goto fail_connect;
+       }
+
+       return result;
+
+fail_connect:
+       ps3_virq_destroy(*virq);
+fail_setup:
+       return result;
+}
+EXPORT_SYMBOL_GPL(ps3_irq_plug_setup);
+
+/**
+ * ps3_irq_plug_destroy - Generic outlet and virq related teardown.
+ * @virq: The assigned Linux virq.
+ *
+ * Disconnects the irq plug and tears down virq.
+ * Do not call for system bus event interrupts setup with
+ * ps3_sb_event_receive_port_setup().
+ */
+
+int ps3_irq_plug_destroy(unsigned int virq)
+{
+       int result;
+       const struct ps3_private *pd = get_irq_chip_data(virq);
+
+       pr_debug("%s:%d: node %lu, cpu %d, virq %u\n", __func__, __LINE__,
+               pd->node, pd->cpu, virq);
+
+       result = lv1_disconnect_irq_plug_ext(pd->node, pd->cpu, virq);
+
+       if (result)
+               pr_info("%s:%d: lv1_disconnect_irq_plug_ext failed: %s\n",
+               __func__, __LINE__, ps3_result(result));
+
+       ps3_virq_destroy(virq);
 
        return result;
 }
+EXPORT_SYMBOL_GPL(ps3_irq_plug_destroy);
 
 /**
- * ps3_alloc_event_irq - Allocate a virq for use with a system event.
+ * ps3_event_receive_port_setup - Setup an event receive port.
+ * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be
+ * serviced on.
  * @virq: The assigned Linux virq.
  *
  * The virq can be used with lv1_connect_interrupt_event_receive_port() to
- * arrange to receive events, or with ps3_send_event_locally() to signal
- * events.
+ * arrange to receive interrupts from system-bus devices, or with
+ * ps3_send_event_locally() to signal events.
  */
 
-int ps3_alloc_event_irq(unsigned int *virq)
+int ps3_event_receive_port_setup(enum ps3_cpu_binding cpu, unsigned int *virq)
 {
        int result;
        unsigned long outlet;
@@ -103,19 +261,27 @@ int ps3_alloc_event_irq(unsigned int *virq)
                return result;
        }
 
-       *virq = irq_create_mapping(NULL, outlet);
-
-       pr_debug("%s:%d: outlet %lu, virq %u\n", __func__, __LINE__, outlet,
-               *virq);
+       result = ps3_irq_plug_setup(cpu, outlet, virq);
+       BUG_ON(result);
 
-       return 0;
+       return result;
 }
+EXPORT_SYMBOL_GPL(ps3_event_receive_port_setup);
+
+/**
+ * ps3_event_receive_port_destroy - Destroy an event receive port.
+ * @virq: The assigned Linux virq.
+ *
+ * Since ps3_event_receive_port_destroy destroys the receive port outlet,
+ * SB devices need to call disconnect_interrupt_event_receive_port() before
+ * this.
+ */
 
-int ps3_free_event_irq(unsigned int virq)
+int ps3_event_receive_port_destroy(unsigned int virq)
 {
        int result;
 
-       pr_debug(" -> %s:%d\n", __func__, __LINE__);
+       pr_debug(" -> %s:%d virq: %u\n", __func__, __LINE__, virq);
 
        result = lv1_destruct_event_receive_port(virq_to_hw(virq));
 
@@ -123,11 +289,17 @@ int ps3_free_event_irq(unsigned int virq)
                pr_debug("%s:%d: lv1_destruct_event_receive_port failed: %s\n",
                        __func__, __LINE__, ps3_result(result));
 
-       irq_dispose_mapping(virq);
+       /* lv1_destruct_event_receive_port() destroys the IRQ plug,
+        * so don't call ps3_irq_plug_destroy() here.
+        */
+
+       result = ps3_virq_destroy(virq);
+       BUG_ON(result);
 
        pr_debug(" <- %s:%d\n", __func__, __LINE__);
        return result;
 }
+EXPORT_SYMBOL_GPL(ps3_event_receive_port_destroy);
 
 int ps3_send_event_locally(unsigned int virq)
 {
@@ -135,7 +307,9 @@ int ps3_send_event_locally(unsigned int virq)
 }
 
 /**
- * ps3_connect_event_irq - Assign a virq to a system bus device.
+ * ps3_sb_event_receive_port_setup - Setup a system bus event receive port.
+ * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be
+ * serviced on.
  * @did: The HV device identifier read from the system repository.
  * @interrupt_id: The device interrupt id read from the system repository.
  * @virq: The assigned Linux virq.
@@ -144,12 +318,15 @@ int ps3_send_event_locally(unsigned int virq)
  * coresponds to the software interrupt number.
  */
 
-int ps3_connect_event_irq(const struct ps3_device_id *did,
-       unsigned int interrupt_id, unsigned int *virq)
+int ps3_sb_event_receive_port_setup(enum ps3_cpu_binding cpu,
+       const struct ps3_device_id *did, unsigned int interrupt_id,
+       unsigned int *virq)
 {
+       /* this should go in system-bus.c */
+
        int result;
 
-       result = ps3_alloc_event_irq(virq);
+       result = ps3_event_receive_port_setup(cpu, virq);
 
        if (result)
                return result;
@@ -161,7 +338,7 @@ int ps3_connect_event_irq(const struct ps3_device_id *did,
                pr_debug("%s:%d: lv1_connect_interrupt_event_receive_port"
                        " failed: %s\n", __func__, __LINE__,
                        ps3_result(result));
-               ps3_free_event_irq(*virq);
+               ps3_event_receive_port_destroy(*virq);
                *virq = NO_IRQ;
                return result;
        }
@@ -171,10 +348,13 @@ int ps3_connect_event_irq(const struct ps3_device_id *did,
 
        return 0;
 }
+EXPORT_SYMBOL(ps3_sb_event_receive_port_setup);
 
-int ps3_disconnect_event_irq(const struct ps3_device_id *did,
+int ps3_sb_event_receive_port_destroy(const struct ps3_device_id *did,
        unsigned int interrupt_id, unsigned int virq)
 {
+       /* this should go in system-bus.c */
+
        int result;
 
        pr_debug(" -> %s:%d: interrupt_id %u, virq %u\n", __func__, __LINE__,
@@ -188,14 +368,67 @@ int ps3_disconnect_event_irq(const struct ps3_device_id *did,
                        " failed: %s\n", __func__, __LINE__,
                        ps3_result(result));
 
-       ps3_free_event_irq(virq);
+       result = ps3_event_receive_port_destroy(virq);
+       BUG_ON(result);
 
        pr_debug(" <- %s:%d\n", __func__, __LINE__);
        return result;
 }
+EXPORT_SYMBOL(ps3_sb_event_receive_port_destroy);
 
 /**
- * ps3_alloc_vuart_irq - Configure the system virtual uart virq.
+ * ps3_io_irq_setup - Setup a system bus io irq.
+ * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be
+ * serviced on.
+ * @interrupt_id: The device interrupt id read from the system repository.
+ * @virq: The assigned Linux virq.
+ *
+ * An io irq represents a non-virtualized device interrupt.  interrupt_id
+ * coresponds to the interrupt number of the interrupt controller.
+ */
+
+int ps3_io_irq_setup(enum ps3_cpu_binding cpu, unsigned int interrupt_id,
+       unsigned int *virq)
+{
+       int result;
+       unsigned long outlet;
+
+       result = lv1_construct_io_irq_outlet(interrupt_id, &outlet);
+
+       if (result) {
+               pr_debug("%s:%d: lv1_construct_io_irq_outlet failed: %s\n",
+                       __func__, __LINE__, ps3_result(result));
+               return result;
+       }
+
+       result = ps3_irq_plug_setup(cpu, outlet, virq);
+       BUG_ON(result);
+
+       return result;
+}
+EXPORT_SYMBOL_GPL(ps3_io_irq_setup);
+
+int ps3_io_irq_destroy(unsigned int virq)
+{
+       int result;
+
+       result = lv1_destruct_io_irq_outlet(virq_to_hw(virq));
+
+       if (result)
+               pr_debug("%s:%d: lv1_destruct_io_irq_outlet failed: %s\n",
+                       __func__, __LINE__, ps3_result(result));
+
+       result = ps3_irq_plug_destroy(virq);
+       BUG_ON(result);
+
+       return result;
+}
+EXPORT_SYMBOL_GPL(ps3_io_irq_destroy);
+
+/**
+ * ps3_vuart_irq_setup - Setup the system virtual uart virq.
+ * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be
+ * serviced on.
  * @virt_addr_bmp: The caller supplied virtual uart interrupt bitmap.
  * @virq: The assigned Linux virq.
  *
@@ -203,13 +436,14 @@ int ps3_disconnect_event_irq(const struct ps3_device_id *did,
  * freeing the interrupt will return a wrong state error.
  */
 
-int ps3_alloc_vuart_irq(void* virt_addr_bmp, unsigned int *virq)
+int ps3_vuart_irq_setup(enum ps3_cpu_binding cpu, void* virt_addr_bmp,
+       unsigned int *virq)
 {
        int result;
        unsigned long outlet;
-       unsigned long lpar_addr;
+       u64 lpar_addr;
 
-       BUG_ON(!is_kernel_addr((unsigned long)virt_addr_bmp));
+       BUG_ON(!is_kernel_addr((u64)virt_addr_bmp));
 
        lpar_addr = ps3_mm_phys_to_lpar(__pa(virt_addr_bmp));
 
@@ -221,15 +455,13 @@ int ps3_alloc_vuart_irq(void* virt_addr_bmp, unsigned int *virq)
                return result;
        }
 
-       *virq = irq_create_mapping(NULL, outlet);
-
-       pr_debug("%s:%d: outlet %lu, virq %u\n", __func__, __LINE__,
-               outlet, *virq);
+       result = ps3_irq_plug_setup(cpu, outlet, virq);
+       BUG_ON(result);
 
-       return 0;
+       return result;
 }
 
-int ps3_free_vuart_irq(unsigned int virq)
+int ps3_vuart_irq_destroy(unsigned int virq)
 {
        int result;
 
@@ -241,21 +473,24 @@ int ps3_free_vuart_irq(unsigned int virq)
                return result;
        }
 
-       irq_dispose_mapping(virq);
+       result = ps3_irq_plug_destroy(virq);
+       BUG_ON(result);
 
        return result;
 }
 
 /**
- * ps3_alloc_spe_irq - Configure an spe virq.
+ * ps3_spe_irq_setup - Setup an spe virq.
+ * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be
+ * serviced on.
  * @spe_id: The spe_id returned from lv1_construct_logical_spe().
  * @class: The spe interrupt class {0,1,2}.
  * @virq: The assigned Linux virq.
  *
  */
 
-int ps3_alloc_spe_irq(unsigned long spe_id, unsigned int class,
-       unsigned int *virq)
+int ps3_spe_irq_setup(enum ps3_cpu_binding cpu, unsigned long spe_id,
+       unsigned int class, unsigned int *virq)
 {
        int result;
        unsigned long outlet;
@@ -270,73 +505,25 @@ int ps3_alloc_spe_irq(unsigned long spe_id, unsigned int class,
                return result;
        }
 
-       *virq = irq_create_mapping(NULL, outlet);
-
-       pr_debug("%s:%d: spe_id %lu, class %u, outlet %lu, virq %u\n",
-               __func__, __LINE__, spe_id, class, outlet, *virq);
+       result = ps3_irq_plug_setup(cpu, outlet, virq);
+       BUG_ON(result);
 
-       return 0;
+       return result;
 }
 
-int ps3_free_spe_irq(unsigned int virq)
+int ps3_spe_irq_destroy(unsigned int virq)
 {
-       irq_dispose_mapping(virq);
+       int result = ps3_irq_plug_destroy(virq);
+       BUG_ON(result);
        return 0;
 }
 
+
 #define PS3_INVALID_OUTLET ((irq_hw_number_t)-1)
 #define PS3_PLUG_MAX 63
 
-/**
- * struct bmp - a per cpu irq status and mask bitmap structure
- * @status: 256 bit status bitmap indexed by plug
- * @unused_1:
- * @mask: 256 bit mask bitmap indexed by plug
- * @unused_2:
- * @lock:
- * @ipi_debug_brk_mask:
- *
- * The HV mantains per SMT thread mappings of HV outlet to HV plug on
- * behalf of the guest.  These mappings are implemented as 256 bit guest
- * supplied bitmaps indexed by plug number.  The address of the bitmaps are
- * registered with the HV through lv1_configure_irq_state_bitmap().
- *
- * The HV supports 256 plugs per thread, assigned as {0..255}, for a total
- * of 512 plugs supported on a processor.  To simplify the logic this
- * implementation equates HV plug value to linux virq value, constrains each
- * interrupt to have a system wide unique plug number, and limits the range
- * of the plug values to map into the first dword of the bitmaps.  This
- * gives a usable range of plug values of  {NUM_ISA_INTERRUPTS..63}.  Note
- * that there is no constraint on how many in this set an individual thread
- * can aquire.
- */
-
-struct bmp {
-       struct {
-               unsigned long status;
-               unsigned long unused_1[3];
-               unsigned long mask;
-               unsigned long unused_2[3];
-       } __attribute__ ((packed));
-       spinlock_t lock;
-       unsigned long ipi_debug_brk_mask;
-};
-
-/**
- * struct private - a per cpu data structure
- * @node: HV node id
- * @cpu: HV thread id
- * @bmp: an HV bmp structure
- */
-
-struct private {
-       unsigned long node;
-       unsigned int cpu;
-       struct bmp bmp;
-};
-
 #if defined(DEBUG)
-static void _dump_64_bmp(const char *header, const unsigned long *p, unsigned cpu,
+static void _dump_64_bmp(const char *header, const u64 *p, unsigned cpu,
        const char* func, int line)
 {
        pr_debug("%s:%d: %s %u {%04lx_%04lx_%04lx_%04lx}\n",
@@ -346,14 +533,14 @@ static void _dump_64_bmp(const char *header, const unsigned long *p, unsigned cp
 }
 
 static void __attribute__ ((unused)) _dump_256_bmp(const char *header,
-       const unsigned long *p, unsigned cpu, const char* func, int line)
+       const u64 *p, unsigned cpu, const char* func, int line)
 {
        pr_debug("%s:%d: %s %u {%016lx:%016lx:%016lx:%016lx}\n",
                func, line, header, cpu, p[0], p[1], p[2], p[3]);
 }
 
 #define dump_bmp(_x) _dump_bmp(_x, __func__, __LINE__)
-static void _dump_bmp(struct private* pd, const char* func, int line)
+static void _dump_bmp(struct ps3_private* pd, const char* func, int line)
 {
        unsigned long flags;
 
@@ -364,7 +551,7 @@ static void _dump_bmp(struct private* pd, const char* func, int line)
 }
 
 #define dump_mask(_x) _dump_mask(_x, __func__, __LINE__)
-static void __attribute__ ((unused)) _dump_mask(struct private* pd,
+static void __attribute__ ((unused)) _dump_mask(struct ps3_private* pd,
        const char* func, int line)
 {
        unsigned long flags;
@@ -374,109 +561,94 @@ static void __attribute__ ((unused)) _dump_mask(struct private* pd,
        spin_unlock_irqrestore(&pd->bmp.lock, flags);
 }
 #else
-static void dump_bmp(struct private* pd) {};
+static void dump_bmp(struct ps3_private* pd) {};
 #endif /* defined(DEBUG) */
 
-static void chip_mask(unsigned int virq)
+static void ps3_chip_mask(unsigned int virq)
 {
+       struct ps3_private *pd = get_irq_chip_data(virq);
+       u64 bit = 0x8000000000000000UL >> virq;
+       u64 *p = &pd->bmp.mask;
+       u64 old;
        unsigned long flags;
-       struct private *pd = get_irq_chip_data(virq);
 
        pr_debug("%s:%d: cpu %u, virq %d\n", __func__, __LINE__, pd->cpu, virq);
 
-       BUG_ON(virq < NUM_ISA_INTERRUPTS);
-       BUG_ON(virq > PS3_PLUG_MAX);
-
-       spin_lock_irqsave(&pd->bmp.lock, flags);
-       pd->bmp.mask &= ~(0x8000000000000000UL >> virq);
-       spin_unlock_irqrestore(&pd->bmp.lock, flags);
+       local_irq_save(flags);
+       asm volatile(
+                    "1:        ldarx %0,0,%3\n"
+                    "andc      %0,%0,%2\n"
+                    "stdcx.    %0,0,%3\n"
+                    "bne-      1b"
+                    : "=&r" (old), "+m" (*p)
+                    : "r" (bit), "r" (p)
+                    : "cc" );
 
        lv1_did_update_interrupt_mask(pd->node, pd->cpu);
+       local_irq_restore(flags);
 }
 
-static void chip_unmask(unsigned int virq)
+static void ps3_chip_unmask(unsigned int virq)
 {
+       struct ps3_private *pd = get_irq_chip_data(virq);
+       u64 bit = 0x8000000000000000UL >> virq;
+       u64 *p = &pd->bmp.mask;
+       u64 old;
        unsigned long flags;
-       struct private *pd = get_irq_chip_data(virq);
 
        pr_debug("%s:%d: cpu %u, virq %d\n", __func__, __LINE__, pd->cpu, virq);
 
-       BUG_ON(virq < NUM_ISA_INTERRUPTS);
-       BUG_ON(virq > PS3_PLUG_MAX);
-
-       spin_lock_irqsave(&pd->bmp.lock, flags);
-       pd->bmp.mask |= (0x8000000000000000UL >> virq);
-       spin_unlock_irqrestore(&pd->bmp.lock, flags);
+       local_irq_save(flags);
+       asm volatile(
+                    "1:        ldarx %0,0,%3\n"
+                    "or        %0,%0,%2\n"
+                    "stdcx.    %0,0,%3\n"
+                    "bne-      1b"
+                    : "=&r" (old), "+m" (*p)
+                    : "r" (bit), "r" (p)
+                    : "cc" );
 
        lv1_did_update_interrupt_mask(pd->node, pd->cpu);
+       local_irq_restore(flags);
 }
 
-static void chip_eoi(unsigned int virq)
+static void ps3_chip_eoi(unsigned int virq)
 {
-       lv1_end_of_interrupt(virq);
+       const struct ps3_private *pd = get_irq_chip_data(virq);
+       lv1_end_of_interrupt_ext(pd->node, pd->cpu, virq);
 }
 
 static struct irq_chip irq_chip = {
        .typename = "ps3",
-       .mask = chip_mask,
-       .unmask = chip_unmask,
-       .eoi = chip_eoi,
+       .mask = ps3_chip_mask,
+       .unmask = ps3_chip_unmask,
+       .eoi = ps3_chip_eoi,
 };
 
-static void host_unmap(struct irq_host *h, unsigned int virq)
+static void ps3_host_unmap(struct irq_host *h, unsigned int virq)
 {
-       int result;
-
-       pr_debug("%s:%d: virq %d\n", __func__, __LINE__, virq);
-
-       lv1_disconnect_irq_plug(virq);
-
-       result = set_irq_chip_data(virq, NULL);
-       BUG_ON(result);
+       set_irq_chip_data(virq, NULL);
 }
 
-static DEFINE_PER_CPU(struct private, private);
-
-static int host_map(struct irq_host *h, unsigned int virq,
+static int ps3_host_map(struct irq_host *h, unsigned int virq,
        irq_hw_number_t hwirq)
 {
-       int result;
-       unsigned int cpu;
-
-       pr_debug(" -> %s:%d\n", __func__, __LINE__);
-       pr_debug("%s:%d: hwirq %lu => virq %u\n", __func__, __LINE__, hwirq,
+       pr_debug("%s:%d: hwirq %lu, virq %u\n", __func__, __LINE__, hwirq,
                virq);
 
-       /* bind this virq to a cpu */
-
-       preempt_disable();
-       cpu = smp_processor_id();
-       result = lv1_connect_irq_plug(virq, hwirq);
-       preempt_enable();
-
-       if (result) {
-               pr_info("%s:%d: lv1_connect_irq_plug failed:"
-                       " %s\n", __func__, __LINE__, ps3_result(result));
-               return -EPERM;
-       }
-
-       result = set_irq_chip_data(virq, &per_cpu(private, cpu));
-       BUG_ON(result);
-
        set_irq_chip_and_handler(virq, &irq_chip, handle_fasteoi_irq);
 
-       pr_debug(" <- %s:%d\n", __func__, __LINE__);
-       return result;
+       return 0;
 }
 
-static struct irq_host_ops host_ops = {
-       .map = host_map,
-       .unmap = host_unmap,
+static struct irq_host_ops ps3_host_ops = {
+       .map = ps3_host_map,
+       .unmap = ps3_host_unmap,
 };
 
 void __init ps3_register_ipi_debug_brk(unsigned int cpu, unsigned int virq)
 {
-       struct private *pd = &per_cpu(private, cpu);
+       struct ps3_private *pd = &per_cpu(ps3_private, cpu);
 
        pd->bmp.ipi_debug_brk_mask = 0x8000000000000000UL >> virq;
 
@@ -484,57 +656,32 @@ void __init ps3_register_ipi_debug_brk(unsigned int cpu, unsigned int virq)
                cpu, virq, pd->bmp.ipi_debug_brk_mask);
 }
 
-static int bmp_get_and_clear_status_bit(struct bmp *m)
+unsigned int ps3_get_irq(void)
 {
-       unsigned long flags;
-       unsigned int bit;
-       unsigned long x;
-
-       spin_lock_irqsave(&m->lock, flags);
+       struct ps3_private *pd = &__get_cpu_var(ps3_private);
+       u64 x = (pd->bmp.status & pd->bmp.mask);
+       unsigned int plug;
 
        /* check for ipi break first to stop this cpu ASAP */
 
-       if (m->status & m->ipi_debug_brk_mask) {
-               m->status &= ~m->ipi_debug_brk_mask;
-               spin_unlock_irqrestore(&m->lock, flags);
-               return __ilog2(m->ipi_debug_brk_mask);
-       }
-
-       x = (m->status & m->mask);
-
-       for (bit = NUM_ISA_INTERRUPTS, x <<= bit; x; bit++, x <<= 1)
-               if (x & 0x8000000000000000UL) {
-                       m->status &= ~(0x8000000000000000UL >> bit);
-                       spin_unlock_irqrestore(&m->lock, flags);
-                       return bit;
-               }
-
-       spin_unlock_irqrestore(&m->lock, flags);
-
-       pr_debug("%s:%d: not found\n", __func__, __LINE__);
-       return -1;
-}
-
-unsigned int ps3_get_irq(void)
-{
-       int plug;
+       if (x & pd->bmp.ipi_debug_brk_mask)
+               x &= pd->bmp.ipi_debug_brk_mask;
 
-       struct private *pd = &__get_cpu_var(private);
+       asm volatile("cntlzd %0,%1" : "=r" (plug) : "r" (x));
+       plug &= 0x3f;
 
-       plug = bmp_get_and_clear_status_bit(&pd->bmp);
-
-       if (plug < 1) {
+       if (unlikely(plug) == NO_IRQ) {
                pr_debug("%s:%d: no plug found: cpu %u\n", __func__, __LINE__,
                        pd->cpu);
-               dump_bmp(&per_cpu(private, 0));
-               dump_bmp(&per_cpu(private, 1));
+               dump_bmp(&per_cpu(ps3_private, 0));
+               dump_bmp(&per_cpu(ps3_private, 1));
                return NO_IRQ;
        }
 
 #if defined(DEBUG)
-       if (plug < NUM_ISA_INTERRUPTS || plug > PS3_PLUG_MAX) {
-               dump_bmp(&per_cpu(private, 0));
-               dump_bmp(&per_cpu(private, 1));
+       if (unlikely(plug < NUM_ISA_INTERRUPTS || plug > PS3_PLUG_MAX)) {
+               dump_bmp(&per_cpu(ps3_private, 0));
+               dump_bmp(&per_cpu(ps3_private, 1));
                BUG();
        }
 #endif
@@ -544,26 +691,27 @@ unsigned int ps3_get_irq(void)
 void __init ps3_init_IRQ(void)
 {
        int result;
-       unsigned long node;
        unsigned cpu;
        struct irq_host *host;
 
-       lv1_get_logical_ppe_id(&node);
-
-       host = irq_alloc_host(IRQ_HOST_MAP_NOMAP, 0, &host_ops,
+       host = irq_alloc_host(IRQ_HOST_MAP_NOMAP, 0, &ps3_host_ops,
                PS3_INVALID_OUTLET);
        irq_set_default_host(host);
        irq_set_virq_count(PS3_PLUG_MAX + 1);
 
        for_each_possible_cpu(cpu) {
-               struct private *pd = &per_cpu(private, cpu);
+               struct ps3_private *pd = &per_cpu(ps3_private, cpu);
 
-               pd->node = node;
-               pd->cpu = cpu;
+               lv1_get_logical_ppe_id(&pd->node);
+               pd->cpu = get_hard_smp_processor_id(cpu);
                spin_lock_init(&pd->bmp.lock);
 
-               result = lv1_configure_irq_state_bitmap(node, cpu,
-                       ps3_mm_phys_to_lpar(__pa(&pd->bmp.status)));
+               pr_debug("%s:%d: node %lu, cpu %d, bmp %lxh\n", __func__,
+                       __LINE__, pd->node, pd->cpu,
+                       ps3_mm_phys_to_lpar(__pa(&pd->bmp)));
+
+               result = lv1_configure_irq_state_bitmap(pd->node, pd->cpu,
+                       ps3_mm_phys_to_lpar(__pa(&pd->bmp)));
 
                if (result)
                        pr_debug("%s:%d: lv1_configure_irq_state_bitmap failed:"