mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-11-24 02:05:01 +00:00
As per the discussion on issue 221745 we will be using 64-bit byte offsets for the MTD partition table and converting to/from sectors internally in cgpt. Existing interfaces do not change, eg sizes are still reported in sectors, only the on-disk representation is affected. BRANCH=none BUG=chromium:221745 TEST=unit tests pass Change-Id: Id312d42783acfdabe6eb8aea11dcbd298e00a100 Reviewed-on: https://gerrit.chromium.org/gerrit/60919 Commit-Queue: Albert Chaulk <achaulk@chromium.org> Reviewed-by: Albert Chaulk <achaulk@chromium.org> Tested-by: Albert Chaulk <achaulk@chromium.org>
369 lines
10 KiB
C
369 lines
10 KiB
C
/* 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.
|
|
*/
|
|
|
|
#include "mtdlib.h"
|
|
|
|
#include "cgptlib.h"
|
|
#include "cgptlib_internal.h"
|
|
#include "crc32.h"
|
|
#include "utility.h"
|
|
#include "vboot_api.h"
|
|
|
|
const int kSectorShift = 9; /* 512 bytes / sector. */
|
|
|
|
int MtdInit(MtdData *mtd) {
|
|
int ret;
|
|
|
|
mtd->modified = 0;
|
|
mtd->current_kernel = CGPT_KERNEL_ENTRY_NOT_FOUND;
|
|
mtd->current_priority = 999;
|
|
|
|
ret = MtdSanityCheck(mtd);
|
|
if (GPT_SUCCESS != ret) {
|
|
VBDEBUG(("MtdInit() failed sanity check\n"));
|
|
return ret;
|
|
}
|
|
|
|
return GPT_SUCCESS;
|
|
}
|
|
|
|
int MtdCheckParameters(MtdData *disk) {
|
|
if (disk->sector_bytes != 512) {
|
|
return GPT_ERROR_INVALID_SECTOR_SIZE;
|
|
}
|
|
|
|
/* At minimum, the disk must consist of at least one erase block */
|
|
if (disk->drive_sectors < disk->flash_block_bytes / disk->sector_bytes) {
|
|
return GPT_ERROR_INVALID_SECTOR_NUMBER;
|
|
}
|
|
|
|
/* Write pages must be an integer multiple of sector size */
|
|
if (disk->flash_page_bytes == 0 ||
|
|
disk->flash_page_bytes % disk->sector_bytes != 0) {
|
|
return GPT_ERROR_INVALID_FLASH_GEOMETRY;
|
|
}
|
|
|
|
/* Erase blocks must be an integer multiple of write pages */
|
|
if (disk->flash_block_bytes == 0 ||
|
|
disk->flash_block_bytes % disk->flash_page_bytes != 0) {
|
|
return GPT_ERROR_INVALID_FLASH_GEOMETRY;
|
|
}
|
|
|
|
/* Without a FTS region, why are you using MTD? */
|
|
if (disk->fts_block_size == 0) {
|
|
return GPT_ERROR_INVALID_FLASH_GEOMETRY;
|
|
}
|
|
return GPT_SUCCESS;
|
|
}
|
|
|
|
int MtdGetEntryPriority(const MtdDiskPartition *e) {
|
|
return ((e->flags & MTD_ATTRIBUTE_PRIORITY_MASK) >>
|
|
MTD_ATTRIBUTE_PRIORITY_OFFSET);
|
|
}
|
|
|
|
int MtdGetEntryTries(const MtdDiskPartition *e) {
|
|
return ((e->flags & MTD_ATTRIBUTE_TRIES_MASK) >>
|
|
MTD_ATTRIBUTE_TRIES_OFFSET);
|
|
}
|
|
|
|
int MtdGetEntrySuccessful(const MtdDiskPartition *e) {
|
|
return ((e->flags & MTD_ATTRIBUTE_SUCCESSFUL_MASK) >>
|
|
MTD_ATTRIBUTE_SUCCESSFUL_OFFSET);
|
|
}
|
|
|
|
int MtdGetEntryType(const MtdDiskPartition *e) {
|
|
return ((e->flags & MTD_ATTRIBUTE_TYPE_MASK) >> MTD_ATTRIBUTE_TYPE_OFFSET);
|
|
}
|
|
|
|
int MtdIsKernelEntry(const MtdDiskPartition *e) {
|
|
return MtdGetEntryType(e) == MTD_PARTITION_TYPE_CHROMEOS_KERNEL;
|
|
}
|
|
|
|
|
|
static void SetBitfield(MtdDiskPartition *e,
|
|
uint32_t offset, uint32_t mask, uint32_t v) {
|
|
e->flags = (e->flags & ~mask) | ((v << offset) & mask);
|
|
}
|
|
void MtdSetEntrySuccessful(MtdDiskPartition *e, int successful) {
|
|
SetBitfield(e, MTD_ATTRIBUTE_SUCCESSFUL_OFFSET,
|
|
MTD_ATTRIBUTE_SUCCESSFUL_MASK, successful);
|
|
}
|
|
void MtdSetEntryPriority(MtdDiskPartition *e, int priority) {
|
|
SetBitfield(e, MTD_ATTRIBUTE_PRIORITY_OFFSET, MTD_ATTRIBUTE_PRIORITY_MASK,
|
|
priority);
|
|
}
|
|
void MtdSetEntryTries(MtdDiskPartition *e, int tries) {
|
|
SetBitfield(e, MTD_ATTRIBUTE_TRIES_OFFSET, MTD_ATTRIBUTE_TRIES_MASK, tries);
|
|
}
|
|
void MtdSetEntryType(MtdDiskPartition *e, int type) {
|
|
SetBitfield(e, MTD_ATTRIBUTE_TYPE_OFFSET, MTD_ATTRIBUTE_TYPE_MASK, type);
|
|
}
|
|
|
|
void MtdModified(MtdData *mtd) {
|
|
mtd->modified = 1;
|
|
mtd->primary.crc32 = MtdHeaderCrc(&mtd->primary);
|
|
}
|
|
|
|
int MtdIsPartitionValid(const MtdDiskPartition *part) {
|
|
return MtdGetEntryType(part) != 0;
|
|
}
|
|
|
|
int MtdCheckEntries(MtdDiskPartition *entries, MtdDiskLayout *h) {
|
|
uint32_t i, j;
|
|
|
|
for (i = 0; i < MTD_MAX_PARTITIONS; i++) {
|
|
for (j = 0; j < MTD_MAX_PARTITIONS; j++) {
|
|
if (i != j) {
|
|
MtdDiskPartition *entry = entries + i;
|
|
MtdDiskPartition *e2 = entries + j;
|
|
uint64_t start, end;
|
|
uint64_t other_start, other_end;
|
|
|
|
if (!MtdIsPartitionValid(entry) || !MtdIsPartitionValid(e2))
|
|
continue;
|
|
|
|
MtdGetPartitionSize(entry, &start, &end, NULL);
|
|
MtdGetPartitionSize(e2, &other_start, &other_end, NULL);
|
|
|
|
if((start == 0 && end == 0) ||
|
|
(other_start == 0 && other_end == 0)) {
|
|
continue;
|
|
}
|
|
|
|
if (end > h->last_offset) {
|
|
return GPT_ERROR_OUT_OF_REGION;
|
|
}
|
|
if (start < h->first_offset) {
|
|
return GPT_ERROR_OUT_OF_REGION;
|
|
}
|
|
if (start > end) {
|
|
return GPT_ERROR_OUT_OF_REGION;
|
|
}
|
|
|
|
if ((start >= other_start) &&
|
|
(start <= other_end)) {
|
|
return GPT_ERROR_START_LBA_OVERLAP;
|
|
}
|
|
if ((end >= other_start) &&
|
|
(end <= other_end)) {
|
|
return GPT_ERROR_END_LBA_OVERLAP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return GPT_SUCCESS;
|
|
}
|
|
|
|
int MtdSanityCheck(MtdData *disk) {
|
|
MtdDiskLayout *h = &disk->primary;
|
|
int ret;
|
|
|
|
ret = MtdCheckParameters(disk);
|
|
if(GPT_SUCCESS != ret)
|
|
return ret;
|
|
|
|
if (Memcmp(disk->primary.signature, MTD_DRIVE_SIGNATURE,
|
|
sizeof(disk->primary.signature))) {
|
|
return GPT_ERROR_INVALID_HEADERS;
|
|
}
|
|
|
|
if (disk->primary.first_offset > disk->primary.last_offset ||
|
|
disk->primary.last_offset > disk->drive_sectors * disk->sector_bytes) {
|
|
return GPT_ERROR_INVALID_SECTOR_NUMBER;
|
|
}
|
|
|
|
if (h->crc32 != MtdHeaderCrc(h)) {
|
|
return GPT_ERROR_CRC_CORRUPTED;
|
|
}
|
|
if (h->size < MTD_DRIVE_V1_SIZE) {
|
|
return GPT_ERROR_INVALID_HEADERS;
|
|
}
|
|
return MtdCheckEntries(h->partitions, h);
|
|
}
|
|
|
|
void MtdRepair(MtdData *gpt) {
|
|
|
|
}
|
|
|
|
void MtdGetCurrentKernelUniqueGuid(MtdData *gpt, void *dest) {
|
|
Memset(dest, 0, 16);
|
|
}
|
|
|
|
uint32_t MtdHeaderCrc(MtdDiskLayout *h) {
|
|
uint32_t crc32, original_crc32;
|
|
|
|
/* Original CRC is calculated with the CRC field 0. */
|
|
original_crc32 = h->crc32;
|
|
h->crc32 = 0;
|
|
crc32 = Crc32((const uint8_t *)h, h->size);
|
|
h->crc32 = original_crc32;
|
|
|
|
return crc32;
|
|
}
|
|
|
|
void MtdGetPartitionSize(const MtdDiskPartition *e,
|
|
uint64_t *start, uint64_t *end, uint64_t *size) {
|
|
uint64_t start_tmp, end_tmp;
|
|
if (!start)
|
|
start = &start_tmp;
|
|
if (!end)
|
|
end = &end_tmp;
|
|
|
|
Memcpy(start, &e->starting_offset, sizeof(e->starting_offset));
|
|
Memcpy(end, &e->ending_offset, sizeof(e->ending_offset));
|
|
if (size) {
|
|
*size = *end - *start + 1;
|
|
}
|
|
}
|
|
|
|
void MtdGetPartitionSizeInSectors(const MtdDiskPartition *e, uint64_t *start,
|
|
uint64_t *end, uint64_t *size) {
|
|
MtdGetPartitionSize(e, start, end, size);
|
|
if (start)
|
|
*start >>= kSectorShift;
|
|
if (end)
|
|
*end >>= kSectorShift;
|
|
if (size)
|
|
*size >>= kSectorShift;
|
|
}
|
|
|
|
|
|
int MtdNextKernelEntry(MtdData *mtd, uint64_t *start_sector, uint64_t *size)
|
|
{
|
|
MtdDiskLayout *header = &mtd->primary;
|
|
MtdDiskPartition *entries = header->partitions;
|
|
MtdDiskPartition *e;
|
|
int new_kernel = CGPT_KERNEL_ENTRY_NOT_FOUND;
|
|
int new_prio = 0;
|
|
uint32_t i;
|
|
|
|
/*
|
|
* If we already found a kernel, continue the scan at the current
|
|
* kernel's priority, in case there is another kernel with the same
|
|
* priority.
|
|
*/
|
|
if (mtd->current_kernel != CGPT_KERNEL_ENTRY_NOT_FOUND) {
|
|
for (i = mtd->current_kernel + 1;
|
|
i < MTD_MAX_PARTITIONS; i++) {
|
|
e = entries + i;
|
|
if (!MtdIsKernelEntry(e))
|
|
continue;
|
|
VBDEBUG(("MtdNextKernelEntry looking at same prio "
|
|
"partition %d\n", i+1));
|
|
VBDEBUG(("MtdNextKernelEntry s%d t%d p%d\n",
|
|
MtdGetEntrySuccessful(e), MtdGetEntryTries(e),
|
|
MtdGetEntryPriority(e)));
|
|
if (!(MtdGetEntrySuccessful(e) || MtdGetEntryTries(e)))
|
|
continue;
|
|
if (MtdGetEntryPriority(e) == mtd->current_priority) {
|
|
MtdGetPartitionSizeInSectors(e, start_sector, NULL, size);
|
|
mtd->current_kernel = i;
|
|
VBDEBUG(("MtdNextKernelEntry likes it\n"));
|
|
return GPT_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We're still here, so scan for the remaining kernel with the highest
|
|
* priority less than the previous attempt.
|
|
*/
|
|
for (i = 0, e = entries; i < MTD_MAX_PARTITIONS; i++, e++) {
|
|
int current_prio = MtdGetEntryPriority(e);
|
|
if (!MtdIsKernelEntry(e))
|
|
continue;
|
|
VBDEBUG(("MtdNextKernelEntry looking at new prio "
|
|
"partition %d\n", i+1));
|
|
VBDEBUG(("MtdNextKernelEntry s%d t%d p%d\n",
|
|
MtdGetEntrySuccessful(e), MtdGetEntryTries(e),
|
|
MtdGetEntryPriority(e)));
|
|
if (!(MtdGetEntrySuccessful(e) || MtdGetEntryTries(e)))
|
|
continue;
|
|
if (current_prio >= mtd->current_priority) {
|
|
/* Already returned this kernel in a previous call */
|
|
continue;
|
|
}
|
|
if (current_prio > new_prio) {
|
|
new_kernel = i;
|
|
new_prio = current_prio;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Save what we found. Note that if we didn't find a new kernel,
|
|
* new_prio will still be -1, so future calls to this function will
|
|
* also fail.
|
|
*/
|
|
mtd->current_kernel = new_kernel;
|
|
mtd->current_priority = new_prio;
|
|
|
|
if (CGPT_KERNEL_ENTRY_NOT_FOUND == new_kernel) {
|
|
VBDEBUG(("MtdNextKernelEntry no more kernels\n"));
|
|
return GPT_ERROR_NO_VALID_KERNEL;
|
|
}
|
|
|
|
VBDEBUG(("MtdNextKernelEntry likes partition %d\n", new_kernel + 1));
|
|
e = entries + new_kernel;
|
|
MtdGetPartitionSizeInSectors(e, start_sector, NULL, size);
|
|
|
|
return GPT_SUCCESS;
|
|
}
|
|
|
|
int MtdUpdateKernelEntry(MtdData *mtd, uint32_t update_type)
|
|
{
|
|
MtdDiskLayout *header = &mtd->primary;
|
|
MtdDiskPartition *entries = header->partitions;
|
|
MtdDiskPartition *e = entries + mtd->current_kernel;
|
|
int modified = 0;
|
|
|
|
if (mtd->current_kernel == CGPT_KERNEL_ENTRY_NOT_FOUND)
|
|
return GPT_ERROR_INVALID_UPDATE_TYPE;
|
|
if (!MtdIsKernelEntry(e))
|
|
return GPT_ERROR_INVALID_UPDATE_TYPE;
|
|
|
|
switch (update_type) {
|
|
case GPT_UPDATE_ENTRY_TRY: {
|
|
/* Used up a try */
|
|
int tries;
|
|
if (MtdGetEntrySuccessful(e)) {
|
|
/*
|
|
* Successfully booted this partition, so tries field
|
|
* is ignored.
|
|
*/
|
|
return GPT_SUCCESS;
|
|
}
|
|
tries = MtdGetEntryTries(e);
|
|
if (tries > 1) {
|
|
/* Still have tries left */
|
|
modified = 1;
|
|
MtdSetEntryTries(e, tries - 1);
|
|
break;
|
|
}
|
|
/* Out of tries, so drop through and mark partition bad. */
|
|
}
|
|
case GPT_UPDATE_ENTRY_BAD: {
|
|
/* Giving up on this partition entirely. */
|
|
if (!MtdGetEntrySuccessful(e)) {
|
|
/*
|
|
* Only clear tries and priority if the successful bit
|
|
* is not set.
|
|
*/
|
|
modified = 1;
|
|
MtdSetEntryTries(e, 0);
|
|
MtdSetEntryPriority(e, 0);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return GPT_ERROR_INVALID_UPDATE_TYPE;
|
|
}
|
|
|
|
if (modified) {
|
|
MtdModified(mtd);
|
|
}
|
|
|
|
return GPT_SUCCESS;
|
|
}
|