blob: 13fe3aa18b33d5e825ddf2e6ff274bd2f7df8073 [file] [log] [blame]
/*
* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*/
/* Implementation of firmware storage access interface for MMC */
#include <common.h>
#include <errno.h>
#include <fdtdec.h>
#include <libfdt.h>
#include <malloc.h>
#include <mmc.h>
#include <cros/common.h>
#include <cros/cros_fdtdec.h>
#include <cros/firmware_storage.h>
DECLARE_GLOBAL_DATA_PTR;
struct vboot_mmc {
struct mmc *mmc;
int device;
u32 ro_section_size; /* Offsets bigger are on partition 2 */
};
/*
* Determine which boot partition the access falls within.
*
* Return partition number, or -EINVAL if discontinuous
*/
static int check_partition(struct vboot_mmc *vboot_mmc, uint32_t offset,
uint32_t count)
{
int partition = 1;
/*
* Check continuity
* If it starts in partition 1, and ends in partition 2, things
* will not go well.
*/
if (offset < vboot_mmc->ro_section_size &&
offset + count >= vboot_mmc->ro_section_size) {
VBDEBUG("Boot partition access not contiguous\n");
return -EINVAL;
}
/* Offsets not in the RO section must be in partition 2 */
if (offset >= vboot_mmc->ro_section_size)
partition = 2;
return partition;
}
static int read_mmc(firmware_storage_t *file, uint32_t offset, uint32_t count,
void *buf)
{
struct vboot_mmc *vboot_mmc = file->context;
int partition, err;
u8 *tmp_buf;
int n, ret = -1;
int start_block, start_block_offset, end_block, total_blocks;
VBDEBUG("offset=%#x, count=%#x\n", offset, count);
partition = check_partition(vboot_mmc, offset, count);
if (partition < 0)
return -1;
if (partition == 2) {
VBDEBUG("Reading from partition 2\n");
offset -= vboot_mmc->ro_section_size;
}
start_block = offset / MMC_MAX_BLOCK_LEN;
start_block_offset = offset % MMC_MAX_BLOCK_LEN;
end_block = (offset + count) / MMC_MAX_BLOCK_LEN;
/* Read start to end, inclusive */
total_blocks = end_block - start_block + 1;
VBDEBUG("Reading %d blocks\n", total_blocks);
tmp_buf = malloc(MMC_MAX_BLOCK_LEN*total_blocks);
if (!tmp_buf) {
VBDEBUG("Failed to allocate buffer\n");
goto out;
}
/* Open partition */
err = mmc_part_access(vboot_mmc->mmc, partition);
if (err) {
VBDEBUG("Failed to open boot partition %d\n", partition);
goto out_free;
}
/* Read data */
n = vboot_mmc->mmc->block_dev.block_read(vboot_mmc->device,
start_block, total_blocks,
tmp_buf);
if (n != total_blocks) {
VBDEBUG("Failed to read blocks\n");
goto out_close;
}
/* Copy to output buffer */
memcpy(buf, tmp_buf + start_block_offset, count);
ret = 0;
out_close:
/* Close partition */
err = mmc_part_access(vboot_mmc->mmc, 0);
if (err) {
VBDEBUG("Failed to close boot partition\n");
ret = -1;
}
out_free:
free(tmp_buf);
out:
return ret;
}
/*
* Does not support unaligned writes.
* Offset and count must be offset aligned.
*/
static int write_mmc(firmware_storage_t *file, uint32_t offset, uint32_t count,
void *buf)
{
struct vboot_mmc *vboot_mmc = file->context;
uint32_t num, start_block, total_blocks;
int partition, err, ret = 0;
/* Writes not aligned to block size are unsupported. */
if (offset % MMC_MAX_BLOCK_LEN) {
VBDEBUG("Offset of %d bytes not aligned to 512 byte boundary\n",
offset);
return -1;
}
if (count % MMC_MAX_BLOCK_LEN) {
VBDEBUG("Count of %d bytes not aligned to 512 byte boundary\n",
count);
return -1;
}
/* Determine partition */
partition = check_partition(vboot_mmc, offset, count);
if (partition < 0)
return -1;
if (partition == 2) {
VBDEBUG("Writing to partition 2\n");
offset -= vboot_mmc->ro_section_size;
}
start_block = offset / MMC_MAX_BLOCK_LEN;
total_blocks = count / MMC_MAX_BLOCK_LEN;
/* Open partition */
err = mmc_part_access(vboot_mmc->mmc, partition);
if (err) {
VBDEBUG("Failed to open boot partition %d\n", partition);
return -1;
}
/* Write data */
num = vboot_mmc->mmc->block_dev.block_write(vboot_mmc->device,
start_block, total_blocks,
buf);
if (num != total_blocks) {
VBDEBUG("Failed to write blocks\n");
ret = -1;
goto out;
}
out:
/* Close partition */
err = mmc_part_access(vboot_mmc->mmc, 0);
if (err) {
VBDEBUG("Failed to close boot partition\n");
return -1;
}
return ret;
}
static int close_mmc(firmware_storage_t *file)
{
free(file->context);
return 0;
}
static int sw_wp_enabled_mmc(firmware_storage_t *file)
{
struct vboot_mmc *vboot_mmc = file->context;
return mmc_get_boot_wp(vboot_mmc->mmc);
}
int firmware_storage_open(firmware_storage_t *file)
{
const void *blob = gd->fdt_blob;
struct mmc *mmc;
int node, parent, err;
struct fmap_entry entry;
struct vboot_mmc *vboot_mmc;
node = cros_fdtdec_config_node(blob);
if (node < 0)
return -1;
node = fdtdec_lookup_phandle(blob, node, "firmware-storage");
if (node < 0) {
VBDEBUG("fail to look up phandle: %d\n", node);
return -1;
}
parent = fdt_parent_offset(blob, node);
if (parent < 0) {
VBDEBUG("fail to look up MMC parent: %d\n", parent);
return -1;
}
mmc = mmc_get_device_by_node(blob, parent);
if (!mmc) {
VBDEBUG("fail to find MMC for node %d\n", parent);
return -1;
}
err = mmc_init(mmc);
if (err) {
VBDEBUG("fail to initialize MMC: error %d\n", err);
return -1;
}
/* Lookup partition size */
node = fdt_path_offset(blob, "/flash/wp-ro");
if (node < 0) {
VBDEBUG("fail to lookup ro section\n");
return -1;
}
err = fdtdec_read_fmap_entry(blob, node, "wp-ro", &entry);
if (err) {
VBDEBUG("fail to determine ro section size\n");
return -1;
}
vboot_mmc = malloc(sizeof(*vboot_mmc));
if (!vboot_mmc) {
VBDEBUG("fail to allocate context structure\n");
return -1;
}
vboot_mmc->mmc = mmc;
vboot_mmc->device = mmc->block_dev.dev;
vboot_mmc->ro_section_size = entry.length;
file->context = vboot_mmc;
file->read = read_mmc;
file->write = write_mmc;
file->close = close_mmc;
file->sw_wp_enabled = sw_wp_enabled_mmc;
return 0;
}