2023-06-15 13:03:50 +05:30
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2021 Western Digital Corporation or its affiliates .
* Copyright ( C ) 2022 Ventana Micro Systems Inc .
*
* Authors :
* Anup Patel < apatel @ ventanamicro . com >
*/
# include <linux/kvm_host.h>
# include <linux/math.h>
# include <linux/spinlock.h>
# include <linux/swab.h>
# include <kvm/iodev.h>
# include <asm/kvm_aia_aplic.h>
struct aplic_irq {
raw_spinlock_t lock ;
u32 sourcecfg ;
u32 state ;
# define APLIC_IRQ_STATE_PENDING BIT(0)
# define APLIC_IRQ_STATE_ENABLED BIT(1)
# define APLIC_IRQ_STATE_ENPEND (APLIC_IRQ_STATE_PENDING | \
APLIC_IRQ_STATE_ENABLED )
# define APLIC_IRQ_STATE_INPUT BIT(8)
u32 target ;
} ;
struct aplic {
struct kvm_io_device iodev ;
u32 domaincfg ;
u32 genmsi ;
u32 nr_irqs ;
u32 nr_words ;
struct aplic_irq * irqs ;
} ;
static u32 aplic_read_sourcecfg ( struct aplic * aplic , u32 irq )
{
u32 ret ;
unsigned long flags ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return 0 ;
irqd = & aplic - > irqs [ irq ] ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
ret = irqd - > sourcecfg ;
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
return ret ;
}
static void aplic_write_sourcecfg ( struct aplic * aplic , u32 irq , u32 val )
{
unsigned long flags ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return ;
irqd = & aplic - > irqs [ irq ] ;
if ( val & APLIC_SOURCECFG_D )
val = 0 ;
else
val & = APLIC_SOURCECFG_SM_MASK ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
irqd - > sourcecfg = val ;
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
}
static u32 aplic_read_target ( struct aplic * aplic , u32 irq )
{
u32 ret ;
unsigned long flags ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return 0 ;
irqd = & aplic - > irqs [ irq ] ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
ret = irqd - > target ;
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
return ret ;
}
static void aplic_write_target ( struct aplic * aplic , u32 irq , u32 val )
{
unsigned long flags ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return ;
irqd = & aplic - > irqs [ irq ] ;
val & = APLIC_TARGET_EIID_MASK |
( APLIC_TARGET_HART_IDX_MASK < < APLIC_TARGET_HART_IDX_SHIFT ) |
( APLIC_TARGET_GUEST_IDX_MASK < < APLIC_TARGET_GUEST_IDX_SHIFT ) ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
irqd - > target = val ;
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
}
static bool aplic_read_pending ( struct aplic * aplic , u32 irq )
{
bool ret ;
unsigned long flags ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return false ;
irqd = & aplic - > irqs [ irq ] ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
ret = ( irqd - > state & APLIC_IRQ_STATE_PENDING ) ? true : false ;
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
return ret ;
}
static void aplic_write_pending ( struct aplic * aplic , u32 irq , bool pending )
{
unsigned long flags , sm ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return ;
irqd = & aplic - > irqs [ irq ] ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
sm = irqd - > sourcecfg & APLIC_SOURCECFG_SM_MASK ;
if ( ! pending & &
( ( sm = = APLIC_SOURCECFG_SM_LEVEL_HIGH ) | |
( sm = = APLIC_SOURCECFG_SM_LEVEL_LOW ) ) )
goto skip_write_pending ;
if ( pending )
irqd - > state | = APLIC_IRQ_STATE_PENDING ;
else
irqd - > state & = ~ APLIC_IRQ_STATE_PENDING ;
skip_write_pending :
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
}
static bool aplic_read_enabled ( struct aplic * aplic , u32 irq )
{
bool ret ;
unsigned long flags ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return false ;
irqd = & aplic - > irqs [ irq ] ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
ret = ( irqd - > state & APLIC_IRQ_STATE_ENABLED ) ? true : false ;
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
return ret ;
}
static void aplic_write_enabled ( struct aplic * aplic , u32 irq , bool enabled )
{
unsigned long flags ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return ;
irqd = & aplic - > irqs [ irq ] ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
if ( enabled )
irqd - > state | = APLIC_IRQ_STATE_ENABLED ;
else
irqd - > state & = ~ APLIC_IRQ_STATE_ENABLED ;
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
}
static bool aplic_read_input ( struct aplic * aplic , u32 irq )
{
bool ret ;
unsigned long flags ;
struct aplic_irq * irqd ;
if ( ! irq | | aplic - > nr_irqs < = irq )
return false ;
irqd = & aplic - > irqs [ irq ] ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
ret = ( irqd - > state & APLIC_IRQ_STATE_INPUT ) ? true : false ;
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
return ret ;
}
static void aplic_inject_msi ( struct kvm * kvm , u32 irq , u32 target )
{
u32 hart_idx , guest_idx , eiid ;
hart_idx = target > > APLIC_TARGET_HART_IDX_SHIFT ;
hart_idx & = APLIC_TARGET_HART_IDX_MASK ;
guest_idx = target > > APLIC_TARGET_GUEST_IDX_SHIFT ;
guest_idx & = APLIC_TARGET_GUEST_IDX_MASK ;
eiid = target & APLIC_TARGET_EIID_MASK ;
kvm_riscv_aia_inject_msi_by_id ( kvm , hart_idx , guest_idx , eiid ) ;
}
static void aplic_update_irq_range ( struct kvm * kvm , u32 first , u32 last )
{
bool inject ;
u32 irq , target ;
unsigned long flags ;
struct aplic_irq * irqd ;
struct aplic * aplic = kvm - > arch . aia . aplic_state ;
if ( ! ( aplic - > domaincfg & APLIC_DOMAINCFG_IE ) )
return ;
for ( irq = first ; irq < = last ; irq + + ) {
if ( ! irq | | aplic - > nr_irqs < = irq )
continue ;
irqd = & aplic - > irqs [ irq ] ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
inject = false ;
target = irqd - > target ;
if ( ( irqd - > state & APLIC_IRQ_STATE_ENPEND ) = =
APLIC_IRQ_STATE_ENPEND ) {
irqd - > state & = ~ APLIC_IRQ_STATE_PENDING ;
inject = true ;
}
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
if ( inject )
aplic_inject_msi ( kvm , irq , target ) ;
}
}
int kvm_riscv_aia_aplic_inject ( struct kvm * kvm , u32 source , bool level )
{
u32 target ;
bool inject = false , ie ;
unsigned long flags ;
struct aplic_irq * irqd ;
struct aplic * aplic = kvm - > arch . aia . aplic_state ;
if ( ! aplic | | ! source | | ( aplic - > nr_irqs < = source ) )
return - ENODEV ;
irqd = & aplic - > irqs [ source ] ;
ie = ( aplic - > domaincfg & APLIC_DOMAINCFG_IE ) ? true : false ;
raw_spin_lock_irqsave ( & irqd - > lock , flags ) ;
if ( irqd - > sourcecfg & APLIC_SOURCECFG_D )
goto skip_unlock ;
switch ( irqd - > sourcecfg & APLIC_SOURCECFG_SM_MASK ) {
case APLIC_SOURCECFG_SM_EDGE_RISE :
if ( level & & ! ( irqd - > state & APLIC_IRQ_STATE_INPUT ) & &
! ( irqd - > state & APLIC_IRQ_STATE_PENDING ) )
irqd - > state | = APLIC_IRQ_STATE_PENDING ;
break ;
case APLIC_SOURCECFG_SM_EDGE_FALL :
if ( ! level & & ( irqd - > state & APLIC_IRQ_STATE_INPUT ) & &
! ( irqd - > state & APLIC_IRQ_STATE_PENDING ) )
irqd - > state | = APLIC_IRQ_STATE_PENDING ;
break ;
case APLIC_SOURCECFG_SM_LEVEL_HIGH :
if ( level & & ! ( irqd - > state & APLIC_IRQ_STATE_PENDING ) )
irqd - > state | = APLIC_IRQ_STATE_PENDING ;
break ;
case APLIC_SOURCECFG_SM_LEVEL_LOW :
if ( ! level & & ! ( irqd - > state & APLIC_IRQ_STATE_PENDING ) )
irqd - > state | = APLIC_IRQ_STATE_PENDING ;
break ;
}
if ( level )
irqd - > state | = APLIC_IRQ_STATE_INPUT ;
else
irqd - > state & = ~ APLIC_IRQ_STATE_INPUT ;
target = irqd - > target ;
if ( ie & & ( ( irqd - > state & APLIC_IRQ_STATE_ENPEND ) = =
APLIC_IRQ_STATE_ENPEND ) ) {
irqd - > state & = ~ APLIC_IRQ_STATE_PENDING ;
inject = true ;
}
skip_unlock :
raw_spin_unlock_irqrestore ( & irqd - > lock , flags ) ;
if ( inject )
aplic_inject_msi ( kvm , source , target ) ;
return 0 ;
}
static u32 aplic_read_input_word ( struct aplic * aplic , u32 word )
{
u32 i , ret = 0 ;
for ( i = 0 ; i < 32 ; i + + )
ret | = aplic_read_input ( aplic , word * 32 + i ) ? BIT ( i ) : 0 ;
return ret ;
}
static u32 aplic_read_pending_word ( struct aplic * aplic , u32 word )
{
u32 i , ret = 0 ;
for ( i = 0 ; i < 32 ; i + + )
ret | = aplic_read_pending ( aplic , word * 32 + i ) ? BIT ( i ) : 0 ;
return ret ;
}
static void aplic_write_pending_word ( struct aplic * aplic , u32 word ,
u32 val , bool pending )
{
u32 i ;
for ( i = 0 ; i < 32 ; i + + ) {
if ( val & BIT ( i ) )
aplic_write_pending ( aplic , word * 32 + i , pending ) ;
}
}
static u32 aplic_read_enabled_word ( struct aplic * aplic , u32 word )
{
u32 i , ret = 0 ;
for ( i = 0 ; i < 32 ; i + + )
ret | = aplic_read_enabled ( aplic , word * 32 + i ) ? BIT ( i ) : 0 ;
return ret ;
}
static void aplic_write_enabled_word ( struct aplic * aplic , u32 word ,
u32 val , bool enabled )
{
u32 i ;
for ( i = 0 ; i < 32 ; i + + ) {
if ( val & BIT ( i ) )
aplic_write_enabled ( aplic , word * 32 + i , enabled ) ;
}
}
static int aplic_mmio_read_offset ( struct kvm * kvm , gpa_t off , u32 * val32 )
{
u32 i ;
struct aplic * aplic = kvm - > arch . aia . aplic_state ;
if ( ( off & 0x3 ) ! = 0 )
return - EOPNOTSUPP ;
if ( off = = APLIC_DOMAINCFG ) {
* val32 = APLIC_DOMAINCFG_RDONLY |
aplic - > domaincfg | APLIC_DOMAINCFG_DM ;
} else if ( ( off > = APLIC_SOURCECFG_BASE ) & &
( off < ( APLIC_SOURCECFG_BASE + ( aplic - > nr_irqs - 1 ) * 4 ) ) ) {
i = ( ( off - APLIC_SOURCECFG_BASE ) > > 2 ) + 1 ;
* val32 = aplic_read_sourcecfg ( aplic , i ) ;
} else if ( ( off > = APLIC_SETIP_BASE ) & &
( off < ( APLIC_SETIP_BASE + aplic - > nr_words * 4 ) ) ) {
i = ( off - APLIC_SETIP_BASE ) > > 2 ;
* val32 = aplic_read_pending_word ( aplic , i ) ;
} else if ( off = = APLIC_SETIPNUM ) {
* val32 = 0 ;
} else if ( ( off > = APLIC_CLRIP_BASE ) & &
( off < ( APLIC_CLRIP_BASE + aplic - > nr_words * 4 ) ) ) {
i = ( off - APLIC_CLRIP_BASE ) > > 2 ;
* val32 = aplic_read_input_word ( aplic , i ) ;
} else if ( off = = APLIC_CLRIPNUM ) {
* val32 = 0 ;
} else if ( ( off > = APLIC_SETIE_BASE ) & &
( off < ( APLIC_SETIE_BASE + aplic - > nr_words * 4 ) ) ) {
i = ( off - APLIC_SETIE_BASE ) > > 2 ;
* val32 = aplic_read_enabled_word ( aplic , i ) ;
} else if ( off = = APLIC_SETIENUM ) {
* val32 = 0 ;
} else if ( ( off > = APLIC_CLRIE_BASE ) & &
( off < ( APLIC_CLRIE_BASE + aplic - > nr_words * 4 ) ) ) {
* val32 = 0 ;
} else if ( off = = APLIC_CLRIENUM ) {
* val32 = 0 ;
} else if ( off = = APLIC_SETIPNUM_LE ) {
* val32 = 0 ;
} else if ( off = = APLIC_SETIPNUM_BE ) {
* val32 = 0 ;
} else if ( off = = APLIC_GENMSI ) {
* val32 = aplic - > genmsi ;
} else if ( ( off > = APLIC_TARGET_BASE ) & &
( off < ( APLIC_TARGET_BASE + ( aplic - > nr_irqs - 1 ) * 4 ) ) ) {
i = ( ( off - APLIC_TARGET_BASE ) > > 2 ) + 1 ;
* val32 = aplic_read_target ( aplic , i ) ;
} else
return - ENODEV ;
return 0 ;
}
static int aplic_mmio_read ( struct kvm_vcpu * vcpu , struct kvm_io_device * dev ,
gpa_t addr , int len , void * val )
{
if ( len ! = 4 )
return - EOPNOTSUPP ;
return aplic_mmio_read_offset ( vcpu - > kvm ,
addr - vcpu - > kvm - > arch . aia . aplic_addr ,
val ) ;
}
static int aplic_mmio_write_offset ( struct kvm * kvm , gpa_t off , u32 val32 )
{
u32 i ;
struct aplic * aplic = kvm - > arch . aia . aplic_state ;
if ( ( off & 0x3 ) ! = 0 )
return - EOPNOTSUPP ;
if ( off = = APLIC_DOMAINCFG ) {
/* Only IE bit writeable */
aplic - > domaincfg = val32 & APLIC_DOMAINCFG_IE ;
} else if ( ( off > = APLIC_SOURCECFG_BASE ) & &
( off < ( APLIC_SOURCECFG_BASE + ( aplic - > nr_irqs - 1 ) * 4 ) ) ) {
i = ( ( off - APLIC_SOURCECFG_BASE ) > > 2 ) + 1 ;
aplic_write_sourcecfg ( aplic , i , val32 ) ;
} else if ( ( off > = APLIC_SETIP_BASE ) & &
( off < ( APLIC_SETIP_BASE + aplic - > nr_words * 4 ) ) ) {
i = ( off - APLIC_SETIP_BASE ) > > 2 ;
aplic_write_pending_word ( aplic , i , val32 , true ) ;
} else if ( off = = APLIC_SETIPNUM ) {
aplic_write_pending ( aplic , val32 , true ) ;
} else if ( ( off > = APLIC_CLRIP_BASE ) & &
( off < ( APLIC_CLRIP_BASE + aplic - > nr_words * 4 ) ) ) {
i = ( off - APLIC_CLRIP_BASE ) > > 2 ;
aplic_write_pending_word ( aplic , i , val32 , false ) ;
} else if ( off = = APLIC_CLRIPNUM ) {
aplic_write_pending ( aplic , val32 , false ) ;
} else if ( ( off > = APLIC_SETIE_BASE ) & &
( off < ( APLIC_SETIE_BASE + aplic - > nr_words * 4 ) ) ) {
i = ( off - APLIC_SETIE_BASE ) > > 2 ;
aplic_write_enabled_word ( aplic , i , val32 , true ) ;
} else if ( off = = APLIC_SETIENUM ) {
aplic_write_enabled ( aplic , val32 , true ) ;
} else if ( ( off > = APLIC_CLRIE_BASE ) & &
( off < ( APLIC_CLRIE_BASE + aplic - > nr_words * 4 ) ) ) {
i = ( off - APLIC_CLRIE_BASE ) > > 2 ;
aplic_write_enabled_word ( aplic , i , val32 , false ) ;
} else if ( off = = APLIC_CLRIENUM ) {
aplic_write_enabled ( aplic , val32 , false ) ;
} else if ( off = = APLIC_SETIPNUM_LE ) {
aplic_write_pending ( aplic , val32 , true ) ;
} else if ( off = = APLIC_SETIPNUM_BE ) {
aplic_write_pending ( aplic , __swab32 ( val32 ) , true ) ;
} else if ( off = = APLIC_GENMSI ) {
aplic - > genmsi = val32 & ~ ( APLIC_TARGET_GUEST_IDX_MASK < <
APLIC_TARGET_GUEST_IDX_SHIFT ) ;
kvm_riscv_aia_inject_msi_by_id ( kvm ,
val32 > > APLIC_TARGET_HART_IDX_SHIFT , 0 ,
val32 & APLIC_TARGET_EIID_MASK ) ;
} else if ( ( off > = APLIC_TARGET_BASE ) & &
( off < ( APLIC_TARGET_BASE + ( aplic - > nr_irqs - 1 ) * 4 ) ) ) {
i = ( ( off - APLIC_TARGET_BASE ) > > 2 ) + 1 ;
aplic_write_target ( aplic , i , val32 ) ;
} else
return - ENODEV ;
aplic_update_irq_range ( kvm , 1 , aplic - > nr_irqs - 1 ) ;
return 0 ;
}
static int aplic_mmio_write ( struct kvm_vcpu * vcpu , struct kvm_io_device * dev ,
gpa_t addr , int len , const void * val )
{
if ( len ! = 4 )
return - EOPNOTSUPP ;
return aplic_mmio_write_offset ( vcpu - > kvm ,
addr - vcpu - > kvm - > arch . aia . aplic_addr ,
* ( ( const u32 * ) val ) ) ;
}
static struct kvm_io_device_ops aplic_iodoev_ops = {
. read = aplic_mmio_read ,
. write = aplic_mmio_write ,
} ;
2023-06-15 13:03:51 +05:30
int kvm_riscv_aia_aplic_set_attr ( struct kvm * kvm , unsigned long type , u32 v )
{
int rc ;
if ( ! kvm - > arch . aia . aplic_state )
return - ENODEV ;
rc = aplic_mmio_write_offset ( kvm , type , v ) ;
if ( rc )
return rc ;
return 0 ;
}
int kvm_riscv_aia_aplic_get_attr ( struct kvm * kvm , unsigned long type , u32 * v )
{
int rc ;
if ( ! kvm - > arch . aia . aplic_state )
return - ENODEV ;
rc = aplic_mmio_read_offset ( kvm , type , v ) ;
if ( rc )
return rc ;
return 0 ;
}
int kvm_riscv_aia_aplic_has_attr ( struct kvm * kvm , unsigned long type )
{
int rc ;
u32 val ;
if ( ! kvm - > arch . aia . aplic_state )
return - ENODEV ;
rc = aplic_mmio_read_offset ( kvm , type , & val ) ;
if ( rc )
return rc ;
return 0 ;
}
2023-06-15 13:03:50 +05:30
int kvm_riscv_aia_aplic_init ( struct kvm * kvm )
{
int i , ret = 0 ;
struct aplic * aplic ;
/* Do nothing if we have zero sources */
if ( ! kvm - > arch . aia . nr_sources )
return 0 ;
/* Allocate APLIC global state */
aplic = kzalloc ( sizeof ( * aplic ) , GFP_KERNEL ) ;
if ( ! aplic )
return - ENOMEM ;
kvm - > arch . aia . aplic_state = aplic ;
/* Setup APLIC IRQs */
aplic - > nr_irqs = kvm - > arch . aia . nr_sources + 1 ;
aplic - > nr_words = DIV_ROUND_UP ( aplic - > nr_irqs , 32 ) ;
aplic - > irqs = kcalloc ( aplic - > nr_irqs ,
sizeof ( * aplic - > irqs ) , GFP_KERNEL ) ;
if ( ! aplic - > irqs ) {
ret = - ENOMEM ;
goto fail_free_aplic ;
}
for ( i = 0 ; i < aplic - > nr_irqs ; i + + )
raw_spin_lock_init ( & aplic - > irqs [ i ] . lock ) ;
/* Setup IO device */
kvm_iodevice_init ( & aplic - > iodev , & aplic_iodoev_ops ) ;
mutex_lock ( & kvm - > slots_lock ) ;
ret = kvm_io_bus_register_dev ( kvm , KVM_MMIO_BUS ,
kvm - > arch . aia . aplic_addr ,
KVM_DEV_RISCV_APLIC_SIZE ,
& aplic - > iodev ) ;
mutex_unlock ( & kvm - > slots_lock ) ;
if ( ret )
goto fail_free_aplic_irqs ;
/* Setup default IRQ routing */
ret = kvm_riscv_setup_default_irq_routing ( kvm , aplic - > nr_irqs ) ;
if ( ret )
goto fail_unreg_iodev ;
return 0 ;
fail_unreg_iodev :
mutex_lock ( & kvm - > slots_lock ) ;
kvm_io_bus_unregister_dev ( kvm , KVM_MMIO_BUS , & aplic - > iodev ) ;
mutex_unlock ( & kvm - > slots_lock ) ;
fail_free_aplic_irqs :
kfree ( aplic - > irqs ) ;
fail_free_aplic :
kvm - > arch . aia . aplic_state = NULL ;
kfree ( aplic ) ;
return ret ;
}
void kvm_riscv_aia_aplic_cleanup ( struct kvm * kvm )
{
struct aplic * aplic = kvm - > arch . aia . aplic_state ;
if ( ! aplic )
return ;
mutex_lock ( & kvm - > slots_lock ) ;
kvm_io_bus_unregister_dev ( kvm , KVM_MMIO_BUS , & aplic - > iodev ) ;
mutex_unlock ( & kvm - > slots_lock ) ;
kfree ( aplic - > irqs ) ;
kvm - > arch . aia . aplic_state = NULL ;
kfree ( aplic ) ;
}