Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read bootloader ESP-IDF version from device (IDFGH-7206) #8800

Closed
BoraBene opened this issue Apr 20, 2022 · 8 comments
Closed

Read bootloader ESP-IDF version from device (IDFGH-7206) #8800

BoraBene opened this issue Apr 20, 2022 · 8 comments
Assignees
Labels
Resolution: NA Issue resolution is unavailable Status: Done Issue is done internally Type: Feature Request Feature request for IDF

Comments

@BoraBene
Copy link

BoraBene commented Apr 20, 2022

Hi,

i tried to read the ESP-IDF version of the bootloader from the nvs. Sadly only the ESP-IDF version of the firmware is readable while using the application. The bootloader ESP-IDF is only mentioned once while starting the serial monitor. I used different ESP-IDF versions ( mentioned in the images) to proof if the firmware or the bootloader ESP-IDF is given back by the function esp_get_idf_version() .

bootloader ESP-IDF:

image

Firmware ESP-IDF:

image

It would be nice to also read the ESP-IDF version of the bootloader from the nvs, so i can see which of my device uses a old version of the bootloader SDK.

@BoraBene BoraBene added the Type: Feature Request Feature request for IDF label Apr 20, 2022
@espressif-bot espressif-bot added the Status: Opened Issue is new label Apr 20, 2022
@github-actions github-actions bot changed the title Read bootloader ESP-IDF version from device Read bootloader ESP-IDF version from device (IDFGH-7206) Apr 20, 2022
@KonstantinKondrashov
Copy link
Collaborator

Hi @BoraBene!
Yes, I agree it might be useful. Seems like we need to add a similar structure in bootloader https://github.com/espressif/esp-idf/blob/master/components/app_update/esp_app_desc.c#L14-L42 and then we will know from which offset we need to read bootloader to extract info about bootloader. The new func like esp_ota_get_bootloader_description() will return this structure.

@chipweinberger
Copy link
Contributor

chipweinberger commented Apr 22, 2022

Yes i've wanted this feature too.

I resorted to using a custom bootloader that writes the esp-idf version to its own partition (and compile date) during boot. Not ideal! but it works. Then i can access that partition from user space.

@BoraBene
Copy link
Author

Thanks, maybe i could use it as a workaround right now.
Hopefully Espressif is including this in the future, would be a lot easier! :)

@chipweinberger
Copy link
Contributor

chipweinberger commented Apr 22, 2022

This might help you.

I write in a simple format:

  • "JSON" (magic number)
  • len (4 bytes, json string length, includes null terminator)
  • json (the json string itself)
bootloader_ver.h

void jboot_write_version_if_needed();

bootloader_ver.c

#include <stdbool.h>
#include "string.h"

#include <esp_err.h>
#include "esp_log.h"
#include "esp_idf_version.h"

#include "bootloader_init.h"
#include "bootloader_utility.h"
#include "bootloader_common.h"

#include "jcommon/system/pd_partition_defines.h"
#include "jcommon/peripherals/pd_gpio.h"

#include "hal/gpio_ll.h"
#include "esp_rom_gpio.h"

#include "bootloader_utils.h"


#include "bootloader_ver.h"

// forward declare
// these are found in "bootloader_flash_priv.h"
#define FLASH_SECTOR_SIZE 0x1000
esp_err_t bootloader_flash_read(size_t src_addr, void *dest, size_t size, bool allow_decrypt);
esp_err_t bootloader_flash_write(size_t dest_addr, void *src, size_t size, bool write_encrypted);
esp_err_t bootloader_flash_erase_sector(size_t sector);

static const char *TAG = "boot ver";

static uint32_t sentinelExpected =  0x4A534F4E; // "JSON" in hex

static uint32_t fourByteAligned(uint32_t a){
    return (a + 3) & ~0x03;
}

// prepend ESP-IDF
static uint32_t expectedBootVer(char* buf, uint32_t len) {

    // len must be 4 byte aligned
    if (len != fourByteAligned(len)){
        abort();
    }

    const char* a = "{";
    const char* b = "\"jBootVer\":\"0.1\"";
    const char* c = ",";
    const char* d = "\"compileDate\":\"" __DATE__ "\"";
    const char* e = ",";
    const char* f = "\"compileTime\":\"" __TIME__ "\"";
    const char* g = ",";
    const char* h = "\"ESP-IDF\":\"" IDF_VER "\"";
    const char* i = "}";
        
    int wrote = 0;
    strncpy(buf+wrote, a, len-wrote); wrote+=strlen(a);
    strncpy(buf+wrote, b, len-wrote); wrote+=strlen(b);
    strncpy(buf+wrote, c, len-wrote); wrote+=strlen(c);
    strncpy(buf+wrote, d, len-wrote); wrote+=strlen(d);
    strncpy(buf+wrote, e, len-wrote); wrote+=strlen(e);
    strncpy(buf+wrote, f, len-wrote); wrote+=strlen(f);
    strncpy(buf+wrote, g, len-wrote); wrote+=strlen(g);
    strncpy(buf+wrote, h, len-wrote); wrote+=strlen(h);
    strncpy(buf+wrote, i, len-wrote); wrote+=strlen(i);

    // include null terminator
    return fourByteAligned(wrote + 1);
}
          
