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

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "common.h"
#include "compile_time_macros.h"
#include "console.h"
#include "link_defs.h"
#include "shared_mem.h"
#include "test_util.h"

/*
 * Total size of memory in the malloc pool (shared between free and allocated
 * buffers.
 */
static int total_size;

/*
 * Number of randomized allocation/free attempts, large enough to execute all
 * branches in the malloc/free module.
 */
static int counter = 500000;

/*
 * A good random number generator approximation. Guaranteed to generate the
 * same sequence on all test runs.
 */
static uint32_t next = 127;
static uint32_t myrand(void)
{
	next = next * 1103515245 + 12345;
	return ((uint32_t)(next/65536) % 32768);
}

/* Keep track of buffers allocated by the test function. */
static struct {
	void *buf;
	size_t buffer_size;
} allocations[12];  /* Up to 12 buffers could be allocated concurrently. */

/*
 * Verify that allocated and free buffers do not overlap, and that our and
 * malloc's ideas of the number of allocated buffers match.
 */

static int check_for_overlaps(void)
{
	int i;
	int allocation_match;
	int allocations_count, allocated_count;

	allocations_count = allocated_count = 0;
	for (i = 0; i < ARRAY_SIZE(allocations); i++) {
		struct shm_buffer *allocced_buf;

		if (!allocations[i].buf)
			continue;

		/*
		 * Indication of finding the allocated buffer in internal
		 * malloc structures.
		 */
		allocation_match = 0;

		/* number of buffers allocated by the test program. */
		allocations_count++;

		/*
		 * Number of allocated buffers malloc knows about, calculated
		 * multiple times to keep things simple.
		 */
		allocated_count = 0;
		for (allocced_buf = allocced_buf_chain;
		     allocced_buf;
		     allocced_buf = allocced_buf->next_buffer) {
			int allocated_size, allocation_size;

			allocated_count++;
			if (allocations[i].buf != (allocced_buf + 1))
				continue;

			allocated_size = allocced_buf->buffer_size;
			allocation_size = allocations[i].buffer_size;

			/*
			 * Verify that size requested by the allocator matches
			 * the value used by malloc, i.e. does not exceed the
			 * allocated size and is no less than two buffer
			 * structures lower (which can happen when the
			 * requested size was rounded up to cover gaps smaller
			 * than the buffer header structure size).
			 */
			if ((allocation_size > allocated_size) ||
			    ((allocated_size - allocation_size) >=
			     (2 * sizeof(struct shm_buffer) + sizeof(int)))) {
				ccprintf("inconsistency: allocated (size %d)"
					 " allocation %d(size %d)\n",
					 allocated_size, i, allocation_size);
				return 0;
			}

			if (allocation_match++) {
				ccprintf("inconsistency: duplicated match\n");
				return 0;
			}
		}
		if (!allocation_match) {
			ccprintf("missing match %pP!\n", allocations[i].buf);
			return 0;
		}
	}
	if (allocations_count != allocated_count) {
		ccprintf("count mismatch (%d != %d)!\n",
			 allocations_count, allocated_count);
		return 0;
	}
	return 1;
}

/*
 * Verify that shared memory is in a consistent state, i.e. that there is no
 * overlaps between allocated and free buffers, and that all memory is
 * accounted for (is either allocated or available).
 */

