/* 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 "mtdlib_unused.h" #include "cgptlib.h" #include "cgptlib_internal.h" #include "crc32.h" #include "utility.h" #include "vboot_api.h" int MtdIsKernelEntry(const MtdDiskPartition *e) { return MtdGetEntryType(e) == MTD_PARTITION_TYPE_CHROMEOS_KERNEL; } void MtdModified(MtdData *mtd) { mtd->modified = 1; mtd->primary.crc32 = MtdHeaderCrc(&mtd->primary); } int MtdIsPartitionValid(const MtdDiskPartition *part) { return MtdGetEntryType(part) != 0; } 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 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); } 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 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; }