iBoot/drivers/flash_nand/boot/nand_boot.c

564 lines
17 KiB
C

/*
* Copyright (c) 2008-2012 Apple 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 <AssertMacros.h>
#include <debug.h>
#include <stdarg.h>
#include <sys.h>
#include <sys/menu.h>
#include <drivers/nand_boot.h>
#include <platform/clocks.h>
#include <platform/soc/hwclocks.h>
#include <lib/profile.h>
#include <lib/random.h>
#include "nand_part.h"
#include "nand_part_interface.h"
#include "nand_export.h"
#if WITH_NAND_FIRMWARE
# include "firmware/nand_firmware.h"
#endif
#if WITH_NAND_NVRAM
# include "nvram/nand_nvram_platform.h"
# include "nvram/nand_nvram_impl.h"
#endif
#if WITH_NAND_SYSCFG
# include <drivers/nand_syscfg.h>
#endif
#if WITH_EFFACEABLE_NAND
# include <lib/effaceable_nand.h>
#endif
/* ========================================================================== */
#if WITH_NAND_NVRAM
extern bool flash_nand_nvram_init(NandPartInterface *npi, uint32_t part, bool isNewborn);
#endif
// =============================================================================
static const char * kLogOrigin = "nand_boot";
#define _logLine(fac, fmt, args...) _logf_impl(kNandPartLogLevel##fac, "[%s:%s@%4d] " fmt "\n", kLogOrigin, kNandPartLogTag##fac, __LINE__, ##args)
#define error(args...) _logLine(Error, ##args)
#define info(args...) _logLine(Info, ##args)
#if NAND_PART_EXTENDED_LOGGING
# define warn(args...) _logLine(Warn, ##args)
#else
# define warn(args...) /* do nothing */
#endif
#if NAND_PART_EXTENDED_LOGGING
# define debug(args...) _logLine(Debug, ##args)
#else
# define debug(args...) /* do nothing */
#endif
#if NAND_PART_LOCAL_DEBUG_ONLY
# define spew(args...) _logLine(Spew, ##args)
#else
# define spew(args...) /* do nothing */
#endif
/* ========================================================================== */
#if NAND_PART_LOCAL_DEBUG_ONLY
static void
_conditionally_force_enable_uarts(void)
{
static bool isEnabled = false;
if (!isEnabled) {
debug_enable_uarts(3);
isEnabled = true;
}
}
#else
# define _conditionally_force_enable_uarts() /* do nothing */
#endif
/* ========================================================================== */
static nand_boot_context_t _context;
// =============================================================================
#if DEBUG_BUILD
# define USING_STRONG_SEED false
#else
# define USING_STRONG_SEED true
#endif
// =============================================================================
#if DEBUG_BUILD
# define USING_STRONG_SEED false
#else
# define USING_STRONG_SEED true
#endif
/* ========================================================================== */
// XXX - eliminate these references
#if WITH_NAND_FILESYSTEM
extern uint32_t nand_fsys_block_offset;
#elif WITH_NAND_SYSCFG
uint32_t nand_fsys_block_offset;
#endif
/* ========================================================================== */
static bool _create_partition_device(void * context, NandPartEntry * entry, uint32_t idx);
static bool _publish_partition_device(void * context, uint32_t idx);
static void * _alloc_mem(void * context, unsigned size, bool align);
static int _compare_mem(void * context, const void * left, const void * right, unsigned int size);
static void * _move_mem(void * context, void * dest, const void * src, unsigned size);
static void * _set_mem(void * context, void * mem, uint8_t val, unsigned size);
static void _free_mem(void * context, void * mem, unsigned size);
static void _lock_interface(void * context);
static void _unlock_interface(void * context);
static void _read_random(void * context, void * buf, unsigned size);
static bool _set_data_property(void * context, const char * key, void * buf, unsigned size);
static bool _set_number_property(void * context, const char * key, uint64_t num, unsigned bits);
static bool _set_string_property(void * context, const char * key, const char * cstring);
static bool _set_boolean_property(void * context, const char * key, bool val);
static bool _wait_for_fbbt_service(void * context);
static void _vlogf_message(void * context, NandPartLogLevel lvl, const char * fmt, va_list ap);
static void _vlogf_impl(NandPartLogLevel lvl, const char * fmt, va_list ap);
static void _logf_impl(NandPartLogLevel lvl, const char * fmt, ...);
static void init_nand_part_callbacks(NandPartProvider *npp);
static void init_nand_part_params(NandPartProvider *npp);
static bool init_nand_boot_context(nand_boot_context_t * cxt);
/* ========================================================================== */
int
nand_boot_init(void)
{
bool ok = true;
static nand_boot_context_t * cxt = NULL;
PROFILE_ENTER('NBI');
// If we've already been initialized for some reason, short
// circuit and finish.
require(NULL == cxt, finish);
cxt = &_context;
info("nand_boot_init() starting");
ok = init_nand_boot_context(cxt);
require(ok, finally);
spew("initializing nand_part subsystem");
ok = nand_part_init(&cxt->npi, &cxt->npp);
require(ok, finally);
spew("scanning for nand boot partition tables");
ok = npi_load_ptab(cxt->npi);
require(ok, finally);
#if WITH_NAND_SYSCFG
// Defer syscfg init until after file system has finished initializing.
//
// XXX - <rdar://problem/9192124> Open FTL before first syscfg request
if (cxt->syscfg_needs_init && cxt->found_fsys_offset) {
if (0 != nand_syscfg_init(cxt->npi)) {
spew("unable to init syscfg");
ok = false;
}
}
#endif
finally:
#if WITH_NAND_NVRAM
// If nvram feature is needed but unable to find valid nand
// nvram partition entry for any reason, create a mock
// ram-based nvram device so that the system won't hang when
// attempting to read the environment settings
// (<rdar://problem/6675471>).
if (!cxt->have_nvram_part) {
if (!flash_nand_nvram_init(cxt->npi, kNandPartEntryMaxCount, true)) {
spew("unable to create temporary nvram device");
ok = false;
}
}
#endif
info("nand_boot_init() %s", ok ? "succeeded" : "failed");
finish:
PROFILE_EXIT('NBI');
return(ok ? 0 : -1);
}
/* ========================================================================== */
static bool
_create_partition_device(void * context, NandPartEntry * entry, uint32_t idx)
{
bool ok = true;
nand_boot_context_t * cxt = withContext(context);
if (kNandPartEntryMaxCount != idx) {
switch (entry->content) {
case kNandPartContentSyscfg:
cxt->syscfg_needs_init = true;
break;
case kNandPartContentNVRAM:
cxt->have_nvram_part = true;
break;
default:
// nothing to do for most cases
break;
}
}
return ok;
}
static bool
_publish_partition_device(void * context, uint32_t idx)
{
bool ok = true;
nand_boot_context_t * cxt = withContext(context);
const NandPartEntry * entry = NULL;
if (kNandPartEntryMaxCount == idx) {
debug("nand has not yet been formatted\n");
return ok;
}
entry = npi_get_entry(cxt->npi, idx);
switch (entry->content) {
case kNandPartContentBootBlock:
#if ALLOW_NAND_FIRMWARE_ERASE
// The only reason to publish boot block device is to allow
// the erasure of LLB in triage/development use cases.
if (!nand_firmware_init(cxt->npi, idx)) {
spew("unable to create nand LLB device\n");
ok = false;
}
#endif
break;
case kNandPartContentNVRAM:
#if WITH_NAND_NVRAM
if (!flash_nand_nvram_init(cxt->npi, idx, false)) {
spew("unable to create nand nvram device\n");
ok = false;
}
#endif
break;
case kNandPartContentFirmware:
#if WITH_NAND_FIRMWARE
if (!nand_firmware_init(cxt->npi, idx)) {
spew("unable to create nand firmware device\n");
ok = false;
}
#endif
break;
case kNandPartContentFilesystem:
cxt->found_fsys_offset = true;
cxt->fsys_block_offset = entry->slice.offset;
#if WITH_NAND_SYSCFG
nand_fsys_block_offset = entry->slice.offset;
#elif WITH_NAND_FILESYS
// XXX never reachable
nand_fsys_block_offset = entry->slice.offset;
#endif
break;
case kNandPartContentEffaceable:
#if WITH_EFFACEABLE_NAND
effaceable_nand_init(cxt->npi, idx);
#endif
break;
default:
// nothing to do...
ok = true;
}
return ok;
}
/* ========================================================================== */
static void *
_alloc_mem(void * context, unsigned size, bool align)
{
void * buf = NULL;
if (align) {
buf = memalign(size, CPU_CACHELINE_SIZE);
} else {
buf = malloc(size);
}
return(buf);
}
static int
_compare_mem(void * context, const void * left, const void * right, unsigned int size)
{
return(memcmp(left, right, size));
}
static void *
_move_mem(void * context, void * dest, const void * src, unsigned size)
{
return(memmove(dest, src, size));
}
static void *
_set_mem(void * context, void * mem, uint8_t val, unsigned size)
{
return(memset(mem, val, size));
}
static void
_free_mem(void * context, void * mem, unsigned size)
{
free(mem);
}
static void
_lock_interface(void * context)
{
// XXX - Given its cooperative multitasking nature, does iBoot
// even need to bother with locking the nand_part_core
// interface?
}
static void
_unlock_interface(void * context)
{
// XXX - Given its cooperative multitasking nature, does iBoot
// even need to bother with locking the nand_part_core
// interface?
}
static void
_read_random(void * context, void * buf, unsigned size)
{
static bool is_seeded = false;
const unsigned increment = sizeof(int);
uint8_t * cursor = (uint8_t *)buf;
unsigned idx;
// if we're using a strong seed, draw from the entropy pool to
// re-seed psuedo-random generator the first time function is called
if (!is_seeded && USING_STRONG_SEED) {
unsigned int seed;
random_get_bytes((u_int8_t*)&seed, sizeof(seed));
srand(seed);
is_seeded = true;
}
// fill with psuedo-random values by 'int'-sized chunks as long as possible
for (idx = 0; (idx + increment) <= size; idx += increment, cursor += increment) {
*((int*)cursor) = rand();
}
// if size doesn't divide evenly by increment, fill fringe bytewise
for (; idx < size; idx++, cursor++) {
*cursor = (uint8_t)rand();
}
}
static bool
_set_data_property(void * context, const char * key, void * buf, unsigned size)
{
spew("%s=[%02x %02x %02x %02x ...]",
key, ((uint8_t*)buf)[0], ((uint8_t*)buf)[1], ((uint8_t*)buf)[2], ((uint8_t*)buf)[3]);
return true;
}
static bool
_set_number_property(void * context, const char * key, uint64_t num, unsigned bits)
{
spew("%s=%llu", key, num);
return true;
}
static bool
_set_string_property(void * context, const char * key, const char * cstring)
{
spew("%s=%s", key, cstring);
return true;
}
static bool
_set_boolean_property(void * context, const char * key, bool val)
{
spew("%s=%s", key, val ? "true" : "false");
return true;
}
static bool
_wait_for_fbbt_service(void * context)
{
// This callback is not relevant in iBoot context.
return true;
}
static void
_vlogf_message(void * context, NandPartLogLevel lvl, const char * fmt, va_list ap)
{
_vlogf_impl(lvl, fmt, ap);
}
static void
_vlogf_impl(NandPartLogLevel lvl, const char * fmt, va_list ap)
{
#if DEBUG_BUILD
const NandPartLogLevel max_level = kNandPartLogLevelDebug;
#elif DEVELOPMENT_BUILD
const NandPartLogLevel max_level = kNandPartLogLevelInfo;
#else
const NandPartLogLevel max_level = kNandPartLogLevelError;
#endif
// XXX - Implement circular log buffer to help triage failures
// before debug uart is turned on in development and debug
// builds.
if (max_level >= lvl) {
if ((kNandPartLogLevelError == lvl) || (kNandPartLogLevelBug == lvl)) {
_conditionally_force_enable_uarts();
}
vprintf(fmt, ap);
}
}
static void
_logf_impl(NandPartLogLevel lvl, const char * fmt, ...)
{
va_list ap;
va_start(ap, fmt);
_vlogf_impl(lvl, fmt, ap);
va_end(ap);
}
/* ========================================================================== */
static void
init_nand_part_callbacks(NandPartProvider *npp)
{
npp->lock_interface = _lock_interface;
npp->unlock_interface = _unlock_interface;
npp->alloc_mem = _alloc_mem;
npp->compare_mem = _compare_mem;
npp->move_mem = _move_mem;
npp->set_mem = _set_mem;
npp->free_mem = _free_mem;
npp->read_random = _read_random;
npp->set_data_property = _set_data_property;
npp->set_number_property = _set_number_property;
npp->set_string_property = _set_string_property;
npp->set_boolean_property = _set_boolean_property;
npp->wait_for_fbbt_service = _wait_for_fbbt_service;
npp->create_partition_device = _create_partition_device;
npp->publish_partition_device = _publish_partition_device;
npp->vlogf_message = _vlogf_message;
// The following callbacks are initialized by nand_export_init():
//
// npp->read_boot_page
// npp->read_full_page
// npp->write_boot_page
// npp->write_full_page
// npp->erase_block
// npp->is_block_factory_bad
// npp->validate_spare_list
// npp->request_spare_blocks
}
static void
init_nand_part_params(NandPartProvider *npp)
{
// The only destructive operation allowed on boot block is
// erase of boot partition (i.e. LLB portion but not partition
// table copies), available only in debug builds.
#if ALLOW_NAND_FIRMWARE_ERASE
npp->params.isBootPartErasable = true;
#else
npp->params.isBootPartErasable = false;
#endif
npp->params.isBootPartWritable = false;
npp->params.isFormatEnabled = false;
// Enable debug-only features only in debug builds of iBoot.
#if DEBUG_BUILD
npp->params.isDebugEnabled = true;
#else
npp->params.isDebugEnabled = false;
#endif
// In iBoot, only enable tracing for local developer builds.
npp->params.isHostTraceEnabled = false;
npp->params.isClientTraceEnabled = false;
}
/* ========================================================================== */
static bool
init_nand_boot_context(nand_boot_context_t * cxt)
{
memset(cxt, 0, sizeof(nand_boot_context_t));
cxt->npp.context = cxt;
init_nand_part_params(&cxt->npp);
init_nand_part_callbacks(&cxt->npp);
return nand_export_init(cxt);
}
/* ========================================================================== */
static int do_nand_failval_set(int argc, struct cmd_arg *args)
{
nand_failval_set(args[1].n, args[2].n);
return 0;
}
MENU_COMMAND_DEBUG(nand_failval_set, do_nand_failval_set, "Set the number of operations before failure is injected", NULL);
// =============================================================================
static int do_nand_part_dump(int argc, struct cmd_arg *args)
{
nand_part_dump(_context.npi);
return 0;
}
MENU_COMMAND_DEBUG(nand_part_dump, do_nand_part_dump, "dump nand partition table info", NULL);
// =============================================================================