static int shmem_is_ok(int line)
{
	int count = 0;
	int running_size = 0;
	struct shm_buffer *pbuf = free_buf_chain;

	if (pbuf && pbuf->prev_buffer) {
		ccprintf("Bad free buffer list start %pP\n", pbuf);
		goto bailout;
	}

	while (pbuf) {
		struct shm_buffer *top;

		running_size += pbuf->buffer_size;
		if (count++ > 100)
			goto bailout;  /* Is there a loop? */

		top = (struct shm_buffer *)((uintptr_t)pbuf +
					     pbuf->buffer_size);
		if (pbuf->next_buffer) {
			if (top >= pbuf->next_buffer) {
				ccprintf("%s:%d"
					 " - inconsistent buffer size at %pP\n",
					 __func__, __LINE__, pbuf);
				goto bailout;
			}
			if (pbuf->next_buffer->prev_buffer != pbuf) {
				ccprintf("%s:%d"
					 " - inconsistent next buffer at %pP\n",
					 __func__, __LINE__, pbuf);
				goto bailout;
			}
		}
		pbuf = pbuf->next_buffer;
	}

	if (pbuf) { /* Must be a loop. */
		ccprintf("Too many buffers in the chain\n");
		goto bailout;
	}

	/* Make sure there were at least 5 buffers allocated at one point. */
	if (count > 5)
		set_map_bit(1 << 24);

	/* Add allocated sizes. */
	for (pbuf = allocced_buf_chain; pbuf; pbuf = pbuf->next_buffer)
		running_size += pbuf->buffer_size;

	if (total_size) {
		if (total_size != running_size)
			goto bailout;
	} else {
		/* Remember total size for future reference. */
		total_size = running_size;
	}

	if (!check_for_overlaps())
		goto bailout;

	return 1;

 bailout:
	ccprintf("Line %d, counter %d. The list has been corrupted, "
		 "total size %d, running size %d\n",
		 line, counter, total_size, running_size);
	return 0;
}

/*
 * Bitmap used to keep track of branches taken by malloc/free routines. Once
 * all bits in the 0..(MAX_MASK_BIT - 1) range are set, consider the test
 * completed.
 */
static uint32_t test_map;

void run_test(int argc, char **argv)
{
	int index;
	const int shmem_size = shared_mem_size();

	while (counter--) {
		char *shptr;
		uint32_t r_data;

		r_data = myrand();

		if (!(counter % 50000))
			ccprintf("%d\n", counter);

		/*
		 * If all bits we care about are set in the map - the test is
		 * over.
		 */
		if ((test_map & ALL_PATHS_MASK) == ALL_PATHS_MASK) {
			if (test_map & ~ALL_PATHS_MASK) {
				ccprintf("Unexpected mask bits set: %x"
					 ", counter %d\n",
					 test_map & ~ALL_PATHS_MASK,
					 counter);
				test_fail();
				return;
			}
			ccprintf("Done testing, counter at %d\n", counter);
			test_pass();
			return;
		}

		/* Pick a random allocation entry. */
		index = r_data % ARRAY_SIZE(allocations);
		if (allocations[index].buf) {
			/*
			 * If there is a buffer associated with the entry -
			 * release it.
			 */
			shared_mem_release(allocations[index].buf);
			allocations[index].buf = 0;
			if (!shmem_is_ok(__LINE__)) {
				test_fail();
				return;
			}
		} else {
			size_t alloc_size = r_data % (shmem_size / 2);

			/*
			 * If the allocation entry is empty - allocate a
			 * buffer of a random size up to max shared memory.
			 */
			if (shared_mem_acquire(alloc_size, &shptr) ==
			    EC_SUCCESS) {
				allocations[index].buf = (void *) shptr;
				allocations[index].buffer_size = alloc_size;

				/*
				 * Make sure every allocated byte is
				 * modified.
				 */
				while (alloc_size--)
					shptr[alloc_size] =
					shptr[alloc_size] ^ 0xff;

				if (!shmem_is_ok(__LINE__)) {
					test_fail();
					return;
				}
			}
		}
	}

	/*
	 * The test is over, free all still allcated buffers, if any. Keep
	 * verifying memory consistency after each free() invocation.
	 */
	for (index = 0; index < ARRAY_SIZE(allocations); index++)
		if (allocations[index].buf) {
			shared_mem_release(allocations[index].buf);
			allocations[index].buf = NULL;
			if (!shmem_is_ok(__LINE__)) {
				test_fail();
				return;
			}
		}

	ccprintf("Did not pass all paths, map %x != %x\n",
		 test_map, ALL_PATHS_MASK);
	test_fail();
}

void set_map_bit(uint32_t mask)
{
	test_map |= mask;
}
