2006-11-23 02:46:56 +03:00
/*
* PS3 interrupt routines .
*
* Copyright ( C ) 2006 Sony Computer Entertainment Inc .
* Copyright 2006 Sony Corp .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; version 2 of the License .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/irq.h>
# include <asm/machdep.h>
# include <asm/udbg.h>
# include <asm/ps3.h>
# include <asm/lv1call.h>
# include "platform.h"
# if defined(DEBUG)
# define DBG(fmt...) udbg_printf(fmt)
# else
# define DBG(fmt...) do{if(0)printk(fmt);}while(0)
# endif
/**
* ps3_alloc_io_irq - Assign a virq to a system bus device .
* 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_alloc_io_irq ( 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 ;
}
* virq = irq_create_mapping ( NULL , outlet ) ;
pr_debug ( " %s:%d: interrupt_id %u => outlet %lu, virq %u \n " ,
__func__ , __LINE__ , interrupt_id , outlet , * virq ) ;
return 0 ;
}
int ps3_free_io_irq ( unsigned int virq )
{
int result ;
result = lv1_destruct_io_irq_outlet ( virq_to_hw ( virq ) ) ;
2006-12-21 15:57:16 +03:00
if ( result )
2006-11-23 02:46:56 +03:00
pr_debug ( " %s:%d: lv1_destruct_io_irq_outlet failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
irq_dispose_mapping ( virq ) ;
return result ;
}
/**
* ps3_alloc_event_irq - Allocate a virq for use with a system event .
* @ 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 .
*/
int ps3_alloc_event_irq ( unsigned int * virq )
{
int result ;
unsigned long outlet ;
result = lv1_construct_event_receive_port ( & outlet ) ;
if ( result ) {
pr_debug ( " %s:%d: lv1_construct_event_receive_port failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
* virq = NO_IRQ ;
return result ;
}
* virq = irq_create_mapping ( NULL , outlet ) ;
pr_debug ( " %s:%d: outlet %lu, virq %u \n " , __func__ , __LINE__ , outlet ,
* virq ) ;
return 0 ;
}
int ps3_free_event_irq ( unsigned int virq )
{
int result ;
pr_debug ( " -> %s:%d \n " , __func__ , __LINE__ ) ;
result = lv1_destruct_event_receive_port ( virq_to_hw ( virq ) ) ;
if ( result )
pr_debug ( " %s:%d: lv1_destruct_event_receive_port failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
irq_dispose_mapping ( virq ) ;
pr_debug ( " <- %s:%d \n " , __func__ , __LINE__ ) ;
return result ;
}
int ps3_send_event_locally ( unsigned int virq )
{
return lv1_send_event_locally ( virq_to_hw ( virq ) ) ;
}
/**
* ps3_connect_event_irq - Assign a virq to a system bus device .
* @ 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 .
*
* An event irq represents a virtual device interrupt . The interrupt_id
* 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 result ;
result = ps3_alloc_event_irq ( virq ) ;
if ( result )
return result ;
result = lv1_connect_interrupt_event_receive_port ( did - > bus_id ,
did - > dev_id , virq_to_hw ( * virq ) , interrupt_id ) ;
if ( result ) {
pr_debug ( " %s:%d: lv1_connect_interrupt_event_receive_port "
" failed: %s \n " , __func__ , __LINE__ ,
ps3_result ( result ) ) ;
ps3_free_event_irq ( * virq ) ;
* virq = NO_IRQ ;
return result ;
}
pr_debug ( " %s:%d: interrupt_id %u, virq %u \n " , __func__ , __LINE__ ,
interrupt_id , * virq ) ;
return 0 ;
}
int ps3_disconnect_event_irq ( const struct ps3_device_id * did ,
unsigned int interrupt_id , unsigned int virq )
{
int result ;
pr_debug ( " -> %s:%d: interrupt_id %u, virq %u \n " , __func__ , __LINE__ ,
interrupt_id , virq ) ;
result = lv1_disconnect_interrupt_event_receive_port ( did - > bus_id ,
did - > dev_id , virq_to_hw ( virq ) , interrupt_id ) ;
if ( result )
pr_debug ( " %s:%d: lv1_disconnect_interrupt_event_receive_port "
" failed: %s \n " , __func__ , __LINE__ ,
ps3_result ( result ) ) ;
ps3_free_event_irq ( virq ) ;
pr_debug ( " <- %s:%d \n " , __func__ , __LINE__ ) ;
return result ;
}
/**
* ps3_alloc_vuart_irq - Configure the system virtual uart virq .
* @ virt_addr_bmp : The caller supplied virtual uart interrupt bitmap .
* @ virq : The assigned Linux virq .
*
* The system supports only a single virtual uart , so multiple calls without
* freeing the interrupt will return a wrong state error .
*/
int ps3_alloc_vuart_irq ( void * virt_addr_bmp , unsigned int * virq )
{
int result ;
unsigned long outlet ;
unsigned long lpar_addr ;
BUG_ON ( ! is_kernel_addr ( ( unsigned long ) virt_addr_bmp ) ) ;
lpar_addr = ps3_mm_phys_to_lpar ( __pa ( virt_addr_bmp ) ) ;
result = lv1_configure_virtual_uart_irq ( lpar_addr , & outlet ) ;
if ( result ) {
pr_debug ( " %s:%d: lv1_configure_virtual_uart_irq failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
return result ;
}
* virq = irq_create_mapping ( NULL , outlet ) ;
pr_debug ( " %s:%d: outlet %lu, virq %u \n " , __func__ , __LINE__ ,
outlet , * virq ) ;
return 0 ;
}
int ps3_free_vuart_irq ( unsigned int virq )
{
int result ;
result = lv1_deconfigure_virtual_uart_irq ( ) ;
if ( result ) {
pr_debug ( " %s:%d: lv1_configure_virtual_uart_irq failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
return result ;
}
irq_dispose_mapping ( virq ) ;
return result ;
}
/**
* ps3_alloc_spe_irq - Configure an spe virq .
* @ 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 result ;
unsigned long outlet ;
BUG_ON ( class > 2 ) ;
result = lv1_get_spe_irq_outlet ( spe_id , class , & outlet ) ;
if ( result ) {
pr_debug ( " %s:%d: lv1_get_spe_irq_outlet failed: %s \n " ,
__func__ , __LINE__ , ps3_result ( result ) ) ;
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 ) ;
return 0 ;
}
int ps3_free_spe_irq ( unsigned int virq )
{
irq_dispose_mapping ( virq ) ;
return 0 ;
}
# define PS3_INVALID_OUTLET ((irq_hw_number_t)-1)
# define PS3_PLUG_MAX 63
/**
2007-01-27 06:07:59 +03:00
* struct ps3_bmp - a per cpu irq status and mask bitmap structure
2006-11-23 02:46:56 +03:00
* @ 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 .
*/
2007-01-27 06:07:59 +03:00
struct ps3_bmp {
2006-11-23 02:46:56 +03:00
struct {
unsigned long status ;
unsigned long unused_1 [ 3 ] ;
unsigned long mask ;
unsigned long unused_2 [ 3 ] ;
2007-01-27 06:08:02 +03:00
} __attribute__ ( ( aligned ( 64 ) ) ) ;
2006-11-23 02:46:56 +03:00
spinlock_t lock ;
unsigned long ipi_debug_brk_mask ;
} ;
/**
2007-01-27 06:07:59 +03:00
* struct ps3_private - a per cpu data structure
2007-01-27 06:08:02 +03:00
* @ bmp : ps3_bmp structure
* @ node : HV logical_ppe_id
* @ cpu : HV thread_id
2006-11-23 02:46:56 +03:00
*/
2007-01-27 06:07:59 +03:00
struct ps3_private {
2007-01-27 06:08:02 +03:00
struct ps3_bmp bmp ;
2006-11-23 02:46:56 +03:00
unsigned long node ;
unsigned int cpu ;
} ;
# if defined(DEBUG)
static void _dump_64_bmp ( const char * header , const unsigned long * p , unsigned cpu ,
const char * func , int line )
{
pr_debug ( " %s:%d: %s %u {%04lx_%04lx_%04lx_%04lx} \n " ,
func , line , header , cpu ,
* p > > 48 , ( * p > > 32 ) & 0xffff , ( * p > > 16 ) & 0xffff ,
* p & 0xffff ) ;
}
static void __attribute__ ( ( unused ) ) _dump_256_bmp ( const char * header ,
const unsigned long * 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__)
2007-01-27 06:07:59 +03:00
static void _dump_bmp ( struct ps3_private * pd , const char * func , int line )
2006-11-23 02:46:56 +03:00
{
unsigned long flags ;
spin_lock_irqsave ( & pd - > bmp . lock , flags ) ;
_dump_64_bmp ( " stat " , & pd - > bmp . status , pd - > cpu , func , line ) ;
_dump_64_bmp ( " mask " , & pd - > bmp . mask , pd - > cpu , func , line ) ;
spin_unlock_irqrestore ( & pd - > bmp . lock , flags ) ;
}
# define dump_mask(_x) _dump_mask(_x, __func__, __LINE__)
2007-01-27 06:07:59 +03:00
static void __attribute__ ( ( unused ) ) _dump_mask ( struct ps3_private * pd ,
2006-11-23 02:46:56 +03:00
const char * func , int line )
{
unsigned long flags ;
spin_lock_irqsave ( & pd - > bmp . lock , flags ) ;
_dump_64_bmp ( " mask " , & pd - > bmp . mask , pd - > cpu , func , line ) ;
spin_unlock_irqrestore ( & pd - > bmp . lock , flags ) ;
}
# else
2007-01-27 06:07:59 +03:00
static void dump_bmp ( struct ps3_private * pd ) { } ;
2006-11-23 02:46:56 +03:00
# endif /* defined(DEBUG) */
2007-01-27 06:07:59 +03:00
static void ps3_chip_mask ( unsigned int virq )
2006-11-23 02:46:56 +03:00
{
2007-01-27 06:07:59 +03:00
struct ps3_private * pd = get_irq_chip_data ( virq ) ;
2007-01-27 06:08:05 +03:00
unsigned long bit = 0x8000000000000000UL > > virq ;
unsigned long * p = & pd - > bmp . mask ;
unsigned long old ;
unsigned long flags ;
2006-11-23 02:46:56 +03:00
pr_debug ( " %s:%d: cpu %u, virq %d \n " , __func__ , __LINE__ , pd - > cpu , virq ) ;
2007-01-27 06:08:05 +03:00
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 " ) ;
2006-11-23 02:46:56 +03:00
lv1_did_update_interrupt_mask ( pd - > node , pd - > cpu ) ;
2007-01-27 06:08:05 +03:00
local_irq_restore ( flags ) ;
2006-11-23 02:46:56 +03:00
}
2007-01-27 06:07:59 +03:00
static void ps3_chip_unmask ( unsigned int virq )
2006-11-23 02:46:56 +03:00
{
2007-01-27 06:07:59 +03:00
struct ps3_private * pd = get_irq_chip_data ( virq ) ;
2007-01-27 06:08:05 +03:00
unsigned long bit = 0x8000000000000000UL > > virq ;
unsigned long * p = & pd - > bmp . mask ;
unsigned long old ;
unsigned long flags ;
2006-11-23 02:46:56 +03:00
pr_debug ( " %s:%d: cpu %u, virq %d \n " , __func__ , __LINE__ , pd - > cpu , virq ) ;
2007-01-27 06:08:05 +03:00
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 " ) ;
2006-11-23 02:46:56 +03:00
lv1_did_update_interrupt_mask ( pd - > node , pd - > cpu ) ;
2007-01-27 06:08:05 +03:00
local_irq_restore ( flags ) ;
2006-11-23 02:46:56 +03:00
}
2007-01-27 06:07:59 +03:00
static void ps3_chip_eoi ( unsigned int virq )
2006-11-23 02:46:56 +03:00
{
2007-01-27 06:08:02 +03:00
const struct ps3_private * pd = get_irq_chip_data ( virq ) ;
lv1_end_of_interrupt_ext ( pd - > node , pd - > cpu , virq ) ;
2006-11-23 02:46:56 +03:00
}
static struct irq_chip irq_chip = {
. typename = " ps3 " ,
2007-01-27 06:07:59 +03:00
. mask = ps3_chip_mask ,
. unmask = ps3_chip_unmask ,
. eoi = ps3_chip_eoi ,
2006-11-23 02:46:56 +03:00
} ;
2007-01-27 06:07:59 +03:00
static void ps3_host_unmap ( struct irq_host * h , unsigned int virq )
2006-11-23 02:46:56 +03:00
{
int result ;
2007-01-27 06:08:02 +03:00
const struct ps3_private * pd = get_irq_chip_data ( virq ) ;
2006-11-23 02:46:56 +03:00
2007-01-27 06:08:02 +03:00
pr_debug ( " %s:%d: node %lu, cpu %d, virq %u \n " , __func__ , __LINE__ ,
pd - > node , pd - > cpu , virq ) ;
2006-11-23 02:46:56 +03:00
2007-01-27 06:08:02 +03:00
lv1_disconnect_irq_plug_ext ( pd - > node , pd - > cpu , virq ) ;
2006-11-23 02:46:56 +03:00
result = set_irq_chip_data ( virq , NULL ) ;
BUG_ON ( result ) ;
}
2007-01-27 06:07:59 +03:00
static DEFINE_PER_CPU ( struct ps3_private , ps3_private ) ;
2006-11-23 02:46:56 +03:00
2007-01-27 06:07:59 +03:00
static int ps3_host_map ( struct irq_host * h , unsigned int virq ,
2006-11-23 02:46:56 +03:00
irq_hw_number_t hwirq )
{
int result ;
2007-01-27 06:08:02 +03:00
struct ps3_private * pd = & __get_cpu_var ( ps3_private ) ;
2006-11-23 02:46:56 +03:00
2007-01-27 06:08:02 +03:00
pr_debug ( " %s:%d: node %lu, cpu %d, hwirq %lu => virq %u \n " , __func__ ,
__LINE__ , pd - > node , pd - > cpu , hwirq , virq ) ;
2006-11-23 02:46:56 +03:00
2007-01-27 06:08:02 +03:00
/* Binds this virq to pd->cpu (current cpu) */
2006-11-23 02:46:56 +03:00
2007-01-27 06:08:02 +03:00
result = lv1_connect_irq_plug_ext ( pd - > node , pd - > cpu , virq , hwirq , 0 ) ;
2006-11-23 02:46:56 +03:00
if ( result ) {
2007-01-27 06:08:02 +03:00
pr_info ( " %s:%d: lv1_connect_irq_plug_ext failed: "
2006-11-23 02:46:56 +03:00
" %s \n " , __func__ , __LINE__ , ps3_result ( result ) ) ;
return - EPERM ;
}
2007-01-27 06:08:02 +03:00
result = set_irq_chip_data ( virq , pd ) ;
2006-11-23 02:46:56 +03:00
BUG_ON ( result ) ;
set_irq_chip_and_handler ( virq , & irq_chip , handle_fasteoi_irq ) ;
return result ;
}
2007-01-27 06:07:59 +03:00
static struct irq_host_ops ps3_host_ops = {
. map = ps3_host_map ,
. unmap = ps3_host_unmap ,
2006-11-23 02:46:56 +03:00
} ;
void __init ps3_register_ipi_debug_brk ( unsigned int cpu , unsigned int virq )
{
2007-01-27 06:07:59 +03:00
struct ps3_private * pd = & per_cpu ( ps3_private , cpu ) ;
2006-11-23 02:46:56 +03:00
pd - > bmp . ipi_debug_brk_mask = 0x8000000000000000UL > > virq ;
pr_debug ( " %s:%d: cpu %u, virq %u, mask %lxh \n " , __func__ , __LINE__ ,
cpu , virq , pd - > bmp . ipi_debug_brk_mask ) ;
}
2007-01-27 06:08:05 +03:00
unsigned int ps3_get_irq ( void )
2006-11-23 02:46:56 +03:00
{
2007-01-27 06:08:05 +03:00
struct ps3_private * pd = & __get_cpu_var ( ps3_private ) ;
unsigned long x = ( pd - > bmp . status & pd - > bmp . mask ) ;
unsigned int plug ;
2006-11-23 02:46:56 +03:00
/* check for ipi break first to stop this cpu ASAP */
2007-01-27 06:08:05 +03:00
if ( x & pd - > bmp . ipi_debug_brk_mask )
x & = pd - > bmp . ipi_debug_brk_mask ;
2006-11-23 02:46:56 +03:00
2007-01-27 06:08:05 +03:00
asm volatile ( " cntlzd %0,%1 " : " =r " ( plug ) : " r " ( x ) ) ;
plug & = 0x3f ;
2006-11-23 02:46:56 +03:00
2007-01-27 06:08:05 +03:00
if ( unlikely ( plug ) = = NO_IRQ ) {
2006-11-23 02:46:56 +03:00
pr_debug ( " %s:%d: no plug found: cpu %u \n " , __func__ , __LINE__ ,
pd - > cpu ) ;
2007-01-27 06:07:59 +03:00
dump_bmp ( & per_cpu ( ps3_private , 0 ) ) ;
dump_bmp ( & per_cpu ( ps3_private , 1 ) ) ;
2006-11-23 02:46:56 +03:00
return NO_IRQ ;
}
# if defined(DEBUG)
2007-01-27 06:08:05 +03:00
if ( unlikely ( plug < NUM_ISA_INTERRUPTS | | plug > PS3_PLUG_MAX ) ) {
2007-01-27 06:07:59 +03:00
dump_bmp ( & per_cpu ( ps3_private , 0 ) ) ;
dump_bmp ( & per_cpu ( ps3_private , 1 ) ) ;
2006-11-23 02:46:56 +03:00
BUG ( ) ;
}
# endif
return plug ;
}
void __init ps3_init_IRQ ( void )
{
int result ;
unsigned cpu ;
struct irq_host * host ;
2007-01-27 06:07:59 +03:00
host = irq_alloc_host ( IRQ_HOST_MAP_NOMAP , 0 , & ps3_host_ops ,
2006-11-23 02:46:56 +03:00
PS3_INVALID_OUTLET ) ;
irq_set_default_host ( host ) ;
irq_set_virq_count ( PS3_PLUG_MAX + 1 ) ;
for_each_possible_cpu ( cpu ) {
2007-01-27 06:07:59 +03:00
struct ps3_private * pd = & per_cpu ( ps3_private , cpu ) ;
2006-11-23 02:46:56 +03:00
2007-01-27 06:08:02 +03:00
lv1_get_logical_ppe_id ( & pd - > node ) ;
pd - > cpu = get_hard_smp_processor_id ( cpu ) ;
2006-11-23 02:46:56 +03:00
spin_lock_init ( & pd - > bmp . lock ) ;
2007-01-27 06:08:02 +03:00
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 ) ) ) ;
2006-11-23 02:46:56 +03:00
if ( result )
pr_debug ( " %s:%d: lv1_configure_irq_state_bitmap failed: "
" %s \n " , __func__ , __LINE__ ,
ps3_result ( result ) ) ;
}
ppc_md . get_irq = ps3_get_irq ;
}