2005-11-15 23:53:48 +03:00
/*
* Low - level SPU handling
*
* ( C ) Copyright IBM Deutschland Entwicklung GmbH 2005
*
* Author : Arnd Bergmann < arndb @ de . ibm . com >
*
* 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 ; either version 2 , or ( at your option )
* any later version .
*
* 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 . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
2005-12-06 06:52:24 +03:00
# undef DEBUG
2005-11-15 23:53:48 +03:00
# include <linux/interrupt.h>
# include <linux/list.h>
# include <linux/module.h>
2006-10-04 19:26:20 +04:00
# include <linux/pci.h>
2005-11-15 23:53:48 +03:00
# include <linux/poll.h>
# include <linux/ptrace.h>
# include <linux/slab.h>
# include <linux/wait.h>
2006-10-04 19:26:20 +04:00
# include <asm/firmware.h>
2005-11-15 23:53:48 +03:00
# include <asm/io.h>
# include <asm/prom.h>
2006-03-26 13:37:14 +04:00
# include <linux/mutex.h>
2005-11-15 23:53:48 +03:00
# include <asm/spu.h>
2006-06-19 22:33:29 +04:00
# include <asm/spu_priv1.h>
2005-11-15 23:53:48 +03:00
# include <asm/mmu_context.h>
# include "interrupt.h"
2006-06-19 22:33:29 +04:00
const struct spu_priv1_ops * spu_priv1_ops ;
EXPORT_SYMBOL_GPL ( spu_priv1_ops ) ;
2005-11-15 23:53:48 +03:00
static int __spu_trap_invalid_dma ( struct spu * spu )
{
pr_debug ( " %s \n " , __FUNCTION__ ) ;
2006-10-04 19:26:14 +04:00
spu - > dma_callback ( spu , SPE_EVENT_INVALID_DMA ) ;
2005-11-15 23:53:48 +03:00
return 0 ;
}
static int __spu_trap_dma_align ( struct spu * spu )
{
pr_debug ( " %s \n " , __FUNCTION__ ) ;
2006-10-04 19:26:14 +04:00
spu - > dma_callback ( spu , SPE_EVENT_DMA_ALIGNMENT ) ;
2005-11-15 23:53:48 +03:00
return 0 ;
}
static int __spu_trap_error ( struct spu * spu )
{
pr_debug ( " %s \n " , __FUNCTION__ ) ;
2006-10-04 19:26:14 +04:00
spu - > dma_callback ( spu , SPE_EVENT_SPE_ERROR ) ;
2005-11-15 23:53:48 +03:00
return 0 ;
}
static void spu_restart_dma ( struct spu * spu )
{
struct spu_priv2 __iomem * priv2 = spu - > priv2 ;
2005-11-15 23:53:49 +03:00
2006-01-04 22:31:28 +03:00
if ( ! test_bit ( SPU_CONTEXT_SWITCH_PENDING , & spu - > flags ) )
2005-11-15 23:53:49 +03:00
out_be64 ( & priv2 - > mfc_control_RW , MFC_CNTL_RESTART_DMA_COMMAND ) ;
2005-11-15 23:53:48 +03:00
}
static int __spu_trap_data_seg ( struct spu * spu , unsigned long ea )
{
2005-11-15 23:53:52 +03:00
struct spu_priv2 __iomem * priv2 = spu - > priv2 ;
struct mm_struct * mm = spu - > mm ;
2006-06-19 22:33:23 +04:00
u64 esid , vsid , llp ;
2005-11-15 23:53:48 +03:00
pr_debug ( " %s \n " , __FUNCTION__ ) ;
2006-01-04 22:31:28 +03:00
if ( test_bit ( SPU_CONTEXT_SWITCH_ACTIVE , & spu - > flags ) ) {
2005-11-15 23:53:52 +03:00
/* SLBs are pre-loaded for context switch, so
* we should never get here !
*/
2005-11-15 23:53:49 +03:00
printk ( " %s: invalid access during switch! \n " , __func__ ) ;
return 1 ;
}
2005-11-15 23:53:52 +03:00
if ( ! mm | | ( REGION_ID ( ea ) ! = USER_REGION_ID ) ) {
/* Future: support kernel segments so that drivers
* can use SPUs .
*/
2005-11-15 23:53:48 +03:00
pr_debug ( " invalid region access at %016lx \n " , ea ) ;
return 1 ;
}
2005-11-15 23:53:52 +03:00
esid = ( ea & ESID_MASK ) | SLB_ESID_V ;
2006-06-19 22:33:23 +04:00
# ifdef CONFIG_HUGETLB_PAGE
2005-11-15 23:53:52 +03:00
if ( in_hugepage_area ( mm - > context , ea ) )
2006-06-19 22:33:23 +04:00
llp = mmu_psize_defs [ mmu_huge_psize ] . sllp ;
else
# endif
llp = mmu_psize_defs [ mmu_virtual_psize ] . sllp ;
vsid = ( get_vsid ( mm - > context . id , ea ) < < SLB_VSID_SHIFT ) |
SLB_VSID_USER | llp ;
2005-11-15 23:53:48 +03:00
2005-11-15 23:53:52 +03:00
out_be64 ( & priv2 - > slb_index_W , spu - > slb_replace ) ;
out_be64 ( & priv2 - > slb_vsid_RW , vsid ) ;
out_be64 ( & priv2 - > slb_esid_RW , esid ) ;
spu - > slb_replace + + ;
2005-11-15 23:53:48 +03:00
if ( spu - > slb_replace > = 8 )
spu - > slb_replace = 0 ;
spu_restart_dma ( spu ) ;
return 0 ;
}
2005-11-15 23:53:49 +03:00
extern int hash_page ( unsigned long ea , unsigned long access , unsigned long trap ) ; //XXX
2005-11-15 23:53:52 +03:00
static int __spu_trap_data_map ( struct spu * spu , unsigned long ea , u64 dsisr )
2005-11-15 23:53:48 +03:00
{
2006-03-23 02:00:11 +03:00
pr_debug ( " %s, %lx, %lx \n " , __FUNCTION__ , dsisr , ea ) ;
2005-11-15 23:53:48 +03:00
2005-11-15 23:53:49 +03:00
/* Handle kernel space hash faults immediately.
User hash faults need to be deferred to process context . */
if ( ( dsisr & MFC_DSISR_PTE_NOT_FOUND )
& & REGION_ID ( ea ) ! = USER_REGION_ID
& & hash_page ( ea , _PAGE_PRESENT , 0x300 ) = = 0 ) {
spu_restart_dma ( spu ) ;
return 0 ;
}
2006-01-04 22:31:28 +03:00
if ( test_bit ( SPU_CONTEXT_SWITCH_ACTIVE , & spu - > flags ) ) {
2005-11-15 23:53:49 +03:00
printk ( " %s: invalid access during switch! \n " , __func__ ) ;
return 1 ;
}
2005-11-15 23:53:48 +03:00
2005-11-15 23:53:52 +03:00
spu - > dar = ea ;
spu - > dsisr = dsisr ;
mb ( ) ;
2006-06-19 22:33:33 +04:00
spu - > stop_callback ( spu ) ;
2005-11-15 23:53:48 +03:00
return 0 ;
}
static irqreturn_t
2006-10-07 00:52:16 +04:00
spu_irq_class_0 ( int irq , void * data )
2005-11-15 23:53:48 +03:00
{
struct spu * spu ;
spu = data ;
spu - > class_0_pending = 1 ;
2006-06-19 22:33:33 +04:00
spu - > stop_callback ( spu ) ;
2005-11-15 23:53:48 +03:00
return IRQ_HANDLED ;
}
2005-12-06 06:52:25 +03:00
int
2005-11-15 23:53:48 +03:00
spu_irq_class_0_bottom ( struct spu * spu )
{
2005-12-06 06:52:27 +03:00
unsigned long stat , mask ;
2005-11-15 23:53:48 +03:00
spu - > class_0_pending = 0 ;
2006-01-04 22:31:30 +03:00
mask = spu_int_mask_get ( spu , 0 ) ;
stat = spu_int_stat_get ( spu , 0 ) ;
2005-11-15 23:53:48 +03:00
2005-12-06 06:52:27 +03:00
stat & = mask ;
2006-06-23 22:57:50 +04:00
if ( stat & 1 ) /* invalid DMA alignment */
2005-11-15 23:53:48 +03:00
__spu_trap_dma_align ( spu ) ;
2006-06-23 22:57:50 +04:00
if ( stat & 2 ) /* invalid MFC DMA */
__spu_trap_invalid_dma ( spu ) ;
2005-11-15 23:53:48 +03:00
if ( stat & 4 ) /* error on SPU */
__spu_trap_error ( spu ) ;
2006-01-04 22:31:30 +03:00
spu_int_stat_clear ( spu , 0 , stat ) ;
2005-12-06 06:52:25 +03:00
return ( stat & 0x7 ) ? - EIO : 0 ;
2005-11-15 23:53:48 +03:00
}
2005-12-06 06:52:25 +03:00
EXPORT_SYMBOL_GPL ( spu_irq_class_0_bottom ) ;
2005-11-15 23:53:48 +03:00
static irqreturn_t
2006-10-07 00:52:16 +04:00
spu_irq_class_1 ( int irq , void * data )
2005-11-15 23:53:48 +03:00
{
struct spu * spu ;
2005-11-15 23:53:52 +03:00
unsigned long stat , mask , dar , dsisr ;
2005-11-15 23:53:48 +03:00
spu = data ;
2005-11-15 23:53:52 +03:00
/* atomically read & clear class1 status. */
spin_lock ( & spu - > register_lock ) ;
2006-01-04 22:31:30 +03:00
mask = spu_int_mask_get ( spu , 1 ) ;
stat = spu_int_stat_get ( spu , 1 ) & mask ;
dar = spu_mfc_dar_get ( spu ) ;
dsisr = spu_mfc_dsisr_get ( spu ) ;
2005-12-09 21:04:18 +03:00
if ( stat & 2 ) /* mapping fault */
2006-01-04 22:31:30 +03:00
spu_mfc_dsisr_set ( spu , 0ul ) ;
spu_int_stat_clear ( spu , 1 , stat ) ;
2005-11-15 23:53:52 +03:00
spin_unlock ( & spu - > register_lock ) ;
2006-03-23 02:00:11 +03:00
pr_debug ( " %s: %lx %lx %lx %lx \n " , __FUNCTION__ , mask , stat ,
dar , dsisr ) ;
2005-11-15 23:53:48 +03:00
if ( stat & 1 ) /* segment fault */
__spu_trap_data_seg ( spu , dar ) ;
if ( stat & 2 ) { /* mapping fault */
2005-11-15 23:53:52 +03:00
__spu_trap_data_map ( spu , dar , dsisr ) ;
2005-11-15 23:53:48 +03:00
}
if ( stat & 4 ) /* ls compare & suspend on get */
;
if ( stat & 8 ) /* ls compare & suspend on put */
;
return stat ? IRQ_HANDLED : IRQ_NONE ;
}
2005-12-06 06:52:25 +03:00
EXPORT_SYMBOL_GPL ( spu_irq_class_1_bottom ) ;
2005-11-15 23:53:48 +03:00
static irqreturn_t
2006-10-07 00:52:16 +04:00
spu_irq_class_2 ( int irq , void * data )
2005-11-15 23:53:48 +03:00
{
struct spu * spu ;
unsigned long stat ;
2005-12-06 06:52:27 +03:00
unsigned long mask ;
2005-11-15 23:53:48 +03:00
spu = data ;
2006-06-19 22:33:33 +04:00
spin_lock ( & spu - > register_lock ) ;
2006-01-04 22:31:30 +03:00
stat = spu_int_stat_get ( spu , 2 ) ;
mask = spu_int_mask_get ( spu , 2 ) ;
2006-06-19 22:33:33 +04:00
/* ignore interrupts we're not waiting for */
stat & = mask ;
/*
* mailbox interrupts ( 0x1 and 0x10 ) are level triggered .
* mask them now before acknowledging .
*/
if ( stat & 0x11 )
spu_int_mask_and ( spu , 2 , ~ ( stat & 0x11 ) ) ;
/* acknowledge all interrupts before the callbacks */
spu_int_stat_clear ( spu , 2 , stat ) ;
spin_unlock ( & spu - > register_lock ) ;
2005-11-15 23:53:48 +03:00
2005-12-06 06:52:27 +03:00
pr_debug ( " class 2 interrupt %d, %lx, %lx \n " , irq , stat , mask ) ;
2005-11-15 23:53:48 +03:00
if ( stat & 1 ) /* PPC core mailbox */
2006-06-19 22:33:33 +04:00
spu - > ibox_callback ( spu ) ;
2005-11-15 23:53:48 +03:00
if ( stat & 2 ) /* SPU stop-and-signal */
2006-06-19 22:33:33 +04:00
spu - > stop_callback ( spu ) ;
2005-11-15 23:53:48 +03:00
if ( stat & 4 ) /* SPU halted */
2006-06-19 22:33:33 +04:00
spu - > stop_callback ( spu ) ;
2005-11-15 23:53:48 +03:00
if ( stat & 8 ) /* DMA tag group complete */
2006-06-19 22:33:33 +04:00
spu - > mfc_callback ( spu ) ;
2005-11-15 23:53:48 +03:00
if ( stat & 0x10 ) /* SPU mailbox threshold */
2006-06-19 22:33:33 +04:00
spu - > wbox_callback ( spu ) ;
2005-11-15 23:53:48 +03:00
return stat ? IRQ_HANDLED : IRQ_NONE ;
}
2006-07-03 15:36:01 +04:00
static int spu_request_irqs ( struct spu * spu )
2005-11-15 23:53:48 +03:00
{
2006-07-03 15:36:01 +04:00
int ret = 0 ;
2005-11-15 23:53:48 +03:00
2006-07-03 15:36:01 +04:00
if ( spu - > irqs [ 0 ] ! = NO_IRQ ) {
snprintf ( spu - > irq_c0 , sizeof ( spu - > irq_c0 ) , " spe%02d.0 " ,
spu - > number ) ;
ret = request_irq ( spu - > irqs [ 0 ] , spu_irq_class_0 ,
IRQF_DISABLED ,
spu - > irq_c0 , spu ) ;
if ( ret )
goto bail0 ;
}
if ( spu - > irqs [ 1 ] ! = NO_IRQ ) {
snprintf ( spu - > irq_c1 , sizeof ( spu - > irq_c1 ) , " spe%02d.1 " ,
spu - > number ) ;
ret = request_irq ( spu - > irqs [ 1 ] , spu_irq_class_1 ,
IRQF_DISABLED ,
spu - > irq_c1 , spu ) ;
if ( ret )
goto bail1 ;
}
if ( spu - > irqs [ 2 ] ! = NO_IRQ ) {
snprintf ( spu - > irq_c2 , sizeof ( spu - > irq_c2 ) , " spe%02d.2 " ,
spu - > number ) ;
ret = request_irq ( spu - > irqs [ 2 ] , spu_irq_class_2 ,
IRQF_DISABLED ,
spu - > irq_c2 , spu ) ;
if ( ret )
goto bail2 ;
}
return 0 ;
2005-11-15 23:53:48 +03:00
2006-07-03 15:36:01 +04:00
bail2 :
if ( spu - > irqs [ 1 ] ! = NO_IRQ )
free_irq ( spu - > irqs [ 1 ] , spu ) ;
bail1 :
if ( spu - > irqs [ 0 ] ! = NO_IRQ )
free_irq ( spu - > irqs [ 0 ] , spu ) ;
bail0 :
2005-11-15 23:53:48 +03:00
return ret ;
}
2006-07-03 15:36:01 +04:00
static void spu_free_irqs ( struct spu * spu )
2005-11-15 23:53:48 +03:00
{
2006-07-03 15:36:01 +04:00
if ( spu - > irqs [ 0 ] ! = NO_IRQ )
free_irq ( spu - > irqs [ 0 ] , spu ) ;
if ( spu - > irqs [ 1 ] ! = NO_IRQ )
free_irq ( spu - > irqs [ 1 ] , spu ) ;
if ( spu - > irqs [ 2 ] ! = NO_IRQ )
free_irq ( spu - > irqs [ 2 ] , spu ) ;
2005-11-15 23:53:48 +03:00
}
2006-10-04 19:26:12 +04:00
static struct list_head spu_list [ MAX_NUMNODES ] ;
2006-03-26 13:37:14 +04:00
static DEFINE_MUTEX ( spu_mutex ) ;
2005-11-15 23:53:48 +03:00
static void spu_init_channels ( struct spu * spu )
{
static const struct {
unsigned channel ;
unsigned count ;
} zero_list [ ] = {
{ 0x00 , 1 , } , { 0x01 , 1 , } , { 0x03 , 1 , } , { 0x04 , 1 , } ,
{ 0x18 , 1 , } , { 0x19 , 1 , } , { 0x1b , 1 , } , { 0x1d , 1 , } ,
} , count_list [ ] = {
{ 0x00 , 0 , } , { 0x03 , 0 , } , { 0x04 , 0 , } , { 0x15 , 16 , } ,
{ 0x17 , 1 , } , { 0x18 , 0 , } , { 0x19 , 0 , } , { 0x1b , 0 , } ,
{ 0x1c , 1 , } , { 0x1d , 0 , } , { 0x1e , 1 , } ,
} ;
2006-01-04 22:31:31 +03:00
struct spu_priv2 __iomem * priv2 ;
2005-11-15 23:53:48 +03:00
int i ;
priv2 = spu - > priv2 ;
/* initialize all channel data to zero */
for ( i = 0 ; i < ARRAY_SIZE ( zero_list ) ; i + + ) {
int count ;
out_be64 ( & priv2 - > spu_chnlcntptr_RW , zero_list [ i ] . channel ) ;
for ( count = 0 ; count < zero_list [ i ] . count ; count + + )
out_be64 ( & priv2 - > spu_chnldata_RW , 0 ) ;
}
/* initialize channel counts to meaningful values */
for ( i = 0 ; i < ARRAY_SIZE ( count_list ) ; i + + ) {
out_be64 ( & priv2 - > spu_chnlcntptr_RW , count_list [ i ] . channel ) ;
out_be64 ( & priv2 - > spu_chnlcnt_RW , count_list [ i ] . count ) ;
}
}
2006-10-04 19:26:12 +04:00
struct spu * spu_alloc_node ( int node )
2005-11-15 23:53:48 +03:00
{
2006-10-04 19:26:12 +04:00
struct spu * spu = NULL ;
2005-11-15 23:53:48 +03:00
2006-03-26 13:37:14 +04:00
mutex_lock ( & spu_mutex ) ;
2006-10-04 19:26:12 +04:00
if ( ! list_empty ( & spu_list [ node ] ) ) {
spu = list_entry ( spu_list [ node ] . next , struct spu , list ) ;
2005-11-15 23:53:48 +03:00
list_del_init ( & spu - > list ) ;
2006-10-04 19:26:12 +04:00
pr_debug ( " Got SPU %x %d %d \n " ,
spu - > isrc , spu - > number , spu - > node ) ;
spu_init_channels ( spu ) ;
2005-11-15 23:53:48 +03:00
}
2006-03-26 13:37:14 +04:00
mutex_unlock ( & spu_mutex ) ;
2005-11-15 23:53:48 +03:00
2006-10-04 19:26:12 +04:00
return spu ;
}
EXPORT_SYMBOL_GPL ( spu_alloc_node ) ;
struct spu * spu_alloc ( void )
{
struct spu * spu = NULL ;
int node ;
for ( node = 0 ; node < MAX_NUMNODES ; node + + ) {
spu = spu_alloc_node ( node ) ;
if ( spu )
break ;
}
2005-11-15 23:53:48 +03:00
return spu ;
}
void spu_free ( struct spu * spu )
{
2006-03-26 13:37:14 +04:00
mutex_lock ( & spu_mutex ) ;
2006-10-04 19:26:12 +04:00
list_add_tail ( & spu - > list , & spu_list [ spu - > node ] ) ;
2006-03-26 13:37:14 +04:00
mutex_unlock ( & spu_mutex ) ;
2005-11-15 23:53:48 +03:00
}
2005-12-06 06:52:21 +03:00
EXPORT_SYMBOL_GPL ( spu_free ) ;
2005-11-15 23:53:48 +03:00
static int spu_handle_mm_fault ( struct spu * spu )
{
struct mm_struct * mm = spu - > mm ;
struct vm_area_struct * vma ;
u64 ea , dsisr , is_write ;
int ret ;
2005-11-15 23:53:52 +03:00
ea = spu - > dar ;
dsisr = spu - > dsisr ;
2005-11-15 23:53:48 +03:00
#if 0
if ( ! IS_VALID_EA ( ea ) ) {
return - EFAULT ;
}
# endif /* XXX */
if ( mm = = NULL ) {
return - EFAULT ;
}
if ( mm - > pgd = = NULL ) {
return - EFAULT ;
}
down_read ( & mm - > mmap_sem ) ;
vma = find_vma ( mm , ea ) ;
if ( ! vma )
goto bad_area ;
if ( vma - > vm_start < = ea )
goto good_area ;
if ( ! ( vma - > vm_flags & VM_GROWSDOWN ) )
goto bad_area ;
#if 0
if ( expand_stack ( vma , ea ) )
goto bad_area ;
# endif /* XXX */
good_area :
is_write = dsisr & MFC_DSISR_ACCESS_PUT ;
if ( is_write ) {
if ( ! ( vma - > vm_flags & VM_WRITE ) )
goto bad_area ;
} else {
if ( dsisr & MFC_DSISR_ACCESS_DENIED )
goto bad_area ;
if ( ! ( vma - > vm_flags & ( VM_READ | VM_EXEC ) ) )
goto bad_area ;
}
ret = 0 ;
switch ( handle_mm_fault ( mm , vma , ea , is_write ) ) {
case VM_FAULT_MINOR :
current - > min_flt + + ;
break ;
case VM_FAULT_MAJOR :
current - > maj_flt + + ;
break ;
case VM_FAULT_SIGBUS :
ret = - EFAULT ;
goto bad_area ;
case VM_FAULT_OOM :
ret = - ENOMEM ;
goto bad_area ;
default :
BUG ( ) ;
}
up_read ( & mm - > mmap_sem ) ;
return ret ;
bad_area :
up_read ( & mm - > mmap_sem ) ;
return - EFAULT ;
}
2005-12-06 06:52:25 +03:00
int spu_irq_class_1_bottom ( struct spu * spu )
2005-11-15 23:53:48 +03:00
{
u64 ea , dsisr , access , error = 0UL ;
int ret = 0 ;
2005-11-15 23:53:52 +03:00
ea = spu - > dar ;
dsisr = spu - > dsisr ;
2006-03-24 21:49:27 +03:00
if ( dsisr & ( MFC_DSISR_PTE_NOT_FOUND | MFC_DSISR_ACCESS_DENIED ) ) {
2006-04-29 04:40:21 +04:00
u64 flags ;
2005-11-15 23:53:52 +03:00
access = ( _PAGE_PRESENT | _PAGE_USER ) ;
access | = ( dsisr & MFC_DSISR_ACCESS_PUT ) ? _PAGE_RW : 0UL ;
2006-04-29 04:40:21 +04:00
local_irq_save ( flags ) ;
2005-11-15 23:53:48 +03:00
if ( hash_page ( ea , access , 0x300 ) ! = 0 )
error | = CLASS1_ENABLE_STORAGE_FAULT_INTR ;
2006-04-29 04:40:21 +04:00
local_irq_restore ( flags ) ;
2005-11-15 23:53:48 +03:00
}
2006-03-24 21:49:27 +03:00
if ( error & CLASS1_ENABLE_STORAGE_FAULT_INTR ) {
2005-11-15 23:53:48 +03:00
if ( ( ret = spu_handle_mm_fault ( spu ) ) ! = 0 )
error | = CLASS1_ENABLE_STORAGE_FAULT_INTR ;
else
error & = ~ CLASS1_ENABLE_STORAGE_FAULT_INTR ;
}
2005-11-15 23:53:52 +03:00
spu - > dar = 0UL ;
spu - > dsisr = 0UL ;
if ( ! error ) {
2005-11-15 23:53:48 +03:00
spu_restart_dma ( spu ) ;
2005-11-15 23:53:52 +03:00
} else {
__spu_trap_invalid_dma ( spu ) ;
}
2005-11-15 23:53:48 +03:00
return ret ;
}
2006-05-01 23:16:11 +04:00
static int __init find_spu_node_id ( struct device_node * spe )
{
2006-07-12 09:39:54 +04:00
const unsigned int * id ;
2006-05-01 23:16:11 +04:00
struct device_node * cpu ;
cpu = spe - > parent - > parent ;
2006-07-12 09:39:54 +04:00
id = get_property ( cpu , " node-id " , NULL ) ;
2006-05-01 23:16:11 +04:00
return id ? * id : 0 ;
}
2006-05-01 23:16:13 +04:00
static int __init cell_spuprop_present ( struct spu * spu , struct device_node * spe ,
const char * prop )
2006-05-01 23:16:11 +04:00
{
static DEFINE_MUTEX ( add_spumem_mutex ) ;
2006-07-12 09:39:54 +04:00
const struct address_prop {
2006-05-01 23:16:11 +04:00
unsigned long address ;
unsigned int len ;
} __attribute__ ( ( packed ) ) * p ;
int proplen ;
unsigned long start_pfn , nr_pages ;
struct pglist_data * pgdata ;
struct zone * zone ;
int ret ;
2006-07-12 09:39:54 +04:00
p = get_property ( spe , prop , & proplen ) ;
2006-05-01 23:16:11 +04:00
WARN_ON ( proplen ! = sizeof ( * p ) ) ;
start_pfn = p - > address > > PAGE_SHIFT ;
nr_pages = ( ( unsigned long ) p - > len + PAGE_SIZE - 1 ) > > PAGE_SHIFT ;
2006-05-01 23:16:13 +04:00
pgdata = NODE_DATA ( spu - > nid ) ;
2006-05-01 23:16:11 +04:00
zone = pgdata - > node_zones ;
/* XXX rethink locking here */
mutex_lock ( & add_spumem_mutex ) ;
ret = __add_pages ( zone , start_pfn , nr_pages ) ;
mutex_unlock ( & add_spumem_mutex ) ;
return ret ;
}
2006-05-01 23:16:13 +04:00
static void __iomem * __init map_spe_prop ( struct spu * spu ,
struct device_node * n , const char * name )
2005-11-15 23:53:48 +03:00
{
2006-07-12 09:39:54 +04:00
const struct address_prop {
2005-11-15 23:53:48 +03:00
unsigned long address ;
unsigned int len ;
} __attribute__ ( ( packed ) ) * prop ;
2006-07-12 09:39:54 +04:00
const void * p ;
2005-11-15 23:53:48 +03:00
int proplen ;
2006-09-23 04:37:41 +04:00
void __iomem * ret = NULL ;
2006-05-01 23:16:11 +04:00
int err = 0 ;
2005-11-15 23:53:48 +03:00
p = get_property ( n , name , & proplen ) ;
if ( proplen ! = sizeof ( struct address_prop ) )
return NULL ;
prop = p ;
2006-05-01 23:16:13 +04:00
err = cell_spuprop_present ( spu , n , name ) ;
2006-05-01 23:16:11 +04:00
if ( err & & ( err ! = - EEXIST ) )
goto out ;
ret = ioremap ( prop - > address , prop - > len ) ;
out :
return ret ;
2005-11-15 23:53:48 +03:00
}
static void spu_unmap ( struct spu * spu )
{
iounmap ( spu - > priv2 ) ;
iounmap ( spu - > priv1 ) ;
iounmap ( spu - > problem ) ;
2006-09-23 04:37:41 +04:00
iounmap ( ( __force u8 __iomem * ) spu - > local_store ) ;
2005-11-15 23:53:48 +03:00
}
2006-07-03 15:36:01 +04:00
/* This function shall be abstracted for HV platforms */
2006-10-04 19:26:20 +04:00
static int __init spu_map_interrupts_old ( struct spu * spu , struct device_node * np )
2006-07-03 15:36:01 +04:00
{
unsigned int isrc ;
2006-07-12 09:39:54 +04:00
const u32 * tmp ;
2006-07-03 15:36:01 +04:00
2006-09-29 09:00:29 +04:00
/* Get the interrupt source unit from the device-tree */
2006-07-12 09:39:54 +04:00
tmp = get_property ( np , " isrc " , NULL ) ;
2006-07-03 15:36:01 +04:00
if ( ! tmp )
return - ENODEV ;
2006-09-29 09:00:29 +04:00
isrc = tmp [ 0 ] ;
/* Add the node number */
isrc | = spu - > node < < IIC_IRQ_NODE_SHIFT ;
spu - > isrc = isrc ;
2006-07-03 15:36:01 +04:00
/* Now map interrupts of all 3 classes */
2006-09-29 09:00:29 +04:00
spu - > irqs [ 0 ] = irq_create_mapping ( NULL , IIC_IRQ_CLASS_0 | isrc ) ;
spu - > irqs [ 1 ] = irq_create_mapping ( NULL , IIC_IRQ_CLASS_1 | isrc ) ;
spu - > irqs [ 2 ] = irq_create_mapping ( NULL , IIC_IRQ_CLASS_2 | isrc ) ;
2006-07-03 15:36:01 +04:00
/* Right now, we only fail if class 2 failed */
return spu - > irqs [ 2 ] = = NO_IRQ ? - EINVAL : 0 ;
}
2006-10-04 19:26:20 +04:00
static int __init spu_map_device_old ( struct spu * spu , struct device_node * node )
2005-11-15 23:53:48 +03:00
{
2006-07-12 09:39:54 +04:00
const char * prop ;
2005-11-15 23:53:48 +03:00
int ret ;
ret = - ENODEV ;
2006-05-01 23:16:13 +04:00
spu - > name = get_property ( node , " name " , NULL ) ;
2005-11-15 23:53:48 +03:00
if ( ! spu - > name )
goto out ;
2006-05-01 23:16:13 +04:00
prop = get_property ( node , " local-store " , NULL ) ;
2005-11-15 23:53:48 +03:00
if ( ! prop )
goto out ;
spu - > local_store_phys = * ( unsigned long * ) prop ;
/* we use local store as ram, not io memory */
2006-05-01 23:16:13 +04:00
spu - > local_store = ( void __force * )
map_spe_prop ( spu , node , " local-store " ) ;
2005-11-15 23:53:48 +03:00
if ( ! spu - > local_store )
goto out ;
2006-05-01 23:16:13 +04:00
prop = get_property ( node , " problem " , NULL ) ;
2006-03-23 02:00:12 +03:00
if ( ! prop )
goto out_unmap ;
spu - > problem_phys = * ( unsigned long * ) prop ;
2006-05-01 23:16:13 +04:00
spu - > problem = map_spe_prop ( spu , node , " problem " ) ;
2005-11-15 23:53:48 +03:00
if ( ! spu - > problem )
goto out_unmap ;
2006-05-01 23:16:13 +04:00
spu - > priv1 = map_spe_prop ( spu , node , " priv1 " ) ;
2006-01-04 22:31:30 +03:00
/* priv1 is not available on a hypervisor */
2005-11-15 23:53:48 +03:00
2006-05-01 23:16:13 +04:00
spu - > priv2 = map_spe_prop ( spu , node , " priv2 " ) ;
2005-11-15 23:53:48 +03:00
if ( ! spu - > priv2 )
goto out_unmap ;
ret = 0 ;
goto out ;
out_unmap :
spu_unmap ( spu ) ;
out :
return ret ;
}
2006-10-04 19:26:20 +04:00
static int __init spu_map_interrupts ( struct spu * spu , struct device_node * np )
{
struct of_irq oirq ;
int ret ;
int i ;
for ( i = 0 ; i < 3 ; i + + ) {
ret = of_irq_map_one ( np , i , & oirq ) ;
if ( ret )
goto err ;
ret = - EINVAL ;
spu - > irqs [ i ] = irq_create_of_mapping ( oirq . controller ,
oirq . specifier , oirq . size ) ;
if ( spu - > irqs [ i ] = = NO_IRQ )
goto err ;
}
return 0 ;
err :
pr_debug ( " failed to map irq %x for spu %s \n " , * oirq . specifier , spu - > name ) ;
for ( ; i > = 0 ; i - - ) {
if ( spu - > irqs [ i ] ! = NO_IRQ )
irq_dispose_mapping ( spu - > irqs [ i ] ) ;
}
return ret ;
}
static int spu_map_resource ( struct device_node * node , int nr ,
void __iomem * * virt , unsigned long * phys )
{
struct resource resource = { } ;
int ret ;
ret = of_address_to_resource ( node , 0 , & resource ) ;
if ( ret )
goto out ;
if ( phys )
* phys = resource . start ;
* virt = ioremap ( resource . start , resource . end - resource . start ) ;
if ( ! * virt )
ret = - EINVAL ;
out :
return ret ;
}
static int __init spu_map_device ( struct spu * spu , struct device_node * node )
{
int ret = - ENODEV ;
spu - > name = get_property ( node , " name " , NULL ) ;
if ( ! spu - > name )
goto out ;
ret = spu_map_resource ( node , 0 , ( void __iomem * * ) & spu - > local_store ,
& spu - > local_store_phys ) ;
if ( ret )
goto out ;
ret = spu_map_resource ( node , 1 , ( void __iomem * * ) & spu - > problem ,
& spu - > problem_phys ) ;
if ( ret )
goto out_unmap ;
ret = spu_map_resource ( node , 2 , ( void __iomem * * ) & spu - > priv2 ,
NULL ) ;
if ( ret )
goto out_unmap ;
if ( ! firmware_has_feature ( FW_FEATURE_LPAR ) )
ret = spu_map_resource ( node , 3 , ( void __iomem * * ) & spu - > priv1 ,
NULL ) ;
if ( ret )
goto out_unmap ;
return 0 ;
out_unmap :
spu_unmap ( spu ) ;
out :
pr_debug ( " failed to map spe %s: %d \n " , spu - > name , ret ) ;
return ret ;
}
2006-06-19 22:33:19 +04:00
struct sysdev_class spu_sysdev_class = {
set_kset_name ( " spu " )
} ;
static ssize_t spu_show_isrc ( struct sys_device * sysdev , char * buf )
{
struct spu * spu = container_of ( sysdev , struct spu , sysdev ) ;
return sprintf ( buf , " %d \n " , spu - > isrc ) ;
}
static SYSDEV_ATTR ( isrc , 0400 , spu_show_isrc , NULL ) ;
extern int attach_sysdev_to_node ( struct sys_device * dev , int nid ) ;
static int spu_create_sysdev ( struct spu * spu )
{
int ret ;
spu - > sysdev . id = spu - > number ;
spu - > sysdev . cls = & spu_sysdev_class ;
ret = sysdev_register ( & spu - > sysdev ) ;
if ( ret ) {
printk ( KERN_ERR " Can't register SPU %d with sysfs \n " ,
spu - > number ) ;
return ret ;
}
2006-07-03 15:36:01 +04:00
if ( spu - > isrc ! = 0 )
sysdev_create_file ( & spu - > sysdev , & attr_isrc ) ;
2006-06-19 22:33:19 +04:00
sysfs_add_device_to_node ( & spu - > sysdev , spu - > nid ) ;
return 0 ;
}
static void spu_destroy_sysdev ( struct spu * spu )
{
sysdev_remove_file ( & spu - > sysdev , & attr_isrc ) ;
sysfs_remove_device_from_node ( & spu - > sysdev , spu - > nid ) ;
sysdev_unregister ( & spu - > sysdev ) ;
}
2005-11-15 23:53:48 +03:00
static int __init create_spu ( struct device_node * spe )
{
struct spu * spu ;
int ret ;
static int number ;
ret = - ENOMEM ;
2006-06-19 22:33:26 +04:00
spu = kzalloc ( sizeof ( * spu ) , GFP_KERNEL ) ;
2005-11-15 23:53:48 +03:00
if ( ! spu )
goto out ;
2006-10-10 09:14:12 +04:00
spu - > node = find_spu_node_id ( spe ) ;
if ( spu - > node > = MAX_NUMNODES ) {
printk ( KERN_WARNING " SPE %s on node %d ignored, "
" node number too big \n " , spe - > full_name , spu - > node ) ;
printk ( KERN_WARNING " Check if CONFIG_NUMA is enabled. \n " ) ;
return - ENODEV ;
}
spu - > nid = of_node_to_nid ( spe ) ;
if ( spu - > nid = = - 1 )
spu - > nid = 0 ;
2005-11-15 23:53:48 +03:00
ret = spu_map_device ( spu , spe ) ;
2006-10-04 19:26:20 +04:00
/* try old method */
if ( ret )
ret = spu_map_device_old ( spu , spe ) ;
2005-11-15 23:53:48 +03:00
if ( ret )
goto out_free ;
2006-07-03 15:36:01 +04:00
ret = spu_map_interrupts ( spu , spe ) ;
2006-10-04 19:26:20 +04:00
if ( ret )
ret = spu_map_interrupts_old ( spu , spe ) ;
2006-07-03 15:36:01 +04:00
if ( ret )
goto out_unmap ;
2005-11-15 23:53:48 +03:00
spin_lock_init ( & spu - > register_lock ) ;
2006-10-24 20:31:14 +04:00
spu_mfc_sdr_setup ( spu ) ;
2006-01-04 22:31:30 +03:00
spu_mfc_sr1_set ( spu , 0x33 ) ;
2006-03-26 13:37:14 +04:00
mutex_lock ( & spu_mutex ) ;
2006-06-19 22:33:26 +04:00
2005-11-15 23:53:48 +03:00
spu - > number = number + + ;
ret = spu_request_irqs ( spu ) ;
if ( ret )
2006-10-04 19:26:20 +04:00
goto out_unlock ;
2005-11-15 23:53:48 +03:00
2006-06-19 22:33:19 +04:00
ret = spu_create_sysdev ( spu ) ;
if ( ret )
goto out_free_irqs ;
2006-10-04 19:26:12 +04:00
list_add ( & spu - > list , & spu_list [ spu - > node ] ) ;
2006-03-26 13:37:14 +04:00
mutex_unlock ( & spu_mutex ) ;
2005-11-15 23:53:48 +03:00
pr_debug ( KERN_DEBUG " Using SPE %s %02x %p %p %p %p %d \n " ,
spu - > name , spu - > isrc , spu - > local_store ,
spu - > problem , spu - > priv1 , spu - > priv2 , spu - > number ) ;
goto out ;
2006-06-19 22:33:19 +04:00
out_free_irqs :
spu_free_irqs ( spu ) ;
2006-10-04 19:26:20 +04:00
out_unlock :
2006-03-26 13:37:14 +04:00
mutex_unlock ( & spu_mutex ) ;
2006-10-04 19:26:20 +04:00
out_unmap :
2005-11-15 23:53:48 +03:00
spu_unmap ( spu ) ;
out_free :
kfree ( spu ) ;
out :
return ret ;
}
static void destroy_spu ( struct spu * spu )
{
list_del_init ( & spu - > list ) ;
2006-06-19 22:33:19 +04:00
spu_destroy_sysdev ( spu ) ;
2005-11-15 23:53:48 +03:00
spu_free_irqs ( spu ) ;
spu_unmap ( spu ) ;
kfree ( spu ) ;
}
static void cleanup_spu_base ( void )
{
struct spu * spu , * tmp ;
2006-10-04 19:26:12 +04:00
int node ;
2006-03-26 13:37:14 +04:00
mutex_lock ( & spu_mutex ) ;
2006-10-04 19:26:12 +04:00
for ( node = 0 ; node < MAX_NUMNODES ; node + + ) {
list_for_each_entry_safe ( spu , tmp , & spu_list [ node ] , list )
destroy_spu ( spu ) ;
}
2006-03-26 13:37:14 +04:00
mutex_unlock ( & spu_mutex ) ;
2006-06-19 22:33:19 +04:00
sysdev_class_unregister ( & spu_sysdev_class ) ;
2005-11-15 23:53:48 +03:00
}
module_exit ( cleanup_spu_base ) ;
static int __init init_spu_base ( void )
{
struct device_node * node ;
2006-10-04 19:26:12 +04:00
int i , ret ;
2005-11-15 23:53:48 +03:00
2006-06-19 22:33:19 +04:00
/* create sysdev class for spus */
ret = sysdev_class_register ( & spu_sysdev_class ) ;
if ( ret )
return ret ;
2006-10-04 19:26:12 +04:00
for ( i = 0 ; i < MAX_NUMNODES ; i + + )
INIT_LIST_HEAD ( & spu_list [ i ] ) ;
2005-11-15 23:53:48 +03:00
ret = - ENODEV ;
for ( node = of_find_node_by_type ( NULL , " spe " ) ;
node ; node = of_find_node_by_type ( node , " spe " ) ) {
ret = create_spu ( node ) ;
if ( ret ) {
printk ( KERN_WARNING " %s: Error initializing %s \n " ,
__FUNCTION__ , node - > name ) ;
cleanup_spu_base ( ) ;
break ;
}
}
return ret ;
}
module_init ( init_spu_base ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Arnd Bergmann <arndb@de.ibm.com> " ) ;