feat(test_app): add test app for multiple phy init data

This commit is contained in:
yinqingzhao
2026-08-28 17:34:59 +08:00
committed by Yin Qing Zhao
parent fbb0518b1a
commit cbeb35d8b4
13 changed files with 346 additions and 24 deletions

View File

@@ -313,6 +313,17 @@ esp_err_t esp_phy_fe_sleep_data_init(void);
*/
void esp_phy_fe_sleep_data_deinit(void);
#endif // SOC_PM_MODEM_RETENTION_BY_REGDMA && (CONFIG_MAC_BB_PD || CONFIG_ESP_PHY_HW_SWITCH_RF)
#if CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN
/**
* @brief Get the PHY init data type that is currently applied to the PHY
*
* @return the applied init data type, ESP_PHY_INIT_DATA_TYPE_DEFAULT as long as no
* certified init data has been selected by a country code
*/
phy_init_data_type_t esp_phy_get_init_data_type(void);
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -1259,6 +1259,11 @@ esp_err_t esp_phy_update_init_data(phy_init_data_type_t init_data_type)
free(init_data_store);
return ESP_OK;
}
phy_init_data_type_t esp_phy_get_init_data_type(void)
{
return s_current_apply_phy_init_data;
}
#endif
esp_err_t esp_phy_update_country_info(const char *country)

View File

@@ -16,7 +16,9 @@ components/esp_phy/test_apps/phy_iram:
- nvs_flash
components/esp_phy/test_apps/phy_multiple_init_data:
disable:
- if: IDF_TARGET == "esp32p4" # Update with caps here when IDF-7460 is resolved
- if: SOC_WIFI_SUPPORTED != 1
depends_components:
- *common_components
- esp_phy
- esp_wifi
- nvs_flash

View File

@@ -1,10 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.22)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(test_build)
project(test_phy_multiple_init_data)

View File

@@ -1,6 +1,26 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | --------- |
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | --------- |
This project tests building with the phy_multiple_init_data configuration.
This project tests the multiple PHY init data bin support, i.e. `CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN`.
This project uses MINIMAL_BUILD=y to reduce build time and dependencies.
The tests cover:
- the layout, the checksums and the certified init data types of `phy_multiple_init_data.bin`
- loading the bin through `esp_phy_get_init_data()`
- switching the PHY init data type from a country code, both through `esp_phy_update_country_info()`
and through `esp_wifi_set_country_code()`
- the fallback to the default init data for countries without a certified init data
Two configurations are tested:
| Config | Where the bin lives |
| --------- | ------------------------------------------------------------------------ |
| `default` | flashed into the `phy` data partition |
| `embed` | embedded into the application binary (`..._MULTIPLE_INIT_DATA_BIN_EMBED`) |
Run the tests with:
```bash
idf.py set-target <target>
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.default" build flash monitor
```

View File

@@ -1,3 +1,7 @@
idf_component_register(SRCS "test_main.c"
set(srcs "test_app_main.c"
"test_phy_multiple_init_data.c")
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
REQUIRES esp_phy)
PRIV_REQUIRES unity esp_phy esp_wifi esp_event nvs_flash spi_flash
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,12 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "unity.h"
#include "unity_test_runner.h"
void app_main(void)
{
unity_run_menu();
}

View File

@@ -1,8 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
void app_main(void)
{
}

View File

