Back to home page

LXR

 
 

    


File indexing completed on 2025-05-11 08:24:10

0001 /* SPDX-License-Identifier: BSD-2-Clause */
0002 
0003 /**
0004  * @file
0005  *
0006  * @ingroup RTEMSBSPsX8664AMD64
0007  *
0008  * @brief APIC implementation
0009  */
0010 
0011 /*
0012  * Copyright (C) 2024 Matheus Pecoraro
0013  *
0014  * Redistribution and use in source and binary forms, with or without
0015  * modification, are permitted provided that the following conditions
0016  * are met:
0017  * 1. Redistributions of source code must retain the above copyright
0018  *    notice, this list of conditions and the following disclaimer.
0019  * 2. Redistributions in binary form must reproduce the above copyright
0020  *    notice, this list of conditions and the following disclaimer in the
0021  *    documentation and/or other materials provided with the distribution.
0022  *
0023  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0024  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0025  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0026  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
0027  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
0028  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
0029  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0030  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
0031  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0032  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0033  * POSSIBILITY OF SUCH DAMAGE.
0034  */
0035 
0036 #include <acpi/acpi.h>
0037 #include <apic.h>
0038 #include <assert.h>
0039 #include <bsp.h>
0040 #include <pic.h>
0041 #include <rtems/score/idt.h>
0042 
0043 extern void apic_spurious_handler(void);
0044 
0045 volatile uint32_t* amd64_lapic_base;
0046 
0047 #ifdef RTEMS_SMP
0048 /* Maps the processor index to the Local APIC ID */
0049 uint8_t amd64_lapic_to_cpu_map[xAPIC_MAX_APIC_ID + 1];
0050 static uint8_t cpu_to_lapic_map[xAPIC_MAX_APIC_ID + 1];
0051 static uint32_t lapic_count = 0;
0052 #endif
0053 
0054 /**
0055  * @brief Returns wheather the system contains a local APIC or not.
0056  *
0057  * When the CPUID instruction is executed with a source operand of 1 in the EAX
0058  * register, bit 9 of the CPUID feature flags returned in the EDX register
0059  * indicates the presence (set) or absence (clear) of a local APIC.
0060  *
0061  * @return true if CPUID reports the presence of a local APIC.
0062  */
0063 static bool has_lapic_support(void)
0064 {
0065   uint32_t eax, ebx, ecx, edx;
0066   cpuid(1, &eax, &ebx, &ecx, &edx);
0067   return (edx >> 9) & 1;
0068 }
0069 
0070 /**
0071  * @brief Passed to acpi_walk_subtables to parse information from MADT entries
0072  *
0073  * @param entry An entry of the table passed to acpi_walk_subtables
0074  */
0075 static void madt_subtables_handler(ACPI_SUBTABLE_HEADER* entry)
0076 {
0077   switch (entry->Type) {
0078 #ifdef RTEMS_SMP
0079     case ACPI_MADT_TYPE_LOCAL_APIC:
0080       ACPI_MADT_LOCAL_APIC* lapic_entry = (ACPI_MADT_LOCAL_APIC*) entry;
0081 
0082       if (lapic_count >= xAPIC_MAX_APIC_ID + 1 ||
0083           lapic_get_id() == lapic_entry->Id) {
0084         break;
0085       }
0086 
0087       amd64_lapic_to_cpu_map[lapic_entry->Id] = (uint8_t) lapic_count;
0088       cpu_to_lapic_map[lapic_count++] = lapic_entry->Id;
0089       break;
0090 #endif
0091     case ACPI_MADT_TYPE_LOCAL_APIC_OVERRIDE:
0092       ACPI_MADT_LOCAL_APIC_OVERRIDE* lapic_override =
0093                                         (ACPI_MADT_LOCAL_APIC_OVERRIDE*) entry;
0094       amd64_lapic_base = (uint32_t*) lapic_override->Address;
0095       break;
0096     default:
0097       break;
0098   }
0099 }
0100 
0101 /**
0102  * @brief Retrieve the MADT and parse the values we need.
0103  *
0104  * @return true if successful.
0105  */
0106 static bool parse_madt(void)
0107 {
0108   ACPI_STATUS status;
0109   ACPI_TABLE_MADT* madt;
0110 
0111   status = AcpiGetTable(ACPI_SIG_MADT, 1, (ACPI_TABLE_HEADER**) &madt);
0112   if (status != (AE_OK)) {
0113     return false;
0114   }
0115 
0116 #ifdef RTEMS_SMP
0117   /* Ensure the boot processor is cpu index 0 */
0118   uint8_t lapic_id = lapic_get_id();
0119   amd64_lapic_to_cpu_map[lapic_id] = (uint8_t) lapic_count;
0120   cpu_to_lapic_map[lapic_count++] = lapic_id;
0121 #endif
0122 
0123   amd64_lapic_base = (uint32_t*) ((uintptr_t) madt->Address);
0124   acpi_walk_subtables(
0125     (ACPI_TABLE_HEADER*) madt,
0126     sizeof(ACPI_TABLE_MADT),
0127     madt_subtables_handler
0128   );
0129 
0130   return true;
0131 }
0132 
0133 /**
0134  * @brief Calculates the amount of LAPIC timer ticks per second using the PIT.
0135  *
0136  * @return The amount of ticks per second calculated.
0137  */
0138 static uint32_t lapic_timer_calc_ticks_per_sec(void)
0139 {
0140   /* Configure LAPIC timer in one-shot mode to prepare for calibration */
0141   amd64_lapic_base[LAPIC_REGISTER_LVT_TIMER] = BSP_VECTOR_APIC_TIMER;
0142   amd64_lapic_base[LAPIC_REGISTER_TIMER_DIV] = LAPIC_TIMER_SELECT_DIVIDER;
0143 
0144   uint8_t chan2_value;
0145   PIT_CHAN2_ENABLE(chan2_value);
0146 
0147   /*
0148    * Make sure interrupts are disabled while we calibrate for 2 reasons:
0149    *   - Writing values to the PIT should be atomic (for now, this is okay
0150    *     because we're the only ones ever touching the PIT ports, but an
0151    *     interrupt resetting the PIT could mess calibration up).
0152    *   - We need to make sure that no interrupts throw our synchronization of
0153    *     the LAPIC timer off.
0154    */
0155   rtems_interrupt_level level;
0156   rtems_interrupt_local_disable(level);
0157 
0158   uint32_t pit_ticks = PIT_CALIBRATE_TICKS;
0159   PIT_CHAN2_WRITE_TICKS(pit_ticks);
0160 
0161   /* Start LAPIC timer's countdown */
0162   const uint32_t lapic_calibrate_init_count = 0xffffffff;
0163 
0164   PIT_CHAN2_START_DELAY(chan2_value);
0165   amd64_lapic_base[LAPIC_REGISTER_TIMER_INITCNT] = lapic_calibrate_init_count;
0166 
0167   PIT_CHAN2_WAIT_DELAY(pit_ticks);
0168   uint32_t lapic_currcnt = amd64_lapic_base[LAPIC_REGISTER_TIMER_CURRCNT];
0169 
0170   DBG_PRINTF("PIT stopped at 0x%" PRIx32 "\n", pit_ticks);
0171 
0172   /* Stop APIC timer to calculate ticks to time ratio */
0173   amd64_lapic_base[LAPIC_REGISTER_LVT_TIMER] = LAPIC_LVT_MASK;
0174 
0175   /* Get counts passed since we started counting */
0176   uint32_t lapic_ticks_per_sec = lapic_calibrate_init_count - lapic_currcnt;
0177 
0178   DBG_PRINTF(
0179     "APIC ticks passed in 1/%d of a second: 0x%" PRIx32 "\n",
0180     PIT_CALIBRATE_DIVIDER,
0181     lapic_ticks_per_sec
0182   );
0183 
0184   /* We ran the PIT for a fraction of a second */
0185   lapic_ticks_per_sec = lapic_ticks_per_sec * PIT_CALIBRATE_DIVIDER;
0186 
0187   /* Restore interrupts now that calibration is complete */
0188   rtems_interrupt_local_enable(level);
0189 
0190   /* Confirm that the APIC timer never hit 0 and IRQ'd during calibration */
0191   assert(lapic_ticks_per_sec != 0 &&
0192          lapic_ticks_per_sec != lapic_calibrate_init_count);
0193 
0194   DBG_PRINTF(
0195     "CPU frequency: 0x%" PRIu64 "\nAPIC ticks/sec: 0x%" PRIu32 "\n",
0196     /* Multiply to undo effects of divider */
0197     (uint64_t) lapic_ticks_per_sec * LAPIC_TIMER_DIVIDE_VALUE,
0198     lapic_ticks_per_sec
0199   );
0200 
0201   return lapic_ticks_per_sec;
0202 }
0203 
0204 #ifdef RTEMS_SMP
0205 /**
0206  * @brief Sends an interprocessor interrupt
0207  *
0208  * @param dest_id Local APIC ID of the destination
0209  * @param icr_low The flags to be written to the low value of the ICR
0210  */
0211 static void send_ipi(uint8_t dest_id, uint32_t icr_low)
0212 {
0213   amd64_lapic_base[LAPIC_REGISTER_ICR_HIGH] =
0214     (amd64_lapic_base[LAPIC_REGISTER_ICR_HIGH] & LAPIC_ICR_HIGH_MASK) | (dest_id << 24);
0215 
0216   amd64_lapic_base[LAPIC_REGISTER_ICR_LOW] =
0217     (amd64_lapic_base[LAPIC_REGISTER_ICR_LOW] & LAPIC_ICR_LOW_MASK) | icr_low;
0218 }
0219 
0220 static void wait_ipi(void)
0221 {
0222   while (amd64_lapic_base[LAPIC_REGISTER_ICR_LOW] & LAPIC_ICR_DELIV_STAT_PEND) {
0223     amd64_spinwait();
0224   }
0225 }
0226 #endif
0227 
0228 bool lapic_initialize(void)
0229 {
0230   if (has_lapic_support() == false || parse_madt() == false) {
0231     return false;
0232   }
0233 
0234   /* Hardware enable the APIC just to be sure */
0235   uint64_t apic_base_msr = rdmsr(APIC_BASE_MSR);
0236   wrmsr(
0237     APIC_BASE_MSR,
0238     apic_base_msr | APIC_BASE_MSR_ENABLE,
0239     apic_base_msr >> 32
0240   );
0241 
0242   DBG_PRINTF("APIC is at 0x%" PRIxPTR "\n", (uintptr_t) amd64_lapic_base);
0243   DBG_PRINTF(
0244     "APIC ID at *0x%" PRIxPTR "=0x%" PRIx32 "\n",
0245     (uintptr_t) &amd64_lapic_base[LAPIC_REGISTER_ID],
0246     amd64_lapic_base[LAPIC_REGISTER_ID]
0247   );
0248 
0249   DBG_PRINTF(
0250     "APIC spurious vector register *0x%" PRIxPTR "=0x%" PRIx32 "\n",
0251     (uintptr_t) &amd64_lapic_base[LAPIC_REGISTER_SPURIOUS],
0252     amd64_lapic_base[LAPIC_REGISTER_SPURIOUS]
0253   );
0254 
0255   /*
0256    * Software enable the APIC by mapping spurious vector and setting enable bit.
0257    */
0258   uintptr_t old;
0259   amd64_install_raw_interrupt(
0260     BSP_VECTOR_SPURIOUS,
0261     (uintptr_t) apic_spurious_handler,
0262     &old
0263   );
0264   amd64_lapic_base[LAPIC_REGISTER_SPURIOUS] =
0265     LAPIC_SPURIOUS_ENABLE | BSP_VECTOR_SPURIOUS;
0266 
0267   DBG_PRINTF(
0268     "APIC spurious vector register *0x%" PRIxPTR "=0x%" PRIx32 "\n",
0269     (uintptr_t) &amd64_lapic_base[LAPIC_REGISTER_SPURIOUS],
0270     amd64_lapic_base[LAPIC_REGISTER_SPURIOUS]
0271   );
0272 
0273   /*
0274    * The PIC may send spurious IRQs even when disabled, and without remapping
0275    * IRQ7 would look like an exception.
0276    */
0277   pic_remap(PIC1_REMAP_DEST, PIC2_REMAP_DEST);
0278   pic_disable();
0279 
0280   return true;
0281 }
0282 
0283 uint32_t lapic_timer_calc_ticks(uint64_t desired_freq_hz)
0284 {
0285   uint32_t lapic_ticks_per_sec = 0;
0286   uint64_t lapic_tick_total = 0;
0287   for (uint32_t i = 0; i < LAPIC_TIMER_NUM_CALIBRATIONS; i++) {
0288     lapic_tick_total += lapic_timer_calc_ticks_per_sec();
0289   }
0290   lapic_ticks_per_sec = lapic_tick_total / LAPIC_TIMER_NUM_CALIBRATIONS;
0291 
0292   /*
0293    * The APIC timer counter is decremented at the speed of the CPU bus
0294    * frequency (and we use a frequency divider).
0295    *
0296    * This means:
0297    *   lapic_ticks_per_sec = (cpu_bus_frequency / timer_divide_value)
0298    *
0299    * Therefore:
0300    *   reload_value = lapic_ticks_per_sec / desired_freq_hz
0301    */
0302   return lapic_ticks_per_sec / desired_freq_hz;
0303 }
0304 
0305 void lapic_timer_enable(uint32_t reload_value)
0306 {
0307   amd64_lapic_base[LAPIC_REGISTER_LVT_TIMER] = BSP_VECTOR_APIC_TIMER | LAPIC_SELECT_TMR_PERIODIC;
0308   amd64_lapic_base[LAPIC_REGISTER_TIMER_DIV] = LAPIC_TIMER_SELECT_DIVIDER;
0309   amd64_lapic_base[LAPIC_REGISTER_TIMER_INITCNT] = reload_value;
0310 }
0311 
0312 #ifdef RTEMS_SMP
0313 uint32_t lapic_get_num_of_procesors(void)
0314 {
0315   return lapic_count;
0316 }
0317 
0318 void lapic_send_ipi(uint32_t target_cpu_index, uint8_t isr_vector)
0319 {
0320   uint8_t target_lapic_id = cpu_to_lapic_map[target_cpu_index];
0321   send_ipi(target_lapic_id, isr_vector);
0322   wait_ipi();
0323 }
0324 
0325 /**
0326  * This routine attempts to follow the algorithm described in the
0327  * Intel Multiprocessor Specification v1.4 in section B.4.
0328  */
0329 void lapic_start_ap(uint32_t cpu_index, uint8_t page_vector)
0330 {
0331   if (cpu_index >= lapic_count) {
0332     return;
0333   }
0334 
0335   uint8_t lapic_id = cpu_to_lapic_map[cpu_index];
0336 
0337   uint8_t chan2_value;
0338   PIT_CHAN2_ENABLE(chan2_value);
0339   uint32_t pit_ticks = PIT_FREQUENCY/100; /* 10 miliseconds */
0340   PIT_CHAN2_WRITE_TICKS(pit_ticks);
0341 
0342   /* INIT IPI */
0343   send_ipi(lapic_id, LAPIC_ICR_DELIV_INIT | LAPIC_ICR_ASSERT | LAPIC_ICR_TRIG_LEVEL);
0344   wait_ipi();
0345   /* Deassert INIT IPI */
0346   send_ipi(lapic_id, LAPIC_ICR_DELIV_INIT  | LAPIC_ICR_TRIG_LEVEL);
0347   /* Wait 10ms */
0348   PIT_CHAN2_START_DELAY(chan2_value);
0349   PIT_CHAN2_WAIT_DELAY(pit_ticks);
0350 
0351   pit_ticks = PIT_FREQUENCY/5000; /* 200 microseconds */
0352   PIT_CHAN2_WRITE_TICKS(pit_ticks);
0353 
0354   /* STARTUP IPI */
0355   send_ipi(lapic_id, LAPIC_ICR_DELIV_START | page_vector);
0356   wait_ipi();
0357   /* Wait 200us */
0358   PIT_CHAN2_START_DELAY(chan2_value);
0359   PIT_CHAN2_WAIT_DELAY(pit_ticks);
0360   /**
0361    * It is possible that the first STARTUP IPI sent is ignored
0362    * so we send it twice.
0363    */
0364   send_ipi(lapic_id, LAPIC_ICR_DELIV_START | page_vector);
0365 }
0366 #endif