blob: 913abe61ca8025a0b2893a6b3d885ce54c5c2a53 [file] [log] [blame]
/*
* Copyright (c) 2011 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.
*/
#include <common.h>
#include <malloc.h>
#include <mmc.h>
#include <chromeos/common.h>
#include <chromeos/firmware_storage.h>
#include <chromeos/os_storage.h>
#define PREFIX "firmware_storage_twostop: "
enum {
SECTION_RO = 0,
SECTION_RW_A,
SECTION_RW_B
};
struct context {
struct twostop_fmap *fmap;
int section;
firmware_storage_t spi_file;
struct mmc *mmc;
};
static int within_entry(const struct fmap_entry *e, uint32_t offset)
{
return e->offset <= offset && offset < e->offset + e->length;
}
static int get_section(const struct twostop_fmap *fmap, off_t offset)
{
if (within_entry(&fmap->readonly.readonly, offset))
return SECTION_RO;
else if (within_entry(&fmap->readwrite_a.readwrite_a, offset))
return SECTION_RW_A;
else if (within_entry(&fmap->readwrite_b.readwrite_b, offset))
return SECTION_RW_B;
else
return -1;
}
static void set_start_block_and_offset(const struct twostop_fmap *fmap,
int section, uint32_t offset,
uint32_t *start_block_ptr, uint32_t *offset_in_block_ptr)
{
*start_block_ptr = (section == SECTION_RW_A) ?
fmap->readwrite_a.block_lba :
fmap->readwrite_b.block_lba;
*offset_in_block_ptr = offset - ((section == SECTION_RW_A) ?
fmap->readwrite_a.readwrite_a.offset :
fmap->readwrite_b.readwrite_b.offset);
*start_block_ptr += (*offset_in_block_ptr >> 9);
*offset_in_block_ptr &= 0x1ff;
}
static int read_mmc(struct mmc *mmc,
uint32_t start_block, uint32_t offset_in_block,
uint32_t offset, uint32_t count, void *buf);
static int write_mmc(struct mmc *mmc,
uint32_t start_block, uint32_t offset_in_block,
uint32_t offset, uint32_t count, void *buf);
static int read_twostop(struct firmware_storage_t *file,
uint32_t offset, uint32_t count, void *buf)
{
struct context *cxt = file->context;
int section = get_section(cxt->fmap, offset);
uint32_t start_block = 0, offset_in_block = 0;
VBDEBUG(PREFIX "read(offset=%08x, count=%08x, buffer=%p)\n",
offset, count, buf);
if (section < 0)
return -1;
else if (section == SECTION_RO)
return cxt->spi_file.read(&cxt->spi_file, offset, count, buf);
else {
set_start_block_and_offset(cxt->fmap, section, offset,
&start_block, &offset_in_block);
return read_mmc(cxt->mmc, start_block, offset_in_block,
offset, count, buf);
}
}
static int write_twostop(struct firmware_storage_t *file,
uint32_t offset, uint32_t count, void *buf)
{
struct context *cxt = file->context;
int section = get_section(cxt->fmap, offset);
uint32_t start_block = 0, offset_in_block = 0;
VBDEBUG(PREFIX "write(offset=%08x, count=%08x, buffer=%p)\n",
offset, count, buf);
if (section < 0)
return -1;
else if (section == SECTION_RO)
return cxt->spi_file.write(&cxt->spi_file, offset, count, buf);
else {
set_start_block_and_offset(cxt->fmap, section, offset,
&start_block, &offset_in_block);
return write_mmc(cxt->mmc, start_block, offset_in_block,
offset, count, buf);
}
}
static int read_mmc(struct mmc *mmc,
uint32_t start_block, uint32_t offset_in_block,
uint32_t offset, uint32_t count, void *buf)
{
uint32_t n_block;
size_t n_byte;
uint8_t *residual;
VBDEBUG(PREFIX "read_mmc(start_block=%08x, offset_in_block=%08x)\n",
start_block, offset_in_block);
residual = memalign(CACHE_LINE_SIZE, 512);
n_byte = 0;
if (offset_in_block) {
n_byte = MIN(512 - offset_in_block, count);
mmc->block_dev.block_read(MMC_INTERNAL_DEVICE,
start_block, 1, residual);
memcpy(buf, residual + offset_in_block, n_byte);
}
offset_in_block += n_byte;
if (offset_in_block == 512) {
start_block++;
offset_in_block = 0;
}
while (count > n_byte && (n_block = (count - n_byte) >> 9)) {
n_block = mmc->block_dev.block_read(MMC_INTERNAL_DEVICE,
start_block, n_block, buf + n_byte);
start_block += n_block;
n_byte += (n_block << 9);
}
if (count > n_byte) {
mmc->block_dev.block_read(MMC_INTERNAL_DEVICE,
start_block, 1, residual);
memcpy(buf + n_byte, residual, count - n_byte);
offset_in_block = count - n_byte;
}
free(residual);
return 0;
}
static int write_mmc(struct mmc *mmc,
uint32_t start_block, uint32_t offset_in_block,
uint32_t offset, uint32_t count, void *buf)
{
uint32_t n_block;
size_t n_byte;
uint8_t *residual;
VBDEBUG(PREFIX "write_mmc(start_block=%08x, offset_in_block=%08x)\n",
start_block, offset_in_block);
residual = memalign(CACHE_LINE_SIZE, 512);
n_byte = 0;
if (offset_in_block) {
n_byte = MIN(512 - offset_in_block, count);
mmc->block_dev.block_read(MMC_INTERNAL_DEVICE,
start_block, 1, residual);
memcpy(residual + offset_in_block, buf, n_byte);
mmc->block_dev.block_write(MMC_INTERNAL_DEVICE,
start_block, 1, residual);
}
offset_in_block += n_byte;
if (offset_in_block == 512) {
start_block++;
offset_in_block = 0;
}
while (count > n_byte && (n_block = (count - n_byte) >> 9)) {
n_block = mmc->block_dev.block_write(MMC_INTERNAL_DEVICE,
start_block, n_block, buf + n_byte);
start_block += n_block;
n_byte += (n_block << 9);
}
if (count > n_byte) {
mmc->block_dev.block_read(MMC_INTERNAL_DEVICE,
start_block, 1, residual);
memcpy(residual, buf + n_byte, count - n_byte);
mmc->block_dev.block_write(MMC_INTERNAL_DEVICE,
start_block, 1, residual);
offset_in_block = count - n_byte;
}
free(residual);
return 0;
}
static int close_twostop(firmware_storage_t *file)
{
struct context *cxt = file->context;
int ret;
ret = cxt->spi_file.close(&cxt->spi_file);
free(cxt);
return ret;
}
int firmware_storage_open_twostop(firmware_storage_t *file,
struct twostop_fmap *fmap)
{
struct context *cxt;
cxt = malloc(sizeof(*cxt));
if (!cxt)
return -1;
cxt->fmap = fmap;
cxt->section = SECTION_RO;
if (firmware_storage_open_spi(&cxt->spi_file)) {
free(cxt);
return -1;
}
cxt->mmc = find_mmc_device(MMC_INTERNAL_DEVICE);
if (!cxt->mmc) {
free(cxt);
return -1;
}
mmc_init(cxt->mmc);
file->read = read_twostop;
file->write = write_twostop;
file->close = close_twostop;
file->context = (void *)cxt;
return 0;
}