iBoot/lib/nvram/nvram.c

556 lines
14 KiB
C

/*
* Copyright (c) 2007-2012 Apple Inc. All rights reserved.
* Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
*
* This document is the property of Apple Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Inc.
*/
#include <debug.h>
#include <stdlib.h>
#include <list.h>
#include <lib/blockdev.h>
#include <lib/cksum.h>
#include <lib/env.h>
#include <lib/libiboot.h>
#include <lib/nvram.h>
#include <string.h>
#include <sys/menu.h>
/*
* Regardless of whether it is NOR or NAND providing the backing
* store, NVRAM_BANK_SIZE controls default size of each nvram bank.
*/
#define NVRAM_BANK_SIZE 0x2000
/*
* Support for CHRP NVRAM hosting our environment.
*/
/* this is a somewhat arbitrary and historical value */
#define NVRAM_MAX_ENV_PARTITION 0x7f0
/* Panic info partition */
#define NVRAM_PANIC_NAME "APL,OSXPanic"
#define NVRAM_PANIC_NAME_TRUNCATED "APL,OSXPani"
#define NVRAM_PANIC_SIZE 0x800
#define NVRAM_PANIC_SIG 0xa1
struct chrp_nvram_header {
uint8_t sig;
uint8_t cksum;
uint16_t len;
char name[12];
uint8_t data[0];
};
struct apple_nvram_header {
struct chrp_nvram_header chrp;
uint32_t adler;
uint32_t generation;
uint8_t padding[8];
};
/* in memory representation of a nvram partition */
struct nvram_partition {
struct list_node node;
uint8_t chrp_sig;
size_t len;
uint8_t *data;
char name[16];
};
/* in memory representation of a nvram bank (may be multiple ones) */
struct nvram_bank {
struct list_node node;
struct list_node part_list;
struct blockdev *dev;
off_t offset;
size_t len;
uint32_t generation;
};
static struct list_node nvram_bank_list = LIST_INITIAL_VALUE(nvram_bank_list);
static struct nvram_bank *newest_bank;
static struct nvram_bank *oldest_bank;
static uint8_t chrp_checksum(const struct chrp_nvram_header *hdr)
{
uint16_t sum;
const uint8_t *p;
/* checksum the header (minus the checksum itself) */
sum = hdr->sig;
for (p = (const uint8_t *)&hdr->len; p < hdr->data; p++)
sum += *p;
while (sum > 0xff)
sum = (sum & 0xff) + (sum >> 8);
return sum;
}
static struct nvram_partition *find_part_in_bank(const struct nvram_bank *bank, const char *name)
{
struct nvram_partition *part;
list_for_every_entry(&bank->part_list, part, struct nvram_partition, node) {
if (!strcmp(name, part->name))
return part;
}
return NULL;
}
/* find the oldest and newest banks */
static void sort_bank_age(void)
{
struct nvram_bank *bank;
list_for_every_entry(&nvram_bank_list, bank, struct nvram_bank, node) {
if (!oldest_bank || bank->generation < oldest_bank->generation)
oldest_bank = bank;
if (!newest_bank || bank->generation > newest_bank->generation)
newest_bank = bank;
}
}
static int load_bank_partitions(struct nvram_bank *bank, uint8_t *buf)
{
struct chrp_nvram_header *hdr;
struct nvram_partition *part = NULL;
size_t offset;
offset = 0x20;
while ((offset + sizeof(struct chrp_nvram_header)) <= bank->len) {
hdr = (struct chrp_nvram_header *)(buf + offset);
#if 0
dprintf(DEBUG_SPEW, "nvram: load_bank_partitions: looking at header at offset %u\n", offset);
dhexdump(DEBUG_SPEW, hdr, sizeof(*hdr));
#endif
/* verify the checksum */
if (hdr->cksum != chrp_checksum(hdr)) {
#if !NVRAM_NOCHECKSUM
printf("load_bank_partitions: checksum failure\n");
goto error_exit;
#endif
}
/* is it free space? if so, we're done */
if (hdr->sig == 0x7f)
break;
/* see if the length makes sense */
if (((hdr->len * 0x10) + offset > bank->len) || (hdr->len < 1)) {
printf("load_bank_partitions: partition len out of range\n");
goto error_exit;
}
/* skip it if it's a known corrupted partition name */
if (!memcmp(hdr->name, NVRAM_PANIC_NAME_TRUNCATED, 12)) {
/* The last byte shouldn't have been terminated */
offset += hdr->len * 0x10;
/* This partition should disappear when written back */
continue;
}
/* it's good, create a partition */
part = malloc(sizeof(struct nvram_partition));
/* note that 0x10 == sizeof(struct chrp_nvram_header) */
part->chrp_sig = hdr->sig;
part->len = (hdr->len * 0x10) - 0x10;
ASSERT(sizeof(part->name) >= sizeof(hdr->name) + 1);
memcpy(part->name, hdr->name, sizeof(hdr->name));
part->name[sizeof(hdr->name)] = '\0';
part->data = malloc(part->len);
memcpy(part->data, buf + offset + 0x10, part->len);
dprintf(DEBUG_SPEW, "found partition '%s' at offset %zu, len %zu\n", part->name, offset, part->len);
/* add it to the bank list */
list_add_tail(&bank->part_list, &part->node);
offset += hdr->len * 0x10;
}
return 0;
error_exit:
if (part != NULL) {
if (part->data != NULL)
free(part->data);
free(part);
}
return -1;
}
static void process_loaded_bank(struct nvram_bank *bank)
{
struct nvram_partition *part;
/* look for the environment variable partition */
part = find_part_in_bank(bank, "common");
if (part) {
env_unserialize(part->data, part->len);
}
}
static int nvram_save_env(struct nvram_bank *bank)
{
struct nvram_partition *part;
size_t env_length;
/* see if there's already a common partition */
part = find_part_in_bank(bank, "common");
if (part == NULL) {
/* create a new one */
part = calloc(1, sizeof(struct nvram_partition));
part->chrp_sig = 0x70;
strlcpy(part->name, "common", sizeof(part->name));
part->len = 0;
part->data = NULL;
list_add_head(&bank->part_list, &part->node);
}
/* throw away the old data, we're going to overwrite it */
if (part->data)
free(part->data);
/* allocate a new buffer for this data */
part->len = NVRAM_MAX_ENV_PARTITION;
part->data = malloc(part->len);
/* serialise environment and adjust buffer size to suit */
env_length = env_serialize(part->data, part->len);
memset(part->data + env_length, 0, part->len - env_length);
return 0;
}
static int nvram_prepare_bank(struct nvram_bank *bank, uint8_t *buf)
{
int err = 0;
struct nvram_partition *part;
struct apple_nvram_header *apple_hdr;
struct chrp_nvram_header *hdr;
size_t offset;
/* lay down an apple nvram header */
apple_hdr = (struct apple_nvram_header *)buf;
apple_hdr->chrp.sig = 0x5a;
apple_hdr->chrp.len = 0x2; // 0x20 bytes
memcpy(apple_hdr->chrp.name, "nvram", sizeof("nvram"));
apple_hdr->chrp.cksum = chrp_checksum(&apple_hdr->chrp);
apple_hdr->generation = bank->generation;
/* iterate through each of the sections in the nvram, laying them out in the buffer */
offset = 0x20;
list_for_every_entry(&bank->part_list, part, struct nvram_partition, node) {
/* partition must have data */
if (0 == part->len)
goto fail;
/* make sure there is room to establish a header in the buffer */
if ((offset + sizeof(struct chrp_nvram_header)) > bank->len)
goto fail;
hdr = (struct chrp_nvram_header *)&buf[offset];
hdr->sig = part->chrp_sig;
hdr->len = ROUNDUP(part->len + 0x10, 0x10) / 0x10;
memcpy(hdr->name, part->name, sizeof(hdr->name));
hdr->cksum = chrp_checksum(hdr);
/* make sure there is room to copy data into the buffer */
if ((offset + sizeof(struct chrp_nvram_header) + part->len) > bank->len)
goto fail;
memcpy(hdr->data, part->data, part->len);
offset += hdr->len * 0x10;
}
/* write out a free space partition to cover the rest of the buffer, if there's free space */
if ((offset + sizeof(struct chrp_nvram_header)) <= bank->len) {
hdr = (struct chrp_nvram_header *)&buf[offset];
hdr->sig = 0x7f;
hdr->len = (bank->len - offset) / 0x10;
memset(hdr->name, 0x77, sizeof(hdr->name));
hdr->cksum = chrp_checksum(hdr);
}
/* fill in the adler32 checksum on the apple header */
apple_hdr->adler = adler32(buf + 0x14, bank->len - 0x14);
out:
return err;
fail:
err = -1;
goto out;
}
static int nvram_write_bank(struct nvram_bank *bank)
{
int err;
uint8_t *buf;
/* allocate a buffer to stage this in */
buf = calloc(1, bank->len);
/* prepare buf with bank data */
err = nvram_prepare_bank(bank, buf);
if (err) {
dprintf(DEBUG_CRITICAL, "unable to prepare buffer with nvram bank data\n");
goto out;
}
/* write it out */
if (bank->dev) {
dprintf(DEBUG_INFO, "saving nvram contents at bdev '%s', offset 0x%llx, len 0x%zx, gen %d\n",
bank->dev->name, bank->offset, bank->len, bank->generation);
err = blockdev_write(bank->dev, buf, bank->offset, bank->len);
} else {
err = 0;
dprintf(DEBUG_CRITICAL, "nowhere to save environment\n");
}
#if 0
printf("would have written this to offset 0x%llx, len %d:\n", bank->offset, bank->len);
hexdump(buf, bank->len);
#endif
out:
free(buf);
return err;
}
int nvram_save(void)
{
int err;
/* overwrite the 'oldest' bank (lowest generation count) */
if (!oldest_bank) {
printf("nvram_save: no oldest bank previously saved, dropping request\n");
return 0;
}
/* find the next generation number to assign this bank */
if (newest_bank)
oldest_bank->generation = newest_bank->generation + 1;
else
oldest_bank->generation = 1;
/* write out our environment */
nvram_save_env(oldest_bank);
/* write out the bank */
err = nvram_write_bank(oldest_bank);
if (err < 0)
return err;
/* we have a new oldest bank, reevaluate */
sort_bank_age();
return 0;
}
int nvram_set_panic(const void *panic_data, size_t length)
{
struct nvram_partition *part;
/* overwrite the 'oldest' bank (lowest generation count) */
if (!oldest_bank) {
printf("nvram_set_panic: no oldest bank previously saved, dropping request\n");
return 0;
}
/* find a panic info partition */
part = find_part_in_bank(oldest_bank, NVRAM_PANIC_NAME);
if (part == NULL) {
/* create a new one */
part = calloc(1, sizeof(struct nvram_partition));
part->chrp_sig = NVRAM_PANIC_SIG;
strlcpy(part->name, NVRAM_PANIC_NAME, sizeof(part->name));
part->len = NVRAM_PANIC_SIZE;
part->data = malloc(NVRAM_PANIC_SIZE);
list_add_head(&oldest_bank->part_list, &part->node);
}
if (part->len < 5) {
/* partition not big enough to store anything */
return -1;
}
/* the first 4 bytes are the panic length. adjust length to fit. */
if (length + 4 > part->len) length = part->len - 4;
*(uint32_t *) part->data = length;
/* copy panic data into the partition. */
memcpy(part->data + 4, panic_data, length);
/* zero the remainder of the partition. */
memset(part->data + 4 + length, 0, part->len - length - 4);
return 0;
}
static int nvram_load_dev(struct blockdev *dev, off_t offset, size_t banklen, int count)
{
int err = 0;
int i;
uint8_t *buf;
int banks_found = 0;
if (offset < 0) {
offset = dev->total_len + offset;
}
buf = malloc(banklen);
for (i=0; i < count; i++, offset += banklen) {
struct nvram_bank *bank;
struct apple_nvram_header *hdr;
/* read in potential nvram bank */
if (dev == NULL) {
dprintf(DEBUG_INFO, "nowhere to load environment from; using transient buffer-only solution\n");
goto bank_fail;
}
err = blockdev_read(dev, buf, offset, banklen);
if (err <= 0) {
dprintf(DEBUG_INFO, "nvram_load: failed to read potential nvram bank at offset 0x%llx\n", offset);
continue;
}
#if 0
printf("potential nvram bank: \n");
hexdump(buf, banklen);
#endif
/* see if it starts with a valid header */
hdr = (struct apple_nvram_header *)buf;
if (hdr->chrp.cksum != chrp_checksum(&hdr->chrp)) {
#if !NVRAM_NOCHECKSUM
dprintf(DEBUG_INFO, "nvram_load: nvram partition at offset 0x%llx failed checksum\n", offset);
goto bank_fail;
#endif
}
/* do an adler32 of the entire buffer */
uint32_t adler = adler32(buf + 0x14, banklen - 0x14);
if (adler != hdr->adler) {
#if !NVRAM_NOCHECKSUM
dprintf(DEBUG_INFO, "nvram_load: nvram bank fails adler32\n");
goto bank_fail;
#endif
}
/* valid header, lets start building a bank structure */
bank = malloc(sizeof(struct nvram_bank));
list_initialize(&bank->part_list);
bank->dev = dev;
bank->offset = offset;
bank->len = banklen;
bank->generation = hdr->generation;
/* read in the partitions within the bank */
err = load_bank_partitions(bank, buf);
if (err < 0) {
// error parsing partitions, free this bank and continue
free(bank);
goto bank_fail;
}
/* add the bank to the list */
list_add_tail(&nvram_bank_list, &bank->node);
banks_found++;
continue;
bank_fail:
/* we failed to load a bank at a nvram slot, allocate a blank bank */
bank = malloc(sizeof(struct nvram_bank));
list_initialize(&bank->part_list);
bank->dev = dev;
bank->offset = offset;
bank->len = banklen;
bank->generation = 0;
dprintf(DEBUG_INFO, "nvram_load: found invalid or empty bank at 0x%llx, creating blank bank\n", offset);
/* add the bank to the list */
banks_found++;
list_add_tail(&nvram_bank_list, &bank->node);
}
dprintf(DEBUG_INFO, "nvram_load: found %d banks of nvram\n", banks_found);
if (banks_found) {
/* search through the bank list, sorting by age */
sort_bank_age();
/* do any bank processing on the current (newest) bank */
process_loaded_bank(newest_bank);
}
free(buf);
return banks_found;
}
int nvram_load(void)
{
int result;
struct blockdev *bdev;
dprintf(DEBUG_INFO, "loading nvram\n");
// Look for a real NVRAM block device
bdev = lookup_blockdev("nvram");
if (bdev) {
result = nvram_load_dev(bdev, 0, NVRAM_BANK_SIZE, 1);
return result;
}
/* Fall back to just creating a temporary nvram not backed by any block device */
return (nvram_load_dev(NULL, 0, NVRAM_BANK_SIZE, 1) ? 0 : -1);
}
int nvram_update_devicetree(dt_node_t *node, const char *propname)
{
uint8_t *buf;
int err = -1;
if (!oldest_bank) {
panic("nvram_update_devicetree: no bank previously saved\n");
} else if (!newest_bank) {
dprintf(DEBUG_CRITICAL, "nvram_update_devicetree: no content available\n");
} else {
buf = malloc(newest_bank->len);
/* prepare device tree proper buffer with bank data */
err = nvram_prepare_bank(newest_bank, buf);
if (err) {
dprintf(DEBUG_CRITICAL, "nvram_update_devicetree: unable to update devicetree with nvram content\n");
} else {
dt_set_prop(node, propname, buf, newest_bank->len);
dprintf(DEBUG_INFO, "nvram_update_devicetree: updated devicetree with nvram generation %d\n",
newest_bank->generation);
}
free(buf);
}
return err;
}