static void read_jboot_ver(char* idfVer, uint32_t bufLen, uint32_t lengthExpected)
{
    ESP_LOGI(TAG, "read json");

    esp_err_t err;

    esp_partition_pos_t pos;

    bool found = findPartition(&pos, PD_PARTITION_JBOOT_VER_NAME);
    if (found == false) {
        ESP_LOGE(TAG, "%s partition not found", PD_PARTITION_JBOOT_VER_NAME);
        return;
    }

    // read sentinel
    uint32_t sentinel = 0;
    err = bootloader_flash_read(pos.offset, &sentinel, sizeof(uint32_t), false);
    if (err != ESP_OK){
        ESP_LOGE(TAG, "bootloader_flash_read() sentinel failed");
        return;
    }

    // did we find sentinel
    if (sentinel != sentinelExpected){
        ESP_LOGE(TAG, "sentinel not found.");
        return;
    }

    // read length
    uint32_t length = 0;
    err = bootloader_flash_read(pos.offset + sizeof(uint32_t), &length, sizeof(uint32_t), false);
    if (err != ESP_OK){
        ESP_LOGE(TAG, "bootloader_flash_read() sentinel failed");
        return;
    }

    // check length
    if (length != lengthExpected){
        ESP_LOGE(TAG, "length (%u) != lengthExpected (%u)", length, lengthExpected);
        return;
    }

    // check buf size
    if (length > bufLen){
        ESP_LOGE(TAG, "read: buffer too short. %u > %u", length + 1, bufLen);
        return;
    }

    // read string
    err = bootloader_flash_read(pos.offset + (sizeof(uint32_t)*2), idfVer,bufLen, false);
    if (err != ESP_OK){
        ESP_LOGE(TAG, "bootloader_flash_read() string failed");
        return;
    }

    // make sure null terminated
    idfVer[length] = 0;

    ESP_LOGI(TAG, "read %s", idfVer);
}


static void write_jboot_ver(char* idfVer, uint32_t len)
{
    ESP_LOGW(TAG, "need to write idf version");

    esp_err_t err;

    esp_partition_pos_t pos;

    bool found = findPartition(&pos, PD_PARTITION_JBOOT_VER_NAME);
    if (found == false) {
        ESP_LOGE(TAG, "%s partition not found", PD_PARTITION_JBOOT_VER_NAME);
        return;
    }

    // check json buff is not too long
    if (pos.size < len) {
        ESP_LOGE(TAG, "%s partition too small. %u < %u", 
            PD_PARTITION_JBOOT_VER_NAME, pos.size, len);
        return;
    }

    // must erase first
    err = bootloader_flash_erase_sector(pos.offset / FLASH_SECTOR_SIZE);
    if (err != ESP_OK){
        ESP_LOGE(TAG, "bootloader_flash_erase_sector() failed");
        return;
    }

    // write string
    err = bootloader_flash_write(pos.offset + (sizeof(uint32_t)*2), idfVer, len, false);
    if (err != ESP_OK){
        ESP_LOGE(TAG, "bootloader_flash_write() string failed");
        return;
    }

    // write length
    err = bootloader_flash_write(pos.offset + sizeof(uint32_t), &len, sizeof(uint32_t), false);
    if (err != ESP_OK){
        ESP_LOGE(TAG, "bootloader_flash_write() length failed");
        return;
    }

    // write sentinel
    err = bootloader_flash_write(pos.offset, &sentinelExpected, sizeof(uint32_t), false);
    if (err != ESP_OK){
        ESP_LOGE(TAG, "bootloader_flash_write() sentinel failed");
        return;
    }

    ESP_LOGI(TAG, "write: JSON%u%s", len, idfVer);
}

void jboot_write_version_if_needed()
{
    char expected[256];
    memset(expected, 0, sizeof(expected));
    int len = expectedBootVer(expected, sizeof(expected));

    char idfVer[256];
    memset(idfVer, 0, sizeof(idfVer));
    read_jboot_ver(idfVer, sizeof(idfVer), len);

    // check if not written yet
    if(strstr(idfVer, expected) == NULL){
        write_jboot_ver(expected, len);
    } else {
        ESP_LOGI(TAG, "jBootVer already written");
    }
}
bootloader_utils.c

