blob: 7e0a637585ba29ff9b8b0974adbda83e58921c9a [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <libpayload.h>
#include "base/gpt.h"
#include "fastboot/backend.h"
#define BACKEND_DEBUG
#ifdef BACKEND_DEBUG
#define BE_LOG(args...) printf(args);
#else
#define BE_LOG(args...)
#endif
/* Weak variables for board-specific data */
size_t fb_bdev_count __attribute__((weak)) = 0;
struct bdev_info fb_bdev_list[] __attribute__((weak)) = {{}} ;
size_t fb_part_count __attribute__((weak)) = 0;
struct part_info fb_part_list[] __attribute__((weak)) = {{}} ;
/* Image partition details */
struct image_part_details {
struct bdev_info *bdev_entry;
struct part_info *part_entry;
GptData *gpt;
GptEntry *gpt_entry;
size_t part_addr;
size_t part_size_lba;
};
/********************** Stub implementations *****************************/
backend_ret_t __attribute__((weak)) board_write_partition(const char *name,
void *image_addr,
size_t image_size)
{
return BE_NOT_HANDLED;
}
/********************** Sparse Image Handling ****************************/
/* Sparse Image Header */
struct sparse_image_hdr {
/* Magic number for sparse image 0xed26ff3a. */
uint32_t magic;
/* Major version = 0x1 */
uint16_t major_version;
uint16_t minor_version;
uint16_t file_hdr_size;
uint16_t chunk_hdr_size;
/* Size of block in bytes. */
uint32_t blk_size;
/* # of blocks in the non-sparse image. */
uint32_t total_blks;
/* # of chunks in the sparse image. */
uint32_t total_chunks;
uint32_t image_checksum;
};
#define SPARSE_IMAGE_MAGIC 0xed26ff3a
#define CHUNK_TYPE_RAW 0xCAC1
#define CHUNK_TYPE_FILL 0xCAC2
#define CHUNK_TYPE_DONT_CARE 0xCAC3
#define CHUNK_TYPE_CRC32 0xCAC4
/* Chunk header in sparse image */
struct sparse_chunk_hdr {
uint16_t type;
uint16_t reserved;
/* Chunk size is in number of blocks */
uint32_t size_in_blks;
/* Size in bytes of chunk header and data */
uint32_t total_size_bytes;
};
/* Check if given image is sparse */
int is_sparse_image(void *image_addr)
{
struct sparse_image_hdr *hdr = image_addr;
/* AOSP sparse format supports major version 0x1 only */
return ((hdr->magic == SPARSE_IMAGE_MAGIC) &&
(hdr->major_version == 0x1));
}
/* Write sparse image to partition */
static backend_ret_t write_sparse_image(struct image_part_details *img,
void *image_addr, size_t image_size)
{
struct sparse_image_hdr *img_hdr = image_addr;
struct sparse_chunk_hdr *chunk_hdr;
size_t bdev_block_size = img->bdev_entry->bdev->block_size;
BE_LOG("Magic : %x\n", img_hdr->magic);
BE_LOG("Major Version : %x\n", img_hdr->major_version);
BE_LOG("Minor Version : %x\n", img_hdr->minor_version);
BE_LOG("File Hdr Size : %x\n", img_hdr->file_hdr_size);
BE_LOG("Chunk Hdr Size : %x\n", img_hdr->chunk_hdr_size);
BE_LOG("Blk Size : %x\n", img_hdr->blk_size);
BE_LOG("Total blks : %x\n", img_hdr->total_blks);
BE_LOG("Total chunks : %x\n", img_hdr->total_chunks);
BE_LOG("Checksum : %x\n", img_hdr->image_checksum);
/* Is image header size as expected? */
if (img_hdr->file_hdr_size != sizeof(*img_hdr))
return BE_SPARSE_HDR_ERR;
/* Is image block size multiple of bdev block size? */
if (img_hdr->blk_size != ALIGN_DOWN(img_hdr->blk_size, bdev_block_size))
return BE_IMAGE_SIZE_MULTIPLE_ERR;
/* Is chunk header size as expected? */
if (img_hdr->chunk_hdr_size != sizeof(*chunk_hdr))
return BE_CHUNK_HDR_ERR;
int i;
size_t part_addr = img->part_addr;
size_t part_size_lba = img->part_size_lba;
BlockDevOps *ops = &img->bdev_entry->bdev->ops;
/* data_ptr points to first byte after image header */
uint8_t *data_ptr = image_addr;
data_ptr += sizeof(*img_hdr);
/* Perform the following operation on each chunk */
for (i = 0; i < img_hdr->total_chunks; i++) {
/* Get chunk header */
chunk_hdr = (struct sparse_chunk_hdr *)data_ptr;
BE_LOG("Chunk %d\n", i);
BE_LOG("Type : %x\n", chunk_hdr->type);
BE_LOG("Size in blks : %x\n", chunk_hdr->size_in_blks);
BE_LOG("Total size : %x\n", chunk_hdr->total_size_bytes);
/*
* Make data_ptr point to chunk data(if any):
* Raw chunk data = chunk_size_in_blks * img_blk_size
* Fill chunk data = 4 bytes of fill data
* CRC32 chunk data = 4 bytes of CRC32
*/
data_ptr += sizeof(*chunk_hdr);
/* Size in bytes and lba of the area occupied by chunk range */
size_t chunk_size_bytes, chunk_size_lba;
chunk_size_bytes = chunk_hdr->size_in_blks * img_hdr->blk_size;
chunk_size_lba = chunk_size_bytes / bdev_block_size;
switch (chunk_hdr->type) {
case CHUNK_TYPE_RAW: {
/*
* For Raw chunk type:
* chunk_size_bytes + chunk_hdr_size = chunk_total_size
*/
if ((chunk_size_bytes + sizeof(*chunk_hdr))!=
chunk_hdr->total_size_bytes) {
BE_LOG("chunk_size_bytes:%zx\n",
chunk_size_bytes + sizeof(*chunk_hdr));
BE_LOG("total_size_bytes:%x\n",
chunk_hdr->total_size_bytes);
return BE_CHUNK_HDR_ERR;
}
/* Should not write past partition size */
if (part_size_lba < chunk_size_lba) {
BE_LOG("part_size_lba:%zx\n", part_size_lba);
BE_LOG("chunk_size_lba:%zx\n", chunk_size_lba);
return BE_IMAGE_OVERFLOW_ERR;
}
if (ops->write(ops, part_addr, chunk_size_lba, data_ptr)
!= chunk_size_lba)
return BE_WRITE_ERR;
/*
* Data present in chunk sparse image is
* chunk_size_bytes
*/
data_ptr += chunk_size_bytes;
break;
}
case CHUNK_TYPE_FILL: {
/*
* For fill chunk type:
* chunk_hdr_size + 4 bytes = chunk_total_size_bytes
*/
if (sizeof(uint32_t) + sizeof(*chunk_hdr) !=
chunk_hdr->total_size_bytes) {
BE_LOG("chunk_size_bytes:%zx\n",
sizeof(uint32_t) + sizeof(*chunk_hdr));
BE_LOG("total_size_bytes:%x\n",
chunk_hdr->total_size_bytes);
return BE_CHUNK_HDR_ERR;
}
/* Perform fill_write operation */
if (ops->fill_write(ops, part_addr, chunk_size_lba,
*(uint32_t *)data_ptr)
!= chunk_size_lba)
return BE_WRITE_ERR;
/* Data present in chunk sparse image is 4 bytes */
data_ptr += sizeof(uint32_t);
break;
}
case CHUNK_TYPE_DONT_CARE: {
/*
* For dont care chunk type:
* chunk_hdr_size = chunk_total_size_bytes
* data in sparse image = 0 bytes
*/
if (sizeof(*chunk_hdr) != chunk_hdr->total_size_bytes) {
BE_LOG("chunk_size_bytes:%zx\n",
sizeof(*chunk_hdr));
BE_LOG("total_size_bytes:%x\n",
chunk_hdr->total_size_bytes);
return BE_CHUNK_HDR_ERR;
}
break;
}
case CHUNK_TYPE_CRC32: {
/*
* For crc32 chunk type:
* chunk_hdr_size + 4 bytes = chunk_total_size_bytes
*/
if (sizeof(uint32_t) + sizeof(*chunk_hdr) !=
chunk_hdr->total_size_bytes) {
BE_LOG("chunk_size_bytes:%zx\n",
sizeof(uint32_t) + sizeof(*chunk_hdr));
BE_LOG("total_size_bytes:%x\n",
chunk_hdr->total_size_bytes);
return BE_CHUNK_HDR_ERR;
}
/* TODO(furquan): Verify CRC32 header? */
/* Data present in chunk sparse image = 4 bytes */
data_ptr += sizeof(uint32_t);
}
default: {
/* Unknown chunk type */
BE_LOG("Unknown chunk type %d\n", chunk_hdr->type);
return BE_CHUNK_HDR_ERR;
}
}
/* Update partition address and size accordingly */
part_addr += chunk_size_lba;
part_size_lba -= chunk_size_lba;
}
return BE_SUCCESS;
}
/********************** Raw Image Handling *******************************/
static backend_ret_t write_raw_image(struct image_part_details *img,
void *image_addr, size_t image_size)
{
struct bdev_info *bdev_entry = img->bdev_entry;
size_t part_addr = img->part_addr;
size_t part_size_lba = img->part_size_lba;
size_t image_size_lba;
BlockDevOps *ops = &bdev_entry->bdev->ops;
size_t block_size = bdev_entry->bdev->block_size;
/* Ensure that image size is multiple of block size */
if (image_size != ALIGN_DOWN(image_size, block_size))
return BE_IMAGE_SIZE_MULTIPLE_ERR;
image_size_lba = image_size / block_size;
/* Ensure image size is less than partition size */
if (part_size_lba < image_size_lba) {
BE_LOG("part_size_lba:%zx\n", part_size_lba);
BE_LOG("image_size_lba:%zx\n", image_size_lba);
return BE_IMAGE_OVERFLOW_ERR;
}
if (ops->write(ops, part_addr, image_size_lba, image_addr) !=
image_size_lba)
return BE_WRITE_ERR;
return BE_SUCCESS;
}
/********************** Image Partition handling ******************************/
struct part_info *get_part_info(const char *name)
{
int i;
for (i = 0; i < fb_part_count; i++) {
if (!strcmp(name, fb_part_list[i].part_name))
return &fb_part_list[i];
}
return NULL;
}
static struct bdev_info *get_bdev_info(const char *name)
{
int i;
for (i = 0; i < fb_bdev_count; i++) {
if (!strcmp(name, fb_bdev_list[i].name))
return &fb_bdev_list[i];
}
return NULL;
}
static backend_ret_t backend_fill_bdev_info(void)
{
ListNode *devs;
int count = get_all_bdevs(BLOCKDEV_FIXED, &devs);
if (count == 0)
return BE_BDEV_NOT_FOUND;
int i;
for (i = 0; i < fb_bdev_count; i++) {
BlockDev *bdev;
BlockDevCtrlr *bdev_ctrlr;
bdev_ctrlr = fb_bdev_list[i].bdev_ctrlr;
if (bdev_ctrlr == NULL)
return BE_BDEV_NOT_FOUND;
list_for_each(bdev, *devs, list_node) {
if (bdev_ctrlr->ops.is_bdev_owned(&bdev_ctrlr->ops,
bdev)) {
fb_bdev_list[i].bdev = bdev;
break;
}
}
if (fb_bdev_list[i].bdev == NULL)
return BE_BDEV_NOT_FOUND;
}
return BE_SUCCESS;
}
static backend_ret_t backend_do_init(void)
{
static int backend_data_init = 0;
if (backend_data_init)
return BE_SUCCESS;
if ((fb_bdev_count == 0) || (fb_bdev_list == NULL))
return BE_BDEV_NOT_FOUND;
if (backend_fill_bdev_info() != BE_SUCCESS)
return BE_BDEV_NOT_FOUND;
if((fb_part_count == 0) || (fb_part_list == NULL))
return BE_PART_NOT_FOUND;
backend_data_init = 1;
return BE_SUCCESS;
}
static backend_ret_t fill_img_part_info(struct image_part_details *img,
const char *name)
{
struct bdev_info *bdev_entry;
struct part_info *part_entry;
GptData *gpt = NULL;
GptEntry *gpt_entry = NULL;
size_t part_addr;
size_t part_size_lba;
/* Get partition info from board-specific partition table */
part_entry = get_part_info(name);
if (part_entry == NULL)
return BE_PART_NOT_FOUND;
/* Get bdev info from part_entry */
bdev_entry = part_entry->bdev_info;
if (bdev_entry == NULL)
return BE_BDEV_NOT_FOUND;
/*
* If partition is GPT based, we have to go through a level of
* indirection to read the GPT entries and identify partition address
* and size. If the partition is not GPT based, board provides address
* and size of the partition on block device
*/
if (part_entry->gpt_based) {
/* Allocate GPT structure used by cgptlib */
gpt = alloc_gpt(bdev_entry->bdev);
if (gpt == NULL)
goto fail;
/* Find nth entry based on GUID & instance provided by board */
gpt_entry = GptFindNthEntry(gpt, &part_entry->guid,
part_entry->instance);
if (gpt_entry == NULL)
goto fail;
/* Get partition addr and size from GPT entry */
part_addr = gpt_entry->starting_lba;
part_size_lba = GptGetEntrySizeLba(gpt_entry);
} else {
/* Take board provided partition addr and size */
part_addr = part_entry->base;
part_size_lba = part_entry->size;
}
/* Fill image partition details structure */
img->bdev_entry = bdev_entry;
img->part_entry = part_entry;
img->gpt = gpt;
img->gpt_entry = gpt_entry;
img->part_addr = part_addr;
img->part_size_lba = part_size_lba;
return BE_SUCCESS;
fail:
/* In case of failure, ensure gpt is freed */
if (gpt)
free_gpt(bdev_entry->bdev, gpt);
return BE_GPT_ERR;
}
static void clean_img_part_info(struct image_part_details *img)
{
if (img->gpt)
free_gpt(img->bdev_entry->bdev, img->gpt);
}
/********************** Backend API functions *******************************/
backend_ret_t backend_write_partition(const char *name, void *image_addr,
size_t image_size)
{
backend_ret_t ret;
struct image_part_details img;
ret = backend_do_init();
if (ret != BE_SUCCESS)
return ret;
ret = fill_img_part_info(&img, name);
if (ret != BE_SUCCESS)
return ret;
if (is_sparse_image(image_addr)) {
BE_LOG("Writing sparse image to %s...\n", name);
ret = write_sparse_image(&img, image_addr, image_size);
} else {
BE_LOG("Writing raw image to %s...\n", name);
ret = write_raw_image(&img, image_addr, image_size);
}
if (img.gpt)
GptUpdateKernelWithEntry(img.gpt, img.gpt_entry,
GPT_UPDATE_ENTRY_RESET);
clean_img_part_info(&img);
return ret;
}
backend_ret_t backend_erase_partition(const char *name)
{
backend_ret_t ret;
struct image_part_details img;
ret = backend_do_init();
if (ret != BE_SUCCESS)
return ret;
ret = fill_img_part_info(&img, name);
if (ret != BE_SUCCESS)
return ret;
struct bdev_info *bdev_entry = img.bdev_entry;
BlockDevOps *ops = &bdev_entry->bdev->ops;
size_t part_size_lba = img.part_size_lba;
size_t part_addr = img.part_addr;
/* Perform fill_write operation on the partition */
if (ops->fill_write(ops, part_addr, part_size_lba, 0xFF)
!= part_size_lba)
ret = BE_WRITE_ERR;
/* If operation was successful, update GPT entry if required */
else if (img.gpt)
GptUpdateKernelWithEntry(img.gpt, img.gpt_entry,
GPT_UPDATE_ENTRY_INVALID);
clean_img_part_info(&img);
return ret;
}
uint64_t backend_get_part_size_bytes(const char *name)
{
uint64_t ret = 0;
struct image_part_details img;
if (backend_do_init() != BE_SUCCESS)
return ret;
if (fill_img_part_info(&img, name) != BE_SUCCESS)
return ret;
ret = (uint64_t)img.part_size_lba * img.bdev_entry->bdev->block_size;
clean_img_part_info(&img);
return ret;
}
const char *backend_get_part_fs_type(const char *name)
{
struct part_info *part_entry;
if (backend_do_init() != BE_SUCCESS)
return NULL;
/* Get partition info from board-specific partition table */
part_entry = get_part_info(name);
if (part_entry == NULL)
return NULL;
return part_entry->part_fs_type;
}
uint64_t backend_get_bdev_size_bytes(const char *name)
{
if (backend_do_init() != BE_SUCCESS)
return 0;
struct bdev_info *bdev_entry;
/* Get bdev info from board-specific bdev table */
bdev_entry = get_bdev_info(name);
if (bdev_entry == NULL)
return 0;
BlockDev *bdev = bdev_entry->bdev;
uint64_t size = (uint64_t)bdev->block_count * bdev->block_size;
return size;
}
uint64_t backend_get_bdev_size_blocks(const char *name)
{
if (backend_do_init() != BE_SUCCESS)
return 0;
struct bdev_info *bdev_entry;
/* Get bdev info from board-specific bdev table */
bdev_entry = get_bdev_info(name);
if (bdev_entry == NULL)
return 0;
BlockDev *bdev = bdev_entry->bdev;
uint64_t size = (uint64_t)bdev->block_count;
return size;
}
int fb_fill_part_list(const char *name, size_t base, size_t size)
{
struct part_info *part_entry = get_part_info(name);
if (part_entry == NULL)
return -1;
part_entry->base = base;
part_entry->size = size;
return 0;
}