blob: a9bcd545618efd37a0a803c4a909ae30f45939b7 [file] [log] [blame] [edit]
/* Copyright (c) 2016, 2018 Google Inc
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* Memory Extent Filesystem block allocation
*
* We use a power-of-two list allocator, similar to the arena allocator.
* There's no xalloc, importing, qcaches, or anything like that. The superblock
* is analogous to the base arena: it must be self-sufficient.
*
* All of the structures are "on disk." In theory, that should change as often
* as a filesystem's disk structures change, which is rarely if ever. Once it
* is done. =) All values are in host-endian, and we operate directly on RAM.
*
* There's no protection for metadata corruption - if you crash in the middle of
* a tree-changing operation, you're out of luck.
*
* Unlike the arena allocator, we don't return a "void *", we actually return
* a pointer to the btag. All of our users (the rest of mefs) will put up with
* this layer of indirection. In exchange, we don't have to muck around with
* hash tables to find the btag when the segment is freed.
*
* This all assumes the caller manages synchronization (e.g. locks).
*
* This uses the BSD list macros, which technically is not guaranteed to not
* change. If someone wants to replace them with local versions that are bound
* to the filesystem's disk format, then be my guest. This doesn't use the
* rbtree code, though we probably could with the same justification for using
* the BSD list code. But it'd be a bit more of a pain to roll our own for
* that, and I doubt it is necessary.
*/
#include "mefs.h"
#include <err.h>
static struct mefs_btag *__get_from_freelists(struct mefs_superblock *sb,
int list_idx);
static bool __account_alloc(struct mefs_superblock *sb, struct mefs_btag *bt,
size_t size, struct mefs_btag *new);
/* Bootstrap from the free list */
static void __ensure_some_btags(struct mefs_superblock *sb)
{
struct mefs_btag *bt, *tags;
size_t nr_bts = MEFS_QUANTUM / sizeof(struct mefs_btag);
if (!BSD_LIST_EMPTY(&sb->unused_btags))
return;
bt = __get_from_freelists(sb, LOG2_UP(MEFS_QUANTUM));
if (!bt)
error(ENOMEM, "Unable to get more BTs in mefs!");
tags = (struct mefs_btag*)bt->start;
if (__account_alloc(sb, bt, MEFS_QUANTUM, &tags[0])) {
/* We used the tag[0]; we'll have to skip over it now. */
tags++;
nr_bts--;
}
for (int i = 0; i < nr_bts; i++)
BSD_LIST_INSERT_HEAD(&sb->unused_btags, &tags[i], misc_link);
}
static struct mefs_btag *__get_btag(struct mefs_superblock *sb)
{
struct mefs_btag *bt;
bt = BSD_LIST_FIRST(&sb->unused_btags);
assert(bt);
BSD_LIST_REMOVE(bt, misc_link);
return bt;
}
static void __free_btag(struct mefs_superblock *sb, struct mefs_btag *bt)
{
BSD_LIST_INSERT_HEAD(&sb->unused_btags, bt, misc_link);
}
static void __track_free_seg(struct mefs_superblock *sb, struct mefs_btag *bt)
{
int list_idx = LOG2_DOWN(bt->size);
bt->status = MEFS_BTAG_FREE;
BSD_LIST_INSERT_HEAD(&sb->free_segs[list_idx], bt, misc_link);
}
static void __untrack_free_seg(struct mefs_superblock *sb, struct mefs_btag *bt)
{
BSD_LIST_REMOVE(bt, misc_link);
}
/* This is a little slow, and is a consequence of not using a tree. However,
* the common case caller was when @bt was created from an old one, and is
* likely to be right after it. The old one is the @hint, which is where to
* start our search. */
static void __insert_btag(struct mefs_btag_list *list, struct mefs_btag *bt,
struct mefs_btag *hint)
{
struct mefs_btag *i, *last = NULL;
bool hinted = false;
BSD_LIST_FOREACH(i, list, all_link) {
if (!hinted && hint) {
i = hint;
hinted = true;
}
if (bt->start < i->start) {
BSD_LIST_INSERT_BEFORE(i, bt, all_link);
return;
}
if (bt->start == i->start)
panic("BT %p == i %p in list %p!", bt, i, list);
last = i;
}
if (last)
BSD_LIST_INSERT_AFTER(last, bt, all_link);
else
BSD_LIST_INSERT_HEAD(list, bt, all_link);
}
/* Unlink the arena allocator, we don't track the segments on an allocated list.
* Our caller is the one that keeps track of it. */
static void __track_alloc_seg(struct mefs_superblock *sb, struct mefs_btag *bt)
{
bt->status = MEFS_BTAG_ALLOC;
}
/* Helper: we decided we want to alloc part of @bt, which has been removed from
* its old list. We need @size units. The rest can go back.
*
* Takes @new, which we'll use if we need a new btag. If @new is NULL, we'll
* allocate one. If we used the caller's btag, we'll return TRUE. */
static bool __account_alloc(struct mefs_superblock *sb, struct mefs_btag *bt,
size_t size, struct mefs_btag *new)
{
bool ret = FALSE;
assert(bt->status == MEFS_BTAG_FREE);
if (bt->size != size) {
assert(bt->size > size);
if (new)
ret = TRUE;
else
new = __get_btag(sb);
new->start = bt->start + size;
new->size = bt->size - size;
bt->size = size;
__track_free_seg(sb, new);
__insert_btag(&sb->all_segs, new, bt);
}
__track_alloc_seg(sb, bt);
sb->amt_alloc_segs += size;
return ret;
}
static struct mefs_btag *__get_from_freelists(struct mefs_superblock *sb,
int list_idx)
{
struct mefs_btag *ret = NULL;
for (int i = list_idx; i < MEFS_NR_FREE_LISTS; i++) {
ret = BSD_LIST_FIRST(&sb->free_segs[i]);
if (ret) {
BSD_LIST_REMOVE(ret, misc_link);
break;
}
}
return ret;
}
/* This uses the arena's "best fit" policy. You could imagine building a
* version that cares about alignment too, for e.g. huge pages. */
struct mefs_btag *mefs_ext_alloc(struct mefs_superblock *sb, size_t size)
{
int list_idx = LOG2_DOWN(size);
struct mefs_btag *bt_i, *best = NULL;
if (!size)
error(EINVAL, "mefs_ext_alloc with 0 size!");
__ensure_some_btags(sb);
size = ROUNDUP(size, MEFS_QUANTUM);
BSD_LIST_FOREACH(bt_i, &sb->free_segs[list_idx], misc_link) {
if (bt_i->size >= size) {
if (!best || (best->size > bt_i->size))
best = bt_i;
}
}
if (best)
BSD_LIST_REMOVE(best, misc_link);
else
best = __get_from_freelists(sb, list_idx + 1);
if (!best)
error(ENOMEM, "couldn't find segment in mefs!");
__account_alloc(sb, best, size, NULL);
return best;
}
static bool __merge_right_to_left(struct mefs_superblock *sb,
struct mefs_btag *left,
struct mefs_btag *right)
{
if (left->status != MEFS_BTAG_FREE)
return false;
if (right->status != MEFS_BTAG_FREE)
return false;
if (left->start + left->size == right->start) {
__untrack_free_seg(sb, left);
__untrack_free_seg(sb, right);
left->size += right->size;
__track_free_seg(sb, left);
BSD_LIST_REMOVE(right, all_link);
__free_btag(sb, right);
return true;
}
return false;
}
static void __coalesce_free_seg(struct mefs_superblock *sb,
struct mefs_btag *bt)
{
struct mefs_btag *bt_p, *bt_n;
bt_n = BSD_LIST_NEXT(bt, all_link);
if (bt_n)
__merge_right_to_left(sb, bt, bt_n);
bt_p = BSD_LIST_PREV(bt, &sb->all_segs, mefs_btag, all_link);
if (bt_p)
__merge_right_to_left(sb, bt_p, bt);
}
void mefs_ext_free(struct mefs_superblock *sb, struct mefs_btag *bt)
{
void *to_free_addr = 0;
size_t to_free_sz = 0;
sb->amt_alloc_segs -= bt->size;
__track_free_seg(sb, bt);
/* Don't use bt after this: */
__coalesce_free_seg(sb, bt);
sb->amt_total_segs -= to_free_sz;
}
/* Bump allocates space in the segment [seg_alloc, seg_alloc + seg_amt).
* Returns the allocation address and updates the allocator's values by
* reference. Throws on error. */
static void *bump_zalloc(size_t amt, size_t align, uintptr_t *seg_alloc,
size_t *seg_amt)
{
size_t align_diff;
void *ret;
align_diff = ALIGN(*seg_alloc, align) - *seg_alloc;
if (*seg_amt < amt + align_diff)
error(ENOMEM, "Out of space in mefs SB");
*seg_amt -= align_diff;
*seg_alloc += align_diff;
ret = (void*)*seg_alloc;
*seg_alloc += amt;
*seg_amt -= amt;
memset(ret, 0, amt);
return ret;
}
/* Creates a superblock and adds the memory segment. The SB will be at the
* beginning of the segment. */
struct mefs_superblock *mefs_super_create(uintptr_t init_seg, size_t size)
{
struct mefs_superblock *sb;
struct mefs_btag *bt;
uintptr_t seg_alloc = init_seg;
size_t seg_amt = size;
sb = bump_zalloc(sizeof(*sb), __alignof__(*sb), &seg_alloc, &seg_amt);
memcpy(sb->magic, MEFS_MAGIC, sizeof(sb->magic));
BSD_LIST_INIT(&sb->all_segs);
BSD_LIST_INIT(&sb->unused_btags);
for (int i = 0; i < MEFS_NR_FREE_LISTS; i++)
BSD_LIST_INIT(&sb->free_segs[i]);
bt = bump_zalloc(sizeof(*bt), __alignof__(*bt), &seg_alloc, &seg_amt);
BSD_LIST_INSERT_HEAD(&sb->unused_btags, bt, misc_link);
seg_alloc = ALIGN(seg_alloc, MEFS_QUANTUM);
seg_amt = ALIGN_DOWN(seg_amt, MEFS_QUANTUM);
mefs_super_add(sb, seg_alloc, seg_amt);
return sb;
}
/* Ignoring size for now. Could use it for sanity checks. */
struct mefs_superblock *mefs_super_attach(uintptr_t init_seg, size_t size)
{
struct mefs_superblock *sb;
init_seg = ALIGN(init_seg, sizeof(*sb));
sb = (struct mefs_superblock*)init_seg;
if (strcmp(sb->magic, MEFS_MAGIC))
return NULL;
return sb;
}
void mefs_super_add(struct mefs_superblock *sb, uintptr_t seg, size_t size)
{
struct mefs_btag *bt;
__ensure_some_btags(sb);
bt = __get_btag(sb);
bt->start = seg;
bt->size = size;
sb->amt_total_segs += size;
__track_free_seg(sb, bt);
__insert_btag(&sb->all_segs, bt, NULL);
}
void mefs_super_destroy(struct mefs_superblock *sb)
{
memset(sb->magic, 0xa, sizeof(sb->magic));
}
void mefs_super_dump(struct mefs_superblock *sb)
{
struct mefs_btag *i;
printk("All segs\n");
BSD_LIST_FOREACH(i, &sb->all_segs, all_link)
printk("bt %p, start %p, +%lu, %s\n", i, i->start, i->size,
i->status == MEFS_BTAG_FREE ? "free" : "alloc");
}
#include <time.h>
static inline void kb_wait(void)
{
int i;
for (i = 0; i < 0x10000; i++) {
if ((inb(0x64) & 0x02) == 0)
break;
udelay(2);
}
}
void food()
{
outb(0xcf9, 0x6);
printk("ACPI cf9 FAILED\n");
}
void foot()
{
for (int i = 0; i < 10; i++) {
kb_wait();
udelay(50);
outb(0x64, 0xfe); /* Pulse reset low */
udelay(50);
}
printk("KBD FAILED\n");
}