1use alloc::alloc::{alloc, dealloc, handle_alloc_error};
2use core::{alloc::Layout, iter::zip, mem::MaybeUninit, ptr::NonNull};
3
4use ax_kernel_guard::NoPreempt;
5use ax_percpu::CpuPin;
6use spin::Once;
7
8use crate::{
9 boxed::ItemBox,
10 item::{Item, Registry},
11};
12
13pub struct Scope {
15 ptr: NonNull<ItemSlot>,
16}
17
18unsafe impl Send for Scope {}
19unsafe impl Sync for Scope {}
20
21impl Scope {
22 fn len() -> usize {
23 Registry.len()
24 }
25
26 fn layout() -> Layout {
27 Layout::array::<ItemSlot>(Self::len()).unwrap()
28 }
29
30 pub fn new() -> Self {
33 let layout = Self::layout();
34 let ptr = NonNull::new(unsafe { alloc(layout) })
35 .unwrap_or_else(|| handle_alloc_error(layout))
36 .cast();
37
38 let slice = unsafe {
39 core::slice::from_raw_parts_mut(ptr.cast::<MaybeUninit<_>>().as_ptr(), Registry.len())
40 };
41 for (item, d) in zip(&*Registry, slice) {
42 d.write(ItemSlot::new(item));
43 }
44
45 Self { ptr }
46 }
47
48 pub(crate) fn get(&self, item: &'static Item) -> &ItemBox {
49 let index = item.index();
50 unsafe { self.ptr.add(index).as_ref() }.get()
51 }
52
53 pub(crate) fn get_mut(&mut self, item: &'static Item) -> &mut ItemBox {
54 let index = item.index();
55 unsafe { self.ptr.add(index).as_mut() }.get_mut()
56 }
57}
58
59impl Default for Scope {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl Drop for Scope {
66 fn drop(&mut self) {
67 let ptr = NonNull::slice_from_raw_parts(self.ptr, Self::len());
68 unsafe {
69 ptr.drop_in_place();
70 dealloc(self.ptr.cast().as_ptr(), Self::layout());
71 }
72 }
73}
74
75struct ItemSlot {
76 item: &'static Item,
77 value: Once<ItemBox>,
78}
79
80impl ItemSlot {
81 fn new(item: &'static Item) -> Self {
82 Self {
83 item,
84 value: Once::new(),
85 }
86 }
87
88 fn get(&self) -> &ItemBox {
89 self.value.call_once(|| ItemBox::new(self.item))
90 }
91
92 fn get_mut(&mut self) -> &mut ItemBox {
93 if !self.value.is_completed() {
94 let item = self.item;
95 self.value.call_once(|| ItemBox::new(item));
96 }
97 self.value
98 .get_mut()
99 .expect("scope-local item must be initialized")
100 }
101
102 fn try_get(&self) -> Option<&ItemBox> {
103 self.value.get()
104 }
105}
106
107static GLOBAL_SCOPE: Once<Scope> = Once::new();
108
109#[ax_percpu::def_percpu]
110pub(crate) static ACTIVE_SCOPE_PTR: usize = 0;
111
112pub struct ActiveScope;
114
115impl ActiveScope {
116 pub unsafe fn set(scope: &Scope) {
124 let _guard = NoPreempt::new();
125 unsafe {
128 ax_percpu::with_cpu_pin(|pin| Self::set_pinned(scope, pin))
129 .expect("scope-local access requires an installed CPU area")
130 };
131 }
132
133 pub unsafe fn set_pinned(scope: &Scope, pin: &CpuPin<'_>) {
139 ACTIVE_SCOPE_PTR.write_current(pin, scope.ptr.addr().get());
140 }
141
142 pub fn set_global() {
144 let _guard = NoPreempt::new();
145 unsafe {
148 ax_percpu::with_cpu_pin(Self::set_global_pinned)
149 .expect("scope-local access requires an installed CPU area")
150 };
151 }
152
153 pub fn set_global_pinned(pin: &CpuPin<'_>) {
155 ACTIVE_SCOPE_PTR.write_current(pin, 0);
156 }
157
158 pub fn is_global() -> bool {
160 let _guard = NoPreempt::new();
161 unsafe {
163 ax_percpu::with_cpu_pin(Self::is_global_pinned)
164 .expect("scope-local access requires an installed CPU area")
165 }
166 }
167
168 pub fn is_global_pinned(pin: &CpuPin<'_>) -> bool {
170 ACTIVE_SCOPE_PTR.read_current(pin) == 0
171 }
172
173 pub(crate) fn with_item<R>(
174 item: &'static Item,
175 pin: &CpuPin<'_>,
176 operation: impl for<'access> FnOnce(&'access ItemBox) -> R,
177 ) -> R {
178 let ptr = ACTIVE_SCOPE_PTR.read_current(pin);
179 let ptr = NonNull::new(ptr as *mut ItemSlot)
180 .unwrap_or_else(|| GLOBAL_SCOPE.call_once(Scope::new).ptr);
181 let index = item.index();
182 operation(unsafe { ptr.add(index).as_ref() }.get())
183 }
184
185 pub(crate) fn try_with_item<R>(
186 item: &'static Item,
187 pin: &CpuPin<'_>,
188 operation: impl for<'access> FnOnce(&'access ItemBox) -> R,
189 ) -> Option<R> {
190 let ptr = ACTIVE_SCOPE_PTR.read_current(pin);
191 let ptr = if ptr == 0 {
192 GLOBAL_SCOPE.get()?.ptr
193 } else {
194 NonNull::new(ptr as *mut ItemSlot)?
195 };
196 let index = item.index();
197 Some(operation(unsafe { ptr.add(index).as_ref() }.try_get()?))
198 }
199}