diff --git a/.gitignore b/.gitignore index e855791af..3abace88d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ GPATH # eclipse setting .settings +# MacOS directory files +.DS_Store + # Example project files examples/**/sdkconfig examples/**/sdkconfig.old diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d813ef981..2153a3d4e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -446,22 +446,21 @@ assign_test: - components/idf_test/*/CIConfigs - components/idf_test/*/TC.sqlite - $EXAMPLE_CONFIG_OUTPUT_PATH + - tools/unit-test-app/output expire_in: 1 mos before_script: *add_gitlab_key_before script: # first move test bins together: test_bins/CHIP_SDK/TestApp/bin_files - mkdir -p $OUTPUT_BIN_PATH - # copy and rename folder name to "UT_config" - - for CONFIG in $(ls $UT_BIN_PATH); do cp -r "$UT_BIN_PATH/$CONFIG" "$OUTPUT_BIN_PATH/UT_$CONFIG"; done - cp -r SSC/ssc_bin/* $OUTPUT_BIN_PATH # assign example tests - python $TEST_FW_PATH/CIAssignExampleTest.py $IDF_PATH/examples $IDF_PATH/.gitlab-ci.yml $EXAMPLE_CONFIG_OUTPUT_PATH + # assign unit test cases + - python $TEST_FW_PATH/CIAssignUnitTest.py $IDF_PATH/components/idf_test/unit_test/TestCaseAll.yml $IDF_PATH/.gitlab-ci.yml $IDF_PATH/components/idf_test/unit_test/CIConfigs # clone test script to assign tests - git clone $TEST_SCRIPT_REPOSITORY - cd auto_test_script - python $CHECKOUT_REF_SCRIPT auto_test_script - # assign unit test cases - - python CIAssignTestCases.py -t $IDF_PATH/components/idf_test/unit_test -c $IDF_PATH/.gitlab-ci.yml -b $IDF_PATH/test_bins # assgin integration test cases - python CIAssignTestCases.py -t $IDF_PATH/components/idf_test/integration_test -c $IDF_PATH/.gitlab-ci.yml -b $IDF_PATH/test_bins @@ -493,6 +492,17 @@ assign_test: # run test - python Runner.py $TEST_CASE_PATH -c $CONFIG_FILE +.unit_test_template: &unit_test_template + <<: *example_test_template + stage: unit_test + dependencies: + - assign_test + variables: + TEST_FW_PATH: "$CI_PROJECT_DIR/tools/tiny-test-fw" + TEST_CASE_PATH: "$CI_PROJECT_DIR/tools/unit-test-app" + CONFIG_FILE: "$CI_PROJECT_DIR/components/idf_test/unit_test/CIConfigs/$CI_JOB_NAME.yml" + LOG_PATH: "$CI_PROJECT_DIR/TEST_LOGS" + .test_template: &test_template stage: test when: on_success @@ -530,18 +540,6 @@ assign_test: # run test - python CIRunner.py -l "$LOG_PATH/$CI_JOB_NAME" -c $CONFIG_FILE -e $LOCAL_ENV_CONFIG_PATH -t $TEST_CASE_FILE_PATH -m $MODULE_UPDATE_FILE -# template for unit test jobs -.unit_test_template: &unit_test_template - <<: *test_template - allow_failure: false - stage: unit_test - variables: - LOCAL_ENV_CONFIG_PATH: "$CI_PROJECT_DIR/ci-test-runner-configs/$CI_RUNNER_DESCRIPTION/ESP32_IDF" - LOG_PATH: "$CI_PROJECT_DIR/$CI_COMMIT_SHA" - TEST_CASE_FILE_PATH: "$CI_PROJECT_DIR/components/idf_test/unit_test" - MODULE_UPDATE_FILE: "$CI_PROJECT_DIR/components/idf_test/ModuleDefinition.yml" - CONFIG_FILE: "$CI_PROJECT_DIR/components/idf_test/unit_test/CIConfigs/$CI_JOB_NAME.yml" - nvs_compatible_test: <<: *test_template artifacts: diff --git a/.gitmodules b/.gitmodules index e118821b7..84fa4ece2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -33,3 +33,7 @@ [submodule "components/spiffs/spiffs"] path = components/spiffs/spiffs url = https://github.com/pellepl/spiffs.git + +[submodule "components/json/cJSON"] + path = components/json/cJSON + url = https://github.com/DaveGamble/cJSON.git diff --git a/components/app_trace/Kconfig b/components/app_trace/Kconfig index 6883db57e..9426c7539 100644 --- a/components/app_trace/Kconfig +++ b/components/app_trace/Kconfig @@ -64,33 +64,37 @@ config SYSVIEW_ENABLE Enables supporrt for SEGGER SystemView tracing functionality. choice SYSVIEW_TS_SOURCE - prompt "ESP32 timer to use as SystemView timestamp source" + prompt "Timer to use as timestamp source" depends on SYSVIEW_ENABLE - default SYSVIEW_TS_SOURCE_TIMER_00 + default SYSVIEW_TS_SOURCE_CCOUNT if FREERTOS_UNICORE && !PM_ENABLE + default SYSVIEW_TS_SOURCE_TIMER_00 if !FREERTOS_UNICORE && !PM_ENABLE + default SYSVIEW_TS_SOURCE_ESP_TIMER if PM_ENABLE help SystemView needs to use a hardware timer as the source of timestamps - when tracing - This option selects HW timer for it. + when tracing. This option selects the timer for it. + +config SYSVIEW_TS_SOURCE_CCOUNT + bool "CPU cycle counter (CCOUNT)" + depends on FREERTOS_UNICORE && !PM_ENABLE config SYSVIEW_TS_SOURCE_TIMER_00 bool "Timer 0, Group 0" - help - Select this to use timer 0 of group 0 + depends on !PM_ENABLE config SYSVIEW_TS_SOURCE_TIMER_01 bool "Timer 1, Group 0" - help - Select this to use timer 1 of group 0 + depends on !PM_ENABLE config SYSVIEW_TS_SOURCE_TIMER_10 bool "Timer 0, Group 1" - help - Select this to use timer 0 of group 1 + depends on !PM_ENABLE config SYSVIEW_TS_SOURCE_TIMER_11 bool "Timer 1, Group 1" - help - Select this to use timer 1 of group 1 + depends on !PM_ENABLE + +config SYSVIEW_TS_SOURCE_ESP_TIMER + bool "esp_timer high resolution timer" endchoice diff --git a/components/app_trace/sys_view/Sample/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c b/components/app_trace/sys_view/Sample/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c index 728879b6a..08e85678e 100644 --- a/components/app_trace/sys_view/Sample/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c +++ b/components/app_trace/sys_view/Sample/Config/SEGGER_SYSVIEW_Config_FreeRTOS.c @@ -64,9 +64,6 @@ Revision: $Rev: 3734 $ #include "freertos/FreeRTOS.h" #include "SEGGER_SYSVIEW.h" #include "rom/ets_sys.h" -#if CONFIG_FREERTOS_UNICORE == 0 -#include "driver/timer.h" -#endif #include "esp_app_trace.h" #include "esp_app_trace_util.h" #include "esp_intr_alloc.h" @@ -86,10 +83,49 @@ extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI; // The target device name #define SYSVIEW_DEVICE_NAME "ESP32" +// Determine which timer to use as timestamp source +#if CONFIG_SYSVIEW_TS_SOURCE_CCOUNT +#define TS_USE_CCOUNT 1 +#elif CONFIG_SYSVIEW_TS_SOURCE_ESP_TIMER +#define TS_USE_ESP_TIMER 1 +#else +#define TS_USE_TIMERGROUP 1 +#endif + +#if TS_USE_TIMERGROUP +#include "driver/timer.h" + // Timer group timer divisor #define SYSVIEW_TIMER_DIV 2 + // Frequency of the timestamp. #define SYSVIEW_TIMESTAMP_FREQ (esp_clk_apb_freq() / SYSVIEW_TIMER_DIV) + +// Timer ID and group ID +#if defined(CONFIG_SYSVIEW_TS_SOURCE_TIMER_00) || defined(CONFIG_SYSVIEW_TS_SOURCE_TIMER_01) +#define TS_TIMER_ID 0 +#else +#define TS_TIMER_ID 1 +#endif // TIMER_00 || TIMER_01 + +#if defined(CONFIG_SYSVIEW_TS_SOURCE_TIMER_00) || defined(CONFIG_SYSVIEW_TS_SOURCE_TIMER_10) +#define TS_TIMER_GROUP 0 +#else +#define TS_TIMER_GROUP 1 +#endif // TIMER_00 || TIMER_10 + +#endif // TS_USE_TIMERGROUP + +#if TS_USE_ESP_TIMER +// esp_timer provides 1us resolution +#define SYSVIEW_TIMESTAMP_FREQ (1000000) +#endif // TS_USE_ESP_TIMER + +#if TS_USE_CCOUNT +// CCOUNT is incremented at CPU frequency +#define SYSVIEW_TIMESTAMP_FREQ (CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ * 1000000) +#endif // TS_USE_CCOUNT + // System Frequency. #define SYSVIEW_CPU_FREQ (esp_clk_cpu_freq()) @@ -103,11 +139,8 @@ extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI; #define SYSTICK_INTR_ID (ETS_INTERNAL_TIMER1_INTR_SOURCE+ETS_INTERNAL_INTR_SOURCE_OFF) #endif -static timer_idx_t s_ts_timer_idx; -static timer_group_t s_ts_timer_group; - // SystemView is single core specific: it implies that SEGGER_SYSVIEW_LOCK() -// disables IRQs (disables rescheduling globaly). So we can not use finite timeouts for locks and return error +// disables IRQs (disables rescheduling globally). So we can not use finite timeouts for locks and return error // in case of expiration, because error will not be handled and SEGGER's code will go further implying that // everything is fine, so for multi-core env we have to wait on underlying lock forever #define SEGGER_LOCK_WAIT_TMO ESP_APPTRACE_TMO_INFINITE @@ -213,35 +246,24 @@ static void _cbSendSystemDesc(void) { */ static void SEGGER_SYSVIEW_TS_Init() { - timer_config_t config; - -#if CONFIG_SYSVIEW_TS_SOURCE_TIMER_00 - s_ts_timer_group = TIMER_GROUP_0; - s_ts_timer_idx = TIMER_0; -#endif -#if CONFIG_SYSVIEW_TS_SOURCE_TIMER_01 - s_ts_timer_group = TIMER_GROUP_0; - s_ts_timer_idx = TIMER_1; -#endif -#if CONFIG_SYSVIEW_TS_SOURCE_TIMER_10 - s_ts_timer_group = TIMER_GROUP_1; - s_ts_timer_idx = TIMER_0; -#endif -#if CONFIG_SYSVIEW_TS_SOURCE_TIMER_11 - s_ts_timer_group = TIMER_GROUP_1; - s_ts_timer_idx = TIMER_1; -#endif - config.alarm_en = 0; - config.auto_reload = 0; - config.counter_dir = TIMER_COUNT_UP; - config.divider = SYSVIEW_TIMER_DIV; - config.counter_en = 0; - /*Configure timer*/ - timer_init(s_ts_timer_group, s_ts_timer_idx, &config); - /*Load counter value */ - timer_set_counter_value(s_ts_timer_group, s_ts_timer_idx, 0x00000000ULL); - /*Enable timer interrupt*/ - timer_start(s_ts_timer_group, s_ts_timer_idx); + /* We only need to initialize something if we use Timer Group. + * esp_timer and ccount can be used as is. + */ +#if TS_USE_TIMERGROUP + timer_config_t config = { + .alarm_en = 0, + .auto_reload = 0, + .counter_dir = TIMER_COUNT_UP, + .divider = SYSVIEW_TIMER_DIV, + .counter_en = 0 + }; + /* Configure timer */ + timer_init(TS_TIMER_GROUP, TS_TIMER_ID, &config); + /* Load counter value */ + timer_set_counter_value(TS_TIMER_GROUP, TS_TIMER_ID, 0x00000000ULL); + /* Start counting */ + timer_start(TS_TIMER_GROUP, TS_TIMER_ID); +#endif // TS_USE_TIMERGROUP } void SEGGER_SYSVIEW_Conf(void) { @@ -296,12 +318,14 @@ void SEGGER_SYSVIEW_Conf(void) { U32 SEGGER_SYSVIEW_X_GetTimestamp() { -#if CONFIG_FREERTOS_UNICORE == 0 +#if TS_USE_TIMERGROUP uint64_t ts = 0; - timer_get_counter_value(s_ts_timer_group, s_ts_timer_idx, &ts); - return (U32)ts; // return lower part of counter value -#else + timer_get_counter_value(TS_TIMER_GROUP, TS_TIMER_ID, &ts); + return (U32) ts; // return lower part of counter value +#elif TS_USE_CCOUNT return portGET_RUN_TIME_COUNTER_VALUE(); +#elif TS_USE_ESP_TIMER + return (U32) esp_timer_get_time(); // return lower part of counter value #endif } diff --git a/components/aws_iot/Kconfig b/components/aws_iot/Kconfig index 76b587e9c..9144f1660 100644 --- a/components/aws_iot/Kconfig +++ b/components/aws_iot/Kconfig @@ -83,3 +83,77 @@ config AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL Maximum delay between reconnection attempts. If the exponentially increased delay interval reaches this value, the client will stop automatically attempting to reconnect. +menu "Thing Shadow" + depends on AWS_IOT_SDK + +config AWS_IOT_OVERRIDE_THING_SHADOW_RX_BUFFER + bool "Override Shadow RX buffer size" + depends on AWS_IOT_SDK + default n + help + Allows setting a different Thing Shadow RX buffer + size. This is the maximum size of a Thing Shadow + message in bytes, plus one. + + If not overridden, the default value is the MQTT RX Buffer length plus one. If overriden, do not set higher than the default value. + +config AWS_IOT_SHADOW_MAX_SIZE_OF_RX_BUFFER + int "Maximum RX Buffer (bytes)" + depends on AWS_IOT_OVERRIDE_THING_SHADOW_RX_BUFFER + default 513 + range 32 65536 + help + Allows setting a different Thing Shadow RX buffer size. + This is the maximum size of a Thing Shadow message in bytes, + plus one. + + +config AWS_IOT_SHADOW_MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + int "Maximum unique client ID size (bytes)" + depends on AWS_IOT_SDK + default 80 + range 4 1000 + help + Maximum size of the Unique Client Id. + +config AWS_IOT_SHADOW_MAX_SIMULTANEOUS_ACKS + int "Maximum simultaneous responses" + depends on AWS_IOT_SDK + default 10 + range 1 100 + help + At any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested + +config AWS_IOT_SHADOW_MAX_SIMULTANEOUS_THINGNAMES + int "Maximum simultaneous Thing Name operations" + depends on AWS_IOT_SDK + default 10 + range 1 100 + help + We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time + +config AWS_IOT_SHADOW_MAX_JSON_TOKEN_EXPECTED + int "Maximum expected JSON tokens" + depends on AWS_IOT_SDK + default 120 + help + These are the max tokens that is expected to be in the Shadow JSON document. Includes the metadata which is published + +config AWS_IOT_SHADOW_MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + int "Maximum topic length (not including Thing Name)" + depends on AWS_IOT_SDK + default 60 + range 10 1000 + help + All shadow actions have to be published or subscribed to a topic which is of the format $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name + +config AWS_IOT_SHADOW_MAX_SIZE_OF_THING_NAME + int "Maximum Thing Name length" + depends on AWS_IOT_SDK + default 20 + range 4 1000 + help + Maximum length of a Thing Name. + +endmenu # Thing Shadow + diff --git a/components/aws_iot/aws-iot-device-sdk-embedded-C b/components/aws_iot/aws-iot-device-sdk-embedded-C index 7132505b0..8bf852db7 160000 --- a/components/aws_iot/aws-iot-device-sdk-embedded-C +++ b/components/aws_iot/aws-iot-device-sdk-embedded-C @@ -1 +1 @@ -Subproject commit 7132505b00d2dd57f48478e75efa636021919aae +Subproject commit 8bf852db77c360eebfa4b800754fdb90e29ea43e diff --git a/components/aws_iot/include/aws_iot_config.h b/components/aws_iot/include/aws_iot_config.h index 2d10702af..14a1ced27 100644 --- a/components/aws_iot/include/aws_iot_config.h +++ b/components/aws_iot/include/aws_iot_config.h @@ -42,15 +42,20 @@ #define AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS CONFIG_AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS ///< Maximum number of topic filters the MQTT client can handle at any given time. This should be increased appropriately when using Thing Shadow // Thing Shadow specific configs -#define SHADOW_MAX_SIZE_OF_RX_BUFFER (AWS_IOT_MQTT_RX_BUF_LEN + 1) ///< Maximum size of the SHADOW buffer to store the received Shadow message +#ifdef CONFIG_AWS_IOT_OVERRIDE_THING_SHADOW_RX_BUFFER +#define SHADOW_MAX_SIZE_OF_RX_BUFFER CONFIG AWS_IOT_SHADOW_MAX_SIZE_OF_RX_BUFFER ///< Maximum size of the SHADOW buffer to store the received Shadow message, including NULL termianting byte +#else +#define SHADOW_MAX_SIZE_OF_RX_BUFFER (AWS_IOT_MQTT_RX_BUF_LEN + 1) +#endif + #define MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES 80 ///< Maximum size of the Unique Client Id. For More info on the Client Id refer \ref response "Acknowledgments" #define MAX_SIZE_CLIENT_ID_WITH_SEQUENCE (MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + 10) ///< This is size of the extra sequence number that will be appended to the Unique client Id #define MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE (MAX_SIZE_CLIENT_ID_WITH_SEQUENCE + 20) ///< This is size of the the total clientToken key and value pair in the JSON -#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME 10 ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested -#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME 10 ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time -#define MAX_JSON_TOKEN_EXPECTED 120 ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published -#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME 60 ///< All shadow actions have to be published or subscribed to a topic which is of the formablogt $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name -#define MAX_SIZE_OF_THING_NAME 20 ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger +#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME CONFIG_AWS_IOT_SHADOW_MAX_SIMULTANEOUS_ACKS ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested +#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME CONFIG_AWS_IOT_SHADOW_MAX_SIMULTANEOUS_THINGNAMES ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time +#define MAX_JSON_TOKEN_EXPECTED CONFIG_AWS_IOT_SHADOW_MAX_JSON_TOKEN_EXPECTED ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published +#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME CONFIG_AWS_IOT_SHADOW_MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME ///< All shadow actions have to be published or subscribed to a topic which is of the formablogt $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name +#define MAX_SIZE_OF_THING_NAME CONFIG_AWS_IOT_SHADOW_MAX_SIZE_OF_THING_NAME ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger #define MAX_SHADOW_TOPIC_LENGTH_BYTES (MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + MAX_SIZE_OF_THING_NAME) ///< This size includes the length of topic with Thing Name // Auto Reconnect specific config diff --git a/components/bootloader_support/src/flash_partitions.c b/components/bootloader_support/src/flash_partitions.c index b60968f96..f8a24f26c 100644 --- a/components/bootloader_support/src/flash_partitions.c +++ b/components/bootloader_support/src/flash_partitions.c @@ -11,50 +11,76 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include #include "esp_flash_partitions.h" #include "esp_log.h" #include "rom/spi_flash.h" +#include "rom/md5_hash.h" +#include "esp_flash_data_types.h" static const char *TAG = "flash_parts"; esp_err_t esp_partition_table_basic_verify(const esp_partition_info_t *partition_table, bool log_errors, int *num_partitions) { - int num_parts; - uint32_t chip_size = g_rom_flashchip.chip_size; - *num_partitions = 0; + int md5_found = 0; + int num_parts; + uint32_t chip_size = g_rom_flashchip.chip_size; + *num_partitions = 0; - for(num_parts = 0; num_parts < ESP_PARTITION_TABLE_MAX_ENTRIES; num_parts++) { - const esp_partition_info_t *part = &partition_table[num_parts]; + for (num_parts = 0; num_parts < ESP_PARTITION_TABLE_MAX_ENTRIES; num_parts++) { + const esp_partition_info_t *part = &partition_table[num_parts]; - if (part->magic == 0xFFFF - && part->type == PART_TYPE_END - && part->subtype == PART_SUBTYPE_END) { - /* TODO: check md5 */ - ESP_LOGD(TAG, "partition table verified, %d entries", num_parts); - *num_partitions = num_parts; - return ESP_OK; - } + if (part->magic == ESP_PARTITION_MAGIC) { + const esp_partition_pos_t *pos = &part->pos; + if (pos->offset > chip_size || pos->offset + pos->size > chip_size) { + if (log_errors) { + ESP_LOGE(TAG, "partition %d invalid - offset 0x%x size 0x%x exceeds flash chip size 0x%x", + num_parts, pos->offset, pos->size, chip_size); + } + return ESP_ERR_INVALID_SIZE; + } + } else if (part->magic == ESP_PARTITION_MAGIC_MD5) { + if (md5_found) { + if (log_errors) { + ESP_LOGE(TAG, "Only one MD5 checksum is allowed"); + } + return ESP_ERR_INVALID_STATE; + } - if (part->magic != ESP_PARTITION_MAGIC) { - if (log_errors) { - ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic); + struct MD5Context context; + unsigned char digest[16]; + MD5Init(&context); + MD5Update(&context, (unsigned char *) partition_table, num_parts * sizeof(esp_partition_info_t)); + MD5Final(digest, &context); + + unsigned char *md5sum = ((unsigned char *) part) + 16; // skip the 2B magic number and the 14B fillup bytes + + if (memcmp(md5sum, digest, sizeof(digest)) != 0) { + if (log_errors) { + ESP_LOGE(TAG, "Incorrect MD5 checksum"); + } + return ESP_ERR_INVALID_STATE; + } + //MD5 checksum matches and we continue with the next interation in + //order to detect the end of the partition table + md5_found = 1; + } else if (part->magic == 0xFFFF + && part->type == PART_TYPE_END + && part->subtype == PART_SUBTYPE_END) { + ESP_LOGD(TAG, "partition table verified, %d entries", num_parts); + *num_partitions = num_parts - md5_found; //do not count the partition where the MD5 checksum is held + return ESP_OK; + } else { + if (log_errors) { + ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic); + } + return ESP_ERR_INVALID_STATE; } - return ESP_ERR_INVALID_STATE; } - const esp_partition_pos_t *pos = &part->pos; - if (pos->offset > chip_size || pos->offset + pos->size > chip_size) { - if (log_errors) { - ESP_LOGE(TAG, "partition %d invalid - offset 0x%x size 0x%x exceeds flash chip size 0x%x", - num_parts, pos->offset, pos->size, chip_size); - } - return ESP_ERR_INVALID_SIZE; + if (log_errors) { + ESP_LOGE(TAG, "partition table has no terminating entry, not valid"); } - } - - if (log_errors) { - ESP_LOGE(TAG, "partition table has no terminating entry, not valid"); - } - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } diff --git a/components/bt/Kconfig b/components/bt/Kconfig index ebc677530..371bab208 100644 --- a/components/bt/Kconfig +++ b/components/bt/Kconfig @@ -113,19 +113,39 @@ config CLASSIC_BT_ENABLED help For now this option needs "SMP_ENABLE" to be set to yes -config A2DP_SNK_ENABLED - bool "A2DP(SINK)" +config A2DP_ENABLE + bool "A2DP" depends on CLASSIC_BT_ENABLED default n help - This option is used to enable/disable Advanced Audio Distribution Profile(Sink) + Advanced Audio Distrubution Profile +choice A2DP_ROLE + prompt "A2DP ROLE config" + depends on A2DP_ENABLE + +config A2DP_SINK_ENABLE + bool "SINK" +config A2DP_SRC_ENABLE + bool "SOURCE" +endchoice + +config A2DP_SINK_TASK_STACK_SIZE + int "A2DP sink (audio stream decoding) task stack size" + depends on A2DP_ENABLE && A2DP_SINK_ENABLE + default 2048 + +config A2DP_SOURCE_TASK_STACK_SIZE + int "A2DP source (audio stream encoding) task stack size" + depends on A2DP_ENABLE && A2DP_SRC_ENABLE + default 2048 + config BT_SPP_ENABLED - bool "SPP Profile" + bool "SPP" depends on CLASSIC_BT_ENABLED default n help - This enables the SPP Profile + This enables the Serial Port Profile config GATTS_ENABLE bool "Include GATT server module(GATTS)" @@ -163,6 +183,20 @@ config BT_ACL_CONNECTIONS help Maximum BT/BLE connection count +config BT_ALLOCATION_FROM_SPIRAM_FIRST + bool "BT/BLE will first malloc the memory from the PSRAM" + depends on BLUEDROID_ENABLED + default n + help + This select can save the internal RAM if there have the PSRAM + +config BT_BLE_DYNAMIC_ENV_MEMORY + bool "Use dynamic memory allocation in BT/BLE stack" + depends on BLUEDROID_ENABLED + default n + help + This select can make the allocation of memory will become more flexible + config SMP_ENABLE bool depends on BLUEDROID_ENABLED diff --git a/components/bt/bluedroid/api/esp_a2dp_api.c b/components/bt/bluedroid/api/esp_a2dp_api.c index cb635908c..2141ce0c2 100644 --- a/components/bt/bluedroid/api/esp_a2dp_api.c +++ b/components/bt/bluedroid/api/esp_a2dp_api.c @@ -22,20 +22,7 @@ #if BTC_AV_INCLUDED -esp_err_t esp_a2d_register_callback(esp_a2d_cb_t callback) -{ - if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; - } - - if (callback == NULL) { - return ESP_FAIL; - } - - btc_profile_cb_set(BTC_PID_A2DP, callback); - return ESP_OK; -} - +#if BTC_AV_SINK_INCLUDED esp_err_t esp_a2d_sink_init(void) { if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { @@ -43,7 +30,7 @@ esp_err_t esp_a2d_sink_init(void) } btc_msg_t msg; - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_A2DP; msg.act = BTC_AV_SINK_API_INIT_EVT; @@ -58,9 +45,9 @@ esp_err_t esp_a2d_sink_deinit(void) if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } - + btc_msg_t msg; - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_A2DP; msg.act = BTC_AV_SINK_API_DEINIT_EVT; @@ -70,7 +57,7 @@ esp_err_t esp_a2d_sink_deinit(void) return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; } -esp_err_t esp_a2d_register_data_callback(esp_a2d_data_cb_t callback) +esp_err_t esp_a2d_sink_register_data_callback(esp_a2d_sink_data_cb_t callback) { if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; @@ -82,11 +69,11 @@ esp_err_t esp_a2d_register_data_callback(esp_a2d_data_cb_t callback) msg.act = BTC_AV_SINK_API_REG_DATA_CB_EVT; btc_av_args_t arg; - memset(&arg, 0, sizeof(btc_av_args_t)); + memset(&arg, 0, sizeof(btc_av_args_t)); arg.data_cb = callback; - + /* Switch to BTC context */ - bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_msg_t), NULL); + bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL); return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; } @@ -95,17 +82,17 @@ esp_err_t esp_a2d_sink_connect(esp_bd_addr_t remote_bda) if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } - + bt_status_t stat; btc_av_args_t arg; btc_msg_t msg; - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_A2DP; msg.act = BTC_AV_SINK_API_CONNECT_EVT; memset(&arg, 0, sizeof(btc_av_args_t)); - + /* Switch to BTC context */ memcpy(&(arg.connect), remote_bda, sizeof(bt_bdaddr_t)); stat = btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL); @@ -117,10 +104,10 @@ esp_err_t esp_a2d_sink_disconnect(esp_bd_addr_t remote_bda) if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } - + bt_status_t stat; btc_msg_t msg; - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_A2DP; msg.act = BTC_AV_SINK_API_DISCONNECT_EVT; @@ -130,4 +117,139 @@ esp_err_t esp_a2d_sink_disconnect(esp_bd_addr_t remote_bda) return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; } +#endif /* BTC_AV_SINK_INCLUDED */ + +esp_err_t esp_a2d_register_callback(esp_a2d_cb_t callback) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (callback == NULL) { + return ESP_FAIL; + } + + btc_profile_cb_set(BTC_PID_A2DP, callback); + return ESP_OK; +} + +esp_err_t esp_a2d_media_ctrl(esp_a2d_media_ctrl_t ctrl) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + bt_status_t stat; + btc_av_args_t arg; + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_API_MEDIA_CTRL_EVT; + + memset(&arg, 0, sizeof(btc_av_args_t)); + + /* Switch to BTC context */ + arg.ctrl = ctrl; + stat = btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +#if BTC_AV_SRC_INCLUDED +esp_err_t esp_a2d_source_init(void) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_SRC_API_INIT_EVT; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, NULL, 0, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_a2d_source_deinit(void) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_SRC_API_DEINIT_EVT; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, NULL, 0, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_a2d_source_connect(esp_bd_addr_t remote_bda) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + bt_status_t stat; + btc_av_args_t arg; + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_SRC_API_CONNECT_EVT; + + memset(&arg, 0, sizeof(btc_av_args_t)); + + /* Switch to BTC context */ + memcpy(&(arg.src_connect), remote_bda, sizeof(bt_bdaddr_t)); + stat = btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_a2d_source_disconnect(esp_bd_addr_t remote_bda) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + bt_status_t stat; + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_SRC_API_DISCONNECT_EVT; + + /* Switch to BTC context */ + stat = btc_transfer_context(&msg, NULL, 0, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +esp_err_t esp_a2d_source_register_data_callback(esp_a2d_source_data_cb_t callback) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_SRC_API_REG_DATA_CB_EVT; + + btc_av_args_t arg; + memset(&arg, 0, sizeof(btc_av_args_t)); + arg.src_data_cb = callback; + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + +#endif /* BTC_AV_SRC_INCLUDED */ + #endif /* #if BTC_AV_INCLUDED */ diff --git a/components/bt/bluedroid/api/esp_blufi_api.c b/components/bt/bluedroid/api/esp_blufi_api.c index b735ad549..833228a4d 100644 --- a/components/bt/bluedroid/api/esp_blufi_api.c +++ b/components/bt/bluedroid/api/esp_blufi_api.c @@ -22,6 +22,7 @@ #include "btc_main.h" #include "future.h" #include "btc_gatts.h" +#include "btc_gatt_util.h" esp_err_t esp_blufi_register_callbacks(esp_blufi_callbacks_t *callbacks) { @@ -57,6 +58,23 @@ esp_err_t esp_blufi_send_wifi_conn_report(wifi_mode_t opmode, esp_blufi_sta_conn return (btc_transfer_context(&msg, &arg, sizeof(btc_blufi_args_t), btc_blufi_call_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } +esp_err_t esp_blufi_send_wifi_list(uint16_t apCount, esp_blufi_ap_record_t *list) +{ + btc_msg_t msg; + btc_blufi_args_t arg; + + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_BLUFI; + msg.act = BTC_BLUFI_ACT_SEND_WIFI_LIST; + arg.wifi_list.apCount = apCount; + arg.wifi_list.list = list; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_blufi_args_t), btc_blufi_call_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} esp_err_t esp_blufi_profile_init(void) { @@ -103,7 +121,24 @@ esp_err_t esp_blufi_close(esp_gatt_if_t gatts_if, uint16_t conn_id) msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_CLOSE; - arg.close.conn_id = conn_id; + arg.close.conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn_id); return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } + +esp_err_t esp_blufi_send_error_info(esp_blufi_error_state_t state) +{ + btc_msg_t msg; + btc_blufi_args_t arg; + + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_BLUFI; + msg.act = BTC_BLUFI_ACT_SEND_ERR_INFO; + arg.blufi_err_infor.state = state; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_blufi_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} diff --git a/components/bt/bluedroid/api/esp_gatt_common_api.c b/components/bt/bluedroid/api/esp_gatt_common_api.c index 6d795c1c0..1146750c7 100644 --- a/components/bt/bluedroid/api/esp_gatt_common_api.c +++ b/components/bt/bluedroid/api/esp_gatt_common_api.c @@ -16,7 +16,7 @@ #include "esp_gatt_common_api.h" #include "esp_bt_main.h" #include "esp_gatt_defs.h" -#include "btc_main.h" +#include "btc_gatt_common.h" /** * @brief This function is called to set local MTU, @@ -32,7 +32,7 @@ esp_err_t esp_ble_gatt_set_local_mtu (uint16_t mtu) { btc_msg_t msg; - btc_ble_main_args_t arg; + btc_ble_gatt_com_args_t arg; ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); @@ -41,9 +41,9 @@ esp_err_t esp_ble_gatt_set_local_mtu (uint16_t mtu) } msg.sig = BTC_SIG_API_CALL; - msg.pid = BTC_PID_MAIN_INIT; + msg.pid = BTC_PID_GATT_COMMON; msg.act = BTC_GATT_ACT_SET_LOCAL_MTU; arg.set_mtu.mtu = mtu; - return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_main_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatt_com_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } \ No newline at end of file diff --git a/components/bt/bluedroid/api/include/esp_a2dp_api.h b/components/bt/bluedroid/api/include/esp_a2dp_api.h index 7f6868dde..8117d4c54 100644 --- a/components/bt/bluedroid/api/include/esp_a2dp_api.h +++ b/components/bt/bluedroid/api/include/esp_a2dp_api.h @@ -67,11 +67,28 @@ typedef enum { ESP_A2D_AUDIO_STATE_STARTED, /*!< audio stream datapath started */ } esp_a2d_audio_state_t; +/// A2DP media control command acknowledgement code +typedef enum { + ESP_A2D_MEDIA_CTRL_ACK_SUCCESS = 0, /*!< media control command is acknowledged with success */ + ESP_A2D_MEDIA_CTRL_ACK_FAILURE, /*!< media control command is acknowledged with failure */ + ESP_A2D_MEDIA_CTRL_ACK_BUSY, /*!< media control command is rejected, as previous command is not yet acknowledged */ +} esp_a2d_media_ctrl_ack_t; + +/// A2DP media control commands +typedef enum { + ESP_A2D_MEDIA_CTRL_NONE = 0, /*!< dummy command */ + ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY, /*!< check whether AVDTP is connected, only used in A2DP source */ + ESP_A2D_MEDIA_CTRL_START, /*!< command to set up media transmission channel */ + ESP_A2D_MEDIA_CTRL_STOP, /*!< command to stop media transmission */ + ESP_A2D_MEDIA_CTRL_SUSPEND, /*!< command to suspend media transmission */ +} esp_a2d_media_ctrl_t; + /// A2DP callback events typedef enum { ESP_A2D_CONNECTION_STATE_EVT = 0, /*!< connection state changed event */ - ESP_A2D_AUDIO_STATE_EVT = 1, /*!< audio stream transmission state changed event */ - ESP_A2D_AUDIO_CFG_EVT = 2 /*!< audio codec is configured */ + ESP_A2D_AUDIO_STATE_EVT, /*!< audio stream transmission state changed event */ + ESP_A2D_AUDIO_CFG_EVT, /*!< audio codec is configured, only used for A2DP SINK */ + ESP_A2D_MEDIA_CTRL_ACK_EVT, /*!< acknowledge event in response to media control commands */ } esp_a2d_cb_event_t; /// A2DP state callback parameters @@ -84,7 +101,7 @@ typedef union { esp_bd_addr_t remote_bda; /*!< remote bluetooth device address */ esp_a2d_disc_rsn_t disc_rsn; /*!< reason of disconnection for "DISCONNECTED" */ } conn_stat; /*!< A2DP connection status */ - + /** * @brief ESP_A2D_AUDIO_STATE_EVT */ @@ -92,7 +109,7 @@ typedef union { esp_a2d_audio_state_t state; /*!< one of the values from esp_a2d_audio_state_t */ esp_bd_addr_t remote_bda; /*!< remote bluetooth device address */ } audio_stat; /*!< audio stream playing state */ - + /** * @brief ESP_A2D_AUDIO_CFG_EVT */ @@ -100,32 +117,52 @@ typedef union { esp_bd_addr_t remote_bda; /*!< remote bluetooth device address */ esp_a2d_mcc_t mcc; /*!< A2DP media codec capability information */ } audio_cfg; /*!< media codec configuration infomation */ + + /** + * @brief ESP_A2D_MEDIA_CTRL_ACK_EVT + */ + struct media_ctrl_stat_param { + esp_a2d_media_ctrl_t cmd; /*!< media control commands to acknowledge */ + esp_a2d_media_ctrl_ack_t status; /*!< acknowledgement to media control commands */ + } media_ctrl_stat; /*!< status in acknowledgement to media control commands */ } esp_a2d_cb_param_t; /** * @brief A2DP profile callback function type + * * @param event : Event type + * * @param param : Pointer to callback parameter */ typedef void (* esp_a2d_cb_t)(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); /** * @brief A2DP profile data callback function - * * @param[in] buf : data received from A2DP source device and is PCM format decoder from SBC decoder; * buf references to a static memory block and can be overwritten by upcoming data - * * @param[in] len : size(in bytes) in buf + */ +typedef void (* esp_a2d_sink_data_cb_t)(const uint8_t *buf, uint32_t len); + +/** + * @brief A2DP source data read callback function + * + * @param[in] buf : buffer to be filled with PCM data stream from higer layer + * + * @param[in] len : size(in bytes) of data block to be copied to buf. -1 is an indication to user + * that data buffer shall be flushed + * + * @return size of bytes read successfully, if the argumetn len is -1, this value is ignored. * */ -typedef void (* esp_a2d_data_cb_t)(const uint8_t *buf, uint32_t len); - +typedef int32_t (* esp_a2d_source_data_cb_t)(uint8_t *buf, int32_t len); /** * @brief Register application callback function to A2DP module. This function should be called - * only after esp_bluedroid_enable() completes successfully - * - * @param[in] callback: A2DP sink event callback function + * only after esp_bluedroid_enable() completes successfully, used by both A2DP source + * and sink. + * + * @param[in] callback: A2DP event callback function * * @return * - ESP_OK: success @@ -138,10 +175,11 @@ esp_err_t esp_a2d_register_callback(esp_a2d_cb_t callback); /** * @brief Register A2DP sink data output function; For now the output is PCM data stream decoded - * from SBC format. This function should be called only after esp_bluedroid_enable() - * completes successfully - * - * @param[in] callback: A2DP data callback function + * from SBC format. This function should be called only after esp_bluedroid_enable() + * completes successfully, used only by A2DP sink. The callback is invoked in the context + * of A2DP sink task whose stack size is configurable through menuconfig + * + * @param[in] callback: A2DP sink data callback function * * @return * - ESP_OK: success @@ -149,7 +187,7 @@ esp_err_t esp_a2d_register_callback(esp_a2d_cb_t callback); * - ESP_FAIL: if callback is a NULL function pointer * */ -esp_err_t esp_a2d_register_data_callback(esp_a2d_data_cb_t callback); +esp_err_t esp_a2d_sink_register_data_callback(esp_a2d_sink_data_cb_t callback); /** @@ -157,7 +195,7 @@ esp_err_t esp_a2d_register_data_callback(esp_a2d_data_cb_t callback); * @brief Initialize the bluetooth A2DP sink module. This function should be called * after esp_bluedroid_enable() completes successfully * - * @return + * @return * - ESP_OK: if the initialization request is sent successfully * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled * - ESP_FAIL: others @@ -168,10 +206,10 @@ esp_err_t esp_a2d_sink_init(void); /** * - * @brief De-initialize for A2DP sink module. This function + * @brief De-initialize for A2DP sink module. This function * should be called only after esp_bluedroid_enable() completes successfully * - * @return + * @return * - ESP_OK: success * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled * - ESP_FAIL: others @@ -182,11 +220,11 @@ esp_err_t esp_a2d_sink_deinit(void); /** * - * @brief Connect the remote bluetooth device bluetooth, must after esp_a2d_sink_init() + * @brief Connect to remote bluetooth A2DP source device, must after esp_a2d_sink_init() * * @param[in] remote_bda: remote bluetooth device address * - * @return + * @return * - ESP_OK: connect request is sent to lower layer * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled * - ESP_FAIL: others @@ -197,10 +235,10 @@ esp_err_t esp_a2d_sink_connect(esp_bd_addr_t remote_bda); /** * - * @brief Disconnect the remote bluetooth device + * @brief Disconnect from the remote A2DP source device * * @param[in] remote_bda: remote bluetooth device address - * @return + * @return * - ESP_OK: disconnect request is sent to lower layer * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled * - ESP_FAIL: others @@ -208,6 +246,94 @@ esp_err_t esp_a2d_sink_connect(esp_bd_addr_t remote_bda); */ esp_err_t esp_a2d_sink_disconnect(esp_bd_addr_t remote_bda); + +/** + * + * @brief media control commands; this API can be used for both A2DP sink and source + * + * @param[in] ctrl: control commands for A2DP data channel + * @return + * - ESP_OK: control command is sent to lower layer + * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled + * - ESP_FAIL: others + * + */ +esp_err_t esp_a2d_media_ctrl(esp_a2d_media_ctrl_t ctrl); + + +/** + * + * @brief Initialize the bluetooth A2DP source module. This function should be called + * after esp_bluedroid_enable() completes successfully + * + * @return + * - ESP_OK: if the initialization request is sent successfully + * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled + * - ESP_FAIL: others + * + */ +esp_err_t esp_a2d_source_init(void); + + +/** + * + * @brief De-initialize for A2DP source module. This function + * should be called only after esp_bluedroid_enable() completes successfully + * + * @return + * - ESP_OK: success + * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled + * - ESP_FAIL: others + * + */ +esp_err_t esp_a2d_source_deinit(void); + + +/** + * @brief Register A2DP source data input function; For now the input is PCM data stream. + * This function should be called only after esp_bluedroid_enable() completes + * successfully. The callback is invoked in the context of A2DP source task whose + * stack size is configurable through menuconfig + * + * @param[in] callback: A2DP source data callback function + * + * @return + * - ESP_OK: success + * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled + * - ESP_FAIL: if callback is a NULL function pointer + * + */ +esp_err_t esp_a2d_source_register_data_callback(esp_a2d_source_data_cb_t callback); + + +/** + * + * @brief Connect to remote A2DP sink device, must after esp_a2d_source_init() + * + * @param[in] remote_bda: remote bluetooth device address + * + * @return + * - ESP_OK: connect request is sent to lower layer + * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled + * - ESP_FAIL: others + * + */ +esp_err_t esp_a2d_source_connect(esp_bd_addr_t remote_bda); + + +/** + * + * @brief Disconnect from the remote A2DP sink device + * + * @param[in] remote_bda: remote bluetooth device address + * @return + * - ESP_OK: disconnect request is sent to lower layer + * - ESP_INVALID_STATE: if bluetooth stack is not yet enabled + * - ESP_FAIL: others + * + */ +esp_err_t esp_a2d_source_disconnect(esp_bd_addr_t remote_bda); + #ifdef __cplusplus } #endif diff --git a/components/bt/bluedroid/api/include/esp_blufi_api.h b/components/bt/bluedroid/api/include/esp_blufi_api.h index 8ab9ba350..a826ddfb4 100644 --- a/components/bt/bluedroid/api/include/esp_blufi_api.h +++ b/components/bt/bluedroid/api/include/esp_blufi_api.h @@ -50,6 +50,8 @@ typedef enum { ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY, /*sig_tmr); + memset(&p_cb->sig_tmr, 0, sizeof(TIMER_LIST_ENT)); + bta_sys_free_timer(&p_cb->acp_sig_tmr); + memset(&p_cb->acp_sig_tmr, 0, sizeof(TIMER_LIST_ENT)); } /******************************************************************************* diff --git a/components/bt/bluedroid/bta/av/bta_av_main.c b/components/bt/bluedroid/bta/av/bta_av_main.c index 5d5f441a6..f44186c6d 100644 --- a/components/bt/bluedroid/bta/av/bta_av_main.c +++ b/components/bt/bluedroid/bta/av/bta_av_main.c @@ -194,6 +194,8 @@ const tBTA_AV_NSM_ACT bta_av_nsm_act[] = { /* AV control block */ #if BTA_DYNAMIC_MEMORY == FALSE tBTA_AV_CB bta_av_cb; +#else +tBTA_AV_CB *bta_av_cb_ptr; #endif #if (defined(BTA_AV_DEBUG) && BTA_AV_DEBUG == TRUE) diff --git a/components/bt/bluedroid/bta/av/bta_av_sbc.c b/components/bt/bluedroid/bta/av/bta_av_sbc.c index 812876ff2..9ea59170a 100644 --- a/components/bt/bluedroid/bta/av/bta_av_sbc.c +++ b/components/bt/bluedroid/bta/av/bta_av_sbc.c @@ -28,7 +28,7 @@ #include "a2d_sbc.h" #include "bta_av_sbc.h" #include "utl.h" -#include "bt_utils.h" +#include "bt_defs.h" #if defined(BTA_AV_INCLUDED) && (BTA_AV_INCLUDED == TRUE) diff --git a/components/bt/bluedroid/bta/dm/bta_dm_act.c b/components/bt/bluedroid/bta/dm/bta_dm_act.c index e0b1595a9..f9a6b9685 100644 --- a/components/bt/bluedroid/bta/dm/bta_dm_act.c +++ b/components/bt/bluedroid/bta/dm/bta_dm_act.c @@ -282,6 +282,47 @@ void bta_dm_enable(tBTA_DM_MSG *p_data) } } +/******************************************************************************* + * + * Function bta_dm_init_cb + * + * Description Initializes the bta_dm_cb control block + * + * + * Returns void + * + ******************************************************************************/ +void bta_dm_init_cb(void) +{ + memset(&bta_dm_cb, 0, sizeof(bta_dm_cb)); +} + +/******************************************************************************* + * + * Function bta_dm_deinit_cb + * + * Description De-initializes the bta_dm_cb control block + * + * + * Returns void + * + ******************************************************************************/ +void bta_dm_deinit_cb(void) +{ + bta_sys_free_timer(&bta_dm_cb.disable_timer); +#if ( BTA_EIR_CANNED_UUID_LIST != TRUE ) + bta_sys_free_timer(&bta_dm_cb.app_ready_timer); +#endif +#if BTM_SSR_INCLUDED == TRUE + for (size_t i = 0; i < BTA_DM_NUM_PM_TIMER; i++) { + for (size_t j = 0; j < BTA_DM_PM_MODE_TIMER_MAX; j++) { + bta_sys_free_timer(&bta_dm_cb.pm_timer[i].timer[j]); + } + } +#endif + memset(&bta_dm_cb, 0, sizeof(bta_dm_cb)); +} + /******************************************************************************* ** ** Function bta_dm_sys_hw_cback @@ -318,7 +359,15 @@ static void bta_dm_sys_hw_cback( tBTA_SYS_HW_EVT status ) } /* reinitialize the control block */ - memset(&bta_dm_cb, 0, sizeof(bta_dm_cb)); + bta_dm_deinit_cb(); + + bta_sys_free_timer(&bta_dm_search_cb.search_timer); +#if ((defined BLE_INCLUDED) && (BLE_INCLUDED == TRUE)) +#if ((defined BTA_GATT_INCLUDED) && (BTA_GATT_INCLUDED == TRUE) && SDP_INCLUDED == TRUE) + bta_sys_free_timer(&bta_dm_search_cb.gatt_close_timer); +#endif +#endif + memset(&bta_dm_search_cb, 0x00, sizeof(bta_dm_search_cb)); /* unregister from SYS */ bta_sys_hw_unregister( BTA_SYS_HW_BLUETOOTH ); @@ -332,11 +381,18 @@ static void bta_dm_sys_hw_cback( tBTA_SYS_HW_EVT status ) /* save security callback */ temp_cback = bta_dm_cb.p_sec_cback; /* make sure the control block is properly initialized */ - memset(&bta_dm_cb, 0, sizeof(bta_dm_cb)); + bta_dm_init_cb(); + /* and retrieve the callback */ bta_dm_cb.p_sec_cback = temp_cback; bta_dm_cb.is_bta_dm_active = TRUE; + bta_sys_free_timer(&bta_dm_search_cb.search_timer); +#if ((defined BLE_INCLUDED) && (BLE_INCLUDED == TRUE)) +#if ((defined BTA_GATT_INCLUDED) && (BTA_GATT_INCLUDED == TRUE) && SDP_INCLUDED == TRUE) + bta_sys_free_timer(&bta_dm_search_cb.gatt_close_timer); +#endif +#endif /* hw is ready, go on with BTA DM initialization */ memset(&bta_dm_search_cb, 0x00, sizeof(bta_dm_search_cb)); #if (BTM_SSR_INCLUDED == TRUE) @@ -2589,7 +2645,7 @@ static UINT8 bta_dm_authorize_cback (BD_ADDR bd_addr, DEV_CLASS dev_class, BD_NA return BTM_NOT_AUTHORIZED; } } - + @@ -4126,6 +4182,8 @@ static void bta_dm_observe_results_cb (tBTM_INQ_RESULTS *p_inq, UINT8 *p_eir) result.inq_res.flag = p_inq->flag; result.inq_res.adv_data_len = p_inq->adv_data_len; result.inq_res.scan_rsp_len = p_inq->scan_rsp_len; + memcpy(result.inq_res.dev_class, p_inq->dev_class, sizeof(DEV_CLASS)); + result.inq_res.ble_evt_type = p_inq->ble_evt_type; /* application will parse EIR to find out remote device name */ result.inq_res.p_eir = p_eir; @@ -4282,7 +4340,7 @@ static UINT8 bta_dm_ble_smp_cback (tBTM_LE_EVT event, BD_ADDR bda, tBTM_LE_EVT_D } else { sec_event.auth_cmpl.success = TRUE; if (!p_data->complt.smp_over_br) { - + } } @@ -5338,7 +5396,7 @@ static void bta_ble_energy_info_cmpl(tBTM_BLE_TX_TIME_MS tx_time, if (BTA_SUCCESS == st) { ctrl_state = bta_dm_pm_obtain_controller_state(); } -#endif +#endif if (bta_dm_cb.p_energy_info_cback) { bta_dm_cb.p_energy_info_cback(tx_time, rx_time, idle_time, energy_used, ctrl_state, st); } diff --git a/components/bt/bluedroid/bta/dm/bta_dm_main.c b/components/bt/bluedroid/bta/dm/bta_dm_main.c index 210331b40..ed118adfb 100644 --- a/components/bt/bluedroid/bta/dm/bta_dm_main.c +++ b/components/bt/bluedroid/bta/dm/bta_dm_main.c @@ -25,6 +25,8 @@ #include "bta_api.h" #include "bta_sys.h" #include "bta_dm_int.h" +#include "allocator.h" +#include /***************************************************************************** @@ -35,6 +37,10 @@ tBTA_DM_CB bta_dm_cb; tBTA_DM_SEARCH_CB bta_dm_search_cb; tBTA_DM_DI_CB bta_dm_di_cb; +#else +tBTA_DM_CB *bta_dm_cb_ptr; +tBTA_DM_SEARCH_CB *bta_dm_search_cb_ptr; +tBTA_DM_DI_CB *bta_dm_di_cb_ptr; #endif @@ -346,6 +352,18 @@ void bta_dm_sm_disable( ) bta_sys_deregister( BTA_ID_DM ); } +void bta_dm_sm_deinit(void) +{ + memset(&bta_dm_cb, 0, sizeof(tBTA_DM_CB)); + memset(&bta_dm_search_cb, 0, sizeof(tBTA_DM_SEARCH_CB)); + memset(&bta_dm_di_cb, 0, sizeof(tBTA_DM_DI_CB)); +#if BTA_DYNAMIC_MEMORY + FREE_AND_RESET(bta_dm_cb_ptr); + FREE_AND_RESET(bta_dm_search_cb_ptr); + FREE_AND_RESET(bta_dm_di_cb_ptr); +#endif /* #if BTA_DYNAMIC_MEMORY */ +} + /******************************************************************************* ** diff --git a/components/bt/bluedroid/bta/gatt/bta_gattc_act.c b/components/bt/bluedroid/bta/gatt/bta_gattc_act.c index 5a25ebafd..37c349dad 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gattc_act.c +++ b/components/bt/bluedroid/bta/gatt/bta_gattc_act.c @@ -48,8 +48,6 @@ #if GATTC_INCLUDED == TRUE && BLE_INCLUDED == TRUE -static osi_mutex_t write_ccc_mutex; - /***************************************************************************** ** Constants *****************************************************************************/ @@ -129,9 +127,9 @@ static void bta_gattc_enable(tBTA_GATTC_CB *p_cb) memset(&bta_gattc_cb, 0, sizeof(tBTA_GATTC_CB)); p_cb->state = BTA_GATTC_STATE_ENABLED; // Create a write ccc mutex when the gatt client enable - osi_mutex_new(&write_ccc_mutex); + osi_mutex_new(&bta_gattc_cb.write_ccc_mutex); } else { - APPL_TRACE_DEBUG("GATTC is arelady enabled"); + APPL_TRACE_DEBUG("GATTC is already enabled"); } } @@ -157,7 +155,7 @@ void bta_gattc_disable(tBTA_GATTC_CB *p_cb) return; } // Free the write ccc mutex when the gatt client disable - osi_mutex_free(&write_ccc_mutex); + osi_mutex_free(&bta_gattc_cb.write_ccc_mutex); for (i = 0; i < BTA_GATTC_CL_MAX; i ++) { if (p_cb->cl_rcb[i].in_use) { @@ -1644,8 +1642,8 @@ static void bta_gattc_conn_cback(tGATT_IF gattc_if, BD_ADDR bda, UINT16 conn_id, else if ((transport == BT_TRANSPORT_LE) && (connected == FALSE) && (p_conn != NULL)){ p_conn->service_change_ccc_written = FALSE; if (p_conn->ccc_timer_used == TRUE){ - assert(write_ccc_mutex != NULL); - osi_mutex_lock(&write_ccc_mutex, OSI_MUTEX_MAX_TIMEOUT); + assert(bta_gattc_cb.write_ccc_mutex != NULL); + osi_mutex_lock(&bta_gattc_cb.write_ccc_mutex, OSI_MUTEX_MAX_TIMEOUT); if (p_conn->service_change_ccc_timer.param != 0) { osi_free((void *)p_conn->service_change_ccc_timer.param); @@ -1653,7 +1651,7 @@ static void bta_gattc_conn_cback(tGATT_IF gattc_if, BD_ADDR bda, UINT16 conn_id, } bta_sys_stop_timer(&(p_conn->service_change_ccc_timer)); p_conn->ccc_timer_used = FALSE; - osi_mutex_unlock(&write_ccc_mutex); + osi_mutex_unlock(&bta_gattc_cb.write_ccc_mutex); } } @@ -2173,7 +2171,7 @@ void bta_gattc_broadcast(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_msg) cb_data.reg_oper.client_if = p_msg->api_listen.client_if; cb_data.reg_oper.status = BTM_BleBroadcast(p_msg->api_listen.start, NULL); - + //TODO need modify callback if used if (p_clreg && p_clreg->p_cback) { (*p_clreg->p_cback)(BTA_GATTC_LISTEN_EVT, &cb_data); } @@ -2354,14 +2352,14 @@ static void bta_gattc_wait4_service_change_ccc_cback (TIMER_LIST_ENT *p_tle) BOOLEAN start_ccc_timer = FALSE; UINT32 new_timeout; - assert(write_ccc_mutex != NULL); - osi_mutex_lock(&write_ccc_mutex, OSI_MUTEX_MAX_TIMEOUT); + assert(bta_gattc_cb.write_ccc_mutex != NULL); + osi_mutex_lock(&bta_gattc_cb.write_ccc_mutex, OSI_MUTEX_MAX_TIMEOUT); tBTA_GATTC_WAIT_CCC_TIMER *p_timer_param = (tBTA_GATTC_WAIT_CCC_TIMER*) p_tle->param; p_tle->param = (TIMER_PARAM_TYPE)0; if (p_timer_param == NULL){ APPL_TRACE_ERROR("p_timer_param is NULL in %s\n", __func__); - osi_mutex_unlock(&write_ccc_mutex); + osi_mutex_unlock(&bta_gattc_cb.write_ccc_mutex); return; } @@ -2369,7 +2367,7 @@ static void bta_gattc_wait4_service_change_ccc_cback (TIMER_LIST_ENT *p_tle) if (p_conn == NULL){ APPL_TRACE_ERROR("p_conn is NULL in %s\n", __func__); osi_free(p_timer_param); - osi_mutex_unlock(&write_ccc_mutex); + osi_mutex_unlock(&bta_gattc_cb.write_ccc_mutex); return; } @@ -2401,7 +2399,7 @@ static void bta_gattc_wait4_service_change_ccc_cback (TIMER_LIST_ENT *p_tle) } osi_free(p_timer_param); - osi_mutex_unlock(&write_ccc_mutex); + osi_mutex_unlock(&bta_gattc_cb.write_ccc_mutex); } #endif diff --git a/components/bt/bluedroid/bta/gatt/bta_gattc_main.c b/components/bt/bluedroid/bta/gatt/bta_gattc_main.c index 0551c0871..f09b3d47f 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gattc_main.c +++ b/components/bt/bluedroid/bta/gatt/bta_gattc_main.c @@ -29,6 +29,7 @@ #include #include "bta_gattc_int.h" +#include "allocator.h" /***************************************************************************** @@ -237,6 +238,8 @@ const tBTA_GATTC_ST_TBL bta_gattc_st_tbl[] = { /* GATTC control block */ #if BTA_DYNAMIC_MEMORY == FALSE tBTA_GATTC_CB bta_gattc_cb; +#else +tBTA_GATTC_CB *bta_gattc_cb_ptr; #endif #if BTA_GATT_DEBUG == TRUE @@ -493,4 +496,12 @@ static char *gattc_state_code(tBTA_GATTC_STATE state_code) } #endif /* Debug Functions */ + +void bta_gattc_deinit(void) +{ +#if BTA_DYNAMIC_MEMORY + memset(bta_gattc_cb_ptr, 0, sizeof(tBTA_GATTC_CB)); + FREE_AND_RESET(bta_gattc_cb_ptr); +#endif /* #if BTA_DYNAMIC_MEMORY */ +} #endif /* GATTC_INCLUDED == TRUE && BLE_INCLUDED == TRUE */ diff --git a/components/bt/bluedroid/bta/gatt/bta_gattc_utils.c b/components/bt/bluedroid/bta/gatt/bta_gattc_utils.c index 70655fd94..4e3cf9737 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gattc_utils.c +++ b/components/bt/bluedroid/bta/gatt/bta_gattc_utils.c @@ -862,6 +862,10 @@ BOOLEAN bta_gattc_conn_dealloc(BD_ADDR remote_bda) if (p_conn != NULL) { p_conn->in_use = FALSE; memset(p_conn->remote_bda, 0, BD_ADDR_LEN); + osi_mutex_lock(&bta_gattc_cb.write_ccc_mutex, OSI_MUTEX_MAX_TIMEOUT); + bta_sys_free_timer(&p_conn->service_change_ccc_timer); + p_conn->ccc_timer_used = FALSE; + osi_mutex_unlock(&bta_gattc_cb.write_ccc_mutex); return TRUE; } return FALSE; diff --git a/components/bt/bluedroid/bta/gatt/bta_gatts_main.c b/components/bt/bluedroid/bta/gatt/bta_gatts_main.c index dc0cb425c..2ca2da566 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gatts_main.c +++ b/components/bt/bluedroid/bta/gatt/bta_gatts_main.c @@ -29,6 +29,7 @@ #include #include "bta_gatts_int.h" +#include "allocator.h" /* type for service building action functions */ typedef void (*tBTA_GATTS_SRVC_ACT)(tBTA_GATTS_SRVC_CB *p_rcb, tBTA_GATTS_DATA *p_data); @@ -46,6 +47,8 @@ const tBTA_GATTS_SRVC_ACT bta_gatts_srvc_build_act[] = { /* GATTS control block */ #if BTA_DYNAMIC_MEMORY == FALSE tBTA_GATTS_CB bta_gatts_cb; +#else +tBTA_GATTS_CB *bta_gatts_cb_ptr; #endif /******************************************************************************* @@ -135,4 +138,12 @@ BOOLEAN bta_gatts_hdl_event(BT_HDR *p_msg) return (TRUE); } +void bta_gatts_deinit(void) +{ + memset(&bta_gatts_cb, 0, sizeof(tBTA_GATTS_CB)); +#if BTA_DYNAMIC_MEMORY + FREE_AND_RESET(bta_gatts_cb_ptr); +#endif /* #if BTA_DYNAMIC_MEMORY */ +} + #endif /* GATTS_INCLUDED */ diff --git a/components/bt/bluedroid/bta/hh/bta_hh_main.c b/components/bt/bluedroid/bta/hh/bta_hh_main.c index aeee1061e..0737d4080 100644 --- a/components/bt/bluedroid/bta/hh/bta_hh_main.c +++ b/components/bt/bluedroid/bta/hh/bta_hh_main.c @@ -245,6 +245,8 @@ const tBTA_HH_ST_TBL bta_hh_st_tbl[] = { *****************************************************************************/ #if BTA_DYNAMIC_MEMORY == FALSE tBTA_HH_CB bta_hh_cb; +#else +tBTA_HH_CB *bta_hh_cb_ptr; #endif /***************************************************************************** ** Static functions diff --git a/components/bt/bluedroid/bta/ar/bta_ar_int.h b/components/bt/bluedroid/bta/include/bta_ar_int.h similarity index 100% rename from components/bt/bluedroid/bta/ar/bta_ar_int.h rename to components/bt/bluedroid/bta/include/bta_ar_int.h diff --git a/components/bt/bluedroid/bta/av/bta_av_int.h b/components/bt/bluedroid/bta/include/bta_av_int.h similarity index 100% rename from components/bt/bluedroid/bta/av/bta_av_int.h rename to components/bt/bluedroid/bta/include/bta_av_int.h diff --git a/components/bt/bluedroid/bta/dm/bta_dm_int.h b/components/bt/bluedroid/bta/include/bta_dm_int.h similarity index 99% rename from components/bt/bluedroid/bta/dm/bta_dm_int.h rename to components/bt/bluedroid/bta/include/bta_dm_int.h index 663791223..10f21726d 100644 --- a/components/bt/bluedroid/bta/dm/bta_dm_int.h +++ b/components/bt/bluedroid/bta/include/bta_dm_int.h @@ -1130,6 +1130,7 @@ extern tBTA_DM_DI_CB *bta_dm_di_cb_ptr; extern BOOLEAN bta_dm_sm_execute(BT_HDR *p_msg); extern void bta_dm_sm_disable( void ); +extern void bta_dm_sm_deinit(void); extern BOOLEAN bta_dm_search_sm_execute(BT_HDR *p_msg); extern void bta_dm_search_sm_disable( void ); diff --git a/components/bt/bluedroid/bta/include/bta_gattc_int.h b/components/bt/bluedroid/bta/include/bta_gattc_int.h index 59810b223..78d8e969e 100644 --- a/components/bt/bluedroid/bta/include/bta_gattc_int.h +++ b/components/bt/bluedroid/bta/include/bta_gattc_int.h @@ -30,6 +30,7 @@ #include "bta_gattc_ci.h" #include "bta_gattc_co.h" #include "fixed_queue.h" +#include "mutex.h" /***************************************************************************** ** Constants and data types @@ -357,8 +358,8 @@ enum { }; typedef struct { - UINT8 state; - + UINT8 state; + osi_mutex_t write_ccc_mutex; tBTA_GATTC_CONN conn_track[BTA_GATTC_CONN_MAX]; tBTA_GATTC_BG_TCK bg_track[BTA_GATTC_KNOWN_SR_MAX]; tBTA_GATTC_RCB cl_rcb[BTA_GATTC_CL_MAX]; @@ -514,5 +515,6 @@ extern BOOLEAN bta_gattc_conn_dealloc(BD_ADDR remote_bda); extern bool bta_gattc_cache_load(tBTA_GATTC_CLCB *p_clcb); extern void bta_gattc_cache_reset(BD_ADDR server_bda); +extern void bta_gattc_deinit(void); #endif /* BTA_GATTC_INT_H */ diff --git a/components/bt/bluedroid/bta/include/bta_gatts_int.h b/components/bt/bluedroid/bta/include/bta_gatts_int.h index 211929f20..9d04423f3 100644 --- a/components/bt/bluedroid/bta/include/bta_gatts_int.h +++ b/components/bt/bluedroid/bta/include/bta_gatts_int.h @@ -249,7 +249,7 @@ extern UINT8 bta_gatts_find_app_rcb_idx_by_app_if(tBTA_GATTS_CB *p_cb, tBTA_GATT extern UINT8 bta_gatts_alloc_srvc_cb(tBTA_GATTS_CB *p_cb, UINT8 rcb_idx); extern tBTA_GATTS_SRVC_CB *bta_gatts_find_srvc_cb_by_srvc_id(tBTA_GATTS_CB *p_cb, UINT16 service_id); extern tBTA_GATTS_SRVC_CB *bta_gatts_find_srvc_cb_by_attr_id(tBTA_GATTS_CB *p_cb, UINT16 attr_id); - +extern void bta_gatts_deinit(void); #endif /* BTA_GATTS_INT_H */ diff --git a/components/bt/bluedroid/bta/hh/bta_hh_int.h b/components/bt/bluedroid/bta/include/bta_hh_int.h similarity index 100% rename from components/bt/bluedroid/bta/hh/bta_hh_int.h rename to components/bt/bluedroid/bta/include/bta_hh_int.h diff --git a/components/bt/bluedroid/bta/sdp/bta_sdp_int.h b/components/bt/bluedroid/bta/include/bta_sdp_int.h similarity index 100% rename from components/bt/bluedroid/bta/sdp/bta_sdp_int.h rename to components/bt/bluedroid/bta/include/bta_sdp_int.h diff --git a/components/bt/bluedroid/bta/include/bta_sys.h b/components/bt/bluedroid/bta/include/bta_sys.h index 94ebd8050..011900869 100644 --- a/components/bt/bluedroid/bta/include/bta_sys.h +++ b/components/bt/bluedroid/bta/include/bta_sys.h @@ -224,6 +224,7 @@ extern UINT16 bta_sys_get_sys_features(void); extern void bta_sys_sendmsg(void *p_msg); extern void bta_sys_start_timer(TIMER_LIST_ENT *p_tle, UINT16 type, INT32 timeout_ms); extern void bta_sys_stop_timer(TIMER_LIST_ENT *p_tle); +extern void bta_sys_free_timer(TIMER_LIST_ENT *p_tle); extern void bta_sys_disable(tBTA_SYS_HW_MODULE module); extern UINT32 bta_sys_get_remaining_ticks(TIMER_LIST_ENT *p_target_tle); diff --git a/components/bt/bluedroid/bta/sdp/bta_sdp.c b/components/bt/bluedroid/bta/sdp/bta_sdp.c index f079d2f92..61cbbd62e 100644 --- a/components/bt/bluedroid/bta/sdp/bta_sdp.c +++ b/components/bt/bluedroid/bta/sdp/bta_sdp.c @@ -37,6 +37,8 @@ #if BTA_DYNAMIC_MEMORY == FALSE tBTA_SDP_CB bta_sdp_cb; +#else +tBTA_SDP_CB *bta_sdp_cb_ptr; #endif /* state machine action enumeration list */ diff --git a/components/bt/bluedroid/bta/sys/bta_sys_main.c b/components/bt/bluedroid/bta/sys/bta_sys_main.c index 48eec5f11..590c907ef 100644 --- a/components/bt/bluedroid/bta/sys/bta_sys_main.c +++ b/components/bt/bluedroid/bta/sys/bta_sys_main.c @@ -48,6 +48,8 @@ /* system manager control block definition */ #if BTA_DYNAMIC_MEMORY == FALSE tBTA_SYS_CB bta_sys_cb; +#else +tBTA_SYS_CB *bta_sys_cb_ptr; #endif static hash_map_t *bta_alarm_hash_map; @@ -190,6 +192,9 @@ void bta_sys_free(void) { hash_map_free(bta_alarm_hash_map); osi_mutex_free(&bta_alarm_lock); +#if BTA_DYNAMIC_MEMORY + FREE_AND_RESET(bta_sys_cb_ptr); +#endif } /******************************************************************************* @@ -652,6 +657,28 @@ void bta_sys_stop_timer(TIMER_LIST_ENT *p_tle) osi_alarm_cancel(alarm); } +/******************************************************************************* +** +** Function bta_sys_free_timer +** +** Description Stop and free a BTA timer. +** +** Returns void +** +*******************************************************************************/ +void bta_sys_free_timer(TIMER_LIST_ENT *p_tle) +{ + assert(p_tle != NULL); + + osi_alarm_t *alarm = hash_map_get(bta_alarm_hash_map, p_tle); + if (alarm == NULL) { + LOG_DEBUG("%s expected alarm was not in bta alarm hash map.", __func__); + return; + } + osi_alarm_cancel(alarm); + hash_map_erase(bta_alarm_hash_map, p_tle); +} + /******************************************************************************* ** ** Function bta_sys_disable diff --git a/components/bt/bluedroid/btc/core/btc_ble_storage.c b/components/bt/bluedroid/btc/core/btc_ble_storage.c index 3e7238cc4..75e33d7e6 100644 --- a/components/bt/bluedroid/btc/core/btc_ble_storage.c +++ b/components/bt/bluedroid/btc/core/btc_ble_storage.c @@ -23,8 +23,13 @@ #if (SMP_INCLUDED == TRUE) +//the maximum nubmer of bonded devices +#define BONED_DEVICES_MAX_COUNT (BTM_SEC_MAX_DEVICE_RECORDS) + static void _btc_storage_save(void) { + uint16_t addr_section_count = 0; + const btc_config_section_iter_t *need_remove_iter = NULL; const btc_config_section_iter_t *iter = btc_config_section_begin(); while (iter != btc_config_section_end()) { @@ -48,10 +53,25 @@ static void _btc_storage_save(void) btc_config_remove_section(section); continue; } - + if(addr_section_count == BONED_DEVICES_MAX_COUNT) { + need_remove_iter = iter; + } + addr_section_count ++; iter = btc_config_section_next(iter); } - + /*exceeded the maximum nubmer of bonded devices, delete them */ + if (need_remove_iter) { + while(need_remove_iter != btc_config_section_end()) { + const char *need_remove_section = btc_config_section_name(need_remove_iter); + if (!string_is_bdaddr(need_remove_section)) { + need_remove_iter = btc_config_section_next(need_remove_iter); + continue; + } + need_remove_iter = btc_config_section_next(need_remove_iter); + BTIF_TRACE_WARNING("exceeded the maximum nubmer of bonded devices, delete the last device info : %s", need_remove_section); + btc_config_remove_section(need_remove_section); + } + } btc_config_flush(); } diff --git a/components/bt/bluedroid/btc/core/btc_dm.c b/components/bt/bluedroid/btc/core/btc_dm.c index 9c74a8abf..1f6322b86 100644 --- a/components/bt/bluedroid/btc/core/btc_dm.c +++ b/components/bt/bluedroid/btc/core/btc_dm.c @@ -429,8 +429,8 @@ void btc_dm_sec_cb_handler(btc_msg_t *msg) switch (msg->act) { case BTA_DM_ENABLE_EVT: { btc_clear_services_mask(); - btc_storage_load_bonded_devices(); #if (SMP_INCLUDED == TRUE) + btc_storage_load_bonded_devices(); //load the bonding device to the btm layer btc_storage_load_bonded_ble_devices(); #endif ///SMP_INCLUDED == TRUE diff --git a/components/bt/bluedroid/btc/core/btc_main.c b/components/bt/bluedroid/btc/core/btc_main.c index 2317074c8..cb5975be1 100644 --- a/components/bt/bluedroid/btc/core/btc_main.c +++ b/components/bt/bluedroid/btc/core/btc_main.c @@ -20,7 +20,10 @@ #include "btc_config.h" #include "alarm.h" #include "btc_ble_storage.h" -#include "bta_gatt_common.h" +#include "btc_gap_ble.h" +#include "bta_gattc_int.h" +#include "bta_gatts_int.h" +#include "bta_dm_int.h" static future_t *main_future[BTC_MAIN_FUTURE_NUM]; @@ -57,8 +60,8 @@ static void btc_init_bluetooth(void) osi_alarm_create_mux(); osi_alarm_init(); bte_main_boot_entry(btc_init_callback); - btc_config_init(); #if (SMP_INCLUDED) + btc_config_init(); //load the ble local key whitch has been store in the flash btc_dm_load_ble_local_keys(); #endif /* #if (SMP_INCLUDED) */ @@ -67,18 +70,23 @@ static void btc_init_bluetooth(void) static void btc_deinit_bluetooth(void) { + btc_gap_ble_deinit(); + bta_dm_sm_deinit(); +#if (GATTC_INCLUDED) + bta_gattc_deinit(); +#endif /* #if (GATTC_INCLUDED) */ +#if (GATTS_INCLUDED) + bta_gatts_deinit(); +#endif /* #if (GATTS_INCLUDED) */ bte_main_shutdown(); +#if (SMP_INCLUDED) btc_config_clean_up(); +#endif osi_alarm_deinit(); osi_alarm_delete_mux(); future_ready(*btc_main_get_future_p(BTC_MAIN_DEINIT_FUTURE), FUTURE_SUCCESS); } -static void btc_set_local_mtu(uint16_t mtu) -{ - BTA_GATT_SetLocalMTU(mtu); -} - void btc_main_call_handler(btc_msg_t *msg) { LOG_DEBUG("%s act %d\n", __func__, msg->act); @@ -96,12 +104,6 @@ void btc_main_call_handler(btc_msg_t *msg) case BTC_MAIN_ACT_DISABLE: btc_disable_bluetooth(); break; - case BTC_GATT_ACT_SET_LOCAL_MTU: - { - btc_ble_main_args_t *arg = (btc_ble_main_args_t *)(msg->arg); - btc_set_local_mtu(arg->set_mtu.mtu); - break; - } default: LOG_ERROR("%s UNKNOWN ACT %d\n", __func__, msg->act); break; diff --git a/components/bt/bluedroid/btc/core/btc_task.c b/components/bt/bluedroid/btc/core/btc_task.c index 0d039c630..004b67f16 100644 --- a/components/bt/bluedroid/btc/core/btc_task.c +++ b/components/bt/bluedroid/btc/core/btc_task.c @@ -24,20 +24,21 @@ #include "btc_dev.h" #include "btc_gatts.h" #include "btc_gattc.h" +#include "btc_gatt_common.h" #include "btc_gap_ble.h" #include "btc_blufi_prf.h" #include "btc_dm.h" #include "btc_alarm.h" #include "bta_gatt_api.h" #if CONFIG_CLASSIC_BT_ENABLED +#include "btc_profile_queue.h" #if (BTC_GAP_BT_INCLUDED == TRUE) #include "btc_gap_bt.h" #endif /* BTC_GAP_BT_INCLUDED == TRUE */ -#include "btc_profile_queue.h" #if BTC_AV_INCLUDED #include "btc_av.h" #include "btc_avrc.h" -#endif /* #if BTC_AV_INCLUDED */ +#endif /* #if BTC_AV_INCLUDED */ #if CONFIG_BT_SPP_ENABLED #include "btc_spp.h" #endif /* #if CONFIG_BT_SPP_ENABLED */ @@ -48,33 +49,36 @@ static xTaskHandle xBtcTaskHandle = NULL; static xQueueHandle xBtcQueue = 0; static btc_func_t profile_tab[BTC_PID_NUM] = { - [BTC_PID_MAIN_INIT] = {btc_main_call_handler, NULL }, - [BTC_PID_DEV] = {btc_dev_call_handler, NULL }, + [BTC_PID_MAIN_INIT] = {btc_main_call_handler, NULL }, + [BTC_PID_DEV] = {btc_dev_call_handler, NULL }, #if (GATTS_INCLUDED == TRUE) - [BTC_PID_GATTS] = {btc_gatts_call_handler, btc_gatts_cb_handler }, + [BTC_PID_GATTS] = {btc_gatts_call_handler, btc_gatts_cb_handler }, #endif ///GATTS_INCLUDED == TRUE #if (GATTC_INCLUDED == TRUE) - [BTC_PID_GATTC] = {btc_gattc_call_handler, btc_gattc_cb_handler }, + [BTC_PID_GATTC] = {btc_gattc_call_handler, btc_gattc_cb_handler }, #endif ///GATTC_INCLUDED == TRUE - [BTC_PID_GAP_BLE] = {btc_gap_ble_call_handler, btc_gap_ble_cb_handler }, - [BTC_PID_BLE_HID] = {NULL, NULL}, - [BTC_PID_SPPLIKE] = {NULL, NULL}, +#if (GATTS_INCLUDED == TRUE || GATTC_INCLUDED == TRUE) + [BTC_PID_GATT_COMMON] = {btc_gatt_com_call_handler, NULL }, +#endif //GATTC_INCLUDED == TRUE || GATTS_INCLUDED == TRUE + [BTC_PID_GAP_BLE] = {btc_gap_ble_call_handler, btc_gap_ble_cb_handler }, + [BTC_PID_BLE_HID] = {NULL, NULL}, + [BTC_PID_SPPLIKE] = {NULL, NULL}, #if (GATTS_INCLUDED == TRUE) - [BTC_PID_BLUFI] = {btc_blufi_call_handler, btc_blufi_cb_handler }, + [BTC_PID_BLUFI] = {btc_blufi_call_handler, btc_blufi_cb_handler }, #endif ///GATTS_INCLUDED == TRUE - [BTC_PID_DM_SEC] = {NULL, btc_dm_sec_cb_handler }, - [BTC_PID_ALARM] = {btc_alarm_handler, NULL }, + [BTC_PID_DM_SEC] = {NULL, btc_dm_sec_cb_handler }, + [BTC_PID_ALARM] = {btc_alarm_handler, NULL }, #if CONFIG_CLASSIC_BT_ENABLED #if (BTC_GAP_BT_INCLUDED == TRUE) - [BTC_PID_GAP_BT] = {btc_gap_bt_call_handler, NULL }, + [BTC_PID_GAP_BT] = {btc_gap_bt_call_handler, NULL }, #endif /* (BTC_GAP_BT_INCLUDED == TRUE) */ - [BTC_PID_PRF_QUE] = {btc_profile_queue_handler, NULL }, + [BTC_PID_PRF_QUE] = {btc_profile_queue_handler, NULL }, #if BTC_AV_INCLUDED - [BTC_PID_A2DP] = {btc_a2dp_call_handler, btc_a2dp_cb_handler }, - [BTC_PID_AVRC] = {btc_avrc_call_handler, NULL }, + [BTC_PID_A2DP] = {btc_a2dp_call_handler, btc_a2dp_cb_handler }, + [BTC_PID_AVRC] = {btc_avrc_call_handler, NULL }, #endif /* #if BTC_AV_INCLUDED */ #if CONFIG_BT_SPP_ENABLED - [BTC_PID_SPP] = {btc_spp_call_handler, btc_spp_cb_handler }, + [BTC_PID_SPP] = {btc_spp_call_handler, btc_spp_cb_handler }, #endif /* #if CONFIG_BT_SPP_ENABLED */ #endif /* #if CONFIG_CLASSIC_BT_ENABLED */ }; diff --git a/components/bt/bluedroid/btc/include/btc_main.h b/components/bt/bluedroid/btc/include/btc_main.h index 523e1bb98..b95ae0bbe 100644 --- a/components/bt/bluedroid/btc/include/btc_main.h +++ b/components/bt/bluedroid/btc/include/btc_main.h @@ -26,7 +26,6 @@ typedef enum { BTC_MAIN_ACT_DEINIT, BTC_MAIN_ACT_ENABLE, BTC_MAIN_ACT_DISABLE, - BTC_GATT_ACT_SET_LOCAL_MTU, } btc_main_act_t; typedef enum { @@ -61,13 +60,5 @@ bt_status_t btc_init_bluetooth(future_t *future); void btc_deinit_bluetooth(future_t *future); #endif -/* btc_ble_gattc_args_t */ -typedef union { - //BTC_GATT_ACT_SET_LOCAL_MTU, - struct set_mtu_arg { - uint16_t mtu; - } set_mtu; -} btc_ble_main_args_t; - void btc_main_call_handler(btc_msg_t *msg); #endif /* __BTC_BT_MAIN_H__ */ diff --git a/components/bt/bluedroid/btc/include/btc_task.h b/components/bt/bluedroid/btc/include/btc_task.h index f19c26699..16388c685 100644 --- a/components/bt/bluedroid/btc/include/btc_task.h +++ b/components/bt/bluedroid/btc/include/btc_task.h @@ -41,6 +41,7 @@ typedef enum { #if (GATTC_INCLUDED == TRUE) BTC_PID_GATTC, #endif ///GATTC_INCLUDED == TRUE + BTC_PID_GATT_COMMON, BTC_PID_GAP_BLE, BTC_PID_BLE_HID, BTC_PID_SPPLIKE, diff --git a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c index 8ffafd7e8..9fc0b5e89 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c @@ -31,6 +31,7 @@ #include "btc_blufi_prf.h" #include "btc_task.h" #include "btc_manage.h" +#include "btc_gatt_util.h" #include "blufi_int.h" @@ -209,6 +210,9 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) break; case BTA_GATTS_CONF_EVT: LOG_DEBUG("CONIRM EVT\n"); + if (p_data && p_data->req_data.value){ + osi_free(p_data->req_data.value); + } /* Nothing */ break; case BTA_GATTS_CREATE_EVT: @@ -276,7 +280,7 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) msg.pid = BTC_PID_BLUFI; msg.act = ESP_BLUFI_EVENT_BLE_CONNECT; memcpy(param.connect.remote_bda, p_data->conn.remote_bda, sizeof(esp_bd_addr_t)); - param.connect.conn_id=p_data->conn.conn_id; + param.connect.conn_id=BTC_GATT_GET_CONN_ID(p_data->conn.conn_id); param.connect.server_if=p_data->conn.server_if; btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); break; @@ -359,6 +363,17 @@ static void btc_blufi_send_notify(uint8_t *pkt, int pkt_len) pkt, rsp); } +void btc_blufi_report_error(esp_blufi_error_state_t state) +{ + btc_msg_t msg; + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_REPORT_ERROR; + esp_blufi_cb_param_t param; + param.report_error.state = state; + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); +} + static void btc_blufi_recv_handler(uint8_t *data, int len) { struct blufi_hdr *hdr = (struct blufi_hdr *)data; @@ -367,6 +382,7 @@ static void btc_blufi_recv_handler(uint8_t *data, int len) if (hdr->seq != blufi_env.recv_seq) { LOG_ERROR("%s seq %d is not expect %d\n", __func__, hdr->seq, blufi_env.recv_seq + 1); + btc_blufi_report_error(ESP_BLUFI_SEQUENCE_ERROR); return; } @@ -378,6 +394,7 @@ static void btc_blufi_recv_handler(uint8_t *data, int len) ret = blufi_env.cbs->decrypt_func(hdr->seq, hdr->data, hdr->data_len); if (ret != hdr->data_len) { /* enc must be success and enc len must equal to plain len */ LOG_ERROR("%s decrypt error %d\n", __func__, ret); + btc_blufi_report_error(ESP_BLUFI_DECRYPT_ERROR); return; } } @@ -389,6 +406,7 @@ static void btc_blufi_recv_handler(uint8_t *data, int len) checksum_pkt = hdr->data[hdr->data_len] | (((uint16_t) hdr->data[hdr->data_len + 1]) << 8); if (checksum != checksum_pkt) { LOG_ERROR("%s checksum error %04x, pkt %04x\n", __func__, checksum, checksum_pkt); + btc_blufi_report_error(ESP_BLUFI_CHECKSUM_ERROR); return; } } @@ -464,7 +482,7 @@ void btc_blufi_send_encap(uint8_t type, uint8_t *data, int total_data_len) checksum = blufi_env.cbs->checksum_func(hdr->seq, &hdr->seq, hdr->data_len + 2); memcpy(&hdr->data[hdr->data_len], &checksum, 2); } - } else if (!BLUFI_TYPE_IS_DATA_NEG(hdr->type)) { + } else if (!BLUFI_TYPE_IS_DATA_NEG(hdr->type) && !BLUFI_TYPE_IS_DATA_ERROR_INFO(hdr->type)) { if ((blufi_env.sec_mode & BLUFI_DATA_SEC_MODE_CHECK_MASK) && (blufi_env.cbs && blufi_env.cbs->checksum_func)) { hdr->fc |= BLUFI_FC_CHECK; @@ -479,6 +497,7 @@ void btc_blufi_send_encap(uint8_t type, uint8_t *data, int total_data_len) hdr->fc |= BLUFI_FC_ENC; } else { LOG_ERROR("%s encrypt error %d\n", __func__, ret); + btc_blufi_report_error(ESP_BLUFI_ENCRYPT_ERROR); osi_free(hdr); return; } @@ -574,6 +593,40 @@ static void btc_blufi_wifi_conn_report(uint8_t opmode, uint8_t sta_conn_state, u osi_free(data); } +void btc_blufi_send_wifi_list(uint16_t apCount, esp_blufi_ap_record_t *list) +{ + uint8_t type; + uint8_t *data; + int data_len; + uint8_t *p; + // malloc size: (len + RSSI + ssid buffer) * apCount; + uint malloc_size = (1 + 1 + sizeof(list->ssid)) * apCount; + p = data = osi_malloc(malloc_size); + if (data == NULL) { + LOG_ERROR("malloc error\n"); + return; + } + type = BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_WIFI_LIST); + for (int i = 0; i < apCount; ++i) + { + uint len = strlen((const char *)list[i].ssid); + data_len = (p - data); + //current_len + ssid + rssi + total_len_value + if((data_len + len + 1 + 1) > malloc_size) { + LOG_ERROR("%s len error", __func__); + osi_free(data); + return; + } + *p++ = len + 1; // length of ssid + rssi + *p++ = list[i].rssi; + memcpy(p, list[i].ssid, len); + p = p + len; + } + data_len = (p - data); + btc_blufi_send_encap(type, data, data_len); + osi_free(data); +} + static void btc_blufi_send_ack(uint8_t seq) { uint8_t type; @@ -584,6 +637,28 @@ static void btc_blufi_send_ack(uint8_t seq) btc_blufi_send_encap(type, &data, 1); } +static void btc_blufi_send_error_info(uint8_t state) +{ + uint8_t type; + uint8_t *data; + int data_len; + uint8_t *p; + + data_len = 1; + p = data = osi_malloc(data_len); + if (data == NULL) { + return; + } + + type = BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_ERROR_INFO); + *p++ = state; + if (p - data > data_len) { + LOG_ERROR("%s len error %d %d\n", __func__, (int)(p - data), data_len); + } + + btc_blufi_send_encap(type, data, data_len); + osi_free(data); +} void btc_blufi_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { @@ -737,6 +812,9 @@ void btc_blufi_cb_handler(btc_msg_t *msg) case ESP_BLUFI_EVENT_GET_WIFI_STATUS: btc_blufi_cb_to_app(ESP_BLUFI_EVENT_GET_WIFI_STATUS, NULL); break; + case ESP_BLUFI_EVENT_GET_WIFI_LIST: + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_GET_WIFI_LIST, NULL); + break; case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA: btc_blufi_cb_to_app(ESP_BLUFI_EVENT_DEAUTHENTICATE_STA, NULL); break; @@ -785,6 +863,9 @@ void btc_blufi_cb_handler(btc_msg_t *msg) case ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE: btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE, param); break; + case ESP_BLUFI_EVENT_REPORT_ERROR: + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_REPORT_ERROR, param); + break; default: LOG_ERROR("%s UNKNOWN %d\n", __func__, msg->act); break; @@ -867,6 +948,19 @@ void btc_blufi_call_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) } break; } + case BTC_BLUFI_ACT_SEND_WIFI_LIST:{ + esp_blufi_ap_record_t *list = src->wifi_list.list; + src->wifi_list.list = NULL; + if (list == NULL || src->wifi_list.apCount <= 0) { + break; + } + dst->wifi_list.list = (esp_blufi_ap_record_t *)osi_malloc(sizeof(esp_blufi_ap_record_t) * src->wifi_list.apCount); + if (dst->wifi_list.list == NULL) { + break; + } + memcpy(dst->wifi_list.list, list, sizeof(esp_blufi_ap_record_t) * src->wifi_list.apCount); + break; + } default: break; } @@ -898,6 +992,13 @@ void btc_blufi_call_deep_free(btc_msg_t *msg) osi_free(info); break; } + case BTC_BLUFI_ACT_SEND_WIFI_LIST:{ + esp_blufi_ap_record_t *list = (esp_blufi_ap_record_t *)arg->wifi_list.list; + if (list){ + osi_free(list); + } + break; + } default: break; } @@ -921,6 +1022,13 @@ void btc_blufi_call_handler(btc_msg_t *msg) arg->wifi_conn_report.extra_info, arg->wifi_conn_report.extra_info_len); break; + case BTC_BLUFI_ACT_SEND_WIFI_LIST:{ + btc_blufi_send_wifi_list(arg->wifi_list.apCount, arg->wifi_list.list); + break; + } + case BTC_BLUFI_ACT_SEND_ERR_INFO: + btc_blufi_send_error_info(arg->blufi_err_infor.state); + break; default: LOG_ERROR("%s UNKNOWN %d\n", __func__, msg->act); break; diff --git a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c index 14cdb5721..23dcdfc01 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c @@ -106,6 +106,12 @@ void btc_blufi_protocol_handler(uint8_t type, uint8_t *data, int len) msg.act = ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE; btc_transfer_context(&msg, NULL, 0, NULL); break; + case BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_LIST: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_GET_WIFI_LIST; + btc_transfer_context(&msg, NULL, 0, NULL); + break; default: LOG_ERROR("%s Unkown Ctrl pkt %02x\n", __func__, type); break; diff --git a/components/bt/bluedroid/btc/profile/esp/blufi/include/blufi_int.h b/components/bt/bluedroid/btc/profile/esp/blufi/include/blufi_int.h index bfac52942..102142979 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/include/blufi_int.h +++ b/components/bt/bluedroid/btc/profile/esp/blufi/include/blufi_int.h @@ -16,7 +16,7 @@ #define __BLUFI_INT_H__ #define BTC_BLUFI_GREAT_VER 0x01 //Version + Subversion -#define BTC_BLUFI_SUB_VER 0x00 //Version + Subversion +#define BTC_BLUFI_SUB_VER 0x01 //Version + Subversion #define BTC_BLUFI_VERSION ((BTC_BLUFI_GREAT_VER<<8)|BTC_BLUFI_SUB_VER) //Version + Subversion /* service engine control block */ @@ -93,6 +93,7 @@ typedef struct blufi_frag_hdr blufi_frag_hdr_t; #define BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA 0x06 #define BLUFI_TYPE_CTRL_SUBTYPE_GET_VERSION 0x07 #define BLUFI_TYPE_CTRL_SUBTYPE_DISCONNECT_BLE 0x08 +#define BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_LIST 0x09 #define BLUFI_TYPE_DATA 0x1 #define BLUFI_TYPE_DATA_SUBTYPE_NEG 0x00 @@ -111,7 +112,9 @@ typedef struct blufi_frag_hdr blufi_frag_hdr_t; #define BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY 0x0d #define BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY 0x0e #define BLUFI_TYPE_DATA_SUBTYPE_WIFI_REP 0x0f -#define BLUFI_TYPE_DATA_SUBTYPE_REPLY_VERSION 0x10 +#define BLUFI_TYPE_DATA_SUBTYPE_REPLY_VERSION 0x10 +#define BLUFI_TYPE_DATA_SUBTYPE_WIFI_LIST 0x11 +#define BLUFI_TYPE_DATA_SUBTYPE_ERROR_INFO 0x12 #define BLUFI_TYPE_IS_CTRL(type) (BLUFI_GET_TYPE((type)) == BLUFI_TYPE_CTRL) #define BLUFI_TYPE_IS_DATA(type) (BLUFI_GET_TYPE((type)) == BLUFI_TYPE_DATA) @@ -140,6 +143,7 @@ typedef struct blufi_frag_hdr blufi_frag_hdr_t; #define BLUFI_TYPE_IS_DATA_SERVER_CERT(type) (BLUFI_TYPE_IS_DATA((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_DATA_SUBTYPE_SERVER_CERT) #define BLUFI_TYPE_IS_DATA_CLIENT_PRIV_KEY(type) (BLUFI_TYPE_IS_DATA((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY) #define BLUFI_TYPE_IS_DATA_SERVER_PRIV_KEY(type) (BLUFI_TYPE_IS_DATA((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY) +#define BLUFI_TYPE_IS_DATA_ERROR_INFO(type) (BLUFI_TYPE_IS_DATA((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_DATA_SUBTYPE_ERROR_INFO) // packet frame control #define BLUFI_FC_ENC_MASK 0x01 diff --git a/components/bt/bluedroid/btc/profile/esp/include/btc_blufi_prf.h b/components/bt/bluedroid/btc/profile/esp/include/btc_blufi_prf.h index 1d82d0c9a..9cb27da27 100644 --- a/components/bt/bluedroid/btc/profile/esp/include/btc_blufi_prf.h +++ b/components/bt/bluedroid/btc/profile/esp/include/btc_blufi_prf.h @@ -23,6 +23,8 @@ typedef enum { BTC_BLUFI_ACT_INIT = 0, BTC_BLUFI_ACT_DEINIT, BTC_BLUFI_ACT_SEND_CFG_REPORT, + BTC_BLUFI_ACT_SEND_WIFI_LIST, + BTC_BLUFI_ACT_SEND_ERR_INFO, } btc_blufi_act_t; typedef union { @@ -33,6 +35,19 @@ typedef union { esp_blufi_extra_info_t *extra_info; int extra_info_len; } wifi_conn_report; + /* + BTC_BLUFI_ACT_SEND_WIFI_LIST + */ + struct blufi_wifi_list { + uint16_t apCount; + esp_blufi_ap_record_t *list; + } wifi_list; + /* + BTC_BLUFI_ACT_SEND_ERR_INFO + */ + struct blufi_error_infor { + esp_blufi_error_state_t state; + } blufi_err_infor; } btc_blufi_args_t; void btc_blufi_cb_handler(btc_msg_t *msg); diff --git a/components/bt/bluedroid/btc/profile/std/a2dp/bta_av_co.c b/components/bt/bluedroid/btc/profile/std/a2dp/bta_av_co.c index 59c06d589..d6c360544 100644 --- a/components/bt/bluedroid/btc/profile/std/a2dp/bta_av_co.c +++ b/components/bt/bluedroid/btc/profile/std/a2dp/bta_av_co.c @@ -31,7 +31,8 @@ #include "bta_av_co.h" #include "bta_av_ci.h" #include "bta_av_sbc.h" -#include "btc_media.h" +#include "btc_a2dp.h" +#include "btc_a2dp_source.h" #include "btc_av_co.h" #include "btc_util.h" #include "mutex.h" @@ -954,12 +955,13 @@ extern void bta_av_co_audio_stop(tBTA_AV_HNDL hndl, tBTA_AV_CODEC codec_type) void *bta_av_co_audio_src_data_path(tBTA_AV_CODEC codec_type, UINT32 *p_len, UINT32 *p_timestamp) { +#if BTC_AV_SRC_INCLUDED BT_HDR *p_buf; UNUSED(p_len); FUNC_TRACE(); - p_buf = btc_media_aa_readbuf(); + p_buf = btc_a2dp_source_audio_readbuf(); if (p_buf != NULL) { switch (codec_type) { case BTA_AV_CODEC_SBC: @@ -992,6 +994,9 @@ void *bta_av_co_audio_src_data_path(tBTA_AV_CODEC codec_type, UINT32 *p_len, #endif } return p_buf; +#else /* BTC_AV_SRC_INCLUDED */ + return NULL; +#endif /* BTC_AV_SRC_INCLUDED */ } /******************************************************************************* @@ -1379,7 +1384,7 @@ static BOOLEAN bta_av_co_audio_media_supports_config(UINT8 codec_type, const UIN ** Returns TRUE if all opened devices support this codec, FALSE otherwise ** *******************************************************************************/ -BOOLEAN bta_av_co_audio_codec_supported(tBTC_STATUS *p_status) +BOOLEAN bta_av_co_audio_codec_supported(tBTC_AV_STATUS *p_status) { UINT8 index; UINT8 snk_index; @@ -1449,7 +1454,7 @@ BOOLEAN bta_av_co_audio_codec_supported(tBTC_STATUS *p_status) } } - *p_status = BTC_SUCCESS; + *p_status = BTC_AV_SUCCESS; return TRUE; } @@ -1488,7 +1493,7 @@ void bta_av_co_audio_codec_reset(void) ** Returns TRUE if successful, FALSE otherwise ** *******************************************************************************/ -BOOLEAN bta_av_co_audio_set_codec(const tBTC_AV_MEDIA_FEEDINGS *p_feeding, tBTC_STATUS *p_status) +BOOLEAN bta_av_co_audio_set_codec(const tBTC_AV_MEDIA_FEEDINGS *p_feeding, tBTC_AV_STATUS *p_status) { tA2D_SBC_CIE sbc_config; tBTC_AV_CODEC_INFO new_cfg; @@ -1555,7 +1560,7 @@ BOOLEAN bta_av_co_audio_set_codec(const tBTC_AV_MEDIA_FEEDINGS *p_feeding, tBTC_ /* Check all devices support it */ - *p_status = BTC_SUCCESS; + *p_status = BTC_AV_SUCCESS; return bta_av_co_audio_codec_supported(p_status); } diff --git a/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp.c b/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp.c new file mode 100644 index 000000000..29004c2f2 --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp.c @@ -0,0 +1,153 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/***************************************************************************** + * + * Filename: btc_a2dp.c + * + *****************************************************************************/ +#include "bt_target.h" +#include "bt_trace.h" +#include "bta_api.h" +#include "bta_av_api.h" +#include "btc_av.h" +#include "btc_av_co.h" +#include "btc_a2dp.h" +#include "btc_a2dp_control.h" +#include "btc_a2dp_sink.h" +#include "btc_a2dp_source.h" + + +#if BTC_AV_INCLUDED + +/***************************************************************************** +** +** Function btc_a2dp_on_init +** +*******************************************************************************/ +void btc_a2dp_on_init(void) +{ + //tput_mon(1, 0, 1); +} + +/***************************************************************************** +** +** Function btc_a2dp_on_idle +** +*******************************************************************************/ + +void btc_a2dp_on_idle(void) +{ + APPL_TRACE_EVENT("## ON A2DP IDLE ## peer_sep = %d", btc_av_get_peer_sep()); +#if BTC_AV_SRC_INCLUDED + if (btc_av_get_peer_sep() == AVDT_TSEP_SNK) { + btc_a2dp_source_on_idle(); + } +#endif // BTC_AV_SRC_INCLUDED + + bta_av_co_init(); + +#if BTC_AV_SINK_INCLUDED + if (btc_av_get_peer_sep() == AVDT_TSEP_SRC) { + btc_a2dp_sink_on_idle(); + } +#endif // BTC_AV_SINK_INCLUDED +} + +/***************************************************************************** +** +** Function btc_a2dp_on_started +** +** Description +** +** Returns +** +*******************************************************************************/ + +BOOLEAN btc_a2dp_on_started(tBTA_AV_START *p_av, BOOLEAN pending_start) +{ + BOOLEAN ack = FALSE; + + APPL_TRACE_EVENT("## ON A2DP STARTED ##"); +#if BTC_AV_SRC_INCLUDED + if (p_av == NULL) { + /* ack back a local start request */ + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + return TRUE; + } + + if (p_av->status == BTA_AV_SUCCESS) { + if (p_av->suspending == FALSE) { + if (p_av->initiator) { + if (pending_start) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + ack = TRUE; + } + } else { + /* we were remotely started, make sure codec + is setup before datapath is started */ + btc_a2dp_source_setup_codec(); + } + + /* media task is autostarted upon a2dp audiopath connection */ + } + } else if (pending_start) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); + ack = TRUE; + } +#endif /* BTC_AV_SRC_INCLUDED */ + return ack; +} + +/***************************************************************************** +** +** Function btc_a2dp_on_stopped +** +*******************************************************************************/ + +void btc_a2dp_on_stopped(tBTA_AV_SUSPEND *p_av) +{ + APPL_TRACE_EVENT("## ON A2DP STOPPED ##"); +#if BTC_AV_SINK_INCLUDED + if (btc_av_get_peer_sep() == AVDT_TSEP_SRC) { + btc_a2dp_sink_on_stopped(p_av); + return; + } +#endif // BTC_AV_SINK_INCLUDED + +#if BTC_AV_SRC_INCLUDED + btc_a2dp_source_on_stopped(p_av); +#endif // BTC_AV_SRC_INCLUDED +} + +/***************************************************************************** +** +** Function btc_a2dp_on_suspended +** +*******************************************************************************/ +void btc_a2dp_on_suspended(tBTA_AV_SUSPEND *p_av) +{ + APPL_TRACE_EVENT("## ON A2DP SUSPENDED ##"); +#if BTC_AV_SINK_INCLUDED + if (btc_av_get_peer_sep() == AVDT_TSEP_SRC) { + btc_a2dp_sink_on_suspended(p_av); + return; + } +#endif // BTC_AV_SINK_INCLUDED +#if BTC_AV_SRC_INCLUDED + btc_a2dp_source_on_suspended(p_av); +#endif // BTC_AV_SRC_INCLUDED +} + +#endif /* #if BTC_AV_INCLUDED */ diff --git a/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_control.c b/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_control.c new file mode 100644 index 000000000..c3ae55572 --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_control.c @@ -0,0 +1,220 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/***************************************************************************** + * + * Filename: btc_a2dp_control.c + * + *****************************************************************************/ +#include "bt_target.h" +#include +#include "bt_trace.h" +#include "bta_api.h" +#include "bta_av_api.h" +#include "btc_manage.h" +#include "btc_av.h" +#include "btc_a2dp.h" +#include "btc_a2dp_control.h" +#include "btc_a2dp_sink.h" +#include "btc_a2dp_source.h" +#include "esp_a2dp_api.h" + +#if BTC_AV_INCLUDED + +typedef struct { + BOOLEAN data_channel_open; + UINT8 a2dp_cmd_pending; /* we can have max one command pending */ +} tBTC_AA_CTRL_CB; + +static tBTC_AA_CTRL_CB btc_aa_ctrl_cb; + +static inline void btc_a2d_cb_to_app(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + esp_a2d_cb_t btc_a2d_cb = (esp_a2d_cb_t)btc_profile_cb_get(BTC_PID_A2DP); + if (btc_a2d_cb) { + btc_a2d_cb(event, param); + } +} + +static inline void a2dp_cmd_acknowledge(int cmd, int status) +{ + esp_a2d_cb_param_t param; + + param.media_ctrl_stat.cmd = cmd; + param.media_ctrl_stat.status = status; + + btc_a2d_cb_to_app(ESP_A2D_MEDIA_CTRL_ACK_EVT, ¶m); +} + +void btc_a2dp_control_command_ack(int status) +{ + /* sanity check */ + if (btc_aa_ctrl_cb.a2dp_cmd_pending == ESP_A2D_MEDIA_CTRL_NONE) { + APPL_TRACE_ERROR("warning : no command pending, ignore ack"); + return; + } + + /* clear pending */ + int cmd = btc_aa_ctrl_cb.a2dp_cmd_pending; + btc_aa_ctrl_cb.a2dp_cmd_pending = ESP_A2D_MEDIA_CTRL_NONE; + + a2dp_cmd_acknowledge(cmd, status); +} + +static void btc_a2dp_datapath_open(void) +{ +#if BTC_AV_SRC_INCLUDED + if (btc_av_get_peer_sep() == AVDT_TSEP_SNK) { + /* Start the media task to encode SBC */ + btc_a2dp_source_start_audio_req(); + + /* make sure we update any changed sbc encoder params */ + btc_a2dp_source_encoder_update(); + } +#endif + btc_aa_ctrl_cb.data_channel_open = TRUE; +} + +BOOLEAN btc_a2dp_control_get_datachnl_stat(void) +{ + return btc_aa_ctrl_cb.data_channel_open; +} + +void btc_a2dp_control_set_datachnl_stat(BOOLEAN open) +{ + btc_aa_ctrl_cb.data_channel_open = open; +} + +static void btc_a2dp_dispatch_datapath_evt(uint32_t dp_evt) +{ + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_DATAPATH_CTRL_EVT; + + btc_av_args_t arg; + memset(&arg, 0, sizeof(btc_av_args_t)); + arg.dp_evt = dp_evt; + + /* Switch to BTC context */ + APPL_TRACE_DEBUG("%s sig %u act %u, dp_evt %u\n", __func__, msg.sig, msg.act, arg.dp_evt); + btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL); +} + +void btc_a2dp_control_media_ctrl(esp_a2d_media_ctrl_t ctrl) +{ + APPL_TRACE_DEBUG("BTC MEDIA (A2DP-DATA) EVENT %u", ctrl); + + if (btc_aa_ctrl_cb.a2dp_cmd_pending != ESP_A2D_MEDIA_CTRL_NONE) { + APPL_TRACE_DEBUG("un-acked a2dp cmd: %u", btc_aa_ctrl_cb.a2dp_cmd_pending); + a2dp_cmd_acknowledge(ctrl, ESP_A2D_MEDIA_CTRL_ACK_BUSY); + return; + } + + btc_aa_ctrl_cb.a2dp_cmd_pending = ctrl; + + switch (ctrl) { + case ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY: +#if BTC_AV_SRC_INCLUDED + if (btc_a2dp_source_is_task_shutting_down()) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); + } else if ((btc_av_stream_ready() == TRUE) || + (btc_av_stream_started_ready() == TRUE)) { + /* check whether av is ready to setup a2dp datapath */ + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + } else { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); + } +#else /* BTC_AV_SRC_INCLUDED */ + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); +#endif /* #if BTC_AV_SRC_INCLUDED */ + break; + case ESP_A2D_MEDIA_CTRL_START: + if (btc_av_stream_ready() == TRUE ) { + /* post start event and wait for audio path to open */ + btc_dispatch_sm_event(BTC_AV_START_STREAM_REQ_EVT, NULL, 0); + + btc_a2dp_dispatch_datapath_evt(BTC_AV_DATAPATH_OPEN_EVT); +#if (BTC_AV_SINK_INCLUDED == TRUE) + if (btc_av_get_peer_sep() == AVDT_TSEP_SRC) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + } +#endif + } else if (btc_av_stream_started_ready()) { + btc_a2dp_dispatch_datapath_evt(BTC_AV_DATAPATH_OPEN_EVT); + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + } else { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); + } + break; + case ESP_A2D_MEDIA_CTRL_STOP: +#if BTC_AV_SRC_INCLUDED + if (btc_av_get_peer_sep() == AVDT_TSEP_SNK && !btc_a2dp_source_is_streaming()) { + /* we are already stopped, just ack back*/ + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + break; + } +#endif /* BTC_AV_SRC_INCLUDED */ + btc_dispatch_sm_event(BTC_AV_STOP_STREAM_REQ_EVT, NULL, 0); +#if (BTC_AV_SINK_INCLUDED == TRUE) + if (btc_av_get_peer_sep() == AVDT_TSEP_SRC) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + } +#endif + break; + + case ESP_A2D_MEDIA_CTRL_SUSPEND: + /* local suspend */ + if (btc_av_stream_started_ready()) { + btc_dispatch_sm_event(BTC_AV_SUSPEND_STREAM_REQ_EVT, NULL, 0); + } else { + /* we are not in started state; just ack back ok. This can happen if we are + remotely suspended; clear REMOTE SUSPEND Flag */ + btc_av_clear_remote_suspend_flag(); + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + } + break; + + default : + APPL_TRACE_ERROR("### A2DP-MEDIA EVENT %u NOT HANDLED ###", ctrl); + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); + break; + } +} + +void btc_a2dp_control_datapath_ctrl(uint32_t dp_evt) +{ + switch (dp_evt) { + case BTC_AV_DATAPATH_OPEN_EVT: { + btc_a2dp_datapath_open(); + break; + } + default: + break; + } + return; +} + +bool btc_a2dp_control_init(void) +{ + memset(&btc_aa_ctrl_cb, 0, sizeof(tBTC_AA_CTRL_CB)); + return true; +} + +void btc_a2dp_control_cleanup(void) +{ + memset(&btc_aa_ctrl_cb, 0, sizeof(tBTC_AA_CTRL_CB)); +} + +#endif /* #if BTC_AV_INCLUDED */ diff --git a/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c b/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c new file mode 100644 index 000000000..671584448 --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c @@ -0,0 +1,763 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/****************************************************************************** + ** + ** Name: btc_a2dp_sink.c + ** + ******************************************************************************/ +#include "bt_target.h" +#include "bt_trace.h" +#include +#include +#include "bt_defs.h" +#include "allocator.h" +#include "mutex.h" +#include "thread.h" +#include "fixed_queue.h" +#include "a2d_api.h" +#include "a2d_sbc.h" +#include "bta_av_api.h" +#include "bta_av_ci.h" +#include "btc_av_co.h" +#include "btc_a2dp.h" +#include "btc_a2dp_control.h" +#include "btc_a2dp_sink.h" +#include "btc_manage.h" +#include "btc_av.h" +#include "btc_util.h" +#include "esp_a2dp_api.h" +#include "oi_codec_sbc.h" +#include "oi_status.h" + +#if (BTC_AV_SINK_INCLUDED == TRUE) + +/***************************************************************************** + ** Constants + *****************************************************************************/ + +/* BTC media cmd event definition : BTC_MEDIA_TASK_CMD */ +enum { + BTC_MEDIA_TASK_SINK_INIT, + BTC_MEDIA_TASK_SINK_CLEAN_UP, + BTC_MEDIA_FLUSH_AA_RX, + BTC_MEDIA_AUDIO_SINK_CFG_UPDATE, + BTC_MEDIA_AUDIO_SINK_CLEAR_TRACK, +}; + +enum { + BTC_A2DP_SINK_STATE_OFF = 0, + BTC_A2DP_SINK_STATE_ON = 1, + BTC_A2DP_SINK_STATE_SHUTTING_DOWN = 2 +}; + +enum { + BTC_A2DP_SINK_DATA_EVT = 0, +}; + +/* + * CONGESTION COMPENSATION CTRL :: + * + * Thus setting controls how many buffers we will hold in media task + * during temp link congestion. Together with the stack buffer queues + * it controls much temporary a2dp link congestion we can + * compensate for. It however also depends on the default run level of sinks + * jitterbuffers. Depending on type of sink this would vary. + * Ideally the (SRC) max tx buffer capacity should equal the sinks + * jitterbuffer runlevel including any intermediate buffers on the way + * towards the sinks codec. + */ + +/* fixme -- define this in pcm time instead of buffer count */ + +/* The typical runlevel of the tx queue size is ~1 buffer + but due to link flow control or thread preemption in lower + layers we might need to temporarily buffer up data */ + +/* 5 frames is equivalent to 6.89*5*2.9 ~= 100 ms @ 44.1 khz, 20 ms mediatick */ +#define MAX_OUTPUT_A2DP_SNK_FRAME_QUEUE_SZ (5) + +typedef struct { + UINT16 num_frames_to_be_processed; + UINT16 len; + UINT16 offset; + UINT16 layer_specific; +} tBT_SBC_HDR; + +typedef struct { + BOOLEAN rx_flush; /* discards any incoming data when true */ + UINT8 channel_count; + fixed_queue_t *RxSbcQ; + UINT32 sample_rate; +} tBTC_A2DP_SINK_CB; + +static void btc_a2dp_sink_thread_init(UNUSED_ATTR void *context); +static void btc_a2dp_sink_thread_cleanup(UNUSED_ATTR void *context); +static void btc_a2dp_sink_flush_q(fixed_queue_t *p_q); +static void btc_a2dp_sink_rx_flush(void); +static int btc_a2dp_sink_get_track_frequency(UINT8 frequency); +static int btc_a2dp_sink_get_track_channel_count(UINT8 channeltype); +/* Handle incoming media packets A2DP SINK streaming*/ +static void btc_a2dp_sink_handle_inc_media(tBT_SBC_HDR *p_msg); +static void btc_a2dp_sink_handle_decoder_reset(tBTC_MEDIA_SINK_CFG_UPDATE *p_msg); +static void btc_a2dp_sink_handle_clear_track(void); +static BOOLEAN btc_a2dp_sink_clear_track(void); +static void btc_a2dp_sink_task_handler(void *arg); + +static void btc_a2dp_sink_data_ready(UNUSED_ATTR void *context); + +static tBTC_A2DP_SINK_CB btc_aa_snk_cb; +static int btc_a2dp_sink_state = BTC_A2DP_SINK_STATE_OFF; +static xTaskHandle btc_aa_snk_task_hdl = NULL; +static QueueHandle_t btc_aa_snk_data_queue = NULL; +static QueueHandle_t btc_aa_snk_ctrl_queue = NULL; +static QueueSetHandle_t btc_aa_snk_queue_set; + +static esp_a2d_sink_data_cb_t bt_aa_snk_data_cb = NULL; + +void btc_a2dp_sink_reg_data_cb(esp_a2d_sink_data_cb_t callback) +{ + // todo: critical section protection + bt_aa_snk_data_cb = callback; +} + +static inline void btc_a2d_data_cb_to_app(const uint8_t *data, uint32_t len) +{ + // todo: critical section protection + if (bt_aa_snk_data_cb) { + bt_aa_snk_data_cb(data, len); + } +} + +OI_CODEC_SBC_DECODER_CONTEXT context; +OI_UINT32 contextData[CODEC_DATA_WORDS(2, SBC_CODEC_FAST_FILTER_BUFFERS)]; +OI_INT16 pcmData[15 * SBC_MAX_SAMPLES_PER_FRAME * SBC_MAX_CHANNELS]; + +/***************************************************************************** + ** Misc helper functions + *****************************************************************************/ +static inline void btc_a2d_cb_to_app(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + esp_a2d_cb_t btc_aa_cb = (esp_a2d_cb_t)btc_profile_cb_get(BTC_PID_A2DP); + if (btc_aa_cb) { + btc_aa_cb(event, param); + } +} + +/***************************************************************************** + ** BTC ADAPTATION + *****************************************************************************/ + +static void btc_a2dp_sink_ctrl_post(uint32_t sig, void *par) +{ + BtTaskEvt_t *evt = (BtTaskEvt_t *)osi_malloc(sizeof(BtTaskEvt_t)); + if (evt == NULL) { + return; + } + + evt->sig = sig; + evt->par = par; + + if (xQueueSend(btc_aa_snk_ctrl_queue, &evt, portMAX_DELAY) != pdTRUE) { + APPL_TRACE_WARNING("btc_aa_snk_ctrl_queue failed, sig 0x%x\n", sig); + } +} + +static void btc_a2dp_sink_ctrl_handler(BtTaskEvt_t *e) +{ + if (e == NULL) { + return; + } + switch (e->sig) { + case BTC_MEDIA_TASK_SINK_INIT: + btc_a2dp_sink_thread_init(NULL); + break; + case BTC_MEDIA_TASK_SINK_CLEAN_UP: + btc_a2dp_sink_thread_cleanup(NULL); + break; + case BTC_MEDIA_AUDIO_SINK_CFG_UPDATE: + btc_a2dp_sink_handle_decoder_reset(e->par); + break; + case BTC_MEDIA_AUDIO_SINK_CLEAR_TRACK: + btc_a2dp_sink_handle_clear_track(); + break; + case BTC_MEDIA_FLUSH_AA_RX: + btc_a2dp_sink_rx_flush(); + break; + default: + APPL_TRACE_WARNING("media task unhandled evt: 0x%x\n", e->sig); + } + if (e->par != NULL) { + osi_free(e->par); + } +} + +static void btc_a2dp_sink_task_handler(void *arg) +{ + QueueSetMemberHandle_t xActivatedMember; + BtTaskEvt_t *e = NULL; + for (;;) { + xActivatedMember = xQueueSelectFromSet(btc_aa_snk_queue_set, portMAX_DELAY); + if (xActivatedMember == btc_aa_snk_data_queue) { + int32_t data_evt; + xQueueReceive(xActivatedMember, &data_evt, 0); + if (data_evt == BTC_A2DP_SINK_DATA_EVT) { + btc_a2dp_sink_data_ready(NULL); + } + } else if (xActivatedMember == btc_aa_snk_ctrl_queue) { + xQueueReceive(xActivatedMember, &e, 0); + btc_a2dp_sink_ctrl_handler(e); + osi_free(e); + } + } +} + +bool btc_a2dp_sink_startup(void) +{ + if (btc_a2dp_sink_state != BTC_A2DP_SINK_STATE_OFF) { + APPL_TRACE_ERROR("warning : media task already running"); + return false; + } + + APPL_TRACE_EVENT("## A2DP SINK START MEDIA THREAD ##"); + + btc_aa_snk_queue_set = xQueueCreateSet(BTC_A2DP_SINK_TASK_QUEUE_SET_LEN); + configASSERT(btc_aa_snk_queue_set); + btc_aa_snk_data_queue = xQueueCreate(BTC_A2DP_SINK_DATA_QUEUE_LEN, sizeof(int32_t)); + configASSERT(btc_aa_snk_data_queue); + xQueueAddToSet(btc_aa_snk_data_queue, btc_aa_snk_queue_set); + + btc_aa_snk_ctrl_queue = xQueueCreate(BTC_A2DP_SINK_CTRL_QUEUE_LEN, sizeof(void *)); + configASSERT(btc_aa_snk_ctrl_queue); + xQueueAddToSet(btc_aa_snk_ctrl_queue, btc_aa_snk_queue_set); + + if (!btc_aa_snk_data_queue || !btc_aa_snk_ctrl_queue || !btc_aa_snk_queue_set ) { + goto error_exit; + } + + xTaskCreatePinnedToCore(btc_a2dp_sink_task_handler, BTC_A2DP_SINK_TASK_NAME, BTC_A2DP_SINK_TASK_STACK_SIZE, NULL, BTC_A2DP_SINK_TASK_PRIO, &btc_aa_snk_task_hdl, BTC_A2DP_SINK_TASK_PINNED_TO_CORE); + if (btc_aa_snk_task_hdl == NULL) { + goto error_exit; + } + + btc_a2dp_sink_ctrl_post(BTC_MEDIA_TASK_SINK_INIT, NULL); + + APPL_TRACE_EVENT("## A2DP SINK MEDIA THREAD STARTED ##\n"); + + return true; + +error_exit:; + APPL_TRACE_ERROR("%s unable to start up media thread\n", __func__); + + if (btc_aa_snk_task_hdl != NULL) { + vTaskDelete(btc_aa_snk_task_hdl); + btc_aa_snk_task_hdl = NULL; + } + + if (btc_aa_snk_data_queue) { + vQueueDelete(btc_aa_snk_data_queue); + btc_aa_snk_data_queue = NULL; + } + if (btc_aa_snk_ctrl_queue) { + vQueueDelete(btc_aa_snk_ctrl_queue); + btc_aa_snk_ctrl_queue = NULL; + } + + return false; +} + +void btc_a2dp_sink_shutdown(void) +{ + APPL_TRACE_EVENT("## A2DP SINK STOP MEDIA THREAD ##\n"); + + // Exit thread + btc_a2dp_sink_ctrl_post(BTC_MEDIA_TASK_SINK_CLEAN_UP, NULL); + + vTaskDelete(btc_aa_snk_task_hdl); + btc_aa_snk_task_hdl = NULL; + + vQueueDelete(btc_aa_snk_data_queue); + btc_aa_snk_data_queue = NULL; + + vQueueDelete(btc_aa_snk_ctrl_queue); + btc_aa_snk_ctrl_queue = NULL; +} + +/***************************************************************************** +** +** Function btc_a2dp_sink_on_idle +** +*******************************************************************************/ + +void btc_a2dp_sink_on_idle(void) +{ + btc_aa_snk_cb.rx_flush = TRUE; + btc_a2dp_sink_rx_flush_req(); + btc_a2dp_sink_clear_track(); + + APPL_TRACE_DEBUG("Stopped BT track"); +} + +/***************************************************************************** +** +** Function btc_a2dp_sink_on_stopped +** +*******************************************************************************/ + +void btc_a2dp_sink_on_stopped(tBTA_AV_SUSPEND *p_av) +{ + btc_aa_snk_cb.rx_flush = TRUE; + btc_a2dp_sink_rx_flush_req(); + btc_a2dp_control_set_datachnl_stat(FALSE); +} + +/***************************************************************************** +** +** Function btc_a2dp_on_suspended +** +*******************************************************************************/ + +void btc_a2dp_sink_on_suspended(tBTA_AV_SUSPEND *p_av) +{ + btc_aa_snk_cb.rx_flush = TRUE; + btc_a2dp_sink_rx_flush_req(); + return; +} + +static void btc_a2dp_sink_data_post(int32_t data_type) +{ + if (xQueueSend(btc_aa_snk_data_queue, &data_type, 0) != pdTRUE) { + APPL_TRACE_DEBUG("Media data Q filled\n"); + } +} + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_clear_track + ** + ** Description + ** + ** Returns TRUE is success + ** + *******************************************************************************/ +static BOOLEAN btc_a2dp_sink_clear_track(void) +{ + btc_a2dp_sink_ctrl_post(BTC_MEDIA_AUDIO_SINK_CLEAR_TRACK, NULL); + return TRUE; +} + +/* when true media task discards any rx frames */ +void btc_a2dp_sink_set_rx_flush(BOOLEAN enable) +{ + APPL_TRACE_EVENT("## DROP RX %d ##\n", enable); + btc_aa_snk_cb.rx_flush = enable; +} + +/***************************************************************************** +** +** Function btc_a2dp_sink_reset_decoder +** +** Description +** +** Returns +** +*******************************************************************************/ + +void btc_a2dp_sink_reset_decoder(UINT8 *p_av) +{ + APPL_TRACE_EVENT("btc reset decoder"); + APPL_TRACE_DEBUG("btc reset decoder p_codec_info[%x:%x:%x:%x:%x:%x]\n", + p_av[1], p_av[2], p_av[3], + p_av[4], p_av[5], p_av[6]); + + tBTC_MEDIA_SINK_CFG_UPDATE *p_buf; + if (NULL == (p_buf = osi_malloc(sizeof(tBTC_MEDIA_SINK_CFG_UPDATE)))) { + APPL_TRACE_ERROR("btc reset decoder No Buffer "); + return; + } + + memcpy(p_buf->codec_info, p_av, AVDT_CODEC_SIZE); + btc_a2dp_sink_ctrl_post(BTC_MEDIA_AUDIO_SINK_CFG_UPDATE, p_buf); +} + +static void btc_a2dp_sink_data_ready(UNUSED_ATTR void *context) +{ + tBT_SBC_HDR *p_msg; + + if (fixed_queue_is_empty(btc_aa_snk_cb.RxSbcQ)) { + APPL_TRACE_DEBUG(" QUE EMPTY "); + } else { + if (btc_aa_snk_cb.rx_flush == TRUE) { + btc_a2dp_sink_flush_q(btc_aa_snk_cb.RxSbcQ); + return; + } + + while ((p_msg = (tBT_SBC_HDR *)fixed_queue_try_peek_first(btc_aa_snk_cb.RxSbcQ)) != NULL ) { + btc_a2dp_sink_handle_inc_media(p_msg); + p_msg = (tBT_SBC_HDR *)fixed_queue_try_dequeue(btc_aa_snk_cb.RxSbcQ); + if ( p_msg == NULL ) { + APPL_TRACE_ERROR("Insufficient data in que "); + break; + } + osi_free(p_msg); + } + APPL_TRACE_DEBUG(" Process Frames - "); + } +} + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_handle_decoder_reset + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_sink_handle_decoder_reset(tBTC_MEDIA_SINK_CFG_UPDATE *p_msg) +{ + tBTC_MEDIA_SINK_CFG_UPDATE *p_buf = p_msg; + tA2D_STATUS a2d_status; + tA2D_SBC_CIE sbc_cie; + OI_STATUS status; + UINT32 freq_multiple = 48 * 20; /* frequency multiple for 20ms of data , initialize with 48K*/ + UINT32 num_blocks = 16; + UINT32 num_subbands = 8; + + APPL_TRACE_EVENT("%s p_codec_info[%x:%x:%x:%x:%x:%x]\n", __FUNCTION__, + p_buf->codec_info[1], p_buf->codec_info[2], p_buf->codec_info[3], + p_buf->codec_info[4], p_buf->codec_info[5], p_buf->codec_info[6]); + + a2d_status = A2D_ParsSbcInfo(&sbc_cie, p_buf->codec_info, FALSE); + if (a2d_status != A2D_SUCCESS) { + APPL_TRACE_ERROR("ERROR dump_codec_info A2D_ParsSbcInfo fail:%d\n", a2d_status); + return; + } + + btc_aa_snk_cb.sample_rate = btc_a2dp_sink_get_track_frequency(sbc_cie.samp_freq); + btc_aa_snk_cb.channel_count = btc_a2dp_sink_get_track_channel_count(sbc_cie.ch_mode); + + btc_aa_snk_cb.rx_flush = FALSE; + APPL_TRACE_EVENT("Reset to sink role"); + status = OI_CODEC_SBC_DecoderReset(&context, contextData, sizeof(contextData), 2, 2, FALSE); + if (!OI_SUCCESS(status)) { + APPL_TRACE_ERROR("OI_CODEC_SBC_DecoderReset failed with error code %d\n", status); + } + + btc_a2dp_control_set_datachnl_stat(TRUE); + + switch (sbc_cie.samp_freq) { + case A2D_SBC_IE_SAMP_FREQ_16: + APPL_TRACE_DEBUG("\tsamp_freq:%d (16000)\n", sbc_cie.samp_freq); + freq_multiple = 16 * 20; + break; + case A2D_SBC_IE_SAMP_FREQ_32: + APPL_TRACE_DEBUG("\tsamp_freq:%d (32000)\n", sbc_cie.samp_freq); + freq_multiple = 32 * 20; + break; + case A2D_SBC_IE_SAMP_FREQ_44: + APPL_TRACE_DEBUG("\tsamp_freq:%d (44100)\n", sbc_cie.samp_freq); + freq_multiple = 441 * 2; + break; + case A2D_SBC_IE_SAMP_FREQ_48: + APPL_TRACE_DEBUG("\tsamp_freq:%d (48000)\n", sbc_cie.samp_freq); + freq_multiple = 48 * 20; + break; + default: + APPL_TRACE_DEBUG(" Unknown Frequency "); + break; + } + + switch (sbc_cie.ch_mode) { + case A2D_SBC_IE_CH_MD_MONO: + APPL_TRACE_DEBUG("\tch_mode:%d (Mono)\n", sbc_cie.ch_mode); + break; + case A2D_SBC_IE_CH_MD_DUAL: + APPL_TRACE_DEBUG("\tch_mode:%d (DUAL)\n", sbc_cie.ch_mode); + break; + case A2D_SBC_IE_CH_MD_STEREO: + APPL_TRACE_DEBUG("\tch_mode:%d (STEREO)\n", sbc_cie.ch_mode); + break; + case A2D_SBC_IE_CH_MD_JOINT: + APPL_TRACE_DEBUG("\tch_mode:%d (JOINT)\n", sbc_cie.ch_mode); + break; + default: + APPL_TRACE_DEBUG(" Unknown Mode "); + break; + } + + switch (sbc_cie.block_len) { + case A2D_SBC_IE_BLOCKS_4: + APPL_TRACE_DEBUG("\tblock_len:%d (4)\n", sbc_cie.block_len); + num_blocks = 4; + break; + case A2D_SBC_IE_BLOCKS_8: + APPL_TRACE_DEBUG("\tblock_len:%d (8)\n", sbc_cie.block_len); + num_blocks = 8; + break; + case A2D_SBC_IE_BLOCKS_12: + APPL_TRACE_DEBUG("\tblock_len:%d (12)\n", sbc_cie.block_len); + num_blocks = 12; + break; + case A2D_SBC_IE_BLOCKS_16: + APPL_TRACE_DEBUG("\tblock_len:%d (16)\n", sbc_cie.block_len); + num_blocks = 16; + break; + default: + APPL_TRACE_DEBUG(" Unknown BlockLen "); + break; + } + + switch (sbc_cie.num_subbands) { + case A2D_SBC_IE_SUBBAND_4: + APPL_TRACE_DEBUG("\tnum_subbands:%d (4)\n", sbc_cie.num_subbands); + num_subbands = 4; + break; + case A2D_SBC_IE_SUBBAND_8: + APPL_TRACE_DEBUG("\tnum_subbands:%d (8)\n", sbc_cie.num_subbands); + num_subbands = 8; + break; + default: + APPL_TRACE_DEBUG(" Unknown SubBands "); + break; + } + + switch (sbc_cie.alloc_mthd) { + case A2D_SBC_IE_ALLOC_MD_S: + APPL_TRACE_DEBUG("\talloc_mthd:%d (SNR)\n", sbc_cie.alloc_mthd); + break; + case A2D_SBC_IE_ALLOC_MD_L: + APPL_TRACE_DEBUG("\talloc_mthd:%d (Loudness)\n", sbc_cie.alloc_mthd); + break; + default: + APPL_TRACE_DEBUG(" Unknown Allocation Method"); + break; + } + + APPL_TRACE_EVENT("\tBit pool Min:%d Max:%d\n", sbc_cie.min_bitpool, sbc_cie.max_bitpool); + + int frames_to_process = ((freq_multiple) / (num_blocks * num_subbands)) + 1; + APPL_TRACE_EVENT(" Frames to be processed in 20 ms %d\n", frames_to_process); +} + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_handle_inc_media + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_sink_handle_inc_media(tBT_SBC_HDR *p_msg) +{ + UINT8 *sbc_start_frame = ((UINT8 *)(p_msg + 1) + p_msg->offset + 1); + int count; + UINT32 pcmBytes, availPcmBytes; + OI_INT16 *pcmDataPointer = pcmData; /*Will be overwritten on next packet receipt*/ + OI_STATUS status; + int num_sbc_frames = p_msg->num_frames_to_be_processed; + UINT32 sbc_frame_len = p_msg->len - 1; + availPcmBytes = 2 * sizeof(pcmData); + + if (btc_av_get_peer_sep() == AVDT_TSEP_SNK || (btc_aa_snk_cb.rx_flush)) { + APPL_TRACE_DEBUG(" State Changed happened in this tick "); + return; + } + + // ignore data if no one is listening + if (!btc_a2dp_control_get_datachnl_stat()) { + return; + } + + APPL_TRACE_DEBUG("Number of sbc frames %d, frame_len %d\n", num_sbc_frames, sbc_frame_len); + + for (count = 0; count < num_sbc_frames && sbc_frame_len != 0; count ++) { + pcmBytes = availPcmBytes; + status = OI_CODEC_SBC_DecodeFrame(&context, (const OI_BYTE **)&sbc_start_frame, + (OI_UINT32 *)&sbc_frame_len, + (OI_INT16 *)pcmDataPointer, + (OI_UINT32 *)&pcmBytes); + if (!OI_SUCCESS(status)) { + APPL_TRACE_ERROR("Decoding failure: %d\n", status); + break; + } + availPcmBytes -= pcmBytes; + pcmDataPointer += pcmBytes / 2; + p_msg->offset += (p_msg->len - 1) - sbc_frame_len; + p_msg->len = sbc_frame_len + 1; + } + + btc_a2d_data_cb_to_app((uint8_t *)pcmData, (2 * sizeof(pcmData) - availPcmBytes)); +} + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_rx_flush_req + ** + ** Description + ** + ** Returns TRUE is success + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_sink_rx_flush_req(void) +{ + if (fixed_queue_is_empty(btc_aa_snk_cb.RxSbcQ) == TRUE) { /* Que is already empty */ + return TRUE; + } + + btc_a2dp_sink_ctrl_post(BTC_MEDIA_FLUSH_AA_RX, NULL); + return TRUE; +} + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_rx_flush + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_sink_rx_flush(void) +{ + /* Flush all enqueued SBC buffers (encoded) */ + APPL_TRACE_DEBUG("btc_a2dp_sink_rx_flush"); + + btc_a2dp_sink_flush_q(btc_aa_snk_cb.RxSbcQ); +} + +static int btc_a2dp_sink_get_track_frequency(UINT8 frequency) +{ + int freq = 48000; + switch (frequency) { + case A2D_SBC_IE_SAMP_FREQ_16: + freq = 16000; + break; + case A2D_SBC_IE_SAMP_FREQ_32: + freq = 32000; + break; + case A2D_SBC_IE_SAMP_FREQ_44: + freq = 44100; + break; + case A2D_SBC_IE_SAMP_FREQ_48: + freq = 48000; + break; + } + return freq; +} + +static int btc_a2dp_sink_get_track_channel_count(UINT8 channeltype) +{ + int count = 1; + switch (channeltype) { + case A2D_SBC_IE_CH_MD_MONO: + count = 1; + break; + case A2D_SBC_IE_CH_MD_DUAL: + case A2D_SBC_IE_CH_MD_STEREO: + case A2D_SBC_IE_CH_MD_JOINT: + count = 2; + break; + } + return count; +} + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_enque_buf + ** + ** Description This function is called by the av_co to fill A2DP Sink Queue + ** + ** + ** Returns size of the queue + *******************************************************************************/ +UINT8 btc_a2dp_sink_enque_buf(BT_HDR *p_pkt) +{ + tBT_SBC_HDR *p_msg; + + if (btc_aa_snk_cb.rx_flush == TRUE) { /* Flush enabled, do not enque*/ + return fixed_queue_length(btc_aa_snk_cb.RxSbcQ); + } + + if (fixed_queue_length(btc_aa_snk_cb.RxSbcQ) >= MAX_OUTPUT_A2DP_SNK_FRAME_QUEUE_SZ) { + APPL_TRACE_WARNING("Pkt dropped\n"); + return fixed_queue_length(btc_aa_snk_cb.RxSbcQ); + } + + APPL_TRACE_DEBUG("btc_a2dp_sink_enque_buf + "); + + /* allocate and Queue this buffer */ + if ((p_msg = (tBT_SBC_HDR *) osi_malloc(sizeof(tBT_SBC_HDR) + + p_pkt->offset + p_pkt->len)) != NULL) { + memcpy(p_msg, p_pkt, (sizeof(BT_HDR) + p_pkt->offset + p_pkt->len)); + p_msg->num_frames_to_be_processed = (*((UINT8 *)(p_msg + 1) + p_msg->offset)) & 0x0f; + APPL_TRACE_VERBOSE("btc_a2dp_sink_enque_buf %d + \n", p_msg->num_frames_to_be_processed); + fixed_queue_enqueue(btc_aa_snk_cb.RxSbcQ, p_msg); + btc_a2dp_sink_data_post(BTC_A2DP_SINK_DATA_EVT); + } else { + /* let caller deal with a failed allocation */ + APPL_TRACE_WARNING("btc_a2dp_sink_enque_buf No Buffer left - "); + } + return fixed_queue_length(btc_aa_snk_cb.RxSbcQ); +} + +static void btc_a2dp_sink_handle_clear_track (void) +{ + APPL_TRACE_DEBUG("%s", __FUNCTION__); +} + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_flush_q + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_sink_flush_q(fixed_queue_t *p_q) +{ + while (! fixed_queue_is_empty(p_q)) { + osi_free(fixed_queue_try_dequeue(p_q)); + } +} + +static void btc_a2dp_sink_thread_init(UNUSED_ATTR void *context) +{ + APPL_TRACE_EVENT("%s\n", __func__); + memset(&btc_aa_snk_cb, 0, sizeof(btc_aa_snk_cb)); + + btc_a2dp_sink_state = BTC_A2DP_SINK_STATE_ON; + + btc_aa_snk_cb.RxSbcQ = fixed_queue_new(SIZE_MAX); + + btc_a2dp_control_init(); +} + +static void btc_a2dp_sink_thread_cleanup(UNUSED_ATTR void *context) +{ + /* make sure no channels are restarted while shutting down */ + btc_a2dp_sink_state = BTC_A2DP_SINK_STATE_SHUTTING_DOWN; + + btc_a2dp_control_set_datachnl_stat(FALSE); + /* Clear task flag */ + btc_a2dp_sink_state = BTC_A2DP_SINK_STATE_OFF; + + btc_a2dp_control_cleanup(); + + fixed_queue_free(btc_aa_snk_cb.RxSbcQ, osi_free_func); +} + +#endif /* BTC_AV_SINK_INCLUDED */ diff --git a/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c b/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c new file mode 100644 index 000000000..d40591783 --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c @@ -0,0 +1,1617 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/****************************************************************************** + ** + ** Name: btc_a2dp_source.c + ** + ******************************************************************************/ +#include "bt_target.h" +#include "bt_trace.h" +#include +#include +#include +#include +#include +#include +#include "allocator.h" +#include "alarm.h" +#include "thread.h" +#include "mutex.h" +#include "fixed_queue.h" +#include "a2d_api.h" +#include "a2d_sbc.h" +#include "bta_av_api.h" +#include "bta_av_sbc.h" +#include "bta_av_ci.h" +#include "btc_manage.h" +#include "btc_common.h" +#include "btc_av_co.h" +#include "btc_a2dp.h" +#include "btc_a2dp_control.h" +#include "btc_a2dp_source.h" +#include "btc_av.h" +#include "btc_util.h" +#include "esp_a2dp_api.h" +#include "sbc_encoder.h" + +#if BTC_AV_SRC_INCLUDED + +/***************************************************************************** + ** Constants + *****************************************************************************/ + +/* BTC source command event definition */ +enum { + BTC_MEDIA_TASK_INIT, + BTC_MEDIA_TASK_CLEAN_UP, + BTC_MEDIA_START_AA_TX, + BTC_MEDIA_STOP_AA_TX, + BTC_MEDIA_SBC_ENC_INIT, + BTC_MEDIA_SBC_ENC_UPDATE, + BTC_MEDIA_FLUSH_AA_TX, + BTC_MEDIA_AUDIO_FEEDING_INIT, +}; + +enum { + BTC_A2DP_SOURCE_STATE_OFF = 0, + BTC_A2DP_SOURCE_STATE_ON = 1, + BTC_A2DP_SOURCE_STATE_SHUTTING_DOWN = 2 +}; + +enum { + BTC_A2DP_SOURCE_DATA_EVT = 1, +}; + +/* Media task tick in milliseconds, must be set to multiple of + (1000/TICKS_PER_SEC) */ +#define BTC_MEDIA_TIME_TICK_MS (30) +#define A2DP_DATA_READ_POLL_MS (BTC_MEDIA_TIME_TICK_MS / 2) + +#ifndef MAX_PCM_FRAME_NUM_PER_TICK +#define MAX_PCM_FRAME_NUM_PER_TICK 21 // 14 for 20ms +#endif + +#define BTC_MEDIA_AA_BUF_SIZE (4096+16) + +#if (BTA_AV_CO_CP_SCMS_T == TRUE) +#define BTC_MEDIA_AA_SBC_OFFSET (AVDT_MEDIA_OFFSET + BTA_AV_SBC_HDR_SIZE + 1) +#else +#define BTC_MEDIA_AA_SBC_OFFSET (AVDT_MEDIA_OFFSET + BTA_AV_SBC_HDR_SIZE) +#endif + +#ifndef BTC_MEDIA_BITRATE_STEP +#define BTC_MEDIA_BITRATE_STEP 5 +#endif + +#ifndef BTC_A2DP_NON_EDR_MAX_RATE +#define BTC_A2DP_NON_EDR_MAX_RATE 229 +#endif + +/* Middle quality quality setting @ 44.1 khz */ +#define DEFAULT_SBC_BITRATE 328 + +/* + * CONGESTION COMPENSATION CTRL :: + * + * Thus setting controls how many buffers we will hold in media task + * during temp link congestion. Together with the stack buffer queues + * it controls much temporary a2dp link congestion we can + * compensate for. It however also depends on the default run level of sinks + * jitterbuffers. Depending on type of sink this would vary. + * Ideally the (SRC) max tx buffer capacity should equal the sinks + * jitterbuffer runlevel including any intermediate buffers on the way + * towards the sinks codec. + */ + +/* fixme -- define this in pcm time instead of buffer count */ + +/* The typical runlevel of the tx queue size is ~1 buffer + but due to link flow control or thread preemption in lower + layers we might need to temporarily buffer up data */ + +/* 5 frames is equivalent to 6.89*5*2.9 ~= 100 ms @ 44.1 khz, 20 ms mediatick */ +#define MAX_OUTPUT_A2DP_FRAME_QUEUE_SZ (5) +#define MAX_OUTPUT_A2DP_SRC_FRAME_QUEUE_SZ (27) // 18 for 20ms tick + +typedef struct { + UINT16 num_frames_to_be_processed; + UINT16 len; + UINT16 offset; + UINT16 layer_specific; +} tBT_SBC_HDR; + +typedef struct { + UINT32 aa_frame_counter; + INT32 aa_feed_counter; + INT32 aa_feed_residue; + UINT32 counter; + UINT32 bytes_per_tick; /* pcm bytes read each media task tick */ +} tBTC_AV_MEDIA_FEEDINGS_PCM_STATE; + +typedef union { + tBTC_AV_MEDIA_FEEDINGS_PCM_STATE pcm; +} tBTC_AV_MEDIA_FEEDINGS_STATE; + +typedef struct { + UINT8 TxTranscoding; + BOOLEAN tx_flush; /* discards any outgoing data when true */ + BOOLEAN is_tx_timer; + UINT16 TxAaMtuSize; + UINT32 timestamp; + fixed_queue_t *TxAaQ; + tBTC_AV_FEEDING_MODE feeding_mode; + tBTC_AV_MEDIA_FEEDINGS_STATE media_feeding_state; + tBTC_AV_MEDIA_FEEDINGS media_feeding; + SBC_ENC_PARAMS encoder; + osi_alarm_t *media_alarm; +} tBTC_A2DP_SOURCE_CB; + +static void btc_a2dp_source_thread_init(UNUSED_ATTR void *context); +static void btc_a2dp_source_thread_cleanup(UNUSED_ATTR void *context); +static void btc_a2dp_source_flush_q(fixed_queue_t *p_q); + +static void btc_a2dp_source_feeding_state_reset(void); +static void btc_a2dp_source_send_aa_frame(void); +static void btc_a2dp_source_aa_start_tx(void); +static void btc_a2dp_source_aa_stop_tx(void); +static void btc_a2dp_source_enc_init(BT_HDR *p_msg); +static void btc_a2dp_source_enc_update(BT_HDR *p_msg); +static void btc_a2dp_source_audio_feeding_init(BT_HDR *p_msg); +static void btc_a2dp_source_aa_tx_flush(void); +static void btc_a2dp_source_prep_2_send(UINT8 nb_frame); +static void btc_a2dp_source_handle_timer(UNUSED_ATTR void *context); +static void btc_a2dp_source_encoder_init(void); + +static tBTC_A2DP_SOURCE_CB btc_aa_src_cb; +static int btc_a2dp_source_state = BTC_A2DP_SOURCE_STATE_OFF; +static xTaskHandle btc_aa_src_task_hdl = NULL; +static QueueHandle_t btc_aa_src_data_queue = NULL; +static QueueHandle_t btc_aa_src_ctrl_queue = NULL; +static QueueSetHandle_t btc_aa_src_queue_set; + +static esp_a2d_source_data_cb_t btc_aa_src_data_cb = NULL; +static UINT64 last_frame_us = 0; + +void btc_a2dp_src_reg_data_cb(esp_a2d_source_data_cb_t callback) +{ + // todo: critical section protection + btc_aa_src_data_cb = callback; +} + +static inline uint32_t btc_aa_src_data_read(uint8_t *data, int32_t len) +{ + // todo: critical section protection + if (btc_aa_src_data_cb) { + return btc_aa_src_data_cb(data, len); + } else { + return 0; + } +} + +/***************************************************************************** + ** Misc helper functions + *****************************************************************************/ +static inline void btc_aa_cb_to_app(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + esp_a2d_cb_t btc_aa_cb = (esp_a2d_cb_t)btc_profile_cb_get(BTC_PID_A2DP); + if (btc_aa_cb) { + btc_aa_cb(event, param); + } +} + +/***************************************************************************** + ** BTC ADAPTATION + *****************************************************************************/ + +bool btc_a2dp_source_is_streaming(void) +{ + return btc_aa_src_cb.is_tx_timer == TRUE; +} + +bool btc_a2dp_source_is_task_shutting_down(void) +{ + return btc_a2dp_source_state == BTC_A2DP_SOURCE_STATE_SHUTTING_DOWN; +} + +static void btc_a2dp_source_ctrl_post(uint32_t sig, void *par) +{ + BtTaskEvt_t *evt = (BtTaskEvt_t *)osi_malloc(sizeof(BtTaskEvt_t)); + if (evt == NULL) { + return; + } + + evt->sig = sig; + evt->par = par; + + if (xQueueSend(btc_aa_src_ctrl_queue, &evt, portMAX_DELAY) != pdTRUE) { + APPL_TRACE_WARNING("btc_aa_src_ctrl_queue failed, sig 0x%x\n", sig); + } +} + +static void btc_a2dp_source_ctrl_handler(BtTaskEvt_t *e) +{ + if (e == NULL) { + return; + } + switch (e->sig) { + case BTC_MEDIA_TASK_INIT: + btc_a2dp_source_thread_init(NULL); + break; + case BTC_MEDIA_TASK_CLEAN_UP: + btc_a2dp_source_thread_cleanup(NULL); + break; + case BTC_MEDIA_START_AA_TX: + btc_a2dp_source_aa_start_tx(); + break; + case BTC_MEDIA_STOP_AA_TX: + btc_a2dp_source_aa_stop_tx(); + break; + case BTC_MEDIA_SBC_ENC_INIT: + btc_a2dp_source_enc_init(e->par); + break; + case BTC_MEDIA_SBC_ENC_UPDATE: + btc_a2dp_source_enc_update(e->par); + break; + case BTC_MEDIA_AUDIO_FEEDING_INIT: + btc_a2dp_source_audio_feeding_init(e->par); + break; + case BTC_MEDIA_FLUSH_AA_TX: + btc_a2dp_source_aa_tx_flush(); + break; + default: + APPL_TRACE_WARNING("media task unhandled evt: 0x%x\n", e->sig); + } + if (e->par != NULL) { + osi_free(e->par); + } +} + +static void btc_a2dp_source_task_handler(void *arg) +{ + QueueSetMemberHandle_t xActivatedMember; + BtTaskEvt_t *e = NULL; + for (;;) { + xActivatedMember = xQueueSelectFromSet(btc_aa_src_queue_set, portMAX_DELAY); + if (xActivatedMember == btc_aa_src_data_queue) { + int32_t data_evt; + xQueueReceive(xActivatedMember, &data_evt, 0); + if (data_evt == BTC_A2DP_SOURCE_DATA_EVT) { + btc_a2dp_source_handle_timer(NULL); + } + } else if (xActivatedMember == btc_aa_src_ctrl_queue) { + xQueueReceive(xActivatedMember, &e, 0); + btc_a2dp_source_ctrl_handler(e); + osi_free(e); + } + } +} + +bool btc_a2dp_source_startup(void) +{ + if (btc_a2dp_source_state != BTC_A2DP_SOURCE_STATE_OFF) { + APPL_TRACE_ERROR("warning : media task already running"); + return false; + } + + APPL_TRACE_EVENT("## A2DP SOURCE START MEDIA THREAD ##"); + + btc_aa_src_queue_set = xQueueCreateSet(BTC_A2DP_SOURCE_TASK_QUEUE_SET_LEN); + configASSERT(btc_aa_src_queue_set); + btc_aa_src_data_queue = xQueueCreate(BTC_A2DP_SOURCE_DATA_QUEUE_LEN, sizeof(void *)); + configASSERT(btc_aa_src_data_queue); + xQueueAddToSet(btc_aa_src_data_queue, btc_aa_src_queue_set); + + btc_aa_src_ctrl_queue = xQueueCreate(BTC_A2DP_SOURCE_CTRL_QUEUE_LEN, sizeof(void *)); + configASSERT(btc_aa_src_ctrl_queue); + xQueueAddToSet(btc_aa_src_ctrl_queue, btc_aa_src_queue_set); + + if (!btc_aa_src_data_queue || !btc_aa_src_ctrl_queue || !btc_aa_src_queue_set ) { + goto error_exit; + } + + xTaskCreatePinnedToCore(btc_a2dp_source_task_handler, BTC_A2DP_SOURCE_TASK_NAME, BTC_A2DP_SOURCE_TASK_STACK_SIZE, NULL, BTC_A2DP_SOURCE_TASK_PRIO, &btc_aa_src_task_hdl, BTC_A2DP_SOURCE_TASK_PINNED_TO_CORE); + if (btc_aa_src_task_hdl == NULL) { + goto error_exit; + } + + btc_a2dp_source_ctrl_post(BTC_MEDIA_TASK_INIT, NULL); + + APPL_TRACE_EVENT("## A2DP SOURCE MEDIA THREAD STARTED ##\n"); + + return true; + +error_exit:; + APPL_TRACE_ERROR("%s unable to start up media thread\n", __func__); + + if (btc_aa_src_task_hdl != NULL) { + vTaskDelete(btc_aa_src_task_hdl); + btc_aa_src_task_hdl = NULL; + } + + if (btc_aa_src_data_queue) { + vQueueDelete(btc_aa_src_data_queue); + btc_aa_src_data_queue = NULL; + } + if (btc_aa_src_ctrl_queue) { + vQueueDelete(btc_aa_src_ctrl_queue); + btc_aa_src_ctrl_queue = NULL; + } + + return false; +} + +void btc_a2dp_source_shutdown(void) +{ + APPL_TRACE_EVENT("## A2DP SOURCE STOP MEDIA THREAD ##\n"); + + // Exit thread + btc_a2dp_source_ctrl_post(BTC_MEDIA_TASK_CLEAN_UP, NULL); + + vTaskDelete(btc_aa_src_task_hdl); + btc_aa_src_task_hdl = NULL; + + vQueueDelete(btc_aa_src_data_queue); + btc_aa_src_data_queue = NULL; + + vQueueDelete(btc_aa_src_ctrl_queue); + btc_aa_src_ctrl_queue = NULL; +} + +/***************************************************************************** +** +** Function btc_a2dp_source_on_idle +** +*******************************************************************************/ +void btc_a2dp_source_on_idle(void) +{ + /* Make sure media task is stopped */ + btc_a2dp_source_stop_audio_req(); +} + +/***************************************************************************** +** +** Function btc_a2dp_source_on_stopped +** +*******************************************************************************/ +void btc_a2dp_source_on_stopped(tBTA_AV_SUSPEND *p_av) +{ + /* allow using this api for other than suspend */ + if (p_av != NULL) { + if (p_av->status != BTA_AV_SUCCESS) { + APPL_TRACE_EVENT("AV STOP FAILED (%d)", p_av->status); + if (p_av->initiator) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); + } + return; + } + } + + /* ensure tx frames are immediately suspended */ + btc_aa_src_cb.tx_flush = 1; + + /* request to stop media task */ + btc_a2dp_source_tx_flush_req(); + btc_a2dp_source_stop_audio_req(); + + /* once stream is fully stopped we will ack back */ +} + +/***************************************************************************** +** +** Function btc_a2dp_source_on_suspended +** +** +*******************************************************************************/ + +void btc_a2dp_source_on_suspended(tBTA_AV_SUSPEND *p_av) +{ + /* check for status failures */ + if (p_av->status != BTA_AV_SUCCESS) { + if (p_av->initiator == TRUE) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); + } + } + + /* once stream is fully stopped we will ack back */ + + /* ensure tx frames are immediately flushed */ + btc_aa_src_cb.tx_flush = 1; + + /* stop timer tick */ + btc_a2dp_source_stop_audio_req(); +} + +static void btc_a2dp_source_data_post(int32_t data_type) +{ + if (xQueueSend(btc_aa_src_data_queue, &data_type, 0) != pdTRUE) { + APPL_TRACE_DEBUG("Media data Q filled\n"); + } +} + +static UINT64 time_now_us() +{ +#if _POSIX_TIMERS + struct timespec ts_now; + clock_gettime(CLOCK_BOOTTIME, &ts_now); + return ((UINT64)ts_now.tv_sec * 1000000L) + ((UINT64)ts_now.tv_nsec / 1000); +#else + struct timeval ts_now; + gettimeofday(&ts_now, NULL); + return ((UINT64)ts_now.tv_sec * 1000000L) + ((UINT64)ts_now.tv_usec); +#endif +} + +static void log_tstamps_us(char *comment) +{ + static UINT64 prev_us = 0; + const UINT64 now_us = time_now_us(); + APPL_TRACE_DEBUG("[%s] ts %08llu, diff : %08llu, queue sz %d", comment, now_us, now_us - prev_us, + fixed_queue_length(btc_aa_src_cb.TxAaQ)); + prev_us = now_us; +} + +/* when true media task discards any tx frames */ +void btc_a2dp_source_set_tx_flush(BOOLEAN enable) +{ + APPL_TRACE_EVENT("## DROP TX %d ##", enable); + btc_aa_src_cb.tx_flush = enable; +} + +/***************************************************************************** +** +** Function btc_a2dp_source_setup_codec +** +** Description +** +** Returns +** +*******************************************************************************/ + +void btc_a2dp_source_setup_codec(void) +{ + tBTC_AV_MEDIA_FEEDINGS media_feeding; + tBTC_AV_STATUS status; + + APPL_TRACE_EVENT("## A2DP SETUP CODEC ##\n"); + + osi_mutex_global_lock(); + + /* for now hardcode 44.1 khz 16 bit stereo PCM format */ + media_feeding.cfg.pcm.sampling_freq = 44100; + media_feeding.cfg.pcm.bit_per_sample = 16; + media_feeding.cfg.pcm.num_channel = 2; + media_feeding.format = BTC_AV_CODEC_PCM; + + if (bta_av_co_audio_set_codec(&media_feeding, &status)) { + tBTC_MEDIA_INIT_AUDIO_FEEDING mfeed; + + /* Init the encoding task */ + btc_a2dp_source_encoder_init(); + + /* Build the media task configuration */ + mfeed.feeding = media_feeding; + mfeed.feeding_mode = BTC_AV_FEEDING_ASYNCHRONOUS; + /* Send message to Media task to configure transcoding */ + btc_a2dp_source_audio_feeding_init_req(&mfeed); + } + + osi_mutex_global_unlock(); +} + + +/******************************************************************************* + ** + ** Function btc_a2dp_source_audio_readbuf + ** + ** Description This function is called by the av_co to get the next buffer to send + ** + ** + ** Returns void + *******************************************************************************/ +BT_HDR *btc_a2dp_source_audio_readbuf(void) +{ + return fixed_queue_try_dequeue(btc_aa_src_cb.TxAaQ); +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_start_audio_req + ** + ** Description + ** + ** Returns TRUE is success + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_start_audio_req(void) +{ + btc_a2dp_source_ctrl_post(BTC_MEDIA_START_AA_TX, NULL); + return TRUE; +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_stop_audio_req + ** + ** Description + ** + ** Returns TRUE is success + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_stop_audio_req(void) +{ + /* + * Explicitly check whether the btc_aa_src_ctrl_queue is not NULL to + * avoid a race condition during shutdown of the Bluetooth stack. + * This race condition is triggered when A2DP audio is streaming on + * shutdown: + * "btc_a2dp_on_stopped() -> btc_a2dp_source_stop_audio_req()" is called + * to stop the particular audio stream, and this happens right after + * the "cleanup() -> btc_a2dp_stop_media_task()" processing during + * the shutdown of the Bluetooth stack. + */ + if (btc_aa_src_ctrl_queue != NULL) { + btc_a2dp_source_ctrl_post(BTC_MEDIA_STOP_AA_TX, NULL); + } + return TRUE; +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_enc_init_req + ** + ** Description + ** + ** Returns TRUE is success + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_enc_init_req(tBTC_MEDIA_INIT_AUDIO *p_msg) +{ + tBTC_MEDIA_INIT_AUDIO *p_buf; + if (NULL == (p_buf = osi_malloc(sizeof(tBTC_MEDIA_INIT_AUDIO)))) { + return FALSE; + } + + memcpy(p_buf, p_msg, sizeof(tBTC_MEDIA_INIT_AUDIO)); + + btc_a2dp_source_ctrl_post(BTC_MEDIA_SBC_ENC_INIT, p_buf); + + return TRUE; +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_enc_update_req + ** + ** Description + ** + ** Returns TRUE is success + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_enc_update_req(tBTC_MEDIA_UPDATE_AUDIO *p_msg) +{ + tBTC_MEDIA_UPDATE_AUDIO *p_buf; + if (NULL == (p_buf = osi_malloc(sizeof(tBTC_MEDIA_UPDATE_AUDIO)))) { + return FALSE; + } + + memcpy(p_buf, p_msg, sizeof(tBTC_MEDIA_UPDATE_AUDIO)); + btc_a2dp_source_ctrl_post(BTC_MEDIA_SBC_ENC_UPDATE, p_buf); + return TRUE; +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_audio_feeding_init_req + ** + ** Description + ** + ** Returns TRUE is success + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_audio_feeding_init_req(tBTC_MEDIA_INIT_AUDIO_FEEDING *p_msg) +{ + tBTC_MEDIA_INIT_AUDIO_FEEDING *p_buf; + if (NULL == (p_buf = osi_malloc(sizeof(tBTC_MEDIA_INIT_AUDIO_FEEDING)))) { + return FALSE; + } + + memcpy(p_buf, p_msg, sizeof(tBTC_MEDIA_INIT_AUDIO_FEEDING)); + btc_a2dp_source_ctrl_post(BTC_MEDIA_AUDIO_FEEDING_INIT, p_buf); + return TRUE; +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_tx_flush_req + ** + ** Description + ** + ** Returns TRUE is success + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_tx_flush_req(void) +{ + /* + * Explicitly check whether the btc_aa_src_ctrl_queue is not NULL to + * avoid a race condition during shutdown of the Bluetooth stack. + * This race condition is triggered when A2DP audio is streaming on + * shutdown: + * "btc_a2dp_on_stopped() -> btc_a2dp_source_tx_flush_req()" is called + * to stop the particular audio stream, and this happens right after + * the "cleanup() -> btc_a2dp_stop_media_task()" processing during + * the shutdown of the Bluetooth stack. + */ + if (btc_aa_src_ctrl_queue != NULL) { + btc_a2dp_source_ctrl_post(BTC_MEDIA_FLUSH_AA_TX, NULL); + } + + return TRUE; +} + +/***************************************************************************** + ** BTC ADAPTATION + *****************************************************************************/ +static UINT16 btc_a2dp_source_get_sbc_rate(void) +{ + UINT16 rate = DEFAULT_SBC_BITRATE; + + /* restrict bitrate if a2dp link is non-edr */ + if (!btc_av_is_peer_edr()) { + rate = BTC_A2DP_NON_EDR_MAX_RATE; + APPL_TRACE_DEBUG("non-edr a2dp sink detected, restrict rate to %d", rate); + } + return rate; +} + +static void btc_a2dp_source_encoder_init(void) +{ + UINT16 minmtu; + tBTC_MEDIA_INIT_AUDIO msg; + tA2D_SBC_CIE sbc_config; + + /* lookup table for converting channel mode */ + UINT16 codec_mode_tbl[5] = { SBC_JOINT_STEREO, SBC_STEREO, SBC_DUAL, 0, SBC_MONO }; + + /* lookup table for converting number of blocks */ + UINT16 codec_block_tbl[5] = { 16, 12, 8, 0, 4 }; + + /* lookup table to convert freq */ + UINT16 freq_block_tbl[5] = { SBC_sf48000, SBC_sf44100, SBC_sf32000, 0, SBC_sf16000 }; + + APPL_TRACE_DEBUG("%s", __FUNCTION__); + + /* Retrieve the current SBC configuration (default if currently not used) */ + bta_av_co_audio_get_sbc_config(&sbc_config, &minmtu); + msg.NumOfSubBands = (sbc_config.num_subbands == A2D_SBC_IE_SUBBAND_4) ? 4 : 8; + msg.NumOfBlocks = codec_block_tbl[sbc_config.block_len >> 5]; + msg.AllocationMethod = (sbc_config.alloc_mthd == A2D_SBC_IE_ALLOC_MD_L) ? SBC_LOUDNESS : SBC_SNR; + msg.ChannelMode = codec_mode_tbl[sbc_config.ch_mode >> 1]; + msg.SamplingFreq = freq_block_tbl[sbc_config.samp_freq >> 5]; + msg.MtuSize = minmtu; + + APPL_TRACE_EVENT("msg.ChannelMode %x", msg.ChannelMode); + + /* Init the media task to encode SBC properly */ + btc_a2dp_source_enc_init_req(&msg); +} + +void btc_a2dp_source_encoder_update(void) +{ + UINT16 minmtu; + tA2D_SBC_CIE sbc_config; + tBTC_MEDIA_UPDATE_AUDIO msg; + UINT8 pref_min; + UINT8 pref_max; + + APPL_TRACE_DEBUG("%s", __FUNCTION__); + + /* Retrieve the current SBC configuration (default if currently not used) */ + bta_av_co_audio_get_sbc_config(&sbc_config, &minmtu); + + APPL_TRACE_DEBUG("%s: Common min_bitpool:%d(0x%x) max_bitpool:%d(0x%x)", __FUNCTION__, + sbc_config.min_bitpool, sbc_config.min_bitpool, + sbc_config.max_bitpool, sbc_config.max_bitpool); + + if (sbc_config.min_bitpool > sbc_config.max_bitpool) { + APPL_TRACE_ERROR("%s: ERROR min_bitpool > max_bitpool", __FUNCTION__); + } + + /* check if remote sink has a preferred bitpool range */ + if (bta_av_co_get_remote_bitpool_pref(&pref_min, &pref_max) == TRUE) { + /* adjust our preferred bitpool with the remote preference if within + our capable range */ + + if (pref_min < sbc_config.min_bitpool) { + pref_min = sbc_config.min_bitpool; + } + + if (pref_max > sbc_config.max_bitpool) { + pref_max = sbc_config.max_bitpool; + } + + msg.MinBitPool = pref_min; + msg.MaxBitPool = pref_max; + + if ((pref_min != sbc_config.min_bitpool) || (pref_max != sbc_config.max_bitpool)) { + APPL_TRACE_EVENT("## adjusted our bitpool range to peer pref [%d:%d] ##", + pref_min, pref_max); + } + } else { + msg.MinBitPool = sbc_config.min_bitpool; + msg.MaxBitPool = sbc_config.max_bitpool; + } + + msg.MinMtuSize = minmtu; + + /* Update the media task to encode SBC properly */ + btc_a2dp_source_enc_update_req(&msg); +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_enc_init + ** + ** Description Initialize encoding task + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_enc_init(BT_HDR *p_msg) +{ + tBTC_MEDIA_INIT_AUDIO *pInitAudio = (tBTC_MEDIA_INIT_AUDIO *) p_msg; + + APPL_TRACE_DEBUG("btc_a2dp_source_enc_init"); + + btc_aa_src_cb.timestamp = 0; + + /* SBC encoder config (enforced even if not used) */ + btc_aa_src_cb.encoder.s16ChannelMode = pInitAudio->ChannelMode; + btc_aa_src_cb.encoder.s16NumOfSubBands = pInitAudio->NumOfSubBands; + btc_aa_src_cb.encoder.s16NumOfBlocks = pInitAudio->NumOfBlocks; + btc_aa_src_cb.encoder.s16AllocationMethod = pInitAudio->AllocationMethod; + btc_aa_src_cb.encoder.s16SamplingFreq = pInitAudio->SamplingFreq; + + btc_aa_src_cb.encoder.u16BitRate = btc_a2dp_source_get_sbc_rate(); + + /* Default transcoding is PCM to SBC, modified by feeding configuration */ + btc_aa_src_cb.TxTranscoding = BTC_MEDIA_TRSCD_PCM_2_SBC; + btc_aa_src_cb.TxAaMtuSize = ((BTC_MEDIA_AA_BUF_SIZE - BTC_MEDIA_AA_SBC_OFFSET - sizeof(BT_HDR)) + < pInitAudio->MtuSize) ? (BTC_MEDIA_AA_BUF_SIZE - BTC_MEDIA_AA_SBC_OFFSET + - sizeof(BT_HDR)) : pInitAudio->MtuSize; + + APPL_TRACE_EVENT("btc_a2dp_source_enc_init mtu %d, peer mtu %d", + btc_aa_src_cb.TxAaMtuSize, pInitAudio->MtuSize); + APPL_TRACE_EVENT(" ch mode %d, subnd %d, nb blk %d, alloc %d, rate %d, freq %d", + btc_aa_src_cb.encoder.s16ChannelMode, btc_aa_src_cb.encoder.s16NumOfSubBands, + btc_aa_src_cb.encoder.s16NumOfBlocks, + btc_aa_src_cb.encoder.s16AllocationMethod, btc_aa_src_cb.encoder.u16BitRate, + btc_aa_src_cb.encoder.s16SamplingFreq); + + /* Reset entirely the SBC encoder */ + SBC_Encoder_Init(&(btc_aa_src_cb.encoder)); + APPL_TRACE_DEBUG("btc_a2dp_source_enc_init bit pool %d", btc_aa_src_cb.encoder.s16BitPool); +} + + +/******************************************************************************* + ** + ** Function btc_a2dp_source_enc_update + ** + ** Description Update encoding task + ** + ** Returns void + ** + *******************************************************************************/ + +static void btc_a2dp_source_enc_update(BT_HDR *p_msg) +{ + tBTC_MEDIA_UPDATE_AUDIO *pUpdateAudio = (tBTC_MEDIA_UPDATE_AUDIO *) p_msg; + SBC_ENC_PARAMS *pstrEncParams = &btc_aa_src_cb.encoder; + UINT16 s16SamplingFreq; + SINT16 s16BitPool = 0; + SINT16 s16BitRate; + SINT16 s16FrameLen; + UINT8 protect = 0; + + APPL_TRACE_DEBUG("%s : minmtu %d, maxbp %d minbp %d", __FUNCTION__, + pUpdateAudio->MinMtuSize, pUpdateAudio->MaxBitPool, pUpdateAudio->MinBitPool); + + /* Only update the bitrate and MTU size while timer is running to make sure it has been initialized */ + //if (btc_aa_src_cb.is_tx_timer) + { + btc_aa_src_cb.TxAaMtuSize = ((BTC_MEDIA_AA_BUF_SIZE - + BTC_MEDIA_AA_SBC_OFFSET - sizeof(BT_HDR)) + < pUpdateAudio->MinMtuSize) ? (BTC_MEDIA_AA_BUF_SIZE - BTC_MEDIA_AA_SBC_OFFSET + - sizeof(BT_HDR)) : pUpdateAudio->MinMtuSize; + + /* Set the initial target bit rate */ + pstrEncParams->u16BitRate = btc_a2dp_source_get_sbc_rate(); + + if (pstrEncParams->s16SamplingFreq == SBC_sf16000) { + s16SamplingFreq = 16000; + } else if (pstrEncParams->s16SamplingFreq == SBC_sf32000) { + s16SamplingFreq = 32000; + } else if (pstrEncParams->s16SamplingFreq == SBC_sf44100) { + s16SamplingFreq = 44100; + } else { + s16SamplingFreq = 48000; + } + + do { + if (pstrEncParams->s16NumOfBlocks == 0 || pstrEncParams->s16NumOfSubBands == 0 + || pstrEncParams->s16NumOfChannels == 0) { + APPL_TRACE_ERROR("%s - Avoiding division by zero...", __FUNCTION__); + APPL_TRACE_ERROR("%s - block=%d, subBands=%d, channels=%d", __FUNCTION__, + pstrEncParams->s16NumOfBlocks, pstrEncParams->s16NumOfSubBands, + pstrEncParams->s16NumOfChannels); + break; + } + + if ((pstrEncParams->s16ChannelMode == SBC_JOINT_STEREO) || + (pstrEncParams->s16ChannelMode == SBC_STEREO) ) { + s16BitPool = (SINT16)( (pstrEncParams->u16BitRate * + pstrEncParams->s16NumOfSubBands * 1000 / s16SamplingFreq) + - ( (32 + (4 * pstrEncParams->s16NumOfSubBands * + pstrEncParams->s16NumOfChannels) + + ( (pstrEncParams->s16ChannelMode - 2) * + pstrEncParams->s16NumOfSubBands ) ) + / pstrEncParams->s16NumOfBlocks) ); + + s16FrameLen = 4 + (4 * pstrEncParams->s16NumOfSubBands * + pstrEncParams->s16NumOfChannels) / 8 + + ( ((pstrEncParams->s16ChannelMode - 2) * + pstrEncParams->s16NumOfSubBands) + + (pstrEncParams->s16NumOfBlocks * s16BitPool) ) / 8; + + s16BitRate = (8 * s16FrameLen * s16SamplingFreq) + / (pstrEncParams->s16NumOfSubBands * + pstrEncParams->s16NumOfBlocks * 1000); + + if (s16BitRate > pstrEncParams->u16BitRate) { + s16BitPool--; + } + + if (pstrEncParams->s16NumOfSubBands == 8) { + s16BitPool = (s16BitPool > 255) ? 255 : s16BitPool; + } else { + s16BitPool = (s16BitPool > 128) ? 128 : s16BitPool; + } + } else { + s16BitPool = (SINT16)( ((pstrEncParams->s16NumOfSubBands * + pstrEncParams->u16BitRate * 1000) + / (s16SamplingFreq * pstrEncParams->s16NumOfChannels)) + - ( ( (32 / pstrEncParams->s16NumOfChannels) + + (4 * pstrEncParams->s16NumOfSubBands) ) + / pstrEncParams->s16NumOfBlocks ) ); + + pstrEncParams->s16BitPool = (s16BitPool > + (16 * pstrEncParams->s16NumOfSubBands)) + ? (16 * pstrEncParams->s16NumOfSubBands) : s16BitPool; + } + + if (s16BitPool < 0) { + s16BitPool = 0; + } + + APPL_TRACE_EVENT("bitpool candidate : %d (%d kbps)", + s16BitPool, pstrEncParams->u16BitRate); + + if (s16BitPool > pUpdateAudio->MaxBitPool) { + APPL_TRACE_DEBUG("%s computed bitpool too large (%d)", __FUNCTION__, s16BitPool); + /* Decrease bitrate */ + btc_aa_src_cb.encoder.u16BitRate -= BTC_MEDIA_BITRATE_STEP; + /* Record that we have decreased the bitrate */ + protect |= 1; + } else if (s16BitPool < pUpdateAudio->MinBitPool) { + APPL_TRACE_WARNING("%s computed bitpool too small (%d)", __FUNCTION__, s16BitPool); + + /* Increase bitrate */ + UINT16 previous_u16BitRate = btc_aa_src_cb.encoder.u16BitRate; + btc_aa_src_cb.encoder.u16BitRate += BTC_MEDIA_BITRATE_STEP; + /* Record that we have increased the bitrate */ + protect |= 2; + /* Check over-flow */ + if (btc_aa_src_cb.encoder.u16BitRate < previous_u16BitRate) { + protect |= 3; + } + } else { + break; + } + /* In case we have already increased and decreased the bitrate, just stop */ + if (protect == 3) { + APPL_TRACE_ERROR("%s could not find bitpool in range", __FUNCTION__); + break; + } + } while (1); + + /* Finally update the bitpool in the encoder structure */ + pstrEncParams->s16BitPool = s16BitPool; + + APPL_TRACE_DEBUG("%s final bit rate %d, final bit pool %d", __FUNCTION__, + btc_aa_src_cb.encoder.u16BitRate, btc_aa_src_cb.encoder.s16BitPool); + + /* make sure we reinitialize encoder with new settings */ + SBC_Encoder_Init(&(btc_aa_src_cb.encoder)); + } +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_pcm2sbc_init + ** + ** Description Init encoding task for PCM to SBC according to feeding + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_pcm2sbc_init(tBTC_MEDIA_INIT_AUDIO_FEEDING *p_feeding) +{ + BOOLEAN reconfig_needed = FALSE; + + APPL_TRACE_DEBUG("PCM feeding:"); + APPL_TRACE_DEBUG("sampling_freq:%d", p_feeding->feeding.cfg.pcm.sampling_freq); + APPL_TRACE_DEBUG("num_channel:%d", p_feeding->feeding.cfg.pcm.num_channel); + APPL_TRACE_DEBUG("bit_per_sample:%d", p_feeding->feeding.cfg.pcm.bit_per_sample); + + /* Check the PCM feeding sampling_freq */ + switch (p_feeding->feeding.cfg.pcm.sampling_freq) { + case 8000: + case 12000: + case 16000: + case 24000: + case 32000: + case 48000: + /* For these sampling_freq the AV connection must be 48000 */ + if (btc_aa_src_cb.encoder.s16SamplingFreq != SBC_sf48000) { + /* Reconfiguration needed at 48000 */ + APPL_TRACE_DEBUG("SBC Reconfiguration needed at 48000"); + btc_aa_src_cb.encoder.s16SamplingFreq = SBC_sf48000; + reconfig_needed = TRUE; + } + break; + + case 11025: + case 22050: + case 44100: + /* For these sampling_freq the AV connection must be 44100 */ + if (btc_aa_src_cb.encoder.s16SamplingFreq != SBC_sf44100) { + /* Reconfiguration needed at 44100 */ + APPL_TRACE_DEBUG("SBC Reconfiguration needed at 44100"); + btc_aa_src_cb.encoder.s16SamplingFreq = SBC_sf44100; + reconfig_needed = TRUE; + } + break; + default: + APPL_TRACE_DEBUG("Feeding PCM sampling_freq unsupported"); + break; + } + + /* Some AV Headsets do not support Mono => always ask for Stereo */ + if (btc_aa_src_cb.encoder.s16ChannelMode == SBC_MONO) { + APPL_TRACE_DEBUG("SBC Reconfiguration needed in Stereo"); + btc_aa_src_cb.encoder.s16ChannelMode = SBC_JOINT_STEREO; + reconfig_needed = TRUE; + } + + if (reconfig_needed != FALSE) { + APPL_TRACE_DEBUG("%s :: mtu %d", __FUNCTION__, btc_aa_src_cb.TxAaMtuSize); + APPL_TRACE_DEBUG("ch mode %d, nbsubd %d, nb %d, alloc %d, rate %d, freq %d", + btc_aa_src_cb.encoder.s16ChannelMode, + btc_aa_src_cb.encoder.s16NumOfSubBands, btc_aa_src_cb.encoder.s16NumOfBlocks, + btc_aa_src_cb.encoder.s16AllocationMethod, btc_aa_src_cb.encoder.u16BitRate, + btc_aa_src_cb.encoder.s16SamplingFreq); + + SBC_Encoder_Init(&(btc_aa_src_cb.encoder)); + } else { + APPL_TRACE_DEBUG("%s no SBC reconfig needed", __FUNCTION__); + } +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_audio_feeding_init + ** + ** Description Initialize the audio path according to the feeding format + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_audio_feeding_init(BT_HDR *p_msg) +{ + tBTC_MEDIA_INIT_AUDIO_FEEDING *p_feeding = (tBTC_MEDIA_INIT_AUDIO_FEEDING *) p_msg; + + APPL_TRACE_DEBUG("%s format:%d", __FUNCTION__, p_feeding->feeding.format); + + /* Save Media Feeding information */ + btc_aa_src_cb.feeding_mode = p_feeding->feeding_mode; + btc_aa_src_cb.media_feeding = p_feeding->feeding; + + /* Handle different feeding formats */ + switch (p_feeding->feeding.format) { + case BTC_AV_CODEC_PCM: + btc_aa_src_cb.TxTranscoding = BTC_MEDIA_TRSCD_PCM_2_SBC; + btc_a2dp_source_pcm2sbc_init(p_feeding); + break; + + default : + APPL_TRACE_ERROR("unknown feeding format %d", p_feeding->feeding.format); + break; + } +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_aa_tx_flush + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_aa_tx_flush(void) +{ + /* Flush all enqueued music buffers (encoded) */ + APPL_TRACE_DEBUG("%s", __FUNCTION__); + + btc_aa_src_cb.media_feeding_state.pcm.counter = 0; + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue = 0; + + btc_a2dp_source_flush_q(btc_aa_src_cb.TxAaQ); + + btc_aa_src_data_read(NULL, -1); +} + +/******************************************************************************* + ** + ** Function btc_get_num_aa_frame + ** + ** Description + ** + ** Returns The number of media frames in this time slice + ** + *******************************************************************************/ +static UINT8 btc_get_num_aa_frame(void) +{ + UINT8 result = 0; + + switch (btc_aa_src_cb.TxTranscoding) { + case BTC_MEDIA_TRSCD_PCM_2_SBC: { + UINT32 pcm_bytes_per_frame = btc_aa_src_cb.encoder.s16NumOfSubBands * + btc_aa_src_cb.encoder.s16NumOfBlocks * + btc_aa_src_cb.media_feeding.cfg.pcm.num_channel * + btc_aa_src_cb.media_feeding.cfg.pcm.bit_per_sample / 8; + + UINT32 us_this_tick = BTC_MEDIA_TIME_TICK_MS * 1000; + UINT64 now_us = time_now_us(); + if (last_frame_us != 0) { +#if _POSIX_TIMERS + us_this_tick = (now_us - last_frame_us); +#else + // consider the case that the number of day increases and timeofday wraps around + us_this_tick = (now_us > last_frame_us) ? (now_us - last_frame_us) : + (now_us + 86400000000ull - last_frame_us); +#endif + } + last_frame_us = now_us; + + btc_aa_src_cb.media_feeding_state.pcm.counter += + btc_aa_src_cb.media_feeding_state.pcm.bytes_per_tick * + us_this_tick / (BTC_MEDIA_TIME_TICK_MS * 1000); + + /* calculate nbr of frames pending for this media tick */ + result = btc_aa_src_cb.media_feeding_state.pcm.counter / pcm_bytes_per_frame; + if (result > MAX_PCM_FRAME_NUM_PER_TICK) { + APPL_TRACE_WARNING("%s() - Limiting frames to be sent from %d to %d" + , __FUNCTION__, result, MAX_PCM_FRAME_NUM_PER_TICK); + result = MAX_PCM_FRAME_NUM_PER_TICK; + } + btc_aa_src_cb.media_feeding_state.pcm.counter -= result * pcm_bytes_per_frame; + + LOG_VERBOSE("WRITE %d FRAMES", result); + } + break; + + default: + APPL_TRACE_ERROR("ERROR btc_get_num_aa_frame Unsupported transcoding format 0x%x", + btc_aa_src_cb.TxTranscoding); + result = 0; + break; + } + + return (UINT8)result; +} + +/******************************************************************************* + ** + ** Function btc_media_aa_read_feeding + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ + +BOOLEAN btc_media_aa_read_feeding(void) +{ + UINT16 blocm_x_subband = btc_aa_src_cb.encoder.s16NumOfSubBands * \ + btc_aa_src_cb.encoder.s16NumOfBlocks; + UINT32 read_size; + UINT16 sbc_sampling = 48000; + UINT32 src_samples; + UINT16 bytes_needed = blocm_x_subband * btc_aa_src_cb.encoder.s16NumOfChannels * \ + btc_aa_src_cb.media_feeding.cfg.pcm.bit_per_sample / 8; + static UINT16 up_sampled_buffer[SBC_MAX_NUM_FRAME * SBC_MAX_NUM_OF_BLOCKS + * SBC_MAX_NUM_OF_CHANNELS * SBC_MAX_NUM_OF_SUBBANDS * 2]; + static UINT16 read_buffer[SBC_MAX_NUM_FRAME * SBC_MAX_NUM_OF_BLOCKS + * SBC_MAX_NUM_OF_CHANNELS * SBC_MAX_NUM_OF_SUBBANDS]; + UINT32 src_size_used; + UINT32 dst_size_used; + BOOLEAN fract_needed; + INT32 fract_max; + INT32 fract_threshold; + UINT32 nb_byte_read = 0; + + /* Get the SBC sampling rate */ + switch (btc_aa_src_cb.encoder.s16SamplingFreq) { + case SBC_sf48000: + sbc_sampling = 48000; + break; + case SBC_sf44100: + sbc_sampling = 44100; + break; + case SBC_sf32000: + sbc_sampling = 32000; + break; + case SBC_sf16000: + sbc_sampling = 16000; + break; + } + + if (sbc_sampling == btc_aa_src_cb.media_feeding.cfg.pcm.sampling_freq) { + read_size = bytes_needed - btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue; + nb_byte_read = btc_aa_src_data_read( + ((uint8_t *)btc_aa_src_cb.encoder.as16PcmBuffer) + + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue, + read_size); + if (nb_byte_read == read_size) { + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue = 0; + return TRUE; + } else { + APPL_TRACE_WARNING("### UNDERFLOW :: ONLY READ %d BYTES OUT OF %d ###", + nb_byte_read, read_size); + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue += nb_byte_read; + return FALSE; + } + } + + /* Some Feeding PCM frequencies require to split the number of sample */ + /* to read. */ + /* E.g 128/6=21.3333 => read 22 and 21 and 21 => max = 2; threshold = 0*/ + fract_needed = FALSE; /* Default */ + switch (btc_aa_src_cb.media_feeding.cfg.pcm.sampling_freq) { + case 32000: + case 8000: + fract_needed = TRUE; + fract_max = 2; /* 0, 1 and 2 */ + fract_threshold = 0; /* Add one for the first */ + break; + case 16000: + fract_needed = TRUE; + fract_max = 2; /* 0, 1 and 2 */ + fract_threshold = 1; /* Add one for the first two frames*/ + break; + } + + /* Compute number of sample to read from source */ + src_samples = blocm_x_subband; + src_samples *= btc_aa_src_cb.media_feeding.cfg.pcm.sampling_freq; + src_samples /= sbc_sampling; + + /* The previous division may have a remainder not null */ + if (fract_needed) { + if (btc_aa_src_cb.media_feeding_state.pcm.aa_feed_counter <= fract_threshold) { + src_samples++; /* for every read before threshold add one sample */ + } + + /* do nothing if counter >= threshold */ + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_counter++; /* one more read */ + if (btc_aa_src_cb.media_feeding_state.pcm.aa_feed_counter > fract_max) { + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_counter = 0; + } + } + + /* Compute number of bytes to read from source */ + read_size = src_samples; + read_size *= btc_aa_src_cb.media_feeding.cfg.pcm.num_channel; + read_size *= (btc_aa_src_cb.media_feeding.cfg.pcm.bit_per_sample / 8); + + /* Read Data from data channel */ + nb_byte_read = btc_aa_src_data_read((uint8_t *)read_buffer, read_size); + + //tput_mon(TRUE, nb_byte_read, FALSE); + + if (nb_byte_read < read_size) { + APPL_TRACE_WARNING("### UNDERRUN :: ONLY READ %d BYTES OUT OF %d ###", + nb_byte_read, read_size); + + if (nb_byte_read == 0) { + return FALSE; + } + + if (btc_aa_src_cb.feeding_mode == BTC_AV_FEEDING_ASYNCHRONOUS) { + /* Fill the unfilled part of the read buffer with silence (0) */ + memset(((UINT8 *)read_buffer) + nb_byte_read, 0, read_size - nb_byte_read); + nb_byte_read = read_size; + } + } + + /* Initialize PCM up-sampling engine */ + bta_av_sbc_init_up_sample(btc_aa_src_cb.media_feeding.cfg.pcm.sampling_freq, + sbc_sampling, btc_aa_src_cb.media_feeding.cfg.pcm.bit_per_sample, + btc_aa_src_cb.media_feeding.cfg.pcm.num_channel); + + /* re-sample read buffer */ + /* The output PCM buffer will be stereo, 16 bit per sample */ + dst_size_used = bta_av_sbc_up_sample((UINT8 *)read_buffer, + (UINT8 *)up_sampled_buffer + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue, + nb_byte_read, + sizeof(up_sampled_buffer) - btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue, + &src_size_used); + + /* update the residue */ + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue += dst_size_used; + + /* only copy the pcm sample when we have up-sampled enough PCM */ + if (btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue >= bytes_needed) { + /* Copy the output pcm samples in SBC encoding buffer */ + memcpy((UINT8 *)btc_aa_src_cb.encoder.as16PcmBuffer, + (UINT8 *)up_sampled_buffer, + bytes_needed); + /* update the residue */ + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue -= bytes_needed; + + if (btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue != 0) { + memcpy((UINT8 *)up_sampled_buffer, + (UINT8 *)up_sampled_buffer + bytes_needed, + btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue); + } + return TRUE; + } + + return FALSE; +} + +/******************************************************************************* + ** + ** Function btc_media_aa_prep_sbc_2_send + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_media_aa_prep_sbc_2_send(UINT8 nb_frame) +{ + BT_HDR *p_buf; + UINT16 blocm_x_subband = btc_aa_src_cb.encoder.s16NumOfSubBands * + btc_aa_src_cb.encoder.s16NumOfBlocks; + + while (nb_frame) { + if (NULL == (p_buf = osi_malloc(BTC_MEDIA_AA_BUF_SIZE))) { + APPL_TRACE_ERROR ("ERROR btc_media_aa_prep_sbc_2_send no buffer TxCnt %d ", + fixed_queue_length(btc_aa_src_cb.TxAaQ)); + return; + } + + /* Init buffer */ + p_buf->offset = BTC_MEDIA_AA_SBC_OFFSET; + p_buf->len = 0; + p_buf->layer_specific = 0; + + do { + /* Write @ of allocated buffer in encoder.pu8Packet */ + btc_aa_src_cb.encoder.pu8Packet = (UINT8 *) (p_buf + 1) + p_buf->offset + p_buf->len; + /* Fill allocated buffer with 0 */ + memset(btc_aa_src_cb.encoder.as16PcmBuffer, 0, blocm_x_subband + * btc_aa_src_cb.encoder.s16NumOfChannels); + + /* Read PCM data and upsample them if needed */ + if (btc_media_aa_read_feeding()) { + /* SBC encode and descramble frame */ + SBC_Encoder(&(btc_aa_src_cb.encoder)); + A2D_SbcChkFrInit(btc_aa_src_cb.encoder.pu8Packet); + A2D_SbcDescramble(btc_aa_src_cb.encoder.pu8Packet, btc_aa_src_cb.encoder.u16PacketLength); + /* Update SBC frame length */ + p_buf->len += btc_aa_src_cb.encoder.u16PacketLength; + nb_frame--; + p_buf->layer_specific++; + } else { + APPL_TRACE_WARNING("btc_media_aa_prep_sbc_2_send underflow %d, %d", + nb_frame, btc_aa_src_cb.media_feeding_state.pcm.aa_feed_residue); + btc_aa_src_cb.media_feeding_state.pcm.counter += nb_frame * + btc_aa_src_cb.encoder.s16NumOfSubBands * + btc_aa_src_cb.encoder.s16NumOfBlocks * + btc_aa_src_cb.media_feeding.cfg.pcm.num_channel * + btc_aa_src_cb.media_feeding.cfg.pcm.bit_per_sample / 8; + /* no more pcm to read */ + nb_frame = 0; + + /* break read loop if timer was stopped (media task stopped) */ + if ( btc_aa_src_cb.is_tx_timer == FALSE ) { + osi_free(p_buf); + return; + } + } + + } while (((p_buf->len + btc_aa_src_cb.encoder.u16PacketLength) < btc_aa_src_cb.TxAaMtuSize) + && (p_buf->layer_specific < 0x0F) && nb_frame); + + if (p_buf->len) { + /* timestamp of the media packet header represent the TS of the first SBC frame + i.e the timestamp before including this frame */ + *((UINT32 *) (p_buf + 1)) = btc_aa_src_cb.timestamp; + + btc_aa_src_cb.timestamp += p_buf->layer_specific * blocm_x_subband; + + if (btc_aa_src_cb.tx_flush) { + APPL_TRACE_DEBUG("### tx suspended, discarded frame ###"); + + if (fixed_queue_length(btc_aa_src_cb.TxAaQ) > 0) { + btc_a2dp_source_flush_q(btc_aa_src_cb.TxAaQ); + } + + osi_free(p_buf); + return; + } + + /* Enqueue the encoded SBC frame in AA Tx Queue */ + fixed_queue_enqueue(btc_aa_src_cb.TxAaQ, p_buf); + } else { + osi_free(p_buf); + } + } +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_prep_2_send + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_prep_2_send(UINT8 nb_frame) +{ + // Check for TX queue overflow + if (nb_frame > MAX_OUTPUT_A2DP_SRC_FRAME_QUEUE_SZ) { + nb_frame = MAX_OUTPUT_A2DP_SRC_FRAME_QUEUE_SZ; + } + + if (fixed_queue_length(btc_aa_src_cb.TxAaQ) > (MAX_OUTPUT_A2DP_SRC_FRAME_QUEUE_SZ - nb_frame)) { + APPL_TRACE_WARNING("TX Q overflow: %d/%d", + fixed_queue_length(btc_aa_src_cb.TxAaQ), MAX_OUTPUT_A2DP_SRC_FRAME_QUEUE_SZ - nb_frame); + } + + while (fixed_queue_length(btc_aa_src_cb.TxAaQ) > (MAX_OUTPUT_A2DP_SRC_FRAME_QUEUE_SZ - nb_frame)) { + osi_free(fixed_queue_try_dequeue(btc_aa_src_cb.TxAaQ)); + } + + // Transcode frame + + switch (btc_aa_src_cb.TxTranscoding) { + case BTC_MEDIA_TRSCD_PCM_2_SBC: + btc_media_aa_prep_sbc_2_send(nb_frame); + break; + + default: + APPL_TRACE_ERROR("%s unsupported transcoding format 0x%x", __func__, btc_aa_src_cb.TxTranscoding); + break; + } +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_send_aa_frame + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_send_aa_frame(void) +{ + UINT8 nb_frame_2_send; + + /* get the number of frame to send */ + nb_frame_2_send = btc_get_num_aa_frame(); + + if (nb_frame_2_send != 0) { + /* format and Q buffer to send */ + btc_a2dp_source_prep_2_send(nb_frame_2_send); + } + + /* send it */ + LOG_VERBOSE("%s: send %d frames", __FUNCTION__, nb_frame_2_send); + bta_av_ci_src_data_ready(BTA_AV_CHNL_AUDIO); +} + +static void btc_a2dp_source_handle_timer(UNUSED_ATTR void *context) +{ + log_tstamps_us("media task tx timer"); + +#if (BTA_AV_INCLUDED == TRUE) + if (btc_aa_src_cb.is_tx_timer == TRUE) { + btc_a2dp_source_send_aa_frame(); + } else { + APPL_TRACE_WARNING("Media task Scheduled after Suspend"); + } +#endif +} + +static void btc_a2dp_source_alarm_cb(UNUSED_ATTR void *context) +{ + btc_a2dp_source_data_post(BTC_A2DP_SOURCE_DATA_EVT); +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_feeding_state_reset + ** + ** Description Reset the media feeding state + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_feeding_state_reset(void) +{ + /* By default, just clear the entire state */ + memset(&btc_aa_src_cb.media_feeding_state, 0, sizeof(btc_aa_src_cb.media_feeding_state)); + + if (btc_aa_src_cb.TxTranscoding == BTC_MEDIA_TRSCD_PCM_2_SBC) { + btc_aa_src_cb.media_feeding_state.pcm.bytes_per_tick = + (btc_aa_src_cb.media_feeding.cfg.pcm.sampling_freq * + btc_aa_src_cb.media_feeding.cfg.pcm.bit_per_sample / 8 * + btc_aa_src_cb.media_feeding.cfg.pcm.num_channel * + BTC_MEDIA_TIME_TICK_MS) / 1000; + + APPL_TRACE_WARNING("pcm bytes per tick %d", + (int)btc_aa_src_cb.media_feeding_state.pcm.bytes_per_tick); + } +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_aa_start_tx + ** + ** Description Start media task encoding + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_aa_start_tx(void) +{ + APPL_TRACE_DEBUG("btc_a2dp_source_aa_start_tx is timer %d, feeding mode %d", + btc_aa_src_cb.is_tx_timer, btc_aa_src_cb.feeding_mode); + + btc_aa_src_cb.is_tx_timer = TRUE; + last_frame_us = 0; + + /* Reset the media feeding state */ + btc_a2dp_source_feeding_state_reset(); + + APPL_TRACE_EVENT("starting timer %dms", BTC_MEDIA_TIME_TICK_MS); + + assert(btc_aa_src_cb.media_alarm == NULL); + + btc_aa_src_cb.media_alarm = osi_alarm_new("aaTx", btc_a2dp_source_alarm_cb, NULL, BTC_MEDIA_TIME_TICK_MS); + + if (!btc_aa_src_cb.media_alarm) { + LOG_ERROR("%s unable to allocate media alarm.", __func__); + return; + } + + osi_alarm_set_periodic(btc_aa_src_cb.media_alarm, BTC_MEDIA_TIME_TICK_MS); +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_aa_stop_tx + ** + ** Description Stop media task encoding + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_aa_stop_tx(void) +{ + APPL_TRACE_DEBUG("%s is_tx_timer: %d", __func__, btc_aa_src_cb.is_tx_timer); + + const bool send_ack = (btc_aa_src_cb.is_tx_timer != FALSE); + + /* Stop the timer first */ + if (btc_aa_src_cb.media_alarm) { + osi_alarm_cancel(btc_aa_src_cb.media_alarm); + osi_alarm_free(btc_aa_src_cb.media_alarm); + } + btc_aa_src_cb.media_alarm = NULL; + btc_aa_src_cb.is_tx_timer = FALSE; + + /* Try to send acknowldegment once the media stream is + stopped. This will make sure that the A2DP HAL layer is + un-blocked on wait for acknowledgment for the sent command. + This resolves a corner cases AVDTP SUSPEND collision + when the DUT and the remote device issue SUSPEND simultaneously + and due to the processing of the SUSPEND request from the remote, + the media path is torn down. If the A2DP HAL happens to wait + for ACK for the initiated SUSPEND, it would never receive it casuing + a block/wait. Due to this acknowledgement, the A2DP HAL is guranteed + to get the ACK for any pending command in such cases. */ + + if (send_ack) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_SUCCESS); + } + + /* audio engine stopped, reset tx suspended flag */ + btc_aa_src_cb.tx_flush = 0; + last_frame_us = 0; + + /* Reset the feeding state */ + btc_a2dp_source_feeding_state_reset(); +} + +/******************************************************************************* + ** + ** Function btc_a2dp_source_flush_q + ** + ** Description + ** + ** Returns void + ** + *******************************************************************************/ +static void btc_a2dp_source_flush_q(fixed_queue_t *p_q) +{ + while (! fixed_queue_is_empty(p_q)) { + osi_free(fixed_queue_try_dequeue(p_q)); + } +} + +static void btc_a2dp_source_thread_init(UNUSED_ATTR void *context) +{ + APPL_TRACE_EVENT("%s\n", __func__); + memset(&btc_aa_src_cb, 0, sizeof(btc_aa_src_cb)); + + btc_a2dp_source_state = BTC_A2DP_SOURCE_STATE_ON; + + btc_aa_src_cb.TxAaQ = fixed_queue_new(SIZE_MAX); + + btc_a2dp_control_init(); +} + +static void btc_a2dp_source_thread_cleanup(UNUSED_ATTR void *context) +{ + /* make sure no channels are restarted while shutting down */ + btc_a2dp_source_state = BTC_A2DP_SOURCE_STATE_SHUTTING_DOWN; + + btc_a2dp_control_set_datachnl_stat(FALSE); + /* Clear media task flag */ + btc_a2dp_source_state = BTC_A2DP_SOURCE_STATE_OFF; + + btc_a2dp_control_cleanup(); + + fixed_queue_free(btc_aa_src_cb.TxAaQ, osi_free_func); +} + +#endif /* BTC_AV_INCLUDED */ diff --git a/components/bt/bluedroid/btc/profile/std/a2dp/btc_avk.c b/components/bt/bluedroid/btc/profile/std/a2dp/btc_av.c similarity index 83% rename from components/bt/bluedroid/btc/profile/std/a2dp/btc_avk.c rename to components/bt/bluedroid/btc/profile/std/a2dp/btc_av.c index 7c04cbfe0..1ac3b96e2 100644 --- a/components/bt/bluedroid/btc/profile/std/a2dp/btc_avk.c +++ b/components/bt/bluedroid/btc/profile/std/a2dp/btc_av.c @@ -23,21 +23,21 @@ #include #include "bt_trace.h" #include "bt_defs.h" -#include "esp_bt_defs.h" -#include "esp_a2dp_api.h" #include "allocator.h" +#include "btu.h" +#include "bta_av_api.h" #include "btc_dm.h" +#include "btc_common.h" +#include "btc_manage.h" #include "btc_av.h" #include "btc_avrc.h" #include "btc_util.h" #include "btc_profile_queue.h" -#include "bta_api.h" -#include "btc_media.h" -#include "bta_av_api.h" -#include "btu.h" -#include "bt_utils.h" -#include "btc_common.h" -#include "btc_manage.h" +#include "btc_a2dp.h" +#include "btc_a2dp_control.h" +#include "btc_a2dp_sink.h" +#include "btc_a2dp_source.h" +#include "esp_a2dp_api.h" #if BTC_AV_INCLUDED @@ -116,6 +116,18 @@ static BOOLEAN btc_av_state_opened_handler(btc_sm_event_t event, void *data); static BOOLEAN btc_av_state_started_handler(btc_sm_event_t event, void *data); static BOOLEAN btc_av_state_closing_handler(btc_sm_event_t event, void *data); +#if BTC_AV_SRC_INCLUDED +static bt_status_t btc_a2d_src_init(void); +static bt_status_t btc_a2d_src_connect(bt_bdaddr_t *remote_bda); +static void btc_a2d_src_deinit(void); +#endif /* BTC_AV_SRC_INCLUDED */ + +#if BTC_AV_SINK_INCLUDED +static bt_status_t btc_a2d_sink_init(void); +static bt_status_t btc_a2d_sink_connect(bt_bdaddr_t *remote_bda); +static void btc_a2d_sink_deinit(void); +#endif /* BTC_AV_SINK_INCLUDED */ + static const btc_sm_handler_t btc_av_state_handlers[] = { btc_av_state_idle_handler, btc_av_state_opening_handler, @@ -226,6 +238,7 @@ static void btc_initiate_av_open_tmr_hdlr(TIMER_LIST_ENT *tle) ******************************************************************************/ static void btc_report_connection_state(esp_a2d_connection_state_t state, bt_bdaddr_t *bd_addr, int disc_rsn) { + // todo: add callback for SRC esp_a2d_cb_param_t param; memset(¶m, 0, sizeof(esp_a2d_cb_param_t)); @@ -242,6 +255,7 @@ static void btc_report_connection_state(esp_a2d_connection_state_t state, bt_bda static void btc_report_audio_state(esp_a2d_audio_state_t state, bt_bdaddr_t *bd_addr) { + // todo: add callback for SRC esp_a2d_cb_param_t param; memset(¶m, 0, sizeof(esp_a2d_cb_param_t)); @@ -389,7 +403,6 @@ static BOOLEAN btc_av_state_opening_handler(btc_sm_event_t event, void *p_data) btc_av_cb.edr = p_bta_data->open.edr; btc_av_cb.peer_sep = p_bta_data->open.sep; - btc_a2dp_set_peer_sep(p_bta_data->open.sep); } else { LOG_WARN("BTA_AV_OPEN_EVT::FAILED status: %d\n", p_bta_data->open.status ); @@ -401,7 +414,13 @@ static BOOLEAN btc_av_state_opening_handler(btc_sm_event_t event, void *p_data) btc_report_connection_state(state, &(btc_av_cb.peer_bda), 0); /* change state to open/idle based on the status */ btc_sm_change_state(btc_av_cb.sm_handle, av_state); - if (btc_av_cb.peer_sep == AVDT_TSEP_SRC) { + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + /* if queued PLAY command, send it now */ + /* necessary to add this? + btc_rc_check_handle_pending_play(p_bta_data->open.bd_addr, + (p_bta_data->open.status == BTA_AV_SUCCESS)); + */ + } else if (btc_av_cb.peer_sep == AVDT_TSEP_SRC) { /* Bring up AVRCP connection too */ BTA_AvOpenRc(btc_av_cb.bta_handle); } @@ -473,17 +492,33 @@ static BOOLEAN btc_av_state_closing_handler(btc_sm_event_t event, void *p_data) switch (event) { case BTC_SM_ENTER_EVT: - if (btc_av_cb.peer_sep == AVDT_TSEP_SRC) { - btc_a2dp_set_rx_flush(TRUE); +#if BTC_AV_SRC_INCLUDED + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + /* immediately stop transmission of frames */ + btc_a2dp_source_set_tx_flush(TRUE); + /* wait for audioflinger to stop a2dp */ } +#endif /* BTC_AV_SRC_INCLUDED */ +#if BTC_AV_SINK_INCLUDED + if (btc_av_cb.peer_sep == AVDT_TSEP_SRC) { + btc_a2dp_sink_set_rx_flush(TRUE); + } +#endif /* BTC_AV_SINK_INCLUDED */ break; case BTA_AV_STOP_EVT: case BTC_AV_STOP_STREAM_REQ_EVT: - if (btc_av_cb.peer_sep == AVDT_TSEP_SRC) { - btc_a2dp_set_rx_flush(TRUE); +#if BTC_AV_SRC_INCLUDED + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + /* immediately flush any pending tx frames while suspend is pending */ + btc_a2dp_source_set_tx_flush(TRUE); } - +#endif /* BTC_AV_SRC_INCLUDED */ +#if BTC_AV_SINK_INCLUDED + if (btc_av_cb.peer_sep == AVDT_TSEP_SRC) { + btc_a2dp_sink_set_rx_flush(TRUE); + } +#endif /* BTC_AV_SINK_INCLUDED */ btc_a2dp_on_stopped(NULL); break; @@ -548,6 +583,11 @@ static BOOLEAN btc_av_state_opened_handler(btc_sm_event_t event, void *p_data) break; case BTC_AV_START_STREAM_REQ_EVT: +#if BTC_AV_SRC_INCLUDED + if (btc_av_cb.peer_sep != AVDT_TSEP_SRC) { + btc_a2dp_source_setup_codec(); + } +#endif /* BTC_AV_SRC_INCLUDED */ BTA_AvStart(); btc_av_cb.flags |= BTC_AV_FLAG_PENDING_START; break; @@ -559,21 +599,44 @@ static BOOLEAN btc_av_state_opened_handler(btc_sm_event_t event, void *p_data) if ((p_av->start.status == BTA_SUCCESS) && (p_av->start.suspending == TRUE)) { return TRUE; } - +#if BTC_AV_SRC_INCLUDED + /* if remote tries to start a2dp when DUT is a2dp source + * then suspend. In case a2dp is sink and call is active + * then disconnect the AVDTP channel + */ + if (!(btc_av_cb.flags & BTC_AV_FLAG_PENDING_START)) { + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + LOG_INFO("%s: trigger suspend as remote initiated!!", __FUNCTION__); + btc_dispatch_sm_event(BTC_AV_SUSPEND_STREAM_REQ_EVT, NULL, 0); + } + } + /* In case peer is A2DP SRC we do not want to ack commands on UIPC*/ + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + if (btc_a2dp_on_started(&p_av->start, + ((btc_av_cb.flags & BTC_AV_FLAG_PENDING_START) != 0))) { + /* only clear pending flag after acknowledgement */ + btc_av_cb.flags &= ~BTC_AV_FLAG_PENDING_START; + } + } +#endif /* BTC_AV_SRC_INCLUDED */ /* remain in open state if status failed */ if (p_av->start.status != BTA_AV_SUCCESS) { return FALSE; } - +#if BTC_AV_SINK_INCLUDED if (btc_av_cb.peer_sep == AVDT_TSEP_SRC) { - btc_a2dp_set_rx_flush(FALSE); /* remove flush state, ready for streaming*/ + btc_a2dp_sink_set_rx_flush(FALSE); /* remove flush state, ready for streaming*/ } - +#endif /* BTC_AV_SINK_INCLUDED */ +#if BTC_AV_SRC_INCLUDED /* change state to started, send acknowledgement if start is pending */ if (btc_av_cb.flags & BTC_AV_FLAG_PENDING_START) { - + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + btc_a2dp_on_started(NULL, TRUE); + } /* pending start flag will be cleared when exit current state */ } +#endif /* BTC_AV_SRC_INCLUDED */ btc_sm_change_state(btc_av_cb.sm_handle, BTC_AV_STATE_STARTED); } break; @@ -599,6 +662,7 @@ static BOOLEAN btc_av_state_opened_handler(btc_sm_event_t event, void *p_data) /* change state to idle, send acknowledgement if start is pending */ if (btc_av_cb.flags & BTC_AV_FLAG_PENDING_START) { + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); /* pending start flag will be cleared when exit current state */ } btc_sm_change_state(btc_av_cb.sm_handle, BTC_AV_STATE_IDLE); @@ -612,6 +676,7 @@ static BOOLEAN btc_av_state_opened_handler(btc_sm_event_t event, void *p_data) BTA_AvStart(); } else if (btc_av_cb.flags & BTC_AV_FLAG_PENDING_START) { btc_av_cb.flags &= ~BTC_AV_FLAG_PENDING_START; + btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); } break; @@ -665,17 +730,23 @@ static BOOLEAN btc_av_state_started_handler(btc_sm_event_t event, void *p_data) /* increase the a2dp consumer task priority temporarily when start ** audio playing, to avoid overflow the audio packet queue. */ - adjust_priority_a2dp(TRUE); + // adjust_priority_a2dp(TRUE); break; case BTC_SM_EXIT_EVT: /* restore the a2dp consumer task priority when stop audio playing. */ - adjust_priority_a2dp(FALSE); + // adjust_priority_a2dp(FALSE); break; case BTC_AV_START_STREAM_REQ_EVT: +#if BTC_AV_SRC_INCLUDED + /* we were remotely started, just ack back the local request */ + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + btc_a2dp_on_started(NULL, TRUE); + } +#endif /* BTC_AV_SRC_INCLUDED */ break; /* fixme -- use suspend = true always to work around issue with BTA AV */ @@ -689,12 +760,18 @@ static BOOLEAN btc_av_state_started_handler(btc_sm_event_t event, void *p_data) /* if we were remotely suspended but suspend locally, local suspend always overrides */ btc_av_cb.flags &= ~BTC_AV_FLAG_REMOTE_SUSPEND; - +#if BTC_AV_SRC_INCLUDED + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + /* immediately stop transmission of frames while suspend is pending */ + btc_a2dp_source_set_tx_flush(TRUE); + } +#endif /* BTC_AV_SRC_INCLUDED */ +#if BTC_AV_SINK_INCLUDED if (btc_av_cb.peer_sep == AVDT_TSEP_SRC) { - btc_a2dp_set_rx_flush(TRUE); + btc_a2dp_sink_set_rx_flush(TRUE); btc_a2dp_on_stopped(NULL); } - +#endif /* BTC_AV_SINK_INCLUDED */ BTA_AvStop(TRUE); break; @@ -724,7 +801,12 @@ static BOOLEAN btc_av_state_started_handler(btc_sm_event_t event, void *p_data) /* if not successful, remain in current state */ if (p_av->suspend.status != BTA_AV_SUCCESS) { btc_av_cb.flags &= ~BTC_AV_FLAG_LOCAL_SUSPEND_PENDING; - +#if BTC_AV_SRC_INCLUDED + if (btc_av_cb.peer_sep == AVDT_TSEP_SNK) { + /* suspend failed, reset back tx flush state */ + btc_a2dp_source_set_tx_flush(FALSE); + } +#endif /* BTC_AV_SRC_INCLUDED */ return FALSE; } @@ -855,62 +937,6 @@ static void btc_av_event_free_data(btc_sm_event_t event, void *p_data) } } -static void bte_av_callback(tBTA_AV_EVT event, tBTA_AV *p_data) -{ - bt_status_t stat; - btc_msg_t msg; - - msg.sig = BTC_SIG_API_CB; - msg.pid = BTC_PID_A2DP; - msg.act = (uint8_t) event; - stat = btc_transfer_context(&msg, p_data, sizeof(tBTA_AV), btc_av_event_deep_copy); - - if (stat) { - LOG_ERROR("%s transfer failed\n", __func__); - } -} - -static void bte_av_media_callback(tBTA_AV_EVT event, tBTA_AV_MEDIA *p_data) -{ - btc_sm_state_t state; - UINT8 que_len; - tA2D_STATUS a2d_status; - tA2D_SBC_CIE sbc_cie; - - if (event == BTA_AV_MEDIA_DATA_EVT) { /* Switch to BTC_MEDIA context */ - state = btc_sm_get_state(btc_av_cb.sm_handle); - if ( (state == BTC_AV_STATE_STARTED) || /* send SBC packets only in Started State */ - (state == BTC_AV_STATE_OPENED) ) { - que_len = btc_media_sink_enque_buf((BT_HDR *)p_data); - LOG_DEBUG(" Packets in Que %d\n", que_len); - } else { - return; - } - } - - if (event == BTA_AV_MEDIA_SINK_CFG_EVT) { - /* send a command to BT Media Task */ - btc_reset_decoder((UINT8 *)p_data); - - /* currently only supportes SBC */ - a2d_status = A2D_ParsSbcInfo(&sbc_cie, (UINT8 *)p_data, FALSE); - if (a2d_status == A2D_SUCCESS) { - btc_msg_t msg; - btc_av_args_t arg; - - msg.sig = BTC_SIG_API_CB; - msg.pid = BTC_PID_A2DP; - msg.act = BTC_AV_SINK_CONFIG_REQ_EVT; - - memset(&arg, 0, sizeof(btc_av_args_t)); - arg.mcc.type = ESP_A2D_MCT_SBC; - memcpy(arg.mcc.cie.sbc, (uint8_t *)p_data + 3, ESP_A2D_CIE_LEN_SBC); - btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL); - } else { - LOG_ERROR("ERROR dump_codec_info A2D_ParsSbcInfo fail:%d\n", a2d_status); - } - } -} /******************************************************************************* ** ** Function btc_av_init @@ -921,10 +947,21 @@ static void bte_av_media_callback(tBTA_AV_EVT event, tBTA_AV_MEDIA *p_data) ** *******************************************************************************/ -bt_status_t btc_av_init(void) +static bt_status_t btc_av_init(int service_id) { if (btc_av_cb.sm_handle == NULL) { - if (!btc_a2dp_start_media_task()) { + bool stat = false; + if (service_id == BTA_A2DP_SOURCE_SERVICE_ID) { +#if BTC_AV_SRC_INCLUDED + stat = btc_a2dp_source_startup(); +#endif + } else if (service_id == BTA_A2DP_SINK_SERVICE_ID) { +#if BTC_AV_SINK_INCLUDED + stat = btc_a2dp_sink_startup(); +#endif + } + + if (!stat) { return BT_STATUS_FAIL; } @@ -933,30 +970,17 @@ bt_status_t btc_av_init(void) btc_sm_init((const btc_sm_handler_t *)btc_av_state_handlers, BTC_AV_STATE_IDLE); btc_dm_enable_service(BTA_A2DP_SOURCE_SERVICE_ID); -#if (BTA_AV_SINK_INCLUDED == TRUE) - btc_dm_enable_service(BTA_A2DP_SINK_SERVICE_ID); -#endif + + if (service_id == BTA_A2DP_SINK_SERVICE_ID) { + btc_dm_enable_service(BTA_A2DP_SINK_SERVICE_ID); + } btc_a2dp_on_init(); + + return BT_STATUS_SUCCESS; } - return BT_STATUS_SUCCESS; -} - -/******************************************************************************* -** -** Function init_sink -** -** Description Initializes the AV interface for sink mode -** -** Returns bt_status_t -** -*******************************************************************************/ -bt_status_t btc_a2d_sink_init(void) -{ - LOG_DEBUG("%s()\n", __func__); - - return btc_av_init(); + return BT_STATUS_FAIL; } /******************************************************************************* @@ -981,33 +1005,34 @@ static bt_status_t connect_int(bt_bdaddr_t *bd_addr, uint16_t uuid) return BT_STATUS_SUCCESS; } -bt_status_t btc_a2d_sink_connect(bt_bdaddr_t *remote_bda) -{ - LOG_DEBUG("%s\n", __FUNCTION__); - CHECK_BTAV_INIT(); - - return btc_queue_connect(UUID_SERVCLASS_AUDIO_SINK, remote_bda, connect_int); -} - /******************************************************************************* ** -** Function cleanup +** Function clean_up ** ** Description Shuts down the AV interface and does the cleanup ** ** Returns None ** *******************************************************************************/ -static void btc_a2d_sink_deinit(void) +static void clean_up(int service_id) { LOG_DEBUG("%s\n", __FUNCTION__); - btc_a2dp_stop_media_task(); + if (service_id == BTA_A2DP_SOURCE_SERVICE_ID) { +#if BTC_AV_SRC_INCLUDED + btc_a2dp_source_shutdown(); +#endif /* BTC_AV_SRC_INCLUDED */ + } else if (service_id == BTA_A2DP_SINK_SERVICE_ID) { +#if BTC_AV_SINK_INCLUDED + btc_a2dp_sink_shutdown(); +#endif /* BTC_AV_SINK_INCLUDED */ + } btc_dm_disable_service(BTA_A2DP_SOURCE_SERVICE_ID); -#if (BTA_AV_SINK_INCLUDED == TRUE) - btc_dm_disable_service(BTA_A2DP_SINK_SERVICE_ID); -#endif + + if (service_id == BTA_A2DP_SINK_SERVICE_ID) { + btc_dm_disable_service(BTA_A2DP_SINK_SERVICE_ID); + } /* Also shut down the AV state machine */ btc_sm_shutdown(btc_av_cb.sm_handle); @@ -1100,6 +1125,70 @@ void btc_dispatch_sm_event(btc_av_sm_event_t event, void *p_data, int len) btc_transfer_context(&msg, p_data, len, NULL); } +static void bte_av_callback(tBTA_AV_EVT event, tBTA_AV *p_data) +{ + bt_status_t stat; + btc_msg_t msg; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_A2DP; + msg.act = (uint8_t) event; + stat = btc_transfer_context(&msg, p_data, sizeof(tBTA_AV), btc_av_event_deep_copy); + + if (stat) { + LOG_ERROR("%s transfer failed\n", __func__); + } +} + +#if BTC_AV_SINK_INCLUDED +static void bte_av_media_callback(tBTA_AV_EVT event, tBTA_AV_MEDIA *p_data) +{ + btc_sm_state_t state; + UINT8 que_len; + tA2D_STATUS a2d_status; + tA2D_SBC_CIE sbc_cie; + + if (event == BTA_AV_MEDIA_DATA_EVT) { /* Switch to BTC_MEDIA context */ + state = btc_sm_get_state(btc_av_cb.sm_handle); + if ( (state == BTC_AV_STATE_STARTED) || /* send SBC packets only in Started State */ + (state == BTC_AV_STATE_OPENED) ) { + que_len = btc_a2dp_sink_enque_buf((BT_HDR *)p_data); + LOG_DEBUG(" Packets in Que %d\n", que_len); + } else { + return; + } + } + + if (event == BTA_AV_MEDIA_SINK_CFG_EVT) { + /* send a command to BT Media Task */ + btc_a2dp_sink_reset_decoder((UINT8 *)p_data); + + /* currently only supportes SBC */ + a2d_status = A2D_ParsSbcInfo(&sbc_cie, (UINT8 *)p_data, FALSE); + if (a2d_status == A2D_SUCCESS) { + btc_msg_t msg; + btc_av_args_t arg; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_SINK_CONFIG_REQ_EVT; + + memset(&arg, 0, sizeof(btc_av_args_t)); + arg.mcc.type = ESP_A2D_MCT_SBC; + memcpy(arg.mcc.cie.sbc, (uint8_t *)p_data + 3, ESP_A2D_CIE_LEN_SBC); + btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL); + } else { + LOG_ERROR("ERROR dump_codec_info A2D_ParsSbcInfo fail:%d\n", a2d_status); + } + } +} +#else +static void bte_av_media_callback(tBTA_AV_EVT event, tBTA_AV_MEDIA *p_data) +{ + LOG_WARN("%s : event %u\n", __func__, event); +} +#endif + /******************************************************************************* ** ** Function btc_av_execute_service @@ -1163,6 +1252,21 @@ BOOLEAN btc_av_is_connected(void) return ((state == BTC_AV_STATE_OPENED) || (state == BTC_AV_STATE_STARTED)); } +/******************************************************************************* + * + * Function btc_av_get_peer_sep + * + * Description Get the stream endpoint type. + * + * Returns The stream endpoint type: either AVDT_TSEP_SRC or + * AVDT_TSEP_SNK. + * + ******************************************************************************/ + +uint8_t btc_av_get_peer_sep(void) +{ + return btc_av_cb.peer_sep; +} /******************************************************************************* ** ** Function btc_av_is_peer_edr @@ -1204,6 +1308,7 @@ void btc_a2dp_call_handler(btc_msg_t *msg) { btc_av_args_t *arg = (btc_av_args_t *)(msg->arg); switch (msg->act) { +#if BTC_AV_SINK_INCLUDED case BTC_AV_SINK_CONFIG_REQ_EVT: { btc_sm_dispatch(btc_av_cb.sm_handle, msg->act, (void *)(msg->arg)); break; @@ -1232,6 +1337,45 @@ void btc_a2dp_call_handler(btc_msg_t *msg) btc_a2dp_sink_reg_data_cb(arg->data_cb); break; } +#endif /* BTC_AV_SINK_INCLUDED */ +#if BTC_AV_SRC_INCLUDED + case BTC_AV_SRC_API_INIT_EVT: { + btc_a2d_src_init(); + break; + } + case BTC_AV_SRC_API_DEINIT_EVT: { + btc_a2d_src_deinit(); + break; + } + case BTC_AV_SRC_API_CONNECT_EVT: { + btc_a2d_src_connect(&arg->src_connect); + break; + } + case BTC_AV_SRC_API_DISCONNECT_EVT: { + CHECK_BTAV_INIT(); + btc_sm_dispatch(btc_av_cb.sm_handle, BTC_AV_DISCONNECT_REQ_EVT, NULL); + break; + } + case BTC_AV_SRC_API_REG_DATA_CB_EVT: { + btc_a2dp_src_reg_data_cb(arg->src_data_cb); + break; + } +#endif /* BTC_AV_SRC_INCLUDED */ + case BTC_AV_API_MEDIA_CTRL_EVT: { + btc_a2dp_control_media_ctrl(arg->ctrl); + break; + } + case BTC_AV_DATAPATH_CTRL_EVT: { + btc_a2dp_control_datapath_ctrl(arg->dp_evt); + break; + } + // case BTC_AV_DISCONNECT_REQ_EVT: + case BTC_AV_START_STREAM_REQ_EVT: + case BTC_AV_STOP_STREAM_REQ_EVT: + case BTC_AV_SUSPEND_STREAM_REQ_EVT: { + btc_sm_dispatch(btc_av_cb.sm_handle, msg->act, NULL); + break; + } default: LOG_WARN("%s : unhandled event: %d\n", __FUNCTION__, msg->act); } @@ -1243,4 +1387,70 @@ void btc_a2dp_cb_handler(btc_msg_t *msg) btc_av_event_free_data(msg->act, msg->arg); } +#if BTC_AV_SINK_INCLUDED + +/******************************************************************************* +** +** Function init_sink +** +** Description Initializes the AV interface for sink mode +** +** Returns bt_status_t +** +*******************************************************************************/ +static bt_status_t btc_a2d_sink_init(void) +{ + LOG_DEBUG("%s()\n", __func__); + + return btc_av_init(BTA_A2DP_SINK_SERVICE_ID); +} + +static bt_status_t btc_a2d_sink_connect(bt_bdaddr_t *remote_bda) +{ + LOG_DEBUG("%s\n", __FUNCTION__); + CHECK_BTAV_INIT(); + + return btc_queue_connect(UUID_SERVCLASS_AUDIO_SINK, remote_bda, connect_int); +} + +static void btc_a2d_sink_deinit(void) +{ + clean_up(BTA_A2DP_SINK_SERVICE_ID); +} + +#endif /* BTC_AV_SINK_INCLUDED */ + +#if BTC_AV_SRC_INCLUDED + +/******************************************************************************* +** +** Function btc_a2d_src_init +** +** Description Initializes the AV interface for source mode +** +** Returns bt_status_t +** +*******************************************************************************/ +static bt_status_t btc_a2d_src_init(void) +{ + LOG_DEBUG("%s()\n", __func__); + + return btc_av_init(BTA_A2DP_SOURCE_SERVICE_ID); +} + +static void btc_a2d_src_deinit(void) +{ + clean_up(BTA_A2DP_SOURCE_SERVICE_ID); +} + +static bt_status_t btc_a2d_src_connect(bt_bdaddr_t *remote_bda) +{ + LOG_DEBUG("%s\n", __FUNCTION__); + CHECK_BTAV_INIT(); + + return btc_queue_connect(UUID_SERVCLASS_AUDIO_SOURCE, remote_bda, connect_int); +} + +#endif /* BTC_AV_SRC_INCLUDED */ + #endif /* #if BTC_AV_INCLUDED */ diff --git a/components/bt/bluedroid/btc/profile/std/a2dp/btc_media_task.c b/components/bt/bluedroid/btc/profile/std/a2dp/btc_media_task.c deleted file mode 100644 index 1b6db63c4..000000000 --- a/components/bt/bluedroid/btc/profile/std/a2dp/btc_media_task.c +++ /dev/null @@ -1,1015 +0,0 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/****************************************************************************** - ** - ** Name: btc_media_task.c - ** - ** Description: This is the multimedia module for the BTC system. It - ** contains task implementations AV, HS and HF profiles - ** audio & video processing - ** - ******************************************************************************/ - -#include "bt_target.h" -#include "bt_trace.h" -#include -#include -#include -#include "fixed_queue.h" -#include "bta_api.h" -#include "btu.h" -#include "bta_sys.h" -#include "bta_sys_int.h" -#include "bta_av_api.h" -#include "a2d_api.h" -#include "a2d_sbc.h" -#include "a2d_int.h" -#include "bta_av_sbc.h" -#include "bta_av_ci.h" -#include "l2c_api.h" -#include "btc_av_co.h" -#include "btc_media.h" -#include "alarm.h" -#include "bt_trace.h" -#include "thread.h" -#include "bt_defs.h" -#include "btc_av.h" -#include "btc_sm.h" -#include "btc_util.h" -#include "allocator.h" -#include "bt_utils.h" -#include "esp_a2dp_api.h" -#include "mutex.h" - -// #if (BTA_AV_SINK_INCLUDED == TRUE) -#include "oi_codec_sbc.h" -#include "oi_status.h" -// #endif - -#if BTC_AV_INCLUDED - -// #if (BTA_AV_SINK_INCLUDED == TRUE) -OI_CODEC_SBC_DECODER_CONTEXT context; -OI_UINT32 contextData[CODEC_DATA_WORDS(2, SBC_CODEC_FAST_FILTER_BUFFERS)]; -OI_INT16 pcmData[15 * SBC_MAX_SAMPLES_PER_FRAME * SBC_MAX_CHANNELS]; -// #endif - -/***************************************************************************** - ** Constants - *****************************************************************************/ - -/* BTC media cmd event definition : BTC_MEDIA_TASK_CMD */ -enum { - BTC_MEDIA_FLUSH_AA_RX = 1, - BTC_MEDIA_AUDIO_SINK_CFG_UPDATE, - BTC_MEDIA_AUDIO_SINK_CLEAR_TRACK -}; - -enum { - MEDIA_TASK_STATE_OFF = 0, - MEDIA_TASK_STATE_ON = 1, - MEDIA_TASK_STATE_SHUTTING_DOWN = 2 -}; - -enum { - SIG_MEDIA_TASK_INIT = 0xf0, - SIG_MEDIA_TASK_CLEAN_UP = 0xf1, - SIG_MEDIA_TASK_AVK_DATA_READY = 0xf2, - SIG_MEDIA_TASK_CMD_READY = 0xf3 -}; - -/* - * CONGESTION COMPENSATION CTRL :: - * - * Thus setting controls how many buffers we will hold in media task - * during temp link congestion. Together with the stack buffer queues - * it controls much temporary a2dp link congestion we can - * compensate for. It however also depends on the default run level of sinks - * jitterbuffers. Depending on type of sink this would vary. - * Ideally the (SRC) max tx buffer capacity should equal the sinks - * jitterbuffer runlevel including any intermediate buffers on the way - * towards the sinks codec. - */ - -/* fixme -- define this in pcm time instead of buffer count */ - -/* The typical runlevel of the tx queue size is ~1 buffer - but due to link flow control or thread preemption in lower - layers we might need to temporarily buffer up data */ - -/* 5 frames is equivalent to 6.89*5*2.9 ~= 100 ms @ 44.1 khz, 20 ms mediatick */ -#define MAX_OUTPUT_A2DP_FRAME_QUEUE_SZ (5) - -typedef struct { - UINT16 num_frames_to_be_processed; - UINT16 len; - UINT16 offset; - UINT16 layer_specific; -} tBT_SBC_HDR; - -typedef struct { - fixed_queue_t *RxSbcQ; - void *av_sm_hdl; - UINT8 peer_sep; - UINT8 busy_level; - BOOLEAN rx_flush; /* discards any incoming data when true */ - BOOLEAN data_channel_open; - UINT8 channel_count; - UINT32 sample_rate; -} tBTC_MEDIA_CB; - -// #if (BTA_AV_SINK_INCLUDED == TRUE) -extern OI_STATUS OI_CODEC_SBC_DecodeFrame(OI_CODEC_SBC_DECODER_CONTEXT *context, - const OI_BYTE **frameData, - unsigned long *frameBytes, - OI_INT16 *pcmData, - unsigned long *pcmBytes); -extern OI_STATUS OI_CODEC_SBC_DecoderReset(OI_CODEC_SBC_DECODER_CONTEXT *context, - unsigned long *decoderData, - unsigned long decoderDataBytes, - OI_UINT8 maxChannels, - OI_UINT8 pcmStride, - OI_BOOL enhanced); -// #endif - -static void btc_media_flush_q(fixed_queue_t *p_q); -static void btc_media_task_aa_rx_flush(void); -static const char *dump_media_event(UINT16 event); -static void btc_media_thread_handle_cmd(fixed_queue_t *queue); - -/* Handle incoming media packets A2DP SINK streaming*/ -static void btc_media_task_handle_inc_media(tBT_SBC_HDR *p_msg); -static void btc_media_task_aa_handle_decoder_reset(BT_HDR *p_msg); -static void btc_media_task_aa_handle_clear_track(void); -BOOLEAN btc_media_task_clear_track(void); -static void btc_media_task_handler(void *arg); - -static void btc_media_task_avk_data_ready(UNUSED_ATTR void *context); -static void btc_media_thread_init(UNUSED_ATTR void *context); -static void btc_media_thread_cleanup(UNUSED_ATTR void *context); - -static tBTC_MEDIA_CB btc_media_cb; -static int media_task_running = MEDIA_TASK_STATE_OFF; -static fixed_queue_t *btc_media_cmd_msg_queue = NULL; -static xTaskHandle xBtcMediaTaskHandle = NULL; -static QueueHandle_t xBtcMediaDataQueue = NULL; -static QueueHandle_t xBtcMediaCtrlQueue = NULL; -static QueueSetHandle_t xBtcMediaQueueSet; -static esp_a2d_data_cb_t bt_av_sink_data_callback = NULL; - -void btc_a2dp_sink_reg_data_cb(esp_a2d_data_cb_t callback) -{ - // todo: critical section protection - bt_av_sink_data_callback = callback; -} - -static inline void btc_a2d_data_cb_to_app(const uint8_t *data, uint32_t len) -{ - // todo: critical section protection - if (bt_av_sink_data_callback) { - bt_av_sink_data_callback(data, len); - } -} - -/***************************************************************************** - ** Misc helper functions - *****************************************************************************/ -UNUSED_ATTR static const char *dump_media_event(UINT16 event) -{ - switch (event) { - CASE_RETURN_STR(BTC_MEDIA_FLUSH_AA_RX) - CASE_RETURN_STR(BTC_MEDIA_AUDIO_SINK_CFG_UPDATE) - CASE_RETURN_STR(BTC_MEDIA_AUDIO_SINK_CLEAR_TRACK) - - default: - return "UNKNOWN MEDIA EVENT"; - } -} - -/***************************************************************************** - ** BTC ADAPTATION - *****************************************************************************/ - -static void btc_media_ctrl_post(uint32_t sig) -{ - BtTaskEvt_t *evt = (BtTaskEvt_t *)osi_malloc(sizeof(BtTaskEvt_t)); - if (evt == NULL) { - return; - } - - evt->sig = sig; - evt->par = 0; - - if (xQueueSend(xBtcMediaCtrlQueue, &evt, 10 / portTICK_RATE_MS) != pdTRUE) { - APPL_TRACE_WARNING("xBtcMediaCtrlQueue failed, sig 0x%x\n", sig); - } -} - -static void btc_media_data_post(void) -{ - void *evt; - if (xQueueSend(xBtcMediaDataQueue, &evt, 0) != pdTRUE) { - APPL_TRACE_DEBUG("Media data Q filled\n"); - } -} - -static void btc_media_ctrl_handler(BtTaskEvt_t *e) -{ - if (e == NULL) { - return; - } - switch (e->sig) { - case SIG_MEDIA_TASK_CMD_READY: - fixed_queue_process(btc_media_cmd_msg_queue); - break; - case SIG_MEDIA_TASK_INIT: - btc_media_thread_init(NULL); - break; - case SIG_MEDIA_TASK_CLEAN_UP: - btc_media_thread_cleanup(NULL); - break; - default: - APPL_TRACE_WARNING("media task unhandled evt: 0x%x\n", e->sig); - } -} - - -static void btc_media_task_handler(void *arg) -{ - QueueSetMemberHandle_t xActivatedMember; - BtTaskEvt_t *e = NULL; - for (;;) { - xActivatedMember = xQueueSelectFromSet(xBtcMediaQueueSet, portMAX_DELAY); - if (xActivatedMember == xBtcMediaDataQueue) { - xQueueReceive(xActivatedMember, &e, 0); - btc_media_task_avk_data_ready(NULL); - } else if (xActivatedMember == xBtcMediaCtrlQueue) { - xQueueReceive(xActivatedMember, &e, 0); - btc_media_ctrl_handler(e); - osi_free(e); - } - } -} - -bool btc_a2dp_start_media_task(void) -{ - if (media_task_running != MEDIA_TASK_STATE_OFF) { - APPL_TRACE_ERROR("warning : media task already running"); - return false; - } - - APPL_TRACE_EVENT("## A2DP START MEDIA THREAD ##"); - - xBtcMediaQueueSet = xQueueCreateSet(BTC_MEDIA_TASK_QUEUE_SET_LEN); - configASSERT(xBtcMediaQueueSet); - xBtcMediaDataQueue = xQueueCreate(BTC_MEDIA_DATA_QUEUE_LEN, sizeof(void *)); - configASSERT(xBtcMediaDataQueue); - xQueueAddToSet(xBtcMediaDataQueue, xBtcMediaQueueSet); - - xBtcMediaCtrlQueue = xQueueCreate(BTC_MEDIA_CTRL_QUEUE_LEN, sizeof(void *)); - configASSERT(xBtcMediaCtrlQueue); - xQueueAddToSet(xBtcMediaCtrlQueue, xBtcMediaQueueSet); - - if (!xBtcMediaDataQueue || !xBtcMediaCtrlQueue || !xBtcMediaQueueSet ) { - goto error_exit; - } - - xTaskCreatePinnedToCore(btc_media_task_handler, "BtcMediaT\n", 2048, NULL, configMAX_PRIORITIES - 3, &xBtcMediaTaskHandle, 0); - if (xBtcMediaTaskHandle == NULL) { - goto error_exit; - } - - btc_media_cmd_msg_queue = fixed_queue_new(SIZE_MAX); - if (btc_media_cmd_msg_queue == NULL) { - goto error_exit; - } - fixed_queue_register_dequeue(btc_media_cmd_msg_queue, btc_media_thread_handle_cmd); - btc_media_ctrl_post(SIG_MEDIA_TASK_INIT); - - APPL_TRACE_EVENT("## A2DP MEDIA THREAD STARTED ##\n"); - - return true; - -error_exit:; - APPL_TRACE_ERROR("%s unable to start up media thread\n", __func__); - - if (xBtcMediaTaskHandle != NULL) { - vTaskDelete(xBtcMediaTaskHandle); - xBtcMediaTaskHandle = NULL; - } - - if (xBtcMediaDataQueue) { - vQueueDelete(xBtcMediaDataQueue); - xBtcMediaDataQueue = NULL; - } - if (xBtcMediaCtrlQueue) { - vQueueDelete(xBtcMediaCtrlQueue); - xBtcMediaCtrlQueue = NULL; - } - - fixed_queue_free(btc_media_cmd_msg_queue, NULL); - btc_media_cmd_msg_queue = NULL; - return false; -} - -void btc_a2dp_stop_media_task(void) -{ - APPL_TRACE_EVENT("## A2DP STOP MEDIA THREAD ##\n"); - - // Exit thread - btc_media_ctrl_post(SIG_MEDIA_TASK_CLEAN_UP); - // TODO: wait until CLEAN up is done, then do task delete - vTaskDelete(xBtcMediaTaskHandle); - xBtcMediaTaskHandle = NULL; - - vQueueDelete(xBtcMediaDataQueue); - xBtcMediaDataQueue = NULL; - - vQueueDelete(xBtcMediaCtrlQueue); - xBtcMediaCtrlQueue = NULL; - - fixed_queue_free(btc_media_cmd_msg_queue, NULL); - btc_media_cmd_msg_queue = NULL; -} - -/***************************************************************************** -** -** Function btc_a2dp_on_init -** -** Description -** -** Returns -** -*******************************************************************************/ -void btc_a2dp_on_init(void) -{ - //tput_mon(1, 0, 1); -} - -/***************************************************************************** -** -** Function btc_a2dp_setup_codec -** -** Description -** -** Returns -** -*******************************************************************************/ - -void btc_a2dp_setup_codec(void) -{ - tBTC_AV_MEDIA_FEEDINGS media_feeding; - tBTC_STATUS status; - - APPL_TRACE_EVENT("## A2DP SETUP CODEC ##\n"); - - osi_mutex_global_lock(); - - /* for now hardcode 44.1 khz 16 bit stereo PCM format */ - media_feeding.cfg.pcm.sampling_freq = 44100; - media_feeding.cfg.pcm.bit_per_sample = 16; - media_feeding.cfg.pcm.num_channel = 2; - media_feeding.format = BTC_AV_CODEC_PCM; - - bta_av_co_audio_set_codec(&media_feeding, &status); - - - osi_mutex_global_unlock(); -} - -/***************************************************************************** -** -** Function btc_a2dp_on_idle -** -** Description -** -** Returns -** -*******************************************************************************/ - -void btc_a2dp_on_idle(void) -{ - APPL_TRACE_EVENT("## ON A2DP IDLE ##\n"); - - bta_av_co_init(); - if (btc_media_cb.peer_sep == AVDT_TSEP_SRC) { - btc_media_cb.rx_flush = TRUE; - btc_media_task_aa_rx_flush_req(); - btc_media_task_clear_track(); - APPL_TRACE_DEBUG("Stopped BT track"); - } -} - -/******************************************************************************* - ** - ** Function btc_media_task_clear_track - ** - ** Description - ** - ** Returns TRUE is success - ** - *******************************************************************************/ -BOOLEAN btc_media_task_clear_track(void) -{ - BT_HDR *p_buf; - - if (NULL == (p_buf = osi_malloc(sizeof(BT_HDR)))) { - return FALSE; - } - - p_buf->event = BTC_MEDIA_AUDIO_SINK_CLEAR_TRACK; - - fixed_queue_enqueue(btc_media_cmd_msg_queue, p_buf); - btc_media_ctrl_post(SIG_MEDIA_TASK_CMD_READY); - return TRUE; -} - -/***************************************************************************** -** -** Function btc_reset_decoder -** -** Description -** -** Returns -** -*******************************************************************************/ - -void btc_reset_decoder(UINT8 *p_av) -{ - APPL_TRACE_EVENT("btc_reset_decoder"); - APPL_TRACE_DEBUG("btc_reset_decoder p_codec_info[%x:%x:%x:%x:%x:%x]\n", - p_av[1], p_av[2], p_av[3], - p_av[4], p_av[5], p_av[6]); - - tBTC_MEDIA_SINK_CFG_UPDATE *p_buf; - if (NULL == (p_buf = osi_malloc(sizeof(tBTC_MEDIA_SINK_CFG_UPDATE)))) { - APPL_TRACE_ERROR("btc_reset_decoder No Buffer "); - return; - } - - memcpy(p_buf->codec_info, p_av, AVDT_CODEC_SIZE); - p_buf->hdr.event = BTC_MEDIA_AUDIO_SINK_CFG_UPDATE; - - fixed_queue_enqueue(btc_media_cmd_msg_queue, p_buf); - btc_media_ctrl_post(SIG_MEDIA_TASK_CMD_READY); -} - -/***************************************************************************** -** -** Function btc_a2dp_on_stopped -** -** Description -** -** Returns -** -*******************************************************************************/ - -void btc_a2dp_on_stopped(tBTA_AV_SUSPEND *p_av) -{ - APPL_TRACE_EVENT("## ON A2DP STOPPED ##\n"); - if (btc_media_cb.peer_sep == AVDT_TSEP_SRC) { /* Handling for A2DP SINK cases*/ - btc_media_cb.rx_flush = TRUE; - btc_media_task_aa_rx_flush_req(); - btc_media_cb.data_channel_open = FALSE; - return; - } -} - -/***************************************************************************** -** -** Function btc_a2dp_on_suspended -** -** Description -** -** Returns -** -*******************************************************************************/ - -void btc_a2dp_on_suspended(tBTA_AV_SUSPEND *p_av) -{ - APPL_TRACE_EVENT("## ON A2DP SUSPENDED ##\n"); - if (btc_media_cb.peer_sep == AVDT_TSEP_SRC) { - btc_media_cb.rx_flush = TRUE; - btc_media_task_aa_rx_flush_req(); - return; - } -} - -/* when true media task discards any rx frames */ -void btc_a2dp_set_rx_flush(BOOLEAN enable) -{ - APPL_TRACE_EVENT("## DROP RX %d ##\n", enable); - btc_media_cb.rx_flush = enable; -} - -static void btc_media_task_avk_data_ready(UNUSED_ATTR void *context) -{ - tBT_SBC_HDR *p_msg; - - if (fixed_queue_is_empty(btc_media_cb.RxSbcQ)) { - APPL_TRACE_DEBUG(" QUE EMPTY "); - } else { - if (btc_media_cb.rx_flush == TRUE) { - btc_media_flush_q(btc_media_cb.RxSbcQ); - return; - } - - while ((p_msg = (tBT_SBC_HDR *)fixed_queue_try_peek_first(btc_media_cb.RxSbcQ)) != NULL ) { - btc_media_task_handle_inc_media(p_msg); - p_msg = (tBT_SBC_HDR *)fixed_queue_try_dequeue(btc_media_cb.RxSbcQ); - if ( p_msg == NULL ) { - APPL_TRACE_ERROR("Insufficient data in que "); - break; - } - osi_free(p_msg); - } - APPL_TRACE_DEBUG(" Process Frames - "); - } -} - -static void btc_media_thread_init(UNUSED_ATTR void *context) -{ - APPL_TRACE_EVENT("%s\n", __func__); - memset(&btc_media_cb, 0, sizeof(btc_media_cb)); - btc_media_cb.av_sm_hdl = btc_av_get_sm_handle(); - raise_priority_a2dp(TASK_HIGH_MEDIA); - media_task_running = MEDIA_TASK_STATE_ON; - - btc_media_cb.RxSbcQ = fixed_queue_new(SIZE_MAX); -} - -static void btc_media_thread_cleanup(UNUSED_ATTR void *context) -{ - /* make sure no channels are restarted while shutting down */ - media_task_running = MEDIA_TASK_STATE_SHUTTING_DOWN; - - btc_media_cb.data_channel_open = FALSE; - /* Clear media task flag */ - media_task_running = MEDIA_TASK_STATE_OFF; - - fixed_queue_free(btc_media_cb.RxSbcQ, osi_free_func); -} - -/******************************************************************************* - ** - ** Function btc_media_flush_q - ** - ** Description - ** - ** Returns void - ** - *******************************************************************************/ -static void btc_media_flush_q(fixed_queue_t *p_q) -{ - while (! fixed_queue_is_empty(p_q)) { - osi_free(fixed_queue_try_dequeue(p_q)); - } -} - -static void btc_media_thread_handle_cmd(fixed_queue_t *queue) -{ - BT_HDR *p_msg; - while (!fixed_queue_is_empty(queue)) { - p_msg = (BT_HDR *)fixed_queue_dequeue(queue); - APPL_TRACE_VERBOSE("btc_media_thread_handle_cmd : %d %s\n", p_msg->event, - dump_media_event(p_msg->event)); - - switch (p_msg->event) { - case BTC_MEDIA_AUDIO_SINK_CFG_UPDATE: - btc_media_task_aa_handle_decoder_reset(p_msg); - break; - case BTC_MEDIA_AUDIO_SINK_CLEAR_TRACK: - btc_media_task_aa_handle_clear_track(); - break; - case BTC_MEDIA_FLUSH_AA_RX: - btc_media_task_aa_rx_flush(); - break; - default: - APPL_TRACE_ERROR("ERROR in %s unknown event %d\n", __func__, p_msg->event); - } - osi_free(p_msg); - APPL_TRACE_VERBOSE("%s: %s DONE\n", __func__, dump_media_event(p_msg->event)); - } -} - -/******************************************************************************* - ** - ** Function btc_media_task_handle_inc_media - ** - ** Description - ** - ** Returns void - ** - *******************************************************************************/ -static void btc_media_task_handle_inc_media(tBT_SBC_HDR *p_msg) -{ - UINT8 *sbc_start_frame = ((UINT8 *)(p_msg + 1) + p_msg->offset + 1); - int count; - UINT32 pcmBytes, availPcmBytes; - OI_INT16 *pcmDataPointer = pcmData; /*Will be overwritten on next packet receipt*/ - OI_STATUS status; - int num_sbc_frames = p_msg->num_frames_to_be_processed; - UINT32 sbc_frame_len = p_msg->len - 1; - availPcmBytes = 2 * sizeof(pcmData); - - if ((btc_media_cb.peer_sep == AVDT_TSEP_SNK) || (btc_media_cb.rx_flush)) { - APPL_TRACE_DEBUG(" State Changed happened in this tick "); - return; - } - - // ignore data if no one is listening - if (!btc_media_cb.data_channel_open) { - return; - } - - APPL_TRACE_DEBUG("Number of sbc frames %d, frame_len %d\n", num_sbc_frames, sbc_frame_len); - - for (count = 0; count < num_sbc_frames && sbc_frame_len != 0; count ++) { - pcmBytes = availPcmBytes; - status = OI_CODEC_SBC_DecodeFrame(&context, (const OI_BYTE **)&sbc_start_frame, - (OI_UINT32 *)&sbc_frame_len, - (OI_INT16 *)pcmDataPointer, - (OI_UINT32 *)&pcmBytes); - if (!OI_SUCCESS(status)) { - APPL_TRACE_ERROR("Decoding failure: %d\n", status); - break; - } - availPcmBytes -= pcmBytes; - pcmDataPointer += pcmBytes / 2; - p_msg->offset += (p_msg->len - 1) - sbc_frame_len; - p_msg->len = sbc_frame_len + 1; - } - - btc_a2d_data_cb_to_app((uint8_t *)pcmData, (2 * sizeof(pcmData) - availPcmBytes)); -} - -/******************************************************************************* - ** - ** Function btc_media_task_aa_rx_flush_req - ** - ** Description - ** - ** Returns TRUE is success - ** - *******************************************************************************/ -BOOLEAN btc_media_task_aa_rx_flush_req(void) -{ - BT_HDR *p_buf; - - if (fixed_queue_is_empty(btc_media_cb.RxSbcQ) == TRUE) { /* Que is already empty */ - return TRUE; - } - - if (NULL == (p_buf = osi_malloc(sizeof(BT_HDR)))) { - return FALSE; - } - - p_buf->event = BTC_MEDIA_FLUSH_AA_RX; - - fixed_queue_enqueue(btc_media_cmd_msg_queue, p_buf); - btc_media_ctrl_post(SIG_MEDIA_TASK_CMD_READY); - return TRUE; -} - -/******************************************************************************* - ** - ** Function btc_media_task_aa_rx_flush - ** - ** Description - ** - ** Returns void - ** - *******************************************************************************/ -static void btc_media_task_aa_rx_flush(void) -{ - /* Flush all enqueued SBC buffers (encoded) */ - APPL_TRACE_DEBUG("btc_media_task_aa_rx_flush"); - - btc_media_flush_q(btc_media_cb.RxSbcQ); -} - -int btc_a2dp_get_track_frequency(UINT8 frequency) -{ - int freq = 48000; - switch (frequency) { - case A2D_SBC_IE_SAMP_FREQ_16: - freq = 16000; - break; - case A2D_SBC_IE_SAMP_FREQ_32: - freq = 32000; - break; - case A2D_SBC_IE_SAMP_FREQ_44: - freq = 44100; - break; - case A2D_SBC_IE_SAMP_FREQ_48: - freq = 48000; - break; - } - return freq; -} - -int btc_a2dp_get_track_channel_count(UINT8 channeltype) -{ - int count = 1; - switch (channeltype) { - case A2D_SBC_IE_CH_MD_MONO: - count = 1; - break; - case A2D_SBC_IE_CH_MD_DUAL: - case A2D_SBC_IE_CH_MD_STEREO: - case A2D_SBC_IE_CH_MD_JOINT: - count = 2; - break; - } - return count; -} - -void btc_a2dp_set_peer_sep(UINT8 sep) -{ - btc_media_cb.peer_sep = sep; -} - -static void btc_media_task_aa_handle_clear_track (void) -{ - APPL_TRACE_DEBUG("btc_media_task_aa_handle_clear_track"); -} - -/******************************************************************************* - ** - ** Function btc_media_task_aa_handle_decoder_reset - ** - ** Description - ** - ** Returns void - ** - *******************************************************************************/ -static void btc_media_task_aa_handle_decoder_reset(BT_HDR *p_msg) -{ - tBTC_MEDIA_SINK_CFG_UPDATE *p_buf = (tBTC_MEDIA_SINK_CFG_UPDATE *) p_msg; - tA2D_STATUS a2d_status; - tA2D_SBC_CIE sbc_cie; - OI_STATUS status; - UINT32 freq_multiple = 48 * 20; /* frequency multiple for 20ms of data , initialize with 48K*/ - UINT32 num_blocks = 16; - UINT32 num_subbands = 8; - - APPL_TRACE_EVENT("btc_media_task_aa_handle_decoder_reset p_codec_info[%x:%x:%x:%x:%x:%x]\n", - p_buf->codec_info[1], p_buf->codec_info[2], p_buf->codec_info[3], - p_buf->codec_info[4], p_buf->codec_info[5], p_buf->codec_info[6]); - - a2d_status = A2D_ParsSbcInfo(&sbc_cie, p_buf->codec_info, FALSE); - if (a2d_status != A2D_SUCCESS) { - APPL_TRACE_ERROR("ERROR dump_codec_info A2D_ParsSbcInfo fail:%d\n", a2d_status); - return; - } - - btc_media_cb.sample_rate = btc_a2dp_get_track_frequency(sbc_cie.samp_freq); - btc_media_cb.channel_count = btc_a2dp_get_track_channel_count(sbc_cie.ch_mode); - - btc_media_cb.rx_flush = FALSE; - APPL_TRACE_EVENT("Reset to sink role"); - status = OI_CODEC_SBC_DecoderReset(&context, contextData, sizeof(contextData), 2, 2, FALSE); - if (!OI_SUCCESS(status)) { - APPL_TRACE_ERROR("OI_CODEC_SBC_DecoderReset failed with error code %d\n", status); - } - - btc_media_cb.data_channel_open = TRUE; - - switch (sbc_cie.samp_freq) { - case A2D_SBC_IE_SAMP_FREQ_16: - APPL_TRACE_DEBUG("\tsamp_freq:%d (16000)\n", sbc_cie.samp_freq); - freq_multiple = 16 * 20; - break; - case A2D_SBC_IE_SAMP_FREQ_32: - APPL_TRACE_DEBUG("\tsamp_freq:%d (32000)\n", sbc_cie.samp_freq); - freq_multiple = 32 * 20; - break; - case A2D_SBC_IE_SAMP_FREQ_44: - APPL_TRACE_DEBUG("\tsamp_freq:%d (44100)\n", sbc_cie.samp_freq); - freq_multiple = 441 * 2; - break; - case A2D_SBC_IE_SAMP_FREQ_48: - APPL_TRACE_DEBUG("\tsamp_freq:%d (48000)\n", sbc_cie.samp_freq); - freq_multiple = 48 * 20; - break; - default: - APPL_TRACE_DEBUG(" Unknown Frequency "); - break; - } - - switch (sbc_cie.ch_mode) { - case A2D_SBC_IE_CH_MD_MONO: - APPL_TRACE_DEBUG("\tch_mode:%d (Mono)\n", sbc_cie.ch_mode); - break; - case A2D_SBC_IE_CH_MD_DUAL: - APPL_TRACE_DEBUG("\tch_mode:%d (DUAL)\n", sbc_cie.ch_mode); - break; - case A2D_SBC_IE_CH_MD_STEREO: - APPL_TRACE_DEBUG("\tch_mode:%d (STEREO)\n", sbc_cie.ch_mode); - break; - case A2D_SBC_IE_CH_MD_JOINT: - APPL_TRACE_DEBUG("\tch_mode:%d (JOINT)\n", sbc_cie.ch_mode); - break; - default: - APPL_TRACE_DEBUG(" Unknown Mode "); - break; - } - - switch (sbc_cie.block_len) { - case A2D_SBC_IE_BLOCKS_4: - APPL_TRACE_DEBUG("\tblock_len:%d (4)\n", sbc_cie.block_len); - num_blocks = 4; - break; - case A2D_SBC_IE_BLOCKS_8: - APPL_TRACE_DEBUG("\tblock_len:%d (8)\n", sbc_cie.block_len); - num_blocks = 8; - break; - case A2D_SBC_IE_BLOCKS_12: - APPL_TRACE_DEBUG("\tblock_len:%d (12)\n", sbc_cie.block_len); - num_blocks = 12; - break; - case A2D_SBC_IE_BLOCKS_16: - APPL_TRACE_DEBUG("\tblock_len:%d (16)\n", sbc_cie.block_len); - num_blocks = 16; - break; - default: - APPL_TRACE_DEBUG(" Unknown BlockLen "); - break; - } - - switch (sbc_cie.num_subbands) { - case A2D_SBC_IE_SUBBAND_4: - APPL_TRACE_DEBUG("\tnum_subbands:%d (4)\n", sbc_cie.num_subbands); - num_subbands = 4; - break; - case A2D_SBC_IE_SUBBAND_8: - APPL_TRACE_DEBUG("\tnum_subbands:%d (8)\n", sbc_cie.num_subbands); - num_subbands = 8; - break; - default: - APPL_TRACE_DEBUG(" Unknown SubBands "); - break; - } - - switch (sbc_cie.alloc_mthd) { - case A2D_SBC_IE_ALLOC_MD_S: - APPL_TRACE_DEBUG("\talloc_mthd:%d (SNR)\n", sbc_cie.alloc_mthd); - break; - case A2D_SBC_IE_ALLOC_MD_L: - APPL_TRACE_DEBUG("\talloc_mthd:%d (Loudness)\n", sbc_cie.alloc_mthd); - break; - default: - APPL_TRACE_DEBUG(" Unknown Allocation Method"); - break; - } - - APPL_TRACE_EVENT("\tBit pool Min:%d Max:%d\n", sbc_cie.min_bitpool, sbc_cie.max_bitpool); - - int frames_to_process = ((freq_multiple) / (num_blocks * num_subbands)) + 1; - APPL_TRACE_EVENT(" Frames to be processed in 20 ms %d\n", frames_to_process); -} - -/******************************************************************************* - ** - ** Function btc_media_sink_enque_buf - ** - ** Description This function is called by the av_co to fill A2DP Sink Queue - ** - ** - ** Returns size of the queue - *******************************************************************************/ -UINT8 btc_media_sink_enque_buf(BT_HDR *p_pkt) -{ - tBT_SBC_HDR *p_msg; - - if (btc_media_cb.rx_flush == TRUE) { /* Flush enabled, do not enque*/ - return fixed_queue_length(btc_media_cb.RxSbcQ); - } - - if (fixed_queue_length(btc_media_cb.RxSbcQ) >= MAX_OUTPUT_A2DP_FRAME_QUEUE_SZ) { - APPL_TRACE_WARNING("Pkt dropped\n"); - return fixed_queue_length(btc_media_cb.RxSbcQ); - } - - APPL_TRACE_DEBUG("btc_media_sink_enque_buf + "); - - /* allocate and Queue this buffer */ - if ((p_msg = (tBT_SBC_HDR *) osi_malloc(sizeof(tBT_SBC_HDR) + - p_pkt->offset + p_pkt->len)) != NULL) { - memcpy(p_msg, p_pkt, (sizeof(BT_HDR) + p_pkt->offset + p_pkt->len)); - p_msg->num_frames_to_be_processed = (*((UINT8 *)(p_msg + 1) + p_msg->offset)) & 0x0f; - APPL_TRACE_VERBOSE("btc_media_sink_enque_buf %d + \n", p_msg->num_frames_to_be_processed); - fixed_queue_enqueue(btc_media_cb.RxSbcQ, p_msg); - btc_media_data_post(); - } else { - /* let caller deal with a failed allocation */ - APPL_TRACE_WARNING("btc_media_sink_enque_buf No Buffer left - "); - } - return fixed_queue_length(btc_media_cb.RxSbcQ); -} - -/******************************************************************************* - ** - ** Function btc_media_aa_readbuf - ** - ** Description This function is called by the av_co to get the next buffer to send - ** - ** - ** Returns void - *******************************************************************************/ -BT_HDR *btc_media_aa_readbuf(void) -{ - return NULL; -} - -/******************************************************************************* - ** - ** Function dump_codec_info - ** - ** Description Decode and display codec_info (for debug) - ** - ** Returns void - ** - *******************************************************************************/ -void dump_codec_info(unsigned char *p_codec) -{ - tA2D_STATUS a2d_status; - tA2D_SBC_CIE sbc_cie; - - a2d_status = A2D_ParsSbcInfo(&sbc_cie, p_codec, FALSE); - if (a2d_status != A2D_SUCCESS) { - APPL_TRACE_ERROR("ERROR dump_codec_info A2D_ParsSbcInfo fail:%d\n", a2d_status); - return; - } - - APPL_TRACE_DEBUG("dump_codec_info"); - - if (sbc_cie.samp_freq == A2D_SBC_IE_SAMP_FREQ_16) { - APPL_TRACE_DEBUG("\tsamp_freq:%d (16000)\n", sbc_cie.samp_freq); - } else if (sbc_cie.samp_freq == A2D_SBC_IE_SAMP_FREQ_32) { - APPL_TRACE_DEBUG("\tsamp_freq:%d (32000)\n", sbc_cie.samp_freq); - } else if (sbc_cie.samp_freq == A2D_SBC_IE_SAMP_FREQ_44) { - APPL_TRACE_DEBUG("\tsamp_freq:%d (44.100)\n", sbc_cie.samp_freq); - } else if (sbc_cie.samp_freq == A2D_SBC_IE_SAMP_FREQ_48) { - APPL_TRACE_DEBUG("\tsamp_freq:%d (48000)\n", sbc_cie.samp_freq); - } else { - APPL_TRACE_DEBUG("\tBAD samp_freq:%d\n", sbc_cie.samp_freq); - } - - if (sbc_cie.ch_mode == A2D_SBC_IE_CH_MD_MONO) { - APPL_TRACE_DEBUG("\tch_mode:%d (Mono)\n", sbc_cie.ch_mode); - } else if (sbc_cie.ch_mode == A2D_SBC_IE_CH_MD_DUAL) { - APPL_TRACE_DEBUG("\tch_mode:%d (Dual)\n", sbc_cie.ch_mode); - } else if (sbc_cie.ch_mode == A2D_SBC_IE_CH_MD_STEREO) { - APPL_TRACE_DEBUG("\tch_mode:%d (Stereo)\n", sbc_cie.ch_mode); - } else if (sbc_cie.ch_mode == A2D_SBC_IE_CH_MD_JOINT) { - APPL_TRACE_DEBUG("\tch_mode:%d (Joint)\n", sbc_cie.ch_mode); - } else { - APPL_TRACE_DEBUG("\tBAD ch_mode:%d\n", sbc_cie.ch_mode); - } - - if (sbc_cie.block_len == A2D_SBC_IE_BLOCKS_4) { - APPL_TRACE_DEBUG("\tblock_len:%d (4)\n", sbc_cie.block_len); - } else if (sbc_cie.block_len == A2D_SBC_IE_BLOCKS_8) { - APPL_TRACE_DEBUG("\tblock_len:%d (8)\n", sbc_cie.block_len); - } else if (sbc_cie.block_len == A2D_SBC_IE_BLOCKS_12) { - APPL_TRACE_DEBUG("\tblock_len:%d (12)\n", sbc_cie.block_len); - } else if (sbc_cie.block_len == A2D_SBC_IE_BLOCKS_16) { - APPL_TRACE_DEBUG("\tblock_len:%d (16)\n", sbc_cie.block_len); - } else { - APPL_TRACE_DEBUG("\tBAD block_len:%d\n", sbc_cie.block_len); - } - - if (sbc_cie.num_subbands == A2D_SBC_IE_SUBBAND_4) { - APPL_TRACE_DEBUG("\tnum_subbands:%d (4)\n", sbc_cie.num_subbands); - } else if (sbc_cie.num_subbands == A2D_SBC_IE_SUBBAND_8) { - APPL_TRACE_DEBUG("\tnum_subbands:%d (8)\n", sbc_cie.num_subbands); - } else { - APPL_TRACE_DEBUG("\tBAD num_subbands:%d\n", sbc_cie.num_subbands); - } - - if (sbc_cie.alloc_mthd == A2D_SBC_IE_ALLOC_MD_S) { - APPL_TRACE_DEBUG("\talloc_mthd:%d (SNR)\n", sbc_cie.alloc_mthd); - } else if (sbc_cie.alloc_mthd == A2D_SBC_IE_ALLOC_MD_L) { - APPL_TRACE_DEBUG("\talloc_mthd:%d (Loundess)\n", sbc_cie.alloc_mthd); - } else { - APPL_TRACE_DEBUG("\tBAD alloc_mthd:%d\n", sbc_cie.alloc_mthd); - } - - APPL_TRACE_DEBUG("\tBit pool Min:%d Max:%d\n", sbc_cie.min_bitpool, sbc_cie.max_bitpool); - -} - -#endif /* #if BTC_AV_INCLUDED */ diff --git a/components/bt/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h b/components/bt/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h index 9ec9b5d44..cacaa01d8 100644 --- a/components/bt/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h +++ b/components/bt/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h @@ -15,7 +15,7 @@ #ifndef __BTC_AV_CO_H__ #define __BTC_AV_CO_H__ -#include "btc_media.h" +#include "btc_a2dp.h" #if (BTA_AV_INCLUDED == TRUE) /******************************************************************************* @@ -93,7 +93,7 @@ void bta_av_co_audio_codec_reset(void); ** Returns TRUE if all opened devices support this codec, FALSE otherwise ** *******************************************************************************/ -BOOLEAN bta_av_co_audio_codec_supported(tBTC_STATUS *p_status); +BOOLEAN bta_av_co_audio_codec_supported(tBTC_AV_STATUS *p_status); /******************************************************************************* ** @@ -106,7 +106,7 @@ BOOLEAN bta_av_co_audio_codec_supported(tBTC_STATUS *p_status); ** Returns TRUE if successful, FALSE otherwise ** *******************************************************************************/ -BOOLEAN bta_av_co_audio_set_codec(const tBTC_AV_MEDIA_FEEDINGS *p_feeding, tBTC_STATUS *p_status); +BOOLEAN bta_av_co_audio_set_codec(const tBTC_AV_MEDIA_FEEDINGS *p_feeding, tBTC_AV_STATUS *p_status); /******************************************************************************* ** diff --git a/components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c b/components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c index a18f42785..8c8e4dde6 100644 --- a/components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c +++ b/components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c @@ -1164,3 +1164,9 @@ void btc_gap_callback_init(void) BTM_BleRegiseterConnParamCallback(btc_update_conn_param_callback); } + +void btc_gap_ble_deinit(void) +{ + btc_cleanup_adv_data(&gl_bta_adv_data); + btc_cleanup_adv_data(&gl_bta_scan_rsp_data); +} diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatt_common.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatt_common.c new file mode 100644 index 000000000..af34c7108 --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatt_common.c @@ -0,0 +1,47 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "btc_task.h" +#include "btc_main.h" +#include "btc_dm.h" +#include "future.h" +#include "esp_err.h" +#include "btc_config.h" +#include "alarm.h" +#include "btc_ble_storage.h" +#include "btc_gatt_common.h" +#include "bta_gatt_common.h" + + +static void btc_set_local_mtu(uint16_t mtu) +{ + BTA_GATT_SetLocalMTU(mtu); +} + +void btc_gatt_com_call_handler(btc_msg_t *msg) +{ + LOG_DEBUG("%s act %d\n", __func__, msg->act); + switch (msg->act) { + case BTC_GATT_ACT_SET_LOCAL_MTU: + { + btc_ble_gatt_com_args_t *arg = (btc_ble_gatt_com_args_t *)(msg->arg); + btc_set_local_mtu(arg->set_mtu.mtu); + break; + } + default: + LOG_ERROR("%s UNKNOWN ACT %d\n", __func__, msg->act); + break; + } +} + diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_a2dp.h b/components/bt/bluedroid/btc/profile/std/include/btc_a2dp.h new file mode 100644 index 000000000..6243eb43b --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/include/btc_a2dp.h @@ -0,0 +1,108 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/******************************************************************************* + * + * Filename: btc_a2dp.h + * + * Description: Common definitions for A2DP + * + *******************************************************************************/ + +#ifndef __BTC_A2DP_H__ +#define __BTC_A2DP_H__ + +#include +#include "bt_target.h" +#include "bta_api.h" +#include "btc_av_api.h" +#include "esp_a2dp_api.h" + +#if BTC_AV_INCLUDED + +/******************************************************************************* + ** Constants + *******************************************************************************/ +#define BTC_AV_SUCCESS (0) +/** + * AV (Audio Video source) Errors + */ +#define BTC_ERROR_SRV_AV_NOT_ENABLED 700 /* AV is not enabled */ +#define BTC_ERROR_SRV_AV_FEEDING_NOT_SUPPORTED 701 /* Requested Feeding not supported */ +#define BTC_ERROR_SRV_AV_BUSY 702 /* Another operation ongoing */ +#define BTC_ERROR_SRV_AV_NOT_OPENED 703 /* No AV link opened */ +#define BTC_ERROR_SRV_AV_NOT_STARTED 704 /* AV is not started */ +#define BTC_ERROR_SRV_AV_CP_NOT_SUPPORTED 705 /* Content protection is not supported by all headsets */ + +/* Transcoding definition for TxTranscoding and RxTranscoding */ +#define BTC_MEDIA_TRSCD_OFF 0 +#define BTC_MEDIA_TRSCD_PCM_2_SBC 1 /* Tx */ + + +/******************************************************************************* + ** Data types + *******************************************************************************/ +typedef int tBTC_AV_STATUS; + +/******************************************************************************* + ** Public functions + *******************************************************************************/ + +void btc_a2dp_on_init(void); + +/******************************************************************************* + ** + ** Function btc_a2dp_on_idle + ** + ** Description Process 'idle' request from BTC AV state machine during + ** initialization + ** + *******************************************************************************/ +void btc_a2dp_on_idle(void); + +/******************************************************************************* + ** + ** Function btc_a2dp_on_started + ** + ** Description Process 'start' request from BTC AV state machine to prepare + ** for A2DP streaming + ** + ** Return TRUE if an ACK for the local command is sent + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_on_started(tBTA_AV_START *p_av, BOOLEAN pending_start); + +/******************************************************************************* + ** + ** Function btc_a2dp_on_stopped + ** + ** Description Process 'stop' request from BTC AV state machine to stop + ** A2DP streaming + ** + *******************************************************************************/ +void btc_a2dp_on_stopped(tBTA_AV_SUSPEND *p_av); + +/******************************************************************************* + ** + ** Function btc_a2dp_on_suspended + ** + ** Description Process 'stop' request from BTC AV state machine to suspend + ** A2DP streaming + ** + *******************************************************************************/ +void btc_a2dp_on_suspended(tBTA_AV_SUSPEND *p_av); + +#endif /* #if BTC_AV_INCLUDED */ + +#endif /* __BTC_A2DP_H__ */ diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_a2dp_control.h b/components/bt/bluedroid/btc/profile/std/include/btc_a2dp_control.h new file mode 100644 index 000000000..e1d5e2ff6 --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/include/btc_a2dp_control.h @@ -0,0 +1,110 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/******************************************************************************* + * + * Filename: btc_a2dp_control.h + * + *******************************************************************************/ + +#ifndef __BTC_A2DP_CONTROL_H__ +#define __BTC_A2DP_CONTROL_H__ + +#include +#include "bt_target.h" +#include "bta_api.h" +#include "btc_av_api.h" +#include "esp_a2dp_api.h" + +#if BTC_AV_INCLUDED +/******************************************************************************* + ** Public functions + *******************************************************************************/ + +/******************************************************************************* + ** + ** Function btc_a2dp_control_media_ctrl + ** + ** Description Handle the media_ctrl command + ** + *******************************************************************************/ +void btc_a2dp_control_media_ctrl(esp_a2d_media_ctrl_t ctrl); + + +/******************************************************************************* + ** + ** Function btc_a2dp_control_datapath_ctrl + ** + ** Description Handle the media datapath event, which is adapted from UIPC + ** data channel from bluedroid + ** + *******************************************************************************/ +void btc_a2dp_control_datapath_ctrl(uint32_t dp_evt); + + +/******************************************************************************* + ** + ** Function btc_a2dp_control_command_ack + ** + ** Description Acknowledge the pending media_ctrl command + ** + *******************************************************************************/ +void btc_a2dp_control_command_ack(int status); + + +/******************************************************************************* + ** + ** Function btc_a2dp_control_get_datachnl_stat + ** + ** Description Check whether the data channel state is open + ** + ** Return TRUE if the data channel state is open + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_control_get_datachnl_stat(void); + + +/******************************************************************************* + ** + ** Function btc_a2dp_control_set_datachnl_stat + ** + ** Description Set the data channel state flag + ** + *******************************************************************************/ +void btc_a2dp_control_set_datachnl_stat(BOOLEAN open); + + +/******************************************************************************* + ** + ** Function btc_a2dp_control_init + ** + ** Description Initialize the A2DP control module. It should be called during + ** the startup stage of A2DP streaming. + ** + *******************************************************************************/ +bool btc_a2dp_control_init(void); + + +/******************************************************************************* + ** + ** Function btc_a2dp_control_cleanup + ** + ** Description Cleanup the A2DP control module + ** + *******************************************************************************/ +void btc_a2dp_control_cleanup(void); + +#endif /* #if BTC_AV_INCLUDED */ + +#endif /* __BTC_A2DP_CONTROL_H__ */ diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_a2dp_sink.h b/components/bt/bluedroid/btc/profile/std/include/btc_a2dp_sink.h new file mode 100644 index 000000000..772ffea6d --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/include/btc_a2dp_sink.h @@ -0,0 +1,139 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/******************************************************************************* + * + * Filename: btc_a2dp_sink.h + * + *******************************************************************************/ + +#ifndef __BTC_A2DP_SINK_H__ +#define __BTC_A2DP_SINK_H__ + +#include +#include "bt_target.h" +#include "bta_api.h" +#include "btc_av_api.h" +#include "esp_a2dp_api.h" + +#if BTC_AV_SINK_INCLUDED +/******************************************************************************* + ** Data types + *******************************************************************************/ +typedef struct { + BT_HDR hdr; + UINT8 codec_info[AVDT_CODEC_SIZE]; +} tBTC_MEDIA_SINK_CFG_UPDATE; + +/******************************************************************************* + ** Public functions + *******************************************************************************/ + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_startup + ** + ** Description Initialize and startup the A2DP sink module. This function + ** should be called by the BTC AV state machine prior to using + ** the module. + ** + ** Returns true if success + ** + *******************************************************************************/ +bool btc_a2dp_sink_startup(void); + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_shutdown + ** + ** Description Shutdown and cleanup the A2DP sink module + ** + *******************************************************************************/ +void btc_a2dp_sink_shutdown(void); + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_rx_flush_req + ** + ** Description Request to flush audio decoding pipe + ** + ** Returns TRUE if success + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_sink_rx_flush_req(void); + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_enque_buf + ** + ** Description Enqueue a Advance Audio media buffer to be processed by btc media task. + ** + ** Returns size of the queue + ** + *******************************************************************************/ +UINT8 btc_a2dp_sink_enque_buf(BT_HDR *p_buf); + + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_on_idle + ** + ** Description Process 'idle' request from the BTC AV state machine during + ** initialization + ** + *******************************************************************************/ +void btc_a2dp_sink_on_idle(void); + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_on_stopped + ** + ** Description Process 'stop' request from the BTC AV state machine to stop + ** A2DP streaming + ** + *******************************************************************************/ +void btc_a2dp_sink_on_stopped(tBTA_AV_SUSPEND *p_av); + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_on_suspended + ** + ** Description Process 'suspend' request from the BTC AV state machine to + ** suspend A2DP streaming + ** + *******************************************************************************/ +void btc_a2dp_sink_on_suspended(tBTA_AV_SUSPEND *p_av); + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_set_rx_flush + ** + ** Description enable/disabel discarding of received A2DP frames + ** + *******************************************************************************/ +void btc_a2dp_sink_set_rx_flush(BOOLEAN enable); + +/******************************************************************************* + ** + ** Function btc_a2dp_sink_reset_decoder + ** + ** Description Reset decoder parameters according to configuration from remote + ** device + ** + *******************************************************************************/ +void btc_a2dp_sink_reset_decoder(UINT8 *p_av); + +#endif /* #if BTC_AV_SINK_INCLUDED */ + +#endif /* __BTC_A2DP_SINK_H__ */ diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_media.h b/components/bt/bluedroid/btc/profile/std/include/btc_a2dp_source.h similarity index 56% rename from components/bt/bluedroid/btc/profile/std/include/btc_media.h rename to components/bt/bluedroid/btc/profile/std/include/btc_a2dp_source.h index 66ad366fb..214c9e723 100644 --- a/components/bt/bluedroid/btc/profile/std/include/btc_media.h +++ b/components/bt/bluedroid/btc/profile/std/include/btc_a2dp_source.h @@ -14,46 +14,24 @@ /******************************************************************************* * - * Filename: btc_media.h - * - * Description: This is the audio module for the BTC system. + * Filename: btc_a2dp_source.h * *******************************************************************************/ -#ifndef __BTC_MEDIA_H__ -#define __BTC_MEDIA_H__ +#ifndef __BTC_A2DP_SOURCE_H__ +#define __BTC_A2DP_SOURCE_H__ #include +#include "bt_target.h" #include "bta_api.h" #include "btc_av_api.h" +#include "esp_a2dp_api.h" -#if (BTA_AV_INCLUDED == TRUE) - -/******************************************************************************* - ** Constants - *******************************************************************************/ -#define BTC_SUCCESS (0) -/** - * AV (Audio Video source) Errors - */ -#define BTC_ERROR_SRV_AV_NOT_ENABLED 700 /* AV is not enabled */ -#define BTC_ERROR_SRV_AV_FEEDING_NOT_SUPPORTED 701 /* Requested Feeding not supported */ -#define BTC_ERROR_SRV_AV_BUSY 702 /* Another operation ongoing */ -#define BTC_ERROR_SRV_AV_NOT_OPENED 703 /* No AV link opened */ -#define BTC_ERROR_SRV_AV_NOT_STARTED 704 /* AV is not started */ -#define BTC_ERROR_SRV_AV_CP_NOT_SUPPORTED 705 /* Content protection is not supported by all headsets */ - -/* Transcoding definition for TxTranscoding and RxTranscoding */ -#define BTC_MEDIA_TRSCD_OFF 0 -#define BTC_MEDIA_TRSCD_PCM_2_SBC 1 /* Tx */ - - +#if BTC_AV_SRC_INCLUDED /******************************************************************************* ** Data types *******************************************************************************/ -typedef int tBTC_STATUS; - /* tBTC_MEDIA_INIT_AUDIO msg structure */ typedef struct { BT_HDR hdr; @@ -65,7 +43,6 @@ typedef struct { UINT16 MtuSize; /* peer mtu size */ } tBTC_MEDIA_INIT_AUDIO; -#if (BTA_AV_INCLUDED == TRUE) /* tBTC_MEDIA_UPDATE_AUDIO msg structure */ typedef struct { BT_HDR hdr; @@ -81,187 +58,187 @@ typedef struct { tBTC_AV_MEDIA_FEEDINGS feeding; } tBTC_MEDIA_INIT_AUDIO_FEEDING; -typedef struct { - BT_HDR hdr; - UINT8 codec_info[AVDT_CODEC_SIZE]; -} tBTC_MEDIA_SINK_CFG_UPDATE; -#endif - /******************************************************************************* ** Public functions *******************************************************************************/ /******************************************************************************* ** - ** Function btc_av_task + ** Function btc_a2dp_source_startup ** - ** Description + ** Description Initialize and startup the A2DP source module. This function + ** should be called by the BTC AV state machine prior to using + ** the module ** - ** Returns void + ** Returns TRUE is success ** *******************************************************************************/ -extern void btc_media_task(void); +bool btc_a2dp_source_startup(void); /******************************************************************************* ** - ** Function btc_media_task_enc_init_req + ** Function btc_a2dp_source_shutdown + ** + ** Description Shutdown and cleanup the A2DP source module. + ** + *******************************************************************************/ +void btc_a2dp_source_shutdown(void); + +/******************************************************************************* + ** + ** Function btc_a2dp_source_enc_init_req ** ** Description Request to initialize the media task encoder ** ** Returns TRUE is success ** *******************************************************************************/ -extern BOOLEAN btc_media_task_enc_init_req(tBTC_MEDIA_INIT_AUDIO *p_msg); +BOOLEAN btc_a2dp_source_enc_init_req(tBTC_MEDIA_INIT_AUDIO *p_msg); /******************************************************************************* ** - ** Function btc_media_task_enc_update_req + ** Function btc_a2dp_source_enc_udpate_req ** ** Description Request to update the media task encoder ** ** Returns TRUE is success ** *******************************************************************************/ -#if (BTA_AV_INCLUDED == TRUE) -extern BOOLEAN btc_media_task_enc_update_req(tBTC_MEDIA_UPDATE_AUDIO *p_msg); -#endif +BOOLEAN btc_a2dp_source_enc_update_req(tBTC_MEDIA_UPDATE_AUDIO *p_msg); + /******************************************************************************* ** - ** Function btc_media_task_start_aa_req + ** Function btc_a2dp_source_start_audio_req ** ** Description Request to start audio encoding task ** ** Returns TRUE is success ** *******************************************************************************/ -extern BOOLEAN btc_media_task_start_aa_req(void); +BOOLEAN btc_a2dp_source_start_audio_req(void); /******************************************************************************* ** - ** Function btc_media_task_stop_aa_req + ** Function btc_a2dp_source_stop_audio_req ** ** Description Request to stop audio encoding task ** ** Returns TRUE is success ** *******************************************************************************/ -extern BOOLEAN btc_media_task_stop_aa_req(void); +BOOLEAN btc_a2dp_source_stop_audio_req(void); /******************************************************************************* ** - ** Function btc_media_task_aa_rx_flush_req - ** - ** Description Request to flush audio decoding pipe - ** - ** Returns TRUE is success - ** - *******************************************************************************/ -extern BOOLEAN btc_media_task_aa_rx_flush_req(void); -/******************************************************************************* - ** - ** Function btc_media_task_aa_tx_flush_req + ** Function btc_a2dp_source_tx_flush_req ** ** Description Request to flush audio encoding pipe ** ** Returns TRUE is success ** *******************************************************************************/ -extern BOOLEAN btc_media_task_aa_tx_flush_req(void); +BOOLEAN btc_a2dp_source_tx_flush_req(void); /******************************************************************************* ** - ** Function btc_media_aa_readbuf + ** Function btc_a2dp_source_audio_readbuf ** ** Description Read an audio buffer from the BTC media TX queue ** ** Returns pointer on a aa buffer ready to send ** *******************************************************************************/ -extern BT_HDR *btc_media_aa_readbuf(void); +BT_HDR *btc_a2dp_source_audio_readbuf(void); /******************************************************************************* ** - ** Function btc_media_sink_enque_buf - ** - ** Description This function is called by the av_co to fill A2DP Sink Queue - ** - ** - ** Returns size of the queue - *******************************************************************************/ -UINT8 btc_media_sink_enque_buf(BT_HDR *p_buf); - - - -/******************************************************************************* - ** - ** Function btc_media_aa_writebuf - ** - ** Description Enqueue a Advance Audio media buffer to be processed by btc media task. - ** - ** Returns TRUE is success - ** - *******************************************************************************/ -extern void btc_media_aa_writebuf(BT_HDR *pBuf, UINT32 timestamp, UINT16 seq_num); - -/******************************************************************************* - ** - ** Function btc_media_av_writebuf - ** - ** Description Enqueue a video media buffer to be processed by btc media task. - ** - ** Returns TRUE is success - ** - *******************************************************************************/ -extern BOOLEAN btc_media_av_writebuf(UINT8 *p_media, UINT32 media_len, - UINT32 timestamp, UINT16 seq_num); - -#if (BTA_AV_INCLUDED == TRUE) -/******************************************************************************* - ** - ** Function btc_media_task_audio_feeding_init_req + ** Function btc_a2dp_source_audio_feeding_init_req ** ** Description Request to initialize audio feeding ** - ** Returns TRUE is success + ** Returns TRUE if success ** *******************************************************************************/ -extern BOOLEAN btc_media_task_audio_feeding_init_req(tBTC_MEDIA_INIT_AUDIO_FEEDING *p_msg); -#endif +BOOLEAN btc_a2dp_source_audio_feeding_init_req(tBTC_MEDIA_INIT_AUDIO_FEEDING *p_msg); /******************************************************************************* ** - ** Function dump_codec_info + ** Function btc_a2dp_source_is_streaming ** - ** Description Decode and display codec_info (for debug) - ** - ** Returns void + ** Description Check whether A2DP source is in streaming state ** *******************************************************************************/ -extern void dump_codec_info(unsigned char *p_codec); +bool btc_a2dp_source_is_streaming(void); -/** - * Local adaptation helper functions between btc and media task - */ +/******************************************************************************* + ** + ** Function btc_a2dp_source_is_task_shutting_down + ** + ** Description Check whether A2DP source media task is shutting down + ** + *******************************************************************************/ +bool btc_a2dp_source_is_task_shutting_down(void); -bool btc_a2dp_start_media_task(void); -void btc_a2dp_stop_media_task(void); -void btc_a2dp_on_init(void); -void btc_a2dp_setup_codec(void); -void btc_a2dp_on_idle(void); -BOOLEAN btc_a2dp_on_started(tBTA_AV_START *p_av, BOOLEAN pending_start); -void btc_a2dp_on_stop_req(void); -void btc_a2dp_on_stopped(tBTA_AV_SUSPEND *p_av); -void btc_a2dp_on_suspend(void); -void btc_a2dp_on_suspended(tBTA_AV_SUSPEND *p_av); -void btc_a2dp_set_rx_flush(BOOLEAN enable); -void btc_media_check_iop_exceptions(UINT8 *peer_bda); -void btc_reset_decoder(UINT8 *p_av); +/******************************************************************************* + ** + ** Function btc_a2dp_source_on_idle + ** + ** Description Request 'idle' request from BTC AV state machine during + ** initialization + ** + *******************************************************************************/ +void btc_a2dp_source_on_idle(void); -int btc_a2dp_get_track_frequency(UINT8 frequency); -int btc_a2dp_get_track_channel_count(UINT8 channeltype); -void btc_a2dp_set_peer_sep(UINT8 sep); -#endif ///BTA_AV_INCLUDED == TRUE -#endif +/******************************************************************************* + ** + ** Function btc_a2dp_source_on_stopped + ** + ** Description Process 'stop' request from the BTC AV state machine to stop + ** A2DP streaming + ** + *******************************************************************************/ +void btc_a2dp_source_on_stopped(tBTA_AV_SUSPEND *p_av); + +/******************************************************************************* + ** + ** Function btc_a2dp_source_on_suspended + ** + ** Description Process 'suspend' request from the BTC AV state machine to stop + ** A2DP streaming + ** + *******************************************************************************/ +void btc_a2dp_source_on_suspended(tBTA_AV_SUSPEND *p_av); + +/******************************************************************************* + ** + ** Function btc_a2dp_source_setup_codec + ** + ** Description initialize the encoder parameters + ** + *******************************************************************************/ +void btc_a2dp_source_setup_codec(void); + +/******************************************************************************* + ** + ** Function btc_a2dp_source_set_tx_flush + ** + ** Description enable/disable discarding of transmitted frames + ** + *******************************************************************************/ +void btc_a2dp_source_set_tx_flush(BOOLEAN enable); + +/******************************************************************************* + ** + ** Function btc_a2dp_source_encoder_update + ** + ** Description update changed SBC encoder parameters + ** + *******************************************************************************/ +void btc_a2dp_source_encoder_update(void); + +#endif /* #if BTC_AV_SRC_INCLUDED */ + +#endif /* __BTC_A2DP_SOURCE_H__ */ diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_av.h b/components/bt/bluedroid/btc/profile/std/include/btc_av.h index 8a8664cd2..312958b0c 100644 --- a/components/bt/bluedroid/btc/profile/std/include/btc_av.h +++ b/components/bt/bluedroid/btc/profile/std/include/btc_av.h @@ -25,17 +25,23 @@ #ifndef __BTC_AV_H__ #define __BTC_AV_H__ +#include "bt_target.h" #include "esp_a2dp_api.h" #include "btc_task.h" #include "btc_common.h" #include "btc_sm.h" #include "bta_av_api.h" -#if (BTA_AV_INCLUDED == TRUE) +#if (BTC_AV_INCLUDED == TRUE) /******************************************************************************* ** Type definitions for callback functions ********************************************************************************/ +enum { + BTC_AV_DATAPATH_OPEN_EVT, // original UIPC_OPEN_EVT for data channel in bluedroid + BTC_AV_DATAPATH_MAX_EVT, +}; + typedef enum { BTC_AV_CONNECT_REQ_EVT = BTA_AV_MAX_EVT, BTC_AV_DISCONNECT_REQ_EVT, @@ -46,21 +52,44 @@ typedef enum { } btc_av_sm_event_t; typedef enum { +#if BTC_AV_SINK_INCLUDED BTC_AV_SINK_API_INIT_EVT = 0, BTC_AV_SINK_API_DEINIT_EVT, BTC_AV_SINK_API_CONNECT_EVT, BTC_AV_SINK_API_DISCONNECT_EVT, BTC_AV_SINK_API_REG_DATA_CB_EVT, +#endif /* BTC_AV_SINK_INCLUDED */ +#if BTC_AV_SRC_INCLUDED + BTC_AV_SRC_API_INIT_EVT, + BTC_AV_SRC_API_DEINIT_EVT, + BTC_AV_SRC_API_CONNECT_EVT, + BTC_AV_SRC_API_DISCONNECT_EVT, + BTC_AV_SRC_API_REG_DATA_CB_EVT, +#endif /* BTC_AV_SRC_INCLUDED */ + BTC_AV_API_MEDIA_CTRL_EVT, + BTC_AV_DATAPATH_CTRL_EVT, } btc_av_act_t; /* btc_av_args_t */ typedef union { +#if BTC_AV_SINK_INCLUDED // BTC_AV_SINK_CONFIG_REQ_EVT -- internal event esp_a2d_mcc_t mcc; // BTC_AV_SINK_API_CONNECT_EVT bt_bdaddr_t connect; // BTC_AV_SINK_API_REG_DATA_CB_EVT - esp_a2d_data_cb_t data_cb; + esp_a2d_sink_data_cb_t data_cb; +#endif /* BTC_AV_SINK_INCLUDED */ +#if BTC_AV_SRC_INCLUDED + // BTC_AV_SRC_API_REG_DATA_CB_EVT + esp_a2d_source_data_cb_t src_data_cb; + // BTC_AV_SRC_API_CONNECT + bt_bdaddr_t src_connect; +#endif /* BTC_AV_SRC_INCLUDED */ + // BTC_AV_API_MEDIA_CTRL_EVT + esp_a2d_media_ctrl_t ctrl; + // BTC_AV_DATAPATH_CTRL_EVT + uint32_t dp_evt; } btc_av_args_t; /******************************************************************************* @@ -71,7 +100,9 @@ void btc_a2dp_call_handler(btc_msg_t *msg); void btc_a2dp_cb_handler(btc_msg_t *msg); -void btc_a2dp_sink_reg_data_cb(esp_a2d_data_cb_t callback); +void btc_a2dp_sink_reg_data_cb(esp_a2d_sink_data_cb_t callback); + +void btc_a2dp_src_reg_data_cb(esp_a2d_source_data_cb_t callback); /******************************************************************************* ** ** Function btc_av_get_sm_handle @@ -121,18 +152,6 @@ BOOLEAN btc_av_stream_started_ready(void); /* used to pass events to AV statemachine from other tasks */ void btc_dispatch_sm_event(btc_av_sm_event_t event, void *p_data, int len); -/******************************************************************************* -** -** Function btc_av_init -** -** Description Initializes btc AV if not already done -** -** Returns bt_status_t -** -*******************************************************************************/ - -bt_status_t btc_av_init(void); - /******************************************************************************* ** ** Function btc_av_is_connected @@ -146,6 +165,19 @@ bt_status_t btc_av_init(void); BOOLEAN btc_av_is_connected(void); +/******************************************************************************* + * + * Function btc_av_get_peer_sep + * + * Description Get the stream endpoint type. + * + * Returns The stream endpoint type: either AVDT_TSEP_SRC or + * AVDT_TSEP_SNK. + * + ******************************************************************************/ + +uint8_t btc_av_get_peer_sep(void); + /******************************************************************************* ** ** Function btc_av_is_peer_edr @@ -171,6 +203,6 @@ BOOLEAN btc_av_is_peer_edr(void); ********************************************************************************/ void btc_av_clear_remote_suspend_flag(void); -#endif ///BTA_AV_INCLUDED == TRUE +#endif ///BTC_AV_INCLUDED == TRUE #endif /* __BTC_AV_H__ */ diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_av_api.h b/components/bt/bluedroid/btc/profile/std/include/btc_av_api.h index c7672a075..7d5f376d6 100644 --- a/components/bt/bluedroid/btc/profile/std/include/btc_av_api.h +++ b/components/bt/bluedroid/btc/profile/std/include/btc_av_api.h @@ -27,8 +27,6 @@ #include "bt_target.h" #include "bta_av_api.h" - -#include "btc_media.h" #include "a2d_api.h" #include "a2d_sbc.h" diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_gap_ble.h b/components/bt/bluedroid/btc/profile/std/include/btc_gap_ble.h index 8074c1b9b..ba744702b 100644 --- a/components/bt/bluedroid/btc/profile/std/include/btc_gap_ble.h +++ b/components/bt/bluedroid/btc/profile/std/include/btc_gap_ble.h @@ -160,5 +160,6 @@ void btc_gap_ble_arg_deep_free(btc_msg_t *msg); void btc_gap_ble_cb_deep_free(btc_msg_t *msg); void btc_gap_ble_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); void btc_gap_callback_init(void); +void btc_gap_ble_deinit(void); #endif /* __BTC_GAP_BLE_H__ */ diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_gatt_common.h b/components/bt/bluedroid/btc/profile/std/include/btc_gatt_common.h new file mode 100644 index 000000000..41b0fee54 --- /dev/null +++ b/components/bt/bluedroid/btc/profile/std/include/btc_gatt_common.h @@ -0,0 +1,37 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __BTC_GATT_COMMON_H__ +#define __BTC_GATT_COMMON_H__ + +#include "future.h" +#include "bt_types.h" +#include "bta_api.h" +#include "btc_main.h" +#include "btc_task.h" + +typedef enum { + BTC_GATT_ACT_SET_LOCAL_MTU = 0, +} btc_gatt_com_act_t; + +/* btc_ble_gattc_args_t */ +typedef union { + //BTC_GATT_ACT_SET_LOCAL_MTU, + struct set_mtu_arg { + uint16_t mtu; + } set_mtu; +} btc_ble_gatt_com_args_t; + +void btc_gatt_com_call_handler(btc_msg_t *msg); +#endif /* __BTC_GATT_COMMON_H__ */ diff --git a/components/bt/bluedroid/btif/bta_dm_co.c b/components/bt/bluedroid/btif/bta_dm_co.c index 83e6db1fe..f71ebfc44 100644 --- a/components/bt/bluedroid/btif/bta_dm_co.c +++ b/components/bt/bluedroid/btif/bta_dm_co.c @@ -24,7 +24,7 @@ #include "bta_dm_ci.h" #include "btc_dm.h" #if (defined(BTIF_INCLUDED) && BTIF_INCLUDED == TRUE) -#include "bt_utils.h" +#include "bt_defs.h" #if (BTM_OOB_INCLUDED == TRUE) #include "btif_dm.h" #endif diff --git a/components/bt/bluedroid/external/sbc/encoder/srce/sbc_analysis.c b/components/bt/bluedroid/external/sbc/encoder/srce/sbc_analysis.c index adc0eeda7..2de5a81aa 100644 --- a/components/bt/bluedroid/external/sbc/encoder/srce/sbc_analysis.c +++ b/components/bt/bluedroid/external/sbc/encoder/srce/sbc_analysis.c @@ -27,6 +27,7 @@ #include "sbc_encoder.h" #include "sbc_enc_func_declare.h" /*#include */ +#if (defined(SBC_ENC_INCLUDED) && SBC_ENC_INCLUDED == TRUE) #if (SBC_IS_64_MULT_IN_WINDOW_ACCU == TRUE) #define WIND_4_SUBBANDS_0_1 (SINT32)0x01659F45 /* gas32CoeffFor4SBs[8] = -gas32CoeffFor4SBs[32] = 0x01659F45 */ diff --git a/components/bt/bluedroid/include/bt_target.h b/components/bt/bluedroid/include/bt_target.h index 2393f9244..5be76cc0a 100644 --- a/components/bt/bluedroid/include/bt_target.h +++ b/components/bt/bluedroid/include/bt_target.h @@ -39,81 +39,57 @@ #include "dyn_mem.h" /* defines static and/or dynamic memory for components */ +/****************************************************************************** +** +** Classic BT features +** +******************************************************************************/ #if CONFIG_CLASSIC_BT_ENABLED #define CLASSIC_BT_INCLUDED TRUE +#define BTC_SM_INCLUDED TRUE +#define BTC_PRF_QUEUE_INCLUDED TRUE +#define BTC_GAP_BT_INCLUDED TRUE #define BTA_SDP_INCLUDED TRUE -#define BTA_PAN_INCLUDED FALSE -#define BTA_HH_INCLUDED FALSE #define SDP_INCLUDED TRUE -#if CONFIG_A2DP_SNK_ENABLED +#if CONFIG_A2DP_ENABLE #define BTA_AR_INCLUDED TRUE #define BTA_AV_INCLUDED TRUE -#define BTA_AV_SINK_INCLUDED TRUE #define AVDT_INCLUDED TRUE #define A2D_INCLUDED TRUE #define AVCT_INCLUDED TRUE #define AVRC_INCLUDED TRUE #define BTC_AV_INCLUDED TRUE +#endif /* CONFIG_A2DP_ENABLE */ + +#if CONFIG_A2DP_SINK_ENABLE +#define BTA_AV_SINK_INCLUDED TRUE +#define BTC_AV_SINK_INCLUDED TRUE #define SBC_DEC_INCLUDED TRUE -#else -#define BTA_AR_INCLUDED FALSE -#define BTA_AV_INCLUDED FALSE -#define BTA_AV_SINK_INCLUDED FALSE -#define AVDT_INCLUDED FALSE -#define A2D_INCLUDED FALSE -#define AVCT_INCLUDED FALSE -#define AVRC_INCLUDED FALSE -#define BTC_AV_INCLUDED FALSE -#define SBC_DEC_INCLUDED FALSE -#endif /* CONFIG_A2DP_SNK_ENABLED */ +#endif /* CONFIG_A2DP_SINK_ENABLE */ + +#if CONFIG_A2DP_SRC_ENABLE +#define BTC_AV_SRC_INCLUDED TRUE +#define SBC_ENC_INCLUDED TRUE +#endif /* CONFIG_A2DP_SRC_ENABLE */ #if CONFIG_BT_SPP_ENABLED #define RFCOMM_INCLUDED TRUE #define BTA_JV_INCLUDED TRUE #define BTC_SPP_INCLUDED TRUE -#else /* #if CONFIG_BT_SPP_ENABLED */ -#define RFCOMM_INCLUDED FALSE -#define BTA_JV_INCLUDED FALSE -#define BTC_SPP_INCLUDED FALSE -#endif /* #if CONFIG_BT_SPP_ENABLED */ - -#define PAN_INCLUDED FALSE -#define HID_HOST_INCLUDED FALSE -#define SBC_ENC_INCLUDED FALSE -#define MCA_INCLUDED FALSE -#define BTC_SM_INCLUDED TRUE -#define BTC_PRF_QUEUE_INCLUDED TRUE -#define BTC_GAP_BT_INCLUDED TRUE - -#else /* #if CONFIG_CLASSIC_BT_ENABLED */ -#define CLASSIC_BT_INCLUDED FALSE -#define BTA_SDP_INCLUDED FALSE -#define BTA_PAN_INCLUDED FALSE -#define BTA_HH_INCLUDED FALSE -#define BTA_AR_INCLUDED FALSE -#define BTA_AV_INCLUDED FALSE -#define BTA_AV_SINK_INCLUDED FALSE -#define SDP_INCLUDED FALSE -#define RFCOMM_INCLUDED FALSE -#define BTA_JV_INCLUDED FALSE -#define BTC_SPP_INCLUDED FALSE -#define PAN_INCLUDED FALSE -#define HID_HOST_INCLUDED FALSE -#define AVDT_INCLUDED FALSE -#define A2D_INCLUDED FALSE -#define AVCT_INCLUDED FALSE -#define AVRC_INCLUDED FALSE -#define SBC_DEC_INCLUDED FALSE -#define SBC_ENC_INCLUDED FALSE -#define MCA_INCLUDED FALSE -#define BTC_SM_INCLUDED FALSE -#define BTC_PRF_QUEUE_INCLUDED FALSE -#define BTC_GAP_BT_INCLUDED FALSE -#define BTC_AV_INCLUDED FALSE +#endif /* CONFIG_BT_SPP_ENABLED */ #endif /* #if CONFIG_CLASSIC_BT_ENABLED */ +#ifndef CLASSIC_BT_INCLUDED +#define CLASSIC_BT_INCLUDED FALSE +#endif /* CLASSIC_BT_INCLUDED */ + +/****************************************************************************** +** +** BLE features +** +******************************************************************************/ #if (CONFIG_GATTS_ENABLE) #define GATTS_INCLUDED TRUE #else @@ -164,16 +140,62 @@ #define BTIF_INCLUDED FALSE #endif +/****************************************************************************** +** +** BTC-layer components +** +******************************************************************************/ +#ifndef BTC_GAP_BT_INCLUDED +#define BTC_GAP_BT_INCLUDED FALSE +#endif + +#ifndef BTC_PRF_QUEUE_INCLUDED +#define BTC_PRF_QUEUE_INCLUDED FALSE +#endif + +#ifndef BTC_SM_INCLUDED +#define BTC_SM_INCLUDED FALSE +#endif + +#ifndef BTC_AV_INCLUDED +#define BTC_AV_INCLUDED FALSE +#endif + +#ifndef BTC_AV_SINK_INCLUDED +#define BTC_AV_SINK_INCLUDED FALSE +#endif + +#ifndef BTC_AV_SRC_INCLUDED +#define BTC_AV_SRC_INCLUDED FALSE +#endif + +#ifndef BTC_SPP_INCLUDED +#define BTC_SPP_INCLUDED FALSE +#endif + +#ifndef SBC_DEC_INCLUDED +#define SBC_DEC_INCLUDED FALSE +#endif + +#ifndef SBC_ENC_INCLUDED +#define SBC_ENC_INCLUDED FALSE +#endif + +/****************************************************************************** +** +** BTA-layer components +** +******************************************************************************/ #ifndef BTA_INCLUDED #define BTA_INCLUDED TRUE #endif #ifndef BTA_PAN_INCLUDED -#define BTA_PAN_INCLUDED FALSE//TRUE +#define BTA_PAN_INCLUDED FALSE #endif #ifndef BTA_HH_INCLUDED -#define BTA_HH_INCLUDED FALSE//TRUE +#define BTA_HH_INCLUDED FALSE #endif #ifndef BTA_HH_ROLE @@ -181,21 +203,47 @@ #endif #ifndef BTA_HH_LE_INCLUDED -#define BTA_HH_LE_INCLUDED FALSE//TRUE +#define BTA_HH_LE_INCLUDED FALSE #endif #ifndef BTA_AR_INCLUDED -#define BTA_AR_INCLUDED TRUE//TRUE +#define BTA_AR_INCLUDED FALSE #endif #ifndef BTA_AV_INCLUDED -#define BTA_AV_INCLUDED TRUE//TRUE +#define BTA_AV_INCLUDED FALSE #endif #ifndef BTA_AV_SINK_INCLUDED -#define BTA_AV_SINK_INCLUDED TRUE//FALSE +#define BTA_AV_SINK_INCLUDED FALSE #endif +#ifndef BTA_JV_INCLUDED +#define BTA_JV_INCLUDED FALSE +#endif + +#ifndef BTA_SDP_INCLUDED +#define BTA_SDP_INCLUDED FALSE +#endif + +/****************************************************************************** +** +** Stack-layer components +** +******************************************************************************/ +#ifndef AVCT_INCLUDED +#define AVCT_INCLUDED FALSE +#endif + +#ifndef AVDT_INCLUDED +#define AVDT_INCLUDED FALSE +#endif + +/****************************************************************************** +** +** Parameter Configurations for components +** +******************************************************************************/ #ifndef BTA_DISABLE_DELAY #define BTA_DISABLE_DELAY 200 /* in milliseconds */ #endif @@ -1071,7 +1119,7 @@ ******************************************************************************/ #ifndef SDP_INCLUDED -#define SDP_INCLUDED FALSE //TRUE +#define SDP_INCLUDED FALSE #endif /* This is set to enable SDP server functionality. */ @@ -1458,7 +1506,7 @@ Range: 2 octets ******************************************************************************/ #ifndef PAN_INCLUDED -#define PAN_INCLUDED FALSE//TRUE +#define PAN_INCLUDED FALSE #endif /* This will enable the PANU role */ @@ -1603,7 +1651,7 @@ Range: 2 octets ** Definitions for HID-Host */ #ifndef HID_HOST_INCLUDED -#define HID_HOST_INCLUDED FALSE//TRUE +#define HID_HOST_INCLUDED FALSE #endif #ifndef HID_HOST_MAX_DEVICES @@ -1630,7 +1678,7 @@ Range: 2 octets * A2DP Definitions */ #ifndef A2D_INCLUDED -#define A2D_INCLUDED FALSE//TRUE +#define A2D_INCLUDED FALSE #endif /****************************************************************************** @@ -1655,7 +1703,7 @@ Range: 2 octets ** ******************************************************************************/ #ifndef AVRC_INCLUDED -#define AVRC_INCLUDED TRUE +#define AVRC_INCLUDED FALSE #endif #ifndef AVRC_METADATA_INCLUDED diff --git a/components/bt/bluedroid/main/bte_init.c b/components/bt/bluedroid/main/bte_init.c index 9996129bc..6d317fd4e 100644 --- a/components/bt/bluedroid/main/bte_init.c +++ b/components/bt/bluedroid/main/bte_init.c @@ -26,6 +26,7 @@ #include "bt_target.h" #include + /* Stack Configuation Related Init Definaton * TODO: Now Just Unmask these defination until stack layer is OK */ @@ -59,6 +60,14 @@ #include "a2d_api.h" #endif +#if (defined(AVDT_INCLUDED) && AVDT_INCLUDED == TRUE) +#include "avdt_int.h" +#endif + +#if (defined(AVCT_INCLUDED) && AVCT_INCLUDED == TRUE) +#include "avct_int.h" +#endif + #if (defined(HID_HOST_INCLUDED) && HID_HOST_INCLUDED == TRUE) #include "hidh_api.h" #endif @@ -78,8 +87,13 @@ #if BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE #include "bta_api.h" #include "bta_sys.h" +#include "allocator.h" -#include "bta_ag_int.h" +//#include "bta_ag_int.h" + +#if BTA_SDP_INCLUDED == TRUE +#include "bta_sdp_int.h" +#endif #if BTA_HS_INCLUDED == TRUE #include "bta_hs_int.h" @@ -119,9 +133,9 @@ tBTA_JV_CB *bta_jv_cb_ptr = NULL; #include "bta_sys_int.h" // control block for patch ram downloading -#include "bta_prm_int.h" +//#include "bta_prm_int.h" -#endif // BTA_INCLUDED +#endif // BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE /***************************************************************************** @@ -169,6 +183,20 @@ void BTE_InitStack(void) AVRC_Init(); #endif +#if (defined(AVDT_INCLUDED) && AVDT_INCLUDED == TRUE && AVDT_DYNAMIC_MEMORY == TRUE) + if ((avdt_cb_ptr = (tAVDT_CB *)osi_malloc(sizeof(tAVDT_CB))) == NULL) { + return; + } + memset((void *)avdt_cb_ptr, 0, sizeof(tAVDT_CB)); +#endif + +#if (defined(AVCT_INCLUDED) && AVCT_INCLUDED == TRUE && AVCT_DYNAMIC_MEMORY == TRUE) + if ((avct_cb_ptr = (tAVCT_CB *)osi_malloc(sizeof(tAVCT_CB))) == NULL) { + return; + } + memset((void *)avct_cb_ptr, 0, sizeof(tAVCT_CB)); +#endif + #if (defined(GAP_INCLUDED) && GAP_INCLUDED == TRUE) GAP_Init(); #endif @@ -183,29 +211,66 @@ void BTE_InitStack(void) //BTA Modules #if (BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE) + if ((bta_sys_cb_ptr = (tBTA_SYS_CB *)osi_malloc(sizeof(tBTA_SYS_CB))) == NULL) { + return; + } + if ((bta_dm_cb_ptr = (tBTA_DM_CB *)osi_malloc(sizeof(tBTA_DM_CB))) == NULL) { + return; + } + if ((bta_dm_search_cb_ptr = (tBTA_DM_SEARCH_CB *)osi_malloc(sizeof(tBTA_DM_SEARCH_CB))) == NULL) { + return; + } + if ((bta_dm_di_cb_ptr = (tBTA_DM_DI_CB *)osi_malloc(sizeof(tBTA_DM_DI_CB))) == NULL) { + return; + } memset((void *)bta_sys_cb_ptr, 0, sizeof(tBTA_SYS_CB)); memset((void *)bta_dm_cb_ptr, 0, sizeof(tBTA_DM_CB)); memset((void *)bta_dm_search_cb_ptr, 0, sizeof(tBTA_DM_SEARCH_CB)); memset((void *)bta_dm_di_cb_ptr, 0, sizeof(tBTA_DM_DI_CB)); - memset((void *)bta_prm_cb_ptr, 0, sizeof(tBTA_PRM_CB)); - memset((void *)bta_ag_cb_ptr, 0, sizeof(tBTA_AG_CB)); + //memset((void *)bta_prm_cb_ptr, 0, sizeof(tBTA_PRM_CB)); + //memset((void *)bta_ag_cb_ptr, 0, sizeof(tBTA_AG_CB)); #if BTA_HS_INCLUDED == TRUE memset((void *)bta_hs_cb_ptr, 0, sizeof(tBTA_HS_CB)); #endif +#if BTA_SDP_INCLUDED == TRUE + if ((bta_sdp_cb_ptr = (tBTA_SDP_CB *)osi_malloc(sizeof(tBTA_SDP_CB))) == NULL) { + return; + } + memset((void *)bta_sdp_cb_ptr, 0, sizeof(tBTA_SDP_CB)); +#endif #if BTA_AR_INCLUDED==TRUE + if ((bta_ar_cb_ptr = (tBTA_AR_CB *)osi_malloc(sizeof(tBTA_AR_CB))) == NULL) { + return; + } memset((void *)bta_ar_cb_ptr, 0, sizeof(tBTA_AR_CB)); #endif #if BTA_AV_INCLUDED==TRUE + if ((bta_av_cb_ptr = (tBTA_AV_CB *)osi_malloc(sizeof(tBTA_AV_CB))) == NULL) { + return; + } memset((void *)bta_av_cb_ptr, 0, sizeof(tBTA_AV_CB)); #endif #if BTA_HH_INCLUDED==TRUE + if ((bta_hh_cb_ptr = (tBTA_HH_CB *)osi_malloc(sizeof(tBTA_HH_CB))) == NULL) { + return; + } memset((void *)bta_hh_cb_ptr, 0, sizeof(tBTA_HH_CB)); #endif #if BTA_HL_INCLUDED==TRUE memset((void *)bta_hl_cb_ptr, 0, sizeof(tBTA_HL_CB)); #endif -#if BTA_GATT_INCLUDED==TRUE +#if GATTC_INCLUDED==TRUE + if ((bta_gattc_cb_ptr = (tBTA_GATTC_CB *)osi_malloc(sizeof(tBTA_GATTC_CB))) == NULL) { + return; + } memset((void *)bta_gattc_cb_ptr, 0, sizeof(tBTA_GATTC_CB)); +#endif +#if GATTS_INCLUDED == TRUE + if ((bta_gatts_cb_ptr = (tBTA_GATTS_CB *)osi_malloc(sizeof(tBTA_GATTS_CB))) == NULL) { + return; + } + memset((void *)bta_gattc_cb_ptr, 0, sizeof(tBTA_GATTC_CB)); + // memset((void *)bta_gatts_cb_ptr, 0, sizeof(tBTA_GATTS_CB)); #endif #if BTA_PAN_INCLUDED==TRUE diff --git a/components/bt/bluedroid/osi/alarm.c b/components/bt/bluedroid/osi/alarm.c index 034ff902c..bdc58ec44 100644 --- a/components/bt/bluedroid/osi/alarm.c +++ b/components/bt/bluedroid/osi/alarm.c @@ -47,6 +47,7 @@ static int alarm_state; static struct alarm_t alarm_cbs[ALARM_CBS_NUM]; static osi_alarm_err_t alarm_free(osi_alarm_t *alarm); +static osi_alarm_err_t alarm_set(osi_alarm_t *alarm, period_ms_t timeout, bool is_periodic); int osi_alarm_create_mux(void) { @@ -207,7 +208,7 @@ end: return; } -osi_alarm_err_t osi_alarm_set(osi_alarm_t *alarm, period_ms_t timeout) +static osi_alarm_err_t alarm_set(osi_alarm_t *alarm, period_ms_t timeout, bool is_periodic) { assert(alarm_mutex != NULL); @@ -226,19 +227,34 @@ osi_alarm_err_t osi_alarm_set(osi_alarm_t *alarm, period_ms_t timeout) } int64_t timeout_us = 1000 * (int64_t)timeout; - esp_err_t stat = esp_timer_start_once(alarm->alarm_hdl, (uint64_t)timeout_us); + esp_err_t stat; + if (is_periodic) { + stat = esp_timer_start_periodic(alarm->alarm_hdl, (uint64_t)timeout_us); + } else { + stat = esp_timer_start_once(alarm->alarm_hdl, (uint64_t)timeout_us); + } if (stat != ESP_OK) { LOG_ERROR("%s failed to start timer, err 0x%x\n", __func__, stat); ret = OSI_ALARM_ERR_FAIL; goto end; } - alarm->deadline_us = timeout_us + esp_timer_get_time(); + alarm->deadline_us = is_periodic ? 0 : (timeout_us + esp_timer_get_time()); end: osi_mutex_unlock(&alarm_mutex); return ret; } +osi_alarm_err_t osi_alarm_set(osi_alarm_t *alarm, period_ms_t timeout) +{ + return alarm_set(alarm, timeout, false); +} + +osi_alarm_err_t osi_alarm_set_periodic(osi_alarm_t *alarm, period_ms_t period) +{ + return alarm_set(alarm, period, true); +} + osi_alarm_err_t osi_alarm_cancel(osi_alarm_t *alarm) { int ret = OSI_ALARM_ERR_PASS; diff --git a/components/bt/bluedroid/osi/allocator.c b/components/bt/bluedroid/osi/allocator.c index b96c28841..a6c2b8ea3 100644 --- a/components/bt/bluedroid/osi/allocator.c +++ b/components/bt/bluedroid/osi/allocator.c @@ -132,26 +132,40 @@ void *osi_malloc_func(size_t size) { #ifdef CONFIG_BLUEDROID_MEM_DEBUG void *p; - +#if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST + p = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); +#else p = malloc(size); +#endif /* #if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST */ osi_mem_dbg_record(p, size, __func__, __LINE__); return p; +#else +#if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST + return heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); #else return malloc(size); -#endif +#endif /* #if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST */ +#endif /* #ifdef CONFIG_BLUEDROID_MEM_DEBUG */ } void *osi_calloc_func(size_t size) { #ifdef CONFIG_BLUEDROID_MEM_DEBUG void *p; - +#if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST + p = heap_caps_calloc_prefer(1, size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); +#else p = calloc(1, size); +#endif /* #if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST */ osi_mem_dbg_record(p, size, __func__, __LINE__); return p; +#else +#if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST + return heap_caps_calloc_prefer(1, size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); #else return calloc(1, size); -#endif +#endif /* #if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST */ +#endif /* #ifdef CONFIG_BLUEDROID_MEM_DEBUG */ } void osi_free_func(void *ptr) diff --git a/components/bt/bluedroid/osi/config.c b/components/bt/bluedroid/osi/config.c index f6f9a6587..44bdacfa0 100644 --- a/components/bt/bluedroid/osi/config.c +++ b/components/bt/bluedroid/osi/config.c @@ -28,8 +28,9 @@ #include "list.h" #include "bt_trace.h" -#define CONFIG_FILE_MAX_SIZE (2048) -#define CONFIG_KEY "bt_cfg_key" +#define CONFIG_FILE_MAX_SIZE (1536)//1.5k +#define CONFIG_FILE_DEFAULE_LENGTH (2048) +#define CONFIG_KEY "bt_cfg_key" typedef struct { char *key; char *value; @@ -134,7 +135,7 @@ bool config_has_key(const config_t *config, const char *section, const char *key } bool config_has_key_in_section(config_t *config, char *key, char *key_value) -{ +{ LOG_DEBUG("key = %s, value = %s", key, key_value); for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) { const section_t *section = (const section_t *)list_node(node); @@ -302,6 +303,78 @@ const char *config_section_name(const config_section_node_t *node) return section->name; } +static int get_config_size(const config_t *config) +{ + assert(config != NULL); + + int w_len = 0, total_size = 0; + + for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) { + const section_t *section = (const section_t *)list_node(node); + w_len = strlen(section->name) + strlen("[]\n");// format "[section->name]\n" + total_size += w_len; + + for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) { + const entry_t *entry = (const entry_t *)list_node(enode); + w_len = strlen(entry->key) + strlen(entry->value) + strlen(" = \n");// format "entry->key = entry->value\n" + total_size += w_len; + } + + // Only add a separating newline if there are more sections. + if (list_next(node) != list_end(config->sections)) { + total_size ++; //'\n' + } else { + break; + } + } + total_size ++; //'\0' + return total_size; +} + +static int get_config_size_from_flash(nvs_handle fp) +{ + assert(fp != 0); + + esp_err_t err; + char *keyname = osi_calloc(sizeof(CONFIG_KEY) + 1); + if (!keyname){ + LOG_ERROR("%s, malloc error\n", __func__); + return 0; + } + size_t length = CONFIG_FILE_DEFAULE_LENGTH; + size_t total_length = 0; + uint16_t i = 0; + snprintf(keyname, sizeof(CONFIG_KEY) + 1, "%s%d", CONFIG_KEY, 0); + err = nvs_get_blob(fp, keyname, NULL, &length); + if (err == ESP_ERR_NVS_NOT_FOUND) { + osi_free(keyname); + return 0; + } + if (err != ESP_OK) { + LOG_ERROR("%s, error %d\n", __func__, err); + osi_free(keyname); + return 0; + } + total_length += length; + while (length == CONFIG_FILE_MAX_SIZE) { + length = CONFIG_FILE_DEFAULE_LENGTH; + snprintf(keyname, sizeof(CONFIG_KEY) + 1, "%s%d", CONFIG_KEY, ++i); + err = nvs_get_blob(fp, keyname, NULL, &length); + + if (err == ESP_ERR_NVS_NOT_FOUND) { + break; + } + if (err != ESP_OK) { + LOG_ERROR("%s, error %d\n", __func__, err); + osi_free(keyname); + return 0; + } + total_length += length; + } + osi_free(keyname); + return total_length; +} + bool config_save(const config_t *config, const char *filename) { assert(config != NULL); @@ -312,8 +385,10 @@ bool config_save(const config_t *config, const char *filename) int err_code = 0; nvs_handle fp; char *line = osi_calloc(1024); - char *buf = osi_calloc(CONFIG_FILE_MAX_SIZE); - if (!line || !buf) { + char *keyname = osi_calloc(sizeof(CONFIG_KEY) + 1); + int config_size = get_config_size(config); + char *buf = osi_calloc(config_size + 100); + if (!line || !buf || !keyname) { err_code |= 0x01; goto error; } @@ -333,45 +408,53 @@ bool config_save(const config_t *config, const char *filename) const section_t *section = (const section_t *)list_node(node); w_cnt = snprintf(line, 1024, "[%s]\n", section->name); LOG_DEBUG("section name: %s, w_cnt + w_cnt_total = %d\n", section->name, w_cnt + w_cnt_total); - if (w_cnt + w_cnt_total < CONFIG_FILE_MAX_SIZE) { - memcpy(buf + w_cnt_total, line, w_cnt); - w_cnt_total += w_cnt; - } else { - break; - } + memcpy(buf + w_cnt_total, line, w_cnt); + w_cnt_total += w_cnt; for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) { const entry_t *entry = (const entry_t *)list_node(enode); LOG_DEBUG("(key, val): (%s, %s)\n", entry->key, entry->value); w_cnt = snprintf(line, 1024, "%s = %s\n", entry->key, entry->value); LOG_DEBUG("%s, w_cnt + w_cnt_total = %d", __func__, w_cnt + w_cnt_total); - if (w_cnt + w_cnt_total < CONFIG_FILE_MAX_SIZE) { - memcpy(buf + w_cnt_total, line, w_cnt); - w_cnt_total += w_cnt; - } else { - break; - } + memcpy(buf + w_cnt_total, line, w_cnt); + w_cnt_total += w_cnt; } // Only add a separating newline if there are more sections. if (list_next(node) != list_end(config->sections)) { - if (1 + w_cnt_total < CONFIG_FILE_MAX_SIZE) { - buf[w_cnt_total] = '\n'; - w_cnt_total += 1; - } + buf[w_cnt_total] = '\n'; + w_cnt_total += 1; } else { break; } } - buf[w_cnt_total] = '\0'; - - err = nvs_set_blob(fp, CONFIG_KEY, buf, w_cnt_total); - - if (err != ESP_OK) { - nvs_close(fp); - err_code |= 0x04; - goto error; + if (w_cnt_total < CONFIG_FILE_MAX_SIZE) { + snprintf(keyname, sizeof(CONFIG_KEY)+1, "%s%d", CONFIG_KEY, 0); + err = nvs_set_blob(fp, keyname, buf, w_cnt_total); + if (err != ESP_OK) { + nvs_close(fp); + err_code |= 0x04; + goto error; + } + }else { + uint count = (w_cnt_total / CONFIG_FILE_MAX_SIZE); + for (int i = 0; i <= count; i++) + { + snprintf(keyname, sizeof(CONFIG_KEY)+1, "%s%d", CONFIG_KEY, i); + if (i == count) { + err = nvs_set_blob(fp, keyname, buf + i*CONFIG_FILE_MAX_SIZE, w_cnt_total - i*CONFIG_FILE_MAX_SIZE); + LOG_DEBUG("save keyname = %s, i = %d, %d\n", keyname, i, w_cnt_total - i*CONFIG_FILE_MAX_SIZE); + }else { + err = nvs_set_blob(fp, keyname, buf + i*CONFIG_FILE_MAX_SIZE, CONFIG_FILE_MAX_SIZE); + LOG_DEBUG("save keyname = %s, i = %d, %d\n", keyname, i, CONFIG_FILE_MAX_SIZE); + } + if (err != ESP_OK) { + nvs_close(fp); + err_code |= 0x04; + goto error; + } + } } err = nvs_commit(fp); @@ -384,6 +467,7 @@ bool config_save(const config_t *config, const char *filename) nvs_close(fp); osi_free(line); osi_free(buf); + osi_free(keyname); return true; error: @@ -393,6 +477,9 @@ error: if (line) { osi_free(line); } + if (keyname) { + osi_free(keyname); + } if (err_code) { LOG_ERROR("%s, err_code: 0x%x\n", __func__, err_code); } @@ -423,19 +510,23 @@ static void config_parse(nvs_handle fp, config_t *config) assert(fp != 0); assert(config != NULL); + esp_err_t err; int line_num = 0; int err_code = 0; + uint16_t i = 0; + size_t length = CONFIG_FILE_DEFAULE_LENGTH; + size_t total_length = 0; char *line = osi_calloc(1024); char *section = osi_calloc(1024); - char *buf = osi_calloc(CONFIG_FILE_MAX_SIZE); - if (!line || !section || !buf) { + char *keyname = osi_calloc(sizeof(CONFIG_KEY) + 1); + int buf_size = get_config_size_from_flash(fp); + char *buf = osi_calloc(buf_size + 100); + if (!line || !section || !buf || !keyname) { err_code |= 0x01; goto error; } - - esp_err_t err; - size_t length = CONFIG_FILE_MAX_SIZE; - err = nvs_get_blob(fp, CONFIG_KEY, buf, &length); + snprintf(keyname, sizeof(CONFIG_KEY)+1, "%s%d", CONFIG_KEY, 0); + err = nvs_get_blob(fp, keyname, buf, &length); if (err == ESP_ERR_NVS_NOT_FOUND) { goto error; } @@ -443,12 +534,26 @@ static void config_parse(nvs_handle fp, config_t *config) err_code |= 0x02; goto error; } + total_length += length; + while (length == CONFIG_FILE_MAX_SIZE) { + length = CONFIG_FILE_DEFAULE_LENGTH; + snprintf(keyname, sizeof(CONFIG_KEY) + 1, "%s%d", CONFIG_KEY, ++i); + err = nvs_get_blob(fp, keyname, buf + CONFIG_FILE_MAX_SIZE * i, &length); + if (err == ESP_ERR_NVS_NOT_FOUND) { + break; + } + if (err != ESP_OK) { + err_code |= 0x02; + goto error; + } + total_length += length; + } char *p_line_end; char *p_line_bgn = buf; strcpy(section, CONFIG_DEFAULT_SECTION); - while ( (p_line_bgn < buf + length - 1) && (p_line_end = strchr(p_line_bgn, '\n'))) { + while ( (p_line_bgn < buf + total_length - 1) && (p_line_end = strchr(p_line_bgn, '\n'))) { // get one line int line_len = p_line_end - p_line_bgn; @@ -496,6 +601,9 @@ error: if (section) { osi_free(section); } + if (keyname) { + osi_free(keyname); + } if (err_code) { LOG_ERROR("%s returned with err code: %d\n", __func__, err_code); } diff --git a/components/bt/bluedroid/osi/include/alarm.h b/components/bt/bluedroid/osi/include/alarm.h index e71168ea0..3dc177c7a 100644 --- a/components/bt/bluedroid/osi/include/alarm.h +++ b/components/bt/bluedroid/osi/include/alarm.h @@ -57,6 +57,9 @@ void osi_alarm_free(osi_alarm_t *alarm); // |alarm| and |cb| may not be NULL. osi_alarm_err_t osi_alarm_set(osi_alarm_t *alarm, period_ms_t timeout); +// Sets an periodic alarm to fire |cb| each given |period|. +osi_alarm_err_t osi_alarm_set_periodic(osi_alarm_t *alarm, period_ms_t period); + // This function cancels the |alarm| if it was previously set. When this call // returns, the caller has a guarantee that the callback is not in progress and // will not be called if it hasn't already been called. This function is idempotent. @@ -65,6 +68,7 @@ osi_alarm_err_t osi_alarm_cancel(osi_alarm_t *alarm); // Figure out how much time until next expiration. // Returns 0 if not armed. |alarm| may not be NULL. +// only for oneshot alarm, not for periodic alarm // TODO: Remove this function once PM timers can be re-factored period_ms_t osi_alarm_get_remaining_ms(const osi_alarm_t *alarm); diff --git a/components/bt/bluedroid/osi/include/allocator.h b/components/bt/bluedroid/osi/include/allocator.h index b217cef9a..707901f43 100644 --- a/components/bt/bluedroid/osi/include/allocator.h +++ b/components/bt/bluedroid/osi/include/allocator.h @@ -21,6 +21,7 @@ #include #include +#include "esp_heap_caps.h" #include "sdkconfig.h" typedef void *(*alloc_fn)(size_t size); @@ -48,11 +49,13 @@ void osi_mem_dbg_record(void *p, int size, const char *func, int line); void osi_mem_dbg_clean(void *p, const char *func, int line); void osi_mem_dbg_show(void); +#if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST #define osi_malloc(size) \ ({ \ void *p; \ - \ - p = malloc((size)); \ + p = heap_caps_malloc_prefer(size, 2, \ + MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, \ + MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); \ osi_mem_dbg_record(p, size, __func__, __LINE__); \ (void *)p; \ }) @@ -60,12 +63,64 @@ void osi_mem_dbg_show(void); #define osi_calloc(size) \ ({ \ void *p; \ - \ + p = heap_caps_calloc_prefer(1, size, 2, \ + MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, \ + MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); \ + osi_mem_dbg_record(p, size, __func__, __LINE__); \ + (void *)p; \ +}) + +#else + +#define osi_malloc(size) \ +({ \ + void *p; \ + p = malloc((size)); \ + osi_mem_dbg_record(p, size, __func__, __LINE__); \ + (void *)p; \ +}) + +#define osi_calloc(size) \ +({ \ + void *p; \ p = calloc(1, (size)); \ osi_mem_dbg_record(p, size, __func__, __LINE__); \ (void *)p; \ }) +#endif /* #if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST */ + + +#if 0 +#define osi_malloc(size) \ +do { \ + void *p; \ + \ +#if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST \ + p = heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); \ +#else \ + p = malloc((size)); \ +#endif /* #if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST */ \ + osi_mem_dbg_record(p, size, __func__, __LINE__); \ + (void *)p; \ +}while(0) + +#define osi_calloc(size) \ +do { \ + void *p; \ + \ +#if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST \ + p = heap_caps_calloc_prefer(1, size, 2, \ + MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, \ + MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); \ +#else \ + p = calloc(1, (size)); \ +#endif /* #if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST */ \ + osi_mem_dbg_record(p, size, __func__, __LINE__); \ + (void *)p; \ +} while(0) +#endif + #define osi_free(ptr) \ do { \ void *tmp_point = (void *)(ptr); \ @@ -75,10 +130,24 @@ do { \ #else +#if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST +#define osi_malloc(size) heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL) +#define osi_calloc(size) heap_caps_calloc_prefer(1, size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL) +#else #define osi_malloc(size) malloc((size)) #define osi_calloc(size) calloc(1, (size)) +#endif /* #if CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST */ #define osi_free(p) free((p)) #endif /* CONFIG_BLUEDROID_MEM_DEBUG */ +#define FREE_AND_RESET(a) \ +do { \ + if (a) { \ + osi_free(a); \ + a = NULL; \ + } \ +}while (0) + + #endif /* _ALLOCATOR_H_ */ diff --git a/components/bt/bluedroid/osi/include/thread.h b/components/bt/bluedroid/osi/include/thread.h index 9ac5da6bd..6a92388d3 100644 --- a/components/bt/bluedroid/osi/include/thread.h +++ b/components/bt/bluedroid/osi/include/thread.h @@ -83,13 +83,21 @@ typedef enum { #define BTC_TASK_PRIO (configMAX_PRIORITIES - 6) #define BTC_TASK_QUEUE_LEN 60 -#define BTC_MEDIA_TASK_PINNED_TO_CORE (TASK_PINNED_TO_CORE) -#define BTC_MEDIA_TASK_STACK_SIZE (CONFIG_BTC_TASK_STACK_SIZE + BT_TASK_EXTRA_STACK_SIZE) -#define BTC_MEDIA_TASK_NAME "BtcMediaT" -#define BTC_MEDIA_TASK_PRIO (configMAX_PRIORITIES - 3) -#define BTC_MEDIA_DATA_QUEUE_LEN (1) -#define BTC_MEDIA_CTRL_QUEUE_LEN (5) -#define BTC_MEDIA_TASK_QUEUE_SET_LEN (BTC_MEDIA_DATA_QUEUE_LEN + BTC_MEDIA_CTRL_QUEUE_LEN) +#define BTC_A2DP_SINK_TASK_PINNED_TO_CORE (TASK_PINNED_TO_CORE) +#define BTC_A2DP_SINK_TASK_STACK_SIZE (CONFIG_A2DP_SINK_TASK_STACK_SIZE + BT_TASK_EXTRA_STACK_SIZE) // by menuconfig +#define BTC_A2DP_SINK_TASK_NAME "BtA2dSinkT" +#define BTC_A2DP_SINK_TASK_PRIO (configMAX_PRIORITIES - 3) +#define BTC_A2DP_SINK_DATA_QUEUE_LEN (3) +#define BTC_A2DP_SINK_CTRL_QUEUE_LEN (5) +#define BTC_A2DP_SINK_TASK_QUEUE_SET_LEN (BTC_A2DP_SINK_DATA_QUEUE_LEN + BTC_A2DP_SINK_CTRL_QUEUE_LEN) + +#define BTC_A2DP_SOURCE_TASK_PINNED_TO_CORE (TASK_PINNED_TO_CORE) +#define BTC_A2DP_SOURCE_TASK_STACK_SIZE (CONFIG_A2DP_SOURCE_TASK_STACK_SIZE + BT_TASK_EXTRA_STACK_SIZE) // by menuconfig +#define BTC_A2DP_SOURCE_TASK_NAME "BtA2dSourceT" +#define BTC_A2DP_SOURCE_TASK_PRIO (configMAX_PRIORITIES - 3) +#define BTC_A2DP_SOURCE_DATA_QUEUE_LEN (3) +#define BTC_A2DP_SOURCE_CTRL_QUEUE_LEN (5) +#define BTC_A2DP_SOURCE_TASK_QUEUE_SET_LEN (BTC_A2DP_SOURCE_DATA_QUEUE_LEN + BTC_A2DP_SOURCE_CTRL_QUEUE_LEN) #define TASK_POST_NON_BLOCKING (0) #define TASK_POST_BLOCKING (portMAX_DELAY) diff --git a/components/bt/bluedroid/stack/a2dp/a2d_api.c b/components/bt/bluedroid/stack/a2dp/a2d_api.c index 5f403cc6d..8c144b8ba 100644 --- a/components/bt/bluedroid/stack/a2dp/a2d_api.c +++ b/components/bt/bluedroid/stack/a2dp/a2d_api.c @@ -27,6 +27,7 @@ #include "a2d_api.h" #include "a2d_int.h" #include "avdt_api.h" +#include "allocator.h" #if (defined(A2D_INCLUDED) && A2D_INCLUDED == TRUE) @@ -35,6 +36,8 @@ *****************************************************************************/ #if A2D_DYNAMIC_MEMORY == FALSE tA2D_CB a2d_cb; +#else +tA2D_CB *a2d_cb_ptr; #endif @@ -374,6 +377,9 @@ UINT8 A2D_BitsSet(UINT8 num) *******************************************************************************/ void A2D_Init(void) { +#if (A2D_DYNAMIC_MEMORY) + a2d_cb_ptr = (tA2D_CB *)osi_malloc(sizeof(tA2D_CB)); +#endif /* #if (A2D_DYNAMIC_MEMORY) */ memset(&a2d_cb, 0, sizeof(tA2D_CB)); a2d_cb.avdt_sdp_ver = AVDT_VERSION; diff --git a/components/bt/bluedroid/stack/a2dp/a2d_sbc.c b/components/bt/bluedroid/stack/a2dp/a2d_sbc.c index 3a8f694fa..0c2b4adc8 100644 --- a/components/bt/bluedroid/stack/a2dp/a2d_sbc.c +++ b/components/bt/bluedroid/stack/a2dp/a2d_sbc.c @@ -29,7 +29,7 @@ #include "a2d_api.h" #include "a2d_int.h" #include "a2d_sbc.h" -#include "bt_utils.h" +#include "bt_defs.h" #if (defined(A2D_INCLUDED) && A2D_INCLUDED == TRUE) diff --git a/components/bt/bluedroid/stack/avct/avct_api.c b/components/bt/bluedroid/stack/avct/avct_api.c index 675ecaf83..2d86ba891 100644 --- a/components/bt/bluedroid/stack/avct/avct_api.c +++ b/components/bt/bluedroid/stack/avct/avct_api.c @@ -25,7 +25,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "l2c_api.h" #include "l2cdefs.h" #include "btm_api.h" @@ -38,6 +38,8 @@ /* Control block for AVCT */ #if AVCT_DYNAMIC_MEMORY == FALSE tAVCT_CB avct_cb; +#else +tAVCT_CB *avct_cb_ptr; #endif /******************************************************************************* diff --git a/components/bt/bluedroid/stack/avct/avct_l2c.c b/components/bt/bluedroid/stack/avct/avct_l2c.c index a1656d7f5..35abf45b2 100644 --- a/components/bt/bluedroid/stack/avct/avct_l2c.c +++ b/components/bt/bluedroid/stack/avct/avct_l2c.c @@ -25,7 +25,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avct_api.h" #include "avct_int.h" #include "l2c_api.h" diff --git a/components/bt/bluedroid/stack/avct/avct_lcb.c b/components/bt/bluedroid/stack/avct/avct_lcb.c index faf07b0a7..71c70f797 100644 --- a/components/bt/bluedroid/stack/avct/avct_lcb.c +++ b/components/bt/bluedroid/stack/avct/avct_lcb.c @@ -26,7 +26,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avct_api.h" #include "avct_int.h" #include "allocator.h" diff --git a/components/bt/bluedroid/stack/avct/avct_lcb_act.c b/components/bt/bluedroid/stack/avct/avct_lcb_act.c index b61690616..c33aa8943 100644 --- a/components/bt/bluedroid/stack/avct/avct_lcb_act.c +++ b/components/bt/bluedroid/stack/avct/avct_lcb_act.c @@ -25,7 +25,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avct_api.h" #include "avct_int.h" #include "btm_api.h" diff --git a/components/bt/bluedroid/stack/avdt/avdt_ad.c b/components/bt/bluedroid/stack/avdt/avdt_ad.c index a47241204..9a85fcef4 100644 --- a/components/bt/bluedroid/stack/avdt/avdt_ad.c +++ b/components/bt/bluedroid/stack/avdt/avdt_ad.c @@ -28,7 +28,7 @@ #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avdt_api.h" #include "avdtc_api.h" #include "avdt_int.h" diff --git a/components/bt/bluedroid/stack/avdt/avdt_api.c b/components/bt/bluedroid/stack/avdt/avdt_api.c index d9147032e..5167af13f 100644 --- a/components/bt/bluedroid/stack/avdt/avdt_api.c +++ b/components/bt/bluedroid/stack/avdt/avdt_api.c @@ -39,6 +39,8 @@ /* Control block for AVDT */ #if AVDT_DYNAMIC_MEMORY == FALSE tAVDT_CB avdt_cb; +#else +tAVDT_CB *avdt_cb_ptr; #endif /******************************************************************************* diff --git a/components/bt/bluedroid/stack/avdt/avdt_ccb.c b/components/bt/bluedroid/stack/avdt/avdt_ccb.c index 55cd1f9dc..3910f76e4 100644 --- a/components/bt/bluedroid/stack/avdt/avdt_ccb.c +++ b/components/bt/bluedroid/stack/avdt/avdt_ccb.c @@ -26,7 +26,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avdt_api.h" #include "avdtc_api.h" #include "avdt_int.h" @@ -407,7 +407,7 @@ void avdt_ccb_dealloc(tAVDT_CCB *p_ccb, tAVDT_CCB_EVT *p_data) UNUSED(p_data); AVDT_TRACE_DEBUG("avdt_ccb_dealloc %d\n", avdt_ccb_to_idx(p_ccb)); - btu_stop_timer(&p_ccb->timer_entry); + btu_free_timer(&p_ccb->timer_entry); fixed_queue_free(p_ccb->cmd_q, NULL); fixed_queue_free(p_ccb->rsp_q, NULL); memset(p_ccb, 0, sizeof(tAVDT_CCB)); diff --git a/components/bt/bluedroid/stack/avdt/avdt_ccb_act.c b/components/bt/bluedroid/stack/avdt/avdt_ccb_act.c index 0314438d5..2a35906eb 100644 --- a/components/bt/bluedroid/stack/avdt/avdt_ccb_act.c +++ b/components/bt/bluedroid/stack/avdt/avdt_ccb_act.c @@ -26,7 +26,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avdt_api.h" #include "avdtc_api.h" #include "avdt_int.h" diff --git a/components/bt/bluedroid/stack/avdt/avdt_l2c.c b/components/bt/bluedroid/stack/avdt/avdt_l2c.c index b99e83159..8df482fcc 100644 --- a/components/bt/bluedroid/stack/avdt/avdt_l2c.c +++ b/components/bt/bluedroid/stack/avdt/avdt_l2c.c @@ -25,7 +25,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avdt_api.h" #include "avdtc_api.h" #include "avdt_int.h" diff --git a/components/bt/bluedroid/stack/avdt/avdt_msg.c b/components/bt/bluedroid/stack/avdt/avdt_msg.c index 43245b87b..61378a6b1 100644 --- a/components/bt/bluedroid/stack/avdt/avdt_msg.c +++ b/components/bt/bluedroid/stack/avdt/avdt_msg.c @@ -29,7 +29,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avdt_api.h" #include "avdtc_api.h" #include "avdt_int.h" diff --git a/components/bt/bluedroid/stack/avdt/avdt_scb.c b/components/bt/bluedroid/stack/avdt/avdt_scb.c index bbad58528..484270d03 100644 --- a/components/bt/bluedroid/stack/avdt/avdt_scb.c +++ b/components/bt/bluedroid/stack/avdt/avdt_scb.c @@ -26,7 +26,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avdt_api.h" #include "avdtc_api.h" #include "avdt_int.h" @@ -644,7 +644,7 @@ void avdt_scb_dealloc(tAVDT_SCB *p_scb, tAVDT_SCB_EVT *p_data) UNUSED(p_data); AVDT_TRACE_DEBUG("avdt_scb_dealloc hdl=%d\n", avdt_scb_to_hdl(p_scb)); - btu_stop_timer(&p_scb->timer_entry); + btu_free_timer(&p_scb->timer_entry); #if AVDT_MULTIPLEXING == TRUE /* free fragments we're holding, if any; it shouldn't happen */ diff --git a/components/bt/bluedroid/stack/avdt/avdt_scb_act.c b/components/bt/bluedroid/stack/avdt/avdt_scb_act.c index d7699c2e6..963e8c3f1 100644 --- a/components/bt/bluedroid/stack/avdt/avdt_scb_act.c +++ b/components/bt/bluedroid/stack/avdt/avdt_scb_act.c @@ -26,7 +26,7 @@ #include #include "bt_types.h" #include "bt_target.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "avdt_api.h" #include "avdtc_api.h" #include "avdt_int.h" diff --git a/components/bt/bluedroid/stack/avrc/avrc_bld_tg.c b/components/bt/bluedroid/stack/avrc/avrc_bld_tg.c index 19ac85c58..2ab7c0534 100644 --- a/components/bt/bluedroid/stack/avrc/avrc_bld_tg.c +++ b/components/bt/bluedroid/stack/avrc/avrc_bld_tg.c @@ -20,7 +20,7 @@ #include "avrc_api.h" #include "avrc_defs.h" #include "avrc_int.h" -#include "bt_utils.h" +#include "bt_defs.h" #include "allocator.h" #if (defined(AVRC_INCLUDED) && AVRC_INCLUDED == TRUE) diff --git a/components/bt/bluedroid/stack/avrc/avrc_pars_ct.c b/components/bt/bluedroid/stack/avrc/avrc_pars_ct.c index 02b1eac88..a108d7077 100644 --- a/components/bt/bluedroid/stack/avrc/avrc_pars_ct.c +++ b/components/bt/bluedroid/stack/avrc/avrc_pars_ct.c @@ -20,7 +20,7 @@ #include "avrc_api.h" #include "avrc_defs.h" #include "avrc_int.h" -#include "bt_utils.h" +#include "bt_defs.h" #if (defined(AVRC_INCLUDED) && AVRC_INCLUDED == TRUE) diff --git a/components/bt/bluedroid/stack/avrc/avrc_sdp.c b/components/bt/bluedroid/stack/avrc/avrc_sdp.c index 4904c533d..cdec74c87 100644 --- a/components/bt/bluedroid/stack/avrc/avrc_sdp.c +++ b/components/bt/bluedroid/stack/avrc/avrc_sdp.c @@ -25,6 +25,7 @@ #include "bt_target.h" #include "avrc_api.h" #include "avrc_int.h" +#include "allocator.h" #if (defined(AVRC_INCLUDED) && AVRC_INCLUDED == TRUE) @@ -41,6 +42,8 @@ *****************************************************************************/ #if AVRC_DYNAMIC_MEMORY == FALSE tAVRC_CB avrc_cb; +#else +tAVRC_CB *avrc_cb_ptr; #endif /* update AVRC_NUM_PROTO_ELEMS if this constant is changed */ @@ -342,6 +345,9 @@ UINT8 AVRC_SetTraceLevel (UINT8 new_level) *******************************************************************************/ void AVRC_Init(void) { +#if AVRC_DYNAMIC_MEMORY + avrc_cb_ptr = (tAVRC_CB *)osi_malloc(sizeof(tAVRC_CB)); +#endif /* #if AVRC_DYNAMIC_MEMORY */ memset(&avrc_cb, 0, sizeof(tAVRC_CB)); #if defined(AVRC_INITIAL_TRACE_LEVEL) diff --git a/components/bt/bluedroid/stack/btm/btm_ble_addr.c b/components/bt/bluedroid/stack/btm/btm_ble_addr.c index cfc963275..9704c2ec4 100644 --- a/components/bt/bluedroid/stack/btm/btm_ble_addr.c +++ b/components/bt/bluedroid/stack/btm/btm_ble_addr.c @@ -596,7 +596,12 @@ void btm_ble_refresh_local_resolvable_private_addr(BD_ADDR pseudo_addr, BD_ADDR dummy_bda = {0}; if (p != NULL) { - if (btm_cb.ble_ctr_cb.privacy_mode != BTM_PRIVACY_NONE) { +/* + * Temporary solutions for pair with random address: + * use BLE_ADDR_RANDOM when adverting with random adress or in privacy mode + * We will do futher work here + */ + if (btm_cb.ble_ctr_cb.privacy_mode != BTM_PRIVACY_NONE || btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type == BLE_ADDR_RANDOM) { p->conn_addr_type = BLE_ADDR_RANDOM; if (memcmp(local_rpa, dummy_bda, BD_ADDR_LEN)) { memcpy(p->conn_addr, local_rpa, BD_ADDR_LEN); diff --git a/components/bt/bluedroid/stack/btm/btm_ble_gap.c b/components/bt/bluedroid/stack/btm/btm_ble_gap.c index cca345e4a..833a4d77a 100644 --- a/components/bt/bluedroid/stack/btm/btm_ble_gap.c +++ b/components/bt/bluedroid/stack/btm/btm_ble_gap.c @@ -517,7 +517,13 @@ tBTM_STATUS BTM_BleBroadcast(BOOLEAN start, tBTM_START_STOP_ADV_CMPL_CBACK *p_s evt_type = p_cb->scan_rsp ? BTM_BLE_CONNECT_EVT : BTM_BLE_NON_CONNECT_EVT; } #endif - + // if adv state is BTM_BLE_ADV_PENDING, return immediately + if (p_cb->state == BTM_BLE_ADV_PENDING) { + if (p_stop_adv_cback) { + (*p_stop_adv_cback)(HCI_ERR_ILLEGAL_COMMAND); + } + return BTM_BUSY; + } if (start && p_cb->adv_mode == BTM_BLE_ADV_DISABLE) { /* update adv params */ if (!btsnd_hcic_ble_write_adv_params ((UINT16)(p_cb->adv_interval_min ? p_cb->adv_interval_min : @@ -538,7 +544,7 @@ tBTM_STATUS BTM_BleBroadcast(BOOLEAN start, tBTM_START_STOP_ADV_CMPL_CBACK *p_s } status = btm_ble_start_adv (); - } else if (!start) { + } else if (!start && p_cb->adv_mode == BTM_BLE_ADV_ENABLE) { //save the stop adv callback to the BTM env. p_cb->p_stop_adv_cb = p_stop_adv_cback; status = btm_ble_stop_adv(); @@ -546,9 +552,14 @@ tBTM_STATUS BTM_BleBroadcast(BOOLEAN start, tBTM_START_STOP_ADV_CMPL_CBACK *p_s btm_ble_disable_resolving_list(BTM_BLE_RL_ADV, TRUE); #endif } else { - status = BTM_WRONG_MODE; - BTM_TRACE_ERROR("Can not %s Broadcast, device %s in Broadcast mode", - (start ? "Start" : "Stop"), (start ? "already" : "not")); + /* + 1. start adv when adv has already started (not used) + 2. stop adv shen adv has already stoped + */ + status = BTM_SUCCESS; + if (p_stop_adv_cback) { + (*p_stop_adv_cback)(status); + } } return status; } @@ -736,6 +747,22 @@ BOOLEAN BTM_BleConfigPrivacy(BOOLEAN privacy_mode, tBTM_SET_LOCAL_PRIVACY_CBACK if (!controller_get_interface()->supports_ble()) { return FALSE; } + +/* + * Temporary solutions for pair with random address: + * can't set privacy when advertising, scaning or using static random address + * We will do futher work here + */ + if (p_cb->privacy_mode == BTM_PRIVACY_NONE + && random_cb->own_addr_type == BLE_ADDR_RANDOM) { + BTM_TRACE_ERROR("Have set random adress, can't set privacy "); + return FALSE; + } + if (!(p_cb->inq_var.state != BTM_BLE_STOP_SCAN && p_cb->inq_var.state != BTM_BLE_STOP_ADV)) { + BTM_TRACE_ERROR("Advertising or scaning now, can't set privacy "); + return FALSE; + } + #if (defined(GAP_INCLUDED) && GAP_INCLUDED == TRUE && GATTS_INCLUDED == TRUE) uint8_t addr_resolution = 0; #endif /* defined(GAP_INCLUDED) && GAP_INCLUDED == TRUE && GATTS_INCLUDED == TRUE */ @@ -1165,6 +1192,16 @@ tBTM_STATUS BTM_BleSetAdvParamsStartAdv(UINT16 adv_int_min, UINT16 adv_int_max, return BTM_ILLEGAL_VALUE; } +/* + * Temporary solutions for pair with random address: + * can't set advertising with BLE_ADDR_PUBLIC when having set random adress or in privacy mode + * We will do futher work here + */ + if (btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type == BLE_ADDR_RANDOM && own_bda_type == BLE_ADDR_PUBLIC) { + BTM_TRACE_ERROR ("own_addr_type is BLE_ADDR_RANDOM but use BLE_ADDR_PUBLIC\n"); + return BTM_ILLEGAL_VALUE; + } + if (!BTM_BLE_ISVALID_PARAM(adv_int_min, BTM_BLE_ADV_INT_MIN, BTM_BLE_ADV_INT_MAX) || !BTM_BLE_ISVALID_PARAM(adv_int_max, BTM_BLE_ADV_INT_MIN, BTM_BLE_ADV_INT_MAX)) { return BTM_ILLEGAL_VALUE; @@ -1191,8 +1228,23 @@ tBTM_STATUS BTM_BleSetAdvParamsStartAdv(UINT16 adv_int_min, UINT16 adv_int_max, } BTM_TRACE_EVENT ("update params for an active adv\n"); - - btm_ble_stop_adv(); + // if adv state is BTM_BLE_ADV_PENDING, return immediately + if (p_cb->state == BTM_BLE_ADV_PENDING) { + if (p_cb->p_adv_cb) { + (*p_cb->p_adv_cb)(HCI_ERR_ILLEGAL_COMMAND); + } + return BTM_BUSY; + } + /* host will stop adv first and then start adv again if adv has already started + it will get callback twice. + */ + if (p_cb->adv_mode == BTM_BLE_ADV_ENABLE) { + p_cb->adv_callback_twice = TRUE; + } + tBTM_STATUS status = btm_ble_stop_adv(); + if (status != BTM_SUCCESS) { + p_cb->adv_callback_twice = FALSE; + } /* update adv params */ btsnd_hcic_ble_write_adv_params (adv_int_min, @@ -1315,6 +1367,19 @@ void BTM_BleSetScanFilterParams(tGATT_IF client_if, UINT32 scan_interval, UINT32 return; } +/* + * Temporary solutions for pair with random address: + * can't set scan with BLE_ADDR_PUBLIC when having set random adress or in privacy mode + * We will do futher work here + */ + if (btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type == BLE_ADDR_RANDOM && addr_type_own == BLE_ADDR_PUBLIC) { + BTM_TRACE_ERROR ("own_addr_type is BLE_ADDR_RANDOM but use BLE_ADDR_PUBLIC\n"); + if (scan_setup_status_cback != NULL) { + scan_setup_status_cback(client_if, BTM_ILLEGAL_VALUE); + } + return; + } + /* If not supporting extended scan support, use the older range for checking */ if (btm_cb.cmn_ble_vsc_cb.extended_scan_support == 0) { max_scan_interval = BTM_BLE_SCAN_INT_MAX; @@ -1500,6 +1565,24 @@ BOOLEAN BTM_BleSetRandAddress(BD_ADDR rand_addr) if (rand_addr == NULL) return set_flag; +/* + * Temporary solutions for pair with random address: + * can't set rand address when advertising, scaning or in privacy mode + * We will do futher work here + */ +#if BLE_PRIVACY_SPT == TRUE + if (btm_cb.ble_ctr_cb.privacy_mode != BTM_PRIVACY_NONE) { + BTM_TRACE_ERROR("privacy_mode is not BTM_PRIVACY_NONE "); + return set_flag; + } + +#endif + if (!(btm_cb.ble_ctr_cb.inq_var.state != BTM_BLE_STOP_SCAN && btm_cb.ble_ctr_cb.inq_var.state != BTM_BLE_STOP_ADV)) { + BTM_TRACE_ERROR("Advertising or scaning now, can't set randaddress "); + return FALSE; + } + memcpy(btm_cb.ble_ctr_cb.addr_mgnt_cb.private_addr, rand_addr, BD_ADDR_LEN); + btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type = BLE_ADDR_RANDOM; //send the set random address to the controller set_flag = btsnd_hcic_ble_set_random_addr(rand_addr); return set_flag; @@ -3297,13 +3380,13 @@ tBTM_STATUS btm_ble_start_adv(void) #endif if (p_cb->afp != AP_SCAN_CONN_ALL) { //find the device in the btm dev buffer and write it to the controller white list - btm_execute_wl_dev_operation(); + btm_execute_wl_dev_operation(); btm_cb.ble_ctr_cb.wl_state |= BTM_BLE_WL_ADV; } if (btsnd_hcic_ble_set_adv_enable (BTM_BLE_ADV_ENABLE)) { p_cb->adv_mode = BTM_BLE_ADV_ENABLE; - p_cb->state = BTM_BLE_ADVERTISING; + p_cb->state = BTM_BLE_ADV_PENDING; btm_ble_adv_states_operation(btm_ble_set_topology_mask, p_cb->evt_type); rt = BTM_SUCCESS; BTM_TRACE_EVENT ("BTM_SUCCESS\n"); @@ -3332,7 +3415,7 @@ tBTM_STATUS btm_ble_stop_adv(void) if (btsnd_hcic_ble_set_adv_enable (BTM_BLE_ADV_DISABLE)) { p_cb->fast_adv_on = FALSE; p_cb->adv_mode = BTM_BLE_ADV_DISABLE; - p_cb->state = BTM_BLE_STOP_ADV; + p_cb->state = BTM_BLE_ADV_PENDING; btm_cb.ble_ctr_cb.wl_state &= ~BTM_BLE_WL_ADV; /* clear all adv states */ @@ -3506,9 +3589,23 @@ void btm_ble_write_adv_enable_complete(UINT8 *p) // callback to the APP after receive the adv complete from the controller. if (p_cb->p_adv_cb && p_cb->adv_mode == BTM_BLE_ADV_ENABLE) { - (*p_cb->p_adv_cb)(status); + if (p_cb->adv_callback_twice) { + p_cb->adv_callback_twice = FALSE; + }else { + p_cb->state = BTM_BLE_ADVERTISING; + (*p_cb->p_adv_cb)(status); + } } else if (p_cb->p_stop_adv_cb && p_cb->adv_mode == BTM_BLE_ADV_DISABLE) { + p_cb->state = BTM_BLE_STOP_ADV; (*p_cb->p_stop_adv_cb)(status); + }else { + // p_cb->p_adv_cb is NULL or p_cb->p_stop_adv_cb is NULL + if (p_cb->adv_mode == BTM_BLE_ADV_ENABLE) { + p_cb->state = BTM_BLE_ADVERTISING; + }else { + p_cb->state = BTM_BLE_STOP_ADV; + } + p_cb->adv_callback_twice = FALSE; } /* if write adv enable/disbale not succeed */ if (*p != HCI_SUCCESS) { @@ -3651,6 +3748,9 @@ void btm_ble_init (void) BTM_TRACE_DEBUG("%s", __func__); + btu_free_timer(&p_cb->obs_timer_ent); + btu_free_timer(&p_cb->scan_timer_ent); + btu_free_timer(&p_cb->inq_var.fast_adv_timer); memset(p_cb, 0, sizeof(tBTM_BLE_CB)); memset(&(btm_cb.cmn_ble_vsc_cb), 0 , sizeof(tBTM_BLE_VSC_CB)); btm_cb.cmn_ble_vsc_cb.values_read = FALSE; diff --git a/components/bt/bluedroid/stack/btm/btm_devctl.c b/components/bt/bluedroid/stack/btm/btm_devctl.c index 00d580b3e..94e143bfe 100644 --- a/components/bt/bluedroid/stack/btm/btm_devctl.c +++ b/components/bt/bluedroid/stack/btm/btm_devctl.c @@ -533,7 +533,7 @@ void btm_read_local_name_complete (UINT8 *p, UINT16 evt_len) UINT8 status; UNUSED(evt_len); - btu_stop_timer (&btm_cb.devcb.rln_timer); + btu_free_timer (&btm_cb.devcb.rln_timer); /* If there was a callback address for read local name, call it */ btm_cb.devcb.p_rln_cmpl_cb = NULL; diff --git a/components/bt/bluedroid/stack/btm/btm_inq.c b/components/bt/bluedroid/stack/btm/btm_inq.c index d9baa4b81..010d1d93c 100644 --- a/components/bt/bluedroid/stack/btm/btm_inq.c +++ b/components/bt/bluedroid/stack/btm/btm_inq.c @@ -1359,6 +1359,12 @@ void btm_inq_db_init (void) #if 0 /* cleared in btm_init; put back in if called from anywhere else! */ memset (&btm_cb.btm_inq_vars, 0, sizeof (tBTM_INQUIRY_VAR_ST)); #endif + + btu_free_timer(&btm_cb.btm_inq_vars.rmt_name_timer_ent); + memset(&btm_cb.btm_inq_vars.rmt_name_timer_ent, 0, sizeof(TIMER_LIST_ENT)); + btu_free_timer(&btm_cb.btm_inq_vars.inq_timer_ent); + memset(&btm_cb.btm_inq_vars.inq_timer_ent, 0, sizeof(TIMER_LIST_ENT)); + btm_cb.btm_inq_vars.no_inc_ssp = BTM_NO_SSP_ON_INQUIRY; } diff --git a/components/bt/bluedroid/stack/btm/btm_main.c b/components/bt/bluedroid/stack/btm/btm_main.c index a2ee6ca3d..043e7fd01 100644 --- a/components/bt/bluedroid/stack/btm/btm_main.c +++ b/components/bt/bluedroid/stack/btm/btm_main.c @@ -33,6 +33,8 @@ */ #if BTM_DYNAMIC_MEMORY == FALSE tBTM_CB btm_cb; +#else +tBTM_CB *btm_cb_ptr; #endif /******************************************************************************* @@ -49,9 +51,11 @@ tBTM_CB btm_cb; *******************************************************************************/ void btm_init (void) { +#if BTM_DYNAMIC_MEMORY + btm_cb_ptr = (tBTM_CB *)osi_malloc(sizeof(tBTM_CB)); +#endif /* #if BTM_DYNAMIC_MEMORY */ /* All fields are cleared; nonzero fields are reinitialized in appropriate function */ memset(&btm_cb, 0, sizeof(tBTM_CB)); - btm_cb.page_queue = fixed_queue_new(SIZE_MAX); btm_cb.sec_pending_q = fixed_queue_new(SIZE_MAX); @@ -87,4 +91,7 @@ void btm_free(void) { fixed_queue_free(btm_cb.page_queue, osi_free_func); fixed_queue_free(btm_cb.sec_pending_q, osi_free_func); +#if BTM_DYNAMIC_MEMORY + FREE_AND_RESET(btm_cb_ptr); +#endif } diff --git a/components/bt/bluedroid/stack/btu/btu_init.c b/components/bt/bluedroid/stack/btu/btu_init.c index a3ffd0e70..60381c183 100644 --- a/components/bt/bluedroid/stack/btu/btu_init.c +++ b/components/bt/bluedroid/stack/btu/btu_init.c @@ -119,12 +119,15 @@ void btu_free_core(void) l2c_free(); #if BLE_INCLUDED == TRUE -#if (defined(GATTS_INCLUDED) && GATTS_INCLUDED == true) +#if (defined(GATT_INCLUDED) && GATT_INCLUDED == true) gatt_free(); #endif btm_ble_free(); #endif btm_free(); +#if SMP_INCLUDED == TRUE + SMP_Free(); +#endif } /***************************************************************************** @@ -141,6 +144,9 @@ void btu_free_core(void) ******************************************************************************/ void BTU_StartUp(void) { +#if BTU_DYNAMIC_MEMORY + btu_cb_ptr = (tBTU_CB *)osi_malloc(sizeof(tBTU_CB)); +#endif /* #if BTU_DYNAMIC_MEMORY */ memset (&btu_cb, 0, sizeof (tBTU_CB)); btu_cb.trace_level = HCI_INITIAL_TRACE_LEVEL; @@ -182,6 +188,9 @@ error_exit:; void BTU_ShutDown(void) { +#if BTU_DYNAMIC_MEMORY + FREE_AND_RESET(btu_cb_ptr); +#endif btu_task_shut_down(); hash_map_free(btu_general_alarm_hash_map); diff --git a/components/bt/bluedroid/stack/btu/btu_task.c b/components/bt/bluedroid/stack/btu/btu_task.c index fc908ba58..ee6bb6d86 100644 --- a/components/bt/bluedroid/stack/btu/btu_task.c +++ b/components/bt/bluedroid/stack/btu/btu_task.c @@ -91,6 +91,8 @@ extern void BTE_InitStack(void); */ #if BTU_DYNAMIC_MEMORY == FALSE tBTU_CB btu_cb; +#else +tBTU_CB *btu_cb_ptr; #endif extern hash_map_t *btu_general_alarm_hash_map; @@ -511,15 +513,12 @@ void btu_free_timer(TIMER_LIST_ENT *p_tle) { assert(p_tle != NULL); - if (p_tle->in_use == FALSE) { - return; - } p_tle->in_use = FALSE; // Get the alarm for the timer list entry. osi_alarm_t *alarm = hash_map_get(btu_general_alarm_hash_map, p_tle); if (alarm == NULL) { - LOG_WARN("%s Unable to find expected alarm in hashmap", __func__); + LOG_DEBUG("%s Unable to find expected alarm in hashmap", __func__); return; } osi_alarm_cancel(alarm); @@ -613,6 +612,23 @@ void btu_stop_quick_timer(TIMER_LIST_ENT *p_tle) } osi_alarm_cancel(alarm); } + +void btu_free_quick_timer(TIMER_LIST_ENT *p_tle) +{ + assert(p_tle != NULL); + + p_tle->in_use = FALSE; + + // Get the alarm for the timer list entry. + osi_alarm_t *alarm = hash_map_get(btu_l2cap_alarm_hash_map, p_tle); + if (alarm == NULL) { + LOG_DEBUG("%s Unable to find expected alarm in hashmap", __func__); + return; + } + osi_alarm_cancel(alarm); + hash_map_erase(btu_l2cap_alarm_hash_map, p_tle); +} + #endif /* defined(QUICK_TIMER_TICKS_PER_SEC) && (QUICK_TIMER_TICKS_PER_SEC > 0) */ void btu_oneshot_alarm_cb(void *data) diff --git a/components/bt/bluedroid/stack/gatt/gatt_main.c b/components/bt/bluedroid/stack/gatt/gatt_main.c index 5ebd2ec0b..7ceb67ddc 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_main.c +++ b/components/bt/bluedroid/stack/gatt/gatt_main.c @@ -77,6 +77,8 @@ static const tL2CAP_APPL_INFO dyn_info = { #if GATT_DYNAMIC_MEMORY == FALSE tGATT_CB gatt_cb; +#else +tGATT_CB *gatt_cb_ptr; #endif tGATT_DEFAULT gatt_default; @@ -94,9 +96,9 @@ tGATT_DEFAULT gatt_default; void gatt_init (void) { tL2CAP_FIXED_CHNL_REG fixed_reg; - - GATT_TRACE_DEBUG("gatt_init()"); - +#if GATT_DYNAMIC_MEMORY + gatt_cb_ptr = (tGATT_CB *)osi_malloc(sizeof(tGATT_CB)); +#endif /* #if GATT_DYNAMIC_MEMORY */ memset (&gatt_cb, 0, sizeof(tGATT_CB)); memset (&fixed_reg, 0, sizeof(tL2CAP_FIXED_CHNL_REG)); @@ -152,7 +154,7 @@ void gatt_init (void) ** Returns void ** *******************************************************************************/ -#if (GATTS_INCLUDED == TRUE) +#if (GATT_INCLUDED == TRUE) void gatt_free(void) { int i; @@ -172,13 +174,26 @@ void gatt_free(void) fixed_queue_free(gatt_cb.tcb[i].pending_ind_q, NULL); gatt_cb.tcb[i].pending_ind_q = NULL; + btu_free_timer(&gatt_cb.tcb[i].conf_timer_ent); + memset(&gatt_cb.tcb[i].conf_timer_ent, 0, sizeof(TIMER_LIST_ENT)); + + btu_free_timer(&gatt_cb.tcb[i].ind_ack_timer_ent); + memset(&gatt_cb.tcb[i].ind_ack_timer_ent, 0, sizeof(TIMER_LIST_ENT)); + +#if (GATTS_INCLUDED == TRUE) fixed_queue_free(gatt_cb.tcb[i].sr_cmd.multi_rsp_q, NULL); gatt_cb.tcb[i].sr_cmd.multi_rsp_q = NULL; +#endif /* #if (GATTS_INCLUDED == TRUE) */ } +#if (GATTS_INCLUDED == TRUE) for (i = 0; i < GATT_MAX_SR_PROFILES; i++) { gatt_free_hdl_buffer(&gatt_cb.hdl_list[i]); } +#endif /* #if (GATTS_INCLUDED == TRUE) */ +#if GATT_DYNAMIC_MEMORY + FREE_AND_RESET(gatt_cb_ptr); +#endif /* #if GATT_DYNAMIC_MEMORY */ } #endif ///GATTS_INCLUDED == TRUE diff --git a/components/bt/bluedroid/stack/gatt/gatt_utils.c b/components/bt/bluedroid/stack/gatt/gatt_utils.c index 5b0db44e9..98339c04e 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_utils.c +++ b/components/bt/bluedroid/stack/gatt/gatt_utils.c @@ -1683,6 +1683,7 @@ tGATT_CLCB *gatt_clcb_alloc (UINT16 conn_id) void gatt_clcb_dealloc (tGATT_CLCB *p_clcb) { if (p_clcb && p_clcb->in_use) { + btu_free_timer(&p_clcb->rsp_timer_ent); memset(p_clcb, 0, sizeof(tGATT_CLCB)); } } @@ -2211,8 +2212,8 @@ void gatt_cleanup_upon_disc(BD_ADDR bda, UINT16 reason, tBT_TRANSPORT transport) } } - btu_stop_timer (&p_tcb->ind_ack_timer_ent); - btu_stop_timer (&p_tcb->conf_timer_ent); + btu_free_timer (&p_tcb->ind_ack_timer_ent); + btu_free_timer (&p_tcb->conf_timer_ent); gatt_free_pending_ind(p_tcb); gatt_free_pending_enc_queue(p_tcb); gatt_free_pending_prepare_write_queue(p_tcb); diff --git a/components/bt/bluedroid/stack/include/btm_ble_int.h b/components/bt/bluedroid/stack/include/btm_ble_int.h index f5b6c90b1..a88108d73 100644 --- a/components/bt/bluedroid/stack/include/btm_ble_int.h +++ b/components/bt/bluedroid/stack/include/btm_ble_int.h @@ -150,6 +150,7 @@ typedef struct { tBTM_START_ADV_CMPL_CBACK *p_adv_cb; tBTM_START_STOP_ADV_CMPL_CBACK *p_stop_adv_cb; tBLE_ADDR_TYPE adv_addr_type; + BOOLEAN adv_callback_twice; UINT8 evt_type; UINT8 adv_mode; tBLE_BD_ADDR direct_bda; diff --git a/components/bt/bluedroid/stack/include/btu.h b/components/bt/bluedroid/stack/include/btu.h index 0401e471c..c59fb7f56 100644 --- a/components/bt/bluedroid/stack/include/btu.h +++ b/components/bt/bluedroid/stack/include/btu.h @@ -246,6 +246,7 @@ void btu_uipc_rx_cback(BT_HDR *p_msg); #if defined(QUICK_TIMER_TICKS_PER_SEC) && (QUICK_TIMER_TICKS_PER_SEC > 0) void btu_start_quick_timer (TIMER_LIST_ENT *p_tle, UINT16 type, UINT32 timeout); void btu_stop_quick_timer (TIMER_LIST_ENT *p_tle); +void btu_free_quick_timer (TIMER_LIST_ENT *p_tle); void btu_process_quick_timer_evt (void); #endif diff --git a/components/bt/bluedroid/stack/include/dyn_mem.h b/components/bt/bluedroid/stack/include/dyn_mem.h index 2654316cb..223ae9f90 100644 --- a/components/bt/bluedroid/stack/include/dyn_mem.h +++ b/components/bt/bluedroid/stack/include/dyn_mem.h @@ -19,7 +19,40 @@ #define DYN_MEM_H #include "sdkconfig.h" -#if CONFIG_CLASSIC_BT_ENABLED +#if CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY +#define BTU_DYNAMIC_MEMORY TRUE +#define BTM_DYNAMIC_MEMORY TRUE +#define L2C_DYNAMIC_MEMORY TRUE +#define GATT_DYNAMIC_MEMORY TRUE +#define SMP_DYNAMIC_MEMORY TRUE +#define BTA_DYNAMIC_MEMORY TRUE +#define SDP_DYNAMIC_MEMORY TRUE +#define RFC_DYNAMIC_MEMORY TRUE +#define TCS_DYNAMIC_MEMORY TRUE +#define BNEP_DYNAMIC_MEMORY TRUE +#define AVDT_DYNAMIC_MEMORY TRUE +#define AVCT_DYNAMIC_MEMORY TRUE +#define MCA_DYNAMIC_MEMORY TRUE +#define A2D_DYNAMIC_MEMORY TRUE +#define VDP_DYNAMIC_MEMORY TRUE +#define AVRC_DYNAMIC_MEMORY TRUE +#define BIP_DYNAMIC_MEMORY TRUE +#define BPP_DYNAMIC_MEMORY TRUE +#define CTP_DYNAMIC_MEMORY TRUE +#define FTP_DYNAMIC_MEMORY TRUE +#define HCRP_DYNAMIC_MEMORY TRUE +#define HFP_DYNAMIC_MEMORY TRUE +#define HID_DYNAMIC_MEMORY TRUE +#define HSP2_DYNAMIC_MEMORY TRUE +#define ICP_DYNAMIC_MEMORY TRUE +#define OPP_DYNAMIC_MEMORY TRUE +#define PAN_DYNAMIC_MEMORY TRUE +#define SPP_DYNAMIC_MEMORY TRUE +#define SLIP_DYNAMIC_MEMORY TRUE +#define LLCP_DYNAMIC_MEMORY TRUE +#define BTC_SBC_DEC_DYNAMIC_MEMORY TRUE + +#else /* #if CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY */ #define SDP_DYNAMIC_MEMORY FALSE #define RFC_DYNAMIC_MEMORY FALSE @@ -45,35 +78,9 @@ #define SPP_DYNAMIC_MEMORY FALSE #define SLIP_DYNAMIC_MEMORY FALSE #define LLCP_DYNAMIC_MEMORY FALSE +#define BTC_SBC_DEC_DYNAMIC_MEMORY FALSE -#else /* #if CONFIG_CLASSIC_BT_ENABLED */ - -#define SDP_DYNAMIC_MEMORY TRUE -#define RFC_DYNAMIC_MEMORY TRUE -#define TCS_DYNAMIC_MEMORY TRUE -#define BNEP_DYNAMIC_MEMORY TRUE -#define AVDT_DYNAMIC_MEMORY TRUE -#define AVCT_DYNAMIC_MEMORY TRUE -#define MCA_DYNAMIC_MEMORY TRUE -#define A2D_DYNAMIC_MEMORY TRUE -#define VDP_DYNAMIC_MEMORY TRUE -#define AVRC_DYNAMIC_MEMORY TRUE -#define BIP_DYNAMIC_MEMORY TRUE -#define BPP_DYNAMIC_MEMORY TRUE -#define CTP_DYNAMIC_MEMORY TRUE -#define FTP_DYNAMIC_MEMORY TRUE -#define HCRP_DYNAMIC_MEMORY TRUE -#define HFP_DYNAMIC_MEMORY TRUE -#define HID_DYNAMIC_MEMORY TRUE -#define HSP2_DYNAMIC_MEMORY TRUE -#define ICP_DYNAMIC_MEMORY TRUE -#define OPP_DYNAMIC_MEMORY TRUE -#define PAN_DYNAMIC_MEMORY TRUE -#define SPP_DYNAMIC_MEMORY TRUE -#define SLIP_DYNAMIC_MEMORY TRUE -#define LLCP_DYNAMIC_MEMORY TRUE - -#endif /* #if CONFIG_CLASSIC_BT_ENABLED */ +#endif /* #if CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY */ /**************************************************************************** ** Define memory usage for each CORE component (if not defined in bdroid_buildcfg.h) ** The default for each component is to use static memory allocations. diff --git a/components/bt/bluedroid/stack/include/smp_api.h b/components/bt/bluedroid/stack/include/smp_api.h index cb043fdfa..193cfe33b 100644 --- a/components/bt/bluedroid/stack/include/smp_api.h +++ b/components/bt/bluedroid/stack/include/smp_api.h @@ -303,6 +303,18 @@ extern "C" *******************************************************************************/ extern void SMP_Init(void); +/******************************************************************************* +** +** Function SMP_Free +** +** Description This function de initializes the SMP unit. +** +** Returns void +** +*******************************************************************************/ +extern void SMP_Free(void); + + /******************************************************************************* ** ** Function SMP_SetTraceLevel diff --git a/components/bt/bluedroid/stack/l2cap/include/l2c_int.h b/components/bt/bluedroid/stack/l2cap/include/l2c_int.h index 44ef74b28..e65b328cf 100644 --- a/components/bt/bluedroid/stack/l2cap/include/l2c_int.h +++ b/components/bt/bluedroid/stack/l2cap/include/l2c_int.h @@ -767,7 +767,7 @@ extern BOOLEAN l2c_fcr_renegotiate_chan(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg extern UINT8 l2c_fcr_process_peer_cfg_req(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); extern void l2c_fcr_adj_monitor_retran_timeout (tL2C_CCB *p_ccb); extern void l2c_fcr_stop_timer (tL2C_CCB *p_ccb); - +extern void l2c_fcr_free_timer (tL2C_CCB *p_ccb); /* Functions provided by l2c_ble.c ************************************ */ diff --git a/components/bt/bluedroid/stack/l2cap/l2c_ble.c b/components/bt/bluedroid/stack/l2cap/l2c_ble.c index 0332366d8..a416826ae 100644 --- a/components/bt/bluedroid/stack/l2cap/l2c_ble.c +++ b/components/bt/bluedroid/stack/l2cap/l2c_ble.c @@ -1025,10 +1025,16 @@ void l2cble_process_rc_param_request_evt(UINT16 handle, UINT16 int_min, UINT16 i if ((p_lcb->conn_update_mask & L2C_BLE_CONN_UPDATE_DISABLE) == 0) { p_lcb->conn_update_mask |= L2C_BLE_UPDATE_PENDING; btsnd_hcic_ble_rc_param_req_reply(handle, int_min, int_max, latency, timeout, 0, 0); - } else { - L2CAP_TRACE_EVENT ("L2CAP - LE - update currently disabled"); - p_lcb->conn_update_mask |= L2C_BLE_NEW_CONN_PARAM; - btsnd_hcic_ble_rc_param_req_neg_reply (handle, HCI_ERR_UNACCEPT_CONN_INTERVAL); + }else { + /* always accept connection parameters request which is sent by itself */ + if (int_max == BTM_BLE_CONN_INT_MIN) { + p_lcb->conn_update_mask |= L2C_BLE_UPDATE_PENDING; + btsnd_hcic_ble_rc_param_req_reply(handle, int_min, int_max, latency, timeout, 0, 0); + }else { + L2CAP_TRACE_EVENT ("L2CAP - LE - update currently disabled"); + p_lcb->conn_update_mask |= L2C_BLE_NEW_CONN_PARAM; + btsnd_hcic_ble_rc_param_req_neg_reply (handle, HCI_ERR_UNACCEPT_CONN_INTERVAL); + } } } else { diff --git a/components/bt/bluedroid/stack/l2cap/l2c_fcr.c b/components/bt/bluedroid/stack/l2cap/l2c_fcr.c index 640e4908c..10f0c5954 100644 --- a/components/bt/bluedroid/stack/l2cap/l2c_fcr.c +++ b/components/bt/bluedroid/stack/l2cap/l2c_fcr.c @@ -201,6 +201,21 @@ void l2c_fcr_stop_timer (tL2C_CCB *p_ccb) } } +/******************************************************************************* +** +** Function l2c_fcr_free_timer +** +** Description This function releases the (monitor or transmission) timer. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_free_timer (tL2C_CCB *p_ccb) +{ + assert(p_ccb != NULL); + btu_free_quick_timer (&p_ccb->fcrb.mon_retrans_timer); +} + /******************************************************************************* ** ** Function l2c_fcr_cleanup @@ -232,9 +247,12 @@ void l2c_fcr_cleanup (tL2C_CCB *p_ccb) fixed_queue_free(p_fcrb->retrans_q, osi_free_func); p_fcrb->retrans_q = NULL; - btu_stop_quick_timer (&p_fcrb->ack_timer); - btu_stop_quick_timer (&p_ccb->fcrb.mon_retrans_timer); - + btu_free_quick_timer (&p_fcrb->ack_timer); + memset(&p_fcrb->ack_timer, 0, sizeof(TIMER_LIST_ENT)); + + btu_free_quick_timer (&p_ccb->fcrb.mon_retrans_timer); + memset(&p_fcrb->mon_retrans_timer, 0, sizeof(TIMER_LIST_ENT)); + #if (L2CAP_ERTM_STATS == TRUE) if ( (p_ccb->local_cid >= L2CAP_BASE_APPL_CID) && (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) ) { UINT32 dur = osi_time_get_os_boottime_ms() - p_ccb->fcrb.connect_tick_count; diff --git a/components/bt/bluedroid/stack/l2cap/l2c_main.c b/components/bt/bluedroid/stack/l2cap/l2c_main.c index a4c5c7e5a..d52bc1228 100644 --- a/components/bt/bluedroid/stack/l2cap/l2c_main.c +++ b/components/bt/bluedroid/stack/l2cap/l2c_main.c @@ -48,6 +48,8 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len); /********************************************************************************/ #if L2C_DYNAMIC_MEMORY == FALSE tL2C_CB l2cb; +#else +tL2C_CB *l2c_cb_ptr; #endif /******************************************************************************* @@ -815,7 +817,9 @@ void l2c_process_held_packets(BOOLEAN timed_out) void l2c_init (void) { INT16 xx; - +#if L2C_DYNAMIC_MEMORY + l2c_cb_ptr = (tL2C_CB *)osi_malloc(sizeof(tL2C_CB)); +#endif /* #if L2C_DYNAMIC_MEMORY */ memset (&l2cb, 0, sizeof (tL2C_CB)); /* the psm is increased by 2 before being used */ l2cb.dyn_psm = 0xFFF; @@ -874,6 +878,9 @@ void l2c_free(void) { list_free(l2cb.rcv_pending_q); l2cb.rcv_pending_q = NULL; +#if L2C_DYNAMIC_MEMORY + FREE_AND_RESET(l2c_cb_ptr); +#endif } /******************************************************************************* diff --git a/components/bt/bluedroid/stack/l2cap/l2c_utils.c b/components/bt/bluedroid/stack/l2cap/l2c_utils.c index b40146183..119b1f8e6 100644 --- a/components/bt/bluedroid/stack/l2cap/l2c_utils.c +++ b/components/bt/bluedroid/stack/l2cap/l2c_utils.c @@ -54,8 +54,11 @@ tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding, tBT_TRANSPOR for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) { if (!p_lcb->in_use) { + btu_free_timer(&p_lcb->timer_entry); + btu_free_timer(&p_lcb->info_timer_entry); + btu_free_timer(&p_lcb->upda_con_timer); + memset (p_lcb, 0, sizeof (tL2C_LCB)); - memcpy (p_lcb->remote_bd_addr, p_bd_addr, BD_ADDR_LEN); p_lcb->in_use = TRUE; @@ -127,10 +130,14 @@ void l2cu_release_lcb (tL2C_LCB *p_lcb) p_lcb->in_use = FALSE; p_lcb->is_bonding = FALSE; - /* Stop timers */ - btu_stop_timer (&p_lcb->timer_entry); - btu_stop_timer (&p_lcb->info_timer_entry); - + /* Stop and release timers */ + btu_free_timer (&p_lcb->timer_entry); + memset(&p_lcb->timer_entry, 0, sizeof(TIMER_LIST_ENT)); + btu_free_timer (&p_lcb->info_timer_entry); + memset(&p_lcb->info_timer_entry, 0, sizeof(TIMER_LIST_ENT)); + btu_free_timer(&p_lcb->upda_con_timer); + memset(&p_lcb->upda_con_timer, 0, sizeof(TIMER_LIST_ENT)); + /* Release any unfinished L2CAP packet on this link */ if (p_lcb->p_hcit_rcv_acl) { osi_free(p_lcb->p_hcit_rcv_acl); @@ -1476,25 +1483,24 @@ tL2C_CCB *l2cu_allocate_ccb (tL2C_LCB *p_lcb, UINT16 cid) memset (&p_ccb->ertm_info, 0, sizeof(tL2CAP_ERTM_INFO)); p_ccb->peer_cfg_already_rejected = FALSE; p_ccb->fcr_cfg_tries = L2CAP_MAX_FCR_CFG_TRIES; + + /* stop and release timers */ + btu_free_quick_timer(&p_ccb->fcrb.ack_timer); + memset(&p_ccb->fcrb.ack_timer, 0, sizeof(TIMER_LIST_ENT)); p_ccb->fcrb.ack_timer.param = (TIMER_PARAM_TYPE)p_ccb; - - /* if timer is running, remove it from timer list */ - if (p_ccb->fcrb.ack_timer.in_use) { - btu_stop_quick_timer (&p_ccb->fcrb.ack_timer); - } - + + btu_free_quick_timer(&p_ccb->fcrb.mon_retrans_timer); + memset(&p_ccb->fcrb.mon_retrans_timer, 0, sizeof(TIMER_LIST_ENT)); p_ccb->fcrb.mon_retrans_timer.param = (TIMER_PARAM_TYPE)p_ccb; // btla-specific ++ /* CSP408639 Fix: When L2CAP send amp move channel request or receive * L2CEVT_AMP_MOVE_REQ do following sequence. Send channel move * request -> Stop retrans/monitor timer -> Change channel state to CST_AMP_MOVING. */ - if (p_ccb->fcrb.mon_retrans_timer.in_use) { - btu_stop_quick_timer (&p_ccb->fcrb.mon_retrans_timer); - } // btla-specific -- + #if (CLASSIC_BT_INCLUDED == TRUE) - l2c_fcr_stop_timer (p_ccb); + l2c_fcr_free_timer (p_ccb); #endif ///CLASSIC_BT_INCLUDED == TRUE p_ccb->ertm_info.preferred_mode = L2CAP_FCR_BASIC_MODE; /* Default mode for channel is basic mode */ p_ccb->ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_BASIC; /* Default mode for channel is basic mode */ @@ -1531,6 +1537,8 @@ tL2C_CCB *l2cu_allocate_ccb (tL2C_LCB *p_lcb, UINT16 cid) p_ccb->is_flushable = FALSE; #endif + btu_free_timer(&p_ccb->timer_entry); + memset(&p_ccb->timer_entry, 0, sizeof(TIMER_LIST_ENT)); p_ccb->timer_entry.param = (TIMER_PARAM_TYPE)p_ccb; p_ccb->timer_entry.in_use = 0; @@ -1628,9 +1636,8 @@ void l2cu_release_ccb (tL2C_CCB *p_ccb) btm_sec_clr_temp_auth_service (p_lcb->remote_bd_addr); } - /* Stop the timer */ - btu_stop_timer (&p_ccb->timer_entry); - + /* Stop and free the timer */ + btu_free_timer (&p_ccb->timer_entry); fixed_queue_free(p_ccb->xmit_hold_q, osi_free_func); p_ccb->xmit_hold_q = NULL; diff --git a/components/bt/bluedroid/stack/rfcomm/include/rfc_int.h b/components/bt/bluedroid/stack/rfcomm/include/rfc_int.h index 9194583e5..35a706ce2 100644 --- a/components/bt/bluedroid/stack/rfcomm/include/rfc_int.h +++ b/components/bt/bluedroid/stack/rfcomm/include/rfc_int.h @@ -296,8 +296,10 @@ tRFC_MCB *rfc_alloc_multiplexer_channel (BD_ADDR bd_addr, BOOLEAN is_initiator) extern void rfc_release_multiplexer_channel (tRFC_MCB *p_rfc_mcb); extern void rfc_timer_start (tRFC_MCB *p_rfc_mcb, UINT16 timeout); extern void rfc_timer_stop (tRFC_MCB *p_rfc_mcb); +extern void rfc_timer_free (tRFC_MCB *p_rfc_mcb); extern void rfc_port_timer_start (tPORT *p_port, UINT16 tout); extern void rfc_port_timer_stop (tPORT *p_port); +extern void rfc_port_timer_free (tPORT *p_port); BOOLEAN rfc_check_uih_fcs (UINT8 dlci, UINT8 received_fcs); BOOLEAN rfc_check_fcs (UINT16 len, UINT8 *p, UINT8 received_fcs); diff --git a/components/bt/bluedroid/stack/rfcomm/port_api.c b/components/bt/bluedroid/stack/rfcomm/port_api.c index 0cf9e0951..db84fe598 100644 --- a/components/bt/bluedroid/stack/rfcomm/port_api.c +++ b/components/bt/bluedroid/stack/rfcomm/port_api.c @@ -1731,6 +1731,9 @@ int PORT_Test (UINT16 handle, UINT8 *p_data, UINT16 len) *******************************************************************************/ void RFCOMM_Init (void) { +#if (RFC_DYNAMIC_MEMORY) + rfc_cb_ptr = (tRFC_CB *)osi_malloc(sizeof(tRFC_CB)); +#endif /* #if (RFC_DYNAMIC_MEMORY) */ memset (&rfc_cb, 0, sizeof (tRFC_CB)); /* Init RFCOMM control block */ rfc_cb.rfc.last_mux = MAX_BD_CONNECTIONS; diff --git a/components/bt/bluedroid/stack/rfcomm/port_utils.c b/components/bt/bluedroid/stack/rfcomm/port_utils.c index 19e208839..a4f6c05ff 100644 --- a/components/bt/bluedroid/stack/rfcomm/port_utils.c +++ b/components/bt/bluedroid/stack/rfcomm/port_utils.c @@ -265,6 +265,7 @@ void port_release_port (tPORT *p_port) memcpy (p_port->bd_addr, BT_BD_ANY, BD_ADDR_LEN); } else { RFCOMM_TRACE_DEBUG ("port_release_port:Clean-up handle:%d", p_port->inx); + rfc_port_timer_free (p_port); memset (p_port, 0, sizeof (tPORT)); } } @@ -568,4 +569,4 @@ void port_flow_control_peer(tPORT *p_port, BOOLEAN enable, UINT16 count) } -#endif ///(defined RFCOMM_INCLUDED && RFCOMM_INCLUDED == TRUE) \ No newline at end of file +#endif ///(defined RFCOMM_INCLUDED && RFCOMM_INCLUDED == TRUE) diff --git a/components/bt/bluedroid/stack/rfcomm/rfc_port_if.c b/components/bt/bluedroid/stack/rfcomm/rfc_port_if.c index 632f82bb2..d30b9a536 100644 --- a/components/bt/bluedroid/stack/rfcomm/rfc_port_if.c +++ b/components/bt/bluedroid/stack/rfcomm/rfc_port_if.c @@ -36,6 +36,8 @@ #if RFC_DYNAMIC_MEMORY == FALSE tRFC_CB rfc_cb; +#else +tRFC_CB *rfc_cb_ptr; #endif /******************************************************************************* diff --git a/components/bt/bluedroid/stack/rfcomm/rfc_utils.c b/components/bt/bluedroid/stack/rfcomm/rfc_utils.c index 6521fb484..7214ffdd8 100644 --- a/components/bt/bluedroid/stack/rfcomm/rfc_utils.c +++ b/components/bt/bluedroid/stack/rfcomm/rfc_utils.c @@ -169,6 +169,7 @@ tRFC_MCB *rfc_alloc_multiplexer_channel (BD_ADDR bd_addr, BOOLEAN is_initiator) if (rfc_cb.port.rfc_mcb[j].state == RFC_MX_STATE_IDLE) { /* New multiplexer control block */ fixed_queue_free(p_mcb->cmd_q, NULL); + rfc_timer_free(p_mcb); memset (p_mcb, 0, sizeof (tRFC_MCB)); memcpy (p_mcb->bd_addr, bd_addr, BD_ADDR_LEN); RFCOMM_TRACE_DEBUG("rfc_alloc_multiplexer_channel:is_initiator:%d, create new p_mcb:%p, index:%d", @@ -201,7 +202,7 @@ void osi_free_fun(void *p){ void rfc_release_multiplexer_channel (tRFC_MCB *p_mcb) { - rfc_timer_stop (p_mcb); + rfc_timer_free (p_mcb); fixed_queue_free(p_mcb->cmd_q, osi_free_fun); @@ -228,7 +229,6 @@ void rfc_timer_start (tRFC_MCB *p_mcb, UINT16 timeout) btu_start_timer (p_tle, BTU_TTYPE_RFCOMM_MFC, timeout); } - /******************************************************************************* ** ** Function rfc_timer_stop @@ -243,6 +243,20 @@ void rfc_timer_stop (tRFC_MCB *p_mcb) btu_stop_timer (&p_mcb->tle); } +/******************************************************************************* +** +** Function rfc_timer_free +** +** Description Stop and free RFC Timer +** +*******************************************************************************/ +void rfc_timer_free (tRFC_MCB *p_mcb) +{ + RFCOMM_TRACE_EVENT ("rfc_timer_free"); + + btu_free_timer (&p_mcb->tle); + memset(&p_mcb->tle, 0, sizeof(TIMER_LIST_ENT)); +} /******************************************************************************* ** @@ -262,7 +276,6 @@ void rfc_port_timer_start (tPORT *p_port, UINT16 timeout) btu_start_timer (p_tle, BTU_TTYPE_RFCOMM_PORT, timeout); } - /******************************************************************************* ** ** Function rfc_port_timer_stop @@ -274,9 +287,23 @@ void rfc_port_timer_stop (tPORT *p_port) { RFCOMM_TRACE_EVENT ("rfc_port_timer_stop"); - btu_free_timer (&p_port->rfc.tle); + btu_stop_timer (&p_port->rfc.tle); } +/******************************************************************************* +** +** Function rfc_port_timer_free +** +** Description Stop and free RFC Timer +** +*******************************************************************************/ +void rfc_port_timer_free (tPORT *p_port) +{ + RFCOMM_TRACE_EVENT ("rfc_port_timer_stop"); + + btu_free_timer (&p_port->rfc.tle); + memset(&p_port->rfc.tle, 0, sizeof(TIMER_LIST_ENT)); +} /******************************************************************************* ** @@ -480,4 +507,4 @@ void rfc_check_send_cmd(tRFC_MCB *p_mcb, BT_HDR *p_buf) } -#endif ///(defined RFCOMM_INCLUDED && RFCOMM_INCLUDED == TRUE) \ No newline at end of file +#endif ///(defined RFCOMM_INCLUDED && RFCOMM_INCLUDED == TRUE) diff --git a/components/bt/bluedroid/stack/sdp/sdp_main.c b/components/bt/bluedroid/stack/sdp/sdp_main.c index 114eb8b2e..db6a7a3d6 100644 --- a/components/bt/bluedroid/stack/sdp/sdp_main.c +++ b/components/bt/bluedroid/stack/sdp/sdp_main.c @@ -47,6 +47,8 @@ /********************************************************************************/ #if SDP_DYNAMIC_MEMORY == FALSE tSDP_CB sdp_cb; +#else +tSDP_CB *sdp_cb_ptr; #endif /********************************************************************************/ @@ -79,6 +81,9 @@ static void sdp_disconnect_cfm (UINT16 l2cap_cid, UINT16 result); *******************************************************************************/ void sdp_init (void) { +#if SDP_DYNAMIC_MEMORY + sdp_cb_ptr = (tSDP_CB *)osi_malloc(sizeof(tSDP_CB)); +#endif /* #if SDP_DYNAMIC_MEMORY */ /* Clears all structures and local SDP database (if Server is enabled) */ memset (&sdp_cb, 0, sizeof (tSDP_CB)); diff --git a/components/bt/bluedroid/stack/sdp/sdp_utils.c b/components/bt/bluedroid/stack/sdp/sdp_utils.c index 223c27400..271013fca 100644 --- a/components/bt/bluedroid/stack/sdp/sdp_utils.c +++ b/components/bt/bluedroid/stack/sdp/sdp_utils.c @@ -119,6 +119,7 @@ tCONN_CB *sdpu_allocate_ccb (void) /* Look through each connection control block for a free one */ for (xx = 0, p_ccb = sdp_cb.ccb; xx < SDP_MAX_CONNECTIONS; xx++, p_ccb++) { if (p_ccb->con_state == SDP_STATE_IDLE) { + btu_free_timer(&p_ccb->timer_entry); memset (p_ccb, 0, sizeof (tCONN_CB)); p_ccb->timer_entry.param = (UINT32) p_ccb; @@ -143,8 +144,8 @@ tCONN_CB *sdpu_allocate_ccb (void) *******************************************************************************/ void sdpu_release_ccb (tCONN_CB *p_ccb) { - /* Ensure timer is stopped */ - btu_stop_timer (&p_ccb->timer_entry); + /* Ensure timer is stopped and released */ + btu_free_timer(&p_ccb->timer_entry); /* Drop any response pointer we may be holding */ p_ccb->con_state = SDP_STATE_IDLE; diff --git a/components/bt/bluedroid/stack/smp/smp_api.c b/components/bt/bluedroid/stack/smp/smp_api.c index 316f2ba2d..54f676a71 100644 --- a/components/bt/bluedroid/stack/smp/smp_api.c +++ b/components/bt/bluedroid/stack/smp/smp_api.c @@ -36,6 +36,7 @@ #include "btu.h" #include "p_256_ecc_pp.h" +#include "allocator.h" /******************************************************************************* ** @@ -48,6 +49,9 @@ *******************************************************************************/ void SMP_Init(void) { +#if SMP_DYNAMIC_MEMORY + smp_cb_ptr = (tSMP_CB *)osi_malloc(sizeof(tSMP_CB)); +#endif memset(&smp_cb, 0, sizeof(tSMP_CB)); #if defined(SMP_INITIAL_TRACE_LEVEL) @@ -62,6 +66,14 @@ void SMP_Init(void) p_256_init_curve(KEY_LENGTH_DWORDS_P256); } +void SMP_Free(void) +{ + memset(&smp_cb, 0, sizeof(tSMP_CB)); +#if SMP_DYNAMIC_MEMORY + FREE_AND_RESET(smp_cb_ptr); +#endif /* #if SMP_DYNAMIC_MEMORY */ +} + /******************************************************************************* ** diff --git a/components/bt/bluedroid/stack/smp/smp_main.c b/components/bt/bluedroid/stack/smp/smp_main.c index b94ce37e4..1bb0fcb47 100644 --- a/components/bt/bluedroid/stack/smp/smp_main.c +++ b/components/bt/bluedroid/stack/smp/smp_main.c @@ -676,6 +676,8 @@ static const tSMP_ENTRY_TBL smp_entry_table[] = { #if SMP_DYNAMIC_MEMORY == FALSE tSMP_CB smp_cb; +#else +tSMP_CB *smp_cb_ptr; #endif #define SMP_ALL_TBL_MASK 0x80 diff --git a/components/bt/bluedroid/stack/smp/smp_utils.c b/components/bt/bluedroid/stack/smp/smp_utils.c index 05cabc92a..aa596df1d 100644 --- a/components/bt/bluedroid/stack/smp/smp_utils.c +++ b/components/bt/bluedroid/stack/smp/smp_utils.c @@ -974,6 +974,9 @@ void smp_proc_pairing_cmpl(tSMP_CB *p_cb) memcpy (pairing_bda, p_cb->pairing_bda, BD_ADDR_LEN); + if (p_cb->role == HCI_ROLE_SLAVE) { + L2CA_EnableUpdateBleConnParams(p_cb->pairing_bda, TRUE); + } smp_reset_control_value(p_cb); if (p_callback) { diff --git a/components/bt/bluedroid/utils/bt_utils.c b/components/bt/bluedroid/utils/bt_utils.c deleted file mode 100644 index 410b83ea9..000000000 --- a/components/bt/bluedroid/utils/bt_utils.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "bt_utils.h" - - -/***************************************************************************** -** -** Function raise_priority_a2dp -** -** Description Raise task priority for A2DP streaming -** -** Returns void -** -*******************************************************************************/ -void raise_priority_a2dp(tHIGH_PRIORITY_TASK high_task) -{ - (void) high_task; - return; -} - -/***************************************************************************** -** -** Function adjust_priority_a2dp -** -** Description increase the a2dp consumer task priority temporarily when start -** audio playing, to avoid overflow the audio packet queue, restore -** the a2dp consumer task priority when stop audio playing. -** -** Returns void -** -*******************************************************************************/ -void adjust_priority_a2dp(int start) -{ - (void) start; - return; -} diff --git a/components/bt/bluedroid/utils/include/bt_utils.h b/components/bt/bluedroid/utils/include/bt_utils.h deleted file mode 100644 index 39e253551..000000000 --- a/components/bt/bluedroid/utils/include/bt_utils.h +++ /dev/null @@ -1,46 +0,0 @@ -/****************************************************************************** - * - * Copyright (C) 2009-2012 Broadcom Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ******************************************************************************/ - -#ifndef BT_UTILS_H -#define BT_UTILS_H - -// static const char BT_UTILS_MODULE[] = "bt_utils_module"; - -/******************************************************************************* -** Type definitions -********************************************************************************/ - -typedef enum { - TASK_HIGH_MEDIA = 0, - TASK_HIGH_GKI_TIMER, - TASK_HIGH_BTU, - TASK_HIGH_HCI_WORKER, - TASK_HIGH_USERIAL_READ, - TASK_UIPC_READ, - TASK_JAVA_ALARM, - TASK_HIGH_MAX -} tHIGH_PRIORITY_TASK; - -/******************************************************************************* -** Functions -********************************************************************************/ - -void raise_priority_a2dp(tHIGH_PRIORITY_TASK high_task); -void adjust_priority_a2dp(int start); -#define UNUSED(x) (void)(x) -#endif /* BT_UTILS_H */ diff --git a/components/bt/component.mk b/components/bt/component.mk index 4fa144ba0..a2682cdd1 100644 --- a/components/bt/component.mk +++ b/components/bt/component.mk @@ -31,6 +31,7 @@ COMPONENT_ADD_INCLUDEDIRS += bluedroid/bta/include \ bluedroid/osi/include \ bluedroid/utils/include \ bluedroid/external/sbc/decoder/include \ + bluedroid/external/sbc/encoder/include \ bluedroid/btc/core/include \ bluedroid/btc/profile/esp/blufi/include \ bluedroid/btc/profile/esp/include \ @@ -75,6 +76,7 @@ COMPONENT_SRCDIRS += bluedroid/bta/dm \ bluedroid/main \ bluedroid/osi \ bluedroid/external/sbc/decoder/srce \ + bluedroid/external/sbc/encoder/srce \ bluedroid/btc/core \ bluedroid/btc/profile/esp/blufi \ bluedroid/btc/profile/std/gap \ diff --git a/components/bt/lib b/components/bt/lib index 1b59f85d0..0575731e3 160000 --- a/components/bt/lib +++ b/components/bt/lib @@ -1 +1 @@ -Subproject commit 1b59f85d078347916d7f1271858cf29edf320f6d +Subproject commit 0575731e349ca54b46cfc084a752790c1ddcdbba diff --git a/components/console/esp_console.h b/components/console/esp_console.h index bea7eee72..45a10b7a2 100644 --- a/components/console/esp_console.h +++ b/components/console/esp_console.h @@ -13,6 +13,11 @@ // limitations under the License. #pragma once +#ifdef __cplusplus +extern "C" +{ +#endif + #include #include "esp_err.h" @@ -181,3 +186,7 @@ const char *esp_console_get_hint(const char *buf, int *color, int *bold); * - ESP_ERR_INVALID_STATE, if esp_console_init wasn't called */ esp_err_t esp_console_register_help_command(); + +#ifdef __cplusplus +} +#endif diff --git a/components/cxx/test/test_cxx.cpp b/components/cxx/test/test_cxx.cpp index 64bf1f986..91bac6313 100644 --- a/components/cxx/test/test_cxx.cpp +++ b/components/cxx/test/test_cxx.cpp @@ -105,30 +105,35 @@ template<> int SlowInit<2>::mInitBy = -1; template<> int SlowInit<2>::mInitCount = 0; template -static void start_slow_init_task(int id, int affinity) +static int start_slow_init_task(int id, int affinity) { - xTaskCreatePinnedToCore(&SlowInit::task, "slow_init", 2048, - reinterpret_cast(id), 3, NULL, affinity); + return xTaskCreatePinnedToCore(&SlowInit::task, "slow_init", 2048, + reinterpret_cast(id), 3, NULL, affinity) ? 1 : 0; } TEST_CASE("static initialization guards work as expected", "[cxx]") { s_slow_init_sem = xSemaphoreCreateCounting(10, 0); TEST_ASSERT_NOT_NULL(s_slow_init_sem); + int task_count = 0; // four tasks competing for static initialization of one object - start_slow_init_task<1>(0, PRO_CPU_NUM); - start_slow_init_task<1>(1, APP_CPU_NUM); - start_slow_init_task<1>(2, PRO_CPU_NUM); - start_slow_init_task<1>(3, tskNO_AFFINITY); + task_count += start_slow_init_task<1>(0, PRO_CPU_NUM); +#if portNUM_PROCESSORS == 2 + task_count += start_slow_init_task<1>(1, APP_CPU_NUM); +#endif + task_count += start_slow_init_task<1>(2, PRO_CPU_NUM); + task_count += start_slow_init_task<1>(3, tskNO_AFFINITY); // four tasks competing for static initialization of another object - start_slow_init_task<2>(0, PRO_CPU_NUM); - start_slow_init_task<2>(1, APP_CPU_NUM); - start_slow_init_task<2>(2, PRO_CPU_NUM); - start_slow_init_task<2>(3, tskNO_AFFINITY); + task_count += start_slow_init_task<2>(0, PRO_CPU_NUM); +#if portNUM_PROCESSORS == 2 + task_count += start_slow_init_task<2>(1, APP_CPU_NUM); +#endif + task_count += start_slow_init_task<2>(2, PRO_CPU_NUM); + task_count += start_slow_init_task<2>(3, tskNO_AFFINITY); // All tasks should - for (int i = 0; i < 8; ++i) { + for (int i = 0; i < task_count; ++i) { TEST_ASSERT_TRUE(xSemaphoreTake(s_slow_init_sem, 500/portTICK_PERIOD_MS)); } vSemaphoreDelete(s_slow_init_sem); diff --git a/components/driver/adc1_i2s_private.h b/components/driver/adc1_i2s_private.h new file mode 100644 index 000000000..cc592ffe1 --- /dev/null +++ b/components/driver/adc1_i2s_private.h @@ -0,0 +1,73 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _DRIVER_ADC1_I2S_PRIVATE_H_ +#define _DRIVER_ADC1_I2S_PRIVATE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" + + +/** + * @brief Force power on for SAR ADC. + * This function should be called for the scenario in which ADC are controlled by digital function like DMA. + * When the ADC power is always on, RTC FSM can still be functional. + * This is an internal API for I2S module to call to enable I2S-ADC function. + * Note that adc_power_off() can still power down ADC. + */ +void adc_power_always_on(); + +/** + * @brief For I2S dma to claim the usage of ADC1. + * + * Other tasks will be forbidden to use ADC1 between ``adc1_i2s_mode_acquire`` and ``adc1_i2s_release``. + * The I2S module may have to wait for a short time for the current conversion (if exist) to finish. + * + * @return + * - ESP_OK success + * - ESP_ERR_TIMEOUT reserved for future use. Currently the function will wait until success. + */ +esp_err_t adc1_i2s_mode_acquire(); + +/** + * @brief For ADC1 to claim the usage of ADC1. + * + * Other tasks will be forbidden to use ADC1 between ``adc1_adc_mode_acquire`` and ``adc1_i2s_release``. + * The ADC1 may have to wait for some time for the I2S read operation to finish. + * + * @return + * - ESP_OK success + * - ESP_ERR_TIMEOUT reserved for future use. Currently the function will wait until success. + */ +esp_err_t adc1_adc_mode_acquire(); + +/** + * @brief to let other tasks use the ADC1 when I2S is not work. + * + * Other tasks will be forbidden to use ADC1 between ``adc1_adc/i2s_mode_acquire`` and ``adc1_i2s_release``. + * Call this function to release the occupation of ADC1 + * + * @return always return ESP_OK. + */ +esp_err_t adc1_lock_release(); + +#ifdef __cplusplus +} +#endif + +#endif /*_DRIVER_ADC1_I2S_PRIVATE_H_*/ + diff --git a/components/driver/gpio.c b/components/driver/gpio.c index 31f177a77..02c9e64f4 100644 --- a/components/driver/gpio.c +++ b/components/driver/gpio.c @@ -30,46 +30,46 @@ static const char* GPIO_TAG = "gpio"; } const uint32_t GPIO_PIN_MUX_REG[GPIO_PIN_COUNT] = { - GPIO_PIN_REG_0, - GPIO_PIN_REG_1, - GPIO_PIN_REG_2, - GPIO_PIN_REG_3, - GPIO_PIN_REG_4, - GPIO_PIN_REG_5, - GPIO_PIN_REG_6, - GPIO_PIN_REG_7, - GPIO_PIN_REG_8, - GPIO_PIN_REG_9, - GPIO_PIN_REG_10, - GPIO_PIN_REG_11, - GPIO_PIN_REG_12, - GPIO_PIN_REG_13, - GPIO_PIN_REG_14, - GPIO_PIN_REG_15, - GPIO_PIN_REG_16, - GPIO_PIN_REG_17, - GPIO_PIN_REG_18, - GPIO_PIN_REG_19, + IO_MUX_GPIO0_REG, + IO_MUX_GPIO1_REG, + IO_MUX_GPIO2_REG, + IO_MUX_GPIO3_REG, + IO_MUX_GPIO4_REG, + IO_MUX_GPIO5_REG, + IO_MUX_GPIO6_REG, + IO_MUX_GPIO7_REG, + IO_MUX_GPIO8_REG, + IO_MUX_GPIO9_REG, + IO_MUX_GPIO10_REG, + IO_MUX_GPIO11_REG, + IO_MUX_GPIO12_REG, + IO_MUX_GPIO13_REG, + IO_MUX_GPIO14_REG, + IO_MUX_GPIO15_REG, + IO_MUX_GPIO16_REG, + IO_MUX_GPIO17_REG, + IO_MUX_GPIO18_REG, + IO_MUX_GPIO19_REG, 0, - GPIO_PIN_REG_21, - GPIO_PIN_REG_22, - GPIO_PIN_REG_23, + IO_MUX_GPIO21_REG, + IO_MUX_GPIO22_REG, + IO_MUX_GPIO23_REG, 0, - GPIO_PIN_REG_25, - GPIO_PIN_REG_26, - GPIO_PIN_REG_27, + IO_MUX_GPIO25_REG, + IO_MUX_GPIO26_REG, + IO_MUX_GPIO27_REG, 0, 0, 0, 0, - GPIO_PIN_REG_32, - GPIO_PIN_REG_33, - GPIO_PIN_REG_34, - GPIO_PIN_REG_35, - GPIO_PIN_REG_36, - GPIO_PIN_REG_37, - GPIO_PIN_REG_38, - GPIO_PIN_REG_39 + IO_MUX_GPIO32_REG, + IO_MUX_GPIO33_REG, + IO_MUX_GPIO34_REG, + IO_MUX_GPIO35_REG, + IO_MUX_GPIO36_REG, + IO_MUX_GPIO37_REG, + IO_MUX_GPIO38_REG, + IO_MUX_GPIO39_REG, }; typedef struct { diff --git a/components/driver/i2s.c b/components/driver/i2s.c index dcb64b58b..b6d01c19c 100644 --- a/components/driver/i2s.c +++ b/components/driver/i2s.c @@ -31,6 +31,7 @@ #include "driver/i2s.h" #include "driver/rtc_io.h" #include "driver/dac.h" +#include "adc1_i2s_private.h" #include "esp_intr.h" #include "esp_err.h" @@ -83,12 +84,14 @@ typedef struct { int bits_per_sample; /*!< Bits per sample*/ i2s_mode_t mode; /*!< I2S Working mode*/ int use_apll; /*!< I2S use APLL clock */ + uint32_t sample_rate; /*!< I2S sample rate */ } i2s_obj_t; static i2s_obj_t *p_i2s_obj[I2S_NUM_MAX] = {0}; static i2s_dev_t* I2S[I2S_NUM_MAX] = {&I2S0, &I2S1}; static portMUX_TYPE i2s_spinlock[I2S_NUM_MAX] = {portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED}; - +static int _i2s_adc_unit = -1; +static int _i2s_adc_channel = -1; /** * @brief Pre define APLL parameters, save compute time * | bits_per_sample | rate | sdm0 | sdm1 | sdm2 | odir @@ -321,12 +324,11 @@ esp_err_t i2s_set_clk(i2s_port_t i2s_num, uint32_t rate, i2s_bits_per_sample_t b return ESP_ERR_INVALID_ARG; } - if (p_i2s_obj[i2s_num] == NULL) { ESP_LOGE(I2S_TAG, "Not initialized yet"); return ESP_FAIL; } - + p_i2s_obj[i2s_num]->sample_rate = rate; double clkmdiv = (double)I2S_BASE_CLK / (rate * factor); if (clkmdiv > 256) { @@ -645,6 +647,18 @@ esp_err_t i2s_start(i2s_port_t i2s_num) { //start DMA link I2S_ENTER_CRITICAL(); + i2s_reset_fifo(i2s_num); + //reset dma + I2S[i2s_num]->lc_conf.in_rst = 1; + I2S[i2s_num]->lc_conf.in_rst = 0; + I2S[i2s_num]->lc_conf.out_rst = 1; + I2S[i2s_num]->lc_conf.out_rst = 0; + + I2S[i2s_num]->conf.tx_reset = 1; + I2S[i2s_num]->conf.tx_reset = 0; + I2S[i2s_num]->conf.rx_reset = 1; + I2S[i2s_num]->conf.rx_reset = 0; + esp_intr_disable(p_i2s_obj[i2s_num]->i2s_isr_handle); I2S[i2s_num]->int_clr.val = 0xFFFFFFFF; if (p_i2s_obj[i2s_num]->mode & I2S_MODE_TX) { @@ -677,17 +691,6 @@ esp_err_t i2s_stop(i2s_port_t i2s_num) i2s_disable_rx_intr(i2s_num); } I2S[i2s_num]->int_clr.val = I2S[i2s_num]->int_st.val; //clear pending interrupt - i2s_reset_fifo(i2s_num); - //reset dma - I2S[i2s_num]->lc_conf.in_rst = 1; - I2S[i2s_num]->lc_conf.in_rst = 0; - I2S[i2s_num]->lc_conf.out_rst = 1; - I2S[i2s_num]->lc_conf.out_rst = 0; - - I2S[i2s_num]->conf.tx_reset = 1; - I2S[i2s_num]->conf.tx_reset = 0; - I2S[i2s_num]->conf.rx_reset = 1; - I2S[i2s_num]->conf.rx_reset = 0; I2S_EXIT_CRITICAL(); return 0; } @@ -714,10 +717,18 @@ esp_err_t i2s_set_dac_mode(i2s_dac_mode_t dac_mode) return ESP_OK; } +static esp_err_t _i2s_adc_mode_recover() +{ + I2S_CHECK(((_i2s_adc_unit != -1) && (_i2s_adc_channel != -1)), "i2s ADC recover error, not initialized...", ESP_ERR_INVALID_ARG); + return adc_i2s_mode_init(_i2s_adc_unit, _i2s_adc_channel); +} + esp_err_t i2s_set_adc_mode(adc_unit_t adc_unit, adc1_channel_t adc_channel) { I2S_CHECK((adc_unit < ADC_UNIT_2), "i2s ADC unit error, only support ADC1 for now", ESP_ERR_INVALID_ARG); // For now, we only support SAR ADC1. + _i2s_adc_unit = adc_unit; + _i2s_adc_channel = adc_channel; return adc_i2s_mode_init(adc_unit, adc_channel); } @@ -856,7 +867,7 @@ static esp_err_t i2s_param_config(i2s_port_t i2s_num, const i2s_config_t *i2s_co //initialize the specific ADC channel. //in the current stage, we only support ADC1 and single channel mode. //In default data mode, the ADC data is in 12-bit resolution mode. - adc_power_on(); + adc_power_always_on(); } // configure I2S data port interface. i2s_reset_fifo(i2s_num); @@ -1144,6 +1155,27 @@ int i2s_write_bytes(i2s_port_t i2s_num, const char *src, size_t size, TickType_t return bytes_writen; } +esp_err_t i2s_adc_enable(i2s_port_t i2s_num) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_ERR_INVALID_ARG); + I2S_CHECK((p_i2s_obj[i2s_num] != NULL), "Not initialized yet", ESP_ERR_INVALID_STATE); + I2S_CHECK((p_i2s_obj[i2s_num]->mode & I2S_MODE_ADC_BUILT_IN), "i2s built-in adc not enabled", ESP_ERR_INVALID_STATE); + + adc1_i2s_mode_acquire(); + _i2s_adc_mode_recover(); + return i2s_set_clk(i2s_num, p_i2s_obj[i2s_num]->sample_rate, p_i2s_obj[i2s_num]->bits_per_sample, p_i2s_obj[i2s_num]->channel_num); +} + +esp_err_t i2s_adc_disable(i2s_port_t i2s_num) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_ERR_INVALID_ARG); + I2S_CHECK((p_i2s_obj[i2s_num] != NULL), "Not initialized yet", ESP_ERR_INVALID_STATE); + I2S_CHECK((p_i2s_obj[i2s_num]->mode & I2S_MODE_ADC_BUILT_IN), "i2s built-in adc not enabled", ESP_ERR_INVALID_STATE); + + adc1_lock_release(); + return ESP_OK; +} + int i2s_read_bytes(i2s_port_t i2s_num, char* dest, size_t size, TickType_t ticks_to_wait) { char *data_ptr; diff --git a/components/driver/include/driver/adc.h b/components/driver/include/driver/adc.h index 9d26c5390..ccbe85014 100644 --- a/components/driver/include/driver/adc.h +++ b/components/driver/include/driver/adc.h @@ -204,12 +204,13 @@ int adc1_get_voltage(adc1_channel_t channel) __attribute__((deprecated)); /** @endcond */ /** - * @brief Power on SAR ADC + * @brief Enable ADC power */ void adc_power_on(); /** * @brief Power off SAR ADC + * This function will force power down for ADC */ void adc_power_off(); diff --git a/components/driver/include/driver/gpio.h b/components/driver/include/driver/gpio.h index 8cc65054b..7cace8836 100644 --- a/components/driver/include/driver/gpio.h +++ b/components/driver/include/driver/gpio.h @@ -73,41 +73,41 @@ extern "C" { #define GPIO_SEL_38 ((uint64_t)(((uint64_t)1)<<38)) /*!< Pin 38 selected */ #define GPIO_SEL_39 ((uint64_t)(((uint64_t)1)<<39)) /*!< Pin 39 selected */ -#define GPIO_PIN_REG_0 PERIPHS_IO_MUX_GPIO0_U -#define GPIO_PIN_REG_1 PERIPHS_IO_MUX_U0TXD_U -#define GPIO_PIN_REG_2 PERIPHS_IO_MUX_GPIO2_U -#define GPIO_PIN_REG_3 PERIPHS_IO_MUX_U0RXD_U -#define GPIO_PIN_REG_4 PERIPHS_IO_MUX_GPIO4_U -#define GPIO_PIN_REG_5 PERIPHS_IO_MUX_GPIO5_U -#define GPIO_PIN_REG_6 PERIPHS_IO_MUX_SD_CLK_U -#define GPIO_PIN_REG_7 PERIPHS_IO_MUX_SD_DATA0_U -#define GPIO_PIN_REG_8 PERIPHS_IO_MUX_SD_DATA1_U -#define GPIO_PIN_REG_9 PERIPHS_IO_MUX_SD_DATA2_U -#define GPIO_PIN_REG_10 PERIPHS_IO_MUX_SD_DATA3_U -#define GPIO_PIN_REG_11 PERIPHS_IO_MUX_SD_CMD_U -#define GPIO_PIN_REG_12 PERIPHS_IO_MUX_MTDI_U -#define GPIO_PIN_REG_13 PERIPHS_IO_MUX_MTCK_U -#define GPIO_PIN_REG_14 PERIPHS_IO_MUX_MTMS_U -#define GPIO_PIN_REG_15 PERIPHS_IO_MUX_MTDO_U -#define GPIO_PIN_REG_16 PERIPHS_IO_MUX_GPIO16_U -#define GPIO_PIN_REG_17 PERIPHS_IO_MUX_GPIO17_U -#define GPIO_PIN_REG_18 PERIPHS_IO_MUX_GPIO18_U -#define GPIO_PIN_REG_19 PERIPHS_IO_MUX_GPIO19_U -#define GPIO_PIN_REG_20 PERIPHS_IO_MUX_GPIO20_U -#define GPIO_PIN_REG_21 PERIPHS_IO_MUX_GPIO21_U -#define GPIO_PIN_REG_22 PERIPHS_IO_MUX_GPIO22_U -#define GPIO_PIN_REG_23 PERIPHS_IO_MUX_GPIO23_U -#define GPIO_PIN_REG_25 PERIPHS_IO_MUX_GPIO25_U -#define GPIO_PIN_REG_26 PERIPHS_IO_MUX_GPIO26_U -#define GPIO_PIN_REG_27 PERIPHS_IO_MUX_GPIO27_U -#define GPIO_PIN_REG_32 PERIPHS_IO_MUX_GPIO32_U -#define GPIO_PIN_REG_33 PERIPHS_IO_MUX_GPIO33_U -#define GPIO_PIN_REG_34 PERIPHS_IO_MUX_GPIO34_U -#define GPIO_PIN_REG_35 PERIPHS_IO_MUX_GPIO35_U -#define GPIO_PIN_REG_36 PERIPHS_IO_MUX_GPIO36_U -#define GPIO_PIN_REG_37 PERIPHS_IO_MUX_GPIO37_U -#define GPIO_PIN_REG_38 PERIPHS_IO_MUX_GPIO38_U -#define GPIO_PIN_REG_39 PERIPHS_IO_MUX_GPIO39_U +#define GPIO_PIN_REG_0 IO_MUX_GPIO0_REG +#define GPIO_PIN_REG_1 IO_MUX_GPIO1_REG +#define GPIO_PIN_REG_2 IO_MUX_GPIO2_REG +#define GPIO_PIN_REG_3 IO_MUX_GPIO3_REG +#define GPIO_PIN_REG_4 IO_MUX_GPIO4_REG +#define GPIO_PIN_REG_5 IO_MUX_GPIO5_REG +#define GPIO_PIN_REG_6 IO_MUX_GPIO6_REG +#define GPIO_PIN_REG_7 IO_MUX_GPIO7_REG +#define GPIO_PIN_REG_8 IO_MUX_GPIO8_REG +#define GPIO_PIN_REG_9 IO_MUX_GPIO9_REG +#define GPIO_PIN_REG_10 IO_MUX_GPIO10_REG +#define GPIO_PIN_REG_11 IO_MUX_GPIO11_REG +#define GPIO_PIN_REG_12 IO_MUX_GPIO12_REG +#define GPIO_PIN_REG_13 IO_MUX_GPIO13_REG +#define GPIO_PIN_REG_14 IO_MUX_GPIO14_REG +#define GPIO_PIN_REG_15 IO_MUX_GPIO15_REG +#define GPIO_PIN_REG_16 IO_MUX_GPIO16_REG +#define GPIO_PIN_REG_17 IO_MUX_GPIO17_REG +#define GPIO_PIN_REG_18 IO_MUX_GPIO18_REG +#define GPIO_PIN_REG_19 IO_MUX_GPIO19_REG +#define GPIO_PIN_REG_20 IO_MUX_GPIO20_REG +#define GPIO_PIN_REG_21 IO_MUX_GPIO21_REG +#define GPIO_PIN_REG_22 IO_MUX_GPIO22_REG +#define GPIO_PIN_REG_23 IO_MUX_GPIO23_REG +#define GPIO_PIN_REG_25 IO_MUX_GPIO25_REG +#define GPIO_PIN_REG_26 IO_MUX_GPIO26_REG +#define GPIO_PIN_REG_27 IO_MUX_GPIO27_REG +#define GPIO_PIN_REG_32 IO_MUX_GPIO32_REG +#define GPIO_PIN_REG_33 IO_MUX_GPIO33_REG +#define GPIO_PIN_REG_34 IO_MUX_GPIO34_REG +#define GPIO_PIN_REG_35 IO_MUX_GPIO35_REG +#define GPIO_PIN_REG_36 IO_MUX_GPIO36_REG +#define GPIO_PIN_REG_37 IO_MUX_GPIO37_REG +#define GPIO_PIN_REG_38 IO_MUX_GPIO38_REG +#define GPIO_PIN_REG_39 IO_MUX_GPIO39_REG #define GPIO_APP_CPU_INTR_ENA (BIT(0)) #define GPIO_APP_CPU_NMI_INTR_ENA (BIT(1)) diff --git a/components/driver/include/driver/i2s.h b/components/driver/include/driver/i2s.h index 3644a831f..f8f6b2a7c 100644 --- a/components/driver/include/driver/i2s.h +++ b/components/driver/include/driver/i2s.h @@ -293,6 +293,8 @@ int i2s_write_bytes(i2s_port_t i2s_num, const char *src, size_t size, TickType_t * * Format of the data in source buffer is determined by the I2S * configuration (see i2s_config_t). + * @note If the built-in ADC mode is enabled, we should call i2s_adc_start and i2s_adc_stop around the whole reading process, + * to prevent the data getting corrupted. * * @return Number of bytes read, or ESP_FAIL (-1) for parameter error. If a timeout occurred, bytes read will be less than total size. */ @@ -417,6 +419,31 @@ esp_err_t i2s_set_clk(i2s_port_t i2s_num, uint32_t rate, i2s_bits_per_sample_t b */ esp_err_t i2s_set_adc_mode(adc_unit_t adc_unit, adc1_channel_t adc_channel); +/** + * @brief Start to use I2S built-in ADC mode + * @note This function would acquire the lock of ADC to prevent the data getting corrupted + * during the I2S peripheral is being used to do fully continuous ADC sampling. + * + * @param i2s_num i2s port index + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE driver state error + * - ESP_FAIL Internal driver error + */ +esp_err_t i2s_adc_enable(i2s_port_t i2s_num); + +/** + * @brief Stop to use I2S built-in ADC mode + * @param i2s_num i2s port index + * @note This function would release the lock of ADC so that other tasks can use ADC. + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE driver state error + */ +esp_err_t i2s_adc_disable(i2s_port_t i2s_num); + #ifdef __cplusplus } #endif diff --git a/components/driver/include/driver/sdmmc_host.h b/components/driver/include/driver/sdmmc_host.h index 63023e903..c298889ed 100644 --- a/components/driver/include/driver/sdmmc_host.h +++ b/components/driver/include/driver/sdmmc_host.h @@ -39,6 +39,7 @@ extern "C" { .io_voltage = 3.3f, \ .init = &sdmmc_host_init, \ .set_bus_width = &sdmmc_host_set_bus_width, \ + .get_bus_width = &sdmmc_host_get_slot_width, \ .set_card_clk = &sdmmc_host_set_card_clk, \ .do_transaction = &sdmmc_host_do_transaction, \ .deinit = &sdmmc_host_deinit, \ @@ -115,6 +116,14 @@ esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config) */ esp_err_t sdmmc_host_set_bus_width(int slot, size_t width); +/** + * @brief Get bus width configured in ``sdmmc_host_init_slot`` to be used for data transfer + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @return configured bus width of the specified slot. + */ +size_t sdmmc_host_get_slot_width(int slot); + /** * @brief Set card clock frequency * diff --git a/components/driver/include/driver/sdmmc_types.h b/components/driver/include/driver/sdmmc_types.h index 835eaa3fb..cece4174e 100644 --- a/components/driver/include/driver/sdmmc_types.h +++ b/components/driver/include/driver/sdmmc_types.h @@ -125,6 +125,7 @@ typedef struct { float io_voltage; /*!< I/O voltage used by the controller (voltage switching is not supported) */ esp_err_t (*init)(void); /*!< Host function to initialize the driver */ esp_err_t (*set_bus_width)(int slot, size_t width); /*!< host function to set bus width */ + size_t (*get_bus_width)(int slot); /*!< host function to get bus width */ esp_err_t (*set_card_clk)(int slot, uint32_t freq_khz); /*!< host function to set card clock frequency */ esp_err_t (*do_transaction)(int slot, sdmmc_command_t* cmdinfo); /*!< host function to do a transaction */ esp_err_t (*deinit)(void); /*!< host function to deinitialize the driver */ diff --git a/components/driver/include/driver/spi_master.h b/components/driver/include/driver/spi_master.h index 2e17e8569..10df00507 100644 --- a/components/driver/include/driver/spi_master.h +++ b/components/driver/include/driver/spi_master.h @@ -30,7 +30,7 @@ extern "C" #define SPI_DEVICE_TXBIT_LSBFIRST (1<<0) ///< Transmit command/address/data LSB first instead of the default MSB first #define SPI_DEVICE_RXBIT_LSBFIRST (1<<1) ///< Receive data LSB first instead of the default MSB first -#define SPI_DEVICE_BIT_LSBFIRST (SPI_DEVICE_TXBIT_LSBFIRST|SPI_DEVICE_RXBIT_LSBFIRST); ///< Transmit and receive LSB first +#define SPI_DEVICE_BIT_LSBFIRST (SPI_DEVICE_TXBIT_LSBFIRST|SPI_DEVICE_RXBIT_LSBFIRST) ///< Transmit and receive LSB first #define SPI_DEVICE_3WIRE (1<<2) ///< Use MOSI (=spid) for both sending and receiving data #define SPI_DEVICE_POSITIVE_CS (1<<3) ///< Make CS positive during a transaction instead of negative #define SPI_DEVICE_HALFDUPLEX (1<<4) ///< Transmit data before receiving it, instead of simultaneously @@ -62,11 +62,11 @@ typedef struct { #define SPI_TRANS_MODE_DIO (1<<0) ///< Transmit/receive data in 2-bit mode #define SPI_TRANS_MODE_QIO (1<<1) ///< Transmit/receive data in 4-bit mode -#define SPI_TRANS_MODE_DIOQIO_ADDR (1<<4) ///< Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO #define SPI_TRANS_USE_RXDATA (1<<2) ///< Receive into rx_data member of spi_transaction_t instead into memory at rx_buffer. #define SPI_TRANS_USE_TXDATA (1<<3) ///< Transmit tx_data member of spi_transaction_t instead of data at tx_buffer. Do not set tx_buffer when using this. -#define SPI_TRANS_VARIABLE_CMD (1<<4) ///< Use the ``command_bits`` in ``spi_transaction_ext_t`` rather than default value in ``spi_device_interface_config_t``. -#define SPI_TRANS_VARIABLE_ADDR (1<<5) ///< Use the ``address_bits`` in ``spi_transaction_ext_t`` rather than default value in ``spi_device_interface_config_t``. +#define SPI_TRANS_MODE_DIOQIO_ADDR (1<<4) ///< Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO +#define SPI_TRANS_VARIABLE_CMD (1<<5) ///< Use the ``command_bits`` in ``spi_transaction_ext_t`` rather than default value in ``spi_device_interface_config_t``. +#define SPI_TRANS_VARIABLE_ADDR (1<<6) ///< Use the ``address_bits`` in ``spi_transaction_ext_t`` rather than default value in ``spi_device_interface_config_t``. /** * This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes. diff --git a/components/driver/include/driver/spi_slave.h b/components/driver/include/driver/spi_slave.h index ed12cb62d..1d5ea3402 100644 --- a/components/driver/include/driver/spi_slave.h +++ b/components/driver/include/driver/spi_slave.h @@ -30,7 +30,7 @@ extern "C" #define SPI_SLAVE_TXBIT_LSBFIRST (1<<0) ///< Transmit command/address/data LSB first instead of the default MSB first #define SPI_SLAVE_RXBIT_LSBFIRST (1<<1) ///< Receive data LSB first instead of the default MSB first -#define SPI_SLAVE_BIT_LSBFIRST (SPI_TXBIT_LSBFIRST|SPI_RXBIT_LSBFIRST); ///< Transmit and receive LSB first +#define SPI_SLAVE_BIT_LSBFIRST (SPI_SLAVE_TXBIT_LSBFIRST|SPI_SLAVE_RXBIT_LSBFIRST) ///< Transmit and receive LSB first typedef struct spi_slave_transaction_t spi_slave_transaction_t; @@ -163,4 +163,4 @@ esp_err_t spi_slave_transmit(spi_host_device_t host, spi_slave_transaction_t *tr } #endif -#endif \ No newline at end of file +#endif diff --git a/components/driver/include/driver/uart.h b/components/driver/include/driver/uart.h index 49e058740..273b7869f 100644 --- a/components/driver/include/driver/uart.h +++ b/components/driver/include/driver/uart.h @@ -618,8 +618,10 @@ int uart_write_bytes_with_break(uart_port_t uart_num, const char* src, size_t si int uart_read_bytes(uart_port_t uart_num, uint8_t* buf, uint32_t length, TickType_t ticks_to_wait); /** - * @brief UART ring buffer flush. This will discard all data in the UART RX buffer. - * + * @brief Alias of uart_flush_input. + * UART ring buffer flush. This will discard all data in the UART RX buffer. + * @note Instead of waiting the data sent out, this function will clear UART rx buffer. + * In order to send all the data in tx FIFO, we can use uart_wait_tx_done function. * @param uart_num UART_NUM_0, UART_NUM_1 or UART_NUM_2 * * @return @@ -629,8 +631,18 @@ int uart_read_bytes(uart_port_t uart_num, uint8_t* buf, uint32_t length, TickTyp esp_err_t uart_flush(uart_port_t uart_num); /** - * @brief UART get RX ring buffer cached data length + * @brief Clear input buffer, discard all the data is in the ring-buffer. + * @note In order to send all the data in tx FIFO, we can use uart_wait_tx_done function. + * @param uart_num UART_NUM_0, UART_NUM_1 or UART_NUM_2 * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t uart_flush_input(uart_port_t uart_num); + +/** + * @brief UART get RX ring buffer cached data length * @param uart_num UART port number. * @param size Pointer of size_t to accept cached data length * @@ -671,6 +683,39 @@ esp_err_t uart_disable_pattern_det_intr(uart_port_t uart_num); */ esp_err_t uart_enable_pattern_det_intr(uart_port_t uart_num, char pattern_chr, uint8_t chr_num, int chr_tout, int post_idle, int pre_idle); +/** + * @brief Return the nearest detected pattern position in buffer. + * The positions of the detected pattern are saved in a queue, + * this function will dequeue the first pattern position and move the pointer to next pattern position. + * @note If the RX buffer is full and flow control is not enabled, + * the detected pattern may not be found in the rx buffer due to overflow. + * + * The following APIs will modify the pattern position info: + * uart_flush_input, uart_read_bytes, uart_driver_delete, uart_pop_pattern_pos + * It is the application's responsibility to ensure atomic access to the pattern queue and the rx data buffer + * when using pattern detect feature. + * + * @param uart_num UART port number + * @return + * - (-1) No pattern found for current index or parameter error + * - others the pattern position in rx buffer. + */ +int uart_pattern_pop_pos(uart_port_t uart_num); + +/** + * @brief Allocate a new memory with the given length to save record the detected pattern position in rx buffer. + * @param uart_num UART port number + * @param queue_length Max queue length for the detected pattern. + * If the queue length is not large enough, some pattern positions might be lost. + * Set this value to the maximum number of patterns that could be saved in data buffer at the same time. + * @return + * - ESP_ERR_NO_MEM No enough memory + * - ESP_ERR_INVALID_STATE Driver not installed + * - ESP_FAIL Parameter error + * - ESP_OK Success + */ +esp_err_t uart_pattern_queue_reset(uart_port_t uart_num, int queue_length); + #ifdef __cplusplus } #endif diff --git a/components/driver/rtc_module.c b/components/driver/rtc_module.c index a04282b80..9a6b6faf1 100644 --- a/components/driver/rtc_module.c +++ b/components/driver/rtc_module.c @@ -34,6 +34,7 @@ #include "sys/lock.h" #include "driver/rtc_cntl.h" #include "driver/gpio.h" +#include "adc1_i2s_private.h" #ifndef NDEBUG // Enable built-in checks in queue.h in debug builds @@ -99,6 +100,9 @@ static _lock_t adc2_wifi_lock = NULL; //prevent ADC2 being used by tasks (regardless of WIFI) portMUX_TYPE adc2_spinlock = portMUX_INITIALIZER_UNLOCKED; +//prevent ADC1 being used by I2S dma and other tasks at the same time. +static _lock_t adc1_i2s_lock = NULL; + typedef struct { TimerHandle_t timer; uint32_t filtered_val[TOUCH_PAD_MAX]; @@ -108,12 +112,6 @@ typedef struct { } touch_pad_filter_t; static touch_pad_filter_t *s_touch_pad_filter = NULL; -typedef enum { - ADC_FORCE_FSM = 0x0, - ADC_FORCE_DISABLE = 0x2, - ADC_FORCE_ENABLE = 0x3, -} adc_force_mode_t; - //Reg,Mux,Fun,IE,Up,Down,Rtc_number const rtc_gpio_desc_t rtc_gpio_desc[GPIO_PIN_COUNT] = { {RTC_IO_TOUCH_PAD1_REG, RTC_IO_TOUCH_PAD1_MUX_SEL_M, RTC_IO_TOUCH_PAD1_FUN_SEL_S, RTC_IO_TOUCH_PAD1_FUN_IE_M, RTC_IO_TOUCH_PAD1_RUE_M, RTC_IO_TOUCH_PAD1_RDE_M, RTC_IO_TOUCH_PAD1_SLP_SEL_M, RTC_IO_TOUCH_PAD1_SLP_IE_M, RTC_IO_TOUCH_PAD1_HOLD_M, RTC_CNTL_TOUCH_PAD1_HOLD_FORCE_M, RTC_IO_TOUCH_PAD1_DRV_V, RTC_IO_TOUCH_PAD1_DRV_S, RTCIO_GPIO0_CHANNEL}, //0 @@ -1025,10 +1023,21 @@ static esp_err_t adc_set_atten(adc_unit_t adc_unit, adc_channel_t channel, adc_a return ESP_OK; } +void adc_power_always_on() +{ + portENTER_CRITICAL(&rtc_spinlock); + SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PU; + portEXIT_CRITICAL(&rtc_spinlock); +} + void adc_power_on() { portENTER_CRITICAL(&rtc_spinlock); - SENS.sar_meas_wait2.force_xpd_sar = ADC_FORCE_FSM; + if (SENS.sar_meas_wait2.force_xpd_sar & SENS_FORCE_XPD_SAR_SW_M) { + SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PU; + } else { + SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_FSM; + } portEXIT_CRITICAL(&rtc_spinlock); } @@ -1037,7 +1046,7 @@ void adc_power_off() portENTER_CRITICAL(&rtc_spinlock); //Bit1 0:Fsm 1: SW mode //Bit0 0:SW mode power down 1: SW mode power on - SENS.sar_meas_wait2.force_xpd_sar = ADC_FORCE_DISABLE; + SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PD; portEXIT_CRITICAL(&rtc_spinlock); } @@ -1161,7 +1170,7 @@ esp_err_t adc_i2s_mode_init(adc_unit_t adc_unit, adc_channel_t channel) uint8_t table_len = 1; //POWER ON SAR - adc_power_on(); + adc_power_always_on(); adc_gpio_init(adc_unit, channel); adc_set_i2s_data_len(adc_unit, table_len); adc_set_i2s_data_pattern(adc_unit, 0, channel, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11); @@ -1246,12 +1255,55 @@ esp_err_t adc1_config_width(adc_bits_width_t width_bit) return ESP_OK; } +esp_err_t adc1_i2s_mode_acquire() +{ + //lazy initialization + //for i2s, block until acquire the lock + _lock_acquire( &adc1_i2s_lock ); + ESP_LOGD( RTC_MODULE_TAG, "i2s mode takes adc1 lock." ); + portENTER_CRITICAL(&rtc_spinlock); + SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PU; + //switch SARADC into DIG channel + SENS.sar_read_ctrl.sar1_dig_force = 1; + portEXIT_CRITICAL(&rtc_spinlock); + return ESP_OK; +} + +esp_err_t adc1_adc_mode_acquire() +{ + //lazy initialization + //for adc1, block until acquire the lock + _lock_acquire( &adc1_i2s_lock ); + ESP_LOGD( RTC_MODULE_TAG, "adc mode takes adc1 lock." ); + portENTER_CRITICAL(&rtc_spinlock); + // for now the WiFi would use ADC2 and set xpd_sar force on. + // so we can not reset xpd_sar to fsm mode directly. + // We should handle this after the synchronization mechanism is established. + + //switch SARADC into RTC channel + SENS.sar_read_ctrl.sar1_dig_force = 0; + portEXIT_CRITICAL(&rtc_spinlock); + return ESP_OK; +} + +esp_err_t adc1_lock_release() +{ + RTC_MODULE_CHECK((uint32_t*)adc1_i2s_lock != NULL, "adc1 lock release called before acquire", ESP_ERR_INVALID_STATE ); + // for now the WiFi would use ADC2 and set xpd_sar force on. + // so we can not reset xpd_sar to fsm mode directly. + // We should handle this after the synchronization mechanism is established. + + _lock_release( &adc1_i2s_lock ); + ESP_LOGD( RTC_MODULE_TAG, "returns adc1 lock." ); + return ESP_OK; +} + int adc1_get_raw(adc1_channel_t channel) { uint16_t adc_value; RTC_MODULE_CHECK(channel < ADC1_CHANNEL_MAX, "ADC Channel Err", ESP_ERR_INVALID_ARG); - - adc_power_on(); + adc1_adc_mode_acquire(); + adc_power_on(); portENTER_CRITICAL(&rtc_spinlock); //Adc Controler is Rtc module,not ulp coprocessor @@ -1274,6 +1326,7 @@ int adc1_get_raw(adc1_channel_t channel) while (SENS.sar_meas_start1.meas1_done_sar == 0); adc_value = SENS.sar_meas_start1.meas1_data_sar; portEXIT_CRITICAL(&rtc_spinlock); + adc1_lock_release(); return adc_value; } @@ -1467,6 +1520,8 @@ esp_err_t adc2_vref_to_gpio(gpio_num_t gpio) rtc_gpio_input_disable(gpio); rtc_gpio_pullup_dis(gpio); rtc_gpio_pulldown_dis(gpio); + //force fsm + adc_power_always_on(); //Select power source of ADC RTCCNTL.bias_conf.dbg_atten = 0; //Check DBG effect outside sleep mode //set dtest (MUX_SEL : 0 -> RTC; 1-> vdd_sar2) @@ -1475,8 +1530,6 @@ esp_err_t adc2_vref_to_gpio(gpio_num_t gpio) RTCCNTL.test_mux.ent_rtc = 1; //set sar2_en_test SENS.sar_start_force.sar2_en_test = 1; - //force fsm - SENS.sar_meas_wait2.force_xpd_sar = ADC_FORCE_ENABLE; //Select power source of ADC //set sar2 en force SENS.sar_meas_start2.sar2_en_pad_force = 1; //Pad bitmap controlled by SW //set en_pad for channels 7,8,9 (bits 0x380) diff --git a/components/driver/sdmmc_host.c b/components/driver/sdmmc_host.c index e233df0a5..4c297a58f 100644 --- a/components/driver/sdmmc_host.c +++ b/components/driver/sdmmc_host.c @@ -40,6 +40,7 @@ typedef struct { uint32_t d5; uint32_t d6; uint32_t d7; + uint8_t d3_gpio; uint8_t card_detect; uint8_t write_protect; uint8_t width; @@ -57,6 +58,7 @@ static const sdmmc_slot_info_t s_slot_info[2] = { .d1 = PERIPHS_IO_MUX_SD_DATA1_U, .d2 = PERIPHS_IO_MUX_SD_DATA2_U, .d3 = PERIPHS_IO_MUX_SD_DATA3_U, + .d3_gpio = 10, .d4 = PERIPHS_IO_MUX_GPIO16_U, .d5 = PERIPHS_IO_MUX_GPIO17_U, .d6 = PERIPHS_IO_MUX_GPIO5_U, @@ -72,6 +74,7 @@ static const sdmmc_slot_info_t s_slot_info[2] = { .d1 = PERIPHS_IO_MUX_GPIO4_U, .d2 = PERIPHS_IO_MUX_MTDI_U, .d3 = PERIPHS_IO_MUX_MTCK_U, + .d3_gpio = 13, .card_detect = HOST_CARD_DETECT_N_2_IDX, .write_protect = HOST_CARD_WRITE_PRT_2_IDX, .width = 4 @@ -82,6 +85,7 @@ static const char* TAG = "sdmmc_periph"; static intr_handle_t s_intr_handle; static QueueHandle_t s_event_queue; +size_t s_slot_width[2] = {1,1}; void sdmmc_host_reset() { @@ -324,14 +328,25 @@ esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config) else if (slot_width > pslot->width) { return ESP_ERR_INVALID_ARG; } + s_slot_width[slot] = slot_width; configure_pin(pslot->clk); configure_pin(pslot->cmd); configure_pin(pslot->d0); + if (slot_width >= 4) { configure_pin(pslot->d1); configure_pin(pslot->d2); - configure_pin(pslot->d3); + //force pull-up D3 to make slave detect SD mode. connect to peripheral after width configuration. + gpio_config_t gpio_conf = { + .pin_bit_mask = BIT(pslot->d3_gpio), + .mode = GPIO_MODE_OUTPUT , + .pull_up_en = 0, + .pull_down_en = 0, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config( &gpio_conf ); + gpio_set_level( pslot->d3_gpio, 1 ); if (slot_width == 8) { configure_pin(pslot->d4); configure_pin(pslot->d5); @@ -404,8 +419,10 @@ esp_err_t sdmmc_host_set_bus_width(int slot, size_t width) } else if (width == 4) { SDMMC.ctype.card_width_8 &= ~mask; SDMMC.ctype.card_width |= mask; + configure_pin(s_slot_info[slot].d3); // D3 was set to GPIO high to force slave into SD 1-bit mode, until 4-bit mode is set } else if (width == 8){ SDMMC.ctype.card_width_8 |= mask; + configure_pin(s_slot_info[slot].d3); // D3 was set to GPIO high to force slave into SD 1-bit mode, until 4-bit mode is set } else { return ESP_ERR_INVALID_ARG; } @@ -413,6 +430,12 @@ esp_err_t sdmmc_host_set_bus_width(int slot, size_t width) return ESP_OK; } +size_t sdmmc_host_get_slot_width(int slot) +{ + assert( slot == 0 || slot == 1 ); + return s_slot_width[slot]; +} + static void sdmmc_host_dma_init() { SDMMC.ctrl.dma_enable = 1; diff --git a/components/driver/spi_common.c b/components/driver/spi_common.c index 02c1b38b6..bd78e9352 100644 --- a/components/driver/spi_common.c +++ b/components/driver/spi_common.c @@ -255,38 +255,38 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf if (native) { //All SPI native pin selections resolve to 1, so we put that here instead of trying to figure //out which FUNC_GPIOx_xSPIxx to grab; they all are defined to 1 anyway. - if (bus_config->mosi_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], 1); - if (bus_config->miso_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], 1); - if (use_quad && bus_config->quadwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], 1); - if (use_quad && bus_config->quadhd_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], 1); - if (bus_config->sclk_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], 1); + if (bus_config->mosi_io_num >= 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], 1); + if (bus_config->miso_io_num >= 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], 1); + if (use_quad && bus_config->quadwp_io_num >= 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], 1); + if (use_quad && bus_config->quadhd_io_num >= 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], 1); + if (bus_config->sclk_io_num >= 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], 1); } else { //Use GPIO - if (bus_config->mosi_io_num > 0) { + if (bus_config->mosi_io_num >= 0) { PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->mosi_io_num], PIN_FUNC_GPIO); gpio_set_direction(bus_config->mosi_io_num, GPIO_MODE_INPUT_OUTPUT); gpio_matrix_out(bus_config->mosi_io_num, io_signal[host].spid_out, false, false); gpio_matrix_in(bus_config->mosi_io_num, io_signal[host].spid_in, false); } - if (bus_config->miso_io_num > 0) { + if (bus_config->miso_io_num >= 0) { PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->miso_io_num], PIN_FUNC_GPIO); gpio_set_direction(bus_config->miso_io_num, GPIO_MODE_INPUT_OUTPUT); gpio_matrix_out(bus_config->miso_io_num, io_signal[host].spiq_out, false, false); gpio_matrix_in(bus_config->miso_io_num, io_signal[host].spiq_in, false); } - if (use_quad && bus_config->quadwp_io_num > 0) { + if (use_quad && bus_config->quadwp_io_num >= 0) { PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], PIN_FUNC_GPIO); gpio_set_direction(bus_config->quadwp_io_num, GPIO_MODE_INPUT_OUTPUT); gpio_matrix_out(bus_config->quadwp_io_num, io_signal[host].spiwp_out, false, false); gpio_matrix_in(bus_config->quadwp_io_num, io_signal[host].spiwp_in, false); } - if (use_quad && bus_config->quadhd_io_num > 0) { + if (use_quad && bus_config->quadhd_io_num >= 0) { PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadhd_io_num], PIN_FUNC_GPIO); gpio_set_direction(bus_config->quadhd_io_num, GPIO_MODE_INPUT_OUTPUT); gpio_matrix_out(bus_config->quadhd_io_num, io_signal[host].spihd_out, false, false); gpio_matrix_in(bus_config->quadhd_io_num, io_signal[host].spihd_in, false); } - if (bus_config->sclk_io_num > 0) { + if (bus_config->sclk_io_num >= 0) { PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->sclk_io_num], PIN_FUNC_GPIO); gpio_set_direction(bus_config->sclk_io_num, GPIO_MODE_INPUT_OUTPUT); gpio_matrix_out(bus_config->sclk_io_num, io_signal[host].spiclk_out, false, false); diff --git a/components/driver/test/test_spi_master.c b/components/driver/test/test_spi_master.c index fad766ee3..cc291f4d7 100644 --- a/components/driver/test/test_spi_master.c +++ b/components/driver/test/test_spi_master.c @@ -728,3 +728,76 @@ TEST_CASE("SPI master variable cmd & addr test","[spi]") ESP_LOGI(MASTER_TAG, "test passed."); } + +#define RECORD_TIME_PREPARE() uint32_t __t1, __t2 +#define RECORD_TIME_START() do {__t1 = xthal_get_ccount();}while(0) +#define RECORD_TIME_END(p_time) do{__t2 = xthal_get_ccount(); *p_time = (__t2-__t1)/240;}while(0) + +TEST_CASE("spi_speed","[spi]") +{ + RECORD_TIME_PREPARE(); + uint32_t t_no_dma, t_dma; + esp_err_t ret; + spi_device_handle_t spi; + spi_bus_config_t buscfg={ + .miso_io_num=PIN_NUM_MISO, + .mosi_io_num=PIN_NUM_MOSI, + .sclk_io_num=PIN_NUM_CLK, + .quadwp_io_num=-1, + .quadhd_io_num=-1 + }; + spi_device_interface_config_t devcfg={ + .clock_speed_hz=10*1000*1000, //currently only up to 4MHz for internel connect + .mode=0, //SPI mode 0 + .spics_io_num=PIN_NUM_CS, //CS pin + .queue_size=16, //We want to be able to queue 7 transactions at a time + .pre_cb=NULL, + .cs_ena_pretrans = 0, + }; + //Initialize the SPI bus + ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1); + TEST_ASSERT(ret==ESP_OK); + //Attach the LCD to the SPI bus + ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi); + TEST_ASSERT(ret==ESP_OK); + + spi_transaction_t trans = { + .length = 1*8, + .flags = SPI_TRANS_USE_TXDATA, + }; + spi_device_transmit(spi, &trans); + + //only record the second time + RECORD_TIME_START(); + spi_device_transmit(spi, &trans); + RECORD_TIME_END(&t_dma); + + TEST_PERFORMANCE_LESS_THAN( SPI_PER_TRANS_NO_POLLING, "%d us", t_dma ); + + TEST_ESP_OK( spi_bus_remove_device(spi) ); + TEST_ESP_OK( spi_bus_free(HSPI_HOST) ); + + ret=spi_bus_initialize(HSPI_HOST, &buscfg, 0); + TEST_ASSERT(ret==ESP_OK); + //Attach the LCD to the SPI bus + ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi); + TEST_ASSERT(ret==ESP_OK); + + trans = (spi_transaction_t){ + .length = 1*8, + .flags = SPI_TRANS_USE_TXDATA, + }; + spi_device_transmit(spi, &trans); + + //only record the second time + RECORD_TIME_START(); + spi_device_transmit(spi, &trans); + RECORD_TIME_END(&t_no_dma); + + TEST_PERFORMANCE_LESS_THAN( SPI_PER_TRANS_NO_POLLING_NO_DMA, "%d us", t_no_dma ); + + TEST_ESP_OK( spi_bus_remove_device(spi) ); + TEST_ESP_OK( spi_bus_free(HSPI_HOST) ); + + +} diff --git a/components/driver/uart.c b/components/driver/uart.c index d839a062f..0b5638a90 100644 --- a/components/driver/uart.c +++ b/components/driver/uart.c @@ -44,6 +44,8 @@ static const char* UART_TAG = "uart"; #define UART_FULL_THRESH_DEFAULT (120) #define UART_TOUT_THRESH_DEFAULT (10) #define UART_TX_IDLE_NUM_DEFAULT (0) +#define UART_PATTERN_DET_QLEN_DEFAULT (10) + #define UART_ENTER_CRITICAL_ISR(mux) portENTER_CRITICAL_ISR(mux) #define UART_EXIT_CRITICAL_ISR(mux) portEXIT_CRITICAL_ISR(mux) #define UART_ENTER_CRITICAL(mux) portENTER_CRITICAL(mux) @@ -58,6 +60,13 @@ typedef struct { } tx_data; } uart_tx_data_t; +typedef struct { + int wr; + int rd; + int len; + int* data; +} uart_pat_rb_t; + typedef struct { uart_port_t uart_num; /*!< UART port number*/ int queue_size; /*!< UART event queue size*/ @@ -74,6 +83,8 @@ typedef struct { uint8_t* rx_head_ptr; /*!< pointer to the head of RX item*/ uint8_t rx_data_buf[UART_FIFO_LEN]; /*!< Data buffer to stash FIFO data*/ uint8_t rx_stash_len; /*!< stashed data length.(When using flow control, after reading out FIFO data, if we fail to push to buffer, we can just stash them.) */ + uart_pat_rb_t rx_pattern_pos; + //tx parameters SemaphoreHandle_t tx_fifo_sem; /*!< UART TX FIFO semaphore*/ SemaphoreHandle_t tx_mux; /*!< UART TX mutex*/ @@ -91,8 +102,6 @@ typedef struct { uint8_t tx_waiting_brk; /*!< Flag to indicate that TX FIFO is ready to send break signal after FIFO is empty, do not push data into TX FIFO right now.*/ } uart_obj_t; - - static uart_obj_t *p_uart_obj[UART_NUM_MAX] = {0}; /* DRAM_ATTR is required to avoid UART array placed in flash, due to accessed from ISR */ static DRAM_ATTR uart_dev_t* const UART[UART_NUM_MAX] = {&UART0, &UART1, &UART2}; @@ -271,8 +280,11 @@ esp_err_t uart_get_hw_flow_ctrl(uart_port_t uart_num, uart_hw_flowcontrol_t* flo static esp_err_t uart_reset_rx_fifo(uart_port_t uart_num) { UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL); - // Read all data from the FIFO - while (UART[uart_num]->status.rxfifo_cnt) { + //Due to hardware issue, we can not use fifo_rst to reset uart fifo. + //See description about UART_TXFIFO_RST and UART_RXFIFO_RST in <> v2.6 or later. + + // we read the data out and make `fifo_len == 0 && rd_addr == wr_addr`. + while(UART[uart_num]->status.rxfifo_cnt != 0 || (UART[uart_num]->mem_rx_status.wr_addr != UART[uart_num]->mem_rx_status.rd_addr)) { READ_PERI_REG(UART_FIFO_REG(uart_num)); } return ESP_OK; @@ -305,6 +317,120 @@ esp_err_t uart_disable_intr_mask(uart_port_t uart_num, uint32_t disable_mask) return ESP_OK; } +static esp_err_t uart_pattern_link_free(uart_port_t uart_num) +{ + UART_CHECK((p_uart_obj[uart_num]), "uart driver error", ESP_FAIL); + if (p_uart_obj[uart_num]->rx_pattern_pos.data != NULL) { + int* pdata = p_uart_obj[uart_num]->rx_pattern_pos.data; + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + p_uart_obj[uart_num]->rx_pattern_pos.data = NULL; + p_uart_obj[uart_num]->rx_pattern_pos.wr = 0; + p_uart_obj[uart_num]->rx_pattern_pos.rd = 0; + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); + free(pdata); + } + return ESP_OK; +} + +static esp_err_t uart_pattern_enqueue(uart_port_t uart_num, int pos) +{ + UART_CHECK((p_uart_obj[uart_num]), "uart driver error", ESP_FAIL); + esp_err_t ret = ESP_OK; + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + uart_pat_rb_t* p_pos = &p_uart_obj[uart_num]->rx_pattern_pos; + int next = p_pos->wr + 1; + if (next >= p_pos->len) { + next = 0; + } + if (next == p_pos->rd) { + ESP_EARLY_LOGW(UART_TAG, "Fail to enqueue pattern position, pattern queue is full."); + ret = ESP_FAIL; + } else { + p_pos->data[p_pos->wr] = pos; + p_pos->wr = next; + ret = ESP_OK; + } + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); + return ret; +} + +static esp_err_t uart_pattern_dequeue(uart_port_t uart_num) +{ + UART_CHECK((p_uart_obj[uart_num]), "uart driver error", ESP_FAIL); + if(p_uart_obj[uart_num]->rx_pattern_pos.data == NULL) { + return ESP_ERR_INVALID_STATE; + } else { + esp_err_t ret = ESP_OK; + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + uart_pat_rb_t* p_pos = &p_uart_obj[uart_num]->rx_pattern_pos; + if (p_pos->rd == p_pos->wr) { + ret = ESP_FAIL; + } else { + p_pos->rd++; + } + if (p_pos->rd >= p_pos->len) { + p_pos->rd = 0; + } + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); + return ret; + } +} + +static esp_err_t uart_pattern_queue_update(uart_port_t uart_num, int diff_len) +{ + UART_CHECK((p_uart_obj[uart_num]), "uart driver error", ESP_FAIL); + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + uart_pat_rb_t* p_pos = &p_uart_obj[uart_num]->rx_pattern_pos; + int rd = p_pos->rd; + while(rd != p_pos->wr) { + p_pos->data[rd] -= diff_len; + int rd_rec = rd; + rd ++; + if (rd >= p_pos->len) { + rd = 0; + } + if (p_pos->data[rd_rec] < 0) { + p_pos->rd = rd; + } + } + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); + return ESP_OK; +} + +int uart_pattern_pop_pos(uart_port_t uart_num) +{ + UART_CHECK((p_uart_obj[uart_num]), "uart driver error", (-1)); + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + uart_pat_rb_t* pat_pos = &p_uart_obj[uart_num]->rx_pattern_pos; + int pos = -1; + if (pat_pos != NULL && pat_pos->rd != pat_pos->wr) { + pos = pat_pos->data[pat_pos->rd]; + uart_pattern_dequeue(uart_num); + } + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); + return pos; +} + +esp_err_t uart_pattern_queue_reset(uart_port_t uart_num, int queue_length) +{ + UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL); + UART_CHECK((p_uart_obj[uart_num]), "uart driver error", ESP_ERR_INVALID_STATE); + + int* pdata = (int*) malloc(queue_length * sizeof(int)); + if(pdata == NULL) { + return ESP_ERR_NO_MEM; + } + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + int* ptmp = p_uart_obj[uart_num]->rx_pattern_pos.data; + p_uart_obj[uart_num]->rx_pattern_pos.data = pdata; + p_uart_obj[uart_num]->rx_pattern_pos.len = queue_length; + p_uart_obj[uart_num]->rx_pattern_pos.rd = 0; + p_uart_obj[uart_num]->rx_pattern_pos.wr = 0; + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); + free(ptmp); + return ESP_OK; +} + esp_err_t uart_enable_pattern_det_intr(uart_port_t uart_num, char pattern_chr, uint8_t chr_num, int chr_tout, int post_idle, int pre_idle) { UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL); @@ -531,26 +657,42 @@ esp_err_t uart_intr_config(uart_port_t uart_num, const uart_intr_config_t *intr_ return ESP_OK; } +static int uart_find_pattern_from_last(uint8_t* buf, int length, uint8_t pat_chr, int pat_num) +{ + int cnt = 0; + int len = length; + while (len >= 0) { + if (buf[len] == pat_chr) { + cnt++; + } else { + cnt = 0; + } + if (cnt >= pat_num) { + break; + } + len --; + } + return len; +} + //internal isr handler for default driver code. static void uart_rx_intr_handler_default(void *param) { uart_obj_t *p_uart = (uart_obj_t*) param; uint8_t uart_num = p_uart->uart_num; uart_dev_t* uart_reg = UART[uart_num]; + int rx_fifo_len = uart_reg->status.rxfifo_cnt; uint8_t buf_idx = 0; uint32_t uart_intr_status = UART[uart_num]->int_st.val; - int rx_fifo_len = 0; uart_event_t uart_event; portBASE_TYPE HPTaskAwoken = 0; - + static uint8_t pat_flg = 0; while(uart_intr_status != 0x0) { buf_idx = 0; uart_event.type = UART_EVENT_MAX; if(uart_intr_status & UART_TXFIFO_EMPTY_INT_ST_M) { - UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_reg->int_ena.txfifo_empty = 0; - uart_reg->int_clr.txfifo_empty = 1; - UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); + uart_clear_intr_status(uart_num, UART_TXFIFO_EMPTY_INT_CLR_M); + uart_disable_intr_mask(uart_num, UART_TXFIFO_EMPTY_INT_ENA_M); if(p_uart->tx_waiting_brk) { continue; } @@ -561,8 +703,7 @@ static void uart_rx_intr_handler_default(void *param) if(HPTaskAwoken == pdTRUE) { portYIELD_FROM_ISR() ; } - } - else { + } else { //We don't use TX ring buffer, because the size is zero. if(p_uart->tx_buf_size == 0) { continue; @@ -604,7 +745,7 @@ static void uart_rx_intr_handler_default(void *param) break; } } - if(p_uart->tx_len_tot > 0 && p_uart->tx_ptr && p_uart->tx_len_cur > 0) { + if (p_uart->tx_len_tot > 0 && p_uart->tx_ptr && p_uart->tx_len_cur > 0) { //To fill the TX FIFO. int send_len = p_uart->tx_len_cur > tx_fifo_rem ? tx_fifo_rem : p_uart->tx_len_cur; for(buf_idx = 0; buf_idx < send_len; buf_idx++) { @@ -613,7 +754,7 @@ static void uart_rx_intr_handler_default(void *param) p_uart->tx_len_tot -= send_len; p_uart->tx_len_cur -= send_len; tx_fifo_rem -= send_len; - if(p_uart->tx_len_cur == 0) { + if (p_uart->tx_len_cur == 0) { //Return item to ring buffer. vRingbufferReturnItemFromISR(p_uart->tx_ring_buf, p_uart->tx_head, &HPTaskAwoken); if(HPTaskAwoken == pdTRUE) { @@ -642,62 +783,96 @@ static void uart_rx_intr_handler_default(void *param) } } } - if(en_tx_flg) { - UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_reg->int_clr.txfifo_empty = 1; - uart_reg->int_ena.txfifo_empty = 1; - UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); + if (en_tx_flg) { + uart_clear_intr_status(uart_num, UART_TXFIFO_EMPTY_INT_CLR_M); + uart_enable_intr_mask(uart_num, UART_TXFIFO_EMPTY_INT_ENA_M); } } } - else if((uart_intr_status & UART_RXFIFO_TOUT_INT_ST_M) || (uart_intr_status & UART_RXFIFO_FULL_INT_ST_M)) { - if(p_uart->rx_buffer_full_flg == false) { - //Get the buffer from the FIFO - rx_fifo_len = uart_reg->status.rxfifo_cnt; - p_uart->rx_stash_len = rx_fifo_len; + else if ((uart_intr_status & UART_RXFIFO_TOUT_INT_ST_M) + || (uart_intr_status & UART_RXFIFO_FULL_INT_ST_M) + || (uart_intr_status & UART_AT_CMD_CHAR_DET_INT_ST_M) + ) { + rx_fifo_len = uart_reg->status.rxfifo_cnt; + if(pat_flg == 1) { + uart_intr_status |= UART_AT_CMD_CHAR_DET_INT_ST_M; + pat_flg = 0; + } + if (p_uart->rx_buffer_full_flg == false) { //We have to read out all data in RX FIFO to clear the interrupt signal - while(buf_idx < rx_fifo_len) { + while (buf_idx < rx_fifo_len) { p_uart->rx_data_buf[buf_idx++] = uart_reg->fifo.rw_byte; } - //After Copying the Data From FIFO ,Clear intr_status - UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_reg->int_clr.rxfifo_tout = 1; - uart_reg->int_clr.rxfifo_full = 1; - UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_event.size = rx_fifo_len; + uint8_t pat_chr = uart_reg->at_cmd_char.data; + int pat_num = uart_reg->at_cmd_char.char_num; + int pat_idx = -1; + + //Get the buffer from the FIFO + if (uart_intr_status & UART_AT_CMD_CHAR_DET_INT_ST_M) { + uart_clear_intr_status(uart_num, UART_AT_CMD_CHAR_DET_INT_CLR_M); + uart_event.type = UART_PATTERN_DET; + uart_event.size = rx_fifo_len; + pat_idx = uart_find_pattern_from_last(p_uart->rx_data_buf, rx_fifo_len - 1, pat_chr, pat_num); + } else { + //After Copying the Data From FIFO ,Clear intr_status + uart_clear_intr_status(uart_num, UART_RXFIFO_TOUT_INT_CLR_M | UART_RXFIFO_FULL_INT_CLR_M); + uart_event.type = UART_DATA; + uart_event.size = rx_fifo_len; + } + p_uart->rx_stash_len = rx_fifo_len; //If we fail to push data to ring buffer, we will have to stash the data, and send next time. //Mainly for applications that uses flow control or small ring buffer. if(pdFALSE == xRingbufferSendFromISR(p_uart->rx_ring_buf, p_uart->rx_data_buf, p_uart->rx_stash_len, &HPTaskAwoken)) { - UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_reg->int_ena.rxfifo_full = 0; - uart_reg->int_ena.rxfifo_tout = 0; - UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); - p_uart->rx_buffer_full_flg = true; + uart_disable_intr_mask(uart_num, UART_RXFIFO_TOUT_INT_ENA_M | UART_RXFIFO_FULL_INT_ENA_M); + if (uart_event.type == UART_PATTERN_DET) { + if (rx_fifo_len < pat_num) { + //some of the characters are read out in last interrupt + uart_pattern_enqueue(uart_num, p_uart->rx_buffered_len - (pat_num - rx_fifo_len)); + } else { + uart_pattern_enqueue(uart_num, + pat_idx <= -1 ? + //can not find the pattern in buffer, + p_uart->rx_buffered_len + p_uart->rx_stash_len : + // find the pattern in buffer + p_uart->rx_buffered_len + pat_idx); + } + if ((p_uart->xQueueUart != NULL) && (pdFALSE == xQueueSendFromISR(p_uart->xQueueUart, (void * )&uart_event, &HPTaskAwoken))) { + ESP_EARLY_LOGW(UART_TAG, "UART event queue full"); + } + } uart_event.type = UART_BUFFER_FULL; + p_uart->rx_buffer_full_flg = true; } else { UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); + if (uart_intr_status & UART_AT_CMD_CHAR_DET_INT_ST_M) { + if (rx_fifo_len < pat_num) { + //some of the characters are read out in last interrupt + uart_pattern_enqueue(uart_num, p_uart->rx_buffered_len - (pat_num - rx_fifo_len)); + } else if(pat_idx >= 0) { + // find pattern in statsh buffer. + uart_pattern_enqueue(uart_num, p_uart->rx_buffered_len + pat_idx); + } + } p_uart->rx_buffered_len += p_uart->rx_stash_len; UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_event.type = UART_DATA; } if(HPTaskAwoken == pdTRUE) { portYIELD_FROM_ISR() ; } } else { - UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_reg->int_ena.rxfifo_full = 0; - uart_reg->int_ena.rxfifo_tout = 0; - uart_reg->int_clr.val = UART_RXFIFO_FULL_INT_CLR_M | UART_RXFIFO_TOUT_INT_CLR_M; - UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_event.type = UART_BUFFER_FULL; + uart_disable_intr_mask(uart_num, UART_RXFIFO_FULL_INT_ENA_M | UART_RXFIFO_TOUT_INT_ENA_M); + uart_clear_intr_status(uart_num, UART_RXFIFO_FULL_INT_CLR_M | UART_RXFIFO_TOUT_INT_CLR_M); + if(uart_intr_status & UART_AT_CMD_CHAR_DET_INT_ST_M) { + uart_reg->int_clr.at_cmd_char_det = 1; + uart_event.type = UART_PATTERN_DET; + uart_event.size = rx_fifo_len; + pat_flg = 1; + } } } else if(uart_intr_status & UART_RXFIFO_OVF_INT_ST_M) { + // When fifo overflows, we reset the fifo. UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); - // Read all data from the FIFO - rx_fifo_len = uart_reg->status.rxfifo_cnt; - for (int i = 0; i < rx_fifo_len; i++) { - READ_PERI_REG(UART_FIFO_REG(uart_num)); - } + uart_reset_rx_fifo(uart_num); uart_reg->int_clr.rxfifo_ovf = 1; UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); uart_event.type = UART_FIFO_OVF; @@ -729,18 +904,14 @@ static void uart_rx_intr_handler_default(void *param) } } } else if(uart_intr_status & UART_TX_BRK_IDLE_DONE_INT_ST_M) { - UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_reg->int_ena.tx_brk_idle_done = 0; - uart_reg->int_clr.tx_brk_idle_done = 1; - UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); + uart_disable_intr_mask(uart_num, UART_TX_BRK_IDLE_DONE_INT_ENA_M); + uart_clear_intr_status(uart_num, UART_TX_BRK_IDLE_DONE_INT_CLR_M); } else if(uart_intr_status & UART_AT_CMD_CHAR_DET_INT_ST_M) { uart_reg->int_clr.at_cmd_char_det = 1; uart_event.type = UART_PATTERN_DET; } else if(uart_intr_status & UART_TX_DONE_INT_ST_M) { - UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); - uart_reg->int_ena.tx_done = 0; - uart_reg->int_clr.tx_done = 1; - UART_EXIT_CRITICAL_ISR(&uart_spinlock[uart_num]); + uart_disable_intr_mask(uart_num, UART_TX_DONE_INT_ENA_M); + uart_clear_intr_status(uart_num, UART_TX_DONE_INT_CLR_M); xSemaphoreGiveFromISR(p_uart_obj[uart_num]->tx_done_sem, &HPTaskAwoken); if(HPTaskAwoken == pdTRUE) { portYIELD_FROM_ISR() ; @@ -751,7 +922,9 @@ static void uart_rx_intr_handler_default(void *param) } if(uart_event.type != UART_EVENT_MAX && p_uart->xQueueUart) { - xQueueSendFromISR(p_uart->xQueueUart, (void * )&uart_event, &HPTaskAwoken); + if (pdFALSE == xQueueSendFromISR(p_uart->xQueueUart, (void * )&uart_event, &HPTaskAwoken)) { + ESP_EARLY_LOGW(UART_TAG, "UART event queue full"); + } if(HPTaskAwoken == pdTRUE) { portYIELD_FROM_ISR() ; } @@ -920,9 +1093,6 @@ int uart_read_bytes(uart_port_t uart_num, uint8_t* buf, uint32_t length, TickTyp p_uart_obj[uart_num]->rx_cur_remain = size; } else { xSemaphoreGive(p_uart_obj[uart_num]->rx_mux); - UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); - p_uart_obj[uart_num]->rx_buffered_len -= copy_len; - UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); return copy_len; } } @@ -932,7 +1102,11 @@ int uart_read_bytes(uart_port_t uart_num, uint8_t* buf, uint32_t length, TickTyp len_tmp = p_uart_obj[uart_num]->rx_cur_remain; } memcpy(buf + copy_len, p_uart_obj[uart_num]->rx_ptr, len_tmp); + UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); + p_uart_obj[uart_num]->rx_buffered_len -= len_tmp; + uart_pattern_queue_update(uart_num, len_tmp); p_uart_obj[uart_num]->rx_ptr += len_tmp; + UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); p_uart_obj[uart_num]->rx_cur_remain -= len_tmp; copy_len += len_tmp; length -= len_tmp; @@ -952,10 +1126,8 @@ int uart_read_bytes(uart_port_t uart_num, uint8_t* buf, uint32_t length, TickTyp } } } + xSemaphoreGive(p_uart_obj[uart_num]->rx_mux); - UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); - p_uart_obj[uart_num]->rx_buffered_len -= copy_len; - UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); return copy_len; } @@ -967,7 +1139,9 @@ esp_err_t uart_get_buffered_data_len(uart_port_t uart_num, size_t* size) return ESP_OK; } -esp_err_t uart_flush(uart_port_t uart_num) +esp_err_t uart_flush(uart_port_t uart_num) __attribute__((alias("uart_flush_input"))); + +esp_err_t uart_flush_input(uart_port_t uart_num) { UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL); UART_CHECK((p_uart_obj[uart_num]), "uart driver error", ESP_FAIL); @@ -983,6 +1157,7 @@ esp_err_t uart_flush(uart_port_t uart_num) vRingbufferReturnItem(p_uart->rx_ring_buf, p_uart->rx_head_ptr); UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); p_uart_obj[uart_num]->rx_buffered_len -= p_uart->rx_cur_remain; + uart_pattern_queue_update(uart_num, p_uart->rx_cur_remain); UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); p_uart->rx_ptr = NULL; p_uart->rx_cur_remain = 0; @@ -994,6 +1169,7 @@ esp_err_t uart_flush(uart_port_t uart_num) } UART_ENTER_CRITICAL(&uart_spinlock[uart_num]); p_uart_obj[uart_num]->rx_buffered_len -= size; + uart_pattern_queue_update(uart_num, size); UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); vRingbufferReturnItem(p_uart->rx_ring_buf, data); if(p_uart_obj[uart_num]->rx_buffer_full_flg) { @@ -1024,7 +1200,7 @@ esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_b UART_CHECK((intr_alloc_flags & ESP_INTR_FLAG_IRAM) == 0, "ESP_INTR_FLAG_IRAM set in intr_alloc_flags", ESP_FAIL); /* uart_rx_intr_handler_default is not in IRAM */ if(p_uart_obj[uart_num] == NULL) { - p_uart_obj[uart_num] = (uart_obj_t*) malloc(sizeof(uart_obj_t)); + p_uart_obj[uart_num] = (uart_obj_t*) calloc(1, sizeof(uart_obj_t)); if(p_uart_obj[uart_num] == NULL) { ESP_LOGE(UART_TAG, "UART driver malloc error"); return ESP_FAIL; @@ -1044,6 +1220,7 @@ esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_b p_uart_obj[uart_num]->tx_brk_len = 0; p_uart_obj[uart_num]->tx_waiting_brk = 0; p_uart_obj[uart_num]->rx_buffered_len = 0; + uart_pattern_queue_reset(uart_num, UART_PATTERN_DET_QLEN_DEFAULT); if(uart_queue) { p_uart_obj[uart_num]->xQueueUart = xQueueCreate(queue_size, sizeof(uart_event_t)); @@ -1103,6 +1280,7 @@ esp_err_t uart_driver_delete(uart_port_t uart_num) esp_intr_free(p_uart_obj[uart_num]->intr_handle); uart_disable_rx_intr(uart_num); uart_disable_tx_intr(uart_num); + uart_pattern_link_free(uart_num); if(p_uart_obj[uart_num]->tx_fifo_sem) { vSemaphoreDelete(p_uart_obj[uart_num]->tx_fifo_sem); diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 57b0599d9..4eb961c07 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -281,7 +281,7 @@ config SYSTEM_EVENT_QUEUE_SIZE config SYSTEM_EVENT_TASK_STACK_SIZE int "Event loop task stack size" - default 2048 + default 2304 help Config system event task stack size in different application. diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 671bbd525..b66201c4f 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -1,4 +1,4 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -328,6 +328,8 @@ void start_cpu0_default(void) do_global_ctors(); #if CONFIG_INT_WDT esp_int_wdt_init(); + //Initialize the interrupt watch dog for CPU0. + esp_int_wdt_cpu_init(); #endif esp_cache_err_int_init(); esp_crosscore_int_init(); @@ -377,6 +379,10 @@ void start_cpu1_default(void) #if CONFIG_ESP32_APPTRACE_ENABLE esp_err_t err = esp_apptrace_init(); assert(err == ESP_OK && "Failed to init apptrace module on APP CPU!"); +#endif +#if CONFIG_INT_WDT + //Initialize the interrupt watch dog for CPU1. + esp_int_wdt_cpu_init(); #endif //Take care putting stuff here: if asked, FreeRTOS will happily tell you the scheduler //has started, but it isn't active *on this CPU* yet. diff --git a/components/esp32/include/esp_attr.h b/components/esp32/include/esp_attr.h index 911201aac..a9c3f9a7a 100644 --- a/components/esp32/include/esp_attr.h +++ b/components/esp32/include/esp_attr.h @@ -26,6 +26,12 @@ // Forces data into DRAM instead of flash #define DRAM_ATTR __attribute__((section(".dram1"))) +// Forces data to be 4 bytes aligned +#define WORD_ALIGNED_ATTR __attribute__((aligned(4))) + +// Forces data to be placed to DMA-capable places +#define DMA_ATTR WORD_ALIGNED_ATTR DRAM_ATTR + // Forces a string into DRAM instead of flash // Use as ets_printf(DRAM_STR("Hello world!\n")); #define DRAM_STR(str) (__extension__({static const DRAM_ATTR char __c[] = (str); (const char *)&__c;})) diff --git a/components/esp32/include/esp_flash_data_types.h b/components/esp32/include/esp_flash_data_types.h index cb6dfc3a1..3e44b2639 100644 --- a/components/esp32/include/esp_flash_data_types.h +++ b/components/esp32/include/esp_flash_data_types.h @@ -23,6 +23,7 @@ extern "C" #define ESP_PARTITION_TABLE_ADDR 0x8000 #define ESP_PARTITION_MAGIC 0x50AA +#define ESP_PARTITION_MAGIC_MD5 0xEBEB /* OTA selection structure (two copies in the OTA data partition.) Size of 32 bytes is friendly to flash encryption */ diff --git a/components/esp32/include/esp_int_wdt.h b/components/esp32/include/esp_int_wdt.h index b32d0219f..f581d939a 100644 --- a/components/esp32/include/esp_int_wdt.h +++ b/components/esp32/include/esp_int_wdt.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ interrupts for too long, or code within interrupt handlers taking too long. It does this by setting up a watchdog which gets fed from the FreeRTOS task switch interrupt. When this watchdog times out, initially it will call a high-level interrupt routine that will panic FreeRTOS in order to allow -for forensic examination of the state of the CPU. When this interrupt +for forensic examination of the state of the both CPUs. When this interrupt handler is not called and the watchdog times out a second time, it will reset the SoC. @@ -38,12 +38,22 @@ This uses the TIMERG1 WDT. /** - * @brief Initialize the interrupt watchdog. This is called in the init code if - * the interrupt watchdog is enabled in menuconfig. + * @brief Initialize the non-CPU-specific parts of interrupt watchdog. + * This is called in the init code if the interrupt watchdog + * is enabled in menuconfig. * */ void esp_int_wdt_init(); +/** + * @brief Enable the interrupt watchdog on the current CPU. This is called + * in the init code by both CPUs if the interrupt watchdog is enabled + * in menuconfig. + * + */ +void esp_int_wdt_cpu_init(); + + /** * @} @@ -54,4 +64,4 @@ void esp_int_wdt_init(); } #endif -#endif \ No newline at end of file +#endif diff --git a/components/esp32/int_wdt.c b/components/esp32/int_wdt.c index debd675c4..efef92511 100644 --- a/components/esp32/int_wdt.c +++ b/components/esp32/int_wdt.c @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -88,10 +88,11 @@ void esp_int_wdt_init() { TIMERG1.wdt_wprotect=0; TIMERG1.int_clr_timers.wdt=1; timer_group_intr_enable(TIMER_GROUP_1, TIMG_WDT_INT_ENA_M); - esp_register_freertos_tick_hook_for_cpu(tick_hook, 0); -#ifndef CONFIG_FREERTOS_UNICORE - esp_register_freertos_tick_hook_for_cpu(tick_hook, 1); -#endif +} + +void esp_int_wdt_cpu_init() +{ + esp_register_freertos_tick_hook_for_cpu(tick_hook, xPortGetCoreID()); ESP_INTR_DISABLE(WDT_INT_NUM); intr_matrix_set(xPortGetCoreID(), ETS_TG1_WDT_LEVEL_INTR_SOURCE, WDT_INT_NUM); //We do not register a handler for the interrupt because it is interrupt level 4 which diff --git a/components/esp32/ld/esp32.common.ld b/components/esp32/ld/esp32.common.ld index 32fc089d3..c6a92356c 100644 --- a/components/esp32/ld/esp32.common.ld +++ b/components/esp32/ld/esp32.common.ld @@ -89,7 +89,6 @@ SECTIONS *libesp32.a:core_dump.o(.literal .text .literal.* .text.*) *libapp_trace.a:(.literal .text .literal.* .text.*) *libxtensa-debug-module.a:eri.o(.literal .text .literal.* .text.*) - *libphy.a:(.literal .text .literal.* .text.*) *librtc.a:(.literal .text .literal.* .text.*) *libsoc.a:(.literal .text .literal.* .text.*) *libhal.a:(.literal .text .literal.* .text.*) @@ -195,6 +194,13 @@ SECTIONS *(.gnu.linkonce.lit4.*) _lit4_end = ABSOLUTE(.); . = ALIGN(4); + _thread_local_start = ABSOLUTE(.); + *(.tdata) + *(.tdata.*) + *(.tbss) + *(.tbss.*) + _thread_local_end = ABSOLUTE(.); + . = ALIGN(4); } >drom0_0_seg .flash.text : diff --git a/components/esp32/lib b/components/esp32/lib index c19d8c7a6..898cc4390 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit c19d8c7a6c8aa5884612c691262b6125327e0bca +Subproject commit 898cc43904003cdf8e19fe6bb8dc50c629d09eff diff --git a/components/esp32/panic.c b/components/esp32/panic.c index d09257268..83bc657f1 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -165,6 +165,7 @@ static const char *edesc[] = { #define NUM_EDESCS (sizeof(edesc) / sizeof(char *)) static void commonErrorHandler(XtExcFrame *frame); +static inline void disableAllWdts(); //The fact that we've panic'ed probably means the other CPU is now running wild, possibly //messing up the serial output, so we stall it here. @@ -185,6 +186,12 @@ static void setFirstBreakpoint(uint32_t pc) ::"r"(pc):"a3", "a4"); } +//When interrupt watchdog happen in one core, both cores will be interrupted. +//The core which doesn't trigger the interrupt watchdog will save the frame and return. +//The core which triggers the interrupt watchdog will use the saved frame, and dump frames for both cores. +#if !CONFIG_FREERTOS_UNICORE +static volatile XtExcFrame * other_core_frame = NULL; +#endif //!CONFIG_FREERTOS_UNICORE void panicHandler(XtExcFrame *frame) { @@ -205,11 +212,26 @@ void panicHandler(XtExcFrame *frame) if (frame->exccause <= PANIC_RSN_MAX) { reason = reasons[frame->exccause]; } + +#if !CONFIG_FREERTOS_UNICORE + //Save frame for other core. + if ((frame->exccause == PANIC_RSN_INTWDT_CPU0 && core_id == 1) || (frame->exccause == PANIC_RSN_INTWDT_CPU1 && core_id == 0)) { + other_core_frame = frame; + while (1); + } + + //The core which triggers the interrupt watchdog will delay 1 us, so the other core can save its frame. + if (frame->exccause == PANIC_RSN_INTWDT_CPU0 || frame->exccause == PANIC_RSN_INTWDT_CPU1) { + ets_delay_us(1); + } + if (frame->exccause == PANIC_RSN_CACHEERR && esp_cache_err_get_cpuid() != core_id) { // Cache error interrupt will be handled by the panic handler // on the other CPU. - return; + while (1); } +#endif //!CONFIG_FREERTOS_UNICORE + haltOtherCore(); esp_dport_access_int_abort(); panicPutStr("Guru Meditation Error: Core "); @@ -257,6 +279,11 @@ void panicHandler(XtExcFrame *frame) } if (esp_cpu_in_ocd_debug_mode()) { + disableAllWdts(); + if (frame->exccause == PANIC_RSN_INTWDT_CPU0 || + frame->exccause == PANIC_RSN_INTWDT_CPU1) { + TIMERG1.int_clr_timers.wdt = 1; + } #if CONFIG_ESP32_APPTRACE_ENABLE #if CONFIG_SYSVIEW_ENABLE SEGGER_RTT_ESP32_FlushNoLock(CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH, APPTRACE_ONPANIC_HOST_FLUSH_TMO); @@ -422,10 +449,9 @@ static void doBacktrace(XtExcFrame *frame) } /* - We arrive here after a panic or unhandled exception, when no OCD is detected. Dump the registers to the - serial port and either jump to the gdb stub, halt the CPU or reboot. -*/ -static __attribute__((noreturn)) void commonErrorHandler(XtExcFrame *frame) + * Dump registers and do backtrace. + */ +static void commonErrorHandler_dump(XtExcFrame *frame, int core_id) { int *regs = (int *)frame; int x, y; @@ -435,17 +461,13 @@ static __attribute__((noreturn)) void commonErrorHandler(XtExcFrame *frame) "A14 ", "A15 ", "SAR ", "EXCCAUSE", "EXCVADDR", "LBEG ", "LEND ", "LCOUNT " }; - // start panic WDT to restart system if we hang in this handler - esp_panic_wdt_start(); - - //Feed the watchdogs, so they will give us time to print out debug info - reconfigureAllWdts(); - /* only dump registers for 'real' crashes, if crashing via abort() the register window is no longer useful. */ if (!abort_called) { - panicPutStr("Register dump:\r\n"); + panicPutStr("Core"); + panicPutDec(core_id); + panicPutStr(" register dump:\r\n"); for (x = 0; x < 24; x += 4) { for (y = 0; y < 4; y++) { @@ -458,11 +480,65 @@ static __attribute__((noreturn)) void commonErrorHandler(XtExcFrame *frame) } panicPutStr("\r\n"); } + + if (xPortInterruptedFromISRContext() +#if !CONFIG_FREERTOS_UNICORE + && other_core_frame != frame +#endif //!CONFIG_FREERTOS_UNICORE + ) { + //If the core which triggers the interrupt watchdog was in ISR context, dump the epc registers. + uint32_t __value; + panicPutStr("Core"); + panicPutDec(core_id); + panicPutStr(" was running in ISR context:\r\n"); + + __asm__("rsr.epc1 %0" : "=a"(__value)); + panicPutStr("EPC1 : 0x"); + panicPutHex(__value); + + __asm__("rsr.epc2 %0" : "=a"(__value)); + panicPutStr(" EPC2 : 0x"); + panicPutHex(__value); + + __asm__("rsr.epc3 %0" : "=a"(__value)); + panicPutStr(" EPC3 : 0x"); + panicPutHex(__value); + + __asm__("rsr.epc4 %0" : "=a"(__value)); + panicPutStr(" EPC4 : 0x"); + panicPutHex(__value); + + panicPutStr("\r\n"); + } + } /* With windowed ABI backtracing is easy, let's do it. */ doBacktrace(frame); +} + +/* + We arrive here after a panic or unhandled exception, when no OCD is detected. Dump the registers to the + serial port and either jump to the gdb stub, halt the CPU or reboot. +*/ +static __attribute__((noreturn)) void commonErrorHandler(XtExcFrame *frame) +{ + + int core_id = xPortGetCoreID(); + // start panic WDT to restart system if we hang in this handler + esp_panic_wdt_start(); + + //Feed the watchdogs, so they will give us time to print out debug info + reconfigureAllWdts(); + + commonErrorHandler_dump(frame, core_id); +#if !CONFIG_FREERTOS_UNICORE + if (other_core_frame != NULL) { + commonErrorHandler_dump((XtExcFrame *)other_core_frame, (core_id ? 0 : 1)); + } +#endif //!CONFIG_FREERTOS_UNICORE + #if CONFIG_ESP32_APPTRACE_ENABLE disableAllWdts(); #if CONFIG_SYSVIEW_ENABLE diff --git a/components/esp32/phy_init.c b/components/esp32/phy_init.c index 280a4f094..325da27b5 100644 --- a/components/esp32/phy_init.c +++ b/components/esp32/phy_init.c @@ -32,6 +32,8 @@ #include "nvs_flash.h" #include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" #include "phy.h" #include "phy_init_data.h" #include "esp_coexist.h" @@ -44,6 +46,16 @@ static int s_phy_rf_init_count = 0; static _lock_t s_phy_rf_init_lock; +uint32_t IRAM_ATTR phy_enter_critical(void) +{ + return portENTER_CRITICAL_NESTED(); +} + +void IRAM_ATTR phy_exit_critical(uint32_t level) +{ + portEXIT_CRITICAL_NESTED(level); +} + esp_err_t esp_phy_rf_init(const esp_phy_init_data_t* init_data, esp_phy_calibration_mode_t mode, esp_phy_calibration_data_t* calibration_data) { @@ -56,6 +68,11 @@ esp_err_t esp_phy_rf_init(const esp_phy_init_data_t* init_data, ESP_LOGV(TAG, "register_chipv7_phy, init_data=%p, cal_data=%p, mode=%d", init_data, calibration_data, mode); phy_set_wifi_mode_only(0); + if (calibration_data != NULL) { + uint8_t mac[6]; + esp_efuse_mac_get_default(mac); + memcpy(&calibration_data->opaque[4], mac, 6); + } register_chipv7_phy(init_data, calibration_data, mode); coex_bt_high_prio(); } else { diff --git a/components/esp32/system_api.c b/components/esp32/system_api.c index e35973175..0c267c6c3 100644 --- a/components/esp32/system_api.c +++ b/components/esp32/system_api.c @@ -355,11 +355,6 @@ void IRAM_ATTR esp_restart_noos() void system_restart(void) __attribute__((alias("esp_restart"))); -void system_restore(void) -{ - esp_wifi_restore(); -} - uint32_t esp_get_free_heap_size( void ) { return heap_caps_get_free_size( MALLOC_CAP_DEFAULT ); diff --git a/components/esptool_py/esptool b/components/esptool_py/esptool index 4dab24e1b..200ab6e39 160000 --- a/components/esptool_py/esptool +++ b/components/esptool_py/esptool @@ -1 +1 @@ -Subproject commit 4dab24e1b28632d270adf7305fe0d008d1acee99 +Subproject commit 200ab6e39487bef1df9db12a5be5b0682d80d3c1 diff --git a/components/fatfs/src/esp_vfs_fat.h b/components/fatfs/src/esp_vfs_fat.h index 278e427c5..bd86e681a 100644 --- a/components/fatfs/src/esp_vfs_fat.h +++ b/components/fatfs/src/esp_vfs_fat.h @@ -88,8 +88,26 @@ esp_err_t esp_vfs_fat_unregister_path(const char* base_path); * @brief Configuration arguments for esp_vfs_fat_sdmmc_mount and esp_vfs_fat_spiflash_mount functions */ typedef struct { - bool format_if_mount_failed; ///< If FAT partition can not be mounted, and this parameter is true, create partition table and format the filesystem + /** + * If FAT partition can not be mounted, and this parameter is true, + * create partition table and format the filesystem. + */ + bool format_if_mount_failed; int max_files; ///< Max number of open files + /** + * If format_if_mount_failed is set, and mount fails, format the card + * with given allocation unit size. Must be a power of 2, between sector + * size and 128 * sector size. + * For SD cards, sector size is always 512 bytes. For wear_levelling, + * sector size is determined by CONFIG_WL_SECTOR_SIZE option. + * + * Using larger allocation unit size will result in higher read/write + * performance and higher overhead when storing small files. + * + * Setting this field to 0 will result in allocation unit set to the + * sector size. + */ + size_t allocation_unit_size; } esp_vfs_fat_mount_config_t; // Compatibility definition diff --git a/components/fatfs/src/vfs_fat_internal.h b/components/fatfs/src/vfs_fat_internal.h new file mode 100644 index 000000000..dc3bae271 --- /dev/null +++ b/components/fatfs/src/vfs_fat_internal.h @@ -0,0 +1,30 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "esp_vfs_fat.h" +#include +#include + +static inline size_t esp_vfs_fat_get_allocation_unit_size( + size_t sector_size, size_t requested_size) +{ + size_t alloc_unit_size = requested_size; + const size_t max_sectors_per_cylinder = 128; + const size_t max_size = sector_size * max_sectors_per_cylinder; + alloc_unit_size = MAX(alloc_unit_size, sector_size); + alloc_unit_size = MIN(alloc_unit_size, max_size); + return alloc_unit_size; +} diff --git a/components/fatfs/src/vfs_fat_sdmmc.c b/components/fatfs/src/vfs_fat_sdmmc.c index 5e79d4a9f..f31790e0f 100644 --- a/components/fatfs/src/vfs_fat_sdmmc.c +++ b/components/fatfs/src/vfs_fat_sdmmc.c @@ -17,6 +17,7 @@ #include "esp_log.h" #include "esp_vfs.h" #include "esp_vfs_fat.h" +#include "vfs_fat_internal.h" #include "driver/sdmmc_host.h" #include "driver/sdspi_host.h" #include "sdmmc_cmd.h" @@ -112,16 +113,23 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, goto fail; } ESP_LOGW(TAG, "partitioning card"); - DWORD plist[] = {100, 0, 0, 0}; workbuf = malloc(workbuf_size); + if (workbuf == NULL) { + err = ESP_ERR_NO_MEM; + goto fail; + } + DWORD plist[] = {100, 0, 0, 0}; res = f_fdisk(s_pdrv, plist, workbuf); if (res != FR_OK) { err = ESP_FAIL; ESP_LOGD(TAG, "f_fdisk failed (%d)", res); goto fail; } - ESP_LOGW(TAG, "formatting card"); - res = f_mkfs(drv, FM_ANY, s_card->csd.sector_size, workbuf, workbuf_size); + size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size( + s_card->csd.sector_size, + mount_config->allocation_unit_size); + ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size); + res = f_mkfs(drv, FM_ANY, alloc_unit_size, workbuf, workbuf_size); if (res != FR_OK) { err = ESP_FAIL; ESP_LOGD(TAG, "f_mkfs failed (%d)", res); diff --git a/components/fatfs/src/vfs_fat_spiflash.c b/components/fatfs/src/vfs_fat_spiflash.c index 2e90468d2..b676ed3a5 100644 --- a/components/fatfs/src/vfs_fat_spiflash.c +++ b/components/fatfs/src/vfs_fat_spiflash.c @@ -17,6 +17,7 @@ #include "esp_log.h" #include "esp_vfs.h" #include "esp_vfs_fat.h" +#include "vfs_fat_internal.h" #include "diskio.h" #include "wear_levelling.h" @@ -78,8 +79,15 @@ esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path, goto fail; } workbuf = malloc(workbuf_size); - ESP_LOGI(TAG, "Formatting FATFS partition"); - fresult = f_mkfs(drv, FM_ANY | FM_SFD, workbuf_size, workbuf, workbuf_size); + if (workbuf == NULL) { + result = ESP_ERR_NO_MEM; + goto fail; + } + size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size( + CONFIG_WL_SECTOR_SIZE, + mount_config->allocation_unit_size); + ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size); + fresult = f_mkfs(drv, FM_ANY | FM_SFD, alloc_unit_size, workbuf, workbuf_size); if (fresult != FR_OK) { result = ESP_FAIL; ESP_LOGE(TAG, "f_mkfs failed (%d)", fresult); diff --git a/components/fatfs/test/test_fatfs_sdmmc.c b/components/fatfs/test/test_fatfs_sdmmc.c index 3610511a9..5b6a2471e 100644 --- a/components/fatfs/test/test_fatfs_sdmmc.c +++ b/components/fatfs/test/test_fatfs_sdmmc.c @@ -185,7 +185,8 @@ static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write) sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = write, - .max_files = 5 + .max_files = 5, + .allocation_unit_size = 64 * 1024 }; TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); diff --git a/components/freertos/include/freertos/portable.h b/components/freertos/include/freertos/portable.h index b0fe405f7..9fe74b60c 100644 --- a/components/freertos/include/freertos/portable.h +++ b/components/freertos/include/freertos/portable.h @@ -183,6 +183,12 @@ void vPortSetStackWatchpoint( void* pxStackStart ); */ BaseType_t xPortInIsrContext(); +/* + * This function will be called in High prio ISRs. Returns true if the current core was in ISR context + * before calling into high prio ISR context. + */ +BaseType_t xPortInterruptedFromISRContext(); + /* * The structures and methods of manipulating the MPU are contained within the * port layer. @@ -213,5 +219,7 @@ uint32_t xPortGetTickRateHz(void); } #endif +void uxPortCompareSetExtram(volatile uint32_t *addr, uint32_t compare, uint32_t *set); + #endif /* PORTABLE_H */ diff --git a/components/freertos/include/freertos/task.h b/components/freertos/include/freertos/task.h index ab45f1c43..5af62c69c 100644 --- a/components/freertos/include/freertos/task.h +++ b/components/freertos/include/freertos/task.h @@ -380,6 +380,9 @@ is used in assert() statements. */ * @return pdPASS if the task was successfully created and added to a ready * list, otherwise an error code defined in the file projdefs.h * + * @note If program uses thread local variables (ones specified with "__thread" keyword) + * then storage for them will be allocated on the task's stack. + * * Example usage: * @code{c} * // Task to be created. @@ -530,6 +533,9 @@ is used in assert() statements. */ * are NULL then the task will not be created and * errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY is returned. * + * @note If program uses thread local variables (ones specified with "__thread" keyword) + * then storage for them will be allocated on the task's stack. + * * Example usage: * @code{c} * diff --git a/components/freertos/port.c b/components/freertos/port.c index 734e3d66c..98fe13d36 100644 --- a/components/freertos/port.c +++ b/components/freertos/port.c @@ -92,6 +92,7 @@ */ #include +#include #include #include "xtensa_rtos.h" @@ -146,9 +147,24 @@ StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t px #if XCHAL_CP_NUM > 0 uint32_t *p; #endif + uint32_t *threadptr; + void *task_thread_local_start; + extern int _thread_local_start, _thread_local_end, _rodata_start; + // TODO: check that TLS area fits the stack + uint32_t thread_local_sz = (uint8_t *)&_thread_local_end - (uint8_t *)&_thread_local_start; - /* Create interrupt stack frame aligned to 16 byte boundary */ - sp = (StackType_t *) (((UBaseType_t)(pxTopOfStack + 1) - XT_CP_SIZE - XT_STK_FRMSZ) & ~0xf); + thread_local_sz = ALIGNUP(0x10, thread_local_sz); + + /* Initialize task's stack so that we have the following structure at the top: + + ----LOW ADDRESSES ----------------------------------------HIGH ADDRESSES---------- + task stack | interrupt stack frame | thread local vars | co-processor save area | + ---------------------------------------------------------------------------------- + | | + SP pxTopOfStack + + All parts are aligned to 16 byte boundary. */ + sp = (StackType_t *) (((UBaseType_t)(pxTopOfStack + 1) - XT_CP_SIZE - thread_local_sz - XT_STK_FRMSZ) & ~0xf); /* Clear the entire frame (do not use memset() because we don't depend on C library) */ for (tp = sp; tp <= pxTopOfStack; ++tp) @@ -178,6 +194,14 @@ StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t px frame->vpri = 0xFFFFFFFF; #endif + /* Init threadptr reg and TLS vars */ + task_thread_local_start = (void *)(((uint32_t)pxTopOfStack - XT_CP_SIZE - thread_local_sz) & ~0xf); + memcpy(task_thread_local_start, &_thread_local_start, thread_local_sz); + threadptr = (uint32_t *)(sp + XT_STK_EXTRA); + /* shift threadptr by the offset of _thread_local_start from DROM start; + need to take into account extra 16 bytes offset */ + *threadptr = (uint32_t)task_thread_local_start - ((uint32_t)&_thread_local_start - (uint32_t)&_rodata_start) - 0x10; + #if XCHAL_CP_NUM > 0 /* Init the coprocessor save area (see xtensa_context.h) */ /* No access to TCB here, so derive indirectly. Stack growth is top to bottom. @@ -288,6 +312,14 @@ BaseType_t xPortInIsrContext() return ret; } +/* + * This function will be called in High prio ISRs. Returns true if the current core was in ISR context + * before calling into high prio ISR context. + */ +BaseType_t IRAM_ATTR xPortInterruptedFromISRContext() +{ + return (port_interruptNesting[xPortGetCoreID()] != 0); +} void vPortAssertIfInISR() { @@ -298,10 +330,6 @@ void vPortAssertIfInISR() * For kernel use: Initialize a per-CPU mux. Mux will be initialized unlocked. */ void vPortCPUInitializeMutex(portMUX_TYPE *mux) { -#if defined(CONFIG_SPIRAM_SUPPORT) - // Check if mux belongs to internal memory (DRAM), prerequisite for atomic operations - configASSERT(esp_ptr_internal((const void *) mux)); -#endif #ifdef CONFIG_FREERTOS_PORTMUX_DEBUG ets_printf("Initializing mux %p\n", mux); @@ -378,6 +406,34 @@ void vPortSetStackWatchpoint( void* pxStackStart ) { esp_set_watchpoint(1, (char*)addr, 32, ESP_WATCHPOINT_STORE); } +#if defined(CONFIG_SPIRAM_SUPPORT) +/* + * Compare & set (S32C1) does not work in external RAM. Instead, this routine uses a mux (in internal memory) to fake it. + */ +static portMUX_TYPE extram_mux = portMUX_INITIALIZER_UNLOCKED; + +void uxPortCompareSetExtram(volatile uint32_t *addr, uint32_t compare, uint32_t *set) { + uint32_t prev; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + vPortCPUAcquireMutexIntsDisabled(&extram_mux, portMUX_NO_TIMEOUT, __FUNCTION__, __LINE__); +#else + vPortCPUAcquireMutexIntsDisabled(&extram_mux, portMUX_NO_TIMEOUT); +#endif + prev=*addr; + if (prev==compare) { + *addr=*set; + } + *set=prev; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + vPortCPUReleaseMutexIntsDisabled(&extram_mux, __FUNCTION__, __LINE__); +#else + vPortCPUReleaseMutexIntsDisabled(&extram_mux); +#endif +} +#endif //defined(CONFIG_SPIRAM_SUPPORT) + + + uint32_t xPortGetTickRateHz(void) { return (uint32_t)configTICK_RATE_HZ; } diff --git a/components/freertos/portmux_impl.h b/components/freertos/portmux_impl.h index 7bb6bcc9d..60bc996b3 100644 --- a/components/freertos/portmux_impl.h +++ b/components/freertos/portmux_impl.h @@ -32,138 +32,80 @@ deal by FreeRTOS internals. It should be #included by freertos port.c or tasks.c, in esp-idf. + + The way it works is that it essentially uses portmux_impl.inc.h as a + generator template of sorts. When no external memory is used, this + template is only used to generate the vPortCPUAcquireMutexIntsDisabledInternal + and vPortCPUReleaseMutexIntsDisabledInternal functions, which use S32C1 to + do an atomic compare & swap. When external memory is used the functions + vPortCPUAcquireMutexIntsDisabledExtram and vPortCPUReleaseMutexIntsDisabledExtram + are also generated, which use uxPortCompareSetExtram to fake the S32C1 instruction. + The wrapper functions vPortCPUAcquireMutexIntsDisabled and + vPortCPUReleaseMutexIntsDisabled will then use the appropriate function to do the + actual lock/unlock. */ #include "soc/cpu.h" +#include "portable.h" /* XOR one core ID with this value to get the other core ID */ #define CORE_ID_XOR_SWAP (CORE_ID_PRO ^ CORE_ID_APP) -static inline bool __attribute__((always_inline)) + + + +//Define the mux routines for use with muxes in internal RAM +#define PORTMUX_AQUIRE_MUX_FN_NAME vPortCPUAcquireMutexIntsDisabledInternal +#define PORTMUX_RELEASE_MUX_FN_NAME vPortCPUReleaseMutexIntsDisabledInternal +#define PORTMUX_COMPARE_SET_FN_NAME uxPortCompareSet +#include "portmux_impl.inc.h" +#undef PORTMUX_AQUIRE_MUX_FN_NAME +#undef PORTMUX_RELEASE_MUX_FN_NAME +#undef PORTMUX_COMPARE_SET_FN_NAME + + +#if defined(CONFIG_SPIRAM_SUPPORT) + +#define PORTMUX_AQUIRE_MUX_FN_NAME vPortCPUAcquireMutexIntsDisabledExtram +#define PORTMUX_RELEASE_MUX_FN_NAME vPortCPUReleaseMutexIntsDisabledExtram +#define PORTMUX_COMPARE_SET_FN_NAME uxPortCompareSetExtram +#include "portmux_impl.inc.h" +#undef PORTMUX_AQUIRE_MUX_FN_NAME +#undef PORTMUX_RELEASE_MUX_FN_NAME +#undef PORTMUX_COMPARE_SET_FN_NAME + +#endif + + #ifdef CONFIG_FREERTOS_PORTMUX_DEBUG -vPortCPUAcquireMutexIntsDisabled(portMUX_TYPE *mux, int timeout_cycles, const char *fnName, int line) { +#define PORTMUX_AQUIRE_MUX_FN_ARGS portMUX_TYPE *mux, int timeout_cycles, const char *fnName, int line +#define PORTMUX_RELEASE_MUX_FN_ARGS portMUX_TYPE *mux, const char *fnName, int line +#define PORTMUX_AQUIRE_MUX_FN_CALL_ARGS(x) x, timeout_cycles, fnName, line +#define PORTMUX_RELEASE_MUX_FN_CALL_ARGS(x) x, fnName, line #else -vPortCPUAcquireMutexIntsDisabled(portMUX_TYPE *mux, int timeout_cycles) { -#endif -#if !CONFIG_FREERTOS_UNICORE - uint32_t res; - portBASE_TYPE coreID, otherCoreID; - uint32_t ccount_start; - bool set_timeout = timeout_cycles > portMUX_NO_TIMEOUT; -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - if (!set_timeout) { - timeout_cycles = 10000; // Always set a timeout in debug mode - set_timeout = true; - } -#endif - if (set_timeout) { // Timeout - RSR(CCOUNT, ccount_start); - } - -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - uint32_t owner = mux->owner; - if (owner != portMUX_FREE_VAL && owner != CORE_ID_PRO && owner != CORE_ID_APP) { - ets_printf("ERROR: vPortCPUAcquireMutex: mux %p is uninitialized (0x%X)! Called from %s line %d.\n", mux, owner, fnName, line); - mux->owner=portMUX_FREE_VAL; - } +#define PORTMUX_AQUIRE_MUX_FN_ARGS portMUX_TYPE *mux, int timeout_cycles +#define PORTMUX_RELEASE_MUX_FN_ARGS portMUX_TYPE *mux +#define PORTMUX_AQUIRE_MUX_FN_CALL_ARGS(x) x, timeout_cycles +#define PORTMUX_RELEASE_MUX_FN_CALL_ARGS(x) x #endif - /* Spin until we own the core */ - RSR(PRID, coreID); - /* Note: coreID is the full 32 bit core ID (CORE_ID_PRO/CORE_ID_APP), - not the 0/1 value returned by xPortGetCoreID() - */ - otherCoreID = CORE_ID_XOR_SWAP ^ coreID; - do { - /* mux->owner should be one of portMUX_FREE_VAL, CORE_ID_PRO, - CORE_ID_APP: - - - If portMUX_FREE_VAL, we want to atomically set to 'coreID'. - - If "our" coreID, we can drop through immediately. - - If "otherCoreID", we spin here. - */ - res = coreID; - uxPortCompareSet(&mux->owner, portMUX_FREE_VAL, &res); - - if (res != otherCoreID) { - break; // mux->owner is "our" coreID - } - - if (set_timeout) { - uint32_t ccount_now; - RSR(CCOUNT, ccount_now); - if (ccount_now - ccount_start > (unsigned)timeout_cycles) { -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - ets_printf("Timeout on mux! last non-recursive lock %s line %d, curr %s line %d\n", mux->lastLockedFn, mux->lastLockedLine, fnName, line); - ets_printf("Owner 0x%x count %d\n", mux->owner, mux->count); -#endif - return false; - } - } - } while (1); - - assert(res == coreID || res == portMUX_FREE_VAL); /* any other value implies memory corruption or uninitialized mux */ - assert((res == portMUX_FREE_VAL) == (mux->count == 0)); /* we're first to lock iff count is zero */ - assert(mux->count < 0xFF); /* Bad count value implies memory corruption */ - - /* now we own it, we can increment the refcount */ - mux->count++; - - -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - if (res==portMUX_FREE_VAL) { //initial lock - mux->lastLockedFn=fnName; - mux->lastLockedLine=line; - } else { - ets_printf("Recursive lock: count=%d last non-recursive lock %s line %d, curr %s line %d\n", mux->count-1, - mux->lastLockedFn, mux->lastLockedLine, fnName, line); +static inline bool __attribute__((always_inline)) vPortCPUAcquireMutexIntsDisabled(PORTMUX_AQUIRE_MUX_FN_ARGS) { +#if defined(CONFIG_SPIRAM_SUPPORT) + if (esp_ptr_external_ram(mux)) { + return vPortCPUAcquireMutexIntsDisabledExtram(PORTMUX_AQUIRE_MUX_FN_CALL_ARGS(mux)); } -#endif /* CONFIG_FREERTOS_PORTMUX_DEBUG */ -#endif /* CONFIG_FREERTOS_UNICORE */ - return true; +#endif + return vPortCPUAcquireMutexIntsDisabledInternal(PORTMUX_AQUIRE_MUX_FN_CALL_ARGS(mux)); } -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - static inline void vPortCPUReleaseMutexIntsDisabled(portMUX_TYPE *mux, const char *fnName, int line) { -#else -static inline void vPortCPUReleaseMutexIntsDisabled(portMUX_TYPE *mux) { -#endif -#if !CONFIG_FREERTOS_UNICORE - portBASE_TYPE coreID; -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - const char *lastLockedFn=mux->lastLockedFn; - int lastLockedLine=mux->lastLockedLine; - mux->lastLockedFn=fnName; - mux->lastLockedLine=line; - uint32_t owner = mux->owner; - if (owner != portMUX_FREE_VAL && owner != CORE_ID_PRO && owner != CORE_ID_APP) { - ets_printf("ERROR: vPortCPUReleaseMutex: mux %p is invalid (0x%x)!\n", mux, mux->owner); + +static inline void vPortCPUReleaseMutexIntsDisabled(PORTMUX_RELEASE_MUX_FN_ARGS) { +#if defined(CONFIG_SPIRAM_SUPPORT) + if (esp_ptr_external_ram(mux)) { + vPortCPUReleaseMutexIntsDisabledExtram(PORTMUX_RELEASE_MUX_FN_CALL_ARGS(mux)); + return; } #endif - -#if CONFIG_FREERTOS_PORTMUX_DEBUG || !defined(NDEBUG) - RSR(PRID, coreID); -#endif - -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG - if (coreID != mux->owner) { - ets_printf("ERROR: vPortCPUReleaseMutex: mux %p was already unlocked!\n", mux); - ets_printf("Last non-recursive unlock %s line %d, curr unlock %s line %d\n", lastLockedFn, lastLockedLine, fnName, line); - } -#endif - - assert(coreID == mux->owner); // This is a mutex we didn't lock, or it's corrupt - assert(mux->count > 0); // Indicates memory corruption - assert(mux->count < 0x100); // Indicates memory corruption - - mux->count--; - if(mux->count == 0) { - mux->owner = portMUX_FREE_VAL; - } -#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG_RECURSIVE - else { - ets_printf("Recursive unlock: count=%d last locked %s line %d, curr %s line %d\n", mux->count, lastLockedFn, lastLockedLine, fnName, line); - } -#endif -#endif //!CONFIG_FREERTOS_UNICORE + vPortCPUReleaseMutexIntsDisabledInternal(PORTMUX_RELEASE_MUX_FN_CALL_ARGS(mux)); } + diff --git a/components/freertos/portmux_impl.inc.h b/components/freertos/portmux_impl.inc.h new file mode 100644 index 000000000..29bfbfc34 --- /dev/null +++ b/components/freertos/portmux_impl.inc.h @@ -0,0 +1,171 @@ +/* + Copyright (C) 2016-2017 Espressif Shanghai PTE LTD + Copyright (C) 2015 Real Time Engineers Ltd. + + All rights reserved + + FreeRTOS is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License (version 2) as published by the + Free Software Foundation >>!AND MODIFIED BY!<< the FreeRTOS exception. + + *************************************************************************** + >>! NOTE: The modification to the GPL is included to allow you to !<< + >>! distribute a combined work that includes FreeRTOS without being !<< + >>! obliged to provide the source code for proprietary components !<< + >>! outside of the FreeRTOS kernel. !<< + *************************************************************************** + + FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. Full license text is available on the following + link: http://www.freertos.org/a00114.html +*/ + + +/* + Warning: funky preprocessor hackery ahead. Including these headers will generate two + functions, which names are defined by the preprocessor macros + PORTMUX_AQUIRE_MUX_FN_NAME and PORTMUX_RELEASE_MUX_FN_NAME. In order to do the compare + and exchange function, they will use whatever PORTMUX_COMPARE_SET_FN_NAME resolves to. + + In some scenarios, this header is included *twice* in portmux_impl.h: one time + for the 'normal' mux code which uses a compare&exchange routine, another time + to generate code for a second set of these routines that use a second mux + (in internal ram) to fake a compare&exchange on a variable in external memory. +*/ + + + +static inline bool __attribute__((always_inline)) +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG +PORTMUX_AQUIRE_MUX_FN_NAME(portMUX_TYPE *mux, int timeout_cycles, const char *fnName, int line) { +#else +PORTMUX_AQUIRE_MUX_FN_NAME(portMUX_TYPE *mux, int timeout_cycles) { +#endif + + +#if !CONFIG_FREERTOS_UNICORE + uint32_t res; + portBASE_TYPE coreID, otherCoreID; + uint32_t ccount_start; + bool set_timeout = timeout_cycles > portMUX_NO_TIMEOUT; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + if (!set_timeout) { + timeout_cycles = 10000; // Always set a timeout in debug mode + set_timeout = true; + } +#endif + if (set_timeout) { // Timeout + RSR(CCOUNT, ccount_start); + } + +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + uint32_t owner = mux->owner; + if (owner != portMUX_FREE_VAL && owner != CORE_ID_PRO && owner != CORE_ID_APP) { + ets_printf("ERROR: vPortCPUAcquireMutex: mux %p is uninitialized (0x%X)! Called from %s line %d.\n", mux, owner, fnName, line); + mux->owner=portMUX_FREE_VAL; + } +#endif + + /* Spin until we own the core */ + + RSR(PRID, coreID); + /* Note: coreID is the full 32 bit core ID (CORE_ID_PRO/CORE_ID_APP), + not the 0/1 value returned by xPortGetCoreID() + */ + otherCoreID = CORE_ID_XOR_SWAP ^ coreID; + do { + /* mux->owner should be one of portMUX_FREE_VAL, CORE_ID_PRO, + CORE_ID_APP: + + - If portMUX_FREE_VAL, we want to atomically set to 'coreID'. + - If "our" coreID, we can drop through immediately. + - If "otherCoreID", we spin here. + */ + res = coreID; + PORTMUX_COMPARE_SET_FN_NAME(&mux->owner, portMUX_FREE_VAL, &res); + + if (res != otherCoreID) { + break; // mux->owner is "our" coreID + } + + if (set_timeout) { + uint32_t ccount_now; + RSR(CCOUNT, ccount_now); + if (ccount_now - ccount_start > (unsigned)timeout_cycles) { +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + ets_printf("Timeout on mux! last non-recursive lock %s line %d, curr %s line %d\n", mux->lastLockedFn, mux->lastLockedLine, fnName, line); + ets_printf("Owner 0x%x count %d\n", mux->owner, mux->count); +#endif + return false; + } + } + } while (1); + + assert(res == coreID || res == portMUX_FREE_VAL); /* any other value implies memory corruption or uninitialized mux */ + assert((res == portMUX_FREE_VAL) == (mux->count == 0)); /* we're first to lock iff count is zero */ + assert(mux->count < 0xFF); /* Bad count value implies memory corruption */ + + /* now we own it, we can increment the refcount */ + mux->count++; + + +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + if (res==portMUX_FREE_VAL) { //initial lock + mux->lastLockedFn=fnName; + mux->lastLockedLine=line; + } else { + ets_printf("Recursive lock: count=%d last non-recursive lock %s line %d, curr %s line %d\n", mux->count-1, + mux->lastLockedFn, mux->lastLockedLine, fnName, line); + } +#endif /* CONFIG_FREERTOS_PORTMUX_DEBUG */ +#endif /* CONFIG_FREERTOS_UNICORE */ + return true; +} + +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG +static inline void PORTMUX_RELEASE_MUX_FN_NAME(portMUX_TYPE *mux, const char *fnName, int line) { +#else +static inline void PORTMUX_RELEASE_MUX_FN_NAME(portMUX_TYPE *mux) { +#endif + + +#if !CONFIG_FREERTOS_UNICORE + portBASE_TYPE coreID; +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + const char *lastLockedFn=mux->lastLockedFn; + int lastLockedLine=mux->lastLockedLine; + mux->lastLockedFn=fnName; + mux->lastLockedLine=line; + uint32_t owner = mux->owner; + if (owner != portMUX_FREE_VAL && owner != CORE_ID_PRO && owner != CORE_ID_APP) { + ets_printf("ERROR: vPortCPUReleaseMutex: mux %p is invalid (0x%x)!\n", mux, mux->owner); + } +#endif + +#if CONFIG_FREERTOS_PORTMUX_DEBUG || !defined(NDEBUG) + RSR(PRID, coreID); +#endif + +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG + if (coreID != mux->owner) { + ets_printf("ERROR: vPortCPUReleaseMutex: mux %p was already unlocked!\n", mux); + ets_printf("Last non-recursive unlock %s line %d, curr unlock %s line %d\n", lastLockedFn, lastLockedLine, fnName, line); + } +#endif + + assert(coreID == mux->owner); // This is a mutex we didn't lock, or it's corrupt + assert(mux->count > 0); // Indicates memory corruption + assert(mux->count < 0x100); // Indicates memory corruption + + mux->count--; + if(mux->count == 0) { + mux->owner = portMUX_FREE_VAL; + } +#ifdef CONFIG_FREERTOS_PORTMUX_DEBUG_RECURSIVE + else { + ets_printf("Recursive unlock: count=%d last locked %s line %d, curr %s line %d\n", mux->count, lastLockedFn, lastLockedLine, fnName, line); + } +#endif +#endif //!CONFIG_FREERTOS_UNICORE +} diff --git a/components/freertos/ringbuf.c b/components/freertos/ringbuf.c index 57ca1c6cd..7cb24a5ca 100644 --- a/components/freertos/ringbuf.c +++ b/components/freertos/ringbuf.c @@ -404,9 +404,8 @@ static void returnItemToRingbufDefault(ringbuf_t *rb, void *item) { //can be increase. //This function by itself is not threadsafe, always call from within a muxed section. static void returnItemToRingbufBytebuf(ringbuf_t *rb, void *item) { - uint8_t *data=(uint8_t*)item; - configASSERT(data >= rb->data); - configASSERT(data < rb->data+rb->size); + configASSERT((uint8_t *)item >= rb->data); + configASSERT((uint8_t *)item < rb->data+rb->size); //Free the read memory. rb->free_ptr=rb->read_ptr; } @@ -603,16 +602,22 @@ BaseType_t xRingbufferSend(RingbufHandle_t ringbuf, void *data, size_t dataSize, //we will need to wait some more. if (ticks_to_wait != portMAX_DELAY) { ticks_remaining = ticks_end - xTaskGetTickCount(); + + // ticks_remaining will always be less than or equal to the original ticks_to_wait, + // unless the timeout is reached - in which case it unsigned underflows to a much + // higher value. + // + // (Check is written this non-intuitive way to allow for the case where xTaskGetTickCount() + // has overflowed but the ticks_end value has not overflowed.) + if(ticks_remaining > ticks_to_wait) { + //Timeout, but there is not enough free space for the item that need to be sent. + xSemaphoreGive(rb->free_space_sem); + return pdFALSE; + } } - // ticks_remaining will always be less than or equal to the original ticks_to_wait, - // unless the timeout is reached - in which case it unsigned underflows to a much - // higher value. - // - // (Check is written this non-intuitive way to allow for the case where xTaskGetTickCount() - // has overflowed but the ticks_end value has not overflowed.) } - } while (ringbufferFreeMem(rb) < needed_size && ticks_remaining > 0 && ticks_remaining <= ticks_to_wait); + } while (ringbufferFreeMem(rb) < needed_size); //Lock the mux in order to make sure no one else is messing with the ringbuffer and do the copy. portENTER_CRITICAL(&rb->mux); @@ -692,10 +697,9 @@ void *xRingbufferReceiveFromISR(RingbufHandle_t ringbuf, size_t *item_size) } void *xRingbufferReceiveUpTo(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait, size_t wanted_size) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; if (wanted_size == 0) return NULL; - configASSERT(rb); - configASSERT(rb->flags & flag_bytebuf); + configASSERT(ringbuf); + configASSERT(((ringbuf_t *)ringbuf)->flags & flag_bytebuf); return xRingbufferReceiveGeneric(ringbuf, item_size, ticks_to_wait, wanted_size); } diff --git a/components/freertos/test/test_spinlocks.c b/components/freertos/test/test_spinlocks.c index 38a87f4a7..7f83e7519 100644 --- a/components/freertos/test/test_spinlocks.c +++ b/components/freertos/test/test_spinlocks.c @@ -47,9 +47,13 @@ TEST_CASE("portMUX spinlocks (no contention)", "[freertos]") #ifdef CONFIG_FREERTOS_UNICORE TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP_UNICORE, "%d cycles/op", ((end - start)/REPEAT_OPS)); +#else +#if CONFIG_SPIRAM_SUPPORT + TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP_PSRAM, "%d cycles/op", ((end - start)/REPEAT_OPS)); #else TEST_PERFORMANCE_LESS_THAN(FREERTOS_SPINLOCK_CYCLES_PER_OP, "%d cycles/op", ((end - start)/REPEAT_OPS)); #endif +#endif } TEST_CASE("portMUX recursive locks (no contention)", "[freertos]") diff --git a/components/freertos/test/test_thread_local.c b/components/freertos/test/test_thread_local.c new file mode 100644 index 000000000..d5f782b43 --- /dev/null +++ b/components/freertos/test/test_thread_local.c @@ -0,0 +1,105 @@ +/* + Test for thread local storage support. +*/ + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "sdkconfig.h" + +static __thread int tl_test_var1; +static __thread uint8_t tl_test_var2 = 55; +static __thread uint16_t tl_test_var3 = 44; +static __thread uint8_t tl_test_arr_var[10]; +static __thread struct test_tls_var { + int f32; + uint8_t f8; + uint16_t f16; + uint8_t farr[10]; +} tl_test_struct_var; + +static void task_test_tls(void *arg) +{ + bool *running = (bool *)arg; + uint32_t tp = (uint32_t)-1; + int test_var1_old = 0; + uint8_t test_var2_old = 0; + uint16_t test_var3_old = 0; + int f32_old = 0; + uint8_t f8_old = 0; + uint16_t f16_old = 0; + + asm volatile ("rur.threadptr %0":"=r"(tp)); + for (int i = 0; i < 5; i++) { + printf("Task[%x]: var = 0x%x 0x%x\n", tp, tl_test_var1, tl_test_var2); + if (i == 0) { + TEST_ASSERT_EQUAL(0, tl_test_var1); + TEST_ASSERT_EQUAL(55, tl_test_var2); + TEST_ASSERT_EQUAL(44, tl_test_var3); + for (int k = 0; k < sizeof(tl_test_arr_var); k++) { + TEST_ASSERT_EQUAL(0, tl_test_arr_var[k]); + } + TEST_ASSERT_EQUAL(0, tl_test_struct_var.f32); + TEST_ASSERT_EQUAL(0, tl_test_struct_var.f8); + TEST_ASSERT_EQUAL(0, tl_test_struct_var.f16); + for (int k = 0; k < sizeof(tl_test_struct_var.farr); k++) { + TEST_ASSERT_EQUAL(0, tl_test_struct_var.farr[k]); + } + } else { + TEST_ASSERT_EQUAL(test_var1_old+1, tl_test_var1); + TEST_ASSERT_EQUAL(test_var2_old+1, tl_test_var2); + TEST_ASSERT_EQUAL(test_var3_old+1, tl_test_var3); + for (int k = 0; k < sizeof(tl_test_arr_var); k++) { + TEST_ASSERT_EQUAL(i-1, tl_test_arr_var[k]); + } + TEST_ASSERT_EQUAL(f32_old+1, tl_test_struct_var.f32); + TEST_ASSERT_EQUAL(f8_old+1, tl_test_struct_var.f8); + TEST_ASSERT_EQUAL(f16_old+1, tl_test_struct_var.f16); + for (int k = 0; k < sizeof(tl_test_struct_var.farr); k++) { + TEST_ASSERT_EQUAL(i-1, tl_test_struct_var.farr[k]); + } + } + test_var1_old = tl_test_var1; + test_var2_old = tl_test_var2; + test_var3_old = tl_test_var3; + f32_old = tl_test_struct_var.f32; + f8_old = tl_test_struct_var.f8; + f16_old = tl_test_struct_var.f16; + tl_test_var1++; + tl_test_var2++; + tl_test_var3++; + memset(tl_test_arr_var, i, sizeof(tl_test_arr_var)); + tl_test_struct_var.f32++; + tl_test_struct_var.f8++; + tl_test_struct_var.f16++; + memset(tl_test_struct_var.farr, i, sizeof(tl_test_struct_var.farr)); + vTaskDelay(10); + } + + if (running) { + *running = false; + vTaskDelete(NULL); + } +} + +TEST_CASE("TLS test", "[freertos]") +{ + static StackType_t s_stack[2048]; + StaticTask_t s_task; + bool running[2] = {true, true}; +#if CONFIG_FREERTOS_UNICORE == 0 + int other_core = 1; +#else + int other_core = 0; +#endif + + xTaskCreatePinnedToCore((TaskFunction_t)&task_test_tls, "task_test_tls", 3072, &running[0], 5, NULL, 0); + xTaskCreateStaticPinnedToCore((TaskFunction_t)&task_test_tls, "task_test_tls", sizeof(s_stack), &running[1], + 5, s_stack, &s_task, other_core); + while (running[0] || running[1]) { + vTaskDelay(10); + } +} diff --git a/components/heap/heap_caps.c b/components/heap/heap_caps.c index 775a79a9f..31cfa8d55 100644 --- a/components/heap/heap_caps.c +++ b/components/heap/heap_caps.c @@ -309,7 +309,7 @@ IRAM_ATTR void *heap_caps_realloc( void *ptr, size_t size, int caps) if (new_p != NULL) { size_t old_size = multi_heap_get_allocated_size(heap->heap, ptr); assert(old_size > 0); - memcpy(new_p, ptr, old_size); + memcpy(new_p, ptr, MIN(size, old_size)); heap_caps_free(ptr); return new_p; } diff --git a/components/heap/test/test_realloc.c b/components/heap/test/test_realloc.c new file mode 100644 index 000000000..290ee3da6 --- /dev/null +++ b/components/heap/test/test_realloc.c @@ -0,0 +1,50 @@ +/* + Generic test for realloc +*/ + +#include +#include +#include "unity.h" +#include "sdkconfig.h" +#include "esp_heap_caps.h" + + +#ifndef CONFIG_HEAP_POISONING_COMPREHENSIVE +/* (can't realloc in place if comprehensive is enabled) */ + +TEST_CASE("realloc shrink buffer in place", "[heap]") +{ + void *x = malloc(64); + TEST_ASSERT(x); + void *y = realloc(p, 48); + TEST_ASSERT_EQUAL_PTR(x, y); +} + +#endif + +TEST_CASE("realloc move data to a new heap type", "[heap]") +{ + const char *test = "I am some test content to put in the heap"; + char buf[64]; + memset(buf, 0xEE, 64); + strlcpy(buf, test, 64); + + char *a = malloc(64); + memcpy(a, buf, 64); + + // move data from 'a' to IRAM + char *b = heap_caps_realloc(a, 64, MALLOC_CAP_EXEC); + TEST_ASSERT_NOT_NULL(b); + TEST_ASSERT_NOT_EQUAL(a, b); + TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_INVALID, true)); + TEST_ASSERT_EQUAL_HEX32_ARRAY(buf, b, 64/sizeof(uint32_t)); + + // Move data back to DRAM + char *c = heap_caps_realloc(b, 48, MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(c); + TEST_ASSERT_NOT_EQUAL(b, c); + TEST_ASSERT(heap_caps_check_integrity(MALLOC_CAP_INVALID, true)); + TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, c, 48); + + free(c); +} diff --git a/components/idf_test/include/idf_performance.h b/components/idf_test/include/idf_performance.h index e4339a129..8244a1c55 100644 --- a/components/idf_test/include/idf_performance.h +++ b/components/idf_test/include/idf_performance.h @@ -9,7 +9,12 @@ /* declare the performance here */ -#define IDF_PERFORMANCE_MAX_HTTPS_REQUEST_BIN_SIZE 610 +#define IDF_PERFORMANCE_MAX_HTTPS_REQUEST_BIN_SIZE 800 #define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP 200 +#define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP_PSRAM 270 #define IDF_PERFORMANCE_MAX_FREERTOS_SPINLOCK_CYCLES_PER_OP_UNICORE 130 #define IDF_PERFORMANCE_MAX_ESP_TIMER_GET_TIME_PER_CALL 1000 +#define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING 30 +#define IDF_PERFORMANCE_MAX_SPI_PER_TRANS_NO_POLLING_NO_DMA 25 + + diff --git a/components/idf_test/integration_test/IT_BTSTK_GAP.yml b/components/idf_test/integration_test/IT_BTSTK_GAP.yml index 7f2809539..8071b041f 100644 --- a/components/idf_test/integration_test/IT_BTSTK_GAP.yml +++ b/components/idf_test/integration_test/IT_BTSTK_GAP.yml @@ -1328,7 +1328,7 @@ test cases: allow fail: '' cmd set: - "" - - - "SSC SSC1 bleconn -S -a " + - - "SSC SSC1 bleconn -S -z ConnParam -a " - ['R SSC1 C +BLECONN:OK'] - ID: BTSTK_GAP_07002 SDK: ESP32_IDF @@ -1353,7 +1353,7 @@ test cases: allow fail: '' cmd set: - "" - - - "SSC SSC2 bleconn -S -a " + - - "SSC SSC2 bleconn -S -z ConnParam -a " - ['R SSC2 C +BLECONN:OK'] - ID: BTSTK_GAP_07003 SDK: ESP32_IDF @@ -1382,13 +1382,13 @@ test cases: allow fail: '' cmd set: - "" - - - "SSC SSC1 bleconn -S -a -i 0-0xFFFF" + - - "SSC SSC1 bleconn -S -z ConnParam -a -i 0-0xFFFF" - ['R SSC1 C +BLECONN:ERROR'] - - - "SSC SSC1 bleconn -S -a -l 0x1F4" + - - "SSC SSC1 bleconn -S -z ConnParam -a -l 0x1F4" - ['R SSC1 C +BLECONN:ERROR'] - - - "SSC SSC1 bleconn -S -a -t 0x08" + - - "SSC SSC1 bleconn -S -z ConnParam -a -t 0x08" - ['R SSC1 C +BLECONN:ERROR'] - - - "SSC SSC1 bleconn -S -a -t 0xC81" + - - "SSC SSC1 bleconn -S -z ConnParam -a -t 0xC81" - ['R SSC1 C +BLECONN:ERROR'] - ID: BTSTK_GAP_08001 SDK: ESP32_IDF diff --git a/components/json/LICENSE b/components/json/LICENSE deleted file mode 100644 index fa0a438e2..000000000 --- a/components/json/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ - Copyright (c) 2009 Dave Gamble - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - diff --git a/components/json/cJSON b/components/json/cJSON new file mode 160000 index 000000000..7cc52f603 --- /dev/null +++ b/components/json/cJSON @@ -0,0 +1 @@ +Subproject commit 7cc52f60356909b3dd260304c7c50c0693699353 diff --git a/components/json/component.mk b/components/json/component.mk index 2dd6ea8b8..a6a424e6d 100644 --- a/components/json/component.mk +++ b/components/json/component.mk @@ -1,7 +1,6 @@ # # Component Makefile # -COMPONENT_ADD_INCLUDEDIRS := include port/include - -COMPONENT_SRCDIRS := library port - +COMPONENT_ADD_INCLUDEDIRS := cJSON +COMPONENT_SRCDIRS := cJSON +COMPONENT_SUBMODULES := cJSON diff --git a/components/json/include/cJSON.h b/components/json/include/cJSON.h deleted file mode 100644 index 7c4f8e7cd..000000000 --- a/components/json/include/cJSON.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#ifndef cJSON__h -#define cJSON__h - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* project version */ -#define CJSON_VERSION_MAJOR 1 -#define CJSON_VERSION_MINOR 6 -#define CJSON_VERSION_PATCH 0 - -#include - -/* cJSON Types: */ -#define cJSON_Invalid (0) -#define cJSON_False (1 << 0) -#define cJSON_True (1 << 1) -#define cJSON_NULL (1 << 2) -#define cJSON_Number (1 << 3) -#define cJSON_String (1 << 4) -#define cJSON_Array (1 << 5) -#define cJSON_Object (1 << 6) -#define cJSON_Raw (1 << 7) /* raw json */ - -#define cJSON_IsReference 256 -#define cJSON_StringIsConst 512 - -/* The cJSON structure: */ -typedef struct cJSON -{ - /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ - struct cJSON *next; - struct cJSON *prev; - /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ - struct cJSON *child; - - /* The type of the item, as above. */ - int type; - - /* The item's string, if type==cJSON_String and type == cJSON_Raw */ - char *valuestring; - /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ - int valueint; - /* The item's number, if type==cJSON_Number */ - double valuedouble; - - /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ - char *string; -} cJSON; - -typedef struct cJSON_Hooks -{ - void *(*malloc_fn)(size_t sz); - void (*free_fn)(void *ptr); -} cJSON_Hooks; - -typedef int cJSON_bool; - -#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) -#define __WINDOWS__ -#endif -#ifdef __WINDOWS__ - -/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options: - -CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols -CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) -CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol - -For *nix builds that support visibility attribute, you can define similar behavior by - -setting default visibility to hidden by adding --fvisibility=hidden (for gcc) -or --xldscope=hidden (for sun cc) -to CFLAGS - -then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does - -*/ - -/* export symbols by default, this is necessary for copy pasting the C and header file */ -#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) -#define CJSON_EXPORT_SYMBOLS -#endif - -#if defined(CJSON_HIDE_SYMBOLS) -#define CJSON_PUBLIC(type) type __stdcall -#elif defined(CJSON_EXPORT_SYMBOLS) -#define CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall -#elif defined(CJSON_IMPORT_SYMBOLS) -#define CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall -#endif -#else /* !WIN32 */ -#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) -#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type -#else -#define CJSON_PUBLIC(type) type -#endif -#endif - -/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. - * This is to prevent stack overflows. */ -#ifndef CJSON_NESTING_LIMIT -#define CJSON_NESTING_LIMIT 1000 -#endif - -/* returns the version of cJSON as a string */ -CJSON_PUBLIC(const char*) cJSON_Version(void); - -/* Supply malloc, realloc and free functions to cJSON */ -CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); - -/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ -/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ -CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); -/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ -/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ -CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); - -/* Render a cJSON entity to text for transfer/storage. */ -CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); -/* Render a cJSON entity to text for transfer/storage without any formatting. */ -CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); -/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ -CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); -/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ -/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); -/* Delete a cJSON entity and all subentities. */ -CJSON_PUBLIC(void) cJSON_Delete(cJSON *c); - -/* Returns the number of items in an array (or object). */ -CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); -/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ -CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); -/* Get item "string" from object. Case insensitive. */ -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); -CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); -/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ -CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); - -/* These functions check the type of an item */ -CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); -CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); - -/* These calls create a cJSON item of the appropriate type. */ -CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); -CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); -CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); -/* raw json */ -CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); -CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); -CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); - -/* These utilities create an Array of count items. */ -CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); -CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count); - -/* Append item to the specified array/object. */ -CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item); -CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); -/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. - * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before - * writing to `item->string` */ -CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); -/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ -CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); -CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); - -/* Remove/Detatch items from Arrays/Objects. */ -CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); -CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); -CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); -CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); - -/* Update array items. */ -CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ -CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); -CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); -CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); -CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); - -/* Duplicate a cJSON item */ -CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); -/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will -need to be released. With recurse!=0, it will duplicate any children connected to the item. -The item->next and ->prev pointers are always zero on return from Duplicate. */ -/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. - * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ -CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); - - -CJSON_PUBLIC(void) cJSON_Minify(char *json); - -/* Macros for creating things quickly. */ -#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) -#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) -#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) -#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) -#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) -#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) -#define cJSON_AddRawToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateRaw(s)) - -/* When assigning an integer value, it needs to be propagated to valuedouble too. */ -#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) -/* helper for the cJSON_SetNumberValue macro */ -CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); -#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) - -/* Macro for iterating over an array or object */ -#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) - -/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ -CJSON_PUBLIC(void *) cJSON_malloc(size_t size); -CJSON_PUBLIC(void) cJSON_free(void *object); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/components/json/library/cJSON.c b/components/json/library/cJSON.c deleted file mode 100644 index cef719f57..000000000 --- a/components/json/library/cJSON.c +++ /dev/null @@ -1,2754 +0,0 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -/* cJSON */ -/* JSON parser in C. */ - -/* disable warnings about old C89 functions in MSVC */ -#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) -#define _CRT_SECURE_NO_DEPRECATE -#endif - -#ifdef __GNUC__ -#pragma GCC visibility push(default) -#endif -#if defined(_MSC_VER) -#pragma warning (push) -/* disable warning about single line comments in system headers */ -#pragma warning (disable : 4001) -#endif - -#include -#include -#include -#include -#include -#include -#include - -#ifdef ENABLE_LOCALES -#include -#endif - -#if defined(_MSC_VER) -#pragma warning (pop) -#endif -#ifdef __GNUC__ -#pragma GCC visibility pop -#endif - -#include "cJSON.h" - -/* define our own boolean type */ -#define true ((cJSON_bool)1) -#define false ((cJSON_bool)0) - -typedef struct { - const unsigned char *json; - size_t position; -} error; -static error global_error = { NULL, 0 }; - -CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) -{ - return (const char*) (global_error.json + global_error.position); -} - -/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ -#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 6) || (CJSON_VERSION_PATCH != 0) - #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. -#endif - -CJSON_PUBLIC(const char*) cJSON_Version(void) -{ - static char version[15]; - sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); - - return version; -} - -/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ -static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) -{ - if ((string1 == NULL) || (string2 == NULL)) - { - return 1; - } - - if (string1 == string2) - { - return 0; - } - - for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) - { - if (*string1 == '\0') - { - return 0; - } - } - - return tolower(*string1) - tolower(*string2); -} - -typedef struct internal_hooks -{ - void *(*allocate)(size_t size); - void (*deallocate)(void *pointer); - void *(*reallocate)(void *pointer, size_t size); -} internal_hooks; - -#if defined(_MSC_VER) -/* work around MSVC error C2322: '...' address of dillimport '...' is not static */ -static void *internal_malloc(size_t size) -{ - return malloc(size); -} -static void internal_free(void *pointer) -{ - free(pointer); -} -static void *internal_realloc(void *pointer, size_t size) -{ - return realloc(pointer, size); -} -#else -#define internal_malloc malloc -#define internal_free free -#define internal_realloc realloc -#endif - -static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; - -static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) -{ - size_t length = 0; - unsigned char *copy = NULL; - - if (string == NULL) - { - return NULL; - } - - length = strlen((const char*)string) + sizeof(""); - copy = (unsigned char*)hooks->allocate(length); - if (copy == NULL) - { - return NULL; - } - memcpy(copy, string, length); - - return copy; -} - -CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) -{ - if (hooks == NULL) - { - /* Reset hooks */ - global_hooks.allocate = malloc; - global_hooks.deallocate = free; - global_hooks.reallocate = realloc; - return; - } - - global_hooks.allocate = malloc; - if (hooks->malloc_fn != NULL) - { - global_hooks.allocate = hooks->malloc_fn; - } - - global_hooks.deallocate = free; - if (hooks->free_fn != NULL) - { - global_hooks.deallocate = hooks->free_fn; - } - - /* use realloc only if both free and malloc are used */ - global_hooks.reallocate = NULL; - if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) - { - global_hooks.reallocate = realloc; - } -} - -/* Internal constructor. */ -static cJSON *cJSON_New_Item(const internal_hooks * const hooks) -{ - cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); - if (node) - { - memset(node, '\0', sizeof(cJSON)); - } - - return node; -} - -/* Delete a cJSON structure. */ -CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) -{ - cJSON *next = NULL; - while (item != NULL) - { - next = item->next; - if (!(item->type & cJSON_IsReference) && (item->child != NULL)) - { - cJSON_Delete(item->child); - } - if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) - { - global_hooks.deallocate(item->valuestring); - } - if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) - { - global_hooks.deallocate(item->string); - } - global_hooks.deallocate(item); - item = next; - } -} - -/* get the decimal point character of the current locale */ -static unsigned char get_decimal_point(void) -{ -#ifdef ENABLE_LOCALES - struct lconv *lconv = localeconv(); - return (unsigned char) lconv->decimal_point[0]; -#else - return '.'; -#endif -} - -typedef struct -{ - const unsigned char *content; - size_t length; - size_t offset; - size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ - internal_hooks hooks; -} parse_buffer; - -/* check if the given size is left to read in a given parse buffer (starting with 1) */ -#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) -/* check if the buffer can be accessed at the given index (starting with 0) */ -#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) -#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) -/* get a pointer to the buffer at the position */ -#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) - -/* Parse the input text to generate a number, and populate the result into item. */ -static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) -{ - double number = 0; - unsigned char *after_end = NULL; - unsigned char number_c_string[64]; - unsigned char decimal_point = get_decimal_point(); - size_t i = 0; - - if ((input_buffer == NULL) || (input_buffer->content == NULL)) - { - return false; - } - - /* copy the number into a temporary buffer and replace '.' with the decimal point - * of the current locale (for strtod) - * This also takes care of '\0' not necessarily being available for marking the end of the input */ - for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) - { - switch (buffer_at_offset(input_buffer)[i]) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '+': - case '-': - case 'e': - case 'E': - number_c_string[i] = buffer_at_offset(input_buffer)[i]; - break; - - case '.': - number_c_string[i] = decimal_point; - break; - - default: - goto loop_end; - } - } -loop_end: - number_c_string[i] = '\0'; - - number = strtod((const char*)number_c_string, (char**)&after_end); - if (number_c_string == after_end) - { - return false; /* parse_error */ - } - - item->valuedouble = number; - - /* use saturation in case of overflow */ - if (number >= INT_MAX) - { - item->valueint = INT_MAX; - } - else if (number <= INT_MIN) - { - item->valueint = INT_MIN; - } - else - { - item->valueint = (int)number; - } - - item->type = cJSON_Number; - - input_buffer->offset += (size_t)(after_end - number_c_string); - return true; -} - -/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ -CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) -{ - if (number >= INT_MAX) - { - object->valueint = INT_MAX; - } - else if (number <= INT_MIN) - { - object->valueint = INT_MIN; - } - else - { - object->valueint = (int)number; - } - - return object->valuedouble = number; -} - -typedef struct -{ - unsigned char *buffer; - size_t length; - size_t offset; - size_t depth; /* current nesting depth (for formatted printing) */ - cJSON_bool noalloc; - cJSON_bool format; /* is this print a formatted print */ - internal_hooks hooks; -} printbuffer; - -/* realloc printbuffer if necessary to have at least "needed" bytes more */ -static unsigned char* ensure(printbuffer * const p, size_t needed) -{ - unsigned char *newbuffer = NULL; - size_t newsize = 0; - - if ((p == NULL) || (p->buffer == NULL)) - { - return NULL; - } - - if ((p->length > 0) && (p->offset >= p->length)) - { - /* make sure that offset is valid */ - return NULL; - } - - if (needed > INT_MAX) - { - /* sizes bigger than INT_MAX are currently not supported */ - return NULL; - } - - needed += p->offset + 1; - if (needed <= p->length) - { - return p->buffer + p->offset; - } - - if (p->noalloc) { - return NULL; - } - - /* calculate new buffer size */ - if (needed > (INT_MAX / 2)) - { - /* overflow of int, use INT_MAX if possible */ - if (needed <= INT_MAX) - { - newsize = INT_MAX; - } - else - { - return NULL; - } - } - else - { - newsize = needed * 2; - } - - if (p->hooks.reallocate != NULL) - { - /* reallocate with realloc if available */ - newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); - if (newbuffer == NULL) - { - p->hooks.deallocate(p->buffer); - p->length = 0; - p->buffer = NULL; - - return NULL; - } - } - else - { - /* otherwise reallocate manually */ - newbuffer = (unsigned char*)p->hooks.allocate(newsize); - if (!newbuffer) - { - p->hooks.deallocate(p->buffer); - p->length = 0; - p->buffer = NULL; - - return NULL; - } - if (newbuffer) - { - memcpy(newbuffer, p->buffer, p->offset + 1); - } - p->hooks.deallocate(p->buffer); - } - p->length = newsize; - p->buffer = newbuffer; - - return newbuffer + p->offset; -} - -/* calculate the new length of the string in a printbuffer and update the offset */ -static void update_offset(printbuffer * const buffer) -{ - const unsigned char *buffer_pointer = NULL; - if ((buffer == NULL) || (buffer->buffer == NULL)) - { - return; - } - buffer_pointer = buffer->buffer + buffer->offset; - - buffer->offset += strlen((const char*)buffer_pointer); -} - -/* Render the number nicely from the given item into a string. */ -static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - double d = item->valuedouble; - int length = 0; - size_t i = 0; - unsigned char number_buffer[26]; /* temporary buffer to print the number into */ - unsigned char decimal_point = get_decimal_point(); - double test; - - if (output_buffer == NULL) - { - return false; - } - - /* This checks for NaN and Infinity */ - if ((d * 0) != 0) - { - length = sprintf((char*)number_buffer, "null"); - } - else - { - /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ - length = sprintf((char*)number_buffer, "%1.15g", d); - - /* Check whether the original double can be recovered */ - if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((double)test != d)) - { - /* If not, print with 17 decimal places of precision */ - length = sprintf((char*)number_buffer, "%1.17g", d); - } - } - - /* sprintf failed or buffer overrun occured */ - if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) - { - return false; - } - - /* reserve appropriate space in the output */ - output_pointer = ensure(output_buffer, (size_t)length); - if (output_pointer == NULL) - { - return false; - } - - /* copy the printed number to the output and replace locale - * dependent decimal point with '.' */ - for (i = 0; i < ((size_t)length); i++) - { - if (number_buffer[i] == decimal_point) - { - output_pointer[i] = '.'; - continue; - } - - output_pointer[i] = number_buffer[i]; - } - output_pointer[i] = '\0'; - - output_buffer->offset += (size_t)length; - - return true; -} - -/* parse 4 digit hexadecimal number */ -static unsigned parse_hex4(const unsigned char * const input) -{ - unsigned int h = 0; - size_t i = 0; - - for (i = 0; i < 4; i++) - { - /* parse digit */ - if ((input[i] >= '0') && (input[i] <= '9')) - { - h += (unsigned int) input[i] - '0'; - } - else if ((input[i] >= 'A') && (input[i] <= 'F')) - { - h += (unsigned int) 10 + input[i] - 'A'; - } - else if ((input[i] >= 'a') && (input[i] <= 'f')) - { - h += (unsigned int) 10 + input[i] - 'a'; - } - else /* invalid */ - { - return 0; - } - - if (i < 3) - { - /* shift left to make place for the next nibble */ - h = h << 4; - } - } - - return h; -} - -/* converts a UTF-16 literal to UTF-8 - * A literal can be one or two sequences of the form \uXXXX */ -static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) -{ - long unsigned int codepoint = 0; - unsigned int first_code = 0; - const unsigned char *first_sequence = input_pointer; - unsigned char utf8_length = 0; - unsigned char utf8_position = 0; - unsigned char sequence_length = 0; - unsigned char first_byte_mark = 0; - - if ((input_end - first_sequence) < 6) - { - /* input ends unexpectedly */ - goto fail; - } - - /* get the first utf16 sequence */ - first_code = parse_hex4(first_sequence + 2); - - /* check that the code is valid */ - if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) - { - goto fail; - } - - /* UTF16 surrogate pair */ - if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) - { - const unsigned char *second_sequence = first_sequence + 6; - unsigned int second_code = 0; - sequence_length = 12; /* \uXXXX\uXXXX */ - - if ((input_end - second_sequence) < 6) - { - /* input ends unexpectedly */ - goto fail; - } - - if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) - { - /* missing second half of the surrogate pair */ - goto fail; - } - - /* get the second utf16 sequence */ - second_code = parse_hex4(second_sequence + 2); - /* check that the code is valid */ - if ((second_code < 0xDC00) || (second_code > 0xDFFF)) - { - /* invalid second half of the surrogate pair */ - goto fail; - } - - - /* calculate the unicode codepoint from the surrogate pair */ - codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); - } - else - { - sequence_length = 6; /* \uXXXX */ - codepoint = first_code; - } - - /* encode as UTF-8 - * takes at maximum 4 bytes to encode: - * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ - if (codepoint < 0x80) - { - /* normal ascii, encoding 0xxxxxxx */ - utf8_length = 1; - } - else if (codepoint < 0x800) - { - /* two bytes, encoding 110xxxxx 10xxxxxx */ - utf8_length = 2; - first_byte_mark = 0xC0; /* 11000000 */ - } - else if (codepoint < 0x10000) - { - /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ - utf8_length = 3; - first_byte_mark = 0xE0; /* 11100000 */ - } - else if (codepoint <= 0x10FFFF) - { - /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ - utf8_length = 4; - first_byte_mark = 0xF0; /* 11110000 */ - } - else - { - /* invalid unicode codepoint */ - goto fail; - } - - /* encode as utf8 */ - for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) - { - /* 10xxxxxx */ - (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); - codepoint >>= 6; - } - /* encode first byte */ - if (utf8_length > 1) - { - (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); - } - else - { - (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); - } - - *output_pointer += utf8_length; - - return sequence_length; - -fail: - return 0; -} - -/* Parse the input text into an unescaped cinput, and populate item. */ -static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) -{ - const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; - const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; - unsigned char *output_pointer = NULL; - unsigned char *output = NULL; - - /* not a string */ - if (buffer_at_offset(input_buffer)[0] != '\"') - { - goto fail; - } - - { - /* calculate approximate size of the output (overestimate) */ - size_t allocation_length = 0; - size_t skipped_bytes = 0; - while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) - { - /* is escape sequence */ - if (input_end[0] == '\\') - { - if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) - { - /* prevent buffer overflow when last input character is a backslash */ - goto fail; - } - skipped_bytes++; - input_end++; - } - input_end++; - } - if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) - { - goto fail; /* string ended unexpectedly */ - } - - /* This is at most how much we need for the output */ - allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; - output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); - if (output == NULL) - { - goto fail; /* allocation failure */ - } - } - - output_pointer = output; - /* loop through the string literal */ - while (input_pointer < input_end) - { - if (*input_pointer != '\\') - { - *output_pointer++ = *input_pointer++; - } - /* escape sequence */ - else - { - unsigned char sequence_length = 2; - if ((input_end - input_pointer) < 1) - { - goto fail; - } - - switch (input_pointer[1]) - { - case 'b': - *output_pointer++ = '\b'; - break; - case 'f': - *output_pointer++ = '\f'; - break; - case 'n': - *output_pointer++ = '\n'; - break; - case 'r': - *output_pointer++ = '\r'; - break; - case 't': - *output_pointer++ = '\t'; - break; - case '\"': - case '\\': - case '/': - *output_pointer++ = input_pointer[1]; - break; - - /* UTF-16 literal */ - case 'u': - sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); - if (sequence_length == 0) - { - /* failed to convert UTF16-literal to UTF-8 */ - goto fail; - } - break; - - default: - goto fail; - } - input_pointer += sequence_length; - } - } - - /* zero terminate the output */ - *output_pointer = '\0'; - - item->type = cJSON_String; - item->valuestring = (char*)output; - - input_buffer->offset = (size_t) (input_end - input_buffer->content); - input_buffer->offset++; - - return true; - -fail: - if (output != NULL) - { - input_buffer->hooks.deallocate(output); - } - - if (input_pointer != NULL) - { - input_buffer->offset = (size_t)(input_pointer - input_buffer->content); - } - - return false; -} - -/* Render the cstring provided to an escaped version that can be printed. */ -static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) -{ - const unsigned char *input_pointer = NULL; - unsigned char *output = NULL; - unsigned char *output_pointer = NULL; - size_t output_length = 0; - /* numbers of additional characters needed for escaping */ - size_t escape_characters = 0; - - if (output_buffer == NULL) - { - return false; - } - - /* empty string */ - if (input == NULL) - { - output = ensure(output_buffer, sizeof("\"\"")); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "\"\""); - - return true; - } - - /* set "flag" to 1 if something needs to be escaped */ - for (input_pointer = input; *input_pointer; input_pointer++) - { - switch (*input_pointer) - { - case '\"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - /* one character escape sequence */ - escape_characters++; - break; - default: - if (*input_pointer < 32) - { - /* UTF-16 escape sequence uXXXX */ - escape_characters += 5; - } - break; - } - } - output_length = (size_t)(input_pointer - input) + escape_characters; - - output = ensure(output_buffer, output_length + sizeof("\"\"")); - if (output == NULL) - { - return false; - } - - /* no characters have to be escaped */ - if (escape_characters == 0) - { - output[0] = '\"'; - memcpy(output + 1, input, output_length); - output[output_length + 1] = '\"'; - output[output_length + 2] = '\0'; - - return true; - } - - output[0] = '\"'; - output_pointer = output + 1; - /* copy the string */ - for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) - { - if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) - { - /* normal character, copy */ - *output_pointer = *input_pointer; - } - else - { - /* character needs to be escaped */ - *output_pointer++ = '\\'; - switch (*input_pointer) - { - case '\\': - *output_pointer = '\\'; - break; - case '\"': - *output_pointer = '\"'; - break; - case '\b': - *output_pointer = 'b'; - break; - case '\f': - *output_pointer = 'f'; - break; - case '\n': - *output_pointer = 'n'; - break; - case '\r': - *output_pointer = 'r'; - break; - case '\t': - *output_pointer = 't'; - break; - default: - /* escape and print as unicode codepoint */ - sprintf((char*)output_pointer, "u%04x", *input_pointer); - output_pointer += 4; - break; - } - } - } - output[output_length + 1] = '\"'; - output[output_length + 2] = '\0'; - - return true; -} - -/* Invoke print_string_ptr (which is useful) on an item. */ -static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) -{ - return print_string_ptr((unsigned char*)item->valuestring, p); -} - -/* Predeclare these prototypes. */ -static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); -static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); -static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); -static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); - -/* Utility to jump whitespace and cr/lf */ -static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) -{ - if ((buffer == NULL) || (buffer->content == NULL)) - { - return NULL; - } - - while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) - { - buffer->offset++; - } - - if (buffer->offset == buffer->length) - { - buffer->offset--; - } - - return buffer; -} - -/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ -static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) -{ - if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) - { - return NULL; - } - - if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) - { - buffer->offset += 3; - } - - return buffer; -} - -/* Parse an object - create a new root, and populate. */ -CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) -{ - parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; - cJSON *item = NULL; - - /* reset error position */ - global_error.json = NULL; - global_error.position = 0; - - if (value == NULL) - { - goto fail; - } - - buffer.content = (const unsigned char*)value; - buffer.length = strlen((const char*)value) + sizeof(""); - buffer.offset = 0; - buffer.hooks = global_hooks; - - item = cJSON_New_Item(&global_hooks); - if (item == NULL) /* memory fail */ - { - goto fail; - } - - if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) - { - /* parse failure. ep is set. */ - goto fail; - } - - /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ - if (require_null_terminated) - { - buffer_skip_whitespace(&buffer); - if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') - { - goto fail; - } - } - if (return_parse_end) - { - *return_parse_end = (const char*)buffer_at_offset(&buffer); - } - - return item; - -fail: - if (item != NULL) - { - cJSON_Delete(item); - } - - if (value != NULL) - { - error local_error; - local_error.json = (const unsigned char*)value; - local_error.position = 0; - - if (buffer.offset < buffer.length) - { - local_error.position = buffer.offset; - } - else if (buffer.length > 0) - { - local_error.position = buffer.length - 1; - } - - if (return_parse_end != NULL) - { - *return_parse_end = (const char*)local_error.json + local_error.position; - } - - global_error = local_error; - } - - return NULL; -} - -/* Default options for cJSON_Parse */ -CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) -{ - return cJSON_ParseWithOpts(value, 0, 0); -} - -#define cjson_min(a, b) ((a < b) ? a : b) - -static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) -{ - printbuffer buffer[1]; - unsigned char *printed = NULL; - - memset(buffer, 0, sizeof(buffer)); - - /* create buffer */ - buffer->buffer = (unsigned char*) hooks->allocate(256); - buffer->format = format; - buffer->hooks = *hooks; - if (buffer->buffer == NULL) - { - goto fail; - } - - /* print the value */ - if (!print_value(item, buffer)) - { - goto fail; - } - update_offset(buffer); - - /* check if reallocate is available */ - if (hooks->reallocate != NULL) - { - printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->length); - buffer->buffer = NULL; - if (printed == NULL) { - goto fail; - } - } - else /* otherwise copy the JSON over to a new buffer */ - { - printed = (unsigned char*) hooks->allocate(buffer->offset + 1); - if (printed == NULL) - { - goto fail; - } - memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); - printed[buffer->offset] = '\0'; /* just to be sure */ - - /* free the buffer */ - hooks->deallocate(buffer->buffer); - } - - return printed; - -fail: - if (buffer->buffer != NULL) - { - hooks->deallocate(buffer->buffer); - } - - if (printed != NULL) - { - hooks->deallocate(printed); - } - - return NULL; -} - -/* Render a cJSON item/entity/structure to text. */ -CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) -{ - return (char*)print(item, true, &global_hooks); -} - -CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) -{ - return (char*)print(item, false, &global_hooks); -} - -CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) -{ - printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; - - if (prebuffer < 0) - { - return NULL; - } - - p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); - if (!p.buffer) - { - return NULL; - } - - p.length = (size_t)prebuffer; - p.offset = 0; - p.noalloc = false; - p.format = fmt; - p.hooks = global_hooks; - - if (!print_value(item, &p)) - { - global_hooks.deallocate(p.buffer); - return NULL; - } - - return (char*)p.buffer; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buf, const int len, const cJSON_bool fmt) -{ - printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; - - if ((len < 0) || (buf == NULL)) - { - return false; - } - - p.buffer = (unsigned char*)buf; - p.length = (size_t)len; - p.offset = 0; - p.noalloc = true; - p.format = fmt; - p.hooks = global_hooks; - - return print_value(item, &p); -} - -/* Parser core - when encountering text, process appropriately. */ -static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) -{ - if ((input_buffer == NULL) || (input_buffer->content == NULL)) - { - return false; /* no input */ - } - - /* parse the different types of values */ - /* null */ - if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) - { - item->type = cJSON_NULL; - input_buffer->offset += 4; - return true; - } - /* false */ - if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) - { - item->type = cJSON_False; - input_buffer->offset += 5; - return true; - } - /* true */ - if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) - { - item->type = cJSON_True; - item->valueint = 1; - input_buffer->offset += 4; - return true; - } - /* string */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) - { - return parse_string(item, input_buffer); - } - /* number */ - if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) - { - return parse_number(item, input_buffer); - } - /* array */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) - { - return parse_array(item, input_buffer); - } - /* object */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) - { - return parse_object(item, input_buffer); - } - - return false; -} - -/* Render a value to text. */ -static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output = NULL; - - if ((item == NULL) || (output_buffer == NULL)) - { - return false; - } - - switch ((item->type) & 0xFF) - { - case cJSON_NULL: - output = ensure(output_buffer, 5); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "null"); - return true; - - case cJSON_False: - output = ensure(output_buffer, 6); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "false"); - return true; - - case cJSON_True: - output = ensure(output_buffer, 5); - if (output == NULL) - { - return false; - } - strcpy((char*)output, "true"); - return true; - - case cJSON_Number: - return print_number(item, output_buffer); - - case cJSON_Raw: - { - size_t raw_length = 0; - if (item->valuestring == NULL) - { - if (!output_buffer->noalloc) - { - output_buffer->hooks.deallocate(output_buffer->buffer); - } - return false; - } - - raw_length = strlen(item->valuestring) + sizeof(""); - output = ensure(output_buffer, raw_length); - if (output == NULL) - { - return false; - } - memcpy(output, item->valuestring, raw_length); - return true; - } - - case cJSON_String: - return print_string(item, output_buffer); - - case cJSON_Array: - return print_array(item, output_buffer); - - case cJSON_Object: - return print_object(item, output_buffer); - - default: - return false; - } -} - -/* Build an array from input text. */ -static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) -{ - cJSON *head = NULL; /* head of the linked list */ - cJSON *current_item = NULL; - - if (input_buffer->depth >= CJSON_NESTING_LIMIT) - { - return false; /* to deeply nested */ - } - input_buffer->depth++; - - if (buffer_at_offset(input_buffer)[0] != '[') - { - /* not an array */ - goto fail; - } - - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) - { - /* empty array */ - goto success; - } - - /* check if we skipped to the end of the buffer */ - if (cannot_access_at_index(input_buffer, 0)) - { - input_buffer->offset--; - goto fail; - } - - /* step back to character in front of the first element */ - input_buffer->offset--; - /* loop through the comma separated array elements */ - do - { - /* allocate next item */ - cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); - if (new_item == NULL) - { - goto fail; /* allocation failure */ - } - - /* attach next item to list */ - if (head == NULL) - { - /* start the linked list */ - current_item = head = new_item; - } - else - { - /* add to the end and advance */ - current_item->next = new_item; - new_item->prev = current_item; - current_item = new_item; - } - - /* parse next value */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_value(current_item, input_buffer)) - { - goto fail; /* failed to parse value */ - } - buffer_skip_whitespace(input_buffer); - } - while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); - - if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') - { - goto fail; /* expected end of array */ - } - -success: - input_buffer->depth--; - - item->type = cJSON_Array; - item->child = head; - - input_buffer->offset++; - - return true; - -fail: - if (head != NULL) - { - cJSON_Delete(head); - } - - return false; -} - -/* Render an array to text */ -static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - size_t length = 0; - cJSON *current_element = item->child; - - if (output_buffer == NULL) - { - return false; - } - - /* Compose the output array. */ - /* opening square bracket */ - output_pointer = ensure(output_buffer, 1); - if (output_pointer == NULL) - { - return false; - } - - *output_pointer = '['; - output_buffer->offset++; - output_buffer->depth++; - - while (current_element != NULL) - { - if (!print_value(current_element, output_buffer)) - { - return false; - } - update_offset(output_buffer); - if (current_element->next) - { - length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ','; - if(output_buffer->format) - { - *output_pointer++ = ' '; - } - *output_pointer = '\0'; - output_buffer->offset += length; - } - current_element = current_element->next; - } - - output_pointer = ensure(output_buffer, 2); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ']'; - *output_pointer = '\0'; - output_buffer->depth--; - - return true; -} - -/* Build an object from the text. */ -static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) -{ - cJSON *head = NULL; /* linked list head */ - cJSON *current_item = NULL; - - if (input_buffer->depth >= CJSON_NESTING_LIMIT) - { - return false; /* to deeply nested */ - } - input_buffer->depth++; - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) - { - goto fail; /* not an object */ - } - - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) - { - goto success; /* empty object */ - } - - /* check if we skipped to the end of the buffer */ - if (cannot_access_at_index(input_buffer, 0)) - { - input_buffer->offset--; - goto fail; - } - - /* step back to character in front of the first element */ - input_buffer->offset--; - /* loop through the comma separated array elements */ - do - { - /* allocate next item */ - cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); - if (new_item == NULL) - { - goto fail; /* allocation failure */ - } - - /* attach next item to list */ - if (head == NULL) - { - /* start the linked list */ - current_item = head = new_item; - } - else - { - /* add to the end and advance */ - current_item->next = new_item; - new_item->prev = current_item; - current_item = new_item; - } - - /* parse the name of the child */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_string(current_item, input_buffer)) - { - goto fail; /* faile to parse name */ - } - buffer_skip_whitespace(input_buffer); - - /* swap valuestring and string, because we parsed the name */ - current_item->string = current_item->valuestring; - current_item->valuestring = NULL; - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) - { - goto fail; /* invalid object */ - } - - /* parse the value */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_value(current_item, input_buffer)) - { - goto fail; /* failed to parse value */ - } - buffer_skip_whitespace(input_buffer); - } - while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) - { - goto fail; /* expected end of object */ - } - -success: - input_buffer->depth--; - - item->type = cJSON_Object; - item->child = head; - - input_buffer->offset++; - return true; - -fail: - if (head != NULL) - { - cJSON_Delete(head); - } - - return false; -} - -/* Render an object to text. */ -static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - size_t length = 0; - cJSON *current_item = item->child; - - if (output_buffer == NULL) - { - return false; - } - - /* Compose the output: */ - length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - - *output_pointer++ = '{'; - output_buffer->depth++; - if (output_buffer->format) - { - *output_pointer++ = '\n'; - } - output_buffer->offset += length; - - while (current_item) - { - if (output_buffer->format) - { - size_t i; - output_pointer = ensure(output_buffer, output_buffer->depth); - if (output_pointer == NULL) - { - return false; - } - for (i = 0; i < output_buffer->depth; i++) - { - *output_pointer++ = '\t'; - } - output_buffer->offset += output_buffer->depth; - } - - /* print key */ - if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) - { - return false; - } - update_offset(output_buffer); - - length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length); - if (output_pointer == NULL) - { - return false; - } - *output_pointer++ = ':'; - if (output_buffer->format) - { - *output_pointer++ = '\t'; - } - output_buffer->offset += length; - - /* print value */ - if (!print_value(current_item, output_buffer)) - { - return false; - } - update_offset(output_buffer); - - /* print comma if not last */ - length = (size_t) ((output_buffer->format ? 1 : 0) + (current_item->next ? 1 : 0)); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) - { - return false; - } - if (current_item->next) - { - *output_pointer++ = ','; - } - - if (output_buffer->format) - { - *output_pointer++ = '\n'; - } - *output_pointer = '\0'; - output_buffer->offset += length; - - current_item = current_item->next; - } - - output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); - if (output_pointer == NULL) - { - return false; - } - if (output_buffer->format) - { - size_t i; - for (i = 0; i < (output_buffer->depth - 1); i++) - { - *output_pointer++ = '\t'; - } - } - *output_pointer++ = '}'; - *output_pointer = '\0'; - output_buffer->depth--; - - return true; -} - -/* Get Array size/item / object item. */ -CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) -{ - cJSON *child = NULL; - size_t size = 0; - - if (array == NULL) - { - return 0; - } - - child = array->child; - - while(child != NULL) - { - size++; - child = child->next; - } - - /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ - - return (int)size; -} - -static cJSON* get_array_item(const cJSON *array, size_t index) -{ - cJSON *current_child = NULL; - - if (array == NULL) - { - return NULL; - } - - current_child = array->child; - while ((current_child != NULL) && (index > 0)) - { - index--; - current_child = current_child->next; - } - - return current_child; -} - -CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) -{ - if (index < 0) - { - return NULL; - } - - return get_array_item(array, (size_t)index); -} - -static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) -{ - cJSON *current_element = NULL; - - if ((object == NULL) || (name == NULL)) - { - return NULL; - } - - current_element = object->child; - if (case_sensitive) - { - while ((current_element != NULL) && (strcmp(name, current_element->string) != 0)) - { - current_element = current_element->next; - } - } - else - { - while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) - { - current_element = current_element->next; - } - } - - return current_element; -} - -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) -{ - return get_object_item(object, string, false); -} - -CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) -{ - return get_object_item(object, string, true); -} - -CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) -{ - return cJSON_GetObjectItem(object, string) ? 1 : 0; -} - -/* Utility for array list handling. */ -static void suffix_object(cJSON *prev, cJSON *item) -{ - prev->next = item; - item->prev = prev; -} - -/* Utility for handling references. */ -static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) -{ - cJSON *reference = NULL; - if (item == NULL) - { - return NULL; - } - - reference = cJSON_New_Item(hooks); - if (reference == NULL) - { - return NULL; - } - - memcpy(reference, item, sizeof(cJSON)); - reference->string = NULL; - reference->type |= cJSON_IsReference; - reference->next = reference->prev = NULL; - return reference; -} - -/* Add item to array/object. */ -CJSON_PUBLIC(void) cJSON_AddItemToArray(cJSON *array, cJSON *item) -{ - cJSON *child = NULL; - - if ((item == NULL) || (array == NULL)) - { - return; - } - - child = array->child; - - if (child == NULL) - { - /* list is empty, start new one */ - array->child = item; - } - else - { - /* append to the end */ - while (child->next) - { - child = child->next; - } - suffix_object(child, item); - } -} - -CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) -{ - if (item == NULL) - { - return; - } - - /* call cJSON_AddItemToObjectCS for code reuse */ - cJSON_AddItemToObjectCS(object, (char*)cJSON_strdup((const unsigned char*)string, &global_hooks), item); - /* remove cJSON_StringIsConst flag */ - item->type &= ~cJSON_StringIsConst; -} - -#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) - #pragma GCC diagnostic push -#endif -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -/* Add an item to an object with constant string as key */ -CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) -{ - if ((item == NULL) || (string == NULL)) - { - return; - } - if (!(item->type & cJSON_StringIsConst) && item->string) - { - global_hooks.deallocate(item->string); - } - item->string = (char*)string; - item->type |= cJSON_StringIsConst; - cJSON_AddItemToArray(object, item); -} -#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) - #pragma GCC diagnostic pop -#endif - -CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) -{ - if (array == NULL) - { - return; - } - - cJSON_AddItemToArray(array, create_reference(item, &global_hooks)); -} - -CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) -{ - if ((object == NULL) || (string == NULL)) - { - return; - } - - cJSON_AddItemToObject(object, string, create_reference(item, &global_hooks)); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) -{ - if ((parent == NULL) || (item == NULL)) - { - return NULL; - } - - if (item->prev != NULL) - { - /* not the first element */ - item->prev->next = item->next; - } - if (item->next != NULL) - { - /* not the last element */ - item->next->prev = item->prev; - } - - if (item == parent->child) - { - /* first element */ - parent->child = item->next; - } - /* make sure the detached item doesn't point anywhere anymore */ - item->prev = NULL; - item->next = NULL; - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) -{ - if (which < 0) - { - return NULL; - } - - return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) -{ - cJSON_Delete(cJSON_DetachItemFromArray(array, which)); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) -{ - cJSON *to_detach = cJSON_GetObjectItem(object, string); - - return cJSON_DetachItemViaPointer(object, to_detach); -} - -CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) -{ - cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); - - return cJSON_DetachItemViaPointer(object, to_detach); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) -{ - cJSON_Delete(cJSON_DetachItemFromObject(object, string)); -} - -CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) -{ - cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); -} - -/* Replace array/object items with new ones. */ -CJSON_PUBLIC(void) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) -{ - cJSON *after_inserted = NULL; - - if (which < 0) - { - return; - } - - after_inserted = get_array_item(array, (size_t)which); - if (after_inserted == NULL) - { - cJSON_AddItemToArray(array, newitem); - return; - } - - newitem->next = after_inserted; - newitem->prev = after_inserted->prev; - after_inserted->prev = newitem; - if (after_inserted == array->child) - { - array->child = newitem; - } - else - { - newitem->prev->next = newitem; - } -} - -CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) -{ - if ((parent == NULL) || (replacement == NULL) || (item == NULL)) - { - return false; - } - - if (replacement == item) - { - return true; - } - - replacement->next = item->next; - replacement->prev = item->prev; - - if (replacement->next != NULL) - { - replacement->next->prev = replacement; - } - if (replacement->prev != NULL) - { - replacement->prev->next = replacement; - } - if (parent->child == item) - { - parent->child = replacement; - } - - item->next = NULL; - item->prev = NULL; - cJSON_Delete(item); - - return true; -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) -{ - if (which < 0) - { - return; - } - - cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); -} - -static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) -{ - if ((replacement == NULL) || (string == NULL)) - { - return false; - } - - /* replace the name in the replacement */ - if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) - { - cJSON_free(replacement->string); - } - replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); - replacement->type &= ~cJSON_StringIsConst; - - cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); - - return true; -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) -{ - replace_item_in_object(object, string, newitem, false); -} - -CJSON_PUBLIC(void) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) -{ - replace_item_in_object(object, string, newitem, true); -} - -/* Create basic types: */ -CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_NULL; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_True; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_False; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool b) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = b ? cJSON_True : cJSON_False; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_Number; - item->valuedouble = num; - - /* use saturation in case of overflow */ - if (num >= INT_MAX) - { - item->valueint = INT_MAX; - } - else if (num <= INT_MIN) - { - item->valueint = INT_MIN; - } - else - { - item->valueint = (int)num; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_String; - item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); - if(!item->valuestring) - { - cJSON_Delete(item); - return NULL; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type = cJSON_Raw; - item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); - if(!item->valuestring) - { - cJSON_Delete(item); - return NULL; - } - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if(item) - { - item->type=cJSON_Array; - } - - return item; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) -{ - cJSON *item = cJSON_New_Item(&global_hooks); - if (item) - { - item->type = cJSON_Object; - } - - return item; -} - -/* Create Arrays: */ -CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if ((count < 0) || (numbers == NULL)) - { - return NULL; - } - - a = cJSON_CreateArray(); - for(i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber(numbers[i]); - if (!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if ((count < 0) || (numbers == NULL)) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for(i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber((double)numbers[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if ((count < 0) || (numbers == NULL)) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for(i = 0;a && (i < (size_t)count); i++) - { - n = cJSON_CreateNumber(numbers[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count) -{ - size_t i = 0; - cJSON *n = NULL; - cJSON *p = NULL; - cJSON *a = NULL; - - if ((count < 0) || (strings == NULL)) - { - return NULL; - } - - a = cJSON_CreateArray(); - - for (i = 0; a && (i < (size_t)count); i++) - { - n = cJSON_CreateString(strings[i]); - if(!n) - { - cJSON_Delete(a); - return NULL; - } - if(!i) - { - a->child = n; - } - else - { - suffix_object(p,n); - } - p = n; - } - - return a; -} - -/* Duplication */ -CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) -{ - cJSON *newitem = NULL; - cJSON *child = NULL; - cJSON *next = NULL; - cJSON *newchild = NULL; - - /* Bail on bad ptr */ - if (!item) - { - goto fail; - } - /* Create new item */ - newitem = cJSON_New_Item(&global_hooks); - if (!newitem) - { - goto fail; - } - /* Copy over all vars */ - newitem->type = item->type & (~cJSON_IsReference); - newitem->valueint = item->valueint; - newitem->valuedouble = item->valuedouble; - if (item->valuestring) - { - newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); - if (!newitem->valuestring) - { - goto fail; - } - } - if (item->string) - { - newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); - if (!newitem->string) - { - goto fail; - } - } - /* If non-recursive, then we're done! */ - if (!recurse) - { - return newitem; - } - /* Walk the ->next chain for the child. */ - child = item->child; - while (child != NULL) - { - newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ - if (!newchild) - { - goto fail; - } - if (next != NULL) - { - /* If newitem->child already set, then crosswire ->prev and ->next and move on */ - next->next = newchild; - newchild->prev = next; - next = newchild; - } - else - { - /* Set newitem->child and move to it */ - newitem->child = newchild; - next = newchild; - } - child = child->next; - } - - return newitem; - -fail: - if (newitem != NULL) - { - cJSON_Delete(newitem); - } - - return NULL; -} - -CJSON_PUBLIC(void) cJSON_Minify(char *json) -{ - unsigned char *into = (unsigned char*)json; - - if (json == NULL) - { - return; - } - - while (*json) - { - if (*json == ' ') - { - json++; - } - else if (*json == '\t') - { - /* Whitespace characters. */ - json++; - } - else if (*json == '\r') - { - json++; - } - else if (*json=='\n') - { - json++; - } - else if ((*json == '/') && (json[1] == '/')) - { - /* double-slash comments, to end of line. */ - while (*json && (*json != '\n')) - { - json++; - } - } - else if ((*json == '/') && (json[1] == '*')) - { - /* multiline comments. */ - while (*json && !((*json == '*') && (json[1] == '/'))) - { - json++; - } - json += 2; - } - else if (*json == '\"') - { - /* string literals, which are \" sensitive. */ - *into++ = (unsigned char)*json++; - while (*json && (*json != '\"')) - { - if (*json == '\\') - { - *into++ = (unsigned char)*json++; - } - *into++ = (unsigned char)*json++; - } - *into++ = (unsigned char)*json++; - } - else - { - /* All other characters. */ - *into++ = (unsigned char)*json++; - } - } - - /* and null-terminate. */ - *into = '\0'; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Invalid; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_False; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xff) == cJSON_True; -} - - -CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & (cJSON_True | cJSON_False)) != 0; -} -CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_NULL; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Number; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_String; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Array; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Object; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) -{ - if (item == NULL) - { - return false; - } - - return (item->type & 0xFF) == cJSON_Raw; -} - -CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) -{ - if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) - { - return false; - } - - /* check if type is valid */ - switch (a->type & 0xFF) - { - case cJSON_False: - case cJSON_True: - case cJSON_NULL: - case cJSON_Number: - case cJSON_String: - case cJSON_Raw: - case cJSON_Array: - case cJSON_Object: - break; - - default: - return false; - } - - /* identical objects are equal */ - if (a == b) - { - return true; - } - - switch (a->type & 0xFF) - { - /* in these cases and equal type is enough */ - case cJSON_False: - case cJSON_True: - case cJSON_NULL: - return true; - - case cJSON_Number: - if (a->valuedouble == b->valuedouble) - { - return true; - } - return false; - - case cJSON_String: - case cJSON_Raw: - if ((a->valuestring == NULL) || (b->valuestring == NULL)) - { - return false; - } - if (strcmp(a->valuestring, b->valuestring) == 0) - { - return true; - } - - return false; - - case cJSON_Array: - { - cJSON *a_element = a->child; - cJSON *b_element = b->child; - - for (; (a_element != NULL) && (b_element != NULL);) - { - if (!cJSON_Compare(a_element, b_element, case_sensitive)) - { - return false; - } - - a_element = a_element->next; - b_element = b_element->next; - } - - /* one of the arrays is longer than the other */ - if (a_element != b_element) { - return false; - } - - return true; - } - - case cJSON_Object: - { - cJSON *a_element = NULL; - cJSON *b_element = NULL; - cJSON_ArrayForEach(a_element, a) - { - /* TODO This has O(n^2) runtime, which is horrible! */ - b_element = get_object_item(b, a_element->string, case_sensitive); - if (b_element == NULL) - { - return false; - } - - if (!cJSON_Compare(a_element, b_element, case_sensitive)) - { - return false; - } - } - - /* doing this twice, once on a and b to prevent true comparison if a subset of b - * TODO: Do this the proper way, this is just a fix for now */ - cJSON_ArrayForEach(b_element, b) - { - a_element = get_object_item(a, b_element->string, case_sensitive); - if (a_element == NULL) - { - return false; - } - - if (!cJSON_Compare(b_element, a_element, case_sensitive)) - { - return false; - } - } - - return true; - } - - default: - return false; - } -} - -CJSON_PUBLIC(void *) cJSON_malloc(size_t size) -{ - return global_hooks.allocate(size); -} - -CJSON_PUBLIC(void) cJSON_free(void *object) -{ - global_hooks.deallocate(object); -} diff --git a/components/json/port/cJSON_Utils.c b/components/json/port/cJSON_Utils.c deleted file mode 100644 index b83cfcd4a..000000000 --- a/components/json/port/cJSON_Utils.c +++ /dev/null @@ -1,1443 +0,0 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -/* disable warnings about old C89 functions in MSVC */ -#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) -#define _CRT_SECURE_NO_DEPRECATE -#endif - -#ifdef __GNUCC__ -#pragma GCC visibility push(default) -#endif -#if defined(_MSC_VER) -#pragma warning (push) -/* disable warning about single line comments in system headers */ -#pragma warning (disable : 4001) -#endif - -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -#pragma warning (pop) -#endif -#ifdef __GNUCC__ -#pragma GCC visibility pop -#endif - -#include "cJSON_Utils.h" - -/* define our own boolean type */ -#define true ((cJSON_bool)1) -#define false ((cJSON_bool)0) - -static unsigned char* cJSONUtils_strdup(const unsigned char* const string) -{ - size_t length = 0; - unsigned char *copy = NULL; - - length = strlen((const char*)string) + sizeof(""); - copy = (unsigned char*) cJSON_malloc(length); - if (copy == NULL) - { - return NULL; - } - memcpy(copy, string, length); - - return copy; -} - -/* string comparison which doesn't consider NULL pointers equal */ -static int compare_strings(const unsigned char *string1, const unsigned char *string2, const cJSON_bool case_sensitive) -{ - if ((string1 == NULL) || (string2 == NULL)) - { - return 1; - } - - if (string1 == string2) - { - return 0; - } - - if (case_sensitive) - { - return strcmp((const char*)string1, (const char*)string2); - } - - for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) - { - if (*string1 == '\0') - { - return 0; - } - } - - return tolower(*string1) - tolower(*string2); -} - -/* Compare the next path element of two JSON pointers, two NULL pointers are considered unequal: */ -static cJSON_bool compare_pointers(const unsigned char *name, const unsigned char *pointer, const cJSON_bool case_sensitive) -{ - if ((name == NULL) || (pointer == NULL)) - { - return false; - } - - for (; (*name != '\0') && (*pointer != '\0') && (*pointer != '/'); (void)name++, pointer++) /* compare until next '/' */ - { - if (*pointer == '~') - { - /* check for escaped '~' (~0) and '/' (~1) */ - if (((pointer[1] != '0') || (*name != '~')) && ((pointer[1] != '1') || (*name != '/'))) - { - /* invalid escape sequence or wrong character in *name */ - return false; - } - else - { - pointer++; - } - } - else if ((!case_sensitive && (tolower(*name) != tolower(*pointer))) || (case_sensitive && (*name != *pointer))) - { - return false; - } - } - if (((*pointer != 0) && (*pointer != '/')) != (*name != 0)) - { - /* one string has ended, the other not */ - return false;; - } - - return true; -} - -/* calculate the length of a string if encoded as JSON pointer with ~0 and ~1 escape sequences */ -static size_t pointer_encoded_length(const unsigned char *string) -{ - size_t length; - for (length = 0; *string != '\0'; (void)string++, length++) - { - /* character needs to be escaped? */ - if ((*string == '~') || (*string == '/')) - { - length++; - } - } - - return length; -} - -/* copy a string while escaping '~' and '/' with ~0 and ~1 JSON pointer escape codes */ -static void encode_string_as_pointer(unsigned char *destination, const unsigned char *source) -{ - for (; source[0] != '\0'; (void)source++, destination++) - { - if (source[0] == '/') - { - destination[1] = '1'; - destination++; - } - else if (source[0] == '~') - { - destination[0] = '~'; - destination[1] = '1'; - destination++; - } - else - { - destination[0] = source[0]; - } - } - - destination[0] = '\0'; -} - -CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(const cJSON * const object, const cJSON * const target) -{ - size_t child_index = 0; - cJSON *current_child = 0; - - if ((object == NULL) || (target == NULL)) - { - return NULL; - } - - if (object == target) - { - /* found */ - return (char*)cJSONUtils_strdup((const unsigned char*)""); - } - - /* recursively search all children of the object or array */ - for (current_child = object->child; current_child != NULL; (void)(current_child = current_child->next), child_index++) - { - unsigned char *target_pointer = (unsigned char*)cJSONUtils_FindPointerFromObjectTo(current_child, target); - /* found the target? */ - if (target_pointer != NULL) - { - if (cJSON_IsArray(object)) - { - /* reserve enough memory for a 64 bit integer + '/' and '\0' */ - unsigned char *full_pointer = (unsigned char*)cJSON_malloc(strlen((char*)target_pointer) + 20 + sizeof("/")); - /* check if conversion to unsigned long is valid - * This should be eliminated at compile time by dead code elimination - * if size_t is an alias of unsigned long, or if it is bigger */ - if (child_index > ULONG_MAX) - { - cJSON_free(target_pointer); - return NULL; - } - sprintf((char*)full_pointer, "/%lu%s", (unsigned long)child_index, target_pointer); /* / */ - cJSON_free(target_pointer); - - return (char*)full_pointer; - } - - if (cJSON_IsObject(object)) - { - unsigned char *full_pointer = (unsigned char*)cJSON_malloc(strlen((char*)target_pointer) + pointer_encoded_length((unsigned char*)current_child->string) + 2); - full_pointer[0] = '/'; - encode_string_as_pointer(full_pointer + 1, (unsigned char*)current_child->string); - strcat((char*)full_pointer, (char*)target_pointer); - cJSON_free(target_pointer); - - return (char*)full_pointer; - } - - /* reached leaf of the tree, found nothing */ - cJSON_free(target_pointer); - return NULL; - } - } - - /* not found */ - return NULL; -} - -/* non broken version of cJSON_GetArrayItem */ -static cJSON *get_array_item(const cJSON *array, size_t item) -{ - cJSON *child = array ? array->child : NULL; - while ((child != NULL) && (item > 0)) - { - item--; - child = child->next; - } - - return child; -} - -static cJSON_bool decode_array_index_from_pointer(const unsigned char * const pointer, size_t * const index) -{ - size_t parsed_index = 0; - size_t position = 0; - - if ((pointer[0] == '0') && ((pointer[1] != '\0') && (pointer[1] != '/'))) - { - /* leading zeroes are not permitted */ - return 0; - } - - for (position = 0; (pointer[position] >= '0') && (pointer[0] <= '9'); position++) - { - parsed_index = (10 * parsed_index) + (size_t)(pointer[position] - '0'); - - } - - if ((pointer[position] != '\0') && (pointer[position] != '/')) - { - return 0; - } - - *index = parsed_index; - - return 1; -} - -static cJSON *get_item_from_pointer(cJSON * const object, const char * pointer, const cJSON_bool case_sensitive) -{ - cJSON *current_element = object; - - if (pointer == NULL) - { - return NULL; - } - - /* follow path of the pointer */ - while ((pointer[0] == '/') && (current_element != NULL)) - { - pointer++; - if (cJSON_IsArray(current_element)) - { - size_t index = 0; - if (!decode_array_index_from_pointer((const unsigned char*)pointer, &index)) - { - return NULL; - } - - current_element = get_array_item(current_element, index); - } - else if (cJSON_IsObject(current_element)) - { - current_element = current_element->child; - /* GetObjectItem. */ - while ((current_element != NULL) && !compare_pointers((unsigned char*)current_element->string, (const unsigned char*)pointer, case_sensitive)) - { - current_element = current_element->next; - } - } - else - { - return NULL; - } - - /* skip to the next path token or end of string */ - while ((pointer[0] != '\0') && (pointer[0] != '/')) - { - pointer++; - } - } - - return current_element; -} - -CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON * const object, const char *pointer) -{ - return get_item_from_pointer(object, pointer, false); -} - -CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointerCaseSensitive(cJSON * const object, const char *pointer) -{ - return get_item_from_pointer(object, pointer, true); -} - -/* JSON Patch implementation. */ -static void decode_pointer_inplace(unsigned char *string) -{ - unsigned char *decoded_string = string; - - if (string == NULL) { - return; - } - - for (; *string; (void)decoded_string++, string++) - { - if (string[0] == '~') - { - if (string[1] == '0') - { - decoded_string[0] = '~'; - } - else if (string[1] == '1') - { - decoded_string[1] = '/'; - } - else - { - /* invalid escape sequence */ - return; - } - - string++; - } - } - - decoded_string[0] = '\0'; -} - -/* non-broken cJSON_DetachItemFromArray */ -static cJSON *detach_item_from_array(cJSON *array, size_t which) -{ - cJSON *c = array->child; - while (c && (which > 0)) - { - c = c->next; - which--; - } - if (!c) - { - /* item doesn't exist */ - return NULL; - } - if (c->prev) - { - /* not the first element */ - c->prev->next = c->next; - } - if (c->next) - { - c->next->prev = c->prev; - } - if (c==array->child) - { - array->child = c->next; - } - /* make sure the detached item doesn't point anywhere anymore */ - c->prev = c->next = NULL; - - return c; -} - -/* detach an item at the given path */ -static cJSON *detach_path(cJSON *object, const unsigned char *path, const cJSON_bool case_sensitive) -{ - unsigned char *parent_pointer = NULL; - unsigned char *child_pointer = NULL; - cJSON *parent = NULL; - cJSON *detached_item = NULL; - - /* copy path and split it in parent and child */ - parent_pointer = cJSONUtils_strdup(path); - if (parent_pointer == NULL) { - goto cleanup; - } - - child_pointer = (unsigned char*)strrchr((char*)parent_pointer, '/'); /* last '/' */ - if (child_pointer == NULL) - { - goto cleanup; - } - /* split strings */ - child_pointer[0] = '\0'; - child_pointer++; - - parent = get_item_from_pointer(object, (char*)parent_pointer, case_sensitive); - decode_pointer_inplace(child_pointer); - - if (cJSON_IsArray(parent)) - { - size_t index = 0; - if (!decode_array_index_from_pointer(child_pointer, &index)) - { - goto cleanup; - } - detached_item = detach_item_from_array(parent, index); - } - else if (cJSON_IsObject(parent)) - { - detached_item = cJSON_DetachItemFromObject(parent, (char*)child_pointer); - } - else - { - /* Couldn't find object to remove child from. */ - goto cleanup; - } - -cleanup: - if (parent_pointer != NULL) - { - cJSON_free(parent_pointer); - } - - return detached_item; -} - -/* sort lists using mergesort */ -static cJSON *sort_list(cJSON *list, const cJSON_bool case_sensitive) -{ - cJSON *first = list; - cJSON *second = list; - cJSON *current_item = list; - cJSON *result = list; - cJSON *result_tail = NULL; - - if ((list == NULL) || (list->next == NULL)) - { - /* One entry is sorted already. */ - return result; - } - - while ((current_item != NULL) && (current_item->next != NULL) && (compare_strings((unsigned char*)current_item->string, (unsigned char*)current_item->next->string, case_sensitive) < 0)) - { - /* Test for list sorted. */ - current_item = current_item->next; - } - if ((current_item == NULL) || (current_item->next == NULL)) - { - /* Leave sorted lists unmodified. */ - return result; - } - - /* reset pointer to the beginning */ - current_item = list; - while (current_item != NULL) - { - /* Walk two pointers to find the middle. */ - second = second->next; - current_item = current_item->next; - /* advances current_item two steps at a time */ - if (current_item != NULL) - { - current_item = current_item->next; - } - } - if ((second != NULL) && (second->prev != NULL)) - { - /* Split the lists */ - second->prev->next = NULL; - } - - /* Recursively sort the sub-lists. */ - first = sort_list(first, case_sensitive); - second = sort_list(second, case_sensitive); - result = NULL; - - /* Merge the sub-lists */ - while ((first != NULL) && (second != NULL)) - { - cJSON *smaller = NULL; - if (compare_strings((unsigned char*)first->string, (unsigned char*)second->string, false) < 0) - { - smaller = first; - } - else - { - smaller = second; - } - - if (result == NULL) - { - /* start merged list with the smaller element */ - result_tail = smaller; - result = smaller; - } - else - { - /* add smaller element to the list */ - result_tail->next = smaller; - smaller->prev = result_tail; - result_tail = smaller; - } - - if (first == smaller) - { - first = first->next; - } - else - { - second = second->next; - } - } - - if (first != NULL) - { - /* Append rest of first list. */ - if (result == NULL) - { - return first; - } - result_tail->next = first; - first->prev = result_tail; - } - if (second != NULL) - { - /* Append rest of second list */ - if (result == NULL) - { - return second; - } - result_tail->next = second; - second->prev = result_tail; - } - - return result; -} - -static void sort_object(cJSON * const object, const cJSON_bool case_sensitive) -{ - if (object == NULL) - { - return; - } - object->child = sort_list(object->child, case_sensitive); -} - -static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensitive) -{ - if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) - { - /* mismatched type. */ - return false; - } - switch (a->type & 0xFF) - { - case cJSON_Number: - /* numeric mismatch. */ - if ((a->valueint != b->valueint) || (a->valuedouble != b->valuedouble)) - { - return false; - } - else - { - return true; - } - - case cJSON_String: - /* string mismatch. */ - if (strcmp(a->valuestring, b->valuestring) != 0) - { - return false; - } - else - { - return true; - } - - case cJSON_Array: - for ((void)(a = a->child), b = b->child; (a != NULL) && (b != NULL); (void)(a = a->next), b = b->next) - { - cJSON_bool identical = compare_json(a, b, case_sensitive); - if (!identical) - { - return false; - } - } - - /* array size mismatch? (one of both children is not NULL) */ - if ((a != NULL) || (b != NULL)) - { - return false; - } - else - { - return true; - } - - case cJSON_Object: - sort_object(a, case_sensitive); - sort_object(b, case_sensitive); - for ((void)(a = a->child), b = b->child; (a != NULL) && (b != NULL); (void)(a = a->next), b = b->next) - { - cJSON_bool identical = false; - /* compare object keys */ - if (compare_strings((unsigned char*)a->string, (unsigned char*)b->string, case_sensitive)) - { - /* missing member */ - return false; - } - identical = compare_json(a, b, case_sensitive); - if (!identical) - { - return false; - } - } - - /* object length mismatch (one of both children is not null) */ - if ((a != NULL) || (b != NULL)) - { - return false; - } - else - { - return true; - } - - default: - break; - } - - /* null, true or false */ - return true; -} - -/* non broken version of cJSON_InsertItemInArray */ -static cJSON_bool insert_item_in_array(cJSON *array, size_t which, cJSON *newitem) -{ - cJSON *child = array->child; - while (child && (which > 0)) - { - child = child->next; - which--; - } - if (which > 0) - { - /* item is after the end of the array */ - return 0; - } - if (child == NULL) - { - cJSON_AddItemToArray(array, newitem); - return 1; - } - - /* insert into the linked list */ - newitem->next = child; - newitem->prev = child->prev; - child->prev = newitem; - - /* was it at the beginning */ - if (child == array->child) - { - array->child = newitem; - } - else - { - newitem->prev->next = newitem; - } - - return 1; -} - -static cJSON *get_object_item(const cJSON * const object, const char* name, const cJSON_bool case_sensitive) -{ - if (case_sensitive) - { - return cJSON_GetObjectItemCaseSensitive(object, name); - } - - return cJSON_GetObjectItem(object, name); -} - -enum patch_operation { INVALID, ADD, REMOVE, REPLACE, MOVE, COPY, TEST }; - -static enum patch_operation decode_patch_operation(const cJSON * const patch, const cJSON_bool case_sensitive) -{ - cJSON *operation = get_object_item(patch, "op", case_sensitive); - if (!cJSON_IsString(operation)) - { - return INVALID; - } - - if (strcmp(operation->valuestring, "add") == 0) - { - return ADD; - } - - if (strcmp(operation->valuestring, "remove") == 0) - { - return REMOVE; - } - - if (strcmp(operation->valuestring, "replace") == 0) - { - return REPLACE; - } - - if (strcmp(operation->valuestring, "move") == 0) - { - return MOVE; - } - - if (strcmp(operation->valuestring, "copy") == 0) - { - return COPY; - } - - if (strcmp(operation->valuestring, "test") == 0) - { - return TEST; - } - - return INVALID; -} - -/* overwrite and existing item with another one and free resources on the way */ -static void overwrite_item(cJSON * const root, const cJSON replacement) -{ - if (root == NULL) - { - return; - } - - if (root->string != NULL) - { - cJSON_free(root->string); - } - if (root->valuestring != NULL) - { - cJSON_free(root->valuestring); - } - if (root->child != NULL) - { - cJSON_Delete(root->child); - } - - memcpy(root, &replacement, sizeof(cJSON)); -} - -static int apply_patch(cJSON *object, const cJSON *patch, const cJSON_bool case_sensitive) -{ - cJSON *path = NULL; - cJSON *value = NULL; - cJSON *parent = NULL; - enum patch_operation opcode = INVALID; - unsigned char *parent_pointer = NULL; - unsigned char *child_pointer = NULL; - int status = 0; - - path = get_object_item(patch, "path", case_sensitive); - if (!cJSON_IsString(path)) - { - /* malformed patch. */ - status = 2; - goto cleanup; - } - - opcode = decode_patch_operation(patch, case_sensitive); - if (opcode == INVALID) - { - status = 3; - goto cleanup; - } - else if (opcode == TEST) - { - /* compare value: {...} with the given path */ - status = !compare_json(get_item_from_pointer(object, path->valuestring, case_sensitive), get_object_item(patch, "value", case_sensitive), case_sensitive); - goto cleanup; - } - - /* special case for replacing the root */ - if (path->valuestring[0] == '\0') - { - if (opcode == REMOVE) - { - static const cJSON invalid = { NULL, NULL, NULL, cJSON_Invalid, NULL, 0, 0, NULL}; - - overwrite_item(object, invalid); - - status = 0; - goto cleanup; - } - - if ((opcode == REPLACE) || (opcode == ADD)) - { - value = get_object_item(patch, "value", case_sensitive); - if (value == NULL) - { - /* missing "value" for add/replace. */ - status = 7; - goto cleanup; - } - - value = cJSON_Duplicate(value, 1); - if (value == NULL) - { - /* out of memory for add/replace. */ - status = 8; - goto cleanup; - } - - overwrite_item(object, *value); - - /* delete the duplicated value */ - cJSON_free(value); - value = NULL; - - /* the string "value" isn't needed */ - if (object->string != NULL) - { - cJSON_free(object->string); - object->string = NULL; - } - - status = 0; - goto cleanup; - } - } - - if ((opcode == REMOVE) || (opcode == REPLACE)) - { - /* Get rid of old. */ - cJSON *old_item = detach_path(object, (unsigned char*)path->valuestring, case_sensitive); - if (old_item == NULL) - { - status = 13; - goto cleanup; - } - cJSON_Delete(old_item); - if (opcode == REMOVE) - { - /* For Remove, this job is done. */ - status = 0; - goto cleanup; - } - } - - /* Copy/Move uses "from". */ - if ((opcode == MOVE) || (opcode == COPY)) - { - cJSON *from = get_object_item(patch, "from", case_sensitive); - if (from == NULL) - { - /* missing "from" for copy/move. */ - status = 4; - goto cleanup; - } - - if (opcode == MOVE) - { - value = detach_path(object, (unsigned char*)from->valuestring, case_sensitive); - } - if (opcode == COPY) - { - value = get_item_from_pointer(object, from->valuestring, case_sensitive); - } - if (value == NULL) - { - /* missing "from" for copy/move. */ - status = 5; - goto cleanup; - } - if (opcode == COPY) - { - value = cJSON_Duplicate(value, 1); - } - if (value == NULL) - { - /* out of memory for copy/move. */ - status = 6; - goto cleanup; - } - } - else /* Add/Replace uses "value". */ - { - value = get_object_item(patch, "value", case_sensitive); - if (value == NULL) - { - /* missing "value" for add/replace. */ - status = 7; - goto cleanup; - } - value = cJSON_Duplicate(value, 1); - if (value == NULL) - { - /* out of memory for add/replace. */ - status = 8; - goto cleanup; - } - } - - /* Now, just add "value" to "path". */ - - /* split pointer in parent and child */ - parent_pointer = cJSONUtils_strdup((unsigned char*)path->valuestring); - child_pointer = (unsigned char*)strrchr((char*)parent_pointer, '/'); - if (child_pointer != NULL) - { - child_pointer[0] = '\0'; - child_pointer++; - } - parent = get_item_from_pointer(object, (char*)parent_pointer, case_sensitive); - decode_pointer_inplace(child_pointer); - - /* add, remove, replace, move, copy, test. */ - if ((parent == NULL) || (child_pointer == NULL)) - { - /* Couldn't find object to add to. */ - status = 9; - goto cleanup; - } - else if (cJSON_IsArray(parent)) - { - if (strcmp((char*)child_pointer, "-") == 0) - { - cJSON_AddItemToArray(parent, value); - value = NULL; - } - else - { - size_t index = 0; - if (!decode_array_index_from_pointer(child_pointer, &index)) - { - status = 11; - goto cleanup; - } - - if (!insert_item_in_array(parent, index, value)) - { - status = 10; - goto cleanup; - } - value = NULL; - } - } - else if (cJSON_IsObject(parent)) - { - if (case_sensitive) - { - cJSON_DeleteItemFromObjectCaseSensitive(parent, (char*)child_pointer); - } - else - { - cJSON_DeleteItemFromObject(parent, (char*)child_pointer); - } - cJSON_AddItemToObject(parent, (char*)child_pointer, value); - value = NULL; - } - -cleanup: - if (value != NULL) - { - cJSON_Delete(value); - } - if (parent_pointer != NULL) - { - cJSON_free(parent_pointer); - } - - return status; -} - -CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON * const object, const cJSON * const patches) -{ - const cJSON *current_patch = NULL; - int status = 0; - - if (!cJSON_IsArray(patches)) - { - /* malformed patches. */ - return 1; - } - - if (patches != NULL) - { - current_patch = patches->child; - } - - while (current_patch != NULL) - { - status = apply_patch(object, current_patch, false); - if (status != 0) - { - return status; - } - current_patch = current_patch->next; - } - - return 0; -} - -CJSON_PUBLIC(int) cJSONUtils_ApplyPatchesCaseSensitive(cJSON * const object, const cJSON * const patches) -{ - const cJSON *current_patch = NULL; - int status = 0; - - if (!cJSON_IsArray(patches)) - { - /* malformed patches. */ - return 1; - } - - if (patches != NULL) - { - current_patch = patches->child; - } - - while (current_patch != NULL) - { - status = apply_patch(object, current_patch, true); - if (status != 0) - { - return status; - } - current_patch = current_patch->next; - } - - return 0; -} - -static void compose_patch(cJSON * const patches, const unsigned char * const operation, const unsigned char * const path, const unsigned char *suffix, const cJSON * const value) -{ - cJSON *patch = NULL; - - if ((patches == NULL) || (operation == NULL) || (path == NULL)) - { - return; - } - - patch = cJSON_CreateObject(); - if (patch == NULL) - { - return; - } - cJSON_AddItemToObject(patch, "op", cJSON_CreateString((const char*)operation)); - - if (suffix == NULL) - { - cJSON_AddItemToObject(patch, "path", cJSON_CreateString((const char*)path)); - } - else - { - size_t suffix_length = pointer_encoded_length(suffix); - size_t path_length = strlen((const char*)path); - unsigned char *full_path = (unsigned char*)cJSON_malloc(path_length + suffix_length + sizeof("/")); - - sprintf((char*)full_path, "%s/", (const char*)path); - encode_string_as_pointer(full_path + path_length + 1, suffix); - - cJSON_AddItemToObject(patch, "path", cJSON_CreateString((const char*)full_path)); - cJSON_free(full_path); - } - - if (value != NULL) - { - cJSON_AddItemToObject(patch, "value", cJSON_Duplicate(value, 1)); - } - cJSON_AddItemToArray(patches, patch); -} - -CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON * const array, const char * const operation, const char * const path, const cJSON * const value) -{ - compose_patch(array, (const unsigned char*)operation, (const unsigned char*)path, NULL, value); -} - -static void create_patches(cJSON * const patches, const unsigned char * const path, cJSON * const from, cJSON * const to, const cJSON_bool case_sensitive) -{ - if ((from == NULL) || (to == NULL)) - { - return; - } - - if ((from->type & 0xFF) != (to->type & 0xFF)) - { - compose_patch(patches, (const unsigned char*)"replace", path, 0, to); - return; - } - - switch (from->type & 0xFF) - { - case cJSON_Number: - if ((from->valueint != to->valueint) || (from->valuedouble != to->valuedouble)) - { - compose_patch(patches, (const unsigned char*)"replace", path, NULL, to); - } - return; - - case cJSON_String: - if (strcmp(from->valuestring, to->valuestring) != 0) - { - compose_patch(patches, (const unsigned char*)"replace", path, NULL, to); - } - return; - - case cJSON_Array: - { - size_t index = 0; - cJSON *from_child = from->child; - cJSON *to_child = to->child; - unsigned char *new_path = (unsigned char*)cJSON_malloc(strlen((const char*)path) + 20 + sizeof("/")); /* Allow space for 64bit int. log10(2^64) = 20 */ - - /* generate patches for all array elements that exist in both "from" and "to" */ - for (index = 0; (from_child != NULL) && (to_child != NULL); (void)(from_child = from_child->next), (void)(to_child = to_child->next), index++) - { - /* check if conversion to unsigned long is valid - * This should be eliminated at compile time by dead code elimination - * if size_t is an alias of unsigned long, or if it is bigger */ - if (index > ULONG_MAX) - { - cJSON_free(new_path); - return; - } - sprintf((char*)new_path, "%s/%lu", path, (unsigned long)index); /* path of the current array element */ - create_patches(patches, new_path, from_child, to_child, case_sensitive); - } - - /* remove leftover elements from 'from' that are not in 'to' */ - for (; (from_child != NULL); (void)(from_child = from_child->next)) - { - /* check if conversion to unsigned long is valid - * This should be eliminated at compile time by dead code elimination - * if size_t is an alias of unsigned long, or if it is bigger */ - if (index > ULONG_MAX) - { - cJSON_free(new_path); - return; - } - sprintf((char*)new_path, "%lu", (unsigned long)index); - compose_patch(patches, (const unsigned char*)"remove", path, new_path, NULL); - } - /* add new elements in 'to' that were not in 'from' */ - for (; (to_child != NULL); (void)(to_child = to_child->next), index++) - { - compose_patch(patches, (const unsigned char*)"add", path, (const unsigned char*)"-", to_child); - } - cJSON_free(new_path); - return; - } - - case cJSON_Object: - { - cJSON *from_child = NULL; - cJSON *to_child = NULL; - sort_object(from, case_sensitive); - sort_object(to, case_sensitive); - - from_child = from->child; - to_child = to->child; - /* for all object values in the object with more of them */ - while ((from_child != NULL) || (to_child != NULL)) - { - int diff; - if (from_child == NULL) - { - diff = 1; - } - else if (to_child == NULL) - { - diff = -1; - } - else - { - diff = compare_strings((unsigned char*)from_child->string, (unsigned char*)to_child->string, case_sensitive); - } - - if (diff == 0) - { - /* both object keys are the same */ - size_t path_length = strlen((const char*)path); - size_t from_child_name_length = pointer_encoded_length((unsigned char*)from_child->string); - unsigned char *new_path = (unsigned char*)cJSON_malloc(path_length + from_child_name_length + sizeof("/")); - - sprintf((char*)new_path, "%s/", path); - encode_string_as_pointer(new_path + path_length + 1, (unsigned char*)from_child->string); - - /* create a patch for the element */ - create_patches(patches, new_path, from_child, to_child, case_sensitive); - cJSON_free(new_path); - - from_child = from_child->next; - to_child = to_child->next; - } - else if (diff < 0) - { - /* object element doesn't exist in 'to' --> remove it */ - compose_patch(patches, (const unsigned char*)"remove", path, (unsigned char*)from_child->string, NULL); - - from_child = from_child->next; - } - else - { - /* object element doesn't exist in 'from' --> add it */ - compose_patch(patches, (const unsigned char*)"add", path, (unsigned char*)to_child->string, to_child); - - to_child = to_child->next; - } - } - return; - } - - default: - break; - } -} - -CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON * const from, cJSON * const to) -{ - cJSON *patches = NULL; - - if ((from == NULL) || (to == NULL)) - { - return NULL; - } - - patches = cJSON_CreateArray(); - create_patches(patches, (const unsigned char*)"", from, to, false); - - return patches; -} - -CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatchesCaseSensitive(cJSON * const from, cJSON * const to) -{ - cJSON *patches = NULL; - - if ((from == NULL) || (to == NULL)) - { - return NULL; - } - - patches = cJSON_CreateArray(); - create_patches(patches, (const unsigned char*)"", from, to, true); - - return patches; -} - -CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON * const object) -{ - sort_object(object, false); -} - -CJSON_PUBLIC(void) cJSONUtils_SortObjectCaseSensitive(cJSON * const object) -{ - sort_object(object, true); -} - -static cJSON *merge_patch(cJSON *target, const cJSON * const patch, const cJSON_bool case_sensitive) -{ - cJSON *patch_child = NULL; - - if (!cJSON_IsObject(patch)) - { - /* scalar value, array or NULL, just duplicate */ - cJSON_Delete(target); - return cJSON_Duplicate(patch, 1); - } - - if (!cJSON_IsObject(target)) - { - cJSON_Delete(target); - target = cJSON_CreateObject(); - } - - patch_child = patch->child; - while (patch_child != NULL) - { - if (cJSON_IsNull(patch_child)) - { - /* NULL is the indicator to remove a value, see RFC7396 */ - if (case_sensitive) - { - cJSON_DeleteItemFromObjectCaseSensitive(target, patch_child->string); - } - else - { - cJSON_DeleteItemFromObject(target, patch_child->string); - } - } - else - { - cJSON *replace_me = NULL; - cJSON *replacement = NULL; - - if (case_sensitive) - { - replace_me = cJSON_DetachItemFromObjectCaseSensitive(target, patch_child->string); - } - else - { - replace_me = cJSON_DetachItemFromObject(target, patch_child->string); - } - - replacement = merge_patch(replace_me, patch_child, case_sensitive); - if (replacement == NULL) - { - return NULL; - } - - cJSON_AddItemToObject(target, patch_child->string, replacement); - } - patch_child = patch_child->next; - } - return target; -} - -CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, const cJSON * const patch) -{ - return merge_patch(target, patch, false); -} - -CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatchCaseSensitive(cJSON *target, const cJSON * const patch) -{ - return merge_patch(target, patch, true); -} - -static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const cJSON_bool case_sensitive) -{ - cJSON *from_child = NULL; - cJSON *to_child = NULL; - cJSON *patch = NULL; - if (to == NULL) - { - /* patch to delete everything */ - return cJSON_CreateNull(); - } - if (!cJSON_IsObject(to) || !cJSON_IsObject(from)) - { - return cJSON_Duplicate(to, 1); - } - - sort_object(from, case_sensitive); - sort_object(to, case_sensitive); - - from_child = from->child; - to_child = to->child; - patch = cJSON_CreateObject(); - while (from_child || to_child) - { - int diff; - if (from_child != NULL) - { - if (to_child != NULL) - { - diff = strcmp(from_child->string, to_child->string); - } - else - { - diff = -1; - } - } - else - { - diff = 1; - } - - if (diff < 0) - { - /* from has a value that to doesn't have -> remove */ - cJSON_AddItemToObject(patch, from_child->string, cJSON_CreateNull()); - - from_child = from_child->next; - } - else if (diff > 0) - { - /* to has a value that from doesn't have -> add to patch */ - cJSON_AddItemToObject(patch, to_child->string, cJSON_Duplicate(to_child, 1)); - - to_child = to_child->next; - } - else - { - /* object key exists in both objects */ - if (!compare_json(from_child, to_child, case_sensitive)) - { - /* not identical --> generate a patch */ - cJSON_AddItemToObject(patch, to_child->string, cJSONUtils_GenerateMergePatch(from_child, to_child)); - } - - /* next key in the object */ - from_child = from_child->next; - to_child = to_child->next; - } - } - if (patch->child == NULL) - { - /* no patch generated */ - cJSON_Delete(patch); - return NULL; - } - - return patch; -} - -CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON * const from, cJSON * const to) -{ - return generate_merge_patch(from, to, false); -} - -CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatchCaseSensitive(cJSON * const from, cJSON * const to) -{ - return generate_merge_patch(from, to, true); -} diff --git a/components/json/port/include/cJSON_Utils.h b/components/json/port/include/cJSON_Utils.h deleted file mode 100644 index 03ec10c9e..000000000 --- a/components/json/port/include/cJSON_Utils.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#include "cJSON.h" - -/* Implement RFC6901 (https://tools.ietf.org/html/rfc6901) JSON Pointer spec. */ -CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON * const object, const char *pointer); -CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointerCaseSensitive(cJSON * const object, const char *pointer); - -/* Implement RFC6902 (https://tools.ietf.org/html/rfc6902) JSON Patch spec. */ -/* NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key */ -CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON * const from, cJSON * const to); -CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatchesCaseSensitive(cJSON * const from, cJSON * const to); -/* Utility for generating patch array entries. */ -CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON * const array, const char * const operation, const char * const path, const cJSON * const value); -/* Returns 0 for success. */ -CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON * const object, const cJSON * const patches); -CJSON_PUBLIC(int) cJSONUtils_ApplyPatchesCaseSensitive(cJSON * const object, const cJSON * const patches); - -/* -// Note that ApplyPatches is NOT atomic on failure. To implement an atomic ApplyPatches, use: -//int cJSONUtils_AtomicApplyPatches(cJSON **object, cJSON *patches) -//{ -// cJSON *modme = cJSON_Duplicate(*object, 1); -// int error = cJSONUtils_ApplyPatches(modme, patches); -// if (!error) -// { -// cJSON_Delete(*object); -// *object = modme; -// } -// else -// { -// cJSON_Delete(modme); -// } -// -// return error; -//} -// Code not added to library since this strategy is a LOT slower. -*/ - -/* Implement RFC7386 (https://tools.ietf.org/html/rfc7396) JSON Merge Patch spec. */ -/* target will be modified by patch. return value is new ptr for target. */ -CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, const cJSON * const patch); -CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatchCaseSensitive(cJSON *target, const cJSON * const patch); -/* generates a patch to move from -> to */ -/* NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key */ -CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON * const from, cJSON * const to); -CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatchCaseSensitive(cJSON * const from, cJSON * const to); - -/* Given a root object and a target object, construct a pointer from one to the other. */ -CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(const cJSON * const object, const cJSON * const target); - -/* Sorts the members of the object into alphabetical order. */ -CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON * const object); -CJSON_PUBLIC(void) cJSONUtils_SortObjectCaseSensitive(cJSON * const object); diff --git a/components/log/include/esp_log.h b/components/log/include/esp_log.h index 2f251c6b8..c13a3f227 100644 --- a/components/log/include/esp_log.h +++ b/components/log/include/esp_log.h @@ -120,8 +120,12 @@ void esp_log_write(esp_log_level_t level, const char* tag, const char* format, . * @param level level of the log * */ -#define ESP_LOG_BUFFER_HEX_LEVEL( tag, buffer, buff_len, level ) do {\ - if ( LOG_LOCAL_LEVEL >= level ) esp_log_buffer_hex_internal( tag, buffer, buff_len, level ); } while(0) +#define ESP_LOG_BUFFER_HEX_LEVEL( tag, buffer, buff_len, level ) \ + do {\ + if ( LOG_LOCAL_LEVEL >= (level) ) { \ + esp_log_buffer_hex_internal( tag, buffer, buff_len, level ); \ + } \ + } while(0) /** * @brief Log a buffer of characters at specified level, separated into 16 bytes each line. Buffer should contain only printable characters. @@ -135,8 +139,12 @@ void esp_log_write(esp_log_level_t level, const char* tag, const char* format, . * @param level level of the log * */ -#define ESP_LOG_BUFFER_CHAR_LEVEL( tag, buffer, buff_len, level ) do {\ - if ( LOG_LOCAL_LEVEL >= level ) esp_log_buffer_char_internal( tag, buffer, buff_len, level ); } while(0) +#define ESP_LOG_BUFFER_CHAR_LEVEL( tag, buffer, buff_len, level ) \ + do {\ + if ( LOG_LOCAL_LEVEL >= (level) ) { \ + esp_log_buffer_char_internal( tag, buffer, buff_len, level ); \ + } \ + } while(0) /** * @brief Dump a buffer to the log at specified level. @@ -157,11 +165,13 @@ void esp_log_write(esp_log_level_t level, const char* tag, const char* format, . * * @param level level of the log */ -#define ESP_LOG_BUFFER_HEXDUMP( tag, buffer, buff_len, level ) do {\ - if ( LOG_LOCAL_LEVEL >= level ) esp_log_buffer_hexdump_internal( tag, buffer, buff_len, level); } while(0) +#define ESP_LOG_BUFFER_HEXDUMP( tag, buffer, buff_len, level ) \ + do { \ + if ( LOG_LOCAL_LEVEL >= (level) ) { \ + esp_log_buffer_hexdump_internal( tag, buffer, buff_len, level); \ + } \ + } while(0) - -#if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) /** * @brief Log a buffer of hex bytes at Info level * @@ -174,7 +184,12 @@ void esp_log_write(esp_log_level_t level, const char* tag, const char* format, . * @see ``esp_log_buffer_hex_level`` * */ -#define ESP_LOG_BUFFER_HEX(tag, buffer, buff_len) ESP_LOG_BUFFER_HEX_LEVEL( tag, buffer, buff_len, ESP_LOG_INFO ) +#define ESP_LOG_BUFFER_HEX(tag, buffer, buff_len) \ + do { \ + if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) { \ + ESP_LOG_BUFFER_HEX_LEVEL( tag, buffer, buff_len, ESP_LOG_INFO ); \ + }\ + } while(0) /** * @brief Log a buffer of characters at Info level. Buffer should contain only printable characters. @@ -188,12 +203,13 @@ void esp_log_write(esp_log_level_t level, const char* tag, const char* format, . * @see ``esp_log_buffer_char_level`` * */ -#define ESP_LOG_BUFFER_CHAR(tag, buffer, buff_len) ESP_LOG_BUFFER_CHAR_LEVEL( tag, buffer, buff_len, ESP_LOG_INFO ) +#define ESP_LOG_BUFFER_CHAR(tag, buffer, buff_len) \ + do { \ + if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) { \ + ESP_LOG_BUFFER_CHAR_LEVEL( tag, buffer, buff_len, ESP_LOG_INFO ); \ + }\ + } while(0) -#else -#define ESP_LOG_BUFFER_HEX(tag, buffer, buff_len) {} -#define ESP_LOG_BUFFER_CHAR(tag, buffer, buff_len) {} -#endif //to be back compatible #define esp_log_buffer_hex ESP_LOG_BUFFER_HEX diff --git a/components/log/log.c b/components/log/log.c index 370c8b01c..28bd89d80 100644 --- a/components/log/log.c +++ b/components/log/log.c @@ -164,7 +164,7 @@ void esp_log_level_set(const char* tag, esp_log_level_t level) #ifdef LOG_BUILTIN_CHECKS assert(i == 0 || s_log_cache[(i - 1) / 2].generation < s_log_cache[i].generation); #endif - if (s_log_cache[i].tag == tag) { + if (strcmp(s_log_cache[i].tag,tag) == 0) { s_log_cache[i].level = level; break; } @@ -419,8 +419,8 @@ void esp_log_buffer_hexdump_internal( const char *tag, const void *buffer, uint1 char temp_buffer[BYTES_PER_LINE+3]; //for not-byte-accessible memory const char *ptr_line; //format: field[length] - // ADDR[10]+" "+DATA_HEX[8*3]+" "+DATA_HEX[8*3]+" |"+DATA_CHAR[8]+"|" - char hd_buffer[10+2+BYTES_PER_LINE*3+3+BYTES_PER_LINE+1+1]; + // ADDR[10]+" "+DATA_HEX[8*3]+" "+DATA_HEX[8*3]+" |"+DATA_CHAR[8]+"|" + char hd_buffer[10+3+BYTES_PER_LINE*3+3+BYTES_PER_LINE+1+1]; char *ptr_hd; int bytes_cur_line; diff --git a/components/lwip/Kconfig b/components/lwip/Kconfig index a0b947863..b595544de 100644 --- a/components/lwip/Kconfig +++ b/components/lwip/Kconfig @@ -16,6 +16,17 @@ config L2_TO_L3_COPY Please make sure you fully understand the impact of this feature before enabling it. +config LWIP_IRAM_OPTIMIZATION + bool "Enable LWIP IRAM optimization" + default n + help + If this feature is enabled, some functions relating to RX/TX in LWIP will be + put into IRAM, it can improve UDP/TCP throughput by >10% for single core mode, + it doesn't help too much for dual core mode. On the other hand, it needs about + 10KB IRAM for these optimizations. + + If this feature is disabled, all lwip functions will be put into FLASH. + config LWIP_MAX_SOCKETS int "Max number of open sockets" range 1 32 diff --git a/components/lwip/api/api_lib.c b/components/lwip/api/api_lib.c index fd2ca1677..42d80a1ee 100755 --- a/components/lwip/api/api_lib.c +++ b/components/lwip/api/api_lib.c @@ -72,7 +72,7 @@ static err_t netconn_close_shutdown(struct netconn *conn, u8_t how); * @param apimsg a struct containing the function to call and its parameters * @return ERR_OK if the function was called, another err_t if not */ -static err_t +static err_t ESP_IRAM_ATTR tcpip_apimsg(struct api_msg *apimsg) { #if LWIP_DEBUG @@ -432,7 +432,7 @@ netconn_accept(struct netconn *conn, struct netconn **new_conn) * @return ERR_OK if data has been received, an error code otherwise (timeout, * memory error or another error) */ -static err_t +static err_t ESP_IRAM_ATTR netconn_recv_data(struct netconn *conn, void **new_buf) { void *buf = NULL; @@ -566,7 +566,7 @@ netconn_recv_tcp_pbuf(struct netconn *conn, struct pbuf **new_buf) * @return ERR_OK if data has been received, an error code otherwise (timeout, * memory error or another error) */ -err_t +err_t ESP_IRAM_ATTR netconn_recv(struct netconn *conn, struct netbuf **new_buf) { #if LWIP_TCP @@ -678,7 +678,7 @@ netconn_sendto(struct netconn *conn, struct netbuf *buf, const ip_addr_t *addr, * @param buf a netbuf containing the data to send * @return ERR_OK if data was sent, any other err_t on error */ -err_t +err_t ESP_IRAM_ATTR netconn_send(struct netconn *conn, struct netbuf *buf) { API_MSG_VAR_DECLARE(msg); diff --git a/components/lwip/api/api_msg.c b/components/lwip/api/api_msg.c index a3f21fb53..a684a33bb 100755 --- a/components/lwip/api/api_msg.c +++ b/components/lwip/api/api_msg.c @@ -1440,7 +1440,7 @@ lwip_netconn_do_listen(void *m) * * @param msg the api_msg_msg pointing to the connection */ -void +void ESP_IRAM_ATTR lwip_netconn_do_send(void *m) { struct api_msg_msg *msg = (struct api_msg_msg*)m; diff --git a/components/lwip/api/netbuf.c b/components/lwip/api/netbuf.c index 9ab76a463..2c33549ef 100755 --- a/components/lwip/api/netbuf.c +++ b/components/lwip/api/netbuf.c @@ -103,7 +103,7 @@ netbuf_delete(struct netbuf *buf) * @return pointer to the allocated memory * NULL if no memory could be allocated */ -void * +void * ESP_IRAM_ATTR netbuf_alloc(struct netbuf *buf, u16_t size) { LWIP_ERROR("netbuf_alloc: invalid buf", (buf != NULL), return NULL;); @@ -127,7 +127,7 @@ netbuf_alloc(struct netbuf *buf, u16_t size) * * @param buf pointer to the netbuf which contains the packet buffer to free */ -void +void ESP_IRAM_ATTR netbuf_free(struct netbuf *buf) { LWIP_ERROR("netbuf_free: invalid buf", (buf != NULL), return;); diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index b09cff19f..badde6613 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -491,7 +491,7 @@ lwip_socket_thread_cleanup(void) * @param s externally used socket index * @return struct lwip_sock for the socket or NULL if not found */ -static struct lwip_sock * +static struct lwip_sock * ESP_IRAM_ATTR get_socket(int s) { struct lwip_sock *sock; @@ -962,7 +962,7 @@ lwip_listen(int s, int backlog) return 0; } -int +int ESP_IRAM_ATTR lwip_recvfrom(int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) { @@ -1340,7 +1340,7 @@ lwip_sendmsg(int s, const struct msghdr *msg, int flags) #endif /* LWIP_UDP || LWIP_RAW */ } -int +int ESP_IRAM_ATTR lwip_sendto(int s, const void *data, size_t size, int flags, const struct sockaddr *to, socklen_t tolen) { @@ -1817,7 +1817,7 @@ return_copy_fdsets: * Callback registered in the netconn layer for each socket-netconn. * Processes recvevent (data available) and wakes up tasks waiting for select. */ -static void +static void ESP_IRAM_ATTR event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len) { int s; @@ -3159,7 +3159,7 @@ static void lwip_socket_drop_registered_memberships(int s) #if ESP_THREAD_SAFE -int +int ESP_IRAM_ATTR lwip_sendto_r(int s, const void *data, size_t size, int flags, const struct sockaddr *to, socklen_t tolen) { @@ -3176,7 +3176,7 @@ lwip_send_r(int s, const void *data, size_t size, int flags) LWIP_API_UNLOCK(); } -int +int ESP_IRAM_ATTR lwip_recvfrom_r(int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) { diff --git a/components/lwip/api/tcpip.c b/components/lwip/api/tcpip.c index 143cf917c..653f6c239 100755 --- a/components/lwip/api/tcpip.c +++ b/components/lwip/api/tcpip.c @@ -77,7 +77,7 @@ sys_mutex_t lock_tcpip_core; * * @param arg unused argument */ -static void +static void ESP_IRAM_ATTR tcpip_thread(void *arg) { @@ -195,7 +195,7 @@ tcpip_thread(void *arg) * @param inp the network interface on which the packet was received * @param input_fn input function to call */ -err_t +err_t ESP_IRAM_ATTR tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn) { #if LWIP_TCPIP_CORE_LOCKING_INPUT @@ -244,7 +244,7 @@ tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn) * NETIF_FLAG_ETHERNET flags) * @param inp the network interface on which the packet was received */ -err_t +err_t ESP_IRAM_ATTR tcpip_input(struct pbuf *p, struct netif *inp) { #if LWIP_ETHERNET @@ -363,7 +363,7 @@ tcpip_untimeout(sys_timeout_handler h, void *arg) * @param sem semaphore to wait on * @return ERR_OK if the function was called, another err_t if not */ -err_t +err_t ESP_IRAM_ATTR tcpip_send_api_msg(tcpip_callback_fn fn, void *apimsg, sys_sem_t* sem) { LWIP_ASSERT("semaphore not initialized", sys_sem_valid(sem)); diff --git a/components/lwip/apps/ping/esp_ping.h b/components/lwip/apps/ping/esp_ping.h index 9605c72af..d3c648828 100644 --- a/components/lwip/apps/ping/esp_ping.h +++ b/components/lwip/apps/ping/esp_ping.h @@ -21,7 +21,7 @@ extern "C" { #endif -#define ESP_ERR_PING_BASE 0x5000 +#define ESP_ERR_PING_BASE 0x6000 #define ESP_ERR_PING_INVALID_PARAMS ESP_ERR_PING_BASE + 0x00 #define ESP_ERR_PING_NO_MEM ESP_ERR_PING_BASE + 0x01 diff --git a/components/lwip/apps/ping/ping.c b/components/lwip/apps/ping/ping.c index 98ebda3e4..8799832db 100644 --- a/components/lwip/apps/ping/ping.c +++ b/components/lwip/apps/ping/ping.c @@ -123,7 +123,7 @@ ping_prepare_echo( struct icmp_echo_hdr *iecho, u16_t len) ICMPH_CODE_SET(iecho, 0); iecho->chksum = 0; iecho->id = PING_ID; - iecho->seqno = lwip_htons(++ping_seq_num); + iecho->seqno = htons(++ping_seq_num); /* fill the additional data buffer with some data */ for(i = 0; i < data_len; i++) { @@ -157,7 +157,7 @@ ping_send(int s, ip_addr_t *addr) to.sin_family = AF_INET; inet_addr_from_ipaddr(&to.sin_addr, ip_2_ip4(addr)); - err = lwip_sendto(s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to)); + err = sendto(s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to)); mem_free(iecho); @@ -175,7 +175,7 @@ ping_recv(int s) int fromlen = sizeof(from); struct timeval now; - while((len = lwip_recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) { + while((len = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) { if (len >= (int)(sizeof(struct ip_hdr)+sizeof(struct icmp_echo_hdr))) { if (from.sin_family != AF_INET) { /* Ping is not IPv4 */ @@ -186,12 +186,12 @@ ping_recv(int s) iphdr = (struct ip_hdr *)buf; iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4)); - LWIP_DEBUGF( PING_DEBUG, ("ping: recv seq=%d ", lwip_ntohs(iecho->seqno))); + LWIP_DEBUGF( PING_DEBUG, ("ping: recv seq=%d ", ntohs(iecho->seqno))); ip4_addr_debug_print(PING_DEBUG, &fromaddr); gettimeofday(&now, NULL); LWIP_DEBUGF( PING_DEBUG, (" %"U32_F" ms\n", PING_TIME_DIFF_MS(now, ping_time))); - if ((iecho->id == PING_ID) && (iecho->seqno == lwip_htons(ping_seq_num))) { + if ((iecho->id == PING_ID) && (iecho->seqno == htons(ping_seq_num))) { /* do some ping result processing */ #ifdef ESP_PING esp_ping_result((ICMPH_TYPE(iecho) == ICMP_ER), len, PING_TIME_DIFF_MS(now, ping_time)); @@ -254,11 +254,11 @@ ping_thread(void *arg) LWIP_UNUSED_ARG(arg); - if ((s = lwip_socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) { - return; + if ((s = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) { + goto _exit_new_socket_failed; } - ret = lwip_setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + ret = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); LWIP_ASSERT("setting receive timeout failed", ret == 0); LWIP_UNUSED_ARG(ret); @@ -289,8 +289,9 @@ ping_thread(void *arg) #ifdef ESP_PING _exit: - lwip_close(s); + close(s); +_exit_new_socket_failed: esp_ping_result(PING_RES_FINISH, 0, 0); SYS_ARCH_PROTECT(lev); if (ping_init_flag) { /* Ping closed by this thread */ @@ -330,7 +331,7 @@ ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr) pbuf_header(p, -PBUF_IP_HLEN) == 0) { iecho = (struct icmp_echo_hdr *)p->payload; - if ((iecho->id == PING_ID) && (iecho->seqno == lwip_htons(ping_seq_num))) { + if ((iecho->id == PING_ID) && (iecho->seqno == htons(ping_seq_num))) { LWIP_DEBUGF( PING_DEBUG, ("ping: recv ")); ip_addr_debug_print(PING_DEBUG, addr); gettimeofday(&now, NULL); diff --git a/components/lwip/core/inet_chksum.c b/components/lwip/core/inet_chksum.c index bcaa3dc82..f104f6b75 100755 --- a/components/lwip/core/inet_chksum.c +++ b/components/lwip/core/inet_chksum.c @@ -259,7 +259,7 @@ lwip_standard_chksum(const void *dataptr, int len) #endif /** Parts of the pseudo checksum which are common to IPv4 and IPv6 */ -static u16_t +static u16_t ESP_IRAM_ATTR inet_cksum_pseudo_base(struct pbuf *p, u8_t proto, u16_t proto_len, u32_t acc) { struct pbuf *q; @@ -309,7 +309,7 @@ inet_cksum_pseudo_base(struct pbuf *p, u8_t proto, u16_t proto_len, u32_t acc) * @param proto_len length of the ip data part (used for checksum of pseudo header) * @return checksum (as u16_t) to be saved directly in the protocol header */ -u16_t +u16_t ESP_IRAM_ATTR inet_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len, const ip4_addr_t *src, const ip4_addr_t *dest) { @@ -378,7 +378,7 @@ ip6_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len, * @param proto_len length of the ip data part (used for checksum of pseudo header) * @return checksum (as u16_t) to be saved directly in the protocol header */ -u16_t +u16_t ESP_IRAM_ATTR ip_chksum_pseudo(struct pbuf *p, u8_t proto, u16_t proto_len, const ip_addr_t *src, const ip_addr_t *dest) { diff --git a/components/lwip/core/ipv4/ip4.c b/components/lwip/core/ipv4/ip4.c index 04bf2181b..d2b9195a4 100755 --- a/components/lwip/core/ipv4/ip4.c +++ b/components/lwip/core/ipv4/ip4.c @@ -136,7 +136,7 @@ bool ip4_netif_exist(const ip4_addr_t *src, const ip4_addr_t *dest) * Source based IPv4 routing hook function. This function works only * when destination IP is broadcast IP. */ -struct netif * +struct netif * ESP_IRAM_ATTR ip4_route_src_hook(const ip4_addr_t *dest, const ip4_addr_t *src) { struct netif *netif = NULL; @@ -162,7 +162,7 @@ ip4_route_src_hook(const ip4_addr_t *dest, const ip4_addr_t *src) * Source based IPv4 routing must be fully implemented in * LWIP_HOOK_IP4_ROUTE_SRC(). This function only provides the parameters. */ -struct netif * +struct netif * ESP_IRAM_ATTR ip4_route_src(const ip4_addr_t *dest, const ip4_addr_t *src) { if (src != NULL) { @@ -189,7 +189,7 @@ ip4_route_src(const ip4_addr_t *dest, const ip4_addr_t *src) * @param dest the destination IP address for which to find the route * @return the netif on which to send to reach dest */ -struct netif * +struct netif * ESP_IRAM_ATTR ip4_route(const ip4_addr_t *dest) { struct netif *netif; @@ -410,7 +410,7 @@ return_noroute: * @return ERR_OK if the packet was processed (could return ERR_* if it wasn't * processed, but currently always returns ERR_OK) */ -err_t +err_t ESP_IRAM_ATTR ip4_input(struct pbuf *p, struct netif *inp) { struct ip_hdr *iphdr; @@ -818,7 +818,7 @@ ip4_output_if_opt(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest, * Same as ip_output_if() but 'src' address is not replaced by netif address * when it is 'any'. */ -err_t +err_t ESP_IRAM_ATTR ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest, u8_t ttl, u8_t tos, u8_t proto, struct netif *netif) @@ -831,7 +831,7 @@ ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest, * Same as ip_output_if_opt() but 'src' address is not replaced by netif address * when it is 'any'. */ -err_t +err_t ESP_IRAM_ATTR ip4_output_if_opt_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest, u8_t ttl, u8_t tos, u8_t proto, struct netif *netif, void *ip_options, u16_t optlen) diff --git a/components/lwip/core/ipv4/ip4_addr.c b/components/lwip/core/ipv4/ip4_addr.c index 0501b84e5..b143f36e2 100755 --- a/components/lwip/core/ipv4/ip4_addr.c +++ b/components/lwip/core/ipv4/ip4_addr.c @@ -55,7 +55,7 @@ const ip_addr_t ip_addr_broadcast = IPADDR4_INIT(IPADDR_BROADCAST); * @param netif the network interface against which the address is checked * @return returns non-zero if the address is a broadcast address */ -u8_t +u8_t ESP_IRAM_ATTR ip4_addr_isbroadcast_u32(u32_t addr, const struct netif *netif) { ip4_addr_t ipaddr; diff --git a/components/lwip/core/pbuf.c b/components/lwip/core/pbuf.c index 8c2eb05a5..a80a002a4 100755 --- a/components/lwip/core/pbuf.c +++ b/components/lwip/core/pbuf.c @@ -513,7 +513,7 @@ pbuf_realloc(struct pbuf *p, u16_t new_len) * @return non-zero on failure, zero on success. * */ -static u8_t +static u8_t ESP_IRAM_ATTR pbuf_header_impl(struct pbuf *p, s16_t header_size_increment, u8_t force) { u16_t type; @@ -609,7 +609,7 @@ pbuf_header_impl(struct pbuf *p, s16_t header_size_increment, u8_t force) * @return non-zero on failure, zero on success. * */ -u8_t +u8_t ESP_IRAM_ATTR pbuf_header(struct pbuf *p, s16_t header_size_increment) { return pbuf_header_impl(p, header_size_increment, 0); diff --git a/components/lwip/core/timers.c b/components/lwip/core/timers.c index ef47b2e18..3358a6a54 100755 --- a/components/lwip/core/timers.c +++ b/components/lwip/core/timers.c @@ -535,7 +535,7 @@ sys_timeouts_sleeptime(void) * @param mbox the mbox to fetch the message from * @param msg the place to store the message */ -void +void ESP_IRAM_ATTR sys_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg) { u32_t time_needed; diff --git a/components/lwip/core/udp.c b/components/lwip/core/udp.c index 779dd9ad7..489e48705 100755 --- a/components/lwip/core/udp.c +++ b/components/lwip/core/udp.c @@ -146,7 +146,7 @@ again: * @param broadcast 1 if his is an IPv4 broadcast (global or subnet-only), 0 otherwise (only used for IPv4) * @return 1 on match, 0 otherwise */ -static u8_t +static u8_t ESP_IRAM_ATTR udp_input_local_match(struct udp_pcb *pcb, struct netif *inp, u8_t broadcast) { LWIP_UNUSED_ARG(inp); /* in IPv6 only case */ @@ -210,7 +210,7 @@ udp_input_local_match(struct udp_pcb *pcb, struct netif *inp, u8_t broadcast) * @param inp network interface on which the datagram was received. * */ -void +void ESP_IRAM_ATTR udp_input(struct pbuf *p, struct netif *inp) { struct udp_hdr *udphdr; @@ -474,7 +474,7 @@ chkerr: * * @see udp_disconnect() udp_sendto() */ -err_t +err_t ESP_IRAM_ATTR udp_send(struct udp_pcb *pcb, struct pbuf *p) { if ((pcb == NULL) || IP_IS_ANY_TYPE_VAL(pcb->remote_ip)) { @@ -519,7 +519,7 @@ udp_send_chksum(struct udp_pcb *pcb, struct pbuf *p, * * @see udp_disconnect() udp_send() */ -err_t +err_t ESP_IRAM_ATTR udp_sendto(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip, u16_t dst_port) { @@ -615,7 +615,7 @@ udp_sendto_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip, * * @see udp_disconnect() udp_send() */ -err_t +err_t ESP_IRAM_ATTR udp_sendto_if(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif) { @@ -681,7 +681,7 @@ udp_sendto_if_chksum(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_i } /** Same as udp_sendto_if(), but with source address */ -err_t +err_t ESP_IRAM_ATTR udp_sendto_if_src(struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *dst_ip, u16_t dst_port, struct netif *netif, const ip_addr_t *src_ip) { diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 4652c6d4a..3e8d26ee3 100644 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -727,6 +727,12 @@ #define ESP_LWIP_LOGI(...) ESP_LOGI("lwip", __VA_ARGS__) #define ESP_PING 1 +#if CONFIG_LWIP_IRAM_OPTIMIZATION +#define ESP_IRAM_ATTR IRAM_ATTR +#else +#define ESP_IRAM_ATTR +#endif + #define TCP_WND_DEFAULT CONFIG_TCP_WND_DEFAULT #define TCP_SND_BUF_DEFAULT CONFIG_TCP_SND_BUF_DEFAULT diff --git a/components/lwip/netif/etharp.c b/components/lwip/netif/etharp.c index d9854dbb0..54f7487ac 100755 --- a/components/lwip/netif/etharp.c +++ b/components/lwip/netif/etharp.c @@ -404,7 +404,7 @@ etharp_find_entry(const ip4_addr_t *ipaddr, u8_t flags, struct netif* netif) * @params dst the destination MAC address to be copied into the ethernet header * @return ERR_OK if the packet was sent, any other err_t on failure */ -static err_t +static err_t ESP_IRAM_ATTR etharp_send_ip(struct netif *netif, struct pbuf *p, struct eth_addr *src, const struct eth_addr *dst) { struct eth_hdr *ethhdr = (struct eth_hdr *)p->payload; @@ -872,7 +872,7 @@ etharp_arp_input(struct netif *netif, struct eth_addr *ethaddr, struct pbuf *p) /** Just a small helper function that sends a pbuf to an ethernet address * in the arp_table specified by the index 'arp_idx'. */ -static err_t +static err_t ESP_IRAM_ATTR etharp_output_to_arp_index(struct netif *netif, struct pbuf *q, u8_t arp_idx) { LWIP_ASSERT("arp_table[arp_idx].state >= ETHARP_STATE_STABLE", @@ -916,7 +916,7 @@ etharp_output_to_arp_index(struct netif *netif, struct pbuf *q, u8_t arp_idx) * - ERR_RTE No route to destination (no gateway to external networks), * or the return type of either etharp_query() or etharp_send_ip(). */ -err_t +err_t ESP_IRAM_ATTR etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr) { const struct eth_addr *dest; diff --git a/components/lwip/netif/ethernet.c b/components/lwip/netif/ethernet.c index 90f62583b..340ce0a51 100755 --- a/components/lwip/netif/ethernet.c +++ b/components/lwip/netif/ethernet.c @@ -63,7 +63,7 @@ const struct eth_addr ethzero = {{0,0,0,0,0,0}}; * @param p the received packet, p->payload pointing to the ethernet header * @param netif the network interface on which the packet was received */ -err_t +err_t ESP_IRAM_ATTR ethernet_input(struct pbuf *p, struct netif *netif) { struct eth_hdr* ethhdr; diff --git a/components/lwip/port/freertos/sys_arch.c b/components/lwip/port/freertos/sys_arch.c index aa58f9bb1..77eaef0a1 100755 --- a/components/lwip/port/freertos/sys_arch.c +++ b/components/lwip/port/freertos/sys_arch.c @@ -72,7 +72,7 @@ sys_mutex_new(sys_mutex_t *pxMutex) /** Lock a mutex * @param mutex the mutex to lock */ -void +void ESP_IRAM_ATTR sys_mutex_lock(sys_mutex_t *pxMutex) { while (xSemaphoreTake(*pxMutex, portMAX_DELAY) != pdPASS); @@ -87,7 +87,7 @@ sys_mutex_trylock(sys_mutex_t *pxMutex) /** Unlock a mutex * @param mutex the mutex to unlock */ -void +void ESP_IRAM_ATTR sys_mutex_unlock(sys_mutex_t *pxMutex) { xSemaphoreGive(*pxMutex); @@ -127,7 +127,7 @@ sys_sem_new(sys_sem_t *sem, u8_t count) /*-----------------------------------------------------------------------------------*/ // Signals a semaphore -void +void ESP_IRAM_ATTR sys_sem_signal(sys_sem_t *sem) { xSemaphoreGive(*sem); @@ -149,7 +149,7 @@ sys_sem_signal(sys_sem_t *sem) Notice that lwIP implements a function with a similar name, sys_sem_wait(), that uses the sys_arch_sem_wait() function. */ -u32_t +u32_t ESP_IRAM_ATTR sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout) { portTickType StartTime, EndTime, Elapsed; @@ -228,14 +228,14 @@ sys_mbox_new(sys_mbox_t *mbox, int size) /*-----------------------------------------------------------------------------------*/ // Posts the "msg" to the mailbox. -void +void ESP_IRAM_ATTR sys_mbox_post(sys_mbox_t *mbox, void *msg) { while (xQueueSendToBack((*mbox)->os_mbox, &msg, portMAX_DELAY) != pdTRUE); } /*-----------------------------------------------------------------------------------*/ -err_t +err_t ESP_IRAM_ATTR sys_mbox_trypost(sys_mbox_t *mbox, void *msg) { err_t xReturn; @@ -266,7 +266,7 @@ sys_mbox_trypost(sys_mbox_t *mbox, void *msg) Note that a function with a similar name, sys_mbox_fetch(), is implemented by lwIP. */ -u32_t +u32_t ESP_IRAM_ATTR sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout) { void *dummyptr; diff --git a/components/lwip/port/netif/wlanif.c b/components/lwip/port/netif/wlanif.c index 7f145624b..7c60b6934 100644 --- a/components/lwip/port/netif/wlanif.c +++ b/components/lwip/port/netif/wlanif.c @@ -101,7 +101,7 @@ low_level_init(struct netif *netif) * to become availale since the stack doesn't retry to send a packet * dropped because of memory failure (except for the TCP timers). */ -static err_t +static err_t ESP_IRAM_ATTR low_level_output(struct netif *netif, struct pbuf *p) { wifi_interface_t wifi_if = tcpip_adapter_get_esp_if(netif); @@ -139,7 +139,7 @@ low_level_output(struct netif *netif, struct pbuf *p) * * @param netif the lwip network interface structure for this ethernetif */ -void +void ESP_IRAM_ATTR wlanif_input(struct netif *netif, void *buffer, u16_t len, void* eb) { struct pbuf *p; diff --git a/components/mdns/component.mk b/components/mdns/component.mk index e69de29bb..064cc0608 100644 --- a/components/mdns/component.mk +++ b/components/mdns/component.mk @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_PRIV_INCLUDEDIRS := private_include diff --git a/components/mdns/include/mdns.h b/components/mdns/include/mdns.h index c0855466a..db0e3b522 100644 --- a/components/mdns/include/mdns.h +++ b/components/mdns/include/mdns.h @@ -20,35 +20,69 @@ extern "C" { #ifndef MDNS_TEST_MODE #include +#include "esp_event.h" #else #include "esp32_compat.h" #endif -struct mdns_server_s; -typedef struct mdns_server_s mdns_server_t; +#define MDNS_TYPE_A 0x0001 +#define MDNS_TYPE_PTR 0x000C +#define MDNS_TYPE_TXT 0x0010 +#define MDNS_TYPE_AAAA 0x001C +#define MDNS_TYPE_SRV 0x0021 +#define MDNS_TYPE_OPT 0x0029 +#define MDNS_TYPE_NSEC 0x002F +#define MDNS_TYPE_ANY 0x00FF /** - * @brief mDNS query result structure - * + * @brief mDNS enum to specify the ip_protocol type + */ +typedef enum { + MDNS_IP_PROTOCOL_V4, + MDNS_IP_PROTOCOL_V6, + MDNS_IP_PROTOCOL_MAX +} mdns_ip_protocol_t; + +/** + * @brief mDNS basic text item structure + * Used in mdns_service_add() + */ +typedef struct { + char * key; /*!< item key name */ + char * value; /*!< item value string */ +} mdns_txt_item_t; + +/** + * @brief mDNS query linked list IP item + */ +typedef struct mdns_ip_addr_s { + ip_addr_t addr; /*!< IP address */ + struct mdns_ip_addr_s * next; /*!< next IP, or NULL for the last IP in the list */ +} mdns_ip_addr_t; + +/** + * @brief mDNS query result structure */ typedef struct mdns_result_s { - const char * host; /*!< hostname */ - const char * instance; /*!< instance */ - const char * txt; /*!< txt data */ - uint16_t priority; /*!< service priority */ - uint16_t weight; /*!< service weight */ - uint16_t port; /*!< service port */ - struct ip4_addr addr; /*!< ip4 address */ - struct ip6_addr addrv6; /*!< ip6 address */ - const struct mdns_result_s * next; /*!< next result, or NULL for the last result in the list */ + struct mdns_result_s * next; /*!< next result, or NULL for the last result in the list */ + + tcpip_adapter_if_t tcpip_if; /*!< interface on which the result came (AP/STA/ETH) */ + mdns_ip_protocol_t ip_protocol; /*!< ip_protocol type of the interface (v4/v6) */ + // PTR + char * instance_name; /*!< instance name */ + // SRV + char * hostname; /*!< hostname */ + uint16_t port; /*!< service port */ + // TXT + mdns_txt_item_t * txt; /*!< txt record */ + size_t txt_count; /*!< number of txt items */ + // A and AAAA + mdns_ip_addr_t * addr; /*!< linked list of IP addreses found */ } mdns_result_t; /** * @brief Initialize mDNS on given interface * - * @param tcpip_if Interface that the server will listen on - * @param server Server pointer to populate on success - * * @return * - ESP_OK on success * - ESP_ERR_INVALID_ARG when bad tcpip_if is given @@ -56,20 +90,18 @@ typedef struct mdns_result_s { * - ESP_ERR_NO_MEM on memory error * - ESP_ERR_WIFI_NOT_INIT when WiFi is not initialized by eps_wifi_init */ -esp_err_t mdns_init(tcpip_adapter_if_t tcpip_if, mdns_server_t ** server); +esp_err_t mdns_init(); /** * @brief Stop and free mDNS server * - * @param server mDNS Server to free - * */ -void mdns_free(mdns_server_t * server); +void mdns_free(); /** * @brief Set the hostname for mDNS server + * required if you want to advertise services * - * @param server mDNS Server * @param hostname Hostname to set * * @return @@ -77,41 +109,42 @@ void mdns_free(mdns_server_t * server); * - ESP_ERR_INVALID_ARG Parameter error * - ESP_ERR_NO_MEM memory error */ -esp_err_t mdns_set_hostname(mdns_server_t * server, const char * hostname); +esp_err_t mdns_hostname_set(const char * hostname); /** * @brief Set the default instance name for mDNS server * - * @param server mDNS Server - * @param instance Instance name to set + * @param instance_name Instance name to set * * @return * - ESP_OK success * - ESP_ERR_INVALID_ARG Parameter error * - ESP_ERR_NO_MEM memory error */ -esp_err_t mdns_set_instance(mdns_server_t * server, const char * instance); +esp_err_t mdns_instance_name_set(const char * instance_name); /** * @brief Add service to mDNS server * - * @param server mDNS Server - * @param service service type (_http, _ftp, etc) - * @param proto service protocol (_tcp, _udp) - * @param port service port + * @param instance_name instance name to set. If NULL, + * global instance name or hostname will be used + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param port service port + * @param num_items number of items in TXT data + * @param txt string array of TXT data (eg. {{"var","val"},{"other","2"}}) * * @return * - ESP_OK success * - ESP_ERR_INVALID_ARG Parameter error * - ESP_ERR_NO_MEM memory error */ -esp_err_t mdns_service_add(mdns_server_t * server, const char * service, const char * proto, uint16_t port); +esp_err_t mdns_service_add(const char * instance_name, const char * service_type, const char * proto, uint16_t port, mdns_txt_item_t txt[], size_t num_items); /** * @brief Remove service from mDNS server * - * @param server mDNS Server - * @param service service type (_http, _ftp, etc) + * @param service_type service type (_http, _ftp, etc) * @param proto service protocol (_tcp, _udp) * * @return @@ -120,15 +153,14 @@ esp_err_t mdns_service_add(mdns_server_t * server, const char * service, const c * - ESP_ERR_NOT_FOUND Service not found * - ESP_FAIL unknown error */ -esp_err_t mdns_service_remove(mdns_server_t * server, const char * service, const char * proto); +esp_err_t mdns_service_remove(const char * service_type, const char * proto); /** * @brief Set instance name for service * - * @param server mDNS Server - * @param service service type (_http, _ftp, etc) - * @param proto service protocol (_tcp, _udp) - * @param instance instance name to set + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param instance_name instance name to set * * @return * - ESP_OK success @@ -136,30 +168,12 @@ esp_err_t mdns_service_remove(mdns_server_t * server, const char * service, cons * - ESP_ERR_NOT_FOUND Service not found * - ESP_ERR_NO_MEM memory error */ -esp_err_t mdns_service_instance_set(mdns_server_t * server, const char * service, const char * proto, const char * instance); - -/** - * @brief Set TXT data for service - * - * @param server mDNS Server - * @param service service type (_http, _ftp, etc) - * @param proto service protocol (_tcp, _udp) - * @param num_items number of items in TXT data - * @param txt string array of TXT data (eg. {"var=val","other=2"}) - * - * @return - * - ESP_OK success - * - ESP_ERR_INVALID_ARG Parameter error - * - ESP_ERR_NOT_FOUND Service not found - * - ESP_ERR_NO_MEM memory error - */ -esp_err_t mdns_service_txt_set(mdns_server_t * server, const char * service, const char * proto, uint8_t num_items, const char ** txt); +esp_err_t mdns_service_instance_name_set(const char * service_type, const char * proto, const char * instance_name); /** * @brief Set service port * - * @param server mDNS Server - * @param service service type (_http, _ftp, etc) + * @param service_type service type (_http, _ftp, etc) * @param proto service protocol (_tcp, _udp) * @param port service port * @@ -168,69 +182,182 @@ esp_err_t mdns_service_txt_set(mdns_server_t * server, const char * service, con * - ESP_ERR_INVALID_ARG Parameter error * - ESP_ERR_NOT_FOUND Service not found */ -esp_err_t mdns_service_port_set(mdns_server_t * server, const char * service, const char * proto, uint16_t port); +esp_err_t mdns_service_port_set(const char * service_type, const char * proto, uint16_t port); + +/** + * @brief Replace all TXT items for service + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param num_items number of items in TXT data + * @param txt array of TXT data (eg. {{"var","val"},{"other","2"}}) + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_service_txt_set(const char * service_type, const char * proto, mdns_txt_item_t txt[], uint8_t num_items); + +/** + * @brief Set/Add TXT item for service TXT record + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param key the key that you want to add/update + * @param value the new value of the key + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_service_txt_item_set(const char * service_type, const char * proto, const char * key, const char * value); + +/** + * @brief Remove TXT item for service TXT record + * + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param key the key that you want to remove + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_NOT_FOUND Service not found + * - ESP_ERR_NO_MEM memory error + */ +esp_err_t mdns_service_txt_item_remove(const char * service_type, const char * proto, const char * key); /** * @brief Remove and free all services from mDNS server * - * @param server mDNS Server - * * @return * - ESP_OK success * - ESP_ERR_INVALID_ARG Parameter error */ -esp_err_t mdns_service_remove_all(mdns_server_t * server); +esp_err_t mdns_service_remove_all(); /** * @brief Query mDNS for host or service + * All following query methods are derived from this one * - * @param server mDNS Server - * @param service service type or host name - * @param proto service protocol or NULL if searching for host - * @param timeout time to wait for answers. If 0, mdns_query_end MUST be called to end the search - * - * @return the number of results found - */ -size_t mdns_query(mdns_server_t * server, const char * service, const char * proto, uint32_t timeout); - -/** - * @brief Stop mDNS Query started with timeout = 0 - * - * @param server mDNS Server - * - * @return the number of results found - */ -size_t mdns_query_end(mdns_server_t * server); - -/** - * @brief get the number of results currently in memoty - * - * @param server mDNS Server - * - * @return the number of results - */ -size_t mdns_result_get_count(mdns_server_t * server); - -/** - * @brief Get mDNS Search result with given index - * - * @param server mDNS Server - * @param num the index of the result - * - * @return the result or NULL if error - */ -const mdns_result_t * mdns_result_get(mdns_server_t * server, size_t num); - -/** - * @brief Remove and free all search results from mDNS server - * - * @param server mDNS Server + * @param name service instance or host name (NULL for PTR queries) + * @param service_type service type (_http, _arduino, _ftp etc.) (NULL for host queries) + * @param proto service protocol (_tcp, _udp, etc.) (NULL for host queries) + * @param type type of query (MDNS_TYPE_*) + * @param timeout time in milliseconds to wait for answers. + * @param max_results maximum results to be collected + * @param results pointer to the results of the query + * results must be freed using mdns_query_results_free below * * @return * - ESP_OK success - * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG timeout was not given */ -esp_err_t mdns_result_free(mdns_server_t * server); +esp_err_t mdns_query(const char * name, const char * service_type, const char * proto, uint16_t type, uint32_t timeout, size_t max_results, mdns_result_t ** results); + +/** + * @brief Free query results + * + * @param results linked list of results to be freed + */ +void mdns_query_results_free(mdns_result_t * results); + +/** + * @brief Query mDNS for service + * + * @param service_type service type (_http, _arduino, _ftp etc.) + * @param proto service protocol (_tcp, _udp, etc.) + * @param timeout time in milliseconds to wait for answer. + * @param max_results maximum results to be collected + * @param results pointer to the results of the query + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_ptr(const char * service_type, const char * proto, uint32_t timeout, size_t max_results, mdns_result_t ** results); + +/** + * @brief Query mDNS for SRV record + * + * @param instance_name service instance name + * @param service_type service type (_http, _arduino, _ftp etc.) + * @param proto service protocol (_tcp, _udp, etc.) + * @param timeout time in milliseconds to wait for answer. + * @param result pointer to the result of the query + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_srv(const char * instance_name, const char * service_type, const char * proto, uint32_t timeout, mdns_result_t ** result); + +/** + * @brief Query mDNS for TXT record + * + * @param instance_name service instance name + * @param service_type service type (_http, _arduino, _ftp etc.) + * @param proto service protocol (_tcp, _udp, etc.) + * @param timeout time in milliseconds to wait for answer. + * @param result pointer to the result of the query + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_txt(const char * instance_name, const char * service_type, const char * proto, uint32_t timeout, mdns_result_t ** result); + +/** + * @brief Query mDNS for A record + * + * @param host_name host name to look for + * @param timeout time in milliseconds to wait for answer. + * @param addr pointer to the resulting IP4 address + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_a(const char * host_name, uint32_t timeout, ip4_addr_t * addr); + +/** + * @brief Query mDNS for A record + * + * @param host_name host name to look for + * @param timeout time in milliseconds to wait for answer. If 0, max_results needs to be defined + * @param addr pointer to the resulting IP6 address + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_STATE mDNS is not running + * - ESP_ERR_NO_MEM memory error + * - ESP_ERR_INVALID_ARG parameter error + */ +esp_err_t mdns_query_aaaa(const char * host_name, uint32_t timeout, ip6_addr_t * addr); + +/** + * @brief System event handler + * This method controls the service state on all active interfaces and applications are required + * to call it from the system event handler for normal operation of mDNS service. + * + * @param ctx The system event context + * @param event The system event + */ +esp_err_t mdns_handle_system_event(void *ctx, system_event_t *event); #ifdef __cplusplus } diff --git a/components/mdns/include/mdns_console.h b/components/mdns/include/mdns_console.h new file mode 100644 index 000000000..5c8b0b5a4 --- /dev/null +++ b/components/mdns/include/mdns_console.h @@ -0,0 +1,22 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _MDNS_CONSOLE_H_ +#define _MDNS_CONSOLE_H_ + +/** + * @brief Register MDNS functions with the console component + */ +void mdns_console_register(); + +#endif /* _MDNS_CONSOLE_H_ */ diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c index 171da177c..13368c3ed 100644 --- a/components/mdns/mdns.c +++ b/components/mdns/mdns.c @@ -14,7 +14,6 @@ #include "mdns.h" #include -#ifndef MDNS_TEST_MODE #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/queue.h" @@ -23,316 +22,359 @@ #include "lwip/pbuf.h" #include "lwip/igmp.h" #include "lwip/udp.h" -#include "lwip/tcpip.h" +#include "lwip/mld6.h" #include "lwip/priv/tcpip_priv.h" #include "esp_wifi.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "esp_event_loop.h" + +#include "mdns_private.h" + +#ifdef MDNS_ENABLE_DEBUG +void mdns_debug_packet(const uint8_t * data, size_t len); #endif -#define MDNS_FLAGS_AUTHORITATIVE 0x8400 - -#define MDNS_NAME_REF 0xC000 - -#define MDNS_TYPE_AAAA 0x001C -#define MDNS_TYPE_A 0x0001 -#define MDNS_TYPE_PTR 0x000C -#define MDNS_TYPE_SRV 0x0021 -#define MDNS_TYPE_TXT 0x0010 -#define MDNS_TYPE_NSEC 0x002F -#define MDNS_TYPE_ANY 0x00FF - -#define MDNS_CLASS_IN 0x0001 -#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 - -#define MDNS_ANSWER_ALL 0x3F -#define MDNS_ANSWER_PTR 0x08 -#define MDNS_ANSWER_TXT 0x04 -#define MDNS_ANSWER_SRV 0x02 -#define MDNS_ANSWER_A 0x01 -#define MDNS_ANSWER_AAAA 0x10 -#define MDNS_ANSWER_NSEC 0x20 -#define MDNS_ANSWER_SDPTR 0x80 - -#define MDNS_SERVICE_PORT 5353 // UDP port that the server runs on -#define MDNS_SERVICE_STACK_DEPTH 4096 // Stack size for the service thread -#define MDNS_PACKET_QUEUE_LEN 16 // Maximum packets that can be queued for parsing -#define MDNS_TXT_MAX_LEN 1024 // Maximum string length of text data in TXT record -#define MDNS_NAME_MAX_LEN 64 // Maximum string length of hostname, instance, service and proto -#define MDNS_NAME_BUF_LEN (MDNS_NAME_MAX_LEN+1) // Maximum char buffer size to hold hostname, instance, service or proto -#define MDNS_MAX_PACKET_SIZE 1460 // Maximum size of mDNS outgoing packet - -#define MDNS_ANSWER_PTR_TTL 4500 -#define MDNS_ANSWER_TXT_TTL 4500 -#define MDNS_ANSWER_SRV_TTL 120 -#define MDNS_ANSWER_A_TTL 120 -#define MDNS_ANSWER_AAAA_TTL 120 - -#define MDNS_HEAD_LEN 12 -#define MDNS_HEAD_ID_OFFSET 0 -#define MDNS_HEAD_FLAGS_OFFSET 2 -#define MDNS_HEAD_QUESTIONS_OFFSET 4 -#define MDNS_HEAD_ANSWERS_OFFSET 6 -#define MDNS_HEAD_SERVERS_OFFSET 8 -#define MDNS_HEAD_ADDITIONAL_OFFSET 10 - -#define MDNS_TYPE_OFFSET 0 -#define MDNS_CLASS_OFFSET 2 -#define MDNS_TTL_OFFSET 4 -#define MDNS_LEN_OFFSET 8 -#define MDNS_DATA_OFFSET 10 - -#define MDNS_SRV_PRIORITY_OFFSET 0 -#define MDNS_SRV_WEIGHT_OFFSET 2 -#define MDNS_SRV_PORT_OFFSET 4 -#define MDNS_SRV_FQDN_OFFSET 6 - -typedef struct { - char host[MDNS_NAME_BUF_LEN]; - char service[MDNS_NAME_BUF_LEN]; - char proto[MDNS_NAME_BUF_LEN]; - char domain[MDNS_NAME_BUF_LEN]; - uint8_t parts; - uint8_t sub; -} mdns_name_t; - -typedef struct { - char host[MDNS_NAME_BUF_LEN]; - char instance[MDNS_NAME_BUF_LEN]; - char txt[MDNS_TXT_MAX_LEN]; - uint16_t priority; - uint16_t weight; - uint16_t port; - uint32_t addr; - uint8_t addrv6[16]; - uint8_t ptr; -} mdns_result_temp_t; - -typedef struct { - const char * host; - const char * sub; - const char * service; - const char * proto; - const char * domain; - uint8_t parts; - uint8_t done; -} mdns_string_t; - -typedef struct mdns_service_s { - const char * instance; - const char * service; - const char * proto; - uint16_t priority; - uint16_t weight; - uint16_t port; - uint8_t txt_num_items; - const char ** txt; -} mdns_service_t; - -typedef struct mdns_srv_item_s { - mdns_service_t * service; - struct mdns_srv_item_s * next; -} mdns_srv_item_t; - -typedef struct mdns_answer_item_s { - mdns_service_t * service; - uint8_t answer; - struct mdns_answer_item_s * next; -} mdns_answer_item_t; - -struct mdns_server_s { - tcpip_adapter_if_t tcpip_if; - struct udp_pcb * pcb; - const char * hostname; - const char * instance; - mdns_srv_item_t * services; - xSemaphoreHandle lock; - xQueueHandle queue; - struct { - char host[MDNS_NAME_BUF_LEN]; - char service[MDNS_NAME_BUF_LEN]; - char proto[MDNS_NAME_BUF_LEN]; - bool running; - xSemaphoreHandle lock; - mdns_result_t * results; - } search; -}; - -typedef struct { - struct tcpip_api_call call; - mdns_server_t *server; - uint8_t *data; - size_t len; - esp_err_t err; -} mdns_api_call_t; - -#define MDNS_MUTEX_LOCK() xSemaphoreTake(server->lock, portMAX_DELAY) -#define MDNS_MUTEX_UNLOCK() xSemaphoreGive(server->lock) - -#define MDNS_SEARCH_LOCK() xSemaphoreTake(server->search.lock, portMAX_DELAY) -#define MDNS_SEARCH_UNLOCK() xSemaphoreGive(server->search.lock) - static const char * MDNS_DEFAULT_DOMAIN = "local"; static const char * MDNS_SUB_STR = "_sub"; -static mdns_server_t * _mdns_servers[TCPIP_ADAPTER_IF_MAX] = {0, 0, 0}; +static mdns_server_t * _mdns_server = NULL; -#ifndef MDNS_TEST_MODE +static volatile TaskHandle_t _mdns_service_task_handle = NULL; +static SemaphoreHandle_t _mdns_service_semaphore = NULL; -static TaskHandle_t _mdns_service_task_handle = NULL; -static QueueSetHandle_t _mdns_queue_set = NULL; +static void _mdns_search_finish_done(); +static mdns_search_once_t * _mdns_search_find_from(mdns_search_once_t * search, mdns_name_t * name, uint16_t type, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +static void _mdns_search_result_add_ip(mdns_search_once_t * search, const char * hostname, ip_addr_t * ip, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +static void _mdns_search_result_add_srv(mdns_search_once_t * search, const char * hostname, uint16_t port, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +static void _mdns_search_result_add_txt(mdns_search_once_t * search, mdns_txt_item_t * txt, size_t txt_count, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +static mdns_result_t * _mdns_search_result_add_ptr(mdns_search_once_t * search, const char * instance, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); -static xSemaphoreHandle _mdns_service_semaphore = NULL; -#define MDNS_SERVICE_LOCK() xSemaphoreTake(_mdns_service_semaphore, portMAX_DELAY) -#define MDNS_SERVICE_UNLOCK() xSemaphoreGive(_mdns_service_semaphore) +/* + * @brief Appends/increments a number to name/instance in case of collision + * */ +static char * _mdns_mangle_name(char* in) { + char *p = strrchr(in, '-'); + int suffix = 0; + if (p == NULL) { + //No - in ``in`` + suffix = 2; + } else { + char *endp = NULL; + suffix = strtol(p + 1, &endp, 10); + if (*endp != 0) { + //suffix is not numerical + suffix = 2; + p = NULL; //so we append -suffix to the entire string + } + } + char *ret; + if (p == NULL) { + //need to add -2 to string + ret = malloc(strlen(in) + 3); + if (ret == NULL) { + return NULL; + } + sprintf(ret, "%s-2", in); + } else { + ret = malloc(strlen(in) + 2); //one extra byte in case 9-10 or 99-100 etc + strcpy(ret, in); + int baseLen = p - in; //length of 'bla' in 'bla-123' + //overwrite suffix with new suffix + sprintf(ret + baseLen, "-%d", suffix + 1); + } + return ret; +} + +/** + * @brief finds service from given service type + * @param server the server + * @param service service type to match + * @param proto proto to match + * + * @return the service item if found or NULL on error + */ +static mdns_srv_item_t * _mdns_get_service_item(const char * service, const char * proto) +{ + mdns_srv_item_t * s = _mdns_server->services; + while (s) { + if (!strcasecmp(s->service->service, service) && !strcasecmp(s->service->proto, proto)) { + return s; + } + s = s->next; + } + return NULL; +} /* * MDNS Server Networking * */ +/** + * @brief Queue RX packet action + */ +static esp_err_t _mdns_send_rx_action(mdns_rx_packet_t * packet) +{ + mdns_action_t * action = NULL; + + action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + + action->type = ACTION_RX_HANDLE; + action->data.rx_handle.packet = packet; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + /** * @brief the receive callback of the raw udp api. Packets are received here * */ -static void _mdns_server_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *addr, uint16_t port) +static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *raddr, uint16_t rport) { + uint8_t i; while (pb != NULL) { struct pbuf * this_pb = pb; pb = pb->next; this_pb->next = NULL; - mdns_server_t * server = (mdns_server_t *)arg; - if (!server || !server->queue || xQueueSend(server->queue, &this_pb, (portTickType)0) != pdPASS) { + + mdns_rx_packet_t * packet = (mdns_rx_packet_t *)malloc(sizeof(mdns_rx_packet_t)); + if (!packet) { + //missed packet - no memory pbuf_free(this_pb); + continue; + } + + packet->tcpip_if = TCPIP_ADAPTER_IF_MAX; + packet->pb = this_pb; + packet->src_port = rport; + memcpy(&packet->src, raddr, sizeof(ip_addr_t)); + packet->dest.type = packet->src.type; + + if (packet->src.type == IPADDR_TYPE_V4) { + packet->ip_protocol = MDNS_IP_PROTOCOL_V4; + struct ip_hdr * iphdr = (struct ip_hdr *)(((uint8_t *)(packet->pb->payload)) - UDP_HLEN - IP_HLEN); + packet->dest.u_addr.ip4.addr = iphdr->dest.addr; + } else { + packet->ip_protocol = MDNS_IP_PROTOCOL_V6; + struct ip6_hdr * ip6hdr = (struct ip6_hdr *)(((uint8_t *)(packet->pb->payload)) - UDP_HLEN - IP6_HLEN); + memcpy(&packet->dest.u_addr.ip6.addr, (uint8_t *)ip6hdr->dest.addr, 16); + } + packet->multicast = ip_addr_ismulticast(&(packet->dest)); + + //lwip does not return the proper pcb if you have more than one for the same multicast address (but different interfaces) + struct netif * netif = NULL; + void * nif = NULL; + struct udp_pcb * pcb = NULL; + for (i=0; iinterfaces[i].pcbs[packet->ip_protocol].pcb; + tcpip_adapter_get_netif (i, &nif); + netif = (struct netif *)nif; + if (pcb && netif && netif == ip_current_input_netif ()) { + if (packet->src.type == IPADDR_TYPE_V4) { + if ((packet->src.u_addr.ip4.addr & netif->netmask.u_addr.ip4.addr) != (netif->ip_addr.u_addr.ip4.addr & netif->netmask.u_addr.ip4.addr)) { + //packet source is not in the same subnet + pcb = NULL; + break; + } + } + packet->tcpip_if = i; + break; + } + pcb = NULL; + } + + if (!pcb || !_mdns_server || !_mdns_server->action_queue + || _mdns_send_rx_action(packet) != ESP_OK) { + pbuf_free(this_pb); + free(packet); } } } /** - * @brief init the network of MDNS server (called in tcpip thread context) + * @brief Stop PCB Main code */ -static err_t _mdns_server_init_api(struct tcpip_api_call *api_call_msg) +static void _udp_pcb_deinit(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) { - mdns_api_call_t *msg = (mdns_api_call_t*)api_call_msg; - mdns_server_t *server = msg->server; - esp_err_t err = ESP_OK; + if (!_mdns_server) { + return; + } + mdns_pcb_t * _pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol]; + if (_pcb->pcb) { + _pcb->state = PCB_OFF; + udp_recv(_pcb->pcb, NULL, NULL); + udp_disconnect(_pcb->pcb); + udp_remove(_pcb->pcb); + free(_pcb->probe_services); + _pcb->pcb = NULL; + _pcb->probe_ip = false; + _pcb->probe_services = NULL; + _pcb->probe_services_len = 0; + _pcb->probe_running = false; + _pcb->failed_probes = 0; + } +} +/** + * @brief Start PCB V4 + */ +static esp_err_t _udp_pcb_v4_init(tcpip_adapter_if_t tcpip_if) +{ tcpip_adapter_ip_info_t if_ip_info; - err = tcpip_adapter_get_ip_info(server->tcpip_if, &if_ip_info); - if (err) { - msg->err = err; - return ERR_OK; + + if (!_mdns_server || _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb) { + return ESP_ERR_INVALID_STATE; } - ip_addr_t laddr; - IP_ADDR4(&laddr, 224, 0, 0, 251); - - ip_addr_t multicast_if_addr = IPADDR4_INIT(if_ip_info.ip.addr); - - if (igmp_joingroup((const struct ip4_addr *)&multicast_if_addr.u_addr.ip4, (const struct ip4_addr *)&laddr.u_addr.ip4)) { - msg->err = ESP_ERR_INVALID_STATE; - return ERR_OK; + if (tcpip_adapter_get_ip_info(tcpip_if, &if_ip_info) || if_ip_info.ip.addr == 0) { + return ESP_ERR_INVALID_ARG; } - struct udp_pcb * pcb = udp_new(); + ip_addr_t interface_addr = IPADDR4_INIT(if_ip_info.ip.addr); + + ip_addr_t multicast_addr; + IP_ADDR4(&multicast_addr, 224, 0, 0, 251); + + if (igmp_joingroup((const struct ip4_addr *)&interface_addr.u_addr.ip4, (const struct ip4_addr *)&multicast_addr.u_addr.ip4)) { + return ESP_ERR_INVALID_STATE; + } + + struct udp_pcb * pcb = udp_new_ip_type(IPADDR_TYPE_V4); if (!pcb) { - msg->err = ESP_ERR_NO_MEM; - return ERR_OK; + return ESP_ERR_NO_MEM; } - pcb->remote_port = MDNS_SERVICE_PORT; - - if (udp_bind(pcb, &multicast_if_addr, pcb->remote_port) != 0) { + if (udp_bind(pcb, &interface_addr, MDNS_SERVICE_PORT) != 0) { udp_remove(pcb); - msg->err = ESP_ERR_INVALID_STATE; - return ERR_OK; + return ESP_ERR_INVALID_STATE; } pcb->mcast_ttl = 1; - ip_addr_copy(pcb->multicast_ip, multicast_if_addr); - ip_addr_copy(pcb->remote_ip, laddr); + pcb->remote_port = MDNS_SERVICE_PORT; + ip_addr_copy(pcb->multicast_ip, interface_addr); + ip_addr_copy(pcb->remote_ip, multicast_addr); - server->pcb = pcb; - udp_recv(pcb, &_mdns_server_recv, server); - msg->err = err; - return ERR_OK; + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb = pcb; + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].failed_probes = 0; + udp_recv(pcb, &_udp_recv, _mdns_server); + + return ESP_OK; } /** - * @brief init the network of MDNS server - * - * @param server The server - * - * @return - * - ESP_OK on success - * - ESP_ERR_INVALID_STATE on igmp/bind error - * - ESP_ERR_NO_MEM on memory error + * @brief Start PCB V6 */ -esp_err_t _mdns_server_init(mdns_server_t * server) +static esp_err_t _udp_pcb_v6_init(tcpip_adapter_if_t tcpip_if) +{ + ip_addr_t multicast_addr = IPADDR6_INIT(0x000002ff, 0, 0, 0xfb000000); + ip_addr_t interface_addr; + interface_addr.type = IPADDR_TYPE_V6; + + if (!_mdns_server || _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb) { + return ESP_ERR_INVALID_STATE; + } + + if (tcpip_adapter_get_ip6_linklocal(tcpip_if, &interface_addr.u_addr.ip6)) { + return ESP_ERR_INVALID_ARG; + } + + if (mld6_joingroup(&(interface_addr.u_addr.ip6), &(multicast_addr.u_addr.ip6))) { + return ESP_ERR_INVALID_STATE; + } + + struct udp_pcb * pcb = udp_new_ip_type(IPADDR_TYPE_V6); + if (!pcb) { + return ESP_ERR_NO_MEM; + } + + if (udp_bind(pcb, &interface_addr, MDNS_SERVICE_PORT) != 0) { + udp_remove(pcb); + return ESP_ERR_INVALID_STATE; + } + + pcb->remote_port = MDNS_SERVICE_PORT; + ip_addr_copy(pcb->remote_ip, multicast_addr); + + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb = pcb; + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].failed_probes = 0; + udp_recv(pcb, &_udp_recv, _mdns_server); + + return ESP_OK; +} + +/** + * @brief Start PCB Main code + */ +static esp_err_t _udp_pcb_init(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + if (ip_protocol == MDNS_IP_PROTOCOL_V4) { + return _udp_pcb_v4_init(tcpip_if); + } else if (ip_protocol == MDNS_IP_PROTOCOL_V6) { + return _udp_pcb_v6_init(tcpip_if); + } + return ESP_ERR_INVALID_ARG; +} + +typedef struct { + struct tcpip_api_call call; + tcpip_adapter_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + esp_err_t err; +} mdns_api_call_t; + +/** + * @brief Start PCB from LwIP thread + */ +static err_t _mdns_pcb_init_api(struct tcpip_api_call *api_call_msg) +{ + mdns_api_call_t * msg = (mdns_api_call_t *)api_call_msg; + msg->err = _udp_pcb_init(msg->tcpip_if, msg->ip_protocol); + return msg->err; +} + +/** + * @brief Start PCB + */ +static esp_err_t _mdns_pcb_init(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) { mdns_api_call_t msg = { - .server = server, + .tcpip_if = tcpip_if, + .ip_protocol = ip_protocol }; - tcpip_api_call(_mdns_server_init_api, (struct tcpip_api_call*)&msg); + tcpip_api_call(_mdns_pcb_init_api, (struct tcpip_api_call*)&msg); return msg.err; } /** - * @brief stop the network of MDNS server (called in tcpip thread context) + * @brief Stop PCB from LwIP thread */ -static err_t _mdns_server_deinit_api(struct tcpip_api_call *api_call_msg) +static err_t _mdns_pcb_deinit_api(struct tcpip_api_call *api_call_msg) { - mdns_api_call_t *msg = (mdns_api_call_t*)api_call_msg; - mdns_server_t *server = msg->server; - - if (server->pcb) { - udp_recv(server->pcb, NULL, NULL); - udp_disconnect(server->pcb); - udp_remove(server->pcb); - server->pcb = NULL; - } + mdns_api_call_t * msg = (mdns_api_call_t *)api_call_msg; + _udp_pcb_deinit(msg->tcpip_if, msg->ip_protocol); msg->err = ESP_OK; - return ERR_OK; + return ESP_OK; } /** - * @brief stop the network of MDNS server - * - * @param server The server - * - * @return ESP_OK + * @brief Stop PCB */ -esp_err_t _mdns_server_deinit(mdns_server_t * server) +static esp_err_t _mdns_pcb_deinit(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) { mdns_api_call_t msg = { - .server = server, + .tcpip_if = tcpip_if, + .ip_protocol = ip_protocol }; - tcpip_api_call(_mdns_server_deinit_api, (struct tcpip_api_call*)&msg); + tcpip_api_call(_mdns_pcb_deinit_api, (struct tcpip_api_call*)&msg); return msg.err; } -#endif -/** - * @brief send packet over UDP (called in tcpip thread context) - */ -static err_t _mdns_server_write_api(struct tcpip_api_call *api_call_msg) -{ - mdns_api_call_t *msg = (mdns_api_call_t*)api_call_msg; - mdns_server_t *server = msg->server; - - struct pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, msg->len, PBUF_RAM); - if (pbt == NULL) { - msg->len = 0; - return ERR_OK; - } - uint8_t* dst = (uint8_t *)pbt->payload; - memcpy(dst, msg->data, msg->len); - err_t err = udp_sendto(server->pcb, pbt, &(server->pcb->remote_ip), server->pcb->remote_port); - pbuf_free(pbt); - if (err) { - msg->len = 0; - return ERR_OK; - } - - return ERR_OK; -} /** * @brief send packet over UDP @@ -343,197 +385,30 @@ static err_t _mdns_server_write_api(struct tcpip_api_call *api_call_msg) * * @return length of sent packet or 0 on error */ -static size_t _mdns_server_write(mdns_server_t * server, uint8_t * data, size_t len) { -#ifndef MDNS_TEST_MODE - mdns_api_call_t msg = { - .server = server, - .data = data, - .len = len, - }; - tcpip_api_call(_mdns_server_write_api, (struct tcpip_api_call*)&msg); - return msg.len; -#else - return len; -#endif -} - -/* - * MDNS Servers - * */ - -#ifndef MDNS_TEST_MODE -void mdns_parse_packet(mdns_server_t * server, const uint8_t * data, size_t len); - -/** - * @brief the main MDNS service task. Packets are received and parsed here - */ -static void _mdns_service_task(void *pvParameters) -{ - uint8_t i; - struct pbuf * pb; - QueueSetMemberHandle_t queue; - - for (;;) { - queue = xQueueSelectFromSet(_mdns_queue_set, portMAX_DELAY); - if (queue && xQueueReceive(queue, &pb, 0) == pdTRUE) { - for (i = 0; i < TCPIP_ADAPTER_IF_MAX; i++) { - mdns_server_t * server = _mdns_servers[i]; - if (server && server->queue == queue) { - MDNS_MUTEX_LOCK(); - mdns_parse_packet(server, (uint8_t*)pb->payload, pb->len); - MDNS_MUTEX_UNLOCK(); - break; - } - } - pbuf_free(pb); - } - } -} -#endif - -/** - * @brief get the server assigned to particular interface - * - * @param tcpip_if The interface - * - * @return reference to the server from the server list or NULL if not found - */ -static mdns_server_t * _mdns_server_get(tcpip_adapter_if_t tcpip_if) -{ - if (tcpip_if < TCPIP_ADAPTER_IF_MAX) { - return _mdns_servers[tcpip_if]; - } - return NULL; -} - -/** - * @brief add server to the server list. Start the service thread if not running - * - * @param server The server to add - * - * @return - * - ESP_OK on success - * - ESP_FAIL on error - * - ESP_ERR_* on network error - */ -static esp_err_t _mdns_server_add(mdns_server_t * server) +static size_t _udp_pcb_write(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const ip_addr_t *ip, uint16_t port, uint8_t * data, size_t len) { #ifndef MDNS_TEST_MODE - if (!_mdns_service_semaphore) { - _mdns_service_semaphore = xSemaphoreCreateMutex(); - if (!_mdns_service_semaphore) { - return ESP_FAIL; - } - } - MDNS_SERVICE_LOCK(); - if (!_mdns_service_task_handle) { - _mdns_queue_set = xQueueCreateSet(TCPIP_ADAPTER_IF_MAX * MDNS_PACKET_QUEUE_LEN); - if (!_mdns_queue_set) { - MDNS_SERVICE_UNLOCK(); - return ESP_FAIL; - } - xTaskCreatePinnedToCore(_mdns_service_task, "mdns", MDNS_SERVICE_STACK_DEPTH, NULL, 1, &_mdns_service_task_handle, 0); - if (!_mdns_service_task_handle) { - vQueueDelete(_mdns_queue_set); - _mdns_queue_set = NULL; - MDNS_SERVICE_UNLOCK(); - return ESP_FAIL; - } - } - MDNS_SERVICE_UNLOCK(); - - if (xQueueAddToSet(server->queue, _mdns_queue_set) != pdPASS) { - return ESP_FAIL; - } - - //start UDP - esp_err_t err = _mdns_server_init(server); + struct netif * netif = NULL; + void * nif = NULL; + esp_err_t err = tcpip_adapter_get_netif(tcpip_if, &nif); + netif = (struct netif *)nif; if (err) { - return err; + return 0; + } + + struct pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (pbt == NULL) { + return 0; + } + memcpy((uint8_t *)pbt->payload, data, len); + + err = udp_sendto_if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb, pbt, ip, port, netif); + pbuf_free(pbt); + if (err) { + return 0; } #endif - _mdns_servers[server->tcpip_if] = server; - - return ESP_OK; -} - -/** - * @brief remove server from server list. Stop the service thread in no more servers are running - * - * @param server The server to remove - * - * @return - * - ESP_OK on success - * - ESP_FAIL on error - */ -static esp_err_t _mdns_server_remove(mdns_server_t * server) -{ - _mdns_servers[server->tcpip_if] = NULL; -#ifndef MDNS_TEST_MODE - //stop UDP - _mdns_server_deinit(server); - - if (xQueueRemoveFromSet(server->queue, _mdns_queue_set) != pdPASS) { - return ESP_FAIL; - } - - uint8_t i; - for (i = 0; i < TCPIP_ADAPTER_IF_MAX; i++) { - if (_mdns_servers[i]) { - break; - } - } - if (i == TCPIP_ADAPTER_IF_MAX) { - //none of the servers are running - MDNS_SERVICE_LOCK(); - if (_mdns_service_task_handle) { - vTaskDelete(_mdns_service_task_handle); - vQueueDelete(_mdns_queue_set); - _mdns_queue_set = NULL; - _mdns_service_task_handle = NULL; - } - MDNS_SERVICE_UNLOCK(); - } -#endif - return ESP_OK; -} - - -/* - * PARSING - * */ - -/** - * @brief queues service for answer (if service is already added, append the new answer type) - * - * @param answers Linked list of answers - * @param service Service to add to the answers - * @param type Type of the answer - * - * @return the new linked list of answers - */ -static mdns_answer_item_t * _mdns_add_answer(mdns_answer_item_t * answers, mdns_service_t * service, uint8_t type) -{ - //see if we already have the service queued - mdns_answer_item_t * a = answers; - while (a) { - if (a->service == service) { - //just add the new answer type to it - a->answer |= type; - return answers; - } - a = a->next; - } - //prepend the q with this new answer - a = (mdns_answer_item_t *)malloc(sizeof(mdns_answer_item_t)); - if (!a) { - return answers;//fail! - } - a->service = service; - a->answer = type; - a->next = answers; - answers = a; - return a; + return len; } /** @@ -561,20 +436,21 @@ static const uint8_t * _mdns_read_fqdn(const uint8_t * packet, const uint8_t * s return NULL; } uint8_t i; - for (i = 0; i < len; i++) { + for (i=0; iparts == 1 && buf[0] != '_' - && (strcmp(buf, MDNS_DEFAULT_DOMAIN) != 0) - && (strcmp(buf, "ip6") != 0) - && (strcmp(buf, "in-addr") != 0)) { + && (strcasecmp(buf, MDNS_DEFAULT_DOMAIN) != 0) + && (strcasecmp(buf, "ip6") != 0) + && (strcasecmp(buf, "in-addr") != 0)) { strlcat(name->host, ".", sizeof(name->host)); strlcat(name->host, buf, sizeof(name->host)); - } else if (strcmp(buf, MDNS_SUB_STR) == 0) { + } else if (strcasecmp(buf, MDNS_SUB_STR) == 0) { name->sub = 1; } else { - memcpy((uint8_t*)name + (name->parts++ * (MDNS_NAME_BUF_LEN)), buf, len + 1); + char* mdns_name_ptrs[]={name->host, name->service, name->proto, name->domain}; + memcpy(mdns_name_ptrs[name->parts++], buf, len+1); } } else { size_t address = (((uint16_t)len & 0x3F) << 8) | start[index++]; @@ -591,48 +467,6 @@ static const uint8_t * _mdns_read_fqdn(const uint8_t * packet, const uint8_t * s return start + index + 1; } -/** - * @brief reads and formats MDNS FQDN into mdns_name_t structure - * - * @param packet MDNS packet - * @param start Starting point of FQDN - * @param name mdns_name_t structure to populate - * - * @return the address after the parsed FQDN in the packet or NULL on error - */ -static const uint8_t * _mdns_parse_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name) -{ - name->parts = 0; - name->sub = 0; - name->host[0] = 0; - name->service[0] = 0; - name->proto[0] = 0; - name->domain[0] = 0; - - static char buf[MDNS_NAME_BUF_LEN]; - - const uint8_t * next_data = (uint8_t*)_mdns_read_fqdn(packet, start, name, buf); - if (!next_data || name->parts < 2) { - return 0; - } - if (name->parts == 3) { - memmove((uint8_t*)name + (MDNS_NAME_BUF_LEN), (uint8_t*)name, 3 * (MDNS_NAME_BUF_LEN)); - name->host[0] = 0; - } else if (name->parts == 2) { - memmove((uint8_t*)(name->domain), (uint8_t*)(name->service), (MDNS_NAME_BUF_LEN)); - name->service[0] = 0; - name->proto[0] = 0; - } - if (strcmp(name->domain, MDNS_DEFAULT_DOMAIN) == 0 || strcmp(name->domain, "arpa") == 0) { - return next_data; - } - return 0; -} - -/* - * Packet construction - * */ - /** * @brief sets uint16_t value in a packet * @@ -646,7 +480,7 @@ static inline void _mdns_set_u16(uint8_t * packet, uint16_t index, uint16_t valu return; } packet[index] = (value >> 8) & 0xFF; - packet[index + 1] = value & 0xFF; + packet[index+1] = value & 0xFF; } /** @@ -718,26 +552,30 @@ static inline uint8_t _mdns_append_u32(uint8_t * packet, uint16_t * index, uint3 * * @return length of added data: 0 on error or 10 on success */ -static inline uint8_t _mdns_append_type(uint8_t * packet, uint16_t * index, uint8_t type, uint32_t ttl) +static inline uint8_t _mdns_append_type(uint8_t * packet, uint16_t * index, uint8_t type, bool flush, uint32_t ttl) { if ((*index + 10) >= MDNS_MAX_PACKET_SIZE) { return 0; } + uint16_t clas = MDNS_CLASS_IN; + if (flush) { + clas = MDNS_CLASS_IN_FLUSH_CACHE; + } if (type == MDNS_ANSWER_PTR) { _mdns_append_u16(packet, index, MDNS_TYPE_PTR); - _mdns_append_u16(packet, index, MDNS_CLASS_IN); + _mdns_append_u16(packet, index, clas); } else if (type == MDNS_ANSWER_TXT) { _mdns_append_u16(packet, index, MDNS_TYPE_TXT); - _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE); + _mdns_append_u16(packet, index, clas); } else if (type == MDNS_ANSWER_SRV) { _mdns_append_u16(packet, index, MDNS_TYPE_SRV); - _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE); + _mdns_append_u16(packet, index, clas); } else if (type == MDNS_ANSWER_A) { _mdns_append_u16(packet, index, MDNS_TYPE_A); - _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE); + _mdns_append_u16(packet, index, clas); } else if (type == MDNS_ANSWER_AAAA) { _mdns_append_u16(packet, index, MDNS_TYPE_AAAA); - _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE); + _mdns_append_u16(packet, index, clas); } else { return 0; } @@ -768,7 +606,8 @@ static inline uint8_t _mdns_append_string(uint8_t * packet, uint16_t * index, co } /** - * @brief appends FQDN to a packet, incrementing the index + * @brief appends FQDN to a packet, incrementing the index and + * compressing the output if previous occurrence of the string (or part of it) has been found * * @param packet MDNS packet * @param index offset in the packet @@ -780,19 +619,24 @@ static inline uint8_t _mdns_append_string(uint8_t * packet, uint16_t * index, co static uint16_t _mdns_append_fqdn(uint8_t * packet, uint16_t * index, const char * strings[], uint8_t count) { if (!count) { + //empty string so terminate return _mdns_append_u8(packet, index, 0); } mdns_name_t name; static char buf[MDNS_NAME_BUF_LEN]; uint8_t len = strlen(strings[0]); + //try to find first the string length in the packet (if it exists) uint8_t * len_location = (uint8_t *)memchr(packet, (char)len, *index); while (len_location) { - if (memcmp(len_location + 1, strings[0], len)) { //not continuing with our string + //check if the string after len_location is the string that we are looking for + if (memcmp(len_location+1, strings[0], len)) { //not continuing with our string search_next: - len_location = (uint8_t *)memchr(len_location + 1, (char)len, *index - (len_location + 1 - packet)); + //try and find the length byte further in the packet + len_location = (uint8_t *)memchr(len_location+1, (char)len, *index - (len_location+1 - packet)); continue; } - //read string into name and compare + //seems that we might have found the string that we are looking for + //read the destination into name and compare name.parts = 0; name.sub = 0; name.host[0] = 0; @@ -801,13 +645,14 @@ search_next: name.domain[0] = 0; const uint8_t * content = _mdns_read_fqdn(packet, len_location, &name, buf); if (!content) { + //not a readable fqdn? return 0; } if (name.parts == count) { uint8_t i; - for (i = 0; i < count; i++) { - if (strcmp(strings[i], (const char *)&name + (i * (MDNS_NAME_BUF_LEN)))) { - //not our string + for (i=0; iinstance) ? service->instance - : (server->instance) ? server->instance - : server->hostname; - str[1] = service->service; - str[2] = service->proto; + str[0] = instance; + str[1] = service; + str[2] = proto; str[3] = MDNS_DEFAULT_DOMAIN; part_length = _mdns_append_fqdn(packet, index, str + 1, 3); @@ -859,7 +705,7 @@ static uint16_t _mdns_append_ptr_record(uint8_t * packet, uint16_t * index, mdns } record_length += part_length; - part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, MDNS_ANSWER_PTR_TTL); + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, false, bye?0:MDNS_ANSWER_PTR_TTL); if (!part_length) { return 0; } @@ -885,7 +731,7 @@ static uint16_t _mdns_append_ptr_record(uint8_t * packet, uint16_t * index, mdns * * @return length of added data: 0 on error or length on success */ -static uint16_t _mdns_append_sdptr_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, mdns_service_t * service) +static uint16_t _mdns_append_sdptr_record(uint8_t * packet, uint16_t * index, mdns_service_t * service, bool flush, bool bye) { const char * str[3]; const char * sd_str[4]; @@ -905,7 +751,7 @@ static uint16_t _mdns_append_sdptr_record(uint8_t * packet, uint16_t * index, md record_length += part_length; - part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, MDNS_ANSWER_PTR_TTL); + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, flush, MDNS_ANSWER_PTR_TTL); if (!part_length) { return 0; } @@ -931,15 +777,15 @@ static uint16_t _mdns_append_sdptr_record(uint8_t * packet, uint16_t * index, md * * @return length of added data: 0 on error or length on success */ -static uint16_t _mdns_append_txt_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, mdns_service_t * service) +static uint16_t _mdns_append_txt_record(uint8_t * packet, uint16_t * index, mdns_service_t * service, bool flush, bool bye) { const char * str[4]; uint16_t record_length = 0; uint8_t part_length; - str[0] = (service->instance) ? service->instance - : (server->instance) ? server->instance - : server->hostname; + str[0] = (service->instance)?service->instance + :(_mdns_server->instance)?_mdns_server->instance + :_mdns_server->hostname; str[1] = service->service; str[2] = service->proto; str[3] = MDNS_DEFAULT_DOMAIN; @@ -950,7 +796,7 @@ static uint16_t _mdns_append_txt_record(uint8_t * packet, uint16_t * index, mdns } record_length += part_length; - part_length = _mdns_append_type(packet, index, MDNS_ANSWER_TXT, MDNS_ANSWER_TXT_TTL); + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_TXT, flush, bye?0:MDNS_ANSWER_TXT_TTL); if (!part_length) { return 0; } @@ -958,17 +804,26 @@ static uint16_t _mdns_append_txt_record(uint8_t * packet, uint16_t * index, mdns uint16_t data_len_location = *index - 2; uint16_t data_len = 0; - if (service->txt_num_items) { - uint8_t len = service->txt_num_items; - const char ** txt = service->txt; - uint8_t i, l; - for (i = 0; i < len; i++) { - l = _mdns_append_string(packet, index, txt[i]); + + char * tmp; + mdns_txt_linked_item_t * txt = service->txt; + while (txt) { + tmp = (char *)malloc(2 + strlen(txt->key) + strlen(txt->value)); + if (tmp) { + sprintf(tmp, "%s=%s", txt->key, txt->value); + uint8_t l = _mdns_append_string(packet, index, tmp); + free(tmp); if (!l) { return 0; } data_len += l; } + txt = txt->next; + } + if (!data_len) { + data_len = 1; + packet[*index] = 0; + *index = *index + 1; } _mdns_set_u16(packet, data_len_location, data_len); record_length += data_len; @@ -985,15 +840,15 @@ static uint16_t _mdns_append_txt_record(uint8_t * packet, uint16_t * index, mdns * * @return length of added data: 0 on error or length on success */ -static uint16_t _mdns_append_srv_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, mdns_service_t * service) +static uint16_t _mdns_append_srv_record(uint8_t * packet, uint16_t * index, mdns_service_t * service, bool flush, bool bye) { const char * str[4]; uint16_t record_length = 0; uint8_t part_length; - str[0] = (service->instance) ? service->instance - : (server->instance) ? server->instance - : server->hostname; + str[0] = (service->instance)?service->instance + :(_mdns_server->instance)?_mdns_server->instance + :_mdns_server->hostname; str[1] = service->service; str[2] = service->proto; str[3] = MDNS_DEFAULT_DOMAIN; @@ -1004,7 +859,7 @@ static uint16_t _mdns_append_srv_record(uint8_t * packet, uint16_t * index, mdns } record_length += part_length; - part_length = _mdns_append_type(packet, index, MDNS_ANSWER_SRV, MDNS_ANSWER_SRV_TTL); + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_SRV, flush, bye?0:MDNS_ANSWER_SRV_TTL); if (!part_length) { return 0; } @@ -1020,7 +875,7 @@ static uint16_t _mdns_append_srv_record(uint8_t * packet, uint16_t * index, mdns return 0; } - str[0] = server->hostname; + str[0] = _mdns_server->hostname; str[1] = MDNS_DEFAULT_DOMAIN; part_length = _mdns_append_fqdn(packet, index, str, 2); if (!part_length) { @@ -1042,13 +897,13 @@ static uint16_t _mdns_append_srv_record(uint8_t * packet, uint16_t * index, mdns * * @return length of added data: 0 on error or length on success */ -static uint16_t _mdns_append_a_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, uint32_t ip) +static uint16_t _mdns_append_a_record(uint8_t * packet, uint16_t * index, uint32_t ip, bool flush, bool bye) { const char * str[2]; uint16_t record_length = 0; uint8_t part_length; - str[0] = server->hostname; + str[0] = _mdns_server->hostname; str[1] = MDNS_DEFAULT_DOMAIN; part_length = _mdns_append_fqdn(packet, index, str, 2); @@ -1057,7 +912,7 @@ static uint16_t _mdns_append_a_record(uint8_t * packet, uint16_t * index, mdns_s } record_length += part_length; - part_length = _mdns_append_type(packet, index, MDNS_ANSWER_A, MDNS_ANSWER_A_TTL); + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_A, flush, bye?0:MDNS_ANSWER_A_TTL); if (!part_length) { return 0; } @@ -1083,18 +938,17 @@ static uint16_t _mdns_append_a_record(uint8_t * packet, uint16_t * index, mdns_s * * @param packet MDNS packet * @param index offset in the packet - * @param server the server * @param ipv6 the IPv6 address to add * * @return length of added data: 0 on error or length on success */ -static uint16_t _mdns_append_aaaa_record(uint8_t * packet, uint16_t * index, mdns_server_t * server, uint8_t * ipv6) +static uint16_t _mdns_append_aaaa_record(uint8_t * packet, uint16_t * index, uint8_t * ipv6, bool flush, bool bye) { const char * str[2]; uint16_t record_length = 0; uint8_t part_length; - str[0] = server->hostname; + str[0] = _mdns_server->hostname; str[1] = MDNS_DEFAULT_DOMAIN; part_length = _mdns_append_fqdn(packet, index, str, 2); @@ -1103,7 +957,7 @@ static uint16_t _mdns_append_aaaa_record(uint8_t * packet, uint16_t * index, mdn } record_length += part_length; - part_length = _mdns_append_type(packet, index, MDNS_ANSWER_AAAA, MDNS_ANSWER_AAAA_TTL); + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_AAAA, flush, bye?0:MDNS_ANSWER_AAAA_TTL); if (!part_length) { return 0; } @@ -1124,194 +978,1059 @@ static uint16_t _mdns_append_aaaa_record(uint8_t * packet, uint16_t * index, mdn } /** - * @brief sends all collected answers - * - * @param server the server - * @param answers linked list of answers + * @brief Append question to packet */ -static void _mdns_send_answers(mdns_server_t * server, mdns_answer_item_t * answers) +static uint16_t _mdns_append_question(uint8_t * packet, uint16_t * index, mdns_out_question_t * q) +{ + const char * str[4]; + uint8_t str_index = 0; + uint8_t part_length; + if (q->host) { + str[str_index++] = q->host; + } + if (q->service) { + str[str_index++] = q->service; + } + if (q->proto) { + str[str_index++] = q->proto; + } + if (q->domain) { + str[str_index++] = q->domain; + } + + part_length = _mdns_append_fqdn(packet, index, str, str_index); + if (!part_length) { + return 0; + } + + part_length += _mdns_append_u16(packet, index, q->type); + part_length += _mdns_append_u16(packet, index, q->unicast?0x8001:0x0001); + return part_length; +} + +/** + * @brief Helper to get either ETH or STA if the other is provided + * Used when two interfaces are on the same subnet + */ +static tcpip_adapter_if_t _mdns_get_other_if (tcpip_adapter_if_t tcpip_if) +{ + if (tcpip_if == TCPIP_ADAPTER_IF_STA) { + return TCPIP_ADAPTER_IF_ETH; + } else if (tcpip_if == TCPIP_ADAPTER_IF_ETH) { + return TCPIP_ADAPTER_IF_STA; + } + return TCPIP_ADAPTER_IF_MAX; +} + +/** + * @brief Check if interface is duplicate (two interfaces on the same subnet) + */ +static bool _mdns_if_is_dup(tcpip_adapter_if_t tcpip_if) +{ + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (other_if == TCPIP_ADAPTER_IF_MAX) { + return false; + } + if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].state == PCB_DUP + || _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].state == PCB_DUP + || _mdns_server->interfaces[other_if].pcbs[MDNS_IP_PROTOCOL_V4].state == PCB_DUP + || _mdns_server->interfaces[other_if].pcbs[MDNS_IP_PROTOCOL_V6].state == PCB_DUP + ) { + return true; + } + return false; +} + +/** + * @brief Check if IPv6 address is NULL + */ +static bool _ipv6_address_is_zero(ip6_addr_t ip6) +{ + uint8_t i; + uint8_t * data = (uint8_t *)ip6.addr; + for (i=0; i<16; i++) { + if (data[i]) { + return false; + } + } + return true; +} + +/** + * @brief Append answer to packet + * + * @return number of answers added to the packet + */ +static uint8_t _mdns_append_answer(uint8_t * packet, uint16_t * index, mdns_out_answer_t * answer, tcpip_adapter_if_t tcpip_if) +{ + if (answer->type == MDNS_TYPE_PTR) { + + if (answer->service) { + return _mdns_append_ptr_record(packet, index, + (answer->service->instance)?answer->service->instance + :(_mdns_server->instance)?_mdns_server->instance + :_mdns_server->hostname, + answer->service->service, answer->service->proto, + answer->flush, answer->bye) > 0; + } else { + return _mdns_append_ptr_record(packet, index, + answer->custom_instance, answer->custom_service, answer->custom_proto, + answer->flush, answer->bye) > 0; + } + } else if (answer->type == MDNS_TYPE_SRV) { + return _mdns_append_srv_record(packet, index, answer->service, answer->flush, answer->bye) > 0; + } else if (answer->type == MDNS_TYPE_TXT) { + return _mdns_append_txt_record(packet, index, answer->service, answer->flush, answer->bye) > 0; + } else if (answer->type == MDNS_TYPE_SDPTR) { + return _mdns_append_sdptr_record(packet, index, answer->service, answer->flush, answer->bye) > 0; + } else if (answer->type == MDNS_TYPE_A) { + tcpip_adapter_ip_info_t if_ip_info; + if (!_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb && _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].state != PCB_DUP) { + return 0; + } + if (tcpip_adapter_get_ip_info(tcpip_if, &if_ip_info)) { + return 0; + } + if (_mdns_append_a_record(packet, index, if_ip_info.ip.addr, answer->flush, answer->bye) <= 0) { + return 0; + } + if (!_mdns_if_is_dup(tcpip_if)) { + return 1; + } + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (tcpip_adapter_get_ip_info(other_if, &if_ip_info)) { + return 1; + } + if (_mdns_append_a_record(packet, index, if_ip_info.ip.addr, answer->flush, answer->bye) > 0) { + return 2; + } + return 1; + } else if (answer->type == MDNS_TYPE_AAAA) { + struct ip6_addr if_ip6; + if (!_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb && _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].state != PCB_DUP) { + return 0; + } + if (tcpip_adapter_get_ip6_linklocal(tcpip_if, &if_ip6)) { + return 0; + } + if (_ipv6_address_is_zero(if_ip6)) { + return 0; + } + if (_mdns_append_aaaa_record(packet, index, (uint8_t*)if_ip6.addr, answer->flush, answer->bye) <= 0) { + return 0; + } + if (!_mdns_if_is_dup(tcpip_if)) { + return 1; + } + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (tcpip_adapter_get_ip6_linklocal(other_if, &if_ip6)) { + return 1; + } + if (_mdns_append_aaaa_record(packet, index, (uint8_t*)if_ip6.addr, answer->flush, answer->bye) > 0) { + return 2; + } + return 1; + } + return 0; +} + +/** + * @brief sends a packet + * + * @param p the packet + */ +static void _mdns_dispatch_tx_packet(mdns_tx_packet_t * p) { - bool send_ip = false; static uint8_t packet[MDNS_MAX_PACKET_SIZE]; uint16_t index = MDNS_HEAD_LEN; - uint8_t answer_count = 0; - memset(packet, 0, MDNS_HEAD_LEN); + mdns_out_question_t * q; + mdns_out_answer_t * a; + uint8_t count; - _mdns_set_u16(packet, MDNS_HEAD_FLAGS_OFFSET, MDNS_FLAGS_AUTHORITATIVE); + _mdns_set_u16(packet, MDNS_HEAD_FLAGS_OFFSET, p->flags); - while (answers) { - if (answers->answer & MDNS_ANSWER_A) { - answers->answer &= ~MDNS_ANSWER_A; - send_ip = true; + count = 0; + q = p->questions; + while (q) { + if (_mdns_append_question(packet, &index, q)) { + count++; } - if (answers->service) { - - if (answers->answer & MDNS_ANSWER_PTR) { - if (!_mdns_append_ptr_record(packet, &index, server, answers->service)) { - return; - } - answer_count += 1; - } - - if (answers->answer & MDNS_ANSWER_TXT) { - if (!_mdns_append_txt_record(packet, &index, server, answers->service)) { - return; - } - answer_count += 1; - } - - if (answers->answer & MDNS_ANSWER_SRV) { - if (!_mdns_append_srv_record(packet, &index, server, answers->service)) { - return; - } - answer_count += 1; - } - - if (answers->answer & MDNS_ANSWER_SDPTR) { - if (!_mdns_append_sdptr_record(packet, &index, server, answers->service)) { - return; - } - answer_count += 1; - } - } - mdns_answer_item_t * a = answers; - answers = answers->next; - free(a); + q = q->next; } - if (send_ip) { - tcpip_adapter_ip_info_t if_ip_info; - tcpip_adapter_get_ip_info(server->tcpip_if, &if_ip_info); + _mdns_set_u16(packet, MDNS_HEAD_QUESTIONS_OFFSET, count); - if (!_mdns_append_a_record(packet, &index, server, if_ip_info.ip.addr)) { - return; - } - answer_count += 1; - - //add ipv6 if available - struct ip6_addr if_ip6; - if (!tcpip_adapter_get_ip6_linklocal(server->tcpip_if, &if_ip6)) { - uint8_t * v6addr = (uint8_t*)if_ip6.addr; - //check if not 0 - int i; - for (i = 0; i < sizeof(ip6_addr_t); i++) { - if (v6addr[i]) { - break; - } - } - if (i < sizeof(ip6_addr_t)) { - if (!_mdns_append_aaaa_record(packet, &index, server, v6addr)) { - return; - } - answer_count += 1; - } - } + count = 0; + a = p->answers; + while (a) { + count += _mdns_append_answer(packet, &index, a, p->tcpip_if); + a = a->next; } + _mdns_set_u16(packet, MDNS_HEAD_ANSWERS_OFFSET, count); - _mdns_set_u16(packet, MDNS_HEAD_ANSWERS_OFFSET, answer_count); - _mdns_server_write(server, packet, index); + count = 0; + a = p->servers; + while (a) { + count += _mdns_append_answer(packet, &index, a, p->tcpip_if); + a = a->next; + } + _mdns_set_u16(packet, MDNS_HEAD_SERVERS_OFFSET, count); + + count = 0; + a = p->additional; + while (a) { + count += _mdns_append_answer(packet, &index, a, p->tcpip_if); + a = a->next; + } + _mdns_set_u16(packet, MDNS_HEAD_ADDITIONAL_OFFSET, count); + +#ifdef MDNS_ENABLE_DEBUG + _mdns_dbg_printf("\nTX[%u][%u]: ", p->tcpip_if, p->ip_protocol); + if (p->dst.type == IPADDR_TYPE_V4) { + _mdns_dbg_printf("To: " IPSTR ":%u, ", IP2STR(&p->dst.u_addr.ip4), p->port); + } else { + _mdns_dbg_printf("To: " IPV6STR ":%u, ", IPV62STR(p->dst.u_addr.ip6), p->port); + } + mdns_debug_packet(packet, index); +#endif + + _udp_pcb_write(p->tcpip_if, p->ip_protocol, &p->dst, p->port, packet, index); } /** - * @brief appends search result from query + * @brief frees a packet * - * @param server the server - * @param r the temporary result to copy + * @param packet the packet */ -static void _mdns_add_result(mdns_server_t * server, mdns_result_temp_t * r) +static void _mdns_free_tx_packet(mdns_tx_packet_t * packet) { - mdns_result_t * n = (mdns_result_t *)malloc(sizeof(mdns_result_t)); - if (!n) { + if (!packet) { return; } - n->priority = r->priority; - n->weight = r->weight; - n->port = r->port; - n->addr.addr = r->addr; - - size_t hlen = strlen(r->host); - if (hlen) { - n->host = strdup(r->host); - if (!n->host) { - free(n); - return; - } - } else { - n->host = NULL; - } - - size_t ilen = strlen(r->instance); - if (ilen) { - n->instance = strdup(r->instance); - if (!n->instance) { - free((char *)n->host); - free(n); - return; - } - } else { - n->instance = NULL; - } - - size_t tlen = strlen(r->txt); - if (tlen) { - n->txt = strdup(r->txt); - if (!n->txt) { - free((char *)n->host); - free((char *)n->instance); - free(n); - return; - } - } else { - n->txt = NULL; - } - - memcpy((uint8_t *)n->addrv6.addr, r->addrv6, sizeof(ip6_addr_t)); - - mdns_result_t * o = server->search.results; - server->search.results = n; - n->next = o; + queueFree(mdns_out_question_t, packet->questions); + queueFree(mdns_out_answer_t, packet->answers); + queueFree(mdns_out_answer_t, packet->servers); + queueFree(mdns_out_answer_t, packet->additional); + free(packet); } /** - * @brief finds service from given service type - * @param server the server - * @param service service type to match - * @param proto proto to match + * @brief schedules a packet to be sent after given milliseconds * - * @return the service item if found or NULL on error + * @param packet the packet + * @param ms_after number of milliseconds after which the packet should be dispatched */ -static mdns_srv_item_t * _mdns_get_service_item(mdns_server_t * server, const char * service, const char * proto) +static void _mdns_schedule_tx_packet(mdns_tx_packet_t * packet, uint32_t ms_after) { - mdns_srv_item_t * s = server->services; - while (s) { - if (!strcmp(s->service->service, service) && !strcmp(s->service->proto, proto)) { - return s; + if (!packet) { + return; + } + packet->send_at = (xTaskGetTickCount() * portTICK_PERIOD_MS) + ms_after; + packet->next = NULL; + if (!_mdns_server->tx_queue_head || _mdns_server->tx_queue_head->send_at > packet->send_at) { + packet->next = _mdns_server->tx_queue_head; + _mdns_server->tx_queue_head = packet; + return; + } + mdns_tx_packet_t * q = _mdns_server->tx_queue_head; + while (q->next && q->next->send_at <= packet->send_at) { + q = q->next; + } + packet->next = q->next; + q->next = packet; +} + +/** + * @brief free all packets scheduled for sending + */ +static void _mdns_clear_tx_queue_head() +{ + mdns_tx_packet_t * q; + while (_mdns_server->tx_queue_head) { + q = _mdns_server->tx_queue_head; + _mdns_server->tx_queue_head = _mdns_server->tx_queue_head->next; + _mdns_free_tx_packet(q); + } +} + +/** + * @brief clear packets scheduled for sending on a specific interface + * + * @param tcpip_if the interface + * @param ip_protocol pcb type V4/V6 + */ +static void _mdns_clear_pcb_tx_queue_head(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_tx_packet_t * q, * p; + while (_mdns_server->tx_queue_head && _mdns_server->tx_queue_head->tcpip_if == tcpip_if && _mdns_server->tx_queue_head->ip_protocol == ip_protocol) { + q = _mdns_server->tx_queue_head; + _mdns_server->tx_queue_head = _mdns_server->tx_queue_head->next; + _mdns_free_tx_packet(q); + } + if (_mdns_server->tx_queue_head) { + q = _mdns_server->tx_queue_head; + while (q->next) { + if (q->next->tcpip_if == tcpip_if && q->next->ip_protocol == ip_protocol) { + p = q->next; + q->next = p->next; + _mdns_free_tx_packet(p); + } else { + q = q->next; + } } - s = s->next; + } +} + +/** + * @brief get the next packet scheduled for sending on a specific interface + * + * @param tcpip_if the interface + * @param ip_protocol pcb type V4/V6 + */ +static mdns_tx_packet_t * _mdns_get_next_pcb_packet(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_tx_packet_t * q = _mdns_server->tx_queue_head; + while (q) { + if (q->tcpip_if == tcpip_if && q->ip_protocol == ip_protocol) { + return q; + } + q = q->next; } return NULL; } +/** + * @brief Find, remove and free answer from the scheduled packets + */ +static void _mdns_remove_scheduled_answer(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint16_t type, mdns_srv_item_t * service) +{ + mdns_srv_item_t s = {NULL, NULL}; + if (!service) { + service = &s; + } + mdns_tx_packet_t * q = _mdns_server->tx_queue_head; + while (q) { + if (q->tcpip_if == tcpip_if && q->ip_protocol == ip_protocol && q->distributed) { + mdns_out_answer_t * a = q->answers; + if (a->type == type && a->service == service->service) { + q->answers = q->answers->next; + free(a); + } else { + while (a->next) { + if (a->next->type == type && a->next->service == service->service) { + mdns_out_answer_t * b = a->next; + a->next = b->next; + free(b); + break; + } + a = a->next; + } + } + } + q = q->next; + } +} + +/** + * @brief Remove and free answer from answer list (destination) + */ +static void _mdns_dealloc_answer(mdns_out_answer_t ** destnation, uint16_t type, mdns_srv_item_t * service) +{ + mdns_out_answer_t * d = *destnation; + if (!d) { + return; + } + mdns_srv_item_t s = {NULL, NULL}; + if (!service) { + service = &s; + } + if (d->type == type && d->service == service->service) { + *destnation = d->next; + free(d); + return; + } + while (d->next) { + mdns_out_answer_t * a = d->next; + if (a->type == type && a->service == service->service) { + d->next = a->next; + free(a); + return; + } + d = d->next; + } +} + +/** + * @brief Allocate new answer and add it to answer list (destination) + */ +static bool _mdns_alloc_answer(mdns_out_answer_t ** destnation, uint16_t type, mdns_service_t * service, bool flush, bool bye) +{ + mdns_out_answer_t * d = *destnation; + while (d) { + if (d->type == type && d->service == service) { + return true; + } + d = d->next; + } + + mdns_out_answer_t * a = (mdns_out_answer_t *)malloc(sizeof(mdns_out_answer_t)); + if (!a) { + return false; + } + a->type = type; + a->service = service; + a->bye = bye; + a->flush = flush; + a->next = NULL; + queueToEnd(mdns_out_answer_t, *destnation, a); + return true; +} + +/** + * @brief Allocate new packet for sending + */ +static mdns_tx_packet_t * _mdns_alloc_packet_default(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_tx_packet_t * packet = (mdns_tx_packet_t*)malloc(sizeof(mdns_tx_packet_t)); + if (!packet) { + return NULL; + } + memset((uint8_t*)packet, 0, sizeof(mdns_tx_packet_t)); + packet->tcpip_if = tcpip_if; + packet->ip_protocol = ip_protocol; + packet->port = MDNS_SERVICE_PORT; + if (ip_protocol == MDNS_IP_PROTOCOL_V4) { + IP_ADDR4(&packet->dst, 224, 0, 0, 251); + } else { + ip_addr_t addr = IPADDR6_INIT(0x000002ff, 0, 0, 0xfb000000); + memcpy(&packet->dst, &addr, sizeof(ip_addr_t)); + } + return packet; +} + +/** + * @brief Create answer packet to questions from parsed packet + */ +static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t * parsed_packet) +{ + if (!parsed_packet->questions) { + return; + } + bool send_flush = parsed_packet->src_port == MDNS_SERVICE_PORT; + bool unicast = false; + bool shared = false; + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(parsed_packet->tcpip_if, parsed_packet->ip_protocol); + if (!packet) { + return; + } + packet->flags = MDNS_FLAGS_AUTHORITATIVE; + packet->distributed = parsed_packet->distributed; + + mdns_parsed_question_t * q = parsed_packet->questions; + while (q) { + mdns_srv_item_t * service = NULL; + if (q->service && q->proto) { + service = _mdns_get_service_item(q->service, q->proto); + if (!service) { + continue; + } + } + if (q->unicast) { + unicast = true; + } + if (service) { + if (q->type == MDNS_TYPE_PTR || q->type == MDNS_TYPE_ANY) { + if (q->type == MDNS_TYPE_PTR) { + shared = true; + } + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, service->service, send_flush, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, service->service, send_flush, false) + || !_mdns_alloc_answer(shared?&packet->additional:&packet->answers, MDNS_TYPE_A, NULL, send_flush, false) + || !_mdns_alloc_answer(shared?&packet->additional:&packet->answers, MDNS_TYPE_AAAA, NULL, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } else if (q->type == MDNS_TYPE_SRV) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, service->service, send_flush, false) + || !_mdns_alloc_answer(&packet->additional, MDNS_TYPE_A, NULL, send_flush, false) + || !_mdns_alloc_answer(&packet->additional, MDNS_TYPE_AAAA, NULL, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } else if (q->type == MDNS_TYPE_TXT) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, service->service, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } else if (q->type == MDNS_TYPE_SDPTR) { + shared = true; + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service->service, false, false)) { + _mdns_free_tx_packet(packet); + return; + } + } + } else { + if (q->type == MDNS_TYPE_ANY || q->type == MDNS_TYPE_A || q->type == MDNS_TYPE_AAAA) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, send_flush, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } else if (!_mdns_alloc_answer(&packet->answers, q->type, NULL, send_flush, false)) { + _mdns_free_tx_packet(packet); + return; + } + } + q = q->next; + } + if (unicast || !send_flush) { + memcpy(&packet->dst, &parsed_packet->src, sizeof(ip_addr_t)); + packet->port = parsed_packet->src_port; + } + + static uint8_t share_step = 0; + if (shared) { + _mdns_schedule_tx_packet(packet, 25 + (share_step * 25)); + share_step = (share_step + 1) & 0x03; + } else { + _mdns_dispatch_tx_packet(packet); + _mdns_free_tx_packet(packet); + } +} + +/** + * @brief Check if question is already in the list + */ +static bool _mdns_question_exists(mdns_out_question_t * needle, mdns_out_question_t * haystack) +{ + while (haystack) { + if (haystack->type == needle->type + && haystack->host == needle->host + && haystack->service == needle->service + && haystack->proto == needle->proto) { + return true; + } + haystack = haystack->next; + } + return false; +} + +/** + * @brief Create probe packet for particular services on particular PCB + */ +static mdns_tx_packet_t * _mdns_create_probe_packet(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t * services[], size_t len, bool first, bool include_ip) +{ + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol); + if (!packet) { + return NULL; + } + + size_t i; + for (i=0; inext = NULL; + q->unicast = first; + q->type = MDNS_TYPE_ANY; + q->host = (services[i]->service->instance)?services[i]->service->instance + :(_mdns_server->instance)?_mdns_server->instance + :_mdns_server->hostname; + q->service = services[i]->service->service; + q->proto = services[i]->service->proto; + q->domain = MDNS_DEFAULT_DOMAIN; + if (_mdns_question_exists(q, packet->questions)) { + free(q); + } else { + queueToEnd(mdns_out_question_t, packet->questions, q); + } + + if (!_mdns_alloc_answer(&packet->servers, MDNS_TYPE_SRV, services[i]->service, false, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + + if (include_ip) { + mdns_out_question_t * q = (mdns_out_question_t *)malloc(sizeof(mdns_out_question_t)); + if (!q) { + _mdns_free_tx_packet(packet); + return NULL; + } + q->next = NULL; + q->unicast = first; + q->type = MDNS_TYPE_ANY; + q->host = _mdns_server->hostname; + q->service = NULL; + q->proto = NULL; + q->domain = MDNS_DEFAULT_DOMAIN; + if (_mdns_question_exists(q, packet->questions)) { + free(q); + } else { + queueToEnd(mdns_out_question_t, packet->questions, q); + } + + if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb) { + if (!_mdns_alloc_answer(&packet->servers, MDNS_TYPE_A, NULL, false, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + + if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb) { + if (!_mdns_alloc_answer(&packet->servers, MDNS_TYPE_AAAA, NULL, false, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + } + + return packet; +} + +/** + * @brief Create announce packet for particular services on particular PCB + */ +static mdns_tx_packet_t * _mdns_create_announce_packet(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t * services[], size_t len, bool include_ip) +{ + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol); + if (!packet) { + return NULL; + } + packet->flags = MDNS_FLAGS_AUTHORITATIVE; + + uint8_t i; + for (i=0; ianswers, MDNS_TYPE_SDPTR, services[i]->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, services[i]->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, services[i]->service, true, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, services[i]->service, true, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + if (include_ip) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, true, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, true, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + return packet; +} + +/** + * @brief Convert probe packet to announce + */ +static mdns_tx_packet_t * _mdns_create_announce_from_probe(mdns_tx_packet_t * probe) +{ + + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(probe->tcpip_if, probe->ip_protocol); + if (!packet) { + return NULL; + } + packet->flags = MDNS_FLAGS_AUTHORITATIVE; + + mdns_out_answer_t * s = probe->servers; + while (s) { + if (s->type == MDNS_TYPE_SRV) { + if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SDPTR, s->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, s->service, false, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SRV, s->service, true, false) + || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_TXT, s->service, true, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + + } else if (s->type == MDNS_TYPE_A || s->type == MDNS_TYPE_AAAA) { + if (!_mdns_alloc_answer(&packet->answers, s->type, NULL, true, false)) { + _mdns_free_tx_packet(packet); + return NULL; + } + } + + s = s->next; + } + return packet; +} + +/** + * @brief Send by for particular services on particular PCB + */ +static void _mdns_pcb_send_bye(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool include_ip) +{ + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol); + if (!packet) { + return; + } + size_t i; + for (i=0; ianswers, MDNS_TYPE_PTR, services[i]->service, true, true)) { + _mdns_free_tx_packet(packet); + return; + } + } + if (include_ip && (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_A, NULL, true, true) || !_mdns_alloc_answer(&packet->answers, MDNS_TYPE_AAAA, NULL, true, true))) { + _mdns_free_tx_packet(packet); + return; + } + _mdns_dispatch_tx_packet(packet); + _mdns_free_tx_packet(packet); +} + +/** + * @brief Send probe for particular services on particular PCB + */ +static void _mdns_init_pcb_probe(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool probe_ip) +{ + mdns_pcb_t * pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol]; + size_t services_final_len = len; + + _mdns_clear_pcb_tx_queue_head(tcpip_if, ip_protocol); + + if (!_mdns_server->hostname || !_mdns_server->hostname[0]) { + pcb->state = PCB_RUNNING; + return; + } + + if (PCB_STATE_IS_PROBING(pcb)) { + services_final_len += pcb->probe_services_len; + } + mdns_srv_item_t ** _services = NULL; + if (services_final_len) { + _services = (mdns_srv_item_t **)malloc(sizeof(mdns_srv_item_t *) * services_final_len); + if (!_services) { + return; + } + + size_t i; + for (i=0; iprobe_services) { + for (i=0; iprobe_services_len; i++) { + _services[len+i] = pcb->probe_services[i]; + } + free(pcb->probe_services); + } + } + + probe_ip = pcb->probe_ip || probe_ip; + + pcb->probe_ip = false; + pcb->probe_services = NULL; + pcb->probe_services_len = 0; + pcb->probe_running = false; + + mdns_tx_packet_t * packet = _mdns_create_probe_packet(tcpip_if, ip_protocol, _services, services_final_len, true, probe_ip); + if (!packet) { + free(_services); + return; + } + + pcb->probe_ip = probe_ip; + pcb->probe_services = _services; + pcb->probe_services_len = services_final_len; + pcb->probe_running = true; + _mdns_schedule_tx_packet(packet, ((pcb->failed_probes > 5)?1000:120) + (esp_random() & 0x7F)); + pcb->state = PCB_PROBE_1; +} + +/** + * @brief Restart the responder on particular PCB + */ +static void _mdns_restart_pcb(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + size_t srv_count = 0; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + srv_count++; + a = a->next; + } + mdns_srv_item_t * services[srv_count]; + size_t i = 0; + a = _mdns_server->services; + while (a) { + services[i++] = a; + a = a->next; + } + _mdns_init_pcb_probe(tcpip_if, ip_protocol, services, srv_count, true); +} + +/** + * @brief Send by for particular services + */ +static void _mdns_send_bye(mdns_srv_item_t ** services, size_t len, bool include_ip) +{ + uint8_t i, j; + if (!_mdns_server->hostname || !_mdns_server->hostname[0]) { + return; + } + + for (i=0; iinterfaces[i].pcbs[j].pcb && _mdns_server->interfaces[i].pcbs[j].state == PCB_RUNNING) { + _mdns_pcb_send_bye((tcpip_adapter_if_t)i, (mdns_ip_protocol_t)j, services, len, include_ip); + } + } + } +} + +/** + * @brief Send announcement on particular PCB + */ +static void _mdns_announce_pcb(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t ** services, size_t len, bool include_ip) +{ + mdns_pcb_t * _pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol]; + size_t i; + if (_pcb->pcb) { + if (PCB_STATE_IS_PROBING(_pcb)) { + _mdns_init_pcb_probe(tcpip_if, ip_protocol, services, len, include_ip); + } else if (PCB_STATE_IS_ANNOUNCING(_pcb)) { + mdns_tx_packet_t * p = _mdns_get_next_pcb_packet(tcpip_if, ip_protocol); + if (p) { + for (i=0; ianswers, MDNS_TYPE_SDPTR, services[i]->service, false, false) + || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_PTR, services[i]->service, false, false) + || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_SRV, services[i]->service, true, false) + || !_mdns_alloc_answer(&p->answers, MDNS_TYPE_TXT, services[i]->service, true, false)) { + break; + } + } + if (include_ip) { + _mdns_dealloc_answer(&p->additional, MDNS_TYPE_A, NULL); + _mdns_dealloc_answer(&p->additional, MDNS_TYPE_AAAA, NULL); + _mdns_alloc_answer(&p->answers, MDNS_TYPE_A, NULL, true, false); + _mdns_alloc_answer(&p->answers, MDNS_TYPE_AAAA, NULL, true, false); + } + _pcb->state = PCB_ANNOUNCE_1; + } + } else if (_pcb->state == PCB_RUNNING) { + + if (!_mdns_server->hostname || !_mdns_server->hostname[0]) { + return; + } + + _pcb->state = PCB_ANNOUNCE_1; + mdns_tx_packet_t * p = _mdns_create_announce_packet(tcpip_if, ip_protocol, services, len, include_ip); + if (p) { + _mdns_schedule_tx_packet(p, 0); + } + } + } +} + +/** + * @brief Send probe on all active PCBs + */ +static void _mdns_probe_all_pcbs(mdns_srv_item_t ** services, size_t len, bool probe_ip, bool clear_old_probe) +{ + uint8_t i, j; + for (i=0; iinterfaces[i].pcbs[j].pcb) { + mdns_pcb_t * _pcb = &_mdns_server->interfaces[i].pcbs[j]; + if (clear_old_probe) { + free(_pcb->probe_services); + _pcb->probe_services = NULL; + _pcb->probe_services_len = 0; + _pcb->probe_running = false; + } + _mdns_init_pcb_probe((tcpip_adapter_if_t)i, (mdns_ip_protocol_t)j, services, len, probe_ip); + } + } + } +} + +/** + * @brief Send announcement on all active PCBs + */ +static void _mdns_announce_all_pcbs(mdns_srv_item_t ** services, size_t len, bool include_ip) +{ + uint8_t i, j; + for (i=0; iservices; + while (a) { + if (!a->service->instance) { + srv_count++; + } + a = a->next; + } + if (!srv_count) { + return; + } + mdns_srv_item_t * services[srv_count]; + size_t i = 0; + a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + services[i++] = a; + } + a = a->next; + } + _mdns_send_bye(services, srv_count, include_ip); +} + +/** + * @brief Stop the responder on all services without instance + */ +static void _mdns_send_bye_all_pcbs_no_instance() +{ + size_t srv_count = 0; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + srv_count++; + } + a = a->next; + } + if (!srv_count) { + return; + } + mdns_srv_item_t * services[srv_count]; + size_t i = 0; + a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + services[i++] = a; + } + a = a->next; + } + _mdns_send_bye(services, srv_count, false); +} + +/** + * @brief Restart the responder on all services without instance + */ +static void _mdns_restart_all_pcbs_no_instance() +{ + size_t srv_count = 0; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + srv_count++; + } + a = a->next; + } + if (!srv_count) { + return; + } + mdns_srv_item_t * services[srv_count]; + size_t i = 0; + a = _mdns_server->services; + while (a) { + if (!a->service->instance) { + services[i++] = a; + } + a = a->next; + } + _mdns_probe_all_pcbs(services, srv_count, false, true); +} + +/** + * @brief Restart the responder on all active PCBs + */ +static void _mdns_restart_all_pcbs() +{ + _mdns_clear_tx_queue_head(); + size_t srv_count = 0; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + srv_count++; + a = a->next; + } + mdns_srv_item_t * services[srv_count]; + size_t l = 0; + a = _mdns_server->services; + while (a) { + services[l++] = a; + a = a->next; + } + + _mdns_probe_all_pcbs(services, srv_count, true, true); +} + + + +/** + * @brief creates/allocates new text item list + * @param num_items service number of txt items or 0 + * @param txt service txt items array or NULL + * + * @return pointer to the linked txt item list or NULL + */ +static mdns_txt_linked_item_t * _mdns_allocate_txt(size_t num_items, mdns_txt_item_t txt[]) +{ + mdns_txt_linked_item_t * new_txt = NULL; + size_t i = 0; + if (num_items) { + for (i=0; ikey = strdup(txt[i].key); + if (!new_item->key) { + free(new_item); + break; + } + new_item->value = strdup(txt[i].value); + if (!new_item->value) { + free((char *)new_item->key); + free(new_item); + break; + } + new_item->next = new_txt; + new_txt = new_item; + } + } + return new_txt; +} + /** * @brief creates/allocates new service * @param service service type * @param proto service proto * @param port service port + * @param instance service instance + * @param num_items service number of txt items or 0 + * @param txt service txt items array or NULL * * @return pointer to the service or NULL on error */ -static mdns_service_t * _mdns_create_service(const char * service, const char * proto, uint16_t port) +static mdns_service_t * _mdns_create_service(const char * service, const char * proto, uint16_t port, const char * instance, size_t num_items, mdns_txt_item_t txt[]) { mdns_service_t * s = (mdns_service_t *)malloc(sizeof(mdns_service_t)); if (!s) { return NULL; } + mdns_txt_linked_item_t * new_txt = _mdns_allocate_txt(num_items, txt); + if (num_items && new_txt == NULL) { + free(s); + return NULL; + } + s->priority = 0; s->weight = 0; - s->txt_num_items = 0; - s->instance = NULL; - s->txt = NULL; + s->instance = instance?strndup(instance, MDNS_NAME_BUF_LEN - 1):NULL; + s->txt = new_txt; s->port = port; s->service = strndup(service, MDNS_NAME_BUF_LEN - 1); @@ -1343,16 +2062,282 @@ static void _mdns_free_service(mdns_service_t * service) free((char *)service->instance); free((char *)service->service); free((char *)service->proto); - if (service->txt_num_items) { - uint8_t i; - for (i = 0; i < service->txt_num_items; i++) { - free((char *)service->txt[i]); - } + while (service->txt) { + mdns_txt_linked_item_t * s = service->txt; + service->txt = service->txt->next; + free((char *)s->key); + free((char *)s->value); + free(s); } free(service->txt); free(service); } + +/* + * Received Packet Handling + * */ + +/** + * @brief Detect SRV collision + */ +static int _mdns_check_srv_collision(mdns_service_t * service, uint16_t priority, uint16_t weight, uint16_t port, const char * host, const char * domain) +{ + size_t our_host_len = strlen(_mdns_server->hostname); + size_t our_len = 14 + our_host_len; + + size_t their_host_len = strlen(host); + size_t their_domain_len = strlen(domain); + size_t their_len = 9 + their_host_len + their_domain_len; + + if (their_len > our_len) { + return 1;//they win + } else if (their_len < our_len) { + return -1;//we win + } + + uint16_t our_index = 0; + uint8_t our_data[our_len]; + _mdns_append_u16(our_data, &our_index, service->priority); + _mdns_append_u16(our_data, &our_index, service->weight); + _mdns_append_u16(our_data, &our_index, service->port); + our_data[our_index++] = our_host_len; + memcpy(our_data + our_index, _mdns_server->hostname, our_host_len); + our_index += our_host_len; + our_data[our_index++] = 5; + memcpy(our_data + our_index, MDNS_DEFAULT_DOMAIN, 5); + our_index += 5; + our_data[our_index++] = 0; + + uint16_t their_index = 0; + uint8_t their_data[their_len]; + _mdns_append_u16(their_data, &their_index, priority); + _mdns_append_u16(their_data, &their_index, weight); + _mdns_append_u16(their_data, &their_index, port); + their_data[their_index++] = their_host_len; + memcpy(their_data + their_index, host, their_host_len); + their_index += their_host_len; + their_data[their_index++] = their_domain_len; + memcpy(their_data + their_index, domain, their_domain_len); + their_index += their_domain_len; + their_data[their_index++] = 0; + + int ret = memcmp(our_data, their_data, our_len); + if (ret > 0) { + return -1;//we win + } else if (ret < 0) { + return 1;//they win + } + return 0;//same +} + +/** + * @brief Detect TXT collision + */ +static int _mdns_check_txt_collision(mdns_service_t * service, const uint8_t * data, size_t len) +{ + size_t data_len = 1; + if (len == 1 && service->txt) { + return -1;//we win + } else if (len > 1 && !service->txt) { + return 1;//they win + } else if (len == 1 && !service->txt) { + return 0;//same + } + + mdns_txt_linked_item_t * txt = service->txt; + while (txt) { + data_len += 2 + strlen(service->txt->key) + strlen(service->txt->value); + txt = txt->next; + } + + if (len > data_len) { + return 1;//they win + } else if (len < data_len) { + return -1;//we win + } + + uint8_t ours[len]; + uint16_t index = 0; + char * tmp; + + txt = service->txt; + while (txt) { + tmp = (char *)malloc(2 + strlen(txt->key) + strlen(txt->value)); + if (tmp) { + sprintf(tmp, "%s=%s", txt->key, txt->value); + _mdns_append_string(ours, &index, tmp); + free(tmp); + } + txt = txt->next; + } + + int ret = memcmp(ours, data, len); + if (ret > 0) { + return -1;//we win + } else if (ret < 0) { + return 1;//they win + } + return 0;//same +} + +/** + * @brief Set interface as duplicate if another is found on the same subnet + */ +static void _mdns_dup_interface(tcpip_adapter_if_t tcpip_if) +{ + uint8_t i; + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + for (i=0; iinterfaces[other_if].pcbs[i].pcb) { + //stop this interface and mark as dup + if (_mdns_server->interfaces[tcpip_if].pcbs[i].pcb) { + _mdns_clear_pcb_tx_queue_head(tcpip_if, i); + _mdns_pcb_deinit(tcpip_if, i); + } + _mdns_server->interfaces[tcpip_if].pcbs[i].state = PCB_DUP; + _mdns_announce_pcb(other_if, i, NULL, 0, true); + } + } +} + +/** + * @brief Detect IPv4 address collision + */ +static int _mdns_check_a_collision(ip4_addr_t * ip, tcpip_adapter_if_t tcpip_if) +{ + tcpip_adapter_ip_info_t if_ip_info; + tcpip_adapter_ip_info_t other_ip_info; + if (!ip->addr) { + return 1;//denial! they win + } + if (tcpip_adapter_get_ip_info(tcpip_if, &if_ip_info)) { + return 1;//they win + } + + int ret = memcmp((uint8_t*)&if_ip_info.ip.addr, (uint8_t*)&ip->addr, sizeof(ip4_addr_t)); + if (ret > 0) { + return -1;//we win + } else if (ret < 0) { + //is it the other interface? + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (other_if == TCPIP_ADAPTER_IF_MAX) { + return 1;//AP interface! They win + } + if (tcpip_adapter_get_ip_info(other_if, &other_ip_info)) { + return 1;//IPv4 not active! They win + } + if (ip->addr != other_ip_info.ip.addr) { + return 1;//IPv4 not ours! They win + } + _mdns_dup_interface(tcpip_if); + return 2;//they win + } + return 0;//same +} + +/** + * @brief Detect IPv6 address collision + */ +static int _mdns_check_aaaa_collision(ip6_addr_t * ip, tcpip_adapter_if_t tcpip_if) +{ + struct ip6_addr if_ip6; + struct ip6_addr other_ip6; + if (_ipv6_address_is_zero(*ip)) { + return 1;//denial! they win + } + if (tcpip_adapter_get_ip6_linklocal(tcpip_if, &if_ip6)) { + return 1;//they win + } + int ret = memcmp((uint8_t*)&if_ip6.addr, (uint8_t*)ip->addr, sizeof(ip6_addr_t)); + if (ret > 0) { + return -1;//we win + } else if (ret < 0) { + //is it the other interface? + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (other_if == TCPIP_ADAPTER_IF_MAX) { + return 1;//AP interface! They win + } + if (tcpip_adapter_get_ip6_linklocal(other_if, &other_ip6)) { + return 1;//IPv6 not active! They win + } + if (memcmp((uint8_t*)&other_ip6.addr, (uint8_t*)ip->addr, sizeof(ip6_addr_t))) { + return 1;//IPv6 not ours! They win + } + _mdns_dup_interface(tcpip_if); + return 2;//they win + } + return 0;//same +} + +/** + * @brief Check if parsed name is discovery + */ +static bool _mdns_name_is_discovery(mdns_name_t * name, uint16_t type) +{ + return ( + (name->host && name->host[0] && !strcasecmp(name->host, "_services")) + && (name->service && name->service[0] && !strcasecmp(name->service, "_dns-sd")) + && (name->proto && name->proto[0] && !strcasecmp(name->proto, "_udp")) + && (name->domain && name->domain[0] && !strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN)) + && type == MDNS_TYPE_PTR + ); +} + +/** + * @brief Check if the parsed name is ours (matches service or host name) + */ +static bool _mdns_name_is_ours(mdns_name_t * name) +{ + //domain have to be "local" + if (!name->domain || !name->domain[0] || strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN)) { + return false; + } + + //if service and proto are empty, host must match out hostname + if ((!name->service || !name->service[0]) && (!name->proto || !name->proto[0])) { + if (name->host && name->host[0] && strcasecmp(name->host, _mdns_server->hostname) == 0) { + return true; + } + return false; + } + + //if service or proto is empty, name is invalid + if ((!name->service || !name->service[0]) || (!name->proto || !name->proto[0])) { + return false; + } + + //find the service + mdns_srv_item_t * service = _mdns_get_service_item(name->service, name->proto); + if (!service) { + return false; + } + + //if host is empty and we have service, we have success + if (!name->host || !name->host[0]) { + return true; + } + + //OK we have host in the name. find what is the instance of the service + const char * instance = service->service->instance; + if (instance == NULL) { + if (_mdns_server->instance && _mdns_server->instance[0]) { + instance = _mdns_server->instance; + } else if (_mdns_server->hostname && _mdns_server->hostname[0]) { + instance = _mdns_server->hostname; + } else { + return false; + } + } + + //compare the instance against the name + if (strcasecmp(name->host, instance) == 0) { + return true; + } + + return false; +} + /** * @brief read uint16_t from a packet * @param packet the packet @@ -1362,374 +2347,1928 @@ static void _mdns_free_service(mdns_service_t * service) */ static inline uint16_t _mdns_read_u16(const uint8_t * packet, uint16_t index) { - return (uint16_t)(packet[index]) << 8 | packet[index + 1]; + return (uint16_t)(packet[index]) << 8 | packet[index+1]; +} + +/** + * @brief read uint32_t from a packet + * @param packet the packet + * @param index index in the packet where the value starts + * + * @return the value + */ +static inline uint32_t _mdns_read_u32(const uint8_t * packet, uint16_t index) +{ + return (uint32_t)(packet[index]) << 24 | (uint32_t)(packet[index+1]) << 16 | (uint32_t)(packet[index+2]) << 8 | packet[index+3]; +} + +/** + * @brief reads and formats MDNS FQDN into mdns_name_t structure + * + * @param packet MDNS packet + * @param start Starting point of FQDN + * @param name mdns_name_t structure to populate + * + * @return the address after the parsed FQDN in the packet or NULL on error + */ +static const uint8_t * _mdns_parse_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name) +{ + name->parts = 0; + name->sub = 0; + name->host[0] = 0; + name->service[0] = 0; + name->proto[0] = 0; + name->domain[0] = 0; + + static char buf[MDNS_NAME_BUF_LEN]; + + const uint8_t * next_data = (uint8_t*)_mdns_read_fqdn(packet, start, name, buf); + if (!next_data) { + return 0; + } + if (!name->parts) { + return next_data; + } + if (name->parts == 3) { + memmove((uint8_t*)name + (MDNS_NAME_BUF_LEN), (uint8_t*)name, 3*(MDNS_NAME_BUF_LEN)); + name->host[0] = 0; + } else if (name->parts == 2) { + memmove((uint8_t*)(name->domain), (uint8_t*)(name->service), (MDNS_NAME_BUF_LEN)); + name->service[0] = 0; + name->proto[0] = 0; + } + if (strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN) == 0 || strcasecmp(name->domain, "arpa") == 0) { + return next_data; + } + return 0; +} + +/** + * @brief Called from parser to check if question matches particular service + */ +static bool _mdns_question_matches(mdns_parsed_question_t * question, uint16_t type, mdns_srv_item_t * service) +{ + if (question->type != type) { + return false; + } + if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) { + return true; + } else if (type == MDNS_TYPE_PTR || type == MDNS_TYPE_SDPTR) { + if (!strcasecmp(service->service->service, question->service) + && !strcasecmp(service->service->proto, question->proto) + && !strcasecmp(MDNS_DEFAULT_DOMAIN, question->domain)) { + return true; + } + } else if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) { + const char * name = service->service->instance; + if (!name) { + name = _mdns_server->instance; + } + if (!name) { + name = _mdns_server->hostname; + } + if (!strcasecmp(name, question->host) + && !strcasecmp(service->service->service, question->service) + && !strcasecmp(service->service->proto, question->proto) + && !strcasecmp(MDNS_DEFAULT_DOMAIN, question->domain)) { + return true; + } + } + + return false; +} + +/** + * @brief Removes saved question from parsed data + */ +static void _mdns_remove_parsed_question(mdns_parsed_packet_t * parsed_packet, uint16_t type, mdns_srv_item_t * service) +{ + mdns_parsed_question_t * q = parsed_packet->questions; + + if (_mdns_question_matches(q, type, service)) { + parsed_packet->questions = q->next; + free(q->host); + free(q->service); + free(q->proto); + free(q->domain); + free(q); + return; + } + + while (q->next) { + mdns_parsed_question_t * p = q->next; + if (_mdns_question_matches(p, type, service)) { + q->next = p->next; + free(p->host); + free(p->service); + free(p->proto); + free(p->domain); + free(p); + return; + } + q = q->next; + } +} + +/** + * @brief Get number of items in TXT parsed data + */ +static int _mdns_txt_items_count_get(const uint8_t * data, size_t len) +{ + if (len == 1) { + return 0; + } + + int num_items = 0; + uint16_t i=0; + size_t partLen = 0; + + while (i < len) { + partLen = data[i++]; + if (!partLen) { + break; + } + if ((i+partLen) > len) { + return -1;//error + } + i+=partLen; + num_items++; + } + return num_items; +} + +/** + * @brief Get the length of TXT item's key name + */ +static int _mdns_txt_item_name_get_len(const uint8_t * data, size_t len) +{ + int i; + if (*data == '=') { + return -1; + } + for (i = 0; i < len; i++) { + if (data[i] == '=') { + return i; + } + } + return len; +} + +/** + * @brief Create TXT result array from parsed TXT data + */ +static void _mdns_result_txt_create(const uint8_t * data, size_t len, mdns_txt_item_t ** out_txt, size_t * out_count) +{ + *out_txt = NULL; + *out_count = 0; + uint16_t i=0, y; + size_t partLen = 0; + int num_items = _mdns_txt_items_count_get(data, len); + if (num_items < 0) { + return;//error + } + + if (!num_items) { + return; + } + + mdns_txt_item_t * txt = (mdns_txt_item_t *)malloc(sizeof(mdns_txt_item_t) * num_items); + memset(txt, 0, sizeof(mdns_txt_item_t) * num_items); + size_t txt_num = 0; + + while (i < len) { + partLen = data[i++]; + if (!partLen) { + break; + } + + if ((i+partLen) > len) { + goto handle_error;//error + } + + int name_len = _mdns_txt_item_name_get_len(data+i, partLen); + if (name_len < 0) {//invalid item (no name) + i += partLen; + continue; + } + char * key = (char *)malloc(name_len + 1); + if (!key) { + goto handle_error;//error + } + + mdns_txt_item_t * t = &txt[txt_num++]; + + memcpy(key, data + i, name_len); + key[name_len] = 0; + i += name_len + 1; + t->key = key; + + int value_len = partLen - name_len - 1; + if (value_len > 0) { + char * value = (char *)malloc(value_len + 1); + memcpy(value, data + i, value_len); + value[value_len] = 0; + i += value_len; + t->value = value; + } + } + + *out_txt = txt; + *out_count = txt_num; + return; + +handle_error : + for (y=0; ykey); + free((char *)t->value); + } + free(txt); +} + +/** + * @brief Duplicate string or return error + */ +static esp_err_t _mdns_strdup_check(char ** out, char * in) +{ + if (in && in[0]) { + *out = strdup(in); + if (!*out) { + return ESP_FAIL; + } + return ESP_OK; + } + *out = NULL; + return ESP_OK; } /** * @brief main packet parser * - * @param server the server - * @param data byte array holding the packet data - * @param len length of the byte array + * @param packet the packet */ -void mdns_parse_packet(mdns_server_t * server, const uint8_t * data, size_t len) +void mdns_parse_packet(mdns_rx_packet_t * packet) { static mdns_name_t n; - static mdns_result_temp_t a; - + mdns_header_t header; + const uint8_t * data = (const uint8_t*)packet->pb->payload; + size_t len = packet->pb->len; const uint8_t * content = data + MDNS_HEAD_LEN; + bool do_not_reply = false; + mdns_search_once_t * search_result = NULL; + +#ifdef MDNS_ENABLE_DEBUG + _mdns_dbg_printf("\nRX[%u][%u]: ", packet->tcpip_if, (uint32_t)packet->ip_protocol); + if (packet->src.type == IPADDR_TYPE_V4) { + _mdns_dbg_printf("From: " IPSTR ":%u, To: " IPSTR ", ", IP2STR(&packet->src.u_addr.ip4), packet->src_port, IP2STR(&packet->dest.u_addr.ip4)); + } else { + _mdns_dbg_printf("From: " IPV6STR ":%u, To: " IPV6STR ", ", IPV62STR(packet->src.u_addr.ip6), packet->src_port, IPV62STR(packet->dest.u_addr.ip6)); + } + mdns_debug_packet(data, len); +#endif + + mdns_parsed_packet_t * parsed_packet = (mdns_parsed_packet_t *)malloc(sizeof(mdns_parsed_packet_t)); + if (!parsed_packet) { + return; + } + memset(parsed_packet, 0, sizeof(mdns_parsed_packet_t)); + mdns_name_t * name = &n; memset(name, 0, sizeof(mdns_name_t)); - uint16_t questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET); - uint16_t answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET); - uint16_t additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET); + header.id = _mdns_read_u16(data, MDNS_HEAD_ID_OFFSET); + header.flags.value = _mdns_read_u16(data, MDNS_HEAD_FLAGS_OFFSET); + header.questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET); + header.answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET); + header.servers = _mdns_read_u16(data, MDNS_HEAD_SERVERS_OFFSET); + header.additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET); - if (questions) { - uint8_t qs = questions; - mdns_answer_item_t * answer_items = NULL; + if (header.flags.value == MDNS_FLAGS_AUTHORITATIVE && packet->src_port != MDNS_SERVICE_PORT) { + free(parsed_packet); + return; + } + + parsed_packet->tcpip_if = packet->tcpip_if; + parsed_packet->ip_protocol = packet->ip_protocol; + parsed_packet->multicast = packet->multicast; + parsed_packet->authoritative = header.flags.value == MDNS_FLAGS_AUTHORITATIVE; + parsed_packet->distributed = header.flags.value == MDNS_FLAGS_DISTRIBUTED; + ip_addr_copy(parsed_packet->src, packet->src); + parsed_packet->src_port = packet->src_port; + + if (header.questions) { + uint8_t qs = header.questions; while (qs--) { content = _mdns_parse_fqdn(data, content, name); if (!content) { - answers = 0; - additional = 0; - break;//error + header.answers = 0; + header.additional = 0; + header.servers = 0; + goto clear_rx_packet;//error } uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t clas = _mdns_read_u16(content, MDNS_CLASS_OFFSET); + bool unicast = !!(clas & 0x8000); + clas &= 0x7FFF; content = content + 4; - if (!name->service[0] || !name->proto[0]) { - if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA || type == MDNS_TYPE_ANY) {//send A + AAAA - if (name->host[0] && server->hostname && server->hostname[0] && !strcmp(name->host, server->hostname)) { - answer_items = _mdns_add_answer(answer_items, NULL, MDNS_ANSWER_A); + if (clas != 0x0001) {//bad class + continue; + } + + if (_mdns_name_is_discovery(name, type)) { + //service discovery + parsed_packet->discovery = true; + mdns_srv_item_t * a = _mdns_server->services; + while (a) { + mdns_parsed_question_t * question = (mdns_parsed_question_t *)malloc(sizeof(mdns_parsed_question_t)); + if (!question) { + goto clear_rx_packet; } - } - continue; - } + question->next = parsed_packet->questions; + parsed_packet->questions = question; - //is this a dns-sd service discovery meta query? - if (!strcmp(name->host, "_services") && !strcmp(name->service, "_dns-sd") && !strcmp(name->proto, "_udp") && !strcmp(name->domain, MDNS_DEFAULT_DOMAIN) && type == MDNS_TYPE_PTR) - { - //add answers for all services - mdns_srv_item_t * s = server->services; - while (s) { - if (s->service->service && s->service->proto) { - answer_items = _mdns_add_answer(answer_items, s->service, MDNS_ANSWER_SDPTR); + question->unicast = unicast; + question->type = MDNS_TYPE_SDPTR; + question->host = NULL; + question->service = strdup(a->service->service); + question->proto = strdup(a->service->proto); + question->domain = strdup(MDNS_DEFAULT_DOMAIN); + if (!question->service || !question->proto || !question->domain) { + goto clear_rx_packet; } - s = s->next; + a = a->next; } continue; + } else if (name->sub || !_mdns_name_is_ours(name)) { + continue; } - if (name->sub) { - continue; + if (type == MDNS_TYPE_ANY) { + parsed_packet->probe = true; } - mdns_srv_item_t * si = _mdns_get_service_item(server, name->service, name->proto); - if (!si) { - //service not found - continue; + mdns_parsed_question_t * question = (mdns_parsed_question_t *)malloc(sizeof(mdns_parsed_question_t)); + if (!question) { + goto clear_rx_packet; } + question->next = parsed_packet->questions; + parsed_packet->questions = question; - if (type == MDNS_TYPE_PTR) { - answer_items = _mdns_add_answer(answer_items, si->service, MDNS_ANSWER_ALL); - } else if (type == MDNS_TYPE_TXT) { - //match instance/host - const char * host = (si->service->instance) ? si->service->instance - : (server->instance) ? server->instance - : server->hostname; - if (!host || !host[0] || !name->host[0] || strcmp(name->host, host)) { - continue; - } - answer_items = _mdns_add_answer(answer_items, si->service, MDNS_ANSWER_TXT); - } else if (type == MDNS_TYPE_SRV) { - //match instance/host - const char * host = (si->service->instance) ? si->service->instance - : (server->instance) ? server->instance - : server->hostname; - if (!host || !host[0] || !name->host[0] || strcmp(name->host, host)) { - continue; - } - answer_items = _mdns_add_answer(answer_items, si->service, MDNS_ANSWER_SRV | MDNS_ANSWER_A); - } else if (type == MDNS_TYPE_ANY) {//send all - //match host - if (!name->host[0] || !server->hostname || !server->hostname[0] || strcmp(name->host, server->hostname)) { - answer_items = _mdns_add_answer(answer_items, si->service, MDNS_ANSWER_ALL); - } + question->unicast = unicast; + question->type = type; + if (_mdns_strdup_check(&(question->host), name->host) + || _mdns_strdup_check(&(question->service), name->service) + || _mdns_strdup_check(&(question->proto), name->proto) + || _mdns_strdup_check(&(question->domain), name->domain)) { + goto clear_rx_packet; } } - if (answer_items) { - _mdns_send_answers(server, answer_items); - } } - if (server->search.running && (answers || additional)) { - mdns_result_temp_t * answer = &a; - memset(answer, 0, sizeof(mdns_result_temp_t)); + if (header.questions && !parsed_packet->questions && !parsed_packet->discovery) { + goto clear_rx_packet; + } else if (header.answers || header.servers || header.additional) { + uint16_t recordIndex = 0; while (content < (data + len)) { + content = _mdns_parse_fqdn(data, content, name); if (!content) { - return;//error + goto clear_rx_packet;//error } + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t clas = _mdns_read_u16(content, MDNS_CLASS_OFFSET); + uint32_t ttl = _mdns_read_u32(content, MDNS_TTL_OFFSET); uint16_t data_len = _mdns_read_u16(content, MDNS_LEN_OFFSET); const uint8_t * data_ptr = content + MDNS_DATA_OFFSET; + clas &= 0x7FFF; content = data_ptr + data_len; if (content > (data + len)) { - return; + goto clear_rx_packet; + } + + bool discovery = false; + bool ours = false; + mdns_srv_item_t * service = NULL; + mdns_parsed_recort_type_t record_type = MDNS_ANSWER; + + if (recordIndex >= (header.answers + header.servers)) { + record_type = MDNS_EXTRA; + } else if (recordIndex >= (header.answers)) { + record_type = MDNS_NS; + } + recordIndex++; + + if (type == MDNS_TYPE_NSEC || type == MDNS_TYPE_OPT) { + //skip NSEC and OPT + continue; + } + + if (parsed_packet->discovery && _mdns_name_is_discovery(name, type)) { + discovery = true; + } else if (!name->sub && _mdns_name_is_ours(name)) { + ours = true; + if (name->service && name->service[0] && name->proto && name->proto[0]) { + service = _mdns_get_service_item(name->service, name->proto); + } + } else { + if (header.questions || !parsed_packet->authoritative || record_type == MDNS_NS) { + //skip this record + continue; + } + search_result = _mdns_search_find_from(_mdns_server->search_once, name, type, packet->tcpip_if, packet->ip_protocol); } if (type == MDNS_TYPE_PTR) { if (!_mdns_parse_fqdn(data, data_ptr, name)) { continue;//error } -#ifndef MDNS_TEST_MODE - if (server->search.host[0] || - (strcmp(name->service, server->search.service) != 0) || - (strcmp(name->proto, server->search.proto) != 0)) { - continue;//not searching for service or wrong service/proto - } -#endif - strlcpy(answer->instance, name->host, MDNS_NAME_BUF_LEN); - } else if (type == MDNS_TYPE_SRV) { -#ifndef MDNS_TEST_MODE - if (server->search.host[0] || - (strcmp(name->service, server->search.service) != 0) || - (strcmp(name->proto, server->search.proto) != 0)) { - continue;//not searching for service or wrong service/proto - } -#endif - if (answer->instance[0]) { - if (strcmp(answer->instance, name->host) != 0) { - continue;//instance name is not the same as the one in the PTR record + if (search_result) { + _mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol); + } else if ((discovery || ours) && !name->sub && _mdns_name_is_ours(name)) { + if (discovery) { + service = _mdns_get_service_item(name->service, name->proto); + _mdns_remove_parsed_question(parsed_packet, MDNS_TYPE_SDPTR, service); + } else if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, service); + } else { + //check if TTL is more than half of the full TTL value (4500) + if (ttl > 2250) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service); + } } - } else { - strlcpy(answer->instance, name->host, MDNS_NAME_BUF_LEN); } - //parse record value + } else if (type == MDNS_TYPE_SRV) { + mdns_result_t * result = NULL; + if (search_result && search_result->type == MDNS_TYPE_PTR) { + result = search_result->result; + while (result) { + if (packet->tcpip_if == result->tcpip_if + && packet->ip_protocol == result->ip_protocol + && result->instance_name && !strcmp(name->host, result->instance_name)) { + break; + } + result = result->next; + } + if (!result) { + result = _mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol); + if (!result) { + continue;//error + } + } + } + if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name)) { continue;//error } + uint16_t priority = _mdns_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET); + uint16_t weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET); + uint16_t port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET); - answer->ptr = 1; - answer->priority = _mdns_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET); - answer->weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET); - answer->port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET); - if (answer->host[0]) { - if (strcmp(answer->host, name->host) != 0) { - answer->addr = 0; - strlcpy(answer->host, name->host, MDNS_NAME_BUF_LEN); + if (search_result) { + if (search_result->type == MDNS_TYPE_PTR) { + result->port = port; + result->hostname = strdup(name->host); + } else { + _mdns_search_result_add_srv(search_result, name->host, port, packet->tcpip_if, packet->ip_protocol); + } + } else if (ours) { + if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, service); + continue; + } else if (parsed_packet->distributed) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service); + continue; + } + //detect collision (-1=won, 0=none, 1=lost) + int col = 0; + if (clas > 1) { + col = 1; + } else if (!clas) { + col = -1; + } else { + col = _mdns_check_srv_collision(service->service, priority, weight, port, name->host, name->domain); + } + if (col && (parsed_packet->probe || parsed_packet->authoritative)) { + if (col > 0 || !port) { + do_not_reply = true; + if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++; + if (service->service->instance) { + char * new_instance = _mdns_mangle_name((char *)service->service->instance); + if (new_instance) { + free((char *)service->service->instance); + service->service->instance = new_instance; + } + _mdns_probe_all_pcbs(&service, 1, false, false); + } else if (_mdns_server->instance) { + char * new_instance = _mdns_mangle_name((char *)_mdns_server->instance); + if (new_instance) { + free((char *)_mdns_server->instance); + _mdns_server->instance = new_instance; + } + _mdns_restart_all_pcbs_no_instance(); + } else { + char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname); + if (new_host) { + free((char *)_mdns_server->hostname); + _mdns_server->hostname = new_host; + } + _mdns_restart_all_pcbs(); + } + } else { + _mdns_pcb_send_bye(packet->tcpip_if, packet->ip_protocol, &service, 1, false); + _mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, &service, 1, false); + } + } + } else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service); } - } else { - strlcpy(answer->host, name->host, MDNS_NAME_BUF_LEN); } } else if (type == MDNS_TYPE_TXT) { - uint16_t i = 0, b = 0, y; - while (i < data_len) { - uint8_t partLen = data_ptr[i++]; - if ((i + partLen) > data_len) { - break;//error + if (search_result) { + mdns_txt_item_t * txt = NULL; + size_t txt_count = 0; + _mdns_result_txt_create(data_ptr, data_len, &txt, &txt_count); + if (!txt_count) { + continue; } - //check if partLen will fit in the buffer - if (partLen > (MDNS_TXT_MAX_LEN - b - 1)) { - break; + + mdns_result_t * result = NULL; + if (search_result->type == MDNS_TYPE_PTR) { + result = search_result->result; + while (result) { + if (packet->tcpip_if == result->tcpip_if + && packet->ip_protocol == result->ip_protocol + && result->instance_name && !strcmp(name->host, result->instance_name)) { + break; + } + result = result->next; + } + if (!result) { + result = _mdns_search_result_add_ptr(search_result, name->host, packet->tcpip_if, packet->ip_protocol); + if (!result) { + continue;//error + } + } + if (!result->txt) { + result->txt = txt; + result->txt_count = txt_count; + } + } else { + _mdns_search_result_add_txt(search_result, txt, txt_count, packet->tcpip_if, packet->ip_protocol); } - for (y = 0; y < partLen; y++) { - char d = data_ptr[i++]; - answer->txt[b++] = d; + } else if (ours) { + if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, service); + continue; } - if (i < data_len) { - answer->txt[b++] = '&'; + //detect collision (-1=won, 0=none, 1=lost) + int col = 0; + if (clas > 1) { + col = 1; + } else if (!clas) { + col = -1; + } else { + col = _mdns_check_txt_collision(service->service, data_ptr, data_len); + } + if (col && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + do_not_reply = true; + _mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, &service, 1, true); + } else if (ttl > 2250 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, service); } } - answer->txt[b] = 0; - } else if (type == MDNS_TYPE_AAAA) { - if (server->search.host[0]) { -#ifndef MDNS_TEST_MODE - if (strcmp(name->host, server->search.host) != 0) { - continue;//wrong host + + } else if (type == MDNS_TYPE_AAAA) {//ipv6 + ip_addr_t ip6; + ip6.type = IPADDR_TYPE_V6; + memcpy(ip6.u_addr.ip6.addr, data_ptr, 16); + if (search_result) { + //check for more applicable searches (PTR & A/AAAA at the same time) + while (search_result) { + _mdns_search_result_add_ip(search_result, name->host, &ip6, packet->tcpip_if, packet->ip_protocol); + search_result = _mdns_search_find_from(search_result->next, name, type, packet->tcpip_if, packet->ip_protocol); + } + } else if (ours) { + if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, NULL); + continue; + } + //detect collision (-1=won, 0=none, 1=lost) + int col = 0; + if (clas > 1) { + col = 1; + } else if (!clas) { + col = -1; + } else { + col = _mdns_check_aaaa_collision(&(ip6.u_addr.ip6), packet->tcpip_if); + } + if (col == 2) { + goto clear_rx_packet; + } else if (col == 1) { + do_not_reply = true; + if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + if (col && (parsed_packet->probe || parsed_packet->authoritative)) { + _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++; + char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname); + if (new_host) { + free((char *)_mdns_server->hostname); + _mdns_server->hostname = new_host; + } + _mdns_restart_all_pcbs(); + } + } else { + _mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, NULL, 0, true); + } + } else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, NULL); } -#endif - } else if (!answer->ptr) { - strlcpy(answer->host, name->host, MDNS_NAME_BUF_LEN); - } else if (strcmp(answer->host, name->host) != 0) { - continue;//wrong host } - memcpy(answer->addrv6, data_ptr, sizeof(ip6_addr_t)); + } else if (type == MDNS_TYPE_A) { - if (server->search.host[0]) { -#ifndef MDNS_TEST_MODE - if (strcmp(name->host, server->search.host) != 0) { - continue;//wrong host + ip_addr_t ip; + ip.type = IPADDR_TYPE_V4; + memcpy(&(ip.u_addr.ip4.addr), data_ptr, 4); + if (search_result) { + //check for more applicable searches (PTR & A/AAAA at the same time) + while (search_result) { + _mdns_search_result_add_ip(search_result, name->host, &ip, packet->tcpip_if, packet->ip_protocol); + search_result = _mdns_search_find_from(search_result->next, name, type, packet->tcpip_if, packet->ip_protocol); + } + } else if (ours) { + if (parsed_packet->questions && !parsed_packet->probe) { + _mdns_remove_parsed_question(parsed_packet, type, NULL); + continue; + } + //detect collision (-1=won, 0=none, 1=lost) + int col = 0; + if (clas > 1) { + col = 1; + } else if (!clas) { + col = -1; + } else { + col = _mdns_check_a_collision(&(ip.u_addr.ip4), packet->tcpip_if); + } + if (col == 2) { + goto clear_rx_packet; + } else if (col == 1) { + do_not_reply = true; + if (_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + if (col && (parsed_packet->probe || parsed_packet->authoritative)) { + _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].failed_probes++; + char * new_host = _mdns_mangle_name((char *)_mdns_server->hostname); + if (new_host) { + free((char *)_mdns_server->hostname); + _mdns_server->hostname = new_host; + } + _mdns_restart_all_pcbs(); + } + } else { + _mdns_init_pcb_probe(packet->tcpip_if, packet->ip_protocol, NULL, 0, true); + } + } else if (ttl > 60 && !col && !parsed_packet->authoritative && !parsed_packet->probe && !parsed_packet->questions && !_mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].probe_running) { + _mdns_remove_scheduled_answer(packet->tcpip_if, packet->ip_protocol, type, NULL); } -#endif - } else if (!answer->ptr) { - strlcpy(answer->host, name->host, MDNS_NAME_BUF_LEN); - } else if (strcmp(answer->host, name->host) != 0) { - continue;//wrong host } - if (server->search.running && answer->addr) { - _mdns_add_result(server, answer);//another IP for our host - } - IP4_ADDR(answer, data_ptr[0], data_ptr[1], data_ptr[2], data_ptr[3]); + } } - if (server->search.running && (server->search.host[0] || answer->ptr) && answer->addr) { - _mdns_add_result(server, answer); - } //end while + if (parsed_packet->authoritative) { + _mdns_search_finish_done(); + } + } + + if (!do_not_reply && _mdns_server->interfaces[packet->tcpip_if].pcbs[packet->ip_protocol].state > PCB_PROBE_3 && (parsed_packet->questions || parsed_packet->discovery)) { + _mdns_create_answer_from_parsed_packet(parsed_packet); + } + + +clear_rx_packet: + while (parsed_packet->questions) { + mdns_parsed_question_t * question = parsed_packet->questions; + parsed_packet->questions = parsed_packet->questions->next; + free(question->host); + free(question->service); + free(question->proto); + free(question->domain); + free(question); + } + free(parsed_packet); +} + +/** + * @brief Enable mDNS interface + */ +void _mdns_enable_pcb(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + if (!_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) { + if (_mdns_pcb_init(tcpip_if, ip_protocol)) { + return; + } + } + _mdns_restart_pcb(tcpip_if, ip_protocol); +} + +/** + * @brief Disable mDNS interface + */ +void _mdns_disable_pcb(tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) { + _mdns_clear_pcb_tx_queue_head(tcpip_if, ip_protocol); + _mdns_pcb_deinit(tcpip_if, ip_protocol); + tcpip_adapter_if_t other_if = _mdns_get_other_if (tcpip_if); + if (other_if != TCPIP_ADAPTER_IF_MAX && _mdns_server->interfaces[other_if].pcbs[ip_protocol].state == PCB_DUP) { + _mdns_server->interfaces[other_if].pcbs[ip_protocol].state = PCB_OFF; + _mdns_enable_pcb(other_if, ip_protocol); + } + } + _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].state = PCB_OFF; +} + +/** + * @brief Dispatch interface changes based on system events + */ +void _mdns_handle_system_event(system_event_id_t event, tcpip_adapter_if_t interface) +{ + if (!_mdns_server) { + return; + } + + tcpip_adapter_dhcp_status_t dcst; + switch(event) { + case SYSTEM_EVENT_STA_CONNECTED: + if (!tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dcst)) { + if (dcst != TCPIP_ADAPTER_DHCP_STARTED) { + _mdns_enable_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V4); + } + } + break; + case SYSTEM_EVENT_STA_GOT_IP: + _mdns_enable_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V4); + _mdns_announce_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V6, NULL, 0, true); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + _mdns_disable_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V4); + _mdns_disable_pcb(TCPIP_ADAPTER_IF_STA, MDNS_IP_PROTOCOL_V6); + break; + case SYSTEM_EVENT_AP_START: + _mdns_enable_pcb(TCPIP_ADAPTER_IF_AP, MDNS_IP_PROTOCOL_V4); + break; + case SYSTEM_EVENT_AP_STOP: + _mdns_disable_pcb(TCPIP_ADAPTER_IF_AP, MDNS_IP_PROTOCOL_V4); + _mdns_disable_pcb(TCPIP_ADAPTER_IF_AP, MDNS_IP_PROTOCOL_V6); + break; + case SYSTEM_EVENT_GOT_IP6: + _mdns_enable_pcb(interface, MDNS_IP_PROTOCOL_V6); + _mdns_announce_pcb(interface, MDNS_IP_PROTOCOL_V4, NULL, 0, true); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + if (!tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_ETH, &dcst)) { + if (dcst != TCPIP_ADAPTER_DHCP_STARTED) { + _mdns_enable_pcb(TCPIP_ADAPTER_IF_ETH, MDNS_IP_PROTOCOL_V4); + } + } + break; + case SYSTEM_EVENT_ETH_GOT_IP: + _mdns_enable_pcb(TCPIP_ADAPTER_IF_ETH, MDNS_IP_PROTOCOL_V4); + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + _mdns_disable_pcb(TCPIP_ADAPTER_IF_ETH, MDNS_IP_PROTOCOL_V4); + _mdns_disable_pcb(TCPIP_ADAPTER_IF_ETH, MDNS_IP_PROTOCOL_V6); + break; + default: + break; } } - - /* - * Public Methods + * MDNS Search * */ -esp_err_t mdns_init(tcpip_adapter_if_t tcpip_if, mdns_server_t ** mdns_server) + +/** + * @brief Free search structure (except the results) + */ +static void _mdns_search_free(mdns_search_once_t * search) { - esp_err_t err = ESP_OK; + free(search->instance); + free(search->service); + free(search->proto); + vSemaphoreDelete(search->lock); + free(search); +} - if (tcpip_if >= TCPIP_ADAPTER_IF_MAX) { - return ESP_ERR_INVALID_ARG; +/** + * @brief Allocate new search structure + */ +static mdns_search_once_t * _mdns_search_init(const char * name, const char * service, const char * proto, uint16_t type, uint32_t timeout, uint8_t max_results) +{ + mdns_search_once_t * search = (mdns_search_once_t *)malloc(sizeof(mdns_search_once_t)); + if (!search) { + return NULL; + } + memset(search, 0, sizeof(mdns_search_once_t)); + + search->lock = xSemaphoreCreateMutex(); + if (!search->lock) { + free(search); + return NULL; } - if (_mdns_server_get(tcpip_if)) { - return ESP_ERR_INVALID_STATE; + if (name) { + search->instance = strndup(name, MDNS_NAME_BUF_LEN-1); + if (!search->instance) { + _mdns_search_free(search); + return NULL; + } } - uint8_t mode; - err = esp_wifi_get_mode((wifi_mode_t*)&mode); + if (service) { + search->service = strndup(service, MDNS_NAME_BUF_LEN-1); + if (!search->service) { + _mdns_search_free(search); + return NULL; + } + } + + if (proto) { + search->proto = strndup(proto, MDNS_NAME_BUF_LEN-1); + if (!search->proto) { + _mdns_search_free(search); + return NULL; + } + } + + search->type = type; + search->timeout = timeout; + search->num_results = 0; + search->max_results = max_results; + search->result = NULL; + search->state = SEARCH_INIT; + search->sent_at = 0; + search->started_at = xTaskGetTickCount() * portTICK_PERIOD_MS; + search->next = NULL; + + xSemaphoreTake(search->lock, 0); + + return search; +} + +/** + * @brief Mark search as finished and remove it from search chain + */ +static void _mdns_search_finish(mdns_search_once_t * search) +{ + search->state = SEARCH_OFF; + queueDetach(mdns_search_once_t, _mdns_server->search_once, search); + xSemaphoreGive(search->lock); +} + +/** + * @brief Add new search to the search chain + */ +static void _mdns_search_add(mdns_search_once_t * search) +{ + search->next = _mdns_server->search_once; + _mdns_server->search_once = search; +} + +/** + * @brief Called from parser to finish any searches that have reached maximum results + */ +static void _mdns_search_finish_done() +{ + mdns_search_once_t * search = _mdns_server->search_once; + mdns_search_once_t * s = NULL; + while (search) { + s = search; + search = search->next; + if (s->max_results && s->num_results >= s->max_results) { + _mdns_search_finish(s); + } + } +} + +/** + * @brief Create linked IP (copy) from parsed one + */ +static mdns_ip_addr_t * _mdns_result_addr_create_ip(ip_addr_t * ip) +{ + mdns_ip_addr_t * a = (mdns_ip_addr_t *)malloc(sizeof(mdns_ip_addr_t)); + if (!a) { + return NULL; + } + memset(a, 0 , sizeof(mdns_ip_addr_t)); + a->addr.type = ip->type; + if (ip->type == IPADDR_TYPE_V6) { + memcpy(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16); + } else { + a->addr.u_addr.ip4.addr = ip->u_addr.ip4.addr; + } + return a; +} + +/** + * @brief Chain new IP to search result + */ +static void _mdns_result_add_ip(mdns_result_t * r, ip_addr_t * ip) +{ + mdns_ip_addr_t * a = r->addr; + while (a) { + if (a->addr.type == ip->type) { + if (a->addr.type == IPADDR_TYPE_V4 && a->addr.u_addr.ip4.addr == ip->u_addr.ip4.addr) { + return; + } + if (a->addr.type == IPADDR_TYPE_V6 && !memcmp(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16)) { + return; + } + } + a = a->next; + } + a = _mdns_result_addr_create_ip(ip); + if (!a) { + return; + } + a->next = r->addr; + r->addr = a; +} + +/** + * @brief Called from parser to add A/AAAA data to search result + */ +static void _mdns_search_result_add_ip(mdns_search_once_t * search, const char * hostname, ip_addr_t * ip, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = NULL; + mdns_ip_addr_t * a = NULL; + + if ((search->type == MDNS_TYPE_A && ip->type == IPADDR_TYPE_V4) + || (search->type == MDNS_TYPE_AAAA && ip->type == IPADDR_TYPE_V6) + || search->type == MDNS_TYPE_ANY) { + r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol) { + _mdns_result_add_ip(r, ip); + return; + } + r = r->next; + } + if (!search->max_results || search->num_results < search->max_results) { + r = (mdns_result_t *)malloc(sizeof(mdns_result_t)); + if (!r) { + return; + } + + memset(r, 0 , sizeof(mdns_result_t)); + + a = _mdns_result_addr_create_ip(ip); + if (!a) { + free(r); + return; + } + a->next = r->addr; + r->addr = a; + r->tcpip_if = tcpip_if; + r->ip_protocol = ip_protocol; + r->next = search->result; + search->result = r; + search->num_results++; + } + } else if (search->type == MDNS_TYPE_PTR) { + r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && r->hostname && !strcasecmp(hostname, r->hostname)) { + _mdns_result_add_ip(r, ip); + break; + } + r = r->next; + } + } +} + +/** + * @brief Called from parser to add PTR data to search result + */ +static mdns_result_t * _mdns_search_result_add_ptr(mdns_search_once_t * search, const char * instance, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && r->instance_name && !strcasecmp(instance, r->instance_name)) { + return r; + } + r = r->next; + } + if (!search->max_results || search->num_results < search->max_results) { + r = (mdns_result_t *)malloc(sizeof(mdns_result_t)); + if (!r) { + return NULL; + } + + memset(r, 0 , sizeof(mdns_result_t)); + r->instance_name = strdup(instance); + if (!r->instance_name) { + free(r); + return NULL; + } + + r->tcpip_if = tcpip_if; + r->ip_protocol = ip_protocol; + r->next = search->result; + search->result = r; + search->num_results++; + return r; + } + return NULL; +} + +/** + * @brief Called from parser to add SRV data to search result + */ +static void _mdns_search_result_add_srv(mdns_search_once_t * search, const char * hostname, uint16_t port, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && r->hostname && !strcasecmp(hostname, r->hostname)) { + return; + } + r = r->next; + } + if (!search->max_results || search->num_results < search->max_results) { + r = (mdns_result_t *)malloc(sizeof(mdns_result_t)); + if (!r) { + return; + } + + memset(r, 0 , sizeof(mdns_result_t)); + r->hostname = strdup(hostname); + if (!r->hostname) { + free(r); + return; + } + r->port = port; + r->tcpip_if = tcpip_if; + r->ip_protocol = ip_protocol; + r->next = search->result; + search->result = r; + search->num_results++; + } +} + +/** + * @brief Called from parser to add TXT data to search result + */ +static void _mdns_search_result_add_txt(mdns_search_once_t * search, mdns_txt_item_t * txt, size_t txt_count, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + int i; + mdns_result_t * r = search->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol) { + if (r->txt) { + return; + } + r->txt = txt; + r->txt_count = txt_count; + return; + } + r = r->next; + } + if (!search->max_results || search->num_results < search->max_results) { + r = (mdns_result_t *)malloc(sizeof(mdns_result_t)); + if (!r) { + for (i=0; itxt = txt; + r->txt_count = txt_count; + r->tcpip_if = tcpip_if; + r->ip_protocol = ip_protocol; + r->next = search->result; + search->result = r; + search->num_results++; + } +} + +/** + * @brief Called from packet parser to find matching running search + */ +static mdns_search_once_t * _mdns_search_find_from(mdns_search_once_t * s, mdns_name_t * name, uint16_t type, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = NULL; + while (s) { + if (s->state == SEARCH_OFF) { + s = s->next; + continue; + } + + if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) { + if ((s->type == MDNS_TYPE_ANY && s->service != NULL) + || (s->type != MDNS_TYPE_ANY && s->type != type && s->type != MDNS_TYPE_PTR)) + { + s = s->next; + continue; + } + if (s->type != MDNS_TYPE_PTR) { + if (!strcasecmp(name->host, s->instance)) { + return s; + } + s = s->next; + continue; + } + r = s->result; + while (r) { + if (r->tcpip_if == tcpip_if && r->ip_protocol == ip_protocol && r->hostname && !strcasecmp(name->host, r->hostname)) { + return s; + } + r = r->next; + } + s = s->next; + continue; + } + + if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) { + if ((s->type == MDNS_TYPE_ANY && s->service == NULL) + || (s->type != MDNS_TYPE_ANY && s->type != type && s->type != MDNS_TYPE_PTR)) + { + s = s->next; + continue; + } + if (strcasecmp(name->service, s->service) + || strcasecmp(name->proto, s->proto)) + { + s = s->next; + continue; + } + if (s->type != MDNS_TYPE_PTR) { + if (!strcasecmp(name->host, s->instance)) { + return s; + } + s = s->next; + continue; + } + return s; + } + + if (type == MDNS_TYPE_PTR && type == s->type && !strcasecmp(name->service, s->service) && !strcasecmp(name->proto, s->proto)) { + return s; + } + + s = s->next; + } + + return NULL; +} + +/** + * @brief Create search packet for partidular interface + */ +static mdns_tx_packet_t * _mdns_create_search_packet(mdns_search_once_t * search, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_result_t * r = NULL; + mdns_tx_packet_t * packet = _mdns_alloc_packet_default(tcpip_if, ip_protocol); + if (!packet) { + return NULL; + } + + mdns_out_question_t * q = (mdns_out_question_t *)malloc(sizeof(mdns_out_question_t)); + if (!q) { + _mdns_free_tx_packet(packet); + return NULL; + } + q->next = NULL; + q->unicast = search->type != MDNS_TYPE_PTR; + q->type = search->type; + q->host = search->instance; + q->service = search->service; + q->proto = search->proto; + q->domain = MDNS_DEFAULT_DOMAIN; + queueToEnd(mdns_out_question_t, packet->questions, q); + + if (search->type == MDNS_TYPE_PTR) { + r = search->result; + while (r) { + //full record on the same interface is available + if (r->tcpip_if != tcpip_if || r->ip_protocol != ip_protocol || r->instance_name == NULL || r->hostname == NULL || r->addr == NULL) { + r = r->next; + continue; + } + mdns_out_answer_t * a = (mdns_out_answer_t *)malloc(sizeof(mdns_out_answer_t)); + if (!a) { + _mdns_free_tx_packet(packet); + return NULL; + } + a->type = MDNS_TYPE_PTR; + a->service = NULL; + a->custom_instance = r->instance_name; + a->custom_service = search->service; + a->custom_proto = search->proto; + a->bye = false; + a->flush = false; + a->next = NULL; + queueToEnd(mdns_out_answer_t, packet->answers, a); + r = r->next; + } + } + + return packet; +} + +/** + * @brief Send search packet to particular interface + */ +static void _mdns_search_send_pcb(mdns_search_once_t * search, tcpip_adapter_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + mdns_tx_packet_t * packet = NULL; + if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb && _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].state == PCB_RUNNING) { + packet = _mdns_create_search_packet(search, tcpip_if, ip_protocol); + if (!packet) { + return; + } + _mdns_dispatch_tx_packet(packet); + _mdns_free_tx_packet(packet); + } +} + +/** + * @brief Send search packet to all available interfaces + */ +static void _mdns_search_send(mdns_search_once_t * search) +{ + uint8_t i, j; + for (i=0; iinterfaces[p->tcpip_if].pcbs[p->ip_protocol]; + uint32_t send_after = 1000; + + if (pcb->state == PCB_OFF) { + _mdns_free_tx_packet(p); + return; + } + _mdns_dispatch_tx_packet(p); + + switch(pcb->state) { + case PCB_PROBE_1: + q = p->questions; + while (q) { + q->unicast = false; + q = q->next; + } + //fallthrough + case PCB_PROBE_2: + _mdns_schedule_tx_packet(p, 250); + pcb->state = (mdns_pcb_state_t)((uint8_t)(pcb->state) + 1); + break; + case PCB_PROBE_3: + a = _mdns_create_announce_from_probe(p); + if (!a) { + _mdns_schedule_tx_packet(p, 250); + break; + } + pcb->probe_running = false; + pcb->probe_ip = false; + pcb->probe_services_len = 0; + pcb->failed_probes = 0; + free(pcb->probe_services); + pcb->probe_services = NULL; + _mdns_free_tx_packet(p); + p = a; + send_after = 250; + //fallthrough + case PCB_ANNOUNCE_1: + //fallthrough + case PCB_ANNOUNCE_2: + _mdns_schedule_tx_packet(p, send_after); + pcb->state = (mdns_pcb_state_t)((uint8_t)(pcb->state) + 1); + break; + case PCB_ANNOUNCE_3: + pcb->state = PCB_RUNNING; + _mdns_free_tx_packet(p); + break; + default: + _mdns_free_tx_packet(p); + break; + } +} + +/** + * @brief Free action data + */ +static void _mdns_free_action(mdns_action_t * action) +{ + switch(action->type) { + case ACTION_HOSTNAME_SET: + free(action->data.hostname); + break; + case ACTION_INSTANCE_SET: + free(action->data.instance); + break; + case ACTION_SERVICE_ADD: + _mdns_free_service(action->data.srv_add.service->service); + free(action->data.srv_add.service); + break; + case ACTION_SERVICE_INSTANCE_SET: + free(action->data.srv_instance.instance); + break; + case ACTION_SERVICE_TXT_REPLACE: + free(action->data.srv_txt_replace.txt); + break; + case ACTION_SERVICE_TXT_SET: + free(action->data.srv_txt_set.key); + free(action->data.srv_txt_set.value); + break; + case ACTION_SERVICE_TXT_DEL: + free(action->data.srv_txt_del.key); + break; + case ACTION_SEARCH_ADD: + //fallthrough + case ACTION_SEARCH_SEND: + //fallthrough + case ACTION_SEARCH_END: + _mdns_search_free(action->data.search_add.search); + break; + case ACTION_TX_HANDLE: + _mdns_free_tx_packet(action->data.tx_handle.packet); + break; + case ACTION_RX_HANDLE: + pbuf_free(action->data.rx_handle.packet->pb); + free(action->data.rx_handle.packet); + break; + default: + break; + } + free(action); +} + +/** + * @brief Called from service thread to execute given action + */ +static void _mdns_execute_action(mdns_action_t * action) +{ + mdns_srv_item_t * a = NULL; + mdns_service_t * service; + char * key; + char * value; + mdns_txt_linked_item_t * txt, * t; + + switch(action->type) { + case ACTION_SYSTEM_EVENT: + _mdns_handle_system_event(action->data.sys_event.event_id, action->data.sys_event.interface); + break; + case ACTION_HOSTNAME_SET: + _mdns_send_final_bye(true); + free((char*)_mdns_server->hostname); + _mdns_server->hostname = action->data.hostname; + _mdns_restart_all_pcbs(); + + break; + case ACTION_INSTANCE_SET: + _mdns_send_bye_all_pcbs_no_instance(); + free((char*)_mdns_server->instance); + _mdns_server->instance = action->data.instance; + _mdns_restart_all_pcbs_no_instance(); + + break; + case ACTION_SERVICE_ADD: + action->data.srv_add.service->next = _mdns_server->services; + _mdns_server->services = action->data.srv_add.service; + _mdns_probe_all_pcbs(&action->data.srv_add.service, 1, false, false); + + break; + case ACTION_SERVICE_INSTANCE_SET: + if (action->data.srv_port.service->service->instance) { + _mdns_send_bye(&action->data.srv_instance.service, 1, false); + free((char*)action->data.srv_instance.service->service->instance); + } + action->data.srv_instance.service->service->instance = action->data.srv_instance.instance; + _mdns_probe_all_pcbs(&action->data.srv_instance.service, 1, false, false); + + break; + case ACTION_SERVICE_PORT_SET: + action->data.srv_port.service->service->port = action->data.srv_port.port; + _mdns_announce_all_pcbs(&action->data.srv_port.service, 1, true); + + break; + case ACTION_SERVICE_TXT_REPLACE: + service = action->data.srv_txt_replace.service->service; + txt = service->txt; + service->txt = NULL; + while (txt) { + t = txt; + txt = txt->next; + free((char *)t->value); + free((char *)t->key); + free(t); + } + service->txt = _mdns_allocate_txt(action->data.srv_txt_replace.num_items, action->data.srv_txt_replace.txt); + _mdns_announce_all_pcbs(&action->data.srv_txt_replace.service, 1, false); + + break; + case ACTION_SERVICE_TXT_SET: + service = action->data.srv_txt_set.service->service; + key = action->data.srv_txt_set.key; + value = action->data.srv_txt_set.value; + txt = service->txt; + while (txt) { + if (strcmp(txt->key, key) == 0) { + free((char *)txt->value); + free(key); + txt->value = value; + break; + } + txt = txt->next; + } + if (!txt) { + txt = (mdns_txt_linked_item_t *)malloc(sizeof(mdns_txt_linked_item_t)); + if (!txt) { + _mdns_free_action(action); + return; + } + txt->key = key; + txt->value = value; + txt->next = service->txt; + service->txt = txt; + } + + _mdns_announce_all_pcbs(&action->data.srv_txt_set.service, 1, false); + + break; + case ACTION_SERVICE_TXT_DEL: + service = action->data.srv_txt_del.service->service; + key = action->data.srv_txt_del.key; + txt = service->txt; + if (!txt) { + break; + } + if (strcmp(txt->key, key) == 0) { + service->txt = txt->next; + free((char *)txt->key); + free((char *)txt->value); + free(txt); + } else { + while (txt->next) { + if (strcmp(txt->next->key, key) == 0) { + t = txt->next; + txt->next = t->next; + free((char *)t->key); + free((char *)t->value); + free(t); + break; + } else { + txt = txt->next; + } + } + } + free(key); + + _mdns_announce_all_pcbs(&action->data.srv_txt_set.service, 1, false); + + break; + case ACTION_SERVICE_DEL: + a = _mdns_server->services; + if (_mdns_server->services == action->data.srv_del.service) { + _mdns_server->services = a->next; + _mdns_send_bye(&a, 1, false); + _mdns_free_service(a->service); + free(a); + } else { + while (a->next && a->next != action->data.srv_del.service) { + a = a->next; + } + if (a->next == action->data.srv_del.service) { + mdns_srv_item_t * b = a; + a->next = a->next->next; + _mdns_send_bye(&b, 1, false); + _mdns_free_service(b->service); + free(b); + } + } + + break; + case ACTION_SERVICES_CLEAR: + _mdns_send_final_bye(false); + a = _mdns_server->services; + _mdns_server->services = NULL; + while (a) { + mdns_srv_item_t * s = a; + a = a->next; + _mdns_free_service(s->service); + free(s); + } + + break; + case ACTION_SEARCH_ADD: + _mdns_search_add(action->data.search_add.search); + break; + case ACTION_SEARCH_SEND: + _mdns_search_send(action->data.search_add.search); + break; + case ACTION_SEARCH_END: + _mdns_search_finish(action->data.search_add.search); + break; + case ACTION_TX_HANDLE: + _mdns_tx_handle_packet(action->data.tx_handle.packet); + break; + case ACTION_RX_HANDLE: + mdns_parse_packet(action->data.rx_handle.packet); + pbuf_free(action->data.rx_handle.packet->pb); + free(action->data.rx_handle.packet); + break; + default: + break; + } + free(action); +} + +/** + * @brief Queue search action + */ +static esp_err_t _mdns_send_search_action(mdns_action_type_t type, mdns_search_once_t * search) +{ + mdns_action_t * action = NULL; + + action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + + action->type = type; + action->data.search_add.search = search; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +/** + * @brief Called from timer task to run mDNS responder + */ +static void _mdns_scheduler_run() +{ + mdns_tx_packet_t * p = _mdns_server->tx_queue_head; + mdns_action_t * action = NULL; + + if (!p) { + return; + } + MDNS_SERVICE_LOCK(); + if ((int32_t)(p->send_at - (xTaskGetTickCount() * portTICK_PERIOD_MS)) < 0) { + action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (action) { + _mdns_server->tx_queue_head = p->next; + action->type = ACTION_TX_HANDLE; + action->data.tx_handle.packet = p; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + _mdns_server->tx_queue_head = p; + } + } + } + MDNS_SERVICE_UNLOCK(); +} + +/** + * @brief Called from timer task to run active searches + */ +static void _mdns_search_run() +{ + mdns_search_once_t * s = _mdns_server->search_once; + uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS; + if (!s) { + return; + } + MDNS_SERVICE_LOCK(); + while (s) { + if (s->state != SEARCH_OFF) { + if (now > (s->started_at + s->timeout)) { + s->state = SEARCH_OFF; + if (_mdns_send_search_action(ACTION_SEARCH_END, s) != ESP_OK) { + s->state = SEARCH_RUNNING; + } + } else if (s->state == SEARCH_INIT || (now - s->sent_at) > 1000) { + s->state = SEARCH_RUNNING; + s->sent_at = now; + if (_mdns_send_search_action(ACTION_SEARCH_SEND, s) != ESP_OK) { + s->sent_at -= 1000; + } + } + } + s = s->next; + } + MDNS_SERVICE_UNLOCK(); +} + +/** + * @brief the main MDNS service task. Packets are received and parsed here + */ +static void _mdns_service_task(void *pvParameters) +{ + mdns_action_t * a = NULL; + for (;;) { + if (_mdns_server && _mdns_server->action_queue) { + if (xQueueReceive(_mdns_server->action_queue, &a, portMAX_DELAY) == pdTRUE) { + if (a->type == ACTION_TASK_STOP) { + break; + } + MDNS_SERVICE_LOCK(); + _mdns_execute_action(a); + MDNS_SERVICE_UNLOCK(); + } + } else { + vTaskDelay(500 * portTICK_PERIOD_MS); + } + } + _mdns_service_task_handle = NULL; + vTaskDelete(NULL); +} + +static void _mdns_timer_cb(void * arg) +{ + _mdns_scheduler_run(); + _mdns_search_run(); +} + +static esp_err_t _mdns_start_timer(){ + esp_timer_create_args_t timer_conf = { + .callback = _mdns_timer_cb, + .arg = NULL, + .dispatch_method = ESP_TIMER_TASK, + .name = "mdns_timer" + }; + esp_err_t err = esp_timer_create(&timer_conf, &(_mdns_server->timer_handle)); if (err) { return err; } + return esp_timer_start_periodic(_mdns_server->timer_handle, MDNS_TIMER_PERIOD_US); +} - if ((tcpip_if == TCPIP_ADAPTER_IF_STA && !(mode & WIFI_MODE_STA)) - || (tcpip_if == TCPIP_ADAPTER_IF_AP && !(mode & WIFI_MODE_AP))) { - return ESP_ERR_INVALID_ARG; +static esp_err_t _mdns_stop_timer(){ + esp_err_t err = ESP_OK; + if (_mdns_server->timer_handle) { + err = esp_timer_stop(_mdns_server->timer_handle); + if (err) { + return err; + } + err = esp_timer_delete(_mdns_server->timer_handle); } + return err; +} - mdns_server_t * server = (mdns_server_t *)malloc(sizeof(mdns_server_t)); - if (!server) { - return ESP_ERR_NO_MEM; +/** + * @brief Start the service thread if not running + * + * @return + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t _mdns_service_task_start() +{ +#ifndef MDNS_TEST_MODE + if (!_mdns_service_semaphore) { + _mdns_service_semaphore = xSemaphoreCreateMutex(); + if (!_mdns_service_semaphore) { + return ESP_FAIL; + } } - - server->tcpip_if = tcpip_if; - server->hostname = NULL; - server->instance = NULL; - server->services = NULL; - server->search.host[0] = 0; - server->search.service[0] = 0; - server->search.proto[0] = 0; - server->search.running = false; - server->search.results = NULL; - server->pcb = NULL; - - server->lock = xSemaphoreCreateMutex(); - if (!server->lock) { - free(server); - return ESP_ERR_NO_MEM; - } - - server->search.lock = xSemaphoreCreateMutex(); - if (!server->search.lock) { - vSemaphoreDelete(server->lock); - free(server); - return ESP_ERR_NO_MEM; - } - - server->queue = xQueueCreate(MDNS_PACKET_QUEUE_LEN, sizeof(struct pbuf *)); - if (!server->queue) { - vSemaphoreDelete(server->lock); - vSemaphoreDelete(server->search.lock); - free(server); - return ESP_ERR_NO_MEM; - } - - if (_mdns_server_add(server)) { - //service start failed! - vSemaphoreDelete(server->lock); - vSemaphoreDelete(server->search.lock); - vQueueDelete(server->queue); - free(server); + MDNS_SERVICE_LOCK(); + if (_mdns_start_timer()) { + MDNS_SERVICE_UNLOCK(); return ESP_FAIL; } - - const char * hostname = NULL; - tcpip_adapter_get_hostname(server->tcpip_if, &hostname); - mdns_set_hostname(server, hostname); - - *mdns_server = server; + if (!_mdns_service_task_handle) { + xTaskCreatePinnedToCore(_mdns_service_task, "mdns", MDNS_SERVICE_STACK_DEPTH, NULL, 1, (TaskHandle_t * const)(&_mdns_service_task_handle), 0); + if (!_mdns_service_task_handle) { + MDNS_SERVICE_UNLOCK(); + return ESP_FAIL; + } + } + MDNS_SERVICE_UNLOCK(); +#endif return ESP_OK; } -void mdns_free(mdns_server_t * server) +/** + * @brief Stop the service thread + * + * @return + * - ESP_OK + */ +static esp_err_t _mdns_service_task_stop() { - if (!server) { - return; - } - _mdns_server_remove(server); - mdns_service_remove_all(server); - MDNS_MUTEX_LOCK(); - free((char*)server->hostname); - free((char*)server->instance); - if (server->queue) { - struct pbuf * c; - while (xQueueReceive(server->queue, &c, 0) == pdTRUE) { - pbuf_free(c); +#ifndef MDNS_TEST_MODE + MDNS_SERVICE_LOCK(); + _mdns_stop_timer(); + if (_mdns_service_task_handle) { + mdns_action_t action; + mdns_action_t * a = &action; + action.type = ACTION_TASK_STOP; + if (xQueueSend(_mdns_server->action_queue, &a, (portTickType)0) != pdPASS) { + vTaskDelete(_mdns_service_task_handle); + _mdns_service_task_handle = NULL; + } + while (_mdns_service_task_handle) { + vTaskDelay(10 / portTICK_PERIOD_MS); } - vQueueDelete(server->queue); } - if (server->search.running) { - mdns_query_end(server); - } - mdns_result_free(server); - vSemaphoreDelete(server->search.lock); - MDNS_MUTEX_UNLOCK(); - vSemaphoreDelete(server->lock); - free(server); + MDNS_SERVICE_UNLOCK(); +#endif + return ESP_OK; } -esp_err_t mdns_set_hostname(mdns_server_t * server, const char * hostname) +/* + * Public Methods + * */ + +esp_err_t mdns_handle_system_event(void *ctx, system_event_t *event) { - if (!server) { + if (!_mdns_server) { + return ESP_OK; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_OK; + } + action->type = ACTION_SYSTEM_EVENT; + action->data.sys_event.event_id = event->event_id; + action->data.sys_event.interface = (event->event_id == SYSTEM_EVENT_GOT_IP6)?event->event_info.got_ip6.if_index:0; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + } + return ESP_OK; +} + +esp_err_t mdns_init() +{ + esp_err_t err = ESP_OK; + + if (_mdns_server) { + return err; + } + + _mdns_server = (mdns_server_t *)malloc(sizeof(mdns_server_t)); + if (!_mdns_server) { + return ESP_ERR_NO_MEM; + } + memset((uint8_t*)_mdns_server, 0, sizeof(mdns_server_t)); + + _mdns_server->lock = xSemaphoreCreateMutex(); + if (!_mdns_server->lock) { + err = ESP_ERR_NO_MEM; + goto free_server; + } + + _mdns_server->action_queue = xQueueCreate(MDNS_ACTION_QUEUE_LEN, sizeof(mdns_action_t *)); + if (!_mdns_server->action_queue) { + err = ESP_ERR_NO_MEM; + goto free_lock; + } + + if (_mdns_service_task_start()) { + //service start failed! + err = ESP_FAIL; + goto free_all; + } + + uint8_t i; + ip6_addr_t tmp_addr6; + tcpip_adapter_ip_info_t if_ip_info; + + for (i=0; iaction_queue); +free_lock: + vSemaphoreDelete(_mdns_server->lock); +free_server: + free(_mdns_server); + _mdns_server = NULL; + return err; +} + +void mdns_free() +{ + uint8_t i, j; + if (!_mdns_server) { + return; + } + _mdns_service_task_stop(); + mdns_service_remove_all(_mdns_server); + for (i=0; ihostname); + free((char*)_mdns_server->instance); + if (_mdns_server->action_queue) { + mdns_action_t * c; + while (xQueueReceive(_mdns_server->action_queue, &c, 0) == pdTRUE) { + _mdns_free_action(c); + } + vQueueDelete(_mdns_server->action_queue); + } + _mdns_clear_tx_queue_head(); + while (_mdns_server->search_once) { + mdns_search_once_t * h = _mdns_server->search_once; + _mdns_server->search_once = h->next; + free(h->instance); + free(h->service); + free(h->proto); + vSemaphoreDelete(h->lock); + if (h->result) { + mdns_query_results_free(h->result); + } + free(h); + } + vSemaphoreDelete(_mdns_server->lock); + free(_mdns_server); + _mdns_server = NULL; +} + +esp_err_t mdns_hostname_set(const char * hostname) +{ + if (!_mdns_server) { return ESP_ERR_INVALID_ARG; } if (strlen(hostname) > (MDNS_NAME_BUF_LEN - 1)) { return ESP_ERR_INVALID_ARG; } - MDNS_MUTEX_LOCK(); - free((char*)server->hostname); - server->hostname = strndup(hostname, MDNS_NAME_BUF_LEN - 1); - if (!server->hostname) { - MDNS_MUTEX_UNLOCK(); + char * new_hostname = strndup(hostname, MDNS_NAME_BUF_LEN - 1); + if (!new_hostname) { + return ESP_ERR_NO_MEM; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + free(new_hostname); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_HOSTNAME_SET; + action->data.hostname = new_hostname; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(new_hostname); + free(action); return ESP_ERR_NO_MEM; } - MDNS_MUTEX_UNLOCK(); return ERR_OK; } -esp_err_t mdns_set_instance(mdns_server_t * server, const char * instance) +esp_err_t mdns_instance_name_set(const char * instance) { - if (!server) { + if (!_mdns_server) { return ESP_ERR_INVALID_ARG; } if (strlen(instance) > (MDNS_NAME_BUF_LEN - 1)) { return ESP_ERR_INVALID_ARG; } - MDNS_MUTEX_LOCK(); - free((char*)server->instance); - server->instance = strndup(instance, MDNS_NAME_BUF_LEN - 1); - if (!server->instance) { - MDNS_MUTEX_UNLOCK(); + char * new_instance = strndup(instance, MDNS_NAME_BUF_LEN - 1); + if (!new_instance) { + return ESP_ERR_NO_MEM; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + free(new_instance); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_INSTANCE_SET; + action->data.instance = new_instance; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(new_instance); + free(action); return ESP_ERR_NO_MEM; } - MDNS_MUTEX_UNLOCK(); return ERR_OK; } @@ -1737,167 +4276,257 @@ esp_err_t mdns_set_instance(mdns_server_t * server, const char * instance) * MDNS SERVICES * */ -esp_err_t mdns_service_add(mdns_server_t * server, const char * service, const char * proto, uint16_t port) +esp_err_t mdns_service_add(const char * instance, const char * service, const char * proto, uint16_t port, mdns_txt_item_t txt[], size_t num_items) { - if (!server || !service || !proto || !port) { - //bad argument + if (!_mdns_server || !service || !proto || !port) { return ESP_ERR_INVALID_ARG; } - mdns_srv_item_t * item = _mdns_get_service_item(server, service, proto); + mdns_srv_item_t * item = _mdns_get_service_item(service, proto); if (item) { - //we already have that service - return mdns_service_port_set(server, service, proto, port); + return ESP_ERR_INVALID_ARG; } - mdns_service_t * s = _mdns_create_service(service, proto, port); + mdns_service_t * s = _mdns_create_service(service, proto, port, instance, num_items, txt); if (!s) { return ESP_ERR_NO_MEM; } item = (mdns_srv_item_t *)malloc(sizeof(mdns_srv_item_t)); if (!item) { - free(s); + _mdns_free_service(s); return ESP_ERR_NO_MEM; } item->service = s; - item->next = server->services; - server->services = item; + item->next = NULL; + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + _mdns_free_service(s); + free(item); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_ADD; + action->data.srv_add.service = item; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + _mdns_free_service(s); + free(item); + free(action); + return ESP_ERR_NO_MEM; + } + + uint8_t i = 0; + while (_mdns_get_service_item(service, proto) == NULL && i++ < 200) { + vTaskDelay(1); + } + if (i >= 200) { + return ESP_FAIL; + } + return ESP_OK; } -esp_err_t mdns_service_port_set(mdns_server_t * server, const char * service, const char * proto, uint16_t port) +esp_err_t mdns_service_port_set(const char * service, const char * proto, uint16_t port) { - if (!server || !server->services || !service || !proto || !port) { + if (!_mdns_server || !_mdns_server->services || !service || !proto || !port) { return ESP_ERR_INVALID_ARG; } - mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto); + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); if (!s) { return ESP_ERR_NOT_FOUND; } - MDNS_MUTEX_LOCK(); - s->service->port = port; - MDNS_MUTEX_UNLOCK(); + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_PORT_SET; + action->data.srv_port.service = s; + action->data.srv_port.port = port; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; + } return ESP_OK; } -esp_err_t mdns_service_txt_set(mdns_server_t * server, const char * service, const char * proto, uint8_t num_items, const char ** txt) +esp_err_t mdns_service_txt_set(const char * service, const char * proto, mdns_txt_item_t txt[], uint8_t num_items) { - if (!server || !server->services || !service || !proto) { + if (!_mdns_server || !_mdns_server->services || !service || !proto || (num_items && txt == NULL)) { return ESP_ERR_INVALID_ARG; } - mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto); + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); if (!s) { return ESP_ERR_NOT_FOUND; } - MDNS_MUTEX_LOCK(); - if (s->service->txt_num_items) { - uint8_t i; - for (i = 0; i < s->service->txt_num_items; i++) { - free((char *)s->service->txt[i]); + + mdns_txt_item_t * txt_copy = NULL; + if (num_items){ + txt_copy = (mdns_txt_item_t *)malloc(num_items * sizeof(mdns_txt_item_t)); + if (!txt_copy) { + return ESP_ERR_NO_MEM; } + memcpy(txt_copy, txt, num_items * sizeof(mdns_txt_item_t)); } - free(s->service->txt); - if (num_items) { - s->service->txt = (const char **)malloc(sizeof(char *) * num_items); - if (!s->service->txt) { - s->service->txt_num_items = 0; - goto fail; - } - uint8_t i; - s->service->txt_num_items = num_items; - for (i = 0; i < num_items; i++) { - s->service->txt[i] = strdup(txt[i]); - if (!s->service->txt[i]) { - s->service->txt_num_items = i; - goto fail; - } - } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + free(txt_copy); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_TXT_REPLACE; + action->data.srv_txt_replace.service = s; + action->data.srv_txt_replace.num_items = num_items; + action->data.srv_txt_replace.txt = txt_copy; + + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(txt_copy); + free(action); + return ESP_ERR_NO_MEM; } - MDNS_MUTEX_UNLOCK(); return ESP_OK; -fail: - MDNS_MUTEX_UNLOCK(); - return ESP_ERR_NO_MEM; } -esp_err_t mdns_service_instance_set(mdns_server_t * server, const char * service, const char * proto, const char * instance) + +esp_err_t mdns_service_txt_item_set(const char * service, const char * proto, const char * key, const char * value) { - if (!server || !server->services || !service || !proto) { + if (!_mdns_server || !_mdns_server->services || !service || !proto) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + + action->type = ACTION_SERVICE_TXT_SET; + action->data.srv_txt_set.service = s; + action->data.srv_txt_set.key = strdup(key); + if (!action->data.srv_txt_set.key) { + free(action); + return ESP_ERR_NO_MEM; + } + action->data.srv_txt_set.value = strdup(value); + if (!action->data.srv_txt_set.value) { + free(action->data.srv_txt_set.key); + free(action); + return ESP_ERR_NO_MEM; + } + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action->data.srv_txt_set.key); + free(action->data.srv_txt_set.value); + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t mdns_service_txt_item_remove(const char * service, const char * proto, const char * key) +{ + if (!_mdns_server || !_mdns_server->services || !service || !proto) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + + action->type = ACTION_SERVICE_TXT_DEL; + action->data.srv_txt_del.service = s; + action->data.srv_txt_del.key = strdup(key); + if (!action->data.srv_txt_del.key) { + free(action); + return ESP_ERR_NO_MEM; + } + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action->data.srv_txt_del.key); + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +esp_err_t mdns_service_instance_name_set(const char * service, const char * proto, const char * instance) +{ + if (!_mdns_server || !_mdns_server->services || !service || !proto) { return ESP_ERR_INVALID_ARG; } if (strlen(instance) > (MDNS_NAME_BUF_LEN - 1)) { return ESP_ERR_INVALID_ARG; } - mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto); + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); if (!s) { return ESP_ERR_NOT_FOUND; } - MDNS_MUTEX_LOCK(); - free((char*)s->service->instance); - s->service->instance = strdup(instance); - if (!s->service->instance) { - MDNS_MUTEX_UNLOCK(); + char * new_instance = strndup(instance, MDNS_NAME_BUF_LEN - 1); + if (!new_instance) { + return ESP_ERR_NO_MEM; + } + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + free(new_instance); + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICE_INSTANCE_SET; + action->data.srv_instance.service = s; + action->data.srv_instance.instance = new_instance; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(new_instance); + free(action); return ESP_ERR_NO_MEM; } - MDNS_MUTEX_UNLOCK(); return ESP_OK; } -esp_err_t mdns_service_remove(mdns_server_t * server, const char * service, const char * proto) +esp_err_t mdns_service_remove(const char * service, const char * proto) { - if (!server || !server->services || !service || !proto) { + if (!_mdns_server || !_mdns_server->services || !service || !proto) { return ESP_ERR_INVALID_ARG; } - mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto); + mdns_srv_item_t * s = _mdns_get_service_item(service, proto); if (!s) { return ESP_ERR_NOT_FOUND; } - //first item - if (server->services == s) { - MDNS_MUTEX_LOCK(); - server->services = server->services->next; - MDNS_MUTEX_UNLOCK(); - _mdns_free_service(s->service); - free(s); - return ESP_OK; + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; } - //not first item - mdns_srv_item_t * a = server->services; - while (a->next && a->next != s) { - a = a->next; + action->type = ACTION_SERVICE_DEL; + action->data.srv_del.service = s; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; } - //next item of the current item is our item - if (a->next == s) { - MDNS_MUTEX_LOCK(); - a->next = s->next; - MDNS_MUTEX_UNLOCK(); - _mdns_free_service(s->service); - free(s); - return ESP_OK; - } - //how did we end here? - return ESP_FAIL; + return ESP_OK; } -esp_err_t mdns_service_remove_all(mdns_server_t * server) +esp_err_t mdns_service_remove_all() { - if (!server) { + if (!_mdns_server) { return ESP_ERR_INVALID_ARG; } - if (!server->services) { + if (!_mdns_server->services) { return ESP_OK; } - MDNS_MUTEX_LOCK(); - mdns_srv_item_t * a = server->services; - server->services = NULL; - while (a) { - mdns_srv_item_t * s = a; - a = a->next; - _mdns_free_service(s->service); - free(s); + + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + return ESP_ERR_NO_MEM; + } + action->type = ACTION_SERVICES_CLEAR; + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action); + return ESP_ERR_NO_MEM; } - MDNS_MUTEX_UNLOCK(); return ESP_OK; } @@ -1905,131 +4534,384 @@ esp_err_t mdns_service_remove_all(mdns_server_t * server) * MDNS QUERY * */ -size_t mdns_query(mdns_server_t * server, const char * service, const char * proto, uint32_t timeout) +void mdns_query_results_free(mdns_result_t * results) { - if (!server || !service) { - return 0; - } - MDNS_SEARCH_LOCK(); - uint16_t qtype = MDNS_TYPE_PTR; - mdns_result_free(server); - if (proto) { - server->search.host[0] = 0; - strlcpy(server->search.service, service, MDNS_NAME_BUF_LEN); - strlcpy(server->search.proto, proto, MDNS_NAME_BUF_LEN); - } else { - strlcpy(server->search.host, service, MDNS_NAME_BUF_LEN); - server->search.service[0] = 0; - server->search.proto[0] = 0; - qtype = MDNS_TYPE_A; - } + mdns_result_t * r; + mdns_ip_addr_t * a; + int i; - uint8_t hostname_len = strlen(server->search.host); - uint8_t service_type_len = strlen(server->search.service); - uint8_t protoname_len = strlen(server->search.proto); + while (results) { + r = results; - size_t len = 23; //head+type+class+(strlen(local)+1)+fqdn_end - if (hostname_len) { - len += hostname_len + 1; - } else if (service_type_len) { - len += service_type_len + protoname_len + 2; - } + free((char *)(r->hostname)); + free((char *)(r->instance_name)); - uint8_t * packet = (uint8_t *)malloc(len); - if (!packet) { - return 0; - } - memset(packet, 0, len); - _mdns_set_u16(packet, MDNS_HEAD_QUESTIONS_OFFSET, 1); - uint16_t index = MDNS_HEAD_LEN; - - if (hostname_len) { - _mdns_append_string(packet, &index, server->search.host); - } else if (service_type_len) { - _mdns_append_string(packet, &index, server->search.service); - _mdns_append_string(packet, &index, server->search.proto); - } - - _mdns_append_string(packet, &index, MDNS_DEFAULT_DOMAIN); - _mdns_append_u8(packet, &index, 0); //fqdn_end - - _mdns_append_u16(packet, &index, qtype); - _mdns_append_u16(packet, &index, MDNS_CLASS_IN); - - MDNS_MUTEX_LOCK(); - size_t written = _mdns_server_write(server, packet, index); - MDNS_MUTEX_UNLOCK(); - free(packet); - if (written != index) { - return 0; - } - - server->search.running = true; - if (timeout) { - uint32_t startAt = xTaskGetTickCount() * portTICK_PERIOD_MS; - while (server->search.running && ((xTaskGetTickCount() * portTICK_PERIOD_MS) - startAt) < timeout) { - vTaskDelay(1 / portTICK_PERIOD_MS); + for (i=0; itxt_count; i++) { + free((char *)(r->txt[i].key)); + free((char *)(r->txt[i].value)); } - server->search.running = false; - MDNS_SEARCH_UNLOCK(); - return mdns_result_get_count(server); + free(r->txt); + + while (r->addr) { + a = r->addr; + r->addr = r->addr->next; + free(a); + } + + results = results->next; + free(r); } - return 0; } -size_t mdns_query_end(mdns_server_t * server) +esp_err_t mdns_query(const char * name, const char * service, const char * proto, uint16_t type, uint32_t timeout, size_t max_results, mdns_result_t ** results) { - if (!server || !server->search.running) { - return 0; - } - server->search.running = false; - MDNS_SEARCH_UNLOCK(); - return mdns_result_get_count(server); -} + mdns_search_once_t * search = NULL; -esp_err_t mdns_result_free(mdns_server_t * server) -{ - if (!server || server->search.running || !server->search.results) { + *results = NULL; + + if (!_mdns_server) { + return ESP_ERR_INVALID_STATE; + } + + if (!timeout) { return ESP_ERR_INVALID_ARG; } - while (server->search.results) { - const mdns_result_t * r = server->search.results; - server->search.results = (mdns_result_t *)r->next; - free((char *)r->host); - free((char *)r->instance); - free((char *)r->txt); - free((mdns_result_t *)r); + + search = _mdns_search_init(name, service, proto, type, timeout, max_results); + if (!search) { + return ESP_ERR_NO_MEM; } - server->search.results = NULL; + + if (_mdns_send_search_action(ACTION_SEARCH_ADD, search)) { + _mdns_search_free(search); + return ESP_ERR_NO_MEM; + } + xSemaphoreTake(search->lock, portMAX_DELAY); + + *results = search->result; + _mdns_search_free(search); + return ESP_OK; } -size_t mdns_result_get_count(mdns_server_t * server) +esp_err_t mdns_query_ptr(const char * service, const char * proto, uint32_t timeout, size_t max_results, mdns_result_t ** results) { - if (!server || !server->search.results) { - return 0; + if (!service || !proto) { + return ESP_ERR_INVALID_ARG; } - size_t len = 0; - const mdns_result_t * r = server->search.results; - while (r) { - len++; - r = r->next; - } - return len; + + return mdns_query(NULL, service, proto, MDNS_TYPE_PTR, timeout, max_results, results); } -const mdns_result_t * mdns_result_get(mdns_server_t * server, size_t num) +esp_err_t mdns_query_srv(const char * instance, const char * service, const char * proto, uint32_t timeout, mdns_result_t ** result) { - if (!server || !server->search.results) { - return NULL; + if (!instance || !service || !proto) { + return ESP_ERR_INVALID_ARG; } - size_t len = 0; - const mdns_result_t * r = server->search.results; - while (r) { - if (len++ == num) { - return r; - } - r = r->next; - } - return NULL; + + return mdns_query(instance, service, proto, MDNS_TYPE_SRV, timeout, 1, result); } + +esp_err_t mdns_query_txt(const char * instance, const char * service, const char * proto, uint32_t timeout, mdns_result_t ** result) +{ + if (!instance || !service || !proto) { + return ESP_ERR_INVALID_ARG; + } + + return mdns_query(instance, service, proto, MDNS_TYPE_TXT, timeout, 1, result); +} + +esp_err_t mdns_query_a(const char * name, uint32_t timeout, ip4_addr_t * addr) +{ + mdns_result_t * result = NULL; + esp_err_t err; + + if (!name) { + return ESP_ERR_INVALID_ARG; + } + + err = mdns_query(name, NULL, NULL, MDNS_TYPE_A, timeout, 1, &result); + + if (err) { + return err; + } + + if (!result) { + return ESP_ERR_NOT_FOUND; + } + + mdns_ip_addr_t * a = result->addr; + while (a) { + if (a->addr.type == IPADDR_TYPE_V4) { + addr->addr = a->addr.u_addr.ip4.addr; + mdns_query_results_free(result); + return ESP_OK; + } + a = a->next; + } + + mdns_query_results_free(result); + return ESP_ERR_NOT_FOUND; +} + +esp_err_t mdns_query_aaaa(const char * name, uint32_t timeout, ip6_addr_t * addr) +{ + mdns_result_t * result = NULL; + esp_err_t err; + + if (!name) { + return ESP_ERR_INVALID_ARG; + } + + err = mdns_query(name, NULL, NULL, MDNS_TYPE_AAAA, timeout, 1, &result); + + if (err) { + return err; + } + + if (!result) { + return ESP_ERR_NOT_FOUND; + } + + mdns_ip_addr_t * a = result->addr; + while (a) { + if (a->addr.type == IPADDR_TYPE_V6) { + memcpy(addr->addr, a->addr.u_addr.ip6.addr, 16); + mdns_query_results_free(result); + return ESP_OK; + } + a = a->next; + } + + mdns_query_results_free(result); + return ESP_ERR_NOT_FOUND; +} + +#ifdef MDNS_ENABLE_DEBUG + +void mdns_debug_packet(const uint8_t * data, size_t len) +{ + static mdns_name_t n; + mdns_header_t header; + const uint8_t * content = data + MDNS_HEAD_LEN; + uint32_t t = xTaskGetTickCount() * portTICK_PERIOD_MS; + mdns_name_t * name = &n; + memset(name, 0, sizeof(mdns_name_t)); + + _mdns_dbg_printf("Packet[%u]: ", t); + + header.id = _mdns_read_u16(data, MDNS_HEAD_ID_OFFSET); + header.flags.value = _mdns_read_u16(data, MDNS_HEAD_FLAGS_OFFSET); + header.questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET); + header.answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET); + header.servers = _mdns_read_u16(data, MDNS_HEAD_SERVERS_OFFSET); + header.additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET); + + _mdns_dbg_printf("%s", + (header.flags.value == MDNS_FLAGS_AUTHORITATIVE)?"AUTHORITATIVE\n": + (header.flags.value == MDNS_FLAGS_DISTRIBUTED)?"DISTRIBUTED\n": + (header.flags.value == 0)?"\n":" " + ); + if (header.flags.value && header.flags.value != MDNS_FLAGS_AUTHORITATIVE) { + _mdns_dbg_printf("0x%04X\n", header.flags.value); + } + + if (header.questions) { + uint8_t qs = header.questions; + + while (qs--) { + content = _mdns_parse_fqdn(data, content, name); + if (!content) { + header.answers = 0; + header.additional = 0; + header.servers = 0; + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + break; + } + + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t clas = _mdns_read_u16(content, MDNS_CLASS_OFFSET); + bool unicast = !!(clas & 0x8000); + clas &= 0x7FFF; + content = content + 4; + + _mdns_dbg_printf(" Q: "); + if (unicast) { + _mdns_dbg_printf("*U* "); + } + if (type == MDNS_TYPE_PTR) { + _mdns_dbg_printf("%s.%s%s.%s.%s. PTR ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_SRV) { + _mdns_dbg_printf("%s.%s%s.%s.%s. SRV ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_TXT) { + _mdns_dbg_printf("%s.%s%s.%s.%s. TXT ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_A) { + _mdns_dbg_printf("%s.%s. A ", name->host, name->domain); + } else if (type == MDNS_TYPE_AAAA) { + _mdns_dbg_printf("%s.%s. AAAA ", name->host, name->domain); + } else if (type == MDNS_TYPE_NSEC) { + _mdns_dbg_printf("%s.%s%s.%s.%s. NSEC ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_ANY) { + _mdns_dbg_printf("%s.%s%s.%s.%s. ANY ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain); + } else { + _mdns_dbg_printf("%s.%s%s.%s.%s. %04X ", name->host, name->sub?"_sub.":"", name->service, name->proto, name->domain, type); + } + + if (clas == 0x0001) { + _mdns_dbg_printf("IN"); + } else { + _mdns_dbg_printf("%04X", clas); + } + _mdns_dbg_printf("\n"); + } + } + + if (header.answers || header.servers || header.additional) { + uint16_t recordIndex = 0; + + while (content < (data + len)) { + + content = _mdns_parse_fqdn(data, content, name); + if (!content) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + break; + } + + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t clas = _mdns_read_u16(content, MDNS_CLASS_OFFSET); + uint32_t ttl = _mdns_read_u32(content, MDNS_TTL_OFFSET); + uint16_t data_len = _mdns_read_u16(content, MDNS_LEN_OFFSET); + const uint8_t * data_ptr = content + MDNS_DATA_OFFSET; + bool flush = !!(clas & 0x8000); + clas &= 0x7FFF; + + content = data_ptr + data_len; + if (content > (data + len)) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + break; + } + + mdns_parsed_recort_type_t record_type = MDNS_ANSWER; + + if (recordIndex >= (header.answers + header.servers)) { + record_type = MDNS_EXTRA; + } else if (recordIndex >= (header.answers)) { + record_type = MDNS_NS; + } + recordIndex++; + + if (record_type == MDNS_EXTRA) { + _mdns_dbg_printf(" X"); + } else if (record_type == MDNS_NS) { + _mdns_dbg_printf(" S"); + } else { + _mdns_dbg_printf(" A"); + } + + if (type == MDNS_TYPE_PTR) { + _mdns_dbg_printf(": %s%s%s.%s.%s. PTR ", name->host, name->host[0]?".":"", name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_SRV) { + _mdns_dbg_printf(": %s.%s.%s.%s. SRV ", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_TXT) { + _mdns_dbg_printf(": %s.%s.%s.%s. TXT ", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_A) { + _mdns_dbg_printf(": %s.%s. A ", name->host, name->domain); + } else if (type == MDNS_TYPE_AAAA) { + _mdns_dbg_printf(": %s.%s. AAAA ", name->host, name->domain); + } else if (type == MDNS_TYPE_NSEC) { + _mdns_dbg_printf(": %s.%s.%s.%s. NSEC ", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_ANY) { + _mdns_dbg_printf(": %s.%s.%s.%s. ANY ", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_OPT) { + _mdns_dbg_printf(": . OPT "); + } else { + _mdns_dbg_printf(": %s.%s.%s.%s. %04X ", name->host, name->service, name->proto, name->domain, type); + } + + if (clas == 0x0001) { + _mdns_dbg_printf("IN "); + } else { + _mdns_dbg_printf("%04X ", clas); + } + if (flush) { + _mdns_dbg_printf("FLUSH "); + } + _mdns_dbg_printf("%u ", ttl); + _mdns_dbg_printf("[%u] ", data_len); + if (type == MDNS_TYPE_PTR) { + if (!_mdns_parse_fqdn(data, data_ptr, name)) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + continue; + } + _mdns_dbg_printf("%s.%s.%s.%s.\n", name->host, name->service, name->proto, name->domain); + } else if (type == MDNS_TYPE_SRV) { + if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name)) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + continue; + } + uint16_t priority = _mdns_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET); + uint16_t weight = _mdns_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET); + uint16_t port = _mdns_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET); + _mdns_dbg_printf("%u %u %u %s.%s.\n", priority, weight, port, name->host, name->domain); + } else if (type == MDNS_TYPE_TXT) { + uint16_t i=0, y; + while (i < data_len) { + uint8_t partLen = data_ptr[i++]; + if ((i+partLen) > data_len) { + _mdns_dbg_printf("ERROR: %s:%u\n", __FILE__, __LINE__); + break; + } + char txt[partLen+1]; + for (y=0; yhost, name->service, name->proto, name->domain); + size_t diff = new_ptr - old_ptr; + data_len -= diff; + data_ptr = new_ptr; + } + size_t i; + for (i=0; i +#include +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "mdns.h" + +static const char * if_str[] = {"STA", "AP", "ETH", "MAX"}; +static const char * ip_protocol_str[] = {"V4", "V6", "MAX"}; + +static void mdns_print_results(mdns_result_t * results) +{ + mdns_result_t * r = results; + mdns_ip_addr_t * a = NULL; + int i = 1, t; + while (r) { + printf("%d: Interface: %s, Type: %s\n", i++, if_str[r->tcpip_if], ip_protocol_str[r->ip_protocol]); + if (r->instance_name) { + printf(" PTR : %s\n", r->instance_name); + } + if (r->hostname) { + printf(" SRV : %s.local:%u\n", r->hostname, r->port); + } + if (r->txt_count) { + printf(" TXT : [%u] ", r->txt_count); + for (t=0; ttxt_count; t++) { + printf("%s=%s; ", r->txt[t].key, r->txt[t].value); + } + printf("\n"); + } + a = r->addr; + while (a) { + if (a->addr.type == IPADDR_TYPE_V6) { + printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6)); + } else { + printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4))); + } + a = a->next; + } + r = r->next; + } +} + +static struct { + struct arg_str *hostname; + struct arg_int *timeout; + struct arg_end *end; +} mdns_query_a_args; + +static int cmd_mdns_query_a(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_a_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_a_args.end, argv[0]); + return 1; + } + + const char * hostname = mdns_query_a_args.hostname->sval[0]; + int timeout = mdns_query_a_args.timeout->ival[0]; + + if (!hostname || !hostname[0]) { + printf("ERROR: Hostname not supplied\n"); + return 1; + } + + if (timeout <= 0) { + timeout = 1000; + } + + printf("Query A: %s.local, Timeout: %d\n", hostname, timeout); + + struct ip4_addr addr; + addr.addr = 0; + + esp_err_t err = mdns_query_a(hostname, timeout, &addr); + if (err) { + if (err == ESP_ERR_NOT_FOUND) { + printf("ERROR: Host was not found!\n"); + return 0; + } + printf("ERROR: Query Failed\n"); + return 1; + } + + printf(IPSTR "\n", IP2STR(&addr)); + + return 0; +} + +static void register_mdns_query_a() +{ + mdns_query_a_args.hostname = arg_str1(NULL, NULL, "", "Hostname that is searched for"); + mdns_query_a_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_a_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_a", + .help = "Query MDNS for IPv4", + .hint = NULL, + .func = &cmd_mdns_query_a, + .argtable = &mdns_query_a_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static int cmd_mdns_query_aaaa(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_a_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_a_args.end, argv[0]); + return 1; + } + + const char * hostname = mdns_query_a_args.hostname->sval[0]; + int timeout = mdns_query_a_args.timeout->ival[0]; + + if (!hostname || !hostname[0]) { + printf("ERROR: Hostname not supplied\n"); + return 1; + } + + if (timeout <= 0) { + timeout = 1000; + } + + printf("Query AAAA: %s.local, Timeout: %d\n", hostname, timeout); + + struct ip6_addr addr; + memset(addr.addr, 0, 16); + + esp_err_t err = mdns_query_aaaa(hostname, timeout, &addr); + if (err) { + if (err == ESP_ERR_NOT_FOUND) { + printf("Host was not found!\n"); + return 0; + } + printf("ERROR: Query Failed\n"); + return 1; + } + + printf(IPV6STR "\n", IPV62STR(addr)); + + return 0; +} + +static void register_mdns_query_aaaa() +{ + mdns_query_a_args.hostname = arg_str1(NULL, NULL, "", "Hostname that is searched for"); + mdns_query_a_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_a_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_aaaa", + .help = "Query MDNS for IPv6", + .hint = NULL, + .func = &cmd_mdns_query_aaaa, + .argtable = &mdns_query_a_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *instance; + struct arg_str *service; + struct arg_str *proto; + struct arg_int *timeout; + struct arg_end *end; +} mdns_query_srv_args; + +static int cmd_mdns_query_srv(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_srv_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_srv_args.end, argv[0]); + return 1; + } + + const char * instance = mdns_query_srv_args.instance->sval[0]; + const char * service = mdns_query_srv_args.service->sval[0]; + const char * proto = mdns_query_srv_args.proto->sval[0]; + int timeout = mdns_query_srv_args.timeout->ival[0]; + + if (timeout <= 0) { + timeout = 1000; + } + + printf("Query SRV: %s.%s.%s.local, Timeout: %d\n", instance, service, proto, timeout); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_srv(instance, service, proto, timeout, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + mdns_print_results(results); + mdns_query_results_free(results); + return 0; +} + +static void register_mdns_query_srv() +{ + mdns_query_srv_args.instance = arg_str1(NULL, NULL, "", "Instance to search for"); + mdns_query_srv_args.service = arg_str1(NULL, NULL, "", "Service to search for (ex. _http, _smb, etc.)"); + mdns_query_srv_args.proto = arg_str1(NULL, NULL, "", "Protocol to search for (_tcp, _udp, etc.)"); + mdns_query_srv_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_srv_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_srv", + .help = "Query MDNS for Service SRV", + .hint = NULL, + .func = &cmd_mdns_query_srv, + .argtable = &mdns_query_srv_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *instance; + struct arg_str *service; + struct arg_str *proto; + struct arg_int *timeout; + struct arg_end *end; +} mdns_query_txt_args; + +static int cmd_mdns_query_txt(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_txt_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_txt_args.end, argv[0]); + return 1; + } + + const char * instance = mdns_query_txt_args.instance->sval[0]; + const char * service = mdns_query_txt_args.service->sval[0]; + const char * proto = mdns_query_txt_args.proto->sval[0]; + int timeout = mdns_query_txt_args.timeout->ival[0]; + + printf("Query TXT: %s.%s.%s.local, Timeout: %d\n", instance, service, proto, timeout); + + if (timeout <= 0) { + timeout = 5000; + } + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_txt(instance, service, proto, timeout, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + + mdns_print_results(results); + mdns_query_results_free(results); + return 0; +} + +static void register_mdns_query_txt() +{ + mdns_query_txt_args.instance = arg_str1(NULL, NULL, "", "Instance to search for"); + mdns_query_txt_args.service = arg_str1(NULL, NULL, "", "Service to search for (ex. _http, _smb, etc.)"); + mdns_query_txt_args.proto = arg_str1(NULL, NULL, "", "Protocol to search for (_tcp, _udp, etc.)"); + mdns_query_txt_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_txt_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_txt", + .help = "Query MDNS for Service TXT", + .hint = NULL, + .func = &cmd_mdns_query_txt, + .argtable = &mdns_query_txt_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_int *timeout; + struct arg_int *max_results; + struct arg_end *end; +} mdns_query_ptr_args; + +static int cmd_mdns_query_ptr(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_ptr_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_ptr_args.end, argv[0]); + return 1; + } + + const char * service = mdns_query_ptr_args.service->sval[0]; + const char * proto = mdns_query_ptr_args.proto->sval[0]; + int timeout = mdns_query_ptr_args.timeout->ival[0]; + int max_results = mdns_query_ptr_args.max_results->ival[0]; + + if (timeout <= 0) { + timeout = 5000; + } + + if (max_results <= 0 || max_results > 255) { + max_results = 255; + } + + printf("Query PTR: %s.%s.local, Timeout: %d, Max Results: %d\n", service, proto, timeout, max_results); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_ptr(service, proto, timeout, max_results, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + + mdns_print_results(results); + mdns_query_results_free(results); + return 0; +} + +static void register_mdns_query_ptr() +{ + mdns_query_ptr_args.service = arg_str1(NULL, NULL, "", "Service to search for (ex. _http, _smb, etc.)"); + mdns_query_ptr_args.proto = arg_str1(NULL, NULL, "", "Protocol to search for (_tcp, _udp, etc.)"); + mdns_query_ptr_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_ptr_args.max_results = arg_int0("m", "max_results", "", "Maximum results returned"); + mdns_query_ptr_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_ptr", + .help = "Query MDNS for Service", + .hint = NULL, + .func = &cmd_mdns_query_ptr, + .argtable = &mdns_query_ptr_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *hostname; + struct arg_int *timeout; + struct arg_int *max_results; + struct arg_end *end; +} mdns_query_ip_args; + +static int cmd_mdns_query_ip(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_ip_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_ip_args.end, argv[0]); + return 1; + } + + const char * hostname = mdns_query_ip_args.hostname->sval[0]; + int timeout = mdns_query_ip_args.timeout->ival[0]; + int max_results = mdns_query_ip_args.max_results->ival[0]; + + if (!hostname || !hostname[0]) { + printf("ERROR: Hostname not supplied\n"); + return 1; + } + + if (timeout <= 0) { + timeout = 1000; + } + + if (max_results < 0 || max_results > 255) { + max_results = 255; + } + + printf("Query IP: %s.local, Timeout: %d, Max Results: %d\n", hostname, timeout, max_results); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query(hostname, NULL, NULL, MDNS_TYPE_ANY, timeout, max_results, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + mdns_print_results(results); + mdns_query_results_free(results); + + return 0; +} + +static void register_mdns_query_ip() +{ + mdns_query_ip_args.hostname = arg_str1(NULL, NULL, "", "Hostname that is searched for"); + mdns_query_ip_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_ip_args.max_results = arg_int0("m", "max_results", "", "Maximum results returned"); + mdns_query_ip_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_ip", + .help = "Query MDNS for IP", + .hint = NULL, + .func = &cmd_mdns_query_ip, + .argtable = &mdns_query_ip_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *instance; + struct arg_str *service; + struct arg_str *proto; + struct arg_int *timeout; + struct arg_int *max_results; + struct arg_end *end; +} mdns_query_svc_args; + +static int cmd_mdns_query_svc(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_query_svc_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_query_svc_args.end, argv[0]); + return 1; + } + + const char * instance = mdns_query_svc_args.instance->sval[0]; + const char * service = mdns_query_svc_args.service->sval[0]; + const char * proto = mdns_query_svc_args.proto->sval[0]; + int timeout = mdns_query_svc_args.timeout->ival[0]; + int max_results = mdns_query_svc_args.max_results->ival[0]; + + if (timeout <= 0) { + timeout = 5000; + } + + if (max_results < 0 || max_results > 255) { + max_results = 255; + } + + printf("Query SVC: %s.%s.%s.local, Timeout: %d, Max Results: %d\n", instance, service, proto, timeout, max_results); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query(instance, service, proto, MDNS_TYPE_ANY, timeout, max_results, &results); + if (err) { + printf("ERROR: Query Failed\n"); + return 1; + } + if (!results) { + printf("No results found!\n"); + return 0; + } + + mdns_print_results(results); + mdns_query_results_free(results); + return 0; +} + +static void register_mdns_query_svc() +{ + mdns_query_svc_args.instance = arg_str1(NULL, NULL, "", "Instance to search for"); + mdns_query_svc_args.service = arg_str1(NULL, NULL, "", "Service to search for (ex. _http, _smb, etc.)"); + mdns_query_svc_args.proto = arg_str1(NULL, NULL, "", "Protocol to search for (_tcp, _udp, etc.)"); + mdns_query_svc_args.timeout = arg_int0("t", "timeout", "", "Timeout for this query"); + mdns_query_svc_args.max_results = arg_int0("m", "max_results", "", "Maximum results returned"); + mdns_query_svc_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_query_svc", + .help = "Query MDNS for Service TXT & SRV", + .hint = NULL, + .func = &cmd_mdns_query_svc, + .argtable = &mdns_query_svc_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static struct { + struct arg_str *hostname; + struct arg_str *instance; + struct arg_end *end; +} mdns_init_args; + +static int cmd_mdns_init(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_init_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_init_args.end, argv[0]); + return 1; + } + + ESP_ERROR_CHECK( mdns_init() ); + + if (mdns_init_args.hostname->sval[0]) { + ESP_ERROR_CHECK( mdns_hostname_set(mdns_init_args.hostname->sval[0]) ); + printf("MDNS: Hostname: %s\n", mdns_init_args.hostname->sval[0]); + } + + if (mdns_init_args.instance->sval[0]) { + ESP_ERROR_CHECK( mdns_instance_name_set(mdns_init_args.instance->sval[0]) ); + printf("MDNS: Instance: %s\n", mdns_init_args.instance->sval[0]); + } + + return 0; +} + +static void register_mdns_init() +{ + mdns_init_args.hostname = arg_str0("h", "hostname", "", "Hostname that the server will advertise"); + mdns_init_args.instance = arg_str0("i", "instance", "", "Default instance name for services"); + mdns_init_args.end = arg_end(2); + + const esp_console_cmd_t cmd_init = { + .command = "mdns_init", + .help = "Start MDNS Server", + .hint = NULL, + .func = &cmd_mdns_init, + .argtable = &mdns_init_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) ); +} + +static int cmd_mdns_free(int argc, char** argv) +{ + mdns_free(); + return 0; +} + +static void register_mdns_free() +{ + const esp_console_cmd_t cmd_free = { + .command = "mdns_free", + .help = "Stop MDNS Server", + .hint = NULL, + .func = &cmd_mdns_free, + .argtable = NULL + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_free) ); +} + +static struct { + struct arg_str *hostname; + struct arg_end *end; +} mdns_set_hostname_args; + +static int cmd_mdns_set_hostname(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_set_hostname_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_set_hostname_args.end, argv[0]); + return 1; + } + + if (mdns_set_hostname_args.hostname->sval[0] == NULL) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_hostname_set(mdns_set_hostname_args.hostname->sval[0]) ); + return 0; +} + +static void register_mdns_set_hostname() +{ + mdns_set_hostname_args.hostname = arg_str1(NULL, NULL, "", "Hostname that the server will advertise"); + mdns_set_hostname_args.end = arg_end(2); + + const esp_console_cmd_t cmd_set_hostname = { + .command = "mdns_set_hostname", + .help = "Set MDNS Server hostname", + .hint = NULL, + .func = &cmd_mdns_set_hostname, + .argtable = &mdns_set_hostname_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_set_hostname) ); +} + +static struct { + struct arg_str *instance; + struct arg_end *end; +} mdns_set_instance_args; + +static int cmd_mdns_set_instance(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_set_instance_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_set_instance_args.end, argv[0]); + return 1; + } + + if (mdns_set_instance_args.instance->sval[0] == NULL) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_instance_name_set(mdns_set_instance_args.instance->sval[0]) ); + return 0; +} + +static void register_mdns_set_instance() +{ + mdns_set_instance_args.instance = arg_str1(NULL, NULL, "", "Default instance name for services"); + mdns_set_instance_args.end = arg_end(2); + + const esp_console_cmd_t cmd_set_instance = { + .command = "mdns_set_instance", + .help = "Set MDNS Server Istance Name", + .hint = NULL, + .func = &cmd_mdns_set_instance, + .argtable = &mdns_set_instance_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_set_instance) ); +} + +static mdns_txt_item_t * _convert_items(const char **values, int count) +{ + int i=0,e; + const char * value = NULL; + mdns_txt_item_t * items = (mdns_txt_item_t*) malloc(sizeof(mdns_txt_item_t) * count); + if (!items) { + printf("ERROR: No Memory!\n"); + goto fail; + + } + memset(items, 0, sizeof(mdns_txt_item_t) * count); + + for (i=0; isval[0] || !mdns_add_args.proto->sval[0] || !mdns_add_args.port->ival[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + const char * instance = NULL; + if (mdns_add_args.instance->sval[0] && mdns_add_args.instance->sval[0][0]) { + instance = mdns_add_args.instance->sval[0]; + printf("MDNS: Service Instance: %s\n", instance); + } + mdns_txt_item_t * items = NULL; + if (mdns_add_args.txt->count) { + items = _convert_items(mdns_add_args.txt->sval, mdns_add_args.txt->count); + if (!items) { + printf("ERROR: No Memory!\n"); + return 1; + + } + } + + ESP_ERROR_CHECK( mdns_service_add(instance, mdns_add_args.service->sval[0], mdns_add_args.proto->sval[0], mdns_add_args.port->ival[0], items, mdns_add_args.txt->count) ); + free(items); + return 0; +} + +static void register_mdns_service_add() +{ + mdns_add_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_add_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_add_args.port = arg_int1(NULL, NULL, "", "Service Port"); + mdns_add_args.instance = arg_str0("i", "instance", "", "Instance name"); + mdns_add_args.txt = arg_strn(NULL, NULL, "item", 0, 30, "TXT Items (name=value)"); + mdns_add_args.end = arg_end(2); + + const esp_console_cmd_t cmd_add = { + .command = "mdns_service_add", + .help = "Add service to MDNS", + .hint = NULL, + .func = &cmd_mdns_service_add, + .argtable = &mdns_add_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_add) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_end *end; +} mdns_remove_args; + +static int cmd_mdns_service_remove(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_remove_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_remove_args.end, argv[0]); + return 1; + } + + if (!mdns_remove_args.service->sval[0] || !mdns_remove_args.proto->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_remove(mdns_remove_args.service->sval[0], mdns_remove_args.proto->sval[0]) ); + return 0; +} + +static void register_mdns_service_remove() +{ + mdns_remove_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_remove_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_remove_args.end = arg_end(2); + + const esp_console_cmd_t cmd_remove = { + .command = "mdns_service_remove", + .help = "Remove service from MDNS", + .hint = NULL, + .func = &cmd_mdns_service_remove, + .argtable = &mdns_remove_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_remove) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_str *instance; + struct arg_end *end; +} mdns_service_instance_set_args; + +static int cmd_mdns_service_instance_set(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_service_instance_set_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_service_instance_set_args.end, argv[0]); + return 1; + } + + if (!mdns_service_instance_set_args.service->sval[0] || !mdns_service_instance_set_args.proto->sval[0] || !mdns_service_instance_set_args.instance->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_instance_name_set(mdns_service_instance_set_args.service->sval[0], mdns_service_instance_set_args.proto->sval[0], mdns_service_instance_set_args.instance->sval[0]) ); + return 0; +} + +static void register_mdns_service_instance_set() +{ + mdns_service_instance_set_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_service_instance_set_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_service_instance_set_args.instance = arg_str1(NULL, NULL, "", "Instance name"); + mdns_service_instance_set_args.end = arg_end(2); + + const esp_console_cmd_t cmd_add = { + .command = "mdns_service_instance_set", + .help = "Set MDNS Service Instance Name", + .hint = NULL, + .func = &cmd_mdns_service_instance_set, + .argtable = &mdns_service_instance_set_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_add) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_int *port; + struct arg_end *end; +} mdns_service_port_set_args; + +static int cmd_mdns_service_port_set(int argc, char** argv) { + int nerrors = arg_parse(argc, argv, (void**) &mdns_service_port_set_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_service_port_set_args.end, argv[0]); + return 1; + } + + if (!mdns_service_port_set_args.service->sval[0] || !mdns_service_port_set_args.proto->sval[0] || !mdns_service_port_set_args.port->ival[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_port_set(mdns_service_port_set_args.service->sval[0], mdns_service_port_set_args.proto->sval[0], mdns_service_port_set_args.port->ival[0]) ); + return 0; +} + +static void register_mdns_service_port_set() +{ + mdns_service_port_set_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_service_port_set_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_service_port_set_args.port = arg_int1(NULL, NULL, "", "Service Port"); + mdns_service_port_set_args.end = arg_end(2); + + const esp_console_cmd_t cmd_add = { + .command = "mdns_service_port_set", + .help = "Set MDNS Service port", + .hint = NULL, + .func = &cmd_mdns_service_port_set, + .argtable = &mdns_service_port_set_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_add) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_str *txt; + struct arg_end *end; +} mdns_txt_replace_args; + +static int cmd_mdns_service_txt_replace(int argc, char** argv) +{ + mdns_txt_item_t * items = NULL; + int nerrors = arg_parse(argc, argv, (void**) &mdns_txt_replace_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_txt_replace_args.end, argv[0]); + return 1; + } + + if (!mdns_txt_replace_args.service->sval[0] || !mdns_txt_replace_args.proto->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + if (mdns_txt_replace_args.txt->count) { + items = _convert_items(mdns_txt_replace_args.txt->sval, mdns_txt_replace_args.txt->count); + if (!items) { + printf("ERROR: No Memory!\n"); + return 1; + + } + } + ESP_ERROR_CHECK( mdns_service_txt_set(mdns_txt_replace_args.service->sval[0], mdns_txt_replace_args.proto->sval[0], items, mdns_txt_replace_args.txt->count) ); + free(items); + return 0; +} + +static void register_mdns_service_txt_replace() +{ + mdns_txt_replace_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_txt_replace_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_txt_replace_args.txt = arg_strn(NULL, NULL, "item", 0, 30, "TXT Items (name=value)"); + mdns_txt_replace_args.end = arg_end(2); + + const esp_console_cmd_t cmd_txt_set = { + .command = "mdns_service_txt_replace", + .help = "Replace MDNS service TXT items", + .hint = NULL, + .func = &cmd_mdns_service_txt_replace, + .argtable = &mdns_txt_replace_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_txt_set) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_str *var; + struct arg_str *value; + struct arg_end *end; +} mdns_txt_set_args; + +static int cmd_mdns_service_txt_set(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_txt_set_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_txt_set_args.end, argv[0]); + return 1; + } + + if (!mdns_txt_set_args.service->sval[0] || !mdns_txt_set_args.proto->sval[0] || !mdns_txt_set_args.var->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_txt_item_set(mdns_txt_set_args.service->sval[0], mdns_txt_set_args.proto->sval[0], mdns_txt_set_args.var->sval[0], mdns_txt_set_args.value->sval[0]) ); + return 0; +} + +static void register_mdns_service_txt_set() +{ + mdns_txt_set_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_txt_set_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_txt_set_args.var = arg_str1(NULL, NULL, "", "Item Name"); + mdns_txt_set_args.value = arg_str1(NULL, NULL, "", "Item Value"); + mdns_txt_set_args.end = arg_end(2); + + const esp_console_cmd_t cmd_txt_set = { + .command = "mdns_service_txt_set", + .help = "Add/Set MDNS service TXT item", + .hint = NULL, + .func = &cmd_mdns_service_txt_set, + .argtable = &mdns_txt_set_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_txt_set) ); +} + +static struct { + struct arg_str *service; + struct arg_str *proto; + struct arg_str *var; + struct arg_end *end; +} mdns_txt_remove_args; + +static int cmd_mdns_service_txt_remove(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &mdns_txt_remove_args); + if (nerrors != 0) { + arg_print_errors(stderr, mdns_txt_remove_args.end, argv[0]); + return 1; + } + + if (!mdns_txt_remove_args.service->sval[0] || !mdns_txt_remove_args.proto->sval[0] || !mdns_txt_remove_args.var->sval[0]) { + printf("ERROR: Bad arguments!\n"); + return 1; + } + + ESP_ERROR_CHECK( mdns_service_txt_item_remove(mdns_txt_remove_args.service->sval[0], mdns_txt_remove_args.proto->sval[0], mdns_txt_remove_args.var->sval[0]) ); + return 0; +} + +static void register_mdns_service_txt_remove() +{ + mdns_txt_remove_args.service = arg_str1(NULL, NULL, "", "MDNS Service"); + mdns_txt_remove_args.proto = arg_str1(NULL, NULL, "", "IP Protocol"); + mdns_txt_remove_args.var = arg_str1(NULL, NULL, "", "Item Name"); + mdns_txt_remove_args.end = arg_end(2); + + const esp_console_cmd_t cmd_txt_remove = { + .command = "mdns_service_txt_remove", + .help = "Remove MDNS service TXT item", + .hint = NULL, + .func = &cmd_mdns_service_txt_remove, + .argtable = &mdns_txt_remove_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_txt_remove) ); +} + +static int cmd_mdns_service_remove_all(int argc, char** argv) +{ + mdns_service_remove_all(); + return 0; +} + +static void register_mdns_service_remove_all() +{ + const esp_console_cmd_t cmd_free = { + .command = "mdns_service_remove_all", + .help = "Remove all MDNS services", + .hint = NULL, + .func = &cmd_mdns_service_remove_all, + .argtable = NULL + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_free) ); +} + +void mdns_console_register() +{ + register_mdns_init(); + register_mdns_free(); + register_mdns_set_hostname(); + register_mdns_set_instance(); + register_mdns_service_add(); + register_mdns_service_remove(); + register_mdns_service_instance_set(); + register_mdns_service_port_set(); + register_mdns_service_txt_replace(); + register_mdns_service_txt_set(); + register_mdns_service_txt_remove(); + register_mdns_service_remove_all(); + + register_mdns_query_a(); + register_mdns_query_aaaa(); + register_mdns_query_txt(); + register_mdns_query_srv(); + register_mdns_query_ptr(); + + register_mdns_query_ip(); + register_mdns_query_svc(); +} + diff --git a/components/mdns/private_include/mdns_private.h b/components/mdns/private_include/mdns_private.h new file mode 100644 index 000000000..a3d9fa279 --- /dev/null +++ b/components/mdns/private_include/mdns_private.h @@ -0,0 +1,388 @@ +// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef MDNS_PRIVATE_H_ +#define MDNS_PRIVATE_H_ + +//#define MDNS_ENABLE_DEBUG + +#ifdef MDNS_ENABLE_DEBUG +#define _mdns_dbg_printf(...) printf(__VA_ARGS__) +#endif + +#define MDNS_ANSWER_PTR_TTL 4500 +#define MDNS_ANSWER_TXT_TTL 4500 +#define MDNS_ANSWER_SRV_TTL 120 +#define MDNS_ANSWER_A_TTL 120 +#define MDNS_ANSWER_AAAA_TTL 120 + +#define MDNS_FLAGS_AUTHORITATIVE 0x8400 +#define MDNS_FLAGS_DISTRIBUTED 0x0200 + +#define MDNS_NAME_REF 0xC000 + +//custom type! only used by this implementation +//to help manage service discovery handling +#define MDNS_TYPE_SDPTR 0x0032 + +#define MDNS_CLASS_IN 0x0001 +#define MDNS_CLASS_ANY 0x00FF +#define MDNS_CLASS_IN_FLUSH_CACHE 0x8001 + +#define MDNS_ANSWER_ALL 0x3F +#define MDNS_ANSWER_PTR 0x08 +#define MDNS_ANSWER_TXT 0x04 +#define MDNS_ANSWER_SRV 0x02 +#define MDNS_ANSWER_A 0x01 +#define MDNS_ANSWER_AAAA 0x10 +#define MDNS_ANSWER_NSEC 0x20 +#define MDNS_ANSWER_SDPTR 0x80 + +#define MDNS_SERVICE_PORT 5353 // UDP port that the server runs on +#define MDNS_SERVICE_STACK_DEPTH 4096 // Stack size for the service thread +#define MDNS_PACKET_QUEUE_LEN 16 // Maximum packets that can be queued for parsing +#define MDNS_ACTION_QUEUE_LEN 16 // Maximum actions pending to the server +#define MDNS_TXT_MAX_LEN 1024 // Maximum string length of text data in TXT record +#define MDNS_NAME_MAX_LEN 64 // Maximum string length of hostname, instance, service and proto +#define MDNS_NAME_BUF_LEN (MDNS_NAME_MAX_LEN+1) // Maximum char buffer size to hold hostname, instance, service or proto +#define MDNS_MAX_PACKET_SIZE 1460 // Maximum size of mDNS outgoing packet + +#define MDNS_HEAD_LEN 12 +#define MDNS_HEAD_ID_OFFSET 0 +#define MDNS_HEAD_FLAGS_OFFSET 2 +#define MDNS_HEAD_QUESTIONS_OFFSET 4 +#define MDNS_HEAD_ANSWERS_OFFSET 6 +#define MDNS_HEAD_SERVERS_OFFSET 8 +#define MDNS_HEAD_ADDITIONAL_OFFSET 10 + +#define MDNS_TYPE_OFFSET 0 +#define MDNS_CLASS_OFFSET 2 +#define MDNS_TTL_OFFSET 4 +#define MDNS_LEN_OFFSET 8 +#define MDNS_DATA_OFFSET 10 + +#define MDNS_SRV_PRIORITY_OFFSET 0 +#define MDNS_SRV_WEIGHT_OFFSET 2 +#define MDNS_SRV_PORT_OFFSET 4 +#define MDNS_SRV_FQDN_OFFSET 6 + +#define MDNS_TIMER_PERIOD_US 100000 + +#define MDNS_SERVICE_LOCK() xSemaphoreTake(_mdns_service_semaphore, portMAX_DELAY) +#define MDNS_SERVICE_UNLOCK() xSemaphoreGive(_mdns_service_semaphore) + +#define queueToEnd(type, queue, item) \ + if (!queue) { \ + queue = item; \ + } else { \ + type * _q = queue; \ + while (_q->next) { _q = _q->next; } \ + _q->next = item; \ + } + +#define queueDetach(type, queue, item) \ + if (queue) { \ + if (queue == item) { \ + queue = queue->next; \ + } else { \ + type * _q = queue; \ + while (_q->next && _q->next != item) { \ + _q = _q->next; \ + } \ + if (_q->next == item) { \ + _q->next = item->next; \ + item->next = NULL; \ + } \ + } \ + } + +#define queueFree(type, queue) while (queue) { type * _q = queue; queue = queue->next; free(_q); } + +#define PCB_STATE_IS_PROBING(s) (s->state > PCB_OFF && s->state < PCB_ANNOUNCE_1) +#define PCB_STATE_IS_ANNOUNCING(s) (s->state > PCB_PROBE_3 && s->state < PCB_RUNNING) +#define PCB_STATE_IS_RUNNING(s) (s->state == PCB_RUNNING) + +#define MDNS_SEARCH_LOCK() xSemaphoreTake(_mdns_server->search.lock, portMAX_DELAY) +#define MDNS_SEARCH_UNLOCK() xSemaphoreGive(_mdns_server->search.lock) + + +typedef enum { + PCB_OFF, PCB_DUP, PCB_INIT, + PCB_PROBE_1, PCB_PROBE_2, PCB_PROBE_3, + PCB_ANNOUNCE_1, PCB_ANNOUNCE_2, PCB_ANNOUNCE_3, + PCB_RUNNING +} mdns_pcb_state_t; + +typedef enum { + MDNS_ANSWER, MDNS_NS, MDNS_EXTRA +} mdns_parsed_recort_type_t; + +typedef enum { + ACTION_SYSTEM_EVENT, + ACTION_HOSTNAME_SET, + ACTION_INSTANCE_SET, + ACTION_SERVICE_ADD, + ACTION_SERVICE_DEL, + ACTION_SERVICE_INSTANCE_SET, + ACTION_SERVICE_PORT_SET, + ACTION_SERVICE_TXT_REPLACE, + ACTION_SERVICE_TXT_SET, + ACTION_SERVICE_TXT_DEL, + ACTION_SERVICES_CLEAR, + ACTION_SEARCH_ADD, + ACTION_SEARCH_SEND, + ACTION_SEARCH_END, + ACTION_TX_HANDLE, + ACTION_RX_HANDLE, + ACTION_TASK_STOP, + ACTION_MAX +} mdns_action_type_t; + + +typedef struct { + uint16_t id; + union { + struct { + uint16_t qr :1; + uint16_t opCode :4; + uint16_t aa :1; + uint16_t tc :1; + uint16_t rd :1; + uint16_t ra :1; + uint16_t z :1; + uint16_t ad :1; + uint16_t cd :1; + uint16_t rCode :4;//response/error code + }; + uint16_t value; + } flags; + uint16_t questions; //QDCOUNT + uint16_t answers; //ANCOUNT + uint16_t servers; //NSCOUNT + uint16_t additional;//ARCOUNT +} mdns_header_t; + +typedef struct { + char host[MDNS_NAME_BUF_LEN]; + char service[MDNS_NAME_BUF_LEN]; + char proto[MDNS_NAME_BUF_LEN]; + char domain[MDNS_NAME_BUF_LEN]; + uint8_t parts; + uint8_t sub; +} mdns_name_t; + +typedef struct mdns_parsed_question_s { + struct mdns_parsed_question_s * next; + uint16_t type; + bool unicast; + char * host; + char * service; + char * proto; + char * domain; +} mdns_parsed_question_t; + +typedef struct mdns_parsed_record_s { + struct mdns_parsed_record_s * next; + mdns_parsed_recort_type_t record_type; + uint16_t type; + uint16_t clas; + uint8_t flush; + uint32_t ttl; + char * host; + char * service; + char * proto; + char * domain; + uint16_t data_len; + uint8_t *data; +} mdns_parsed_record_t; + +typedef struct { + tcpip_adapter_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + //struct udp_pcb *pcb; + ip_addr_t src; + uint16_t src_port; + uint8_t multicast; + uint8_t authoritative; + uint8_t probe; + uint8_t discovery; + uint8_t distributed; + mdns_parsed_question_t * questions; + mdns_parsed_record_t * records; +} mdns_parsed_packet_t; + +typedef struct { + tcpip_adapter_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + struct pbuf *pb; + ip_addr_t src; + ip_addr_t dest; + uint16_t src_port; + uint8_t multicast; +} mdns_rx_packet_t; + +typedef struct mdns_txt_linked_item_s { + const char * key; /*!< item key name */ + const char * value; /*!< item value string */ + struct mdns_txt_linked_item_s * next; /*!< next result, or NULL for the last result in the list */ +} mdns_txt_linked_item_t; + +typedef struct { + const char * instance; + const char * service; + const char * proto; + uint16_t priority; + uint16_t weight; + uint16_t port; + mdns_txt_linked_item_t * txt; +} mdns_service_t; + +typedef struct mdns_srv_item_s { + struct mdns_srv_item_s * next; + mdns_service_t * service; +} mdns_srv_item_t; + +typedef struct mdns_out_question_s { + struct mdns_out_question_s * next; + uint16_t type; + uint8_t unicast; + const char * host; + const char * service; + const char * proto; + const char * domain; +} mdns_out_question_t; + +typedef struct mdns_out_answer_s { + struct mdns_out_answer_s * next; + uint16_t type; + uint8_t bye; + uint8_t flush; + mdns_service_t * service; + const char * custom_instance; + const char * custom_service; + const char * custom_proto; +} mdns_out_answer_t; + +typedef struct mdns_tx_packet_s { + struct mdns_tx_packet_s * next; + uint32_t send_at; + tcpip_adapter_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + ip_addr_t dst; + uint16_t port; + uint16_t flags; + uint8_t distributed; + mdns_out_question_t * questions; + mdns_out_answer_t * answers; + mdns_out_answer_t * servers; + mdns_out_answer_t * additional; +} mdns_tx_packet_t; + +typedef struct { + mdns_pcb_state_t state; + struct udp_pcb * pcb; + mdns_srv_item_t ** probe_services; + uint8_t probe_services_len; + uint8_t probe_ip; + uint8_t probe_running; + uint16_t failed_probes; +} mdns_pcb_t; + +typedef enum { + SEARCH_OFF, + SEARCH_INIT, + SEARCH_RUNNING, + SEARCH_MAX +} mdns_search_once_state_t; + +typedef struct mdns_search_once_s { + struct mdns_search_once_s * next; + + mdns_search_once_state_t state; + uint32_t started_at; + uint32_t sent_at; + uint32_t timeout; + SemaphoreHandle_t lock; + uint16_t type; + uint8_t max_results; + uint8_t num_results; + char * instance; + char * service; + char * proto; + mdns_result_t * result; +} mdns_search_once_t; + +typedef struct mdns_server_s { + struct { + mdns_pcb_t pcbs[MDNS_IP_PROTOCOL_MAX]; + } interfaces[TCPIP_ADAPTER_IF_MAX]; + const char * hostname; + const char * instance; + mdns_srv_item_t * services; + SemaphoreHandle_t lock; + QueueHandle_t action_queue; + mdns_tx_packet_t * tx_queue_head; + mdns_search_once_t * search_once; + esp_timer_handle_t timer_handle; +} mdns_server_t; + +typedef struct { + mdns_action_type_t type; + union { + char * hostname; + char * instance; + struct { + system_event_id_t event_id; + tcpip_adapter_if_t interface; + } sys_event; + struct { + mdns_srv_item_t * service; + } srv_add; + struct { + mdns_srv_item_t * service; + } srv_del; + struct { + mdns_srv_item_t * service; + char * instance; + } srv_instance; + struct { + mdns_srv_item_t * service; + uint16_t port; + } srv_port; + struct { + mdns_srv_item_t * service; + uint8_t num_items; + mdns_txt_item_t * txt; + } srv_txt_replace; + struct { + mdns_srv_item_t * service; + char * key; + char * value; + } srv_txt_set; + struct { + mdns_srv_item_t * service; + char * key; + } srv_txt_del; + struct { + mdns_search_once_t * search; + } search_add; + struct { + mdns_tx_packet_t * packet; + } tx_handle; + struct { + mdns_rx_packet_t * packet; + } rx_handle; + } data; +} mdns_action_t; + +#endif /* MDNS_PRIVATE_H_ */ diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index 897e637d0..7b80cabf8 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -26,8 +26,11 @@ import os import re import struct import sys +import hashlib +import binascii MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature +MD5_PARTITION_BEGIN = b"\xEB\xEB" + b"\xFF" * 14 # The first 2 bytes are like magic numbers for MD5 sum __version__ = '1.0' @@ -112,6 +115,7 @@ class PartitionTable(list): @classmethod def from_binary(cls, b): + md5 = hashlib.md5(); result = cls() for o in range(0,len(b),32): data = b[o:o+32] @@ -119,11 +123,19 @@ class PartitionTable(list): raise InputError("Partition table length must be a multiple of 32 bytes") if data == b'\xFF'*32: return result # got end marker + if data[:2] == MD5_PARTITION_BEGIN[:2]: #check only the magic number part + if data[16:] == md5.digest(): + continue # the next iteration will check for the end marker + else: + raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:]))) + else: + md5.update(data) result.append(PartitionDefinition.from_binary(data)) raise InputError("Partition table is missing an end-of-table marker") def to_binary(self): result = b"".join(e.to_binary() for e in self) + result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest() if len(result )>= MAX_PARTITION_LENGTH: raise InputError("Binary partition table length (%d) longer than max" % len(result)) result += b"\xFF" * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing diff --git a/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py b/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py index 46fe45c22..4919a53d8 100755 --- a/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py +++ b/components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py @@ -37,6 +37,10 @@ LONGER_BINARY_TABLE += b"\xAA\x50\x10\x00" + \ b"\x00\x10\x00\x00" + \ b"second" + (b"\0"*10) + \ b"\x00\x00\x00\x00" +# MD5 checksum +LONGER_BINARY_TABLE += b"\xEB\xEB" + b"\xFF" * 14 +LONGER_BINARY_TABLE += b'\xf9\xbd\x06\x1b\x45\x68\x6f\x86\x57\x1a\x2c\xd5\x2a\x1d\xa6\x5b' +# empty partition LONGER_BINARY_TABLE += b"\xFF" * 32 @@ -168,12 +172,14 @@ first, 0x30, 0xEE, 0x100400, 0x300000 """ t = PartitionTable.from_csv(csv) tb = _strip_trailing_ffs(t.to_binary()) - self.assertEqual(len(tb), 64) + self.assertEqual(len(tb), 64+32) self.assertEqual(b'\xAA\x50', tb[0:2]) # magic self.assertEqual(b'\x30\xee', tb[2:4]) # type, subtype eo, es = struct.unpack("host, config, sizeof(*config)); const bool is_spi = host_is_spi(card); + if ( !is_spi ) { + //check HOST flags compatible with slot configuration. + int slot_bit_width = config->get_bus_width(config->slot); + if ( slot_bit_width == 1 && (config->flags & (SDMMC_HOST_FLAG_4BIT|SDMMC_HOST_FLAG_8BIT))) { + ESP_LOGW(TAG, "HOST slot only enables 1-bit."); + card->host.flags = ((card->host.flags&(~(SDMMC_HOST_FLAG_4BIT|SDMMC_HOST_FLAG_8BIT)))|SDMMC_HOST_FLAG_1BIT); + } else if ( slot_bit_width == 4 && (config->flags & SDMMC_HOST_FLAG_8BIT)){ + ESP_LOGW(TAG, "HOST slot only enables 4-bit."); + card->host.flags = ((card->host.flags&(~(SDMMC_HOST_FLAG_1BIT|SDMMC_HOST_FLAG_8BIT)))|SDMMC_HOST_FLAG_4BIT); + } + } /* GO_IDLE_STATE (CMD0) command resets the card */ esp_err_t err = sdmmc_send_cmd_go_idle_state(card); if (err != ESP_OK) { @@ -218,7 +229,7 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) /* If the host has been initialized with 4-bit bus support, and the card * supports 4-bit bus, switch to 4-bit bus now. */ - if ((config->flags & SDMMC_HOST_FLAG_4BIT) && + if ((card->host.flags & SDMMC_HOST_FLAG_4BIT) && (card->scr.bus_width & SCR_SD_BUS_WIDTHS_4BIT)) { ESP_LOGD(TAG, "switching to 4-bit bus mode"); err = sdmmc_send_cmd_set_bus_width(card, 4); diff --git a/components/sdmmc/test/test_sd.c b/components/sdmmc/test/test_sd.c index 3f2ce0134..0fbe6c5ae 100644 --- a/components/sdmmc/test/test_sd.c +++ b/components/sdmmc/test/test_sd.c @@ -36,7 +36,7 @@ TEST_CASE("MMC_RSP_BITS", "[sd]") TEST_ASSERT_EQUAL_HEX32(0x11, MMC_RSP_BITS(data, 59, 5)); } -TEST_CASE("can probe SD", "[sd][test_env=UT_T1_SDMODE][ignore]") +TEST_CASE("can probe SD (4-bit)", "[sd][test_env=UT_T1_SDMODE]") { sdmmc_host_t config = SDMMC_HOST_DEFAULT(); sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); @@ -50,8 +50,37 @@ TEST_CASE("can probe SD", "[sd][test_env=UT_T1_SDMODE][ignore]") free(card); } +TEST_CASE("can probe SD (1-bit)", "[sd][test_env=UT_T1_SDMODE]") +{ + //the card DAT3 should be connected to high in SD 1-bit mode + //do it by our own GPIO. + gpio_config_t conf = { + .pin_bit_mask = GPIO_SEL_13, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = 1, + .pull_down_en = 0, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&conf); + gpio_set_level(GPIO_NUM_13, 1); -TEST_CASE("can probe SD(using SPI)", "[sdspi][test_env=UT_T1_SPIMODE][ignore]") + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + config.flags = SDMMC_HOST_FLAG_1BIT; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.width=1; + TEST_ESP_OK(sdmmc_host_init()); + TEST_ESP_OK(sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config)); + sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t)); + TEST_ASSERT_NOT_NULL(card); + TEST_ESP_OK(sdmmc_card_init(&config, card)); + sdmmc_card_print_info(stdout, card); + TEST_ESP_OK(sdmmc_host_deinit()); + free(card); +} + + + +TEST_CASE("can probe SD(using SPI)", "[sdspi][test_env=UT_T1_SPIMODE]") { sdmmc_host_t config = SDSPI_HOST_DEFAULT(); sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT(); diff --git a/components/soc/esp32/include/soc/io_mux_reg.h b/components/soc/esp32/include/soc/io_mux_reg.h index 2598989d0..5516e5983 100644 --- a/components/soc/esp32/include/soc/io_mux_reg.h +++ b/components/soc/esp32/include/soc/io_mux_reg.h @@ -124,18 +124,21 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define CLK_OUT1_S 0 #define PERIPHS_IO_MUX_GPIO0_U (DR_REG_IO_MUX_BASE +0x44) +#define IO_MUX_GPIO0_REG PERIPHS_IO_MUX_GPIO0_U #define FUNC_GPIO0_EMAC_TX_CLK 5 #define FUNC_GPIO0_GPIO0 2 #define FUNC_GPIO0_CLK_OUT1 1 #define FUNC_GPIO0_GPIO0_0 0 #define PERIPHS_IO_MUX_U0TXD_U (DR_REG_IO_MUX_BASE +0x88) +#define IO_MUX_GPIO1_REG PERIPHS_IO_MUX_U0TXD_U #define FUNC_U0TXD_EMAC_RXD2 5 #define FUNC_U0TXD_GPIO1 2 #define FUNC_U0TXD_CLK_OUT3 1 #define FUNC_U0TXD_U0TXD 0 #define PERIPHS_IO_MUX_GPIO2_U (DR_REG_IO_MUX_BASE +0x40) +#define IO_MUX_GPIO2_REG PERIPHS_IO_MUX_GPIO2_U #define FUNC_GPIO2_SD_DATA0 4 #define FUNC_GPIO2_HS2_DATA0 3 #define FUNC_GPIO2_GPIO2 2 @@ -143,11 +146,13 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_GPIO2_GPIO2_0 0 #define PERIPHS_IO_MUX_U0RXD_U (DR_REG_IO_MUX_BASE +0x84) +#define IO_MUX_GPIO3_REG PERIPHS_IO_MUX_U0RXD_U #define FUNC_U0RXD_GPIO3 2 #define FUNC_U0RXD_CLK_OUT2 1 #define FUNC_U0RXD_U0RXD 0 #define PERIPHS_IO_MUX_GPIO4_U (DR_REG_IO_MUX_BASE +0x48) +#define IO_MUX_GPIO4_REG PERIPHS_IO_MUX_GPIO4_U #define FUNC_GPIO4_EMAC_TX_ER 5 #define FUNC_GPIO4_SD_DATA1 4 #define FUNC_GPIO4_HS2_DATA1 3 @@ -156,6 +161,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_GPIO4_GPIO4_0 0 #define PERIPHS_IO_MUX_GPIO5_U (DR_REG_IO_MUX_BASE +0x6c) +#define IO_MUX_GPIO5_REG PERIPHS_IO_MUX_GPIO5_U #define FUNC_GPIO5_EMAC_RX_CLK 5 #define FUNC_GPIO5_HS1_DATA6 3 #define FUNC_GPIO5_GPIO5 2 @@ -163,6 +169,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_GPIO5_GPIO5_0 0 #define PERIPHS_IO_MUX_SD_CLK_U (DR_REG_IO_MUX_BASE +0x60) +#define IO_MUX_GPIO6_REG PERIPHS_IO_MUX_SD_CLK_U #define FUNC_SD_CLK_U1CTS 4 #define FUNC_SD_CLK_HS1_CLK 3 #define FUNC_SD_CLK_GPIO6 2 @@ -170,6 +177,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_SD_CLK_SD_CLK 0 #define PERIPHS_IO_MUX_SD_DATA0_U (DR_REG_IO_MUX_BASE +0x64) +#define IO_MUX_GPIO7_REG PERIPHS_IO_MUX_SD_DATA0_U #define FUNC_SD_DATA0_U2RTS 4 #define FUNC_SD_DATA0_HS1_DATA0 3 #define FUNC_SD_DATA0_GPIO7 2 @@ -177,6 +185,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_SD_DATA0_SD_DATA0 0 #define PERIPHS_IO_MUX_SD_DATA1_U (DR_REG_IO_MUX_BASE +0x68) +#define IO_MUX_GPIO8_REG PERIPHS_IO_MUX_SD_DATA1_U #define FUNC_SD_DATA1_U2CTS 4 #define FUNC_SD_DATA1_HS1_DATA1 3 #define FUNC_SD_DATA1_GPIO8 2 @@ -184,6 +193,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_SD_DATA1_SD_DATA1 0 #define PERIPHS_IO_MUX_SD_DATA2_U (DR_REG_IO_MUX_BASE +0x54) +#define IO_MUX_GPIO9_REG PERIPHS_IO_MUX_SD_DATA2_U #define FUNC_SD_DATA2_U1RXD 4 #define FUNC_SD_DATA2_HS1_DATA2 3 #define FUNC_SD_DATA2_GPIO9 2 @@ -191,6 +201,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_SD_DATA2_SD_DATA2 0 #define PERIPHS_IO_MUX_SD_DATA3_U (DR_REG_IO_MUX_BASE +0x58) +#define IO_MUX_GPIO10_REG PERIPHS_IO_MUX_SD_DATA3_U #define FUNC_SD_DATA3_U1TXD 4 #define FUNC_SD_DATA3_HS1_DATA3 3 #define FUNC_SD_DATA3_GPIO10 2 @@ -198,6 +209,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_SD_DATA3_SD_DATA3 0 #define PERIPHS_IO_MUX_SD_CMD_U (DR_REG_IO_MUX_BASE +0x5c) +#define IO_MUX_GPIO11_REG PERIPHS_IO_MUX_SD_CMD_U #define FUNC_SD_CMD_U1RTS 4 #define FUNC_SD_CMD_HS1_CMD 3 #define FUNC_SD_CMD_GPIO11 2 @@ -205,6 +217,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_SD_CMD_SD_CMD 0 #define PERIPHS_IO_MUX_MTDI_U (DR_REG_IO_MUX_BASE +0x34) +#define IO_MUX_GPIO12_REG PERIPHS_IO_MUX_MTDI_U #define FUNC_MTDI_EMAC_TXD3 5 #define FUNC_MTDI_SD_DATA2 4 #define FUNC_MTDI_HS2_DATA2 3 @@ -213,6 +226,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_MTDI_MTDI 0 #define PERIPHS_IO_MUX_MTCK_U (DR_REG_IO_MUX_BASE +0x38) +#define IO_MUX_GPIO13_REG PERIPHS_IO_MUX_MTCK_U #define FUNC_MTCK_EMAC_RX_ER 5 #define FUNC_MTCK_SD_DATA3 4 #define FUNC_MTCK_HS2_DATA3 3 @@ -221,6 +235,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_MTCK_MTCK 0 #define PERIPHS_IO_MUX_MTMS_U (DR_REG_IO_MUX_BASE +0x30) +#define IO_MUX_GPIO14_REG PERIPHS_IO_MUX_MTMS_U #define FUNC_MTMS_EMAC_TXD2 5 #define FUNC_MTMS_SD_CLK 4 #define FUNC_MTMS_HS2_CLK 3 @@ -229,6 +244,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_MTMS_MTMS 0 #define PERIPHS_IO_MUX_MTDO_U (DR_REG_IO_MUX_BASE +0x3c) +#define IO_MUX_GPIO15_REG PERIPHS_IO_MUX_MTDO_U #define FUNC_MTDO_EMAC_RXD3 5 #define FUNC_MTDO_SD_CMD 4 #define FUNC_MTDO_HS2_CMD 3 @@ -237,6 +253,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_MTDO_MTDO 0 #define PERIPHS_IO_MUX_GPIO16_U (DR_REG_IO_MUX_BASE +0x4c) +#define IO_MUX_GPIO16_REG PERIPHS_IO_MUX_GPIO16_U #define FUNC_GPIO16_EMAC_CLK_OUT 5 #define FUNC_GPIO16_U2RXD 4 #define FUNC_GPIO16_HS1_DATA4 3 @@ -244,6 +261,7 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_GPIO16_GPIO16_0 0 #define PERIPHS_IO_MUX_GPIO17_U (DR_REG_IO_MUX_BASE +0x50) +#define IO_MUX_GPIO17_REG PERIPHS_IO_MUX_GPIO17_U #define FUNC_GPIO17_EMAC_CLK_OUT_180 5 #define FUNC_GPIO17_U2TXD 4 #define FUNC_GPIO17_HS1_DATA5 3 @@ -251,12 +269,14 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_GPIO17_GPIO17_0 0 #define PERIPHS_IO_MUX_GPIO18_U (DR_REG_IO_MUX_BASE +0x70) +#define IO_MUX_GPIO18_REG PERIPHS_IO_MUX_GPIO18_U #define FUNC_GPIO18_HS1_DATA7 3 #define FUNC_GPIO18_GPIO18 2 #define FUNC_GPIO18_VSPICLK 1 #define FUNC_GPIO18_GPIO18_0 0 #define PERIPHS_IO_MUX_GPIO19_U (DR_REG_IO_MUX_BASE +0x74) +#define IO_MUX_GPIO19_REG PERIPHS_IO_MUX_GPIO19_U #define FUNC_GPIO19_EMAC_TXD0 5 #define FUNC_GPIO19_U0CTS 3 #define FUNC_GPIO19_GPIO19 2 @@ -264,16 +284,19 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_GPIO19_GPIO19_0 0 #define PERIPHS_IO_MUX_GPIO20_U (DR_REG_IO_MUX_BASE +0x78) +#define IO_MUX_GPIO20_REG PERIPHS_IO_MUX_GPIO20_U #define FUNC_GPIO20_GPIO20 2 #define FUNC_GPIO20_GPIO20_0 0 #define PERIPHS_IO_MUX_GPIO21_U (DR_REG_IO_MUX_BASE +0x7c) +#define IO_MUX_GPIO21_REG PERIPHS_IO_MUX_GPIO21_U #define FUNC_GPIO21_EMAC_TX_EN 5 #define FUNC_GPIO21_GPIO21 2 #define FUNC_GPIO21_VSPIHD 1 #define FUNC_GPIO21_GPIO21_0 0 #define PERIPHS_IO_MUX_GPIO22_U (DR_REG_IO_MUX_BASE +0x80) +#define IO_MUX_GPIO22_REG PERIPHS_IO_MUX_GPIO22_U #define FUNC_GPIO22_EMAC_TXD1 5 #define FUNC_GPIO22_U0RTS 3 #define FUNC_GPIO22_GPIO22 2 @@ -281,59 +304,72 @@ static inline void __attribute__ ((deprecated)) PIN_PULLDWN_EN(uint32_t PIN_NAME #define FUNC_GPIO22_GPIO22_0 0 #define PERIPHS_IO_MUX_GPIO23_U (DR_REG_IO_MUX_BASE +0x8c) +#define IO_MUX_GPIO23_REG PERIPHS_IO_MUX_GPIO23_U #define FUNC_GPIO23_HS1_STROBE 3 #define FUNC_GPIO23_GPIO23 2 #define FUNC_GPIO23_VSPID 1 #define FUNC_GPIO23_GPIO23_0 0 #define PERIPHS_IO_MUX_GPIO24_U (DR_REG_IO_MUX_BASE +0x90) +#define IO_MUX_GPIO24_REG PERIPHS_IO_MUX_GPIO24_U #define FUNC_GPIO24_GPIO24 2 #define FUNC_GPIO24_GPIO24_0 0 #define PERIPHS_IO_MUX_GPIO25_U (DR_REG_IO_MUX_BASE +0x24) +#define IO_MUX_GPIO25_REG PERIPHS_IO_MUX_GPIO25_U #define FUNC_GPIO25_EMAC_RXD0 5 #define FUNC_GPIO25_GPIO25 2 #define FUNC_GPIO25_GPIO25_0 0 #define PERIPHS_IO_MUX_GPIO26_U (DR_REG_IO_MUX_BASE +0x28) +#define IO_MUX_GPIO26_REG PERIPHS_IO_MUX_GPIO26_U #define FUNC_GPIO26_EMAC_RXD1 5 #define FUNC_GPIO26_GPIO26 2 #define FUNC_GPIO26_GPIO26_0 0 #define PERIPHS_IO_MUX_GPIO27_U (DR_REG_IO_MUX_BASE +0x2c) +#define IO_MUX_GPIO27_REG PERIPHS_IO_MUX_GPIO27_U #define FUNC_GPIO27_EMAC_RX_DV 5 #define FUNC_GPIO27_GPIO27 2 #define FUNC_GPIO27_GPIO27_0 0 #define PERIPHS_IO_MUX_GPIO32_U (DR_REG_IO_MUX_BASE +0x1c) +#define IO_MUX_GPIO32_REG PERIPHS_IO_MUX_GPIO32_U #define FUNC_GPIO32_GPIO32 2 #define FUNC_GPIO32_GPIO32_0 0 #define PERIPHS_IO_MUX_GPIO33_U (DR_REG_IO_MUX_BASE +0x20) +#define IO_MUX_GPIO33_REG PERIPHS_IO_MUX_GPIO33_U #define FUNC_GPIO33_GPIO33 2 #define FUNC_GPIO33_GPIO33_0 0 #define PERIPHS_IO_MUX_GPIO34_U (DR_REG_IO_MUX_BASE +0x14) +#define IO_MUX_GPIO34_REG PERIPHS_IO_MUX_GPIO34_U #define FUNC_GPIO34_GPIO34 2 #define FUNC_GPIO34_GPIO34_0 0 #define PERIPHS_IO_MUX_GPIO35_U (DR_REG_IO_MUX_BASE +0x18) +#define IO_MUX_GPIO35_REG PERIPHS_IO_MUX_GPIO35_U #define FUNC_GPIO35_GPIO35 2 #define FUNC_GPIO35_GPIO35_0 0 #define PERIPHS_IO_MUX_GPIO36_U (DR_REG_IO_MUX_BASE +0x04) +#define IO_MUX_GPIO36_REG PERIPHS_IO_MUX_GPIO36_U #define FUNC_GPIO36_GPIO36 2 #define FUNC_GPIO36_GPIO36_0 0 #define PERIPHS_IO_MUX_GPIO37_U (DR_REG_IO_MUX_BASE +0x08) +#define IO_MUX_GPIO37_REG PERIPHS_IO_MUX_GPIO37_U #define FUNC_GPIO37_GPIO37 2 #define FUNC_GPIO37_GPIO37_0 0 #define PERIPHS_IO_MUX_GPIO38_U (DR_REG_IO_MUX_BASE +0x0c) +#define IO_MUX_GPIO38_REG PERIPHS_IO_MUX_GPIO38_U #define FUNC_GPIO38_GPIO38 2 #define FUNC_GPIO38_GPIO38_0 0 #define PERIPHS_IO_MUX_GPIO39_U (DR_REG_IO_MUX_BASE +0x10) +#define IO_MUX_GPIO39_REG PERIPHS_IO_MUX_GPIO39_U #define FUNC_GPIO39_GPIO39 2 #define FUNC_GPIO39_GPIO39_0 0 diff --git a/components/soc/esp32/include/soc/sens_reg.h b/components/soc/esp32/include/soc/sens_reg.h index adc971905..34834376e 100644 --- a/components/soc/esp32/include/soc/sens_reg.h +++ b/components/soc/esp32/include/soc/sens_reg.h @@ -96,6 +96,7 @@ #define SENS_FORCE_XPD_SAR_M ((SENS_FORCE_XPD_SAR_V)<<(SENS_FORCE_XPD_SAR_S)) #define SENS_FORCE_XPD_SAR_V 0x3 #define SENS_FORCE_XPD_SAR_S 18 +#define SENS_FORCE_XPD_SAR_SW_M (BIT1) #define SENS_FORCE_XPD_SAR_FSM 0 // Use FSM to control power down #define SENS_FORCE_XPD_SAR_PD 2 // Force power down #define SENS_FORCE_XPD_SAR_PU 3 // Force power up diff --git a/components/soc/esp32/include/soc/soc.h b/components/soc/esp32/include/soc/soc.h index 7c7517d46..660abbdb3 100644 --- a/components/soc/esp32/include/soc/soc.h +++ b/components/soc/esp32/include/soc/soc.h @@ -308,7 +308,6 @@ #define SOC_MEM_INTERNAL_LOW 0x3FF90000 #define SOC_MEM_INTERNAL_HIGH 0x400C2000 - //Interrupt hardware source table //This table is decided by hardware, don't touch this. #define ETS_WIFI_MAC_INTR_SOURCE 0/**< interrupt of WiFi MAC, level*/ diff --git a/components/soc/esp32/include/soc/uart_reg.h b/components/soc/esp32/include/soc/uart_reg.h index 9d5ba2fb1..33b6e998e 100644 --- a/components/soc/esp32/include/soc/uart_reg.h +++ b/components/soc/esp32/include/soc/uart_reg.h @@ -1105,11 +1105,24 @@ #define UART_MEM_RX_STATUS_REG(i) (REG_UART_BASE(i) + 0x60) /* UART_MEM_RX_STATUS : RO ;bitpos:[23:0] ;default: 24'h0 ; */ -/*description: */ +/*description: This register stores the current uart rx mem read address + and rx mem write address */ #define UART_MEM_RX_STATUS 0x00FFFFFF #define UART_MEM_RX_STATUS_M ((UART_MEM_RX_STATUS_V)<<(UART_MEM_RX_STATUS_S)) #define UART_MEM_RX_STATUS_V 0xFFFFFF #define UART_MEM_RX_STATUS_S 0 +/* UART_MEM_RX_RD_ADDR : RO ;bitpos:[12:2] ;default: 11'h0 ; */ +/*description: This register stores the rx mem read address */ +#define UART_MEM_RX_RD_ADDR 0x000007FF +#define UART_MEM_RX_RD_ADDR_M ((UART_MEM_RX_RD_ADDR_V)<<(UART_MEM_RX_RD_ADDR_S)) +#define UART_MEM_RX_RD_ADDR_V (0x7FF) +#define UART_MEM_RX_RD_ADDR_S (2) +/* UART_MEM_RX_WR_ADDR : RO ;bitpos:[23:13] ;default: 11'h0 ; */ +/*description: This register stores the rx mem write address */ +#define UART_MEM_RX_WR_ADDR 0x000007FF +#define UART_MEM_RX_WR_ADDR_M ((UART_MEM_RX_WR_ADDR_V)<<(UART_MEM_RX_WR_ADDR_S)) +#define UART_MEM_RX_WR_ADDR_V (0x7FF) +#define UART_MEM_RX_WR_ADDR_S (13) #define UART_MEM_CNT_STATUS_REG(i) (REG_UART_BASE(i) + 0x64) /* UART_TX_MEM_CNT : RO ;bitpos:[5:3] ;default: 3'b0 ; */ diff --git a/components/soc/esp32/include/soc/uart_struct.h b/components/soc/esp32/include/soc/uart_struct.h index eb14620aa..59b84d2af 100644 --- a/components/soc/esp32/include/soc/uart_struct.h +++ b/components/soc/esp32/include/soc/uart_struct.h @@ -332,8 +332,14 @@ typedef volatile struct { } mem_tx_status; union { struct { - uint32_t status:24; - uint32_t reserved24: 8; + uint32_t status: 24; + uint32_t reserved24: 8; + }; + struct { + uint32_t reserved0: 2; + uint32_t rd_addr: 11; /*This register stores the rx mem read address.*/ + uint32_t wr_addr: 11; /*This register stores the rx mem write address.*/ + uint32_t reserved: 8; }; uint32_t val; } mem_rx_status; diff --git a/components/soc/esp32/rtc_clk.c b/components/soc/esp32/rtc_clk.c index 7c8d9609a..54dea84c5 100644 --- a/components/soc/esp32/rtc_clk.c +++ b/components/soc/esp32/rtc_clk.c @@ -589,7 +589,7 @@ static rtc_xtal_freq_t rtc_clk_xtal_freq_estimate() * (shifted by RTC_CLK_CAL_FRACT bits). * Xtal frequency will be (cal_val * 8M / 256) / 2^19 */ - uint32_t freq_mhz = (cal_val * (RTC_FAST_CLK_FREQ_APPROX / MHZ) / 256 ) >> RTC_CLK_CAL_FRACT; + uint32_t freq_mhz = (cal_val * RTC_FAST_CLK_FREQ_APPROX / MHZ / 256 ) >> RTC_CLK_CAL_FRACT; /* Guess the XTAL type. For now, only 40 and 26MHz are supported. */ switch (freq_mhz) { diff --git a/components/soc/include/soc/soc_memory_layout.h b/components/soc/include/soc/soc_memory_layout.h index 1c1415e3d..6273b1dbc 100644 --- a/components/soc/include/soc/soc_memory_layout.h +++ b/components/soc/include/soc/soc_memory_layout.h @@ -89,3 +89,8 @@ inline static bool IRAM_ATTR esp_ptr_internal(const void *p) { r |= ((intptr_t)p >= SOC_RTC_DATA_LOW && (intptr_t)p < SOC_RTC_DATA_HIGH); return r; } + + +inline static bool IRAM_ATTR esp_ptr_external_ram(const void *p) { + return ((intptr_t)p >= SOC_EXTRAM_DATA_LOW && (intptr_t)p < SOC_EXTRAM_DATA_HIGH); +} diff --git a/components/spiffs/Kconfig b/components/spiffs/Kconfig index 1f7a196d7..697f2b68b 100644 --- a/components/spiffs/Kconfig +++ b/components/spiffs/Kconfig @@ -5,16 +5,14 @@ config SPIFFS_MAX_PARTITIONS default 3 range 1 10 help - Define maximum number of partitions - that can be mounted. + Define maximum number of partitions that can be mounted. menu "SPIFFS Cache Configuration" config SPIFFS_CACHE bool "Enable SPIFFS Cache" default "y" help - Enables/disable memory read - caching of nucleus file system + Enables/disable memory read caching of nucleus file system operations. config SPIFFS_CACHE_WR @@ -22,16 +20,14 @@ config SPIFFS_CACHE_WR default "y" depends on SPIFFS_CACHE help - Enables memory write caching for - file descriptors in hydrogen. + Enables memory write caching for file descriptors in hydrogen. config SPIFFS_CACHE_STATS bool "Enable SPIFFS Cache Statistics" default "n" depends on SPIFFS_CACHE help - Enable/disable statistics on caching. - Debug/test purpose only. + Enable/disable statistics on caching. Debug/test purpose only. endmenu @@ -39,44 +35,54 @@ config SPIFFS_PAGE_CHECK bool "Enable SPIFFS Page Check" default "y" help - Always check header of each - accessed page to ensure consistent state. - If enabled it will increase number - of reads, will increase flash. + Always check header of each accessed page to ensure consistent state. + If enabled it will increase number of reads from flash, especially + if cache is disabled. config SPIFFS_GC_MAX_RUNS int "Set Maximum GC Runs" default 10 range 1 255 help - Define maximum number of gc runs to - perform to reach desired free pages. + Define maximum number of GC runs to perform to reach desired free pages. config SPIFFS_GC_STATS bool "Enable SPIFFS GC Statistics" default "n" help - Enable/disable statistics on gc. - Debug/test purpose only. + Enable/disable statistics on gc. Debug/test purpose only. + +config SPIFFS_PAGE_SIZE + int "SPIFFS logical page size" + default 256 + range 256 1024 + help + Logical page size of SPIFFS partition, in bytes. Must be multiple + of flash page size (which is usually 256 bytes). + Larger page sizes reduce overhead when storing large files, and + improve filesystem performance when reading large files. + Smaller page sizes reduce overhead when storing small (< page size) + files. config SPIFFS_OBJ_NAME_LEN int "Set SPIFFS Maximum Name Length" default 32 range 1 256 help - Object name maximum length. Note that this length - include the zero-termination character, - meaning maximum string of characters can at most be - SPIFFS_OBJ_NAME_LEN - 1. + Object name maximum length. Note that this length include the + zero-termination character, meaning maximum string of characters + can at most be SPIFFS_OBJ_NAME_LEN - 1. + + SPIFFS_OBJ_NAME_LEN + SPIFFS_META_LENGTH should not exceed + SPIFFS_PAGE_SIZE - 64. config SPIFFS_USE_MAGIC bool "Enable SPIFFS Filesystem Magic" default "y" help Enable this to have an identifiable spiffs filesystem. - This will look for a magic in all sectors - to determine if this is a valid spiffs system - or not on mount point. + This will look for a magic in all sectors to determine if this + is a valid spiffs system or not at mount time. config SPIFFS_USE_MAGIC_LENGTH bool "Enable SPIFFS Filesystem Length Magic" @@ -96,6 +102,9 @@ config SPIFFS_META_LENGTH These bytes can be used in an application-specific manner. Set this to at least 4 bytes to enable support for saving file modification time. + + SPIFFS_OBJ_NAME_LEN + SPIFFS_META_LENGTH should not exceed + SPIFFS_PAGE_SIZE - 64. config SPIFFS_USE_MTIME bool "Save file modification time" @@ -113,45 +122,39 @@ config SPIFFS_DBG bool "Enable general SPIFFS debug" default "n" help - Enabling this option will print - general debug mesages to the console + Enabling this option will print general debug mesages to the console. config SPIFFS_API_DBG bool "Enable SPIFFS API debug" default "n" help - Enabling this option will print - API debug mesages to the console + Enabling this option will print API debug mesages to the console. config SPIFFS_GC_DBG bool "Enable SPIFFS Garbage Cleaner debug" default "n" help - Enabling this option will print - GC debug mesages to the console + Enabling this option will print GC debug mesages to the console. config SPIFFS_CACHE_DBG bool "Enable SPIFFS Cache debug" default "n" depends on SPIFFS_CACHE help - Enabling this option will print - Cache debug mesages to the console + Enabling this option will print cache debug mesages to the console. config SPIFFS_CHECK_DBG bool "Enable SPIFFS Filesystem Check debug" default "n" help - Enabling this option will print - Filesystem Check debug mesages - to the console + Enabling this option will print Filesystem Check debug mesages + to the console. config SPIFFS_TEST_VISUALISATION bool "Enable SPIFFS Filesystem Visualization" default "n" help - Enable this option to enable SPIFFS_vis function - in the api. + Enable this option to enable SPIFFS_vis function in the API. endmenu diff --git a/components/spiffs/esp_spiffs.c b/components/spiffs/esp_spiffs.c index 2c454e9dd..c344e5776 100644 --- a/components/spiffs/esp_spiffs.c +++ b/components/spiffs/esp_spiffs.c @@ -222,6 +222,14 @@ static esp_err_t esp_spiffs_init(const esp_vfs_spiffs_conf_t* conf) return ESP_ERR_INVALID_STATE; } + uint32_t flash_page_size = g_rom_flashchip.page_size; + uint32_t log_page_size = CONFIG_SPIFFS_PAGE_SIZE; + if (log_page_size % flash_page_size != 0) { + ESP_LOGE(TAG, "SPIFFS_PAGE_SIZE is not multiple of flash chip page size (%d)", + flash_page_size); + return ESP_ERR_INVALID_ARG; + } + esp_partition_subtype_t subtype = conf->partition_label ? ESP_PARTITION_SUBTYPE_ANY : ESP_PARTITION_SUBTYPE_DATA_SPIFFS; const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, @@ -247,7 +255,7 @@ static esp_err_t esp_spiffs_init(const esp_vfs_spiffs_conf_t* conf) efs->cfg.hal_read_f = spiffs_api_read; efs->cfg.hal_write_f = spiffs_api_write; efs->cfg.log_block_size = g_rom_flashchip.sector_size; - efs->cfg.log_page_size = g_rom_flashchip.page_size; + efs->cfg.log_page_size = log_page_size; efs->cfg.phys_addr = 0; efs->cfg.phys_erase_block = g_rom_flashchip.sector_size; efs->cfg.phys_size = partition->size; @@ -349,8 +357,12 @@ esp_err_t esp_spiffs_info(const char* partition_label, size_t *total_bytes, size esp_err_t esp_spiffs_format(const char* partition_label) { - bool mount_on_success = false; + bool partition_was_mounted = false; int index; + /* If the partition is not mounted, need to create SPIFFS structures + * and mount the partition, unmount, format, delete SPIFFS structures. + * See SPIFFS wiki for the reason why. + */ esp_err_t err = esp_spiffs_by_label(partition_label, &index); if (err != ESP_OK) { esp_vfs_spiffs_conf_t conf = { @@ -363,23 +375,28 @@ esp_err_t esp_spiffs_format(const char* partition_label) return err; } err = esp_spiffs_by_label(partition_label, &index); - if (err != ESP_OK) { - return err; - } - esp_spiffs_free(&_efs[index]); - return ESP_OK; + assert(err == ESP_OK && "failed to get index of the partition just mounted"); } else if (SPIFFS_mounted(_efs[index]->fs)) { - SPIFFS_unmount(_efs[index]->fs); - mount_on_success = true; + partition_was_mounted = true; } + + SPIFFS_unmount(_efs[index]->fs); + s32_t res = SPIFFS_format(_efs[index]->fs); if (res != SPIFFS_OK) { ESP_LOGE(TAG, "format failed, %i", SPIFFS_errno(_efs[index]->fs)); SPIFFS_clearerr(_efs[index]->fs); + /* If the partition was previously mounted, but format failed, don't + * try to mount the partition back (it will probably fail). On the + * other hand, if it was not mounted, need to clean up. + */ + if (!partition_was_mounted) { + esp_spiffs_free(&_efs[index]); + } return ESP_FAIL; } - if (mount_on_success) { + if (partition_was_mounted) { res = SPIFFS_mount(_efs[index]->fs, &_efs[index]->cfg, _efs[index]->work, _efs[index]->fds, _efs[index]->fds_sz, _efs[index]->cache, _efs[index]->cache_sz, spiffs_api_check); @@ -388,6 +405,8 @@ esp_err_t esp_spiffs_format(const char* partition_label) SPIFFS_clearerr(_efs[index]->fs); return ESP_FAIL; } + } else { + esp_spiffs_free(&_efs[index]); } return ESP_OK; } diff --git a/components/spiffs/include/spiffs_config.h b/components/spiffs/include/spiffs_config.h index 28414facf..a382ba6f9 100755 --- a/components/spiffs/include/spiffs_config.h +++ b/components/spiffs/include/spiffs_config.h @@ -153,12 +153,15 @@ extern void spiffs_api_unlock(struct spiffs_t *fs); // changes the on-disk format, so the change is not backward-compatible. // // Do note: the meta length must never exceed -// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64) +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + SPIFFS_PAGE_EXTRA_SIZE) // // This is derived from following: // logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) + // spiffs_object_ix_header fields + at least some LUT entries) #define SPIFFS_OBJ_META_LEN (CONFIG_SPIFFS_META_LENGTH) +#define SPIFFS_PAGE_EXTRA_SIZE (64) +_Static_assert(SPIFFS_OBJ_META_LEN + SPIFFS_OBJ_NAME_LEN + SPIFFS_PAGE_EXTRA_SIZE + <= CONFIG_SPIFFS_PAGE_SIZE, "SPIFFS_OBJ_META_LEN or SPIFFS_OBJ_NAME_LEN too long"); // Size of buffer allocated on stack used when copying data. // Lower value generates more read/writes. No meaning having it bigger diff --git a/components/spiffs/test/test_spiffs.c b/components/spiffs/test/test_spiffs.c index b428de4ce..fdd3d241a 100644 --- a/components/spiffs/test/test_spiffs.c +++ b/components/spiffs/test/test_spiffs.c @@ -407,7 +407,7 @@ static void test_teardown() TEST_ESP_OK(esp_vfs_spiffs_unregister(spiffs_test_partition_label)); } -TEST_CASE("can format partition", "[spiffs]") +TEST_CASE("can initialize SPIFFS in erased partition", "[spiffs]") { const esp_partition_t* part = get_test_data_partition(); TEST_ASSERT_NOT_NULL(part); @@ -420,6 +420,44 @@ TEST_CASE("can format partition", "[spiffs]") test_teardown(); } +TEST_CASE("can format mounted partition", "[spiffs]") +{ + // Mount SPIFFS, create file, format, check that the file does not exist. + const esp_partition_t* part = get_test_data_partition(); + TEST_ASSERT_NOT_NULL(part); + test_setup(); + const char* filename = "/spiffs/hello.txt"; + test_spiffs_create_file_with_text(filename, spiffs_test_hello_str); + esp_spiffs_format(part->label); + FILE* f = fopen(filename, "r"); + TEST_ASSERT_NULL(f); + test_teardown(); +} + +TEST_CASE("can format unmounted partition", "[spiffs]") +{ + // Mount SPIFFS, create file, unmount. Format. Mount again, check that + // the file does not exist. + const esp_partition_t* part = get_test_data_partition(); + TEST_ASSERT_NOT_NULL(part); + test_setup(); + const char* filename = "/spiffs/hello.txt"; + test_spiffs_create_file_with_text(filename, spiffs_test_hello_str); + test_teardown(); + esp_spiffs_format(part->label); + // Don't use test_setup here, need to mount without formatting + esp_vfs_spiffs_conf_t conf = { + .base_path = "/spiffs", + .partition_label = spiffs_test_partition_label, + .max_files = 5, + .format_if_mount_failed = false + }; + TEST_ESP_OK(esp_vfs_spiffs_register(&conf)); + FILE* f = fopen(filename, "r"); + TEST_ASSERT_NULL(f); + test_teardown(); +} + TEST_CASE("can create and write file", "[spiffs]") { test_setup(); diff --git a/components/tcpip_adapter/include/tcpip_adapter.h b/components/tcpip_adapter/include/tcpip_adapter.h index d7ee00ea0..4777f1605 100644 --- a/components/tcpip_adapter/include/tcpip_adapter.h +++ b/components/tcpip_adapter/include/tcpip_adapter.h @@ -184,10 +184,10 @@ typedef struct tcpip_adapter_dns_param_s { msg.data = (void*)(_data);\ msg.api_fn = (_fn);\ if (TCPIP_ADAPTER_IPC_REMOTE == tcpip_adapter_ipc_check(&msg)) {\ - ESP_LOGD(TAG, "check: remote, if=%d fn=%p\n", (_if), (_fn));\ + ESP_LOGV(TAG, "check: remote, if=%d fn=%p\n", (_if), (_fn));\ return msg.ret;\ } else {\ - ESP_LOGD(TAG, "check: local, if=%d fn=%p\n", (_if), (_fn));\ + ESP_LOGV(TAG, "check: local, if=%d fn=%p\n", (_if), (_fn));\ }\ }while(0) @@ -603,6 +603,18 @@ esp_err_t tcpip_adapter_set_hostname(tcpip_adapter_if_t tcpip_if, const char *ho */ esp_err_t tcpip_adapter_get_hostname(tcpip_adapter_if_t tcpip_if, const char **hostname); +/** + * @brief Get the LwIP netif* that is assigned to the interface + * + * @param[in] tcpip_if: the interface which we will get the hostname + * @param[out] void ** netif: pointer to fill the resulting interface + * + * @return ESP_OK:success + * ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY:interface status error + * ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS:parameter error + */ +esp_err_t tcpip_adapter_get_netif(tcpip_adapter_if_t tcpip_if, void ** netif); + #ifdef __cplusplus } #endif diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index 97511cb9d..313e9b1d5 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -67,7 +67,7 @@ static sys_sem_t api_sync_sem = NULL; static bool tcpip_inited = false; static sys_sem_t api_lock_sem = NULL; extern sys_thread_t g_lwip_task; -#define TAG "tcpip_adapter" +static const char* TAG = "tcpip_adapter"; static void tcpip_adapter_api_cb(void* api_msg) { @@ -79,7 +79,7 @@ static void tcpip_adapter_api_cb(void* api_msg) } msg->ret = msg->api_fn(msg); - ESP_LOGD(TAG, "call api in lwip: ret=0x%x, give sem", msg->ret); + ESP_LOGV(TAG, "call api in lwip: ret=0x%x, give sem", msg->ret); sys_sem_signal(&api_sync_sem); return; @@ -1181,4 +1181,18 @@ static esp_err_t tcpip_adapter_reset_ip_info(tcpip_adapter_if_t tcpip_if) return ESP_OK; } +esp_err_t tcpip_adapter_get_netif(tcpip_adapter_if_t tcpip_if, void ** netif) +{ + if (tcpip_if >= TCPIP_ADAPTER_IF_MAX) { + return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; + } + + *netif = esp_netif[tcpip_if]; + + if (*netif == NULL) { + return ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY; + } + return ESP_OK; +} + #endif /* CONFIG_TCPIP_LWIP */ diff --git a/docs/_static/esp32-devkitc-dimensions-back.jpg b/docs/_static/esp32-devkitc-dimensions-back.jpg new file mode 100644 index 000000000..a19fdce2a Binary files /dev/null and b/docs/_static/esp32-devkitc-dimensions-back.jpg differ diff --git a/docs/_static/esp32-devkitc-functional-overview.jpg b/docs/_static/esp32-devkitc-functional-overview.jpg new file mode 100644 index 000000000..0aec63513 Binary files /dev/null and b/docs/_static/esp32-devkitc-functional-overview.jpg differ diff --git a/docs/_static/esp32-devkitc-functional-overview.png b/docs/_static/esp32-devkitc-v2-functional-overview.png similarity index 100% rename from docs/_static/esp32-devkitc-functional-overview.png rename to docs/_static/esp32-devkitc-v2-functional-overview.png diff --git a/docs/api-guides/general-notes.rst b/docs/api-guides/general-notes.rst index 7bbe616f9..f3f934968 100644 --- a/docs/api-guides/general-notes.rst +++ b/docs/api-guides/general-notes.rst @@ -121,6 +121,56 @@ RTC slow memory Global and static variables used by code which runs from RTC memory (i.e. deep sleep stub code) must be placed into RTC slow memory. Please check detailed description in :doc:`deep sleep ` documentation. +DMA Capable Requirement +----------------------- +Most DMA controllers (e.g. SPI, sdmmc, etc.) have requirements that sending/receiving buffers should be placed in DRAM +and word-aligned. We suggest to place DMA buffers in static variables rather than in the stack. Use macro ``DMA_ATTR`` +to declare global/local static variables like:: + + DMA_ATTR uint8_t buffer[]="I want to send something"; + void app_main() + { + // initialization code... + spi_transaction_t temp = { + .tx_buffer = buffer, + .length = 8*sizeof(buffer), + }; + spi_device_transmit( spi, &temp ); + // other stuff + } +Or:: + + void app_main() + { + DMA_ATTR static uint8_t buffer[]="I want to send something"; + // initialization code... + spi_transaction_t temp = { + .tx_buffer = buffer, + .length = 8*sizeof(buffer), + }; + spi_device_transmit( spi, &temp ); + // other stuff + } + +Placing DMA buffers in the stack is still allowed, though you have to keep in mind: + +1. Never try to do this if the stack is in the pSRAM. If the stack of a task is placed in the pSRAM, several steps have + to be taken as described in :doc:`external-ram` (at least ``SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY`` option enabled in + the menuconfig). Make sure your task is not in the pSRAM. +2. Use macro ``WORD_ALIGNED_ATTR`` in functions before variables to place them in proper positions like:: + + void app_main() + { + uint8_t stuff; + WORD_ALIGNED_ATTR uint8_t buffer[]="I want to send something"; //or the buffer will be placed right after stuff. + // initialization code... + spi_transaction_t temp = { + .tx_buffer = buffer, + .length = 8*sizeof(buffer), + }; + spi_device_transmit( spi, &temp ); + // other stuff + } diff --git a/docs/api-guides/index.rst b/docs/api-guides/index.rst index 662ab0e79..fcacd187d 100644 --- a/docs/api-guides/index.rst +++ b/docs/api-guides/index.rst @@ -10,6 +10,7 @@ API Guides ESP32 Core Dump Flash Encryption <../security/flash-encryption> FreeRTOS SMP Changes + Thread Local Storage High Level Interrupts JTAG Debugging Partition Tables diff --git a/docs/api-guides/partition-tables.rst b/docs/api-guides/partition-tables.rst index 6a5c67669..ee8863123 100644 --- a/docs/api-guides/partition-tables.rst +++ b/docs/api-guides/partition-tables.rst @@ -6,7 +6,7 @@ Overview A single ESP32's flash can contain multiple apps, as well as many different kinds of data (calibration data, filesystems, parameter storage, etc). For this reason a partition table is flashed to offset 0x8000 in the flash. -Partition table length is 0xC00 bytes (maximum 95 partition table entries). If the partition table is signed due to `secure boot`, the signature is appended after the table data. +Partition table length is 0xC00 bytes (maximum 95 partition table entries). An MD5 checksum is appended after the table data. If the partition table is signed due to `secure boot`, the signature is appended after the partition table. Each entry in the partition table has a name (label), type (app, data, or something else), subtype and the offset in flash where the partition is loaded. @@ -148,6 +148,11 @@ To display the contents of a binary partition table on stdout (this is how the s ``gen_esp32part.py`` takes one optional argument, ``--verify``, which will also verify the partition table during conversion (checking for overlapping partitions, unaligned partitions, etc.) +MD5 checksum +~~~~~~~~~~~~ + +The binary format of the partition table contains an MD5 checksum computed based on the partition table. This checksum is used for checking the integrity of the partition table during the boot. + Flashing the partition table ---------------------------- diff --git a/docs/api-guides/thread-local-storage.rst b/docs/api-guides/thread-local-storage.rst new file mode 100644 index 000000000..3c380db4b --- /dev/null +++ b/docs/api-guides/thread-local-storage.rst @@ -0,0 +1,65 @@ +Thread Local Storage +==================== + +Overview +-------- + +Thread-local storage (TLS) is a mechanism by which variables are allocated such that there +is one instance of the variable per extant thread. ESP-IDF provides three ways to make use +of such variables: + + - :ref:`freertos-native`: ESP-IDF FreeRTOS native API. + - :ref:`pthread-api`: ESP-IDF's pthread API. + - :ref:`c11-std`: C11 standard introduces special keyword to declare variables as thread local. + +.. _freertos-native: + +FreeRTOS Native API +-------------------- + +The ESP-IDF FreeRTOS provides the following API to manage thread local variables: + + - :cpp:func:`vTaskSetThreadLocalStoragePointer` + - :cpp:func:`pvTaskGetThreadLocalStoragePointer` + - :cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback` + +In this case maximum number of variables that can be allocated is limited by +``configNUM_THREAD_LOCAL_STORAGE_POINTERS`` macro. Variables are kept in the task control block (TCB) +and accessed by their index. Note that index 0 is reserved for ESP-IDF internal uses. +Using that API user can allocate thread local variables of an arbitrary size and assign them to any number of tasks. +Different tasks can have different sets of TLS variables. +If size of the variable is more then 4 bytes then user is responsible for allocating/deallocating memory for it. +Variable's deallocation is initiated by FreeRTOS when task is deleted, but user must provide function (callback) +to do proper cleanup. + +.. _pthread-api: + +Pthread API +---------------- + +The ESP-IDF provides the following pthread API to manage thtread local variables: + + - :cpp:func:`pthread_key_create` + - :cpp:func:`pthread_key_delete` + - :cpp:func:`pthread_getspecific` + - :cpp:func:`pthread_setspecific` + +This API has all benefits of the one above, but eliminates some its limits. The number of variables is +limited only by size of available memory on the heap. +Due to the dynamic nature this API introduces additional performance overhead compared to the native one. + +.. _c11-std: + +C11 Standard +------------ + +The ESP-IDF FreeRTOS supports thread local variables according to C11 standard (ones specified with ``__thread`` keyword). +For details on this GCC feature please see https://gcc.gnu.org/onlinedocs/gcc-5.5.0/gcc/Thread-Local.html#Thread-Local. +Storage for that kind of variables is allocated on the task's stack. +Note that area for all such variables in the program will be allocated on the stack of +every task in the system even if that task does not use such variables at all. For example +ESP-IDF system tasks (like ``ipc``, ``timer`` tasks etc.) will also have that extra stack space allocated. +So this feature should be used with care. There is a tradeoff: C11 thread local variables are quite handy +to use in programming and can be accessed using just a few Xtensa instructions, but this benefit goes +with the cost of additional stack usage for all tasks in the system. +Due to static nature of variables allocation all tasks in the system have the same sets of C11 thread local variables. diff --git a/docs/api-guides/ulp_instruction_set.rst b/docs/api-guides/ulp_instruction_set.rst index 6f123d672..bc042ed65 100755 --- a/docs/api-guides/ulp_instruction_set.rst +++ b/docs/api-guides/ulp_instruction_set.rst @@ -857,7 +857,7 @@ Convenience macros for peripheral registers access ULP source files are passed through C preprocessor before the assembler. This allows certain macros to be used to facilitate access to peripheral registers. Some existing macros are defined in ``soc/soc_ulp.h`` header file. These macros allow access to the fields of peripheral registers by their names. -Peripheral registers names which can be used with these macros are the ones defined in ``soc/rtc_cntl_reg.h``, ``soc/rtc_io_reg.h``, ``soc/sens_reg.h``, and ``soc_rtc_i2c_reg.h``. +Peripheral registers names which can be used with these macros are the ones defined in ``soc/rtc_cntl_reg.h``, ``soc/rtc_io_reg.h``, ``soc/sens_reg.h``, and ``soc/rtc_i2c_reg.h``. READ_RTC_REG(rtc_reg, low_bit, bit_width) Read up to 16 bits from rtc_reg[low_bit + bit_width - 1 : low_bit] into R0. For example:: diff --git a/docs/api-reference/bluetooth/esp_gap_ble.rst b/docs/api-reference/bluetooth/esp_gap_ble.rst index 9d0b50322..6e69a0167 100644 --- a/docs/api-reference/bluetooth/esp_gap_ble.rst +++ b/docs/api-reference/bluetooth/esp_gap_ble.rst @@ -11,9 +11,17 @@ Overview Application Example ------------------- -Check :example:`bluetooth` folder in ESP-IDF examples, which contains the following applications: +Check :example:`bluetooth` folder in ESP-IDF examples, which contains the following demos and their tutorials: -* The two demos use different GAP APIs, such like advertising, scan, set device name and others - :example:`bluetooth/gatt_server`, :example:`bluetooth/gatt_client` +* This is a SMP security client demo and its tutorial. This demo initiates its security parameters and acts as a GATT client, which can send a security request to the peer device and then complete the encryption procedure. + + - :example:`bluetooth/gatt_security_client` + - :example_file:`GATT Security Client Example Walkthrough ` + +* This is a SMP security server demo and its tutorial. This demo initiates its security parameters and acts as a GATT server, which can send a pair request to the peer device and then complete the encryption procedure. + + - :example:`bluetooth/gatt_security_server` + - :example_file:`GATT Security Server Example Walkthrough ` API Reference ------------- diff --git a/docs/api-reference/bluetooth/esp_gattc.rst b/docs/api-reference/bluetooth/esp_gattc.rst index 6efbc7aff..450eda702 100644 --- a/docs/api-reference/bluetooth/esp_gattc.rst +++ b/docs/api-reference/bluetooth/esp_gattc.rst @@ -11,9 +11,21 @@ Overview Application Example ------------------- -Check :example:`bluetooth` folder in ESP-IDF examples, which contains the following application: +Check :example:`bluetooth` folder in ESP-IDF examples, which contains the following demos and their tutorials: -* This is a GATT client demo. This demo can scan devices, connect to the GATT server and discover the service :example:`bluetooth/gatt_client` +* This is a GATT client demo and its tutorial. This demo can scan for devices, connect to the GATT server and discover its services. + + - :example:`bluetooth/gatt_client` + - :example_file:`GATT Client Example Walkthrough ` + +* This is a multiple connection demo and its tutorial. This demo can connect to multiple GATT server devices and discover their services. + + - :example:`bluetooth/gattc_multi_connect` + - :example_file:`GATT Client Multi-connection Example Walkthrough ` + +* This is a BLE SPP-Like demo. This demo, which acts as a GATT client, can receive data from UART and then send the data to the peer device automatically. + + - :example:`bluetooth/ble_spp_client` API Reference ------------- diff --git a/docs/api-reference/bluetooth/esp_gatts.rst b/docs/api-reference/bluetooth/esp_gatts.rst index 3b0fb4532..78a1bc3bb 100644 --- a/docs/api-reference/bluetooth/esp_gatts.rst +++ b/docs/api-reference/bluetooth/esp_gatts.rst @@ -11,9 +11,21 @@ Overview Application Example ------------------- -Check :example:`bluetooth` folder in ESP-IDF examples, which contains the following application: +Check :example:`bluetooth` folder in ESP-IDF examples, which contains the following demos and their tutorials: -* This is a GATT server demo. Use GATT API to create a GATT server with send advertising. This GATT server can be connected and the service can be discovery - :example:`bluetooth/gatt_server` +* This is a GATT sever demo and its tutorial. This demo creates a GATT service with an attribute table, which releases the user from adding attributes one by one. This is the recommended method of adding attributes. + + - :example:`bluetooth/gatt_server_service_table` + - :example_file:`GATT Server Service Table Example Walkthrough ` + +* This is a GATT server demo and its tutorial. This demo creates a GATT service by adding attributes one by one as defined by Bluedroid. The recommended method of adding attributes is presented in example above. + + - :example:`bluetooth/gatt_server` + - :example_file:`GATT Server Example Walkthrough ` + +* This is a BLE SPP-Like demo. This demo, which acts as a GATT server, can receive data from UART and then send the data to the peer device automatically. + + - :example:`bluetooth/ble_spp_server` API Reference ------------- diff --git a/docs/api-reference/bluetooth/index.rst b/docs/api-reference/bluetooth/index.rst index e24c1fab1..af7d3cfca 100644 --- a/docs/api-reference/bluetooth/index.rst +++ b/docs/api-reference/bluetooth/index.rst @@ -10,4 +10,18 @@ Bluetooth API Bluetooth Classic +To see the overview of the ESP32 Bluetooth stack architecture, follow links below: + +* `ESP32 Bluetooth Architecture (PDF) [English] `_ +* `ESP32 Bluetooth Architecture (PDF) [中文] `_ + Example code for this API section is provided in :example:`bluetooth` directory of ESP-IDF examples. + +Several examples contain detailed description. To see them please follow links below: + +* :example_file:`GATT Client Example Walkthrough ` +* :example_file:`GATT Server Service Table Example Walkthrough ` +* :example_file:`GATT Server Example Walkthrough ` +* :example_file:`GATT Security Client Example Walkthrough ` +* :example_file:`GATT Security Server Example Walkthrough ` +* :example_file:`GATT Client Multi-connection Example Walkthrough ` diff --git a/docs/api-reference/protocols/mdns.rst b/docs/api-reference/protocols/mdns.rst index 751fb0776..d01e24149 100644 --- a/docs/api-reference/protocols/mdns.rst +++ b/docs/api-reference/protocols/mdns.rst @@ -20,21 +20,19 @@ Example method to start mDNS for the STA interface and set ``hostname`` and ``de :: - mdns_server_t * mdns = NULL; - void start_mdns_service() { - //initialize mDNS service on STA interface - esp_err_t err = mdns_init(TCPIP_ADAPTER_IF_STA, &mdns); + //initialize mDNS service + esp_err_t err = mdns_init(); if (err) { printf("MDNS Init failed: %d\n", err); return; } //set hostname - mdns_set_hostname(mdns, "my-esp32"); + mdns_hostname_set("my-esp32"); //set default instance - mdns_set_instance(mdns, "Jhon's ESP32 Thing"); + mdns_instance_name_set("Jhon's ESP32 Thing"); } mDNS Services @@ -42,36 +40,35 @@ mDNS Services mDNS can advertise information about network services that your device offers. Each service is defined by a few properties. - * ``service``: (required) service type, prepended with underscore. Some common types can be found `here `_. + * ``instance_name``: friendly name for your service, like ``Jhon's ESP32 Web Server``. If not defined, ``default_instance`` will be used. + * ``service_type``: (required) service type, prepended with underscore. Some common types can be found `here `_. * ``proto``: (required) protocol that the service runs on, prepended with underscore. Example: ``_tcp`` or ``_udp`` * ``port``: (required) network port that the service runs on - * ``instance``: friendly name for your service, like ``Jhon's ESP32 Web Server``. If not defined, ``default_instance`` will be used. - * ``txt``: ``var=val`` array of strings, used to define properties for your service + * ``txt``: ``{var, val}`` array of strings, used to define properties for your service Example method to add a few services and different properties:: void add_mdns_services() { //add our services - mdns_service_add(mdns, "_http", "_tcp", 80); - mdns_service_add(mdns, "_arduino", "_tcp", 3232); - mdns_service_add(mdns, "_myservice", "_udp", 1234); + mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0); + mdns_service_add(NULL, "_arduino", "_tcp", 3232, NULL, 0); + mdns_service_add(NULL, "_myservice", "_udp", 1234, NULL, 0); //NOTE: services must be added before their properties can be set //use custom instance for the web server - mdns_service_instance_set(mdns, "_http", "_tcp", "Jhon's ESP32 Web Server"); + mdns_service_instance_name_set("_http", "_tcp", "Jhon's ESP32 Web Server"); - const char * arduTxtData[4] = { - "board=esp32", - "tcp_check=no", - "ssh_upload=no", - "auth_upload=no" + mdns_txt_item_t serviceTxtData[3] = { + {"board","esp32"}, + {"u","user"}, + {"p","password"} }; //set txt data for service (will free and replace current data) - mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData); + mdns_service_txt_set("_http", "_tcp", serviceTxtData, 3); //change service port - mdns_service_port_set(mdns, "_myservice", "_udp", 4321); + mdns_service_port_set("_myservice", "_udp", 4321); } mDNS Query @@ -79,60 +76,85 @@ mDNS Query mDNS provides methods for browsing for services and resolving host's IP/IPv6 addresses. -Results are returned as a linked list of ``mdns_result_t`` objects. If the result is from host query, it will contain only ``addr`` and ``addrv6`` if found. Service queries will populate all fields in a result that were found. +Results for services are returned as a linked list of ``mdns_result_t`` objects. Example method to resolve host IPs:: - void resolve_mdns_host(const char * hostname) + void resolve_mdns_host(const char * host_name) { - printf("mDNS Host Lookup: %s.local\n", hostname); - //run search for 1000 ms - if (mdns_query(mdns, hostname, NULL, 1000)) { - //results were found - const mdns_result_t * results = mdns_result_get(mdns, 0); - //itterate through all results - size_t i = 1; - while(results) { - //print result information - printf(" %u: IP:" IPSTR ", IPv6:" IPV6STR "\n", i++ - IP2STR(&results->addr), IPV62STR(results->addrv6)); - //load next result. Will be NULL if this was the last one - results = results->next; + printf("Query A: %s.local", host_name); + + struct ip4_addr addr; + addr.addr = 0; + + esp_err_t err = mdns_query_a(host_name, 2000, &addr); + if(err){ + if(err == ESP_ERR_NOT_FOUND){ + printf("Host was not found!"); + return; } - //free the results from memory - mdns_result_free(mdns); - } else { - //host was not found - printf(" Host Not Found\n"); + printf("Query Failed"); + return; } + + printf(IPSTR, IP2STR(&addr)); } Example method to resolve local services:: - void find_mdns_service(const char * service, const char * proto) - { - printf("mDNS Service Lookup: %s.%s\n", service, proto); - //run search for 1000 ms - if (mdns_query(mdns, service, proto, 1000)) { - //results were found - const mdns_result_t * results = mdns_result_get(mdns, 0); - //itterate through all results - size_t i = 1; - while(results) { - //print result information - printf(" %u: hostname:%s, instance:\"%s\", IP:" IPSTR ", IPv6:" IPV6STR ", port:%u, txt:%s\n", i++, - (results->host)?results->host:"NULL", (results->instance)?results->instance:"NULL", - IP2STR(&results->addr), IPV62STR(results->addrv6), - results->port, (results->txt)?results->txt:"\r"); - //load next result. Will be NULL if this was the last one - results = results->next; + static const char * if_str[] = {"STA", "AP", "ETH", "MAX"}; + static const char * ip_protocol_str[] = {"V4", "V6", "MAX"}; + + void mdns_print_results(mdns_result_t * results){ + mdns_result_t * r = results; + mdns_ip_addr_t * a = NULL; + int i = 1, t; + while(r){ + printf("%d: Interface: %s, Type: %s\n", i++, if_str[r->tcpip_if], ip_protocol_str[r->ip_protocol]); + if(r->instance_name){ + printf(" PTR : %s\n", r->instance_name); } - //free the results from memory - mdns_result_free(mdns); - } else { - //service was not found - printf(" Service Not Found\n"); + if(r->hostname){ + printf(" SRV : %s.local:%u\n", r->hostname, r->port); + } + if(r->txt_count){ + printf(" TXT : [%u] ", r->txt_count); + for(t=0; ttxt_count; t++){ + printf("%s=%s; ", r->txt[t].key, r->txt[t].value); + } + printf("\n"); + } + a = r->addr; + while(a){ + if(a->addr.type == MDNS_IP_PROTOCOL_V6){ + printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6)); + } else { + printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4))); + } + a = a->next; + } + r = r->next; } + + } + + void find_mdns_service(const char * service_name, const char * proto) + { + ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results); + if(err){ + ESP_LOGE(TAG, "Query Failed"); + return; + } + if(!results){ + ESP_LOGW(TAG, "No results found!"); + return; + } + + mdns_print_results(results); + mdns_query_results_free(results); } Example of using the methods above:: diff --git a/docs/conf.py b/docs/conf.py index caa233c93..aee4803d4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,11 @@ breathe_default_project = "esp32-idf" templates_path = ['_templates'] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ['.rst', '.md'] + +source_parsers = { + '.md': 'recommonmark.parser.CommonMarkParser', + } # The encoding of source files. #source_encoding = 'utf-8-sig' @@ -111,7 +115,7 @@ print 'Version: {0} Release: {1}'.format(version, release) # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ['_build','README.md'] # The reST default role (used for this markup: `text`) to use for all # documents. diff --git a/docs/contribute/documenting-code.rst b/docs/contribute/documenting-code.rst index 228ff74b5..588416dff 100644 --- a/docs/contribute/documenting-code.rst +++ b/docs/contribute/documenting-code.rst @@ -153,6 +153,16 @@ The following roles are provided: - ``:example_file:`path``` - points to file inside ESP-IDF examples dir - ``:example_raw:`path``` - points to raw view of the file inside ESP-IDF examples dir +Example implementation:: + + * :example:`get-started/hello_world` + * :example:`Hello World! ` + +How it renders: + +* :example:`get-started/hello_world` +* :example:`Hello World! ` + A check is added to the CI build script, which searches RST files for presence of hard-coded links (identified by tree/master, blob/master, or raw/master part of the URL). This check can be run manually: ``cd docs`` and then ``make gh-linkcheck``. .. _add-illustrations: @@ -198,7 +208,7 @@ Once documentation is ready, follow instruction in :doc:`../api-reference/templa OK, but I am new to Sphinx! --------------------------- -1. No worries. All the software you need is well documented. It is also open source and free. Start by checking `Sphinx `_ documentation. If you are not clear how to write using rst markup language, see `reStructuredText Primer `_. +1. No worries. All the software you need is well documented. It is also open source and free. Start by checking `Sphinx `_ documentation. If you are not clear how to write using rst markup language, see `reStructuredText Primer `_. You can also use markdown (.md) files, and find out about more about the specific markdown syntax that we use on`Recommonmark parser's documentation page `_. 2. Check the source files of this documentation to understand what is behind of what you see now on the screen. Sources are maintained on GitHub in `espressif/esp-idf`_ repository in :idf:`docs` folder. You can go directly to the source file of this page by scrolling up and clicking the link in the top right corner. When on GitHub, see what's really inside, open source files by clicking ``Raw`` button. @@ -221,6 +231,7 @@ You can setup environment to build documentation locally on your PC by installin 3. Docment theme "sphinx_rtd_theme" - https://github.com/rtfd/sphinx_rtd_theme 4. Breathe - https://github.com/michaeljones/breathe#breathe 5. Blockdiag - http://blockdiag.com/en/index.html +6. Recommonmark - https://github.com/rtfd/recommonmark The package "sphinx_rtd_theme" is added to have the same "look and feel" of `ESP32 Programming Guide `_ documentation like on the "Read the Docs" hosting site. @@ -248,7 +259,7 @@ Installation of Doxygen is OS dependent: .. note:: - If you are installing on Windows system (Linux and MacOS users should skip this note), **before** going further, execute two extra steps below. These steps are required for the :ref:`blockdiag ` to install: + If you are installing on Windows system (Linux and MacOS users should skip this note), **before** going further, execute two extra steps below. These steps are required to install dependencies of "blockdiag" discussed under :ref:`add-illustrations`. 1. Update all the system packages: diff --git a/docs/get-started/get-started-devkitc-v2.rst b/docs/get-started/get-started-devkitc-v2.rst new file mode 100644 index 000000000..8303c0d15 --- /dev/null +++ b/docs/get-started/get-started-devkitc-v2.rst @@ -0,0 +1,79 @@ +ESP32-DevKitC V2 Getting Started Guide +====================================== + +This user guide shows how to get started with ESP32-DevKitC development board. + + +What You Need +------------- + +* 1 × :ref:`ESP32-DevKitC V2 board ` +* 1 × USB A / micro USB B cable +* 1 × PC loaded with Windows, Linux or Mac OS + + +Overview +-------- + +ESP32-DevKitC is a small-sized ESP32-based development board produced by `Espressif `_. Most of the I/O pins are broken out to the pin headers on both sides for easy interfacing. Developers can connect these pins to peripherals as needed. Standard headers also make development easy and convenient when using a breadboard. + + +Functional Description +---------------------- + +The following list and figure below describe key components, interfaces and controls of ESP32-DevKitC board. + +ESP-WROOM-32 + Standard `ESP-WROOM-32 `_ module soldered to the ESP32-DevKitC board. +EN + Reset button: pressing this button resets the system. +Boot + Download button: holding down the **Boot** button and pressing the **EN** button initiates the firmware download mode. Then user can download firmware through the serial port. +USB + USB interface. It functions as the power supply for the board and the communication interface between PC and ESP-WROOM-32. +I/O + Most of the pins on the ESP-WROOM-32 are broken out to the pin headers on the board. Users can program ESP32 to enable multiple functions such as PWM, ADC, DAC, I2C, I2S, SPI, etc. + +.. _get-started-esp32-devkitc-v2-board-front: + +.. figure:: ../_static/esp32-devkitc-v2-functional-overview.png + :align: center + :alt: ESP32-DevKitC V2 board layout + :figclass: align-center + + ESP32-DevKitC V2 board layout + + +Power Supply Options +-------------------- + +There following options are available to provide power supply to this board: + +1. Micro USB port, this is default power supply connection +2. 5V / GND header pins +3. 3V3 / GND header pins + +.. warning:: + + Above options are mutually exclusive, i.e. the power supply may be provided using only one of the above options. Attempt to power the board using more than one connection at a time may damage the board and/or the power supply source. + + +Start Application Development +------------------------------ + +Before powering up the ESP32-DevKitC, please make sure that the board has been received in good condition with no obvious signs of damage. + +To start development of applications, proceed to section :doc:`index`, that will walk you through the following steps: + +* :ref:`get-started-setup-toolchain` in your PC to develop applications for ESP32 in C language +* :ref:`get-started-connect` the module to the PC and verify if it is accessible +* :ref:`get-started-build-flash` an example application to the ESP32 +* :ref:`get-started-build-monitor` instantly what the application is doing + + +Related Documents +----------------- + +* `ESP32-DevKitC schematic `_ (PDF) +* `ESP32 Datasheet `_ (PDF) +* `ESP-WROOM-32 Datasheet `_ (PDF) diff --git a/docs/get-started/get-started-devkitc.rst b/docs/get-started/get-started-devkitc.rst index b5f14ab10..1c9772bbf 100644 --- a/docs/get-started/get-started-devkitc.rst +++ b/docs/get-started/get-started-devkitc.rst @@ -15,7 +15,9 @@ What You Need Overview -------- -ESP32-DevKitC is a small-sized ESP32-based development board produced by `Espressif `_. Most of the I/O pins are broken out to the pin headers on both sides for easy interfacing. Developers can connect these pins to peripherals as needed. Standard headers also make development easy and convenient when using a breadboard. +ESP32-DevKitC is a small-sized ESP32-based development board produced by `Espressif `_. Most of the I/O pins are broken out to the pin headers on both sides for easy interfacing. Developers can connect these pins to peripherals as needed. Standard headers also make development easy and convenient when using a breadboard. + +The board comes in two versions, either with :ref:`esp-modules-and-boards-esp-wroom-32` or :ref:`esp-modules-and-boards-esp32-wrover` module soldered. Functional Description @@ -24,30 +26,45 @@ Functional Description The following list and figure below describe key components, interfaces and controls of ESP32-DevKitC board. ESP-WROOM-32 - Standard `ESP-WROOM-32 `_ module soldered to the ESP32-DevKitC board. -EN - Reset button: pressing this button resets the system. + :ref:`esp-modules-and-boards-esp-wroom-32` module soldered to the ESP32-DevKitC board. +ESP32-WROVER + Optionally :ref:`esp-modules-and-boards-esp32-wrover` module may be soldered instead of the ESP-WROOM-32. +USB-UART Bridge + A single chip USB-UART bridge provides up to 3 Mbps transfers rates. Boot Download button: holding down the **Boot** button and pressing the **EN** button initiates the firmware download mode. Then user can download firmware through the serial port. -USB - USB interface. It functions as the power supply for the board and the communication interface between PC and ESP-WROOM-32. +Micro USB Port + USB interface. It functions as the power supply for the board and the communication interface between PC and the ESP module. +5V Power On LED + This LED lights when the USB or an external 5V power supply is applied to the board. For details see schematic in `Related Documents`_. +EN + Reset button: pressing this button resets the system. I/O - Most of the pins on the ESP-WROOM-32 are broken out to the pin headers on the board. Users can program ESP32 to enable multiple functions such as PWM,ADC, DAC, I2C, I2S, SPI, etc. + Most of the pins on the ESP module are broken out to the pin headers on the board. Users can program ESP32 to enable multiple functions such as PWM, ADC, DAC, I2C, I2S, SPI, etc. + + .. note:: + + Some of broken out pins are used internally be the ESP32 module to communicate with SPI memory. They are grouped on one side of the board besides the USB connector and labeled D0, D1, D2, D3, CMD and CLK. In general these pins should be left unconnected or access to the SPI flash memory / SPI RAM may be disturbed. + + .. note:: + + GPIO16 and 17 are used internally by the ESP32-WROVER module. They are broken out and avialable for use only for boards that have the ESP-WROOM-32 module installed. + .. _get-started-esp32-devkitc-board-front: -.. figure:: ../_static/esp32-devkitc-functional-overview.png +.. figure:: ../_static/esp32-devkitc-functional-overview.jpg :align: center - :alt: ESP32-DevKitC board layout + :alt: ESP32-DevKitC with ESP-WROOM-32 module soldered :figclass: align-center - ESP32-DevKitC board layout + ESP32-DevKitC with ESP-WROOM-32 module soldered Power Supply Options -------------------- -There following options are available to provide power supply to the ESP32-PICO-KIT V4: +There following options are available to provide power supply to this board: 1. Micro USB port, this is default power supply connection 2. 5V / GND header pins @@ -71,9 +88,26 @@ To start development of applications, proceed to section :doc:`index`, that will * :ref:`get-started-build-monitor` instantly what the application is doing +Board Dimensions +---------------- + +.. figure:: ../_static/esp32-devkitc-dimensions-back.jpg + :align: center + :alt: ESP32 DevKitC board dimensions - back + :figclass: align-center + + ESP32 DevKitC board dimensions - back + + Related Documents ----------------- -* `ESP32-DevKitC schematic `_ (PDF) +* `ESP32-DevKitC schematic `_ (PDF) * `ESP32 Datasheet `_ (PDF) * `ESP-WROOM-32 Datasheet `_ (PDF) +* `ESP32-WROVER Datasheet `_ (PDF) + +.. toctree:: + :hidden: + + get-started-devkitc-v2 \ No newline at end of file diff --git a/docs/get-started/index-cn.rst b/docs/get-started/index-cn.rst new file mode 100644 index 000000000..5a0bbbe62 --- /dev/null +++ b/docs/get-started/index-cn.rst @@ -0,0 +1,324 @@ +*********** +快速入门 +*********** + +本文档旨在指导用户创建 ESP32 的软件环境。本文将通过一个简单的例子来说明如何使用 ESP-IDF (Espressif IoT Development Framework),包括配置、编译、下载固件到开发板等步骤。 + +概述 +====== + +ESP32 是一套 Wi-Fi (2.4 GHz) 和蓝牙 (4.2) 双模解决方案,集成了高性能的 CPU 内核、超低功耗协处理器和丰富的外设。ESP32 采用 40 nm 工艺制成,具有最佳的功耗性能、射频性能、稳定性、通用性和可靠性,适用于各种应用和不同功耗需求。 + +乐鑫为用户提供完整的软、硬件资源进行 ESP32 设备的开发。乐鑫所研发的软件开发环境 ESP-IDF 能够帮助用户快速开发物联网 (IoT) 应用,满足用户对于 Wi-Fi、蓝牙、低功耗等性能的需求。 + +准备工作 +========= + +开发 ESP32 应用程序需要准备: + + +* **电脑**:安装 Windows、Linux 或者 Mac 操作系统 +* **工具链**:用于编译 ESP32 **应用程序** +* **ESP-IDF**:包含 ESP32 API 和用于操作 **工具链** 的脚本 +* **文本编辑器**:编写 C 语言程序,例如 `Eclipse `_ +* **ESP32 开发板** 和将其连接到 **电脑** 的 **USB 线** + +.. figure:: ../_static/what-you-need.png + :align: center + :alt: Development of applications for ESP32 + :figclass: align-center + + 开发应用程序 + +开发环境的准备工作包括以下三部分: + +1. 设置 **工具链** +2. 从 GitHub 上获取 **ESP-IDF** +3. 安装和配置 **Eclipse** + +如果你偏好使用其它编辑器,可以跳过最后一步。 + +环境设置好后,就可以开始开发应用程序了。整个过程可以概括为如下四步: + +1. 配置 **工程** 并编写代码 +2. 编译 **工程** 并链接成一个 **应用程序** +3. 烧写 **应用程序** 到 **ESP32** +4. 监视/调试 **应用程序** + +下文将全程指导你操作完成这些步骤。 + +开发板指南 +========== + +如果你有下列任一 ESP32 开发板,请点击对应的链接,对照指南进行操作就可以让你的板子跑起来。 + +.. toctree:: + :maxdepth: 1 + + ESP32 DevKitC <../get-started/get-started-devkitc> + ESP-WROVER-KIT <../get-started/get-started-wrover-kit> + ESP32-PICO-KIT <../get-started/get-started-pico-kit> + +如果你使用其它开发板,请查看下面的内容。 + +.. _get-started-setup-toolchain-cn: + +设置工具链 +=============== + +用 ESP32 进行开发最快的方法是安装预编译的工具链。请根据你的操作系点击对应的链接,并按照链接中的指导进行安装。 + +.. toctree:: + :hidden: + + Windows <../get-started/windows-setup-cn> + Linux <../get-started/linux-setup-cn> + MacOS <../get-started/macos-setup> + ++-------------------+-------------------+-------------------+ +| |windows-logo| | |linux-logo| | |macos-logo| | ++-------------------+-------------------+-------------------+ +| `Windows`_ | `Linux`_ | `Mac OS`_ | ++-------------------+-------------------+-------------------+ + +.. |windows-logo| image:: ../_static/windows-logo.png + :target: ../get-started/windows-setup-cn.html + +.. |linux-logo| image:: ../_static/linux-logo.png + :target: ../get-started/linux-setup-cn.html + +.. |macos-logo| image:: ../_static/macos-logo.png + :target: ../get-started/macos-setup.html + +.. _Windows: ../get-started/windows-setup-cn.html +.. _Linux: ../get-started/linux-setup-cn.html +.. _Mac OS: ../get-started/macos-setup.html + +.. note:: + + 我们使用 ``~/esp`` 目录来安装预编译的工具链、ESP-IDF 和示例程序。你也可以使用其它目录,但是需要注意调整相应的指令。 + +你可以安装预编译的工具链或者自定义你的环境,这完全取决于个人经验和偏好。如果你要自定义环境,请参考 :ref:`get-started-customized-setup`。 + +工具链设置完成后,就可以 :ref:`get-started-get-esp-idf-cn` 了。 + +.. _get-started-get-esp-idf-cn: + +获取 ESP-IDF +================= + +.. highlight:: bash + +工具链(包括用于编译和构建应用程序的程序)安装完后,你还需要 ESP32 相关的 API/库。API/库在 `ESP-IDF 仓库 `_ 中。要获取这些 API/库,打开一个终端,进入某个你希望存放 ESP-IDF 的目录,然后 ``git clone`` 以下指令: :: + + cd ~/esp + git clone --recursive https://github.com/espressif/esp-idf.git + +ESP-IDF 将会被下载到 ``~/esp/esp-idf``。 + +.. note:: + + 注意这里有个 ``--recursive`` 选项。如果你克隆 ESP-IDF 时没有带这个选项,你还需要运行额外的命令来获取子模块: :: + + cd ~/esp/esp-idf + git submodule update --init + + +.. _get-started-setup-path-cn: + +设置 ESP-IDF 路径 +===================== + +工具链程序使用环境变量 ``IDF_PATH`` 来访问 ESP-IDF。这个变量应该设置在你的 PC 中,否则工程将不能编译。你可以在每次 PC 重启时手工设置,也可以通过在用户配置文件中定义 ``IDF_PATH`` 变量来永久性设置。要永久性设置,请参考 :doc:`add-idf_path-to-profile` 文档中 :ref:`Windows ` 或 :ref:`Linux and MacOS ` 相关的指导进行操作。 + + +.. _get-started-start-project-cn: + +创建一个工程 +=============== + +现在可以开始创建 ESP32 应用程序了。为了快速开始,我们这里以 IDF 的 :idf:`examples` 目录下的 :example:`get-started/hello_world` 工程为例进行说明。 + +将 :example:`get-started/hello_world` 拷贝到 ``~/esp`` 目录: :: + + cd ~/esp + cp -r $IDF_PATH/examples/get-started/hello_world + +ESP-IDF 的 :idf:`examples` 目录下有一系列示例工程,都可以按照上面的方法进行创建。 + +.. important:: + + esp-idf 构建系统不支持在路径中存在空格。 + +.. _get-started-connect-cn: + +连接 +======= + +还有几个步骤就完成了。在继续后续操作前,先将 ESP32 开发板连接到 PC,然后检查串口号,看看它能否正常通信。如果你不知道如何操作,请查看 Establish Serial Connection with ESP32 中的相关指导。请注意一下端口号,我们在下一步中会用到。 + +.. _get-started-configure-cn: + +配置 +========= + +在终端窗口中,输入 ``cd ~/esp/hello_world`` 进入 ``hello_world`` 所在目录,然后启动工程配置工具 ``menuconfig``: :: + + cd ~/esp/hello_world + make menuconfig + +如果之前的步骤都正确,则会显示下面的菜单: + +.. figure:: ../_static/project-configuration.png + :align: center + :alt: Project configuration - Home window + :figclass: align-center + + 工程配置 - 主窗口 + +在菜单中,进入 ``Serial flasher config`` > ``Default serial port`` 配置串口(工程将会加载到该串口上)。输入回车确认选择,选择 ``< Save >`` 保存配置,然后选择 ``< Exit >`` 退出应用程序。 + +.. note:: + + 在 Windows 系统中,端口号的名称类似 COM1,在 MacOS 中以 ``/dev/cu.`` 开始,而在 Linux 系统中,以 ``/dev/tty`` 开始。 + (详细内容可以参考章节 :doc:`establish-serial-connection`。) + +下面是一些使用 ``menuconfig`` 的小技巧: + +* 使用 up & down 组合键在菜单中上下移动 +* 使用 Enter 键进入一个子菜单,Escape 键退出子菜单或退出整个菜单 +* 输入 ``?`` 查看帮助信息,Enter 键退出帮助屏幕 +* 使用空格键或 ``Y`` 和 ``N`` 键来使能 (Yes) 和禁止 (No) 带有复选框 "``[*]``" 的配置项 +* 当光标在某个配置项上面高亮时,输入 ``?`` 可以直接查看该项的帮助信息 +* 输入 ``/`` 搜索配置项 + +.. note:: + + 如果你是 **Arch Linux** 用户,需要进入 ``SDK tool configuration`` 将 ``Python 2 interpreter`` 从 ``python`` 修改为 ``python2``。 + + +.. _get-started-build-flash-cn: + +编译和烧写 +=============== + +现在可以编译和烧写应用程序了,执行指令: :: + + make flash + +这条命令会编译应用程序和所有的 ESP-IDF 组件,生成 bootloader、分区表和应用程序 bin 文件,并将这些 bin 文件烧写到 ESP32 板子上。 + +.. highlight:: none + +:: + + esptool.py v2.0-beta2 + Flashing binaries to serial port /dev/ttyUSB0 (app at offset 0x10000)... + esptool.py v2.0-beta2 + Connecting........___ + Uploading stub... + Running stub... + Stub running... + Changing baud rate to 921600 + Changed. + Attaching SPI flash... + Configuring flash size... + Auto-detected Flash size: 4MB + Flash params set to 0x0220 + Compressed 11616 bytes to 6695... + Wrote 11616 bytes (6695 compressed) at 0x00001000 in 0.1 seconds (effective 920.5 kbit/s)... + Hash of data verified. + Compressed 408096 bytes to 171625... + Wrote 408096 bytes (171625 compressed) at 0x00010000 in 3.9 seconds (effective 847.3 kbit/s)... + Hash of data verified. + Compressed 3072 bytes to 82... + Wrote 3072 bytes (82 compressed) at 0x00008000 in 0.0 seconds (effective 8297.4 kbit/s)... + Hash of data verified. + + Leaving... + Hard resetting... + +如果没有任何问题,在编译过程结束后将能看到类似上面的消息。最后,板子将会复位,应用程序 "hello_world" 开始启动。 + +如果你想使用 Eclipse IDE 而不是运行 ``make``,请参考 :doc:`Eclipse guide `。 + +.. _get-started-build-monitor-cn: + +监视器 +======= + +如果要查看 "hello_world" 程序是否真的在运行,输入命令 ``make monitor``。这个命令会启动 IDF Monitor 程序: :: + + $ make monitor + MONITOR + --- idf_monitor on /dev/ttyUSB0 115200 --- + --- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- + ets Jun 8 2016 00:22:57 + + rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) + ets Jun 8 2016 00:22:57 + ... + +在启动消息和诊断消息后,你就能看到 "Hello world!" 程序所打印的消息: :: + + ... + Hello world! + Restarting in 10 seconds... + I (211) cpu_start: Starting scheduler on APP CPU. + Restarting in 9 seconds... + Restarting in 8 seconds... + Restarting in 7 seconds... + +要退出监视器,请使用快捷键 ``Ctrl+]``。 + +.. note:: + + 如果串口打印的不是上面显示的消息而是类似下面的乱码: :: + + e���)(Xn@�y.!��(�PW+)��Hn9a؅/9�!�t5��P�~�k��e�ea�5�jA + ~zY��Y(1�,1�� e���)(Xn@�y.!Dr�zY(�jpi�|�+z5Ymvp + + 或者监视器程序启动失败,那么可能你的开发板用的是 26 MHz 晶振,而 ESP-IDF 默认的是 40 MHz 晶振。请退出监视器,回到 :ref:`配置 `,将 :ref:`CONFIG_ESP32_XTAL_FREQ_SEL` 改为 26 MHz,然后再次 :ref:`编译和烧写 `。请在 ``make menuconfig`` 的 Component config --> ESP32-specific --> Main XTAL frequency 中配置。 + +要一次性执行 ``make flash`` 和 ``make monitor``,输入 ``make flash monitor``。参考文档 :doc:`IDF Monitor ` 里的快捷键和更多内容。 + +你已完成 ESP32 的入门! + +现在你可以尝试其他的示例工程 :idf:`examples`,或者直接开发自己的应用程序。 + +更新 ESP-IDF +============= + +使用 ESP-IDF 一段时间后,你可能想要进行升级来获得新的性能或者对 bug 进行修复。最简单的更新方式是删除已有的 ``esp-idf`` 文件夹然后再克隆一个,即重复 :ref:`get-started-get-esp-idf-cn` 里的操作。 + +另外一种方法是只更新有改动的部分,如果你不容易登陆 GitHub,那么这种方法比较合适。执行以下命令: :: + + cd ~/esp/esp-idf + git pull + git submodule update --init --recursive + +``git pull`` 指令是从 ESP-IDF 仓库中获取合并更新。``git submodule update --init --recursive`` 用来更新现有的子模块或拷贝新的子模块。在 GitHub 上,子模块链接到其他仓库,所以需要这个额外的指令来下载到你的电脑里。 + +如果你想使用某一版本的 ESP-IDF,比如 `v2.1` 版本,请执行以下指令: :: + + cd ~/esp + git clone https://github.com/espressif/esp-idf.git esp-idf-v2.1 + cd esp-idf-v2.1/ + git checkout v2.1 + git submodule update --init --recursive + +然后 :doc:`add-idf_path-to-profile`,这样工具链脚本就能够知道这一版本的 ESP-IDF 的具体位置。 + + +相关文档 +================= + +.. toctree:: + :maxdepth: 1 + + ../get-started/add-idf_path-to-profile + ../get-started/establish-serial-connection + ../get-started/make-project + ../get-started/eclipse-setup + ../get-started/idf-monitor + ../get-started/toolchain-setup-scratch diff --git a/docs/get-started/linux-setup-cn.rst b/docs/get-started/linux-setup-cn.rst new file mode 100644 index 000000000..7eefd3a2d --- /dev/null +++ b/docs/get-started/linux-setup-cn.rst @@ -0,0 +1,94 @@ +***************************** +Linux 平台工具链的标准设置 +***************************** + +安装前提 +===================== + +编译 ESP-IDF 需要以下软件包: + +- CentOS 7:: + + sudo yum install git wget make ncurses-devel flex bison gperf python pyserial + +- Ubuntu and Debian:: + + sudo apt-get install git wget make libncurses-dev flex bison gperf python python-serial + +- Arch:: + + sudo pacman -S --needed gcc git make ncurses flex bison gperf python2-pyserial + +工具链的设置 +=============== + +Linux 版的 ESP32 工具链可以从 Espressif 的网站下载: + +- 64-bit Linux: + + https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz + +- 32-bit Linux: + + https://dl.espressif.com/dl/xtensa-esp32-elf-linux32-1.22.0-80-g6c4433a-5.2.0.tar.gz + +1. 下载完成后,将它解压到 ``~/esp`` 目录: :: + + mkdir -p ~/esp + cd ~/esp + tar -xzf ~/Downloads/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz + +.. _setup-linux-toolchain-add-it-to-path-cn: + +2. 工具链将会被解压到 ``~/esp/xtensa-esp32-elf/`` 目录。 + + 要使用工具链,你还需要在 ``~/.profile`` 文件中更新环境变量 ``PATH``。要使 ``xtensa-esp32-elf`` 在所有的终端会话中都有效,需要将下面这一行代码添加到你的 ``~/.profile`` 文件中: :: + + export PATH="$PATH:$HOME/esp/xtensa-esp32-elf/bin" + + 或者你也可以给上面的命令创建一个别名。这样做的好处是,你只在需要使用它的时候才获取工具链。将下面这行代码添加到 ``~/.profile`` 文件中即可: :: + + alias get_esp32='export PATH="$PATH:$HOME/esp/xtensa-esp32-elf/bin"' + + 然后,当你需要使用工具链时,在命令行输入 ``get_esp32``,然后工具链会自动添加到你的 ``PATH`` 中。 + + .. note:: + + 如果将 ``/bin/bash`` 设置为登录 shell,且同时存在 ``.bash_profile`` 和 ``.profile``,则更新 ``.bash_profile`` 。 + +3. 退出并重新登录以使 ``.profile`` 更改生效。 运行以下命令来检查 ``PATH`` 设置是否正确: :: + + printenv PATH + + 检查一下字符串的末尾是否包含类似的工具链的路径: :: + + $ printenv PATH + /home/user-name/bin:/home/user-name/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/user-name/esp/xtensa-esp32-elf/bin + + 除了 ``/home/user-name``,应该有具体的安装的主路径。 + +权限问题 /dev/ttyUSB0 +------------------------------ + +某些 Linux 版本可能在烧写 ESP32 时会出现 ``Failed to open port /dev/ttyUSB0`` 错误消息。 :ref:`可以通过将当前用户添加到拨出组来解决`。 + +Arch Linux 用户 +---------------- + +在 Arch 中运行预编译的 gdb (xtensa-esp32-elf-gdb) 需要 ncurses 5,但是 Arch 使用的是 ncurses 6。在 AUR_ 中向下兼容的库文件,可用于本地和 lib32 的配置: + +- https://aur.archlinux.org/packages/ncurses5-compat-libs/ +- https://aur.archlinux.org/packages/lib32-ncurses5-compat-libs/ + +在安装这些软件包之前,你可能需要将作者的公钥添加到你的钥匙圈中,上面链接中的“Comments”部分有所叙述。 + +或者,你也可以使用 crosstool-NG 编译一个链接 ncurses 6 的 gdb。 + +后续步骤 +========== + +要继续设置开发环境,请参考 :ref:`get-started-get-esp-idf-cn` 一节。 + + +.. _AUR: https://wiki.archlinux.org/index.php/Arch_User_Repository + diff --git a/docs/get-started/linux-setup-scratch.rst b/docs/get-started/linux-setup-scratch.rst index a44e6521f..b2cb96ec3 100644 --- a/docs/get-started/linux-setup-scratch.rst +++ b/docs/get-started/linux-setup-scratch.rst @@ -36,9 +36,9 @@ Compile the Toolchain from Source sudo apt-get install gawk gperf grep gettext python python-dev automake bison flex texinfo help2man libtool libtool-bin - - Debian:: + - Debian 9:: - TODO + sudo apt-get install gawk gperf grep gettext libncurses-dev python python-dev automake bison flex texinfo help2man libtool libtool-bin - Arch:: diff --git a/docs/get-started/windows-setup-cn.rst b/docs/get-started/windows-setup-cn.rst new file mode 100644 index 000000000..3b67552eb --- /dev/null +++ b/docs/get-started/windows-setup-cn.rst @@ -0,0 +1,63 @@ +*************************************** +Windows 平台工具链的标准设置 +*************************************** + +引言 +============ + +Windows 没有内置的 "make" 环境,因此如果要安装工具链,你需要一个 GNU 兼容环境。我们这里使用 MSYS2_ 来提供该环境。你不需要一直使用这个环境(你可以使用 :doc:`Eclipse ` 或其它前端工具),但是它是在后台运行的。 + +工具链的设置 +=============== + +快速设置的方法是从 dl.espressif.com 下载集成在一起的工具链和 MSYS2 压缩文件: + +https://dl.espressif.com/dl/esp32_win32_msys2_environment_and_toolchain-20180110.zip + +将 zip 压缩文件解压到 ``C:\`` (或其它路径,这里假设是 ``C:\``),它会使用预先准备的环境创建一个 ``msys32`` 目录。 + +检出 +============ + +运行 ``C:\msys32\mingw32.exe`` 打开一个 MSYS2 的终端窗口。该窗口的环境是一个 bash shell。 + +.. figure:: ../_static/msys2-terminal-window.png + :align: center + :alt: MSYS2 MINGW32 shell window + :figclass: align-center + + MSYS2 终端窗口 + +后续步骤将会使用这个窗口来为 ESP32 设置开发环境。 + +后续步骤 +========== + +要继续设置开发环境,请参考 :ref:`get-started-get-esp-idf-cn` 一节。 + +更新环境 +======================== + +当 IDF 更新时,有时需要新的工具链,或者将新的需求添加到 Windows MSYS2 环境中。要将旧版本的预编译环境中的数据移动到新版本: + +- 把旧的 MSYS2 环境(即 ``C:\msys32``)移动/重命名为不同的目录(即 ``C:\msys32_old``)。 +- 按照前文所述步骤下载新的预编译环境。 +- 将新的 MSYS2 环境解压缩到 ``C:\msys32`` (或其他位置)。 +- 找到旧的 ``C:\msys32_old\home`` 目录并把它移到 ``C:\msys32``。 +- 如果你不再需要 ``C:\msys32_old`` 可以将它删除。 + +你可以在系统上拥有独立的不同的 MSYS2 环境,前提是在不同的目录中。 + +或者,:ref:`你也可以更新现有的环境而不是下载新环境 `,但是这样更复杂。 + +相关文档 +================= + +.. toctree:: + :maxdepth: 1 + + windows-setup-scratch + + +.. _MSYS2: https://msys2.github.io/ + diff --git a/docs/hw-reference/modules-and-boards-previous.rst b/docs/hw-reference/modules-and-boards-previous.rst index 95a968804..41f764250 100644 --- a/docs/hw-reference/modules-and-boards-previous.rst +++ b/docs/hw-reference/modules-and-boards-previous.rst @@ -30,6 +30,28 @@ Documentation * `ESP32-PICO-D4 Datasheet `_ (PDF) +.. _esp-modules-and-boards-esp32-devkitc-v2: + +ESP32 Core Board V2 / ESP32 DevKitC +----------------------------------- + +Small and convenient development board with :ref:`esp-modules-and-boards-esp-wroom-32` module installed, break out pin headers and minimum additional components. Includes USB to serial programming interface, that also provides power supply for the board. Has pushbuttons to reset the board and put it in upload mode. + +.. figure:: https://dl.espressif.com/dl/schematics/pictures/esp32-core-board-v2.png + :align: center + :alt: ESP32 Core Board V2 / ESP32 DevKitC board + :width: 50% + + ESP32 Core Board V2 / ESP32 DevKitC board + +Documentation +""""""""""""" + +* :doc:`../get-started/get-started-devkitc-v2` +* `ESP32 DevKitC V2 Schematic `__ (PDF) +* `CP210x USB to UART Bridge VCP Drivers `_ + + .. _esp-modules-and-boards-esp-wrover-kit-v1: ESP-WROVER-KIT V1 / ESP32 DevKitJ V1 diff --git a/docs/hw-reference/modules-and-boards.rst b/docs/hw-reference/modules-and-boards.rst index b300a5668..c1d8cd08d 100644 --- a/docs/hw-reference/modules-and-boards.rst +++ b/docs/hw-reference/modules-and-boards.rst @@ -104,6 +104,7 @@ Documentation * `ESP32-WROVER Datasheet `__ (PDF) * `ESP-PSRAM32 Datasheet `__ (PDF) +* `ESP32-WROVER Reference Design `_ PDF containing OrCAD schematic, PCB layout, gerbers and BOM .. _esp-modules-and-boards-esp32-pico-pit-v4: @@ -138,26 +139,31 @@ Previous Versions .. _esp-modules-and-boards-esp32-devkitc: -ESP32 Core Board V2 / ESP32 DevKitC -=================================== +ESP32 DevKitC V4 +================ -Small and convenient development board with :ref:`esp-modules-and-boards-esp-wroom-32` module installed, break out pin headers and minimum additional components. Includes USB to serial programming interface, that also provides power supply for the board. Has pushbuttons to reset the board and put it in upload mode. +Small and convenient development board with :ref:`esp-modules-and-boards-esp-wroom-32` module installed, break out pin headers and minimum additional components. Includes USB to serial programming interface, that also provides power supply for the board. Has pushbuttons to reset the board and put it in upload mode. Comparing to the previous :ref:`esp-modules-and-boards-esp32-devkitc-v2`, instead of ESP-WROOM-32 it can accommodate :ref:`esp-modules-and-boards-esp32-wrover` module and has CP2102N chip that supports faster baud rates. -.. figure:: https://dl.espressif.com/dl/schematics/pictures/esp32-core-board-v2.png +.. figure:: https://dl.espressif.com/dl/schematics/pictures/esp32-devkitc-v4-front.jpg :align: center - :alt: ESP32 Core Board V2 / ESP32 DevKitC board + :alt: ESP32 DevKitC V4 board :width: 50% - ESP32 Core Board V2 / ESP32 DevKitC board + ESP32 DevKitC V4 board Documentation ------------- * :doc:`../get-started/get-started-devkitc` -* `ESP32 DevKitC Schematic `__ (PDF) -* `ESP32 Development Board Reference Design `_ (ZIP) containing OrCAD schematic, PCB layout, gerbers and BOM +* `ESP32-DevKitC schematic `_ (PDF) +* `ESP32-DevKitC Reference Design `_ (ZIP) containing OrCAD schematic, PCB layout, gerbers and BOM * `CP210x USB to UART Bridge VCP Drivers `_ +Previous Versions +----------------- + +* :ref:`esp-modules-and-boards-esp32-devkitc-v2` + .. _esp-modules-and-boards-esp-wrover-kit-v3: diff --git a/docs/index.rst b/docs/index.rst index e4ee3fe4d..a7ff1ee91 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,7 +36,8 @@ This is the documentation for Espressif IoT Development Framework (`esp-idf + Get Started + 快速入门 API Reference H/W Reference API Guides diff --git a/docs/link-roles.py b/docs/link-roles.py index a7d549e82..a9e45145e 100644 --- a/docs/link-roles.py +++ b/docs/link-roles.py @@ -1,5 +1,6 @@ # based on http://protips.readthedocs.io/link-roles.html +import re from docutils import nodes from repo_util import run_cmd_get_output @@ -28,7 +29,14 @@ def setup(app): def autolink(pattern): def role(name, rawtext, text, lineno, inliner, options={}, content=[]): - url = pattern % (text,) - node = nodes.reference(rawtext, text, refuri=url, **options) + m = re.search('(.*)\s*<(.*)>', text) + if m: + link_text = m.group(1) + link = m.group(2) + else: + link_text = text + link = text + url = pattern % (link,) + node = nodes.reference(rawtext, link_text, refuri=url, **options) return [node], [] return role diff --git a/docs/requirements.txt b/docs/requirements.txt index 8aaf322a9..b85fc09a1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -7,4 +7,5 @@ breathe==4.7.3 sphinxcontrib.blockdiag==1.5.3 sphinxcontrib.seqdiag==0.8.5 sphinxcontrib.actdiag==0.8.5 -sphinxcontrib.nwdiag==0.9.5 \ No newline at end of file +sphinxcontrib.nwdiag==0.9.5 +recommonmark diff --git a/examples/bluetooth/a2dp_sink/README.md b/examples/bluetooth/a2dp_sink/README.md index b27393a73..cb45d838c 100755 --- a/examples/bluetooth/a2dp_sink/README.md +++ b/examples/bluetooth/a2dp_sink/README.md @@ -3,27 +3,17 @@ ESP-IDF A2DP-SINK demo Demo of A2DP audio sink role -This is the demo for user to use ESP_APIs to create a GATT Server. - -Options choose step: - 1. make menuconfig. - 2. enter menuconfig "Component config", choose "Bluetooth" - 3. enter menu Bluetooth, choose "Classic Bluetooth" and do not choose "Release DRAM from Classic BT controller" - 4. choose your options. +This is the demo of API implementing Advanced Audio Distribution Profile to receive an audio stream. For the I2S codec, pick whatever chip or board works for you; this code was written using a PCM5102 chip, but other I2S boards and chips will probably work as well. The default I2S connections are shown below, but these can be changed in menuconfig: -+-----------+--------------+ | ESP pin | I2S signal | -+===========+==============+ +| :-------- | :----------- | | GPIO22 | LRCK | -+-----------+--------------+ | GPIO25 | DATA | -+-----------+--------------+ | GPIO26 | BCK | -+-----------+--------------+ If the internal DAC is selected, analog audio will be available on GPIO25 and GPIO26. The output resolution on these pins will always be limited to 8 bit because of the internal structure of the DACs. -After the program started, other bluetooth devices such as smart phones can discover this device named "ESP_SPEAKER", and after connection is established, audio data can be transmitted and there will occur a count of audio data packets printed. +After the program is started, other bluetooth devices such as smart phones can discover a device named "ESP_SPEAKER". Once a connection is established, audio data can be transmitted. This will be visible in the application log including a count of audio data packets. \ No newline at end of file diff --git a/examples/bluetooth/a2dp_sink/main/bt_app_av.c b/examples/bluetooth/a2dp_sink/main/bt_app_av.c index 2698d4a5a..289c1f160 100644 --- a/examples/bluetooth/a2dp_sink/main/bt_app_av.c +++ b/examples/bluetooth/a2dp_sink/main/bt_app_av.c @@ -1,16 +1,11 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include #include @@ -37,6 +32,8 @@ static void bt_av_hdl_avrc_evt(uint16_t event, void *p_param); static uint32_t m_pkt_cnt = 0; static esp_a2d_audio_state_t m_audio_state = ESP_A2D_AUDIO_STATE_STOPPED; +static const char *m_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; +static const char *m_a2d_audio_state_str[] = {"Suspended", "Stopped", "Started"}; /* callback for A2DP sink */ void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) @@ -49,7 +46,7 @@ void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) break; } default: - ESP_LOGE(BT_AV_TAG, "a2dp invalid cb event: %d", event); + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); break; } } @@ -58,7 +55,7 @@ void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) { i2s_write_bytes(0, (const char *)data, len, portMAX_DELAY); if (++m_pkt_cnt % 100 == 0) { - ESP_LOGE(BT_AV_TAG, "audio data pkt cnt %u", m_pkt_cnt); + ESP_LOGI(BT_AV_TAG, "Audio packet count %u", m_pkt_cnt); } } @@ -85,7 +82,7 @@ void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param break; } default: - ESP_LOGE(BT_AV_TAG, "avrc invalid cb event: %d", event); + ESP_LOGE(BT_AV_TAG, "Invalid AVRC event: %d", event); break; } } @@ -97,12 +94,14 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) switch (event) { case ESP_A2D_CONNECTION_STATE_EVT: { a2d = (esp_a2d_cb_param_t *)(p_param); - ESP_LOGI(BT_AV_TAG, "a2dp conn_state_cb, state %d", a2d->conn_stat.state); + uint8_t *bda = a2d->conn_stat.remote_bda; + ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", + m_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); break; } case ESP_A2D_AUDIO_STATE_EVT: { a2d = (esp_a2d_cb_param_t *)(p_param); - ESP_LOGI(BT_AV_TAG, "a2dp audio_state_cb state %d", a2d->audio_stat.state); + ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", m_a2d_audio_state_str[a2d->audio_stat.state]); m_audio_state = a2d->audio_stat.state; if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { m_pkt_cnt = 0; @@ -111,7 +110,7 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) } case ESP_A2D_AUDIO_CFG_EVT: { a2d = (esp_a2d_cb_param_t *)(p_param); - ESP_LOGI(BT_AV_TAG, "a2dp audio_cfg_cb , codec type %d", a2d->audio_cfg.mcc.type); + ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type); // for now only SBC stream is supported if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) { int sample_rate = 16000; @@ -125,12 +124,12 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) } i2s_set_clk(0, sample_rate, 16, 2); - ESP_LOGI(BT_AV_TAG, "configure audio player %x-%x-%x-%x\n", + ESP_LOGI(BT_AV_TAG, "Configure audio player %x-%x-%x-%x", a2d->audio_cfg.mcc.cie.sbc[0], a2d->audio_cfg.mcc.cie.sbc[1], a2d->audio_cfg.mcc.cie.sbc[2], a2d->audio_cfg.mcc.cie.sbc[3]); - ESP_LOGI(BT_AV_TAG, "audio player configured, samplerate=%d", sample_rate); + ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate=%d", sample_rate); } break; } @@ -163,7 +162,7 @@ static void bt_av_hdl_avrc_evt(uint16_t event, void *p_param) switch (event) { case ESP_AVRC_CT_CONNECTION_STATE_EVT: { uint8_t *bda = rc->conn_stat.remote_bda; - ESP_LOGI(BT_AV_TAG, "avrc conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + ESP_LOGI(BT_AV_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); if (rc->conn_stat.connected) { @@ -172,21 +171,21 @@ static void bt_av_hdl_avrc_evt(uint16_t event, void *p_param) break; } case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { - ESP_LOGI(BT_AV_TAG, "avrc passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state); + ESP_LOGI(BT_AV_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state); break; } case ESP_AVRC_CT_METADATA_RSP_EVT: { - ESP_LOGI(BT_AV_TAG, "avrc metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); + ESP_LOGI(BT_AV_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); free(rc->meta_rsp.attr_text); break; } case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { - ESP_LOGI(BT_AV_TAG, "avrc event notification: %d, param: %d", rc->change_ntf.event_id, rc->change_ntf.event_parameter); + ESP_LOGI(BT_AV_TAG, "AVRC event notification: %d, param: %d", rc->change_ntf.event_id, rc->change_ntf.event_parameter); bt_av_notify_evt_handler(rc->change_ntf.event_id, rc->change_ntf.event_parameter); break; } case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { - ESP_LOGI(BT_AV_TAG, "avrc remote features %x", rc->rmt_feats.feat_mask); + ESP_LOGI(BT_AV_TAG, "AVRC remote features %x", rc->rmt_feats.feat_mask); break; } default: diff --git a/examples/bluetooth/a2dp_sink/main/bt_app_av.h b/examples/bluetooth/a2dp_sink/main/bt_app_av.h index 7300b9134..6648eb95e 100644 --- a/examples/bluetooth/a2dp_sink/main/bt_app_av.h +++ b/examples/bluetooth/a2dp_sink/main/bt_app_av.h @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #ifndef __BT_APP_AV_H__ #define __BT_APP_AV_H__ diff --git a/examples/bluetooth/a2dp_sink/main/bt_app_core.c b/examples/bluetooth/a2dp_sink/main/bt_app_core.c index 533dfda2f..6c48d9a70 100644 --- a/examples/bluetooth/a2dp_sink/main/bt_app_core.c +++ b/examples/bluetooth/a2dp_sink/main/bt_app_core.c @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include #include diff --git a/examples/bluetooth/a2dp_sink/main/bt_app_core.h b/examples/bluetooth/a2dp_sink/main/bt_app_core.h index 0238ba4e1..4415058a7 100644 --- a/examples/bluetooth/a2dp_sink/main/bt_app_core.h +++ b/examples/bluetooth/a2dp_sink/main/bt_app_core.h @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #ifndef __BT_APP_CORE_H__ #define __BT_APP_CORE_H__ diff --git a/examples/bluetooth/a2dp_sink/main/main.c b/examples/bluetooth/a2dp_sink/main/main.c index aa489f583..a60933862 100644 --- a/examples/bluetooth/a2dp_sink/main/main.c +++ b/examples/bluetooth/a2dp_sink/main/main.c @@ -125,7 +125,7 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) /* initialize A2DP sink */ esp_a2d_register_callback(&bt_app_a2d_cb); - esp_a2d_register_data_callback(bt_app_a2d_data_cb); + esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); esp_a2d_sink_init(); /* initialize AVRCP controller */ diff --git a/examples/bluetooth/a2dp_sink/sdkconfig.defaults b/examples/bluetooth/a2dp_sink/sdkconfig.defaults index 371169bd4..d99fa240c 100644 --- a/examples/bluetooth/a2dp_sink/sdkconfig.defaults +++ b/examples/bluetooth/a2dp_sink/sdkconfig.defaults @@ -1,6 +1,12 @@ # Override some defaults so BT stack is enabled and # Classic BT is enabled and BT_DRAM_RELEASE is disabled CONFIG_BT_ENABLED=y +CONFIG_BLUEDROID_ENABLED=y CONFIG_CLASSIC_BT_ENABLED=y -CONFIG_A2DP_SNK_ENABLED=y - +CONFIG_A2DP_ENABLE=y +CONFIG_A2DP_SINK_ENABLE=y +CONFIG_A2DP_SRC_ENABLE= +CONFIG_BT_SPP_ENABLED= +CONFIG_GATTS_ENABLE= +CONFIG_GATTC_ENABLE= +CONFIG_BLE_SMP_ENABLE= diff --git a/examples/bluetooth/a2dp_source/Makefile b/examples/bluetooth/a2dp_source/Makefile new file mode 100755 index 000000000..62e30c037 --- /dev/null +++ b/examples/bluetooth/a2dp_source/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := a2dp_source + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/bluetooth/a2dp_source/README.md b/examples/bluetooth/a2dp_source/README.md new file mode 100755 index 000000000..457ee5c40 --- /dev/null +++ b/examples/bluetooth/a2dp_source/README.md @@ -0,0 +1,18 @@ +ESP-IDF A2DP-SOURCE demo +======================== + +Demo of A2DP audio source role + +This is the demo for user to use ESP_APIs to use Advanced Audio Distribution Profile in transmitting audio stream + +Options choose step: + 1. make menuconfig. + 2. enter menuconfig "Component config", choose "Bluetooth" + 3. enter menu Bluetooth, choose "Bluedroid Enable" + 4. enter menu Bluedroid Enable, choose "Classic Bluetooth" + 5. select "A2DP" and choose "SOURCE" + +In this example, the bluetooth device implements A2DP source. The A2DP sink device to be connected to can be set up with the example "A2DP sink" in another folder in ESP-IDF example directory. +For the first step, the device performs device discovery to find a target device(A2DP sink) named "ESP_SPEAKER". Then it initiate connection with the target device. +After connection is established, the device then start media transmission. The raw PCM media stream to be encoded and transmited in this example is random sequence therefore continuous noise can be heard if the stream is decoded and played on the sink side. +After a period of time, media stream suspend, disconnection and reconnection procedure will be performed. diff --git a/examples/bluetooth/a2dp_source/main/bt_app_core.c b/examples/bluetooth/a2dp_source/main/bt_app_core.c new file mode 100644 index 000000000..be699ef8a --- /dev/null +++ b/examples/bluetooth/a2dp_source/main/bt_app_core.c @@ -0,0 +1,113 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "bt_app_core.h" + +static void bt_app_task_handler(void *arg); +static bool bt_app_send_msg(bt_app_msg_t *msg); +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +static xQueueHandle bt_app_task_queue = NULL; +static xTaskHandle bt_app_task_handle = NULL; + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) +{ + ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return bt_app_send_msg(&msg); + } + } + + return false; +} + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + for (;;) { + if (pdTRUE == xQueueReceive(bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + bt_app_work_dispatched(&msg); + break; + default: + ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig); + break; + } // switch (msg.sig) + + if (msg.param) { + free(msg.param); + } + } + } +} + +void bt_app_task_start_up(void) +{ + bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, bt_app_task_handle); + return; +} + +void bt_app_task_shut_down(void) +{ + if (bt_app_task_handle) { + vTaskDelete(bt_app_task_handle); + bt_app_task_handle = NULL; + } + if (bt_app_task_queue) { + vQueueDelete(bt_app_task_queue); + bt_app_task_queue = NULL; + } +} diff --git a/examples/bluetooth/a2dp_source/main/bt_app_core.h b/examples/bluetooth/a2dp_source/main/bt_app_core.h new file mode 100644 index 000000000..4415058a7 --- /dev/null +++ b/examples/bluetooth/a2dp_source/main/bt_app_core.h @@ -0,0 +1,47 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#ifndef __BT_APP_CORE_H__ +#define __BT_APP_CORE_H__ + +#include +#include +#include + +#define BT_APP_CORE_TAG "BT_APP_CORE" + +#define BT_APP_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + */ +typedef void (* bt_app_cb_t) (uint16_t event, void *param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to bt_app_task */ + uint16_t event; /*!< message event id */ + bt_app_cb_t cb; /*!< context switch callback */ + void *param; /*!< parameter area needs to be last */ +} bt_app_msg_t; + +/** + * @brief parameter deep-copy function to be customized + */ +typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src); + +/** + * @brief work dispatcher for the application task + */ +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback); + +void bt_app_task_start_up(void); + +void bt_app_task_shut_down(void); + +#endif /* __BT_APP_CORE_H__ */ diff --git a/examples/bluetooth/a2dp_source/main/component.mk b/examples/bluetooth/a2dp_source/main/component.mk new file mode 100755 index 000000000..0b9d7585e --- /dev/null +++ b/examples/bluetooth/a2dp_source/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/examples/bluetooth/a2dp_source/main/main.c b/examples/bluetooth/a2dp_source/main/main.c new file mode 100644 index 000000000..bd4529d20 --- /dev/null +++ b/examples/bluetooth/a2dp_source/main/main.c @@ -0,0 +1,520 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/timers.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "esp_bt.h" +#include "bt_app_core.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#define BT_AV_TAG "BT_AV" + +/* event for handler "bt_av_hdl_stack_up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/* A2DP global state */ +enum { + APP_AV_STATE_IDLE, + APP_AV_STATE_DISCOVERING, + APP_AV_STATE_DISCOVERED, + APP_AV_STATE_UNCONNECTED, + APP_AV_STATE_CONNECTING, + APP_AV_STATE_CONNECTED, + APP_AV_STATE_DISCONNECTING, +}; + +/* sub states of APP_AV_STATE_CONNECTED */ +enum { + APP_AV_MEDIA_STATE_IDLE, + APP_AV_MEDIA_STATE_STARTING, + APP_AV_MEDIA_STATE_STARTED, + APP_AV_MEDIA_STATE_STOPPING, +}; + +#define BT_APP_HEART_BEAT_EVT (0xff00) + +/// handler for bluetooth stack enabled events +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/// callback function for A2DP source +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/// callback function for A2DP source audio data stream +static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len); + +static void a2d_app_heart_beat(void *arg); + +/// A2DP application state machine +static void bt_app_av_sm_hdlr(uint16_t event, void *param); + +/* A2DP application state machine handler for each state */ +static void bt_app_av_state_unconnected(uint16_t event, void *param); +static void bt_app_av_state_connecting(uint16_t event, void *param); +static void bt_app_av_state_connected(uint16_t event, void *param); +static void bt_app_av_state_disconnecting(uint16_t event, void *param); + +static esp_bd_addr_t peer_bda = {0}; +static uint8_t peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; +static int m_a2d_state = APP_AV_STATE_IDLE; +static int m_media_state = APP_AV_MEDIA_STATE_IDLE; +static int m_intv_cnt = 0; +static int m_connecting_intv = 0; +static uint32_t m_pkt_cnt = 0; + +TimerHandle_t tmr; + +static char *bda2str(esp_bd_addr_t bda, char *str, size_t size) +{ + if (bda == NULL || str == NULL || size < 18) { + return NULL; + } + + uint8_t *p = bda; + sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", + p[0], p[1], p[2], p[3], p[4], p[5]); + return str; +} + +void app_main() +{ + // Initialize NVS. + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK( ret ); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + + if (esp_bt_controller_init(&bt_cfg) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize controller failed\n", __func__); + return; + } + + if (esp_bt_controller_enable(ESP_BT_MODE_BTDM) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable controller failed\n", __func__); + return; + } + + if (esp_bluedroid_init() != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed\n", __func__); + return; + } + + if (esp_bluedroid_enable() != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed\n", __func__); + return; + } + + /* create application task */ + bt_app_task_start_up(); + + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); +} + +static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len) +{ + uint8_t *rmt_bdname = NULL; + uint8_t rmt_bdname_len = 0; + + if (!eir) { + return false; + } + + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len); + if (!rmt_bdname) { + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len); + } + + if (rmt_bdname) { + if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) { + rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN; + } + + if (bdname) { + memcpy(bdname, rmt_bdname, rmt_bdname_len); + bdname[rmt_bdname_len] = '\0'; + } + if (bdname_len) { + *bdname_len = rmt_bdname_len; + } + return true; + } + + return false; +} + +static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param) +{ + char bda_str[18]; + uint32_t cod = 0; + int32_t rssi = -129; /* invalid value */ + uint8_t *eir = NULL; + esp_bt_gap_dev_prop_t *p; + + ESP_LOGI(BT_AV_TAG, "Scanned device: %s", bda2str(param->disc_res.bda, bda_str, 18)); + for (int i = 0; i < param->disc_res.num_prop; i++) { + p = param->disc_res.prop + i; + switch (p->type) { + case ESP_BT_GAP_DEV_PROP_COD: + cod = *(uint32_t *)(p->val); + ESP_LOGI(BT_AV_TAG, "--Class of Device: 0x%x", cod); + break; + case ESP_BT_GAP_DEV_PROP_RSSI: + rssi = *(int8_t *)(p->val); + ESP_LOGI(BT_AV_TAG, "--RSSI: %d", rssi); + break; + case ESP_BT_GAP_DEV_PROP_EIR: + eir = (uint8_t *)(p->val); + break; + case ESP_BT_GAP_DEV_PROP_BDNAME: + default: + break; + } + } + + /* search for device with MAJOR service class as "rendering" in COD */ + if (!esp_bt_gap_is_valid_cod(cod) || + !(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) { + return; + } + + /* search for device named "ESP_SPEAKER" in its extended inqury response */ + if (eir) { + get_name_from_eir(eir, peer_bdname, NULL); + if (strcmp((char *)peer_bdname, "ESP_SPEAKER") != 0) { + return; + } + + ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, peer_bdname); + m_a2d_state = APP_AV_STATE_DISCOVERED; + memcpy(peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); + ESP_LOGI(BT_AV_TAG, "Cancel device discovery ..."); + esp_bt_gap_cancel_discovery(); + } +} + + +void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + case ESP_BT_GAP_DISC_RES_EVT: { + filter_inquiry_scan_result(param); + break; + } + case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: { + if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) { + if (m_a2d_state == APP_AV_STATE_DISCOVERED) { + m_a2d_state = APP_AV_STATE_CONNECTING; + ESP_LOGI(BT_AV_TAG, "Device discovery stopped."); + ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", peer_bdname); + esp_a2d_source_connect(peer_bda); + } else { + // not discovered, continue to discover + ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover..."); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + } + } else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) { + ESP_LOGI(BT_AV_TAG, "Discovery started."); + } + break; + } + case ESP_BT_GAP_RMT_SRVCS_EVT: + case ESP_BT_GAP_RMT_SRVC_REC_EVT: + default: { + ESP_LOGI(BT_AV_TAG, "event: %d", event); + break; + } + } + return; +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event); + switch (event) { + case BT_APP_EVT_STACK_UP: { + /* set up device name */ + char *dev_name = "ESP_A2DP_SRC"; + esp_bt_dev_set_device_name(dev_name); + + /* register GAP callback function */ + esp_bt_gap_register_callback(bt_app_gap_cb); + + /* initialize A2DP source */ + esp_a2d_register_callback(&bt_app_a2d_cb); + esp_a2d_source_register_data_callback(bt_app_a2d_data_cb); + esp_a2d_source_init(); + + /* set discoverable and connectable mode */ + esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE); + + /* start device discovery */ + ESP_LOGI(BT_AV_TAG, "Starting device discovery..."); + m_a2d_state = APP_AV_STATE_DISCOVERING; + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + + /* create and start heart beat timer */ + do { + int tmr_id = 0; + tmr = xTimerCreate("connTmr", (10000 / portTICK_RATE_MS), + pdTRUE, (void *)tmr_id, a2d_app_heart_beat); + xTimerStart(tmr, portMAX_DELAY); + } while (0); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL); +} + +static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len) +{ + if (len < 0 || data == NULL) { + return 0; + } + + // generate random sequence + int val = rand() % (1 << 16); + for (int i = 0; i < (len >> 1); i++) { + data[(i << 1)] = val & 0xff; + data[(i << 1) + 1] = (val >> 8) & 0xff; + } + + return len; +} + +static void a2d_app_heart_beat(void *arg) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL); +} + +static void bt_app_av_sm_hdlr(uint16_t event, void *param) +{ + ESP_LOGE(BT_AV_TAG, "%s state %d, evt 0x%x", __func__, m_a2d_state, event); + switch (m_a2d_state) { + case APP_AV_STATE_DISCOVERING: + case APP_AV_STATE_DISCOVERED: + break; + case APP_AV_STATE_UNCONNECTED: + bt_app_av_state_unconnected(event, param); + break; + case APP_AV_STATE_CONNECTING: + bt_app_av_state_connecting(event, param); + break; + case APP_AV_STATE_CONNECTED: + bt_app_av_state_connected(event, param); + break; + case APP_AV_STATE_DISCONNECTING: + bt_app_av_state_disconnecting(event, param); + break; + default: + ESP_LOGE(BT_AV_TAG, "%s invalid state %d", __func__, m_a2d_state); + break; + } +} + +static void bt_app_av_state_unconnected(uint16_t event, void *param) +{ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + break; + case BT_APP_HEART_BEAT_EVT: { + uint8_t *p = peer_bda; + ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x", + p[0], p[1], p[2], p[3], p[4], p[5]); + esp_a2d_source_connect(peer_bda); + m_a2d_state = APP_AV_STATE_CONNECTING; + m_connecting_intv = 0; + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_state_connecting(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp connected"); + m_a2d_state = APP_AV_STATE_CONNECTED; + m_media_state = APP_AV_MEDIA_STATE_IDLE; + + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + m_a2d_state = APP_AV_STATE_UNCONNECTED; + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + break; + case BT_APP_HEART_BEAT_EVT: + if (++m_connecting_intv >= 2) { + m_a2d_state = APP_AV_STATE_UNCONNECTED; + m_connecting_intv = 0; + } + break; + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_media_proc(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (m_media_state) { + case APP_AV_MEDIA_STATE_IDLE: { + if (event == BT_APP_HEART_BEAT_EVT) { + ESP_LOGI(BT_AV_TAG, "a2dp media ready checking ..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); + } else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media ready, starting ..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START); + m_media_state = APP_AV_MEDIA_STATE_STARTING; + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTING: { + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media start successfully."); + m_intv_cnt = 0; + m_media_state = APP_AV_MEDIA_STATE_STARTED; + } else { + // not started succesfully, transfer to idle state + ESP_LOGI(BT_AV_TAG, "a2dp media start failed."); + m_media_state = APP_AV_MEDIA_STATE_IDLE; + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTED: { + if (event == BT_APP_HEART_BEAT_EVT) { + if (++m_intv_cnt >= 10) { + ESP_LOGI(BT_AV_TAG, "a2dp media stopping..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); + m_media_state = APP_AV_MEDIA_STATE_STOPPING; + m_intv_cnt = 0; + } + } + break; + } + case APP_AV_MEDIA_STATE_STOPPING: { + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media stopped successfully, disconnecting..."); + m_media_state = APP_AV_MEDIA_STATE_IDLE; + esp_a2d_source_disconnect(peer_bda); + m_a2d_state = APP_AV_STATE_DISCONNECTING; + } else { + ESP_LOGI(BT_AV_TAG, "a2dp media stopping..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); + } + } + break; + } + } +} + +static void bt_app_av_state_connected(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp disconnected"); + m_a2d_state = APP_AV_STATE_UNCONNECTED; + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + m_pkt_cnt = 0; + } + break; + } + case ESP_A2D_AUDIO_CFG_EVT: + // not suppposed to occur for A2DP source + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + case BT_APP_HEART_BEAT_EVT: { + bt_app_av_media_proc(event, param); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_state_disconnecting(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp disconnected"); + m_a2d_state = APP_AV_STATE_UNCONNECTED; + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + case BT_APP_HEART_BEAT_EVT: + break; + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} diff --git a/examples/bluetooth/a2dp_source/sdkconfig.defaults b/examples/bluetooth/a2dp_source/sdkconfig.defaults new file mode 100644 index 000000000..1ec00447a --- /dev/null +++ b/examples/bluetooth/a2dp_source/sdkconfig.defaults @@ -0,0 +1,12 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled +CONFIG_BT_ENABLED=y +CONFIG_BLUEDROID_ENABLED=y +CONFIG_CLASSIC_BT_ENABLED=y +CONFIG_A2DP_ENABLE=y +CONFIG_A2DP_SINK_ENABLE= +CONFIG_A2DP_SRC_ENABLE=y +CONFIG_BT_SPP_ENABLED= +CONFIG_GATTS_ENABLE= +CONFIG_GATTC_ENABLE= +CONFIG_BLE_SMP_ENABLE= diff --git a/examples/bluetooth/ble_adv/main/app_bt.c b/examples/bluetooth/ble_adv/main/app_bt.c index 74697844e..c843985a5 100644 --- a/examples/bluetooth/ble_adv/main/app_bt.c +++ b/examples/bluetooth/ble_adv/main/app_bt.c @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include #include diff --git a/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.c b/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.c index 8ab8a241a..333913bd7 100644 --- a/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.c +++ b/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ /**************************************************************************** diff --git a/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.h b/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.h index b83b1a959..946abab54 100644 --- a/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.h +++ b/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.h @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #ifndef __ESP_EDDYSTONE_API_H__ diff --git a/examples/bluetooth/ble_eddystone/main/esp_eddystone_demo.c b/examples/bluetooth/ble_eddystone/main/esp_eddystone_demo.c index 95f239431..593dda124 100644 --- a/examples/bluetooth/ble_eddystone/main/esp_eddystone_demo.c +++ b/examples/bluetooth/ble_eddystone/main/esp_eddystone_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ /**************************************************************************** diff --git a/examples/bluetooth/ble_eddystone/main/esp_eddystone_protocol.h b/examples/bluetooth/ble_eddystone/main/esp_eddystone_protocol.h index da183f382..5c286c928 100644 --- a/examples/bluetooth/ble_eddystone/main/esp_eddystone_protocol.h +++ b/examples/bluetooth/ble_eddystone/main/esp_eddystone_protocol.h @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #ifndef __ESP_EDDYSTONE_PROTOCOL_H__ #define __ESP_EDDYSTONE_PROTOCOL_H__ diff --git a/examples/bluetooth/ble_ibeacon/main/esp_ibeacon_api.c b/examples/bluetooth/ble_ibeacon/main/esp_ibeacon_api.c index 96d602a24..277f007f7 100644 --- a/examples/bluetooth/ble_ibeacon/main/esp_ibeacon_api.c +++ b/examples/bluetooth/ble_ibeacon/main/esp_ibeacon_api.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ diff --git a/examples/bluetooth/ble_ibeacon/main/esp_ibeacon_api.h b/examples/bluetooth/ble_ibeacon/main/esp_ibeacon_api.h index b91cd646b..f04b638a0 100644 --- a/examples/bluetooth/ble_ibeacon/main/esp_ibeacon_api.h +++ b/examples/bluetooth/ble_ibeacon/main/esp_ibeacon_api.h @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ diff --git a/examples/bluetooth/ble_ibeacon/main/ibeacon_demo.c b/examples/bluetooth/ble_ibeacon/main/ibeacon_demo.c index ded437828..17c71f7b9 100644 --- a/examples/bluetooth/ble_ibeacon/main/ibeacon_demo.c +++ b/examples/bluetooth/ble_ibeacon/main/ibeacon_demo.c @@ -1,8 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ diff --git a/examples/bluetooth/ble_spp_client/README.md b/examples/bluetooth/ble_spp_client/README.md index b0d24946c..ad3377509 100644 --- a/examples/bluetooth/ble_spp_client/README.md +++ b/examples/bluetooth/ble_spp_client/README.md @@ -1,3 +1,80 @@ -ESP-IDF SPP GATT CLIENT demo -======================== +# ESP-IDF SPP GATT CLIENT demo +## 1. Overview + + In Bluetooth classic (BR/EDR) systems, a Serial Port Profile (SPP) is an adopted profile defined by the Bluetooth Special Interest Group (SIG) used to emulate a serial port connection over a Bluetooth wireless connection. For BLE systems, an adopted SPP profile over BLE is not defined, thus emulation of a serial port must be implemented as a vendor-specific custom profile. + + This reference design consists of two Demos, the ble spp server and ble spp client that run on their respective endpoints. These devices connect and exchange data wirelessly with each other. This capability creates a virtual serial link over the air. Each byte input can be sent and received by both the server and client. The spp server is implemented as the [ble_spp_server](../ble_spp_server) demo while the spp client is implemented as the [ble_spp_client](../ble_spp_client) demo. Espressif designed the BLE SPP applications to use the UART transport layer but you could adapt this design to work with other serial protocols, such as SPI. + +## 2. Packet Structure + + After the Uart received data, the data will be posted to Uart task. Then, in the UART_DATA event, the raw data may be retrieved. The max length is 120 bytes every time. + If you run the ble spp demo with two ESP32 chips, the MTU size will be exchanged for 200 bytes after the ble connection is established, so every packet can be send directly. + If you only run the ble_spp_server demo, and it was connected by a phone, the MTU size may be less than 123 bytes. In such a case the data will be split into fragments and send in turn. + In every packet, we add 4 bytes to indicate that this is a fragment packet. The first two bytes contain "##" if this is a fragment packet, the third byte is the total number of the packets, the fourth byte is the current number of this packet. + The phone APP need to check the structure of the packet if it want to communicate with the ble_spp_server demo. + +## 3. Software Description + + The application is implemented in [spp_client_demo.c](../ble_spp_client/main/spp_client_demo.c) and [spp_server_demo.c](../ble_spp_server/main/ble_spp_server_demo.c). + +### 3.1 Initialization + + Both the server and client will first initialize the uart and ble. The server demo will set up the serial port service with standard GATT and GAP services in the attribute server. The client demo will scan the ble broadcast over the air to find the spp server. + +### 3.2 Event Processing + + The spp server has two main event processing functions for BLE event: + + * static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t * param); + * static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t * param); + + The spp client has two main event processing function for BLE event: + + * esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t * param); + * void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t * param); + + These are some queues and tasks used by SPP application: + + Queues: + + * spp_uart_queue - Uart data messages received from the Uart + * cmd_cmd_queue - commands received from the client + * cmd_heartbeat_queue - heartbeat received, if supported + + Tasks: + + * uart_task - processs Uart + * spp_cmd_task - process command messages, the commands and processing were defined by customer + * spp_heartbeat_task - if heartbeat is supported, the task will send a heatbeat packet to the remote device + +### 3.3 Sending Data Wirelessly + + The client will be sending WriteNoRsp packets to the server. The server side sends data through notifications. When the Uart receives data, the Uart task places it in the buffer. if the size of the data is larger than (MTU size - 3), the data will be split into packets and send in turn. + +### 3.4 Receiving Data Wirelessly + + The server will receive this data in the ESP_GATTS_WRITE_EVT event and send data to the Uart terminal by `uart_wrire_bytes` function. For example: + + case ESP_GATTS_WRITE_EVT: + ... + if(res == SPP_IDX_SPP_DATA_RECV_VAL){ + uart_write_bytes(UART_NUM_0, (char *)(p_data->write.value), p_data->write.len); + } + ... + break; + +### 3.5 GATT Server Attribute Table + + charactertistic|UUID|Permissions + :-:|:-:|:-: + SPP_DATA_RECV_CHAR|0xABF1|READ&WRITE_NR + SPP_DATA_NOTIFY_CHAR|0xABF2|READ&NOTIFY + SPP_COMMAND_CHAR|0xABF3|READ&WRITE_NR + SPP_STATUS_CHAR|0xABF4|READ & NOTIFY + SPP_HEARTBEAT_CHAR|0xABF5|READ&WRITE_NR&NOTIFY + +## 4. How to run these demos + + Compile and download each application to the ESP32. The spp cilent will auto connect to the spp server, do service search, exchange MTU size and register notification. + if you input data to the Uart terminal, it will be print in the remote device Uart terminal. diff --git a/examples/bluetooth/ble_spp_client/main/spp_client_demo.c b/examples/bluetooth/ble_spp_client/main/spp_client_demo.c index 7aa28e76a..7597ffb2e 100644 --- a/examples/bluetooth/ble_spp_client/main/spp_client_demo.c +++ b/examples/bluetooth/ble_spp_client/main/spp_client_demo.c @@ -1,15 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ /**************************************************************************** * diff --git a/examples/bluetooth/ble_spp_server/README.md b/examples/bluetooth/ble_spp_server/README.md index 17298a363..ab46f531b 100644 --- a/examples/bluetooth/ble_spp_server/README.md +++ b/examples/bluetooth/ble_spp_server/README.md @@ -1,5 +1,3 @@ -ESP-IDF GATT SERVER SPP demo -=============================================== - -This is the demo for user to realization BLE Serial Port Profile. +## ESP-IDF GATT SERVER SPP demo +For description of this application please refer to [ESP-IDF GATT CLIENT SPP demo](../ble_spp_client/README.md) diff --git a/examples/bluetooth/ble_spp_server/main/ble_spp_server_demo.c b/examples/bluetooth/ble_spp_server/main/ble_spp_server_demo.c index 6ed2a2380..50c819082 100644 --- a/examples/bluetooth/ble_spp_server/main/ble_spp_server_demo.c +++ b/examples/bluetooth/ble_spp_server/main/ble_spp_server_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include "freertos/FreeRTOS.h" diff --git a/examples/bluetooth/ble_spp_server/main/ble_spp_server_demo.h b/examples/bluetooth/ble_spp_server/main/ble_spp_server_demo.h index 6cd300610..15b3b67b7 100644 --- a/examples/bluetooth/ble_spp_server/main/ble_spp_server_demo.h +++ b/examples/bluetooth/ble_spp_server/main/ble_spp_server_demo.h @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include diff --git a/examples/bluetooth/blufi/main/blufi_example_main.c b/examples/bluetooth/blufi/main/blufi_example_main.c index a780d606c..afe6fb6bc 100644 --- a/examples/bluetooth/blufi/main/blufi_example_main.c +++ b/examples/bluetooth/blufi/main/blufi_example_main.c @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include #include @@ -139,6 +133,38 @@ static esp_err_t example_net_event_handler(void *ctx, system_event_t *event) esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL); } break; + case SYSTEM_EVENT_SCAN_DONE: { + uint16_t apCount = 0; + esp_wifi_scan_get_ap_num(&apCount); + if (apCount == 0) { + BLUFI_INFO("Nothing AP found"); + break; + } + wifi_ap_record_t *ap_list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); + if (!ap_list) { + BLUFI_ERROR("malloc error, ap_list is NULL"); + break; + } + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, ap_list)); + esp_blufi_ap_record_t * blufi_ap_list = (esp_blufi_ap_record_t *)malloc(apCount * sizeof(esp_blufi_ap_record_t)); + if (!blufi_ap_list) { + if (ap_list) { + free(ap_list); + } + BLUFI_ERROR("malloc error, blufi_ap_list is NULL"); + break; + } + for (int i = 0; i < apCount; ++i) + { + blufi_ap_list[i].rssi = ap_list[i].rssi; + memcpy(blufi_ap_list[i].ssid, ap_list[i].ssid, sizeof(ap_list[i].ssid)); + } + esp_blufi_send_wifi_list(apCount, blufi_ap_list); + esp_wifi_scan_stop(); + free(ap_list); + free(blufi_ap_list); + break; + } default: break; } @@ -181,8 +207,8 @@ static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_para break; case ESP_BLUFI_EVENT_BLE_CONNECT: BLUFI_INFO("BLUFI ble connect\n"); - server_if=param->connect.server_if; - conn_id=param->connect.conn_id; + server_if = param->connect.server_if; + conn_id = param->connect.conn_id; esp_ble_gap_stop_advertising(); blufi_security_init(); break; @@ -203,6 +229,10 @@ static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_para BLUFI_INFO("BLUFI requset wifi disconnect from AP\n"); esp_wifi_disconnect(); break; + case ESP_BLUFI_EVENT_REPORT_ERROR: + BLUFI_ERROR("BLUFI report error, error code %d\n", param->report_error.state); + esp_blufi_send_error_info(param->report_error.state); + break; case ESP_BLUFI_EVENT_GET_WIFI_STATUS: { wifi_mode_t mode; esp_blufi_extra_info_t info; @@ -225,7 +255,7 @@ static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_para } case ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE: BLUFI_INFO("blufi close a gatt connection"); - esp_blufi_close(server_if,conn_id); + esp_blufi_close(server_if, conn_id); break; case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA: /* TODO */ @@ -283,6 +313,16 @@ static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_para esp_wifi_set_config(WIFI_IF_AP, &ap_config); BLUFI_INFO("Recv SOFTAP CHANNEL %d\n", ap_config.ap.channel); break; + case ESP_BLUFI_EVENT_GET_WIFI_LIST:{ + wifi_scan_config_t scanConf = { + .ssid = NULL, + .bssid = NULL, + .channel = 0, + .show_hidden = false + }; + ESP_ERROR_CHECK(esp_wifi_scan_start(&scanConf, true)); + break; + } case ESP_BLUFI_EVENT_RECV_USERNAME: /* Not handle currently */ break; @@ -361,9 +401,17 @@ void app_main() BLUFI_INFO("BLUFI VERSION %04x\n", esp_blufi_get_version()); - blufi_security_init(); - esp_ble_gap_register_callback(example_gap_event_handler); + ret = esp_ble_gap_register_callback(example_gap_event_handler); + if(ret){ + BLUFI_ERROR("%s gap register failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_blufi_register_callbacks(&example_callbacks); + if(ret){ + BLUFI_ERROR("%s blufi register failed, error code = %x\n", __func__, ret); + return; + } - esp_blufi_register_callbacks(&example_callbacks); esp_blufi_profile_init(); } diff --git a/examples/bluetooth/blufi/main/blufi_security.c b/examples/bluetooth/blufi/main/blufi_security.c index fae034dd2..c32d3ad49 100644 --- a/examples/bluetooth/blufi/main/blufi_security.c +++ b/examples/bluetooth/blufi/main/blufi_security.c @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include #include @@ -75,6 +69,8 @@ static int myrand( void *rng_state, unsigned char *output, size_t len ) return( 0 ); } +extern void btc_blufi_report_error(esp_blufi_error_state_t state); + void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free) { int ret; @@ -82,6 +78,7 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da if (blufi_sec == NULL) { BLUFI_ERROR("BLUFI Security is not initialized"); + btc_blufi_report_error(ESP_BLUFI_INIT_SECURITY_ERROR); return; } @@ -94,6 +91,7 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da } blufi_sec->dh_param = (uint8_t *)malloc(blufi_sec->dh_param_len); if (blufi_sec->dh_param == NULL) { + btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR); BLUFI_ERROR("%s, malloc failed\n", __func__); return; } @@ -101,6 +99,7 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da case SEC_TYPE_DH_PARAM_DATA:{ if (blufi_sec->dh_param == NULL) { BLUFI_ERROR("%s, blufi_sec->dh_param == NULL\n", __func__); + btc_blufi_report_error(ESP_BLUFI_DH_PARAM_ERROR); return; } uint8_t *param = blufi_sec->dh_param; @@ -108,6 +107,7 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da ret = mbedtls_dhm_read_params(&blufi_sec->dhm, ¶m, ¶m[blufi_sec->dh_param_len]); if (ret) { BLUFI_ERROR("%s read param failed %d\n", __func__, ret); + btc_blufi_report_error(ESP_BLUFI_READ_PARAM_ERROR); return; } free(blufi_sec->dh_param); @@ -115,6 +115,7 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da ret = mbedtls_dhm_make_public(&blufi_sec->dhm, (int) mbedtls_mpi_size( &blufi_sec->dhm.P ), blufi_sec->self_public_key, blufi_sec->dhm.len, myrand, NULL); if (ret) { BLUFI_ERROR("%s make public failed %d\n", __func__, ret); + btc_blufi_report_error(ESP_BLUFI_MAKE_PUBLIC_ERROR); return; } diff --git a/examples/bluetooth/bt_discovery/main/bt_discovery.c b/examples/bluetooth/bt_discovery/main/bt_discovery.c index 7c0bf5320..dd5bb4f44 100644 --- a/examples/bluetooth/bt_discovery/main/bt_discovery.c +++ b/examples/bluetooth/bt_discovery/main/bt_discovery.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ diff --git a/examples/bluetooth/bt_discovery/sdkconfig.defaults b/examples/bluetooth/bt_discovery/sdkconfig.defaults index dd570f866..ab028190f 100644 --- a/examples/bluetooth/bt_discovery/sdkconfig.defaults +++ b/examples/bluetooth/bt_discovery/sdkconfig.defaults @@ -2,4 +2,7 @@ # Classic BT is enabled and BT_DRAM_RELEASE is disabled CONFIG_BT_ENABLED=y CONFIG_CLASSIC_BT_ENABLED=y - +CONFIG_A2DP_ENABLE= +CONFIG_GATTS_ENABLE= +CONFIG_GATTC_ENABLE= +CONFIG_BLE_SMP_ENABLE= diff --git a/examples/bluetooth/bt_spp_acceptor/main/example_spp_acceptor_demo.c b/examples/bluetooth/bt_spp_acceptor/main/example_spp_acceptor_demo.c index e46f018aa..b893dd62c 100644 --- a/examples/bluetooth/bt_spp_acceptor/main/example_spp_acceptor_demo.c +++ b/examples/bluetooth/bt_spp_acceptor/main/example_spp_acceptor_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include #include diff --git a/examples/bluetooth/bt_spp_initiator/main/example_spp_initiator_demo.c b/examples/bluetooth/bt_spp_initiator/main/example_spp_initiator_demo.c index 11d1efd25..d7659f5e3 100644 --- a/examples/bluetooth/bt_spp_initiator/main/example_spp_initiator_demo.c +++ b/examples/bluetooth/bt_spp_initiator/main/example_spp_initiator_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include #include diff --git a/examples/bluetooth/controller_hci_uart/main/controller_hci_uart_demo.c b/examples/bluetooth/controller_hci_uart/main/controller_hci_uart_demo.c index 872aca64f..836caafbe 100644 --- a/examples/bluetooth/controller_hci_uart/main/controller_hci_uart_demo.c +++ b/examples/bluetooth/controller_hci_uart/main/controller_hci_uart_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include #include diff --git a/examples/bluetooth/gatt_client/README.md b/examples/bluetooth/gatt_client/README.md index 847c1bb64..cacdbaf76 100644 --- a/examples/bluetooth/gatt_client/README.md +++ b/examples/bluetooth/gatt_client/README.md @@ -1,9 +1,10 @@ -ESP-IDF GATT CLIENT demo +ESP-IDF Gatt Client Demo ======================== -This is the demo for user to use ESP_APIs to create a GATT Client. -Run the gatt_server demo, the client demo will automatically connect to the gatt_server demo. -Client demo will enable gatt_server's notify after connection. Then the two devices will exchange -data. +This is the demo for users to use ESP_APIs to create a GATT Client. -Please check the [tutorial](tutorial/gatt_client_example_walkthrough.md) for more information about this example. +To test this demo, you can run the [gatt_server_demo](../gatt_server), which creates services and starts advertising. `Gatt_client_demo` will start scanning and connect to the `gatt_server_demo` automatically. + +This demo will enable gatt server's notification function once the connection is established and then the devices start exchanging data. + +Please check the [tutorial](tutorial/Gatt_Client_Example_Walkthrough.md) for more information about this example. diff --git a/examples/bluetooth/gatt_client/main/gattc_demo.c b/examples/bluetooth/gatt_client/main/gattc_demo.c index 3dac18266..40ea97ed2 100644 --- a/examples/bluetooth/gatt_client/main/gattc_demo.c +++ b/examples/bluetooth/gatt_client/main/gattc_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ diff --git a/examples/bluetooth/gatt_client/tutorial/gatt_client_example_walkthrough.md b/examples/bluetooth/gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md similarity index 77% rename from examples/bluetooth/gatt_client/tutorial/gatt_client_example_walkthrough.md rename to examples/bluetooth/gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md index aca6ca34e..45761427c 100644 --- a/examples/bluetooth/gatt_client/tutorial/gatt_client_example_walkthrough.md +++ b/examples/bluetooth/gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md @@ -2,11 +2,11 @@ ## Introduction -In this example, the GATT Client example code for the ESP32 is reviewed. This example implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) client which scans for nearby peripheral servers and connects to a predefined service. The client then searches for available characteristics and subscribes to a known characteristic in order to receive notifications or indications. The example can register an Application Profile and initializes a sequence of events, which can be used to configure Generic Access Profile (GAP) parameters and to handle events such as scanning, connecting to peripherals and reading and writing characteristics. +In this tutorial, the GATT client example code for the ESP32 is reviewed. The code implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) client, which scans for nearby peripheral servers and connects to a predefined service. The client then searches for available characteristics and subscribes to a known characteristic in order to receive notifications or indications. The example can register an Application Profile and initializes a sequence of events, which can be used to configure Generic Access Profile (GAP) parameters and to handle events such as scanning, connecting to peripherals and reading and writing characteristics. # Includes -This example is located in the examples folder of the ESP-IDF under the `bluetooth/gatt_client/main` directory. The `gattc_demo.c` file located in the main folder contains all the functionality that we are going to review. The header files contained in `gattc_demo.c` are: +This example is located in the examples folder of the ESP-IDF under the [bluetooth/gatt_client/main](../main). The [gattc_demo.c](../main/gattc_demo.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [gattc_demo.c](../main/gattc_demo.c) are: ```c #include @@ -54,7 +54,7 @@ void app_main() return; } - ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); if (ret) { ESP_LOGE(GATTC_TAG, "%s enable controller failed, error code = %x\n", __func__, ret); return; @@ -102,12 +102,12 @@ void app_main() The main function starts by initializing the non-volatile storage library. This library allows to save key-value pairs in flash memory and is used by some components such as the Wi-Fi library to save the SSID and password: ```c - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK( ret ); +esp_err_t ret = nvs_flash_init(); +if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); +} +ESP_ERROR_CHECK( ret ); ``` ## BT Controller and Stack Initialization @@ -116,15 +116,16 @@ The main function also initializes the BT controller by first creating a BT cont ```c esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - ret = esp_bt_controller_init(&bt_cfg); +ret = esp_bt_controller_init(&bt_cfg); ``` -Next, the controller is enabled in Dual Mode (BLE + BT Classic). +Next, the controller is enabled in BLE Mode. ```c -ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM); +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); ``` - +>The controller should be enabled in `ESP_BT_MODE_BTDM`, if you want to use the dual mode (BLE + BT). + There are four Bluetooth modes supported: 1. `ESP_BT_MODE_IDLE`: Bluetooth not running @@ -202,9 +203,8 @@ The Application Profile are stored in the `gl_profile_tab` array, which is initi ```c /* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { - [PROFILE_A_APP_ID] = { - .gattc_cb = gattc_profile_event_handler, - .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + [PROFILE_A_APP_ID] = {.gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ }, }; ``` @@ -261,25 +261,15 @@ typedef struct { esp_ble_scan_type_t scan_type; /*!< Scan type */ esp_ble_addr_type_t own_addr_type; /*!< Owner address type */ esp_ble_scan_filter_t scan_filter_policy; /*!< Scan filter policy */ - uint16_t scan_interval; /*!< Scan interval. This is defined - as the time interval from - when the Controller started its - last LE scan until it begins the - subsequent LE scan. - Range: 0x0004 to 0x4000 - Default: 0x0010 (10 ms) - Time = N * 0.625 msec - Time Range: 2.5 msec to 10.24 - seconds*/ - uint16_t scan_window; /*!< Scan window. The duration of - the LE scan. LE_Scan_Window - shall be less than or equal to - LE_Scan_Interval - Range: 0x0004 to 0x4000 - Default: 0x0010 (10 ms) - Time = N * 0.625 msec - Time Range: 2.5 msec to 10240 - msec */ + uint16_t scan_interval; /*!< Scan interval. This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan.*/ + //Range: 0x0004 to 0x4000 + //Default: 0x0010 (10 ms) + //Time = N * 0.625 msec + //Time Range: 2.5 msec to 10.24 seconds + uint16_t scan_window; /*!< Scan window. The duration of the LE scan. LE_Scan_Window shall be less than or equal to LE_Scan_Interval*/ + //Range: 0x0004 to 0x4000 //Default: 0x0010 (10 ms) + //Time = N * 0.625 msec + //Time Range: 2.5 msec to 10240 msec } esp_ble_scan_params_t; ``` An it is initialized as: @@ -318,7 +308,7 @@ Once the scanning parameters are set, an `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EV uint32_t duration = 30; esp_ble_gap_start_scanning(duration); break; - } + } ``` The scanning is started using the `esp_ble_gap_start_scanning()` function which takes a parameter representing the duration of the continuous scanning (in seconds). Once the scanning period is ended, an `ESP_GAP_SEARCH_INQ_CMPL_EVT` event is triggered. @@ -333,19 +323,17 @@ The results of the scanning are displayed as soon as they arrive with the `ESP_G */ struct ble_scan_result_evt_param { esp_gap_search_evt_t search_evt; /*!< Search event type */ - esp_bd_addr_t bda; /*!< Bluetooth device address which - has been searched */ + esp_bd_addr_t bda; /*!< Bluetooth device address which has been searched */ esp_bt_dev_type_t dev_type; /*!< Device type */ esp_ble_addr_type_t ble_addr_type; /*!< Ble device address type */ esp_ble_evt_type_t ble_evt_type; /*!< Ble scan result event type */ int rssi; /*!< Searched device's RSSI */ - uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX]; /*!< Received EIR */ + uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX]; /*!< Received EIR */ int flag; /*!< Advertising data flag bit */ int num_resps; /*!< Scan result number */ uint8_t adv_data_len; /*!< Adv data length */ uint8_t scan_rsp_len; /*!< Scan response length */ - } scan_rst; /*!< Event parameter of - ESP_GAP_BLE_SCAN_RESULT_EVT */ + } scan_rst; /*!< Event parameter of ESP_GAP_BLE_SCAN_RESULT_EVT */ ``` This event also includes a list of sub events as shown below: @@ -353,34 +341,30 @@ This event also includes a list of sub events as shown below: ```c /// Sub Event of ESP_GAP_BLE_SCAN_RESULT_EVT typedef enum { - ESP_GAP_SEARCH_INQ_RES_EVT = 0, /*!< Inquiry result for a peer - device. */ + ESP_GAP_SEARCH_INQ_RES_EVT = 0, /*!< Inquiry result for a peer device. */ ESP_GAP_SEARCH_INQ_CMPL_EVT = 1, /*!< Inquiry complete. */ - ESP_GAP_SEARCH_DISC_RES_EVT = 2, /*!< Discovery result for a peer - device. */ - ESP_GAP_SEARCH_DISC_BLE_RES_EVT = 3, /*!< Discovery result for BLE GATT - based service on a peer device. - */ + ESP_GAP_SEARCH_DISC_RES_EVT = 2, /*!< Discovery result for a peer device. */ + ESP_GAP_SEARCH_DISC_BLE_RES_EVT = 3, /*!< Discovery result for BLE GATT based service on a peer device. */ ESP_GAP_SEARCH_DISC_CMPL_EVT = 4, /*!< Discovery complete. */ ESP_GAP_SEARCH_DI_DISC_CMPL_EVT = 5, /*!< Discovery complete. */ ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT = 6, /*!< Search cancelled */ } esp_gap_search_evt_t; ``` We are interested in the `ESP_GAP_SEARCH_INQ_RES_EVT` event, which is called every time a new device is found. We are also interested in the `ESP_GAP_SEARCH_INQ_CMPL_EVT`, which is triggered when the duration of the scanning is completed and can be used to restart the scanning procedure: + ```c case ESP_GAP_BLE_SCAN_RESULT_EVT: { esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; switch (scan_result->scan_rst.search_evt) { - case ESP_GAP_SEARCH_INQ_RES_EVT: - esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); - ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); - adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, - ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); - ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len); - esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); - ESP_LOGI(GATTC_TAG, "\n"); - if (adv_name != NULL) { - if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); + ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len); + esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); + ESP_LOGI(GATTC_TAG, "\n"); + if (adv_name != NULL) { + if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { ESP_LOGI(GATTC_TAG, "searched device %s\n", remote_device_name); if (connect == false) { connect = true; @@ -402,7 +386,7 @@ Every time we receive a result from the `ESP_GAP_SEARCH_INQ_RES_EVT` event, the ```c case ESP_GAP_SEARCH_INQ_RES_EVT: - esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); ``` The client then prints the advertised data length and the scan response length: @@ -414,9 +398,7 @@ ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_resul In order to get the device name, we use the `esp_ble_resolve_adv_data()` function, which takes the advertised data stored in `scan_result->scan_rst.ble_adv`, the type of advertising data and the length, in order to extract the value from the advertising packet frame. Then the device name is printed. ```c -adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, - ESP_BLE_AD_TYPE_NAME_CMPL, - &adv_name_len); +adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len); esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); ``` @@ -564,55 +546,55 @@ static esp_gatt_srvc_id_t remote_service_id = { Once defined, we can get the characteristics from that service using the `esp_ble_gattc_get_characteristic()` function, which is called in the `ESP_GATTC_SEARCH_CMPL_EVT` event after the search for services is completed and the client has found the service that it was looking for. ```c - case ESP_GATTC_SEARCH_CMPL_EVT: - if (p_data->search_cmpl.status != ESP_GATT_OK){ - ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); - break; +case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + conn_id = p_data->search_cmpl.conn_id;h + if (get_server){ + uint16_t count = 0; + esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, + p_data->search_cmpl.conn_id,ESP_GATT_DB_CHARACTERISTIC, gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); } - conn_id = p_data->search_cmpl.conn_id;h - if (get_server){ - uint16_t count = 0; - esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, - p_data->search_cmpl.conn_id,ESP_GATT_DB_CHARACTERISTIC, gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, - INVALID_HANDLE, - &count); - if (status != ESP_GATT_OK){ - ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); - } - if (count > 0){ - char_elem_result = (esp_gattc_char_elem_t*)malloc - (sizeof(esp_gattc_char_elem_t) * count); - if (!char_elem_result){ - ESP_LOGE(GATTC_TAG, "gattc no mem"); - }else{ - status = esp_ble_gattc_get_char_by_uuid( gattc_if, - p_data->search_cmpl.conn_id, - gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, - gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, - remote_filter_char_uuid, - char_elem_result, - &count); - if (status != ESP_GATT_OK){ - ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error"); - } - - /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, - so we used first 'char_elem_result' */ - if (count > 0 && (char_elem_result[0].properties - &ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ - gl_profile_tab[PROFILE_A_APP_ID].char_handle = - char_elem_result[0].char_handle; - esp_ble_gattc_register_for_notify (gattc_if, - gl_profile_tab[PROFILE_A_APP_ID].remote_bda, - char_elem_result[0].char_handle); - } - } - /* free char_elem_result */ - free(char_elem_result); + if (count > 0){ + char_elem_result = (esp_gattc_char_elem_t*)malloc + (sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result){ + ESP_LOGE(GATTC_TAG, "gattc no mem"); }else{ - ESP_LOGE(GATTC_TAG, "no char found"); - } } + status = esp_ble_gattc_get_char_by_uuid( gattc_if, + p_data->search_cmpl.conn_id, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + remote_filter_char_uuid, + char_elem_result, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error"); + } + + /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, + so we used first 'char_elem_result' */ + if (count > 0 && (char_elem_result[0].properties + &ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_A_APP_ID].char_handle = + char_elem_result[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].remote_bda, + char_elem_result[0].char_handle); + } + } + /* free char_elem_result */ + free(char_elem_result); + }else{ + ESP_LOGE(GATTC_TAG, "no char found"); + } } break; ``` @@ -625,14 +607,11 @@ The client can register to receive notifications from the server every time the ```c … /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */ - if(count > 0 && (char_elem_result[0].properties & - ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ - gl_profile_tab[PROFILE_A_APP_ID].char_handle = - char_elem_result[0].char_handle; - esp_ble_gattc_register_for_notify (gattc_if, - gl_profile_tab[PROFILE_A_APP_ID].remote_bda, - char_elem_result[0].char_handle); - } + if(count > 0 && (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, + char_elem_result[0].char_handle); + } … ``` @@ -642,18 +621,15 @@ This procedure registers notifications to the BLE stack, and triggers an `ESP_GA case ESP_GATTC_REG_FOR_NOTIFY_EVT: { ESP_LOGI(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT"); if (p_data->reg_for_notify.status != ESP_GATT_OK){ - ESP_LOGE(GATTC_TAG, "REG FOR NOTIFY failed: error status = %d", - p_data->reg_for_notify.status); + ESP_LOGE(GATTC_TAG, "REG FOR NOTIFY failed: error status = %d", p_data->reg_for_notify.status); }else{ uint16_t count = 0; uint16_t notify_en = 1; - esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, - gl_profile_tab[PROFILE_A_APP_ID].conn_id, - ESP_GATT_DB_DESCRIPTOR, - gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, - gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, - gl_profile_tab[PROFILE_A_APP_ID].char_handle, - &count); + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, &count); if (ret_status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); } @@ -662,27 +638,27 @@ This procedure registers notifications to the BLE stack, and triggers an `ESP_GA if (!descr_elem_result){ ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); }else{ - ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if, - gl_profile_tab[PROFILE_A_APP_ID].conn_id, - p_data->reg_for_notify.handle, - notify_descr_uuid, - descr_elem_result, - &count); + ret_status = esp_ble_gattc_get_descr_by_char_handle( + gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + p_data->reg_for_notify.handle, + notify_descr_uuid, + descr_elem_result,&count); + if (ret_status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error"); } /* Erery char have only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */ - if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && -descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ - ret_status = esp_ble_gattc_write_char_descr( gattc_if, - gl_profile_tab[PROFILE_A_APP_ID].conn_id, - descr_elem_result[0].handle, - sizeof(notify_en), - (Uint8 *)¬ify_en, - ESP_GATT_WRITE_TYPE_RSP, - ESP_GATT_AUTH_REQ_NONE); + if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ + ret_status = esp_ble_gattc_write_char_descr( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + descr_elem_result[0].handle, + sizeof(notify_en), + (Uint8 *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); } if (ret_status != ESP_GATT_OK){ diff --git a/examples/bluetooth/gatt_security_client/README.md b/examples/bluetooth/gatt_security_client/README.md index 92c2132ea..07bd019c3 100644 --- a/examples/bluetooth/gatt_security_client/README.md +++ b/examples/bluetooth/gatt_security_client/README.md @@ -1,9 +1,13 @@ -ESP-IDF GATT SECURITY CLIENT DEMO +ESP-IDF Gatt Security Client Demo ======================== -This is the demo for user to use ESP BLE security API to connection with peer device & communication. -1.Should used the esp_ble_gap_set_security_param API to set the security parameter to the BLE stack in the init stage; -2.Used the esp_ble_set_encryption API to start encryption with peer device, if the peer device take the initiative encryption, should used the esp_ble_gap_security_rsp API to sent response to peer device when receive the ESP_GAP_BLE_SEC_REQ_EVT. -3.It will receive the ESP_GAP_BLE_AUTH_CMPL_EVT event when encryption finish will peer device. +This is the demo for users to use ESP BLE security APIs to connect to and encrypt with peer devices. -Please check the [tutorial](tutorial/GATT_Security_Client_Example_Walkthrough.md) for more information about this example. +To test this demo, you can run the [gatt_security_server_demo](../gatt_security_server), which starts advertising and can be connected to this demo automatically. + +There are some important points for this demo: +1.`esp_ble_gap_set_security_param` should be used to set the security parameters in the initial stage; +2.`esp_ble_set_encryption` should be used to start encryption with peer device. If the peer device initiates the encryption, `esp_ble_gap_security_rsp` should be used to send security response to the peer device when `ESP_GAP_BLE_SEC_REQ_EVT` is received. +3.The `gatt_security_client_demo` will receive a `ESP_GAP_BLE_AUTH_CMPL_EVT` once the encryption procedure has completed. + +Please check the [tutorial](tutorial/Gatt_Security_Client_Example_Walkthrough.md) for more information about this example. diff --git a/examples/bluetooth/gatt_security_client/main/example_ble_sec_gattc_demo.c b/examples/bluetooth/gatt_security_client/main/example_ble_sec_gattc_demo.c index 3679b33af..ee0ee4170 100644 --- a/examples/bluetooth/gatt_security_client/main/example_ble_sec_gattc_demo.c +++ b/examples/bluetooth/gatt_security_client/main/example_ble_sec_gattc_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ diff --git a/examples/bluetooth/gatt_security_client/tutorial/GATT_Security_Client_Example_Walkthrough.md b/examples/bluetooth/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md similarity index 61% rename from examples/bluetooth/gatt_security_client/tutorial/GATT_Security_Client_Example_Walkthrough.md rename to examples/bluetooth/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md index ebb9ee59f..9942605dc 100644 --- a/examples/bluetooth/gatt_security_client/tutorial/GATT_Security_Client_Example_Walkthrough.md +++ b/examples/bluetooth/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md @@ -4,42 +4,42 @@ ## Introduction -This documents presents a review of the GATT Security Client code example for the ESP32. The GATT Client is capable of scanning for nearby devices and once it has found a device of interest, it requests a secure connection. The GATT client behaves as a master device that initiates a connection to a slave by sending a *Pairing Request* as specified by the [Bluetooth Core Specification Version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification). The remote slave device is normally a GATT Server that exposes Services and Characteristics. The slave replies with a *Pairing Response* followed by authentication and exchange of keys. If the bonding process is also executed, the Long Term Keys are stored for subsequent connections. Finally an encrypted channel is established which can support protection against Man-In-The-Middle (MITM) attacks depending on the security configuration. The code is implemented using an Application Profile that upon registration, allows to set the local privacy configuration as events are triggered during the lifetime of the program. +This document presents a review of the GATT Security Client code example for the ESP32. The GATT Client is capable of scanning for nearby devices and once it has found a device of interest, it requests a secure connection. The GATT client behaves as a master device that initiates a connection to a slave by sending a *Pairing Request* as specified by the [Bluetooth Core Specification Version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification). The remote slave device is normally a GATT Server that exposes Services and Characteristics. The slave replies with a *Pairing Response* followed by authentication and exchange of keys. If the bonding process is also executed, the Long Term Keys are stored for subsequent connections. Finally an encrypted channel is established which can support protection against Man-In-The-Middle (MITM) attacks depending on the security configuration. The code is implemented using an Application Profile that upon registration, allows to set the local privacy configuration as events are triggered during the lifetime of the program. -This document only includes a description of the security aspects of the GATT Client implementation, for a review of how to define the functionality of the GATT client such as scanning parameters and opening connections please refer to **GATT Client Example Walkthrough**. +This document only includes a description of the security aspects of the GATT Client implementation, for a review of how to define the functionality of the GATT client such as scanning parameters and opening connections please refer to [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). ## Configuring Local Privacy of the Security Client The example registers one Application Profile defined as: -``` +```c #define PROFILE_NUM 1 #define PROFILE_A_APP_ID 0 ``` The registration takes place in the ``app_main()`` function by using the ``esp_ble_gattc_app_register()`` function: -``` +```c … ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); - if (ret){ - ESP_LOGE(GATTC_TAG, "%s gattc app register error, error code = %x\n", __func__, ret); - } +if (ret){ + ESP_LOGE(GATTC_TAG, "%s gattc app register error, error code = %x\n", __func__, ret); +} … ``` The Application Profile registration triggers an ``ESP_GATTC_REG_EVT`` event which is managed by the ``esp_gattc_cb()`` callback function and forwarded to the Profile A event handler ``gattc_profile_event_handler()``. Here, the event is used to configure the local privacy of the slave device by using the ``esp_ble_gap_config_local_privacy()`` function. -``` +```c case ESP_GATTC_REG_EVT: - ESP_LOGI(GATTC_TAG, "REG_EVT"); - esp_ble_gap_config_local_privacy(true); - break; + ESP_LOGI(GATTC_TAG, "REG_EVT"); + esp_ble_gap_config_local_privacy(true); + break; ``` This function is a Bluedroid API call for configuring default privacy settings on the local device. Once the privacy is set, an ``ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT`` is triggered which is used to set scan parameters and start scanning for nearby peripherals: -``` +```c case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS){ ESP_LOGE(GATTC_TAG, "config local privacy failed, error code =%x", param->local_privacy_cmpl.status); @@ -70,22 +70,23 @@ This function is a Bluedroid API call for configuring default privacy settings o The rest of the configuration for the GATT Client is performed normally in the same way as the regular GATT Client example. That is, the client finds a device of interest and opens a connection. At this point the GATT client, which is usually the master, initiates the pairing process by sending a Pairing Request to the slave device. This request should be acknowledged with a Pairing Response. The Pairing process is implemented automatically by the stack and no extra user configuration is needed. However, depending on the I/O capabilities of both devices, a passkey might be generated on the ESP32 which is presented to the user with the ``ESP_GAP_BLE_PASSKEY_NOTIF_EVT``: -``` - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. - ///show the passkey number to the user to input it in the peer deivce. - ESP_LOGE(GATTS_TABLE_TAG, "The passkey Notify number:%d", param->ble_security.key_notif.passkey); - break; +```c + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: + ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + ///show the passkey number to the user to input it in the peer deivce. + ESP_LOGE(GATTS_TABLE_TAG, "The passkey Notify number:%d", param->ble_security.key_notif.passkey); + break; ``` The combination of input and output capabilities that determine which algorithm is used are: - | Display Only | Display Yes/No|Keyboard Only|No Input No Output|Keyboard Display| ---------------------|------------------|-----------------------|--------------------|------------------|-----------------------| -Display Only | Just Works| Just Works|Passkey Entry|Just Works|Passkey Entry| -Display Yes/No |Just Works|Just Works|Passkey Entry|Just Works|Passkey Entry| -Keyboard Only |Passkey Entry |Passkey Entry|Passkey Entry|Just Works|Passkey Entry| -No Input No Output| Just Works|Just Works|Just Works|Just Works|Just Works| -Keyboard Display|Passkey Entry|Passkey Entry|Passkey Entry|Just Works|Passkey Entry| +| | Display Only | Display Yes/No | Keyboard Only | No Input No Output | Keyboard Display| +| :-- | :------------- | :------------- | :------------- | :----------------- | :-------------- | +| **Display Only** | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry | +| **Display Yes/No** | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry | +| **Keyboard Only** | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry | +| **No Input No Output** | Just Works | Just Works | Just Works | Just Works | Just Works | +| **Keyboard Display** | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry | In the Just Works method, the Temporary Key is set to 0. This is a practical way to authenticate devices when no display or keyboards are attached to them, so that there is no way to show or enter a passkey. However, if the ESP32 GATT Client has an LCD, it can present the passkey generated locally so that the user can input it on the other peer device, or if the GATT Client has a keyboard, it can input the passkey generated by the other peer device. Additionally, a numeric comparison can be performed if both devices have a display and yes/no confirm buttons and LE Secure Connections are used, that way an independently generated passkey is displayed on both devices and the user manually checks that both 6-digit confirmation values match. @@ -93,27 +94,26 @@ In the Just Works method, the Temporary Key is set to 0. This is a practical way When the client connects to a remote device and the pairing is done successfully, the initiator and responder keys are exchanged. For each key exchange message, an ``ESP_GAP_BLE_KEY_EVT`` event is triggered which can be used to print the type of key received: -``` +```c case ESP_GAP_BLE_KEY_EVT: - //shows the ble key info share with peer device to the user. - ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type)); - break; + //shows the ble key info share with peer device to the user. + ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type)); + break; ``` When the keys are exchanged successfully, the pairing process is completed and encryption of payload data can be started using the AES-128 engine. This triggers an ``ESP_GAP_BLE_AUTH_CMPL_EVT`` event which is used to print information: -``` +```c case ESP_GAP_BLE_AUTH_CMPL_EVT: { - esp_bd_addr_t bd_addr; - memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); - ESP_LOGI(GATTS_TABLE_TAG, "remote BD_ADDR: %08x%04x",\ - (bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3], - (bd_addr[4] << 8) + bd_addr[5]); - ESP_LOGI(GATTS_TABLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type); - ESP_LOGI(GATTS_TABLE_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail"); - break; + esp_bd_addr_t bd_addr; + memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTS_TABLE_TAG, "remote BD_ADDR: %08x%04x",\ + (bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3], (bd_addr[4] << 8) + bd_addr[5]); + ESP_LOGI(GATTS_TABLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type); + ESP_LOGI(GATTS_TABLE_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail"); + break; ``` ## Conclusion -In this document, a review of the security aspects of the GATT Client has been realized. BLE security encompasses Pairing, Bonding and Encryption. In order to establish a secure link between a master and a slave device, the local privacy of the GATT client is set, which allows the BLE stack to set necessary security parameters automatically without the need of additional user configuration. The combination of features and capabilities of the peer devices results in the selection of the appropriate pairing method which the BLE stack then executes. Immediately, the required keys are generated and exchanged and the encryption of subsequent messages is started using the AES-128 engine. These steps trigger different events that are managed by the GATT and GAP event handlers which can be used to print useful information such as the types of keys exchanged and the pairing status. The rest of the security GATT client functionality such as registering for notifications of characteristics is implemented in the same way as in **GATT Client Example Walkthrough**. \ No newline at end of file +In this document, a review of the security aspects of the GATT Client has been realized. BLE security encompasses Pairing, Bonding and Encryption. In order to establish a secure link between a master and a slave device, the local privacy of the GATT client is set, which allows the BLE stack to set necessary security parameters automatically without the need of additional user configuration. The combination of features and capabilities of the peer devices results in the selection of the appropriate pairing method which the BLE stack then executes. Immediately, the required keys are generated and exchanged and the encryption of subsequent messages is started using the AES-128 engine. These steps trigger different events that are managed by the GATT and GAP event handlers which can be used to print useful information such as the types of keys exchanged and the pairing status. The rest of the security GATT client functionality such as registering for notifications of characteristics is implemented in the same way as in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). \ No newline at end of file diff --git a/examples/bluetooth/gatt_security_server/README.md b/examples/bluetooth/gatt_security_server/README.md index 5e3958350..61067098d 100644 --- a/examples/bluetooth/gatt_security_server/README.md +++ b/examples/bluetooth/gatt_security_server/README.md @@ -1,10 +1,14 @@ -ESP-IDF GATT SECURITY SERVICE demo +ESP-IDF Gatt Security Server Demo ======================== -This is the demo for user to use ESP BLE security API to connection with peer device & communication. -1.Should used the esp_ble_gap_set_security_param API to set the security parameter to the BLE stack in the init stage; -2.Used the esp_ble_set_encryption API to start encryption with peer device, if the peer device take the initiative encryption, should used the esp_ble_gap_security_rsp API to sent response to peer device when receive the ESP_GAP_BLE_SEC_REQ_EVT. -3.It will receive the ESP_GAP_BLE_AUTH_CMPL_EVT event when encryption finish will peer device. +This is the demo of APIs to connect to and encrypt with peer devices. -Please check the [tutorial](tutorial/GATT_SECURITY_SERVER_EXAMPLE_WALKTHROUGH.md) for more information about this example. +To test this demo, you can run [gatt_security_client_demo](../gatt_security_client), which starts scanning, connects to and starts encryption with `gatt_security_server_demo` automatically. + +There are some important points for this demo: +1.`esp_ble_gap_set_security_param` should be used to set the security parameters in the initial stage; +2.`esp_ble_set_encryption` should be used to start encryption with peer device. If the peer device initiates the encryption, `esp_ble_gap_security_rsp` should be used to send security response to the peer device when `ESP_GAP_BLE_SEC_REQ_EVT` is received. +3.The `gatt_security_client_demo` will receive a `ESP_GAP_BLE_AUTH_CMPL_EVT` once the encryption procedure has completed. + +Please check the [tutorial](tutorial/Gatt_Security_Server_Example_Walkthrough.md) for more information about this example. diff --git a/examples/bluetooth/gatt_security_server/main/example_ble_sec_gatts_demo.c b/examples/bluetooth/gatt_security_server/main/example_ble_sec_gatts_demo.c index 5b7d460c4..952b0dc7d 100644 --- a/examples/bluetooth/gatt_security_server/main/example_ble_sec_gatts_demo.c +++ b/examples/bluetooth/gatt_security_server/main/example_ble_sec_gatts_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include "freertos/FreeRTOS.h" #include "freertos/task.h" diff --git a/examples/bluetooth/gatt_security_server/main/example_ble_sec_gatts_demo.h b/examples/bluetooth/gatt_security_server/main/example_ble_sec_gatts_demo.h index 9cc7ec04c..f8f7c83ce 100644 --- a/examples/bluetooth/gatt_security_server/main/example_ble_sec_gatts_demo.h +++ b/examples/bluetooth/gatt_security_server/main/example_ble_sec_gatts_demo.h @@ -1,11 +1,10 @@ -/* BLE Security example_ble_security_gatts_demo example - 2 - 3 This example code is in the Public Domain (or CC0 licensed, at your option.) - 4 - 5 Unless required by applicable law or agreed to in writing, this - 6 software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - 7 CONDITIONS OF ANY KIND, either express or implied. - 8 */ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include diff --git a/examples/bluetooth/gatt_security_server/tutorial/GATT_SECURITY_SERVER_EXAMPLE_WALKTHROUGH.md b/examples/bluetooth/gatt_security_server/tutorial/Gatt_Security_Server_Example_Walkthrough.md similarity index 87% rename from examples/bluetooth/gatt_security_server/tutorial/GATT_SECURITY_SERVER_EXAMPLE_WALKTHROUGH.md rename to examples/bluetooth/gatt_security_server/tutorial/Gatt_Security_Server_Example_Walkthrough.md index f4086cd9e..5c311d95d 100644 --- a/examples/bluetooth/gatt_security_server/tutorial/GATT_SECURITY_SERVER_EXAMPLE_WALKTHROUGH.md +++ b/examples/bluetooth/gatt_security_server/tutorial/Gatt_Security_Server_Example_Walkthrough.md @@ -2,7 +2,7 @@ ## Introduction -In this document, a description of the security GATT Server BLE example for the ESP32 is presented. The security configuration enables a GATT Server acting as a slave device to bond with a master and establish an encrypted link between them. This functionality is defined by the [Bluetooth Specification version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification) and implemented on the ESP-IDF BLE stack, specifically on the Security Manager Protocol (SMP) API. +In this document, a description of the security GATT Server BLE example for the ESP32 is presented. The security configuration enables a GATT Server acting as a slave device to bond with a master and establish an encrypted link between them. This functionality is defined by the [Bluetooth Specification version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification) and implemented on the ESP-IDF BLE stack, specifically on the Security Manager Protocol (SMP) API. BLE security involves three interrelated concepts: pairing, bonding and encryption. Pairing concerns with the exchange of security features and types of keys needed. In addition, the pairing procedure takes care of the generation and exchange of shared keys. The core specification defines the legacy pairing and Secure Connections pairing (introduced in Bluetooth 4.2), which are both supported by ESP32. Once the exchange of shared keys is completed, a temporary encrypted link is established to exchange short term and long term keys. Bonding refers to storing the exchanged keys for subsequent connections so that they do not have to be transmitted again. Finally, encryption pertains to the ciphering of plain text data using the AES-128 engine and the shared keys. Server attributes may also be defined to allow only encrypted write and read messages. At any point of the communication, a slave device can always ask to start encryption by issuing a security request to the other peer device, which returns a security response by calling an API. @@ -30,18 +30,17 @@ In code, these parameters are defined as follows: The possible values for *IO Capabilities* are: ```c - ESP_IO_CAP_OUT 0 /*!< DisplayOnly */ - ESP_IO_CAP_IO 1 /*!< DisplayYesNo */ - ESP_IO_CAP_IN 2 /*!< KeyboardOnly */ - ESP_IO_CAP_NONE 3 /*!< NoInputNoOutput */ - ESP_IO_CAP_KBDISP 4 /*!< Keyboard display */ + ESP_IO_CAP_OUT 0 /*!< DisplayOnly */ + ESP_IO_CAP_IO 1 /*!< DisplayYesNo */ + ESP_IO_CAP_IN 2 /*!< KeyboardOnly */ + ESP_IO_CAP_NONE 3 /*!< NoInputNoOutput */ + ESP_IO_CAP_KBDISP 4 /*!< Keyboard display */ ``` * *Authorization Request*: ```c - esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND; -//bonding with peer device after authentication + esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND; //bonding with peer device after authentication ``` The possible values for *Authorization Request* are a combination of Bonding, MITM protection and Secure Connections requests: @@ -74,7 +73,7 @@ In code, these parameters are defined as follows: ```c uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; -``` + ``` The responder distributes the LTK and IRK keys by the setting the *EncKey* and *IdKey* masks. @@ -88,7 +87,7 @@ In code, these parameters are defined as follows: esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t)); ``` -This information is enough for the BLE stack to perform the pairing process, including pairing confirmation and key generation. The procedure is invisible to the user and executed automatically by the stack. + This information is enough for the BLE stack to perform the pairing process, including pairing confirmation and key generation. The procedure is invisible to the user and executed automatically by the stack. ## Connecting and Bonding to a Peer Device @@ -109,7 +108,7 @@ The types of encryptions available are: The difference between `ESP_BLE_SEC_ENCRYPT` and `ESP_BLE_SEC_ENCRYPT_NO_MITM` lies in the fact that a previous connection might have a security level that needs to be upgraded, therefore requires to exchange keys again. -In this example, the I/O capabilities are set to *No Input No Output*, therefore the *Just Works* pairing method, which doesn't not require the generation of a random 6-digit passkey, is used(For details, please refer to the the table below.) The user may modify the example to set the I/O capabilities to use other than *No Input No Output*. Therefore, depending on the I/O capabilities of the remote device, a passkey might be generated on the ESP32 which is presented to the user with the `ESP_GAP_BLE_PASSKEY_NOTIF_EVT`: +In this example, the I/O capabilities are set to *No Input No Output*, therefore the *Just Works* pairing method, which doesn't not require the generation of a random 6-digit passkey, is used (For details, please refer to the the table below). The user may modify the example to set the I/O capabilities to use other than *No Input No Output*. Therefore, depending on the I/O capabilities of the remote device, a passkey might be generated on the ESP32 which is presented to the user with the `ESP_GAP_BLE_PASSKEY_NOTIF_EVT`: ```c case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: @@ -121,6 +120,14 @@ case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: The combination of input and output capabilities that determine which algorithm is used are: +| | Display Only | Display Yes/No | Keyboard Only | No Input No Output | Keyboard Display| +| :-- | :------------- | :------------- | :------------- | :----------------- | :-------------- | +| **Display Only** | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry | +| **Display Yes/No** | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry | +| **Keyboard Only** | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry | +| **No Input No Output** | Just Works | Just Works | Just Works | Just Works | Just Works | +| **Keyboard Display** | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry | + ## Exchanging Keys @@ -128,7 +135,7 @@ When the client connects to the server and the pairing is done successfully, the * Local device LTK * Local device IRK -* Local device CSRK +* Local device CSRK * Peer device LTK * Peer device IRK @@ -192,8 +199,8 @@ When creating the services table, each attribute can have permissions to read or [HRS_IDX_BOBY_SENSOR_LOC_VAL] = { {ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, - (uint8_t *)&body_sensor_location_uuid, - ESP_GATT_PERM_READ_ENCRYPTED, + (uint8_t *)&body_sensor_location_uuid, + ESP_GATT_PERM_READ_ENCRYPTED, sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val} @@ -217,7 +224,7 @@ When creating the services table, each attribute can have permissions to read or During the communication between a master and a slave device, the slave might request to start encryption at any moment by issuing a security request command. This command will trigger an `ESP_GAP_BLE_SEC_REQ_EVT` event on the master, which will reply a positive (true) security response to the peer device to accept the request or a negative (false) one to reject the request. In this example, this event is used to reply a start encryption response by using the `esp_ble_gap_security_rsp()` API call. ```c - case ESP_GAP_BLE_SEC_REQ_EVT: +case ESP_GAP_BLE_SEC_REQ_EVT: /* send the positive (true) security response to the peer device to accept the security request. If not accept the security request, should sent the security response with negative(false) accept value*/ esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); @@ -225,5 +232,4 @@ During the communication between a master and a slave device, the slave might re ``` ## Conclusion -In this document, a review of the security aspects of the GATT Server has been realized. BLE security encompasses Pairing, Bonding and Encryption. In order to establish a secured link between a master and a slave device, security parameters that define the capabilities and features each device possess are set. The combination of features and capabilities of the peer devices results in the selection of the appropriate pairing method which the BLE stack then executes. Immediately, the required keys are generated and exchanged, and the encryption of subsequent messages is started using the AES-128 engine and these keys. These steps trigger different events that are managed by the GATT and GAP event handlers which can be used to print useful information such as the types of keys exchanged and the pairing status. In addition, attribute permissions are appointed to allow only encrypted read and write events when needed. The rest of the Security GATT server functionalities such as defining services and characteristics are implemented in the same way as presented in the GATT Server example. - +In this document, a review of the security aspects of the GATT Server has been realized. BLE security encompasses Pairing, Bonding and Encryption. In order to establish a secured link between a master and a slave device, security parameters that define the capabilities and features each device possess are set. The combination of features and capabilities of the peer devices results in the selection of the appropriate pairing method which the BLE stack then executes. Immediately, the required keys are generated and exchanged, and the encryption of subsequent messages is started using the AES-128 engine and these keys. These steps trigger different events that are managed by the GATT and GAP event handlers which can be used to print useful information such as the types of keys exchanged and the pairing status. In addition, attribute permissions are appointed to allow only encrypted read and write events when needed. The rest of the Security GATT server functionalities such as defining services and characteristics are implemented in the same way as presented in the GATT Server example. \ No newline at end of file diff --git a/examples/bluetooth/gatt_server/README.md b/examples/bluetooth/gatt_server/README.md index 3df4ecb79..5bda20a97 100644 --- a/examples/bluetooth/gatt_server/README.md +++ b/examples/bluetooth/gatt_server/README.md @@ -1,10 +1,13 @@ -ESP-IDF GATT SERVER demo +ESP-IDF Gatt Server Demo ======================== -This is the demo for user to use ESP_APIs to create a GATT Server.The demo can send adv data, -be connected by client. Run the gatt_client demo, the client demo will automatically connect -to the gatt_server demo. The client demo will enable gatt_server's notify after connection. -Then the two devices will exchange data. +This is the demo of APIs to create a GATT service by adding attributes one by one. However, this method is defined by Bluedroid and is difficult for users to use. -Please check the [tutorial] (./tutorial/GATT_SERVER_EXAMPLE_WALKTHROUGH.md) for more information about this example. +Hence, we also allow users to create a GATT service with an attribute table, which releases the user from adding attributes one by one. And it is recommended for users to use. For more information about this method, please refer to [gatt_server_service_table_demo](../gatt_server_service_table). + +This demo creates GATT a service and then starts advertising, waiting to be connected to a GATT client. + +To test this demo, we can run the [gatt_client_demo](../gatt_client), which can scan for and connect to this demo automatically. They will start exchanging data once the GATT client has enabled the notification function of the GATT server. + +Please check the [tutorial](tutorial/Gatt_Server_Example_Walkthrough.md) for more information about this example. diff --git a/examples/bluetooth/gatt_server/main/gatts_demo.c b/examples/bluetooth/gatt_server/main/gatts_demo.c index 4a60cb6d4..a02a3e64e 100644 --- a/examples/bluetooth/gatt_server/main/gatts_demo.c +++ b/examples/bluetooth/gatt_server/main/gatts_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ /**************************************************************************** * diff --git a/examples/bluetooth/gatt_server/tutorial/GATT_SERVER_EXAMPLE_WALKTHROUGH.md b/examples/bluetooth/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md similarity index 93% rename from examples/bluetooth/gatt_server/tutorial/GATT_SERVER_EXAMPLE_WALKTHROUGH.md rename to examples/bluetooth/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md index 88ba01ebf..a717a3bff 100644 --- a/examples/bluetooth/gatt_server/tutorial/GATT_SERVER_EXAMPLE_WALKTHROUGH.md +++ b/examples/bluetooth/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md @@ -57,7 +57,7 @@ The entry point to this example is the app_main() function: return; } - ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); if (ret) { ESP_LOGE(GATTS_TAG, "%s enable controller failed\n", __func__); return; @@ -98,7 +98,7 @@ The entry point to this example is the app_main() function: ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret); } return; -} +} ``` The main function starts by initializing the non-volatile storage library. This library allows to save key-value pairs in flash memory and is used by some components such as the Wi-Fi library to save the SSID and password: @@ -113,11 +113,13 @@ The main function also initializes the BT controller by first creating a BT cont esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); ret = esp_bt_controller_init(&bt_cfg); ``` -Next, the controller is enabled in Dual Mode (BLE + BT Classic). + +Next, the controller is enabled in BLE Mode. ```c -ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM); +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); ``` +>The controller should be enabled in `ESP_BT_MODE_BTDM`, if you want to use the dual mode (BLE + BT). There are four Bluetooth modes supported: @@ -142,10 +144,10 @@ The functions `gatts_event_handler()` and `gap_event_handler()` handle all the e ## APPLICATION PROFILES -The GATT Server example application is organized by using Application Profiles as shown in Fig 1. Each Application Profile describes a way to group functionalities that are designed for one client application, such as a mobile app running on a smartphone or tablet. In this way, a single design, enabled by different Application Profiles, can behave differently when used by different smartphone apps, allowing the server to react differently according to the client app that is being used. In practice, each profile is seen by the client as an independent BLE service. It is up to the client to discriminate the services that it is interested in. +The GATT Server example application is organized by using Application Profiles as shown in the figure below. Each Application Profile describes a way to group functionalities that are designed for one client application, such as a mobile app running on a smartphone or tablet. In this way, a single design, enabled by different Application Profiles, can behave differently when used by different smartphone apps, allowing the server to react differently according to the client app that is being used. In practice, each profile is seen by the client as an independent BLE service. It is up to the client to discriminate the services that it is interested in. + +
Application Profiles are used to organize a BLE application in order to implement different functionality for different clients.
-![](image/GATT SERVER FIGURE 1.png) -

Fig.1. Application Profiles are used to organize a BLE application in order to implement different functionality for different clients.

Each profile is defined as a struct where the struct members depend on the services and characteristics that are implemented in that Application Profile. The members also include a GATT interface, Application ID, Connection ID and a callback function to handle profile events. In this example, each profile is composed by: @@ -194,7 +196,7 @@ static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = { }, }; ``` -Finally, the Application Profiles are registered using the Application ID, which is an user-assigned number to identify each profile. In this way, multiple Application Profiles can run in one server. +Finally, the Application Profiles are registered using the Application ID, which is an user-assigned number to identify each profile. In this way, multiple Application Profiles can run in one server. ```c esp_ble_gatts_app_register(PROFILE_A_APP_ID); @@ -291,13 +293,13 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i ``` ## GAP Event Handler -Once the advertising data have been set, the GAP event `ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT` is triggered. For the case of raw advertising data set, the event triggered is `ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT`. Additionally when the raw scan response data is set, `ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT` event is triggered. +Once the advertising data have been set, the GAP event `ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT` is triggered. For the case of raw advertising data set, the event triggered is `ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT`. Additionally when the raw scan response data is set, `ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT` event is triggered. ```c static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { - #ifdef CONFIG_SET_RAW_ADV_DATA +#ifdef CONFIG_SET_RAW_ADV_DATA case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: adv_config_done &= (~adv_config_flag); if (adv_config_done==0){ @@ -331,18 +333,17 @@ In any case, the server can start advertising using the `esp_ble_gap_start_adver ```c /// Advertising parameters typedef struct { - uint16_t adv_int_min; + uint16_t adv_int_min; /*!< Minimum advertising interval for undirected and low duty cycle directed advertising. Range: 0x0020 to 0x4000 Default: N = 0x0800 (1.28 second) Time = N * 0.625 msec Time Range: 20 ms to 10.24 sec */ - uint16_t adv_int_max; - /*!< Maximum advertising interval for undirected and low duty - cycle directed advertising. - Range: 0x0020 to 0x4000 + uint16_t adv_int_max; + /*!< Maximum advertising interval for undirected and low duty cycle directed advertising. + Range: 0x0020 to 0x4000 Default: N = 0x0800 (1.28 second) - Time = N * 0.625 msec + Time = N * 0.625 msec Time Range: 20 ms to 10.24 sec */ esp_ble_adv_type_t adv_type; /*!< Advertising type */ esp_ble_addr_type_t own_addr_type; /*!< Owner bluetooth device address type */ @@ -350,7 +351,7 @@ typedef struct { esp_ble_addr_type_t peer_addr_type; /*!< Peer device bluetooth device address type */ esp_ble_adv_channel_t channel_map; /*!< Advertising channel map */ esp_ble_adv_filter_t adv_filter_policy; /*!< Advertising filter policy */ -} +} esp_ble_adv_params_t; ``` @@ -390,11 +391,13 @@ If the advertising started successfully, an `ESP_GAP_BLE_ADV_START_COMPLETE_EVT` When an Application Profile is registered, an `ESP_GATTS_REG_EVT` event is triggered. The parameters of the `ESP_GATTS_REG_EVT` are: -* `esp_gatt_status_t status; /*!< Operation status */` -* `uint16_t app_id;       /*!< Application id which input in register API */` +```c +esp_gatt_status_t status; /*!< Operation status */` +uint16_t app_id; /*!< Application id which input in register API */` +``` In addition to the previous parameters, the event also contains the GATT interface assigned by the BLE stack. The event is captured by the `gatts_event_handler()`, which used to store the generated interface in the profile table, and then the event is forwarded to the corresponding profile event handler. - + ```c static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { @@ -424,7 +427,7 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_ } while (0); } ``` - + ## Creating Services The register event is also used to create a profile attributes table by using the `esp_ble_gatts_create_attr_tab()` function, which takes an `esp_gatts_attr_db_t` type structure that has all the information of the service table. The way to create this table is: @@ -448,7 +451,7 @@ The handles are: 3. Characteristic value handle 4. Characteristic descriptor handle -The service is defined as a primary service with a UUID length of 16 bits. The service ID is initialized with instance ID = 0 and UUID defined by `GATTS_SERVICE_UUID_TEST_A`. +The service is defined as a primary service with a UUID length of 16 bits. The service ID is initialized with instance ID = 0 and UUID defined by `GATTS_SERVICE_UUID_TEST_A`. The service instance ID can be used to differentiate multiple services with the same UUID. In this example, since there is only one service for each Application Profiles and the services have different UUIDs, the service instance ID can be defined as 0 in both profile A and B. However if there was only one Application Profile with two services using the same UUID, then it would be necessary to use different instance IDs to refer to one service or the other. @@ -480,7 +483,7 @@ case ESP_GATTS_CREATE_EVT: gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle; gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16; gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A; - + esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle); a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; esp_err_t add_char_ret = @@ -541,10 +544,12 @@ Finally, the characteristic is configured in a way that it is required to send a Adding a characteristic to a service triggers an `ESP_GATTS_ADD_CHAR_EVT` event, which returns the handle generated by the stack for the characteristic just added. The event includes the following parameters: -* `esp_gatt_status_t status;     /*!< Operation status */` -* `uint16_t attr_handle;       /*!< Characteristic attribute handle */` -* `uint16_t service_handle;     /*!< Service attribute handle */` -* `esp_bt_uuid_t char_uuid;     /*!< Characteristic uuid */` +```c +esp_gatt_status_t status; /*!< Operation status */ +uint16_t attr_handle; /*!< Characteristic attribute handle */ +uint16_t service_handle; /*!< Service attribute handle */ +esp_bt_uuid_t char_uuid; /*!< Characteristic uuid */ +``` The attribute handle returned by the event is stored in the profile table and the characteristic descriptor length and UUID are set as well. The characteristic length and value are read using the `esp_ble_gatts_get_attr_value()` function, and then printed for information purposes. Finally, the characteristic description is added using the `esp_ble_gatts_add_char_descr()` function. The parameters used are the service handle, the descriptor UUID, write and read permissions, an initial value and the auto response setting. The initial value for the characteristic descriptor can be a NULL pointer and the auto response parameter is set to NULL as well, which means that requests that require responses have to be replied manually. @@ -655,14 +660,16 @@ The `esp_ble_gap_update_conn_params()` function triggers a GAP event `ESP_GAP_BL ## Managing Read Events Now that the services and characteristics are created and started, the program can receive read and write events. The read operations are represented by the `ESP_GATTS_READ_EVT` event, which has the following parameters: - -* `uint16_t conn_id;     /*!< Connection id */` -* `uint32_t trans_id;     /*!< Transfer id */` -* `esp_bd_addr_t bda;     /*!< The bluetooth device address which been read */` -* `uint16_t handle;      /*!< The attribute handle */` -* `uint16_t offset;      /*!< Offset of the value, if the value is too long */` -* `bool is_long;        /*!< The value is too long or not */` -* `bool need_rsp;       /*!< The read operation need to do response */` + +```c +uint16_t conn_id; /*!< Connection id */ +uint32_t trans_id; /*!< Transfer id */ +esp_bd_addr_t bda; /*!< The bluetooth device address which been read */ +uint16_t handle; /*!< The attribute handle */ +uint16_t offset; /*!< Offset of the value, if the value is too long */ +bool is_long; /*!< The value is too long or not */ +bool need_rsp; /*!< The read operation need to do response */ +``` In this example, a response is constructed with dummy data and sent back to the host using the same handle given by the event. In addition to the response, the GATT interface, the connection ID and the transfer ID are also included as parameters in the `esp_ble_gatts_send_response()` function. This function is necessary if the auto response byte is set to NULL when creating the characteristic or descriptor. @@ -690,17 +697,19 @@ case ESP_GATTS_READ_EVT: { The write events are represented by the `ESP_GATTS_WRITE_EVT` event, which has the following parameters: -* `uint16_t conn_id;     /*!< Connection id */` -* `uint32_t trans_id;    /*!< Transfer id */` -* `esp_bd_addr_t bda;    /*!< The bluetooth device address which been written */` -* `uint16_t handle;     /*!< The attribute handle */` -* `uint16_t offset;     /*!< Offset of the value, if the value is too long */` -* `bool need_rsp;      /*!< The write operation need to do response */` -* `bool is_prep;       /*!< This write operation is prepare write */` -* `uint16_t len;       /*!< The write attribute value length */` -* `uint8_t *value;      /*!< The write attribute value */` +```c +uint16_t conn_id; /*!< Connection id */ +uint32_t trans_id; /*!< Transfer id */ +esp_bd_addr_t bda; /*!< The bluetooth device address which been written */ +uint16_t handle; /*!< The attribute handle */ +uint16_t offset; /*!< Offset of the value, if the value is too long */ +bool need_rsp; /*!< The write operation need to do response */ +bool is_prep; /*!< This write operation is prepare write */ +uint16_t len; /*!< The write attribute value length */ +uint8_t *value; /*!< The write attribute value */ +``` -There are two types of write events implemented in this example, write characteristic value and write long characteristic value. The first type of write is used when the characteristic value can fit in one Attribute Protocol Maximum Transmission Unit (ATT MTU), which is usually 23 bytes long. The second type is used when the attribute to write is longer than what can be sent in one single ATT message by dividing the data into multiple chunks using Prepare Write Responses, after which an Executive Write Request is used to confirm or cancel the complete write request. This behavior is defined in the [Bluetooth Specification Version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification), Vol 3, Part G, section 4.9. The write long characteristic message flow is shown in Fig. 2. +There are two types of write events implemented in this example, write characteristic value and write long characteristic value. The first type of write is used when the characteristic value can fit in one Attribute Protocol Maximum Transmission Unit (ATT MTU), which is usually 23 bytes long. The second type is used when the attribute to write is longer than what can be sent in one single ATT message by dividing the data into multiple chunks using Prepare Write Responses, after which an Executive Write Request is used to confirm or cancel the complete write request. This behavior is defined in the [Bluetooth Specification Version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification), Vol 3, Part G, section 4.9. The write long characteristic message flow is shown in the figure below. When a write event is triggered, this example prints logging messages, and then executes the `example_write_event_env()` function. @@ -748,13 +757,12 @@ case ESP_GATTS_WRITE_EVT: { } } } - example_write_event_env(gatts_if, &a_prepare_write_env, param); + example_write_event_env(gatts_if, &a_prepare_write_env, param); break; } ``` -![](image/GATT SERVER FIGURE 2.jpg) -

Fig.2. Message flow for Write Long Characteristic

+
Message flow for Write Long Characteristic
The `example_write_event_env()` function contains the logic for the write long characteristic procedure: @@ -773,7 +781,7 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare } else { if(param->write.offset > PREPARE_BUF_MAX_SIZE) { status = ESP_GATT_INVALID_OFFSET; - } + } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) { status = ESP_GATT_INVALID_ATTR_LEN; } @@ -803,7 +811,7 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); } } -} +} ``` When the client sends a Write Request or a Prepare Write Request, the server shall respond. However, if the client sends a Write Without Response command, the server does not need to reply back a response. This is checked in the write procedure by examining the value of the `write.need_rsp parameter`. If a response is needed, the procedure continues doing the response preparation, if not present, the client does not need a response and therefore the procedure is ended. @@ -859,7 +867,7 @@ If the buffer is not NULL, which means initialization completed, the procedure t else { if(param->write.offset > PREPARE_BUF_MAX_SIZE) { status = ESP_GATT_INVALID_OFFSET; - } + } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) { status = ESP_GATT_INVALID_ATTR_LEN; } @@ -916,7 +924,7 @@ void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble free(prepare_write_env->prepare_buf); prepare_write_env->prepare_buf = NULL; } - prepare_write_env->prepare_len = 0; +#### prepare_write_env->prepare_len = 0; } ``` @@ -944,5 +952,3 @@ prepare_write_env->prepare_len = 0; ## Conclusion In this document, we have gone through the GATT SERVER example code describing each section. The application is designed around the concept of Application Profiles. In addition, the procedures that this example uses to register event handlers are explained. The events follow a sequence of configuration steps, such as defining advertising parameters, updating connection parameters and creating services and characteristics. Finally, the way in which read and write events are handled, including Write Long characteristics by dividing the write into chunks that can fit the Attribute Protocol message is explained. - - diff --git a/examples/bluetooth/gatt_server/tutorial/image/GATT SERVER FIGURE 1.png b/examples/bluetooth/gatt_server/tutorial/image/GATT SERVER FIGURE 1.png deleted file mode 100644 index 17111a93b..000000000 Binary files a/examples/bluetooth/gatt_server/tutorial/image/GATT SERVER FIGURE 1.png and /dev/null differ diff --git a/examples/bluetooth/gatt_server/tutorial/image/GATT SERVER FIGURE 2.jpg b/examples/bluetooth/gatt_server/tutorial/image/GATT SERVER FIGURE 2.jpg deleted file mode 100644 index fc5b14a18..000000000 Binary files a/examples/bluetooth/gatt_server/tutorial/image/GATT SERVER FIGURE 2.jpg and /dev/null differ diff --git a/examples/bluetooth/gatt_server/tutorial/image/GATT_Server_Figure_1.png b/examples/bluetooth/gatt_server/tutorial/image/GATT_Server_Figure_1.png new file mode 100644 index 000000000..a5838cfc7 Binary files /dev/null and b/examples/bluetooth/gatt_server/tutorial/image/GATT_Server_Figure_1.png differ diff --git a/examples/bluetooth/gatt_server/tutorial/image/GATT_Server_Figure_2.png b/examples/bluetooth/gatt_server/tutorial/image/GATT_Server_Figure_2.png new file mode 100644 index 000000000..65fa0ecc8 Binary files /dev/null and b/examples/bluetooth/gatt_server/tutorial/image/GATT_Server_Figure_2.png differ diff --git a/examples/bluetooth/gatt_server_service_table/README.md b/examples/bluetooth/gatt_server_service_table/README.md index 313ecb773..0c086f0d5 100644 --- a/examples/bluetooth/gatt_server_service_table/README.md +++ b/examples/bluetooth/gatt_server_service_table/README.md @@ -1,11 +1,6 @@ -ESP-IDF GATT SERVER create attribute table demo +ESP-IDF Gatt Server Service Table Demo =============================================== -This is the demo for user to use ESP_APIs to create a GATT Server attribute table. -The table is easy to use to create GATT server service database without use each "attribute create" functions. -Actually, there are two way to create server service and characteristics. -One is use the esp_gatts_create_service or esp_ble_gatts_add_char and etc. -The other way is use esp_ble_gatts_create_attr_tab. -The important things: the two ways cannot use in the same service, but can use in different service. +This demo shows how to create a GATT service with an attribute table defined in one place. Provided API releases the user from adding attributes one by one as implemented in BLUEDROID. A demo of the other method to create the attribute table is presented in [gatt_server_demo](../gatt_server). -Please check the [tutorial](tutorial/GATT_Server_Service_Table_Example_Walkthrough.md) for more information about this example. +Please check the [tutorial](tutorial/Gatt_Server_Service_Table_Example_Walkthrough.md) for more information about this example. diff --git a/examples/bluetooth/gatt_server_service_table/main/gatts_table_creat_demo.c b/examples/bluetooth/gatt_server_service_table/main/gatts_table_creat_demo.c index 4e8174e5e..525a73456 100644 --- a/examples/bluetooth/gatt_server_service_table/main/gatts_table_creat_demo.c +++ b/examples/bluetooth/gatt_server_service_table/main/gatts_table_creat_demo.c @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ /**************************************************************************** * diff --git a/examples/bluetooth/gatt_server_service_table/main/gatts_table_creat_demo.h b/examples/bluetooth/gatt_server_service_table/main/gatts_table_creat_demo.h index 97bf16d51..20379cf07 100644 --- a/examples/bluetooth/gatt_server_service_table/main/gatts_table_creat_demo.h +++ b/examples/bluetooth/gatt_server_service_table/main/gatts_table_creat_demo.h @@ -1,16 +1,10 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ #include diff --git a/examples/bluetooth/gatt_server_service_table/tutorial/GATT_Server_Service_Table_Example_Walkthrough.md b/examples/bluetooth/gatt_server_service_table/tutorial/Gatt_Server_Service_Table_Example_Walkthrough.md similarity index 81% rename from examples/bluetooth/gatt_server_service_table/tutorial/GATT_Server_Service_Table_Example_Walkthrough.md rename to examples/bluetooth/gatt_server_service_table/tutorial/Gatt_Server_Service_Table_Example_Walkthrough.md index e5dc3615f..a317dc8da 100644 --- a/examples/bluetooth/gatt_server_service_table/tutorial/GATT_Server_Service_Table_Example_Walkthrough.md +++ b/examples/bluetooth/gatt_server_service_table/tutorial/Gatt_Server_Service_Table_Example_Walkthrough.md @@ -2,17 +2,17 @@ ## Introduction -This document presents a walkthrough of the GATT Server Service Table example code for the ESP32. This example implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) Server using a table-like data structure to define the server services and characteristics such as the one shown in Fig. 1. Therefore, it demonstrates a practical way to define the server functionality in one place instead of adding services and characteristics one by one. +This document presents a walkthrough of the GATT Server Service Table example code for the ESP32. This example implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) Server using a table-like data structure to define the server services and characteristics such as the one shown in the figure below Therefore, it demonstrates a practical way to define the server functionality in one place instead of adding services and characteristics one by one. This example implements the *Heart Rate Profile* as defined by the [Traditional Profile Specifications](https://www.bluetooth.com/specifications/profiles-overview). -![Table-like data structure representing the Heart Rate Service](image/Table-like data structure representing the Heart Rate Service.png) +
Table-like data structure representing the Heart Rate Service
## Includes -Let’s start by taking a look at the included headers in the ``gatts_table_creat_demo.c`` file: +Let’s start by taking a look at the included headers in the [gatts_table_creat_demo.c](../main/gatts_table_creat_demo.c) file: -``` +```c #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" @@ -38,9 +38,9 @@ These includes are required for the *FreeRTOS* and underlaying system components ## Service Table -The header file ``gatts_table_creat_demo.h`` is where an enumeration of the services and characteristics is created: +The header file [gatts_table_creat_demo.h](../main/gatts_table_creat_demo.h) is where an enumeration of the services and characteristics is created: -``` +```c enum { HRS_IDX_SVC, @@ -60,21 +60,21 @@ enum ``` The enumeration elements are set up in the same order as the Heart Rate Profile attributes, starting with the service followed by the characteristics of that service. In addition, the Heart Rate Measurement characteristic has a Client Characteristic Configuration (CCC) descriptor which is an additional attribute that describes if the characteristic has notifications enabled. The enumeration index can be used to identify each element later when creating the actual attributes table. In summary, the elements are described as follows: -* HRS_IDX_SVC: Heart Rate Service index -* HRS_IDX_HR_MEAS_CHAR: Heart Rate Measurement characteristic index -* HRS_IDX_HR_MEAS_VAL: Heart Rate Measurement characteristic value index -* HRS_IDX_HR_MEAS_NTF_CFG: Heart Rate Measurement notifications configuration (CCC) index -* HRS_IDX_BOBY_SENSOR_LOC_CHAR: Heart Rate Body Sensor Location characteristic index -* HRS_IDX_BOBY_SENSOR_LOC_VAL: Heart Rate Body Sensor Location characteristic value index -* HRS_IDX_HR_CTNL_PT_CHAR: Heart Rate Control Point characteristic index -* HRS_IDX_HR_CTNL_PT_VAL: Heart Rate Control Point characteristic value index -* HRS_IDX_NB: Number of table elements. +* ``HRS_IDX_SVC``: Heart Rate Service index +* ``HRS_IDX_HR_MEAS_CHAR``: Heart Rate Measurement characteristic index +* ``HRS_IDX_HR_MEAS_VAL``: Heart Rate Measurement characteristic value index +* ``HRS_IDX_HR_MEAS_NTF_CFG``: Heart Rate Measurement notifications configuration (CCC) index +* ``HRS_IDX_BOBY_SENSOR_LOC_CHAR``: Heart Rate Body Sensor Location characteristic index +* ``HRS_IDX_BOBY_SENSOR_LOC_VAL``: Heart Rate Body Sensor Location characteristic value index +* ``HRS_IDX_HR_CTNL_PT_CHAR``: Heart Rate Control Point characteristic index +* ``HRS_IDX_HR_CTNL_PT_VAL``: Heart Rate Control Point characteristic value index +* ``HRS_IDX_NB``: Number of table elements. ## Main Entry Point The entry point to this example is the ``app_main()`` function: -``` +```c void app_main() { esp_err_t ret; @@ -94,7 +94,7 @@ void app_main() return; } - ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM); + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); if (ret) { ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__); return; @@ -121,20 +121,20 @@ void app_main() The main function starts by initializing the non-volatile storage library in order to be able to save parameters in flash memory. -``` +```c ret = nvs_flash_init(); ``` ## BT Controller and Stack Initialization -See this section in **GATT Server Example Walkthrough**. +See this section in [GATT Server Example Walkthrough](../../gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md). ## Application Profiles This example implements one Application Profile for the Heart Rate Service. An Application Profile is a way to group functionality which is designed to be used by one client application, for example one smartphone mobile app. In this way, different types of profiles can be accommodated in one server. The Application Profile ID, which is an user-assigned number to identify each profile, is used to register the profile in the stack, in this example the ID is 0x55. -``` +```c #define HEART_PROFILE_NUM 1 #define HEART_PROFILE_APP_IDX 0 #define ESP_HEART_RATE_APP_ID 0x55 @@ -142,7 +142,7 @@ This example implements one Application Profile for the Heart Rate Service. An A The profiles are stored in the ``heart_rate_profile_tab`` array. Since there is only one profile in this example, one element is stored in the array with index zero as defined by the ``HEART_PROFILE_APP_IDX``. Additionally, the profile event handler callback function is initialized. Each application on the GATT server uses a different interface, represented by the gatts_if parameter. For initialization, this parameter is set to ``ESP_GATT_IF_NONE``, later when the application is registered, the gatts_if parameter is updated with the corresponding interface generated by the stack. -``` +```c /* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */ static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = { [HEART_PROFILE_APP_IDX] = { @@ -155,7 +155,7 @@ static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = { The application registration takes place inside ``app_main()`` using the ``esp_ble_gatts_app_register()`` function: -``` +```c esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID); ``` @@ -168,27 +168,27 @@ The register application event is the first one that is triggered during the lif The function used to configure standard Bluetooth Specification advertisement parameters is ``esp_ble_gap_config_adv_data()`` which takes a pointer to an ``esp_ble_adv_data_t`` structure. The ``esp_ble_adv_data_t`` data structure for advertising data has the following definition: -``` +```c typedef struct { - bool set_scan_rsp; /*!< Set this advertising data as scan response or not*/ - bool include_name; /*!< Advertising data include device name or not */ + bool set_scan_rsp; /*!< Set this advertising data as scan response or not*/ + bool include_name; /*!< Advertising data include device name or not */ bool include_txpower; /*!< Advertising data include TX power */ - int min_interval; /*!< Advertising data show advertising min interval */ - int max_interval; /*!< Advertising data show advertising max interval */ - int appearance; /*!< External appearance of device */ + int min_interval; /*!< Advertising data show advertising min interval */ + int max_interval; /*!< Advertising data show advertising max interval */ + int appearance; /*!< External appearance of device */ uint16_t manufacturer_len; /*!< Manufacturer data length */ uint8_t *p_manufacturer_data; /*!< Manufacturer data point */ - uint16_t service_data_len; /*!< Service data length */ - uint8_t *p_service_data; /*!< Service data point */ - uint16_t service_uuid_len; /*!< Service uuid length */ - uint8_t *p_service_uuid; /*!< Service uuid array point */ - uint8_t flag; /*!< Advertising flag of discovery mode, see BLE_ADV_DATA_FLAG detail */ + uint16_t service_data_len; /*!< Service data length */ + uint8_t *p_service_data; /*!< Service data point */ + uint16_t service_uuid_len; /*!< Service uuid length */ + uint8_t *p_service_uuid; /*!< Service uuid array point */ + uint8_t flag; /*!< Advertising flag of discovery mode, see BLE_ADV_DATA_FLAG detail */ } esp_ble_adv_data_t; ``` In this example, the structure is initialized as follows: -``` +```c static esp_ble_adv_data_t heart_rate_adv_config = { .set_scan_rsp = false, .include_name = true, @@ -210,19 +210,18 @@ The minimum and maximum advertisement intervals are set in units of 0.625 ms. In An advertising payload can be up to 31 bytes of data. It is possible that some of the parameters surpass the 31-byte advertisement packet limit which causes the stack to cut the message and leave some of the parameters out. To solve this, usually the longer parameters are stored in the scan response, which can be configured using the same ``esp_ble_gap_config_adv_data()`` function and an additional esp_ble_adv_data_t type structure with the .set_scan_rsp parameter is set to true. Finally, to set the device name the ``esp_ble_gap_set_device_name()`` function is used. The registering event handler is shown as follows: -``` -static void gatts_profile_event_handler(esp_gatts_cb_event_t event, - esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +```c +static void gatts_profile_event_handler(esp_gatts_cb_event_t event, +esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { ESP_LOGE(GATTS_TABLE_TAG, "event = %x\n",event); switch (event) { case ESP_GATTS_REG_EVT: - ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); - esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME); - ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); - esp_ble_gap_config_adv_data(&heart_rate_adv_config); - - ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); + esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME); + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); + esp_ble_gap_config_adv_data(&heart_rate_adv_config); + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); … ``` @@ -230,7 +229,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, Once the advertising data have been set, the ``ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT`` is triggered and managed by the GAP event handler. Moreover, an ``ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT`` is triggered as well if the scan response is also set. Once the configuration of the advertising and scan response data has been set, the handler can use any of these events to start advertising, which is done using the ``esp_ble_gap_start_advertising()`` function: -``` +```c static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event); @@ -253,27 +252,25 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param The function to start advertising takes a structure of type ``esp_ble_adv_params_t`` with the advertising parameters required. -``` +```c /// Advertising parameters typedef struct { - uint16_t adv_int_min; /*!< Minimum advertising interval for - undirected and low duty cycle directed advertising. - Range: 0x0020 to 0x4000 - Default: N = 0x0800 (1.28 second) - Time = N * 0.625 msec - Time Range: 20 ms to 10.24 sec */ - uint16_t adv_int_max; /*!< Maximum advertising interval for undirected and low duty - cycle directed advertising. - Range: 0x0020 to 0x4000 - Default: N = 0x0800 (1.28 second) - Time = N * 0.625 msec - Time Range: 20 ms to 10.24 sec */ - esp_ble_adv_type_t adv_type; /*!< Advertising type */ - esp_ble_addr_type_t own_addr_type; /*!< Owner bluetooth device address type */ - esp_bd_addr_t peer_addr; /*!< Peer device bluetooth device address */ - esp_ble_addr_type_t peer_addr_type;/*!< Peer device bluetooth device address type */ - esp_ble_adv_channel_t channel_map; /*!< Advertising channel map */ - esp_ble_adv_filter_t adv_filter_policy; /*!< Advertising filter policy */ + uint16_t adv_int_min; /*!< Minimum advertising interval for undirected and low duty cycle directed advertising. + Range: 0x0020 to 0x4000 + Default: N = 0x0800 (1.28 second) + Time = N * 0.625 msec + Time Range: 20 ms to 10.24 sec */ + uint16_t adv_int_max; /*!< Maximum advertising interval for undirected and low duty cycle directed advertising. + Range: 0x0020 to 0x4000 + Default: N = 0x0800 (1.28 second) + Time = N * 0.625 msec + Time Range: 20 ms to 10.24 sec */ + esp_ble_adv_type_t adv_type; /*!< Advertising type */ + esp_ble_addr_type_t own_addr_type; /*!< Owner bluetooth device address type */ + esp_bd_addr_t peer_addr; /*!< Peer device bluetooth device address */ + esp_ble_addr_type_t peer_addr_type; /*!< Peer device bluetooth device address type */ + esp_ble_adv_channel_t channel_map; /*!< Advertising channel map */ + esp_ble_adv_filter_t adv_filter_policy; /*!< Advertising filter policy */ } esp_ble_adv_params_t; ``` @@ -281,7 +278,7 @@ Note that ``esp_ble_gap_config_adv_data()`` configures the data that is advertis For this example, the advertisement parameters are initialized as follows: -``` +```c static esp_ble_adv_params_t heart_rate_adv_params = { .adv_int_min = 0x20, .adv_int_max = 0x40, @@ -298,7 +295,7 @@ These parameters configure the advertising interval between 20 ms to 40 ms. The If the advertising started successfully, an ``ESP_GAP_BLE_ADV_START_COMPLETE_EVT`` event is generated which in this example is used to check if the advertising status is indeed advertising or otherwise print an error message. -``` +```c … case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: //advertising start complete event to indicate advertising start successfully or failed @@ -313,14 +310,15 @@ If the advertising started successfully, an ``ESP_GAP_BLE_ADV_START_COMPLETE_EVT When an Application Profile is registered, an ``ESP_GATTS_REG_EVT`` event is triggered. The parameters of the ``ESP_GATTS_REG_EVT`` are: -* esp_gatt_status_t status; /*!< Operation status */ -* uint16_t app_id; /*!< Application id which input in register API */ +```c +esp_gatt_status_t status; /*!< Operation status */ +uint16_t app_id; /*!< Application id which input in register API */ +``` In addition to the previous parameters, the event also contains the GATT interface assigned by the BLE stack. The event is captured by the ``gatts_event_handler()`` which stores the generated interface in the profile table and then forwards it to the corresponding profile event handler. -``` -static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, - esp_ble_gatts_cb_param_t *param) +```c +static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if); @@ -340,7 +338,7 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_ int idx; for (idx = 0; idx < HEART_PROFILE_NUM; idx++) { if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ - gatts_if == heart_rate_profile_tab[idx].gatts_if) { + gatts_if == heart_rate_profile_tab[idx].gatts_if) { if (heart_rate_profile_tab[idx].gatts_cb) { heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param); } @@ -356,23 +354,27 @@ The register event is used to create a table of profile attributes by employing The ``esp_gatts_attr_db_t`` structure has two members: -* esp_attr_control_t attr_control; /*!< The attribute control type*/ -* esp_attr_desc_t att_desc; /*!< The attribute type*/ +```c +esp_attr_control_t attr_control; /*!< The attribute control type*/ +esp_attr_desc_t att_desc; /*!< The attribute type*/ +``` The attr_control is the auto-respond parameter which can be set as ``ESP_GATT_AUTO_RSP`` to allow the BLE stack to take care of responding messages when read or write events arrive. The other option is ``ESP_GATT_RSP_BY_APP`` which allows to manually respond to messages using the ``esp_ble_gatts_send_response()`` function. The ``att_desc`` is the attribute description which is made of: -* ``uint16_t uuid_length``; /*!< UUID length */ -* ``uint8_t *uuid_p``; /*!< UUID value */ -* ``uint16_t perm``; /*!< Attribute permission */ -* ``uint16_t max_length``; /*!< Maximum length of the element*/ -* ``uint16_t length``; /*!< Current length of the element*/ -* ``uint8_t *value``; /*!< Element value array*/ +```c +uint16_t uuid_length; /*!< UUID length */ +uint8_t *uuid_p; /*!< UUID value */ +uint16_t perm; /*!< Attribute permission */ +uint16_t max_length; /*!< Maximum length of the element*/ +uint16_t length; /*!< Current length of the element*/ +uint8_t *value; /*!< Element value array*/ +``` For example, the first element of the table in this example is the service attribute: -``` +```c [HRS_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}}, @@ -391,7 +393,7 @@ The initialization values are: The rest of the attributes is initialized in the same way. Some attributes also have the *NOTIFY* property which is set by ``&char_prop_notify``. The complete table structure is initialized as follows: -``` +```c /// Full HRS Database Description - Used to add attributes into the database static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] = { @@ -440,14 +442,16 @@ static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] = ## Starting the Service When the attribute table is created, an ``ESP_GATTS_CREAT_ATTR_TAB_EVT`` event is triggered. This event has the following parameters: -* ``esp_gatt_status_t status``; /*!< Operation status */ -* ``esp_bt_uuid_t svc_uuid``; /*!< Service uuid type */ -* ``uint16_t num_handle``; /*!< The number of the attribute handle to be added to the gatts database */ -* ``uint16_t *handles``; /*!< The number to the handles */ +```c +esp_gatt_status_t status; /*!< Operation status */ +esp_bt_uuid_t svc_uuid; /*!< Service uuid type */ +uint16_t num_handle; /*!< The number of the attribute handle to be added to the gatts database */ +uint16_t *handles; /*!< The number to the handles */ +``` This example uses this event to print information and to check that the size of the created table equals the number of elements in the enumeration HRS_IDX_NB. If the table is correctly created, the attribute handles are copied into the handle table heart_rate_handle_table and the service is started using the ``esp_ble_gatts_start_service()`` function: -``` +```c case ESP_GATTS_CREAT_ATTR_TAB_EVT:{ ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle); if (param->add_attr_tab.status != ESP_GATT_OK){ @@ -468,7 +472,7 @@ The handles stored in the handles pointer of the event parameters are numbers th Finally, the heart_rate_handle_table contains the Application Profile in the form of a structure with information about the attribute parameters as well as GATT interface, connection ID, permissions and application ID. The profile structure is shown as follows, note that not all members are used in this example: -``` +```c struct gatts_profile_inst { esp_gatts_cb_t gatts_cb; uint16_t gatts_if; diff --git a/examples/bluetooth/gatt_server_service_table/tutorial/image/Table-like data structure representing the Heart Rate Service.png b/examples/bluetooth/gatt_server_service_table/tutorial/image/Heart_Rate_Service.png similarity index 100% rename from examples/bluetooth/gatt_server_service_table/tutorial/image/Table-like data structure representing the Heart Rate Service.png rename to examples/bluetooth/gatt_server_service_table/tutorial/image/Heart_Rate_Service.png diff --git a/examples/bluetooth/gattc_multi_connect/README.md b/examples/bluetooth/gattc_multi_connect/README.md index c22436a4d..11b1c7b78 100644 --- a/examples/bluetooth/gattc_multi_connect/README.md +++ b/examples/bluetooth/gattc_multi_connect/README.md @@ -1,10 +1,10 @@ -ESP-IDF GATT MULTIPLE CLIENT demo +ESP-IDF Gatt Client Multi Connection Demo ======================== -This is the demo for user to use ESP_APIs to create a GATT Multi-connection Client. -The gattc_multi_connect demo can connect three ble slaves at the same time. -Modify the name of gatt_server demo named ESP_GATTS_DEMO_a, ESP_GATTS_DEMO_b and ESP_GATTS_DEMO_c, then run the three demos, -the gattc_multi_connect demo will connect the three gatt_server demos, and then exchange data. -Of course you can also modify the code to connect more devices, we default to connect up to 4 devices, more than 4 you need to modify menuconfig. +This is the demo of APIs to create a GATT multi-connection client. It can be used to connect to three GATT servers at the same time. -Please check the [tutorial](tutorial/GATT_Client_Multi-Connection_Example_Walkthrough.md) for more information about this example. +To test this demo, please run [gatt_server_demo](../gatt_server) to create three GATT server devices, namely ESP_GATTS_DEMO_a, ESP_GATTS_DEMO_b and ESP_GATTS_DEMO_c, `Gatt_client_multi_connection_demo` will connect to these three gatt server demos, and then exchange data. + +The code can be modified to connect to more devices (up to 4 devices by default). If you need to connect to more devices (more than 4 devices), you need to change `BT/BLE MAX ACL CONNECTIONS` in menuconfig. + +Please check the [tutorial](tutorial/Gatt_Client_Multi_Connection_Example_Walkthrough.md) for more information about this example. diff --git a/examples/bluetooth/gattc_multi_connect/main/gattc_multi_connect.c b/examples/bluetooth/gattc_multi_connect/main/gattc_multi_connect.c index eb09cee23..7467da7cc 100644 --- a/examples/bluetooth/gattc_multi_connect/main/gattc_multi_connect.c +++ b/examples/bluetooth/gattc_multi_connect/main/gattc_multi_connect.c @@ -1,16 +1,10 @@ -// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ diff --git a/examples/bluetooth/gattc_multi_connect/tutorial/GATT_Client_Multi-Connection_Example_Walkthrough.md b/examples/bluetooth/gattc_multi_connect/tutorial/Gatt_Client_Multi_Connection_Example_Walkthrough.md similarity index 69% rename from examples/bluetooth/gattc_multi_connect/tutorial/GATT_Client_Multi-Connection_Example_Walkthrough.md rename to examples/bluetooth/gattc_multi_connect/tutorial/Gatt_Client_Multi_Connection_Example_Walkthrough.md index 0bd558dc8..918f6377a 100644 --- a/examples/bluetooth/gattc_multi_connect/tutorial/GATT_Client_Multi-Connection_Example_Walkthrough.md +++ b/examples/bluetooth/gattc_multi_connect/tutorial/Gatt_Client_Multi_Connection_Example_Walkthrough.md @@ -1,22 +1,22 @@ -# GATT Client Multi-Connection Example Walkthrough +# GATT Client Multi-connection Example Walkthrough ## Introduction This document presents a description of the multi-connection BLE GATT client example for the ESP32. In this implementation, a single ESP32 working as a GATT client connects to three different GATT servers at the same time. This set up illustrates the use case of an ESP32 device acting in a way so that it receives data from different BLE sensors. The unique combination of ESP32’s BLE + Wi-Fi capabilities in addition to connection to multiple peripherals makes it a great candidate to serve as an IoT gateway. -This example’s workflow is similar to the **GATT Client Example Walkthrough** and is shown in Fig. 1. However, in the multi-connection implementation, a GATT client searches for three specific server names and once that it has found them it opens a connection to all three of them one after the other. In code, each connection is handled separately with one Application Profile. +This example’s workflow is similar to the [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md) and is shown in the figure below. However, in the multi-connection implementation, a GATT client searches for three specific server names and once that it has found them it opens a connection to all three of them one after the other. In code, each connection is handled separately with one Application Profile. Four ESP32 devices are needed in order to demonstrate this example, among which: -* one would be employed as a GATT Client flashed with the ``gattc_multi_connect`` demo, and, -* the rest run as GATT servers flashed with the ``gatt_server`` demo of the ESP-IDF Bluetooth examples folder. +* one would be employed as a GATT Client flashed with the [gattc_multi_connect](../../gattc_multi_connect) demo, and, +* the rest run as GATT servers flashed with the [gatt_server](../../gatt_server) demo of the ESP-IDF Bluetooth examples folder. -![Multi-Connection GATT Client Flowchart](image/Multi-connection GATT Client Flowchart.png) +
Multi-Connection GATT Client Flowchart
## Includes -The multi-connection example’s main source file is ``gattc_multi_connect.c``. For details, see Section **Includes** in **GATT Client Example Walkthrough**. +The multi-connection example’s main source file is [gattc_multi_connect.c](../main/gattc_multi_connect.c). For details, see Section [Includes](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#includes) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). ## Main Entry Point -See Section **Main Entry Point** in **GATT Client Example Walkthrough**. +See Section [Main Entry Point](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#main-entry-point) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). ## Implementation Procedure The GATT Client implementation includes the following steps: @@ -28,53 +28,53 @@ The GATT Client implementation includes the following steps: * registering for notifications. ### Initializing -See Section **Main Entry Point** in **GATT Client Example Walkthrough**. +See Section [Main Entry Point](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#main-entry-point) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). #### Application Profiles -Application Profiles are a way to group functionality. They are designed so that each Application Profile connects to one peer device, that way the same ESP32 can connect to multiple devices by assigning one Application Profile to each one, as Fig. 2 shows. Each Application Profile creates a GATT interface to connect to other devices. The Application Profiles are defined by an ID number, there are three profiles in this example: +Application Profiles are a way to group functionality. They are designed so that each Application Profile connects to one peer device, that way the same ESP32 can connect to multiple devices by assigning one Application Profile to each one, as figure below shows. Each Application Profile creates a GATT interface to connect to other devices. The Application Profiles are defined by an ID number, there are three profiles in this example: -``` +```c #define PROFILE_NUM 3 -#define PROFILE_A_APP_ID 0 +#define PROFILE_A_APP_ID 0 #define PROFILE_B_APP_ID 1 #define PROFILE_C_APP_ID 2 ``` -![ESP32 GATT Multi-Connect Client Application Profiles](image/ESP32 GATT Multi-connect Client Application Profiles.png) + +
Multi-Connection GATT Client Flowchart
+ The ``esp_ble_gattc_app_register()`` function is used to register each Application Profile to the BLE stack. The registration operation generates a GATT interface that is returned as a parameter in a registration event. In addition, each Application Profile is also defined by a structure that can be used to keep the state of the application and update its parameters when new data is propagated by the stack. -The Application Profiles in code are instances of a ``gattc_profile_inst`` structure. For details, see Section **Application Profiles** in **GATT Client Example Walkthrough**. +The Application Profiles in code are instances of a ``gattc_profile_inst`` structure. For details, see Section [Application Profiles](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#application-profiles) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). ### Scanning #### Setting Scan Parameters -See Section **Setting Scan Parameters** in **GATT Client Example Walkthrough**. +See Section [Setting Scan Parameters](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#setting-scan-parameters) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). #### Starting to Scan -See Section **Start Scanning** in **GATT Client Example Walkthrough**. +See Section [Start Scanning](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#start-scanning) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). #### Getting Scan Results -See Section **Getting Scan Results** in **GATT Client Example Walkthrough**. +See Section [Getting Scan Results](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#start-scanning) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). #### Name Comparison * First, the name of the device is extracted from the advertised data and stored in the ``adv_name`` variable: - ``` - adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, - ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ```c + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); ``` * Then, the device name found is compared to the server names that the client wants to connect to. The server names are defined in the ``remote_device_name`` array: + ```c + static const char remote_device_name[3][20] = {"ESP_GATTS_DEMO_1", "ESP_GATTS_DEMO_2", “ESP_GATTS_DEMO_3"}; ``` -static const char remote_device_name[3][20] = {"ESP_GATTS_DEMO_1", "ESP_GATTS_DEMO_2", “ESP_GATTS_DEMO_3"}; - ``` - The name comparison takes places as follows: - - ``` -if (strlen(remote_device_name[0]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[0], adv_name_len) == 0) { + + ```c + if (strlen(remote_device_name[0]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[0], adv_name_len) == 0) { if (find_device_1 == false) { find_device_1 = true; ESP_LOGI(GATTC_TAG, "Searched device %s", remote_device_name[0]); @@ -106,37 +106,36 @@ if (strlen(remote_device_name[0]) == adv_name_len && strncmp((char *)adv_name, r #### Connecting to the First Remote Device Once all devices have been found, the client stops scanning: -``` -if (find_device_1 && find_device_2 && find_device_3 && stop_scan == false) - { - stop_scan = true; - esp_ble_gap_stop_scanning(); - } +```c +if (find_device_1 && find_device_2 && find_device_3 && stop_scan == false { + stop_scan = true; + esp_ble_gap_stop_scanning(); + } ``` The scan stop triggers an ``ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT`` event which is used to open a connection to the first remote device. The second and third devices get connected once the client searches for services, gets characteristics and registers for notifications on the first device. This workflow is designed to test that the communication between each remote device is working correctly before trying to connect to the next device or in case of error, skip to the next device. -``` +```c case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: - if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ - ESP_LOGE(GATTC_TAG, "Scan stop failed"); - break; - } - ESP_LOGI(GATTC_TAG, "Stop scan successfully"); - if (!stop_scan){ - ESP_LOGE(GATTC_TAG, "Did not find all devices"); - } - if (find_device_1){ - esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, true); - } + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "Scan stop failed"); break; + } + ESP_LOGI(GATTC_TAG, "Stop scan successfully"); + if (!stop_scan){ + ESP_LOGE(GATTC_TAG, "Did not find all devices"); + } + if (find_device_1){ + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, true); + } + break; ``` * The connection is opened with the ``esp_ble_gattc_open()`` function which takes the GATT interface, the remote device address and a boolean value set to true for direct connection or false for background auto connection. To disconnect the physical connection, the GAP API function ``esp_ble_gap_disconnect()`` is used. - - When connecting to the first device, an ``ESP_GATTC_CONNECT_EVT`` event is generated which is forwarded to all profiles. It also triggers an ``ESP_GATTC_OPEN_EVT`` event that is forwarded to the Profile A event handler only, or ``gattc_profile_a_event_handler()`` function. The event checks that the connection is opened successfully, if not, the device is ignored and the client tries to open a connection to the second device: + + When connecting to the first device, an ``ESP_GATTC_CONNECT_EVT`` event is generated which is forwarded to all profiles. It also triggers an ``ESP_GATTC_OPEN_EVT`` event that is forwarded to the Profile A event handler only, or ``gattc_profile_a_event_handler()`` function. The event checks that the connection is opened successfully, if not, the device is ignored and the client tries to open a connection to the second device: - ``` + ```c case ESP_GATTC_OPEN_EVT: if (p_data->open.status != ESP_GATT_OK){ //open failed, ignore the first device, connect the second device @@ -146,35 +145,32 @@ case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: } break; } - ``` - - If the connection is successful the client saves the connection ID, prints the remote device information and configures the MTU size to 200 bytes. - - ``` -gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id; - ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d", p_data->open.conn_id, gattc_if, p_data->open.status, p_data->open.mtu); - ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); - esp_log_buffer_hex(GATTC_TAG, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); - esp_err_t mtu_ret = esp_ble_gattc_config_mtu (gattc_if, p_data->open.conn_id, 200); - if (mtu_ret){ - ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); - } - break; - ``` - + ``` + If the connection is successful the client saves the connection ID, prints the remote device information and configures the MTU size to 200 bytes. + + ```c + gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id; + ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d", p_data->open.conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_config_mtu (gattc_if, p_data->open.conn_id, 200); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + ``` * After configuration of the MTU size, an ``ESP_GATTC_CFG_MTU_EVT`` is generated. This event is used to search for available known services on the remote device. The search is performed by using the ``esp_ble_gattc_search_service()`` function and a service ID defined by: - - ``` -static esp_bt_uuid_t remote_filter_service_uuid = { + + ```c + static esp_bt_uuid_t remote_filter_service_uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, -}; - ``` - + }; + ``` * The handler then searches for the service: - ``` -case ESP_GATTC_CFG_MTU_EVT: + ```c + case ESP_GATTC_CFG_MTU_EVT: if (param->cfg_mtu.status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG,"Config mtu failed"); } @@ -182,13 +178,12 @@ case ESP_GATTC_CFG_MTU_EVT: esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); break; ``` - If the service is found, an ``ESP_GATTC_SEARCH_RES_EVT`` event is triggered which allows to set the ``get_service_1 flag`` to true. This flag is used to print information and later get the characteristic that the client is interested in. * Once the search for all services is completed, an ``ESP_GATTC_SEARCH_CMPL_EVT`` event is generated which is used to get the characteristics of the service just discovered. This is done with the ``esp_ble_gattc_get_characteristic()`` function: - - ``` - case ESP_GATTC_SEARCH_CMPL_EVT: + + ```c + case ESP_GATTC_SEARCH_CMPL_EVT: if (p_data->search_cmpl.status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); break; @@ -198,16 +193,14 @@ case ESP_GATTC_CFG_MTU_EVT: } break; ``` - The ``esp_ble_gattc_get_characteristic()`` function takes the GATT interface, the connection ID and the remote service ID as parameters. In addition, a NULL value is passed to indicate that we want all the characteristics starting from the first one. If the client is interested in a specific characteristic it could pass the characteristic ID in this field to specify that. - - An ``ESP_GATTC_GET_CHAR_EVT`` event is triggered when a characteristic is discovered. This event is used to print information about the characteristic. + The ``esp_ble_gattc_get_characteristic()`` function takes the GATT interface, the connection ID and the remote service ID as parameters. In addition, a NULL value is passed to indicate that we want all the characteristics starting from the first one. If the client is interested in a specific characteristic it could pass the characteristic ID in this field to specify that. + An ``ESP_GATTC_GET_CHAR_EVT`` event is triggered when a characteristic is discovered. This event is used to print information about the characteristic. * If the characteristic ID is the same as the one defined by ``REMOTE_NOTIFY_CHAR_UUID``, the client registers for notifications on that characteristic value. - * Finally, the next characteristic is requested using the same ``esp_ble_gattc_get_characteristic()`` function, this time, the last parameter is set to the current characteristic. This triggers another ``ESP_GATTC_GET_CHAR_EVT`` and the process is repeated until all characteristics are obtained. - ``` -case ESP_GATTC_GET_CHAR_EVT: + ```c + case ESP_GATTC_GET_CHAR_EVT: if (p_data->get_char.status != ESP_GATT_OK) { break; } @@ -228,25 +221,24 @@ At this point the client has acquired all characteristics from the remote device #### Connecting to the Next Remote Device * In order to pass this descriptor as a parameter we first define it as: - ``` -static esp_gatt_id_t notify_descr_id = { + ```c + static esp_gatt_id_t notify_descr_id = { .uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,}, - }, + }, .inst_id = 0, -}; + }; ``` - Where ``ESP_GATT_UUID_CHAR_CLIENT_CONFIG`` is defined as the UUID to identify the CCC: - - ``` + Where ``ESP_GATT_UUID_CHAR_CLIENT_CONFIG`` is defined as the UUID to identify the CCC: + + ```c #define ESP_GATT_UUID_CHAR_CLIENT_CONFIG 0x2902 /* Client Characteristic Configuration */ ``` - - The value to write is “1” to enable notifications. The parameter ``ESP_GATT_WRITE_TYPE_RSP`` is also passed to request that the server responds to the write request, as well as the ``ESP_GATT_AUTH_REQ_NONE`` parameter to indicate that the write request does not need authorization: - - ``` + The value to write is “1” to enable notifications. The parameter ``ESP_GATT_WRITE_TYPE_RSP`` is also passed to request that the server responds to the write request, as well as the ``ESP_GATT_AUTH_REQ_NONE`` parameter to indicate that the write request does not need authorization: + + ```c case ESP_GATTC_REG_FOR_NOTIFY_EVT: { if (p_data->reg_for_notify.status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "reg notify failed, error status =%x", p_data->reg_for_notify.status); @@ -269,12 +261,12 @@ static esp_gatt_id_t notify_descr_id = { ESP_GATT_AUTH_REQ_NONE); break; } - ``` + * Once notifications are enabled, the remote device sends a notification which triggers the ``ESP_GATTC_NOTIFY_EVT`` event on the client. This event is handled to write back to the characteristic using the ``esp_ble_gattc_write_char()`` function: - ``` -case ESP_GATTC_NOTIFY_EVT: + ```c + case ESP_GATTC_NOTIFY_EVT: ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, Receive notify value:"); esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len); //write back @@ -287,12 +279,12 @@ case ESP_GATTC_NOTIFY_EVT: ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); break; - ``` - + ``` + * If the writing procedure is acknowledged then the remote device has connected successfully and communication is established without error. Immediately, the write procedure generates an ``ESP_GATTC_WRITE_CHAR_EVT`` event which in this example is used to print information and connect to the second remote device: - ``` -case ESP_GATTC_WRITE_CHAR_EVT: + ```c + case ESP_GATTC_WRITE_CHAR_EVT: if (p_data->write.status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status); }else{ @@ -304,10 +296,11 @@ case ESP_GATTC_WRITE_CHAR_EVT: } break; ``` + * This triggers an open event which is handled by the Profile B event handler. This handler follows the same steps to search for services, get characteristics, register for notifications and write to the characteristic as the first device. The sequence for the second device also ends with an ``ESP_GATTC_WRITE_CHAR_EVT`` event which in turn is used to connect to the third device: - ``` - case ESP_GATTC_WRITE_CHAR_EVT: + ```c + case ESP_GATTC_WRITE_CHAR_EVT: if (p_data->write.status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "Write char failed, error status = %x", p_data->write.status); }else{ diff --git a/examples/bluetooth/gattc_multi_connect/tutorial/image/ESP32 GATT Multi-connect Client Application Profiles.png b/examples/bluetooth/gattc_multi_connect/tutorial/image/ESP32 GATT Multi-connect Client Application Profiles.png deleted file mode 100644 index ab455674c..000000000 Binary files a/examples/bluetooth/gattc_multi_connect/tutorial/image/ESP32 GATT Multi-connect Client Application Profiles.png and /dev/null differ diff --git a/examples/bluetooth/gattc_multi_connect/tutorial/image/ESP32_GATT_Multi_Connect_Client_Application_Profiles.png b/examples/bluetooth/gattc_multi_connect/tutorial/image/ESP32_GATT_Multi_Connect_Client_Application_Profiles.png new file mode 100644 index 000000000..dbc61b962 Binary files /dev/null and b/examples/bluetooth/gattc_multi_connect/tutorial/image/ESP32_GATT_Multi_Connect_Client_Application_Profiles.png differ diff --git a/examples/bluetooth/gattc_multi_connect/tutorial/image/Multi-connection GATT Client Flowchart.png b/examples/bluetooth/gattc_multi_connect/tutorial/image/Multi-connection GATT Client Flowchart.png deleted file mode 100644 index e53548f7d..000000000 Binary files a/examples/bluetooth/gattc_multi_connect/tutorial/image/Multi-connection GATT Client Flowchart.png and /dev/null differ diff --git a/examples/bluetooth/gattc_multi_connect/tutorial/image/Multi_Connection_GATT_Client_Flowchart.png b/examples/bluetooth/gattc_multi_connect/tutorial/image/Multi_Connection_GATT_Client_Flowchart.png new file mode 100644 index 000000000..eb4b45918 Binary files /dev/null and b/examples/bluetooth/gattc_multi_connect/tutorial/image/Multi_Connection_GATT_Client_Flowchart.png differ diff --git a/examples/peripherals/i2s_adc_dac/main/app_main.c b/examples/peripherals/i2s_adc_dac/main/app_main.c index d71645d1d..032edd3b4 100755 --- a/examples/peripherals/i2s_adc_dac/main/app_main.c +++ b/examples/peripherals/i2s_adc_dac/main/app_main.c @@ -9,8 +9,11 @@ #include "driver/i2s.h" #include "driver/adc.h" #include "audio_example_file.h" +#include "esp_adc_cal.h" static const char* TAG = "ad/da"; +#define V_REF 1100 +#define ADC1_TEST_CHANNEL (ADC1_CHANNEL_7) #define PARTITION_NAME "storage" @@ -36,6 +39,10 @@ static const char* TAG = "ad/da"; #define EXAMPLE_I2S_FORMAT (I2S_CHANNEL_FMT_RIGHT_LEFT) //I2S channel number #define EXAMPLE_I2S_CHANNEL_NUM ((EXAMPLE_I2S_FORMAT < I2S_CHANNEL_FMT_ONLY_RIGHT) ? (2) : (1)) +//I2S built-in ADC unit +#define I2S_ADC_UNIT ADC_UNIT_1 +//I2S built-in ADC channel +#define I2S_ADC_CHANNEL ADC1_CHANNEL_0 //flash record size, for recording 5 seconds' data #define FLASH_RECORD_SIZE (EXAMPLE_I2S_CHANNEL_NUM * EXAMPLE_I2S_SAMPLE_RATE * EXAMPLE_I2S_SAMPLE_BITS / 8 * 5) @@ -45,6 +52,7 @@ static const char* TAG = "ad/da"; //flash read / write address #define FLASH_ADDR (0x200000) + /** * @brief I2S ADC/DAC mode init. */ @@ -66,7 +74,7 @@ void example_i2s_init() //init DAC pad i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); //init ADC pad - i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_0); + i2s_set_adc_mode(I2S_ADC_UNIT, I2S_ADC_CHANNEL); } /* @@ -193,10 +201,8 @@ void example_i2s_adc_dac(void*arg) ESP_LOGE(TAG, "Partition error: can't find partition name: %s\n", PARTITION_NAME); vTaskDelete(NULL); } - //1. Erase flash example_erase_flash(); - example_i2s_init(); int i2s_read_len = EXAMPLE_I2S_READ_LEN; int flash_wr_size = 0; @@ -204,6 +210,7 @@ void example_i2s_adc_dac(void*arg) #if RECORD_IN_FLASH_EN char* i2s_read_buff = (char*) calloc(i2s_read_len, sizeof(char)); uint8_t* flash_write_buff = (uint8_t*) calloc(i2s_read_len, sizeof(char)); + i2s_adc_enable(EXAMPLE_I2S_NUM); while (flash_wr_size < FLASH_RECORD_SIZE) { //read data from I2S bus, in this case, from ADC. i2s_read_bytes(EXAMPLE_I2S_NUM, (char*) i2s_read_buff, i2s_read_len, portMAX_DELAY); @@ -213,6 +220,7 @@ void example_i2s_adc_dac(void*arg) flash_wr_size += i2s_read_len; ets_printf("Sound recording %u%%\n", flash_wr_size * 100 / FLASH_RECORD_SIZE); } + i2s_adc_disable(EXAMPLE_I2S_NUM); free(i2s_read_buff); i2s_read_buff = NULL; free(flash_write_buff); @@ -256,10 +264,25 @@ void example_i2s_adc_dac(void*arg) vTaskDelete(NULL); } +void adc_read_task(void* arg) +{ + adc1_config_width(ADC_WIDTH_12Bit); + adc1_config_channel_atten(ADC1_TEST_CHANNEL, ADC_ATTEN_11db); + esp_adc_cal_characteristics_t characteristics; + esp_adc_cal_get_characteristics(V_REF, ADC_ATTEN_11db, ADC_WIDTH_12Bit, &characteristics); + while(1) { + uint32_t voltage = adc1_to_voltage(ADC1_TEST_CHANNEL, &characteristics); + ESP_LOGI(TAG, "%d mV", voltage); + vTaskDelay(200 / portTICK_RATE_MS); + } +} + esp_err_t app_main() { + example_i2s_init(); esp_log_level_set("I2S", ESP_LOG_INFO); xTaskCreate(example_i2s_adc_dac, "example_i2s_adc_dac", 1024 * 2, NULL, 5, NULL); + xTaskCreate(adc_read_task, "ADC read task", 2048, NULL, 5, NULL); return ESP_OK; } diff --git a/examples/peripherals/uart_events/main/uart_events_example_main.c b/examples/peripherals/uart_events/main/uart_events_example_main.c index ac8ac3c91..8c9f545cb 100644 --- a/examples/peripherals/uart_events/main/uart_events_example_main.c +++ b/examples/peripherals/uart_events/main/uart_events_example_main.c @@ -7,6 +7,7 @@ CONDITIONS OF ANY KIND, either express or implied. */ #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" @@ -29,58 +30,85 @@ static const char *TAG = "uart_events"; */ #define EX_UART_NUM UART_NUM_0 +#define PATTERN_CHR_NUM (3) /*!< Set the number of consecutive and identical characters received by receiver which defines a UART pattern*/ #define BUF_SIZE (1024) +#define RD_BUF_SIZE (BUF_SIZE) static QueueHandle_t uart0_queue; static void uart_event_task(void *pvParameters) { uart_event_t event; size_t buffered_size; - uint8_t *dtmp = (uint8_t *) malloc(BUF_SIZE); - while (1) { - /* Waiting for UART event. - If it happens then print out information what is it */ - if (xQueueReceive(uart0_queue, (void * )&event, (portTickType)portMAX_DELAY)) { + uint8_t* dtmp = (uint8_t*) malloc(RD_BUF_SIZE); + for(;;) { + //Waiting for UART event. + if(xQueueReceive(uart0_queue, (void * )&event, (portTickType)portMAX_DELAY)) { + bzero(dtmp, RD_BUF_SIZE); ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM); - switch (event.type) { - case UART_DATA: - /* Event of UART receiving data - * We'd better handler data event fast, there would be much more data events - * than other types of events. - * If we take too much time on data event, the queue might be full. - * In this example, we don't process data in event, but read data outside. - */ - uart_get_buffered_data_len(EX_UART_NUM, &buffered_size); - ESP_LOGI(TAG, "data, len: %d; buffered len: %d", event.size, buffered_size); - break; - case UART_FIFO_OVF: - ESP_LOGE(TAG, "hw fifo overflow"); - // If fifo overflow happened, you should consider adding flow control for your application. - // We can read data out out the buffer, or directly flush the Rx buffer. - uart_flush(EX_UART_NUM); - break; - case UART_BUFFER_FULL: - ESP_LOGE(TAG, "ring buffer full"); - // If buffer full happened, you should consider increasing your buffer size - // We can read data out out the buffer, or directly flush the Rx buffer. - uart_flush(EX_UART_NUM); - break; - case UART_BREAK: - ESP_LOGI(TAG, "uart rx break detected"); - break; - case UART_PARITY_ERR: - ESP_LOGE(TAG, "uart parity error"); - break; - case UART_FRAME_ERR: - ESP_LOGE(TAG, "uart frame error"); - break; - case UART_PATTERN_DET: - ESP_LOGI(TAG, "uart pattern detected"); - break; - default: - ESP_LOGE(TAG, "not serviced uart event type: %d\n", event.type); - break; + switch(event.type) { + //Event of UART receving data + /*We'd better handler data event fast, there would be much more data events than + other types of events. If we take too much time on data event, the queue might + be full.*/ + case UART_DATA: + ESP_LOGI(TAG, "[UART DATA]: %d", event.size); + uart_read_bytes(EX_UART_NUM, dtmp, event.size, portMAX_DELAY); + ESP_LOGI(TAG, "[DATA EVT]:"); + uart_write_bytes(EX_UART_NUM, (const char*) dtmp, event.size); + break; + //Event of HW FIFO overflow detected + case UART_FIFO_OVF: + ESP_LOGI(TAG, "hw fifo overflow"); + // If fifo overflow happened, you should consider adding flow control for your application. + // The ISR has already reset the rx FIFO, + // As an example, we directly flush the rx buffer here in order to read more data. + uart_flush_input(EX_UART_NUM); + xQueueReset(uart0_queue); + break; + //Event of UART ring buffer full + case UART_BUFFER_FULL: + ESP_LOGI(TAG, "ring buffer full"); + // If buffer full happened, you should consider encreasing your buffer size + // As an example, we directly flush the rx buffer here in order to read more data. + uart_flush_input(EX_UART_NUM); + xQueueReset(uart0_queue); + break; + //Event of UART RX break detected + case UART_BREAK: + ESP_LOGI(TAG, "uart rx break"); + break; + //Event of UART parity check error + case UART_PARITY_ERR: + ESP_LOGI(TAG, "uart parity error"); + break; + //Event of UART frame error + case UART_FRAME_ERR: + ESP_LOGI(TAG, "uart frame error"); + break; + //UART_PATTERN_DET + case UART_PATTERN_DET: + uart_get_buffered_data_len(EX_UART_NUM, &buffered_size); + int pos = uart_pattern_pop_pos(EX_UART_NUM); + ESP_LOGI(TAG, "[UART PATTERN DETECTED] pos: %d, buffered size: %d", pos, buffered_size); + if (pos == -1) { + // There used to be a UART_PATTERN_DET event, but the pattern position queue is full so that it can not + // record the position. We should set a larger queue size. + // As an example, we directly flush the rx buffer here. + uart_flush_input(EX_UART_NUM); + } else { + uart_read_bytes(EX_UART_NUM, dtmp, pos, 100 / portTICK_PERIOD_MS); + uint8_t pat[PATTERN_CHR_NUM + 1]; + memset(pat, 0, sizeof(pat)); + uart_read_bytes(EX_UART_NUM, pat, PATTERN_CHR_NUM, 100 / portTICK_PERIOD_MS); + ESP_LOGI(TAG, "read data: %s", dtmp); + ESP_LOGI(TAG, "read pat : %s", pat); + } + break; + //Others + default: + ESP_LOGI(TAG, "uart event type: %d", event.type); + break; } } } @@ -103,23 +131,19 @@ void app_main() .flow_ctrl = UART_HW_FLOWCTRL_DISABLE }; uart_param_config(EX_UART_NUM, &uart_config); - // Set UART pins using UART0 default pins i.e. no changes + + //Set UART log level + esp_log_level_set(TAG, ESP_LOG_INFO); + //Set UART pins (using UART0 default pins ie no changes.) uart_set_pin(EX_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); - uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 10, &uart0_queue, 0); + //Install UART driver, and get the queue. + uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0); - // Set uart pattern detection function - uart_enable_pattern_det_intr(EX_UART_NUM, '+', 3, 10000, 10, 10); + //Set uart pattern detect function. + uart_enable_pattern_det_intr(EX_UART_NUM, '+', PATTERN_CHR_NUM, 10000, 10, 10); + //Reset the pattern queue length to record at most 20 pattern positions. + uart_pattern_queue_reset(EX_UART_NUM, 20); - // Create a task to handle uart event from ISR + //Create a task to handler UART event from ISR xTaskCreate(uart_event_task, "uart_event_task", 2048, NULL, 12, NULL); - - // Reserve a buffer and process incoming data - uint8_t *data = (uint8_t *) malloc(BUF_SIZE); - while (1) { - int len = uart_read_bytes(EX_UART_NUM, data, BUF_SIZE, 100 / portTICK_RATE_MS); - if (len > 0) { - ESP_LOGI(TAG, "uart read : %d", len); - uart_write_bytes(EX_UART_NUM, (const char *)data, len); - } - } } diff --git a/examples/protocols/mdns/README.md b/examples/protocols/mdns/README.md index 76dbdc17b..8cc56d835 100644 --- a/examples/protocols/mdns/README.md +++ b/examples/protocols/mdns/README.md @@ -2,4 +2,20 @@ Shows how to use mDNS to advertise lookup services and hosts +## Example workflow + +- mDNS is initialized with host name and instance name defined through `make menuconfig` and `_http._tcp` service is added to be advertised +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- The system event handler is used to pass the network events to mDNS so the service is aware when the interface comes up or down +- GPIO0 (BOOT Button) is initialized as pulled-up input that can be monitored for button press +- Example task is started to check if the button is pressed so it can execute the mDNS queries defined + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and the default device mDNS host name and instance name +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connec to your access point +- You can now ping the device at `[hostname].local` and browse for `_http._tcp` on the same network to find the advertised service +- Pressing the BOOT button will start quring the local network for the predefined in `check_button` hosts and services + See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/mdns/main/mdns_example_main.c b/examples/protocols/mdns/main/mdns_example_main.c old mode 100644 new mode 100755 index 29e4e9b47..e6495b0eb --- a/examples/protocols/mdns/main/mdns_example_main.c +++ b/examples/protocols/mdns/main/mdns_example_main.c @@ -16,6 +16,9 @@ #include "esp_log.h" #include "nvs_flash.h" #include "mdns.h" +#include "driver/gpio.h" +#include +#include /* The examples use simple WiFi configuration that you can set via 'make menuconfig'. @@ -35,9 +38,11 @@ static EventGroupHandle_t wifi_event_group; /* The event group allows multiple bits for each event, but we only care about one event - are we connected to the AP with an IP? */ -const int CONNECTED_BIT = BIT0; +const int IP4_CONNECTED_BIT = BIT0; +const int IP6_CONNECTED_BIT = BIT1; static const char *TAG = "mdns-test"; +static bool auto_reconnect = true; static esp_err_t event_handler(void *ctx, system_event_t *event) { @@ -50,17 +55,23 @@ static esp_err_t event_handler(void *ctx, system_event_t *event) tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); break; case SYSTEM_EVENT_STA_GOT_IP: - xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, IP4_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STA_GOT_IP6: + xEventGroupSetBits(wifi_event_group, IP6_CONNECTED_BIT); break; case SYSTEM_EVENT_STA_DISCONNECTED: /* This is a workaround as ESP32 WiFi libs don't currently auto-reassociate. */ - esp_wifi_connect(); - xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + if (auto_reconnect) { + esp_wifi_connect(); + } + xEventGroupClearBits(wifi_event_group, IP4_CONNECTED_BIT | IP6_CONNECTED_BIT); break; default: break; } + mdns_handle_system_event(ctx, event); return ESP_OK; } @@ -84,101 +95,150 @@ static void initialise_wifi(void) ESP_ERROR_CHECK( esp_wifi_start() ); } -static void query_mdns_service(mdns_server_t * mdns, const char * service, const char * proto) +static void initialise_mdns(void) { - if(!mdns) { + //initialize mDNS + ESP_ERROR_CHECK( mdns_init() ); + //set mDNS hostname (required if you want to advertise services) + ESP_ERROR_CHECK( mdns_hostname_set(EXAMPLE_MDNS_HOSTNAME) ); + //set default mDNS instance name + ESP_ERROR_CHECK( mdns_instance_name_set(EXAMPLE_MDNS_INSTANCE) ); + + //structure with TXT records + mdns_txt_item_t serviceTxtData[3] = { + {"board","esp32"}, + {"u","user"}, + {"p","password"} + }; + + //initialize service + ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, serviceTxtData, 3) ); + //add another TXT item + ESP_ERROR_CHECK( mdns_service_txt_item_set("_http", "_tcp", "path", "/foobar") ); + //change TXT item value + ESP_ERROR_CHECK( mdns_service_txt_item_set("_http", "_tcp", "u", "admin") ); +} + +static const char * if_str[] = {"STA", "AP", "ETH", "MAX"}; +static const char * ip_protocol_str[] = {"V4", "V6", "MAX"}; + +static void mdns_print_results(mdns_result_t * results){ + mdns_result_t * r = results; + mdns_ip_addr_t * a = NULL; + int i = 1, t; + while(r){ + printf("%d: Interface: %s, Type: %s\n", i++, if_str[r->tcpip_if], ip_protocol_str[r->ip_protocol]); + if(r->instance_name){ + printf(" PTR : %s\n", r->instance_name); + } + if(r->hostname){ + printf(" SRV : %s.local:%u\n", r->hostname, r->port); + } + if(r->txt_count){ + printf(" TXT : [%u] ", r->txt_count); + for(t=0; ttxt_count; t++){ + printf("%s=%s; ", r->txt[t].key, r->txt[t].value); + } + printf("\n"); + } + a = r->addr; + while(a){ + if(a->addr.type == MDNS_IP_PROTOCOL_V6){ + printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6)); + } else { + printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4))); + } + a = a->next; + } + r = r->next; + } + +} + +static void query_mdns_service(const char * service_name, const char * proto) +{ + ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto); + + mdns_result_t * results = NULL; + esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results); + if(err){ + ESP_LOGE(TAG, "Query Failed"); return; } - uint32_t res; - if (!proto) { - ESP_LOGI(TAG, "Host Lookup: %s", service); - res = mdns_query(mdns, service, 0, 1000); - if (res) { - size_t i; - for(i=0; iaddr), IPV62STR(r->addrv6)); - } - } - mdns_result_free(mdns); - } else { - ESP_LOGI(TAG, " Not Found"); - } - } else { - ESP_LOGI(TAG, "Service Lookup: %s.%s ", service, proto); - res = mdns_query(mdns, service, proto, 1000); - if (res) { - size_t i; - for(i=0; ihost)?r->host:"", (r->instance)?r->instance:"", - IP2STR(&r->addr), IPV62STR(r->addrv6), - r->port, (r->txt)?r->txt:""); - } - } - mdns_result_free(mdns); - } + if(!results){ + ESP_LOGW(TAG, "No results found!"); + return; } + + mdns_print_results(results); + mdns_query_results_free(results); +} + +static void query_mdns_host(const char * host_name) +{ + ESP_LOGI(TAG, "Query A: %s.local", host_name); + + struct ip4_addr addr; + addr.addr = 0; + + esp_err_t err = mdns_query_a(host_name, 2000, &addr); + if(err){ + if(err == ESP_ERR_NOT_FOUND){ + ESP_LOGW(TAG, "Host was not found!"); + return; + } + ESP_LOGE(TAG, "Query Failed"); + return; + } + + ESP_LOGI(TAG, IPSTR, IP2STR(&addr)); +} + +static void initialise_button(void) +{ + gpio_config_t io_conf; + io_conf.intr_type = GPIO_PIN_INTR_DISABLE; + io_conf.pin_bit_mask = 1; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + io_conf.pull_down_en = 0; + gpio_config(&io_conf); +} + +static void check_button(void) +{ + static bool old_level = true; + bool new_level = gpio_get_level(GPIO_NUM_0); + if (!new_level && old_level) { + query_mdns_host("esp32"); + query_mdns_service("_arduino", "_tcp"); + query_mdns_service("_http", "_tcp"); + query_mdns_service("_printer", "_tcp"); + query_mdns_service("_ipp", "_tcp"); + query_mdns_service("_afpovertcp", "_tcp"); + query_mdns_service("_smb", "_tcp"); + query_mdns_service("_ftp", "_tcp"); + query_mdns_service("_nfs", "_tcp"); + } + old_level = new_level; } static void mdns_example_task(void *pvParameters) { - mdns_server_t * mdns = NULL; + /* Wait for the callback to set the CONNECTED_BIT in the event group. */ + xEventGroupWaitBits(wifi_event_group, IP4_CONNECTED_BIT | IP6_CONNECTED_BIT, + false, true, portMAX_DELAY); while(1) { - /* Wait for the callback to set the CONNECTED_BIT in the - event group. - */ - xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, - false, true, portMAX_DELAY); - ESP_LOGI(TAG, "Connected to AP"); - - if (!mdns) { - esp_err_t err = mdns_init(TCPIP_ADAPTER_IF_STA, &mdns); - if (err) { - ESP_LOGE(TAG, "Failed starting MDNS: %u", err); - continue; - } - - ESP_ERROR_CHECK( mdns_set_hostname(mdns, EXAMPLE_MDNS_HOSTNAME) ); - ESP_ERROR_CHECK( mdns_set_instance(mdns, EXAMPLE_MDNS_INSTANCE) ); - - const char * arduTxtData[4] = { - "board=esp32", - "tcp_check=no", - "ssh_upload=no", - "auth_upload=no" - }; - - ESP_ERROR_CHECK( mdns_service_add(mdns, "_arduino", "_tcp", 3232) ); - ESP_ERROR_CHECK( mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData) ); - ESP_ERROR_CHECK( mdns_service_add(mdns, "_http", "_tcp", 80) ); - ESP_ERROR_CHECK( mdns_service_instance_set(mdns, "_http", "_tcp", "ESP32 WebServer") ); - ESP_ERROR_CHECK( mdns_service_add(mdns, "_smb", "_tcp", 445) ); - } else { - query_mdns_service(mdns, "esp32", NULL); - query_mdns_service(mdns, "_arduino", "_tcp"); - query_mdns_service(mdns, "_http", "_tcp"); - query_mdns_service(mdns, "_printer", "_tcp"); - query_mdns_service(mdns, "_ipp", "_tcp"); - query_mdns_service(mdns, "_afpovertcp", "_tcp"); - query_mdns_service(mdns, "_smb", "_tcp"); - query_mdns_service(mdns, "_ftp", "_tcp"); - query_mdns_service(mdns, "_nfs", "_tcp"); - } - - ESP_LOGI(TAG, "Restarting in 10 seconds!"); - vTaskDelay(10000 / portTICK_PERIOD_MS); - ESP_LOGI(TAG, "Starting again!"); + check_button(); + vTaskDelay(50 / portTICK_PERIOD_MS); } } void app_main() { ESP_ERROR_CHECK( nvs_flash_init() ); + initialise_mdns(); initialise_wifi(); + initialise_button(); xTaskCreate(&mdns_example_task, "mdns_example_task", 2048, NULL, 5, NULL); } diff --git a/examples/storage/sd_card/main/sd_card_example_main.c b/examples/storage/sd_card/main/sd_card_example_main.c index 459673e95..bf9b5deab 100644 --- a/examples/storage/sd_card/main/sd_card_example_main.c +++ b/examples/storage/sd_card/main/sd_card_example_main.c @@ -81,7 +81,8 @@ void app_main(void) // formatted in case when mounting fails. esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = false, - .max_files = 5 + .max_files = 5, + .allocation_unit_size = 16 * 1024 }; // Use settings defined above to initialize SD card and mount FAT filesystem. diff --git a/examples/storage/wear_levelling/README.md b/examples/storage/wear_levelling/README.md index 40e492498..80733b8bd 100644 --- a/examples/storage/wear_levelling/README.md +++ b/examples/storage/wear_levelling/README.md @@ -1,4 +1,4 @@ -# SD Card example +# Wear levelling example This example demonstrates how to use wear levelling library and FATFS library to store files in a partition inside SPI flash. Example does the following steps: @@ -15,9 +15,14 @@ This example demonstrates how to use wear levelling library and FATFS library to Here is an typical example console output. ``` -Try to open file ... -I (239) wear_level: Reading file -Read from file: 'Hello User! I'm happy to see you!1' -W (239) wear_levelling: wl_unmount Delete driver +I (280) example: Mounting FAT filesystem +W (440) vfs_fat_spiflash: f_mount failed (13) +I (440) vfs_fat_spiflash: Formatting FATFS partition, allocation unit size=4096 +I (660) vfs_fat_spiflash: Mounting again +I (660) example: Opening file +I (910) example: File written +I (910) example: Reading file +I (920) example: Read from file: 'written using ESP-IDF v3.1-dev-171-gf9ad17eee-dirty' +I (920) example: Unmounting FAT filesystem +I (1000) example: Done ``` - diff --git a/examples/storage/wear_levelling/main/wear_levelling_example_main.c b/examples/storage/wear_levelling/main/wear_levelling_example_main.c index dd02542fc..474b608c5 100644 --- a/examples/storage/wear_levelling/main/wear_levelling_example_main.c +++ b/examples/storage/wear_levelling/main/wear_levelling_example_main.c @@ -32,7 +32,8 @@ void app_main(void) // and allow format partition in case if it is new one and was not formated before const esp_vfs_fat_mount_config_t mount_config = { .max_files = 4, - .format_if_mount_failed = true + .format_if_mount_failed = true, + .allocation_unit_size = CONFIG_WL_SECTOR_SIZE }; esp_err_t err = esp_vfs_fat_spiflash_mount(base_path, "storage", &mount_config, &s_wl_handle); if (err != ESP_OK) { diff --git a/examples/system/console/main/cmd_system.c b/examples/system/console/main/cmd_system.c index 04003e8d5..e7192b9fe 100644 --- a/examples/system/console/main/cmd_system.c +++ b/examples/system/console/main/cmd_system.c @@ -20,11 +20,19 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "soc/rtc_cntl_reg.h" +#include "sdkconfig.h" + +#ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS +#define WITH_TASKS_INFO 1 +#endif static void register_free(); static void register_restart(); static void register_deep_sleep(); static void register_make(); +#if WITH_TASKS_INFO +static void register_tasks(); +#endif void register_system() { @@ -32,6 +40,9 @@ void register_system() register_restart(); register_deep_sleep(); register_make(); +#if WITH_TASKS_INFO + register_tasks(); +#endif } /** 'restart' command restarts the program */ @@ -72,6 +83,36 @@ static void register_free() ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); } +/** 'tasks' command prints the list of tasks and related information */ +#if WITH_TASKS_INFO + +static int tasks_info(int argc, char** argv) +{ + const size_t bytes_per_task = 40; /* see vTaskList description */ + char* task_list_buffer = malloc(uxTaskGetNumberOfTasks() * bytes_per_task); + if (task_list_buffer == NULL) { + ESP_LOGE(__func__, "failed to allocate buffer for vTaskList output"); + return 1; + } + vTaskList(task_list_buffer); + fputs(task_list_buffer, stdout); + free(task_list_buffer); + return 0; +} + +static void register_tasks() +{ + const esp_console_cmd_t cmd = { + .command = "tasks", + .help = "Get information about running tasks", + .hint = NULL, + .func = &tasks_info, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); +} + +#endif // WITH_TASKS_INFO + /** 'deep_sleep' command puts the chip into deep sleep mode */ static struct { diff --git a/examples/system/console/main/console_example_main.c b/examples/system/console/main/console_example_main.c index 4cd1ac830..65df945b7 100644 --- a/examples/system/console/main/console_example_main.c +++ b/examples/system/console/main/console_example_main.c @@ -18,6 +18,8 @@ #include "argtable3/argtable3.h" #include "cmd_decl.h" #include "esp_vfs_fat.h" +#include "nvs.h" +#include "nvs_flash.h" static const char* TAG = "example"; @@ -45,6 +47,16 @@ static void initialize_filesystem() } #endif // CONFIG_STORE_HISTORY +static void initialize_nvs() +{ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK( nvs_flash_erase() ); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); +} + static void initialize_console() { /* Disable buffering on stdin and stdout */ @@ -94,6 +106,8 @@ static void initialize_console() void app_main() { + initialize_nvs(); + #if CONFIG_STORE_HISTORY initialize_filesystem(); #endif diff --git a/examples/system/console/sdkconfig.defaults b/examples/system/console/sdkconfig.defaults index 96cda3dea..cb3bc3451 100644 --- a/examples/system/console/sdkconfig.defaults +++ b/examples/system/console/sdkconfig.defaults @@ -3,7 +3,7 @@ CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y CONFIG_LOG_BOOTLOADER_LEVEL=2 # Increase main task stack size -CONFIG_MAIN_TASK_STACK_SIZE=6144 +CONFIG_MAIN_TASK_STACK_SIZE=7168 # Enable filesystem CONFIG_PARTITION_TABLE_CUSTOM=y @@ -11,3 +11,7 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv" CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000 CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv" CONFIG_APP_OFFSET=0x10000 + +# Enable FreeRTOS stats formatting functions, needed for 'tasks' command +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y diff --git a/examples/wifi/iperf/components/iperf.c b/examples/wifi/iperf/components/iperf.c index e58457cbc..80cadb400 100644 --- a/examples/wifi/iperf/components/iperf.c +++ b/examples/wifi/iperf/components/iperf.c @@ -110,7 +110,8 @@ esp_err_t iperf_start_report(void) { int ret; - ret = xTaskCreate(iperf_report_task, IPERF_REPORT_TASK_NAME, IPERF_REPORT_TASK_STACK, NULL, IPERF_REPORT_TASK_PRIORITY, NULL); + ret = xTaskCreatePinnedToCore(iperf_report_task, IPERF_REPORT_TASK_NAME, IPERF_REPORT_TASK_STACK, NULL, IPERF_REPORT_TASK_PRIORITY, NULL, portNUM_PROCESSORS-1); + if (ret != pdPASS) { ESP_LOGE(TAG, "create task %s failed", IPERF_REPORT_TASK_NAME); return ESP_FAIL; @@ -191,7 +192,7 @@ esp_err_t iperf_run_tcp_server(void) return ESP_OK; } -esp_err_t iperf_run_udp_server(void) +esp_err_t IRAM_ATTR iperf_run_udp_server(void) { socklen_t addr_len = sizeof(struct sockaddr_in); struct sockaddr_in addr; @@ -321,7 +322,8 @@ esp_err_t iperf_run_udp_client(void) esp_err_t iperf_run_tcp_client(void) { - struct sockaddr_in addr; + struct sockaddr_in local_addr; + struct sockaddr_in remote_addr; int actual_send = 0; int want_send = 0; uint8_t *buffer; @@ -336,19 +338,21 @@ esp_err_t iperf_run_tcp_client(void) setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - addr.sin_family = AF_INET; - addr.sin_port = 0; - addr.sin_addr.s_addr = s_iperf_ctrl.cfg.sip; - if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sin_family = AF_INET; + local_addr.sin_port = 0; + local_addr.sin_addr.s_addr = s_iperf_ctrl.cfg.sip; + if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) != 0) { iperf_show_socket_error_reason("tcp client bind", sockfd); return ESP_FAIL; } - addr.sin_family = AF_INET; - addr.sin_port = htons(s_iperf_ctrl.cfg.dport); - addr.sin_addr.s_addr = s_iperf_ctrl.cfg.dip; - if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + memset(&remote_addr, 0, sizeof(remote_addr)); + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(s_iperf_ctrl.cfg.dport); + remote_addr.sin_addr.s_addr = s_iperf_ctrl.cfg.dip; + if (connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) { iperf_show_socket_error_reason("tcp client connect", sockfd); return ESP_FAIL; } @@ -359,7 +363,8 @@ esp_err_t iperf_run_tcp_client(void) while (!s_iperf_ctrl.finish) { actual_send = send(sockfd, buffer, want_send, 0); if (actual_send <= 0) { - vTaskDelay(1); + iperf_show_socket_error_reason("tcp client send", sockfd); + break; } else { s_iperf_ctrl.total_len += actual_send; } @@ -427,8 +432,9 @@ esp_err_t iperf_start(iperf_cfg_t *cfg) ESP_LOGE(TAG, "create buffer: out of memory"); return ESP_FAIL; } + memset(s_iperf_ctrl.buffer, 0, s_iperf_ctrl.buffer_len); - ret = xTaskCreate(iperf_task_traffic, IPERF_TRAFFIC_TASK_NAME, IPERF_TRAFFIC_TASK_STACK, NULL, IPERF_TRAFFIC_TASK_PRIORITY, NULL); + ret = xTaskCreatePinnedToCore(iperf_task_traffic, IPERF_TRAFFIC_TASK_NAME, IPERF_TRAFFIC_TASK_STACK, NULL, IPERF_TRAFFIC_TASK_PRIORITY, NULL, portNUM_PROCESSORS-1); if (ret != pdPASS) { ESP_LOGE(TAG, "create task %s failed", IPERF_TRAFFIC_TASK_NAME); free(s_iperf_ctrl.buffer); diff --git a/examples/wifi/iperf/components/iperf.h b/examples/wifi/iperf/components/iperf.h index 5d19e1d6d..1bbdaf18d 100644 --- a/examples/wifi/iperf/components/iperf.h +++ b/examples/wifi/iperf/components/iperf.h @@ -24,17 +24,17 @@ extern "C" { #define IPERF_DEFAULT_TIME 30 #define IPERF_TRAFFIC_TASK_NAME "iperf_traffic" -#define IPERF_TRAFFIC_TASK_PRIORITY 19 +#define IPERF_TRAFFIC_TASK_PRIORITY 10 #define IPERF_TRAFFIC_TASK_STACK 4096 #define IPERF_REPORT_TASK_NAME "iperf_report" -#define IPERF_REPORT_TASK_PRIORITY 10 +#define IPERF_REPORT_TASK_PRIORITY 20 #define IPERF_REPORT_TASK_STACK 4096 #define IPERF_REPORT_TASK_NAME "iperf_report" #define IPERF_UDP_TX_LEN (1472) -#define IPERF_UDP_RX_LEN (32<<10) -#define IPERF_TCP_TX_LEN (32<<10) -#define IPERF_TCP_RX_LEN (32<<10) +#define IPERF_UDP_RX_LEN (16<<10) +#define IPERF_TCP_TX_LEN (16<<10) +#define IPERF_TCP_RX_LEN (16<<10) #define IPERF_MAX_DELAY 64 diff --git a/examples/wifi/iperf/sdkconfig.defaults b/examples/wifi/iperf/sdkconfig.defaults index 2ec55c5ee..52b0cbaef 100644 --- a/examples/wifi/iperf/sdkconfig.defaults +++ b/examples/wifi/iperf/sdkconfig.defaults @@ -6,9 +6,9 @@ CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=16 CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=64 CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=64 CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y -CONFIG_ESP32_WIFI_TX_BA_WIN=12 +CONFIG_ESP32_WIFI_TX_BA_WIN=32 CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y -CONFIG_ESP32_WIFI_RX_BA_WIN=12 +CONFIG_ESP32_WIFI_RX_BA_WIN=32 CONFIG_FREERTOS_UNICORE= CONFIG_FREERTOS_HZ=1000 @@ -23,3 +23,7 @@ CONFIG_UDP_RECVMBOX_SIZE=64 CONFIG_TCPIP_RECVMBOX_SIZE=64 CONFIG_LWIP_ETHARP_TRUST_IP_MAC= +CONFIG_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_LWIP_IRAM_OPTIMIZATION=y + diff --git a/tools/ci/mirror-list.txt b/tools/ci/mirror-list.txt index d3972aa5d..789d4e844 100644 --- a/tools/ci/mirror-list.txt +++ b/tools/ci/mirror-list.txt @@ -3,6 +3,7 @@ components/bt/lib @GENERAL_MIRROR_SERVER@/idf/ components/aws_iot/aws-iot-device-sdk-embedded-C @GENERAL_MIRROR_SERVER@/idf/aws-iot-device-sdk-embedded-C.git ALLOW_TO_SYNC_FROM_PUBLIC components/coap/libcoap @GENERAL_MIRROR_SERVER@/idf/libcoap.git ALLOW_TO_SYNC_FROM_PUBLIC components/esptool_py/esptool @GENERAL_MIRROR_SERVER@/idf/esptool.git ALLOW_TO_SYNC_FROM_PUBLIC +components/json/cJSON @GENERAL_MIRROR_SERVER@/idf/cJSON.git ALLOW_TO_SYNC_FROM_PUBLIC components/libsodium/libsodium @GENERAL_MIRROR_SERVER@/idf/libsodium.git ALLOW_TO_SYNC_FROM_PUBLIC components/micro-ecc/micro-ecc @GENERAL_MIRROR_SERVER@/idf/micro-ecc.git ALLOW_TO_SYNC_FROM_PUBLIC components/nghttp/nghttp2 @GENERAL_MIRROR_SERVER@/idf/nghttp2.git ALLOW_TO_SYNC_FROM_PUBLIC diff --git a/tools/idf_monitor.py b/tools/idf_monitor.py index ccaddffa1..f0dcd6f7c 100755 --- a/tools/idf_monitor.py +++ b/tools/idf_monitor.py @@ -443,13 +443,25 @@ class Monitor(object): with self: # disable console control sys.stderr.write(ANSI_NORMAL) try: - subprocess.call(["%sgdb" % self.toolchain_prefix, + process = subprocess.Popen(["%sgdb" % self.toolchain_prefix, "-ex", "set serial baud %d" % self.serial.baudrate, "-ex", "target remote %s" % self.serial.port, "-ex", "interrupt", # monitor has already parsed the first 'reason' command, need a second self.elf_file], cwd=".") + process.wait() except KeyboardInterrupt: pass # happens on Windows, maybe other OSes + finally: + try: + # on Linux, maybe other OSes, gdb sometimes seems to be alive even after wait() returns... + process.terminate() + except: + pass + try: + # also on Linux, maybe other OSes, gdb sometimes exits uncleanly and breaks the tty mode + subprocess.call(["stty", "sane"]) + except: + pass # don't care if there's no stty, we tried... self.prompt_next_action("gdb exited") def output_enable(self, enable): @@ -567,6 +579,17 @@ if os.name == 'nt': self.handle = GetStdHandle(STD_ERROR_HANDLE if self.output == sys.stderr else STD_OUTPUT_HANDLE) self.matched = b'' + def _output_write(self, data): + # Windows 10 bug since the Fall Creators Update, sometimes writing to console randomly fails + # (but usually succeeds afterwards, it seems.) + # Ref https://github.com/espressif/esp-idf/issues/1136 + for tries in range(3): + try: + self.output.write(data) + return + except IOError: + pass + def write(self, data): for b in data: l = len(self.matched) @@ -585,18 +608,10 @@ if os.name == 'nt': color |= FOREGROUND_INTENSITY SetConsoleTextAttribute(self.handle, color) else: - self.output.write(self.matched) # not an ANSI color code, display verbatim + self._output_write(self.matched) # not an ANSI color code, display verbatim self.matched = b'' else: - try: - self.output.write(b) - except IOError: - # Windows 10 bug since the Fall Creators Update, sometimes writing to console randomly fails - # (but usually succeeds the second time, it seems.) Ref https://github.com/espressif/esp-idf/issues/1136 - try: - self.output.write(b) - except IOError: - pass + self._output_write(b) self.matched = b'' def flush(self): diff --git a/tools/tiny-test-fw/CIAssignExampleTest.py b/tools/tiny-test-fw/CIAssignExampleTest.py index 1cd361313..f13a803e4 100644 --- a/tools/tiny-test-fw/CIAssignExampleTest.py +++ b/tools/tiny-test-fw/CIAssignExampleTest.py @@ -22,147 +22,16 @@ import sys import re import argparse -import yaml - test_fw_path = os.getenv("TEST_FW_PATH") if test_fw_path: sys.path.insert(0, test_fw_path) -from Utility import CaseConfig, SearchCases, GitlabCIJob +from Utility.CIAssignTest import AssignTest -class Group(object): - - MAX_EXECUTION_TIME = 30 - MAX_CASE = 15 - SORT_KEYS = ["env_tag"] - - def __init__(self, case): - self.execution_time = 0 - self.case_list = [case] - self.filters = dict(zip(self.SORT_KEYS, [case.case_info[x] for x in self.SORT_KEYS])) - - def accept_new_case(self): - """ - check if allowed to add any case to this group - - :return: True or False - """ - max_time = (sum([x.case_info["execution_time"] for x in self.case_list]) < self.MAX_EXECUTION_TIME) - max_case = (len(self.case_list) < self.MAX_CASE) - return max_time and max_case - - def add_case(self, case): - """ - add case to current group - - :param case: test case - :return: True if add succeed, else False - """ - added = False - if self.accept_new_case(): - for key in self.filters: - if case.case_info[key] != self.filters[key]: - break - else: - self.case_list.append(case) - added = True - return added - - def output(self): - """ - output data for job configs - - :return: {"Filter": case filter, "CaseConfig": list of case configs for cases in this group} - """ - output_data = { - "Filter": self.filters, - "CaseConfig": [{"name": x.case_info["name"]} for x in self.case_list], - } - return output_data - - -class AssignTest(object): - """ - Auto assign tests to CI jobs. - - :param test_case: path of test case file(s) - :param ci_config_file: path of ``.gitlab-ci.yml`` - """ - +class CIExampleAssignTest(AssignTest): CI_TEST_JOB_PATTERN = re.compile(r"^example_test_.+") - def __init__(self, test_case, ci_config_file): - self.test_cases = self._search_cases(test_case) - self.jobs = self._parse_gitlab_ci_config(ci_config_file) - - def _parse_gitlab_ci_config(self, ci_config_file): - - with open(ci_config_file, "r") as f: - ci_config = yaml.load(f) - - job_list = list() - for job_name in ci_config: - if self.CI_TEST_JOB_PATTERN.search(job_name) is not None: - job_list.append(GitlabCIJob.Job(ci_config[job_name], job_name)) - return job_list - - @staticmethod - def _search_cases(test_case, case_filter=None): - """ - :param test_case: path contains test case folder - :param case_filter: filter for test cases - :return: filtered test case list - """ - test_methods = SearchCases.Search.search_test_cases(test_case) - return CaseConfig.filter_test_cases(test_methods, case_filter if case_filter else dict()) - - def _group_cases(self): - """ - separate all cases into groups according group rules. each group will be executed by one CI job. - - :return: test case groups. - """ - groups = [] - for case in self.test_cases: - for group in groups: - # add to current group - if group.add_case(case): - break - else: - # create new group - groups.append(Group(case)) - return groups - - def assign_cases(self): - """ - separate test cases to groups and assign test cases to CI jobs. - - :raise AssertError: if failed to assign any case to CI job. - :return: None - """ - failed_to_assign = [] - test_groups = self._group_cases() - for group in test_groups: - for job in self.jobs: - if job.match_group(group): - job.assign_group(group) - break - else: - failed_to_assign.append(group) - assert not failed_to_assign - - def output_configs(self, output_path): - """ - - :param output_path: path to output config files for each CI job - :return: None - """ - if not os.path.exists(output_path): - os.makedirs(output_path) - for job in self.jobs: - job.output_config(output_path) - if __name__ == '__main__': parser = argparse.ArgumentParser() @@ -174,6 +43,6 @@ if __name__ == '__main__': help="output path of config files") args = parser.parse_args() - assign_test = AssignTest(args.test_case, args.ci_config_file) + assign_test = CIExampleAssignTest(args.test_case, args.ci_config_file) assign_test.assign_cases() assign_test.output_configs(args.output_path) diff --git a/tools/tiny-test-fw/CIAssignUnitTest.py b/tools/tiny-test-fw/CIAssignUnitTest.py new file mode 100644 index 000000000..a621eb8e0 --- /dev/null +++ b/tools/tiny-test-fw/CIAssignUnitTest.py @@ -0,0 +1,121 @@ +""" +Command line tool to assign unit tests to CI test jobs. +""" + +import re +import os +import sys +import argparse + +import yaml + +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path: + sys.path.insert(0, test_fw_path) + +from Utility import CIAssignTest + + +class Group(CIAssignTest.Group): + SORT_KEYS = ["Test App", "SDK", "test environment"] + MAX_CASE = 30 + ATTR_CONVERT_TABLE = { + "execution_time": "execution time" + } + + @staticmethod + def _get_case_attr(case, attr): + if attr in Group.ATTR_CONVERT_TABLE: + attr = Group.ATTR_CONVERT_TABLE[attr] + return case[attr] + + @staticmethod + def _get_ut_config(test_app): + # we format test app "UT_ + config" when parsing test cases + # now we need to extract config + assert test_app[:3] == "UT_" + return test_app[3:] + + def _create_extra_data(self): + case_data = [] + for case in self.case_list: + if self._get_case_attr(case, "cmd set") == "multiple_devices_case": + case_data.append({ + "config": self._get_ut_config(self._get_case_attr(case, "Test App")), + "name": self._get_case_attr(case, "summary"), + "child case num": self._get_case_attr(case, "child case num") + }) + else: + case_data.append({ + "config": self._get_ut_config(self._get_case_attr(case, "Test App")), + "name": self._get_case_attr(case, "summary"), + "reset": self._get_case_attr(case, "reset") , + }) + return case_data + + def output(self): + """ + output data for job configs + + :return: {"Filter": case filter, "CaseConfig": list of case configs for cases in this group} + """ + output_data = { + # we don't need filter for test function, as UT uses a few test functions for all cases + "CaseConfig": [ + { + "name": self.case_list[0]["cmd set"] if isinstance(self.case_list[0]["cmd set"], str) else self.case_list[0]["cmd set"][0], + "extra_data": self._create_extra_data(), + } + ] + } + return output_data + + +class UnitTestAssignTest(CIAssignTest.AssignTest): + CI_TEST_JOB_PATTERN = re.compile(r"^UT_.+") + + def __init__(self, test_case_path, ci_config_file): + CIAssignTest.AssignTest.__init__(self, test_case_path, ci_config_file, case_group=Group) + + @staticmethod + def _search_cases(test_case_path, case_filter=None): + """ + For unit test case, we don't search for test functions. + The unit test cases is stored in a yaml file which is created in job build-idf-test. + """ + + with open(test_case_path, "r") as f: + raw_data = yaml.load(f) + test_cases = raw_data["test cases"] + if case_filter: + for key in case_filter: + filtered_cases = [] + for case in test_cases: + try: + # bot converts string to lower case + if isinstance(case[key], str): + _value = case[key].lower() + else: + _value = case[key] + if _value in case_filter[key]: + filtered_cases.append(case) + except KeyError: + # case don't have this key, regard as filter success + filtered_cases.append(case) + test_cases = filtered_cases + return test_cases + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("test_case", + help="test case folder or file") + parser.add_argument("ci_config_file", + help="gitlab ci config file") + parser.add_argument("output_path", + help="output path of config files") + args = parser.parse_args() + + assign_test = UnitTestAssignTest(args.test_case, args.ci_config_file) + assign_test.assign_cases() + assign_test.output_configs(args.output_path) diff --git a/tools/tiny-test-fw/DUT.py b/tools/tiny-test-fw/DUT.py index 1c6526709..1cfd2c7d5 100644 --- a/tools/tiny-test-fw/DUT.py +++ b/tools/tiny-test-fw/DUT.py @@ -316,7 +316,7 @@ class BaseDUT(object): if flush: self.data_cache.flush() # do write if cache - if data: + if data is not None: self._port_write(data + eol if eol else data) @_expect_lock @@ -557,9 +557,9 @@ class SerialDUT(BaseDUT): :return: formatted data (str) """ timestamp = time.time() - timestamp = "{}:{}".format(time.strftime("%m-%d %H:%M:%S", time.localtime(timestamp)), - str(timestamp % 1)[2:5]) - formatted_data = "[{}]:\r\n{}\r\n".format(timestamp, _decode_data(data)) + timestamp = "[{}:{}]".format(time.strftime("%m-%d %H:%M:%S", time.localtime(timestamp)), + str(timestamp % 1)[2:5]) + formatted_data = timestamp.encode() + b"\r\n" + data + b"\r\n" return formatted_data def _port_open(self): @@ -571,11 +571,13 @@ class SerialDUT(BaseDUT): def _port_read(self, size=1): data = self.port_inst.read(size) if data: - with open(self.log_file, "a+") as _log_file: + with open(self.log_file, "ab+") as _log_file: _log_file.write(self._format_data(data)) return data def _port_write(self, data): + if isinstance(data, str): + data = data.encode() self.port_inst.write(data) @classmethod diff --git a/tools/tiny-test-fw/IDF/IDFApp.py b/tools/tiny-test-fw/IDF/IDFApp.py index 3828277ed..4bf667f64 100644 --- a/tools/tiny-test-fw/IDF/IDFApp.py +++ b/tools/tiny-test-fw/IDF/IDFApp.py @@ -144,11 +144,28 @@ class Example(IDFApp): class UT(IDFApp): def get_binary_path(self, app_path): - if app_path: - # specified path, join it and the idf path - path = os.path.join(self.idf_path, app_path) - else: - path = os.path.join(self.idf_path, "tools", "unit-test-app", "build") + """ + :param app_path: app path or app config + :return: binary path + """ + if not app_path: + app_path = "default" + + path = os.path.join(self.idf_path, app_path) + if not os.path.exists(path): + while True: + # try to get by config + if app_path == "default": + # it's default config, we first try to get form build folder of unit-test-app + path = os.path.join(self.idf_path, "tools", "unit-test-app", "build") + if os.path.exists(path): + # found, use bin in build path + break + # ``make ut-build-all-configs`` or ``make ut-build-CONFIG`` will copy binary to output folder + path = os.path.join(self.idf_path, "tools", "unit-test-app", "output", app_path) + if os.path.exists(path): + break + raise OSError("Failed to get unit-test-app binary path") return path diff --git a/tools/tiny-test-fw/Utility/CIAssignTest.py b/tools/tiny-test-fw/Utility/CIAssignTest.py new file mode 100644 index 000000000..ff1bf994b --- /dev/null +++ b/tools/tiny-test-fw/Utility/CIAssignTest.py @@ -0,0 +1,206 @@ +# Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common logic to assign test cases to CI jobs. + +Some background knowledge about Gitlab CI and use flow in esp-idf: + +* Gitlab CI jobs are static in ``.gitlab-ci.yml``. We can't dynamically create test jobs +* For test job running on DUT, we use ``tags`` to select runners with different test environment +* We have ``assign_test`` stage, will collect cases, and then assign them to correct test jobs +* ``assign_test`` will fail if failed to assign any cases +* with ``assign_test``, we can: + * dynamically filter test case we want to test + * alert user if they forget to add CI jobs and guide how to add test jobs +* the last step of ``assign_test`` is to output config files, then test jobs will run these cases + +The Basic logic to assign test cases is as follow: + +1. do search all the cases +2. do filter case (if filter is specified by @bot) +3. put cases to different groups according to rule of ``Group`` + * try to put them in existed groups + * if failed then create a new group and add this case +4. parse and filter the test jobs from CI config file +5. try to assign all groups to jobs according to tags +6. output config files for jobs + +""" + +import os +import re +import json + +import yaml + +from Utility import (CaseConfig, SearchCases, GitlabCIJob) + + +class Group(object): + + MAX_EXECUTION_TIME = 30 + MAX_CASE = 15 + SORT_KEYS = ["env_tag"] + + def __init__(self, case): + self.execution_time = 0 + self.case_list = [case] + self.filters = dict(zip(self.SORT_KEYS, [self._get_case_attr(case, x) for x in self.SORT_KEYS])) + + @staticmethod + def _get_case_attr(case, attr): + # we might use different type for case (dict or test_func) + # this method will do get attribute form cases + return case.case_info[attr] + + def accept_new_case(self): + """ + check if allowed to add any case to this group + + :return: True or False + """ + max_time = (sum([self._get_case_attr(x, "execution_time") for x in self.case_list]) + < self.MAX_EXECUTION_TIME) + max_case = (len(self.case_list) < self.MAX_CASE) + return max_time and max_case + + def add_case(self, case): + """ + add case to current group + + :param case: test case + :return: True if add succeed, else False + """ + added = False + if self.accept_new_case(): + for key in self.filters: + if self._get_case_attr(case, key) != self.filters[key]: + break + else: + self.case_list.append(case) + added = True + return added + + def output(self): + """ + output data for job configs + + :return: {"Filter": case filter, "CaseConfig": list of case configs for cases in this group} + """ + output_data = { + "Filter": self.filters, + "CaseConfig": [{"name": self._get_case_attr(x, "name")} for x in self.case_list], + } + return output_data + + +class AssignTest(object): + """ + Auto assign tests to CI jobs. + + :param test_case_path: path of test case file(s) + :param ci_config_file: path of ``.gitlab-ci.yml`` + """ + # subclass need to rewrite CI test job pattern, to filter all test jobs + CI_TEST_JOB_PATTERN = re.compile(r"^test_.+") + + def __init__(self, test_case_path, ci_config_file, case_group=Group): + self.test_case_path = test_case_path + self.test_cases = [] + self.jobs = self._parse_gitlab_ci_config(ci_config_file) + self.case_group = case_group + + def _parse_gitlab_ci_config(self, ci_config_file): + + with open(ci_config_file, "r") as f: + ci_config = yaml.load(f) + + job_list = list() + for job_name in ci_config: + if self.CI_TEST_JOB_PATTERN.search(job_name) is not None: + job_list.append(GitlabCIJob.Job(ci_config[job_name], job_name)) + return job_list + + @staticmethod + def _search_cases(test_case_path, case_filter=None): + """ + :param test_case_path: path contains test case folder + :param case_filter: filter for test cases + :return: filtered test case list + """ + test_methods = SearchCases.Search.search_test_cases(test_case_path) + return CaseConfig.filter_test_cases(test_methods, case_filter if case_filter else dict()) + + def _group_cases(self): + """ + separate all cases into groups according group rules. each group will be executed by one CI job. + + :return: test case groups. + """ + groups = [] + for case in self.test_cases: + for group in groups: + # add to current group + if group.add_case(case): + break + else: + # create new group + groups.append(self.case_group(case)) + return groups + + @staticmethod + def _apply_bot_filter(): + """ + we support customize CI test with bot. + here we process from and return the filter which ``_search_cases`` accepts. + + :return: filter for search test cases + """ + bot_filter = os.getenv("BOT_CASE_FILTER") + if bot_filter: + bot_filter = json.loads(bot_filter) + else: + bot_filter = dict() + return bot_filter + + def assign_cases(self): + """ + separate test cases to groups and assign test cases to CI jobs. + + :raise AssertError: if failed to assign any case to CI job. + :return: None + """ + failed_to_assign = [] + case_filter = self._apply_bot_filter() + self.test_cases = self._search_cases(self.test_case_path, case_filter) + test_groups = self._group_cases() + for group in test_groups: + for job in self.jobs: + if job.match_group(group): + job.assign_group(group) + break + else: + failed_to_assign.append(group) + assert not failed_to_assign + + def output_configs(self, output_path): + """ + :param output_path: path to output config files for each CI job + :return: None + """ + if not os.path.exists(output_path): + os.makedirs(output_path) + for job in self.jobs: + job.output_config(output_path) diff --git a/tools/tiny-test-fw/Utility/CaseConfig.py b/tools/tiny-test-fw/Utility/CaseConfig.py index 1fe5df42b..af013ec28 100644 --- a/tools/tiny-test-fw/Utility/CaseConfig.py +++ b/tools/tiny-test-fw/Utility/CaseConfig.py @@ -51,6 +51,20 @@ import yaml import TestCase +def _convert_to_lower_case(item): + """ + bot filter is always lower case string. + this function will convert to all string to lower case. + """ + if isinstance(item, (tuple, list)): + output = [_convert_to_lower_case(v) for v in item] + elif isinstance(item, str): + output = item.lower() + else: + output = item + return output + + def _filter_one_case(test_method, case_filter): """ Apply filter for one case (the filter logic is the same as described in ``filter_test_cases``) """ filter_result = True @@ -58,7 +72,8 @@ def _filter_one_case(test_method, case_filter): if key in test_method.case_info: # the filter key is both in case and filter # we need to check if they match - filter_item, accepted_item = case_filter[key], test_method.case_info[key] + filter_item = _convert_to_lower_case(case_filter[key]) + accepted_item = _convert_to_lower_case(test_method.case_info[key]) if isinstance(filter_item, (tuple, list)) \ and isinstance(accepted_item, (tuple, list)): @@ -91,6 +106,7 @@ def filter_test_cases(test_methods, case_filter): * if one is list/tuple, the other one is string/int, then check if string/int is in list/tuple * if both are list/tuple, then check if they have common item 2. if only case attribute or filter have the key, filter succeed + 3. will do case insensitive compare for string for example, the following are match succeed scenarios (the rule is symmetric, result is same if exchange values for user filter and case attribute): diff --git a/tools/tiny-test-fw/Utility/GitlabCIJob.py b/tools/tiny-test-fw/Utility/GitlabCIJob.py index 05f6393c6..9d1223c94 100644 --- a/tools/tiny-test-fw/Utility/GitlabCIJob.py +++ b/tools/tiny-test-fw/Utility/GitlabCIJob.py @@ -70,4 +70,4 @@ class Job(dict): file_name = os.path.join(file_path, self["name"] + ".yml") if "case group" in self: with open(file_name, "w") as f: - yaml.dump(self["case group"].output(), f) + yaml.dump(self["case group"].output(), f, default_flow_style=False) diff --git a/tools/unit-test-app/components/unity/unity_platform.c b/tools/unit-test-app/components/unity/unity_platform.c index fd979f0da..318e89f42 100644 --- a/tools/unit-test-app/components/unity/unity_platform.c +++ b/tools/unit-test-app/components/unity/unity_platform.c @@ -342,6 +342,12 @@ void unity_run_menu() print_test_menu(); } } + /*use '-' to show test history. Need to do it before UNITY_BEGIN cleanup history */ + if (cmdline[0] == '-') + { + UNITY_END(); + continue; + } UNITY_BEGIN(); diff --git a/tools/unit-test-app/tools/UnitTestParser.py b/tools/unit-test-app/tools/UnitTestParser.py index c296077d3..f3ca496dc 100644 --- a/tools/unit-test-app/tools/UnitTestParser.py +++ b/tools/unit-test-app/tools/UnitTestParser.py @@ -20,7 +20,8 @@ TEST_CASE_PATTERN = { "version": "v1 (2016-12-06)", "test environment": "UT_T1_1", "reset": "", - "expected result": "1. set succeed" + "expected result": "1. set succeed", + "cmd set": "test_unit_test_case", } CONFIG_FILE_PATTERN = { @@ -78,10 +79,10 @@ class Parser(object): name_addr = table.get_unsigned_int(section, test_addr, 4) desc_addr = table.get_unsigned_int(section, test_addr + 4, 4) file_name_addr = table.get_unsigned_int(section, test_addr + 12, 4) + function_count = table.get_unsigned_int(section, test_addr+20, 4) name = table.get_string("any", name_addr) desc = table.get_string("any", desc_addr) file_name = table.get_string("any", file_name_addr) - tc = self.parse_one_test_case(name, desc, file_name, app_name) # check if duplicated case names @@ -100,7 +101,13 @@ class Parser(object): self.test_env_tags[tc["test environment"]].append(tc["ID"]) else: self.test_env_tags.update({tc["test environment"]: [tc["ID"]]}) - # only add cases need to be executed + + if function_count > 1: + tc.update({"cmd set": "multiple_devices_case", + "child case num": function_count}) + del tc['reset'] + + # only add cases need to be executed test_cases.append(tc) os.remove("section_table.tmp") @@ -178,7 +185,6 @@ class Parser(object): test_case.update({"Test App": self.APP_NAME_PREFIX + app_name, "module": self.module_map[prop["module"]]['module'], "CI ready": "No" if prop["ignore"] == "Yes" else "Yes", - "cmd set": ["IDFUnitTest/UnitTest", [name]], "ID": tc_id, "test point 2": prop["module"], "steps": name, @@ -262,4 +268,3 @@ def main(): if __name__ == '__main__': main() - diff --git a/tools/unit-test-app/unit_test.py b/tools/unit-test-app/unit_test.py new file mode 100644 index 000000000..bb6cf74e5 --- /dev/null +++ b/tools/unit-test-app/unit_test.py @@ -0,0 +1,384 @@ +""" +Test script for unit test case. +""" + +import re +import os +import sys +import time + +import threading + +# if we want to run test case outside `tiny-test-fw` folder, +# we need to insert tiny-test-fw path into sys path +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF +import Utility +from DUT import ExpectTimeout +from IDF.IDFApp import UT + + +UT_APP_BOOT_UP_DONE = "Press ENTER to see the list of tests." +UT_TIMEOUT = 30 + +def format_test_case_config(test_case_data): + """ + convert the test case data to unified format. + We need to following info to run unit test cases: + + 1. unit test app config + 2. test case name + 3. test case reset info + + the formatted case config is a dict, with ut app config as keys. The value is a list of test cases. + Each test case is a dict with "name" and "reset" as keys. For example:: + + case_config = { + "default": [{"name": "restart from PRO CPU", "reset": "SW_CPU_RESET"}, {...}], + "psram": [{"name": "restart from PRO CPU", "reset": "SW_CPU_RESET"}], + } + + If config is not specified for test case, then + + :param test_case_data: string, list, or a dictionary list + :return: formatted data + """ + + case_config = dict() + + def parse_case(one_case_data): + """ parse and format one case """ + + def process_reset_list(reset_list): + # strip space and remove white space only items + _output = list() + for _r in reset_list: + _data = _r.strip(" ") + if _data: + _output.append(_data) + return _output + + _case = dict() + if isinstance(one_case_data, str): + _temp = one_case_data.split(" [reset=") + _case["name"] = _temp[0] + try: + _case["reset"] = process_reset_list(_temp[1][0:-1].split(",")) + except IndexError: + _case["reset"] = list() + elif isinstance(one_case_data, dict): + _case = one_case_data.copy() + assert "name" in _case + if "reset" not in _case: + _case["reset"] = list() + else: + if isinstance(_case["reset"], str): + _case["reset"] = process_reset_list(_case["reset"].split(",")) + else: + raise TypeError("Not supported type during parsing unit test case") + + if "config" not in _case: + _case["config"] = "default" + + return _case + + if not isinstance(test_case_data, list): + test_case_data = [test_case_data] + + for case_data in test_case_data: + parsed_case = parse_case(case_data) + try: + case_config[parsed_case["config"]].append(parsed_case) + except KeyError: + case_config[parsed_case["config"]] = [parsed_case] + + return case_config + + +@TinyFW.test_method(app=UT, dut=IDF.IDFDUT, chip="ESP32", module="unit_test", + execution_time=1, env_tag="UT_T1_1") +def test_unit_test_case(env, extra_data): + """ + extra_data can be three types of value + 1. as string: + 1. "case_name" + 2. "case_name [reset=RESET_REASON]" + 2. as dict: + 1. with key like {"name": "Intr_alloc test, shared ints"} + 2. with key like {"name": "restart from PRO CPU", "reset": "SW_CPU_RESET", "config": "psram"} + 3. as list of string or dict: + [case1, case2, case3, {"name": "restart from PRO CPU", "reset": "SW_CPU_RESET"}, ...] + + :param extra_data: the case name or case list or case dictionary + :return: None + """ + + case_config = format_test_case_config(extra_data) + + # compile the patterns for expect only once + reset_pattern = re.compile(r"(ets [\w]{3}\s+[\d]{1,2} [\d]{4} [\d]{2}:[\d]{2}:[\d]{2}[^()]*\([\w].*?\))") + exception_pattern = re.compile(r"(Guru Meditation Error: Core\s+\d panic'ed \([\w].*?\))") + abort_pattern = re.compile(r"(abort\(\) was called at PC 0x[a-eA-E\d]{8} on core \d)") + finish_pattern = re.compile(r"1 Tests (\d) Failures (\d) Ignored") + + # we don't want stop on failed case (unless some special scenarios we can't handle) + # this flag is used to log if any of the case failed during executing + # Before exit test function this flag is used to log if the case fails + failed_cases = [] + + for ut_config in case_config: + dut = env.get_dut("unit-test-app", app_path=ut_config) + dut.start_app() + + for one_case in case_config[ut_config]: + dut.reset() + # esptool ``run`` cmd takes quite long time. + # before reset finish, serial port is closed. therefore DUT could already bootup before serial port opened. + # this could cause checking bootup print failed. + # now we input cmd `-`, and check either bootup print or test history, + # to determine if DUT is ready to test. + dut.write("-", flush=False) + dut.expect_any(UT_APP_BOOT_UP_DONE, + "0 Tests 0 Failures 0 Ignored") + + # run test case + dut.write("\"{}\"".format(one_case["name"])) + dut.expect("Running " + one_case["name"] + "...") + + exception_reset_list = [] + + # we want to set this flag in callbacks (inner functions) + # use list here so we can use append to set this flag + test_finish = list() + + # expect callbacks + def one_case_finish(result): + """ one test finished, let expect loop break and log result """ + test_finish.append(True) + if result: + Utility.console_log("Success: " + one_case["name"], color="green") + else: + failed_cases.append(one_case["name"]) + Utility.console_log("Failed: " + one_case["name"], color="red") + + def handle_exception_reset(data): + """ + just append data to exception list. + exception list will be checked in ``handle_reset_finish``, once reset finished. + """ + exception_reset_list.append(data[0]) + + def handle_test_finish(data): + """ test finished without reset """ + # in this scenario reset should not happen + assert not exception_reset_list + if int(data[1]): + # case ignored + Utility.console_log("Ignored: " + one_case["name"], color="orange") + one_case_finish(not int(data[0])) + + def handle_reset_finish(data): + """ reset happened and reboot finished """ + assert exception_reset_list # reboot but no exception/reset logged. should never happen + result = False + if len(one_case["reset"]) == len(exception_reset_list): + for i, exception in enumerate(exception_reset_list): + if one_case["reset"][i] not in exception: + break + else: + result = True + if not result: + Utility.console_log("""Reset Check Failed: \r\n\tExpected: {}\r\n\tGet: {}""" + .format(one_case["reset"], exception_reset_list), + color="orange") + one_case_finish(result) + + while not test_finish: + try: + dut.expect_any((reset_pattern, handle_exception_reset), # reset pattern + (exception_pattern, handle_exception_reset), # exception pattern + (abort_pattern, handle_exception_reset), # abort pattern + (finish_pattern, handle_test_finish), # test finish pattern + (UT_APP_BOOT_UP_DONE, handle_reset_finish), # reboot finish pattern + timeout=UT_TIMEOUT) + except ExpectTimeout: + Utility.console_log("Timeout in expect", color="orange") + one_case_finish(False) + break + + # raise exception if any case fails + if failed_cases: + Utility.console_log("Failed Cases:", color="red") + for _case_name in failed_cases: + Utility.console_log("\t" + _case_name, color="red") + raise AssertionError("Unit Test Failed") + + +class Handler(threading.Thread): + + WAIT_SIGNAL_PATTERN = re.compile(r'Waiting for signal: \[(.+)\]!') + SEND_SIGNAL_PATTERN = re.compile(r'Send signal: \[(.+)\]!') + FINISH_PATTERN = re.compile(r"1 Tests (\d) Failures (\d) Ignored") + + def __init__(self, dut, sent_signal_list, lock, parent_case_name, child_case_index, timeout=30): + self.dut = dut + self.sent_signal_list = sent_signal_list + self.lock = lock + self.parent_case_name = parent_case_name + self.child_case_name = "" + self.child_case_index = child_case_index + 1 + self.finish = False + self.result = False + self.fail_name = None + self.timeout = timeout + threading.Thread.__init__(self, name="{} Handler".format(dut)) + + def run(self): + def get_child_case_name(data): + self.child_case_name = data[0] + time.sleep(1) + self.dut.write(str(self.child_case_index)) + + def one_device_case_finish(result): + """ one test finished, let expect loop break and log result """ + self.finish = True + self.result = result + if not result: + self.fail_name = self.child_case_name + + def device_wait_action(data): + start_time = time.time() + expected_signal = data[0] + while 1: + if time.time() > start_time + self.timeout: + Utility.console_log("Timeout in device for function: %s"%self.child_case_name, color="orange") + break + with self.lock: + if expected_signal in self.sent_signal_list: + self.dut.write(" ") + self.sent_signal_list.remove(expected_signal) + break + time.sleep(0.01) + + def device_send_action(data): + with self.lock: + self.sent_signal_list.append(data[0].encode('utf-8')) + + def handle_device_test_finish(data): + """ test finished without reset """ + # in this scenario reset should not happen + if int(data[1]): + # case ignored + Utility.console_log("Ignored: " + self.child_case_name, color="orange") + one_device_case_finish(not int(data[0])) + + self.dut.reset() + self.dut.write("-", flush=False) + self.dut.expect_any(UT_APP_BOOT_UP_DONE, "0 Tests 0 Failures 0 Ignored") + time.sleep(1) + self.dut.write("\"{}\"".format(self.parent_case_name)) + self.dut.expect("Running " + self.parent_case_name + "...") + + while not self.finish: + try: + self.dut.expect_any((re.compile('\(' + str(self.child_case_index) + '\)\s"(\w+)"'), get_child_case_name), + (self.WAIT_SIGNAL_PATTERN, device_wait_action), # wait signal pattern + (self.SEND_SIGNAL_PATTERN, device_send_action), # send signal pattern + (self.FINISH_PATTERN, handle_device_test_finish), # test finish pattern + timeout=UT_TIMEOUT) + except ExpectTimeout: + Utility.console_log("Timeout in expect", color="orange") + one_device_case_finish(False) + break + + +def get_case_info(one_case): + parent_case = one_case["name"] + child_case_num = one_case["child case num"] + return parent_case, child_case_num + + +def get_dut(duts, env, name, ut_config): + if name in duts: + dut = duts[name] + else: + dut = env.get_dut(name, app_path=ut_config) + duts[name] = dut + dut.start_app() + return dut + + +def case_run(duts, ut_config, env, one_case, failed_cases): + lock = threading.RLock() + threads = [] + send_signal_list = [] + failed_device = [] + result = True + parent_case, case_num = get_case_info(one_case) + for i in range(case_num): + dut = get_dut(duts, env, "dut%d" % i, ut_config) + threads.append(Handler(dut, send_signal_list, lock, + parent_case, i)) + for thread in threads: + thread.setDaemon(True) + thread.start() + for thread in threads: + thread.join() + result = result and thread.result + if not thread.result: + failed_device.append(thread.fail_name) + if result: + Utility.console_log("Success: " + one_case["name"], color="green") + else: + failed_cases.append(one_case["name"]) + Utility.console_log("Failed: " + one_case["name"], color="red") + + +@TinyFW.test_method(app=UT, dut=IDF.IDFDUT, chip="ESP32", module="master_slave_test_case", execution_time=1, + env_tag="UT_T2_1") +def multiple_devices_case(env, extra_data): + """ + extra_data can be two types of value + 1. as dict: + e.g. + {"name": "gpio master/slave test example", + "child case num": 2, + "config": "release", + "env_tag": "UT_T2_1"} + 2. as list dict: + e.g. + [{"name": "gpio master/slave test example1", + "child case num": 2, + "config": "release", + "env_tag": "UT_T2_1"}, + {"name": "gpio master/slave test example2", + "child case num": 2, + "config": "release", + "env_tag": "UT_T2_1"}] + + """ + failed_cases = [] + case_config = format_test_case_config(extra_data) + DUTS = {} + for ut_config in case_config: + for one_case in case_config[ut_config]: + case_run(DUTS, ut_config, env, one_case, failed_cases) + + if failed_cases: + Utility.console_log("Failed Cases:", color="red") + for _case_name in failed_cases: + Utility.console_log("\t" + _case_name, color="red") + raise AssertionError("Unit Test Failed") + +if __name__ == '__main__': + multiple_devices_case(extra_data={"name": "gpio master/slave test example", + "child case num": 2, + "config": "release", + "env_tag": "UT_T2_1"}) + + +