| /****************************************************************************** |
| * 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(unsigned 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 unsigned int calc_used_nvram_space(void) |
| { |
| unsigned 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}; |
| unsigned 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, unsigned len) |
| { |
| partition_t ret = { 0, 0 }; |
| unsigned i, offset, plen; |
| |
| 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) |
| { |
| unsigned 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; |
| } |