summaryrefslogtreecommitdiffstats
path: root/qemu/roms/SLOF/lib/libnvram/nvram.c
blob: 5c11376699d722bd0cf8cc00537c67063513631d (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
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
/******************************************************************************
 * Copyright (c) 2004, 2008 IBM Corporation
 * All rights reserved.
 * This program and the accompanying materials
 * are made available under the terms of the BSD License
 * which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/bsd-license.php
 *
 * Contributors:
 *     IBM Corporation - initial implementation
 *****************************************************************************/

#include "cache.h"
#include "nvram.h"
#include "../libhvcall/libhvcall.h"

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <southbridge.h>
#include <nvramlog.h>
#include <byteorder.h>

#ifdef RTAS_NVRAM
static uint32_t fetch_token;
static uint32_t store_token;
static uint32_t NVRAM_LENGTH;
static char *nvram_buffer; /* use buffer allocated by SLOF code */
#else
#ifndef NVRAM_LENGTH
#define NVRAM_LENGTH	0x10000
#endif
/*
 * This is extremely ugly, but still better than implementing 
 * another sbrk() around it.
 */
static char nvram_buffer[NVRAM_LENGTH];
#endif

static uint8_t nvram_buffer_locked=0x00;

void nvram_init(uint32_t _fetch_token, uint32_t _store_token, 
		long _nvram_length, void* nvram_addr)
{
#ifdef RTAS_NVRAM
	fetch_token = _fetch_token;
	store_token = _store_token;
	NVRAM_LENGTH = _nvram_length;
	nvram_buffer = nvram_addr;

	DEBUG("\nNVRAM: size=%d, fetch=%x, store=%x\n",
		NVRAM_LENGTH, fetch_token, store_token);
#endif
}


void asm_cout(long Character,long UART,long NVRAM);

#if defined(DISABLE_NVRAM)

static volatile uint8_t nvram[NVRAM_LENGTH]; /* FAKE */

#define nvram_access(type,size,name) 				\
	type nvram_read_##name(unsigned int offset)		\
	{							\
		type *pos;					\
		if (offset > (NVRAM_LENGTH - sizeof(type)))	\
			return 0;				\
		pos = (type *)(nvram+offset);			\
		return *pos;					\
	}							\
	void nvram_write_##name(unsigned int offset, type data)	\
	{							\
		type *pos;					\
		if (offset > (NVRAM_LENGTH - sizeof(type)))	\
			return;					\
		pos = (type *)(nvram+offset);			\
		*pos = data;					\
	}

#elif defined(RTAS_NVRAM)

static inline void nvram_fetch(unsigned int offset, void *buf, unsigned int len)
{
 	struct hv_rtas_call rtas = {
		.token = fetch_token,
		.nargs = 3,
		.nrets = 2,
		.argret = { offset, (uint32_t)(unsigned long)buf, len },
	};
	h_rtas(&rtas);
}

static inline void nvram_store(unsigned int offset, void *buf, unsigned int len)
{
	struct hv_rtas_call rtas = {
		.token = store_token,
		.nargs = 3,
		.nrets = 2,
		.argret = { offset, (uint32_t)(unsigned long)buf, len },
	};
	h_rtas(&rtas);
}

#define nvram_access(type,size,name) 				\
	type nvram_read_##name(unsigned int offset)		\
	{							\
		type val;					\
		if (offset > (NVRAM_LENGTH - sizeof(type)))	\
			return 0;				\
		nvram_fetch(offset, &val, size / 8);		\
		return val;					\
	}							\
	void nvram_write_##name(unsigned int offset, type data)	\
	{							\
		if (offset > (NVRAM_LENGTH - sizeof(type)))	\
			return;					\
		nvram_store(offset, &data, size / 8);		\
	}

#else	/* DISABLE_NVRAM */

static volatile uint8_t *nvram = (volatile uint8_t *)SB_NVRAM_adr;

#define nvram_access(type,size,name) 				\
	type nvram_read_##name(unsigned int offset)		\
	{							\
		type *pos;					\
		if (offset > (NVRAM_LENGTH - sizeof(type)))	\
			return 0;				\
		pos = (type *)(nvram+offset);			\
		return ci_read_##size(pos);			\
	}							\
	void nvram_write_##name(unsigned int offset, type data)	\
	{							\
		type *pos;					\
		if (offset > (NVRAM_LENGTH - sizeof(type)))	\
			return;					\
		pos = (type *)(nvram+offset);			\
		ci_write_##size(pos, data);			\
	}

#endif

/*
 * producer for nvram access functions. Since these functions are
 * basically all the same except for the used data types, produce 
 * them via the nvram_access macro to keep the code from bloating.
 */

nvram_access(uint8_t,   8, byte)
nvram_access(uint16_t, 16, word)
nvram_access(uint32_t, 32, dword)
nvram_access(uint64_t, 64, qword)



/**
 * This function is a minimal abstraction for our temporary
 * buffer. It should have been malloced, but since there is no
 * usable malloc, we go this route.
 *
 * @return pointer to temporary buffer
 */

char *get_nvram_buffer(int len)
{
	if(len>NVRAM_LENGTH)
		return NULL;

	if(nvram_buffer_locked)
		return NULL;

	nvram_buffer_locked = 0xff;

	return nvram_buffer;
}

/**
 * @param buffer pointer to the allocated buffer. This
 * is unused, but nice in case we ever get a real malloc
 */

void free_nvram_buffer(char *buffer __attribute__((unused)))
{
	nvram_buffer_locked = 0x00;
}

/**
 * @param fmt format string, like in printf
 * @param ... variable number of arguments
 */

int nvramlog_printf(const char* fmt, ...)
{
	char buff[256];
	int count, i;
	va_list ap;

	va_start(ap, fmt);
	count = vsprintf(buff, fmt, ap);
	va_end(ap);

	for (i=0; i<count; i++)
		asm_cout(buff[i], 0, 1);

	return count;
}

/**
 * @param offset start offset of the partition header
 */

static uint8_t get_partition_type(int offset)
{
	return nvram_read_byte(offset);
}

/**
 * @param offset start offset of the partition header
 */

static uint8_t get_partition_header_checksum(int offset)
{
	return nvram_read_byte(offset+1);
}

/**
 * @param offset start offset of the partition header
 */

static uint16_t get_partition_len(int offset)
{
	return nvram_read_word(offset+2);
}

/**
 * @param offset start offset of the partition header
 * @return static char array containing the partition name
 *
 * NOTE: If the partition name needs to be non-temporary, strdup 
 * and use the copy instead.
 */

static char * get_partition_name(int offset)
{
	static char name[12];
	int i;
	for (i=0; i<12; i++)
		name[i]=nvram_read_byte(offset+4+i);

	DEBUG("name: \"%s\"\n", name);
	return name;
}

static uint8_t calc_partition_header_checksum(int offset)
{
	uint16_t plainsum;
	uint8_t checksum;
	int i;

	plainsum = nvram_read_byte(offset);

	for (i=2; i<PARTITION_HEADER_SIZE; i++)
		plainsum+=nvram_read_byte(offset+i);

	checksum=(plainsum>>8)+(plainsum&0xff);

	return checksum;
}

static int calc_used_nvram_space(void)
{
	int walk, len;

	for (walk=0; walk<NVRAM_LENGTH;) {
		if(nvram_read_byte(walk) == 0 
		   || get_partition_header_checksum(walk) != 
				calc_partition_header_checksum(walk)) {
			/* If there's no valid entry, bail out */
			break;
		}

		len=get_partition_len(walk);
		DEBUG("... part len=%x, %x\n", len, len*16);

		if(!len) {
			/* If there's a partition type but no len, bail out.
			 * Don't bail out if type is 0. This can be used to
			 * find the offset of the first free byte.
			 */
			break;
		}

		walk += len * 16;
	}
	DEBUG("used nvram space: %d\n", walk);

	return walk;
}

/**
 *
 * @param type partition type. Set this to the partition type you are looking
 *             for. If there are several partitions with the same type, only
 *             the first partition with that type will be found.
 *             Set to -1 to ignore. Set to 0 to find free unpartitioned space.
 *
 * @param name partition name. Set this to the name of the partition you are
 *             looking for. If there are several partitions with the same name,
 *             only the first partition with that name will be found.
 *             Set to NULL to ignore.
 *
 * To disambiguate the partitions you should have a unique name if you plan to
 * have several partitions of the same type.
 *
 */

partition_t get_partition(unsigned int type, char *name)
{
	partition_t ret={0,-1};
	int walk, len;

	DEBUG("get_partition(%i, '%s')\n", type, name);

	for (walk=0; walk<NVRAM_LENGTH;) {
		// DEBUG("get_partition: walk=%x\n", walk);
		if(get_partition_header_checksum(walk) != 
				calc_partition_header_checksum(walk)) {
			/* If there's no valid entry, bail out */
			break;
		}

		len=get_partition_len(walk);
		if(type && !len) {
			/* If there's a partition type but no len, bail out.
			 * Don't bail out if type is 0. This can be used to
			 * find the offset of the first free byte.
			 */
			break;
		}

		/* Check if either type or name or both do not match. */
		if ( (type!=(unsigned int)-1 && type != get_partition_type(walk)) ||
			(name && strncmp(get_partition_name(walk), name, 12)) ) {
			/* We hit another partition. Continue
			 * at the end of this partition
			 */
			walk += len*16;
			continue;
		}

		ret.addr=walk+PARTITION_HEADER_SIZE;
		ret.len=(len*16)-PARTITION_HEADER_SIZE;
		break;
	}

	return ret;
}

void erase_nvram(int offset, int len)
{
	int i;

	for (i=offset; i<offset+len; i++)
		nvram_write_byte(i, 0);
}

void wipe_nvram(void)
{
	erase_nvram(0, NVRAM_LENGTH);
}

/**
 * @param partition   partition structure pointing to the partition to wipe.
 * @param header_only if header_only is != 0 only the partition header is
 *                    nulled out, not the whole partition.
 */

int wipe_partition(partition_t partition, int header_only)
{
	int pstart, len;

	pstart=partition.addr-PARTITION_HEADER_SIZE;
	
	len=PARTITION_HEADER_SIZE;

	if(!header_only)
		len += partition.len;

	erase_nvram(pstart, len);

	return 0;
}


static partition_t create_nvram_partition(int type, const char *name, int len)
{
	partition_t ret = { 0, 0 };
	int offset, plen;
	unsigned int i;

	plen = ALIGN(len+PARTITION_HEADER_SIZE, 16);

	DEBUG("Creating partition type=%x, name=%s, len=%d plen=%d\n",
			type, name, len, plen);

	offset = calc_used_nvram_space();

	if (NVRAM_LENGTH-(calc_used_nvram_space())<plen) {
		DEBUG("Not enough free space.\n");
		return ret;
	}

	DEBUG("Writing header.");

	nvram_write_byte(offset, type);
	nvram_write_word(offset+2, plen/16);

	for (i=0; i<strlen(name); i++)
		nvram_write_byte(offset+4+i, name[i]);

	nvram_write_byte(offset+1, calc_partition_header_checksum(offset));

	ret.addr = offset+PARTITION_HEADER_SIZE;
	ret.len = len;

	DEBUG("partition created: addr=%lx len=%lx\n", ret.addr, ret.len);

	return ret;
}

static int create_free_partition(void)
{
	int free_space;
	partition_t free_part;

	free_space = NVRAM_LENGTH - calc_used_nvram_space() - PARTITION_HEADER_SIZE;
	free_part = create_nvram_partition(0x7f, "free space", free_space);

	return (free_part.addr != 0);
}

partition_t new_nvram_partition(int type, char *name, int len)
{
	partition_t free_part, new_part = { 0, 0 };

	/* NOTE: Assume all free space is consumed by the "free space"
	 * partition. This means a partition can not be increased in the middle
	 * of reset_nvram, which is obviously not a big loss.
	 */

	free_part=get_partition(0x7f, NULL);
	if( free_part.len && free_part.len != -1)
		wipe_partition(free_part, 1);

	new_part = create_nvram_partition(type, name, len);

	if(new_part.len != len) {
		new_part.len = 0;
		new_part.addr = 0;
	}

	create_free_partition();

	return new_part;
}

/**
 * @param partition   partition structure pointing to the partition to wipe.
 */

int delete_nvram_partition(partition_t partition)
{
	int i;
	partition_t free_part;

	if(!partition.len || partition.len == -1)
		return 0;

	for (i=partition.addr+partition.len; i< NVRAM_LENGTH; i++) 
		nvram_write_byte(i - partition.len - PARTITION_HEADER_SIZE, nvram_read_byte(i));

	erase_nvram(NVRAM_LENGTH-partition.len-PARTITION_HEADER_SIZE, 
			partition.len-PARTITION_HEADER_SIZE);

	free_part=get_partition(0x7f, NULL);
	wipe_partition(free_part, 0);
	create_free_partition();

	return 1;
}

int clear_nvram_partition(partition_t part)
{
	if(!part.addr)
		return 0;

	erase_nvram(part.addr, part.len);

	return 1;
}


int increase_nvram_partition_size(partition_t partition, int newsize)
{
	partition_t free_part;
	int free_offset, end_offset, i;

	/* We don't support shrinking partitions (yet) */
	if (newsize < partition.len) {
		return 0;
	}

	/* NOTE: Assume all free space is consumed by the "free space"
	 * partition. This means a partition can not be increased in the middle
	 * of reset_nvram, which is obviously not a big loss.
	 */

	free_part=get_partition(0x7f, NULL);

	// FIXME: It could be 16 byte more. Also handle empty "free" partition.
	if (free_part.len == -1 || free_part.len < newsize - partition.len ) {
		return 0;
	}
	
	free_offset=free_part.addr - PARTITION_HEADER_SIZE; // first unused byte
	end_offset=partition.addr + partition.len; // last used byte of partition + 1

	if(free_offset > end_offset) {
		int j, bufferlen;
		char *overlap_buffer;

		bufferlen=free_offset - end_offset;

		overlap_buffer=get_nvram_buffer(bufferlen);
		if(!overlap_buffer) {
			return 0;
		}

		for (i=end_offset, j=0; i<free_offset; i++, j++)
			overlap_buffer[j]=nvram_read_byte(i);

		/* Only wipe the header. The free space partition is empty per
		 * definition
		 */

		wipe_partition(free_part, 1);

		for (i=partition.addr+newsize, j=0; i<(int)(partition.addr+newsize+bufferlen); i++, j++)
			nvram_write_byte(i, overlap_buffer[j]);

		free_nvram_buffer(overlap_buffer);
	} else {
		/* Only wipe the header. */
		wipe_partition(free_part, 1);
	}

	/* Clear the new partition space */
	erase_nvram(partition.addr+partition.len, newsize-partition.len);

	nvram_write_word(partition.addr - 16 + 2, newsize);

	create_free_partition();

	return 1;
}

static void init_cpulog_partition(partition_t cpulog)
{
	unsigned int offset=cpulog.addr;

	/* see board-xxx/include/nvramlog.h for information */
	nvram_write_word(offset+0, 0x40);  // offset
	nvram_write_word(offset+2, 0x00);  // flags
	nvram_write_dword(offset+4, 0x01); // pointer

}

void reset_nvram(void)
{
	partition_t cpulog0, cpulog1;
	struct {
		uint32_t prefix;
		uint64_t name;
	} __attribute__((packed)) header;

	DEBUG("Erasing NVRAM\n");
	erase_nvram(0, NVRAM_LENGTH);

	DEBUG("Creating CPU log partitions\n");
	header.prefix = be32_to_cpu(LLFW_LOG_BE0_NAME_PREFIX);
	header.name   = be64_to_cpu(LLFW_LOG_BE0_NAME);
	cpulog0=create_nvram_partition(LLFW_LOG_BE0_SIGNATURE, (char *)&header, 
			(LLFW_LOG_BE0_LENGTH*16)-PARTITION_HEADER_SIZE);

	header.prefix = be32_to_cpu(LLFW_LOG_BE1_NAME_PREFIX);
	header.name   = be64_to_cpu(LLFW_LOG_BE1_NAME);
	cpulog1=create_nvram_partition(LLFW_LOG_BE1_SIGNATURE, (char *)&header, 
			(LLFW_LOG_BE1_LENGTH*16)-PARTITION_HEADER_SIZE);

	DEBUG("Initializing CPU log partitions\n");
	init_cpulog_partition(cpulog0);
	init_cpulog_partition(cpulog1);

	nvramlog_printf("Creating common NVRAM partition\r\n");
	create_nvram_partition(0x70, "common", 0x01000-PARTITION_HEADER_SIZE);

	create_free_partition();
}

void nvram_debug(void)
{
#ifndef RTAS_NVRAM
	printf("\nNVRAM_BASE: %p\n", nvram);
	printf("NVRAM_LEN: 0x%x\n", NVRAM_LENGTH);
#endif
}

unsigned int get_nvram_size(void)
{
	return NVRAM_LENGTH;
}