1 /*
2 * Copyright (c) 2008 Travis Geiselbrecht
3 * Copyright (c) 2019 Google, Inc.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files
7 * (the "Software"), to deal in the Software without restriction,
8 * including without limitation the rights to use, copy, modify, merge,
9 * publish, distribute, sublicense, and/or sell copies of the Software,
10 * and to permit persons to whom the Software is furnished to do so,
11 * subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24 #include <assert.h>
25 #include <debug.h>
26 #include <kernel/event.h>
27 #include <kernel/spinlock.h>
28 #include <kernel/thread.h>
29 #include <lib/dpc.h>
30 #include <lk/init.h>
31 #include <lk/list.h>
32 #include <malloc.h>
33 #include <stddef.h>
34 #include <trace.h>
35 #include <uapi/err.h>
36
37 #define LOCAL_TRACE 0
38
39 struct dpc_queue {
40 struct list_node list;
41 struct event event;
42 spin_lock_t lock;
43 struct thread* thread;
44 };
45
46 static struct dpc_queue default_queue;
47
48 static int dpc_thread_routine(void* arg);
49
dpc_work_init(struct dpc * work,dpc_callback cb,uint32_t flags)50 void dpc_work_init(struct dpc* work, dpc_callback cb, uint32_t flags) {
51 ASSERT(work);
52
53 list_clear_node(&work->node);
54 work->cb = cb;
55 work->q = NULL;
56 }
57
dpc_enqueue_work(struct dpc_queue * q,struct dpc * work,bool resched)58 int dpc_enqueue_work(struct dpc_queue* q, struct dpc* work, bool resched) {
59 spin_lock_saved_state_t state;
60
61 ASSERT(work);
62 ASSERT(work->cb);
63
64 if (!q) {
65 q = &default_queue;
66 }
67
68 spin_lock_irqsave(&q->lock, state);
69 ASSERT(!work->q || (work->q == q));
70 if (!list_in_list(&work->node)) {
71 list_add_tail(&q->list, &work->node);
72 work->q = q;
73 }
74 spin_unlock_irqrestore(&q->lock, state);
75 event_signal(&q->event, resched);
76 return 0;
77 }
78
dpc_thread_routine(void * arg)79 static int dpc_thread_routine(void* arg) {
80 struct dpc* work;
81 struct dpc_queue* q = arg;
82 spin_lock_saved_state_t state;
83
84 DEBUG_ASSERT(q);
85
86 for (;;) {
87 event_wait(&q->event);
88 do {
89 spin_lock_irqsave(&q->lock, state);
90 work = list_remove_head_type(&q->list, struct dpc, node);
91 spin_unlock_irqrestore(&q->lock, state);
92
93 if (work) {
94 LTRACEF("dpc calling %p\n", work->cb);
95 work->cb(work);
96 }
97 } while (work);
98 }
99
100 return 0;
101 }
102
dpc_queue_start(struct dpc_queue * q,const char * name,int thread_priority,size_t thread_stack_size)103 static status_t dpc_queue_start(struct dpc_queue* q,
104 const char* name,
105 int thread_priority,
106 size_t thread_stack_size) {
107 DEBUG_ASSERT(q);
108 DEBUG_ASSERT(!q->thread);
109
110 /* Initiliaze queue */
111 spin_lock_init(&q->lock);
112 list_initialize(&q->list);
113 event_init(&q->event, false, EVENT_FLAG_AUTOUNSIGNAL);
114
115 /* create thread */
116 q->thread = thread_create(name, dpc_thread_routine, q, thread_priority,
117 thread_stack_size);
118 if (!q->thread)
119 return ERR_NO_MEMORY;
120
121 /* start thread */
122 thread_detach_and_resume(q->thread);
123 return 0;
124 }
125
dpc_init(uint level)126 static void dpc_init(uint level) {
127 status_t rc;
128
129 /* init and start default DPC queue */
130 rc = dpc_queue_start(&default_queue, "dpc", DPC_PRIORITY,
131 DEFAULT_STACK_SIZE);
132 if (rc != NO_ERROR) {
133 panic("failed to start default dpc queue\n");
134 }
135 }
136
137 LK_INIT_HOOK(libdpc, &dpc_init, LK_INIT_LEVEL_THREADING);
138
dpc_queue_create(struct dpc_queue ** pq,const char * name,int thread_priority,size_t thread_stack_size)139 status_t dpc_queue_create(struct dpc_queue** pq,
140 const char* name,
141 int thread_priority,
142 size_t thread_stack_size) {
143 status_t rc;
144
145 DEBUG_ASSERT(pq);
146
147 *pq = calloc(sizeof(struct dpc_queue), 1);
148 if (!*pq)
149 return ERR_NO_MEMORY;
150
151 rc = dpc_queue_start(*pq, name, thread_priority, thread_stack_size);
152 if (rc) {
153 free(*pq);
154 *pq = NULL;
155 }
156
157 return rc;
158 }
159