661 lines
15 KiB
C
661 lines
15 KiB
C
/******************************************************************************
|
|
* 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;
|
|
}
|
|
|
|
/* Get partition specified by a Forth string */
|
|
partition_t get_partition_fs(char *name, int namelen)
|
|
{
|
|
char buf[namelen + 1];
|
|
|
|
memcpy(buf, name, namelen);
|
|
buf[namelen] = 0;
|
|
|
|
return get_partition(-1, buf);
|
|
}
|
|
|
|
void erase_nvram(int offset, int len)
|
|
{
|
|
int i;
|
|
|
|
#ifdef RTAS_NVRAM
|
|
char *erase_buf = get_nvram_buffer(len);
|
|
if (erase_buf) {
|
|
/* Speed up by erasing all memory at once */
|
|
memset(erase_buf, 0, len);
|
|
nvram_store(offset, erase_buf, len);
|
|
free_nvram_buffer(erase_buf);
|
|
return;
|
|
}
|
|
/* If get_nvram_buffer failed, fall through to default code */
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
partition_t new_nvram_partition_fs(int type, char *name, int namelen, int len)
|
|
{
|
|
char buf[13];
|
|
int i;
|
|
|
|
for (i = 0; i < 12; i++) {
|
|
if (i < namelen)
|
|
buf[i] = name[i];
|
|
else
|
|
buf[i] = 0;
|
|
}
|
|
buf[12] = 0;
|
|
|
|
return new_nvram_partition(type, buf, len);
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
}
|