| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files |
| * (the "Software"), to deal in the Software without restriction, |
| * including without limitation the rights to use, copy, modify, merge, |
| * publish, distribute, sublicense, and/or sell copies of the Software, |
| * and to permit persons to whom the Software is furnished to do so, |
| * subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| #include <assert.h> |
| #include <endian.h> |
| #include <errno.h> |
| #include <lib/bio.h> |
| #include <lib/bootctrl.h> |
| #include <lib/bootctrl_plat.h> |
| #include <lib/cksum.h> |
| #include <lib/mempool.h> |
| #include <libavb_ab/libavb_ab.h> |
| #include <platform.h> |
| #include <string.h> |
| #include <trace.h> |
| |
| #define BOOTCTR_PARTITION "misc" |
| #define MOD "bootctrl" |
| #define SLOT_COUNT 2 |
| #define LOCAL_TRACE 0 |
| /****************************************************************************** |
| * DEBUG |
| ******************************************************************************/ |
| static AvbABData metadata_saved; |
| static int metadata_read = 0; |
| |
| const char *get_suffix(void) |
| { |
| const char *suffix[] = {BOOTCTRL_SUFFIX_A, BOOTCTRL_SUFFIX_B}; |
| int slot = get_current_slot(); |
| if (slot < 0 || slot >= SLOT_COUNT) |
| return NULL; |
| return suffix[slot]; |
| } |
| |
| static int read_write_partition_info(AvbABData *bctrl ,int mode) |
| { |
| int ret = -1; |
| bdev_t *bdev; |
| size_t bdev_erase_size; |
| size_t total_erase_size; |
| ssize_t read_bytes; |
| ssize_t write_bytes; |
| void *metadata_info_buf = NULL; |
| uint32_t crc32val; |
| |
| bdev = bio_open_by_label(BOOTCTR_PARTITION); |
| if (!bdev) { |
| LTRACEF("Partition [%s] is not exist.\n", BOOTCTR_PARTITION); |
| return ret; |
| } |
| |
| ASSERT(bdev->geometry != NULL); |
| bdev_erase_size = (size_t)bdev->geometry->erase_size; |
| LTRACEF("bdev_erase_size is %d. \n", bdev_erase_size); |
| switch (mode) { |
| case READ_PARTITION: |
| if ((metadata_read) && (!memcmp(metadata_saved.magic ,AVB_AB_MAGIC, |
| AVB_AB_MAGIC_LEN))) { |
| memcpy(bctrl, &metadata_saved, sizeof(AvbABData)); |
| } else { |
| read_bytes = bio_read(bdev, (void *)bctrl, |
| OFFSETOF_METADATA_INFO, sizeof(AvbABData)); |
| if (-1 == read_bytes) { |
| LTRACEF("bio_read error %ld\n", read_bytes); |
| goto out; |
| } |
| memcpy(&metadata_saved,bctrl,sizeof(AvbABData)); |
| metadata_read = 1; |
| } |
| break; |
| case WRITE_PARTITION: |
| crc32val = (uint32_t)crc32(0UL, (const uint8_t *)bctrl, |
| sizeof(AvbABData) - sizeof(uint32_t)); |
| bctrl->crc32 = ntohl(crc32val); |
| |
| total_erase_size = bdev_erase_size * |
| ((OFFSETOF_METADATA_INFO + sizeof(AvbABData)) / bdev_erase_size + 1); |
| |
| // allocate buffer for the partition |
| metadata_info_buf = mempool_alloc(total_erase_size, MEMPOOL_ANY); |
| if (!metadata_info_buf) { |
| LTRACEF("mempool alloc error. \n"); |
| goto out; |
| } |
| |
| // read the partition data |
| read_bytes = bio_read(bdev, (void *)metadata_info_buf, |
| 0, total_erase_size); |
| if (-1 == read_bytes) { |
| LTRACEF("bio_read error: %zd\n", read_bytes); |
| goto out; |
| } |
| |
| // update the content in partition_buf |
| memcpy(metadata_info_buf + OFFSETOF_METADATA_INFO, bctrl, sizeof(AvbABData)); |
| |
| // earse the partition |
| write_bytes = bio_erase(bdev, 0, total_erase_size); |
| if (write_bytes < 0) { |
| LTRACEF("bio_erase error: %zd\n", write_bytes); |
| goto out; |
| } |
| |
| // write the partition |
| write_bytes = bio_write(bdev, (void *)metadata_info_buf, |
| 0, total_erase_size); |
| if ((size_t)write_bytes != total_erase_size) { |
| LTRACEF("bio_write error: %zd\n", write_bytes); |
| goto out; |
| } |
| |
| metadata_read = 0; //force to read from partition after successful blkdev_write()E |
| break; |
| default: |
| break; |
| } |
| ret = 0; |
| out: |
| bio_close(bdev); |
| if (metadata_info_buf != NULL) { |
| mempool_free(metadata_info_buf); |
| } |
| return ret; |
| } |
| |
| int ab_metadata_init(int slot) |
| { |
| int slot1 = 0; |
| int ret = -1 ; |
| AvbABSlotData *slotp; |
| |
| AvbABData metadata; |
| |
| if (slot < 0 || slot >= SLOT_COUNT) { |
| return -1; |
| } |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| memcpy(metadata.magic ,AVB_AB_MAGIC, AVB_AB_MAGIC_LEN); |
| |
| /* Set highest priority and reset retry count */ |
| slotp = &metadata.slots[slot]; |
| slotp->successful_boot = 0; |
| slotp->priority = AVB_AB_MAX_PRIORITY; |
| slotp->tries_remaining = AVB_AB_MAX_TRIES_REMAINING; |
| slotp->efuse_write = 0; |
| slotp->bl_ver = 0; |
| |
| /* Re-set arg to another slot */ |
| slot1 = (slot == 0) ? 1 : 0; |
| slotp = &metadata.slots[slot1]; |
| slotp->successful_boot = 0; |
| slotp->priority = AVB_AB_MAX_PRIORITY - 1; |
| slotp->tries_remaining = AVB_AB_MAX_TRIES_REMAINING; |
| slotp->efuse_write = 0; |
| slotp->bl_ver = 0; |
| |
| ret = read_write_partition_info(&metadata, WRITE_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| int get_current_slot(void) |
| { |
| int slot = 0, ret = -1; |
| |
| AvbABData metadata; |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| if (memcmp(metadata.magic , AVB_AB_MAGIC, AVB_AB_MAGIC_LEN) != 0) { |
| LTRACEF("booctrl magic not match init default value\n"); |
| ab_metadata_init(0); |
| plat_ab_set_active_bootdev(0); |
| return slot; |
| } |
| slot = (metadata.slots[0].priority >= metadata.slots[1].priority) ? 0 : 1; |
| |
| return slot; |
| } |
| |
| |
| int rollback_slot(int slot) |
| { |
| int slot1,ret = -1; |
| AvbABSlotData *slotp; |
| AvbABData metadata; |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| /* Set highest priority and reset retry count */ |
| slotp = &metadata.slots[slot]; |
| slotp->priority = AVB_AB_MAX_PRIORITY; |
| |
| /* Ensure other slot doesn't have as high a priority. */ |
| slot1 = (slot == 0) ? 1 : 0; |
| slotp = &metadata.slots[slot1]; |
| if (slotp->priority == AVB_AB_MAX_PRIORITY) |
| slotp->priority = AVB_AB_MAX_PRIORITY - 1; |
| |
| ret = read_write_partition_info(&metadata, WRITE_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| uint8_t get_retry_count(int slot) |
| { |
| int ret = -1; |
| AvbABSlotData *slotp; |
| AvbABData metadata; |
| |
| if (slot < 0 || slot >= SLOT_COUNT) { |
| return ret; |
| } |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| slotp = &metadata.slots[slot]; |
| return slotp->tries_remaining; |
| } |
| |
| int reduce_retry_count(int slot) |
| { |
| int ret = -1; |
| AvbABSlotData *slotp; |
| AvbABData metadata; |
| |
| if (slot < 0 || slot >= SLOT_COUNT) { |
| return -1; |
| } |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| slotp = &metadata.slots[slot]; |
| if (slotp->tries_remaining > 0) |
| slotp->tries_remaining--; |
| |
| ret = read_write_partition_info(&metadata, WRITE_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int check_valid_slot(void) |
| { |
| int ret = -1; |
| AvbABData metadata; |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| |
| if (ret < 0) { |
| return -1; |
| } |
| |
| if (memcmp(metadata.magic , AVB_AB_MAGIC, AVB_AB_MAGIC_LEN) != 0) { |
| LTRACEF("booctrl magic not match init default value\n"); |
| ab_metadata_init(0); |
| return 0; |
| } |
| |
| if ((metadata.slots[0].priority > 0) || (metadata.slots[1].priority > 0)) |
| return 0; |
| |
| return -1; |
| } |
| |
| int mark_slot_invalid(int slot) |
| { |
| int ret = -1; |
| AvbABSlotData *slotp; |
| AvbABData metadata; |
| |
| if (slot < 0 || slot >= SLOT_COUNT) { |
| return -1; |
| } |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| slotp = &metadata.slots[slot]; |
| slotp->successful_boot = 0; |
| slotp->priority = 0; |
| |
| ret = read_write_partition_info(&metadata, WRITE_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int get_bootup_status(int slot) |
| { |
| int ret = -1; |
| AvbABSlotData *slotp; |
| AvbABData metadata; |
| |
| if (slot < 0 || slot >= SLOT_COUNT) { |
| return -1; |
| } |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| if (ret < 0) { |
| return -1; |
| } |
| |
| slotp = &metadata.slots[slot]; |
| return slotp->successful_boot; |
| } |
| |
| static void check_update_anti_rollback_bl_ver(int slot) |
| { |
| int ret = -1; |
| AvbABSlotData *slotp; |
| AvbABData metadata; |
| |
| ret = read_write_partition_info(&metadata, READ_PARTITION); |
| if (ret < 0) { |
| return; |
| } |
| slotp = &metadata.slots[slot]; |
| if (slotp->efuse_write) { |
| ret = plat_write_lk_antirollback_version(slotp->bl_ver); |
| if (ret) { |
| LTRACEF("efuse write bl ver %d fail: %d\n",slotp->bl_ver,ret); |
| } |
| slotp->efuse_write = 0; |
| read_write_partition_info(&metadata, WRITE_PARTITION); |
| } |
| } |
| |
| int check_ab_boot(void) |
| { |
| int ret, slot,next_slot; |
| |
| if (check_valid_slot() != 0){ |
| ab_metadata_init(0); |
| plat_ab_set_active_bootdev(0); |
| } |
| |
| slot = get_current_slot(); |
| if (get_bootup_status(slot) == 1) { |
| check_update_anti_rollback_bl_ver(slot); |
| return 0; |
| } |
| |
| if (get_retry_count(slot) > 0) { |
| reduce_retry_count(slot); |
| return 0; |
| } |
| |
| mark_slot_invalid(slot); |
| |
| if (check_valid_slot() == -1) |
| return -EIO; |
| next_slot = (slot == 0) ? 1 : 0; |
| ret = plat_ab_set_active_bootdev(next_slot); |
| |
| /* report the bad slot error code when set new active bootdev ok */ |
| if (ret == 0) |
| return -EBADSLT; |
| |
| /* otherwise, report the error code we encounter */ |
| return ret; |
| } |
| |