2007-07-18 13:03:39 +04:00
/*
* Copyright ( C ) 2001 MandrakeSoft S . A .
*
* MandrakeSoft S . A .
* 43 , rue d ' Aboukir
* 75002 Paris - France
* http : //www.linux-mandrake.com/
* http : //www.mandrakesoft.com/
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 of the License , or ( at your option ) any later version .
*
* This library 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
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
* Yunhong Jiang < yunhong . jiang @ intel . com >
* Yaozu ( Eddie ) Dong < eddie . dong @ intel . com >
* Based on Xen 3.1 code .
*/
2007-12-16 12:02:48 +03:00
# include <linux/kvm_host.h>
2007-07-18 13:03:39 +04:00
# include <linux/kvm.h>
# include <linux/mm.h>
# include <linux/highmem.h>
# include <linux/smp.h>
# include <linux/hrtimer.h>
# include <linux/io.h>
# include <asm/processor.h>
# include <asm/page.h>
# include <asm/current.h>
2007-12-17 08:59:56 +03:00
# include "ioapic.h"
# include "lapic.h"
2007-10-12 13:01:59 +04:00
#if 0
# define ioapic_debug(fmt,arg...) printk(KERN_WARNING fmt,##arg)
# else
2007-07-18 13:03:39 +04:00
# define ioapic_debug(fmt, arg...)
2007-10-12 13:01:59 +04:00
# endif
2008-06-05 07:08:11 +04:00
static int ioapic_deliver ( struct kvm_ioapic * vioapic , int irq ) ;
2007-07-18 13:03:39 +04:00
static unsigned long ioapic_read_indirect ( struct kvm_ioapic * ioapic ,
unsigned long addr ,
unsigned long length )
{
unsigned long result = 0 ;
switch ( ioapic - > ioregsel ) {
case IOAPIC_REG_VERSION :
result = ( ( ( ( IOAPIC_NUM_PINS - 1 ) & 0xff ) < < 16 )
| ( IOAPIC_VERSION_ID & 0xff ) ) ;
break ;
case IOAPIC_REG_APIC_ID :
case IOAPIC_REG_ARB_ID :
result = ( ( ioapic - > id & 0xf ) < < 24 ) ;
break ;
default :
{
u32 redir_index = ( ioapic - > ioregsel - 0x10 ) > > 1 ;
u64 redir_content ;
ASSERT ( redir_index < IOAPIC_NUM_PINS ) ;
redir_content = ioapic - > redirtbl [ redir_index ] . bits ;
result = ( ioapic - > ioregsel & 0x1 ) ?
( redir_content > > 32 ) & 0xffffffff :
redir_content & 0xffffffff ;
break ;
}
}
return result ;
}
static void ioapic_service ( struct kvm_ioapic * ioapic , unsigned int idx )
{
union ioapic_redir_entry * pent ;
pent = & ioapic - > redirtbl [ idx ] ;
if ( ! pent - > fields . mask ) {
2008-06-05 07:08:11 +04:00
int injected = ioapic_deliver ( ioapic , idx ) ;
if ( injected & & pent - > fields . trig_mode = = IOAPIC_LEVEL_TRIG )
2007-07-18 13:03:39 +04:00
pent - > fields . remote_irr = 1 ;
}
if ( ! pent - > fields . trig_mode )
ioapic - > irr & = ~ ( 1 < < idx ) ;
}
static void ioapic_write_indirect ( struct kvm_ioapic * ioapic , u32 val )
{
unsigned index ;
switch ( ioapic - > ioregsel ) {
case IOAPIC_REG_VERSION :
/* Writes are ignored. */
break ;
case IOAPIC_REG_APIC_ID :
ioapic - > id = ( val > > 24 ) & 0xf ;
break ;
case IOAPIC_REG_ARB_ID :
break ;
default :
index = ( ioapic - > ioregsel - 0x10 ) > > 1 ;
2007-10-12 13:01:59 +04:00
ioapic_debug ( " change redir index %x val %x \n " , index , val ) ;
2007-07-18 13:03:39 +04:00
if ( index > = IOAPIC_NUM_PINS )
return ;
if ( ioapic - > ioregsel & 1 ) {
ioapic - > redirtbl [ index ] . bits & = 0xffffffff ;
ioapic - > redirtbl [ index ] . bits | = ( u64 ) val < < 32 ;
} else {
ioapic - > redirtbl [ index ] . bits & = ~ 0xffffffffULL ;
ioapic - > redirtbl [ index ] . bits | = ( u32 ) val ;
ioapic - > redirtbl [ index ] . fields . remote_irr = 0 ;
}
if ( ioapic - > irr & ( 1 < < index ) )
ioapic_service ( ioapic , index ) ;
break ;
}
}
2008-06-05 07:08:11 +04:00
static int ioapic_inj_irq ( struct kvm_ioapic * ioapic ,
2007-12-02 17:35:57 +03:00
struct kvm_vcpu * vcpu ,
2007-07-18 13:03:39 +04:00
u8 vector , u8 trig_mode , u8 delivery_mode )
{
2007-10-12 13:01:59 +04:00
ioapic_debug ( " irq %d trig %d deliv %d \n " , vector , trig_mode ,
2007-07-18 13:03:39 +04:00
delivery_mode ) ;
2007-12-02 17:49:09 +03:00
ASSERT ( ( delivery_mode = = IOAPIC_FIXED ) | |
( delivery_mode = = IOAPIC_LOWEST_PRIORITY ) ) ;
2007-07-18 13:03:39 +04:00
2008-06-05 07:08:11 +04:00
return kvm_apic_set_irq ( vcpu , vector , trig_mode ) ;
2007-07-18 13:03:39 +04:00
}
static u32 ioapic_get_delivery_bitmask ( struct kvm_ioapic * ioapic , u8 dest ,
u8 dest_mode )
{
u32 mask = 0 ;
int i ;
struct kvm * kvm = ioapic - > kvm ;
struct kvm_vcpu * vcpu ;
2007-10-12 13:01:59 +04:00
ioapic_debug ( " dest %d dest_mode %d \n " , dest , dest_mode ) ;
2007-07-18 13:03:39 +04:00
if ( dest_mode = = 0 ) { /* Physical mode. */
if ( dest = = 0xFF ) { /* Broadcast. */
for ( i = 0 ; i < KVM_MAX_VCPUS ; + + i )
2007-12-13 18:50:52 +03:00
if ( kvm - > vcpus [ i ] & & kvm - > vcpus [ i ] - > arch . apic )
2007-07-18 13:03:39 +04:00
mask | = 1 < < i ;
return mask ;
}
for ( i = 0 ; i < KVM_MAX_VCPUS ; + + i ) {
vcpu = kvm - > vcpus [ i ] ;
if ( ! vcpu )
continue ;
2007-12-13 18:50:52 +03:00
if ( kvm_apic_match_physical_addr ( vcpu - > arch . apic , dest ) ) {
if ( vcpu - > arch . apic )
2007-07-18 13:03:39 +04:00
mask = 1 < < i ;
break ;
}
}
} else if ( dest ! = 0 ) /* Logical mode, MDA non-zero. */
for ( i = 0 ; i < KVM_MAX_VCPUS ; + + i ) {
vcpu = kvm - > vcpus [ i ] ;
if ( ! vcpu )
continue ;
2007-12-13 18:50:52 +03:00
if ( vcpu - > arch . apic & &
kvm_apic_match_logical_addr ( vcpu - > arch . apic , dest ) )
2007-07-18 13:03:39 +04:00
mask | = 1 < < vcpu - > vcpu_id ;
}
2007-10-12 13:01:59 +04:00
ioapic_debug ( " mask %x \n " , mask ) ;
2007-07-18 13:03:39 +04:00
return mask ;
}
2008-06-05 07:08:11 +04:00
static int ioapic_deliver ( struct kvm_ioapic * ioapic , int irq )
2007-07-18 13:03:39 +04:00
{
u8 dest = ioapic - > redirtbl [ irq ] . fields . dest_id ;
u8 dest_mode = ioapic - > redirtbl [ irq ] . fields . dest_mode ;
u8 delivery_mode = ioapic - > redirtbl [ irq ] . fields . delivery_mode ;
u8 vector = ioapic - > redirtbl [ irq ] . fields . vector ;
u8 trig_mode = ioapic - > redirtbl [ irq ] . fields . trig_mode ;
u32 deliver_bitmask ;
struct kvm_vcpu * vcpu ;
2008-06-05 07:08:11 +04:00
int vcpu_id , r = 0 ;
2007-07-18 13:03:39 +04:00
ioapic_debug ( " dest=%x dest_mode=%x delivery_mode=%x "
2007-10-12 13:01:59 +04:00
" vector=%x trig_mode=%x \n " ,
2007-07-18 13:03:39 +04:00
dest , dest_mode , delivery_mode , vector , trig_mode ) ;
deliver_bitmask = ioapic_get_delivery_bitmask ( ioapic , dest , dest_mode ) ;
if ( ! deliver_bitmask ) {
2007-10-12 13:01:59 +04:00
ioapic_debug ( " no target on destination \n " ) ;
2008-06-05 07:08:11 +04:00
return 0 ;
2007-07-18 13:03:39 +04:00
}
switch ( delivery_mode ) {
2007-12-02 17:49:09 +03:00
case IOAPIC_LOWEST_PRIORITY :
2007-12-02 17:35:57 +03:00
vcpu = kvm_get_lowest_prio_vcpu ( ioapic - > kvm , vector ,
deliver_bitmask ) ;
2008-02-25 11:28:31 +03:00
# ifdef CONFIG_X86
if ( irq = = 0 )
vcpu = ioapic - > kvm - > vcpus [ 0 ] ;
# endif
2007-12-02 17:35:57 +03:00
if ( vcpu ! = NULL )
2008-06-05 07:08:11 +04:00
r = ioapic_inj_irq ( ioapic , vcpu , vector ,
2007-07-18 13:03:39 +04:00
trig_mode , delivery_mode ) ;
else
2007-12-02 17:35:57 +03:00
ioapic_debug ( " null lowest prio vcpu: "
2007-10-12 13:01:59 +04:00
" mask=%x vector=%x delivery_mode=%x \n " ,
2007-12-02 17:49:09 +03:00
deliver_bitmask , vector , IOAPIC_LOWEST_PRIORITY ) ;
2007-07-18 13:03:39 +04:00
break ;
2007-12-02 17:49:09 +03:00
case IOAPIC_FIXED :
2008-02-25 11:28:31 +03:00
# ifdef CONFIG_X86
if ( irq = = 0 )
deliver_bitmask = 1 ;
# endif
2007-07-18 13:03:39 +04:00
for ( vcpu_id = 0 ; deliver_bitmask ! = 0 ; vcpu_id + + ) {
if ( ! ( deliver_bitmask & ( 1 < < vcpu_id ) ) )
continue ;
deliver_bitmask & = ~ ( 1 < < vcpu_id ) ;
vcpu = ioapic - > kvm - > vcpus [ vcpu_id ] ;
if ( vcpu ) {
2008-06-05 07:08:11 +04:00
r = ioapic_inj_irq ( ioapic , vcpu , vector ,
2007-07-18 13:03:39 +04:00
trig_mode , delivery_mode ) ;
}
}
break ;
/* TODO: NMI */
default :
printk ( KERN_WARNING " Unsupported delivery mode %d \n " ,
delivery_mode ) ;
break ;
}
2008-06-05 07:08:11 +04:00
return r ;
2007-07-18 13:03:39 +04:00
}
void kvm_ioapic_set_irq ( struct kvm_ioapic * ioapic , int irq , int level )
{
u32 old_irr = ioapic - > irr ;
u32 mask = 1 < < irq ;
union ioapic_redir_entry entry ;
if ( irq > = 0 & & irq < IOAPIC_NUM_PINS ) {
entry = ioapic - > redirtbl [ irq ] ;
level ^ = entry . fields . polarity ;
if ( ! level )
ioapic - > irr & = ~ mask ;
else {
ioapic - > irr | = mask ;
if ( ( ! entry . fields . trig_mode & & old_irr ! = ioapic - > irr )
| | ! entry . fields . remote_irr )
ioapic_service ( ioapic , irq ) ;
}
}
}
static int get_eoi_gsi ( struct kvm_ioapic * ioapic , int vector )
{
int i ;
for ( i = 0 ; i < IOAPIC_NUM_PINS ; i + + )
if ( ioapic - > redirtbl [ i ] . fields . vector = = vector )
return i ;
return - 1 ;
}
void kvm_ioapic_update_eoi ( struct kvm * kvm , int vector )
{
2007-12-14 05:17:34 +03:00
struct kvm_ioapic * ioapic = kvm - > arch . vioapic ;
2007-07-18 13:03:39 +04:00
union ioapic_redir_entry * ent ;
int gsi ;
gsi = get_eoi_gsi ( ioapic , vector ) ;
if ( gsi = = - 1 ) {
printk ( KERN_WARNING " Can't find redir item for %d EOI \n " ,
vector ) ;
return ;
}
ent = & ioapic - > redirtbl [ gsi ] ;
ASSERT ( ent - > fields . trig_mode = = IOAPIC_LEVEL_TRIG ) ;
ent - > fields . remote_irr = 0 ;
if ( ! ent - > fields . mask & & ( ioapic - > irr & ( 1 < < gsi ) ) )
ioapic_deliver ( ioapic , gsi ) ;
}
static int ioapic_in_range ( struct kvm_io_device * this , gpa_t addr )
{
struct kvm_ioapic * ioapic = ( struct kvm_ioapic * ) this - > private ;
return ( ( addr > = ioapic - > base_address & &
( addr < ioapic - > base_address + IOAPIC_MEM_LENGTH ) ) ) ;
}
static void ioapic_mmio_read ( struct kvm_io_device * this , gpa_t addr , int len ,
void * val )
{
struct kvm_ioapic * ioapic = ( struct kvm_ioapic * ) this - > private ;
u32 result ;
2007-10-12 13:01:59 +04:00
ioapic_debug ( " addr %lx \n " , ( unsigned long ) addr ) ;
2007-07-18 13:03:39 +04:00
ASSERT ( ! ( addr & 0xf ) ) ; /* check alignment */
addr & = 0xff ;
switch ( addr ) {
case IOAPIC_REG_SELECT :
result = ioapic - > ioregsel ;
break ;
case IOAPIC_REG_WINDOW :
result = ioapic_read_indirect ( ioapic , addr , len ) ;
break ;
default :
result = 0 ;
break ;
}
switch ( len ) {
case 8 :
* ( u64 * ) val = result ;
break ;
case 1 :
case 2 :
case 4 :
memcpy ( val , ( char * ) & result , len ) ;
break ;
default :
printk ( KERN_WARNING " ioapic: wrong length %d \n " , len ) ;
}
}
static void ioapic_mmio_write ( struct kvm_io_device * this , gpa_t addr , int len ,
const void * val )
{
struct kvm_ioapic * ioapic = ( struct kvm_ioapic * ) this - > private ;
u32 data ;
2007-10-12 13:01:59 +04:00
ioapic_debug ( " ioapic_mmio_write addr=%p len=%d val=%p \n " ,
( void * ) addr , len , val ) ;
2007-07-18 13:03:39 +04:00
ASSERT ( ! ( addr & 0xf ) ) ; /* check alignment */
if ( len = = 4 | | len = = 8 )
data = * ( u32 * ) val ;
else {
printk ( KERN_WARNING " ioapic: Unsupported size %d \n " , len ) ;
return ;
}
addr & = 0xff ;
switch ( addr ) {
case IOAPIC_REG_SELECT :
ioapic - > ioregsel = data ;
break ;
case IOAPIC_REG_WINDOW :
ioapic_write_indirect ( ioapic , data ) ;
break ;
2007-12-02 17:53:07 +03:00
# ifdef CONFIG_IA64
case IOAPIC_REG_EOI :
2007-12-17 09:16:14 +03:00
kvm_ioapic_update_eoi ( ioapic - > kvm , data ) ;
2007-12-02 17:53:07 +03:00
break ;
# endif
2007-07-18 13:03:39 +04:00
default :
break ;
}
}
2007-10-10 14:15:54 +04:00
void kvm_ioapic_reset ( struct kvm_ioapic * ioapic )
{
int i ;
for ( i = 0 ; i < IOAPIC_NUM_PINS ; i + + )
ioapic - > redirtbl [ i ] . fields . mask = 1 ;
ioapic - > base_address = IOAPIC_DEFAULT_BASE_ADDRESS ;
ioapic - > ioregsel = 0 ;
ioapic - > irr = 0 ;
ioapic - > id = 0 ;
}
2007-07-18 13:03:39 +04:00
int kvm_ioapic_init ( struct kvm * kvm )
{
struct kvm_ioapic * ioapic ;
ioapic = kzalloc ( sizeof ( struct kvm_ioapic ) , GFP_KERNEL ) ;
if ( ! ioapic )
return - ENOMEM ;
2007-12-14 05:17:34 +03:00
kvm - > arch . vioapic = ioapic ;
2007-10-10 14:15:54 +04:00
kvm_ioapic_reset ( ioapic ) ;
2007-07-18 13:03:39 +04:00
ioapic - > dev . read = ioapic_mmio_read ;
ioapic - > dev . write = ioapic_mmio_write ;
ioapic - > dev . in_range = ioapic_in_range ;
ioapic - > dev . private = ioapic ;
ioapic - > kvm = kvm ;
kvm_io_bus_register_dev ( & kvm - > mmio_bus , & ioapic - > dev ) ;
return 0 ;
}