hvm: Support hardware task switching.

Signed-off-by: Keir Fraser <keir@xensource.com>
This commit is contained in:
kfraser@localhost.localdomain committed 2007-09-18 11:49:42 +01:00
1 parent 15b866d372
commit 623e837167
5 files changed
+474 -81

No files matched your search

+406
View File
@@ -672,6 +672,412 @@ int hvm_set_cr4(unsigned long value)
return 0;
}
int hvm_virtual_to_linear_addr(
enum x86_segment seg,
struct segment_register *reg,
unsigned long offset,
unsigned int bytes,
enum hvm_access_type access_type,
unsigned int addr_size,
unsigned long *linear_addr)
{
unsigned long addr = offset;
uint32_t last_byte;
if ( addr_size != 64 )
{
/*
* COMPATIBILITY MODE: Apply segment checks and add base.
*/
switch ( access_type )
{
case hvm_access_read:
if ( (reg->attr.fields.type & 0xa) == 0x8 )
goto gpf; /* execute-only code segment */
break;
case hvm_access_write:
if ( (reg->attr.fields.type & 0xa) != 0x2 )
goto gpf; /* not a writable data segment */
break;
default:
break;
}
last_byte = offset + bytes - 1;
/* Is this a grows-down data segment? Special limit check if so. */
if ( (reg->attr.fields.type & 0xc) == 0x4 )
{
/* Is upper limit 0xFFFF or 0xFFFFFFFF? */
if ( !reg->attr.fields.db )
last_byte = (uint16_t)last_byte;
/* Check first byte and last byte against respective bounds. */
if ( (offset <= reg->limit) || (last_byte < offset) )
goto gpf;
}
else if ( (last_byte > reg->limit) || (last_byte < offset) )
goto gpf; /* last byte is beyond limit or wraps 0xFFFFFFFF */
/*
* Hardware truncates to 32 bits in compatibility mode.
* It does not truncate to 16 bits in 16-bit address-size mode.
*/
addr = (uint32_t)(addr + reg->base);
}
else
{
/*
* LONG MODE: FS and GS add segment base. Addresses must be canonical.
*/
if ( (seg == x86_seg_fs) || (seg == x86_seg_gs) )
addr += reg->base;
if ( !is_canonical_address(addr) )
goto gpf;
}
*linear_addr = addr;
return 1;
gpf:
return 0;
}
static void *hvm_map(unsigned long va, int size)
{
unsigned long gfn, mfn;
p2m_type_t p2mt;
if ( ((va & ~PAGE_MASK) + size) > PAGE_SIZE )
{
hvm_inject_exception(TRAP_page_fault, PFEC_write_access,
(va + PAGE_SIZE - 1) & PAGE_MASK);
return NULL;
}
gfn = paging_gva_to_gfn(current, va);
mfn = mfn_x(gfn_to_mfn_current(gfn, &p2mt));
if ( !p2m_is_ram(p2mt) )
{
hvm_inject_exception(TRAP_page_fault, PFEC_write_access, va);
return NULL;
}
ASSERT(mfn_valid(mfn));
paging_mark_dirty(current->domain, mfn);
return (char *)map_domain_page(mfn) + (va & ~PAGE_MASK);
}
static void hvm_unmap(void *p)
{
if ( p )
unmap_domain_page(p);
}
static int hvm_load_segment_selector(
struct vcpu *v, enum x86_segment seg, uint16_t sel)
{
struct segment_register desctab, cs, segr;
struct desc_struct *pdesc, desc;
u8 dpl, rpl, cpl;
int fault_type = TRAP_invalid_tss;
/* NULL selector? */
if ( (sel & 0xfffc) == 0 )
{
if ( (seg == x86_seg_cs) || (seg == x86_seg_ss) )
goto fail;
memset(&segr, 0, sizeof(segr));
hvm_set_segment_register(v, seg, &segr);
return 0;
}
/* LDT descriptor must be in the GDT. */
if ( (seg == x86_seg_ldtr) && (sel & 4) )
goto fail;
hvm_get_segment_register(v, x86_seg_cs, &cs);
hvm_get_segment_register(
v, (sel & 4) ? x86_seg_ldtr : x86_seg_gdtr, &desctab);
/* Check against descriptor table limit. */
if ( ((sel & 0xfff8) + 7) > desctab.limit )
goto fail;
pdesc = hvm_map(desctab.base + (sel & 0xfff8), 8);
if ( pdesc == NULL )
goto hvm_map_fail;
do {
desc = *pdesc;
/* Segment present in memory? */
if ( !(desc.b & (1u<<15)) )
{
fault_type = TRAP_no_segment;
goto unmap_and_fail;
}
/* LDT descriptor is a system segment. All others are code/data. */
if ( (desc.b & (1u<<12)) == ((seg == x86_seg_ldtr) << 12) )
goto unmap_and_fail;
dpl = (desc.b >> 13) & 3;
rpl = sel & 3;
cpl = cs.sel & 3;
switch ( seg )
{
case x86_seg_cs:
/* Code segment? */
if ( !(desc.b & (1u<<11)) )
goto unmap_and_fail;
/* Non-conforming segment: check DPL against RPL. */
if ( ((desc.b & (6u<<9)) != 6) && (dpl != rpl) )
goto unmap_and_fail;
break;
case x86_seg_ss:
/* Writable data segment? */
if ( (desc.b & (5u<<9)) != (1u<<9) )
goto unmap_and_fail;
if ( (dpl != cpl) || (dpl != rpl) )
goto unmap_and_fail;
break;
case x86_seg_ldtr:
/* LDT system segment? */
if ( (desc.b & (15u<<8)) != (2u<<8) )
goto unmap_and_fail;
goto skip_accessed_flag;
default:
/* Readable code or data segment? */
if ( (desc.b & (5u<<9)) == (4u<<9) )
goto unmap_and_fail;
/* Non-conforming segment: check DPL against RPL and CPL. */
if ( ((desc.b & (6u<<9)) != 6) && ((dpl < cpl) || (dpl < rpl)) )
goto unmap_and_fail;
break;
}
} while ( !(desc.b & 0x100) && /* Ensure Accessed flag is set */
(cmpxchg(&pdesc->b, desc.b, desc.b | 0x100) != desc.b) );
/* Force the Accessed flag in our local copy. */
desc.b |= 0x100;
skip_accessed_flag:
hvm_unmap(pdesc);
segr.base = (((desc.b << 0) & 0xff000000u) |
((desc.b << 16) & 0x00ff0000u) |
((desc.a >> 16) & 0x0000ffffu));
segr.attr.bytes = (((desc.b >> 8) & 0x00ffu) |
((desc.b >> 12) & 0x0f00u));
segr.limit = (desc.b & 0x000f0000u) | (desc.a & 0x0000ffffu);
if ( segr.attr.fields.g )
segr.limit = (segr.limit << 12) | 0xfffu;
segr.sel = sel;
hvm_set_segment_register(v, seg, &segr);
return 0;
unmap_and_fail:
hvm_unmap(pdesc);
fail:
hvm_inject_exception(fault_type, sel & 0xfffc, 0);
hvm_map_fail:
return 1;
}
void hvm_task_switch(
uint16_t tss_sel, enum hvm_task_switch_reason taskswitch_reason,
int32_t errcode)
{
struct vcpu *v = current;
struct cpu_user_regs *regs = guest_cpu_user_regs();
struct segment_register gdt, tr, prev_tr, segr;
struct desc_struct *optss_desc = NULL, *nptss_desc = NULL, tss_desc;
unsigned long eflags;
int exn_raised;
struct {
u16 back_link,__blh;
u32 esp0;
u16 ss0, _0;
u32 esp1;
u16 ss1, _1;
u32 esp2;
u16 ss2, _2;
u32 cr3, eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
u16 es, _3, cs, _4, ss, _5, ds, _6, fs, _7, gs, _8, ldt, _9;
u16 trace, iomap;
} *ptss, tss;
hvm_get_segment_register(v, x86_seg_gdtr, &gdt);
hvm_get_segment_register(v, x86_seg_tr, &prev_tr);
if ( ((tss_sel & 0xfff8) + 7) > gdt.limit )
{
hvm_inject_exception((taskswitch_reason == TSW_iret) ?
TRAP_invalid_tss : TRAP_gp_fault,
tss_sel & 0xfff8, 0);
goto out;
}
optss_desc = hvm_map(gdt.base + (prev_tr.sel & 0xfff8), 8);
if ( optss_desc == NULL )
goto out;
nptss_desc = hvm_map(gdt.base + (tss_sel & 0xfff8), 8);
if ( nptss_desc == NULL )
goto out;
tss_desc = *nptss_desc;
tr.sel = tss_sel;
tr.base = (((tss_desc.b << 0) & 0xff000000u) |
((tss_desc.b << 16) & 0x00ff0000u) |
((tss_desc.a >> 16) & 0x0000ffffu));
tr.limit = (tss_desc.b & 0x000f0000u) | (tss_desc.a & 0x0000ffffu);
tr.attr.bytes = (((tss_desc.b >> 8) & 0x00ffu) |
((tss_desc.b >> 20) & 0x0f00u));
if ( !tr.attr.fields.p )
{
hvm_inject_exception(TRAP_no_segment, tss_sel & 0xfff8, 0);
goto out;
}
if ( tr.attr.fields.type != ((taskswitch_reason == TSW_iret) ? 0xb : 0x9) )
{
hvm_inject_exception(
(taskswitch_reason == TSW_iret) ? TRAP_invalid_tss : TRAP_gp_fault,
tss_sel & 0xfff8, 0);
goto out;
}
if ( !tr.attr.fields.g && (tr.limit < (sizeof(tss)-1)) )
{
hvm_inject_exception(TRAP_invalid_tss, tss_sel & 0xfff8, 0);
goto out;
}
hvm_store_cpu_guest_regs(v, regs, NULL);
ptss = hvm_map(prev_tr.base, sizeof(tss));
if ( ptss == NULL )
goto out;
eflags = regs->eflags;
if ( taskswitch_reason == TSW_iret )
eflags &= ~X86_EFLAGS_NT;
ptss->cr3 = v->arch.hvm_vcpu.guest_cr[3];
ptss->eip = regs->eip;
ptss->eflags = eflags;
ptss->eax = regs->eax;
ptss->ecx = regs->ecx;
ptss->edx = regs->edx;
ptss->ebx = regs->ebx;
ptss->esp = regs->esp;
ptss->ebp = regs->ebp;
ptss->esi = regs->esi;
ptss->edi = regs->edi;
hvm_get_segment_register(v, x86_seg_es, &segr);
ptss->es = segr.sel;
hvm_get_segment_register(v, x86_seg_cs, &segr);
ptss->cs = segr.sel;
hvm_get_segment_register(v, x86_seg_ss, &segr);
ptss->ss = segr.sel;
hvm_get_segment_register(v, x86_seg_ds, &segr);
ptss->ds = segr.sel;
hvm_get_segment_register(v, x86_seg_fs, &segr);
ptss->fs = segr.sel;
hvm_get_segment_register(v, x86_seg_gs, &segr);
ptss->gs = segr.sel;
hvm_get_segment_register(v, x86_seg_ldtr, &segr);
ptss->ldt = segr.sel;
hvm_unmap(ptss);
ptss = hvm_map(tr.base, sizeof(tss));
if ( ptss == NULL )
goto out;
if ( !hvm_set_cr3(ptss->cr3) )
{
hvm_unmap(ptss);
goto out;
}
regs->eip = ptss->eip;
regs->eflags = ptss->eflags;
regs->eax = ptss->eax;
regs->ecx = ptss->ecx;
regs->edx = ptss->edx;
regs->ebx = ptss->ebx;
regs->esp = ptss->esp;
regs->ebp = ptss->ebp;
regs->esi = ptss->esi;
regs->edi = ptss->edi;
if ( (taskswitch_reason == TSW_call_or_int) )
{
regs->eflags |= X86_EFLAGS_NT;
ptss->back_link = prev_tr.sel;
}
exn_raised = 0;
if ( hvm_load_segment_selector(v, x86_seg_es, ptss->es) ||
hvm_load_segment_selector(v, x86_seg_cs, ptss->cs) ||
hvm_load_segment_selector(v, x86_seg_ss, ptss->ss) ||
hvm_load_segment_selector(v, x86_seg_ds, ptss->ds) ||
hvm_load_segment_selector(v, x86_seg_fs, ptss->fs) ||
hvm_load_segment_selector(v, x86_seg_gs, ptss->gs) ||
hvm_load_segment_selector(v, x86_seg_ldtr, ptss->ldt) )
exn_raised = 1;
if ( (ptss->trace & 1) && !exn_raised )
hvm_inject_exception(TRAP_debug, tss_sel & 0xfff8, 0);
hvm_unmap(ptss);
tr.attr.fields.type = 0xb; /* busy 32-bit tss */
hvm_set_segment_register(v, x86_seg_tr, &tr);
paging_update_cr3(v);
v->arch.hvm_vcpu.guest_cr[0] |= X86_CR0_TS;
hvm_update_guest_cr(v, 0);
if ( (taskswitch_reason == TSW_iret) ||
(taskswitch_reason == TSW_jmp) )
clear_bit(41, optss_desc); /* clear B flag of old task */
if ( taskswitch_reason != TSW_iret )
set_bit(41, nptss_desc); /* set B flag of new task */
if ( errcode >= 0 )
{
struct segment_register reg;
unsigned long linear_addr;
regs->esp -= 4;
hvm_get_segment_register(current, x86_seg_ss, &reg);
/* Todo: do not ignore access faults here. */
if ( hvm_virtual_to_linear_addr(x86_seg_ss, &reg, regs->esp,
4, hvm_access_write, 32,
&linear_addr) )
hvm_copy_to_guest_virt(linear_addr, &errcode, 4);
}
hvm_load_cpu_guest_regs(v, regs);
out:
hvm_unmap(optss_desc);
hvm_unmap(nptss_desc);
}
/*
* __hvm_copy():
* @buf = hypervisor buffer
+19 -5
View File
@@ -648,6 +648,8 @@ static void svm_get_segment_register(struct vcpu *v, enum x86_segment seg,
{
struct vmcb_struct *vmcb = v->arch.hvm_svm.vmcb;
ASSERT(v == current);
switch ( seg )
{
case x86_seg_cs:
@@ -694,10 +696,13 @@ static void svm_set_segment_register(struct vcpu *v, enum x86_segment seg,
{
struct vmcb_struct *vmcb = v->arch.hvm_svm.vmcb;
ASSERT(v == current);
switch ( seg )
{
case x86_seg_cs:
memcpy(&vmcb->cs, reg, sizeof(*reg));
guest_cpu_user_regs()->cs = reg->sel;
break;
case x86_seg_ds:
memcpy(&vmcb->ds, reg, sizeof(*reg));
@@ -717,6 +722,7 @@ static void svm_set_segment_register(struct vcpu *v, enum x86_segment seg,
break;
case x86_seg_ss:
memcpy(&vmcb->ss, reg, sizeof(*reg));
guest_cpu_user_regs()->ss = reg->sel;
break;
case x86_seg_tr:
svm_sync_vmcb(v);
@@ -2299,12 +2305,20 @@ asmlinkage void svm_vmexit_handler(struct cpu_user_regs *regs)
svm_vmexit_do_invd(v);
break;
case VMEXIT_GDTR_WRITE:
printk("WRITE to GDTR\n");
case VMEXIT_TASK_SWITCH: {
enum hvm_task_switch_reason reason;
int32_t errcode = -1;
if ( (vmcb->exitinfo2 >> 36) & 1 )
reason = TSW_iret;
else if ( (vmcb->exitinfo2 >> 38) & 1 )
reason = TSW_jmp;
else
reason = TSW_call_or_int;
if ( (vmcb->exitinfo2 >> 44) & 1 )
errcode = (uint32_t)vmcb->exitinfo2;
hvm_task_switch((uint16_t)vmcb->exitinfo1, reason, errcode);
break;
case VMEXIT_TASK_SWITCH:
goto exit_and_crash;
}
case VMEXIT_CPUID:
svm_vmexit_do_cpuid(vmcb, regs);
+23 -5
View File
@@ -885,7 +885,7 @@ static unsigned long vmx_get_segment_base(struct vcpu *v, enum x86_segment seg)
static void vmx_get_segment_register(struct vcpu *v, enum x86_segment seg,
struct segment_register *reg)
{
u16 attr = 0;
uint32_t attr = 0;
ASSERT(v == current);
@@ -960,13 +960,17 @@ static void vmx_get_segment_register(struct vcpu *v, enum x86_segment seg,
static void vmx_set_segment_register(struct vcpu *v, enum x86_segment seg,
struct segment_register *reg)
{
u16 attr;
uint32_t attr;
ASSERT(v == current);
attr = reg->attr.bytes;
attr = ((attr & 0xf00) << 4) | (attr & 0xff);
/* Not-present must mean unusable. */
if ( !reg->attr.fields.p )
attr |= (1u << 16);
switch ( seg )
{
case x86_seg_cs:
@@ -974,6 +978,7 @@ static void vmx_set_segment_register(struct vcpu *v, enum x86_segment seg,
__vmwrite(GUEST_CS_LIMIT, reg->limit);
__vmwrite(GUEST_CS_BASE, reg->base);
__vmwrite(GUEST_CS_AR_BYTES, attr);
guest_cpu_user_regs()->cs = reg->sel;
break;
case x86_seg_ds:
__vmwrite(GUEST_DS_SELECTOR, reg->sel);
@@ -1004,6 +1009,7 @@ static void vmx_set_segment_register(struct vcpu *v, enum x86_segment seg,
__vmwrite(GUEST_SS_LIMIT, reg->limit);
__vmwrite(GUEST_SS_BASE, reg->base);
__vmwrite(GUEST_SS_AR_BYTES, attr);
guest_cpu_user_regs()->ss = reg->sel;
break;
case x86_seg_tr:
__vmwrite(GUEST_TR_SELECTOR, reg->sel);
@@ -2668,7 +2674,8 @@ asmlinkage void vmx_vmexit_handler(struct cpu_user_regs *regs)
/* Event delivery caused this intercept? Queue for redelivery. */
idtv_info = __vmread(IDT_VECTORING_INFO);
if ( unlikely(idtv_info & INTR_INFO_VALID_MASK) )
if ( unlikely(idtv_info & INTR_INFO_VALID_MASK) &&
(exit_reason != EXIT_REASON_TASK_SWITCH) )
{
if ( hvm_event_needs_reinjection((idtv_info>>8)&7, idtv_info&0xff) )
{
@@ -2785,8 +2792,19 @@ asmlinkage void vmx_vmexit_handler(struct cpu_user_regs *regs)
__vmwrite(CPU_BASED_VM_EXEC_CONTROL,
v->arch.hvm_vmx.exec_control);
break;
case EXIT_REASON_TASK_SWITCH:
goto exit_and_crash;
case EXIT_REASON_TASK_SWITCH: {
const enum hvm_task_switch_reason reasons[] = {
TSW_call_or_int, TSW_iret, TSW_jmp, TSW_call_or_int };
int32_t errcode = -1;
exit_qualification = __vmread(EXIT_QUALIFICATION);
if ( (idtv_info & INTR_INFO_VALID_MASK) &&
(idtv_info & INTR_INFO_DELIVER_CODE_MASK) )
errcode = __vmread(IDT_VECTORING_ERROR_CODE);
hvm_task_switch((uint16_t)exit_qualification,
reasons[(exit_qualification >> 30) & 3],
errcode);
break;
}
case EXIT_REASON_CPUID:
inst_len = __get_instruction_length(); /* Safe: CPUID */
__update_guest_eip(inst_len);
+9 -71
View File
@@ -101,7 +101,7 @@ int _shadow_mode_refcounts(struct domain *d)
/* x86 emulator support for the shadow code
*/
struct segment_register *hvm_get_seg_reg(
static struct segment_register *hvm_get_seg_reg(
enum x86_segment seg, struct sh_emulate_ctxt *sh_ctxt)
{
struct segment_register *seg_reg = &sh_ctxt->seg_reg[seg];
@@ -110,10 +110,6 @@ struct segment_register *hvm_get_seg_reg(
return seg_reg;
}
enum hvm_access_type {
hvm_access_insn_fetch, hvm_access_read, hvm_access_write
};
static int hvm_translate_linear_addr(
enum x86_segment seg,
unsigned long offset,
@@ -123,76 +119,18 @@ static int hvm_translate_linear_addr(
unsigned long *paddr)
{
struct segment_register *reg = hvm_get_seg_reg(seg, sh_ctxt);
unsigned long limit, addr = offset;
uint32_t last_byte;
int okay;
if ( sh_ctxt->ctxt.addr_size != 64 )
okay = hvm_virtual_to_linear_addr(
seg, reg, offset, bytes, access_type, sh_ctxt->ctxt.addr_size, paddr);
if ( !okay )
{
/*
* COMPATIBILITY MODE: Apply segment checks and add base.
*/
switch ( access_type )
{
case hvm_access_read:
if ( (reg->attr.fields.type & 0xa) == 0x8 )
goto gpf; /* execute-only code segment */
break;
case hvm_access_write:
if ( (reg->attr.fields.type & 0xa) != 0x2 )
goto gpf; /* not a writable data segment */
break;
default:
break;
}
/* Calculate the segment limit, including granularity flag. */
limit = reg->limit;
if ( reg->attr.fields.g )
limit = (limit << 12) | 0xfff;
last_byte = offset + bytes - 1;
/* Is this a grows-down data segment? Special limit check if so. */
if ( (reg->attr.fields.type & 0xc) == 0x4 )
{
/* Is upper limit 0xFFFF or 0xFFFFFFFF? */
if ( !reg->attr.fields.db )
last_byte = (uint16_t)last_byte;
/* Check first byte and last byte against respective bounds. */
if ( (offset <= limit) || (last_byte < offset) )
goto gpf;
}
else if ( (last_byte > limit) || (last_byte < offset) )
goto gpf; /* last byte is beyond limit or wraps 0xFFFFFFFF */
/*
* Hardware truncates to 32 bits in compatibility mode.
* It does not truncate to 16 bits in 16-bit address-size mode.
*/
addr = (uint32_t)(addr + reg->base);
}
else
{
/*
* LONG MODE: FS and GS add segment base. Addresses must be canonical.
*/
if ( (seg == x86_seg_fs) || (seg == x86_seg_gs) )
addr += reg->base;
if ( !is_canonical_address(addr) )
goto gpf;
hvm_inject_exception(TRAP_gp_fault, 0, 0);
return X86EMUL_EXCEPTION;
}
*paddr = addr;
return 0;
gpf:
/* Inject #GP(0). */
hvm_inject_exception(TRAP_gp_fault, 0, 0);
return X86EMUL_EXCEPTION;
return 0;
}
static int
+17
View File
@@ -362,4 +362,21 @@ static inline void hvm_cpu_down(void)
hvm_funcs.cpu_down();
}
enum hvm_task_switch_reason { TSW_jmp, TSW_iret, TSW_call_or_int };
void hvm_task_switch(
uint16_t tss_sel, enum hvm_task_switch_reason taskswitch_reason,
int32_t errcode);
enum hvm_access_type {
hvm_access_insn_fetch, hvm_access_read, hvm_access_write
};
int hvm_virtual_to_linear_addr(
enum x86_segment seg,
struct segment_register *reg,
unsigned long offset,
unsigned int bytes,
enum hvm_access_type access_type,
unsigned int addr_size,
unsigned long *linear_addr);
#endif /* __ASM_X86_HVM_HVM_H__ */
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.