/* * Copyright (c) 2011-2015 Wind River Systems, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file * @brief Intel Local APIC timer driver * * This module implements a kernel device driver for the Intel local APIC * timer device. It provides the standard "system clock driver" interfaces for * use with P6 (PentiumPro, II, III) and P7 (Pentium4) family processors. * The local APIC timer contains a 32-bit programmable down counter that * generates an interrupt for use by the local processor when it reaches zero. * The time base is derived from the processor's bus clock, divided by a value * specified in the divide configuration register. After reset, the timer is * initialized to zero. * * Typically, the local APIC timer operates in periodic mode. That is, after * its down counter reaches zero and triggers a timer interrupt, it is reset * to its initial value and the down counting continues. * * If the TICKLESS_IDLE kernel configuration option is enabled, the timer may * be programmed to wake the system in N >= TICKLESS_IDLE_THRESH ticks. The * kernel invokes _timer_idle_enter() to program the down counter in one-shot * mode to trigger an interrupt in N ticks. When the timer expires or when * another interrupt is detected, the kernel's interrupt stub invokes * _timer_idle_exit() to leave the tickless idle state. * * @internal * Factors that increase the driver's complexity: * * 1. As the down-counter is a 32-bit value, the number of ticks for which the * system can be in tickless idle is limited to 'max_system_ticks'; This * corresponds to 'cycles_per_max_ticks' (as the timer is programmed in cycles). * * 2. When the request to enter tickless arrives, any remaining cycles until * the next tick must be accounted for to maintain accuracy. * * 3. The act of entering tickless idle may potentially straddle a tick * boundary. Thus the number of remaining cycles to the next tick read from * the down counter is suspect as it could occur before or after the tick * boundary (thus before or after the counter is reset). If the tick is * straddled, the following will occur: * a. Enter tickless idle in one-shot mode * b. Immediately leave tickless idle * c. Process the tick event in the _timer_int_handler() and revert * to periodic mode. * d. Re-run the scheduler and possibly re-enter tickless idle * * 4. Tickless idle may be prematurely aborted due to a straddled tick. See * previous factor. * * 5. Tickless idle may be prematurely aborted due to a non-timer interrupt. * Its handler may make a task or fiber ready to run, so any elapsed ticks * must be accounted for and the timer must also expire at the end of the * next logical tick so _timer_int_handler() can put it back in periodic mode. * This can only be distinguished from the previous factor by the executiion of * _timer_int_handler(). * * 6. Tickless idle may end naturally. The down counter should be zero in * this case. However, some targets do not implement the local APIC timer * correctly and the down-counter continues to decrement. * @endinternal */ #include #include #include #include #include #include #include #include #include /* Local APIC Timer Bits */ #define LOAPIC_TIMER_DIVBY_2 0x0 /* Divide by 2 */ #define LOAPIC_TIMER_DIVBY_4 0x1 /* Divide by 4 */ #define LOAPIC_TIMER_DIVBY_8 0x2 /* Divide by 8 */ #define LOAPIC_TIMER_DIVBY_16 0x3 /* Divide by 16 */ #define LOAPIC_TIMER_DIVBY_32 0x8 /* Divide by 32 */ #define LOAPIC_TIMER_DIVBY_64 0x9 /* Divide by 64 */ #define LOAPIC_TIMER_DIVBY_128 0xa /* Divide by 128 */ #define LOAPIC_TIMER_DIVBY_1 0xb /* Divide by 1 */ #define LOAPIC_TIMER_DIVBY_MASK 0xf /* mask bits */ #define LOAPIC_TIMER_PERIODIC 0x00020000 /* Timer Mode: Periodic */ /* Helpful macros and inlines for programming timer. * We support both standard LOAPIC, and MVIC which has a similar * interface */ #if defined(CONFIG_LOAPIC) #define _REG_TIMER ((volatile uint32_t *) \ (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER)) #define _REG_TIMER_ICR ((volatile uint32_t *) \ (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER_ICR)) #define _REG_TIMER_CCR ((volatile uint32_t *) \ (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER_CCR)) #define _REG_TIMER_CFG ((volatile uint32_t *) \ (CONFIG_LOAPIC_BASE_ADDRESS + LOAPIC_TIMER_CONFIG)) #define TIMER_IRQ CONFIG_LOAPIC_TIMER_IRQ #define TIMER_IRQ_PRIORITY CONFIG_LOAPIC_TIMER_IRQ_PRIORITY #elif defined(CONFIG_MVIC) #define _REG_TIMER ((volatile uint32_t *)MVIC_LVTTIMER) #define _REG_TIMER_ICR ((volatile uint32_t *)MVIC_ICR) #define _REG_TIMER_CCR ((volatile uint32_t *)MVIC_CCR) /* MVIC has no TIMER_CFG register */ #define TIMER_IRQ CONFIG_MVIC_TIMER_IRQ #define TIMER_IRQ_PRIORITY -1 #endif #if defined(CONFIG_TICKLESS_IDLE) #define TIMER_MODE_ONE_SHOT 0 #define TIMER_MODE_PERIODIC 1 #else /* !CONFIG_TICKLESS_IDLE */ #define tickless_idle_init() \ do {/* nothing */ \ } while (0) #endif /* !CONFIG_TICKLESS_IDLE */ #if defined(CONFIG_TICKLESS_IDLE) extern int32_t _sys_idle_elapsed_ticks; #endif /* CONFIG_TICKLESS_IDLE */ /* computed counter 0 initial count value */ static uint32_t __noinit cycles_per_tick; static uint32_t accumulated_cycle_count; #if defined(CONFIG_TICKLESS_IDLE) static uint32_t programmed_cycles; static uint32_t programmed_full_ticks; static uint32_t __noinit max_system_ticks; static uint32_t __noinit cycles_per_max_ticks; static bool timer_known_to_have_expired; static unsigned char timer_mode = TIMER_MODE_PERIODIC; #endif /* CONFIG_TICKLESS_IDLE */ #ifdef CONFIG_DEVICE_POWER_MANAGEMENT static uint32_t loapic_timer_device_power_state; static uint32_t reg_timer_save; #ifndef CONFIG_MVIC static uint32_t reg_timer_cfg_save; #endif #endif /** * * @brief Set the timer for periodic mode * * This routine sets the timer for periodic mode. * * @return N/A */ static inline void periodic_mode_set(void) { *_REG_TIMER |= LOAPIC_TIMER_PERIODIC; } /** * * @brief Set the initial count register * * This routine sets value from which the timer will count down. * Note that setting the value to zero stops the timer. * * @param count Count from which timer is to count down * @return N/A */ static inline void initial_count_register_set(uint32_t count) { *_REG_TIMER_ICR = count; } #if defined(CONFIG_TICKLESS_IDLE) /** * * @brief Set the timer for one shot mode * * This routine sets the timer for one shot mode. * * @return N/A */ static inline void one_shot_mode_set(void) { *_REG_TIMER &= ~LOAPIC_TIMER_PERIODIC; } #endif /* CONFIG_TICKLESS_IDLE */ /** * * @brief Set the rate at which the timer is decremented * * This routine sets rate at which the timer is decremented to match the * external bus frequency. * This is not supported with MVIC, only real LOAPIC. * * @return N/A */ #ifndef CONFIG_MVIC static inline void divide_configuration_register_set(void) { *_REG_TIMER_CFG = (*_REG_TIMER_CFG & ~0xf) | LOAPIC_TIMER_DIVBY_1; } #endif /** * * @brief Get the value from the current count register * * This routine gets the value from the timer's current count register. This * value is the 'time' remaining to decrement before the timer triggers an * interrupt. * * @return N/A */ static inline uint32_t current_count_register_get(void) { return *_REG_TIMER_CCR; } #if defined(CONFIG_TICKLESS_IDLE) /** * * @brief Get the value from the initial count register * * This routine gets the value from the initial count register. * * @return N/A */ static inline uint32_t initial_count_register_get(void) { return *_REG_TIMER_ICR; } #endif /* CONFIG_TICKLESS_IDLE */ /** * * @brief System clock tick handler * * This routine handles the system clock tick interrupt. A TICK_EVENT event * is pushed onto the microkernel stack. * * @return N/A */ void _timer_int_handler(void *unused /* parameter is not used */ ) { ARG_UNUSED(unused); #ifdef CONFIG_TICKLESS_IDLE if (timer_mode == TIMER_MODE_ONE_SHOT) { if (!timer_known_to_have_expired) { uint32_t cycles; /* * The timer fired unexpectedly. This is due to one of two cases: * 1. Entering tickless idle straddled a tick. * 2. Leaving tickless idle straddled the final tick. * Due to the timer reprogramming in _timer_idle_exit(), case #2 * can be handled as a fall-through. * * NOTE: Although the cycle count is supposed to stop decrementing * once it hits zero in one-shot mode, not all targets implement * this properly (and continue to decrement). Thus, we have to * perform a second comparison to check for wrap-around. */ cycles = current_count_register_get(); if ((cycles > 0) && (cycles < programmed_cycles)) { /* Case 1 */ _sys_idle_elapsed_ticks = 0; } } /* Return the timer to periodic mode */ initial_count_register_set(cycles_per_tick - 1); periodic_mode_set(); timer_known_to_have_expired = false; timer_mode = TIMER_MODE_PERIODIC; } _sys_clock_final_tick_announce(); /* track the accumulated cycle count */ accumulated_cycle_count += cycles_per_tick * _sys_idle_elapsed_ticks; #else /* track the accumulated cycle count */ accumulated_cycle_count += cycles_per_tick; _sys_clock_tick_announce(); #endif /*CONFIG_TICKLESS_IDLE*/ } #if defined(CONFIG_TICKLESS_IDLE) /** * * @brief Initialize the tickless idle feature * * This routine initializes the tickless idle feature. Note that the maximum * number of ticks that can elapse during a "tickless idle" is limited by * . The larger the value (the lower the tick frequency), * the fewer elapsed ticks during a "tickless idle". Conversely, the smaller * the value (the higher the tick frequency), the more elapsed ticks during a * "tickless idle". * * @return N/A */ static void tickless_idle_init(void) { /* * Calculate the maximum number of system ticks less one. This * guarantees that an overflow will not occur when any remaining * cycles are added to when calculating * . */ max_system_ticks = (0xffffffff / cycles_per_tick) - 1; cycles_per_max_ticks = max_system_ticks * cycles_per_tick; } /** * * @brief Place system timer into idle state * * Re-program the timer to enter into the idle state for the given number of * ticks. It is placed into one shot mode where it will fire in the number of * ticks supplied or the maximum number of ticks that can be programmed into * hardware. A value of -1 means inifinite number of ticks. * * @return N/A */ void _timer_idle_enter(int32_t ticks /* system ticks */ ) { uint32_t cycles; /* * Although interrupts are disabled, the LOAPIC timer is still counting * down. Take a snapshot of current count register to get the number of * cycles remaining in the timer before it signals an interrupt and apply * that towards the one-shot calculation to maintain accuracy. * * NOTE: If entering tickless idle straddles a tick, 'programmed_cycles' * and 'programmmed_full_ticks' may be incorrect as we do not know which * side of the tick the snapshot occurred. This is not a problem as the * values will be corrected once the straddling is detected. */ cycles = current_count_register_get(); if ((ticks == TICKS_UNLIMITED) || (ticks > max_system_ticks)) { /* * The number of cycles until the timer must fire next might not fit * in the 32-bit counter register. To work around this, program * the counter to fire in the maximum number of ticks (plus any * remaining cycles). */ programmed_full_ticks = max_system_ticks; programmed_cycles = cycles + cycles_per_max_ticks; } else { programmed_full_ticks = ticks - 1; programmed_cycles = cycles + (programmed_full_ticks * cycles_per_tick); } /* Set timer to one-shot mode */ initial_count_register_set(programmed_cycles); one_shot_mode_set(); timer_mode = TIMER_MODE_ONE_SHOT; } /** * * @brief Handling of tickless idle when interrupted * * The routine is responsible for taking the timer out of idle mode and * generating an interrupt at the next tick interval. * * Note that in this routine, _sys_idle_elapsed_ticks must be zero because the * ticker has done its work and consumed all the ticks. This has to be true * otherwise idle mode wouldn't have been entered in the first place. * * @return N/A */ void _timer_idle_exit(void) { uint32_t remaining_cycles; uint32_t remaining_full_ticks; /* * Interrupts are locked and idling has ceased. The cause of the cessation * is unknown. It may be due to one of three cases. * 1. The timer, which was previously placed into one-shot mode has * counted down to zero and signaled an interrupt. * 2. A non-timer interrupt occurred. Note that the LOAPIC timer will * still continue to decrement and may yet signal an interrupt. * 3. The LOAPIC timer signaled an interrupt while the timer was being * programmed for one-shot mode. * * NOTE: Although the cycle count is supposed to stop decrementing once it * hits zero in one-shot mode, not all targets implement this properly * (and continue to decrement). Thus a second comparison is required to * check for wrap-around. */ remaining_cycles = current_count_register_get(); if ((remaining_cycles == 0) || (remaining_cycles >= programmed_cycles)) { /* * The timer has expired. The handler _timer_int_handler() is * guaranteed to execute. Track the number of elapsed ticks. The * handler _timer_int_handler() will account for the final tick. */ _sys_idle_elapsed_ticks = programmed_full_ticks; /* * Announce elapsed ticks to the microkernel. Note we are guaranteed * that the timer ISR will execute before the tick event is serviced. * (The timer ISR reprograms the timer for the next tick.) */ _sys_clock_tick_announce(); timer_known_to_have_expired = true; return; } timer_known_to_have_expired = false; /* * Either a non-timer interrupt occurred, or we straddled a tick when * entering tickless idle. It is impossible to determine which occurred * at this point. Regardless of the cause, ensure that the timer will * expire at the end of the next tick in case the ISR makes any tasks * and/or fibers ready to run. * * NOTE #1: In the case of a straddled tick, the '_sys_idle_elapsed_ticks' * calculation below may result in either 0 or 1. If 1, then this may * result in a harmless extra call to _sys_clock_tick_announce(). * * NOTE #2: In the case of a straddled tick, it is assumed that when the * timer is reprogrammed, it will be reprogrammed with a cycle count * sufficiently close to one tick that the timer will not expire before * _timer_int_handler() is executed. */ remaining_full_ticks = remaining_cycles / cycles_per_tick; _sys_idle_elapsed_ticks = programmed_full_ticks - remaining_full_ticks; if (_sys_idle_elapsed_ticks > 0) { _sys_clock_tick_announce(); } if (remaining_full_ticks > 0) { /* * Re-program the timer (still in one-shot mode) to fire at the end of * the tick, being careful to not program zero thus stopping the timer. */ programmed_cycles = 1 + ((remaining_cycles - 1) % cycles_per_tick); initial_count_register_set(programmed_cycles); } } #endif /* CONFIG_TICKLESS_IDLE */ /** * * @brief Initialize and enable the system clock * * This routine is used to program the timer to deliver interrupts at the * rate specified via the 'sys_clock_us_per_tick' global variable. * * @return 0 */ int _sys_clock_driver_init(struct device *device) { ARG_UNUSED(device); /* determine the timer counter value (in timer clock cycles/system tick) */ cycles_per_tick = sys_clock_hw_cycles_per_tick; tickless_idle_init(); #ifndef CONFIG_MVIC divide_configuration_register_set(); #endif initial_count_register_set(cycles_per_tick - 1); periodic_mode_set(); #ifdef CONFIG_DEVICE_POWER_MANAGEMENT loapic_timer_device_power_state = DEVICE_PM_ACTIVE_STATE; #endif IRQ_CONNECT(TIMER_IRQ, TIMER_IRQ_PRIORITY, _timer_int_handler, 0, 0); /* Everything has been configured. It is now safe to enable the * interrupt */ irq_enable(TIMER_IRQ); return 0; } #ifdef CONFIG_DEVICE_POWER_MANAGEMENT static int sys_clock_suspend(struct device *dev) { ARG_UNUSED(dev); reg_timer_save = *_REG_TIMER; #ifndef CONFIG_MVIC reg_timer_cfg_save = *_REG_TIMER_CFG; #endif loapic_timer_device_power_state = DEVICE_PM_SUSPEND_STATE; return 0; } static int sys_clock_resume(struct device *dev) { ARG_UNUSED(dev); *_REG_TIMER = reg_timer_save; #ifndef CONFIG_MVIC *_REG_TIMER_CFG = reg_timer_cfg_save; #endif /* * It is difficult to accurately know the time spent in DS. * We can use TSC or RTC but that will create a dependency * on those components. Other issue is about what to do * with pending timers. Following are some options :- * * 1) Expire all timers based on time spent found using some * source like TSC * 2) Expire all timers anyway * 3) Expire only the timer at the top * 4) Contine from where the timer left * * 1 and 2 require change to how timers are handled. 4 may not * give a good user experience. After waiting for a long period * in DS, the system would appear dead if it waits again. * * Current implementation uses option 3. The top most timer is * expired. Following code will set the counter to a low number * so it would immediately expire and generate timer interrupt * which will process the top most timer. Note that timer IC * cannot be set to 0. Setting it to 0 will stop the timer. */ initial_count_register_set(1); loapic_timer_device_power_state = DEVICE_PM_ACTIVE_STATE; return 0; } /* * Implements the driver control management functionality * the *context may include IN data or/and OUT data */ int sys_clock_device_ctrl(struct device *port, uint32_t ctrl_command, void *context) { if (ctrl_command == DEVICE_PM_SET_POWER_STATE) { if (*((uint32_t *)context) == DEVICE_PM_SUSPEND_STATE) { return sys_clock_suspend(port); } else if (*((uint32_t *)context) == DEVICE_PM_ACTIVE_STATE) { return sys_clock_resume(port); } } else if (ctrl_command == DEVICE_PM_GET_POWER_STATE) { *((uint32_t *)context) = loapic_timer_device_power_state; return 0; } return 0; } #endif /** * * @brief Read the platform's timer hardware * * This routine returns the current time in terms of timer hardware clock * cycles. * * @return up counter of elapsed clock cycles */ uint32_t k_cycle_get_32(void) { uint32_t val; /* system clock value */ /* * The LOAPIC timer counter is a down counter. Thus to get the number * of elapsed cycles since 'accumlated_cycle_count' was last updated, * subtract the value in the Current Count Register (CCR) from the value * in the Initial Count Register (ICR). */ #if !defined(CONFIG_TICKLESS_IDLE) /* The value in the ICR always matches cycles_per_tick. */ val = accumulated_cycle_count - current_count_register_get() + cycles_per_tick; #else /* The value in the ICR may vary. Read from the register. */ val = accumulated_cycle_count - current_count_register_get() + initial_count_register_get(); #endif return val; } #if defined(CONFIG_SYSTEM_CLOCK_DISABLE) /** * * @brief Stop announcing ticks into the kernel * * This routine simply disables the LOAPIC counter such that interrupts are no * longer delivered. * * @return N/A */ void sys_clock_disable(void) { unsigned int key; /* interrupt lock level */ key = irq_lock(); irq_disable(TIMER_IRQ); initial_count_register_set(0); irq_unlock(key); } #endif /* CONFIG_SYSTEM_CLOCK_DISABLE */