/* * Copyright (c) 2019 Intel Corporation * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include BUILD_ASSERT(!IS_ENABLED(CONFIG_TICKLESS_KERNEL), "this is a tickfull driver"); /* * Overview: * * This driver enables the local APIC as the Zephyr system timer. It supports * legacy ("tickful") mode only. The driver will work with any APIC that has * the ARAT "always running APIC timer" feature (CPUID 0x06, EAX bit 2). * * Configuration: * * CONFIG_APIC_TIMER=y enables this timer driver. * CONFIG_APIC_TIMER_IRQ= which IRQ to configure for the timer. * CONFIG_APIC_TIMER_IRQ_PRIORITY=

priority for IRQ_CONNECT() * * CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC= must contain the frequency seen * by the local APIC timer block (before it gets to the timer divider). */ /* These should be merged into include/drivers/interrupt_controller/loapic.h. */ #define DCR_DIVIDER_MASK 0x0000000F /* divider bits */ #define DCR_DIVIDER 0x0000000B /* divide by 1 */ #define LVT_MODE_MASK 0x00060000 /* timer mode bits */ #define LVT_MODE 0x00020000 /* periodic mode */ #if defined(CONFIG_TEST) const int32_t z_sys_timer_irq_for_test = CONFIG_APIC_TIMER_IRQ; #endif #define CYCLES_PER_TICK \ (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / CONFIG_SYS_CLOCK_TICKS_PER_SEC) BUILD_ASSERT(CYCLES_PER_TICK >= 1, "APIC timer: bad CYCLES_PER_TICK"); static volatile uint64_t total_cycles; static void isr(const void *arg) { ARG_UNUSED(arg); total_cycles += CYCLES_PER_TICK; sys_clock_announce(1); } uint32_t sys_clock_elapsed(void) { return 0U; } uint64_t sys_clock_cycle_get_64(void) { uint32_t ccr_1st, ccr_2nd; uint64_t cycles; /* * We may race with CCR reaching 0 and reloading, and the interrupt * handler updating total_cycles. Let's make sure we sample everything * away from this roll-over transition by ensuring consecutive CCR * values are descending so we're sure the enclosed (volatile) * total_cycles sample and CCR value are coherent with each other. */ do { ccr_1st = x86_read_loapic(LOAPIC_TIMER_CCR); cycles = total_cycles; ccr_2nd = x86_read_loapic(LOAPIC_TIMER_CCR); } while (ccr_2nd > ccr_1st); return cycles + (CYCLES_PER_TICK - ccr_2nd); } uint32_t sys_clock_cycle_get_32(void) { return (uint32_t)sys_clock_cycle_get_64(); } static int sys_clock_driver_init(void) { uint32_t val; val = x86_read_loapic(LOAPIC_TIMER_CONFIG); /* set divider */ val &= ~DCR_DIVIDER_MASK; val |= DCR_DIVIDER; x86_write_loapic(LOAPIC_TIMER_CONFIG, val); val = x86_read_loapic(LOAPIC_TIMER); /* set timer mode */ val &= ~LVT_MODE_MASK; val |= LVT_MODE; x86_write_loapic(LOAPIC_TIMER, val); /* remember, wiring up the interrupt will mess with the LVT, too */ IRQ_CONNECT(CONFIG_APIC_TIMER_IRQ, CONFIG_APIC_TIMER_IRQ_PRIORITY, isr, 0, 0); x86_write_loapic(LOAPIC_TIMER_ICR, CYCLES_PER_TICK); irq_enable(CONFIG_APIC_TIMER_IRQ); return 0; } SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);