1/* Implements slab workers and daemon threads */
2#include <mem/alloc_or_die.h>
3#include <smp/domain.h>
4#include <thread/daemon.h>
5#include <thread/workqueue.h>
6
7#include "gc_internal.h"
8
9/* Ok, there's quite a bit of background work to be done. */
10
11/* We first want to go ahead and flush our freequeue. We'll trylock
12 * the individual per-cpu caches to see if we can sneak some of the
13 * elements in there */
14static enum daemon_thread_command slab_background_work(void *a, void *b) {
15 /* TODO: */
16 return DAEMON_THREAD_COMMAND_DEFAULT;
17}
18
19/* Aside on deferred frees:
20 *
21 * We can guarantee that any slab object is at minimum pointer sized
22 *
23 * This means we can embed a *next pointer in every object to another
24 * object. The idea is this:
25 *
26 * The address of every node is what we free. We use the *next at the
27 * node to get to the next one to free it too. SLIST semantics
28 * mean that this can be MPSC, which is what we're looking to be
29 * able to pull off here.
30 */
31static void slab_defer_free_dpc(struct dpc *unused, void *unused_arg) {
32 (void) unused_arg;
33 struct slab_percpu_cache *c = slab_percpu_cache_local();
34 /* Enter a loop here of stealing the defer free list,
35 * and so long as we actually steal something, we
36 * drain whatever is on that list */
37
38 struct mpsc_slist_node *tmp, *iter, *got = NULL;
39 while ((got = mpsc_slist_drain(q: &c->defer_frees))) {
40 got = mpsc_slist_reverse(chain: got);
41 mpsc_slist_for_each_safe(iter, tmp, got) {
42 kfree(iter);
43 }
44 }
45}
46
47static struct daemon_work bg =
48 DAEMON_WORK_FROM(slab_background_work, WORK_ARGS(NULL, NULL));
49
50void slab_domain_init_daemon(struct slab_domain *domain) {
51 struct cpu_mask cmask;
52 alloc_or_die(cpu_mask_init(&cmask, global.core_count));
53
54 domain_set_cpu_mask(mask: &cmask, domain: domain->domain);
55 struct daemon_attributes attrs = {
56 .max_timesharing_threads = 0,
57 .min_timesharing_threads = 0,
58 .thread_cpu_mask = cmask,
59 .flags = DAEMON_FLAG_NO_TS_THREADS | DAEMON_FLAG_HAS_NAME,
60 };
61
62 domain->daemon = daemon_create(
63 /* fmt = */ "slab_domain_%u",
64 /* attrs = */ &attrs,
65 /* timesharing_work = */ NULL,
66 /* background_work = */ &bg,
67 /* wq_attrs = */ NULL,
68 /* ... = */ domain->domain->id);
69}
70
71void slab_domain_init_workqueue(struct slab_domain *domain) {
72 struct cpu_mask mask = {0};
73 alloc_or_die(cpu_mask_init(&mask, global.core_count));
74
75 domain_set_cpu_mask(mask: &mask, domain: domain->domain);
76
77 struct workqueue_attributes attrs = {
78 .capacity = WORKQUEUE_DEFAULT_CAPACITY,
79
80 /* We set static_workers and spawn_via_request to make these safe
81 * here since if those weren't set the dynamic memory allocation
82 * could potentially spiral into bigger problems... */
83 .flags = WORKQUEUE_FLAG_STATIC_WORKERS | WORKQUEUE_FLAG_NAMED |
84 WORKQUEUE_FLAG_NO_WORKER_GC,
85 .max_workers = domain->domain->num_cores,
86 .min_workers = 1,
87 .spawn_delay = WORKQUEUE_DEFAULT_SPAWN_DELAY,
88 .worker_cpu_mask = mask,
89 .worker_niceness = 0,
90 .idle_check = WORKQUEUE_DEFAULT_IDLE_CHECK,
91 };
92
93 domain->workqueue =
94 workqueue_create(fmt: "slab_domain_%u_wq", attrs: &attrs, domain->domain->id);
95 for (size_t i = 0; i < domain->domain->num_cores; i++) {
96 struct dpc *defer_dpc = &domain->percpu_caches[i]->defer_dpc;
97 dpc_init(d: defer_dpc, fn: slab_defer_free_dpc, NULL);
98 }
99}
100
101void kfree_defer_irq(void *ptr) {
102 kassert(irq_in_interrupt());
103 if (!ptr)
104 return;
105
106 struct slab_percpu_cache *pc = slab_percpu_cache_local();
107 struct mpsc_slist_node *n = ptr;
108 mpsc_slist_push(q: &pc->defer_frees, n);
109 dpc_enqueue_local(d: &pc->defer_dpc, e: DPC_NONE);
110}
111