λ

furix

cute os

settings

kernel/kernel.c

6236 bytes raw

#include <kernel.h>

#include <acpi.h>
#include <ap_boot.h>
#include <gdt.h>
#include <idt.h>
#include <io.h>
#include <ioapic.h>
#include <lapic.h>
#include <pic.h>
#include <percpu.h>
#include <serial.h>

/* addresses spread across different PML4 slots. OVMF maps the first PML4 entry
   (0-512 GiB), so low candidates may be present; the high ones are not. we READ
   rather than write so a mapped candidate cannot corrupt firmware. */
static const uint64_t probe_addrs[] = {
  0x0000700000000000ull, /* ~123 TiB    (PML4 slot 224, canonical) */
  0xFFFF800000000000ull, /* kernel half (PML4 slot 256, canonical) */
  0x0000008000000000ull, /* 512 GiB     (PML4 slot 1) */
};

/*
 * self-IPI round trip: proves the LAPIC can deliver, the IDT dispatches, and
 * iretq returns us cleanly. on real hardware this is also how we would verify
 * a vector before trusting it for inter-CPU traffic.
 */
static volatile uint32_t ipi_count;
static volatile uint64_t ipi_sender_apic_id;

static void on_ipi(void) {
  ipi_sender_apic_id = lapic_id();
  ipi_count++;
}

static bool test_self_ipi(void) {
  const uint32_t spins = 2000000;
  uint32_t i;

  ipi_count = 0;
  idt_set_handler(VECTOR_IPI_TIMER, on_ipi);

  /*
   * interrupts must be enabled for the IPI to be delivered: our gates are
   * interrupt gates, so IF clear means the request is latched, not taken.
   */
  cpu_sti();

  lapic_ipi_send(LAPIC_IPI_FIXED, LAPIC_SHORTHAND_SELF, VECTOR_IPI_TIMER, 0);

  for (i = 0; i < spins && ipi_count == 0; i++) {
    /* wait for the interrupt to arrive */
  }

  cpu_cli();

  serial_puts("apic: self-IPI on vector ");
  serial_put_hex(VECTOR_IPI_TIMER);
  serial_puts(" -> count = ");
  serial_put_hex(ipi_count);
  serial_puts("\n");

  if (ipi_count == 0) {
    serial_puts("apic: FAIL (no interrupt delivered)\n");
    return false;
  }

  serial_puts("apic: ipi sender apic id = ");
  serial_put_hex(ipi_sender_apic_id);
  serial_puts(ipi_sender_apic_id == lapic_id() ? " (ok)\n" : " (WRONG)\n");

  idt_set_handler(VECTOR_IPI_TIMER, NULL);
  return true;
}

static bool report_madt(const EFI_SYSTEM_TABLE *st, struct acpi_madt_info *madt) {
  uint32_t i;

  if (!acpi_madt_parse(st, madt)) {
    serial_puts("madt: not found\n");
    return false;
  }

  serial_puts("madt: cpus = ");
  serial_put_hex(madt->cpu_count);
  serial_puts(", ioapics = ");
  serial_put_hex(madt->ioapic_count);
  serial_puts("\n  apic ids:");

  for (i = 0; i < madt->cpu_count; i++) {
    serial_puts(" ");
    serial_put_hex(madt->apic_ids[i]);
  }
  serial_puts("\n");

  return true;
}

void kernel_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *st) {
  uint64_t trampoline_base = 0;
  struct acpi_madt_info madt;
  uint32_t started = 0;
  uint64_t gs_check;
  uint32_t count;
  uint32_t i;

  (void)image;

  /* firmware console first: once our own GDT/IDT are live we must not call UEFI
     boot services again */
  st->ConOut->OutputString(st->ConOut, (CHAR16 *)L"meow :3 hello world!\r\n");

  cpu_cli();
  serial_init();
  pic_disarm();
  ioapic_disarm();
  serial_puts("furix: phase 0\n");

  /*
   * the LAPIC timer MUST be masked before the first UEFI boot service call
   * below, not merely before we enable the LAPIC. boot services re-enable
   * interrupts on return, so the firmware's timer is live across the window
   * where our IDT is installed but carries no handler for vector 0x20; a tick
   * there lands on the default gate and halts the kernel, at a point that
   * wanders with boot timing. observed roughly 1 run in 10 on smp=8.
   */
  lapic_enable();
  serial_puts("lapic: base = ");
  serial_put_hex(lapic_base());
  serial_puts(", id = ");
  serial_put_hex(lapic_id());
  serial_puts("\n");

  /*
   * the boot services below can leave IF set on the way out, so re-assert it
   * here rather than trusting the state we handed them.
   */
  cpu_cli();

  /*
   * must happen before we install our own GDT/IDT: afterwards we must not call
   * UEFI boot services again, and we need GetMemoryMap to find a low page.
   */
  if (!ap_boot_reserve(st, &trampoline_base)) {
    serial_puts("ERROR: ap trampoline reservation failed\n");
    for (;;) {
      cpu_hlt();
    }
  }

  gdt_init(); /* GDT + TSS + IST stacks */
  idt_init(); /* IDT, IST1 on #DF and IST2 on NMI */
  serial_puts("gdt + tss + ist + idt ok\n");

  percpu_init();
  serial_puts("percpu: gs load = ");
  gs_check = percpu_gs_selftest(0xfeedfacecafebeefull);
  serial_put_hex(gs_check);
  serial_puts(gs_check == 0xfeedfacecafebeefull ? " (ok)\n" : " (BROKEN)\n");
  serial_puts("percpu: c   load = ");
  gs_check = percpu_gs_selftest_via_c(0x1111222233334444ull);
  serial_put_hex(gs_check);
  serial_puts(gs_check == 0x1111222233334444ull ? " (ok)\n" : " (BROKEN)\n");

  percpu_set_id(lapic_id());

  test_self_ipi();

  if (report_madt(st, &madt)) {
    /*
     * bring the APs up before the deliberate page fault below: that probe halts
     * the kernel, so it has to be the last thing that happens.
     *
     * interrupts are OFF for the duration. AP startup is driven entirely by
     * IPIs sent FROM this CPU, so nothing here needs an interrupt serviced,
     * and the spin that waits for an AP to report in is long enough (tens of
     * millions of iterations) for the timer to fire. nothing routes vector
     * 0x20 yet, so it lands on the default gate, which reports and halts,
     * which stopped the BSP mid-bringup, making an AP failure and a BSP death
     * indistinguishable from the outside.
     */
    cpu_cli();

    started = ap_boot_start(&madt);

    serial_puts("ap: ");
    serial_put_hex(started);
    serial_puts(" of ");
    serial_put_hex(acpi_madt_ap_count(&madt));
    serial_puts(" aps started\n");
  }

  cpu_cli();

  serial_puts("probing unmapped addresses...\n");

  count = (uint32_t)(sizeof(probe_addrs) / sizeof(probe_addrs[0]));
  for (i = 0; i < count; i++) {
    volatile uint64_t sink;

    serial_puts("  probing ");
    serial_put_hex(probe_addrs[i]);
    serial_puts(" ...\n");

    sink = *(volatile uint64_t *)probe_addrs[i];
    (void)sink;
  }

  serial_puts("ERROR: none of the probe addresses faulted\n");

  /* do not return to firmware with our GDT/IDT installed */
  for (;;) {
    cpu_hlt();
  }
}