2013-07-15 08:50:57 +04:00
/*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License , version 2 , as
* published by the Free Software Foundation .
*
* 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 , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*
* Copyright ( C ) 2013 Freescale Semiconductor , Inc .
* Author : Varun Sethi < varun . sethi @ freescale . com >
*
*/
# define pr_fmt(fmt) "fsl-pamu-domain: %s: " fmt, __func__
# include "fsl_pamu_domain.h"
2015-01-28 17:34:33 +03:00
# include <sysdev/fsl_pci.h>
2013-07-15 08:50:57 +04:00
/*
* Global spinlock that needs to be held while
* configuring PAMU .
*/
static DEFINE_SPINLOCK ( iommu_lock ) ;
static struct kmem_cache * fsl_pamu_domain_cache ;
static struct kmem_cache * iommu_devinfo_cache ;
static DEFINE_SPINLOCK ( device_domain_lock ) ;
2017-08-23 17:28:09 +03:00
struct iommu_device pamu_iommu ; /* IOMMU core code handle */
2015-03-26 15:43:18 +03:00
static struct fsl_dma_domain * to_fsl_dma_domain ( struct iommu_domain * dom )
{
return container_of ( dom , struct fsl_dma_domain , iommu_domain ) ;
}
2013-07-15 08:50:57 +04:00
static int __init iommu_init_mempool ( void )
{
fsl_pamu_domain_cache = kmem_cache_create ( " fsl_pamu_domain " ,
2015-01-28 17:34:33 +03:00
sizeof ( struct fsl_dma_domain ) ,
0 ,
SLAB_HWCACHE_ALIGN ,
NULL ) ;
2013-07-15 08:50:57 +04:00
if ( ! fsl_pamu_domain_cache ) {
pr_debug ( " Couldn't create fsl iommu_domain cache \n " ) ;
return - ENOMEM ;
}
iommu_devinfo_cache = kmem_cache_create ( " iommu_devinfo " ,
2015-01-28 17:34:33 +03:00
sizeof ( struct device_domain_info ) ,
0 ,
SLAB_HWCACHE_ALIGN ,
NULL ) ;
2013-07-15 08:50:57 +04:00
if ( ! iommu_devinfo_cache ) {
pr_debug ( " Couldn't create devinfo cache \n " ) ;
kmem_cache_destroy ( fsl_pamu_domain_cache ) ;
return - ENOMEM ;
}
return 0 ;
}
static phys_addr_t get_phys_addr ( struct fsl_dma_domain * dma_domain , dma_addr_t iova )
{
u32 win_cnt = dma_domain - > win_cnt ;
2015-01-28 17:34:33 +03:00
struct dma_window * win_ptr = & dma_domain - > win_arr [ 0 ] ;
2013-07-15 08:50:57 +04:00
struct iommu_domain_geometry * geom ;
2015-03-26 15:43:18 +03:00
geom = & dma_domain - > iommu_domain . geometry ;
2013-07-15 08:50:57 +04:00
if ( ! win_cnt | | ! dma_domain - > geom_size ) {
pr_debug ( " Number of windows/geometry not configured for the domain \n " ) ;
return 0 ;
}
if ( win_cnt > 1 ) {
u64 subwin_size ;
dma_addr_t subwin_iova ;
u32 wnd ;
subwin_size = dma_domain - > geom_size > > ilog2 ( win_cnt ) ;
subwin_iova = iova & ~ ( subwin_size - 1 ) ;
wnd = ( subwin_iova - geom - > aperture_start ) > > ilog2 ( subwin_size ) ;
win_ptr = & dma_domain - > win_arr [ wnd ] ;
}
if ( win_ptr - > valid )
2015-01-28 17:34:33 +03:00
return win_ptr - > paddr + ( iova & ( win_ptr - > size - 1 ) ) ;
2013-07-15 08:50:57 +04:00
return 0 ;
}
static int map_subwins ( int liodn , struct fsl_dma_domain * dma_domain )
{
2015-01-28 17:34:33 +03:00
struct dma_window * sub_win_ptr = & dma_domain - > win_arr [ 0 ] ;
2013-07-15 08:50:57 +04:00
int i , ret ;
unsigned long rpn , flags ;
for ( i = 0 ; i < dma_domain - > win_cnt ; i + + ) {
if ( sub_win_ptr [ i ] . valid ) {
2015-01-28 17:34:33 +03:00
rpn = sub_win_ptr [ i ] . paddr > > PAMU_PAGE_SHIFT ;
2013-07-15 08:50:57 +04:00
spin_lock_irqsave ( & iommu_lock , flags ) ;
ret = pamu_config_spaace ( liodn , dma_domain - > win_cnt , i ,
sub_win_ptr [ i ] . size ,
~ ( u32 ) 0 ,
rpn ,
dma_domain - > snoop_id ,
dma_domain - > stash_id ,
( i > 0 ) ? 1 : 0 ,
sub_win_ptr [ i ] . prot ) ;
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
if ( ret ) {
2015-01-28 17:34:33 +03:00
pr_debug ( " SPAACE configuration failed for liodn %d \n " ,
2013-07-15 08:50:57 +04:00
liodn ) ;
return ret ;
}
}
}
return ret ;
}
static int map_win ( int liodn , struct fsl_dma_domain * dma_domain )
{
int ret ;
struct dma_window * wnd = & dma_domain - > win_arr [ 0 ] ;
2015-03-26 15:43:18 +03:00
phys_addr_t wnd_addr = dma_domain - > iommu_domain . geometry . aperture_start ;
2013-07-15 08:50:57 +04:00
unsigned long flags ;
spin_lock_irqsave ( & iommu_lock , flags ) ;
ret = pamu_config_ppaace ( liodn , wnd_addr ,
wnd - > size ,
~ ( u32 ) 0 ,
wnd - > paddr > > PAMU_PAGE_SHIFT ,
dma_domain - > snoop_id , dma_domain - > stash_id ,
0 , wnd - > prot ) ;
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
if ( ret )
2015-01-28 17:34:33 +03:00
pr_debug ( " PAACE configuration failed for liodn %d \n " , liodn ) ;
2013-07-15 08:50:57 +04:00
return ret ;
}
/* Map the DMA window corresponding to the LIODN */
static int map_liodn ( int liodn , struct fsl_dma_domain * dma_domain )
{
if ( dma_domain - > win_cnt > 1 )
return map_subwins ( liodn , dma_domain ) ;
else
return map_win ( liodn , dma_domain ) ;
}
/* Update window/subwindow mapping for the LIODN */
static int update_liodn ( int liodn , struct fsl_dma_domain * dma_domain , u32 wnd_nr )
{
int ret ;
struct dma_window * wnd = & dma_domain - > win_arr [ wnd_nr ] ;
unsigned long flags ;
spin_lock_irqsave ( & iommu_lock , flags ) ;
if ( dma_domain - > win_cnt > 1 ) {
ret = pamu_config_spaace ( liodn , dma_domain - > win_cnt , wnd_nr ,
wnd - > size ,
~ ( u32 ) 0 ,
wnd - > paddr > > PAMU_PAGE_SHIFT ,
dma_domain - > snoop_id ,
dma_domain - > stash_id ,
( wnd_nr > 0 ) ? 1 : 0 ,
wnd - > prot ) ;
if ( ret )
2015-01-28 17:34:33 +03:00
pr_debug ( " Subwindow reconfiguration failed for liodn %d \n " ,
liodn ) ;
2013-07-15 08:50:57 +04:00
} else {
phys_addr_t wnd_addr ;
2015-03-26 15:43:18 +03:00
wnd_addr = dma_domain - > iommu_domain . geometry . aperture_start ;
2013-07-15 08:50:57 +04:00
ret = pamu_config_ppaace ( liodn , wnd_addr ,
wnd - > size ,
~ ( u32 ) 0 ,
wnd - > paddr > > PAMU_PAGE_SHIFT ,
2015-01-28 17:34:33 +03:00
dma_domain - > snoop_id , dma_domain - > stash_id ,
0 , wnd - > prot ) ;
2013-07-15 08:50:57 +04:00
if ( ret )
2015-01-28 17:34:33 +03:00
pr_debug ( " Window reconfiguration failed for liodn %d \n " ,
liodn ) ;
2013-07-15 08:50:57 +04:00
}
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
return ret ;
}
static int update_liodn_stash ( int liodn , struct fsl_dma_domain * dma_domain ,
2015-01-28 17:34:33 +03:00
u32 val )
2013-07-15 08:50:57 +04:00
{
int ret = 0 , i ;
unsigned long flags ;
spin_lock_irqsave ( & iommu_lock , flags ) ;
if ( ! dma_domain - > win_arr ) {
2015-01-28 17:34:33 +03:00
pr_debug ( " Windows not configured, stash destination update failed for liodn %d \n " ,
liodn ) ;
2013-07-15 08:50:57 +04:00
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
return - EINVAL ;
}
for ( i = 0 ; i < dma_domain - > win_cnt ; i + + ) {
ret = pamu_update_paace_stash ( liodn , i , val ) ;
if ( ret ) {
2015-01-28 17:34:33 +03:00
pr_debug ( " Failed to update SPAACE %d field for liodn %d \n " ,
i , liodn ) ;
2013-07-15 08:50:57 +04:00
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
return ret ;
}
}
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
return ret ;
}
/* Set the geometry parameters for a LIODN */
static int pamu_set_liodn ( int liodn , struct device * dev ,
2015-01-28 17:34:33 +03:00
struct fsl_dma_domain * dma_domain ,
struct iommu_domain_geometry * geom_attr ,
u32 win_cnt )
2013-07-15 08:50:57 +04:00
{
phys_addr_t window_addr , window_size ;
phys_addr_t subwin_size ;
int ret = 0 , i ;
u32 omi_index = ~ ( u32 ) 0 ;
unsigned long flags ;
/*
* Configure the omi_index at the geometry setup time .
* This is a static value which depends on the type of
* device and would not change thereafter .
*/
get_ome_index ( & omi_index , dev ) ;
window_addr = geom_attr - > aperture_start ;
window_size = dma_domain - > geom_size ;
spin_lock_irqsave ( & iommu_lock , flags ) ;
ret = pamu_disable_liodn ( liodn ) ;
if ( ! ret )
ret = pamu_config_ppaace ( liodn , window_addr , window_size , omi_index ,
0 , dma_domain - > snoop_id ,
dma_domain - > stash_id , win_cnt , 0 ) ;
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
if ( ret ) {
2015-01-28 17:34:33 +03:00
pr_debug ( " PAACE configuration failed for liodn %d, win_cnt =%d \n " ,
liodn , win_cnt ) ;
2013-07-15 08:50:57 +04:00
return ret ;
}
if ( win_cnt > 1 ) {
subwin_size = window_size > > ilog2 ( win_cnt ) ;
for ( i = 0 ; i < win_cnt ; i + + ) {
spin_lock_irqsave ( & iommu_lock , flags ) ;
ret = pamu_disable_spaace ( liodn , i ) ;
if ( ! ret )
ret = pamu_config_spaace ( liodn , win_cnt , i ,
subwin_size , omi_index ,
0 , dma_domain - > snoop_id ,
dma_domain - > stash_id ,
0 , 0 ) ;
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
if ( ret ) {
2015-01-28 17:34:33 +03:00
pr_debug ( " SPAACE configuration failed for liodn %d \n " ,
liodn ) ;
2013-07-15 08:50:57 +04:00
return ret ;
}
}
}
return ret ;
}
static int check_size ( u64 size , dma_addr_t iova )
{
/*
* Size must be a power of two and at least be equal
* to PAMU page size .
*/
2014-06-24 17:57:15 +04:00
if ( ( size & ( size - 1 ) ) | | size < PAMU_PAGE_SIZE ) {
2015-01-28 17:34:33 +03:00
pr_debug ( " Size too small or not a power of two \n " ) ;
2013-07-15 08:50:57 +04:00
return - EINVAL ;
}
2015-01-28 17:34:33 +03:00
/* iova must be page size aligned */
2013-07-15 08:50:57 +04:00
if ( iova & ( size - 1 ) ) {
2015-01-28 17:34:33 +03:00
pr_debug ( " Address is not aligned with window size \n " ) ;
2013-07-15 08:50:57 +04:00
return - EINVAL ;
}
return 0 ;
}
static struct fsl_dma_domain * iommu_alloc_dma_domain ( void )
{
struct fsl_dma_domain * domain ;
domain = kmem_cache_zalloc ( fsl_pamu_domain_cache , GFP_KERNEL ) ;
if ( ! domain )
return NULL ;
domain - > stash_id = ~ ( u32 ) 0 ;
domain - > snoop_id = ~ ( u32 ) 0 ;
domain - > win_cnt = pamu_get_max_subwin_cnt ( ) ;
domain - > geom_size = 0 ;
INIT_LIST_HEAD ( & domain - > devices ) ;
spin_lock_init ( & domain - > domain_lock ) ;
return domain ;
}
static void remove_device_ref ( struct device_domain_info * info , u32 win_cnt )
{
unsigned long flags ;
list_del ( & info - > link ) ;
spin_lock_irqsave ( & iommu_lock , flags ) ;
if ( win_cnt > 1 )
pamu_free_subwins ( info - > liodn ) ;
pamu_disable_liodn ( info - > liodn ) ;
spin_unlock_irqrestore ( & iommu_lock , flags ) ;
spin_lock_irqsave ( & device_domain_lock , flags ) ;
info - > dev - > archdata . iommu_domain = NULL ;
kmem_cache_free ( iommu_devinfo_cache , info ) ;
spin_unlock_irqrestore ( & device_domain_lock , flags ) ;
}
static void detach_device ( struct device * dev , struct fsl_dma_domain * dma_domain )
{
struct device_domain_info * info , * tmp ;
unsigned long flags ;
spin_lock_irqsave ( & dma_domain - > domain_lock , flags ) ;
/* Remove the device from the domain device list */
list_for_each_entry_safe ( info , tmp , & dma_domain - > devices , link ) {
if ( ! dev | | ( info - > dev = = dev ) )
remove_device_ref ( info , dma_domain - > win_cnt ) ;
}
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
}
static void attach_device ( struct fsl_dma_domain * dma_domain , int liodn , struct device * dev )
{
struct device_domain_info * info , * old_domain_info ;
unsigned long flags ;
spin_lock_irqsave ( & device_domain_lock , flags ) ;
/*
* Check here if the device is already attached to domain or not .
* If the device is already attached to a domain detach it .
*/
2014-06-24 17:57:16 +04:00
old_domain_info = dev - > archdata . iommu_domain ;
2013-07-15 08:50:57 +04:00
if ( old_domain_info & & old_domain_info - > domain ! = dma_domain ) {
spin_unlock_irqrestore ( & device_domain_lock , flags ) ;
detach_device ( dev , old_domain_info - > domain ) ;
spin_lock_irqsave ( & device_domain_lock , flags ) ;
}
info = kmem_cache_zalloc ( iommu_devinfo_cache , GFP_ATOMIC ) ;
info - > dev = dev ;
info - > liodn = liodn ;
info - > domain = dma_domain ;
list_add ( & info - > link , & dma_domain - > devices ) ;
/*
* In case of devices with multiple LIODNs just store
* the info for the first LIODN as all
* LIODNs share the same domain
*/
2014-06-24 17:57:16 +04:00
if ( ! dev - > archdata . iommu_domain )
2013-07-15 08:50:57 +04:00
dev - > archdata . iommu_domain = info ;
spin_unlock_irqrestore ( & device_domain_lock , flags ) ;
}
static phys_addr_t fsl_pamu_iova_to_phys ( struct iommu_domain * domain ,
2015-01-28 17:34:33 +03:00
dma_addr_t iova )
2013-07-15 08:50:57 +04:00
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
2015-01-28 17:34:33 +03:00
if ( iova < domain - > geometry . aperture_start | |
iova > domain - > geometry . aperture_end )
2013-07-15 08:50:57 +04:00
return 0 ;
return get_phys_addr ( dma_domain , iova ) ;
}
2014-09-05 12:50:27 +04:00
static bool fsl_pamu_capable ( enum iommu_cap cap )
2013-07-15 08:50:57 +04:00
{
return cap = = IOMMU_CAP_CACHE_COHERENCY ;
}
2015-03-26 15:43:18 +03:00
static void fsl_pamu_domain_free ( struct iommu_domain * domain )
2013-07-15 08:50:57 +04:00
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
/* remove all the devices from the device list */
detach_device ( NULL , dma_domain ) ;
dma_domain - > enabled = 0 ;
dma_domain - > mapped = 0 ;
kmem_cache_free ( fsl_pamu_domain_cache , dma_domain ) ;
}
2015-03-26 15:43:18 +03:00
static struct iommu_domain * fsl_pamu_domain_alloc ( unsigned type )
2013-07-15 08:50:57 +04:00
{
struct fsl_dma_domain * dma_domain ;
2015-03-26 15:43:18 +03:00
if ( type ! = IOMMU_DOMAIN_UNMANAGED )
return NULL ;
2013-07-15 08:50:57 +04:00
dma_domain = iommu_alloc_dma_domain ( ) ;
if ( ! dma_domain ) {
pr_debug ( " dma_domain allocation failed \n " ) ;
2015-03-26 15:43:18 +03:00
return NULL ;
2013-07-15 08:50:57 +04:00
}
/* defaul geometry 64 GB i.e. maximum system address */
2015-03-26 15:43:18 +03:00
dma_domain - > iommu_domain . geometry . aperture_start = 0 ;
dma_domain - > iommu_domain . geometry . aperture_end = ( 1ULL < < 36 ) - 1 ;
dma_domain - > iommu_domain . geometry . force_aperture = true ;
2013-07-15 08:50:57 +04:00
2015-03-26 15:43:18 +03:00
return & dma_domain - > iommu_domain ;
2013-07-15 08:50:57 +04:00
}
/* Configure geometry settings for all LIODNs associated with domain */
static int pamu_set_domain_geometry ( struct fsl_dma_domain * dma_domain ,
struct iommu_domain_geometry * geom_attr ,
u32 win_cnt )
{
struct device_domain_info * info ;
int ret = 0 ;
list_for_each_entry ( info , & dma_domain - > devices , link ) {
ret = pamu_set_liodn ( info - > liodn , info - > dev , dma_domain ,
2015-01-28 17:34:33 +03:00
geom_attr , win_cnt ) ;
2013-07-15 08:50:57 +04:00
if ( ret )
break ;
}
return ret ;
}
/* Update stash destination for all LIODNs associated with the domain */
static int update_domain_stash ( struct fsl_dma_domain * dma_domain , u32 val )
{
struct device_domain_info * info ;
int ret = 0 ;
list_for_each_entry ( info , & dma_domain - > devices , link ) {
ret = update_liodn_stash ( info - > liodn , dma_domain , val ) ;
if ( ret )
break ;
}
return ret ;
}
/* Update domain mappings for all LIODNs associated with the domain */
static int update_domain_mapping ( struct fsl_dma_domain * dma_domain , u32 wnd_nr )
{
struct device_domain_info * info ;
int ret = 0 ;
list_for_each_entry ( info , & dma_domain - > devices , link ) {
ret = update_liodn ( info - > liodn , dma_domain , wnd_nr ) ;
if ( ret )
break ;
}
return ret ;
}
static int disable_domain_win ( struct fsl_dma_domain * dma_domain , u32 wnd_nr )
{
struct device_domain_info * info ;
int ret = 0 ;
list_for_each_entry ( info , & dma_domain - > devices , link ) {
if ( dma_domain - > win_cnt = = 1 & & dma_domain - > enabled ) {
ret = pamu_disable_liodn ( info - > liodn ) ;
if ( ! ret )
dma_domain - > enabled = 0 ;
} else {
ret = pamu_disable_spaace ( info - > liodn , wnd_nr ) ;
}
}
return ret ;
}
static void fsl_pamu_window_disable ( struct iommu_domain * domain , u32 wnd_nr )
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
unsigned long flags ;
int ret ;
spin_lock_irqsave ( & dma_domain - > domain_lock , flags ) ;
if ( ! dma_domain - > win_arr ) {
pr_debug ( " Number of windows not configured \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return ;
}
if ( wnd_nr > = dma_domain - > win_cnt ) {
pr_debug ( " Invalid window index \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return ;
}
if ( dma_domain - > win_arr [ wnd_nr ] . valid ) {
ret = disable_domain_win ( dma_domain , wnd_nr ) ;
if ( ! ret ) {
dma_domain - > win_arr [ wnd_nr ] . valid = 0 ;
dma_domain - > mapped - - ;
}
}
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
}
static int fsl_pamu_window_enable ( struct iommu_domain * domain , u32 wnd_nr ,
phys_addr_t paddr , u64 size , int prot )
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
struct dma_window * wnd ;
int pamu_prot = 0 ;
int ret ;
unsigned long flags ;
u64 win_size ;
if ( prot & IOMMU_READ )
pamu_prot | = PAACE_AP_PERMS_QUERY ;
if ( prot & IOMMU_WRITE )
pamu_prot | = PAACE_AP_PERMS_UPDATE ;
spin_lock_irqsave ( & dma_domain - > domain_lock , flags ) ;
if ( ! dma_domain - > win_arr ) {
pr_debug ( " Number of windows not configured \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - ENODEV ;
}
if ( wnd_nr > = dma_domain - > win_cnt ) {
pr_debug ( " Invalid window index \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EINVAL ;
}
win_size = dma_domain - > geom_size > > ilog2 ( dma_domain - > win_cnt ) ;
if ( size > win_size ) {
2015-01-28 17:34:33 +03:00
pr_debug ( " Invalid window size \n " ) ;
2013-07-15 08:50:57 +04:00
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EINVAL ;
}
if ( dma_domain - > win_cnt = = 1 ) {
if ( dma_domain - > enabled ) {
pr_debug ( " Disable the window before updating the mapping \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EBUSY ;
}
ret = check_size ( size , domain - > geometry . aperture_start ) ;
if ( ret ) {
pr_debug ( " Aperture start not aligned to the size \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EINVAL ;
}
}
wnd = & dma_domain - > win_arr [ wnd_nr ] ;
if ( ! wnd - > valid ) {
wnd - > paddr = paddr ;
wnd - > size = size ;
wnd - > prot = pamu_prot ;
ret = update_domain_mapping ( dma_domain , wnd_nr ) ;
if ( ! ret ) {
wnd - > valid = 1 ;
dma_domain - > mapped + + ;
}
} else {
pr_debug ( " Disable the window before updating the mapping \n " ) ;
ret = - EBUSY ;
}
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return ret ;
}
/*
* Attach the LIODN to the DMA domain and configure the geometry
* and window mappings .
*/
static int handle_attach_device ( struct fsl_dma_domain * dma_domain ,
2015-01-28 17:34:33 +03:00
struct device * dev , const u32 * liodn ,
int num )
2013-07-15 08:50:57 +04:00
{
unsigned long flags ;
2015-03-26 15:43:18 +03:00
struct iommu_domain * domain = & dma_domain - > iommu_domain ;
2013-07-15 08:50:57 +04:00
int ret = 0 ;
int i ;
spin_lock_irqsave ( & dma_domain - > domain_lock , flags ) ;
for ( i = 0 ; i < num ; i + + ) {
/* Ensure that LIODN value is valid */
if ( liodn [ i ] > = PAACE_NUMBER_ENTRIES ) {
2017-07-19 00:43:09 +03:00
pr_debug ( " Invalid liodn %d, attach device failed for %pOF \n " ,
liodn [ i ] , dev - > of_node ) ;
2013-07-15 08:50:57 +04:00
ret = - EINVAL ;
break ;
}
attach_device ( dma_domain , liodn [ i ] , dev ) ;
/*
* Check if geometry has already been configured
* for the domain . If yes , set the geometry for
* the LIODN .
*/
if ( dma_domain - > win_arr ) {
u32 win_cnt = dma_domain - > win_cnt > 1 ? dma_domain - > win_cnt : 0 ;
2015-01-28 17:34:33 +03:00
2013-07-15 08:50:57 +04:00
ret = pamu_set_liodn ( liodn [ i ] , dev , dma_domain ,
2015-01-28 17:34:33 +03:00
& domain - > geometry , win_cnt ) ;
2013-07-15 08:50:57 +04:00
if ( ret )
break ;
if ( dma_domain - > mapped ) {
/*
* Create window / subwindow mapping for
* the LIODN .
*/
ret = map_liodn ( liodn [ i ] , dma_domain ) ;
if ( ret )
break ;
}
}
}
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return ret ;
}
static int fsl_pamu_attach_device ( struct iommu_domain * domain ,
struct device * dev )
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
const u32 * liodn ;
u32 liodn_cnt ;
int len , ret = 0 ;
struct pci_dev * pdev = NULL ;
struct pci_controller * pci_ctl ;
/*
* Use LIODN of the PCI controller while attaching a
* PCI device .
*/
2013-12-05 15:42:49 +04:00
if ( dev_is_pci ( dev ) ) {
2013-07-15 08:50:57 +04:00
pdev = to_pci_dev ( dev ) ;
pci_ctl = pci_bus_to_host ( pdev - > bus ) ;
/*
* make dev point to pci controller device
* so we can get the LIODN programmed by
* u - boot .
*/
dev = pci_ctl - > parent ;
}
liodn = of_get_property ( dev - > of_node , " fsl,liodn " , & len ) ;
if ( liodn ) {
liodn_cnt = len / sizeof ( u32 ) ;
2015-01-28 17:34:33 +03:00
ret = handle_attach_device ( dma_domain , dev , liodn , liodn_cnt ) ;
2013-07-15 08:50:57 +04:00
} else {
2017-07-19 00:43:09 +03:00
pr_debug ( " missing fsl,liodn property at %pOF \n " , dev - > of_node ) ;
2015-01-28 17:34:33 +03:00
ret = - EINVAL ;
2013-07-15 08:50:57 +04:00
}
return ret ;
}
static void fsl_pamu_detach_device ( struct iommu_domain * domain ,
2015-01-28 17:34:33 +03:00
struct device * dev )
2013-07-15 08:50:57 +04:00
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
const u32 * prop ;
int len ;
struct pci_dev * pdev = NULL ;
struct pci_controller * pci_ctl ;
/*
* Use LIODN of the PCI controller while detaching a
* PCI device .
*/
2013-12-05 15:42:49 +04:00
if ( dev_is_pci ( dev ) ) {
2013-07-15 08:50:57 +04:00
pdev = to_pci_dev ( dev ) ;
pci_ctl = pci_bus_to_host ( pdev - > bus ) ;
/*
* make dev point to pci controller device
* so we can get the LIODN programmed by
* u - boot .
*/
dev = pci_ctl - > parent ;
}
prop = of_get_property ( dev - > of_node , " fsl,liodn " , & len ) ;
if ( prop )
detach_device ( dev , dma_domain ) ;
else
2017-07-19 00:43:09 +03:00
pr_debug ( " missing fsl,liodn property at %pOF \n " , dev - > of_node ) ;
2013-07-15 08:50:57 +04:00
}
static int configure_domain_geometry ( struct iommu_domain * domain , void * data )
{
struct iommu_domain_geometry * geom_attr = data ;
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
dma_addr_t geom_size ;
unsigned long flags ;
geom_size = geom_attr - > aperture_end - geom_attr - > aperture_start + 1 ;
/*
* Sanity check the geometry size . Also , we do not support
* DMA outside of the geometry .
*/
if ( check_size ( geom_size , geom_attr - > aperture_start ) | |
2015-01-28 17:34:33 +03:00
! geom_attr - > force_aperture ) {
pr_debug ( " Invalid PAMU geometry attributes \n " ) ;
return - EINVAL ;
}
2013-07-15 08:50:57 +04:00
spin_lock_irqsave ( & dma_domain - > domain_lock , flags ) ;
if ( dma_domain - > enabled ) {
pr_debug ( " Can't set geometry attributes as domain is active \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EBUSY ;
}
/* Copy the domain geometry information */
memcpy ( & domain - > geometry , geom_attr ,
sizeof ( struct iommu_domain_geometry ) ) ;
dma_domain - > geom_size = geom_size ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return 0 ;
}
/* Set the domain stash attribute */
static int configure_domain_stash ( struct fsl_dma_domain * dma_domain , void * data )
{
struct pamu_stash_attribute * stash_attr = data ;
unsigned long flags ;
int ret ;
spin_lock_irqsave ( & dma_domain - > domain_lock , flags ) ;
memcpy ( & dma_domain - > dma_stash , stash_attr ,
2015-01-28 17:34:33 +03:00
sizeof ( struct pamu_stash_attribute ) ) ;
2013-07-15 08:50:57 +04:00
dma_domain - > stash_id = get_stash_id ( stash_attr - > cache ,
stash_attr - > cpu ) ;
if ( dma_domain - > stash_id = = ~ ( u32 ) 0 ) {
pr_debug ( " Invalid stash attributes \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EINVAL ;
}
ret = update_domain_stash ( dma_domain , dma_domain - > stash_id ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return ret ;
}
2015-01-28 17:34:33 +03:00
/* Configure domain dma state i.e. enable/disable DMA */
2013-07-15 08:50:57 +04:00
static int configure_domain_dma_state ( struct fsl_dma_domain * dma_domain , bool enable )
{
struct device_domain_info * info ;
unsigned long flags ;
int ret ;
spin_lock_irqsave ( & dma_domain - > domain_lock , flags ) ;
if ( enable & & ! dma_domain - > mapped ) {
pr_debug ( " Can't enable DMA domain without valid mapping \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - ENODEV ;
}
dma_domain - > enabled = enable ;
2015-01-28 17:34:33 +03:00
list_for_each_entry ( info , & dma_domain - > devices , link ) {
2013-07-15 08:50:57 +04:00
ret = ( enable ) ? pamu_enable_liodn ( info - > liodn ) :
pamu_disable_liodn ( info - > liodn ) ;
if ( ret )
pr_debug ( " Unable to set dma state for liodn %d " ,
info - > liodn ) ;
}
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return 0 ;
}
static int fsl_pamu_set_domain_attr ( struct iommu_domain * domain ,
2015-01-28 17:34:33 +03:00
enum iommu_attr attr_type , void * data )
2013-07-15 08:50:57 +04:00
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
int ret = 0 ;
switch ( attr_type ) {
case DOMAIN_ATTR_GEOMETRY :
ret = configure_domain_geometry ( domain , data ) ;
break ;
case DOMAIN_ATTR_FSL_PAMU_STASH :
ret = configure_domain_stash ( dma_domain , data ) ;
break ;
case DOMAIN_ATTR_FSL_PAMU_ENABLE :
ret = configure_domain_dma_state ( dma_domain , * ( int * ) data ) ;
break ;
default :
pr_debug ( " Unsupported attribute type \n " ) ;
ret = - EINVAL ;
break ;
2015-01-28 17:34:33 +03:00
}
2013-07-15 08:50:57 +04:00
return ret ;
}
static int fsl_pamu_get_domain_attr ( struct iommu_domain * domain ,
2015-01-28 17:34:33 +03:00
enum iommu_attr attr_type , void * data )
2013-07-15 08:50:57 +04:00
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
int ret = 0 ;
switch ( attr_type ) {
case DOMAIN_ATTR_FSL_PAMU_STASH :
2015-01-28 17:34:33 +03:00
memcpy ( data , & dma_domain - > dma_stash ,
sizeof ( struct pamu_stash_attribute ) ) ;
2013-07-15 08:50:57 +04:00
break ;
case DOMAIN_ATTR_FSL_PAMU_ENABLE :
* ( int * ) data = dma_domain - > enabled ;
break ;
case DOMAIN_ATTR_FSL_PAMUV1 :
* ( int * ) data = DOMAIN_ATTR_FSL_PAMUV1 ;
break ;
default :
pr_debug ( " Unsupported attribute type \n " ) ;
ret = - EINVAL ;
break ;
2015-01-28 17:34:33 +03:00
}
2013-07-15 08:50:57 +04:00
return ret ;
}
static struct iommu_group * get_device_iommu_group ( struct device * dev )
{
struct iommu_group * group ;
group = iommu_group_get ( dev ) ;
if ( ! group )
group = iommu_group_alloc ( ) ;
return group ;
}
static bool check_pci_ctl_endpt_part ( struct pci_controller * pci_ctl )
{
u32 version ;
/* Check the PCI controller version number by readding BRR1 register */
version = in_be32 ( pci_ctl - > cfg_addr + ( PCI_FSL_BRR1 > > 2 ) ) ;
version & = PCI_FSL_BRR1_VER ;
2015-01-28 17:34:33 +03:00
/* If PCI controller version is >= 0x204 we can partition endpoints */
return version > = 0x204 ;
2013-07-15 08:50:57 +04:00
}
/* Get iommu group information from peer devices or devices on the parent bus */
static struct iommu_group * get_shared_pci_device_group ( struct pci_dev * pdev )
{
struct pci_dev * tmp ;
struct iommu_group * group ;
struct pci_bus * bus = pdev - > bus ;
2013-08-14 13:42:29 +04:00
/*
2013-07-15 08:50:57 +04:00
* Traverese the pci bus device list to get
* the shared iommu group .
*/
while ( bus ) {
list_for_each_entry ( tmp , & bus - > devices , bus_list ) {
if ( tmp = = pdev )
continue ;
group = iommu_group_get ( & tmp - > dev ) ;
if ( group )
return group ;
}
bus = bus - > parent ;
}
return NULL ;
}
static struct iommu_group * get_pci_device_group ( struct pci_dev * pdev )
{
struct pci_controller * pci_ctl ;
bool pci_endpt_partioning ;
struct iommu_group * group = NULL ;
pci_ctl = pci_bus_to_host ( pdev - > bus ) ;
pci_endpt_partioning = check_pci_ctl_endpt_part ( pci_ctl ) ;
/* We can partition PCIe devices so assign device group to the device */
if ( pci_endpt_partioning ) {
2015-10-22 00:51:40 +03:00
group = pci_device_group ( & pdev - > dev ) ;
2013-07-15 08:50:57 +04:00
/*
* PCIe controller is not a paritionable entity
* free the controller device iommu_group .
*/
if ( pci_ctl - > parent - > iommu_group )
iommu_group_remove_device ( pci_ctl - > parent ) ;
} else {
/*
* All devices connected to the controller will share the
* PCI controllers device group . If this is the first
* device to be probed for the pci controller , copy the
* device group information from the PCI controller device
* node and remove the PCI controller iommu group .
* For subsequent devices , the iommu group information can
* be obtained from sibling devices ( i . e . from the bus_devices
* link list ) .
*/
if ( pci_ctl - > parent - > iommu_group ) {
group = get_device_iommu_group ( pci_ctl - > parent ) ;
iommu_group_remove_device ( pci_ctl - > parent ) ;
2015-01-28 17:34:33 +03:00
} else {
2013-07-15 08:50:57 +04:00
group = get_shared_pci_device_group ( pdev ) ;
2015-01-28 17:34:33 +03:00
}
2013-07-15 08:50:57 +04:00
}
2014-06-24 17:57:17 +04:00
if ( ! group )
group = ERR_PTR ( - ENODEV ) ;
2013-07-15 08:50:57 +04:00
return group ;
}
2015-10-22 00:51:40 +03:00
static struct iommu_group * fsl_pamu_device_group ( struct device * dev )
2013-07-15 08:50:57 +04:00
{
2014-06-24 17:57:17 +04:00
struct iommu_group * group = ERR_PTR ( - ENODEV ) ;
2015-10-22 00:51:40 +03:00
int len ;
2013-07-15 08:50:57 +04:00
/*
* For platform devices we allocate a separate group for
* each of the devices .
*/
2015-10-22 00:51:40 +03:00
if ( dev_is_pci ( dev ) )
group = get_pci_device_group ( to_pci_dev ( dev ) ) ;
else if ( of_get_property ( dev - > of_node , " fsl,liodn " , & len ) )
group = get_device_iommu_group ( dev ) ;
2013-07-15 08:50:57 +04:00
2015-10-22 00:51:40 +03:00
return group ;
}
2013-07-15 08:50:57 +04:00
2015-10-22 00:51:40 +03:00
static int fsl_pamu_add_device ( struct device * dev )
{
struct iommu_group * group ;
2013-07-15 08:50:57 +04:00
2015-10-22 00:51:40 +03:00
group = iommu_group_get_for_dev ( dev ) ;
2014-06-24 17:57:17 +04:00
if ( IS_ERR ( group ) )
2013-07-15 08:50:57 +04:00
return PTR_ERR ( group ) ;
iommu_group_put ( group ) ;
2015-10-22 00:51:40 +03:00
2017-08-09 17:36:00 +03:00
iommu_device_link ( & pamu_iommu , dev ) ;
2015-10-22 00:51:40 +03:00
return 0 ;
2013-07-15 08:50:57 +04:00
}
static void fsl_pamu_remove_device ( struct device * dev )
{
2017-08-09 17:36:00 +03:00
iommu_device_unlink ( & pamu_iommu , dev ) ;
2013-07-15 08:50:57 +04:00
iommu_group_remove_device ( dev ) ;
}
static int fsl_pamu_set_windows ( struct iommu_domain * domain , u32 w_count )
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
unsigned long flags ;
int ret ;
spin_lock_irqsave ( & dma_domain - > domain_lock , flags ) ;
/* Ensure domain is inactive i.e. DMA should be disabled for the domain */
if ( dma_domain - > enabled ) {
pr_debug ( " Can't set geometry attributes as domain is active \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EBUSY ;
}
/* Ensure that the geometry has been set for the domain */
if ( ! dma_domain - > geom_size ) {
pr_debug ( " Please configure geometry before setting the number of windows \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EINVAL ;
}
/*
* Ensure we have valid window count i . e . it should be less than
* maximum permissible limit and should be a power of two .
*/
if ( w_count > pamu_get_max_subwin_cnt ( ) | | ! is_power_of_2 ( w_count ) ) {
pr_debug ( " Invalid window count \n " ) ;
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - EINVAL ;
}
ret = pamu_set_domain_geometry ( dma_domain , & domain - > geometry ,
2015-01-28 17:34:33 +03:00
w_count > 1 ? w_count : 0 ) ;
2013-07-15 08:50:57 +04:00
if ( ! ret ) {
2014-06-29 12:01:26 +04:00
kfree ( dma_domain - > win_arr ) ;
2015-01-28 17:34:33 +03:00
dma_domain - > win_arr = kcalloc ( w_count ,
sizeof ( * dma_domain - > win_arr ) ,
GFP_ATOMIC ) ;
2013-07-15 08:50:57 +04:00
if ( ! dma_domain - > win_arr ) {
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return - ENOMEM ;
}
dma_domain - > win_cnt = w_count ;
}
spin_unlock_irqrestore ( & dma_domain - > domain_lock , flags ) ;
return ret ;
}
static u32 fsl_pamu_get_windows ( struct iommu_domain * domain )
{
2015-03-26 15:43:18 +03:00
struct fsl_dma_domain * dma_domain = to_fsl_dma_domain ( domain ) ;
2013-07-15 08:50:57 +04:00
return dma_domain - > win_cnt ;
}
2014-06-27 11:03:12 +04:00
static const struct iommu_ops fsl_pamu_ops = {
2014-09-05 12:50:27 +04:00
. capable = fsl_pamu_capable ,
2015-03-26 15:43:18 +03:00
. domain_alloc = fsl_pamu_domain_alloc ,
. domain_free = fsl_pamu_domain_free ,
2013-07-15 08:50:57 +04:00
. attach_dev = fsl_pamu_attach_device ,
. detach_dev = fsl_pamu_detach_device ,
. domain_window_enable = fsl_pamu_window_enable ,
. domain_window_disable = fsl_pamu_window_disable ,
. domain_get_windows = fsl_pamu_get_windows ,
. domain_set_windows = fsl_pamu_set_windows ,
. iova_to_phys = fsl_pamu_iova_to_phys ,
. domain_set_attr = fsl_pamu_set_domain_attr ,
. domain_get_attr = fsl_pamu_get_domain_attr ,
. add_device = fsl_pamu_add_device ,
. remove_device = fsl_pamu_remove_device ,
2015-10-22 00:51:40 +03:00
. device_group = fsl_pamu_device_group ,
2013-07-15 08:50:57 +04:00
} ;
2015-01-28 17:34:33 +03:00
int __init pamu_domain_init ( void )
2013-07-15 08:50:57 +04:00
{
int ret = 0 ;
ret = iommu_init_mempool ( ) ;
if ( ret )
return ret ;
2017-08-23 17:28:09 +03:00
ret = iommu_device_sysfs_add ( & pamu_iommu , NULL , NULL , " iommu0 " ) ;
if ( ret )
return ret ;
iommu_device_set_ops ( & pamu_iommu , & fsl_pamu_ops ) ;
ret = iommu_device_register ( & pamu_iommu ) ;
if ( ret ) {
iommu_device_sysfs_remove ( & pamu_iommu ) ;
pr_err ( " Can't register iommu device \n " ) ;
return ret ;
}
2013-07-15 08:50:57 +04:00
bus_set_iommu ( & platform_bus_type , & fsl_pamu_ops ) ;
bus_set_iommu ( & pci_bus_type , & fsl_pamu_ops ) ;
return ret ;
}