1/* Implements slab workers and daemon threads */
2#include <mem/nofail.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 cc_ignore(a, b);
16 /* TODO: */
17 return DAEMON_THREAD_COMMAND_DEFAULT;
18}
19
20/* Aside on deferred frees:
21 *
22 * We can guarantee that any slab object is at minimum pointer sized
23 *
24 * This means we can embed a *next pointer in every object to another
25 * object. The idea is this:
26 *
27 * The address of every node is what we free. We use the *next at the
28 * node to get to the next one to free it too. SLIST semantics
29 * mean that this can be MPSC, which is what we're looking to be
30 * able to pull off here.
31 */
32static void slab_defer_free_dpc(struct dpc_context *dc, void *unused_arg,
33 void *unused_arg2) {
34 cc_ignore(dc);
35 cc_ignore(unused_arg2);
36 cc_ignore(unused_arg);
37 struct slab_percpu_cache *c = slab_percpu_cache_local();
38 /* Enter a loop here of stealing the defer free list,
39 * and so long as we actually steal something, we
40 * drain whatever is on that list */
41
42 struct mpsc_lifo_list_node *tmp, *iter, *got = NULL;
43 while ((got = mpsc_lifo_list_drain(q: &c->defer_frees))) {
44 got = mpsc_lifo_list_reverse(chain: got);
45 mpsc_lifo_list_for_each_safe(iter, tmp, got) {
46 vfree(iter);
47 }
48 }
49}
50
51static struct daemon_work bg =
52 DAEMON_WORK_FROM(slab_background_work, WORK_ARGS(NULL, NULL));
53
54void slab_domain_init_daemon(struct slab_domain *domain) {
55 struct cpu_mask cmask;
56 cpu_mask_init(m: &cmask, nbits: global.core_count);
57
58 domain_set_cpu_mask(mask: &cmask, domain: domain->domain);
59 struct daemon_attributes attrs = {
60 .max_timesharing_threads = 0,
61 .min_timesharing_threads = 0,
62 .thread_cpu_mask = cmask,
63 .flags = DAEMON_FLAG_NO_TS_THREADS | DAEMON_FLAG_HAS_NAME,
64 };
65
66 domain->daemon = daemon_create(
67 /* name = */ ("slab_domain_%zu", domain->domain->id),
68 /* attrs = */ &attrs,
69 /* timesharing_work = */ NULL,
70 /* background_work = */ &bg,
71 /* wq_attrs = */ NULL);
72}
73
74void slab_domain_init_workqueue(struct slab_domain *domain) {
75 struct cpu_mask mask = {0};
76 cpu_mask_init(m: &mask, nbits: global.core_count);
77
78 domain_set_cpu_mask(mask: &mask, domain: domain->domain);
79
80 struct workqueue_attributes attrs = {
81 /* We set static_workers and spawn_via_request to make these safe
82 * here since if those weren't set the dynamic memory allocation
83 * could potentially spiral into bigger problems... */
84 .flags = WORKQUEUE_FLAG_STATIC_WORKERS | WORKQUEUE_FLAG_NAMED |
85 WORKQUEUE_FLAG_NO_WORKER_GC,
86 .max_workers = domain->domain->num_cores,
87 .min_workers = 1,
88 .spawn_delay = WORKQUEUE_DEFAULT_SPAWN_DELAY,
89 .worker_cpu_mask = mask,
90 .worker_niceness = 0,
91 .idle_check = WORKQUEUE_DEFAULT_IDLE_CHECK,
92 };
93
94 domain->workqueue =
95 workqueue_create(("slab_domain_%zu_wq", domain->domain->id), &attrs);
96 for (size_t i = 0; i < domain->domain->num_cores; i++) {
97 struct dpc *defer_dpc = &domain->percpu_caches[i]->defer_dpc;
98 dpc_init(d: defer_dpc, fn: slab_defer_free_dpc, NULL, NULL);
99 }
100}
101
102void vfree_defer_irq(void *ptr) {
103 enforce(irq_in_interrupt(), INVALID_CONTEXT);
104 if (!ptr)
105 return;
106
107 struct slab_percpu_cache *pc = slab_percpu_cache_local();
108 struct mpsc_lifo_list_node *n = ptr;
109 mpsc_lifo_list_push(q: &pc->defer_frees, n);
110 dpc_enqueue_local(d: &pc->defer_dpc);
111}
112