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