/*
* arch / ppc64 / kernel / iSeries_iommu . c
*
* Copyright ( C ) 2001 Mike Corrigan & Dave Engebretsen , IBM Corporation
*
* Rewrite , cleanup :
*
* Copyright ( C ) 2004 Olof Johansson < olof @ austin . ibm . com > , IBM Corporation
*
* Dynamic DMA mapping support , iSeries - specific parts .
*
*
* 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 of the License , 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 . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/types.h>
# include <linux/dma-mapping.h>
# include <linux/list.h>
# include <asm/iommu.h>
# include <asm/machdep.h>
# include <asm/iSeries/HvCallXm.h>
# include <asm/iSeries/iSeries_pci.h>
extern struct list_head iSeries_Global_Device_List ;
static void tce_build_iSeries ( struct iommu_table * tbl , long index , long npages ,
unsigned long uaddr , enum dma_data_direction direction )
{
u64 rc ;
union tce_entry tce ;
while ( npages - - ) {
tce . te_word = 0 ;
tce . te_bits . tb_rpn = virt_to_abs ( uaddr ) > > PAGE_SHIFT ;
if ( tbl - > it_type = = TCE_VB ) {
/* Virtual Bus */
tce . te_bits . tb_valid = 1 ;
tce . te_bits . tb_allio = 1 ;
if ( direction ! = DMA_TO_DEVICE )
tce . te_bits . tb_rdwr = 1 ;
} else {
/* PCI Bus */
tce . te_bits . tb_rdwr = 1 ; /* Read allowed */
if ( direction ! = DMA_TO_DEVICE )
tce . te_bits . tb_pciwr = 1 ;
}
rc = HvCallXm_setTce ( ( u64 ) tbl - > it_index , ( u64 ) index ,
tce . te_word ) ;
if ( rc )
panic ( " PCI_DMA: HvCallXm_setTce failed, Rc: 0x%lx \n " ,
rc ) ;
index + + ;
uaddr + = PAGE_SIZE ;
}
}
static void tce_free_iSeries ( struct iommu_table * tbl , long index , long npages )
{
u64 rc ;
while ( npages - - ) {
rc = HvCallXm_setTce ( ( u64 ) tbl - > it_index , ( u64 ) index , 0 ) ;
if ( rc )
panic ( " PCI_DMA: HvCallXm_setTce failed, Rc: 0x%lx \n " ,
rc ) ;
index + + ;
}
}
# ifdef CONFIG_PCI
/*
* This function compares the known tables to find an iommu_table
* that has already been built for hardware TCEs .
*/
static struct iommu_table * iommu_table_find ( struct iommu_table * tbl )
{
struct iSeries_Device_Node * dp ;
list_for_each_entry ( dp , & iSeries_Global_Device_List , Device_List ) {
if ( ( dp - > iommu_table ! = NULL ) & &
( dp - > iommu_table - > it_type = = TCE_PCI ) & &
( dp - > iommu_table - > it_offset = = tbl - > it_offset ) & &
( dp - > iommu_table - > it_index = = tbl - > it_index ) & &
( dp - > iommu_table - > it_size = = tbl - > it_size ) )
return dp - > iommu_table ;
}
return NULL ;
}
/*
* Call Hv with the architected data structure to get TCE table info .
* info . Put the returned data into the Linux representation of the
* TCE table data .
* The Hardware Tce table comes in three flavors .
* 1. TCE table shared between Buses .
* 2. TCE table per Bus .
* 3. TCE Table per IOA .
*/
static void iommu_table_getparms ( struct iSeries_Device_Node * dn ,
struct iommu_table * tbl )
{
struct iommu_table_cb * parms ;
parms = kmalloc ( sizeof ( * parms ) , GFP_KERNEL ) ;
if ( parms = = NULL )
panic ( " PCI_DMA: TCE Table Allocation failed. " ) ;
memset ( parms , 0 , sizeof ( * parms ) ) ;
parms - > itc_busno = ISERIES_BUS ( dn ) ;
parms - > itc_slotno = dn - > LogicalSlot ;
parms - > itc_virtbus = 0 ;
HvCallXm_getTceTableParms ( ISERIES_HV_ADDR ( parms ) ) ;
if ( parms - > itc_size = = 0 )
panic ( " PCI_DMA: parms->size is zero, parms is 0x%p " , parms ) ;
/* itc_size is in pages worth of table, it_size is in # of entries */
tbl - > it_size = ( parms - > itc_size * PAGE_SIZE ) / sizeof ( union tce_entry ) ;
tbl - > it_busno = parms - > itc_busno ;
tbl - > it_offset = parms - > itc_offset ;
tbl - > it_index = parms - > itc_index ;
tbl - > it_blocksize = 1 ;
tbl - > it_type = TCE_PCI ;
kfree ( parms ) ;
}
void iommu_devnode_init_iSeries ( struct iSeries_Device_Node * dn )
{
struct iommu_table * tbl ;
tbl = kmalloc ( sizeof ( struct iommu_table ) , GFP_KERNEL ) ;
iommu_table_getparms ( dn , tbl ) ;
/* Look for existing tce table */
dn - > iommu_table = iommu_table_find ( tbl ) ;
if ( dn - > iommu_table = = NULL )
dn - > iommu_table = iommu_init_table ( tbl ) ;
else
kfree ( tbl ) ;
}
# endif
static void iommu_dev_setup_iSeries ( struct pci_dev * dev ) { }
static void iommu_bus_setup_iSeries ( struct pci_bus * bus ) { }
void iommu_init_early_iSeries ( void )
{
ppc_md . tce_build = tce_build_iSeries ;
ppc_md . tce_free = tce_free_iSeries ;
ppc_md . iommu_dev_setup = iommu_dev_setup_iSeries ;
ppc_md . iommu_bus_setup = iommu_bus_setup_iSeries ;
pci_iommu_init ( ) ;
}