1cf707bd0SJens Wiklander // SPDX-License-Identifier: BSD-2-Clause 2cf707bd0SJens Wiklander /* 3cf707bd0SJens Wiklander * Copyright (c) 2024, Linaro Limited 4cf707bd0SJens Wiklander */ 5cf707bd0SJens Wiklander 6cf707bd0SJens Wiklander #include <kernel/callout.h> 7cf707bd0SJens Wiklander #include <kernel/misc.h> 8cf707bd0SJens Wiklander #include <kernel/spinlock.h> 9cf707bd0SJens Wiklander #include <mm/core_memprot.h> 10cf707bd0SJens Wiklander 11cf707bd0SJens Wiklander TAILQ_HEAD(callout_head, callout); 12cf707bd0SJens Wiklander 13cf707bd0SJens Wiklander static unsigned int callout_sched_lock __nex_data = SPINLOCK_UNLOCK; 14cf707bd0SJens Wiklander static size_t callout_sched_core __nex_bss; 15cf707bd0SJens Wiklander static unsigned int callout_lock __nex_data = SPINLOCK_UNLOCK; 16cf707bd0SJens Wiklander static const struct callout_timer_desc *callout_desc __nex_bss; 17cf707bd0SJens Wiklander static struct callout_head callout_head __nex_data = 18cf707bd0SJens Wiklander TAILQ_HEAD_INITIALIZER(callout_head); 19cf707bd0SJens Wiklander 20cf707bd0SJens Wiklander static void insert_callout(struct callout *co) 21cf707bd0SJens Wiklander { 22cf707bd0SJens Wiklander struct callout *co2 = NULL; 23cf707bd0SJens Wiklander 24cf707bd0SJens Wiklander TAILQ_FOREACH(co2, &callout_head, link) { 25cf707bd0SJens Wiklander if (co->expiry_value < co2->expiry_value) { 26cf707bd0SJens Wiklander TAILQ_INSERT_BEFORE(co2, co, link); 27cf707bd0SJens Wiklander return; 28cf707bd0SJens Wiklander } 29cf707bd0SJens Wiklander } 30cf707bd0SJens Wiklander 31cf707bd0SJens Wiklander TAILQ_INSERT_TAIL(&callout_head, co, link); 32cf707bd0SJens Wiklander } 33cf707bd0SJens Wiklander 34cf707bd0SJens Wiklander static void schedule_next_timeout(void) 35cf707bd0SJens Wiklander { 36cf707bd0SJens Wiklander const struct callout_timer_desc *desc = callout_desc; 37cf707bd0SJens Wiklander struct callout *co = TAILQ_FIRST(&callout_head); 38cf707bd0SJens Wiklander 39cf707bd0SJens Wiklander if (co) 40cf707bd0SJens Wiklander desc->set_next_timeout(desc, co->expiry_value); 41cf707bd0SJens Wiklander else 42cf707bd0SJens Wiklander desc->disable_timeout(desc); 43cf707bd0SJens Wiklander 44cf707bd0SJens Wiklander if (desc->is_per_cpu) { 45cf707bd0SJens Wiklander /* 46cf707bd0SJens Wiklander * Remember which core is supposed to receive the next 47cf707bd0SJens Wiklander * timer interrupt. This will not disable timers on other 48cf707bd0SJens Wiklander * CPUs, instead they will be ignored as a spurious call. 49cf707bd0SJens Wiklander */ 50cf707bd0SJens Wiklander cpu_spin_lock(&callout_sched_lock); 51cf707bd0SJens Wiklander callout_sched_core = get_core_pos(); 52cf707bd0SJens Wiklander cpu_spin_unlock(&callout_sched_lock); 53cf707bd0SJens Wiklander } 54cf707bd0SJens Wiklander } 55cf707bd0SJens Wiklander 56cf707bd0SJens Wiklander static bool callout_is_active(struct callout *co) 57cf707bd0SJens Wiklander { 58cf707bd0SJens Wiklander struct callout *co2 = NULL; 59cf707bd0SJens Wiklander 60cf707bd0SJens Wiklander TAILQ_FOREACH(co2, &callout_head, link) 61cf707bd0SJens Wiklander if (co2 == co) 62cf707bd0SJens Wiklander return true; 63cf707bd0SJens Wiklander 64cf707bd0SJens Wiklander return false; 65cf707bd0SJens Wiklander } 66cf707bd0SJens Wiklander 67cf707bd0SJens Wiklander void callout_rem(struct callout *co) 68cf707bd0SJens Wiklander { 69cf707bd0SJens Wiklander uint32_t state = 0; 70cf707bd0SJens Wiklander 71cf707bd0SJens Wiklander state = cpu_spin_lock_xsave(&callout_lock); 72cf707bd0SJens Wiklander 73cf707bd0SJens Wiklander if (callout_is_active(co)) { 74cf707bd0SJens Wiklander TAILQ_REMOVE(&callout_head, co, link); 75cf707bd0SJens Wiklander schedule_next_timeout(); 76cf707bd0SJens Wiklander } 77cf707bd0SJens Wiklander 78cf707bd0SJens Wiklander cpu_spin_unlock_xrestore(&callout_lock, state); 79cf707bd0SJens Wiklander } 80cf707bd0SJens Wiklander 81cf707bd0SJens Wiklander void callout_add(struct callout *co, bool (*callback)(struct callout *co), 82cf707bd0SJens Wiklander uint32_t ms) 83cf707bd0SJens Wiklander { 84cf707bd0SJens Wiklander const struct callout_timer_desc *desc = callout_desc; 85cf707bd0SJens Wiklander uint32_t state = 0; 86cf707bd0SJens Wiklander 87cf707bd0SJens Wiklander state = cpu_spin_lock_xsave(&callout_lock); 88cf707bd0SJens Wiklander 89*c5b5aca0SJens Wiklander assert(is_nexus(co) && !callout_is_active(co) && is_unpaged(callback)); 90cf707bd0SJens Wiklander *co = (struct callout){ .callback = callback, }; 91cf707bd0SJens Wiklander 92cf707bd0SJens Wiklander if (desc) { 93cf707bd0SJens Wiklander co->period = desc->ms_to_ticks(desc, ms); 94cf707bd0SJens Wiklander co->expiry_value = desc->get_now(desc) + co->period; 95cf707bd0SJens Wiklander } else { 96cf707bd0SJens Wiklander /* This will be converted to ticks in callout_service_init(). */ 97cf707bd0SJens Wiklander co->period = ms; 98cf707bd0SJens Wiklander } 99cf707bd0SJens Wiklander 100cf707bd0SJens Wiklander insert_callout(co); 101cf707bd0SJens Wiklander if (desc && co == TAILQ_FIRST(&callout_head)) 102cf707bd0SJens Wiklander schedule_next_timeout(); 103cf707bd0SJens Wiklander 104cf707bd0SJens Wiklander cpu_spin_unlock_xrestore(&callout_lock, state); 105cf707bd0SJens Wiklander } 106cf707bd0SJens Wiklander 107cf707bd0SJens Wiklander void callout_set_next_timeout(struct callout *co, uint32_t ms) 108cf707bd0SJens Wiklander { 109cf707bd0SJens Wiklander co->period = callout_desc->ms_to_ticks(callout_desc, ms); 110cf707bd0SJens Wiklander } 111cf707bd0SJens Wiklander 112cf707bd0SJens Wiklander void callout_service_init(const struct callout_timer_desc *desc) 113cf707bd0SJens Wiklander { 114cf707bd0SJens Wiklander struct callout_head tmp_head = TAILQ_HEAD_INITIALIZER(tmp_head); 115cf707bd0SJens Wiklander struct callout *co = NULL; 116cf707bd0SJens Wiklander uint32_t state = 0; 117cf707bd0SJens Wiklander uint64_t now = 0; 118cf707bd0SJens Wiklander 119cf707bd0SJens Wiklander state = cpu_spin_lock_xsave(&callout_lock); 120cf707bd0SJens Wiklander 121cf707bd0SJens Wiklander assert(!callout_desc); 122cf707bd0SJens Wiklander assert(is_nexus(desc) && is_unpaged(desc->disable_timeout) && 123cf707bd0SJens Wiklander is_unpaged(desc->set_next_timeout) && 124cf707bd0SJens Wiklander is_unpaged(desc->ms_to_ticks) && is_unpaged(desc->get_now)); 125cf707bd0SJens Wiklander 126cf707bd0SJens Wiklander callout_desc = desc; 127cf707bd0SJens Wiklander now = desc->get_now(desc); 128cf707bd0SJens Wiklander 129cf707bd0SJens Wiklander TAILQ_CONCAT(&tmp_head, &callout_head, link); 130cf707bd0SJens Wiklander while (!TAILQ_EMPTY(&tmp_head)) { 131cf707bd0SJens Wiklander co = TAILQ_FIRST(&tmp_head); 132cf707bd0SJens Wiklander TAILQ_REMOVE(&tmp_head, co, link); 133cf707bd0SJens Wiklander 134cf707bd0SJens Wiklander /* 135cf707bd0SJens Wiklander * Periods set before the timer descriptor are in 136cf707bd0SJens Wiklander * milliseconds since the frequency of the timer isn't 137cf707bd0SJens Wiklander * available at that point. So update it to ticks now. 138cf707bd0SJens Wiklander */ 139cf707bd0SJens Wiklander co->period = desc->ms_to_ticks(desc, co->period); 140cf707bd0SJens Wiklander co->expiry_value = now + co->period; 141cf707bd0SJens Wiklander insert_callout(co); 142cf707bd0SJens Wiklander } 143cf707bd0SJens Wiklander schedule_next_timeout(); 144cf707bd0SJens Wiklander 145cf707bd0SJens Wiklander cpu_spin_unlock_xrestore(&callout_lock, state); 146cf707bd0SJens Wiklander } 147cf707bd0SJens Wiklander 148cf707bd0SJens Wiklander void callout_service_cb(void) 149cf707bd0SJens Wiklander { 150cf707bd0SJens Wiklander const struct callout_timer_desc *desc = callout_desc; 151cf707bd0SJens Wiklander struct callout *co = NULL; 152cf707bd0SJens Wiklander uint64_t now = 0; 153cf707bd0SJens Wiklander 154cf707bd0SJens Wiklander if (desc->is_per_cpu) { 155cf707bd0SJens Wiklander bool do_callout = false; 156cf707bd0SJens Wiklander 157cf707bd0SJens Wiklander /* 158cf707bd0SJens Wiklander * schedule_next_timeout() saves the core it was last 159cf707bd0SJens Wiklander * called on. If there's a mismatch here it means that 160cf707bd0SJens Wiklander * another core has been scheduled for the next callout, so 161cf707bd0SJens Wiklander * there's no work to be done for this core. 162cf707bd0SJens Wiklander */ 163cf707bd0SJens Wiklander cpu_spin_lock(&callout_sched_lock); 164cf707bd0SJens Wiklander do_callout = (get_core_pos() == callout_sched_core); 165cf707bd0SJens Wiklander cpu_spin_unlock(&callout_sched_lock); 166cf707bd0SJens Wiklander if (!do_callout) 167cf707bd0SJens Wiklander return; 168cf707bd0SJens Wiklander } 169cf707bd0SJens Wiklander 170cf707bd0SJens Wiklander cpu_spin_lock(&callout_lock); 171cf707bd0SJens Wiklander 172cf707bd0SJens Wiklander now = desc->get_now(desc); 173cf707bd0SJens Wiklander while (!TAILQ_EMPTY(&callout_head)) { 174cf707bd0SJens Wiklander co = TAILQ_FIRST(&callout_head); 175cf707bd0SJens Wiklander if (co->expiry_value > now) 176cf707bd0SJens Wiklander break; 177cf707bd0SJens Wiklander 178cf707bd0SJens Wiklander TAILQ_REMOVE(&callout_head, co, link); 179cf707bd0SJens Wiklander 180cf707bd0SJens Wiklander if (co->callback(co)) { 181cf707bd0SJens Wiklander co->expiry_value += co->period; 182cf707bd0SJens Wiklander insert_callout(co); 183cf707bd0SJens Wiklander } 184cf707bd0SJens Wiklander } 185cf707bd0SJens Wiklander schedule_next_timeout(); 186cf707bd0SJens Wiklander 187cf707bd0SJens Wiklander cpu_spin_unlock(&callout_lock); 188cf707bd0SJens Wiklander } 189