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