mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-10-31 04:59:55 +00:00 
			
		
		
		
	Merge branch 'feature/enable_secure_boot_in_esp32c61' into 'master'
feat: enable secure boot feature for esp32c61 Closes IDF-9233 See merge request espressif/esp-idf!31420
This commit is contained in:
		| @@ -33,6 +33,8 @@ | |||||||
| #include "esp32p4/rom/secure_boot.h" | #include "esp32p4/rom/secure_boot.h" | ||||||
| #elif CONFIG_IDF_TARGET_ESP32C5 | #elif CONFIG_IDF_TARGET_ESP32C5 | ||||||
| #include "esp32c5/rom/secure_boot.h" | #include "esp32c5/rom/secure_boot.h" | ||||||
|  | #elif CONFIG_IDF_TARGET_ESP32C61 | ||||||
|  | #include "esp32c61/rom/secure_boot.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef CONFIG_SECURE_BOOT_V1_ENABLED | #ifdef CONFIG_SECURE_BOOT_V1_ENABLED | ||||||
|   | |||||||
| @@ -27,6 +27,8 @@ | |||||||
| #include "esp32p4/rom/secure_boot.h" | #include "esp32p4/rom/secure_boot.h" | ||||||
| #elif CONFIG_IDF_TARGET_ESP32C5 | #elif CONFIG_IDF_TARGET_ESP32C5 | ||||||
| #include "esp32c5/rom/secure_boot.h" | #include "esp32c5/rom/secure_boot.h" | ||||||
|  | #elif CONFIG_IDF_TARGET_ESP32C61 | ||||||
|  | #include "esp32c61/rom/secure_boot.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
|   | |||||||
| @@ -0,0 +1,71 @@ | |||||||
|  | /* | ||||||
|  |  * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: Apache-2.0 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include <strings.h> | ||||||
|  | #include "esp_flash_encrypt.h" | ||||||
|  | #include "esp_secure_boot.h" | ||||||
|  | #include "esp_efuse.h" | ||||||
|  | #include "esp_efuse_table.h" | ||||||
|  | #include "esp_log.h" | ||||||
|  | #include "sdkconfig.h" | ||||||
|  |  | ||||||
|  | static __attribute__((unused)) const char *TAG = "secure_boot"; | ||||||
|  |  | ||||||
|  | esp_err_t esp_secure_boot_enable_secure_features(void) | ||||||
|  | { | ||||||
|  |     esp_efuse_write_field_bit(ESP_EFUSE_DIS_DIRECT_BOOT); | ||||||
|  |  | ||||||
|  | #ifdef CONFIG_SECURE_ENABLE_SECURE_ROM_DL_MODE | ||||||
|  |     ESP_LOGI(TAG, "Enabling Security download mode..."); | ||||||
|  |     esp_err_t err = esp_efuse_enable_rom_secure_download_mode(); | ||||||
|  |     if (err != ESP_OK) { | ||||||
|  |         ESP_LOGE(TAG, "Could not enable Security download mode..."); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | #elif CONFIG_SECURE_DISABLE_ROM_DL_MODE | ||||||
|  |     ESP_LOGI(TAG, "Disable ROM Download mode..."); | ||||||
|  |     esp_err_t err = esp_efuse_disable_rom_download_mode(); | ||||||
|  |     if (err != ESP_OK) { | ||||||
|  |         ESP_LOGE(TAG, "Could not disable ROM Download mode..."); | ||||||
|  |         return err; | ||||||
|  |     } | ||||||
|  | #else | ||||||
|  |     ESP_LOGW(TAG, "UART ROM Download mode kept enabled - SECURITY COMPROMISED"); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifndef CONFIG_SECURE_BOOT_ALLOW_JTAG | ||||||
|  |     ESP_LOGI(TAG, "Disable hardware & software JTAG..."); | ||||||
|  |     esp_efuse_write_field_bit(ESP_EFUSE_DIS_PAD_JTAG); | ||||||
|  |     esp_efuse_write_field_bit(ESP_EFUSE_DIS_USB_JTAG); | ||||||
|  |     // TODO in IDF-10694 | ||||||
|  |     // esp_efuse_write_field_cnt(ESP_EFUSE_SOFT_DIS_JTAG, ESP_EFUSE_SOFT_DIS_JTAG[0]->bit_count); | ||||||
|  | #else | ||||||
|  |     ESP_LOGW(TAG, "Not disabling JTAG - SECURITY COMPROMISED"); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifdef CONFIG_SECURE_BOOT_ENABLE_AGGRESSIVE_KEY_REVOKE | ||||||
|  |     esp_efuse_write_field_bit(ESP_EFUSE_SECURE_BOOT_AGGRESSIVE_REVOKE); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     esp_efuse_write_field_bit(ESP_EFUSE_SECURE_BOOT_EN); | ||||||
|  |  | ||||||
|  | #ifndef CONFIG_SECURE_BOOT_V2_ALLOW_EFUSE_RD_DIS | ||||||
|  |     bool rd_dis_now = true; | ||||||
|  | #ifdef CONFIG_SECURE_FLASH_ENC_ENABLED | ||||||
|  |     /* If flash encryption is not enabled yet then don't read-disable efuses yet, do it later in the boot | ||||||
|  |        when Flash Encryption is being enabled */ | ||||||
|  |     rd_dis_now = esp_flash_encryption_enabled(); | ||||||
|  | #endif | ||||||
|  |     if (rd_dis_now) { | ||||||
|  |         ESP_LOGI(TAG, "Prevent read disabling of additional efuses..."); | ||||||
|  |         esp_efuse_write_field_bit(ESP_EFUSE_WR_DIS_RD_DIS); | ||||||
|  |     } | ||||||
|  | #else | ||||||
|  |     ESP_LOGW(TAG, "Allowing read disabling of additional efuses - SECURITY COMPROMISED"); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     return ESP_OK; | ||||||
|  | } | ||||||
| @@ -23,6 +23,8 @@ | |||||||
| #include "esp32p4/rom/secure_boot.h" | #include "esp32p4/rom/secure_boot.h" | ||||||
| #elif CONFIG_IDF_TARGET_ESP32C5 | #elif CONFIG_IDF_TARGET_ESP32C5 | ||||||
| #include "esp32c5/rom/secure_boot.h" | #include "esp32c5/rom/secure_boot.h" | ||||||
|  | #elif CONFIG_IDF_TARGET_ESP32C61 | ||||||
|  | #include "esp32c61/rom/secure_boot.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| esp_err_t verify_ecdsa_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest, const ets_secure_boot_sig_block_t *trusted_block); | esp_err_t verify_ecdsa_signature_block(const ets_secure_boot_signature_t *sig_block, const uint8_t *image_digest, const ets_secure_boot_sig_block_t *trusted_block); | ||||||
|   | |||||||
| @@ -360,7 +360,7 @@ static const esp_efuse_desc_t UART_PRINT_CONTROL[] = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| static const esp_efuse_desc_t FORCE_SEND_RESUME[] = { | static const esp_efuse_desc_t FORCE_SEND_RESUME[] = { | ||||||
|     {EFUSE_BLK0, 103, 1}, 	 // [] Represents whether ROM code is forced to send a resume commmand during SPI boot, |     {EFUSE_BLK0, 103, 1}, 	 // [] Represents whether ROM code is forced to send a resume command during SPI boot, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| static const esp_efuse_desc_t SECURE_VERSION[] = { | static const esp_efuse_desc_t SECURE_VERSION[] = { | ||||||
| @@ -879,7 +879,7 @@ const esp_efuse_desc_t* ESP_EFUSE_UART_PRINT_CONTROL[] = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const esp_efuse_desc_t* ESP_EFUSE_FORCE_SEND_RESUME[] = { | const esp_efuse_desc_t* ESP_EFUSE_FORCE_SEND_RESUME[] = { | ||||||
|     &FORCE_SEND_RESUME[0],    		// [] Represents whether ROM code is forced to send a resume commmand during SPI boot |     &FORCE_SEND_RESUME[0],    		// [] Represents whether ROM code is forced to send a resume command during SPI boot | ||||||
|     NULL |     NULL | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -67,6 +67,10 @@ config SOC_FLASH_ENC_SUPPORTED | |||||||
|     bool |     bool | ||||||
|     default y |     default y | ||||||
|  |  | ||||||
|  | config SOC_SECURE_BOOT_SUPPORTED | ||||||
|  |     bool | ||||||
|  |     default y | ||||||
|  |  | ||||||
| config SOC_PMU_SUPPORTED | config SOC_PMU_SUPPORTED | ||||||
|     bool |     bool | ||||||
|     default y |     default y | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ | |||||||
| #define SOC_ECC_SUPPORTED               1 | #define SOC_ECC_SUPPORTED               1 | ||||||
| #define SOC_ECC_EXTENDED_MODES_SUPPORTED   1 | #define SOC_ECC_EXTENDED_MODES_SUPPORTED   1 | ||||||
| #define SOC_FLASH_ENC_SUPPORTED         1 | #define SOC_FLASH_ENC_SUPPORTED         1 | ||||||
| //  \#define SOC_SECURE_BOOT_SUPPORTED       1    //TODO: [ESP32C61] IDF-9233 | #define SOC_SECURE_BOOT_SUPPORTED       1 | ||||||
| //  \#define SOC_BOD_SUPPORTED               1    //TODO: [ESP32C61] IDF-9254 | //  \#define SOC_BOD_SUPPORTED               1    //TODO: [ESP32C61] IDF-9254 | ||||||
| //  \#define SOC_APM_SUPPORTED               1    //TODO: [ESP32C61] IDF-9230 | //  \#define SOC_APM_SUPPORTED               1    //TODO: [ESP32C61] IDF-9230 | ||||||
| #define SOC_PMU_SUPPORTED               1    //TODO: [ESP32C61] IDF-9250 | #define SOC_PMU_SUPPORTED               1    //TODO: [ESP32C61] IDF-9250 | ||||||
|   | |||||||
| @@ -565,6 +565,8 @@ def test_examples_efuse_with_virt_secure_boot_v2_pre_loaded(dut: Dut) -> None: | |||||||
| @pytest.mark.esp32c2 | @pytest.mark.esp32c2 | ||||||
| # TODO: [ESP32C5] IDF-10043 | # TODO: [ESP32C5] IDF-10043 | ||||||
| # @pytest.mark.esp32c5 | # @pytest.mark.esp32c5 | ||||||
|  | # TODO: [ESP32C5] IDF-10102 | ||||||
|  | # @pytest.mark.esp32c61 | ||||||
| @pytest.mark.esp32c6 | @pytest.mark.esp32c6 | ||||||
| @pytest.mark.esp32h2 | @pytest.mark.esp32h2 | ||||||
| @pytest.mark.esp32p4 | @pytest.mark.esp32p4 | ||||||
| @@ -640,6 +642,8 @@ def test_examples_efuse_with_virt_secure_boot_v2_esp32xx(dut: Dut) -> None: | |||||||
| @pytest.mark.esp32c2 | @pytest.mark.esp32c2 | ||||||
| # TODO: [ESP32C5] IDF-10043 | # TODO: [ESP32C5] IDF-10043 | ||||||
| # @pytest.mark.esp32c5 | # @pytest.mark.esp32c5 | ||||||
|  | # TODO: [ESP32C5] IDF-10102 | ||||||
|  | # @pytest.mark.esp32c61 | ||||||
| @pytest.mark.esp32c6 | @pytest.mark.esp32c6 | ||||||
| @pytest.mark.esp32h2 | @pytest.mark.esp32h2 | ||||||
| @pytest.mark.esp32p4 | @pytest.mark.esp32p4 | ||||||
|   | |||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | # FLASH_ENCRYPTION & SECURE_BOOT_V2 with EFUSE_VIRTUAL_KEEP_IN_FLASH | ||||||
|  |  | ||||||
|  | CONFIG_IDF_TARGET="esp32c61" | ||||||
|  |  | ||||||
|  | CONFIG_PARTITION_TABLE_OFFSET=0xD000 | ||||||
|  | CONFIG_PARTITION_TABLE_CUSTOM=y | ||||||
|  | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" | ||||||
|  |  | ||||||
|  | CONFIG_SECURE_BOOT=y | ||||||
|  | CONFIG_SECURE_BOOT_V2_ENABLED=y | ||||||
|  | CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key.pem" | ||||||
|  | CONFIG_SECURE_ENABLE_SECURE_ROM_DL_MODE=y | ||||||
|  |  | ||||||
|  | CONFIG_SECURE_FLASH_ENC_ENABLED=y | ||||||
|  |  | ||||||
|  | # IMPORTANT: ONLY VIRTUAL eFuse MODE! | ||||||
|  | CONFIG_EFUSE_VIRTUAL=y | ||||||
|  | CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | # SECURE_BOOT_V2 with EFUSE_VIRTUAL_KEEP_IN_FLASH | ||||||
|  |  | ||||||
|  | CONFIG_IDF_TARGET="esp32c61" | ||||||
|  |  | ||||||
|  | CONFIG_PARTITION_TABLE_OFFSET=0xC000 | ||||||
|  | CONFIG_PARTITION_TABLE_CUSTOM=y | ||||||
|  | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" | ||||||
|  |  | ||||||
|  | CONFIG_SECURE_BOOT=y | ||||||
|  | CONFIG_SECURE_BOOT_V2_ENABLED=y | ||||||
|  | CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key.pem" | ||||||
|  | CONFIG_SECURE_INSECURE_ALLOW_DL_MODE=y | ||||||
|  |  | ||||||
|  | # IMPORTANT: ONLY VIRTUAL eFuse MODE! | ||||||
|  | CONFIG_EFUSE_VIRTUAL=y | ||||||
|  | CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH=y | ||||||
| @@ -17,6 +17,7 @@ Any of the following ESP module: | |||||||
| * ESP32S3 (supports Secure Boot V2) | * ESP32S3 (supports Secure Boot V2) | ||||||
| * ESP32P4 (supports Secure Boot V2) | * ESP32P4 (supports Secure Boot V2) | ||||||
| * ESP32C5 (supports Secure Boot V2) | * ESP32C5 (supports Secure Boot V2) | ||||||
|  | * ESP32C61 (supports Secure Boot V2) | ||||||
|  |  | ||||||
| It is recommended to use Secure Boot V2 from ESP32-ECO3 onwards. | It is recommended to use Secure Boot V2 from ESP32-ECO3 onwards. | ||||||
|  |  | ||||||
| @@ -71,7 +72,7 @@ Purpose of the test case (`pytest_secure_boot.py`) is to test the secure boot im | |||||||
|  |  | ||||||
| ### Hardware required | ### Hardware required | ||||||
|  |  | ||||||
| * FPGA setup with ESP32C3/ESP32S3/ESP32P4/ESP32C5 image | * FPGA setup with ESP32C3/ESP32S3/ESP32P4/ESP32C5/ESP32C61 image | ||||||
|  |  | ||||||
| * COM port for programming and export it as ESPPORT | * COM port for programming and export it as ESPPORT | ||||||
|     e.g `export ESPPORT=/dev/ttyUSB0` |     e.g `export ESPPORT=/dev/ttyUSB0` | ||||||
| @@ -84,7 +85,7 @@ Purpose of the test case (`pytest_secure_boot.py`) is to test the secure boot im | |||||||
| ``` | ``` | ||||||
| export IDF_ENV_FPGA=1 | export IDF_ENV_FPGA=1 | ||||||
|  |  | ||||||
| idf.py set-target esp32c3   #(or esp32s3 / esp32p4 / esp32c5) | idf.py set-target esp32c3   #(or esp32s3 / esp32p4 / esp32c5 / esp32c61) | ||||||
|  |  | ||||||
| idf.py menuconfig | idf.py menuconfig | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -186,6 +186,20 @@ class Esp32c5FpgaDut(FpgaDut): | |||||||
|         self.serial.burn_efuse_key_digest(digest, 'SECURE_BOOT_DIGEST%d' % key_index, 'BLOCK_KEY%d' % block) |         self.serial.burn_efuse_key_digest(digest, 'SECURE_BOOT_DIGEST%d' % key_index, 'BLOCK_KEY%d' % block) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Esp32c61FpgaDut(FpgaDut): | ||||||
|  |     SECURE_BOOT_EN_KEY = 'SECURE_BOOT_EN' | ||||||
|  |     SECURE_BOOT_EN_VAL = 1 | ||||||
|  |  | ||||||
|  |     def burn_wafer_version(self) -> None: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def secure_boot_burn_en_bit(self) -> None: | ||||||
|  |         self.serial.burn_efuse(self.SECURE_BOOT_EN_KEY, self.SECURE_BOOT_EN_VAL) | ||||||
|  |  | ||||||
|  |     def secure_boot_burn_digest(self, digest: str, key_index: int = 0, block: int = 0) -> None: | ||||||
|  |         self.serial.burn_efuse_key_digest(digest, 'SECURE_BOOT_DIGEST%d' % key_index, 'BLOCK_KEY%d' % block) | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture(scope='module') | @pytest.fixture(scope='module') | ||||||
| def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: | def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch: | ||||||
|     mp = MonkeyPatch() |     mp = MonkeyPatch() | ||||||
| @@ -204,5 +218,7 @@ def replace_dut_class(monkeypatch_module: MonkeyPatch, pytestconfig: pytest.Conf | |||||||
|         monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', Esp32p4FpgaDut) |         monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', Esp32p4FpgaDut) | ||||||
|     elif target == 'esp32c5': |     elif target == 'esp32c5': | ||||||
|         monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', Esp32c5FpgaDut) |         monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', Esp32c5FpgaDut) | ||||||
|  |     elif target == 'esp32c61': | ||||||
|  |         monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', Esp32c61FpgaDut) | ||||||
|  |  | ||||||
|     monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', FpgaSerial) |     monkeypatch_module.setattr('pytest_embedded_idf.IdfSerial', FpgaSerial) | ||||||
|   | |||||||
| @@ -82,6 +82,8 @@ def dut_start_secure_app(dut: Dut) -> None: | |||||||
| @pytest.mark.esp32c3 | @pytest.mark.esp32c3 | ||||||
| # TODO: [ESP32C5] IDF-10043 | # TODO: [ESP32C5] IDF-10043 | ||||||
| # @pytest.mark.esp32c5 | # @pytest.mark.esp32c5 | ||||||
|  | # TODO: [ESP32C5] IDF-10102 | ||||||
|  | # @pytest.mark.esp32c61 | ||||||
| @pytest.mark.esp32s3 | @pytest.mark.esp32s3 | ||||||
| @pytest.mark.esp32p4 | @pytest.mark.esp32p4 | ||||||
| def test_examples_security_secure_boot(dut: Dut) -> None: | def test_examples_security_secure_boot(dut: Dut) -> None: | ||||||
| @@ -96,6 +98,8 @@ def test_examples_security_secure_boot(dut: Dut) -> None: | |||||||
| @pytest.mark.esp32c3 | @pytest.mark.esp32c3 | ||||||
| # TODO: [ESP32C5] IDF-10043 | # TODO: [ESP32C5] IDF-10043 | ||||||
| # @pytest.mark.esp32c5 | # @pytest.mark.esp32c5 | ||||||
|  | # TODO: [ESP32C5] IDF-10102 | ||||||
|  | # @pytest.mark.esp32c61 | ||||||
| @pytest.mark.esp32s3 | @pytest.mark.esp32s3 | ||||||
| @pytest.mark.esp32p4 | @pytest.mark.esp32p4 | ||||||
| # Increasing the test timeout to 1200s as the test runs for 18 iterations | # Increasing the test timeout to 1200s as the test runs for 18 iterations | ||||||
| @@ -120,6 +124,8 @@ def test_examples_security_secure_boot_key_combo(dut: Dut) -> None: | |||||||
| @pytest.mark.esp32c3 | @pytest.mark.esp32c3 | ||||||
| # TODO: [ESP32C5] IDF-10043 | # TODO: [ESP32C5] IDF-10043 | ||||||
| # @pytest.mark.esp32c5 | # @pytest.mark.esp32c5 | ||||||
|  | # TODO: [ESP32C5] IDF-10102 | ||||||
|  | # @pytest.mark.esp32c61 | ||||||
| @pytest.mark.esp32s3 | @pytest.mark.esp32s3 | ||||||
| @pytest.mark.esp32p4 | @pytest.mark.esp32p4 | ||||||
| def test_examples_security_secure_boot_key_revoke(dut: Dut) -> None: | def test_examples_security_secure_boot_key_revoke(dut: Dut) -> None: | ||||||
| @@ -141,6 +147,8 @@ def test_examples_security_secure_boot_key_revoke(dut: Dut) -> None: | |||||||
| @pytest.mark.esp32c3 | @pytest.mark.esp32c3 | ||||||
| # TODO: [ESP32C5] IDF-10043 | # TODO: [ESP32C5] IDF-10043 | ||||||
| # @pytest.mark.esp32c5 | # @pytest.mark.esp32c5 | ||||||
|  | # TODO: [ESP32C5] IDF-10102 | ||||||
|  | # @pytest.mark.esp32c61 | ||||||
| @pytest.mark.esp32s3 | @pytest.mark.esp32s3 | ||||||
| @pytest.mark.esp32p4 | @pytest.mark.esp32p4 | ||||||
| @pytest.mark.timeout(18000) | @pytest.mark.timeout(18000) | ||||||
| @@ -180,6 +188,8 @@ def test_examples_security_secure_boot_corrupt_bl_sig(dut: Dut) -> None: | |||||||
| @pytest.mark.esp32c3 | @pytest.mark.esp32c3 | ||||||
| # TODO: [ESP32C5] IDF-10043 | # TODO: [ESP32C5] IDF-10043 | ||||||
| # @pytest.mark.esp32c5 | # @pytest.mark.esp32c5 | ||||||
|  | # TODO: [ESP32C5] IDF-10102 | ||||||
|  | # @pytest.mark.esp32c61 | ||||||
| @pytest.mark.esp32s3 | @pytest.mark.esp32s3 | ||||||
| @pytest.mark.esp32p4 | @pytest.mark.esp32p4 | ||||||
| @pytest.mark.timeout(18000) | @pytest.mark.timeout(18000) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Nilesh Kale
					Nilesh Kale