| /* | |
| * Copyright 2014, NICTA | |
| * | |
| * This software may be distributed and modified according to the terms of | |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. | |
| * See "LICENSE_BSD2.txt" for details. | |
| * | |
| * @TAG(NICTA_BSD) | |
| */ | |
| #include <autoconf.h> | |
| #include <allocman/utspace/trickle.h> | |
| #include <allocman/allocman.h> | |
| #include <allocman/util.h> | |
| #include <sel4/sel4.h> | |
| #include <vka/object.h> | |
| #ifdef CONFIG_KERNEL_STABLE | |
| static inline size_t _make_bitmap(size_t bits) { | |
| /* avoid shift by int max in BIT/MASK code */ | |
| if (BIT(bits - 1) == CONFIG_WORD_SIZE) { | |
| return -1; | |
| } else { | |
| /* shift the result up so that the high bits are set */ | |
| return MASK(BIT(bits - 1)) << (CONFIG_WORD_SIZE - BIT(bits - 1)); | |
| } | |
| } | |
| static inline void _insert_node(struct utspace_trickle_node **head, struct utspace_trickle_node *node) | |
| { | |
| if (*head) { | |
| (*head)->prev = node; | |
| } | |
| node->next = *head; | |
| node->prev = NULL; | |
| *head = node; | |
| } | |
| static inline void _remove_node(struct utspace_trickle_node **head, struct utspace_trickle_node *node) | |
| { | |
| if (node->next) { | |
| node->next->prev = node->prev; | |
| } | |
| if (node->prev) { | |
| node->prev->next = node->next; | |
| } else { | |
| *head = node->next; | |
| } | |
| } | |
| static inline seL4_Word _make_cookie(struct utspace_trickle_node *node, size_t offset) | |
| { | |
| size_t int_node = (size_t) node; | |
| assert( (int_node % (CONFIG_WORD_SIZE)) == 0); | |
| assert(offset < CONFIG_WORD_SIZE); | |
| return int_node | offset; | |
| } | |
| static inline struct utspace_trickle_node *_cookie_to_node(seL4_Word cookie) { | |
| cookie -= cookie % (CONFIG_WORD_SIZE); | |
| return (struct utspace_trickle_node*)cookie; | |
| } | |
| static inline size_t _cookie_to_offset(seL4_Word cookie) | |
| { | |
| return cookie % (CONFIG_WORD_SIZE); | |
| } | |
| static inline struct utspace_trickle_node *_make_node(struct allocman *alloc, int *error) { | |
| uintptr_t addr = (uintptr_t)allocman_mspace_alloc(alloc, sizeof(struct utspace_trickle_node) * 2 - sizeof(size_t), error); | |
| struct utspace_trickle_node *node; | |
| if (*error) { | |
| return NULL; | |
| } | |
| node = _cookie_to_node(addr + CONFIG_WORD_SIZE); | |
| node->padding = addr; | |
| return node; | |
| } | |
| static inline void _free_node(struct allocman *alloc, struct utspace_trickle_node *node) | |
| { | |
| allocman_mspace_free(alloc, (void*)node->padding, sizeof(struct utspace_trickle_node) * 2 - sizeof(size_t)); | |
| } | |
| int _utspace_trickle_add_uts(allocman_t *alloc, void *_trickle, size_t num, const cspacepath_t *uts, size_t *size_bits, uintptr_t *paddr) { | |
| utspace_trickle_t *trickle = (utspace_trickle_t*) _trickle; | |
| struct utspace_trickle_node *nodes[num]; | |
| cspacepath_t *uts_copy[num]; | |
| int error; | |
| size_t i; | |
| for (i = 0; i < num; i++) { | |
| nodes[i] = _make_node(alloc, &error); | |
| if (error) { | |
| for (i--; i >= 0; i--) { | |
| _free_node(alloc, nodes[i]); | |
| allocman_mspace_free(alloc, uts_copy[i], sizeof(cspacepath_t)); | |
| } | |
| return error; | |
| } | |
| uts_copy[i] = allocman_mspace_alloc(alloc, sizeof(cspacepath_t), &error); | |
| if (error) { | |
| _free_node(alloc, nodes[i]); | |
| for (i--; i >= 0; i--) { | |
| _free_node(alloc, nodes[i]); | |
| allocman_mspace_free(alloc, uts_copy[i], sizeof(cspacepath_t)); | |
| } | |
| } | |
| } | |
| for (i = 0; i < num; i++) { | |
| *uts_copy[i] = uts[i]; | |
| nodes[i]->ut = uts_copy[i]; | |
| nodes[i]->offset = 0; | |
| nodes[i]->paddr = paddr[i]; | |
| nodes[i]->parent_cookie = 0; | |
| nodes[i]->next = nodes[i]->prev = NULL; | |
| /* Start with only 1 thing free */ | |
| nodes[i]->bitmap = BIT(31); | |
| nodes[i]->bitmap_bits = 1; | |
| _insert_node(&trickle->heads[size_bits[i]], nodes[i]); | |
| } | |
| return 0; | |
| } | |
| void utspace_trickle_create(utspace_trickle_t *trickle) | |
| { | |
| uint32_t i; | |
| for (i = 0; i < CONFIG_WORD_SIZE; i++) { | |
| trickle->heads[i] = NULL; | |
| } | |
| } | |
| static int _refill_pool(struct allocman *alloc, utspace_trickle_t *trickle, size_t size_bits) | |
| { | |
| size_t i; | |
| int error; | |
| struct utspace_trickle_node *node; | |
| seL4_Word cookie; | |
| node = _make_node(alloc, &error); | |
| if (error) { | |
| return error; | |
| } | |
| /* Check if there are untypeds >= 5 size_bits from us */ | |
| for (i = size_bits + 5 > CONFIG_WORD_SIZE - 1 ? CONFIG_WORD_SIZE - 1 : size_bits + 5; i < CONFIG_WORD_SIZE; i++) { | |
| if (trickle->heads[i]) { | |
| i = size_bits + 5; | |
| break; | |
| } | |
| } | |
| if (i == CONFIG_WORD_SIZE) { | |
| /* Search for the biggest one near us */ | |
| for (i = size_bits + 5 > CONFIG_WORD_SIZE - 1 ? CONFIG_WORD_SIZE - 1 : size_bits + 5; i > size_bits; i--) { | |
| if (trickle->heads[i]) { | |
| break; | |
| } | |
| } | |
| } | |
| if (i != size_bits) { | |
| cookie = _utspace_trickle_alloc(alloc, trickle, i, seL4_UntypedObject, NULL, &error); | |
| if (!error) { | |
| struct utspace_trickle_node *parent = _cookie_to_node(cookie); | |
| size_t offset = _cookie_to_offset(cookie); | |
| node->ut = parent->ut; | |
| node->offset = parent->offset + (offset << (i)); | |
| if (parent->paddr) { | |
| node->paddr = parent->paddr + (offset << (i)); | |
| } else { | |
| node->paddr = 0; | |
| } | |
| node->parent_cookie = cookie; | |
| node->bitmap_bits = i - size_bits + 1; | |
| node->bitmap = _make_bitmap(node->bitmap_bits); | |
| node->next = node->prev = NULL; | |
| _insert_node(&trickle->heads[size_bits], node); | |
| return 0; | |
| } | |
| } | |
| _free_node(alloc, node); | |
| return 1; | |
| } | |
| seL4_Word _utspace_trickle_alloc(struct allocman *alloc, void *_trickle, size_t size_bits, seL4_Word type, const cspacepath_t *slot, int *error) | |
| { | |
| size_t sel4_size_bits; | |
| int _error; | |
| utspace_trickle_t *trickle = (utspace_trickle_t*)_trickle; | |
| struct utspace_trickle_node *node; | |
| size_t offset; | |
| size_t mem_offset; | |
| /* get size of untyped call */ | |
| sel4_size_bits = get_sel4_object_size(type, size_bits); | |
| if (size_bits != vka_get_object_size(type, sel4_size_bits) || size_bits == 0) { | |
| SET_ERROR(error, 1); | |
| return 0; | |
| } | |
| assert(size_bits < CONFIG_WORD_SIZE); | |
| if (!trickle->heads[size_bits]) { | |
| _error = _refill_pool(alloc, trickle, size_bits); | |
| if (_error) { | |
| SET_ERROR(error, _error); | |
| return 0; | |
| } | |
| } | |
| node = trickle->heads[size_bits]; | |
| offset = CLZL(node->bitmap); | |
| mem_offset = node->offset + (offset << size_bits); | |
| if (slot) { | |
| _error = seL4_Untyped_RetypeAtOffset(node->ut->capPtr, type, mem_offset, sel4_size_bits, slot->root, slot->dest, slot->destDepth, slot->offset, 1); | |
| if (_error != seL4_NoError) { | |
| SET_ERROR(error, 1); | |
| return 0; | |
| } | |
| } | |
| node->bitmap &= ~BIT(CONFIG_WORD_SIZE - 1 - offset); | |
| if (node->bitmap == 0) { | |
| _remove_node(&trickle->heads[size_bits], node); | |
| } | |
| SET_ERROR(error, 0); | |
| return _make_cookie(node, offset); | |
| } | |
| void _utspace_trickle_free(struct allocman *alloc, void *_trickle, seL4_Word cookie, size_t size_bits) | |
| { | |
| utspace_trickle_t *trickle = (utspace_trickle_t*)_trickle; | |
| struct utspace_trickle_node *node = _cookie_to_node(cookie); | |
| size_t offset = _cookie_to_offset(cookie); | |
| int in_list = !(node->bitmap == 0); | |
| node->bitmap |= BIT(CONFIG_WORD_SIZE - 1 - offset); | |
| if (node->bitmap == _make_bitmap(node->bitmap_bits)) { | |
| if (node->parent_cookie) { | |
| if (in_list) { | |
| _remove_node(&trickle->heads[size_bits], node); | |
| } | |
| _utspace_trickle_free(alloc, trickle, node->parent_cookie, size_bits + node->bitmap_bits - 1); | |
| _free_node(alloc, node); | |
| } else if (!in_list) { | |
| _insert_node(&trickle->heads[size_bits], node); | |
| } | |
| } else if (!in_list) { | |
| _insert_node(&trickle->heads[size_bits], node); | |
| } | |
| } | |
| uintptr_t _utspace_trickle_paddr(void *_trickle, seL4_Word cookie, size_t size_bits) { | |
| struct utspace_trickle_node *node = _cookie_to_node(cookie); | |
| size_t offset = _cookie_to_offset(cookie); | |
| return node->paddr + (offset << size_bits); | |
| } | |
| #endif |