| /* |
| * 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 |