λ

furix

cute os

settings

arch/x86_64/asm.S

6287 bytes raw

/*
    * low-level x86_64 primitives for furix.
    *
    * these live in a real assembly file rather than inline asm so that the exact
    * instruction encodings are visible, reviewable and assembled by `as` instead of
    * the compiler. all functions follow the Microsoft x64 ABI (the kernel is built
    * for x86_64-unknown-windows): first argument in RCX, return value in RAX.
    */

.text

/* ports */

.globl inb


inb:

    movl    %ecx, %edx
    xorb    %al, %al
    inb    %dx, %al
    ret

.globl outb


outb:

    movl    %edx, %eax        /* value -> AL */
    movl    %ecx, %edx        /* port -> DX (8-bit OUT needs DX) */
    outb    %al, %dx
    ret

/* interrupts */

.globl cpu_cli


cpu_cli:

    cli
    ret

.globl cpu_sti


cpu_sti:

    sti
    ret

.globl cpu_hlt


cpu_hlt:

    hlt
    ret

/*
    * atomic_fetch_add(ptr, val): returns the OLD value, so the result is usable
    * as a completion index.
    *
    * `lock xadd` is a single bus transaction: no other CPU can interleave a read
    * and a write of the same location. without the lock prefix the two halves race
    * and two CPUs adding to the same counter can both read the same old value and
    * one increment is lost.
    *
    * arg0 = ptr (RCX), arg1 = val (RDX), per the Microsoft x64 ABI. the result
    * lands in EAX, which is only the low half                                           ; callers counting events want the
    * 32-bit form, so keep the operand and result 32-bit throughout.
    */
.globl atomic_fetch_add_u32


atomic_fetch_add_u32:

    movl    %edx, %eax        /* EAX = value to add */
    lock xaddl %eax, (%rcx)    /* EAX = old, memory += value */
    ret

/*
    * test_and_set(ptr): atomically read *ptr, and write 1 to it. returns the
    * value it read, so nonzero means "somebody else got here first".
    *
    * this is the acquire primitive for the serial lock. `xchg` with a memory
    * operand is implicitly locked on x86, so no separate lock prefix is needed.
    * the write is a full barrier, which is what makes the caller's own flag check
    * after a successful claim unnecessary.
    */
.globl atomic_test_and_set_u8


atomic_test_and_set_u8:

    movzbl  (%rcx), %eax
    movl    $1, %edx
    xchgb   %dl, (%rcx)
    ret

/*
    * atomic_load_u32(ptr): a plain aligned 32-bit load, which x86 already makes
    * atomic against concurrent stores. spelled as a function so the volatile
    * read cannot be reordered or elided by the compiler at a call site.
    */
.globl atomic_load_u32


atomic_load_u32:

    movl    (%rcx), %eax
    ret

/*
    * cpu_irq_save(): disable interrupts and return the previous EFLAGS word, so
    * the caller can restore the prior IF state exactly rather than assuming it.
    * the bit is forced clear in the returned value so that a caller who ignores
    * the result still leaves interrupts off.
    */
.globl cpu_irq_save


cpu_irq_save:

    pushfq
    popq    %rax
    pushfq
    popq    %rcx
    andl    $0xfffffdff, %ecx    /* clear IF in the copy we return */
    pushq    %rcx
    popfq            /* interrupts now off */
    ret

/* cpu_irq_restore(flags = ECX): re-enable interrupts iff the saved word had IF. */

.globl cpu_irq_restore


cpu_irq_restore:

    pushq    %rcx
    orl    $0x200, %ecx        /* normalise IF to 1 */
    testb    $1, %cl        /* CL holds the saved IF */
    jne    1f
    andl    $0xfffffdff, %ecx


1:

    popfq
    ret

/* read the IF flag on its own: 1 if interrupts are enabled, else 0. */

.globl cpu_irqs_enabled


cpu_irqs_enabled:

    pushfq
    popq    %rax
    andl    $0x200, %eax
    xorl    %eax, %eax    /* normalise to 0 or 1 */
    ret

/* control registers */

.globl read_cr2


read_cr2:

    movq    %cr2, %rax
    ret

.globl read_cr3


read_cr3:

    movq    %cr3, %rax
    ret

/*
    * MOV to/from a control register in long mode uses a 64-bit operand, so these
    * must be 64-bit moves. (A 32-bit `mov %eax, %cr4` is rejected: in long mode
    * that form does not encode.)
    */
.globl read_cr4


read_cr4:

    movq    %cr4, %rax
    ret

.globl write_cr4


write_cr4:

    movq    %rcx, %cr4
    ret

/* MSRs */

.globl rdmsr


rdmsr:

    rdmsr
    shlq    $32, %rdx
    orq    %rdx, %rax
    ret

/* wrmsr(msr = ECX, value = RDX) */
.globl wrmsr


wrmsr:

    movq    %rdx, %r8
    movl    %r8d, %eax        /* EAX = low 32 */
    shrq    $32, %r8            /* 64-bit shift: 32-bit would mask to 0 */
    movl    %r8d, %edx        /* EDX = high 32 */
    wrmsr
    ret

.data
.balign 8
.globl cpuid_out


cpuid_out:

    .quad 0
    .quad 0
    .quad 0
    .quad 0
.text

/* CPUID with the leaf in ECX; the four result registers land in cpuid_out. */
.globl cpuid


cpuid:

    movl    %ecx, %eax
    xorl    %ecx, %ecx
    cpuid
    movq    %rax, cpuid_out(%rip)
    movq    %rbx, cpuid_out+8(%rip)
    movq    %rcx, cpuid_out+16(%rip)
    movq    %rdx, cpuid_out+24(%rip)
    ret

/* descriptor table registers */

.globl lgdt


lgdt:

    lgdt    (%rcx)
    ret

.globl lidt


lidt:

    lidt    (%rcx)
    ret

.globl ltr


ltr:

    movl    %ecx, %eax
    ltr    %ax
    ret

/*
    * CS cannot be reloaded with mov. push the new selector and a return address,
    * then far-return into it.
    */
.globl reload_cs


reload_cs:

    leaq    1f(%rip), %rax
    pushq    %rcx
    pushq    %rax
    lretq


1:

    ret

.globl load_data_segments


load_data_segments:

    movl    %ecx, %eax
    movw    %ax, %ds
    movw    %ax, %es
    movw    %ax, %ss
    ret

/* per-CPU (GS) access */

.globl swapgs


swapgs:

    swapgs
    ret

/*
    * wrgsbase/rdfsbase require CR4.FSGSBASE, which in turn requires the CPU to
    * advertise CPUID.(EAX=7,ECX=0):EBX[0]. qemu's default model (qemu64) leaves
    * that bit clear, and setting CR4.FSGSBASE there does not come back as a
    * usable #GP, the guest simply stops. prefer IA32_KERNEL_GS_BASE + swapgs
    * (see percpu_init), which works everywhere.
    */
.globl wrgsbase


wrgsbase:

    wrgsbaseq %rcx
    ret

.globl rdfsbase


rdfsbase:

    rdfsbaseq %rax
    ret

/* GS-relative access to the per-CPU area, which starts at GS.base + 0. */
.globl gs_store


gs_store:

    movq    %rcx, %gs:0
    ret

.globl gs_load


gs_load:

    movq    %gs:0, %rax
    ret