ESP-IDF OTA机制详解(9)

接前一篇文章:ESP-IDF OTA机制详解(8)

上一回开始对于乐鑫官网例程中OTA代码主要流程中的核心部分——esp_https_ota_perform函数进行解析,讲解了函数的前两段内容。本回继续往下进行解析。为了便于理解和回顾,再次贴出该函数源码,在C:\Espressif\frameworks\esp-idf-v5.2.1\components\esp_https_ota\src\esp_https_ota.c中,如下:

esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle)
{
    esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
    if (handle == NULL) {
        ESP_LOGE(TAG, "esp_https_ota_perform: Invalid argument");
        return ESP_ERR_INVALID_ARG;
    }
    if (handle->state < ESP_HTTPS_OTA_BEGIN) {
        ESP_LOGE(TAG, "esp_https_ota_perform: Invalid state");
        return ESP_FAIL;
    }

    esp_err_t err;
    int data_read;
    const int erase_size = handle->bulk_flash_erase ? OTA_SIZE_UNKNOWN : OTA_WITH_SEQUENTIAL_WRITES;
    switch (handle->state) {
        case ESP_HTTPS_OTA_BEGIN:
            err = esp_ota_begin(handle->update_partition, erase_size, &handle->update_handle);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
                return err;
            }
            handle->state = ESP_HTTPS_OTA_IN_PROGRESS;
            /* In case `esp_https_ota_read_img_desc` was invoked first,
               then the image data read there should be written to OTA partition
               */
            int binary_file_len = 0;
            if (handle->binary_file_len) {
                binary_file_len = handle->binary_file_len;
            } else {
                if (read_header(handle) != ESP_OK) {
                    return ESP_FAIL;
                }
                binary_file_len = IMAGE_HEADER_SIZE;
            }
            /*
            * Header length gets added to handle->binary_file_len in _ota_write
            * Clear handle->binary_file_len to avoid additional bytes in upgrade image size calculation
            */
            handle->binary_file_len = 0;

            const void *data_buf = (const void *) handle->ota_upgrade_buf;
#if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB
            decrypt_cb_arg_t args = {};
            args.data_in = handle->ota_upgrade_buf;
            args.data_in_len = binary_file_len;
            err = esp_https_ota_decrypt_cb(handle, &args);
            if (err == ESP_OK) {
                data_buf = args.data_out;
                binary_file_len = args.data_out_len;
            } else {
                ESP_LOGE(TAG, "Decryption of image header failed");
                return ESP_FAIL;
            }
#endif // CONFIG_ESP_HTTPS_OTA_DECRYPT_CB
            err = esp_ota_verify_chip_id(data_buf);
            if (err != ESP_OK) {
                return err;
            }
            return _ota_write(handle, data_buf, binary_file_len);
        case ESP_HTTPS_OTA_IN_PROGRESS:
            data_read = esp_http_client_read(handle->http_client,
                                             handle->ota_upgrade_buf,
                                             handle->ota_upgrade_buf_size);
            if (data_read == 0) {
                /*
                 *  esp_http_client_is_complete_data_received is added to check whether
                 *  complete image is received.
                 */
                if (!esp_http_client_is_complete_data_received(handle->http_client)) {
                    ESP_LOGE(TAG, "Connection closed before complete data was received!");
                    return ESP_FAIL;
                }
                ESP_LOGD(TAG, "Connection closed");
            } else if (data_read > 0) {
                const void *data_buf = (const void *) handle->ota_upgrade_buf;
                int data_len = data_read;
#if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB
                decrypt_cb_arg_t args = {};
                args.data_in = handle->ota_upgrade_buf;
                args.data_in_len = data_read;
                err = esp_https_ota_decrypt_cb(handle, &args);
                if (err == ESP_OK) {
                    data_buf = args.data_out;
                    data_len = args.data_out_len;
                } else {
                    return err;
                }
#endif // CONFIG_ESP_HTTPS_OTA_DECRYPT_CB
                return _ota_write(handle, data_buf, data_len);
            } else {
                if (data_read == -ESP_ERR_HTTP_EAGAIN) {
                    ESP_LOGD(TAG, "ESP_ERR_HTTP_EAGAIN invoked: Call timed out before data was ready");
                    return ESP_ERR_HTTPS_OTA_IN_PROGRESS;
                }
                ESP_LOGE(TAG, "data read %d, errno %d", data_read, errno);
                return ESP_FAIL;
            }
            if (!handle->partial_http_download || (handle->partial_http_download && handle->image_length == handle->binary_file_len)) {
                handle->state = ESP_HTTPS_OTA_SUCCESS;
            }
            break;
         default:
            ESP_LOGE(TAG, "Invalid ESP HTTPS OTA State");
            return ESP_FAIL;
            break;
    }
    if (handle->partial_http_download) {
        if (handle->state == ESP_HTTPS_OTA_IN_PROGRESS && handle->image_length > handle->binary_file_len) {
            esp_http_client_close(handle->http_client);
            char *header_val = NULL;
            int header_size = 0;
#if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB
            header_size = handle->enc_img_header_size;
#endif
            if ((handle->image_length - handle->binary_file_len) > handle->max_http_request_size) {
                asprintf(&header_val, "bytes=%d-%d", handle->binary_file_len + header_size, (handle->binary_file_len + header_size + handle->max_http_request_size - 1));
            } else {
                asprintf(&header_val, "bytes=%d-", handle->binary_file_len + header_size);
            }
            if (header_val == NULL) {
                ESP_LOGE(TAG, "Failed to allocate memory for HTTP header");
                return ESP_ERR_NO_MEM;
            }
            esp_http_client_set_header(handle->http_client, "Range", header_val);
            free(header_val);
            err = _http_connect(handle);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to establish HTTP connection");
                return ESP_FAIL;
            }
            ESP_LOGD(TAG, "Connection start");
            return ESP_ERR_HTTPS_OTA_IN_PROGRESS;
        }
    }
    return ESP_OK;
}