@@ -0,0 +1,247 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_phy_init.h"
#include "esp_private/phy.h"
#include "esp_rom_crc.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "phy_init_data.h"
#include "sdkconfig.h"
#if CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN_EMBED
extern const uint8_t multi_phy_init_data_bin_start[] asm("_binary_phy_multiple_init_data_bin_start");
extern const uint8_t multi_phy_init_data_bin_end[] asm("_binary_phy_multiple_init_data_bin_end");
#else
#include "esp_partition.h"
#endif
/* phy_multiple_init_data.bin layout:
* [magic][default init data][magic][control info][certified init data] * number
*/
#define PHY_INIT_DATA_LEN (sizeof(esp_phy_init_data_t))
#define CONTROL_INFO_OFFSET (PHY_INIT_MAGIC_LEN + PHY_INIT_DATA_LEN + PHY_INIT_MAGIC_LEN)
#define MULTIPLE_DATA_OFFSET (CONTROL_INFO_OFFSET + sizeof(phy_control_info_data_t))
static uint8_t *multiple_init_data_load(size_t *out_len)
{
uint8_t *blob = NULL;
size_t len = 0;
#if CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN_EMBED
len = (size_t)(multi_phy_init_data_bin_end - multi_phy_init_data_bin_start);
blob = malloc(len);
TEST_ASSERT_NOT_NULL(blob);
memcpy(blob, multi_phy_init_data_bin_start, len);
#else
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_PHY, NULL);
TEST_ASSERT_NOT_NULL_MESSAGE(partition, "no phy data partition in the partition table");
len = partition->size;
blob = malloc(len);
TEST_ASSERT_NOT_NULL(blob);
TEST_ESP_OK(esp_partition_read(partition, 0, blob, len));
#endif
*out_len = len;
return blob;
}
/* The checksums in the bin are stored big-endian, see phy_crc_check_init_data() */
static bool crc32_matches(const uint8_t *data, size_t len, const uint8_t *expected)
{
uint32_t crc = esp_rom_crc32_le(0, data, len);
const uint8_t crc_be[4] = { crc >> 24, crc >> 16, crc >> 8, crc };
return memcmp(crc_be, expected, sizeof(crc_be)) == 0;
}
static void control_info_get(const uint8_t *blob, phy_control_info_data_t *out_info)
{
memcpy(out_info, blob + CONTROL_INFO_OFFSET, sizeof(*out_info));
}
static void wifi_start(void)
{
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
TEST_ESP_OK(nvs_flash_erase());
err = nvs_flash_init();
}
TEST_ESP_OK(err);
TEST_ESP_OK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
TEST_ESP_OK(esp_wifi_init(&cfg));
TEST_ESP_OK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
TEST_ESP_OK(esp_wifi_set_mode(WIFI_MODE_STA));
TEST_ESP_OK(esp_wifi_start());
}
static void wifi_stop(void)
{
TEST_ESP_OK(esp_wifi_stop());
TEST_ESP_OK(esp_wifi_deinit());
TEST_ESP_OK(esp_event_loop_delete_default());
TEST_ESP_OK(nvs_flash_deinit());
}
TEST_CASE("multiple PHY init data bin matches the PHY init data of this target", "[phy_multiple_init_data]")
{
size_t blob_len = 0;
uint8_t *blob = multiple_init_data_load(&blob_len);
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(MULTIPLE_DATA_OFFSET, blob_len);
TEST_ASSERT_EQUAL_MEMORY_MESSAGE(PHY_INIT_MAGIC, blob, PHY_INIT_MAGIC_LEN, "leading magic");
TEST_ASSERT_EQUAL_MEMORY_MESSAGE(PHY_INIT_MAGIC, blob + CONTROL_INFO_OFFSET - PHY_INIT_MAGIC_LEN,
PHY_INIT_MAGIC_LEN, "trailing magic");
/* Without this flag esp_phy_update_country_info() rejects every country code */
TEST_ASSERT_NOT_EQUAL_MESSAGE(0, blob[PHY_INIT_MAGIC_LEN + PHY_SUPPORT_MULTIPLE_BIN_OFFSET],
"bin is not marked as supporting multiple init data");
phy_control_info_data_t info;
control_info_get(blob, &info);
TEST_ASSERT_EQUAL(PHY_CRC_ALGORITHM, info.check_algorithm);
TEST_ASSERT_GREATER_THAN(0, info.number);
/* Big-endian per-entry length, a mismatch means the bin was built for another PHY */
TEST_ASSERT_EQUAL_MESSAGE(PHY_INIT_DATA_LEN, ((uint16_t)info.length[0] << 8) | info.length[1],
"per-entry length differs from sizeof(esp_phy_init_data_t)");
TEST_ASSERT_TRUE_MESSAGE(crc32_matches(info.multiple_bin_checksum,
sizeof(info) - sizeof(info.control_info_checksum),
info.control_info_checksum),
"control info checksum mismatch");
size_t multiple_data_len = PHY_INIT_DATA_LEN * info.number;
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(MULTIPLE_DATA_OFFSET + multiple_data_len, blob_len);
TEST_ASSERT_TRUE_MESSAGE(crc32_matches(blob + MULTIPLE_DATA_OFFSET, multiple_data_len,
info.multiple_bin_checksum),
"certified init data checksum mismatch");
free(blob);
}
TEST_CASE("certified PHY init data types are known and unique", "[phy_multiple_init_data]")
{
size_t blob_len = 0;
uint8_t *blob = multiple_init_data_load(&blob_len);
phy_control_info_data_t info;
control_info_get(blob, &info);
/* A bin may carry certifications that phy_init_data_type_t does not name yet,
so the type byte is only bounded by its own width */
bool seen[256] = { false };
unsigned unnamed = 0;
for (uint8_t i = 0; i < info.number; i++) {
uint8_t type = blob[MULTIPLE_DATA_OFFSET + i * PHY_INIT_DATA_LEN + PHY_INIT_DATA_TYPE_OFFSET];
/* The DEFAULT type is the block in front of the control info, not a certified entry */
TEST_ASSERT_NOT_EQUAL(ESP_PHY_INIT_DATA_TYPE_DEFAULT, type);
TEST_ASSERT_FALSE_MESSAGE(seen[type], "duplicated init data type");
seen[type] = true;
if (type >= ESP_PHY_INIT_DATA_TYPE_NUMBER) {
unnamed++;
}
}
printf("%u certified entries, %u of them unnamed by phy_init_data_type_t\n", info.number, unnamed);
/* The country codes exercised by this app have to be certified in the bin */
static const phy_init_data_type_t required[] = {
ESP_PHY_INIT_DATA_TYPE_SRRC,
ESP_PHY_INIT_DATA_TYPE_FCC,
ESP_PHY_INIT_DATA_TYPE_CE,
ESP_PHY_INIT_DATA_TYPE_NCC,
ESP_PHY_INIT_DATA_TYPE_KCC,
ESP_PHY_INIT_DATA_TYPE_MIC,
};
for (size_t i = 0; i < sizeof(required) / sizeof(required[0]); i++) {
TEST_ASSERT_TRUE_MESSAGE(seen[required[i]], "a country code tested below is not certified");
}
free(blob);
}
TEST_CASE("esp_phy_get_init_data loads the multiple PHY init data bin", "[phy_multiple_init_data]")
{
const esp_phy_init_data_t *init_data = esp_phy_get_init_data();
TEST_ASSERT_NOT_NULL(init_data);
esp_phy_release_init_data(init_data);
}
TEST_CASE("country code selects the certified PHY init data", "[phy_multiple_init_data]")
{
static const struct {
const char *country;
phy_init_data_type_t type;
} certified[] = {
{ "CN", ESP_PHY_INIT_DATA_TYPE_SRRC },
{ "US", ESP_PHY_INIT_DATA_TYPE_FCC },
{ "DE", ESP_PHY_INIT_DATA_TYPE_CE },
{ "TW", ESP_PHY_INIT_DATA_TYPE_NCC },
{ "KR", ESP_PHY_INIT_DATA_TYPE_KCC },
{ "JP", ESP_PHY_INIT_DATA_TYPE_MIC },
};
wifi_start();
for (size_t i = 0; i < sizeof(certified) / sizeof(certified[0]); i++) {
TEST_ESP_OK(esp_phy_update_country_info(certified[i].country));
TEST_ASSERT_EQUAL_MESSAGE(certified[i].type, esp_phy_get_init_data_type(), certified[i].country);
}
/* Re-applying the same country keeps the PHY on the same init data */
TEST_ESP_OK(esp_phy_update_country_info("JP"));
TEST_ASSERT_EQUAL(ESP_PHY_INIT_DATA_TYPE_MIC, esp_phy_get_init_data_type());
wifi_stop();
}
TEST_CASE("uncertified country codes fall back to the default PHY init data", "[phy_multiple_init_data]")
{
wifi_start();
TEST_ESP_OK(esp_phy_update_country_info("CN"));
TEST_ASSERT_EQUAL(ESP_PHY_INIT_DATA_TYPE_SRRC, esp_phy_get_init_data_type());
/* AU maps to ACMA, for which the bin carries no certified init data */
TEST_ESP_OK(esp_phy_update_country_info("AU"));
TEST_ASSERT_EQUAL(ESP_PHY_INIT_DATA_TYPE_DEFAULT, esp_phy_get_init_data_type());
/* An unmapped country code is answered with the default init data as well */
TEST_ESP_OK(esp_phy_update_country_info("ZZ"));
TEST_ASSERT_EQUAL(ESP_PHY_INIT_DATA_TYPE_DEFAULT, esp_phy_get_init_data_type());
wifi_stop();
}
TEST_CASE("esp_wifi_set_country_code updates the PHY init data", "[phy_multiple_init_data]")
{
wifi_start();
TEST_ESP_OK(esp_wifi_set_country_code("JP", false));
/* The Wi-Fi task drives the PHY update, so the type may lag the API call */
for (int i = 0; i < 100 && esp_phy_get_init_data_type() != ESP_PHY_INIT_DATA_TYPE_MIC; i++) {
vTaskDelay(pdMS_TO_TICKS(10));
}
TEST_ASSERT_EQUAL(ESP_PHY_INIT_DATA_TYPE_MIC, esp_phy_get_init_data_type());
/* wifi_country_t.cc is 3 octets (ISO code + environment), not a C string */
char country_code[3] = { 0 };
TEST_ESP_OK(esp_wifi_get_country_code(country_code));
TEST_ASSERT_EQUAL('J', country_code[0]);
TEST_ASSERT_EQUAL('P', country_code[1]);
wifi_stop();
}

View File

@@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'default',
'embed',
],
indirect=True,
)
@idf_parametrize(
'target',
['esp32', 'esp32c2', 'esp32c3', 'esp32s2', 'esp32s3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32s31'],
indirect=['target'],
)
def test_phy_multiple_init_data(dut: Dut) -> None:
# each case switches the PHY init data type, so start every one of them from a fresh boot
dut.run_all_single_board_cases(reset=True)

View File

@@ -1,2 +1,2 @@
CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION=y
CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN=y
# The multiple PHY init data bin is flashed into the phy data partition,
# the options are already set by sdkconfig.defaults.

View File

@@ -0,0 +1,3 @@
# The multiple PHY init data bin is embedded into the application binary
# instead of being flashed into the phy data partition.
CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN_EMBED=y

View File

@@ -0,0 +1,5 @@
CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION=y
CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN=y
# ignore task watchdog triggered by unity_run_menu
CONFIG_ESP_TASK_WDT_INIT=n