mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-02 12:49:03 +00:00
Following steps led to incorrect behavior: - upload update image - restart device and perform the update - upload the same update image again without confirming This led to the unwanted confirm of the update image. This change ensures the update image is not confirmed by uploading the same image to the update slot. The correct behavior is to perform revert to the last stable image if this occurs. Signed-off-by: Michal Lenc <michallenc@seznam.cz>
932 lines
25 KiB
C
932 lines
25 KiB
C
/****************************************************************************
|
|
* apps/boot/nxboot/loader/boot.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <errno.h>
|
|
#include <syslog.h>
|
|
#include <sys/param.h>
|
|
|
|
#include <nuttx/crc32.h>
|
|
#include <nxboot.h>
|
|
|
|
#include "flash.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NXBOOT_PREVENT_DOWNGRADE
|
|
# warning "Downgrade prevention currently ignores prerelease."
|
|
#endif
|
|
|
|
#define IS_INTERNAL_MAGIC(magic) ((magic & NXBOOT_HEADER_MAGIC_INT_MASK) \
|
|
== NXBOOT_HEADER_MAGIC_INT)
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
static inline void get_image_header(int fd, struct nxboot_img_header *header)
|
|
{
|
|
int ret;
|
|
ret = flash_partition_read(fd, header, sizeof *header, 0);
|
|
if (ret < 0)
|
|
{
|
|
/* Something went wrong, treat the partition as empty. */
|
|
|
|
memset(header, 0, sizeof *header);
|
|
}
|
|
}
|
|
|
|
static inline bool validate_image_header(struct nxboot_img_header *header)
|
|
{
|
|
return (header->magic == NXBOOT_HEADER_MAGIC ||
|
|
IS_INTERNAL_MAGIC(header->magic)) &&
|
|
header->identifier == CONFIG_NXBOOT_PLATFORM_IDENTIFIER;
|
|
}
|
|
|
|
static uint32_t calculate_crc(int fd, struct nxboot_img_header *header)
|
|
{
|
|
char *buf;
|
|
int remain;
|
|
int readsiz;
|
|
off_t off;
|
|
uint32_t crc;
|
|
struct flash_partition_info info;
|
|
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
|
|
int total_size;
|
|
#endif
|
|
|
|
if (flash_partition_info(fd, &info) < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
buf = malloc(info.blocksize);
|
|
if (!buf)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
crc = 0xffffffff;
|
|
off = offsetof(struct nxboot_img_header, crc) + sizeof crc;
|
|
remain = header->size + header->header_size - off;
|
|
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
|
|
total_size = remain;
|
|
#endif
|
|
while (remain > 0)
|
|
{
|
|
readsiz = remain > info.blocksize ? info.blocksize : remain;
|
|
if (flash_partition_read(fd, buf, readsiz, off) != 0)
|
|
{
|
|
free(buf);
|
|
return 0xffffffff;
|
|
}
|
|
|
|
off += readsiz;
|
|
remain -= readsiz;
|
|
crc = crc32part((uint8_t *)buf, readsiz, crc);
|
|
if ((remain % 25) == 0)
|
|
{
|
|
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
|
|
nxboot_progress(nxboot_progress_percent,
|
|
((total_size - remain) * 100) / total_size);
|
|
#else
|
|
nxboot_progress(nxboot_progress_dot);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
return ~crc;
|
|
}
|
|
|
|
static int copy_partition(int from, int where, struct nxboot_state *state,
|
|
bool update)
|
|
{
|
|
struct nxboot_img_header header;
|
|
struct flash_partition_info info_from;
|
|
struct flash_partition_info info_where;
|
|
uint32_t magic;
|
|
int readsiz;
|
|
int remain;
|
|
int blocksize;
|
|
off_t off;
|
|
char *buf;
|
|
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
|
|
int total_size;
|
|
#endif
|
|
|
|
get_image_header(from, &header);
|
|
|
|
if (flash_partition_info(from, &info_from) < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
if (flash_partition_info(where, &info_where) < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
remain = header.size + header.header_size;
|
|
if (remain > info_where.size)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
|
|
total_size = remain;
|
|
#endif
|
|
blocksize = info_where.blocksize;
|
|
|
|
buf = malloc(blocksize);
|
|
if (!buf)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
/* Flip header's magic. We go from standard to internal in case of
|
|
* image update and from internal to standard in case of recovery
|
|
* creation or revert operation.
|
|
*/
|
|
|
|
magic = IS_INTERNAL_MAGIC(header.magic) ?
|
|
NXBOOT_HEADER_MAGIC : NXBOOT_HEADER_MAGIC_INT;
|
|
|
|
if (update)
|
|
{
|
|
/* This is update operation, add pointer to recovery image to the
|
|
* image's magic.
|
|
*/
|
|
|
|
magic |= state->update;
|
|
}
|
|
|
|
if (flash_partition_read(from, buf, blocksize, 0) < 0)
|
|
{
|
|
free(buf);
|
|
return ERROR;
|
|
}
|
|
|
|
memcpy(buf + offsetof(struct nxboot_img_header, magic), &magic,
|
|
sizeof magic);
|
|
if (flash_partition_write(where, buf, blocksize, 0) < 0)
|
|
{
|
|
free(buf);
|
|
return ERROR;
|
|
}
|
|
|
|
off = blocksize;
|
|
remain -= blocksize;
|
|
|
|
while (remain > 0)
|
|
{
|
|
readsiz = remain > blocksize ? blocksize : remain;
|
|
if (flash_partition_read(from, buf, readsiz, off) < 0)
|
|
{
|
|
free(buf);
|
|
return ERROR;
|
|
}
|
|
|
|
if (flash_partition_write(where, buf, readsiz, off) < 0)
|
|
{
|
|
free(buf);
|
|
return ERROR;
|
|
}
|
|
|
|
off += readsiz;
|
|
remain -= readsiz;
|
|
if ((remain % 25) == 0)
|
|
{
|
|
#ifdef CONFIG_NXBOOT_PRINTF_PROGRESS_PERCENT
|
|
nxboot_progress(nxboot_progress_percent,
|
|
100 - ((100 * remain) / total_size));
|
|
#else
|
|
nxboot_progress(nxboot_progress_dot);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
return OK;
|
|
}
|
|
|
|
static bool validate_image(int fd)
|
|
{
|
|
struct nxboot_img_header header;
|
|
|
|
get_image_header(fd, &header);
|
|
if (!validate_image_header(&header))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
syslog(LOG_INFO, "Validating image.\n");
|
|
return calculate_crc(fd, &header) == header.crc;
|
|
}
|
|
|
|
static bool compare_versions(struct nxboot_img_version *v1,
|
|
struct nxboot_img_version *v2)
|
|
{
|
|
#ifndef CONFIG_NXBOOT_PREVENT_DOWNGRADE
|
|
int i;
|
|
if (v1->major != v2->major ||
|
|
v1->minor != v2->minor ||
|
|
v1->patch != v2->patch)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < NXBOOT_HEADER_PRERELEASE_MAXLEN; i++)
|
|
{
|
|
if (v1->pre_release[i] != v2->pre_release[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
if (v1->major > v2->major ||
|
|
v1->minor > v2->minor ||
|
|
v1->patch > v2->patch)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (v1->major == v2->major &&
|
|
v1->minor == v2->minor &&
|
|
v1->patch == v2->patch)
|
|
{
|
|
/* TODO: compare prerelease */
|
|
}
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static enum nxboot_update_type
|
|
get_update_type(struct nxboot_state *state,
|
|
int primary, int update, int recovery,
|
|
struct nxboot_img_header *primary_header,
|
|
struct nxboot_img_header *update_header,
|
|
struct nxboot_img_header *recovery_header)
|
|
{
|
|
nxboot_progress(nxboot_progress_start, validate_primary);
|
|
bool primary_valid = validate_image(primary);
|
|
nxboot_progress(nxboot_progress_end);
|
|
|
|
nxboot_progress(nxboot_progress_start, validate_update);
|
|
if (update_header->magic == NXBOOT_HEADER_MAGIC && validate_image(update))
|
|
{
|
|
if (primary_header->crc != update_header->crc ||
|
|
!compare_versions(&primary_header->img_version,
|
|
&update_header->img_version) || !primary_valid)
|
|
{
|
|
nxboot_progress(nxboot_progress_end);
|
|
return NXBOOT_UPDATE_TYPE_UPDATE;
|
|
}
|
|
|
|
flash_partition_erase_first_sector(update);
|
|
}
|
|
|
|
nxboot_progress(nxboot_progress_end);
|
|
|
|
if (IS_INTERNAL_MAGIC(recovery_header->magic) && state->recovery_valid &&
|
|
((IS_INTERNAL_MAGIC(primary_header->magic) &&
|
|
!state->primary_confirmed) || !primary_valid))
|
|
{
|
|
return NXBOOT_UPDATE_TYPE_REVERT;
|
|
}
|
|
|
|
return NXBOOT_UPDATE_TYPE_NONE;
|
|
}
|
|
|
|
static int perform_update(struct nxboot_state *state, bool check_only)
|
|
{
|
|
int successful;
|
|
int update;
|
|
int recovery;
|
|
int primary;
|
|
int secondary;
|
|
int tertiary;
|
|
bool primary_valid;
|
|
|
|
primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH);
|
|
if (primary < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
secondary = flash_partition_open(CONFIG_NXBOOT_SECONDARY_SLOT_PATH);
|
|
if (secondary < 0)
|
|
{
|
|
flash_partition_close(primary);
|
|
return ERROR;
|
|
}
|
|
|
|
tertiary = flash_partition_open(CONFIG_NXBOOT_TERTIARY_SLOT_PATH);
|
|
if (tertiary < 0)
|
|
{
|
|
flash_partition_close(primary);
|
|
flash_partition_close(secondary);
|
|
return ERROR;
|
|
}
|
|
|
|
if (state->update == NXBOOT_SECONDARY_SLOT_NUM)
|
|
{
|
|
update = secondary;
|
|
recovery = tertiary;
|
|
}
|
|
else
|
|
{
|
|
update = tertiary;
|
|
recovery = secondary;
|
|
}
|
|
|
|
nxboot_progress(nxboot_progress_start, validate_primary);
|
|
if (state->next_boot == NXBOOT_UPDATE_TYPE_REVERT &&
|
|
(!check_only || !validate_image(primary)))
|
|
{
|
|
nxboot_progress(nxboot_progress_end);
|
|
if (state->recovery_valid)
|
|
{
|
|
syslog(LOG_INFO, "Reverting image to recovery.\n");
|
|
nxboot_progress(nxboot_progress_start, recovery_revert);
|
|
copy_partition(recovery, primary, state, false);
|
|
nxboot_progress(nxboot_progress_end);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nxboot_progress(nxboot_progress_end);
|
|
nxboot_progress(nxboot_progress_start, validate_primary);
|
|
primary_valid = validate_image(primary);
|
|
nxboot_progress(nxboot_progress_end);
|
|
if (primary_valid && check_only)
|
|
{
|
|
/* Skip if primary image is valid (does not mather whether
|
|
* confirmed or not) and check_only option is set.
|
|
*/
|
|
|
|
goto perform_update_done;
|
|
}
|
|
|
|
if ((!state->recovery_present || !state->recovery_valid) &&
|
|
state->primary_confirmed && primary_valid)
|
|
{
|
|
/* Save current image as recovery only if it is valid and
|
|
* confirmed. We have to check this in case of restart
|
|
* during update process.
|
|
* If board is restarted during update, primary slot contains
|
|
* non-valid image and we do not want to copy this image to
|
|
* recovery slot.
|
|
* There also might be a case where primary image is valid
|
|
* but not confirmed (timing of board reset right after
|
|
* update is uploaded to secondary). Still we do not want
|
|
* to upload this to recovery.
|
|
*/
|
|
|
|
syslog(LOG_INFO, "Creating recovery image.\n");
|
|
nxboot_progress(nxboot_progress_start, recovery_create);
|
|
copy_partition(primary, recovery, state, false);
|
|
nxboot_progress(nxboot_progress_end);
|
|
nxboot_progress(nxboot_progress_start, validate_recovery);
|
|
successful = validate_image(recovery);
|
|
nxboot_progress(nxboot_progress_end);
|
|
if (!successful)
|
|
{
|
|
syslog(LOG_INFO,
|
|
"New recovery is not valid,stop update.\n");
|
|
nxboot_progress(nxboot_info, recovery_invalid);
|
|
goto perform_update_done;
|
|
}
|
|
|
|
syslog(LOG_INFO, "Recovery image created.\n");
|
|
nxboot_progress(nxboot_info, recovery_created);
|
|
}
|
|
|
|
nxboot_progress(nxboot_progress_start, validate_update);
|
|
successful = validate_image(update);
|
|
nxboot_progress(nxboot_progress_end);
|
|
if (successful)
|
|
{
|
|
/* Perform update only if update slot contains valid image. */
|
|
|
|
syslog(LOG_INFO, "Updating from update image.\n");
|
|
nxboot_progress(nxboot_progress_start, update_from_update);
|
|
if (copy_partition(update, primary, state, true) >= 0)
|
|
{
|
|
/* Erase the first sector of update partition. This marks the
|
|
* partition as updated so we don't end up in an update loop.
|
|
* The sector is written back again during the image
|
|
* confirmation.
|
|
*/
|
|
|
|
flash_partition_erase_first_sector(update);
|
|
}
|
|
|
|
nxboot_progress(nxboot_progress_end);
|
|
}
|
|
}
|
|
|
|
perform_update_done:
|
|
flash_partition_close(primary);
|
|
flash_partition_close(secondary);
|
|
flash_partition_close(tertiary);
|
|
return OK;
|
|
}
|
|
|
|
#ifdef CONFIG_NXBOOT_COPY_TO_RAM
|
|
int nxboot_ramcopy(void)
|
|
{
|
|
int ret = OK;
|
|
int primary;
|
|
struct nxboot_img_header header;
|
|
ssize_t bytes;
|
|
static uint8_t *buf;
|
|
|
|
primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH);
|
|
if (primary < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
get_image_header(primary, &header);
|
|
buf = malloc(header.size);
|
|
if (!buf)
|
|
{
|
|
ret = ERROR;
|
|
goto exit_with_error;
|
|
}
|
|
|
|
bytes = pread(primary, buf, header.size, header.header_size);
|
|
if (bytes != header.size)
|
|
{
|
|
ret = ERROR;
|
|
goto exit_with_error;
|
|
}
|
|
|
|
memcpy((uint32_t *)CONFIG_NXBOOT_RAMSTART, buf, header.size);
|
|
|
|
exit_with_error:
|
|
flash_partition_close(primary);
|
|
free(buf);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: nxboot_get_state
|
|
*
|
|
* Description:
|
|
* Gets the current bootloader state and stores it in the nxboot_state
|
|
* structure passed as an argument. This function may be used to determine
|
|
* which slot is update slot and where should application save incoming
|
|
* firmware.
|
|
*
|
|
* Input parameters:
|
|
* state: The pointer to nxboot_state structure. The state is stored here.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, -1 and sets errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxboot_get_state(struct nxboot_state *state)
|
|
{
|
|
int primary;
|
|
int secondary;
|
|
int tertiary;
|
|
int update;
|
|
int recovery;
|
|
int recovery_pointer;
|
|
struct nxboot_img_header primary_header;
|
|
struct nxboot_img_header secondary_header;
|
|
struct nxboot_img_header tertiary_header;
|
|
struct nxboot_img_header *update_header;
|
|
struct nxboot_img_header *recovery_header;
|
|
|
|
memset(state, 0, sizeof *state);
|
|
|
|
primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH);
|
|
if (primary < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
secondary = flash_partition_open(CONFIG_NXBOOT_SECONDARY_SLOT_PATH);
|
|
if (secondary < 0)
|
|
{
|
|
flash_partition_close(primary);
|
|
return ERROR;
|
|
}
|
|
|
|
tertiary = flash_partition_open(CONFIG_NXBOOT_TERTIARY_SLOT_PATH);
|
|
if (tertiary < 0)
|
|
{
|
|
flash_partition_close(primary);
|
|
flash_partition_close(secondary);
|
|
return ERROR;
|
|
}
|
|
|
|
get_image_header(primary, &primary_header);
|
|
get_image_header(secondary, &secondary_header);
|
|
get_image_header(tertiary, &tertiary_header);
|
|
|
|
/* Determine which partition is for update and which is a recovery.
|
|
* This depends on many factors, but in general a partition with
|
|
* NXBOOT_HEADER_MAGIC is an update partition and partition with
|
|
* NXBOOT_HEADER_MAGIC_INT is a recovery.
|
|
*
|
|
* A special case is when both partitions have NXBOOT_HEADER_MAGIC_INT,
|
|
* then we look into a recovery pointer in primary header magic and
|
|
* determine the recovery from it.
|
|
*/
|
|
|
|
update = secondary;
|
|
recovery = tertiary;
|
|
update_header = &secondary_header;
|
|
recovery_header = &tertiary_header;
|
|
state->update = NXBOOT_SECONDARY_SLOT_NUM;
|
|
state->recovery = NXBOOT_TERTIARY_SLOT_NUM;
|
|
|
|
if (tertiary_header.magic == NXBOOT_HEADER_MAGIC)
|
|
{
|
|
update = tertiary;
|
|
recovery = secondary;
|
|
update_header = &tertiary_header;
|
|
recovery_header = &secondary_header;
|
|
state->recovery = NXBOOT_SECONDARY_SLOT_NUM;
|
|
state->update = NXBOOT_TERTIARY_SLOT_NUM;
|
|
}
|
|
else if (IS_INTERNAL_MAGIC(secondary_header.magic) &&
|
|
IS_INTERNAL_MAGIC(tertiary_header.magic))
|
|
{
|
|
if (IS_INTERNAL_MAGIC(primary_header.magic))
|
|
{
|
|
recovery_pointer = primary_header.magic & NXBOOT_RECOVERY_PTR_MASK;
|
|
if (recovery_pointer == NXBOOT_SECONDARY_SLOT_NUM &&
|
|
primary_header.crc == secondary_header.crc)
|
|
{
|
|
update = tertiary;
|
|
recovery = secondary;
|
|
update_header = &tertiary_header;
|
|
recovery_header = &secondary_header;
|
|
state->recovery = NXBOOT_SECONDARY_SLOT_NUM;
|
|
state->update = NXBOOT_TERTIARY_SLOT_NUM;
|
|
}
|
|
}
|
|
else if (primary_header.crc == secondary_header.crc)
|
|
{
|
|
update = tertiary;
|
|
recovery = secondary;
|
|
update_header = &tertiary_header;
|
|
recovery_header = &secondary_header;
|
|
state->recovery = NXBOOT_SECONDARY_SLOT_NUM;
|
|
state->update = NXBOOT_TERTIARY_SLOT_NUM;
|
|
}
|
|
}
|
|
else if (IS_INTERNAL_MAGIC(secondary_header.magic))
|
|
{
|
|
update = tertiary;
|
|
recovery = secondary;
|
|
update_header = &tertiary_header;
|
|
recovery_header = &secondary_header;
|
|
state->recovery = NXBOOT_SECONDARY_SLOT_NUM;
|
|
state->update = NXBOOT_TERTIARY_SLOT_NUM;
|
|
}
|
|
|
|
nxboot_progress(nxboot_progress_start, validate_recovery);
|
|
state->recovery_valid = validate_image(recovery);
|
|
nxboot_progress(nxboot_progress_end);
|
|
state->recovery_present = primary_header.crc == recovery_header->crc;
|
|
|
|
/* The image is confirmed if it has either NXBOOT_HEADER_MAGIC or a
|
|
* recovery exists.
|
|
*/
|
|
|
|
if (primary_header.magic == NXBOOT_HEADER_MAGIC)
|
|
{
|
|
state->primary_confirmed = true;
|
|
}
|
|
else if (IS_INTERNAL_MAGIC(primary_header.magic))
|
|
{
|
|
recovery_pointer = primary_header.magic & NXBOOT_RECOVERY_PTR_MASK;
|
|
if (recovery_pointer == NXBOOT_SECONDARY_SLOT_NUM &&
|
|
IS_INTERNAL_MAGIC(secondary_header.magic))
|
|
{
|
|
state->primary_confirmed =
|
|
primary_header.crc == secondary_header.crc;
|
|
}
|
|
else if (recovery_pointer == NXBOOT_TERTIARY_SLOT_NUM &&
|
|
IS_INTERNAL_MAGIC(tertiary_header.magic))
|
|
{
|
|
state->primary_confirmed =
|
|
primary_header.crc == tertiary_header.crc;
|
|
}
|
|
}
|
|
|
|
state->next_boot = get_update_type(state, primary, update, recovery,
|
|
&primary_header, update_header,
|
|
recovery_header);
|
|
|
|
flash_partition_close(primary);
|
|
flash_partition_close(secondary);
|
|
flash_partition_close(tertiary);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxboot_open_update_partition
|
|
*
|
|
* Description:
|
|
* Gets the current bootloader state and opens the partition to which an
|
|
* update image should be stored. It returns the valid file descriptor to
|
|
* this partition, the user is responsible for writing to it and closing
|
|
* if afterwards.
|
|
*
|
|
* Returned Value:
|
|
* Valid file descriptor on success, -1 and sets errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxboot_open_update_partition(void)
|
|
{
|
|
char *path;
|
|
struct nxboot_state state;
|
|
|
|
nxboot_get_state(&state);
|
|
|
|
path = state.update == NXBOOT_SECONDARY_SLOT_NUM ?
|
|
CONFIG_NXBOOT_SECONDARY_SLOT_PATH : CONFIG_NXBOOT_TERTIARY_SLOT_PATH;
|
|
|
|
return flash_partition_open(path);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxboot_get_confirm
|
|
*
|
|
* Description:
|
|
* This function can be used to determine whether primary image is
|
|
* confirmed or not. This provides more direct access to confirm
|
|
* state compared to nxboot_get_state function that returns the full
|
|
* state of the bootloader.
|
|
*
|
|
* Returned Value:
|
|
* 1 means confirmed, 0 not confirmed, -1 and sets errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxboot_get_confirm(void)
|
|
{
|
|
int primary;
|
|
int recovery;
|
|
int recovery_pointer;
|
|
char *path;
|
|
int ret = OK;
|
|
struct nxboot_img_header primary_header;
|
|
struct nxboot_img_header recovery_header;
|
|
|
|
primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH);
|
|
if (primary < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
get_image_header(primary, &primary_header);
|
|
|
|
if (primary_header.magic == NXBOOT_HEADER_MAGIC)
|
|
{
|
|
close(primary);
|
|
return 1;
|
|
}
|
|
else if (IS_INTERNAL_MAGIC(primary_header.magic))
|
|
{
|
|
recovery_pointer = primary_header.magic & NXBOOT_RECOVERY_PTR_MASK;
|
|
if (recovery_pointer != 0)
|
|
{
|
|
path = recovery_pointer == NXBOOT_SECONDARY_SLOT_NUM ?
|
|
CONFIG_NXBOOT_SECONDARY_SLOT_PATH :
|
|
CONFIG_NXBOOT_TERTIARY_SLOT_PATH;
|
|
|
|
recovery = flash_partition_open(path);
|
|
if (recovery < 0)
|
|
{
|
|
close(primary);
|
|
return ERROR;
|
|
}
|
|
|
|
get_image_header(recovery, &recovery_header);
|
|
if (primary_header.crc == recovery_header.crc)
|
|
{
|
|
ret = 1;
|
|
}
|
|
|
|
close(recovery);
|
|
}
|
|
}
|
|
|
|
close(primary);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxboot_confirm
|
|
*
|
|
* Description:
|
|
* Confirms the image currently located in primary partition and marks
|
|
* its copy in update partition as a recovery.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, -1 and sets errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxboot_confirm(void)
|
|
{
|
|
int ret;
|
|
int update;
|
|
int primary;
|
|
int remain;
|
|
int readsiz;
|
|
char *path;
|
|
char *buf;
|
|
off_t off;
|
|
struct nxboot_state state;
|
|
struct flash_partition_info info_update;
|
|
|
|
ret = OK;
|
|
nxboot_get_state(&state);
|
|
if (state.primary_confirmed)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
path = state.update == NXBOOT_SECONDARY_SLOT_NUM ?
|
|
CONFIG_NXBOOT_SECONDARY_SLOT_PATH : CONFIG_NXBOOT_TERTIARY_SLOT_PATH;
|
|
|
|
primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH);
|
|
if (primary < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
update = flash_partition_open(path);
|
|
if (update < 0)
|
|
{
|
|
flash_partition_close(primary);
|
|
return ERROR;
|
|
}
|
|
|
|
/* Confirm the image by creating a recovery. The recovery image is
|
|
* already present in the update slot, but without the first erase
|
|
* page. Therefore, just copy the first erase page to the update slot.
|
|
*/
|
|
|
|
if (flash_partition_info(update, &info_update) < 0)
|
|
{
|
|
ret = ERROR;
|
|
goto confirm_done;
|
|
}
|
|
|
|
/* Write by write pages to avoid large array buffering. */
|
|
|
|
buf = malloc(info_update.blocksize);
|
|
remain = info_update.erasesize;
|
|
off = 0;
|
|
|
|
while (remain > 0)
|
|
{
|
|
readsiz = remain > info_update.blocksize ?
|
|
info_update.blocksize : remain;
|
|
if (flash_partition_read(primary, buf, readsiz, off) < 0)
|
|
{
|
|
free(buf);
|
|
ret = ERROR;
|
|
goto confirm_done;
|
|
}
|
|
|
|
if (flash_partition_write(update, buf, readsiz, off) < 0)
|
|
{
|
|
free(buf);
|
|
ret = ERROR;
|
|
goto confirm_done;
|
|
}
|
|
|
|
off += readsiz;
|
|
remain -= readsiz;
|
|
}
|
|
|
|
free(buf);
|
|
|
|
confirm_done:
|
|
flash_partition_close(primary);
|
|
flash_partition_close(update);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxboot_perform_update
|
|
*
|
|
* Description:
|
|
* Checks for the possible firmware update and performs it by copying
|
|
* update image to primary slot or recovery image to primary slot in case
|
|
* of the revert. In any situation, this function ends with the valid
|
|
* image in primary slot.
|
|
*
|
|
* This is an entry point function that should be called from the
|
|
* bootloader application.
|
|
*
|
|
* Input parameters:
|
|
* check_only: Only repairs corrupted update, but do not start another one
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, -1 and sets errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxboot_perform_update(bool check_only)
|
|
{
|
|
int ret;
|
|
int primary;
|
|
struct nxboot_state state;
|
|
struct nxboot_img_header header;
|
|
|
|
ret = nxboot_get_state(&state);
|
|
if (ret < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
if (state.next_boot != NXBOOT_UPDATE_TYPE_NONE)
|
|
{
|
|
/* We either want to update or revert. */
|
|
|
|
ret = perform_update(&state, check_only);
|
|
if (ret < 0)
|
|
{
|
|
/* Update process failed, raise error and try to boot into
|
|
* primary.
|
|
*/
|
|
|
|
syslog(LOG_ERR, "Update process failed: %s\n", strerror(errno));
|
|
nxboot_progress(nxboot_error, update_failed);
|
|
}
|
|
}
|
|
|
|
/* Check whether there is a valid image in the primary slot. This just
|
|
* checks whether the header is valid, but does not calculate the CRC
|
|
* of the image as this would prolong the boot process.
|
|
*/
|
|
|
|
primary = flash_partition_open(CONFIG_NXBOOT_PRIMARY_SLOT_PATH);
|
|
if (primary < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
get_image_header(primary, &header);
|
|
if (!validate_image_header(&header))
|
|
{
|
|
ret = ERROR;
|
|
}
|
|
|
|
flash_partition_close(primary);
|
|
|
|
return ret;
|
|
}
|
|
|