3)switch中的ESP_HTTPS_OTA_BEGIN

代码片段如下:

    switch (handle->state) {
        case ESP_HTTPS_OTA_BEGIN:
            err = esp_ota_begin(handle->update_partition, erase_size, &handle->update_handle);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
                return err;
            }
            handle->state = ESP_HTTPS_OTA_IN_PROGRESS;
            /* In case `esp_https_ota_read_img_desc` was invoked first,
               then the image data read there should be written to OTA partition
               */
            int binary_file_len = 0;
            if (handle->binary_file_len) {
                binary_file_len = handle->binary_file_len;
            } else {
                if (read_header(handle) != ESP_OK) {
                    return ESP_FAIL;
                }
                binary_file_len = IMAGE_HEADER_SIZE;
            }
            /*
            * Header length gets added to handle->binary_file_len in _ota_write
            * Clear handle->binary_file_len to avoid additional bytes in upgrade image size calculation
            */
            handle->binary_file_len = 0;

            const void *data_buf = (const void *) handle->ota_upgrade_buf;
#if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB
            decrypt_cb_arg_t args = {};
            args.data_in = handle->ota_upgrade_buf;
            args.data_in_len = binary_file_len;
            err = esp_https_ota_decrypt_cb(handle, &args);
            if (err == ESP_OK) {
                data_buf = args.data_out;
                binary_file_len = args.data_out_len;
            } else {
                ESP_LOGE(TAG, "Decryption of image header failed");
                return ESP_FAIL;
            }
#endif // CONFIG_ESP_HTTPS_OTA_DECRYPT_CB
            err = esp_ota_verify_chip_id(data_buf);
            if (err != ESP_OK) {
                return err;
            }
            return _ota_write(handle, data_buf, binary_file_len);
        ……
    }

上一回已经讲到,handle->state即https_ota_handle->state的值在esp_https_ota_begin函数中被赋值(初始化)为ESP_HTTPS_OTA_BEGIN。那么,这里一上来当然应该进入switch中的case ESP_HTTPS_OTA_BEGIN。

ESP_HTTPS_OTA_BEGIN这个case中,也包括了很多步骤,仍然一个一个来解析。

3.1)esp_ota_begin

代码片段为:

            err = esp_ota_begin(handle->update_partition, erase_size, &handle->update_handle);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
                return err;
            }

esp_ota_begin函数在C:\Espressif\frameworks\esp-idf-v5.2.1\components\app_update\esp_ota_ops.c中,代码如下:

esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle)
{
    ota_ops_entry_t *new_entry;
    esp_err_t ret = ESP_OK;

    if ((partition == NULL) || (out_handle == NULL)) {
        return ESP_ERR_INVALID_ARG;
    }

    partition = esp_partition_verify(partition);
    if (partition == NULL) {
        return ESP_ERR_NOT_FOUND;
    }

    if (!is_ota_partition(partition)) {
        return ESP_ERR_INVALID_ARG;
    }

    const esp_partition_t* running_partition = esp_ota_get_running_partition();
    if (partition == running_partition) {
        return ESP_ERR_OTA_PARTITION_CONFLICT;
    }

#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
    esp_ota_img_states_t ota_state_running_part;
    if (esp_ota_get_state_partition(running_partition, &ota_state_running_part) == ESP_OK) {
        if (ota_state_running_part == ESP_OTA_IMG_PENDING_VERIFY) {
            ESP_LOGE(TAG, "Running app has not confirmed state (ESP_OTA_IMG_PENDING_VERIFY)");
            return ESP_ERR_OTA_ROLLBACK_INVALID_STATE;
        }
    }
#endif

    if (image_size != OTA_WITH_SEQUENTIAL_WRITES) {
        // If input image size is 0 or OTA_SIZE_UNKNOWN, erase entire partition
        if ((image_size == 0) || (image_size == OTA_SIZE_UNKNOWN)) {
            ret = esp_partition_erase_range(partition, 0, partition->size);
        } else {
            const int aligned_erase_size = (image_size + SPI_FLASH_SEC_SIZE - 1) & ~(SPI_FLASH_SEC_SIZE - 1);
            ret = esp_partition_erase_range(partition, 0, aligned_erase_size);
        }
        if (ret != ESP_OK) {
            return ret;
        }
    }

    new_entry = (ota_ops_entry_t *) calloc(sizeof(ota_ops_entry_t), 1);
    if (new_entry == NULL) {
        return ESP_ERR_NO_MEM;
    }

    LIST_INSERT_HEAD(&s_ota_ops_entries_head, new_entry, entries);

    new_entry->part = partition;
    new_entry->handle = ++s_ota_ops_last_handle;
    new_entry->need_erase = (image_size == OTA_WITH_SEQUENTIAL_WRITES);
    *out_handle = new_entry->handle;
    return ESP_OK;
}
  • esp_partition_verify函数

esp_partition_verify函数在C:\Espressif\frameworks\esp-idf-v5.2.1\components\esp_partition\partition.c中,代码如下:

const esp_partition_t *esp_partition_verify(const esp_partition_t *partition)
{
    assert(partition != NULL);
    const char *label = (strlen(partition->label) > 0) ? partition->label : NULL;
    esp_partition_iterator_t it = esp_partition_find(partition->type,
                                  partition->subtype,
                                  label);
    while (it != NULL) {
        const esp_partition_t *p = esp_partition_get(it);
        /* Can't memcmp() whole structure here as padding contents may be different */
        if (p->flash_chip == partition->flash_chip
                && p->address == partition->address
                && partition->size == p->size
                && partition->encrypted == p->encrypted) {
            esp_partition_iterator_release(it);
            return p;
        }
        it = esp_partition_next(it);
    }
    esp_partition_iterator_release(it);
    return NULL;
}

esp_partition_verify函数的作用是:给定一个指向分区数据的指针,验证分区表中是否存在此分区(保证所有字段都匹配)。此函数还可用于获取RAM缓冲区中的分区数据,并将其转换为指向存储在闪存中的永久分区数据的指针。

这里,传入该函数第1个形参const esp_partition_t *partition的实参为handle->update_partition,也就是说将要写入升级数据的分区。再说明白点,就是如果当前运行分区是ota_0,那么update_partition就是ota_1;如果当前运行分区是ota_1,那么update_partition就是ota_0。

而handle->update_partition则是在之前esp_https_ota_begin函数中得到的,相关代码如下:

    https_ota_handle->update_partition = NULL;
    ESP_LOGI(TAG, "Starting OTA...");
    https_ota_handle->update_partition = esp_ota_get_next_update_partition(NULL);
    if (https_ota_handle->update_partition == NULL) {
        ESP_LOGE(TAG, "Passive OTA partition not found");
        err = ESP_FAIL;
        goto http_cleanup;
    }
  • is_ota_partition函数

is_ota_partition函数在C:\Espressif\frameworks\esp-idf-v5.2.1\components\app_update\esp_ota_ops.c中,代码如下:

/* Return true if this is an OTA app partition */
static bool is_ota_partition(const esp_partition_t *p)
{
    return (p != NULL
            && p->type == ESP_PARTITION_TYPE_APP
            && p->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_0
            && p->subtype < ESP_PARTITION_SUBTYPE_APP_OTA_MAX);
}

is_ota_partition函数很简单,就是检查(给定)分区是否是OTA app分区。

esp_ota_begin函数其余内容的解析请看下回。

  • 31
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝天居士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值