| /* |
| * Copyright (C) 2019 Cradlepoint <kswenson@cradlepoint.com> |
| * |
| * Commands for reading/writing/updating bootconfig paritions |
| * |
| * Probably GPLv2 cause everything else in here is too |
| */ |
| |
| #include <common.h> |
| #include <command.h> |
| #include <environment.h> |
| #include <malloc.h> |
| |
| #include <asm/arch-qca-common/smem.h> |
| |
| #include <mmc.h> |
| #include <part.h> |
| #include <part_efi.h> |
| |
| #include <asm/byteorder.h> |
| #include <linux/compiler.h> |
| #include <linux/ctype.h> |
| #include <linux/err.h> |
| |
| #include <bootconfig.h> |
| #include <blkdev.h> |
| #include <cp_board.h> |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| static struct bootconfig* gBootconfig0Ptr = NULL; |
| static struct bootconfig* gBootconfig1Ptr = NULL; |
| static struct bootconfig* gCurrentBootconfig= NULL; |
| |
| struct bootconfig * init_bootconfig() |
| { |
| if (gBootconfig0Ptr && gBootconfig1Ptr && gCurrentBootconfig) { |
| return gCurrentBootconfig; |
| } |
| printf("Bootconfig init begin\n"); |
| if (!gBootconfig0Ptr) { |
| gBootconfig0Ptr = (struct bootconfig *) malloc(sizeof(struct bootconfig)); |
| if (gBootconfig0Ptr == NULL) { |
| printf("Failed to malloc a struct bootconfig during bootconfig init\n"); |
| goto init_malloc_fail; |
| } |
| } |
| if (!gBootconfig1Ptr) { |
| gBootconfig1Ptr = (struct bootconfig *) malloc(sizeof(struct bootconfig)); |
| if (gBootconfig1Ptr == NULL) { |
| printf("Failed to malloc a struct bootconfig during bootconfig init\n"); |
| goto init_malloc_fail; |
| } |
| } |
| gBootconfig0Ptr = read_bootconfig(gBootconfig0Ptr, 0); |
| if (!gBootconfig0Ptr) { |
| printf("Failed to read the bootconfig\n"); |
| goto init_malloc_fail; |
| } |
| gBootconfig1Ptr = read_bootconfig(gBootconfig1Ptr, 1); |
| if (!gBootconfig1Ptr){ |
| printf("Failed to read the bootconfig1\n"); |
| goto init_malloc_fail; |
| } |
| |
| if (gBootconfig0Ptr->age >= gBootconfig1Ptr->age) { |
| gCurrentBootconfig = gBootconfig0Ptr; |
| } else { |
| printf("Bootconfig1 Age is older than Bootconfig0\n"); |
| gCurrentBootconfig = gBootconfig1Ptr; |
| } |
| |
| return gCurrentBootconfig; |
| init_malloc_fail: |
| if (gBootconfig0Ptr) { |
| free(gBootconfig0Ptr); |
| gBootconfig0Ptr = NULL; |
| } |
| if (gBootconfig1Ptr) { |
| free(gBootconfig1Ptr); |
| gBootconfig1Ptr = NULL; |
| } |
| return NULL; |
| } |
| |
| struct cp_part* get_active_partition_info(char * name) |
| { |
| debug("%s(name=%s)\n", __func__, name); |
| char * real_name = (char*) malloc(sizeof(char) * MAX_PART_NAME_LENGTH ); |
| if (real_name == NULL){ |
| printf("Failed to allocate the real partition name\n"); |
| return NULL; |
| } |
| real_name = memset(real_name, 0, sizeof(char)* MAX_PART_NAME_LENGTH); |
| strcpy(real_name, name); |
| |
| if (smem_bootconfig_info() == 0) { |
| if (get_active_partition(name)) { |
| strcat(real_name, "_1"); |
| } |
| } else { |
| printf("Failed to get smem_bootconfig_info()\n"); |
| } |
| debug("Returning partition info for %s\n", real_name); |
| struct cp_part * p = get_partition(real_name); |
| free(real_name); |
| return p; |
| |
| } |
| |
| void deinit_bootconfig() |
| { |
| gCurrentBootconfig = NULL; |
| if (gBootconfig0Ptr){ |
| free(gBootconfig0Ptr); |
| gBootconfig0Ptr = NULL; |
| } |
| if (gBootconfig1Ptr){ |
| free(gBootconfig1Ptr); |
| gBootconfig1Ptr = NULL; |
| } |
| } |
| int switch_primary_boot(const char * partition_name) |
| { |
| |
| struct bootconfig * bootconfig_buffer = init_bootconfig(); |
| if (!bootconfig_buffer){ |
| printf("Failed to init the bootconfig \n"); |
| return -2; |
| } |
| for (int i = 0; i < bootconfig_buffer->num_alt_parts; i++) { |
| |
| /* We want things like APPSBL_1 to be the same as APPSBL below, hence the strncmp */ |
| ssize_t ncompare = strlen(bootconfig_buffer->partitions[i].name)-1; |
| if (ncompare <= 0) { |
| printf("Warning ncompare is %d\n", ncompare); |
| } |
| printf("comparing the first %zu bytes of %s and %s\n", ncompare, partition_name, bootconfig_buffer->partitions[i].name); |
| if (strncmp(bootconfig_buffer->partitions[i].name, partition_name, ncompare) == 0) { |
| printf("Match found, primary_boot value for %s is %u\n", partition_name, bootconfig_buffer->partitions[i].primary_boot); |
| if(bootconfig_buffer->partitions[i].primary_boot){ |
| bootconfig_buffer->partitions[i].primary_boot = 0; |
| } else { |
| bootconfig_buffer->partitions[i].primary_boot = 1; |
| } |
| printf("primary_boot value for %s has been updated to %u\n", partition_name, bootconfig_buffer->partitions[i].primary_boot); |
| return 0; |
| } |
| } |
| printf("Failed to find partition %s\n", partition_name); |
| return -1; |
| } |
| |
| |
| /* Take a partition name as input, return the disk_partition_t for the right partition to upgrade */ |
| int upgrade_failsafe_partition(const char * partition_name, void* upgrade_data, uint32_t upgrade_length) |
| { |
| struct cp_part * upgrade_part = NULL; |
| struct bootconfig * bc = init_bootconfig(); |
| if (!bc) { |
| printf("Failed to init bootconfig\n"); |
| return -EINVAL; |
| } |
| upgrade_part = find_upgrade_partition(partition_name); |
| if (upgrade_part == NULL) { |
| printf("Failed to find upgrade partition for %s\n", partition_name); |
| return -ENOENT; |
| } |
| |
| printf("Upgrading partition %s\n", upgrade_part->name); |
| if (upgrade_length > (PARTITION_SIZE(upgrade_part))) { |
| printf("Upgrade length (0x%X) is too large to fit into the partition of size %u bytes (0x%X)\n", upgrade_length, PARTITION_SIZE(upgrade_part),PARTITION_SIZE(upgrade_part) ); |
| return -ENOMEM; |
| } |
| partition_erase(upgrade_part); |
| partition_write(upgrade_part, 0, upgrade_length, upgrade_data); |
| |
| if(switch_primary_boot(partition_name)) { |
| printf("We might've failed to siwtch the primary boot partition for %s\n", partition_name); |
| } |
| return 0; |
| } |
| |
| int upgrade_complete(void) |
| { |
| struct bootconfig * bc = init_bootconfig(); |
| if (!bc) { |
| printf("Failed to initalize the bootconfig\n"); |
| return -1; |
| } |
| bc->age += 1; |
| return write_bootconfig(bc); |
| } |
| int write_bootconfig(struct bootconfig * bootconfig ) |
| { |
| struct cp_part * bc = get_partition("0:BOOTCONFIG"); |
| struct cp_part * bc1 = get_partition("0:BOOTCONFIG1"); |
| |
| |
| |
| uint32_t buffer_len= PAD_SIZE(sizeof(struct bootconfig), bc->blkdev->sector_size); |
| printf("About to allocate 0x%X bytes for a bootconfig buffer", buffer_len); |
| uint8_t * buffer = (uint8_t*) malloc(sizeof(uint8_t)*buffer_len); |
| if (buffer == NULL) { |
| printf("Failed to allcoate buffer to serialize the bootcofnig\n"); |
| return -1; |
| } |
| buffer = memset(buffer, 0, buffer_len); |
| if (!buffer) { |
| printf("Failed to memset buffer to write the bootconfigs!\n"); |
| return -1; |
| } |
| buffer = serialize_bootconfig(buffer, gCurrentBootconfig); |
| if (!buffer) { |
| printf("Failed to serialize the bootconfig buffer for writing\n"); |
| return -1; |
| } |
| |
| printf("Writing %d bytes out to partition %s\n", buffer_len, bc->name); |
| partition_write(bc, 0, buffer_len, buffer); |
| printf("Writing %d bytes out to partition %s\n", buffer_len, bc1->name); |
| partition_write(bc1, 0, buffer_len, buffer); |
| |
| if (buffer) { |
| free(buffer); |
| } |
| deinit_bootconfig(); |
| gCurrentBootconfig = init_bootconfig(); |
| return 0; |
| } |
| struct bootconfig * read_bootconfig(struct bootconfig * bootconfig, int primary) |
| { |
| struct cp_part * part = NULL; |
| |
| if (primary) { |
| part = get_partition("0:BOOTCONFIG"); |
| } else { |
| part = get_partition("0:BOOTCONFIG1"); |
| } |
| |
| if (!part) { |
| printf("Failed to get the bootconfig parition!\n"); |
| return NULL; |
| } |
| uint8_t * buffer = (uint8_t*) malloc(PARTITION_SIZE(part)); |
| if (!buffer) { |
| printf("Failed to allocate buffer to read the bootconfigs!\n"); |
| return NULL; |
| } |
| |
| partition_read(part, 0, PARTITION_SIZE(part), buffer); |
| struct bootconfig * res = parse_bootconfig(bootconfig, buffer); |
| if (buffer) { |
| free(buffer); |
| } |
| return res; |
| } |
| |
| void print_bootconfig_partition(struct bootconfig_partition * p) |
| { |
| if (!p) { |
| return ; |
| } |
| printf("%s\t\t\tPrimary Boot: %d\n", p->name, p->primary_boot); |
| return; |
| } |
| void print_bootconfig(struct bootconfig * bc) |
| { |
| if (!bc) { |
| printf("NULL BOOTCONFIG\n"); |
| return; |
| } |
| |
| printf("BOOTCONFIG: (Magic is 0x%X)\n", bc->start_magic); |
| printf("\tAGE: %d\n", bc->age); |
| printf("\tNum Parts: %d\n", bc->num_alt_parts); |
| printf("\tpartiions: %d\n", bc->num_alt_parts); |
| printf("\tNum Parts: %d\n", bc->num_alt_parts); |
| for (int i = 0; i < bc->num_alt_parts; i++) { |
| printf("\t"); |
| print_bootconfig_partition(&bc->partitions[i]); |
| } |
| printf("BOOTCONFIG: (End Magic is 0x%X)\n", bc->end_magic); |
| } |
| |
| |
| uint8_t * serialize_bootconfig(uint8_t * buffer, struct bootconfig * bootconfig) |
| { |
| printf("Serilizing a bootconfig structure into buffer at 0x%p from struct bootconfig at 0x%p\n", (void*)buffer, (void*)bootconfig); |
| return (uint8_t*) memcpy(buffer, bootconfig, sizeof(struct bootconfig)); |
| } |
| |
| struct bootconfig * parse_bootconfig(struct bootconfig * bootconfig, uint8_t * buffer) |
| { |
| printf("deserilizing a bootconfig structure into struct bootconfig at 0x%p from buffer at 0x%p\n", (void*)bootconfig, (void*)buffer); |
| struct bootconfig * setbc = memset(bootconfig, 0, sizeof(struct bootconfig)); |
| return (struct bootconfig *) memcpy(setbc, buffer, sizeof(struct bootconfig)); |
| |
| } |
| static void display_bootconfig_info(void) |
| { |
| struct bootconfig* bc = init_bootconfig(); |
| if (!bc) { |
| printf("Failed to init the bootconfig\n"); |
| return; |
| } |
| |
| printf("Active bootconfig is "); |
| if(bc == gBootconfig0Ptr) { |
| printf("primary"); |
| } |
| if(bc == gBootconfig1Ptr) { |
| printf("secondary"); |
| } |
| printf("\n"); |
| print_bootconfig(bc); |
| printf("\n\nPrimary bootconfig: \n"); |
| print_bootconfig(gBootconfig0Ptr); |
| printf("\n\nSecondary bootconfig: \n"); |
| print_bootconfig(gBootconfig1Ptr); |
| |
| } |
| /** |
| * do_bootconfig() - Handle the "bootconfig" command-line command |
| * @cmdtp: Command data struct pointer |
| * @flag: Command flag |
| * @argc: Command-line argument count |
| * @argv: Array of command-line arguments |
| * |
| * Returns zero on success, CMD_RET_USAGE in case of misuse and negative |
| * on error. |
| */ |
| static int do_bootconfig_cmd(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| struct cp_part * part = NULL; |
| |
| switch(argc) { |
| case 3: |
| printf("Switching the primary boot of %s\n", argv[2]); |
| if (switch_primary_boot(argv[2]) != 0) { |
| printf("Failed\n"); |
| return CMD_RET_FAILURE; |
| } |
| upgrade_complete(); |
| break; |
| case 2: |
| printf("Active partition for %s\n", argv[1]); |
| part = get_active_partition_info(argv[1]); |
| if (part == NULL) { |
| printf("Partition %s not found\n", argv[1]); |
| return CMD_RET_FAILURE; |
| } |
| printf("Active partition for %s is %s\n", argv[1], part->name); |
| break; |
| case 1: |
| display_bootconfig_info(); |
| break; |
| default: |
| return CMD_RET_USAGE; |
| } |
| return CMD_RET_SUCCESS; |
| |
| |
| return 0; |
| |
| } |
| |
| /***************************************************/ |
| |
| U_BOOT_CMD( |
| bootconfig, 6, 1, do_bootconfig_cmd, |
| "Bootconfig Information", |
| "bootconfig" |
| ); |