| # |
| # Local APIC acceleration for Windows XP and related guests |
| # |
| # Copyright 2011 Red Hat, Inc. and/or its affiliates |
| # |
| # Author: Avi Kivity <avi@redhat.com> |
| # |
| # This work is licensed under the terms of the GNU GPL, version 2, or (at your |
| # option) any later version. See the COPYING file in the top-level directory. |
| # |
| |
| #include "optionrom.h" |
| |
| OPTION_ROM_START |
| |
| # clear vapic area: firmware load using rep insb may cause |
| # stale tpr/isr/irr data to corrupt the vapic area. |
| push %es |
| push %cs |
| pop %es |
| xor %ax, %ax |
| mov $vapic_size/2, %cx |
| lea vapic, %di |
| cld |
| rep stosw |
| pop %es |
| |
| # announce presence to the hypervisor |
| mov $vapic_base, %ax |
| out %ax, $0x7e |
| |
| lret |
| |
| .code32 |
| vapic_size = 2*4096 |
| |
| .macro fixup delta=-4 |
| 777: |
| .text 1 |
| .long 777b + \delta - vapic_base |
| .text 0 |
| .endm |
| |
| .macro reenable_vtpr |
| out %al, $0x7e |
| .endm |
| |
| .text 1 |
| fixup_start = . |
| .text 0 |
| |
| .align 16 |
| |
| vapic_base: |
| .ascii "kvm aPiC" |
| |
| /* relocation data */ |
| .long vapic_base ; fixup |
| .long fixup_start ; fixup |
| .long fixup_end ; fixup |
| |
| .long vapic ; fixup |
| .long vapic_size |
| vcpu_shift: |
| .long 0 |
| real_tpr: |
| .long 0 |
| .long up_set_tpr ; fixup |
| .long up_set_tpr_eax ; fixup |
| .long up_get_tpr_eax ; fixup |
| .long up_get_tpr_ecx ; fixup |
| .long up_get_tpr_edx ; fixup |
| .long up_get_tpr_ebx ; fixup |
| .long 0 /* esp. won't work. */ |
| .long up_get_tpr_ebp ; fixup |
| .long up_get_tpr_esi ; fixup |
| .long up_get_tpr_edi ; fixup |
| .long up_get_tpr_stack ; fixup |
| .long mp_set_tpr ; fixup |
| .long mp_set_tpr_eax ; fixup |
| .long mp_get_tpr_eax ; fixup |
| .long mp_get_tpr_ecx ; fixup |
| .long mp_get_tpr_edx ; fixup |
| .long mp_get_tpr_ebx ; fixup |
| .long 0 /* esp. won't work. */ |
| .long mp_get_tpr_ebp ; fixup |
| .long mp_get_tpr_esi ; fixup |
| .long mp_get_tpr_edi ; fixup |
| .long mp_get_tpr_stack ; fixup |
| |
| .macro kvm_hypercall |
| .byte 0x0f, 0x01, 0xc1 |
| .endm |
| |
| kvm_hypercall_vapic_poll_irq = 1 |
| |
| pcr_cpu = 0x51 |
| |
| .align 64 |
| |
| mp_get_tpr_eax: |
| pushf |
| cli |
| reenable_vtpr |
| push %ecx |
| |
| fs/movzbl pcr_cpu, %eax |
| |
| mov vcpu_shift, %ecx ; fixup |
| shl %cl, %eax |
| testb $1, vapic+4(%eax) ; fixup delta=-5 |
| jz mp_get_tpr_bad |
| movzbl vapic(%eax), %eax ; fixup |
| |
| mp_get_tpr_out: |
| pop %ecx |
| popf |
| ret |
| |
| mp_get_tpr_bad: |
| mov real_tpr, %eax ; fixup |
| mov (%eax), %eax |
| jmp mp_get_tpr_out |
| |
| mp_get_tpr_ebx: |
| mov %eax, %ebx |
| call mp_get_tpr_eax |
| xchg %eax, %ebx |
| ret |
| |
| mp_get_tpr_ecx: |
| mov %eax, %ecx |
| call mp_get_tpr_eax |
| xchg %eax, %ecx |
| ret |
| |
| mp_get_tpr_edx: |
| mov %eax, %edx |
| call mp_get_tpr_eax |
| xchg %eax, %edx |
| ret |
| |
| mp_get_tpr_esi: |
| mov %eax, %esi |
| call mp_get_tpr_eax |
| xchg %eax, %esi |
| ret |
| |
| mp_get_tpr_edi: |
| mov %eax, %edi |
| call mp_get_tpr_edi |
| xchg %eax, %edi |
| ret |
| |
| mp_get_tpr_ebp: |
| mov %eax, %ebp |
| call mp_get_tpr_eax |
| xchg %eax, %ebp |
| ret |
| |
| mp_get_tpr_stack: |
| call mp_get_tpr_eax |
| xchg %eax, 4(%esp) |
| ret |
| |
| mp_set_tpr_eax: |
| push %eax |
| call mp_set_tpr |
| ret |
| |
| mp_set_tpr: |
| pushf |
| push %eax |
| push %ecx |
| push %edx |
| push %ebx |
| cli |
| reenable_vtpr |
| |
| mp_set_tpr_failed: |
| fs/movzbl pcr_cpu, %edx |
| |
| mov vcpu_shift, %ecx ; fixup |
| shl %cl, %edx |
| |
| testb $1, vapic+4(%edx) ; fixup delta=-5 |
| jz mp_set_tpr_bad |
| |
| mov vapic(%edx), %eax ; fixup |
| |
| mov %eax, %ebx |
| mov 24(%esp), %bl |
| |
| /* %ebx = new vapic (%bl = tpr, %bh = isr, %b3 = irr) */ |
| |
| lock cmpxchg %ebx, vapic(%edx) ; fixup |
| jnz mp_set_tpr_failed |
| |
| /* compute ppr */ |
| cmp %bh, %bl |
| jae mp_tpr_is_bigger |
| mp_isr_is_bigger: |
| mov %bh, %bl |
| mp_tpr_is_bigger: |
| /* %bl = ppr */ |
| rol $8, %ebx |
| /* now: %bl = irr, %bh = ppr */ |
| cmp %bh, %bl |
| ja mp_set_tpr_poll_irq |
| |
| mp_set_tpr_out: |
| pop %ebx |
| pop %edx |
| pop %ecx |
| pop %eax |
| popf |
| ret $4 |
| |
| mp_set_tpr_poll_irq: |
| mov $kvm_hypercall_vapic_poll_irq, %eax |
| kvm_hypercall |
| jmp mp_set_tpr_out |
| |
| mp_set_tpr_bad: |
| mov 24(%esp), %ecx |
| mov real_tpr, %eax ; fixup |
| mov %ecx, (%eax) |
| jmp mp_set_tpr_out |
| |
| up_get_tpr_eax: |
| reenable_vtpr |
| movzbl vapic, %eax ; fixup |
| ret |
| |
| up_get_tpr_ebx: |
| reenable_vtpr |
| movzbl vapic, %ebx ; fixup |
| ret |
| |
| up_get_tpr_ecx: |
| reenable_vtpr |
| movzbl vapic, %ecx ; fixup |
| ret |
| |
| up_get_tpr_edx: |
| reenable_vtpr |
| movzbl vapic, %edx ; fixup |
| ret |
| |
| up_get_tpr_esi: |
| reenable_vtpr |
| movzbl vapic, %esi ; fixup |
| ret |
| |
| up_get_tpr_edi: |
| reenable_vtpr |
| movzbl vapic, %edi ; fixup |
| ret |
| |
| up_get_tpr_ebp: |
| reenable_vtpr |
| movzbl vapic, %ebp ; fixup |
| ret |
| |
| up_get_tpr_stack: |
| reenable_vtpr |
| movzbl vapic, %eax ; fixup |
| xchg %eax, 4(%esp) |
| ret |
| |
| up_set_tpr_eax: |
| push %eax |
| call up_set_tpr |
| ret |
| |
| up_set_tpr: |
| pushf |
| push %eax |
| push %ebx |
| reenable_vtpr |
| |
| up_set_tpr_failed: |
| mov vapic, %eax ; fixup |
| |
| mov %eax, %ebx |
| mov 16(%esp), %bl |
| |
| /* %ebx = new vapic (%bl = tpr, %bh = isr, %b3 = irr) */ |
| |
| lock cmpxchg %ebx, vapic ; fixup |
| jnz up_set_tpr_failed |
| |
| /* compute ppr */ |
| cmp %bh, %bl |
| jae up_tpr_is_bigger |
| up_isr_is_bigger: |
| mov %bh, %bl |
| up_tpr_is_bigger: |
| /* %bl = ppr */ |
| rol $8, %ebx |
| /* now: %bl = irr, %bh = ppr */ |
| cmp %bh, %bl |
| ja up_set_tpr_poll_irq |
| |
| up_set_tpr_out: |
| pop %ebx |
| pop %eax |
| popf |
| ret $4 |
| |
| up_set_tpr_poll_irq: |
| mov $kvm_hypercall_vapic_poll_irq, %eax |
| kvm_hypercall |
| jmp up_set_tpr_out |
| |
| .text 1 |
| fixup_end = . |
| .text 0 |
| |
| /* |
| * vapic format: |
| * per-vcpu records of size 2^vcpu shift. |
| * byte 0: tpr (r/w) |
| * byte 1: highest in-service interrupt (isr) (r/o); bits 3:0 are zero |
| * byte 2: zero (r/o) |
| * byte 3: highest pending interrupt (irr) (r/o) |
| */ |
| .text 2 |
| |
| .align 128 |
| |
| vapic: |
| . = . + vapic_size |
| |
| OPTION_ROM_END |