; Build instructions: `nasm article.asm -o article.flp`, and boot article.flp.

; 16-bit, 0x7C00 based code.
org 0x7C00
bits 16

dining_table equ 0x7F00
idt equ 0x8000
scratch equ 0x9000

local_apic equ 0xFEE00000

spurious_interrupt_vector_register equ 0x00F0
icr_low equ 0x0300
icr_high equ 0x0310
eoi equ 0x0B0

lvt_timer equ 0x0320
initial_count_timer equ 0x0380
current_count_timer equ 0x0390
timer_divide_config equ 0x03E0

; Far jump, reset CS to 0x0000.
; CS cannot be set via a 'mov', and requires a far jump.
start:
    jmp 0x0000:seg_setup

seg_setup:
    xor ax, ax
    mov ds, ax
    mov es, ax

    ; Start the stack from beneath start (0x7C00).
    mov ss, ax
    mov esp, start

    ; Clear direction flag.
    cld

    ; Read the 0x92 port.
    in al, 0x92
    ; Enable fast A20.
    or al, 2
    ; Bit 0 is used to specify fast reset, and it out.
    and al, 0xFE
    out 0x92, al

    mov si, trampoline
    mov di, 0x7000
    mov cx, trampoline_end - trampoline
    rep movsb

    ; From 0x7E00, for the IDT, clear till 0xA000.
    mov di, 0x7E00
    xor eax, eax
    mov cx, (0xA000 - 0x7E00)/4

    ; Store EAX - ECX numbers of time.
    rep stosd

    ; Each bit specifies each line.
    mov al, 0xFF
    ; For the master PIC.
    out 0xA1, al
    ; For the slave PIC.
    out 0x21, al

    ; Load the GDTR - the pointer to the GDT.
    lgdt [gdtr]

    mov eax, cr0
    or ax, 1                       ; Set the protection enable bit.
    mov cr0, eax
    jmp 0x08:protected_mode

bits 32
protected_mode:
    ; Selector 0x10 is the data selector.
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax

    mov eax, lapic_handler
    mov ecx, 63
    call add_idt_gate

    lidt [idtr]

    ; Port 0x43 is the command register.
    ; 0b -> 16-bit binary mode, while specifying the reload value.
    ; 001b -> mode 1, hardware re-triggerable one-shot.
    ; 11b -> lobyte/hibyte access mode.
    ; 10b -> channel 2.
    mov al, 10110010b
    out 0x43, al

    ; We set a frequency of 100 Hz.
    ; 1193182/100 = 0x2E9C.
    ; Low byte.
    mov al, 0x9C
    out 0x42, al
    ; High byte.
    mov al, 0x2E
    out 0x42, al

    ; Bit 8 enables the LAPIC.
    ; Bits 0 to 7 specify the vector of the spurious interrupt handler.
    ; We set it to 63 (bits 0 to 3 are hardwired 1).
    mov esi, local_apic
    mov dword [local_apic + spurious_interrupt_vector_register], (1 << 8) | (11b << 4)

    ; Send the INIT IPI.
    ; 101b -> INIT.
    ; 1 << 14 -> level.
    ; 11b << 18 -> all excluding self.
    mov dword [local_apic + icr_low], (101b << 8) | (1 << 14) | (11b << 18)

    ; Though alarmingly versatile, LAPIC eerily echoes nice sentiments of
    ; lots of effort for little gain.
    ; Set the divide configuration register as divide by 1.
    mov dword [local_apic + timer_divide_config], 1011b
    mov dword [local_apic + lvt_timer], 63
    mov dword [local_apic + initial_count_timer], -1

    ; Start the PIT channel 2 timer.
    in al, 0x61
    and al, 0xFE
    out 0x61, al
    or al, 1
    out 0x61, al

    .delay:
        in al, 0x61
        ; Bit 5 specifies if the output is high or not.
        and al, 0x20
        jz .delay

    mov eax, [local_apic + current_count_timer]
    not eax
    mov [initial_count], eax

    ; Send the Startup IPI.
    ; Vector XX specifies the page, giving trampoline address 0x000XX000.
    ; In our case, 0x07000.
    ; 110b -> SIPI.
    mov dword [local_apic + icr_low], 7 | (110b << 8) | (1 << 14) | (11b << 18)

    xor ebp, ebp
ap_initialized:
    mov dword [local_apic + timer_divide_config], 1011b
    ; (1 << 17) specifies periodic.
    mov dword [local_apic + lvt_timer], 63 | (1 << 17)
    mov eax, [initial_count]
    mov dword [local_apic + initial_count_timer], eax

    sti
hlt:
    hlt
    jmp hlt

