2018-07-04 16:13:45 +10:00
// SPDX-License-Identifier: GPL-2.0+
/*
* TCE helpers for IODA PCI / PCIe on PowerNV platforms
*
* Copyright 2018 IBM 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 ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# include <linux/kernel.h>
# include <linux/iommu.h>
# include <asm/iommu.h>
# include <asm/tce.h>
# include "pci.h"
void pnv_pci_setup_iommu_table ( struct iommu_table * tbl ,
void * tce_mem , u64 tce_size ,
u64 dma_offset , unsigned int page_shift )
{
tbl - > it_blocksize = 16 ;
tbl - > it_base = ( unsigned long ) tce_mem ;
tbl - > it_page_shift = page_shift ;
tbl - > it_offset = dma_offset > > tbl - > it_page_shift ;
tbl - > it_index = 0 ;
tbl - > it_size = tce_size > > 3 ;
tbl - > it_busno = 0 ;
tbl - > it_type = TCE_PCI ;
}
2018-07-04 16:13:48 +10:00
static __be64 * pnv_alloc_tce_level ( int nid , unsigned int shift )
{
struct page * tce_mem = NULL ;
__be64 * addr ;
tce_mem = alloc_pages_node ( nid , GFP_KERNEL , shift - PAGE_SHIFT ) ;
if ( ! tce_mem ) {
pr_err ( " Failed to allocate a TCE memory, level shift=%d \n " ,
shift ) ;
return NULL ;
}
addr = page_address ( tce_mem ) ;
memset ( addr , 0 , 1UL < < shift ) ;
return addr ;
}
2018-07-04 16:13:49 +10:00
static __be64 * pnv_tce ( struct iommu_table * tbl , bool user , long idx , bool alloc )
2018-07-04 16:13:45 +10:00
{
2018-07-04 16:13:47 +10:00
__be64 * tmp = user ? tbl - > it_userspace : ( __be64 * ) tbl - > it_base ;
2018-07-04 16:13:45 +10:00
int level = tbl - > it_indirect_levels ;
const long shift = ilog2 ( tbl - > it_level_size ) ;
unsigned long mask = ( tbl - > it_level_size - 1 ) < < ( level * shift ) ;
while ( level ) {
int n = ( idx & mask ) > > ( level * shift ) ;
2018-07-04 16:13:49 +10:00
unsigned long tce ;
if ( tmp [ n ] = = 0 ) {
__be64 * tmp2 ;
if ( ! alloc )
return NULL ;
tmp2 = pnv_alloc_tce_level ( tbl - > it_nid ,
ilog2 ( tbl - > it_level_size ) + 3 ) ;
if ( ! tmp2 )
return NULL ;
tmp [ n ] = cpu_to_be64 ( __pa ( tmp2 ) |
TCE_PCI_READ | TCE_PCI_WRITE ) ;
}
tce = be64_to_cpu ( tmp [ n ] ) ;
2018-07-04 16:13:45 +10:00
tmp = __va ( tce & ~ ( TCE_PCI_READ | TCE_PCI_WRITE ) ) ;
idx & = ~ mask ;
mask > > = shift ;
- - level ;
}
return tmp + idx ;
}
int pnv_tce_build ( struct iommu_table * tbl , long index , long npages ,
unsigned long uaddr , enum dma_data_direction direction ,
unsigned long attrs )
{
u64 proto_tce = iommu_direction_to_tce_perm ( direction ) ;
u64 rpn = __pa ( uaddr ) > > tbl - > it_page_shift ;
long i ;
if ( proto_tce & TCE_PCI_WRITE )
proto_tce | = TCE_PCI_READ ;
for ( i = 0 ; i < npages ; i + + ) {
unsigned long newtce = proto_tce |
( ( rpn + i ) < < tbl - > it_page_shift ) ;
unsigned long idx = index - tbl - > it_offset + i ;
2018-07-04 16:13:49 +10:00
* ( pnv_tce ( tbl , false , idx , true ) ) = cpu_to_be64 ( newtce ) ;
2018-07-04 16:13:45 +10:00
}
return 0 ;
}
# ifdef CONFIG_IOMMU_API
int pnv_tce_xchg ( struct iommu_table * tbl , long index ,
2018-07-04 16:13:49 +10:00
unsigned long * hpa , enum dma_data_direction * direction ,
bool alloc )
2018-07-04 16:13:45 +10:00
{
u64 proto_tce = iommu_direction_to_tce_perm ( * direction ) ;
unsigned long newtce = * hpa | proto_tce , oldtce ;
unsigned long idx = index - tbl - > it_offset ;
2018-07-04 16:13:49 +10:00
__be64 * ptce = NULL ;
2018-07-04 16:13:45 +10:00
BUG_ON ( * hpa & ~ IOMMU_PAGE_MASK ( tbl ) ) ;
2018-07-04 16:13:49 +10:00
if ( * direction = = DMA_NONE ) {
ptce = pnv_tce ( tbl , false , idx , false ) ;
if ( ! ptce ) {
* hpa = 0 ;
return 0 ;
}
}
if ( ! ptce ) {
ptce = pnv_tce ( tbl , false , idx , alloc ) ;
if ( ! ptce )
return alloc ? H_HARDWARE : H_TOO_HARD ;
}
2018-07-04 16:13:45 +10:00
if ( newtce & TCE_PCI_WRITE )
newtce | = TCE_PCI_READ ;
2018-07-04 16:13:49 +10:00
oldtce = be64_to_cpu ( xchg ( ptce , cpu_to_be64 ( newtce ) ) ) ;
2018-07-04 16:13:45 +10:00
* hpa = oldtce & ~ ( TCE_PCI_READ | TCE_PCI_WRITE ) ;
* direction = iommu_tce_direction ( oldtce ) ;
return 0 ;
}
2018-07-04 16:13:47 +10:00
2018-07-04 16:13:49 +10:00
__be64 * pnv_tce_useraddrptr ( struct iommu_table * tbl , long index , bool alloc )
2018-07-04 16:13:47 +10:00
{
if ( WARN_ON_ONCE ( ! tbl - > it_userspace ) )
return NULL ;
2018-07-04 16:13:49 +10:00
return pnv_tce ( tbl , true , index - tbl - > it_offset , alloc ) ;
2018-07-04 16:13:47 +10:00
}
2018-07-04 16:13:45 +10:00
# endif
void pnv_tce_free ( struct iommu_table * tbl , long index , long npages )
{
long i ;
for ( i = 0 ; i < npages ; i + + ) {
unsigned long idx = index - tbl - > it_offset + i ;
2018-07-04 16:13:49 +10:00
__be64 * ptce = pnv_tce ( tbl , false , idx , false ) ;
2018-07-04 16:13:45 +10:00
2018-07-04 16:13:49 +10:00
if ( ptce )
* ptce = cpu_to_be64 ( 0 ) ;
2018-07-04 16:13:45 +10:00
}
}
unsigned long pnv_tce_get ( struct iommu_table * tbl , long index )
{
2018-07-04 16:13:49 +10:00
__be64 * ptce = pnv_tce ( tbl , false , index - tbl - > it_offset , false ) ;
if ( ! ptce )
return 0 ;
2018-07-04 16:13:47 +10:00
return be64_to_cpu ( * ptce ) ;
2018-07-04 16:13:45 +10:00
}
static void pnv_pci_ioda2_table_do_free_pages ( __be64 * addr ,
unsigned long size , unsigned int levels )
{
const unsigned long addr_ul = ( unsigned long ) addr &
~ ( TCE_PCI_READ | TCE_PCI_WRITE ) ;
if ( levels ) {
long i ;
u64 * tmp = ( u64 * ) addr_ul ;
for ( i = 0 ; i < size ; + + i ) {
unsigned long hpa = be64_to_cpu ( tmp [ i ] ) ;
if ( ! ( hpa & ( TCE_PCI_READ | TCE_PCI_WRITE ) ) )
continue ;
pnv_pci_ioda2_table_do_free_pages ( __va ( hpa ) , size ,
levels - 1 ) ;
}
}
free_pages ( addr_ul , get_order ( size < < 3 ) ) ;
}
void pnv_pci_ioda2_table_free_pages ( struct iommu_table * tbl )
{
const unsigned long size = tbl - > it_indirect_levels ?
tbl - > it_level_size : tbl - > it_size ;
if ( ! tbl - > it_size )
return ;
pnv_pci_ioda2_table_do_free_pages ( ( __be64 * ) tbl - > it_base , size ,
tbl - > it_indirect_levels ) ;
2018-07-04 16:13:47 +10:00
if ( tbl - > it_userspace ) {
pnv_pci_ioda2_table_do_free_pages ( tbl - > it_userspace , size ,
tbl - > it_indirect_levels ) ;
}
2018-07-04 16:13:45 +10:00
}
static __be64 * pnv_pci_ioda2_table_do_alloc_pages ( int nid , unsigned int shift ,
unsigned int levels , unsigned long limit ,
unsigned long * current_offset , unsigned long * total_allocated )
{
__be64 * addr , * tmp ;
2018-07-04 16:13:48 +10:00
unsigned long allocated = 1UL < < shift ;
2018-07-04 16:13:45 +10:00
unsigned int entries = 1UL < < ( shift - 3 ) ;
long i ;
2018-07-04 16:13:48 +10:00
addr = pnv_alloc_tce_level ( nid , shift ) ;
2018-07-04 16:13:45 +10:00
* total_allocated + = allocated ;
- - levels ;
if ( ! levels ) {
* current_offset + = allocated ;
return addr ;
}
for ( i = 0 ; i < entries ; + + i ) {
tmp = pnv_pci_ioda2_table_do_alloc_pages ( nid , shift ,
levels , limit , current_offset , total_allocated ) ;
if ( ! tmp )
break ;
addr [ i ] = cpu_to_be64 ( __pa ( tmp ) |
TCE_PCI_READ | TCE_PCI_WRITE ) ;
if ( * current_offset > = limit )
break ;
}
return addr ;
}
long pnv_pci_ioda2_table_alloc_pages ( int nid , __u64 bus_offset ,
__u32 page_shift , __u64 window_size , __u32 levels ,
2018-07-04 16:13:47 +10:00
bool alloc_userspace_copy , struct iommu_table * tbl )
2018-07-04 16:13:45 +10:00
{
2018-07-04 16:13:47 +10:00
void * addr , * uas = NULL ;
2018-07-04 16:13:45 +10:00
unsigned long offset = 0 , level_shift , total_allocated = 0 ;
2018-07-04 16:13:47 +10:00
unsigned long total_allocated_uas = 0 ;
2018-07-04 16:13:45 +10:00
const unsigned int window_shift = ilog2 ( window_size ) ;
unsigned int entries_shift = window_shift - page_shift ;
unsigned int table_shift = max_t ( unsigned int , entries_shift + 3 ,
PAGE_SHIFT ) ;
const unsigned long tce_table_size = 1UL < < table_shift ;
2018-07-04 16:13:49 +10:00
unsigned int tmplevels = levels ;
2018-07-04 16:13:45 +10:00
if ( ! levels | | ( levels > POWERNV_IOMMU_MAX_LEVELS ) )
return - EINVAL ;
if ( ! is_power_of_2 ( window_size ) )
return - EINVAL ;
2018-07-04 16:13:49 +10:00
if ( alloc_userspace_copy & & ( window_size > ( 1ULL < < 32 ) ) )
tmplevels = 1 ;
2018-07-04 16:13:45 +10:00
/* Adjust direct table size from window_size and levels */
entries_shift = ( entries_shift + levels - 1 ) / levels ;
level_shift = entries_shift + 3 ;
level_shift = max_t ( unsigned int , level_shift , PAGE_SHIFT ) ;
2018-09-11 15:38:05 +10:00
if ( ( level_shift - 3 ) * levels + page_shift > = 55 )
2018-07-04 16:13:45 +10:00
return - EINVAL ;
/* Allocate TCE table */
addr = pnv_pci_ioda2_table_do_alloc_pages ( nid , level_shift ,
2018-07-04 16:13:49 +10:00
tmplevels , tce_table_size , & offset , & total_allocated ) ;
2018-07-04 16:13:45 +10:00
/* addr==NULL means that the first level allocation failed */
if ( ! addr )
return - ENOMEM ;
/*
* First level was allocated but some lower level failed as
* we did not allocate as much as we wanted ,
* release partially allocated table .
*/
2018-07-04 16:13:49 +10:00
if ( tmplevels = = levels & & offset < tce_table_size )
2018-07-04 16:13:47 +10:00
goto free_tces_exit ;
/* Allocate userspace view of the TCE table */
if ( alloc_userspace_copy ) {
offset = 0 ;
uas = pnv_pci_ioda2_table_do_alloc_pages ( nid , level_shift ,
2018-09-28 16:45:39 +10:00
tmplevels , tce_table_size , & offset ,
2018-07-04 16:13:47 +10:00
& total_allocated_uas ) ;
if ( ! uas )
goto free_tces_exit ;
2018-07-04 16:13:49 +10:00
if ( tmplevels = = levels & & ( offset < tce_table_size | |
total_allocated_uas ! = total_allocated ) )
2018-07-04 16:13:47 +10:00
goto free_uas_exit ;
2018-07-04 16:13:45 +10:00
}
/* Setup linux iommu table */
pnv_pci_setup_iommu_table ( tbl , addr , tce_table_size , bus_offset ,
page_shift ) ;
tbl - > it_level_size = 1ULL < < ( level_shift - 3 ) ;
tbl - > it_indirect_levels = levels - 1 ;
2018-07-04 16:13:47 +10:00
tbl - > it_userspace = uas ;
2018-07-04 16:13:49 +10:00
tbl - > it_nid = nid ;
2018-07-04 16:13:45 +10:00
2018-07-04 16:13:49 +10:00
pr_debug ( " Created TCE table: ws=%08llx ts=%lx @%08llx base=%lx uas=%p levels=%d/%d \n " ,
2018-07-04 16:13:47 +10:00
window_size , tce_table_size , bus_offset , tbl - > it_base ,
2018-07-04 16:13:49 +10:00
tbl - > it_userspace , tmplevels , levels ) ;
2018-07-04 16:13:45 +10:00
return 0 ;
2018-07-04 16:13:47 +10:00
free_uas_exit :
pnv_pci_ioda2_table_do_free_pages ( uas ,
1ULL < < ( level_shift - 3 ) , levels - 1 ) ;
free_tces_exit :
pnv_pci_ioda2_table_do_free_pages ( addr ,
1ULL < < ( level_shift - 3 ) , levels - 1 ) ;
return - ENOMEM ;
2018-07-04 16:13:45 +10:00
}
static void pnv_iommu_table_group_link_free ( struct rcu_head * head )
{
struct iommu_table_group_link * tgl = container_of ( head ,
struct iommu_table_group_link , rcu ) ;
kfree ( tgl ) ;
}
void pnv_pci_unlink_table_and_group ( struct iommu_table * tbl ,
struct iommu_table_group * table_group )
{
long i ;
bool found ;
struct iommu_table_group_link * tgl ;
if ( ! tbl | | ! table_group )
return ;
/* Remove link to a group from table's list of attached groups */
found = false ;
list_for_each_entry_rcu ( tgl , & tbl - > it_group_list , next ) {
if ( tgl - > table_group = = table_group ) {
list_del_rcu ( & tgl - > next ) ;
call_rcu ( & tgl - > rcu , pnv_iommu_table_group_link_free ) ;
found = true ;
break ;
}
}
if ( WARN_ON ( ! found ) )
return ;
/* Clean a pointer to iommu_table in iommu_table_group::tables[] */
found = false ;
for ( i = 0 ; i < IOMMU_TABLE_GROUP_MAX_TABLES ; + + i ) {
if ( table_group - > tables [ i ] = = tbl ) {
2018-12-19 19:52:23 +11:00
iommu_tce_table_put ( tbl ) ;
2018-07-04 16:13:45 +10:00
table_group - > tables [ i ] = NULL ;
found = true ;
break ;
}
}
WARN_ON ( ! found ) ;
}
long pnv_pci_link_table_and_group ( int node , int num ,
struct iommu_table * tbl ,
struct iommu_table_group * table_group )
{
struct iommu_table_group_link * tgl = NULL ;
if ( WARN_ON ( ! tbl | | ! table_group ) )
return - EINVAL ;
tgl = kzalloc_node ( sizeof ( struct iommu_table_group_link ) , GFP_KERNEL ,
node ) ;
if ( ! tgl )
return - ENOMEM ;
tgl - > table_group = table_group ;
list_add_rcu ( & tgl - > next , & tbl - > it_group_list ) ;
2018-12-19 19:52:23 +11:00
table_group - > tables [ num ] = iommu_tce_table_get ( tbl ) ;
2018-07-04 16:13:45 +10:00
return 0 ;
}