| // 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); |
| } |
| } |