λ

furix

cute os

settings

arch/x86_64/lapic.c

5201 bytes raw

#include <lapic.h>

#include <io.h>
#include <types.h>

#define IA32_APIC_BASE 0x1B

#define APIC_BASE_ENABLE (1ull << 11)
#define APIC_BASE_ADDR_MASK 0xFFFFF000ull
#define APIC_DEFAULT_BASE 0xFEE00000u

#define APIC_REG_ID 0x20
#define APIC_REG_VERSION 0x30
#define APIC_REG_EOI 0xB0
#define APIC_REG_SPURIOUS 0xF0
#define APIC_REG_TIMER 0x320

/* bit 16 of the timer control register masks it */
#define APIC_TIMER_MASKED (1u << 16)
#define APIC_REG_ICR 0x300
#define APIC_REG_ICR_HIGH 0x310

#define ICR_DELIVERY_STATUS 0x1000u /* bit 12: set while the IPI is in flight */
#define ICR_TRIGGER_MODE 0x4000u    /* bit 14: level vs edge */
#define ICR_SHORTHAND_SHIFT 18

/* bounded so a lost IPI surfaces as a failed bringup, never a hang */
#define ICR_WAIT_SPINS 1000000u

#define SPURIOUS_VECTOR 0xFF

static volatile uint32_t *apic_reg(uint32_t reg) {
  return (volatile uint32_t *)(uint64_t)(lapic_base() + reg);
}

void lapic_enable(void) {
  uint64_t base = rdmsr(IA32_APIC_BASE);

  /*
   * ORDER MATTERS: mask the timer BEFORE enabling the LAPIC.
   *
   * firmware leaves the LAPIC timer running on whatever vector its divide
   * config selects, which is 0x20 here. enabling the LAPIC first opens the
   * delivery path, so that timer can latch vector 0x20 in the ISR before the
   * mask below lands. IF is clear at this point so the request is held pending
   * rather than taken, and is delivered at the first `sti` much later, which
   * is the self-IPI test. that lands on the default gate and halts the kernel,
   * so the failure reads as "unexpected interrupt during the self-IPI test"
   * with nothing pointing at the timer.
   */
  *apic_reg(APIC_REG_TIMER) = APIC_TIMER_MASKED;

  *apic_reg(APIC_REG_SPURIOUS) = (uint32_t)(SPURIOUS_VECTOR | (1u << 8));

  if ((base & APIC_BASE_ENABLE) == 0) {
    wrmsr(IA32_APIC_BASE, base | APIC_BASE_ENABLE);
  }

  /*
   * drain anything firmware already had in flight. masking the LVT stops new
   * requests but does not retract one already pending, and there is no way to
   * read the ISR to find out what it was. acknowledge blindly: an EOI with
   * nothing outstanding is a no-op.
   */
  lapic_eoi();
}

uint32_t lapic_base(void) {
  uint32_t base = (uint32_t)(rdmsr(IA32_APIC_BASE) & APIC_BASE_ADDR_MASK);

  if (base == 0) {
    base = APIC_DEFAULT_BASE;
  }
  return base;
}

uint32_t lapic_id(void) {
  /*
   * only the low 8 bits of the xAPIC ID register are meaningful; bits above
   * that read back as zero on real hardware but are not architecturally
   * guaranteed to, so mask rather than trust the read. widened from uint8_t
   * because an x2APIC MADT reports 32-bit IDs; see lapic_ipi_send_flags for
   * what happens to those.
   */
  return (uint32_t)(*apic_reg(APIC_REG_ID) >> 24) & 0xffu;
}

void lapic_eoi(void) {
  *apic_reg(APIC_REG_EOI) = 0;
}

bool lapic_ipi_wait(void) {
  uint32_t spins = ICR_WAIT_SPINS;

  while ((*apic_reg(APIC_REG_ICR) & ICR_DELIVERY_STATUS) != 0) {
    if (spins-- == 0) {
      return false;
    }
  }
  return true;
}

/*
 * returns 0 if the IPI was sent, non-zero if the destination could not be
 * addressed at all (an x2APIC ID beyond the xAPIC ICR's 8-bit field). nothing
 * is delivered in that case.
 */
uint32_t lapic_ipi_send_flags(uint32_t delivery, uint32_t shorthand, uint32_t vector,
                              uint32_t apic_id, uint32_t flags) {
  /*
   * the delivery-status bit reads from the high half of the 64-bit ICR, and
   * the destination field only applies when shorthand is unused, so it is
   * harmless to always program ICR_HIGH.
   */
  if (shorthand == LAPIC_DEST) {
    /*
     * the xAPIC destination field is only bits 24..31 of ICR_HIGH, so it can
     * name at most 256 processors. an ID above that comes from an x2APIC MADT
     * entry and would be silently truncated, sending INIT to processor 0 and
     * resetting the BSP instead of the intended AP.
     *
     * addressing those needs IA32_X2APIC_ICR (MSR 0x8B), which is only
     * reachable once x2APIC mode is enabled in IA32_APIC_BASE. that is not done
     * yet, so refuse loudly instead of misdirecting the IPI. callers treat a
     * non-zero return as "this AP was not started".
     */
    if (apic_id > 0xffu) {
      return 1;
    }
    *apic_reg(APIC_REG_ICR_HIGH) = apic_id << 24;
  } else {
    *apic_reg(APIC_REG_ICR_HIGH) = 0;
  }

  *apic_reg(APIC_REG_ICR) = vector |
                            (delivery << 8) |
                            (shorthand << ICR_SHORTHAND_SHIFT) |
                            flags;

  lapic_ipi_wait();
  return 0;
}

uint32_t lapic_ipi_send(uint32_t delivery, uint32_t shorthand, uint32_t vector,
                        uint32_t apic_id) {
  return lapic_ipi_send_flags(delivery, shorthand, vector, apic_id, 0);
}

uint32_t lapic_ipi_init_assert(uint32_t apic_id) {
  return lapic_ipi_send_flags(LAPIC_IPI_INIT, LAPIC_DEST, 0, apic_id, ICR_TRIGGER_MODE);
}

uint32_t lapic_ipi_init_deassert(uint32_t apic_id) {
  return lapic_ipi_send_flags(LAPIC_IPI_INIT, LAPIC_DEST, 0, apic_id, 0);
}

uint32_t lapic_ipi_sipi(uint32_t apic_id, uint8_t vector) {
  return lapic_ipi_send_flags(LAPIC_IPI_STARTUP, LAPIC_DEST, vector, apic_id, 0);
}