/*
* Copyright (c) 2014-2015, Linaro Ltd and Contributors. All rights reserved.
* Copyright (c) 2014-2015, Hisilicon Ltd and Contributors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of ARM nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include <dw_mmc.h>
#include <fastboot.h>
#include <io_block.h>
#include <io_driver.h>
#include <io_fip.h>
#include <io_memmap.h>
#include <io_storage.h>
#include <mmio.h>
#include <partitions.h>
#include <platform_def.h>
#include <semihosting.h> /* For FOPEN_MODE_... */
#include <string.h>
#include "hikey_private.h"
#define LOADER_MAX_ENTRIES 2
#define PTABLE_MAX_ENTRIES 3
#define USER_MAX_ENTRIES 2
#define FLUSH_BASE (DDR_BASE + 0x100000)
struct entry_head {
unsigned char magic[8];
unsigned char name[8];
unsigned int start; /* lba */
unsigned int count; /* lba */
unsigned int flag;
};
static const io_dev_connector_t *bl1_mem_dev_con;
static uintptr_t bl1_mem_dev_spec;
static uintptr_t loader_mem_dev_handle;
static uintptr_t bl1_mem_init_params;
static const io_dev_connector_t *fip_dev_con;
static uintptr_t fip_dev_spec;
static uintptr_t fip_dev_handle;
static const io_dev_connector_t *dw_mmc_dev_con;
static struct block_ops dw_mmc_ops;
static uintptr_t emmc_dev_handle;
#define SPARSE_FILL_BUFFER_ADDRESS 0x18000000
#define SPARSE_FILL_BUFFER_SIZE 0x08000000
/* Page 1024, since only a few pages before 2048 are used as partition table */
#define SERIALNO_OFFSET (1024 * 512)
static const io_block_spec_t loader_mem_spec = {
/* l-loader.bin that contains bl1.bin */
.offset = LOADER_RAM_BASE,
.length = BL1_RO_LIMIT - LOADER_RAM_BASE,
};
static const io_block_spec_t boot_emmc_spec = {
.offset = MMC_LOADER_BASE,
.length = BL1_RO_LIMIT - LOADER_RAM_BASE,
};
static const io_block_spec_t normal_emmc_spec = {
.offset = MMC_BASE,
.length = MMC_SIZE,
};
static io_block_spec_t fip_block_spec = {
.offset = 0,
.length = 0,
};
static const io_file_spec_t bl2_file_spec = {
.path = BL2_IMAGE_NAME,
.mode = FOPEN_MODE_RB
};
static const io_file_spec_t bl30_file_spec = {
.path = BL30_IMAGE_NAME,
.mode = FOPEN_MODE_RB
};
static const io_file_spec_t bl31_file_spec = {
.path = BL31_IMAGE_NAME,
.mode = FOPEN_MODE_RB
};
static const io_file_spec_t bl32_file_spec = {
.path = BL32_IMAGE_NAME,
.mode = FOPEN_MODE_RB
};
static const io_file_spec_t bl33_file_spec = {
.path = BL33_IMAGE_NAME,
.mode = FOPEN_MODE_RB
};
static int open_loader_mem(const uintptr_t spec);
static int open_fip(const uintptr_t spec);
static int open_dw_mmc(const uintptr_t spec);
static int open_dw_mmc_boot(const uintptr_t spec);
struct plat_io_policy {
const char *image_name;
uintptr_t *dev_handle;
uintptr_t image_spec;
int (*check)(const uintptr_t spec);
};
static const struct plat_io_policy policies[] = {
{
LOADER_MEM_NAME,
&loader_mem_dev_handle,
(uintptr_t)&loader_mem_spec,
open_loader_mem
}, {
BOOT_EMMC_NAME,
&emmc_dev_handle,
(uintptr_t)&boot_emmc_spec,
open_dw_mmc_boot
}, {
NORMAL_EMMC_NAME,
&emmc_dev_handle,
(uintptr_t)&normal_emmc_spec,
open_dw_mmc
}, {
FIP_IMAGE_NAME,
&emmc_dev_handle,
(uintptr_t)&fip_block_spec,
open_dw_mmc
}, {
BL2_IMAGE_NAME,
&fip_dev_handle,
(uintptr_t)&bl2_file_spec,
open_fip
}, {
BL30_IMAGE_NAME,
&fip_dev_handle,
(uintptr_t)&bl30_file_spec,
open_fip
}, {
BL31_IMAGE_NAME,
&fip_dev_handle,
(uintptr_t)&bl31_file_spec,
open_fip
}, {
BL32_IMAGE_NAME,
&fip_dev_handle,
(uintptr_t)&bl32_file_spec,
open_fip
}, {
BL33_IMAGE_NAME,
&fip_dev_handle,
(uintptr_t)&bl33_file_spec,
open_fip
}, {
0, 0, 0, 0
}
};
static int open_loader_mem(const uintptr_t spec)
{
int result = IO_FAIL;
uintptr_t image_handle;
result = io_dev_init(loader_mem_dev_handle, bl1_mem_init_params);
if (result == IO_SUCCESS) {
result = io_open(loader_mem_dev_handle, spec, &image_handle);
if (result == IO_SUCCESS) {
io_close(image_handle);
}
}
return result;
}
static int open_fip(const uintptr_t spec)
{
int result = IO_FAIL;
/* See if a Firmware Image Package is available */
result = io_dev_init(fip_dev_handle, (uintptr_t)FIP_IMAGE_NAME);
if (result == IO_SUCCESS) {
INFO("Using FIP\n");
/*TODO: Check image defined in spec is present in FIP. */
}
return result;
}
static int open_dw_mmc(const uintptr_t spec)
{
int result = IO_FAIL;
uintptr_t image_handle;
/* indicate to select normal partition in eMMC */
result = io_dev_init(emmc_dev_handle, 0);
if (result == IO_SUCCESS) {
result = io_open(emmc_dev_handle, spec, &image_handle);
if (result == IO_SUCCESS) {
/* INFO("Using DW MMC IO\n"); */
io_close(image_handle);
}
}
return result;
}
static int open_dw_mmc_boot(const uintptr_t spec)
{
int result = IO_FAIL;
uintptr_t image_handle;
/* indicate to select boot partition in eMMC */
result = io_dev_init(emmc_dev_handle, 1);
if (result == IO_SUCCESS) {
result = io_open(emmc_dev_handle, spec, &image_handle);
if (result == IO_SUCCESS) {
/* INFO("Using DW MMC IO\n"); */
io_close(image_handle);
}
}
return result;
}
void io_setup(void)
{
int io_result = IO_FAIL;
/* Register the IO devices on this platform */
io_result = register_io_dev_fip(&fip_dev_con);
assert(io_result == IO_SUCCESS);
io_result = register_io_dev_block(&dw_mmc_dev_con);
assert(io_result == IO_SUCCESS);
io_result = register_io_dev_memmap(&bl1_mem_dev_con);
assert(io_result == IO_SUCCESS);
/* Open connections to devices and cache the handles */
io_result = io_dev_open(fip_dev_con, fip_dev_spec, &fip_dev_handle);
assert(io_result == IO_SUCCESS);
dw_mmc_ops.init = init_mmc;
dw_mmc_ops.read = mmc0_read;
dw_mmc_ops.write = mmc0_write;
io_result = io_dev_open(dw_mmc_dev_con, (uintptr_t)&dw_mmc_ops,
&emmc_dev_handle);
assert(io_result == IO_SUCCESS);
io_result = io_dev_open(bl1_mem_dev_con, bl1_mem_dev_spec,
&loader_mem_dev_handle);
assert(io_result == IO_SUCCESS);
/* Ignore improbable errors in release builds */
(void)io_result;
}
/* Return an IO device handle and specification which can be used to access
* an image. Use this to enforce platform load policy */
int plat_get_image_source(const char *image_name, uintptr_t *dev_handle,
uintptr_t *image_spec)
{
int result = IO_FAIL;
const struct plat_io_policy *policy;
if ((image_name != NULL) && (dev_handle != NULL) &&
(image_spec != NULL)) {
policy = policies;
while (policy->image_name != NULL) {
if (strcmp(policy->image_name, image_name) == 0) {
result = policy->check(policy->image_spec);
if (result == IO_SUCCESS) {
*image_spec = policy->image_spec;
*dev_handle = *(policy->dev_handle);
break;
}
}
policy++;
}
} else {
result = IO_FAIL;
}
return result;
}
int update_fip_spec(void)
{
struct ptentry *ptn;
ptn = find_ptn("fastboot");
if (!ptn) {
WARN("failed to find partition fastboot\n");
ptn = find_ptn("bios");
if (!ptn) {
WARN("failed to find partition bios\n");
return IO_FAIL;
}
}
VERBOSE("%s: name:%s, start:%llx, length:%llx\n",
__func__, ptn->name, ptn->start, ptn->length);
fip_block_spec.offset = ptn->start;
fip_block_spec.length = ptn->length;
return IO_SUCCESS;
}
static int fetch_entry_head(void *buf, int num, struct entry_head *hd)
{
unsigned char magic[8] = "ENTRYHDR";
if (hd == NULL)
return IO_FAIL;
memcpy((void *)hd, buf, sizeof(struct entry_head) * num);
if (!strncmp((void *)hd->magic, (void *)magic, 8))
return IO_SUCCESS;
return IO_NOT_SUPPORTED;
}
static int flush_loader(void)
{
struct entry_head entries[5];
uintptr_t img_handle, spec;
int result = IO_FAIL;
size_t bytes_read, length;
ssize_t offset;
int i, fp;
result = fetch_entry_head((void *)(FLUSH_BASE + 28),
LOADER_MAX_ENTRIES, entries);
if (result) {
WARN("failed to parse entries in loader image\n");
return result;
}
spec = 0;
for (i = 0, fp = 0; i < LOADER_MAX_ENTRIES; i++) {
if (entries[i].flag != 1) {
WARN("Invalid flag in entry:0x%x\n", entries[i].flag);
return IO_NOT_SUPPORTED;
}
result = plat_get_image_source(BOOT_EMMC_NAME, &emmc_dev_handle,
&spec);
if (result) {
WARN("failed to open emmc boot area\n");
return result;
}
/* offset in Boot Area1 */
offset = MMC_LOADER_BASE + entries[i].start * 512;
result = io_open(emmc_dev_handle, spec, &img_handle);
if (result != IO_SUCCESS) {
WARN("Failed to open memmap device\n");
return result;
}
length = entries[i].count * 512;
result = io_seek(img_handle, IO_SEEK_SET, offset);
if (result)
goto exit;
if (i == 1)
fp = (entries[1].start - entries[0].start) * 512;
result = io_write(img_handle, FLUSH_BASE + fp, length,
&bytes_read);
if ((result != IO_SUCCESS) || (bytes_read < length)) {
WARN("Failed to write '%s' file (%i)\n",
LOADER_MEM_NAME, result);
goto exit;
}
io_close(img_handle);
}
return result;
exit:
io_close(img_handle);
return result;
}
/*
* Flush l-loader.bin (loader & bl1.bin) into Boot Area1 of eMMC.
*/
int flush_loader_image(void)
{
uintptr_t bl1_image_spec;
int result = IO_FAIL;
size_t bytes_read, length;
uintptr_t img_handle;
result = plat_get_image_source(LOADER_MEM_NAME, &loader_mem_dev_handle,
&bl1_image_spec);
result = io_open(loader_mem_dev_handle, bl1_image_spec, &img_handle);
if (result != IO_SUCCESS) {
WARN("Failed to open memmap device\n");
goto exit;
}
length = loader_mem_spec.length;
result = io_read(img_handle, FLUSH_BASE, length, &bytes_read);
if ((result != IO_SUCCESS) || (bytes_read < length)) {
WARN("Failed to load '%s' file (%i)\n", LOADER_MEM_NAME, result);
goto exit;
}
io_close(img_handle);
result = flush_loader();
if (result != IO_SUCCESS) {
io_dev_close(loader_mem_dev_handle);
return result;
}
exit:
io_close(img_handle);
io_dev_close(loader_mem_dev_handle);
return result;
}
static int flush_single_image(const char *mmc_name, unsigned long img_addr,
ssize_t offset, size_t length)
{
uintptr_t img_handle, spec = 0;
size_t bytes_read;
int result = IO_FAIL;
result = plat_get_image_source(mmc_name, &emmc_dev_handle,
&spec);
if (result) {
NOTICE("failed to open emmc user data area\n");
return result;
}
result = io_open(emmc_dev_handle, spec, &img_handle);
if (result != IO_SUCCESS) {
NOTICE("Failed to open memmap device\n");
return result;
}
result = io_seek(img_handle, IO_SEEK_SET, offset);
if (result) {
NOTICE("Failed to seek at offset:0x%x\n", offset);
goto exit;
}
result = io_write(img_handle, img_addr, length,
&bytes_read);
if ((result != IO_SUCCESS) || (bytes_read < length)) {
NOTICE("Failed to write file (%i)\n", result);
goto exit;
}
exit:
io_close(img_handle);
return result;
}
static int is_sparse_image(unsigned long img_addr)
{
if (*(uint32_t *)img_addr == SPARSE_HEADER_MAGIC)
return 1;
return 0;
}
static int do_unsparse(char *cmdbuf, unsigned long img_addr, unsigned long img_length)
{
sparse_header_t *header = (sparse_header_t *)img_addr;
chunk_header_t *chunk = NULL;
struct ptentry *ptn;
void *data = (void *)img_addr;
uint64_t out_blks = 0, out_length = 0;
uint64_t length;
uint32_t fill_value;
uint64_t left, count;
int i, result;
ptn = find_ptn(cmdbuf);
if (!ptn) {
NOTICE("failed to find partition %s\n", cmdbuf);
return IO_FAIL;
}
length = (uint64_t)(header->total_blks) * (uint64_t)(header->blk_sz);
if (length > ptn->length) {
NOTICE("Unsparsed image length is %lld, pentry length is %lld.\n",
length, ptn->length);
return IO_FAIL;
}
data = (void *)((unsigned long)data + header->file_hdr_sz);
for (i = 0; i < header->total_chunks; i++) {
chunk = (chunk_header_t *)data;
data = (void *)((unsigned long)data + sizeof(chunk_header_t));
length = (uint64_t)chunk->chunk_sz * (uint64_t)header->blk_sz;
switch (chunk->chunk_type) {
case CHUNK_TYPE_RAW:
result = flush_single_image(NORMAL_EMMC_NAME,
(unsigned long)data,
ptn->start + out_length, length);
if (result < 0) {
NOTICE("sparse: failed to flush raw chunk\n");
return result;
}
out_blks += length / 512;
out_length += length;
/* next chunk is just after the raw data */
data = (void *)((unsigned long)data + length);
break;
case CHUNK_TYPE_FILL:
if (chunk->total_sz != (sizeof(unsigned int) + sizeof(chunk_header_t))) {
NOTICE("sparse: bad chunk size\n");
return IO_FAIL;
}
fill_value = *(unsigned int *)data;
if (fill_value != 0) {
NOTICE("sparse: filled value shouldn't be zero.\n");
}
memset((void *)SPARSE_FILL_BUFFER_ADDRESS,
0, SPARSE_FILL_BUFFER_SIZE);
left = length;
while (left > 0) {
if (left < SPARSE_FILL_BUFFER_SIZE)
count = left;
else
count = SPARSE_FILL_BUFFER_SIZE;
result = flush_single_image(NORMAL_EMMC_NAME,
SPARSE_FILL_BUFFER_ADDRESS,
ptn->start + out_length, count);
if (result < 0) {
WARN("sparse: failed to flush fill chunk\n");
return result;
}
out_blks += count / 512;
out_length += count;
left = left - count;
}
/* next chunk is just after the filled data */
data = (void *)((unsigned long)data + sizeof(unsigned int));
break;
case CHUNK_TYPE_DONT_CARE:
if (chunk->total_sz != sizeof(chunk_header_t)) {
NOTICE("sparse: unmatched chunk size\n");
return IO_FAIL;
}
out_blks += length / 512;
out_length += length;
break;
default:
NOTICE("sparse: unrecognized type 0x%x\n", chunk->chunk_type);
break;
}
}
return 0;
}
/* Page 1024 is used to store serial number */
int flush_random_serialno(unsigned long addr, unsigned long length)
{
int result;
memset((void *)SPARSE_FILL_BUFFER_ADDRESS, 0, 512);
memcpy((void *)SPARSE_FILL_BUFFER_ADDRESS, (void *)addr, length);
result = flush_single_image(NORMAL_EMMC_NAME, SPARSE_FILL_BUFFER_ADDRESS,
SERIALNO_OFFSET, 512);
return result;
}
char *load_serialno(void)
{
uintptr_t img_handle, spec = 0;
size_t bytes_read;
struct random_serial_num *random = NULL;
int result;
result = plat_get_image_source(NORMAL_EMMC_NAME, &emmc_dev_handle,
&spec);
if (result) {
NOTICE("failed to open emmc user data area\n");
return NULL;
}
result = io_open(emmc_dev_handle, spec, &img_handle);
if (result != IO_SUCCESS) {
NOTICE("Failed to open memmap device\n");
return NULL;
}
result = io_seek(img_handle, IO_SEEK_SET, SERIALNO_OFFSET);
if (result) {
NOTICE("Failed to seek at offset 0\n");
goto exit;
}
result = io_read(img_handle, SPARSE_FILL_BUFFER_ADDRESS, 512, &bytes_read);
if ((result != IO_SUCCESS) || (bytes_read < 512)) {
NOTICE("Failed to load '%s' file (%i)\n", LOADER_MEM_NAME, result);
goto exit;
}
io_close(img_handle);
random = (struct random_serial_num *)SPARSE_FILL_BUFFER_ADDRESS;
if (random->magic != RANDOM_MAGIC)
return NULL;
return random->serialno;
exit:
io_close(img_handle);
return NULL;
}
/*
* Flush bios.bin into User Data Area in eMMC
*/
int flush_user_images(char *cmdbuf, unsigned long img_addr,
unsigned long img_length)
{
struct entry_head entries[5];
struct ptentry *ptn;
size_t length;
ssize_t offset;
int result = IO_FAIL;
int i, fp;
result = fetch_entry_head((void *)img_addr, USER_MAX_ENTRIES, entries);
switch (result) {
case IO_NOT_SUPPORTED:
if (!strncmp(cmdbuf, "fastboot", 8) ||
!strncmp(cmdbuf, "bios", 4)) {
update_fip_spec();
}
if (is_sparse_image(img_addr)) {
result = do_unsparse(cmdbuf, img_addr, img_length);
} else {
ptn = find_ptn(cmdbuf);
if (!ptn) {
WARN("failed to find partition %s\n", cmdbuf);
return IO_FAIL;
}
img_length = (img_length + 512 - 1) / 512 * 512;
result = flush_single_image(NORMAL_EMMC_NAME, img_addr,
ptn->start, img_length);
}
break;
case IO_SUCCESS:
if (strncmp(cmdbuf, "ptable", 6)) {
WARN("it's not for ptable\n");
return IO_FAIL;
}
/* currently it's for partition table */
/* the first block is for entry headers */
fp = 512;
for (i = 0; i < USER_MAX_ENTRIES; i++) {
if (entries[i].flag != 0) {
WARN("Invalid flag in entry:0x%x\n",
entries[i].flag);
return IO_NOT_SUPPORTED;
}
if (entries[i].count == 0)
continue;
length = entries[i].count * 512;
offset = MMC_BASE + entries[i].start * 512;
VERBOSE("i:%d, start:%x, count:%x\n",
i, entries[i].start, entries[i].count);
result = flush_single_image(NORMAL_EMMC_NAME,
img_addr + fp, offset, length);
fp += entries[i].count * 512;
}
get_partition();
break;
case IO_FAIL:
WARN("failed to parse entries in user image.\n");
return result;
}
return result;
}