lapic_handler:
    lea edx, [scratch + ebp * 2]
    mov dword [local_apic + eoi], edx
    dec byte [edx]
    jz time_up
    iretd

    time_up:
        ; Reset it for the next time-out.
        mov byte [edx], 200

        lea edi, [dining_table + ebp]
        lea esi, [edi + 1]
        cmp bp, [cpu_id]
        jb not_highest

        mov esi, dining_table

    not_highest:
        xor al, al

        ; Not eating?
        cmp byte [edx + 1], al
        je get_forks

        not byte [edx + 1]
        xchg [esi], al
        xor al, al
        xchg [edi], al

        xchg bx, bx
        iretd

    get_forks:
        mov cl, 2

    fork_loop:
        mov al, 1
        spinlock:
            xchg [edi], al
            test al, al
            jnz spinlock

        mov edi, esi
        dec cl
        jnz fork_loop

        not byte [edx + 1]

    xchg bx, bx
    iretd

; ecx: interrupt vector.
; eax: the interrupt handler.
; Trash edi.
add_idt_gate:
    ; The entry into the table.
    lea edi, [idt + ecx * 8] 
    
    ; The first two bytes specify the lower 16-bits of the interrupt handler.
    mov [edi], ax
    shr eax, 16

    ; The upper-most two bytes specify the highest 16-bits.
    mov [edi + 6], ax

    ; The third and fourth byte specify the selector of the interrupt function,
    ; 0x08 in this case.
    ; The fifth byte is reserved 0.
    ; The sixth byte is for flags:
    ;   Bits 0:3 -> type. 0x0E is 32-bit interrupt gate.
    ;   Bits 5:6 -> the privilege level the calling descriptor should have;
    ;   Bit 7 -> present flag.
    mov dword [edi + 2], 0x08 | (1 << 31) | (0x0E << 24)
    ret

; The GDT.
gdt:
    ; The first entry in the GDT is supposed to be a
    ; null entry, but we'll substitute it with the
    ; 'pointer to gdt'.
    gdtr:
        ; Size of GDT - 1.
        ; 3 entries, each 8 bytes.
        dw (0x8 * 3) - 1
        ; Pointer to GDT.
        dd gdt
        ; Make it 8 bytes.
        dw 0x0000

    ; The code entry.
    dw 0xFFFF       ; First 16-bits of limit.
    dw 0x0000       ; First 16-bits of base.
    db 0x00         ; Next 8-bits of base.
    db 0x9A         ; Read/writable, executable, present.
                    ; Bits 5 & 6 are zero, specifying the descriptor's
                    ; privilege level as 0.
    db 0xCF         ; 0b11001111.
                    ; The least significant four bits are next four bits of
                    ; limit.
                    ; The most significant two bits specify that this is for
                    ; 32-bit protected mode, and that the 20-bit limit is in
                    ; 4KiB blocks. Thus, the 20-bit 0b11111111111111111111
                    ; specifies a limit of 0xFFFFFFFF.
    db 0x00         ; Last 8-bits of base.

    ; The data entry.
    dw 0xFFFF, 0x0000
    db 0x00
    db 0x92         ; Read/writable, present.
    db 0xCF
    db 0x00

idtr:
    ; Size of IDT - 1.
    dw (256 * 8) - 1
    dd idt

free_stack:
    dw scratch + 0x2000

bits 16
trampoline:
    xor ax, ax
    mov ds, ax

    ; Disable interrupts.
    cli

    ; Load the GDTR - the pointer to the GDT.
    lgdt [gdtr]

    mov eax, cr0
    or ax, 1                       ; Set the protection enable bit.
    mov cr0, eax
    jmp 0x08:ap_pmode

bits 32
ap_pmode:
    ; Selector 0x10 is the data selector.
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov ss, ax

    lidt [idtr]

    mov al, 1

    get_lock:
        xchg [resource_lock], al
        test al, al
        jnz get_lock

    movzx esp, word [free_stack]
    add word [free_stack], 0x1000

    inc word [cpu_id]
    movzx ebp, word [cpu_id]

    xor al, al
    xchg [resource_lock], al

    mov byte [scratch + ebp * 2], 200
    ; Enable the LAPIC.
    mov dword [local_apic + spurious_interrupt_vector_register], (1 << 8) | (11b << 4)
    jmp long ap_initialized

trampoline_end:

; Pad to the 510th byte, after which store the BIOS boot signature.
times 510-($-$$) db 0
dw 0xAA55

initial_count:
    dd 0
cpu_id:
    dw 0
resource_lock:
    db 0

; Pad to floppy disk.
times (1440 * 1024) - ($ - $$) db 0
