接前一篇文章: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函数其余内容的解析请看下回。