MOS Source Code
Loading...
Searching...
No Matches
buddy_core.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "mos/assert.hpp"
8
9#include <algorithm>
11#include <mos_stdlib.hpp>
12
13constexpr size_t pow2(size_t x)
14{
15 return 1 << x;
16}
17
18constexpr size_t log2(size_t x)
19{
20 return x ? (sizeof(x) * 8 - 1 - __builtin_clzl(x)) : 0;
21}
22
23#define log2_ceil(x) (log2(x) + (pow2(log2(x)) < x))
24
25static const size_t orders[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
26static const size_t max_order = MOS_ARRAY_SIZE(orders) - 1;
27
28static struct
29{
32
34
35static void add_to_freelist(size_t order, phyframe_t *frame)
36{
38 MOS_ASSERT(frame->state == phyframe::PHYFRAME_FREE);
39 frame->order = order;
40 list_node_t *frame_node = list_node(&frame->info);
41 MOS_ASSERT(list_is_empty(frame_node));
42
43 list_head *const head = &buddy.freelists[order];
44 list_head *node = head->next;
45
46 // performance hot spot, even a binary tree would always be faster
47 while (node != head && node < frame_node)
48 node = node->next;
49
50 list_node_insert_before(node, frame_node);
51}
52
53static pfn_t get_buddy_pfn(size_t page_pfn, size_t order)
54{
55 // the address of a block's "buddy" is equal to the XOR of the block's address and the block's size.
56 return page_pfn ^ pow2(order);
57}
58
59static void dump_list(size_t order)
60{
62 const list_head *head = &buddy.freelists[order];
63 pr_cont("\nlist of order %zu: ", order);
64 list_foreach(phyframe_t::additional_info, info, *head)
65 {
66 const auto frame = container_of(info, phyframe_t, info);
67
68 if (order == 0)
69 pr_cont("[" PFN_FMT "] ", phyframe_pfn(frame));
70 else
71 pr_cont(PFN_RANGE " ", phyframe_pfn(frame), phyframe_pfn(frame) + pow2(order) - 1);
72 }
74}
75
83static void populate_freelist(const size_t start_pfn, const size_t nframes, const size_t order)
84{
86
87 const size_t step = pow2(order);
88
89 pr_dinfo2(pmm_buddy, " order: %zu, step: %zu", order, step);
90
91 pfn_t current = start_pfn;
92 size_t nframes_left = nframes;
93
94 for (; current + step <= start_pfn + nframes; current += step)
95 {
96 phyframe_t *const frame = pfn_phyframe(current);
98 frame->state = phyframe::PHYFRAME_FREE; // free or reserved
99
100 pr_dinfo2(pmm_buddy, " - " PFN_RANGE, current, current + step - 1);
101 frame->order = order;
102 add_to_freelist(order, frame);
103 nframes_left -= step;
104 }
105
106 // we have some frames left over, add them to a smaller order
107 if (nframes_left)
108 populate_freelist(current, nframes_left, order - 1);
109}
110
111static void break_this_pfn(pfn_t this_pfn, size_t this_order)
112{
114
115 phyframe_t *const frame = pfn_phyframe(this_pfn);
116 MOS_ASSERT(frame->state == phyframe::PHYFRAME_FREE); // must be free
117 list_remove(&frame->info);
118
119 // split this frame into two frames of order-1
120 const pfn_t frame2_pfn = this_pfn + pow2(this_order - 1); // pow2(order) / 2
121 pr_dinfo2(pmm_buddy, " breaking order %zu" PFN_RANGE " -> " PFN_RANGE " and " PFN_RANGE, this_order, this_pfn, this_pfn + pow2(this_order) - 1, this_pfn,
122 frame2_pfn - 1, frame2_pfn, frame2_pfn + pow2(this_order - 1) - 1);
123
124 phyframe_t *const frame2 = pfn_phyframe(frame2_pfn);
125 linked_list_init(list_node(&frame2->info)); // this must not be in any list, so we init it
126 frame2->state = frame->state; // which is PHYFRAME_FREE
127
128 add_to_freelist(this_order - 1, frame);
129 add_to_freelist(this_order - 1, frame2);
130}
131
132static void extract_exact_range(pfn_t start, size_t nframes, enum phyframe::phyframe_state state)
133{
135
136 size_t last_nframes = 0;
137
138 while (nframes)
139 {
140 if (last_nframes == nframes)
141 {
142 phyframe_t *frame = pfn_phyframe(start);
143 if (state == phyframe::PHYFRAME_RESERVED && frame->state == phyframe::PHYFRAME_RESERVED)
144 {
145 // XXX: A great hack, there may be overlapped reserved ranges.
146 MOS_ASSERT(frame->order == 0);
147 start++;
148 nframes--;
149 }
150 else
151 {
152 mos_panic("infinite loop detected");
153 }
154 }
155
156 last_nframes = nframes;
157
158 MOS_ASSERT_X(start <= pmm_total_frames, "insane!");
159 pr_dinfo2(pmm_buddy, " extracting, n left: %zu, start: " PFN_FMT, nframes, start);
160
161 for (size_t order = max_order; order != (size_t) -1; order--)
162 {
163 // find if the freelist of this order contains something [start]
164 list_head *freelist = &buddy.freelists[order];
165 if (list_is_empty(freelist))
166 continue; // no, the frame must be at a lower order
167
168 list_foreach(phyframe_t::additional_info, info, *freelist)
169 {
170 const auto f = container_of(info, phyframe_t, info);
171 const pfn_t start_pfn = phyframe_pfn(f);
172 const pfn_t end_pfn = start_pfn + pow2(order) - 1;
173
174 if (start_pfn == start)
175 {
176 // so, we found a set of frame that starts with [start], here are the cases:
177 // - pow2(order) < nframes: we not only need this frame, but also some more frames
178 // - pow2(order) = nframes: we need this frame, and we are done
179 // - pow2(order) > nframes: we need this frame, but we need to break it into two smaller frames so that
180 // in the next iteration, a more precise subset of this frame can be found
181
182 pr_dinfo2(pmm_buddy, " found a frame that starts with " PFN_FMT "...", start);
183 if (pow2(order) <= nframes)
184 {
185 list_remove(&f->info);
186 f->state = state;
187 f->order = 0;
188
189 nframes -= pow2(order);
190 start += pow2(order);
191
192 pr_dinfo2(pmm_buddy, " done, n left: %zu, start: " PFN_FMT, nframes, start);
193 break; // we're done with the current order
194 }
195 else
196 {
197 pr_dinfo2(pmm_buddy, " narrowing down...");
198 // break this frame into two smaller frames, so that in the next iteration
199 // we can find a frame that exactly ends with [start + nframes - 1]
200 break_this_pfn(start_pfn, order);
201 break;
202 }
203 }
204
205 if (start_pfn <= start && end_pfn >= start)
206 {
207 pr_dinfo2(pmm_buddy, " found a frame that contains " PFN_FMT, start);
208 // we found a frame that contains [start]
209 // so we will break it into two frames,
210 // - one of which contains [start], and
211 // - the other contains [start + 1, end_pfn]
212
213 // the lower order frames will be broken down in the next iteration of this loop
214 // until we have a frame that exactly starts with [start]
215 break_this_pfn(start_pfn, order);
216 break;
217 }
218 }
219
220 if (nframes == 0)
221 break; // fast exit path
222 }
223 }
224}
225
226static void break_the_order(const size_t order)
227{
228 if (order > orders[MOS_ARRAY_SIZE(orders) - 1])
229 return; // we can't break any further
230
231 const list_head *freelist = &buddy.freelists[order];
232 if (list_is_empty(freelist))
233 break_the_order(order + 1);
234
235 if (list_is_empty(freelist))
236 {
237 pr_dinfo2(pmm_buddy, " no free frames of order %zu, can't break", order);
238 return; // out of memory!
239 }
240
241 const auto info = list_entry(freelist->next, phyframe_t::additional_info);
242 const auto frame = container_of(info, phyframe_t, info);
243 MOS_ASSERT(frame->state == phyframe::PHYFRAME_FREE);
244 list_remove(info);
245
246 const pfn_t frame_pfn = phyframe_pfn(frame);
247 const pfn_t frame2_pfn = frame_pfn + pow2(order - 1); // pow2(order) / 2
248
249 pr_dinfo2(pmm_buddy, " breaking order %3zu, " PFN_RANGE " -> " PFN_RANGE " and " PFN_RANGE, order, frame_pfn, frame_pfn + pow2(order) - 1, frame_pfn, frame2_pfn - 1,
250 frame2_pfn, frame2_pfn + pow2(order - 1) - 1);
251
252 phyframe_t *const frame2 = pfn_phyframe(frame2_pfn);
254 frame2->state = phyframe::PHYFRAME_FREE;
255
256 add_to_freelist(order - 1, frame);
257 add_to_freelist(order - 1, frame2);
258}
259
268[[nodiscard]] static bool try_merge(const pfn_t pfn, u8 order)
269{
270 if (order > orders[MOS_ARRAY_SIZE(orders) - 1])
271 {
272 pr_dinfo2(pmm_buddy, " order %hhu is too large, cannot merge", order);
273 return false;
274 }
275
276 phyframe_t *const frame = pfn_phyframe(pfn);
277
278 const pfn_t buddy_pfn = get_buddy_pfn(pfn, order);
279 if (buddy_pfn >= pmm_total_frames)
280 return false;
281
282 phyframe_t *const buddy = pfn_phyframe(buddy_pfn);
283 if (buddy->state != phyframe::PHYFRAME_FREE)
284 {
285 pr_dinfo2(pmm_buddy, " buddy pfn " PFN_FMT " is not free for pfn " PFN_FMT ", not merging", buddy_pfn, pfn);
286 return false;
287 }
288
289 if (buddy->order != order)
290 {
291 pr_dinfo2(pmm_buddy, " buddy pfn " PFN_FMT " is not the same order (%hhu != %hhu) as " PFN_FMT ", not merging", buddy_pfn, buddy->order, order, pfn);
292 return false;
293 }
294
295 list_remove(&buddy->info);
296 frame->state = phyframe::PHYFRAME_FREE;
297
298 pr_dinfo2(pmm_buddy, " merging order %hhu, " PFN_RANGE " and " PFN_RANGE, order, pfn, pfn + pow2(order) - 1, buddy_pfn, buddy_pfn + pow2(order) - 1);
299
300 const size_t high_order_pfn = std::min(pfn, buddy_pfn); // the lower pfn
301
302 if (!try_merge(high_order_pfn, order + 1))
303 {
304 phyframe_t *const high_order_frame = pfn_phyframe(high_order_pfn);
305 linked_list_init(list_node(&high_order_frame->info));
306 high_order_frame->state = phyframe::PHYFRAME_FREE;
307 add_to_freelist(order + 1, high_order_frame); // use the lower pfn
308 }
309 return true;
310}
311
313{
314 for (size_t i = 0; i < MOS_ARRAY_SIZE(buddy.freelists); i++)
315 dump_list(i);
316
317 pr_info("");
318}
319
320void buddy_init(size_t max_nframes)
321{
323 for (size_t i = 0; i < MOS_ARRAY_SIZE(buddy.freelists); i++)
324 {
325 pr_dinfo2(pmm_buddy, "init freelist[%zu], order: %zu", i, orders[i]);
326 linked_list_init(&buddy.freelists[i]);
327 }
328
329 const size_t order = std::min(log2(max_nframes), orders[MOS_ARRAY_SIZE(orders) - 1]);
330 populate_freelist(0, max_nframes, order);
332}
333
334void buddy_reserve_n(pfn_t pfn, size_t nframes)
335{
337 pr_dinfo2(pmm_buddy, "reserving " PFN_RANGE " (%zu frames)", pfn, pfn + nframes - 1, nframes);
338 extract_exact_range(pfn, nframes, phyframe::PHYFRAME_RESERVED);
340}
341
343{
345 const size_t order = log2_ceil(nframes);
346
347 // check if this order is too large
348 if (order > orders[MOS_ARRAY_SIZE(orders) - 1])
349 return NULL;
350
351 pr_dinfo2(pmm_buddy, "allocating %zu contiguous frames (order %zu, which is %zu frames, wasting %zu frames)", nframes, order, pow2(order), pow2(order) - nframes);
352
353 list_head *free = &buddy.freelists[order];
354 if (list_is_empty(free))
355 break_the_order(order + 1);
356
357 if (unlikely(list_is_empty(free)))
358 {
360 pr_emerg("no free frames of order %zu, can't break", order);
361 pr_emerg("out of memory!");
362 return NULL; // out of memory!
363 }
364
365 const auto frame = container_of(list_entry(free->next, phyframe_t::additional_info), phyframe_t, info);
366 const pfn_t start = phyframe_pfn(frame);
367
368 extract_exact_range(start, nframes, phyframe::PHYFRAME_ALLOCATED); // extract the exact range from the buddy.freelists
369
370 for (size_t i = 0; i < nframes; i++)
371 {
372 phyframe_t *const f = pfn_phyframe(start + i);
373 f->state = phyframe::PHYFRAME_ALLOCATED;
374 f->order = 0; // so that they can be freed individually
375 }
376
378 return frame;
379}
380
381void buddy_free_n(pfn_t pfn, size_t nframes)
382{
383 pr_dinfo2(pmm_buddy, "freeing " PFN_RANGE " (%zu frames)", pfn, pfn + nframes - 1, nframes);
385
386 phyframe_t *const frame = pfn_phyframe(pfn);
387 MOS_ASSERT_X(frame->state == phyframe::PHYFRAME_ALLOCATED, "frame must be allocated");
389 frame->state = phyframe::PHYFRAME_FREE;
390
391 const size_t order = log2_ceil(nframes);
392 if (!try_merge(pfn, order))
393 add_to_freelist(order, frame);
394
396}
#define MOS_ASSERT_X(cond, msg,...)
Definition assert.hpp:15
#define MOS_ASSERT(cond)
Definition assert.hpp:14
static bool try_merge(const pfn_t pfn, u8 order)
Try finding a buddy frame and merging it with the given frame.
phyframe_t * buddy_alloc_n_exact(size_t nframes)
Allocate nframes of contiguous physical memory.
static const size_t orders[]
static void break_the_order(const size_t order)
static void extract_exact_range(pfn_t start, size_t nframes, enum phyframe::phyframe_state state)
static void dump_list(size_t order)
static struct @157025346226044251143103256261246056252331122177 buddy
void buddy_dump_all()
Dump the state of the buddy allocator.
constexpr size_t log2(size_t x)
list_head freelists[MOS_ARRAY_SIZE(orders)]
static void populate_freelist(const size_t start_pfn, const size_t nframes, const size_t order)
Add [start_pfn, start_pfn + nframes - 1] to the freelist of order order
static pfn_t get_buddy_pfn(size_t page_pfn, size_t order)
#define log2_ceil(x)
static const size_t max_order
void buddy_free_n(pfn_t pfn, size_t nframes)
Free nframes of contiguous physical memory.
constexpr size_t pow2(size_t x)
static void add_to_freelist(size_t order, phyframe_t *frame)
void buddy_init(size_t max_nframes)
Initialize the buddy allocator with the given maximum number of frames.
static void break_this_pfn(pfn_t this_pfn, size_t this_order)
void buddy_reserve_n(pfn_t pfn, size_t nframes)
Reserve several frames at the given physical frame number.
static spinlock_t buddy_lock
MOSAPI void linked_list_init(list_node_t *head_node)
Initialise a circular double linked list.
Definition list.cpp:15
MOSAPI void list_node_insert_before(list_node_t *element, list_node_t *item)
Definition list.cpp:74
#define list_foreach(t, v, h)
Iterate over a list.
Definition list.hpp:89
#define list_node(element)
Get the ‘list_node’ of a list element. This is exactly the reverse of ‘list_entry’ above.
Definition list.hpp:74
#define list_entry(node, type)
Get the container struct of a list node.
Definition list.hpp:52
list_node_t list_head
A linked list head.
Definition list.hpp:23
MOSAPI bool list_is_empty(const list_node_t *head)
Definition list.cpp:21
#define list_remove(element)
Definition list.hpp:80
#define pfn_phyframe(pfn)
Definition pmm.hpp:74
size_t pmm_total_frames
Definition pmm.cpp:16
#define phyframe_pfn(frame)
Definition pmm.hpp:73
#define MOS_ARRAY_SIZE(x)
Definition mos_global.h:84
#define unlikely(x)
Definition mos_global.h:40
#define current
#define mos_panic(fmt,...)
Definition panic.hpp:51
#define NULL
Definition pb_syshdr.h:46
uint32_t size_t
Definition pb_syshdr.h:42
#define pr_emerg(fmt,...)
Definition printk.hpp:39
#define pr_info(fmt,...)
Definition printk.hpp:35
#define pr_cont(fmt,...)
Definition printk.hpp:41
#define pr_dinfo2(feat, fmt,...)
Definition printk.hpp:27
should_inline bool spinlock_is_locked(const spinlock_t *lock)
Definition spinlock.hpp:71
#define spinlock_acquire(lock)
Definition spinlock.hpp:64
#define spinlock_release(lock)
Definition spinlock.hpp:65
enum phyframe_t::phyframe_state state
u8 order
Definition pmm.hpp:35
union phyframe_t::additional_info info
unsigned long long pfn_t
Definition types.h:37
#define PFN_FMT
Definition types.h:38
#define PFN_RANGE
Definition types.h:39
unsigned char u8
Definition types.h:15
#define container_of(ptr, type, member)
Definition types.hpp:31
list_node_t list_node
Definition pmm.hpp:39