394 lines
9.4 KiB
C
394 lines
9.4 KiB
C
|
/*
|
||
|
* 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 <stdint.h>
|
||
|
|
||
|
#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 */
|