| /* 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; |
| } |