blob: 118b274414ad4b1f93fd5c2e086b5c64befe9f80 [file] [log] [blame]
/*
* 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"
);