blob: 746db6f8ae8cabcfedfc39f6c0d0397db8b9b78c [file] [log] [blame]
/* Copyright 2013-2014 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <skiboot.h>
#include <cpu.h>
#include <lock.h>
#include <opal.h>
#include <opal-msg.h>
#include <device.h>
#include <libflash/libflash.h>
#include <libflash/libffs.h>
#include <libflash/blocklevel.h>
#include <libflash/ecc.h>
struct flash {
bool registered;
bool busy;
struct blocklevel_device *bl;
uint32_t size;
uint32_t block_size;
};
#define MAX_FLASH 1
static struct flash flashes[MAX_FLASH];
static struct flash *system_flash;
/* Using a single lock as we only have one flash at present. */
static struct lock flash_lock;
/* nvram-on-flash support */
static struct flash *nvram_flash;
static u32 nvram_offset, nvram_size;
bool flash_reserve(void)
{
bool rc = false;
if (!try_lock(&flash_lock))
return false;
if (!system_flash->busy) {
system_flash->busy = true;
rc = true;
}
unlock(&flash_lock);
return rc;
}
void flash_release(void)
{
lock(&flash_lock);
system_flash->busy = false;
unlock(&flash_lock);
}
static int flash_nvram_info(uint32_t *total_size)
{
int rc;
lock(&flash_lock);
if (!nvram_flash) {
rc = OPAL_HARDWARE;
} else if (nvram_flash->busy) {
rc = OPAL_BUSY;
} else {
*total_size = nvram_size;
rc = OPAL_SUCCESS;
}
unlock(&flash_lock);
return rc;
}
static int flash_nvram_start_read(void *dst, uint32_t src, uint32_t len)
{
int rc;
if (!try_lock(&flash_lock))
return OPAL_BUSY;
if (!nvram_flash) {
rc = OPAL_HARDWARE;
goto out;
}
if (nvram_flash->busy) {
rc = OPAL_BUSY;
goto out;
}
if ((src + len) > nvram_size) {
prerror("FLASH_NVRAM: read out of bound (0x%x,0x%x)\n",
src, len);
rc = OPAL_PARAMETER;
goto out;
}
rc = blocklevel_read(nvram_flash->bl, nvram_offset + src, dst, len);
out:
unlock(&flash_lock);
if (!rc)
nvram_read_complete(true);
return rc;
}
static int flash_nvram_write(uint32_t dst, void *src, uint32_t len)
{
int rc;
if (!try_lock(&flash_lock))
return OPAL_BUSY;
if (nvram_flash->busy) {
rc = OPAL_BUSY;
goto out;
}
/* TODO: When we have async jobs for PRD, turn this into one */
if ((dst + len) > nvram_size) {
prerror("FLASH_NVRAM: write out of bound (0x%x,0x%x)\n",
dst, len);
rc = OPAL_PARAMETER;
goto out;
}
rc = blocklevel_write(nvram_flash->bl, nvram_offset + dst, src, len);
out:
unlock(&flash_lock);
return rc;
}
static int flash_nvram_probe(struct flash *flash, struct ffs_handle *ffs)
{
uint32_t start, size, part;
int rc;
prlog(PR_INFO, "FLASH: probing for NVRAM\n");
rc = ffs_lookup_part(ffs, "NVRAM", &part);
if (rc) {
prlog(PR_WARNING, "FLASH: no NVRAM partition found\n");
return OPAL_HARDWARE;
}
rc = ffs_part_info(ffs, part, NULL,
&start, &size, NULL, NULL);
if (rc) {
prlog(PR_ERR, "FLASH: Can't parse ffs info for NVRAM\n");
return OPAL_HARDWARE;
}
nvram_flash = flash;
nvram_offset = start;
nvram_size = size;
platform.nvram_info = flash_nvram_info;
platform.nvram_start_read = flash_nvram_start_read;
platform.nvram_write = flash_nvram_write;
return 0;
}
/* core flash support */
static struct dt_node *flash_add_dt_node(struct flash *flash, int id)
{
struct dt_node *flash_node;
flash_node = dt_new_addr(opal_node, "flash", id);
dt_add_property_strings(flash_node, "compatible", "ibm,opal-flash");
dt_add_property_cells(flash_node, "ibm,opal-id", id);
dt_add_property_cells(flash_node, "reg", 0, flash->size);
dt_add_property_cells(flash_node, "ibm,flash-block-size",
flash->block_size);
/* we fix to 32-bits */
dt_add_property_cells(flash_node, "#address-cells", 1);
dt_add_property_cells(flash_node, "#size-cells", 1);
return flash_node;
}
static void setup_system_flash(struct flash *flash, struct dt_node *node,
const char *name, struct ffs_handle *ffs)
{
char *path;
if (system_flash) {
prlog(PR_WARNING, "FLASH: attempted to register a second "
"system flash device %s\n", name);
return;
}
if (!ffs) {
prlog(PR_WARNING, "FLASH: attempted to register system flash "
"%s, wwhich has no partition info\n", name);
return;
}
system_flash = flash;
path = dt_get_path(node);
dt_add_property_string(dt_chosen, "ibm,system-flash", path);
free(path);
prlog(PR_INFO, "FLASH: registered system flash device %s\n", name);
flash_nvram_probe(flash, ffs);
}
int flash_register(struct blocklevel_device *bl, bool is_system_flash)
{
uint32_t size, block_size;
struct ffs_handle *ffs;
struct dt_node *node;
struct flash *flash = NULL;
const char *name;
unsigned int i;
int rc;
rc = blocklevel_get_info(bl, &name, &size, &block_size);
if (rc)
return rc;
prlog(PR_INFO, "FLASH: registering flash device %s "
"(size 0x%x, blocksize 0x%x)\n",
name ?: "(unnamed)", size, block_size);
lock(&flash_lock);
for (i = 0; i < ARRAY_SIZE(flashes); i++) {
if (flashes[i].registered)
continue;
flash = &flashes[i];
flash->registered = true;
flash->busy = false;
flash->bl = bl;
flash->size = size;
flash->block_size = block_size;
break;
}
if (!flash) {
unlock(&flash_lock);
prlog(PR_ERR, "FLASH: No flash slots available\n");
return OPAL_RESOURCE;
}
rc = ffs_init(0, flash->size, bl, &ffs, 0);
if (rc) {
prlog(PR_WARNING, "FLASH: No ffs info; "
"using raw device only\n");
ffs = NULL;
}
node = flash_add_dt_node(flash, i);
if (is_system_flash)
setup_system_flash(flash, node, name, ffs);
if (ffs)
ffs_close(ffs);
unlock(&flash_lock);
return OPAL_SUCCESS;
}
enum flash_op {
FLASH_OP_READ,
FLASH_OP_WRITE,
FLASH_OP_ERASE,
};
static int64_t opal_flash_op(enum flash_op op, uint64_t id, uint64_t offset,
uint64_t buf, uint64_t size, uint64_t token)
{
struct flash *flash;
int rc;
if (id >= ARRAY_SIZE(flashes))
return OPAL_PARAMETER;
if (!try_lock(&flash_lock))
return OPAL_BUSY;
flash = &flashes[id];
if (flash->busy) {
rc = OPAL_BUSY;
goto err;
}
if (!flash->registered) {
rc = OPAL_PARAMETER;
goto err;
}
if (size >= flash->size || offset >= flash->size
|| offset + size > flash->size) {
rc = OPAL_PARAMETER;
goto err;
}
switch (op) {
case FLASH_OP_READ:
rc = blocklevel_read(flash->bl, offset, (void *)buf, size);
break;
case FLASH_OP_WRITE:
/*
* Note: blocklevel_write() uses flash_smart_write(), this call used to
* be flash_write()
*/
rc = blocklevel_write(flash->bl, offset, (void *)buf, size);
break;
case FLASH_OP_ERASE:
rc = blocklevel_erase(flash->bl, offset, size);
break;
default:
assert(0);
}
if (rc) {
rc = OPAL_HARDWARE;
goto err;
}
unlock(&flash_lock);
opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, token, rc);
return OPAL_ASYNC_COMPLETION;
err:
unlock(&flash_lock);
return rc;
}
static int64_t opal_flash_read(uint64_t id, uint64_t offset, uint64_t buf,
uint64_t size, uint64_t token)
{
return opal_flash_op(FLASH_OP_READ, id, offset, buf, size, token);
}
static int64_t opal_flash_write(uint64_t id, uint64_t offset, uint64_t buf,
uint64_t size, uint64_t token)
{
return opal_flash_op(FLASH_OP_WRITE, id, offset, buf, size, token);
}
static int64_t opal_flash_erase(uint64_t id, uint64_t offset, uint64_t size,
uint64_t token)
{
return opal_flash_op(FLASH_OP_ERASE, id, offset, 0L, size, token);
}
opal_call(OPAL_FLASH_READ, opal_flash_read, 5);
opal_call(OPAL_FLASH_WRITE, opal_flash_write, 5);
opal_call(OPAL_FLASH_ERASE, opal_flash_erase, 4);
/* flash resource API */
static struct {
enum resource_id id;
uint32_t subid;
char name[PART_NAME_MAX+1];
} part_name_map[] = {
{ RESOURCE_ID_KERNEL, RESOURCE_SUBID_NONE, "BOOTKERNEL" },
{ RESOURCE_ID_INITRAMFS,RESOURCE_SUBID_NONE, "ROOTFS" },
{ RESOURCE_ID_CAPP, RESOURCE_SUBID_SUPPORTED, "CAPP" },
};
/* This mimics the hostboot SBE format */
#define FLASH_SUBPART_ALIGNMENT 0x1000
#define FLASH_SUBPART_HEADER_SIZE FLASH_SUBPART_ALIGNMENT
struct flash_hostboot_toc {
be32 ec;
be32 offset; /* From start of header. 4K aligned */
be32 size;
};
#define FLASH_HOSTBOOT_TOC_MAX_ENTRIES ((FLASH_SUBPART_HEADER_SIZE - 8) \
/sizeof(struct flash_hostboot_toc))
struct flash_hostboot_header {
char eyecatcher[4];
be32 version;
struct flash_hostboot_toc toc[FLASH_HOSTBOOT_TOC_MAX_ENTRIES];
};
/* start and total size include ECC */
static int flash_find_subpartition(struct blocklevel_device *bl, uint32_t subid,
uint32_t *start, uint32_t *total_size,
bool *ecc)
{
struct flash_hostboot_header *header;
char eyecatcher[5];
uint32_t i, partsize;
int rc;
header = malloc(FLASH_SUBPART_HEADER_SIZE);
if (!header)
return false;
/* Get raw partition size without ECC */
partsize = *total_size;
if (ecc)
partsize = ecc_buffer_size_minus_ecc(*total_size);
/* Get the TOC */
rc = flash_read_corrected(bl, *start, header,
FLASH_SUBPART_HEADER_SIZE, ecc);
if (rc) {
prerror("FLASH: flash subpartition TOC read failed %i\n", rc);
goto end;
}
/* Perform sanity */
i = be32_to_cpu(header->version);
if (i != 1) {
prerror("FLASH: flash subpartition TOC version unknown %i\n", i);
rc = OPAL_RESOURCE;
goto end;
}
/* NULL terminate eyecatcher */
strncpy(eyecatcher, header->eyecatcher, 4);
eyecatcher[4] = 0;
prlog(PR_DEBUG, "FLASH: flash subpartition eyecatcher %s\n",
eyecatcher);
rc = OPAL_RESOURCE;
for (i = 0; i< FLASH_HOSTBOOT_TOC_MAX_ENTRIES; i++) {
uint32_t ec, offset, size;
ec = be32_to_cpu(header->toc[i].ec);
offset = be32_to_cpu(header->toc[i].offset);
size = be32_to_cpu(header->toc[i].size);
/* Check for null terminating entry */
if (!ec && !offset && !size) {
prerror("FLASH: flash subpartition not found.\n");
goto end;
}
if (ec != subid)
continue;
/* Sanity check the offset and size. */
if (offset + size > partsize) {
prerror("FLASH: flash subpartition too big: %i\n", i);
goto end;
}
if (!size) {
prerror("FLASH: flash subpartition zero size: %i\n", i);
goto end;
}
if (offset < FLASH_SUBPART_HEADER_SIZE) {
prerror("FLASH: flash subpartition "
"offset too small: %i\n", i);
goto end;
}
prlog(PR_DEBUG, "FLASH: flash found subpartition: "
"%i size: %i offset %i\n",
i, size, offset);
/*
* Adjust the start and size. The start location in the needs
* to account for ecc but the size doesn't.
*/
*start += offset;
*total_size = size;
if (ecc) {
*start += ecc_size(offset);
*total_size += ecc_size(size);
}
rc = 0;
goto end;
}
end:
free(header);
return rc;
}
/*
* load a resource from FLASH
* buf and len shouldn't account for ECC even if partition is ECCed.
*/
static int flash_load_resource(enum resource_id id, uint32_t subid,
void *buf, size_t *len)
{
int i, rc, part_num, part_size, part_start, size;
struct ffs_handle *ffs;
struct flash *flash;
const char *name;
bool status, ecc;
rc = OPAL_RESOURCE;
status = false;
lock(&flash_lock);
if (!system_flash)
goto out_unlock;
flash = system_flash;
if (flash->busy)
goto out_unlock;
for (i = 0, name = NULL; i < ARRAY_SIZE(part_name_map); i++) {
if (part_name_map[i].id == id) {
name = part_name_map[i].name;
break;
}
}
if (!name) {
prerror("FLASH: Couldn't find partition for id %d\n", id);
goto out_unlock;
}
/*
* If partition doesn't have a subindex but the caller specifies one,
* we fail. eg. kernel partition doesn't have a subindex
*/
if ((part_name_map[i].subid == RESOURCE_SUBID_NONE) &&
(subid != RESOURCE_SUBID_NONE)) {
prerror("PLAT: Partition %s doesn't have subindex\n", name);
goto out_unlock;
}
rc = ffs_init(0, flash->size, flash->bl, &ffs, 0);
if (rc) {
prerror("FLASH: Can't open ffs handle\n");
goto out_unlock;
}
rc = ffs_lookup_part(ffs, name, &part_num);
if (rc) {
prerror("FLASH: No %s partition\n", name);
goto out_free_ffs;
}
rc = ffs_part_info(ffs, part_num, NULL,
&part_start, &part_size, NULL, &ecc);
if (rc) {
prerror("FLASH: Failed to get %s partition info\n", name);
goto out_free_ffs;
}
prlog(PR_DEBUG,"FLASH: %s partition %s ECC\n",
name, ecc ? "has" : "doesn't have");
/*
* part_start/size are raw pointers into the partition.
* ie. they will account for ECC if included.
*/
/* Find the sub partition if required */
if (subid != RESOURCE_SUBID_NONE) {
rc = flash_find_subpartition(flash->bl, subid, &part_start,
&part_size, &ecc);
if (rc)
goto out_free_ffs;
}
/* Work out what the final size of buffer will be without ECC */
size = part_size;
if (ecc) {
if (ecc_buffer_size_check(part_size)) {
prerror("FLASH: %s image invalid size for ECC %d\n",
name, part_size);
goto out_free_ffs;
}
size = ecc_buffer_size_minus_ecc(part_size);
}
if (size > *len) {
prerror("FLASH: %s image too large (%d > %zd)\n", name,
part_size, *len);
goto out_free_ffs;
}
rc = flash_read_corrected(flash->bl, part_start, buf, size, ecc);
if (rc) {
prerror("FLASH: failed to read %s partition\n", name);
goto out_free_ffs;
}
*len = size;
status = true;
out_free_ffs:
ffs_close(ffs);
out_unlock:
unlock(&flash_lock);
return status ? OPAL_SUCCESS : rc;
}
struct flash_load_resource_item {
enum resource_id id;
uint32_t subid;
int result;
void *buf;
size_t *len;
struct list_node link;
};
static LIST_HEAD(flash_load_resource_queue);
static LIST_HEAD(flash_loaded_resources);
static struct lock flash_load_resource_lock = LOCK_UNLOCKED;
static struct cpu_job *flash_load_job = NULL;
int flash_resource_loaded(enum resource_id id, uint32_t subid)
{
struct flash_load_resource_item *resource = NULL;
struct flash_load_resource_item *r;
int rc = OPAL_BUSY;
lock(&flash_load_resource_lock);
list_for_each(&flash_loaded_resources, r, link) {
if (r->id == id && r->subid == subid) {
resource = r;
break;
}
}
if (resource) {
rc = resource->result;
list_del(&resource->link);
free(resource);
}
if (list_empty(&flash_load_resource_queue) && flash_load_job) {
cpu_wait_job(flash_load_job, true);
flash_load_job = NULL;
}
unlock(&flash_load_resource_lock);
return rc;
}
static void flash_load_resources(void *data __unused)
{
struct flash_load_resource_item *r;
int result;
lock(&flash_load_resource_lock);
do {
if (list_empty(&flash_load_resource_queue)) {
break;
}
r = list_top(&flash_load_resource_queue,
struct flash_load_resource_item, link);
if (r->result != OPAL_EMPTY)
prerror("flash_load_resources() list_top unexpected "
" result %d\n", r->result);
r->result = OPAL_BUSY;
unlock(&flash_load_resource_lock);
result = flash_load_resource(r->id, r->subid, r->buf, r->len);
lock(&flash_load_resource_lock);
r = list_pop(&flash_load_resource_queue,
struct flash_load_resource_item, link);
r->result = result;
list_add_tail(&flash_loaded_resources, &r->link);
} while(true);
unlock(&flash_load_resource_lock);
}
static void start_flash_load_resource_job(void)
{
if (flash_load_job)
cpu_wait_job(flash_load_job, true);
flash_load_job = cpu_queue_job(NULL, "flash_load_resources",
flash_load_resources, NULL);
cpu_process_local_jobs();
}
int flash_start_preload_resource(enum resource_id id, uint32_t subid,
void *buf, size_t *len)
{
struct flash_load_resource_item *r;
bool start_thread = false;
r = malloc(sizeof(struct flash_load_resource_item));
assert(r != NULL);
r->id = id;
r->subid = subid;
r->buf = buf;
r->len = len;
r->result = OPAL_EMPTY;
printf("FLASH: Queueing preload of %x/%x\n", r->id, r->subid);
lock(&flash_load_resource_lock);
if (list_empty(&flash_load_resource_queue)) {
start_thread = true;
}
list_add_tail(&flash_load_resource_queue, &r->link);
unlock(&flash_load_resource_lock);
if (start_thread)
start_flash_load_resource_job();
return OPAL_SUCCESS;
}