| // Copyright Microsoft and CHERIoT Contributors. | |
| // SPDX-License-Identifier: MIT | |
| #include <errno.h> | |
| #include <queue.h> | |
| #include <ring_buffer.hh> | |
| #include <thread.h> | |
| #include <thread_pool.h> | |
| using namespace CHERI; | |
| namespace | |
| { | |
| /** | |
| * Ring buffer. We use a ticket lock to ensure that things are dispatched | |
| * in order (independent of priority) on the sending side and a flag lock on | |
| * the other side to ensure that the highest-priority thread picks up | |
| * messages as soon as they're ready. | |
| */ | |
| RingBuffer<ThreadPoolMessage, 16, TicketLock, FlagLock> queue; | |
| } // namespace | |
| int thread_pool_async(ThreadPoolCallback fn, void *data) | |
| { | |
| Capability<void> fnCap{reinterpret_cast<void *>(fn)}; | |
| Capability<void> dataCap{data}; | |
| // The function must be sealed with the type used for export table entries | |
| // for us to be able to invoke it. The data capability doesn't *have* to | |
| // be sealed, but it's a bad idea if it is unsealed because it adds the | |
| // thread pool to the TCB for confidentiality and integrity. | |
| // We want to avoid this being able to make us trap and so we validate that | |
| // the function is cross-compartment entry point and both can be stored in | |
| // the message queue. | |
| if (!fnCap.is_valid() || (fnCap.type() != 9) || | |
| !fnCap.permissions().contains(Permission::Global) || | |
| (dataCap.is_valid() && !dataCap.is_sealed()) || | |
| (dataCap.is_valid() && | |
| !dataCap.permissions().contains(Permission::Global))) | |
| { | |
| return -EINVAL; | |
| } | |
| queue.push({fn, data}); | |
| return 0; | |
| } | |
| void __cheri_compartment("thread_pool") thread_pool_run() | |
| { | |
| while (true) | |
| { | |
| ThreadPoolMessage message = queue.pop(); | |
| message.invoke(message.data); | |
| } | |
| } |