1 /*
2  * Copyright (c) 2008-2015 Travis Geiselbrecht
3  *
4  * Use of this source code is governed by a MIT-style
5  * license that can be found in the LICENSE file or at
6  * https://opensource.org/licenses/MIT
7  */
8 #include <lk/debug.h>
9 #include <lk/trace.h>
10 #include <rand.h>
11 #include <lk/err.h>
12 #include <assert.h>
13 #include <string.h>
14 #include <app/tests.h>
15 #include <kernel/thread.h>
16 #include <kernel/mutex.h>
17 #include <kernel/semaphore.h>
18 #include <kernel/event.h>
19 #include <platform.h>
20 #include <arch/atomic.h>
21 
sleep_thread(void * arg)22 static int sleep_thread(void *arg) {
23     for (;;) {
24         printf("sleeper %p\n", get_current_thread());
25         thread_sleep(rand() % 500);
26     }
27     return 0;
28 }
29 
sleep_test(void)30 static int sleep_test(void) {
31     int i;
32     for (i=0; i < 16; i++)
33         thread_detach_and_resume(thread_create("sleeper", &sleep_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
34     return 0;
35 }
36 
37 static semaphore_t sem;
38 static const int sem_total_its = 10000;
39 static const int sem_thread_max_its = 1000;
40 static const int sem_start_value = 10;
41 static int sem_remaining_its = 0;
42 static int sem_threads = 0;
43 static mutex_t sem_test_mutex;
44 
semaphore_producer(void * unused)45 static int semaphore_producer(void *unused) {
46     printf("semaphore producer %p starting up, running for %d iterations\n", get_current_thread(), sem_total_its);
47 
48     for (int x = 0; x < sem_total_its; x++) {
49         sem_post(&sem, true);
50     }
51 
52     return 0;
53 }
54 
semaphore_consumer(void * unused)55 static int semaphore_consumer(void *unused) {
56     unsigned int iterations = 0;
57 
58     mutex_acquire(&sem_test_mutex);
59     if (sem_remaining_its >= sem_thread_max_its) {
60         iterations = rand();
61         iterations %= sem_thread_max_its;
62     } else {
63         iterations = sem_remaining_its;
64     }
65     sem_remaining_its -= iterations;
66     mutex_release(&sem_test_mutex);
67 
68     printf("semaphore consumer %p starting up, running for %u iterations\n", get_current_thread(), iterations);
69     for (unsigned int x = 0; x < iterations; x++)
70         sem_wait(&sem);
71     printf("semaphore consumer %p done\n", get_current_thread());
72     atomic_add(&sem_threads, -1);
73     return 0;
74 }
75 
semaphore_test(void)76 static int semaphore_test(void) {
77     static semaphore_t isem = SEMAPHORE_INITIAL_VALUE(isem, 99);
78     printf("preinitialized semaphore:\n");
79     hexdump(&isem, sizeof(isem));
80 
81     sem_init(&sem, sem_start_value);
82     mutex_init(&sem_test_mutex);
83 
84     sem_remaining_its = sem_total_its;
85     while (1) {
86         mutex_acquire(&sem_test_mutex);
87         if (sem_remaining_its) {
88             thread_detach_and_resume(thread_create("semaphore consumer", &semaphore_consumer, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
89             atomic_add(&sem_threads, 1);
90         } else {
91             mutex_release(&sem_test_mutex);
92             break;
93         }
94         mutex_release(&sem_test_mutex);
95     }
96 
97     thread_detach_and_resume(thread_create("semaphore producer", &semaphore_producer, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
98 
99     while (sem_threads)
100         thread_yield();
101 
102     if (sem.count == sem_start_value)
103         printf("semaphore tests successfully complete\n");
104     else
105         printf("semaphore tests failed: %d != %d\n", sem.count, sem_start_value);
106 
107     sem_destroy(&sem);
108     mutex_destroy(&sem_test_mutex);
109 
110     return 0;
111 }
112 
mutex_thread(void * arg)113 static int mutex_thread(void *arg) {
114     int i;
115     const int iterations = 1000000;
116 
117     static volatile int shared = 0;
118 
119     mutex_t *m = (mutex_t *)arg;
120 
121     printf("mutex tester thread %p starting up, will go for %d iterations\n", get_current_thread(), iterations);
122 
123     for (i = 0; i < iterations; i++) {
124         mutex_acquire(m);
125 
126         if (shared != 0)
127             panic("someone else has messed with the shared data\n");
128 
129         shared = (intptr_t)get_current_thread();
130         thread_yield();
131         shared = 0;
132 
133         mutex_release(m);
134         thread_yield();
135     }
136 
137     return 0;
138 }
139 
mutex_timeout_thread(void * arg)140 static int mutex_timeout_thread(void *arg) {
141     mutex_t *timeout_mutex = (mutex_t *)arg;
142     status_t err;
143 
144     printf("mutex_timeout_thread acquiring mutex %p with 1 second timeout\n", timeout_mutex);
145     err = mutex_acquire_timeout(timeout_mutex, 1000);
146     if (err == ERR_TIMED_OUT)
147         printf("mutex_acquire_timeout returns with TIMEOUT\n");
148     else
149         printf("mutex_acquire_timeout returns %d\n", err);
150 
151     return err;
152 }
153 
mutex_zerotimeout_thread(void * arg)154 static int mutex_zerotimeout_thread(void *arg) {
155     mutex_t *timeout_mutex = (mutex_t *)arg;
156     status_t err;
157 
158     printf("mutex_zerotimeout_thread acquiring mutex %p with zero second timeout\n", timeout_mutex);
159     err = mutex_acquire_timeout(timeout_mutex, 0);
160     if (err == ERR_TIMED_OUT)
161         printf("mutex_acquire_timeout returns with TIMEOUT\n");
162     else
163         printf("mutex_acquire_timeout returns %d\n", err);
164 
165     return err;
166 }
167 
mutex_test(void)168 static int mutex_test(void) {
169     static mutex_t imutex = MUTEX_INITIAL_VALUE(imutex);
170     printf("preinitialized mutex:\n");
171     hexdump(&imutex, sizeof(imutex));
172 
173     mutex_t m;
174     mutex_init(&m);
175 
176     thread_t *threads[5];
177 
178     for (uint i=0; i < countof(threads); i++) {
179         threads[i] = thread_create("mutex tester", &mutex_thread, &m, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
180         thread_resume(threads[i]);
181     }
182 
183     for (uint i=0; i < countof(threads); i++) {
184         thread_join(threads[i], NULL, INFINITE_TIME);
185     }
186 
187     printf("done with simple mutex tests\n");
188 
189     printf("testing mutex timeout\n");
190 
191     mutex_t timeout_mutex;
192 
193     mutex_init(&timeout_mutex);
194     mutex_acquire(&timeout_mutex);
195 
196     for (uint i=0; i < 2; i++) {
197         threads[i] = thread_create("mutex timeout tester", &mutex_timeout_thread, (void *)&timeout_mutex, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
198         thread_resume(threads[i]);
199     }
200 
201     for (uint i=2; i < 4; i++) {
202         threads[i] = thread_create("mutex timeout tester", &mutex_zerotimeout_thread, (void *)&timeout_mutex, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
203         thread_resume(threads[i]);
204     }
205 
206     thread_sleep(5000);
207     mutex_release(&timeout_mutex);
208 
209     for (uint i=0; i < 4; i++) {
210         thread_join(threads[i], NULL, INFINITE_TIME);
211     }
212 
213     printf("done with mutex tests\n");
214 
215     mutex_destroy(&timeout_mutex);
216 
217     return 0;
218 }
219 
220 static event_t e;
221 
event_signaler(void * arg)222 static int event_signaler(void *arg) {
223     printf("event signaler pausing\n");
224     thread_sleep(1000);
225 
226 //  for (;;) {
227     printf("signaling event\n");
228     event_signal(&e, true);
229     printf("done signaling event\n");
230     thread_yield();
231 //  }
232 
233     return 0;
234 }
235 
event_waiter(void * arg)236 static int event_waiter(void *arg) {
237     int count = (intptr_t)arg;
238 
239     printf("event waiter starting\n");
240 
241     while (count > 0) {
242         printf("%p: waiting on event...\n", get_current_thread());
243         if (event_wait(&e) < 0) {
244             printf("%p: event_wait() returned error\n", get_current_thread());
245             return -1;
246         }
247         printf("%p: done waiting on event...\n", get_current_thread());
248         thread_yield();
249         count--;
250     }
251 
252     return 0;
253 }
254 
event_test(void)255 static void event_test(void) {
256     thread_t *threads[5];
257 
258     static event_t ievent = EVENT_INITIAL_VALUE(ievent, true, 0x1234);
259     printf("preinitialized event:\n");
260     hexdump(&ievent, sizeof(ievent));
261 
262     printf("event tests starting\n");
263 
264     /* make sure signaling the event wakes up all the threads */
265     event_init(&e, false, 0);
266     threads[0] = thread_create("event signaler", &event_signaler, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
267     threads[1] = thread_create("event waiter 0", &event_waiter, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
268     threads[2] = thread_create("event waiter 1", &event_waiter, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
269     threads[3] = thread_create("event waiter 2", &event_waiter, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
270     threads[4] = thread_create("event waiter 3", &event_waiter, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
271 
272     for (uint i = 0; i < countof(threads); i++)
273         thread_resume(threads[i]);
274 
275     thread_sleep(2000);
276     printf("destroying event\n");
277     event_destroy(&e);
278 
279     for (uint i = 0; i < countof(threads); i++)
280         thread_join(threads[i], NULL, INFINITE_TIME);
281 
282     /* make sure signaling the event wakes up precisely one thread */
283     event_init(&e, false, EVENT_FLAG_AUTOUNSIGNAL);
284     threads[0] = thread_create("event signaler", &event_signaler, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
285     threads[1] = thread_create("event waiter 0", &event_waiter, (void *)99, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
286     threads[2] = thread_create("event waiter 1", &event_waiter, (void *)99, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
287     threads[3] = thread_create("event waiter 2", &event_waiter, (void *)99, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
288     threads[4] = thread_create("event waiter 3", &event_waiter, (void *)99, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
289 
290     for (uint i = 0; i < countof(threads); i++)
291         thread_resume(threads[i]);
292 
293     thread_sleep(2000);
294     event_destroy(&e);
295 
296     for (uint i = 0; i < countof(threads); i++)
297         thread_join(threads[i], NULL, INFINITE_TIME);
298 
299     printf("event tests done\n");
300 }
301 
quantum_tester(void * arg)302 static int quantum_tester(void *arg) {
303     for (;;) {
304         printf("%p: in this thread. rq %d\n", get_current_thread(), get_current_thread()->remaining_quantum);
305     }
306     return 0;
307 }
308 
quantum_test(void)309 static void quantum_test(void) {
310     thread_detach_and_resume(thread_create("quantum tester 0", &quantum_tester, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
311     thread_detach_and_resume(thread_create("quantum tester 1", &quantum_tester, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
312     thread_detach_and_resume(thread_create("quantum tester 2", &quantum_tester, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
313     thread_detach_and_resume(thread_create("quantum tester 3", &quantum_tester, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
314 }
315 
316 static event_t context_switch_event;
317 static event_t context_switch_done_event;
318 
context_switch_tester(void * arg)319 static int context_switch_tester(void *arg) {
320     int i;
321     uint total_count = 0;
322     const int iter = 100000;
323     int thread_count = (intptr_t)arg;
324 
325     event_wait(&context_switch_event);
326 
327     uint count = arch_cycle_count();
328     for (i = 0; i < iter; i++) {
329         thread_yield();
330     }
331     total_count += arch_cycle_count() - count;
332     thread_sleep(1000);
333     printf("took %u cycles to yield %d times, %u per yield, %u per yield per thread\n",
334            total_count, iter, total_count / iter, total_count / iter / thread_count);
335 
336     event_signal(&context_switch_done_event, true);
337 
338     return 0;
339 }
340 
context_switch_test(void)341 static void context_switch_test(void) {
342     event_init(&context_switch_event, false, 0);
343     event_init(&context_switch_done_event, false, 0);
344 
345     thread_detach_and_resume(thread_create("context switch idle", &context_switch_tester, (void *)1, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
346     thread_sleep(100);
347     event_signal(&context_switch_event, true);
348     event_wait(&context_switch_done_event);
349     thread_sleep(100);
350 
351     event_unsignal(&context_switch_event);
352     event_unsignal(&context_switch_done_event);
353     thread_detach_and_resume(thread_create("context switch 2a", &context_switch_tester, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
354     thread_detach_and_resume(thread_create("context switch 2b", &context_switch_tester, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
355     thread_sleep(100);
356     event_signal(&context_switch_event, true);
357     event_wait(&context_switch_done_event);
358     thread_sleep(100);
359 
360     event_unsignal(&context_switch_event);
361     event_unsignal(&context_switch_done_event);
362     thread_detach_and_resume(thread_create("context switch 4a", &context_switch_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
363     thread_detach_and_resume(thread_create("context switch 4b", &context_switch_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
364     thread_detach_and_resume(thread_create("context switch 4c", &context_switch_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
365     thread_detach_and_resume(thread_create("context switch 4d", &context_switch_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
366     thread_sleep(100);
367     event_signal(&context_switch_event, true);
368     event_wait(&context_switch_done_event);
369     thread_sleep(100);
370 }
371 
372 static volatile int atomic;
373 static volatile int atomic_count;
374 
atomic_tester(void * arg)375 static int atomic_tester(void *arg) {
376     int add = (intptr_t)arg;
377     int i;
378 
379     const int iter = 10000000;
380 
381     TRACEF("add %d, %d iterations\n", add, iter);
382 
383     for (i=0; i < iter; i++) {
384         atomic_add(&atomic, add);
385     }
386 
387     int old = atomic_add(&atomic_count, -1);
388     TRACEF("exiting, old count %d\n", old);
389 
390     return 0;
391 }
392 
atomic_test(void)393 static void atomic_test(void) {
394     atomic = 0;
395     atomic_count = 8;
396 
397     printf("testing atomic routines\n");
398 
399     thread_t *threads[8];
400     threads[0] = thread_create("atomic tester 1", &atomic_tester, (void *)1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
401     threads[1] = thread_create("atomic tester 1", &atomic_tester, (void *)1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
402     threads[2] = thread_create("atomic tester 1", &atomic_tester, (void *)1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
403     threads[3] = thread_create("atomic tester 1", &atomic_tester, (void *)1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
404     threads[4] = thread_create("atomic tester 2", &atomic_tester, (void *)-1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
405     threads[5] = thread_create("atomic tester 2", &atomic_tester, (void *)-1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
406     threads[6] = thread_create("atomic tester 2", &atomic_tester, (void *)-1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
407     threads[7] = thread_create("atomic tester 2", &atomic_tester, (void *)-1, LOW_PRIORITY, DEFAULT_STACK_SIZE);
408 
409     /* start all the threads */
410     for (uint i = 0; i < countof(threads); i++)
411         thread_resume(threads[i]);
412 
413     /* wait for them to all stop */
414     for (uint i = 0; i < countof(threads); i++) {
415         thread_join(threads[i], NULL, INFINITE_TIME);
416     }
417 
418     printf("atomic count == %d (should be zero)\n", atomic);
419 }
420 
421 static volatile int preempt_count;
422 
preempt_tester(void * arg)423 static int preempt_tester(void *arg) {
424     spin(1000000);
425 
426     printf("exiting ts %lld\n", current_time_hires());
427 
428     atomic_add(&preempt_count, -1);
429 #undef COUNT
430 
431     return 0;
432 }
433 
preempt_test(void)434 static void preempt_test(void) {
435     /* create 5 threads, let them run. If the system is properly timer preempting,
436      * the threads should interleave each other at a fine enough granularity so
437      * that they complete at roughly the same time. */
438     printf("testing preemption\n");
439 
440     preempt_count = 5;
441 
442     for (int i = 0; i < preempt_count; i++)
443         thread_detach_and_resume(thread_create("preempt tester", &preempt_tester, NULL, LOW_PRIORITY, DEFAULT_STACK_SIZE));
444 
445     while (preempt_count > 0) {
446         thread_sleep(1000);
447     }
448 
449     printf("done with preempt test, above time stamps should be very close\n");
450 
451     /* do the same as above, but mark the threads as real time, which should
452      * effectively disable timer based preemption for them. They should
453      * complete in order, about a second apart. */
454     printf("testing real time preemption\n");
455 
456     preempt_count = 5;
457 
458     for (int i = 0; i < preempt_count; i++) {
459         thread_t *t = thread_create("preempt tester", &preempt_tester, NULL, LOW_PRIORITY, DEFAULT_STACK_SIZE);
460         thread_set_real_time(t);
461         thread_detach_and_resume(t);
462     }
463 
464     while (preempt_count > 0) {
465         thread_sleep(1000);
466     }
467 
468     printf("done with real-time preempt test, above time stamps should be 1 second apart\n");
469 }
470 
join_tester(void * arg)471 static int join_tester(void *arg) {
472     long val = (long)arg;
473 
474     printf("\t\tjoin tester starting\n");
475     thread_sleep(500);
476     printf("\t\tjoin tester exiting with result %ld\n", val);
477 
478     return val;
479 }
480 
join_tester_server(void * arg)481 static int join_tester_server(void *arg) {
482     int ret;
483     status_t err;
484     thread_t *t;
485 
486     printf("\ttesting thread_join/thread_detach\n");
487 
488     printf("\tcreating and waiting on thread to exit with thread_join\n");
489     t = thread_create("join tester", &join_tester, (void *)1, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
490     thread_resume(t);
491     ret = 99;
492     printf("\tthread magic is 0x%x (should be 0x%x)\n", t->magic, THREAD_MAGIC);
493     err = thread_join(t, &ret, INFINITE_TIME);
494     printf("\tthread_join returns err %d, retval %d\n", err, ret);
495     printf("\tthread magic is 0x%x (should be 0)\n", t->magic);
496 
497     printf("\tcreating and waiting on thread to exit with thread_join, after thread has exited\n");
498     t = thread_create("join tester", &join_tester, (void *)2, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
499     thread_resume(t);
500     thread_sleep(1000); // wait until thread is already dead
501     ret = 99;
502     printf("\tthread magic is 0x%x (should be 0x%x)\n", t->magic, THREAD_MAGIC);
503     err = thread_join(t, &ret, INFINITE_TIME);
504     printf("\tthread_join returns err %d, retval %d\n", err, ret);
505     printf("\tthread magic is 0x%x (should be 0)\n", t->magic);
506 
507     printf("\tcreating a thread, detaching it, let it exit on its own\n");
508     t = thread_create("join tester", &join_tester, (void *)3, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
509     thread_detach(t);
510     thread_resume(t);
511     thread_sleep(1000); // wait until the thread should be dead
512     printf("\tthread magic is 0x%x (should be 0)\n", t->magic);
513 
514     printf("\tcreating a thread, detaching it after it should be dead\n");
515     t = thread_create("join tester", &join_tester, (void *)4, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
516     thread_resume(t);
517     thread_sleep(1000); // wait until thread is already dead
518     printf("\tthread magic is 0x%x (should be 0x%x)\n", t->magic, THREAD_MAGIC);
519     thread_detach(t);
520     printf("\tthread magic is 0x%x\n", t->magic);
521 
522     printf("\texiting join tester server\n");
523 
524     return 55;
525 }
526 
join_test(void)527 static void join_test(void) {
528     int ret;
529     status_t err;
530     thread_t *t;
531 
532     printf("testing thread_join/thread_detach\n");
533 
534     printf("creating thread join server thread\n");
535     t = thread_create("join tester server", &join_tester_server, (void *)1, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
536     thread_resume(t);
537     ret = 99;
538     err = thread_join(t, &ret, INFINITE_TIME);
539     printf("thread_join returns err %d, retval %d (should be 0 and 55)\n", err, ret);
540 }
541 
spinlock_test(void)542 static void spinlock_test(void) {
543     spin_lock_saved_state_t state;
544     spin_lock_t lock;
545 
546     spin_lock_init(&lock);
547 
548     // verify basic functionality (single core)
549     printf("testing spinlock:\n");
550     ASSERT(!spin_lock_held(&lock));
551     ASSERT(!arch_ints_disabled());
552     spin_lock_irqsave(&lock, state);
553     ASSERT(arch_ints_disabled());
554     ASSERT(spin_lock_held(&lock));
555     spin_unlock_irqrestore(&lock, state);
556     ASSERT(!spin_lock_held(&lock));
557     ASSERT(!arch_ints_disabled());
558     printf("seems to work\n");
559 
560 #define COUNT (1024*1024)
561     arch_interrupt_save(&state, SPIN_LOCK_FLAG_INTERRUPTS);
562     uint32_t c = arch_cycle_count();
563     for (uint i = 0; i < COUNT; i++) {
564         spin_lock(&lock);
565         spin_unlock(&lock);
566     }
567     c = arch_cycle_count() - c;
568     arch_interrupt_restore(state, SPIN_LOCK_FLAG_INTERRUPTS);
569 
570     printf("%u cycles to acquire/release lock %u times (%u cycles per)\n", c, COUNT, c / COUNT);
571 
572     c = arch_cycle_count();
573     for (uint i = 0; i < COUNT; i++) {
574         spin_lock_irqsave(&lock, state);
575         spin_unlock_irqrestore(&lock, state);
576     }
577     c = arch_cycle_count() - c;
578 
579     printf("%u cycles to acquire/release lock w/irqsave %u times (%u cycles per)\n", c, COUNT, c / COUNT);
580 #undef COUNT
581 }
582 
thread_tests(int argc,const console_cmd_args * argv)583 int thread_tests(int argc, const console_cmd_args *argv) {
584     mutex_test();
585     semaphore_test();
586     event_test();
587 
588     spinlock_test();
589     atomic_test();
590 
591     thread_sleep(200);
592     context_switch_test();
593 
594     preempt_test();
595 
596     join_test();
597 
598     return 0;
599 }
600 
spinner_thread(void * arg)601 static int spinner_thread(void *arg) {
602     for (;;)
603         ;
604 
605     return 0;
606 }
607 
spinner(int argc,const console_cmd_args * argv)608 int spinner(int argc, const console_cmd_args *argv) {
609     if (argc < 2) {
610         printf("not enough args\n");
611         printf("usage: %s <priority> <rt>\n", argv[0].str);
612         return -1;
613     }
614 
615     thread_t *t = thread_create("spinner", spinner_thread, NULL, argv[1].u, DEFAULT_STACK_SIZE);
616     if (!t)
617         return ERR_NO_MEMORY;
618 
619     if (argc >= 3 && !strcmp(argv[2].str, "rt")) {
620         thread_set_real_time(t);
621     }
622     thread_resume(t);
623 
624     return 0;
625 }
626