/* * Copyright 2016 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* Malloc/free memory module for Chrome EC */ #include #include "common.h" #include "hooks.h" #include "link_defs.h" #include "shared_mem.h" #include "system.h" #include "task.h" #include "util.h" static struct mutex shmem_lock; #ifndef TEST_SHMALLOC #define set_map_bit(x) #define TEST_GLOBAL static #else #define TEST_GLOBAL #endif /* * At the beginning there is a single free memory chunk which includes all * memory available in the system. It then gets fragmented/defragmented based * on actual allocations/releases. */ TEST_GLOBAL struct shm_buffer *free_buf_chain; /* At the beginning there is no allocated buffers */ TEST_GLOBAL struct shm_buffer *allocced_buf_chain; /* The size of the biggest ever allocated buffer. */ static int max_allocated_size; static void shared_mem_init(void) { /* * Use all the RAM we can. The shared memory buffer is the last thing * allocated from the start of RAM, so we can use everything up to the * jump data at the end of RAM. */ free_buf_chain = (struct shm_buffer *)__shared_mem_buf; free_buf_chain->next_buffer = NULL; free_buf_chain->prev_buffer = NULL; free_buf_chain->buffer_size = system_usable_ram_end() - (uintptr_t)__shared_mem_buf; } DECLARE_HOOK(HOOK_INIT, shared_mem_init, HOOK_PRIO_FIRST); /* Called with the mutex lock acquired. */ static void do_release(struct shm_buffer *ptr) { struct shm_buffer *pfb; struct shm_buffer *top; size_t released_size; /* Take the buffer out of the allocated buffers chain. */ if (ptr == allocced_buf_chain) { if (ptr->next_buffer) { set_map_bit(BIT(20)); ptr->next_buffer->prev_buffer = NULL; } else { set_map_bit(BIT(21)); } allocced_buf_chain = ptr->next_buffer; } else { /* * Saninty check: verify that the buffer is in the allocated * buffers chain. */ for (pfb = allocced_buf_chain->next_buffer; pfb; pfb = pfb->next_buffer) if (pfb == ptr) break; if (!pfb) return; ptr->prev_buffer->next_buffer = ptr->next_buffer; if (ptr->next_buffer) { set_map_bit(BIT(22)); ptr->next_buffer->prev_buffer = ptr->prev_buffer; } else { set_map_bit(BIT(23)); } } /* * Let's bring the released buffer back into the fold. Cache its size * for quick reference. */ released_size = ptr->buffer_size; if (!free_buf_chain) { /* * All memory had been allocated - this buffer is going to be * the only available free space. */ set_map_bit(BIT(0)); free_buf_chain = ptr; free_buf_chain->buffer_size = released_size; free_buf_chain->next_buffer = NULL; free_buf_chain->prev_buffer = NULL; return; } if (ptr < free_buf_chain) { /* * Insert this buffer in the beginning of the chain, possibly * merging it with the first buffer of the chain. */ pfb = (struct shm_buffer *)((uintptr_t)ptr + released_size); if (pfb == free_buf_chain) { set_map_bit(BIT(1)); /* Merge the two buffers. */ ptr->buffer_size = free_buf_chain->buffer_size + released_size; ptr->next_buffer = free_buf_chain->next_buffer; } else { set_map_bit(BIT(2)); ptr->buffer_size = released_size; ptr->next_buffer = free_buf_chain; free_buf_chain->prev_buffer = ptr; } if (ptr->next_buffer) { set_map_bit(BIT(3)); ptr->next_buffer->prev_buffer = ptr; } else { set_map_bit(BIT(4)); } ptr->prev_buffer = NULL; free_buf_chain = ptr; return; } /* * Need to merge the new free buffer into the existing chain. Find a * spot for it, it should be above the highest address buffer which is * still below the new one. */ pfb = free_buf_chain; while (pfb->next_buffer && (pfb->next_buffer < ptr)) pfb = pfb->next_buffer; top = (struct shm_buffer *)((uintptr_t)pfb + pfb->buffer_size); if (top == ptr) { /* * The returned buffer is adjacent to an existing free buffer, * below it, merge the two buffers. */ pfb->buffer_size += released_size; /* * Is the returned buffer the exact gap between two free * buffers? */ top = (struct shm_buffer *)((uintptr_t)ptr + released_size); if (top == pfb->next_buffer) { /* Yes, it is. */ pfb->buffer_size += pfb->next_buffer->buffer_size; pfb->next_buffer = pfb->next_buffer->next_buffer; if (pfb->next_buffer) { set_map_bit(BIT(5)); pfb->next_buffer->prev_buffer = pfb; } else { set_map_bit(BIT(6)); } } return; } top = (struct shm_buffer *)((uintptr_t)ptr + released_size); if (top == pfb->next_buffer) { /* The new buffer is adjacent with the one right above it. */ set_map_bit(BIT(7)); ptr->buffer_size = released_size + pfb->next_buffer->buffer_size; ptr->next_buffer = pfb->next_buffer->next_buffer; } else { /* Just include the new free buffer into the chain. */ set_map_bit(BIT(8)); ptr->next_buffer = pfb->next_buffer; ptr->buffer_size = released_size; } ptr->prev_buffer = pfb; pfb->next_buffer = ptr; if (ptr->next_buffer) { set_map_bit(BIT(9)); ptr->next_buffer->prev_buffer = ptr; } else { set_map_bit(BIT(10)); } } /* Called with the mutex lock acquired. */ static int do_acquire(int size, struct shm_buffer **dest_ptr) { int headroom = 0x10000000; /* we'll never have this much. */ struct shm_buffer *pfb; struct shm_buffer *candidate = 0; /* To keep things simple let's align the size. */ size = (size + sizeof(int) - 1) & ~(sizeof(int) - 1); /* And let's allocate room to fit the buffer header. */ size += sizeof(struct shm_buffer); pfb = free_buf_chain; while (pfb) { if ((pfb->buffer_size >= size) && ((pfb->buffer_size - size) < headroom)) { /* this is a new candidate. */ headroom = pfb->buffer_size - size; candidate = pfb; } pfb = pfb->next_buffer; } if (!candidate) { set_map_bit(BIT(11)); return EC_ERROR_BUSY; } *dest_ptr = candidate; /* Now let's take the candidate out of the free buffer chain. */ if (headroom <= sizeof(struct shm_buffer)) { /* * The entire buffer should be allocated, there is no need to * re-define its tail as a new free buffer. */ if (candidate == free_buf_chain) { /* * The next buffer becomes the head of the free buffer * chain. */ free_buf_chain = candidate->next_buffer; if (free_buf_chain) { set_map_bit(BIT(12)); free_buf_chain->prev_buffer = 0; } else { set_map_bit(BIT(13)); } } else { candidate->prev_buffer->next_buffer = candidate->next_buffer; if (candidate->next_buffer) { set_map_bit(BIT(14)); candidate->next_buffer->prev_buffer = candidate->prev_buffer; } else { set_map_bit(BIT(15)); } } return EC_SUCCESS; } candidate->buffer_size = size; /* Candidate's tail becomes a new free buffer. */ pfb = (struct shm_buffer *)((uintptr_t)candidate + size); pfb->buffer_size = headroom; pfb->next_buffer = candidate->next_buffer; pfb->prev_buffer = candidate->prev_buffer; if (pfb->next_buffer) { set_map_bit(BIT(16)); pfb->next_buffer->prev_buffer = pfb; } else { set_map_bit(BIT(17)); } if (candidate == free_buf_chain) { set_map_bit(BIT(18)); free_buf_chain = pfb; } else { set_map_bit(BIT(19)); pfb->prev_buffer->next_buffer = pfb; } return EC_SUCCESS; } int shared_mem_size(void) { struct shm_buffer *pfb; size_t max_available = 0; mutex_lock(&shmem_lock); /* Find the maximum available buffer size. */ pfb = free_buf_chain; while (pfb) { if (pfb->buffer_size > max_available) max_available = pfb->buffer_size; pfb = pfb->next_buffer; } mutex_unlock(&shmem_lock); /* Leave room for shmem header */ max_available -= sizeof(struct shm_buffer); return max_available; } int shared_mem_acquire(int size, char **dest_ptr) { int rv; struct shm_buffer *new_buf; *dest_ptr = NULL; if (in_interrupt_context()) return EC_ERROR_INVAL; if (!free_buf_chain) return EC_ERROR_BUSY; mutex_lock(&shmem_lock); rv = do_acquire(size, &new_buf); if (rv == EC_SUCCESS) { new_buf->next_buffer = allocced_buf_chain; new_buf->prev_buffer = NULL; if (allocced_buf_chain) allocced_buf_chain->prev_buffer = new_buf; allocced_buf_chain = new_buf; *dest_ptr = (void *)(new_buf + 1); if (size > max_allocated_size) max_allocated_size = size; } mutex_unlock(&shmem_lock); return rv; } void shared_mem_release(void *ptr) { if (in_interrupt_context()) return; mutex_lock(&shmem_lock); do_release((struct shm_buffer *)ptr - 1); mutex_unlock(&shmem_lock); } #ifdef CONFIG_CMD_SHMEM static int command_shmem(int argc, char **argv) { size_t allocated_size; size_t free_size; size_t max_free; struct shm_buffer *buf; allocated_size = free_size = max_free = 0; mutex_lock(&shmem_lock); for (buf = free_buf_chain; buf; buf = buf->next_buffer) { size_t buf_room; buf_room = buf->buffer_size; free_size += buf_room; if (buf_room > max_free) max_free = buf_room; } for (buf = allocced_buf_chain; buf; buf = buf->next_buffer) allocated_size += buf->buffer_size; mutex_unlock(&shmem_lock); ccprintf("Total: %6d\n", allocated_size + free_size); ccprintf("Allocated: %6d\n", allocated_size); ccprintf("Free: %6d\n", free_size); ccprintf("Max free buf: %6d\n", max_free); ccprintf("Max allocated: %6d\n", max_allocated_size); return EC_SUCCESS; } DECLARE_SAFE_CONSOLE_COMMAND(shmem, command_shmem, NULL, "Print shared memory stats"); #endif /* CONFIG_CMD_SHMEM ^^^^^^^ defined */