This tests how the master i8259 handles a falling IRQ2 cascade
interrupt when the original interrupt is masked off in the slave's
IMR register.  It should cancel the interrupt, and not deliver
anything to the CPU until it is unmasked again.

Signed-off-by: Matthew Ogilvie <mmogilvi_q...@miniinfo.net>
---

On Mon, Sep 03, 2012 at 09:08:58AM +0200, Paolo Bonzini wrote:
> You can write a test for kvm-unit-tests.  These tests can be written in
> C, including interrupt handlers (see lib/x86/isr.h).  The git tree is at
> git://git.kernel.org/pub/scm/virt/kvm/kvm-unit-tests.git.

I attempted to rewrite this in C, but ran into snags with
figuring out how to bypass APIC stuff that the unit test framework
is setting up (I don't know anything about APIC), the VM would
reboot rather than service IRQ14/IRQ15 correctly, etc.  So I
just adapted my assembly language boot sector test instead.
I could send the non-functional C code, if anyone wants it.

I also considered adding it to x86/realmode.c, but there is
relatively little sharable real mode infrastructure between them.
(Setting up real mode interrupt shims to tiny C functions where the
shim is likely bigger than the converted C, etc...)

Unlike most kvm-unit-test programs, this one duplicates the output on the
VGA, so in theory it can actually be usefully run on real hardware
without the qemu test devices.  Unfortunately, grub gives an
error #7 "Loading below 1MB is not supported".  The original
bootsector version from the following URL works better (use grub's
"chainloader /filename/"), although it seems to hang as soon as
it accesses the hard disk on my newest machine (issues with
SATA drive, maybe?).
http://home.comcast.net/~mmogilvi/downloads/i8259-imr-test-2012-09-02.tar.bz2


 config-x86-common.mak |  10 +-
 x86/test8259.S        | 587 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 596 insertions(+), 1 deletion(-)
 create mode 100644 x86/test8259.S

diff --git a/config-x86-common.mak b/config-x86-common.mak
index c76cd11..1a57079 100644
--- a/config-x86-common.mak
+++ b/config-x86-common.mak
@@ -34,7 +34,8 @@ tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \
                $(TEST_DIR)/realmode.flat $(TEST_DIR)/msr.flat \
                $(TEST_DIR)/hypercall.flat $(TEST_DIR)/sieve.flat \
                $(TEST_DIR)/kvmclock_test.flat  $(TEST_DIR)/eventinj.flat \
-               $(TEST_DIR)/s3.flat $(TEST_DIR)/pmu.flat 
$(TEST_DIR)/asyncpf.flat
+               $(TEST_DIR)/s3.flat $(TEST_DIR)/pmu.flat \
+               $(TEST_DIR)/asyncpf.flat $(TEST_DIR)/test8259.flat
 
 ifdef API
 tests-common += api/api-sample
@@ -92,6 +93,13 @@ $(TEST_DIR)/pmu.elf: $(cstart.o) $(TEST_DIR)/pmu.o
 
 $(TEST_DIR)/asyncpf.elf: $(cstart.o) $(TEST_DIR)/asyncpf.o
 
+$(TEST_DIR)/test8259.elf: $(TEST_DIR)/test8259.o
+       $(CC) -m32 -nostdlib -o $@ -Wl,-T,$(TEST_DIR)/realmode.lds $^
+
+$(TEST_DIR)/test8259.o: bits = 32
+
+$(TEST_DIR)/test8259.o: CFLAGS += -m32
+
 $(TEST_DIR)/pcid.elf: $(cstart.o) $(TEST_DIR)/pcid.o
 
 arch_clean:
diff --git a/x86/test8259.S b/x86/test8259.S
new file mode 100644
index 0000000..93e09c6
--- /dev/null
+++ b/x86/test8259.S
@@ -0,0 +1,593 @@
+# Copyright 2012 Matthew Ogilvie
+# Released under LGPLv2.
+#
+# This program demonstrates that edge triggered interrupts are
+# canceled on the trailing edge.  Especially with respect to
+# the cascade IRQ2 and the slave's IMR register.
+#
+# Strategy: Tested by masking out IRQ14 in the slave's IMR register, and
+# noticing that you don't get an interrupt until it is unmasked.
+# Search for "INTERESTING_PART" below for the heart of the test.
+#
+# This outputs both to the VGA (useful for running on a real
+# machine) and the same test devices as the rest of kvm-unit-tests.
+#
+# Assumes that BIOS and the bootloader left interrupts, the VGA, etc
+# all in the standard real mode/DOS configurations, as specified in
+# the multiboot specification:
+# 
http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Machine-state
+#
+# Adapted from plain boot-sector version:
+# http://home.comcast.net/~mmogilvi/downloads/i8259-imr-test-2012-09-02.tar.bz2
+
+        .section .init
+        .code32
+        mb_magic = 0x1BADB002
+        mb_flags = 0x0
+
+        # multiboot header
+        .long mb_magic, mb_flags, 0 - (mb_magic + mb_flags)
+
+        .globl start
+        .data
+        . = . + 4096
+
+        .section .text
+start:
+        # Multiboot tries to be helpful with protected mode, but it's
+        # just in the way of a simple test like this.
+        lgdt r_gdt_descr
+        ljmp $8, $backToRealMode
+backToRealMode:
+        .code16
+        mov $16, %eax
+        mov %ax, %ds
+        mov %ax, %es
+        mov %ax, %fs
+        mov %ax, %gs
+        mov %ax, %ss
+        mov %cr0, %eax
+        btc $0, %eax
+        mov %eax, %cr0
+        ljmp $0, $realmode_entry
+
+#define VGA_SEGMENT 0xb800
+/*#define VGA_SEGMENT 0xb000*/ /* MDA */
+
+#define VGA_CRTC_BASE 0x3d4
+/*#define VGA_CRTC_BASE 0x3b4*/ /* MDA */
+
+######## print
+# ax: pointer to '\0'-terminated string to print.  This just copies
+#   bytes to the VGA frame buffer (and qemu-kvm test device, if present).
+#   No special control characters.
+        .text
+.globl print
+        .type print, @function
+print:
+        pushaw
+        movw %ax,%si
+        movw (cursorOffset),%di
+        cld
+        jmp .loopControl4
+.loop5:
+          # TO TEST DEVICE "serial":
+        movb (%si),%al
+        outb %al,$0xf1
+          # TO VGA:
+        movsb
+        movb $7,%al
+        stosb
+.loopControl4:
+        cmpb $0,(%si)
+        jne .loop5
+        movw %di,(cursorOffset)
+        popaw
+        ret
+        .size print, .-print
+
+######## printHex
+# ax: value to print
+        .text
+.globl printHex
+        .type print, @function
+printHex:
+        pushaw
+        movw %ax,%si
+        movw (cursorOffset),%di
+        movw $.hexChars,%bx
+        movw $4,%cx
+        cld
+
+.loop7:
+        movw %si,%ax
+        shlw $4,%si
+        shrw $12,%ax
+        xlatb
+          # TO TEST DEVICE "serial":
+        outb %al,$0xf1
+          # TO VGA:
+        stosb
+        movb $7,%al
+        stosb
+        sub $1,%cx
+        jne .loop7
+
+        movw %di,(cursorOffset)
+        popaw
+        ret
+        .size print, .-print
+
+#if 0 /* not currently needed */
+####### clearScreen
+        .text
+.globl clearScreen
+        .type clearScreen, @function
+clearScreen:
+        pushaw
+        xorw %ax,%ax
+        mov %ax,(cursorOffset)
+        movw %ax,%di
+        movw %ax,%cx
+        movb $0x10,%ch
+        cld
+        rep stosb
+        popaw
+        ret
+        .size clearScreen, .-clearScreen
+#endif /* not currently needed */
+
+######## initFromCursor
+        .text
+.globl initFromCursor
+        .type initFromCursor, @function
+initFromCursor:
+        pushaw
+
+          # High byte:
+        movw $VGA_CRTC_BASE,%dx
+        movb $0xe,%al
+        outb %al,%dx
+        incw %dx
+        inb %dx,%al
+          # Low byte:
+        decw %dx
+        movb %al,%bh
+        movb $0xf,%al
+        outb %al,%dx
+        incw %dx
+        inb %dx,%al
+          # times two (char and attr) and save:
+        movb %al,%bl
+        shlw $1,%bx
+        movw %bx,cursorOffset
+
+        popaw
+        ret
+        .size initFromCursor, .-initFromCursor
+
+######## delayChunk
+        .text
+.globl delayChunk
+        .type delayChunk, @function
+delayChunk:
+        pushaw
+
+        movw $0x400,%cx
+.loop2:
+        pushaw
+        popaw
+        subw $1,%cx
+        jne .loop2
+
+        popaw
+        ret
+        .size delayChunk, .-delayChunk
+
+######## calibrateDelay
+        .text
+.globl calibrateDelay
+        .type calibrateDelay, @function
+calibrateDelay:
+        pushaw
+
+           # Try to let TCG compile stuff first:
+        call delayChunk
+
+           # Set BIOS tick count:
+        xorl %esi,%esi   # also used as loop counter below
+        movw %si,(0x46c) # BIOS timer_low direct access
+
+           # Count chunks until about 1 sec has passed based on BIOS tick count
+.loop1:
+        call delayChunk
+        incl %esi
+        cmpw $18,(0x46c) # BIOS timer_low direct access
+        jb .loop1
+
+          # Divide by 16 to chunks per 16th:
+        shrl $4,%esi
+
+        incl %esi   # ensure not 0
+        movl %esi,(chunksPer16th)
+
+        popaw
+        ret
+        .size calibrateDelay, .-calibrateDelay
+
+######## delay16ths
+# ax: How many 16ths of a second to wait
+#     (This is NOT high precision, but it should be within several percent.)
+        .text
+.globl delay16ths
+        .type delay16ths, @function
+delay16ths:
+        pushaw
+
+        movzwl %ax,%eax
+        imull (chunksPer16th),%eax
+
+        jmp .loopControl3
+.continueLoop:
+        call delayChunk
+        dec %eax
+.loopControl3:
+        cmpl $0,%eax
+        jne .continueLoop
+
+        popaw
+        ret
+        .size delay16ths, .-delay16ths
+
+######## scheduleIrq14
+        .text
+.globl scheduleIrq14
+        .type scheduleIrq14, @function
+scheduleIrq14:
+        pushaw
+
+          # Strategy:
+          #  - Request a hard drive verify.  Adapted from C write-to-disk
+          #    example in "The Indispensable PC Hardware Book" by
+          #    Hans-Peter Messmer, Copyright 1994 Addison-Wesley
+          #    Publishers, Ltd, page 580.  (See page 811 for read specs.)
+          #  - Get the interrupt, but don't do anything with it.
+
+          # Wait until not busy:
+        movw $0x1f7,%dx
+.L1status:
+        inb %dx,%al
+        test $0x80,%al
+        jnz .L1status
+
+          # Setup AT task file:
+        movw $0x1f2,%dx
+        movb $1,%al   # 1 sector
+        outb %al,%dx
+
+        inc %dx  # $0x1f3
+        movb $7,%al   # sector number 7
+        outb %al,%dx
+
+        inc %dx  # $0x1f4
+        movb $167,%al  # cylinder LSB: 167
+        outb %al,%dx
+
+        inc %dx  # $0x1f5
+        movb $0,%al    # cylinder MSB: 0
+        outb %al,%dx
+
+        inc %dx  # $0x1f6
+        movb $0xa3,%al # requireBits(0xa0), drive(0x00)[vs 0x10], head(0x03)
+        outb %al,%dx
+
+          # Issue command:
+        inc %dx  # $0x1f7
+        movb $0x40,%al # cmd:verify, no retry
+        outb %al,%dx
+
+        popaw
+        ret
+        .size scheduleIrq14, .-scheduleIrq14
+
+######## initIrqHandlers
+        .text
+.globl initIrqHandlers
+        .type initIrqHandlers, @function
+initIrqHandlers:
+
+        movw $.irq14,(4*0x76)
+        movw $0,(4*0x76+2)
+
+        movw $.irq15,(4*0x77)
+        movw $0,(4*0x77+2)
+
+        ret
+
+.irq14:
+          # Don't bother actually doing anything with the drive controller;
+          # this program is testing the interrupt controller, not the drive.
+        push %ax
+        push %ds
+        push %es
+
+           # Fixup segment registers, just in case:
+        movw $VGA_SEGMENT,%ax
+        movw %ax,%es
+        xorw %ax,%ax
+        movw %ax,%ds
+
+        incl (irq14_count)
+
+        mov $.msgIrq14,%ax
+        call print
+
+        movb $0x20,%al  # non-specific acknowledge interrupt
+        outb %al,$0xa0;
+
+        pop %es
+        pop %ds
+        pop %ax
+        iret
+
+.irq15:
+          # This is only reached by spurious interrupts.
+        push %ax
+        push %ds
+        push %es
+
+           # Fixup segment registers, just in case:
+        movw $VGA_SEGMENT,%ax
+        movw %ax,%es
+        xorw %ax,%ax
+        movw %ax,%ds
+
+        incl (irq15_count)
+
+        mov $.msgIrq15,%ax
+        call print
+
+        movb $0x20,%al  # non-specific acknowledge interrupt
+        outb %al,$0xa0;
+
+        pop %es
+        pop %ds
+        pop %ax
+        iret
+
+        .size initIrqHandlers, .-initIrqHandlers
+
+
+######## strings
+        .section        .rodata.str1.1,"aMS",@progbits,1
+.msgInitDelay:
+        .string "dly="
+.msgElcr:
+        .string " elcr="
+.msgPassed:
+        .string " PASSED\n"
+.msgFailed:
+        .string " FAILED\n"
+.msgCmdVerify:
+        .string " cmdVerify"
+.msgMask:
+        .string " mask"
+.msgUnmask:
+        .string " unmask"
+.msgSti:
+        .string " sti"
+.msgIrq14:
+        .string " irq14"
+.msgIrq15:
+        .string " irq15"
+.hexChars:
+        .string "0123456789abcdef"
+r_gdt:
+        .long 0,0,0xffff,0x9b00,0xffff,0x9300
+r_gdt_descr:
+        .word r_gdt_descr-r_gdt
+        .long r_gdt
+
+######## realmode_entry
+        .text
+.globl realmode_entry
+        .type realmode_entry, @function
+realmode_entry:
+          # init segments, VGA, etc:
+        movw $VGA_SEGMENT,%ax
+        movw %ax,%es
+
+        xorw %ax,%ax
+        movw %ax,%ds
+        movw %ax,%fs
+        movw %ax,%gs
+        movw %ax,%ss
+
+        decw %ax
+        decw %ax
+        movw %ax,%sp
+        sti
+
+        #call clearScreen
+        call initFromCursor
+
+          # put something on the screen quickly (first part of elcr msg)
+        movw $.msgInitDelay,%ax
+        call print
+
+        call calibrateDelay
+
+          # Report delay calibration information:
+        movw (chunksPer16th+2),%ax
+        call printHex
+        movw (chunksPer16th),%ax
+        call printHex
+
+          # read and report elcr value:
+        movw $.msgElcr,%ax
+        call print
+
+        mov $0x4d1,%dx
+        inb %dx,%al
+        mov %al,%ah
+        decw %dx  # $0x4d0
+        inb %dx,%al
+        call printHex
+
+        cli
+
+          # save off IMM (to restore later)
+        inb $0x21,%al
+        movb %al,%bl
+        inb $0xa1,%al
+        movb %al,%bh
+
+####### INTERESTING_PART: starts here:
+
+          # i8259:imm: mask off everything except IRQ2
+        movb $0xfb,%al     # master (only IRQ2 clear)
+        outb %al,$0x21
+        movb $0xff,%al     # slave
+        outb %al,$0xa1
+
+        mov $.msgCmdVerify,%ax
+        call print
+        call initIrqHandlers
+        call scheduleIrq14
+
+        call .largeDelay   # important: IRQ14 raised while this is waiting
+
+        mov $.msgUnmask,%ax
+        call print
+        movb $0x3f,%al     # unmask IRQ14 and IRQ15
+        outb %al,$0xa1
+
+        call .medDelay   # (not important)
+
+        mov $.msgMask,%ax
+        call print
+        movb $0xff,%al     # mask IRQ14 and IRQ15 again
+        outb %al,$0xa1
+
+        call .medDelay   # (not important)
+
+        mov $.msgSti,%ax
+        call print
+        sti
+
+        call .medDelay   # important: spurious interrupt (IRQ15)
+
+        xorw %cx,%cx
+
+        cli
+
+            # Validate initial counts
+        cmpw $0,(irq14_count)
+        je good1
+        incw %cx
+good1:
+        cmpw $0,(irq15_count)
+        je good2
+        incw %cx
+good2:
+
+           # Unmask:
+        mov $.msgUnmask,%ax
+        call print
+        movb $0x3f,%al     # unmask IRQ14 and IRQ15
+        outb %al,$0xa1
+        sti
+
+        call .medDelay   # important: we should finally see IRQ14 here?
+
+            # Validate final counts
+        cmpw $1,(irq14_count)
+        je good3
+        incw %cx
+good3:
+        cmpw $0,(irq15_count)
+        je good4
+        incw %cx
+good4:
+
+          # Show message:
+        cmpw $0,%cx
+        je passed
+        movw $.msgFailed,%ax
+        jmp showPassFail
+passed:
+        movw $.msgPassed,%ax
+showPassFail:
+        call print
+
+        call .largeDelay   # let user see message before exiting
+
+          # done:
+        movzwl %cx,%eax
+        outl %eax,$0xf4  # TO TEST DEVICE "exit"
+          # ... if exit failed, restore IMR's and wait:
+        cli
+        movb %bl,%al
+        outb %al,$0x21
+        movb %bh,%al
+        outb %al,$0xa1
+        sti
+.Lhalt:
+        hlt
+        jmp .Lhalt
+
+.largeDelay:
+        push %ax
+        mov $40,%ax
+        call delay16ths
+        pop %ax
+        ret
+
+.medDelay:
+        push %ax
+        mov $4,%ax
+        call delay16ths
+        pop %ax
+        ret
+
+        .size realmode_entry, .-realmode_entry
+
+
+########
+.globl cursorOffset
+        .data
+        .align 2
+        .type   cursorOffset, @object
+        .size   cursorOffset, 2
+cursorOffset:
+        .word   0
+
+########
+.globl chunksPer16th
+        .data
+        .align 4
+        .type   chunksPer16th, @object
+        .size   chunksPer16th, 4
+chunksPer16th:
+        .long   1
+
+########
+.globl irq14_count
+        .data
+        .align 4
+        .type   irq14_count, @object
+        .size   irq14_count, 4
+irq14_count:
+        .long   0
+
+########
+.globl irq15_count
+        .data
+        .align 4
+        .type   irq15_count, @object
+        .size   irq15_count, 4
+irq15_count:
+        .long   0
+
+########
+        .section        .note.GNU-stack,"",@progbits
-- 
1.7.10.2.484.gcd07cc5


Reply via email to