summaryrefslogtreecommitdiff
path: root/common/shmalloc.c
blob: b1705b52d148ca212347cc80f48986e5d3e7fdf1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/*
 * 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:         %6zd\n", allocated_size + free_size);
	ccprintf("Allocated:     %6zd\n", allocated_size);
	ccprintf("Free:          %6zd\n", free_size);
	ccprintf("Max free buf:  %6zd\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 */