2006-06-26 15:58:14 +04:00
/*
2006-09-26 12:52:29 +04:00
* This file manages the translation entries for the IBM Calgary IOMMU .
*
2006-06-26 15:58:14 +04:00
* Derived from arch / powerpc / platforms / pseries / iommu . c
*
2006-07-10 19:06:15 +04:00
* Copyright ( C ) IBM Corporation , 2006
*
* Author : Jon Mason < jdmason @ us . ibm . com >
* Author : Muli Ben - Yehuda < muli @ il . ibm . com >
2006-06-26 15:58:14 +04:00
*
* 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/slab.h>
# include <linux/mm.h>
# include <linux/spinlock.h>
# include <linux/string.h>
# include <linux/pci.h>
# include <linux/dma-mapping.h>
# include <linux/bootmem.h>
# include <asm/tce.h>
# include <asm/calgary.h>
# include <asm/proto.h>
/* flush a tce at 'tceaddr' to main memory */
static inline void flush_tce ( void * tceaddr )
{
/* a single tce can't cross a cache line */
if ( cpu_has_clflush )
2007-10-17 20:04:37 +04:00
clflush ( tceaddr ) ;
2006-06-26 15:58:14 +04:00
else
2007-10-17 20:04:37 +04:00
wbinvd ( ) ;
2006-06-26 15:58:14 +04:00
}
void tce_build ( struct iommu_table * tbl , unsigned long index ,
unsigned int npages , unsigned long uaddr , int direction )
{
u64 * tp ;
u64 t ;
u64 rpn ;
t = ( 1 < < TCE_READ_SHIFT ) ;
if ( direction ! = DMA_TO_DEVICE )
t | = ( 1 < < TCE_WRITE_SHIFT ) ;
tp = ( ( u64 * ) tbl - > it_base ) + index ;
while ( npages - - ) {
rpn = ( virt_to_bus ( ( void * ) uaddr ) ) > > PAGE_SHIFT ;
t & = ~ TCE_RPN_MASK ;
t | = ( rpn < < TCE_RPN_SHIFT ) ;
* tp = cpu_to_be64 ( t ) ;
flush_tce ( tp ) ;
uaddr + = PAGE_SIZE ;
tp + + ;
}
}
void tce_free ( struct iommu_table * tbl , long index , unsigned int npages )
{
u64 * tp ;
tp = ( ( u64 * ) tbl - > it_base ) + index ;
while ( npages - - ) {
* tp = cpu_to_be64 ( 0 ) ;
flush_tce ( tp ) ;
tp + + ;
}
}
static inline unsigned int table_size_to_number_of_entries ( unsigned char size )
{
/*
* size is the order of the table , 0 - 7
* smallest table is 8 K entries , so shift result by 13 to
* multiply by 8 K
*/
return ( 1 < < size ) < < 13 ;
}
static int tce_table_setparms ( struct pci_dev * dev , struct iommu_table * tbl )
{
unsigned int bitmapsz ;
unsigned long bmppages ;
int ret ;
tbl - > it_busno = dev - > bus - > number ;
/* set the tce table size - measured in entries */
tbl - > it_size = table_size_to_number_of_entries ( specified_table_size ) ;
/*
* number of bytes needed for the bitmap size in number of
* entries ; we need one bit per entry
*/
bitmapsz = tbl - > it_size / BITS_PER_BYTE ;
bmppages = __get_free_pages ( GFP_KERNEL , get_order ( bitmapsz ) ) ;
if ( ! bmppages ) {
printk ( KERN_ERR " Calgary: cannot allocate bitmap \n " ) ;
ret = - ENOMEM ;
goto done ;
}
tbl - > it_map = ( unsigned long * ) bmppages ;
memset ( tbl - > it_map , 0 , bitmapsz ) ;
tbl - > it_hint = 0 ;
spin_lock_init ( & tbl - > it_lock ) ;
return 0 ;
done :
return ret ;
}
2007-07-31 11:37:32 +04:00
int __init build_tce_table ( struct pci_dev * dev , void __iomem * bbar )
2006-06-26 15:58:14 +04:00
{
struct iommu_table * tbl ;
int ret ;
2007-07-22 01:23:39 +04:00
if ( pci_iommu ( dev - > bus ) ) {
printk ( KERN_ERR " Calgary: dev %p has sysdata->iommu %p \n " ,
dev , pci_iommu ( dev - > bus ) ) ;
2006-06-26 15:58:14 +04:00
BUG ( ) ;
}
tbl = kzalloc ( sizeof ( struct iommu_table ) , GFP_KERNEL ) ;
if ( ! tbl ) {
printk ( KERN_ERR " Calgary: error allocating iommu_table \n " ) ;
ret = - ENOMEM ;
goto done ;
}
ret = tce_table_setparms ( dev , tbl ) ;
if ( ret )
goto free_tbl ;
tbl - > bbar = bbar ;
2007-07-22 01:23:39 +04:00
set_pci_iommu ( dev - > bus , tbl ) ;
2006-06-26 15:58:14 +04:00
return 0 ;
free_tbl :
kfree ( tbl ) ;
done :
return ret ;
}
2007-07-26 21:41:16 +04:00
void * __init alloc_tce_table ( void )
2006-06-26 15:58:14 +04:00
{
unsigned int size ;
size = table_size_to_number_of_entries ( specified_table_size ) ;
size * = TCE_ENTRY_SIZE ;
return __alloc_bootmem_low ( size , size , 0 ) ;
}
2007-07-26 21:41:16 +04:00
void __init free_tce_table ( void * tbl )
2006-06-26 15:58:14 +04:00
{
unsigned int size ;
if ( ! tbl )
return ;
size = table_size_to_number_of_entries ( specified_table_size ) ;
size * = TCE_ENTRY_SIZE ;
free_bootmem ( __pa ( tbl ) , size ) ;
}