#include <stdbool.h>
#include <esp_err.h>
#include "esp_log.h"
#include "string.h"

#include "bootloader_init.h"
#include "bootloader_utility.h"
#include "bootloader_common.h"

// forward declare
// these are found in "bootloader_flash_priv.h"
const void *bootloader_mmap(uint32_t src_addr, uint32_t size);
void bootloader_munmap(const void *mapping);

static const char* TAG = "boot";

bool findPartition(esp_partition_pos_t* posOut, const char* findLabel)
{
    esp_err_t err;

    const esp_partition_info_t *partitions;
    partitions = bootloader_mmap(ESP_PARTITION_TABLE_OFFSET, ESP_PARTITION_TABLE_MAX_LEN);
    if (!partitions) {
        ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", ESP_PARTITION_TABLE_OFFSET, ESP_PARTITION_TABLE_MAX_LEN);
        return false;
    }

    int num_partitions;
    err = esp_partition_table_verify(partitions, true, &num_partitions);

    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to verify partition table");
        bootloader_munmap(partitions);
        return false;
    }

    bool found = false;

    for (int i = 0; i < num_partitions; i++) {

        const esp_partition_info_t *partition = &partitions[i];

        char label[sizeof(partition->label) + 1] = {0};

        if (partition->type == PART_TYPE_DATA) {

            // partition->label is not null-terminated string.
            strncpy(label, (char *)&partition->label, sizeof(label) - 1);

            if (bootloader_common_label_search(findLabel, label) == true) {

                posOut->offset = partition->pos.offset;
                posOut->size = partition->pos.size;

                ESP_LOGI(TAG, "found: %s, offset:0x%x size:%u", findLabel, posOut->offset,  posOut->size);
                found = true;
            } 
        }
    }

    bootloader_munmap(partitions);

    return found;
}

@espressif-bot espressif-bot added Status: In Progress Work is in progress and removed Status: Opened Issue is new labels Dec 13, 2022
@espressif-bot espressif-bot added Status: Selected for Development Issue is selected for development and removed Status: In Progress Work is in progress labels Mar 29, 2023
@espressif-bot espressif-bot added Status: Done Issue is done internally Resolution: NA Issue resolution is unavailable and removed Status: Selected for Development Issue is selected for development labels May 15, 2023
@chipweinberger
Copy link
Contributor

wow nice work @KonstantinKondrashov , looks perfect ❤️

I'm glad you guys found this important enough to do such a good job implementing it.

Its very useful to be able to read the bootloader version programatically!

typedef struct {
    uint8_t magic_byte;         /*!< Magic byte ESP_BOOTLOADER_DESC_MAGIC_BYTE */
    uint8_t reserved[3];        /*!< reserved for IDF */
    uint32_t version;           /*!< Bootloader version */
    char idf_ver[32];           /*!< Version IDF */
    char date_time[24];         /*!< Compile date and time*/
    uint8_t reserved2[16];      /*!< reserved for IDF */
} esp_bootloader_desc_t;
/**
 * @brief Returns the description structure of the bootloader.
 *
 * @param[in] bootloader_partition Pointer to bootloader partition.
 *                                 If NULL, then the current bootloader is used (the default location).
 *                                 offset = CONFIG_BOOTLOADER_OFFSET_IN_FLASH,
 *                                 size = CONFIG_PARTITION_TABLE_OFFSET - CONFIG_BOOTLOADER_OFFSET_IN_FLASH,
 * @param[out] desc     Structure of info about bootloader.
 * @return
 *  - ESP_OK                Successful.
 *  - ESP_ERR_NOT_FOUND     Description structure is not found in the bootloader image. Magic byte is incorrect.
 *  - ESP_ERR_INVALID_ARG   Arguments is NULL.
 *  - ESP_ERR_INVALID_SIZE  Read would go out of bounds of the partition.
 *  - or one of error codes from lower-level flash driver.
 */
esp_err_t esp_ota_get_bootloader_description(const esp_partition_t *bootloader_partition, esp_bootloader_desc_t *desc);

@jonnydman
Copy link

I know it's done, but for some reason I can't seem to find it under esp_ota_ops.h. is it implemented in a different place?
I'm using ESP-IDF version 5.1.1

@KaeLL
Copy link
Contributor

KaeLL commented Nov 30, 2023

@jonnydman
image

@jonnydman
Copy link

@KaeLL and when would it be released? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Resolution: NA Issue resolution is unavailable Status: Done Issue is done internally Type: Feature Request Feature request for IDF
Projects
None yet
Development

No branches or pull requests

6 participants