mirror of
https://github.com/apache/nuttx-apps.git
synced 2025-07-06 20:00:45 +08:00

This commit adds NuttX based bootloader with the support for image update and recovery if not confirmed. The algorithm utilizes three flash partitions: primary (image runs from this area), secondary and tertiary. Secondary and tertiary areas are used for update upload and recovery. The update is performed by simple copy from update area to primary area with recovery being created in recovery area if not already present. Once image is confirmed by the user, the image in update area is confirmed as well, update area becomes recovery area and vice versa. This means the recovery is always present (except for the first update) and subsequent updates just copy image from update to primary. This makes the update significantly faster and more considerable to flash wear while keeping the recovery/revert possibility. A header (aligned to flash's erase size) must be added to the beginning of the image. Python script nximage.py can be used to prepend this header to built binary. The algorithm also uses one erase page at the end of a partition (partition, not image!) to store flags used to indicate image confirm status and to detect update/recovery partitions. Any program uploading update image to the update partition has to erase this page for the boot to work correctly! The algorithm implementation is based on a patch initially developed for MCUboot project but rejected by the project's maintainers https://github.com/mcu-tools/mcuboot/pull/1902 Signed-off-by: Michal Lenc <michallenc@seznam.cz>
312 lines
7.8 KiB
C
312 lines
7.8 KiB
C
/****************************************************************************
|
|
* apps/boot/nxboot/loader/flash.c
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <fcntl.h>
|
|
#include <syslog.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/mtd/mtd.h>
|
|
|
|
#include "flash.h"
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: flash_partition_open
|
|
*
|
|
* Description:
|
|
* Opens the partition based on a given name and returns the file
|
|
* descriptor to it.
|
|
*
|
|
* Input parameters:
|
|
* path: Path to the device.
|
|
*
|
|
* Returned Value:
|
|
* Valid file descriptor on success, -1 on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int flash_partition_open(const char *path)
|
|
{
|
|
int fd;
|
|
|
|
fd = open(path, O_RDWR);
|
|
if (fd < 0)
|
|
{
|
|
syslog(LOG_ERR, "Could not open %s partition: %s\n",
|
|
path, strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: flash_partition_close
|
|
*
|
|
* Description:
|
|
* Closes opened partition.
|
|
*
|
|
* Input parameters:
|
|
* fd: Valid file descriptor.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, -1 on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int flash_partition_close(int fd)
|
|
{
|
|
return close(fd);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: flash_partition_write
|
|
*
|
|
* Description:
|
|
* Writes count data pointed to by buf at offset off to a partition
|
|
* referenced by file descriptor fd.
|
|
*
|
|
* Input parameters:
|
|
* fd: Valid file descriptor.
|
|
* buf: The pointer to data to be written.
|
|
* count: Number of bytes to be written.
|
|
* off: Write offset in bytes.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, -1 on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int flash_partition_write(int fd, const void *buf, size_t count, off_t off)
|
|
{
|
|
int ret;
|
|
off_t pos;
|
|
size_t size;
|
|
ssize_t nbytes;
|
|
struct mtd_geometry_s geometry;
|
|
|
|
ret = ioctl(fd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geometry));
|
|
if (ret < 0)
|
|
{
|
|
syslog(LOG_ERR, "ioctl MTDIOC_GEOMETRY failed: %s\n", strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
size = geometry.erasesize * geometry.neraseblocks;
|
|
if (count + off > size)
|
|
{
|
|
syslog(LOG_ERR, "Trying to write outside of flash area.\n");
|
|
return ERROR;
|
|
}
|
|
|
|
pos = lseek(fd, off, SEEK_SET);
|
|
if (pos != off)
|
|
{
|
|
syslog(LOG_ERR, "Could not seek to %ld: %s\n", off, strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
nbytes = write(fd, buf, count);
|
|
if (nbytes != count)
|
|
{
|
|
syslog(LOG_ERR, "Write to offset %ld failed %s\n",
|
|
off, strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: flash_partition_read
|
|
*
|
|
* Description:
|
|
* Read count data to buffer buf at offset off from a partition
|
|
* referenced by file descriptor fd.
|
|
*
|
|
* Input parameters:
|
|
* fd: Valid file descriptor.
|
|
* buf: The pointer where read data are stored.
|
|
* count: Number of bytes to be read.
|
|
* off: Read offset in bytes.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, -1 on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int flash_partition_read(int fd, void *buf, size_t count, off_t off)
|
|
{
|
|
int ret;
|
|
off_t pos;
|
|
size_t size;
|
|
ssize_t nbytes;
|
|
struct mtd_geometry_s geometry;
|
|
|
|
ret = ioctl(fd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geometry));
|
|
if (ret < 0)
|
|
{
|
|
syslog(LOG_ERR, "ioctl MTDIOC_GEOMETRY failed: %s\n", strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
size = geometry.erasesize * geometry.neraseblocks;
|
|
if (count + off > size)
|
|
{
|
|
syslog(LOG_ERR, "Trying to read outside of flash area.\n");
|
|
return ERROR;
|
|
}
|
|
|
|
pos = lseek(fd, off, SEEK_SET);
|
|
if (pos != off)
|
|
{
|
|
syslog(LOG_ERR, "Could not seek to %ld: %s\n", off, strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
nbytes = read(fd, buf, count);
|
|
if (nbytes != count)
|
|
{
|
|
syslog(LOG_ERR, "Read from offset %ld failed %s\n",
|
|
off, strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: flash_partition_erase
|
|
*
|
|
* Description:
|
|
* Erases the entire partition.
|
|
*
|
|
* Input parameters:
|
|
* fd: Valid file descriptor.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, -1 on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int flash_partition_erase(int fd)
|
|
{
|
|
int ret;
|
|
|
|
ret = ioctl(fd, MTDIOC_BULKERASE, 0);
|
|
if (ret < 0)
|
|
{
|
|
syslog(LOG_ERR, "Could not erase the partition: %s\n",
|
|
strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: flash_partition_erase_last_sector
|
|
*
|
|
* Description:
|
|
* Erases the last sector of the partition
|
|
*
|
|
* Input parameters:
|
|
* fd: Valid file descriptor.
|
|
*
|
|
* Returned Value:
|
|
* 0 on success, -1 on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int flash_partition_erase_last_sector(int fd)
|
|
{
|
|
int ret;
|
|
struct mtd_erase_s erase;
|
|
struct flash_partition_info info;
|
|
|
|
if (flash_partition_info(fd, &info) < 0)
|
|
{
|
|
return ERROR;
|
|
}
|
|
|
|
erase.startblock = info.neraseblocks - 1;
|
|
erase.nblocks = 1;
|
|
|
|
ret = ioctl(fd, MTDIOC_ERASESECTORS, &erase);
|
|
if (ret < 0)
|
|
{
|
|
syslog(LOG_ERR, "Could not erase the partition: %s\n",
|
|
strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: flash_partition_info
|
|
*
|
|
* Description:
|
|
* Returns the size of one block.
|
|
*
|
|
* Input parameters:
|
|
* fd: Valid file descriptor.
|
|
* info: Pointer to flash_partition_info structure where info is filled.
|
|
*
|
|
* Returned Value:
|
|
* Size of the block on success, -1 on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int flash_partition_info(int fd, struct flash_partition_info *info)
|
|
{
|
|
int ret;
|
|
struct mtd_geometry_s geometry;
|
|
|
|
ret = ioctl(fd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geometry));
|
|
if (ret < 0)
|
|
{
|
|
syslog(LOG_ERR, "ioctl MTDIOC_GEOMETRY failed: %s\n", strerror(errno));
|
|
return ERROR;
|
|
}
|
|
|
|
info->blocksize = geometry.blocksize;
|
|
info->size = geometry.erasesize * geometry.neraseblocks;
|
|
info->neraseblocks = geometry.neraseblocks;
|
|
|
|
return OK;
|
|
}
|