Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2020 Intel Corporation |
| 3 | * |
| 4 | * SPDX-License-Identifier: Apache-2.0 |
| 5 | */ |
Gerard Marull-Paretas | cbd31d7 | 2022-05-06 11:23:05 +0200 | [diff] [blame] | 6 | #include <zephyr/logging/log.h> |
| 7 | #include <zephyr/sys/p4wq.h> |
Gerard Marull-Paretas | cbd31d7 | 2022-05-06 11:23:05 +0200 | [diff] [blame] | 8 | #include <zephyr/kernel.h> |
Gerard Marull-Paretas | cbd31d7 | 2022-05-06 11:23:05 +0200 | [diff] [blame] | 9 | #include <zephyr/init.h> |
Gerard Marull-Paretas | dacb3db | 2023-05-15 15:50:28 +0200 | [diff] [blame] | 10 | #include <zephyr/sys/iterable_sections.h> |
Anas Nashif | 8634c3b | 2023-08-29 17:03:12 +0000 | [diff] [blame] | 11 | /* private kernel APIs */ |
| 12 | #include <ksched.h> |
| 13 | #include <wait_q.h> |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 14 | |
Anas Nashif | e5471a2 | 2022-05-17 16:58:17 -0400 | [diff] [blame] | 15 | LOG_MODULE_REGISTER(p4wq, CONFIG_LOG_DEFAULT_LEVEL); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 16 | |
| 17 | struct device; |
| 18 | |
| 19 | static void set_prio(struct k_thread *th, struct k_p4wq_work *item) |
| 20 | { |
| 21 | __ASSERT_NO_MSG(!IS_ENABLED(CONFIG_SMP) || !z_is_thread_queued(th)); |
| 22 | th->base.prio = item->priority; |
| 23 | th->base.prio_deadline = item->deadline; |
| 24 | } |
| 25 | |
| 26 | static bool rb_lessthan(struct rbnode *a, struct rbnode *b) |
| 27 | { |
| 28 | struct k_p4wq_work *aw = CONTAINER_OF(a, struct k_p4wq_work, rbnode); |
| 29 | struct k_p4wq_work *bw = CONTAINER_OF(b, struct k_p4wq_work, rbnode); |
| 30 | |
| 31 | if (aw->priority != bw->priority) { |
| 32 | return aw->priority > bw->priority; |
| 33 | } |
| 34 | |
| 35 | if (aw->deadline != bw->deadline) { |
| 36 | return aw->deadline - bw->deadline > 0; |
| 37 | } |
| 38 | |
| 39 | return (uintptr_t)a < (uintptr_t)b; |
| 40 | } |
| 41 | |
Andy Ross | 9a594a0 | 2021-02-10 14:54:21 -0800 | [diff] [blame] | 42 | static void thread_set_requeued(struct k_thread *th) |
| 43 | { |
| 44 | th->base.user_options |= K_CALLBACK_STATE; |
| 45 | } |
| 46 | |
| 47 | static void thread_clear_requeued(struct k_thread *th) |
| 48 | { |
| 49 | th->base.user_options &= ~K_CALLBACK_STATE; |
| 50 | } |
| 51 | |
| 52 | static bool thread_was_requeued(struct k_thread *th) |
| 53 | { |
| 54 | return !!(th->base.user_options & K_CALLBACK_STATE); |
| 55 | } |
| 56 | |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 57 | /* Slightly different semantics: rb_lessthan must be perfectly |
| 58 | * symmetric (to produce a single tree structure) and will use the |
| 59 | * pointer value to break ties where priorities are equal, here we |
| 60 | * tolerate equality as meaning "not lessthan" |
| 61 | */ |
| 62 | static inline bool item_lessthan(struct k_p4wq_work *a, struct k_p4wq_work *b) |
| 63 | { |
| 64 | if (a->priority > b->priority) { |
| 65 | return true; |
| 66 | } else if ((a->priority == b->priority) && |
| 67 | (a->deadline != b->deadline)) { |
| 68 | return a->deadline - b->deadline > 0; |
Jennifer Williams | be5a377 | 2021-04-28 10:56:06 -0700 | [diff] [blame] | 69 | } else { |
| 70 | ; |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 71 | } |
| 72 | return false; |
| 73 | } |
| 74 | |
| 75 | static FUNC_NORETURN void p4wq_loop(void *p0, void *p1, void *p2) |
| 76 | { |
| 77 | ARG_UNUSED(p1); |
| 78 | ARG_UNUSED(p2); |
| 79 | struct k_p4wq *queue = p0; |
| 80 | k_spinlock_key_t k = k_spin_lock(&queue->lock); |
| 81 | |
| 82 | while (true) { |
| 83 | struct rbnode *r = rb_get_max(&queue->queue); |
| 84 | |
| 85 | if (r) { |
| 86 | struct k_p4wq_work *w |
| 87 | = CONTAINER_OF(r, struct k_p4wq_work, rbnode); |
| 88 | |
| 89 | rb_remove(&queue->queue, r); |
| 90 | w->thread = _current; |
| 91 | sys_dlist_append(&queue->active, &w->dlnode); |
| 92 | set_prio(_current, w); |
Andy Ross | 9a594a0 | 2021-02-10 14:54:21 -0800 | [diff] [blame] | 93 | thread_clear_requeued(_current); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 94 | |
| 95 | k_spin_unlock(&queue->lock, k); |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 96 | |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 97 | w->handler(w); |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 98 | |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 99 | k = k_spin_lock(&queue->lock); |
| 100 | |
| 101 | /* Remove from the active list only if it |
| 102 | * wasn't resubmitted already |
| 103 | */ |
Andy Ross | 9a594a0 | 2021-02-10 14:54:21 -0800 | [diff] [blame] | 104 | if (!thread_was_requeued(_current)) { |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 105 | sys_dlist_remove(&w->dlnode); |
| 106 | w->thread = NULL; |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 107 | k_sem_give(&w->done_sem); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 108 | } |
| 109 | } else { |
| 110 | z_pend_curr(&queue->lock, k, &queue->waitq, K_FOREVER); |
| 111 | k = k_spin_lock(&queue->lock); |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 116 | /* Must be called to regain ownership of the work item */ |
| 117 | int k_p4wq_wait(struct k_p4wq_work *work, k_timeout_t timeout) |
| 118 | { |
Maksim Masalski | 3960d41 | 2021-06-02 15:36:42 +0800 | [diff] [blame] | 119 | if (work->sync) { |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 120 | return k_sem_take(&work->done_sem, timeout); |
Maksim Masalski | 3960d41 | 2021-06-02 15:36:42 +0800 | [diff] [blame] | 121 | } |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 122 | |
| 123 | return k_sem_count_get(&work->done_sem) ? 0 : -EBUSY; |
| 124 | } |
| 125 | |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 126 | void k_p4wq_init(struct k_p4wq *queue) |
| 127 | { |
| 128 | memset(queue, 0, sizeof(*queue)); |
| 129 | z_waitq_init(&queue->waitq); |
| 130 | queue->queue.lessthan_fn = rb_lessthan; |
| 131 | sys_dlist_init(&queue->active); |
| 132 | } |
| 133 | |
| 134 | void k_p4wq_add_thread(struct k_p4wq *queue, struct k_thread *thread, |
| 135 | k_thread_stack_t *stack, |
| 136 | size_t stack_size) |
| 137 | { |
| 138 | k_thread_create(thread, stack, stack_size, |
| 139 | p4wq_loop, queue, NULL, NULL, |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 140 | K_HIGHEST_THREAD_PRIO, 0, |
| 141 | queue->flags & K_P4WQ_DELAYED_START ? K_FOREVER : K_NO_WAIT); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 142 | } |
| 143 | |
Gerard Marull-Paretas | a5fd0d1 | 2022-10-19 09:33:44 +0200 | [diff] [blame] | 144 | static int static_init(void) |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 145 | { |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 146 | |
Fabio Baltieri | f88a420 | 2021-08-04 23:05:54 +0100 | [diff] [blame] | 147 | STRUCT_SECTION_FOREACH(k_p4wq_initparam, pp) { |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 148 | for (int i = 0; i < pp->num; i++) { |
| 149 | uintptr_t ssz = K_THREAD_STACK_LEN(pp->stack_size); |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 150 | struct k_p4wq *q = pp->flags & K_P4WQ_QUEUE_PER_THREAD ? |
| 151 | pp->queue + i : pp->queue; |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 152 | |
Maksim Masalski | 3960d41 | 2021-06-02 15:36:42 +0800 | [diff] [blame] | 153 | if (!i || (pp->flags & K_P4WQ_QUEUE_PER_THREAD)) { |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 154 | k_p4wq_init(q); |
Maksim Masalski | 3960d41 | 2021-06-02 15:36:42 +0800 | [diff] [blame] | 155 | } |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 156 | |
| 157 | q->flags = pp->flags; |
| 158 | |
| 159 | /* |
| 160 | * If the user wants to specify CPU affinity, we have to |
| 161 | * delay starting threads until that has been done |
| 162 | */ |
Maksim Masalski | 3960d41 | 2021-06-02 15:36:42 +0800 | [diff] [blame] | 163 | if (q->flags & K_P4WQ_USER_CPU_MASK) { |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 164 | q->flags |= K_P4WQ_DELAYED_START; |
Maksim Masalski | 3960d41 | 2021-06-02 15:36:42 +0800 | [diff] [blame] | 165 | } |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 166 | |
| 167 | k_p4wq_add_thread(q, &pp->threads[i], |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 168 | &pp->stacks[ssz * i], |
| 169 | pp->stack_size); |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 170 | |
Maksim Masalski | 3960d41 | 2021-06-02 15:36:42 +0800 | [diff] [blame] | 171 | if (pp->flags & K_P4WQ_DELAYED_START) { |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 172 | z_mark_thread_as_suspended(&pp->threads[i]); |
Maksim Masalski | 3960d41 | 2021-06-02 15:36:42 +0800 | [diff] [blame] | 173 | } |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 174 | |
| 175 | #ifdef CONFIG_SCHED_CPU_MASK |
| 176 | if (pp->flags & K_P4WQ_USER_CPU_MASK) { |
| 177 | int ret = k_thread_cpu_mask_clear(&pp->threads[i]); |
| 178 | |
| 179 | if (ret < 0) |
| 180 | LOG_ERR("Couldn't clear CPU mask: %d", ret); |
| 181 | } |
| 182 | #endif |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 183 | } |
| 184 | } |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 185 | |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 186 | return 0; |
| 187 | } |
| 188 | |
Guennadi Liakhovetski | 91d90df | 2021-04-07 15:34:30 +0200 | [diff] [blame] | 189 | void k_p4wq_enable_static_thread(struct k_p4wq *queue, struct k_thread *thread, |
| 190 | uint32_t cpu_mask) |
| 191 | { |
| 192 | #ifdef CONFIG_SCHED_CPU_MASK |
| 193 | if (queue->flags & K_P4WQ_USER_CPU_MASK) { |
| 194 | unsigned int i; |
| 195 | |
| 196 | while ((i = find_lsb_set(cpu_mask))) { |
| 197 | int ret = k_thread_cpu_mask_enable(thread, i - 1); |
| 198 | |
| 199 | if (ret < 0) |
| 200 | LOG_ERR("Couldn't set CPU mask for %u: %d", i, ret); |
| 201 | cpu_mask &= ~BIT(i - 1); |
| 202 | } |
| 203 | } |
| 204 | #endif |
| 205 | |
| 206 | if (queue->flags & K_P4WQ_DELAYED_START) { |
| 207 | z_mark_thread_as_not_suspended(thread); |
| 208 | k_thread_start(thread); |
| 209 | } |
| 210 | } |
| 211 | |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 212 | /* We spawn a bunch of high priority threads, use the "SMP" initlevel |
| 213 | * so they can initialize in parallel instead of serially on the main |
| 214 | * CPU. |
| 215 | */ |
Guennadi Liakhovetski | a4b8819 | 2021-04-07 14:43:07 +0200 | [diff] [blame] | 216 | SYS_INIT(static_init, APPLICATION, 99); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 217 | |
| 218 | void k_p4wq_submit(struct k_p4wq *queue, struct k_p4wq_work *item) |
| 219 | { |
| 220 | k_spinlock_key_t k = k_spin_lock(&queue->lock); |
| 221 | |
| 222 | /* Input is a delta time from now (to match |
| 223 | * k_thread_deadline_set()), but we store and use the absolute |
| 224 | * cycle count. |
| 225 | */ |
| 226 | item->deadline += k_cycle_get_32(); |
| 227 | |
| 228 | /* Resubmission from within handler? Remove from active list */ |
| 229 | if (item->thread == _current) { |
| 230 | sys_dlist_remove(&item->dlnode); |
Andy Ross | 9a594a0 | 2021-02-10 14:54:21 -0800 | [diff] [blame] | 231 | thread_set_requeued(_current); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 232 | item->thread = NULL; |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 233 | } else { |
| 234 | k_sem_init(&item->done_sem, 0, 1); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 235 | } |
| 236 | __ASSERT_NO_MSG(item->thread == NULL); |
| 237 | |
| 238 | rb_insert(&queue->queue, &item->rbnode); |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 239 | item->queue = queue; |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 240 | |
| 241 | /* If there were other items already ahead of it in the queue, |
| 242 | * then we don't need to revisit active thread state and can |
| 243 | * return. |
| 244 | */ |
| 245 | if (rb_get_max(&queue->queue) != &item->rbnode) { |
| 246 | goto out; |
| 247 | } |
| 248 | |
| 249 | /* Check the list of active (running or preempted) items, if |
| 250 | * there are at least an "active target" of those that are |
| 251 | * higher priority than the new item, then no one needs to be |
| 252 | * preempted and we can return. |
| 253 | */ |
| 254 | struct k_p4wq_work *wi; |
Kumar Gala | 9be0914 | 2022-10-26 14:31:14 -0500 | [diff] [blame] | 255 | uint32_t n_beaten_by = 0, active_target = arch_num_cpus(); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 256 | |
| 257 | SYS_DLIST_FOR_EACH_CONTAINER(&queue->active, wi, dlnode) { |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 258 | /* |
| 259 | * item_lessthan(a, b) == true means a has lower priority than b |
| 260 | * !item_lessthan(a, b) counts all work items with higher or |
| 261 | * equal priority |
| 262 | */ |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 263 | if (!item_lessthan(wi, item)) { |
| 264 | n_beaten_by++; |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | if (n_beaten_by >= active_target) { |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 269 | /* Too many already have higher priority, not preempting */ |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 270 | goto out; |
| 271 | } |
| 272 | |
| 273 | /* Grab a thread, set its priority and queue it. If there are |
| 274 | * no threads available to unpend, this is a soft runtime |
| 275 | * error: we are breaking our promise about run order. |
| 276 | * Complain. |
| 277 | */ |
| 278 | struct k_thread *th = z_unpend_first_thread(&queue->waitq); |
| 279 | |
| 280 | if (th == NULL) { |
| 281 | LOG_WRN("Out of worker threads, priority guarantee violated"); |
| 282 | goto out; |
| 283 | } |
| 284 | |
| 285 | set_prio(th, item); |
| 286 | z_ready_thread(th); |
| 287 | z_reschedule(&queue->lock, k); |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 288 | |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 289 | return; |
| 290 | |
| 291 | out: |
| 292 | k_spin_unlock(&queue->lock, k); |
| 293 | } |
| 294 | |
| 295 | bool k_p4wq_cancel(struct k_p4wq *queue, struct k_p4wq_work *item) |
| 296 | { |
| 297 | k_spinlock_key_t k = k_spin_lock(&queue->lock); |
| 298 | bool ret = rb_contains(&queue->queue, &item->rbnode); |
| 299 | |
| 300 | if (ret) { |
| 301 | rb_remove(&queue->queue, &item->rbnode); |
Guennadi Liakhovetski | 56610bd | 2021-04-07 15:13:27 +0200 | [diff] [blame] | 302 | k_sem_give(&item->done_sem); |
Andy Ross | d2eadfa | 2020-07-28 13:14:23 -0700 | [diff] [blame] | 303 | } |
| 304 | |
| 305 | k_spin_unlock(&queue->lock, k); |
| 306 | return ret; |
| 307 | } |