taiHEN  1.0
CFW framework for PS Vita
slab.c
1 /* ref: https://github.com/bbu/userland-slab-allocator */
2 
3 #include "slab.h"
4 #include "taihen_internal.h"
5 #include <psp2kern/kernel/sysmem.h>
6 
7 #include <stdint.h>
8 #include <stddef.h>
9 #include <string.h>
10 
11 #define assert(x) // turn off asserts
12 
13 #define SLAB_DUMP_COLOURED
14 
15 #ifdef SLAB_DUMP_COLOURED
16 # define GRAY(s) "\033[1;30m" s "\033[0m"
17 # define RED(s) "\033[0;31m" s "\033[0m"
18 # define GREEN(s) "\033[0;32m" s "\033[0m"
19 # define YELLOW(s) "\033[1;33m" s "\033[0m"
20 #else
21 # define GRAY(s) s
22 # define RED(s) s
23 # define GREEN(s) s
24 # define YELLOW(s) s
25 #endif
26 
27 #define SLOTS_ALL_ZERO ((uint64_t) 0)
28 #define SLOTS_FIRST ((uint64_t) 1)
29 #define FIRST_FREE_SLOT(s) ((size_t) __builtin_ctzll(s))
30 #define FREE_SLOTS(s) ((size_t) __builtin_popcountll(s))
31 #define ONE_USED_SLOT(slots, empty_slotmask) \
32  ( \
33  ( \
34  (~(slots) & (empty_slotmask)) & \
35  ((~(slots) & (empty_slotmask)) - 1) \
36  ) == SLOTS_ALL_ZERO \
37  )
38 
39 #define POWEROF2(x) ((x) != 0 && ((x) & ((x) - 1)) == 0)
40 
41 #define LIKELY(exp) __builtin_expect(exp, 1)
42 #define UNLIKELY(exp) __builtin_expect(exp, 0)
43 
44 const size_t slab_pagesize = 0x1000;
45 
60 static SceUID sce_exe_alloc(SceUID pid, void **ptr, uintptr_t *exe_addr, SceUID *exe_res, size_t align, size_t size) {
61  SceKernelAllocMemBlockKernelOpt opt;
62  SceKernelMemBlockType type;
63  SceUID res, blkid;
64 
65  LOG("Allocating exec slab for %x size 0x%08X", pid, size);
66  // allocate exe mem
67  memset(&opt, 0, sizeof(opt));
68  opt.size = sizeof(opt);
69  opt.attr = 0xA0000000 | 0x400000;
70  opt.alignment = align;
71  if (align) {
72  opt.attr |= SCE_KERNEL_ALLOC_MEMBLOCK_ATTR_HAS_ALIGNMENT;
73  }
74  if (pid == KERNEL_PID) {
75  type = SCE_KERNEL_MEMBLOCK_TYPE_KERNEL_RX;
76  } else if (pid == SHARED_PID) {
77  type = SCE_KERNEL_MEMBLOCK_TYPE_SHARED_RX;
78  } else {
79  type = SCE_KERNEL_MEMBLOCK_TYPE_USER_RX;
80  opt.attr |= 0x80080;
81  opt.pid = pid;
82  }
83  *exe_res = sceKernelAllocMemBlockForKernel("taislab", type, size, &opt);
84  LOG("sceKernelAllocMemBlockForKernel(taislab): 0x%08X", *exe_res);
85  if (*exe_res < 0) {
86  return *exe_res;
87  }
88  res = sceKernelGetMemBlockBaseForKernel(*exe_res, (void **)exe_addr);
89  LOG("sceKernelGetMemBlockBaseForKernel(%x): 0x%08X, addr: 0x%08X", *exe_res, res, *exe_addr);
90  if (res < 0) {
91  goto err2;
92  }
93 
94  // TODO: Perhaps move this to execmem seal?
95  if (pid != KERNEL_PID) {
96  res = sceKernelMapBlockUserVisible(*exe_res);
97  LOG("sceKernelMapBlockUserVisible: %x", res);
98  if (res < 0) {
99  goto err2;
100  }
101  }
102 
103  // map in every process if needed
104  if (pid == SHARED_PID) {
105  // FIXME: implement this
106  }
107 
108  // allocate mirror
109  memset(&opt, 0, sizeof(opt));
110  opt.size = sizeof(opt);
111  opt.attr = 0x1000040;
112  opt.mirror_blkid = *exe_res;
113  res = sceKernelAllocMemBlockForKernel("taimirror", SCE_KERNEL_MEMBLOCK_TYPE_RW_UNK0, 0, &opt);
114  LOG("sceKernelAllocMemBlockForKernel(taimirror): 0x%08X", res);
115  if (res < 0) {
116  goto err2;
117  }
118  blkid = res;
119  res = sceKernelGetMemBlockBaseForKernel(blkid, ptr);
120  LOG("sceKernelGetMemBlockBaseForKernel(%x): 0x%08X, addr: 0x%08X", blkid, res, *ptr);
121  if (res < 0) {
122  goto err1;
123  }
124 
125  return blkid;
126 
127 err1:
128  sceKernelFreeMemBlockForKernel(blkid);
129 err2:
130  sceKernelFreeMemBlockForKernel(*exe_res);
131  return res;
132 }
133 
142 static int sce_exe_free(SceUID write_res, SceUID exe_res) {
143  LOG("freeing slab %x, mirror %x", exe_res, write_res);
144  sceKernelFreeMemBlockForKernel(write_res);
145  sceKernelFreeMemBlockForKernel(exe_res);
146  return 0;
147 }
148 
156 static inline uint32_t next_pow_2(uint32_t v) {
157  v--;
158  v |= v >> 1;
159  v |= v >> 2;
160  v |= v >> 4;
161  v |= v >> 8;
162  v |= v >> 16;
163  v++;
164  v += (v == 0);
165  return v;
166 }
167 
168 void slab_init(struct slab_chain *const sch, const size_t itemsize, SceUID pid)
169 {
170  assert(sch != NULL);
171  assert(itemsize >= 1 && itemsize <= SIZE_MAX);
172  assert(POWEROF2(slab_pagesize));
173 
174  sch->itemsize = itemsize;
175  sch->pid = pid;
176 
177  const size_t data_offset = offsetof(struct slab_header, data);
178  const size_t least_slabsize = data_offset + 64 * sch->itemsize;
179  sch->slabsize = (size_t) next_pow_2(least_slabsize);
180  sch->itemcount = 64;
181 
182  if (sch->slabsize - least_slabsize != 0) {
183  const size_t shrinked_slabsize = sch->slabsize >> 1;
184 
185  if (data_offset < shrinked_slabsize &&
186  shrinked_slabsize - data_offset >= 2 * sch->itemsize) {
187 
188  sch->slabsize = shrinked_slabsize;
189  sch->itemcount = (shrinked_slabsize - data_offset) / sch->itemsize;
190  }
191  }
192 
193  sch->pages_per_alloc = sch->slabsize > slab_pagesize ?
194  sch->slabsize : slab_pagesize;
195 
196  sch->empty_slotmask = ~SLOTS_ALL_ZERO >> (64 - sch->itemcount);
197  sch->initial_slotmask = sch->empty_slotmask ^ SLOTS_FIRST;
198  sch->alignment_mask = ~(sch->slabsize - 1);
199  sch->partial = sch->empty = sch->full = NULL;
200 
201  assert(slab_is_valid(sch));
202 }
203 
204 void *slab_alloc(struct slab_chain *const sch, uintptr_t *exe_addr)
205 {
206  assert(sch != NULL);
207  assert(slab_is_valid(sch));
208 
209  if (LIKELY(sch->partial != NULL)) {
210  /* found a partial slab, locate the first free slot */
211  register const size_t slot = FIRST_FREE_SLOT(sch->partial->slots);
212  sch->partial->slots ^= SLOTS_FIRST << slot;
213 
214  if (UNLIKELY(sch->partial->slots == SLOTS_ALL_ZERO)) {
215  /* slab has become full, change state from partial to full */
216  struct slab_header *const tmp = sch->partial;
217 
218  /* skip first slab from partial list */
219  if (LIKELY((sch->partial = sch->partial->next) != NULL))
220  sch->partial->prev = NULL;
221 
222  if (LIKELY((tmp->next = sch->full) != NULL))
223  sch->full->prev = tmp;
224 
225  sch->full = tmp;
226  *exe_addr = sch->full->exe_data + slot * sch->itemsize;
227  return sch->full->data + slot * sch->itemsize;
228  } else {
229  *exe_addr = sch->partial->exe_data + slot * sch->itemsize;
230  return sch->partial->data + slot * sch->itemsize;
231  }
232  } else if (LIKELY((sch->partial = sch->empty) != NULL)) {
233  /* found an empty slab, change state from empty to partial */
234  if (LIKELY((sch->empty = sch->empty->next) != NULL))
235  sch->empty->prev = NULL;
236 
237  sch->partial->next = NULL;
238 
239  /* slab is located either at the beginning of page, or beyond */
240  UNLIKELY(sch->partial->refcount != 0) ?
241  sch->partial->refcount++ : sch->partial->page->refcount++;
242 
243  sch->partial->slots = sch->initial_slotmask;
244  *exe_addr = sch->partial->exe_data;
245  return sch->partial->data;
246  } else {
247  /* no empty or partial slabs available, create a new one */
248  SceUID write_res, exe_res;
249  uintptr_t exe_data;
250  if ((write_res = sce_exe_alloc(sch->pid, (void **)&sch->partial, &exe_data,
251  &exe_res, sch->slabsize, sch->pages_per_alloc)) < 0) {
252  *exe_addr = 0;
253  return sch->partial = NULL;
254  }
255  sch->partial->write_res = write_res;
256  sch->partial->exe_res = exe_res;
257  sch->partial->exe_data = exe_data + offsetof(struct slab_header, data);
258  exe_data += sch->slabsize;
259 
260  struct slab_header *prev = NULL;
261 
262  const char *const page_end =
263  (char *) sch->partial + sch->pages_per_alloc;
264 
265  union {
266  const char *c;
267  struct slab_header *const s;
268  } curr = {
269  .c = (const char *) sch->partial + sch->slabsize
270  };
271 
272  __builtin_prefetch(sch->partial, 1);
273 
274  sch->partial->prev = sch->partial->next = NULL;
275  sch->partial->refcount = 1;
276  sch->partial->slots = sch->initial_slotmask;
277 
278  if (LIKELY(curr.c != page_end)) {
279  curr.s->prev = NULL;
280  curr.s->refcount = 0;
281  curr.s->page = sch->partial;
282  curr.s->write_res = write_res;
283  curr.s->exe_res = exe_res;
284  curr.s->exe_data = exe_data;
285  exe_data += sch->slabsize;
286  curr.s->slots = sch->empty_slotmask;
287  sch->empty = prev = curr.s;
288 
289  while (LIKELY((curr.c += sch->slabsize) != page_end)) {
290  prev->next = curr.s;
291  curr.s->prev = prev;
292  curr.s->refcount = 0;
293  curr.s->page = sch->partial;
294  curr.s->write_res = write_res;
295  curr.s->exe_res = exe_res;
296  curr.s->exe_data = exe_data;
297  exe_data += sch->slabsize;
298  curr.s->slots = sch->empty_slotmask;
299  prev = curr.s;
300  }
301 
302  prev->next = NULL;
303  }
304 
305  *exe_addr = sch->partial->exe_data;
306  return sch->partial->data;
307  }
308 
309  /* unreachable */
310 }
311 
312 void slab_free(struct slab_chain *const sch, const void *const addr)
313 {
314  assert(sch != NULL);
315  assert(slab_is_valid(sch));
316  assert(addr != NULL);
317 
318  struct slab_header *const slab = (void *)
319  ((uintptr_t) addr & sch->alignment_mask);
320 
321  register const int slot = ((char *) addr - (char *) slab -
322  offsetof(struct slab_header, data)) / sch->itemsize;
323 
324  if (UNLIKELY(slab->slots == SLOTS_ALL_ZERO)) {
325  /* target slab is full, change state to partial */
326  slab->slots = SLOTS_FIRST << slot;
327 
328  if (LIKELY(slab != sch->full)) {
329  if (LIKELY((slab->prev->next = slab->next) != NULL))
330  slab->next->prev = slab->prev;
331 
332  slab->prev = NULL;
333  } else if (LIKELY((sch->full = sch->full->next) != NULL)) {
334  sch->full->prev = NULL;
335  }
336 
337  slab->next = sch->partial;
338 
339  if (LIKELY(sch->partial != NULL))
340  sch->partial->prev = slab;
341 
342  sch->partial = slab;
343  } else if (UNLIKELY(ONE_USED_SLOT(slab->slots, sch->empty_slotmask))) {
344  /* target slab is partial and has only one filled slot */
345  if (UNLIKELY(slab->refcount == 1 || (slab->refcount == 0 &&
346  slab->page->refcount == 1))) {
347 
348  /* unmap the whole page if this slab is the only partial one */
349  if (LIKELY(slab != sch->partial)) {
350  if (LIKELY((slab->prev->next = slab->next) != NULL))
351  slab->next->prev = slab->prev;
352  } else if (LIKELY((sch->partial = sch->partial->next) != NULL)) {
353  sch->partial->prev = NULL;
354  }
355 
356  void *const page = UNLIKELY(slab->refcount != 0) ? slab : slab->page;
357  const char *const page_end = (char *) page + sch->pages_per_alloc;
358  char found_head = 0;
359 
360  union {
361  const char *c;
362  const struct slab_header *const s;
363  } s;
364 
365  for (s.c = page; s.c != page_end; s.c += sch->slabsize) {
366  if (UNLIKELY(s.s == sch->empty))
367  found_head = 1;
368  else if (UNLIKELY(s.s == slab))
369  continue;
370  else if (LIKELY((s.s->prev->next = s.s->next) != NULL))
371  s.s->next->prev = s.s->prev;
372  }
373 
374  if (UNLIKELY(found_head && (sch->empty = sch->empty->next) != NULL))
375  sch->empty->prev = NULL;
376 
377  sce_exe_free(slab->write_res, slab->exe_res);
378  } else {
379  slab->slots = sch->empty_slotmask;
380 
381  if (LIKELY(slab != sch->partial)) {
382  if (LIKELY((slab->prev->next = slab->next) != NULL))
383  slab->next->prev = slab->prev;
384 
385  slab->prev = NULL;
386  } else if (LIKELY((sch->partial = sch->partial->next) != NULL)) {
387  sch->partial->prev = NULL;
388  }
389 
390  slab->next = sch->empty;
391 
392  if (LIKELY(sch->empty != NULL))
393  sch->empty->prev = slab;
394 
395  sch->empty = slab;
396 
397  UNLIKELY(slab->refcount != 0) ?
398  slab->refcount-- : slab->page->refcount--;
399  }
400  } else {
401  /* target slab is partial, no need to change state */
402  slab->slots |= SLOTS_FIRST << slot;
403  }
404 }
405 
406 uintptr_t slab_getmirror(struct slab_chain *const sch, const void *const addr)
407 {
408  assert(sch != NULL);
409  assert(slab_is_valid(sch));
410  assert(addr != NULL);
411 
412  struct slab_header *const slab = (void *)
413  ((uintptr_t) addr & sch->alignment_mask);
414 
415 
416  return slab->exe_data - offsetof(struct slab_header, data) + (ptrdiff_t)((char *) addr - (char *) slab);
417 }
418 
419 void slab_traverse(const struct slab_chain *const sch, void (*fn)(const void *))
420 {
421  assert(sch != NULL);
422  assert(fn != NULL);
423  assert(slab_is_valid(sch));
424 
425  const struct slab_header *slab;
426  const char *item, *end;
427  const size_t data_offset = offsetof(struct slab_header, data);
428 
429  for (slab = sch->partial; slab; slab = slab->next) {
430  item = (const char *) slab + data_offset;
431  end = item + sch->itemcount * sch->itemsize;
432  uint64_t mask = SLOTS_FIRST;
433 
434  do {
435  if (!(slab->slots & mask))
436  fn(item);
437 
438  mask <<= 1;
439  } while ((item += sch->itemsize) != end);
440  }
441 
442  for (slab = sch->full; slab; slab = slab->next) {
443  item = (const char *) slab + data_offset;
444  end = item + sch->itemcount * sch->itemsize;
445 
446  do fn(item);
447  while ((item += sch->itemsize) != end);
448  }
449 }
450 
451 void slab_destroy(const struct slab_chain *const sch)
452 {
453  assert(sch != NULL);
454  assert(slab_is_valid(sch));
455 
456  struct slab_header *const heads[] = {sch->partial, sch->empty, sch->full};
457  struct slab_header *pages_head = NULL, *pages_tail;
458 
459  for (size_t i = 0; i < 3; ++i) {
460  struct slab_header *slab = heads[i];
461 
462  while (slab != NULL) {
463  if (slab->refcount != 0) {
464  struct slab_header *const page = slab;
465  slab = slab->next;
466 
467  if (UNLIKELY(pages_head == NULL))
468  pages_head = page;
469  else
470  pages_tail->next = page;
471 
472  pages_tail = page;
473  } else {
474  slab = slab->next;
475  }
476  }
477  }
478 
479  if (LIKELY(pages_head != NULL)) {
480  pages_tail->next = NULL;
481  struct slab_header *page = pages_head;
482 
483  do {
484  struct slab_header *target = page;
485  page = page->next;
486  sce_exe_free(target->write_res, target->exe_res);
487  } while (page != NULL);
488  }
489 }
#define KERNEL_PID
Definition: taihen.h:34