/*
* pv/callback.c
*
* hypercall handles and helper functions for guest callback
*
* This program is free software; you can redistribute it and/or
* modify it under the terms and conditions 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, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* Override macros from asm/page.h to make them work with mfn_t */
#undef mfn_to_page
#define mfn_to_page(mfn) __mfn_to_page(mfn_x(mfn))
#undef page_to_mfn
#define page_to_mfn(pg) _mfn(__page_to_mfn(pg))
static int register_guest_nmi_callback(unsigned long address)
{
struct vcpu *curr = current;
struct domain *d = curr->domain;
struct trap_info *t = &curr->arch.pv_vcpu.trap_ctxt[TRAP_nmi];
if ( !is_canonical_address(address) )
return -EINVAL;
t->vector = TRAP_nmi;
t->flags = 0;
t->cs = (is_pv_32bit_domain(d) ?
FLAT_COMPAT_KERNEL_CS : FLAT_KERNEL_CS);
t->address = address;
TI_SET_IF(t, 1);
/*
* If no handler was registered we can 'lose the NMI edge'. Re-assert it
* now.
*/
if ( curr->vcpu_id == 0 && arch_get_nmi_reason(d) != 0 )
curr->nmi_pending = 1;
return 0;
}
static void unregister_guest_nmi_callback(void)
{
struct vcpu *curr = current;
struct trap_info *t = &curr->arch.pv_vcpu.trap_ctxt[TRAP_nmi];
memset(t, 0, sizeof(*t));
}
static long register_guest_callback(struct callback_register *reg)
{
long ret = 0;
struct vcpu *curr = current;
if ( !is_canonical_address(reg->address) )
return -EINVAL;
switch ( reg->type )
{
case CALLBACKTYPE_event:
curr->arch.pv_vcpu.event_callback_eip = reg->address;
break;
case CALLBACKTYPE_failsafe:
curr->arch.pv_vcpu.failsafe_callback_eip = reg->address;
if ( reg->flags & CALLBACKF_mask_events )
set_bit(_VGCF_failsafe_disables_events,
&curr->arch.vgc_flags);
else
clear_bit(_VGCF_failsafe_disables_events,
&curr->arch.vgc_flags);
break;
case CALLBACKTYPE_syscall:
curr->arch.pv_vcpu.syscall_callback_eip = reg->address;
if ( reg->flags & CALLBACKF_mask_events )
set_bit(_VGCF_syscall_disables_events,
&curr->arch.vgc_flags);
else
clear_bit(_VGCF_syscall_disables_events,
&curr->arch.vgc_flags);
break;
case CALLBACKTYPE_syscall32:
curr->arch.pv_vcpu.syscall32_callback_eip = reg->address;
curr->arch.pv_vcpu.syscall32_disables_events =
!!(reg->flags & CALLBACKF_mask_events);
break;
case CALLBACKTYPE_sysenter:
curr->arch.pv_vcpu.sysenter_callback_eip = reg->address;
curr->arch.pv_vcpu.sysenter_disables_events =
!!(reg->flags & CALLBACKF_mask_events);
break;
case CALLBACKTYPE_nmi:
ret = register_guest_nmi_callback(reg->address);
break;
default:
ret = -ENOSYS;
break;
}
return ret;
}
static long unregister_guest_callback(struct callback_unregister *unreg)
{
long ret;
switch ( unreg->type )
{
case CALLBACKTYPE_event:
case CALLBACKTYPE_failsafe:
case CALLBACKTYPE_syscall:
case CALLBACKTYPE_syscall32:
case CALLBACKTYPE_sysenter:
ret = -EINVAL;
break;
case CALLBACKTYPE_nmi:
unregister_guest_nmi_callback();
ret = 0;
break;
default:
ret = -ENOSYS;
break;
}
return ret;
}
long do_callback_op(int cmd, XEN_GUEST_HANDLE_PARAM(const_void) arg)
{
long ret;
switch ( cmd )
{
case CALLBACKOP_register:
{
struct callback_register reg;
ret = -EFAULT;
if ( copy_from_guest(®, arg, 1) )
break;
ret = register_guest_callback(®);
}
break;
case CALLBACKOP_unregister:
{
struct callback_unregister unreg;
ret = -EFAULT;
if ( copy_from_guest(&unreg, arg, 1) )
break;
ret = unregister_guest_callback(&unreg);
}
break;
default:
ret = -ENOSYS;
break;
}
return ret;
}
long do_set_callbacks(unsigned long event_address,
unsigned long failsafe_address,
unsigned long syscall_address)
{
struct callback_register event = {
.type = CALLBACKTYPE_event,
.address = event_address,
};
struct callback_register failsafe = {
.type = CALLBACKTYPE_failsafe,
.address = failsafe_address,
};
struct callback_register syscall = {
.type = CALLBACKTYPE_syscall,
.address = syscall_address,
};
register_guest_callback(&event);
register_guest_callback(&failsafe);
register_guest_callback(&syscall);
return 0;
}
static long compat_register_guest_callback(struct compat_callback_register *reg)
{
long ret = 0;
struct vcpu *curr = current;
fixup_guest_code_selector(curr->domain, reg->address.cs);
switch ( reg->type )
{
case CALLBACKTYPE_event:
curr->arch.pv_vcpu.event_callback_cs = reg->address.cs;
curr->arch.pv_vcpu.event_callback_eip = reg->address.eip;
break;
case CALLBACKTYPE_failsafe:
curr->arch.pv_vcpu.failsafe_callback_cs = reg->address.cs;
curr->arch.pv_vcpu.failsafe_callback_eip = reg->address.eip;
if ( reg->flags & CALLBACKF_mask_events )
set_bit(_VGCF_failsafe_disables_events,
&curr->arch.vgc_flags);
else
clear_bit(_VGCF_failsafe_disables_events,
&curr->arch.vgc_flags);
break;
case CALLBACKTYPE_syscall32:
curr->arch.pv_vcpu.syscall32_callback_cs = reg->address.cs;
curr->arch.pv_vcpu.syscall32_callback_eip = reg->address.eip;
curr->arch.pv_vcpu.syscall32_disables_events =
(reg->flags & CALLBACKF_mask_events) != 0;
break;
case CALLBACKTYPE_sysenter:
curr->arch.pv_vcpu.sysenter_callback_cs = reg->address.cs;
curr->arch.pv_vcpu.sysenter_callback_eip = reg->address.eip;
curr->arch.pv_vcpu.sysenter_disables_events =
(reg->flags & CALLBACKF_mask_events) != 0;
break;
case CALLBACKTYPE_nmi:
ret = register_guest_nmi_callback(reg->address.eip);
break;
default:
ret = -ENOSYS;
break;
}
return ret;
}
static long compat_unregister_guest_callback(
struct compat_callback_unregister *unreg)
{
long ret;
switch ( unreg->type )
{
case CALLBACKTYPE_event:
case CALLBACKTYPE_failsafe:
case CALLBACKTYPE_syscall32:
case CALLBACKTYPE_sysenter:
ret = -EINVAL;
break;
case CALLBACKTYPE_nmi:
unregister_guest_nmi_callback();
ret = 0;
break;
default:
ret = -ENOSYS;
break;
}
return ret;
}
long compat_callback_op(int cmd, XEN_GUEST_HANDLE(void) arg)
{
long ret;
switch ( cmd )
{
case CALLBACKOP_register:
{
struct compat_callback_register reg;
ret = -EFAULT;
if ( copy_from_guest(®, arg, 1) )
break;
ret = compat_register_guest_callback(®);
}
break;
case CALLBACKOP_unregister:
{
struct compat_callback_unregister unreg;
ret = -EFAULT;
if ( copy_from_guest(&unreg, arg, 1) )
break;
ret = compat_unregister_guest_callback(&unreg);
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
long compat_set_callbacks(unsigned long event_selector,
unsigned long event_address,
unsigned long failsafe_selector,
unsigned long failsafe_address)
{
struct compat_callback_register event = {
.type = CALLBACKTYPE_event,
.address = {
.cs = event_selector,
.eip = event_address
}
};
struct compat_callback_register failsafe = {
.type = CALLBACKTYPE_failsafe,
.address = {
.cs = failsafe_selector,
.eip = failsafe_address
}
};
compat_register_guest_callback(&event);
compat_register_guest_callback(&failsafe);
return 0;
}
long do_set_trap_table(XEN_GUEST_HANDLE_PARAM(const_trap_info_t) traps)
{
struct trap_info cur;
struct vcpu *curr = current;
struct trap_info *dst = curr->arch.pv_vcpu.trap_ctxt;
long rc = 0;
/* If no table is presented then clear the entire virtual IDT. */
if ( guest_handle_is_null(traps) )
{
memset(dst, 0, NR_VECTORS * sizeof(*dst));
init_int80_direct_trap(curr);
return 0;
}
for ( ; ; )
{
if ( copy_from_guest(&cur, traps, 1) )
{
rc = -EFAULT;
break;
}
if ( cur.address == 0 )
break;
if ( !is_canonical_address(cur.address) )
return -EINVAL;
fixup_guest_code_selector(curr->domain, cur.cs);
memcpy(&dst[cur.vector], &cur, sizeof(cur));
if ( cur.vector == 0x80 )
init_int80_direct_trap(curr);
guest_handle_add_offset(traps, 1);
if ( hypercall_preempt_check() )
{
rc = hypercall_create_continuation(
__HYPERVISOR_set_trap_table, "h", traps);
break;
}
}
return rc;
}
int compat_set_trap_table(XEN_GUEST_HANDLE(trap_info_compat_t) traps)
{
struct vcpu *curr = current;
struct compat_trap_info cur;
struct trap_info *dst = curr->arch.pv_vcpu.trap_ctxt;
long rc = 0;
/* If no table is presented then clear the entire virtual IDT. */
if ( guest_handle_is_null(traps) )
{
memset(dst, 0, NR_VECTORS * sizeof(*dst));
init_int80_direct_trap(curr);
return 0;
}
for ( ; ; )
{
if ( copy_from_guest(&cur, traps, 1) )
{
rc = -EFAULT;
break;
}
if ( cur.address == 0 )
break;
fixup_guest_code_selector(curr->domain, cur.cs);
XLAT_trap_info(dst + cur.vector, &cur);
if ( cur.vector == 0x80 )
init_int80_direct_trap(curr);
guest_handle_add_offset(traps, 1);
if ( hypercall_preempt_check() )
{
rc = hypercall_create_continuation(
__HYPERVISOR_set_trap_table, "h", traps);
break;
}
}
return rc;
}
long do_nmi_op(unsigned int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
{
struct xennmi_callback cb;
long rc = 0;
switch ( cmd )
{
case XENNMI_register_callback:
rc = -EFAULT;
if ( copy_from_guest(&cb, arg, 1) )
break;
rc = register_guest_nmi_callback(cb.handler_address);
break;
case XENNMI_unregister_callback:
unregister_guest_nmi_callback();
rc = 0;
break;
default:
rc = -ENOSYS;
break;
}
return rc;
}
int compat_nmi_op(unsigned int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
{
struct compat_nmi_callback cb;
int rc = 0;
switch ( cmd )
{
case XENNMI_register_callback:
rc = -EFAULT;
if ( copy_from_guest(&cb, arg, 1) )
break;
rc = register_guest_nmi_callback(cb.handler_address);
break;
case XENNMI_unregister_callback:
unregister_guest_nmi_callback();
rc = 0;
break;
default:
rc = -ENOSYS;
break;
}
return rc;
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/