blob: ebd3b3f8b1451a318c633bfc35b649018ca609fe [file] [log] [blame]
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001/*
2 * Copyright (c) 2020 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7/**
8 * @file
9 *
10 * Second generation work queue implementation
11 */
12
13#include <kernel.h>
14#include <kernel_structs.h>
15#include <wait_q.h>
16#include <spinlock.h>
17#include <errno.h>
18#include <ksched.h>
19#include <sys/printk.h>
20
21static inline void flag_clear(uint32_t *flagp,
22 uint32_t bit)
23{
24 *flagp &= ~BIT(bit);
25}
26
27static inline void flag_set(uint32_t *flagp,
28 uint32_t bit)
29{
30 *flagp |= BIT(bit);
31}
32
33static inline bool flag_test(const uint32_t *flagp,
34 uint32_t bit)
35{
36 return (*flagp & BIT(bit)) != 0U;
37}
38
39static inline bool flag_test_and_clear(uint32_t *flagp,
40 int bit)
41{
42 bool ret = flag_test(flagp, bit);
43
44 flag_clear(flagp, bit);
45
46 return ret;
47}
48
49static inline void flags_set(uint32_t *flagp,
50 uint32_t flags)
51{
52 *flagp = flags;
53}
54
55static inline uint32_t flags_get(const uint32_t *flagp)
56{
57 return *flagp;
58}
59
60/* Lock to protect the internal state of all work items, work queues,
61 * and pending_cancels.
62 */
63static struct k_spinlock lock;
64
65/* Invoked by work thread */
66static void handle_flush(struct k_work *work)
67{
68 struct z_work_flusher *flusher
69 = CONTAINER_OF(work, struct z_work_flusher, work);
70
71 k_sem_give(&flusher->sem);
72}
73
74static inline void init_flusher(struct z_work_flusher *flusher)
75{
76 k_sem_init(&flusher->sem, 0, 1);
77 k_work_init(&flusher->work, handle_flush);
78}
79
80/* List of pending cancellations. */
81static sys_slist_t pending_cancels;
82
83/* Initialize a canceler record and add it to the list of pending
84 * cancels.
85 *
86 * Invoked with work lock held.
87 *
88 * @param canceler the structure used to notify a waiting process.
89 * @param work the work structure that is to be canceled
90 */
91static inline void init_work_cancel(struct z_work_canceller *canceler,
92 struct k_work *work)
93{
94 k_sem_init(&canceler->sem, 0, 1);
95 canceler->work = work;
96 sys_slist_append(&pending_cancels, &canceler->node);
97}
98
99/* Complete cancellation of a work item and unlock held lock.
100 *
101 * Invoked with work lock held.
102 *
103 * Invoked from a work queue thread.
104 *
105 * Reschedules.
106 *
Stefan Eicherfbe7d722021-08-23 11:36:22 +0000107 * @param work the work structure that has completed cancellation
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500108 */
109static void finalize_cancel_locked(struct k_work *work)
110{
111 struct z_work_canceller *wc, *tmp;
112 sys_snode_t *prev = NULL;
113
114 /* Clear this first, so released high-priority threads don't
115 * see it when doing things.
116 */
117 flag_clear(&work->flags, K_WORK_CANCELING_BIT);
118
119 /* Search for and remove the matching container, and release
120 * what's waiting for the completion. The same work item can
121 * appear multiple times in the list if multiple threads
122 * attempt to cancel it.
123 */
124 SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&pending_cancels, wc, tmp, node) {
125 if (wc->work == work) {
126 sys_slist_remove(&pending_cancels, prev, &wc->node);
127 k_sem_give(&wc->sem);
128 } else {
129 prev = &wc->node;
130 }
131 }
132}
133
134void k_work_init(struct k_work *work,
135 k_work_handler_t handler)
136{
137 __ASSERT_NO_MSG(work != NULL);
Daniel Leungc8177ac2021-04-27 12:09:35 -0700138 __ASSERT_NO_MSG(handler != NULL);
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500139
140 *work = (struct k_work)Z_WORK_INITIALIZER(handler);
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100141
142 SYS_PORT_TRACING_OBJ_INIT(k_work, work);
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500143}
144
145static inline int work_busy_get_locked(const struct k_work *work)
146{
147 return flags_get(&work->flags) & K_WORK_MASK;
148}
149
150int k_work_busy_get(const struct k_work *work)
151{
152 k_spinlock_key_t key = k_spin_lock(&lock);
153 int ret = work_busy_get_locked(work);
154
155 k_spin_unlock(&lock, key);
156
157 return ret;
158}
159
160/* Add a flusher work item to the queue.
161 *
162 * Invoked with work lock held.
163 *
164 * Caller must notify queue of pending work.
165 *
166 * @param queue queue on which a work item may appear.
167 * @param work the work item that is either queued or running on @p
168 * queue
169 * @param flusher an uninitialized/unused flusher object
170 */
171static void queue_flusher_locked(struct k_work_q *queue,
172 struct k_work *work,
173 struct z_work_flusher *flusher)
174{
175 bool in_list = false;
176 struct k_work *wn;
177
178 /* Determine whether the work item is still queued. */
179 SYS_SLIST_FOR_EACH_CONTAINER(&queue->pending, wn, node) {
180 if (wn == work) {
181 in_list = true;
182 break;
183 }
184 }
185
186 init_flusher(flusher);
187 if (in_list) {
188 sys_slist_insert(&queue->pending, &work->node,
189 &flusher->work.node);
190 } else {
191 sys_slist_prepend(&queue->pending, &flusher->work.node);
192 }
193}
194
195/* Try to remove a work item from the given queue.
196 *
197 * Invoked with work lock held.
198 *
199 * @param queue the queue from which the work should be removed
200 * @param work work that may be on the queue
201 */
202static inline void queue_remove_locked(struct k_work_q *queue,
203 struct k_work *work)
204{
205 if (flag_test_and_clear(&work->flags, K_WORK_QUEUED_BIT)) {
206 (void)sys_slist_find_and_remove(&queue->pending, &work->node);
207 }
208}
209
210/* Potentially notify a queue that it needs to look for pending work.
211 *
212 * This may make the work queue thread ready, but as the lock is held it
213 * will not be a reschedule point. Callers should yield after the lock is
214 * released where appropriate (generally if this returns true).
215 *
216 * @param queue to be notified. If this is null no notification is required.
217 *
218 * @return true if and only if the queue was notified and woken, i.e. a
219 * reschedule is pending.
220 */
221static inline bool notify_queue_locked(struct k_work_q *queue)
222{
223 bool rv = false;
224
225 if (queue != NULL) {
226 rv = z_sched_wake(&queue->notifyq, 0, NULL);
227 }
228
229 return rv;
230}
231
232/* Submit an work item to a queue if queue state allows new work.
233 *
234 * Submission is rejected if no queue is provided, or if the queue is
235 * draining and the work isn't being submitted from the queue's
236 * thread (chained submission).
237 *
238 * Invoked with work lock held.
239 * Conditionally notifies queue.
240 *
241 * @param queue the queue to which work should be submitted. This may
242 * be null, in which case the submission will fail.
243 *
244 * @param work to be submitted
245 *
246 * @retval 1 if successfully queued
247 * @retval -EINVAL if no queue is provided
248 * @retval -ENODEV if the queue is not started
249 * @retval -EBUSY if the submission was rejected (draining, plugged)
250 */
251static inline int queue_submit_locked(struct k_work_q *queue,
252 struct k_work *work)
253{
254 if (queue == NULL) {
255 return -EINVAL;
256 }
257
258 int ret = -EBUSY;
259 bool chained = (_current == &queue->thread) && !k_is_in_isr();
260 bool draining = flag_test(&queue->flags, K_WORK_QUEUE_DRAIN_BIT);
261 bool plugged = flag_test(&queue->flags, K_WORK_QUEUE_PLUGGED_BIT);
262
263 /* Test for acceptability, in priority order:
264 *
265 * * -ENODEV if the queue isn't running.
266 * * -EBUSY if draining and not chained
267 * * -EBUSY if plugged and not draining
268 * * otherwise OK
269 */
270 if (!flag_test(&queue->flags, K_WORK_QUEUE_STARTED_BIT)) {
271 ret = -ENODEV;
272 } else if (draining && !chained) {
273 ret = -EBUSY;
274 } else if (plugged && !draining) {
275 ret = -EBUSY;
276 } else {
277 sys_slist_append(&queue->pending, &work->node);
278 ret = 1;
279 (void)notify_queue_locked(queue);
280 }
281
282 return ret;
283}
284
285/* Attempt to submit work to a queue.
286 *
287 * The submission can fail if:
288 * * the work is cancelling,
289 * * no candidate queue can be identified;
290 * * the candidate queue rejects the submission.
291 *
292 * Invoked with work lock held.
293 * Conditionally notifies queue.
294 *
295 * @param work the work structure to be submitted
296
297 * @param queuep pointer to a queue reference. On input this should
298 * dereference to the proposed queue (which may be null); after completion it
299 * will be null if the work was not submitted or if submitted will reference
300 * the queue it was submitted to. That may or may not be the queue provided
301 * on input.
302 *
303 * @retval 0 if work was already submitted to a queue
304 * @retval 1 if work was not submitted and has been queued to @p queue
305 * @retval 2 if work was running and has been queued to the queue that was
306 * running it
307 * @retval -EBUSY if canceling or submission was rejected by queue
308 * @retval -EINVAL if no queue is provided
309 * @retval -ENODEV if the queue is not started
310 */
311static int submit_to_queue_locked(struct k_work *work,
312 struct k_work_q **queuep)
313{
314 int ret = 0;
315
316 if (flag_test(&work->flags, K_WORK_CANCELING_BIT)) {
317 /* Disallowed */
318 ret = -EBUSY;
319 } else if (!flag_test(&work->flags, K_WORK_QUEUED_BIT)) {
320 /* Not currently queued */
321 ret = 1;
322
323 /* If no queue specified resubmit to last queue.
324 */
325 if (*queuep == NULL) {
326 *queuep = work->queue;
327 }
328
329 /* If the work is currently running we have to use the
330 * queue it's running on to prevent handler
331 * re-entrancy.
332 */
333 if (flag_test(&work->flags, K_WORK_RUNNING_BIT)) {
334 __ASSERT_NO_MSG(work->queue != NULL);
335 *queuep = work->queue;
336 ret = 2;
337 }
338
339 int rc = queue_submit_locked(*queuep, work);
340
341 if (rc < 0) {
342 ret = rc;
343 } else {
344 flag_set(&work->flags, K_WORK_QUEUED_BIT);
345 work->queue = *queuep;
346 }
347 } else {
348 /* Already queued, do nothing. */
349 }
350
351 if (ret <= 0) {
352 *queuep = NULL;
353 }
354
355 return ret;
356}
357
358int k_work_submit_to_queue(struct k_work_q *queue,
359 struct k_work *work)
360{
361 __ASSERT_NO_MSG(work != NULL);
362
363 k_spinlock_key_t key = k_spin_lock(&lock);
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100364
365 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, submit_to_queue, queue, work);
366
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500367 int ret = submit_to_queue_locked(work, &queue);
368
369 k_spin_unlock(&lock, key);
370
371 /* If we changed the queue contents (as indicated by a positive ret)
372 * the queue thread may now be ready, but we missed the reschedule
373 * point because the lock was held. If this is being invoked by a
374 * preemptible thread then yield.
375 */
376 if ((ret > 0) && (k_is_preempt_thread() != 0)) {
377 k_yield();
378 }
379
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100380 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, submit_to_queue, queue, work, ret);
381
382 return ret;
383}
384
385int k_work_submit(struct k_work *work)
386{
387 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, submit, work);
388
389 int ret = k_work_submit_to_queue(&k_sys_work_q, work);
390
391 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, submit, work, ret);
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500392
393 return ret;
394}
395
396/* Flush the work item if necessary.
397 *
398 * Flushing is necessary only if the work is either queued or running.
399 *
400 * Invoked with work lock held by key.
401 * Sleeps.
402 *
403 * @param work the work item that is to be flushed
404 * @param flusher state used to synchronize the flush
405 *
406 * @retval true if work is queued or running. If this happens the
407 * caller must take the flusher semaphore after releasing the lock.
408 *
409 * @retval false otherwise. No wait required.
410 */
411static bool work_flush_locked(struct k_work *work,
412 struct z_work_flusher *flusher)
413{
414 bool need_flush = (flags_get(&work->flags)
Anas Nashifbbbc38b2021-03-29 10:03:49 -0400415 & (K_WORK_QUEUED | K_WORK_RUNNING)) != 0U;
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500416
417 if (need_flush) {
418 struct k_work_q *queue = work->queue;
419
420 __ASSERT_NO_MSG(queue != NULL);
421
422 queue_flusher_locked(queue, work, flusher);
423 notify_queue_locked(queue);
424 }
425
426 return need_flush;
427}
428
429bool k_work_flush(struct k_work *work,
430 struct k_work_sync *sync)
431{
432 __ASSERT_NO_MSG(work != NULL);
433 __ASSERT_NO_MSG(!flag_test(&work->flags, K_WORK_DELAYABLE_BIT));
434 __ASSERT_NO_MSG(!k_is_in_isr());
435 __ASSERT_NO_MSG(sync != NULL);
436#ifdef CONFIG_KERNEL_COHERENCE
437 __ASSERT_NO_MSG(arch_mem_coherent(sync));
438#endif
439
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100440 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, flush, work);
441
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500442 struct z_work_flusher *flusher = &sync->flusher;
443 k_spinlock_key_t key = k_spin_lock(&lock);
444
445 bool need_flush = work_flush_locked(work, flusher);
446
447 k_spin_unlock(&lock, key);
448
449 /* If necessary wait until the flusher item completes */
450 if (need_flush) {
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100451 SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_work, flush, work, K_FOREVER);
452
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500453 k_sem_take(&flusher->sem, K_FOREVER);
454 }
455
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100456 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, flush, work, need_flush);
457
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500458 return need_flush;
459}
460
461/* Execute the non-waiting steps necessary to cancel a work item.
462 *
463 * Invoked with work lock held.
464 *
465 * @param work the work item to be canceled.
466 *
467 * @retval true if we need to wait for the work item to finish canceling
468 * @retval false if the work item is idle
469 *
470 * @return k_busy_wait() captured under lock
471 */
472static int cancel_async_locked(struct k_work *work)
473{
474 /* If we haven't already started canceling, do it now. */
475 if (!flag_test(&work->flags, K_WORK_CANCELING_BIT)) {
476 /* Remove it from the queue, if it's queued. */
477 queue_remove_locked(work->queue, work);
478 }
479
480 /* If it's still busy after it's been dequeued, then flag it
481 * as canceling.
482 */
483 int ret = work_busy_get_locked(work);
484
485 if (ret != 0) {
486 flag_set(&work->flags, K_WORK_CANCELING_BIT);
487 ret = work_busy_get_locked(work);
488 }
489
490 return ret;
491}
492
493/* Complete cancellation necessary, release work lock, and wait if
494 * necessary.
495 *
496 * Invoked with work lock held by key.
497 * Sleeps.
498 *
499 * @param work work that is being canceled
500 * @param canceller state used to synchronize the cancellation
501 * @param key used by work lock
502 *
503 * @retval true if and only if the work was still active on entry. The caller
504 * must wait on the canceller semaphore after releasing the lock.
505 *
506 * @retval false if work was idle on entry. The caller need not wait.
507 */
508static bool cancel_sync_locked(struct k_work *work,
509 struct z_work_canceller *canceller)
510{
511 bool ret = flag_test(&work->flags, K_WORK_CANCELING_BIT);
512
513 /* If something's still running then we have to wait for
514 * completion, which is indicated when finish_cancel() gets
515 * invoked.
516 */
517 if (ret) {
518 init_work_cancel(canceller, work);
519 }
520
521 return ret;
522}
523
524int k_work_cancel(struct k_work *work)
525{
526 __ASSERT_NO_MSG(work != NULL);
527 __ASSERT_NO_MSG(!flag_test(&work->flags, K_WORK_DELAYABLE_BIT));
528
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100529 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, cancel, work);
530
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500531 k_spinlock_key_t key = k_spin_lock(&lock);
532 int ret = cancel_async_locked(work);
533
534 k_spin_unlock(&lock, key);
535
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100536 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, cancel, work, ret);
537
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500538 return ret;
539}
540
541bool k_work_cancel_sync(struct k_work *work,
542 struct k_work_sync *sync)
543{
544 __ASSERT_NO_MSG(work != NULL);
545 __ASSERT_NO_MSG(sync != NULL);
546 __ASSERT_NO_MSG(!flag_test(&work->flags, K_WORK_DELAYABLE_BIT));
547 __ASSERT_NO_MSG(!k_is_in_isr());
548#ifdef CONFIG_KERNEL_COHERENCE
549 __ASSERT_NO_MSG(arch_mem_coherent(sync));
550#endif
551
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100552 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, cancel_sync, work, sync);
553
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500554 struct z_work_canceller *canceller = &sync->canceller;
555 k_spinlock_key_t key = k_spin_lock(&lock);
Peter Bigot707dc222021-04-16 11:48:50 -0500556 bool pending = (work_busy_get_locked(work) != 0U);
557 bool need_wait = false;
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500558
Peter Bigot707dc222021-04-16 11:48:50 -0500559 if (pending) {
560 (void)cancel_async_locked(work);
561 need_wait = cancel_sync_locked(work, canceller);
562 }
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500563
564 k_spin_unlock(&lock, key);
565
566 if (need_wait) {
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100567 SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_work, cancel_sync, work, sync);
568
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500569 k_sem_take(&canceller->sem, K_FOREVER);
570 }
571
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100572 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, cancel_sync, work, sync, pending);
Peter Bigot707dc222021-04-16 11:48:50 -0500573 return pending;
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500574}
575
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500576/* Loop executed by a work queue thread.
577 *
578 * @param workq_ptr pointer to the work queue structure
579 */
580static void work_queue_main(void *workq_ptr, void *p2, void *p3)
581{
582 struct k_work_q *queue = (struct k_work_q *)workq_ptr;
583
584 while (true) {
585 sys_snode_t *node;
586 struct k_work *work = NULL;
Peter Bigot656c0952021-05-12 08:20:55 -0500587 k_work_handler_t handler = NULL;
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500588 k_spinlock_key_t key = k_spin_lock(&lock);
589
Peter Bigot656c0952021-05-12 08:20:55 -0500590 /* Check for and prepare any new work. */
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500591 node = sys_slist_get(&queue->pending);
592 if (node != NULL) {
593 /* Mark that there's some work active that's
594 * not on the pending list.
595 */
596 flag_set(&queue->flags, K_WORK_QUEUE_BUSY_BIT);
597 work = CONTAINER_OF(node, struct k_work, node);
Peter Bigot656c0952021-05-12 08:20:55 -0500598 flag_set(&work->flags, K_WORK_RUNNING_BIT);
599 flag_clear(&work->flags, K_WORK_QUEUED_BIT);
Maksim Masalski8ed9cdd2021-07-07 16:05:14 +0800600
601 /* Static code analysis tool can raise a false-positive violation
602 * in the line below that 'work' is checked for null after being
603 * dereferenced.
604 *
605 * The work is figured out by CONTAINER_OF, as a container
606 * of type struct k_work that contains the node.
607 * The only way for it to be NULL is if node would be a member
608 * of struct k_work object that has been placed at address NULL,
609 * which should never happen, even line 'if (work != NULL)'
610 * ensures that.
611 * This means that if node is not NULL, then work will not be NULL.
612 */
Peter Bigot656c0952021-05-12 08:20:55 -0500613 handler = work->handler;
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500614 } else if (flag_test_and_clear(&queue->flags,
615 K_WORK_QUEUE_DRAIN_BIT)) {
616 /* Not busy and draining: move threads waiting for
617 * drain to ready state. The held spinlock inhibits
618 * immediate reschedule; released threads get their
619 * chance when this invokes z_sched_wait() below.
620 *
621 * We don't touch K_WORK_QUEUE_PLUGGABLE, so getting
622 * here doesn't mean that the queue will allow new
623 * submissions.
624 */
625 (void)z_sched_wake_all(&queue->drainq, 1, NULL);
Jennifer Williams9aa0f212021-03-10 05:05:56 +0200626 } else {
627 /* No work is available and no queue state requires
628 * special handling.
629 */
630 ;
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500631 }
632
633 if (work == NULL) {
634 /* Nothing's had a chance to add work since we took
635 * the lock, and we didn't find work nor got asked to
636 * stop. Just go to sleep: when something happens the
637 * work thread will be woken and we can check again.
638 */
639
640 (void)z_sched_wait(&lock, key, &queue->notifyq,
641 K_FOREVER, NULL);
642 continue;
643 }
644
645 k_spin_unlock(&lock, key);
646
647 if (work != NULL) {
648 bool yield;
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500649
Daniel Leungc8177ac2021-04-27 12:09:35 -0700650 __ASSERT_NO_MSG(handler != NULL);
Peter Bigot656c0952021-05-12 08:20:55 -0500651 handler(work);
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500652
Peter Bigot656c0952021-05-12 08:20:55 -0500653 /* Mark the work item as no longer running and deal
654 * with any cancellation issued while it was running.
655 * Clear the BUSY flag and optionally yield to prevent
656 * starving other threads.
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500657 */
658 key = k_spin_lock(&lock);
Peter Bigot656c0952021-05-12 08:20:55 -0500659
660 flag_clear(&work->flags, K_WORK_RUNNING_BIT);
661 if (flag_test(&work->flags, K_WORK_CANCELING_BIT)) {
662 finalize_cancel_locked(work);
663 }
664
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500665 flag_clear(&queue->flags, K_WORK_QUEUE_BUSY_BIT);
666 yield = !flag_test(&queue->flags, K_WORK_QUEUE_NO_YIELD_BIT);
667 k_spin_unlock(&lock, key);
668
669 /* Optionally yield to prevent the work queue from
670 * starving other threads.
671 */
672 if (yield) {
673 k_yield();
674 }
675 }
676 }
677}
678
Flavio Ceolind9aa4142021-08-23 14:33:40 -0700679void k_work_queue_init(struct k_work_q *queue)
680{
681 __ASSERT_NO_MSG(queue != NULL);
682
683 *queue = (struct k_work_q) {
684 .flags = 0,
685 };
686
687 SYS_PORT_TRACING_OBJ_INIT(k_work_queue, queue);
688}
689
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500690void k_work_queue_start(struct k_work_q *queue,
691 k_thread_stack_t *stack,
692 size_t stack_size,
693 int prio,
694 const struct k_work_queue_config *cfg)
695{
696 __ASSERT_NO_MSG(queue);
697 __ASSERT_NO_MSG(stack);
698 __ASSERT_NO_MSG(!flag_test(&queue->flags, K_WORK_QUEUE_STARTED_BIT));
699 uint32_t flags = K_WORK_QUEUE_STARTED;
700
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100701 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work_queue, start, queue);
702
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500703 sys_slist_init(&queue->pending);
704 z_waitq_init(&queue->notifyq);
705 z_waitq_init(&queue->drainq);
706
707 if ((cfg != NULL) && cfg->no_yield) {
708 flags |= K_WORK_QUEUE_NO_YIELD;
709 }
710
711 /* It hasn't actually been started yet, but all the state is in place
712 * so we can submit things and once the thread gets control it's ready
713 * to roll.
714 */
715 flags_set(&queue->flags, flags);
716
717 (void)k_thread_create(&queue->thread, stack, stack_size,
718 work_queue_main, queue, NULL, NULL,
719 prio, 0, K_FOREVER);
720
721 if ((cfg != NULL) && (cfg->name != NULL)) {
722 k_thread_name_set(&queue->thread, cfg->name);
723 }
724
725 k_thread_start(&queue->thread);
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100726
727 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work_queue, start, queue);
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500728}
729
730int k_work_queue_drain(struct k_work_q *queue,
731 bool plug)
732{
733 __ASSERT_NO_MSG(queue);
734 __ASSERT_NO_MSG(!k_is_in_isr());
735
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100736 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work_queue, drain, queue);
737
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500738 int ret = 0;
739 k_spinlock_key_t key = k_spin_lock(&lock);
740
741 if (((flags_get(&queue->flags)
742 & (K_WORK_QUEUE_BUSY | K_WORK_QUEUE_DRAIN)) != 0U)
743 || plug
744 || !sys_slist_is_empty(&queue->pending)) {
745 flag_set(&queue->flags, K_WORK_QUEUE_DRAIN_BIT);
746 if (plug) {
747 flag_set(&queue->flags, K_WORK_QUEUE_PLUGGED_BIT);
748 }
749
750 notify_queue_locked(queue);
751 ret = z_sched_wait(&lock, key, &queue->drainq,
752 K_FOREVER, NULL);
753 } else {
754 k_spin_unlock(&lock, key);
755 }
756
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100757 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work_queue, drain, queue, ret);
758
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500759 return ret;
760}
761
762int k_work_queue_unplug(struct k_work_q *queue)
763{
764 __ASSERT_NO_MSG(queue);
765
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100766 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work_queue, unplug, queue);
767
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500768 int ret = -EALREADY;
769 k_spinlock_key_t key = k_spin_lock(&lock);
770
771 if (flag_test_and_clear(&queue->flags, K_WORK_QUEUE_PLUGGED_BIT)) {
772 ret = 0;
773 }
774
775 k_spin_unlock(&lock, key);
776
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100777 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work_queue, unplug, queue, ret);
778
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500779 return ret;
780}
781
782#ifdef CONFIG_SYS_CLOCK_EXISTS
783
784/* Timeout handler for delayable work.
785 *
786 * Invoked by timeout infrastructure.
787 * Takes and releases work lock.
788 * Conditionally reschedules.
789 */
790static void work_timeout(struct _timeout *to)
791{
792 struct k_work_delayable *dw
793 = CONTAINER_OF(to, struct k_work_delayable, timeout);
794 struct k_work *wp = &dw->work;
795 k_spinlock_key_t key = k_spin_lock(&lock);
796 struct k_work_q *queue = NULL;
797
798 /* If the work is still marked delayed (should be) then clear that
799 * state and submit it to the queue. If successful the queue will be
800 * notified of new work at the next reschedule point.
801 *
802 * If not successful there is no notification that the work has been
803 * abandoned. Sorry.
804 */
805 if (flag_test_and_clear(&wp->flags, K_WORK_DELAYED_BIT)) {
806 queue = dw->queue;
807 (void)submit_to_queue_locked(wp, &queue);
808 }
809
810 k_spin_unlock(&lock, key);
811}
812
813void k_work_init_delayable(struct k_work_delayable *dwork,
814 k_work_handler_t handler)
815{
816 __ASSERT_NO_MSG(dwork != NULL);
Daniel Leungc8177ac2021-04-27 12:09:35 -0700817 __ASSERT_NO_MSG(handler != NULL);
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500818
819 *dwork = (struct k_work_delayable){
820 .work = {
821 .handler = handler,
822 .flags = K_WORK_DELAYABLE,
823 },
824 };
825 z_init_timeout(&dwork->timeout);
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100826
827 SYS_PORT_TRACING_OBJ_INIT(k_work_delayable, dwork);
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500828}
829
830static inline int work_delayable_busy_get_locked(const struct k_work_delayable *dwork)
831{
Chris Reedab4d69b2021-10-13 15:02:42 -0500832 return flags_get(&dwork->work.flags) & K_WORK_MASK;
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500833}
834
835int k_work_delayable_busy_get(const struct k_work_delayable *dwork)
836{
837 k_spinlock_key_t key = k_spin_lock(&lock);
838 int ret = work_delayable_busy_get_locked(dwork);
839
840 k_spin_unlock(&lock, key);
841 return ret;
842}
843
844/* Attempt to schedule a work item for future (maybe immediate)
845 * submission.
846 *
847 * Invoked with work lock held.
848 *
849 * See also submit_to_queue_locked(), which implements this for a no-wait
850 * delay.
851 *
852 * Invoked with work lock held.
853 *
854 * @param queuep pointer to a pointer to a queue. On input this
855 * should dereference to the proposed queue (which may be null); after
856 * completion it will be null if the work was not submitted or if
857 * submitted will reference the queue it was submitted to. That may
858 * or may not be the queue provided on input.
859 *
860 * @param dwork the delayed work structure
861 *
862 * @param delay the delay to use before scheduling.
863 *
864 * @retval from submit_to_queue_locked() if delay is K_NO_WAIT; otherwise
865 * @retval 1 to indicate successfully scheduled.
866 */
867static int schedule_for_queue_locked(struct k_work_q **queuep,
868 struct k_work_delayable *dwork,
869 k_timeout_t delay)
870{
871 int ret = 1;
872 struct k_work *work = &dwork->work;
873
874 if (K_TIMEOUT_EQ(delay, K_NO_WAIT)) {
875 return submit_to_queue_locked(work, queuep);
876 }
877
878 flag_set(&work->flags, K_WORK_DELAYED_BIT);
879 dwork->queue = *queuep;
880
881 /* Add timeout */
882 z_add_timeout(&dwork->timeout, work_timeout, delay);
883
884 return ret;
885}
886
887/* Unschedule delayable work.
888 *
889 * If the work is delayed, cancel the timeout and clear the delayed
890 * flag.
891 *
892 * Invoked with work lock held.
893 *
894 * @param dwork pointer to delayable work structure.
895 *
896 * @return true if and only if work had been delayed so the timeout
897 * was cancelled.
898 */
899static inline bool unschedule_locked(struct k_work_delayable *dwork)
900{
901 bool ret = false;
902 struct k_work *work = &dwork->work;
903
904 /* If scheduled, try to cancel. */
905 if (flag_test_and_clear(&work->flags, K_WORK_DELAYED_BIT)) {
906 z_abort_timeout(&dwork->timeout);
907 ret = true;
908 }
909
910 return ret;
911}
912
913/* Full cancellation of a delayable work item.
914 *
915 * Unschedules the delayed part then delegates to standard work
916 * cancellation.
917 *
918 * Invoked with work lock held.
919 *
920 * @param dwork delayable work item
921 *
922 * @return k_work_busy_get() flags
923 */
924static int cancel_delayable_async_locked(struct k_work_delayable *dwork)
925{
926 (void)unschedule_locked(dwork);
927
928 return cancel_async_locked(&dwork->work);
929}
930
931int k_work_schedule_for_queue(struct k_work_q *queue,
932 struct k_work_delayable *dwork,
933 k_timeout_t delay)
934{
935 __ASSERT_NO_MSG(dwork != NULL);
936
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100937 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, schedule_for_queue, queue, dwork, delay);
938
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500939 struct k_work *work = &dwork->work;
940 int ret = 0;
941 k_spinlock_key_t key = k_spin_lock(&lock);
942
Peter Bigotfed03522021-03-15 11:23:00 -0500943 /* Schedule the work item if it's idle or running. */
944 if ((work_busy_get_locked(work) & ~K_WORK_RUNNING) == 0U) {
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500945 ret = schedule_for_queue_locked(&queue, dwork, delay);
946 }
947
948 k_spin_unlock(&lock, key);
949
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100950 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, schedule_for_queue, queue, dwork, delay, ret);
951
952 return ret;
953}
954
955int k_work_schedule(struct k_work_delayable *dwork,
956 k_timeout_t delay)
957{
958 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, schedule, dwork, delay);
959
960 int ret = k_work_schedule_for_queue(&k_sys_work_q, dwork, delay);
961
962 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, schedule, dwork, delay, ret);
963
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500964 return ret;
965}
966
967int k_work_reschedule_for_queue(struct k_work_q *queue,
968 struct k_work_delayable *dwork,
969 k_timeout_t delay)
970{
971 __ASSERT_NO_MSG(dwork != NULL);
972
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100973 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, reschedule_for_queue, queue, dwork, delay);
974
Peter Bigotdc34e7c2020-10-28 11:24:05 -0500975 int ret = 0;
976 k_spinlock_key_t key = k_spin_lock(&lock);
977
978 /* Remove any active scheduling. */
979 (void)unschedule_locked(dwork);
980
981 /* Schedule the work item with the new parameters. */
982 ret = schedule_for_queue_locked(&queue, dwork, delay);
983
984 k_spin_unlock(&lock, key);
985
Torbjörn Leksell7a646b32021-03-26 14:41:18 +0100986 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, reschedule_for_queue, queue, dwork, delay, ret);
987
988 return ret;
989}
990
991int k_work_reschedule(struct k_work_delayable *dwork,
992 k_timeout_t delay)
993{
994 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, reschedule, dwork, delay);
995
996 int ret = k_work_reschedule_for_queue(&k_sys_work_q, dwork, delay);
997
998 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, reschedule, dwork, delay, ret);
999
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001000 return ret;
1001}
1002
1003int k_work_cancel_delayable(struct k_work_delayable *dwork)
1004{
1005 __ASSERT_NO_MSG(dwork != NULL);
1006
Torbjörn Leksell7a646b32021-03-26 14:41:18 +01001007 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, cancel_delayable, dwork);
1008
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001009 k_spinlock_key_t key = k_spin_lock(&lock);
1010 int ret = cancel_delayable_async_locked(dwork);
1011
1012 k_spin_unlock(&lock, key);
Torbjörn Leksell7a646b32021-03-26 14:41:18 +01001013
1014 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, cancel_delayable, dwork, ret);
1015
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001016 return ret;
1017}
1018
1019bool k_work_cancel_delayable_sync(struct k_work_delayable *dwork,
1020 struct k_work_sync *sync)
1021{
1022 __ASSERT_NO_MSG(dwork != NULL);
1023 __ASSERT_NO_MSG(sync != NULL);
1024 __ASSERT_NO_MSG(!k_is_in_isr());
1025#ifdef CONFIG_KERNEL_COHERENCE
1026 __ASSERT_NO_MSG(arch_mem_coherent(sync));
1027#endif
1028
Torbjörn Leksell7a646b32021-03-26 14:41:18 +01001029 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, cancel_delayable_sync, dwork, sync);
1030
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001031 struct z_work_canceller *canceller = &sync->canceller;
1032 k_spinlock_key_t key = k_spin_lock(&lock);
Peter Bigot707dc222021-04-16 11:48:50 -05001033 bool pending = (work_delayable_busy_get_locked(dwork) != 0U);
1034 bool need_wait = false;
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001035
Peter Bigot707dc222021-04-16 11:48:50 -05001036 if (pending) {
1037 (void)cancel_delayable_async_locked(dwork);
1038 need_wait = cancel_sync_locked(&dwork->work, canceller);
1039 }
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001040
1041 k_spin_unlock(&lock, key);
1042
1043 if (need_wait) {
1044 k_sem_take(&canceller->sem, K_FOREVER);
1045 }
1046
Torbjörn Leksell7a646b32021-03-26 14:41:18 +01001047 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, cancel_delayable_sync, dwork, sync, pending);
Peter Bigot707dc222021-04-16 11:48:50 -05001048 return pending;
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001049}
1050
1051bool k_work_flush_delayable(struct k_work_delayable *dwork,
1052 struct k_work_sync *sync)
1053{
1054 __ASSERT_NO_MSG(dwork != NULL);
1055 __ASSERT_NO_MSG(sync != NULL);
1056 __ASSERT_NO_MSG(!k_is_in_isr());
1057#ifdef CONFIG_KERNEL_COHERENCE
1058 __ASSERT_NO_MSG(arch_mem_coherent(sync));
1059#endif
1060
Torbjörn Leksell7a646b32021-03-26 14:41:18 +01001061 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, flush_delayable, dwork, sync);
1062
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001063 struct k_work *work = &dwork->work;
1064 struct z_work_flusher *flusher = &sync->flusher;
1065 k_spinlock_key_t key = k_spin_lock(&lock);
1066
1067 /* If it's idle release the lock and return immediately. */
1068 if (work_busy_get_locked(work) == 0U) {
1069 k_spin_unlock(&lock, key);
Torbjörn Leksell7a646b32021-03-26 14:41:18 +01001070
1071 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, flush_delayable, dwork, sync, false);
1072
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001073 return false;
1074 }
1075
1076 /* If unscheduling did something then submit it. Ignore a
1077 * failed submission (e.g. when cancelling).
1078 */
1079 if (unschedule_locked(dwork)) {
1080 struct k_work_q *queue = dwork->queue;
1081
1082 (void)submit_to_queue_locked(work, &queue);
1083 }
1084
1085 /* Wait for it to finish */
1086 bool need_flush = work_flush_locked(work, flusher);
1087
1088 k_spin_unlock(&lock, key);
1089
1090 /* If necessary wait until the flusher item completes */
1091 if (need_flush) {
1092 k_sem_take(&flusher->sem, K_FOREVER);
1093 }
1094
Torbjörn Leksell7a646b32021-03-26 14:41:18 +01001095 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, flush_delayable, dwork, sync, need_flush);
1096
Peter Bigotdc34e7c2020-10-28 11:24:05 -05001097 return need_flush;
1098}
1099
1100#endif /* CONFIG_SYS_CLOCK_EXISTS */