2021-03-05 15:19:52 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2021 Intel Corporation
* Author : Johannes Berg < johannes @ sipsolutions . net >
*/
# include <linux/types.h>
# include <linux/slab.h>
# include <linux/logic_iomem.h>
2021-06-25 11:34:35 +03:00
# include <asm/io.h>
2021-03-05 15:19:52 +03:00
struct logic_iomem_region {
const struct resource * res ;
const struct logic_iomem_region_ops * ops ;
struct list_head list ;
} ;
struct logic_iomem_area {
const struct logic_iomem_ops * ops ;
void * priv ;
} ;
# define AREA_SHIFT 24
# define MAX_AREA_SIZE (1 << AREA_SHIFT)
2021-09-15 21:30:22 +03:00
# define MAX_AREAS ((1U << 31) / MAX_AREA_SIZE)
2021-03-05 15:19:52 +03:00
# define AREA_BITS ((MAX_AREAS - 1) << AREA_SHIFT)
# define AREA_MASK (MAX_AREA_SIZE - 1)
# ifdef CONFIG_64BIT
# define IOREMAP_BIAS 0xDEAD000000000000UL
# define IOREMAP_MASK 0xFFFFFFFF00000000UL
# else
2021-09-15 21:30:22 +03:00
# define IOREMAP_BIAS 0x80000000UL
# define IOREMAP_MASK 0x80000000UL
2021-03-05 15:19:52 +03:00
# endif
static DEFINE_MUTEX ( regions_mtx ) ;
static LIST_HEAD ( regions_list ) ;
static struct logic_iomem_area mapped_areas [ MAX_AREAS ] ;
int logic_iomem_add_region ( struct resource * resource ,
const struct logic_iomem_region_ops * ops )
{
struct logic_iomem_region * rreg ;
int err ;
if ( WARN_ON ( ! resource | | ! ops ) )
return - EINVAL ;
if ( WARN_ON ( ( resource - > flags & IORESOURCE_TYPE_BITS ) ! = IORESOURCE_MEM ) )
return - EINVAL ;
rreg = kzalloc ( sizeof ( * rreg ) , GFP_KERNEL ) ;
if ( ! rreg )
return - ENOMEM ;
err = request_resource ( & iomem_resource , resource ) ;
if ( err ) {
kfree ( rreg ) ;
return - ENOMEM ;
}
mutex_lock ( & regions_mtx ) ;
rreg - > res = resource ;
rreg - > ops = ops ;
list_add_tail ( & rreg - > list , & regions_list ) ;
mutex_unlock ( & regions_mtx ) ;
return 0 ;
}
EXPORT_SYMBOL ( logic_iomem_add_region ) ;
2022-01-03 18:40:15 +03:00
# ifndef CONFIG_INDIRECT_IOMEM_FALLBACK
2021-03-05 15:19:52 +03:00
static void __iomem * real_ioremap ( phys_addr_t offset , size_t size )
{
WARN ( 1 , " invalid ioremap(0x%llx, 0x%zx) \n " ,
( unsigned long long ) offset , size ) ;
return NULL ;
}
2021-09-21 00:32:49 +03:00
static void real_iounmap ( volatile void __iomem * addr )
2021-03-05 15:19:52 +03:00
{
WARN ( 1 , " invalid iounmap for addr 0x%llx \n " ,
2021-09-15 21:30:21 +03:00
( unsigned long long ) ( uintptr_t __force ) addr ) ;
2021-03-05 15:19:52 +03:00
}
2022-01-03 18:40:15 +03:00
# endif /* CONFIG_INDIRECT_IOMEM_FALLBACK */
2021-03-05 15:19:52 +03:00
void __iomem * ioremap ( phys_addr_t offset , size_t size )
{
void __iomem * ret = NULL ;
struct logic_iomem_region * rreg , * found = NULL ;
int i ;
mutex_lock ( & regions_mtx ) ;
list_for_each_entry ( rreg , & regions_list , list ) {
if ( rreg - > res - > start > offset )
continue ;
if ( rreg - > res - > end < offset + size - 1 )
continue ;
found = rreg ;
break ;
}
if ( ! found )
goto out ;
for ( i = 0 ; i < MAX_AREAS ; i + + ) {
long offs ;
if ( mapped_areas [ i ] . ops )
continue ;
offs = rreg - > ops - > map ( offset - found - > res - > start ,
size , & mapped_areas [ i ] . ops ,
& mapped_areas [ i ] . priv ) ;
if ( offs < 0 ) {
mapped_areas [ i ] . ops = NULL ;
break ;
}
if ( WARN_ON ( ! mapped_areas [ i ] . ops ) ) {
mapped_areas [ i ] . ops = NULL ;
break ;
}
ret = ( void __iomem * ) ( IOREMAP_BIAS + ( i < < AREA_SHIFT ) + offs ) ;
break ;
}
out :
mutex_unlock ( & regions_mtx ) ;
if ( ret )
return ret ;
return real_ioremap ( offset , size ) ;
}
EXPORT_SYMBOL ( ioremap ) ;
static inline struct logic_iomem_area *
get_area ( const volatile void __iomem * addr )
{
unsigned long a = ( unsigned long ) addr ;
unsigned int idx ;
if ( WARN_ON ( ( a & IOREMAP_MASK ) ! = IOREMAP_BIAS ) )
return NULL ;
idx = ( a & AREA_BITS ) > > AREA_SHIFT ;
if ( mapped_areas [ idx ] . ops )
return & mapped_areas [ idx ] ;
return NULL ;
}
2021-09-21 00:32:49 +03:00
void iounmap ( volatile void __iomem * addr )
2021-03-05 15:19:52 +03:00
{
struct logic_iomem_area * area = get_area ( addr ) ;
if ( ! area ) {
real_iounmap ( addr ) ;
return ;
}
if ( area - > ops - > unmap )
area - > ops - > unmap ( area - > priv ) ;
mutex_lock ( & regions_mtx ) ;
area - > ops = NULL ;
area - > priv = NULL ;
mutex_unlock ( & regions_mtx ) ;
}
EXPORT_SYMBOL ( iounmap ) ;
2022-01-03 18:40:15 +03:00
# ifndef CONFIG_INDIRECT_IOMEM_FALLBACK
2021-03-05 15:19:52 +03:00
# define MAKE_FALLBACK(op, sz) \
static u # # sz real_raw_read # # op ( const volatile void __iomem * addr ) \
{ \
WARN ( 1 , " Invalid read " # op " at address %llx \n " , \
2021-09-15 21:30:21 +03:00
( unsigned long long ) ( uintptr_t __force ) addr ) ; \
2021-03-05 15:19:52 +03:00
return ( u # # sz ) ~ 0ULL ; \
} \
\
2021-06-25 11:34:35 +03:00
static void real_raw_write # # op ( u # # sz val , \
volatile void __iomem * addr ) \
2021-03-05 15:19:52 +03:00
{ \
WARN ( 1 , " Invalid writeq " # op " of 0x%llx at address %llx \n " , \
2021-09-15 21:30:21 +03:00
( unsigned long long ) val , \
( unsigned long long ) ( uintptr_t __force ) addr ) ; \
2021-03-05 15:19:52 +03:00
} \
MAKE_FALLBACK ( b , 8 ) ;
MAKE_FALLBACK ( w , 16 ) ;
MAKE_FALLBACK ( l , 32 ) ;
# ifdef CONFIG_64BIT
MAKE_FALLBACK ( q , 64 ) ;
# endif
static void real_memset_io ( volatile void __iomem * addr , int value , size_t size )
{
WARN ( 1 , " Invalid memset_io at address 0x%llx \n " ,
2021-09-15 21:30:21 +03:00
( unsigned long long ) ( uintptr_t __force ) addr ) ;
2021-03-05 15:19:52 +03:00
}
static void real_memcpy_fromio ( void * buffer , const volatile void __iomem * addr ,
size_t size )
{
WARN ( 1 , " Invalid memcpy_fromio at address 0x%llx \n " ,
2021-09-15 21:30:21 +03:00
( unsigned long long ) ( uintptr_t __force ) addr ) ;
2021-03-05 15:19:52 +03:00
memset ( buffer , 0xff , size ) ;
}
static void real_memcpy_toio ( volatile void __iomem * addr , const void * buffer ,
size_t size )
{
WARN ( 1 , " Invalid memcpy_toio at address 0x%llx \n " ,
2021-09-15 21:30:21 +03:00
( unsigned long long ) ( uintptr_t __force ) addr ) ;
2021-03-05 15:19:52 +03:00
}
2022-01-03 18:40:15 +03:00
# endif /* CONFIG_INDIRECT_IOMEM_FALLBACK */
2021-03-05 15:19:52 +03:00
# define MAKE_OP(op, sz) \
u # # sz __raw_read # # op ( const volatile void __iomem * addr ) \
{ \
struct logic_iomem_area * area = get_area ( addr ) ; \
\
if ( ! area ) \
return real_raw_read # # op ( addr ) ; \
\
return ( u # # sz ) area - > ops - > read ( area - > priv , \
( unsigned long ) addr & AREA_MASK , \
sz / 8 ) ; \
} \
EXPORT_SYMBOL ( __raw_read # # op ) ; \
\
void __raw_write # # op ( u # # sz val , volatile void __iomem * addr ) \
{ \
struct logic_iomem_area * area = get_area ( addr ) ; \
\
if ( ! area ) { \
real_raw_write # # op ( val , addr ) ; \
return ; \
} \
\
area - > ops - > write ( area - > priv , \
( unsigned long ) addr & AREA_MASK , \
sz / 8 , val ) ; \
} \
EXPORT_SYMBOL ( __raw_write # # op )
MAKE_OP ( b , 8 ) ;
MAKE_OP ( w , 16 ) ;
MAKE_OP ( l , 32 ) ;
# ifdef CONFIG_64BIT
MAKE_OP ( q , 64 ) ;
# endif
void memset_io ( volatile void __iomem * addr , int value , size_t size )
{
struct logic_iomem_area * area = get_area ( addr ) ;
unsigned long offs , start ;
if ( ! area ) {
real_memset_io ( addr , value , size ) ;
return ;
}
start = ( unsigned long ) addr & AREA_MASK ;
if ( area - > ops - > set ) {
area - > ops - > set ( area - > priv , start , value , size ) ;
return ;
}
for ( offs = 0 ; offs < size ; offs + + )
area - > ops - > write ( area - > priv , start + offs , 1 , value ) ;
}
EXPORT_SYMBOL ( memset_io ) ;
void memcpy_fromio ( void * buffer , const volatile void __iomem * addr ,
size_t size )
{
struct logic_iomem_area * area = get_area ( addr ) ;
u8 * buf = buffer ;
unsigned long offs , start ;
if ( ! area ) {
real_memcpy_fromio ( buffer , addr , size ) ;
return ;
}
start = ( unsigned long ) addr & AREA_MASK ;
if ( area - > ops - > copy_from ) {
area - > ops - > copy_from ( area - > priv , buffer , start , size ) ;
return ;
}
for ( offs = 0 ; offs < size ; offs + + )
buf [ offs ] = area - > ops - > read ( area - > priv , start + offs , 1 ) ;
}
EXPORT_SYMBOL ( memcpy_fromio ) ;
void memcpy_toio ( volatile void __iomem * addr , const void * buffer , size_t size )
{
struct logic_iomem_area * area = get_area ( addr ) ;
const u8 * buf = buffer ;
unsigned long offs , start ;
if ( ! area ) {
real_memcpy_toio ( addr , buffer , size ) ;
return ;
}
start = ( unsigned long ) addr & AREA_MASK ;
if ( area - > ops - > copy_to ) {
area - > ops - > copy_to ( area - > priv , start , buffer , size ) ;
return ;
}
for ( offs = 0 ; offs < size ; offs + + )
area - > ops - > write ( area - > priv , start + offs , 1 , buf [ offs ] ) ;
}
EXPORT_SYMBOL ( memcpy_toio ) ;