|  | # | 
|  | # 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 |