From 2c165aba366d4b22e7be02c723187795b91cf3e3 Mon Sep 17 00:00:00 2001 From: Deomid Ryabkov Date: Fri, 16 Dec 2016 22:27:45 +0000 Subject: [PATCH 001/167] uart_intr_config should return ESP_OK on success --- components/driver/uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/driver/uart.c b/components/driver/uart.c index 556e97baac..a2479f5db0 100644 --- a/components/driver/uart.c +++ b/components/driver/uart.c @@ -447,7 +447,7 @@ esp_err_t uart_intr_config(uart_port_t uart_num, const uart_intr_config_t *intr_ } UART[uart_num]->int_ena.val = intr_conf->intr_enable_mask; UART_EXIT_CRITICAL(&uart_spinlock[uart_num]); - return ESP_FAIL; + return ESP_OK; } //internal isr handler for default driver code. From c01dedcb06afb3dee3199435ddba2f83f9e0186f Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Sun, 11 Dec 2016 16:36:47 +0800 Subject: [PATCH 002/167] component/bt : change api for V2.0 1. change all gatt cb function to 3 args and function type. add gattc_if/gatts_if as second argument 2. delete gatt_if of "gatt cb param" 3. separate conn_id and gatt_if from conn_id 4. change the demo code as the gatt changed --- components/bt/bluedroid/api/esp_blufi_api.c | 2 +- components/bt/bluedroid/api/esp_gap_ble_api.c | 2 +- components/bt/bluedroid/api/esp_gattc_api.c | 117 +++++++------ components/bt/bluedroid/api/esp_gatts_api.c | 27 +-- .../bt/bluedroid/api/include/esp_blufi_api.h | 18 +- .../bt/bluedroid/api/include/esp_bt_defs.h | 7 - .../bluedroid/api/include/esp_gap_ble_api.h | 11 +- .../bt/bluedroid/api/include/esp_gatt_defs.h | 4 +- .../bt/bluedroid/api/include/esp_gattc_api.h | 129 ++++++++------ .../bt/bluedroid/api/include/esp_gatts_api.h | 45 +++-- components/bt/bluedroid/btc/core/btc_manage.c | 6 +- .../bt/bluedroid/btc/include/btc_manage.h | 4 +- .../btc/profile/esp/blufi/blufi_prf.c | 6 +- .../btc/profile/std/gap/btc_gap_ble.c | 2 +- .../btc/profile/std/gatt/btc_gatt_util.c | 5 +- .../btc/profile/std/gatt/btc_gattc.c | 139 +++++++++------ .../btc/profile/std/gatt/btc_gatts.c | 101 ++++++----- .../std/{gatt => }/include/btc_gatt_util.h | 6 +- .../btc/profile/std/include/btc_gattc.h | 8 +- .../btc/profile/std/include/btc_gatts.h | 6 +- examples/12_blufi/components/blufi/blufi.c | 9 +- examples/14_gatt_server/main/gatts_demo.c | 140 ++++++++++----- examples/15_gatt_client/main/gattc_demo.c | 161 ++++++++++++------ 23 files changed, 573 insertions(+), 382 deletions(-) rename components/bt/bluedroid/btc/profile/std/{gatt => }/include/btc_gatt_util.h (80%) diff --git a/components/bt/bluedroid/api/esp_blufi_api.c b/components/bt/bluedroid/api/esp_blufi_api.c index a74c9d04b5..2697f2cbf9 100644 --- a/components/bt/bluedroid/api/esp_blufi_api.c +++ b/components/bt/bluedroid/api/esp_blufi_api.c @@ -22,7 +22,7 @@ #include "btc_main.h" #include "future.h" -esp_err_t esp_blufi_register_callback(esp_profile_cb_t callback) +esp_err_t esp_blufi_register_callback(esp_blufi_cb_t callback) { return (btc_profile_cb_set(BTC_PID_BLUFI, callback) == 0 ? ESP_OK : ESP_FAIL); } diff --git a/components/bt/bluedroid/api/esp_gap_ble_api.c b/components/bt/bluedroid/api/esp_gap_ble_api.c index 0770fcfed5..6e18f65822 100644 --- a/components/bt/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/bluedroid/api/esp_gap_ble_api.c @@ -21,7 +21,7 @@ #include "btc_gap_ble.h" -esp_err_t esp_ble_gap_register_callback(esp_profile_cb_t callback) +esp_err_t esp_ble_gap_register_callback(esp_gap_ble_cb_t callback) { return (btc_profile_cb_set(BTC_PID_GAP_BLE, callback) == 0 ? ESP_OK : ESP_FAIL); } diff --git a/components/bt/bluedroid/api/esp_gattc_api.c b/components/bt/bluedroid/api/esp_gattc_api.c index c856947f01..28fc429575 100644 --- a/components/bt/bluedroid/api/esp_gattc_api.c +++ b/components/bt/bluedroid/api/esp_gattc_api.c @@ -17,8 +17,9 @@ #include "esp_gattc_api.h" #include "btc_manage.h" #include "btc_gattc.h" +#include "btc_gatt_util.h" -esp_err_t esp_ble_gattc_register_callback(esp_profile_cb_t callback) +esp_err_t esp_ble_gattc_register_callback(esp_gattc_cb_t callback) { if (callback == NULL) { return ESP_FAIL; @@ -33,7 +34,6 @@ esp_err_t esp_ble_gattc_app_register(uint16_t app_id) btc_msg_t msg; btc_ble_gattc_args_t arg; - //if (app_id < ESP_APP_ID_MIN || app_id > ESP_APP_ID_MAX) { if (app_id > ESP_APP_ID_MAX) { return ESP_ERR_INVALID_ARG; } @@ -46,7 +46,7 @@ esp_err_t esp_ble_gattc_app_register(uint16_t app_id) return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gatt_if) +esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gattc_if) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -54,12 +54,12 @@ esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gatt_if) msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_APP_UNREGISTER; - arg.app_unreg.gatt_if = gatt_if; + arg.app_unreg.gattc_if = gattc_if; return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_open(esp_gatt_if_t gatt_if, esp_bd_addr_t remote_bda, bool is_direct) +esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, bool is_direct) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -67,14 +67,14 @@ esp_err_t esp_ble_gattc_open(esp_gatt_if_t gatt_if, esp_bd_addr_t remote_bda, bo msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_OPEN; - arg.open.gatt_if = gatt_if; + arg.open.gattc_if = gattc_if; memcpy(arg.open.remote_bda, remote_bda, ESP_BD_ADDR_LEN); arg.open.is_direct = is_direct; return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_close (uint16_t conn_id) +esp_err_t esp_ble_gattc_close (esp_gatt_if_t gattc_if, uint16_t conn_id) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -82,12 +82,12 @@ esp_err_t esp_ble_gattc_close (uint16_t conn_id) msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_CLOSE; - arg.close.conn_id = conn_id; + arg.close.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_config_mtu (uint16_t conn_id, uint16_t mtu) +esp_err_t esp_ble_gattc_config_mtu (esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t mtu) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -99,13 +99,13 @@ esp_err_t esp_ble_gattc_config_mtu (uint16_t conn_id, uint16_t mtu) msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_CFG_MTU; - arg.cfg_mtu.conn_id = conn_id; + arg.cfg_mtu.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); arg.cfg_mtu.mtu = mtu; return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_search_service(uint16_t conn_id, esp_bt_uuid_t *filter_uuid) +esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, esp_bt_uuid_t *filter_uuid) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -113,7 +113,8 @@ esp_err_t esp_ble_gattc_search_service(uint16_t conn_id, esp_bt_uuid_t *filter_u msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_SEARCH_SERVICE; - arg.search_srvc.conn_id = conn_id; + arg.search_srvc.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); + if (filter_uuid) { arg.search_srvc.filter_uuid_enable = true; memcpy(&arg.search_srvc.filter_uuid, filter_uuid, sizeof(esp_bt_uuid_t)); @@ -124,9 +125,10 @@ esp_err_t esp_ble_gattc_search_service(uint16_t conn_id, esp_bt_uuid_t *filter_u return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_get_characteristic(uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *start_char_id) +esp_err_t esp_ble_gattc_get_characteristic(esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *start_char_id) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -134,12 +136,12 @@ esp_err_t esp_ble_gattc_get_characteristic(uint16_t conn_id, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; if (start_char_id) { - arg.get_next_char.conn_id = conn_id; + arg.get_next_char.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.get_next_char.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.get_next_char.char_id, start_char_id, sizeof(esp_gatt_id_t)); msg.act = BTC_GATTC_ACT_GET_NEXT_CHAR; } else { - arg.get_first_char.conn_id = conn_id; + arg.get_first_char.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.get_first_char.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); msg.act = BTC_GATTC_ACT_GET_FIRST_CHAR; } @@ -147,7 +149,8 @@ esp_err_t esp_ble_gattc_get_characteristic(uint16_t conn_id, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_get_descriptor(uint16_t conn_id, +esp_err_t esp_ble_gattc_get_descriptor(esp_gatt_if_t gattc_if, + uint16_t conn_id, esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *char_id, esp_gatt_id_t *start_descr_id) @@ -159,13 +162,13 @@ esp_err_t esp_ble_gattc_get_descriptor(uint16_t conn_id, msg.pid = BTC_PID_GATTC; if (start_descr_id) { - arg.get_next_descr.conn_id = conn_id; + arg.get_next_descr.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.get_next_descr.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.get_next_descr.char_id, char_id, sizeof(esp_gatt_id_t)); memcpy(&arg.get_next_descr.descr_id, start_descr_id, sizeof(esp_gatt_id_t)); msg.act = BTC_GATTC_ACT_GET_NEXT_DESCR; } else { - arg.get_first_descr.conn_id = conn_id; + arg.get_first_descr.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.get_first_descr.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.get_first_descr.char_id, char_id, sizeof(esp_gatt_id_t)); msg.act = BTC_GATTC_ACT_GET_FIRST_DESCR; @@ -174,9 +177,10 @@ esp_err_t esp_ble_gattc_get_descriptor(uint16_t conn_id, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_get_included_service(uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_srvc_id_t *start_incl_srvc_id) +esp_err_t esp_ble_gattc_get_included_service(esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_srvc_id_t *start_incl_srvc_id) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -185,12 +189,12 @@ esp_err_t esp_ble_gattc_get_included_service(uint16_t conn_id, msg.pid = BTC_PID_GATTC; if (start_incl_srvc_id) { - arg.get_next_incl_srvc.conn_id = conn_id; + arg.get_next_incl_srvc.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.get_next_incl_srvc.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.get_next_incl_srvc.start_service_id, start_incl_srvc_id, sizeof(esp_gatt_srvc_id_t)); msg.act = BTC_GATTC_ACT_GET_NEXT_INCL_SERVICE; } else { - arg.get_first_incl_srvc.conn_id = conn_id; + arg.get_first_incl_srvc.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.get_first_incl_srvc.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); msg.act = BTC_GATTC_ACT_GET_FIRST_INCL_SERVICE; } @@ -198,8 +202,11 @@ esp_err_t esp_ble_gattc_get_included_service(uint16_t conn_id, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_read_char (uint16_t conn_id, esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *char_id, esp_gatt_auth_req_t auth_req) +esp_err_t esp_ble_gattc_read_char (esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id, + esp_gatt_auth_req_t auth_req) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -207,7 +214,7 @@ esp_err_t esp_ble_gattc_read_char (uint16_t conn_id, esp_gatt_srvc_id_t *srvc_id msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_READ_CHAR; - arg.read_char.conn_id = conn_id; + arg.read_char.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.read_char.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.read_char.char_id, char_id, sizeof(esp_gatt_id_t)); arg.read_char.auth_req = auth_req; @@ -215,11 +222,12 @@ esp_err_t esp_ble_gattc_read_char (uint16_t conn_id, esp_gatt_srvc_id_t *srvc_id return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_read_char_descr (uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *char_id, - esp_gatt_id_t *descr_id, - esp_gatt_auth_req_t auth_req) +esp_err_t esp_ble_gattc_read_char_descr (esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id, + esp_gatt_id_t *descr_id, + esp_gatt_auth_req_t auth_req) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -227,7 +235,7 @@ esp_err_t esp_ble_gattc_read_char_descr (uint16_t conn_id, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_READ_CHAR_DESCR; - arg.read_descr.conn_id = conn_id; + arg.read_descr.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.read_descr.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.read_descr.char_id, char_id, sizeof(esp_gatt_id_t)); memcpy(&arg.read_descr.descr_id, descr_id, sizeof(esp_gatt_id_t)); @@ -236,7 +244,8 @@ esp_err_t esp_ble_gattc_read_char_descr (uint16_t conn_id, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_write_char( uint16_t conn_id, +esp_err_t esp_ble_gattc_write_char( esp_gatt_if_t gattc_if, + uint16_t conn_id, esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *char_id, uint16_t value_len, @@ -250,7 +259,7 @@ esp_err_t esp_ble_gattc_write_char( uint16_t conn_id, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_WRITE_CHAR; - arg.write_char.conn_id = (uint16_t) conn_id; + arg.write_char.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.write_char.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.write_char.char_id, char_id, sizeof(esp_gatt_id_t)); arg.write_char.value_len = value_len > ESP_GATT_MAX_ATTR_LEN ? ESP_GATT_MAX_ATTR_LEN : value_len; @@ -261,14 +270,15 @@ esp_err_t esp_ble_gattc_write_char( uint16_t conn_id, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), btc_gattc_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_write_char_descr (uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *char_id, - esp_gatt_id_t *descr_id, - uint16_t value_len, - uint8_t *value, - esp_gatt_write_type_t write_type, - esp_gatt_auth_req_t auth_req) +esp_err_t esp_ble_gattc_write_char_descr (esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id, + esp_gatt_id_t *descr_id, + uint16_t value_len, + uint8_t *value, + esp_gatt_write_type_t write_type, + esp_gatt_auth_req_t auth_req) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -276,7 +286,7 @@ esp_err_t esp_ble_gattc_write_char_descr (uint16_t conn_id, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_WRITE_CHAR_DESCR; - arg.write_descr.conn_id = (uint16_t) conn_id; + arg.write_descr.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.write_descr.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.write_descr.char_id, char_id, sizeof(esp_gatt_id_t)); memcpy(&arg.write_descr.descr_id, descr_id, sizeof(esp_gatt_id_t)); @@ -288,7 +298,8 @@ esp_err_t esp_ble_gattc_write_char_descr (uint16_t conn_id, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), btc_gattc_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_prepare_write(uint16_t conn_id, +esp_err_t esp_ble_gattc_prepare_write(esp_gatt_if_t gattc_if, + uint16_t conn_id, esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *char_id, uint16_t offset, @@ -303,7 +314,7 @@ esp_err_t esp_ble_gattc_prepare_write(uint16_t conn_id, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_PREPARE_WRITE; - arg.prep_write.conn_id = conn_id; + arg.prep_write.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); memcpy(&arg.prep_write.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.prep_write.char_id, char_id, sizeof(esp_gatt_id_t)); arg.prep_write.offset = offset; @@ -314,7 +325,7 @@ esp_err_t esp_ble_gattc_prepare_write(uint16_t conn_id, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), btc_gattc_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gattc_execute_write (uint16_t conn_id, bool is_execute) +esp_err_t esp_ble_gattc_execute_write (esp_gatt_if_t gattc_if, uint16_t conn_id, bool is_execute) { btc_msg_t msg; btc_ble_gattc_args_t arg; @@ -322,13 +333,13 @@ esp_err_t esp_ble_gattc_execute_write (uint16_t conn_id, bool is_execute) msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_EXECUTE_WRITE; - arg.exec_write.conn_id = conn_id; + arg.exec_write.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); arg.exec_write.is_execute = is_execute; return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gatt_if, +esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gattc_if, esp_bd_addr_t server_bda, esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *char_id) @@ -339,7 +350,7 @@ esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gatt_if, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_REG_FOR_NOTIFY; - arg.reg_for_notify.gatt_if = gatt_if; + arg.reg_for_notify.gattc_if = gattc_if; memcpy(arg.reg_for_notify.remote_bda, server_bda, sizeof(esp_bd_addr_t)); memcpy(&arg.reg_for_notify.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.reg_for_notify.char_id, char_id, sizeof(esp_gatt_id_t)); @@ -347,7 +358,7 @@ esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gatt_if, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_gatt_status_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gatt_if, +esp_gatt_status_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gattc_if, esp_bd_addr_t server_bda, esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *char_id) @@ -358,7 +369,7 @@ esp_gatt_status_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gatt_if, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_UNREG_FOR_NOTIFY; - arg.unreg_for_notify.gatt_if = gatt_if; + arg.unreg_for_notify.gattc_if = gattc_if; memcpy(arg.unreg_for_notify.remote_bda, server_bda, sizeof(esp_bd_addr_t)); memcpy(&arg.unreg_for_notify.service_id, srvc_id, sizeof(esp_gatt_srvc_id_t)); memcpy(&arg.unreg_for_notify.char_id, char_id, sizeof(esp_gatt_id_t)); diff --git a/components/bt/bluedroid/api/esp_gatts_api.c b/components/bt/bluedroid/api/esp_gatts_api.c index 803ff030dd..2504e58f8f 100644 --- a/components/bt/bluedroid/api/esp_gatts_api.c +++ b/components/bt/bluedroid/api/esp_gatts_api.c @@ -17,10 +17,11 @@ #include "esp_gatts_api.h" #include "btc_manage.h" #include "btc_gatts.h" +#include "btc_gatt_util.h" #define COPY_TO_GATTS_ARGS(_gatt_args, _arg, _arg_type) memcpy(_gatt_args, _arg, sizeof(_arg_type)) -esp_err_t esp_ble_gatts_register_callback(esp_profile_cb_t callback) +esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback) { return (btc_profile_cb_set(BTC_PID_GATTS, callback) == 0 ? ESP_OK : ESP_FAIL); } @@ -44,7 +45,7 @@ esp_err_t esp_ble_gatts_app_register(uint16_t app_id) } -esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatt_if) +esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatts_if) { btc_msg_t msg; btc_ble_gatts_args_t arg; @@ -52,12 +53,12 @@ esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatt_if) msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_APP_UNREGISTER; - arg.app_unreg.gatt_if = gatt_if; + arg.app_unreg.gatts_if = gatts_if; return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatt_if, +esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, esp_gatt_srvc_id_t *service_id, uint16_t num_handle) { btc_msg_t msg; @@ -66,7 +67,7 @@ esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatt_if, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_CREATE_SERVICE; - arg.create_srvc.gatt_if = gatt_if; + arg.create_srvc.gatts_if = gatts_if; arg.create_srvc.num_handle = num_handle; memcpy(&arg.create_srvc.service_id, service_id, sizeof(esp_gatt_srvc_id_t)); @@ -164,7 +165,7 @@ esp_err_t esp_ble_gatts_stop_service(uint16_t service_handle) } -esp_err_t esp_ble_gatts_send_indicate(uint16_t conn_id, uint16_t attr_handle, +esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, uint16_t attr_handle, uint16_t value_len, uint8_t *value, bool need_confirm) { btc_msg_t msg; @@ -173,7 +174,7 @@ esp_err_t esp_ble_gatts_send_indicate(uint16_t conn_id, uint16_t attr_handle, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_SEND_INDICATE; - arg.send_ind.conn_id = conn_id; + arg.send_ind.conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn_id); arg.send_ind.attr_handle = attr_handle; arg.send_ind.need_confirm = need_confirm; arg.send_ind.value_len = value_len; @@ -182,7 +183,7 @@ esp_err_t esp_ble_gatts_send_indicate(uint16_t conn_id, uint16_t attr_handle, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gatts_send_response(uint16_t conn_id, uint32_t trans_id, +esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, uint32_t trans_id, esp_gatt_status_t status, esp_gatt_rsp_t *rsp) { btc_msg_t msg; @@ -191,7 +192,7 @@ esp_err_t esp_ble_gatts_send_response(uint16_t conn_id, uint32_t trans_id, msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_SEND_RESPONSE; - arg.send_rsp.conn_id = conn_id; + arg.send_rsp.conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn_id); arg.send_rsp.trans_id = trans_id; arg.send_rsp.status = status; arg.send_rsp.rsp = rsp; @@ -199,7 +200,7 @@ esp_err_t esp_ble_gatts_send_response(uint16_t conn_id, uint32_t trans_id, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatt_if, esp_bd_addr_t remote_bda, bool is_direct) +esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatts_if, esp_bd_addr_t remote_bda, bool is_direct) { btc_msg_t msg; btc_ble_gatts_args_t arg; @@ -207,14 +208,14 @@ esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatt_if, esp_bd_addr_t remote_bda, bo msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_OPEN; - arg.open.gatt_if = gatt_if; + arg.open.gatts_if = gatts_if; arg.open.is_direct = is_direct; memcpy(&arg.open.remote_bda, remote_bda, sizeof(esp_bd_addr_t)); return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } -esp_err_t esp_ble_gatts_close(uint16_t conn_id) +esp_err_t esp_ble_gatts_close(esp_gatt_if_t gatts_if, uint16_t conn_id) { btc_msg_t msg; btc_ble_gatts_args_t arg; @@ -222,7 +223,7 @@ esp_err_t esp_ble_gatts_close(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); } diff --git a/components/bt/bluedroid/api/include/esp_blufi_api.h b/components/bt/bluedroid/api/include/esp_blufi_api.h index 609d9c1c59..2644bd5244 100644 --- a/components/bt/bluedroid/api/include/esp_blufi_api.h +++ b/components/bt/bluedroid/api/include/esp_blufi_api.h @@ -25,9 +25,11 @@ extern "C" { #define ESP_BLUFI_RECV_DATA_LEN_MAX (64+1) -#define ESP_BLUFI_EVENT_INIT_FINISH 0 -#define ESP_BLUFI_EVENT_DEINIT_FINISH 1 -#define ESP_BLUFI_EVENT_RECV_DATA 2 +typedef enum { + ESP_BLUFI_EVENT_INIT_FINISH = 0, + ESP_BLUFI_EVENT_DEINIT_FINISH = 1, + ESP_BLUFI_EVENT_RECV_DATA = 2, +} esp_blufi_cb_event_t; /// BLUFI config status typedef enum { @@ -74,6 +76,14 @@ typedef union { } recv_data; /*!< Blufi callback param of ESP_BLUFI_EVENT_RECV_DATA */ } esp_blufi_cb_param_t; +/** + * @brief BLUFI callback function type + * @param event : Event type + * @param param : Point to callback parameter, currently is union type + */ +typedef void (* esp_blufi_cb_t)(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param); + + /** * * @brief This function is called to receive blufi callback event @@ -83,7 +93,7 @@ typedef union { * @return ESP_OK - success, other - failed * */ -esp_err_t esp_blufi_register_callback(esp_profile_cb_t callback); +esp_err_t esp_blufi_register_callback(esp_blufi_cb_t callback); /** * diff --git a/components/bt/bluedroid/api/include/esp_bt_defs.h b/components/bt/bluedroid/api/include/esp_bt_defs.h index 65de8df5ee..cba8fbe74f 100644 --- a/components/bt/bluedroid/api/include/esp_bt_defs.h +++ b/components/bt/bluedroid/api/include/esp_bt_defs.h @@ -96,13 +96,6 @@ typedef enum { /// Maximum of the application id #define ESP_APP_ID_MAX 0x7fff -/** - * @brief Each profile callback function type - * @param event : Event type - * @param param : Point to callback parameter, currently is union type - */ -typedef void (* esp_profile_cb_t)(uint32_t event, void *param); - #ifdef __cplusplus } #endif diff --git a/components/bt/bluedroid/api/include/esp_gap_ble_api.h b/components/bt/bluedroid/api/include/esp_gap_ble_api.h index d01595d595..f500f84031 100644 --- a/components/bt/bluedroid/api/include/esp_gap_ble_api.h +++ b/components/bt/bluedroid/api/include/esp_gap_ble_api.h @@ -31,7 +31,7 @@ typedef enum { ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT , /*!< When scan response data set complete, the event comes */ ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, /*!< When scan parameters set complete, the event comes */ ESP_GAP_BLE_SCAN_RESULT_EVT, /*!< When one scan result ready, the event comes each time */ -}esp_gap_ble_cb_event_t; +} esp_gap_ble_cb_event_t; /// Advertising data maximum length #define ESP_BLE_ADV_DATA_LEN_MAX 31 @@ -257,6 +257,13 @@ typedef union { } scan_rst; /*!< Event parameter of ESP_GAP_BLE_SCAN_RESULT_EVT */ } esp_ble_gap_cb_param_t; +/** + * @brief GAP callback function type + * @param event : Event type + * @param param : Point to callback parameter, currently is union type + */ +typedef void (* esp_gap_ble_cb_t)(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); + /** * @brief This function is called to occur gap event, such as scan result * @@ -267,7 +274,7 @@ typedef union { * - other : failed * */ -esp_err_t esp_ble_gap_register_callback(esp_profile_cb_t callback); +esp_err_t esp_ble_gap_register_callback(esp_gap_ble_cb_t callback); /** diff --git a/components/bt/bluedroid/api/include/esp_gatt_defs.h b/components/bt/bluedroid/api/include/esp_gatt_defs.h index 931733e402..7b119a62cd 100644 --- a/components/bt/bluedroid/api/include/esp_gatt_defs.h +++ b/components/bt/bluedroid/api/include/esp_gatt_defs.h @@ -269,7 +269,9 @@ typedef enum { ESP_GATT_WRITE_TYPE_RSP, /*!< Gatt write attribute need remote response */ } esp_gatt_write_type_t; -typedef uint32_t esp_gatt_if_t; /*!< Gatt interface type, different application on GATT client use different gatt_if */ +#define ESP_GATT_IF_NONE 0xff /*!< If callback report gattc_if/gatts_if as this macro, means this event is not correspond to any app */ + +typedef uint8_t esp_gatt_if_t; /*!< Gatt interface type, different application on GATT client use different gatt_if */ #ifdef __cplusplus } diff --git a/components/bt/bluedroid/api/include/esp_gattc_api.h b/components/bt/bluedroid/api/include/esp_gattc_api.h index 4407975a0d..b52dabbdac 100644 --- a/components/bt/bluedroid/api/include/esp_gattc_api.h +++ b/components/bt/bluedroid/api/include/esp_gattc_api.h @@ -81,7 +81,6 @@ typedef union { */ struct gattc_reg_evt_param { esp_gatt_status_t status; /*!< Operation status */ - esp_gatt_if_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t app_id; /*!< Application id which input in register API */ } reg; /*!< Gatt client callback param of ESP_GATTC_REG_EVT */ @@ -91,7 +90,6 @@ typedef union { struct gattc_open_evt_param { esp_gatt_status_t status; /*!< Operation status */ uint16_t conn_id; /*!< Connection id */ - esp_gatt_if_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */ uint16_t mtu; /*!< MTU size */ } open; /*!< Gatt client callback param of ESP_GATTC_OPEN_EVT */ @@ -102,7 +100,6 @@ typedef union { struct gattc_close_evt_param { esp_gatt_status_t status; /*!< Operation status */ uint16_t conn_id; /*!< Connection id */ - esp_gatt_if_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */ esp_gatt_conn_reason_t reason; /*!< The reason of gatt connection close */ } close; /*!< Gatt client callback param of ESP_GATTC_CLOSE_EVT */ @@ -247,6 +244,14 @@ typedef union { } esp_ble_gattc_cb_param_t; /*!< GATT client callback parameter union type */ +/** + * @brief GATT Client callback function type + * @param event : Event type + * @param gatts_if : GATT client access interface, normally + * different gattc_if correspond to different profile + * @param param : Point to callback parameter, currently is union type + */ +typedef void (* esp_gattc_cb_t)(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); /** * @brief This function is called to register application callbacks @@ -259,7 +264,7 @@ typedef union { * - other: failed * */ -esp_err_t esp_ble_gattc_register_callback(esp_profile_cb_t callback); +esp_err_t esp_ble_gattc_register_callback(esp_gattc_cb_t callback); /** @@ -280,20 +285,20 @@ esp_err_t esp_ble_gattc_app_register(uint16_t app_id); * @brief This function is called to unregister an application * from GATTC module. * - * @param[in] gatt_if : app identifier. + * @param[in] gattc_if: Gatt client access interface. * * @return * - ESP_OK: success * - other: failed * */ -esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gatt_if); +esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gattc_if); /** * @brief Open a direct connection or add a background auto connection * - * @param[in] gatt_if: application identity. + * @param[in] gattc_if: Gatt client access interface. * @param[in] remote_bda: remote device bluetooth device address. * @param[in] is_direct: direct connection or background auto connection * @@ -302,12 +307,13 @@ esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gatt_if); * - other: failed * */ -esp_err_t esp_ble_gattc_open(esp_gatt_if_t gatt_if, esp_bd_addr_t remote_bda, bool is_direct); +esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, bool is_direct); /** * @brief Close a connection to a GATT server. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id: connection ID to be closed. * * @return @@ -315,13 +321,14 @@ esp_err_t esp_ble_gattc_open(esp_gatt_if_t gatt_if, esp_bd_addr_t remote_bda, bo * - other: failed * */ -esp_err_t esp_ble_gattc_close(uint16_t conn_id); +esp_err_t esp_ble_gattc_close (esp_gatt_if_t gattc_if, uint16_t conn_id); /** * @brief Configure the MTU size in the GATT channel. This can be done * only once per connection. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id: connection ID. * @param[in] mtu: desired MTU size to use. * @@ -330,7 +337,7 @@ esp_err_t esp_ble_gattc_close(uint16_t conn_id); * - other: failed * */ -esp_err_t esp_ble_gattc_config_mtu(uint16_t conn_id, uint16_t mtu); +esp_err_t esp_ble_gattc_config_mtu (esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t mtu); /** @@ -339,6 +346,7 @@ esp_err_t esp_ble_gattc_config_mtu(uint16_t conn_id, uint16_t mtu); * by a callback event, and followed by a service search complete * event. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id: connection ID. * @param[in] filter_uuid: a UUID of the service application is interested in. * If Null, discover for all services. @@ -348,32 +356,32 @@ esp_err_t esp_ble_gattc_config_mtu(uint16_t conn_id, uint16_t mtu); * - other: failed * */ -esp_err_t esp_ble_gattc_search_service(uint16_t conn_id, esp_bt_uuid_t *filter_uuid); +esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, esp_bt_uuid_t *filter_uuid); /** * @brief This function is called to find the first characteristic of the * service on the given server. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id: connection ID which identify the server. - * * @param[in] srvc_id: service ID - * * @param[in] start_char_id: the start characteristic ID - * * @return * - ESP_OK: success * - other: failed * */ -esp_err_t esp_ble_gattc_get_characteristic(uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *start_char_id); - +esp_err_t esp_ble_gattc_get_characteristic(esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *start_char_id); /** * @brief This function is called to find the descriptor of the * service on the given server. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id: connection ID which identify the server. * @param[in] srvc_id: the service ID of which the characteristic is belonged to. * @param[in] char_id: Characteristic ID, if NULL find the first available @@ -385,8 +393,10 @@ esp_err_t esp_ble_gattc_get_characteristic(uint16_t conn_id, * - other: failed * */ -esp_err_t esp_ble_gattc_get_descriptor(uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *char_id, +esp_err_t esp_ble_gattc_get_descriptor(esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id, esp_gatt_id_t *start_descr_id); @@ -394,6 +404,7 @@ esp_err_t esp_ble_gattc_get_descriptor(uint16_t conn_id, * @brief This function is called to find the first characteristic of the * service on the given server. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id: connection ID which identify the server. * @param[in] srvc_id: the service ID of which the characteristic is belonged to. * @param[in] start_incl_srvc_id: the start include service id @@ -403,14 +414,17 @@ esp_err_t esp_ble_gattc_get_descriptor(uint16_t conn_id, * - other: failed * */ -esp_err_t esp_ble_gattc_get_included_service(uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, esp_gatt_srvc_id_t *start_incl_srvc_id); +esp_err_t esp_ble_gattc_get_included_service(esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_srvc_id_t *start_incl_srvc_id); /** * @brief This function is called to read a service's characteristics of - * the given characteriistic ID + * the given characteristic ID * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id : connection ID. * @param[in] srvc_id : service ID. * @param[in] char_id : characteristic ID to read. @@ -421,15 +435,17 @@ esp_err_t esp_ble_gattc_get_included_service(uint16_t conn_id, * - other: failed * */ -esp_err_t esp_ble_gattc_read_char (uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *char_id, - esp_gatt_auth_req_t auth_req); +esp_err_t esp_ble_gattc_read_char (esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id, + esp_gatt_auth_req_t auth_req); /** * @brief This function is called to read a characteristics descriptor. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id : connection ID. * @param[in] srvc_id : service ID. * @param[in] char_id : characteristic ID to read. @@ -441,16 +457,18 @@ esp_err_t esp_ble_gattc_read_char (uint16_t conn_id, * - other: failed * */ -esp_err_t esp_ble_gattc_read_char_descr (uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *char_id, - esp_gatt_id_t *descr_id, - esp_gatt_auth_req_t auth_req); +esp_err_t esp_ble_gattc_read_char_descr (esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id, + esp_gatt_id_t *descr_id, + esp_gatt_auth_req_t auth_req); /** * @brief This function is called to write characteristic value. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id : connection ID. * @param[in] srvc_id : service ID. * @param[in] char_id : characteristic ID to write. @@ -464,7 +482,8 @@ esp_err_t esp_ble_gattc_read_char_descr (uint16_t conn_id, * - other: failed * */ -esp_err_t esp_ble_gattc_write_char( uint16_t conn_id, +esp_err_t esp_ble_gattc_write_char( esp_gatt_if_t gattc_if, + uint16_t conn_id, esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *char_id, uint16_t value_len, @@ -476,6 +495,7 @@ esp_err_t esp_ble_gattc_write_char( uint16_t conn_id, /** * @brief This function is called to write characteristic descriptor value. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id : connection ID * @param[in] srvc_id : service ID. * @param[in] char_id : characteristic ID. @@ -490,19 +510,21 @@ esp_err_t esp_ble_gattc_write_char( uint16_t conn_id, * - other: failed * */ -esp_err_t esp_ble_gattc_write_char_descr (uint16_t conn_id, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *char_id, - esp_gatt_id_t *descr_id, - uint16_t value_len, - uint8_t *value, - esp_gatt_write_type_t write_type, - esp_gatt_auth_req_t auth_req); +esp_err_t esp_ble_gattc_write_char_descr (esp_gatt_if_t gattc_if, + uint16_t conn_id, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id, + esp_gatt_id_t *descr_id, + uint16_t value_len, + uint8_t *value, + esp_gatt_write_type_t write_type, + esp_gatt_auth_req_t auth_req); /** * @brief This function is called to prepare write a characteristic value. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id : connection ID. * @param[in] srvc_id : service ID. * @param[in] char_id : GATT characteristic ID of the service. @@ -516,7 +538,8 @@ esp_err_t esp_ble_gattc_write_char_descr (uint16_t conn_id, * - other: failed * */ -esp_err_t esp_ble_gattc_prepare_write(uint16_t conn_id, +esp_err_t esp_ble_gattc_prepare_write(esp_gatt_if_t gattc_if, + uint16_t conn_id, esp_gatt_srvc_id_t *srvc_id, esp_gatt_id_t *char_id, uint16_t offset, @@ -524,9 +547,11 @@ esp_err_t esp_ble_gattc_prepare_write(uint16_t conn_id, uint8_t *value, esp_gatt_auth_req_t auth_req); + /** * @brief This function is called to execute write a prepare write sequence. * + * @param[in] gattc_if: Gatt client access interface. * @param[in] conn_id : connection ID. * @param[in] is_execute : execute or cancel. * @@ -535,13 +560,13 @@ esp_err_t esp_ble_gattc_prepare_write(uint16_t conn_id, * - other: failed * */ -esp_err_t esp_ble_gattc_execute_write (uint16_t conn_id, bool is_execute); +esp_err_t esp_ble_gattc_execute_write (esp_gatt_if_t gattc_if, uint16_t conn_id, bool is_execute); /** * @brief This function is called to register for notification of a service. * - * @param[in] gatt_if : gatt interface id. + * @param[in] gattc_if: Gatt client access interface. * @param[in] server_bda : target GATT server. * @param[in] srvc_id : pointer to GATT service ID. * @param[in] char_id : pointer to GATT characteristic ID. @@ -551,16 +576,16 @@ esp_err_t esp_ble_gattc_execute_write (uint16_t conn_id, bool is_execute); * - other: failed * */ -esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gatt_if, - esp_bd_addr_t server_bda, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *char_id); +esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gattc_if, + esp_bd_addr_t server_bda, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id); /** * @brief This function is called to de-register for notification of a service. * - * @param[in] gatt_if : gatt interface id. + * @param[in] gattc_if: Gatt client access interface. * @param[in] server_bda : target GATT server. * @param[in] srvc_id : pointer to GATT service ID. * @param[in] char_id : pointer to GATT characteristic ID. @@ -570,10 +595,10 @@ esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gatt_if, * - other: failed * */ -esp_gatt_status_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gatt_if, - esp_bd_addr_t server_bda, - esp_gatt_srvc_id_t *srvc_id, - esp_gatt_id_t *char_id); +esp_gatt_status_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gattc_if, + esp_bd_addr_t server_bda, + esp_gatt_srvc_id_t *srvc_id, + esp_gatt_id_t *char_id); #ifdef __cplusplus } diff --git a/components/bt/bluedroid/api/include/esp_gatts_api.h b/components/bt/bluedroid/api/include/esp_gatts_api.h index 30aa3ecf56..d0fc055a7f 100644 --- a/components/bt/bluedroid/api/include/esp_gatts_api.h +++ b/components/bt/bluedroid/api/include/esp_gatts_api.h @@ -59,7 +59,6 @@ typedef union { */ struct gatts_reg_evt_param { esp_gatt_status_t status; /*!< Operation status */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t app_id; /*!< Application id which input in register API */ } reg; /*!< Gatt server callback param of ESP_GATTS_REG_EVT */ @@ -127,7 +126,6 @@ typedef union { */ struct gatts_create_evt_param { esp_gatt_status_t status; /*!< Operation status */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t service_handle; /*!< Service attribute handle */ esp_gatt_srvc_id_t service_id; /*!< Service id, include service uuid and other information */ } create; /*!< Gatt server callback param of ESP_GATTS_CREATE_EVT */ @@ -137,7 +135,6 @@ typedef union { */ struct gatts_add_incl_srvc_evt_param { esp_gatt_status_t status; /*!< Operation status */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t attr_handle; /*!< Included service attribute handle */ uint16_t service_handle; /*!< Service attribute handle */ } add_incl_srvc; /*!< Gatt server callback param of ESP_GATTS_ADD_INCL_SRVC_EVT */ @@ -147,7 +144,6 @@ typedef union { */ struct gatts_add_char_evt_param { esp_gatt_status_t status; /*!< Operation status */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t attr_handle; /*!< Characteristic attribute handle */ uint16_t service_handle; /*!< Service attribute handle */ esp_bt_uuid_t char_uuid; /*!< Characteristic uuid */ @@ -158,7 +154,6 @@ typedef union { */ struct gatts_add_char_descr_evt_param { esp_gatt_status_t status; /*!< Operation status */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t attr_handle; /*!< Descriptor attribute handle */ uint16_t service_handle; /*!< Service attribute handle */ esp_bt_uuid_t char_uuid; /*!< Characteristic uuid */ @@ -169,7 +164,6 @@ typedef union { */ struct gatts_delete_evt_param { esp_gatt_status_t status; /*!< Operation status */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t service_handle; /*!< Service attribute handle */ } del; /*!< Gatt server callback param of ESP_GATTS_DELETE_EVT */ @@ -178,7 +172,6 @@ typedef union { */ struct gatts_start_evt_param { esp_gatt_status_t status; /*!< Operation status */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t service_handle; /*!< Service attribute handle */ } start; /*!< Gatt server callback param of ESP_GATTS_START_EVT */ @@ -187,7 +180,6 @@ typedef union { */ struct gatts_stop_evt_param { esp_gatt_status_t status; /*!< Operation status */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ uint16_t service_handle; /*!< Service attribute handle */ } stop; /*!< Gatt server callback param of ESP_GATTS_STOP_EVT */ @@ -196,7 +188,6 @@ typedef union { */ struct gatts_connect_evt_param { uint16_t conn_id; /*!< Connection id */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */ bool is_connected; /*!< Indicate it is connected or not */ } connect; /*!< Gatt server callback param of ESP_GATTS_CONNECT_EVT */ @@ -206,7 +197,6 @@ typedef union { */ struct gatts_disconnect_evt_param { uint16_t conn_id; /*!< Connection id */ - uint16_t gatt_if; /*!< Gatt interface id, different application on gatt client different gatt_if */ esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */ bool is_connected; /*!< Indicate it is connected or not */ } disconnect; /*!< Gatt server callback param of ESP_GATTS_DISCONNECT_EVT */ @@ -240,6 +230,15 @@ typedef union { } rsp; /*!< Gatt server callback param of ESP_GATTS_RESPONSE_EVT */ } esp_ble_gatts_cb_param_t; +/** + * @brief GATT Server callback function type + * @param event : Event type + * @param gatts_if : GATT server access interface, normally + * different gatts_if correspond to different profile + * @param param : Point to callback parameter, currently is union type + */ +typedef void (* esp_gatts_cb_t)(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + /** * @brief This function is called to register application callbacks * with BTA GATTS module. @@ -249,9 +248,7 @@ typedef union { * - other : failed * */ -esp_err_t esp_ble_gatts_register_callback(esp_profile_cb_t callback); - - +esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback); /** * @brief This function is called to register application identifier @@ -268,14 +265,13 @@ esp_err_t esp_ble_gatts_app_register(uint16_t app_id); /** * @brief unregister with GATT Server. * - * @param[in] gatt_if: gatt interface id. - * + * @param[in] gatts_if: GATT server access interface * @return * - ESP_OK : success * - other : failed * */ -esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatt_if); +esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatts_if); /** @@ -285,7 +281,7 @@ esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatt_if); * the callback function needs to be used when adding included * service and characteristics/descriptors into the service. * - * @param[in] gatt_if: gatt interface ID + * @param[in] gatts_if: GATT server access interface * @param[in] service_id: service ID. * @param[in] num_handle: number of handle requested for this service. * @@ -294,7 +290,7 @@ esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatt_if); * - other : failed * */ -esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatt_if, +esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, esp_gatt_srvc_id_t *service_id, uint16_t num_handle); @@ -402,6 +398,7 @@ esp_err_t esp_ble_gatts_stop_service(uint16_t service_handle); /** * @brief This function is called to read a characteristics descriptor. * + * @param[in] gatts_if: GATT server access interface * @param[in] conn_id - connection id to indicate. * @param[in] attr_handle - attribute handle to indicate. * @param[in] value_len - indicate value length. @@ -413,13 +410,14 @@ esp_err_t esp_ble_gatts_stop_service(uint16_t service_handle); * - other : failed * */ -esp_err_t esp_ble_gatts_send_indicate(uint16_t conn_id, uint16_t attr_handle, +esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, uint16_t attr_handle, uint16_t value_len, uint8_t *value, bool need_confirm); /** * @brief This function is called to send a response to a request. * + * @param[in] gatts_if: GATT server access interface * @param[in] conn_id - connection identifier. * @param[in] trans_id - transfer id * @param[in] status - response status @@ -430,14 +428,14 @@ esp_err_t esp_ble_gatts_send_indicate(uint16_t conn_id, uint16_t attr_handle, * - other : failed * */ -esp_err_t esp_ble_gatts_send_response(uint16_t conn_id, uint32_t trans_id, +esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, uint32_t trans_id, esp_gatt_status_t status, esp_gatt_rsp_t *rsp); /** * @brief Open a direct open connection or add a background auto connection * - * @param[in] gatt_if: application ID. + * @param[in] gatts_if: GATT server access interface * @param[in] remote_bda: remote device bluetooth device address. * @param[in] is_direct: direct connection or background auto connection * @@ -446,11 +444,12 @@ esp_err_t esp_ble_gatts_send_response(uint16_t conn_id, uint32_t trans_id, * - other : failed * */ -esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatt_if, esp_bd_addr_t remote_bda, bool is_direct); +esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatts_if, esp_bd_addr_t remote_bda, bool is_direct); /** * @brief Close a connection a remote device. * + * @param[in] gatts_if: GATT server access interface * @param[in] conn_id: connection ID to be closed. * * @return @@ -458,7 +457,7 @@ esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatt_if, esp_bd_addr_t remote_bda, bo * - other : failed * */ -esp_err_t esp_ble_gatts_close(uint16_t conn_id); +esp_err_t esp_ble_gatts_close(esp_gatt_if_t gatts_if, uint16_t conn_id); #ifdef __cplusplus } diff --git a/components/bt/bluedroid/btc/core/btc_manage.c b/components/bt/bluedroid/btc/core/btc_manage.c index 46d20dc2d8..213c4939c1 100644 --- a/components/bt/bluedroid/btc/core/btc_manage.c +++ b/components/bt/bluedroid/btc/core/btc_manage.c @@ -20,7 +20,7 @@ #include "esp_bt_defs.h" #include "esp_gatt_defs.h" -static esp_profile_cb_t btc_profile_cb_tab[BTC_PID_NUM] = {}; +static void *btc_profile_cb_tab[BTC_PID_NUM] = {}; void esp_profile_cb_reset(void) { @@ -31,7 +31,7 @@ void esp_profile_cb_reset(void) } } -int btc_profile_cb_set(btc_pid_t profile_id, esp_profile_cb_t cb) +int btc_profile_cb_set(btc_pid_t profile_id, void *cb) { if (profile_id < 0 || profile_id >= BTC_PID_NUM) { return -1; @@ -42,7 +42,7 @@ int btc_profile_cb_set(btc_pid_t profile_id, esp_profile_cb_t cb) return 0; } -esp_profile_cb_t btc_profile_cb_get(btc_pid_t profile_id) +void *btc_profile_cb_get(btc_pid_t profile_id) { if (profile_id < 0 || profile_id >= BTC_PID_NUM) { return NULL; diff --git a/components/bt/bluedroid/btc/include/btc_manage.h b/components/bt/bluedroid/btc/include/btc_manage.h index e8591caa8a..8789f543dd 100644 --- a/components/bt/bluedroid/btc/include/btc_manage.h +++ b/components/bt/bluedroid/btc/include/btc_manage.h @@ -22,7 +22,7 @@ /* reset gatt callback table */ void esp_profile_cb_reset(void); -int btc_profile_cb_set(btc_pid_t profile_id, esp_profile_cb_t cb); -esp_profile_cb_t btc_profile_cb_get(btc_pid_t profile_id); +int btc_profile_cb_set(btc_pid_t profile_id, void *cb); +void *btc_profile_cb_get(btc_pid_t profile_id); #endif /* __BTC_MANAGE_H__ */ 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 8acb9565c8..d6e23e63a3 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c @@ -37,7 +37,7 @@ const char success_msg[] = "BLUFI_CONFIG_OK"; const char failed_msg[] = "BLUFI_CONFIG_FAILED"; -#define BTC_BLUFI_CB_TO_APP(_event, _param) ((esp_profile_cb_t)btc_profile_cb_get(BTC_PID_BLUFI))(_event, _param) +#define BTC_BLUFI_CB_TO_APP(event, param) ((esp_blufi_cb_t)btc_profile_cb_get(BTC_PID_BLUFI))((event), (param)) #define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" #define BT_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] @@ -147,6 +147,8 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) tBT_UUID uuid = {LEN_UUID_16, {SVC_BLUFI_UUID}}; UINT8 *p_rec_data = NULL; tBTA_GATT_STATUS status; + tBTA_DM_DISC disc_mode = BTA_DM_BLE_GENERAL_DISCOVERABLE; + tBTA_DM_CONN conn_mode = BTA_DM_BLE_CONNECTABLE; LOG_DEBUG("blufi profile cb event = %x\n", event); switch (event) { @@ -166,6 +168,8 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) event, status, blufi_cb_env.gatt_if); LOG_DEBUG("set advertising parameters\n"); + //set connectable,discoverable, pairable and paired only modes of local device + BTA_DmSetVisibility(disc_mode, conn_mode, (uint8_t)BTA_DM_NON_PAIRABLE, (uint8_t)BTA_DM_CONN_ALL); //set the advertising data to the btm layer BlufiBleConfigadvData(&esp32_adv_data[BLE_ADV_DATA_IDX], NULL); //set the adversting data to the btm layer 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 f458cb4ced..39b392e62f 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 @@ -26,7 +26,7 @@ static tBTA_BLE_ADV_DATA gl_bta_adv_data; static tBTA_BLE_ADV_DATA gl_bta_scan_rsp_data; -#define BTC_GAP_BLE_CB_TO_APP(_event, _param) ((esp_profile_cb_t )btc_profile_cb_get(BTC_PID_GAP_BLE))(_event, _param) +#define BTC_GAP_BLE_CB_TO_APP(event, param) ((esp_gap_ble_cb_t)btc_profile_cb_get(BTC_PID_GAP_BLE))((event), (param)) static void btc_gap_adv_point_cleanup(void **buf) diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatt_util.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatt_util.c index 938d2dc409..217ec9db4b 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatt_util.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatt_util.c @@ -169,13 +169,14 @@ uint16_t get_uuid16(tBT_UUID *p_uuid) return (UINT16) p_uuid->uu.uuid32; } } -uint16_t set_read_value(esp_ble_gattc_cb_param_t *p_dest, tBTA_GATTC_READ *p_src) +uint16_t set_read_value(uint8_t *gattc_if, esp_ble_gattc_cb_param_t *p_dest, tBTA_GATTC_READ *p_src) { uint16_t descr_type = 0; uint16_t len = 0; p_dest->read.status = p_src->status; - p_dest->read.conn_id = p_src->conn_id; + p_dest->read.conn_id = BTC_GATT_GET_CONN_ID(p_src->conn_id); + *gattc_if = BTC_GATT_GET_GATT_IF(p_src->conn_id); bta_to_btc_srvc_id(&p_dest->read.srvc_id, &p_src->srvc_id); bta_to_btc_gatt_id(&p_dest->read.char_id, &p_src->char_id); bta_to_btc_gatt_id(&p_dest->read.descr_id, &p_src->descr_type); diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c index b5da7ed249..b7df92fcfa 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c @@ -22,7 +22,7 @@ #include "bt_trace.h" #include "esp_gattc_api.h" -#define BTC_GATTC_CB_TO_APP(_event, _param) ((esp_profile_cb_t )btc_profile_cb_get(BTC_PID_GATTC))(_event, _param) +#define BTC_GATTC_CB_TO_APP(event, gattc_if, param) ((esp_gattc_cb_t )btc_profile_cb_get(BTC_PID_GATTC))((event), (gattc_if), (param)) void btc_gattc_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { @@ -141,13 +141,13 @@ static void btc_gattc_app_register(btc_ble_gattc_args_t *arg) static void btc_gattc_app_unregister(btc_ble_gattc_args_t *arg) { - BTA_GATTC_AppDeregister(arg->app_unreg.gatt_if); + BTA_GATTC_AppDeregister(arg->app_unreg.gattc_if); } static void btc_gattc_open(btc_ble_gattc_args_t *arg) { tBTA_GATT_TRANSPORT transport = BTA_GATT_TRANSPORT_LE; - BTA_GATTC_Open(arg->open.gatt_if, arg->open.remote_bda, arg->open.is_direct, transport); + BTA_GATTC_Open(arg->open.gattc_if, arg->open.remote_bda, arg->open.is_direct, transport); } static void btc_gattc_close(btc_ble_gattc_args_t *arg) @@ -181,6 +181,7 @@ static void btc_gattc_get_first_char(btc_ble_gattc_args_t *arg) tBTA_GATT_CHAR_PROP out_char_prop; tBTA_GATT_SRVC_ID srvc_id; esp_ble_gattc_cb_param_t param; + esp_gatt_if_t gattc_if; btc_to_bta_srvc_id(&srvc_id, &arg->get_first_char.service_id); status = BTA_GATTC_GetFirstChar(arg->get_first_char.conn_id, &srvc_id, NULL, @@ -189,13 +190,14 @@ static void btc_gattc_get_first_char(btc_ble_gattc_args_t *arg) bta_to_btc_gatt_id(&char_id, &out_char_id.char_id); } + gattc_if = BTC_GATT_GET_GATT_IF(arg->get_first_char.conn_id); memset(¶m, 0, sizeof(esp_ble_gattc_cb_param_t)); - param.get_char.conn_id = arg->get_first_char.conn_id; + param.get_char.conn_id = BTC_GATT_GET_CONN_ID(arg->get_first_char.conn_id); param.get_char.status = status; memcpy(¶m.get_char.srvc_id, &arg->get_first_char.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_char.char_id, &char_id, sizeof(esp_gatt_id_t)); param.get_char.char_prop = out_char_prop; - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_CHAR_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_CHAR_EVT, gattc_if, ¶m); } static void btc_gattc_get_next_char(btc_ble_gattc_args_t *arg) @@ -206,6 +208,8 @@ static void btc_gattc_get_next_char(btc_ble_gattc_args_t *arg) tBTA_GATTC_CHAR_ID out_char_id; tBTA_GATT_CHAR_PROP out_char_prop; esp_ble_gattc_cb_param_t param; + esp_gatt_if_t gattc_if; + btc_to_bta_srvc_id(&in_char_id.srvc_id, &arg->get_next_char.service_id); btc_to_bta_gatt_id(&in_char_id.char_id, &arg->get_next_char.char_id); @@ -216,13 +220,14 @@ static void btc_gattc_get_next_char(btc_ble_gattc_args_t *arg) bta_to_btc_gatt_id(&char_id, &out_char_id.char_id); } + gattc_if = BTC_GATT_GET_GATT_IF(arg->get_next_char.conn_id); memset(¶m, 0, sizeof(esp_ble_gattc_cb_param_t)); - param.get_char.conn_id = arg->get_next_char.conn_id; + param.get_char.conn_id = BTC_GATT_GET_CONN_ID(arg->get_next_char.conn_id); param.get_char.status = status; memcpy(¶m.get_char.srvc_id, &arg->get_next_char.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_char.char_id, &char_id, sizeof(esp_gatt_id_t)); param.get_char.char_prop = out_char_prop; - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_CHAR_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_CHAR_EVT, gattc_if, ¶m); } static void btc_gattc_get_first_descr(btc_ble_gattc_args_t *arg) @@ -232,6 +237,7 @@ static void btc_gattc_get_first_descr(btc_ble_gattc_args_t *arg) tBTA_GATTC_CHAR_ID in_char_id; tBTA_GATTC_CHAR_DESCR_ID out_char_descr_id; esp_ble_gattc_cb_param_t param; + esp_gatt_if_t gattc_if; btc_to_bta_srvc_id(&in_char_id.srvc_id, &arg->get_first_descr.service_id); btc_to_bta_gatt_id(&in_char_id.char_id, &arg->get_first_descr.char_id); @@ -243,13 +249,14 @@ static void btc_gattc_get_first_descr(btc_ble_gattc_args_t *arg) bta_to_btc_gatt_id(&descr_id, &out_char_descr_id.descr_id); } + gattc_if = BTC_GATT_GET_GATT_IF(arg->get_first_descr.conn_id); memset(¶m, 0, sizeof(esp_ble_gattc_cb_param_t)); - param.get_descr.conn_id = arg->get_first_descr.conn_id; + param.get_descr.conn_id = BTC_GATT_GET_CONN_ID(arg->get_first_descr.conn_id); param.get_descr.status = status; memcpy(¶m.get_descr.srvc_id, &arg->get_first_descr.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_descr.char_id, &arg->get_first_descr.char_id, sizeof(esp_gatt_id_t)); memcpy(¶m.get_descr.descr_id, &descr_id, sizeof(esp_gatt_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_DESCR_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_DESCR_EVT, gattc_if, ¶m); } static void btc_gattc_get_next_descr(btc_ble_gattc_args_t *arg) @@ -259,6 +266,7 @@ static void btc_gattc_get_next_descr(btc_ble_gattc_args_t *arg) tBTA_GATTC_CHAR_DESCR_ID in_char_descr_id; tBTA_GATTC_CHAR_DESCR_ID out_char_descr_id; esp_ble_gattc_cb_param_t param; + esp_gatt_if_t gattc_if; btc_to_bta_srvc_id(&in_char_descr_id.char_id.srvc_id, &arg->get_next_descr.service_id); btc_to_bta_gatt_id(&in_char_descr_id.char_id.char_id, &arg->get_next_descr.char_id); @@ -270,13 +278,14 @@ static void btc_gattc_get_next_descr(btc_ble_gattc_args_t *arg) bta_to_btc_gatt_id(&descr_id, &out_char_descr_id.descr_id); } + gattc_if = BTC_GATT_GET_GATT_IF(arg->get_next_descr.conn_id); memset(¶m, 0, sizeof(esp_ble_gattc_cb_param_t)); - param.get_descr.conn_id = arg->get_next_descr.conn_id; + param.get_descr.conn_id = BTC_GATT_GET_CONN_ID(arg->get_next_descr.conn_id); param.get_descr.status = status; memcpy(¶m.get_descr.srvc_id, &arg->get_next_descr.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_descr.char_id, &arg->get_next_descr.char_id, sizeof(esp_gatt_id_t)); memcpy(¶m.get_descr.descr_id, &descr_id, sizeof(esp_gatt_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_DESCR_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_DESCR_EVT, gattc_if, ¶m); } static void btc_gattc_get_first_incl_service(btc_ble_gattc_args_t *arg) @@ -286,6 +295,7 @@ static void btc_gattc_get_first_incl_service(btc_ble_gattc_args_t *arg) tBTA_GATT_SRVC_ID srvc_id; tBTA_GATTC_INCL_SVC_ID out_incl_svc_id; esp_ble_gattc_cb_param_t param; + esp_gatt_if_t gattc_if; btc_to_bta_srvc_id(&srvc_id, &arg->get_first_incl_srvc.service_id); @@ -294,12 +304,13 @@ static void btc_gattc_get_first_incl_service(btc_ble_gattc_args_t *arg) bta_to_btc_srvc_id(&incl_srvc_id, &out_incl_svc_id.incl_svc_id); + gattc_if = BTC_GATT_GET_GATT_IF(arg->get_first_incl_srvc.conn_id); memset(¶m, 0, sizeof(esp_ble_gattc_cb_param_t)); - param.get_incl_srvc.conn_id = arg->get_first_incl_srvc.conn_id; + param.get_incl_srvc.conn_id = BTC_GATT_GET_CONN_ID(arg->get_first_incl_srvc.conn_id); param.get_incl_srvc.status = status; memcpy(¶m.get_incl_srvc.srvc_id, &arg->get_first_incl_srvc.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_incl_srvc.incl_srvc_id, &incl_srvc_id, sizeof(esp_gatt_srvc_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_INCL_SRVC_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_INCL_SRVC_EVT, gattc_if, ¶m); } static void btc_gattc_get_next_incl_service(btc_ble_gattc_args_t *arg) @@ -309,6 +320,7 @@ static void btc_gattc_get_next_incl_service(btc_ble_gattc_args_t *arg) tBTA_GATTC_INCL_SVC_ID in_incl_svc_id; tBTA_GATTC_INCL_SVC_ID out_incl_svc_id; esp_ble_gattc_cb_param_t param; + esp_gatt_if_t gattc_if; btc_to_bta_srvc_id(&in_incl_svc_id.srvc_id, &arg->get_next_incl_srvc.service_id); btc_to_bta_srvc_id(&in_incl_svc_id.incl_svc_id, &arg->get_next_incl_srvc.start_service_id); @@ -318,12 +330,13 @@ static void btc_gattc_get_next_incl_service(btc_ble_gattc_args_t *arg) bta_to_btc_srvc_id(&incl_srvc_id, &out_incl_svc_id.incl_svc_id); + gattc_if = BTC_GATT_GET_GATT_IF(arg->get_next_incl_srvc.conn_id); memset(¶m, 0, sizeof(esp_ble_gattc_cb_param_t)); - param.get_incl_srvc.conn_id = arg->get_next_incl_srvc.conn_id; + param.get_incl_srvc.conn_id = BTC_GATT_GET_CONN_ID(arg->get_next_incl_srvc.conn_id); param.get_incl_srvc.status = status; memcpy(¶m.get_incl_srvc.srvc_id, &arg->get_next_incl_srvc.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_incl_srvc.incl_srvc_id, &incl_srvc_id, sizeof(esp_gatt_srvc_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_INCL_SRVC_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_INCL_SRVC_EVT, gattc_if, ¶m); } static void btc_gattc_read_char(btc_ble_gattc_args_t *arg) @@ -402,7 +415,7 @@ static void btc_gattc_reg_for_notify(btc_ble_gattc_args_t *arg) btc_to_bta_srvc_id(&in_char_id.srvc_id, &arg->reg_for_notify.service_id); btc_to_bta_gatt_id(&in_char_id.char_id, &arg->reg_for_notify.char_id); - status = BTA_GATTC_RegisterForNotifications(arg->reg_for_notify.gatt_if, + status = BTA_GATTC_RegisterForNotifications(arg->reg_for_notify.gattc_if, arg->reg_for_notify.remote_bda, &in_char_id); @@ -410,7 +423,7 @@ static void btc_gattc_reg_for_notify(btc_ble_gattc_args_t *arg) param.reg_for_notify.status = status; memcpy(¶m.reg_for_notify.srvc_id, &arg->reg_for_notify.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.reg_for_notify.char_id, &arg->reg_for_notify.service_id, sizeof(esp_gatt_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_REG_FOR_NOTIFY_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_REG_FOR_NOTIFY_EVT, arg->reg_for_notify.gattc_if, ¶m); } static void btc_gattc_unreg_for_notify(btc_ble_gattc_args_t *arg) @@ -422,7 +435,7 @@ static void btc_gattc_unreg_for_notify(btc_ble_gattc_args_t *arg) btc_to_bta_srvc_id(&in_char_id.srvc_id, &arg->unreg_for_notify.service_id); btc_to_bta_gatt_id(&in_char_id.char_id, &arg->unreg_for_notify.char_id); - status = BTA_GATTC_DeregisterForNotifications(arg->unreg_for_notify.gatt_if, + status = BTA_GATTC_DeregisterForNotifications(arg->unreg_for_notify.gattc_if, arg->unreg_for_notify.remote_bda, &in_char_id); @@ -430,7 +443,7 @@ static void btc_gattc_unreg_for_notify(btc_ble_gattc_args_t *arg) param.unreg_for_notify.status = status; memcpy(¶m.unreg_for_notify.srvc_id, &arg->unreg_for_notify.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.unreg_for_notify.char_id, &arg->unreg_for_notify.service_id, sizeof(esp_gatt_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_UNREG_FOR_NOTIFY_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_UNREG_FOR_NOTIFY_EVT, arg->unreg_for_notify.gattc_if, ¶m); } void btc_gattc_call_handler(btc_msg_t *msg) @@ -508,6 +521,7 @@ void btc_gattc_call_handler(btc_msg_t *msg) void btc_gattc_cb_handler(btc_msg_t *msg) { tBTA_GATTC *arg = (tBTA_GATTC *)(msg->arg); + esp_gatt_if_t gattc_if; esp_ble_gattc_cb_param_t param; memset(¶m, 0, sizeof(esp_ble_gattc_cb_param_t)); @@ -515,75 +529,90 @@ void btc_gattc_cb_handler(btc_msg_t *msg) switch (msg->act) { case BTA_GATTC_REG_EVT: { tBTA_GATTC_REG *reg_oper = &arg->reg_oper; + + gattc_if = reg_oper->client_if; param.reg.status = reg_oper->status; - param.reg.gatt_if = reg_oper->client_if; param.reg.app_id = reg_oper->app_uuid.uu.uuid16; - BTC_GATTC_CB_TO_APP(ESP_GATTC_REG_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_REG_EVT, gattc_if, ¶m); break; } case BTA_GATTC_DEREG_EVT: { - BTC_GATTC_CB_TO_APP(ESP_GATTC_UNREG_EVT, NULL); + tBTA_GATTC_REG *reg_oper = &arg->reg_oper; + + gattc_if = reg_oper->client_if; + BTC_GATTC_CB_TO_APP(ESP_GATTC_UNREG_EVT, gattc_if, NULL); break; } case BTA_GATTC_READ_CHAR_EVT: { - set_read_value(¶m, &arg->read); - BTC_GATTC_CB_TO_APP(ESP_GATTC_READ_CHAR_EVT, ¶m); + set_read_value(&gattc_if, ¶m, &arg->read); + BTC_GATTC_CB_TO_APP(ESP_GATTC_READ_CHAR_EVT, gattc_if, ¶m); break; } - case BTA_GATTC_WRITE_CHAR_EVT: case BTA_GATTC_PREP_WRITE_EVT: { tBTA_GATTC_WRITE *write = &arg->write; uint32_t ret_evt = (msg->act == BTA_GATTC_WRITE_CHAR_EVT) ? ESP_GATTC_WRITE_CHAR_EVT : ESP_GATTC_PREP_WRITE_EVT; - param.write.conn_id = write->conn_id; + + gattc_if = BTC_GATT_GET_GATT_IF(write->conn_id); + param.write.conn_id = BTC_GATT_GET_CONN_ID(write->conn_id); param.write.status = write->status; bta_to_btc_srvc_id(¶m.write.srvc_id, &write->srvc_id); bta_to_btc_gatt_id(¶m.write.char_id, &write->char_id); - BTC_GATTC_CB_TO_APP(ret_evt, ¶m); + BTC_GATTC_CB_TO_APP(ret_evt, gattc_if, ¶m); break; } case BTA_GATTC_EXEC_EVT: { tBTA_GATTC_EXEC_CMPL *exec_cmpl = &arg->exec_cmpl; - param.exec_cmpl.conn_id = exec_cmpl->conn_id; + + gattc_if = BTC_GATT_GET_GATT_IF(exec_cmpl->conn_id); + param.exec_cmpl.conn_id = BTC_GATT_GET_CONN_ID(exec_cmpl->conn_id); param.exec_cmpl.status = exec_cmpl->status; - BTC_GATTC_CB_TO_APP(ESP_GATTC_EXEC_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_EXEC_EVT, gattc_if, ¶m); break; } case BTA_GATTC_SEARCH_CMPL_EVT: { tBTA_GATTC_SEARCH_CMPL *search_cmpl = &arg->search_cmpl; - param.search_cmpl.conn_id = search_cmpl->conn_id; + + gattc_if = BTC_GATT_GET_GATT_IF(search_cmpl->conn_id); + param.search_cmpl.conn_id = BTC_GATT_GET_CONN_ID(search_cmpl->conn_id); param.search_cmpl.status = search_cmpl->status; - BTC_GATTC_CB_TO_APP(ESP_GATTC_SEARCH_CMPL_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_SEARCH_CMPL_EVT, gattc_if, ¶m); break; } case BTA_GATTC_SEARCH_RES_EVT: { tBTA_GATTC_SRVC_RES *srvc_res = &arg->srvc_res; - param.search_res.conn_id = srvc_res->conn_id; + + gattc_if = BTC_GATT_GET_GATT_IF(srvc_res->conn_id); + param.search_res.conn_id = BTC_GATT_GET_CONN_ID(srvc_res->conn_id); bta_to_btc_srvc_id(¶m.search_res.srvc_id, &srvc_res->service_uuid); - BTC_GATTC_CB_TO_APP(ESP_GATTC_SEARCH_RES_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_SEARCH_RES_EVT, gattc_if, ¶m); break; } case BTA_GATTC_READ_DESCR_EVT: { - set_read_value(¶m, &arg->read); - BTC_GATTC_CB_TO_APP(ESP_GATTC_READ_DESCR_EVT, ¶m); + set_read_value(&gattc_if, ¶m, &arg->read); + BTC_GATTC_CB_TO_APP(ESP_GATTC_READ_DESCR_EVT, gattc_if, ¶m); break; } case BTA_GATTC_WRITE_DESCR_EVT: { tBTA_GATTC_WRITE *write = &arg->write; - param.write.conn_id = write->conn_id; + + gattc_if = BTC_GATT_GET_GATT_IF(write->conn_id); + param.write.conn_id = BTC_GATT_GET_CONN_ID(write->conn_id); param.write.status = write->status; bta_to_btc_srvc_id(¶m.write.srvc_id, &write->srvc_id); bta_to_btc_gatt_id(¶m.write.char_id, &write->char_id); bta_to_btc_gatt_id(¶m.write.descr_id, &write->descr_type); - BTC_GATTC_CB_TO_APP(ESP_GATTC_WRITE_DESCR_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_WRITE_DESCR_EVT, gattc_if, ¶m); break; } case BTA_GATTC_NOTIF_EVT: { tBTA_GATTC_NOTIFY *notify = &arg->notify; - param.notify.conn_id = notify->conn_id; + + gattc_if = BTC_GATT_GET_GATT_IF(notify->conn_id); + param.notify.conn_id = BTC_GATT_GET_CONN_ID(notify->conn_id); memcpy(param.notify.remote_bda, notify->bda, sizeof(esp_bd_addr_t)); bta_to_btc_srvc_id(¶m.notify.srvc_id, ¬ify->char_id.srvc_id); bta_to_btc_gatt_id(¶m.notify.char_id, ¬ify->char_id.char_id); @@ -597,57 +626,63 @@ void btc_gattc_cb_handler(btc_msg_t *msg) BTA_GATTC_SendIndConfirm(notify->conn_id, ¬ify->char_id); } - BTC_GATTC_CB_TO_APP(ESP_GATTC_NOTIFY_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_NOTIFY_EVT, gattc_if, ¶m); break; } case BTA_GATTC_OPEN_EVT: { tBTA_GATTC_OPEN *open = &arg->open; + + gattc_if = open->client_if; param.open.status = open->status; - param.open.conn_id = open->conn_id; - param.open.gatt_if = open->client_if; + param.open.conn_id = BTC_GATT_GET_CONN_ID(open->conn_id); memcpy(param.open.remote_bda, open->remote_bda, sizeof(esp_bd_addr_t)); param.open.mtu = open->mtu; - BTC_GATTC_CB_TO_APP(ESP_GATTC_OPEN_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_OPEN_EVT, gattc_if, ¶m); break; } case BTA_GATTC_CLOSE_EVT: { tBTA_GATTC_CLOSE *close = &arg->close; + + gattc_if = close->client_if; param.close.status = close->status; - param.close.conn_id = close->conn_id; - param.close.gatt_if = close->client_if; + param.close.conn_id = BTC_GATT_GET_CONN_ID(close->conn_id); memcpy(param.close.remote_bda, close->remote_bda, sizeof(esp_bd_addr_t)); param.close.reason = close->reason; - BTC_GATTC_CB_TO_APP(ESP_GATTC_CLOSE_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_CLOSE_EVT, gattc_if, ¶m); break; } case BTA_GATTC_CFG_MTU_EVT: { tBTA_GATTC_CFG_MTU *cfg_mtu = &arg->cfg_mtu; - param.cfg_mtu.conn_id = cfg_mtu->conn_id; + + gattc_if = BTC_GATT_GET_GATT_IF(cfg_mtu->conn_id); + param.cfg_mtu.conn_id = BTC_GATT_GET_CONN_ID(cfg_mtu->conn_id); param.cfg_mtu.status = cfg_mtu->status; param.cfg_mtu.mtu = cfg_mtu->mtu; - BTC_GATTC_CB_TO_APP(ESP_GATTC_CFG_MTU_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_CFG_MTU_EVT, gattc_if, ¶m); break; } case BTA_GATTC_ACL_EVT: { - BTC_GATTC_CB_TO_APP(ESP_GATTC_ACL_EVT, NULL); + /* Currently, this event will never happen */ break; } case BTA_GATTC_CANCEL_OPEN_EVT: { - BTC_GATTC_CB_TO_APP(ESP_GATTC_CANCEL_OPEN_EVT, NULL); + /* Currently, this event will never happen */ break; } case BTA_GATTC_CONGEST_EVT: { tBTA_GATTC_CONGEST *congest = &arg->congest; - param.congest.conn_id = congest->conn_id; + + gattc_if = BTC_GATT_GET_GATT_IF(congest->conn_id); + param.congest.conn_id = BTC_GATT_GET_CONN_ID(congest->conn_id); param.congest.congested = (congest->congested == TRUE) ? true : false; - BTC_GATTC_CB_TO_APP(ESP_GATTC_CONGEST_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_CONGEST_EVT, gattc_if, ¶m); break; } case BTA_GATTC_SRVC_CHG_EVT: { memcpy(param.srvc_chg.remote_bda, arg->remote_bda, sizeof(esp_bd_addr_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_SRVC_CHG_EVT, ¶m); + BTC_GATTC_CB_TO_APP(ESP_GATTC_SRVC_CHG_EVT, ESP_GATT_IF_NONE, ¶m); break; } default: diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c index 8ef1e9aefd..c7afabfa2a 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c @@ -23,7 +23,7 @@ #include "esp_gatts_api.h" -#define BTC_GATTS_CB_TO_APP(_event, _param) ((esp_profile_cb_t)btc_profile_cb_get(BTC_PID_GATTS))(_event, _param) +#define BTC_GATTS_CB_TO_APP(event, gatts_if, param) ((esp_gatts_cb_t)btc_profile_cb_get(BTC_PID_GATTS))((event), (gatts_if), (param)) #define A2C_GATTS_EVT(_bta_event) (_bta_event) //BTA TO BTC EVT #define C2A_GATTS_EVT(_btc_event) (_btc_event) //BTC TO BTA EVT @@ -171,12 +171,12 @@ void btc_gatts_call_handler(btc_msg_t *msg) break; } case BTC_GATTS_ACT_APP_UNREGISTER: - BTA_GATTS_AppDeregister(arg->app_unreg.gatt_if); + BTA_GATTS_AppDeregister(arg->app_unreg.gatts_if); break; case BTC_GATTS_ACT_CREATE_SERVICE: { tBTA_GATT_SRVC_ID srvc_id; btc_to_bta_srvc_id(&srvc_id, &arg->create_srvc.service_id); - BTA_GATTS_CreateService(arg->create_srvc.gatt_if, &srvc_id.id.uuid, + BTA_GATTS_CreateService(arg->create_srvc.gatts_if, &srvc_id.id.uuid, srvc_id.id.inst_id, arg->create_srvc.num_handle, srvc_id.is_primary); break; @@ -227,7 +227,7 @@ void btc_gatts_call_handler(btc_msg_t *msg) } param.rsp.status = 0; - BTC_GATTS_CB_TO_APP(ESP_GATTS_RESPONSE_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_RESPONSE_EVT, BTC_GATT_GET_GATT_IF(arg->send_rsp.conn_id), ¶m); break; } case BTC_GATTS_ACT_OPEN: { @@ -252,7 +252,7 @@ void btc_gatts_call_handler(btc_msg_t *msg) transport = BTA_GATT_TRANSPORT_LE; // Connect! - BTA_GATTS_Open(arg->open.gatt_if, arg->open.remote_bda, + BTA_GATTS_Open(arg->open.gatts_if, arg->open.remote_bda, arg->open.is_direct, transport); break; } @@ -277,34 +277,38 @@ void btc_gatts_call_handler(btc_msg_t *msg) void btc_gatts_cb_handler(btc_msg_t *msg) { esp_ble_gatts_cb_param_t param; - tBTA_GATTS *p_data = (tBTA_GATTS *)msg->arg; + esp_gatt_if_t gatts_if; switch (msg->act) { case BTA_GATTS_REG_EVT: { + gatts_if = p_data->reg_oper.server_if; param.reg.status = p_data->reg_oper.status; - param.reg.gatt_if = p_data->reg_oper.server_if; param.reg.app_id = p_data->reg_oper.uuid.uu.uuid16; - BTC_GATTS_CB_TO_APP(ESP_GATTS_REG_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_REG_EVT, gatts_if, ¶m); break; } - case BTA_GATTS_DEREG_EVT: - // do nothing + case BTA_GATTS_DEREG_EVT: { + gatts_if = p_data->reg_oper.server_if; + BTC_GATTS_CB_TO_APP(ESP_GATTS_UNREG_EVT, gatts_if, NULL); break; + } case BTA_GATTS_READ_EVT: { - param.read.conn_id = p_data->req_data.conn_id; + gatts_if = BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id); + param.read.conn_id = BTC_GATT_GET_CONN_ID(p_data->req_data.conn_id); param.read.trans_id = p_data->req_data.trans_id; memcpy(param.read.bda, p_data->req_data.remote_bda, ESP_BD_ADDR_LEN); param.read.handle = p_data->req_data.p_data->read_req.handle, - param.read.offset = p_data->req_data.p_data->read_req.offset, - param.read.is_long = p_data->req_data.p_data->read_req.is_long, + param.read.offset = p_data->req_data.p_data->read_req.offset, + param.read.is_long = p_data->req_data.p_data->read_req.is_long, - BTC_GATTS_CB_TO_APP(ESP_GATTS_READ_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_READ_EVT, gatts_if, ¶m); break; } case BTA_GATTS_WRITE_EVT: { - param.write.conn_id = p_data->req_data.conn_id; + gatts_if = BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id); + param.write.conn_id = BTC_GATT_GET_CONN_ID(p_data->req_data.conn_id); param.write.trans_id = p_data->req_data.trans_id; memcpy(param.write.bda, p_data->req_data.remote_bda, ESP_BD_ADDR_LEN); param.write.handle = p_data->req_data.p_data->write_req.handle; @@ -314,103 +318,104 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.write.len = p_data->req_data.p_data->write_req.len; param.write.value = p_data->req_data.p_data->write_req.value; - BTC_GATTS_CB_TO_APP(ESP_GATTS_WRITE_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_WRITE_EVT, gatts_if, ¶m); break; } case BTA_GATTS_EXEC_WRITE_EVT: { - param.exec_write.conn_id = p_data->req_data.conn_id; + gatts_if = BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id); + param.exec_write.conn_id = BTC_GATT_GET_CONN_ID(p_data->req_data.conn_id); param.exec_write.trans_id = p_data->req_data.trans_id; memcpy(param.exec_write.bda, p_data->req_data.remote_bda, ESP_BD_ADDR_LEN); param.exec_write.exec_write_flag = p_data->req_data.p_data->exec_write; - BTC_GATTS_CB_TO_APP(ESP_GATTS_EXEC_WRITE_EVT, ¶m); - + BTC_GATTS_CB_TO_APP(ESP_GATTS_EXEC_WRITE_EVT, gatts_if, ¶m); break; } case BTA_GATTS_MTU_EVT: - param.mtu.conn_id = p_data->req_data.conn_id; + gatts_if = BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id); + param.mtu.conn_id = BTC_GATT_GET_CONN_ID(p_data->req_data.conn_id); param.mtu.mtu = p_data->req_data.p_data->mtu; - BTC_GATTS_CB_TO_APP(ESP_GATTS_MTU_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_MTU_EVT, gatts_if, ¶m); break; case BTA_GATTS_CONF_EVT: - param.conf.conn_id = p_data->req_data.conn_id; + gatts_if = BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id); + param.conf.conn_id = BTC_GATT_GET_CONN_ID(p_data->req_data.conn_id); param.conf.status = p_data->req_data.status; - BTC_GATTS_CB_TO_APP(ESP_GATTS_CONF_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_CONF_EVT, gatts_if, ¶m); break; case BTA_GATTS_CREATE_EVT: + gatts_if = p_data->create.server_if; param.create.status = p_data->create.status; - param.create.gatt_if = p_data->create.server_if; param.create.service_handle = p_data->create.service_id; param.create.service_id.is_primary = p_data->create.is_primary; param.create.service_id.id.inst_id = p_data->create.svc_instance; bta_to_btc_uuid(¶m.create.service_id.id.uuid, &p_data->create.uuid); - BTC_GATTS_CB_TO_APP(ESP_GATTS_CREATE_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_CREATE_EVT, gatts_if, ¶m); break; case BTA_GATTS_ADD_INCL_SRVC_EVT: + gatts_if = p_data->add_result.server_if; param.add_incl_srvc.status = p_data->add_result.status; - param.add_incl_srvc.gatt_if = p_data->add_result.server_if; param.add_incl_srvc.attr_handle = p_data->add_result.attr_id; param.add_incl_srvc.service_handle = p_data->add_result.service_id; - BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_INCL_SRVC_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_INCL_SRVC_EVT, gatts_if, ¶m); break; case BTA_GATTS_ADD_CHAR_EVT: + gatts_if = p_data->add_result.server_if; param.add_char.status = p_data->add_result.status; - param.add_char.gatt_if = p_data->add_result.server_if; param.add_char.attr_handle = p_data->add_result.attr_id; param.add_char.service_handle = p_data->add_result.service_id; bta_to_btc_uuid(¶m.add_char.char_uuid, &p_data->add_result.char_uuid); - BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_CHAR_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_CHAR_EVT, gatts_if, ¶m); break; case BTA_GATTS_ADD_CHAR_DESCR_EVT: + gatts_if = p_data->add_result.server_if; param.add_char_descr.status = p_data->add_result.status; - param.add_char_descr.gatt_if = p_data->add_result.server_if; param.add_char_descr.attr_handle = p_data->add_result.attr_id; param.add_char_descr.service_handle = p_data->add_result.service_id; bta_to_btc_uuid(¶m.add_char_descr.char_uuid, &p_data->add_result.char_uuid); - BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_CHAR_DESCR_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_CHAR_DESCR_EVT, gatts_if, ¶m); break; case BTA_GATTS_DELELTE_EVT: + gatts_if = p_data->srvc_oper.server_if; param.del.status = p_data->srvc_oper.status; - param.del.gatt_if = p_data->srvc_oper.server_if; param.del.service_handle = p_data->srvc_oper.service_id; - - BTC_GATTS_CB_TO_APP(ESP_GATTS_DELETE_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_DELETE_EVT, gatts_if, ¶m); break; case BTA_GATTS_START_EVT: + gatts_if = p_data->srvc_oper.server_if; param.start.status = p_data->srvc_oper.status; - param.start.gatt_if = p_data->srvc_oper.server_if; param.start.service_handle = p_data->srvc_oper.service_id; - BTC_GATTS_CB_TO_APP(ESP_GATTS_START_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_START_EVT, gatts_if, ¶m); break; case BTA_GATTS_STOP_EVT: + gatts_if = p_data->srvc_oper.server_if; param.stop.status = p_data->srvc_oper.status; - param.stop.gatt_if = p_data->srvc_oper.server_if; param.stop.service_handle = p_data->srvc_oper.service_id; - BTC_GATTS_CB_TO_APP(ESP_GATTS_STOP_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_STOP_EVT, gatts_if, ¶m); break; case BTA_GATTS_CONNECT_EVT: - param.connect.conn_id = p_data->conn.conn_id; - param.connect.gatt_if = p_data->conn.server_if; + gatts_if = p_data->conn.server_if; + param.connect.conn_id = BTC_GATT_GET_CONN_ID(p_data->conn.conn_id); param.connect.is_connected = true; memcpy(param.connect.remote_bda, p_data->conn.remote_bda, ESP_BD_ADDR_LEN); - BTC_GATTS_CB_TO_APP(ESP_GATTS_CONNECT_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_CONNECT_EVT, gatts_if, ¶m); break; case BTA_GATTS_DISCONNECT_EVT: - param.connect.conn_id = p_data->conn.conn_id; - param.connect.gatt_if = p_data->conn.server_if; - param.connect.is_connected = false; - memcpy(param.connect.remote_bda, p_data->conn.remote_bda, ESP_BD_ADDR_LEN); + gatts_if = p_data->conn.server_if; + param.disconnect.conn_id = BTC_GATT_GET_CONN_ID(p_data->conn.conn_id); + param.disconnect.is_connected = false; + memcpy(param.disconnect.remote_bda, p_data->conn.remote_bda, ESP_BD_ADDR_LEN); - BTC_GATTS_CB_TO_APP(ESP_GATTS_DISCONNECT_EVT, ¶m); + BTC_GATTS_CB_TO_APP(ESP_GATTS_DISCONNECT_EVT, gatts_if, ¶m); break; case BTA_GATTS_OPEN_EVT: // do nothing @@ -422,8 +427,10 @@ void btc_gatts_cb_handler(btc_msg_t *msg) // do nothing break; case BTA_GATTS_CONGEST_EVT: - param.congest.conn_id = p_data->congest.conn_id; + gatts_if = BTC_GATT_GET_GATT_IF(p_data->congest.conn_id); + param.congest.conn_id = BTC_GATT_GET_CONN_ID(p_data->congest.conn_id); param.congest.congested = p_data->congest.congested; + BTC_GATTS_CB_TO_APP(ESP_GATTS_CONGEST_EVT, gatts_if, ¶m); break; default: // do nothing diff --git a/components/bt/bluedroid/btc/profile/std/gatt/include/btc_gatt_util.h b/components/bt/bluedroid/btc/profile/std/include/btc_gatt_util.h similarity index 80% rename from components/bt/bluedroid/btc/profile/std/gatt/include/btc_gatt_util.h rename to components/bt/bluedroid/btc/profile/std/include/btc_gatt_util.h index 540e118d59..99083f74f9 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/include/btc_gatt_util.h +++ b/components/bt/bluedroid/btc/profile/std/include/btc_gatt_util.h @@ -21,6 +21,10 @@ #include "esp_gatt_defs.h" #include "esp_gattc_api.h" +#define BTC_GATT_CREATE_CONN_ID(gatt_if, conn_id) ((uint16_t) ((((uint8_t)(conn_id)) << 8) | ((uint8_t)(gatt_if)))) +#define BTC_GATT_GET_CONN_ID(conn_id) (((uint16_t)(conn_id)) >> 8) +#define BTC_GATT_GET_GATT_IF(conn_id) ((uint8_t)(conn_id)) + void btc128_to_bta_uuid(tBT_UUID *p_dest, uint8_t *p_src); void btc_to_bta_uuid(tBT_UUID *p_dest, esp_bt_uuid_t *p_src); void btc_to_bta_gatt_id(tBTA_GATT_ID *p_dest, esp_gatt_id_t *p_src); @@ -31,6 +35,6 @@ void bta_to_btc_uuid(esp_bt_uuid_t *p_dest, tBT_UUID *p_src); void bta_to_btc_gatt_id(esp_gatt_id_t *p_dest, tBTA_GATT_ID *p_src); void bta_to_btc_srvc_id(esp_gatt_srvc_id_t *p_dest, tBTA_GATT_SRVC_ID *p_src); -uint16_t set_read_value(esp_ble_gattc_cb_param_t *p_dest, tBTA_GATTC_READ *p_src); +uint16_t set_read_value(uint8_t *gattc_if, esp_ble_gattc_cb_param_t *p_dest, tBTA_GATTC_READ *p_src); #endif /* __BTC_GATT_UTIL_H__*/ diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_gattc.h b/components/bt/bluedroid/btc/profile/std/include/btc_gattc.h index 4bca4ae5ab..aef8418957 100644 --- a/components/bt/bluedroid/btc/profile/std/include/btc_gattc.h +++ b/components/bt/bluedroid/btc/profile/std/include/btc_gattc.h @@ -51,11 +51,11 @@ typedef union { } app_reg; //BTC_GATTC_ACT_APP_UNREGISTER, struct app_unreg_arg { - esp_gatt_if_t gatt_if; + esp_gatt_if_t gattc_if; } app_unreg; //BTC_GATTC_ACT_OPEN, struct open_arg { - esp_gatt_if_t gatt_if; + esp_gatt_if_t gattc_if; esp_bd_addr_t remote_bda; bool is_direct; } open; @@ -162,14 +162,14 @@ typedef union { } exec_write; //BTC_GATTC_ACT_REG_FOR_NOTIFY, struct reg_for_notify_arg { - esp_gatt_if_t gatt_if; + esp_gatt_if_t gattc_if; esp_bd_addr_t remote_bda; esp_gatt_srvc_id_t service_id; esp_gatt_id_t char_id; } reg_for_notify; //BTC_GATTC_ACT_UNREG_FOR_NOTIFY struct unreg_for_notify_arg { - esp_gatt_if_t gatt_if; + esp_gatt_if_t gattc_if; esp_bd_addr_t remote_bda; esp_gatt_srvc_id_t service_id; esp_gatt_id_t char_id; diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_gatts.h b/components/bt/bluedroid/btc/profile/std/include/btc_gatts.h index 0ba0f8869a..4ad276f3ec 100644 --- a/components/bt/bluedroid/btc/profile/std/include/btc_gatts.h +++ b/components/bt/bluedroid/btc/profile/std/include/btc_gatts.h @@ -44,11 +44,11 @@ typedef union { } app_reg; //BTC_GATTS_ACT_APP_UNREGISTER, struct app_unreg_args { - esp_gatt_if_t gatt_if; + esp_gatt_if_t gatts_if; } app_unreg; //BTC_GATTS_ACT_CREATE_SERVICE, struct create_srvc_args { - esp_gatt_if_t gatt_if; + esp_gatt_if_t gatts_if; esp_gatt_srvc_id_t service_id; uint16_t num_handle; } create_srvc; @@ -99,7 +99,7 @@ typedef union { } send_rsp; //BTC_GATTS_ACT_OPEN, struct open_args { - esp_gatt_if_t gatt_if; + esp_gatt_if_t gatts_if; esp_bd_addr_t remote_bda; bool is_direct; } open; diff --git a/examples/12_blufi/components/blufi/blufi.c b/examples/12_blufi/components/blufi/blufi.c index 5a1c543b50..7d61fca928 100644 --- a/examples/12_blufi/components/blufi/blufi.c +++ b/examples/12_blufi/components/blufi/blufi.c @@ -69,7 +69,7 @@ static void blufi_data_recv(uint8_t *data, int len) } -static void blufi_callback(uint32_t event, void *param) +static void blufi_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param) { /* actually, should post to blufi_task handle the procedure, * now, as a demo, we do simplely */ @@ -90,11 +90,6 @@ static void blufi_callback(uint32_t event, void *param) static esp_err_t blufi_startup_in_blufi_task(void *arg) { - /*set connectable,discoverable, pairable and paired only modes of local device*/ - tBTA_DM_DISC disc_mode = BTA_DM_BLE_GENERAL_DISCOVERABLE; - tBTA_DM_CONN conn_mode = BTA_DM_BLE_CONNECTABLE; - BTA_DmSetVisibility(disc_mode, conn_mode, (uint8_t)BTA_DM_NON_PAIRABLE, (uint8_t)BTA_DM_CONN_ALL); - esp_blufi_register_callback(blufi_callback); esp_blufi_profile_init(); @@ -111,8 +106,6 @@ esp_err_t blufi_enable(void *arg) { esp_err_t err; - BTM_SetTraceLevel(BT_TRACE_LEVEL_ERROR); - err = esp_enable_bluetooth(); if (err) { LOG_ERROR("%s failed\n", __func__); diff --git a/examples/14_gatt_server/main/gatts_demo.c b/examples/14_gatt_server/main/gatts_demo.c index 48576d362f..64e84fd378 100644 --- a/examples/14_gatt_server/main/gatts_demo.c +++ b/examples/14_gatt_server/main/gatts_demo.c @@ -2,7 +2,7 @@ // // 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 +// You may obtain A copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // @@ -30,6 +30,9 @@ #include "esp_bt_main.h" #include "esp_bt_main.h" +///Declare the static function +static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *parama); + #define GATTS_SERVICE_UUID_TEST 0x00FF #define GATTS_CHAR_UUID_TEST 0xFF01 #define GATTS_DESCR_UUID_TEST 0x3333 @@ -74,8 +77,13 @@ static esp_ble_adv_params_t test_adv_params = { .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, }; -struct gatts_test_inst { - uint16_t gatt_if; +#define PROFILE_NUM 2 +#define PROFILE_A_APP_ID 0 +#define PROFILE_B_APP_ID 1 + +struct gatts_profile_inst { + esp_gatts_cb_t gatts_cb; + uint16_t gatts_if; uint16_t app_id; uint16_t conn_id; uint16_t service_handle; @@ -87,9 +95,20 @@ struct gatts_test_inst { uint16_t descr_handle; esp_bt_uuid_t descr_uuid; }; -static struct gatts_test_inst gl_test; -static void gap_event_handler(uint32_t event, void *param) +/* 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 gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gatts_cb = gatts_profile_a_event_handler, + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + [PROFILE_B_APP_ID] = { + .gatts_cb = NULL, /* This demo does not implement, similar as profile A */ + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { LOG_ERROR("GAP_EVT, event %d\n", event); @@ -102,42 +121,42 @@ static void gap_event_handler(uint32_t event, void *param) } } -static void gatts_event_handler(uint32_t event, void *param) -{ - esp_ble_gatts_cb_param_t *p = (esp_ble_gatts_cb_param_t *)param; - +static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { switch (event) { case ESP_GATTS_REG_EVT: - LOG_INFO("REGISTER_APP_EVT, status %d, gatt_if %d, app_id %d\n", p->reg.status, p->reg.gatt_if, p->reg.app_id); - gl_test.gatt_if = p->reg.gatt_if; - gl_test.service_id.is_primary = true; - gl_test.service_id.id.inst_id = 0x00; - gl_test.service_id.id.uuid.len = ESP_UUID_LEN_16; - gl_test.service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST; + LOG_INFO("REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST; + LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gap_set_device_name(TEST_DEVICE_NAME); + LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gap_config_adv_data(&test_adv_data); - esp_ble_gatts_create_service(gl_test.gatt_if, &gl_test.service_id, GATTS_NUM_HANDLE_TEST); + LOG_INFO("%s %d\n", __func__, __LINE__); + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST); + LOG_INFO("%s %d\n", __func__, __LINE__); break; case ESP_GATTS_READ_EVT: { - LOG_INFO("GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", p->read.conn_id, p->read.trans_id, p->read.handle); + LOG_INFO("GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); esp_gatt_rsp_t rsp; memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); - rsp.attr_value.handle = p->read.handle; + rsp.attr_value.handle = param->read.handle; rsp.attr_value.len = 4; rsp.attr_value.value[0] = 0xde; rsp.attr_value.value[1] = 0xed; rsp.attr_value.value[2] = 0xbe; rsp.attr_value.value[3] = 0xef; - esp_ble_gatts_send_response(p->read.conn_id, p->read.trans_id, + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp); break; } case ESP_GATTS_WRITE_EVT: { - LOG_INFO("GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", p->write.conn_id, p->write.trans_id, p->write.handle); - LOG_INFO("GATT_WRITE_EVT, value len %d, value %08x\n", p->write.len, *(uint32_t *)p->write.value); - esp_ble_gatts_send_response(p->write.conn_id, p->write.trans_id, ESP_GATT_OK, NULL); + LOG_INFO("GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + LOG_INFO("GATT_WRITE_EVT, value len %d, value %08x\n", param->write.len, *(uint32_t *)param->write.value); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); break; } case ESP_GATTS_EXEC_WRITE_EVT: @@ -146,48 +165,48 @@ static void gatts_event_handler(uint32_t event, void *param) case ESP_GATTS_UNREG_EVT: break; case ESP_GATTS_CREATE_EVT: - LOG_INFO("CREATE_SERVICE_EVT, status %d, gatt_if %d, service_handle %d\n", p->create.status, p->create.gatt_if, p->create.service_handle); - gl_test.service_handle = p->create.service_handle; - gl_test.char_uuid.len = ESP_UUID_LEN_16; - gl_test.char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST; + LOG_INFO("CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + 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; - esp_ble_gatts_start_service(gl_test.service_handle); + esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle); - esp_ble_gatts_add_char(gl_test.service_handle, &gl_test.char_uuid, + esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY); break; case ESP_GATTS_ADD_INCL_SRVC_EVT: break; case ESP_GATTS_ADD_CHAR_EVT: - LOG_INFO("ADD_CHAR_EVT, status %d, gatt_if %d, attr_handle %d, service_handle %d\n", - p->add_char.status, p->add_char.gatt_if, p->add_char.attr_handle, p->add_char.service_handle); + LOG_INFO("ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); - gl_test.char_handle = p->add_char.attr_handle; - gl_test.descr_uuid.len = ESP_UUID_LEN_16; - gl_test.descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; - esp_ble_gatts_add_char_descr(gl_test.service_handle, &gl_test.descr_uuid, + gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); break; case ESP_GATTS_ADD_CHAR_DESCR_EVT: - LOG_INFO("ADD_DESCR_EVT, status %d, gatt_if %d, attr_handle %d, service_handle %d\n", - p->add_char.status, p->add_char.gatt_if, p->add_char.attr_handle, p->add_char.service_handle); + LOG_INFO("ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); break; case ESP_GATTS_DELETE_EVT: break; case ESP_GATTS_START_EVT: - LOG_INFO("SERVICE_START_EVT, status %d, gatt_if %d, service_handle %d\n", - p->start.status, p->start.gatt_if, p->start.service_handle); + LOG_INFO("SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); break; case ESP_GATTS_STOP_EVT: break; case ESP_GATTS_CONNECT_EVT: - LOG_INFO("SERVICE_START_EVT, conn_id %d, gatt_if %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n", - p->connect.conn_id, p->connect.gatt_if, - p->connect.remote_bda[0], p->connect.remote_bda[1], p->connect.remote_bda[2], - p->connect.remote_bda[3], p->connect.remote_bda[4], p->connect.remote_bda[5], - p->connect.is_connected); - gl_test.conn_id = p->connect.conn_id; + LOG_INFO("SERVICE_START_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n", + param->connect.conn_id, + param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], + param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5], + param->connect.is_connected); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id; break; case ESP_GATTS_DISCONNECT_EVT: case ESP_GATTS_OPEN_EVT: @@ -200,6 +219,37 @@ static void gatts_event_handler(uint32_t event, void *param) } } +static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + LOG_INFO("EVT %d, gatts if %d\n", event, gatts_if); + + /* If event is register event, store the gatts_if for each profile */ + if (event == ESP_GATTS_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gatts_if = gatts_if; + } else { + LOG_INFO("Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gatts_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < 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 == gl_profile_tab[idx].gatts_if) { + if (gl_profile_tab[idx].gatts_cb) { + gl_profile_tab[idx].gatts_cb(event, gatts_if, param); + } + } + } + } while (0); +} + void app_main() { esp_err_t ret; @@ -219,7 +269,7 @@ void app_main() esp_ble_gatts_register_callback(gatts_event_handler); esp_ble_gap_register_callback(gap_event_handler); - esp_ble_gatts_app_register(GATTS_SERVICE_UUID_TEST); + esp_ble_gatts_app_register(PROFILE_A_APP_ID); return; } diff --git a/examples/15_gatt_client/main/gattc_demo.c b/examples/15_gatt_client/main/gattc_demo.c index 3dc6af6d75..f1e7c311c0 100644 --- a/examples/15_gatt_client/main/gattc_demo.c +++ b/examples/15_gatt_client/main/gattc_demo.c @@ -37,16 +37,17 @@ #include "esp_gatt_defs.h" #include "esp_bt_main.h" +///Declare static functions +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + #define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" #define BT_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] -esp_gatt_if_t client_if; -esp_gatt_status_t status = ESP_GATT_ERROR; -bool connet = false; -uint16_t simpleClient_id = 0xEE; - -const char device_name[] = "Heart Rate"; +static bool connect = false; +static const char device_name[] = "Heart Rate"; static esp_ble_scan_params_t ble_scan_params = { .scan_type = BLE_SCAN_TYPE_ACTIVE, @@ -57,11 +58,74 @@ static esp_ble_scan_params_t ble_scan_params = { }; -static void esp_gap_cb(uint32_t event, void *param); +#define PROFILE_NUM 2 +#define PROFILE_A_APP_ID 0 +#define PROFILE_B_APP_ID 1 -static void esp_gattc_cb(uint32_t event, void *param); +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; +}; -static void esp_gap_cb(uint32_t event, void *param) +/* 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_a_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + [PROFILE_B_APP_ID] = { + .gattc_cb = NULL, /* This demo does not implement, similar as profile A */ + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + uint16_t conn_id = 0; + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + LOG_INFO("REG_EVT\n"); + esp_ble_gap_set_scan_params(&ble_scan_params); + break; + case ESP_GATTC_OPEN_EVT: + conn_id = p_data->open.conn_id; + LOG_INFO("ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d\n", conn_id, gattc_if, p_data->open.status); + esp_ble_gattc_search_service(gattc_if, conn_id, NULL); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + esp_gatt_srvc_id_t *srvc_id = &p_data->search_res.srvc_id; + conn_id = p_data->open.conn_id; + LOG_INFO("SEARCH RES: conn_id = %x\n", conn_id); + if (srvc_id->id.uuid.len == ESP_UUID_LEN_16) { + LOG_INFO("UUID16: %x\n", srvc_id->id.uuid.uuid.uuid16); + } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_32) { + LOG_INFO("UUID32: %x\n", srvc_id->id.uuid.uuid.uuid32); + } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_128) { + LOG_INFO("UUID128: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", srvc_id->id.uuid.uuid.uuid128[0], + srvc_id->id.uuid.uuid.uuid128[1], srvc_id->id.uuid.uuid.uuid128[2], srvc_id->id.uuid.uuid.uuid128[3], + srvc_id->id.uuid.uuid.uuid128[4], srvc_id->id.uuid.uuid.uuid128[5], srvc_id->id.uuid.uuid.uuid128[6], + srvc_id->id.uuid.uuid.uuid128[7], srvc_id->id.uuid.uuid.uuid128[8], srvc_id->id.uuid.uuid.uuid128[9], + srvc_id->id.uuid.uuid.uuid128[10], srvc_id->id.uuid.uuid.uuid128[11], srvc_id->id.uuid.uuid.uuid128[12], + srvc_id->id.uuid.uuid.uuid128[13], srvc_id->id.uuid.uuid.uuid128[14], srvc_id->id.uuid.uuid.uuid128[15]); + } else { + LOG_ERROR("UNKNOWN LEN %d\n", srvc_id->id.uuid.len); + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + conn_id = p_data->search_cmpl.conn_id; + LOG_INFO("SEARCH_CMPL: conn_id = %x, status %d\n", conn_id, p_data->search_cmpl.status); + break; + default: + break; + } +} + +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { uint8_t *adv_name = NULL; uint8_t adv_name_len = 0; @@ -94,12 +158,12 @@ static void esp_gap_cb(uint32_t event, void *param) if (adv_name != NULL) { if (strcmp((char *)adv_name, device_name) == 0) { - LOG_INFO("the name eque to Heart Rate.\n"); - if (status == ESP_GATT_OK && connet == false) { - connet = true; - LOG_INFO("Connet to the remote device.\n"); + LOG_INFO("the name equal to Heart Rate\n"); + if (connect == false) { + connect = true; + LOG_INFO("Connect to the remote device.\n"); esp_ble_gap_stop_scanning(); - esp_ble_gattc_open(client_if, scan_result->scan_rst.bda, true); + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, true); } } } @@ -116,55 +180,41 @@ static void esp_gap_cb(uint32_t event, void *param) } } - -static void esp_gattc_cb(uint32_t event, void *param) +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { - uint16_t conn_id = 0; - esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + LOG_INFO("EVT %d, gattc if %d\n", event, gattc_if); - LOG_INFO("esp_gattc_cb, event = %x\n", event); - switch (event) { - case ESP_GATTC_REG_EVT: - status = p_data->reg.status; - client_if = p_data->reg.gatt_if; - LOG_INFO("status = %x, client_if = %x\n", status, client_if); - break; - case ESP_GATTC_OPEN_EVT: - conn_id = p_data->open.conn_id; - LOG_INFO("ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d\n", conn_id, p_data->open.gatt_if, p_data->open.status); - esp_ble_gattc_search_service(conn_id, NULL); - break; - case ESP_GATTC_SEARCH_RES_EVT: { - esp_gatt_srvc_id_t *srvc_id = &p_data->search_res.srvc_id; - conn_id = p_data->open.conn_id; - LOG_INFO("SEARCH RES: conn_id = %x\n", conn_id); - if (srvc_id->id.uuid.len == ESP_UUID_LEN_16) { - LOG_INFO("UUID16: %x\n", srvc_id->id.uuid.uuid.uuid16); - } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_32) { - LOG_INFO("UUID32: %x\n", srvc_id->id.uuid.uuid.uuid32); - } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_128) { - LOG_INFO("UUID128: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", srvc_id->id.uuid.uuid.uuid128[0], - srvc_id->id.uuid.uuid.uuid128[1], srvc_id->id.uuid.uuid.uuid128[2], srvc_id->id.uuid.uuid.uuid128[3], - srvc_id->id.uuid.uuid.uuid128[4], srvc_id->id.uuid.uuid.uuid128[5], srvc_id->id.uuid.uuid.uuid128[6], - srvc_id->id.uuid.uuid.uuid128[7], srvc_id->id.uuid.uuid.uuid128[8], srvc_id->id.uuid.uuid.uuid128[9], - srvc_id->id.uuid.uuid.uuid128[10], srvc_id->id.uuid.uuid.uuid128[11], srvc_id->id.uuid.uuid.uuid128[12], - srvc_id->id.uuid.uuid.uuid128[13], srvc_id->id.uuid.uuid.uuid128[14], srvc_id->id.uuid.uuid.uuid128[15]); + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; } else { - LOG_ERROR("UNKNOWN LEN %d\n", srvc_id->id.uuid.len); + LOG_INFO("Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; } - break; - } - case ESP_GATTC_SEARCH_CMPL_EVT: - conn_id = p_data->search_cmpl.conn_id; - LOG_INFO("SEARCH_CMPL: conn_id = %x, status %d\n", conn_id, p_data->search_cmpl.status); - break; - default: - break; } + + /* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gl_profile_tab[idx].gattc_if) { + if (gl_profile_tab[idx].gattc_cb) { + gl_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); } void ble_client_appRegister(void) { + esp_err_t status; + LOG_INFO("register callback\n"); //register the scan callback function to the gap moudule @@ -178,8 +228,7 @@ void ble_client_appRegister(void) LOG_ERROR("gattc register error, error code = %x\n", status); return; } - esp_ble_gattc_app_register(simpleClient_id); - esp_ble_gap_set_scan_params(&ble_scan_params); + esp_ble_gattc_app_register(PROFILE_A_APP_ID); } void gattc_client_test(void) From 65fe6ab320c95da79b3311ea92006c094399a481 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Mon, 19 Dec 2016 17:12:43 +0800 Subject: [PATCH 003/167] component/bt : modify demo for new api 1. modify GATT Client demo 2. modify GATT Server demo 3. fix a register notify bug --- .../btc/profile/std/gatt/btc_gattc.c | 2 +- examples/14_gatt_server/main/gatts_demo.c | 126 +++++++++++-- examples/15_gatt_client/main/gattc_demo.c | 168 +++++++++++++++++- 3 files changed, 280 insertions(+), 16 deletions(-) diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c index b7df92fcfa..9bc7b0d2bd 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c @@ -422,7 +422,7 @@ static void btc_gattc_reg_for_notify(btc_ble_gattc_args_t *arg) memset(¶m, 0, sizeof(esp_ble_gattc_cb_param_t)); param.reg_for_notify.status = status; memcpy(¶m.reg_for_notify.srvc_id, &arg->reg_for_notify.service_id, sizeof(esp_gatt_srvc_id_t)); - memcpy(¶m.reg_for_notify.char_id, &arg->reg_for_notify.service_id, sizeof(esp_gatt_id_t)); + memcpy(¶m.reg_for_notify.char_id, &arg->reg_for_notify.char_id, sizeof(esp_gatt_id_t)); BTC_GATTC_CB_TO_APP(ESP_GATTC_REG_FOR_NOTIFY_EVT, arg->reg_for_notify.gattc_if, ¶m); } diff --git a/examples/14_gatt_server/main/gatts_demo.c b/examples/14_gatt_server/main/gatts_demo.c index 64e84fd378..1785835d7c 100644 --- a/examples/14_gatt_server/main/gatts_demo.c +++ b/examples/14_gatt_server/main/gatts_demo.c @@ -31,15 +31,20 @@ #include "esp_bt_main.h" ///Declare the static function -static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *parama); +static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + +#define GATTS_SERVICE_UUID_TEST_A 0x00FF +#define GATTS_CHAR_UUID_TEST_A 0xFF01 +#define GATTS_DESCR_UUID_TEST_A 0x3333 +#define GATTS_NUM_HANDLE_TEST_A 4 + +#define GATTS_SERVICE_UUID_TEST_B 0x00EE +#define GATTS_CHAR_UUID_TEST_B 0xEE01 +#define GATTS_DESCR_UUID_TEST_B 0x2222 +#define GATTS_NUM_HANDLE_TEST_B 4 -#define GATTS_SERVICE_UUID_TEST 0x00FF -#define GATTS_CHAR_UUID_TEST 0xFF01 -#define GATTS_DESCR_UUID_TEST 0x3333 -#define APP_ID_TEST 0x18 -#define GATTS_NUM_HANDLE_TEST 4 #define TEST_DEVICE_NAME "ESP_GATTS_DEMO" - #define TEST_MANUFACTURER_DATA_LEN 17 static uint8_t test_service_uuid128[32] = { /* LSB <--------------------------------------------------------------------------------> MSB */ @@ -103,7 +108,7 @@ static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = { .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ }, [PROFILE_B_APP_ID] = { - .gatts_cb = NULL, /* This demo does not implement, similar as profile A */ + .gatts_cb = gatts_profile_b_event_handler, /* This demo does not implement, similar as profile A */ .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ }, }; @@ -128,7 +133,7 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; - gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A; LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gap_set_device_name(TEST_DEVICE_NAME); @@ -136,7 +141,7 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i esp_ble_gap_config_adv_data(&test_adv_data); LOG_INFO("%s %d\n", __func__, __LINE__); - esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST); + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A); LOG_INFO("%s %d\n", __func__, __LINE__); break; case ESP_GATTS_READ_EVT: { @@ -168,7 +173,105 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i LOG_INFO("CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); 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; + 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); + + esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + break; + case ESP_GATTS_ADD_INCL_SRVC_EVT: + break; + case ESP_GATTS_ADD_CHAR_EVT: + LOG_INFO("ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + + gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); + break; + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + LOG_INFO("ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + LOG_INFO("SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: + LOG_INFO("SERVICE_START_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n", + param->connect.conn_id, + param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], + param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5], + param->connect.is_connected); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id; + break; + case ESP_GATTS_DISCONNECT_EVT: + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + default: + break; + } +} + +static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { + switch (event) { + case ESP_GATTS_REG_EVT: + LOG_INFO("REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B; + + LOG_INFO("%s %d\n", __func__, __LINE__); + esp_ble_gap_set_device_name(TEST_DEVICE_NAME); + LOG_INFO("%s %d\n", __func__, __LINE__); + esp_ble_gap_config_adv_data(&test_adv_data); + + LOG_INFO("%s %d\n", __func__, __LINE__); + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B); + LOG_INFO("%s %d\n", __func__, __LINE__); + break; + case ESP_GATTS_READ_EVT: { + LOG_INFO("GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } + case ESP_GATTS_WRITE_EVT: { + LOG_INFO("GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + LOG_INFO("GATT_WRITE_EVT, value len %d, value %08x\n", param->write.len, *(uint32_t *)param->write.value); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: + case ESP_GATTS_MTU_EVT: + case ESP_GATTS_CONF_EVT: + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_CREATE_EVT: + LOG_INFO("CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + 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_B; esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle); @@ -270,6 +373,7 @@ void app_main() esp_ble_gatts_register_callback(gatts_event_handler); esp_ble_gap_register_callback(gap_event_handler); esp_ble_gatts_app_register(PROFILE_A_APP_ID); + esp_ble_gatts_app_register(PROFILE_B_APP_ID); return; } diff --git a/examples/15_gatt_client/main/gattc_demo.c b/examples/15_gatt_client/main/gattc_demo.c index f1e7c311c0..50faaa7c98 100644 --- a/examples/15_gatt_client/main/gattc_demo.c +++ b/examples/15_gatt_client/main/gattc_demo.c @@ -41,13 +41,32 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static esp_gatt_srvc_id_t alert_service_id = { + .id = { + .uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = 0x1811,}, + }, + .inst_id = 0, + }, + .is_primary = true, +}; + +static esp_gatt_id_t notify_descr_id = { + .uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = GATT_UUID_CHAR_CLIENT_CONFIG,}, + }, + .inst_id = 0, +}; #define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" #define BT_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] static bool connect = false; -static const char device_name[] = "Heart Rate"; +static const char device_name[] = "Alert Notification"; static esp_ble_scan_params_t ble_scan_params = { .scan_type = BLE_SCAN_TYPE_ACTIVE, @@ -67,6 +86,7 @@ struct gattc_profile_inst { uint16_t gattc_if; uint16_t app_id; uint16_t conn_id; + esp_bd_addr_t remote_bda; }; /* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ @@ -76,7 +96,7 @@ static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ }, [PROFILE_B_APP_ID] = { - .gattc_cb = NULL, /* This demo does not implement, similar as profile A */ + .gattc_cb = gattc_profile_b_event_handler, .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ }, }; @@ -93,12 +113,21 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i break; case ESP_GATTC_OPEN_EVT: conn_id = p_data->open.conn_id; - LOG_INFO("ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d\n", conn_id, gattc_if, p_data->open.status); + + memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); + LOG_INFO("ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d\n", conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + + LOG_INFO("REMOTE BDA %02x:%02x:%02x:%02x:%02x:%02x\n", + gl_profile_tab[PROFILE_A_APP_ID].remote_bda[0], gl_profile_tab[PROFILE_A_APP_ID].remote_bda[1], + gl_profile_tab[PROFILE_A_APP_ID].remote_bda[2], gl_profile_tab[PROFILE_A_APP_ID].remote_bda[3], + gl_profile_tab[PROFILE_A_APP_ID].remote_bda[4], gl_profile_tab[PROFILE_A_APP_ID].remote_bda[5] + ); + esp_ble_gattc_search_service(gattc_if, conn_id, NULL); break; case ESP_GATTC_SEARCH_RES_EVT: { esp_gatt_srvc_id_t *srvc_id = &p_data->search_res.srvc_id; - conn_id = p_data->open.conn_id; + conn_id = p_data->search_res.conn_id; LOG_INFO("SEARCH RES: conn_id = %x\n", conn_id); if (srvc_id->id.uuid.len == ESP_UUID_LEN_16) { LOG_INFO("UUID16: %x\n", srvc_id->id.uuid.uuid.uuid16); @@ -119,6 +148,135 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SEARCH_CMPL_EVT: conn_id = p_data->search_cmpl.conn_id; LOG_INFO("SEARCH_CMPL: conn_id = %x, status %d\n", conn_id, p_data->search_cmpl.status); + esp_ble_gattc_get_characteristic(gattc_if, conn_id, &alert_service_id, NULL); + break; + case ESP_GATTC_GET_CHAR_EVT: + if (p_data->get_char.status != ESP_GATT_OK) { + break; + } + LOG_INFO("GET CHAR: conn_id = %x, status %d\n", p_data->get_char.conn_id, p_data->get_char.status); + LOG_INFO("GET CHAR: srvc_id = %04x, char_id = %04x\n", p_data->get_char.srvc_id.id.uuid.uuid.uuid16, p_data->get_char.char_id.uuid.uuid.uuid16); + + if (p_data->get_char.char_id.uuid.uuid.uuid16 == 0x2a46) { + LOG_INFO("register notify\n"); + esp_ble_gattc_register_for_notify(gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, &alert_service_id, &p_data->get_char.char_id); + } + + esp_ble_gattc_get_characteristic(gattc_if, conn_id, &alert_service_id, &p_data->get_char.char_id); + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + uint16_t notify_en = 1; + LOG_INFO("REG FOR NOTIFY: status %d\n", p_data->reg_for_notify.status); + LOG_INFO("REG FOR_NOTIFY: srvc_id = %04x, char_id = %04x\n", p_data->reg_for_notify.srvc_id.id.uuid.uuid.uuid16, p_data->reg_for_notify.char_id.uuid.uuid.uuid16); + + esp_ble_gattc_write_char_descr( + gattc_if, + conn_id, + &alert_service_id, + &p_data->reg_for_notify.char_id, + ¬ify_descr_id, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + } + case ESP_GATTC_NOTIFY_EVT: + LOG_INFO("NOTIFY: len %d, value %08x\n", p_data->notify.value_len, *(uint32_t *)p_data->notify.value); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + LOG_INFO("WRITE: status %d\n", p_data->write.status); + break; + default: + break; + } +} + +static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + uint16_t conn_id = 0; + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + LOG_INFO("REG_EVT\n"); + //esp_ble_gap_set_scan_params(&ble_scan_params); + break; + case ESP_GATTC_OPEN_EVT: + conn_id = p_data->open.conn_id; + + memcpy(gl_profile_tab[PROFILE_B_APP_ID].remote_bda, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); + LOG_INFO("ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d\n", conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + + LOG_INFO("REMOTE BDA %02x:%02x:%02x:%02x:%02x:%02x\n", + gl_profile_tab[PROFILE_B_APP_ID].remote_bda[0], gl_profile_tab[PROFILE_B_APP_ID].remote_bda[1], + gl_profile_tab[PROFILE_B_APP_ID].remote_bda[2], gl_profile_tab[PROFILE_B_APP_ID].remote_bda[3], + gl_profile_tab[PROFILE_B_APP_ID].remote_bda[4], gl_profile_tab[PROFILE_B_APP_ID].remote_bda[5] + ); + + esp_ble_gattc_search_service(gattc_if, conn_id, NULL); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + esp_gatt_srvc_id_t *srvc_id = &p_data->search_res.srvc_id; + conn_id = p_data->search_res.conn_id; + LOG_INFO("SEARCH RES: conn_id = %x\n", conn_id); + if (srvc_id->id.uuid.len == ESP_UUID_LEN_16) { + LOG_INFO("UUID16: %x\n", srvc_id->id.uuid.uuid.uuid16); + } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_32) { + LOG_INFO("UUID32: %x\n", srvc_id->id.uuid.uuid.uuid32); + } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_128) { + LOG_INFO("UUID128: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", srvc_id->id.uuid.uuid.uuid128[0], + srvc_id->id.uuid.uuid.uuid128[1], srvc_id->id.uuid.uuid.uuid128[2], srvc_id->id.uuid.uuid.uuid128[3], + srvc_id->id.uuid.uuid.uuid128[4], srvc_id->id.uuid.uuid.uuid128[5], srvc_id->id.uuid.uuid.uuid128[6], + srvc_id->id.uuid.uuid.uuid128[7], srvc_id->id.uuid.uuid.uuid128[8], srvc_id->id.uuid.uuid.uuid128[9], + srvc_id->id.uuid.uuid.uuid128[10], srvc_id->id.uuid.uuid.uuid128[11], srvc_id->id.uuid.uuid.uuid128[12], + srvc_id->id.uuid.uuid.uuid128[13], srvc_id->id.uuid.uuid.uuid128[14], srvc_id->id.uuid.uuid.uuid128[15]); + } else { + LOG_ERROR("UNKNOWN LEN %d\n", srvc_id->id.uuid.len); + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + conn_id = p_data->search_cmpl.conn_id; + LOG_INFO("SEARCH_CMPL: conn_id = %x, status %d\n", conn_id, p_data->search_cmpl.status); + esp_ble_gattc_get_characteristic(gattc_if, conn_id, &alert_service_id, NULL); + break; + case ESP_GATTC_GET_CHAR_EVT: + if (p_data->get_char.status != ESP_GATT_OK) { + break; + } + LOG_INFO("GET CHAR: conn_id = %x, status %d\n", p_data->get_char.conn_id, p_data->get_char.status); + LOG_INFO("GET CHAR: srvc_id = %04x, char_id = %04x\n", p_data->get_char.srvc_id.id.uuid.uuid.uuid16, p_data->get_char.char_id.uuid.uuid.uuid16); + + if (p_data->get_char.char_id.uuid.uuid.uuid16 == 0x2a46) { + LOG_INFO("register notify\n"); + esp_ble_gattc_register_for_notify(gattc_if, gl_profile_tab[PROFILE_B_APP_ID].remote_bda, &alert_service_id, &p_data->get_char.char_id); + } + + esp_ble_gattc_get_characteristic(gattc_if, conn_id, &alert_service_id, &p_data->get_char.char_id); + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + uint16_t notify_en = 1; + LOG_INFO("REG FOR NOTIFY: status %d\n", p_data->reg_for_notify.status); + LOG_INFO("REG FOR_NOTIFY: srvc_id = %04x, char_id = %04x\n", p_data->reg_for_notify.srvc_id.id.uuid.uuid.uuid16, p_data->reg_for_notify.char_id.uuid.uuid.uuid16); + + esp_ble_gattc_write_char_descr( + gattc_if, + conn_id, + &alert_service_id, + &p_data->reg_for_notify.char_id, + ¬ify_descr_id, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + } + case ESP_GATTC_NOTIFY_EVT: + LOG_INFO("NOTIFY: len %d, value %08x\n", p_data->notify.value_len, *(uint32_t *)p_data->notify.value); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + LOG_INFO("WRITE: status %d\n", p_data->write.status); break; default: break; @@ -164,6 +322,7 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par LOG_INFO("Connect to the remote device.\n"); esp_ble_gap_stop_scanning(); esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, true); + esp_ble_gattc_open(gl_profile_tab[PROFILE_B_APP_ID].gattc_if, scan_result->scan_rst.bda, true); } } } @@ -229,6 +388,7 @@ void ble_client_appRegister(void) return; } esp_ble_gattc_app_register(PROFILE_A_APP_ID); + esp_ble_gattc_app_register(PROFILE_B_APP_ID); } void gattc_client_test(void) From 2cffaf9cc841cbc5d2b495a326a2ba8bd3930d1b Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Thu, 22 Dec 2016 10:17:39 +0800 Subject: [PATCH 004/167] freertos: fix dual core issue This commit fixes: 1. xTaskGetCurrentTaskHandle may return wrong TCB when current task switch to a different core 2. Idle task may have problem when it terminate the task in both core --- components/freertos/tasks.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 16cce3b967..baaccb43e5 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -3496,26 +3496,27 @@ static void prvCheckTasksWaitingTermination( void ) /* ucTasksDeleted is used to prevent vTaskSuspendAll() being called too often in the idle task. */ + taskENTER_CRITICAL(&xTaskQueueMutex); while( uxTasksDeleted > ( UBaseType_t ) 0U ) { - taskENTER_CRITICAL(&xTaskQueueMutex); + //taskENTER_CRITICAL(&xTaskQueueMutex); { xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination ); } - taskEXIT_CRITICAL(&xTaskQueueMutex); + //taskEXIT_CRITICAL(&xTaskQueueMutex); if( xListIsEmpty == pdFALSE ) { TCB_t *pxTCB; - taskENTER_CRITICAL(&xTaskQueueMutex); + //taskENTER_CRITICAL(&xTaskQueueMutex); { pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) ); ( void ) uxListRemove( &( pxTCB->xGenericListItem ) ); --uxCurrentNumberOfTasks; --uxTasksDeleted; } - taskEXIT_CRITICAL(&xTaskQueueMutex); + //taskEXIT_CRITICAL(&xTaskQueueMutex); #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS ) { @@ -3535,7 +3536,8 @@ static void prvCheckTasksWaitingTermination( void ) { mtCOVERAGE_TEST_MARKER(); } - } + } + taskEXIT_CRITICAL(&xTaskQueueMutex); } #endif /* vTaskDelete */ } @@ -3806,10 +3808,12 @@ TCB_t *pxTCB; { TaskHandle_t xReturn; + vPortCPUAcquireMutex(&xTaskQueueMutex); /* A critical section is not required as this is not called from an interrupt and the current TCB will always be the same for any individual execution thread. */ xReturn = pxCurrentTCB[ xPortGetCoreID() ]; + vPortCPUReleaseMutex(&xTaskQueueMutex); return xReturn; } From 57817f7c53e4f4de41d5829f1a272f1e41e1406e Mon Sep 17 00:00:00 2001 From: Yinling Date: Thu, 24 Nov 2016 11:46:58 +0800 Subject: [PATCH 005/167] generate test result and commit to CI-test-result: 1. config git user name before commit 2. continue committing test result for failed jobs 3. update test result repository path 4. change escape key word in branch name, use '___' to escape key word '/' in report file name --- .gitlab-ci.yml | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7cb63be8c3..20970597e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -149,11 +149,12 @@ test_build_system: test_report: stage: test_report + image: espressif/esp32-ci-env only: - master - triggers tags: - - test_report + - report variables: LOG_PATH: "$CI_PROJECT_DIR/$CI_BUILD_REF" TEST_CASE_FILE_PATH: "$CI_PROJECT_DIR/components/idf_test" @@ -165,12 +166,33 @@ test_report: - $LOG_PATH expire_in: 12 mos script: + # calc log path + - VER_NUM=`git rev-list HEAD | wc -l | awk '{print $1}'` + - SHA_ID=`echo $CI_BUILD_REF | cut -c 1-7` + - REVISION="${VER_NUM}_${SHA_ID}" + # replace / to _ in branch name + - ESCAPED_BRANCH_NAME=`echo $CI_BUILD_REF_NAME | sed 's/\//___/g'` + # result path and artifacts path + - RESULT_PATH="$CI_PROJECT_NAME/$ESCAPED_BRANCH_NAME/$REVISION" + - ARTIFACTS_PATH="$GITLAB_HTTP_SERVER/idf/esp-idf/builds/$CI_BUILD_ID/artifacts/browse/$CI_BUILD_REF" # clone test bench - git clone $GITLAB_SSH_SERVER/yinling/auto_test_script.git - cd auto_test_script # generate report - - python CITestReport.py -l $LOG_PATH -t $TEST_CASE_FILE_PATH -p $REPORT_PATH - + - python CITestReport.py -l $LOG_PATH -t $TEST_CASE_FILE_PATH -p $REPORT_PATH -r $RESULT_PATH -a $ARTIFACTS_PATH || FAIL=True + # commit to CI-test-result project + - git clone $GITLAB_SSH_SERVER/qa/CI-test-result.git + - rm -rf CI-test-result/RawData/$RESULT_PATH + - cp -R $CI_PROJECT_NAME CI-test-result/RawData + - cd CI-test-result + # config git user + - git config --global user.email "ci-test-result@espressif.com" + - git config --global user.name "ci-test-result" + # commit test result + - git add . + - git commit . -m "update test result for $CI_PROJECT_NAME/$CI_BUILD_REF_NAME/$CI_BUILD_REF, pipeline ID $CI_PIPELINE_ID" || exit 0 + - git push origin master + - test "${FAIL}" = "True" && exit 1 push_master_to_github: before_script: From 3a2fbda35c7bb0d1cbc99a59a6db32258be549f1 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Thu, 22 Dec 2016 10:51:40 +0800 Subject: [PATCH 006/167] freertos: minor change according to review comments --- components/freertos/tasks.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index baaccb43e5..f2bdf8ccb0 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -3499,24 +3499,20 @@ static void prvCheckTasksWaitingTermination( void ) taskENTER_CRITICAL(&xTaskQueueMutex); while( uxTasksDeleted > ( UBaseType_t ) 0U ) { - //taskENTER_CRITICAL(&xTaskQueueMutex); { xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination ); } - //taskEXIT_CRITICAL(&xTaskQueueMutex); if( xListIsEmpty == pdFALSE ) { TCB_t *pxTCB; - //taskENTER_CRITICAL(&xTaskQueueMutex); { pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) ); ( void ) uxListRemove( &( pxTCB->xGenericListItem ) ); --uxCurrentNumberOfTasks; --uxTasksDeleted; } - //taskEXIT_CRITICAL(&xTaskQueueMutex); #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS ) { @@ -3809,9 +3805,6 @@ TCB_t *pxTCB; TaskHandle_t xReturn; vPortCPUAcquireMutex(&xTaskQueueMutex); - /* A critical section is not required as this is not called from - an interrupt and the current TCB will always be the same for any - individual execution thread. */ xReturn = pxCurrentTCB[ xPortGetCoreID() ]; vPortCPUReleaseMutex(&xTaskQueueMutex); From 2f9772860a1ad0747b509eb41c48040fd16adaee Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 12:18:15 +1100 Subject: [PATCH 007/167] Examples: Add READMEs for examples which did not have them Closes github #128 https://github.com/espressif/esp-idf/issues/128 --- examples/11_rmt_nec_tx_rx/README.md | 6 ++++++ examples/13_timer_group/README.md | 3 +++ examples/16_pcnt/README.md | 14 ++++++++++++++ examples/16_pcnt/main/pcnt_test.c | 24 ++++++++++++------------ examples/19_sigmadelta/README.md | 7 +++++++ 5 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 examples/11_rmt_nec_tx_rx/README.md create mode 100644 examples/13_timer_group/README.md create mode 100644 examples/16_pcnt/README.md create mode 100644 examples/19_sigmadelta/README.md diff --git a/examples/11_rmt_nec_tx_rx/README.md b/examples/11_rmt_nec_tx_rx/README.md new file mode 100644 index 0000000000..d578600d01 --- /dev/null +++ b/examples/11_rmt_nec_tx_rx/README.md @@ -0,0 +1,6 @@ +# Example: rmt_nec_tx_rx + +This example uses the remote control (RMT) peripheral to transmit and receive codes for the NEC infrared remote protocol. + +Configuration (pin numbers, etc.) can be modified in top of the main/infrared_nec.c file. + diff --git a/examples/13_timer_group/README.md b/examples/13_timer_group/README.md new file mode 100644 index 0000000000..0be2c847a8 --- /dev/null +++ b/examples/13_timer_group/README.md @@ -0,0 +1,3 @@ +# Example: timer_group + +This example uses the timer group driver to generate timer interrupts at two specified alarm intervals. diff --git a/examples/16_pcnt/README.md b/examples/16_pcnt/README.md new file mode 100644 index 0000000000..a7019ea9e7 --- /dev/null +++ b/examples/16_pcnt/README.md @@ -0,0 +1,14 @@ +# Example: pcnt + +This example uses the pulse counter module (PCNT) to count the rising edges of pulses generated by the LED Controller module (LEDC). + +By default GPIO18 is used as output pin, GPIO4 is used as pulse input pin and GPIO5 is used as control input pin. This configuration (pin numbers, etc.) can be modified in top of the main/pcnt_test.c file. + +* Open serial port to view the message printed on your screen +* To do this test, you should connect GPIO18 with GPIO4 +* GPIO5 is the control signal, you can leave it floating with internal pulled up, or connect it to ground. HIGH = Count increases, LOW = count decreases. +* An interrupt is configured to trigger when the count reaches threshold values. +* The counter will reset when it reaches the limit values. + + + diff --git a/examples/16_pcnt/main/pcnt_test.c b/examples/16_pcnt/main/pcnt_test.c index b8489ecb2f..6089c8edd6 100644 --- a/examples/16_pcnt/main/pcnt_test.c +++ b/examples/16_pcnt/main/pcnt_test.c @@ -36,14 +36,14 @@ * When counter value reaches thresh1 or thresh0 value, it will trigger interrupt. * When counter value reaches l_lim value or h_lim value, counter value will be reset to zero and trigger interrupt. */ -#define PCNT_TEST_UNIT PCNT_UNIT_0 -#define PCNT_H_LIM_VAL (10) -#define PCNT_L_LIM_VAL (-10) -#define PCNT_THRESH1_VAL (5) -#define PCNT_THRESH0_VAL (-5) -#define PCNT_INPUT_SIG_IO (4) -#define PCNT_INPUT_CTRL_IO (5) -#define LEDC_OUPUT_IO (18) +#define PCNT_TEST_UNIT PCNT_UNIT_0 +#define PCNT_H_LIM_VAL 10 +#define PCNT_L_LIM_VAL -10 +#define PCNT_THRESH1_VAL 5 +#define PCNT_THRESH0_VAL -5 +#define PCNT_INPUT_SIG_IO 4 /* Pulse Input GPIO */ +#define PCNT_INPUT_CTRL_IO 5 /* Control GPIO HIGH=count up, LOW=count down */ +#define LEDC_OUTPUT_IO 18 /* Output GPIO */ xQueueHandle pcnt_evt_queue; /*A queue to handle pulse counter event*/ @@ -96,8 +96,8 @@ void IRAM_ATTR pcnt_intr_handler(void* arg) static void ledc_init(void) { ledc_channel_config_t ledc_channel; - /*use GPIO18 as output pin*/ - ledc_channel.gpio_num = LEDC_OUPUT_IO; + /*use LEDC_OUTPUT_IO as output pin*/ + ledc_channel.gpio_num = LEDC_OUTPUT_IO; /*LEDC high speed mode */ ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE; /*use LEDC channel 1*/ @@ -125,9 +125,9 @@ static void ledc_init(void) static void pcnt_init(void) { pcnt_config_t pcnt_config = { - /*Set GPIO4 as pulse input gpio */ + /*Set PCNT_INPUT_SIG_IO as pulse input gpio */ .pulse_gpio_num = PCNT_INPUT_SIG_IO, - /*set gpio5 as control gpio */ + /*set PCNT_INPUT_CTRL_IO as control gpio */ .ctrl_gpio_num = PCNT_INPUT_CTRL_IO, /*Choose channel 0 */ .channel = PCNT_CHANNEL_0, diff --git a/examples/19_sigmadelta/README.md b/examples/19_sigmadelta/README.md new file mode 100644 index 0000000000..e8cac2c87b --- /dev/null +++ b/examples/19_sigmadelta/README.md @@ -0,0 +1,7 @@ +# Example: sigma_delta modulation + +This example uses the sigma_delta output modulation driver to generate modulated output on a GPIO. + +By default the GPIO output is 4, however you can edit this in the `sigmadelta_init()` function inside `main/sigmadelta_test.c`. + +If you connect an LED to the output GPIO, you will see it blinking slowly. From ab5915ff8b8cd9765b7814fe8f3acbe5912e03e5 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 12:28:08 +1100 Subject: [PATCH 008/167] spi_flash: Standardise argument types & names used for flash offsets Closes github #88: https://github.com/espressif/esp-idf/issues/88 --- components/spi_flash/flash_mmap.c | 2 +- components/spi_flash/include/esp_spi_flash.h | 26 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/spi_flash/flash_mmap.c b/components/spi_flash/flash_mmap.c index 2165a784d1..15f75f3634 100644 --- a/components/spi_flash/flash_mmap.c +++ b/components/spi_flash/flash_mmap.c @@ -77,7 +77,7 @@ static void IRAM_ATTR spi_flash_mmap_init() } } -esp_err_t IRAM_ATTR spi_flash_mmap(uint32_t src_addr, size_t size, spi_flash_mmap_memory_t memory, +esp_err_t IRAM_ATTR spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t memory, const void** out_ptr, spi_flash_mmap_handle_t* out_handle) { esp_err_t ret; diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index 91675088ae..bb3ec39b45 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -78,13 +78,13 @@ esp_err_t spi_flash_erase_range(size_t start_address, size_t size); * @note If source address is in DROM, this function will return * ESP_ERR_INVALID_ARG. * - * @param dest destination address in Flash. Must be a multiple of 4 bytes. - * @param src pointer to the source buffer. - * @param size length of data, in bytes. Must be a multiple of 4 bytes. + * @param dest_addr destination address in Flash. Must be a multiple of 4 bytes. + * @param src pointer to the source buffer. + * @param size length of data, in bytes. Must be a multiple of 4 bytes. * * @return esp_err_t */ -esp_err_t spi_flash_write(size_t dest, const void *src, size_t size); +esp_err_t spi_flash_write(size_t dest_addr, const void *src, size_t size); /** @@ -97,24 +97,24 @@ esp_err_t spi_flash_write(size_t dest, const void *src, size_t size); * @note If source address is in DROM, this function will return * ESP_ERR_INVALID_ARG. * - * @param dest destination address in Flash. Must be a multiple of 32 bytes. - * @param src pointer to the source buffer. - * @param size length of data, in bytes. Must be a multiple of 32 bytes. + * @param dest_addr destination address in Flash. Must be a multiple of 32 bytes. + * @param src pointer to the source buffer. + * @param size length of data, in bytes. Must be a multiple of 32 bytes. * * @return esp_err_t */ -esp_err_t spi_flash_write_encrypted(size_t dest, const void *src, size_t size); +esp_err_t spi_flash_write_encrypted(size_t dest_addr, const void *src, size_t size); /** * @brief Read data from Flash. * - * @param src source address of the data in Flash. - * @param dest pointer to the destination buffer - * @param size length of data + * @param src_addr source address of the data in Flash. + * @param dest pointer to the destination buffer + * @param size length of data * * @return esp_err_t */ -esp_err_t spi_flash_read(size_t src, void *dest, size_t size); +esp_err_t spi_flash_read(size_t src_addr, void *dest, size_t size); /** * @brief Enumeration which specifies memory space requested in an mmap call @@ -149,7 +149,7 @@ typedef uint32_t spi_flash_mmap_handle_t; * * @return ESP_OK on success, ESP_ERR_NO_MEM if pages can not be allocated */ -esp_err_t spi_flash_mmap(uint32_t src_addr, size_t size, spi_flash_mmap_memory_t memory, +esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t memory, const void** out_ptr, spi_flash_mmap_handle_t* out_handle); /** From 99f4c697ee16d69f1f02ce6e14e64a4e35fc2651 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Thu, 22 Dec 2016 13:37:07 +0800 Subject: [PATCH 009/167] freertos: enable dual core by default --- components/freertos/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index f03da6bc01..b9db00e50b 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -3,7 +3,7 @@ menu "FreeRTOS" # This is actually also handled in the ESP32 startup code, not only in FreeRTOS. config FREERTOS_UNICORE bool "Run FreeRTOS only on first core" - default y + default n help This version of FreeRTOS normally takes control of all cores of the CPU. Select this if you only want to start it on the first core. From 794f7dd2940b200bcaa052a78b1f0a4adbf38f62 Mon Sep 17 00:00:00 2001 From: Wangjialin Date: Thu, 22 Dec 2016 12:08:15 +0800 Subject: [PATCH 010/167] bugfix: uart event mismatch 1. Fix bug of uart frame error and parity error interrupt mismatch in driver code, which will cause the corresponding interrupt can not be cleared correctly, and will finally cause a interrupt watch dog. 2. Add gpio pull-up for rx pin and cts pin. --- components/driver/uart.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/driver/uart.c b/components/driver/uart.c index e85c54d8c4..b9ebc4b5f0 100644 --- a/components/driver/uart.c +++ b/components/driver/uart.c @@ -387,6 +387,7 @@ esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int r if(rx_io_num >= 0) { PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[rx_io_num], PIN_FUNC_GPIO); + gpio_set_pull_mode(rx_io_num, GPIO_PULLUP_ONLY); gpio_set_direction(rx_io_num, GPIO_MODE_INPUT); gpio_matrix_in(rx_io_num, rx_sig, 0); } @@ -397,6 +398,7 @@ esp_err_t uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int r } if(cts_io_num >= 0) { PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[cts_io_num], PIN_FUNC_GPIO); + gpio_set_pull_mode(cts_io_num, GPIO_PULLUP_ONLY); gpio_set_direction(cts_io_num, GPIO_MODE_INPUT); gpio_matrix_in(cts_io_num, cts_sig, 0); } @@ -639,10 +641,10 @@ static void IRAM_ATTR uart_rx_intr_handler_default(void *param) uart_reg->int_clr.brk_det = 1; uart_event.type = UART_BREAK; } else if(uart_intr_status & UART_FRM_ERR_INT_ST_M) { - uart_reg->int_clr.parity_err = 1; + uart_reg->int_clr.frm_err = 1; uart_event.type = UART_FRAME_ERR; } else if(uart_intr_status & UART_PARITY_ERR_INT_ST_M) { - uart_reg->int_clr.frm_err = 1; + uart_reg->int_clr.parity_err = 1; uart_event.type = UART_PARITY_ERR; } else if(uart_intr_status & UART_TX_BRK_DONE_INT_ST_M) { UART_ENTER_CRITICAL_ISR(&uart_spinlock[uart_num]); From e8b194d5e55ed5df252446ab5682f8921022db01 Mon Sep 17 00:00:00 2001 From: Malte Janduda Date: Thu, 22 Sep 2016 01:50:53 +0200 Subject: [PATCH 011/167] provide list of packages for homebrew --- docs/macos-setup.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/macos-setup.rst b/docs/macos-setup.rst index 53c6fe54c8..67a1fa9906 100644 --- a/docs/macos-setup.rst +++ b/docs/macos-setup.rst @@ -66,7 +66,9 @@ In any case, here are the steps to compile the toolchain yourself. sudo port install gsed gawk binutils gperf grep gettext ncurses - - with homebrew (*TODO: provide list of packages for homebrew*) + - with homebrew + + brew install gnu-sed gawk binutils gperf grep gettext ncurses Create a case-sensitive filesystem image:: From 15651b5923b61f4e113db1790f3d9a4e5fb73cdb Mon Sep 17 00:00:00 2001 From: XiaXiaotian Date: Mon, 26 Dec 2016 15:47:20 +0800 Subject: [PATCH 012/167] lwip: add ip frag and reassembly option in menuconfig --- components/lwip/Kconfig | 13 +++++++++++++ components/lwip/include/lwip/port/lwipopts.h | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/components/lwip/Kconfig b/components/lwip/Kconfig index 2e7e31a8a9..9e2b031674 100644 --- a/components/lwip/Kconfig +++ b/components/lwip/Kconfig @@ -49,6 +49,19 @@ config LWIP_DHCP_MAX_NTP_SERVERS First argument of sntp_setserver/sntp_setservername functions is limited to this value. +config LWIP_IP_FRAG + bool "Enable fragment outgoing IP packets" + default 0 + help + Enabling this option allows fragmenting outgoing IP packets if their size + exceeds MTU. + +config LWIP_IP_REASSEMBLY + bool "Enable reassembly incoming fragmented IP packets" + default 0 + help + Enabling this option allows reassemblying incoming fragmented IP packets. + endmenu diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 8612eb11b0..5000d63ba9 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -154,14 +154,14 @@ * this option does not affect outgoing packet sizes, which can be controlled * via IP_FRAG. */ -#define IP_REASSEMBLY 0 +#define IP_REASSEMBLY CONFIG_LWIP_IP_REASSEMBLY /** * IP_FRAG==1: Fragment outgoing IP packets if their size exceeds MTU. Note * that this option does not affect incoming packet sizes, which can be * controlled via IP_REASSEMBLY. */ -#define IP_FRAG 0 +#define IP_FRAG CONFIG_LWIP_IP_FRAG /** * IP_REASS_MAXAGE: Maximum time (in multiples of IP_TMR_INTERVAL - so seconds, normally) From d2e58193d235bcde15b1124af63358300412d31c Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Mon, 26 Dec 2016 19:04:41 +0800 Subject: [PATCH 013/167] add more protection for per-core data --- components/freertos/Kconfig | 6 ++- .../freertos/include/freertos/portmacro.h | 4 +- components/freertos/tasks.c | 54 +++++++++++++------ 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index b9db00e50b..859ece2c09 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -195,7 +195,11 @@ config FREERTOS_PORTMUX_DEBUG_RECURSIVE If enabled, additional debug information will be printed for recursive portMUX usage. - +config FREERTOS_INT_DISABLING_DURATION_DEBUG + bool "Debug interrupt disabling duration" + default n + help + If enabled, the longest interrupt disabling duration will be recorded. endif # FREERTOS_DEBUG_INTERNALS diff --git a/components/freertos/include/freertos/portmacro.h b/components/freertos/include/freertos/portmacro.h index f20a4a1e26..9c15eebf9a 100644 --- a/components/freertos/include/freertos/portmacro.h +++ b/components/freertos/include/freertos/portmacro.h @@ -213,7 +213,6 @@ portBASE_TYPE vPortCPUReleaseMutex(portMUX_TYPE *mux); #define portEXIT_CRITICAL_ISR(mux) vPortCPUReleaseMutex(mux) #endif - // Cleaner and preferred solution allows nested interrupts disabling and restoring via local registers or stack. // They can be called from interrupts too. //NOT SMP-COMPATIBLE! Use only if all you want is to disable the interrupts locally! @@ -225,6 +224,9 @@ static inline unsigned portENTER_CRITICAL_NESTED() { unsigned state = XTOS_SET_I #define portCLEAR_INTERRUPT_MASK_FROM_ISR(state) portEXIT_CRITICAL_NESTED(state) +#define portDisableINT() portENTER_CRITICAL_NESTED() +#define portEnableINT(state) portEXIT_CRITICAL_NESTED((state)) + /* * Wrapper for the Xtensa compare-and-set instruction. This subroutine will atomically compare * *mux to compare, and if it's the same, will set *mux to set. It will return the old value diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index f2bdf8ccb0..00df0df871 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -369,7 +369,7 @@ PRIVILEGED_DATA static portMUX_TYPE xTickCountMutex = portMUX_INITIALIZER_UNLOCK \ /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \ the same priority get an equal share of the processor time. */ \ - listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB[ xPortGetCoreID() ], &( pxReadyTasksLists[ uxTopReadyPriority ] ) ); \ + listGET_OWNER_OF_NEXT_ENTRY( xTaskGetCurrentTaskHandle(), &( pxReadyTasksLists[ uxTopReadyPriority ] ) ); \ } /* taskSELECT_HIGHEST_PRIORITY_TASK */ /*-----------------------------------------------------------*/ @@ -398,7 +398,7 @@ PRIVILEGED_DATA static portMUX_TYPE xTickCountMutex = portMUX_INITIALIZER_UNLOCK /* Find the highest priority queue that contains ready tasks. */ \ portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \ configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \ - listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB[ xPortGetCoreID() ], &( pxReadyTasksLists[ uxTopPriority ] ) ); \ + listGET_OWNER_OF_NEXT_ENTRY( xTaskGetCurrentTaskHandle(), &( pxReadyTasksLists[ uxTopPriority ] ) ); \ } /* taskSELECT_HIGHEST_PRIORITY_TASK() */ /*-----------------------------------------------------------*/ @@ -456,7 +456,7 @@ count overflows. */ * see if the parameter is NULL and returns a pointer to the appropriate TCB. */ /* ToDo: See if this still works for multicore. */ -#define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? ( TCB_t * ) pxCurrentTCB[ xPortGetCoreID() ] : ( TCB_t * ) ( pxHandle ) ) +#define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? ( TCB_t * ) xTaskGetCurrentTaskHandle() : ( TCB_t * ) ( pxHandle ) ) /* The item value of the event list item is normally used to hold the priority of the task to which it belongs (coded to allow it to be held in reverse @@ -631,9 +631,11 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode */ void taskYIELD_OTHER_CORE( BaseType_t xCoreID, UBaseType_t uxPriority ) { + TCB_t *curTCB = xTaskGetCurrentTaskHandle(); BaseType_t i; + if (xCoreID != tskNO_AFFINITY) { - if ( pxCurrentTCB[ xCoreID ]->uxPriority < uxPriority ) { + if ( curTCB->uxPriority < uxPriority ) { vPortYieldOtherCore( xCoreID ); } } @@ -1039,6 +1041,7 @@ UBaseType_t x; static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode, const BaseType_t xCoreID ) { + TCB_t *curTCB; BaseType_t i; /* Ensure interrupts don't access the task lists while the lists are being @@ -1111,6 +1114,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode portSETUP_TCB( pxNewTCB ); } + curTCB = pxCurrentTCB[ xPortGetCoreID() ]; taskEXIT_CRITICAL(&xTaskQueueMutex); if( xSchedulerRunning != pdFALSE ) @@ -1121,17 +1125,17 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode the other processor will keep running the task it's working on, and only switch to the newer task on a timer interrupt. */ //No mux here, uxPriority is mostly atomic and there's not really any harm if this check misfires. - if( pxCurrentTCB[ xPortGetCoreID() ]->uxPriority < pxNewTCB->uxPriority ) + if( curTCB->uxPriority < pxNewTCB->uxPriority ) { /* Scheduler is running. If the created task is of a higher priority than an executing task then it should run now. No mux here, uxPriority is mostly atomic and there's not really any harm if this check misfires. */ - if( tskCAN_RUN_HERE( xCoreID ) && pxCurrentTCB[ xPortGetCoreID() ]->uxPriority < pxNewTCB->uxPriority ) + if( tskCAN_RUN_HERE( xCoreID ) && curTCB->uxPriority < pxNewTCB->uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); } - else if( xCoreID != xPortGetCoreID() ) { + else if( xCoreID != xPortGetCoreID() ) {//TODO taskYIELD_OTHER_CORE(xCoreID, pxNewTCB->uxPriority); } else @@ -1409,11 +1413,12 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode eTaskState eReturn; List_t *pxStateList; const TCB_t * const pxTCB = ( TCB_t * ) xTask; + TCB_t * curTCB = xTaskGetCurrentTaskHandle(); UNTESTED_FUNCTION(); configASSERT( pxTCB ); - if( pxTCB == pxCurrentTCB[ xPortGetCoreID() ] ) + if( pxTCB == curTCB ) { /* The task calling this function is querying its own state. */ eReturn = eRunning; @@ -1691,6 +1696,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode void vTaskSuspend( TaskHandle_t xTaskToSuspend ) { TCB_t *pxTCB; + TCB_t *curTCB; UNTESTED_FUNCTION(); taskENTER_CRITICAL(&xTaskQueueMutex); @@ -1723,10 +1729,11 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode } vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xGenericListItem ) ); + curTCB = pxCurrentTCB[ xPortGetCoreID() ]; } taskEXIT_CRITICAL(&xTaskQueueMutex); - if( pxTCB == pxCurrentTCB[ xPortGetCoreID() ] ) + if( pxTCB == curTCB ) { if( xSchedulerRunning != pdFALSE ) { @@ -2034,7 +2041,7 @@ void vTaskEndScheduler( void ) //Return global reent struct if FreeRTOS isn't running, struct _reent* __getreent() { //No lock needed because if this changes, we won't be running anymore. - TCB_t *currTask=pxCurrentTCB[ xPortGetCoreID() ]; + TCB_t *currTask=xTaskGetCurrentTaskHandle(); if (currTask==NULL) { //No task running. Return global struct. return _GLOBAL_REENT; @@ -2052,7 +2059,11 @@ void vTaskSuspendAll( void ) BaseType_t. Please read Richard Barry's reply in the following link to a post in the FreeRTOS support forum before reporting this as a bug! - http://goo.gl/wu4acr */ + unsigned state; + + state = portDisableINT(); ++uxSchedulerSuspended[ xPortGetCoreID() ]; + portEnableINT(state); } /*----------------------------------------------------------*/ @@ -2595,7 +2606,7 @@ BaseType_t xSwitchRequired = pdFALSE; /* If xTask is NULL then we are setting our own task hook. */ if( xTask == NULL ) { - xTCB = ( TCB_t * ) pxCurrentTCB[ xPortGetCoreID() ]; + xTCB = ( TCB_t * ) xTaskGetCurrentTaskHandle(); } else { @@ -2626,7 +2637,7 @@ BaseType_t xSwitchRequired = pdFALSE; /* If xTask is NULL then we are calling our own task hook. */ if( xTask == NULL ) { - xTCB = ( TCB_t * ) pxCurrentTCB[ xPortGetCoreID() ]; + xTCB = ( TCB_t * ) xTaskGetCurrentTaskHandle(); } else { @@ -3387,8 +3398,8 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters ) if( xIndex < configNUM_THREAD_LOCAL_STORAGE_POINTERS ) { - pxTCB = prvGetTCBFromHandle( xTaskToSet ); taskENTER_CRITICAL(&xTaskQueueMutex); + pxTCB = prvGetTCBFromHandle( xTaskToSet ); pxTCB->pvThreadLocalStoragePointers[ xIndex ] = pvValue; pxTCB->pvThreadLocalStoragePointersDelCallback[ xIndex ] = xDelCallback; taskEXIT_CRITICAL(&xTaskQueueMutex); @@ -3408,8 +3419,10 @@ static portTASK_FUNCTION( prvIdleTask, pvParameters ) if( xIndex < configNUM_THREAD_LOCAL_STORAGE_POINTERS ) { + taskENTER_CRITICAL(&xTaskQueueMutex); pxTCB = prvGetTCBFromHandle( xTaskToSet ); pxTCB->pvThreadLocalStoragePointers[ xIndex ] = pvValue; + taskEXIT_CRITICAL(&xTaskQueueMutex); } } #endif /* configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS */ @@ -3803,10 +3816,11 @@ TCB_t *pxTCB; TaskHandle_t xTaskGetCurrentTaskHandle( void ) { TaskHandle_t xReturn; + unsigned state; - vPortCPUAcquireMutex(&xTaskQueueMutex); + state = portDisableINT(); xReturn = pxCurrentTCB[ xPortGetCoreID() ]; - vPortCPUReleaseMutex(&xTaskQueueMutex); + portEnableINT(state); return xReturn; } @@ -3832,7 +3846,9 @@ TCB_t *pxTCB; BaseType_t xTaskGetSchedulerState( void ) { BaseType_t xReturn; + unsigned state; + state = portDisableINT(); if( xSchedulerRunning == pdFALSE ) { xReturn = taskSCHEDULER_NOT_STARTED; @@ -3848,6 +3864,7 @@ TCB_t *pxTCB; xReturn = taskSCHEDULER_SUSPENDED; } } + portEnableINT(state); return xReturn; } @@ -4383,16 +4400,19 @@ TickType_t uxReturn; void *pvTaskIncrementMutexHeldCount( void ) { + TCB_t *curTCB; + /* If xSemaphoreCreateMutex() is called before any tasks have been created - then pxCurrentTCB will be NULL. */ + then xTaskGetCurrentTaskHandle() will be NULL. */ taskENTER_CRITICAL(&xTaskQueueMutex); if( pxCurrentTCB[ xPortGetCoreID() ] != NULL ) { ( pxCurrentTCB[ xPortGetCoreID() ]->uxMutexesHeld )++; } + curTCB = pxCurrentTCB[ xPortGetCoreID() ]; taskEXIT_CRITICAL(&xTaskQueueMutex); - return pxCurrentTCB[ xPortGetCoreID() ]; + return curTCB; } #endif /* configUSE_MUTEXES */ From d049fd392953d8aaf2c3cb4270aa8b401e4f016a Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Tue, 27 Dec 2016 12:11:07 +0800 Subject: [PATCH 014/167] freertos: rework code based on review --- components/freertos/Kconfig | 6 ------ .../freertos/include/freertos/portmacro.h | 3 --- components/freertos/tasks.c | 20 ++++++++++--------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index 859ece2c09..d8b392d577 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -195,12 +195,6 @@ config FREERTOS_PORTMUX_DEBUG_RECURSIVE If enabled, additional debug information will be printed for recursive portMUX usage. -config FREERTOS_INT_DISABLING_DURATION_DEBUG - bool "Debug interrupt disabling duration" - default n - help - If enabled, the longest interrupt disabling duration will be recorded. - endif # FREERTOS_DEBUG_INTERNALS endmenu diff --git a/components/freertos/include/freertos/portmacro.h b/components/freertos/include/freertos/portmacro.h index 9c15eebf9a..7cae4b05b6 100644 --- a/components/freertos/include/freertos/portmacro.h +++ b/components/freertos/include/freertos/portmacro.h @@ -224,9 +224,6 @@ static inline unsigned portENTER_CRITICAL_NESTED() { unsigned state = XTOS_SET_I #define portCLEAR_INTERRUPT_MASK_FROM_ISR(state) portEXIT_CRITICAL_NESTED(state) -#define portDisableINT() portENTER_CRITICAL_NESTED() -#define portEnableINT(state) portEXIT_CRITICAL_NESTED((state)) - /* * Wrapper for the Xtensa compare-and-set instruction. This subroutine will atomically compare * *mux to compare, and if it's the same, will set *mux to set. It will return the old value diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 00df0df871..159d96c5b2 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -1041,7 +1041,7 @@ UBaseType_t x; static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode, const BaseType_t xCoreID ) { - TCB_t *curTCB; + TCB_t *curTCB; BaseType_t i; /* Ensure interrupts don't access the task lists while the lists are being @@ -1119,6 +1119,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode if( xSchedulerRunning != pdFALSE ) { + taskENTER_CRITICAL(&xTaskQueueMutex); /* Scheduler is running. If the created task is of a higher priority than an executing task then it should run now. ToDo: This only works for the current core. If a task is scheduled on an other processor, @@ -1135,7 +1136,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode { taskYIELD_IF_USING_PREEMPTION(); } - else if( xCoreID != xPortGetCoreID() ) {//TODO + else if( xCoreID != xPortGetCoreID() ) { taskYIELD_OTHER_CORE(xCoreID, pxNewTCB->uxPriority); } else @@ -1147,6 +1148,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode { mtCOVERAGE_TEST_MARKER(); } + taskEXIT_CRITICAL(&xTaskQueueMutex); } else { @@ -2061,9 +2063,9 @@ void vTaskSuspendAll( void ) http://goo.gl/wu4acr */ unsigned state; - state = portDisableINT(); + state = portENTER_CRITICAL_NESTED(); ++uxSchedulerSuspended[ xPortGetCoreID() ]; - portEnableINT(state); + portEXIT_CRITICAL_NESTED(state); } /*----------------------------------------------------------*/ @@ -3818,9 +3820,9 @@ TCB_t *pxTCB; TaskHandle_t xReturn; unsigned state; - state = portDisableINT(); + state = portENTER_CRITICAL_NESTED(); xReturn = pxCurrentTCB[ xPortGetCoreID() ]; - portEnableINT(state); + portEXIT_CRITICAL_NESTED(state); return xReturn; } @@ -3848,7 +3850,7 @@ TCB_t *pxTCB; BaseType_t xReturn; unsigned state; - state = portDisableINT(); + state = portENTER_CRITICAL_NESTED(); if( xSchedulerRunning == pdFALSE ) { xReturn = taskSCHEDULER_NOT_STARTED; @@ -3864,7 +3866,7 @@ TCB_t *pxTCB; xReturn = taskSCHEDULER_SUSPENDED; } } - portEnableINT(state); + portEXIT_CRITICAL_NESTED(state); return xReturn; } @@ -4403,7 +4405,7 @@ TickType_t uxReturn; TCB_t *curTCB; /* If xSemaphoreCreateMutex() is called before any tasks have been created - then xTaskGetCurrentTaskHandle() will be NULL. */ + then pxCurrentTCB will be NULL. */ taskENTER_CRITICAL(&xTaskQueueMutex); if( pxCurrentTCB[ xPortGetCoreID() ] != NULL ) { From 6a39bc6996548aaedbbdb63178182c0076fa55fc Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Tue, 27 Dec 2016 12:32:40 +0100 Subject: [PATCH 015/167] Clarification on documenting examples --- docs/api/template.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/api/template.rst b/docs/api/template.rst index 6feb7ba271..3cc8713ba4 100644 --- a/docs/api/template.rst +++ b/docs/api/template.rst @@ -40,10 +40,15 @@ Application Example *INSTRUCTIONS* - 1. Provide one or more practical examples to demonstrate functionality of this API. - 2. Break down the code into parts and describe functionality of each part. - 3. Provide screenshots if applicable. - + 1. Prepare one or more practical examples to demonstrate functionality of this API. + 2. Each example should follow pattern of projects located in ``esp-idf/examples/`` folder. + 3. Place example in this folder complete with ``README.md`` file. + 4. Provide overview of demonstrated functionality in ``README.md``. + 5. With good overview reader should be able to understand what example does without opening the source code. + 6. Depending on complexity of example, break down description of code into parts and provide overview of functionality of each part. + 7. Include flow diagram and screenshots of application output if applicable. + 8. Finally add in this section synopsis of each example together with link to respective folder in ``esp-idf/examples/``. + API Reference ------------- From da977149f67487c023766974696cb1e8a5bc9117 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 6 Dec 2016 16:33:24 -0800 Subject: [PATCH 016/167] panic handlers: Print the PC address where abort() was called, don't dump registers --- components/esp32/cpu_util.c | 12 +++++ components/esp32/include/soc/cpu.h | 9 ++++ components/esp32/panic.c | 75 +++++++++++++++++------------- components/newlib/syscalls.c | 9 ---- 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/components/esp32/cpu_util.c b/components/esp32/cpu_util.c index cff61ab796..e3b4ef8f16 100644 --- a/components/esp32/cpu_util.c +++ b/components/esp32/cpu_util.c @@ -42,3 +42,15 @@ void IRAM_ATTR esp_cpu_unstall(int cpu_id) CLEAR_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_STALL_PROCPU_C0_M); } } + +bool IRAM_ATTR esp_cpu_in_ocd_debug_mode() +{ +#if CONFIG_ESP32_DEBUG_OCDAWARE + int dcr; + int reg=0x10200C; //DSRSET register + asm("rer %0,%1":"=r"(dcr):"r"(reg)); + return (dcr&0x1); +#else + return false; // Always return false if "OCD aware" is disabled +#endif +} diff --git a/components/esp32/include/soc/cpu.h b/components/esp32/include/soc/cpu.h index 4457c81a22..b89ae2875f 100644 --- a/components/esp32/include/soc/cpu.h +++ b/components/esp32/include/soc/cpu.h @@ -94,4 +94,13 @@ void esp_cpu_stall(int cpu_id); */ void esp_cpu_unstall(int cpu_id); +/** + * @brief Returns true if a JTAG debugger is attached to CPU + * OCD (on chip debug) port. + * + * @note If "Make exception and panic handlers JTAG/OCD aware" + * is disabled, this function always returns false. + */ +bool esp_cpu_in_ocd_debug_mode(); + #endif diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 0efe56fe01..3cdbfb3e39 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -36,7 +36,7 @@ /* Panic handlers; these get called when an unhandled exception occurs or the assembly-level task switching / interrupt code runs into an unrecoverable error. The default task stack - overflow handler also is in here. + overflow handler and abort handler are also in here. */ /* @@ -95,15 +95,29 @@ inline static void panicPutHex(int a) { } inline static void panicPutDec(int a) { } #endif - void __attribute__((weak)) vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName ) { panicPutStr("***ERROR*** A stack overflow in task "); panicPutStr((char *)pcTaskName); panicPutStr(" has been detected.\r\n"); - configASSERT(0); + abort(); } +static bool abort_called; + +void abort() +{ +#if !CONFIG_ESP32_PANIC_SILENT_REBOOT + ets_printf("abort() was called at PC 0x%08x\n", (intptr_t)__builtin_return_address(0) - 3); +#endif + abort_called = true; + while(1) { + __asm__ ("break 0,0"); + *((int*) 0) = 0; + } +} + + static const char *edesc[] = { "IllegalInstruction", "Syscall", "InstructionFetchError", "LoadStoreError", "Level1Interrupt", "Alloca", "IntegerDivideByZero", "PCValue", @@ -118,7 +132,7 @@ static const char *edesc[] = { }; -void commonErrorHandler(XtExcFrame *frame); +static void commonErrorHandler(XtExcFrame *frame); //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. @@ -127,19 +141,6 @@ static void haltOtherCore() esp_cpu_stall( xPortGetCoreID() == 0 ? 1 : 0 ); } -//Returns true when a debugger is attached using JTAG. -static int inOCDMode() -{ -#if CONFIG_ESP32_DEBUG_OCDAWARE - int dcr; - int reg = 0x10200C; //DSRSET register - asm("rer %0,%1":"=r"(dcr):"r"(reg)); - return (dcr & 0x1); -#else - return 0; //Always return no debugger is attached. -#endif -} - void panicHandler(XtExcFrame *frame) { int *regs = (int *)frame; @@ -165,7 +166,7 @@ void panicHandler(XtExcFrame *frame) panicPutStr(reason); panicPutStr(")\r\n"); - if (inOCDMode()) { + if (esp_cpu_in_ocd_debug_mode()) { asm("break.n 1"); } commonErrorHandler(frame); @@ -197,7 +198,7 @@ void xt_unhandled_exception(XtExcFrame *frame) } panicPutStr(" occurred on core "); panicPutDec(xPortGetCoreID()); - if (inOCDMode()) { + if (esp_cpu_in_ocd_debug_mode()) { panicPutStr(" at pc="); panicPutHex(regs[1]); panicPutStr(". Setting bp and returning..\r\n"); @@ -255,6 +256,7 @@ static inline bool stackPointerIsSane(uint32_t sp) { return !(sp < 0x3ffae010 || sp > 0x3ffffff0 || ((sp & 0xf) != 0)); } + static void putEntry(uint32_t pc, uint32_t sp) { if (pc & 0x80000000) { @@ -265,7 +267,8 @@ static void putEntry(uint32_t pc, uint32_t sp) panicPutStr(":0x"); panicPutHex(sp); } -void doBacktrace(XtExcFrame *frame) + +static void doBacktrace(XtExcFrame *frame) { uint32_t i = 0, pc = frame->pc, sp = frame->a1; panicPutStr("\nBacktrace:"); @@ -291,7 +294,7 @@ 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. */ -void commonErrorHandler(XtExcFrame *frame) +static void commonErrorHandler(XtExcFrame *frame) { int *regs = (int *)frame; int x, y; @@ -304,21 +307,28 @@ void commonErrorHandler(XtExcFrame *frame) //Feed the watchdogs, so they will give us time to print out debug info reconfigureAllWdts(); - panicPutStr("Register dump:\r\n"); + /* 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"); - for (x = 0; x < 24; x += 4) { - for (y = 0; y < 4; y++) { - if (sdesc[x + y][0] != 0) { - panicPutStr(sdesc[x + y]); - panicPutStr(": 0x"); - panicPutHex(regs[x + y + 1]); - panicPutStr(" "); + for (x = 0; x < 24; x += 4) { + for (y = 0; y < 4; y++) { + if (sdesc[x + y][0] != 0) { + panicPutStr(sdesc[x + y]); + panicPutStr(": 0x"); + panicPutHex(regs[x + y + 1]); + panicPutStr(" "); + } } + panicPutStr("\r\n"); } - panicPutStr("\r\n"); } + /* With windowed ABI backtracing is easy, let's do it. */ doBacktrace(frame); + #if CONFIG_ESP32_PANIC_GDBSTUB disableAllWdts(); panicPutStr("Entering gdb stub now.\r\n"); @@ -339,8 +349,7 @@ void commonErrorHandler(XtExcFrame *frame) void esp_set_breakpoint_if_jtag(void *fn) { - if (!inOCDMode()) { - return; + if (esp_cpu_in_ocd_debug_mode()) { + setFirstBreakpoint((uint32_t)fn); } - setFirstBreakpoint((uint32_t)fn); } diff --git a/components/newlib/syscalls.c b/components/newlib/syscalls.c index 3b2fbf62ca..74182d07f2 100644 --- a/components/newlib/syscalls.c +++ b/components/newlib/syscalls.c @@ -22,15 +22,6 @@ #include "esp_attr.h" #include "freertos/FreeRTOS.h" -void IRAM_ATTR abort() -{ - do - { - __asm__ ("break 0,0"); - *((int*) 0) = 0; - } while(true); -} - void* IRAM_ATTR _malloc_r(struct _reent *r, size_t size) { return pvPortMalloc(size); From c1a6d5511663560bbed058e0ecd4d3ec6a3da37e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 12:34:11 +1100 Subject: [PATCH 017/167] WiFi interface: SSID and password fields should be uint8_t in all cases Closes github #40 https://github.com/espressif/esp-idf/issues/40 --- components/esp32/include/esp_wifi_types.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/esp32/include/esp_wifi_types.h b/components/esp32/include/esp_wifi_types.h index 583d7a6a91..ac7642829f 100755 --- a/components/esp32/include/esp_wifi_types.h +++ b/components/esp32/include/esp_wifi_types.h @@ -96,7 +96,7 @@ typedef enum { } wifi_second_chan_t; typedef struct { - char *ssid; /**< SSID of AP */ + uint8_t *ssid; /**< SSID of AP */ uint8_t *bssid; /**< MAC address of AP */ uint8_t channel; /**< channel, scan the specific channel */ bool show_hidden; /**< enable to scan AP whose SSID is hidden */ @@ -126,8 +126,8 @@ typedef enum { } wifi_bandwidth_t; typedef struct { - char ssid[32]; /**< SSID of ESP32 soft-AP */ - char password[64]; /**< Password of ESP32 soft-AP */ + uint8_t ssid[32]; /**< SSID of ESP32 soft-AP */ + uint8_t password[64]; /**< Password of ESP32 soft-AP */ uint8_t ssid_len; /**< Length of SSID. If softap_config.ssid_len==0, check the SSID until there is a termination character; otherwise, set the SSID length according to softap_config.ssid_len. */ uint8_t channel; /**< Channel of ESP32 soft-AP */ wifi_auth_mode_t authmode; /**< Auth mode of ESP32 soft-AP. Do not support AUTH_WEP in soft-AP mode */ @@ -137,8 +137,8 @@ typedef struct { } wifi_ap_config_t; typedef struct { - char ssid[32]; /**< SSID of target AP*/ - char password[64]; /**< password of target AP*/ + uint8_t ssid[32]; /**< SSID of target AP*/ + uint8_t password[64]; /**< password of target AP*/ bool bssid_set; /**< whether set MAC address of target AP or not. Generally, station_config.bssid_set needs to be 0; and it needs to be 1 only when users need to check the MAC address of the AP.*/ uint8_t bssid[6]; /**< MAC address of target AP*/ } wifi_sta_config_t; @@ -215,7 +215,7 @@ typedef struct { typedef struct { wifi_pkt_rx_ctrl_t rx_ctrl; - char payload[0]; /**< ieee80211 packet buff, The length of payload is described by sig_len */ + uint8_t payload[0]; /**< ieee80211 packet buff, The length of payload is described by sig_len */ } wifi_promiscuous_pkt_t; /** From 1e44f72e98ba03e27a014c8518037345062e1667 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 12:37:03 +1100 Subject: [PATCH 018/167] esp_wifi_init: Update comment about init event_q Closes github #28 https://github.com/espressif/esp-idf/issues/28 --- components/esp32/include/esp_wifi.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/esp32/include/esp_wifi.h b/components/esp32/include/esp_wifi.h index 68d06aae52..ac49764f1f 100755 --- a/components/esp32/include/esp_wifi.h +++ b/components/esp32/include/esp_wifi.h @@ -107,17 +107,15 @@ typedef struct { * WiFi NVS structure etc, this WiFi also start WiFi task * * @attention 1. This API must be called before all other WiFi API can be called - * @attention 2. Generally we should init event_q in *config, WiFi driver will post the event - * to this queue when event happens, such as, when station connects to WiFi, WiFi driver - * will post station connected event to this queue. If the queue is not initialized, WiFi - * will not post any events + * @attention 2. event_handler field in cfg should be set to a valid event handler function. + * In most cases, use the WIFI_INIT_CONFIG_DEFAULT macro which sets esp_event_send(). * * @param config provide WiFi init configuration * * @return * - ESP_OK: succeed * - ESP_ERR_WIFI_NO_MEM: out of memory - * - others: refer to error code esp_err.h + * - others: refer to error code esp_err.h */ esp_err_t esp_wifi_init(wifi_init_config_t *config); From ff1fa8a32340bff9ced31262857ddc66b17a6306 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 13:05:19 +1100 Subject: [PATCH 019/167] gpio driver: Fix gpio_set_level validation of gpio_num argument Closes #125 https://github.com/espressif/esp-idf/issues/125 --- components/driver/gpio.c | 2 +- components/driver/include/driver/gpio.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/driver/gpio.c b/components/driver/gpio.c index 1a38620dbb..3201372bc1 100644 --- a/components/driver/gpio.c +++ b/components/driver/gpio.c @@ -161,7 +161,7 @@ static esp_err_t gpio_output_enable(gpio_num_t gpio_num) esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level) { - GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); + GPIO_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), "GPIO output gpio_num error", ESP_ERR_INVALID_ARG); if (level) { if (gpio_num < 32) { GPIO.out_w1ts = (1 << gpio_num); diff --git a/components/driver/include/driver/gpio.h b/components/driver/include/driver/gpio.h index 83d3806834..4f2a800724 100644 --- a/components/driver/include/driver/gpio.h +++ b/components/driver/include/driver/gpio.h @@ -269,7 +269,7 @@ esp_err_t gpio_intr_disable(gpio_num_t gpio_num); * * @return * - ESP_OK Success - * - GPIO_IS_VALID_GPIO GPIO number error + * - ESP_ERR_INVALID_ARG GPIO number error * */ esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level); From 41eca2c67ba276c3a85e5da094b6da5e2e569487 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 13:21:11 +1100 Subject: [PATCH 020/167] RMT: Don't require carrier_freq_hz to be non-zero if carrier_en unset Closes github #123 https://github.com/espressif/esp-idf/issues/123 --- components/driver/rmt.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/components/driver/rmt.c b/components/driver/rmt.c index e29f190024..a16f0f8fe5 100644 --- a/components/driver/rmt.c +++ b/components/driver/rmt.c @@ -380,10 +380,16 @@ esp_err_t rmt_config(rmt_config_t* rmt_param) uint8_t gpio_num = rmt_param->gpio_num; uint8_t mem_cnt = rmt_param->mem_block_num; int clk_div = rmt_param->clk_div; + uint32_t carrier_freq_hz = rmt_param->tx_config.carrier_freq_hz; + bool carrier_en = rmt_param->tx_config.carrier_en; RMT_CHECK(channel < RMT_CHANNEL_MAX, RMT_CHANNEL_ERROR_STR, ESP_ERR_INVALID_ARG); RMT_CHECK(GPIO_IS_VALID_GPIO(gpio_num), RMT_GPIO_ERROR_STR, ESP_ERR_INVALID_ARG); RMT_CHECK((mem_cnt + channel <= 8 && mem_cnt > 0), RMT_MEM_CNT_ERROR_STR, ESP_ERR_INVALID_ARG); RMT_CHECK((clk_div > 0), RMT_CLK_DIV_ERROR_STR, ESP_ERR_INVALID_ARG); + if (mode == RMT_MODE_TX) { + RMT_CHECK((!carrier_en || carrier_freq_hz > 0), "RMT carrier frequency can't be zero", ESP_ERR_INVALID_ARG); + } + periph_module_enable(PERIPH_RMT_MODULE); RMT.conf_ch[channel].conf0.div_cnt = clk_div; @@ -397,7 +403,6 @@ esp_err_t rmt_config(rmt_config_t* rmt_param) if(mode == RMT_MODE_TX) { uint32_t rmt_source_clk_hz = 0; - uint32_t carrier_freq_hz = rmt_param->tx_config.carrier_freq_hz; uint16_t carrier_duty_percent = rmt_param->tx_config.carrier_duty_percent; uint8_t carrier_level = rmt_param->tx_config.carrier_level; uint8_t idle_level = rmt_param->tx_config.idle_level; @@ -416,16 +421,23 @@ esp_err_t rmt_config(rmt_config_t* rmt_param) portEXIT_CRITICAL(&rmt_spinlock); /*Set carrier*/ - uint32_t duty_div, duty_h, duty_l; - duty_div = rmt_source_clk_hz / carrier_freq_hz; - duty_h = duty_div * carrier_duty_percent / 100; - duty_l = duty_div - duty_h; - RMT.conf_ch[channel].conf0.carrier_out_lv = carrier_level; - RMT.carrier_duty_ch[channel].high = duty_h; - RMT.carrier_duty_ch[channel].low = duty_l; - RMT.conf_ch[channel].conf0.carrier_en = rmt_param->tx_config.carrier_en; + RMT.conf_ch[channel].conf0.carrier_en = carrier_en; + if (carrier_en) { + uint32_t duty_div, duty_h, duty_l; + duty_div = rmt_source_clk_hz / carrier_freq_hz; + duty_h = duty_div * carrier_duty_percent / 100; + duty_l = duty_div - duty_h; + RMT.conf_ch[channel].conf0.carrier_out_lv = carrier_level; + RMT.carrier_duty_ch[channel].high = duty_h; + RMT.carrier_duty_ch[channel].low = duty_l; + } else { + RMT.conf_ch[channel].conf0.carrier_out_lv = 0; + RMT.carrier_duty_ch[channel].high = 0; + RMT.carrier_duty_ch[channel].low = 0; + } ESP_LOGD(RMT_TAG, "Rmt Tx Channel %u|Gpio %u|Sclk_Hz %u|Div %u|Carrier_Hz %u|Duty %u", - channel, gpio_num, rmt_source_clk_hz, clk_div, carrier_freq_hz, carrier_duty_percent); + channel, gpio_num, rmt_source_clk_hz, clk_div, carrier_freq_hz, carrier_duty_percent); + } else if(RMT_MODE_RX == mode) { uint8_t filter_cnt = rmt_param->rx_config.filter_ticks_thresh; From 45571b3c3810f174ee315d603f54f7cd34f96d31 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 13:30:24 +1100 Subject: [PATCH 021/167] LEDC driver: Use ledc_channel_t for all channel arguments Closes github #54: https://github.com/espressif/esp-idf/issues/54 --- components/driver/include/driver/ledc.h | 6 +++--- components/driver/ledc.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/driver/include/driver/ledc.h b/components/driver/include/driver/ledc.h index fb97c6c011..691379a3d8 100644 --- a/components/driver/include/driver/ledc.h +++ b/components/driver/include/driver/ledc.h @@ -253,7 +253,7 @@ int ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel); * - ESP_OK Success * - ESP_ERR_INVALID_ARG Parameter error */ -esp_err_t ledc_set_fade(ledc_mode_t speed_mode, uint32_t channel, uint32_t duty, ledc_duty_direction_t gradule_direction, +esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, ledc_duty_direction_t gradule_direction, uint32_t step_num, uint32_t duty_cyle_num, uint32_t duty_scale); /** @@ -354,7 +354,7 @@ esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, uint32_t timer_sel); * - ESP_OK Success * */ -esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, uint32_t channel, uint32_t timer_idx); +esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t timer_idx); /***************************EXAMPLE********************************** * @@ -391,7 +391,7 @@ esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, uint32_t channel, uint * * ----------------EXAMPLE OF SETTING DUTY --- ----------------- * @code{c} - * uint32_t ledc_channel = LEDC_CHANNEL_0; //LEDC channel(0-73) + * ledc_channel_t ledc_channel = LEDC_CHANNEL_0; //LEDC channel(0-73) * uint32_t duty = 2000; //duty range is 0 ~ ((2**bit_num)-1) * LEDC_set_duty(LEDC_HIGH_SPEED_MODE, ledc_channel, duty); //set speed mode, channel, and duty. * ledc_update_duty(LEDC_HIGH_SPEED_MODE, ledc_channel); //after set duty, we need to call ledc_update_duty to update the settings. diff --git a/components/driver/ledc.c b/components/driver/ledc.c index 77ca975969..c00cf26bb1 100644 --- a/components/driver/ledc.c +++ b/components/driver/ledc.c @@ -44,7 +44,7 @@ esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_ return ESP_OK; } -static esp_err_t ledc_duty_config(ledc_mode_t speed_mode, uint32_t channel_num, uint32_t hpoint_val, uint32_t duty_val, +static esp_err_t ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t hpoint_val, uint32_t duty_val, uint32_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale) { portENTER_CRITICAL(&ledc_spinlock); @@ -58,7 +58,7 @@ static esp_err_t ledc_duty_config(ledc_mode_t speed_mode, uint32_t channel_num, return ESP_OK; } -esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, uint32_t channel, uint32_t timer_idx) +esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t timer_idx) { LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); LEDC_CHECK(timer_idx <= LEDC_TIMER_3, "ledc timer error", ESP_ERR_INVALID_ARG); @@ -239,7 +239,7 @@ esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idl portEXIT_CRITICAL(&ledc_spinlock); return ESP_OK; } -esp_err_t ledc_set_fade(ledc_mode_t speed_mode, uint32_t channel, uint32_t duty, ledc_duty_direction_t fade_direction, +esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, ledc_duty_direction_t fade_direction, uint32_t step_num, uint32_t duty_cyle_num, uint32_t duty_scale) { LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); From 6395081503069ea569d94eb706742e684a4269cd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 13:43:42 +1100 Subject: [PATCH 022/167] uart driver: Set type of uart_driver_install queue param Closes github #91 https://github.com/espressif/esp-idf/issues/91 --- components/driver/include/driver/uart.h | 5 +++-- components/driver/uart.c | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/driver/include/driver/uart.h b/components/driver/include/driver/uart.h index c193fb0ef8..c788374bdc 100644 --- a/components/driver/include/driver/uart.h +++ b/components/driver/include/driver/uart.h @@ -477,7 +477,8 @@ esp_err_t uart_intr_config(uart_port_t uart_num, const uart_intr_config_t *intr_ * @param tx_buffer_size UART TX ring buffer size. * If set to zero, driver will not use TX buffer, TX function will block task until all data have been sent out.. * @param queue_size UART event queue size/depth. - * @param uart_queue UART event queue handle, if set NULL, driver will not use an event queue. + * @param uart_queue UART event queue handle (out param). On success, a new queue handle is written here to provide + * access to UART events. If set to NULL, driver will not use an event queue. * @param intr_alloc_flags Flags used to allocate the interrupt. One or multiple (ORred) * ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info. * @@ -485,7 +486,7 @@ esp_err_t uart_intr_config(uart_port_t uart_num, const uart_intr_config_t *intr_ * - ESP_OK Success * - ESP_FAIL Parameter error */ -esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, void* uart_queue, int intr_alloc_flags); +esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, QueueHandle_t* uart_queue, int intr_alloc_flags); /** * @brief Uninstall UART driver. diff --git a/components/driver/uart.c b/components/driver/uart.c index e85c54d8c4..45f4e23945 100644 --- a/components/driver/uart.c +++ b/components/driver/uart.c @@ -950,7 +950,7 @@ esp_err_t uart_flush(uart_port_t uart_num) return ESP_OK; } -esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, void* uart_queue, int intr_alloc_flags) +esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, QueueHandle_t *uart_queue, int intr_alloc_flags) { UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL); UART_CHECK((rx_buffer_size > UART_FIFO_LEN), "uart rx buffer length error(>128)", ESP_FAIL); @@ -978,7 +978,7 @@ esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_b if(uart_queue) { p_uart_obj[uart_num]->xQueueUart = xQueueCreate(queue_size, sizeof(uart_event_t)); - *((QueueHandle_t*) uart_queue) = p_uart_obj[uart_num]->xQueueUart; + *uart_queue = p_uart_obj[uart_num]->xQueueUart; ESP_LOGI(UART_TAG, "queue free spaces: %d", uxQueueSpacesAvailable(p_uart_obj[uart_num]->xQueueUart)); } else { p_uart_obj[uart_num]->xQueueUart = NULL; From 948a2ba23af4bee60849d5d90dc0f6f28bef6cc0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 13:44:50 +1100 Subject: [PATCH 023/167] uart driver: Remove invalid UART_BITRATE_115200 enum from example Closes github #92 https://github.com/espressif/esp-idf/issues/92 --- components/driver/include/driver/uart.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/driver/include/driver/uart.h b/components/driver/include/driver/uart.h index c788374bdc..68d02a5e0a 100644 --- a/components/driver/include/driver/uart.h +++ b/components/driver/include/driver/uart.h @@ -648,7 +648,7 @@ esp_err_t uart_enable_pattern_det_intr(uart_port_t uart_num, char pattern_chr, u * //a. Set UART parameter * int uart_num = 0; //uart port number * uart_config_t uart_config = { - * .baud_rate = UART_BITRATE_115200, //baudrate + * .baud_rate = 115200, //baudrate * .data_bits = UART_DATA_8_BITS, //data bit mode * .parity = UART_PARITY_DISABLE, //parity mode * .stop_bits = UART_STOP_BITS_1, //stop bit mode From 9496fda66285c949fc258b11049832d1953d3c86 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 13:54:42 +1100 Subject: [PATCH 024/167] RMT driver: Rename rmt_set_evt_intr_en to rmt_set_tx_thr_intr_en Closes github #115: https://github.com/espressif/esp-idf/issues/115 --- components/driver/include/driver/rmt.h | 6 ++++-- components/driver/rmt.c | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/driver/include/driver/rmt.h b/components/driver/include/driver/rmt.h index 24df1ac8ed..36e33e732e 100644 --- a/components/driver/include/driver/rmt.h +++ b/components/driver/include/driver/rmt.h @@ -524,7 +524,9 @@ esp_err_t rmt_set_err_intr_en(rmt_channel_t channel, bool en); esp_err_t rmt_set_tx_intr_en(rmt_channel_t channel, bool en); /** - * @brief Set RMT TX event interrupt enable + * @brief Set RMT TX threshold event interrupt enable + * + * Causes an interrupt when a threshold number of items have been transmitted. * * @param channel RMT channel (0 - 7) * @@ -536,7 +538,7 @@ esp_err_t rmt_set_tx_intr_en(rmt_channel_t channel, bool en); * - ESP_ERR_INVALID_ARG Parameter error * - ESP_OK Success */ -esp_err_t rmt_set_evt_intr_en(rmt_channel_t channel, bool en, uint16_t evt_thresh); +esp_err_t rmt_set_tx_thr_intr_en(rmt_channel_t channel, bool en, uint16_t evt_thresh); /** * @brief Set RMT pins diff --git a/components/driver/rmt.c b/components/driver/rmt.c index a16f0f8fe5..d277ea00cd 100644 --- a/components/driver/rmt.c +++ b/components/driver/rmt.c @@ -341,7 +341,7 @@ esp_err_t rmt_set_tx_intr_en(rmt_channel_t channel, bool en) return ESP_OK; } -esp_err_t rmt_set_evt_intr_en(rmt_channel_t channel, bool en, uint16_t evt_thresh) +esp_err_t rmt_set_tx_thr_intr_en(rmt_channel_t channel, bool en, uint16_t evt_thresh) { RMT_CHECK(channel < RMT_CHANNEL_MAX, RMT_CHANNEL_ERROR_STR, ESP_ERR_INVALID_ARG); RMT_CHECK(evt_thresh < 256, "RMT EVT THRESH ERR", ESP_ERR_INVALID_ARG); @@ -624,7 +624,7 @@ esp_err_t rmt_driver_uninstall(rmt_channel_t channel) rmt_set_rx_intr_en(channel, 0); rmt_set_err_intr_en(channel, 0); rmt_set_tx_intr_en(channel, 0); - rmt_set_evt_intr_en(channel, 0, 0xffff); + rmt_set_tx_thr_intr_en(channel, 0, 0xffff); if(p_rmt_obj[channel]->tx_sem) { vSemaphoreDelete(p_rmt_obj[channel]->tx_sem); p_rmt_obj[channel]->tx_sem = NULL; @@ -697,7 +697,7 @@ esp_err_t rmt_write_items(rmt_channel_t channel, rmt_item32_t* rmt_item, int ite RMT.apb_conf.mem_tx_wrap_en = 1; len_rem -= item_block_len; RMT.conf_ch[channel].conf1.tx_conti_mode = 0; - rmt_set_evt_intr_en(channel, 1, item_sub_len); + rmt_set_tx_thr_intr_en(channel, 1, item_sub_len); p_rmt->tx_data = rmt_item + item_block_len; p_rmt->tx_len_rem = len_rem; p_rmt->tx_offset = 0; From 665dcc571280900ee206966fded16d39948ba3a2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 14:20:00 +1100 Subject: [PATCH 025/167] linux docs: Add note about precompiled gdb on Arch Closes github #150: https://github.com/espressif/esp-idf/issues/150 --- docs/linux-setup.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/linux-setup.rst b/docs/linux-setup.rst index cf5e78b63d..20f460aa62 100644 --- a/docs/linux-setup.rst +++ b/docs/linux-setup.rst @@ -17,7 +17,6 @@ To compile with ESP-IDF you need to get the following packages: sudo pacman -S --needed gcc git make ncurses flex bison gperf python2-pyserial - Step 1: Download binary toolchain for the ESP32 ================================================== @@ -49,6 +48,16 @@ Alternatively, you may create an alias for the above command. This way you can g Then when you need the toolchain you can type ``get_esp32`` on the command line and the toolchain will be added to your ``PATH``. +Arch Linux Users +---------------- + +To run the precompiled gdb (xtensa-esp32-elf-gdb) in Arch Linux requires ncurses 5, but Arch uses ncurses 6. Backwards compatibility libraries are available in AUR_ for native and lib32 configurations: +- https://aur.archlinux.org/packages/ncurses5-compat-libs/ +- https://aur.archlinux.org/packages/lib32-ncurses5-compat-libs/ + +(Alternatively, use crosstool-NG to compile a gdb that links against ncurses 6.) + + Alternative Step 1: Compile the toolchain from source using crosstool-NG ======================================================================== @@ -156,3 +165,4 @@ Further reading If you'd like to use the Eclipse IDE instead of running ``make``, check out the Eclipse setup guide in this directory. +.. _AUR: https://wiki.archlinux.org/index.php/Arch_User_Repository From e6b09dc258a415c5cda5bdc282ffc02e59ed8821 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 14:47:14 +1100 Subject: [PATCH 026/167] FreeRTOS: Default to canary byte stack overflow checking Was mistakenly "none" due to name change not being propagated. Closes github issue #181: https://github.com/espressif/esp-idf/issues/181 --- components/freertos/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index f03da6bc01..2489270c05 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -54,7 +54,7 @@ config FREERTOS_ASSERT_ON_UNTESTED_FUNCTION choice FREERTOS_CHECK_STACKOVERFLOW prompt "Check for stack overflow" - default FREERTOS_CHECK_STACKOVERFLOW_QUICK + default FREERTOS_CHECK_STACKOVERFLOW_CANARY help FreeRTOS can check for stack overflows in threads and trigger an user function called vApplicationStackOverflowHook when this happens. From 06e03ff52e0737ba6ecd6ec0e40a7d0ef8e48335 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 22 Dec 2016 12:42:21 +1100 Subject: [PATCH 027/167] Replace backwards-compatible portTICK_RATE_MS with FreeRTOS v8+ portTICK_PERIOD_MS Closes github #51 https://github.com/espressif/esp-idf/issues/51 --- components/bt/bluedroid/btc/core/btc_task.c | 2 +- components/bt/bluedroid/hci/hci_hal_h4.c | 2 +- components/bt/bluedroid/hci/hci_layer.c | 2 +- components/bt/bluedroid/osi/osi_arch.c | 8 ++++---- components/bt/bluedroid/stack/btu/btu_task.c | 2 +- components/bt/bt.c | 2 +- components/esp32/test/test_intr_alloc.c | 16 ++++++++-------- components/ethernet/emac_main.c | 4 ++-- .../freertos/test/test_freertos_eventgroups.c | 4 ++-- components/lwip/port/freertos/sys_arch.c | 14 +++++++------- examples/01_hello_world/main/hello_world_main.c | 2 +- examples/02_blink/main/blink.c | 4 ++-- .../03_http_request/main/http_request_main.c | 10 +++++----- .../04_https_request/main/https_request_main.c | 2 +- examples/07_nvs_rw_value/main/nvs_rw_value.c | 2 +- examples/08_nvs_rw_blob/main/nvs_rw_blob.c | 4 ++-- examples/11_rmt_nec_tx_rx/main/infrared_nec.c | 2 +- examples/12_blufi/components/blufi/blufi_task.c | 2 +- examples/16_pcnt/main/pcnt_test.c | 2 +- examples/17_ethernet/main/ethernet_main.c | 4 ++-- examples/19_sigmadelta/main/sigmadelta_test.c | 2 +- 21 files changed, 46 insertions(+), 46 deletions(-) diff --git a/components/bt/bluedroid/btc/core/btc_task.c b/components/bt/bluedroid/btc/core/btc_task.c index b4ce0d95ca..773d7889bb 100644 --- a/components/bt/bluedroid/btc/core/btc_task.c +++ b/components/bt/bluedroid/btc/core/btc_task.c @@ -80,7 +80,7 @@ static bt_status_t btc_task_post(btc_msg_t *msg) return BT_STATUS_PARM_INVALID; } - if (xQueueSend(xBtcQueue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + if (xQueueSend(xBtcQueue, msg, 10 / portTICK_PERIOD_MS) != pdTRUE) { LOG_ERROR("Btc Post failed\n"); return BT_STATUS_BUSY; } diff --git a/components/bt/bluedroid/hci/hci_hal_h4.c b/components/bt/bluedroid/hci/hci_hal_h4.c index 922ee6ecc0..237226266a 100644 --- a/components/bt/bluedroid/hci/hci_hal_h4.c +++ b/components/bt/bluedroid/hci/hci_hal_h4.c @@ -177,7 +177,7 @@ void hci_hal_h4_task_post(void) evt.sig = 0xff; evt.par = 0; - if (xQueueSend(xHciH4Queue, &evt, 10 / portTICK_RATE_MS) != pdTRUE) { + if (xQueueSend(xHciH4Queue, &evt, 10 / portTICK_PERIOD_MS) != pdTRUE) { LOG_ERROR("xHciH4Queue failed\n"); } } diff --git a/components/bt/bluedroid/hci/hci_layer.c b/components/bt/bluedroid/hci/hci_layer.c index e0f15e0ea9..d71690d4a6 100644 --- a/components/bt/bluedroid/hci/hci_layer.c +++ b/components/bt/bluedroid/hci/hci_layer.c @@ -146,7 +146,7 @@ void hci_host_task_post(void) evt.sig = 0xff; evt.par = 0; - if (xQueueSend(xHciHostQueue, &evt, 10 / portTICK_RATE_MS) != pdTRUE) { + if (xQueueSend(xHciHostQueue, &evt, 10 / portTICK_PERIOD_MS) != pdTRUE) { LOG_ERROR("xHciHostQueue failed\n"); } } diff --git a/components/bt/bluedroid/osi/osi_arch.c b/components/bt/bluedroid/osi/osi_arch.c index e896efd871..d1d0185aec 100644 --- a/components/bt/bluedroid/osi/osi_arch.c +++ b/components/bt/bluedroid/osi/osi_arch.c @@ -137,9 +137,9 @@ osi_sem_wait(osi_sem_t *sem, uint32_t timeout) StartTime = xTaskGetTickCount(); if (timeout != 0) { - if (xSemaphoreTake(*sem, timeout / portTICK_RATE_MS) == pdTRUE) { + if (xSemaphoreTake(*sem, timeout / portTICK_PERIOD_MS) == pdTRUE) { EndTime = xTaskGetTickCount(); - Elapsed = (EndTime - StartTime) * portTICK_RATE_MS; + Elapsed = (EndTime - StartTime) * portTICK_PERIOD_MS; if (Elapsed == 0) { Elapsed = 1; @@ -153,7 +153,7 @@ osi_sem_wait(osi_sem_t *sem, uint32_t timeout) while (xSemaphoreTake(*sem, portMAX_DELAY) != pdTRUE); EndTime = xTaskGetTickCount(); - Elapsed = (EndTime - StartTime) * portTICK_RATE_MS; + Elapsed = (EndTime - StartTime) * portTICK_PERIOD_MS; if (Elapsed == 0) { Elapsed = 1; @@ -190,7 +190,7 @@ osi_now(void) void osi_delay_ms(uint32_t ms) { - vTaskDelay(ms / portTICK_RATE_MS); + vTaskDelay(ms / portTICK_PERIOD_MS); } diff --git a/components/bt/bluedroid/stack/btu/btu_task.c b/components/bt/bluedroid/stack/btu/btu_task.c index 5cca29dd83..4c640fad7c 100644 --- a/components/bt/bluedroid/stack/btu/btu_task.c +++ b/components/bt/bluedroid/stack/btu/btu_task.c @@ -338,7 +338,7 @@ void btu_task_post(uint32_t sig) evt.sig = sig; evt.par = 0; - if (xQueueSend(xBtuQueue, &evt, 10 / portTICK_RATE_MS) != pdTRUE) { + if (xQueueSend(xBtuQueue, &evt, 10 / portTICK_PERIOD_MS) != pdTRUE) { LOG_ERROR("xBtuQueue failed\n"); } } diff --git a/components/bt/bt.c b/components/bt/bt.c index ef9a063d69..8d1fecb777 100644 --- a/components/bt/bt.c +++ b/components/bt/bt.c @@ -85,7 +85,7 @@ static int32_t IRAM_ATTR semphr_give_from_isr_wrapper(void *semphr, void *hptw) static int32_t IRAM_ATTR semphr_take_wrapper(void *semphr, uint32_t block_time_ms) { - return (int32_t)xSemaphoreTake(semphr, block_time_ms / portTICK_RATE_MS); + return (int32_t)xSemaphoreTake(semphr, block_time_ms / portTICK_PERIOD_MS); } static void *IRAM_ATTR mutex_create_wrapper(void) diff --git a/components/esp32/test/test_intr_alloc.c b/components/esp32/test/test_intr_alloc.c index 31991b4e41..329fc9a9e4 100644 --- a/components/esp32/test/test_intr_alloc.c +++ b/components/esp32/test/test_intr_alloc.c @@ -98,7 +98,7 @@ static void timer_test(int flags) { esp_intr_get_intno(inth[0]), esp_intr_get_intno(inth[1]), esp_intr_get_intno(inth[2]), esp_intr_get_intno(inth[3])); printf("Timer values on start: %d %d %d %d\n", count[0], count[1], count[2], count[3]); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); printf("Timer values after 1 sec: %d %d %d %d\n", count[0], count[1], count[2], count[3]); TEST_ASSERT(count[0]==0); TEST_ASSERT(count[1]!=0); @@ -110,7 +110,7 @@ static void timer_test(int flags) { esp_intr_disable(inth[1]); esp_intr_disable(inth[2]); for (x=0; x<4; x++) count[x]=0; - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); printf("Timer values after 1 sec: %d %d %d %d\n", count[0], count[1], count[2], count[3]); TEST_ASSERT(count[0]!=0); TEST_ASSERT(count[1]==0); @@ -122,7 +122,7 @@ static void timer_test(int flags) { esp_intr_disable(inth[0]); esp_intr_disable(inth[3]); for (x=0; x<4; x++) count[x]=0; - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); printf("Timer values after 1 sec: %d %d %d %d\n", count[0], count[1], count[2], count[3]); TEST_ASSERT(count[0]==0); TEST_ASSERT(count[1]!=0); @@ -152,18 +152,18 @@ void local_timer_test() printf("Int timer 1 intno %d\n", esp_intr_get_intno(ih)); xthal_set_ccompare(1, xthal_get_ccount()+8000000); int_timer_ctr=0; - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); printf("Timer val after 1 sec: %d\n", int_timer_ctr); TEST_ASSERT(int_timer_ctr!=0); printf("Disabling int\n"); esp_intr_disable(ih); int_timer_ctr=0; - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); printf("Timer val after 1 sec: %d\n", int_timer_ctr); TEST_ASSERT(int_timer_ctr==0); printf("Re-enabling\n"); esp_intr_enable(ih); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); printf("Timer val after 1 sec: %d\n", int_timer_ctr); TEST_ASSERT(int_timer_ctr!=0); @@ -173,12 +173,12 @@ void local_timer_test() r=esp_intr_alloc(ETS_INTERNAL_TIMER1_INTR_SOURCE, ESP_INTR_FLAG_INTRDISABLED, int_timer_handler, NULL, &ih); TEST_ASSERT(r==ESP_OK); int_timer_ctr=0; - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); printf("Timer val after 1 sec: %d\n", int_timer_ctr); TEST_ASSERT(int_timer_ctr==0); printf("Re-enabling\n"); esp_intr_enable(ih); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); printf("Timer val after 1 sec: %d\n", int_timer_ctr); TEST_ASSERT(int_timer_ctr!=0); r=esp_intr_free(ih); diff --git a/components/ethernet/emac_main.c b/components/ethernet/emac_main.c index ab2ca8964c..20d428cf7b 100644 --- a/components/ethernet/emac_main.c +++ b/components/ethernet/emac_main.c @@ -585,7 +585,7 @@ void emac_link_check_func(void *pv_parameters) static bool emac_link_check_timer_init(void) { - emac_timer = xTimerCreate("emac_timer", (2000 / portTICK_RATE_MS), + emac_timer = xTimerCreate("emac_timer", (2000 / portTICK_PERIOD_MS), pdTRUE, (void *)rand(), emac_link_check_func); if (emac_timer == NULL) { return false; @@ -844,7 +844,7 @@ esp_err_t IRAM_ATTR emac_post(emac_sig_t sig, emac_par_t par) evt.sig = sig; evt.par = par; - if (xQueueSend(emac_xqueue, &evt, 10 / portTICK_RATE_MS) != pdTRUE) { + if (xQueueSend(emac_xqueue, &evt, 10 / portTICK_PERIOD_MS) != pdTRUE) { return ESP_FAIL; } } diff --git a/components/freertos/test/test_freertos_eventgroups.c b/components/freertos/test/test_freertos_eventgroups.c index 35a5cc4ed2..b17e127c28 100644 --- a/components/freertos/test/test_freertos_eventgroups.c +++ b/components/freertos/test/test_freertos_eventgroups.c @@ -34,7 +34,7 @@ static void task_event_group_call_response(void *param) printf("Task %d done\n", task_num); /* Delay is due to not-yet-fixed bug with deleting tasks at same time */ - vTaskDelay(100 / portTICK_RATE_MS); + vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelete(NULL); } @@ -85,7 +85,7 @@ static void task_test_sync(void *param) printf("Done %d = %x\n", task_num, after_done); /* Delay is due to not-yet-fixed bug with deleting tasks at same time */ - vTaskDelay(100 / portTICK_RATE_MS); + vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelete(NULL); } diff --git a/components/lwip/port/freertos/sys_arch.c b/components/lwip/port/freertos/sys_arch.c index 97ee32d1bd..7f96c9b859 100755 --- a/components/lwip/port/freertos/sys_arch.c +++ b/components/lwip/port/freertos/sys_arch.c @@ -164,9 +164,9 @@ sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout) StartTime = xTaskGetTickCount(); if (timeout != 0) { - if (xSemaphoreTake(*sem, timeout / portTICK_RATE_MS) == pdTRUE) { + if (xSemaphoreTake(*sem, timeout / portTICK_PERIOD_MS) == pdTRUE) { EndTime = xTaskGetTickCount(); - Elapsed = (EndTime - StartTime) * portTICK_RATE_MS; + Elapsed = (EndTime - StartTime) * portTICK_PERIOD_MS; if (Elapsed == 0) { Elapsed = 1; @@ -180,7 +180,7 @@ sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout) while (xSemaphoreTake(*sem, portMAX_DELAY) != pdTRUE); EndTime = xTaskGetTickCount(); - Elapsed = (EndTime - StartTime) * portTICK_RATE_MS; + Elapsed = (EndTime - StartTime) * portTICK_PERIOD_MS; if (Elapsed == 0) { Elapsed = 1; @@ -293,9 +293,9 @@ sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout) sys_mutex_lock(&(*mbox)->lock); if (timeout != 0) { - if (pdTRUE == xQueueReceive((*mbox)->os_mbox, &(*msg), timeout / portTICK_RATE_MS)) { + if (pdTRUE == xQueueReceive((*mbox)->os_mbox, &(*msg), timeout / portTICK_PERIOD_MS)) { EndTime = xTaskGetTickCount(); - Elapsed = (EndTime - StartTime) * portTICK_RATE_MS; + Elapsed = (EndTime - StartTime) * portTICK_PERIOD_MS; if (Elapsed == 0) { Elapsed = 1; @@ -323,7 +323,7 @@ sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout) } EndTime = xTaskGetTickCount(); - Elapsed = (EndTime - StartTime) * portTICK_RATE_MS; + Elapsed = (EndTime - StartTime) * portTICK_PERIOD_MS; if (Elapsed == 0) { Elapsed = 1; @@ -566,7 +566,7 @@ void sys_thread_sem_deinit(void) void sys_delay_ms(uint32_t ms) { - vTaskDelay(ms/portTICK_RATE_MS); + vTaskDelay(ms / portTICK_PERIOD_MS); } diff --git a/examples/01_hello_world/main/hello_world_main.c b/examples/01_hello_world/main/hello_world_main.c index 0e872522fa..c8b9f5f0c9 100644 --- a/examples/01_hello_world/main/hello_world_main.c +++ b/examples/01_hello_world/main/hello_world_main.c @@ -17,7 +17,7 @@ void hello_task(void *pvParameter) printf("Hello world!\n"); for (int i = 10; i >= 0; i--) { printf("Restarting in %d seconds...\n", i); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); } printf("Restarting now.\n"); fflush(stdout); diff --git a/examples/02_blink/main/blink.c b/examples/02_blink/main/blink.c index 1e49e51b2f..f97572ac21 100644 --- a/examples/02_blink/main/blink.c +++ b/examples/02_blink/main/blink.c @@ -33,10 +33,10 @@ void blink_task(void *pvParameter) while(1) { /* Blink off (output low) */ gpio_set_level(BLINK_GPIO, 0); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); /* Blink on (output high) */ gpio_set_level(BLINK_GPIO, 1); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); } } diff --git a/examples/03_http_request/main/http_request_main.c b/examples/03_http_request/main/http_request_main.c index 9fe1933373..3831ae65b9 100644 --- a/examples/03_http_request/main/http_request_main.c +++ b/examples/03_http_request/main/http_request_main.c @@ -115,7 +115,7 @@ static void http_get_task(void *pvParameters) if(err != 0 || res == NULL) { ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); continue; } @@ -129,7 +129,7 @@ static void http_get_task(void *pvParameters) if(s < 0) { ESP_LOGE(TAG, "... Failed to allocate socket."); freeaddrinfo(res); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); continue; } ESP_LOGI(TAG, "... allocated socket\r\n"); @@ -138,7 +138,7 @@ static void http_get_task(void *pvParameters) ESP_LOGE(TAG, "... socket connect failed errno=%d", errno); close(s); freeaddrinfo(res); - vTaskDelay(4000 / portTICK_RATE_MS); + vTaskDelay(4000 / portTICK_PERIOD_MS); continue; } @@ -148,7 +148,7 @@ static void http_get_task(void *pvParameters) if (write(s, REQUEST, strlen(REQUEST)) < 0) { ESP_LOGE(TAG, "... socket send failed"); close(s); - vTaskDelay(4000 / portTICK_RATE_MS); + vTaskDelay(4000 / portTICK_PERIOD_MS); continue; } ESP_LOGI(TAG, "... socket send success"); @@ -166,7 +166,7 @@ static void http_get_task(void *pvParameters) close(s); for(int countdown = 10; countdown >= 0; countdown--) { ESP_LOGI(TAG, "%d... ", countdown); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "Starting again!"); } diff --git a/examples/04_https_request/main/https_request_main.c b/examples/04_https_request/main/https_request_main.c index caf3f374a3..933d97ac83 100644 --- a/examples/04_https_request/main/https_request_main.c +++ b/examples/04_https_request/main/https_request_main.c @@ -362,7 +362,7 @@ static void https_get_task(void *pvParameters) for(int countdown = 10; countdown >= 0; countdown--) { ESP_LOGI(TAG, "%d...", countdown); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); } ESP_LOGI(TAG, "Starting again!"); } diff --git a/examples/07_nvs_rw_value/main/nvs_rw_value.c b/examples/07_nvs_rw_value/main/nvs_rw_value.c index dac2d4077e..1b3e06b859 100644 --- a/examples/07_nvs_rw_value/main/nvs_rw_value.c +++ b/examples/07_nvs_rw_value/main/nvs_rw_value.c @@ -72,7 +72,7 @@ void app_main() // Restart module for (int i = 10; i >= 0; i--) { printf("Restarting in %d seconds...\n", i); - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); } printf("Restarting now.\n"); fflush(stdout); diff --git a/examples/08_nvs_rw_blob/main/nvs_rw_blob.c b/examples/08_nvs_rw_blob/main/nvs_rw_blob.c index 7c13c15ba7..0d4b7db4ee 100644 --- a/examples/08_nvs_rw_blob/main/nvs_rw_blob.c +++ b/examples/08_nvs_rw_blob/main/nvs_rw_blob.c @@ -164,7 +164,7 @@ void app_main() */ while (1) { if (gpio_get_level(GPIO_NUM_0) == 0) { - vTaskDelay(1000 / portTICK_RATE_MS); + vTaskDelay(1000 / portTICK_PERIOD_MS); if(gpio_get_level(GPIO_NUM_0) == 0) { err = save_run_time(); if (err != ESP_OK) printf("Error (%d) saving run time blob to NVS!\n", err); @@ -173,6 +173,6 @@ void app_main() esp_restart(); } } - vTaskDelay(200 / portTICK_RATE_MS); + vTaskDelay(200 / portTICK_PERIOD_MS); } } diff --git a/examples/11_rmt_nec_tx_rx/main/infrared_nec.c b/examples/11_rmt_nec_tx_rx/main/infrared_nec.c index d2f7b091fa..ea42502749 100644 --- a/examples/11_rmt_nec_tx_rx/main/infrared_nec.c +++ b/examples/11_rmt_nec_tx_rx/main/infrared_nec.c @@ -352,7 +352,7 @@ void rmt_nec_tx_task() rmt_wait_tx_done(channel); //before we free the data, make sure sending is already done. free(item); - vTaskDelay(2000 / portTICK_RATE_MS); + vTaskDelay(2000 / portTICK_PERIOD_MS); } vTaskDelete(NULL); } diff --git a/examples/12_blufi/components/blufi/blufi_task.c b/examples/12_blufi/components/blufi/blufi_task.c index cda66c0511..75547c6432 100644 --- a/examples/12_blufi/components/blufi/blufi_task.c +++ b/examples/12_blufi/components/blufi/blufi_task.c @@ -69,7 +69,7 @@ static esp_err_t blufi_task_post(uint32_t sig, void *par, void *cb, void *arg) evt.cb = cb; evt.arg = arg; - if (xQueueSend(xBlufiTaskQueue, &evt, 10 / portTICK_RATE_MS) != pdTRUE) { + if (xQueueSend(xBlufiTaskQueue, &evt, 10 / portTICK_PERIOD_MS) != pdTRUE) { LOG_ERROR("Blufi Post failed\n"); return ESP_FAIL; } diff --git a/examples/16_pcnt/main/pcnt_test.c b/examples/16_pcnt/main/pcnt_test.c index b8489ecb2f..0185f9b6f9 100644 --- a/examples/16_pcnt/main/pcnt_test.c +++ b/examples/16_pcnt/main/pcnt_test.c @@ -196,7 +196,7 @@ void app_main() portBASE_TYPE res; while(1) { - res = xQueueReceive(pcnt_evt_queue, &evt, 1000 / portTICK_RATE_MS); + res = xQueueReceive(pcnt_evt_queue, &evt, 1000 / portTICK_PERIOD_MS); if(res == pdTRUE) { pcnt_get_counter_value(PCNT_TEST_UNIT, &count); printf("Event PCNT unit[%d]; cnt: %d\n", evt.unit, count); diff --git a/examples/17_ethernet/main/ethernet_main.c b/examples/17_ethernet/main/ethernet_main.c index 7e84a9badd..fc4347f7d8 100644 --- a/examples/17_ethernet/main/ethernet_main.c +++ b/examples/17_ethernet/main/ethernet_main.c @@ -108,11 +108,11 @@ void eth_task(void *pvParameter) { tcpip_adapter_ip_info_t ip; memset(&ip, 0, sizeof(tcpip_adapter_ip_info_t)); - vTaskDelay(2000 / portTICK_RATE_MS); + vTaskDelay(2000 / portTICK_PERIOD_MS); while (1) { - vTaskDelay(2000 / portTICK_RATE_MS); + vTaskDelay(2000 / portTICK_PERIOD_MS); if (tcpip_adapter_get_ip_info(ESP_IF_ETH, &ip) == 0) { ESP_LOGI(TAG, "\n~~~~~~~~~~~\n"); diff --git a/examples/19_sigmadelta/main/sigmadelta_test.c b/examples/19_sigmadelta/main/sigmadelta_test.c index 92bc6e0a83..60880311d5 100644 --- a/examples/19_sigmadelta/main/sigmadelta_test.c +++ b/examples/19_sigmadelta/main/sigmadelta_test.c @@ -46,7 +46,7 @@ void app_main() while(1) { sigmadelta_set_duty(SIGMADELTA_CHANNEL_0, duty); /*by changing delay time, you can change the blink frequency of LED. */ - vTaskDelay(10 / portTICK_RATE_MS); + vTaskDelay(10 / portTICK_PERIOD_MS); duty += inc; if(duty == 127 || duty == -127) inc = (-1) * inc; From ade7ee20920f733a682f5be6ed9e6705c5db5ceb Mon Sep 17 00:00:00 2001 From: Wangjialin Date: Sat, 24 Dec 2016 20:45:57 +0800 Subject: [PATCH 028/167] gpio_driver: add per-pin interrupt handlers 1. add ISR handler apis so that users of different layers can hook their own isr handler on different GPIO. Audio project has different software layers, they need different gpio isr handler for layer instead of processing all GPIO interrupts in one handler. If this kind of calling a handler from isr is not proper, please kindly point out. 2. add gpio example code. 3. improve gpio.rst 4. add readme for gpio example Squashed commits: [278e50f] update: GPIO 1. coding style, add a space between conditional or loop keyword and an opening paren. 2. modify some return value and doc 3. use printf in example code Squashed commits: [efb23bb] minor change of comment --- components/driver/gpio.c | 134 +++++++++++++++++++++--- components/driver/include/driver/gpio.h | 128 ++++++++-------------- docs/api/gpio.rst | 28 +++-- examples/21_gpio/Makefile | 9 ++ examples/21_gpio/README.md | 20 ++++ examples/21_gpio/main/component.mk | 3 + examples/21_gpio/main/gpio_test.c | 114 ++++++++++++++++++++ 7 files changed, 330 insertions(+), 106 deletions(-) create mode 100644 examples/21_gpio/Makefile create mode 100644 examples/21_gpio/README.md create mode 100644 examples/21_gpio/main/component.mk create mode 100644 examples/21_gpio/main/gpio_test.c diff --git a/components/driver/gpio.c b/components/driver/gpio.c index 3201372bc1..4e83705408 100644 --- a/components/driver/gpio.c +++ b/components/driver/gpio.c @@ -72,45 +72,59 @@ const uint32_t GPIO_PIN_MUX_REG[GPIO_PIN_COUNT] = { GPIO_PIN_REG_39 }; -esp_err_t gpio_pullup_en(gpio_num_t gpio_num) { +typedef struct { + gpio_isr_t fn; /*!< isr function */ + void* args; /*!< isr function args */ +} gpio_isr_func_t; + +static gpio_isr_func_t* gpio_isr_func = NULL; +static gpio_isr_handle_t gpio_isr_handle; +static portMUX_TYPE gpio_spinlock = portMUX_INITIALIZER_UNLOCKED; + +esp_err_t gpio_pullup_en(gpio_num_t gpio_num) +{ GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); - if(RTC_GPIO_IS_VALID_GPIO(gpio_num)){ + if (RTC_GPIO_IS_VALID_GPIO(gpio_num)) { rtc_gpio_pullup_en(gpio_num); - }else{ + } else { REG_SET_BIT(GPIO_PIN_MUX_REG[gpio_num], FUN_PU); } return ESP_OK; } -esp_err_t gpio_pullup_dis(gpio_num_t gpio_num) { +esp_err_t gpio_pullup_dis(gpio_num_t gpio_num) +{ GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); - if(RTC_GPIO_IS_VALID_GPIO(gpio_num)){ + if (RTC_GPIO_IS_VALID_GPIO(gpio_num)) { rtc_gpio_pullup_dis(gpio_num); - }else{ + } else { REG_CLR_BIT(GPIO_PIN_MUX_REG[gpio_num], FUN_PU); } return ESP_OK; } -esp_err_t gpio_pulldown_en(gpio_num_t gpio_num) { +esp_err_t gpio_pulldown_en(gpio_num_t gpio_num) +{ GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); - if(RTC_GPIO_IS_VALID_GPIO(gpio_num)){ + if (RTC_GPIO_IS_VALID_GPIO(gpio_num)) { rtc_gpio_pulldown_en(gpio_num); - }else{ - REG_SET_BIT(GPIO_PIN_MUX_REG[gpio_num], FUN_PD); + } else { + REG_SET_BIT(GPIO_PIN_MUX_REG[gpio_num], FUN_PD); } return ESP_OK; } -esp_err_t gpio_pulldown_dis(gpio_num_t gpio_num) { +esp_err_t gpio_pulldown_dis(gpio_num_t gpio_num) +{ GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); - if(RTC_GPIO_IS_VALID_GPIO(gpio_num)){ - rtc_gpio_pulldown_dis(gpio_num); - }else{ + if (RTC_GPIO_IS_VALID_GPIO(gpio_num)) { + rtc_gpio_pulldown_dis(gpio_num); + } else { REG_CLR_BIT(GPIO_PIN_MUX_REG[gpio_num], FUN_PD); } return ESP_OK; } + esp_err_t gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type) { GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); @@ -323,6 +337,96 @@ esp_err_t gpio_config(gpio_config_t *pGPIOConfig) return ESP_OK; } +void IRAM_ATTR gpio_intr_service(void* arg) +{ + //GPIO intr process + uint32_t gpio_num = 0; + //read status to get interrupt status for GPIO0-31 + uint32_t gpio_intr_status; + gpio_intr_status = GPIO.status; + //read status1 to get interrupt status for GPIO32-39 + uint32_t gpio_intr_status_h; + gpio_intr_status_h = GPIO.status1.intr_st; + + if (gpio_isr_func == NULL) { + return; + } + do { + if (gpio_num < 32) { + if (gpio_intr_status & BIT(gpio_num)) { //gpio0-gpio31 + if (gpio_isr_func[gpio_num].fn != NULL) { + gpio_isr_func[gpio_num].fn(gpio_isr_func[gpio_num].args); + } + GPIO.status_w1tc = BIT(gpio_num); + } + } else { + if (gpio_intr_status_h & BIT(gpio_num - 32)) { + if (gpio_isr_func[gpio_num].fn != NULL) { + gpio_isr_func[gpio_num].fn(gpio_isr_func[gpio_num].args); + } + GPIO.status1_w1tc.intr_st = BIT(gpio_num - 32); + } + } + } while (++gpio_num < GPIO_PIN_COUNT); +} + +esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void* args) +{ + GPIO_CHECK(gpio_isr_func != NULL, "GPIO isr service is not installed, call gpio_install_isr_service() first", ESP_ERR_INVALID_STATE); + GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); + portENTER_CRITICAL(&gpio_spinlock); + gpio_intr_disable(gpio_num); + if (gpio_isr_func) { + gpio_isr_func[gpio_num].fn = isr_handler; + gpio_isr_func[gpio_num].args = args; + } + gpio_intr_enable(gpio_num); + portEXIT_CRITICAL(&gpio_spinlock); + return ESP_OK; +} + +esp_err_t gpio_isr_handler_remove(gpio_num_t gpio_num) +{ + GPIO_CHECK(gpio_isr_func != NULL, "GPIO isr service is not installed, call gpio_install_isr_service() first", ESP_ERR_INVALID_STATE); + GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); + portENTER_CRITICAL(&gpio_spinlock); + gpio_intr_disable(gpio_num); + if (gpio_isr_func) { + gpio_isr_func[gpio_num].fn = NULL; + gpio_isr_func[gpio_num].args = NULL; + } + portEXIT_CRITICAL(&gpio_spinlock); + return ESP_OK; +} + +esp_err_t gpio_install_isr_service(int intr_alloc_flags) +{ + GPIO_CHECK(gpio_isr_func == NULL, "GPIO isr service already installed", ESP_FAIL); + esp_err_t ret; + portENTER_CRITICAL(&gpio_spinlock); + gpio_isr_func = (gpio_isr_func_t*) calloc(GPIO_NUM_MAX, sizeof(gpio_isr_func_t)); + if (gpio_isr_func == NULL) { + ret = ESP_ERR_NO_MEM; + } else { + ret = gpio_isr_register(gpio_intr_service, NULL, intr_alloc_flags, &gpio_isr_handle); + } + portEXIT_CRITICAL(&gpio_spinlock); + return ret; +} + +void gpio_uninstall_isr_service() +{ + if (gpio_isr_func == NULL) { + return; + } + portENTER_CRITICAL(&gpio_spinlock); + esp_intr_free(gpio_isr_handle); + free(gpio_isr_func); + gpio_isr_func = NULL; + portEXIT_CRITICAL(&gpio_spinlock); + return; +} + esp_err_t gpio_isr_register(void (*fn)(void*), void * arg, int intr_alloc_flags, gpio_isr_handle_t *handle) { GPIO_CHECK(fn, "GPIO ISR null", ESP_ERR_INVALID_ARG); @@ -334,7 +438,7 @@ esp_err_t gpio_wakeup_enable(gpio_num_t gpio_num, gpio_int_type_t intr_type) { GPIO_CHECK(GPIO_IS_VALID_GPIO(gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG); esp_err_t ret = ESP_OK; - if ((intr_type == GPIO_INTR_LOW_LEVEL) || (intr_type == GPIO_INTR_HIGH_LEVEL)) { + if (( intr_type == GPIO_INTR_LOW_LEVEL ) || ( intr_type == GPIO_INTR_HIGH_LEVEL )) { GPIO.pin[gpio_num].int_type = intr_type; GPIO.pin[gpio_num].wakeup_enable = 0x1; } else { diff --git a/components/driver/include/driver/gpio.h b/components/driver/include/driver/gpio.h index 4f2a800724..1472ba8352 100644 --- a/components/driver/include/driver/gpio.h +++ b/components/driver/include/driver/gpio.h @@ -156,6 +156,7 @@ typedef enum { GPIO_NUM_37 = 37, /*!< GPIO37, input mode only */ GPIO_NUM_38 = 38, /*!< GPIO38, input mode only */ GPIO_NUM_39 = 39, /*!< GPIO39, input mode only */ + GPIO_NUM_MAX = 40, } gpio_num_t; typedef enum { @@ -205,9 +206,8 @@ typedef enum { } gpio_pull_mode_t; - +typedef void (*gpio_isr_t)(void*); typedef intr_handle_t gpio_isr_handle_t; -typedef void (*gpio_event_callback)(gpio_num_t gpio_intr_num); /** * @brief GPIO common configuration @@ -357,8 +357,6 @@ esp_err_t gpio_wakeup_disable(gpio_num_t gpio_num); */ esp_err_t gpio_isr_register(void (*fn)(void*), void * arg, int intr_alloc_flags, gpio_isr_handle_t *handle); - - /** * @brief Enable pull-up on GPIO. * @@ -403,93 +401,55 @@ esp_err_t gpio_pulldown_en(gpio_num_t gpio_num); */ esp_err_t gpio_pulldown_dis(gpio_num_t gpio_num); +/** + * @brief Install a GPIO ISR service, so we can assign different ISR handler for different pins + * + * @param intr_alloc_flags Flags used to allocate the interrupt. One or multiple (ORred) + * ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info. + * + * @return + * - ESP_OK Success + * - ESP_FAIL Operation fail + * - ESP_ERR_NO_MEM No memory to install this service + */ +esp_err_t gpio_install_isr_service(int intr_alloc_flags); /** - * *************** ATTENTION ********************/ -/** - *@attention - * Each GPIO has its own separate configuration register, so we do not use - * a lock to serialize access to them. This works under the assumption that - * no situation will occur where two tasks try to configure the same GPIO - * pin simultaneously. It is up to the application developer to guarantee this. - */ + * @brief Un-install GPIO ISR service, free the resources. + */ +void gpio_uninstall_isr_service(); /** - *----------EXAMPLE TO CONFIGURE GPIO AS OUTPUT ------------ * - * @code{c} - * gpio_config_t io_conf; - * io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt - * io_conf.mode = GPIO_MODE_OUTPUT; //set as output mode - * io_conf.pin_bit_mask = GPIO_SEL_18 | GPIO_SEL_19; //bit mask of the pins that you want to set,e.g.GPIO18/19 - * io_conf.pull_down_en = 0; //disable pull-down mode - * io_conf.pull_up_en = 0; //disable pull-up mode - * gpio_config(&io_conf); //configure GPIO with the given settings - * @endcode - **/ + * @brief Add ISR handler for the corresponding GPIO. + * + * Interrupt handlers no longer need to be declared with IRAM_ATTR, unless you pass the ESP_INTR_FLAG_IRAM flag + * when allocating the ISR in gpio_install_isr_service(). + * This ISR handler will be called from an ISR. So there probably is some stack size limit, and this limit + * is smaller compared to a "raw" interrupt handler due to another level of indirection. + * + * @param gpio_num GPIO number + * @param isr_handler ISR handler function for the corresponding GPIO number. + * @param args parameter for ISR handler. + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE Wrong state, the ISR service has not been initialized. + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void* args); /** - *----------EXAMPLE TO CONFIGURE GPIO AS OUTPUT ------------ * - * @code{c} - * io_conf.intr_type = GPIO_INTR_POSEDGE; //set posedge interrupt - * io_conf.mode = GPIO_MODE_INPUT; //set as input - * io_conf.pin_bit_mask = GPIO_SEL_4 | GPIO_SEL_5; //bit mask of the pins that you want to set, e.g.,GPIO4/5 - * io_conf.pull_down_en = 0; //disable pull-down mode - * io_conf.pull_up_en = 1; //enable pull-up mode - * gpio_config(&io_conf); //configure GPIO with the given settings - * @endcode - */ -/** - *----------EXAMPLE TO SET ISR HANDLER ---------------------- - * @code{c} - * gpio_isr_register(gpio_intr_test, 0, NULL); //hook the isr handler for GPIO interrupt - * @endcode - */ -/** - *-------------EXAMPLE OF HANDLER FUNCTION-------------------* - * @code{c} - * #include "esp_attr.h" - * void IRAM_ATTR gpio_intr_test(void* arg) - * { - * //GPIO intr process - * ets_printf("in gpio_intr\n"); - * uint32_t gpio_num = 0; - * uint32_t gpio_intr_status = READ_PERI_REG(GPIO_STATUS_REG); //read status to get interrupt status for GPIO0-31 - * uint32_t gpio_intr_status_h = READ_PERI_REG(GPIO_STATUS1_REG);//read status1 to get interrupt status for GPIO32-39 - * SET_PERI_REG_MASK(GPIO_STATUS_W1TC_REG, gpio_intr_status); //Clear intr for gpio0-gpio31 - * SET_PERI_REG_MASK(GPIO_STATUS1_W1TC_REG, gpio_intr_status_h); //Clear intr for gpio32-39 - * do { - * if(gpio_num < 32) { - * if(gpio_intr_status & BIT(gpio_num)) { //gpio0-gpio31 - * ets_printf("Intr GPIO%d ,val: %d\n",gpio_num,gpio_get_level(gpio_num)); - * //This is an isr handler, you should post an event to process it in RTOS queue. - * } - * } else { - * if(gpio_intr_status_h & BIT(gpio_num - 32)) { - * ets_printf("Intr GPIO%d, val : %d\n",gpio_num,gpio_get_level(gpio_num)); - * //This is an isr handler, you should post an event to process it in RTOS queue. - * } - * } - * } while(++gpio_num < GPIO_PIN_COUNT); - * } - * @endcode - */ + * @brief Remove ISR handler for the corresponding GPIO. + * + * @param gpio_num GPIO number + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE Wrong state, the ISR service has not been initialized. + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t gpio_isr_handler_remove(gpio_num_t gpio_num); -/** - *----EXAMPLE OF I2C CONFIG AND PICK SIGNAL FOR IO MATRIX---* - * @code{c} - * gpio_config_t io_conf; - * io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt - * io_conf.mode = GPIO_MODE_INPUT_OUTPUT_OD; //set as output mode - * io_conf.pin_bit_mask = GPIO_SEL_21 | GPIO_SEL_22; //bit mask of the pins that you want to set,e.g.GPIO21/22 - * io_conf.pull_down_en = 0; //disable pull-down mode - * io_conf.pull_up_en = 1; //enable pull-up mode - * gpio_config(&io_conf); //configure GPIO with the given settings - * gpio_matrix_out(21, EXT_I2C_SCL_O_IDX, 0, 0); //set output signal for io_matrix - * gpio_matrix_out(22, EXT_I2C_SDA_O_IDX, 0, 0); //set output signal for io_matrix - * gpio_matrix_in( 22, EXT_I2C_SDA_I_IDX, 0); //set input signal for io_matrix - * @endcode - * - */ #ifdef __cplusplus } diff --git a/docs/api/gpio.rst b/docs/api/gpio.rst index 0cd4eca365..f331b9d265 100644 --- a/docs/api/gpio.rst +++ b/docs/api/gpio.rst @@ -4,20 +4,17 @@ GPIO Overview -------- -`Instructions`_ +The ESP32 chip features 40 physical GPIO pads. Some GPIO pads cannot be used or do not have the corresponding pin on the chip package(refer to technical reference manual ). Each pad can be used as a general purpose I/O or can be connected to an internal peripheral signal. +Note that GPIO6-11 are usually used for SPI flash. GPIO34-39 can only be set as input mode. Application Example ------------------- -`Instructions`_ +GPIO output and input interrupt example: `examples/21_gpio `_. API Reference ------------- -`Instructions`_ - -.. _Instructions: template.html - Header Files ^^^^^^^^^^^^ @@ -110,7 +107,8 @@ Macros Type Definitions ^^^^^^^^^^^^^^^^ -.. doxygentypedef:: gpio_event_callback +.. doxygentypedef:: gpio_isr_t +.. doxygentypedef:: gpio_isr_handle_t Enumerations ^^^^^^^^^^^^ @@ -122,6 +120,13 @@ Enumerations .. doxygenenum:: gpio_pulldown_t .. doxygenenum:: gpio_pull_mode_t +Structures +^^^^^^^^^^ + +.. doxygenstruct:: gpio_config_t + :members: + + Functions ^^^^^^^^^ @@ -136,3 +141,12 @@ Functions .. doxygenfunction:: gpio_wakeup_enable .. doxygenfunction:: gpio_wakeup_disable .. doxygenfunction:: gpio_isr_register +.. doxygenfunction:: gpio_pullup_en +.. doxygenfunction:: gpio_pullup_dis +.. doxygenfunction:: gpio_pulldown_en +.. doxygenfunction:: gpio_pulldown_dis +.. doxygenfunction:: gpio_install_isr_service +.. doxygenfunction:: gpio_uninstall_isr_service +.. doxygenfunction:: gpio_isr_handler_add +.. doxygenfunction:: gpio_isr_handler_remove + diff --git a/examples/21_gpio/Makefile b/examples/21_gpio/Makefile new file mode 100644 index 0000000000..0589a9f21d --- /dev/null +++ b/examples/21_gpio/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 := gpio + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/21_gpio/README.md b/examples/21_gpio/README.md new file mode 100644 index 0000000000..7436d77532 --- /dev/null +++ b/examples/21_gpio/README.md @@ -0,0 +1,20 @@ +# Example: GPIO + +###This test code shows how to configure gpio and how to use gpio interrupt. + + +####GPIO functions: + + * GPIO18: output + * GPIO19: output + * GPIO4: input, pulled up, interrupt from rising edge and falling edge + * GPIO5: input, pulled up, interrupt from rising edge. + +####Test: + * Connect GPIO18 with GPIO4 + * Connect GPIO19 with GPIO5 + * Generate pulses on GPIO18/19, that triggers interrupt on GPIO4/5 + + + + diff --git a/examples/21_gpio/main/component.mk b/examples/21_gpio/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/21_gpio/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/21_gpio/main/gpio_test.c b/examples/21_gpio/main/gpio_test.c new file mode 100644 index 0000000000..a5e9571bca --- /dev/null +++ b/examples/21_gpio/main/gpio_test.c @@ -0,0 +1,114 @@ +/* GPIO Example + + 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/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "driver/gpio.h" + +/** + * Brief: + * This test code shows how to configure gpio and how to use gpio interrupt. + * + * GPIO status: + * GPIO18: output + * GPIO19: output + * GPIO4: input, pulled up, interrupt from rising edge and falling edge + * GPIO5: input, pulled up, interrupt from rising edge. + * + * Test: + * Connect GPIO18 with GPIO4 + * Connect GPIO19 with GPIO5 + * Generate pulses on GPIO18/19, that triggers interrupt on GPIO4/5 + * + */ + +#define GPIO_OUTPUT_IO_0 18 +#define GPIO_OUTPUT_IO_1 19 +#define GPIO_OUTPUT_PIN_SEL ((1< Date: Thu, 29 Dec 2016 17:29:14 +0800 Subject: [PATCH 029/167] Add i2s driver --- components/driver/i2s.c | 807 +++++++++++++++++++++++++ components/driver/include/driver/i2s.h | 380 ++++++++++++ examples/22_i2s/Makefile | 8 + examples/22_i2s/main/app_main.c | 72 +++ examples/22_i2s/main/component.mk | 10 + 5 files changed, 1277 insertions(+) create mode 100644 components/driver/i2s.c create mode 100644 components/driver/include/driver/i2s.h create mode 100644 examples/22_i2s/Makefile create mode 100644 examples/22_i2s/main/app_main.c create mode 100644 examples/22_i2s/main/component.mk diff --git a/components/driver/i2s.c b/components/driver/i2s.c new file mode 100644 index 0000000000..d8e6198370 --- /dev/null +++ b/components/driver/i2s.c @@ -0,0 +1,807 @@ +// 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 +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/xtensa_api.h" + +#include "soc/dport_reg.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/rtc_io_reg.h" +#include "soc/sens_reg.h" +#include "rom/lldesc.h" + +#include "driver/gpio.h" +#include "driver/i2s.h" + +#include "esp_intr.h" +#include "esp_err.h" +#include "esp_log.h" + +static const char* I2S_TAG = "I2S"; +#define I2S_CHECK(a, str, ret) if (!(a)) { \ + ESP_LOGE(I2S_TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + return (ret); \ + } +#define I2S_BASE_CLK (2*APB_CLK_FREQ) +#define I2S_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_spinlock[i2s_num]) +#define I2S_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_spinlock[i2s_num]) +#define I2S_ENTER_CRITICAL() portENTER_CRITICAL(&i2s_spinlock[i2s_num]) +#define I2S_EXIT_CRITICAL() portEXIT_CRITICAL(&i2s_spinlock[i2s_num]) +#define gpio_matrix_out_check(a, b, c, d) if(a != -1) gpio_matrix_out(a, b, c, d) //if pin = -1, do not need to configure +#define gpio_matrix_in_check(a, b, c) if(a != -1) gpio_matrix_in(a, b, c) + + +/** + * @brief DMA buffer object + * + */ +typedef struct { + char **buf; + int buf_size; + int rw_pos; + void *curr_ptr; + SemaphoreHandle_t mux; + xQueueHandle queue; + lldesc_t **desc; +} i2s_dma_t; + +/** + * @brief I2S object instance + * + */ +typedef struct { + i2s_port_t i2s_num; /*!< I2S port number*/ + int queue_size; /*!< I2S event queue size*/ + QueueHandle_t i2s_queue; /*!< I2S queue handler*/ + int dma_buf_count; /*!< DMA buffer count, number of buffer*/ + int dma_buf_len; /*!< DMA buffer length, length of each buffer*/ + i2s_dma_t *rx; /*!< DMA Tx buffer*/ + i2s_dma_t *tx; /*!< DMA Rx buffer*/ + i2s_isr_handle_t i2s_isr_handle; /*!< I2S Interrupt handle*/ + int channel_num; /*!< Number of channels*/ + int bytes_per_sample; /*!< Bytes per sample*/ + i2s_mode_t mode; /*!< I2S Working mode*/ +} 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 esp_err_t i2s_reset_fifo(i2s_port_t i2s_num) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + I2S_ENTER_CRITICAL(); + I2S[i2s_num]->conf.rx_fifo_reset = 1; + I2S[i2s_num]->conf.rx_fifo_reset = 0; + I2S[i2s_num]->conf.tx_fifo_reset = 1; + I2S[i2s_num]->conf.tx_fifo_reset = 0; + I2S_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t i2s_clear_intr_status(i2s_port_t i2s_num, uint32_t clr_mask) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + I2S[i2s_num]->int_clr.val = clr_mask; + return ESP_OK; +} + +esp_err_t i2s_enable_rx_intr(i2s_port_t i2s_num) +{ + + I2S_ENTER_CRITICAL(); + I2S[i2s_num]->int_ena.in_suc_eof = 1; + I2S[i2s_num]->int_ena.in_dscr_err = 1; + I2S_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t i2s_disable_rx_intr(i2s_port_t i2s_num) +{ + I2S_ENTER_CRITICAL(); + I2S[i2s_num]->int_ena.in_suc_eof = 0; + I2S[i2s_num]->int_ena.in_dscr_err = 0; + I2S_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t i2s_disable_tx_intr(i2s_port_t i2s_num) +{ + I2S_ENTER_CRITICAL(); + I2S[i2s_num]->int_ena.out_eof = 0; + I2S[i2s_num]->int_ena.out_dscr_err = 0; + I2S_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t i2s_enable_tx_intr(i2s_port_t i2s_num) +{ + I2S_ENTER_CRITICAL(); + I2S[i2s_num]->int_ena.out_eof = 1; + I2S[i2s_num]->int_ena.out_dscr_err = 1; + I2S_EXIT_CRITICAL(); + return ESP_OK; +} + +static esp_err_t i2s_isr_register(i2s_port_t i2s_num, uint8_t intr_alloc_flags, void (*fn)(void*), void * arg, i2s_isr_handle_t *handle) +{ + return esp_intr_alloc(ETS_I2S0_INTR_SOURCE + i2s_num, intr_alloc_flags, fn, arg, handle); +} + +static esp_err_t i2s_set_clk(i2s_port_t i2s_num, uint32_t rate, uint8_t bits, bool fuzzy) +{ + int factor = (256%bits)? 384 : 256; // According to hardware codec requirement(supported 256fs or 384fs) + int clkmInteger, clkmDecimals, bck = 0; + float denom = (float)1 / 64; + int channel = 2; + + float clkmdiv = (float)I2S_BASE_CLK / (rate * factor); + if (clkmdiv > 256) { + ESP_LOGE(I2S_TAG, "clkmdiv is too large\r\n"); + return ESP_FAIL; + } + clkmInteger = clkmdiv; + clkmDecimals = (clkmdiv - clkmInteger) / denom; + float mclk = clkmInteger + denom * clkmDecimals; + bck = factor/(bits * channel); + + I2S[i2s_num]->clkm_conf.clka_en = 0; + I2S[i2s_num]->clkm_conf.clkm_div_a = 63; + I2S[i2s_num]->clkm_conf.clkm_div_b = clkmDecimals; + I2S[i2s_num]->clkm_conf.clkm_div_num = clkmInteger; + I2S[i2s_num]->sample_rate_conf.tx_bck_div_num = bck; + I2S[i2s_num]->sample_rate_conf.rx_bck_div_num = bck; + I2S[i2s_num]->sample_rate_conf.tx_bits_mod = bits; + I2S[i2s_num]->sample_rate_conf.rx_bits_mod = bits; + float real_rate = (float)(I2S_BASE_CLK / (bck * bits * clkmInteger)/2); + ESP_LOGI(I2S_TAG, "Req RATE: %d, real rate: %0.3f, BITS: %u, CLKM: %u, BCK: %u, MCLK: %0.3f, SCLK: %f, diva: %d, divb: %d", + rate, real_rate, bits, clkmInteger, bck, (float)I2S_BASE_CLK / mclk, real_rate *16*2, 64, clkmDecimals); + return ESP_OK; +} + +static void IRAM_ATTR i2s_intr_handler_default(void *arg) +{ + i2s_obj_t *p_i2s = (i2s_obj_t*) arg; + uint8_t i2s_num = p_i2s->i2s_num; + i2s_dev_t* i2s_reg = I2S[i2s_num]; + i2s_event_t i2s_event; + int dummy; + + portBASE_TYPE high_priority_task_awoken = 0; + + lldesc_t *finish_desc; + + if (i2s_reg->int_st.out_dscr_err || i2s_reg->int_st.in_dscr_err) { + ESP_LOGE(I2S_TAG, "out_dscr_err: %d or in_dscr_err:%d", i2s_reg->int_st.out_dscr_err == 1, i2s_reg->int_st.in_dscr_err == 1); + if (p_i2s->i2s_queue) { + i2s_event.type = I2S_EVENT_DMA_ERROR; + if (xQueueIsQueueFullFromISR(p_i2s->i2s_queue)) { + xQueueReceiveFromISR(p_i2s->i2s_queue, &dummy, &high_priority_task_awoken); + } + xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken); + } + } + + if (i2s_reg->int_st.out_eof && p_i2s->tx) { + finish_desc = (lldesc_t*) i2s_reg->out_eof_des_addr; + // All buffers are empty. This means we have an underflow on our hands. + if (xQueueIsQueueFullFromISR(p_i2s->tx->queue)) { + xQueueReceiveFromISR(p_i2s->tx->queue, &dummy, &high_priority_task_awoken); + } + xQueueSendFromISR(p_i2s->tx->queue, (void*)(&finish_desc->buf), &high_priority_task_awoken); + if (p_i2s->i2s_queue) { + i2s_event.type = I2S_EVENT_TX_DONE; + if (xQueueIsQueueFullFromISR(p_i2s->i2s_queue)) { + xQueueReceiveFromISR(p_i2s->i2s_queue, &dummy, &high_priority_task_awoken); + } + xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken); + } + + } + + if (i2s_reg->int_st.in_suc_eof && p_i2s->rx) { + // All buffers are full. This means we have an overflow. + finish_desc = (lldesc_t*) i2s_reg->in_eof_des_addr; + if (xQueueIsQueueFullFromISR(p_i2s->rx->queue)) { + xQueueReceiveFromISR(p_i2s->rx->queue, &dummy, &high_priority_task_awoken); + } + xQueueSendFromISR(p_i2s->rx->queue, (void*)(&finish_desc->buf), &high_priority_task_awoken); + if (p_i2s->i2s_queue) { + i2s_event.type = I2S_EVENT_RX_DONE; + if (p_i2s->i2s_queue && xQueueIsQueueFullFromISR(p_i2s->i2s_queue)) { + xQueueReceiveFromISR(p_i2s->i2s_queue, &dummy, &high_priority_task_awoken); + } + xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken); + } + } + if (high_priority_task_awoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + + i2s_reg->int_clr.val = I2S[i2s_num]->int_st.val; +} + +static esp_err_t i2s_destroy_dma_queue(i2s_port_t i2s_num, i2s_dma_t *dma) +{ + int bux_idx; + if (p_i2s_obj[i2s_num] == NULL) { + ESP_LOGE(I2S_TAG, "Not initialized yet"); + return ESP_FAIL; + } + if (dma == NULL) { + return ESP_FAIL; + } + for (bux_idx = 0; bux_idx < p_i2s_obj[i2s_num]->dma_buf_count; bux_idx++) { + if (dma->desc && dma->desc[bux_idx]) + free(dma->desc[bux_idx]); + if (dma->buf && dma->buf[bux_idx]) + free(dma->buf[bux_idx]); + } + if (dma->buf) + free(dma->buf); + if (dma->desc) + free(dma->desc); + vQueueDelete(dma->queue); + vSemaphoreDelete(dma->mux); + return ESP_OK; +} + +static i2s_dma_t *i2s_create_dma_queue(i2s_port_t i2s_num, int dma_buf_count, int dma_buf_len) +{ + int bux_idx; + int sample_size = p_i2s_obj[i2s_num]->bytes_per_sample * p_i2s_obj[i2s_num]->channel_num; + i2s_dma_t *dma = (i2s_dma_t*) malloc(sizeof(i2s_dma_t)); + if (dma == NULL) { + ESP_LOGE(I2S_TAG, "Error malloc i2s_dma_t"); + return NULL; + } + memset(dma, 0, sizeof(i2s_dma_t)); + + dma->buf = (char **)malloc(sizeof(char*) * dma_buf_count); + if (dma->buf == NULL) { + ESP_LOGE(I2S_TAG, "Error malloc dma buffer pointer"); + + return NULL; + } + memset(dma->buf, 0, sizeof(char*) * dma_buf_count); + + for (bux_idx = 0; bux_idx < dma_buf_count; bux_idx++) { + dma->buf[bux_idx] = (char*) malloc(dma_buf_len * sample_size); + if (dma->buf[bux_idx] == NULL) { + ESP_LOGE(I2S_TAG, "Error malloc dma buffer"); + i2s_destroy_dma_queue(i2s_num, dma); + return NULL; + } + ESP_LOGD(I2S_TAG, "Addr[%d] = %d", bux_idx, (int)dma->buf[bux_idx]); + memset(dma->buf[bux_idx], 0, dma_buf_len * sample_size); + } + + dma->desc = (lldesc_t**) malloc(sizeof(lldesc_t*) * dma_buf_count); + if (dma->desc == NULL) { + ESP_LOGE(I2S_TAG, "Error malloc dma description"); + i2s_destroy_dma_queue(i2s_num, dma); + return NULL; + } + for (bux_idx = 0; bux_idx < dma_buf_count; bux_idx++) { + dma->desc[bux_idx] = (lldesc_t*) malloc(sizeof(lldesc_t)); + if (dma->desc[bux_idx] == NULL) { + ESP_LOGE(I2S_TAG, "Error malloc dma description entry"); + i2s_destroy_dma_queue(i2s_num, dma); + return NULL; + } + } + + for (bux_idx = 0; bux_idx < dma_buf_count; bux_idx++) { + dma->desc[bux_idx]->owner = 1; + dma->desc[bux_idx]->eof = 1; + dma->desc[bux_idx]->sosf = 0; + dma->desc[bux_idx]->length = dma_buf_len * sample_size; + dma->desc[bux_idx]->size = dma_buf_len * sample_size; + dma->desc[bux_idx]->buf = (uint8_t *) dma->buf[bux_idx]; + dma->desc[bux_idx]->offset = 0; + dma->desc[bux_idx]->empty = (uint32_t)((bux_idx < (dma_buf_count - 1)) ? (dma->desc[bux_idx + 1]) : dma->desc[0]); + } + dma->queue = xQueueCreate(dma_buf_count - 1, sizeof(char*)); + dma->mux = xSemaphoreCreateMutex(); + dma->rw_pos = 0; + dma->buf_size = dma_buf_len * sample_size; + dma->curr_ptr = NULL; + ESP_LOGI(I2S_TAG, "DMA Malloc info, datalen=blocksize=%d, dma_buf_count=%d", dma_buf_len * sample_size, dma_buf_count); + return dma; +} + + +esp_err_t i2s_start(i2s_port_t i2s_num) +{ + //start DMA link + I2S_ENTER_CRITICAL(); + 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) { + ESP_LOGD(I2S_TAG, "I2S_MODE_TX"); + i2s_enable_tx_intr(i2s_num); + I2S[i2s_num]->out_link.start = 1; + I2S[i2s_num]->conf.tx_start = 1; + } + if (p_i2s_obj[i2s_num]->mode & I2S_MODE_RX) { + ESP_LOGD(I2S_TAG, "I2S_MODE_RX"); + i2s_enable_rx_intr(i2s_num); + I2S[i2s_num]->in_link.start = 1; + I2S[i2s_num]->conf.rx_start = 1; + } + esp_intr_enable(p_i2s_obj[i2s_num]->i2s_isr_handle); + I2S_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t i2s_stop(i2s_port_t i2s_num) +{ + I2S_ENTER_CRITICAL(); + esp_intr_disable(p_i2s_obj[i2s_num]->i2s_isr_handle); + if (p_i2s_obj[i2s_num]->mode & I2S_MODE_TX) { + I2S[i2s_num]->out_link.stop = 1; + I2S[i2s_num]->conf.tx_start = 0; + i2s_disable_tx_intr(i2s_num); + } + if (p_i2s_obj[i2s_num]->mode & I2S_MODE_RX) { + I2S[i2s_num]->in_link.stop = 1; + I2S[i2s_num]->conf.rx_start = 0; + i2s_disable_rx_intr(i2s_num); + } + I2S_EXIT_CRITICAL(); + return 0; +} + +static esp_err_t configure_dac_pin(void) +{ + SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_DAC_DIG_FORCE_M); + SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_DAC_CLK_INV_M); + + SET_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_DAC_XPD_FORCE_M); + SET_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_XPD_DAC_M); + + CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_RUE_M); + CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_RDE_M); + + SET_PERI_REG_MASK(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_DAC_XPD_FORCE_M); + SET_PERI_REG_MASK(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_XPD_DAC_M); + + CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_RUE_M); + CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_RDE_M); + return ESP_OK; +} + +esp_err_t i2s_set_pin(i2s_port_t i2s_num, const i2s_pin_config_t *pin) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + if (pin == NULL) { + return configure_dac_pin(); + } + if (pin->bck_io_num != -1 && !GPIO_IS_VALID_GPIO(pin->bck_io_num)) { + ESP_LOGE(I2S_TAG, "bck_io_num error"); + return ESP_FAIL; + } + if (pin->ws_io_num != -1 && !GPIO_IS_VALID_GPIO(pin->ws_io_num)) { + ESP_LOGE(I2S_TAG, "ws_io_num error"); + return ESP_FAIL; + } + if (pin->data_out_num != -1 && !GPIO_IS_VALID_GPIO(pin->data_out_num)) { + ESP_LOGE(I2S_TAG, "data_out_num error"); + return ESP_FAIL; + } + if (pin->data_in_num != -1 && !GPIO_IS_VALID_GPIO(pin->data_in_num)) { + ESP_LOGE(I2S_TAG, "data_in_num error"); + return ESP_FAIL; + } + + int bck_sig = -1, ws_sig = -1, data_out_sig = -1, data_in_sig = -1; + //TX & RX + if (p_i2s_obj[i2s_num]->mode & I2S_MODE_RX) { + bck_sig = I2S0I_BCK_OUT_IDX; + ws_sig = I2S0I_WS_OUT_IDX; + data_in_sig = I2S0I_DATA_IN15_IDX; + if (i2s_num == I2S_NUM_1) { + bck_sig = I2S1I_BCK_OUT_IDX; + ws_sig = I2S1I_WS_OUT_IDX; + data_in_sig = I2S1I_DATA_IN15_IDX; + } + } + if (p_i2s_obj[i2s_num]->mode & I2S_MODE_TX) { + bck_sig = I2S0O_BCK_OUT_IDX; + ws_sig = I2S0O_WS_OUT_IDX; + data_out_sig = I2S0O_DATA_OUT23_IDX; + if (i2s_num == I2S_NUM_1) { + bck_sig = I2S1O_BCK_OUT_IDX; + ws_sig = I2S1O_WS_OUT_IDX; + data_out_sig = I2S1O_DATA_OUT23_IDX; + } + } + + gpio_matrix_out_check(pin->data_out_num, data_out_sig, 0, 0); + gpio_matrix_in_check(pin->data_in_num, data_in_sig, 0); + if (p_i2s_obj[i2s_num]->mode & I2S_MODE_MASTER) { + gpio_matrix_out_check(pin->ws_io_num, ws_sig, 0, 0); + gpio_matrix_out_check(pin->bck_io_num, bck_sig, 0, 0); + } else if (p_i2s_obj[i2s_num]->mode & I2S_MODE_SLAVE) { + gpio_matrix_in_check(pin->ws_io_num, ws_sig, 0); + gpio_matrix_in_check(pin->bck_io_num, bck_sig, 0); + } + ESP_LOGE(I2S_TAG, "data: out %d, in: %d, ws: %d, bck: %d", data_out_sig, data_in_sig, ws_sig, bck_sig); + return ESP_OK; +} + +esp_err_t i2s_set_sample_rates(i2s_port_t i2s_num, uint32_t rate) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + I2S_CHECK((p_i2s_obj[i2s_num]->bytes_per_sample > 0), "bits_per_sample not set", ESP_FAIL); + return i2s_set_clk(i2s_num, rate, p_i2s_obj[i2s_num]->bytes_per_sample*8, 0); +} +static esp_err_t i2s_param_config(i2s_port_t i2s_num, const i2s_config_t *i2s_config) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + I2S_CHECK((i2s_config), "param null", ESP_FAIL); + if (i2s_num == I2S_NUM_1) { + periph_module_enable(PERIPH_I2S1_MODULE); + } else { + periph_module_enable(PERIPH_I2S0_MODULE); + } + // configure I2S data port interface. + i2s_reset_fifo(i2s_num); + + //reset i2s + 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; + + + //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; + + + //Enable and configure DMA + I2S[i2s_num]->lc_conf.check_owner = 0; + I2S[i2s_num]->lc_conf.out_loop_test = 0; + I2S[i2s_num]->lc_conf.out_auto_wrback = 0; + I2S[i2s_num]->lc_conf.out_data_burst_en = 0; + I2S[i2s_num]->lc_conf.outdscr_burst_en = 0; + I2S[i2s_num]->lc_conf.out_no_restart_clr = 0; + I2S[i2s_num]->lc_conf.indscr_burst_en = 0; + I2S[i2s_num]->lc_conf.out_eof_mode = 1; + + + I2S[i2s_num]->conf2.lcd_en = 0; + I2S[i2s_num]->conf2.camera_en = 0; + I2S[i2s_num]->pdm_conf.pcm2pdm_conv_en = 0; + I2S[i2s_num]->pdm_conf.pdm2pcm_conv_en = 0; + + I2S[i2s_num]->fifo_conf.dscr_en = 0; + p_i2s_obj[i2s_num]->channel_num = i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 2 : 1; + + I2S[i2s_num]->conf_chan.tx_chan_mod = i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? i2s_config->channel_format : (i2s_config->channel_format >> 1); // 0-two channel;1-right;2-left;3-righ;4-left + I2S[i2s_num]->fifo_conf.tx_fifo_mod = i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1; // 0-right&left channel;1-one channel + I2S[i2s_num]->conf.tx_mono = i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1; // 0-right&left channel;1-one channel + + I2S[i2s_num]->conf_chan.rx_chan_mod = i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? i2s_config->channel_format : (i2s_config->channel_format >> 1); // 0-two channel;1-right;2-left;3-righ;4-left + I2S[i2s_num]->fifo_conf.rx_fifo_mod = i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1; // 0-right&left channel;1-one channel + I2S[i2s_num]->conf.rx_mono = i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1; // 0-right&left channel;1-one channel + I2S[i2s_num]->fifo_conf.dscr_en = 1;//connect dma to fifo + + I2S[i2s_num]->conf.tx_start = 0; + I2S[i2s_num]->conf.rx_start = 0; + + if (i2s_config->mode & I2S_MODE_TX) { + I2S[i2s_num]->conf.tx_msb_right = 0; + I2S[i2s_num]->conf.tx_right_first = 0; + + I2S[i2s_num]->conf.tx_slave_mod = 0; // Master + I2S[i2s_num]->fifo_conf.tx_fifo_mod_force_en = 1;//? + + if (i2s_config->mode & I2S_MODE_SLAVE) { + I2S[i2s_num]->conf.tx_slave_mod = 1;//TX Slave + } + } + + if (i2s_config->mode & I2S_MODE_RX) { + I2S[i2s_num]->conf.rx_msb_right = 0; + I2S[i2s_num]->conf.rx_right_first = 0; + I2S[i2s_num]->conf.rx_slave_mod = 0; // Master + I2S[i2s_num]->fifo_conf.rx_fifo_mod_force_en = 1;//? + I2S[i2s_num]->rx_eof_num = (i2s_config->dma_buf_len); + if (i2s_config->mode & I2S_MODE_SLAVE) { + I2S[i2s_num]->conf.rx_slave_mod = 1;//RX Slave + } + } + + if (i2s_config->mode & I2S_MODE_DAC_BUILT_IN) { + I2S[i2s_num]->conf2.lcd_en = 1; + I2S[i2s_num]->conf.tx_right_first = 1; + I2S[i2s_num]->fifo_conf.tx_fifo_mod = 3; + } + + if (i2s_config->communication_format & I2S_COMM_FORMAT_I2S) { + I2S[i2s_num]->conf.tx_short_sync = 0; + I2S[i2s_num]->conf.rx_short_sync = 0; + I2S[i2s_num]->conf.tx_msb_shift = 1; + I2S[i2s_num]->conf.rx_msb_shift = 1; + if (i2s_config->communication_format & I2S_COMM_FORMAT_I2S_LSB) { + if (i2s_config->mode & I2S_MODE_TX) { + I2S[i2s_num]->conf.tx_msb_shift = 0; + } + if (i2s_config->mode & I2S_MODE_RX) { + I2S[i2s_num]->conf.rx_msb_shift = 0; + } + } + } + + if (i2s_config->communication_format & I2S_COMM_FORMAT_PCM) { + I2S[i2s_num]->conf.tx_msb_shift = 0; + I2S[i2s_num]->conf.rx_msb_shift = 0; + I2S[i2s_num]->conf.tx_short_sync = 0; + I2S[i2s_num]->conf.rx_short_sync = 0; + if (i2s_config->communication_format & I2S_COMM_FORMAT_PCM_SHORT) { + if (i2s_config->mode & I2S_MODE_TX) { + I2S[i2s_num]->conf.tx_short_sync = 1; + } + if (i2s_config->mode & I2S_MODE_RX) { + I2S[i2s_num]->conf.rx_short_sync = 1; + } + } + } + if ((p_i2s_obj[i2s_num]->mode & I2S_MODE_RX) && (p_i2s_obj[i2s_num]->mode & I2S_MODE_TX)) { + I2S[i2s_num]->conf.sig_loopback = 1; + } + i2s_set_clk(i2s_num, i2s_config->sample_rate, p_i2s_obj[i2s_num]->bytes_per_sample*8, 0); + return ESP_OK; +} + +esp_err_t i2s_zero_dma_buffer(i2s_port_t i2s_num) +{ + int total_buffer_in_bytes = p_i2s_obj[i2s_num]->dma_buf_count * p_i2s_obj[i2s_num]->dma_buf_len * p_i2s_obj[i2s_num]->bytes_per_sample * p_i2s_obj[i2s_num]->channel_num; + const uint32_t zero_sample[2] = { 0 }; + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + while (total_buffer_in_bytes > 0) { + i2s_push_sample(i2s_num, (const char*) zero_sample, 10); + total_buffer_in_bytes -= p_i2s_obj[i2s_num]->bytes_per_sample * p_i2s_obj[i2s_num]->channel_num; + } + return ESP_OK; +} + +esp_err_t i2s_driver_install(i2s_port_t i2s_num, const i2s_config_t *i2s_config, int queue_size, void* i2s_queue) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + I2S_CHECK((i2s_config != NULL), "I2S configuration must not NULL", ESP_FAIL); + I2S_CHECK((i2s_config->dma_buf_count >= 2 && i2s_config->dma_buf_count <= 128), "I2S buffer count less than 128 and more than 2", ESP_FAIL); + I2S_CHECK((i2s_config->dma_buf_len >= 8 && i2s_config->dma_buf_len <= 2048), "I2S buffer length at most 2048 and more than 8", ESP_FAIL); + if (p_i2s_obj[i2s_num] == NULL) { + p_i2s_obj[i2s_num] = (i2s_obj_t*) malloc(sizeof(i2s_obj_t)); + if (p_i2s_obj[i2s_num] == NULL) { + ESP_LOGE(I2S_TAG, "Malloc I2S driver error"); + return ESP_FAIL; + } + + p_i2s_obj[i2s_num]->i2s_num = i2s_num; + p_i2s_obj[i2s_num]->dma_buf_count = i2s_config->dma_buf_count; + p_i2s_obj[i2s_num]->dma_buf_len = i2s_config->dma_buf_len; + p_i2s_obj[i2s_num]->i2s_queue = i2s_queue; + p_i2s_obj[i2s_num]->mode = i2s_config->mode; + p_i2s_obj[i2s_num]->bytes_per_sample = i2s_config->bits_per_sample/8; + + //initial dma + if (ESP_FAIL == i2s_isr_register(i2s_num, i2s_config->intr_alloc_flags, i2s_intr_handler_default, p_i2s_obj[i2s_num], &p_i2s_obj[i2s_num]->i2s_isr_handle)) { + free(p_i2s_obj[i2s_num]); + ESP_LOGE(I2S_TAG, "Register I2S Interrupt error"); + return ESP_FAIL; + } + i2s_stop(i2s_num); + i2s_param_config(i2s_num, i2s_config); + + if (p_i2s_obj[i2s_num]->mode & I2S_MODE_TX) { + p_i2s_obj[i2s_num]->tx = i2s_create_dma_queue(i2s_num, i2s_config->dma_buf_count, i2s_config->dma_buf_len); + if (p_i2s_obj[i2s_num]->tx == NULL) { + ESP_LOGE(I2S_TAG, "Failed to create tx dma buffer"); + i2s_driver_uninstall(i2s_num); + return ESP_FAIL; + } + I2S[i2s_num]->out_link.addr = (uint32_t) p_i2s_obj[i2s_num]->tx->desc[0]; + } + + if (p_i2s_obj[i2s_num]->mode & I2S_MODE_RX) { + p_i2s_obj[i2s_num]->rx = i2s_create_dma_queue(i2s_num, i2s_config->dma_buf_count, i2s_config->dma_buf_len); + if (p_i2s_obj[i2s_num]->rx == NULL){ + ESP_LOGE(I2S_TAG, "Failed to create rx dma buffer"); + i2s_driver_uninstall(i2s_num); + return ESP_FAIL; + } + I2S[i2s_num]->in_link.addr = (uint32_t) p_i2s_obj[i2s_num]->rx->desc[0]; + } + + + if (i2s_queue) { + p_i2s_obj[i2s_num]->i2s_queue = xQueueCreate(queue_size, sizeof(i2s_event_t)); + *((QueueHandle_t*) i2s_queue) = p_i2s_obj[i2s_num]->i2s_queue; + ESP_LOGI(I2S_TAG, "queue free spaces: %d", uxQueueSpacesAvailable(p_i2s_obj[i2s_num]->i2s_queue)); + } else { + p_i2s_obj[i2s_num]->i2s_queue = NULL; + } + + i2s_start(i2s_num); + } else { + ESP_LOGE(I2S_TAG, "I2S driver already installed"); + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t i2s_driver_uninstall(i2s_port_t i2s_num) +{ + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + if (p_i2s_obj[i2s_num] == NULL) { + ESP_LOGI(I2S_TAG, "ALREADY NULL"); + return ESP_OK; + } + i2s_stop(i2s_num); + esp_intr_free(p_i2s_obj[i2s_num]->i2s_isr_handle); + + if (p_i2s_obj[i2s_num]->tx != NULL && p_i2s_obj[i2s_num]->mode & I2S_MODE_TX) { + i2s_destroy_dma_queue(i2s_num, p_i2s_obj[i2s_num]->tx); + p_i2s_obj[i2s_num]->tx = NULL; + } + if (p_i2s_obj[i2s_num]->rx != NULL && p_i2s_obj[i2s_num]->mode & I2S_MODE_RX) { + i2s_destroy_dma_queue(i2s_num, p_i2s_obj[i2s_num]->rx); + p_i2s_obj[i2s_num]->rx = NULL; + } + + if (p_i2s_obj[i2s_num]->i2s_queue) { + vQueueDelete(p_i2s_obj[i2s_num]->i2s_queue); + p_i2s_obj[i2s_num]->i2s_queue = NULL; + } + + free(p_i2s_obj[i2s_num]); + p_i2s_obj[i2s_num] = NULL; + + if (i2s_num == I2S_NUM_0) { + periph_module_disable(PERIPH_I2S0_MODULE); + } else if (i2s_num == I2S_NUM_1) { + periph_module_disable(PERIPH_I2S1_MODULE); + } + return ESP_OK; +} + +int i2s_write_bytes(i2s_port_t i2s_num, const char *src, size_t size, TickType_t ticks_to_wait) +{ + char *data_ptr; + int bytes_can_write, bytes_writen = 0; + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + if (p_i2s_obj[i2s_num]->tx == NULL) { + return 0; + } + xSemaphoreTake(p_i2s_obj[i2s_num]->tx->mux, (portTickType)portMAX_DELAY); + while (size > 0) { + if (p_i2s_obj[i2s_num]->tx->rw_pos == p_i2s_obj[i2s_num]->tx->buf_size || p_i2s_obj[i2s_num]->tx->curr_ptr == NULL) { + if (xQueueReceive(p_i2s_obj[i2s_num]->tx->queue, &p_i2s_obj[i2s_num]->tx->curr_ptr, ticks_to_wait) == pdFALSE) { + break; + } + p_i2s_obj[i2s_num]->tx->rw_pos = 0; + } + ESP_LOGD(I2S_TAG, "size: %d, rw_pos: %d, buf_size: %d, curr_ptr: %d", size, p_i2s_obj[i2s_num]->tx->rw_pos, p_i2s_obj[i2s_num]->tx->buf_size, (int)p_i2s_obj[i2s_num]->tx->curr_ptr); + data_ptr = (char*)p_i2s_obj[i2s_num]->tx->curr_ptr; + data_ptr += p_i2s_obj[i2s_num]->tx->rw_pos; + bytes_can_write = p_i2s_obj[i2s_num]->tx->buf_size - p_i2s_obj[i2s_num]->tx->rw_pos; + if (bytes_can_write > size) { + bytes_can_write = size; + } + memcpy(data_ptr, src, bytes_can_write); + size -= bytes_can_write; + src += bytes_can_write; + p_i2s_obj[i2s_num]->tx->rw_pos += bytes_can_write; + bytes_writen += bytes_can_write; + } + xSemaphoreGive(p_i2s_obj[i2s_num]->tx->mux); + return bytes_writen; +} + +int i2s_read_bytes(i2s_port_t i2s_num, char* dest, size_t size, TickType_t ticks_to_wait) +{ + char *data_ptr; + int bytes_can_read, byte_read = 0; + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + if (p_i2s_obj[i2s_num]->rx == NULL) { + return 0; + } + xSemaphoreTake(p_i2s_obj[i2s_num]->rx->mux, (portTickType)portMAX_DELAY); + while (size > 0) { + if (p_i2s_obj[i2s_num]->rx->rw_pos == p_i2s_obj[i2s_num]->rx->buf_size || p_i2s_obj[i2s_num]->rx->curr_ptr == NULL) { + if (xQueueReceive(p_i2s_obj[i2s_num]->rx->queue, &p_i2s_obj[i2s_num]->rx->curr_ptr, ticks_to_wait) == pdFALSE) { + break; + } + p_i2s_obj[i2s_num]->rx->rw_pos = 0; + } + data_ptr = (char*)p_i2s_obj[i2s_num]->rx->curr_ptr; + data_ptr += p_i2s_obj[i2s_num]->rx->rw_pos; + bytes_can_read = p_i2s_obj[i2s_num]->rx->buf_size - p_i2s_obj[i2s_num]->rx->rw_pos; + if (bytes_can_read > size) { + bytes_can_read = size; + } + memcpy(dest, data_ptr, bytes_can_read); + size -= bytes_can_read; + dest += bytes_can_read; + p_i2s_obj[i2s_num]->rx->rw_pos += bytes_can_read; + byte_read += bytes_can_read; + } + xSemaphoreGive(p_i2s_obj[i2s_num]->rx->mux); + return byte_read; +} +int i2s_push_sample(i2s_port_t i2s_num, const char *sample, TickType_t ticks_to_wait) +{ + int i, bytes_to_push = 0; + char *data_ptr; + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + if (p_i2s_obj[i2s_num]->tx->rw_pos == p_i2s_obj[i2s_num]->tx->buf_size || p_i2s_obj[i2s_num]->tx->curr_ptr == NULL) { + if (xQueueReceive(p_i2s_obj[i2s_num]->tx->queue, &p_i2s_obj[i2s_num]->tx->curr_ptr, ticks_to_wait) == pdFALSE) { + return 0; + } + ESP_LOGD(I2S_TAG, "rw_pos: %d, buf_size: %d, curr_ptr: %d", p_i2s_obj[i2s_num]->tx->rw_pos, p_i2s_obj[i2s_num]->tx->buf_size, (int)p_i2s_obj[i2s_num]->tx->curr_ptr); + p_i2s_obj[i2s_num]->tx->rw_pos = 0; + } + data_ptr = (char*)p_i2s_obj[i2s_num]->tx->curr_ptr; + data_ptr += p_i2s_obj[i2s_num]->tx->rw_pos; + for (i = 0; i < p_i2s_obj[i2s_num]->bytes_per_sample; i++) { + *data_ptr++ = *sample++; + bytes_to_push ++; + } + if (p_i2s_obj[i2s_num]->channel_num == 2) { + for (i = 0; i < p_i2s_obj[i2s_num]->bytes_per_sample; i++) { + *data_ptr++ = *sample++; + bytes_to_push ++; + } + } + + p_i2s_obj[i2s_num]->tx->rw_pos += p_i2s_obj[i2s_num]->bytes_per_sample * p_i2s_obj[i2s_num]->channel_num; + return bytes_to_push; +} + +int i2s_pop_sample(i2s_port_t i2s_num, char *sample, TickType_t ticks_to_wait) +{ + int i, bytes_to_pop = 0; + char *data_ptr; + I2S_CHECK((i2s_num < I2S_NUM_MAX), "i2s_num error", ESP_FAIL); + if (p_i2s_obj[i2s_num]->rx->rw_pos == p_i2s_obj[i2s_num]->rx->buf_size || p_i2s_obj[i2s_num]->rx->curr_ptr == NULL) { + if (xQueueReceive(p_i2s_obj[i2s_num]->rx->queue, &p_i2s_obj[i2s_num]->rx->curr_ptr, ticks_to_wait) == pdFALSE) { + return 0; + } + p_i2s_obj[i2s_num]->rx->rw_pos = 0; + } + data_ptr = (char*)p_i2s_obj[i2s_num]->rx->curr_ptr; + data_ptr += p_i2s_obj[i2s_num]->rx->rw_pos; + for (i = 0; i < p_i2s_obj[i2s_num]->bytes_per_sample; i++) { + *sample++ = *data_ptr++; + bytes_to_pop++; + } + if (p_i2s_obj[i2s_num]->channel_num == 2) { + for (i = 0; i < p_i2s_obj[i2s_num]->bytes_per_sample; i++) { + *sample++ = *data_ptr++; + bytes_to_pop++; + } + } + + p_i2s_obj[i2s_num]->rx->rw_pos += p_i2s_obj[i2s_num]->bytes_per_sample * p_i2s_obj[i2s_num]->channel_num; + return bytes_to_pop; +} diff --git a/components/driver/include/driver/i2s.h b/components/driver/include/driver/i2s.h new file mode 100644 index 0000000000..d44bb676a3 --- /dev/null +++ b/components/driver/include/driver/i2s.h @@ -0,0 +1,380 @@ +// 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_I2S_H_ +#define _DRIVER_I2S_H_ +#include "esp_err.h" +#include +#include "soc/gpio_reg.h" +#include "soc/soc.h" +#include "soc/i2s_struct.h" +#include "soc/i2s_reg.h" +#include "soc/rtc_io_reg.h" +#include "soc/io_mux_reg.h" +#include "rom/gpio.h" +#include "esp_attr.h" +#include "esp_intr_alloc.h" +#include "driver/periph_ctrl.h" +#include "freertos/semphr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define I2S_PIN_NO_CHANGE (-1) + + +/** + * @brief I2S bit width per sample. + * + */ +typedef enum { + I2S_BITS_PER_SAMPLE_8BIT = 8, /*!< I2S bits per sample: 8-bits*/ + I2S_BITS_PER_SAMPLE_16BIT = 16, /*!< I2S bits per sample: 16-bits*/ + I2S_BITS_PER_SAMPLE_24BIT = 24, /*!< I2S bits per sample: 24-bits*/ + I2S_BITS_PER_SAMPLE_32BIT = 32, /*!< I2S bits per sample: 32-bits*/ +} i2s_bits_per_sample_t; + +/** + * @brief I2S communication standard format + * + */ +typedef enum { + I2S_COMM_FORMAT_I2S = 0x01, /*!< I2S communication format I2S*/ + I2S_COMM_FORMAT_I2S_MSB = 0x02, /*!< I2S format MSB*/ + I2S_COMM_FORMAT_I2S_LSB = 0x04, /*!< I2S format LSB*/ + I2S_COMM_FORMAT_PCM = 0x08, /*!< I2S communication format PCM*/ + I2S_COMM_FORMAT_PCM_SHORT = 0x10, /*!< PCM Short*/ + I2S_COMM_FORMAT_PCM_LONG = 0x20, /*!< PCM Long*/ +} i2s_comm_format_t; + + +/** + * @brief I2S channel format type + */ +typedef enum { + I2S_CHANNEL_FMT_RIGHT_LEFT = 0x00, + I2S_CHANNEL_FMT_ALL_RIGHT, + I2S_CHANNEL_FMT_ALL_LEFT, + I2S_CHANNEL_FMT_ONLY_RIGHT, + I2S_CHANNEL_FMT_ONLY_LEFT, +} i2s_channel_fmt_t; + +/** + * @brief PDM sample rate ratio, measured in Hz. + * + */ +typedef enum { + PDM_SAMPLE_RATE_RATIO_64, + PDM_SAMPLE_RATE_RATIO_128, +} pdm_sample_rate_ratio_t; + +/** + * @brief PDM PCM convter enable/disable. + * + */ +typedef enum { + PDM_PCM_CONV_ENABLE, + PDM_PCM_CONV_DISABLE, +} pdm_pcm_conv_t; + + +/** + * @brief I2S Peripheral, 0 & 1. + * + */ +typedef enum { + I2S_NUM_0 = 0x0, /*!< I2S 0*/ + I2S_NUM_1 = 0x1, /*!< I2S 1*/ + I2S_NUM_MAX, +} i2s_port_t; + +/** + * @brief I2S Mode, defaut is I2S_MODE_MASTER | I2S_MODE_TX + * + */ +typedef enum { + I2S_MODE_MASTER = 1, + I2S_MODE_SLAVE = 2, + I2S_MODE_TX = 4, + I2S_MODE_RX = 8, + I2S_MODE_DAC_BUILT_IN = 16 +} i2s_mode_t; + +/** + * @brief I2S configuration parameters for i2s_param_config function + * + */ +typedef struct { + i2s_mode_t mode; /*!< I2S work mode*/ + int sample_rate; /*!< I2S sample rate*/ + i2s_bits_per_sample_t bits_per_sample; /*!< I2S bits per sample*/ + i2s_channel_fmt_t channel_format; /*!< I2S channel format */ + i2s_comm_format_t communication_format; /*!< I2S communication format */ + int intr_alloc_flags; /*!< Flags used to allocate the interrupt. One or multiple (ORred) ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info */ + int dma_buf_count; /*!< I2S DMA Buffer Count */ + int dma_buf_len; /*!< I2S DMA Buffer Length */ +} i2s_config_t; + +/** + * @brief I2S event types + * + */ +typedef enum { + I2S_EVENT_DMA_ERROR, + I2S_EVENT_TX_DONE, /*!< I2S DMA finish sent 1 buffer*/ + I2S_EVENT_RX_DONE, /*!< I2S DMA finish received 1 buffer*/ + I2S_EVENT_MAX, /*!< I2S event max index*/ +} i2s_event_type_t; + +/** + * @brief Event structure used in I2S event queue + * + */ +typedef struct { + i2s_event_type_t type; /*!< I2S event type */ + size_t size; /*!< I2S data size for I2S_DATA event*/ +} i2s_event_t; + +/** + * @brief I2S pin number for i2s_set_pin + * + */ +typedef struct { + int bck_io_num; /*!< BCK in out pin*/ + int ws_io_num; /*!< WS in out pin*/ + int data_out_num; /*!< DATA out pin*/ + int data_in_num; /*!< DATA in pin*/ +} i2s_pin_config_t; + +typedef intr_handle_t i2s_isr_handle_t; +/** + * @brief Set I2S pin number + * + * @note + * Internal signal can be output to multiple GPIO pads + * Only one GPIO pad can connect with input signal + * + * @param i2s_num I2S_NUM_0 or I2S_NUM_1 + * + * @param pin I2S Pin struct, or NULL for 2-channels, 8-bits DAC pin configuration (GPIO25 & GPIO26) + * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t i2s_set_pin(i2s_port_t i2s_num, const i2s_pin_config_t *pin); + +/** + * @brief i2s install and start driver + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @param i2s_config I2S configurations - see i2s_config_t struct + * + * @param queue_size I2S event queue size/depth. + * + * @param i2s_queue I2S event queue handle, if set NULL, driver will not use an event queue. + * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t i2s_driver_install(i2s_port_t i2s_num, const i2s_config_t *i2s_config, int queue_size, void* i2s_queue); + +/** + * @brief Uninstall I2S driver. + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t i2s_driver_uninstall(i2s_port_t i2s_num); + +/** + * @brief i2s read data buffer to i2s dma buffer + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @param src source address to write + * + * @param size size of data (size in bytes) + * + * @param ticks_to_wait Write timeout + * + * @return number of written bytes + */ +int i2s_write_bytes(i2s_port_t i2s_num, const char *src, size_t size, TickType_t ticks_to_wait); + +/** + * @brief i2s write data buffer to i2s dma buffer + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @param dest destination address to read + * + * @param size size of data (size in bytes) + * + * @param ticks_to_wait Read timeout + * + * @return number of read bytes + */ +int i2s_read_bytes(i2s_port_t i2s_num, char* dest, size_t size, TickType_t ticks_to_wait); + +/** + * @brief i2s push 1 sample to i2s dma buffer, with the size parameter equal to one sample's size in bytes = bits_per_sample/8. + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @param sample destination address to write (depend on bits_per_sample, size of sample (in bytes) = 2*bits_per_sample/8) + * + * @param ticks_to_wait Push timeout + * + * @return number of push bytes + */ +int i2s_push_sample(i2s_port_t i2s_num, const char *sample, TickType_t ticks_to_wait); + +/** + * @brief Pop 1 sample to i2s dma buffer, with the size parameter equal to one sample's size in bytes = bits_per_sample/8. + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @param sample destination address to write (depend on bits_per_sample, size of sample (in bytes) = 2*bits_per_sample/8) + * + * @param ticks_to_wait Pop timeout + * + * @return number of pop bytes + */ +int i2s_pop_sample(i2s_port_t i2s_num, char *sample, TickType_t ticks_to_wait); + + +/** + * @brief Set clock rate used for I2S RX and TX + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @param rate I2S clock (ex: 8000, 44100...) + * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t i2s_set_sample_rates(i2s_port_t i2s_num, uint32_t rate); + +/** + * @brief Start driver + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * +* @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t i2s_start(i2s_port_t i2s_num); + +/** + * @brief Stop driver + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t i2s_stop(i2s_port_t i2s_num); + +/** + * @brief Set the TX DMA buffer contents to all zeroes + * + * @param i2s_num I2S_NUM_0, I2S_NUM_1 + * + * @return + * - ESP_OK Success + * - ESP_FAIL Parameter error + */ +esp_err_t i2s_zero_dma_buffer(i2s_port_t i2s_num); + +/***************************EXAMPLE********************************** + * + * + * ----------------EXAMPLE OF I2S SETTING --------------------- + * @code{c} + * + * #include "freertos/queue.h" + * #define I2S_INTR_NUM 17 //choose one interrupt number from soc.h + * int i2s_num = 0; //i2s port number + * i2s_config_t i2s_config = { + * .mode = I2S_MODE_MASTER | I2S_MODE_TX, + * .sample_rate = 44100, + * .bits_per_sample = 16, //16, 32 + * .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //format LEFT_RIGHT + * .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB, + * .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + * .dma_buf_count = 8, + * .dma_buf_len = 64 + * }; + * + * i2s_pin_config_t pin_config = { + * .bck_io_num = 26, + * .ws_io_num = 25, + * .data_out_num = 22, + * .data_in_num = I2S_PIN_NO_CHANGE + * }; + * + * i2s_driver_install(i2s_num, &i2s_config, 0, NULL); //install and start i2s driver + * + * i2s_set_pin(i2s_num, &pin_config); + * + * i2s_set_sample_rates(i2s_num, 22050); //set sample rates + * + * + * i2s_driver_uninstall(i2s_num); //stop & destroy i2s driver + *@endcode + * + * ----------------EXAMPLE USING I2S WITH DAC --------------------- + * @code{c} + * + * #include "freertos/queue.h" + * #define I2S_INTR_NUM 17 //choose one interrupt number from soc.h + * int i2s_num = 0; //i2s port number + * i2s_config_t i2s_config = { + * .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN, + * .sample_rate = 44100, + * .bits_per_sample = 8, // Only 8-bit DAC support + * .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // + * .communication_format = I2S_COMM_FORMAT_I2S_MSB, + * .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + * .dma_buf_count = 8, + * .dma_buf_len = 64 + * }; + * + * + * i2s_driver_install(i2s_num, &i2s_config, 0, NULL); //install and start i2s driver + * + * i2s_set_pin(i2s_num, NULL); //for internal DAC + * + * i2s_set_sample_rates(i2s_num, 22050); //set sample rates + * + * i2s_driver_uninstall(i2s_num); //stop & destroy i2s driver + *@endcode + *-----------------------------------------------------------------------------* + ***************************END OF EXAMPLE**********************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* _DRIVER_I2S_H_ */ diff --git a/examples/22_i2s/Makefile b/examples/22_i2s/Makefile new file mode 100644 index 0000000000..b15a137b88 --- /dev/null +++ b/examples/22_i2s/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +VERBOSE = 1 +PROJECT_NAME := esp32-i2s-driver-example +include $(IDF_PATH)/make/project.mk + diff --git a/examples/22_i2s/main/app_main.c b/examples/22_i2s/main/app_main.c new file mode 100644 index 0000000000..9c8f80fd56 --- /dev/null +++ b/examples/22_i2s/main/app_main.c @@ -0,0 +1,72 @@ +/* I2S Example + + This example code will output 100Hz sine wave and triangle wave to 2-channel of I2S driver + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "driver/i2s.h" +#include + + +#define SAMPLE_RATE (36000) +#define I2S_NUM (0) +#define WAVE_FREQ_HZ (100) +#define PI 3.14159265 + +#define SAMPLE_PER_CYCLE (SAMPLE_RATE/WAVE_FREQ_HZ) + +void app_main() +{ + unsigned int i, sample_val; + float sin_float, triangle_float, triangle_step = 65536.0 / SAMPLE_PER_CYCLE; + //for 36Khz sample rates, we create 100Hz sine wave, every cycle need 36000/100 = 360 samples (4-bytes each sample) + //using 6 buffers, we need 60-samples per buffer + //2-channels, 16-bit each channel, total buffer is 360*4 = 1440 bytes + i2s_config_t i2s_config = { + .mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX + .sample_rate = SAMPLE_RATE, + .bits_per_sample = 16, //16-bit per channel + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB, + .dma_buf_count = 6, + .dma_buf_len = 60, // + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 //Interrupt level 1 + }; + i2s_pin_config_t pin_config = { + .bck_io_num = 26, + .ws_io_num = 25, + .data_out_num = 22, + .data_in_num = -1 //Not used + }; + + nvs_flash_init(); + i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL); + i2s_set_pin(I2S_NUM, &pin_config); + + triangle_float = -32767; + + for(i = 0; i < SAMPLE_PER_CYCLE; i++) { + sin_float = sin(i * PI / 180.0); + if(sin_float >= 0) + triangle_float += triangle_step; + else + triangle_float -= triangle_step; + sin_float *= 32767; + + sample_val = 0; + sample_val += (short)triangle_float; + sample_val = sample_val << 16; + sample_val += (short) sin_float; + + i2s_push_sample(I2S_NUM, (char *)&sample_val, portMAX_DELAY); + } +} diff --git a/examples/22_i2s/main/component.mk b/examples/22_i2s/main/component.mk new file mode 100644 index 0000000000..dbc5a7a9e7 --- /dev/null +++ b/examples/22_i2s/main/component.mk @@ -0,0 +1,10 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# This Makefile should, at the very least, just include $(SDK_PATH)/make/component.mk. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_ADD_INCLUDEDIRS := . From d0fccbce15360ff520bc1133f311592895c1e024 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Mon, 19 Dec 2016 15:40:21 +0800 Subject: [PATCH 030/167] examples: Add OTA demo --- components/app_update/esp_ota_ops.c | 2 +- examples/18_ota/Makefile | 9 + examples/18_ota/OTA_workflow.png | Bin 0 -> 58211 bytes examples/18_ota/README.md | 101 ++++++++ examples/18_ota/main/Kconfig.projbuild | 40 ++++ examples/18_ota/main/component.mk | 4 + examples/18_ota/main/ota.c | 318 +++++++++++++++++++++++++ examples/18_ota/sdkconfig | 5 + 8 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 examples/18_ota/Makefile create mode 100644 examples/18_ota/OTA_workflow.png create mode 100644 examples/18_ota/README.md create mode 100644 examples/18_ota/main/Kconfig.projbuild create mode 100644 examples/18_ota/main/component.mk create mode 100644 examples/18_ota/main/ota.c create mode 100644 examples/18_ota/sdkconfig diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index e95937256f..439117b596 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -378,4 +378,4 @@ const esp_partition_t *esp_ota_get_boot_partition(void) ESP_LOGE(TAG, "not found current bin"); return NULL; } -} \ No newline at end of file +} diff --git a/examples/18_ota/Makefile b/examples/18_ota/Makefile new file mode 100644 index 0000000000..7ffb10234f --- /dev/null +++ b/examples/18_ota/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 := ota + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/18_ota/OTA_workflow.png b/examples/18_ota/OTA_workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..3e91dafb3801df54ddd86cceb069d6ecf9bae75f GIT binary patch literal 58211 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 +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_partition.h" + +#include "nvs_flash.h" +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" + +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD +#define EXAMPLE_SERVER_IP CONFIG_SERVER_IP +#define EXAMPLE_SERVER_PORT CONFIG_SERVER_PORT +#define EXAMPLE_FILENAME CONFIG_EXAMPLE_FILENAME +#define BUFFSIZE 1024 +#define TEXT_BUFFSIZE 1024 + +static const char *TAG = "ota"; +/*an ota data write buffer ready to write to the flash*/ +char ota_write_data[BUFFSIZE + 1] = { 0 }; +/*an packet receive buffer*/ +char text[BUFFSIZE + 1] = { 0 }; +/* an image total length*/ +int binary_file_length = 0; +/*socket id*/ +int socket_id = -1; +char http_request[64] = {0}; +/* operate handle : uninitialized value is zero ,every ota begin would exponential growth*/ +esp_ota_handle_t out_handle = 0; +esp_partition_t operate_partition; + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +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; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, 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); + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +/*read buffer by byte still delim ,return read bytes counts*/ +int read_until(char *buffer, char delim, int len) +{ +// /*TODO: delim check,buffer check,further: do an buffer length limited*/ + int i = 0; + while (buffer[i] != delim && i < len) { + ++i; + } + return i + 1; +} + +/* resolve a packet from http socket + * return true if packet including \r\n\r\n that means http packet header finished,start to receive packet body + * otherwise return false + * */ +bool resolve_pkg(char text[], int total_len, esp_ota_handle_t out_handle) +{ + /* i means current position */ + int i = 0, i_read_len = 0; + while (text[i] != 0 && i < total_len) { + i_read_len = read_until(&text[i], '\n', total_len); + // if we resolve \r\n line,we think packet header is finished + if (i_read_len == 2) { + int i_write_len = total_len - (i + 2); + memset(ota_write_data, 0, BUFFSIZE); + /*copy first http packet body to write buffer*/ + memcpy(ota_write_data, &(text[i + 2]), i_write_len); + /*check write packet header first byte:0xE9 second byte:0x09 */ + if (ota_write_data[0] == 0xE9 && i_write_len >= 2 && ota_write_data[1] == 0x09) { + ESP_LOGI(TAG, "OTA Write Header format Check OK. first byte is %02x ,second byte is %02x", ota_write_data[0], ota_write_data[1]); + } else { + ESP_LOGE(TAG, "OTA Write Header format Check Failed! first byte is %02x ,second byte is %02x", ota_write_data[0], ota_write_data[1]); + return false; + } + + esp_err_t err = esp_ota_write( out_handle, (const void *)ota_write_data, i_write_len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error: esp_ota_write failed! err=%x", err); + return false; + } else { + ESP_LOGI(TAG, "esp_ota_write header OK"); + binary_file_length += i_write_len; + } + return true; + } + i += i_read_len; + } + return false; +} + +bool connect_to_http_server() +{ + ESP_LOGI(TAG, "Server IP: %s Server Port:%s", EXAMPLE_SERVER_IP, EXAMPLE_SERVER_PORT); + sprintf(http_request, "GET %s HTTP/1.1\r\nHost: %s:%s \r\n\r\n", EXAMPLE_FILENAME, EXAMPLE_SERVER_IP, EXAMPLE_SERVER_PORT); + + int http_connect_flag = -1; + struct sockaddr_in sock_info; + + socket_id = socket(AF_INET, SOCK_STREAM, 0); + if (socket_id == -1) { + ESP_LOGE(TAG, "Create socket failed!"); + return false; + } + + // set connect info + memset(&sock_info, 0, sizeof(struct sockaddr_in)); + sock_info.sin_family = AF_INET; + sock_info.sin_addr.s_addr = inet_addr(EXAMPLE_SERVER_IP); + sock_info.sin_port = htons(atoi(EXAMPLE_SERVER_PORT)); + + // connect to http server + http_connect_flag = connect(socket_id, (struct sockaddr *)&sock_info, sizeof(sock_info)); + if (http_connect_flag == -1) { + ESP_LOGE(TAG, "Connect to server failed! errno=%d", errno); + close(socket_id); + return false; + } else { + ESP_LOGI(TAG, "Connected to server"); + return true; + } + return false; +} + +bool ota_init() +{ + esp_err_t err; + const esp_partition_t *esp_current_partition = esp_ota_get_boot_partition(); + if (esp_current_partition->type != ESP_PARTITION_TYPE_APP) { + ESP_LOGE(TAG, "Error: esp_current_partition->type != ESP_PARTITION_TYPE_APP"); + return false; + } + + esp_partition_t find_partition; + memset(&operate_partition, 0, sizeof(esp_partition_t)); + /*choose which OTA image should we write to*/ + switch (esp_current_partition->subtype) { + case ESP_PARTITION_SUBTYPE_APP_FACTORY: + find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0; + break; + case ESP_PARTITION_SUBTYPE_APP_OTA_0: + find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_1; + break; + case ESP_PARTITION_SUBTYPE_APP_OTA_1: + find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0; + break; + default: + break; + } + find_partition.type = ESP_PARTITION_TYPE_APP; + + const esp_partition_t *partition = esp_partition_find_first(find_partition.type, find_partition.subtype, NULL); + assert(partition != NULL); + memset(&operate_partition, 0, sizeof(esp_partition_t)); + err = esp_ota_begin( partition, OTA_SIZE_UNKNOWN, &out_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_begin failed err=0x%x!", err); + return false; + } else { + memcpy(&operate_partition, partition, sizeof(esp_partition_t)); + ESP_LOGI(TAG, "esp_ota_begin init OK"); + return true; + } + return false; +} + +void __attribute__((noreturn)) task_fatal_error() +{ + ESP_LOGE(TAG, "Exiting task due to fatal error..."); + close(socket_id); + (void)vTaskDelete(NULL); +} + +void main_task(void *pvParameter) +{ + esp_err_t err; + ESP_LOGI(TAG, "Starting OTA example..."); + /* 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, "Connect to Wifi ! Start to Connect to Server...."); + + /*connect to http server*/ + if (connect_to_http_server()) { + ESP_LOGI(TAG, "Connected to http server"); + } else { + ESP_LOGE(TAG, "Connect to http server failed!"); + task_fatal_error(); + } + + int res = -1; + /*send GET request to http server*/ + res = send(socket_id, http_request, strlen(http_request), 0); + if (res == -1) { + ESP_LOGE(TAG, "Send GET request to server failed"); + task_fatal_error(); + } else { + ESP_LOGI(TAG, "Send GET request to server succeeded"); + } + + if ( ota_init() ) { + ESP_LOGI(TAG, "OTA Init succeeded"); + } else { + ESP_LOGE(TAG, "OTA Init failed"); + task_fatal_error(); + } + + bool pkg_body_start = false, flag = true; + /*deal with all receive packet*/ + while (flag) { + memset(text, 0, TEXT_BUFFSIZE); + memset(ota_write_data, 0, BUFFSIZE); + int buff_len = recv(socket_id, text, TEXT_BUFFSIZE, 0); + if (buff_len < 0) { /*receive error*/ + ESP_LOGE(TAG, "Error: receive data error! errno=%d", errno); + task_fatal_error(); + } else if (buff_len > 0 && !pkg_body_start) { /*deal with packet header*/ + memcpy(ota_write_data, text, buff_len); + pkg_body_start = resolve_pkg(text, buff_len, out_handle); + } else if (buff_len > 0 && pkg_body_start) { /*deal with packet body*/ + memcpy(ota_write_data, text, buff_len); + err = esp_ota_write( out_handle, (const void *)ota_write_data, buff_len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error: esp_ota_write failed! err=%x", err); + task_fatal_error(); + } + binary_file_length += buff_len; + ESP_LOGI(TAG, "Have written image length %d", binary_file_length); + } else if (buff_len == 0) { /*packet over*/ + flag = false; + ESP_LOGI(TAG, "Connection closed, all packets received"); + close(socket_id); + } else { + ESP_LOGE(TAG, "Unexpected recv result"); + } + } + + ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length); + + if (esp_ota_end(out_handle) != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_end failed!"); + task_fatal_error(); + } + err = esp_ota_set_boot_partition(&operate_partition); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%x", err); + task_fatal_error(); + } + ESP_LOGI(TAG, "Prepare to restart system!"); + esp_restart(); + return ; +} + +void app_main() +{ + nvs_flash_init(); + initialise_wifi(); + xTaskCreate(&main_task, "main_task", 8192, NULL, 5, NULL); +} diff --git a/examples/18_ota/sdkconfig b/examples/18_ota/sdkconfig new file mode 100644 index 0000000000..070b529a05 --- /dev/null +++ b/examples/18_ota/sdkconfig @@ -0,0 +1,5 @@ +# Some sdkconfig parameters overriden from the defaults for this example, +# This file is in git but will be overwritten with the autogenerated file +# the first time "menuconfig" is run (changes ignored by git). +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_TWO_OTA=y From 2350288a33a3fd6396603eee0642d494fbf42c0d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 21 Dec 2016 12:46:24 +1100 Subject: [PATCH 031/167] examples: Move sdkconfig.defaults support into build system Is used fairly widely, and a little bit buggy in the form where it was in each Makefile (didn't always get copied in place). --- docs/build_system.rst | 9 +++++++++ examples/05_ble_adv/Makefile | 8 -------- examples/12_blufi/Makefile | 8 -------- examples/14_gatt_server/Makefile | 8 -------- examples/15_gatt_client/Makefile | 8 -------- examples/18_ota/sdkconfig | 5 ----- examples/18_ota/sdkconfig.defaults | 4 ++++ make/project_config.mk | 18 +++++++++++++++--- 8 files changed, 28 insertions(+), 40 deletions(-) delete mode 100644 examples/18_ota/sdkconfig create mode 100644 examples/18_ota/sdkconfig.defaults diff --git a/docs/build_system.rst b/docs/build_system.rst index 6687fa69ed..2e388bd495 100644 --- a/docs/build_system.rst +++ b/docs/build_system.rst @@ -477,3 +477,12 @@ is set then the component can instruct the linker to link other binaries instead .. _esp-idf-template: https://github.com/espressif/esp-idf-template .. _GNU Make Manual: https://www.gnu.org/software/make/manual/make.html .. _[_f1]: Actually, some components in esp-idf are "pure configuration" components that don't have a component.mk file, only a Makefile.projbuild and/or Kconfig.projbuild file. However, these components are unusual and most components have a component.mk file. + + +Custom sdkconfig defaults +------------------------- + +For example projects or other projects where you don't want to specify a full sdkconfig configuration, but you do want to override some key values from the esp-idf defaults, it is possible to create a file ``sdkconfig.defaults`` in the project directory. This file will be used when running ``make defconfig``, or creating a new config from scratch. + +To override the name of this file, set the ``SDKCONFIG_DEFAULTS`` environment variable. + diff --git a/examples/05_ble_adv/Makefile b/examples/05_ble_adv/Makefile index 3a913b817b..2319786d4a 100644 --- a/examples/05_ble_adv/Makefile +++ b/examples/05_ble_adv/Makefile @@ -6,11 +6,3 @@ PROJECT_NAME := ble_adv include $(IDF_PATH)/make/project.mk - -# Copy some defaults into the sdkconfig by default -# so BT stack is enabled -sdkconfig: sdkconfig.defaults - $(Q) cp $< $@ - -menuconfig: sdkconfig -defconfig: sdkconfig diff --git a/examples/12_blufi/Makefile b/examples/12_blufi/Makefile index 7e7548444f..9c80f26c1a 100644 --- a/examples/12_blufi/Makefile +++ b/examples/12_blufi/Makefile @@ -8,11 +8,3 @@ PROJECT_NAME := blufi_demo COMPONENT_ADD_INCLUDEDIRS := components/include include $(IDF_PATH)/make/project.mk - -# Copy some defaults into the sdkconfig by default -# so BT stack is enabled -sdkconfig: sdkconfig.defaults - $(Q) cp $< $@ - -menuconfig: sdkconfig -defconfig: sdkconfig diff --git a/examples/14_gatt_server/Makefile b/examples/14_gatt_server/Makefile index d7732bd280..2f76e60b60 100644 --- a/examples/14_gatt_server/Makefile +++ b/examples/14_gatt_server/Makefile @@ -8,11 +8,3 @@ PROJECT_NAME := gatt_server_demos COMPONENT_ADD_INCLUDEDIRS := components/include include $(IDF_PATH)/make/project.mk - -# Copy some defaults into the sdkconfig by default -# so BT stack is enabled -sdkconfig: sdkconfig.defaults - $(Q) cp $< $@ - -menuconfig: sdkconfig -defconfig: sdkconfig diff --git a/examples/15_gatt_client/Makefile b/examples/15_gatt_client/Makefile index 93f793308f..700ffd7add 100644 --- a/examples/15_gatt_client/Makefile +++ b/examples/15_gatt_client/Makefile @@ -8,11 +8,3 @@ PROJECT_NAME := gatt_client_demo COMPONENT_ADD_INCLUDEDIRS := components/include include $(IDF_PATH)/make/project.mk - -# Copy some defaults into the sdkconfig by default -# so BT stack is enabled -sdkconfig: sdkconfig.defaults - $(Q) cp $< $@ - -menuconfig: sdkconfig -defconfig: sdkconfig diff --git a/examples/18_ota/sdkconfig b/examples/18_ota/sdkconfig deleted file mode 100644 index 070b529a05..0000000000 --- a/examples/18_ota/sdkconfig +++ /dev/null @@ -1,5 +0,0 @@ -# Some sdkconfig parameters overriden from the defaults for this example, -# This file is in git but will be overwritten with the autogenerated file -# the first time "menuconfig" is run (changes ignored by git). -CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y -CONFIG_PARTITION_TABLE_TWO_OTA=y diff --git a/examples/18_ota/sdkconfig.defaults b/examples/18_ota/sdkconfig.defaults new file mode 100644 index 0000000000..2289a82300 --- /dev/null +++ b/examples/18_ota/sdkconfig.defaults @@ -0,0 +1,4 @@ +# Default sdkconfig parameters to use the OTA +# partition table layout, with a 4MB flash size +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_TWO_OTA=y diff --git a/make/project_config.mk b/make/project_config.mk index 187d1ac282..b8e40f4357 100644 --- a/make/project_config.mk +++ b/make/project_config.mk @@ -11,6 +11,10 @@ KCONFIG_TOOL_DIR=$(IDF_PATH)/tools/kconfig # unless it's overriden (happens for bootloader) SDKCONFIG ?= $(PROJECT_PATH)/sdkconfig +# SDKCONFIG_DEFAULTS is an optional file containing default +# overrides (usually used for esp-idf examples) +SDKCONFIG_DEFAULTS ?= $(PROJECT_PATH)/sdkconfig.defaults + # reset MAKEFLAGS as the menuconfig makefile uses implicit compile rules $(KCONFIG_TOOL_DIR)/mconf $(KCONFIG_TOOL_DIR)/conf: MAKEFLAGS=$(ORIGINAL_MAKEFLAGS) CC=$(HOSTCC) LD=$(HOSTLD) \ @@ -21,21 +25,29 @@ KCONFIG_TOOL_ENV=KCONFIG_AUTOHEADER=$(abspath $(BUILD_DIR_BASE)/include/sdkconfi COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" KCONFIG_CONFIG=$(SDKCONFIG) \ COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)" -menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig +menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig | defconfig $(summary) MENUCONFIG $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig ifeq ("$(wildcard $(SDKCONFIG))","") ifeq ("$(call prereq_if_explicit,defconfig)","") -# if not configuration is present and defconfig is not a target, run makeconfig -$(SDKCONFIG): menuconfig +# if not configuration is present and defconfig is not a target, run defconfig then menuconfig +$(SDKCONFIG): defconfig menuconfig else +# otherwise, just defconfig $(SDKCONFIG): defconfig endif endif +$(wildcard $(PROJECT_PATH)/sdkconfig.defaults): | menuconfig defconfig + cp $< $@ + +# defconfig creates a default config, based on SDKCONFIG_DEFAULTS if present defconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(BUILD_DIR_BASE) $(summary) DEFCONFIG +ifneq ("$(wildcard $(SDKCONFIG_DEFAULTS))","") + cp $(SDKCONFIG_DEFAULTS) $(SDKCONFIG) +endif mkdir -p $(BUILD_DIR_BASE)/include/config $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/conf --olddefconfig $(IDF_PATH)/Kconfig From d245f016ea013b6c12e1ca4755a671eb57a1cc61 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 20 Dec 2016 18:02:47 +1100 Subject: [PATCH 032/167] esptool: Add new options to reset before/after, detect flash size --- components/bootloader/Makefile.projbuild | 5 ++- components/esptool_py/Kconfig.projbuild | 57 ++++++++++++++++++++++++ components/esptool_py/Makefile.projbuild | 9 +++- components/esptool_py/esptool | 2 +- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/components/bootloader/Makefile.projbuild b/components/bootloader/Makefile.projbuild index 35be94e4a3..9e7b17b65f 100644 --- a/components/bootloader/Makefile.projbuild +++ b/components/bootloader/Makefile.projbuild @@ -50,11 +50,14 @@ else ifdef CONFIG_SECURE_BOOTLOADER_ONE_TIME_FLASH # One time flashing requires user to run esptool.py command themselves, # and warning is printed about inability to reflash. +# +# The flashing command is deliberately printed without an auto-reset +# step, so the device doesn't immediately reset to flash itself. bootloader: $(BOOTLOADER_BIN) @echo $(SEPARATOR) @echo "Bootloader built. One-time flash command is:" - @echo "$(ESPTOOLPY_WRITE_FLASH) $(BOOTLOADER_OFFSET) $(BOOTLOADER_BIN)" + @echo "$(subst hard_reset,no_reset,$(ESPTOOLPY_WRITE_FLASH)) $(BOOTLOADER_OFFSET) $(BOOTLOADER_BIN)" @echo $(SEPARATOR) @echo "* IMPORTANT: After first boot, BOOTLOADER CANNOT BE RE-FLASHED on same device" diff --git a/components/esptool_py/Kconfig.projbuild b/components/esptool_py/Kconfig.projbuild index 8bab51225e..dd2077cbb3 100644 --- a/components/esptool_py/Kconfig.projbuild +++ b/components/esptool_py/Kconfig.projbuild @@ -121,4 +121,61 @@ config ESPTOOLPY_FLASHSIZE default "8MB" if ESPTOOLPY_FLASHSIZE_8MB default "16MB" if ESPTOOLPY_FLASHSIZE_16MB +config ESPTOOLPY_FLASHSIZE_DETECT + bool "Detect flash size when flashing bootloader" + default y + help + If this option is set, 'make flash' targets will automatically detect + the flash size and update the bootloader image when flashing. + +choice ESPTOOLPY_BEFORE + prompt "Before flashing" + default ESPTOOLPY_BEFORE_RESET + help + Configure whether esptool.py should reset the ESP32 before flashing. + + Automatic resetting depends on the RTS & DTR signals being + wired from the serial port to the ESP32. Most USB development + boards do this internally. + + The "Reset with ESP32R0 Windows workaround" option works + around an automatic reset bug in hardware, when using Windows + with some development boards. This fix only works if you're + using a silicon revision 0 ESP32. + +config ESPTOOLPY_BEFORE_RESET + bool "Reset to bootloader" +config ESPTOOLPY_BEFORE_NORESET + bool "No reset" +config ESPTOOLPY_BEFORE_ESP32R0 + bool "Reset with ESP32R0 Windows workaround" +endchoice + +config ESPTOOLPY_BEFORE + string + default "default_reset" if ESPTOOLPY_BEFORE_RESET + default "no_reset" if ESPTOOLPY_BEFORE_NORESET + default "esp32r0" if ESPTOOLPY_BEFORE_ESP32R0 + +choice ESPTOOLPY_AFTER + prompt "After flashing" + default ESPTOOLPY_AFTER_RESET + help + Configure whether esptool.py should reset the ESP32 after flashing. + + Automatic resetting depends on the RTS & DTR signals being + wired from the serial port to the ESP32. Most USB development + boards do this internally. + +config ESPTOOLPY_AFTER_RESET + bool "Reset after flashing" +config ESPTOOLPY_AFTER_NORESET + bool "Stay in bootloader" +endchoice + +config ESPTOOLPY_AFTER + string + default "hard_reset" if ESPTOOLPY_AFTER_RESET + default "no_reset" if ESPTOOLPY_AFTER_NORESET + endmenu diff --git a/components/esptool_py/Makefile.projbuild b/components/esptool_py/Makefile.projbuild index b5992a4d96..88b5894731 100644 --- a/components/esptool_py/Makefile.projbuild +++ b/components/esptool_py/Makefile.projbuild @@ -13,7 +13,7 @@ PYTHON ?= $(call dequote,$(CONFIG_PYTHON)) # ESPTOOLPY_SRC := $(COMPONENT_PATH)/esptool/esptool.py ESPTOOLPY := $(PYTHON) $(ESPTOOLPY_SRC) --chip esp32 -ESPTOOLPY_SERIAL := $(ESPTOOLPY) --port $(ESPPORT) --baud $(ESPBAUD) +ESPTOOLPY_SERIAL := $(ESPTOOLPY) --port $(ESPPORT) --baud $(ESPBAUD) --before $(CONFIG_ESPTOOLPY_BEFORE) --after $(CONFIG_ESPTOOLPY_AFTER) # Supporting esptool command line tools ESPEFUSEPY := $(PYTHON) $(COMPONENT_PATH)/esptool/espefuse.py @@ -21,10 +21,15 @@ ESPSECUREPY := $(PYTHON) $(COMPONENT_PATH)/esptool/espsecure.py export ESPSECUREPY # is used in bootloader_support component ESPTOOL_FLASH_OPTIONS := --flash_mode $(ESPFLASHMODE) --flash_freq $(ESPFLASHFREQ) --flash_size $(ESPFLASHSIZE) +ifdef CONFIG_ESPTOOLPY_FLASHSIZE_DETECT +ESPTOOL_WRITE_FLASH_OPTIONS := --flash_mode $(ESPFLASHMODE) --flash_freq $(ESPFLASHFREQ) --flash_size detect +else +ESPTOOL_WRITE_FLASH_OPTIONS := $(ESPTOOL_FLASH_OPTIONS) +endif ESPTOOL_ELF2IMAGE_OPTIONS := -ESPTOOLPY_WRITE_FLASH=$(ESPTOOLPY_SERIAL) write_flash $(if $(CONFIG_ESPTOOLPY_COMPRESSED),-z) $(ESPTOOL_FLASH_OPTIONS) +ESPTOOLPY_WRITE_FLASH=$(ESPTOOLPY_SERIAL) write_flash $(if $(CONFIG_ESPTOOLPY_COMPRESSED),-z) $(ESPTOOL_WRITE_FLASH_OPTIONS) ESPTOOL_ALL_FLASH_ARGS += $(CONFIG_APP_OFFSET) $(APP_BIN) diff --git a/components/esptool_py/esptool b/components/esptool_py/esptool index adc914b91a..fe69994270 160000 --- a/components/esptool_py/esptool +++ b/components/esptool_py/esptool @@ -1 +1 @@ -Subproject commit adc914b91ac6d2cfd1ace56307b4374eb9439e14 +Subproject commit fe69994270e2a450aad3e94a409b58460b1a214f From 76e61ded3055c8933c9616688a1241bfdaaab1cc Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Dec 2016 13:15:01 +1100 Subject: [PATCH 033/167] bootloader: Call esp_partition_table_basic_verify() as part of standard boot Was previously only verified during flash encryption. --- .../bootloader/src/main/bootloader_start.c | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/components/bootloader/src/main/bootloader_start.c b/components/bootloader/src/main/bootloader_start.c index 7dd753b3f8..e5fda9cf53 100644 --- a/components/bootloader/src/main/bootloader_start.c +++ b/components/bootloader/src/main/bootloader_start.c @@ -40,6 +40,7 @@ #include "esp_image_format.h" #include "esp_secure_boot.h" #include "esp_flash_encrypt.h" +#include "esp_flash_partitions.h" #include "bootloader_flash.h" #include "bootloader_config.h" @@ -116,16 +117,14 @@ bool load_partition_table(bootloader_state_t* bs) { const esp_partition_info_t *partitions; const int ESP_PARTITION_TABLE_DATA_LEN = 0xC00; /* length of actual data (signature is appended to this) */ - const int MAX_PARTITIONS = ESP_PARTITION_TABLE_DATA_LEN / sizeof(esp_partition_info_t); char *partition_usage; - - ESP_LOGI(TAG, "Partition Table:"); - ESP_LOGI(TAG, "## Label Usage Type ST Offset Length"); + esp_err_t err; + int num_partitions; #ifdef CONFIG_SECURE_BOOT_ENABLED if(esp_secure_boot_enabled()) { ESP_LOGI(TAG, "Verifying partition table signature..."); - esp_err_t err = esp_secure_boot_verify_signature(ESP_PARTITION_TABLE_ADDR, ESP_PARTITION_TABLE_DATA_LEN); + err = esp_secure_boot_verify_signature(ESP_PARTITION_TABLE_ADDR, ESP_PARTITION_TABLE_DATA_LEN); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to verify partition table signature."); return false; @@ -141,17 +140,21 @@ bool load_partition_table(bootloader_state_t* bs) } ESP_LOGD(TAG, "mapped partition table 0x%x at 0x%x", ESP_PARTITION_TABLE_ADDR, (intptr_t)partitions); - for(int i = 0; i < MAX_PARTITIONS; i++) { + err = esp_partition_table_basic_verify(partitions, true, &num_partitions); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to verify partition table"); + return false; + } + + ESP_LOGI(TAG, "Partition Table:"); + ESP_LOGI(TAG, "## Label Usage Type ST Offset Length"); + + for(int i = 0; i < num_partitions; i++) { const esp_partition_info_t *partition = &partitions[i]; ESP_LOGD(TAG, "load partition table entry 0x%x", (intptr_t)partition); ESP_LOGD(TAG, "type=%x subtype=%x", partition->type, partition->subtype); partition_usage = "unknown"; - if (partition->magic != ESP_PARTITION_MAGIC) { - /* invalid partition definition indicates end-of-table */ - break; - } - /* valid partition table */ switch(partition->type) { case PART_TYPE_APP: /* app partition */ From 3783e28f0e57bf47ba12c67df9e0f09143b17abd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Dec 2016 13:16:22 +1100 Subject: [PATCH 034/167] bootloader: Check all partitions fit inside configured flash size --- .../bootloader_support/src/flash_partitions.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/components/bootloader_support/src/flash_partitions.c b/components/bootloader_support/src/flash_partitions.c index ed427df1a6..b60968f969 100644 --- a/components/bootloader_support/src/flash_partitions.c +++ b/components/bootloader_support/src/flash_partitions.c @@ -13,32 +13,43 @@ // limitations under the License. #include "esp_flash_partitions.h" #include "esp_log.h" +#include "rom/spi_flash.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; 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) { + 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) { + if (part->magic != ESP_PARTITION_MAGIC) { if (log_errors) { ESP_LOGE(TAG, "partition %d invalid magic number 0x%x", num_parts, part->magic); } 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) { From 79d6d9f7019b5d33ff7cb4ebedcbf49457be25c2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Dec 2016 15:20:49 +1100 Subject: [PATCH 035/167] CI build_examples: Correctly detect example build failures "pipefail" regression when fail-on-warnings was added... --- make/build_examples.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/make/build_examples.sh b/make/build_examples.sh index f1b37b7b4b..dc10d95ab2 100755 --- a/make/build_examples.sh +++ b/make/build_examples.sh @@ -30,10 +30,11 @@ for example in ${IDF_PATH}/examples/*; do # build non-verbose first BUILDLOG=$(mktemp -t examplebuild.XXXX.log) ( + set -o pipefail # so result of make all isn't lost when piping to tee set -e make clean defconfig - make all 2>&1 | tee $BUILDLOG - ) || (RESULT=$?; make V=1) # only build verbose if there's an error + make $* all 2>&1 | tee $BUILDLOG + ) || { RESULT=$?; make V=1; } # only build verbose if there's an error popd EXAMPLE_NUM=$(( $EXAMPLE_NUM + 1 )) From eb8324e2c280837aecde5d454ba52d663d3c77a8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Dec 2016 16:12:48 +1100 Subject: [PATCH 036/167] examples: Fix build errors that weren't being caught by CI --- examples/12_blufi/components/blufi/blufi.c | 1 + examples/12_blufi/main/demo_main.c | 14 +++++++------- .../{14_ethernet => 17_ethernet}/main/tlk110_phy.h | 0 3 files changed, 8 insertions(+), 7 deletions(-) rename examples/{14_ethernet => 17_ethernet}/main/tlk110_phy.h (100%) diff --git a/examples/12_blufi/components/blufi/blufi.c b/examples/12_blufi/components/blufi/blufi.c index 5a1c543b50..ce473667a4 100644 --- a/examples/12_blufi/components/blufi/blufi.c +++ b/examples/12_blufi/components/blufi/blufi.c @@ -30,6 +30,7 @@ #include "bt_trace.h" #include "bt_types.h" +#include "bta_api.h" #include "blufi.h" diff --git a/examples/12_blufi/main/demo_main.c b/examples/12_blufi/main/demo_main.c index f1c3807741..90992313c2 100644 --- a/examples/12_blufi/main/demo_main.c +++ b/examples/12_blufi/main/demo_main.c @@ -46,15 +46,15 @@ const int CONNECTED_BIT = BIT0; static wifi_config_t sta_config; static char tmp_ssid[33]; -static char tmp_passwd[33]; +static char tmp_passwd[65]; static bool confirm = false; void wifi_set_blue_config(char *ssid, char *passwd) { - memset(tmp_ssid, 0, 33); - memset(tmp_passwd, 0, 33); - strcpy(tmp_ssid, ssid); - strcpy(tmp_passwd, passwd); + memset(tmp_ssid, 0, sizeof(tmp_ssid)); + memset(tmp_passwd, 0, sizeof(tmp_passwd)); + strlcpy(tmp_ssid, ssid, sizeof(tmp_ssid)); + strlcpy(tmp_passwd, passwd, sizeof(tmp_passwd)); confirm = true; LOG_DEBUG("confirm true\n"); } @@ -105,8 +105,8 @@ void wifiTestTask(void *pvParameters) if (confirm) { confirm = false; - strcpy(sta_config.sta.ssid, tmp_ssid); - strcpy(sta_config.sta.password, tmp_passwd); + memcpy(sta_config.sta.ssid, tmp_ssid, sizeof(sta_config.sta.ssid)); + memcpy(sta_config.sta.password, tmp_passwd, sizeof(sta_config.sta.password)); sta_config.sta.bssid_set = 0; ret = esp_wifi_disconnect(); diff --git a/examples/14_ethernet/main/tlk110_phy.h b/examples/17_ethernet/main/tlk110_phy.h similarity index 100% rename from examples/14_ethernet/main/tlk110_phy.h rename to examples/17_ethernet/main/tlk110_phy.h From 6b87419d420427ef4cec012a784b046731b27714 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Dec 2016 16:13:07 +1100 Subject: [PATCH 037/167] CI build_examples: Don't stop on first failed example, print failure summary --- make/build_examples.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/make/build_examples.sh b/make/build_examples.sh index dc10d95ab2..ab59208795 100755 --- a/make/build_examples.sh +++ b/make/build_examples.sh @@ -11,11 +11,10 @@ EXAMPLE_NUM=1 RESULT=0 +FAILED_EXAMPLES="" RESULT_WARNINGS=22 # magic number result code for "warnings found" -set -e - for example in ${IDF_PATH}/examples/*; do [ -f ${example}/Makefile ] || continue echo "Building ${example} as ${EXAMPLE_NUM}..." @@ -34,13 +33,13 @@ for example in ${IDF_PATH}/examples/*; do set -e make clean defconfig make $* all 2>&1 | tee $BUILDLOG - ) || { RESULT=$?; make V=1; } # only build verbose if there's an error + ) || { RESULT=$?; FAILED_EXAMPLES+=" ${example}"; make V=1; } # only build verbose if there's an error popd EXAMPLE_NUM=$(( $EXAMPLE_NUM + 1 )) - if [ $RESULT -eq 0 ] && grep -q ": warning:" $BUILDLOG; then - echo "Build will fail, due to warnings in this example" - RESULT=$RESULT_WARNINGS + if grep -q ": warning:" $BUILDLOG; then + [ $RESULT -eq 0 ] && RESULT=$RESULT_WARNINGS + FAILED_EXAMPLES+=" ${example} (warnings)" fi rm -f $BUILDLOG @@ -50,5 +49,7 @@ if [ $RESULT -eq $RESULT_WARNINGS ]; then echo "Build would have passed, except for warnings." fi +[ $RESULT -eq 0 ] || echo "Failed examples: $FAILED_EXAMPLES" + exit $RESULT From 2b41c1b8b26e144523812b84daf3e7706f9798e7 Mon Sep 17 00:00:00 2001 From: liuhan Date: Sat, 24 Dec 2016 12:58:37 +0800 Subject: [PATCH 038/167] components/coap: Add libcoap library as submodule --- .gitmodules | 3 +++ components/coap/libcoap | 1 + 2 files changed, 4 insertions(+) create mode 160000 components/coap/libcoap diff --git a/.gitmodules b/.gitmodules index c26f92e80c..9cba0ec5ef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "components/micro-ecc/micro-ecc"] path = components/micro-ecc/micro-ecc url = https://github.com/kmackay/micro-ecc.git +[submodule "components/coap/libcoap"] + path = components/coap/libcoap + url = https://github.com/obgm/libcoap.git diff --git a/components/coap/libcoap b/components/coap/libcoap new file mode 160000 index 0000000000..6468887a12 --- /dev/null +++ b/components/coap/libcoap @@ -0,0 +1 @@ +Subproject commit 6468887a12666f88b8704d797fc176cd4f40ee4c From d0b10ba2dd30ee5fbd3343778b8e77859f621aee Mon Sep 17 00:00:00 2001 From: liuhan Date: Sat, 24 Dec 2016 14:43:53 +0800 Subject: [PATCH 039/167] components/coap: Add libcoap port for ESP32 platform --- components/coap/Makefile.projbuild | 1 + components/coap/component.mk | 9 + components/coap/port/coap_io_socket.c | 468 ++++++++++++++++++ components/coap/port/include/coap/coap.h | 50 ++ components/coap/port/include/coap_config.h | 39 ++ .../coap/port/include/coap_config_posix.h | 41 ++ components/lwip/include/lwip/port/arpa/inet.h | 20 + .../lwip/include/lwip/port/netinet/in.h | 22 + components/nghttp/Makefile.projbuild | 3 - 9 files changed, 650 insertions(+), 3 deletions(-) create mode 100644 components/coap/Makefile.projbuild create mode 100644 components/coap/component.mk create mode 100644 components/coap/port/coap_io_socket.c create mode 100644 components/coap/port/include/coap/coap.h create mode 100644 components/coap/port/include/coap_config.h create mode 100644 components/coap/port/include/coap_config_posix.h create mode 100644 components/lwip/include/lwip/port/arpa/inet.h create mode 100644 components/lwip/include/lwip/port/netinet/in.h diff --git a/components/coap/Makefile.projbuild b/components/coap/Makefile.projbuild new file mode 100644 index 0000000000..e976ee32f6 --- /dev/null +++ b/components/coap/Makefile.projbuild @@ -0,0 +1 @@ +CFLAGS += -DWITH_POSIX diff --git a/components/coap/component.mk b/components/coap/component.mk new file mode 100644 index 0000000000..b5f6ec43c2 --- /dev/null +++ b/components/coap/component.mk @@ -0,0 +1,9 @@ +# +# Component Makefile +# + +COMPONENT_ADD_INCLUDEDIRS := port/include port/include/coap libcoap/include libcoap/include/coap + +COMPONENT_OBJS = libcoap/src/address.o libcoap/src/async.o libcoap/src/block.o libcoap/src/coap_time.o libcoap/src/debug.o libcoap/src/encode.o libcoap/src/hashkey.o libcoap/src/mem.o libcoap/src/net.o libcoap/src/option.o libcoap/src/pdu.o libcoap/src/resource.o libcoap/src/str.o libcoap/src/subscribe.o libcoap/src/uri.o port/coap_io_socket.o + +COMPONENT_SRCDIRS := libcoap/src libcoap port diff --git a/components/coap/port/coap_io_socket.c b/components/coap/port/coap_io_socket.c new file mode 100644 index 0000000000..eec8cc1319 --- /dev/null +++ b/components/coap/port/coap_io_socket.c @@ -0,0 +1,468 @@ +/* + * Network function implementation with socket for ESP32 platform. + * + * Uses libcoap software implementation for failover when concurrent + * network operations are in use. + * + * coap_io.h -- Default network I/O functions for libcoap + * + * Copyright (C) 2012,2014 Olaf Bergmann + * + * Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +#include "coap_config.h" + +#ifdef HAVE_STDIO_H +# include +#endif + +#ifdef HAVE_SYS_SELECT_H +# include +#endif +#ifdef HAVE_SYS_SOCKET_H +# include +#endif +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_SYS_UIO_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#include + +#ifdef WITH_CONTIKI +# include "uip.h" +#endif + +#include "pdu.h" +#include "debug.h" +#include "mem.h" +#include "coap_io.h" + +#ifdef WITH_POSIX +/* define generic PKTINFO for IPv4 */ +#if defined(IP_PKTINFO) +# define GEN_IP_PKTINFO IP_PKTINFO +#elif defined(IP_RECVDSTADDR) +# define GEN_IP_PKTINFO IP_RECVDSTADDR +#else +# error "Need IP_PKTINFO or IP_RECVDSTADDR to request ancillary data from OS." +#endif /* IP_PKTINFO */ + +/* define generic KTINFO for IPv6 */ +#ifdef IPV6_RECVPKTINFO +# define GEN_IPV6_PKTINFO IPV6_RECVPKTINFO +#elif defined(IPV6_PKTINFO) +# define GEN_IPV6_PKTINFO IPV6_PKTINFO +#else +# error "Need IPV6_PKTINFO or IPV6_RECVPKTINFO to request ancillary data from OS." +#endif /* IPV6_RECVPKTINFO */ + +struct coap_packet_t { + coap_if_handle_t hnd; /**< the interface handle */ + coap_address_t src; /**< the packet's source address */ + coap_address_t dst; /**< the packet's destination address */ + const coap_endpoint_t *interface; + + int ifindex; + void *session; /**< opaque session data */ + + size_t length; /**< length of payload */ + unsigned char payload[]; /**< payload */ +}; +#endif + +#ifdef CUSTOM_COAP_NETWORK_ENDPOINT + +#ifdef WITH_CONTIKI +static int ep_initialized = 0; + +static inline struct coap_endpoint_t * +coap_malloc_contiki_endpoint() { + static struct coap_endpoint_t ep; + + if (ep_initialized) { + return NULL; + } else { + ep_initialized = 1; + return &ep; + } +} + +static inline void +coap_free_contiki_endpoint(struct coap_endpoint_t *ep) { + ep_initialized = 0; +} + +coap_endpoint_t * +coap_new_endpoint(const coap_address_t *addr, int flags) { + struct coap_endpoint_t *ep = coap_malloc_contiki_endpoint(); + + if (ep) { + memset(ep, 0, sizeof(struct coap_endpoint_t)); + ep->handle.conn = udp_new(NULL, 0, NULL); + + if (!ep->handle.conn) { + coap_free_endpoint(ep); + return NULL; + } + + coap_address_init(&ep->addr); + uip_ipaddr_copy(&ep->addr.addr, &addr->addr); + ep->addr.port = addr->port; + udp_bind((struct uip_udp_conn *)ep->handle.conn, addr->port); + } + return ep; +} + +void +coap_free_endpoint(coap_endpoint_t *ep) { + if (ep) { + if (ep->handle.conn) { + uip_udp_remove((struct uip_udp_conn *)ep->handle.conn); + } + coap_free_contiki_endpoint(ep); + } +} + +#else /* WITH_CONTIKI */ +static inline struct coap_endpoint_t * +coap_malloc_posix_endpoint(void) { + return (struct coap_endpoint_t *)coap_malloc(sizeof(struct coap_endpoint_t)); +} + +static inline void +coap_free_posix_endpoint(struct coap_endpoint_t *ep) { + coap_free(ep); +} + +coap_endpoint_t * +coap_new_endpoint(const coap_address_t *addr, int flags) { + int sockfd = socket(addr->addr.sa.sa_family, SOCK_DGRAM, 0); + int on = 1; + struct coap_endpoint_t *ep; + + if (sockfd < 0) { + coap_log(LOG_WARNING, "coap_new_endpoint: socket"); + return NULL; + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + coap_log(LOG_WARNING, "coap_new_endpoint: setsockopt SO_REUSEADDR"); + + if (bind(sockfd, &addr->addr.sa, addr->size) < 0) { + coap_log(LOG_WARNING, "coap_new_endpoint: bind"); + close (sockfd); + return NULL; + } + + ep = coap_malloc_posix_endpoint(); + if (!ep) { + coap_log(LOG_WARNING, "coap_new_endpoint: malloc"); + close(sockfd); + return NULL; + } + + memset(ep, 0, sizeof(struct coap_endpoint_t)); + ep->handle.fd = sockfd; + ep->flags = flags; + + ep->addr.size = addr->size; + if (getsockname(sockfd, &ep->addr.addr.sa, &ep->addr.size) < 0) { + coap_log(LOG_WARNING, "coap_new_endpoint: cannot determine local address"); + close (sockfd); + return NULL; + } + +#ifndef NDEBUG + if (LOG_DEBUG <= coap_get_log_level()) { +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 40 +#endif + unsigned char addr_str[INET6_ADDRSTRLEN+8]; + + if (coap_print_addr(&ep->addr, addr_str, INET6_ADDRSTRLEN+8)) { + debug("created %sendpoint %s\n", + ep->flags & COAP_ENDPOINT_DTLS ? "DTLS " : "", + addr_str); + } + } +#endif /* NDEBUG */ + + return (coap_endpoint_t *)ep; +} + +void +coap_free_endpoint(coap_endpoint_t *ep) { + if(ep) { + if (ep->handle.fd >= 0) + close(ep->handle.fd); + coap_free_posix_endpoint((struct coap_endpoint_t *)ep); + } +} + +#endif /* WITH_CONTIKI */ +#endif /* CUSTOM_COAP_NETWORK_ENDPOINT */ + +#ifdef CUSTOM_COAP_NETWORK_SEND + +#if defined(WITH_POSIX) != defined(HAVE_NETINET_IN_H) +/* define struct in6_pktinfo and struct in_pktinfo if not available + FIXME: check with configure +*/ +struct in6_pktinfo { + struct in6_addr ipi6_addr; /* src/dst IPv6 address */ + unsigned int ipi6_ifindex; /* send/recv interface index */ +}; + +struct in_pktinfo { + int ipi_ifindex; + struct in_addr ipi_spec_dst; + struct in_addr ipi_addr; +}; +#endif + +#if defined(WITH_POSIX) && !defined(SOL_IP) +/* Solaris expects level IPPROTO_IP for ancillary data. */ +#define SOL_IP IPPROTO_IP +#endif + +#ifdef __GNUC__ +#define UNUSED_PARAM __attribute__ ((unused)) +#else /* not a GCC */ +#define UNUSED_PARAM +#endif /* GCC */ + +ssize_t +coap_network_send(struct coap_context_t *context UNUSED_PARAM, + const coap_endpoint_t *local_interface, + const coap_address_t *dst, + unsigned char *data, + size_t datalen) { + + struct coap_endpoint_t *ep = + (struct coap_endpoint_t *)local_interface; + +#ifndef WITH_CONTIKI + return sendto(ep->handle.fd, data, datalen, 0, (struct sockaddr*)&dst->addr.sa, sizeof(struct sockaddr)); +#else /* WITH_CONTIKI */ + /* FIXME: untested */ + /* FIXME: is there a way to check if send was successful? */ + uip_udp_packet_sendto((struct uip_udp_conn *)ep->handle.conn, data, datalen, + &dst->addr, dst->port); + return datalen; +#endif /* WITH_CONTIKI */ +} + +#endif /* CUSTOM_COAP_NETWORK_SEND */ + +#ifdef CUSTOM_COAP_NETWORK_READ + +#define SIN6(A) ((struct sockaddr_in6 *)(A)) + +#ifdef WITH_POSIX +static coap_packet_t * +coap_malloc_packet(void) { + coap_packet_t *packet; + const size_t need = sizeof(coap_packet_t) + COAP_MAX_PDU_SIZE; + + packet = (coap_packet_t *)coap_malloc(need); + if (packet) { + memset(packet, 0, need); + } + return packet; +} + +void +coap_free_packet(coap_packet_t *packet) { + coap_free(packet); +} +#endif /* WITH_POSIX */ +#ifdef WITH_CONTIKI +static inline coap_packet_t * +coap_malloc_packet(void) { + return (coap_packet_t *)coap_malloc_type(COAP_PACKET, 0); +} + +void +coap_free_packet(coap_packet_t *packet) { + coap_free_type(COAP_PACKET, packet); +} +#endif /* WITH_CONTIKI */ + +static inline size_t +coap_get_max_packetlength(const coap_packet_t *packet UNUSED_PARAM) { + return COAP_MAX_PDU_SIZE; +} + +void +coap_packet_populate_endpoint(coap_packet_t *packet, coap_endpoint_t *target) +{ + target->handle = packet->interface->handle; + memcpy(&target->addr, &packet->dst, sizeof(target->addr)); + target->ifindex = packet->ifindex; + target->flags = 0; /* FIXME */ +} +void +coap_packet_copy_source(coap_packet_t *packet, coap_address_t *target) +{ + memcpy(target, &packet->src, sizeof(coap_address_t)); +} +void +coap_packet_get_memmapped(coap_packet_t *packet, unsigned char **address, size_t *length) +{ + *address = packet->payload; + *length = packet->length; +} + +/** + * Checks if a message with destination address @p dst matches the + * local interface with address @p local. This function returns @c 1 + * if @p dst is a valid match, and @c 0 otherwise. + */ +static inline int +is_local_if(const coap_address_t *local, const coap_address_t *dst) { + return coap_address_isany(local) || coap_address_equals(dst, local) || + coap_is_mcast(dst); +} + +ssize_t +coap_network_read(coap_endpoint_t *ep, coap_packet_t **packet) { + ssize_t len = -1; + +#ifdef WITH_POSIX + #define SOC_APPDATA_LEN 1460 + char *soc_appdata = NULL; + struct sockaddr_in soc_srcipaddr; + socklen_t soc_srcsize = sizeof(struct sockaddr_in); +#endif /* WITH_POSIX */ + + assert(ep); + assert(packet); + + *packet = coap_malloc_packet(); + + if (!*packet) { + warn("coap_network_read: insufficient memory, drop packet\n"); + return -1; + } + + coap_address_init(&(*packet)->dst); /* the local interface address */ + coap_address_init(&(*packet)->src); /* the remote peer */ + +#ifdef WITH_POSIX + soc_appdata = coap_malloc(SOC_APPDATA_LEN); + if (soc_appdata){ + len = recvfrom(ep->handle.fd, soc_appdata, SOC_APPDATA_LEN, 0, (struct sockaddr *)&soc_srcipaddr, (socklen_t *)&soc_srcsize); + + if (len < 0){ + coap_log(LOG_WARNING, "coap_network_read: %s\n", strerror(errno)); + goto error; + } else { + /* use getsockname() to get the local port */ + (*packet)->dst.size = sizeof((*packet)->dst.addr); + if (getsockname(ep->handle.fd, &(*packet)->dst.addr.sa, &(*packet)->dst.size) < 0) { + coap_log(LOG_DEBUG, "cannot determine local port\n"); + goto error; + } + + /* local interface for IPv4 */ + (*packet)->src.size = sizeof((*packet)->src.addr); + memcpy(&(*packet)->src.addr.sa, &soc_srcipaddr, (*packet)->src.size); + + if (len > coap_get_max_packetlength(*packet)) { + /* FIXME: we might want to send back a response */ + warn("discarded oversized packet\n"); + goto error; + } + + if (!is_local_if(&ep->addr, &(*packet)->dst)) { + coap_log(LOG_DEBUG, "packet received on wrong interface, dropped\n"); + printf("error 3\n"); + goto error; + } + + (*packet)->length = len; + + memcpy(&(*packet)->payload, soc_appdata, len); + } + + coap_free(soc_appdata); + soc_appdata = NULL; + } else { + goto error; + } +#endif /* WITH_POSIX */ +#ifdef WITH_CONTIKI + /* FIXME: untested, make this work */ +#define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN]) +#define UIP_UDP_BUF ((struct uip_udp_hdr *)&uip_buf[UIP_LLIPH_LEN]) + + if(uip_newdata()) { + uip_ipaddr_copy(&(*packet)->src.addr, &UIP_IP_BUF->srcipaddr); + (*packet)->src.port = UIP_UDP_BUF->srcport; + uip_ipaddr_copy(&(*packet)->dst.addr, &UIP_IP_BUF->destipaddr); + (*packet)->dst.port = UIP_UDP_BUF->destport; + + if (!is_local_if(&ep->addr, &(*packet)->dst)) { + coap_log(LOG_DEBUG, "packet received on wrong interface, dropped\n"); + goto error; + } + + len = uip_datalen(); + + if (len > coap_get_max_packetlength(*packet)) { + /* FIXME: we might want to send back a response */ + warn("discarded oversized packet\n"); + return -1; + } + + ((char *)uip_appdata)[len] = 0; +#ifndef NDEBUG + if (LOG_DEBUG <= coap_get_log_level()) { +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 40 +#endif + unsigned char addr_str[INET6_ADDRSTRLEN+8]; + + if (coap_print_addr(&(*packet)->src, addr_str, INET6_ADDRSTRLEN+8)) { + debug("received %zd bytes from %s\n", len, addr_str); + } + } +#endif /* NDEBUG */ + + (*packet)->length = len; + memcpy(&(*packet)->payload, uip_appdata, len); + } + +#undef UIP_IP_BUF +#undef UIP_UDP_BUF +#endif /* WITH_CONTIKI */ +#ifdef WITH_LWIP +#error "coap_network_read() not implemented on this platform" +#endif + + (*packet)->interface = ep; + + return len; + error: +#ifdef WITH_POSIX + if (soc_appdata) + coap_free(soc_appdata); + soc_appdata = NULL; +#endif + coap_free_packet(*packet); + *packet = NULL; + return -1; +} + +#undef SIN6 + +#endif /* CUSTOM_COAP_NETWORK_READ */ diff --git a/components/coap/port/include/coap/coap.h b/components/coap/port/include/coap/coap.h new file mode 100644 index 0000000000..cbdc9dfc81 --- /dev/null +++ b/components/coap/port/include/coap/coap.h @@ -0,0 +1,50 @@ +/* Modify head file implementation for ESP32 platform. + * + * Uses libcoap software implementation for failover when concurrent + * define operations are in use. + * + * coap.h -- main header file for CoAP stack of libcoap + * + * Copyright (C) 2010-2012,2015-2016 Olaf Bergmann + * 2015 Carsten Schoenert + * + * Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#ifndef _COAP_H_ +#define _COAP_H_ + +#include "libcoap.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "address.h" +#include "async.h" +#include "bits.h" +#include "block.h" +#include "coap_io.h" +#include "coap_time.h" +#include "debug.h" +#include "encode.h" +#include "mem.h" +#include "net.h" +#include "option.h" +#include "pdu.h" +#include "prng.h" +#include "resource.h" +#include "str.h" +#include "subscribe.h" +#include "uri.h" +#include "uthash.h" +#include "utlist.h" + +#ifdef __cplusplus +} +#endif + +#endif /* _COAP_H_ */ diff --git a/components/coap/port/include/coap_config.h b/components/coap/port/include/coap_config.h new file mode 100644 index 0000000000..db314f2de9 --- /dev/null +++ b/components/coap/port/include/coap_config.h @@ -0,0 +1,39 @@ +/* + * libcoap configure implementation for ESP32 platform. + * + * Uses libcoap software implementation for failover when concurrent + * configure operations are in use. + * + * coap.h -- main header file for CoAP stack of libcoap + * + * Copyright (C) 2010-2012,2015-2016 Olaf Bergmann + * 2015 Carsten Schoenert + * + * Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#ifdef WITH_POSIX +#include "coap_config_posix.h" +#endif + +#define HAVE_STDIO_H +#define HAVE_ASSERT_H + +#define PACKAGE_STRING PACKAGE_NAME PACKAGE_VERSION + +/* it's just provided by libc. i hope we don't get too many of those, as + * actually we'd need autotools again to find out what environment we're + * building in */ +#define HAVE_STRNLEN 1 + +#define HAVE_LIMITS_H + +#define COAP_RESOURCES_NOHASH + +#endif /* _CONFIG_H_ */ diff --git a/components/coap/port/include/coap_config_posix.h b/components/coap/port/include/coap_config_posix.h new file mode 100644 index 0000000000..a77e97f074 --- /dev/null +++ b/components/coap/port/include/coap_config_posix.h @@ -0,0 +1,41 @@ +/* + * libcoap configure implementation for ESP32 platform. + * + * Uses libcoap software implementation for failover when concurrent + * configure operations are in use. + * + * coap.h -- main header file for CoAP stack of libcoap + * + * Copyright (C) 2010-2012,2015-2016 Olaf Bergmann + * 2015 Carsten Schoenert + * + * Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#ifndef COAP_CONFIG_POSIX_H_ +#define COAP_CONFIG_POSIX_H_ + +#ifdef WITH_POSIX + +#include + +#define HAVE_SYS_SOCKET_H +#define HAVE_MALLOC +#define HAVE_ARPA_INET_H + +#define IP_PKTINFO IP_MULTICAST_IF +#define IPV6_PKTINFO IPV6_V6ONLY + +#define PACKAGE_NAME "libcoap-posix" +#define PACKAGE_VERSION "?" + +#define CUSTOM_COAP_NETWORK_ENDPOINT +#define CUSTOM_COAP_NETWORK_SEND +#define CUSTOM_COAP_NETWORK_READ + +#endif + +#endif /* COAP_CONFIG_POSIX_H_ */ diff --git a/components/lwip/include/lwip/port/arpa/inet.h b/components/lwip/include/lwip/port/arpa/inet.h new file mode 100644 index 0000000000..94c6c17ed5 --- /dev/null +++ b/components/lwip/include/lwip/port/arpa/inet.h @@ -0,0 +1,20 @@ +// 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 INET_H_ +#define INET_H_ + +#include "lwip/inet.h" + +#endif /* INET_H_ */ diff --git a/components/lwip/include/lwip/port/netinet/in.h b/components/lwip/include/lwip/port/netinet/in.h new file mode 100644 index 0000000000..7eaec63342 --- /dev/null +++ b/components/lwip/include/lwip/port/netinet/in.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 IN_H_ +#define IN_H_ + +#include "lwip/inet.h" + +#define IN6_IS_ADDR_MULTICAST(a) IN_MULTICAST(a) + +#endif /* IN_H_ */ diff --git a/components/nghttp/Makefile.projbuild b/components/nghttp/Makefile.projbuild index a93010ade9..5c6ce7fc9b 100644 --- a/components/nghttp/Makefile.projbuild +++ b/components/nghttp/Makefile.projbuild @@ -1,4 +1 @@ -# Anyone compiling mbedTLS code needs the name of the -# alternative config file - CFLAGS += -DHAVE_CONFIG_H From 9c7cc86793e6c710554942b61556b7382100fb9a Mon Sep 17 00:00:00 2001 From: Wangjialin Date: Fri, 16 Dec 2016 23:41:04 +0800 Subject: [PATCH 040/167] 1. modify i2c_set_pin function 2. update example comments and other minor changes 3. rename API: i2c_cmd_link_create/i2c_cmd_link_delete (+4 squashed commits) Squashed commits: [2e0ac3e] 1. coding style: add one space after condition key words. 2. modify i2c.h, use gpio_num_t instead of int, improve comments of return values 3. add i2c index in index.rst 4. add readme for i2c example [4991d92] update i2c.doc [88b672e] driver: i2c 1. add mux and spin lock to run in a thread-safe way. 2. modify example code [4eb15fe] driver: i2c code 1. add i2c master code 2. add i2c slave code 3. add i2c example code 4. add DRAM_ATTR for I2C array --- components/driver/i2c.c | 1037 +++++++++++++++++++++ components/driver/include/driver/i2c.h | 514 ++++++++++ components/esp32/include/soc/i2c_reg.h | 2 + components/esp32/include/soc/i2c_struct.h | 6 +- docs/api/i2c.rst | 82 ++ docs/index.rst | 1 + examples/18_i2c/Makefile | 9 + examples/18_i2c/README.md | 29 + examples/18_i2c/main/component.mk | 3 + examples/18_i2c/main/i2c_test.c | 303 ++++++ 10 files changed, 1983 insertions(+), 3 deletions(-) create mode 100644 components/driver/i2c.c create mode 100644 components/driver/include/driver/i2c.h create mode 100644 docs/api/i2c.rst create mode 100644 examples/18_i2c/Makefile create mode 100644 examples/18_i2c/README.md create mode 100644 examples/18_i2c/main/component.mk create mode 100644 examples/18_i2c/main/i2c_test.c diff --git a/components/driver/i2c.c b/components/driver/i2c.c new file mode 100644 index 0000000000..311f01516c --- /dev/null +++ b/components/driver/i2c.c @@ -0,0 +1,1037 @@ +// 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 +#include +#include "esp_types.h" +#include "esp_attr.h" +#include "esp_intr.h" +#include "esp_log.h" +#include "malloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/xtensa_api.h" +#include "freertos/task.h" +#include "freertos/ringbuf.h" +#include "soc/dport_reg.h" +#include "soc/i2c_struct.h" +#include "soc/i2c_reg.h" +#include "driver/i2c.h" +#include "driver/gpio.h" +#include "driver/periph_ctrl.h" + +static const char* I2C_TAG = "i2c"; +#define I2C_CHECK(a, str, ret) if(!(a)) { \ + ESP_LOGE(I2C_TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + return (ret); \ + } + +static portMUX_TYPE i2c_spinlock[I2C_NUM_MAX] = {portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED}; +/* DRAM_ATTR is required to avoid I2C array placed in flash, due to accessed from ISR */ +static DRAM_ATTR i2c_dev_t* const I2C[I2C_NUM_MAX] = { &I2C0, &I2C1 }; + + +#define I2C_ENTER_CRITICAL_ISR(mux) portENTER_CRITICAL_ISR(mux) +#define I2C_EXIT_CRITICAL_ISR(mux) portEXIT_CRITICAL_ISR(mux) +#define I2C_ENTER_CRITICAL(mux) portENTER_CRITICAL(mux) +#define I2C_EXIT_CRITICAL(mux) portEXIT_CRITICAL(mux) + +#define I2C_DRIVER_ERR_STR "i2c driver install error" +#define I2C_DRIVER_MALLOC_ERR_STR "i2c driver malloc error" +#define I2C_NUM_ERROR_STR "i2c number error" +#define I2C_ADDR_ERROR_STR "i2c null address error" +#define I2C_DRIVER_NOT_INSTALL_ERR_STR "i2c driver not installed" +#define I2C_SLAVE_BUFFER_LEN_ERR_STR "i2c buffer size too short for slave mode" +#define I2C_EVT_QUEUE_ERR_STR "i2c evt queue error" +#define I2C_SEM_ERR_STR "i2c semaphore error" +#define I2C_BUF_ERR_STR "i2c ringbuffer error" +#define I2C_MASTER_MODE_ERR_STR "Only allowed in master mode" +#define I2C_MODE_SLAVE_ERR_STR "Only allowed in slave mode" +#define I2C_CMD_MALLOC_ERR_STR "i2c command link malloc error" +#define I2C_TRANS_MODE_ERR_STR "i2c trans mode error" +#define I2C_MODE_ERR_STR "i2c mode error" +#define I2C_SDA_IO_ERR_STR "sda gpio number error" +#define I2C_SCL_IO_ERR_STR "scl gpio number error" +#define I2C_CMD_LINK_INIT_ERR_STR "i2c command link error" +#define I2C_GPIO_PULLUP_ERR_STR "this i2c pin do not support internal pull-up" +#define I2C_FIFO_FULL_THRESH_VAL (28) +#define I2C_FIFO_EMPTY_THRESH_VAL (5) + +typedef struct { + uint8_t byte_num; /*!< cmd byte number */ + uint8_t ack_en; /*!< ack check enable */ + uint8_t ack_exp; /*!< expected ack level to get */ + uint8_t ack_val; /*!< ack value to send */ + uint8_t* data; /*!< data address */ + uint8_t byte_cmd; /*!< to save cmd for one byte command mode */ + i2c_opmode_t op_code; /*!< haredware cmd type */ +}i2c_cmd_t; + +typedef struct i2c_cmd_link{ + i2c_cmd_t cmd; /*!< command in current cmd link */ + struct i2c_cmd_link *next; /*!< next cmd link */ +} i2c_cmd_link_t; + +typedef struct { + i2c_cmd_link_t* head; /*!< head of the command link */ + i2c_cmd_link_t* cur; /*!< last node of the command link */ + i2c_cmd_link_t* free; /*!< the first node to free of the command link */ +} i2c_cmd_desc_t; + +typedef enum { + I2C_STATUS_READ, /*!< read status for current master command */ + I2C_STATUS_WRITE, /*!< write status for current master command */ + I2C_STATUS_IDLE, /*!< idle status for current master command */ + I2C_STATUS_ACK_ERROR, /*!< ack error status for current master command */ + I2C_STATUS_DONE, /*!< I2C command done */ +} i2c_status_t; + +typedef struct { + int i2c_num; /*!< I2C port number */ + int mode; /*!< I2C mode, master or slave */ + intr_handle_t intr_handle; /*!< I2C interrupt handle*/ + + int cmd_idx; /*!< record current command index, for master mode */ + int status; /*!< record current command status, for master mode */ + int rx_cnt; /*!< record current read index, for master mode */ + uint8_t data_buf[I2C_FIFO_LEN]; /*!< a buffer to store i2c fifo data */ + i2c_cmd_desc_t cmd_link; /*!< I2C command link */ + xSemaphoreHandle cmd_sem; /*!< semaphore to sync command status */ + xSemaphoreHandle cmd_mux; /*!< semaphore to lock command process */ + size_t tx_fifo_remain; /*!< tx fifo remain length, for master mode */ + size_t rx_fifo_remain; /*!< rx fifo remain length, for master mode */ + + xSemaphoreHandle slv_rx_mux; /*!< slave rx buffer mux */ + xSemaphoreHandle slv_tx_mux; /*!< slave tx buffer mux */ + size_t rx_buf_length; /*!< rx buffer length */ + RingbufHandle_t rx_ring_buf; /*!< rx ringbuffer handler of slave mode */ + size_t tx_buf_length; /*!< tx buffer length */ + RingbufHandle_t tx_ring_buf; /*!< tx ringbuffer handler of slave mode */ +} i2c_obj_t; + +static i2c_obj_t *p_i2c_obj[I2C_NUM_MAX] = {0}; +static void i2c_isr_handler_default(void* arg); +static void IRAM_ATTR i2c_master_cmd_begin_static(i2c_port_t i2c_num); + +/* + For i2c master mode, we don't need to use a buffer for the data, the APIs will execute the master commands +and return after all of the commands have been sent out or when error occurs. So when we send master commands, +we should free or modify the source data only after the i2c_master_cmd_begin function returns. + For i2c slave mode, we need a data buffer to stash the sending and receiving data, because the hardware fifo +has only 32 bytes. +*/ + +esp_err_t i2c_driver_install(i2c_port_t i2c_num, i2c_mode_t mode, size_t slv_rx_buf_len, size_t slv_tx_buf_len, + int intr_alloc_flags) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(mode == I2C_MODE_MASTER || ( slv_rx_buf_len > 100 || slv_tx_buf_len > 100 ), I2C_SLAVE_BUFFER_LEN_ERR_STR, + ESP_ERR_INVALID_ARG); + uint32_t intr_mask = 0; + if (p_i2c_obj[i2c_num] == NULL) { + p_i2c_obj[i2c_num] = (i2c_obj_t*) calloc(1, sizeof(i2c_obj_t)); + if (p_i2c_obj[i2c_num] == NULL) { + ESP_LOGE(I2C_TAG, I2C_DRIVER_MALLOC_ERR_STR); + return ESP_FAIL; + } + i2c_obj_t* p_i2c = p_i2c_obj[i2c_num]; + p_i2c->i2c_num = i2c_num; + p_i2c->mode = mode; + p_i2c->cmd_idx = 0; + p_i2c->rx_cnt = 0; + p_i2c->status = I2C_STATUS_IDLE; + + p_i2c->rx_fifo_remain = I2C_FIFO_LEN; + p_i2c->tx_fifo_remain = I2C_FIFO_LEN; + + if (mode == I2C_MODE_SLAVE) { + //we only use ringbuffer for slave mode. + if (slv_rx_buf_len > 0) { + p_i2c->rx_ring_buf = xRingbufferCreate(slv_rx_buf_len, RINGBUF_TYPE_BYTEBUF); + if (p_i2c->rx_ring_buf == NULL) { + ESP_LOGE(I2C_TAG, I2C_BUF_ERR_STR); + goto err; + } + p_i2c->rx_buf_length = slv_rx_buf_len; + } else { + p_i2c->tx_ring_buf = NULL; + p_i2c->rx_buf_length = 0; + } + if (slv_tx_buf_len > 0) { + p_i2c->tx_ring_buf = xRingbufferCreate(slv_tx_buf_len, RINGBUF_TYPE_BYTEBUF); + if (p_i2c->tx_ring_buf == NULL) { + ESP_LOGE(I2C_TAG, I2C_BUF_ERR_STR); + goto err; + } + p_i2c->tx_buf_length = slv_tx_buf_len; + } else { + p_i2c->tx_ring_buf = NULL; + p_i2c->tx_buf_length = 0; + } + p_i2c->slv_rx_mux = xSemaphoreCreateMutex(); + p_i2c->slv_tx_mux = xSemaphoreCreateMutex(); + if (p_i2c->slv_rx_mux == NULL || p_i2c->slv_rx_mux == NULL) { + ESP_LOGE(I2C_TAG, I2C_SEM_ERR_STR); + goto err; + } + intr_mask |= ( I2C_RXFIFO_FULL_INT_ENA_M | I2C_TRANS_COMPLETE_INT_ENA_M ); + } else { + //semaphore to sync sending process, because we only have 32 bytes for hardware fifo. + p_i2c->cmd_sem = xSemaphoreCreateBinary(); + p_i2c->cmd_mux = xSemaphoreCreateMutex(); + if (p_i2c->cmd_sem == NULL || p_i2c->cmd_mux == NULL) { + ESP_LOGE(I2C_TAG, I2C_SEM_ERR_STR); + goto err; + } + //command link + p_i2c->cmd_link.cur = NULL; + p_i2c->cmd_link.head = NULL; + p_i2c->cmd_link.free = NULL; + + p_i2c->tx_ring_buf = NULL; + p_i2c->rx_buf_length = 0; + p_i2c->tx_ring_buf = NULL; + p_i2c->tx_buf_length = 0; + } + } else { + ESP_LOGE(I2C_TAG, I2C_DRIVER_ERR_STR); + return ESP_FAIL; + } + //hook isr handler + i2c_isr_register(i2c_num, i2c_isr_handler_default, p_i2c_obj[i2c_num], intr_alloc_flags, &p_i2c_obj[i2c_num]->intr_handle); + intr_mask |= ( I2C_TRANS_COMPLETE_INT_ENA_M | + I2C_TRANS_START_INT_ENA_M | + I2C_ARBITRATION_LOST_INT_ENA_M | + I2C_ACK_ERR_INT_ENA_M | + I2C_RXFIFO_OVF_INT_ENA_M | + I2C_SLAVE_TRAN_COMP_INT_ENA_M ); + SET_PERI_REG_MASK(I2C_INT_ENA_REG(i2c_num), intr_mask); + return ESP_OK; + + err: + //Some error has happened. Free/destroy all allocated things and return ESP_FAIL. + if (p_i2c_obj[i2c_num]) { + if (p_i2c_obj[i2c_num]->rx_ring_buf) { + vRingbufferDelete(p_i2c_obj[i2c_num]->rx_ring_buf); + p_i2c_obj[i2c_num]->rx_ring_buf = NULL; + p_i2c_obj[i2c_num]->rx_buf_length = 0; + } + if (p_i2c_obj[i2c_num]->tx_ring_buf) { + vRingbufferDelete(p_i2c_obj[i2c_num]->tx_ring_buf); + p_i2c_obj[i2c_num]->tx_ring_buf = NULL; + p_i2c_obj[i2c_num]->tx_buf_length = 0; + } + if (p_i2c_obj[i2c_num]->cmd_sem) { + vSemaphoreDelete(p_i2c_obj[i2c_num]->cmd_sem); + } + if (p_i2c_obj[i2c_num]->cmd_mux) { + vSemaphoreDelete(p_i2c_obj[i2c_num]->cmd_mux); + } + if (p_i2c_obj[i2c_num]->slv_rx_mux) { + vSemaphoreDelete(p_i2c_obj[i2c_num]->slv_rx_mux); + } + if (p_i2c_obj[i2c_num]->slv_tx_mux) { + vSemaphoreDelete(p_i2c_obj[i2c_num]->slv_tx_mux); + } + } + free(p_i2c_obj[i2c_num]); + return ESP_FAIL; +} + +esp_err_t i2c_driver_delete(i2c_port_t i2c_num) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(p_i2c_obj[i2c_num] != NULL, I2C_DRIVER_ERR_STR, ESP_FAIL); + + i2c_obj_t* p_i2c = p_i2c_obj[i2c_num]; + if (p_i2c->cmd_mux) { + xSemaphoreTake(p_i2c->cmd_mux, portMAX_DELAY); + vSemaphoreDelete(p_i2c->cmd_mux); + } + if (p_i2c->cmd_sem) { + vSemaphoreDelete(p_i2c->cmd_sem); + } + if (p_i2c->slv_rx_mux) { + vSemaphoreDelete(p_i2c->slv_rx_mux); + } + if (p_i2c->slv_tx_mux) { + vSemaphoreDelete(p_i2c->slv_tx_mux); + } + + if (p_i2c->rx_ring_buf) { + vRingbufferDelete(p_i2c->rx_ring_buf); + p_i2c->rx_ring_buf = NULL; + p_i2c->rx_buf_length = 0; + } + if (p_i2c->tx_ring_buf) { + vRingbufferDelete(p_i2c->tx_ring_buf); + p_i2c->tx_ring_buf = NULL; + p_i2c->tx_buf_length = 0; + } + uint32_t intr_mask = I2C_MASTER_TRAN_COMP_INT_ENA_M | + I2C_TIME_OUT_INT_ENA_M | + I2C_TRANS_COMPLETE_INT_ENA_M | + I2C_TRANS_START_INT_ENA_M | + I2C_TX_SEND_EMPTY_INT_ENA_M | + I2C_ARBITRATION_LOST_INT_ENA_M | + I2C_ACK_ERR_INT_ENA_M | + I2C_RXFIFO_OVF_INT_ENA_M | + I2C_RX_REC_FULL_INT_ENA_M | + I2C_SLAVE_TRAN_COMP_INT_ENA_M; + CLEAR_PERI_REG_MASK(I2C_INT_ENA_REG(i2c_num), intr_mask); + esp_intr_free(p_i2c->intr_handle); + p_i2c->intr_handle = NULL; + free(p_i2c_obj[i2c_num]); + p_i2c_obj[i2c_num] = NULL; + return ESP_OK; +} + +esp_err_t i2c_reset_tx_fifo(i2c_port_t i2c_num) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + I2C[i2c_num]->fifo_conf.tx_fifo_rst = 1; + I2C[i2c_num]->fifo_conf.tx_fifo_rst = 0; + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +esp_err_t i2c_reset_rx_fifo(i2c_port_t i2c_num) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + I2C[i2c_num]->fifo_conf.rx_fifo_rst = 1; + I2C[i2c_num]->fifo_conf.rx_fifo_rst = 0; + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +static void i2c_isr_handler_default(void* arg) +{ + i2c_obj_t* p_i2c = (i2c_obj_t*) arg; + int i2c_num = p_i2c->i2c_num; + uint32_t status = I2C[i2c_num]->int_status.val; + int idx = 0; + portBASE_TYPE HPTaskAwoken = pdFALSE; + while (status != 0) { + status = I2C[i2c_num]->int_status.val; + if (status & I2C_TX_SEND_EMPTY_INT_ST_M) { + I2C[i2c_num]->int_clr.tx_send_empty = 1; + } else if (status & I2C_RX_REC_FULL_INT_ST_M) { + I2C[i2c_num]->int_clr.rx_rec_full = 1; + } else if (status & I2C_ACK_ERR_INT_ST_M) { + I2C[i2c_num]->int_clr.ack_err = 1; + if (p_i2c->mode == I2C_MODE_MASTER) { + p_i2c_obj[i2c_num]->status = I2C_STATUS_ACK_ERROR; + I2C[i2c_num]->int_clr.ack_err = 1; + //get error ack value from slave device, stop the commands + i2c_master_cmd_begin_static(i2c_num); + } + } else if (status & I2C_TRANS_START_INT_ST_M) { + I2C[i2c_num]->int_clr.trans_start = 1; + } else if (status & I2C_TIME_OUT_INT_ST_M) { + I2C[i2c_num]->int_clr.time_out = 1; + } else if (status & I2C_TRANS_COMPLETE_INT_ST_M) { + I2C[i2c_num]->int_clr.trans_complete = 1; + if (p_i2c->mode == I2C_MODE_SLAVE) { + int rx_fifo_cnt = I2C[i2c_num]->status_reg.rx_fifo_cnt; + for (idx = 0; idx < rx_fifo_cnt; idx++) { + p_i2c->data_buf[idx] = I2C[i2c_num]->fifo_data.data; + } + xRingbufferSendFromISR(p_i2c->rx_ring_buf, p_i2c->data_buf, rx_fifo_cnt, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + I2C[i2c_num]->int_clr.rx_fifo_full = 1; + } else { + if (p_i2c->status != I2C_STATUS_ACK_ERROR) { + i2c_master_cmd_begin_static(i2c_num); + } + } + } else if (status & I2C_MASTER_TRAN_COMP_INT_ST_M) { + I2C[i2c_num]->int_clr.master_tran_comp = 1; + } else if (status & I2C_ARBITRATION_LOST_INT_ST_M) { + I2C[i2c_num]->int_clr.arbitration_lost = 1; + } else if (status & I2C_SLAVE_TRAN_COMP_INT_ST_M) { + I2C[i2c_num]->int_clr.slave_tran_comp = 1; + } else if (status & I2C_END_DETECT_INT_ST_M) { + I2C[i2c_num]->int_ena.end_detect = 0; + I2C[i2c_num]->int_clr.end_detect = 1; + i2c_master_cmd_begin_static(i2c_num); + } else if (status & I2C_RXFIFO_OVF_INT_ST_M) { + I2C[i2c_num]->int_clr.rx_fifo_ovf = 1; + } else if (status & I2C_TXFIFO_EMPTY_INT_ST_M) { + int tx_fifo_rem = I2C_FIFO_LEN - I2C[i2c_num]->status_reg.tx_fifo_cnt; + size_t size = 0; + uint8_t *data = (uint8_t*) xRingbufferReceiveUpToFromISR(p_i2c->tx_ring_buf, &size, tx_fifo_rem); + if (data) { + for (idx = 0; idx < size; idx++) { + WRITE_PERI_REG(I2C_DATA_APB_REG(i2c_num), data[idx]); + } + vRingbufferReturnItemFromISR(p_i2c->tx_ring_buf, data, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + I2C[i2c_num]->int_ena.tx_fifo_empty = 1; + I2C[i2c_num]->int_clr.tx_fifo_empty = 1; + } else { + I2C[i2c_num]->int_ena.tx_fifo_empty = 0; + I2C[i2c_num]->int_clr.tx_fifo_empty = 1; + } + } else if (status & I2C_RXFIFO_FULL_INT_ST_M) { + int rx_fifo_cnt = I2C[i2c_num]->status_reg.rx_fifo_cnt; + for (idx = 0; idx < rx_fifo_cnt; idx++) { + p_i2c->data_buf[idx] = I2C[i2c_num]->fifo_data.data; + } + xRingbufferSendFromISR(p_i2c->rx_ring_buf, p_i2c->data_buf, rx_fifo_cnt, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + I2C[i2c_num]->int_clr.rx_fifo_full = 1; + } else { + I2C[i2c_num]->int_clr.val = status; + } + } +} + +esp_err_t i2c_set_data_mode(i2c_port_t i2c_num, i2c_trans_mode_t tx_trans_mode, i2c_trans_mode_t rx_trans_mode) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(tx_trans_mode < I2C_DATA_MODE_MAX, I2C_TRANS_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(rx_trans_mode < I2C_DATA_MODE_MAX, I2C_TRANS_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + I2C[i2c_num]->ctr.rx_lsb_first = rx_trans_mode; //set rx data msb first + I2C[i2c_num]->ctr.tx_lsb_first = tx_trans_mode; //set tx data msb first + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +esp_err_t i2c_get_data_mode(i2c_port_t i2c_num, i2c_trans_mode_t *tx_trans_mode, i2c_trans_mode_t *rx_trans_mode) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + if (tx_trans_mode) { + *tx_trans_mode = I2C[i2c_num]->ctr.tx_lsb_first; + } + if (rx_trans_mode) { + *rx_trans_mode = I2C[i2c_num]->ctr.rx_lsb_first; + } + return ESP_OK; +} + +esp_err_t i2c_param_config(i2c_port_t i2c_num, i2c_config_t* i2c_conf) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(i2c_conf != NULL, I2C_ADDR_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(i2c_conf->mode < I2C_MODE_MAX, I2C_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + + esp_err_t ret = i2c_set_pin(i2c_num, i2c_conf->sda_io_num, i2c_conf->scl_io_num, + i2c_conf->sda_pullup_en, i2c_conf->scl_pullup_en, i2c_conf->mode); + if (ret != ESP_OK) { + return ret; + } + if (i2c_num == I2C_NUM_0) { + periph_module_enable(PERIPH_I2C0_MODULE); + } else if (i2c_num == I2C_NUM_1) { + periph_module_enable(PERIPH_I2C1_MODULE); + } + + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + I2C[i2c_num]->ctr.rx_lsb_first = I2C_DATA_MODE_MSB_FIRST; //set rx data msb first + I2C[i2c_num]->ctr.tx_lsb_first = I2C_DATA_MODE_MSB_FIRST; //set tx data msb first + I2C[i2c_num]->ctr.ms_mode = i2c_conf->mode; //mode for master or slave + I2C[i2c_num]->ctr.sda_force_out = 1; // set open-drain output mode + I2C[i2c_num]->ctr.scl_force_out = 1; // set open-drain output mode + I2C[i2c_num]->ctr.sample_scl_level = 0; //sample at high level of clock + + if (i2c_conf->mode == I2C_MODE_SLAVE) { //slave mode + I2C[i2c_num]->slave_addr.addr = i2c_conf->slave.slave_addr; + I2C[i2c_num]->slave_addr.en_10bit = i2c_conf->slave.addr_10bit_en; + I2C[i2c_num]->fifo_conf.nonfifo_en = 0; + I2C[i2c_num]->fifo_conf.fifo_addr_cfg_en = 0; + I2C[i2c_num]->fifo_conf.rx_fifo_full_thrhd = I2C_FIFO_FULL_THRESH_VAL; + I2C[i2c_num]->fifo_conf.tx_fifo_empty_thrhd = I2C_FIFO_EMPTY_THRESH_VAL; + I2C[i2c_num]->int_ena.rx_fifo_full = 1; + I2C[i2c_num]->ctr.trans_start = 0; + } else { + I2C[i2c_num]->fifo_conf.nonfifo_en = 0; + } + //set frequency + int half_cycle = ( I2C_APB_CLK_FREQ / i2c_conf->master.clk_speed ) / 2; + I2C[i2c_num]->scl_low_period.period = half_cycle - 1; + I2C[i2c_num]->scl_high_period.period = ( I2C_APB_CLK_FREQ / i2c_conf->master.clk_speed ) - half_cycle - 1; + //set timing for start signal + I2C[i2c_num]->scl_start_hold.time = half_cycle; + I2C[i2c_num]->scl_rstart_setup.time = half_cycle; + //set timing for stop signal + I2C[i2c_num]->scl_stop_hold.time = half_cycle; + I2C[i2c_num]->scl_stop_setup.time = half_cycle; + //set timing for data + I2C[i2c_num]->sda_hold.time = half_cycle / 2; + I2C[i2c_num]->sda_sample.time = half_cycle / 2; + //set timeout of receving data + I2C[i2c_num]->timeout.tout = 200000; + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +esp_err_t i2c_set_period(i2c_port_t i2c_num, int high_period, int low_period) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + I2C[i2c_num]->scl_high_period.period = high_period; + I2C[i2c_num]->scl_low_period.period = low_period; + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +esp_err_t i2s_get_period(i2c_port_t i2c_num, int* high_period, int* low_period) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + if (high_period) { + *high_period = I2C[i2c_num]->scl_high_period.period; + } + if (low_period) { + *low_period = I2C[i2c_num]->scl_low_period.period; + } + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +esp_err_t i2c_set_start_timing(i2c_port_t i2c_num, int setup_time, int hold_time) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C[i2c_num]->scl_start_hold.time = hold_time; + I2C[i2c_num]->scl_rstart_setup.time = setup_time; + return ESP_OK; +} + +esp_err_t i2c_get_start_timing(i2c_port_t i2c_num, int* setup_time, int* hold_time) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + if (hold_time) { + *hold_time = I2C[i2c_num]->scl_start_hold.time; + } + if (setup_time) { + *setup_time = I2C[i2c_num]->scl_rstart_setup.time; + } + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +esp_err_t i2c_set_stop_timing(i2c_port_t i2c_num, int setup_time, int hold_time) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C[i2c_num]->scl_stop_hold.time = hold_time; + I2C[i2c_num]->scl_stop_setup.time = setup_time; + return ESP_OK; +} + +esp_err_t i2c_get_stop_timing(i2c_port_t i2c_num, int* setup_time, int* hold_time) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + if (setup_time) { + *setup_time = I2C[i2c_num]->scl_stop_setup.time; + } + if (hold_time) { + *hold_time = I2C[i2c_num]->scl_stop_hold.time; + } + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +esp_err_t i2c_set_data_timing(i2c_port_t i2c_num, int sample_time, int hold_time) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C[i2c_num]->sda_hold.time = hold_time; + I2C[i2c_num]->sda_sample.time = sample_time; + return ESP_OK; +} + +esp_err_t i2c_get_data_timing(i2c_port_t i2c_num, int* sample_time, int* hold_time) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + if (sample_time) { + *sample_time = I2C[i2c_num]->sda_sample.time; + } + if (hold_time) { + *hold_time = I2C[i2c_num]->sda_hold.time; + } + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + return ESP_OK; +} + +esp_err_t i2c_isr_register(i2c_port_t i2c_num, void (*fn)(void*), void * arg, int intr_alloc_flags, intr_handle_t *handle) +{ + I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(fn != NULL, I2C_ADDR_ERROR_STR, ESP_ERR_INVALID_ARG); + esp_err_t ret; + switch (i2c_num) { + case I2C_NUM_1: + ret = esp_intr_alloc(ETS_I2C_EXT1_INTR_SOURCE, intr_alloc_flags, fn, arg, handle); + break; + case I2C_NUM_0: + default: + ret = esp_intr_alloc(ETS_I2C_EXT0_INTR_SOURCE, intr_alloc_flags, fn, arg, handle); + break; + } + return ret; +} + +esp_err_t i2c_isr_free(intr_handle_t handle) +{ + return esp_intr_free(handle); +} + +esp_err_t i2c_set_pin(i2c_port_t i2c_num, gpio_num_t sda_io_num, gpio_num_t scl_io_num, gpio_pullup_t sda_pullup_en, gpio_pullup_t scl_pullup_en, i2c_mode_t mode) +{ + I2C_CHECK(( i2c_num < I2C_NUM_MAX ), I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(((GPIO_IS_VALID_OUTPUT_GPIO(sda_io_num))), I2C_SDA_IO_ERR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK((GPIO_IS_VALID_OUTPUT_GPIO(scl_io_num)) || + (GPIO_IS_VALID_GPIO(scl_io_num) && mode == I2C_MODE_SLAVE), + I2C_SCL_IO_ERR_STR, + ESP_ERR_INVALID_ARG); + I2C_CHECK((sda_pullup_en == GPIO_PULLUP_ENABLE && GPIO_IS_VALID_OUTPUT_GPIO(sda_io_num)) || + sda_pullup_en == GPIO_PULLUP_DISABLE, I2C_GPIO_PULLUP_ERR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK((scl_pullup_en == GPIO_PULLUP_ENABLE && GPIO_IS_VALID_OUTPUT_GPIO(scl_io_num)) || + scl_pullup_en == GPIO_PULLUP_DISABLE, I2C_GPIO_PULLUP_ERR_STR, ESP_ERR_INVALID_ARG); + + int sda_in_sig, sda_out_sig, scl_in_sig, scl_out_sig; + switch (i2c_num) { + case I2C_NUM_1: + sda_out_sig = I2CEXT1_SDA_OUT_IDX; + sda_in_sig = I2CEXT1_SDA_IN_IDX; + scl_out_sig = I2CEXT1_SCL_OUT_IDX; + scl_in_sig = I2CEXT1_SCL_IN_IDX; + break; + case I2C_NUM_0: + default: + sda_out_sig = I2CEXT0_SDA_OUT_IDX; + sda_in_sig = I2CEXT0_SDA_IN_IDX; + scl_out_sig = I2CEXT0_SCL_OUT_IDX; + scl_in_sig = I2CEXT0_SCL_IN_IDX; + break; + } + if (sda_io_num >= 0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[sda_io_num], PIN_FUNC_GPIO); + gpio_set_direction(sda_io_num, GPIO_MODE_INPUT_OUTPUT_OD); + if (sda_pullup_en == GPIO_PULLUP_ENABLE) { + gpio_set_pull_mode(sda_io_num, GPIO_PULLUP_ONLY); + } else { + gpio_set_pull_mode(sda_io_num, GPIO_FLOATING); + } + gpio_matrix_out(sda_io_num, sda_out_sig, 0, 0); + gpio_matrix_in(sda_io_num, sda_in_sig, 0); + } + + if (scl_io_num >= 0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[scl_io_num], PIN_FUNC_GPIO); + if (mode == I2C_MODE_MASTER) { + gpio_set_direction(scl_io_num, GPIO_MODE_INPUT_OUTPUT_OD); + gpio_matrix_out(scl_io_num, scl_out_sig, 0, 0); + } else { + gpio_set_direction(scl_io_num, GPIO_MODE_INPUT); + } + if (scl_pullup_en == GPIO_PULLUP_ENABLE) { + gpio_set_pull_mode(scl_io_num, GPIO_PULLUP_ONLY); + } else { + gpio_set_pull_mode(scl_io_num, GPIO_FLOATING); + } + gpio_matrix_in(scl_io_num, scl_in_sig, 0); + } + return ESP_OK; +} + +i2c_cmd_handle_t i2c_cmd_link_create() +{ + i2c_cmd_desc_t* cmd_desc = (i2c_cmd_desc_t*) calloc(1, sizeof(i2c_cmd_desc_t)); + return (i2c_cmd_handle_t) cmd_desc; +} + +void i2c_cmd_link_delete(i2c_cmd_handle_t cmd_handle) +{ + if (cmd_handle == NULL) { + return; + } + i2c_cmd_desc_t* cmd = (i2c_cmd_desc_t*) cmd_handle; + while (cmd->free) { + i2c_cmd_link_t* ptmp = cmd->free; + cmd->free = cmd->free->next; + free(ptmp); + } + cmd->cur = NULL; + cmd->free = NULL; + cmd->head = NULL; + free(cmd_handle); + return; +} + +static esp_err_t i2c_cmd_link_append(i2c_cmd_handle_t cmd_handle, i2c_cmd_t* cmd) +{ + i2c_cmd_desc_t* cmd_desc = (i2c_cmd_desc_t*) cmd_handle; + if (cmd_desc->head == NULL) { + cmd_desc->head = (i2c_cmd_link_t*) malloc(sizeof(i2c_cmd_link_t)); + if (cmd_desc->head == NULL) { + ESP_LOGE(I2C_TAG, I2C_CMD_MALLOC_ERR_STR); + goto err; + } + cmd_desc->cur = cmd_desc->head; + cmd_desc->free = cmd_desc->head; + } else { + cmd_desc->cur->next = (i2c_cmd_link_t*) malloc(sizeof(i2c_cmd_link_t)); + if (cmd_desc->cur->next == NULL) { + ESP_LOGE(I2C_TAG, I2C_CMD_MALLOC_ERR_STR); + goto err; + } + cmd_desc->cur = cmd_desc->cur->next; + } + memcpy((uint8_t*) &cmd_desc->cur->cmd, (uint8_t*) cmd, sizeof(i2c_cmd_t)); + cmd_desc->cur->next = NULL; + return ESP_OK; + + err: + return ESP_FAIL; +} + +esp_err_t i2c_master_start(i2c_cmd_handle_t cmd_handle) +{ + I2C_CHECK(cmd_handle != NULL, I2C_CMD_LINK_INIT_ERR_STR, ESP_ERR_INVALID_ARG); + i2c_cmd_t cmd; + cmd.ack_en = 0; + cmd.ack_exp = 0; + cmd.ack_val = 0; + cmd.byte_num = 0; + cmd.data = NULL; + cmd.op_code = I2C_CMD_RESTART; + return i2c_cmd_link_append(cmd_handle, &cmd); +} + +esp_err_t i2c_master_stop(i2c_cmd_handle_t cmd_handle) +{ + I2C_CHECK(cmd_handle != NULL, I2C_CMD_LINK_INIT_ERR_STR, ESP_ERR_INVALID_ARG); + i2c_cmd_t cmd; + cmd.ack_en = 0; + cmd.ack_exp = 0; + cmd.ack_val = 0; + cmd.byte_num = 0; + cmd.data = NULL; + cmd.op_code = I2C_CMD_STOP; + return i2c_cmd_link_append(cmd_handle, &cmd); +} + +esp_err_t i2c_master_write(i2c_cmd_handle_t cmd_handle, uint8_t* data, size_t data_len, bool ack_en) +{ + I2C_CHECK((data != NULL), I2C_ADDR_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(cmd_handle != NULL, I2C_CMD_LINK_INIT_ERR_STR, ESP_ERR_INVALID_ARG); + + uint8_t len_tmp; + int data_offset = 0; + esp_err_t ret; + while (data_len > 0) { + len_tmp = data_len > 0xff ? 0xff : data_len; + data_len -= len_tmp; + i2c_cmd_t cmd; + cmd.ack_en = ack_en; + cmd.ack_exp = 0; + cmd.ack_val = 0; + cmd.byte_num = len_tmp; + cmd.op_code = I2C_CMD_WRITE; + cmd.data = data + data_offset; + ret = i2c_cmd_link_append(cmd_handle, &cmd); + data_offset += len_tmp; + if (ret != ESP_OK) { + return ret; + } + } + return ESP_OK; +} + +esp_err_t i2c_master_write_byte(i2c_cmd_handle_t cmd_handle, uint8_t data, bool ack_en) +{ + I2C_CHECK(cmd_handle != NULL, I2C_CMD_LINK_INIT_ERR_STR, ESP_ERR_INVALID_ARG); + i2c_cmd_t cmd; + cmd.ack_en = ack_en; + cmd.ack_exp = 0; + cmd.ack_val = 0; + cmd.byte_num = 1; + cmd.op_code = I2C_CMD_WRITE; + cmd.data = NULL; + cmd.byte_cmd = data; + return i2c_cmd_link_append(cmd_handle, &cmd); +} + +esp_err_t i2c_master_read(i2c_cmd_handle_t cmd_handle, uint8_t* data, size_t data_len, int ack) +{ + I2C_CHECK((data != NULL), I2C_ADDR_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(cmd_handle != NULL, I2C_CMD_LINK_INIT_ERR_STR, ESP_ERR_INVALID_ARG); + + int len_tmp; + int data_offset = 0; + esp_err_t ret; + while (data_len > 0) { + len_tmp = data_len > 0xff ? 0xff : data_len; + data_len -= len_tmp; + i2c_cmd_t cmd; + cmd.ack_en = 0; + cmd.ack_exp = 0; + cmd.ack_val = ack & 0x1; + cmd.byte_num = len_tmp; + cmd.op_code = I2C_CMD_READ; + cmd.data = data + data_offset; + ret = i2c_cmd_link_append(cmd_handle, &cmd); + data_offset += len_tmp; + if (ret != ESP_OK) { + return ret; + } + } + return ESP_OK; +} + +esp_err_t i2c_master_read_byte(i2c_cmd_handle_t cmd_handle, uint8_t* data, int ack) +{ + I2C_CHECK((data != NULL), I2C_ADDR_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(cmd_handle != NULL, I2C_CMD_LINK_INIT_ERR_STR, ESP_ERR_INVALID_ARG); + i2c_cmd_t cmd; + cmd.ack_en = 0; + cmd.ack_exp = 0; + cmd.ack_val = ack & 0x1; + cmd.byte_num = 1; + cmd.op_code = I2C_CMD_READ; + cmd.data = data; + return i2c_cmd_link_append(cmd_handle, &cmd); +} + +static void IRAM_ATTR i2c_master_cmd_begin_static(i2c_port_t i2c_num) +{ + i2c_obj_t* p_i2c = p_i2c_obj[i2c_num]; + portBASE_TYPE HPTaskAwoken = pdFALSE; + //This should never happen + if (p_i2c->mode == I2C_MODE_SLAVE) { + return; + } + if (p_i2c->status == I2C_STATUS_DONE) { + return; + } else if (p_i2c->status == I2C_STATUS_ACK_ERROR) { + I2C[i2c_num]->int_ena.end_detect = 0; + I2C[i2c_num]->int_clr.end_detect = 1; + I2C[i2c_num]->int_ena.master_tran_comp = 0; + xSemaphoreGiveFromISR(p_i2c->cmd_sem, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + return; + } else if (p_i2c->status == I2C_STATUS_READ) { + i2c_cmd_t *cmd = &p_i2c->cmd_link.head->cmd; + while (p_i2c->rx_cnt-- > 0) { + *cmd->data++ = READ_PERI_REG(I2C_DATA_APB_REG(i2c_num)); + } + if (cmd->byte_num > 0) { + p_i2c->rx_fifo_remain = I2C_FIFO_LEN; + p_i2c->cmd_idx = 0; + } else { + p_i2c->cmd_link.head = p_i2c->cmd_link.head->next; + } + } + if (p_i2c->cmd_link.head == NULL) { + p_i2c->cmd_link.cur = NULL; + xSemaphoreGiveFromISR(p_i2c->cmd_sem, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + return; + } + while (p_i2c->cmd_link.head) { + i2c_cmd_t *cmd = &p_i2c->cmd_link.head->cmd; + I2C[i2c_num]->command[p_i2c->cmd_idx].val = 0; + I2C[i2c_num]->command[p_i2c->cmd_idx].ack_en = cmd->ack_en; + I2C[i2c_num]->command[p_i2c->cmd_idx].ack_exp = cmd->ack_exp; + I2C[i2c_num]->command[p_i2c->cmd_idx].ack_val = cmd->ack_val; + I2C[i2c_num]->command[p_i2c->cmd_idx].byte_num = cmd->byte_num; + I2C[i2c_num]->command[p_i2c->cmd_idx].op_code = cmd->op_code; + if (cmd->op_code == I2C_CMD_WRITE) { + //TODO: to reduce interrupt number + if (cmd->data) { + while (p_i2c->tx_fifo_remain > 0 && cmd->byte_num > 0) { + WRITE_PERI_REG(I2C_DATA_APB_REG(i2c_num), *cmd->data++); + p_i2c->tx_fifo_remain--; + cmd->byte_num--; + } + } else { + WRITE_PERI_REG(I2C_DATA_APB_REG(i2c_num), cmd->byte_cmd); + p_i2c->tx_fifo_remain--; + cmd->byte_num--; + } + I2C[i2c_num]->command[p_i2c->cmd_idx].byte_num -= cmd->byte_num; + I2C[i2c_num]->command[p_i2c->cmd_idx + 1].val = 0; + I2C[i2c_num]->command[p_i2c->cmd_idx + 1].op_code = I2C_CMD_END; + p_i2c->tx_fifo_remain = I2C_FIFO_LEN; + p_i2c->cmd_idx = 0; + if (cmd->byte_num > 0) { + } else { + p_i2c->cmd_link.head = p_i2c->cmd_link.head->next; + } + p_i2c->status = I2C_STATUS_WRITE; + break; + } else if(cmd->op_code == I2C_CMD_READ) { + //TODO: to reduce interrupt number + p_i2c->rx_cnt = cmd->byte_num > p_i2c->rx_fifo_remain ? p_i2c->rx_fifo_remain : cmd->byte_num; + cmd->byte_num -= p_i2c->rx_cnt; + I2C[i2c_num]->command[p_i2c->cmd_idx].byte_num = p_i2c->rx_cnt; + I2C[i2c_num]->command[p_i2c->cmd_idx].ack_val = cmd->ack_val; + I2C[i2c_num]->command[p_i2c->cmd_idx + 1].val = 0; + I2C[i2c_num]->command[p_i2c->cmd_idx + 1].op_code = I2C_CMD_END; + p_i2c->status = I2C_STATUS_READ; + break; + } else { + } + p_i2c->cmd_idx++; + p_i2c->cmd_link.head = p_i2c->cmd_link.head->next; + if (p_i2c->cmd_link.head == NULL || p_i2c->cmd_idx >= 15) { + p_i2c->tx_fifo_remain = I2C_FIFO_LEN; + p_i2c->cmd_idx = 0; + p_i2c->status = I2C_STATUS_IDLE; + break; + } + } + I2C[i2c_num]->int_clr.end_detect = 1; + I2C[i2c_num]->int_clr.master_tran_comp = 1; + I2C[i2c_num]->int_ena.end_detect = 1; + I2C[i2c_num]->int_ena.master_tran_comp = 1; + I2C[i2c_num]->ctr.trans_start = 0; + I2C[i2c_num]->ctr.trans_start = 1; + return; +} + +esp_err_t i2c_master_cmd_begin(i2c_port_t i2c_num, i2c_cmd_handle_t cmd_handle, portBASE_TYPE ticks_to_wait) +{ + I2C_CHECK(( i2c_num < I2C_NUM_MAX ), I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); + I2C_CHECK(p_i2c_obj[i2c_num] != NULL, I2C_DRIVER_NOT_INSTALL_ERR_STR, ESP_ERR_INVALID_STATE); + I2C_CHECK(p_i2c_obj[i2c_num]->mode == I2C_MODE_MASTER, I2C_MASTER_MODE_ERR_STR, ESP_ERR_INVALID_STATE); + I2C_CHECK(cmd_handle != NULL, I2C_CMD_LINK_INIT_ERR_STR, ESP_ERR_INVALID_ARG); + + esp_err_t ret; + i2c_obj_t* p_i2c = p_i2c_obj[i2c_num]; + portTickType ticks_end = xTaskGetTickCount() + ticks_to_wait; + portBASE_TYPE res = xSemaphoreTake(p_i2c->cmd_mux, ticks_to_wait); + if (res == pdFALSE) { + return ESP_ERR_TIMEOUT; + } + xSemaphoreTake(p_i2c->cmd_sem, 0); + i2c_reset_tx_fifo(i2c_num); + i2c_reset_rx_fifo(i2c_num); + i2c_cmd_desc_t* cmd = (i2c_cmd_desc_t*) cmd_handle; + p_i2c->cmd_link.free = cmd->free; + p_i2c->cmd_link.cur = cmd->cur; + p_i2c->cmd_link.head = cmd->head; + p_i2c->status = I2C_STATUS_IDLE; + p_i2c->cmd_idx = 0; + p_i2c->rx_cnt = 0; + p_i2c->tx_fifo_remain = I2C_FIFO_LEN; + p_i2c->rx_fifo_remain = I2C_FIFO_LEN; + i2c_reset_tx_fifo(i2c_num); + i2c_reset_rx_fifo(i2c_num); + + //start send commands, at most 32 bytes one time, isr handler will process the remaining commands. + i2c_master_cmd_begin_static(i2c_num); + ticks_to_wait = ticks_end - xTaskGetTickCount(); + res = xSemaphoreTake(p_i2c->cmd_sem, ticks_to_wait); + if (res == pdFALSE) { + ret = ESP_ERR_TIMEOUT; + } else if (p_i2c->status == I2C_STATUS_ACK_ERROR) { + ret = ESP_FAIL; + } else { + ret = ESP_OK; + } + p_i2c->status = I2C_STATUS_DONE; + xSemaphoreGive(p_i2c->cmd_mux); + return ret; +} + +int i2c_slave_write_buffer(i2c_port_t i2c_num, uint8_t* data, int size, portBASE_TYPE ticks_to_wait) +{ + I2C_CHECK(( i2c_num < I2C_NUM_MAX ), I2C_NUM_ERROR_STR, ESP_FAIL); + I2C_CHECK((data != NULL), I2C_ADDR_ERROR_STR, ESP_FAIL); + I2C_CHECK(p_i2c_obj[i2c_num]->mode == I2C_MODE_SLAVE, I2C_MODE_SLAVE_ERR_STR, ESP_FAIL); + i2c_obj_t* p_i2c = p_i2c_obj[i2c_num]; + + portBASE_TYPE res; + int cnt = 0; + portTickType ticks_end = xTaskGetTickCount() + ticks_to_wait; + + res = xSemaphoreTake(p_i2c->slv_tx_mux, ticks_to_wait); + if (res == pdFALSE) { + return 0; + } + ticks_to_wait = ticks_end - xTaskGetTickCount(); + res = xRingbufferSend(p_i2c->tx_ring_buf, data, size, ticks_to_wait); + if (res == pdFALSE) { + cnt = 0; + } else { + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + I2C[i2c_num]->int_clr.tx_fifo_empty = 1; + I2C[i2c_num]->int_ena.tx_fifo_empty = 1; + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + cnt = size; + } + xSemaphoreGive(p_i2c->slv_tx_mux); + return cnt; +} + +static int i2c_slave_read(i2c_port_t i2c_num, uint8_t* data, size_t max_size, portBASE_TYPE ticks_to_wait) +{ + i2c_obj_t* p_i2c = p_i2c_obj[i2c_num]; + size_t size; + uint8_t* pdata = (uint8_t*) xRingbufferReceiveUpTo(p_i2c->rx_ring_buf, &size, ticks_to_wait, max_size); + if (pdata && size > 0) { + memcpy(data, pdata, size); + vRingbufferReturnItem(p_i2c->rx_ring_buf, pdata); + } + return size; +} + +int i2c_slave_read_buffer(i2c_port_t i2c_num, uint8_t* data, size_t max_size, portBASE_TYPE ticks_to_wait) +{ + I2C_CHECK(( i2c_num < I2C_NUM_MAX ), I2C_NUM_ERROR_STR, ESP_FAIL); + I2C_CHECK((data != NULL), I2C_ADDR_ERROR_STR, ESP_FAIL); + I2C_CHECK(p_i2c_obj[i2c_num]->mode == I2C_MODE_SLAVE, I2C_MODE_SLAVE_ERR_STR, ESP_FAIL); + + i2c_obj_t* p_i2c = p_i2c_obj[i2c_num]; + portBASE_TYPE res; + portTickType ticks_end = xTaskGetTickCount() + ticks_to_wait; + res = xSemaphoreTake(p_i2c->slv_rx_mux, ticks_to_wait); + if (res == pdFALSE) { + return 0; + } + ticks_to_wait = ticks_end - xTaskGetTickCount(); + int cnt = i2c_slave_read(i2c_num, data, max_size, ticks_to_wait); + if (cnt > 0) { + I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); + I2C[i2c_num]->int_ena.rx_fifo_full = 1; + I2C_EXIT_CRITICAL(&i2c_spinlock[i2c_num]); + ticks_to_wait = ticks_end - xTaskGetTickCount(); + if (cnt < max_size && ticks_to_wait > 0) { + cnt += i2c_slave_read(i2c_num, data + cnt, max_size - cnt, ticks_to_wait); + } + } else { + cnt = 0; + } + xSemaphoreGive(p_i2c->slv_rx_mux); + return cnt; +} + + + + diff --git a/components/driver/include/driver/i2c.h b/components/driver/include/driver/i2c.h new file mode 100644 index 0000000000..69b80b1e67 --- /dev/null +++ b/components/driver/include/driver/i2c.h @@ -0,0 +1,514 @@ +// 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_I2C_H_ +#define _DRIVER_I2C_H_ + + +#ifdef __cplusplus +extern "C" { +#endif +#include +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/xtensa_api.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/ringbuf.h" +#include "driver/gpio.h" + +#define I2C_APB_CLK_FREQ APB_CLK_FREQ /*!< I2C source clock is APB clock, 80MHz */ +#define I2C_FIFO_LEN (32) /*!< I2C hardware fifo length */ +typedef enum{ + I2C_MODE_SLAVE = 0, /*!< I2C slave mode */ + I2C_MODE_MASTER, /*!< I2C master mode */ + I2C_MODE_MAX, +}i2c_mode_t; + +typedef enum { + I2C_MASTER_WRITE = 0, /*!< I2C write data */ + I2C_MASTER_READ, /*!< I2C read data */ +} i2c_rw_t; + +typedef enum { + I2C_DATA_MODE_MSB_FIRST = 0, /*!< I2C data msb first */ + I2C_DATA_MODE_LSB_FIRST = 1, /*!< I2C data lsb first */ + I2C_DATA_MODE_MAX +} i2c_trans_mode_t; + +typedef enum{ + I2C_CMD_RESTART = 0, /*!=0) The number of data bytes that pushed to the I2C slave buffer. + */ +int i2c_slave_write_buffer(i2c_port_t i2c_num, uint8_t* data, int size, portBASE_TYPE ticks_to_wait); + +/** + * @brief I2C slave read data from internal buffer. When I2C slave receive data, isr will copy received data + * from hardware rx fifo to internal ringbuffer. Then users can read from internal ringbuffer. + * @note + * Only call this function in I2C slave mode + * + * @param i2c_num I2C port number + * @param data data pointer to write into internal buffer + * @param max_size Maximum data size to read + * @param ticks_to_wait Maximum waiting ticks + * + * @return + * - ESP_FAIL(-1) Parameter error + * - Others(>=0) The number of data bytes that read from I2C slave buffer. + */ +int i2c_slave_read_buffer(i2c_port_t i2c_num, uint8_t* data, size_t max_size, portBASE_TYPE ticks_to_wait); + +/** + * @brief set I2C master clock period + * + * @param i2c_num I2C port number + * @param high_period clock cycle number during SCL is high level, high_period is a 14 bit value + * @param low_period clock cycle number during SCL is low level, low_period is a 14 bit value + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_set_period(i2c_port_t i2c_num, int high_period, int low_period); + +/** + * @brief get I2C master clock period + * + * @param i2c_num I2C port number + * @param high_period pointer to get clock cycle number during SCL is high level, will get a 14 bit value + * @param low_period pointer to get clock cycle number during SCL is low level, will get a 14 bit value + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2s_get_period(i2c_port_t i2c_num, int* high_period, int* low_period); + +/** + * @brief set I2C master start signal timing + * + * @param i2c_num I2C port number + * @param setup_time clock number between the falling-edge of SDA and rising-edge of SCL for start mark, it's a 10-bit value. + * @param hold_time clock num between the falling-edge of SDA and falling-edge of SCL for start mark, it's a 10-bit value. + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_set_start_timing(i2c_port_t i2c_num, int setup_time, int hold_time); + +/** + * @brief get I2C master start signal timing + * + * @param i2c_num I2C port number + * @param setup_time pointer to get setup time + * @param hold_time pointer to get hold time + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_get_start_timing(i2c_port_t i2c_num, int* setup_time, int* hold_time); + +/** + * @brief set I2C master stop signal timing + * + * @param i2c_num I2C port number + * @param setup_time clock num between the rising-edge of SCL and the rising-edge of SDA, it's a 10-bit value. + * @param hold_time clock number after the STOP bit's rising-edge, it's a 14-bit value. + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_set_stop_timing(i2c_port_t i2c_num, int setup_time, int hold_time); + +/** + * @brief get I2C master stop signal timing + * + * @param i2c_num I2C port number + * @param setup_time pointer to get setup time. + * @param hold_time pointer to get hold time. + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_get_stop_timing(i2c_port_t i2c_num, int* setup_time, int* hold_time); + +/** + * @brief set I2C data signal timing + * + * @param i2c_num I2C port number + * @param sample_time clock number I2C used to sample data on SDA after the rising-edge of SCL, it's a 10-bit value + * @param hold_time clock number I2C used to hold the data after the falling-edge of SCL, it's a 10-bit value + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_set_data_timing(i2c_port_t i2c_num, int sample_time, int hold_time); + +/** + * @brief get I2C data signal timing + * + * @param i2c_num I2C port number + * @param sample_time pointer to get sample time + * @param hold_time pointer to get hold time + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_get_data_timing(i2c_port_t i2c_num, int* sample_time, int* hold_time); + +/** + * @brief set I2C data transfer mode + * + * @param i2c_num I2C port number + * @param tx_trans_mode I2C sending data mode + * @param rx_trans_mode I2C receving data mode + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_set_data_mode(i2c_port_t i2c_num, i2c_trans_mode_t tx_trans_mode, i2c_trans_mode_t rx_trans_mode); + +/** + * @brief get I2C data transfer mode + * + * @param i2c_num I2C port number + * @param tx_trans_mode pointer to get I2C sending data mode + * @param rx_trans_mode pointer to get I2C receiving data mode + * + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t i2c_get_data_mode(i2c_port_t i2c_num, i2c_trans_mode_t *tx_trans_mode, i2c_trans_mode_t *rx_trans_mode); + +#ifdef __cplusplus +} +#endif + +#endif /*_DRIVER_I2C_H_*/ diff --git a/components/esp32/include/soc/i2c_reg.h b/components/esp32/include/soc/i2c_reg.h index d5c9858a92..9693ca5b59 100644 --- a/components/esp32/include/soc/i2c_reg.h +++ b/components/esp32/include/soc/i2c_reg.h @@ -261,6 +261,8 @@ #define I2C_RXFIFO_FULL_THRHD_V 0x1F #define I2C_RXFIFO_FULL_THRHD_S 0 +#define I2C_DATA_APB_REG(i) (0x60013000 + (i) * 0x14000 + 0x001c) + #define I2C_DATA_REG(i) (REG_I2C_BASE(i) + 0x001c) /* I2C_FIFO_RDATA : RO ;bitpos:[7:0] ;default: 8'b0 ; */ /*description: The register represent the byte data read from rxfifo when use apb fifo access*/ diff --git a/components/esp32/include/soc/i2c_struct.h b/components/esp32/include/soc/i2c_struct.h index a29a9c5286..ea50d6bee5 100644 --- a/components/esp32/include/soc/i2c_struct.h +++ b/components/esp32/include/soc/i2c_struct.h @@ -16,7 +16,7 @@ typedef volatile struct { union { struct { - uint32_t scl_low_period:14; /*This register is used to configure the low level width of SCL clock.*/ + uint32_t period:14; /*This register is used to configure the low level width of SCL clock.*/ uint32_t reserved14: 18; }; uint32_t val; @@ -58,7 +58,7 @@ typedef volatile struct { } status_reg; union { struct { - uint32_t tout: 20; /*This register is used to configure the max clock number of receiving a data.*/ + uint32_t tout: 20; /*This register is used to configure the max clock number of receiving a data, unit: APB clock cycle.*/ uint32_t reserved20:12; }; uint32_t val; @@ -282,7 +282,7 @@ typedef volatile struct { uint32_t reserved_f4; uint32_t date; /**/ uint32_t reserved_fc; - uint32_t fifo_start_addr; /*This the start address for ram when use apb nonfifo access.*/ + uint32_t ram_data[32]; /*This the start address for ram when use apb nonfifo access.*/ } i2c_dev_t; extern i2c_dev_t I2C0; extern i2c_dev_t I2C1; diff --git a/docs/api/i2c.rst b/docs/api/i2c.rst new file mode 100644 index 0000000000..1186e0583b --- /dev/null +++ b/docs/api/i2c.rst @@ -0,0 +1,82 @@ +I2C +=========== + +Overview +-------- + +ESP32 has two I2C controllers which can be set as master mode or slave mode. + +Application Example +------------------- + +I2C master and slave example: `examples/18_i2c `_. + +API Reference +------------- + +Header Files +^^^^^^^^^^^^ + + * `driver/include/driver/i2c.h `_ + +Macros +^^^^^^ + +.. doxygendefine:: I2C_APB_CLK_FREQ +.. doxygendefine:: I2C_FIFO_LEN + +Type Definitions +^^^^^^^^^^^^^^^^ + +.. doxygentypedef:: i2c_cmd_handle_t + +Enumerations +^^^^^^^^^^^^ + +.. doxygenenum:: i2c_mode_t +.. doxygenenum:: i2c_rw_t +.. doxygenenum:: i2c_trans_mode_t +.. doxygenenum:: i2c_opmode_t +.. doxygenenum:: i2c_port_t +.. doxygenenum:: i2c_addr_mode_t + +Structures +^^^^^^^^^^ + +.. doxygenstruct:: i2c_config_t + :members: + + +Functions +^^^^^^^^^ + +.. doxygenfunction:: i2c_driver_install +.. doxygenfunction:: i2c_driver_delete +.. doxygenfunction:: i2c_param_config +.. doxygenfunction:: i2c_reset_tx_fifo +.. doxygenfunction:: i2c_reset_rx_fifo +.. doxygenfunction:: i2c_isr_register +.. doxygenfunction:: i2c_isr_free +.. doxygenfunction:: i2c_set_pin +.. doxygenfunction:: i2c_master_start +.. doxygenfunction:: i2c_master_write_byte +.. doxygenfunction:: i2c_master_write +.. doxygenfunction:: i2c_master_read_byte +.. doxygenfunction:: i2c_master_read +.. doxygenfunction:: i2c_master_stop +.. doxygenfunction:: i2c_master_cmd_begin +.. doxygenfunction:: i2c_slave_write_buffer +.. doxygenfunction:: i2c_slave_read +.. doxygenfunction:: i2c_set_period +.. doxygenfunction:: i2s_get_period +.. doxygenfunction:: i2c_set_start_timing +.. doxygenfunction:: i2c_get_start_timing +.. doxygenfunction:: i2c_set_stop_timing +.. doxygenfunction:: i2c_get_stop_timing +.. doxygenfunction:: i2c_set_data_timing +.. doxygenfunction:: i2c_get_data_timing +.. doxygenfunction:: i2c_set_data_mode +.. doxygenfunction:: i2c_get_data_mode +.. doxygenfunction:: i2c_cmd_link_create +.. doxygenfunction:: i2c_cmd_link_delete + diff --git a/docs/index.rst b/docs/index.rst index ba25b49691..9c2b0643f4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -107,6 +107,7 @@ Contents: LED Control Remote Control Timer + I2C Pulse Counter Sigma-delta Modulation SPI Flash and Partition APIs diff --git a/examples/18_i2c/Makefile b/examples/18_i2c/Makefile new file mode 100644 index 0000000000..d575e29b9d --- /dev/null +++ b/examples/18_i2c/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 := i2c + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/18_i2c/README.md b/examples/18_i2c/README.md new file mode 100644 index 0000000000..7a88894bf1 --- /dev/null +++ b/examples/18_i2c/README.md @@ -0,0 +1,29 @@ +# I2C Example + + +* This example will show you how to use I2C module by running two tasks on i2c bus: + + * read external i2c sensor, here we use a BH1750 light sensor(GY-30 module) for instance. + * Use one I2C port(master mode) to read or write the other I2C port(slave mode) on one ESP32 chip. + +* Pin assignment: + + * slave : + * GPIO25 is assigned as the data signal of i2c slave port + * GPIO26 is assigned as the clock signal of i2c slave port + * master: + * GPIO18 is assigned as the data signal of i2c master port + * GPIO19 is assigned as the clock signal of i2c master port + +* Connection: + + * connect GPIO18 with GPIO25 + * connect GPIO19 with GPIO26 + * connect sda/scl of sensor with GPIO18/GPIO19 + * no need to add external pull-up resistors, driver will enable internal pull-up resistors. + +* Test items: + + * read the sensor data, if connected. + * i2c master(ESP32) will write data to i2c slave(ESP32). + * i2c master(ESP32) will read data from i2c slave(ESP32). diff --git a/examples/18_i2c/main/component.mk b/examples/18_i2c/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/18_i2c/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/18_i2c/main/i2c_test.c b/examples/18_i2c/main/i2c_test.c new file mode 100644 index 0000000000..2ef6d75b2f --- /dev/null +++ b/examples/18_i2c/main/i2c_test.c @@ -0,0 +1,303 @@ +/* i2c - Example + + For other examples please check: + https://github.com/espressif/esp-idf/tree/master/examples + + 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 "driver/i2c.h" + +/** + * TEST CODE BRIEF + * + * This example will show you how to use I2C module by running two tasks on i2c bus: + * + * - read external i2c sensor, here we use a BH1750 light sensor(GY-30 module) for instance. + * - Use one I2C port(master mode) to read or write the other I2C port(slave mode) on one ESP32 chip. + * + * Pin assignment: + * + * - slave : + * GPIO25 is assigned as the data signal of i2c slave port + * GPIO26 is assigned as the clock signal of i2c slave port + * - master: + * GPIO18 is assigned as the data signal of i2c master port + * GPIO19 is assigned as the clock signal of i2c master port + * + * Connection: + * + * - connect GPIO18 with GPIO25 + * - connect GPIO19 with GPIO26 + * - connect sda/scl of sensor with GPIO18/GPIO19 + * - no need to add external pull-up resistors, driver will enable internal pull-up resistors. + * + * Test items: + * + * - read the sensor data, if connected. + * - i2c master(ESP32) will write data to i2c slave(ESP32). + * - i2c master(ESP32) will read data from i2c slave(ESP32). + */ + +#define DATA_LENGTH 512 /*! 1) { + i2c_master_read(cmd, data_rd, size - 1, ACK_VAL); + } + i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + return ret; +} + +/** + * @brief Test code to write esp-i2c-slave + * Master device write data to slave(both esp32), + * the data will be stored in slave buffer. + * We can read them out from slave buffer. + * + * ___________________________________________________________________ + * | start | slave_addr + wr_bit + ack | write n bytes + ack | stop | + * --------|---------------------------|----------------------|------| + * + */ +esp_err_t i2c_master_write_slave(i2c_port_t i2c_num, uint8_t* data_wr, size_t size) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( ESP_SLAVE_ADDR << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write(cmd, data_wr, size, ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + return ret; +} + +/** + * @brief test code to write esp-i2c-slave + * + * 1. set mode + * _________________________________________________________________ + * | start | slave_addr + wr_bit + ack | write 1 byte + ack | stop | + * --------|---------------------------|---------------------|------| + * 2. wait more than 24 ms + * 3. read data + * ______________________________________________________________________________________ + * | start | slave_addr + rd_bit + ack | read 1 byte + ack | read 1 byte + nack | stop | + * --------|---------------------------|--------------------|--------------------|------| + */ +esp_err_t i2c_master_sensor_test(i2c_port_t i2c_num, uint8_t* data_h, uint8_t* data_l) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, BH1750_SENSOR_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, BH1750_CMD_START, ACK_CHECK_EN); + i2c_master_stop(cmd); + int ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if (ret == ESP_FAIL) { + return ret; + } + vTaskDelay(30 / portTICK_RATE_MS); + + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, BH1750_SENSOR_ADDR << 1 | READ_BIT, ACK_CHECK_EN); + i2c_master_read_byte(cmd, data_h, ACK_VAL); + i2c_master_read_byte(cmd, data_l, NACK_VAL); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(i2c_num, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + if (ret == ESP_FAIL) { + return ESP_FAIL; + } + return ESP_OK; +} + +/** + * @brief i2c master initialization + */ +void i2c_master_init() +{ + int i2c_master_port = I2C_MASTER_NUM; + i2c_config_t conf; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = I2C_MASTER_SDA_IO; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_io_num = I2C_MASTER_SCL_IO; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = I2C_MASTER_FREQ_HZ; + i2c_param_config(i2c_master_port, &conf); + i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); +} + +/** + * @brief i2c slave initialization + */ +void i2c_slave_init() +{ + int i2c_slave_port = I2C_SLAVE_NUM; + i2c_config_t conf_slave; + conf_slave.sda_io_num = I2C_SLAVE_SDA_IO; + conf_slave.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf_slave.scl_io_num = I2C_SLAVE_SCL_IO; + conf_slave.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf_slave.mode = I2C_MODE_SLAVE; + conf_slave.slave.addr_10bit_en = 0; + conf_slave.slave.slave_addr = ESP_SLAVE_ADDR; + i2c_param_config(i2c_slave_port, &conf_slave); + i2c_driver_install(i2c_slave_port, conf_slave.mode, I2C_SLAVE_RX_BUF_LEN, I2C_SLAVE_TX_BUF_LEN, 0); +} + +/** + * @brief test function to show buffer + */ +void disp_buf(uint8_t* buf, int len) +{ + int i; + for (i = 0; i < len; i++) { + printf("%02x ", buf[i]); + if (( i + 1 ) % 16 == 0) { + printf("\n"); + } + } + printf("\n"); +} + +void i2c_test_task(void* arg) +{ + int i = 0; + int ret; + uint32_t task_idx = (uint32_t) arg; + uint8_t* data = (uint8_t*) malloc(DATA_LENGTH); + uint8_t* data_wr = (uint8_t*) malloc(DATA_LENGTH); + uint8_t* data_rd = (uint8_t*) malloc(DATA_LENGTH); + uint8_t sensor_data_h, sensor_data_l; + + while (1) { + ret = i2c_master_sensor_test( I2C_MASTER_NUM, &sensor_data_h, &sensor_data_l); + xSemaphoreTake(print_mux, portMAX_DELAY); + printf("*******************\n"); + printf("TASK[%d] MASTER READ SENSOR( BH1750 )\n", task_idx); + printf("*******************\n"); + if (ret == ESP_OK) { + printf("data_h: %02x\n", sensor_data_h); + printf("data_l: %02x\n", sensor_data_l); + printf("sensor val: %f\n", ( sensor_data_h << 8 | sensor_data_l ) / 1.2); + } else { + printf("No ack, sensor not connected...skip...\n"); + } + xSemaphoreGive(print_mux); + vTaskDelay(( DELAY_TIME_BETWEEN_ITEMS_MS * ( task_idx + 1 ) ) / portTICK_RATE_MS); + + //--------------------------------------------------- + for (i = 0; i < DATA_LENGTH; i++) { + data[i] = i; + } + size_t d_size = i2c_slave_write_buffer(I2C_SLAVE_NUM, data, RW_TEST_LENGTH, 1000 / portTICK_RATE_MS); + if (d_size == 0) { + printf("i2c slave tx buffer full\n"); + ret = i2c_master_read_slave(I2C_MASTER_NUM, data_rd, DATA_LENGTH); + } else { + ret = i2c_master_read_slave(I2C_MASTER_NUM, data_rd, RW_TEST_LENGTH); + } + xSemaphoreTake(print_mux, portMAX_DELAY); + printf("*******************\n"); + printf("TASK[%d] MASTER READ FROM SLAVE\n", task_idx); + printf("*******************\n"); + printf("====TASK[%d] Slave buffer data ====\n", task_idx); + disp_buf(data, d_size); + if (ret == ESP_OK) { + printf("====TASK[%d] Master read ====\n", task_idx); + disp_buf(data_rd, d_size); + } else { + printf("Master read slave error, IO not connected...\n"); + } + xSemaphoreGive(print_mux); + vTaskDelay(( DELAY_TIME_BETWEEN_ITEMS_MS * ( task_idx + 1 ) ) / portTICK_RATE_MS); + //--------------------------------------------------- + int size; + for (i = 0; i < DATA_LENGTH; i++) { + data_wr[i] = i + 10; + } + //we need to fill the slave buffer so that master can read later + ret = i2c_master_write_slave( I2C_MASTER_NUM, data_wr, RW_TEST_LENGTH); + if (ret == ESP_OK) { + size = i2c_slave_read_buffer( I2C_SLAVE_NUM, data, RW_TEST_LENGTH, 1000 / portTICK_RATE_MS); + } + xSemaphoreTake(print_mux, portMAX_DELAY); + printf("*******************\n"); + printf("TASK[%d] MASTER WRITE TO SLAVE\n", task_idx); + printf("*******************\n"); + printf("----TASK[%d] Master write ----\n", task_idx); + disp_buf(data_wr, RW_TEST_LENGTH); + if (ret == ESP_OK) { + printf("----TASK[%d] Slave read: [%d] bytes ----\n", task_idx, size); + disp_buf(data, size); + } else { + printf("TASK[%d] Master write slave error, IO not connected....\n", task_idx); + } + xSemaphoreGive(print_mux); + vTaskDelay(( DELAY_TIME_BETWEEN_ITEMS_MS * ( task_idx + 1 ) ) / portTICK_RATE_MS); + } +} + +void app_main() +{ + print_mux = xSemaphoreCreateMutex(); + i2c_slave_init(); + i2c_master_init(); + + xTaskCreate(i2c_test_task, "i2c_test_task_0", 1024 * 2, (void* ) 0, 10, NULL); + xTaskCreate(i2c_test_task, "i2c_test_task_1", 1024 * 2, (void* ) 1, 10, NULL); +} + From 03551ec2da506dd6471de566803505e48c64e2db Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Dec 2016 12:19:02 +1100 Subject: [PATCH 041/167] build system: Add 'make monitor' target from arduino-esp32 Originally added to arduino-esp32 by @me-no-dev. It's so useful, we want it in esp-idf as well! :) --- README.md | 10 ++++++ components/esptool_py/Kconfig.projbuild | 40 ++++++++++++++++++++++++ components/esptool_py/Makefile.projbuild | 8 +++++ make/project.mk | 1 + 4 files changed, 59 insertions(+) diff --git a/README.md b/README.md index f3007fdb88..32c65bb820 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,16 @@ This will flash the entire project (app, bootloader and partition table) to a ne You don't need to run `make all` before running `make flash`, `make flash` will automatically rebuild anything which needs it. +# Viewing Serial Output + +The `make monitor` target will use the already-installed [miniterm](http://pyserial.readthedocs.io/en/latest/tools.html#module-serial.tools.miniterm) (a part of pyserial) to display serial output from the ESP32 on the terminal console. + +Exit miniterm by typing Ctrl-]. + +To flash and monitor output in one pass, you can run: + +`make flash monitor` + # Compiling & Flashing Just the App After the initial flash, you may just want to build and flash just your app, not the bootloader and partition table: diff --git a/components/esptool_py/Kconfig.projbuild b/components/esptool_py/Kconfig.projbuild index dd2077cbb3..edff88c55d 100644 --- a/components/esptool_py/Kconfig.projbuild +++ b/components/esptool_py/Kconfig.projbuild @@ -178,4 +178,44 @@ config ESPTOOLPY_AFTER default "hard_reset" if ESPTOOLPY_AFTER_RESET default "no_reset" if ESPTOOLPY_AFTER_NORESET +choice MONITOR_BAUD + prompt "'make monitor' baud rate" + default MONITOR_BAUD_115200B + help + Baud rate to use when running 'make monitor' to view serial output + from a running chip. + + Can override by setting the MONITORBAUD environment variable. + +config MONITOR_BAUD_9600B + bool "9600 bps" +config MONITOR_BAUD_57600B + bool "57600 bps" +config MONITOR_BAUD_115200B + bool "115200 bps" +config MONITOR_BAUD_230400B + bool "230400 bps" +config MONITOR_BAUD_921600B + bool "921600 bps" +config MONITOR_BAUD_2MB + bool "2 Mbps" +config MONITOR_BAUD_OTHER + bool "Custom baud rate" + +endchoice + +config MONITOR_BAUD_OTHER_VAL + int "Custom baud rate value" if MONITOR_BAUD_OTHER + default 115200 + +config MONITOR_BAUD + int + default 9600 if MONITOR_BAUD_9600B + default 57600 if MONITOR_BAUD_57600B + default 115200 if MONITOR_BAUD_115200B + default 230400 if MONITOR_BAUD_230400B + default 921600 if MONITOR_BAUD_921600B + default 2000000 if MONITOR_BAUD_2MB + default MONITOR_BAUD_OTHER_VAL if MONITOR_BAUD_OTHER + endmenu diff --git a/components/esptool_py/Makefile.projbuild b/components/esptool_py/Makefile.projbuild index 88b5894731..e249d14432 100644 --- a/components/esptool_py/Makefile.projbuild +++ b/components/esptool_py/Makefile.projbuild @@ -67,4 +67,12 @@ erase_flash: @echo "Erasing entire flash..." $(ESPTOOLPY_SERIAL) erase_flash +MONITORBAUD ?= $(CONFIG_MONITOR_BAUD) + +# note: if you want to run miniterm from command line, can simply run +# miniterm.py on the console. The '$(PYTHON) -m serial.tools.miniterm' +# is to allow for the $(PYTHON) variable overriding the python path. +monitor: $(call prereq_if_explicit,%flash) + $(PYTHON) -m serial.tools.miniterm --rts 0 --dtr 0 --raw $(ESPPORT) $(MONITORBAUD) + .PHONY: erase_flash diff --git a/make/project.mk b/make/project.mk index a5e487b85c..e2a93ce119 100644 --- a/make/project.mk +++ b/make/project.mk @@ -30,6 +30,7 @@ help: @echo "make clean - Remove all build output" @echo "make size - Display the memory footprint of the app" @echo "make erase_flash - Erase entire flash contents" + @echo "make monitor - Display serial output on terminal console" @echo "" @echo "make app - Build just the app" @echo "make app-flash - Flash just the app" From 4f7314a760377e9947af2b57d655b5d797e6cf27 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Dec 2016 12:19:39 +1100 Subject: [PATCH 042/167] README: Add a note about parallel builds --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 32c65bb820..5290aa7981 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,16 @@ After the initial flash, you may just want to build and flash just your app, not (There's no downside to reflashing the bootloader and partition table each time, if they haven't changed.) +# Parallel Builds + +esp-idf supports compiling multiple files in parallel, so all of the above commands can be run as `make -jN` where `N` is the number of parallel make processes to run (generally N should be equal to or one more than the number of CPU cores in your system.) + +Multiple make functions can be combined into one. For example: to build the app & bootloader using 5 jobs in parallel, then flash everything, and then display serial output from the ESP32 run: + +``` +make -j5 flash monitor +``` + # The Partition Table Once you've compiled your project, the "build" directory will contain a binary file with a name like "my_app.bin". This is an ESP32 image binary that can be loaded by the bootloader. From 5e96070c27ecd0901c4501666d1f5b274adadf94 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 30 Dec 2016 10:38:21 +1100 Subject: [PATCH 043/167] linker script: Remove KEEP from RAM-resident sections Reduce RAM usage when not all data/bss sections in source files were used. --- components/esp32/ld/esp32.common.ld | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/components/esp32/ld/esp32.common.ld b/components/esp32/ld/esp32.common.ld index aafafbb495..09b7634445 100644 --- a/components/esp32/ld/esp32.common.ld +++ b/components/esp32/ld/esp32.common.ld @@ -99,7 +99,7 @@ SECTIONS *(.sbss2.*) *(.gnu.linkonce.sb2.*) *(.dynbss) - KEEP(*(.bss)) + *(.bss) *(.bss.*) *(.share.mem) *(.gnu.linkonce.b.*) @@ -111,17 +111,17 @@ SECTIONS .dram0.data : { _data_start = ABSOLUTE(.); - KEEP(*(.data)) - KEEP(*(.data.*)) - KEEP(*(.gnu.linkonce.d.*)) - KEEP(*(.data1)) - KEEP(*(.sdata)) - KEEP(*(.sdata.*)) - KEEP(*(.gnu.linkonce.s.*)) - KEEP(*(.sdata2)) - KEEP(*(.sdata2.*)) - KEEP(*(.gnu.linkonce.s2.*)) - KEEP(*(.jcr)) + *(.data) + *(.data.*) + *(.gnu.linkonce.d.*) + *(.data1) + *(.sdata) + *(.sdata.*) + *(.gnu.linkonce.s.*) + *(.sdata2) + *(.sdata2.*) + *(.gnu.linkonce.s2.*) + *(.jcr) *(.dram1 .dram1.*) *libesp32.a:panic.o(.rodata .rodata.*) _data_end = ABSOLUTE(.); From c47bc43afdea856c130a99a8ae77ba0ff99f739f Mon Sep 17 00:00:00 2001 From: Yinling Date: Mon, 2 Jan 2017 13:12:30 +0800 Subject: [PATCH 044/167] CI: fix bug that restore initial condition fail: in STA+AP mode, when STA is reconnecting to external AP, its AP interface is not connectable. do disconnect on STA interface first --- .../integration_test/InitialConditionAll.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/components/idf_test/integration_test/InitialConditionAll.yml b/components/idf_test/integration_test/InitialConditionAll.yml index 3821894552..5d4ce7ab3f 100644 --- a/components/idf_test/integration_test/InitialConditionAll.yml +++ b/components/idf_test/integration_test/InitialConditionAll.yml @@ -260,6 +260,8 @@ initial condition: - ['R SSC1 C +MAC:AP,OK'] - - SSC SSC1 dhcp -S -o 2 - [R SSC1 C +DHCP] + - - SSC SSC1 sta -D + - ['R SSC1 C +QAP'] - - SSC SSC1 ap -S -s -p -t - ['R SSC1 C +SAP:OK'] initial condition detail: testing ap on sta + ap mode (autogen by APM1) @@ -271,6 +273,8 @@ initial condition: - ['R SSC1 C +MAC:AP,OK'] - - SSC SSC1 dhcp -S -o 2 - [R SSC1 C +DHCP] + - - SSC SSC1 sta -D + - ['R SSC1 C +QAP'] - - SSC SSC1 ap -S -s -p -t - ['R SSC1 C +SAP:OK'] restore post cmd set: @@ -305,6 +309,8 @@ initial condition: - ['R SSC1 C +MAC:AP,OK'] - - SSC SSC1 dhcp -S -o 2 - [R SSC1 C +DHCP] + - - SSC SSC1 sta -D + - ['R SSC1 C +QAP'] - - SSC SSC1 ap -S -s -p -t - ['R SSC1 C +SAP:OK'] - - WIFI CONN @@ -319,6 +325,8 @@ initial condition: - ['R SSC1 C +MAC:AP,OK'] - - SSC SSC1 dhcp -S -o 2 - [R SSC1 C +DHCP] + - - SSC SSC1 sta -D + - ['R SSC1 C +QAP'] - - SSC SSC1 ap -S -s -p -t - ['R SSC1 C +SAP:OK'] - - WIFI CONN @@ -2854,6 +2862,8 @@ initial condition: - ['R SSC1 C +MODE:OK'] - - SSC SSC2 op -S -o 3 - ['R SSC2 C +MODE:OK'] + - - SSC SSC1 sta -D + - ['R SSC1 C +QAP'] - - SSC SSC2 sta -D - ['R SSC2 C +QAP:'] - - SSC SSC2 soc -T @@ -2873,6 +2883,8 @@ initial condition: - ['R SSC1 C +MODE:OK'] - - SSC SSC2 op -S -o 3 - ['R SSC2 C +MODE:OK'] + - - SSC SSC1 sta -D + - ['R SSC1 C +QAP'] - - SSC SSC2 sta -D - ['R SSC2 C +QAP:'] - - SSC SSC2 soc -T From b9a6d276a2cda8a7aa0ec32fdff2ca71fd8b64fc Mon Sep 17 00:00:00 2001 From: Yinling Date: Mon, 2 Jan 2017 13:15:10 +0800 Subject: [PATCH 045/167] CI: update known issues --- .../idf_test/integration_test/KnownIssues | 109 +++++++++++++----- 1 file changed, 81 insertions(+), 28 deletions(-) diff --git a/components/idf_test/integration_test/KnownIssues b/components/idf_test/integration_test/KnownIssues index e0991f39b0..c6cb7b77b9 100644 --- a/components/idf_test/integration_test/KnownIssues +++ b/components/idf_test/integration_test/KnownIssues @@ -5,24 +5,6 @@ TCPIP_ICMP_0101 ^TCPIP_ICMP_0101 -# IGMP cases are not supported for now -TCPIP_IGMP_0101 -TCPIP_IGMP_0102 -TCPIP_IGMP_0103 -TCPIP_IGMP_0104 -TCPIP_IGMP_0201 -TCPIP_IGMP_0202 -TCPIP_IGMP_0203 -TCPIP_IGMP_0204 -^TCPIP_IGMP_0101 -^TCPIP_IGMP_0102 -^TCPIP_IGMP_0103 -^TCPIP_IGMP_0104 -^TCPIP_IGMP_0201 -^TCPIP_IGMP_0202 -^TCPIP_IGMP_0203 -^TCPIP_IGMP_0204 - # don't support PHY mode command WIFI_SCAN_0201 WIFI_SCAN_0302 @@ -48,29 +30,62 @@ WIFI_CONN_0801 ^WIFI_CONN_0801 # disconnect reason -WIFI_CONN_0904 -^WIFI_CONN_0904 WIFI_CONN_0901 ^WIFI_CONN_0901 +WIFI_CONN_0904 +^WIFI_CONN_0904 +^WIFI_CONN_0902 +WIFI_CONN_0902 # Wifi connect issue -WIFI_CONN_0104 -^WIFI_CONN_0104 +^WIFI_CONN_0101 +WIFI_CONN_0101 +WIFI_CONN_0103 +^WIFI_CONN_0401 +^WIFI_CONN_0103 +WIFI_CONN_0102 +WIFI_CONN_0601 ^WIFI_CONN_0601 +WIFI_ADDR_0102 +^WIFI_ADDR_0102 +WIFI_CONN_0502 +WIFI_CONN_0501 # Wifi scan issue -WIFI_SCAN_0303 +^WIFI_SCAN_0101 +^WIFI_SCAN_0102 +WIFI_SCAN_0103 ^WIFI_SCAN_0103 +WIFI_SCAN_0104 +^WIFI_SCAN_0104 ^WIFI_SCAN_0105 +WIFI_SCAN_0303 +WIFI_SCAN_0304 +^WIFI_SCAN_0104 -# set mac address may lead to exception -WIFI_ADDR_0101 -^WIFI_ADDR_0101 +# IGMP cases are supported but as UDP is not stable, exclude them first +TCPIP_IGMP_0101 +TCPIP_IGMP_0102 +TCPIP_IGMP_0103 +TCPIP_IGMP_0104 +TCPIP_IGMP_0201 +TCPIP_IGMP_0202 +TCPIP_IGMP_0203 +TCPIP_IGMP_0204 +^TCPIP_IGMP_0101 +^TCPIP_IGMP_0102 +^TCPIP_IGMP_0103 +^TCPIP_IGMP_0104 +^TCPIP_IGMP_0201 +^TCPIP_IGMP_0202 +^TCPIP_IGMP_0203 +^TCPIP_IGMP_0204 # DHCP issues ^TCPIP_DHCP_0301 TCPIP_DHCP_0301 TCPIP_DHCP_0101 +^TCPIP_DHCP_0101 TCPIP_DHCP_0207 ^TCPIP_DHCP_0207 TCPIP_DHCP_0208 @@ -79,6 +94,14 @@ TCPIP_DHCP_0205 ^TCPIP_DHCP_0205 TCPIP_DHCP_0209 ^TCPIP_DHCP_0209 +^TCPIP_DHCP_0204 +TCPIP_DHCP_0204 +^TCPIP_DHCP_0210 +TCPIP_DHCP_0210 +^TCPIP_DHCP_0211 +TCPIP_DHCP_0211 +^TCPIP_DHCP_0206 +^TCPIP_DHCP_0302 # TCP issue TCPIP_TCP_0402 @@ -90,17 +113,47 @@ TCPIP_TCP_0103 ^TCPIP_TCP_0103 TCPIP_TCP_0112 ^TCPIP_TCP_0112 - +TCPIP_TCP_0106 +TCPIP_TCP_0107 +TCPIP_TCP_0105 +^TCPIP_TCP_0407 +^TCPIP_TCP_0404 +^TCPIP_TCP_0102 +^TCPIP_TCP_0105 +^TCPIP_TCP_0107 +^TCPIP_TCP_0206 +TCPIP_TCP_0102 +^TCPIP_TCP_0403 +^TCPIP_TCP_0402 +^TCPIP_TCP_0101 +^TCPIP_TCP_0203 +^TCPIP_TCP_0106 +^TCPIP_TCP_0201 +^TCPIP_TCP_0412 +TCPIP_TCP_0101 # UDP issue TCPIP_UDP_0103 ^TCPIP_UDP_0103 +^TCPIP_UDP_0108 TCPIP_UDP_0110 ^TCPIP_UDP_0110 +^TCPIP_UDP_0112 TCPIP_UDP_0305 ^TCPIP_UDP_0305 ^TCPIP_UDP_0304 TCPIP_UDP_0104 +^TCPIP_UDP_0104 +^TCPIP_UDP_0301 +TCPIP_UDP_0301 +TCPIP_UDP_0302 +TCPIP_UDP_0303 +^TCPIP_UDP_0303 +^TCPIP_UDP_0307 +^TCPIP_UDP_0302 +^TCPIP_UDP_0306 - +#DNS +^TCPIP_DNS_0103 +^TCPIP_DNS_0102 From 16de41941e7fde28a7b83a2b71893879645c10dc Mon Sep 17 00:00:00 2001 From: Yinling Date: Mon, 2 Jan 2017 16:25:26 +0800 Subject: [PATCH 046/167] CI: fix bug that test report job failed --- .gitlab-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 75a1fb59ff..8cb5c8bcac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -180,7 +180,8 @@ test_report: - git clone $GITLAB_SSH_SERVER/yinling/auto_test_script.git - cd auto_test_script # generate report - - python CITestReport.py -l $LOG_PATH -t $TEST_CASE_FILE_PATH -p $REPORT_PATH -r $RESULT_PATH -a $ARTIFACTS_PATH || FAIL=True + - TEST_RESULT=Pass + - python CITestReport.py -l $LOG_PATH -t $TEST_CASE_FILE_PATH -p $REPORT_PATH -r $RESULT_PATH -a $ARTIFACTS_PATH || TEST_RESULT=Fail # commit to CI-test-result project - git clone $GITLAB_SSH_SERVER/qa/CI-test-result.git - rm -rf CI-test-result/RawData/$RESULT_PATH @@ -193,7 +194,7 @@ test_report: - git add . - git commit . -m "update test result for $CI_PROJECT_NAME/$CI_BUILD_REF_NAME/$CI_BUILD_REF, pipeline ID $CI_PIPELINE_ID" || exit 0 - git push origin master - - test "${FAIL}" = "True" && exit 1 + - test "${TEST_RESULT}" = "Pass" || exit 1 push_master_to_github: before_script: From 2e3ca1c2f7f35775010cd7637bd85fac0ef6dbef Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Jan 2017 15:36:40 +1100 Subject: [PATCH 047/167] bootloader: Boost bootloader CPU to 80MHz Partially needed to use RNG, also useful to improve boot performance. --- .../bootloader/src/main/bootloader_start.c | 46 +++++++++---------- components/bootloader/src/main/component.mk | 8 ++++ components/esp32/system_api.c | 4 ++ 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/components/bootloader/src/main/bootloader_start.c b/components/bootloader/src/main/bootloader_start.c index e5fda9cf53..cd845b0b92 100644 --- a/components/bootloader/src/main/bootloader_start.c +++ b/components/bootloader/src/main/bootloader_start.c @@ -44,6 +44,7 @@ #include "bootloader_flash.h" #include "bootloader_config.h" +#include "rtc.h" extern int _bss_start; extern int _bss_end; @@ -232,6 +233,13 @@ static bool ota_select_valid(const esp_ota_select_entry_t *s) void bootloader_main() { + /* Set CPU to 80MHz. + Start by ensuring it is set to XTAL, as PLL must be off first + (may still be on due to soft reset.) + */ + rtc_set_cpu_freq(CPU_XTAL); + rtc_set_cpu_freq(CPU_80M); + uart_console_configure(); ESP_LOGI(TAG, "Espressif ESP32 2nd stage bootloader v. %s", BOOT_VERSION); #if defined(CONFIG_SECURE_BOOT_ENABLED) || defined(CONFIG_FLASH_ENCRYPTION_ENABLED) @@ -666,26 +674,6 @@ void print_flash_info(const esp_image_header_t* phdr) #endif } -#if CONFIG_CONSOLE_UART_CUSTOM -static uint32_t get_apb_freq(void) -{ - // Get the value of APB clock from RTC memory. - // The value is initialized in ROM code, and updated by librtc.a - // when APB clock is changed. - // This value is stored in RTC_CNTL_STORE5_REG as follows: - // RTC_CNTL_STORE5_REG = (freq >> 12) | ((freq >> 12) << 16) - uint32_t apb_freq_reg = REG_READ(RTC_CNTL_STORE5_REG); - uint32_t apb_freq_l = apb_freq_reg & 0xffff; - uint32_t apb_freq_h = apb_freq_reg >> 16; - if (apb_freq_l == apb_freq_h && apb_freq_l != 0) { - return apb_freq_l << 12; - } else { - // fallback value - return APB_CLK_FREQ_ROM; - } -} -#endif - static void uart_console_configure(void) { #if CONFIG_CONSOLE_UART_NONE @@ -695,20 +683,21 @@ static void uart_console_configure(void) uartAttach(); ets_install_uart_printf(); + // ROM bootloader may have put a lot of text into UART0 FIFO. + // Wait for it to be printed. + uart_tx_wait_idle(0); + #if CONFIG_CONSOLE_UART_CUSTOM // Some constants to make the following code less upper-case const int uart_num = CONFIG_CONSOLE_UART_NUM; const int uart_baud = CONFIG_CONSOLE_UART_BAUDRATE; const int uart_tx_gpio = CONFIG_CONSOLE_UART_TX_GPIO; const int uart_rx_gpio = CONFIG_CONSOLE_UART_RX_GPIO; - // ROM bootloader may have put a lot of text into UART0 FIFO. - // Wait for it to be printed. - uart_tx_wait_idle(0); // Switch to the new UART (this just changes UART number used for // ets_printf in ROM code). uart_tx_switch(uart_num); // Set new baud rate - uart_div_modify(uart_num, (((uint64_t) get_apb_freq()) << 4) / uart_baud); + uart_div_modify(uart_num, (APB_CLK_FREQ << 4) / uart_baud); // If console is attached to UART1 or if non-default pins are used, // need to reconfigure pins using GPIO matrix if (uart_num != 0 || uart_tx_gpio != 1 || uart_rx_gpio != 3) { @@ -727,3 +716,12 @@ static void uart_console_configure(void) #endif // CONFIG_CONSOLE_UART_CUSTOM #endif // CONFIG_CONSOLE_UART_NONE } + +/* empty rtc_printf implementation, to work with librtc + linking. Can be removed once -lrtc is removed from bootloader's + main component.mk. +*/ +int rtc_printf(void) +{ + return 0; +} diff --git a/components/bootloader/src/main/component.mk b/components/bootloader/src/main/component.mk index e98545fc47..9bcc5352a0 100644 --- a/components/bootloader/src/main/component.mk +++ b/components/bootloader/src/main/component.mk @@ -10,3 +10,11 @@ LINKER_SCRIPTS := esp32.bootloader.ld $(IDF_PATH)/components/esp32/ld/esp32.rom. COMPONENT_ADD_LDFLAGS := -L $(COMPONENT_PATH) -lmain $(addprefix -T ,$(LINKER_SCRIPTS)) COMPONENT_ADD_LINKER_DEPS := $(LINKER_SCRIPTS) + +ifdef IS_BOOTLOADER_BUILD +# following lines are a workaround to link librtc into the +# bootloader, until clock setting code is in a source-based esp-idf +# component. See also rtc_printf() in bootloader_start.c +COMPONENT_ADD_LDFLAGS += -L $(IDF_PATH)/components/esp32/lib/ -lrtc +COMPONENT_EXTRA_INCLUDES += $(IDF_PATH)/components/esp32/ +endif diff --git a/components/esp32/system_api.c b/components/esp32/system_api.c index e8013441b1..63a28de533 100644 --- a/components/esp32/system_api.c +++ b/components/esp32/system_api.c @@ -29,6 +29,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/xtensa_api.h" +#include "rtc.h" static const char* TAG = "system_api"; @@ -119,6 +120,9 @@ void IRAM_ATTR esp_restart(void) DPORT_TIMERS_RST | DPORT_SPI_RST_1 | DPORT_UART_RST); REG_WRITE(DPORT_PERIP_RST_EN_REG, 0); + // Set CPU back to XTAL source, no PLL, same as hard reset + rtc_set_cpu_freq(CPU_XTAL); + // Reset CPUs if (core_id == 0) { // Running on PRO CPU: APP CPU is stalled. Can reset both CPUs. From 83442526e0c21b32e2501f2c2c0e238670260037 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Jan 2017 16:16:41 +1100 Subject: [PATCH 048/167] bootloader: Allow custom baud rate on UART 0 --- components/bootloader/src/main/bootloader_start.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/bootloader/src/main/bootloader_start.c b/components/bootloader/src/main/bootloader_start.c index cd845b0b92..23434a11d6 100644 --- a/components/bootloader/src/main/bootloader_start.c +++ b/components/bootloader/src/main/bootloader_start.c @@ -680,6 +680,8 @@ static void uart_console_configure(void) ets_install_putc1(NULL); ets_install_putc2(NULL); #else // CONFIG_CONSOLE_UART_NONE + const int uart_num = CONFIG_CONSOLE_UART_NUM; + uartAttach(); ets_install_uart_printf(); @@ -689,15 +691,11 @@ static void uart_console_configure(void) #if CONFIG_CONSOLE_UART_CUSTOM // Some constants to make the following code less upper-case - const int uart_num = CONFIG_CONSOLE_UART_NUM; - const int uart_baud = CONFIG_CONSOLE_UART_BAUDRATE; const int uart_tx_gpio = CONFIG_CONSOLE_UART_TX_GPIO; const int uart_rx_gpio = CONFIG_CONSOLE_UART_RX_GPIO; // Switch to the new UART (this just changes UART number used for // ets_printf in ROM code). uart_tx_switch(uart_num); - // Set new baud rate - uart_div_modify(uart_num, (APB_CLK_FREQ << 4) / uart_baud); // If console is attached to UART1 or if non-default pins are used, // need to reconfigure pins using GPIO matrix if (uart_num != 0 || uart_tx_gpio != 1 || uart_rx_gpio != 3) { @@ -714,6 +712,11 @@ static void uart_console_configure(void) gpio_matrix_in(uart_rx_gpio, rx_idx, 0); } #endif // CONFIG_CONSOLE_UART_CUSTOM + + // Set configured UART console baud rate + const int uart_baud = CONFIG_CONSOLE_UART_BAUDRATE; + uart_div_modify(uart_num, (APB_CLK_FREQ << 4) / uart_baud); + #endif // CONFIG_CONSOLE_UART_NONE } From 3922ce47b26ea5eaefbd07b47c7c92434c92901a Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Jan 2017 15:36:04 +1100 Subject: [PATCH 049/167] bootloader: Enable early boot RNG entropy source This reverts commit ceb85669702f01f3e30ddfcdd841dfe64e1a597c. --- .../bootloader/src/main/bootloader_start.c | 9 +- .../include_priv/bootloader_random.h | 24 ++ .../src/bootloader_random.c | 110 ++++++- .../bootloader_support/src/flash_encrypt.c | 17 +- .../bootloader_support/src/secure_boot.c | 19 +- components/esp32/include/soc/soc.h | 1 + components/esp32/include/soc/syscon_reg.h | 294 ++++++++++++++++++ components/esp32/include/soc/syscon_struct.h | 120 +++++++ docs/security/flash-encryption.rst | 3 - docs/security/secure-boot.rst | 2 - 10 files changed, 569 insertions(+), 30 deletions(-) create mode 100644 components/esp32/include/soc/syscon_reg.h create mode 100644 components/esp32/include/soc/syscon_struct.h diff --git a/components/bootloader/src/main/bootloader_start.c b/components/bootloader/src/main/bootloader_start.c index 23434a11d6..dfcfb0a081 100644 --- a/components/bootloader/src/main/bootloader_start.c +++ b/components/bootloader/src/main/bootloader_start.c @@ -25,6 +25,7 @@ #include "rom/rtc.h" #include "rom/uart.h" #include "rom/gpio.h" +#include "rom/secure_boot.h" #include "soc/soc.h" #include "soc/cpu.h" @@ -42,7 +43,7 @@ #include "esp_flash_encrypt.h" #include "esp_flash_partitions.h" #include "bootloader_flash.h" - +#include "bootloader_random.h" #include "bootloader_config.h" #include "rtc.h" @@ -259,6 +260,9 @@ void bootloader_main() REG_CLR_BIT( TIMG_WDTCONFIG0_REG(0), TIMG_WDT_FLASHBOOT_MOD_EN ); SPIUnlock(); + ESP_LOGI(TAG, "Enabling RNG early entropy source..."); + bootloader_random_enable(); + if(esp_image_load_header(0x1000, true, &fhdr) != ESP_OK) { ESP_LOGE(TAG, "failed to load bootloader header!"); return; @@ -370,6 +374,9 @@ void bootloader_main() } #endif + ESP_LOGI(TAG, "Disabling RNG early entropy source..."); + bootloader_random_disable(); + // copy loaded segments to RAM, set up caches for mapped segments, and start application ESP_LOGI(TAG, "Loading app partition at offset %08x", load_part_pos); unpack_load_app(&load_part_pos); diff --git a/components/bootloader_support/include_priv/bootloader_random.h b/components/bootloader_support/include_priv/bootloader_random.h index 86d42e31d9..bb3b2a81dd 100644 --- a/components/bootloader_support/include_priv/bootloader_random.h +++ b/components/bootloader_support/include_priv/bootloader_random.h @@ -16,6 +16,30 @@ #include +/** + * @brief Enable early entropy source for RNG + * + * Uses the SAR ADC to feed entropy into the HWRNG. The ADC is put + * into a test mode that reads the 1.1V internal reference source and + * feeds the LSB of data into the HWRNG. + * + * Can also be used from app code early during operation, if entropy + * is required before WiFi stack is initialised. Call this function + * from app code only if WiFi/BT are not yet enabled and I2S and SAR + * ADC are not in use. + * + * Call bootloader_random_disable() when done. + */ +void bootloader_random_enable(void); + +/** + * @brief Disable early entropy source for RNG + * + * Disables SAR ADC source and resets the I2S hardware. + * + */ +void bootloader_random_disable(void); + /** * @brief Fill buffer with 'length' random bytes * diff --git a/components/bootloader_support/src/bootloader_random.c b/components/bootloader_support/src/bootloader_random.c index fd780c4b7b..c8b6c24b1f 100644 --- a/components/bootloader_support/src/bootloader_random.c +++ b/components/bootloader_support/src/bootloader_random.c @@ -12,37 +12,45 @@ // See the License for the specific language governing permissions and // limitations under the License. #include "bootloader_random.h" +#include "soc/cpu.h" #include "soc/wdev_reg.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/sens_reg.h" +#include "soc/syscon_reg.h" +#include "soc/dport_reg.h" +#include "soc/i2s_reg.h" +#include "esp_log.h" #ifndef BOOTLOADER_BUILD #include "esp_system.h" #endif +const char *TAG = "boot_rng"; + void bootloader_fill_random(void *buffer, size_t length) { uint8_t *buffer_bytes = (uint8_t *)buffer; uint32_t random; - - /* TODO: enable HW RNG clock - - Until this clock is enabled, this is not secure - */ +#ifdef BOOTLOADER_BUILD + uint32_t start, now; +#endif for (int i = 0; i < length; i++) { if (i == 0 || i % 4 == 0) { /* redundant check is for a compiler warning */ #ifdef BOOTLOADER_BUILD - /* HW RNG generates 32 bits entropy per 16 APB cycles, - in bootloader CPU clock == APB clock. + /* in bootloader with ADC feeding HWRNG, we accumulate 1 + bit of entropy per 40 APB cycles (==80 CPU cycles.) - We are being conservative here and waiting at least - that long, as loop shift overhead, etc will add more - cycles. + To avoid reading the entire RNG hardware state out + as-is, we repeatedly read the RNG register and XOR all + values. */ - asm volatile("nop; nop; nop; nop;"); - asm volatile("nop; nop; nop; nop;"); - asm volatile("nop; nop; nop; nop;"); - asm volatile("nop; nop; nop; nop;"); random = REG_READ(WDEV_RND_REG); + RSR(CCOUNT, start); + do { + random ^= REG_READ(WDEV_RND_REG); + RSR(CCOUNT, now); + } while(now - start < 80*32*2); /* extra factor of 2 is precautionary */ #else random = esp_random(); #endif @@ -51,3 +59,77 @@ void bootloader_fill_random(void *buffer, size_t length) buffer_bytes[i] = random >> ((i % 4) * 8); } } + +void bootloader_random_enable(void) +{ + /* Enable SAR ADC in test mode to feed ADC readings of the 1.1V + reference via I2S into the RNG entropy input. + + Note: I2S requires the PLL to be running, so the call to rtc_set_cpu_freq(CPU_80M) + in early bootloader startup must have been made. + */ + SET_PERI_REG_BITS(RTC_CNTL_TEST_MUX_REG, RTC_CNTL_DTEST_RTC, 2, RTC_CNTL_DTEST_RTC_S); + SET_PERI_REG_MASK(RTC_CNTL_TEST_MUX_REG, RTC_CNTL_ENT_RTC); + SET_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_SAR2_EN_TEST); + + SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_I2S0_CLK_EN); + CLEAR_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_ULP_CP_FORCE_START_TOP); + CLEAR_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_ULP_CP_START_TOP); + // Test pattern configuration byte 0xAD: + //--[7:4] channel_sel: 10-->en_test + //--[3:2] bit_width : 3-->12bit + //--[1:0] atten : 1-->3dB attenuation + WRITE_PERI_REG(SYSCON_SARADC_SAR2_PATT_TAB1_REG, 0xADADADAD); + WRITE_PERI_REG(SYSCON_SARADC_SAR2_PATT_TAB2_REG, 0xADADADAD); + WRITE_PERI_REG(SYSCON_SARADC_SAR2_PATT_TAB3_REG, 0xADADADAD); + WRITE_PERI_REG(SYSCON_SARADC_SAR2_PATT_TAB4_REG, 0xADADADAD); + + SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_MASK(SENS_SAR_READ_CTRL_REG, SENS_SAR1_DIG_FORCE); + SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DIG_FORCE); + SET_PERI_REG_MASK(SYSCON_SARADC_CTRL_REG, SYSCON_SARADC_SAR2_MUX); + SET_PERI_REG_BITS(SYSCON_SARADC_CTRL_REG, SYSCON_SARADC_SAR_CLK_DIV, 4, SYSCON_SARADC_SAR_CLK_DIV_S); + + SET_PERI_REG_BITS(SYSCON_SARADC_FSM_REG, SYSCON_SARADC_RSTB_WAIT, 8, SYSCON_SARADC_RSTB_WAIT_S); /* was 1 */ + SET_PERI_REG_BITS(SYSCON_SARADC_FSM_REG, SYSCON_SARADC_START_WAIT, 10, SYSCON_SARADC_START_WAIT_S); + SET_PERI_REG_BITS(SYSCON_SARADC_CTRL_REG, SYSCON_SARADC_WORK_MODE, 0, SYSCON_SARADC_WORK_MODE_S); + SET_PERI_REG_MASK(SYSCON_SARADC_CTRL_REG, SYSCON_SARADC_SAR_SEL); + CLEAR_PERI_REG_MASK(SYSCON_SARADC_CTRL_REG, SYSCON_SARADC_DATA_SAR_SEL); + + SET_PERI_REG_BITS(I2S_SAMPLE_RATE_CONF_REG(0), I2S_RX_BCK_DIV_NUM, 20, I2S_RX_BCK_DIV_NUM_S); + + SET_PERI_REG_MASK(SYSCON_SARADC_CTRL_REG,SYSCON_SARADC_DATA_TO_I2S); + + CLEAR_PERI_REG_MASK(I2S_CONF2_REG(0), I2S_CAMERA_EN); + SET_PERI_REG_MASK(I2S_CONF2_REG(0), I2S_LCD_EN); + SET_PERI_REG_MASK(I2S_CONF2_REG(0), I2S_DATA_ENABLE); + SET_PERI_REG_MASK(I2S_CONF2_REG(0), I2S_DATA_ENABLE_TEST_EN); + SET_PERI_REG_MASK(I2S_CONF_REG(0), I2S_RX_START); +} + +void bootloader_random_disable(void) +{ + /* Disable i2s clock */ + CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_I2S0_CLK_EN); + + + /* Reset some i2s configuration (possibly redundant as we reset entire + I2S peripheral further down). */ + CLEAR_PERI_REG_MASK(I2S_CONF2_REG(0), I2S_CAMERA_EN); + CLEAR_PERI_REG_MASK(I2S_CONF2_REG(0), I2S_LCD_EN); + CLEAR_PERI_REG_MASK(I2S_CONF2_REG(0), I2S_DATA_ENABLE_TEST_EN); + CLEAR_PERI_REG_MASK(I2S_CONF2_REG(0), I2S_DATA_ENABLE); + CLEAR_PERI_REG_MASK(I2S_CONF_REG(0), I2S_RX_START); + + /* Restore SYSCON mode registers */ + CLEAR_PERI_REG_MASK(SENS_SAR_READ_CTRL_REG, SENS_SAR1_DIG_FORCE); + CLEAR_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DIG_FORCE); + CLEAR_PERI_REG_MASK(SYSCON_SARADC_CTRL_REG, SYSCON_SARADC_SAR2_MUX | SYSCON_SARADC_SAR_SEL); + + /* Restore SAR ADC mode */ + CLEAR_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_SAR2_EN_TEST); + + /* Reset i2s peripheral */ + SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_I2S0_RST); + CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_I2S0_RST); +} diff --git a/components/bootloader_support/src/flash_encrypt.c b/components/bootloader_support/src/flash_encrypt.c index f4fd7e783d..8b9ca60cd1 100644 --- a/components/bootloader_support/src/flash_encrypt.c +++ b/components/bootloader_support/src/flash_encrypt.c @@ -78,12 +78,19 @@ static esp_err_t initialise_flash_encryption(void) && REG_READ(EFUSE_BLK1_RDATA5_REG) == 0 && REG_READ(EFUSE_BLK1_RDATA6_REG) == 0 && REG_READ(EFUSE_BLK1_RDATA7_REG) == 0) { + ESP_LOGI(TAG, "Generating new flash encryption key..."); + uint32_t buf[8]; + bootloader_fill_random(buf, sizeof(buf)); + for (int i = 0; i < 8; i++) { + ESP_LOGV(TAG, "EFUSE_BLK1_WDATA%d_REG = 0x%08x", i, buf[i]); + REG_WRITE(EFUSE_BLK1_WDATA0_REG + 4*i, buf[i]); + } + bzero(buf, sizeof(buf)); + esp_efuse_burn_new_values(); - /* On-device key generation is temporarily disabled, until - * RNG operation during bootloader is qualified. - * See docs/security/flash-encryption.rst for details. */ - ESP_LOGE(TAG, "On-device key generation is not yet available."); - return ESP_ERR_NOT_SUPPORTED; + ESP_LOGI(TAG, "Read & write protecting new key..."); + REG_WRITE(EFUSE_BLK0_WDATA0_REG, EFUSE_WR_DIS_BLK1 | EFUSE_RD_DIS_BLK1); + esp_efuse_burn_new_values(); } else { if(!(efuse_key_read_protected && efuse_key_write_protected)) { diff --git a/components/bootloader_support/src/secure_boot.c b/components/bootloader_support/src/secure_boot.c index c5ae1f19ea..0230e85ad5 100644 --- a/components/bootloader_support/src/secure_boot.c +++ b/components/bootloader_support/src/secure_boot.c @@ -130,12 +130,21 @@ esp_err_t esp_secure_boot_permanently_enable(void) { && REG_READ(EFUSE_BLK2_RDATA5_REG) == 0 && REG_READ(EFUSE_BLK2_RDATA6_REG) == 0 && REG_READ(EFUSE_BLK2_RDATA7_REG) == 0) { + ESP_LOGI(TAG, "Generating new secure boot key..."); + uint32_t buf[8]; + bootloader_fill_random(buf, sizeof(buf)); + for (int i = 0; i < 8; i++) { + ESP_LOGV(TAG, "EFUSE_BLK2_WDATA%d_REG = 0x%08x", i, buf[i]); + REG_WRITE(EFUSE_BLK2_WDATA0_REG + 4*i, buf[i]); + } + bzero(buf, sizeof(buf)); + burn_efuses(); + ESP_LOGI(TAG, "Read & write protecting new key..."); + REG_WRITE(EFUSE_BLK0_WDATA0_REG, EFUSE_WR_DIS_BLK2 | EFUSE_RD_DIS_BLK2); + burn_efuses(); + efuse_key_read_protected = true; + efuse_key_write_protected = true; - /* On-device key generation is temporarily disabled, until - * RNG operation during bootloader is qualified. - * See docs/security/secure-boot.rst for details. */ - ESP_LOGE(TAG, "On-device key generation is not yet available."); - return ESP_ERR_NOT_SUPPORTED; } else { ESP_LOGW(TAG, "Using pre-loaded secure boot key in EFUSE block 2"); } diff --git a/components/esp32/include/soc/soc.h b/components/esp32/include/soc/soc.h index b93bae7298..94d3e1f3e5 100755 --- a/components/esp32/include/soc/soc.h +++ b/components/esp32/include/soc/soc.h @@ -158,6 +158,7 @@ #define DR_REG_RTCMEM0_BASE 0x3ff61000 #define DR_REG_RTCMEM1_BASE 0x3ff62000 #define DR_REG_RTCMEM2_BASE 0x3ff63000 +#define DR_REG_SYSCON_BASE 0x3ff66000 #define DR_REG_HINF_BASE 0x3ff4B000 #define DR_REG_UHCI1_BASE 0x3ff4C000 #define DR_REG_I2S_BASE 0x3ff4F000 diff --git a/components/esp32/include/soc/syscon_reg.h b/components/esp32/include/soc/syscon_reg.h new file mode 100644 index 0000000000..5012b27e57 --- /dev/null +++ b/components/esp32/include/soc/syscon_reg.h @@ -0,0 +1,294 @@ +// 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 _SOC_SYSCON_REG_H_ +#define _SOC_SYSCON_REG_H_ + +#include "soc.h" +#define SYSCON_SYSCLK_CONF_REG (DR_REG_SYSCON_BASE + 0x0) +/* SYSCON_QUICK_CLK_CHNG : R/W ;bitpos:[13] ;default: 1'b1 ; */ +/*description: */ +#define SYSCON_QUICK_CLK_CHNG (BIT(13)) +#define SYSCON_QUICK_CLK_CHNG_M (BIT(13)) +#define SYSCON_QUICK_CLK_CHNG_V 0x1 +#define SYSCON_QUICK_CLK_CHNG_S 13 +/* SYSCON_RST_TICK_CNT : R/W ;bitpos:[12] ;default: 1'b0 ; */ +/*description: */ +#define SYSCON_RST_TICK_CNT (BIT(12)) +#define SYSCON_RST_TICK_CNT_M (BIT(12)) +#define SYSCON_RST_TICK_CNT_V 0x1 +#define SYSCON_RST_TICK_CNT_S 12 +/* SYSCON_CLK_EN : R/W ;bitpos:[11] ;default: 1'b0 ; */ +/*description: */ +#define SYSCON_CLK_EN (BIT(11)) +#define SYSCON_CLK_EN_M (BIT(11)) +#define SYSCON_CLK_EN_V 0x1 +#define SYSCON_CLK_EN_S 11 +/* SYSCON_CLK_320M_EN : R/W ;bitpos:[10] ;default: 1'b0 ; */ +/*description: */ +#define SYSCON_CLK_320M_EN (BIT(10)) +#define SYSCON_CLK_320M_EN_M (BIT(10)) +#define SYSCON_CLK_320M_EN_V 0x1 +#define SYSCON_CLK_320M_EN_S 10 +/* SYSCON_PRE_DIV_CNT : R/W ;bitpos:[9:0] ;default: 10'h0 ; */ +/*description: */ +#define SYSCON_PRE_DIV_CNT 0x000003FF +#define SYSCON_PRE_DIV_CNT_M ((SYSCON_PRE_DIV_CNT_V)<<(SYSCON_PRE_DIV_CNT_S)) +#define SYSCON_PRE_DIV_CNT_V 0x3FF +#define SYSCON_PRE_DIV_CNT_S 0 + +#define SYSCON_XTAL_TICK_CONF_REG (DR_REG_SYSCON_BASE + 0x4) +/* SYSCON_XTAL_TICK_NUM : R/W ;bitpos:[7:0] ;default: 8'd39 ; */ +/*description: */ +#define SYSCON_XTAL_TICK_NUM 0x000000FF +#define SYSCON_XTAL_TICK_NUM_M ((SYSCON_XTAL_TICK_NUM_V)<<(SYSCON_XTAL_TICK_NUM_S)) +#define SYSCON_XTAL_TICK_NUM_V 0xFF +#define SYSCON_XTAL_TICK_NUM_S 0 + +#define SYSCON_PLL_TICK_CONF_REG (DR_REG_SYSCON_BASE + 0x8) +/* SYSCON_PLL_TICK_NUM : R/W ;bitpos:[7:0] ;default: 8'd79 ; */ +/*description: */ +#define SYSCON_PLL_TICK_NUM 0x000000FF +#define SYSCON_PLL_TICK_NUM_M ((SYSCON_PLL_TICK_NUM_V)<<(SYSCON_PLL_TICK_NUM_S)) +#define SYSCON_PLL_TICK_NUM_V 0xFF +#define SYSCON_PLL_TICK_NUM_S 0 + +#define SYSCON_CK8M_TICK_CONF_REG (DR_REG_SYSCON_BASE + 0xC) +/* SYSCON_CK8M_TICK_NUM : R/W ;bitpos:[7:0] ;default: 8'd11 ; */ +/*description: */ +#define SYSCON_CK8M_TICK_NUM 0x000000FF +#define SYSCON_CK8M_TICK_NUM_M ((SYSCON_CK8M_TICK_NUM_V)<<(SYSCON_CK8M_TICK_NUM_S)) +#define SYSCON_CK8M_TICK_NUM_V 0xFF +#define SYSCON_CK8M_TICK_NUM_S 0 + +#define SYSCON_SARADC_CTRL_REG (DR_REG_SYSCON_BASE + 0x10) +/* SYSCON_SARADC_DATA_TO_I2S : R/W ;bitpos:[26] ;default: 1'b0 ; */ +/*description: 1: I2S input data is from SAR ADC (for DMA) 0: I2S input data + is from GPIO matrix*/ +#define SYSCON_SARADC_DATA_TO_I2S (BIT(26)) +#define SYSCON_SARADC_DATA_TO_I2S_M (BIT(26)) +#define SYSCON_SARADC_DATA_TO_I2S_V 0x1 +#define SYSCON_SARADC_DATA_TO_I2S_S 26 +/* SYSCON_SARADC_DATA_SAR_SEL : R/W ;bitpos:[25] ;default: 1'b0 ; */ +/*description: 1: sar_sel will be coded by the MSB of the 16-bit output data + in this case the resolution should not be larger than 11 bits.*/ +#define SYSCON_SARADC_DATA_SAR_SEL (BIT(25)) +#define SYSCON_SARADC_DATA_SAR_SEL_M (BIT(25)) +#define SYSCON_SARADC_DATA_SAR_SEL_V 0x1 +#define SYSCON_SARADC_DATA_SAR_SEL_S 25 +/* SYSCON_SARADC_SAR2_PATT_P_CLEAR : R/W ;bitpos:[24] ;default: 1'd0 ; */ +/*description: clear the pointer of pattern table for DIG ADC2 CTRL*/ +#define SYSCON_SARADC_SAR2_PATT_P_CLEAR (BIT(24)) +#define SYSCON_SARADC_SAR2_PATT_P_CLEAR_M (BIT(24)) +#define SYSCON_SARADC_SAR2_PATT_P_CLEAR_V 0x1 +#define SYSCON_SARADC_SAR2_PATT_P_CLEAR_S 24 +/* SYSCON_SARADC_SAR1_PATT_P_CLEAR : R/W ;bitpos:[23] ;default: 1'd0 ; */ +/*description: clear the pointer of pattern table for DIG ADC1 CTRL*/ +#define SYSCON_SARADC_SAR1_PATT_P_CLEAR (BIT(23)) +#define SYSCON_SARADC_SAR1_PATT_P_CLEAR_M (BIT(23)) +#define SYSCON_SARADC_SAR1_PATT_P_CLEAR_V 0x1 +#define SYSCON_SARADC_SAR1_PATT_P_CLEAR_S 23 +/* SYSCON_SARADC_SAR2_PATT_LEN : R/W ;bitpos:[22:19] ;default: 4'd15 ; */ +/*description: 0 ~ 15 means length 1 ~ 16*/ +#define SYSCON_SARADC_SAR2_PATT_LEN 0x0000000F +#define SYSCON_SARADC_SAR2_PATT_LEN_M ((SYSCON_SARADC_SAR2_PATT_LEN_V)<<(SYSCON_SARADC_SAR2_PATT_LEN_S)) +#define SYSCON_SARADC_SAR2_PATT_LEN_V 0xF +#define SYSCON_SARADC_SAR2_PATT_LEN_S 19 +/* SYSCON_SARADC_SAR1_PATT_LEN : R/W ;bitpos:[18:15] ;default: 4'd15 ; */ +/*description: 0 ~ 15 means length 1 ~ 16*/ +#define SYSCON_SARADC_SAR1_PATT_LEN 0x0000000F +#define SYSCON_SARADC_SAR1_PATT_LEN_M ((SYSCON_SARADC_SAR1_PATT_LEN_V)<<(SYSCON_SARADC_SAR1_PATT_LEN_S)) +#define SYSCON_SARADC_SAR1_PATT_LEN_V 0xF +#define SYSCON_SARADC_SAR1_PATT_LEN_S 15 +/* SYSCON_SARADC_SAR_CLK_DIV : R/W ;bitpos:[14:7] ;default: 8'd4 ; */ +/*description: SAR clock divider*/ +#define SYSCON_SARADC_SAR_CLK_DIV 0x000000FF +#define SYSCON_SARADC_SAR_CLK_DIV_M ((SYSCON_SARADC_SAR_CLK_DIV_V)<<(SYSCON_SARADC_SAR_CLK_DIV_S)) +#define SYSCON_SARADC_SAR_CLK_DIV_V 0xFF +#define SYSCON_SARADC_SAR_CLK_DIV_S 7 +/* SYSCON_SARADC_SAR_CLK_GATED : R/W ;bitpos:[6] ;default: 1'b1 ; */ +/*description: */ +#define SYSCON_SARADC_SAR_CLK_GATED (BIT(6)) +#define SYSCON_SARADC_SAR_CLK_GATED_M (BIT(6)) +#define SYSCON_SARADC_SAR_CLK_GATED_V 0x1 +#define SYSCON_SARADC_SAR_CLK_GATED_S 6 +/* SYSCON_SARADC_SAR_SEL : R/W ;bitpos:[5] ;default: 1'd0 ; */ +/*description: 0: SAR1 1: SAR2 only work for single SAR mode*/ +#define SYSCON_SARADC_SAR_SEL (BIT(5)) +#define SYSCON_SARADC_SAR_SEL_M (BIT(5)) +#define SYSCON_SARADC_SAR_SEL_V 0x1 +#define SYSCON_SARADC_SAR_SEL_S 5 +/* SYSCON_SARADC_WORK_MODE : R/W ;bitpos:[4:3] ;default: 2'd0 ; */ +/*description: 0: single mode 1: double mode 2: alternate mode*/ +#define SYSCON_SARADC_WORK_MODE 0x00000003 +#define SYSCON_SARADC_WORK_MODE_M ((SYSCON_SARADC_WORK_MODE_V)<<(SYSCON_SARADC_WORK_MODE_S)) +#define SYSCON_SARADC_WORK_MODE_V 0x3 +#define SYSCON_SARADC_WORK_MODE_S 3 +/* SYSCON_SARADC_SAR2_MUX : R/W ;bitpos:[2] ;default: 1'd0 ; */ +/*description: 1: SAR ADC2 is controlled by DIG ADC2 CTRL 0: SAR ADC2 is controlled + by PWDET CTRL*/ +#define SYSCON_SARADC_SAR2_MUX (BIT(2)) +#define SYSCON_SARADC_SAR2_MUX_M (BIT(2)) +#define SYSCON_SARADC_SAR2_MUX_V 0x1 +#define SYSCON_SARADC_SAR2_MUX_S 2 +/* SYSCON_SARADC_START : R/W ;bitpos:[1] ;default: 1'd0 ; */ +/*description: */ +#define SYSCON_SARADC_START (BIT(1)) +#define SYSCON_SARADC_START_M (BIT(1)) +#define SYSCON_SARADC_START_V 0x1 +#define SYSCON_SARADC_START_S 1 +/* SYSCON_SARADC_START_FORCE : R/W ;bitpos:[0] ;default: 1'd0 ; */ +/*description: */ +#define SYSCON_SARADC_START_FORCE (BIT(0)) +#define SYSCON_SARADC_START_FORCE_M (BIT(0)) +#define SYSCON_SARADC_START_FORCE_V 0x1 +#define SYSCON_SARADC_START_FORCE_S 0 + +#define SYSCON_SARADC_CTRL2_REG (DR_REG_SYSCON_BASE + 0x14) +/* SYSCON_SARADC_SAR2_INV : R/W ;bitpos:[10] ;default: 1'd0 ; */ +/*description: 1: data to DIG ADC2 CTRL is inverted otherwise not*/ +#define SYSCON_SARADC_SAR2_INV (BIT(10)) +#define SYSCON_SARADC_SAR2_INV_M (BIT(10)) +#define SYSCON_SARADC_SAR2_INV_V 0x1 +#define SYSCON_SARADC_SAR2_INV_S 10 +/* SYSCON_SARADC_SAR1_INV : R/W ;bitpos:[9] ;default: 1'd0 ; */ +/*description: 1: data to DIG ADC1 CTRL is inverted otherwise not*/ +#define SYSCON_SARADC_SAR1_INV (BIT(9)) +#define SYSCON_SARADC_SAR1_INV_M (BIT(9)) +#define SYSCON_SARADC_SAR1_INV_V 0x1 +#define SYSCON_SARADC_SAR1_INV_S 9 +/* SYSCON_SARADC_MAX_MEAS_NUM : R/W ;bitpos:[8:1] ;default: 8'd255 ; */ +/*description: max conversion number*/ +#define SYSCON_SARADC_MAX_MEAS_NUM 0x000000FF +#define SYSCON_SARADC_MAX_MEAS_NUM_M ((SYSCON_SARADC_MAX_MEAS_NUM_V)<<(SYSCON_SARADC_MAX_MEAS_NUM_S)) +#define SYSCON_SARADC_MAX_MEAS_NUM_V 0xFF +#define SYSCON_SARADC_MAX_MEAS_NUM_S 1 +/* SYSCON_SARADC_MEAS_NUM_LIMIT : R/W ;bitpos:[0] ;default: 1'd0 ; */ +/*description: */ +#define SYSCON_SARADC_MEAS_NUM_LIMIT (BIT(0)) +#define SYSCON_SARADC_MEAS_NUM_LIMIT_M (BIT(0)) +#define SYSCON_SARADC_MEAS_NUM_LIMIT_V 0x1 +#define SYSCON_SARADC_MEAS_NUM_LIMIT_S 0 + +#define SYSCON_SARADC_FSM_REG (DR_REG_SYSCON_BASE + 0x18) +/* SYSCON_SARADC_SAMPLE_CYCLE : R/W ;bitpos:[31:24] ;default: 8'd2 ; */ +/*description: sample cycles*/ +#define SYSCON_SARADC_SAMPLE_CYCLE 0x000000FF +#define SYSCON_SARADC_SAMPLE_CYCLE_M ((SYSCON_SARADC_SAMPLE_CYCLE_V)<<(SYSCON_SARADC_SAMPLE_CYCLE_S)) +#define SYSCON_SARADC_SAMPLE_CYCLE_V 0xFF +#define SYSCON_SARADC_SAMPLE_CYCLE_S 24 +/* SYSCON_SARADC_START_WAIT : R/W ;bitpos:[23:16] ;default: 8'd8 ; */ +/*description: */ +#define SYSCON_SARADC_START_WAIT 0x000000FF +#define SYSCON_SARADC_START_WAIT_M ((SYSCON_SARADC_START_WAIT_V)<<(SYSCON_SARADC_START_WAIT_S)) +#define SYSCON_SARADC_START_WAIT_V 0xFF +#define SYSCON_SARADC_START_WAIT_S 16 +/* SYSCON_SARADC_STANDBY_WAIT : R/W ;bitpos:[15:8] ;default: 8'd255 ; */ +/*description: */ +#define SYSCON_SARADC_STANDBY_WAIT 0x000000FF +#define SYSCON_SARADC_STANDBY_WAIT_M ((SYSCON_SARADC_STANDBY_WAIT_V)<<(SYSCON_SARADC_STANDBY_WAIT_S)) +#define SYSCON_SARADC_STANDBY_WAIT_V 0xFF +#define SYSCON_SARADC_STANDBY_WAIT_S 8 +/* SYSCON_SARADC_RSTB_WAIT : R/W ;bitpos:[7:0] ;default: 8'd8 ; */ +/*description: */ +#define SYSCON_SARADC_RSTB_WAIT 0x000000FF +#define SYSCON_SARADC_RSTB_WAIT_M ((SYSCON_SARADC_RSTB_WAIT_V)<<(SYSCON_SARADC_RSTB_WAIT_S)) +#define SYSCON_SARADC_RSTB_WAIT_V 0xFF +#define SYSCON_SARADC_RSTB_WAIT_S 0 + +#define SYSCON_SARADC_SAR1_PATT_TAB1_REG (DR_REG_SYSCON_BASE + 0x1C) +/* SYSCON_SARADC_SAR1_PATT_TAB1 : R/W ;bitpos:[31:0] ;default: 32'hf0f0f0f ; */ +/*description: item 0 ~ 3 for pattern table 1 (each item one byte)*/ +#define SYSCON_SARADC_SAR1_PATT_TAB1 0xFFFFFFFF +#define SYSCON_SARADC_SAR1_PATT_TAB1_M ((SYSCON_SARADC_SAR1_PATT_TAB1_V)<<(SYSCON_SARADC_SAR1_PATT_TAB1_S)) +#define SYSCON_SARADC_SAR1_PATT_TAB1_V 0xFFFFFFFF +#define SYSCON_SARADC_SAR1_PATT_TAB1_S 0 + +#define SYSCON_SARADC_SAR1_PATT_TAB2_REG (DR_REG_SYSCON_BASE + 0x20) +/* SYSCON_SARADC_SAR1_PATT_TAB2 : R/W ;bitpos:[31:0] ;default: 32'hf0f0f0f ; */ +/*description: Item 4 ~ 7 for pattern table 1 (each item one byte)*/ +#define SYSCON_SARADC_SAR1_PATT_TAB2 0xFFFFFFFF +#define SYSCON_SARADC_SAR1_PATT_TAB2_M ((SYSCON_SARADC_SAR1_PATT_TAB2_V)<<(SYSCON_SARADC_SAR1_PATT_TAB2_S)) +#define SYSCON_SARADC_SAR1_PATT_TAB2_V 0xFFFFFFFF +#define SYSCON_SARADC_SAR1_PATT_TAB2_S 0 + +#define SYSCON_SARADC_SAR1_PATT_TAB3_REG (DR_REG_SYSCON_BASE + 0x24) +/* SYSCON_SARADC_SAR1_PATT_TAB3 : R/W ;bitpos:[31:0] ;default: 32'hf0f0f0f ; */ +/*description: Item 8 ~ 11 for pattern table 1 (each item one byte)*/ +#define SYSCON_SARADC_SAR1_PATT_TAB3 0xFFFFFFFF +#define SYSCON_SARADC_SAR1_PATT_TAB3_M ((SYSCON_SARADC_SAR1_PATT_TAB3_V)<<(SYSCON_SARADC_SAR1_PATT_TAB3_S)) +#define SYSCON_SARADC_SAR1_PATT_TAB3_V 0xFFFFFFFF +#define SYSCON_SARADC_SAR1_PATT_TAB3_S 0 + +#define SYSCON_SARADC_SAR1_PATT_TAB4_REG (DR_REG_SYSCON_BASE + 0x28) +/* SYSCON_SARADC_SAR1_PATT_TAB4 : R/W ;bitpos:[31:0] ;default: 32'hf0f0f0f ; */ +/*description: Item 12 ~ 15 for pattern table 1 (each item one byte)*/ +#define SYSCON_SARADC_SAR1_PATT_TAB4 0xFFFFFFFF +#define SYSCON_SARADC_SAR1_PATT_TAB4_M ((SYSCON_SARADC_SAR1_PATT_TAB4_V)<<(SYSCON_SARADC_SAR1_PATT_TAB4_S)) +#define SYSCON_SARADC_SAR1_PATT_TAB4_V 0xFFFFFFFF +#define SYSCON_SARADC_SAR1_PATT_TAB4_S 0 + +#define SYSCON_SARADC_SAR2_PATT_TAB1_REG (DR_REG_SYSCON_BASE + 0x2C) +/* SYSCON_SARADC_SAR2_PATT_TAB1 : R/W ;bitpos:[31:0] ;default: 32'hf0f0f0f ; */ +/*description: item 0 ~ 3 for pattern table 2 (each item one byte)*/ +#define SYSCON_SARADC_SAR2_PATT_TAB1 0xFFFFFFFF +#define SYSCON_SARADC_SAR2_PATT_TAB1_M ((SYSCON_SARADC_SAR2_PATT_TAB1_V)<<(SYSCON_SARADC_SAR2_PATT_TAB1_S)) +#define SYSCON_SARADC_SAR2_PATT_TAB1_V 0xFFFFFFFF +#define SYSCON_SARADC_SAR2_PATT_TAB1_S 0 + +#define SYSCON_SARADC_SAR2_PATT_TAB2_REG (DR_REG_SYSCON_BASE + 0x30) +/* SYSCON_SARADC_SAR2_PATT_TAB2 : R/W ;bitpos:[31:0] ;default: 32'hf0f0f0f ; */ +/*description: Item 4 ~ 7 for pattern table 2 (each item one byte)*/ +#define SYSCON_SARADC_SAR2_PATT_TAB2 0xFFFFFFFF +#define SYSCON_SARADC_SAR2_PATT_TAB2_M ((SYSCON_SARADC_SAR2_PATT_TAB2_V)<<(SYSCON_SARADC_SAR2_PATT_TAB2_S)) +#define SYSCON_SARADC_SAR2_PATT_TAB2_V 0xFFFFFFFF +#define SYSCON_SARADC_SAR2_PATT_TAB2_S 0 + +#define SYSCON_SARADC_SAR2_PATT_TAB3_REG (DR_REG_SYSCON_BASE + 0x34) +/* SYSCON_SARADC_SAR2_PATT_TAB3 : R/W ;bitpos:[31:0] ;default: 32'hf0f0f0f ; */ +/*description: Item 8 ~ 11 for pattern table 2 (each item one byte)*/ +#define SYSCON_SARADC_SAR2_PATT_TAB3 0xFFFFFFFF +#define SYSCON_SARADC_SAR2_PATT_TAB3_M ((SYSCON_SARADC_SAR2_PATT_TAB3_V)<<(SYSCON_SARADC_SAR2_PATT_TAB3_S)) +#define SYSCON_SARADC_SAR2_PATT_TAB3_V 0xFFFFFFFF +#define SYSCON_SARADC_SAR2_PATT_TAB3_S 0 + +#define SYSCON_SARADC_SAR2_PATT_TAB4_REG (DR_REG_SYSCON_BASE + 0x38) +/* SYSCON_SARADC_SAR2_PATT_TAB4 : R/W ;bitpos:[31:0] ;default: 32'hf0f0f0f ; */ +/*description: Item 12 ~ 15 for pattern table 2 (each item one byte)*/ +#define SYSCON_SARADC_SAR2_PATT_TAB4 0xFFFFFFFF +#define SYSCON_SARADC_SAR2_PATT_TAB4_M ((SYSCON_SARADC_SAR2_PATT_TAB4_V)<<(SYSCON_SARADC_SAR2_PATT_TAB4_S)) +#define SYSCON_SARADC_SAR2_PATT_TAB4_V 0xFFFFFFFF +#define SYSCON_SARADC_SAR2_PATT_TAB4_S 0 + +#define SYSCON_APLL_TICK_CONF_REG (DR_REG_SYSCON_BASE + 0x3C) +/* SYSCON_APLL_TICK_NUM : R/W ;bitpos:[7:0] ;default: 8'd99 ; */ +/*description: */ +#define SYSCON_APLL_TICK_NUM 0x000000FF +#define SYSCON_APLL_TICK_NUM_M ((SYSCON_APLL_TICK_NUM_V)<<(SYSCON_APLL_TICK_NUM_S)) +#define SYSCON_APLL_TICK_NUM_V 0xFF +#define SYSCON_APLL_TICK_NUM_S 0 + +#define SYSCON_DATE_REG (DR_REG_SYSCON_BASE + 0x7C) +/* SYSCON_DATE : R/W ;bitpos:[31:0] ;default: 32'h16042000 ; */ +/*description: */ +#define SYSCON_DATE 0xFFFFFFFF +#define SYSCON_DATE_M ((SYSCON_DATE_V)<<(SYSCON_DATE_S)) +#define SYSCON_DATE_V 0xFFFFFFFF +#define SYSCON_DATE_S 0 + + + + +#endif /*_SOC_SYSCON_REG_H_ */ + + diff --git a/components/esp32/include/soc/syscon_struct.h b/components/esp32/include/soc/syscon_struct.h new file mode 100644 index 0000000000..700aeecf4c --- /dev/null +++ b/components/esp32/include/soc/syscon_struct.h @@ -0,0 +1,120 @@ +// 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 _SOC_SYSCON_STRUCT_H_ +#define _SOC_SYSCON_STRUCT_H_ +typedef struct { + union { + struct { + volatile uint32_t pre_div: 10; + volatile uint32_t clk_320m_en: 1; + volatile uint32_t clk_en: 1; + volatile uint32_t rst_tick: 1; + volatile uint32_t quick_clk_chng: 1; + volatile uint32_t reserved14: 18; + }; + volatile uint32_t val; + }clk_conf; + union { + struct { + volatile uint32_t xtal_tick: 8; + volatile uint32_t reserved8: 24; + }; + volatile uint32_t val; + }xtal_tick_conf; + union { + struct { + volatile uint32_t pll_tick: 8; + volatile uint32_t reserved8: 24; + }; + volatile uint32_t val; + }pll_tick_conf; + union { + struct { + volatile uint32_t ck8m_tick: 8; + volatile uint32_t reserved8: 24; + }; + volatile uint32_t val; + }ck8m_tick_conf; + union { + struct { + volatile uint32_t start_force: 1; + volatile uint32_t start: 1; + volatile uint32_t sar2_mux: 1; /*1: SAR ADC2 is controlled by DIG ADC2 CTRL 0: SAR ADC2 is controlled by PWDET CTRL*/ + volatile uint32_t work_mode: 2; /*0: single mode 1: double mode 2: alternate mode*/ + volatile uint32_t sar_sel: 1; /*0: SAR1 1: SAR2 only work for single SAR mode*/ + volatile uint32_t sar_clk_gated: 1; + volatile uint32_t sar_clk_div: 8; /*SAR clock divider*/ + volatile uint32_t sar1_patt_len: 4; /*0 ~ 15 means length 1 ~ 16*/ + volatile uint32_t sar2_patt_len: 4; /*0 ~ 15 means length 1 ~ 16*/ + volatile uint32_t sar1_patt_p_clear: 1; /*clear the pointer of pattern table for DIG ADC1 CTRL*/ + volatile uint32_t sar2_patt_p_clear: 1; /*clear the pointer of pattern table for DIG ADC2 CTRL*/ + volatile uint32_t data_sar_sel: 1; /*1: sar_sel will be coded by the MSB of the 16-bit output data in this case the resolution should not be larger than 11 bits.*/ + volatile uint32_t data_to_i2s: 1; /*1: I2S input data is from SAR ADC (for DMA) 0: I2S input data is from GPIO matrix*/ + volatile uint32_t reserved27: 5; + }; + volatile uint32_t val; + }saradc_ctrl; + union { + struct { + volatile uint32_t meas_num_limit: 1; + volatile uint32_t max_meas_num: 8; /*max conversion number*/ + volatile uint32_t sar1_inv: 1; /*1: data to DIG ADC1 CTRL is inverted otherwise not*/ + volatile uint32_t sar2_inv: 1; /*1: data to DIG ADC2 CTRL is inverted otherwise not*/ + volatile uint32_t reserved11: 21; + }; + volatile uint32_t val; + }saradc_ctrl2; + union { + struct { + volatile uint32_t rstb_wait: 8; + volatile uint32_t standby_wait: 8; + volatile uint32_t start_wait: 8; + volatile uint32_t sample_cycle: 8; /*sample cycles*/ + }; + volatile uint32_t val; + }saradc_fsm; + volatile uint32_t saradc_sar1_patt_tab1; /*item 0 ~ 3 for pattern table 1 (each item one byte)*/ + volatile uint32_t saradc_sar1_patt_tab2; /*Item 4 ~ 7 for pattern table 1 (each item one byte)*/ + volatile uint32_t saradc_sar1_patt_tab3; /*Item 8 ~ 11 for pattern table 1 (each item one byte)*/ + volatile uint32_t saradc_sar1_patt_tab4; /*Item 12 ~ 15 for pattern table 1 (each item one byte)*/ + volatile uint32_t saradc_sar2_patt_tab1; /*item 0 ~ 3 for pattern table 2 (each item one byte)*/ + volatile uint32_t saradc_sar2_patt_tab2; /*Item 4 ~ 7 for pattern table 2 (each item one byte)*/ + volatile uint32_t saradc_sar2_patt_tab3; /*Item 8 ~ 11 for pattern table 2 (each item one byte)*/ + volatile uint32_t saradc_sar2_patt_tab4; /*Item 12 ~ 15 for pattern table 2 (each item one byte)*/ + union { + struct { + volatile uint32_t apll_tick: 8; + volatile uint32_t reserved8: 24; + }; + volatile uint32_t val; + }apll_tick_conf; + volatile uint32_t reserved_40; + volatile uint32_t reserved_44; + volatile uint32_t reserved_48; + volatile uint32_t reserved_4c; + volatile uint32_t reserved_50; + volatile uint32_t reserved_54; + volatile uint32_t reserved_58; + volatile uint32_t reserved_5c; + volatile uint32_t reserved_60; + volatile uint32_t reserved_64; + volatile uint32_t reserved_68; + volatile uint32_t reserved_6c; + volatile uint32_t reserved_70; + volatile uint32_t reserved_74; + volatile uint32_t reserved_78; + volatile uint32_t date; /**/ +} syscon_dev_t; + +#endif /* _SOC_SYSCON_STRUCT_H_ */ diff --git a/docs/security/flash-encryption.rst b/docs/security/flash-encryption.rst index b73d8a9ad3..298a0a75bf 100644 --- a/docs/security/flash-encryption.rst +++ b/docs/security/flash-encryption.rst @@ -7,9 +7,6 @@ Flash Encryption is separate from the `Secure Boot` feature, and you can use fla **IMPORTANT: Enabling flash encryption limits your options for further updates of your ESP32. Make sure to read this document (including `Limitations of Flash Encryption` and understand the implications of enabling flash encryption.** -**IMPORTANT: Flash Encryption feature is currently enabled for development use only, with a key generated on the host. The recommended production configuration, where the flash encryption key is generated by the device on first boot, is currently disabled while final testing is done. This documentation refers to flash encryption keys being generated on first boot, however for now it is necessary to follow the additional steps shown under `Precalculated Flash Encryption Key`.** - - Background ---------- diff --git a/docs/security/secure-boot.rst b/docs/security/secure-boot.rst index 7aaf9abf81..3c247d4a46 100644 --- a/docs/security/secure-boot.rst +++ b/docs/security/secure-boot.rst @@ -5,8 +5,6 @@ Secure Boot is a feature for ensuring only your code can run on the chip. Data l Secure Boot is separate from the `Flash Encryption` feature, and you can use secure boot without encrypting the flash contents. However we recommend using both features together for a secure environment. -**IMPORTANT: Secure Boot feature is currently enabled for development use only, with a key generated on the host. The recommended production configuration, where the secure boot key is generated by the device on first boot, is currently disabled while final testing is done. This documentation refers to "One-Time Flashable" mode (where keys are generated on the device), but for now only the `Re-Flashable Software Bootloader` mode is available.** - Background ---------- From 63e9806d855718d848c1dde953048e4eaba20e8b Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 4 Jan 2017 16:30:07 +1100 Subject: [PATCH 050/167] esp_random: XOR the RNG register value several times before returning it Probably unnecessary, but avoids returning internal RNG state as-is. --- components/esp32/hw_random.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/esp32/hw_random.c b/components/esp32/hw_random.c index 11c7af936b..3e5cb81a0a 100644 --- a/components/esp32/hw_random.c +++ b/components/esp32/hw_random.c @@ -29,13 +29,19 @@ uint32_t IRAM_ATTR esp_random(void) * this function needs to wait for at least 16 APB clock cycles after reading * previous word. This implementation may actually wait a bit longer * due to extra time spent in arithmetic and branch statements. + * + * As a (probably unncessary) precaution to avoid returning the + * RNG state as-is, the result is XORed with additional + * WDEV_RND_REG reads while waiting. */ static uint32_t last_ccount = 0; uint32_t ccount; + uint32_t result = 0; do { ccount = XTHAL_GET_CCOUNT(); + result ^= REG_READ(WDEV_RND_REG); } while (ccount - last_ccount < XT_CLOCK_FREQ / APB_CLK_FREQ * 16); last_ccount = ccount; - return REG_READ(WDEV_RND_REG); + return result ^ REG_READ(WDEV_RND_REG); } From 6c41080fd91fb8d20dd63aabe1afeb3268fab027 Mon Sep 17 00:00:00 2001 From: Yinling Date: Mon, 2 Jan 2017 19:28:01 +0800 Subject: [PATCH 051/167] CI: update known issues --- .../idf_test/integration_test/KnownIssues | 139 ++++++++++++------ 1 file changed, 95 insertions(+), 44 deletions(-) diff --git a/components/idf_test/integration_test/KnownIssues b/components/idf_test/integration_test/KnownIssues index c6cb7b77b9..884bd2f590 100644 --- a/components/idf_test/integration_test/KnownIssues +++ b/components/idf_test/integration_test/KnownIssues @@ -38,30 +38,45 @@ WIFI_CONN_0904 WIFI_CONN_0902 # Wifi connect issue -^WIFI_CONN_0101 WIFI_CONN_0101 -WIFI_CONN_0103 -^WIFI_CONN_0401 -^WIFI_CONN_0103 +^WIFI_CONN_0101 WIFI_CONN_0102 +^WIFI_CONN_0102 +WIFI_CONN_0103 +^WIFI_CONN_0103 +WIFI_CONN_0104 +^WIFI_CONN_0104 +WIFI_CONN_0201 +^WIFI_CONN_0201 +WIFI_CONN_0401 +^WIFI_CONN_0401 WIFI_CONN_0601 ^WIFI_CONN_0601 WIFI_ADDR_0102 ^WIFI_ADDR_0102 WIFI_CONN_0502 +^WIFI_CONN_0502 WIFI_CONN_0501 +^WIFI_CONN_0501 # Wifi scan issue +WIFI_SCAN_0101 ^WIFI_SCAN_0101 +WIFI_SCAN_0102 ^WIFI_SCAN_0102 WIFI_SCAN_0103 ^WIFI_SCAN_0103 WIFI_SCAN_0104 ^WIFI_SCAN_0104 +WIFI_SCAN_0105 ^WIFI_SCAN_0105 WIFI_SCAN_0303 +^WIFI_SCAN_0303 WIFI_SCAN_0304 -^WIFI_SCAN_0104 +^WIFI_SCAN_0304 +WIFI_MODE_0101 +WIFI_MODE_0102 +WIFI_MODE_0103 # IGMP cases are supported but as UDP is not stable, exclude them first TCPIP_IGMP_0101 @@ -82,78 +97,114 @@ TCPIP_IGMP_0204 ^TCPIP_IGMP_0204 # DHCP issues -^TCPIP_DHCP_0301 -TCPIP_DHCP_0301 TCPIP_DHCP_0101 ^TCPIP_DHCP_0101 +TCPIP_DHCP_0202 +^TCPIP_DHCP_0202 +TCPIP_DHCP_0204 +^TCPIP_DHCP_0204 +TCPIP_DHCP_0205 +^TCPIP_DHCP_0205 +TCPIP_DHCP_0206 +^TCPIP_DHCP_0206 TCPIP_DHCP_0207 ^TCPIP_DHCP_0207 TCPIP_DHCP_0208 ^TCPIP_DHCP_0208 -TCPIP_DHCP_0205 -^TCPIP_DHCP_0205 TCPIP_DHCP_0209 ^TCPIP_DHCP_0209 -^TCPIP_DHCP_0204 -TCPIP_DHCP_0204 -^TCPIP_DHCP_0210 TCPIP_DHCP_0210 -^TCPIP_DHCP_0211 +^TCPIP_DHCP_0210 TCPIP_DHCP_0211 -^TCPIP_DHCP_0206 +^TCPIP_DHCP_0211 +TCPIP_DHCP_0301 +^TCPIP_DHCP_0301 +TCPIP_DHCP_0302 ^TCPIP_DHCP_0302 # TCP issue -TCPIP_TCP_0402 -^TCPIP_TCP_0406 -^TCPIP_TCP_0401 -TCPIP_TCP_0210 -^TCPIP_TCP_0210 +TCPIP_TCP_0101 +^TCPIP_TCP_0101 +TCPIP_TCP_0102 +^TCPIP_TCP_0102 TCPIP_TCP_0103 ^TCPIP_TCP_0103 +TCPIP_TCP_0104 +^TCPIP_TCP_0104 +TCPIP_TCP_0105 +^TCPIP_TCP_0105 +TCPIP_TCP_0106 +^TCPIP_TCP_0106 +TCPIP_TCP_0107 +^TCPIP_TCP_0107 TCPIP_TCP_0112 ^TCPIP_TCP_0112 -TCPIP_TCP_0106 -TCPIP_TCP_0107 -TCPIP_TCP_0105 -^TCPIP_TCP_0407 -^TCPIP_TCP_0404 -^TCPIP_TCP_0102 -^TCPIP_TCP_0105 -^TCPIP_TCP_0107 -^TCPIP_TCP_0206 -TCPIP_TCP_0102 -^TCPIP_TCP_0403 -^TCPIP_TCP_0402 -^TCPIP_TCP_0101 -^TCPIP_TCP_0203 -^TCPIP_TCP_0106 +TCPIP_TCP_0201 ^TCPIP_TCP_0201 +TCPIP_TCP_0202 +^TCPIP_TCP_0202 +TCPIP_TCP_0203 +^TCPIP_TCP_0203 +TCPIP_TCP_0204 +^TCPIP_TCP_0204 +TCPIP_TCP_0206 +^TCPIP_TCP_0206 +TCPIP_TCP_0208 +^TCPIP_TCP_0208 +TCPIP_TCP_0210 +^TCPIP_TCP_0210 +TCPIP_TCP_0401 +^TCPIP_TCP_0401 +TCPIP_TCP_0402 +^TCPIP_TCP_0402 +TCPIP_TCP_0403 +^TCPIP_TCP_0403 +TCPIP_TCP_0404 +^TCPIP_TCP_0404 +TCPIP_TCP_0406 +^TCPIP_TCP_0406 +TCPIP_TCP_0407 +^TCPIP_TCP_0407 +TCPIP_TCP_0408 +^TCPIP_TCP_0408 +TCPIP_TCP_0412 ^TCPIP_TCP_0412 -TCPIP_TCP_0101 +TCPIP_TCP_0411 +^TCPIP_TCP_0411 # UDP issue +TCPIP_UDP_0102 +^TCPIP_UDP_0102 TCPIP_UDP_0103 ^TCPIP_UDP_0103 +TCPIP_UDP_0104 +^TCPIP_UDP_0104 +TCPIP_UDP_0108 ^TCPIP_UDP_0108 TCPIP_UDP_0110 ^TCPIP_UDP_0110 +TCPIP_UDP_0112 ^TCPIP_UDP_0112 -TCPIP_UDP_0305 -^TCPIP_UDP_0305 -^TCPIP_UDP_0304 -TCPIP_UDP_0104 -^TCPIP_UDP_0104 -^TCPIP_UDP_0301 TCPIP_UDP_0301 +^TCPIP_UDP_0301 TCPIP_UDP_0302 +^TCPIP_UDP_0302 TCPIP_UDP_0303 ^TCPIP_UDP_0303 -^TCPIP_UDP_0307 -^TCPIP_UDP_0302 +TCPIP_UDP_0304 +^TCPIP_UDP_0304 +TCPIP_UDP_0305 +^TCPIP_UDP_0305 +TCPIP_UDP_0306 ^TCPIP_UDP_0306 +TCPIP_UDP_0307 +^TCPIP_UDP_0307 #DNS -^TCPIP_DNS_0103 +TCPIP_DNS_0101 +^TCPIP_DNS_0101 +TCPIP_DNS_0102 ^TCPIP_DNS_0102 +TCPIP_DNS_0103 +^TCPIP_DNS_0103 From b1cac83f610ad7a9918c61c0b87bb05e97a7ffbb Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 4 Jan 2017 17:39:08 +0800 Subject: [PATCH 052/167] examples: Rename 18_ota to 26_ota example numbers should accord with our internal definition. --- examples/{18_ota => 26_ota}/Makefile | 0 examples/{18_ota => 26_ota}/OTA_workflow.png | Bin examples/{18_ota => 26_ota}/README.md | 0 examples/{18_ota => 26_ota}/main/Kconfig.projbuild | 0 examples/{18_ota => 26_ota}/main/component.mk | 0 examples/{18_ota => 26_ota}/main/ota.c | 0 examples/{18_ota => 26_ota}/sdkconfig.defaults | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename examples/{18_ota => 26_ota}/Makefile (100%) rename examples/{18_ota => 26_ota}/OTA_workflow.png (100%) rename examples/{18_ota => 26_ota}/README.md (100%) rename examples/{18_ota => 26_ota}/main/Kconfig.projbuild (100%) rename examples/{18_ota => 26_ota}/main/component.mk (100%) rename examples/{18_ota => 26_ota}/main/ota.c (100%) rename examples/{18_ota => 26_ota}/sdkconfig.defaults (100%) diff --git a/examples/18_ota/Makefile b/examples/26_ota/Makefile similarity index 100% rename from examples/18_ota/Makefile rename to examples/26_ota/Makefile diff --git a/examples/18_ota/OTA_workflow.png b/examples/26_ota/OTA_workflow.png similarity index 100% rename from examples/18_ota/OTA_workflow.png rename to examples/26_ota/OTA_workflow.png diff --git a/examples/18_ota/README.md b/examples/26_ota/README.md similarity index 100% rename from examples/18_ota/README.md rename to examples/26_ota/README.md diff --git a/examples/18_ota/main/Kconfig.projbuild b/examples/26_ota/main/Kconfig.projbuild similarity index 100% rename from examples/18_ota/main/Kconfig.projbuild rename to examples/26_ota/main/Kconfig.projbuild diff --git a/examples/18_ota/main/component.mk b/examples/26_ota/main/component.mk similarity index 100% rename from examples/18_ota/main/component.mk rename to examples/26_ota/main/component.mk diff --git a/examples/18_ota/main/ota.c b/examples/26_ota/main/ota.c similarity index 100% rename from examples/18_ota/main/ota.c rename to examples/26_ota/main/ota.c diff --git a/examples/18_ota/sdkconfig.defaults b/examples/26_ota/sdkconfig.defaults similarity index 100% rename from examples/18_ota/sdkconfig.defaults rename to examples/26_ota/sdkconfig.defaults From 0fb2ab9f5cefd84da023008e120703dc676c8443 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Mon, 2 Jan 2017 20:03:10 +0800 Subject: [PATCH 053/167] lwip/freertos/esp32: add throughput optimization related code 1. Update wifi lib which contains ampdu and other optimizations 2. Add throughput code debug code 3. Other misc modification about throughput optimization --- components/esp32/include/esp_wifi_types.h | 2 +- components/esp32/lib | 2 +- components/freertos/Kconfig | 2 + components/freertos/port.c | 6 +++ components/lwip/api/api_lib.c | 4 ++ components/lwip/api/api_msg.c | 4 ++ components/lwip/api/sockets.c | 1 + components/lwip/api/tcpip.c | 15 +++---- components/lwip/include/lwip/lwip/opt.h | 2 +- components/lwip/include/lwip/port/lwipopts.h | 41 ++++++++++++++------ components/lwip/netif/ethernet.c | 4 ++ components/lwip/port/netif/ethernetif.c | 4 -- components/lwip/port/netif/wlanif.c | 3 -- 13 files changed, 59 insertions(+), 31 deletions(-) diff --git a/components/esp32/include/esp_wifi_types.h b/components/esp32/include/esp_wifi_types.h index ac7642829f..be3183ba5d 100755 --- a/components/esp32/include/esp_wifi_types.h +++ b/components/esp32/include/esp_wifi_types.h @@ -121,7 +121,7 @@ typedef enum { #define WIFI_PROTOCOL_11N 4 typedef enum { - WIFI_BW_HT20 = 0, /* Bandwidth is HT20 */ + WIFI_BW_HT20 = 1, /* Bandwidth is HT20 */ WIFI_BW_HT40, /* Bandwidth is HT40 */ } wifi_bandwidth_t; diff --git a/components/esp32/lib b/components/esp32/lib index 02232f974b..074303d74f 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 02232f974b0ff1568ddd6d7015a41fb4f4870994 +Subproject commit 074303d74fc9c68823adee0a38fc1e8de42943b6 diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index fc4cf56061..fe7e6afd88 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -187,6 +187,7 @@ config FREERTOS_PORTMUX_DEBUG If enabled, debug information (including integrity checks) will be printed to UART for the port-specific MUX implementation. +if !FREERTOS_UNICORE config FREERTOS_PORTMUX_DEBUG_RECURSIVE bool "Debug portMUX Recursion" depends on FREERTOS_PORTMUX_DEBUG @@ -194,6 +195,7 @@ config FREERTOS_PORTMUX_DEBUG_RECURSIVE help If enabled, additional debug information will be printed for recursive portMUX usage. +endif #FREERTOS_UNICORE endif # FREERTOS_DEBUG_INTERNALS diff --git a/components/freertos/port.c b/components/freertos/port.c index 25480ed475..e5898038bc 100644 --- a/components/freertos/port.c +++ b/components/freertos/port.c @@ -282,6 +282,7 @@ void vPortCPUAcquireMutex(portMUX_TYPE *mux, const char *fnName, int line) { #else void vPortCPUAcquireMutex(portMUX_TYPE *mux) { #endif +#if !CONFIG_FREERTOS_UNICORE uint32_t res; uint32_t recCnt; unsigned int irqStatus; @@ -324,6 +325,7 @@ void vPortCPUAcquireMutex(portMUX_TYPE *mux) { } #endif portEXIT_CRITICAL_NESTED(irqStatus); +#endif } /* @@ -335,6 +337,7 @@ portBASE_TYPE vPortCPUReleaseMutex(portMUX_TYPE *mux, const char *fnName, int li #else portBASE_TYPE vPortCPUReleaseMutex(portMUX_TYPE *mux) { #endif +#if !CONFIG_FREERTOS_UNICORE uint32_t res=0; uint32_t recCnt; unsigned int irqStatus; @@ -379,6 +382,9 @@ portBASE_TYPE vPortCPUReleaseMutex(portMUX_TYPE *mux) { } portEXIT_CRITICAL_NESTED(irqStatus); return ret; +#else //!CONFIG_FREERTOS_UNICORE + return 0; +#endif } #if CONFIG_FREERTOS_BREAK_ON_SCHEDULER_START_JTAG diff --git a/components/lwip/api/api_lib.c b/components/lwip/api/api_lib.c index ecebf4f813..86ee5576ab 100755 --- a/components/lwip/api/api_lib.c +++ b/components/lwip/api/api_lib.c @@ -508,6 +508,10 @@ netconn_recv_data(struct netconn *conn, void **new_buf) } #endif /* (LWIP_UDP || LWIP_RAW) */ +#ifdef ESP_PERF + if (len > DBG_PERF_FILTER_LEN) { DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_SOC_IN); } +#endif + #if LWIP_SO_RCVBUF SYS_ARCH_DEC(conn->recv_avail, len); #endif /* LWIP_SO_RCVBUF */ diff --git a/components/lwip/api/api_msg.c b/components/lwip/api/api_msg.c index 2d98734b67..87e5aeb8e5 100755 --- a/components/lwip/api/api_msg.c +++ b/components/lwip/api/api_msg.c @@ -202,6 +202,10 @@ recv_udp(void *arg, struct udp_pcb *pcb, struct pbuf *p, #endif /* LWIP_NETBUF_RECVINFO */ } +#ifdef ESP_PERF + if (p->len > DBG_PERF_FILTER_LEN) DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_LWIP_OUT); +#endif + len = p->tot_len; if (sys_mbox_trypost(&conn->recvmbox, buf) != ERR_OK) { ESP_STATS_INC(esp.rx_udpmbox_post_fail); diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 4acf518cc9..c163b3ac0b 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -1438,6 +1438,7 @@ lwip_sendto(int s, const void *data, size_t size, int flags, err = netbuf_ref(&buf, data, short_size); #endif /* LWIP_NETIF_TX_SINGLE_PBUF */ if (err == ERR_OK) { + DBG_PERF_PATH_SET(DBG_PERF_DIR_TX, DBG_PERF_POINT_SOC_OUT); /* send the data */ err = netconn_send(sock->conn, &buf); } diff --git a/components/lwip/api/tcpip.c b/components/lwip/api/tcpip.c index 72de714e2a..dfb235718b 100755 --- a/components/lwip/api/tcpip.c +++ b/components/lwip/api/tcpip.c @@ -219,6 +219,12 @@ tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn) msg->msg.inp.p = p; msg->msg.inp.netif = inp; msg->msg.inp.input_fn = input_fn; +#ifdef ESP_PERF + if (p->len > DBG_PERF_FILTER_LEN) { + DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_WIFI_OUT); + } +#endif + if (sys_mbox_trypost(&mbox, msg) != ERR_OK) { ESP_STATS_INC(esp.tcpip_inpkt_post_fail); memp_free(MEMP_TCPIP_MSG_INPKT, msg); @@ -492,20 +498,11 @@ tcpip_init(tcpip_init_done_fn initfunc, void *arg) #endif /* LWIP_TCPIP_CORE_LOCKING */ -#if ESP_LWIP -#if ESP_DUAL_CORE - sys_thread_t xLwipTaskHandle = 0; - xTaskCreatePinnedToCore(tcpip_thread, TCPIP_THREAD_NAME, TCPIP_THREAD_STACKSIZE, NULL, TCPIP_THREAD_PRIO, NULL, 1); -#else sys_thread_t xLwipTaskHandle = sys_thread_new(TCPIP_THREAD_NAME , tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO); -#endif printf("tcpip_task_hdlxxx : %x, prio:%d,stack:%d\n", (u32_t)xLwipTaskHandle,TCPIP_THREAD_PRIO,TCPIP_THREAD_STACKSIZE); -#else - sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO); -#endif } diff --git a/components/lwip/include/lwip/lwip/opt.h b/components/lwip/include/lwip/lwip/opt.h index c42f3cd735..4d8d6bf70c 100755 --- a/components/lwip/include/lwip/lwip/opt.h +++ b/components/lwip/include/lwip/lwip/opt.h @@ -1665,7 +1665,7 @@ * LWIP_STATS==1: Enable statistics collection in lwip_stats. */ #ifndef LWIP_STATS -#define LWIP_STATS 1 +#define LWIP_STATS 0 #endif #if LWIP_STATS diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 5000d63ba9..933d55a510 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -572,21 +572,37 @@ #define ESP_LIGHT_SLEEP 1 #define ESP_L2_TO_L3_COPY CONFIG_L2_TO_L3_COPY #define ESP_CNT_DEBUG 0 -#define ESP_DUAL_CORE 0 -#define TCP_WND_DEFAULT (4*TCP_MSS) -#define TCP_SND_BUF_DEFAULT (2*TCP_MSS) +#define TCP_WND_DEFAULT (4*TCP_MSS) +#define TCP_SND_BUF_DEFAULT (2*TCP_MSS) + +#if ESP_PERF +#define DBG_PERF_PATH_SET(dir, point) +#define DBG_PERF_FILTER_LEN 1000 + +enum { + DBG_PERF_DIR_RX = 0, + DBG_PERF_DIR_TX, +}; + +enum { + DBG_PERF_POINT_INT = 0, + DBG_PERF_POINT_WIFI_IN = 1, + DBG_PERF_POINT_WIFI_OUT = 2, + DBG_PERF_POINT_LWIP_IN = 3, + DBG_PERF_POINT_LWIP_OUT = 4, + DBG_PERF_POINT_SOC_IN = 5, + DBG_PERF_POINT_SOC_OUT = 6, +}; + +#else +#define DBG_PERF_PATH_SET(dir, point) +#define DBG_PERF_FILTER_LEN 1000 +#endif #if ESP_PER_SOC_TCP_WND -#define TCP_WND(pcb) (pcb->per_soc_tcp_wnd) -#define TCP_SND_BUF(pcb) (pcb->per_soc_tcp_snd_buf) -#else -#if ESP_PERF -extern unsigned char misc_prof_get_tcpw(void); -extern unsigned char misc_prof_get_tcp_snd_buf(void); -#define TCP_WND(pcb) (misc_prof_get_tcpw()*TCP_MSS) -#define TCP_SND_BUF(pcb) (misc_prof_get_tcp_snd_buf()*TCP_MSS) -#endif +#define TCP_WND(pcb) (pcb->per_soc_tcp_wnd) +#define TCP_SND_BUF(pcb) (pcb->per_soc_tcp_snd_buf) #endif /** @@ -595,6 +611,7 @@ extern unsigned char misc_prof_get_tcp_snd_buf(void); #define DHCP_DEBUG LWIP_DBG_OFF #define LWIP_DEBUG LWIP_DBG_OFF #define TCP_DEBUG LWIP_DBG_OFF +#define ESP_STATS 0 #define CHECKSUM_CHECK_UDP 0 #define CHECKSUM_CHECK_IP 0 diff --git a/components/lwip/netif/ethernet.c b/components/lwip/netif/ethernet.c index 6d843913da..5d618f6a31 100755 --- a/components/lwip/netif/ethernet.c +++ b/components/lwip/netif/ethernet.c @@ -72,6 +72,10 @@ ethernet_input(struct pbuf *p, struct netif *netif) s16_t ip_hdr_offset = SIZEOF_ETH_HDR; #endif /* LWIP_ARP || ETHARP_SUPPORT_VLAN */ +#ifdef ESP_PERF + if (p->len > DBG_PERF_FILTER_LEN) DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_LWIP_IN); +#endif + if (p->len <= SIZEOF_ETH_HDR) { /* a packet with only an ethernet header (or less) is not valid for us */ ETHARP_STATS_INC(etharp.proterr); diff --git a/components/lwip/port/netif/ethernetif.c b/components/lwip/port/netif/ethernetif.c index 79f21f6b3f..6b1245e2e6 100755 --- a/components/lwip/port/netif/ethernetif.c +++ b/components/lwip/port/netif/ethernetif.c @@ -56,9 +56,6 @@ #define IFNAME1 'n' static char hostname[16]; -#if ESP_PERF -uint32_t g_rx_alloc_pbuf_fail_cnt = 0; -#endif /** * In this function, the hardware should be initialized. @@ -163,7 +160,6 @@ ethernetif_input(struct netif *netif, void *buffer, uint16_t len) #if CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE p = pbuf_alloc(PBUF_RAW, len, PBUF_RAM); if (p == NULL) { - //g_rx_alloc_pbuf_fail_cnt++; return; } memcpy(p->payload, buffer, len); diff --git a/components/lwip/port/netif/wlanif.c b/components/lwip/port/netif/wlanif.c index fea163f8cc..e114105f30 100755 --- a/components/lwip/port/netif/wlanif.c +++ b/components/lwip/port/netif/wlanif.c @@ -57,9 +57,6 @@ #define IFNAME1 'n' static char hostname[16]; -#if ESP_PERF -uint32_t g_rx_alloc_pbuf_fail_cnt = 0; -#endif /** * In this function, the hardware should be initialized. From d98b99f4f05cf367046af045931cf1fd909e92e4 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Thu, 5 Jan 2017 12:22:49 +0800 Subject: [PATCH 054/167] lwip: rework according review comments --- components/lwip/api/api_lib.c | 6 ++++-- components/lwip/api/api_msg.c | 6 ++++-- components/lwip/api/sockets.c | 1 + components/lwip/api/tcpip.c | 2 +- components/lwip/netif/ethernet.c | 6 ++++-- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/components/lwip/api/api_lib.c b/components/lwip/api/api_lib.c index 86ee5576ab..087115f0b9 100755 --- a/components/lwip/api/api_lib.c +++ b/components/lwip/api/api_lib.c @@ -508,8 +508,10 @@ netconn_recv_data(struct netconn *conn, void **new_buf) } #endif /* (LWIP_UDP || LWIP_RAW) */ -#ifdef ESP_PERF - if (len > DBG_PERF_FILTER_LEN) { DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_SOC_IN); } +#if ESP_PERF + if (len > DBG_PERF_FILTER_LEN) { + DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_SOC_IN); + } #endif #if LWIP_SO_RCVBUF diff --git a/components/lwip/api/api_msg.c b/components/lwip/api/api_msg.c index 87e5aeb8e5..878cd289b0 100755 --- a/components/lwip/api/api_msg.c +++ b/components/lwip/api/api_msg.c @@ -202,8 +202,10 @@ recv_udp(void *arg, struct udp_pcb *pcb, struct pbuf *p, #endif /* LWIP_NETBUF_RECVINFO */ } -#ifdef ESP_PERF - if (p->len > DBG_PERF_FILTER_LEN) DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_LWIP_OUT); +#if ESP_PERF + if (p->len > DBG_PERF_FILTER_LEN) { + DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_LWIP_OUT); + } #endif len = p->tot_len; diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index c163b3ac0b..7335246eb9 100755 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -1437,6 +1437,7 @@ lwip_sendto(int s, const void *data, size_t size, int flags, #else /* LWIP_NETIF_TX_SINGLE_PBUF */ err = netbuf_ref(&buf, data, short_size); #endif /* LWIP_NETIF_TX_SINGLE_PBUF */ + if (err == ERR_OK) { DBG_PERF_PATH_SET(DBG_PERF_DIR_TX, DBG_PERF_POINT_SOC_OUT); /* send the data */ diff --git a/components/lwip/api/tcpip.c b/components/lwip/api/tcpip.c index dfb235718b..f3422af706 100755 --- a/components/lwip/api/tcpip.c +++ b/components/lwip/api/tcpip.c @@ -219,7 +219,7 @@ tcpip_inpkt(struct pbuf *p, struct netif *inp, netif_input_fn input_fn) msg->msg.inp.p = p; msg->msg.inp.netif = inp; msg->msg.inp.input_fn = input_fn; -#ifdef ESP_PERF +#if ESP_PERF if (p->len > DBG_PERF_FILTER_LEN) { DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_WIFI_OUT); } diff --git a/components/lwip/netif/ethernet.c b/components/lwip/netif/ethernet.c index 5d618f6a31..90f62583bc 100755 --- a/components/lwip/netif/ethernet.c +++ b/components/lwip/netif/ethernet.c @@ -72,8 +72,10 @@ ethernet_input(struct pbuf *p, struct netif *netif) s16_t ip_hdr_offset = SIZEOF_ETH_HDR; #endif /* LWIP_ARP || ETHARP_SUPPORT_VLAN */ -#ifdef ESP_PERF - if (p->len > DBG_PERF_FILTER_LEN) DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_LWIP_IN); +#if ESP_PERF + if (p->len > DBG_PERF_FILTER_LEN) { + DBG_PERF_PATH_SET(DBG_PERF_DIR_RX, DBG_PERF_POINT_LWIP_IN); + } #endif if (p->len <= SIZEOF_ETH_HDR) { From 790a3d9ab365844dc4940a3c8ff5f6c500d63238 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Thu, 5 Jan 2017 14:56:16 +0800 Subject: [PATCH 055/167] component/bt : update bluetooth api doxygen ref --- docs/api/esp_bt_defs.rst | 1 - docs/api/esp_gap_ble.rst | 1 + docs/api/esp_gatt_defs.rst | 1 + docs/api/esp_gattc.rst | 1 + docs/api/esp_gatts.rst | 1 + 5 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api/esp_bt_defs.rst b/docs/api/esp_bt_defs.rst index 9a2131f74c..801b13df78 100644 --- a/docs/api/esp_bt_defs.rst +++ b/docs/api/esp_bt_defs.rst @@ -40,7 +40,6 @@ Type Definitions ^^^^^^^^^^^^^^^^ .. doxygentypedef:: esp_bd_addr_t -.. doxygentypedef:: esp_profile_cb_t Enumerations ^^^^^^^^^^^^ diff --git a/docs/api/esp_gap_ble.rst b/docs/api/esp_gap_ble.rst index f1837d4025..f16fa0da14 100644 --- a/docs/api/esp_gap_ble.rst +++ b/docs/api/esp_gap_ble.rst @@ -38,6 +38,7 @@ Macros Type Definitions ^^^^^^^^^^^^^^^^ +.. doxygentypedef:: esp_gap_ble_cb_t Enumerations ^^^^^^^^^^^^ diff --git a/docs/api/esp_gatt_defs.rst b/docs/api/esp_gatt_defs.rst index b5a416d436..637acaab99 100644 --- a/docs/api/esp_gatt_defs.rst +++ b/docs/api/esp_gatt_defs.rst @@ -85,6 +85,7 @@ Macros .. doxygendefine:: ESP_GATT_UUID_SCAN_REFRESH .. doxygendefine:: ESP_GATT_ILLEGAL_UUID .. doxygendefine:: ESP_GATT_MAX_ATTR_LEN +.. doxygendefine:: ESP_GATT_IF_NONE Type Definitions ^^^^^^^^^^^^^^^^ diff --git a/docs/api/esp_gattc.rst b/docs/api/esp_gattc.rst index efd2623ad9..7ff1e9de7e 100644 --- a/docs/api/esp_gattc.rst +++ b/docs/api/esp_gattc.rst @@ -37,6 +37,7 @@ Macros Type Definitions ^^^^^^^^^^^^^^^^ +.. doxygentypedef:: esp_gattc_cb_t Enumerations ^^^^^^^^^^^^ diff --git a/docs/api/esp_gatts.rst b/docs/api/esp_gatts.rst index fdb0e055a9..fbaa1c236d 100644 --- a/docs/api/esp_gatts.rst +++ b/docs/api/esp_gatts.rst @@ -37,6 +37,7 @@ Macros Type Definitions ^^^^^^^^^^^^^^^^ +.. doxygentypedef:: esp_gatts_cb_t Enumerations ^^^^^^^^^^^^ From 899f61f4a2496bb261e388a28b7d3d6e232b2832 Mon Sep 17 00:00:00 2001 From: Liu Han Date: Sat, 10 Dec 2016 13:48:38 +0800 Subject: [PATCH 056/167] examples: Add CoAP server demo Test CoAP protocol server --- examples/24_coap_server/Makefile | 9 + .../24_coap_server/main/Kconfig.projbuild | 22 ++ examples/24_coap_server/main/coap_server.c | 190 ++++++++++++++++++ examples/24_coap_server/main/coap_server.h | 38 ++++ examples/24_coap_server/main/component.mk | 5 + 5 files changed, 264 insertions(+) create mode 100644 examples/24_coap_server/Makefile create mode 100644 examples/24_coap_server/main/Kconfig.projbuild create mode 100644 examples/24_coap_server/main/coap_server.c create mode 100644 examples/24_coap_server/main/coap_server.h create mode 100644 examples/24_coap_server/main/component.mk diff --git a/examples/24_coap_server/Makefile b/examples/24_coap_server/Makefile new file mode 100644 index 0000000000..f82e5fc278 --- /dev/null +++ b/examples/24_coap_server/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 := coap_server + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/24_coap_server/main/Kconfig.projbuild b/examples/24_coap_server/main/Kconfig.projbuild new file mode 100644 index 0000000000..4926bfb200 --- /dev/null +++ b/examples/24_coap_server/main/Kconfig.projbuild @@ -0,0 +1,22 @@ +menu "Example Configuration" + +config LOCAL_PORT_NUMBER + int "Local port number" + range 0 65535 + default 5683 + help + Local port number for the example to use. + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +endmenu \ No newline at end of file diff --git a/examples/24_coap_server/main/coap_server.c b/examples/24_coap_server/main/coap_server.c new file mode 100644 index 0000000000..4e066689bb --- /dev/null +++ b/examples/24_coap_server/main/coap_server.c @@ -0,0 +1,190 @@ +/* CoAP server Example + + 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 "coap_server.h" + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" + +#include "nvs_flash.h" +#include + +#include "coap_config.h" +#include "resource.h" +#include "coap.h" + + +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 static int CONNECTED_BIT = BIT0; + +const static char *TAG = "CoAP_demo"; + +static coap_async_state_t *async = NULL; + +static void +send_async_response(coap_context_t *ctx, const coap_endpoint_t *local_if) +{ + coap_pdu_t *response; + unsigned char buf[3]; + const char* response_data = "Hello World!"; + size_t size = sizeof(coap_hdr_t) + 20; + response = coap_pdu_init(async->flags & COAP_MESSAGE_CON, COAP_RESPONSE_CODE(205), 0, size); + response->hdr->id = coap_new_message_id(ctx); + if (async->tokenlen) + coap_add_token(response, async->tokenlen, async->token); + coap_add_option(response, COAP_OPTION_CONTENT_TYPE, coap_encode_var_bytes(buf, COAP_MEDIATYPE_TEXT_PLAIN), buf); + coap_add_data (response, strlen(response_data), (unsigned char *)response_data); + + if (coap_send(ctx, local_if, &async->peer, response) == COAP_INVALID_TID) { + + } + coap_delete_pdu(response); + coap_async_state_t *tmp; + coap_remove_async(ctx, async->id, &tmp); + coap_free_async(async); + async = NULL; +} + +/* + * The resource handler + */ +static void +async_handler(coap_context_t *ctx, struct coap_resource_t *resource, + const coap_endpoint_t *local_interface, coap_address_t *peer, + coap_pdu_t *request, str *token, coap_pdu_t *response) +{ + async = coap_register_async(ctx, peer, request, COAP_ASYNC_SEPARATE | COAP_ASYNC_CONFIRM, (void*)"no data"); +} + +static void coap_demo_thread(void *p) +{ + coap_context_t* ctx = NULL; + coap_address_t serv_addr; + coap_resource_t* resource = NULL; + fd_set readfds; + struct timeval tv; + int flags = 0; + /* Prepare the CoAP server socket */ + coap_address_init(&serv_addr); + serv_addr.addr.sin.sin_family = AF_INET; + serv_addr.addr.sin.sin_addr.s_addr = INADDR_ANY; + serv_addr.addr.sin.sin_port = htons(COAP_DEFAULT_PORT); + ctx = coap_new_context(&serv_addr); + if (ctx) { + flags = fcntl(ctx->sockfd, F_GETFL, 0); + fcntl(ctx->sockfd, F_SETFL, flags|O_NONBLOCK); + + tv.tv_usec = COAP_DEFAULT_TIME_USEC; + tv.tv_sec = COAP_DEFAULT_TIME_SEC; + /* Initialize the resource */ + resource = coap_resource_init((unsigned char *)"Espressif", 9, 0); + if (resource){ + coap_register_handler(resource, COAP_REQUEST_GET, async_handler); + coap_add_resource(ctx, resource); + /*For incoming connections*/ + for (;;) { + FD_ZERO(&readfds); + FD_CLR( ctx->sockfd, &readfds); + FD_SET( ctx->sockfd, &readfds); + + int result = select( FD_SETSIZE, &readfds, 0, 0, &tv ); + if (result > 0){ + if (FD_ISSET( ctx->sockfd, &readfds )) + coap_read(ctx); + } else if (result < 0){ + break; + } else { + printf("select timeout\n"); + } + + if (async) + send_async_response(ctx, ctx->endpoint); + } + } + + coap_free_context(ctx); + } + + vTaskDelete(NULL); +} + +static void coap_server_init(void) +{ + int ret = pdPASS; + xTaskHandle coap_handle = NULL; + + ret = xTaskCreate(coap_demo_thread, + COAP_DEMO_THREAD_NAME, + COAP_DEMO_THREAD_STACK_WORDS, + NULL, + COAP_DEMO_THREAD_PRORIOTY, + &coap_handle); + + if (ret != pdPASS) { + ESP_LOGI(TAG, "create thread %s failed", COAP_DEMO_THREAD_NAME); + } +} + +static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + coap_server_init(); + 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); + break; + default: + break; + } + return ESP_OK; +} + +static void wifi_conn_init(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(wifi_event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); + + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +void app_main(void) +{ + nvs_flash_init(); + wifi_conn_init(); +} diff --git a/examples/24_coap_server/main/coap_server.h b/examples/24_coap_server/main/coap_server.h new file mode 100644 index 0000000000..e9e8728fc3 --- /dev/null +++ b/examples/24_coap_server/main/coap_server.h @@ -0,0 +1,38 @@ +/* CoAP server Example + + 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 _COAP_SERVER_H_ +#define _COAP_SERVER_H_ + +#include + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#define COAP_DEMO_THREAD_NAME "CoAP_demo" +#define COAP_DEMO_THREAD_STACK_WORDS 10240 +#define COAP_DEMO_THREAD_PRORIOTY 8 + +/* The examples use local port number of 5683 that you can set via 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define OPENSSL_DEMO_TARGET_TCP_PORT 5683 +*/ +#define COAP_DEFAULT_PORT CONFIG_LOCAL_PORT_NUMBER +#define COAP_DEFAULT_TIME_SEC 5 +#define COAP_DEFAULT_TIME_USEC 0 + +#endif + diff --git a/examples/24_coap_server/main/component.mk b/examples/24_coap_server/main/component.mk new file mode 100644 index 0000000000..0b9d7585e7 --- /dev/null +++ b/examples/24_coap_server/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.) + From 56514d5ab20c5d506f382a7a9c7966bbcd1ba65a Mon Sep 17 00:00:00 2001 From: Liu Han Date: Sat, 10 Dec 2016 14:34:50 +0800 Subject: [PATCH 057/167] examples: Add CoAP client demo Test CoAP protocol client --- examples/23_coap_client/Makefile | 9 + .../23_coap_client/main/Kconfig.projbuild | 34 ++++ examples/23_coap_client/main/coap_client.c | 176 ++++++++++++++++++ examples/23_coap_client/main/coap_client.h | 43 +++++ examples/23_coap_client/main/component.mk | 5 + 5 files changed, 267 insertions(+) create mode 100644 examples/23_coap_client/Makefile create mode 100644 examples/23_coap_client/main/Kconfig.projbuild create mode 100644 examples/23_coap_client/main/coap_client.c create mode 100644 examples/23_coap_client/main/coap_client.h create mode 100644 examples/23_coap_client/main/component.mk diff --git a/examples/23_coap_client/Makefile b/examples/23_coap_client/Makefile new file mode 100644 index 0000000000..c9a25d117f --- /dev/null +++ b/examples/23_coap_client/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 := coap_client + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/23_coap_client/main/Kconfig.projbuild b/examples/23_coap_client/main/Kconfig.projbuild new file mode 100644 index 0000000000..ba3e0d458b --- /dev/null +++ b/examples/23_coap_client/main/Kconfig.projbuild @@ -0,0 +1,34 @@ +menu "Example Configuration" + +config TARGET_DOMAIN + string "Target Domain" + default "californium.eclipse.org" + help + Target domain for the example to connect to. + +config TARGET_DOMAIN_URI + string "Target Uri" + default "coap://californium.eclipse.org" + help + Target uri for the example to use. + +config TARGET_PORT_NUMBER + int "Target port number" + range 0 65535 + default 5683 + help + Target port number for the example to connect to. + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +endmenu \ No newline at end of file diff --git a/examples/23_coap_client/main/coap_client.c b/examples/23_coap_client/main/coap_client.c new file mode 100644 index 0000000000..cef24f4c6e --- /dev/null +++ b/examples/23_coap_client/main/coap_client.c @@ -0,0 +1,176 @@ +/* CoAP client Example + + 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 "coap_client.h" + +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" + +#include "nvs_flash.h" +#include + +#include "coap_config.h" +#include "resource.h" +#include "coap.h" + + +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 static int CONNECTED_BIT = BIT0; + +const static char *TAG = "CoAP_demo"; + +static void message_handler(struct coap_context_t *ctx, const coap_endpoint_t *local_interface, const coap_address_t *remote, + coap_pdu_t *sent, coap_pdu_t *received, + const coap_tid_t id) +{ + unsigned char* data = NULL; + size_t data_len; + if (COAP_RESPONSE_CLASS(received->hdr->code) == 2) { + if (coap_get_data(received, &data_len, &data)) { + printf("Received: %s\n", data); + } + } +} + +static void coap_demo_thread(void *p) +{ + coap_context_t* ctx = NULL; + coap_address_t dst_addr, src_addr; + static coap_uri_t uri; + fd_set readfds; + struct timeval tv; + int flags, result; + coap_pdu_t* request = NULL; + const char* server_uri = COAP_DEFAULT_DEMO_URI; + uint8_t get_method = 1; + + coap_address_init(&src_addr); + src_addr.addr.sin.sin_family = AF_INET; + src_addr.addr.sin.sin_port = htons(0); + src_addr.addr.sin.sin_addr.s_addr = INADDR_ANY; + + ctx = coap_new_context(&src_addr); + if (ctx) { + coap_address_init(&dst_addr); + dst_addr.addr.sin.sin_family = AF_INET; + dst_addr.addr.sin.sin_port = htons(COAP_DEFAULT_PORT); + dst_addr.addr.sin.sin_addr.s_addr = inet_addr(COAP_DEFAULT_DEMO_ADDR); + + coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri); + request = coap_new_pdu(); + if (request){ + request->hdr->type = COAP_MESSAGE_CON; + request->hdr->id = coap_new_message_id(ctx); + request->hdr->code = get_method; + coap_add_option(request, COAP_OPTION_URI_PATH, uri.path.length, uri.path.s); + + coap_register_response_handler(ctx, message_handler); + coap_send_confirmed(ctx, ctx->endpoint, &dst_addr, request); + + flags = fcntl(ctx->sockfd, F_GETFL, 0); + fcntl(ctx->sockfd, F_SETFL, flags|O_NONBLOCK); + + tv.tv_usec = COAP_DEFAULT_TIME_USEC; + tv.tv_sec = COAP_DEFAULT_TIME_SEC; + + for(;;) { + FD_ZERO(&readfds); + FD_CLR( ctx->sockfd, &readfds ); + FD_SET( ctx->sockfd, &readfds ); + result = select( FD_SETSIZE, &readfds, 0, 0, &tv ); + if (result > 0) { + if (FD_ISSET( ctx->sockfd, &readfds )) + coap_read(ctx); + } else if (result < 0) { + break; + } else { + printf("select timeout\n"); + } + } + } + coap_free_context(ctx); + } + + vTaskDelete(NULL); +} + +static void coap_server_init(void) +{ + int ret = pdPASS; + xTaskHandle coap_handle = NULL; + + ret = xTaskCreate(coap_demo_thread, + COAP_DEMO_THREAD_NAME, + COAP_DEMO_THREAD_STACK_WORDS, + NULL, + COAP_DEMO_THREAD_PRORIOTY, + &coap_handle); + + if (ret != pdPASS) { + ESP_LOGI(TAG, "create thread %s failed", COAP_DEMO_THREAD_NAME); + } +} + +static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + coap_server_init(); + 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); + break; + default: + break; + } + return ESP_OK; +} + +static void wifi_conn_init(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(wifi_event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); + + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +void app_main(void) +{ + nvs_flash_init(); + wifi_conn_init(); +} diff --git a/examples/23_coap_client/main/coap_client.h b/examples/23_coap_client/main/coap_client.h new file mode 100644 index 0000000000..50f4ccd16f --- /dev/null +++ b/examples/23_coap_client/main/coap_client.h @@ -0,0 +1,43 @@ +/* CoAP client Example + + 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 _COAP_CLIENT_H_ +#define _COAP_CLIENT_H_ + +#include + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#define COAP_DEMO_THREAD_NAME "CoAP_demo" +#define COAP_DEMO_THREAD_STACK_WORDS 10240 +#define COAP_DEMO_THREAD_PRORIOTY 8 + +#define COAP_DEFAULT_TIME_SEC 5 +#define COAP_DEFAULT_TIME_USEC 0 + +/* The examples use domain of "californium.eclipse.org",uri "coap://californium.eclipse.org" and port number of 5683 that + you can set via 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define COAP_DEFAULT_DEMO_ADDR "californium.eclipse.org" + , ie #define COAP_DEFAULT_DEMO_URI "coap://californium.eclipse.org" and ie #define COAP_DEFAULT_PORT 5683 +*/ +#define COAP_DEFAULT_PORT CONFIG_TARGET_PORT_NUMBER +#define COAP_DEFAULT_DEMO_ADDR CONFIG_TARGET_DOMAIN +#define COAP_DEFAULT_DEMO_URI CONFIG_TARGET_DOMAIN_URI + +#endif + diff --git a/examples/23_coap_client/main/component.mk b/examples/23_coap_client/main/component.mk new file mode 100644 index 0000000000..0b9d7585e7 --- /dev/null +++ b/examples/23_coap_client/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.) + From 8c7dfef3173f03e7fd498ad3584cc76854e99010 Mon Sep 17 00:00:00 2001 From: Dong Heng Date: Wed, 21 Dec 2016 12:04:26 +0800 Subject: [PATCH 058/167] examples/10_openssl_server: fixup SSL server with method of specific version 1. add method of any version supporting at OpenSSL and add API in header file 2. change OpenSSL server context method to be method of any version Fixes http://esp32.com/viewtopic.php?f=14&t=696. --- components/openssl/include/openssl/ssl.h | 18 +++++++++++++++++ components/openssl/platform/ssl_pm.c | 3 +++ examples/10_openssl_server/README.md | 2 +- .../10_openssl_server/main/Kconfig.projbuild | 2 +- .../10_openssl_server/main/openssl_server.c | 20 +++++++++++++------ .../10_openssl_server/main/openssl_server.h | 6 ++++-- 6 files changed, 41 insertions(+), 10 deletions(-) mode change 100644 => 100755 components/openssl/include/openssl/ssl.h mode change 100644 => 100755 components/openssl/platform/ssl_pm.c mode change 100644 => 100755 examples/10_openssl_server/README.md mode change 100644 => 100755 examples/10_openssl_server/main/Kconfig.projbuild mode change 100644 => 100755 examples/10_openssl_server/main/openssl_server.c mode change 100644 => 100755 examples/10_openssl_server/main/openssl_server.h diff --git a/components/openssl/include/openssl/ssl.h b/components/openssl/include/openssl/ssl.h old mode 100644 new mode 100755 index 7f8eb88302..39d4bf737c --- a/components/openssl/include/openssl/ssl.h +++ b/components/openssl/include/openssl/ssl.h @@ -214,6 +214,14 @@ const SSL_METHOD* TLSv1_1_client_method(void); */ const SSL_METHOD* TLSv1_2_client_method(void); +/** + * @brief create the target SSL context server method + * + * @param none + * + * @return the TLS any version SSL context client method + */ +const SSL_METHOD* TLS_client_method(void); /** * @brief create the target SSL context server method @@ -260,6 +268,16 @@ const SSL_METHOD* TLSv1_server_method(void); */ const SSL_METHOD* SSLv3_server_method(void); +/** + * @brief create the target SSL context server method + * + * @param none + * + * @return the TLS any version SSL context server method + */ +const SSL_METHOD* TLS_server_method(void); + + /** * @brief set the SSL context ALPN select callback function * diff --git a/components/openssl/platform/ssl_pm.c b/components/openssl/platform/ssl_pm.c old mode 100644 new mode 100755 index 522721ad7c..15015107f0 --- a/components/openssl/platform/ssl_pm.c +++ b/components/openssl/platform/ssl_pm.c @@ -125,6 +125,9 @@ int ssl_pm_new(SSL *ssl) mbedtls_ssl_conf_max_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, version); mbedtls_ssl_conf_min_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, version); + } else { + mbedtls_ssl_conf_max_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + mbedtls_ssl_conf_min_version(&ssl_pm->conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_0); } mbedtls_ssl_conf_rng(&ssl_pm->conf, mbedtls_ctr_drbg_random, &ssl_pm->ctr_drbg); diff --git a/examples/10_openssl_server/README.md b/examples/10_openssl_server/README.md old mode 100644 new mode 100755 index 333cb3d6a6..984a83c308 --- a/examples/10_openssl_server/README.md +++ b/examples/10_openssl_server/README.md @@ -5,7 +5,7 @@ The Example contains of OpenSSL server demo. First you should configure the project by "make menuconfig": Example Configuration -> 1. WIFI SSID: WIFI network to which your PC is also connected to. - 1. WIFI Password: WIFI password + 2. WIFI Password: WIFI password IF you want to test the OpenSSL server demo: 1. compile the code and load the firmware diff --git a/examples/10_openssl_server/main/Kconfig.projbuild b/examples/10_openssl_server/main/Kconfig.projbuild old mode 100644 new mode 100755 index 7a9cb97a0e..176d8fb334 --- a/examples/10_openssl_server/main/Kconfig.projbuild +++ b/examples/10_openssl_server/main/Kconfig.projbuild @@ -12,4 +12,4 @@ config WIFI_PASSWORD help WiFi password (WPA or WPA2) for the example to use. -endmenu \ No newline at end of file +endmenu diff --git a/examples/10_openssl_server/main/openssl_server.c b/examples/10_openssl_server/main/openssl_server.c old mode 100644 new mode 100755 index 756c1407f5..1eea2110ce --- a/examples/10_openssl_server/main/openssl_server.c +++ b/examples/10_openssl_server/main/openssl_server.c @@ -43,7 +43,8 @@ const static char *TAG = "Openssl_demo"; "OpenSSL demo\r\n" \ "OpenSSL server demo!\r\n" \ "\r\n" \ - "\r\n" + "\r\n" \ + "\r\n" static void openssl_demo_thread(void *p) { @@ -70,7 +71,7 @@ static void openssl_demo_thread(void *p) const unsigned int prvtkey_pem_bytes = prvtkey_pem_end - prvtkey_pem_start; ESP_LOGI(TAG, "SSL server context create ......"); - ctx = SSL_CTX_new(SSLv3_server_method()); + ctx = SSL_CTX_new(TLS_server_method()); if (!ctx) { ESP_LOGI(TAG, "failed"); goto failed1; @@ -155,14 +156,21 @@ reconnect: if (ret <= 0) { break; } - if (strstr(recv_buf, "GET / HTTP/1.1")) { - SSL_write(ssl, send_data, send_bytes); + ESP_LOGI(TAG, "SSL read: %s", recv_buf); + if (strstr(recv_buf, "GET ") && + strstr(recv_buf, " HTTP/1.1")) { + ESP_LOGI(TAG, "SSL get matched message") + ESP_LOGI(TAG, "SSL write message") + ret = SSL_write(ssl, send_data, send_bytes); + if (ret > 0) { + ESP_LOGI(TAG, "OK") + } else { + ESP_LOGI(TAG, "error") + } break; } } while (1); - ESP_LOGI(TAG, "result %d", ret); - SSL_shutdown(ssl); failed5: close(new_socket); diff --git a/examples/10_openssl_server/main/openssl_server.h b/examples/10_openssl_server/main/openssl_server.h old mode 100644 new mode 100755 index 5f49de35f2..51708535f5 --- a/examples/10_openssl_server/main/openssl_server.h +++ b/examples/10_openssl_server/main/openssl_server.h @@ -7,8 +7,10 @@ CONDITIONS OF ANY KIND, either express or implied. */ -#ifndef _OPENSSL_DEMO_H_ -#define _OPENSSL_DEMO_H_ +#ifndef _OPENSSL_SERVER_H_ +#define _OPENSSL_SERVER_H_ + +#include "sdkconfig.h" /* The examples use simple WiFi configuration that you can set via 'make menuconfig'. From daf58e385291b9abfdd7244ae2870ee994ced6fc Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Fri, 23 Dec 2016 15:57:44 +0800 Subject: [PATCH 059/167] component/bt : fix advertising bug 1. adv data flag 2. default adv data not BR/EDR in demo --- components/bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c | 1 + examples/14_gatt_server/main/gatts_demo.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 39b392e62f..4040ee42e0 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 @@ -109,6 +109,7 @@ static void btc_to_bta_adv_data(esp_ble_adv_data_t *p_adv_data, tBTA_BLE_ADV_DAT if (p_adv_data->flag != 0) { mask = BTM_BLE_AD_BIT_FLAGS; + bta_adv_data->flag = p_adv_data->flag; } if (p_adv_data->include_name) { diff --git a/examples/14_gatt_server/main/gatts_demo.c b/examples/14_gatt_server/main/gatts_demo.c index 1785835d7c..58deadc5c9 100644 --- a/examples/14_gatt_server/main/gatts_demo.c +++ b/examples/14_gatt_server/main/gatts_demo.c @@ -68,7 +68,7 @@ static esp_ble_adv_data_t test_adv_data = { .p_service_data = NULL, .service_uuid_len = 32, .p_service_uuid = test_service_uuid128, - .flag = 0x2, + .flag = 0x6, }; static esp_ble_adv_params_t test_adv_params = { From d6fcec73b2d623cab3dd6582485e11c2f445d1f4 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Thu, 5 Jan 2017 15:24:09 +0800 Subject: [PATCH 060/167] component/bt : add macro for adv_data_flag 1. add macro for adv data flag 2. add docs for doxygen --- .../bt/bluedroid/api/include/esp_gap_ble_api.h | 15 ++++++++++++++- docs/api/esp_gap_ble.rst | 6 ++++++ examples/14_gatt_server/main/gatts_demo.c | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/components/bt/bluedroid/api/include/esp_gap_ble_api.h b/components/bt/bluedroid/api/include/esp_gap_ble_api.h index f500f84031..f92143e6af 100644 --- a/components/bt/bluedroid/api/include/esp_gap_ble_api.h +++ b/components/bt/bluedroid/api/include/esp_gap_ble_api.h @@ -25,6 +25,19 @@ extern "C" { #endif +/**@{ + * BLE_ADV_DATA_FLAG data flag bit definition used for advertising data flag + */ +#define ESP_BLE_ADV_FLAG_LIMIT_DISC (0x01 << 0) +#define ESP_BLE_ADV_FLAG_GEN_DISC (0x01 << 1) +#define ESP_BLE_ADV_FLAG_BREDR_NOT_SPT (0x01 << 2) +#define ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT (0x01 << 3) +#define ESP_BLE_ADV_FLAG_DMT_HOST_SPT (0x01 << 4) +#define ESP_BLE_ADV_FLAG_NON_LIMIT_DISC (0x00 ) +/** + * @} + */ + /// GAP BLE callback event type typedef enum { ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT = 0, /*!< When advertising data set complete, the event comes */ @@ -126,7 +139,7 @@ typedef struct { 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 */ + uint8_t flag; /*!< Advertising flag of discovery mode, see BLE_ADV_DATA_FLAG detail */ } esp_ble_adv_data_t; /// Own BD address source of the device diff --git a/docs/api/esp_gap_ble.rst b/docs/api/esp_gap_ble.rst index f16fa0da14..856ed27302 100644 --- a/docs/api/esp_gap_ble.rst +++ b/docs/api/esp_gap_ble.rst @@ -33,6 +33,12 @@ Header Files Macros ^^^^^^ +.. doxygendefine:: ESP_BLE_ADV_FLAG_LIMIT_DISC +.. doxygendefine:: ESP_BLE_ADV_FLAG_GEN_DISC +.. doxygendefine:: ESP_BLE_ADV_FLAG_BREDR_NOT_SPT +.. doxygendefine:: ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT +.. doxygendefine:: ESP_BLE_ADV_FLAG_DMT_HOST_SPT +.. doxygendefine:: ESP_BLE_ADV_FLAG_NON_LIMIT_DISC .. doxygendefine:: ESP_BLE_ADV_DATA_LEN_MAX Type Definitions diff --git a/examples/14_gatt_server/main/gatts_demo.c b/examples/14_gatt_server/main/gatts_demo.c index 58deadc5c9..cf9a5789b4 100644 --- a/examples/14_gatt_server/main/gatts_demo.c +++ b/examples/14_gatt_server/main/gatts_demo.c @@ -68,7 +68,7 @@ static esp_ble_adv_data_t test_adv_data = { .p_service_data = NULL, .service_uuid_len = 32, .p_service_uuid = test_service_uuid128, - .flag = 0x6, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), }; static esp_ble_adv_params_t test_adv_params = { From 6b5e7349012bb2efaf45c9ea9671a8c05c5c648c Mon Sep 17 00:00:00 2001 From: Wangjialin Date: Sun, 25 Dec 2016 23:11:24 +0800 Subject: [PATCH 061/167] driver: ledc - update fading functions, add example and doc 1. add fading functions. 2. clear up ledc.c and ledc.h 3. update api doc. 4. add ledc example 5. edit ledc.rst and add readme for example code. 6. add s_ prefix for static global values. 7. add mutex for fade functions 8. minor changes according to the comments. --- components/driver/include/driver/ledc.h | 267 ++++++++---------- components/driver/ledc.c | 355 ++++++++++++++++++++---- docs/api/ledc.rst | 32 ++- examples/29_ledc/Makefile | 9 + examples/29_ledc/README.md | 16 ++ examples/29_ledc/main/component.mk | 3 + examples/29_ledc/main/ledc_fade.c | 131 +++++++++ 7 files changed, 596 insertions(+), 217 deletions(-) create mode 100644 examples/29_ledc/Makefile create mode 100644 examples/29_ledc/README.md create mode 100644 examples/29_ledc/main/component.mk create mode 100644 examples/29_ledc/main/ledc_fade.c diff --git a/components/driver/include/driver/ledc.h b/components/driver/include/driver/ledc.h index 691379a3d8..af7c6b8078 100644 --- a/components/driver/include/driver/ledc.h +++ b/components/driver/include/driver/ledc.h @@ -16,9 +16,6 @@ #define _DRIVER_LEDC_H_ #include "esp_err.h" #include "soc/soc.h" -#include "soc/ledc_reg.h" -#include "soc/ledc_reg.h" -#include "soc/ledc_struct.h" #include "driver/gpio.h" #include "driver/periph_ctrl.h" #include "esp_intr_alloc.h" @@ -68,6 +65,7 @@ typedef enum { LEDC_CHANNEL_5, /*!< LEDC channel 5 */ LEDC_CHANNEL_6, /*!< LEDC channel 6 */ LEDC_CHANNEL_7, /*!< LEDC channel 7 */ + LEDC_CHANNEL_MAX, } ledc_channel_t; typedef enum { @@ -79,6 +77,11 @@ typedef enum { LEDC_TIMER_15_BIT = 15, /*!< LEDC PWM depth 15Bit */ } ledc_timer_bit_t; +typedef enum { + LEDC_FADE_NO_WAIT = 0, /*!< LEDC fade function will return immediately */ + LEDC_FADE_WAIT_DONE, /*!< LEDC fade function will block until fading to the target duty*/ + LEDC_FADE_MAX, +} ledc_fade_mode_t; /** * @brief Configuration parameters of LEDC channel for ledc_channel_config function */ @@ -104,43 +107,39 @@ typedef struct { typedef intr_handle_t ledc_isr_handle_t; /** - * @brief LEDC channel configuration + * @brief LEDC channel configuration + * Configure LEDC channel with the given channel/output gpio_num/interrupt/source timer/frequency(Hz)/LEDC depth * - * User this Function, configure LEDC channel with the given channel/output gpio_num/interrupt/source timer/frequency(Hz)/LEDC depth + * @param ledc_conf Pointer of LEDC channel configure struct * - * @param ledc_conf Pointer of LEDC channel configure struct * @return * - ESP_OK Success * - ESP_ERR_INVALID_ARG Parameter error - * */ esp_err_t ledc_channel_config(ledc_channel_config_t* ledc_conf); /** - * @brief LEDC timer configuration - * - * User this Function, configure LEDC timer with the given source timer/frequency(Hz)/bit_num + * @brief LEDC timer configuration + * Configure LEDC timer with the given source timer/frequency(Hz)/bit_num * * @param timer_conf Pointer of LEDC timer configure struct * - * * @return * - ESP_OK Success * - ESP_ERR_INVALID_ARG Parameter error * - ESP_FAIL Can not find a proper pre-divider number base on the given frequency and the current bit_num. - * */ esp_err_t ledc_timer_config(ledc_timer_config_t* timer_conf); /** - * @brief LEDC update channel parameters + * @brief LEDC update channel parameters + * Call this function to activate the LEDC updated parameters. + * After ledc_set_duty, ledc_set_fade, we need to call this function to update the settings. * - * Call this function to activate the LEDC updated parameters. - * After ledc_set_duty, ledc_set_fade, we need to call this function to update the settings. - * - * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * - * @param channel LEDC channel(0-7), select from ledc_channel_t + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, + * now we only support high-speed mode. + * We will access low-speed mode in next version + * @param channel LEDC channel(0-7), select from ledc_channel_t * * @return * - ESP_OK Success @@ -150,14 +149,11 @@ esp_err_t ledc_timer_config(ledc_timer_config_t* timer_conf); esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel); /** - * @brief LEDC stop - * - * Disable LEDC output, and set idle level + * @brief LEDC stop. + * Disable LEDC output, and set idle level * * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * * @param channel LEDC channel(0-7), select from ledc_channel_t - * * @param idle_level Set output idle level after LEDC stops. * * @return @@ -167,14 +163,10 @@ esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel); esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idle_level); /** - * @brief LEDC set channel frequency(Hz) - * - * Set LEDC frequency(Hz) + * @brief LEDC set channel frequency(Hz) * * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * * @param timer_num LEDC timer index(0-3), select from ledc_timer_t - * * @param freq_hz Set the LEDC frequency * * @return @@ -188,25 +180,20 @@ esp_err_t ledc_set_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num, uint32_t * @brief LEDC get channel frequency(Hz) * * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * * @param timer_num LEDC timer index(0-3), select from ledc_timer_t * * @return * - 0 error * - Others Current LEDC frequency - * */ uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num); /** - * @brief LEDC set duty - * - * Set LEDC duty, After the function calls the ledc_update_duty function, the function can take effect. + * @brief LEDC set duty + * Only after calling ledc_update_duty will the duty update. * * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * * @param channel LEDC channel(0-7), select from ledc_channel_t - * * @param duty Set the LEDC duty, the duty range is [0, (2**bit_num) - 1] * * @return @@ -216,37 +203,27 @@ uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num); esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty); /** - * @brief LEDC get duty + * @brief LEDC get duty * * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * * @param channel LEDC channel(0-7), select from ledc_channel_t * - * * @return * - (-1) parameter error * - Others Current LEDC duty - * */ int ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel); /** - * @brief LEDC set gradient - * - * Set LEDC gradient, After the function calls the ledc_update_duty function, the function can take effect. + * @brief LEDC set gradient + * Set LEDC gradient, After the function calls the ledc_update_duty function, the function can take effect. * * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * * @param channel LEDC channel(0-7), select from ledc_channel_t - * * @param duty Set the start of the gradient duty, the duty range is [0, (2**bit_num) - 1] - * * @param gradule_direction Set the direction of the gradient - * * @param step_num Set the number of the gradient - * * @param duty_cyle_num Set how many LEDC tick each time the gradient lasts - * * @param duty_scale Set gradient change amplitude * * @return @@ -257,16 +234,16 @@ esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t uint32_t step_num, uint32_t duty_cyle_num, uint32_t duty_scale); /** - * @brief register LEDC interrupt handler, the handler is an ISR. - * The handler will be attached to the same CPU core that this function is running on. + * @brief Register LEDC interrupt handler, the handler is an ISR. + * The handler will be attached to the same CPU core that this function is running on. * - * @param fn Interrupt handler function. - * @param arg User-supplied argument passed to the handler function. - * @param intr_alloc_flags Flags used to allocate the interrupt. One or multiple (ORred) - * ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info. - * @param arg Parameter for handler function - * @param handle Pointer to return handle. If non-NULL, a handle for the interrupt will - * be returned here. + * @param fn Interrupt handler function. + * @param arg User-supplied argument passed to the handler function. + * @param intr_alloc_flags Flags used to allocate the interrupt. One or multiple (ORred) + * ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info. + * @param arg Parameter for handler function + * @param handle Pointer to return handle. If non-NULL, a handle for the interrupt will + * be returned here. * * @return * - ESP_OK Success @@ -275,48 +252,38 @@ esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t esp_err_t ledc_isr_register(void (*fn)(void*), void * arg, int intr_alloc_flags, ledc_isr_handle_t *handle); /** - * @brief configure LEDC settings + * @brief Configure LEDC settings * - * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * - * @param timer_sel Timer index(0-3), there are 4 timers in LEDC module - * - * @param div_num Timer clock divide number, the timer clock is divided from the selected clock source - * - * @param bit_num The count number of one period, counter range is 0 ~ ((2 ** bit_num) - 1) - * - * @param clk_src Select LEDC source clock. + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param timer_sel Timer index(0-3), there are 4 timers in LEDC module + * @param div_num Timer clock divide number, the timer clock is divided from the selected clock source + * @param bit_num The count number of one period, counter range is 0 ~ ((2 ** bit_num) - 1) + * @param clk_src Select LEDC source clock. * * @return * - (-1) Parameter error * - Other Current LEDC duty - * */ esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_t div_num, uint32_t bit_num, ledc_clk_src_t clk_src); /** - * @brief reset LEDC timer + * @brief Reset LEDC timer * * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * * @param timer_sel LEDC timer index(0-3), select from ledc_timer_t * - * * @return * - ESP_ERR_INVALID_ARG Parameter error * - ESP_OK Success - * */ esp_err_t ledc_timer_rst(ledc_mode_t speed_mode, uint32_t timer_sel); /** - * @brief pause LEDC timer counter + * @brief Pause LEDC timer counter * * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * * @param timer_sel LEDC timer index(0-3), select from ledc_timer_t * - * * @return * - ESP_ERR_INVALID_ARG Parameter error * - ESP_OK Success @@ -325,104 +292,96 @@ esp_err_t ledc_timer_rst(ledc_mode_t speed_mode, uint32_t timer_sel); esp_err_t ledc_timer_pause(ledc_mode_t speed_mode, uint32_t timer_sel); /** - * @brief pause LEDC timer resume - * - * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * - * @param timer_sel LEDC timer index(0-3), select from ledc_timer_t + * @brief Resume LEDC timer * + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param timer_sel LEDC timer index(0-3), select from ledc_timer_t * * @return * - ESP_ERR_INVALID_ARG Parameter error * - ESP_OK Success - * */ esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, uint32_t timer_sel); /** - * @brief bind LEDC channel with the selected timer - * - * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version - * - * @param channel LEDC channel index(0-7), select from ledc_channel_t - * - * @param timer_idx LEDC timer index(0-3), select from ledc_timer_t + * @brief Bind LEDC channel with the selected timer * + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, now we only support high-speed mode. We will access low-speed mode in next version + * @param channel LEDC channel index(0-7), select from ledc_channel_t + * @param timer_idx LEDC timer index(0-3), select from ledc_timer_t * * @return * - ESP_ERR_INVALID_ARG Parameter error * - ESP_OK Success + */ +esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, uint32_t channel, uint32_t timer_idx); + +/** + * @brief Set LEDC fade function. Should call ledc_fade_func_install() before calling this function. + * Call ledc_fade_start() after this to start fading. + * + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, + * For now we only support high-speed mode. We will access low-speed mode soon. + * @param channel LEDC channel index(0-7), select from ledc_channel_t + * @param target_duty Target duty of fading.( 0 - (2 ** bit_num - 1))) + * @param scale Controls the increase or decrease step scale. + * @param cycle_num increase or decrease the duty every cycle_num cycles + * + * @return + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE Fade function not installed. + */ +esp_err_t ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel, int target_duty, int scale, int cycle_num); + +/** + * @brief Set LEDC fade function, with a limited time. Should call ledc_fade_func_install() before calling this function. + * Call ledc_fade_start() after this to start fading. + * + * @param speed_mode Select the LEDC speed_mode, high-speed mode and low-speed mode, + * For now we only support high-speed mode. We will access low-speed mode soon. + * @param channel LEDC channel index(0-7), select from ledc_channel_t + * @param target_duty Target duty of fading.( 0 - (2 ** bit_num - 1))) + * @param max_fade_time_ms The maximum time of the fading ( ms ). + * + * @return + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE Fade function not installed. + */ +esp_err_t ledc_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t channel, int target_duty, int max_fade_time_ms); + +/** + * @brief Install ledc fade function. This function will occupy interrupt of LEDC module. + * + * @param intr_alloc_flags Flags used to allocate the interrupt. One or multiple (ORred) + * ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info. + * + * @return + * - ESP_ERR_NO_MEM No enough memory + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE Fade function already installed. + */ +esp_err_t ledc_fade_func_install(int intr_alloc_flags); + +/** + * @brief Uninstall LEDC fade function. * */ -esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t timer_idx); +void ledc_fade_func_uninstall(); -/***************************EXAMPLE********************************** +/** + * @brief Start LEDC fading. * + * @param channel LEDC channel number + * @param wait_done Whether to block until fading done. * - * ----------------EXAMPLE OF LEDC SETTING --------------------- - * @code{c} - * //1. enable LEDC - * //enable LEDC module, or you can not set any register of it. - * periph_module_enable(PERIPH_LEDC_MODULE); - * @endcode - * - * @code{c} - * //2. set LEDC timer - * ledc_timer_config_t timer_conf = { - * .bit_num = LEDC_TIMER_12_BIT, //set timer counter bit number - * .freq_hz = 1000, //set frequency of pwm, here, 1000Hz - * .speed_mode = LEDC_HIGH_SPEED_MODE, //timer mode, - * .timer_num = LEDC_TIMER_0, //timer number - * }; - * ledc_timer_config(&timer_conf); //setup timer. - * @endcode - * - * @code{c} - * //3. set LEDC channel - * ledc_channel_config_t ledc_conf = { - * .channel = LEDC_CHANNEL_0; //set LEDC channel 0 - * .duty = 1000; //set the duty for initialization.(duty range is 0 ~ ((2**bit_num)-1) - * .gpio_num = 16; //GPIO number - * .intr_type = LEDC_INTR_FADE_END; //GPIO INTR TYPE, as an example, we enable fade_end interrupt here. - * .speed_mode = LEDC_HIGH_SPEED_MODE; //set LEDC mode, from ledc_mode_t - * .timer_sel = LEDC_TIMER_0; //set LEDC timer source, if different channel use one timer, the frequency and bit_num of these channels should be the same - * } - * ledc_channel_config(&ledc_conf); //setup the configuration - * - * ----------------EXAMPLE OF SETTING DUTY --- ----------------- - * @code{c} - * ledc_channel_t ledc_channel = LEDC_CHANNEL_0; //LEDC channel(0-73) - * uint32_t duty = 2000; //duty range is 0 ~ ((2**bit_num)-1) - * LEDC_set_duty(LEDC_HIGH_SPEED_MODE, ledc_channel, duty); //set speed mode, channel, and duty. - * ledc_update_duty(LEDC_HIGH_SPEED_MODE, ledc_channel); //after set duty, we need to call ledc_update_duty to update the settings. - * @endcode - * - * ----------------EXAMPLE OF LEDC INTERRUPT ------------------ - * @code{c} - * //we have fade_end interrupt and counter overflow interrupt. we just give an example of fade_end interrupt here. - * ledc_isr_register(ledc_isr_handler, NULL, 0); //hook the isr handler for LEDC interrupt - * @endcode - * - * ----------------EXAMPLE OF INTERRUPT HANDLER --------------- - * @code{c} - * #include "esp_attr.h" - * void IRAM_ATTR ledc_isr_handler(void* arg) //we should add 'IRAM_ATTR' attribution when we declare the isr function - * { - * uint32_t intr_st = LEDC.int_st.val; //read LEDC interrupt status. - * - * //you will find which channels have triggered fade_end interrupt here, - * //then, you can post some event to RTOS queue to process the event. - * //later we will add a queue in the driver code. - * - * LEDC.int_clr.val = intr_st; //clear LEDC interrupt status. - * } - * @endcode - * - *--------------------------END OF EXAMPLE -------------------------- + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE Fade function not installed. + * - ESP_ERR_INVALID_ARG Parameter error. */ - - - +esp_err_t ledc_fade_start(ledc_channel_t channel, ledc_fade_mode_t wait_done); #ifdef __cplusplus } diff --git a/components/driver/ledc.c b/components/driver/ledc.c index c00cf26bb1..6944f1b115 100644 --- a/components/driver/ledc.c +++ b/components/driver/ledc.c @@ -19,6 +19,8 @@ #include "freertos/xtensa_api.h" #include "soc/gpio_sig_map.h" #include "driver/ledc.h" +#include "soc/ledc_reg.h" +#include "soc/ledc_struct.h" #include "esp_log.h" static const char* LEDC_TAG = "ledc"; @@ -29,26 +31,56 @@ static portMUX_TYPE ledc_spinlock = portMUX_INITIALIZER_UNLOCKED; return (ret_val); \ } +typedef struct { + uint16_t speed_mode; + uint16_t direction; + int target_duty; + int cycle_num; + int scale; + ledc_fade_mode_t mode; + xSemaphoreHandle ledc_fade_sem; + xSemaphoreHandle ledc_fade_mux; +} ledc_fade_t; +static ledc_fade_t* s_ledc_fade_rec = NULL; +static ledc_isr_handle_t s_ledc_fade_isr_handle = NULL; + +#define LEDC_VAL_NO_CHANGE (-1) +#define LEDC_STEP_NUM_MAX (1023) +#define LEDC_DUTY_DECIMAL_BIT_NUM (4) + +#define LEDC_MODE_ERR_STR "LEDC mode error" +#define LEDC_TIMER_ERR_STR "LEDC timer error" +#define LEDC_CHANNEL_ERR_STR "LEDC channel error" +#define LEDC_GPIO_OUT_ERR_STR "LEDC GPIO output number error" +#define LEDC_FADE_DIR_ERR_STR "LEDC fade direction error" +#define LEDC_FADE_SERVICE_ERR_STR "LEDC fade service not installed" +#define LEDC_FADE_TARGET_ERR_STR "LEDC fade target duty error" +#define LEDC_FADE_INSTALLED_ERR_STR "LEDC fade service already installed" +#define LEDC_FADE_MODE_ERR_STR "LEDC fade mode error" + + esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_t div_num, uint32_t bit_num, ledc_clk_src_t clk_src) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(timer_sel <= LEDC_TIMER_3, "ledc timer error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(timer_sel <= LEDC_TIMER_3, LEDC_TIMER_ERR_STR, ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); LEDC.timer_group[speed_mode].timer[timer_sel].conf.div_num = div_num; LEDC.timer_group[speed_mode].timer[timer_sel].conf.tick_sel = clk_src; LEDC.timer_group[speed_mode].timer[timer_sel].conf.bit_num = bit_num; - if(speed_mode != LEDC_HIGH_SPEED_MODE) { + if (speed_mode != LEDC_HIGH_SPEED_MODE) { LEDC.timer_group[speed_mode].timer[timer_sel].conf.low_speed_update = 1; } portEXIT_CRITICAL(&ledc_spinlock); return ESP_OK; } -static esp_err_t ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel_num, uint32_t hpoint_val, uint32_t duty_val, +static IRAM_ATTR esp_err_t ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel_num, int hpoint_val, uint32_t duty_val, uint32_t duty_direction, uint32_t duty_num, uint32_t duty_cycle, uint32_t duty_scale) { portENTER_CRITICAL(&ledc_spinlock); - LEDC.channel_group[speed_mode].channel[channel_num].hpoint.hpoint = hpoint_val; + if (hpoint_val >= 0) { + LEDC.channel_group[speed_mode].channel[channel_num].hpoint.hpoint = hpoint_val; + } LEDC.channel_group[speed_mode].channel[channel_num].duty.duty = duty_val; LEDC.channel_group[speed_mode].channel[channel_num].conf1.val = ((duty_direction & LEDC_DUTY_INC_HSCH0_V) << LEDC_DUTY_INC_HSCH0_S) | ((duty_num & LEDC_DUTY_NUM_HSCH0_V) << LEDC_DUTY_NUM_HSCH0_S) | @@ -60,8 +92,8 @@ static esp_err_t ledc_duty_config(ledc_mode_t speed_mode, ledc_channel_t channel esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t timer_idx) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(timer_idx <= LEDC_TIMER_3, "ledc timer error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(timer_idx <= LEDC_TIMER_3, LEDC_TIMER_ERR_STR, ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); LEDC.channel_group[speed_mode].channel[channel].conf0.timer_sel = timer_idx; portEXIT_CRITICAL(&ledc_spinlock); @@ -70,8 +102,8 @@ esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel esp_err_t ledc_timer_rst(ledc_mode_t speed_mode, uint32_t timer_sel) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(timer_sel <= LEDC_TIMER_3, "ledc timer error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(timer_sel <= LEDC_TIMER_3, LEDC_TIMER_ERR_STR, ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); LEDC.timer_group[speed_mode].timer[timer_sel].conf.rst = 1; LEDC.timer_group[speed_mode].timer[timer_sel].conf.rst = 0; @@ -81,8 +113,8 @@ esp_err_t ledc_timer_rst(ledc_mode_t speed_mode, uint32_t timer_sel) esp_err_t ledc_timer_pause(ledc_mode_t speed_mode, uint32_t timer_sel) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(timer_sel <= LEDC_TIMER_3, "ledc timer error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(timer_sel <= LEDC_TIMER_3, LEDC_TIMER_ERR_STR, ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); LEDC.timer_group[speed_mode].timer[timer_sel].conf.pause = 1; portEXIT_CRITICAL(&ledc_spinlock); @@ -91,8 +123,8 @@ esp_err_t ledc_timer_pause(ledc_mode_t speed_mode, uint32_t timer_sel) esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, uint32_t timer_sel) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(timer_sel <= LEDC_TIMER_3, "ledc timer error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(timer_sel <= LEDC_TIMER_3, LEDC_TIMER_ERR_STR, ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); LEDC.timer_group[speed_mode].timer[timer_sel].conf.pause = 0; portEXIT_CRITICAL(&ledc_spinlock); @@ -101,15 +133,15 @@ esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, uint32_t timer_sel) static esp_err_t ledc_enable_intr_type(ledc_mode_t speed_mode, uint32_t channel, ledc_intr_type_t type) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); uint32_t value; uint32_t intr_type = type; portENTER_CRITICAL(&ledc_spinlock); value = LEDC.int_ena.val; - if(intr_type == LEDC_INTR_FADE_END) { + if (intr_type == LEDC_INTR_FADE_END) { LEDC.int_ena.val = value | BIT(LEDC_DUTY_CHNG_END_HSCH0_INT_ENA_S + channel); } else { - LEDC.int_ena.val = (value & (~(BIT(LEDC_DUTY_CHNG_END_HSCH0_INT_ENA_S + channel)))); + LEDC.int_ena.val = ( value & ( ~( BIT(LEDC_DUTY_CHNG_END_HSCH0_INT_ENA_S + channel) ) ) ); } portEXIT_CRITICAL(&ledc_spinlock); return ESP_OK; @@ -120,7 +152,7 @@ esp_err_t ledc_isr_register(void (*fn)(void*), void * arg, int intr_alloc_flags, esp_err_t ret; LEDC_CHECK(fn, "ledc isr null", ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); - ret=esp_intr_alloc(ETS_LEDC_INTR_SOURCE, intr_alloc_flags, fn, arg, handle); + ret = esp_intr_alloc(ETS_LEDC_INTR_SOURCE, intr_alloc_flags, fn, arg, handle); portEXIT_CRITICAL(&ledc_spinlock); return ret; } @@ -131,32 +163,35 @@ esp_err_t ledc_timer_config(ledc_timer_config_t* timer_conf) int bit_num = timer_conf->bit_num; int timer_num = timer_conf->timer_num; int speed_mode = timer_conf->speed_mode; - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - if(freq_hz == 0 || bit_num == 0 || bit_num > LEDC_TIMER_15_BIT) { + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + periph_module_enable(PERIPH_LEDC_MODULE); + if (freq_hz == 0 || bit_num == 0 || bit_num > LEDC_TIMER_15_BIT) { ESP_LOGE(LEDC_TAG, "freq_hz=%u bit_num=%u", freq_hz, bit_num); return ESP_ERR_INVALID_ARG; } - if(timer_num > LEDC_TIMER_3) { + if (timer_num > LEDC_TIMER_3) { ESP_LOGE(LEDC_TAG, "invalid timer #%u", timer_num); return ESP_ERR_INVALID_ARG; } esp_err_t ret = ESP_OK; - uint32_t precision = (0x1 << bit_num); // 2**depth + uint32_t precision = ( 0x1 << bit_num ); // 2**depth // Try calculating divisor based on LEDC_APB_CLK ledc_clk_src_t timer_clk_src = LEDC_APB_CLK; // div_param is a Q10.8 fixed point value - uint64_t div_param = ((uint64_t) LEDC_APB_CLK_HZ << 8) / freq_hz / precision; + uint64_t div_param = ( (uint64_t) LEDC_APB_CLK_HZ << 8 ) / freq_hz / precision; if (div_param < 256) { // divisor is too low - ESP_LOGE(LEDC_TAG, "requested frequency and bit depth can not be achieved, try reducing freq_hz or bit_num. div_param=%d", (uint32_t) div_param); + ESP_LOGE(LEDC_TAG, "requested frequency and bit depth can not be achieved, try reducing freq_hz or bit_num. div_param=%d", + (uint32_t ) div_param); ret = ESP_FAIL; } if (div_param > LEDC_DIV_NUM_HSTIMER0_V) { // APB_CLK results in divisor which too high. Try using REF_TICK as clock source. timer_clk_src = LEDC_REF_TICK; - div_param = ((uint64_t) LEDC_REF_CLK_HZ << 8) / freq_hz / precision; - if(div_param < 256 || div_param > LEDC_DIV_NUM_HSTIMER0_V) { - ESP_LOGE(LEDC_TAG, "requested frequency and bit depth can not be achieved, try increasing freq_hz or bit_num. div_param=%d", (uint32_t) div_param); + div_param = ( (uint64_t) LEDC_REF_CLK_HZ << 8 ) / freq_hz / precision; + if (div_param < 256 || div_param > LEDC_DIV_NUM_HSTIMER0_V) { + ESP_LOGE(LEDC_TAG, "requested frequency and bit depth can not be achieved, try increasing freq_hz or bit_num. div_param=%d", + (uint32_t ) div_param); ret = ESP_FAIL; } } @@ -169,12 +204,12 @@ esp_err_t ledc_timer_config(ledc_timer_config_t* timer_conf) esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t ledc_channel) { - LEDC_CHECK(ledc_channel <= LEDC_CHANNEL_7, "ledc channel error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), "ledc GPIO output number error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(ledc_channel <= LEDC_CHANNEL_7, LEDC_CHANNEL_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), LEDC_GPIO_OUT_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpio_num], PIN_FUNC_GPIO); gpio_set_direction(gpio_num, GPIO_MODE_OUTPUT); - if(speed_mode == LEDC_HIGH_SPEED_MODE) { + if (speed_mode == LEDC_HIGH_SPEED_MODE) { gpio_matrix_out(gpio_num, LEDC_HS_SIG_OUT0_IDX + ledc_channel, 0, 0); } else { ESP_LOGE(LEDC_TAG, "low speed mode is not implemented"); @@ -191,10 +226,10 @@ esp_err_t ledc_channel_config(ledc_channel_config_t* ledc_conf) uint32_t timer_select = ledc_conf->timer_sel; uint32_t intr_type = ledc_conf->intr_type; uint32_t duty = ledc_conf->duty; - LEDC_CHECK(ledc_channel <= LEDC_CHANNEL_7, "ledc channel error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), "ledc GPIO output number error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(timer_select <= LEDC_TIMER_3, "ledc timer error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(ledc_channel <= LEDC_CHANNEL_7, LEDC_CHANNEL_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), LEDC_GPIO_OUT_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(timer_select <= LEDC_TIMER_3, LEDC_TIMER_ERR_STR, ESP_ERR_INVALID_ARG); periph_module_enable(PERIPH_LEDC_MODULE); esp_err_t ret = ESP_OK; /*set channel parameters*/ @@ -207,7 +242,7 @@ esp_err_t ledc_channel_config(ledc_channel_config_t* ledc_conf) ledc_bind_channel_timer(speed_mode, ledc_channel, timer_select); /*set interrupt type*/ ledc_enable_intr_type(speed_mode, ledc_channel, intr_type); - ESP_LOGI(LEDC_TAG, "LEDC_PWM CHANNEL %1u|GPIO %02u|Duty %04u|Time %01u", + ESP_LOGD(LEDC_TAG, "LEDC_PWM CHANNEL %1u|GPIO %02u|Duty %04u|Time %01u", ledc_channel, gpio_num, duty, timer_select ); /*set LEDC signal in gpio matrix*/ @@ -219,8 +254,8 @@ esp_err_t ledc_channel_config(ledc_channel_config_t* ledc_conf) esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(channel <= LEDC_CHANNEL_7, "ledc channel error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(channel <= LEDC_CHANNEL_7, LEDC_CHANNEL_ERR_STR, ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); LEDC.channel_group[speed_mode].channel[channel].conf0.sig_out_en = 1; LEDC.channel_group[speed_mode].channel[channel].conf1.duty_start = 1; @@ -230,8 +265,8 @@ esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel) esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idle_level) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(channel <= LEDC_CHANNEL_7, "ledc channel error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(channel <= LEDC_CHANNEL_7, LEDC_CHANNEL_ERR_STR, ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); LEDC.channel_group[speed_mode].channel[channel].conf0.idle_lv = idle_level & 0x1; LEDC.channel_group[speed_mode].channel[channel].conf0.sig_out_en = 0; @@ -239,16 +274,20 @@ esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idl portEXIT_CRITICAL(&ledc_spinlock); return ESP_OK; } + esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, ledc_duty_direction_t fade_direction, uint32_t step_num, uint32_t duty_cyle_num, uint32_t duty_scale) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(channel <= LEDC_CHANNEL_7, "ledc channel error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(fade_direction <= LEDC_DUTY_DIR_INCREASE, "ledc fade direction error", ESP_ERR_INVALID_ARG); - if(step_num > LEDC_DUTY_NUM_HSCH0_V || duty_cyle_num > LEDC_DUTY_CYCLE_HSCH0_V || duty_scale > LEDC_DUTY_SCALE_HSCH0_V) { + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(channel <= LEDC_CHANNEL_7, LEDC_CHANNEL_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(fade_direction <= LEDC_DUTY_DIR_INCREASE, LEDC_FADE_DIR_ERR_STR, ESP_ERR_INVALID_ARG); + if (step_num > LEDC_DUTY_NUM_HSCH0_V || duty_cyle_num > LEDC_DUTY_CYCLE_HSCH0_V || duty_scale > LEDC_DUTY_SCALE_HSCH0_V) { ESP_LOGE(LEDC_TAG, "step_num=%u duty_cyle_num=%u duty_scale=%u", step_num, duty_cyle_num, duty_scale); return ESP_ERR_INVALID_ARG; } + if (s_ledc_fade_rec) { + ledc_enable_intr_type(speed_mode, channel, LEDC_INTR_DISABLE); + } ledc_duty_config(speed_mode, channel, //uint32_t chan_num, 0, //uint32_t hpoint_val, @@ -263,8 +302,11 @@ esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); - LEDC_CHECK(channel <= LEDC_CHANNEL_7, "ledc channel error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(channel <= LEDC_CHANNEL_7, LEDC_CHANNEL_ERR_STR, ESP_ERR_INVALID_ARG); + if (s_ledc_fade_rec) { + ledc_enable_intr_type(speed_mode, channel, LEDC_INTR_DISABLE); + } ledc_duty_config(speed_mode, channel, //uint32_t chan_num, 0, //uint32_t hpoint_val, @@ -279,26 +321,26 @@ esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t int ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", (-1)); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, (-1)); uint32_t duty = (LEDC.channel_group[speed_mode].channel[channel].duty_rd.duty_read >> 4); return duty; } esp_err_t ledc_set_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num, uint32_t freq_hz) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", ESP_ERR_INVALID_ARG); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); portENTER_CRITICAL(&ledc_spinlock); esp_err_t ret = ESP_OK; uint32_t div_num = 0; uint32_t bit_num = LEDC.timer_group[speed_mode].timer[timer_num].conf.bit_num; uint32_t timer_source_clk = LEDC.timer_group[speed_mode].timer[timer_num].conf.tick_sel; - uint32_t precision = (0x1 << bit_num); - if(timer_source_clk == LEDC_APB_CLK) { - div_num = ((uint64_t) LEDC_APB_CLK_HZ << 8) / freq_hz / precision; + uint32_t precision = ( 0x1 << bit_num ); + if (timer_source_clk == LEDC_APB_CLK) { + div_num = ( (uint64_t) LEDC_APB_CLK_HZ << 8 ) / freq_hz / precision; } else { - div_num = ((uint64_t) LEDC_REF_CLK_HZ << 8) / freq_hz / precision; + div_num = ( (uint64_t) LEDC_REF_CLK_HZ << 8 ) / freq_hz / precision; } - if(div_num <= 256 || div_num > LEDC_DIV_NUM_HSTIMER0) { + if (div_num <= 256 || div_num > LEDC_DIV_NUM_HSTIMER0) { ESP_LOGE(LEDC_TAG, "div param err,div_param=%u", div_num); ret = ESP_FAIL; } @@ -309,18 +351,217 @@ esp_err_t ledc_set_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num, uint32_t uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num) { - LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "ledc mode error", (0)); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ( 0 )); portENTER_CRITICAL(&ledc_spinlock); uint32_t freq = 0; uint32_t timer_source_clk = LEDC.timer_group[speed_mode].timer[timer_num].conf.tick_sel; uint32_t bit_num = LEDC.timer_group[speed_mode].timer[timer_num].conf.bit_num; uint32_t div_num = LEDC.timer_group[speed_mode].timer[timer_num].conf.div_num; - uint32_t precision = (0x1 << bit_num); - if(timer_source_clk == LEDC_APB_CLK) { - freq = ((uint64_t) LEDC_APB_CLK_HZ << 8) / precision / div_num; + uint32_t precision = ( 0x1 << bit_num ); + if (timer_source_clk == LEDC_APB_CLK) { + freq = ( (uint64_t) LEDC_APB_CLK_HZ << 8 ) / precision / div_num; } else { - freq = ((uint64_t) LEDC_REF_CLK_HZ << 8) / precision / div_num; + freq = ( (uint64_t) LEDC_REF_CLK_HZ << 8 ) / precision / div_num; } portEXIT_CRITICAL(&ledc_spinlock); return freq; } + +void IRAM_ATTR ledc_fade_isr() +{ + int i; + portBASE_TYPE HPTaskAwoken = pdFALSE; + uint32_t intr_status = LEDC.int_st.val; //read LEDC interrupt status. + LEDC.int_clr.val = intr_status; //clear LEDC interrupt status. + for (i = 0; i < 8; i++) { + if (intr_status & BIT(LEDC_DUTY_CHNG_END_HSCH0_INT_ST_S + i)) { + int speed_mode = s_ledc_fade_rec[i].speed_mode; + int duty_cur = LEDC.channel_group[speed_mode].channel[i].duty_rd.duty_read >> LEDC_DUTY_DECIMAL_BIT_NUM; + if (duty_cur == s_ledc_fade_rec[i].target_duty) { + if(s_ledc_fade_rec[i].mode == LEDC_FADE_WAIT_DONE) { + xSemaphoreGiveFromISR(s_ledc_fade_rec[i].ledc_fade_sem, &HPTaskAwoken); + if(HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR() ; + } + } + continue; + } + int duty_tar = s_ledc_fade_rec[i].target_duty; + int scale = s_ledc_fade_rec[i].scale; + if (scale == 0) { + continue; + } + int cycle = s_ledc_fade_rec[i].cycle_num; + int delta = s_ledc_fade_rec[i].direction == LEDC_DUTY_DIR_DECREASE ? duty_cur - duty_tar : duty_tar - duty_cur; + int step = delta / scale > LEDC_STEP_NUM_MAX ? LEDC_STEP_NUM_MAX : delta / scale; + + if (delta > scale) { + ledc_duty_config( + speed_mode, + i, + LEDC_VAL_NO_CHANGE, + duty_cur << LEDC_DUTY_DECIMAL_BIT_NUM, + s_ledc_fade_rec[i].direction, + step, + cycle, + scale); + } else { + ledc_duty_config( + speed_mode, + i, + LEDC_VAL_NO_CHANGE, + duty_tar << LEDC_DUTY_DECIMAL_BIT_NUM, + s_ledc_fade_rec[i].direction, + 1, + 1, + 0); + } + LEDC.channel_group[speed_mode].channel[i].conf1.duty_start = 1; + } + } + LEDC.int_clr.val = intr_status; //clear LEDC interrupt status. +} + +esp_err_t ledc_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t channel, int target_duty, int max_fade_time_ms) +{ + int timer_sel = LEDC.channel_group[speed_mode].channel[channel].conf0.timer_sel; + int max_duty = ( 1 << ( LEDC.timer_group[speed_mode].timer[timer_sel].conf.bit_num ) ) - 1; + LEDC_CHECK(target_duty <= max_duty, LEDC_FADE_TARGET_ERR_STR, ESP_ERR_INVALID_ARG); + uint32_t freq = ledc_get_freq(speed_mode, timer_sel); + int duty_cur = LEDC.channel_group[speed_mode].channel[channel].duty_rd.duty_read >> LEDC_DUTY_DECIMAL_BIT_NUM; + int duty_delta = target_duty > duty_cur ? target_duty - duty_cur : duty_cur - target_duty; + + if (duty_delta == 0) { + return ESP_OK; + } + int total_cycles = max_fade_time_ms * freq / 1000; + int scale, cycle_num; + if (total_cycles > duty_delta) { + scale = 1; + cycle_num = total_cycles / duty_delta; + } else { + cycle_num = 1; + scale = ( duty_delta + total_cycles - 1 ) / total_cycles; + } + return ledc_set_fade_with_step(speed_mode, channel, target_duty, scale, cycle_num); +} + +esp_err_t ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel, int target_duty, int scale, int cycle_num) +{ + LEDC_CHECK(s_ledc_fade_rec != NULL, LEDC_FADE_SERVICE_ERR_STR, ESP_ERR_INVALID_STATE); + LEDC_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, LEDC_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + LEDC_CHECK(channel < LEDC_CHANNEL_MAX, LEDC_CHANNEL_ERR_STR, ESP_ERR_INVALID_ARG); + int timer_sel = LEDC.channel_group[speed_mode].channel[channel].conf0.timer_sel; + int max_duty = (1 << (LEDC.timer_group[speed_mode].timer[timer_sel].conf.bit_num)) - 1; + LEDC_CHECK(target_duty <= max_duty, LEDC_FADE_TARGET_ERR_STR, ESP_ERR_INVALID_ARG); + //disable the interrupt, so the operation will not mess up + ledc_enable_intr_type(speed_mode, channel, LEDC_INTR_DISABLE); + + portENTER_CRITICAL(&ledc_spinlock); + int duty_cur = LEDC.channel_group[speed_mode].channel[channel].duty_rd.duty_read >> LEDC_DUTY_DECIMAL_BIT_NUM; + int duty_delta = target_duty > duty_cur ? target_duty - duty_cur : duty_cur - target_duty; + if (duty_delta == 0) { + return ESP_OK; + } + s_ledc_fade_rec[channel].speed_mode = speed_mode; + s_ledc_fade_rec[channel].target_duty = target_duty; + s_ledc_fade_rec[channel].cycle_num = cycle_num; + s_ledc_fade_rec[channel].scale = scale; + int step_num; + if (duty_cur > target_duty) { + s_ledc_fade_rec[channel].direction = LEDC_DUTY_DIR_DECREASE; + step_num = ( duty_cur - target_duty ) / scale; + step_num = step_num > LEDC_STEP_NUM_MAX ? LEDC_STEP_NUM_MAX : step_num; + } else { + s_ledc_fade_rec[channel].direction = LEDC_DUTY_DIR_INCREASE; + step_num = ( target_duty - duty_cur ) / scale; + step_num = step_num > LEDC_STEP_NUM_MAX ? LEDC_STEP_NUM_MAX : step_num; + } + portEXIT_CRITICAL(&ledc_spinlock); + + ledc_set_fade( + speed_mode, + channel, + duty_cur, + s_ledc_fade_rec[channel].direction, + step_num, + s_ledc_fade_rec[channel].cycle_num, + s_ledc_fade_rec[channel].scale + ); + ESP_LOGD(LEDC_TAG, "cur duty: %d; target: %d, step: %d, cycle: %d; scale: %d\n", + LEDC.channel_group[speed_mode].channel[channel].duty_rd.duty_read >> LEDC_DUTY_DECIMAL_BIT_NUM, + target_duty, + step_num, + s_ledc_fade_rec[channel].cycle_num, + s_ledc_fade_rec[channel].scale + ); + LEDC.int_clr.val |= BIT(LEDC_DUTY_CHNG_END_HSCH0_INT_ENA_S + channel); + ledc_enable_intr_type(speed_mode, channel, LEDC_INTR_FADE_END); + return ESP_OK; +} + +esp_err_t ledc_fade_func_install(int intr_alloc_flags) +{ + LEDC_CHECK(s_ledc_fade_rec == NULL, LEDC_FADE_INSTALLED_ERR_STR, ESP_ERR_INVALID_STATE); + s_ledc_fade_rec = (ledc_fade_t*) calloc(LEDC_CHANNEL_MAX, sizeof(ledc_fade_t)); + if (s_ledc_fade_rec == NULL) { + return ESP_ERR_NO_MEM; + } + int i = 0; + for (i = 0; i < LEDC_CHANNEL_MAX; i++) { + s_ledc_fade_rec[i].ledc_fade_sem = xSemaphoreCreateBinary(); + s_ledc_fade_rec[i].ledc_fade_mux = xSemaphoreCreateMutex(); + if (s_ledc_fade_rec[i].ledc_fade_sem == NULL || s_ledc_fade_rec[i].ledc_fade_mux == NULL) { + ledc_fade_func_uninstall(); + return ESP_ERR_NO_MEM; + } + } + //OR intr_alloc_flags with ESP_INTR_FLAG_IRAM because the fade isr is in IRAM + ledc_isr_register(ledc_fade_isr, NULL, intr_alloc_flags | ESP_INTR_FLAG_IRAM, &s_ledc_fade_isr_handle); + return ESP_OK; +} + +void ledc_fade_func_uninstall() +{ + if (s_ledc_fade_rec == NULL) { + return; + } + if(s_ledc_fade_isr_handle) { + esp_intr_free(s_ledc_fade_isr_handle); + s_ledc_fade_isr_handle = NULL; + } + int i; + for (i = 0; i < LEDC_CHANNEL_MAX; i++) { + if (s_ledc_fade_rec[i].ledc_fade_sem) { + xSemaphoreHandle sem_tmp = s_ledc_fade_rec[i].ledc_fade_sem; + s_ledc_fade_rec[i].ledc_fade_sem = NULL; + vSemaphoreDelete(sem_tmp); + } + if (s_ledc_fade_rec[i].ledc_fade_mux) { + xSemaphoreHandle mux_tmp = s_ledc_fade_rec[i].ledc_fade_mux; + s_ledc_fade_rec[i].ledc_fade_mux = NULL; + vSemaphoreDelete(mux_tmp); + } + } + free(s_ledc_fade_rec); + s_ledc_fade_rec = NULL; + return; +} + +esp_err_t ledc_fade_start(ledc_channel_t channel, ledc_fade_mode_t wait_done) +{ + LEDC_CHECK(s_ledc_fade_rec != NULL, LEDC_FADE_SERVICE_ERR_STR, ESP_ERR_INVALID_STATE); + LEDC_CHECK(wait_done < LEDC_FADE_MAX, LEDC_FADE_MODE_ERR_STR, ESP_ERR_INVALID_ARG); + int speed_mode = s_ledc_fade_rec[channel].speed_mode; + xSemaphoreTake(s_ledc_fade_rec[channel].ledc_fade_mux, portMAX_DELAY); + if (wait_done == LEDC_FADE_WAIT_DONE) { + s_ledc_fade_rec[channel].mode = LEDC_FADE_WAIT_DONE; + ledc_update_duty(speed_mode, channel); + xSemaphoreTake(s_ledc_fade_rec[channel].ledc_fade_sem, portMAX_DELAY); + } else { + s_ledc_fade_rec[channel].mode = LEDC_FADE_NO_WAIT; + ledc_update_duty(speed_mode, channel); + } + xSemaphoreGive(s_ledc_fade_rec[channel].ledc_fade_mux); + return ESP_OK; +} diff --git a/docs/api/ledc.rst b/docs/api/ledc.rst index 855f822163..40855ddb47 100644 --- a/docs/api/ledc.rst +++ b/docs/api/ledc.rst @@ -4,20 +4,19 @@ LED Control Overview -------- -`Instructions`_ +The LED control module is primarily designed to control the intensity of LEDs, although it can be used to generate PWM signals for other purposes as well. +It has 16 channels which can generate independent waveforms that can be used to drive e.g. RGB LED devices. For maximum flexibility, the high-speed as well +as the low-speed channels can be driven from one of four high-speed/low-speed timers. The PWM controller also has the ability to automatically increase or +decrease the duty cycle gradually, allowing for fades without any processor interference. Application Example ------------------- -`Instructions`_ +LEDC change duty cycle and fading control example: `examples/29_ledc `_. API Reference ------------- -`Instructions`_ - -.. _Instructions: template.html - Header Files ^^^^^^^^^^^^ @@ -38,6 +37,11 @@ Macros .. doxygendefine:: LEDC_APB_CLK_HZ .. doxygendefine:: LEDC_REF_CLK_HZ +Type Definitions +^^^^^^^^^^^^^^^^ + +.. doxygentypedef:: ledc_isr_handle_t + Enumerations ^^^^^^^^^^^^ @@ -49,6 +53,16 @@ Enumerations .. doxygenenum:: ledc_channel_t .. doxygenenum:: ledc_timer_bit_t +Structures +^^^^^^^^^^ + +.. doxygenstruct:: ledc_channel_config_t + :members: + +.. doxygenstruct:: ledc_timer_config_t + :members: + + Functions ^^^^^^^^^ @@ -67,3 +81,9 @@ Functions .. doxygenfunction:: ledc_timer_pause .. doxygenfunction:: ledc_timer_resume .. doxygenfunction:: ledc_bind_channel_timer +.. doxygenfunction:: ledc_set_fade_with_step +.. doxygenfunction:: ledc_set_fade_with_time +.. doxygenfunction:: ledc_fade_func_install +.. doxygenfunction:: ledc_fade_func_uninstall +.. doxygenfunction:: ledc_fade_start + diff --git a/examples/29_ledc/Makefile b/examples/29_ledc/Makefile new file mode 100644 index 0000000000..675fc8a38b --- /dev/null +++ b/examples/29_ledc/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 := ledc + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/29_ledc/README.md b/examples/29_ledc/README.md new file mode 100644 index 0000000000..9feea6b25b --- /dev/null +++ b/examples/29_ledc/README.md @@ -0,0 +1,16 @@ +# LEDC(LED control) Example + +###This example shows: + + * init LEDC module: + + a. You need to set the timer of LEDC first, this decide the frequency and resolution of PWM. + + b. You need to set the LEDC channel you want to use, and bind the channel with one of the timers. + + * You can install a default fade function, then you can use fade APIs. + + * You can also set a target duty directly without fading. + + * This example use GPIO18/19/4/5 as LEDC ouput, and it will change the duty repeatedly. + diff --git a/examples/29_ledc/main/component.mk b/examples/29_ledc/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/29_ledc/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/29_ledc/main/ledc_fade.c b/examples/29_ledc/main/ledc_fade.c new file mode 100644 index 0000000000..dcdb0462f6 --- /dev/null +++ b/examples/29_ledc/main/ledc_fade.c @@ -0,0 +1,131 @@ +/* Ledc fade example + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/xtensa_api.h" +#include "freertos/queue.h" +#include "driver/ledc.h" +#include "esp_attr.h" +#include "esp_err.h" + +/* + * About this example + * 1. init LEDC module: + * a. You need to set the timer of LEDC first, this decide the frequency and resolution of PWM. + * b. You need to set the LEDC channel you want to use, and bind with one of the timers. + * + * 2. You can install a default fade function, then you can use fade APIs. + * + * 3. You can also set a target duty directly without fading. + * + * 4. This example use GPIO18/19/4/5 as LEDC ouput, and it will change the duty repeatedly. + * + * + */ + +#define LEDC_IO_0 (18) +#define LEDC_IO_1 (19) +#define LEDC_IO_2 (4) +#define LEDC_IO_3 (5) + +esp_err_t app_main() +{ + ledc_timer_config_t ledc_timer = { + //set timer counter bit number + .bit_num = LEDC_TIMER_13_BIT, + //set frequency of pwm + .freq_hz = 5000, + //timer mode, + .speed_mode = LEDC_HIGH_SPEED_MODE, + //timer index + .timer_num = LEDC_TIMER_0 + }; + ledc_timer_config(&ledc_timer); + + ledc_channel_config_t ledc_channel = { + //set LEDC channel 0 + .channel = LEDC_CHANNEL_0, + //set the duty for initialization.(duty range is 0 ~ ((2**bit_num)-1) + .duty = 100, + //GPIO number + .gpio_num = LEDC_IO_0, + //GPIO INTR TYPE, as an example, we enable fade_end interrupt here. + .intr_type = LEDC_INTR_FADE_END, + //set LEDC mode, from ledc_mode_t + .speed_mode = LEDC_HIGH_SPEED_MODE, + //set LEDC timer source, if different channel use one timer, + //the frequency and bit_num of these channels should be the same + .timer_sel = LEDC_TIMER_0 + }; + //set the configuration + ledc_channel_config(&ledc_channel); + + //config ledc channel1 + ledc_channel.channel = LEDC_CHANNEL_1; + ledc_channel.gpio_num = LEDC_IO_1; + ledc_channel_config(&ledc_channel); + //config ledc channel2 + ledc_channel.channel = LEDC_CHANNEL_2; + ledc_channel.gpio_num = LEDC_IO_2; + ledc_channel_config(&ledc_channel); + //config ledc channel3 + ledc_channel.channel = LEDC_CHANNEL_3; + ledc_channel.gpio_num = LEDC_IO_3; + ledc_channel_config(&ledc_channel); + + //initialize fade service. + ledc_fade_func_install(0); + + while(1) { + printf("LEDC fade up\n"); + ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 1000, 2000); + ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, 7000, 2000); + ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_2, 5000, 2000); + ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_3, 3000, 2000); + ledc_fade_start(LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT); + ledc_fade_start(LEDC_CHANNEL_1, LEDC_FADE_NO_WAIT); + ledc_fade_start(LEDC_CHANNEL_2, LEDC_FADE_NO_WAIT); + ledc_fade_start(LEDC_CHANNEL_3, LEDC_FADE_NO_WAIT); + vTaskDelay(3000 / portTICK_PERIOD_MS); + + printf("LEDC fade down\n"); + ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 100, 2000); + ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, 300, 2000); + ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_2, 500, 2000); + ledc_set_fade_with_time(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_3, 700, 2000); + ledc_fade_start(LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT); + ledc_fade_start(LEDC_CHANNEL_1, LEDC_FADE_NO_WAIT); + ledc_fade_start(LEDC_CHANNEL_2, LEDC_FADE_NO_WAIT); + ledc_fade_start(LEDC_CHANNEL_3, LEDC_FADE_NO_WAIT); + vTaskDelay(3000 / portTICK_PERIOD_MS); + + printf("LEDC set duty without fade\n"); + ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 1000); + ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, 7000); + ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_2, 5000); + ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_3, 3000); + ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0); + ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1); + ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_2); + ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_3); + vTaskDelay(2000 / portTICK_PERIOD_MS); + + printf("LEDC set duty without fade\n"); + ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 0); + ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1, 0); + ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_2, 0); + ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_3, 0); + ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0); + ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_1); + ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_2); + ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_3); + vTaskDelay(2000 / portTICK_PERIOD_MS); + } +} From 5677149833b824e89054cd2ac3d42cf7a24c86c6 Mon Sep 17 00:00:00 2001 From: qiyueixa Date: Fri, 30 Dec 2016 21:22:06 +0800 Subject: [PATCH 062/167] esp32: add wifi low rate feature 1. add low_rate_enable flag to scan results to identify if AP low rate is enabled. 2. add WIFI_PROTOCOL_LR for users to enable low rate feature. --- components/esp32/include/esp_wifi_types.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/esp32/include/esp_wifi_types.h b/components/esp32/include/esp_wifi_types.h index be3183ba5d..88ad3dcf5b 100755 --- a/components/esp32/include/esp_wifi_types.h +++ b/components/esp32/include/esp_wifi_types.h @@ -109,6 +109,8 @@ typedef struct { wifi_second_chan_t second; /**< second channel of AP */ int8_t rssi; /**< signal strength of AP */ wifi_auth_mode_t authmode; /**< authmode of AP */ + uint32_t low_rate_enable:1; /**< bit: 0 flag to identify if low rate is enabled or not */ + uint32_t reserved:31; /**< bit: 1..31 reserved */ } wifi_ap_record_t; typedef enum { @@ -119,6 +121,7 @@ typedef enum { #define WIFI_PROTOCOL_11B 1 #define WIFI_PROTOCOL_11G 2 #define WIFI_PROTOCOL_11N 4 +#define WIFI_PROTOCOL_LR 8 typedef enum { WIFI_BW_HT20 = 1, /* Bandwidth is HT20 */ From 7778273314ef83f12df7c37dc490bccc40c4e8c4 Mon Sep 17 00:00:00 2001 From: qiyueixa Date: Thu, 5 Jan 2017 18:43:09 +0800 Subject: [PATCH 063/167] esp32: update wifi libs 1. add wifi low rate feature --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index 074303d74f..edad974840 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 074303d74fc9c68823adee0a38fc1e8de42943b6 +Subproject commit edad9748406d06bfd2dfba6cf1a0735c3982460f From 0feb3633fc3953babb314ead1ea8c83b55fad7fa Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Thu, 5 Jan 2017 13:17:52 +0200 Subject: [PATCH 064/167] add menuconfig option to enable SO_RCVBUF --- components/lwip/Kconfig | 6 ++++++ components/lwip/include/lwip/port/lwipopts.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/lwip/Kconfig b/components/lwip/Kconfig index 9e2b031674..760e035c8c 100644 --- a/components/lwip/Kconfig +++ b/components/lwip/Kconfig @@ -40,6 +40,12 @@ config LWIP_SO_REUSE Enabling this option allows binding to a port which remains in TIME_WAIT. +config LWIP_SO_RCVBUF + bool "Enable SO_RCVBUF option" + default 0 + help + Enabling this option allows checking for available data on a netconn. + config LWIP_DHCP_MAX_NTP_SERVERS int "Maximum number of NTP servers" default 1 diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 933d55a510..14ec0e67a4 100755 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -440,7 +440,7 @@ /** * LWIP_SO_RCVBUF==1: Enable SO_RCVBUF processing. */ -#define LWIP_SO_RCVBUF 0 +#define LWIP_SO_RCVBUF CONFIG_LWIP_SO_RCVBUF /** * SO_REUSE==1: Enable SO_REUSEADDR option. From 2e7748d625ee02c3e183a7e6d37371d3a213a846 Mon Sep 17 00:00:00 2001 From: wangmengyang Date: Tue, 3 Jan 2017 15:53:06 +0800 Subject: [PATCH 065/167] component/bt: modify bluetooth API 1. VHCI api and doxygen 2. Controller api and doxygen 3. bluedroid init/enable api and doxygen 4. cleanup demo codes --- components/bt/bluedroid/api/esp_bt_main.c | 8 +- .../bt/bluedroid/api/include/esp_bt_main.h | 12 +-- components/bt/bluedroid/hci/hci_hal_h4.c | 8 +- components/bt/bluedroid/hci/hci_layer.c | 2 +- components/bt/bt.c | 26 ++++- components/bt/include/bt.h | 22 ++-- components/esp32/include/esp_phy_init.h | 2 +- docs/api/bt.rst | 2 +- docs/api/{vhci.rst => controller_vhci.rst} | 26 +++-- docs/api/esp_bt_main.rst | 8 +- examples/05_ble_adv/main/app_bt.c | 16 +-- examples/12_blufi/components/blufi/blufi.c | 4 +- examples/12_blufi/main/demo_main.c | 8 +- examples/14_gatt_server/main/gatts_demo.c | 66 +++++------- examples/15_gatt_client/main/gattc_demo.c | 100 +++++++++--------- 15 files changed, 166 insertions(+), 144 deletions(-) rename docs/api/{vhci.rst => controller_vhci.rst} (66%) diff --git a/components/bt/bluedroid/api/esp_bt_main.c b/components/bt/bluedroid/api/esp_bt_main.c index f185999287..2dd9166d22 100644 --- a/components/bt/bluedroid/api/esp_bt_main.c +++ b/components/bt/bluedroid/api/esp_bt_main.c @@ -21,7 +21,7 @@ static bool esp_already_enable = false; static bool esp_already_init = false; -esp_err_t esp_enable_bluetooth(void) +esp_err_t esp_bluedroid_enable(void) { btc_msg_t msg; future_t **future_p; @@ -53,7 +53,7 @@ esp_err_t esp_enable_bluetooth(void) return ESP_OK; } -esp_err_t esp_disable_bluetooth(void) +esp_err_t esp_bluedroid_disable(void) { btc_msg_t msg; future_t **future_p; @@ -85,7 +85,7 @@ esp_err_t esp_disable_bluetooth(void) return ESP_OK; } -esp_err_t esp_init_bluetooth(void) +esp_err_t esp_bluedroid_init(void) { btc_msg_t msg; future_t **future_p; @@ -120,7 +120,7 @@ esp_err_t esp_init_bluetooth(void) } -esp_err_t esp_deinit_bluetooth(void) +esp_err_t esp_bluedroid_deinit(void) { btc_msg_t msg; future_t **future_p; diff --git a/components/bt/bluedroid/api/include/esp_bt_main.h b/components/bt/bluedroid/api/include/esp_bt_main.h index 859da092dd..b24daf33cd 100644 --- a/components/bt/bluedroid/api/include/esp_bt_main.h +++ b/components/bt/bluedroid/api/include/esp_bt_main.h @@ -22,22 +22,22 @@ extern "C" { #endif /** - * @brief Enable bluetooth, must after esp_init_bluetooth() + * @brief Enable bluetooth, must after esp_bluedroid_init() * * @return * - ESP_OK : Succeed * - Other : Failed */ -esp_err_t esp_enable_bluetooth(void); +esp_err_t esp_bluedroid_enable(void); /** - * @brief Disable bluetooth, must prior to esp_deinit_bluetooth() + * @brief Disable bluetooth, must prior to esp_bluedroid_deinit() * * @return * - ESP_OK : Succeed * - Other : Failed */ -esp_err_t esp_disable_bluetooth(void); +esp_err_t esp_bluedroid_disable(void); /** * @brief Init and alloc the resource for bluetooth, must be prior to every bluetooth stuff @@ -46,7 +46,7 @@ esp_err_t esp_disable_bluetooth(void); * - ESP_OK : Succeed * - Other : Failed */ -esp_err_t esp_init_bluetooth(void); +esp_err_t esp_bluedroid_init(void); /** * @brief Deinit and free the resource for bluetooth, must be after every bluetooth stuff @@ -55,7 +55,7 @@ esp_err_t esp_init_bluetooth(void); * - ESP_OK : Succeed * - Other : Failed */ -esp_err_t esp_deinit_bluetooth(void); +esp_err_t esp_bluedroid_deinit(void); #ifdef __cplusplus } diff --git a/components/bt/bluedroid/hci/hci_hal_h4.c b/components/bt/bluedroid/hci/hci_hal_h4.c index 237226266a..eb78cab5d3 100644 --- a/components/bt/bluedroid/hci/hci_hal_h4.c +++ b/components/bt/bluedroid/hci/hci_hal_h4.c @@ -57,7 +57,7 @@ typedef struct { static hci_hal_env_t hci_hal_env; static const hci_hal_t interface; static const hci_hal_callbacks_t *callbacks; -static const vhci_host_callback_t vhci_host_cb; +static const esp_vhci_host_callback_t vhci_host_cb; static xTaskHandle xHciH4TaskHandle; static xQueueHandle xHciH4Queue; @@ -105,7 +105,7 @@ static bool hal_open(const hci_hal_callbacks_t *upper_callbacks) xTaskCreate(hci_hal_h4_rx_handler, HCI_H4_TASK_NAME, HCI_H4_TASK_STACK_SIZE, NULL, HCI_H4_TASK_PRIO, &xHciH4TaskHandle); //register vhci host cb - API_vhci_host_register_callback(&vhci_host_cb); + esp_vhci_host_register_callback(&vhci_host_cb); return true; @@ -148,7 +148,7 @@ static uint16_t transmit_data(serial_data_type_t type, BTTRC_DUMP_BUFFER("Transmit Pkt", data, length); // TX Data to target - API_vhci_host_send_packet(data, length); + esp_vhci_host_send_packet(data, length); // Be nice and restore the old value of that byte *(data) = previous_byte; @@ -278,7 +278,7 @@ static int host_recv_pkt_cb(uint8_t *data, uint16_t len) return 0; } -static const vhci_host_callback_t vhci_host_cb = { +static const esp_vhci_host_callback_t vhci_host_cb = { .notify_host_send_available = host_send_pkt_available_cb, .notify_host_recv = host_recv_pkt_cb, }; diff --git a/components/bt/bluedroid/hci/hci_layer.c b/components/bt/bluedroid/hci/hci_layer.c index d71690d4a6..747a582953 100644 --- a/components/bt/bluedroid/hci/hci_layer.c +++ b/components/bt/bluedroid/hci/hci_layer.c @@ -228,7 +228,7 @@ static void hci_host_thread_handler(void *arg) if (pdTRUE == xQueueReceive(xHciHostQueue, &e, (portTickType)portMAX_DELAY)) { if (e.sig == 0xff) { - if (API_vhci_host_check_send_available()) { + if (esp_vhci_host_check_send_available()) { /*Now Target only allowed one packet per TX*/ BT_HDR *pkt = packet_fragmenter->fragment_current_packet(); if (pkt != NULL) { diff --git a/components/bt/bt.c b/components/bt/bt.c index 8d1fecb777..5bad45ea2c 100644 --- a/components/bt/bt.c +++ b/components/bt/bt.c @@ -35,6 +35,15 @@ extern void btdm_osi_funcs_register(void *osi_funcs); extern void btdm_controller_init(void); +/* VHCI function interface */ +typedef struct vhci_host_callback { + void (*notify_host_send_available)(void); /*!< callback used to notify that the host can send packet to controller */ + int (*notify_host_recv)(uint8_t *data, uint16_t len); /*!< callback used to notify that the controller has a packet to send to the host*/ +} vhci_host_callback_t; + +extern bool API_vhci_host_check_send_available(void); +extern void API_vhci_host_send_packet(uint8_t *data, uint16_t len); +extern void API_vhci_host_register_callback(const vhci_host_callback_t *callback); #define BT_DEBUG(...) #define BT_API_CALL_CHECK(info, api_call, ret) \ @@ -118,13 +127,28 @@ static struct osi_funcs_t osi_funcs = { ._read_efuse_mac = esp_efuse_read_mac, }; +bool esp_vhci_host_check_send_available(void) +{ + return API_vhci_host_check_send_available(); +} + +void esp_vhci_host_send_packet(uint8_t *data, uint16_t len) +{ + API_vhci_host_send_packet(data, len); +} + +void esp_vhci_host_register_callback(const esp_vhci_host_callback_t *callback) +{ + API_vhci_host_register_callback((const vhci_host_callback_t *)callback); +} + static void bt_controller_task(void *pvParam) { btdm_osi_funcs_register(&osi_funcs); btdm_controller_init(); } -void bt_controller_init() +void esp_bt_controller_init() { xTaskCreatePinnedToCore(bt_controller_task, "btController", ESP_TASK_BT_CONTROLLER_STACK, NULL, diff --git a/components/bt/include/bt.h b/components/bt/include/bt.h index 310fdcb921..926ecfadcd 100644 --- a/components/bt/include/bt.h +++ b/components/bt/include/bt.h @@ -29,35 +29,35 @@ extern "C" { * * This function should be called only once, before any other BT functions are called. */ -void bt_controller_init(void); +void esp_bt_controller_init(void); -/** @brief vhci_host_callback +/** @brief esp_vhci_host_callback * used for vhci call host function to notify what host need to do */ -typedef struct vhci_host_callback { +typedef struct esp_vhci_host_callback { void (*notify_host_send_available)(void); /*!< callback used to notify that the host can send packet to controller */ int (*notify_host_recv)(uint8_t *data, uint16_t len); /*!< callback used to notify that the controller has a packet to send to the host*/ -} vhci_host_callback_t; +} esp_vhci_host_callback_t; -/** @brief API_vhci_host_check_send_available +/** @brief esp_vhci_host_check_send_available * used for check actively if the host can send packet to controller or not. * @return true for ready to send, false means cannot send packet */ -bool API_vhci_host_check_send_available(void); +bool esp_vhci_host_check_send_available(void); -/** @brief API_vhci_host_send_packet +/** @brief esp_vhci_host_send_packet * host send packet to controller * @param data the packet point *,@param len the packet length */ -void API_vhci_host_send_packet(uint8_t *data, uint16_t len); +void esp_vhci_host_send_packet(uint8_t *data, uint16_t len); -/** @brief API_vhci_host_register_callback +/** @brief esp_vhci_host_register_callback * register the vhci referece callback, the call back * struct defined by vhci_host_callback structure. - * @param callback vhci_host_callback type variable + * @param callback esp_vhci_host_callback type variable */ -void API_vhci_host_register_callback(const vhci_host_callback_t *callback); +void esp_vhci_host_register_callback(const esp_vhci_host_callback_t *callback); #ifdef __cplusplus } diff --git a/components/esp32/include/esp_phy_init.h b/components/esp32/include/esp_phy_init.h index 7bc0536103..e669a44151 100644 --- a/components/esp32/include/esp_phy_init.h +++ b/components/esp32/include/esp_phy_init.h @@ -229,7 +229,7 @@ esp_err_t esp_phy_store_cal_data_to_nvs(const esp_phy_calibration_data_t* cal_da * * Applications which don't need to enable PHY on every start up should * disable this menuconfig option and call esp_phy_init before calling - * esp_wifi_init or bt_controller_init. See do_phy_init function in + * esp_wifi_init or esp_bt_controller_init. See do_phy_init function in * cpu_start.c for an example of using this function. * * @param init_data PHY parameters. Default set of parameters can diff --git a/docs/api/bt.rst b/docs/api/bt.rst index 924e855c2f..6a14d40684 100644 --- a/docs/api/bt.rst +++ b/docs/api/bt.rst @@ -4,7 +4,7 @@ Bluetooth .. toctree:: :caption: Bluetooth APIs - Bluetooth VHCI + Bluetooth Controller && VHCI Bluetooth Common Bluetooth Classic Bluetooth LE diff --git a/docs/api/vhci.rst b/docs/api/controller_vhci.rst similarity index 66% rename from docs/api/vhci.rst rename to docs/api/controller_vhci.rst index 0d5d8b5fff..a9c1a79289 100644 --- a/docs/api/vhci.rst +++ b/docs/api/controller_vhci.rst @@ -1,5 +1,5 @@ -VHCI -==== +Controller && VHCI +================== Overview -------- @@ -30,12 +30,24 @@ Header Files Type Definitions ^^^^^^^^^^^^^^^^ -.. doxygenstruct:: vhci_host_callback +.. doxygentypedef:: esp_vhci_host_callback_t + +Enumerations +^^^^^^^^^^^^ + + +Structures +^^^^^^^^^^ + +.. doxygenstruct:: esp_vhci_host_callback + :members: + Functions ^^^^^^^^^ -.. doxygenfunction:: API_vhci_host_check_send_available -.. doxygenfunction:: API_vhci_host_register_callback -.. doxygenfunction:: API_vhci_host_send_packet -.. doxygenfunction:: bt_controller_init +.. doxygenfunction:: esp_bt_controller_init +.. doxygenfunction:: esp_vhci_host_check_send_available +.. doxygenfunction:: esp_vhci_host_send_packet +.. doxygenfunction:: esp_vhci_host_register_callback + diff --git a/docs/api/esp_bt_main.rst b/docs/api/esp_bt_main.rst index dc317c584f..cc6e80b2e5 100644 --- a/docs/api/esp_bt_main.rst +++ b/docs/api/esp_bt_main.rst @@ -42,8 +42,8 @@ Structures Functions ^^^^^^^^^ -.. doxygenfunction:: esp_enable_bluetooth -.. doxygenfunction:: esp_disable_bluetooth -.. doxygenfunction:: esp_init_bluetooth -.. doxygenfunction:: esp_deinit_bluetooth +.. doxygenfunction:: esp_bluedroid_enable +.. doxygenfunction:: esp_bluedroid_disable +.. doxygenfunction:: esp_bluedroid_init +.. doxygenfunction:: esp_bluedroid_deinit diff --git a/examples/05_ble_adv/main/app_bt.c b/examples/05_ble_adv/main/app_bt.c index 5bdbfd5c73..f0780c950c 100644 --- a/examples/05_ble_adv/main/app_bt.c +++ b/examples/05_ble_adv/main/app_bt.c @@ -73,7 +73,7 @@ static int host_rcv_pkt(uint8_t *data, uint16_t len) return 0; } -static vhci_host_callback_t vhci_host_cb = { +static esp_vhci_host_callback_t vhci_host_cb = { controller_rcv_pkt_ready, host_rcv_pkt }; @@ -139,13 +139,13 @@ static uint16_t make_cmd_ble_set_adv_data(uint8_t *buf, uint8_t data_len, uint8_ static void hci_cmd_send_reset(void) { uint16_t sz = make_cmd_reset (hci_cmd_buf); - API_vhci_host_send_packet(hci_cmd_buf, sz); + esp_vhci_host_send_packet(hci_cmd_buf, sz); } static void hci_cmd_send_ble_adv_start(void) { uint16_t sz = make_cmd_ble_set_adv_enable (hci_cmd_buf, 1); - API_vhci_host_send_packet(hci_cmd_buf, sz); + esp_vhci_host_send_packet(hci_cmd_buf, sz); } static void hci_cmd_send_ble_set_adv_param(void) @@ -168,7 +168,7 @@ static void hci_cmd_send_ble_set_adv_param(void) peer_addr, adv_chn_map, adv_filter_policy); - API_vhci_host_send_packet(hci_cmd_buf, sz); + esp_vhci_host_send_packet(hci_cmd_buf, sz); } static void hci_cmd_send_ble_set_adv_data(void) @@ -185,7 +185,7 @@ static void hci_cmd_send_ble_set_adv_data(void) adv_data_len = 5 + name_len; uint16_t sz = make_cmd_ble_set_adv_data(hci_cmd_buf, adv_data_len, (uint8_t *)adv_data); - API_vhci_host_send_packet(hci_cmd_buf, sz); + esp_vhci_host_send_packet(hci_cmd_buf, sz); } /* @@ -195,11 +195,11 @@ void bleAdvtTask(void *pvParameters) { int cmd_cnt = 0; bool send_avail = false; - API_vhci_host_register_callback(&vhci_host_cb); + esp_vhci_host_register_callback(&vhci_host_cb); printf("BLE advt task start\n"); while (1) { vTaskDelay(1000 / portTICK_PERIOD_MS); - send_avail = API_vhci_host_check_send_available(); + send_avail = esp_vhci_host_check_send_available(); if (send_avail) { switch (cmd_cnt) { case 0: hci_cmd_send_reset(); ++cmd_cnt; break; @@ -214,7 +214,7 @@ void bleAdvtTask(void *pvParameters) void app_main() { - bt_controller_init(); + esp_bt_controller_init(); xTaskCreatePinnedToCore(&bleAdvtTask, "bleAdvtTask", 2048, NULL, 5, NULL, 0); } diff --git a/examples/12_blufi/components/blufi/blufi.c b/examples/12_blufi/components/blufi/blufi.c index 2523ebf065..8e2b5d1f39 100644 --- a/examples/12_blufi/components/blufi/blufi.c +++ b/examples/12_blufi/components/blufi/blufi.c @@ -107,7 +107,7 @@ esp_err_t blufi_enable(void *arg) { esp_err_t err; - err = esp_enable_bluetooth(); + err = esp_bluedroid_enable(); if (err) { LOG_ERROR("%s failed\n", __func__); return err; @@ -122,7 +122,7 @@ esp_err_t blufi_disable(void *arg) { esp_err_t err; - err = esp_disable_bluetooth(); + err = esp_bluedroid_disable(); if (arg) { ((void (*)(void))arg)(); diff --git a/examples/12_blufi/main/demo_main.c b/examples/12_blufi/main/demo_main.c index 90992313c2..abb47aedb0 100644 --- a/examples/12_blufi/main/demo_main.c +++ b/examples/12_blufi/main/demo_main.c @@ -68,8 +68,8 @@ static esp_err_t event_handler(void *ctx, system_event_t *event) case SYSTEM_EVENT_STA_GOT_IP: xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); esp_blufi_send_config_state(ESP_BLUFI_CONFIG_OK); - esp_disable_bluetooth(); //close bluetooth function - //esp_deinit_bluetooth(); //free bluetooth resource + esp_bluedroid_disable(); //close bluetooth function + //esp_bluedroid_deinit(); //free bluetooth resource break; case SYSTEM_EVENT_STA_DISCONNECTED: /* This is a workaround as ESP32 WiFi libs don't currently @@ -131,11 +131,11 @@ void app_main() //vTaskDelay(3000 / portTICK_PERIOD_MS); - bt_controller_init(); + esp_bt_controller_init(); xTaskCreatePinnedToCore(&wifiTestTask, "wifiTestTask", 2048, NULL, 20, NULL, 0); LOG_ERROR("%s init bluetooth\n", __func__); - ret = esp_init_bluetooth(); + ret = esp_bluedroid_init(); if (ret) { LOG_ERROR("%s init bluetooth failed\n", __func__); return; diff --git a/examples/14_gatt_server/main/gatts_demo.c b/examples/14_gatt_server/main/gatts_demo.c index cf9a5789b4..9b3b6cc802 100644 --- a/examples/14_gatt_server/main/gatts_demo.c +++ b/examples/14_gatt_server/main/gatts_demo.c @@ -2,7 +2,7 @@ // // 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 +// You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // @@ -30,6 +30,8 @@ #include "esp_bt_main.h" #include "esp_bt_main.h" +#define GATTS_TAG "GATTS_DEMO" + ///Declare the static function static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); @@ -115,8 +117,6 @@ static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = { static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - LOG_ERROR("GAP_EVT, event %d\n", event); - switch (event) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: esp_ble_gap_start_advertising(&test_adv_params); @@ -129,23 +129,19 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { switch (event) { case ESP_GATTS_REG_EVT: - LOG_INFO("REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A; - LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gap_set_device_name(TEST_DEVICE_NAME); - LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gap_config_adv_data(&test_adv_data); - LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A); - LOG_INFO("%s %d\n", __func__, __LINE__); break; case ESP_GATTS_READ_EVT: { - LOG_INFO("GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); esp_gatt_rsp_t rsp; memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); rsp.attr_value.handle = param->read.handle; @@ -159,8 +155,8 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i break; } case ESP_GATTS_WRITE_EVT: { - LOG_INFO("GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); - LOG_INFO("GATT_WRITE_EVT, value len %d, value %08x\n", param->write.len, *(uint32_t *)param->write.value); + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value %08x\n", param->write.len, *(uint32_t *)param->write.value); esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); break; } @@ -170,7 +166,7 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i case ESP_GATTS_UNREG_EVT: break; case ESP_GATTS_CREATE_EVT: - LOG_INFO("CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); 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; @@ -184,7 +180,7 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i case ESP_GATTS_ADD_INCL_SRVC_EVT: break; case ESP_GATTS_ADD_CHAR_EVT: - LOG_INFO("ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; @@ -194,19 +190,19 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); break; case ESP_GATTS_ADD_CHAR_DESCR_EVT: - LOG_INFO("ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); break; case ESP_GATTS_DELETE_EVT: break; case ESP_GATTS_START_EVT: - LOG_INFO("SERVICE_START_EVT, status %d, service_handle %d\n", + ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", param->start.status, param->start.service_handle); break; case ESP_GATTS_STOP_EVT: break; case ESP_GATTS_CONNECT_EVT: - LOG_INFO("SERVICE_START_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n", + ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n", param->connect.conn_id, param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5], @@ -227,23 +223,19 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { switch (event) { case ESP_GATTS_REG_EVT: - LOG_INFO("REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B; - LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gap_set_device_name(TEST_DEVICE_NAME); - LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gap_config_adv_data(&test_adv_data); - LOG_INFO("%s %d\n", __func__, __LINE__); esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B); - LOG_INFO("%s %d\n", __func__, __LINE__); break; case ESP_GATTS_READ_EVT: { - LOG_INFO("GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); esp_gatt_rsp_t rsp; memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); rsp.attr_value.handle = param->read.handle; @@ -257,8 +249,8 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i break; } case ESP_GATTS_WRITE_EVT: { - LOG_INFO("GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); - LOG_INFO("GATT_WRITE_EVT, value len %d, value %08x\n", param->write.len, *(uint32_t *)param->write.value); + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value %08x\n", param->write.len, *(uint32_t *)param->write.value); esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); break; } @@ -268,7 +260,7 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i case ESP_GATTS_UNREG_EVT: break; case ESP_GATTS_CREATE_EVT: - LOG_INFO("CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); 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_B; @@ -282,7 +274,7 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i case ESP_GATTS_ADD_INCL_SRVC_EVT: break; case ESP_GATTS_ADD_CHAR_EVT: - LOG_INFO("ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; @@ -292,19 +284,19 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); break; case ESP_GATTS_ADD_CHAR_DESCR_EVT: - LOG_INFO("ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); break; case ESP_GATTS_DELETE_EVT: break; case ESP_GATTS_START_EVT: - LOG_INFO("SERVICE_START_EVT, status %d, service_handle %d\n", + ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", param->start.status, param->start.service_handle); break; case ESP_GATTS_STOP_EVT: break; case ESP_GATTS_CONNECT_EVT: - LOG_INFO("SERVICE_START_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n", + ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n", param->connect.conn_id, param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5], @@ -324,14 +316,12 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { - LOG_INFO("EVT %d, gatts if %d\n", event, gatts_if); - /* If event is register event, store the gatts_if for each profile */ if (event == ESP_GATTS_REG_EVT) { if (param->reg.status == ESP_GATT_OK) { gl_profile_tab[param->reg.app_id].gatts_if = gatts_if; } else { - LOG_INFO("Reg app failed, app_id %04x, status %d\n", + ESP_LOGI(GATTS_TAG, "Reg app failed, app_id %04x, status %d\n", param->reg.app_id, param->reg.status); return; @@ -357,16 +347,16 @@ void app_main() { esp_err_t ret; - bt_controller_init(); - LOG_INFO("%s init bluetooth\n", __func__); - ret = esp_init_bluetooth(); + esp_bt_controller_init(); + + ret = esp_bluedroid_init(); if (ret) { - LOG_ERROR("%s init bluetooth failed\n", __func__); + ESP_LOGE(GATTS_TAG, "%s init bluetooth failed\n", __func__); return; } - ret = esp_enable_bluetooth(); + ret = esp_bluedroid_enable(); if (ret) { - LOG_ERROR("%s enable bluetooth failed\n", __func__); + ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed\n", __func__); return; } diff --git a/examples/15_gatt_client/main/gattc_demo.c b/examples/15_gatt_client/main/gattc_demo.c index 50faaa7c98..e6ee867293 100644 --- a/examples/15_gatt_client/main/gattc_demo.c +++ b/examples/15_gatt_client/main/gattc_demo.c @@ -37,6 +37,8 @@ #include "esp_gatt_defs.h" #include "esp_bt_main.h" +#define GATTC_TAG "GATTC_DEMO" + ///Declare static functions static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); @@ -108,16 +110,16 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i switch (event) { case ESP_GATTC_REG_EVT: - LOG_INFO("REG_EVT\n"); + ESP_LOGI(GATTC_TAG, "REG_EVT\n"); esp_ble_gap_set_scan_params(&ble_scan_params); break; case ESP_GATTC_OPEN_EVT: conn_id = p_data->open.conn_id; memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); - LOG_INFO("ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d\n", conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d\n", conn_id, gattc_if, p_data->open.status, p_data->open.mtu); - LOG_INFO("REMOTE BDA %02x:%02x:%02x:%02x:%02x:%02x\n", + ESP_LOGI(GATTC_TAG, "REMOTE BDA %02x:%02x:%02x:%02x:%02x:%02x\n", gl_profile_tab[PROFILE_A_APP_ID].remote_bda[0], gl_profile_tab[PROFILE_A_APP_ID].remote_bda[1], gl_profile_tab[PROFILE_A_APP_ID].remote_bda[2], gl_profile_tab[PROFILE_A_APP_ID].remote_bda[3], gl_profile_tab[PROFILE_A_APP_ID].remote_bda[4], gl_profile_tab[PROFILE_A_APP_ID].remote_bda[5] @@ -128,37 +130,37 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SEARCH_RES_EVT: { esp_gatt_srvc_id_t *srvc_id = &p_data->search_res.srvc_id; conn_id = p_data->search_res.conn_id; - LOG_INFO("SEARCH RES: conn_id = %x\n", conn_id); + ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x\n", conn_id); if (srvc_id->id.uuid.len == ESP_UUID_LEN_16) { - LOG_INFO("UUID16: %x\n", srvc_id->id.uuid.uuid.uuid16); + ESP_LOGI(GATTC_TAG, "UUID16: %x\n", srvc_id->id.uuid.uuid.uuid16); } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_32) { - LOG_INFO("UUID32: %x\n", srvc_id->id.uuid.uuid.uuid32); + ESP_LOGI(GATTC_TAG, "UUID32: %x\n", srvc_id->id.uuid.uuid.uuid32); } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_128) { - LOG_INFO("UUID128: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", srvc_id->id.uuid.uuid.uuid128[0], + ESP_LOGI(GATTC_TAG, "UUID128: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", srvc_id->id.uuid.uuid.uuid128[0], srvc_id->id.uuid.uuid.uuid128[1], srvc_id->id.uuid.uuid.uuid128[2], srvc_id->id.uuid.uuid.uuid128[3], srvc_id->id.uuid.uuid.uuid128[4], srvc_id->id.uuid.uuid.uuid128[5], srvc_id->id.uuid.uuid.uuid128[6], srvc_id->id.uuid.uuid.uuid128[7], srvc_id->id.uuid.uuid.uuid128[8], srvc_id->id.uuid.uuid.uuid128[9], srvc_id->id.uuid.uuid.uuid128[10], srvc_id->id.uuid.uuid.uuid128[11], srvc_id->id.uuid.uuid.uuid128[12], srvc_id->id.uuid.uuid.uuid128[13], srvc_id->id.uuid.uuid.uuid128[14], srvc_id->id.uuid.uuid.uuid128[15]); } else { - LOG_ERROR("UNKNOWN LEN %d\n", srvc_id->id.uuid.len); + ESP_LOGE(GATTC_TAG, "UNKNOWN LEN %d\n", srvc_id->id.uuid.len); } break; } case ESP_GATTC_SEARCH_CMPL_EVT: conn_id = p_data->search_cmpl.conn_id; - LOG_INFO("SEARCH_CMPL: conn_id = %x, status %d\n", conn_id, p_data->search_cmpl.status); + ESP_LOGI(GATTC_TAG, "SEARCH_CMPL: conn_id = %x, status %d\n", conn_id, p_data->search_cmpl.status); esp_ble_gattc_get_characteristic(gattc_if, conn_id, &alert_service_id, NULL); break; case ESP_GATTC_GET_CHAR_EVT: if (p_data->get_char.status != ESP_GATT_OK) { break; } - LOG_INFO("GET CHAR: conn_id = %x, status %d\n", p_data->get_char.conn_id, p_data->get_char.status); - LOG_INFO("GET CHAR: srvc_id = %04x, char_id = %04x\n", p_data->get_char.srvc_id.id.uuid.uuid.uuid16, p_data->get_char.char_id.uuid.uuid.uuid16); + ESP_LOGI(GATTC_TAG, "GET CHAR: conn_id = %x, status %d\n", p_data->get_char.conn_id, p_data->get_char.status); + ESP_LOGI(GATTC_TAG, "GET CHAR: srvc_id = %04x, char_id = %04x\n", p_data->get_char.srvc_id.id.uuid.uuid.uuid16, p_data->get_char.char_id.uuid.uuid.uuid16); if (p_data->get_char.char_id.uuid.uuid.uuid16 == 0x2a46) { - LOG_INFO("register notify\n"); + ESP_LOGI(GATTC_TAG, "register notify\n"); esp_ble_gattc_register_for_notify(gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, &alert_service_id, &p_data->get_char.char_id); } @@ -166,8 +168,8 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i break; case ESP_GATTC_REG_FOR_NOTIFY_EVT: { uint16_t notify_en = 1; - LOG_INFO("REG FOR NOTIFY: status %d\n", p_data->reg_for_notify.status); - LOG_INFO("REG FOR_NOTIFY: srvc_id = %04x, char_id = %04x\n", p_data->reg_for_notify.srvc_id.id.uuid.uuid.uuid16, p_data->reg_for_notify.char_id.uuid.uuid.uuid16); + ESP_LOGI(GATTC_TAG, "REG FOR NOTIFY: status %d\n", p_data->reg_for_notify.status); + ESP_LOGI(GATTC_TAG, "REG FOR_NOTIFY: srvc_id = %04x, char_id = %04x\n", p_data->reg_for_notify.srvc_id.id.uuid.uuid.uuid16, p_data->reg_for_notify.char_id.uuid.uuid.uuid16); esp_ble_gattc_write_char_descr( gattc_if, @@ -182,10 +184,10 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i break; } case ESP_GATTC_NOTIFY_EVT: - LOG_INFO("NOTIFY: len %d, value %08x\n", p_data->notify.value_len, *(uint32_t *)p_data->notify.value); + ESP_LOGI(GATTC_TAG, "NOTIFY: len %d, value %08x\n", p_data->notify.value_len, *(uint32_t *)p_data->notify.value); break; case ESP_GATTC_WRITE_DESCR_EVT: - LOG_INFO("WRITE: status %d\n", p_data->write.status); + ESP_LOGI(GATTC_TAG, "WRITE: status %d\n", p_data->write.status); break; default: break; @@ -199,16 +201,15 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i switch (event) { case ESP_GATTC_REG_EVT: - LOG_INFO("REG_EVT\n"); - //esp_ble_gap_set_scan_params(&ble_scan_params); + ESP_LOGI(GATTC_TAG, "REG_EVT\n"); break; case ESP_GATTC_OPEN_EVT: conn_id = p_data->open.conn_id; memcpy(gl_profile_tab[PROFILE_B_APP_ID].remote_bda, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); - LOG_INFO("ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d\n", conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d\n", conn_id, gattc_if, p_data->open.status, p_data->open.mtu); - LOG_INFO("REMOTE BDA %02x:%02x:%02x:%02x:%02x:%02x\n", + ESP_LOGI(GATTC_TAG, "REMOTE BDA %02x:%02x:%02x:%02x:%02x:%02x\n", gl_profile_tab[PROFILE_B_APP_ID].remote_bda[0], gl_profile_tab[PROFILE_B_APP_ID].remote_bda[1], gl_profile_tab[PROFILE_B_APP_ID].remote_bda[2], gl_profile_tab[PROFILE_B_APP_ID].remote_bda[3], gl_profile_tab[PROFILE_B_APP_ID].remote_bda[4], gl_profile_tab[PROFILE_B_APP_ID].remote_bda[5] @@ -219,37 +220,37 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SEARCH_RES_EVT: { esp_gatt_srvc_id_t *srvc_id = &p_data->search_res.srvc_id; conn_id = p_data->search_res.conn_id; - LOG_INFO("SEARCH RES: conn_id = %x\n", conn_id); + ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x\n", conn_id); if (srvc_id->id.uuid.len == ESP_UUID_LEN_16) { - LOG_INFO("UUID16: %x\n", srvc_id->id.uuid.uuid.uuid16); + ESP_LOGI(GATTC_TAG, "UUID16: %x\n", srvc_id->id.uuid.uuid.uuid16); } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_32) { - LOG_INFO("UUID32: %x\n", srvc_id->id.uuid.uuid.uuid32); + ESP_LOGI(GATTC_TAG, "UUID32: %x\n", srvc_id->id.uuid.uuid.uuid32); } else if (srvc_id->id.uuid.len == ESP_UUID_LEN_128) { - LOG_INFO("UUID128: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", srvc_id->id.uuid.uuid.uuid128[0], + ESP_LOGI(GATTC_TAG, "UUID128: %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", srvc_id->id.uuid.uuid.uuid128[0], srvc_id->id.uuid.uuid.uuid128[1], srvc_id->id.uuid.uuid.uuid128[2], srvc_id->id.uuid.uuid.uuid128[3], srvc_id->id.uuid.uuid.uuid128[4], srvc_id->id.uuid.uuid.uuid128[5], srvc_id->id.uuid.uuid.uuid128[6], srvc_id->id.uuid.uuid.uuid128[7], srvc_id->id.uuid.uuid.uuid128[8], srvc_id->id.uuid.uuid.uuid128[9], srvc_id->id.uuid.uuid.uuid128[10], srvc_id->id.uuid.uuid.uuid128[11], srvc_id->id.uuid.uuid.uuid128[12], srvc_id->id.uuid.uuid.uuid128[13], srvc_id->id.uuid.uuid.uuid128[14], srvc_id->id.uuid.uuid.uuid128[15]); } else { - LOG_ERROR("UNKNOWN LEN %d\n", srvc_id->id.uuid.len); + ESP_LOGE(GATTC_TAG, "UNKNOWN LEN %d\n", srvc_id->id.uuid.len); } break; } case ESP_GATTC_SEARCH_CMPL_EVT: conn_id = p_data->search_cmpl.conn_id; - LOG_INFO("SEARCH_CMPL: conn_id = %x, status %d\n", conn_id, p_data->search_cmpl.status); + ESP_LOGI(GATTC_TAG, "SEARCH_CMPL: conn_id = %x, status %d\n", conn_id, p_data->search_cmpl.status); esp_ble_gattc_get_characteristic(gattc_if, conn_id, &alert_service_id, NULL); break; case ESP_GATTC_GET_CHAR_EVT: if (p_data->get_char.status != ESP_GATT_OK) { break; } - LOG_INFO("GET CHAR: conn_id = %x, status %d\n", p_data->get_char.conn_id, p_data->get_char.status); - LOG_INFO("GET CHAR: srvc_id = %04x, char_id = %04x\n", p_data->get_char.srvc_id.id.uuid.uuid.uuid16, p_data->get_char.char_id.uuid.uuid.uuid16); + ESP_LOGI(GATTC_TAG, "GET CHAR: conn_id = %x, status %d\n", p_data->get_char.conn_id, p_data->get_char.status); + ESP_LOGI(GATTC_TAG, "GET CHAR: srvc_id = %04x, char_id = %04x\n", p_data->get_char.srvc_id.id.uuid.uuid.uuid16, p_data->get_char.char_id.uuid.uuid.uuid16); if (p_data->get_char.char_id.uuid.uuid.uuid16 == 0x2a46) { - LOG_INFO("register notify\n"); + ESP_LOGI(GATTC_TAG, "register notify\n"); esp_ble_gattc_register_for_notify(gattc_if, gl_profile_tab[PROFILE_B_APP_ID].remote_bda, &alert_service_id, &p_data->get_char.char_id); } @@ -257,8 +258,8 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i break; case ESP_GATTC_REG_FOR_NOTIFY_EVT: { uint16_t notify_en = 1; - LOG_INFO("REG FOR NOTIFY: status %d\n", p_data->reg_for_notify.status); - LOG_INFO("REG FOR_NOTIFY: srvc_id = %04x, char_id = %04x\n", p_data->reg_for_notify.srvc_id.id.uuid.uuid.uuid16, p_data->reg_for_notify.char_id.uuid.uuid.uuid16); + ESP_LOGI(GATTC_TAG, "REG FOR NOTIFY: status %d\n", p_data->reg_for_notify.status); + ESP_LOGI(GATTC_TAG, "REG FOR_NOTIFY: srvc_id = %04x, char_id = %04x\n", p_data->reg_for_notify.srvc_id.id.uuid.uuid.uuid16, p_data->reg_for_notify.char_id.uuid.uuid.uuid16); esp_ble_gattc_write_char_descr( gattc_if, @@ -273,10 +274,10 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i break; } case ESP_GATTC_NOTIFY_EVT: - LOG_INFO("NOTIFY: len %d, value %08x\n", p_data->notify.value_len, *(uint32_t *)p_data->notify.value); + ESP_LOGI(GATTC_TAG, "NOTIFY: len %d, value %08x\n", p_data->notify.value_len, *(uint32_t *)p_data->notify.value); break; case ESP_GATTC_WRITE_DESCR_EVT: - LOG_INFO("WRITE: status %d\n", p_data->write.status); + ESP_LOGI(GATTC_TAG, "WRITE: status %d\n", p_data->write.status); break; default: break; @@ -299,27 +300,22 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par switch (scan_result->scan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_RES_EVT: for (int i = 0; i < 6; i++) { - LOG_INFO("%x:", scan_result->scan_rst.bda[i]); + ESP_LOGI(GATTC_TAG, "%x:", scan_result->scan_rst.bda[i]); } - LOG_INFO("\n"); + ESP_LOGI(GATTC_TAG, "\n"); adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); - LOG_INFO("adv_name_len=%x\n", adv_name_len); + ESP_LOGI(GATTC_TAG, "Searched Device Name Len %d\n", adv_name_len); for (int j = 0; j < adv_name_len; j++) { - LOG_INFO("%c", adv_name[j]); + ESP_LOGI(GATTC_TAG, "%c", adv_name[j]); } - LOG_INFO("\n"); - for (int j = 0; j < adv_name_len; j++) { - LOG_INFO("%c", device_name[j]); - } - LOG_INFO("\n"); if (adv_name != NULL) { if (strcmp((char *)adv_name, device_name) == 0) { - LOG_INFO("the name equal to Heart Rate\n"); + ESP_LOGI(GATTC_TAG, "Searched device %s\n", device_name); if (connect == false) { connect = true; - LOG_INFO("Connect to the remote device.\n"); + ESP_LOGI(GATTC_TAG, "Connect to the remote device.\n"); esp_ble_gap_stop_scanning(); esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, true); esp_ble_gattc_open(gl_profile_tab[PROFILE_B_APP_ID].gattc_if, scan_result->scan_rst.bda, true); @@ -341,14 +337,14 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { - LOG_INFO("EVT %d, gattc if %d\n", event, gattc_if); + ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d\n", event, gattc_if); /* If event is register event, store the gattc_if for each profile */ if (event == ESP_GATTC_REG_EVT) { if (param->reg.status == ESP_GATT_OK) { gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; } else { - LOG_INFO("Reg app failed, app_id %04x, status %d\n", + ESP_LOGI(GATTC_TAG, "Reg app failed, app_id %04x, status %d\n", param->reg.app_id, param->reg.status); return; @@ -374,17 +370,17 @@ void ble_client_appRegister(void) { esp_err_t status; - LOG_INFO("register callback\n"); + ESP_LOGI(GATTC_TAG, "register callback\n"); //register the scan callback function to the gap moudule if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) { - LOG_ERROR("gap register error, error code = %x\n", status); + ESP_LOGE(GATTC_TAG, "gap register error, error code = %x\n", status); return; } //register the callback function to the gattc module if ((status = esp_ble_gattc_register_callback(esp_gattc_cb)) != ESP_OK) { - LOG_ERROR("gattc register error, error code = %x\n", status); + ESP_LOGE(GATTC_TAG, "gattc register error, error code = %x\n", status); return; } esp_ble_gattc_app_register(PROFILE_A_APP_ID); @@ -393,14 +389,14 @@ void ble_client_appRegister(void) void gattc_client_test(void) { - esp_init_bluetooth(); - esp_enable_bluetooth(); + esp_bluedroid_init(); + esp_bluedroid_enable(); ble_client_appRegister(); } void app_main() { - bt_controller_init(); + esp_bt_controller_init(); gattc_client_test(); } From 24af07fd13e69f1e82620b5608feb14dc50283f9 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Wed, 28 Dec 2016 12:02:43 +0800 Subject: [PATCH 066/167] component/bt : new blufi 1. new blufi protocol 2. new blufi demo 3. support security 4. support sta/ap/sta_ap 5. support wpa-enterprise --- components/bt/bluedroid/api/esp_blufi_api.c | 20 +- .../bt/bluedroid/api/include/esp_blufi_api.h | 276 ++++- components/bt/bluedroid/bta/dm/bta_dm_api.c | 4 +- .../btc/profile/esp/blufi/blufi_adv.c | 164 --- .../btc/profile/esp/blufi/blufi_prf.c | 940 ++++++++++++++---- .../btc/profile/esp/blufi/blufi_protocol.c | 240 +++++ .../btc/profile/esp/blufi/include/blufi_adv.h | 39 - .../btc/profile/esp/blufi/include/blufi_int.h | 172 +++- .../btc/profile/esp/include/btc_blufi_prf.h | 30 +- .../btc/profile/std/gap/btc_gap_ble.c | 4 +- .../bt/bluedroid/hci/packet_fragmenter.c | 16 +- components/bt/bluedroid/include/bt_trace.h | 13 +- docs/api/bt_le.rst | 1 + docs/api/esp_blufi.rst | 128 +++ examples/12_blufi/components/blufi/blufi.c | 132 --- .../12_blufi/components/blufi/blufi_task.c | 110 -- .../12_blufi/components/blufi/component.mk | 13 - .../12_blufi/components/blufi/include/blufi.h | 39 - examples/12_blufi/main/blufi_demo.h | 17 + examples/12_blufi/main/blufi_main.c | 336 +++++++ examples/12_blufi/main/blufi_security.c | 205 ++++ examples/12_blufi/main/demo_main.c | 144 --- 22 files changed, 2090 insertions(+), 953 deletions(-) delete mode 100644 components/bt/bluedroid/btc/profile/esp/blufi/blufi_adv.c create mode 100644 components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c delete mode 100644 components/bt/bluedroid/btc/profile/esp/blufi/include/blufi_adv.h create mode 100644 docs/api/esp_blufi.rst delete mode 100644 examples/12_blufi/components/blufi/blufi.c delete mode 100644 examples/12_blufi/components/blufi/blufi_task.c delete mode 100644 examples/12_blufi/components/blufi/component.mk delete mode 100644 examples/12_blufi/components/blufi/include/blufi.h create mode 100644 examples/12_blufi/main/blufi_demo.h create mode 100644 examples/12_blufi/main/blufi_main.c create mode 100644 examples/12_blufi/main/blufi_security.c delete mode 100644 examples/12_blufi/main/demo_main.c diff --git a/components/bt/bluedroid/api/esp_blufi_api.c b/components/bt/bluedroid/api/esp_blufi_api.c index 2697f2cbf9..5493824d2a 100644 --- a/components/bt/bluedroid/api/esp_blufi_api.c +++ b/components/bt/bluedroid/api/esp_blufi_api.c @@ -22,22 +22,30 @@ #include "btc_main.h" #include "future.h" -esp_err_t esp_blufi_register_callback(esp_blufi_cb_t callback) +esp_err_t esp_blufi_register_callbacks(esp_blufi_callbacks_t *callbacks) { - return (btc_profile_cb_set(BTC_PID_BLUFI, callback) == 0 ? ESP_OK : ESP_FAIL); + if (callbacks == NULL) { + return ESP_FAIL; + } + + btc_blufi_set_callbacks(callbacks); + return (btc_profile_cb_set(BTC_PID_BLUFI, callbacks->event_cb) == 0 ? ESP_OK : ESP_FAIL); } -esp_err_t esp_blufi_send_config_state(esp_blufi_config_state_t state) +esp_err_t esp_blufi_send_wifi_conn_report(wifi_mode_t opmode, esp_blufi_sta_conn_state_t sta_conn_state, uint8_t softap_conn_num, esp_blufi_extra_info_t *extra_info) { btc_msg_t msg; btc_blufi_args_t arg; msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_BLUFI; - msg.act = BTC_BLUFI_ACT_SEND_CFG_STATE; - arg.cfg_state.state = state; + msg.act = BTC_BLUFI_ACT_SEND_CFG_REPORT; + arg.wifi_conn_report.opmode = opmode; + arg.wifi_conn_report.sta_conn_state = sta_conn_state; + arg.wifi_conn_report.softap_conn_num = softap_conn_num; + arg.wifi_conn_report.extra_info = extra_info; - return (btc_transfer_context(&msg, &arg, sizeof(btc_blufi_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + return (btc_transfer_context(&msg, &arg, sizeof(btc_blufi_args_t), btc_blufi_call_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } diff --git a/components/bt/bluedroid/api/include/esp_blufi_api.h b/components/bt/bluedroid/api/include/esp_blufi_api.h index 2644bd5244..5490fec05a 100644 --- a/components/bt/bluedroid/api/include/esp_blufi_api.h +++ b/components/bt/bluedroid/api/include/esp_blufi_api.h @@ -18,24 +18,44 @@ #include "esp_bt_defs.h" #include "esp_gatt_defs.h" #include "esp_err.h" +#include "esp_wifi_types.h" #ifdef __cplusplus extern "C" { #endif -#define ESP_BLUFI_RECV_DATA_LEN_MAX (64+1) - typedef enum { - ESP_BLUFI_EVENT_INIT_FINISH = 0, - ESP_BLUFI_EVENT_DEINIT_FINISH = 1, - ESP_BLUFI_EVENT_RECV_DATA = 2, + ESP_BLUFI_EVENT_INIT_FINISH = 0, /*adv_name != NULL) { - data_mask |= BTM_BLE_AD_BIT_DEV_NAME; - BTA_DmSetDeviceName(adv_data->adv_name); - } - if (adv_data->ble_adv_data.int_range.low != 0 || - adv_data->ble_adv_data.int_range.hi != 0) { - data_mask |= BTM_BLE_AD_BIT_INT_RANGE; - } - - if (adv_data->ble_adv_data.p_manu != NULL) { - data_mask |= BTM_BLE_AD_BIT_MANU; - } - - if (adv_data->ble_adv_data.p_services != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE; - } - - if (adv_data->ble_adv_data.p_service_32b != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_32; - } - - if (adv_data->ble_adv_data.p_services_128b != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_128; - } - - if (adv_data->ble_adv_data.p_sol_services != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_SOL; - } - - if (adv_data->ble_adv_data.p_sol_service_32b != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_32SOL; - } - - if (adv_data->ble_adv_data.p_sol_service_128b != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_128SOL; - } - - if (adv_data->ble_adv_data.p_service_data != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_DATA; - } - - if (adv_data->ble_adv_data.appearance != 0) { - data_mask |= BTM_BLE_AD_BIT_APPEARANCE; - } - - if (adv_data->ble_adv_data.p_proprietary != NULL) { - data_mask |= BTM_BLE_AD_BIT_PROPRIETARY; - } - - if (adv_data->ble_adv_data.tx_power != 0) { - data_mask |= BTM_BLE_AD_BIT_TX_PWR; - } - - BTA_DmBleSetAdvConfig(data_mask, &(adv_data->ble_adv_data), p_adv_data_cback); -} - - -/******************************************************************************* -** -** Function BLUFI_BleSetScanRsp -** -** Description This function is called to override the app scan response. -** -** Parameters Pointer to User defined ADV data structure -** -** Returns None -** -*******************************************************************************/ -void BlufiBleSetScanRsp(tBLUFI_BLE_ADV_DATA *scan_rsp_data, - tBTA_SET_ADV_DATA_CMPL_CBACK *p_scan_rsp_data_cback) -{ - tBTA_BLE_AD_MASK data_mask = 0; - if (scan_rsp_data->adv_name != NULL) { - data_mask |= BTM_BLE_AD_BIT_DEV_NAME; - BTA_DmSetDeviceName(scan_rsp_data->adv_name); - } - if (scan_rsp_data->ble_adv_data.int_range.low != 0 || - scan_rsp_data->ble_adv_data.int_range.hi != 0) { - data_mask |= BTM_BLE_AD_BIT_INT_RANGE; - } - - if (scan_rsp_data->ble_adv_data.p_manu != NULL) { - data_mask |= BTM_BLE_AD_BIT_MANU; - } - - if (scan_rsp_data->ble_adv_data.p_services != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE; - } - - if (scan_rsp_data->ble_adv_data.p_service_32b != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_32; - } - - if (scan_rsp_data->ble_adv_data.p_services_128b != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_128; - } - - if (scan_rsp_data->ble_adv_data.p_sol_services != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_SOL; - } - - if (scan_rsp_data->ble_adv_data.p_sol_service_32b != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_32SOL; - } - - if (scan_rsp_data->ble_adv_data.p_sol_service_128b != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_128SOL; - } - - if (scan_rsp_data->ble_adv_data.p_service_data != NULL) { - data_mask |= BTM_BLE_AD_BIT_SERVICE_DATA; - } - - if (scan_rsp_data->ble_adv_data.appearance != 0) { - data_mask |= BTM_BLE_AD_BIT_APPEARANCE; - } - - if (scan_rsp_data->ble_adv_data.p_proprietary != NULL) { - data_mask |= BTM_BLE_AD_BIT_PROPRIETARY; - } - - if (scan_rsp_data->ble_adv_data.tx_power != 0) { - data_mask |= BTM_BLE_AD_BIT_TX_PWR; - } - - BTA_DmBleSetScanRsp(data_mask, &(scan_rsp_data->ble_adv_data), p_scan_rsp_data_cback); -} - - 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 d6e23e63a3..d480350453 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c @@ -31,255 +31,240 @@ #include "btc_task.h" #include "btc_manage.h" -#include "blufi_adv.h" #include "blufi_int.h" -const char success_msg[] = "BLUFI_CONFIG_OK"; -const char failed_msg[] = "BLUFI_CONFIG_FAILED"; +#include "esp_blufi_api.h" -#define BTC_BLUFI_CB_TO_APP(event, param) ((esp_blufi_cb_t)btc_profile_cb_get(BTC_PID_BLUFI))((event), (param)) +#define BTC_BLUFI_CB_TO_APP(event, param) ((esp_blufi_event_cb_t)btc_profile_cb_get(BTC_PID_BLUFI))((event), (param)) #define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" #define BT_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] +//define the blufi serivce uuid +#define BLUFI_SERVICE_UUID 0xFFFF +//define the blufi Char uuid (PHONE to ESP32) +#define BLUFI_CHAR_P2E_UUID 0xFF01 +//define the blufi Char uuid (ESP32 to PHONE) +#define BLUFI_CHAR_E2P_UUID 0xFF02 +//define the blufi Descriptor uuid (ESP32 to PHONE) +#define BLUFI_DESCR_E2P_UUID GATT_UUID_CHAR_CLIENT_CONFIG +//define the blufi APP ID +#define BLUFI_APP_UUID 0xFFFF -UINT16 esp32_uuid = SVC_BLUFI_UUID; -UINT8 esp32_manu[17] = {0xff, 0x20, 0x14, 0x07, 0x22, 0x00, 0x02, 0x5B, 0x00, 0x33, 0x49, 0x31, 0x30, 0x4a, 0x30, 0x30, 0x31}; -tBTA_BLE_MANU p_esp32_manu = {sizeof(esp32_manu), esp32_manu}; /* manufacturer data */ +#define BLUFI_HDL_NUM 6 -tBTA_BLE_SERVICE esp32_service = { - 0x01, //only one service in the ijiazu button profile - false, - &esp32_uuid -}; /* 16 bits services */ +tBLUFI_ENV blufi_env; +static /* const */ tBT_UUID blufi_srvc_uuid = {LEN_UUID_16, {BLUFI_SERVICE_UUID}}; +static /* const */ tBT_UUID blufi_char_uuid_p2e = {LEN_UUID_16, {BLUFI_CHAR_P2E_UUID}}; +static /* const */ tBT_UUID blufi_char_uuid_e2p = {LEN_UUID_16, {BLUFI_CHAR_E2P_UUID}}; +static /* const */ tBT_UUID blufi_descr_uuid_e2p = {LEN_UUID_16, {BLUFI_DESCR_E2P_UUID}}; +static /* const */ tBT_UUID blufi_app_uuid = {LEN_UUID_16, {BLUFI_APP_UUID}}; -tBLUFI_BLE_ADV_DATA esp32_adv_data[ADV_SCAN_IDX_MAX] = { - [BLE_ADV_DATA_IDX] = { - .adv_name = "Espressif_008", - { - {0, 0}, - NULL, //no manufature data to be setting in the esp32 adervetisiing datas - &esp32_service, - NULL, //the 128 bits service uuid set to null(not used) - NULL, //the 32 bits Service UUID set to null(not used) - NULL, //16 bits services Solicitation UUIDs set to null(not used) - NULL, //List of 32 bit Service Solicitation UUIDs set to null(not used) - NULL, //List of 128 bit Service Solicitation UUIDs set to null(not used) - NULL, //proprietary data set to null(not used) - NULL, //service data set not null(no service data to be sent) - 0x0200, //device type : generic display - BTA_DM_GENERAL_DISC, // General discoverable. - 0xFE //the tx power value,defult value is 0 - }, - }, - - [BLE_SCAN_RSP_DATA_IDX] = { - .adv_name = NULL, - { - {0, 0}, - &p_esp32_manu, - NULL, - NULL, //the 128 bits service uuid set to null(not used) - NULL, //the 32 bits Service UUID set to null(not used) - NULL, //16 bits services Solicitation UUIDs set to null(not used) - NULL, //List of 32 bit Service Solicitation UUIDs set to null(not used) - NULL, //List of 128 bit Service Solicitation UUIDs set to null(not used) - NULL, //proprietary data set to null(not used) - NULL, //service data set not null(no service data to be sent) - 0x0000, //device type : generic display - 0x00, // General discoverable. - 0x00 - }, //the tx power value,defult value is 0 - } -}; - - - -static tBLUFI_CB_ENV blufi_cb_env; - - - -/***************************************************************************** -** Constants -*****************************************************************************/ +// static functions declare static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data); +static void btc_blufi_recv_handler(uint8_t *data, int len); +static void btc_blufi_send_ack(uint8_t seq); - -/******************************************************************************* -** -** Function blufi_create_service -** -** Description Create a Service for the blufi profile -** -** Returns NULL -** -*******************************************************************************/ static void blufi_create_service(void) { - tBTA_GATTS_IF server_if ; - tBT_UUID uuid = {LEN_UUID_16, {SVC_BLUFI_UUID}}; - UINT16 num_handle = BLUFI_HDL_NUM; - UINT8 inst = 0x00; - server_if = blufi_cb_env.gatt_if; - blufi_cb_env.inst_id = inst; - if (!blufi_cb_env.enabled) { + if (!blufi_env.enabled) { LOG_ERROR("blufi service added error."); return; } - BTA_GATTS_CreateService(server_if, &uuid, inst, num_handle, true); + blufi_env.srvc_inst = 0x00; + BTA_GATTS_CreateService(blufi_env.gatt_if, &blufi_srvc_uuid, blufi_env.srvc_inst, BLUFI_HDL_NUM, true); } - -/******************************************************************************* -** -** Function blufi_profile_cb -** -** Description the callback function after the profile has been register to the BTA manager module -** -** Returns NULL -** -*******************************************************************************/ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) { tBTA_GATTS_RSP rsp; - tBT_UUID uuid = {LEN_UUID_16, {SVC_BLUFI_UUID}}; - UINT8 *p_rec_data = NULL; - tBTA_GATT_STATUS status; - tBTA_DM_DISC disc_mode = BTA_DM_BLE_GENERAL_DISCOVERABLE; - tBTA_DM_CONN conn_mode = BTA_DM_BLE_CONNECTABLE; LOG_DEBUG("blufi profile cb event = %x\n", event); switch (event) { case BTA_GATTS_REG_EVT: - status = p_data->reg_oper.status; + LOG_DEBUG("REG: status %d, app_uuid %04x, gatt_if %d\n", p_data->reg_oper.status, p_data->reg_oper.uuid.uu.uuid16, p_data->reg_oper.server_if); - LOG_DEBUG("p_data->reg_oper.status = %x\n", p_data->reg_oper.status); - LOG_DEBUG("(p_data->reg_oper.uuid.uu.uuid16=%x\n", p_data->reg_oper.uuid.uu.uuid16); if (p_data->reg_oper.status != BTA_GATT_OK) { - LOG_ERROR("blufi profile register failed\n"); + LOG_ERROR("BLUFI profile register failed\n"); return; } - blufi_cb_env.gatt_if = p_data->reg_oper.server_if; - blufi_cb_env.enabled = true; - LOG_DEBUG("register complete: event=%d, status=%d, server_if=%d\n", - event, status, blufi_cb_env.gatt_if); - - LOG_DEBUG("set advertising parameters\n"); - //set connectable,discoverable, pairable and paired only modes of local device - BTA_DmSetVisibility(disc_mode, conn_mode, (uint8_t)BTA_DM_NON_PAIRABLE, (uint8_t)BTA_DM_CONN_ALL); - //set the advertising data to the btm layer - BlufiBleConfigadvData(&esp32_adv_data[BLE_ADV_DATA_IDX], NULL); - //set the adversting data to the btm layer - BlufiBleSetScanRsp(&esp32_adv_data[BLE_SCAN_RSP_DATA_IDX], NULL); - BTA_GATTS_Listen(blufi_cb_env.gatt_if, true, NULL); + blufi_env.gatt_if = p_data->reg_oper.server_if; + blufi_env.enabled = true; //create the blufi service to the service data base. - if (p_data->reg_oper.uuid.uu.uuid16 == SVC_BLUFI_UUID) { + if (p_data->reg_oper.uuid.uu.uuid16 == BLUFI_APP_UUID) { + LOG_DEBUG("%s %d\n", __func__, __LINE__); blufi_create_service(); } break; case BTA_GATTS_READ_EVT: memset(&rsp, 0, sizeof(tBTA_GATTS_API_RSP)); rsp.attr_value.handle = p_data->req_data.p_data->read_req.handle; - rsp.attr_value.len = 2; - //rsp.attr_value.value[0] = 0xde; - //rsp.attr_value.value[1] = 0xed; - //rsp.attr_value.value[2] = 0xbe; - //rsp.attr_value.value[3] = 0xef; + rsp.attr_value.len = 1; + rsp.attr_value.value[0] = 0x00; BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, p_data->req_data.status, &rsp); break; - case BTA_GATTS_WRITE_EVT: - BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, + case BTA_GATTS_WRITE_EVT: { + if(p_data->req_data.p_data->write_req.is_prep) { + tBTA_GATT_STATUS status = GATT_SUCCESS; + + if (blufi_env.prepare_buf == NULL) { + blufi_env.prepare_buf = GKI_getbuf(BLUFI_PREPAIR_BUF_MAX_SIZE); + if (blufi_env.prepare_buf == NULL) { + LOG_ERROR("Blufi prep no mem\n"); + status = GATT_NO_RESOURCES; + } + } else { + if(p_data->req_data.p_data->write_req.offset > BLUFI_PREPAIR_BUF_MAX_SIZE) { + status = GATT_INVALID_OFFSET; + } else if ((p_data->req_data.p_data->write_req.offset + p_data->req_data.p_data->write_req.len) > BLUFI_PREPAIR_BUF_MAX_SIZE) { + status = GATT_INVALID_ATTR_LEN; + } + } + + memset(&rsp, 0, sizeof(tGATTS_RSP)); + rsp.attr_value.handle = p_data->req_data.p_data->write_req.handle; + rsp.attr_value.len = p_data->req_data.p_data->write_req.len; + rsp.attr_value.offset = p_data->req_data.p_data->write_req.offset; + memcpy(rsp.attr_value.value, p_data->req_data.p_data->write_req.value, p_data->req_data.p_data->write_req.len); + + LOG_DEBUG("prep write, len=%d, offset=%d\n", p_data->req_data.p_data->write_req.len, p_data->req_data.p_data->write_req.offset); + + BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, + status, &rsp); + + memcpy(blufi_env.prepare_buf + p_data->req_data.p_data->write_req.offset, + p_data->req_data.p_data->write_req.value, + p_data->req_data.p_data->write_req.len); + blufi_env.prepare_len += p_data->req_data.p_data->write_req.len; + + return; + } else { + LOG_DEBUG("norm write, len=%d, offset=%d\n", p_data->req_data.p_data->write_req.len, p_data->req_data.p_data->write_req.offset); + BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, p_data->req_data.status, NULL); - - LOG_DEBUG("Received blufi data:"); - for (int i = 0; i < p_data->req_data.p_data->write_req.len; i++) { - LOG_DEBUG("%x", p_data->req_data.p_data->write_req.value[i]); } - LOG_DEBUG("\n"); - - if (p_data->req_data.p_data->write_req.handle == blufi_cb_env.blufi_inst.blufi_hdl) { - btc_msg_t msg; - struct blufi_recv_evt_param recv_data; - - memset(&recv_data, 0x00, sizeof(struct blufi_recv_evt_param)); - - p_rec_data = &p_data->req_data.p_data->write_req.value[0]; - recv_data.data_len = p_data->req_data.p_data->write_req.len; - memcpy(recv_data.data, p_rec_data, recv_data.data_len); - - msg.sig = BTC_SIG_API_CB; - msg.pid = BTC_PID_BLUFI; - msg.act = BTC_BLUFI_CB_ACT_RECV_DATA; - - btc_transfer_context(&msg, &recv_data, sizeof(struct blufi_recv_evt_param), NULL); + + if (p_data->req_data.p_data->write_req.handle == blufi_env.handle_char_p2e) { + btc_blufi_recv_handler(&p_data->req_data.p_data->write_req.value[0], + p_data->req_data.p_data->write_req.len); } + break; + } + case BTA_GATTS_EXEC_WRITE_EVT: + LOG_DEBUG("exec write exec %d\n", p_data->req_data.p_data->exec_write); + + BTA_GATTS_SendRsp(p_data->req_data.conn_id, p_data->req_data.trans_id, + GATT_SUCCESS, NULL); + + if (p_data->req_data.p_data->exec_write == GATT_PREP_WRITE_EXEC) { + btc_blufi_recv_handler(blufi_env.prepare_buf, blufi_env.prepare_len); + } + + if (blufi_env.prepare_buf) { + GKI_freebuf(blufi_env.prepare_buf); + blufi_env.prepare_buf = NULL; + } + break; case BTA_GATTS_CONF_EVT: + LOG_DEBUG("CONIRM EVT\n"); /* Nothing */ break; case BTA_GATTS_CREATE_EVT: - uuid.uu.uuid16 = CHAR_BLUFI_UUID; - blufi_cb_env.cur_srvc_id = p_data->create.service_id; - blufi_cb_env.is_primery = p_data->create.is_primary; - //start the blufi service after created - BTA_GATTS_StartService(p_data->create.service_id, BTA_GATT_TRANSPORT_LE); + blufi_env.handle_srvc = p_data->create.service_id; + //add the frist blufi characteristic --> write characteristic - BTA_GATTS_AddCharacteristic(blufi_cb_env.cur_srvc_id, &uuid, - (GATT_PERM_WRITE | GATT_PERM_READ), - (GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_WRITE | GATT_CHAR_PROP_BIT_NOTIFY)); + BTA_GATTS_AddCharacteristic(blufi_env.handle_srvc, &blufi_char_uuid_p2e, + (GATT_PERM_WRITE), + (GATT_CHAR_PROP_BIT_WRITE)); break; case BTA_GATTS_ADD_CHAR_EVT: - if (p_data->add_result.char_uuid.uu.uuid16 == CHAR_BLUFI_UUID) { - //save the att handle to the env - blufi_cb_env.blufi_inst.blufi_hdl = p_data->add_result.attr_id; + switch (p_data->add_result.char_uuid.uu.uuid16) { + case BLUFI_CHAR_P2E_UUID: /* Phone to ESP32 */ + blufi_env.handle_char_p2e = p_data->add_result.attr_id; - uuid.uu.uuid16 = GATT_UUID_CHAR_CLIENT_CONFIG; - BTA_GATTS_AddCharDescriptor (blufi_cb_env.cur_srvc_id, - (GATT_PERM_WRITE | GATT_PERM_WRITE), - &uuid); + BTA_GATTS_AddCharacteristic(blufi_env.handle_srvc, &blufi_char_uuid_e2p, + (GATT_PERM_READ), + (GATT_PERM_READ | GATT_CHAR_PROP_BIT_NOTIFY)); + break; + case BLUFI_CHAR_E2P_UUID: /* ESP32 to Phone */ + blufi_env.handle_char_e2p = p_data->add_result.attr_id; + + BTA_GATTS_AddCharDescriptor (blufi_env.handle_srvc, + (GATT_PERM_READ | GATT_PERM_WRITE), + &blufi_descr_uuid_e2p); + break; + default: + break; } break; case BTA_GATTS_ADD_CHAR_DESCR_EVT: { /* call init finish */ + esp_blufi_cb_param_t param; btc_msg_t msg; + blufi_env.handle_descr_e2p = p_data->add_result.attr_id; + //start the blufi service after created + BTA_GATTS_StartService(blufi_env.handle_srvc, BTA_GATT_TRANSPORT_LE); + msg.sig = BTC_SIG_API_CB; msg.pid = BTC_PID_BLUFI; - msg.act = BTC_BLUFI_CB_ACT_INIT_FINISH; - btc_transfer_context(&msg, NULL, 0, NULL); + msg.act = ESP_BLUFI_EVENT_INIT_FINISH; + param.init_finish.state = ESP_BLUFI_INIT_OK; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); break; } - case BTA_GATTS_CONNECT_EVT: + case BTA_GATTS_CONNECT_EVT: { + btc_msg_t msg; + esp_blufi_cb_param_t param; + //set the connection flag to true - LOG_ERROR("\ndevice is connected "BT_BD_ADDR_STR", server_if=%d,reason=0x%x,connect_id=%d\n", + LOG_INFO("\ndevice is connected "BT_BD_ADDR_STR", server_if=%d,reason=0x%x,connect_id=%d\n", BT_BD_ADDR_HEX(p_data->conn.remote_bda), p_data->conn.server_if, p_data->conn.reason, p_data->conn.conn_id); - blufi_cb_env.conn_id = p_data->conn.conn_id; + memcpy(blufi_env.remote_bda, p_data->conn.remote_bda, sizeof(esp_bd_addr_t)); + blufi_env.conn_id = p_data->conn.conn_id; + blufi_env.is_connected = true; - /*return whether the remote device is currently connected*/ - int is_connected = BTA_DmGetConnectionState(p_data->conn.remote_bda); - LOG_DEBUG("is_connected=%d\n", is_connected); - BTA_DmBleBroadcast(0); //stop adv + msg.sig = BTC_SIG_API_CB; + 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)); + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); break; - case BTA_GATTS_DISCONNECT_EVT: + } + case BTA_GATTS_DISCONNECT_EVT: { + btc_msg_t msg; + esp_blufi_cb_param_t param; + + blufi_env.is_connected = false; //set the connection flag to true - blufi_cb_env.connected = false; + LOG_INFO("\ndevice is disconnected "BT_BD_ADDR_STR", server_if=%d,reason=0x%x,connect_id=%d\n", + BT_BD_ADDR_HEX(p_data->conn.remote_bda), p_data->conn.server_if, + p_data->conn.reason, p_data->conn.conn_id); + + memcpy(blufi_env.remote_bda, p_data->conn.remote_bda, sizeof(esp_bd_addr_t)); + blufi_env.conn_id = p_data->conn.conn_id; + blufi_env.is_connected = false; + blufi_env.recv_seq = blufi_env.send_seq = 0; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_BLE_DISCONNECT; + memcpy(param.disconnect.remote_bda, p_data->conn.remote_bda, sizeof(esp_bd_addr_t)); + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); break; + } case BTA_GATTS_OPEN_EVT: break; case BTA_GATTS_CLOSE_EVT: - if (blufi_cb_env.connected && (blufi_cb_env.conn_id == p_data->conn.conn_id)) { - //set the connection channal congested flag to true - blufi_cb_env.congest = p_data->congest.congested; - } - break; - case BTA_GATTS_LISTEN_EVT: break; case BTA_GATTS_CONGEST_EVT: break; @@ -290,72 +275,594 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) static tGATT_STATUS btc_blufi_profile_init(void) { - tBT_UUID app_uuid = {LEN_UUID_16, {SVC_BLUFI_UUID}}; + esp_blufi_callbacks_t *store_p = blufi_env.cbs; - - if (blufi_cb_env.enabled) { - LOG_ERROR("blufi svc already initaliezd"); + if (blufi_env.enabled) { + LOG_ERROR("BLUFI already initialized"); return GATT_ERROR; - } else { - memset(&blufi_cb_env, 0, sizeof(tBLUFI_CB_ENV)); } + memset(&blufi_env, 0x0, sizeof(blufi_env)); + blufi_env.cbs = store_p; /* if set callback prior, restore the point */ - /* register the blufi profile to the BTA_GATTS module*/ - BTA_GATTS_AppRegister(&app_uuid, blufi_profile_cb); + /* register the BLUFI profile to the BTA_GATTS module*/ + BTA_GATTS_AppRegister(&blufi_app_uuid, blufi_profile_cb); return GATT_SUCCESS; } -static void blufi_msg_notify(UINT8 *blufi_msg, UINT8 len) +static tGATT_STATUS btc_blufi_profile_deinit(void) { - BOOLEAN conn_status = blufi_cb_env.connected; - UINT16 conn_id = blufi_cb_env.conn_id; - UINT16 attr_id = blufi_cb_env.blufi_inst.blufi_hdl; - //notify rsp==false; indicate rsp==true. - BOOLEAN rsp = false; - if (!conn_status && blufi_cb_env.congest) { - LOG_ERROR("the conneciton for blufi profile has been loss"); + esp_blufi_cb_param_t param; + btc_msg_t msg; + + if (!blufi_env.enabled) { + LOG_ERROR("BLUFI already initialized"); + return GATT_ERROR; + } + + BTA_GATTS_StopService(blufi_env.handle_srvc); + BTA_GATTS_DeleteService(blufi_env.handle_srvc); + /* register the BLUFI profile to the BTA_GATTS module*/ + BTA_GATTS_AppDeregister(blufi_env.gatt_if); + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_DEINIT_FINISH; + param.deinit_finish.state = ESP_BLUFI_DEINIT_OK; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); + + return GATT_SUCCESS; +} + +static void btc_blufi_send_notify(uint8_t *pkt, int pkt_len) +{ + UINT16 conn_id = blufi_env.conn_id; + UINT16 attr_id = blufi_env.handle_char_e2p; + bool rsp = false; + + BTA_GATTS_HandleValueIndication(conn_id, attr_id, pkt_len, + pkt, rsp); +} + +static void btc_blufi_recv_handler(uint8_t *data, int len) +{ + struct blufi_hdr *hdr = (struct blufi_hdr *)data; + uint16_t checksum; + int ret; + + 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); return; } - BTA_GATTS_HandleValueIndication (conn_id, attr_id, len, - blufi_msg, rsp); + blufi_env.recv_seq++; + + // first step, decrypt + if (BLUFI_FC_IS_ENC(hdr->fc) + && (blufi_env.cbs && blufi_env.cbs->decrypt_func)) { + 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); + return; + } + } + + // second step, check sum + if (BLUFI_FC_IS_CHECK(hdr->fc) + && (blufi_env.cbs && blufi_env.cbs->checksum_func)) { + checksum = blufi_env.cbs->checksum_func(hdr->seq, &hdr->seq, hdr->data_len + 2); + if (memcmp(&checksum, &hdr->data[hdr->data_len], 2) != 0) { + LOG_ERROR("%s checksum error %04x, pkt %04x\n", __func__, checksum, *(uint16_t *)&hdr->data[hdr->data_len]); + return; + } + } + + if (BLUFI_FC_IS_REQ_ACK(hdr->fc)) { + btc_blufi_send_ack(hdr->seq); + } + + if (BLUFI_FC_IS_FRAG(hdr->fc)) { + if (blufi_env.offset == 0) { + blufi_env.total_len = *(uint16_t *)(hdr->data); + blufi_env.aggr_buf = GKI_getbuf(blufi_env.total_len); + if (blufi_env.aggr_buf == NULL) { + LOG_ERROR("%s no mem, len %d\n", __func__, blufi_env.total_len); + return; + } + } + memcpy(blufi_env.aggr_buf + blufi_env.offset, hdr->data + 2, hdr->data_len - 2); + blufi_env.offset += (hdr->data_len - 2); + } else { + if (blufi_env.offset > 0) { /* if previous pkt is frag */ + memcpy(blufi_env.aggr_buf + blufi_env.offset, hdr->data, hdr->data_len); + + btc_blufi_protocol_handler(hdr->type, blufi_env.aggr_buf, blufi_env.total_len); + blufi_env.offset = 0; + GKI_freebuf(blufi_env.aggr_buf); + blufi_env.aggr_buf = NULL; + } else { + btc_blufi_protocol_handler(hdr->type, hdr->data, hdr->data_len); + blufi_env.offset = 0; + } + } +} +void btc_blufi_send_encap(uint8_t type, uint8_t *data, int total_data_len) +{ + struct blufi_hdr *hdr = NULL; + int remain_len = total_data_len; + uint16_t checksum; + int ret; + + while (remain_len > 0) { + if (remain_len > BLUFI_FRAG_DATA_MAX_LEN) { + hdr = GKI_getbuf(sizeof(struct blufi_hdr) + 2 + BLUFI_FRAG_DATA_MAX_LEN + 2); + if (hdr == NULL) { + LOG_ERROR("%s no mem\n", __func__); + return; + } + hdr->fc = 0x0; + hdr->data_len = BLUFI_FRAG_DATA_MAX_LEN + 2; + *(uint16_t *)hdr->data = remain_len; + memcpy(hdr->data + 2, &data[total_data_len - remain_len], BLUFI_FRAG_DATA_MAX_LEN); //copy first, easy for check sum + hdr->fc |= BLUFI_FC_FRAG; + } else { + hdr = GKI_getbuf(sizeof(struct blufi_hdr) + remain_len + 2); + if (hdr == NULL) { + LOG_ERROR("%s no mem\n", __func__); + return; + } + hdr->fc = 0x0; + hdr->data_len = remain_len; + memcpy(hdr->data, &data[total_data_len - remain_len], hdr->data_len); //copy first, easy for check sum + } + + hdr->type = type; + hdr->fc |= BLUFI_FC_DIR_E2P; + hdr->seq = blufi_env.send_seq++; + + if (BLUFI_TYPE_IS_CTRL(hdr->type)) { + if ((blufi_env.sec_mode & BLUFI_CTRL_SEC_MODE_CHECK_MASK) + && (blufi_env.cbs && blufi_env.cbs->checksum_func)) { + hdr->fc |= BLUFI_FC_CHECK; + 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)) { + if ((blufi_env.sec_mode & BLUFI_DATA_SEC_MODE_CHECK_MASK) + && (blufi_env.cbs && blufi_env.cbs->checksum_func)) { + hdr->fc |= BLUFI_FC_CHECK; + checksum = blufi_env.cbs->checksum_func(hdr->seq, &hdr->seq, hdr->data_len + 2); + memcpy(&hdr->data[hdr->data_len], &checksum, 2); + } + + if ((blufi_env.sec_mode & BLUFI_DATA_SEC_MODE_ENC_MASK) + && (blufi_env.cbs && blufi_env.cbs->encrypt_func)) { + ret = blufi_env.cbs->encrypt_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 */ + hdr->fc |= BLUFI_FC_ENC; + } else { + LOG_ERROR("%s encrypt error %d\n", __func__, ret); + GKI_freebuf(hdr); + return; + } + } + } + + if (hdr->fc & BLUFI_FC_FRAG) { + remain_len -= (hdr->data_len - 2); + } else { + remain_len -= hdr->data_len; + } + + btc_blufi_send_notify((uint8_t *)hdr, + ((hdr->fc & BLUFI_FC_CHECK) ? + hdr->data_len + sizeof(struct blufi_hdr) + 2 : + hdr->data_len + sizeof(struct blufi_hdr))); + + GKI_freebuf(hdr); + hdr = NULL; + } } -static void btc_blufi_config_success(void) +static void btc_blufi_wifi_conn_report(uint8_t opmode, uint8_t sta_conn_state, uint8_t softap_conn_num, esp_blufi_extra_info_t *info, int info_len) { - LOG_DEBUG("config success\n"); - blufi_msg_notify((uint8_t *)success_msg, strlen(success_msg)); + uint8_t type; + uint8_t *data; + int data_len; + uint8_t *p; + + data_len = info_len + 3; + p = data = GKI_getbuf(data_len); + if (data == NULL) { + return; + } + + type = BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_WIFI_REP); + *p++ = opmode; + *p++ = sta_conn_state; + *p++ = softap_conn_num; + + if (info) { + if (info->sta_bssid_set) { + *p++ = BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID; + *p++ = 6; + memcpy(p, info->sta_bssid, 6); + p += 6; + } + if (info->sta_ssid) { + *p++ = BLUFI_TYPE_DATA_SUBTYPE_STA_SSID; + *p++ = info->sta_ssid_len; + memcpy(p, info->sta_ssid, info->sta_ssid_len); + p += info->sta_ssid_len; + } + if (info->sta_passwd) { + *p++ = BLUFI_TYPE_DATA_SUBTYPE_STA_PASSWD; + *p++ = info->sta_passwd_len; + memcpy(p, info->sta_passwd, info->sta_passwd_len); + p += info->sta_passwd_len; + } + if (info->softap_ssid) { + *p++ = BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_SSID; + *p++ = info->softap_ssid_len; + memcpy(p, info->softap_ssid, info->softap_ssid_len); + p += info->softap_ssid_len; + } + if (info->softap_passwd) { + *p++ = BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_PASSWD; + *p++ = info->softap_passwd_len; + memcpy(p, info->softap_passwd, info->softap_passwd_len); + p += info->softap_passwd_len; + } + if (info->softap_authmode_set) { + *p++ = BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_AUTH_MODE; + *p++ = 1; + *p++ = info->softap_authmode; + } + if (info->softap_max_conn_num_set) { + *p++ = BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM; + *p++ = 1; + *p++ = info->softap_max_conn_num; + } + if (info->softap_channel_set) { + *p++ = BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM; + *p++ = 1; + *p++ = info->softap_channel; + } + } + 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); + GKI_freebuf(data); } -static void btc_blufi_config_failed(void) +static void btc_blufi_send_ack(uint8_t seq) { - LOG_DEBUG("config faield\n"); - blufi_msg_notify((uint8_t *)failed_msg, strlen(failed_msg)); + uint8_t type; + uint8_t data; + + type = BLUFI_BUILD_TYPE(BLUFI_TYPE_CTRL, BLUFI_TYPE_CTRL_SUBTYPE_ACK); + data = seq; + + btc_blufi_send_encap(type, &data, 1); +} + +void btc_blufi_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_blufi_cb_param_t *dst = (esp_blufi_cb_param_t *) p_dest; + esp_blufi_cb_param_t *src = (esp_blufi_cb_param_t *) p_src; + + switch (msg->act) { + case ESP_BLUFI_EVENT_RECV_STA_SSID: + dst->sta_ssid.ssid = GKI_getbuf(src->sta_ssid.ssid_len); + if (dst->sta_ssid.ssid == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->sta_ssid.ssid, src->sta_ssid.ssid, src->sta_ssid.ssid_len); + break; + case ESP_BLUFI_EVENT_RECV_STA_PASSWD: + dst->sta_passwd.passwd = GKI_getbuf(src->sta_passwd.passwd_len); + if (dst->sta_passwd.passwd == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->sta_passwd.passwd, src->sta_passwd.passwd, src->sta_passwd.passwd_len); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID: + dst->softap_ssid.ssid = GKI_getbuf(src->softap_ssid.ssid_len); + if (dst->softap_ssid.ssid == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->softap_ssid.ssid, src->softap_ssid.ssid, src->softap_ssid.ssid_len); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD: + dst->softap_passwd.passwd = GKI_getbuf(src->softap_passwd.passwd_len); + if (dst->softap_passwd.passwd == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->softap_passwd.passwd, src->softap_passwd.passwd, src->softap_passwd.passwd_len); + break; + case ESP_BLUFI_EVENT_RECV_USERNAME: + dst->username.name = GKI_getbuf(src->username.name_len); + if (dst->username.name == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->username.name, src->username.name, src->username.name_len); + break; + case ESP_BLUFI_EVENT_RECV_CA_CERT: + dst->ca.cert = GKI_getbuf(src->ca.cert_len); + if (dst->ca.cert == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->ca.cert, src->ca.cert, src->ca.cert_len); + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_CERT: + dst->client_cert.cert = GKI_getbuf(src->client_cert.cert_len); + if (dst->client_cert.cert == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->client_cert.cert, src->client_cert.cert, src->client_cert.cert_len); + break; + case ESP_BLUFI_EVENT_RECV_SERVER_CERT: + dst->server_cert.cert = GKI_getbuf(src->server_cert.cert_len); + if (dst->server_cert.cert == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->server_cert.cert, src->server_cert.cert, src->server_cert.cert_len); + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY: + dst->client_pkey.pkey = GKI_getbuf(src->client_pkey.pkey_len); + if (dst->client_pkey.pkey == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->client_pkey.pkey, src->client_pkey.pkey, src->client_pkey.pkey_len); + break; + case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY: + dst->server_pkey.pkey = GKI_getbuf(src->server_pkey.pkey_len); + if (dst->server_pkey.pkey == NULL) { + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + memcpy(dst->server_pkey.pkey, src->server_pkey.pkey, src->server_pkey.pkey_len); + break; + default: + break; + } +} + +void btc_blufi_cb_deep_free(btc_msg_t *msg) +{ + esp_blufi_cb_param_t *param = (esp_blufi_cb_param_t *)msg->arg; + + switch (msg->act) { + case ESP_BLUFI_EVENT_RECV_STA_SSID: + GKI_freebuf(param->sta_ssid.ssid); + break; + case ESP_BLUFI_EVENT_RECV_STA_PASSWD: + GKI_freebuf(param->sta_passwd.passwd); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID: + GKI_freebuf(param->softap_ssid.ssid); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD: + GKI_freebuf(param->softap_passwd.passwd); + break; + case ESP_BLUFI_EVENT_RECV_USERNAME: + GKI_freebuf(param->username.name); + break; + case ESP_BLUFI_EVENT_RECV_CA_CERT: + GKI_freebuf(param->ca.cert); + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_CERT: + GKI_freebuf(param->client_cert.cert); + break; + case ESP_BLUFI_EVENT_RECV_SERVER_CERT: + GKI_freebuf(param->server_cert.cert); + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY: + GKI_freebuf(param->client_pkey.pkey); + break; + case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY: + GKI_freebuf(param->server_pkey.pkey); + break; + default: + break; + } } void btc_blufi_cb_handler(btc_msg_t *msg) { - esp_blufi_cb_param_t param; + esp_blufi_cb_param_t *param = (esp_blufi_cb_param_t *)msg->arg; switch (msg->act) { - case BTC_BLUFI_CB_ACT_INIT_FINISH: - param.init_finish.state = ESP_BLUFI_INIT_OK; - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_INIT_FINISH, ¶m); + case ESP_BLUFI_EVENT_INIT_FINISH: { + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_INIT_FINISH, param); break; - case BTC_BLUFI_CB_ACT_DEINIT_FINISH: - /* TODO: but now nothing */ + } + case ESP_BLUFI_EVENT_DEINIT_FINISH: { + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_DEINIT_FINISH, param); break; - case BTC_BLUFI_CB_ACT_RECV_DATA: - memcpy(¶m.recv_data, msg->arg, sizeof(struct blufi_recv_evt_param)); - - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_DATA, ¶m); + } + case ESP_BLUFI_EVENT_BLE_CONNECT: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_BLE_CONNECT, param); + break; + case ESP_BLUFI_EVENT_BLE_DISCONNECT: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_BLE_DISCONNECT, param); + break; + case ESP_BLUFI_EVENT_SET_WIFI_OPMODE: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_SET_WIFI_OPMODE, param); + break; + case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP, NULL); + break; + case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP, NULL); + break; + case ESP_BLUFI_EVENT_GET_WIFI_STATUS: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_GET_WIFI_STATUS, NULL); + break; + case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_DEAUTHENTICATE_STA, NULL); + break; + case ESP_BLUFI_EVENT_RECV_STA_BSSID: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_STA_BSSID, param); + break; + case ESP_BLUFI_EVENT_RECV_STA_SSID: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_STA_SSID, param); + break; + case ESP_BLUFI_EVENT_RECV_STA_PASSWD: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_STA_PASSWD, param); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_SSID, param); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD, param); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM, param); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE, param); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL, param); + break; + case ESP_BLUFI_EVENT_RECV_USERNAME: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_USERNAME, param); + break; + case ESP_BLUFI_EVENT_RECV_CA_CERT: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_CA_CERT, param); + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_CERT: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_CLIENT_CERT, param); + break; + case ESP_BLUFI_EVENT_RECV_SERVER_CERT: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SERVER_CERT, param); + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY, param); + break; + case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY: + BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY, param); break; default: LOG_ERROR("%s UNKNOWN %d\n", __func__, msg->act); break; } + + btc_blufi_cb_deep_free(msg); +} + +void btc_blufi_call_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_blufi_args_t *dst = (btc_blufi_args_t *) p_dest; + btc_blufi_args_t *src = (btc_blufi_args_t *) p_src; + + switch (msg->act) { + case BTC_BLUFI_ACT_SEND_CFG_REPORT: { + esp_blufi_extra_info_t *src_info = src->wifi_conn_report.extra_info; + dst->wifi_conn_report.extra_info_len = 0; + dst->wifi_conn_report.extra_info = NULL; + + if (src_info == NULL) { + return; + } + + dst->wifi_conn_report.extra_info = GKI_getbuf(sizeof(esp_blufi_extra_info_t)); + if (dst->wifi_conn_report.extra_info == NULL) { + return; + } + + if (src_info->sta_bssid_set) { + memcpy(dst->wifi_conn_report.extra_info->sta_bssid, src_info->sta_bssid, 6); + dst->wifi_conn_report.extra_info->sta_bssid_set = src_info->sta_bssid_set; + dst->wifi_conn_report.extra_info_len += (6 + 2); + } + if (src_info->sta_ssid) { + dst->wifi_conn_report.extra_info->sta_ssid = GKI_getbuf(src_info->sta_ssid_len); + if (dst->wifi_conn_report.extra_info->sta_ssid) { + memcpy(dst->wifi_conn_report.extra_info->sta_ssid, src_info->sta_ssid, src_info->sta_ssid_len); + dst->wifi_conn_report.extra_info->sta_ssid_len = src_info->sta_ssid_len; + dst->wifi_conn_report.extra_info_len += (dst->wifi_conn_report.extra_info->sta_ssid_len + 2); + } + } + if (src_info->sta_passwd) { + dst->wifi_conn_report.extra_info->sta_passwd = GKI_getbuf(src_info->sta_passwd_len); + if (dst->wifi_conn_report.extra_info->sta_passwd) { + memcpy(dst->wifi_conn_report.extra_info->sta_passwd, src_info->sta_passwd, src_info->sta_passwd_len); + dst->wifi_conn_report.extra_info->sta_passwd_len = src_info->sta_passwd_len; + dst->wifi_conn_report.extra_info_len += (dst->wifi_conn_report.extra_info->sta_passwd_len + 2); + } + } + if (src_info->softap_ssid) { + dst->wifi_conn_report.extra_info->softap_ssid = GKI_getbuf(src_info->softap_ssid_len); + if (dst->wifi_conn_report.extra_info->softap_ssid) { + memcpy(dst->wifi_conn_report.extra_info->softap_ssid, src_info->softap_ssid, src_info->softap_ssid_len); + dst->wifi_conn_report.extra_info->softap_ssid_len = src_info->softap_ssid_len; + dst->wifi_conn_report.extra_info_len += (dst->wifi_conn_report.extra_info->softap_ssid_len + 2); + } + } + if (src_info->softap_passwd) { + dst->wifi_conn_report.extra_info->softap_passwd = GKI_getbuf(src_info->softap_passwd_len); + if (dst->wifi_conn_report.extra_info->softap_passwd) { + memcpy(dst->wifi_conn_report.extra_info->softap_passwd, src_info->softap_passwd, src_info->softap_passwd_len); + dst->wifi_conn_report.extra_info->softap_passwd_len = src_info->softap_passwd_len; + dst->wifi_conn_report.extra_info_len += (dst->wifi_conn_report.extra_info->softap_passwd_len + 2); + } + } + if (src_info->softap_authmode_set) { + dst->wifi_conn_report.extra_info->softap_authmode_set = src_info->softap_authmode_set; + dst->wifi_conn_report.extra_info->softap_authmode = src_info->softap_authmode; + dst->wifi_conn_report.extra_info_len += (1 + 2); + } + if (src_info->softap_max_conn_num_set) { + dst->wifi_conn_report.extra_info->softap_max_conn_num_set = src_info->softap_max_conn_num_set; + dst->wifi_conn_report.extra_info->softap_max_conn_num = src_info->softap_max_conn_num; + dst->wifi_conn_report.extra_info_len += (1 + 2); + } + if (src_info->softap_channel_set) { + dst->wifi_conn_report.extra_info->softap_channel_set = src_info->softap_channel_set; + dst->wifi_conn_report.extra_info->softap_channel = src_info->softap_channel; + dst->wifi_conn_report.extra_info_len += (1 + 2); + } + break; + } + default: + break; + } +} + +void btc_blufi_call_deep_free(btc_msg_t *msg) +{ + btc_blufi_args_t *arg = (btc_blufi_args_t *)msg->arg; + + switch (msg->act) { + case BTC_BLUFI_ACT_SEND_CFG_REPORT: { + esp_blufi_extra_info_t *info = (esp_blufi_extra_info_t *)arg->wifi_conn_report.extra_info; + + if (info == NULL) { + return; + } + if (info->sta_ssid) { + GKI_freebuf(info->sta_ssid); + } + if (info->sta_passwd) { + GKI_freebuf(info->sta_passwd); + } + if (info->softap_ssid) { + GKI_freebuf(info->softap_ssid); + } + if (info->softap_passwd) { + GKI_freebuf(info->softap_passwd); + } + GKI_freebuf(info); + break; + } + default: + break; + } } void btc_blufi_call_handler(btc_msg_t *msg) @@ -367,17 +874,24 @@ void btc_blufi_call_handler(btc_msg_t *msg) btc_blufi_profile_init(); break; case BTC_BLUFI_ACT_DEINIT: - /* TODO: but now nothing */ + btc_blufi_profile_deinit(); break; - case BTC_BLUFI_ACT_SEND_CFG_STATE: - if (arg->cfg_state.state == ESP_BLUFI_CONFIG_OK) { - btc_blufi_config_success(); - } else { - btc_blufi_config_failed(); - } + case BTC_BLUFI_ACT_SEND_CFG_REPORT: + btc_blufi_wifi_conn_report(arg->wifi_conn_report.opmode, + arg->wifi_conn_report.sta_conn_state, + arg->wifi_conn_report.softap_conn_num, + arg->wifi_conn_report.extra_info, + arg->wifi_conn_report.extra_info_len); break; default: LOG_ERROR("%s UNKNOWN %d\n", __func__, msg->act); break; } + btc_blufi_call_deep_free(msg); } + +void btc_blufi_set_callbacks(esp_blufi_callbacks_t *callbacks) +{ + blufi_env.cbs = callbacks; +} + diff --git a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c new file mode 100644 index 0000000000..2f92a43806 --- /dev/null +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c @@ -0,0 +1,240 @@ +// 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 + +#include +#include +#include + + +#include "bt_target.h" +#include "bt_trace.h" +#include "bt_types.h" +#include "gatt_api.h" +#include "bta_api.h" +#include "bta_gatt_api.h" +#include "bta_gatts_int.h" + +#include "btc_blufi_prf.h" +#include "btc_task.h" +#include "btc_manage.h" + +#include "blufi_int.h" + +#include "esp_wifi.h" + +extern tBLUFI_ENV blufi_env; + +void btc_blufi_protocol_handler(uint8_t type, uint8_t *data, int len) +{ + btc_msg_t msg; + esp_blufi_cb_param_t param; + uint8_t *output_data = NULL; + int output_len = 0; + bool need_free = false; + + switch (BLUFI_GET_TYPE(type)) { + case BLUFI_TYPE_CTRL: + switch (BLUFI_GET_SUBTYPE(type)) { + case BLUFI_TYPE_CTRL_SUBTYPE_ACK: + /* TODO: check sequence */ + break; + case BLUFI_TYPE_CTRL_SUBTYPE_SET_SEC_MODE: + blufi_env.sec_mode = data[0]; + break; + case BLUFI_TYPE_CTRL_SUBTYPE_SET_WIFI_OPMODE: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_SET_WIFI_OPMODE; + param.wifi_mode.op_mode = data[0]; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); + break; + case BLUFI_TYPE_CTRL_SUBTYPE_CONN_TO_AP: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP; + + btc_transfer_context(&msg, NULL, 0, NULL); + break; + case BLUFI_TYPE_CTRL_SUBTYPE_DISCONN_FROM_AP: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP; + + btc_transfer_context(&msg, NULL, 0, NULL); + break; + case BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_STATUS: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_GET_WIFI_STATUS; + + btc_transfer_context(&msg, NULL, 0, NULL); + break; + case BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_DEAUTHENTICATE_STA; + + btc_transfer_context(&msg, NULL, 0, NULL); + break; + default: + LOG_ERROR("%s Unkown Ctrl pkt %02x\n", __func__, type); + break; + } + break; + case BLUFI_TYPE_DATA: + switch (BLUFI_GET_SUBTYPE(type)) { + case BLUFI_TYPE_DATA_SUBTYPE_NEG: + if (blufi_env.cbs && blufi_env.cbs->negotiate_data_handler) { + blufi_env.cbs->negotiate_data_handler(data, len, &output_data, &output_len, &need_free); + } + + if (output_data && output_len > 0) { + btc_blufi_send_encap(BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_NEG), + output_data, output_len); + } + break; + case BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_STA_BSSID; + memcpy(param.sta_bssid.bssid, &data[0], 6); + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); + break; + case BLUFI_TYPE_DATA_SUBTYPE_STA_SSID: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_STA_SSID; + param.sta_ssid.ssid = &data[0]; + param.sta_ssid.ssid_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_STA_PASSWD: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_STA_PASSWD; + param.sta_passwd.passwd = &data[0]; + param.sta_passwd.passwd_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_SSID: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_SOFTAP_SSID; + param.softap_ssid.ssid = &data[0]; + param.softap_ssid.ssid_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_PASSWD: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD; + param.softap_passwd.passwd = &data[0]; + param.softap_passwd.passwd_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_MAX_CONN_NUM: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM; + param.softap_max_conn_num.max_conn_num = data[0]; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); + break; + case BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_AUTH_MODE: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE; + param.softap_auth_mode.auth_mode = data[0]; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); + break; + case BLUFI_TYPE_DATA_SUBTYPE_SOFTAP_CHANNEL: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL; + param.softap_channel.channel = data[0]; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), NULL); + break; + case BLUFI_TYPE_DATA_SUBTYPE_USERNAME: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_USERNAME; + param.username.name = &data[0]; + param.username.name_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_CA: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_CA_CERT; + param.ca.cert = &data[0]; + param.ca.cert_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_CLIENT_CERT: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_CLIENT_CERT; + param.client_cert.cert = &data[0]; + param.client_cert.cert_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_SERVER_CERT: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_SERVER_CERT; + param.client_cert.cert = &data[0]; + param.client_cert.cert_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_CLIENT_PRIV_KEY: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY; + param.client_pkey.pkey = &data[0]; + param.client_pkey.pkey_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + case BLUFI_TYPE_DATA_SUBTYPE_SERVER_PRIV_KEY: + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLUFI; + msg.act = ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY; + param.client_pkey.pkey = &data[0]; + param.client_pkey.pkey_len = len; + + btc_transfer_context(&msg, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy); + break; + default: + LOG_ERROR("%s Unkown Ctrl pkt %02x\n", __func__, type); + break; + } + break; + default: + break; + } +} diff --git a/components/bt/bluedroid/btc/profile/esp/blufi/include/blufi_adv.h b/components/bt/bluedroid/btc/profile/esp/blufi/include/blufi_adv.h deleted file mode 100644 index 39869ec9e4..0000000000 --- a/components/bt/bluedroid/btc/profile/esp/blufi/include/blufi_adv.h +++ /dev/null @@ -1,39 +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. - -#ifndef __BLUFI_ADV_H__ -#define __BLUFI_ADV_H__ - -#include "bta_api.h" -#include "btm_ble_api.h" -#include "esp_bt_defs.h" - -typedef enum { - BLE_ADV_DATA_IDX = 0, - BLE_SCAN_RSP_DATA_IDX = 1, - ADV_SCAN_IDX_MAX, -} ADV_SCAN_IDX_t; - -typedef struct { - char *adv_name; //set the device name to be sent on the advertising - tBTA_BLE_ADV_DATA ble_adv_data; -} tBLUFI_BLE_ADV_DATA; - -extern void BlufiBleConfigadvData(tBLUFI_BLE_ADV_DATA *adv_data, - tBTA_SET_ADV_DATA_CMPL_CBACK *p_adv_data_cback); - -extern void BlufiBleSetScanRsp(tBLUFI_BLE_ADV_DATA *scan_rsp_data, - tBTA_SET_ADV_DATA_CMPL_CBACK *p_scan_rsp_data_cback); - -#endif /* __BLUFI_ADV_H__ */ 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 f46a41a2e4..0de66b8e77 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 @@ -15,38 +15,156 @@ #ifndef __BLUFI_INT_H__ #define __BLUFI_INT_H__ -//define the blufi serivce uuid -#define SVC_BLUFI_UUID 0xFFFF -//define the blufi Char uuid -#define CHAR_BLUFI_UUID 0xFF01 - -#define BLUFI_HDL_NUM 4 - -#define BLUFI_VAL_MAX_LEN (128) - -#define BLUFI_MAX_STRING_DATA 128 - - -typedef struct { - UINT8 app_id; - UINT16 blufi_hdl; -} tBLUFI_INST; - - /* service engine control block */ typedef struct { - BOOLEAN enabled; - BOOLEAN is_primery; - UINT8 inst_id; + /* Protocol reference */ tGATT_IF gatt_if; - tBLUFI_INST blufi_inst; - BOOLEAN in_use; - BOOLEAN congest; + UINT8 srvc_inst; + UINT16 handle_srvc; + UINT16 handle_char_p2e; + UINT16 handle_char_e2p; + UINT16 handle_descr_e2p; UINT16 conn_id; - BOOLEAN connected; + BOOLEAN is_connected; BD_ADDR remote_bda; UINT32 trans_id; - UINT8 cur_srvc_id; -} tBLUFI_CB_ENV; + UINT8 congest; +#define BLUFI_PREPAIR_BUF_MAX_SIZE 1024 + uint8_t *prepare_buf; + int prepare_len; + /* Control reference */ + esp_blufi_callbacks_t *cbs; + BOOLEAN enabled; + uint8_t send_seq; + uint8_t recv_seq; + uint8_t sec_mode; + uint8_t *aggr_buf; + uint16_t total_len; + uint16_t offset; +} tBLUFI_ENV; + +/* BLUFI protocol */ +struct blufi_hdr{ + uint8_t type; + uint8_t fc; + uint8_t seq; + uint8_t data_len; + uint8_t data[0]; +}; +typedef struct blufi_hdr blufi_hd_t; + +struct blufi_frag_hdr { + uint8_t type; + uint8_t fc; + uint8_t seq; + uint8_t data_len; + uint16_t total_len; + uint8_t content[0]; +}; +typedef struct blufi_frag_hdr blufi_frag_hdr_t; + +#define BLUFI_DATA_SEC_MODE_CHECK_MASK 0x01 +#define BLUFI_DATA_SEC_MODE_ENC_MASK 0x02 +#define BLUFI_CTRL_SEC_MODE_CHECK_MASK 0x10 +#define BLUFI_CTRL_SEC_MODE_ENC_MASK 0x20 + +// packet type +#define BLUFI_TYPE_MASK 0x03 +#define BLUFI_TYPE_SHIFT 0 +#define BLUFI_SUBTYPE_MASK 0xFC +#define BLUFI_SUBTYPE_SHIFT 2 + +#define BLUFI_GET_TYPE(type) ((type) & BLUFI_TYPE_MASK) +#define BLUFI_GET_SUBTYPE(type) (((type) & BLUFI_SUBTYPE_MASK) >>BLUFI_SUBTYPE_SHIFT) +#define BLUFI_BUILD_TYPE(type, subtype) (((type) & BLUFI_TYPE_MASK) | ((subtype)<p_services->p_uuid) { - LOG_ERROR("%s - In 16-UUID_data", __FUNCTION__); + LOG_DEBUG("%s - In 16-UUID_data", __FUNCTION__); mask |= BTM_BLE_AD_BIT_SERVICE; ++bta_adv_data->p_services->num_service; *p_uuid_out16++ = bt_uuid.uu.uuid16; @@ -221,7 +221,7 @@ static void btc_to_bta_adv_data(esp_ble_adv_data_t *p_adv_data, tBTA_BLE_ADV_DAT } if (NULL != bta_adv_data->p_service_32b->p_uuid) { - LOG_ERROR("%s - In 32-UUID_data", __FUNCTION__); + LOG_DEBUG("%s - In 32-UUID_data", __FUNCTION__); mask |= BTM_BLE_AD_BIT_SERVICE_32; ++bta_adv_data->p_service_32b->num_service; *p_uuid_out32++ = bt_uuid.uu.uuid32; diff --git a/components/bt/bluedroid/hci/packet_fragmenter.c b/components/bt/bluedroid/hci/packet_fragmenter.c index 42663a249a..bd46041c3b 100644 --- a/components/bt/bluedroid/hci/packet_fragmenter.c +++ b/components/bt/bluedroid/hci/packet_fragmenter.c @@ -147,16 +147,16 @@ static void reassemble_and_dispatch(BT_HDR *packet) if (boundary_flag == START_PACKET_BOUNDARY) { if (partial_packet) { - LOG_ERROR("%s found unfinished packet for handle with start packet. Dropping old.\n", __func__); - LOG_ERROR("partial_packet->len = %x, offset = %x\n", partial_packet->len, partial_packet->len); + LOG_DEBUG("%s found unfinished packet for handle with start packet. Dropping old.\n", __func__); + LOG_DEBUG("partial_packet->len = %x, offset = %x\n", partial_packet->len, partial_packet->len); - for (int i = 0; i < partial_packet->len; i++) { - LOG_ERROR("%x", partial_packet->data[i]); - } - LOG_ERROR("\n"); + //for (int i = 0; i < partial_packet->len; i++) { + // LOG_ERROR("%x", partial_packet->data[i]); + //} + //LOG_ERROR("\n"); hash_map_erase(partial_packets, (void *)(uintptr_t)handle); //buffer_allocator->free(partial_packet); - LOG_ERROR("+++++++++++++++++++\n"); + //LOG_ERROR("+++++++++++++++++++\n"); } uint16_t full_length = l2cap_length + L2CAP_HEADER_SIZE + HCI_ACL_PREAMBLE_SIZE; @@ -214,7 +214,7 @@ static void reassemble_and_dispatch(BT_HDR *packet) STREAM_TO_UINT16(handle, stream); STREAM_TO_UINT16(acl_length, stream); STREAM_TO_UINT16(l2cap_length, stream); - LOG_ERROR("partial_packet->offset = %x\n", partial_packet->offset); + LOG_DEBUG("partial_packet->offset = %x\n", partial_packet->offset); hash_map_erase(partial_packets, (void *)(uintptr_t)handle); partial_packet->offset = 0; diff --git a/components/bt/bluedroid/include/bt_trace.h b/components/bt/bluedroid/include/bt_trace.h index fd92f6e8c5..787c4c8aec 100644 --- a/components/bt/bluedroid/include/bt_trace.h +++ b/components/bt/bluedroid/include/bt_trace.h @@ -32,7 +32,7 @@ #define assert(x) do { if (!(x)) BT_PRINTF("bt host error %s %u\n", __FILE__, __LINE__); } while (0) #endif -inline void trc_dump_buffer(uint8_t *prefix, uint8_t *data, uint16_t len) +inline void trc_dump_buffer(const char *prefix, uint8_t *data, uint16_t len) { uint16_t i; @@ -41,14 +41,13 @@ inline void trc_dump_buffer(uint8_t *prefix, uint8_t *data, uint16_t len) } if (prefix) { - BT_PRINTF("%s:\t", prefix); + BT_PRINTF("%s: len %d\n", prefix, len); } - for (i = 0; i < len; i++) { - BT_PRINTF(" %02x", *(data + i)); - if (!((i + 1) & 0xf)) { - BT_PRINTF("\n"); - } + for (i = 0; i < len; i+=16) { + BT_PRINTF("%02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x\n", + *(data + i), *(data + i + 1), *(data + i + 2), *(data + i + 3), *(data + i + 4), *(data + i + 5), *(data + i + 6), *(data + i + 7), + *(data + i + 8), *(data + i + 9), *(data + i + 10), *(data + i + 11), *(data + i + 12), *(data + i + 13), *(data + i + 14), *(data + i + 15)); } BT_PRINTF("\n"); } diff --git a/docs/api/bt_le.rst b/docs/api/bt_le.rst index 6ae4869372..11d09809fe 100644 --- a/docs/api/bt_le.rst +++ b/docs/api/bt_le.rst @@ -8,3 +8,4 @@ BT COMMON BLE GATT DEFINE BLE GATT SERVER BLE GATT CLIENT + BLE BLUFI diff --git a/docs/api/esp_blufi.rst b/docs/api/esp_blufi.rst new file mode 100644 index 0000000000..1ffb67bec9 --- /dev/null +++ b/docs/api/esp_blufi.rst @@ -0,0 +1,128 @@ +BLUFI API +========= + +Overview +-------- +BLUFI is a profile based GATT to config ESP32 WIFI to connect/disconnect AP or setup a softap and etc. +Use should concern these things: + 1. The event sent from profile. Then you need to do something as the event indicate. + 2. Security reference. You can write your own Security functions such as symmetrical encryption/decryption and checksum functions. + Even you can define the "Key Exchange/Negotiation" procedure. + +Application Example +------------------- + +Check `/examples `_ folder of `espressif/esp-idf `_ repository, that contains the following example: + +`12_blufi `_ + +This is a BLUFI demo. This demo can set ESP32's wifi to softap/station/softap&station mode and config wifi connections. + + +API Reference +------------- + +Header Files +^^^^^^^^^^^^ + + * `bt/bluedroid/api/include/esp_blufi_api.h `_ + +Macros +^^^^^^ + + +Type Definitions +^^^^^^^^^^^^^^^^ + +.. doxygentypedef:: esp_blufi_event_cb_t +.. doxygentypedef:: esp_blufi_negotiate_data_handler_t +.. doxygentypedef:: esp_blufi_encrypt_func_t +.. doxygentypedef:: esp_blufi_decrypt_func_t +.. doxygentypedef:: esp_blufi_checksum_func_t + +Enumerations +^^^^^^^^^^^^ + +.. doxygenenum:: esp_blufi_cb_event_t +.. doxygenenum:: esp_blufi_sta_conn_state_t +.. doxygenenum:: esp_blufi_init_state_t +.. doxygenenum:: esp_blufi_deinit_state_t + +Structures +^^^^^^^^^^ + +.. doxygenstruct:: esp_blufi_extra_info_t + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_init_finish_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_deinit_finish_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_set_wifi_mode_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_connect_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_disconnect_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_sta_bssid_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_sta_ssid_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_sta_passwd_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_softap_ssid_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_softap_passwd_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_softap_max_conn_num_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_softap_auth_mode_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_softap_channel_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_username_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_ca_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_client_cert_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_server_cert_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_client_pkey_evt_param + :members: + +.. doxygenstruct:: esp_blufi_cb_param_t::blufi_recv_server_pkey_evt_param + :members: + +.. doxygenstruct:: esp_blufi_callbacks_t + :members: + + +Functions +^^^^^^^^^ + +.. doxygenfunction:: esp_blufi_register_callbacks +.. doxygenfunction:: esp_blufi_profile_init +.. doxygenfunction:: esp_blufi_profile_deinit +.. doxygenfunction:: esp_blufi_send_wifi_conn_report + diff --git a/examples/12_blufi/components/blufi/blufi.c b/examples/12_blufi/components/blufi/blufi.c deleted file mode 100644 index 8e2b5d1f39..0000000000 --- a/examples/12_blufi/components/blufi/blufi.c +++ /dev/null @@ -1,132 +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. - -/*************************************************************** -* -* This file is for gatt server device. It instantiates BATTERY -* sevice. It can be scanned and connected by central device, -* and the client will get the BAS value. It calls the API bta -* layer provides. -* -****************************************************************/ - -#include -#include -#include -#include - -#include "controller.h" - -#include "bt_trace.h" -#include "bt_types.h" -#include "bta_api.h" - -#include "blufi.h" - -#include "esp_bt_defs.h" -#include "esp_bt_main.h" -#include "esp_blufi_api.h" - -extern void wifi_set_blue_config(char *ssid, char *passwd); - -#define HEADER_SSID "ssid" -#define HEADER_PASSWD "passwd" -#define HEADER_CONFIRM "confirm" - -static char tmp_ssid[32 + 1]; -static char tmp_passwd[64 + 1]; - -static void blufi_data_recv(uint8_t *data, int len) -{ - char *p = NULL; - LOG_DEBUG("the data is:%s\n", data); - - p = strstr((char *)data, HEADER_SSID); - if (p) { - LOG_ERROR("SSID: %s\n", p + strlen(HEADER_SSID) + 1); - strcpy(tmp_ssid, p + strlen(HEADER_SSID) + 1); - } - p = strstr((char *)data, HEADER_PASSWD); - if (p) { - LOG_ERROR("PASSWORD: %s\n", p + strlen(HEADER_PASSWD) + 1); - strcpy(tmp_passwd, p + strlen(HEADER_PASSWD) + 1); - } - p = strstr((char *)data, HEADER_CONFIRM); - if (p) { - LOG_ERROR("CONFIRM\n"); - wifi_set_blue_config(tmp_ssid, tmp_passwd); - } - -} - -static void blufi_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param) -{ - /* actually, should post to blufi_task handle the procedure, - * now, as a demo, we do simplely */ - switch (event) { - case ESP_BLUFI_EVENT_INIT_FINISH: - LOG_ERROR("blufi init finish\n"); - break; - case ESP_BLUFI_EVENT_RECV_DATA: { - LOG_DEBUG("blufi recv data\n"); - esp_blufi_cb_param_t *blufi_param = (esp_blufi_cb_param_t *)param; - blufi_data_recv(blufi_param->recv_data.data, blufi_param->recv_data.data_len); - break; - } - default: - break; - } -} - -static esp_err_t blufi_startup_in_blufi_task(void *arg) -{ - esp_blufi_register_callback(blufi_callback); - esp_blufi_profile_init(); - - return ESP_OK; -} - - -static void blufi_startup(void) -{ - blufi_transfer_context(blufi_startup_in_blufi_task, NULL); -} - -esp_err_t blufi_enable(void *arg) -{ - esp_err_t err; - - err = esp_bluedroid_enable(); - if (err) { - LOG_ERROR("%s failed\n", __func__); - return err; - } - blufi_startup(); - vTaskDelay(1000 / portTICK_PERIOD_MS); - - return err; -} - -esp_err_t blufi_disable(void *arg) -{ - esp_err_t err; - - err = esp_bluedroid_disable(); - - if (arg) { - ((void (*)(void))arg)(); - } - - return err; -} diff --git a/examples/12_blufi/components/blufi/blufi_task.c b/examples/12_blufi/components/blufi/blufi_task.c deleted file mode 100644 index 75547c6432..0000000000 --- a/examples/12_blufi/components/blufi/blufi_task.c +++ /dev/null @@ -1,110 +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. - -#include -#include -#include -#include - -#include "gki.h" -#include "bt_defs.h" -#include "bt_trace.h" -#include "bt_types.h" -#include "allocator.h" - -#include "bta_api.h" -#include "bta_gatt_api.h" - -#include "controller.h" - -#include "hash_map.h" -#include "hash_functions.h" -#include "alarm.h" -#include "thread.h" - -#include "blufi.h" -#include "blufi_adv.h" - -xQueueHandle xBlufiTaskQueue; -xTaskHandle xBlufiTaskHandle; - -extern void ble_server_test(void); - -static void blufi_task(void *arg) -{ - BtTaskEvt_t e; - - for (;;) { - if (pdTRUE == xQueueReceive(xBlufiTaskQueue, &e, (portTickType)portMAX_DELAY)) { - switch (e.sig) { - case BLUFI_SIG_SWITCH_CONTEXT: - if (e.cb) { - ((BtTaskCb_t)e.cb)(e.arg); - } - break; - default: - break; - } - } - } -} - -static esp_err_t blufi_task_post(uint32_t sig, void *par, void *cb, void *arg) -{ - BtTaskEvt_t evt; - - evt.sig = sig; - evt.par = par; - evt.cb = cb; - evt.arg = arg; - - if (xQueueSend(xBlufiTaskQueue, &evt, 10 / portTICK_PERIOD_MS) != pdTRUE) { - LOG_ERROR("Blufi Post failed\n"); - return ESP_FAIL; - } - - return ESP_OK; -} - -esp_err_t blufi_transfer_context(blufi_task_cb_t cb, void *arg) -{ - LOG_DEBUG("%s cb %08x, arg %u\n", __func__, (uint32_t)cb, (uint32_t)arg); - - return blufi_task_post(BLUFI_SIG_SWITCH_CONTEXT, 0, cb, arg); -} - -static void blufi_task_deinit(void) -{ - vTaskDelete(xBlufiTaskHandle); - vQueueDelete(xBlufiTaskQueue); -} - - -static void blufi_task_init(void) -{ - xBlufiTaskQueue = xQueueCreate(5, sizeof(BtTaskEvt_t)); - xTaskCreate(blufi_task, "BlUFI", 4096, NULL, configMAX_PRIORITIES - 3, xBlufiTaskHandle); -} - -void blufi_init(void) -{ - blufi_task_init(); - blufi_transfer_context(blufi_enable, NULL); -} - -void blufi_deinit(void) -{ - blufi_transfer_context(blufi_disable, blufi_task_deinit); -} - diff --git a/examples/12_blufi/components/blufi/component.mk b/examples/12_blufi/components/blufi/component.mk deleted file mode 100644 index 297e63f919..0000000000 --- a/examples/12_blufi/components/blufi/component.mk +++ /dev/null @@ -1,13 +0,0 @@ -# -# Main Makefile. This is basically the same as a component makefile. -# -# This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default, -# this will take the sources in the src/ directory, compile them and link them into -# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, -# please read the ESP-IDF documents if you need to do this. -# - -COMPONENT_SRCDIRS := . - -CFLAGS += -Wno-error=unused-label -Wno-error=return-type -Wno-error=missing-braces -Wno-error=pointer-sign -Wno-error=parentheses -I./include - diff --git a/examples/12_blufi/components/blufi/include/blufi.h b/examples/12_blufi/components/blufi/include/blufi.h deleted file mode 100644 index c79695edcd..0000000000 --- a/examples/12_blufi/components/blufi/include/blufi.h +++ /dev/null @@ -1,39 +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. - -#ifndef __BT_APP_COMMON_H__ -#define __BT_APP_COMMON_H__ - -#include -#include "osi.h" -#include "bt_common_types.h" -#include "esp_err.h" - -enum BLUFI_SIG { - BLUFI_SIG_SWITCH_CONTEXT = 0, - BLUFI_SIG_ENABLE, - BLUFI_SIG_DISABLE, -}; - -typedef esp_err_t (*blufi_task_cb_t)(void *arg); - -void blufi_init(void); -void blufi_deinit(void); - -esp_err_t blufi_enable(void *arg); -esp_err_t blufi_disable(void *arg); - -esp_err_t blufi_transfer_context(blufi_task_cb_t cb, void *arg); - -#endif /* __BT_APP_COMMON_H__ */ diff --git a/examples/12_blufi/main/blufi_demo.h b/examples/12_blufi/main/blufi_demo.h new file mode 100644 index 0000000000..c5bf55c7d5 --- /dev/null +++ b/examples/12_blufi/main/blufi_demo.h @@ -0,0 +1,17 @@ +#ifndef __BLUFI_DEMO_H__ +#define __BLUFI_DEMO_H__ + + +#define BLUFI_DEMO_TAG "BLUFI_DEMO" +#define BLUFI_INFO(fmt, ...) ESP_LOGI(BLUFI_DEMO_TAG, fmt, ##__VA_ARGS__) +#define BLUFI_ERROR(fmt, ...) ESP_LOGE(BLUFI_DEMO_TAG, fmt, ##__VA_ARGS__) + +void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free); +int blufi_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len); +int blufi_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len); +uint16_t blufi_crc_checksum(uint8_t iv8, uint8_t *data, int len); + +int blufi_security_init(void); +void blufi_security_deinit(void); + +#endif /* __BLUFI_DEMO_H__ */ diff --git a/examples/12_blufi/main/blufi_main.c b/examples/12_blufi/main/blufi_main.c new file mode 100644 index 0000000000..3f5cd3dcc6 --- /dev/null +++ b/examples/12_blufi/main/blufi_main.c @@ -0,0 +1,336 @@ +// 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 +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "bt.h" + +#include "esp_blufi_api.h" +#include "esp_bt_defs.h" +#include "esp_gap_ble_api.h" +#include "esp_bt_main.h" +#include "blufi_demo.h" + +static void blufi_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param); + +#define BLUFI_DEVICE_NAME "BLUFI_DEVICE" +static uint8_t blufi_service_uuid128[32] = { + /* LSB <--------------------------------------------------------------------------------> MSB */ + //first uuid, 16bit, [12],[13] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, +}; + +//static uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] = {0x12, 0x23, 0x45, 0x56}; +static esp_ble_adv_data_t blufi_adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x100, + .max_interval = 0x100, + .appearance = 0x00, + .manufacturer_len = 0, + .p_manufacturer_data = NULL, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = 16, + .p_service_uuid = blufi_service_uuid128, + .flag = 0x6, +}; + +static esp_ble_adv_params_t blufi_adv_params = { + .adv_int_min = 0x100, + .adv_int_max = 0x100, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + //.peer_addr = + //.peer_addr_type = + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +#define WIFI_LIST_NUM 10 + +static wifi_config_t sta_config; +static wifi_config_t ap_config; + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +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; + +/* store the station info for send back to phone */ +static bool gl_sta_connected = false; +static uint8_t gl_sta_bssid[6]; +static uint8_t gl_sta_ssid[32]; +static int gl_sta_ssid_len; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + wifi_mode_t mode; + + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: { + esp_blufi_extra_info_t info; + + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + esp_wifi_get_mode(&mode); + + memset(&info, 0, sizeof(esp_blufi_extra_info_t)); + memcpy(info.sta_bssid, gl_sta_bssid, 6); + info.sta_bssid_set = true; + info.sta_ssid = gl_sta_ssid; + info.sta_ssid_len = gl_sta_ssid_len; + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info); + break; + } + case SYSTEM_EVENT_STA_CONNECTED: + gl_sta_connected = true; + memcpy(gl_sta_bssid, event->event_info.connected.bssid, 6); + memcpy(gl_sta_ssid, event->event_info.connected.ssid, event->event_info.connected.ssid_len); + gl_sta_ssid_len = event->event_info.connected.ssid_len; + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + gl_sta_connected = false; + memset(gl_sta_ssid, 0, 32); + memset(gl_sta_bssid, 0, 6); + gl_sta_ssid_len = 0; + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_START: + esp_wifi_get_mode(&mode); + + /* TODO: get config or information of softap, then set to report extra_info */ + if (gl_sta_connected) { + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, NULL); + } else { + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL); + } + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +static esp_blufi_callbacks_t blufi_callbacks = { + .event_cb = blufi_event_callback, + .negotiate_data_handler = blufi_dh_negotiate_data_handler, + .encrypt_func = blufi_aes_encrypt, + .decrypt_func = blufi_aes_decrypt, + .checksum_func = blufi_crc_checksum, +}; + +static void blufi_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param) +{ + /* actually, should post to blufi_task handle the procedure, + * now, as a demo, we do simplely */ + switch (event) { + case ESP_BLUFI_EVENT_INIT_FINISH: + BLUFI_INFO("BLUFI init finish\n"); + + esp_ble_gap_set_device_name(BLUFI_DEVICE_NAME); + esp_ble_gap_config_adv_data(&blufi_adv_data); + break; + case ESP_BLUFI_EVENT_DEINIT_FINISH: + BLUFI_INFO("BLUFI init finish\n"); + break; + case ESP_BLUFI_EVENT_BLE_CONNECT: + BLUFI_INFO("BLUFI ble connect\n"); + esp_ble_gap_stop_advertising(); + blufi_security_deinit(); + blufi_security_init(); + break; + case ESP_BLUFI_EVENT_BLE_DISCONNECT: + BLUFI_INFO("BLUFI ble disconnect\n"); + esp_ble_gap_start_advertising(&blufi_adv_params); + break; + case ESP_BLUFI_EVENT_SET_WIFI_OPMODE: + BLUFI_INFO("BLUFI Set WIFI opmode %d\n", param->wifi_mode.op_mode); + ESP_ERROR_CHECK( esp_wifi_set_mode(param->wifi_mode.op_mode) ); + break; + case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP: + BLUFI_INFO("BLUFI requset wifi connect to AP\n"); + esp_wifi_connect(); + break; + case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP: + BLUFI_INFO("BLUFI requset wifi disconnect from AP\n"); + esp_wifi_disconnect(); + break; + case ESP_BLUFI_EVENT_GET_WIFI_STATUS: { + wifi_mode_t mode; + esp_blufi_extra_info_t info; + + esp_wifi_get_mode(&mode); + + if (gl_sta_connected ) { + memset(&info, 0, sizeof(esp_blufi_extra_info_t)); + memcpy(info.sta_bssid, gl_sta_bssid, 6); + info.sta_bssid_set = true; + info.sta_ssid = gl_sta_ssid; + info.sta_ssid_len = gl_sta_ssid_len; + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info); + } else { + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL); + } + BLUFI_INFO("BLUFI get wifi status from AP\n"); + + break; + } + case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA: + /* TODO */ + break; + case ESP_BLUFI_EVENT_RECV_STA_BSSID: + memcpy(sta_config.sta.bssid, param->sta_bssid.bssid, 6); + sta_config.sta.bssid_set = 1; + esp_wifi_set_config(WIFI_IF_STA, &sta_config); + BLUFI_INFO("Recv STA BSSID %s\n", sta_config.sta.ssid); + break; + case ESP_BLUFI_EVENT_RECV_STA_SSID: + strncpy((char *)sta_config.sta.ssid, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len); + sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0'; + esp_wifi_set_config(WIFI_IF_STA, &sta_config); + BLUFI_INFO("Recv STA SSID %s\n", sta_config.sta.ssid); + break; + case ESP_BLUFI_EVENT_RECV_STA_PASSWD: + strncpy((char *)sta_config.sta.password, (char *)param->sta_passwd.passwd, param->sta_passwd.passwd_len); + sta_config.sta.password[param->sta_passwd.passwd_len] = '\0'; + esp_wifi_set_config(WIFI_IF_STA, &sta_config); + BLUFI_INFO("Recv STA PASSWORD %s\n", sta_config.sta.password); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID: + strncpy((char *)ap_config.ap.ssid, (char *)param->softap_ssid.ssid, param->softap_ssid.ssid_len); + ap_config.ap.ssid_len = param->softap_ssid.ssid_len; + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP SSID %s, ssid len %d\n", ap_config.ap.ssid, ap_config.ap.ssid_len); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD: + strncpy((char *)ap_config.ap.password, (char *)param->softap_passwd.passwd, param->softap_passwd.passwd_len); + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP PASSWORD %s\n", ap_config.ap.password); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM: + if (param->softap_max_conn_num.max_conn_num > 4) { + return; + } + ap_config.ap.max_connection = param->softap_max_conn_num.max_conn_num; + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP MAX CONN NUM %d\n", ap_config.ap.max_connection); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE: + if (param->softap_auth_mode.auth_mode >= WIFI_AUTH_MAX) { + return; + } + ap_config.ap.authmode = param->softap_auth_mode.auth_mode; + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP AUTH MODE %d\n", ap_config.ap.authmode); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL: + if (param->softap_channel.channel > 13) { + return; + } + ap_config.ap.channel = param->softap_channel.channel; + 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_RECV_USERNAME: + /* Not handle currently */ + break; + case ESP_BLUFI_EVENT_RECV_CA_CERT: + /* Not handle currently */ + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_CERT: + /* Not handle currently */ + break; + case ESP_BLUFI_EVENT_RECV_SERVER_CERT: + /* Not handle currently */ + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY: + /* Not handle currently */ + break;; + case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY: + /* Not handle currently */ + break; + default: + break; + } +} + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + esp_ble_gap_start_advertising(&blufi_adv_params); + break; + default: + break; + } +} + +void app_main() +{ + esp_err_t ret; + + nvs_flash_init(); + initialise_wifi(); + + esp_bt_controller_init(); + + ret = esp_bluedroid_init(); + if (ret) { + BLUFI_ERROR("%s init bluedroid failed\n", __func__); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + BLUFI_ERROR("%s init bluedroid failed\n", __func__); + return; + } + + blufi_security_init(); + esp_ble_gap_register_callback(gap_event_handler); + + esp_blufi_register_callbacks(&blufi_callbacks); + esp_blufi_profile_init(); +} diff --git a/examples/12_blufi/main/blufi_security.c b/examples/12_blufi/main/blufi_security.c new file mode 100644 index 0000000000..94755eaaee --- /dev/null +++ b/examples/12_blufi/main/blufi_security.c @@ -0,0 +1,205 @@ +// 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 +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "bt.h" + +#include "esp_blufi_api.h" +#include "esp_bt_defs.h" +#include "esp_gap_ble_api.h" +#include "esp_bt_main.h" +#include "blufi_demo.h" + +#include "mbedtls/aes.h" +#include "mbedtls/dhm.h" +#include "mbedtls/md5.h" +#include "rom/crc.h" + +/* + The SEC_TYPE_xxx is for self-defined packet data type in the procedure of "BLUFI negotiate key" + If user use other negotiation procedure to exchange(or generate) key, should redefine the type by yourself. + */ +#define SEC_TYPE_DH_PARAM_LEN 0x00 +#define SEC_TYPE_DH_PARAM_DATA 0x01 +#define SEC_TYPE_DH_P 0x02 +#define SEC_TYPE_DH_G 0x03 +#define SEC_TYPE_DH_PUBLIC 0x04 + + +struct blufi_security { +#define DH_SELF_PUB_KEY_LEN 128 +#define DH_SELF_PUB_KEY_BIT_LEN (DH_SELF_PUB_KEY_LEN * 8) + uint8_t self_public_key[DH_SELF_PUB_KEY_LEN]; +#define SHARE_KEY_LEN 128 +#define SHARE_KEY_BIT_LEN (SHARE_KEY_LEN * 8) + uint8_t share_key[SHARE_KEY_LEN]; + size_t share_len; +#define PSK_LEN 16 + uint8_t psk[PSK_LEN]; + uint8_t *dh_param; + int dh_param_len; + uint8_t iv[16]; + mbedtls_dhm_context dhm; + mbedtls_aes_context aes; +}; +static struct blufi_security *blufi_sec; + +static int myrand( void *rng_state, unsigned char *output, size_t len ) +{ + size_t i; + + for( i = 0; i < len; ++i ) + output[i] = esp_random(); + + return( 0 ); +} + +void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free) +{ + int ret; + uint8_t type = data[0]; + + if (blufi_sec == NULL) { + BLUFI_ERROR("BLUFI Security is not initialized"); + return; + } + + switch (type) { + case SEC_TYPE_DH_PARAM_LEN: + blufi_sec->dh_param_len = ((data[1]<<8)|data[2]); + if (blufi_sec->dh_param) { + free(blufi_sec->dh_param); + } + blufi_sec->dh_param = (uint8_t *)malloc(blufi_sec->dh_param_len); + if (blufi_sec->dh_param == NULL) { + return; + } + break; + case SEC_TYPE_DH_PARAM_DATA: + + memcpy(blufi_sec->dh_param, &data[1], blufi_sec->dh_param_len); + + ret = mbedtls_dhm_read_params(&blufi_sec->dhm, &blufi_sec->dh_param, &blufi_sec->dh_param[blufi_sec->dh_param_len]); + if (ret) { + BLUFI_ERROR("%s read param failed %d\n", __func__, ret); + return; + } + + 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); + return; + } + + mbedtls_dhm_calc_secret( &blufi_sec->dhm, + blufi_sec->share_key, + SHARE_KEY_BIT_LEN, + &blufi_sec->share_len, + NULL, NULL); + + mbedtls_md5(blufi_sec->share_key, blufi_sec->share_len, blufi_sec->psk); + + mbedtls_aes_setkey_enc(&blufi_sec->aes, blufi_sec->psk, 128); + mbedtls_aes_setkey_dec(&blufi_sec->aes, blufi_sec->psk, 128); + + /* alloc output data */ + *output_data = &blufi_sec->self_public_key[0]; + *output_len = blufi_sec->dhm.len; + *need_free = false; + break; + case SEC_TYPE_DH_P: + break; + case SEC_TYPE_DH_G: + break; + case SEC_TYPE_DH_PUBLIC: + break; + } +} + +int blufi_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len) +{ + int ret; + size_t iv_offset = 0; + uint8_t iv0[16]; + + memcpy(iv0, blufi_sec->iv, sizeof(blufi_sec->iv)); + iv0[0] = iv8; /* set iv8 as the iv0[0] */ + + ret = mbedtls_aes_crypt_cfb128(&blufi_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0, crypt_data, crypt_data); + if (ret) { + return -1; + } + + return crypt_len; +} + +int blufi_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len) +{ + int ret; + size_t iv_offset = 0; + uint8_t iv0[16]; + + memcpy(iv0, blufi_sec->iv, sizeof(blufi_sec->iv)); + iv0[0] = iv8; /* set iv8 as the iv0[0] */ + + ret = mbedtls_aes_crypt_cfb128(&blufi_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0, crypt_data, crypt_data); + if (ret) { + return -1; + } + + return crypt_len; +} + +uint16_t blufi_crc_checksum(uint8_t iv8, uint8_t *data, int len) +{ + /* This iv8 ignore, not used */ + return crc16_be(0, data, len); +} + +esp_err_t blufi_security_init(void) +{ + blufi_sec = (struct blufi_security *)malloc(sizeof(struct blufi_security)); + if (blufi_sec == NULL) { + return ESP_FAIL; + } + + memset(&blufi_sec, 0x0, sizeof(struct blufi_security)); + + mbedtls_dhm_init(&blufi_sec->dhm); + mbedtls_aes_init(&blufi_sec->aes); + + memset(blufi_sec->iv, 0x0, 16); + return 0; +} + +void blufi_security_deinit(void) +{ + mbedtls_dhm_free(&blufi_sec->dhm); + mbedtls_aes_free(&blufi_sec->aes); + + memset(&blufi_sec, 0x0, sizeof(struct blufi_security)); + + free(blufi_sec); + blufi_sec = NULL; +} diff --git a/examples/12_blufi/main/demo_main.c b/examples/12_blufi/main/demo_main.c deleted file mode 100644 index abb47aedb0..0000000000 --- a/examples/12_blufi/main/demo_main.c +++ /dev/null @@ -1,144 +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. - -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/event_groups.h" -#include "esp_system.h" -#include "esp_wifi.h" -#include "esp_event_loop.h" -#include "esp_log.h" -#include "nvs_flash.h" -#include "bt.h" -#include "bta_api.h" - -#include "esp_blufi_api.h" -#include "esp_bt_defs.h" -#include "esp_bt_main.h" -#include "blufi.h" - -#define WIFI_LIST_NUM 10 - -/* FreeRTOS event group to signal when we are connected & ready to make a request */ -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; - - - -static wifi_config_t sta_config; - -static char tmp_ssid[33]; -static char tmp_passwd[65]; -static bool confirm = false; - -void wifi_set_blue_config(char *ssid, char *passwd) -{ - memset(tmp_ssid, 0, sizeof(tmp_ssid)); - memset(tmp_passwd, 0, sizeof(tmp_passwd)); - strlcpy(tmp_ssid, ssid, sizeof(tmp_ssid)); - strlcpy(tmp_passwd, passwd, sizeof(tmp_passwd)); - confirm = true; - LOG_DEBUG("confirm true\n"); -} - -static esp_err_t event_handler(void *ctx, system_event_t *event) -{ - switch (event->event_id) { - case SYSTEM_EVENT_STA_START: - esp_wifi_connect(); - break; - case SYSTEM_EVENT_STA_GOT_IP: - xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); - esp_blufi_send_config_state(ESP_BLUFI_CONFIG_OK); - esp_bluedroid_disable(); //close bluetooth function - //esp_bluedroid_deinit(); //free bluetooth resource - 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); - break; - default: - break; - } - return ESP_OK; -} - -static void initialise_wifi(void) -{ - tcpip_adapter_init(); - wifi_event_group = xEventGroupCreate(); - ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); - ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); - ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); - ESP_ERROR_CHECK( esp_wifi_start() ); -} - - -void wifiTestTask(void *pvParameters) -{ - esp_err_t ret; - - while (1) { - vTaskDelay(1000 / portTICK_PERIOD_MS); - if (confirm) { - confirm = false; - - memcpy(sta_config.sta.ssid, tmp_ssid, sizeof(sta_config.sta.ssid)); - memcpy(sta_config.sta.password, tmp_passwd, sizeof(sta_config.sta.password)); - sta_config.sta.bssid_set = 0; - - ret = esp_wifi_disconnect(); - LOG_INFO("esp_wifi config\n"); - esp_wifi_set_config(WIFI_IF_STA, &sta_config); - LOG_INFO("esp_wifi connect\n"); - ret = esp_wifi_connect(); - if (ret != ESP_OK) { - LOG_ERROR("esp_wifi connect failed\n"); - esp_blufi_send_config_state(ESP_BLUFI_CONFIG_FAILED); - } - } - } -} - -void app_main() -{ - esp_err_t ret; - - nvs_flash_init(); - initialise_wifi(); - - //vTaskDelay(3000 / portTICK_PERIOD_MS); - - esp_bt_controller_init(); - xTaskCreatePinnedToCore(&wifiTestTask, "wifiTestTask", 2048, NULL, 20, NULL, 0); - - LOG_ERROR("%s init bluetooth\n", __func__); - ret = esp_bluedroid_init(); - if (ret) { - LOG_ERROR("%s init bluetooth failed\n", __func__); - return; - } - blufi_init(); -} From 9017c408ac5ddfb87acd4f4015def51b6bc07a81 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Thu, 5 Jan 2017 20:41:15 +0800 Subject: [PATCH 067/167] component/bt : use new lib, optimize BT power --- components/bt/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/lib b/components/bt/lib index 91657e0c40..566acfd8c6 160000 --- a/components/bt/lib +++ b/components/bt/lib @@ -1 +1 @@ -Subproject commit 91657e0c4025f5a694b0a89f449c347b0f2fdf79 +Subproject commit 566acfd8c61a4ba0fb6b9026c89488b01af0fff0 From 7853893731021f6d8f841bf287a1d7ae2175879c Mon Sep 17 00:00:00 2001 From: XiaXiaotian Date: Thu, 5 Jan 2017 17:57:41 +0800 Subject: [PATCH 068/167] wifi: add wifi rx buffer number config in menuconfig --- components/esp32/Kconfig | 9 +++++++++ components/esp32/include/esp_wifi.h | 2 ++ components/esp32/lib | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 6ee5313b9b..90cd734bf1 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -508,5 +508,14 @@ config ESP32_PHY_MAX_TX_POWER help Set maximum transmit power. Actual transmit power for high data rates may be lower than this setting. + +config ESP32_WIFI_RX_BUFFER_NUM + int "Max number of WiFi RX buffers" + range 2 25 + default 25 + help + Set the number of WiFi rx buffers. Each buffer takes approximately 1.6KB of RAM. + Larger number for higher throughput but more memory. Smaller number for lower + throughput but less memory. endmenu diff --git a/components/esp32/include/esp_wifi.h b/components/esp32/include/esp_wifi.h index ac49764f1f..c835c071c7 100755 --- a/components/esp32/include/esp_wifi.h +++ b/components/esp32/include/esp_wifi.h @@ -94,11 +94,13 @@ extern "C" { */ typedef struct { system_event_handler_t event_handler; /**< WiFi event handler */ + uint32_t rx_buf_num; /**< WiFi RX buffer number */ } wifi_init_config_t; #define WIFI_INIT_CONFIG_DEFAULT() { \ .event_handler = &esp_event_send, \ + .rx_buf_num = CONFIG_ESP32_WIFI_RX_BUFFER_NUM, \ }; /** diff --git a/components/esp32/lib b/components/esp32/lib index edad974840..21e433b827 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit edad9748406d06bfd2dfba6cf1a0735c3982460f +Subproject commit 21e433b8277adc1d65894ec0a65c60f78dc84f7c From 99d698480098858883272439b09096586c8b611b Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Fri, 6 Jan 2017 12:24:37 +0800 Subject: [PATCH 069/167] component/bt : blufi fix security init bug --- examples/12_blufi/main/blufi_security.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/12_blufi/main/blufi_security.c b/examples/12_blufi/main/blufi_security.c index 94755eaaee..59d889a52d 100644 --- a/examples/12_blufi/main/blufi_security.c +++ b/examples/12_blufi/main/blufi_security.c @@ -184,7 +184,7 @@ esp_err_t blufi_security_init(void) return ESP_FAIL; } - memset(&blufi_sec, 0x0, sizeof(struct blufi_security)); + memset(blufi_sec, 0x0, sizeof(struct blufi_security)); mbedtls_dhm_init(&blufi_sec->dhm); mbedtls_aes_init(&blufi_sec->aes); @@ -198,7 +198,7 @@ void blufi_security_deinit(void) mbedtls_dhm_free(&blufi_sec->dhm); mbedtls_aes_free(&blufi_sec->aes); - memset(&blufi_sec, 0x0, sizeof(struct blufi_security)); + memset(blufi_sec, 0x0, sizeof(struct blufi_security)); free(blufi_sec); blufi_sec = NULL; From 1b38494df4887306653d0ccf444c1192c983f2fd Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Fri, 6 Jan 2017 12:23:11 +0800 Subject: [PATCH 070/167] bootloader: modify bootloader dram start address to 0x3fff0000 Modify bootloader dram_seg from address 0x3ffc0000 to 0x3fff0000, len from 0x20000 to 0x10000. Please be notified that this is just a workaround for fixing app data overwrite bootloader data issue! --- components/bootloader/src/main/esp32.bootloader.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bootloader/src/main/esp32.bootloader.ld b/components/bootloader/src/main/esp32.bootloader.ld index 6a77eb6ade..0c57bdf48d 100644 --- a/components/bootloader/src/main/esp32.bootloader.ld +++ b/components/bootloader/src/main/esp32.bootloader.ld @@ -17,7 +17,7 @@ MEMORY dport0_seg (RW) : org = 0x3FF00000, len = 0x10 /* IO */ iram_seg (RWX) : org = 0x40080000, len = 0x400 /* 1k of IRAM used by bootloader functions which need to flush/enable APP CPU cache */ iram_pool_1_seg (RWX) : org = 0x40078000, len = 0x8000 /* IRAM POOL1, used for APP CPU cache. We can abuse it in bootloader because APP CPU is still held in reset, until we enable APP CPU cache */ - dram_seg (RW) : org = 0x3FFC0000, len = 0x20000 /* Shared RAM, minus rom bss/data/stack.*/ + dram_seg (RW) : org = 0x3FFF0000, len = 0x10000 /* Shared RAM, minus rom bss/data/stack.*/ } /* Default entry point: */ From 61c6ce86d200428985f0539c0255899d58d46330 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 6 Jan 2017 13:03:07 +0800 Subject: [PATCH 071/167] esp32: put .data before .bss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change reduces chances that a large .bss segment will push .data all the way into 0x3ffe1320 — 0x3ffe5320 range where the bootloader stack is, creating a problem when bootloader will be loading application into memory. With this change, .data would need to be at least 200k big to cause problems. --- components/esp32/ld/esp32.common.ld | 42 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/components/esp32/ld/esp32.common.ld b/components/esp32/ld/esp32.common.ld index 09b7634445..c199a41d3d 100644 --- a/components/esp32/ld/esp32.common.ld +++ b/components/esp32/ld/esp32.common.ld @@ -85,6 +85,27 @@ SECTIONS _iram_text_end = ABSOLUTE(.); } > iram0_0_seg + .dram0.data : + { + _data_start = ABSOLUTE(.); + KEEP(*(.data)) + KEEP(*(.data.*)) + KEEP(*(.gnu.linkonce.d.*)) + KEEP(*(.data1)) + KEEP(*(.sdata)) + KEEP(*(.sdata.*)) + KEEP(*(.gnu.linkonce.s.*)) + KEEP(*(.sdata2)) + KEEP(*(.sdata2.*)) + KEEP(*(.gnu.linkonce.s2.*)) + KEEP(*(.jcr)) + *(.dram1 .dram1.*) + *libesp32.a:panic.o(.rodata .rodata.*) + _data_end = ABSOLUTE(.); + . = ALIGN(4); + _heap_start = ABSOLUTE(.); + } >dram0_0_seg + /* Shared RAM */ .dram0.bss (NOLOAD) : { @@ -108,27 +129,6 @@ SECTIONS _bss_end = ABSOLUTE(.); } >dram0_0_seg - .dram0.data : - { - _data_start = ABSOLUTE(.); - *(.data) - *(.data.*) - *(.gnu.linkonce.d.*) - *(.data1) - *(.sdata) - *(.sdata.*) - *(.gnu.linkonce.s.*) - *(.sdata2) - *(.sdata2.*) - *(.gnu.linkonce.s2.*) - *(.jcr) - *(.dram1 .dram1.*) - *libesp32.a:panic.o(.rodata .rodata.*) - _data_end = ABSOLUTE(.); - . = ALIGN(4); - _heap_start = ABSOLUTE(.); - } >dram0_0_seg - .flash.rodata : { _rodata_start = ABSOLUTE(.); From 0b264f4f7b38a10d30ab864cbd5fe1a9b9b909fa Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 6 Jan 2017 13:47:53 +0800 Subject: [PATCH 072/167] bootloader: update ld script comment --- components/bootloader/src/main/esp32.bootloader.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bootloader/src/main/esp32.bootloader.ld b/components/bootloader/src/main/esp32.bootloader.ld index 0c57bdf48d..500478814c 100644 --- a/components/bootloader/src/main/esp32.bootloader.ld +++ b/components/bootloader/src/main/esp32.bootloader.ld @@ -17,7 +17,7 @@ MEMORY dport0_seg (RW) : org = 0x3FF00000, len = 0x10 /* IO */ iram_seg (RWX) : org = 0x40080000, len = 0x400 /* 1k of IRAM used by bootloader functions which need to flush/enable APP CPU cache */ iram_pool_1_seg (RWX) : org = 0x40078000, len = 0x8000 /* IRAM POOL1, used for APP CPU cache. We can abuse it in bootloader because APP CPU is still held in reset, until we enable APP CPU cache */ - dram_seg (RW) : org = 0x3FFF0000, len = 0x10000 /* Shared RAM, minus rom bss/data/stack.*/ + dram_seg (RW) : org = 0x3FFF0000, len = 0x10000 /* 64k at the end of DRAM, after ROM bootloader stack */ } /* Default entry point: */ From 489701eb2d424790f85fee1d84b2665769274405 Mon Sep 17 00:00:00 2001 From: shangke Date: Fri, 6 Jan 2017 13:49:42 +0800 Subject: [PATCH 073/167] ethernet : fix sometimes ethernet init fail bug --- components/ethernet/emac_main.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/ethernet/emac_main.c b/components/ethernet/emac_main.c index 20d428cf7b..06e641453b 100644 --- a/components/ethernet/emac_main.c +++ b/components/ethernet/emac_main.c @@ -628,6 +628,7 @@ static void emac_start(void *param) cmd->err = EMAC_CMD_OK; emac_enable_clk(true); + emac_reset(); emac_macaddr_init(); emac_check_mac_addr(); @@ -839,7 +840,9 @@ esp_err_t IRAM_ATTR emac_post(emac_sig_t sig, emac_par_t par) } } } else { + portENTER_CRITICAL(&g_emac_mux); emac_sig_cnt[sig]++; + portEXIT_CRITICAL(&g_emac_mux); emac_event_t evt; evt.sig = sig; evt.par = par; @@ -898,10 +901,8 @@ esp_err_t esp_eth_init(eth_config_t *config) emac_xqueue = xQueueCreate(EMAC_EVT_QNUM, sizeof(emac_event_t)); xTaskCreate(emac_task, "emacT", 2048, NULL, EMAC_TASK_PRIORITY, &emac_task_hdl); - esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, 0, emac_process_intr, NULL, NULL); - - emac_reset(); emac_enable_clk(false); + esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, 0, emac_process_intr, NULL, NULL); emac_config.emac_status = EMAC_RUNTIME_INIT; From 6e1150473e0267a6378708d2cc0a6b87ddfd9d45 Mon Sep 17 00:00:00 2001 From: shangke Date: Fri, 6 Jan 2017 14:16:34 +0800 Subject: [PATCH 074/167] ethernet: update docs --- docs/api/esp_eth.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/api/esp_eth.rst b/docs/api/esp_eth.rst index d6d98e43d8..1fcb1c2351 100644 --- a/docs/api/esp_eth.rst +++ b/docs/api/esp_eth.rst @@ -21,14 +21,20 @@ Macros Type Definitions ^^^^^^^^^^^^^^^^ -.. doxygentypedef:: eth_phy_fun -.. doxygentypedef:: eth_tcpip_input_fun +.. doxygentypedef:: eth_phy_check_link_func +.. doxygentypedef:: eth_phy_check_init_func +.. doxygentypedef:: eth_phy_get_speed_mode_func +.. doxygentypedef:: eth_phy_get_duplex_mode_func +.. doxygentypedef:: eth_phy_func +.. doxygentypedef:: eth_tcpip_input_func .. doxygentypedef:: eth_gpio_config_func Enumerations ^^^^^^^^^^^^ .. doxygenenum:: eth_mode_t +.. doxygenenum:: eth_speed_mode_t +.. doxygenenum:: eth_duplex_mode_t .. doxygenenum:: eth_phy_base_t Structures @@ -48,4 +54,4 @@ Functions .. doxygenfunction:: esp_eth_get_mac .. doxygenfunction:: esp_eth_smi_write .. doxygenfunction:: esp_eth_smi_read - +.. doxygenfunction:: esp_eth_free_rx_buf From 23455de4c2f6f8308f60d11badf4a95ed644bd57 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 6 Jan 2017 14:20:32 +0800 Subject: [PATCH 075/167] Add SPI Master driver, example, test and docs --- .../driver/include/driver/periph_ctrl.h | 3 + components/driver/include/driver/spi_master.h | 235 ++++++ components/driver/periph_ctrl.c | 24 + components/driver/spi_master.c | 692 ++++++++++++++++++ components/driver/test/component.mk | 5 + components/driver/test/test_spi_master.c | 80 ++ components/esp32/include/soc/gpio_sig_map.h | 2 +- components/esp32/include/soc/spi_reg.h | 20 +- components/esp32/include/soc/spi_struct.h | 21 +- docs/api/spi_master.rst | 156 ++++ docs/index.rst | 3 +- examples/26_spi_master/Makefile | 9 + examples/26_spi_master/main/component.mk | 5 + examples/26_spi_master/main/spi_master.c | 275 +++++++ 14 files changed, 1505 insertions(+), 25 deletions(-) create mode 100644 components/driver/include/driver/spi_master.h create mode 100644 components/driver/spi_master.c create mode 100644 components/driver/test/component.mk create mode 100644 components/driver/test/test_spi_master.c create mode 100644 docs/api/spi_master.rst create mode 100644 examples/26_spi_master/Makefile create mode 100644 examples/26_spi_master/main/component.mk create mode 100644 examples/26_spi_master/main/spi_master.c diff --git a/components/driver/include/driver/periph_ctrl.h b/components/driver/include/driver/periph_ctrl.h index 8c404e5b13..0aab55088d 100644 --- a/components/driver/include/driver/periph_ctrl.h +++ b/components/driver/include/driver/periph_ctrl.h @@ -41,6 +41,9 @@ typedef enum { PERIPH_UHCI1_MODULE, PERIPH_RMT_MODULE, PERIPH_PCNT_MODULE, + PERIPH_SPI_MODULE, + PERIPH_HSPI_MODULE, + PERIPH_VSPI_MODULE, } periph_module_t; /** diff --git a/components/driver/include/driver/spi_master.h b/components/driver/include/driver/spi_master.h new file mode 100644 index 0000000000..4dd8738ad7 --- /dev/null +++ b/components/driver/include/driver/spi_master.h @@ -0,0 +1,235 @@ +// Copyright 2010-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_SPI_MASTER_H_ +#define _DRIVER_SPI_MASTER_H_ + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/** + * @brief Enum with the three SPI peripherals that are software-accessible in it + */ +typedef enum { + SPI_HOST=0, ///< SPI1, SPI + HSPI_HOST=1, ///< SPI2, HSPI + VSPI_HOST=2 ///< SPI3, VSPI +} spi_host_device_t; + + +/** + * @brief This is a configuration structure for a SPI bus. + * + * You can use this structure to specify the GPIO pins of the bus. Normally, the driver will use the + * GPIO matrix to route the signals. An exception is made when all signals either can be routed through + * the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds. + */ +typedef struct { + int spid_io_num; ///< GPIO pin for spi_d (=MOSI)signal, or -1 if not used. + int spiq_io_num; ///< GPIO pin for spi_q (=MISO) signal, or -1 if not used. + int spiclk_io_num; ///< GPIO pin for spi_clk signal, or -1 if not used. + int spiwp_io_num; ///< GPIO pin for spi_wp signal, or -1 if not used. + int spihd_io_num; ///< GPIO pin for spi_hd signal, or -1 if not used. +} spi_bus_config_t; + + +#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_TXBIT_LSBFIRST|SPI_RXBIT_LSBFIRST); ///< Transmit and receive LSB first +#define SPI_DEVICE_3WIRE (1<<2) ///< Use spiq 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 +#define SPI_DEVICE_CLK_AS_CS (1<<5) ///< Output clock on CS line if CS is active + + +typedef struct spi_transaction_t spi_transaction_t; +typedef void(*transaction_cb_t)(spi_transaction_t *trans); + +/** + * @brief This is a configuration for a SPI slave device that is connected to one of the SPI buses. + */ +typedef struct { + uint8_t command_bits; ///< Amount of bits in command phase (0-16) + uint8_t address_bits; ///< Amount of bits in address phase (0-64) + uint8_t dummy_bits; ///< Amount of dummy bits to insert between address and data phase + uint8_t mode; ///< SPI mode (0-3) + uint8_t duty_cycle_pos; ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128. + uint8_t cs_ena_pretrans; ///< Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). This only works on half-duplex transactions. + uint8_t cs_ena_posttrans; ///< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16) + int clock_speed_hz; ///< Clock speed, in Hz + int spics_io_num; ///< CS GPIO pin for this device, or -1 if not used + uint32_t flags; ///< Bitwise OR of SPI_DEVICE_* flags + int queue_size; ///< Transaction queue size + transaction_cb_t pre_cb; ///< Callback to be called before a transmission is started. This callback is called within interrupt context. + transaction_cb_t post_cb; ///< Callback to be called after a transmission has completed. This callback is called within interrupt context. +} spi_device_interface_config_t; + + +#define SPI_MODE_DIO (1<<0) ///< Transmit/receive data in 2-bit mode +#define SPI_MODE_QIO (1<<1) ///< Transmit/receive data in 4-bit mode +#define SPI_MODE_DIOQIO_ADDR (1<<2) ///< Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO +#define SPI_USE_RXDATA (1<<2) ///< Receive into rx_data member of spi_transaction_t instead into memory at rx_buffer. +#define SPI_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. + +/** + * This structure describes one SPI transaction + */ +struct spi_transaction_t { + uint32_t flags; ///< Bitwise OR of SPI_TRANS_* flags + uint16_t command; ///< Command data. Specific length was given when device was added to the bus. + uint64_t address; ///< Address. Specific length was given when device was added to the bus. + size_t length; ///< Total data length, in bits + size_t rxlength; ///< Total data length received, if different from length. (0 defaults this to the value of ``length``) + void *user; ///< User-defined variable. Can be used to store eg transaction ID. + union { + const void *tx_buffer; ///< Pointer to transmit buffer, or NULL for no MOSI phase + uint8_t tx_data[4]; ///< If SPI_USE_TXDATA is set, data set here is sent directly from this variable. + }; + union { + void *rx_buffer; ///< Pointer to receive buffer, or NULL for no MISO phase + uint8_t rx_data[4]; ///< If SPI_USE_RXDATA is set, data is received directly to this variable + }; +}; + + +typedef struct spi_device_t* spi_device_handle_t; ///< Handle for a device on a SPI bus + +/** + * @brief Initialize a SPI bus + * + * @warning For now, only supports HSPI and VSPI. + * + * @param host SPI peripheral that controls this bus + * @param bus_config Pointer to a spi_bus_config_t struct specifying how the host should be initialized + * @param dma_chan Either 1 or 2. A SPI bus used by this driver must have a DMA channel associated with + * it. The SPI hardware has two DMA channels to share. This parameter indicates which + * one to use. + * @return + * - ESP_ERR_INVALID_ARG if configuration is invalid + * - ESP_ERR_INVALID_STATE if host already is in use + * - ESP_ERR_NO_MEM if out of memory + * - ESP_OK on success + */ +esp_err_t spi_bus_initialize(spi_host_device_t host, spi_bus_config_t *bus_config, int dma_chan); + +/** + * @brief Free a SPI bus + * + * @warning In order for this to succeed, all devices have to be removed first. + * + * @param host SPI peripheral to free + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_INVALID_STATE if not all devices on the bus are freed + * - ESP_OK on success + */ +esp_err_t spi_bus_free(spi_host_device_t host); + +/** + * @brief Allocate a device on a SPI bus + * + * This initializes the internal structures for a device, plus allocates a CS pin on the indicated SPI master + * peripheral and routes it to the indicated GPIO. All SPI master devices have three CS pins and can thus control + * up to three devices. + * + * @param host SPI peripheral to allocate device on + * @param dev_config SPI interface protocol config for the device + * @param handle Pointer to variable to hold the device handle + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_NOT_FOUND if host doesn't have any free CS slots + * - ESP_ERR_NO_MEM if out of memory + * - ESP_OK on success + */ +esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config_t *dev_config, spi_device_handle_t *handle); + + +/** + * @brief Remove a device from the SPI bus + * + * @param handle Device handle to free + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_INVALID_STATE if device already is freed + * - ESP_OK on success + */ +esp_err_t spi_bus_remove_device(spi_device_handle_t handle); + + +/** + * @brief Queue a SPI transaction for execution + * + * @param handle Device handle obtained using spi_host_add_dev + * @param trans_desc Description of transaction to execute + * @param ticks_to_wait Ticks to wait until there's room in the queue; use portMAX_DELAY to + * never time out. + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + */ +esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait); + + +/** + * @brief Get the result of a SPI transaction queued earlier + * + * This routine will wait until a transaction to the given device (queued earlier with + * spi_device_queue_trans) has succesfully completed. It will then return the description of the + * completed transaction so software can inspect the result and e.g. free the memory or + * re-use the buffers. + * + * @param handle Device handle obtained using spi_host_add_dev + * @param trans_desc Pointer to variable able to contain a pointer to the description of the + * transaction that is executed + * @param ticks_to_wait Ticks to wait until there's a returned item; use portMAX_DELAY to never time + out. + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + */ +esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, spi_transaction_t **trans_desc, TickType_t ticks_to_wait); + + +/** + * @brief Do a SPI transaction + * + * Essentially does the same as spi_device_queue_trans followed by spi_device_get_trans_result. Do + * not use this when there is still a transaction queued that hasn't been finalized + * using spi_device_get_trans_result. + * + * @param handle Device handle obtained using spi_host_add_dev + * @param trans_desc Pointer to variable able to contain a pointer to the description of the + * transaction that is executed + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_OK on success + */ +esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc); + + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/components/driver/periph_ctrl.c b/components/driver/periph_ctrl.c index 7fc4091aa5..d90fa595f9 100644 --- a/components/driver/periph_ctrl.c +++ b/components/driver/periph_ctrl.c @@ -97,6 +97,18 @@ void periph_module_enable(periph_module_t periph) SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_PCNT_CLK_EN); CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_PCNT_RST); break; + case PERIPH_SPI_MODULE: + SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN_1); + CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST_1); + break; + case PERIPH_HSPI_MODULE: + SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN); + CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST); + break; + case PERIPH_VSPI_MODULE: + SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN_2); + CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST_2); + break; default: break; } @@ -179,6 +191,18 @@ void periph_module_disable(periph_module_t periph) CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_PCNT_CLK_EN); SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_PCNT_RST); break; + case PERIPH_SPI_MODULE: + CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN_1); + SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST_1); + break; + case PERIPH_HSPI_MODULE: + CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN); + SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST); + break; + case PERIPH_VSPI_MODULE: + CLEAR_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI_CLK_EN_2); + SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI_RST_2); + break; default: break; } diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c new file mode 100644 index 0000000000..fd4c5b2e29 --- /dev/null +++ b/components/driver/spi_master.c @@ -0,0 +1,692 @@ +// 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. + +/* +Architecture: + +We can initialize a SPI driver, but we don't talk to the SPI driver itself, we address a device. A device essentially +is a combination of SPI port and CS pin, plus some information about the specifics of communication to the device +(timing, command/address length etc) + +The essence of the interface to a device is a set of queues; one per device. The idea is that to send something to a SPI +device, you allocate a transaction descriptor. It contains some information about the transfer like the lenghth, address, +command etc, plus pointers to transmit and receive buffer. The address of this block gets pushed into the transmit queue. +The SPI driver does its magic, and sends and retrieves the data eventually. The data gets written to the receive buffers, +if needed the transaction descriptor is modified to indicate returned parameters and the entire thing goes into the return +queue, where whatever software initiated the transaction can retrieve it. + +The entire thing is run from the SPI interrupt handler. If SPI is done transmitting/receiving but nothing is in the queue, +it will not clear the SPI interrupt but just disable it. This way, when a new thing is sent, pushing the packet into the send +queue and re-enabling the interrupt will trigger the interrupt again, which can then take care of the sending. +*/ + + + +#include +#include "driver/spi_master.h" +#include "soc/gpio_sig_map.h" +#include "soc/spi_reg.h" +#include "soc/dport_reg.h" +#include "soc/spi_struct.h" +#include "soc/rtc_cntl_reg.h" +#include "rom/ets_sys.h" +#include "esp_types.h" +#include "esp_attr.h" +#include "esp_intr.h" +#include "esp_intr_alloc.h" +#include "esp_log.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/xtensa_api.h" +#include "freertos/task.h" +#include "freertos/ringbuf.h" +#include "soc/soc.h" +#include "soc/dport_reg.h" +#include "soc/uart_struct.h" +#include "rom/lldesc.h" +#include "driver/uart.h" +#include "driver/gpio.h" +#include "driver/periph_ctrl.h" +#include "esp_heap_alloc_caps.h" + +typedef struct spi_device_t spi_device_t; + +#define NO_CS 3 //Number of CS pins per SPI host + +typedef struct { + spi_device_t *device[NO_CS]; + intr_handle_t intr; + spi_dev_t *hw; + spi_transaction_t *cur_trans; + int cur_cs; + lldesc_t dmadesc_tx, dmadesc_rx; + bool no_gpio_matrix; +} spi_host_t; + +struct spi_device_t { + QueueHandle_t trans_queue; + QueueHandle_t ret_queue; + spi_device_interface_config_t cfg; + spi_host_t *host; +}; + +static spi_host_t *spihost[3]; + + +static const char *SPI_TAG = "spi_master"; +#define SPI_CHECK(a, str, ret_val) \ + if (!(a)) { \ + ESP_LOGE(SPI_TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +/* + Stores a bunch of per-spi-peripheral data. +*/ +typedef struct { + const uint8_t spiclk_out; //GPIO mux output signals + const uint8_t spid_out; + const uint8_t spiq_out; + const uint8_t spiwp_out; + const uint8_t spihd_out; + const uint8_t spid_in; //GPIO mux input signals + const uint8_t spiq_in; + const uint8_t spiwp_in; + const uint8_t spihd_in; + const uint8_t spics_out[3]; // /CS GPIO output mux signals + const uint8_t spiclk_native; //IO pins of IO_MUX muxed signals + const uint8_t spid_native; + const uint8_t spiq_native; + const uint8_t spiwp_native; + const uint8_t spihd_native; + const uint8_t spics0_native; + const uint8_t irq; //irq source for interrupt mux + const uint8_t irq_dma; //dma irq source for interrupt mux + const periph_module_t module; //peripheral module, for enabling clock etc + spi_dev_t *hw; //Pointer to the hardware registers +} spi_signal_conn_t; + +/* + Bunch of constants for every SPI peripheral: GPIO signals, irqs, hw addr of registers etc +*/ +static const spi_signal_conn_t io_signal[3]={ + { + .spiclk_out=SPICLK_OUT_IDX, + .spid_out=SPID_OUT_IDX, + .spiq_out=SPIQ_OUT_IDX, + .spiwp_out=SPIWP_OUT_IDX, + .spihd_out=SPIHD_OUT_IDX, + .spid_in=SPID_IN_IDX, + .spiq_in=SPIQ_IN_IDX, + .spiwp_in=SPIWP_IN_IDX, + .spihd_in=SPIHD_IN_IDX, + .spics_out={SPICS0_OUT_IDX, SPICS1_OUT_IDX, SPICS2_OUT_IDX}, + .spiclk_native=6, + .spid_native=8, + .spiq_native=7, + .spiwp_native=10, + .spihd_native=9, + .spics0_native=11, + .irq=ETS_SPI1_INTR_SOURCE, + .irq_dma=ETS_SPI1_DMA_INTR_SOURCE, + .module=PERIPH_SPI_MODULE, + .hw=&SPI1 + }, { + .spiclk_out=HSPICLK_OUT_IDX, + .spid_out=HSPID_OUT_IDX, + .spiq_out=HSPIQ_OUT_IDX, + .spiwp_out=HSPIWP_OUT_IDX, + .spihd_out=HSPIHD_OUT_IDX, + .spid_in=HSPID_IN_IDX, + .spiq_in=HSPIQ_IN_IDX, + .spiwp_in=HSPIWP_IN_IDX, + .spihd_in=HSPIHD_IN_IDX, + .spics_out={HSPICS0_OUT_IDX, HSPICS1_OUT_IDX, HSPICS2_OUT_IDX}, + .spiclk_native=14, + .spid_native=13, + .spiq_native=12, + .spiwp_native=2, + .spihd_native=4, + .spics0_native=15, + .irq=ETS_SPI2_INTR_SOURCE, + .irq_dma=ETS_SPI2_DMA_INTR_SOURCE, + .module=PERIPH_HSPI_MODULE, + .hw=&SPI2 + }, { + .spiclk_out=VSPICLK_OUT_IDX, + .spid_out=VSPID_OUT_IDX, + .spiq_out=VSPIQ_OUT_IDX, + .spiwp_out=VSPIWP_OUT_IDX, + .spihd_out=VSPIHD_OUT_IDX, + .spid_in=VSPID_IN_IDX, + .spiq_in=VSPIQ_IN_IDX, + .spiwp_in=VSPIWP_IN_IDX, + .spihd_in=VSPIHD_IN_IDX, + .spics_out={VSPICS0_OUT_IDX, VSPICS1_OUT_IDX, VSPICS2_OUT_IDX}, + .spiclk_native=18, + .spid_native=23, + .spiq_native=19, + .spiwp_native=22, + .spihd_native=21, + .spics0_native=5, + .irq=ETS_SPI3_INTR_SOURCE, + .irq_dma=ETS_SPI3_DMA_INTR_SOURCE, + .module=PERIPH_VSPI_MODULE, + .hw=&SPI3 + } +}; + +static void spi_intr(void *arg); + + +esp_err_t spi_bus_initialize(spi_host_device_t host, spi_bus_config_t *bus_config, int dma_chan) +{ + bool native=true; + /* ToDo: remove this when we have flash operations cooperating with this */ + SPI_CHECK(host!=SPI_HOST, "SPI1 is not supported", ESP_ERR_NOT_SUPPORTED); + + SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG); + SPI_CHECK(spihost[host]==NULL, "host already in use", ESP_ERR_INVALID_STATE); + + SPI_CHECK(bus_config->spid_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spid_io_num), "spid pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->spiclk_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spiclk_io_num), "spiclk pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->spiq_io_num<0 || GPIO_IS_VALID_GPIO(bus_config->spiq_io_num), "spiq pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->spiwp_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spiwp_io_num), "spiwp pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->spihd_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spihd_io_num), "spihd pin invalid", ESP_ERR_INVALID_ARG); + + //The host struct contains two dma descriptors, so we need DMA'able memory for this. + spihost[host]=pvPortMallocCaps(sizeof(spi_host_t), MALLOC_CAP_DMA); + if (spihost[host]==NULL) return ESP_ERR_NO_MEM; + memset(spihost[host], 0, sizeof(spi_host_t)); + + //Check if the selected pins correspond to the native pins of the peripheral + if (bus_config->spid_io_num >= 0 && bus_config->spid_io_num!=io_signal[host].spid_native) native=false; + if (bus_config->spiq_io_num >= 0 && bus_config->spiq_io_num!=io_signal[host].spiq_native) native=false; + if (bus_config->spiclk_io_num >= 0 && bus_config->spiclk_io_num!=io_signal[host].spiclk_native) native=false; + if (bus_config->spiwp_io_num >= 0 && bus_config->spiwp_io_num!=io_signal[host].spiwp_native) native=false; + if (bus_config->spihd_io_num >= 0 && bus_config->spihd_io_num!=io_signal[host].spihd_native) native=false; + + spihost[host]->no_gpio_matrix=native; + 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->spid_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spid_io_num], 1); + if (bus_config->spiq_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiq_io_num], 1); + if (bus_config->spiwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiwp_io_num], 1); + if (bus_config->spihd_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spihd_io_num], 1); + if (bus_config->spiclk_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiclk_io_num], 1); + } else { + //Use GPIO + if (bus_config->spid_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spid_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->spid_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(bus_config->spid_io_num, io_signal[host].spid_out, false, false); + gpio_matrix_in(bus_config->spid_io_num, io_signal[host].spid_in, false); + } + if (bus_config->spiq_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiq_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->spiq_io_num, GPIO_MODE_INPUT); + gpio_matrix_out(bus_config->spiq_io_num, io_signal[host].spiq_out, false, false); + gpio_matrix_in(bus_config->spiq_io_num, io_signal[host].spiq_in, false); + } + if (bus_config->spiwp_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiwp_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->spiwp_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(bus_config->spiwp_io_num, io_signal[host].spiwp_out, false, false); + gpio_matrix_in(bus_config->spiwp_io_num, io_signal[host].spiwp_in, false); + } + if (bus_config->spihd_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spihd_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->spihd_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(bus_config->spihd_io_num, io_signal[host].spihd_out, false, false); + gpio_matrix_in(bus_config->spihd_io_num, io_signal[host].spihd_in, false); + } + if (bus_config->spiclk_io_num>0) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiclk_io_num], PIN_FUNC_GPIO); + gpio_set_direction(bus_config->spiclk_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(bus_config->spiclk_io_num, io_signal[host].spiclk_out, false, false); + } + } + periph_module_enable(io_signal[host].module); + esp_intr_alloc(io_signal[host].irq, ESP_INTR_FLAG_INTRDISABLED, spi_intr, (void*)spihost[host], &spihost[host]->intr); + spihost[host]->hw=io_signal[host].hw; + + //Reset DMA + spihost[host]->hw->dma_conf.val|=SPI_OUT_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; + spihost[host]->hw->dma_out_link.start=0; + spihost[host]->hw->dma_in_link.start=0; + spihost[host]->hw->dma_conf.val&=~(SPI_OUT_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); + + //Disable unneeded ints + spihost[host]->hw->slave.rd_buf_done=0; + spihost[host]->hw->slave.wr_buf_done=0; + spihost[host]->hw->slave.rd_sta_done=0; + spihost[host]->hw->slave.wr_sta_done=0; + spihost[host]->hw->slave.rd_buf_inten=0; + spihost[host]->hw->slave.wr_buf_inten=0; + spihost[host]->hw->slave.rd_sta_inten=0; + spihost[host]->hw->slave.wr_sta_inten=0; + + //Force a transaction done interrupt. This interrupt won't fire yet because we initialized the SPI interrupt as + //disabled. This way, we can just enable the SPI interrupt and the interrupt handler will kick in, handling + //any transactions that are queued. + spihost[host]->hw->slave.trans_inten=1; + spihost[host]->hw->slave.trans_done=1; + + //Select DMA channel. + SET_PERI_REG_BITS(DPORT_SPI_DMA_CHAN_SEL_REG, 3, dma_chan, (host * 2)); + + return ESP_OK; +} + +esp_err_t spi_bus_free(spi_host_device_t host) +{ + int x; + SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG); + SPI_CHECK(spihost[host]!=NULL, "host not in use", ESP_ERR_INVALID_STATE); + for (x=0; xdevice[x]==NULL, "not all CSses freed", ESP_ERR_INVALID_STATE); + } + spihost[host]->hw->slave.trans_inten=0; + spihost[host]->hw->slave.trans_done=0; + esp_intr_free(spihost[host]->intr); + periph_module_disable(io_signal[host].module); + free(spihost[host]); + spihost[host]=NULL; + return ESP_OK; +} + +/* + Add a device. This allocates a CS line for the device, allocates memory for the device structure and hooks + up the CS pin to whatever is specified. +*/ +esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config_t *dev_config, spi_device_handle_t *handle) +{ + int freecs; + SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG); + SPI_CHECK(spihost[host]!=NULL, "host not initialized", ESP_ERR_INVALID_STATE); + SPI_CHECK(dev_config->spics_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_io_num), "spics pin invalid", ESP_ERR_INVALID_ARG); + for (freecs=0; freecsdevice[freecs], NULL, (spi_device_t *)1)) break; + } + SPI_CHECK(freecs!=NO_CS, "no free cs pins for host", ESP_ERR_NOT_FOUND); + //The hardware looks like it would support this, but actually setting cs_ena_pretrans when transferring in full + //duplex mode does absolutely nothing on the ESP32. + SPI_CHECK(dev_config->cs_ena_pretrans==0 || (dev_config->flags & SPI_DEVICE_HALFDUPLEX), "cs pretrans delay incompatible with full-duplex", ESP_ERR_INVALID_ARG); + + //Allocate memory for device + spi_device_t *dev=malloc(sizeof(spi_device_t)); + if (dev==NULL) return ESP_ERR_NO_MEM; + memset(dev, 0, sizeof(spi_device_t)); + spihost[host]->device[freecs]=dev; + + //Allocate queues, set defaults + dev->trans_queue=xQueueCreate(dev_config->queue_size, sizeof(spi_transaction_t *)); + dev->ret_queue=xQueueCreate(dev_config->queue_size, sizeof(spi_transaction_t *)); + if (dev_config->duty_cycle_pos==0) dev_config->duty_cycle_pos=128; + dev->host=spihost[host]; + + //We want to save a copy of the dev config in the dev struct. + memcpy(&dev->cfg, dev_config, sizeof(spi_device_interface_config_t)); + + //Set CS pin, CS options + if (dev_config->spics_io_num > 0) { + if (spihost[host]->no_gpio_matrix &&dev_config->spics_io_num == io_signal[host].spics0_native && freecs==0) { + //Again, the cs0s for all SPI peripherals map to pin mux source 1, so we use that instead of a define. + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[dev_config->spics_io_num], 1); + } else { + //Use GPIO matrix + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[dev_config->spics_io_num], PIN_FUNC_GPIO); + gpio_set_direction(dev_config->spics_io_num, GPIO_MODE_OUTPUT); + gpio_matrix_out(dev_config->spics_io_num, io_signal[host].spics_out[freecs], false, false); + } + } + if (dev_config->flags&SPI_DEVICE_CLK_AS_CS) { + spihost[host]->hw->pin.master_ck_sel |= (1<hw->pin.master_ck_sel &= (1<flags&SPI_DEVICE_POSITIVE_CS) { + spihost[host]->hw->pin.master_cs_pol |= (1<hw->pin.master_cs_pol &= (1<trans_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE); + SPI_CHECK(handle->host->cur_trans==0 || handle->host->device[handle->host->cur_cs]!=handle, "Have unfinished transactions", ESP_ERR_INVALID_STATE); + SPI_CHECK(uxQueueMessagesWaiting(handle->ret_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE); + + //Kill queues + vQueueDelete(handle->trans_queue); + vQueueDelete(handle->ret_queue); + //Remove device from list of csses and free memory + for (x=0; xhost->device[x] == handle) handle->host->device[x]=NULL; + } + free(handle); + return ESP_OK; +} + +static void spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) { + int pre, n, h, l; + //In hw, n, h and l are 1-32, pre is 0-8K. Value written to register is one lower than used value. + if (hz>(fapb/2)) { + //Can only solve this using fapb directly. + hw->clock.clkcnt_l=0; + hw->clock.clkcnt_h=0; + hw->clock.clkcnt_n=0; + hw->clock.clkdiv_pre=0; + hw->clock.clk_equ_sysclk=1; + } else { + //For best duty cycle resolution, we want n to be as close to 32 as possible. + //ToDo: + //This algo could use some tweaking; at the moment it either fixes n to 32 and + //uses the prescaler to get a suitable division factor, or sets the prescaler to 0 + //and uses n to set a value. In practice, sometimes a better result can be + //obtained by setting both n and pre to well-chosen valued... ToDo: fix up some algo to + //do this automatically (worst-case: bruteforce n/pre combo's) - JD + //Also ToDo: + //The ESP32 has a SPI_CK_OUT_HIGH_MODE and SPI_CK_OUT_LOW_MODE register; it looks like we can + //use those to specify the duty cycle in a more precise way. Figure out how to use these. - JD + n=(fapb/(hz*32)); + if (n>32) { + //Need to use prescaler + n=32; + } + if (n<32) { + //No need for prescaler. + n=(fapb/hz); + } + pre=(fapb/n)/hz; + h=n; + l=(((256-duty_cycle)*n+127)/256); + hw->clock.clk_equ_sysclk=0; + hw->clock.clkcnt_n=n-1; + hw->clock.clkdiv_pre=pre-1; + hw->clock.clkcnt_h=h-1; + hw->clock.clkcnt_l=l-1; + } +} + + +//If a transaction is smaller than or equal to of bits, we do not use DMA; instead, we directly copy/paste +//bits from/to the work registers. Keep between 32 and (8*32) please. +#define THRESH_DMA_TRANS (8*32) + +//This is run in interrupt context and apart from initialization and destruction, this is the only code +//touching the host (=spihost[x]) variable. The rest of the data arrives in queues. That is why there are +//no muxes in this code. +static void IRAM_ATTR spi_intr(void *arg) +{ + int i; + int prevCs=-1; + BaseType_t r; + BaseType_t do_yield=pdFALSE; + spi_transaction_t *trans=NULL; + spi_host_t *host=(spi_host_t*)arg; + + //Ignore all but the trans_done int. + if (!host->hw->slave.trans_done) return; + + if (host->cur_trans) { + //Okay, transaction is done. + if ((host->cur_trans->rx_buffer || (host->cur_trans->flags & SPI_USE_RXDATA)) && host->cur_trans->rxlength<=THRESH_DMA_TRANS) { + //Need to copy from SPI regs to result buffer. + uint32_t *data; + if (host->cur_trans->flags & SPI_USE_RXDATA) { + data=(uint32_t*)&host->cur_trans->rx_data[0]; + } else { + data=(uint32_t*)host->cur_trans->rx_buffer; + } + for (int x=0; x < host->cur_trans->rxlength; x+=32) { + //Do a memcpy to get around possible alignment issues in rx_buffer + uint32_t word=host->hw->data_buf[x/32]; + memcpy(&data[x/32], &word, 4); + } + } + //Call post-transaction callback, if any + if (host->device[host->cur_cs]->cfg.post_cb) host->device[host->cur_cs]->cfg.post_cb(host->cur_trans); + //Return transaction descriptor. + xQueueSendFromISR(host->device[host->cur_cs]->ret_queue, &host->cur_trans, &do_yield); + host->cur_trans=NULL; + prevCs=host->cur_cs; + } + //ToDo: This is a stupidly simple low-cs-first priority scheme. Make this configurable somehow. - JD + for (i=0; idevice[i]) { + r=xQueueReceiveFromISR(host->device[i]->trans_queue, &trans, &do_yield); + //Stop looking if we have a transaction to send. + if (r) break; + } + } + if (i==NO_CS) { + //No packet waiting. Disable interrupt. + esp_intr_disable(host->intr); + } else { + host->hw->slave.trans_done=0; //clear int bit + //We have a transaction. Send it. + spi_device_t *dev=host->device[i]; + host->cur_trans=trans; + //We should be done with the transmission. + assert(host->hw->cmd.usr == 0); + + //Default rxlength to be the same as length, if not filled in. + if (trans->rxlength==0) { + trans->rxlength=trans->length; + } + + //Reconfigure accoding to device settings, but only if we change CSses. + if (i!=prevCs) { + //Assumes a hardcoded 80MHz Fapb for now. ToDo: figure out something better once we have + //clock scaling working. + int apbclk=APB_CLK_FREQ; + spi_set_clock(host->hw, apbclk, dev->cfg.clock_speed_hz, dev->cfg.duty_cycle_pos); + //Configure bit order + host->hw->ctrl.rd_bit_order=(dev->cfg.flags & SPI_DEVICE_RXBIT_LSBFIRST)?1:0; + host->hw->ctrl.wr_bit_order=(dev->cfg.flags & SPI_DEVICE_TXBIT_LSBFIRST)?1:0; + + //Configure polarity + //SPI iface needs to be configured for a delay unless it is not routed through GPIO and clock is >=apb/2 + int nodelay=(host->no_gpio_matrix && dev->cfg.clock_speed_hz >= (apbclk/2)); + if (dev->cfg.mode==0) { + host->hw->pin.ck_idle_edge=0; + host->hw->user.ck_out_edge=0; + host->hw->ctrl2.miso_delay_mode=nodelay?0:2; + } else if (dev->cfg.mode==1) { + host->hw->pin.ck_idle_edge=0; + host->hw->user.ck_out_edge=1; + host->hw->ctrl2.miso_delay_mode=nodelay?0:1; + } else if (dev->cfg.mode==2) { + host->hw->pin.ck_idle_edge=1; + host->hw->user.ck_out_edge=1; + host->hw->ctrl2.miso_delay_mode=nodelay?0:1; + } else if (dev->cfg.mode==3) { + host->hw->pin.ck_idle_edge=1; + host->hw->user.ck_out_edge=0; + host->hw->ctrl2.miso_delay_mode=nodelay?0:2; + } + + //Configure bit sizes, load addr and command + host->hw->user.usr_dummy=(dev->cfg.dummy_bits)?1:0; + host->hw->user.usr_addr=(dev->cfg.address_bits)?1:0; + host->hw->user.usr_command=(dev->cfg.command_bits)?1:0; + host->hw->user1.usr_addr_bitlen=dev->cfg.address_bits-1; + host->hw->user1.usr_dummy_cyclelen=dev->cfg.dummy_bits-1; + host->hw->user2.usr_command_bitlen=dev->cfg.command_bits-1; + //Configure misc stuff + host->hw->user.doutdin=(dev->cfg.flags & SPI_DEVICE_HALFDUPLEX)?0:1; + host->hw->user.sio=(dev->cfg.flags & SPI_DEVICE_3WIRE)?1:0; + + host->hw->ctrl2.setup_time=dev->cfg.cs_ena_pretrans-1; + host->hw->user.cs_setup=dev->cfg.cs_ena_pretrans?1:0; + host->hw->ctrl2.hold_time=dev->cfg.cs_ena_posttrans-1; + host->hw->user.cs_hold=(dev->cfg.cs_ena_posttrans)?1:0; + + //Configure CS pin + host->hw->pin.cs0_dis=(i==0)?0:1; + host->hw->pin.cs1_dis=(i==1)?0:1; + host->hw->pin.cs2_dis=(i==2)?0:1; + } + //Reset DMA + host->hw->dma_conf.val |= SPI_OUT_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST; + host->hw->dma_out_link.start=0; + host->hw->dma_in_link.start=0; + host->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST); + //QIO/DIO + host->hw->ctrl.val &= ~(SPI_FREAD_DUAL|SPI_FREAD_QUAD|SPI_FREAD_DIO|SPI_FREAD_QIO); + host->hw->user.val &= ~(SPI_FWRITE_DUAL|SPI_FWRITE_QUAD|SPI_FWRITE_DIO|SPI_FWRITE_QIO); + if (trans->flags & SPI_MODE_DIO) { + if (trans->flags & SPI_MODE_DIOQIO_ADDR) { + host->hw->ctrl.fread_dio=1; + host->hw->user.fwrite_dio=1; + } else { + host->hw->ctrl.fread_dual=1; + host->hw->user.fwrite_dual=1; + } + host->hw->ctrl.fastrd_mode=1; + } else if (trans->flags & SPI_MODE_QIO) { + if (trans->flags & SPI_MODE_DIOQIO_ADDR) { + host->hw->ctrl.fread_qio=1; + host->hw->user.fwrite_qio=1; + } else { + host->hw->ctrl.fread_quad=1; + host->hw->user.fwrite_quad=1; + } + host->hw->ctrl.fastrd_mode=1; + } + + + //Fill DMA descriptors + if (trans->rx_buffer || (trans->flags & SPI_USE_RXDATA)) { + uint32_t *data; + if (trans->flags & SPI_USE_RXDATA) { + data=(uint32_t *)&trans->rx_data[0]; + } else { + data=trans->rx_buffer; + } + if (trans->rxlengthhw->user.usr_miso_highpart=0; + host->dmadesc_rx.size=(trans->rxlength+7)/8; + host->dmadesc_rx.length=(trans->rxlength+7)/8; + host->dmadesc_rx.buf=(uint8_t*)data; + host->dmadesc_rx.eof=1; + host->dmadesc_rx.sosf=0; + host->dmadesc_rx.owner=1; + host->hw->dma_in_link.addr=(int)(&host->dmadesc_rx)&0xFFFFF; + host->hw->dma_in_link.start=1; + } + host->hw->user.usr_miso=1; + } else { + host->hw->user.usr_miso=0; + } + + if (trans->tx_buffer || (trans->flags & SPI_USE_TXDATA)) { + uint32_t *data; + if (trans->flags & SPI_USE_TXDATA) { + data=(uint32_t *)&trans->tx_data[0]; + } else { + data=(uint32_t *)trans->tx_buffer; + } + if (trans->rxlength < 8*32) { + //No need for DMA. + for (int x=0; x < trans->rxlength; x+=32) { + //Use memcpy to get around alignment issues for txdata + uint32_t word; + memcpy(&word, &data[x/32], 4); + host->hw->data_buf[(x/32)+8]=word; + } + host->hw->user.usr_mosi_highpart=1; + } else { + host->hw->user.usr_mosi_highpart=0; + host->dmadesc_tx.size=(trans->length+7)/8; + host->dmadesc_tx.length=(trans->length+7)/8; + host->dmadesc_tx.buf=(uint8_t*)data; + host->dmadesc_tx.eof=1; + host->dmadesc_tx.sosf=0; + host->dmadesc_tx.owner=1; + host->hw->dma_out_link.addr=(int)(&host->dmadesc_tx) & 0xFFFFF; + host->hw->dma_out_link.start=1; + } + } + host->hw->mosi_dlen.usr_mosi_dbitlen=trans->length-1; + host->hw->miso_dlen.usr_miso_dbitlen=trans->rxlength-1; + host->hw->user2.usr_command_value=trans->command; + if (dev->cfg.address_bits>32) { + host->hw->addr=trans->address >> 32; + host->hw->slv_wr_status=trans->address & 0xffffffff; + } else { + host->hw->addr=trans->address & 0xffffffff; + } + host->hw->user.usr_mosi=(trans->tx_buffer==NULL)?0:1; + host->hw->user.usr_miso=(trans->tx_buffer==NULL)?0:1; + + //Call pre-transmission callback, if any + if (dev->cfg.pre_cb) dev->cfg.pre_cb(trans); + //Kick off transfer + host->hw->cmd.usr=1; + } + if (do_yield) portYIELD_FROM_ISR(); +} + + +esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t *trans_desc, TickType_t ticks_to_wait) +{ + BaseType_t r; + SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG); + SPI_CHECK((trans_desc->flags & SPI_USE_RXDATA)==0 ||trans_desc->length <= 32, "rxdata transfer > 32bytes", ESP_ERR_INVALID_ARG); + SPI_CHECK((trans_desc->flags & SPI_USE_TXDATA)==0 ||trans_desc->length <= 32, "txdata transfer > 32bytes", ESP_ERR_INVALID_ARG); + SPI_CHECK(!((trans_desc->flags & (SPI_MODE_DIO|SPI_MODE_QIO)) && (handle->cfg.flags & SPI_DEVICE_3WIRE)), "incompatible iface params", ESP_ERR_INVALID_ARG); + SPI_CHECK(!((trans_desc->flags & (SPI_MODE_DIO|SPI_MODE_QIO)) && (!(handle->cfg.flags & SPI_DEVICE_HALFDUPLEX))), "incompatible iface params", ESP_ERR_INVALID_ARG); + r=xQueueSend(handle->trans_queue, (void*)&trans_desc, ticks_to_wait); + if (!r) return ESP_ERR_TIMEOUT; + esp_intr_enable(handle->host->intr); + return ESP_OK; +} + +esp_err_t spi_device_get_trans_result(spi_device_handle_t handle, spi_transaction_t **trans_desc, TickType_t ticks_to_wait) +{ + BaseType_t r; + SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG); + r=xQueueReceive(handle->ret_queue, (void*)trans_desc, ticks_to_wait); + if (!r) return ESP_ERR_TIMEOUT; + return ESP_OK; +} + +//Porcelain to do one blocking transmission. +esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc) +{ + esp_err_t ret; + spi_transaction_t *ret_trans; + //ToDo: check if any spi transfers in flight + ret=spi_device_queue_trans(handle, trans_desc, portMAX_DELAY); + if (ret!=ESP_OK) return ret; + ret=spi_device_get_trans_result(handle, &ret_trans, portMAX_DELAY); + if (ret!=ESP_OK) return ret; + assert(ret_trans==trans_desc); + return ESP_OK; +} + diff --git a/components/driver/test/component.mk b/components/driver/test/component.mk new file mode 100644 index 0000000000..5dd172bdb7 --- /dev/null +++ b/components/driver/test/component.mk @@ -0,0 +1,5 @@ +# +#Component Makefile +# + +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/driver/test/test_spi_master.c b/components/driver/test/test_spi_master.c new file mode 100644 index 0000000000..0fd47cbdba --- /dev/null +++ b/components/driver/test/test_spi_master.c @@ -0,0 +1,80 @@ +/* + Tests for the spi_master device driver +*/ + +#include +#include +#include +#include +#include +#include "rom/ets_sys.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "freertos/xtensa_api.h" +#include "unity.h" +#include "driver/spi_master.h" + + + +TEST_CASE("SPI Master test", "[spi]") +{ + spi_bus_config_t buscfg={ + .spid_io_num=4, + .spiq_io_num=16, + .spiclk_io_num=25, + .spiwp_io_num=-1, + .spihd_io_num=-1 + }; + spi_device_interface_config_t devcfg={ + .command_bits=8, + .address_bits=64, + .dummy_bits=0, + .clock_speed_hz=8000, + .duty_cycle_pos=128, + .cs_ena_pretrans=7, + .cs_ena_posttrans=7, + .mode=0, + .spics_io_num=21, + .queue_size=3 + }; + + esp_err_t ret; + spi_device_handle_t handle; + printf("THIS TEST NEEDS A JUMPER BETWEEN IO4 AND IO16\n"); + + ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1); + TEST_ASSERT(ret==ESP_OK); + ret=spi_bus_add_device(HSPI_HOST, &devcfg, &handle); + TEST_ASSERT(ret==ESP_OK); + printf("Bus/dev inited.\n"); + spi_transaction_t t; + char sendbuf[16]="Hello World!"; + char recvbuf[16]="UUUUUUUUUUUUUUU"; + memset(&t, 0, sizeof(t)); + + t.length=16*8; + t.tx_buffer=sendbuf; + t.rx_buffer=recvbuf; + t.address=0xA00000000000000FL; + t.command=0x55; + printf("Transmit...\n"); + ret=spi_device_transmit(handle, &t); + TEST_ASSERT(ret==ESP_OK); + printf("Send vs recv:\n"); + for (int x=0; x<16; x++) printf("%02X ", (int)sendbuf[x]); + printf("`_ + +Macros +^^^^^^ + +.. doxygendefine:: SPI_DEVICE_TXBIT_LSBFIRST +.. doxygendefine:: SPI_DEVICE_RXBIT_LSBFIRST +.. doxygendefine:: SPI_DEVICE_BIT_LSBFIRST +.. doxygendefine:: SPI_DEVICE_3WIRE +.. doxygendefine:: SPI_DEVICE_POSITIVE_CS +.. doxygendefine:: SPI_DEVICE_HALFDUPLEX +.. doxygendefine:: SPI_DEVICE_CLK_AS_CS + +.. doxygendefine:: SPI_MODE_DIO +.. doxygendefine:: SPI_MODE_QIO +.. doxygendefine:: SPI_MODE_DIOQIO_ADDR +.. doxygendefine:: SPI_USE_RXDATA +.. doxygendefine:: SPI_USE_TXDATA + +Type Definitions +^^^^^^^^^^^^^^^^ + +.. doxygentypedef:: spi_device_handle_t + +Enumerations +^^^^^^^^^^^^ + +.. doxygenenum:: spi_host_device_t + +Structures +^^^^^^^^^^ + +.. doxygenstruct:: spi_transaction_t + :members: + +.. doxygenstruct:: spi_bus_config_t + :members: + +.. doxygenstruct:: spi_device_interface_config_t + :members: + + + +Functions +--------- + +.. doxygenfunction:: spi_bus_initialize +.. doxygenfunction:: spi_bus_free +.. doxygenfunction:: spi_bus_add_device +.. doxygenfunction:: spi_bus_remove_device +.. doxygenfunction:: spi_device_queue_trans +.. doxygenfunction:: spi_device_get_trans_result +.. doxygenfunction:: spi_device_transmit + diff --git a/docs/index.rst b/docs/index.rst index 9c2b0643f4..3161db345b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -70,7 +70,7 @@ Contents: 6.4. UART 6.5. I2C - TBA 6.6. I2S - TBA - 6.7. SPI - TBA + 6.7. SPI - 6.8. CAN - TBA 6.9. SD Controller - TBA 6.10. Infrared - TBA @@ -111,6 +111,7 @@ Contents: Pulse Counter Sigma-delta Modulation SPI Flash and Partition APIs + SPI Master API Logging Non-Volatile Storage Virtual Filesystem diff --git a/examples/26_spi_master/Makefile b/examples/26_spi_master/Makefile new file mode 100644 index 0000000000..7ca171bb52 --- /dev/null +++ b/examples/26_spi_master/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 := spi_master + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/26_spi_master/main/component.mk b/examples/26_spi_master/main/component.mk new file mode 100644 index 0000000000..4d3b30caf3 --- /dev/null +++ b/examples/26_spi_master/main/component.mk @@ -0,0 +1,5 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/examples/26_spi_master/main/spi_master.c b/examples/26_spi_master/main/spi_master.c new file mode 100644 index 0000000000..1cdbf72ad5 --- /dev/null +++ b/examples/26_spi_master/main/spi_master.c @@ -0,0 +1,275 @@ +/* SPI Master example + + 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/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "driver/spi_master.h" +#include "soc/gpio_struct.h" +#include "driver/gpio.h" + + +/* + This code displays some fancy graphics on the ILI9341-based 320x240 LCD on an ESP-WROVER_KIT board. + It is not very fast, even when the SPI transfer itself happens at 8MHz and with DMA, because + the rest of the code is not very optimized. Especially calculating the image line-by-line + is inefficient; it would be quicker to send an entire screenful at once. This example does, however, + demonstrate the use of both spi_device_transmit as well as spi_device_queue_trans/spi_device_get_trans_result + as well as pre-transmit callbacks. + + Some info about the ILI9341: It has an C/D line, which is connected to a GPIO here. It expects this + line to be low for a command and high for data. We use a pre-transmit callback here to control that + line: every transaction has as the user-definable argument the needed state of the D/C line and just + before the transaction is sent, the callback will set this line to the correct state. +*/ + +#define PIN_NUM_MISO 25 +#define PIN_NUM_MOSI 23 +#define PIN_NUM_CLK 19 +#define PIN_NUM_CS 22 + +#define PIN_NUM_DC 21 +#define PIN_NUM_RST 18 +#define PIN_NUM_BCKL 5 + + +/* + The ILI9341 needs a bunch of command/argument values to be initialized. They are stored in this struct. +*/ +typedef struct { + uint8_t cmd; + uint8_t data[16]; + uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds. +} ili_init_cmd_t; + +static const ili_init_cmd_t ili_init_cmds[]={ + {0xCF, {0x00, 0x83, 0X30}, 3}, + {0xED, {0x64, 0x03, 0X12, 0X81}, 4}, + {0xE8, {0x85, 0x01, 0x79}, 3}, + {0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5}, + {0xF7, {0x20}, 1}, + {0xEA, {0x00, 0x00}, 2}, + {0xC0, {0x26}, 1}, + {0xC1, {0x11}, 1}, + {0xC5, {0x35, 0x3E}, 2}, + {0xC7, {0xBE}, 1}, + {0x36, {0x28}, 1}, + {0x3A, {0x55}, 1}, + {0xB1, {0x00, 0x1B}, 2}, + {0xF2, {0x08}, 1}, + {0x26, {0x01}, 1}, + {0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15}, + {0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15}, + {0x2A, {0x00, 0x00, 0x00, 0xEF}, 4}, + {0x2B, {0x00, 0x00, 0x01, 0x3f}, 4}, + {0x2C, {0}, 0}, + {0xB7, {0x07}, 1}, + {0xB6, {0x0A, 0x82, 0x27, 0x00}, 4}, + {0x11, {0}, 0x80}, + {0x29, {0}, 0x80}, + {0, {0}, 0xff}, +}; + +//Send a command to the ILI9341. Uses spi_device_transmit, which waits until the transfer is complete. +void ili_cmd(spi_device_handle_t spi, const uint8_t cmd) +{ + esp_err_t ret; + spi_transaction_t t; + memset(&t, 0, sizeof(t)); //Zero out the transaction + t.length=8; //Command is 8 bits + t.tx_buffer=&cmd; //The data is the cmd itself + t.user=(void*)0; //D/C needs to be set to 0 + ret=spi_device_transmit(spi, &t); //Transmit! + assert(ret==ESP_OK); //Should have had no issues. +} + +//Send data to the ILI9341. Uses spi_device_transmit, which waits until the transfer is complete. +void ili_data(spi_device_handle_t spi, const uint8_t *data, int len) +{ + esp_err_t ret; + spi_transaction_t t; + if (len==0) return; //no need to send anything + memset(&t, 0, sizeof(t)); //Zero out the transaction + t.length=len*8; //Len is in bytes, transaction length is in bits. + t.tx_buffer=data; //Data + t.user=(void*)1; //D/C needs to be set to 1 + ret=spi_device_transmit(spi, &t); //Transmit! + assert(ret==ESP_OK); //Should have had no issues. +} + +//This function is called (in irq context!) just before a transmission starts. It will +//set the D/C line to the value indicated in the user field. +void ili_spi_pre_transfer_callback(spi_transaction_t *t) +{ + int dc=(int)t->user; + gpio_set_level(PIN_NUM_DC, dc); +} + +//Initialize the display +void ili_init(spi_device_handle_t spi) +{ + int cmd=0; + //Initialize non-SPI GPIOs + gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT); + gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT); + gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT); + + //Reset the display + gpio_set_level(PIN_NUM_RST, 0); + vTaskDelay(100 / portTICK_RATE_MS); + gpio_set_level(PIN_NUM_RST, 1); + vTaskDelay(100 / portTICK_RATE_MS); + + //Send all the commands + while (ili_init_cmds[cmd].databytes!=0xff) { + ili_cmd(spi, ili_init_cmds[cmd].cmd); + ili_data(spi, ili_init_cmds[cmd].data, ili_init_cmds[cmd].databytes&0x1F); + if (ili_init_cmds[cmd].databytes&0x80) { + vTaskDelay(100 / portTICK_RATE_MS); + } + cmd++; + } + + ///Enable backlight + gpio_set_level(PIN_NUM_BCKL, 0); +} + + +//To send a line we have to send a command, 2 data bytes, another command, 2 more data bytes and another command +//before sending the line data itself; a total of 6 transactions. (We can't put all of this in just one transaction +//because the D/C line needs to be toggled in the middle.) +//This routine queues these commands up so they get sent as quickly as possible. +void send_line(spi_device_handle_t spi, int ypos, uint16_t *line) +{ + esp_err_t ret; + int x; + //Transaction descriptors. Declared static so they're not allocated on the stack; we need this memory even when this + //function is finished because the SPI driver needs access to it even while we're already calculating the next line. + static spi_transaction_t trans[6]; + + //In theory, it's better to initialize trans and data only once and hang on to the initialized + //variables. We allocate them on the stack, so we need to re-init them each call. + for (x=0; x<6; x++) { + memset(&trans[x], 0, sizeof(spi_transaction_t)); + if ((x&1)==0) { + //Even transfers are commands + trans[x].length=8; + trans[x].user=(void*)0; + } else { + //Odd transfers are data + trans[x].length=8*4; + trans[x].user=(void*)1; + } + trans[x].flags=SPI_USE_TXDATA; + } + trans[0].tx_data[0]=0x2A; //Column Address Set + trans[1].tx_data[0]=0; //Start Col High + trans[1].tx_data[1]=0; //Start Col Low + trans[1].tx_data[2]=(320)>>8; //End Col High + trans[1].tx_data[3]=(320)&0xff; //End Col Low + trans[2].tx_data[0]=0x2B; //Page address set + trans[3].tx_data[0]=ypos>>8; //Start page high + trans[3].tx_data[1]=ypos&0xff; //start page low + trans[3].tx_data[2]=(ypos+1)>>8; //end page high + trans[3].tx_data[3]=(ypos+1)&0xff; //end page low + trans[4].tx_data[0]=0x2C; //memory write + trans[5].tx_buffer=line; //finally send the line data + trans[5].length=320*2*8; //Data length, in bits + trans[5].flags=0; //undo SPI_USE_TXDATA flag + + //Queue all transactions. + for (x=0; x<6; x++) { + ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY); + assert(ret==ESP_OK); + } + + //When we are here, the SPI driver is busy (in the background) getting the transactions sent. That happens + //mostly using DMA, so the CPU doesn't have much to do here. We're not going to wait for the transaction to + //finish because we may as well spend the time calculating the next line. When that is done, we can call + //send_line_finish, which will wait for the transfers to be done and check their status. +} + + +void send_line_finish(spi_device_handle_t spi) +{ + spi_transaction_t *rtrans; + esp_err_t ret; + //Wait for all 6 transactions to be done and get back the results. + for (int x=0; x<6; x++) { + ret=spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY); + assert(ret==ESP_OK); + //We could inspect rtrans now if we received any info back. The LCD is treated as write-only, though. + } +} + + +//Simple routine to generate some patterns and send them to the LCD. Don't expect anything too +//impressive. Because the SPI driver handles transactions in the background, we can calculate the next line +//while the previous one is being sent. +void display_pretty_colors(spi_device_handle_t spi) +{ + uint16_t line[2][320]; + int x, y, frame=0; + //Indexes of the line currently being sent to the LCD and the line we're calculating. + int sending_line=-1; + int calc_line=0; + + while(1) { + frame++; + for (y=0; y<240; y++) { + //Calculate a line. + for (x=0; x<320; x++) { + line[calc_line][x]=((x<<3)^(y<<3)^(frame+x*y)); + } + //Finish up the sending process of the previous line, if any + if (sending_line!=-1) send_line_finish(spi); + //Swap sending_line and calc_line + sending_line=calc_line; + calc_line=(calc_line==1)?0:1; + //Send the line we currently calculated. + send_line(spi, y, line[sending_line]); + //The line is queued up for sending now; the actual sending happens in the + //background. We can go on to calculate the next line as long as we do not + //touch line[sending_line]; the SPI sending process is still reading from that. + } + } +} + + +void app_main() +{ + esp_err_t ret; + spi_device_handle_t spi; + spi_bus_config_t buscfg={ + .spiq_io_num=PIN_NUM_MISO, + .spid_io_num=PIN_NUM_MOSI, + .spiclk_io_num=PIN_NUM_CLK, + .spiwp_io_num=-1, + .spihd_io_num=-1 + }; + spi_device_interface_config_t devcfg={ + .clock_speed_hz=10000000, //Clock out at 10 MHz + .mode=0, //SPI mode 0 + .spics_io_num=PIN_NUM_CS, //CS pin + .queue_size=7, //We want to be able to queue 7 transactions at a time + .pre_cb=ili_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line + }; + //Initialize the SPI bus + ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1); + assert(ret==ESP_OK); + //Attach the LCD to the SPI bus + ret=spi_bus_add_device(HSPI_HOST, &devcfg, &spi); + assert(ret==ESP_OK); + //Initialize the LCD + ili_init(spi); + //Go do nice stuff. + display_pretty_colors(spi); +} From 845cd09570aed9b1602481494ce4184a7f29b099 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 5 Jan 2017 00:27:59 +0800 Subject: [PATCH 076/167] cxx: link against libstdc++, remove abi.cpp --- components/esp32/abi.cpp | 114 --------------------------------------- make/project.mk | 1 + 2 files changed, 1 insertion(+), 114 deletions(-) delete mode 100644 components/esp32/abi.cpp diff --git a/components/esp32/abi.cpp b/components/esp32/abi.cpp deleted file mode 100644 index c40dba85e0..0000000000 --- a/components/esp32/abi.cpp +++ /dev/null @@ -1,114 +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. -#include -#include -#include -#include - -// using __cxxabiv1::__guard; - -void *operator new(size_t size) -{ - return malloc(size); -} - -void *operator new[](size_t size) -{ - return malloc(size); -} - -void operator delete(void * ptr) -{ - free(ptr); -} - -void operator delete[](void * ptr) -{ - free(ptr); -} - -extern "C" void __cxa_pure_virtual(void) __attribute__ ((__noreturn__)); -extern "C" void __cxa_deleted_virtual(void) __attribute__ ((__noreturn__)); - -void __cxa_pure_virtual(void) -{ - abort(); -} - -void __cxa_deleted_virtual(void) -{ - abort(); -} - -#if 0 -typedef struct { - uint8_t guard; - uint8_t ps; -} guard_t; - -extern "C" int __cxa_guard_acquire(__guard* pg) -{ - uint8_t ps = xt_rsil(15); - if (reinterpret_cast(pg)->guard) { - xt_wsr_ps(ps); - return 0; - } - reinterpret_cast(pg)->ps = ps; - return 1; -} - -extern "C" void __cxa_guard_release(__guard* pg) -{ - reinterpret_cast(pg)->guard = 1; - xt_wsr_ps(reinterpret_cast(pg)->ps); -} - -extern "C" void __cxa_guard_abort(__guard* pg) -{ - xt_wsr_ps(reinterpret_cast(pg)->ps); -} -#endif - -extern "C" void __cxa_throw_bad_array_new_length() -{ - abort(); -} - -namespace std -{ -void __throw_bad_function_call() -{ - abort(); -} - -void __throw_length_error(char const*) -{ - abort(); -} - -void __throw_bad_alloc() -{ - abort(); -} - -void __throw_logic_error(const char* str) -{ - abort(); -} - -void __throw_out_of_range(const char* str) -{ - abort(); -} -} diff --git a/make/project.mk b/make/project.mk index e2a93ce119..2b8eedeeb5 100644 --- a/make/project.mk +++ b/make/project.mk @@ -187,6 +187,7 @@ LDFLAGS ?= -nostdlib \ -Wl,--start-group \ $(COMPONENT_LDFLAGS) \ -lgcc \ + -lstdc++ \ -Wl,--end-group \ -Wl,-EL From 5b2888e1135311b9a5abc1334af2a26dc2d2c87e Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 5 Jan 2017 00:37:56 +0800 Subject: [PATCH 077/167] unity: fix testcase initializer for compiling with C++ --- .../components/unity/include/unity_config.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/unit-test-app/components/unity/include/unity_config.h b/tools/unit-test-app/components/unity/include/unity_config.h index b8e56a79c1..07df5b3058 100644 --- a/tools/unit-test-app/components/unity/include/unity_config.h +++ b/tools/unit-test-app/components/unity/include/unity_config.h @@ -61,11 +61,20 @@ void unity_run_all_tests(); .desc = desc_, \ .fn = &UNITY_TEST_UID(test_func_), \ .file = __FILE__, \ - .line = __LINE__ \ + .line = __LINE__, \ + .next = NULL \ }; \ unity_testcase_register( & UNITY_TEST_UID(test_desc_) ); \ }\ static void UNITY_TEST_UID(test_func_) (void) +/** + * Note: initialization of test_desc_t fields above has to be done exactly + * in the same order as the fields are declared in the structure. + * Otherwise the initializer will not be valid in C++ (which doesn't + * support designated initializers). G++ can parse the syntax, but + * field names are treated as annotations and don't affect initialization + * order. Also make sure all the fields are initialized. + */ // shorthand to check esp_err_t return code #define TEST_ESP_OK(rc) TEST_ASSERT_EQUAL_INT32(ESP_OK, rc) From 1cbc2fa046f4c1afd96054eb2987859c265ec361 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 5 Jan 2017 22:55:38 +0800 Subject: [PATCH 078/167] esp32: ets_update_cpu_frequency should set tick scale for both CPUs --- components/esp32/cpu_freq.c | 8 +++++ components/esp32/ld/esp32.rom.ld | 4 ++- components/esp32/test/test_delay.c | 58 ++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 components/esp32/test/test_delay.c diff --git a/components/esp32/cpu_freq.c b/components/esp32/cpu_freq.c index 257639cf90..7618f147af 100644 --- a/components/esp32/cpu_freq.c +++ b/components/esp32/cpu_freq.c @@ -13,6 +13,7 @@ // limitations under the License. #include +#include "esp_attr.h" #include "rom/ets_sys.h" #include "rom/uart.h" #include "sdkconfig.h" @@ -66,3 +67,10 @@ void esp_set_cpu_freq(void) ets_update_cpu_frequency(freq_mhz); } +void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us) +{ + extern uint32_t g_ticks_per_us_pro; // g_ticks_us defined in ROM for PRO CPU + extern uint32_t g_ticks_per_us_app; // same defined for APP CPU + g_ticks_per_us_pro = ticks_per_us; + g_ticks_per_us_app = ticks_per_us; +} diff --git a/components/esp32/ld/esp32.rom.ld b/components/esp32/ld/esp32.rom.ld index 6241ff840e..8b5ed5f8dc 100644 --- a/components/esp32/ld/esp32.rom.ld +++ b/components/esp32/ld/esp32.rom.ld @@ -202,7 +202,7 @@ PROVIDE ( ets_timer_init = 0x400084e8 ); PROVIDE ( ets_timer_setfn = 0x40008350 ); PROVIDE ( ets_unpack_flash_code = 0x40007018 ); PROVIDE ( ets_unpack_flash_code_legacy = 0x4000694c ); -PROVIDE ( ets_update_cpu_frequency = 0x40008550 ); +/* PROVIDE ( ets_update_cpu_frequency = 0x40008550 ); */ /* Updates g_ticks_per_us on the current CPU only; not on the other core */ PROVIDE ( ets_waiti0 = 0x400067d8 ); PROVIDE ( exc_cause_table = 0x3ff991d0 ); PROVIDE ( _exit_r = 0x4000bd28 ); @@ -1725,6 +1725,8 @@ PROVIDE ( xthal_memcpy = 0x4000c0bc ); PROVIDE ( xthal_set_ccompare = 0x4000c058 ); PROVIDE ( xthal_set_intclear = 0x4000c1ec ); PROVIDE ( _xtos_set_intlevel = 0x4000bfdc ); +PROVIDE ( g_ticks_per_us_pro = 0x3ffe01e0 ); +PROVIDE ( g_ticks_per_us_app = 0x3ffe40f0 ); /* These functions are xtos-related (or call xtos-related functions) and do not play well with multicore FreeRTOS. Where needed, we provide alternatives that are multicore diff --git a/components/esp32/test/test_delay.c b/components/esp32/test/test_delay.c new file mode 100644 index 0000000000..0b6c4aaf97 --- /dev/null +++ b/components/esp32/test/test_delay.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include "unity.h" +#include "rom/ets_sys.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +typedef struct { + int delay_us; + int method; +} delay_test_arg_t; + +static void test_delay_task(void* p) +{ + const delay_test_arg_t* arg = (delay_test_arg_t*) p; + struct timeval tv_start, tv_stop; + gettimeofday(&tv_start, NULL); + switch (arg->method) { + case 0: + ets_delay_us(arg->delay_us); + break; + case 1: + vTaskDelay(arg->delay_us / portTICK_PERIOD_MS / 1000); + break; + default: + TEST_FAIL(); + } + gettimeofday(&tv_stop, NULL); + int real_delay_us = (tv_stop.tv_sec - tv_start.tv_sec) * 1000000 + + tv_stop.tv_usec - tv_start.tv_usec; + printf("%s core=%d expected=%d actual=%d\n", arg->method ? "vTaskDelay" : "ets_delay_us", + xPortGetCoreID(), arg->delay_us, real_delay_us); + TEST_ASSERT_TRUE(abs(real_delay_us - arg->delay_us) < 1000); + vTaskDelay(1); + vTaskDelete(NULL); +} + +TEST_CASE("ets_delay produces correct delay on both CPUs", "[delay]") +{ + int delay_ms = 50; + const delay_test_arg_t args = { .delay_us = delay_ms * 1000, .method = 0 }; + xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void*) &args, 3, NULL, 0); + vTaskDelay(delay_ms / portTICK_PERIOD_MS + 1); + xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void*) &args, 3, NULL, 1); + vTaskDelay(delay_ms / portTICK_PERIOD_MS + 1); +} + +TEST_CASE("vTaskDelay produces correct delay on both CPUs", "[delay]") +{ + int delay_ms = 50; + const delay_test_arg_t args = { .delay_us = delay_ms * 1000, .method = 1 }; + xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void*) &args, 3, NULL, 0); + vTaskDelay(delay_ms / portTICK_PERIOD_MS + 1); + xTaskCreatePinnedToCore(test_delay_task, "", 2048, (void*) &args, 3, NULL, 1); + vTaskDelay(delay_ms / portTICK_PERIOD_MS + 1); +} From 079138201da75a40c5d06a7d5f7ce7a46512f42a Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 5 Jan 2017 00:25:55 +0800 Subject: [PATCH 079/167] cxx: implement static initialization guards --- components/cxx/component.mk | 3 + components/cxx/cxx_guards.cpp | 216 +++++++++++++++++++++++++++++++ components/cxx/test/component.mk | 1 + components/cxx/test/test_cxx.cpp | 203 +++++++++++++++++++++++++++++ 4 files changed, 423 insertions(+) create mode 100644 components/cxx/component.mk create mode 100644 components/cxx/cxx_guards.cpp create mode 100644 components/cxx/test/component.mk create mode 100644 components/cxx/test/test_cxx.cpp diff --git a/components/cxx/component.mk b/components/cxx/component.mk new file mode 100644 index 0000000000..3ce43fc328 --- /dev/null +++ b/components/cxx/component.mk @@ -0,0 +1,3 @@ +# Mark __cxa_guard_dummy as undefined so that implementation of static guards +# is taken from cxx_guards.o instead of libstdc++.a +COMPONENT_ADD_LDFLAGS := -l$(COMPONENT_NAME) -u __cxa_guard_dummy diff --git a/components/cxx/cxx_guards.cpp b/components/cxx/cxx_guards.cpp new file mode 100644 index 0000000000..288ff3ea03 --- /dev/null +++ b/components/cxx/cxx_guards.cpp @@ -0,0 +1,216 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" + +using __cxxabiv1::__guard; + +static SemaphoreHandle_t s_static_init_mutex = NULL; //!< lock used for the critical section +static SemaphoreHandle_t s_static_init_wait_sem = NULL; //!< counting semaphore used by the waiting tasks +static portMUX_TYPE s_init_spinlock = portMUX_INITIALIZER_UNLOCKED; //!< spinlock used to guard initialization of the above two primitives +static size_t s_static_init_waiting_count = 0; //!< number of tasks which are waiting for static init guards +#ifndef _NDEBUG +static size_t s_static_init_max_waiting_count = 0; //!< maximum ever value of the above; can be inspected using GDB for debugging purposes +#endif + + +/** + * Layout of the guard object (defined by the ABI). + * + * Compiler will check lower byte before calling guard functions. + */ +typedef struct { + uint8_t ready; //!< nonzero if initialization is done + uint8_t pending; //!< nonzero if initialization is in progress +} guard_t; + +static void static_init_prepare() +{ + portENTER_CRITICAL(&s_init_spinlock); + if (s_static_init_mutex == NULL) { + s_static_init_mutex = xSemaphoreCreateMutex(); + s_static_init_wait_sem = xSemaphoreCreateCounting(INT_MAX, 0); + if (s_static_init_mutex == NULL || s_static_init_wait_sem == NULL) { + // no way to bail out of static initialization without these + abort(); + } + } + portEXIT_CRITICAL(&s_init_spinlock); +} + +/** + * Use s_static_init_wait_sem to wait until guard->pending == 0. + * Preconditions: + * - s_static_init_mutex taken + * - guard.pending == 1 + * Postconditions: + * - s_static_init_mutex taken + * - guard.pending == 0 + */ +static void wait_for_guard_obj(guard_t* g) +{ + s_static_init_waiting_count++; +#ifndef _NDEBUG + s_static_init_max_waiting_count = std::max(s_static_init_waiting_count, + s_static_init_max_waiting_count); +#endif + + do { + auto result = xSemaphoreGive(s_static_init_mutex); + assert(result); + /* Task may be preempted here, but this isn't a problem, + * as the semaphore will be given exactly the s_static_init_waiting_count + * number of times; eventually the current task will execute next statement, + * which will immediately succeed. + */ + result = xSemaphoreTake(s_static_init_wait_sem, portMAX_DELAY); + assert(result); + /* At this point the semaphore was given, so all waiting tasks have woken up. + * We take s_static_init_mutex before accessing the state of the guard + * object again. + */ + result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY); + assert(result); + /* Semaphore may have been given because some other guard object became ready. + * Check the guard object we need and wait again if it is still pending. + */ + } while(g->pending); + s_static_init_waiting_count--; +} + +/** + * Unblock tasks waiting for static initialization to complete. + * Preconditions: + * - s_static_init_mutex taken + * Postconditions: + * - s_static_init_mutex taken + */ +static void signal_waiting_tasks() +{ + auto count = s_static_init_waiting_count; + while (count--) { + xSemaphoreGive(s_static_init_wait_sem); + } +} + +extern "C" int __cxa_guard_acquire(__guard* pg) +{ + guard_t* g = reinterpret_cast(pg); + const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED; + if (!scheduler_started) { + if (g->pending) { + /* Before the scheduler has started, there we don't support simultaneous + * static initialization. This may be implemented using a spinlock and a + * s32c1i instruction, though. + */ + abort(); + } + } else { + if (s_static_init_mutex == NULL) { + static_init_prepare(); + } + + /* We don't need to use double-checked locking pattern here, as the compiler + * must generate code to check if the first byte of *pg is non-zero, before + * calling __cxa_guard_acquire. + */ + auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY); + assert(result); + if (g->pending) { + /* Another task is doing initialization at the moment; wait until it calls + * __cxa_guard_release or __cxa_guard_abort + */ + wait_for_guard_obj(g); + /* At this point there are two scenarios: + * - the task which was doing static initialization has called __cxa_guard_release, + * which means that g->ready is set. We need to return 0. + * - the task which was doing static initialization has called __cxa_guard_abort, + * which means that g->ready is not set; we should acquire the guard and return 1, + * same as for the case if we didn't have to wait. + * Note: actually the second scenario is unlikely to occur in the current + * configuration because exception support is disabled. + */ + } + } + int ret; + if (g->ready) { + /* Static initialization has been done by another task; nothing to do here */ + ret = 0; + } else { + /* Current task can start doing static initialization */ + g->pending = 1; + ret = 1; + } + if (scheduler_started) { + auto result = xSemaphoreGive(s_static_init_mutex); + assert(result); + } + return ret; +} + +extern "C" void __cxa_guard_release(__guard* pg) +{ + guard_t* g = reinterpret_cast(pg); + const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED; + if (scheduler_started) { + auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY); + assert(result); + } + assert(g->pending && "tried to release a guard which wasn't acquired"); + g->pending = 0; + /* Initialization was successful */ + g->ready = 1; + if (scheduler_started) { + /* Unblock the tasks waiting for static initialization to complete */ + signal_waiting_tasks(); + auto result = xSemaphoreGive(s_static_init_mutex); + assert(result); + } +} + +extern "C" void __cxa_guard_abort(__guard* pg) +{ + guard_t* g = reinterpret_cast(pg); + const auto scheduler_started = xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED; + if (scheduler_started) { + auto result = xSemaphoreTake(s_static_init_mutex, portMAX_DELAY); + assert(result); + } + assert(!g->ready && "tried to abort a guard which is ready"); + assert(g->pending && "tried to release a guard which is not acquired"); + g->pending = 0; + if (scheduler_started) { + /* Unblock the tasks waiting for static initialization to complete */ + signal_waiting_tasks(); + auto result = xSemaphoreGive(s_static_init_mutex); + assert(result); + } +} + +/** + * Dummy function used to force linking this file instead of the same one in libstdc++. + * This works via -u __cxa_guard_dummy flag in component.mk + */ +extern "C" void __cxa_guard_dummy() +{ +} diff --git a/components/cxx/test/component.mk b/components/cxx/test/component.mk new file mode 100644 index 0000000000..ce464a212a --- /dev/null +++ b/components/cxx/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/cxx/test/test_cxx.cpp b/components/cxx/test/test_cxx.cpp new file mode 100644 index 0000000000..8b790783ed --- /dev/null +++ b/components/cxx/test/test_cxx.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include "unity.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +static const char* TAG = "cxx"; + +TEST_CASE("can use new and delete", "[cxx]") +{ + int* int_p = new int(10); + delete int_p; + int* int_array = new int[10]; + delete[] int_array; +} + +class Base +{ +public: + virtual ~Base() {} + virtual void foo() = 0; +}; + +class Derived : public Base +{ +public: + virtual void foo() { } +}; + +TEST_CASE("can call virtual functions", "[cxx]") +{ + Derived d; + Base& b = static_cast(d); + b.foo(); +} + +class NonPOD +{ +public: + NonPOD(int a_) : a(a_) { } + int a; +}; + +static int non_pod_test_helper(int new_val) +{ + static NonPOD non_pod(42); + int ret = non_pod.a; + non_pod.a = new_val; + return ret; +} + +TEST_CASE("can use static initializers for non-POD types", "[cxx]") +{ + TEST_ASSERT_EQUAL(42, non_pod_test_helper(1)); + TEST_ASSERT_EQUAL(1, non_pod_test_helper(0)); +} + +TEST_CASE("can call std::function and bind", "[cxx]") +{ + int outer = 1; + std::function fn = [&outer](int x) -> int { + return x + outer; + }; + outer = 5; + TEST_ASSERT_EQUAL(6, fn(1)); + + auto bound = std::bind(fn, outer); + outer = 10; + TEST_ASSERT_EQUAL(15, bound()); +} + +TEST_CASE("can use std::vector", "[cxx]") +{ + std::vector v(10, 1); + v[0] = 42; + TEST_ASSERT_EQUAL(51, std::accumulate(std::begin(v), std::end(v), 0)); +} + +/* + * This test exercises static initialization guards for two objects. + * For each object, 4 tasks are created which attempt to perform static initialization. + * We check that constructor runs only once for each object. + */ + +static SemaphoreHandle_t s_slow_init_sem = NULL; + +template +class SlowInit +{ +public: + SlowInit(int arg) { + ESP_LOGD(TAG, "init obj=%d start, arg=%d\n", obj, arg); + vTaskDelay(300/portTICK_PERIOD_MS); + TEST_ASSERT_EQUAL(-1, mInitBy); + TEST_ASSERT_EQUAL(0, mInitCount); + mInitBy = arg; + ++mInitCount; + ESP_LOGD(TAG, "init obj=%d done\n", obj); + } + + static void task(void* arg) { + int taskId = reinterpret_cast(arg); + ESP_LOGD(TAG, "obj=%d before static init, task=%d\n", obj, taskId); + static SlowInit slowinit(taskId); + ESP_LOGD(TAG, "obj=%d after static init, task=%d\n", obj, taskId); + xSemaphoreGive(s_slow_init_sem); + vTaskDelay(10); + vTaskDelete(NULL); + } +private: + static int mInitBy; + static int mInitCount; +}; + +template<> int SlowInit<1>::mInitBy = -1; +template<> int SlowInit<1>::mInitCount = 0; +template<> int SlowInit<2>::mInitBy = -1; +template<> int SlowInit<2>::mInitCount = 0; + +template +static void start_slow_init_task(int id, int affinity) +{ + xTaskCreatePinnedToCore(&SlowInit::task, "slow_init", 2048, + reinterpret_cast(id), 3, NULL, affinity); +} + +TEST_CASE("static initialization guards work as expected", "[cxx]") +{ + s_slow_init_sem = xSemaphoreCreateCounting(10, 0); + TEST_ASSERT_NOT_NULL(s_slow_init_sem); + // 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); + + // 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); + + // All tasks should + for (int i = 0; i < 8; ++i) { + TEST_ASSERT_TRUE(xSemaphoreTake(s_slow_init_sem, 500/portTICK_PERIOD_MS)); + } + vSemaphoreDelete(s_slow_init_sem); +} + +struct GlobalInitTest +{ + GlobalInitTest() : index(order++) { + } + int index; + static int order; +}; + +int GlobalInitTest::order = 0; + +GlobalInitTest g_init_test1; +GlobalInitTest g_init_test2; +GlobalInitTest g_init_test3; + +TEST_CASE("global initializers run in the correct order", "[cxx]") +{ + TEST_ASSERT_EQUAL(0, g_init_test1.index); + TEST_ASSERT_EQUAL(1, g_init_test2.index); + TEST_ASSERT_EQUAL(2, g_init_test3.index); +} + +struct StaticInitTestBeforeScheduler +{ + StaticInitTestBeforeScheduler() + { + static int first_init_order = getOrder(); + index = first_init_order; + } + + int getOrder() + { + return order++; + } + + int index; + static int order; +}; + +int StaticInitTestBeforeScheduler::order = 1; + +StaticInitTestBeforeScheduler g_static_init_test1; +StaticInitTestBeforeScheduler g_static_init_test2; +StaticInitTestBeforeScheduler g_static_init_test3; + +TEST_CASE("before scheduler has started, static initializers work correctly", "[cxx]") +{ + TEST_ASSERT_EQUAL(1, g_static_init_test1.index); + TEST_ASSERT_EQUAL(1, g_static_init_test2.index); + TEST_ASSERT_EQUAL(1, g_static_init_test3.index); + TEST_ASSERT_EQUAL(2, StaticInitTestBeforeScheduler::order); +} From e4b9563dacf5daee27ba8c6ff21d1b003fb37ae9 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Fri, 6 Jan 2017 17:40:46 +0800 Subject: [PATCH 080/167] component/bt : blufi add version --- components/bt/bluedroid/api/esp_blufi_api.c | 4 ++++ components/bt/bluedroid/api/include/esp_blufi_api.h | 9 +++++++++ components/bt/bluedroid/bta/gatt/bta_gatts_act.c | 2 -- .../bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c | 4 ++++ .../bluedroid/btc/profile/esp/blufi/blufi_protocol.c | 9 +++++++++ .../btc/profile/esp/blufi/include/blufi_int.h | 12 +++++++++--- .../btc/profile/esp/include/btc_blufi_prf.h | 3 +++ docs/api/esp_blufi.rst | 1 + examples/12_blufi/main/blufi_main.c | 2 ++ 9 files changed, 41 insertions(+), 5 deletions(-) diff --git a/components/bt/bluedroid/api/esp_blufi_api.c b/components/bt/bluedroid/api/esp_blufi_api.c index 5493824d2a..094fbfae5f 100644 --- a/components/bt/bluedroid/api/esp_blufi_api.c +++ b/components/bt/bluedroid/api/esp_blufi_api.c @@ -71,4 +71,8 @@ esp_err_t esp_blufi_profile_deinit(void) return (btc_transfer_context(&msg, NULL, 0, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } +uint16_t esp_blufi_get_version(void) +{ + return btc_blufi_get_version(); +} diff --git a/components/bt/bluedroid/api/include/esp_blufi_api.h b/components/bt/bluedroid/api/include/esp_blufi_api.h index 5490fec05a..6da6512163 100644 --- a/components/bt/bluedroid/api/include/esp_blufi_api.h +++ b/components/bt/bluedroid/api/include/esp_blufi_api.h @@ -344,6 +344,15 @@ esp_err_t esp_blufi_profile_deinit(void); */ esp_err_t esp_blufi_send_wifi_conn_report(wifi_mode_t opmode, esp_blufi_sta_conn_state_t sta_conn_state, uint8_t softap_conn_num, esp_blufi_extra_info_t *extra_info); +/** + * + * @brief Get BLUFI profile version + * + * @return Most 8bit significant is Great version, Least 8bit is Sub version + * + */ +uint16_t esp_blufi_get_version(void); + #ifdef __cplusplus } #endif diff --git a/components/bt/bluedroid/bta/gatt/bta_gatts_act.c b/components/bt/bluedroid/bta/gatt/bta_gatts_act.c index aa00bf287d..04bb3ee7cb 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gatts_act.c +++ b/components/bt/bluedroid/bta/gatt/bta_gatts_act.c @@ -233,8 +233,6 @@ void bta_gatts_register(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) if (p_msg->api_reg.p_cback) { (*p_msg->api_reg.p_cback)(BTA_GATTS_REG_EVT, &cb_data); } - - LOG_ERROR("status=%x\n", status); } 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 d480350453..c126273a72 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c @@ -895,3 +895,7 @@ void btc_blufi_set_callbacks(esp_blufi_callbacks_t *callbacks) blufi_env.cbs = callbacks; } +uint16_t btc_blufi_get_version(void) +{ + return BTC_BLUFI_VERSION; +} 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 2f92a43806..d4d617f000 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_protocol.c @@ -90,6 +90,15 @@ void btc_blufi_protocol_handler(uint8_t type, uint8_t *data, int len) btc_transfer_context(&msg, NULL, 0, NULL); break; + case BLUFI_TYPE_CTRL_SUBTYPE_GET_VERSION: { + uint8_t type = BLUFI_BUILD_TYPE(BLUFI_TYPE_DATA, BLUFI_TYPE_DATA_SUBTYPE_REPLY_VERSION); + uint8_t data[2]; + + data[0] = BTC_BLUFI_GREAT_VER; + data[1] = BTC_BLUFI_SUB_VER; + btc_blufi_send_encap(type, &data[0], sizeof(data)); + 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 0de66b8e77..c21b41c4ca 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 @@ -15,6 +15,10 @@ #ifndef __BLUFI_INT_H__ #define __BLUFI_INT_H__ +#define BTC_BLUFI_GREAT_VER 0x01 //Version + Subversion +#define BTC_BLUFI_SUB_VER 0x00 //Version + Subversion +#define BTC_BLUFI_VERSION ((BTC_BLUFI_GREAT_VER<<8)|BTC_BLUFI_SUB_VER) //Version + Subversion + /* service engine control block */ typedef struct { /* Protocol reference */ @@ -85,7 +89,8 @@ typedef struct blufi_frag_hdr blufi_frag_hdr_t; #define BLUFI_TYPE_CTRL_SUBTYPE_CONN_TO_AP 0x03 #define BLUFI_TYPE_CTRL_SUBTYPE_DISCONN_FROM_AP 0x04 #define BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_STATUS 0x05 -#define BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA 0x06 +#define BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA 0x06 +#define BLUFI_TYPE_CTRL_SUBTYPE_GET_VERSION 0x07 #define BLUFI_TYPE_DATA 0x1 #define BLUFI_TYPE_DATA_SUBTYPE_NEG 0x00 @@ -104,7 +109,7 @@ 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_IS_CTRL(type) (BLUFI_GET_TYPE((type)) == BLUFI_TYPE_CTRL) #define BLUFI_TYPE_IS_DATA(type) (BLUFI_GET_TYPE((type)) == BLUFI_TYPE_DATA) @@ -115,7 +120,8 @@ typedef struct blufi_frag_hdr blufi_frag_hdr_t; #define BLUFI_TYPE_IS_CTRL_CONN_WIFI(type) (BLUFI_TYPE_IS_CTRL((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_CTRL_SUBTYPE_CONN_TO_AP) #define BLUFI_TYPE_IS_CTRL_DISCONN_WIFI(type) (BLUFI_TYPE_IS_CTRL((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_CTRL_SUBTYPE_DISCONN_FROM_AP) #define BLUFI_TYPE_IS_CTRL_GET_WIFI_STATUS(type) (BLUFI_TYPE_IS_CTRL((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_CTRL_SUBTYPE_GET_WIFI_STATUS) -#define BLUFI_TYPE_IS_CTRL_DEAUTHENTICATE_STA(type) (BLUFI_TYPE_IS_CTRL((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA) +#define BLUFI_TYPE_IS_CTRL_DEAUTHENTICATE_STA(type) (BLUFI_TYPE_IS_CTRL((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_CTRL_SUBTYPE_DEAUTHENTICATE_STA) +#define BLUFI_TYPE_IS_CTRL_GET_VERSION(type) (BLUFI_TYPE_IS_CTRL((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_CTRL_SUBTYPE_GET_VERSION) #define BLUFI_TYPE_IS_DATA_NEG(type) (BLUFI_TYPE_IS_DATA((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_DATA_SUBTYPE_NEG) #define BLUFI_TYPE_IS_DATA_STA_BSSID(type) (BLUFI_TYPE_IS_DATA((type)) && BLUFI_GET_SUBTYPE((type)) == BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID) 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 eb909e3a2e..1d82d0c9a5 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 @@ -41,4 +41,7 @@ void btc_blufi_set_callbacks(esp_blufi_callbacks_t *callbacks); void btc_blufi_call_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); void btc_blufi_call_deep_free(btc_msg_t *msg); + +uint16_t btc_blufi_get_version(void); + #endif /* __BTC_BLUFI_PRF_H__ */ diff --git a/docs/api/esp_blufi.rst b/docs/api/esp_blufi.rst index 1ffb67bec9..f620edeb83 100644 --- a/docs/api/esp_blufi.rst +++ b/docs/api/esp_blufi.rst @@ -125,4 +125,5 @@ Functions .. doxygenfunction:: esp_blufi_profile_init .. doxygenfunction:: esp_blufi_profile_deinit .. doxygenfunction:: esp_blufi_send_wifi_conn_report +.. doxygenfunction:: esp_blufi_get_version diff --git a/examples/12_blufi/main/blufi_main.c b/examples/12_blufi/main/blufi_main.c index 3f5cd3dcc6..9e2b1da0ab 100644 --- a/examples/12_blufi/main/blufi_main.c +++ b/examples/12_blufi/main/blufi_main.c @@ -328,6 +328,8 @@ void app_main() return; } + BLUFI_INFO("BLUFI VERSION %04x\n", esp_blufi_get_version()); + blufi_security_init(); esp_ble_gap_register_callback(gap_event_handler); From 9c630b759f3b0f5be4abf556fef509592818a3f0 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Fri, 6 Jan 2017 21:19:58 +0800 Subject: [PATCH 081/167] component/bt : add bt device api 1. add get bd_addr function --- components/bt/bluedroid/api/esp_bt_device.c | 22 +++++++++ .../bt/bluedroid/api/include/esp_bt_defs.h | 3 ++ .../bt/bluedroid/api/include/esp_bt_device.h | 38 +++++++++++++++ docs/api/bt_common.rst | 1 + docs/api/esp_bt_device.rst | 48 +++++++++++++++++++ examples/12_blufi/main/blufi_main.c | 3 ++ 6 files changed, 115 insertions(+) create mode 100644 components/bt/bluedroid/api/esp_bt_device.c create mode 100644 components/bt/bluedroid/api/include/esp_bt_device.h create mode 100644 docs/api/esp_bt_device.rst diff --git a/components/bt/bluedroid/api/esp_bt_device.c b/components/bt/bluedroid/api/esp_bt_device.c new file mode 100644 index 0000000000..745e446c23 --- /dev/null +++ b/components/bt/bluedroid/api/esp_bt_device.c @@ -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. + + +#include "esp_bt_device.h" +#include "controller.h" + +const uint8_t *esp_bt_dev_get_address(void) +{ + return controller_get_interface()->get_address()->address; +} diff --git a/components/bt/bluedroid/api/include/esp_bt_defs.h b/components/bt/bluedroid/api/include/esp_bt_defs.h index cba8fbe74f..cdb56a49b8 100644 --- a/components/bt/bluedroid/api/include/esp_bt_defs.h +++ b/components/bt/bluedroid/api/include/esp_bt_defs.h @@ -96,6 +96,9 @@ typedef enum { /// Maximum of the application id #define ESP_APP_ID_MAX 0x7fff +#define ESP_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define ESP_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] + #ifdef __cplusplus } #endif diff --git a/components/bt/bluedroid/api/include/esp_bt_device.h b/components/bt/bluedroid/api/include/esp_bt_device.h new file mode 100644 index 0000000000..51c24f2881 --- /dev/null +++ b/components/bt/bluedroid/api/include/esp_bt_device.h @@ -0,0 +1,38 @@ +// 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 __ESP_BT_DEVICE_H__ +#define __ESP_BT_DEVICE_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * + * @brief Get bluetooth device address. Must use after "esp_bluedroid_enable". + * + * @return bluetooth device address (six bytes) + */ +const uint8_t *esp_bt_dev_get_address(void); + +#ifdef __cplusplus +} +#endif + + +#endif /* __ESP_BT_DEVICE_H__ */ diff --git a/docs/api/bt_common.rst b/docs/api/bt_common.rst index eaa6b8d31f..f2ce6bae9d 100644 --- a/docs/api/bt_common.rst +++ b/docs/api/bt_common.rst @@ -6,3 +6,4 @@ BT COMMON Bluetooth DEFINE Bluetooth MAIN + Bluetooth DEVICE diff --git a/docs/api/esp_bt_device.rst b/docs/api/esp_bt_device.rst new file mode 100644 index 0000000000..c344a5e633 --- /dev/null +++ b/docs/api/esp_bt_device.rst @@ -0,0 +1,48 @@ +BT DEVICE APIs +=============== + +Overview +-------- + +Bluetooth device reference APIs. + +`Instructions`_ + +Application Example +------------------- + +`Instructions`_ + +.. _Instructions: template.html + + +API Reference +------------- + +Header Files +^^^^^^^^^^^^ + + * `bt/bluedroid/api/include/esp_bt_device.h `_ + + +Macros +^^^^^^ + + +Type Definitions +^^^^^^^^^^^^^^^^ + + +Enumerations +^^^^^^^^^^^^ + + +Structures +^^^^^^^^^^ + + +Functions +^^^^^^^^^ + +.. doxygenfunction:: esp_bt_dev_get_address + diff --git a/examples/12_blufi/main/blufi_main.c b/examples/12_blufi/main/blufi_main.c index 9e2b1da0ab..a95db66eb5 100644 --- a/examples/12_blufi/main/blufi_main.c +++ b/examples/12_blufi/main/blufi_main.c @@ -29,6 +29,7 @@ #include "esp_bt_defs.h" #include "esp_gap_ble_api.h" #include "esp_bt_main.h" +#include "esp_bt_device.h" #include "blufi_demo.h" static void blufi_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param); @@ -328,6 +329,8 @@ void app_main() return; } + BLUFI_INFO("BD ADDR: "ESP_BD_ADDR_STR"\n", ESP_BD_ADDR_HEX(esp_bt_dev_get_address())); + BLUFI_INFO("BLUFI VERSION %04x\n", esp_blufi_get_version()); blufi_security_init(); From 6ca2934843d4e0d07d45e481d663d56e34b54716 Mon Sep 17 00:00:00 2001 From: Wu Jian Gang Date: Fri, 6 Jan 2017 18:36:11 +0800 Subject: [PATCH 082/167] example: fix CI error of coap client demo --- .../23_coap_client/main/Kconfig.projbuild | 13 -- examples/23_coap_client/main/coap_client.c | 183 ++++++++++-------- examples/23_coap_client/main/coap_client.h | 43 ---- 3 files changed, 106 insertions(+), 133 deletions(-) delete mode 100644 examples/23_coap_client/main/coap_client.h diff --git a/examples/23_coap_client/main/Kconfig.projbuild b/examples/23_coap_client/main/Kconfig.projbuild index ba3e0d458b..045137a0e9 100644 --- a/examples/23_coap_client/main/Kconfig.projbuild +++ b/examples/23_coap_client/main/Kconfig.projbuild @@ -1,24 +1,11 @@ menu "Example Configuration" -config TARGET_DOMAIN - string "Target Domain" - default "californium.eclipse.org" - help - Target domain for the example to connect to. - config TARGET_DOMAIN_URI string "Target Uri" default "coap://californium.eclipse.org" help Target uri for the example to use. -config TARGET_PORT_NUMBER - int "Target port number" - range 0 65535 - default 5683 - help - Target port number for the example to connect to. - config WIFI_SSID string "WiFi SSID" default "myssid" diff --git a/examples/23_coap_client/main/coap_client.c b/examples/23_coap_client/main/coap_client.c index cef24f4c6e..7c0df1afec 100644 --- a/examples/23_coap_client/main/coap_client.c +++ b/examples/23_coap_client/main/coap_client.c @@ -6,9 +6,10 @@ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ -#include "coap_client.h" #include +#include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -19,12 +20,28 @@ #include "esp_event_loop.h" #include "nvs_flash.h" -#include -#include "coap_config.h" -#include "resource.h" #include "coap.h" +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#define COAP_DEFAULT_TIME_SEC 5 +#define COAP_DEFAULT_TIME_USEC 0 + +/* The examples use uri "coap://californium.eclipse.org" that + you can set via 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define COAP_DEFAULT_DEMO_URI "coap://californium.eclipse.org" +*/ +#define COAP_DEFAULT_DEMO_URI CONFIG_TARGET_DOMAIN_URI static EventGroupHandle_t wifi_event_group; @@ -33,98 +50,111 @@ static EventGroupHandle_t wifi_event_group; to the AP with an IP? */ const static int CONNECTED_BIT = BIT0; -const static char *TAG = "CoAP_demo"; +const static char *TAG = "CoAP_client"; static void message_handler(struct coap_context_t *ctx, const coap_endpoint_t *local_interface, const coap_address_t *remote, coap_pdu_t *sent, coap_pdu_t *received, const coap_tid_t id) { - unsigned char* data = NULL; - size_t data_len; - if (COAP_RESPONSE_CLASS(received->hdr->code) == 2) { - if (coap_get_data(received, &data_len, &data)) { - printf("Received: %s\n", data); - } - } + unsigned char* data = NULL; + size_t data_len; + if (COAP_RESPONSE_CLASS(received->hdr->code) == 2) { + if (coap_get_data(received, &data_len, &data)) { + printf("Received: %s\n", data); + } + } } static void coap_demo_thread(void *p) { - coap_context_t* ctx = NULL; - coap_address_t dst_addr, src_addr; - static coap_uri_t uri; - fd_set readfds; - struct timeval tv; - int flags, result; - coap_pdu_t* request = NULL; - const char* server_uri = COAP_DEFAULT_DEMO_URI; - uint8_t get_method = 1; + struct hostent *hp; + struct ip4_addr *ip4_addr; - coap_address_init(&src_addr); - src_addr.addr.sin.sin_family = AF_INET; - src_addr.addr.sin.sin_port = htons(0); - src_addr.addr.sin.sin_addr.s_addr = INADDR_ANY; + coap_context_t* ctx = NULL; + coap_address_t dst_addr, src_addr; + static coap_uri_t uri; + fd_set readfds; + struct timeval tv; + int flags, result; + coap_pdu_t* request = NULL; + const char* server_uri = COAP_DEFAULT_DEMO_URI; + uint8_t get_method = 1; - ctx = coap_new_context(&src_addr); - if (ctx) { - coap_address_init(&dst_addr); - dst_addr.addr.sin.sin_family = AF_INET; - dst_addr.addr.sin.sin_port = htons(COAP_DEFAULT_PORT); - dst_addr.addr.sin.sin_addr.s_addr = inet_addr(COAP_DEFAULT_DEMO_ADDR); + 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"); - coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri); - request = coap_new_pdu(); - if (request){ - request->hdr->type = COAP_MESSAGE_CON; - request->hdr->id = coap_new_message_id(ctx); - request->hdr->code = get_method; - coap_add_option(request, COAP_OPTION_URI_PATH, uri.path.length, uri.path.s); + if (coap_split_uri((const uint8_t *)server_uri, strlen(server_uri), &uri) == -1) { + ESP_LOGE(TAG, "CoAP server uri error"); + break; + } - coap_register_response_handler(ctx, message_handler); - coap_send_confirmed(ctx, ctx->endpoint, &dst_addr, request); + hp = gethostbyname((const char *)uri.host.s); - flags = fcntl(ctx->sockfd, F_GETFL, 0); - fcntl(ctx->sockfd, F_SETFL, flags|O_NONBLOCK); + if (hp == NULL) { + ESP_LOGE(TAG, "DNS lookup failed"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + continue; + } - tv.tv_usec = COAP_DEFAULT_TIME_USEC; - tv.tv_sec = COAP_DEFAULT_TIME_SEC; + /* Code to print the resolved IP. - for(;;) { - FD_ZERO(&readfds); - FD_CLR( ctx->sockfd, &readfds ); - FD_SET( ctx->sockfd, &readfds ); - result = select( FD_SETSIZE, &readfds, 0, 0, &tv ); - if (result > 0) { - if (FD_ISSET( ctx->sockfd, &readfds )) - coap_read(ctx); - } else if (result < 0) { - break; - } else { - printf("select timeout\n"); - } - } - } - coap_free_context(ctx); - } + Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */ + ip4_addr = (struct ip4_addr *)hp->h_addr; + ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*ip4_addr)); - vTaskDelete(NULL); -} + coap_address_init(&src_addr); + src_addr.addr.sin.sin_family = AF_INET; + src_addr.addr.sin.sin_port = htons(0); + src_addr.addr.sin.sin_addr.s_addr = INADDR_ANY; -static void coap_server_init(void) -{ - int ret = pdPASS; - xTaskHandle coap_handle = NULL; + ctx = coap_new_context(&src_addr); + if (ctx) { + coap_address_init(&dst_addr); + dst_addr.addr.sin.sin_family = AF_INET; + dst_addr.addr.sin.sin_port = htons(COAP_DEFAULT_PORT); + dst_addr.addr.sin.sin_addr.s_addr = ip4_addr->addr; - ret = xTaskCreate(coap_demo_thread, - COAP_DEMO_THREAD_NAME, - COAP_DEMO_THREAD_STACK_WORDS, - NULL, - COAP_DEMO_THREAD_PRORIOTY, - &coap_handle); + request = coap_new_pdu(); + if (request){ + request->hdr->type = COAP_MESSAGE_CON; + request->hdr->id = coap_new_message_id(ctx); + request->hdr->code = get_method; + coap_add_option(request, COAP_OPTION_URI_PATH, uri.path.length, uri.path.s); - if (ret != pdPASS) { - ESP_LOGI(TAG, "create thread %s failed", COAP_DEMO_THREAD_NAME); + coap_register_response_handler(ctx, message_handler); + coap_send_confirmed(ctx, ctx->endpoint, &dst_addr, request); + + flags = fcntl(ctx->sockfd, F_GETFL, 0); + fcntl(ctx->sockfd, F_SETFL, flags|O_NONBLOCK); + + tv.tv_usec = COAP_DEFAULT_TIME_USEC; + tv.tv_sec = COAP_DEFAULT_TIME_SEC; + + for(;;) { + FD_ZERO(&readfds); + FD_CLR( ctx->sockfd, &readfds ); + FD_SET( ctx->sockfd, &readfds ); + result = select( FD_SETSIZE, &readfds, 0, 0, &tv ); + if (result > 0) { + if (FD_ISSET( ctx->sockfd, &readfds )) + coap_read(ctx); + } else if (result < 0) { + break; + } else { + ESP_LOGE(TAG, "select timeout"); + } + } + } + coap_free_context(ctx); + } } + + vTaskDelete(NULL); } static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) @@ -135,7 +165,6 @@ static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) break; case SYSTEM_EVENT_STA_GOT_IP: xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); - coap_server_init(); break; case SYSTEM_EVENT_STA_DISCONNECTED: /* This is a workaround as ESP32 WiFi libs don't currently @@ -165,7 +194,6 @@ static void wifi_conn_init(void) }; ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); - ESP_ERROR_CHECK( esp_wifi_start() ); } @@ -173,4 +201,5 @@ void app_main(void) { nvs_flash_init(); wifi_conn_init(); + xTaskCreate(coap_demo_thread, "coap", 2048, NULL, 5, NULL); } diff --git a/examples/23_coap_client/main/coap_client.h b/examples/23_coap_client/main/coap_client.h deleted file mode 100644 index 50f4ccd16f..0000000000 --- a/examples/23_coap_client/main/coap_client.h +++ /dev/null @@ -1,43 +0,0 @@ -/* CoAP client Example - - 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 _COAP_CLIENT_H_ -#define _COAP_CLIENT_H_ - -#include - -/* The examples use simple WiFi configuration that you can set via - 'make menuconfig'. - - If you'd rather not, just change the below entries to strings with - the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" -*/ -#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID -#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD - -#define COAP_DEMO_THREAD_NAME "CoAP_demo" -#define COAP_DEMO_THREAD_STACK_WORDS 10240 -#define COAP_DEMO_THREAD_PRORIOTY 8 - -#define COAP_DEFAULT_TIME_SEC 5 -#define COAP_DEFAULT_TIME_USEC 0 - -/* The examples use domain of "californium.eclipse.org",uri "coap://californium.eclipse.org" and port number of 5683 that - you can set via 'make menuconfig'. - - If you'd rather not, just change the below entries to strings with - the config you want - ie #define COAP_DEFAULT_DEMO_ADDR "californium.eclipse.org" - , ie #define COAP_DEFAULT_DEMO_URI "coap://californium.eclipse.org" and ie #define COAP_DEFAULT_PORT 5683 -*/ -#define COAP_DEFAULT_PORT CONFIG_TARGET_PORT_NUMBER -#define COAP_DEFAULT_DEMO_ADDR CONFIG_TARGET_DOMAIN -#define COAP_DEFAULT_DEMO_URI CONFIG_TARGET_DOMAIN_URI - -#endif - From 4491dd0e2a2024e5c3fcc3e877aa459079179d87 Mon Sep 17 00:00:00 2001 From: Wu Jian Gang Date: Fri, 6 Jan 2017 18:42:46 +0800 Subject: [PATCH 083/167] example: fix CI error of coap server demo --- .../24_coap_server/main/Kconfig.projbuild | 7 - examples/24_coap_server/main/coap_server.c | 168 +++++++++--------- examples/24_coap_server/main/coap_server.h | 38 ---- 3 files changed, 85 insertions(+), 128 deletions(-) delete mode 100644 examples/24_coap_server/main/coap_server.h diff --git a/examples/24_coap_server/main/Kconfig.projbuild b/examples/24_coap_server/main/Kconfig.projbuild index 4926bfb200..7a9cb97a0e 100644 --- a/examples/24_coap_server/main/Kconfig.projbuild +++ b/examples/24_coap_server/main/Kconfig.projbuild @@ -1,12 +1,5 @@ menu "Example Configuration" -config LOCAL_PORT_NUMBER - int "Local port number" - range 0 65535 - default 5683 - help - Local port number for the example to use. - config WIFI_SSID string "WiFi SSID" default "myssid" diff --git a/examples/24_coap_server/main/coap_server.c b/examples/24_coap_server/main/coap_server.c index 4e066689bb..75e3296f79 100644 --- a/examples/24_coap_server/main/coap_server.c +++ b/examples/24_coap_server/main/coap_server.c @@ -6,9 +6,9 @@ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ -#include "coap_server.h" #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -19,12 +19,20 @@ #include "esp_event_loop.h" #include "nvs_flash.h" -#include -#include "coap_config.h" -#include "resource.h" #include "coap.h" +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#define COAP_DEFAULT_TIME_SEC 5 +#define COAP_DEFAULT_TIME_USEC 0 static EventGroupHandle_t wifi_event_group; @@ -33,32 +41,32 @@ static EventGroupHandle_t wifi_event_group; to the AP with an IP? */ const static int CONNECTED_BIT = BIT0; -const static char *TAG = "CoAP_demo"; +const static char *TAG = "CoAP_server"; static coap_async_state_t *async = NULL; static void send_async_response(coap_context_t *ctx, const coap_endpoint_t *local_if) { - coap_pdu_t *response; - unsigned char buf[3]; - const char* response_data = "Hello World!"; - size_t size = sizeof(coap_hdr_t) + 20; - response = coap_pdu_init(async->flags & COAP_MESSAGE_CON, COAP_RESPONSE_CODE(205), 0, size); - response->hdr->id = coap_new_message_id(ctx); - if (async->tokenlen) - coap_add_token(response, async->tokenlen, async->token); - coap_add_option(response, COAP_OPTION_CONTENT_TYPE, coap_encode_var_bytes(buf, COAP_MEDIATYPE_TEXT_PLAIN), buf); - coap_add_data (response, strlen(response_data), (unsigned char *)response_data); + coap_pdu_t *response; + unsigned char buf[3]; + const char* response_data = "Hello World!"; + size_t size = sizeof(coap_hdr_t) + 20; + response = coap_pdu_init(async->flags & COAP_MESSAGE_CON, COAP_RESPONSE_CODE(205), 0, size); + response->hdr->id = coap_new_message_id(ctx); + if (async->tokenlen) + coap_add_token(response, async->tokenlen, async->token); + coap_add_option(response, COAP_OPTION_CONTENT_TYPE, coap_encode_var_bytes(buf, COAP_MEDIATYPE_TEXT_PLAIN), buf); + coap_add_data (response, strlen(response_data), (unsigned char *)response_data); - if (coap_send(ctx, local_if, &async->peer, response) == COAP_INVALID_TID) { + if (coap_send(ctx, local_if, &async->peer, response) == COAP_INVALID_TID) { - } - coap_delete_pdu(response); - coap_async_state_t *tmp; - coap_remove_async(ctx, async->id, &tmp); - coap_free_async(async); - async = NULL; + } + coap_delete_pdu(response); + coap_async_state_t *tmp; + coap_remove_async(ctx, async->id, &tmp); + coap_free_async(async); + async = NULL; } /* @@ -69,76 +77,70 @@ async_handler(coap_context_t *ctx, struct coap_resource_t *resource, const coap_endpoint_t *local_interface, coap_address_t *peer, coap_pdu_t *request, str *token, coap_pdu_t *response) { - async = coap_register_async(ctx, peer, request, COAP_ASYNC_SEPARATE | COAP_ASYNC_CONFIRM, (void*)"no data"); + async = coap_register_async(ctx, peer, request, COAP_ASYNC_SEPARATE | COAP_ASYNC_CONFIRM, (void*)"no data"); } static void coap_demo_thread(void *p) { - coap_context_t* ctx = NULL; - coap_address_t serv_addr; - coap_resource_t* resource = NULL; - fd_set readfds; - struct timeval tv; - int flags = 0; - /* Prepare the CoAP server socket */ - coap_address_init(&serv_addr); - serv_addr.addr.sin.sin_family = AF_INET; - serv_addr.addr.sin.sin_addr.s_addr = INADDR_ANY; - serv_addr.addr.sin.sin_port = htons(COAP_DEFAULT_PORT); - ctx = coap_new_context(&serv_addr); - if (ctx) { - flags = fcntl(ctx->sockfd, F_GETFL, 0); - fcntl(ctx->sockfd, F_SETFL, flags|O_NONBLOCK); + coap_context_t* ctx = NULL; + coap_address_t serv_addr; + coap_resource_t* resource = NULL; + fd_set readfds; + struct timeval tv; + int flags = 0; - tv.tv_usec = COAP_DEFAULT_TIME_USEC; - tv.tv_sec = COAP_DEFAULT_TIME_SEC; - /* Initialize the resource */ - resource = coap_resource_init((unsigned char *)"Espressif", 9, 0); - if (resource){ - coap_register_handler(resource, COAP_REQUEST_GET, async_handler); - coap_add_resource(ctx, resource); - /*For incoming connections*/ - for (;;) { - FD_ZERO(&readfds); - FD_CLR( ctx->sockfd, &readfds); - FD_SET( ctx->sockfd, &readfds); + 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"); - int result = select( FD_SETSIZE, &readfds, 0, 0, &tv ); - if (result > 0){ - if (FD_ISSET( ctx->sockfd, &readfds )) - coap_read(ctx); - } else if (result < 0){ - break; - } else { - printf("select timeout\n"); - } + /* Prepare the CoAP server socket */ + coap_address_init(&serv_addr); + serv_addr.addr.sin.sin_family = AF_INET; + serv_addr.addr.sin.sin_addr.s_addr = INADDR_ANY; + serv_addr.addr.sin.sin_port = htons(COAP_DEFAULT_PORT); + ctx = coap_new_context(&serv_addr); + if (ctx) { + flags = fcntl(ctx->sockfd, F_GETFL, 0); + fcntl(ctx->sockfd, F_SETFL, flags|O_NONBLOCK); - if (async) - send_async_response(ctx, ctx->endpoint); - } - } + tv.tv_usec = COAP_DEFAULT_TIME_USEC; + tv.tv_sec = COAP_DEFAULT_TIME_SEC; + /* Initialize the resource */ + resource = coap_resource_init((unsigned char *)"Espressif", 9, 0); + if (resource){ + coap_register_handler(resource, COAP_REQUEST_GET, async_handler); + coap_add_resource(ctx, resource); + /*For incoming connections*/ + for (;;) { + FD_ZERO(&readfds); + FD_CLR( ctx->sockfd, &readfds); + FD_SET( ctx->sockfd, &readfds); - coap_free_context(ctx); - } + int result = select( FD_SETSIZE, &readfds, 0, 0, &tv ); + if (result > 0){ + if (FD_ISSET( ctx->sockfd, &readfds )) + coap_read(ctx); + } else if (result < 0){ + break; + } else { + ESP_LOGE(TAG, "select timeout"); + } - vTaskDelete(NULL); -} + if (async) { + send_async_response(ctx, ctx->endpoint); + } + } + } -static void coap_server_init(void) -{ - int ret = pdPASS; - xTaskHandle coap_handle = NULL; - - ret = xTaskCreate(coap_demo_thread, - COAP_DEMO_THREAD_NAME, - COAP_DEMO_THREAD_STACK_WORDS, - NULL, - COAP_DEMO_THREAD_PRORIOTY, - &coap_handle); - - if (ret != pdPASS) { - ESP_LOGI(TAG, "create thread %s failed", COAP_DEMO_THREAD_NAME); + coap_free_context(ctx); + } } + + vTaskDelete(NULL); } static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) @@ -149,7 +151,6 @@ static esp_err_t wifi_event_handler(void *ctx, system_event_t *event) break; case SYSTEM_EVENT_STA_GOT_IP: xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); - coap_server_init(); break; case SYSTEM_EVENT_STA_DISCONNECTED: /* This is a workaround as ESP32 WiFi libs don't currently @@ -179,7 +180,6 @@ static void wifi_conn_init(void) }; ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); - ESP_ERROR_CHECK( esp_wifi_start() ); } @@ -187,4 +187,6 @@ void app_main(void) { nvs_flash_init(); wifi_conn_init(); + + xTaskCreate(coap_demo_thread, "coap", 2048, NULL, 5, NULL); } diff --git a/examples/24_coap_server/main/coap_server.h b/examples/24_coap_server/main/coap_server.h deleted file mode 100644 index e9e8728fc3..0000000000 --- a/examples/24_coap_server/main/coap_server.h +++ /dev/null @@ -1,38 +0,0 @@ -/* CoAP server Example - - 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 _COAP_SERVER_H_ -#define _COAP_SERVER_H_ - -#include - -/* The examples use simple WiFi configuration that you can set via - 'make menuconfig'. - - If you'd rather not, just change the below entries to strings with - the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" -*/ -#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID -#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD - -#define COAP_DEMO_THREAD_NAME "CoAP_demo" -#define COAP_DEMO_THREAD_STACK_WORDS 10240 -#define COAP_DEMO_THREAD_PRORIOTY 8 - -/* The examples use local port number of 5683 that you can set via 'make menuconfig'. - - If you'd rather not, just change the below entries to strings with - the config you want - ie #define OPENSSL_DEMO_TARGET_TCP_PORT 5683 -*/ -#define COAP_DEFAULT_PORT CONFIG_LOCAL_PORT_NUMBER -#define COAP_DEFAULT_TIME_SEC 5 -#define COAP_DEFAULT_TIME_USEC 0 - -#endif - From ed01eb2df16c871ba8e67746953d5602867bc1a0 Mon Sep 17 00:00:00 2001 From: Wu Jian Gang Date: Fri, 6 Jan 2017 18:52:58 +0800 Subject: [PATCH 084/167] example: fix CI error of ota demo --- examples/26_ota/main/ota.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/26_ota/main/ota.c b/examples/26_ota/main/ota.c index 8c8caf2d37..4956129e5d 100644 --- a/examples/26_ota/main/ota.c +++ b/examples/26_ota/main/ota.c @@ -7,6 +7,9 @@ CONDITIONS OF ANY KIND, either express or implied. */ #include +#include +#include + #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" @@ -19,11 +22,6 @@ #include "esp_partition.h" #include "nvs_flash.h" -#include "lwip/err.h" -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/netdb.h" -#include "lwip/dns.h" #define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID #define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD @@ -226,6 +224,10 @@ void __attribute__((noreturn)) task_fatal_error() ESP_LOGE(TAG, "Exiting task due to fatal error..."); close(socket_id); (void)vTaskDelete(NULL); + + while (1) { + ; + } } void main_task(void *pvParameter) From 1b9f477b152c843fe1e6215d5aac3a5966c33674 Mon Sep 17 00:00:00 2001 From: Wu Jian Gang Date: Fri, 6 Jan 2017 21:39:32 +0800 Subject: [PATCH 085/167] example: Reindex ota demo --- examples/{26_ota => 25_ota}/Makefile | 0 examples/{26_ota => 25_ota}/OTA_workflow.png | Bin examples/{26_ota => 25_ota}/README.md | 0 examples/{26_ota => 25_ota}/main/Kconfig.projbuild | 0 examples/{26_ota => 25_ota}/main/component.mk | 0 examples/{26_ota => 25_ota}/main/ota.c | 0 examples/{26_ota => 25_ota}/sdkconfig.defaults | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename examples/{26_ota => 25_ota}/Makefile (100%) rename examples/{26_ota => 25_ota}/OTA_workflow.png (100%) rename examples/{26_ota => 25_ota}/README.md (100%) rename examples/{26_ota => 25_ota}/main/Kconfig.projbuild (100%) rename examples/{26_ota => 25_ota}/main/component.mk (100%) rename examples/{26_ota => 25_ota}/main/ota.c (100%) rename examples/{26_ota => 25_ota}/sdkconfig.defaults (100%) diff --git a/examples/26_ota/Makefile b/examples/25_ota/Makefile similarity index 100% rename from examples/26_ota/Makefile rename to examples/25_ota/Makefile diff --git a/examples/26_ota/OTA_workflow.png b/examples/25_ota/OTA_workflow.png similarity index 100% rename from examples/26_ota/OTA_workflow.png rename to examples/25_ota/OTA_workflow.png diff --git a/examples/26_ota/README.md b/examples/25_ota/README.md similarity index 100% rename from examples/26_ota/README.md rename to examples/25_ota/README.md diff --git a/examples/26_ota/main/Kconfig.projbuild b/examples/25_ota/main/Kconfig.projbuild similarity index 100% rename from examples/26_ota/main/Kconfig.projbuild rename to examples/25_ota/main/Kconfig.projbuild diff --git a/examples/26_ota/main/component.mk b/examples/25_ota/main/component.mk similarity index 100% rename from examples/26_ota/main/component.mk rename to examples/25_ota/main/component.mk diff --git a/examples/26_ota/main/ota.c b/examples/25_ota/main/ota.c similarity index 100% rename from examples/26_ota/main/ota.c rename to examples/25_ota/main/ota.c diff --git a/examples/26_ota/sdkconfig.defaults b/examples/25_ota/sdkconfig.defaults similarity index 100% rename from examples/26_ota/sdkconfig.defaults rename to examples/25_ota/sdkconfig.defaults From a575b9e893ca649008cba5df97b0c0e107c6e08b Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Fri, 6 Jan 2017 19:35:22 +0800 Subject: [PATCH 086/167] esp32: modify ld file to fix a crash issue --- components/esp32/heap_alloc_caps.c | 4 ++-- components/esp32/ld/esp32.common.ld | 2 +- components/esp32/ld/esp32.rom.ld | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/esp32/heap_alloc_caps.c b/components/esp32/heap_alloc_caps.c index 7bb3b40879..a89455835c 100644 --- a/components/esp32/heap_alloc_caps.c +++ b/components/esp32/heap_alloc_caps.c @@ -170,7 +170,7 @@ Warning: These variables are assumed to have the start and end of the data and i area used statically by the program, respectively. These variables are defined in the ld file. */ -extern int _bss_start, _heap_start, _init_start, _iram_text_end; +extern int _data_start, _heap_start, _init_start, _iram_text_end; /* Initialize the heap allocator. We pass it a bunch of region descriptors, but we need to modify those first to accommodate for @@ -183,7 +183,7 @@ void heap_alloc_caps_init() { //Compile-time assert to see if we don't have more tags than is set in heap_regions.h _Static_assert((sizeof(tag_desc)/sizeof(tag_desc[0]))-1 <= HEAPREGIONS_MAX_TAGCOUNT, "More than HEAPREGIONS_MAX_TAGCOUNT tags defined!"); //Disable the bits of memory where this code is loaded. - disable_mem_region(&_bss_start, &_heap_start); //DRAM used by bss/data static variables + disable_mem_region(&_data_start, &_heap_start); //DRAM used by bss/data static variables disable_mem_region(&_init_start, &_iram_text_end); //IRAM used by code disable_mem_region((void*)0x3ffae000, (void*)0x3ffb0000); //knock out ROM data region disable_mem_region((void*)0x40070000, (void*)0x40078000); //CPU0 cache region diff --git a/components/esp32/ld/esp32.common.ld b/components/esp32/ld/esp32.common.ld index c199a41d3d..833eb90969 100644 --- a/components/esp32/ld/esp32.common.ld +++ b/components/esp32/ld/esp32.common.ld @@ -103,7 +103,6 @@ SECTIONS *libesp32.a:panic.o(.rodata .rodata.*) _data_end = ABSOLUTE(.); . = ALIGN(4); - _heap_start = ABSOLUTE(.); } >dram0_0_seg /* Shared RAM */ @@ -127,6 +126,7 @@ SECTIONS *(COMMON) . = ALIGN (8); _bss_end = ABSOLUTE(.); + _heap_start = ABSOLUTE(.); } >dram0_0_seg .flash.rodata : diff --git a/components/esp32/ld/esp32.rom.ld b/components/esp32/ld/esp32.rom.ld index 6241ff840e..fef42c9528 100644 --- a/components/esp32/ld/esp32.rom.ld +++ b/components/esp32/ld/esp32.rom.ld @@ -87,9 +87,9 @@ PROVIDE ( _ctype_ = 0x3ff96354 ); PROVIDE ( __ctype_ptr__ = 0x3ff96350 ); PROVIDE ( __ctzdi2 = 0x4000ca64 ); PROVIDE ( __ctzsi2 = 0x4000c7f0 ); -PROVIDE ( _data_end = 0x4000d5c8 ); +PROVIDE ( _data_end_rom = 0x4000d5c8 ); PROVIDE ( _data_end_btdm_rom = 0x4000d4f8 ); -PROVIDE ( _data_start = 0x4000d4f8 ); +PROVIDE ( _data_start_rom = 0x4000d4f8 ); PROVIDE ( _data_start_btdm_rom = 0x4000d4f4 ); PROVIDE ( _data_start_btdm = 0x3ffae6e0); PROVIDE ( _data_end_btdm = 0x3ffaff10); From 0e7f7a2112c94b937f99638fe4ae8f50bc133719 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Sat, 7 Jan 2017 16:32:03 +0800 Subject: [PATCH 087/167] esp32: fix wifi auth issue Fix wifi auth failed issue by default phy mode to 11N --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index 21e433b827..23d627498d 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 21e433b8277adc1d65894ec0a65c60f78dc84f7c +Subproject commit 23d627498d60e524e5d95d649481bd650aff1aad From 0bfe08578b7a30180e357343d12f2bdfcbb7ff53 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Fri, 6 Jan 2017 17:19:09 +0800 Subject: [PATCH 088/167] Add sdkconfig include to cpu_util.h so CONFIG_ESP32_DEBUG_OCDAWARE actually resolves --- components/esp32/cpu_util.c | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esp32/cpu_util.c b/components/esp32/cpu_util.c index e3b4ef8f16..ecfcab4baf 100644 --- a/components/esp32/cpu_util.c +++ b/components/esp32/cpu_util.c @@ -16,6 +16,7 @@ #include "soc/cpu.h" #include "soc/soc.h" #include "soc/rtc_cntl_reg.h" +#include "sdkconfig.h" void IRAM_ATTR esp_cpu_stall(int cpu_id) { From c1b06bf0a2596bbfbddb4ecf9b0eb275d5e0cc45 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Jan 2017 03:08:24 +0800 Subject: [PATCH 089/167] bootloader: export ets_update_cpu_frequency --- components/bootloader/src/main/component.mk | 5 ++++- components/bootloader/src/main/esp32.bootloader.rom.ld | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 components/bootloader/src/main/esp32.bootloader.rom.ld diff --git a/components/bootloader/src/main/component.mk b/components/bootloader/src/main/component.mk index 9bcc5352a0..73cd9287df 100644 --- a/components/bootloader/src/main/component.mk +++ b/components/bootloader/src/main/component.mk @@ -5,7 +5,10 @@ # we pull in bootloader-specific linker arguments. # -LINKER_SCRIPTS := esp32.bootloader.ld $(IDF_PATH)/components/esp32/ld/esp32.rom.ld +LINKER_SCRIPTS := \ + esp32.bootloader.ld \ + $(IDF_PATH)/components/esp32/ld/esp32.rom.ld \ + esp32.bootloader.rom.ld COMPONENT_ADD_LDFLAGS := -L $(COMPONENT_PATH) -lmain $(addprefix -T ,$(LINKER_SCRIPTS)) diff --git a/components/bootloader/src/main/esp32.bootloader.rom.ld b/components/bootloader/src/main/esp32.bootloader.rom.ld new file mode 100644 index 0000000000..70f83bdf54 --- /dev/null +++ b/components/bootloader/src/main/esp32.bootloader.rom.ld @@ -0,0 +1 @@ +PROVIDE ( ets_update_cpu_frequency = 0x40008550 ); /* Updates g_ticks_per_us on the current CPU only; not on the other core */ From edd924f2735449be266ae5e48f0cbb1ca486067c Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 19 Dec 2016 22:19:47 +0800 Subject: [PATCH 090/167] sdmmc: add peripheral driver and protocol layer --- components/driver/include/driver/sdmmc_defs.h | 303 ++++++++++ components/driver/include/driver/sdmmc_host.h | 165 +++++ .../driver/include/driver/sdmmc_types.h | 140 +++++ components/driver/sdmmc_host.c | 463 ++++++++++++++ components/driver/sdmmc_private.h | 44 ++ components/driver/sdmmc_transaction.c | 372 ++++++++++++ components/esp32/include/esp_err.h | 3 +- components/esp32/include/soc/sdmmc_reg.h | 94 +++ components/esp32/include/soc/sdmmc_struct.h | 371 ++++++++++++ components/esp32/include/soc/soc.h | 2 +- components/esp32/ld/esp32.peripherals.ld | 1 + components/sdmmc/component.mk | 0 components/sdmmc/include/sdmmc_cmd.h | 77 +++ components/sdmmc/sdmmc_cmd.c | 571 ++++++++++++++++++ components/sdmmc/test/component.mk | 1 + components/sdmmc/test/test_sd.c | 95 +++ examples/27_sd_card/Makefile | 9 + examples/27_sd_card/README.md | 81 +++ examples/27_sd_card/main/component.mk | 4 + examples/27_sd_card/main/sd_card.c | 107 ++++ 20 files changed, 2901 insertions(+), 2 deletions(-) create mode 100644 components/driver/include/driver/sdmmc_defs.h create mode 100644 components/driver/include/driver/sdmmc_host.h create mode 100644 components/driver/include/driver/sdmmc_types.h create mode 100644 components/driver/sdmmc_host.c create mode 100644 components/driver/sdmmc_private.h create mode 100644 components/driver/sdmmc_transaction.c create mode 100644 components/esp32/include/soc/sdmmc_reg.h create mode 100644 components/esp32/include/soc/sdmmc_struct.h create mode 100755 components/sdmmc/component.mk create mode 100644 components/sdmmc/include/sdmmc_cmd.h create mode 100644 components/sdmmc/sdmmc_cmd.c create mode 100644 components/sdmmc/test/component.mk create mode 100644 components/sdmmc/test/test_sd.c create mode 100644 examples/27_sd_card/Makefile create mode 100644 examples/27_sd_card/README.md create mode 100644 examples/27_sd_card/main/component.mk create mode 100644 examples/27_sd_card/main/sd_card.c diff --git a/components/driver/include/driver/sdmmc_defs.h b/components/driver/include/driver/sdmmc_defs.h new file mode 100644 index 0000000000..9913bfd175 --- /dev/null +++ b/components/driver/include/driver/sdmmc_defs.h @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2006 Uwe Stuehler + * Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SDMMC_DEFS_H_ +#define _SDMMC_DEFS_H_ + +#include +#include + +/* MMC commands */ /* response type */ +#define MMC_GO_IDLE_STATE 0 /* R0 */ +#define MMC_SEND_OP_COND 1 /* R3 */ +#define MMC_ALL_SEND_CID 2 /* R2 */ +#define MMC_SET_RELATIVE_ADDR 3 /* R1 */ +#define MMC_SWITCH 6 /* R1B */ +#define MMC_SELECT_CARD 7 /* R1 */ +#define MMC_SEND_EXT_CSD 8 /* R1 */ +#define MMC_SEND_CSD 9 /* R2 */ +#define MMC_STOP_TRANSMISSION 12 /* R1B */ +#define MMC_SEND_STATUS 13 /* R1 */ +#define MMC_SET_BLOCKLEN 16 /* R1 */ +#define MMC_READ_BLOCK_SINGLE 17 /* R1 */ +#define MMC_READ_BLOCK_MULTIPLE 18 /* R1 */ +#define MMC_SET_BLOCK_COUNT 23 /* R1 */ +#define MMC_WRITE_BLOCK_SINGLE 24 /* R1 */ +#define MMC_WRITE_BLOCK_MULTIPLE 25 /* R1 */ +#define MMC_APP_CMD 55 /* R1 */ + +/* SD commands */ /* response type */ +#define SD_SEND_RELATIVE_ADDR 3 /* R6 */ +#define SD_SEND_SWITCH_FUNC 6 /* R1 */ +#define SD_SEND_IF_COND 8 /* R7 */ + +/* SD application commands */ /* response type */ +#define SD_APP_SET_BUS_WIDTH 6 /* R1 */ +#define SD_APP_OP_COND 41 /* R3 */ +#define SD_APP_SEND_SCR 51 /* R1 */ + +/* OCR bits */ +#define MMC_OCR_MEM_READY (1<<31) /* memory power-up status bit */ +#define MMC_OCR_ACCESS_MODE_MASK 0x60000000 /* bits 30:29 */ +#define MMC_OCR_SECTOR_MODE (1<<30) +#define MMC_OCR_BYTE_MODE (1<<29) +#define MMC_OCR_3_5V_3_6V (1<<23) +#define MMC_OCR_3_4V_3_5V (1<<22) +#define MMC_OCR_3_3V_3_4V (1<<21) +#define MMC_OCR_3_2V_3_3V (1<<20) +#define MMC_OCR_3_1V_3_2V (1<<19) +#define MMC_OCR_3_0V_3_1V (1<<18) +#define MMC_OCR_2_9V_3_0V (1<<17) +#define MMC_OCR_2_8V_2_9V (1<<16) +#define MMC_OCR_2_7V_2_8V (1<<15) +#define MMC_OCR_2_6V_2_7V (1<<14) +#define MMC_OCR_2_5V_2_6V (1<<13) +#define MMC_OCR_2_4V_2_5V (1<<12) +#define MMC_OCR_2_3V_2_4V (1<<11) +#define MMC_OCR_2_2V_2_3V (1<<10) +#define MMC_OCR_2_1V_2_2V (1<<9) +#define MMC_OCR_2_0V_2_1V (1<<8) +#define MMC_OCR_1_65V_1_95V (1<<7) + +#define SD_OCR_SDHC_CAP (1<<30) +#define SD_OCR_VOL_MASK 0xFF8000 /* bits 23:15 */ + +/* R1 response type bits */ +#define MMC_R1_READY_FOR_DATA (1<<8) /* ready for next transfer */ +#define MMC_R1_APP_CMD (1<<5) /* app. commands supported */ + +/* 48-bit response decoding (32 bits w/o CRC) */ +#define MMC_R1(resp) ((resp)[0]) +#define MMC_R3(resp) ((resp)[0]) +#define SD_R6(resp) ((resp)[0]) +#define MMC_R1_CURRENT_STATE(resp) (((resp)[0] >> 9) & 0xf) + +/* RCA argument and response */ +#define MMC_ARG_RCA(rca) ((rca) << 16) +#define SD_R6_RCA(resp) (SD_R6((resp)) >> 16) + +/* bus width argument */ +#define SD_ARG_BUS_WIDTH_1 0 +#define SD_ARG_BUS_WIDTH_4 2 + +/* EXT_CSD fields */ +#define EXT_CSD_BUS_WIDTH 183 /* WO */ +#define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_REV 192 /* RO */ +#define EXT_CSD_STRUCTURE 194 /* RO */ +#define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_SEC_COUNT 212 /* RO */ + +/* EXT_CSD field definitions */ +#define EXT_CSD_CMD_SET_NORMAL (1U << 0) +#define EXT_CSD_CMD_SET_SECURE (1U << 1) +#define EXT_CSD_CMD_SET_CPSECURE (1U << 2) + +/* EXT_CSD_HS_TIMING */ +#define EXT_CSD_HS_TIMING_BC 0 +#define EXT_CSD_HS_TIMING_HS 1 +#define EXT_CSD_HS_TIMING_HS200 2 +#define EXT_CSD_HS_TIMING_HS400 3 + +/* EXT_CSD_BUS_WIDTH */ +#define EXT_CSD_BUS_WIDTH_1 0 +#define EXT_CSD_BUS_WIDTH_4 1 +#define EXT_CSD_BUS_WIDTH_8 2 +#define EXT_CSD_BUS_WIDTH_4_DDR 5 +#define EXT_CSD_BUS_WIDTH_8_DDR 6 + +/* EXT_CSD_CARD_TYPE */ +/* The only currently valid values for this field are 0x01, 0x03, 0x07, + * 0x0B and 0x0F. */ +#define EXT_CSD_CARD_TYPE_F_26M (1 << 0) +#define EXT_CSD_CARD_TYPE_F_52M (1 << 1) +#define EXT_CSD_CARD_TYPE_F_52M_1_8V (1 << 2) +#define EXT_CSD_CARD_TYPE_F_52M_1_2V (1 << 3) +#define EXT_CSD_CARD_TYPE_26M 0x01 +#define EXT_CSD_CARD_TYPE_52M 0x03 +#define EXT_CSD_CARD_TYPE_52M_V18 0x07 +#define EXT_CSD_CARD_TYPE_52M_V12 0x0b +#define EXT_CSD_CARD_TYPE_52M_V12_18 0x0f + +/* MMC_SWITCH access mode */ +#define MMC_SWITCH_MODE_CMD_SET 0x00 /* Change the command set */ +#define MMC_SWITCH_MODE_SET_BITS 0x01 /* Set bits in value */ +#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits in value */ +#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ + +/* MMC R2 response (CSD) */ +#define MMC_CSD_CSDVER(resp) MMC_RSP_BITS((resp), 126, 2) +#define MMC_CSD_CSDVER_1_0 1 +#define MMC_CSD_CSDVER_2_0 2 +#define MMC_CSD_CSDVER_EXT_CSD 3 +#define MMC_CSD_MMCVER(resp) MMC_RSP_BITS((resp), 122, 4) +#define MMC_CSD_MMCVER_1_0 0 /* MMC 1.0 - 1.2 */ +#define MMC_CSD_MMCVER_1_4 1 /* MMC 1.4 */ +#define MMC_CSD_MMCVER_2_0 2 /* MMC 2.0 - 2.2 */ +#define MMC_CSD_MMCVER_3_1 3 /* MMC 3.1 - 3.3 */ +#define MMC_CSD_MMCVER_4_0 4 /* MMC 4 */ +#define MMC_CSD_READ_BL_LEN(resp) MMC_RSP_BITS((resp), 80, 4) +#define MMC_CSD_C_SIZE(resp) MMC_RSP_BITS((resp), 62, 12) +#define MMC_CSD_CAPACITY(resp) ((MMC_CSD_C_SIZE((resp))+1) << \ + (MMC_CSD_C_SIZE_MULT((resp))+2)) +#define MMC_CSD_C_SIZE_MULT(resp) MMC_RSP_BITS((resp), 47, 3) + +/* MMC v1 R2 response (CID) */ +#define MMC_CID_MID_V1(resp) MMC_RSP_BITS((resp), 104, 24) +#define MMC_CID_PNM_V1_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = MMC_RSP_BITS((resp), 56, 8); \ + (pnm)[6] = MMC_RSP_BITS((resp), 48, 8); \ + (pnm)[7] = '\0'; \ + } while (0) +#define MMC_CID_REV_V1(resp) MMC_RSP_BITS((resp), 40, 8) +#define MMC_CID_PSN_V1(resp) MMC_RSP_BITS((resp), 16, 24) +#define MMC_CID_MDT_V1(resp) MMC_RSP_BITS((resp), 8, 8) + +/* MMC v2 R2 response (CID) */ +#define MMC_CID_MID_V2(resp) MMC_RSP_BITS((resp), 120, 8) +#define MMC_CID_OID_V2(resp) MMC_RSP_BITS((resp), 104, 16) +#define MMC_CID_PNM_V2_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = MMC_RSP_BITS((resp), 56, 8); \ + (pnm)[6] = '\0'; \ + } while (0) +#define MMC_CID_PSN_V2(resp) MMC_RSP_BITS((resp), 16, 32) + +/* SD R2 response (CSD) */ +#define SD_CSD_CSDVER(resp) MMC_RSP_BITS((resp), 126, 2) +#define SD_CSD_CSDVER_1_0 0 +#define SD_CSD_CSDVER_2_0 1 +#define SD_CSD_TAAC(resp) MMC_RSP_BITS((resp), 112, 8) +#define SD_CSD_TAAC_1_5_MSEC 0x26 +#define SD_CSD_NSAC(resp) MMC_RSP_BITS((resp), 104, 8) +#define SD_CSD_SPEED(resp) MMC_RSP_BITS((resp), 96, 8) +#define SD_CSD_SPEED_25_MHZ 0x32 +#define SD_CSD_SPEED_50_MHZ 0x5a +#define SD_CSD_CCC(resp) MMC_RSP_BITS((resp), 84, 12) +#define SD_CSD_CCC_BASIC (1 << 0) /* basic */ +#define SD_CSD_CCC_BR (1 << 2) /* block read */ +#define SD_CSD_CCC_BW (1 << 4) /* block write */ +#define SD_CSD_CCC_ERASE (1 << 5) /* erase */ +#define SD_CSD_CCC_WP (1 << 6) /* write protection */ +#define SD_CSD_CCC_LC (1 << 7) /* lock card */ +#define SD_CSD_CCC_AS (1 << 8) /*application specific*/ +#define SD_CSD_CCC_IOM (1 << 9) /* I/O mode */ +#define SD_CSD_CCC_SWITCH (1 << 10) /* switch */ +#define SD_CSD_READ_BL_LEN(resp) MMC_RSP_BITS((resp), 80, 4) +#define SD_CSD_READ_BL_PARTIAL(resp) MMC_RSP_BITS((resp), 79, 1) +#define SD_CSD_WRITE_BLK_MISALIGN(resp) MMC_RSP_BITS((resp), 78, 1) +#define SD_CSD_READ_BLK_MISALIGN(resp) MMC_RSP_BITS((resp), 77, 1) +#define SD_CSD_DSR_IMP(resp) MMC_RSP_BITS((resp), 76, 1) +#define SD_CSD_C_SIZE(resp) MMC_RSP_BITS((resp), 62, 12) +#define SD_CSD_CAPACITY(resp) ((SD_CSD_C_SIZE((resp))+1) << \ + (SD_CSD_C_SIZE_MULT((resp))+2)) +#define SD_CSD_V2_C_SIZE(resp) MMC_RSP_BITS((resp), 48, 22) +#define SD_CSD_V2_CAPACITY(resp) ((SD_CSD_V2_C_SIZE((resp))+1) << 10) +#define SD_CSD_V2_BL_LEN 0x9 /* 512 */ +#define SD_CSD_VDD_R_CURR_MIN(resp) MMC_RSP_BITS((resp), 59, 3) +#define SD_CSD_VDD_R_CURR_MAX(resp) MMC_RSP_BITS((resp), 56, 3) +#define SD_CSD_VDD_W_CURR_MIN(resp) MMC_RSP_BITS((resp), 53, 3) +#define SD_CSD_VDD_W_CURR_MAX(resp) MMC_RSP_BITS((resp), 50, 3) +#define SD_CSD_VDD_RW_CURR_100mA 0x7 +#define SD_CSD_VDD_RW_CURR_80mA 0x6 +#define SD_CSD_C_SIZE_MULT(resp) MMC_RSP_BITS((resp), 47, 3) +#define SD_CSD_ERASE_BLK_EN(resp) MMC_RSP_BITS((resp), 46, 1) +#define SD_CSD_SECTOR_SIZE(resp) MMC_RSP_BITS((resp), 39, 7) /* +1 */ +#define SD_CSD_WP_GRP_SIZE(resp) MMC_RSP_BITS((resp), 32, 7) /* +1 */ +#define SD_CSD_WP_GRP_ENABLE(resp) MMC_RSP_BITS((resp), 31, 1) +#define SD_CSD_R2W_FACTOR(resp) MMC_RSP_BITS((resp), 26, 3) +#define SD_CSD_WRITE_BL_LEN(resp) MMC_RSP_BITS((resp), 22, 4) +#define SD_CSD_RW_BL_LEN_2G 0xa +#define SD_CSD_RW_BL_LEN_1G 0x9 +#define SD_CSD_WRITE_BL_PARTIAL(resp) MMC_RSP_BITS((resp), 21, 1) +#define SD_CSD_FILE_FORMAT_GRP(resp) MMC_RSP_BITS((resp), 15, 1) +#define SD_CSD_COPY(resp) MMC_RSP_BITS((resp), 14, 1) +#define SD_CSD_PERM_WRITE_PROTECT(resp) MMC_RSP_BITS((resp), 13, 1) +#define SD_CSD_TMP_WRITE_PROTECT(resp) MMC_RSP_BITS((resp), 12, 1) +#define SD_CSD_FILE_FORMAT(resp) MMC_RSP_BITS((resp), 10, 2) + +/* SD R2 response (CID) */ +#define SD_CID_MID(resp) MMC_RSP_BITS((resp), 120, 8) +#define SD_CID_OID(resp) MMC_RSP_BITS((resp), 104, 16) +#define SD_CID_PNM_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = '\0'; \ + } while (0) +#define SD_CID_REV(resp) MMC_RSP_BITS((resp), 56, 8) +#define SD_CID_PSN(resp) MMC_RSP_BITS((resp), 24, 32) +#define SD_CID_MDT(resp) MMC_RSP_BITS((resp), 8, 12) + +/* SCR (SD Configuration Register) */ +#define SCR_STRUCTURE(scr) MMC_RSP_BITS((scr), 60, 4) +#define SCR_STRUCTURE_VER_1_0 0 /* Version 1.0 */ +#define SCR_SD_SPEC(scr) MMC_RSP_BITS((scr), 56, 4) +#define SCR_SD_SPEC_VER_1_0 0 /* Version 1.0 and 1.01 */ +#define SCR_SD_SPEC_VER_1_10 1 /* Version 1.10 */ +#define SCR_SD_SPEC_VER_2 2 /* Version 2.00 or Version 3.0X */ +#define SCR_DATA_STAT_AFTER_ERASE(scr) MMC_RSP_BITS((scr), 55, 1) +#define SCR_SD_SECURITY(scr) MMC_RSP_BITS((scr), 52, 3) +#define SCR_SD_SECURITY_NONE 0 /* no security */ +#define SCR_SD_SECURITY_1_0 1 /* security protocol 1.0 */ +#define SCR_SD_SECURITY_1_0_2 2 /* security protocol 1.0 */ +#define SCR_SD_BUS_WIDTHS(scr) MMC_RSP_BITS((scr), 48, 4) +#define SCR_SD_BUS_WIDTHS_1BIT (1 << 0) /* 1bit (DAT0) */ +#define SCR_SD_BUS_WIDTHS_4BIT (1 << 2) /* 4bit (DAT0-3) */ +#define SCR_SD_SPEC3(scr) MMC_RSP_BITS((scr), 47, 1) +#define SCR_EX_SECURITY(scr) MMC_RSP_BITS((scr), 43, 4) +#define SCR_SD_SPEC4(scr) MMC_RSP_BITS((scr), 42, 1) +#define SCR_RESERVED(scr) MMC_RSP_BITS((scr), 34, 8) +#define SCR_CMD_SUPPORT_CMD23(scr) MMC_RSP_BITS((scr), 33, 1) +#define SCR_CMD_SUPPORT_CMD20(scr) MMC_RSP_BITS((scr), 32, 1) +#define SCR_RESERVED2(scr) MMC_RSP_BITS((scr), 0, 32) + +/* Status of Switch Function */ +#define SFUNC_STATUS_GROUP(status, group) \ + (__bitfield((uint32_t *)(status), 400 + (group - 1) * 16, 16)) + +#define SD_ACCESS_MODE_SDR12 0 +#define SD_ACCESS_MODE_SDR25 1 +#define SD_ACCESS_MODE_SDR50 2 +#define SD_ACCESS_MODE_SDR104 3 +#define SD_ACCESS_MODE_DDR50 4 + +static inline uint32_t MMC_RSP_BITS(uint32_t *src, int start, int len) +{ + uint32_t mask = (len % 32 == 0) ? UINT_MAX : UINT_MAX >> (32 - (len % 32)); + size_t word = 3 - start / 32; + size_t shift = start % 32; + uint32_t right = src[word] >> shift; + uint32_t left = (len + shift <= 32) ? 0 : src[word - 1] << ((32 - shift) % 32); + return (left | right) & mask; +} + +#endif //_SDMMC_DEFS_H_ diff --git a/components/driver/include/driver/sdmmc_host.h b/components/driver/include/driver/sdmmc_host.h new file mode 100644 index 0000000000..0f56c266a8 --- /dev/null +++ b/components/driver/include/driver/sdmmc_host.h @@ -0,0 +1,165 @@ +// 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. + +#pragma once + +#include +#include +#include "esp_err.h" +#include "sdmmc_types.h" +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDMMC_HOST_SLOT_0 0 ///< SDMMC slot 0 +#define SDMMC_HOST_SLOT_1 1 ///< SDMMC slot 1 + +/** + * @brief Default sdmmc_host_t structure initializer for SDMMC peripheral + * + * Uses SDMMC peripheral, with 4-bit mode enabled, and max frequency set to 20MHz + */ +#define SDMMC_HOST_DEFAULT() {\ + .flags = SDMMC_HOST_FLAG_4BIT, \ + .slot = SDMMC_HOST_SLOT_1, \ + .max_freq_khz = SDMMC_FREQ_DEFAULT, \ + .io_voltage = 3.3f, \ + .init = &sdmmc_host_init, \ + .set_bus_width = &sdmmc_host_set_bus_width, \ + .set_card_clk = &sdmmc_host_set_card_clk, \ + .do_transaction = &sdmmc_host_do_transaction, \ + .deinit = &sdmmc_host_deinit, \ +} + +/** + * Extra configuration for SDMMC peripheral slot + */ +typedef struct { + gpio_num_t gpio_cd; ///< GPIO number of card detect signal + gpio_num_t gpio_wp; ///< GPIO number of write protect signal +} sdmmc_slot_config_t; + +#define SDMMC_SLOT_NO_CD ((gpio_num_t) -1) ///< indicates that card detect line is not used +#define SDMMC_SLOT_NO_WP ((gpio_num_t) -1) ///< indicates that write protect line is not used + +/** + * Macro defining default configuration of SDMMC host slot + */ +#define SDMMC_SLOT_CONFIG_DEFAULT() {\ + .gpio_cd = SDMMC_SLOT_NO_CD, \ + .gpio_wp = SDMMC_SLOT_NO_WP, \ +} + +/** + * @brief Initialize SDMMC host peripheral + * + * @note This function is not thread safe + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if sdmmc_host_init was already called + * - ESP_ERR_NO_MEM if memory can not be allocated + */ +esp_err_t sdmmc_host_init(); + +/** + * @brief Initialize given slot of SDMMC peripheral + * + * On the ESP32, SDMMC peripheral has two slots: + * - Slot 0: 8-bit wide, maps to HS1_* signals in PIN MUX + * - Slot 1: 4-bit wide, maps to HS2_* signals in PIN MUX + * + * Card detect and write protect signals can be routed to + * arbitrary GPIOs using GPIO matrix. + * + * @note This function is not thread safe + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param slot_config additional configuration for the slot + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if host has not been initialized using sdmmc_host_init + */ +esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config); + +/** + * @brief Select bus width to be used for data transfer + * + * SD/MMC card must be initialized prior to this command, and a command to set + * bus width has to be sent to the card (e.g. SD_APP_SET_BUS_WIDTH) + * + * @note This function is not thread safe + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param width bus width (1, 4, or 8 for slot 0; 1 or 4 for slot 1) + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if slot number or width is not valid + */ +esp_err_t sdmmc_host_set_bus_width(int slot, size_t width); + +/** + * @brief Set card clock frequency + * + * Currently only integer fractions of 40MHz clock can be used. + * For High Speed cards, 40MHz can be used. + * For Default Speed cards, 20MHz can be used. + * + * @note This function is not thread safe + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param freq_khz card clock frequency, in kHz + * @return + * - ESP_OK on success + * - other error codes may be returned in the future + */ +esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz); + +/** + * @brief Send command to the card and get response + * + * This function returns when command is sent and response is received, + * or data is transferred, or timeout occurs. + * + * @note This function is not thread safe w.r.t. init/deinit functions, + * and bus width/clock speed configuration functions. Multiple tasks + * can call sdmmc_host_do_transaction as long as other sdmmc_host_* + * functions are not called. + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param cmdinfo pointer to structure describing command and data to transfer + * @return + * - ESP_OK on success + * - ESP_ERR_TIMEOUT if response or data transfer has timed out + * - ESP_ERR_INVALID_CRC if response or data transfer CRC check has failed + * - ESP_ERR_INVALID_RESPONSE if the card has sent an invalid response + */ +esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo); + +/** + * @brief Disable SDMMC host and release allocated resources + * + * @note This function is not thread safe + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if sdmmc_host_init function has not been called + */ +esp_err_t sdmmc_host_deinit(); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/sdmmc_types.h b/components/driver/include/driver/sdmmc_types.h new file mode 100644 index 0000000000..dfbd2439c6 --- /dev/null +++ b/components/driver/include/driver/sdmmc_types.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2006 Uwe Stuehler + * Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SDMMC_TYPES_H_ +#define _SDMMC_TYPES_H_ + +#include +#include +#include "esp_err.h" + +/** + * Decoded values from SD card Card Specific Data register + */ +typedef struct { + int csd_ver; /*!< CSD structure format */ + int mmc_ver; /*!< MMC version (for CID format) */ + int capacity; /*!< total number of sectors */ + int sector_size; /*!< sector size in bytes */ + int read_block_len; /*!< block length for reads */ + int card_command_class; /*!< Card Command Class for SD */ + int tr_speed; /*!< Max transfer speed */ +} sdmmc_csd_t; + +/** + * Decoded values from SD card Card IDentification register + */ +typedef struct { + int mfg_id; /*!< manufacturer identification number */ + int oem_id; /*!< OEM/product identification number */ + char name[8]; /*!< product name (MMC v1 has the longest) */ + int revision; /*!< product revision */ + int serial; /*!< product serial number */ + int date; /*!< manufacturing date */ +} sdmmc_cid_t; + +/** + * Decoded values from SD Configuration Register + */ +typedef struct { + int sd_spec; /*!< SD Physical layer specification version, reported by card */ + int bus_width; /*!< bus widths supported by card: BIT(0) — 1-bit bus, BIT(2) — 4-bit bus */ +} sdmmc_scr_t; + +/** + * SD/MMC command response buffer + */ +typedef uint32_t sdmmc_response_t[4]; + +/** + * SD/MMC command information + */ +typedef struct { + uint32_t opcode; /*!< SD or MMC command index */ + uint32_t arg; /*!< SD/MMC command argument */ + sdmmc_response_t response; /*!< response buffer */ + void* data; /*!< buffer to send or read into */ + size_t datalen; /*!< length of data buffer */ + size_t blklen; /*!< block length */ + int flags; /*!< see below */ +#define SCF_ITSDONE 0x0001 /*!< command is complete */ +#define SCF_CMD(flags) ((flags) & 0x00f0) +#define SCF_CMD_AC 0x0000 +#define SCF_CMD_ADTC 0x0010 +#define SCF_CMD_BC 0x0020 +#define SCF_CMD_BCR 0x0030 +#define SCF_CMD_READ 0x0040 /*!< read command (data expected) */ +#define SCF_RSP_BSY 0x0100 +#define SCF_RSP_136 0x0200 +#define SCF_RSP_CRC 0x0400 +#define SCF_RSP_IDX 0x0800 +#define SCF_RSP_PRESENT 0x1000 +/* response types */ +#define SCF_RSP_R0 0 /*!< none */ +#define SCF_RSP_R1 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) +#define SCF_RSP_R1B (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX|SCF_RSP_BSY) +#define SCF_RSP_R2 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_136) +#define SCF_RSP_R3 (SCF_RSP_PRESENT) +#define SCF_RSP_R4 (SCF_RSP_PRESENT) +#define SCF_RSP_R5 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) +#define SCF_RSP_R5B (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX|SCF_RSP_BSY) +#define SCF_RSP_R6 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) +#define SCF_RSP_R7 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) + esp_err_t error; /*!< error returned from transfer */ +} sdmmc_command_t; + +/** + * SD/MMC Host description + * + * This structure defines properties of SD/MMC host and functions + * of SD/MMC host which can be used by upper layers. + */ +typedef struct { + uint32_t flags; /*!< flags defining host properties */ +#define SDMMC_HOST_FLAG_1BIT BIT(0) /*!< host supports 1-line SD and MMC protocol */ +#define SDMMC_HOST_FLAG_4BIT BIT(1) /*!< host supports 4-line SD and MMC protocol */ +#define SDMMC_HOST_FLAG_8BIT BIT(2) /*!< host supports 8-line MMC protocol */ +#define SDMMC_HOST_FLAG_SPI BIT(3) /*!< host supports SPI protocol */ + int slot; /*!< slot number, to be passed to host functions */ + int max_freq_khz; /*!< max frequency supported by the host */ +#define SDMMC_FREQ_DEFAULT 20000 /*!< SD/MMC Default speed (limited by clock divider) */ +#define SDMMC_FREQ_HIGHSPEED 40000 /*!< SD High speed (limited by clock divider) */ +#define SDMMC_FREQ_PROBING 4000 /*!< SD/MMC probing speed */ + 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 */ + 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 */ +} sdmmc_host_t; + +/** + * SD/MMC card information structure + */ +typedef struct { + sdmmc_host_t host; /*!< Host with which the card is associated */ + uint32_t ocr; /*!< OCR (Operation Conditions Register) value */ + sdmmc_cid_t cid; /*!< decoded CID (Card IDentification) register value */ + sdmmc_csd_t csd; /*!< decoded CSD (Card-Specific Data) register value */ + sdmmc_scr_t scr; /*!< decoded SCR (SD card Configuration Register) value */ + uint16_t rca; /*!< RCA (Relative Card Address) */ +} sdmmc_card_t; + + + + +#endif // _SDMMC_TYPES_H_ diff --git a/components/driver/sdmmc_host.c b/components/driver/sdmmc_host.c new file mode 100644 index 0000000000..400c85b889 --- /dev/null +++ b/components/driver/sdmmc_host.c @@ -0,0 +1,463 @@ +// 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 +#include +#include +#include "esp_log.h" +#include "esp_intr_alloc.h" +#include "soc/sdmmc_struct.h" +#include "soc/sdmmc_reg.h" +#include "soc/io_mux_reg.h" +#include "soc/gpio_sig_map.h" +#include "rom/gpio.h" +#include "driver/gpio.h" +#include "driver/sdmmc_host.h" +#include "sdmmc_private.h" + +#define SDMMC_EVENT_QUEUE_LENGTH 32 + +typedef struct { + uint32_t clk; + uint32_t cmd; + uint32_t d0; + uint32_t d1; + uint32_t d2; + uint32_t d3; + uint32_t d4; + uint32_t d5; + uint32_t d6; + uint32_t d7; + uint8_t card_detect; + uint8_t write_protect; + uint8_t width; +} sdmmc_slot_info_t; + + +static void sdmmc_isr(void* arg); +static void sdmmc_host_dma_init(); + +static const sdmmc_slot_info_t s_slot_info[2] = { + { + .clk = PERIPHS_IO_MUX_SD_CLK_U, + .cmd = PERIPHS_IO_MUX_SD_CMD_U, + .d0 = PERIPHS_IO_MUX_SD_DATA0_U, + .d1 = PERIPHS_IO_MUX_SD_DATA1_U, + .d2 = PERIPHS_IO_MUX_SD_DATA2_U, + .d3 = PERIPHS_IO_MUX_SD_DATA3_U, + .d4 = PERIPHS_IO_MUX_GPIO16_U, + .d5 = PERIPHS_IO_MUX_GPIO17_U, + .d6 = PERIPHS_IO_MUX_GPIO5_U, + .d7 = PERIPHS_IO_MUX_GPIO18_U, + .card_detect = HOST_CARD_DETECT_N_1_IDX, + .write_protect = HOST_CARD_WRITE_PRT_1_IDX, + .width = 8 + }, + { + .clk = PERIPHS_IO_MUX_MTMS_U, + .cmd = PERIPHS_IO_MUX_MTDO_U, + .d0 = PERIPHS_IO_MUX_GPIO2_U, + .d1 = PERIPHS_IO_MUX_GPIO4_U, + .d2 = PERIPHS_IO_MUX_MTDI_U, + .d3 = PERIPHS_IO_MUX_MTCK_U, + .card_detect = HOST_CARD_DETECT_N_2_IDX, + .write_protect = HOST_CARD_WRITE_PRT_2_IDX, + .width = 4 + } +}; + +static const char* TAG = "sdmmc_periph"; +static intr_handle_t s_intr_handle; +static QueueHandle_t s_event_queue; + + +void sdmmc_host_reset() +{ + // Set reset bits + SDMMC.ctrl.controller_reset = 1; + SDMMC.ctrl.dma_reset = 1; + SDMMC.ctrl.fifo_reset = 1; + // Wait for the reset bits to be cleared by hardware + while (SDMMC.ctrl.controller_reset || SDMMC.ctrl.fifo_reset || SDMMC.ctrl.dma_reset) { + ; + } +} + +/* We have two clock divider stages: + * - one is the clock generator which drives SDMMC peripheral, + * it can be configured using SDMMC.clock register. It can generate + * frequencies 160MHz/(N + 1), where 0 < N < 16, I.e. from 10 to 80 MHz. + * - 4 clock dividers inside SDMMC peripheral, which can divide clock + * from the first stage by 2 * M, where 0 < M < 255 + * (they can also be bypassed). + * + * For cards which aren't UHS-1 or UHS-2 cards, which we don't support, + * maximum bus frequency in high speed (HS) mode is 50 MHz. + * Note: for non-UHS-1 cards, HS mode is optional. + * Default speed (DS) mode is mandatory, it works up to 25 MHz. + * Whether the card supports HS or not can be determined using TRAN_SPEED + * field of card's CSD register. + * + * 50 MHz can not be obtained exactly, closest we can get is 53 MHz. + * For now set the first stage divider to generate 40MHz, and then configure + * the second stage dividers to generate the frequency requested. + * + * Of the second stage dividers, div0 is used for card 0, and div1 is used + * for card 1. + */ + +static void sdmmc_host_input_clk_enable() +{ + // Set frequency to 160MHz / (p + 1) = 40MHz, duty cycle (h + 1)/(p + 1) = 1/2 + SDMMC.clock.div_factor_p = 3; + SDMMC.clock.div_factor_h = 1; + SDMMC.clock.div_factor_m = 3; + // Set phases for in/out clocks + SDMMC.clock.phase_dout = 4; + SDMMC.clock.phase_din = 4; + SDMMC.clock.phase_core = 0; + // Wait for the clock to propagate + ets_delay_us(10); +} + +static void sdmmc_host_input_clk_disable() +{ + SDMMC.clock.val = 0; +} + +static void sdmmc_host_clock_update_command(int slot) +{ + // Clock update command (not a real command; just updates CIU registers) + sdmmc_hw_cmd_t cmd_val = { + .card_num = slot, + .update_clk_reg = 1, + .wait_complete = 1 + }; + bool repeat = true; + while(repeat) { + sdmmc_host_start_command(slot, cmd_val, 0); + while (true) { + // Sending clock update command to the CIU can generate HLE error. + // According to the manual, this is okay and we must retry the command. + if (SDMMC.rintsts.hle) { + SDMMC.rintsts.hle = 1; + repeat = true; + break; + } + // When the command is accepted by CIU, start_command bit will be + // cleared in SDMMC.cmd register. + if (SDMMC.cmd.start_command == 0) { + repeat = false; + break; + } + } + } +} + +esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz) +{ + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + const int clk40m = 40000; + + // Disable clock first + SDMMC.clkena.cclk_enable &= ~BIT(slot); + sdmmc_host_clock_update_command(slot); + + // Calculate new dividers + int div = 0; + if (freq_khz < clk40m) { + // round up; extra *2 is because clock divider divides by 2*n + div = (clk40m + freq_khz * 2 - 1) / (freq_khz * 2); + } + ESP_LOGD(TAG, "slot=%d div=%d freq=%dkHz", slot, div, + (div == 0) ? clk40m : clk40m / (2 * div)); + + // Program CLKDIV and CLKSRC, send them to the CIU + switch(slot) { + case 0: + SDMMC.clksrc.card0 = 0; + SDMMC.clkdiv.div0 = div; + break; + case 1: + SDMMC.clksrc.card1 = 1; + SDMMC.clkdiv.div1 = div; + break; + } + sdmmc_host_clock_update_command(slot); + + // Re-enable clocks + SDMMC.clkena.cclk_enable |= BIT(slot); + SDMMC.clkena.cclk_low_power |= BIT(slot); + sdmmc_host_clock_update_command(slot); + return ESP_OK; +} + +esp_err_t sdmmc_host_start_command(int slot, sdmmc_hw_cmd_t cmd, uint32_t arg) { + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + while (SDMMC.cmd.start_command == 1) { + ; + } + SDMMC.cmdarg = arg; + cmd.card_num = slot; + cmd.start_command = 1; + SDMMC.cmd = cmd; + return ESP_OK; +} + +esp_err_t sdmmc_host_init() +{ + if (s_intr_handle) { + return ESP_ERR_INVALID_STATE; + } + + // Enable clock to peripheral + sdmmc_host_input_clk_enable(); + + // Reset + sdmmc_host_reset(); + ESP_LOGD(TAG, "peripheral version %x, hardware config %08x", SDMMC.verid, SDMMC.hcon); + + // Clear interrupt status and set interrupt mask to known state + SDMMC.rintsts.val = 0xffffffff; + SDMMC.intmask.val = 0; + SDMMC.ctrl.int_enable = 0; + + // Allocate event queue + s_event_queue = xQueueCreate(SDMMC_EVENT_QUEUE_LENGTH, sizeof(sdmmc_event_t)); + if (!s_event_queue) { + return ESP_ERR_NO_MEM; + } + // Attach interrupt handler + esp_err_t ret = esp_intr_alloc(ETS_SDIO_HOST_INTR_SOURCE, 0, &sdmmc_isr, s_event_queue, &s_intr_handle); + if (ret != ESP_OK) { + vQueueDelete(s_event_queue); + s_event_queue = NULL; + return ret; + } + // Enable interrupts + SDMMC.intmask.val = + SDMMC_INTMASK_CD | + SDMMC_INTMASK_CMD_DONE | + SDMMC_INTMASK_DATA_OVER | + SDMMC_INTMASK_RCRC | SDMMC_INTMASK_DCRC | + SDMMC_INTMASK_RTO | SDMMC_INTMASK_DTO | SDMMC_INTMASK_HTO | + SDMMC_INTMASK_SBE | SDMMC_INTMASK_EBE | + SDMMC_INTMASK_RESP_ERR | SDMMC_INTMASK_HLE; + SDMMC.ctrl.int_enable = 1; + + // Enable DMA + sdmmc_host_dma_init(); + + // Initialize transaction handler + ret = sdmmc_host_transaction_handler_init(); + if (ret != ESP_OK) { + vQueueDelete(s_event_queue); + s_event_queue = NULL; + esp_intr_free(s_intr_handle); + s_intr_handle = NULL; + return ret; + } + + return ESP_OK; +} + + +static inline void configure_pin(uint32_t io_mux_reg) +{ + const int sdmmc_func = 3; + const int drive_strength = 3; + PIN_INPUT_ENABLE(io_mux_reg); + PIN_FUNC_SELECT(io_mux_reg, sdmmc_func); + PIN_SET_DRV(io_mux_reg, drive_strength); +} + +esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config) +{ + if (!s_intr_handle) { + return ESP_ERR_INVALID_STATE; + } + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + if (slot_config == NULL) { + return ESP_ERR_INVALID_ARG; + } + int gpio_cd = slot_config->gpio_cd; + int gpio_wp = slot_config->gpio_wp; + + // Configure pins + const sdmmc_slot_info_t* pslot = &s_slot_info[slot]; + configure_pin(pslot->clk); + configure_pin(pslot->cmd); + configure_pin(pslot->d0); + configure_pin(pslot->d1); + configure_pin(pslot->d2); + configure_pin(pslot->d3); + if (pslot->width == 8) { + configure_pin(pslot->d4); + configure_pin(pslot->d5); + configure_pin(pslot->d6); + configure_pin(pslot->d7); + } + if (gpio_cd != -1) { + gpio_set_direction(gpio_cd, GPIO_MODE_INPUT); + gpio_matrix_in(gpio_cd, pslot->card_detect, 0); + } + if (gpio_wp != -1) { + gpio_set_direction(gpio_wp, GPIO_MODE_INPUT); + gpio_matrix_in(gpio_wp, pslot->write_protect, 0); + } + // By default, set probing frequency (400kHz) and 1-bit bus + esp_err_t ret = sdmmc_host_set_card_clk(slot, 400); + if (ret != ESP_OK) { + return ret; + } + ret = sdmmc_host_set_bus_width(slot, 1); + if (ret != ESP_OK) { + return ret; + } + return ESP_OK; +} + +esp_err_t sdmmc_host_deinit() +{ + if (!s_intr_handle) { + return ESP_ERR_INVALID_STATE; + } + esp_intr_free(s_intr_handle); + s_intr_handle = NULL; + vQueueDelete(s_event_queue); + s_event_queue = NULL; + sdmmc_host_input_clk_disable(); + sdmmc_host_transaction_handler_deinit(); + return ESP_OK; +} + +esp_err_t sdmmc_host_wait_for_event(int tick_count, sdmmc_event_t* out_event) +{ + if (!out_event) { + return ESP_ERR_INVALID_ARG; + } + if (!s_event_queue) { + return ESP_ERR_INVALID_STATE; + } + int ret = xQueueReceive(s_event_queue, out_event, tick_count); + if (ret == pdFALSE) { + return ESP_ERR_TIMEOUT; + } + return ESP_OK; +} + +esp_err_t sdmmc_host_set_bus_width(int slot, size_t width) +{ + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + if (s_slot_info[slot].width < width) { + return ESP_ERR_INVALID_ARG; + } + const uint16_t mask = BIT(slot); + if (width == 1) { + SDMMC.ctype.card_width_8 &= ~mask; + SDMMC.ctype.card_width &= ~mask; + } else if (width == 4) { + SDMMC.ctype.card_width_8 &= ~mask; + SDMMC.ctype.card_width |= mask; + } else if (width == 8){ + SDMMC.ctype.card_width_8 |= mask; + } else { + return ESP_ERR_INVALID_ARG; + } + ESP_LOGD(TAG, "slot=%d width=%d", slot, width); + return ESP_OK; +} + +static void sdmmc_host_dma_init() +{ + SDMMC.ctrl.dma_enable = 1; + SDMMC.bmod.val = 0; + SDMMC.bmod.sw_reset = 1; + SDMMC.idinten.ni = 1; + SDMMC.idinten.ri = 1; + SDMMC.idinten.ti = 1; +} + + +void sdmmc_host_dma_stop() +{ + SDMMC.ctrl.use_internal_dma = 0; + SDMMC.ctrl.dma_reset = 1; + SDMMC.bmod.fb = 0; + SDMMC.bmod.enable = 0; +} + +void sdmmc_host_dma_prepare(sdmmc_desc_t* desc, size_t block_size, size_t data_size) +{ + // TODO: set timeout depending on data size + SDMMC.tmout.val = 0xffffffff; + + // Set size of data and DMA descriptor pointer + SDMMC.bytcnt = data_size; + SDMMC.blksiz = block_size; + SDMMC.dbaddr = desc; + + // Enable everything needed to use DMA + SDMMC.ctrl.dma_enable = 1; + SDMMC.ctrl.use_internal_dma = 1; + SDMMC.bmod.enable = 1; + SDMMC.bmod.fb = 1; + sdmmc_host_dma_resume(); +} + +void sdmmc_host_dma_resume() +{ + SDMMC.pldmnd = 1; +} + +/** + * @brief SDMMC interrupt handler + * + * Ignoring SDIO and streaming read/writes for now (and considering just SD memory cards), + * all communication is driven by the master, and the hardware handles things like stop + * commands automatically. So the interrupt handler doesn't need to do much, we just push + * interrupt status into a queue, clear interrupt flags, and let the task currently doing + * communication figure out what to do next. + * + * Card detect interrupts pose a small issue though, because if a card is plugged in and + * out a few times, while there is no task to process the events, event queue can become + * full and some card detect events may be dropped. We ignore this problem for now, since + * the there are no other interesting events which can get lost due to this. + */ +static void sdmmc_isr(void* arg) { + QueueHandle_t queue = (QueueHandle_t) arg; + sdmmc_event_t event; + uint32_t pending = SDMMC.mintsts.val; + SDMMC.rintsts.val = pending; + event.sdmmc_status = pending; + + uint32_t dma_pending = SDMMC.idsts.val; + SDMMC.idsts.val = dma_pending; + event.dma_status = dma_pending & 0x1f; + + int higher_priority_task_awoken = pdFALSE; + xQueueSendFromISR(queue, &event, &higher_priority_task_awoken); + if (higher_priority_task_awoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + diff --git a/components/driver/sdmmc_private.h b/components/driver/sdmmc_private.h new file mode 100644 index 0000000000..5a10a3b672 --- /dev/null +++ b/components/driver/sdmmc_private.h @@ -0,0 +1,44 @@ +// 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. + +#pragma once + +#include +#include +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "soc/sdmmc_struct.h" + +typedef struct { + uint32_t sdmmc_status; ///< masked SDMMC interrupt status + uint32_t dma_status; ///< masked DMA interrupt status +} sdmmc_event_t; + +void sdmmc_host_reset(); + +esp_err_t sdmmc_host_start_command(int slot, sdmmc_hw_cmd_t cmd, uint32_t arg); + +esp_err_t sdmmc_host_wait_for_event(int tick_count, sdmmc_event_t* out_event); + +void sdmmc_host_dma_prepare(sdmmc_desc_t* desc, size_t block_size, size_t data_size); + +void sdmmc_host_dma_stop(); + +void sdmmc_host_dma_resume(); + +esp_err_t sdmmc_host_transaction_handler_init(); + +void sdmmc_host_transaction_handler_deinit(); + diff --git a/components/driver/sdmmc_transaction.c b/components/driver/sdmmc_transaction.c new file mode 100644 index 0000000000..59b439eec9 --- /dev/null +++ b/components/driver/sdmmc_transaction.c @@ -0,0 +1,372 @@ +// 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 +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "soc/sdmmc_reg.h" +#include "soc/sdmmc_struct.h" +#include "driver/sdmmc_types.h" +#include "driver/sdmmc_defs.h" +#include "driver/sdmmc_host.h" +#include "sdmmc_private.h" + + +/* Number of DMA descriptors used for transfer. + * Increasing this value above 4 doesn't improve performance for the usual case + * of SD memory cards (most data transfers are multiples of 512 bytes). + */ +#define SDMMC_DMA_DESC_CNT 4 + +static const char* TAG = "sdmmc_req"; + +typedef enum { + SDMMC_IDLE, + SDMMC_SENDING_CMD, + SDMMC_SENDING_DATA, + SDMMC_BUSY, +} sdmmc_req_state_t; + +typedef struct { + uint8_t* ptr; + size_t size_remaining; + size_t next_desc; + size_t desc_remaining; +} sdmmc_transfer_state_t; + +const uint32_t SDMMC_DATA_ERR_MASK = + SDMMC_INTMASK_DTO | SDMMC_INTMASK_DCRC | + SDMMC_INTMASK_HTO | SDMMC_INTMASK_SBE | + SDMMC_INTMASK_EBE; + +const uint32_t SDMMC_DMA_DONE_MASK = + SDMMC_IDMAC_INTMASK_RI | SDMMC_IDMAC_INTMASK_TI | + SDMMC_IDMAC_INTMASK_NI; + +const uint32_t SDMMC_CMD_ERR_MASK = + SDMMC_INTMASK_RTO | + SDMMC_INTMASK_RCRC | + SDMMC_INTMASK_RESP_ERR; + +static sdmmc_desc_t s_dma_desc[SDMMC_DMA_DESC_CNT]; +static sdmmc_transfer_state_t s_cur_transfer = { 0 }; +static QueueHandle_t s_request_mutex; + +static esp_err_t handle_idle_state_events(); +static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd); +static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* pstate); +static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_req_state_t* pstate); +static void process_command_response(uint32_t status, sdmmc_command_t* cmd); +static void fill_dma_descriptors(size_t num_desc); + +esp_err_t sdmmc_host_transaction_handler_init() +{ + assert(s_request_mutex == NULL); + s_request_mutex = xSemaphoreCreateMutex(); + if (!s_request_mutex) { + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +void sdmmc_host_transaction_handler_deinit() +{ + assert(s_request_mutex); + vSemaphoreDelete(s_request_mutex); + s_request_mutex = NULL; +} + +esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo) +{ + xSemaphoreTake(s_request_mutex, portMAX_DELAY); + // dispose of any events which happened asynchronously + handle_idle_state_events(); + // convert cmdinfo to hardware register value + sdmmc_hw_cmd_t hw_cmd = make_hw_cmd(cmdinfo); + if (cmdinfo->data) { + // these constraints should be handled by upper layer + assert(cmdinfo->datalen >= 4); + assert(cmdinfo->blklen % 4 == 0); + // this clears "owned by IDMAC" bits + memset(s_dma_desc, 0, sizeof(s_dma_desc)); + // initialize first descriptor + s_dma_desc[0].first_descriptor = 1; + // save transfer info + s_cur_transfer.ptr = (uint8_t*) cmdinfo->data; + s_cur_transfer.size_remaining = cmdinfo->datalen; + s_cur_transfer.next_desc = 0; + s_cur_transfer.desc_remaining = (cmdinfo->datalen + SDMMC_DMA_MAX_BUF_LEN - 1) / SDMMC_DMA_MAX_BUF_LEN; + // prepare descriptors + fill_dma_descriptors(SDMMC_DMA_DESC_CNT); + // write transfer info into hardware + sdmmc_host_dma_prepare(&s_dma_desc[0], cmdinfo->blklen, cmdinfo->datalen); + } + // write command into hardware, this also sends the command to the card + esp_err_t ret = sdmmc_host_start_command(slot, hw_cmd, cmdinfo->arg); + if (ret != ESP_OK) { + xSemaphoreGive(s_request_mutex); + return ret; + } + // process events until transfer is complete + cmdinfo->error = ESP_OK; + sdmmc_req_state_t state = SDMMC_SENDING_CMD; + while (state != SDMMC_IDLE) { + ret = handle_event(cmdinfo, &state); + if (ret != ESP_OK) { + break; + } + } + xSemaphoreGive(s_request_mutex); + return ret; +} + +static void fill_dma_descriptors(size_t num_desc) +{ + for (size_t i = 0; i < num_desc; ++i) { + if (s_cur_transfer.size_remaining == 0) { + return; + } + const size_t next = s_cur_transfer.next_desc; + sdmmc_desc_t* desc = &s_dma_desc[next]; + assert(!desc->owned_by_idmac); + size_t size_to_fill = + (s_cur_transfer.size_remaining < SDMMC_DMA_MAX_BUF_LEN) ? + s_cur_transfer.size_remaining : SDMMC_DMA_MAX_BUF_LEN; + bool last = size_to_fill == s_cur_transfer.size_remaining; + desc->last_descriptor = last; + desc->second_address_chained = 1; + desc->owned_by_idmac = 1; + desc->buffer1_ptr = s_cur_transfer.ptr; + desc->next_desc_ptr = (last) ? NULL : &s_dma_desc[(next + 1) % SDMMC_DMA_DESC_CNT]; + desc->buffer1_size = size_to_fill; + + s_cur_transfer.size_remaining -= size_to_fill; + s_cur_transfer.ptr += size_to_fill; + s_cur_transfer.next_desc = (s_cur_transfer.next_desc + 1) % SDMMC_DMA_DESC_CNT; + ESP_LOGV(TAG, "fill %d desc=%d rem=%d next=%d last=%d sz=%d", + num_desc, next, s_cur_transfer.size_remaining, + s_cur_transfer.next_desc, desc->last_descriptor, desc->buffer1_size); + } +} + +static esp_err_t handle_idle_state_events() +{ + /* Handle any events which have happened in between transfers. + * Under current assumptions (no SDIO support) only card detect events + * can happen in the idle state. + */ + sdmmc_event_t evt; + while (sdmmc_host_wait_for_event(0, &evt) == ESP_OK) { + if (evt.sdmmc_status & SDMMC_INTMASK_CD) { + ESP_LOGV(TAG, "card detect event"); + evt.sdmmc_status &= ~SDMMC_INTMASK_CD; + } + if (evt.sdmmc_status != 0 || evt.dma_status != 0) { + ESP_LOGE(TAG, "handle_idle_state_events unhandled: %08x %08x", + evt.sdmmc_status, evt.dma_status); + } + + } + return ESP_OK; +} + + +static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* state) +{ + sdmmc_event_t evt; + esp_err_t err = sdmmc_host_wait_for_event(portMAX_DELAY, &evt); + if (err != ESP_OK) { + ESP_LOGE(TAG, "sdmmc_host_wait_for_event returned %d", err); + return err; + } + ESP_LOGV(TAG, "sdmmc_handle_event: evt %08x %08x", evt.sdmmc_status, evt.dma_status); + process_events(evt, cmd, state); + return ESP_OK; +} + +static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd) +{ + sdmmc_hw_cmd_t res = { 0 }; + + res.cmd_index = cmd->opcode; + if (cmd->opcode == MMC_STOP_TRANSMISSION) { + res.stop_abort_cmd = 1; + } else { + res.wait_complete = 1; + } + if (cmd->opcode == SD_APP_SET_BUS_WIDTH) { + res.send_auto_stop = 1; + res.data_expected = 1; + } + if (cmd->flags & SCF_RSP_PRESENT) { + res.response_expect = 1; + if (cmd->flags & SCF_RSP_136) { + res.response_long = 1; + } + } + if (cmd->flags & SCF_RSP_CRC) { + res.check_response_crc = 1; + } + res.use_hold_reg = 1; + if (cmd->data) { + res.data_expected = 1; + if ((cmd->flags & SCF_CMD_READ) == 0) { + res.rw = 1; + } + assert(cmd->datalen % cmd->blklen == 0); + if ((cmd->datalen / cmd->blklen) > 1) { + res.send_auto_stop = 1; + } + } + ESP_LOGV(TAG, "%s: opcode=%d, rexp=%d, crc=%d", __func__, + res.cmd_index, res.response_expect, res.check_response_crc); + return res; +} + +static void process_command_response(uint32_t status, sdmmc_command_t* cmd) +{ + if (cmd->flags & SCF_RSP_PRESENT) { + if (cmd->flags & SCF_RSP_136) { + cmd->response[3] = SDMMC.resp[0]; + cmd->response[2] = SDMMC.resp[1]; + cmd->response[1] = SDMMC.resp[2]; + cmd->response[0] = SDMMC.resp[3]; + + } else { + cmd->response[0] = SDMMC.resp[0]; + cmd->response[1] = 0; + cmd->response[2] = 0; + cmd->response[3] = 0; + } + } + + if ((status & SDMMC_INTMASK_RTO) && + cmd->opcode != MMC_ALL_SEND_CID && + cmd->opcode != MMC_SELECT_CARD && + cmd->opcode != MMC_STOP_TRANSMISSION) { + cmd->error = ESP_ERR_TIMEOUT; + } else if ((cmd->flags & SCF_RSP_CRC) && (status & SDMMC_INTMASK_RCRC)) { + cmd->error = ESP_ERR_INVALID_CRC; + } else if (status & SDMMC_INTMASK_RESP_ERR) { + cmd->error = ESP_ERR_INVALID_RESPONSE; + } + if (cmd->error != 0) { + if (cmd->data) { + sdmmc_host_dma_stop(); + } + ESP_LOGD(TAG, "%s: error %d", __func__, cmd->error); + } +} + +static void process_data_status(uint32_t status, sdmmc_command_t* cmd) +{ + if (status & SDMMC_DATA_ERR_MASK) { + if (status & SDMMC_INTMASK_DTO) { + cmd->error = ESP_ERR_TIMEOUT; + } else if (status & SDMMC_INTMASK_DCRC) { + cmd->error = ESP_ERR_INVALID_CRC; + } else if ((status & SDMMC_INTMASK_EBE) && + (cmd->flags & SCF_CMD_READ) == 0) { + cmd->error = ESP_ERR_TIMEOUT; + } else { + cmd->error = ESP_FAIL; + } + SDMMC.ctrl.fifo_reset = 1; + } + if (cmd->error != 0) { + if (cmd->data) { + sdmmc_host_dma_stop(); + } + ESP_LOGD(TAG, "%s: error %d", __func__, cmd->error); + } + +} + +static inline bool mask_check_and_clear(uint32_t* state, uint32_t mask) { + bool ret = ((*state) & mask) != 0; + *state &= ~mask; + return ret; +} + +static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_req_state_t* pstate) +{ + const char* const s_state_names[] __attribute__((unused)) = { + "IDLE", + "SENDING_CMD", + "SENDIND_DATA", + "BUSY" + }; + sdmmc_event_t orig_evt = evt; + ESP_LOGV(TAG, "%s: state=%s", __func__, s_state_names[*pstate]); + sdmmc_req_state_t next_state = *pstate; + sdmmc_req_state_t state = (sdmmc_req_state_t) -1; + while (next_state != state) { + state = next_state; + switch (state) { + case SDMMC_IDLE: + break; + + case SDMMC_SENDING_CMD: + if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_CMD_ERR_MASK)) { + process_command_response(orig_evt.sdmmc_status, cmd); + break; + } + if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_CMD_DONE)) { + break; + } + process_command_response(orig_evt.sdmmc_status, cmd); + if (cmd->error != ESP_OK || cmd->data == NULL) { + next_state = SDMMC_IDLE; + break; + } + next_state = SDMMC_SENDING_DATA; + break; + + + case SDMMC_SENDING_DATA: + if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_DATA_ERR_MASK)) { + process_data_status(orig_evt.sdmmc_status, cmd); + sdmmc_host_dma_stop(); + } + if (mask_check_and_clear(&evt.dma_status, SDMMC_DMA_DONE_MASK)) { + s_cur_transfer.desc_remaining--; + if (s_cur_transfer.size_remaining) { + fill_dma_descriptors(1); + sdmmc_host_dma_resume(); + } + if (s_cur_transfer.desc_remaining == 0) { + next_state = SDMMC_BUSY; + } + } + break; + + case SDMMC_BUSY: + if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_DATA_OVER)) { + break; + } + process_data_status(orig_evt.sdmmc_status, cmd); + next_state = SDMMC_IDLE; + break; + } + ESP_LOGV(TAG, "%s state=%s next_state=%s", __func__, s_state_names[state], s_state_names[next_state]); + } + *pstate = state; + return ESP_OK; +} + + + diff --git a/components/esp32/include/esp_err.h b/components/esp32/include/esp_err.h index a1f4b8f359..b6a1e8b421 100644 --- a/components/esp32/include/esp_err.h +++ b/components/esp32/include/esp_err.h @@ -35,7 +35,8 @@ typedef int32_t esp_err_t; #define ESP_ERR_NOT_FOUND 0x105 #define ESP_ERR_NOT_SUPPORTED 0x106 #define ESP_ERR_TIMEOUT 0x107 - +#define ESP_ERR_INVALID_RESPONSE 0x108 +#define ESP_ERR_INVALID_CRC 0x109 #define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */ diff --git a/components/esp32/include/soc/sdmmc_reg.h b/components/esp32/include/soc/sdmmc_reg.h new file mode 100644 index 0000000000..d1b452d1d1 --- /dev/null +++ b/components/esp32/include/soc/sdmmc_reg.h @@ -0,0 +1,94 @@ +// 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 _SOC_SDMMC_REG_H_ +#define _SOC_SDMMC_REG_H_ +#include "soc.h" + +#define SDMMC_CTRL_REG (DR_REG_SDMMC_BASE + 0x00) +#define SDMMC_PWREN_REG (DR_REG_SDMMC_BASE + 0x04) +#define SDMMC_CLKDIV_REG (DR_REG_SDMMC_BASE + 0x08) +#define SDMMC_CLKSRC_REG (DR_REG_SDMMC_BASE + 0x0c) +#define SDMMC_CLKENA_REG (DR_REG_SDMMC_BASE + 0x10) +#define SDMMC_TMOUT_REG (DR_REG_SDMMC_BASE + 0x14) +#define SDMMC_CTYPE_REG (DR_REG_SDMMC_BASE + 0x18) +#define SDMMC_BLKSIZ_REG (DR_REG_SDMMC_BASE + 0x1c) +#define SDMMC_BYTCNT_REG (DR_REG_SDMMC_BASE + 0x20) +#define SDMMC_INTMASK_REG (DR_REG_SDMMC_BASE + 0x24) +#define SDMMC_CMDARG_REG (DR_REG_SDMMC_BASE + 0x28) +#define SDMMC_CMD_REG (DR_REG_SDMMC_BASE + 0x2c) +#define SDMMC_RESP0_REG (DR_REG_SDMMC_BASE + 0x30) +#define SDMMC_RESP1_REG (DR_REG_SDMMC_BASE + 0x34) +#define SDMMC_RESP2_REG (DR_REG_SDMMC_BASE + 0x38) +#define SDMMC_RESP3_REG (DR_REG_SDMMC_BASE + 0x3c) + +#define SDMMC_MINTSTS_REG (DR_REG_SDMMC_BASE + 0x40) +#define SDMMC_RINTSTS_REG (DR_REG_SDMMC_BASE + 0x44) +#define SDMMC_STATUS_REG (DR_REG_SDMMC_BASE + 0x48) +#define SDMMC_FIFOTH_REG (DR_REG_SDMMC_BASE + 0x4c) +#define SDMMC_CDETECT_REG (DR_REG_SDMMC_BASE + 0x50) +#define SDMMC_WRTPRT_REG (DR_REG_SDMMC_BASE + 0x54) +#define SDMMC_GPIO_REG (DR_REG_SDMMC_BASE + 0x58) +#define SDMMC_TCBCNT_REG (DR_REG_SDMMC_BASE + 0x5c) +#define SDMMC_TBBCNT_REG (DR_REG_SDMMC_BASE + 0x60) +#define SDMMC_DEBNCE_REG (DR_REG_SDMMC_BASE + 0x64) +#define SDMMC_USRID_REG (DR_REG_SDMMC_BASE + 0x68) +#define SDMMC_VERID_REG (DR_REG_SDMMC_BASE + 0x6c) +#define SDMMC_HCON_REG (DR_REG_SDMMC_BASE + 0x70) +#define SDMMC_UHS_REG_REG (DR_REG_SDMMC_BASE + 0x74) +#define SDMMC_RST_N_REG (DR_REG_SDMMC_BASE + 0x78) +#define SDMMC_BMOD_REG (DR_REG_SDMMC_BASE + 0x80) +#define SDMMC_PLDMND_REG (DR_REG_SDMMC_BASE + 0x84) +#define SDMMC_DBADDR_REG (DR_REG_SDMMC_BASE + 0x88) +#define SDMMC_DBADDRU_REG (DR_REG_SDMMC_BASE + 0x8c) +#define SDMMC_IDSTS_REG (DR_REG_SDMMC_BASE + 0x8c) +#define SDMMC_IDINTEN_REG (DR_REG_SDMMC_BASE + 0x90) +#define SDMMC_DSCADDR_REG (DR_REG_SDMMC_BASE + 0x94) +#define SDMMC_DSCADDRL_REG (DR_REG_SDMMC_BASE + 0x98) +#define SDMMC_DSCADDRU_REG (DR_REG_SDMMC_BASE + 0x9c) +#define SDMMC_BUFADDRL_REG (DR_REG_SDMMC_BASE + 0xa0) +#define SDMMC_BUFADDRU_REG (DR_REG_SDMMC_BASE + 0xa4) +#define SDMMC_CARDTHRCTL_REG (DR_REG_SDMMC_BASE + 0x100) +#define SDMMC_BACK_END_POWER_REG (DR_REG_SDMMC_BASE + 0x104) +#define SDMMC_UHS_REG_EXT_REG (DR_REG_SDMMC_BASE + 0x108) +#define SDMMC_EMMC_DDR_REG_REG (DR_REG_SDMMC_BASE + 0x10c) +#define SDMMC_ENABLE_SHIFT_REG (DR_REG_SDMMC_BASE + 0x110) + +#define SDMMC_CLOCK_REG (DR_REG_SDMMC_BASE + 0x800) + +#define SDMMC_INTMASK_EBE BIT(15) +#define SDMMC_INTMASK_ACD BIT(14) +#define SDMMC_INTMASK_SBE BIT(13) +#define SDMMC_INTMASK_HLE BIT(12) +#define SDMMC_INTMASK_FRUN BIT(11) +#define SDMMC_INTMASK_HTO BIT(10) +#define SDMMC_INTMASK_DTO BIT(9) +#define SDMMC_INTMASK_RTO BIT(8) +#define SDMMC_INTMASK_DCRC BIT(7) +#define SDMMC_INTMASK_RCRC BIT(6) +#define SDMMC_INTMASK_RXDR BIT(5) +#define SDMMC_INTMASK_TXDR BIT(4) +#define SDMMC_INTMASK_DATA_OVER BIT(3) +#define SDMMC_INTMASK_CMD_DONE BIT(2) +#define SDMMC_INTMASK_RESP_ERR BIT(1) +#define SDMMC_INTMASK_CD BIT(0) + +#define SDMMC_IDMAC_INTMASK_AI BIT(9) +#define SDMMC_IDMAC_INTMASK_NI BIT(8) +#define SDMMC_IDMAC_INTMASK_CES BIT(5) +#define SDMMC_IDMAC_INTMASK_DU BIT(4) +#define SDMMC_IDMAC_INTMASK_FBE BIT(2) +#define SDMMC_IDMAC_INTMASK_RI BIT(1) +#define SDMMC_IDMAC_INTMASK_TI BIT(0) + +#endif /* _SOC_SDMMC_REG_H_ */ diff --git a/components/esp32/include/soc/sdmmc_struct.h b/components/esp32/include/soc/sdmmc_struct.h new file mode 100644 index 0000000000..20bb9d260d --- /dev/null +++ b/components/esp32/include/soc/sdmmc_struct.h @@ -0,0 +1,371 @@ +// 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 _SOC_SDMMC_STRUCT_H_ +#define _SOC_SDMMC_STRUCT_H_ + +#include + +typedef struct { + uint32_t reserved1: 1; + uint32_t disable_int_on_completion: 1; + uint32_t last_descriptor: 1; + uint32_t first_descriptor: 1; + uint32_t second_address_chained: 1; + uint32_t end_of_ring: 1; + uint32_t reserved2: 24; + uint32_t card_error_summary: 1; + uint32_t owned_by_idmac: 1; + uint32_t buffer1_size: 13; + uint32_t buffer2_size: 13; + uint32_t reserved3: 6; + void* buffer1_ptr; + union { + void* buffer2_ptr; + void* next_desc_ptr; + }; +} sdmmc_desc_t; + +#define SDMMC_DMA_MAX_BUF_LEN 4096 + +_Static_assert(sizeof(sdmmc_desc_t) == 16, "invalid size of sdmmc_desc_t structure"); + + +typedef struct { + uint32_t cmd_index: 6; ///< Command index + uint32_t response_expect: 1; ///< set if response is expected + uint32_t response_long: 1; ///< 0: short response expected, 1: long response expected + uint32_t check_response_crc: 1; ///< set if controller should check response CRC + uint32_t data_expected: 1; ///< 0: no data expected, 1: data expected + uint32_t rw: 1; ///< 0: read from card, 1: write to card (don't care if no data expected) + uint32_t stream_mode: 1; ///< 0: block transfer, 1: stream transfer (don't care if no data expected) + uint32_t send_auto_stop: 1; ///< set to send stop at the end of the transfer + uint32_t wait_complete: 1; ///< 0: send command at once, 1: wait for previous command to complete + uint32_t stop_abort_cmd: 1; ///< set if this is a stop or abort command intended to stop current transfer + uint32_t send_init: 1; ///< set to send init sequence (80 clocks of 1) + uint32_t card_num: 5; ///< card number + uint32_t update_clk_reg: 1; ///< 0: normal command, 1: don't send command, just update clock registers + uint32_t read_ceata: 1; ///< set if performing read from CE-ATA device + uint32_t ccs_expected: 1; ///< set if CCS is expected from CE-ATA device + uint32_t enable_boot: 1; ///< set for mandatory boot mode + uint32_t expect_boot_ack: 1; ///< when set along with enable_boot, controller expects boot ack pattern + uint32_t disable_boot: 1; ///< set to terminate boot operation (don't set along with enable_boot) + uint32_t boot_mode: 1; ///< 0: mandatory boot operation, 1: alternate boot operation + uint32_t volt_switch: 1; ///< set to enable voltage switching (for CMD11 only) + uint32_t use_hold_reg: 1; ///< clear to bypass HOLD register + uint32_t reserved: 1; + uint32_t start_command: 1; ///< Start command; once command is sent to the card, bit is cleared. +} sdmmc_hw_cmd_t; ///< command format used in cmd register; this structure is defined to make it easier to build command values + +_Static_assert(sizeof(sdmmc_hw_cmd_t) == 4, "invalid size of sdmmc_cmd_t structure"); + + +typedef volatile struct { + union { + struct { + uint32_t controller_reset: 1; + uint32_t fifo_reset: 1; + uint32_t dma_reset: 1; + uint32_t reserved1: 1; + uint32_t int_enable: 1; + uint32_t dma_enable: 1; + uint32_t read_wait: 1; + uint32_t send_irq_response: 1; + uint32_t abort_read_data: 1; + uint32_t send_ccsd: 1; + uint32_t send_auto_stop_ccsd: 1; + uint32_t ceata_device_interrupt_status: 1; + uint32_t reserved2: 4; + uint32_t card_voltage_a: 4; + uint32_t card_voltage_b: 4; + uint32_t enable_od_pullup: 1; + uint32_t use_internal_dma: 1; + uint32_t reserved3: 6; + }; + uint32_t val; + } ctrl; + + uint32_t pwren; ///< 1: enable power to card, 0: disable power to card + + union { + struct { + uint32_t div0: 8; ///< 0: bypass, 1-255: divide clock by (2*div0). + uint32_t div1: 8; ///< 0: bypass, 1-255: divide clock by (2*div0). + uint32_t div2: 8; ///< 0: bypass, 1-255: divide clock by (2*div0). + uint32_t div3: 8; ///< 0: bypass, 1-255: divide clock by (2*div0). + }; + uint32_t val; + } clkdiv; + + union { + struct { + uint32_t card0: 2; ///< 0-3: select clock divider for card 0 among div0-div3 + uint32_t card1: 2; ///< 0-3: select clock divider for card 1 among div0-div3 + uint32_t reserved: 28; + }; + uint32_t val; + } clksrc; + + union { + struct { + uint32_t cclk_enable: 16; ///< 1: enable clock to card, 0: disable clock + uint32_t cclk_low_power: 16; ///< 1: enable clock gating when card is idle, 0: disable clock gating + }; + uint32_t val; + } clkena; + + union { + struct { + uint32_t response: 8; ///< response timeout, in card output clock cycles + uint32_t data: 24; ///< data read timeout, in card output clock cycles + }; + uint32_t val; + } tmout; + + union { + struct { + uint32_t card_width: 16; ///< one bit for each card: 0: 1-bit mode, 1: 4-bit mode + uint32_t card_width_8: 16; ///< one bit for each card: 0: not 8-bit mode (corresponding card_width bit is used), 1: 8-bit mode (card_width bit is ignored) + }; + uint32_t val; + } ctype; + + uint32_t blksiz: 16; ///< block size, default 0x200 + uint32_t : 16; + + uint32_t bytcnt; ///< number of bytes to be transferred + + union { + struct { + uint32_t cd: 1; ///< Card detect interrupt enable + uint32_t re: 1; ///< Response error interrupt enable + uint32_t cmd_done: 1; ///< Command done interrupt enable + uint32_t dto: 1; ///< Data transfer over interrupt enable + uint32_t txdr: 1; ///< Transmit FIFO data request interrupt enable + uint32_t rxdr: 1; ///< Receive FIFO data request interrupt enable + uint32_t rcrc: 1; ///< Response CRC error interrupt enable + uint32_t dcrc: 1; ///< Data CRC error interrupt enable + uint32_t rto: 1; ///< Response timeout interrupt enable + uint32_t drto: 1; ///< Data read timeout interrupt enable + uint32_t hto: 1; ///< Data starvation-by-host timeout interrupt enable + uint32_t frun: 1; ///< FIFO underrun/overrun error interrupt enable + uint32_t hle: 1; ///< Hardware locked write error interrupt enable + uint32_t sbi_bci: 1; ///< Start bit error / busy clear interrupt enable + uint32_t acd: 1; ///< Auto command done interrupt enable + uint32_t ebe: 1; ///< End bit error / write no CRC interrupt enable + uint32_t sdio: 16; ///< SDIO interrupt enable + }; + uint32_t val; + } intmask; + + uint32_t cmdarg; ///< Command argument to be passed to card + + sdmmc_hw_cmd_t cmd; + + uint32_t resp[4]; ///< Response from card + + union { + struct { + uint32_t cd: 1; ///< Card detect interrupt masked status + uint32_t re: 1; ///< Response error interrupt masked status + uint32_t cmd_done: 1; ///< Command done interrupt masked status + uint32_t dto: 1; ///< Data transfer over interrupt masked status + uint32_t txdr: 1; ///< Transmit FIFO data request interrupt masked status + uint32_t rxdr: 1; ///< Receive FIFO data request interrupt masked status + uint32_t rcrc: 1; ///< Response CRC error interrupt masked status + uint32_t dcrc: 1; ///< Data CRC error interrupt masked status + uint32_t rto: 1; ///< Response timeout interrupt masked status + uint32_t drto: 1; ///< Data read timeout interrupt masked status + uint32_t hto: 1; ///< Data starvation-by-host timeout interrupt masked status + uint32_t frun: 1; ///< FIFO underrun/overrun error interrupt masked status + uint32_t hle: 1; ///< Hardware locked write error interrupt masked status + uint32_t sbi_bci: 1; ///< Start bit error / busy clear interrupt masked status + uint32_t acd: 1; ///< Auto command done interrupt masked status + uint32_t ebe: 1; ///< End bit error / write no CRC interrupt masked status + uint32_t sdio: 16; ///< SDIO interrupt masked status + }; + uint32_t val; + } mintsts; + + union { + struct { + uint32_t cd: 1; ///< Card detect raw interrupt status + uint32_t re: 1; ///< Response error raw interrupt status + uint32_t cmd_done: 1; ///< Command done raw interrupt status + uint32_t dto: 1; ///< Data transfer over raw interrupt status + uint32_t txdr: 1; ///< Transmit FIFO data request raw interrupt status + uint32_t rxdr: 1; ///< Receive FIFO data request raw interrupt status + uint32_t rcrc: 1; ///< Response CRC error raw interrupt status + uint32_t dcrc: 1; ///< Data CRC error raw interrupt status + uint32_t rto: 1; ///< Response timeout raw interrupt status + uint32_t drto: 1; ///< Data read timeout raw interrupt status + uint32_t hto: 1; ///< Data starvation-by-host timeout raw interrupt status + uint32_t frun: 1; ///< FIFO underrun/overrun error raw interrupt status + uint32_t hle: 1; ///< Hardware locked write error raw interrupt status + uint32_t sbi_bci: 1; ///< Start bit error / busy clear raw interrupt status + uint32_t acd: 1; ///< Auto command done raw interrupt status + uint32_t ebe: 1; ///< End bit error / write no CRC raw interrupt status + uint32_t sdio: 16; ///< SDIO raw interrupt status + }; + uint32_t val; + } rintsts; ///< interrupts can be cleared by writing this register + + union { + struct { + uint32_t fifo_rx_watermark: 1; ///< FIFO reached receive watermark level + uint32_t fifo_tx_watermark: 1; ///< FIFO reached transmit watermark level + uint32_t fifo_empty: 1; ///< FIFO is empty + uint32_t fifo_full: 1; ///< FIFO is full + uint32_t cmd_fsm_state: 4; ///< command FSM state + uint32_t data3_status: 1; ///< this bit reads 1 if card is present + uint32_t data_busy: 1; ///< this bit reads 1 if card is busy + uint32_t data_fsm_busy: 1; ///< this bit reads 1 if transmit/receive FSM is busy + uint32_t response_index: 6; ///< index of the previous response + uint32_t fifo_count: 13; ///< number of filled locations in the FIFO + uint32_t dma_ack: 1; ///< DMA acknowledge signal + uint32_t dma_req: 1; ///< DMA request signal + }; + uint32_t val; + } status; + + union { + struct { + uint32_t tx_watermark: 12; ///< FIFO TX watermark level + uint32_t reserved1: 4; + uint32_t rx_watermark: 12; ///< FIFO RX watermark level + uint32_t dw_dma_mts: 3; + uint32_t reserved2: 1; + }; + uint32_t val; + } fifoth; + + union { + struct { + uint32_t cards: 2; ///< bit N reads 1 if card N is present + uint32_t reserved: 30; + }; + uint32_t val; + } cdetect; + + union { + struct { + uint32_t card0: 2; ///< bit N reads 1 if card N is write protected + uint32_t reserved: 30; + }; + uint32_t val; + } wrtprt; + + uint32_t gpio; ///< unused + uint32_t tcbcnt; ///< transferred (to card) byte count + uint32_t tbbcnt; ///< transferred from host to FIFO byte count + + union { + struct { + uint32_t debounce_count: 24; ///< number of host cycles used by debounce filter, typical time should be 5-25ms + uint32_t reserved: 8; + }; + } debnce; + + uint32_t usrid; ///< user ID + uint32_t verid; ///< IP block version + uint32_t hcon; ///< compile-time IP configuration + uint32_t uhs; ///< TBD + + union { + struct { + uint32_t cards: 2; ///< bit N resets card N, active low + uint32_t reserved: 30; + }; + } rst_n; + + uint32_t reserved_7c; + + union { + struct { + uint32_t sw_reset: 1; ///< set to reset DMA controller + uint32_t fb: 1; ///< set if AHB master performs fixed burst transfers + uint32_t dsl: 5; ///< descriptor skip length: number of words to skip between two unchained descriptors + uint32_t enable: 1; ///< set to enable IDMAC + uint32_t pbl: 3; ///< programmable burst length + uint32_t reserved: 21; + }; + uint32_t val; + } bmod; + + uint32_t pldmnd; ///< set any bit to resume IDMAC FSM from suspended state + sdmmc_desc_t* dbaddr; ///< descriptor list base + + union { + struct { + uint32_t ti: 1; ///< transmit interrupt status + uint32_t ri: 1; ///< receive interrupt status + uint32_t fbe: 1; ///< fatal bus error + uint32_t reserved1: 1; + uint32_t du: 1; ///< descriptor unavailable + uint32_t ces: 1; ///< card error summary + uint32_t reserved2: 2; + uint32_t nis: 1; ///< normal interrupt summary + uint32_t fbe_code: 3; ///< code of fatal bus error + uint32_t fsm: 4; ///< DMAC FSM state + uint32_t reserved3: 15; + }; + uint32_t val; + } idsts; + + union { + struct { + uint32_t ti: 1; ///< transmit interrupt enable + uint32_t ri: 1; ///< receive interrupt enable + uint32_t fbe: 1; ///< fatal bus error interrupt enable + uint32_t reserved1: 1; + uint32_t du: 1; ///< descriptor unavailable interrupt enable + uint32_t ces: 1; ///< card error interrupt enable + uint32_t reserved2: 2; + uint32_t ni: 1; ///< normal interrupt interrupt enable + uint32_t ai: 1; ///< abnormal interrupt enable + uint32_t reserved3: 22; + }; + uint32_t val; + } idinten; + + uint32_t dscaddr; ///< current host descriptor address + uint32_t dscaddrl; ///< unused + uint32_t dscaddru; ///< unused + uint32_t bufaddrl; ///< unused + uint32_t bufaddru; ///< unused + uint32_t reserved_a8[22]; + uint32_t cardthrctl; + uint32_t back_end_power; + uint32_t uhs_reg_ext; + uint32_t emmc_ddr_reg; + uint32_t enable_shift; + uint32_t reserved_114[443]; + union { + struct { + uint32_t phase_dout: 3; ///< phase of data output clock (0x0: 0, 0x1: 90, 0x4: 180, 0x6: 270) + uint32_t phase_din: 3; ///< phase of data input clock + uint32_t phase_core: 3; ///< phase of the clock to SDMMC peripheral + uint32_t div_factor_p: 4; ///< controls clock period; it will be (div_factor_p + 1) / 160MHz + uint32_t div_factor_h: 4; ///< controls length of high pulse; it will be (div_factor_h + 1) / 160MHz + uint32_t div_factor_m: 4; ///< should be equal to div_factor_p + }; + uint32_t val; + } clock; +} sdmmc_dev_t; +extern sdmmc_dev_t SDMMC; + +_Static_assert(sizeof(sdmmc_dev_t) == 0x804, "invalid size of sdmmc_dev_t structure"); + + + +#endif //_SOC_SDMMC_STRUCT_H_ diff --git a/components/esp32/include/soc/soc.h b/components/esp32/include/soc/soc.h index b93bae7298..1d447fadb3 100755 --- a/components/esp32/include/soc/soc.h +++ b/components/esp32/include/soc/soc.h @@ -153,7 +153,7 @@ #define DR_REG_FRC_TIMER_BASE 0x3ff47000 #define DR_REG_RTCCNTL_BASE 0x3ff48000 #define DR_REG_RTCIO_BASE 0x3ff48400 -#define DR_REG_SENS_BASE 0x3ff48800 +#define DR_REG_SENS_BASE 0x3ff48800 #define DR_REG_IO_MUX_BASE 0x3ff49000 #define DR_REG_RTCMEM0_BASE 0x3ff61000 #define DR_REG_RTCMEM1_BASE 0x3ff62000 diff --git a/components/esp32/ld/esp32.peripherals.ld b/components/esp32/ld/esp32.peripherals.ld index 95aaadcbcc..8d8d4e8b1b 100644 --- a/components/esp32/ld/esp32.peripherals.ld +++ b/components/esp32/ld/esp32.peripherals.ld @@ -19,3 +19,4 @@ PROVIDE ( SPI3 = 0x3ff65000 ); PROVIDE ( I2C1 = 0x3ff67000 ); PROVIDE ( I2S1 = 0x3ff6D000 ); PROVIDE ( UART2 = 0x3ff6E000 ); +PROVIDE ( SDMMC = 0x3ff68000 ); diff --git a/components/sdmmc/component.mk b/components/sdmmc/component.mk new file mode 100755 index 0000000000..e69de29bb2 diff --git a/components/sdmmc/include/sdmmc_cmd.h b/components/sdmmc/include/sdmmc_cmd.h new file mode 100644 index 0000000000..58b6f082cc --- /dev/null +++ b/components/sdmmc/include/sdmmc_cmd.h @@ -0,0 +1,77 @@ +// 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. + +#pragma once + +#include +#include "esp_err.h" +#include "driver/sdmmc_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Probe and initialize SD/MMC card using given host + * + * @note Only SD cards (SDSC and SDHC/SDXC) are supported now. + * Support for MMC/eMMC cards will be added later. + * + * @param host pointer to structure defining host controller + * @param out_card pointer to structure which will receive information about the card when the function completes + * @return + * - ESP_OK on success + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_card_init(const sdmmc_host_t* host, + sdmmc_card_t* out_card); + +/** + * @brief Print information about the card to a stream + * @param stream stream obtained using fopen or fdopen + * @param card card information structure initialized using sdmmc_card_init + */ +void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card); + +/** + * Write given number of sectors to SD/MMC card + * + * @param card pointer to card information structure previously initialized using sdmmc_card_init + * @param src pointer to data buffer to read data from; data size must be equal to sector_count * card->csd.sector_size + * @param start_sector sector where to start writing + * @param sector_count number of sectors to write + * @return + * - ESP_OK on success + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src, + size_t start_sector, size_t sector_count); + +/** + * Write given number of sectors to SD/MMC card + * + * @param card pointer to card information structure previously initialized using sdmmc_card_init + * @param dst pointer to data buffer to write into; buffer size must be at least sector_count * card->csd.sector_size + * @param start_sector sector where to start reading + * @param sector_count number of sectors to read + * @return + * - ESP_OK on success + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst, + size_t start_sector, size_t sector_count); + +#ifdef __cplusplus +} +#endif diff --git a/components/sdmmc/sdmmc_cmd.c b/components/sdmmc/sdmmc_cmd.c new file mode 100644 index 0000000000..659ff5dd6c --- /dev/null +++ b/components/sdmmc/sdmmc_cmd.c @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2006 Uwe Stuehler + * Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "esp_log.h" +#include "esp_heap_alloc_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/sdmmc_defs.h" +#include "driver/sdmmc_types.h" +#include "sdmmc_cmd.h" + +#define MIN(a,b) (((a)<(b))?(a):(b)) + +static const char* TAG = "sdmmc_cmd"; + +static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd); +static esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd); +static esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card); +static esp_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr); +static esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp); +static esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid); +static esp_err_t sddmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_cid_t* out_cid); +static esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca); +static esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd); +static esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd); +static esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd); +static esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card); +static esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr); +static esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr); +static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width); +static esp_err_t sdmmc_send_cmd_stop_transmission(sdmmc_card_t* card, uint32_t* status); +static esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status); +static uint32_t get_host_ocr(float voltage); + + +esp_err_t sdmmc_card_init(const sdmmc_host_t* config, + sdmmc_card_t* card) +{ + ESP_LOGD(TAG, "%s", __func__); + memset(card, 0, sizeof(*card)); + memcpy(&card->host, config, sizeof(*config)); + esp_err_t err = sdmmc_send_cmd_go_idle_state(card); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: go_idle_state (1) returned 0x%x", __func__, err); + return err; + } + ets_delay_us(10000); + uint32_t host_ocr = get_host_ocr(config->io_voltage); + err = sdmmc_send_cmd_send_if_cond(card, host_ocr); + if (err == ESP_OK) { + ESP_LOGD(TAG, "SDHC/SDXC card"); + host_ocr |= SD_OCR_SDHC_CAP; + } else if (err == ESP_ERR_TIMEOUT) { + ESP_LOGD(TAG, "CMD8 timeout; not an SDHC/SDXC card"); + } else { + ESP_LOGE(TAG, "%s: send_if_cond (1) returned 0x%x", __func__, err); + return err; + } + err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_op_cond (1) returned 0x%x", __func__, err); + return err; + } + host_ocr &= card->ocr; + ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr); + err = sddmc_send_cmd_all_send_cid(card, &card->cid); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: all_send_cid returned 0x%x", __func__, err); + return err; + } + err = sdmmc_send_cmd_set_relative_addr(card, &card->rca); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: set_relative_addr returned 0x%x", __func__, err); + return err; + } + err = sdmmc_send_cmd_send_csd(card, &card->csd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_csd returned 0x%x", __func__, err); + return err; + } + const size_t max_sdsc_capacity = UINT32_MAX / card->csd.sector_size + 1; + if (!(card->ocr & SD_OCR_SDHC_CAP) && + card->csd.capacity > max_sdsc_capacity) { + ESP_LOGW(TAG, "%s: SDSC card reports capacity=%u. Limiting to %u.", + __func__, card->csd.capacity, max_sdsc_capacity); + card->csd.capacity = max_sdsc_capacity; + } + err = sdmmc_send_cmd_select_card(card); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: select_card returned 0x%x", __func__, err); + return err; + } + if ((card->ocr & SD_OCR_SDHC_CAP) == 0) { + err = sdmmc_send_cmd_set_blocklen(card, &card->csd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: set_blocklen returned 0x%x", __func__, err); + return err; + } + } + err = sdmmc_send_cmd_send_scr(card, &card->scr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err); + return err; + } + if ((config->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); + if (err != ESP_OK) { + ESP_LOGE(TAG, "set_bus_width failed"); + return err; + } + err = (*config->set_bus_width)(config->slot, 4); + if (err != ESP_OK) { + ESP_LOGE(TAG, "slot->set_bus_width failed"); + return err; + } + uint32_t status; + err = sdmmc_send_cmd_stop_transmission(card, &status); + if (err != ESP_OK) { + ESP_LOGE(TAG, "stop_transmission failed (0x%x)", err); + return err; + } + } + uint32_t status = 0; + while (!(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + uint32_t count = 0; + err = sdmmc_send_cmd_send_status(card, &status); + if (err != ESP_OK) { + return err; + } + if (++count % 10 == 0) { + ESP_LOGV(TAG, "waiting for card to become ready (%d)", count); + } + } + if (config->max_freq_khz >= SDMMC_FREQ_HIGHSPEED && + card->csd.tr_speed / 1000 >= SDMMC_FREQ_HIGHSPEED) { + ESP_LOGD(TAG, "switching to HS bus mode"); + err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_HIGHSPEED); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode"); + return err; + } + } else if (config->max_freq_khz >= SDMMC_FREQ_DEFAULT && + card->csd.tr_speed / 1000 >= SDMMC_FREQ_DEFAULT) { + ESP_LOGD(TAG, "switching to DS bus mode"); + err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_DEFAULT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode"); + return err; + } + } + sdmmc_scr_t scr_tmp; + err = sdmmc_send_cmd_send_scr(card, &scr_tmp); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err); + return err; + } + if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) { + ESP_LOGE(TAG, "data check fail!"); + return ESP_ERR_INVALID_RESPONSE; + } + return ESP_OK; +} + +void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card) +{ + fprintf(stream, "Name: %s\n", card->cid.name); + fprintf(stream, "Type: %s\n", (card->ocr & SD_OCR_SDHC_CAP)?"SDHC/SDXC":"SDSC"); + fprintf(stream, "Speed: %s\n", (card->csd.tr_speed > 25000000)?"high speed":"default speed"); + fprintf(stream, "Size: %lluMB\n", ((uint64_t) card->csd.capacity) * card->csd.sector_size / (1024 * 1024)); + fprintf(stream, "CSD: ver=%d, sector_size=%d, capacity=%d read_bl_len=%d\n", + card->csd.csd_ver, + card->csd.sector_size, card->csd.capacity, card->csd.read_block_len); + fprintf(stream, "SCR: sd_spec=%d, bus_width=%d\n", card->scr.sd_spec, card->scr.bus_width); +} + +static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd) +{ + int slot = card->host.slot; + ESP_LOGV(TAG, "sending cmd slot=%d op=%d arg=%x flags=%x data=%p blklen=%d datalen=%d", + slot, cmd->opcode, cmd->arg, cmd->flags, cmd->data, cmd->blklen, cmd->datalen); + esp_err_t err = (*card->host.do_transaction)(slot, cmd); + if (err != 0) { + ESP_LOGD(TAG, "sdmmc_req_run returned 0x%x", err); + return err; + } + int state = MMC_R1_CURRENT_STATE(cmd->response); + ESP_LOGV(TAG, "cmd response %08x %08x %08x %08x err=0x%x state=%d", + cmd->response[0], + cmd->response[1], + cmd->response[2], + cmd->response[3], + cmd->error, + state); + return cmd->error; +} + +static esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd) +{ + sdmmc_command_t app_cmd = { + .opcode = MMC_APP_CMD, + .flags = SCF_CMD_AC | SCF_RSP_R1, + .arg = MMC_ARG_RCA(card->rca), + }; + esp_err_t err = sdmmc_send_cmd(card, &app_cmd); + if (err != ESP_OK) { + return err; + } + if (!(MMC_R1(app_cmd.response) & MMC_R1_APP_CMD)) { + ESP_LOGW(TAG, "card doesn't support APP_CMD"); + return ESP_ERR_NOT_SUPPORTED; + } + return sdmmc_send_cmd(card, cmd); +} + + +static esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card) +{ + sdmmc_command_t cmd = { + .opcode = MMC_GO_IDLE_STATE, + .flags = SCF_CMD_BC | SCF_RSP_R0, + }; + return sdmmc_send_cmd(card, &cmd); +} + + +static esp_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr) +{ + const uint8_t pattern = 0xaa; /* any pattern will do here */ + sdmmc_command_t cmd = { + .opcode = SD_SEND_IF_COND, + .arg = (((ocr & SD_OCR_VOL_MASK) != 0) << 8) | pattern, + .flags = SCF_CMD_BCR | SCF_RSP_R7, + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + uint8_t response = cmd.response[0] & 0xff; + if (response != pattern) { + return ESP_ERR_INVALID_RESPONSE; + } + return ESP_OK; +} + +static esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp) +{ + sdmmc_command_t cmd = { + .arg = ocr, + .flags = SCF_CMD_BCR | SCF_RSP_R3, + .opcode = SD_APP_OP_COND + }; + int nretries = 100; // arbitrary, BSD driver uses this value + for (; nretries != 0; --nretries) { + esp_err_t err = sdmmc_send_app_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + if ((MMC_R3(cmd.response) & MMC_OCR_MEM_READY) || + ocr == 0) { + break; + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } + if (nretries == 0) { + return ESP_ERR_TIMEOUT; + } + if (ocrp) { + *ocrp = MMC_R3(cmd.response); + } + return ESP_OK; +} + +static esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid) +{ + out_cid->mfg_id = SD_CID_MID(resp); + out_cid->oem_id = SD_CID_OID(resp); + SD_CID_PNM_CPY(resp, out_cid->name); + out_cid->revision = SD_CID_REV(resp); + out_cid->serial = SD_CID_PSN(resp); + out_cid->date = SD_CID_MDT(resp); + return ESP_OK; +} + +static esp_err_t sddmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_cid_t* out_cid) +{ + assert(out_cid); + sdmmc_command_t cmd = { + .opcode = MMC_ALL_SEND_CID, + .flags = SCF_CMD_BCR | SCF_RSP_R2 + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + return sdmmc_decode_cid(cmd.response, out_cid); +} + + +static esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca) +{ + assert(out_rca); + sdmmc_command_t cmd = { + .opcode = SD_SEND_RELATIVE_ADDR, + .flags = SCF_CMD_BCR | SCF_RSP_R6 + }; + + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + *out_rca = SD_R6_RCA(cmd.response); + return ESP_OK; +} + + +static esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SET_BLOCKLEN, + .arg = csd->sector_size, + .flags = SCF_CMD_AC | SCF_RSP_R1 + }; + return sdmmc_send_cmd(card, &cmd); +} + +static esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd) +{ + out_csd->csd_ver = SD_CSD_CSDVER(response); + switch (out_csd->csd_ver) { + case SD_CSD_CSDVER_2_0: + out_csd->capacity = SD_CSD_V2_CAPACITY(response); + out_csd->read_block_len = SD_CSD_V2_BL_LEN; + break; + case SD_CSD_CSDVER_1_0: + out_csd->capacity = SD_CSD_CAPACITY(response); + out_csd->read_block_len = SD_CSD_READ_BL_LEN(response); + break; + default: + ESP_LOGE(TAG, "unknown SD CSD structure version 0x%x", out_csd->csd_ver); + return ESP_ERR_NOT_SUPPORTED; + } + out_csd->card_command_class = SD_CSD_CCC(response); + int read_bl_size = 1 << out_csd->read_block_len; + out_csd->sector_size = MIN(read_bl_size, 512); + if (out_csd->sector_size < read_bl_size) { + out_csd->capacity *= read_bl_size / out_csd->sector_size; + } + int speed = SD_CSD_SPEED(response); + if (speed == SD_CSD_SPEED_50_MHZ) { + out_csd->tr_speed = 50000000; + } else { + out_csd->tr_speed = 25000000; + } + return ESP_OK; +} + +static esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SEND_CSD, + .arg = MMC_ARG_RCA(card->rca), + .flags = SCF_CMD_AC | SCF_RSP_R2 + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + return sdmmc_decode_csd(cmd.response, out_csd); +} + +static esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SELECT_CARD, + .arg = MMC_ARG_RCA(card->rca), + .flags = SCF_CMD_AC | SCF_RSP_R1 + }; + return sdmmc_send_cmd(card, &cmd); +} + +static esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr) +{ + sdmmc_response_t resp = {0xabababab, 0xabababab, 0x12345678, 0x09abcdef}; + resp[2] = __builtin_bswap32(raw_scr[0]); + resp[3] = __builtin_bswap32(raw_scr[1]); + int ver = SCR_STRUCTURE(resp); + if (ver != 0) { + return ESP_ERR_NOT_SUPPORTED; + } + out_scr->sd_spec = SCR_SD_SPEC(resp); + out_scr->bus_width = SCR_SD_BUS_WIDTHS(resp); + return ESP_OK; +} + +static esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr) +{ + size_t datalen = 8; + uint32_t* buf = (uint32_t*) pvPortMallocCaps(datalen, MALLOC_CAP_DMA); + if (buf == NULL) { + return ESP_ERR_NO_MEM; + } + sdmmc_command_t cmd = { + .data = buf, + .datalen = datalen, + .blklen = datalen, + .flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1, + .opcode = SD_APP_SEND_SCR + }; + esp_err_t err = sdmmc_send_app_cmd(card, &cmd); + if (err == ESP_OK) { + buf[0] = (buf[0]); + buf[1] = (buf[1]); + err = sdmmc_decode_scr(buf, out_scr); + } + free(buf); + return err; +} + +static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width) +{ + sdmmc_command_t cmd = { + .opcode = SD_APP_SET_BUS_WIDTH, + .flags = SCF_RSP_R1 | SCF_CMD_AC, + .arg = (width == 4) ? SD_ARG_BUS_WIDTH_4 : SD_ARG_BUS_WIDTH_1 + }; + + return sdmmc_send_app_cmd(card, &cmd); +} + +static esp_err_t sdmmc_send_cmd_stop_transmission(sdmmc_card_t* card, uint32_t* status) +{ + sdmmc_command_t cmd = { + .opcode = MMC_STOP_TRANSMISSION, + .arg = 0, + .flags = SCF_RSP_R1B | SCF_CMD_AC + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err == 0) { + *status = MMC_R1(cmd.response); + } + return err; +} + +static uint32_t get_host_ocr(float voltage) +{ + // TODO: report exact voltage to the card + // For now tell that the host has 2.8-3.6V voltage range + (void) voltage; + return SD_OCR_VOL_MASK; +} + +static esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SEND_STATUS, + .arg = MMC_ARG_RCA(card->rca), + .flags = SCF_CMD_AC | SCF_RSP_R1 + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + if (out_status) { + *out_status = MMC_R1(cmd.response); + } + return ESP_OK; +} + +esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src, + size_t start_block, size_t block_count) +{ + if (start_block + block_count > card->csd.capacity) { + return ESP_ERR_INVALID_SIZE; + } + size_t block_size = card->csd.sector_size; + sdmmc_command_t cmd = { + .flags = SCF_CMD_ADTC | SCF_RSP_R1, + .blklen = block_size, + .data = (void*) src, + .datalen = block_count * block_size + }; + if (block_count == 1) { + cmd.opcode = MMC_WRITE_BLOCK_SINGLE; + } else { + cmd.opcode = MMC_WRITE_BLOCK_MULTIPLE; + } + if (card->ocr & SD_OCR_SDHC_CAP) { + cmd.arg = start_block; + } else { + cmd.arg = start_block * block_size; + } + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + uint32_t status = 0; + size_t count = 0; + while (!(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + err = sdmmc_send_cmd_send_status(card, &status); + if (err != ESP_OK) { + return err; + } + if (++count % 10 == 0) { + ESP_LOGV(TAG, "waiting for card to become ready (%d)", count); + } + } + return ESP_OK; +} + +esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst, + size_t start_block, size_t block_count) +{ + if (start_block + block_count > card->csd.capacity) { + return ESP_ERR_INVALID_SIZE; + } + size_t block_size = card->csd.sector_size; + sdmmc_command_t cmd = { + .flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1, + .blklen = block_size, + .data = (void*) dst, + .datalen = block_count * block_size + }; + if (block_count == 1) { + cmd.opcode = MMC_READ_BLOCK_SINGLE; + } else { + cmd.opcode = MMC_READ_BLOCK_MULTIPLE; + } + if (card->ocr & SD_OCR_SDHC_CAP) { + cmd.arg = start_block; + } else { + cmd.arg = start_block * block_size; + } + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + uint32_t status = 0; + size_t count = 0; + while (!(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + err = sdmmc_send_cmd_send_status(card, &status); + if (err != ESP_OK) { + return err; + } + if (++count % 10 == 0) { + ESP_LOGV(TAG, "waiting for card to become ready (%d)", count); + } + } + return ESP_OK; +} diff --git a/components/sdmmc/test/component.mk b/components/sdmmc/test/component.mk new file mode 100644 index 0000000000..ce464a212a --- /dev/null +++ b/components/sdmmc/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/sdmmc/test/test_sd.c b/components/sdmmc/test/test_sd.c new file mode 100644 index 0000000000..e8fbd25451 --- /dev/null +++ b/components/sdmmc/test/test_sd.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include "unity.h" +#include "driver/gpio.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "esp_log.h" +#include "esp_heap_alloc_caps.h" +#include +#include + + +TEST_CASE("can probe SD", "[sd]") +{ + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + sdmmc_host_init(); + 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); + sdmmc_host_deinit(); + free(card); +} + + +static void do_single_write_read_test(sdmmc_card_t* card, + size_t start_block, size_t block_count) +{ + size_t block_size = card->csd.sector_size; + size_t total_size = block_size * block_count; + printf(" %8d | %3d | %4.1f ", start_block, block_count, total_size / 1024.0f); + uint32_t* buffer = pvPortMallocCaps(total_size, MALLOC_CAP_DMA); + srand(start_block); + for (size_t i = 0; i < total_size / sizeof(buffer[0]); ++i) { + buffer[i] = rand(); + } + struct timeval t_start_wr; + gettimeofday(&t_start_wr, NULL); + TEST_ESP_OK(sdmmc_write_sectors(card, buffer, start_block, block_count)); + struct timeval t_stop_wr; + gettimeofday(&t_stop_wr, NULL); + float time_wr = 1e3f * (t_stop_wr.tv_sec - t_start_wr.tv_sec) + 1e-3f * (t_stop_wr.tv_usec - t_start_wr.tv_usec); + memset(buffer, 0xbb, total_size); + struct timeval t_start_rd; + gettimeofday(&t_start_rd, NULL); + TEST_ESP_OK(sdmmc_read_sectors(card, buffer, start_block, block_count)); + struct timeval t_stop_rd; + gettimeofday(&t_stop_rd, NULL); + float time_rd = 1e3f * (t_stop_rd.tv_sec - t_start_rd.tv_sec) + 1e-3f * (t_stop_rd.tv_usec - t_start_rd.tv_usec); + + printf(" | %6.2f | %.2f | %.2fs | %.2f\n", + time_wr, total_size / (time_wr / 1000) / (1024 * 1024), + time_rd, total_size / (time_rd / 1000) / (1024 * 1024)); + srand(start_block); + for (size_t i = 0; i < total_size / sizeof(buffer[0]); ++i) { + TEST_ASSERT_EQUAL_HEX32(rand(), buffer[i]); + } + free(buffer); +} + +TEST_CASE("can write and read back blocks", "[sd]") +{ + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + config.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_host_init(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + 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); + printf(" sector | count | size(kB) | wr_time(ms) | wr_speed(MB/s) | rd_time(ms) | rd_speed(MB/s)\n"); + do_single_write_read_test(card, 0, 1); + do_single_write_read_test(card, 0, 4); + do_single_write_read_test(card, 1, 16); + do_single_write_read_test(card, 16, 32); + do_single_write_read_test(card, 48, 64); + do_single_write_read_test(card, 128, 128); + do_single_write_read_test(card, card->csd.capacity - 64, 32); + do_single_write_read_test(card, card->csd.capacity - 64, 64); + do_single_write_read_test(card, card->csd.capacity - 8, 1); + do_single_write_read_test(card, card->csd.capacity/2, 1); + do_single_write_read_test(card, card->csd.capacity/2, 4); + do_single_write_read_test(card, card->csd.capacity/2, 8); + do_single_write_read_test(card, card->csd.capacity/2, 16); + do_single_write_read_test(card, card->csd.capacity/2, 32); + do_single_write_read_test(card, card->csd.capacity/2, 64); + do_single_write_read_test(card, card->csd.capacity/2, 128); + free(card); + sdmmc_host_deinit(); +} diff --git a/examples/27_sd_card/Makefile b/examples/27_sd_card/Makefile new file mode 100644 index 0000000000..512e40fa67 --- /dev/null +++ b/examples/27_sd_card/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 := sd_card + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/27_sd_card/README.md b/examples/27_sd_card/README.md new file mode 100644 index 0000000000..4053ad2609 --- /dev/null +++ b/examples/27_sd_card/README.md @@ -0,0 +1,81 @@ +# SD Card example + +This example demonstrates how to use an SD card with ESP32. Example does the following steps: + +1. Use an "all-in-one" `esp_vfs_fat_sdmmc_mount` function to: + - initialize SDMMC peripheral, + - probe and initialize the card connected to SD/MMC slot 1 (HS2_CMD, HS2_CLK, HS2_D0, HS2_D1, HS2_D2, HS2_D3 lines), + - mount FAT filesystem using FATFS library (and format card, if the filesystem can not be mounted), + - register FAT filesystem in VFS, enabling C standard library and POSIX functions to be used. +2. Print information about the card, such as name, type, capacity, and maximum supported frequency. +3. Create a file using `fopen` and write to it using `fprintf`. +4. Rename the file. Before renaming, check if destination file already exists using `stat` function, and remove it using `unlink` function. +5. Open renamed file for reading, read back the line, and print it to the terminal. + +*Note:* despite the name, `sdmmc` component doesn't support MMC/eMMC cards yet. It is also possible to extend `sdmmc` component to support SPI mode with SD cards via SPI peripheral. + +## Hardware + +To run this example, ESP32 development board needs to be connected to SD card as follows: + +ESP32 pin | SD card pin | Notes +--------------|-------------|------------ +GPIO14 (MTMS) | CLK | 10k pullup +GPIO15 (MTDO) | CMD | 10k pullup +GPIO2 | D0 | 10k pullup, pull low to go into download mode +GPIO4 | D1 | 10k pullup; not used in 1-line mode +GPIO12 (MTDI) | D2 | otherwise 10k pullup (see note below!); not used in 1-line mode +GPIO13 (MTCK) | D3 | 10k pullup needed at card side, even in 1-line mode +N/C | CD | +N/C | WP | + +This example doesn't utilize card detect (CD) and write protect (WP) signals from SD card slot. + +### Note about GPIO2 +GPIO2 pin is used as a bootstrapping pin, and should be low to enter UART download mode. One way to do this is to connect GPIO0 and GPIO2 using a jumper, and then the auto-reset circuit on most development boards will pull GPIO2 low along with GPIO2, when entering download mode. + +### Note about GPIO12 + +GPIO12 is used as a bootstrapping pin to select output voltage of an internal regulator which powers the flash chip. + +- For boards which don't use the internal regulator, GPIO12 can be pulled high. +- On boards which use the internal regulator and a 3.3V flash chip, GPIO12 should be pulled up high, which is compatible with SD card operation. +- For boards which use 1.8V flash chip, GPIO12 needs to be low at reset. + * In this case, internal pullup can be enabled using a `gpio_pullup_en(GPIO_NUM_12);` call. Most SD cards work fine when an internal pullup on GPIO12 line is enabled. Note that if ESP32 experiences a power-on reset while the SD card is sending data, high level on GPIO12 can be latched into the bootstrapping register, and ESP32 will enter a boot loop until external reset with correct GPIO12 level is applied. + * Another option is to program flash voltage selection efuses: set `SDIO_TIEH=0` and `SDIO_FORCE=1`. This will permanently select 1.8v output voltage for the internal regulator, and GPIO12 will not be used as a bootstrapping pin anymore. Then it is safe to connect a pullup resistor to GPIO12. This option is suggested for production use. + +## 4-line and 1-line modes + +By default, example code uses the following initializer for SDMMC host peripheral configuration: + +```c++ +sdmmc_host_t host = SDMMC_HOST_DEFAULT(); +``` + +Among other things, this sets `host.flags` to `SDMMC_HOST_FLAG_4BIT`, which means that SD/MMC driver will switch to 4-line mode when initializing the card (initial communication always happens in 1-line mode). If some of D1, D2, D3 pins are not connected to the card, set `host.flags` to `SDMMC_HOST_FLAG_1BIT` — then the SD/MMC driver will not attempt to switch to 4-line mode. +Note that even if D3 line is not connected to the ESP32, it still has to be pulled up at card side, otherwise the card will go into SPI protocol mode. + + +## Example output + +Here is an example console output. In this case a 128MB SDSC card was connected, and `format_if_mount_failed` parameter was set to `true` in the source code. Card was unformatted, so the initial mount has failed. Card was then partitioned, formatted, and mounted again. + +``` +I (1776) example: Initializing SD card +W (1856) vfs_fat_sdmmc: failed to mount card (13) +W (1856) vfs_fat_sdmmc: partitioning card +W (1856) vfs_fat_sdmmc: formatting card +W (2726) vfs_fat_sdmmc: mounting again +I (2736) example: Card info: +I (2736) example: Name: SU128 +I (2736) example: Type: SDSC +I (2736) example: Capacity: 120 MB +I (2736) example: Max clock speed: 25 MHz +I (2736) example: Opening file +I (2756) example: File written +I (2756) example: Renaming file +I (2756) example: Reading file +I (2756) example: Read from file: 'Hello SU128!' +I (2756) example: Card unmounted +``` + diff --git a/examples/27_sd_card/main/component.mk b/examples/27_sd_card/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/27_sd_card/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/27_sd_card/main/sd_card.c b/examples/27_sd_card/main/sd_card.c new file mode 100644 index 0000000000..03d0ec1310 --- /dev/null +++ b/examples/27_sd_card/main/sd_card.c @@ -0,0 +1,107 @@ +/* SD card and FAT filesystem example. + 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 "esp_err.h" +#include "esp_log.h" +#include "esp_vfs_fat.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" + +static const char* TAG = "example"; + +void app_main(void) +{ + ESP_LOGI(TAG, "Initializing SD card"); + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + + // To use 1-line SD mode, uncomment the following line: + // host.flags = SDMMC_HOST_FLAG_1BIT; + + // This initializes the slot without card detect (CD) and write protect (WP) signals. + // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + + // Options for mounting the filesystem. + // If format_if_mount_failed is set to true, SD card will be partitioned and formatted + // in case when mounting fails. + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5 + }; + + // Use settings defined above to initialize SD card and mount FAT filesystem. + // Note: esp_vfs_fat_sdmmc_mount is an all-in-one convenience function. + // Please check its source code and implement error recovery when developing + // production applications. + sdmmc_card_t* card; + esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); + if (ret != ESP_OK) { + if (ret == ESP_FAIL) { + ESP_LOGE(TAG, "Failed to mount filesystem. If you want the card to be formatted, set format_if_mount_failed = true."); + } else { + ESP_LOGE(TAG, "Failed to initialize the card (%d). Make sure SD card lines have pull-up resistors in place.", ret); + } + return; + } + + // Card has been initialized, print its properties + sdmmc_card_print_info(stdout, card); + + // Use POSIX and C standard library functions to work with files. + // First create a file. + ESP_LOGI(TAG, "Opening file"); + FILE* f = fopen("/sdcard/hello.txt", "w"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for writing"); + return; + } + fprintf(f, "Hello %s!\n", card->cid.name); + fclose(f); + ESP_LOGI(TAG, "File written"); + + // Check if destination file exists before renaming + struct stat st; + if (stat("/sdcard/foo.txt", &st) == 0) { + // Delete it if it exists + unlink("/sdcard/foo.txt"); + } + + // Rename original file + ESP_LOGI(TAG, "Renaming file"); + if (rename("/sdcard/hello.txt", "/sdcard/foo.txt") != 0) { + ESP_LOGE(TAG, "Rename failed"); + return; + } + + // Open renamed file for reading + ESP_LOGI(TAG, "Reading file"); + f = fopen("/sdcard/foo.txt", "r"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for reading"); + return; + } + char line[64]; + fgets(line, sizeof(line), f); + fclose(f); + // strip newline + char* pos = strchr(line, '\n'); + if (pos) { + *pos = '\0'; + } + ESP_LOGI(TAG, "Read from file: '%s'", line); + + // All done, unmount partition and disable SDMMC host peripheral + esp_vfs_fat_sdmmc_unmount(); + ESP_LOGI(TAG, "Card unmounted"); +} From 3f889de5ab72e69a54d0076df394797ac87f4500 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 29 Dec 2016 22:21:29 +0800 Subject: [PATCH 091/167] vfs: implement vfs_unregister --- components/vfs/include/esp_vfs.h | 9 +++++++++ components/vfs/vfs.c | 34 ++++++++++++++++++++++++++------ docs/api/vfs.rst | 1 + 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 7dd273fb00..5d9236b54c 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -131,6 +131,15 @@ typedef struct esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx); +/** + * Unregister a virtual filesystem for given path prefix + * + * @param base_path file prefix previously used in esp_vfs_register call + * @return ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for given prefix + * hasn't been registered + */ +esp_err_t esp_vfs_unregister(const char* base_path); + /** * These functions are to be used in newlib syscall table. They will be called by * newlib when it needs to use any of the syscalls. diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index b60c60a818..b166a427bb 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -54,9 +54,6 @@ static size_t s_vfs_count = 0; esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx) { - if (s_vfs_count >= VFS_MAX_COUNT) { - return ESP_ERR_NO_MEM; - } size_t len = strlen(base_path); if (len < 2 || len > ESP_VFS_PATH_MAX) { return ESP_ERR_INVALID_ARG; @@ -68,16 +65,41 @@ esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ct if (entry == NULL) { return ESP_ERR_NO_MEM; } + size_t index; + for (index = 0; index < s_vfs_count; ++index) { + if (s_vfs[index] == NULL) { + break; + } + } + if (index == s_vfs_count) { + if (s_vfs_count >= VFS_MAX_COUNT) { + free(entry); + return ESP_ERR_NO_MEM; + } + ++s_vfs_count; + } + s_vfs[index] = entry; strcpy(entry->path_prefix, base_path); // we have already verified argument length memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t)); entry->path_prefix_len = len; entry->ctx = ctx; - entry->offset = s_vfs_count; - s_vfs[s_vfs_count] = entry; - ++s_vfs_count; + entry->offset = index; return ESP_OK; } +esp_err_t esp_vfs_unregister(const char* base_path) +{ + for (size_t i = 0; i < s_vfs_count; ++i) { + vfs_entry_t* vfs = s_vfs[i]; + if (memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) { + free(vfs); + s_vfs[i] = NULL; + return ESP_OK; + } + } + return ESP_ERR_INVALID_STATE; +} + static const vfs_entry_t* get_vfs_for_fd(int fd) { int index = ((fd & VFS_INDEX_MASK) >> VFS_INDEX_S); diff --git a/docs/api/vfs.rst b/docs/api/vfs.rst index 798aac5492..71550f111c 100644 --- a/docs/api/vfs.rst +++ b/docs/api/vfs.rst @@ -31,6 +31,7 @@ Functions ^^^^^^^^^ .. doxygenfunction:: esp_vfs_register +.. doxygenfunction:: esp_vfs_unregister .. doxygenfunction:: esp_vfs_write .. doxygenfunction:: esp_vfs_lseek .. doxygenfunction:: esp_vfs_read From 6fb430f45eb6aa81f2066995442ee83cdeba203f Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 3 Jan 2017 03:26:25 +0800 Subject: [PATCH 092/167] vfs: add directory APIs --- components/newlib/include/sys/dirent.h | 13 --- components/vfs/include/esp_vfs.h | 34 +++++++ components/vfs/include/sys/dirent.h | 55 ++++++++++ components/vfs/vfs.c | 135 +++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 13 deletions(-) delete mode 100644 components/newlib/include/sys/dirent.h create mode 100644 components/vfs/include/sys/dirent.h diff --git a/components/newlib/include/sys/dirent.h b/components/newlib/include/sys/dirent.h deleted file mode 100644 index a3fb5c02c5..0000000000 --- a/components/newlib/include/sys/dirent.h +++ /dev/null @@ -1,13 +0,0 @@ -/* includes , which is this file. On a - system which supports , this file is overridden by - dirent.h in the libc/sys/.../sys directory. On a system which does - not support , we will get this file which uses #error to force - an error. */ - -#ifdef __cplusplus -extern "C" { -#endif -#error " not supported" -#ifdef __cplusplus -} -#endif diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 5d9236b54c..304750aabd 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -21,6 +21,8 @@ #include #include #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -106,6 +108,38 @@ typedef struct int (*rename_p)(void* ctx, const char *src, const char *dst); int (*rename)(const char *src, const char *dst); }; + union { + DIR* (*opendir_p)(void* ctx, const char* name); + DIR* (*opendir)(const char* name); + }; + union { + struct dirent* (*readdir_p)(void* ctx, DIR* pdir); + struct dirent* (*readdir)(DIR* pdir); + }; + union { + int (*readdir_r_p)(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + int (*readdir_r)(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + }; + union { + long (*telldir_p)(void* ctx, DIR* pdir); + long (*telldir)(DIR* pdir); + }; + union { + void (*seekdir_p)(void* ctx, DIR* pdir, long offset); + void (*seekdir)(DIR* pdir, long offset); + }; + union { + int (*closedir_p)(void* ctx, DIR* pdir); + int (*closedir)(DIR* pdir); + }; + union { + int (*mkdir_p)(void* ctx, const char* name, mode_t mode); + int (*mkdir)(const char* name, mode_t mode); + }; + union { + int (*rmdir_p)(void* ctx, const char* name); + int (*rmdir)(const char* name); + }; } esp_vfs_t; diff --git a/components/vfs/include/sys/dirent.h b/components/vfs/include/sys/dirent.h new file mode 100644 index 0000000000..57b5be5eea --- /dev/null +++ b/components/vfs/include/sys/dirent.h @@ -0,0 +1,55 @@ +// 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. + +#pragma once + +#include +#include + +/** + * This header file provides POSIX-compatible definitions of directory + * access functions and related data types. + * See http://pubs.opengroup.org/onlinepubs/7908799/xsh/dirent.h.html + * for reference. + */ + +/** + * @brief Opaque directory structure + */ +typedef struct { + uint16_t dd_vfs_idx; /*!< VFS index, not to be used by applications */ + uint16_t dd_rsv; /*!< field reserved for future extension */ + /* remaining fields are defined by VFS implementation */ +} DIR; + +/** + * @brief Directory entry structure + */ +struct dirent { + int d_ino; /*!< file number */ + uint8_t d_type; /*!< not defined in POSIX, but present in BSD and Linux */ +#define DT_UNKNOWN 0 +#define DT_REG 1 +#define DT_DIR 2 + char d_name[256]; /*!< zero-terminated file name */ +}; + +DIR* opendir(const char* name); +struct dirent* readdir(DIR* pdir); +long telldir(DIR* pdir); +void seekdir(DIR* pdir, long loc); +void rewinddir(DIR* pdir); +int closedir(DIR* pdir); +int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index b166a427bb..d80972a533 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -163,6 +163,28 @@ static const vfs_entry_t* get_vfs_for_path(const char* path) } +#define CHECK_AND_CALLV(r, pvfs, func, ...) \ + if (pvfs->vfs.func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return; \ + } \ + if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + (*pvfs->vfs.func)(__VA_ARGS__);\ + } + +#define CHECK_AND_CALLP(ret, r, pvfs, func, ...) \ + if (pvfs->vfs.func == NULL) { \ + __errno_r(r) = ENOSYS; \ + return NULL; \ + } \ + if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \ + ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \ + } else { \ + ret = (*pvfs->vfs.func)(__VA_ARGS__);\ + } + int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode) { const vfs_entry_t* vfs = get_vfs_for_path(path); @@ -309,3 +331,116 @@ int esp_vfs_rename(struct _reent *r, const char *src, const char *dst) CHECK_AND_CALL(ret, r, vfs, rename, src_within_vfs, dst_within_vfs); return ret; } + +DIR* opendir(const char* name) +{ + const vfs_entry_t* vfs = get_vfs_for_path(name); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return NULL; + } + const char* path_within_vfs = translate_path(vfs, name); + DIR* ret; + CHECK_AND_CALLP(ret, r, vfs, opendir, path_within_vfs); + if (ret != NULL) { + ret->dd_vfs_idx = vfs->offset << VFS_INDEX_S; + } + return ret; +} + +struct dirent* readdir(DIR* pdir) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = EBADF; + return NULL; + } + struct dirent* ret; + CHECK_AND_CALLP(ret, r, vfs, readdir, pdir); + return ret; +} + +int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + errno = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, readdir_r, pdir, entry, out_dirent); + return ret; +} + +long telldir(DIR* pdir) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + errno = EBADF; + return -1; + } + long ret; + CHECK_AND_CALL(ret, r, vfs, telldir, pdir); + return ret; +} + +void seekdir(DIR* pdir, long loc) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + errno = EBADF; + return; + } + CHECK_AND_CALLV(r, vfs, seekdir, pdir, loc); +} + +void rewinddir(DIR* pdir) +{ + seekdir(pdir, 0); +} + +int closedir(DIR* pdir) +{ + const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx); + struct _reent* r = __getreent(); + if (vfs == NULL) { + errno = EBADF; + return -1; + } + int ret; + CHECK_AND_CALL(ret, r, vfs, closedir, pdir); + return ret; +} + +int mkdir(const char* name, mode_t mode) +{ + const vfs_entry_t* vfs = get_vfs_for_path(name); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, name); + int ret; + CHECK_AND_CALL(ret, r, vfs, mkdir, path_within_vfs, mode); + return ret; +} + +int rmdir(const char* name) +{ + const vfs_entry_t* vfs = get_vfs_for_path(name); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, name); + int ret; + CHECK_AND_CALL(ret, r, vfs, rmdir, path_within_vfs); + return ret; +} From 1440c866fc9e0609accd706c750745104d5252c9 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 29 Dec 2016 17:28:39 +0800 Subject: [PATCH 093/167] fatfs: import version 0.12b --- components/fatfs/component.mk | 2 + components/fatfs/src/00history.txt | 279 ++ components/fatfs/src/00readme.txt | 21 + components/fatfs/src/diskio.c | 225 + components/fatfs/src/diskio.h | 80 + components/fatfs/src/ff.c | 6041 +++++++++++++++++++++++++ components/fatfs/src/ff.h | 366 ++ components/fatfs/src/ffconf.h | 267 ++ components/fatfs/src/integer.h | 38 + components/fatfs/src/option/syscall.c | 151 + components/fatfs/src/option/unicode.c | 17 + 11 files changed, 7487 insertions(+) create mode 100644 components/fatfs/component.mk create mode 100644 components/fatfs/src/00history.txt create mode 100644 components/fatfs/src/00readme.txt create mode 100644 components/fatfs/src/diskio.c create mode 100644 components/fatfs/src/diskio.h create mode 100644 components/fatfs/src/ff.c create mode 100644 components/fatfs/src/ff.h create mode 100644 components/fatfs/src/ffconf.h create mode 100644 components/fatfs/src/integer.h create mode 100644 components/fatfs/src/option/syscall.c create mode 100644 components/fatfs/src/option/unicode.c diff --git a/components/fatfs/component.mk b/components/fatfs/component.mk new file mode 100644 index 0000000000..f2094e7ab9 --- /dev/null +++ b/components/fatfs/component.mk @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS := src +COMPONENT_SRCDIRS := src src/option diff --git a/components/fatfs/src/00history.txt b/components/fatfs/src/00history.txt new file mode 100644 index 0000000000..49aac282b2 --- /dev/null +++ b/components/fatfs/src/00history.txt @@ -0,0 +1,279 @@ +---------------------------------------------------------------------------- + Revision history of FatFs module +---------------------------------------------------------------------------- + +R0.00 (February 26, 2006) + + Prototype. + + + +R0.01 (April 29, 2006) + + The first release. + + + +R0.02 (June 01, 2006) + + Added FAT12 support. + Removed unbuffered mode. + Fixed a problem on small (<32M) partition. + + + +R0.02a (June 10, 2006) + + Added a configuration option (_FS_MINIMUM). + + + +R0.03 (September 22, 2006) + + Added f_rename(). + Changed option _FS_MINIMUM to _FS_MINIMIZE. + + + +R0.03a (December 11, 2006) + + Improved cluster scan algorithm to write files fast. + Fixed f_mkdir() creates incorrect directory on FAT32. + + + +R0.04 (February 04, 2007) + + Added f_mkfs(). + Supported multiple drive system. + Changed some interfaces for multiple drive system. + Changed f_mountdrv() to f_mount(). + + + +R0.04a (April 01, 2007) + + Supported multiple partitions on a physical drive. + Added a capability of extending file size to f_lseek(). + Added minimization level 3. + Fixed an endian sensitive code in f_mkfs(). + + + +R0.04b (May 05, 2007) + + Added a configuration option _USE_NTFLAG. + Added FSINFO support. + Fixed DBCS name can result FR_INVALID_NAME. + Fixed short seek (<= csize) collapses the file object. + + + +R0.05 (August 25, 2007) + + Changed arguments of f_read(), f_write() and f_mkfs(). + Fixed f_mkfs() on FAT32 creates incorrect FSINFO. + Fixed f_mkdir() on FAT32 creates incorrect directory. + + + +R0.05a (February 03, 2008) + + Added f_truncate() and f_utime(). + Fixed off by one error at FAT sub-type determination. + Fixed btr in f_read() can be mistruncated. + Fixed cached sector is not flushed when create and close without write. + + + +R0.06 (April 01, 2008) + + Added fputc(), fputs(), fprintf() and fgets(). + Improved performance of f_lseek() on moving to the same or following cluster. + + + +R0.07 (April 01, 2009) + + Merged Tiny-FatFs as a configuration option. (_FS_TINY) + Added long file name feature. (_USE_LFN) + Added multiple code page feature. (_CODE_PAGE) + Added re-entrancy for multitask operation. (_FS_REENTRANT) + Added auto cluster size selection to f_mkfs(). + Added rewind option to f_readdir(). + Changed result code of critical errors. + Renamed string functions to avoid name collision. + + + +R0.07a (April 14, 2009) + + Septemberarated out OS dependent code on reentrant cfg. + Added multiple sector size feature. + + + +R0.07c (June 21, 2009) + + Fixed f_unlink() can return FR_OK on error. + Fixed wrong cache control in f_lseek(). + Added relative path feature. + Added f_chdir() and f_chdrive(). + Added proper case conversion to extended character. + + + +R0.07e (November 03, 2009) + + Septemberarated out configuration options from ff.h to ffconf.h. + Fixed f_unlink() fails to remove a sub-directory on _FS_RPATH. + Fixed name matching error on the 13 character boundary. + Added a configuration option, _LFN_UNICODE. + Changed f_readdir() to return the SFN with always upper case on non-LFN cfg. + + + +R0.08 (May 15, 2010) + + Added a memory configuration option. (_USE_LFN = 3) + Added file lock feature. (_FS_SHARE) + Added fast seek feature. (_USE_FASTSEEK) + Changed some types on the API, XCHAR->TCHAR. + Changed .fname in the FILINFO structure on Unicode cfg. + String functions support UTF-8 encoding files on Unicode cfg. + + + +R0.08a (August 16, 2010) + + Added f_getcwd(). (_FS_RPATH = 2) + Added sector erase feature. (_USE_ERASE) + Moved file lock semaphore table from fs object to the bss. + Fixed f_mkfs() creates wrong FAT32 volume. + + + +R0.08b (January 15, 2011) + + Fast seek feature is also applied to f_read() and f_write(). + f_lseek() reports required table size on creating CLMP. + Extended format syntax of f_printf(). + Ignores duplicated directory separators in given path name. + + + +R0.09 (September 06, 2011) + + f_mkfs() supports multiple partition to complete the multiple partition feature. + Added f_fdisk(). + + + +R0.09a (August 27, 2012) + + Changed f_open() and f_opendir() reject null object pointer to avoid crash. + Changed option name _FS_SHARE to _FS_LOCK. + Fixed assertion failure due to OS/2 EA on FAT12/16 volume. + + + +R0.09b (January 24, 2013) + + Added f_setlabel() and f_getlabel(). + + + +R0.10 (October 02, 2013) + + Added selection of character encoding on the file. (_STRF_ENCODE) + Added f_closedir(). + Added forced full FAT scan for f_getfree(). (_FS_NOFSINFO) + Added forced mount feature with changes of f_mount(). + Improved behavior of volume auto detection. + Improved write throughput of f_puts() and f_printf(). + Changed argument of f_chdrive(), f_mkfs(), disk_read() and disk_write(). + Fixed f_write() can be truncated when the file size is close to 4GB. + Fixed f_open(), f_mkdir() and f_setlabel() can return incorrect value on error. + + + +R0.10a (January 15, 2014) + + Added arbitrary strings as drive number in the path name. (_STR_VOLUME_ID) + Added a configuration option of minimum sector size. (_MIN_SS) + 2nd argument of f_rename() can have a drive number and it will be ignored. + Fixed f_mount() with forced mount fails when drive number is >= 1. (appeared at R0.10) + Fixed f_close() invalidates the file object without volume lock. + Fixed f_closedir() returns but the volume lock is left acquired. (appeared at R0.10) + Fixed creation of an entry with LFN fails on too many SFN collisions. (appeared at R0.07) + + + +R0.10b (May 19, 2014) + + Fixed a hard error in the disk I/O layer can collapse the directory entry. + Fixed LFN entry is not deleted on delete/rename an object with lossy converted SFN. (appeared at R0.07) + + + +R0.10c (November 09, 2014) + + Added a configuration option for the platforms without RTC. (_FS_NORTC) + Changed option name _USE_ERASE to _USE_TRIM. + Fixed volume label created by Mac OS X cannot be retrieved with f_getlabel(). (appeared at R0.09b) + Fixed a potential problem of FAT access that can appear on disk error. + Fixed null pointer dereference on attempting to delete the root direcotry. (appeared at R0.08) + + + +R0.11 (February 09, 2015) + + Added f_findfirst(), f_findnext() and f_findclose(). (_USE_FIND) + Fixed f_unlink() does not remove cluster chain of the file. (appeared at R0.10c) + Fixed _FS_NORTC option does not work properly. (appeared at R0.10c) + + + +R0.11a (September 05, 2015) + + Fixed wrong media change can lead a deadlock at thread-safe configuration. + Added code page 771, 860, 861, 863, 864, 865 and 869. (_CODE_PAGE) + Removed some code pages actually not exist on the standard systems. (_CODE_PAGE) + Fixed errors in the case conversion teble of code page 437 and 850 (ff.c). + Fixed errors in the case conversion teble of Unicode (cc*.c). + + + +R0.12 (April 12, 2016) + + Added support for exFAT file system. (_FS_EXFAT) + Added f_expand(). (_USE_EXPAND) + Changed some members in FINFO structure and behavior of f_readdir(). + Added an option _USE_CHMOD. + Removed an option _WORD_ACCESS. + Fixed errors in the case conversion table of Unicode (cc*.c). + + + +R0.12a (July 10, 2016) + + Added support for creating exFAT volume with some changes of f_mkfs(). + Added a file open method FA_OPEN_APPEND. An f_lseek() following f_open() is no longer needed. + f_forward() is available regardless of _FS_TINY. + Fixed f_mkfs() creates wrong volume. (appeared at R0.12) + Fixed wrong memory read in create_name(). (appeared at R0.12) + Fixed compilation fails at some configurations, _USE_FASTSEEK and _USE_FORWARD. + + + +R0.12b (September 04, 2016) + + Improved f_rename() to be able to rename objects with the same name but case. + Fixed an error in the case conversion teble of code page 866. (ff.c) + Fixed writing data is truncated at the file offset 4GiB on the exFAT volume. (appeared at R0.12) + Fixed creating a file in the root directory of exFAT volume can fail. (appeared at R0.12) + Fixed f_mkfs() creating exFAT volume with too small cluster size can collapse unallocated memory. (appeared at R0.12) + Fixed wrong object name can be returned when read directory at Unicode cfg. (appeared at R0.12) + Fixed large file allocation/removing on the exFAT volume collapses allocation bitmap. (appeared at R0.12) + Fixed some internal errors in f_expand() and f_lseek(). (appeared at R0.12) + diff --git a/components/fatfs/src/00readme.txt b/components/fatfs/src/00readme.txt new file mode 100644 index 0000000000..42426a4011 --- /dev/null +++ b/components/fatfs/src/00readme.txt @@ -0,0 +1,21 @@ +FatFs Module Source Files R0.12a + + +FILES + + 00readme.txt This file. + history.txt Revision history. + ffconf.h Configuration file for FatFs module. + ff.h Common include file for FatFs and application module. + ff.c FatFs module. + diskio.h Common include file for FatFs and disk I/O module. + diskio.c An example of glue function to attach existing disk I/O module to FatFs. + integer.h Integer type definitions for FatFs. + option Optional external functions. + + + Low level disk I/O module is not included in this archive because the FatFs + module is only a generic file system layer and not depend on any specific + storage device. You have to provide a low level disk I/O module that written + to control the target storage device. + diff --git a/components/fatfs/src/diskio.c b/components/fatfs/src/diskio.c new file mode 100644 index 0000000000..25f5e53bc0 --- /dev/null +++ b/components/fatfs/src/diskio.c @@ -0,0 +1,225 @@ +/*-----------------------------------------------------------------------*/ +/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */ +/*-----------------------------------------------------------------------*/ +/* If a working storage control module is available, it should be */ +/* attached to the FatFs via a glue function rather than modifying it. */ +/* This is an example of glue functions to attach various exsisting */ +/* storage control modules to the FatFs module with a defined API. */ +/*-----------------------------------------------------------------------*/ + +#include "diskio.h" /* FatFs lower layer API */ + +/* Definitions of physical drive number for each drive */ +#define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */ +#define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */ +#define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */ + + +/*-----------------------------------------------------------------------*/ +/* Get Drive Status */ +/*-----------------------------------------------------------------------*/ + +DSTATUS disk_status ( + BYTE pdrv /* Physical drive nmuber to identify the drive */ +) +{ + DSTATUS stat; + int result; + + switch (pdrv) { + case DEV_RAM : + result = RAM_disk_status(); + + // translate the reslut code here + + return stat; + + case DEV_MMC : + result = MMC_disk_status(); + + // translate the reslut code here + + return stat; + + case DEV_USB : + result = USB_disk_status(); + + // translate the reslut code here + + return stat; + } + return STA_NOINIT; +} + + + +/*-----------------------------------------------------------------------*/ +/* Inidialize a Drive */ +/*-----------------------------------------------------------------------*/ + +DSTATUS disk_initialize ( + BYTE pdrv /* Physical drive nmuber to identify the drive */ +) +{ + DSTATUS stat; + int result; + + switch (pdrv) { + case DEV_RAM : + result = RAM_disk_initialize(); + + // translate the reslut code here + + return stat; + + case DEV_MMC : + result = MMC_disk_initialize(); + + // translate the reslut code here + + return stat; + + case DEV_USB : + result = USB_disk_initialize(); + + // translate the reslut code here + + return stat; + } + return STA_NOINIT; +} + + + +/*-----------------------------------------------------------------------*/ +/* Read Sector(s) */ +/*-----------------------------------------------------------------------*/ + +DRESULT disk_read ( + BYTE pdrv, /* Physical drive nmuber to identify the drive */ + BYTE *buff, /* Data buffer to store read data */ + DWORD sector, /* Start sector in LBA */ + UINT count /* Number of sectors to read */ +) +{ + DRESULT res; + int result; + + switch (pdrv) { + case DEV_RAM : + // translate the arguments here + + result = RAM_disk_read(buff, sector, count); + + // translate the reslut code here + + return res; + + case DEV_MMC : + // translate the arguments here + + result = MMC_disk_read(buff, sector, count); + + // translate the reslut code here + + return res; + + case DEV_USB : + // translate the arguments here + + result = USB_disk_read(buff, sector, count); + + // translate the reslut code here + + return res; + } + + return RES_PARERR; +} + + + +/*-----------------------------------------------------------------------*/ +/* Write Sector(s) */ +/*-----------------------------------------------------------------------*/ + +DRESULT disk_write ( + BYTE pdrv, /* Physical drive nmuber to identify the drive */ + const BYTE *buff, /* Data to be written */ + DWORD sector, /* Start sector in LBA */ + UINT count /* Number of sectors to write */ +) +{ + DRESULT res; + int result; + + switch (pdrv) { + case DEV_RAM : + // translate the arguments here + + result = RAM_disk_write(buff, sector, count); + + // translate the reslut code here + + return res; + + case DEV_MMC : + // translate the arguments here + + result = MMC_disk_write(buff, sector, count); + + // translate the reslut code here + + return res; + + case DEV_USB : + // translate the arguments here + + result = USB_disk_write(buff, sector, count); + + // translate the reslut code here + + return res; + } + + return RES_PARERR; +} + + + +/*-----------------------------------------------------------------------*/ +/* Miscellaneous Functions */ +/*-----------------------------------------------------------------------*/ + +DRESULT disk_ioctl ( + BYTE pdrv, /* Physical drive nmuber (0..) */ + BYTE cmd, /* Control code */ + void *buff /* Buffer to send/receive control data */ +) +{ + DRESULT res; + int result; + + switch (pdrv) { + case DEV_RAM : + + // Process of the command for the RAM drive + + return res; + + case DEV_MMC : + + // Process of the command for the MMC/SD card + + return res; + + case DEV_USB : + + // Process of the command the USB drive + + return res; + } + + return RES_PARERR; +} + diff --git a/components/fatfs/src/diskio.h b/components/fatfs/src/diskio.h new file mode 100644 index 0000000000..03e8b7c560 --- /dev/null +++ b/components/fatfs/src/diskio.h @@ -0,0 +1,80 @@ +/*-----------------------------------------------------------------------/ +/ Low level disk interface modlue include file (C)ChaN, 2014 / +/-----------------------------------------------------------------------*/ + +#ifndef _DISKIO_DEFINED +#define _DISKIO_DEFINED + +#ifdef __cplusplus +extern "C" { +#endif + +#include "integer.h" + + +/* Status of Disk Functions */ +typedef BYTE DSTATUS; + +/* Results of Disk Functions */ +typedef enum { + RES_OK = 0, /* 0: Successful */ + RES_ERROR, /* 1: R/W Error */ + RES_WRPRT, /* 2: Write Protected */ + RES_NOTRDY, /* 3: Not Ready */ + RES_PARERR /* 4: Invalid Parameter */ +} DRESULT; + + +/*---------------------------------------*/ +/* Prototypes for disk control functions */ + + +DSTATUS disk_initialize (BYTE pdrv); +DSTATUS disk_status (BYTE pdrv); +DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); +DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); +DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); + + +/* Disk Status Bits (DSTATUS) */ + +#define STA_NOINIT 0x01 /* Drive not initialized */ +#define STA_NODISK 0x02 /* No medium in the drive */ +#define STA_PROTECT 0x04 /* Write protected */ + + +/* Command code for disk_ioctrl fucntion */ + +/* Generic command (Used by FatFs) */ +#define CTRL_SYNC 0 /* Complete pending write process (needed at _FS_READONLY == 0) */ +#define GET_SECTOR_COUNT 1 /* Get media size (needed at _USE_MKFS == 1) */ +#define GET_SECTOR_SIZE 2 /* Get sector size (needed at _MAX_SS != _MIN_SS) */ +#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at _USE_MKFS == 1) */ +#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) */ + +/* Generic command (Not used by FatFs) */ +#define CTRL_POWER 5 /* Get/Set power status */ +#define CTRL_LOCK 6 /* Lock/Unlock media removal */ +#define CTRL_EJECT 7 /* Eject media */ +#define CTRL_FORMAT 8 /* Create physical format on the media */ + +/* MMC/SDC specific ioctl command */ +#define MMC_GET_TYPE 10 /* Get card type */ +#define MMC_GET_CSD 11 /* Get CSD */ +#define MMC_GET_CID 12 /* Get CID */ +#define MMC_GET_OCR 13 /* Get OCR */ +#define MMC_GET_SDSTAT 14 /* Get SD status */ +#define ISDIO_READ 55 /* Read data form SD iSDIO register */ +#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */ +#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */ + +/* ATA/CF specific ioctl command */ +#define ATA_GET_REV 20 /* Get F/W revision */ +#define ATA_GET_MODEL 21 /* Get model name */ +#define ATA_GET_SN 22 /* Get serial number */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/fatfs/src/ff.c b/components/fatfs/src/ff.c new file mode 100644 index 0000000000..ffc5240970 --- /dev/null +++ b/components/fatfs/src/ff.c @@ -0,0 +1,6041 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT file system module R0.12b / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2016, ChaN, all right reserved. +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: + +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +/----------------------------------------------------------------------------*/ + + +#include "ff.h" /* Declarations of FatFs API */ +#include "diskio.h" /* Declarations of device I/O functions */ + + +/*-------------------------------------------------------------------------- + + Module Private Definitions + +---------------------------------------------------------------------------*/ + +#if _FATFS != 68020 /* Revision ID */ +#error Wrong include file (ff.h). +#endif + + +#define ABORT(fs, res) { fp->err = (BYTE)(res); LEAVE_FF(fs, res); } + + +/* Reentrancy related */ +#if _FS_REENTRANT +#if _USE_LFN == 1 +#error Static LFN work area cannot be used at thread-safe configuration +#endif +#define ENTER_FF(fs) { if (!lock_fs(fs)) return FR_TIMEOUT; } +#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; } +#else +#define ENTER_FF(fs) +#define LEAVE_FF(fs, res) return res +#endif + + + +/* Definitions of sector size */ +#if (_MAX_SS < _MIN_SS) || (_MAX_SS != 512 && _MAX_SS != 1024 && _MAX_SS != 2048 && _MAX_SS != 4096) || (_MIN_SS != 512 && _MIN_SS != 1024 && _MIN_SS != 2048 && _MIN_SS != 4096) +#error Wrong sector size configuration +#endif +#if _MAX_SS == _MIN_SS +#define SS(fs) ((UINT)_MAX_SS) /* Fixed sector size */ +#else +#define SS(fs) ((fs)->ssize) /* Variable sector size */ +#endif + + +/* Timestamp */ +#if _FS_NORTC == 1 +#if _NORTC_YEAR < 1980 || _NORTC_YEAR > 2107 || _NORTC_MON < 1 || _NORTC_MON > 12 || _NORTC_MDAY < 1 || _NORTC_MDAY > 31 +#error Invalid _FS_NORTC settings +#endif +#define GET_FATTIME() ((DWORD)(_NORTC_YEAR - 1980) << 25 | (DWORD)_NORTC_MON << 21 | (DWORD)_NORTC_MDAY << 16) +#else +#define GET_FATTIME() get_fattime() +#endif + + +/* File lock controls */ +#if _FS_LOCK != 0 +#if _FS_READONLY +#error _FS_LOCK must be 0 at read-only configuration +#endif +typedef struct { + FATFS *fs; /* Object ID 1, volume (NULL:blank entry) */ + DWORD clu; /* Object ID 2, directory (0:root) */ + DWORD ofs; /* Object ID 3, directory offset */ + WORD ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */ +} FILESEM; +#endif + + + +/* DBCS code ranges and SBCS upper conversion tables */ + +#if _CODE_PAGE == 932 /* Japanese Shift-JIS */ +#define _DF1S 0x81 /* DBC 1st byte range 1 start */ +#define _DF1E 0x9F /* DBC 1st byte range 1 end */ +#define _DF2S 0xE0 /* DBC 1st byte range 2 start */ +#define _DF2E 0xFC /* DBC 1st byte range 2 end */ +#define _DS1S 0x40 /* DBC 2nd byte range 1 start */ +#define _DS1E 0x7E /* DBC 2nd byte range 1 end */ +#define _DS2S 0x80 /* DBC 2nd byte range 2 start */ +#define _DS2E 0xFC /* DBC 2nd byte range 2 end */ + +#elif _CODE_PAGE == 936 /* Simplified Chinese GBK */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x40 +#define _DS1E 0x7E +#define _DS2S 0x80 +#define _DS2E 0xFE + +#elif _CODE_PAGE == 949 /* Korean */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x41 +#define _DS1E 0x5A +#define _DS2S 0x61 +#define _DS2E 0x7A +#define _DS3S 0x81 +#define _DS3E 0xFE + +#elif _CODE_PAGE == 950 /* Traditional Chinese Big5 */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x40 +#define _DS1E 0x7E +#define _DS2S 0xA1 +#define _DS2E 0xFE + +#elif _CODE_PAGE == 437 /* U.S. */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 720 /* Arabic */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 737 /* Greek */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \ + 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xEF,0xF5,0xF0,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 771 /* KBL */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDC,0xDE,0xDE, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFE,0xFF} + +#elif _CODE_PAGE == 775 /* Baltic */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xE0,0xA3,0xA3,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 850 /* Latin 1 */ +#define _DF1S 0 +#define _EXCVT {0x43,0x55,0x45,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x49,0x49,0x49,0x41,0x41, \ + 0x45,0x92,0x92,0x4F,0x4F,0x4F,0x55,0x55,0x59,0x4F,0x55,0x4F,0x9C,0x4F,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0x41,0x41,0x41,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0x41,0x41,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0x45,0x45,0x45,0x49,0x49,0x49,0x49,0xD9,0xDA,0xDB,0xDC,0xDD,0x49,0xDF, \ + 0x4F,0xE1,0x4F,0x4F,0x4F,0x4F,0xE6,0xE8,0xE8,0x55,0x55,0x55,0x59,0x59,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 852 /* Latin 2 */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0xAC, \ + 0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF} + +#elif _CODE_PAGE == 855 /* Cyrillic */ +#define _DF1S 0 +#define _EXCVT {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F, \ + 0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \ + 0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \ + 0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF, \ + 0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 857 /* Turkish */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0x49,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 860 /* Portuguese */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0x8F,0x8E,0x91,0x86,0x80,0x89,0x89,0x92,0x8B,0x8C,0x98,0x8E,0x8F, \ + 0x90,0x91,0x92,0x8C,0x99,0xA9,0x96,0x9D,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x86,0x8B,0x9F,0x96,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 861 /* Icelandic */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x8B,0x8B,0x8D,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x8D,0x55,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xA4,0xA5,0xA6,0xA7,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 862 /* Hebrew */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 863 /* Canadian-French */ +#define _DF1S 0 +#define _EXCVT {0x43,0x55,0x45,0x41,0x41,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x41,0x8F, \ + 0x45,0x45,0x45,0x4F,0x45,0x49,0x55,0x55,0x98,0x4F,0x55,0x9B,0x9C,0x55,0x55,0x9F, \ + 0xA0,0xA1,0x4F,0x55,0xA4,0xA5,0xA6,0xA7,0x49,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 864 /* Arabic */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 865 /* Nordic */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \ + 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \ + 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 866 /* Russian */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 869 /* Greek 2 */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \ + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x86,0x9C,0x8D,0x8F,0x90, \ + 0x91,0x90,0x92,0x95,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \ + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \ + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xA4,0xA5,0xA6,0xD9,0xDA,0xDB,0xDC,0xA7,0xA8,0xDF, \ + 0xA9,0xAA,0xAC,0xAD,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xCF,0xCF,0xD0,0xEF, \ + 0xF0,0xF1,0xD1,0xD2,0xD3,0xF5,0xD4,0xF7,0xF8,0xF9,0xD5,0x96,0x95,0x98,0xFE,0xFF} + +#elif _CODE_PAGE == 1 /* ASCII (for only non-LFN cfg) */ +#if _USE_LFN != 0 +#error Cannot enable LFN without valid code page. +#endif +#define _DF1S 0 + +#else +#error Unknown code page + +#endif + + +/* Character code support macros */ +#define IsUpper(c) (((c)>='A')&&((c)<='Z')) +#define IsLower(c) (((c)>='a')&&((c)<='z')) +#define IsDigit(c) (((c)>='0')&&((c)<='9')) + +#if _DF1S != 0 /* Code page is DBCS */ + +#ifdef _DF2S /* Two 1st byte areas */ +#define IsDBCS1(c) (((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) || ((BYTE)(c) >= _DF2S && (BYTE)(c) <= _DF2E)) +#else /* One 1st byte area */ +#define IsDBCS1(c) ((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) +#endif + +#ifdef _DS3S /* Three 2nd byte areas */ +#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E) || ((BYTE)(c) >= _DS3S && (BYTE)(c) <= _DS3E)) +#else /* Two 2nd byte areas */ +#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E)) +#endif + +#else /* Code page is SBCS */ + +#define IsDBCS1(c) 0 +#define IsDBCS2(c) 0 + +#endif /* _DF1S */ + + +/* File attribute bits (internal use) */ +#define AM_VOL 0x08 /* Volume label */ +#define AM_LFN 0x0F /* LFN entry */ +#define AM_MASK 0x3F /* Mask of defined bits */ + + +/* File access control and file status flags (internal use) */ +#define FA_SEEKEND 0x20 /* Seek to end of the file on file open */ +#define FA_MODIFIED 0x40 /* File has been modified */ +#define FA_DIRTY 0x80 /* FIL.buf[] needs to be written-back */ + + +/* Name status flags */ +#define NSFLAG 11 /* Index of name status byte in fn[] */ +#define NS_LOSS 0x01 /* Out of 8.3 format */ +#define NS_LFN 0x02 /* Force to create LFN entry */ +#define NS_LAST 0x04 /* Last segment */ +#define NS_BODY 0x08 /* Lower case flag (body) */ +#define NS_EXT 0x10 /* Lower case flag (ext) */ +#define NS_DOT 0x20 /* Dot entry */ +#define NS_NOLFN 0x40 /* Do not find LFN */ +#define NS_NONAME 0x80 /* Not followed */ + + +/* Limits and boundaries (differ from specs but correct for real DOS/Windows) */ +#define MAX_FAT12 0xFF5 /* Maximum number of FAT12 clusters */ +#define MAX_FAT16 0xFFF5 /* Maximum number of FAT16 clusters */ +#define MAX_FAT32 0xFFFFFF5 /* Maximum number of FAT32 clusters */ +#define MAX_EXFAT 0x7FFFFFFD /* Maximum number of exFAT clusters (limited by implementation) */ +#define MAX_DIR 0x200000 /* Maximum size of FAT directory */ +#define MAX_DIR_EX 0x10000000 /* Maximum size of exFAT directory */ + + +/* FatFs refers the members in the FAT structures as byte array instead of +/ structure members because the structure is not binary compatible between +/ different platforms */ + +#define BS_JmpBoot 0 /* x86 jump instruction (3-byte) */ +#define BS_OEMName 3 /* OEM name (8-byte) */ +#define BPB_BytsPerSec 11 /* Sector size [byte] (WORD) */ +#define BPB_SecPerClus 13 /* Cluster size [sector] (BYTE) */ +#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (WORD) */ +#define BPB_NumFATs 16 /* Number of FATs (BYTE) */ +#define BPB_RootEntCnt 17 /* Size of root directory area for FAT12/16 [entry] (WORD) */ +#define BPB_TotSec16 19 /* Volume size (16-bit) [sector] (WORD) */ +#define BPB_Media 21 /* Media descriptor byte (BYTE) */ +#define BPB_FATSz16 22 /* FAT size (16-bit) [sector] (WORD) */ +#define BPB_SecPerTrk 24 /* Track size for int13h [sector] (WORD) */ +#define BPB_NumHeads 26 /* Number of heads for int13h (WORD) */ +#define BPB_HiddSec 28 /* Volume offset from top of the drive (DWORD) */ +#define BPB_TotSec32 32 /* Volume size (32-bit) [sector] (DWORD) */ +#define BS_DrvNum 36 /* Physical drive number for int13h (BYTE) */ +#define BS_NTres 37 /* Error flag (BYTE) */ +#define BS_BootSig 38 /* Extended boot signature (BYTE) */ +#define BS_VolID 39 /* Volume serial number (DWORD) */ +#define BS_VolLab 43 /* Volume label string (8-byte) */ +#define BS_FilSysType 54 /* File system type string (8-byte) */ +#define BS_BootCode 62 /* Boot code (448-byte) */ +#define BS_55AA 510 /* Signature word (WORD) */ + +#define BPB_FATSz32 36 /* FAT32: FAT size [sector] (DWORD) */ +#define BPB_ExtFlags32 40 /* FAT32: Extended flags (WORD) */ +#define BPB_FSVer32 42 /* FAT32: File system version (WORD) */ +#define BPB_RootClus32 44 /* FAT32: Root directory cluster (DWORD) */ +#define BPB_FSInfo32 48 /* FAT32: Offset of FSINFO sector (WORD) */ +#define BPB_BkBootSec32 50 /* FAT32: Offset of backup boot sector (WORD) */ +#define BS_DrvNum32 64 /* FAT32: Physical drive number for int13h (BYTE) */ +#define BS_NTres32 65 /* FAT32: Error flag (BYTE) */ +#define BS_BootSig32 66 /* FAT32: Extended boot signature (BYTE) */ +#define BS_VolID32 67 /* FAT32: Volume serial number (DWORD) */ +#define BS_VolLab32 71 /* FAT32: Volume label string (8-byte) */ +#define BS_FilSysType32 82 /* FAT32: File system type string (8-byte) */ +#define BS_BootCode32 90 /* FAT32: Boot code (420-byte) */ + +#define BPB_ZeroedEx 11 /* exFAT: MBZ field (53-byte) */ +#define BPB_VolOfsEx 64 /* exFAT: Volume offset from top of the drive [sector] (QWORD) */ +#define BPB_TotSecEx 72 /* exFAT: Volume size [sector] (QWORD) */ +#define BPB_FatOfsEx 80 /* exFAT: FAT offset from top of the volume [sector] (DWORD) */ +#define BPB_FatSzEx 84 /* exFAT: FAT size [sector] (DWORD) */ +#define BPB_DataOfsEx 88 /* exFAT: Data offset from top of the volume [sector] (DWORD) */ +#define BPB_NumClusEx 92 /* exFAT: Number of clusters (DWORD) */ +#define BPB_RootClusEx 96 /* exFAT: Root directory cluster (DWORD) */ +#define BPB_VolIDEx 100 /* exFAT: Volume serial number (DWORD) */ +#define BPB_FSVerEx 104 /* exFAT: File system version (WORD) */ +#define BPB_VolFlagEx 106 /* exFAT: Volume flags (BYTE) */ +#define BPB_ActFatEx 107 /* exFAT: Active FAT flags (BYTE) */ +#define BPB_BytsPerSecEx 108 /* exFAT: Log2 of sector size in byte (BYTE) */ +#define BPB_SecPerClusEx 109 /* exFAT: Log2 of cluster size in sector (BYTE) */ +#define BPB_NumFATsEx 110 /* exFAT: Number of FATs (BYTE) */ +#define BPB_DrvNumEx 111 /* exFAT: Physical drive number for int13h (BYTE) */ +#define BPB_PercInUseEx 112 /* exFAT: Percent in use (BYTE) */ +#define BPB_RsvdEx 113 /* exFAT: Reserved (7-byte) */ +#define BS_BootCodeEx 120 /* exFAT: Boot code (390-byte) */ + +#define FSI_LeadSig 0 /* FAT32 FSI: Leading signature (DWORD) */ +#define FSI_StrucSig 484 /* FAT32 FSI: Structure signature (DWORD) */ +#define FSI_Free_Count 488 /* FAT32 FSI: Number of free clusters (DWORD) */ +#define FSI_Nxt_Free 492 /* FAT32 FSI: Last allocated cluster (DWORD) */ + +#define MBR_Table 446 /* MBR: Offset of partition table in the MBR */ +#define SZ_PTE 16 /* MBR: Size of a partition table entry */ +#define PTE_Boot 0 /* MBR PTE: Boot indicator */ +#define PTE_StHead 1 /* MBR PTE: Start head */ +#define PTE_StSec 2 /* MBR PTE: Start sector */ +#define PTE_StCyl 3 /* MBR PTE: Start cylinder */ +#define PTE_System 4 /* MBR PTE: System ID */ +#define PTE_EdHead 5 /* MBR PTE: End head */ +#define PTE_EdSec 6 /* MBR PTE: End sector */ +#define PTE_EdCyl 7 /* MBR PTE: End cylinder */ +#define PTE_StLba 8 /* MBR PTE: Start in LBA */ +#define PTE_SizLba 12 /* MBR PTE: Size in LBA */ + +#define DIR_Name 0 /* Short file name (11-byte) */ +#define DIR_Attr 11 /* Attribute (BYTE) */ +#define DIR_NTres 12 /* Lower case flag (BYTE) */ +#define DIR_CrtTime10 13 /* Created time sub-second (BYTE) */ +#define DIR_CrtTime 14 /* Created time (DWORD) */ +#define DIR_LstAccDate 18 /* Last accessed date (WORD) */ +#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (WORD) */ +#define DIR_ModTime 22 /* Modified time (DWORD) */ +#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (WORD) */ +#define DIR_FileSize 28 /* File size (DWORD) */ +#define LDIR_Ord 0 /* LFN entry order and LLE flag (BYTE) */ +#define LDIR_Attr 11 /* LFN attribute (BYTE) */ +#define LDIR_Type 12 /* LFN type (BYTE) */ +#define LDIR_Chksum 13 /* Checksum of the SFN entry (BYTE) */ +#define LDIR_FstClusLO 26 /* Must be zero (WORD) */ +#define XDIR_Type 0 /* Type of exFAT directory entry (BYTE) */ +#define XDIR_NumLabel 1 /* Number of volume label characters (BYTE) */ +#define XDIR_Label 2 /* Volume label (11-WORD) */ +#define XDIR_CaseSum 4 /* Sum of case conversion table (DWORD) */ +#define XDIR_NumSec 1 /* Number of secondary entries (BYTE) */ +#define XDIR_SetSum 2 /* Sum of the set of directory entries (WORD) */ +#define XDIR_Attr 4 /* File attribute (WORD) */ +#define XDIR_CrtTime 8 /* Created time (DWORD) */ +#define XDIR_ModTime 12 /* Modified time (DWORD) */ +#define XDIR_AccTime 16 /* Last accessed time (DWORD) */ +#define XDIR_CrtTime10 20 /* Created time subsecond (BYTE) */ +#define XDIR_ModTime10 21 /* Modified time subsecond (BYTE) */ +#define XDIR_CrtTZ 22 /* Created timezone (BYTE) */ +#define XDIR_ModTZ 23 /* Modified timezone (BYTE) */ +#define XDIR_AccTZ 24 /* Last accessed timezone (BYTE) */ +#define XDIR_GenFlags 33 /* Gneral secondary flags (WORD) */ +#define XDIR_NumName 35 /* Number of file name characters (BYTE) */ +#define XDIR_NameHash 36 /* Hash of file name (WORD) */ +#define XDIR_ValidFileSize 40 /* Valid file size (QWORD) */ +#define XDIR_FstClus 52 /* First cluster of the file data (DWORD) */ +#define XDIR_FileSize 56 /* File/Directory size (QWORD) */ + +#define SZDIRE 32 /* Size of a directory entry */ +#define LLEF 0x40 /* Last long entry flag in LDIR_Ord */ +#define DDEM 0xE5 /* Deleted directory entry mark set to DIR_Name[0] */ +#define RDDEM 0x05 /* Replacement of the character collides with DDEM */ + + + + + +/*-------------------------------------------------------------------------- + + Module Private Work Area + +---------------------------------------------------------------------------*/ + +/* Remark: Variables here without initial value shall be guaranteed zero/null +/ at start-up. If not, either the linker or start-up routine being used is +/ not compliance with C standard. */ + +#if _VOLUMES < 1 || _VOLUMES > 9 +#error Wrong _VOLUMES setting +#endif +static FATFS *FatFs[_VOLUMES]; /* Pointer to the file system objects (logical drives) */ +static WORD Fsid; /* File system mount ID */ + +#if _FS_RPATH != 0 && _VOLUMES >= 2 +static BYTE CurrVol; /* Current drive */ +#endif + +#if _FS_LOCK != 0 +static FILESEM Files[_FS_LOCK]; /* Open object lock semaphores */ +#endif + +#if _USE_LFN == 0 /* Non-LFN configuration */ +#define DEF_NAMBUF +#define INIT_NAMBUF(fs) +#define FREE_NAMBUF() +#else +#if _MAX_LFN < 12 || _MAX_LFN > 255 +#error Wrong _MAX_LFN setting +#endif + +#if _USE_LFN == 1 /* LFN enabled with static working buffer */ +#if _FS_EXFAT +static BYTE DirBuf[SZDIRE*19]; /* Directory entry block scratchpad buffer (19 entries in size) */ +#endif +static WCHAR LfnBuf[_MAX_LFN+1]; /* LFN enabled with static working buffer */ +#define DEF_NAMBUF +#define INIT_NAMBUF(fs) +#define FREE_NAMBUF() + +#elif _USE_LFN == 2 /* LFN enabled with dynamic working buffer on the stack */ +#if _FS_EXFAT +#define DEF_NAMBUF WCHAR lbuf[_MAX_LFN+1]; BYTE dbuf[SZDIRE*19]; +#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; (fs)->dirbuf = dbuf; } +#define FREE_NAMBUF() +#else +#define DEF_NAMBUF WCHAR lbuf[_MAX_LFN+1]; +#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; } +#define FREE_NAMBUF() +#endif + +#elif _USE_LFN == 3 /* LFN enabled with dynamic working buffer on the heap */ +#if _FS_EXFAT +#define DEF_NAMBUF WCHAR *lfn; +#define INIT_NAMBUF(fs) { lfn = ff_memalloc((_MAX_LFN+1)*2 + SZDIRE*19); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; (fs)->dirbuf = (BYTE*)(lfn+_MAX_LFN+1); } +#define FREE_NAMBUF() ff_memfree(lfn) +#else +#define DEF_NAMBUF WCHAR *lfn; +#define INIT_NAMBUF(fs) { lfn = ff_memalloc((_MAX_LFN+1)*2); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; } +#define FREE_NAMBUF() ff_memfree(lfn) +#endif + +#else +#error Wrong _USE_LFN setting +#endif +#endif + +#ifdef _EXCVT +static const BYTE ExCvt[] = _EXCVT; /* Upper conversion table for SBCS extended characters */ +#endif + + + + + + +/*-------------------------------------------------------------------------- + + Module Private Functions + +---------------------------------------------------------------------------*/ + + +/*-----------------------------------------------------------------------*/ +/* Load/Store multi-byte word in the FAT structure */ +/*-----------------------------------------------------------------------*/ + +static +WORD ld_word (const BYTE* ptr) /* Load a 2-byte little-endian word */ +{ + WORD rv; + + rv = ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} + +static +DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian word */ +{ + DWORD rv; + + rv = ptr[3]; + rv = rv << 8 | ptr[2]; + rv = rv << 8 | ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} + +#if _FS_EXFAT +static +QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian word */ +{ + QWORD rv; + + rv = ptr[7]; + rv = rv << 8 | ptr[6]; + rv = rv << 8 | ptr[5]; + rv = rv << 8 | ptr[4]; + rv = rv << 8 | ptr[3]; + rv = rv << 8 | ptr[2]; + rv = rv << 8 | ptr[1]; + rv = rv << 8 | ptr[0]; + return rv; +} +#endif + +#if !_FS_READONLY +static +void st_word (BYTE* ptr, WORD val) /* Store a 2-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} + +static +void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} + +#if _FS_EXFAT +static +void st_qword (BYTE* ptr, QWORD val) /* Store an 8-byte word in little-endian */ +{ + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; val >>= 8; + *ptr++ = (BYTE)val; +} +#endif +#endif /* !_FS_READONLY */ + + + +/*-----------------------------------------------------------------------*/ +/* String functions */ +/*-----------------------------------------------------------------------*/ + +/* Copy memory to memory */ +static +void mem_cpy (void* dst, const void* src, UINT cnt) { + BYTE *d = (BYTE*)dst; + const BYTE *s = (const BYTE*)src; + + if (cnt) { + do *d++ = *s++; while (--cnt); + } +} + +/* Fill memory block */ +static +void mem_set (void* dst, int val, UINT cnt) { + BYTE *d = (BYTE*)dst; + + do *d++ = (BYTE)val; while (--cnt); +} + +/* Compare memory block */ +static +int mem_cmp (const void* dst, const void* src, UINT cnt) { /* ZR:same, NZ:different */ + const BYTE *d = (const BYTE *)dst, *s = (const BYTE *)src; + int r = 0; + + do { + r = *d++ - *s++; + } while (--cnt && r == 0); + + return r; +} + +/* Check if chr is contained in the string */ +static +int chk_chr (const char* str, int chr) { /* NZ:contained, ZR:not contained */ + while (*str && *str != chr) str++; + return *str; +} + + + + +#if _FS_REENTRANT +/*-----------------------------------------------------------------------*/ +/* Request/Release grant to access the volume */ +/*-----------------------------------------------------------------------*/ +static +int lock_fs ( + FATFS* fs /* File system object */ +) +{ + return ff_req_grant(fs->sobj); +} + + +static +void unlock_fs ( + FATFS* fs, /* File system object */ + FRESULT res /* Result code to be returned */ +) +{ + if (fs && res != FR_NOT_ENABLED && res != FR_INVALID_DRIVE && res != FR_TIMEOUT) { + ff_rel_grant(fs->sobj); + } +} + +#endif + + + +#if _FS_LOCK != 0 +/*-----------------------------------------------------------------------*/ +/* File lock control functions */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT chk_lock ( /* Check if the file can be accessed */ + DIR* dp, /* Directory object pointing the file to be checked */ + int acc /* Desired access type (0:Read, 1:Write, 2:Delete/Rename) */ +) +{ + UINT i, be; + + /* Search file semaphore table */ + for (i = be = 0; i < _FS_LOCK; i++) { + if (Files[i].fs) { /* Existing entry */ + if (Files[i].fs == dp->obj.fs && /* Check if the object matched with an open object */ + Files[i].clu == dp->obj.sclust && + Files[i].ofs == dp->dptr) break; + } else { /* Blank entry */ + be = 1; + } + } + if (i == _FS_LOCK) { /* The object is not opened */ + return (be || acc == 2) ? FR_OK : FR_TOO_MANY_OPEN_FILES; /* Is there a blank entry for new object? */ + } + + /* The object has been opened. Reject any open against writing file and all write mode open */ + return (acc || Files[i].ctr == 0x100) ? FR_LOCKED : FR_OK; +} + + +static +int enq_lock (void) /* Check if an entry is available for a new object */ +{ + UINT i; + + for (i = 0; i < _FS_LOCK && Files[i].fs; i++) ; + return (i == _FS_LOCK) ? 0 : 1; +} + + +static +UINT inc_lock ( /* Increment object open counter and returns its index (0:Internal error) */ + DIR* dp, /* Directory object pointing the file to register or increment */ + int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */ +) +{ + UINT i; + + + for (i = 0; i < _FS_LOCK; i++) { /* Find the object */ + if (Files[i].fs == dp->obj.fs && + Files[i].clu == dp->obj.sclust && + Files[i].ofs == dp->dptr) break; + } + + if (i == _FS_LOCK) { /* Not opened. Register it as new. */ + for (i = 0; i < _FS_LOCK && Files[i].fs; i++) ; + if (i == _FS_LOCK) return 0; /* No free entry to register (int err) */ + Files[i].fs = dp->obj.fs; + Files[i].clu = dp->obj.sclust; + Files[i].ofs = dp->dptr; + Files[i].ctr = 0; + } + + if (acc && Files[i].ctr) return 0; /* Access violation (int err) */ + + Files[i].ctr = acc ? 0x100 : Files[i].ctr + 1; /* Set semaphore value */ + + return i + 1; +} + + +static +FRESULT dec_lock ( /* Decrement object open counter */ + UINT i /* Semaphore index (1..) */ +) +{ + WORD n; + FRESULT res; + + + if (--i < _FS_LOCK) { /* Shift index number origin from 0 */ + n = Files[i].ctr; + if (n == 0x100) n = 0; /* If write mode open, delete the entry */ + if (n > 0) n--; /* Decrement read mode open count */ + Files[i].ctr = n; + if (n == 0) Files[i].fs = 0; /* Delete the entry if open count gets zero */ + res = FR_OK; + } else { + res = FR_INT_ERR; /* Invalid index nunber */ + } + return res; +} + + +static +void clear_lock ( /* Clear lock entries of the volume */ + FATFS *fs +) +{ + UINT i; + + for (i = 0; i < _FS_LOCK; i++) { + if (Files[i].fs == fs) Files[i].fs = 0; + } +} + +#endif /* _FS_LOCK != 0 */ + + + +/*-----------------------------------------------------------------------*/ +/* Move/Flush disk access window in the file system object */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT sync_window ( /* Returns FR_OK or FR_DISK_ERROR */ + FATFS* fs /* File system object */ +) +{ + DWORD wsect; + UINT nf; + FRESULT res = FR_OK; + + + if (fs->wflag) { /* Write back the sector if it is dirty */ + wsect = fs->winsect; /* Current sector number */ + if (disk_write(fs->drv, fs->win, wsect, 1) != RES_OK) { + res = FR_DISK_ERR; + } else { + fs->wflag = 0; + if (wsect - fs->fatbase < fs->fsize) { /* Is it in the FAT area? */ + for (nf = fs->n_fats; nf >= 2; nf--) { /* Reflect the change to all FAT copies */ + wsect += fs->fsize; + disk_write(fs->drv, fs->win, wsect, 1); + } + } + } + } + return res; +} +#endif + + +static +FRESULT move_window ( /* Returns FR_OK or FR_DISK_ERROR */ + FATFS* fs, /* File system object */ + DWORD sector /* Sector number to make appearance in the fs->win[] */ +) +{ + FRESULT res = FR_OK; + + + if (sector != fs->winsect) { /* Window offset changed? */ +#if !_FS_READONLY + res = sync_window(fs); /* Write-back changes */ +#endif + if (res == FR_OK) { /* Fill sector window with new data */ + if (disk_read(fs->drv, fs->win, sector, 1) != RES_OK) { + sector = 0xFFFFFFFF; /* Invalidate window if data is not reliable */ + res = FR_DISK_ERR; + } + fs->winsect = sector; + } + } + return res; +} + + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Synchronize file system and strage device */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT sync_fs ( /* FR_OK:succeeded, !=0:error */ + FATFS* fs /* File system object */ +) +{ + FRESULT res; + + + res = sync_window(fs); + if (res == FR_OK) { + /* Update FSInfo sector if needed */ + if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { + /* Create FSInfo structure */ + mem_set(fs->win, 0, SS(fs)); + st_word(fs->win + BS_55AA, 0xAA55); + st_dword(fs->win + FSI_LeadSig, 0x41615252); + st_dword(fs->win + FSI_StrucSig, 0x61417272); + st_dword(fs->win + FSI_Free_Count, fs->free_clst); + st_dword(fs->win + FSI_Nxt_Free, fs->last_clst); + /* Write it into the FSInfo sector */ + fs->winsect = fs->volbase + 1; + disk_write(fs->drv, fs->win, fs->winsect, 1); + fs->fsi_flag = 0; + } + /* Make sure that no pending write process in the physical drive */ + if (disk_ioctl(fs->drv, CTRL_SYNC, 0) != RES_OK) res = FR_DISK_ERR; + } + + return res; +} + +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Get sector# from cluster# */ +/*-----------------------------------------------------------------------*/ + +static +DWORD clust2sect ( /* !=0:Sector number, 0:Failed (invalid cluster#) */ + FATFS* fs, /* File system object */ + DWORD clst /* Cluster# to be converted */ +) +{ + clst -= 2; + if (clst >= fs->n_fatent - 2) return 0; /* Invalid cluster# */ + return clst * fs->csize + fs->database; +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT access - Read value of a FAT entry */ +/*-----------------------------------------------------------------------*/ + +static +DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluster status */ + _FDID* obj, /* Corresponding object */ + DWORD clst /* Cluster number to get the value */ +) +{ + UINT wc, bc; + DWORD val; + FATFS *fs = obj->fs; + + + if (clst < 2 || clst >= fs->n_fatent) { /* Check if in valid range */ + val = 1; /* Internal error */ + + } else { + val = 0xFFFFFFFF; /* Default value falls on disk error */ + + switch (fs->fs_type) { + case FS_FAT12 : + bc = (UINT)clst; bc += bc / 2; + if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break; + wc = fs->win[bc++ % SS(fs)]; + if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break; + wc |= fs->win[bc % SS(fs)] << 8; + val = (clst & 1) ? (wc >> 4) : (wc & 0xFFF); + break; + + case FS_FAT16 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))) != FR_OK) break; + val = ld_word(fs->win + clst * 2 % SS(fs)); + break; + + case FS_FAT32 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break; + val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x0FFFFFFF; + break; +#if _FS_EXFAT + case FS_EXFAT : + if (obj->objsize) { + DWORD cofs = clst - obj->sclust; /* Offset from start cluster */ + DWORD clen = (DWORD)((obj->objsize - 1) / SS(fs)) / fs->csize; /* Number of clusters - 1 */ + + if (obj->stat == 2) { /* Is there no valid chain on the FAT? */ + if (cofs <= clen) { + val = (cofs == clen) ? 0x7FFFFFFF : clst + 1; /* Generate the value */ + break; + } + } + if (obj->stat == 3 && cofs < obj->n_cont) { /* Is it in the contiguous part? */ + val = clst + 1; /* Generate the value */ + break; + } + if (obj->stat != 2) { /* Get value from FAT if FAT chain is valid */ + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break; + val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x7FFFFFFF; + break; + } + } + /* go next */ +#endif + default: + val = 1; /* Internal error */ + } + } + + return val; +} + + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT access - Change value of a FAT entry */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT put_fat ( /* FR_OK(0):succeeded, !=0:error */ + FATFS* fs, /* Corresponding file system object */ + DWORD clst, /* FAT index number (cluster number) to be changed */ + DWORD val /* New value to be set to the entry */ +) +{ + UINT bc; + BYTE *p; + FRESULT res = FR_INT_ERR; + + + if (clst >= 2 && clst < fs->n_fatent) { /* Check if in valid range */ + switch (fs->fs_type) { + case FS_FAT12 : /* Bitfield items */ + bc = (UINT)clst; bc += bc / 2; + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = fs->win + bc++ % SS(fs); + *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; + fs->wflag = 1; + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = fs->win + bc % SS(fs); + *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); + fs->wflag = 1; + break; + + case FS_FAT16 : /* WORD aligned items */ + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))); + if (res != FR_OK) break; + st_word(fs->win + clst * 2 % SS(fs), (WORD)val); + fs->wflag = 1; + break; + + case FS_FAT32 : /* DWORD aligned items */ +#if _FS_EXFAT + case FS_EXFAT : +#endif + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))); + if (res != FR_OK) break; + if (!_FS_EXFAT || fs->fs_type != FS_EXFAT) { + val = (val & 0x0FFFFFFF) | (ld_dword(fs->win + clst * 4 % SS(fs)) & 0xF0000000); + } + st_dword(fs->win + clst * 4 % SS(fs), val); + fs->wflag = 1; + break; + } + } + return res; +} + +#endif /* !_FS_READONLY */ + + + + +#if _FS_EXFAT && !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* exFAT: Accessing FAT and Allocation Bitmap */ +/*-----------------------------------------------------------------------*/ + +/*---------------------------------------------*/ +/* exFAT: Find a contiguous free cluster block */ +/*---------------------------------------------*/ + +static +DWORD find_bitmap ( /* 0:No free cluster, 2..:Free cluster found, 0xFFFFFFFF:Disk error */ + FATFS* fs, /* File system object */ + DWORD clst, /* Cluster number to scan from */ + DWORD ncl /* Number of contiguous clusters to find (1..) */ +) +{ + BYTE bm, bv; + UINT i; + DWORD val, scl, ctr; + + + clst -= 2; /* The first bit in the bitmap corresponds to cluster #2 */ + if (clst >= fs->n_fatent - 2) clst = 0; + scl = val = clst; ctr = 0; + for (;;) { + if (move_window(fs, fs->database + val / 8 / SS(fs)) != FR_OK) return 0xFFFFFFFF; /* (assuming bitmap is located top of the cluster heap) */ + i = val / 8 % SS(fs); bm = 1 << (val % 8); + do { + do { + bv = fs->win[i] & bm; bm <<= 1; /* Get bit value */ + if (++val >= fs->n_fatent - 2) { /* Next cluster (with wrap-around) */ + val = 0; bm = 0; i = 4096; + } + if (!bv) { /* Is it a free cluster? */ + if (++ctr == ncl) return scl + 2; /* Check run length */ + } else { + scl = val; ctr = 0; /* Encountered a live cluster, restart to scan */ + } + if (val == clst) return 0; /* All cluster scanned? */ + } while (bm); + bm = 1; + } while (++i < SS(fs)); + } +} + + +/*------------------------------------*/ +/* exFAT: Set/Clear a block of bitmap */ +/*------------------------------------*/ + +static +FRESULT change_bitmap ( + FATFS* fs, /* File system object */ + DWORD clst, /* Cluster number to change from */ + DWORD ncl, /* Number of clusters to be changed */ + int bv /* bit value to be set (0 or 1) */ +) +{ + BYTE bm; + UINT i; + DWORD sect; + + + clst -= 2; /* The first bit corresponds to cluster #2 */ + sect = fs->database + clst / 8 / SS(fs); /* Sector address (assuming bitmap is located top of the cluster heap) */ + i = clst / 8 % SS(fs); /* Byte offset in the sector */ + bm = 1 << (clst % 8); /* Bit mask in the byte */ + for (;;) { + if (move_window(fs, sect++) != FR_OK) return FR_DISK_ERR; + do { + do { + if (bv == (int)((fs->win[i] & bm) != 0)) return FR_INT_ERR; /* Is the bit expected value? */ + fs->win[i] ^= bm; /* Flip the bit */ + fs->wflag = 1; + if (--ncl == 0) return FR_OK; /* All bits processed? */ + } while (bm <<= 1); /* Next bit */ + bm = 1; + } while (++i < SS(fs)); /* Next byte */ + i = 0; + } +} + + +/*---------------------------------------------*/ +/* Complement contiguous part of the FAT chain */ +/*---------------------------------------------*/ + +static +FRESULT fill_fat_chain ( + _FDID* obj /* Pointer to the corresponding object */ +) +{ + FRESULT res; + DWORD cl, n; + + if (obj->stat == 3) { /* Has the object been changed 'fragmented'? */ + for (cl = obj->sclust, n = obj->n_cont; n; cl++, n--) { /* Create cluster chain on the FAT */ + res = put_fat(obj->fs, cl, cl + 1); + if (res != FR_OK) return res; + } + obj->stat = 0; /* Change status 'FAT chain is valid' */ + } + return FR_OK; +} + +#endif /* _FS_EXFAT && !_FS_READONLY */ + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT handling - Remove a cluster chain */ +/*-----------------------------------------------------------------------*/ +static +FRESULT remove_chain ( /* FR_OK(0):succeeded, !=0:error */ + _FDID* obj, /* Corresponding object */ + DWORD clst, /* Cluster to remove a chain from */ + DWORD pclst /* Previous cluster of clst (0:an entire chain) */ +) +{ + FRESULT res = FR_OK; + DWORD nxt; + FATFS *fs = obj->fs; +#if _FS_EXFAT || _USE_TRIM + DWORD scl = clst, ecl = clst; +#endif +#if _USE_TRIM + DWORD rt[2]; +#endif + + if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Check if in valid range */ + + /* Mark the previous cluster 'EOC' on the FAT if it exists */ + if (pclst && (!_FS_EXFAT || fs->fs_type != FS_EXFAT || obj->stat != 2)) { + res = put_fat(fs, pclst, 0xFFFFFFFF); + if (res != FR_OK) return res; + } + + /* Remove the chain */ + do { + nxt = get_fat(obj, clst); /* Get cluster status */ + if (nxt == 0) break; /* Empty cluster? */ + if (nxt == 1) return FR_INT_ERR; /* Internal error? */ + if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error? */ + if (!_FS_EXFAT || fs->fs_type != FS_EXFAT) { + res = put_fat(fs, clst, 0); /* Mark the cluster 'free' on the FAT */ + if (res != FR_OK) return res; + } + if (fs->free_clst < fs->n_fatent - 2) { /* Update FSINFO */ + fs->free_clst++; + fs->fsi_flag |= 1; + } +#if _FS_EXFAT || _USE_TRIM + if (ecl + 1 == nxt) { /* Is next cluster contiguous? */ + ecl = nxt; + } else { /* End of contiguous cluster block */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */ + if (res != FR_OK) return res; + } +#endif +#if _USE_TRIM + rt[0] = clust2sect(fs, scl); /* Start sector */ + rt[1] = clust2sect(fs, ecl) + fs->csize - 1; /* End sector */ + disk_ioctl(fs->drv, CTRL_TRIM, rt); /* Inform device the block can be erased */ +#endif + scl = ecl = nxt; + } +#endif + clst = nxt; /* Next cluster */ + } while (clst < fs->n_fatent); /* Repeat while not the last link */ + +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + if (pclst == 0) { /* Does object have no chain? */ + obj->stat = 0; /* Change the object status 'initial' */ + } else { + if (obj->stat == 3 && pclst >= obj->sclust && pclst <= obj->sclust + obj->n_cont) { /* Did the chain got contiguous? */ + obj->stat = 2; /* Change the object status 'contiguous' */ + } + } + } +#endif + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT handling - Stretch a chain or Create a new chain */ +/*-----------------------------------------------------------------------*/ +static +DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */ + _FDID* obj, /* Corresponding object */ + DWORD clst /* Cluster# to stretch, 0:Create a new chain */ +) +{ + DWORD cs, ncl, scl; + FRESULT res; + FATFS *fs = obj->fs; + + + if (clst == 0) { /* Create a new chain */ + scl = fs->last_clst; /* Get suggested cluster to start from */ + if (scl == 0 || scl >= fs->n_fatent) scl = 1; + } + else { /* Stretch current chain */ + cs = get_fat(obj, clst); /* Check the cluster status */ + if (cs < 2) return 1; /* Invalid value */ + if (cs == 0xFFFFFFFF) return cs; /* A disk error occurred */ + if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */ + scl = clst; + } + +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + ncl = find_bitmap(fs, scl, 1); /* Find a free cluster */ + if (ncl == 0 || ncl == 0xFFFFFFFF) return ncl; /* No free cluster or hard error? */ + res = change_bitmap(fs, ncl, 1, 1); /* Mark the cluster 'in use' */ + if (res == FR_INT_ERR) return 1; + if (res == FR_DISK_ERR) return 0xFFFFFFFF; + if (clst == 0) { /* Is it a new chain? */ + obj->stat = 2; /* Set status 'contiguous chain' */ + } else { /* This is a stretched chain */ + if (obj->stat == 2 && ncl != scl + 1) { /* Is the chain got fragmented? */ + obj->n_cont = scl - obj->sclust; /* Set size of the contiguous part */ + obj->stat = 3; /* Change status 'just fragmented' */ + } + } + } else +#endif + { /* On the FAT12/16/32 volume */ + ncl = scl; /* Start cluster */ + for (;;) { + ncl++; /* Next cluster */ + if (ncl >= fs->n_fatent) { /* Check wrap-around */ + ncl = 2; + if (ncl > scl) return 0; /* No free cluster */ + } + cs = get_fat(obj, ncl); /* Get the cluster status */ + if (cs == 0) break; /* Found a free cluster */ + if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* An error occurred */ + if (ncl == scl) return 0; /* No free cluster */ + } + } + + if (_FS_EXFAT && fs->fs_type == FS_EXFAT && obj->stat == 2) { /* Is it a contiguous chain? */ + res = FR_OK; /* FAT does not need to be written */ + } else { + res = put_fat(fs, ncl, 0xFFFFFFFF); /* Mark the new cluster 'EOC' */ + if (res == FR_OK && clst) { + res = put_fat(fs, clst, ncl); /* Link it from the previous one if needed */ + } + } + + if (res == FR_OK) { /* Update FSINFO if function succeeded. */ + fs->last_clst = ncl; + if (fs->free_clst < fs->n_fatent - 2) fs->free_clst--; + fs->fsi_flag |= 1; + } else { + ncl = (res == FR_DISK_ERR) ? 0xFFFFFFFF : 1; /* Failed. Create error status */ + } + + return ncl; /* Return new cluster number or error status */ +} + +#endif /* !_FS_READONLY */ + + + + +#if _USE_FASTSEEK +/*-----------------------------------------------------------------------*/ +/* FAT handling - Convert offset into cluster with link map table */ +/*-----------------------------------------------------------------------*/ + +static +DWORD clmt_clust ( /* <2:Error, >=2:Cluster number */ + FIL* fp, /* Pointer to the file object */ + FSIZE_t ofs /* File offset to be converted to cluster# */ +) +{ + DWORD cl, ncl, *tbl; + FATFS *fs = fp->obj.fs; + + + tbl = fp->cltbl + 1; /* Top of CLMT */ + cl = (DWORD)(ofs / SS(fs) / fs->csize); /* Cluster order from top of the file */ + for (;;) { + ncl = *tbl++; /* Number of cluters in the fragment */ + if (ncl == 0) return 0; /* End of table? (error) */ + if (cl < ncl) break; /* In this fragment? */ + cl -= ncl; tbl++; /* Next fragment */ + } + return cl + *tbl; /* Return the cluster number */ +} + +#endif /* _USE_FASTSEEK */ + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Set directory index */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp, /* Pointer to directory object */ + DWORD ofs /* Offset of directory table */ +) +{ + DWORD csz, clst; + FATFS *fs = dp->obj.fs; + + + if (ofs >= (DWORD)((_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR) || ofs % SZDIRE) { /* Check range of offset and alignment */ + return FR_INT_ERR; + } + dp->dptr = ofs; /* Set current offset */ + clst = dp->obj.sclust; /* Table start cluster (0:root) */ + if (clst == 0 && fs->fs_type >= FS_FAT32) { /* Replace cluster# 0 with root cluster# */ + clst = fs->dirbase; + if (_FS_EXFAT) dp->obj.stat = 0; /* exFAT: Root dir has an FAT chain */ + } + + if (clst == 0) { /* Static table (root-directory in FAT12/16) */ + if (ofs / SZDIRE >= fs->n_rootdir) return FR_INT_ERR; /* Is index out of range? */ + dp->sect = fs->dirbase; + + } else { /* Dynamic table (sub-directory or root-directory in FAT32+) */ + csz = (DWORD)fs->csize * SS(fs); /* Bytes per cluster */ + while (ofs >= csz) { /* Follow cluster chain */ + clst = get_fat(&dp->obj, clst); /* Get next cluster */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Reached to end of table or internal error */ + ofs -= csz; + } + dp->sect = clust2sect(fs, clst); + } + dp->clust = clst; /* Current cluster# */ + if (!dp->sect) return FR_INT_ERR; + dp->sect += ofs / SS(fs); /* Sector# of the directory entry */ + dp->dir = fs->win + (ofs % SS(fs)); /* Pointer to the entry in the win[] */ + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Move directory table index next */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */ + DIR* dp, /* Pointer to the directory object */ + int stretch /* 0: Do not stretch table, 1: Stretch table if needed */ +) +{ + DWORD ofs, clst; + FATFS *fs = dp->obj.fs; +#if !_FS_READONLY + UINT n; +#endif + + ofs = dp->dptr + SZDIRE; /* Next entry */ + if (!dp->sect || ofs >= (DWORD)((_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR)) return FR_NO_FILE; /* Report EOT when offset has reached max value */ + + if (ofs % SS(fs) == 0) { /* Sector changed? */ + dp->sect++; /* Next sector */ + + if (!dp->clust) { /* Static table */ + if (ofs / SZDIRE >= fs->n_rootdir) { /* Report EOT if it reached end of static table */ + dp->sect = 0; return FR_NO_FILE; + } + } + else { /* Dynamic table */ + if ((ofs / SS(fs) & (fs->csize - 1)) == 0) { /* Cluster changed? */ + clst = get_fat(&dp->obj, dp->clust); /* Get next cluster */ + if (clst <= 1) return FR_INT_ERR; /* Internal error */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst >= fs->n_fatent) { /* Reached end of dynamic table */ +#if !_FS_READONLY + if (!stretch) { /* If no stretch, report EOT */ + dp->sect = 0; return FR_NO_FILE; + } + clst = create_chain(&dp->obj, dp->clust); /* Allocate a cluster */ + if (clst == 0) return FR_DENIED; /* No free cluster */ + if (clst == 1) return FR_INT_ERR; /* Internal error */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + /* Clean-up the stretched table */ + if (_FS_EXFAT) dp->obj.stat |= 4; /* The directory needs to be updated */ + if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */ + mem_set(fs->win, 0, SS(fs)); /* Clear window buffer */ + for (n = 0, fs->winsect = clust2sect(fs, clst); n < fs->csize; n++, fs->winsect++) { /* Fill the new cluster with 0 */ + fs->wflag = 1; + if (sync_window(fs) != FR_OK) return FR_DISK_ERR; + } + fs->winsect -= n; /* Restore window offset */ +#else + if (!stretch) dp->sect = 0; /* If no stretch, report EOT (this is to suppress warning) */ + dp->sect = 0; return FR_NO_FILE; /* Report EOT */ +#endif + } + dp->clust = clst; /* Initialize data for new cluster */ + dp->sect = clust2sect(fs, clst); + } + } + } + dp->dptr = ofs; /* Current entry */ + dp->dir = fs->win + ofs % SS(fs); /* Pointer to the entry in the win[] */ + + return FR_OK; +} + + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Directory handling - Reserve a block of directory entries */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_alloc ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp, /* Pointer to the directory object */ + UINT nent /* Number of contiguous entries to allocate */ +) +{ + FRESULT res; + UINT n; + FATFS *fs = dp->obj.fs; + + + res = dir_sdi(dp, 0); + if (res == FR_OK) { + n = 0; + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; +#if _FS_EXFAT + if ((fs->fs_type == FS_EXFAT) ? (int)((dp->dir[XDIR_Type] & 0x80) == 0) : (int)(dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0)) { +#else + if (dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0) { +#endif + if (++n == nent) break; /* A block of contiguous free entries is found */ + } else { + n = 0; /* Not a blank entry. Restart to search */ + } + res = dir_next(dp, 1); + } while (res == FR_OK); /* Next entry with table stretch enabled */ + } + + if (res == FR_NO_FILE) res = FR_DENIED; /* No directory entry to allocate */ + return res; +} + +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* FAT: Directory handling - Load/Store start cluster number */ +/*-----------------------------------------------------------------------*/ + +static +DWORD ld_clust ( /* Returns the top cluster value of the SFN entry */ + FATFS* fs, /* Pointer to the fs object */ + const BYTE* dir /* Pointer to the key entry */ +) +{ + DWORD cl; + + cl = ld_word(dir + DIR_FstClusLO); + if (fs->fs_type == FS_FAT32) { + cl |= (DWORD)ld_word(dir + DIR_FstClusHI) << 16; + } + + return cl; +} + + +#if !_FS_READONLY +static +void st_clust ( + FATFS* fs, /* Pointer to the fs object */ + BYTE* dir, /* Pointer to the key entry */ + DWORD cl /* Value to be set */ +) +{ + st_word(dir + DIR_FstClusLO, (WORD)cl); + if (fs->fs_type == FS_FAT32) { + st_word(dir + DIR_FstClusHI, (WORD)(cl >> 16)); + } +} +#endif + + + +#if _USE_LFN != 0 +/*------------------------------------------------------------------------*/ +/* FAT-LFN: LFN handling */ +/*------------------------------------------------------------------------*/ +static +const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* Offset of LFN characters in the directory entry */ + + +/*--------------------------------------------------------*/ +/* FAT-LFN: Compare a part of file name with an LFN entry */ +/*--------------------------------------------------------*/ +static +int cmp_lfn ( /* 1:matched, 0:not matched */ + const WCHAR* lfnbuf, /* Pointer to the LFN working buffer to be compared */ + BYTE* dir /* Pointer to the directory entry containing the part of LFN */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */ + + i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */ + + for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */ + uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */ + if (wc) { + if (i >= _MAX_LFN || ff_wtoupper(uc) != ff_wtoupper(lfnbuf[i++])) { /* Compare it */ + return 0; /* Not matched */ + } + wc = uc; + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } + + if ((dir[LDIR_Ord] & LLEF) && wc && lfnbuf[i]) return 0; /* Last segment matched but different length */ + + return 1; /* The part of LFN matched */ +} + + +#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 || _USE_LABEL || _FS_EXFAT +/*-----------------------------------------------------*/ +/* FAT-LFN: Pick a part of file name from an LFN entry */ +/*-----------------------------------------------------*/ +static +int pick_lfn ( /* 1:succeeded, 0:buffer overflow or invalid LFN entry */ + WCHAR* lfnbuf, /* Pointer to the LFN working buffer */ + BYTE* dir /* Pointer to the LFN entry */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */ + + i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */ + + for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */ + uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */ + if (wc) { + if (i >= _MAX_LFN) return 0; /* Buffer overflow? */ + lfnbuf[i++] = wc = uc; /* Store it */ + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } + + if (dir[LDIR_Ord] & LLEF) { /* Put terminator if it is the last LFN part */ + if (i >= _MAX_LFN) return 0; /* Buffer overflow? */ + lfnbuf[i] = 0; + } + + return 1; /* The part of LFN is valid */ +} +#endif + + +#if !_FS_READONLY +/*-----------------------------------------*/ +/* FAT-LFN: Create an entry of LFN entries */ +/*-----------------------------------------*/ +static +void put_lfn ( + const WCHAR* lfn, /* Pointer to the LFN */ + BYTE* dir, /* Pointer to the LFN entry to be created */ + BYTE ord, /* LFN order (1-20) */ + BYTE sum /* Checksum of the corresponding SFN */ +) +{ + UINT i, s; + WCHAR wc; + + + dir[LDIR_Chksum] = sum; /* Set checksum */ + dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */ + dir[LDIR_Type] = 0; + st_word(dir + LDIR_FstClusLO, 0); + + i = (ord - 1) * 13; /* Get offset in the LFN working buffer */ + s = wc = 0; + do { + if (wc != 0xFFFF) wc = lfn[i++]; /* Get an effective character */ + st_word(dir + LfnOfs[s], wc); /* Put it */ + if (wc == 0) wc = 0xFFFF; /* Padding characters for left locations */ + } while (++s < 13); + if (wc == 0xFFFF || !lfn[i]) ord |= LLEF; /* Last LFN part is the start of LFN sequence */ + dir[LDIR_Ord] = ord; /* Set the LFN order */ +} + +#endif /* !_FS_READONLY */ +#endif /* _USE_LFN != 0 */ + + + +#if _USE_LFN != 0 && !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT-LFN: Create a Numbered SFN */ +/*-----------------------------------------------------------------------*/ + +static +void gen_numname ( + BYTE* dst, /* Pointer to the buffer to store numbered SFN */ + const BYTE* src, /* Pointer to SFN */ + const WCHAR* lfn, /* Pointer to LFN */ + UINT seq /* Sequence number */ +) +{ + BYTE ns[8], c; + UINT i, j; + WCHAR wc; + DWORD sr; + + + mem_cpy(dst, src, 11); + + if (seq > 5) { /* In case of many collisions, generate a hash number instead of sequential number */ + sr = seq; + while (*lfn) { /* Create a CRC */ + wc = *lfn++; + for (i = 0; i < 16; i++) { + sr = (sr << 1) + (wc & 1); + wc >>= 1; + if (sr & 0x10000) sr ^= 0x11021; + } + } + seq = (UINT)sr; + } + + /* itoa (hexdecimal) */ + i = 7; + do { + c = (BYTE)((seq % 16) + '0'); + if (c > '9') c += 7; + ns[i--] = c; + seq /= 16; + } while (seq); + ns[i] = '~'; + + /* Append the number */ + for (j = 0; j < i && dst[j] != ' '; j++) { + if (IsDBCS1(dst[j])) { + if (j == i - 1) break; + j++; + } + } + do { + dst[j++] = (i < 8) ? ns[i++] : ' '; + } while (j < 8); +} +#endif /* _USE_LFN != 0 && !_FS_READONLY */ + + + +#if _USE_LFN != 0 +/*-----------------------------------------------------------------------*/ +/* FAT-LFN: Calculate checksum of an SFN entry */ +/*-----------------------------------------------------------------------*/ + +static +BYTE sum_sfn ( + const BYTE* dir /* Pointer to the SFN entry */ +) +{ + BYTE sum = 0; + UINT n = 11; + + do sum = (sum >> 1) + (sum << 7) + *dir++; while (--n); + return sum; +} + +#endif /* _USE_LFN != 0 */ + + + +#if _FS_EXFAT +/*-----------------------------------------------------------------------*/ +/* exFAT: Checksum */ +/*-----------------------------------------------------------------------*/ + +static +WORD xdir_sum ( /* Get checksum of the directoly block */ + const BYTE* dir /* Directory entry block to be calculated */ +) +{ + UINT i, szblk; + WORD sum; + + + szblk = (dir[XDIR_NumSec] + 1) * SZDIRE; + for (i = sum = 0; i < szblk; i++) { + if (i == XDIR_SetSum) { /* Skip sum field */ + i++; + } else { + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + dir[i]; + } + } + return sum; +} + + + +static +WORD xname_sum ( /* Get check sum (to be used as hash) of the name */ + const WCHAR* name /* File name to be calculated */ +) +{ + WCHAR chr; + WORD sum = 0; + + + while ((chr = *name++) != 0) { + chr = ff_wtoupper(chr); /* File name needs to be ignored case */ + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr & 0xFF); + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr >> 8); + } + return sum; +} + + +#if !_FS_READONLY && _USE_MKFS +static +DWORD xsum32 ( + BYTE dat, /* Data to be sumed */ + DWORD sum /* Previous value */ +) +{ + sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + dat; + return sum; +} +#endif + + +#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 +/*------------------------------------------------------*/ +/* exFAT: Get object information from a directory block */ +/*------------------------------------------------------*/ + +static +void get_xdir_info ( + BYTE* dirb, /* Pointer to the direcotry entry block 85+C0+C1s */ + FILINFO* fno /* Buffer to store the extracted file information */ +) +{ + UINT di, si; + WCHAR w; +#if !_LFN_UNICODE + UINT nc; +#endif + + /* Get file name */ +#if _LFN_UNICODE + if (dirb[XDIR_NumName] <= _MAX_LFN) { + for (si = SZDIRE * 2, di = 0; di < dirb[XDIR_NumName]; si += 2, di++) { + if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */ + w = ld_word(dirb + si); /* Get a character */ + fno->fname[di] = w; /* Store it */ + } + } else { + di = 0; /* Buffer overflow and inaccessible object */ + } +#else + for (si = SZDIRE * 2, di = nc = 0; nc < dirb[XDIR_NumName]; si += 2, nc++) { + if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */ + w = ld_word(dirb + si); /* Get a character */ + w = ff_convert(w, 0); /* Unicode -> OEM */ + if (w == 0) { di = 0; break; } /* Could not be converted and inaccessible object */ + if (_DF1S && w >= 0x100) { /* Put 1st byte if it is a DBC (always false at SBCS cfg) */ + fno->fname[di++] = (char)(w >> 8); + } + if (di >= _MAX_LFN) { di = 0; break; } /* Buffer overflow and inaccessible object */ + fno->fname[di++] = (char)w; + } +#endif + if (di == 0) fno->fname[di++] = '?'; /* Inaccessible object? */ + fno->fname[di] = 0; /* Terminate file name */ + + fno->altname[0] = 0; /* No SFN */ + fno->fattrib = dirb[XDIR_Attr]; /* Attribute */ + fno->fsize = (fno->fattrib & AM_DIR) ? 0 : ld_qword(dirb + XDIR_FileSize); /* Size */ + fno->ftime = ld_word(dirb + XDIR_ModTime + 0); /* Time */ + fno->fdate = ld_word(dirb + XDIR_ModTime + 2); /* Date */ +} + +#endif /* _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 */ + + +/*-----------------------------------*/ +/* exFAT: Get a directry entry block */ +/*-----------------------------------*/ + +static +FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ + DIR* dp /* Pointer to the reading direcotry object pointing the 85 entry */ +) +{ + FRESULT res; + UINT i, nent; + BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory direcotry entry block 85+C0+C1s */ + + + /* Load 85 entry */ + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != 0x85) return FR_INT_ERR; + mem_cpy(dirb, dp->dir, SZDIRE); + nent = dirb[XDIR_NumSec] + 1; + + /* Load C0 entry */ + res = dir_next(dp, 0); + if (res != FR_OK) return res; + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != 0xC0) return FR_INT_ERR; + mem_cpy(dirb + SZDIRE, dp->dir, SZDIRE); + + /* Load C1 entries */ + if (nent < 3 || nent > 19) return FR_NO_FILE; + i = SZDIRE * 2; nent *= SZDIRE; + do { + res = dir_next(dp, 0); + if (res != FR_OK) return res; + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != 0xC1) return FR_INT_ERR; + mem_cpy(dirb + i, dp->dir, SZDIRE); + i += SZDIRE; + } while (i < nent); + + /* Sanity check */ + if (xdir_sum(dirb) != ld_word(dirb + XDIR_SetSum)) return FR_INT_ERR; + + return FR_OK; +} + + +#if !_FS_READONLY || _FS_RPATH != 0 +/*------------------------------------------------*/ +/* exFAT: Load the object's directory entry block */ +/*------------------------------------------------*/ +static +FRESULT load_obj_dir ( + DIR* dp, /* Blank directory object to be used to access containing direcotry */ + const _FDID* obj /* Object with containing directory information */ +) +{ + FRESULT res; + + + /* Open object containing directory */ + dp->obj.fs = obj->fs; + dp->obj.sclust = obj->c_scl; + dp->obj.stat = (BYTE)obj->c_size; + dp->obj.objsize = obj->c_size & 0xFFFFFF00; + dp->blk_ofs = obj->c_ofs; + + res = dir_sdi(dp, dp->blk_ofs); /* Goto the block location */ + if (res == FR_OK) { + res = load_xdir(dp); /* Load the object's entry block */ + } + return res; +} +#endif + + +#if !_FS_READONLY +/*-----------------------------------------------*/ +/* exFAT: Store the directory block to the media */ +/*-----------------------------------------------*/ +static +FRESULT store_xdir ( + DIR* dp /* Pointer to the direcotry object */ +) +{ + FRESULT res; + UINT nent; + BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the direcotry entry block 85+C0+C1s */ + + /* Create set sum */ + st_word(dirb + XDIR_SetSum, xdir_sum(dirb)); + nent = dirb[XDIR_NumSec] + 1; + + /* Store the set of directory to the volume */ + res = dir_sdi(dp, dp->blk_ofs); + while (res == FR_OK) { + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) break; + mem_cpy(dp->dir, dirb, SZDIRE); + dp->obj.fs->wflag = 1; + if (--nent == 0) break; + dirb += SZDIRE; + res = dir_next(dp, 0); + } + return (res == FR_OK || res == FR_DISK_ERR) ? res : FR_INT_ERR; +} + + + +/*-------------------------------------------*/ +/* exFAT: Create a new directory enrty block */ +/*-------------------------------------------*/ + +static +void create_xdir ( + BYTE* dirb, /* Pointer to the direcotry entry block buffer */ + const WCHAR* lfn /* Pointer to the nul terminated file name */ +) +{ + UINT i; + BYTE nb, nc; + WCHAR chr; + + + mem_set(dirb, 0, 2 * SZDIRE); /* Initialize 85+C0 entry */ + dirb[XDIR_Type] = 0x85; + dirb[XDIR_Type + SZDIRE] = 0xC0; + st_word(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */ + + i = SZDIRE * 2; /* C1 offset */ + nc = 0; nb = 1; chr = 1; + do { + dirb[i++] = 0xC1; dirb[i++] = 0; /* Entry type C1 */ + do { /* Fill name field */ + if (chr && (chr = lfn[nc]) != 0) nc++; /* Get a character if exist */ + st_word(dirb + i, chr); i += 2; /* Store it */ + } while (i % SZDIRE); + nb++; + } while (lfn[nc]); /* Fill next entry if any char follows */ + + dirb[XDIR_NumName] = nc; /* Set name length */ + dirb[XDIR_NumSec] = nb; /* Set number of C0+C1s */ +} + +#endif /* !_FS_READONLY */ +#endif /* _FS_EXFAT */ + + + +#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 || _USE_LABEL || _FS_EXFAT +/*-----------------------------------------------------------------------*/ +/* Read an object from the directory */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_read ( + DIR* dp, /* Pointer to the directory object */ + int vol /* Filtered by 0:file/directory or 1:volume label */ +) +{ + FRESULT res = FR_NO_FILE; + FATFS *fs = dp->obj.fs; + BYTE a, c; +#if _USE_LFN != 0 + BYTE ord = 0xFF, sum = 0xFF; +#endif + + while (dp->sect) { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + c = dp->dir[DIR_Name]; /* Test for the entry type */ + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of the directory */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + if (_USE_LABEL && vol) { + if (c == 0x83) break; /* Volume label entry? */ + } else { + if (c == 0x85) { /* Start of the file entry block? */ + dp->blk_ofs = dp->dptr; /* Get location of the block */ + res = load_xdir(dp); /* Load the entry block */ + if (res == FR_OK) { + dp->obj.attr = fs->dirbuf[XDIR_Attr] & AM_MASK; /* Get attribute */ + } + break; + } + } + } else +#endif + { /* On the FAT12/16/32 volume */ + dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK; /* Get attribute */ +#if _USE_LFN != 0 /* LFN configuration */ + if (c == DDEM || c == '.' || (int)((a & ~AM_ARC) == AM_VOL) != vol) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (c & LLEF) { /* Is it start of an LFN sequence? */ + sum = dp->dir[LDIR_Chksum]; + c &= (BYTE)~LLEF; ord = c; + dp->blk_ofs = dp->dptr; + } + /* Check LFN validity and capture it */ + ord = (c == ord && sum == dp->dir[LDIR_Chksum] && pick_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF; + } else { /* An SFN entry is found */ + if (ord || sum != sum_sfn(dp->dir)) { /* Is there a valid LFN? */ + dp->blk_ofs = 0xFFFFFFFF; /* It has no LFN. */ + } + break; + } + } +#else /* Non LFN configuration */ + if (c != DDEM && c != '.' && a != AM_LFN && (int)((a & ~AM_ARC) == AM_VOL) == vol) { /* Is it a valid entry? */ + break; + } +#endif + } + res = dir_next(dp, 0); /* Next entry */ + if (res != FR_OK) break; + } + + if (res != FR_OK) dp->sect = 0; /* Terminate the read operation on error or EOT */ + return res; +} + +#endif /* _FS_MINIMIZE <= 1 || _USE_LABEL || _FS_RPATH >= 2 */ + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Find an object in the directory */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */ + DIR* dp /* Pointer to the directory object with the file name */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; + BYTE c; +#if _USE_LFN != 0 + BYTE a, ord, sum; +#endif + + res = dir_sdi(dp, 0); /* Rewind directory object */ + if (res != FR_OK) return res; +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + BYTE nc; + UINT di, ni; + WORD hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */ + + while ((res = dir_read(dp, 0)) == FR_OK) { /* Read an item */ + if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip the comparison if hash value mismatched */ + for (nc = fs->dirbuf[XDIR_NumName], di = SZDIRE * 2, ni = 0; nc; nc--, di += 2, ni++) { /* Compare the name */ + if ((di % SZDIRE) == 0) di += 2; + if (ff_wtoupper(ld_word(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break; + } + if (nc == 0 && !fs->lfnbuf[ni]) break; /* Name matched? */ + } + return res; + } +#endif + /* On the FAT12/16/32 volume */ +#if _USE_LFN != 0 + ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ +#endif + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + c = dp->dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ +#if _USE_LFN != 0 /* LFN configuration */ + dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK; + if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */ + ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (!(dp->fn[NSFLAG] & NS_NOLFN)) { + if (c & LLEF) { /* Is it start of LFN sequence? */ + sum = dp->dir[LDIR_Chksum]; + c &= (BYTE)~LLEF; ord = c; /* LFN start order */ + dp->blk_ofs = dp->dptr; /* Start offset of LFN */ + } + /* Check validity of the LFN entry and compare it with given name */ + ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF; + } + } else { /* An SFN entry is found */ + if (!ord && sum == sum_sfn(dp->dir)) break; /* LFN matched? */ + if (!(dp->fn[NSFLAG] & NS_LOSS) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* SFN matched? */ + ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ + } + } +#else /* Non LFN configuration */ + dp->obj.attr = dp->dir[DIR_Attr] & AM_MASK; + if (!(dp->dir[DIR_Attr] & AM_VOL) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */ +#endif + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK); + + return res; +} + + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Register an object to the directory */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */ + DIR* dp /* Target directory with object name to be created */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; +#if _USE_LFN != 0 /* LFN configuration */ + UINT n, nlen, nent; + BYTE sn[12], sum; + + + if (dp->fn[NSFLAG] & (NS_DOT | NS_NONAME)) return FR_INVALID_NAME; /* Check name validity */ + for (nlen = 0; fs->lfnbuf[nlen]; nlen++) ; /* Get lfn length */ + +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + DIR dj; + + nent = (nlen + 14) / 15 + 2; /* Number of entries to allocate (85+C0+C1s) */ + res = dir_alloc(dp, nent); /* Allocate entries */ + if (res != FR_OK) return res; + dp->blk_ofs = dp->dptr - SZDIRE * (nent - 1); /* Set block position */ + + if (dp->obj.sclust != 0 && (dp->obj.stat & 4)) { /* Has the sub-directory been stretched? */ + dp->obj.stat &= 3; + dp->obj.objsize += (DWORD)fs->csize * SS(fs); /* Increase object size by cluster size */ + res = fill_fat_chain(&dp->obj); /* Complement FAT chain if needed */ + if (res != FR_OK) return res; + res = load_obj_dir(&dj, &dp->obj); + if (res != FR_OK) return res; /* Load the object status */ + st_qword(fs->dirbuf + XDIR_FileSize, dp->obj.objsize); /* Update the allocation status */ + st_qword(fs->dirbuf + XDIR_ValidFileSize, dp->obj.objsize); + fs->dirbuf[XDIR_GenFlags] = dp->obj.stat | 1; + res = store_xdir(&dj); /* Store the object status */ + if (res != FR_OK) return res; + } + + create_xdir(fs->dirbuf, fs->lfnbuf); /* Create on-memory directory block to be written later */ + return FR_OK; + } +#endif + /* On the FAT12/16/32 volume */ + mem_cpy(sn, dp->fn, 12); + if (sn[NSFLAG] & NS_LOSS) { /* When LFN is out of 8.3 format, generate a numbered name */ + dp->fn[NSFLAG] = NS_NOLFN; /* Find only SFN */ + for (n = 1; n < 100; n++) { + gen_numname(dp->fn, sn, fs->lfnbuf, n); /* Generate a numbered name */ + res = dir_find(dp); /* Check if the name collides with existing SFN */ + if (res != FR_OK) break; + } + if (n == 100) return FR_DENIED; /* Abort if too many collisions */ + if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */ + dp->fn[NSFLAG] = sn[NSFLAG]; + } + + /* Create an SFN with/without LFNs. */ + nent = (sn[NSFLAG] & NS_LFN) ? (nlen + 12) / 13 + 1 : 1; /* Number of entries to allocate */ + res = dir_alloc(dp, nent); /* Allocate entries */ + if (res == FR_OK && --nent) { /* Set LFN entry if needed */ + res = dir_sdi(dp, dp->dptr - nent * SZDIRE); + if (res == FR_OK) { + sum = sum_sfn(dp->fn); /* Checksum value of the SFN tied to the LFN */ + do { /* Store LFN entries in bottom first */ + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + put_lfn(fs->lfnbuf, dp->dir, (BYTE)nent, sum); + fs->wflag = 1; + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK && --nent); + } + } + +#else /* Non LFN configuration */ + res = dir_alloc(dp, 1); /* Allocate an entry for SFN */ + +#endif + + /* Set SFN entry */ + if (res == FR_OK) { + res = move_window(fs, dp->sect); + if (res == FR_OK) { + mem_set(dp->dir, 0, SZDIRE); /* Clean the entry */ + mem_cpy(dp->dir + DIR_Name, dp->fn, 11); /* Put SFN */ +#if _USE_LFN != 0 + dp->dir[DIR_NTres] = dp->fn[NSFLAG] & (NS_BODY | NS_EXT); /* Put NT flag */ +#endif + fs->wflag = 1; + } + } + + return res; +} + +#endif /* !_FS_READONLY */ + + + +#if !_FS_READONLY && _FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Remove an object from the directory */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */ + DIR* dp /* Directory object pointing the entry to be removed */ +) +{ + FRESULT res; + FATFS *fs = dp->obj.fs; +#if _USE_LFN != 0 /* LFN configuration */ + DWORD last = dp->dptr; + + res = (dp->blk_ofs == 0xFFFFFFFF) ? FR_OK : dir_sdi(dp, dp->blk_ofs); /* Goto top of the entry block if LFN is exist */ + if (res == FR_OK) { + do { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + /* Mark an entry 'deleted' */ + if (_FS_EXFAT && fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + dp->dir[XDIR_Type] &= 0x7F; + } else { /* On the FAT12/16/32 volume */ + dp->dir[DIR_Name] = DDEM; + } + fs->wflag = 1; + if (dp->dptr >= last) break; /* If reached last entry then all entries of the object has been deleted. */ + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR; + } +#else /* Non LFN configuration */ + + res = move_window(fs, dp->sect); + if (res == FR_OK) { + dp->dir[DIR_Name] = DDEM; + fs->wflag = 1; + } +#endif + + return res; +} + +#endif /* !_FS_READONLY && _FS_MINIMIZE == 0 */ + + + +#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 +/*-----------------------------------------------------------------------*/ +/* Get file information from directory entry */ +/*-----------------------------------------------------------------------*/ + +static +void get_fileinfo ( /* No return code */ + DIR* dp, /* Pointer to the directory object */ + FILINFO* fno /* Pointer to the file information to be filled */ +) +{ + UINT i, j; + TCHAR c; + DWORD tm; +#if _USE_LFN != 0 + WCHAR w, lfv; + FATFS *fs = dp->obj.fs; +#endif + + + fno->fname[0] = 0; /* Invaidate file info */ + if (!dp->sect) return; /* Exit if read pointer has reached end of directory */ + +#if _USE_LFN != 0 /* LFN configuration */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + get_xdir_info(fs->dirbuf, fno); + return; + } else +#endif + { /* On the FAT12/16/32 volume */ + if (dp->blk_ofs != 0xFFFFFFFF) { /* Get LFN if available */ + i = j = 0; + while ((w = fs->lfnbuf[j++]) != 0) { /* Get an LFN character */ +#if !_LFN_UNICODE + w = ff_convert(w, 0); /* Unicode -> OEM */ + if (w == 0) { i = 0; break; } /* No LFN if it could not be converted */ + if (_DF1S && w >= 0x100) { /* Put 1st byte if it is a DBC (always false at SBCS cfg) */ + fno->fname[i++] = (char)(w >> 8); + } +#endif + if (i >= _MAX_LFN) { i = 0; break; } /* No LFN if buffer overflow */ + fno->fname[i++] = (TCHAR)w; + } + fno->fname[i] = 0; /* Terminate the LFN */ + } + } + + i = j = 0; + lfv = fno->fname[i]; /* LFN is exist if non-zero */ + while (i < 11) { /* Copy name body and extension */ + c = (TCHAR)dp->dir[i++]; + if (c == ' ') continue; /* Skip padding spaces */ + if (c == RDDEM) c = (TCHAR)DDEM; /* Restore replaced DDEM character */ + if (i == 9) { /* Insert a . if extension is exist */ + if (!lfv) fno->fname[j] = '.'; + fno->altname[j++] = '.'; + } +#if _LFN_UNICODE + if (IsDBCS1(c) && i != 8 && i != 11 && IsDBCS2(dp->dir[i])) { + c = c << 8 | dp->dir[i++]; + } + c = ff_convert(c, 1); /* OEM -> Unicode */ + if (!c) c = '?'; +#endif + fno->altname[j] = c; + if (!lfv) { + if (IsUpper(c) && (dp->dir[DIR_NTres] & (i >= 9 ? NS_EXT : NS_BODY))) { + c += 0x20; /* To lower */ + } + fno->fname[j] = c; + } + j++; + } + if (!lfv) { + fno->fname[j] = 0; + if (!dp->dir[DIR_NTres]) j = 0; /* Altname is no longer needed if neither LFN nor case info is exist. */ + } + fno->altname[j] = 0; /* Terminate the SFN */ + +#else /* Non-LFN configuration */ + i = j = 0; + while (i < 11) { /* Copy name body and extension */ + c = (TCHAR)dp->dir[i++]; + if (c == ' ') continue; /* Skip padding spaces */ + if (c == RDDEM) c = (TCHAR)DDEM; /* Restore replaced DDEM character */ + if (i == 9) fno->fname[j++] = '.'; /* Insert a . if extension is exist */ + fno->fname[j++] = c; + } + fno->fname[j] = 0; +#endif + + fno->fattrib = dp->dir[DIR_Attr]; /* Attribute */ + fno->fsize = ld_dword(dp->dir + DIR_FileSize); /* Size */ + tm = ld_dword(dp->dir + DIR_ModTime); /* Timestamp */ + fno->ftime = (WORD)tm; fno->fdate = (WORD)(tm >> 16); +} + +#endif /* _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 */ + + + +#if _USE_FIND && _FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Pattern matching */ +/*-----------------------------------------------------------------------*/ + +static +WCHAR get_achar ( /* Get a character and advances ptr 1 or 2 */ + const TCHAR** ptr /* Pointer to pointer to the SBCS/DBCS/Unicode string */ +) +{ +#if !_LFN_UNICODE + WCHAR chr; + + chr = (BYTE)*(*ptr)++; /* Get a byte */ + if (IsLower(chr)) chr -= 0x20; /* To upper ASCII char */ +#ifdef _EXCVT + if (chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */ +#else + if (IsDBCS1(chr) && IsDBCS2(**ptr)) { /* Get DBC 2nd byte if needed */ + chr = chr << 8 | (BYTE)*(*ptr)++; + } +#endif + return chr; +#else + return ff_wtoupper(*(*ptr)++); /* Get a word and to upper */ +#endif +} + + +static +int pattern_matching ( /* 0:not matched, 1:matched */ + const TCHAR* pat, /* Matching pattern */ + const TCHAR* nam, /* String to be tested */ + int skip, /* Number of pre-skip chars (number of ?s) */ + int inf /* Infinite search (* specified) */ +) +{ + const TCHAR *pp, *np; + WCHAR pc, nc; + int nm, nx; + + + while (skip--) { /* Pre-skip name chars */ + if (!get_achar(&nam)) return 0; /* Branch mismatched if less name chars */ + } + if (!*pat && inf) return 1; /* (short circuit) */ + + do { + pp = pat; np = nam; /* Top of pattern and name to match */ + for (;;) { + if (*pp == '?' || *pp == '*') { /* Wildcard? */ + nm = nx = 0; + do { /* Analyze the wildcard chars */ + if (*pp++ == '?') nm++; else nx = 1; + } while (*pp == '?' || *pp == '*'); + if (pattern_matching(pp, np, nm, nx)) return 1; /* Test new branch (recurs upto number of wildcard blocks in the pattern) */ + nc = *np; break; /* Branch mismatched */ + } + pc = get_achar(&pp); /* Get a pattern char */ + nc = get_achar(&np); /* Get a name char */ + if (pc != nc) break; /* Branch mismatched? */ + if (pc == 0) return 1; /* Branch matched? (matched at end of both strings) */ + } + get_achar(&nam); /* nam++ */ + } while (inf && nc); /* Retry until end of name if infinite search is specified */ + + return 0; +} + +#endif /* _USE_FIND && _FS_MINIMIZE <= 1 */ + + + +/*-----------------------------------------------------------------------*/ +/* Pick a top segment and create the object name in directory form */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */ + DIR* dp, /* Pointer to the directory object */ + const TCHAR** path /* Pointer to pointer to the segment in the path string */ +) +{ +#if _USE_LFN != 0 /* LFN configuration */ + BYTE b, cf; + WCHAR w, *lfn; + UINT i, ni, si, di; + const TCHAR *p; + + /* Create LFN in Unicode */ + p = *path; lfn = dp->obj.fs->lfnbuf; si = di = 0; + for (;;) { + w = p[si++]; /* Get a character */ + if (w < ' ') break; /* Break if end of the path name */ + if (w == '/' || w == '\\') { /* Break if a separator is found */ + while (p[si] == '/' || p[si] == '\\') si++; /* Skip duplicated separator if exist */ + break; + } + if (di >= _MAX_LFN) return FR_INVALID_NAME; /* Reject too long name */ +#if !_LFN_UNICODE + w &= 0xFF; + if (IsDBCS1(w)) { /* Check if it is a DBC 1st byte (always false on SBCS cfg) */ + b = (BYTE)p[si++]; /* Get 2nd byte */ + w = (w << 8) + b; /* Create a DBC */ + if (!IsDBCS2(b)) return FR_INVALID_NAME; /* Reject invalid sequence */ + } + w = ff_convert(w, 1); /* Convert ANSI/OEM to Unicode */ + if (!w) return FR_INVALID_NAME; /* Reject invalid code */ +#endif + if (w < 0x80 && chk_chr("\"*:<>\?|\x7F", w)) return FR_INVALID_NAME; /* Reject illegal characters for LFN */ + lfn[di++] = w; /* Store the Unicode character */ + } + *path = &p[si]; /* Return pointer to the next segment */ + cf = (w < ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */ +#if _FS_RPATH != 0 + if ((di == 1 && lfn[di - 1] == '.') || + (di == 2 && lfn[di - 1] == '.' && lfn[di - 2] == '.')) { /* Is this segment a dot name? */ + lfn[di] = 0; + for (i = 0; i < 11; i++) /* Create dot name for SFN entry */ + dp->fn[i] = (i < di) ? '.' : ' '; + dp->fn[i] = cf | NS_DOT; /* This is a dot entry */ + return FR_OK; + } +#endif + while (di) { /* Snip off trailing spaces and dots if exist */ + w = lfn[di - 1]; + if (w != ' ' && w != '.') break; + di--; + } + lfn[di] = 0; /* LFN is created */ + if (di == 0) return FR_INVALID_NAME; /* Reject nul name */ + + /* Create SFN in directory form */ + mem_set(dp->fn, ' ', 11); + for (si = 0; lfn[si] == ' ' || lfn[si] == '.'; si++) ; /* Strip leading spaces and dots */ + if (si) cf |= NS_LOSS | NS_LFN; + while (di && lfn[di - 1] != '.') di--; /* Find extension (di<=si: no extension) */ + + i = b = 0; ni = 8; + for (;;) { + w = lfn[si++]; /* Get an LFN character */ + if (!w) break; /* Break on end of the LFN */ + if (w == ' ' || (w == '.' && si != di)) { /* Remove spaces and dots */ + cf |= NS_LOSS | NS_LFN; continue; + } + + if (i >= ni || si == di) { /* Extension or end of SFN */ + if (ni == 11) { /* Long extension */ + cf |= NS_LOSS | NS_LFN; break; + } + if (si != di) cf |= NS_LOSS | NS_LFN; /* Out of 8.3 format */ + if (si > di) break; /* No extension */ + si = di; i = 8; ni = 11; /* Enter extension section */ + b <<= 2; continue; + } + + if (w >= 0x80) { /* Non ASCII character */ +#ifdef _EXCVT + w = ff_convert(w, 0); /* Unicode -> OEM code */ + if (w) w = ExCvt[w - 0x80]; /* Convert extended character to upper (SBCS) */ +#else + w = ff_convert(ff_wtoupper(w), 0); /* Upper converted Unicode -> OEM code */ +#endif + cf |= NS_LFN; /* Force create LFN entry */ + } + + if (_DF1S && w >= 0x100) { /* Is this DBC? (always false at SBCS cfg) */ + if (i >= ni - 1) { + cf |= NS_LOSS | NS_LFN; i = ni; continue; + } + dp->fn[i++] = (BYTE)(w >> 8); + } else { /* SBC */ + if (!w || chk_chr("+,;=[]", w)) { /* Replace illegal characters for SFN */ + w = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */ + } else { + if (IsUpper(w)) { /* ASCII large capital */ + b |= 2; + } else { + if (IsLower(w)) { /* ASCII small capital */ + b |= 1; w -= 0x20; + } + } + } + } + dp->fn[i++] = (BYTE)w; + } + + if (dp->fn[0] == DDEM) dp->fn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */ + + if (ni == 8) b <<= 2; + if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN; /* Create LFN entry when there are composite capitals */ + if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */ + if ((b & 0x03) == 0x01) cf |= NS_EXT; /* NT flag (Extension has only small capital) */ + if ((b & 0x0C) == 0x04) cf |= NS_BODY; /* NT flag (Filename has only small capital) */ + } + + dp->fn[NSFLAG] = cf; /* SFN is created */ + + return FR_OK; + + +#else /* _USE_LFN != 0 : Non-LFN configuration */ + BYTE c, d, *sfn; + UINT ni, si, i; + const char *p; + + /* Create file name in directory form */ + p = *path; sfn = dp->fn; + mem_set(sfn, ' ', 11); + si = i = 0; ni = 8; +#if _FS_RPATH != 0 + if (p[si] == '.') { /* Is this a dot entry? */ + for (;;) { + c = (BYTE)p[si++]; + if (c != '.' || si >= 3) break; + sfn[i++] = c; + } + if (c != '/' && c != '\\' && c > ' ') return FR_INVALID_NAME; + *path = p + si; /* Return pointer to the next segment */ + sfn[NSFLAG] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of the path */ + return FR_OK; + } +#endif + for (;;) { + c = (BYTE)p[si++]; + if (c <= ' ') break; /* Break if end of the path name */ + if (c == '/' || c == '\\') { /* Break if a separator is found */ + while (p[si] == '/' || p[si] == '\\') si++; /* Skip duplicated separator if exist */ + break; + } + if (c == '.' || i >= ni) { /* End of body or over size? */ + if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Over size or invalid dot */ + i = 8; ni = 11; /* Goto extension */ + continue; + } + if (c >= 0x80) { /* Extended character? */ +#ifdef _EXCVT + c = ExCvt[c - 0x80]; /* To upper extended characters (SBCS cfg) */ +#else +#if !_DF1S + return FR_INVALID_NAME; /* Reject extended characters (ASCII only cfg) */ +#endif +#endif + } + if (IsDBCS1(c)) { /* Check if it is a DBC 1st byte (always false at SBCS cfg.) */ + d = (BYTE)p[si++]; /* Get 2nd byte */ + if (!IsDBCS2(d) || i >= ni - 1) return FR_INVALID_NAME; /* Reject invalid DBC */ + sfn[i++] = c; + sfn[i++] = d; + } else { /* SBC */ + if (chk_chr("\"*+,:;<=>\?[]|\x7F", c)) return FR_INVALID_NAME; /* Reject illegal chrs for SFN */ + if (IsLower(c)) c -= 0x20; /* To upper */ + sfn[i++] = c; + } + } + *path = p + si; /* Return pointer to the next segment */ + if (i == 0) return FR_INVALID_NAME; /* Reject nul string */ + + if (sfn[0] == DDEM) sfn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */ + sfn[NSFLAG] = (c <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */ + + return FR_OK; +#endif /* _USE_LFN != 0 */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Follow a file path */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ + DIR* dp, /* Directory object to return last directory and found object */ + const TCHAR* path /* Full-path string to find a file or directory */ +) +{ + FRESULT res; + BYTE ns; + _FDID *obj = &dp->obj; + FATFS *fs = obj->fs; + + +#if _FS_RPATH != 0 + if (*path != '/' && *path != '\\') { /* Without heading separator */ + obj->sclust = fs->cdir; /* Start from the current directory */ + } else +#endif + { /* With heading separator */ + while (*path == '/' || *path == '\\') path++; /* Strip heading separator */ + obj->sclust = 0; /* Start from the root directory */ + } +#if _FS_EXFAT && _FS_RPATH != 0 + if (fs->fs_type == FS_EXFAT && obj->sclust) { /* Retrieve the sub-directory status if needed */ + DIR dj; + + obj->c_scl = fs->cdc_scl; + obj->c_size = fs->cdc_size; + obj->c_ofs = fs->cdc_ofs; + res = load_obj_dir(&dj, obj); + if (res != FR_OK) return res; + obj->objsize = ld_dword(fs->dirbuf + XDIR_FileSize); + obj->stat = fs->dirbuf[XDIR_GenFlags] & 2; + } +#endif + + if ((UINT)*path < ' ') { /* Null path name is the origin directory itself */ + dp->fn[NSFLAG] = NS_NONAME; + res = dir_sdi(dp, 0); + + } else { /* Follow path */ + for (;;) { + res = create_name(dp, &path); /* Get a segment name of the path */ + if (res != FR_OK) break; + res = dir_find(dp); /* Find an object with the segment name */ + ns = dp->fn[NSFLAG]; + if (res != FR_OK) { /* Failed to find the object */ + if (res == FR_NO_FILE) { /* Object is not found */ + if (_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exist, stay there */ + if (!(ns & NS_LAST)) continue; /* Continue to follow if not last segment */ + dp->fn[NSFLAG] = NS_NONAME; + res = FR_OK; + } else { /* Could not find the object */ + if (!(ns & NS_LAST)) res = FR_NO_PATH; /* Adjust error code if not last segment */ + } + } + break; + } + if (ns & NS_LAST) break; /* Last segment matched. Function completed. */ + /* Get into the sub-directory */ + if (!(obj->attr & AM_DIR)) { /* It is not a sub-directory and cannot follow */ + res = FR_NO_PATH; break; + } +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + obj->c_scl = obj->sclust; /* Save containing directory information for next dir */ + obj->c_size = ((DWORD)obj->objsize & 0xFFFFFF00) | obj->stat; + obj->c_ofs = dp->blk_ofs; + obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Open next directory */ + obj->stat = fs->dirbuf[XDIR_GenFlags] & 2; + obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize); + } else +#endif + { + obj->sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs)); /* Open next directory */ + } + } + } + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Get logical drive number from path name */ +/*-----------------------------------------------------------------------*/ + +static +int get_ldnumber ( /* Returns logical drive number (-1:invalid drive) */ + const TCHAR** path /* Pointer to pointer to the path name */ +) +{ + const TCHAR *tp, *tt; + UINT i; + int vol = -1; +#if _STR_VOLUME_ID /* Find string drive id */ + static const char* const str[] = {_VOLUME_STRS}; + const char *sp; + char c; + TCHAR tc; +#endif + + + if (*path) { /* If the pointer is not a null */ + for (tt = *path; (UINT)*tt >= (_USE_LFN ? ' ' : '!') && *tt != ':'; tt++) ; /* Find ':' in the path */ + if (*tt == ':') { /* If a ':' is exist in the path name */ + tp = *path; + i = *tp++ - '0'; + if (i < 10 && tp == tt) { /* Is there a numeric drive id? */ + if (i < _VOLUMES) { /* If a drive id is found, get the value and strip it */ + vol = (int)i; + *path = ++tt; + } + } +#if _STR_VOLUME_ID + else { /* No numeric drive number, find string drive id */ + i = 0; tt++; + do { + sp = str[i]; tp = *path; + do { /* Compare a string drive id with path name */ + c = *sp++; tc = *tp++; + if (IsLower(tc)) tc -= 0x20; + } while (c && (TCHAR)c == tc); + } while ((c || tp != tt) && ++i < _VOLUMES); /* Repeat for each id until pattern match */ + if (i < _VOLUMES) { /* If a drive id is found, get the value and strip it */ + vol = (int)i; + *path = tt; + } + } +#endif + return vol; + } +#if _FS_RPATH != 0 && _VOLUMES >= 2 + vol = CurrVol; /* Current drive */ +#else + vol = 0; /* Drive 0 */ +#endif + } + return vol; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load a sector and check if it is an FAT boot sector */ +/*-----------------------------------------------------------------------*/ + +static +BYTE check_fs ( /* 0:FAT, 1:exFAT, 2:Valid BS but not FAT, 3:Not a BS, 4:Disk error */ + FATFS* fs, /* File system object */ + DWORD sect /* Sector# (lba) to check if it is an FAT-VBR or not */ +) +{ + fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */ + if (move_window(fs, sect) != FR_OK) return 4; /* Load boot record */ + + if (ld_word(fs->win + BS_55AA) != 0xAA55) return 3; /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */ + + if (fs->win[BS_JmpBoot] == 0xE9 || (fs->win[BS_JmpBoot] == 0xEB && fs->win[BS_JmpBoot + 2] == 0x90)) { + if ((ld_dword(fs->win + BS_FilSysType) & 0xFFFFFF) == 0x544146) return 0; /* Check "FAT" string */ + if (ld_dword(fs->win + BS_FilSysType32) == 0x33544146) return 0; /* Check "FAT3" string */ + } +#if _FS_EXFAT + if (!mem_cmp(fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11)) return 1; +#endif + return 2; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Find logical drive and check if the volume is mounted */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT find_volume ( /* FR_OK(0): successful, !=0: any error occurred */ + const TCHAR** path, /* Pointer to pointer to the path name (drive number) */ + FATFS** rfs, /* Pointer to pointer to the found file system object */ + BYTE mode /* !=0: Check write protection for write access */ +) +{ + BYTE fmt, *pt; + int vol; + DSTATUS stat; + DWORD bsect, fasize, tsect, sysect, nclst, szbfat, br[4]; + WORD nrsv; + FATFS *fs; + UINT i; + + + /* Get logical drive number */ + *rfs = 0; + vol = get_ldnumber(path); + if (vol < 0) return FR_INVALID_DRIVE; + + /* Check if the file system object is valid or not */ + fs = FatFs[vol]; /* Get pointer to the file system object */ + if (!fs) return FR_NOT_ENABLED; /* Is the file system object available? */ + + ENTER_FF(fs); /* Lock the volume */ + *rfs = fs; /* Return pointer to the file system object */ + + mode &= (BYTE)~FA_READ; /* Desired access mode, write access or not */ + if (fs->fs_type) { /* If the volume has been mounted */ + stat = disk_status(fs->drv); + if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */ + if (!_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check write protection if needed */ + return FR_WRITE_PROTECTED; + } + return FR_OK; /* The file system object is valid */ + } + } + + /* The file system object is not valid. */ + /* Following code attempts to mount the volume. (analyze BPB and initialize the fs object) */ + + fs->fs_type = 0; /* Clear the file system object */ + fs->drv = LD2PD(vol); /* Bind the logical drive and a physical drive */ + stat = disk_initialize(fs->drv); /* Initialize the physical drive */ + if (stat & STA_NOINIT) { /* Check if the initialization succeeded */ + return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */ + } + if (!_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */ + return FR_WRITE_PROTECTED; + } +#if _MAX_SS != _MIN_SS /* Get sector size (multiple sector size cfg only) */ + if (disk_ioctl(fs->drv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR; + if (SS(fs) > _MAX_SS || SS(fs) < _MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR; +#endif + /* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */ + bsect = 0; + fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT-VBR as SFD */ + if (fmt == 2 || (fmt < 2 && LD2PT(vol) != 0)) { /* Not an FAT-VBR or forced partition number */ + for (i = 0; i < 4; i++) { /* Get partition offset */ + pt = fs->win + (MBR_Table + i * SZ_PTE); + br[i] = pt[PTE_System] ? ld_dword(pt + PTE_StLba) : 0; + } + i = LD2PT(vol); /* Partition number: 0:auto, 1-4:forced */ + if (i) i--; + do { /* Find an FAT volume */ + bsect = br[i]; + fmt = bsect ? check_fs(fs, bsect) : 3; /* Check the partition */ + } while (!LD2PT(vol) && fmt >= 2 && ++i < 4); + } + if (fmt == 4) return FR_DISK_ERR; /* An error occured in the disk I/O layer */ + if (fmt >= 2) return FR_NO_FILESYSTEM; /* No FAT volume is found */ + + /* An FAT volume is found. Following code initializes the file system object */ + +#if _FS_EXFAT + if (fmt == 1) { + QWORD maxlba; + + for (i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++) ; /* Check zero filler */ + if (i < BPB_ZeroedEx + 53) return FR_NO_FILESYSTEM; + + if (ld_word(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM; /* Check exFAT revision (Must be 1.0) */ + + if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) /* (BPB_BytsPerSecEx must be equal to the physical sector size) */ + return FR_NO_FILESYSTEM; + + maxlba = ld_qword(fs->win + BPB_TotSecEx) + bsect; /* Last LBA + 1 of the volume */ + if (maxlba >= 0x100000000) return FR_NO_FILESYSTEM; /* (It cannot be handled in 32-bit LBA) */ + + fs->fsize = ld_dword(fs->win + BPB_FatSzEx); /* Number of sectors per FAT */ + + fs->n_fats = fs->win[BPB_NumFATsEx]; /* Number of FATs */ + if (fs->n_fats != 1) return FR_NO_FILESYSTEM; /* (Supports only 1 FAT) */ + + fs->csize = 1 << fs->win[BPB_SecPerClusEx]; /* Cluster size */ + if (fs->csize == 0) return FR_NO_FILESYSTEM; /* (Must be 1..32768) */ + + nclst = ld_dword(fs->win + BPB_NumClusEx); /* Number of clusters */ + if (nclst > MAX_EXFAT) return FR_NO_FILESYSTEM; /* (Too many clusters) */ + fs->n_fatent = nclst + 2; + + /* Boundaries and Limits */ + fs->volbase = bsect; + fs->database = bsect + ld_dword(fs->win + BPB_DataOfsEx); + fs->fatbase = bsect + ld_dword(fs->win + BPB_FatOfsEx); + if (maxlba < (QWORD)fs->database + nclst * fs->csize) return FR_NO_FILESYSTEM; /* (Volume size must not be smaller than the size requiered) */ + fs->dirbase = ld_dword(fs->win + BPB_RootClusEx); + + /* Check if bitmap location is in assumption (at the first cluster) */ + if (move_window(fs, clust2sect(fs, fs->dirbase)) != FR_OK) return FR_DISK_ERR; + for (i = 0; i < SS(fs); i += SZDIRE) { + if (fs->win[i] == 0x81 && ld_dword(fs->win + i + 20) == 2) break; /* 81 entry with cluster #2? */ + } + if (i == SS(fs)) return FR_NO_FILESYSTEM; +#if !_FS_READONLY + fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ +#endif + fmt = FS_EXFAT; /* FAT sub-type */ + } else +#endif /* _FS_EXFAT */ + { + if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */ + + fasize = ld_word(fs->win + BPB_FATSz16); /* Number of sectors per FAT */ + if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32); + fs->fsize = fasize; + + fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FATs */ + if (fs->n_fats != 1 && fs->n_fats != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */ + fasize *= fs->n_fats; /* Number of sectors for FAT area */ + + fs->csize = fs->win[BPB_SecPerClus]; /* Cluster size */ + if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */ + + fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt); /* Number of root directory entries */ + if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM; /* (Must be sector aligned) */ + + tsect = ld_word(fs->win + BPB_TotSec16); /* Number of sectors on the volume */ + if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32); + + nrsv = ld_word(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */ + if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */ + + /* Determine the FAT sub type */ + sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */ + if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ + if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + fmt = FS_FAT32; + if (nclst <= MAX_FAT16) fmt = FS_FAT16; + if (nclst <= MAX_FAT12) fmt = FS_FAT12; + + /* Boundaries and Limits */ + fs->n_fatent = nclst + 2; /* Number of FAT entries */ + fs->volbase = bsect; /* Volume start sector */ + fs->fatbase = bsect + nrsv; /* FAT start sector */ + fs->database = bsect + sysect; /* Data start sector */ + if (fmt == FS_FAT32) { + if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */ + if (fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */ + fs->dirbase = ld_dword(fs->win + BPB_RootClus32); /* Root directory start cluster */ + szbfat = fs->n_fatent * 4; /* (Needed FAT size) */ + } else { + if (fs->n_rootdir == 0) return FR_NO_FILESYSTEM;/* (BPB_RootEntCnt must not be 0) */ + fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */ + szbfat = (fmt == FS_FAT16) ? /* (Needed FAT size) */ + fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1); + } + if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_FATSz must not be less than the size needed) */ + +#if !_FS_READONLY + /* Get FSINFO if available */ + fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ + fs->fsi_flag = 0x80; +#if (_FS_NOFSINFO & 3) != 3 + if (fmt == FS_FAT32 /* Enable FSINFO only if FAT32 and BPB_FSInfo32 == 1 */ + && ld_word(fs->win + BPB_FSInfo32) == 1 + && move_window(fs, bsect + 1) == FR_OK) + { + fs->fsi_flag = 0; + if (ld_word(fs->win + BS_55AA) == 0xAA55 /* Load FSINFO data if available */ + && ld_dword(fs->win + FSI_LeadSig) == 0x41615252 + && ld_dword(fs->win + FSI_StrucSig) == 0x61417272) + { +#if (_FS_NOFSINFO & 1) == 0 + fs->free_clst = ld_dword(fs->win + FSI_Free_Count); +#endif +#if (_FS_NOFSINFO & 2) == 0 + fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free); +#endif + } + } +#endif /* (_FS_NOFSINFO & 3) != 3 */ +#endif /* !_FS_READONLY */ + } + + fs->fs_type = fmt; /* FAT sub-type */ + fs->id = ++Fsid; /* File system mount ID */ +#if _USE_LFN == 1 + fs->lfnbuf = LfnBuf; /* Static LFN working buffer */ +#if _FS_EXFAT + fs->dirbuf = DirBuf; /* Static directory block working buuffer */ +#endif +#endif +#if _FS_RPATH != 0 + fs->cdir = 0; /* Initialize current directory */ +#endif +#if _FS_LOCK != 0 /* Clear file lock semaphores */ + clear_lock(fs); +#endif + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Check if the file/directory object is valid or not */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */ + _FDID* obj, /* Pointer to the _OBJ, the 1st member in the FIL/DIR object, to check validity */ + FATFS** fs /* Pointer to pointer to the owner file system object to return */ +) +{ + FRESULT res; + + + if (!obj || !obj->fs || !obj->fs->fs_type || obj->fs->id != obj->id || (disk_status(obj->fs->drv) & STA_NOINIT)) { + *fs = 0; /* The object is invalid */ + res = FR_INVALID_OBJECT; + } else { + *fs = obj->fs; /* Owner file sytem object */ + ENTER_FF(obj->fs); /* Lock file system */ + res = FR_OK; + } + return res; +} + + + + +/*--------------------------------------------------------------------------- + + Public Functions (FatFs API) + +----------------------------------------------------------------------------*/ + + + +/*-----------------------------------------------------------------------*/ +/* Mount/Unmount a Logical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mount ( + FATFS* fs, /* Pointer to the file system object (NULL:unmount)*/ + const TCHAR* path, /* Logical drive number to be mounted/unmounted */ + BYTE opt /* Mode option 0:Do not mount (delayed mount), 1:Mount immediately */ +) +{ + FATFS *cfs; + int vol; + FRESULT res; + const TCHAR *rp = path; + + + /* Get logical drive number */ + vol = get_ldnumber(&rp); + if (vol < 0) return FR_INVALID_DRIVE; + cfs = FatFs[vol]; /* Pointer to fs object */ + + if (cfs) { +#if _FS_LOCK != 0 + clear_lock(cfs); +#endif +#if _FS_REENTRANT /* Discard sync object of the current volume */ + if (!ff_del_syncobj(cfs->sobj)) return FR_INT_ERR; +#endif + cfs->fs_type = 0; /* Clear old fs object */ + } + + if (fs) { + fs->fs_type = 0; /* Clear new fs object */ +#if _FS_REENTRANT /* Create sync object for the new volume */ + if (!ff_cre_syncobj((BYTE)vol, &fs->sobj)) return FR_INT_ERR; +#endif + } + FatFs[vol] = fs; /* Register new fs object */ + + if (!fs || opt != 1) return FR_OK; /* Do not mount now, it will be mounted later */ + + res = find_volume(&path, &fs, 0); /* Force mounted the volume */ + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Open or Create a File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_open ( + FIL* fp, /* Pointer to the blank file object */ + const TCHAR* path, /* Pointer to the file name */ + BYTE mode /* Access mode and file open mode flags */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; +#if !_FS_READONLY + DWORD dw, cl, bcs, clst, sc; + FSIZE_t ofs; +#endif + DEF_NAMBUF + + + if (!fp) return FR_INVALID_OBJECT; + + /* Get logical drive */ + mode &= _FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND | FA_SEEKEND; + res = find_volume(&path, &fs, mode); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ +#if !_FS_READONLY /* R/W configuration */ + if (res == FR_OK) { + if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */ + res = FR_INVALID_NAME; + } +#if _FS_LOCK != 0 + else { + res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); + } +#endif + } + /* Create or Open a file */ + if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) { + if (res != FR_OK) { /* No file, create new */ + if (res == FR_NO_FILE) /* There is no file to open, create a new entry */ +#if _FS_LOCK != 0 + res = enq_lock() ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES; +#else + res = dir_register(&dj); +#endif + mode |= FA_CREATE_ALWAYS; /* File is created */ + } + else { /* Any object is already existing */ + if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */ + res = FR_DENIED; + } else { + if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */ + } + } + if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate it if overwrite mode */ + dw = GET_FATTIME(); +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + /* Get current allocation info */ + fp->obj.fs = fs; + fp->obj.sclust = ld_dword(fs->dirbuf + XDIR_FstClus); + fp->obj.objsize = ld_qword(fs->dirbuf + XDIR_FileSize); + fp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2; + /* Initialize directory entry block */ + st_dword(fs->dirbuf + XDIR_CrtTime, dw); /* Set created time */ + fs->dirbuf[XDIR_CrtTime10] = 0; + st_dword(fs->dirbuf + XDIR_ModTime, dw); /* Set modified time */ + fs->dirbuf[XDIR_ModTime10] = 0; + fs->dirbuf[XDIR_Attr] = AM_ARC; /* Reset attribute */ + st_dword(fs->dirbuf + XDIR_FstClus, 0); /* Reset file allocation info */ + st_qword(fs->dirbuf + XDIR_FileSize, 0); + st_qword(fs->dirbuf + XDIR_ValidFileSize, 0); + fs->dirbuf[XDIR_GenFlags] = 1; + res = store_xdir(&dj); + if (res == FR_OK && fp->obj.sclust) { /* Remove the cluster chain if exist */ + res = remove_chain(&fp->obj, fp->obj.sclust, 0); + fs->last_clst = fp->obj.sclust - 1; /* Reuse the cluster hole */ + } + } else +#endif + { + /* Clean directory info */ + st_dword(dj.dir + DIR_CrtTime, dw); /* Set created time */ + st_dword(dj.dir + DIR_ModTime, dw); /* Set modified time */ + dj.dir[DIR_Attr] = AM_ARC; /* Reset attribute */ + cl = ld_clust(fs, dj.dir); /* Get cluster chain */ + st_clust(fs, dj.dir, 0); /* Reset file allocation info */ + st_dword(dj.dir + DIR_FileSize, 0); + fs->wflag = 1; + + if (cl) { /* Remove the cluster chain if exist */ + dw = fs->winsect; + res = remove_chain(&dj.obj, cl, 0); + if (res == FR_OK) { + res = move_window(fs, dw); + fs->last_clst = cl - 1; /* Reuse the cluster hole */ + } + } + } + } + } + else { /* Open an existing file */ + if (res == FR_OK) { /* Following succeeded */ + if (dj.obj.attr & AM_DIR) { /* It is a directory */ + res = FR_NO_FILE; + } else { + if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* R/O violation */ + res = FR_DENIED; + } + } + } + } + if (res == FR_OK) { + if (mode & FA_CREATE_ALWAYS) /* Set file change flag if created or overwritten */ + mode |= FA_MODIFIED; + fp->dir_sect = fs->winsect; /* Pointer to the directory entry */ + fp->dir_ptr = dj.dir; +#if _FS_LOCK != 0 + fp->obj.lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); + if (!fp->obj.lockid) res = FR_INT_ERR; +#endif + } +#else /* R/O configuration */ + if (res == FR_OK) { + if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */ + res = FR_INVALID_NAME; + } else { + if (dj.obj.attr & AM_DIR) { /* It is a directory */ + res = FR_NO_FILE; + } + } + } +#endif + + if (res == FR_OK) { +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fp->obj.sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Get allocation info */ + fp->obj.objsize = ld_qword(fs->dirbuf + XDIR_FileSize); + fp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2; + fp->obj.c_scl = dj.obj.sclust; + fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat; + fp->obj.c_ofs = dj.blk_ofs; + } else +#endif + { + fp->obj.sclust = ld_clust(fs, dj.dir); /* Get allocation info */ + fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize); + } +#if _USE_FASTSEEK + fp->cltbl = 0; /* Disable fast seek mode */ +#endif + fp->obj.fs = fs; /* Validate the file object */ + fp->obj.id = fs->id; + fp->flag = mode; /* Set file access mode */ + fp->err = 0; /* Clear error flag */ + fp->sect = 0; /* Invalidate current data sector */ + fp->fptr = 0; /* Set file pointer top of the file */ +#if !_FS_READONLY +#if !_FS_TINY + mem_set(fp->buf, 0, _MAX_SS); /* Clear sector buffer */ +#endif + if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */ + fp->fptr = fp->obj.objsize; /* Offset to seek */ + bcs = (DWORD)fs->csize * SS(fs); /* Cluster size in byte */ + clst = fp->obj.sclust; /* Follow the cluster chain */ + for (ofs = fp->obj.objsize; res == FR_OK && ofs > bcs; ofs -= bcs) { + clst = get_fat(&fp->obj, clst); + if (clst <= 1) res = FR_INT_ERR; + if (clst == 0xFFFFFFFF) res = FR_DISK_ERR; + } + fp->clust = clst; + if (res == FR_OK && ofs % SS(fs)) { /* Fill sector buffer if not on the sector boundary */ + if ((sc = clust2sect(fs, clst)) == 0) { + res = FR_INT_ERR; + } else { + fp->sect = sc + (DWORD)(ofs / SS(fs)); +#if !_FS_TINY + if (disk_read(fs->drv, fp->buf, fp->sect, 1) != RES_OK) res = FR_DISK_ERR; +#endif + } + } + } +#endif + } + + FREE_NAMBUF(); + } + + if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */ + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_read ( + FIL* fp, /* Pointer to the file object */ + void* buff, /* Pointer to data buffer */ + UINT btr, /* Number of bytes to read */ + UINT* br /* Pointer to number of bytes read */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, sect; + FSIZE_t remain; + UINT rcnt, cc, csect; + BYTE *rbuff = (BYTE*)buff; + + + *br = 0; /* Clear read byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */ + if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + remain = fp->obj.objsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + for ( ; btr; /* Repeat until all data read */ + rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) { + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */ + if (csect == 0) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->obj.sclust; /* Follow cluster chain from the origin */ + } else { /* Middle or end of the file */ +#if _USE_FASTSEEK + if (fp->cltbl) { + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + } else +#endif + { + clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */ + } + } + if (clst < 2) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } + sect = clust2sect(fs, fp->clust); /* Get current sector */ + if (!sect) ABORT(fs, FR_INT_ERR); + sect += csect; + cc = btr / SS(fs); /* When remaining bytes >= sector size, */ + if (cc) { /* Read maximum contiguous sectors directly */ + if (csect + cc > fs->csize) { /* Clip at cluster boundary */ + cc = fs->csize - csect; + } + if (disk_read(fs->drv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); +#if !_FS_READONLY && _FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */ +#if _FS_TINY + if (fs->wflag && fs->winsect - sect < cc) { + mem_cpy(rbuff + ((fs->winsect - sect) * SS(fs)), fs->win, SS(fs)); + } +#else + if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) { + mem_cpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs)); + } +#endif +#endif + rcnt = SS(fs) * cc; /* Number of bytes transferred */ + continue; + } +#if !_FS_TINY + if (fp->sect != sect) { /* Load data sector if not in cache */ +#if !_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */ + } +#endif + fp->sect = sect; + } + rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */ + if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */ +#if _FS_TINY + if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */ + mem_cpy(rbuff, fs->win + fp->fptr % SS(fs), rcnt); /* Extract partial sector */ +#else + mem_cpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */ +#endif + } + + LEAVE_FF(fs, FR_OK); +} + + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Write File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_write ( + FIL* fp, /* Pointer to the file object */ + const void* buff, /* Pointer to the data to be written */ + UINT btw, /* Number of bytes to write */ + UINT* bw /* Pointer to number of bytes written */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, sect; + UINT wcnt, cc, csect; + const BYTE *wbuff = (const BYTE*)buff; + + + *bw = 0; /* Clear write byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */ + if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + /* Check fptr wrap-around (file size cannot reach 4GiB on FATxx) */ + if ((!_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) { + btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr); + } + + for ( ; btw; /* Repeat until all data written */ + wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize, *bw += wcnt, btw -= wcnt) { + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + csect = (UINT)(fp->fptr / SS(fs)) & (fs->csize - 1); /* Sector offset in the cluster */ + if (csect == 0) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->obj.sclust; /* Follow from the origin */ + if (clst == 0) { /* If no cluster is allocated, */ + clst = create_chain(&fp->obj, 0); /* create a new cluster chain */ + } + } else { /* On the middle or end of the file */ +#if _USE_FASTSEEK + if (fp->cltbl) { + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + } else +#endif + { + clst = create_chain(&fp->obj, fp->clust); /* Follow or stretch cluster chain on the FAT */ + } + } + if (clst == 0) break; /* Could not allocate a new cluster (disk full) */ + if (clst == 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + if (fp->obj.sclust == 0) fp->obj.sclust = clst; /* Set start cluster if the first write */ + } +#if _FS_TINY + if (fs->winsect == fp->sect && sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Write-back sector cache */ +#else + if (fp->flag & FA_DIRTY) { /* Write-back sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + sect = clust2sect(fs, fp->clust); /* Get current sector */ + if (!sect) ABORT(fs, FR_INT_ERR); + sect += csect; + cc = btw / SS(fs); /* When remaining bytes >= sector size, */ + if (cc) { /* Write maximum contiguous sectors directly */ + if (csect + cc > fs->csize) { /* Clip at cluster boundary */ + cc = fs->csize - csect; + } + if (disk_write(fs->drv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); +#if _FS_MINIMIZE <= 2 +#if _FS_TINY + if (fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + mem_cpy(fs->win, wbuff + ((fs->winsect - sect) * SS(fs)), SS(fs)); + fs->wflag = 0; + } +#else + if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + mem_cpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs)); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif +#endif + wcnt = SS(fs) * cc; /* Number of bytes transferred */ + continue; + } +#if _FS_TINY + if (fp->fptr >= fp->obj.objsize) { /* Avoid silly cache filling on the growing edge */ + if (sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); + fs->winsect = sect; + } +#else + if (fp->sect != sect && /* Fill sector cache with file data */ + fp->fptr < fp->obj.objsize && + disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) { + ABORT(fs, FR_DISK_ERR); + } +#endif + fp->sect = sect; + } + wcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */ + if (wcnt > btw) wcnt = btw; /* Clip it by btw if needed */ +#if _FS_TINY + if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */ + mem_cpy(fs->win + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */ + fs->wflag = 1; +#else + mem_cpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */ + fp->flag |= FA_DIRTY; +#endif + } + + fp->flag |= FA_MODIFIED; /* Set file change flag */ + + LEAVE_FF(fs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Synchronize the File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_sync ( + FIL* fp /* Pointer to the file object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD tm; + BYTE *dir; + DEF_NAMBUF + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res == FR_OK) { + if (fp->flag & FA_MODIFIED) { /* Is there any change to the file? */ +#if !_FS_TINY + if (fp->flag & FA_DIRTY) { /* Write-back cached data if needed */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) LEAVE_FF(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + /* Update the directory entry */ + tm = GET_FATTIME(); /* Modified time */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + res = fill_fat_chain(&fp->obj); /* Create FAT chain if needed */ + if (res == FR_OK) { + DIR dj; + + INIT_NAMBUF(fs); + res = load_obj_dir(&dj, &fp->obj); /* Load directory entry block */ + if (res == FR_OK) { + fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive bit */ + fs->dirbuf[XDIR_GenFlags] = fp->obj.stat | 1; /* Update file allocation info */ + st_dword(fs->dirbuf + XDIR_FstClus, fp->obj.sclust); + st_qword(fs->dirbuf + XDIR_FileSize, fp->obj.objsize); + st_qword(fs->dirbuf + XDIR_ValidFileSize, fp->obj.objsize); + st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Update modified time */ + fs->dirbuf[XDIR_ModTime10] = 0; + st_dword(fs->dirbuf + XDIR_AccTime, 0); + res = store_xdir(&dj); /* Restore it to the directory */ + if (res == FR_OK) { + res = sync_fs(fs); + fp->flag &= (BYTE)~FA_MODIFIED; + } + } + FREE_NAMBUF(); + } + } else +#endif + { + res = move_window(fs, fp->dir_sect); + if (res == FR_OK) { + dir = fp->dir_ptr; + dir[DIR_Attr] |= AM_ARC; /* Set archive bit */ + st_clust(fp->obj.fs, dir, fp->obj.sclust); /* Update file allocation info */ + st_dword(dir + DIR_FileSize, (DWORD)fp->obj.objsize); /* Update file size */ + st_dword(dir + DIR_ModTime, tm); /* Update modified time */ + st_word(dir + DIR_LstAccDate, 0); + fs->wflag = 1; + res = sync_fs(fs); /* Restore it to the directory */ + fp->flag &= (BYTE)~FA_MODIFIED; + } + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Close File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_close ( + FIL* fp /* Pointer to the file object to be closed */ +) +{ + FRESULT res; + FATFS *fs; + +#if !_FS_READONLY + res = f_sync(fp); /* Flush cached data */ + if (res == FR_OK) +#endif + { + res = validate(&fp->obj, &fs); /* Lock volume */ + if (res == FR_OK) { +#if _FS_LOCK != 0 + res = dec_lock(fp->obj.lockid); /* Decrement file open counter */ + if (res == FR_OK) +#endif + { + fp->obj.fs = 0; /* Invalidate file object */ + } +#if _FS_REENTRANT + unlock_fs(fs, FR_OK); /* Unlock volume */ +#endif + } + } + return res; +} + + + + +#if _FS_RPATH >= 1 +/*-----------------------------------------------------------------------*/ +/* Change Current Directory or Current Drive, Get Current Directory */ +/*-----------------------------------------------------------------------*/ + +#if _VOLUMES >= 2 +FRESULT f_chdrive ( + const TCHAR* path /* Drive number */ +) +{ + int vol; + + + /* Get logical drive number */ + vol = get_ldnumber(&path); + if (vol < 0) return FR_INVALID_DRIVE; + + CurrVol = (BYTE)vol; /* Set it as current volume */ + + return FR_OK; +} +#endif + + +FRESULT f_chdir ( + const TCHAR* path /* Pointer to the directory path */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + DEF_NAMBUF + + /* Get logical drive */ + res = find_volume(&path, &fs, 0); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the path */ + if (res == FR_OK) { /* Follow completed */ + if (dj.fn[NSFLAG] & NS_NONAME) { + fs->cdir = dj.obj.sclust; /* It is the start directory itself */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->cdc_scl = dj.obj.c_scl; + fs->cdc_size = dj.obj.c_size; + fs->cdc_ofs = dj.obj.c_ofs; + } +#endif + } else { + if (dj.obj.attr & AM_DIR) { /* It is a sub-directory */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->cdir = ld_dword(fs->dirbuf + XDIR_FstClus); /* Sub-directory cluster */ + fs->cdc_scl = dj.obj.sclust; /* Save containing directory information */ + fs->cdc_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat; + fs->cdc_ofs = dj.blk_ofs; + } else +#endif + { + fs->cdir = ld_clust(fs, dj.dir); /* Sub-directory cluster */ + } + } else { + res = FR_NO_PATH; /* Reached but a file */ + } + } + } + FREE_NAMBUF(); + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + + LEAVE_FF(fs, res); +} + + +#if _FS_RPATH >= 2 +FRESULT f_getcwd ( + TCHAR* buff, /* Pointer to the directory path */ + UINT len /* Size of path */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + UINT i, n; + DWORD ccl; + TCHAR *tp; + FILINFO fno; + DEF_NAMBUF + + + *buff = 0; + /* Get logical drive */ + res = find_volume((const TCHAR**)&buff, &fs, 0); /* Get current volume */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + i = len; /* Bottom of buffer (directory stack base) */ + if (!_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* (Cannot do getcwd on exFAT and returns root path) */ + dj.obj.sclust = fs->cdir; /* Start to follow upper directory from current directory */ + while ((ccl = dj.obj.sclust) != 0) { /* Repeat while current directory is a sub-directory */ + res = dir_sdi(&dj, 1 * SZDIRE); /* Get parent directory */ + if (res != FR_OK) break; + res = move_window(fs, dj.sect); + if (res != FR_OK) break; + dj.obj.sclust = ld_clust(fs, dj.dir); /* Goto parent directory */ + res = dir_sdi(&dj, 0); + if (res != FR_OK) break; + do { /* Find the entry links to the child directory */ + res = dir_read(&dj, 0); + if (res != FR_OK) break; + if (ccl == ld_clust(fs, dj.dir)) break; /* Found the entry */ + res = dir_next(&dj, 0); + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */ + if (res != FR_OK) break; + get_fileinfo(&dj, &fno); /* Get the directory name and push it to the buffer */ + for (n = 0; fno.fname[n]; n++) ; + if (i < n + 3) { + res = FR_NOT_ENOUGH_CORE; break; + } + while (n) buff[--i] = fno.fname[--n]; + buff[--i] = '/'; + } + } + tp = buff; + if (res == FR_OK) { +#if _VOLUMES >= 2 + *tp++ = '0' + CurrVol; /* Put drive number */ + *tp++ = ':'; +#endif + if (i == len) { /* Root-directory */ + *tp++ = '/'; + } else { /* Sub-directroy */ + do /* Add stacked path str */ + *tp++ = buff[i++]; + while (i < len); + } + } + *tp = 0; + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + +#endif /* _FS_RPATH >= 2 */ +#endif /* _FS_RPATH >= 1 */ + + + +#if _FS_MINIMIZE <= 2 +/*-----------------------------------------------------------------------*/ +/* Seek File R/W Pointer */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_lseek ( + FIL* fp, /* Pointer to the file object */ + FSIZE_t ofs /* File pointer from top of file */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, bcs, nsect; + FSIZE_t ifptr; +#if _USE_FASTSEEK + DWORD cl, pcl, ncl, tcl, dsc, tlen, ulen, *tbl; +#endif + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */ +#if _USE_FASTSEEK + if (fp->cltbl) { /* Fast seek */ + if (ofs == CREATE_LINKMAP) { /* Create CLMT */ + tbl = fp->cltbl; + tlen = *tbl++; ulen = 2; /* Given table size and required table size */ + cl = fp->obj.sclust; /* Origin of the chain */ + if (cl) { + do { + /* Get a fragment */ + tcl = cl; ncl = 0; ulen += 2; /* Top, length and used items */ + do { + pcl = cl; ncl++; + cl = get_fat(&fp->obj, cl); + if (cl <= 1) ABORT(fs, FR_INT_ERR); + if (cl == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + } while (cl == pcl + 1); + if (ulen <= tlen) { /* Store the length and top of the fragment */ + *tbl++ = ncl; *tbl++ = tcl; + } + } while (cl < fs->n_fatent); /* Repeat until end of chain */ + } + *fp->cltbl = ulen; /* Number of items used */ + if (ulen <= tlen) { + *tbl = 0; /* Terminate table */ + } else { + res = FR_NOT_ENOUGH_CORE; /* Given table size is smaller than required */ + } + } else { /* Fast seek */ + if (ofs > fp->obj.objsize) ofs = fp->obj.objsize; /* Clip offset at the file size */ + fp->fptr = ofs; /* Set file pointer */ + if (ofs) { + fp->clust = clmt_clust(fp, ofs - 1); + dsc = clust2sect(fs, fp->clust); + if (!dsc) ABORT(fs, FR_INT_ERR); + dsc += (DWORD)((ofs - 1) / SS(fs)) & (fs->csize - 1); + if (fp->fptr % SS(fs) && dsc != fp->sect) { /* Refill sector cache if needed */ +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->drv, fp->buf, dsc, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Load current sector */ +#endif + fp->sect = dsc; + } + } + } + } else +#endif + + /* Normal Seek */ + { +#if _FS_EXFAT + if (fs->fs_type != FS_EXFAT && ofs >= 0x100000000) ofs = 0xFFFFFFFF; /* Clip at 4GiB-1 if at FATxx */ +#endif + if (ofs > fp->obj.objsize && (_FS_READONLY || !(fp->flag & FA_WRITE))) { /* In read-only mode, clip offset with the file size */ + ofs = fp->obj.objsize; + } + ifptr = fp->fptr; + fp->fptr = nsect = 0; + if (ofs) { + bcs = (DWORD)fs->csize * SS(fs); /* Cluster size (byte) */ + if (ifptr > 0 && + (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */ + fp->fptr = (ifptr - 1) & ~(FSIZE_t)(bcs - 1); /* start from the current cluster */ + ofs -= fp->fptr; + clst = fp->clust; + } else { /* When seek to back cluster, */ + clst = fp->obj.sclust; /* start from the first cluster */ +#if !_FS_READONLY + if (clst == 0) { /* If no cluster chain, create a new chain */ + clst = create_chain(&fp->obj, 0); + if (clst == 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->obj.sclust = clst; + } +#endif + fp->clust = clst; + } + if (clst != 0) { + while (ofs > bcs) { /* Cluster following loop */ + ofs -= bcs; fp->fptr += bcs; +#if !_FS_READONLY + if (fp->flag & FA_WRITE) { /* Check if in write mode or not */ + if (_FS_EXFAT && fp->fptr > fp->obj.objsize) { /* No FAT chain object needs correct objsize to generate FAT value */ + fp->obj.objsize = fp->fptr; + fp->flag |= FA_MODIFIED; + } + clst = create_chain(&fp->obj, clst); /* Follow chain with forceed stretch */ + if (clst == 0) { /* Clip file size in case of disk full */ + ofs = 0; break; + } + } else +#endif + { + clst = get_fat(&fp->obj, clst); /* Follow cluster chain if not in write mode */ + } + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + if (clst <= 1 || clst >= fs->n_fatent) ABORT(fs, FR_INT_ERR); + fp->clust = clst; + } + fp->fptr += ofs; + if (ofs % SS(fs)) { + nsect = clust2sect(fs, clst); /* Current sector */ + if (!nsect) ABORT(fs, FR_INT_ERR); + nsect += (DWORD)(ofs / SS(fs)); + } + } + } + if (!_FS_READONLY && fp->fptr > fp->obj.objsize) { /* Set file change flag if the file size is extended */ + fp->obj.objsize = fp->fptr; + fp->flag |= FA_MODIFIED; + } + if (fp->fptr % SS(fs) && nsect != fp->sect) { /* Fill sector cache if needed */ +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->drv, fp->buf, nsect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */ +#endif + fp->sect = nsect; + } + } + + LEAVE_FF(fs, res); +} + + + +#if _FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Create a Directory Object */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_opendir ( + DIR* dp, /* Pointer to directory object to create */ + const TCHAR* path /* Pointer to the directory path */ +) +{ + FRESULT res; + FATFS *fs; + _FDID *obj; + DEF_NAMBUF + + + if (!dp) return FR_INVALID_OBJECT; + + /* Get logical drive */ + obj = &dp->obj; + res = find_volume(&path, &fs, 0); + if (res == FR_OK) { + obj->fs = fs; + INIT_NAMBUF(fs); + res = follow_path(dp, path); /* Follow the path to the directory */ + if (res == FR_OK) { /* Follow completed */ + if (!(dp->fn[NSFLAG] & NS_NONAME)) { /* It is not the origin directory itself */ + if (obj->attr & AM_DIR) { /* This object is a sub-directory */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + obj->c_scl = obj->sclust; /* Save containing directory inforamation */ + obj->c_size = ((DWORD)obj->objsize & 0xFFFFFF00) | obj->stat; + obj->c_ofs = dp->blk_ofs; + obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Get object location and status */ + obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize); + obj->stat = fs->dirbuf[XDIR_GenFlags] & 2; + } else +#endif + { + obj->sclust = ld_clust(fs, dp->dir); /* Get object location */ + } + } else { /* This object is a file */ + res = FR_NO_PATH; + } + } + if (res == FR_OK) { + obj->id = fs->id; + res = dir_sdi(dp, 0); /* Rewind directory */ +#if _FS_LOCK != 0 + if (res == FR_OK) { + if (obj->sclust) { + obj->lockid = inc_lock(dp, 0); /* Lock the sub directory */ + if (!obj->lockid) res = FR_TOO_MANY_OPEN_FILES; + } else { + obj->lockid = 0; /* Root directory need not to be locked */ + } + } +#endif + } + } + FREE_NAMBUF(); + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + if (res != FR_OK) obj->fs = 0; /* Invalidate the directory object if function faild */ + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Close Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_closedir ( + DIR *dp /* Pointer to the directory object to be closed */ +) +{ + FRESULT res; + FATFS *fs; + + + res = validate(&dp->obj, &fs); /* Check validity of the file object */ + if (res == FR_OK) { +#if _FS_LOCK != 0 + if (dp->obj.lockid) { /* Decrement sub-directory open counter */ + res = dec_lock(dp->obj.lockid); + } + if (res == FR_OK) +#endif + { + dp->obj.fs = 0; /* Invalidate directory object */ + } +#if _FS_REENTRANT + unlock_fs(fs, FR_OK); /* Unlock volume */ +#endif + } + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read Directory Entries in Sequence */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_readdir ( + DIR* dp, /* Pointer to the open directory object */ + FILINFO* fno /* Pointer to file information to return */ +) +{ + FRESULT res; + FATFS *fs; + DEF_NAMBUF + + + res = validate(&dp->obj, &fs); /* Check validity of the directory object */ + if (res == FR_OK) { + if (!fno) { + res = dir_sdi(dp, 0); /* Rewind the directory object */ + } else { + INIT_NAMBUF(fs); + res = dir_read(dp, 0); /* Read an item */ + if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory */ + if (res == FR_OK) { /* A valid entry is found */ + get_fileinfo(dp, fno); /* Get the object information */ + res = dir_next(dp, 0); /* Increment index for next */ + if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory now */ + } + FREE_NAMBUF(); + } + } + LEAVE_FF(fs, res); +} + + + +#if _USE_FIND +/*-----------------------------------------------------------------------*/ +/* Find Next File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_findnext ( + DIR* dp, /* Pointer to the open directory object */ + FILINFO* fno /* Pointer to the file information structure */ +) +{ + FRESULT res; + + + for (;;) { + res = f_readdir(dp, fno); /* Get a directory item */ + if (res != FR_OK || !fno || !fno->fname[0]) break; /* Terminate if any error or end of directory */ + if (pattern_matching(dp->pat, fno->fname, 0, 0)) break; /* Test for the file name */ +#if _USE_LFN != 0 && _USE_FIND == 2 + if (pattern_matching(dp->pat, fno->altname, 0, 0)) break; /* Test for alternative name if exist */ +#endif + } + return res; +} + + + +/*-----------------------------------------------------------------------*/ +/* Find First File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_findfirst ( + DIR* dp, /* Pointer to the blank directory object */ + FILINFO* fno, /* Pointer to the file information structure */ + const TCHAR* path, /* Pointer to the directory to open */ + const TCHAR* pattern /* Pointer to the matching pattern */ +) +{ + FRESULT res; + + + dp->pat = pattern; /* Save pointer to pattern string */ + res = f_opendir(dp, path); /* Open the target directory */ + if (res == FR_OK) { + res = f_findnext(dp, fno); /* Find the first item */ + } + return res; +} + +#endif /* _USE_FIND */ + + + +#if _FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Get File Status */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_stat ( + const TCHAR* path, /* Pointer to the file path */ + FILINFO* fno /* Pointer to file information to return */ +) +{ + FRESULT res; + DIR dj; + DEF_NAMBUF + + + /* Get logical drive */ + res = find_volume(&path, &dj.obj.fs, 0); + if (res == FR_OK) { + INIT_NAMBUF(dj.obj.fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) { /* Follow completed */ + if (dj.fn[NSFLAG] & NS_NONAME) { /* It is origin directory */ + res = FR_INVALID_NAME; + } else { /* Found an object */ + if (fno) get_fileinfo(&dj, fno); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(dj.obj.fs, res); +} + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Get Number of Free Clusters */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getfree ( + const TCHAR* path, /* Path name of the logical drive number */ + DWORD* nclst, /* Pointer to a variable to return number of free clusters */ + FATFS** fatfs /* Pointer to return pointer to corresponding file system object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD nfree, clst, sect, stat; + UINT i; + BYTE *p; + _FDID obj; + + + /* Get logical drive */ + res = find_volume(&path, &fs, 0); + if (res == FR_OK) { + *fatfs = fs; /* Return ptr to the fs object */ + /* If free_clst is valid, return it without full cluster scan */ + if (fs->free_clst <= fs->n_fatent - 2) { + *nclst = fs->free_clst; + } else { + /* Get number of free clusters */ + nfree = 0; + if (fs->fs_type == FS_FAT12) { /* FAT12: Sector unalighed FAT entries */ + clst = 2; obj.fs = fs; + do { + stat = get_fat(&obj, clst); + if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } + if (stat == 1) { res = FR_INT_ERR; break; } + if (stat == 0) nfree++; + } while (++clst < fs->n_fatent); + } else { +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* exFAT: Scan bitmap table */ + BYTE bm; + UINT b; + + clst = fs->n_fatent - 2; + sect = fs->database; + i = 0; + do { + if (i == 0 && (res = move_window(fs, sect++)) != FR_OK) break; + for (b = 8, bm = fs->win[i]; b && clst; b--, clst--) { + if (!(bm & 1)) nfree++; + bm >>= 1; + } + i = (i + 1) % SS(fs); + } while (clst); + } else +#endif + { /* FAT16/32: Sector alighed FAT entries */ + clst = fs->n_fatent; sect = fs->fatbase; + i = 0; p = 0; + do { + if (i == 0) { + res = move_window(fs, sect++); + if (res != FR_OK) break; + p = fs->win; + i = SS(fs); + } + if (fs->fs_type == FS_FAT16) { + if (ld_word(p) == 0) nfree++; + p += 2; i -= 2; + } else { + if ((ld_dword(p) & 0x0FFFFFFF) == 0) nfree++; + p += 4; i -= 4; + } + } while (--clst); + } + } + *nclst = nfree; /* Return the free clusters */ + fs->free_clst = nfree; /* Now free_clst is valid */ + fs->fsi_flag |= 1; /* FSInfo is to be updated */ + } + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Truncate File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_truncate ( + FIL* fp /* Pointer to the file object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD ncl; + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + if (fp->obj.objsize > fp->fptr) { + if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */ + res = remove_chain(&fp->obj, fp->obj.sclust, 0); + fp->obj.sclust = 0; + } else { /* When truncate a part of the file, remove remaining clusters */ + ncl = get_fat(&fp->obj, fp->clust); + res = FR_OK; + if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (ncl == 1) res = FR_INT_ERR; + if (res == FR_OK && ncl < fs->n_fatent) { + res = remove_chain(&fp->obj, ncl, fp->clust); + } + } + fp->obj.objsize = fp->fptr; /* Set file size to current R/W point */ + fp->flag |= FA_MODIFIED; +#if !_FS_TINY + if (res == FR_OK && (fp->flag & FA_DIRTY)) { + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) { + res = FR_DISK_ERR; + } else { + fp->flag &= (BYTE)~FA_DIRTY; + } + } +#endif + if (res != FR_OK) ABORT(fs, res); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Delete a File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_unlink ( + const TCHAR* path /* Pointer to the file or directory path */ +) +{ + FRESULT res; + DIR dj, sdj; + DWORD dclst = 0; + FATFS *fs; +#if _FS_EXFAT + _FDID obj; +#endif + DEF_NAMBUF + + + /* Get logical drive */ + res = find_volume(&path, &fs, FA_WRITE); + dj.obj.fs = fs; + if (res == FR_OK) { + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (_FS_RPATH && res == FR_OK && (dj.fn[NSFLAG] & NS_DOT)) { + res = FR_INVALID_NAME; /* Cannot remove dot entry */ + } +#if _FS_LOCK != 0 + if (res == FR_OK) res = chk_lock(&dj, 2); /* Check if it is an open object */ +#endif + if (res == FR_OK) { /* The object is accessible */ + if (dj.fn[NSFLAG] & NS_NONAME) { + res = FR_INVALID_NAME; /* Cannot remove the origin directory */ + } else { + if (dj.obj.attr & AM_RDO) { + res = FR_DENIED; /* Cannot remove R/O object */ + } + } + if (res == FR_OK) { +#if _FS_EXFAT + obj.fs = fs; + if (fs->fs_type == FS_EXFAT) { + obj.sclust = dclst = ld_dword(fs->dirbuf + XDIR_FstClus); + obj.objsize = ld_qword(fs->dirbuf + XDIR_FileSize); + obj.stat = fs->dirbuf[XDIR_GenFlags] & 2; + } else +#endif + { + dclst = ld_clust(fs, dj.dir); + } + if (dj.obj.attr & AM_DIR) { /* Is it a sub-directory ? */ +#if _FS_RPATH != 0 + if (dclst == fs->cdir) { /* Is it the current directory? */ + res = FR_DENIED; + } else +#endif + { + sdj.obj.fs = fs; /* Open the sub-directory */ + sdj.obj.sclust = dclst; +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + sdj.obj.objsize = obj.objsize; + sdj.obj.stat = obj.stat; + } +#endif + res = dir_sdi(&sdj, 0); + if (res == FR_OK) { + res = dir_read(&sdj, 0); /* Read an item */ + if (res == FR_OK) res = FR_DENIED; /* Not empty? */ + if (res == FR_NO_FILE) res = FR_OK; /* Empty? */ + } + } + } + } + if (res == FR_OK) { + res = dir_remove(&dj); /* Remove the directory entry */ + if (res == FR_OK && dclst) { /* Remove the cluster chain if exist */ +#if _FS_EXFAT + res = remove_chain(&obj, dclst, 0); +#else + res = remove_chain(&dj.obj, dclst, 0); +#endif + } + if (res == FR_OK) res = sync_fs(fs); + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create a Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkdir ( + const TCHAR* path /* Pointer to the directory path */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + BYTE *dir; + UINT n; + DWORD dsc, dcl, pcl, tm; + DEF_NAMBUF + + + /* Get logical drive */ + res = find_volume(&path, &fs, FA_WRITE); + dj.obj.fs = fs; + if (res == FR_OK) { + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) res = FR_EXIST; /* Any object with same name is already existing */ + if (_FS_RPATH && res == FR_NO_FILE && (dj.fn[NSFLAG] & NS_DOT)) { + res = FR_INVALID_NAME; + } + if (res == FR_NO_FILE) { /* Can create a new directory */ + dcl = create_chain(&dj.obj, 0); /* Allocate a cluster for the new directory table */ + dj.obj.objsize = (DWORD)fs->csize * SS(fs); + res = FR_OK; + if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster */ + if (dcl == 1) res = FR_INT_ERR; + if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (res == FR_OK) res = sync_window(fs); /* Flush FAT */ + tm = GET_FATTIME(); + if (res == FR_OK) { /* Initialize the new directory table */ + dsc = clust2sect(fs, dcl); + dir = fs->win; + mem_set(dir, 0, SS(fs)); + if (!_FS_EXFAT || fs->fs_type != FS_EXFAT) { + mem_set(dir + DIR_Name, ' ', 11); /* Create "." entry */ + dir[DIR_Name] = '.'; + dir[DIR_Attr] = AM_DIR; + st_dword(dir + DIR_ModTime, tm); + st_clust(fs, dir, dcl); + mem_cpy(dir + SZDIRE, dir, SZDIRE); /* Create ".." entry */ + dir[SZDIRE + 1] = '.'; pcl = dj.obj.sclust; + if (fs->fs_type == FS_FAT32 && pcl == fs->dirbase) pcl = 0; + st_clust(fs, dir + SZDIRE, pcl); + } + for (n = fs->csize; n; n--) { /* Write dot entries and clear following sectors */ + fs->winsect = dsc++; + fs->wflag = 1; + res = sync_window(fs); + if (res != FR_OK) break; + mem_set(dir, 0, SS(fs)); + } + } + if (res == FR_OK) res = dir_register(&dj); /* Register the object to the directoy */ + if (res == FR_OK) { +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* Initialize directory entry block */ + st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Created time */ + st_dword(fs->dirbuf + XDIR_FstClus, dcl); /* Table start cluster */ + st_dword(fs->dirbuf + XDIR_FileSize, (DWORD)dj.obj.objsize); /* File size needs to be valid */ + st_dword(fs->dirbuf + XDIR_ValidFileSize, (DWORD)dj.obj.objsize); + fs->dirbuf[XDIR_GenFlags] = 3; /* Initialize the object flag (contiguous) */ + fs->dirbuf[XDIR_Attr] = AM_DIR; /* Attribute */ + res = store_xdir(&dj); + } else +#endif + { + dir = dj.dir; + st_dword(dir + DIR_ModTime, tm); /* Created time */ + st_clust(fs, dir, dcl); /* Table start cluster */ + dir[DIR_Attr] = AM_DIR; /* Attribute */ + fs->wflag = 1; + } + if (res == FR_OK) res = sync_fs(fs); + } else { + remove_chain(&dj.obj, dcl, 0); /* Could not register, remove cluster chain */ + } + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Rename a File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_rename ( + const TCHAR* path_old, /* Pointer to the object name to be renamed */ + const TCHAR* path_new /* Pointer to the new name */ +) +{ + FRESULT res; + DIR djo, djn; + FATFS *fs; + BYTE buf[_FS_EXFAT ? SZDIRE * 2 : 24], *dir; + DWORD dw; + DEF_NAMBUF + + + get_ldnumber(&path_new); /* Ignore drive number of new name */ + res = find_volume(&path_old, &fs, FA_WRITE); /* Get logical drive of the old object */ + if (res == FR_OK) { + djo.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&djo, path_old); /* Check old object */ + if (res == FR_OK && (djo.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check validity of name */ +#if _FS_LOCK != 0 + if (res == FR_OK) res = chk_lock(&djo, 2); +#endif + if (res == FR_OK) { /* Object to be renamed is found */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* At exFAT */ + BYTE nf, nn; + WORD nh; + + mem_cpy(buf, fs->dirbuf, SZDIRE * 2); /* Save 85+C0 entry of old object */ + mem_cpy(&djn, &djo, sizeof djo); + res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */ + if (res == FR_OK) { /* Is new name already in use by any other object? */ + res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST; + } + if (res == FR_NO_FILE) { /* It is a valid path and no name collision */ + res = dir_register(&djn); /* Register the new entry */ + if (res == FR_OK) { + nf = fs->dirbuf[XDIR_NumSec]; nn = fs->dirbuf[XDIR_NumName]; + nh = ld_word(fs->dirbuf + XDIR_NameHash); + mem_cpy(fs->dirbuf, buf, SZDIRE * 2); + fs->dirbuf[XDIR_NumSec] = nf; fs->dirbuf[XDIR_NumName] = nn; + st_word(fs->dirbuf + XDIR_NameHash, nh); +/* Start of critical section where any interruption can cause a cross-link */ + res = store_xdir(&djn); + } + } + } else +#endif + { /* At FAT12/FAT16/FAT32 */ + mem_cpy(buf, djo.dir + DIR_Attr, 21); /* Save information about the object except name */ + mem_cpy(&djn, &djo, sizeof (DIR)); /* Duplicate the directory object */ + res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */ + if (res == FR_OK) { /* Is new name already in use by any other object? */ + res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST; + } + if (res == FR_NO_FILE) { /* It is a valid path and no name collision */ + res = dir_register(&djn); /* Register the new entry */ + if (res == FR_OK) { + dir = djn.dir; /* Copy information about object except name */ + mem_cpy(dir + 13, buf + 2, 19); + dir[DIR_Attr] = buf[0] | AM_ARC; + fs->wflag = 1; + if ((dir[DIR_Attr] & AM_DIR) && djo.obj.sclust != djn.obj.sclust) { /* Update .. entry in the sub-directory if needed */ + dw = clust2sect(fs, ld_clust(fs, dir)); + if (!dw) { + res = FR_INT_ERR; + } else { +/* Start of critical section where any interruption can cause a cross-link */ + res = move_window(fs, dw); + dir = fs->win + SZDIRE * 1; /* Ptr to .. entry */ + if (res == FR_OK && dir[1] == '.') { + st_clust(fs, dir, djn.obj.sclust); + fs->wflag = 1; + } + } + } + } + } + } + if (res == FR_OK) { + res = dir_remove(&djo); /* Remove old entry */ + if (res == FR_OK) { + res = sync_fs(fs); + } + } +/* End of critical section */ + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + +#endif /* !_FS_READONLY */ +#endif /* _FS_MINIMIZE == 0 */ +#endif /* _FS_MINIMIZE <= 1 */ +#endif /* _FS_MINIMIZE <= 2 */ + + + +#if _USE_CHMOD && !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Change Attribute */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chmod ( + const TCHAR* path, /* Pointer to the file path */ + BYTE attr, /* Attribute bits */ + BYTE mask /* Attribute mask to change */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + DEF_NAMBUF + + + res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */ + dj.obj.fs = fs; + if (res == FR_OK) { + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */ + if (res == FR_OK) { + mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */ +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fs->dirbuf[XDIR_Attr] = (attr & mask) | (fs->dirbuf[XDIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + res = store_xdir(&dj); + } else +#endif + { + dj.dir[DIR_Attr] = (attr & mask) | (dj.dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + fs->wflag = 1; + } + if (res == FR_OK) res = sync_fs(fs); + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Timestamp */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_utime ( + const TCHAR* path, /* Pointer to the file/directory name */ + const FILINFO* fno /* Pointer to the time stamp to be set */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + DEF_NAMBUF + + + res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */ + dj.obj.fs = fs; + if (res == FR_OK) { + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */ + if (res == FR_OK) { +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + st_dword(fs->dirbuf + XDIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime); + res = store_xdir(&dj); + } else +#endif + { + st_dword(dj.dir + DIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime); + fs->wflag = 1; + } + if (res == FR_OK) res = sync_fs(fs); + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + +#endif /* _USE_CHMOD && !_FS_READONLY */ + + + +#if _USE_LABEL +/*-----------------------------------------------------------------------*/ +/* Get Volume Label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getlabel ( + const TCHAR* path, /* Path name of the logical drive number */ + TCHAR* label, /* Pointer to a buffer to return the volume label */ + DWORD* vsn /* Pointer to a variable to return the volume serial number */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + UINT si, di; +#if _LFN_UNICODE || _FS_EXFAT + WCHAR w; +#endif + + /* Get logical drive */ + res = find_volume(&path, &fs, 0); + + /* Get volume label */ + if (res == FR_OK && label) { + dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = dir_read(&dj, 1); /* Find a volume label entry */ + if (res == FR_OK) { +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + for (si = di = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */ + w = ld_word(dj.dir + XDIR_Label + si * 2); +#if _LFN_UNICODE + label[di++] = w; +#else + w = ff_convert(w, 0); /* Unicode -> OEM */ + if (w == 0) w = '?'; /* Replace wrong character */ + if (_DF1S && w >= 0x100) label[di++] = (char)(w >> 8); + label[di++] = (char)w; +#endif + } + label[di] = 0; + } else +#endif + { + si = di = 0; /* Extract volume label from AM_VOL entry with code comversion */ + do { +#if _LFN_UNICODE + w = (si < 11) ? dj.dir[si++] : ' '; + if (IsDBCS1(w) && si < 11 && IsDBCS2(dj.dir[si])) { + w = w << 8 | dj.dir[si++]; + } + label[di++] = ff_convert(w, 1); /* OEM -> Unicode */ +#else + label[di++] = dj.dir[si++]; +#endif + } while (di < 11); + do { /* Truncate trailing spaces */ + label[di] = 0; + if (di == 0) break; + } while (label[--di] == ' '); + } + } + } + if (res == FR_NO_FILE) { /* No label entry and return nul string */ + label[0] = 0; + res = FR_OK; + } + } + + /* Get volume serial number */ + if (res == FR_OK && vsn) { + res = move_window(fs, fs->volbase); + if (res == FR_OK) { + switch (fs->fs_type) { + case FS_EXFAT: di = BPB_VolIDEx; break; + case FS_FAT32: di = BS_VolID32; break; + default: di = BS_VolID; + } + *vsn = ld_dword(fs->win + di); + } + } + + LEAVE_FF(fs, res); +} + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Set Volume Label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_setlabel ( + const TCHAR* label /* Pointer to the volume label to set */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + BYTE dirvn[22]; + UINT i, j, slen; + WCHAR w; + static const char badchr[] = "\"*+,.:;<=>\?[]|\x7F"; + + + /* Get logical drive */ + res = find_volume(&label, &fs, FA_WRITE); + if (res != FR_OK) LEAVE_FF(fs, res); + dj.obj.fs = fs; + + /* Get length of given volume label */ + for (slen = 0; (UINT)label[slen] >= ' '; slen++) ; /* Get name length */ + +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + for (i = j = 0; i < slen; ) { /* Create volume label in directory form */ + w = label[i++]; +#if !_LFN_UNICODE + if (IsDBCS1(w)) { + w = (i < slen && IsDBCS2(label[i])) ? w << 8 | (BYTE)label[i++] : 0; + } + w = ff_convert(w, 1); +#endif + if (w == 0 || chk_chr(badchr, w) || j == 22) { /* Check validity check validity of the volume label */ + LEAVE_FF(fs, FR_INVALID_NAME); + } + st_word(dirvn + j, w); j += 2; + } + slen = j; + } else +#endif + { /* On the FAT12/16/32 volume */ + for ( ; slen && label[slen - 1] == ' '; slen--) ; /* Remove trailing spaces */ + if (slen) { /* Is there a volume label to be set? */ + dirvn[0] = 0; i = j = 0; /* Create volume label in directory form */ + do { +#if _LFN_UNICODE + w = ff_convert(ff_wtoupper(label[i++]), 0); +#else + w = (BYTE)label[i++]; + if (IsDBCS1(w)) { + w = (j < 10 && i < slen && IsDBCS2(label[i])) ? w << 8 | (BYTE)label[i++] : 0; + } +#if _USE_LFN != 0 + w = ff_convert(ff_wtoupper(ff_convert(w, 1)), 0); +#else + if (IsLower(w)) w -= 0x20; /* To upper ASCII characters */ +#ifdef _EXCVT + if (w >= 0x80) w = ExCvt[w - 0x80]; /* To upper extended characters (SBCS cfg) */ +#else + if (!_DF1S && w >= 0x80) w = 0; /* Reject extended characters (ASCII cfg) */ +#endif +#endif +#endif + if (w == 0 || chk_chr(badchr, w) || j >= (UINT)((w >= 0x100) ? 10 : 11)) { /* Reject invalid characters for volume label */ + LEAVE_FF(fs, FR_INVALID_NAME); + } + if (w >= 0x100) dirvn[j++] = (BYTE)(w >> 8); + dirvn[j++] = (BYTE)w; + } while (i < slen); + while (j < 11) dirvn[j++] = ' '; /* Fill remaining name field */ + if (dirvn[0] == DDEM) LEAVE_FF(fs, FR_INVALID_NAME); /* Reject illegal name (heading DDEM) */ + } + } + + /* Set volume label */ + dj.obj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = dir_read(&dj, 1); /* Get volume label entry */ + if (res == FR_OK) { + if (_FS_EXFAT && fs->fs_type == FS_EXFAT) { + dj.dir[XDIR_NumLabel] = (BYTE)(slen / 2); /* Change the volume label */ + mem_cpy(dj.dir + XDIR_Label, dirvn, slen); + } else { + if (slen) { + mem_cpy(dj.dir, dirvn, 11); /* Change the volume label */ + } else { + dj.dir[DIR_Name] = DDEM; /* Remove the volume label */ + } + } + fs->wflag = 1; + res = sync_fs(fs); + } else { /* No volume label entry is found or error */ + if (res == FR_NO_FILE) { + res = FR_OK; + if (slen) { /* Create a volume label entry */ + res = dir_alloc(&dj, 1); /* Allocate an entry */ + if (res == FR_OK) { + mem_set(dj.dir, 0, SZDIRE); /* Clear the entry */ + if (_FS_EXFAT && fs->fs_type == FS_EXFAT) { + dj.dir[XDIR_Type] = 0x83; /* Create 83 entry */ + dj.dir[XDIR_NumLabel] = (BYTE)(slen / 2); + mem_cpy(dj.dir + XDIR_Label, dirvn, slen); + } else { + dj.dir[DIR_Attr] = AM_VOL; /* Create volume label entry */ + mem_cpy(dj.dir, dirvn, 11); + } + fs->wflag = 1; + res = sync_fs(fs); + } + } + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* !_FS_READONLY */ +#endif /* _USE_LABEL */ + + + +#if _USE_EXPAND && !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Allocate a Contiguous Blocks to the File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_expand ( + FIL* fp, /* Pointer to the file object */ + FSIZE_t fsz, /* File size to be expanded to */ + BYTE opt /* Operation mode 0:Find and prepare or 1:Find and allocate */ +) +{ + FRESULT res; + FATFS *fs; + DWORD n, clst, stcl, scl, ncl, tcl, lclst; + + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (fsz == 0 || fp->obj.objsize != 0 || !(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); +#if _FS_EXFAT + if (fs->fs_type != FS_EXFAT && fsz >= 0x100000000) LEAVE_FF(fs, FR_DENIED); /* Check if in size limit */ +#endif + n = (DWORD)fs->csize * SS(fs); /* Cluster size */ + tcl = (DWORD)(fsz / n) + ((fsz & (n - 1)) ? 1 : 0); /* Number of clusters required */ + stcl = fs->last_clst; lclst = 0; + if (stcl < 2 || stcl >= fs->n_fatent) stcl = 2; + +#if _FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + scl = find_bitmap(fs, stcl, tcl); /* Find a contiguous cluster block */ + if (scl == 0) res = FR_DENIED; /* No contiguous cluster block was found */ + if (scl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (res == FR_OK) { + if (opt) { + res = change_bitmap(fs, scl, tcl, 1); /* Mark the cluster block 'in use' */ + lclst = scl + tcl - 1; + } else { + lclst = scl - 1; + } + } + } else +#endif + { + scl = clst = stcl; ncl = 0; + for (;;) { /* Find a contiguous cluster block */ + n = get_fat(&fp->obj, clst); + if (++clst >= fs->n_fatent) clst = 2; + if (n == 1) { res = FR_INT_ERR; break; } + if (n == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } + if (n == 0) { /* Is it a free cluster? */ + if (++ncl == tcl) break; /* Break if a contiguous cluster block is found */ + } else { + scl = clst; ncl = 0; /* Not a free cluster */ + } + if (clst == stcl) { res = FR_DENIED; break; } /* No contiguous cluster? */ + } + if (res == FR_OK) { + if (opt) { + for (clst = scl, n = tcl; n; clst++, n--) { /* Create a cluster chain on the FAT */ + res = put_fat(fs, clst, (n == 1) ? 0xFFFFFFFF : clst + 1); + if (res != FR_OK) break; + lclst = clst; + } + } else { + lclst = scl - 1; + } + } + } + + if (res == FR_OK) { + fs->last_clst = lclst; /* Set suggested start cluster to start next */ + if (opt) { + fp->obj.sclust = scl; /* Update object allocation information */ + fp->obj.objsize = fsz; + if (_FS_EXFAT) fp->obj.stat = 2; /* Set status 'contiguous chain' */ + fp->flag |= FA_MODIFIED; + if (fs->free_clst < fs->n_fatent - 2) { /* Update FSINFO */ + fs->free_clst -= tcl; + fs->fsi_flag |= 1; + } + } + } + + LEAVE_FF(fs, res); +} + +#endif /* _USE_EXPAND && !_FS_READONLY */ + + + +#if _USE_FORWARD +/*-----------------------------------------------------------------------*/ +/* Forward data to the stream directly */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_forward ( + FIL* fp, /* Pointer to the file object */ + UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */ + UINT btf, /* Number of bytes to forward */ + UINT* bf /* Pointer to number of bytes forwarded */ +) +{ + FRESULT res; + FATFS *fs; + DWORD clst, sect; + FSIZE_t remain; + UINT rcnt, csect; + BYTE *dbuf; + + + *bf = 0; /* Clear transfer byte counter */ + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); + if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + + remain = fp->obj.objsize - fp->fptr; + if (btf > remain) btf = (UINT)remain; /* Truncate btf by remaining bytes */ + + for ( ; btf && (*func)(0, 0); /* Repeat until all data transferred or stream goes busy */ + fp->fptr += rcnt, *bf += rcnt, btf -= rcnt) { + csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */ + if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ + if (csect == 0) { /* On the cluster boundary? */ + clst = (fp->fptr == 0) ? /* On the top of the file? */ + fp->obj.sclust : get_fat(&fp->obj, fp->clust); + if (clst <= 1) ABORT(fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } + } + sect = clust2sect(fs, fp->clust); /* Get current data sector */ + if (!sect) ABORT(fs, FR_INT_ERR); + sect += csect; +#if _FS_TINY + if (move_window(fs, sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window to the file data */ + dbuf = fs->win; +#else + if (fp->sect != sect) { /* Fill sector cache with file data */ +#if !_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + } + dbuf = fp->buf; +#endif + fp->sect = sect; + rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */ + if (rcnt > btf) rcnt = btf; /* Clip it by btr if needed */ + rcnt = (*func)(dbuf + ((UINT)fp->fptr % SS(fs)), rcnt); /* Forward the file data */ + if (!rcnt) ABORT(fs, FR_INT_ERR); + } + + LEAVE_FF(fs, FR_OK); +} +#endif /* _USE_FORWARD */ + + + +#if _USE_MKFS && !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Create FAT file system on the logical drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkfs ( + const TCHAR* path, /* Logical drive number */ + BYTE opt, /* Format option */ + DWORD au, /* Size of allocation unit [byte] */ + void* work, /* Pointer to working buffer */ + UINT len /* Size of working buffer */ +) +{ + const UINT n_fats = 1; /* Number of FATs for FAT12/16/32 volume (1 or 2) */ + const UINT n_rootdir = 512; /* Number of root directory entries for FAT12/16 volume */ + static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT12/16 volume (4Ks unit) */ + static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */ + BYTE fmt, sys, *buf, *pte, pdrv, part; + WORD ss; + DWORD szb_buf, sz_buf, sz_blk, n_clst, pau, sect, nsect, n; + DWORD b_vol, b_fat, b_data; /* Base LBA for volume, fat, data */ + DWORD sz_vol, sz_rsv, sz_fat, sz_dir; /* Size for volume, fat, dir, data */ + UINT i; + int vol; + DSTATUS stat; +#if _USE_TRIM || _FS_EXFAT + DWORD tbl[3]; +#endif + + + /* Check mounted drive and clear work area */ + vol = get_ldnumber(&path); /* Get target logical drive */ + if (vol < 0) return FR_INVALID_DRIVE; + if (FatFs[vol]) FatFs[vol]->fs_type = 0; /* Clear mounted volume */ + pdrv = LD2PD(vol); /* Physical drive */ + part = LD2PT(vol); /* Partition (0:create as new, 1-4:get from partition table) */ + + /* Check physical drive status */ + stat = disk_initialize(pdrv); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; + if (disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_blk) != RES_OK || !sz_blk || sz_blk > 32768 || (sz_blk & (sz_blk - 1))) sz_blk = 1; /* Erase block to align data area */ +#if _MAX_SS != _MIN_SS /* Get sector size of the medium */ + if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR; + if (ss > _MAX_SS || ss < _MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR; +#else + ss = _MAX_SS; +#endif + if ((au != 0 && au < ss) || au > 0x1000000 || (au & (au - 1))) return FR_INVALID_PARAMETER; /* Check if au is valid */ + au /= ss; /* Cluster size in unit of sector */ + + /* Get working buffer */ + buf = (BYTE*)work; /* Working buffer */ + sz_buf = len / ss; /* Size of working buffer (sector) */ + szb_buf = sz_buf * ss; /* Size of working buffer (byte) */ + if (!szb_buf) return FR_MKFS_ABORTED; + + /* Determine where the volume to be located (b_vol, sz_vol) */ + if (_MULTI_PARTITION && part != 0) { + /* Get partition information from partition table in the MBR */ + if (disk_read(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Load MBR */ + if (ld_word(buf + BS_55AA) != 0xAA55) return FR_MKFS_ABORTED; /* Check if MBR is valid */ + pte = buf + (MBR_Table + (part - 1) * SZ_PTE); + if (!pte[PTE_System]) return FR_MKFS_ABORTED; /* No partition? */ + b_vol = ld_dword(pte + PTE_StLba); /* Get volume start sector */ + sz_vol = ld_dword(pte + PTE_SizLba); /* Get volume size */ + } else { + /* Create a single-partition in this function */ + if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_vol) != RES_OK) return FR_DISK_ERR; + b_vol = (opt & FM_SFD) ? 0 : 63; /* Volume start sector */ + if (sz_vol < b_vol) return FR_MKFS_ABORTED; + sz_vol -= b_vol; /* Volume size */ + } + if (sz_vol < 128) return FR_MKFS_ABORTED; /* Check if volume size is >=128s */ + + /* Pre-determine the FAT type */ + do { + if (_FS_EXFAT && (opt & FM_EXFAT)) { /* exFAT possible? */ + if ((opt & FM_ANY) == FM_EXFAT || sz_vol >= 0x4000000 || au > 128) { /* exFAT only, vol >= 64Ms or au > 128s ? */ + fmt = FS_EXFAT; break; + } + } + if (au > 128) return FR_INVALID_PARAMETER; /* Too large au for FAT/FAT32 */ + if (opt & FM_FAT32) { /* FAT32 possible? */ + if ((opt & FM_ANY) == FM_FAT32 || !(opt & FM_FAT)) { /* FAT32 only or no-FAT? */ + fmt = FS_FAT32; break; + } + } + if (!(opt & FM_FAT)) return FR_INVALID_PARAMETER; /* no-FAT? */ + fmt = FS_FAT16; + } while (0); + +#if _FS_EXFAT + if (fmt == FS_EXFAT) { /* Create an exFAT volume */ + DWORD szb_bit, szb_case, sum, nb, cl; + WCHAR ch, si; + UINT j, st; + BYTE b; + + if (sz_vol < 0x1000) return FR_MKFS_ABORTED; /* Too small volume? */ +#if _USE_TRIM + tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area can be erased */ + disk_ioctl(pdrv, CTRL_TRIM, tbl); +#endif + /* Determine FAT location, data location and number of clusters */ + if (!au) { /* au auto-selection */ + au = 8; + if (sz_vol >= 0x80000) au = 64; /* >= 512Ks */ + if (sz_vol >= 0x4000000) au = 256; /* >= 64Ms */ + } + b_fat = b_vol + 32; /* FAT start at offset 32 */ + sz_fat = ((sz_vol / au + 2) * 4 + ss - 1) / ss; /* Number of FAT sectors */ + b_data = (b_fat + sz_fat + sz_blk - 1) & ~(sz_blk - 1); /* Align data area to the erase block boundary */ + if (b_data >= sz_vol / 2) return FR_MKFS_ABORTED; /* Too small volume? */ + n_clst = (sz_vol - (b_data - b_vol)) / au; /* Number of clusters */ + if (n_clst <16) return FR_MKFS_ABORTED; /* Too few clusters? */ + if (n_clst > MAX_EXFAT) return FR_MKFS_ABORTED; /* Too many clusters? */ + + szb_bit = (n_clst + 7) / 8; /* Size of allocation bitmap */ + tbl[0] = (szb_bit + au * ss - 1) / (au * ss); /* Number of allocation bitmap clusters */ + + /* Create a compressed up-case table */ + sect = b_data + au * tbl[0]; /* Table start sector */ + sum = 0; /* Table checksum to be stored in the 82 entry */ + st = si = i = j = szb_case = 0; + do { + switch (st) { + case 0: + ch = ff_wtoupper(si); /* Get an up-case char */ + if (ch != si) { + si++; break; /* Store the up-case char if exist */ + } + for (j = 1; (WCHAR)(si + j) && (WCHAR)(si + j) == ff_wtoupper((WCHAR)(si + j)); j++) ; /* Get run length of no-case block */ + if (j >= 128) { + ch = 0xFFFF; st = 2; break; /* Compress the no-case block if run is >= 128 */ + } + st = 1; /* Do not compress short run */ + /* continue */ + case 1: + ch = si++; /* Fill the short run */ + if (--j == 0) st = 0; + break; + default: + ch = (WCHAR)j; si += j; /* Number of chars to skip */ + st = 0; + } + sum = xsum32(buf[i + 0] = (BYTE)ch, sum); /* Put it into the write buffer */ + sum = xsum32(buf[i + 1] = (BYTE)(ch >> 8), sum); + i += 2; szb_case += 2; + if (!si || i == szb_buf) { /* Write buffered data when buffer full or end of process */ + n = (i + ss - 1) / ss; + if (disk_write(pdrv, buf, sect, n) != RES_OK) return FR_DISK_ERR; + sect += n; i = 0; + } + } while (si); + tbl[1] = (szb_case + au * ss - 1) / (au * ss); /* Number of up-case table clusters */ + tbl[2] = 1; /* Number of root dir clusters */ + + /* Initialize the allocation bitmap */ + sect = b_data; nsect = (szb_bit + ss - 1) / ss; /* Start of bitmap and number of sectors */ + nb = tbl[0] + tbl[1] + tbl[2]; /* Number of clusters in-use by system */ + do { + mem_set(buf, 0, szb_buf); + for (i = 0; nb >= 8 && i < szb_buf; buf[i++] = 0xFF, nb -= 8) ; + for (b = 1; nb && i < szb_buf; buf[i] |= b, b <<= 1, nb--) ; + n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */ + if (disk_write(pdrv, buf, sect, n) != RES_OK) return FR_DISK_ERR; + sect += n; nsect -= n; + } while (nsect); + + /* Initialize the FAT */ + sect = b_fat; nsect = sz_fat; /* Start of FAT and number of FAT sectors */ + j = nb = cl = 0; + do { + mem_set(buf, 0, szb_buf); i = 0; /* Clear work area and reset write index */ + if (cl == 0) { /* Set entry 0 and 1 */ + st_dword(buf + i, 0xFFFFFFF8); i += 4; cl++; + st_dword(buf + i, 0xFFFFFFFF); i += 4; cl++; + } + do { /* Create chains of bitmap, up-case and root dir */ + while (nb && i < szb_buf) { /* Create a chain */ + st_dword(buf + i, (nb > 1) ? cl + 1 : 0xFFFFFFFF); + i += 4; cl++; nb--; + } + if (!nb && j < 3) nb = tbl[j++]; /* Next chain */ + } while (nb && i < szb_buf); + n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */ + if (disk_write(pdrv, buf, sect, n) != RES_OK) return FR_DISK_ERR; + sect += n; nsect -= n; + } while (nsect); + + /* Initialize the root directory */ + mem_set(buf, 0, szb_buf); + buf[SZDIRE * 0 + 0] = 0x83; /* 83 entry (volume label) */ + buf[SZDIRE * 1 + 0] = 0x81; /* 81 entry (allocation bitmap) */ + st_dword(buf + SZDIRE * 1 + 20, 2); + st_dword(buf + SZDIRE * 1 + 24, szb_bit); + buf[SZDIRE * 2 + 0] = 0x82; /* 82 entry (up-case table) */ + st_dword(buf + SZDIRE * 2 + 4, sum); + st_dword(buf + SZDIRE * 2 + 20, 2 + tbl[0]); + st_dword(buf + SZDIRE * 2 + 24, szb_case); + sect = b_data + au * (tbl[0] + tbl[1]); nsect = au; /* Start of the root directory and number of sectors */ + do { /* Fill root directory sectors */ + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, n) != RES_OK) return FR_DISK_ERR; + mem_set(buf, 0, ss); + sect += n; nsect -= n; + } while (nsect); + + /* Create two set of the exFAT VBR blocks */ + sect = b_vol; + for (n = 0; n < 2; n++) { + /* Main record (+0) */ + mem_set(buf, 0, ss); + mem_cpy(buf + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11); /* Boot jump code (x86), OEM name */ + st_dword(buf + BPB_VolOfsEx, b_vol); /* Volume offset in the physical drive [sector] */ + st_dword(buf + BPB_TotSecEx, sz_vol); /* Volume size [sector] */ + st_dword(buf + BPB_FatOfsEx, b_fat - b_vol); /* FAT offset [sector] */ + st_dword(buf + BPB_FatSzEx, sz_fat); /* FAT size [sector] */ + st_dword(buf + BPB_DataOfsEx, b_data - b_vol); /* Data offset [sector] */ + st_dword(buf + BPB_NumClusEx, n_clst); /* Number of clusters */ + st_dword(buf + BPB_RootClusEx, 2 + tbl[0] + tbl[1]); /* Root dir cluster # */ + st_dword(buf + BPB_VolIDEx, GET_FATTIME()); /* VSN */ + st_word(buf + BPB_FSVerEx, 0x100); /* File system version (1.00) */ + for (buf[BPB_BytsPerSecEx] = 0, i = ss; i >>= 1; buf[BPB_BytsPerSecEx]++) ; /* Log2 of sector size [byte] */ + for (buf[BPB_SecPerClusEx] = 0, i = au; i >>= 1; buf[BPB_SecPerClusEx]++) ; /* Log2 of cluster size [sector] */ + buf[BPB_NumFATsEx] = 1; /* Number of FATs */ + buf[BPB_DrvNumEx] = 0x80; /* Drive number (for int13) */ + st_word(buf + BS_BootCodeEx, 0xFEEB); /* Boot code (x86) */ + st_word(buf + BS_55AA, 0xAA55); /* Signature (placed here regardless of sector size) */ + for (i = sum = 0; i < ss; i++) { /* VBR checksum */ + if (i != BPB_VolFlagEx && i != BPB_VolFlagEx + 1 && i != BPB_PercInUseEx) sum = xsum32(buf[i], sum); + } + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) return FR_DISK_ERR; + /* Extended bootstrap record (+1..+8) */ + mem_set(buf, 0, ss); + st_word(buf + ss - 2, 0xAA55); /* Signature (placed at end of sector) */ + for (j = 1; j < 9; j++) { + for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) return FR_DISK_ERR; + } + /* OEM/Reserved record (+9..+10) */ + mem_set(buf, 0, ss); + for ( ; j < 11; j++) { + for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) return FR_DISK_ERR; + } + /* Sum record (+11) */ + for (i = 0; i < ss; i += 4) st_dword(buf + i, sum); /* Fill with checksum value */ + if (disk_write(pdrv, buf, sect++, 1) != RES_OK) return FR_DISK_ERR; + } + + } else +#endif /* _FS_EXFAT */ + { /* Create an FAT12/16/32 volume */ + do { + pau = au; + /* Pre-determine number of clusters and FAT sub-type */ + if (fmt == FS_FAT32) { /* FAT32 volume */ + if (!pau) { /* au auto-selection */ + n = sz_vol / 0x20000; /* Volume size in unit of 128KS */ + for (i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1) ; /* Get from table */ + } + n_clst = sz_vol / pau; /* Number of clusters */ + sz_fat = (n_clst * 4 + 8 + ss - 1) / ss; /* FAT size [sector] */ + sz_rsv = 32; /* Number of reserved sectors */ + sz_dir = 0; /* No static directory */ + if (n_clst <= MAX_FAT16 || n_clst > MAX_FAT32) return FR_MKFS_ABORTED; + } else { /* FAT12/16 volume */ + if (!pau) { /* au auto-selection */ + n = sz_vol / 0x1000; /* Volume size in unit of 4KS */ + for (i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1) ; /* Get from table */ + } + n_clst = sz_vol / pau; + if (n_clst > MAX_FAT12) { + n = n_clst * 2 + 4; /* FAT size [byte] */ + } else { + fmt = FS_FAT12; + n = (n_clst * 3 + 1) / 2 + 3; /* FAT size [byte] */ + } + sz_fat = (n + ss - 1) / ss; /* FAT size [sector] */ + sz_rsv = 1; /* Number of reserved sectors */ + sz_dir = (DWORD)n_rootdir * SZDIRE / ss; /* Rootdir size [sector] */ + } + b_fat = b_vol + sz_rsv; /* FAT base */ + b_data = b_fat + sz_fat * n_fats + sz_dir; /* Data base */ + + /* Align data base to erase block boundary (for flash memory media) */ + n = ((b_data + sz_blk - 1) & ~(sz_blk - 1)) - b_data; /* Next nearest erase block from current data base */ + if (fmt == FS_FAT32) { /* FAT32: Move FAT base */ + sz_rsv += n; b_fat += n; + } else { /* FAT12/16: Expand FAT size */ + sz_fat += n / n_fats; + } + + /* Determine number of clusters and final check of validity of the FAT sub-type */ + if (sz_vol < b_data + pau * 16 - b_vol) return FR_MKFS_ABORTED; /* Too small volume */ + n_clst = (sz_vol - sz_rsv - sz_fat * n_fats - sz_dir) / pau; + if (fmt == FS_FAT32) { + if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32 */ + if (!au && (au = pau / 2) != 0) continue; /* Adjust cluster size and retry */ + return FR_MKFS_ABORTED; + } + } + if (fmt == FS_FAT16) { + if (n_clst > MAX_FAT16) { /* Too many clusters for FAT16 */ + if (!au && (pau * 2) <= 64) { + au = pau * 2; continue; /* Adjust cluster size and retry */ + } + if ((opt & FM_FAT32)) { + fmt = FS_FAT32; continue; /* Switch type to FAT32 and retry */ + } + if (!au && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ + return FR_MKFS_ABORTED; + } + if (n_clst <= MAX_FAT12) { /* Too few clusters for FAT16 */ + if (!au && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ + return FR_MKFS_ABORTED; + } + } + if (fmt == FS_FAT12 && n_clst > MAX_FAT12) return FR_MKFS_ABORTED; /* Too many clusters for FAT12 */ + + /* Ok, it is the valid cluster configuration */ + break; + } while (1); + +#if _USE_TRIM + tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area can be erased */ + disk_ioctl(pdrv, CTRL_TRIM, tbl); +#endif + /* Create FAT VBR */ + mem_set(buf, 0, ss); + mem_cpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code (x86), OEM name */ + st_word(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */ + buf[BPB_SecPerClus] = (BYTE)pau; /* Cluster size [sector] */ + st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv); /* Size of reserved area */ + buf[BPB_NumFATs] = (BYTE)n_fats; /* Number of FATs */ + st_word(buf + BPB_RootEntCnt, (WORD)((fmt == FS_FAT32) ? 0 : n_rootdir)); /* Number of root directory entries */ + if (sz_vol < 0x10000) { + st_word(buf + BPB_TotSec16, (WORD)sz_vol); /* Volume size in 16-bit LBA */ + } else { + st_dword(buf + BPB_TotSec32, sz_vol); /* Volume size in 32-bit LBA */ + } + buf[BPB_Media] = 0xF8; /* Media descriptor byte */ + st_word(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */ + st_word(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */ + st_dword(buf + BPB_HiddSec, b_vol); /* Volume offset in the physical drive [sector] */ + if (fmt == FS_FAT32) { + st_dword(buf + BS_VolID32, GET_FATTIME()); /* VSN */ + st_dword(buf + BPB_FATSz32, sz_fat); /* FAT size [sector] */ + st_dword(buf + BPB_RootClus32, 2); /* Root directory cluster # (2) */ + st_word(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */ + st_word(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */ + buf[BS_DrvNum32] = 0x80; /* Drive number (for int13) */ + buf[BS_BootSig32] = 0x29; /* Extended boot signature */ + mem_cpy(buf + BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */ + } else { + st_dword(buf + BS_VolID, GET_FATTIME()); /* VSN */ + st_word(buf + BPB_FATSz16, (WORD)sz_fat); /* FAT size [sector] */ + buf[BS_DrvNum] = 0x80; /* Drive number (for int13) */ + buf[BS_BootSig] = 0x29; /* Extended boot signature */ + mem_cpy(buf + BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */ + } + st_word(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */ + if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) return FR_DISK_ERR; /* Write it to the VBR sector */ + + /* Create FSINFO record if needed */ + if (fmt == FS_FAT32) { + disk_write(pdrv, buf, b_vol + 6, 1); /* Write backup VBR (VBR + 6) */ + mem_set(buf, 0, ss); + st_dword(buf + FSI_LeadSig, 0x41615252); + st_dword(buf + FSI_StrucSig, 0x61417272); + st_dword(buf + FSI_Free_Count, n_clst - 1); /* Number of free clusters */ + st_dword(buf + FSI_Nxt_Free, 2); /* Last allocated cluster# */ + st_word(buf + BS_55AA, 0xAA55); + disk_write(pdrv, buf, b_vol + 7, 1); /* Write backup FSINFO (VBR + 7) */ + disk_write(pdrv, buf, b_vol + 1, 1); /* Write original FSINFO (VBR + 1) */ + } + + /* Initialize FAT area */ + mem_set(buf, 0, (UINT)szb_buf); + sect = b_fat; /* FAT start sector */ + for (i = 0; i < n_fats; i++) { /* Initialize FATs each */ + if (fmt == FS_FAT32) { + st_dword(buf + 0, 0xFFFFFFF8); /* Entry 0 */ + st_dword(buf + 4, 0xFFFFFFFF); /* Entry 1 */ + st_dword(buf + 8, 0x0FFFFFFF); /* Entry 2 (root directory) */ + } else { + st_dword(buf + 0, (fmt == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8); /* Entry 0 and 1 */ + } + nsect = sz_fat; /* Number of FAT sectors */ + do { /* Fill FAT sectors */ + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) return FR_DISK_ERR; + mem_set(buf, 0, ss); + sect += n; nsect -= n; + } while (nsect); + } + + /* Initialize root directory (fill with zero) */ + nsect = (fmt == FS_FAT32) ? pau : sz_dir; /* Number of root directory sectors */ + do { + n = (nsect > sz_buf) ? sz_buf : nsect; + if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) return FR_DISK_ERR; + sect += n; nsect -= n; + } while (nsect); + } + + /* Determine system ID in the partition table */ + if (_FS_EXFAT && fmt == FS_EXFAT) { + sys = 0x07; /* HPFS/NTFS/exFAT */ + } else { + if (fmt == FS_FAT32) { + sys = 0x0C; /* FAT32X */ + } else { + if (sz_vol >= 0x10000) { + sys = 0x06; /* FAT12/16 (>=64KS) */ + } else { + sys = (fmt == FS_FAT16) ? 0x04 : 0x01; /* FAT16 (<64KS) : FAT12 (<64KS) */ + } + } + } + + if (_MULTI_PARTITION && part != 0) { + /* Update system ID in the partition table */ + if (disk_read(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Read the MBR */ + buf[MBR_Table + (part - 1) * SZ_PTE + PTE_System] = sys; /* Set system type */ + if (disk_write(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Write it back to the MBR */ + } else { + if (!(opt & FM_SFD)) { + /* Create partition table in FDISK format */ + mem_set(buf, 0, ss); + st_word(buf + BS_55AA, 0xAA55); /* MBR signature */ + pte = buf + MBR_Table; /* Create partition table for single partition in the drive */ + pte[PTE_Boot] = 0; /* Boot indicator */ + pte[PTE_StHead] = 1; /* Start head */ + pte[PTE_StSec] = 1; /* Start sector */ + pte[PTE_StCyl] = 0; /* Start cylinder */ + pte[PTE_System] = sys; /* System type */ + n = (b_vol + sz_vol) / (63 * 255); /* (End CHS is incorrect) */ + pte[PTE_EdHead] = 254; /* End head */ + pte[PTE_EdSec] = (BYTE)(n >> 2 | 63); /* End sector */ + pte[PTE_EdCyl] = (BYTE)n; /* End cylinder */ + st_dword(pte + PTE_StLba, b_vol); /* Start offset in LBA */ + st_dword(pte + PTE_SizLba, sz_vol); /* Size in sectors */ + if (disk_write(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Write it to the MBR */ + } + } + + if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) return FR_DISK_ERR; + + return FR_OK; +} + + + +#if _MULTI_PARTITION +/*-----------------------------------------------------------------------*/ +/* Create partition table on the physical drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_fdisk ( + BYTE pdrv, /* Physical drive number */ + const DWORD* szt, /* Pointer to the size table for each partitions */ + void* work /* Pointer to the working buffer */ +) +{ + UINT i, n, sz_cyl, tot_cyl, b_cyl, e_cyl, p_cyl; + BYTE s_hd, e_hd, *p, *buf = (BYTE*)work; + DSTATUS stat; + DWORD sz_disk, sz_part, s_part; + + + stat = disk_initialize(pdrv); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; + if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_disk)) return FR_DISK_ERR; + + /* Determine the CHS without any care of the drive geometry */ + for (n = 16; n < 256 && sz_disk / n / 63 > 1024; n *= 2) ; + if (n == 256) n--; + e_hd = n - 1; + sz_cyl = 63 * n; + tot_cyl = sz_disk / sz_cyl; + + /* Create partition table */ + mem_set(buf, 0, _MAX_SS); + p = buf + MBR_Table; b_cyl = 0; + for (i = 0; i < 4; i++, p += SZ_PTE) { + p_cyl = (szt[i] <= 100U) ? (DWORD)tot_cyl * szt[i] / 100 : szt[i] / sz_cyl; + if (!p_cyl) continue; + s_part = (DWORD)sz_cyl * b_cyl; + sz_part = (DWORD)sz_cyl * p_cyl; + if (i == 0) { /* Exclude first track of cylinder 0 */ + s_hd = 1; + s_part += 63; sz_part -= 63; + } else { + s_hd = 0; + } + e_cyl = b_cyl + p_cyl - 1; + if (e_cyl >= tot_cyl) return FR_INVALID_PARAMETER; + + /* Set partition table */ + p[1] = s_hd; /* Start head */ + p[2] = (BYTE)((b_cyl >> 2) + 1); /* Start sector */ + p[3] = (BYTE)b_cyl; /* Start cylinder */ + p[4] = 0x06; /* System type (temporary setting) */ + p[5] = e_hd; /* End head */ + p[6] = (BYTE)((e_cyl >> 2) + 63); /* End sector */ + p[7] = (BYTE)e_cyl; /* End cylinder */ + st_dword(p + 8, s_part); /* Start sector in LBA */ + st_dword(p + 12, sz_part); /* Partition size */ + + /* Next partition */ + b_cyl += p_cyl; + } + st_word(p, 0xAA55); + + /* Write it to the MBR */ + return (disk_write(pdrv, buf, 0, 1) != RES_OK || disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) ? FR_DISK_ERR : FR_OK; +} + +#endif /* _MULTI_PARTITION */ +#endif /* _USE_MKFS && !_FS_READONLY */ + + + + +#if _USE_STRFUNC +/*-----------------------------------------------------------------------*/ +/* Get a string from the file */ +/*-----------------------------------------------------------------------*/ + +TCHAR* f_gets ( + TCHAR* buff, /* Pointer to the string buffer to read */ + int len, /* Size of string buffer (characters) */ + FIL* fp /* Pointer to the file object */ +) +{ + int n = 0; + TCHAR c, *p = buff; + BYTE s[2]; + UINT rc; + + + while (n < len - 1) { /* Read characters until buffer gets filled */ +#if _LFN_UNICODE +#if _STRF_ENCODE == 3 /* Read a character in UTF-8 */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = s[0]; + if (c >= 0x80) { + if (c < 0xC0) continue; /* Skip stray trailer */ + if (c < 0xE0) { /* Two-byte sequence */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = (c & 0x1F) << 6 | (s[0] & 0x3F); + if (c < 0x80) c = '?'; + } else { + if (c < 0xF0) { /* Three-byte sequence */ + f_read(fp, s, 2, &rc); + if (rc != 2) break; + c = c << 12 | (s[0] & 0x3F) << 6 | (s[1] & 0x3F); + if (c < 0x800) c = '?'; + } else { /* Reject four-byte sequence */ + c = '?'; + } + } + } +#elif _STRF_ENCODE == 2 /* Read a character in UTF-16BE */ + f_read(fp, s, 2, &rc); + if (rc != 2) break; + c = s[1] + (s[0] << 8); +#elif _STRF_ENCODE == 1 /* Read a character in UTF-16LE */ + f_read(fp, s, 2, &rc); + if (rc != 2) break; + c = s[0] + (s[1] << 8); +#else /* Read a character in ANSI/OEM */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = s[0]; + if (IsDBCS1(c)) { + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = (c << 8) + s[0]; + } + c = ff_convert(c, 1); /* OEM -> Unicode */ + if (!c) c = '?'; +#endif +#else /* Read a character without conversion */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = s[0]; +#endif + if (_USE_STRFUNC == 2 && c == '\r') continue; /* Strip '\r' */ + *p++ = c; + n++; + if (c == '\n') break; /* Break on EOL */ + } + *p = 0; + return n ? buff : 0; /* When no data read (eof or error), return with error. */ +} + + + + +#if !_FS_READONLY +#include +/*-----------------------------------------------------------------------*/ +/* Put a character to the file */ +/*-----------------------------------------------------------------------*/ + +typedef struct { + FIL *fp; /* Ptr to the writing file */ + int idx, nchr; /* Write index of buf[] (-1:error), number of chars written */ + BYTE buf[64]; /* Write buffer */ +} putbuff; + + +static +void putc_bfd ( /* Buffered write with code conversion */ + putbuff* pb, + TCHAR c +) +{ + UINT bw; + int i; + + + if (_USE_STRFUNC == 2 && c == '\n') { /* LF -> CRLF conversion */ + putc_bfd(pb, '\r'); + } + + i = pb->idx; /* Write index of pb->buf[] */ + if (i < 0) return; + +#if _LFN_UNICODE +#if _STRF_ENCODE == 3 /* Write a character in UTF-8 */ + if (c < 0x80) { /* 7-bit */ + pb->buf[i++] = (BYTE)c; + } else { + if (c < 0x800) { /* 11-bit */ + pb->buf[i++] = (BYTE)(0xC0 | c >> 6); + } else { /* 16-bit */ + pb->buf[i++] = (BYTE)(0xE0 | c >> 12); + pb->buf[i++] = (BYTE)(0x80 | (c >> 6 & 0x3F)); + } + pb->buf[i++] = (BYTE)(0x80 | (c & 0x3F)); + } +#elif _STRF_ENCODE == 2 /* Write a character in UTF-16BE */ + pb->buf[i++] = (BYTE)(c >> 8); + pb->buf[i++] = (BYTE)c; +#elif _STRF_ENCODE == 1 /* Write a character in UTF-16LE */ + pb->buf[i++] = (BYTE)c; + pb->buf[i++] = (BYTE)(c >> 8); +#else /* Write a character in ANSI/OEM */ + c = ff_convert(c, 0); /* Unicode -> OEM */ + if (!c) c = '?'; + if (c >= 0x100) + pb->buf[i++] = (BYTE)(c >> 8); + pb->buf[i++] = (BYTE)c; +#endif +#else /* Write a character without conversion */ + pb->buf[i++] = (BYTE)c; +#endif + + if (i >= (int)(sizeof pb->buf) - 3) { /* Write buffered characters to the file */ + f_write(pb->fp, pb->buf, (UINT)i, &bw); + i = (bw == (UINT)i) ? 0 : -1; + } + pb->idx = i; + pb->nchr++; +} + + +static +int putc_flush ( /* Flush left characters in the buffer */ + putbuff* pb +) +{ + UINT nw; + + if ( pb->idx >= 0 /* Flush buffered characters to the file */ + && f_write(pb->fp, pb->buf, (UINT)pb->idx, &nw) == FR_OK + && (UINT)pb->idx == nw) return pb->nchr; + return EOF; +} + + +static +void putc_init ( /* Initialize write buffer */ + putbuff* pb, + FIL* fp +) +{ + pb->fp = fp; + pb->nchr = pb->idx = 0; +} + + + +int f_putc ( + TCHAR c, /* A character to be output */ + FIL* fp /* Pointer to the file object */ +) +{ + putbuff pb; + + + putc_init(&pb, fp); + putc_bfd(&pb, c); /* Put the character */ + return putc_flush(&pb); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a string to the file */ +/*-----------------------------------------------------------------------*/ + +int f_puts ( + const TCHAR* str, /* Pointer to the string to be output */ + FIL* fp /* Pointer to the file object */ +) +{ + putbuff pb; + + + putc_init(&pb, fp); + while (*str) putc_bfd(&pb, *str++); /* Put the string */ + return putc_flush(&pb); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a formatted string to the file */ +/*-----------------------------------------------------------------------*/ + +int f_printf ( + FIL* fp, /* Pointer to the file object */ + const TCHAR* fmt, /* Pointer to the format string */ + ... /* Optional arguments... */ +) +{ + va_list arp; + putbuff pb; + BYTE f, r; + UINT i, j, w; + DWORD v; + TCHAR c, d, str[32], *p; + + + putc_init(&pb, fp); + + va_start(arp, fmt); + + for (;;) { + c = *fmt++; + if (c == 0) break; /* End of string */ + if (c != '%') { /* Non escape character */ + putc_bfd(&pb, c); + continue; + } + w = f = 0; + c = *fmt++; + if (c == '0') { /* Flag: '0' padding */ + f = 1; c = *fmt++; + } else { + if (c == '-') { /* Flag: left justified */ + f = 2; c = *fmt++; + } + } + while (IsDigit(c)) { /* Precision */ + w = w * 10 + c - '0'; + c = *fmt++; + } + if (c == 'l' || c == 'L') { /* Prefix: Size is long int */ + f |= 4; c = *fmt++; + } + if (!c) break; + d = c; + if (IsLower(d)) d -= 0x20; + switch (d) { /* Type is... */ + case 'S' : /* String */ + p = va_arg(arp, TCHAR*); + for (j = 0; p[j]; j++) ; + if (!(f & 2)) { + while (j++ < w) putc_bfd(&pb, ' '); + } + while (*p) putc_bfd(&pb, *p++); + while (j++ < w) putc_bfd(&pb, ' '); + continue; + case 'C' : /* Character */ + putc_bfd(&pb, (TCHAR)va_arg(arp, int)); continue; + case 'B' : /* Binary */ + r = 2; break; + case 'O' : /* Octal */ + r = 8; break; + case 'D' : /* Signed decimal */ + case 'U' : /* Unsigned decimal */ + r = 10; break; + case 'X' : /* Hexdecimal */ + r = 16; break; + default: /* Unknown type (pass-through) */ + putc_bfd(&pb, c); continue; + } + + /* Get an argument and put it in numeral */ + v = (f & 4) ? (DWORD)va_arg(arp, long) : ((d == 'D') ? (DWORD)(long)va_arg(arp, int) : (DWORD)va_arg(arp, unsigned int)); + if (d == 'D' && (v & 0x80000000)) { + v = 0 - v; + f |= 8; + } + i = 0; + do { + d = (TCHAR)(v % r); v /= r; + if (d > 9) d += (c == 'x') ? 0x27 : 0x07; + str[i++] = d + '0'; + } while (v && i < sizeof str / sizeof str[0]); + if (f & 8) str[i++] = '-'; + j = i; d = (f & 1) ? '0' : ' '; + while (!(f & 2) && j++ < w) putc_bfd(&pb, d); + do putc_bfd(&pb, str[--i]); while (i); + while (j++ < w) putc_bfd(&pb, d); + } + + va_end(arp); + + return putc_flush(&pb); +} + +#endif /* !_FS_READONLY */ +#endif /* _USE_STRFUNC */ diff --git a/components/fatfs/src/ff.h b/components/fatfs/src/ff.h new file mode 100644 index 0000000000..981a88634f --- /dev/null +++ b/components/fatfs/src/ff.h @@ -0,0 +1,366 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT file system module R0.12b / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2016, ChaN, all right reserved. +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: + +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +/----------------------------------------------------------------------------*/ + + +#ifndef _FATFS +#define _FATFS 68020 /* Revision ID */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "integer.h" /* Basic integer types */ +#include "ffconf.h" /* FatFs configuration options */ + +#if _FATFS != _FFCONF +#error Wrong configuration file (ffconf.h). +#endif + + + +/* Definitions of volume management */ + +#if _MULTI_PARTITION /* Multiple partition configuration */ +typedef struct { + BYTE pd; /* Physical drive number */ + BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */ +} PARTITION; +extern PARTITION VolToPart[]; /* Volume - Partition resolution table */ +#define LD2PD(vol) (VolToPart[vol].pd) /* Get physical drive number */ +#define LD2PT(vol) (VolToPart[vol].pt) /* Get partition index */ + +#else /* Single partition configuration */ +#define LD2PD(vol) (BYTE)(vol) /* Each logical drive is bound to the same physical drive number */ +#define LD2PT(vol) 0 /* Find first valid partition or in SFD */ + +#endif + + + +/* Type of path name strings on FatFs API */ + +#if _LFN_UNICODE /* Unicode (UTF-16) string */ +#if _USE_LFN == 0 +#error _LFN_UNICODE must be 0 at non-LFN cfg. +#endif +#ifndef _INC_TCHAR +typedef WCHAR TCHAR; +#define _T(x) L ## x +#define _TEXT(x) L ## x +#endif +#else /* ANSI/OEM string */ +#ifndef _INC_TCHAR +typedef char TCHAR; +#define _T(x) x +#define _TEXT(x) x +#endif +#endif + + + +/* Type of file size variables */ + +#if _FS_EXFAT +#if _USE_LFN == 0 +#error LFN must be enabled when enable exFAT +#endif +typedef QWORD FSIZE_t; +#else +typedef DWORD FSIZE_t; +#endif + + + +/* File system object structure (FATFS) */ + +typedef struct { + BYTE fs_type; /* File system type (0:N/A) */ + BYTE drv; /* Physical drive number */ + BYTE n_fats; /* Number of FATs (1 or 2) */ + BYTE wflag; /* win[] flag (b0:dirty) */ + BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ + WORD id; /* File system mount ID */ + WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ + WORD csize; /* Cluster size [sectors] */ +#if _MAX_SS != _MIN_SS + WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ +#endif +#if _USE_LFN != 0 + WCHAR* lfnbuf; /* LFN working buffer */ +#endif +#if _FS_EXFAT + BYTE* dirbuf; /* Directory entry block scratchpad buffer */ +#endif +#if _FS_REENTRANT + _SYNC_t sobj; /* Identifier of sync object */ +#endif +#if !_FS_READONLY + DWORD last_clst; /* Last allocated cluster */ + DWORD free_clst; /* Number of free clusters */ +#endif +#if _FS_RPATH != 0 + DWORD cdir; /* Current directory start cluster (0:root) */ +#if _FS_EXFAT + DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */ + DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */ + DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */ +#endif +#endif + DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */ + DWORD fsize; /* Size of an FAT [sectors] */ + DWORD volbase; /* Volume base sector */ + DWORD fatbase; /* FAT base sector */ + DWORD dirbase; /* Root directory base sector/cluster */ + DWORD database; /* Data base sector */ + DWORD winsect; /* Current sector appearing in the win[] */ + BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ +} FATFS; + + + +/* Object ID and allocation information (_FDID) */ + +typedef struct { + FATFS* fs; /* Pointer to the owner file system object */ + WORD id; /* Owner file system mount ID */ + BYTE attr; /* Object attribute */ + BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous (no data on FAT), =3:got flagmented, b2:sub-directory stretched) */ + DWORD sclust; /* Object start cluster (0:no cluster or root directory) */ + FSIZE_t objsize; /* Object size (valid when sclust != 0) */ +#if _FS_EXFAT + DWORD n_cont; /* Size of coutiguous part, clusters - 1 (valid when stat == 3) */ + DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */ + DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */ + DWORD c_ofs; /* Offset in the containing directory (valid when sclust != 0) */ +#endif +#if _FS_LOCK != 0 + UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ +#endif +} _FDID; + + + +/* File object structure (FIL) */ + +typedef struct { + _FDID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */ + BYTE flag; /* File status flags */ + BYTE err; /* Abort flag (error code) */ + FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */ + DWORD clust; /* Current cluster of fpter (invalid when fprt is 0) */ + DWORD sect; /* Sector number appearing in buf[] (0:invalid) */ +#if !_FS_READONLY + DWORD dir_sect; /* Sector number containing the directory entry */ + BYTE* dir_ptr; /* Pointer to the directory entry in the win[] */ +#endif +#if _USE_FASTSEEK + DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ +#endif +#if !_FS_TINY + BYTE buf[_MAX_SS]; /* File private data read/write window */ +#endif +} FIL; + + + +/* Directory object structure (DIR) */ + +typedef struct { + _FDID obj; /* Object identifier */ + DWORD dptr; /* Current read/write offset */ + DWORD clust; /* Current cluster */ + DWORD sect; /* Current sector */ + BYTE* dir; /* Pointer to the directory item in the win[] */ + BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */ +#if _USE_LFN != 0 + DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ +#endif +#if _USE_FIND + const TCHAR* pat; /* Pointer to the name matching pattern */ +#endif +} DIR; + + + +/* File information structure (FILINFO) */ + +typedef struct { + FSIZE_t fsize; /* File size */ + WORD fdate; /* Modified date */ + WORD ftime; /* Modified time */ + BYTE fattrib; /* File attribute */ +#if _USE_LFN != 0 + TCHAR altname[13]; /* Altenative file name */ + TCHAR fname[_MAX_LFN + 1]; /* Primary file name */ +#else + TCHAR fname[13]; /* File name */ +#endif +} FILINFO; + + + +/* File function return code (FRESULT) */ + +typedef enum { + FR_OK = 0, /* (0) Succeeded */ + FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ + FR_INT_ERR, /* (2) Assertion failed */ + FR_NOT_READY, /* (3) The physical drive cannot work */ + FR_NO_FILE, /* (4) Could not find the file */ + FR_NO_PATH, /* (5) Could not find the path */ + FR_INVALID_NAME, /* (6) The path name format is invalid */ + FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ + FR_EXIST, /* (8) Access denied due to prohibited access */ + FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ + FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ + FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ + FR_NOT_ENABLED, /* (12) The volume has no work area */ + FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ + FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ + FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ + FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ + FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ + FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_LOCK */ + FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ +} FRESULT; + + + +/*--------------------------------------------------------------*/ +/* FatFs module application interface */ + +FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ +FRESULT f_close (FIL* fp); /* Close an open file object */ +FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ +FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ +FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ +FRESULT f_truncate (FIL* fp); /* Truncate the file */ +FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ +FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_closedir (DIR* dp); /* Close an open directory */ +FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */ +FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */ +FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ +FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ +FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ +FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */ +FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ +FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */ +FRESULT f_chdir (const TCHAR* path); /* Change current directory */ +FRESULT f_chdrive (const TCHAR* path); /* Change current drive */ +FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */ +FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ +FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */ +FRESULT f_setlabel (const TCHAR* label); /* Set volume label */ +FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ +FRESULT f_expand (FIL* fp, FSIZE_t szf, BYTE opt); /* Allocate a contiguous block to the file */ +FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ +FRESULT f_mkfs (const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */ +FRESULT f_fdisk (BYTE pdrv, const DWORD* szt, void* work); /* Divide a physical drive into some partitions */ +int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */ +int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */ +int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */ +TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */ + +#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) +#define f_error(fp) ((fp)->err) +#define f_tell(fp) ((fp)->fptr) +#define f_size(fp) ((fp)->obj.objsize) +#define f_rewind(fp) f_lseek((fp), 0) +#define f_rewinddir(dp) f_readdir((dp), 0) + +#ifndef EOF +#define EOF (-1) +#endif + + + + +/*--------------------------------------------------------------*/ +/* Additional user defined functions */ + +/* RTC function */ +#if !_FS_READONLY && !_FS_NORTC +DWORD get_fattime (void); +#endif + +/* Unicode support functions */ +#if _USE_LFN != 0 /* Unicode - OEM code conversion */ +WCHAR ff_convert (WCHAR chr, UINT dir); /* OEM-Unicode bidirectional conversion */ +WCHAR ff_wtoupper (WCHAR chr); /* Unicode upper-case conversion */ +#if _USE_LFN == 3 /* Memory functions */ +void* ff_memalloc (UINT msize); /* Allocate memory block */ +void ff_memfree (void* mblock); /* Free memory block */ +#endif +#endif + +/* Sync functions */ +#if _FS_REENTRANT +int ff_cre_syncobj (BYTE vol, _SYNC_t* sobj); /* Create a sync object */ +int ff_req_grant (_SYNC_t sobj); /* Lock sync object */ +void ff_rel_grant (_SYNC_t sobj); /* Unlock sync object */ +int ff_del_syncobj (_SYNC_t sobj); /* Delete a sync object */ +#endif + + + + +/*--------------------------------------------------------------*/ +/* Flags and offset address */ + + +/* File access mode and open method flags (3rd argument of f_open) */ +#define FA_READ 0x01 +#define FA_WRITE 0x02 +#define FA_OPEN_EXISTING 0x00 +#define FA_CREATE_NEW 0x04 +#define FA_CREATE_ALWAYS 0x08 +#define FA_OPEN_ALWAYS 0x10 +#define FA_OPEN_APPEND 0x30 + +/* Fast seek controls (2nd argument of f_lseek) */ +#define CREATE_LINKMAP ((FSIZE_t)0 - 1) + +/* Format options (2nd argument of f_mkfs) */ +#define FM_FAT 0x01 +#define FM_FAT32 0x02 +#define FM_EXFAT 0x04 +#define FM_ANY 0x07 +#define FM_SFD 0x08 + +/* Filesystem type (FATFS.fs_type) */ +#define FS_FAT12 1 +#define FS_FAT16 2 +#define FS_FAT32 3 +#define FS_EXFAT 4 + +/* File attribute bits for directory entry (FILINFO.fattrib) */ +#define AM_RDO 0x01 /* Read only */ +#define AM_HID 0x02 /* Hidden */ +#define AM_SYS 0x04 /* System */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ + + +#ifdef __cplusplus +} +#endif + +#endif /* _FATFS */ diff --git a/components/fatfs/src/ffconf.h b/components/fatfs/src/ffconf.h new file mode 100644 index 0000000000..c43321f626 --- /dev/null +++ b/components/fatfs/src/ffconf.h @@ -0,0 +1,267 @@ +/*---------------------------------------------------------------------------/ +/ FatFs - FAT file system module configuration file +/---------------------------------------------------------------------------*/ + +#define _FFCONF 68020 /* Revision ID */ + +/*---------------------------------------------------------------------------/ +/ Function Configurations +/---------------------------------------------------------------------------*/ + +#define _FS_READONLY 0 +/* This option switches read-only configuration. (0:Read/Write or 1:Read-only) +/ Read-only configuration removes writing API functions, f_write(), f_sync(), +/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree() +/ and optional writing functions as well. */ + + +#define _FS_MINIMIZE 0 +/* This option defines minimization level to remove some basic API functions. +/ +/ 0: All basic functions are enabled. +/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename() +/ are removed. +/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. +/ 3: f_lseek() function is removed in addition to 2. */ + + +#define _USE_STRFUNC 0 +/* This option switches string functions, f_gets(), f_putc(), f_puts() and +/ f_printf(). +/ +/ 0: Disable string functions. +/ 1: Enable without LF-CRLF conversion. +/ 2: Enable with LF-CRLF conversion. */ + + +#define _USE_FIND 0 +/* This option switches filtered directory read functions, f_findfirst() and +/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ + + +#define _USE_MKFS 0 +/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */ + + +#define _USE_FASTSEEK 0 +/* This option switches fast seek function. (0:Disable or 1:Enable) */ + + +#define _USE_EXPAND 0 +/* This option switches f_expand function. (0:Disable or 1:Enable) */ + + +#define _USE_CHMOD 0 +/* This option switches attribute manipulation functions, f_chmod() and f_utime(). +/ (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */ + + +#define _USE_LABEL 0 +/* This option switches volume label functions, f_getlabel() and f_setlabel(). +/ (0:Disable or 1:Enable) */ + + +#define _USE_FORWARD 0 +/* This option switches f_forward() function. (0:Disable or 1:Enable) */ + + +/*---------------------------------------------------------------------------/ +/ Locale and Namespace Configurations +/---------------------------------------------------------------------------*/ + +#define _CODE_PAGE 932 +/* This option specifies the OEM code page to be used on the target system. +/ Incorrect setting of the code page can cause a file open failure. +/ +/ 1 - ASCII (No extended character. Non-LFN cfg. only) +/ 437 - U.S. +/ 720 - Arabic +/ 737 - Greek +/ 771 - KBL +/ 775 - Baltic +/ 850 - Latin 1 +/ 852 - Latin 2 +/ 855 - Cyrillic +/ 857 - Turkish +/ 860 - Portuguese +/ 861 - Icelandic +/ 862 - Hebrew +/ 863 - Canadian French +/ 864 - Arabic +/ 865 - Nordic +/ 866 - Russian +/ 869 - Greek 2 +/ 932 - Japanese (DBCS) +/ 936 - Simplified Chinese (DBCS) +/ 949 - Korean (DBCS) +/ 950 - Traditional Chinese (DBCS) +*/ + + +#define _USE_LFN 0 +#define _MAX_LFN 255 +/* The _USE_LFN switches the support of long file name (LFN). +/ +/ 0: Disable support of LFN. _MAX_LFN has no effect. +/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. +/ 2: Enable LFN with dynamic working buffer on the STACK. +/ 3: Enable LFN with dynamic working buffer on the HEAP. +/ +/ To enable the LFN, Unicode handling functions (option/unicode.c) must be added +/ to the project. The working buffer occupies (_MAX_LFN + 1) * 2 bytes and +/ additional 608 bytes at exFAT enabled. _MAX_LFN can be in range from 12 to 255. +/ It should be set 255 to support full featured LFN operations. +/ When use stack for the working buffer, take care on stack overflow. When use heap +/ memory for the working buffer, memory management functions, ff_memalloc() and +/ ff_memfree(), must be added to the project. */ + + +#define _LFN_UNICODE 0 +/* This option switches character encoding on the API. (0:ANSI/OEM or 1:UTF-16) +/ To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1. +/ This option also affects behavior of string I/O functions. */ + + +#define _STRF_ENCODE 3 +/* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to +/ be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf(). +/ +/ 0: ANSI/OEM +/ 1: UTF-16LE +/ 2: UTF-16BE +/ 3: UTF-8 +/ +/ This option has no effect when _LFN_UNICODE == 0. */ + + +#define _FS_RPATH 0 +/* This option configures support of relative path. +/ +/ 0: Disable relative path and remove related functions. +/ 1: Enable relative path. f_chdir() and f_chdrive() are available. +/ 2: f_getcwd() function is available in addition to 1. +*/ + + +/*---------------------------------------------------------------------------/ +/ Drive/Volume Configurations +/---------------------------------------------------------------------------*/ + +#define _VOLUMES 1 +/* Number of volumes (logical drives) to be used. */ + + +#define _STR_VOLUME_ID 0 +#define _VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" +/* _STR_VOLUME_ID switches string support of volume ID. +/ When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive +/ number in the path name. _VOLUME_STRS defines the drive ID strings for each +/ logical drives. Number of items must be equal to _VOLUMES. Valid characters for +/ the drive ID strings are: A-Z and 0-9. */ + + +#define _MULTI_PARTITION 0 +/* This option switches support of multi-partition on a physical drive. +/ By default (0), each logical drive number is bound to the same physical drive +/ number and only an FAT volume found on the physical drive will be mounted. +/ When multi-partition is enabled (1), each logical drive number can be bound to +/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() +/ funciton will be available. */ + + +#define _MIN_SS 512 +#define _MAX_SS 512 +/* These options configure the range of sector size to be supported. (512, 1024, +/ 2048 or 4096) Always set both 512 for most systems, all type of memory cards and +/ harddisk. But a larger value may be required for on-board flash memory and some +/ type of optical media. When _MAX_SS is larger than _MIN_SS, FatFs is configured +/ to variable sector size and GET_SECTOR_SIZE command must be implemented to the +/ disk_ioctl() function. */ + + +#define _USE_TRIM 0 +/* This option switches support of ATA-TRIM. (0:Disable or 1:Enable) +/ To enable Trim function, also CTRL_TRIM command should be implemented to the +/ disk_ioctl() function. */ + + +#define _FS_NOFSINFO 0 +/* If you need to know correct free space on the FAT32 volume, set bit 0 of this +/ option, and f_getfree() function at first time after volume mount will force +/ a full FAT scan. Bit 1 controls the use of last allocated cluster number. +/ +/ bit0=0: Use free cluster count in the FSINFO if available. +/ bit0=1: Do not trust free cluster count in the FSINFO. +/ bit1=0: Use last allocated cluster number in the FSINFO if available. +/ bit1=1: Do not trust last allocated cluster number in the FSINFO. +*/ + + + +/*---------------------------------------------------------------------------/ +/ System Configurations +/---------------------------------------------------------------------------*/ + +#define _FS_TINY 0 +/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) +/ At the tiny configuration, size of file object (FIL) is reduced _MAX_SS bytes. +/ Instead of private sector buffer eliminated from the file object, common sector +/ buffer in the file system object (FATFS) is used for the file data transfer. */ + + +#define _FS_EXFAT 0 +/* This option switches support of exFAT file system. (0:Disable or 1:Enable) +/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1) +/ Note that enabling exFAT discards C89 compatibility. */ + + +#define _FS_NORTC 0 +#define _NORTC_MON 1 +#define _NORTC_MDAY 1 +#define _NORTC_YEAR 2016 +/* The option _FS_NORTC switches timestamp functiton. If the system does not have +/ any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable +/ the timestamp function. All objects modified by FatFs will have a fixed timestamp +/ defined by _NORTC_MON, _NORTC_MDAY and _NORTC_YEAR in local time. +/ To enable timestamp function (_FS_NORTC = 0), get_fattime() function need to be +/ added to the project to get current time form real-time clock. _NORTC_MON, +/ _NORTC_MDAY and _NORTC_YEAR have no effect. +/ These options have no effect at read-only configuration (_FS_READONLY = 1). */ + + +#define _FS_LOCK 0 +/* The option _FS_LOCK switches file lock function to control duplicated file open +/ and illegal operation to open objects. This option must be 0 when _FS_READONLY +/ is 1. +/ +/ 0: Disable file lock function. To avoid volume corruption, application program +/ should avoid illegal open, remove and rename to the open objects. +/ >0: Enable file lock function. The value defines how many files/sub-directories +/ can be opened simultaneously under file lock control. Note that the file +/ lock control is independent of re-entrancy. */ + + +#define _FS_REENTRANT 0 +#define _FS_TIMEOUT 1000 +#define _SYNC_t HANDLE +/* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs +/ module itself. Note that regardless of this option, file access to different +/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs() +/ and f_fdisk() function, are always not re-entrant. Only file/directory access +/ to the same volume is under control of this function. +/ +/ 0: Disable re-entrancy. _FS_TIMEOUT and _SYNC_t have no effect. +/ 1: Enable re-entrancy. Also user provided synchronization handlers, +/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj() +/ function, must be added to the project. Samples are available in +/ option/syscall.c. +/ +/ The _FS_TIMEOUT defines timeout period in unit of time tick. +/ The _SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*, +/ SemaphoreHandle_t and etc.. A header file for O/S definitions needs to be +/ included somewhere in the scope of ff.h. */ + +/* #include // O/S definitions */ + + +/*--- End of configuration options ---*/ diff --git a/components/fatfs/src/integer.h b/components/fatfs/src/integer.h new file mode 100644 index 0000000000..4660ed6244 --- /dev/null +++ b/components/fatfs/src/integer.h @@ -0,0 +1,38 @@ +/*-------------------------------------------*/ +/* Integer type definitions for FatFs module */ +/*-------------------------------------------*/ + +#ifndef _FF_INTEGER +#define _FF_INTEGER + +#ifdef _WIN32 /* FatFs development platform */ + +#include +#include +typedef unsigned __int64 QWORD; + + +#else /* Embedded platform */ + +/* These types MUST be 16-bit or 32-bit */ +typedef int INT; +typedef unsigned int UINT; + +/* This type MUST be 8-bit */ +typedef unsigned char BYTE; + +/* These types MUST be 16-bit */ +typedef short SHORT; +typedef unsigned short WORD; +typedef unsigned short WCHAR; + +/* These types MUST be 32-bit */ +typedef long LONG; +typedef unsigned long DWORD; + +/* This type MUST be 64-bit (Remove this for C89 compatibility) */ +typedef unsigned long long QWORD; + +#endif + +#endif diff --git a/components/fatfs/src/option/syscall.c b/components/fatfs/src/option/syscall.c new file mode 100644 index 0000000000..c9d219b797 --- /dev/null +++ b/components/fatfs/src/option/syscall.c @@ -0,0 +1,151 @@ +/*------------------------------------------------------------------------*/ +/* Sample code of OS dependent controls for FatFs */ +/* (C)ChaN, 2014 */ +/*------------------------------------------------------------------------*/ + + +#include "../ff.h" + + +#if _FS_REENTRANT +/*------------------------------------------------------------------------*/ +/* Create a Synchronization Object +/*------------------------------------------------------------------------*/ +/* This function is called in f_mount() function to create a new +/ synchronization object, such as semaphore and mutex. When a 0 is returned, +/ the f_mount() function fails with FR_INT_ERR. +*/ + +int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */ + BYTE vol, /* Corresponding volume (logical drive number) */ + _SYNC_t *sobj /* Pointer to return the created sync object */ +) +{ + int ret; + + + *sobj = CreateMutex(NULL, FALSE, NULL); /* Win32 */ + ret = (int)(*sobj != INVALID_HANDLE_VALUE); + +// *sobj = SyncObjects[vol]; /* uITRON (give a static sync object) */ +// ret = 1; /* The initial value of the semaphore must be 1. */ + +// *sobj = OSMutexCreate(0, &err); /* uC/OS-II */ +// ret = (int)(err == OS_NO_ERR); + +// *sobj = xSemaphoreCreateMutex(); /* FreeRTOS */ +// ret = (int)(*sobj != NULL); + + return ret; +} + + + +/*------------------------------------------------------------------------*/ +/* Delete a Synchronization Object */ +/*------------------------------------------------------------------------*/ +/* This function is called in f_mount() function to delete a synchronization +/ object that created with ff_cre_syncobj() function. When a 0 is returned, +/ the f_mount() function fails with FR_INT_ERR. +*/ + +int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to any error */ + _SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ +) +{ + int ret; + + + ret = CloseHandle(sobj); /* Win32 */ + +// ret = 1; /* uITRON (nothing to do) */ + +// OSMutexDel(sobj, OS_DEL_ALWAYS, &err); /* uC/OS-II */ +// ret = (int)(err == OS_NO_ERR); + +// vSemaphoreDelete(sobj); /* FreeRTOS */ +// ret = 1; + + return ret; +} + + + +/*------------------------------------------------------------------------*/ +/* Request Grant to Access the Volume */ +/*------------------------------------------------------------------------*/ +/* This function is called on entering file functions to lock the volume. +/ When a 0 is returned, the file function fails with FR_TIMEOUT. +*/ + +int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */ + _SYNC_t sobj /* Sync object to wait */ +) +{ + int ret; + + ret = (int)(WaitForSingleObject(sobj, _FS_TIMEOUT) == WAIT_OBJECT_0); /* Win32 */ + +// ret = (int)(wai_sem(sobj) == E_OK); /* uITRON */ + +// OSMutexPend(sobj, _FS_TIMEOUT, &err)); /* uC/OS-II */ +// ret = (int)(err == OS_NO_ERR); + +// ret = (int)(xSemaphoreTake(sobj, _FS_TIMEOUT) == pdTRUE); /* FreeRTOS */ + + return ret; +} + + + +/*------------------------------------------------------------------------*/ +/* Release Grant to Access the Volume */ +/*------------------------------------------------------------------------*/ +/* This function is called on leaving file functions to unlock the volume. +*/ + +void ff_rel_grant ( + _SYNC_t sobj /* Sync object to be signaled */ +) +{ + ReleaseMutex(sobj); /* Win32 */ + +// sig_sem(sobj); /* uITRON */ + +// OSMutexPost(sobj); /* uC/OS-II */ + +// xSemaphoreGive(sobj); /* FreeRTOS */ +} + +#endif + + + + +#if _USE_LFN == 3 /* LFN with a working buffer on the heap */ +/*------------------------------------------------------------------------*/ +/* Allocate a memory block */ +/*------------------------------------------------------------------------*/ +/* If a NULL is returned, the file function fails with FR_NOT_ENOUGH_CORE. +*/ + +void* ff_memalloc ( /* Returns pointer to the allocated memory block */ + UINT msize /* Number of bytes to allocate */ +) +{ + return malloc(msize); /* Allocate a new memory block with POSIX API */ +} + + +/*------------------------------------------------------------------------*/ +/* Free a memory block */ +/*------------------------------------------------------------------------*/ + +void ff_memfree ( + void* mblock /* Pointer to the memory block to free */ +) +{ + free(mblock); /* Discard the memory block with POSIX API */ +} + +#endif diff --git a/components/fatfs/src/option/unicode.c b/components/fatfs/src/option/unicode.c new file mode 100644 index 0000000000..170e2e09e0 --- /dev/null +++ b/components/fatfs/src/option/unicode.c @@ -0,0 +1,17 @@ +#include "../ff.h" + +#if _USE_LFN != 0 + +#if _CODE_PAGE == 932 /* Japanese Shift_JIS */ +#include "cc932.c" +#elif _CODE_PAGE == 936 /* Simplified Chinese GBK */ +#include "cc936.c" +#elif _CODE_PAGE == 949 /* Korean */ +#include "cc949.c" +#elif _CODE_PAGE == 950 /* Traditional Chinese Big5 */ +#include "cc950.c" +#else /* Single Byte Character-Set */ +#include "ccsbcs.c" +#endif + +#endif From 9398d1b1cc2e28c8cb16011438c4a823960dcbb8 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 29 Dec 2016 17:33:38 +0800 Subject: [PATCH 094/167] fatfs: add pluggable diskio layer, sdmmc implementation --- components/fatfs/src/diskio.c | 301 +++++++++----------------- components/fatfs/src/diskio.h | 22 +- components/fatfs/src/ffconf.h | 8 +- components/fatfs/src/option/syscall.c | 2 +- 4 files changed, 130 insertions(+), 203 deletions(-) diff --git a/components/fatfs/src/diskio.c b/components/fatfs/src/diskio.c index 25f5e53bc0..b00e9d2ca5 100644 --- a/components/fatfs/src/diskio.c +++ b/components/fatfs/src/diskio.c @@ -7,219 +7,126 @@ /* storage control modules to the FatFs module with a defined API. */ /*-----------------------------------------------------------------------*/ +#include #include "diskio.h" /* FatFs lower layer API */ +#include "ffconf.h" +#include "ff.h" +#include "sdmmc_cmd.h" +#include "esp_log.h" +#include +#include -/* Definitions of physical drive number for each drive */ -#define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */ -#define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */ -#define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */ +static const char* TAG = "ff_diskio"; +static ff_diskio_impl_t s_impls[_VOLUMES] = { { 0 } }; +static sdmmc_card_t* s_cards[_VOLUMES] = { NULL }; +PARTITION VolToPart[] = { + {0, 1}, /* Logical drive 0 ==> Physical drive 0, 1st partition */ + {1, 0} /* Logical drive 1 ==> Physical drive 1, auto detection */ +}; -/*-----------------------------------------------------------------------*/ -/* Get Drive Status */ -/*-----------------------------------------------------------------------*/ - -DSTATUS disk_status ( - BYTE pdrv /* Physical drive nmuber to identify the drive */ -) +void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl) { - DSTATUS stat; - int result; - - switch (pdrv) { - case DEV_RAM : - result = RAM_disk_status(); - - // translate the reslut code here - - return stat; - - case DEV_MMC : - result = MMC_disk_status(); - - // translate the reslut code here - - return stat; - - case DEV_USB : - result = USB_disk_status(); - - // translate the reslut code here - - return stat; - } - return STA_NOINIT; + assert(pdrv < _VOLUMES); + memcpy(&s_impls[pdrv], discio_impl, sizeof(ff_diskio_impl_t)); } - - -/*-----------------------------------------------------------------------*/ -/* Inidialize a Drive */ -/*-----------------------------------------------------------------------*/ - -DSTATUS disk_initialize ( - BYTE pdrv /* Physical drive nmuber to identify the drive */ -) +DSTATUS ff_disk_initialize (BYTE pdrv) { - DSTATUS stat; - int result; - - switch (pdrv) { - case DEV_RAM : - result = RAM_disk_initialize(); - - // translate the reslut code here - - return stat; - - case DEV_MMC : - result = MMC_disk_initialize(); - - // translate the reslut code here - - return stat; - - case DEV_USB : - result = USB_disk_initialize(); - - // translate the reslut code here - - return stat; - } - return STA_NOINIT; + return s_impls[pdrv].init(pdrv); +} +DSTATUS ff_disk_status (BYTE pdrv) +{ + return s_impls[pdrv].status(pdrv); +} +DRESULT ff_disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count) +{ + return s_impls[pdrv].read(pdrv, buff, sector, count); +} +DRESULT ff_disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) +{ + return s_impls[pdrv].write(pdrv, buff, sector, count); +} +DRESULT ff_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff) +{ + return s_impls[pdrv].ioctl(pdrv, cmd, buff); } - - -/*-----------------------------------------------------------------------*/ -/* Read Sector(s) */ -/*-----------------------------------------------------------------------*/ - -DRESULT disk_read ( - BYTE pdrv, /* Physical drive nmuber to identify the drive */ - BYTE *buff, /* Data buffer to store read data */ - DWORD sector, /* Start sector in LBA */ - UINT count /* Number of sectors to read */ -) +DWORD get_fattime(void) { - DRESULT res; - int result; - - switch (pdrv) { - case DEV_RAM : - // translate the arguments here - - result = RAM_disk_read(buff, sector, count); - - // translate the reslut code here - - return res; - - case DEV_MMC : - // translate the arguments here - - result = MMC_disk_read(buff, sector, count); - - // translate the reslut code here - - return res; - - case DEV_USB : - // translate the arguments here - - result = USB_disk_read(buff, sector, count); - - // translate the reslut code here - - return res; - } - - return RES_PARERR; + time_t t = time(NULL); + struct tm *tmr = gmtime(&t); + return ((DWORD)(tmr->tm_year - 80) << 25) + | ((DWORD)(tmr->tm_mon + 1) << 21) + | ((DWORD)tmr->tm_mday << 16) + | (WORD)(tmr->tm_hour << 11) + | (WORD)(tmr->tm_min << 5) + | (WORD)(tmr->tm_sec >> 1); } - - -/*-----------------------------------------------------------------------*/ -/* Write Sector(s) */ -/*-----------------------------------------------------------------------*/ - -DRESULT disk_write ( - BYTE pdrv, /* Physical drive nmuber to identify the drive */ - const BYTE *buff, /* Data to be written */ - DWORD sector, /* Start sector in LBA */ - UINT count /* Number of sectors to write */ -) +DSTATUS ff_sdmmc_initialize (BYTE pdrv) { - DRESULT res; - int result; - - switch (pdrv) { - case DEV_RAM : - // translate the arguments here - - result = RAM_disk_write(buff, sector, count); - - // translate the reslut code here - - return res; - - case DEV_MMC : - // translate the arguments here - - result = MMC_disk_write(buff, sector, count); - - // translate the reslut code here - - return res; - - case DEV_USB : - // translate the arguments here - - result = USB_disk_write(buff, sector, count); - - // translate the reslut code here - - return res; - } - - return RES_PARERR; + return 0; } - - -/*-----------------------------------------------------------------------*/ -/* Miscellaneous Functions */ -/*-----------------------------------------------------------------------*/ - -DRESULT disk_ioctl ( - BYTE pdrv, /* Physical drive nmuber (0..) */ - BYTE cmd, /* Control code */ - void *buff /* Buffer to send/receive control data */ -) +DSTATUS ff_sdmmc_status (BYTE pdrv) { - DRESULT res; - int result; - - switch (pdrv) { - case DEV_RAM : - - // Process of the command for the RAM drive - - return res; - - case DEV_MMC : - - // Process of the command for the MMC/SD card - - return res; - - case DEV_USB : - - // Process of the command the USB drive - - return res; - } - - return RES_PARERR; + return 0; +} + +DRESULT ff_sdmmc_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count) +{ + sdmmc_card_t* card = s_cards[pdrv]; + assert(card); + esp_err_t err = sdmmc_read_blocks(card, buff, sector, count); + if (err != ESP_OK) { + ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err); + return RES_ERROR; + } + return RES_OK; +} + +DRESULT ff_sdmmc_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) +{ + sdmmc_card_t* card = s_cards[pdrv]; + assert(card); + esp_err_t err = sdmmc_write_blocks(card, buff, sector, count); + if (err != ESP_OK) { + ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err); + return RES_ERROR; + } + return RES_OK; +} + +DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff) +{ + sdmmc_card_t* card = s_cards[pdrv]; + assert(card); + switch(cmd) { + case CTRL_SYNC: + return RES_OK; + case GET_SECTOR_COUNT: + *((uint32_t*) buff) = card->csd.capacity; + return RES_OK; + case GET_SECTOR_SIZE: + *((uint32_t*) buff) = card->csd.sector_size; + return RES_OK; + case GET_BLOCK_SIZE: + return RES_ERROR; + } + return RES_ERROR; +} + +void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card) +{ + static const ff_diskio_impl_t sdmmc_impl = { + .init = &ff_sdmmc_initialize, + .status = &ff_sdmmc_status, + .read = &ff_sdmmc_read, + .write = &ff_sdmmc_write, + .ioctl = &ff_sdmmc_ioctl + }; + s_cards[pdrv] = card; + ff_diskio_register(pdrv, &sdmmc_impl); } diff --git a/components/fatfs/src/diskio.h b/components/fatfs/src/diskio.h index 03e8b7c560..921ef9836a 100644 --- a/components/fatfs/src/diskio.h +++ b/components/fatfs/src/diskio.h @@ -10,7 +10,8 @@ extern "C" { #endif #include "integer.h" - +#include "sdmmc_cmd.h" +#include "driver/sdmmc_host.h" /* Status of Disk Functions */ typedef BYTE DSTATUS; @@ -29,12 +30,31 @@ typedef enum { /* Prototypes for disk control functions */ +/* Redefine names of disk IO functions to prevent name collisions */ +#define disk_initialize ff_disk_initialize +#define disk_status ff_disk_status +#define disk_read ff_disk_read +#define disk_write ff_disk_write +#define disk_ioctl ff_disk_ioctl + + DSTATUS disk_initialize (BYTE pdrv); DSTATUS disk_status (BYTE pdrv); DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); +typedef struct { + DSTATUS (*init) (BYTE pdrv); + DSTATUS (*status) (BYTE pdrv); + DRESULT (*read) (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); + DRESULT (*write) (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); + DRESULT (*ioctl) (BYTE pdrv, BYTE cmd, void* buff); +} ff_diskio_impl_t; + +void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl); + +void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card); /* Disk Status Bits (DSTATUS) */ diff --git a/components/fatfs/src/ffconf.h b/components/fatfs/src/ffconf.h index c43321f626..b3a1bf5747 100644 --- a/components/fatfs/src/ffconf.h +++ b/components/fatfs/src/ffconf.h @@ -39,7 +39,7 @@ / f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ -#define _USE_MKFS 0 +#define _USE_MKFS 1 /* This option switches f_mkfs() function. (0:Disable or 1:Enable) */ @@ -69,7 +69,7 @@ / Locale and Namespace Configurations /---------------------------------------------------------------------------*/ -#define _CODE_PAGE 932 +#define _CODE_PAGE 1 /* This option specifies the OEM code page to be used on the target system. / Incorrect setting of the code page can cause a file open failure. / @@ -147,7 +147,7 @@ / Drive/Volume Configurations /---------------------------------------------------------------------------*/ -#define _VOLUMES 1 +#define _VOLUMES 2 /* Number of volumes (logical drives) to be used. */ @@ -160,7 +160,7 @@ / the drive ID strings are: A-Z and 0-9. */ -#define _MULTI_PARTITION 0 +#define _MULTI_PARTITION 1 /* This option switches support of multi-partition on a physical drive. / By default (0), each logical drive number is bound to the same physical drive / number and only an FAT volume found on the physical drive will be mounted. diff --git a/components/fatfs/src/option/syscall.c b/components/fatfs/src/option/syscall.c index c9d219b797..14140c5a73 100644 --- a/components/fatfs/src/option/syscall.c +++ b/components/fatfs/src/option/syscall.c @@ -9,7 +9,7 @@ #if _FS_REENTRANT /*------------------------------------------------------------------------*/ -/* Create a Synchronization Object +/* Create a Synchronization Object */ /*------------------------------------------------------------------------*/ /* This function is called in f_mount() function to create a new / synchronization object, such as semaphore and mutex. When a 0 is returned, From d4187769820db9fa91d85d7076bfe41f0583c381 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 3 Jan 2017 03:45:11 +0800 Subject: [PATCH 095/167] fatfs: rename DIR to FF_DIR to avoid conflict with dirent.h --- components/fatfs/src/ff.c | 1 + components/fatfs/src/ff.h | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/components/fatfs/src/ff.c b/components/fatfs/src/ff.c index ffc5240970..0d7d7366bf 100644 --- a/components/fatfs/src/ff.c +++ b/components/fatfs/src/ff.c @@ -18,6 +18,7 @@ /----------------------------------------------------------------------------*/ +#define FF_DEFINE_DIR #include "ff.h" /* Declarations of FatFs API */ #include "diskio.h" /* Declarations of device I/O functions */ diff --git a/components/fatfs/src/ff.h b/components/fatfs/src/ff.h index 981a88634f..18bc85b1c7 100644 --- a/components/fatfs/src/ff.h +++ b/components/fatfs/src/ff.h @@ -32,6 +32,9 @@ extern "C" { #error Wrong configuration file (ffconf.h). #endif +#ifdef FF_DEFINE_DIR +#define FF_DIR DIR +#endif /* Definitions of volume management */ @@ -179,7 +182,7 @@ typedef struct { -/* Directory object structure (DIR) */ +/* Directory object structure (FF_DIR) */ typedef struct { _FDID obj; /* Object identifier */ @@ -194,7 +197,7 @@ typedef struct { #if _USE_FIND const TCHAR* pat; /* Pointer to the name matching pattern */ #endif -} DIR; +} FF_DIR; @@ -252,11 +255,11 @@ FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data t FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ FRESULT f_truncate (FIL* fp); /* Truncate the file */ FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ -FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */ -FRESULT f_closedir (DIR* dp); /* Close an open directory */ -FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */ -FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ -FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */ +FRESULT f_opendir (FF_DIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_closedir (FF_DIR* dp); /* Close an open directory */ +FRESULT f_readdir (FF_DIR* dp, FILINFO* fno); /* Read a directory item */ +FRESULT f_findfirst (FF_DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findnext (FF_DIR* dp, FILINFO* fno); /* Find next file */ FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ From 44ce833d76146415537136d4d7ddf5f0f2df2638 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Jan 2017 05:54:04 +0800 Subject: [PATCH 096/167] fatfs: add vfs support --- components/fatfs/component.mk | 2 +- components/fatfs/src/diskio.c | 8 +- components/fatfs/src/diskio.h | 30 +- components/fatfs/src/esp_vfs_fat.h | 107 +++++ components/fatfs/src/ffconf.h | 8 +- components/fatfs/src/option/syscall.c | 55 +-- components/fatfs/src/vfs_fat.c | 538 +++++++++++++++++++++++++ components/fatfs/src/vfs_fat_sdmmc.c | 126 ++++++ components/fatfs/test/component.mk | 1 + components/fatfs/test/test_fatfs.c | 552 ++++++++++++++++++++++++++ components/newlib/test/test_newlib.c | 29 ++ components/sdmmc/test/test_sd.c | 14 + 12 files changed, 1408 insertions(+), 62 deletions(-) create mode 100644 components/fatfs/src/esp_vfs_fat.h create mode 100644 components/fatfs/src/vfs_fat.c create mode 100644 components/fatfs/src/vfs_fat_sdmmc.c create mode 100644 components/fatfs/test/component.mk create mode 100644 components/fatfs/test/test_fatfs.c diff --git a/components/fatfs/component.mk b/components/fatfs/component.mk index f2094e7ab9..591e080c62 100644 --- a/components/fatfs/component.mk +++ b/components/fatfs/component.mk @@ -1,2 +1,2 @@ COMPONENT_ADD_INCLUDEDIRS := src -COMPONENT_SRCDIRS := src src/option +COMPONENT_SRCDIRS := src/option src diff --git a/components/fatfs/src/diskio.c b/components/fatfs/src/diskio.c index b00e9d2ca5..004cfc5bea 100644 --- a/components/fatfs/src/diskio.c +++ b/components/fatfs/src/diskio.c @@ -1,5 +1,6 @@ /*-----------------------------------------------------------------------*/ /* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */ +/* ESP-IDF port Copyright 2016 Espressif Systems (Shanghai) PTE LTD */ /*-----------------------------------------------------------------------*/ /* If a working storage control module is available, it should be */ /* attached to the FatFs via a glue function rather than modifying it. */ @@ -56,7 +57,8 @@ DWORD get_fattime(void) { time_t t = time(NULL); struct tm *tmr = gmtime(&t); - return ((DWORD)(tmr->tm_year - 80) << 25) + int year = tmr->tm_year < 80 ? 0 : tmr->tm_year - 80; + return ((DWORD)(year) << 25) | ((DWORD)(tmr->tm_mon + 1) << 21) | ((DWORD)tmr->tm_mday << 16) | (WORD)(tmr->tm_hour << 11) @@ -78,7 +80,7 @@ DRESULT ff_sdmmc_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { sdmmc_card_t* card = s_cards[pdrv]; assert(card); - esp_err_t err = sdmmc_read_blocks(card, buff, sector, count); + esp_err_t err = sdmmc_read_sectors(card, buff, sector, count); if (err != ESP_OK) { ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err); return RES_ERROR; @@ -90,7 +92,7 @@ DRESULT ff_sdmmc_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { sdmmc_card_t* card = s_cards[pdrv]; assert(card); - esp_err_t err = sdmmc_write_blocks(card, buff, sector, count); + esp_err_t err = sdmmc_write_sectors(card, buff, sector, count); if (err != ESP_OK) { ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err); return RES_ERROR; diff --git a/components/fatfs/src/diskio.h b/components/fatfs/src/diskio.h index 921ef9836a..7c224809f9 100644 --- a/components/fatfs/src/diskio.h +++ b/components/fatfs/src/diskio.h @@ -44,16 +44,36 @@ DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); +/** + * Structure of pointers to disk IO driver functions. + * + * See FatFs documentation for details about these functions + */ typedef struct { - DSTATUS (*init) (BYTE pdrv); - DSTATUS (*status) (BYTE pdrv); - DRESULT (*read) (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); - DRESULT (*write) (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); - DRESULT (*ioctl) (BYTE pdrv, BYTE cmd, void* buff); + DSTATUS (*init) (BYTE pdrv); /*!< disk initialization function */ + DSTATUS (*status) (BYTE pdrv); /*!< disk status check function */ + DRESULT (*read) (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); /*!< sector read function */ + DRESULT (*write) (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); /*!< sector write function */ + DRESULT (*ioctl) (BYTE pdrv, BYTE cmd, void* buff); /*!< function to get info about disk and do some misc operations */ } ff_diskio_impl_t; +/** + * Register diskio driver for given drive number. + * + * When FATFS library calls one of disk_xxx functions for driver number pdrv, + * corresponding function in discio_impl for given pdrv will be called. + * + * @param pdrv drive number + * @param discio_impl pointer to ff_diskio_impl_t structure with diskio functions + */ void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl); +/** + * Register SD/MMC diskio driver + * + * @param pdrv drive number + * @param card pointer to sdmmc_card_t structure describing a card; card should be initialized before calling f_mount. + */ void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card); /* Disk Status Bits (DSTATUS) */ diff --git a/components/fatfs/src/esp_vfs_fat.h b/components/fatfs/src/esp_vfs_fat.h new file mode 100644 index 0000000000..087d6cf77e --- /dev/null +++ b/components/fatfs/src/esp_vfs_fat.h @@ -0,0 +1,107 @@ +// 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. + +#pragma once +#include +#include "esp_err.h" +#include "driver/gpio.h" +#include "driver/sdmmc_types.h" +#include "driver/sdmmc_host.h" +#include "ff.h" + +/** + * @brief Register FATFS with VFS component + * + * This function registers given FAT drive in VFS, at the specified base path. + * If only one drive is used, fat_drive argument can be an empty string. + * Refer to FATFS library documentation on how to specify FAT drive. + * This function also allocates FATFS structure which should be used for f_mount + * call. + * + * @note This function doesn't mount the drive into FATFS, it just connects + * POSIX and C standard library IO function with FATFS. You need to mount + * desired drive into FATFS separately. + * + * @param base_path path prefix where FATFS should be registered + * @param fat_drive FATFS drive specification; if only one drive is used, can be an empty string + * @param max_files maximum number of files which can be open at the same time + * @param[out] out_fs pointer to FATFS structure which can be used for FATFS f_mount call is returned via this argument. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if esp_vfs_fat_register was already called + * - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered + */ +esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, + size_t max_files, FATFS** out_fs); + +/** + * @brief Un-register FATFS from VFS + * + * @note FATFS structure returned by esp_vfs_fat_register is destroyed after + * this call. Make sure to call f_mount function to unmount it before + * calling esp_vfs_fat_unregister. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS + */ +esp_err_t esp_vfs_fat_unregister(); + +/** + * @brief Configuration arguments for esp_vfs_fat_sdmmc_mount function + */ +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 + int max_files; ///< Max number of open files +} esp_vfs_fat_sdmmc_mount_config_t; + +/** + * @brief Convenience function to get FAT filesystem on SD card registered in VFS + * + * This is an all-in-one function which does the following: + * - initializes SD/MMC peripheral with configuration in host_config + * - initializes SD/MMC card with configuration in slot_config + * - mounts FAT partition on SD/MMC card using FATFS library, with configuration in mount_config + * - registers FATFS library with VFS, with prefix given by base_prefix variable + * + * This function is intended to make example code more compact. + * For real world applications, developers should implement the logic of + * probing SD card, locating and mounting partition, and registering FATFS in VFS, + * with proper error checking and handling of exceptional conditions. + * + * @param base_path path where partition should be registered (e.g. "/sdcard") + * @param host_config pointer to structure describing SDMMC host + * @param slot_config pointer to structure with extra SDMMC slot configuration + * @param mount_config pointer to structure with extra parameters for mounting FATFS + * @param[out] out_card if not NULL, pointer to the card information structure will be returned via this argument + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called + * - ESP_ERR_NO_MEM if memory can not be allocated + * - ESP_FAIL if partition can not be mounted + * - other error codes from SDMMC host, SDMMC protocol, or FATFS drivers + */ +esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, + const sdmmc_host_t* host_config, + const sdmmc_slot_config_t* slot_config, + const esp_vfs_fat_sdmmc_mount_config_t* mount_config, + sdmmc_card_t** out_card); + +/** + * @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_sdmmc_mount + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount hasn't been called + */ +esp_err_t esp_vfs_fat_sdmmc_unmount(); diff --git a/components/fatfs/src/ffconf.h b/components/fatfs/src/ffconf.h index b3a1bf5747..23b63ea9e9 100644 --- a/components/fatfs/src/ffconf.h +++ b/components/fatfs/src/ffconf.h @@ -241,9 +241,9 @@ / lock control is independent of re-entrancy. */ -#define _FS_REENTRANT 0 +#define _FS_REENTRANT 1 #define _FS_TIMEOUT 1000 -#define _SYNC_t HANDLE +#define _SYNC_t SemaphoreHandle_t /* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs / module itself. Note that regardless of this option, file access to different / volume is always re-entrant and volume control functions, f_mount(), f_mkfs() @@ -261,7 +261,7 @@ / SemaphoreHandle_t and etc.. A header file for O/S definitions needs to be / included somewhere in the scope of ff.h. */ -/* #include // O/S definitions */ - +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" /*--- End of configuration options ---*/ diff --git a/components/fatfs/src/option/syscall.c b/components/fatfs/src/option/syscall.c index 14140c5a73..b6ceeaa3fe 100644 --- a/components/fatfs/src/option/syscall.c +++ b/components/fatfs/src/option/syscall.c @@ -21,22 +21,8 @@ int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object _SYNC_t *sobj /* Pointer to return the created sync object */ ) { - int ret; - - - *sobj = CreateMutex(NULL, FALSE, NULL); /* Win32 */ - ret = (int)(*sobj != INVALID_HANDLE_VALUE); - -// *sobj = SyncObjects[vol]; /* uITRON (give a static sync object) */ -// ret = 1; /* The initial value of the semaphore must be 1. */ - -// *sobj = OSMutexCreate(0, &err); /* uC/OS-II */ -// ret = (int)(err == OS_NO_ERR); - -// *sobj = xSemaphoreCreateMutex(); /* FreeRTOS */ -// ret = (int)(*sobj != NULL); - - return ret; + *sobj = xSemaphoreCreateMutex(); + return (*sobj != NULL) ? 1 : 0; } @@ -53,20 +39,8 @@ int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to any erro _SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ ) { - int ret; - - - ret = CloseHandle(sobj); /* Win32 */ - -// ret = 1; /* uITRON (nothing to do) */ - -// OSMutexDel(sobj, OS_DEL_ALWAYS, &err); /* uC/OS-II */ -// ret = (int)(err == OS_NO_ERR); - -// vSemaphoreDelete(sobj); /* FreeRTOS */ -// ret = 1; - - return ret; + vSemaphoreDelete(sobj); + return 1; } @@ -82,18 +56,7 @@ int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a gran _SYNC_t sobj /* Sync object to wait */ ) { - int ret; - - ret = (int)(WaitForSingleObject(sobj, _FS_TIMEOUT) == WAIT_OBJECT_0); /* Win32 */ - -// ret = (int)(wai_sem(sobj) == E_OK); /* uITRON */ - -// OSMutexPend(sobj, _FS_TIMEOUT, &err)); /* uC/OS-II */ -// ret = (int)(err == OS_NO_ERR); - -// ret = (int)(xSemaphoreTake(sobj, _FS_TIMEOUT) == pdTRUE); /* FreeRTOS */ - - return ret; + return (xSemaphoreTake(sobj, _FS_TIMEOUT) == pdTRUE) ? 1 : 0; } @@ -108,13 +71,7 @@ void ff_rel_grant ( _SYNC_t sobj /* Sync object to be signaled */ ) { - ReleaseMutex(sobj); /* Win32 */ - -// sig_sem(sobj); /* uITRON */ - -// OSMutexPost(sobj); /* uC/OS-II */ - -// xSemaphoreGive(sobj); /* FreeRTOS */ + xSemaphoreGive(sobj); } #endif diff --git a/components/fatfs/src/vfs_fat.c b/components/fatfs/src/vfs_fat.c new file mode 100644 index 0000000000..4ef387b43b --- /dev/null +++ b/components/fatfs/src/vfs_fat.c @@ -0,0 +1,538 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_log.h" +#include "ff.h" + +#include "diskio.h" + + +typedef struct { + char fat_drive[8]; + size_t max_files; + FATFS fs; + FIL files[0]; + _lock_t lock; +} vfs_fat_ctx_t; + +typedef struct { + DIR dir; + long offset; + FF_DIR ffdir; + FILINFO filinfo; + struct dirent cur_dirent; +} vfs_fat_dir_t; + + +static const char* TAG = "vfs_fat"; + +static size_t vfs_fat_write(void* p, int fd, const void * data, size_t size); +static off_t vfs_fat_lseek(void* p, int fd, off_t size, int mode); +static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size); +static int vfs_fat_open(void* ctx, const char * path, int flags, int mode); +static int vfs_fat_close(void* ctx, int fd); +static int vfs_fat_fstat(void* ctx, int fd, struct stat * st); +static int vfs_fat_stat(void* ctx, const char * path, struct stat * st); +static int vfs_fat_link(void* ctx, const char* n1, const char* n2); +static int vfs_fat_unlink(void* ctx, const char *path); +static int vfs_fat_rename(void* ctx, const char *src, const char *dst); +static DIR* vfs_fat_opendir(void* ctx, const char* name); +static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir); +static int vfs_fat_readdir_r(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent); +static long vfs_fat_telldir(void* ctx, DIR* pdir); +static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset); +static int vfs_fat_closedir(void* ctx, DIR* pdir); +static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode); +static int vfs_fat_rmdir(void* ctx, const char* name); + + +static char s_base_path[ESP_VFS_PATH_MAX]; +static vfs_fat_ctx_t* s_fat_ctx = NULL; + +esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, size_t max_files, FATFS** out_fs) +{ + if (s_fat_ctx) { + return ESP_ERR_INVALID_STATE; + } + const esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .write_p = &vfs_fat_write, + .lseek_p = &vfs_fat_lseek, + .read_p = &vfs_fat_read, + .open_p = &vfs_fat_open, + .close_p = &vfs_fat_close, + .fstat_p = &vfs_fat_fstat, + .stat_p = &vfs_fat_stat, + .link_p = &vfs_fat_link, + .unlink_p = &vfs_fat_unlink, + .rename_p = &vfs_fat_rename, + .opendir_p = &vfs_fat_opendir, + .closedir_p = &vfs_fat_closedir, + .readdir_p = &vfs_fat_readdir, + .readdir_r_p = &vfs_fat_readdir_r, + .seekdir_p = &vfs_fat_seekdir, + .telldir_p = &vfs_fat_telldir, + .mkdir_p = &vfs_fat_mkdir, + .rmdir_p = &vfs_fat_rmdir + }; + size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL); + s_fat_ctx = (vfs_fat_ctx_t*) calloc(1, ctx_size); + if (s_fat_ctx == NULL) { + return ESP_ERR_NO_MEM; + } + s_fat_ctx->max_files = max_files; + strncpy(s_fat_ctx->fat_drive, fat_drive, sizeof(s_fat_ctx->fat_drive) - 1); + *out_fs = &s_fat_ctx->fs; + esp_err_t err = esp_vfs_register(base_path, &vfs, s_fat_ctx); + if (err != ESP_OK) { + free(s_fat_ctx); + s_fat_ctx = NULL; + return err; + } + _lock_init(&s_fat_ctx->lock); + strncpy(s_base_path, base_path, sizeof(s_base_path) - 1); + s_base_path[sizeof(s_base_path) - 1] = 0; + return ESP_OK; +} + +esp_err_t esp_vfs_fat_unregister() +{ + if (s_fat_ctx == NULL) { + return ESP_ERR_INVALID_STATE; + } + esp_err_t err = esp_vfs_unregister(s_base_path); + if (err != ESP_OK) { + return err; + } + _lock_close(&s_fat_ctx->lock); + free(s_fat_ctx); + s_fat_ctx = NULL; + return ESP_OK; +} + +static int get_next_fd(vfs_fat_ctx_t* fat_ctx) +{ + for (size_t i = 0; i < fat_ctx->max_files; ++i) { + if (fat_ctx->files[i].obj.fs == NULL) { + return (int) i; + } + } + return -1; +} + +static int fat_mode_conv(int m) +{ + int res = 0; + int acc_mode = m & O_ACCMODE; + if (acc_mode == O_RDONLY) { + res |= FA_READ; + } else if (acc_mode == O_WRONLY) { + res |= FA_WRITE; + } else if (acc_mode == O_RDWR) { + res |= FA_READ | FA_WRITE; + } + if ((m & O_CREAT) && (m & O_EXCL)) { + res |= FA_CREATE_NEW; + } else if (m & O_CREAT) { + res |= FA_CREATE_ALWAYS; + } else if (m & O_APPEND) { + res |= FA_OPEN_ALWAYS; + } else { + res |= FA_OPEN_EXISTING; + } + return res; +} + +static int fresult_to_errno(FRESULT fr) +{ + switch(fr) { + case FR_DISK_ERR: return EIO; + case FR_INT_ERR: + assert(0 && "fatfs internal error"); + return EIO; + case FR_NOT_READY: return ENODEV; + case FR_NO_FILE: return ENOENT; + case FR_NO_PATH: return ENOENT; + case FR_INVALID_NAME: return EINVAL; + case FR_DENIED: return EACCES; + case FR_EXIST: return EEXIST; + case FR_INVALID_OBJECT: return EBADF; + case FR_WRITE_PROTECTED: return EACCES; + case FR_INVALID_DRIVE: return ENXIO; + case FR_NOT_ENABLED: return ENODEV; + case FR_NO_FILESYSTEM: return ENODEV; + case FR_MKFS_ABORTED: return EINTR; + case FR_TIMEOUT: return ETIMEDOUT; + case FR_LOCKED: return EACCES; + case FR_NOT_ENOUGH_CORE: return ENOMEM; + case FR_TOO_MANY_OPEN_FILES: return ENFILE; + case FR_INVALID_PARAMETER: return EINVAL; + case FR_OK: return 0; + } + assert(0 && "unhandled FRESULT"); + return ENOTSUP; +} + +static void file_cleanup(vfs_fat_ctx_t* ctx, int fd) +{ + memset(&ctx->files[fd], 0, sizeof(FIL)); +} + +static int vfs_fat_open(void* ctx, const char * path, int flags, int mode) +{ + ESP_LOGV(TAG, "%s: path=\"%s\", flags=%x, mode=%x", __func__, path, flags, mode); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&s_fat_ctx->lock); + int fd = get_next_fd(fat_ctx); + if (fd < 0) { + ESP_LOGE(TAG, "open: no free file descriptors"); + errno = ENFILE; + fd = -1; + goto out; + } + FRESULT res = f_open(&fat_ctx->files[fd], path, fat_mode_conv(flags)); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + file_cleanup(fat_ctx, fd); + errno = fresult_to_errno(res); + fd = -1; + goto out; + } +out: + _lock_release(&s_fat_ctx->lock); + return fd; +} + +static size_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + FIL* file = &fat_ctx->files[fd]; + unsigned written = 0; + FRESULT res = f_write(file, data, size, &written); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + if (written == 0) { + return -1; + } + } + return written; +} + +static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + FIL* file = &fat_ctx->files[fd]; + unsigned read = 0; + FRESULT res = f_read(file, dst, size, &read); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + if (read == 0) { + return -1; + } + } + return read; +} + +static int vfs_fat_close(void* ctx, int fd) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&s_fat_ctx->lock); + FIL* file = &fat_ctx->files[fd]; + FRESULT res = f_close(file); + file_cleanup(fat_ctx, fd); + int rc = 0; + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + rc = -1; + } + _lock_release(&s_fat_ctx->lock); + return rc; +} + +static off_t vfs_fat_lseek(void* ctx, int fd, off_t offset, int mode) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + FIL* file = &fat_ctx->files[fd]; + off_t new_pos; + if (mode == SEEK_SET) { + new_pos = offset; + } else if (mode == SEEK_CUR) { + off_t cur_pos = f_tell(file); + new_pos = cur_pos + offset; + } else if (mode == SEEK_END) { + off_t size = f_size(file); + new_pos = size + offset; + } else { + errno = EINVAL; + return -1; + } + FRESULT res = f_lseek(file, new_pos); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return new_pos; +} + +static int vfs_fat_fstat(void* ctx, int fd, struct stat * st) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + FIL* file = &fat_ctx->files[fd]; + st->st_size = f_size(file); + st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFREG; + return 0; +} + +static int vfs_fat_stat(void* ctx, const char * path, struct stat * st) +{ + FILINFO info; + FRESULT res = f_stat(path, &info); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + st->st_size = info.fsize; + st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | + ((info.fattrib & AM_DIR) ? S_IFDIR : S_IFREG); + struct tm tm; + uint16_t fdate = info.fdate; + tm.tm_mday = fdate & 0x1f; + fdate >>= 5; + tm.tm_mon = (fdate & 0xf) - 1; + fdate >>=4; + tm.tm_year = fdate + 80; + uint16_t ftime = info.ftime; + tm.tm_sec = (ftime & 0x1f) * 2; + ftime >>= 5; + tm.tm_min = (ftime & 0x3f); + ftime >>= 6; + tm.tm_hour = (ftime & 0x1f); + st->st_mtime = mktime(&tm); + return 0; +} + +static int vfs_fat_unlink(void* ctx, const char *path) +{ + FRESULT res = f_unlink(path); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static int vfs_fat_link(void* ctx, const char* n1, const char* n2) +{ + const size_t copy_buf_size = 4096; + void* buf = malloc(copy_buf_size); + if (buf == NULL) { + errno = ENOMEM; + return -1; + } + FIL f1; + FRESULT res = f_open(&f1, n1, FA_READ | FA_OPEN_EXISTING); + if (res != FR_OK) { + goto fail1; + } + FIL f2; + res = f_open(&f2, n2, FA_WRITE | FA_CREATE_NEW); + if (res != FR_OK) { + goto fail2; + } + size_t size_left = f_size(&f1); + while (size_left > 0) { + size_t will_copy = (size_left < copy_buf_size) ? size_left : copy_buf_size; + size_t read; + res = f_read(&f1, buf, will_copy, &read); + if (res != FR_OK) { + goto fail3; + } else if (read != will_copy) { + res = FR_DISK_ERR; + goto fail3; + } + size_t written; + res = f_write(&f2, buf, will_copy, &written); + if (res != FR_OK) { + goto fail3; + } else if (written != will_copy) { + res = FR_DISK_ERR; + goto fail3; + } + size_left -= will_copy; + } + +fail3: + f_close(&f2); +fail2: + f_close(&f1); +fail1: + free(buf); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static int vfs_fat_rename(void* ctx, const char *src, const char *dst) +{ + FRESULT res = f_rename(src, dst); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static DIR* vfs_fat_opendir(void* ctx, const char* name) +{ + vfs_fat_dir_t* fat_dir = calloc(1, sizeof(vfs_fat_dir_t)); + if (!fat_dir) { + errno = ENOMEM; + return NULL; + } + FRESULT res = f_opendir(&fat_dir->ffdir, name); + if (res != FR_OK) { + free(fat_dir); + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return NULL; + } + return (DIR*) fat_dir; +} + +static int vfs_fat_closedir(void* ctx, DIR* pdir) +{ + assert(pdir); + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + FRESULT res = f_closedir(&fat_dir->ffdir); + free(pdir); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir) +{ + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + struct dirent* out_dirent; + int err = vfs_fat_readdir_r(ctx, pdir, &fat_dir->cur_dirent, &out_dirent); + if (err != 0) { + errno = err; + return NULL; + } + return out_dirent; +} + +static int vfs_fat_readdir_r(void* ctx, DIR* pdir, + struct dirent* entry, struct dirent** out_dirent) +{ + assert(pdir); + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + FRESULT res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + return fresult_to_errno(res); + } + if (fat_dir->filinfo.fname[0] == 0) { + // end of directory + *out_dirent = NULL; + return 0; + } + entry->d_ino = 0; + if (fat_dir->filinfo.fattrib & AM_DIR) { + entry->d_type = DT_DIR; + } else { + entry->d_type = DT_REG; + } + strlcpy(entry->d_name, fat_dir->filinfo.fname, + sizeof(entry->d_name)); + fat_dir->offset++; + *out_dirent = entry; + return 0; +} + +static long vfs_fat_telldir(void* ctx, DIR* pdir) +{ + assert(pdir); + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + return fat_dir->offset; +} + +static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset) +{ + assert(pdir); + vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; + FRESULT res; + if (offset < fat_dir->offset) { + res = f_rewinddir(&fat_dir->ffdir); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: rewinddir fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return; + } + fat_dir->offset = 0; + } + while (fat_dir->offset < offset) { + res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: f_readdir fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return; + } + fat_dir->offset++; + } +} + +static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode) +{ + (void) mode; + FRESULT res = f_mkdir(name); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} + +static int vfs_fat_rmdir(void* ctx, const char* name) +{ + FRESULT res = f_unlink(name); + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + return 0; +} diff --git a/components/fatfs/src/vfs_fat_sdmmc.c b/components/fatfs/src/vfs_fat_sdmmc.c new file mode 100644 index 0000000000..e5956dd7b2 --- /dev/null +++ b/components/fatfs/src/vfs_fat_sdmmc.c @@ -0,0 +1,126 @@ +// 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 +#include "esp_log.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "driver/sdmmc_host.h" +#include "sdmmc_cmd.h" +#include "diskio.h" + +static const char* TAG = "vfs_fat_sdmmc"; +static sdmmc_card_t* s_card = NULL; + +esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, + const sdmmc_host_t* host_config, + const sdmmc_slot_config_t* slot_config, + const esp_vfs_fat_sdmmc_mount_config_t* mount_config, + sdmmc_card_t** out_card) +{ + const size_t workbuf_size = 4096; + void* workbuf = NULL; + + if (s_card != NULL) { + return ESP_ERR_INVALID_STATE; + } + // enable SDMMC + sdmmc_host_init(); + + // enable card slot + sdmmc_host_init_slot(host_config->slot, slot_config); + s_card = malloc(sizeof(sdmmc_card_t)); + if (s_card == NULL) { + return ESP_ERR_NO_MEM; + } + + // probe and initialize card + esp_err_t err = sdmmc_card_init(host_config, s_card); + if (err != ESP_OK) { + ESP_LOGD(TAG, "sdmmc_card_init failed 0x(%x)", err); + goto fail; + } + if (out_card != NULL) { + *out_card = s_card; + } + + // connect SDMMC driver to FATFS + ff_diskio_register_sdmmc(0, s_card); + + // connect FATFS to VFS + FATFS* fs; + err = esp_vfs_fat_register(base_path, "", mount_config->max_files, &fs); + if (err == ESP_ERR_INVALID_STATE) { + // it's okay, already registered with VFS + } else if (err != ESP_OK) { + ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", err); + goto fail; + } + + // Try to mount partition + FRESULT res = f_mount(fs, "", 1); + if (res != FR_OK) { + err = ESP_FAIL; + ESP_LOGW(TAG, "failed to mount card (%d)", res); + if (!(res == FR_NO_FILESYSTEM && mount_config->format_if_mount_failed)) { + goto fail; + } + ESP_LOGW(TAG, "partitioning card"); + DWORD plist[] = {100, 0, 0, 0}; + workbuf = malloc(workbuf_size); + res = f_fdisk(0, 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("", FM_ANY, s_card->csd.sector_size, workbuf, workbuf_size); + if (res != FR_OK) { + err = ESP_FAIL; + ESP_LOGD(TAG, "f_mkfs failed (%d)", res); + goto fail; + } + free(workbuf); + ESP_LOGW(TAG, "mounting again"); + res = f_mount(fs, "", 0); + if (res != FR_OK) { + err = ESP_FAIL; + ESP_LOGD(TAG, "f_mount failed after formatting (%d)", res); + goto fail; + } + } + return ESP_OK; + +fail: + free(workbuf); + esp_vfs_unregister(base_path); + free(s_card); + s_card = NULL; + return err; +} + +esp_err_t esp_vfs_fat_sdmmc_unmount() +{ + if (s_card == NULL) { + return ESP_ERR_INVALID_STATE; + } + // unmount + f_mount(0, "", 0); + // release SD driver + free(s_card); + s_card = NULL; + sdmmc_host_deinit(); + return esp_vfs_fat_unregister(); +} diff --git a/components/fatfs/test/component.mk b/components/fatfs/test/component.mk new file mode 100644 index 0000000000..ce464a212a --- /dev/null +++ b/components/fatfs/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/fatfs/test/test_fatfs.c b/components/fatfs/test/test_fatfs.c new file mode 100644 index 0000000000..9ee6606fb8 --- /dev/null +++ b/components/fatfs/test/test_fatfs.c @@ -0,0 +1,552 @@ +// 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 +#include +#include +#include +#include +#include +#include "unity.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "diskio.h" +#include "ff.h" + +static const char* hello_str = "Hello, World!\n"; + +#define HEAP_SIZE_CAPTURE() \ + size_t heap_size = esp_get_free_heap_size(); + +#define HEAP_SIZE_CHECK(tolerance) \ + do {\ + size_t final_heap_size = esp_get_free_heap_size(); \ + if (final_heap_size < heap_size - tolerance) { \ + printf("Initial heap size: %d, final: %d, diff=%d\n", heap_size, final_heap_size, heap_size - final_heap_size); \ + } \ + } while(0) + +static void create_file_with_text(const char* name, const char* text) +{ + FILE* f = fopen(name, "wb"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_TRUE(fputs(text, f) != EOF); + TEST_ASSERT_EQUAL(0, fclose(f)); +} + +TEST_CASE("can create and write file on sd card", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + create_file_with_text("/sdcard/hello.txt", hello_str); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("can read file on sd card", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + FILE* f = fopen("/sdcard/hello.txt", "r"); + TEST_ASSERT_NOT_NULL(f); + char buf[32]; + int cb = fread(buf, 1, sizeof(buf), f); + TEST_ASSERT_EQUAL(strlen(hello_str), cb); + TEST_ASSERT_EQUAL(0, strcmp(hello_str, buf)); + TEST_ASSERT_EQUAL(0, fclose(f)); + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write) +{ + const size_t buf_count = file_size / buf_size; + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + 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 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + FILE* f = fopen("/sdcard/4mb.bin", (write) ? "wb" : "rb"); + TEST_ASSERT_NOT_NULL(f); + + struct timeval tv_start; + gettimeofday(&tv_start, NULL); + for (size_t n = 0; n < buf_count; ++n) { + if (write) { + TEST_ASSERT_EQUAL(1, fwrite(buf, buf_size, 1, f)); + } else { + if (fread(buf, buf_size, 1, f) != 1) { + printf("reading at n=%d, eof=%d", n, feof(f)); + TEST_FAIL(); + } + } + } + + struct timeval tv_end; + gettimeofday(&tv_end, NULL); + + TEST_ASSERT_EQUAL(0, fclose(f)); + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + + float t_s = tv_end.tv_sec - tv_start.tv_sec + 1e-6f * (tv_end.tv_usec - tv_start.tv_usec); + printf("%s %d bytes (block size %d) in %.3fms (%.3f MB/s)\n", + (write)?"Wrote":"Read", file_size, buf_size, t_s * 1e3, + (file_size / 1024 / 1024) / t_s); +} + + +TEST_CASE("read speed test", "[fatfs]") +{ + + HEAP_SIZE_CAPTURE(); + const size_t buf_size = 16 * 1024; + uint32_t* buf = (uint32_t*) calloc(1, buf_size); + const size_t file_size = 4 * 1024 * 1024; + speed_test(buf, 4 * 1024, file_size, false); + HEAP_SIZE_CHECK(0); + speed_test(buf, 8 * 1024, file_size, false); + HEAP_SIZE_CHECK(0); + speed_test(buf, 16 * 1024, file_size, false); + HEAP_SIZE_CHECK(0); + free(buf); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("write speed test", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + + const size_t buf_size = 16 * 1024; + uint32_t* buf = (uint32_t*) calloc(1, buf_size); + for (size_t i = 0; i < buf_size / 4; ++i) { + buf[i] = esp_random(); + } + const size_t file_size = 4 * 1024 * 1024; + + speed_test(buf, 4 * 1024, file_size, true); + speed_test(buf, 8 * 1024, file_size, true); + speed_test(buf, 16 * 1024, file_size, true); + + free(buf); + + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("can lseek", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + FILE* f = fopen("/sdcard/seek.txt", "wb+"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n")); + TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR)); + TEST_ASSERT_EQUAL('9', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET)); + TEST_ASSERT_EQUAL('3', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END)); + TEST_ASSERT_EQUAL('8', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_END)); + TEST_ASSERT_EQUAL(14, ftell(f)); + TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n")); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END)); + TEST_ASSERT_EQUAL(18, ftell(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET)); + char buf[20]; + TEST_ASSERT_EQUAL(18, fread(buf, 1, sizeof(buf), f)); + const char ref_buf[] = "0123456789\n\0\0\0abc\n"; + TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1); + + TEST_ASSERT_EQUAL(0, fclose(f)); + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("stat returns correct values", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + struct tm tm; + tm.tm_year = 2016 - 1900; + tm.tm_mon = 0; + tm.tm_mday = 10; + tm.tm_hour = 16; + tm.tm_min = 30; + tm.tm_sec = 0; + time_t t = mktime(&tm); + printf("Setting time: %s", asctime(&tm)); + struct timeval now = { .tv_sec = t }; + settimeofday(&now, NULL); + + create_file_with_text("/sdcard/stat.txt", "foo\n"); + + struct stat st; + TEST_ASSERT_EQUAL(0, stat("/sdcard/stat.txt", &st)); + time_t mtime = st.st_mtime; + struct tm mtm; + localtime_r(&mtime, &mtm); + printf("File time: %s", asctime(&mtm)); + TEST_ASSERT(abs(mtime - t) < 2); // fatfs library stores time with 2 second precision + + TEST_ASSERT(st.st_mode & S_IFREG); + TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("unlink removes a file", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + create_file_with_text("/sdcard/unlink.txt", "unlink\n"); + + TEST_ASSERT_EQUAL(0, unlink("/sdcard/unlink.txt")); + + TEST_ASSERT_NULL(fopen("/sdcard/unlink.txt", "r")); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("link copies a file, rename moves a file", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + unlink("/sdcard/linkcopy.txt"); + unlink("/sdcard/link_dst.txt"); + unlink("/sdcard/link_src.txt"); + + FILE* f = fopen("/sdcard/link_src.txt", "w+"); + TEST_ASSERT_NOT_NULL(f); + char* str = "0123456789"; + for (int i = 0; i < 4000; ++i) { + TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f)); + } + TEST_ASSERT_EQUAL(0, fclose(f)); + + TEST_ASSERT_EQUAL(0, link("/sdcard/link_src.txt", "/sdcard/linkcopy.txt")); + + FILE* fcopy = fopen("/sdcard/linkcopy.txt", "r"); + TEST_ASSERT_NOT_NULL(fcopy); + TEST_ASSERT_EQUAL(0, fseek(fcopy, 0, SEEK_END)); + TEST_ASSERT_EQUAL(40000, ftell(fcopy)); + TEST_ASSERT_EQUAL(0, fclose(fcopy)); + + TEST_ASSERT_EQUAL(0, rename("/sdcard/linkcopy.txt", "/sdcard/link_dst.txt")); + TEST_ASSERT_NULL(fopen("/sdcard/linkcopy.txt", "r")); + FILE* fdst = fopen("/sdcard/link_dst.txt", "r"); + TEST_ASSERT_NOT_NULL(fdst); + TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END)); + TEST_ASSERT_EQUAL(40000, ftell(fdst)); + TEST_ASSERT_EQUAL(0, fclose(fdst)); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +typedef struct { + const char* filename; + bool write; + size_t word_count; + int seed; + SemaphoreHandle_t done; + int result; +} read_write_test_arg_t; + +#define READ_WRITE_TEST_ARG_INIT(name, seed_) \ + { \ + .filename = name, \ + .seed = seed_, \ + .word_count = 8192, \ + .write = true, \ + .done = xSemaphoreCreateBinary() \ + } + +static void read_write_task(void* param) +{ + read_write_test_arg_t* args = (read_write_test_arg_t*) param; + FILE* f = fopen(args->filename, args->write ? "wb" : "rb"); + if (f == NULL) { + args->result = ESP_ERR_NOT_FOUND; + goto done; + } + + srand(args->seed); + for (size_t i = 0; i < args->word_count; ++i) { + uint32_t val = rand(); + if (args->write) { + int cnt = fwrite(&val, sizeof(val), 1, f); + if (cnt != 1) { + args->result = ESP_FAIL; + goto close; + } + } else { + uint32_t rval; + int cnt = fread(&rval, sizeof(rval), 1, f); + if (cnt != 1 || rval != val) { + ets_printf("E: i=%d, cnt=%d rval=%d val=%d\n\n", i, cnt, rval, val); + args->result = ESP_FAIL; + goto close; + } + } + } + args->result = ESP_OK; + +close: + fclose(f); + +done: + xSemaphoreGive(args->done); + vTaskDelay(1); + vTaskDelete(NULL); +} + + +TEST_CASE("multiple tasks can use same volume", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT("/sdcard/f1", 1); + read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT("/sdcard/f2", 2); + + printf("writing f1 and f2\n"); + + xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1); + + xSemaphoreTake(args1.done, portMAX_DELAY); + printf("f1 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args1.result); + xSemaphoreTake(args2.done, portMAX_DELAY); + printf("f2 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args2.result); + + args1.write = false; + args2.write = false; + read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT("/sdcard/f3", 3); + read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT("/sdcard/f4", 4); + + printf("reading f1 and f2, writing f3 and f4\n"); + + xTaskCreatePinnedToCore(&read_write_task, "rw3", 2048, &args3, 3, NULL, 1); + xTaskCreatePinnedToCore(&read_write_task, "rw4", 2048, &args4, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1); + + xSemaphoreTake(args1.done, portMAX_DELAY); + printf("f1 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args1.result); + xSemaphoreTake(args2.done, portMAX_DELAY); + printf("f2 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args2.result); + xSemaphoreTake(args3.done, portMAX_DELAY); + printf("f3 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args3.result); + xSemaphoreTake(args4.done, portMAX_DELAY); + printf("f4 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args4.result); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + vSemaphoreDelete(args1.done); + vSemaphoreDelete(args2.done); + vSemaphoreDelete(args3.done); + vSemaphoreDelete(args4.done); + vTaskDelay(10); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("can create and remove directories", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir1", 0755)); + struct stat st; + TEST_ASSERT_EQUAL(0, stat("/sdcard/dir1", &st)); + TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); + TEST_ASSERT_FALSE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(0, rmdir("/sdcard/dir1")); + TEST_ASSERT_EQUAL(-1, stat("/sdcard/dir1", &st)); + + TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir2", 0755)); + create_file_with_text("/sdcard/dir2/1.txt", "foo\n"); + TEST_ASSERT_EQUAL(0, stat("/sdcard/dir2", &st)); + TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); + TEST_ASSERT_FALSE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(0, stat("/sdcard/dir2/1.txt", &st)); + TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); + TEST_ASSERT_TRUE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(-1, rmdir("/sdcard/dir2")); + TEST_ASSERT_EQUAL(0, unlink("/sdcard/dir2/1.txt")); + TEST_ASSERT_EQUAL(0, rmdir("/sdcard/dir2")); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} + +TEST_CASE("opendir, readdir, rewinddir, seekdir work as expected", "[fatfs]") +{ + HEAP_SIZE_CAPTURE(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + unlink("/sdcard/dir/inner/3.txt"); + rmdir("/sdcard/dir/inner"); + unlink("/sdcard/dir/2.txt"); + unlink("/sdcard/dir/1.txt"); + unlink("/sdcard/dir/boo.bin"); + rmdir("/sdcard/dir"); + + TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir", 0755)); + create_file_with_text("/sdcard/dir/2.txt", "1\n"); + create_file_with_text("/sdcard/dir/1.txt", "1\n"); + create_file_with_text("/sdcard/dir/boo.bin", "\01\02\03"); + TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir/inner", 0755)); + create_file_with_text("/sdcard/dir/inner/3.txt", "3\n"); + + DIR* dir = opendir("/sdcard/dir"); + TEST_ASSERT_NOT_NULL(dir); + int count = 0; + const char* names[4]; + while(count < 4) { + struct dirent* de = readdir(dir); + if (!de) { + break; + } + printf("found '%s'\n", de->d_name); + if (strcasecmp(de->d_name, "1.txt") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "1.txt"; + ++count; + } else if (strcasecmp(de->d_name, "2.txt") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "2.txt"; + ++count; + } else if (strcasecmp(de->d_name, "inner") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_DIR); + names[count] = "inner"; + ++count; + } else if (strcasecmp(de->d_name, "boo.bin") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "boo.bin"; + ++count; + } else { + TEST_FAIL_MESSAGE("unexpected directory entry"); + } + } + TEST_ASSERT_EQUAL(count, 4); + + rewinddir(dir); + struct dirent* de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0])); + seekdir(dir, 3); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3])); + seekdir(dir, 1); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1])); + seekdir(dir, 2); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2])); + + TEST_ASSERT_EQUAL(0, closedir(dir)); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); + HEAP_SIZE_CHECK(0); +} diff --git a/components/newlib/test/test_newlib.c b/components/newlib/test/test_newlib.c index 1d86cd4a97..1d2a4bf09f 100644 --- a/components/newlib/test/test_newlib.c +++ b/components/newlib/test/test_newlib.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include "unity.h" #include "sdkconfig.h" @@ -86,6 +88,33 @@ TEST_CASE("test time functions", "[newlib]") } +TEST_CASE("test asctime", "[newlib]") +{ + char buf[64]; + struct tm tm = { 0 }; + tm.tm_year = 2016 - 1900; + tm.tm_mon = 0; + tm.tm_mday = 10; + tm.tm_hour = 16; + tm.tm_min = 30; + tm.tm_sec = 0; + time_t t = mktime(&tm); + const char* time_str = asctime(&tm); + strlcpy(buf, time_str, sizeof(buf)); + printf("Setting time: %s", time_str); + struct timeval now = { .tv_sec = t }; + settimeofday(&now, NULL); + + struct timeval tv; + gettimeofday(&tv, NULL); + time_t mtime = tv.tv_sec; + struct tm mtm; + localtime_r(&mtime, &mtm); + time_str = asctime(&mtm); + printf("Got time: %s", time_str); + TEST_ASSERT_EQUAL_STRING(buf, time_str); +} + static bool fn_in_rom(void *fn, char *name) { const int fnaddr = (int)fn; diff --git a/components/sdmmc/test/test_sd.c b/components/sdmmc/test/test_sd.c index e8fbd25451..6c2154817f 100644 --- a/components/sdmmc/test/test_sd.c +++ b/components/sdmmc/test/test_sd.c @@ -1,3 +1,17 @@ +// 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 #include #include From 47f4a097049d74d6154c8dedda1850a4e99f45a2 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Jan 2017 04:09:09 +0800 Subject: [PATCH 097/167] docs: add sdmmc and fatfs docs --- docs/Doxyfile | 5 ++- docs/api/fatfs.rst | 64 +++++++++++++++++++++++++++++++ docs/api/sdmmc.rst | 95 ++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 2 + 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 docs/api/fatfs.rst create mode 100644 docs/api/sdmmc.rst diff --git a/docs/Doxyfile b/docs/Doxyfile index 668ebaba02..7dbd1cc8d1 100755 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -32,7 +32,10 @@ INPUT = ../components/esp32/include/esp_wifi.h \ ../components/esp32/include/esp_heap_alloc_caps.h \ ../components/freertos/include/freertos/heap_regions.h \ ../components/esp32/include/esp_smartconfig.h \ - ../components/esp32/include/esp_deep_sleep.h + ../components/esp32/include/esp_deep_sleep.h \ + ../components/sdmmc/include/sdmmc_cmd.h \ + ../components/fatfs/src/esp_vfs_fat.h \ + ../components/fatfs/src/diskio.h ## Get warnings for functions that have no documentation for their parameters or return value ## diff --git a/docs/api/fatfs.rst b/docs/api/fatfs.rst new file mode 100644 index 0000000000..d2efc87abf --- /dev/null +++ b/docs/api/fatfs.rst @@ -0,0 +1,64 @@ +FAT Filesystem Support +====================== + +ESP-IDF uses `FatFs `_ library to work with FAT filesystems. FatFs library resides in ``fatfs`` component. Although it can be used directly, many of its features can be accessed via VFS using C standard library and POSIX APIs. + +Additionally, FatFs has been modified to support run-time pluggable disk IO layer. This allows mapping of FatFs drives to physical disks at run-time. + +Using FatFs with VFS +-------------------- + +``esp_vfs_fat.h`` header file defines functions to connect FatFs with VFS. ``esp_vfs_fat_register`` function allocates a ``FATFS`` structure, and registers a given path prefix in VFS. Subsequent operations on files starting with this prefix are forwarded to FatFs APIs. ``esp_vfs_fat_unregister`` function deletes the registration with VFS, and frees the ``FATFS`` structure. + +Most applications will use the following flow when working with ``esp_vfs_fat_`` functions: + +1. Call ``esp_vfs_fat_register``, specifying path prefix where the filesystem has to be mounted (e.g. ``"/sdcard"``), FatFs drive number, and a variable which will receive a pointer to ``FATFS`` structure. + +2. Call ``ff_diskio_register`` function to register disk IO driver for the drive number used in step 1. + +3. Call ``f_mount`` function (and optionally ``f_fdisk``, ``f_mkfs``) to mount the filesystem using the same drive number which was passed to ``esp_vfs_fat_register``. See FatFs documentation for more details. + +4. Call POSIX and C standard library functions to open, read, write, erase, copy files, etc. Use paths starting with the prefix passed to ``esp_vfs_register`` (such as ``"/sdcard/hello.txt"``). + +5. Optionally, call FatFs library functions directly. Use paths without a VFS prefix in this case (``"/hello.txt"``). + +6. Close all open files. + +7. Call ``f_mount`` function for the same drive number, with NULL ``FATFS*`` argument, to unmount the filesystem. + +8. Call ``ff_diskio_register`` with NULL ``ff_diskio_impl_t*`` argument and the same drive number. + +9. Call ``esp_vfs_fat_unregister`` to remove FatFs from VFS, and free the ``FATFS`` structure allocated on step 1. + +Convenience functions, ``esp_vfs_fat_sdmmc_mount`` and ``esp_vfs_fat_sdmmc_unmount``, which wrap these steps and also handle SD card initialization, are described in the next section. + +.. doxygenfunction:: esp_vfs_fat_register +.. doxygenfunction:: esp_vfs_fat_unregister + + +Using FatFs with VFS and SD cards +--------------------------------- + +``esp_vfs_fat.h`` header file also provides a convenience function to perform steps 1–3 and 7–9, and also handle SD card initialization: ``esp_vfs_fat_sdmmc_mount``. This function does only limited error handling. Developers are encouraged to look at its source code and incorporate more advanced versions into production applications. ``esp_vfs_fat_sdmmc_unmount`` function unmounts the filesystem and releases resources acquired by ``esp_vfs_fat_sdmmc_mount``. + +.. doxygenfunction:: esp_vfs_fat_sdmmc_mount + +.. doxygenstruct:: esp_vfs_fat_sdmmc_mount_config_t + :members: + +.. doxygenfunction:: esp_vfs_fat_sdmmc_unmount + +FatFS disk IO layer +------------------- + +FatFs has been extended with an API to register disk IO driver at runtime. + +Implementation of disk IO functions for SD/MMC cards is provided. It can be registered for the given FatFs drive number using ``ff_diskio_register_sdmmc`` function. + +.. doxygenfunction:: ff_diskio_register + +.. doxygenstruct:: ff_diskio_impl_t + :members: + +.. doxygenfunction:: ff_diskio_register_sdmmc + diff --git a/docs/api/sdmmc.rst b/docs/api/sdmmc.rst new file mode 100644 index 0000000000..126be86576 --- /dev/null +++ b/docs/api/sdmmc.rst @@ -0,0 +1,95 @@ +SDMMC Host Peripheral +===================== + +Overview +-------- + +SDMMC peripheral supports SD and MMC memory cards and SDIO cards. SDMMC software builds on top of SDMMC driver and consists of the following parts: + +1. SDMMC host driver (``driver/sdmmc_host.h``) — this driver provides APIs to send commands to the slave device(s), send and receive data, and handling error conditions on the bus. + +2. SDMMC protocol layer (``sdmmc_cmd.h``) — this component handles specifics of SD protocol such as card initialization and data transfer commands. Despite the name, only SD (SDSC/SDHC/SDXC) cards are supported at the moment. Support for MCC/eMMC cards can be added in the future. + +Protocol layer works with the host via ``sdmmc_host_t`` structure. This structure contains pointers to various functions of the host. This design makes it possible to implement an SD host using SPI interface later. + +Application Example +------------------- + +An example which combines SDMMC driver with FATFS library is provided in ``examples/24_sd_card`` directory. This example initializes the card, writes and reads data from it using POSIX and C library APIs. See README.md file in the example directory for more information. + + +Protocol layer APIs +------------------- + +Protocol layer is given ``sdmmc_host_t`` structure which describes the SD/MMC host driver, lists its capabilites, and provides pointers to functions of the driver. Protocol layer stores card-specific information in ``sdmmc_card_t`` structure. When sending commands to the SD/MMC host driver, protocol layer uses ``sdmmc_command_t`` structure to describe the command, argument, expected return value, and data to transfer, if any. + +Normal usage of the protocol layer is as follows: + +1. Call the host driver functions to initialize the host (e.g. ``sdmmc_host_init``, ``sdmmc_host_init_slot``). +2. Call ``sdmmc_card_init`` to initialize the card, passing it host driver information (``host``) and a pointer to ``sdmmc_card_t`` structure which will be filled in (``card``). +3. To read and write sectors of the card, use ``sdmmc_read_sectors`` and ``sdmmc_write_sectors``, passing the pointer to card information structure (``card``). +4. When card is not used anymore, call the host driver function to disable SDMMC host peripheral and free resources allocated by the driver (e.g. ``sdmmc_host_deinit``). + +Most applications need to use the protocol layer only in one task; therefore the protocol layer doesn't implement any kind of locking on the ``sdmmc_card_t`` structure, or when accessing SDMMC host driver. Such locking has to be implemented in the higher layer, if necessary (e.g. in the filesystem driver). + +.. doxygenstruct:: sdmmc_host_t + :members: + +.. doxygendefine:: SDMMC_HOST_FLAG_1BIT +.. doxygendefine:: SDMMC_HOST_FLAG_4BIT +.. doxygendefine:: SDMMC_HOST_FLAG_8BIT +.. doxygendefine:: SDMMC_HOST_FLAG_SPI +.. doxygendefine:: SDMMC_FREQ_DEFAULT +.. doxygendefine:: SDMMC_FREQ_HIGHSPEED +.. doxygendefine:: SDMMC_FREQ_PROBING + +.. doxygenstruct:: sdmmc_command_t + :members: + +.. doxygenstruct:: sdmmc_card_t + :members: + +.. doxygenstruct:: sdmmc_csd_t + :members: + +.. doxygenstruct:: sdmmc_cid_t + :members: + +.. doxygenstruct:: sdmmc_scr_t + :members: + +.. doxygenfunction:: sdmmc_card_init +.. doxygenfunction:: sdmmc_write_sectors +.. doxygenfunction:: sdmmc_read_sectors + +SDMMC host driver APIs +---------------------- + +On the ESP32, SDMMC host peripheral has two slots: + +- Slot 0 (``SDMMC_HOST_SLOT_0``) is an 8-bit slot. It uses ``HS1_*`` signals in the PIN MUX. +- Slot 1 (``SDMMC_HOST_SLOT_1``) is a 4-bit slot. It uses ``HS2_*`` signals in the PIN MUX. + +Card Detect and Write Protect signals can be routed to arbitrary pins using GPIO matrix. To use these pins, set ``gpio_cd`` and ``gpio_wp`` members of ``sdmmc_slot_config_t`` structure when calling ``sdmmc_host_init_slot``. + +Of all the funtions listed below, only ``sdmmc_host_init``, ``sdmmc_host_init_slot``, and ``sdmmc_host_deinit`` will be used directly by most applications. Other functions, such as ``sdmmc_host_set_bus_width``, ``sdmmc_host_set_card_clk``, and ``sdmmc_host_do_transaction`` will be called by the SD/MMC protocol layer via function pointers in ``sdmmc_host_t`` structure. + +.. doxygenfunction:: sdmmc_host_init + +.. doxygendefine:: SDMMC_HOST_SLOT_0 +.. doxygendefine:: SDMMC_HOST_SLOT_1 +.. doxygendefine:: SDMMC_HOST_DEFAULT + +.. doxygenfunction:: sdmmc_host_init_slot + +.. doxygenstruct:: sdmmc_slot_config_t + :members: + +.. doxygendefine:: SDMMC_SLOT_NO_CD +.. doxygendefine:: SDMMC_SLOT_NO_WP +.. doxygendefine:: SDMMC_SLOT_CONFIG_DEFAULT + +.. doxygenfunction:: sdmmc_host_set_bus_width +.. doxygenfunction:: sdmmc_host_set_card_clk +.. doxygenfunction:: sdmmc_host_do_transaction +.. doxygenfunction:: sdmmc_host_deinit diff --git a/docs/index.rst b/docs/index.rst index 3161db345b..2d9a62f14b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -110,11 +110,13 @@ Contents: I2C Pulse Counter Sigma-delta Modulation + SD/MMC SPI Flash and Partition APIs SPI Master API Logging Non-Volatile Storage Virtual Filesystem + FAT Filesystem Ethernet Interrupt Allocation Memory Allocation From 084c14f1e441b589434785632d8d450bd4bb54e4 Mon Sep 17 00:00:00 2001 From: Liu Zhi Fu Date: Mon, 9 Jan 2017 10:09:10 +0800 Subject: [PATCH 098/167] esp32: fix wifi timer thread-safe issue Update wifi lib to fix a wifi timer thread-safe issue --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index 23d627498d..6ccb241d45 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 23d627498d60e524e5d95d649481bd650aff1aad +Subproject commit 6ccb241d457f9efddb9cc6b82307335400d7f8f9 From 9dd5f2a952456d2f996d0be50a518bba21c081b7 Mon Sep 17 00:00:00 2001 From: Wu Jian Gang Date: Sat, 7 Jan 2017 21:20:39 +0800 Subject: [PATCH 099/167] lwip: fix compile issue when autoip option enabled --- components/lwip/core/ipv4/autoip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/lwip/core/ipv4/autoip.c b/components/lwip/core/ipv4/autoip.c index faac4957ca..f27d28aa24 100755 --- a/components/lwip/core/ipv4/autoip.c +++ b/components/lwip/core/ipv4/autoip.c @@ -273,7 +273,7 @@ autoip_bind(struct netif *netif) #if ESP_LWIP struct dhcp *dhcp = netif->dhcp; if (dhcp->cb != NULL) { - dhcp->cb(); + dhcp->cb(netif); } #endif return ERR_OK; From 11a994d5d8c5d479ab436010ed20db59f11e16d1 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Mon, 9 Jan 2017 12:04:21 +0800 Subject: [PATCH 100/167] Calling esp_intr_noniram_[en|dis]able twice is an error, so abort instead of doing an assert which disappears in non-debug mode --- components/esp32/intr_alloc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esp32/intr_alloc.c b/components/esp32/intr_alloc.c index 9476a433d4..4fdda2f396 100644 --- a/components/esp32/intr_alloc.c +++ b/components/esp32/intr_alloc.c @@ -691,7 +691,7 @@ void esp_intr_noniram_disable() int oldint; int cpu=xPortGetCoreID(); int intmask=~non_iram_int_mask[cpu]; - assert(non_iram_int_disabled_flag[cpu]==false); + if (non_iram_int_disabled_flag[cpu]) abort(); non_iram_int_disabled_flag[cpu]=true; asm volatile ( "movi %0,0\n" @@ -709,7 +709,7 @@ void esp_intr_noniram_enable() { int cpu=xPortGetCoreID(); int intmask=non_iram_int_disabled[cpu]; - assert(non_iram_int_disabled_flag[cpu]==true); + if (!non_iram_int_disabled_flag[cpu]) abort(); non_iram_int_disabled_flag[cpu]=false; asm volatile ( "movi a3,0\n" From 6421479dabaea425ce08a705200533f02600b050 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Mon, 9 Jan 2017 10:09:05 +1100 Subject: [PATCH 101/167] build system: Fix sdkconfig.defaults file preventing menuconfig changes 'make defconfig' now behaves similarly whether sdkconfig.defaults is present or not, and 'make menuconfig' doesn't trigger a defconfig. --- make/project_config.mk | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/make/project_config.mk b/make/project_config.mk index b8e40f4357..011aa1ff0e 100644 --- a/make/project_config.mk +++ b/make/project_config.mk @@ -25,7 +25,7 @@ KCONFIG_TOOL_ENV=KCONFIG_AUTOHEADER=$(abspath $(BUILD_DIR_BASE)/include/sdkconfi COMPONENT_KCONFIGS="$(COMPONENT_KCONFIGS)" KCONFIG_CONFIG=$(SDKCONFIG) \ COMPONENT_KCONFIGS_PROJBUILD="$(COMPONENT_KCONFIGS_PROJBUILD)" -menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig | defconfig +menuconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(call prereq_if_explicit,defconfig) $(summary) MENUCONFIG $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig @@ -39,14 +39,11 @@ $(SDKCONFIG): defconfig endif endif -$(wildcard $(PROJECT_PATH)/sdkconfig.defaults): | menuconfig defconfig - cp $< $@ - # defconfig creates a default config, based on SDKCONFIG_DEFAULTS if present defconfig: $(KCONFIG_TOOL_DIR)/mconf $(IDF_PATH)/Kconfig $(BUILD_DIR_BASE) $(summary) DEFCONFIG ifneq ("$(wildcard $(SDKCONFIG_DEFAULTS))","") - cp $(SDKCONFIG_DEFAULTS) $(SDKCONFIG) + cat $(SDKCONFIG_DEFAULTS) >> $(SDKCONFIG) # append defaults to sdkconfig, will override existing values endif mkdir -p $(BUILD_DIR_BASE)/include/config $(KCONFIG_TOOL_ENV) $(KCONFIG_TOOL_DIR)/conf --olddefconfig $(IDF_PATH)/Kconfig From ca57a86f2091cc55ee40c20e7124089ee8813959 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Mon, 9 Jan 2017 16:42:45 +0800 Subject: [PATCH 102/167] Add option to automatically set a watchpoint at the end of the swapped-in task --- components/esp32/include/esp_panic.h | 10 ++++++ components/esp32/panic.c | 48 ++++++++++++++++++++++++++++ components/freertos/Kconfig | 16 ++++++++++ components/freertos/tasks.c | 7 ++++ 4 files changed, 81 insertions(+) diff --git a/components/esp32/include/esp_panic.h b/components/esp32/include/esp_panic.h index 6aba6c5f48..ce4ea3c501 100644 --- a/components/esp32/include/esp_panic.h +++ b/components/esp32/include/esp_panic.h @@ -14,8 +14,18 @@ #ifndef __ASSEMBLER__ +#include "esp_err.h" + void esp_set_breakpoint_if_jtag(void *fn); +#define ESP_WATCHPOINT_LOAD 0x40000000 +#define ESP_WATCHPOINT_STORE 0x80000000 +#define ESP_WATCHPOINT_ACCESS 0xC0000000 + +esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags); +void esp_clear_watchpoint(int no); + + #endif #endif \ No newline at end of file diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 3cdbfb3e39..b4a84aee8e 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -32,6 +32,7 @@ #include "esp_gdbstub.h" #include "esp_panic.h" #include "esp_attr.h" +#include "esp_err.h" /* Panic handlers; these get called when an unhandled exception occurs or the assembly-level @@ -353,3 +354,50 @@ void esp_set_breakpoint_if_jtag(void *fn) setFirstBreakpoint((uint32_t)fn); } } + + +esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags) +{ + int x; + if (no<0 || no>1) return ESP_ERR_INVALID_ARG; + if (flags&(~0xC0000000)) return ESP_ERR_INVALID_ARG; + int dbreakc=0x3F; + //We support watching 2^n byte values, from 1 to 64. Calculate the mask for that. + for (x=0; x<6; x++) { + if (size==(1<pxStack, 32, ESP_WATCHPOINT_STORE); +#endif + + } portEXIT_CRITICAL_NESTED(irqstate); } From 5c1c6d2c32245e017646b1cb17e6da8a603dc553 Mon Sep 17 00:00:00 2001 From: Liu Han Date: Mon, 9 Jan 2017 17:51:48 +0800 Subject: [PATCH 103/167] cJSON:Add float format process --- components/json/include/cJSON.h | 1 + components/json/library/cJSON.c | 1 + 2 files changed, 2 insertions(+) diff --git a/components/json/include/cJSON.h b/components/json/include/cJSON.h index 466d10dbd0..92ed8c3b48 100644 --- a/components/json/include/cJSON.h +++ b/components/json/include/cJSON.h @@ -90,6 +90,7 @@ extern cJSON *cJSON_CreateTrue(void); extern cJSON *cJSON_CreateFalse(void); extern cJSON *cJSON_CreateBool(int b); extern cJSON *cJSON_CreateNumber(double num); +extern cJSON *cJSON_CreateDouble(double num,int i_num); extern cJSON *cJSON_CreateString(const char *string); extern cJSON *cJSON_CreateArray(void); extern cJSON *cJSON_CreateObject(void); diff --git a/components/json/library/cJSON.c b/components/json/library/cJSON.c index ab3043711e..2a5c392161 100644 --- a/components/json/library/cJSON.c +++ b/components/json/library/cJSON.c @@ -694,6 +694,7 @@ cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if(item)item->ty cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} +cJSON *cJSON_CreateDouble(double num,int i_num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=i_num;}return item;} cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} cJSON *cJSON_CreateArray(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} cJSON *cJSON_CreateObject(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;} From d5464074ddaa3de09d8a9435f3750a5ba15ba507 Mon Sep 17 00:00:00 2001 From: qiyueixa Date: Mon, 9 Jan 2017 18:44:27 +0800 Subject: [PATCH 104/167] update wifi libs --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index 6ccb241d45..231ee92755 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 6ccb241d457f9efddb9cc6b82307335400d7f8f9 +Subproject commit 231ee92755a41c92f6243e0550557ce9e1131744 From 433ff1474ec01d31cd0a4b22157e9ff533d4e7a3 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 3 Jan 2017 11:34:23 +1100 Subject: [PATCH 105/167] WiFi software coexistence: Clarify KConfig description Ref http://esp32.com/viewtopic.php?f=13&t=844&p=3576 --- components/esp32/Kconfig | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 90cd734bf1..2a27c4df39 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -39,12 +39,13 @@ config ESP32_ENABLE_STACK_BT #endchoice config SW_COEXIST_ENABLE - bool "Software do control of wifi/bt coexisit" + bool "Software controls WiFi/BT coexistence" depends on ESP32_ENABLE_STACK_BT && ESP32_ENABLE_STACK_WIFI - default "n" - help - Software do something control of wifi/bt coexist. For some heavy traffic senario, - do sotware coexist, may be better. + default "n" + help + If enabled, WiFi & BT coexistence is controlled by software rather than hardware. + Recommended for heavy traffic scenarios. Both coexistence configuration options are + automatically managed, no user intervention is required. config MEMMAP_BT bool From b877216fefa8a49901b1f0f9a083c2a1d5dd5269 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 6 Jan 2017 10:14:32 +1100 Subject: [PATCH 106/167] stdatomic.h: Include stdint.h, required for some limit types --- components/newlib/include/stdatomic.h | 1 + 1 file changed, 1 insertion(+) diff --git a/components/newlib/include/stdatomic.h b/components/newlib/include/stdatomic.h index 09c0cf73e0..beba325b1a 100644 --- a/components/newlib/include/stdatomic.h +++ b/components/newlib/include/stdatomic.h @@ -32,6 +32,7 @@ #include #include +#include #if __has_extension(c_atomic) || __has_extension(cxx_atomic) #define __CLANG_ATOMICS From a54791846b54d1d24f5728acaf63f1c80b24f627 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Jan 2017 11:12:02 +1100 Subject: [PATCH 107/167] coap: Initialise/update the libcoap submodule --- components/coap/component.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/coap/component.mk b/components/coap/component.mk index b5f6ec43c2..86993b29e9 100644 --- a/components/coap/component.mk +++ b/components/coap/component.mk @@ -7,3 +7,5 @@ COMPONENT_ADD_INCLUDEDIRS := port/include port/include/coap libcoap/include libc COMPONENT_OBJS = libcoap/src/address.o libcoap/src/async.o libcoap/src/block.o libcoap/src/coap_time.o libcoap/src/debug.o libcoap/src/encode.o libcoap/src/hashkey.o libcoap/src/mem.o libcoap/src/net.o libcoap/src/option.o libcoap/src/pdu.o libcoap/src/resource.o libcoap/src/str.o libcoap/src/subscribe.o libcoap/src/uri.o port/coap_io_socket.o COMPONENT_SRCDIRS := libcoap/src libcoap port + +COMPONENT_SUBMODULES += libcoap From 315b3f979ffed5e1beef1e5f515d972394faa2dc Mon Sep 17 00:00:00 2001 From: Liu Han Date: Wed, 7 Dec 2016 15:37:59 +0800 Subject: [PATCH 108/167] components/tcpip_adapter: Allow to set different hostname for each interface --- components/lwip/port/netif/ethernetif.c | 68 +++++++------- components/lwip/port/netif/wlanif.c | 88 +++++++------------ .../tcpip_adapter/include/tcpip_adapter.h | 10 +-- components/tcpip_adapter/tcpip_adapter_lwip.c | 26 +++--- 4 files changed, 82 insertions(+), 110 deletions(-) mode change 100755 => 100644 components/lwip/port/netif/ethernetif.c mode change 100755 => 100644 components/lwip/port/netif/wlanif.c diff --git a/components/lwip/port/netif/ethernetif.c b/components/lwip/port/netif/ethernetif.c old mode 100755 new mode 100644 index 6b1245e2e6..90a5b241b0 --- a/components/lwip/port/netif/ethernetif.c +++ b/components/lwip/port/netif/ethernetif.c @@ -55,8 +55,6 @@ #define IFNAME0 'e' #define IFNAME1 'n' -static char hostname[16]; - /** * In this function, the hardware should be initialized. * Called from ethernetif_init(). @@ -78,14 +76,13 @@ ethernet_low_level_init(struct netif *netif) /* device capabilities */ /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; - + #if ESP_LWIP #if LWIP_IGMP - - netif->flags |= NETIF_FLAG_IGMP; + netif->flags |= NETIF_FLAG_IGMP; #endif #endif - /* Do whatever else is needed to initialize interface. */ + /* Do whatever else is needed to initialize interface. */ } /** @@ -113,30 +110,28 @@ ethernet_low_level_output(struct netif *netif, struct pbuf *p) LWIP_DEBUGF(NETIF_DEBUG,("eth_if=%d netif=%p pbuf=%p len=%d\n", eth_if, netif, p, p->len)); return ERR_IF; - } - + } + #if ESP_LWIP - q = p; - u16_t pbuf_x_len = 0; - pbuf_x_len = q->len; - if(q->next !=NULL) - { - //char cnt = 0; - struct pbuf *tmp = q->next; - while(tmp != NULL) - { - memcpy( (u8_t *)( (u8_t *)(q->payload) + pbuf_x_len), (u8_t *)tmp->payload , tmp->len ); - pbuf_x_len += tmp->len; - //cnt++; - tmp = tmp->next; - } + q = p; + u16_t pbuf_x_len = 0; + pbuf_x_len = q->len; + if(q->next !=NULL) { + //char cnt = 0; + struct pbuf *tmp = q->next; + while(tmp != NULL) { + memcpy( (u8_t *)( (u8_t *)(q->payload) + pbuf_x_len), (u8_t *)tmp->payload , tmp->len ); + pbuf_x_len += tmp->len; + //cnt++; + tmp = tmp->next; } - - return esp_eth_tx(q->payload, pbuf_x_len); + } + + return esp_eth_tx(q->payload, pbuf_x_len); #else - for(q = p; q != NULL; q = q->next) { - return esp_emac_tx(q->payload, q->len); - } + for(q = p; q != NULL; q = q->next) { + return esp_emac_tx(q->payload, q->len); + } return ERR_OK; #endif } @@ -154,9 +149,9 @@ void ethernetif_input(struct netif *netif, void *buffer, uint16_t len) { struct pbuf *p; - + if(buffer== NULL || netif == NULL) - goto _exit; + goto _exit; #if CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE p = pbuf_alloc(PBUF_RAW, len, PBUF_RAM); if (p == NULL) { @@ -178,7 +173,7 @@ if (netif->input(p, netif) != ERR_OK) { p->payload = buffer; p->user_flag = PBUF_USER_FLAG_OWNER_ETH; p->user_buf = buffer; - + /* full packet send to tcpip_thread to process */ if (netif->input(p, netif) != ERR_OK) { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n")); @@ -187,7 +182,7 @@ if (netif->input(p, netif) != ERR_OK) { } #endif _exit: -; +; } /** @@ -211,14 +206,11 @@ ethernetif_init(struct netif *netif) /* Initialize interface hostname */ #if ESP_LWIP - sprintf(hostname, "ESP_%02X%02X%02X", netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]); - netif->hostname = hostname; - + netif->hostname = "espressif"; #else - sprintf(hostname, "ESP_%02X%02X%02X", netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]); - netif->hostname = hostname; + netif->hostname = "lwip"; #endif - + #endif /* LWIP_NETIF_HOSTNAME */ /* @@ -239,7 +231,7 @@ ethernetif_init(struct netif *netif) netif->output_ip6 = ethip6_output; #endif /* LWIP_IPV6 */ netif->linkoutput = ethernet_low_level_output; - + /* initialize the hardware */ ethernet_low_level_init(netif); diff --git a/components/lwip/port/netif/wlanif.c b/components/lwip/port/netif/wlanif.c old mode 100755 new mode 100644 index e114105f30..f9def49218 --- a/components/lwip/port/netif/wlanif.c +++ b/components/lwip/port/netif/wlanif.c @@ -56,8 +56,6 @@ #define IFNAME0 'e' #define IFNAME1 'n' -static char hostname[16]; - /** * In this function, the hardware should be initialized. * Called from ethernetif_init(). @@ -67,11 +65,7 @@ static char hostname[16]; */ static void low_level_init(struct netif *netif) -{ - - - - +{ /* set MAC hardware address length */ netif->hwaddr_len = ETHARP_HWADDR_LEN; @@ -83,18 +77,14 @@ low_level_init(struct netif *netif) /* device capabilities */ /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; - + #if ESP_LWIP - #if LWIP_IGMP - - netif->flags |= NETIF_FLAG_IGMP; + netif->flags |= NETIF_FLAG_IGMP; +#endif #endif - - #endif - - /* Do whatever else is needed to initialize interface. */ + /* Do whatever else is needed to initialize interface. */ } /** @@ -115,29 +105,29 @@ low_level_init(struct netif *netif) static err_t low_level_output(struct netif *netif, struct pbuf *p) { - wifi_interface_t wifi_if = tcpip_adapter_get_esp_if(netif); - struct pbuf *q = p; - err_t ret; + wifi_interface_t wifi_if = tcpip_adapter_get_esp_if(netif); + struct pbuf *q = p; + err_t ret; - if (wifi_if >= ESP_IF_MAX) { - return ERR_IF; - } + if (wifi_if >= ESP_IF_MAX) { + return ERR_IF; + } - if(q->next == NULL) { - ret = esp_wifi_internal_tx(wifi_if, q->payload, q->len); + if(q->next == NULL) { + ret = esp_wifi_internal_tx(wifi_if, q->payload, q->len); + } else { + LWIP_DEBUGF(PBUF_DEBUG, ("low_level_output: pbuf is a list, application may has bug")); + q = pbuf_alloc(PBUF_RAW_TX, p->tot_len, PBUF_RAM); + if (q != NULL) { + pbuf_copy(q, p); } else { - LWIP_DEBUGF(PBUF_DEBUG, ("low_level_output: pbuf is a list, application may has bug")); - q = pbuf_alloc(PBUF_RAW_TX, p->tot_len, PBUF_RAM); - if (q != NULL) { - pbuf_copy(q, p); - } else { - return ERR_MEM; - } - ret = esp_wifi_internal_tx(wifi_if, q->payload, q->len); - pbuf_free(q); + return ERR_MEM; } - - return ret; + ret = esp_wifi_internal_tx(wifi_if, q->payload, q->len); + pbuf_free(q); + } + + return ret; } /** @@ -153,9 +143,9 @@ void wlanif_input(struct netif *netif, void *buffer, u16_t len, void* eb) { struct pbuf *p; - + if(!buffer || !netif) - goto _exit; + goto _exit; #if (ESP_L2_TO_L3_COPY == 1) p = pbuf_alloc(PBUF_RAW, len, PBUF_RAM); @@ -182,9 +172,9 @@ wlanif_input(struct netif *netif, void *buffer, u16_t len, void* eb) LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n")); pbuf_free(p); } - + _exit: -; +; } /** @@ -208,25 +198,11 @@ wlanif_init(struct netif *netif) /* Initialize interface hostname */ #if ESP_LWIP -//TO_DO -/* - if ((struct netif *)wifi_get_netif(STATION_IF) == netif) { - if (default_hostname == 1) { - wifi_station_set_default_hostname(netif->hwaddr); - } - netif->hostname = hostname; - } else { - netif->hostname = NULL; - } -*/ - sprintf(hostname, "ESP_%02X%02X%02X", netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]); - netif->hostname = hostname; - + netif->hostname = "espressif"; #else - sprintf(hostname, "ESP_%02X%02X%02X", netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]); - netif->hostname = hostname; + netif->hostname = "lwip"; #endif - + #endif /* LWIP_NETIF_HOSTNAME */ /* @@ -247,7 +223,7 @@ wlanif_init(struct netif *netif) netif->output_ip6 = ethip6_output; #endif /* LWIP_IPV6 */ netif->linkoutput = low_level_output; - + /* initialize the hardware */ low_level_init(netif); diff --git a/components/tcpip_adapter/include/tcpip_adapter.h b/components/tcpip_adapter/include/tcpip_adapter.h index 861f7ccb8d..07bdc12f91 100644 --- a/components/tcpip_adapter/include/tcpip_adapter.h +++ b/components/tcpip_adapter/include/tcpip_adapter.h @@ -98,7 +98,7 @@ typedef struct { typedef enum { TCPIP_ADAPTER_IF_STA = 0, /**< ESP32 station interface */ TCPIP_ADAPTER_IF_AP, /**< ESP32 soft-AP interface */ - TCPIP_ADAPTER_IF_ETH, /**< ESP32 ethernet interface */ + TCPIP_ADAPTER_IF_ETH, /**< ESP32 ethernet interface */ TCPIP_ADAPTER_IF_MAX } tcpip_adapter_if_t; @@ -126,7 +126,7 @@ typedef enum{ } tcpip_adapter_option_id_t; /** - * @brief Initialize tcpip adpater + * @brief Initialize tcpip adapter * * This will initialize TCPIP stack inside. */ @@ -411,12 +411,12 @@ esp_interface_t tcpip_adapter_get_esp_if(void *dev); */ esp_err_t tcpip_adapter_get_sta_list(wifi_sta_list_t *wifi_sta_list, tcpip_adapter_sta_list_t *tcpip_sta_list); -#define TCPIP_HOSTNAME_MAX_SIZE 31 +#define TCPIP_HOSTNAME_MAX_SIZE 32 /** * @brief Set the hostname to the interface * * @param[in] tcpip_if: the interface which we will set the hostname - * @param[in] hostname: the host name for set the interfce + * @param[in] hostname: the host name for set the interface, the max length of hostname is 32 bytes * * @return ESP_OK:success * ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY:interface status error @@ -428,7 +428,7 @@ esp_err_t tcpip_adapter_set_hostname(tcpip_adapter_if_t tcpip_if, const char *ho * @brief Get the hostname from the interface * * @param[in] tcpip_if: the interface which we will get the hostname - * @param[in] hostname: the host name from the interfce + * @param[in] hostname: the host name from the interface * * @return ESP_OK:success * ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY:interface status error diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index 726434b5fa..03f364fe67 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -706,34 +706,35 @@ esp_err_t tcpip_adapter_get_sta_list(wifi_sta_list_t *wifi_sta_list, tcpip_adapt esp_err_t tcpip_adapter_set_hostname(tcpip_adapter_if_t tcpip_if, const char *hostname) { +#if LWIP_NETIF_HOSTNAME struct netif *p_netif; - static char hostinfo[TCPIP_HOSTNAME_MAX_SIZE + 1]; + static char hostinfo[TCPIP_HOSTNAME_MAX_SIZE + 1][TCPIP_ADAPTER_IF_MAX]; if (tcpip_if >= TCPIP_ADAPTER_IF_MAX || hostname == NULL) { return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; } - if (strlen(hostname) >= TCPIP_HOSTNAME_MAX_SIZE) { + if (strlen(hostname) > TCPIP_HOSTNAME_MAX_SIZE) { return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; } p_netif = esp_netif[tcpip_if]; if (p_netif != NULL) { - if (netif_is_up(p_netif)) { - return ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY; - } else { - memset(hostinfo, 0, sizeof(hostinfo)); - memcpy(hostinfo, hostname, strlen(hostname)); - p_netif->hostname = hostinfo; - return ESP_OK; - } + memset(hostinfo[tcpip_if], 0, sizeof(hostinfo[tcpip_if])); + memcpy(hostinfo[tcpip_if], hostname, strlen(hostname)); + p_netif->hostname = hostinfo[tcpip_if]; + return ESP_OK; } else { - return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; + return ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY; } +#else + return ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY; +#endif } esp_err_t tcpip_adapter_get_hostname(tcpip_adapter_if_t tcpip_if, const char **hostname) { +#if LWIP_NETIF_HOSTNAME struct netif *p_netif = NULL; if (tcpip_if >= TCPIP_ADAPTER_IF_MAX || hostname == NULL) { return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; @@ -746,6 +747,9 @@ esp_err_t tcpip_adapter_get_hostname(tcpip_adapter_if_t tcpip_if, const char **h } else { return ESP_ERR_TCPIP_ADAPTER_INVALID_PARAMS; } +#else + return ESP_ERR_TCPIP_ADAPTER_IF_NOT_READY; +#endif } #endif From 2e78b397bc68ce8beaba95c543689a6d72c778ae Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Jan 2017 16:04:04 +1100 Subject: [PATCH 109/167] config: Simplify WiFi & Bluetooth config options Removes redundant menu options, splits WiFi configuration out from generic ESP32 configuration. --- components/bt/Kconfig | 29 +- components/bt/component.mk | 3 + components/esp32/Kconfig | 304 ++++++++++----------- components/esp32/heap_alloc_caps.c | 2 +- examples/05_ble_adv/sdkconfig.defaults | 7 - examples/12_blufi/sdkconfig.defaults | 7 - examples/14_gatt_server/sdkconfig.defaults | 7 - examples/15_gatt_client/sdkconfig.defaults | 7 - 8 files changed, 150 insertions(+), 216 deletions(-) diff --git a/components/bt/Kconfig b/components/bt/Kconfig index 9a8014fa66..204ccd1e5e 100644 --- a/components/bt/Kconfig +++ b/components/bt/Kconfig @@ -1,41 +1,26 @@ -menu "BT config" -visible if MEMMAP_BT - +menu "Bluetooth" config BT_ENABLED - bool - depends on ESP32_ENABLE_STACK_BT - help - This compiles in the low-level BT stack. + bool "Enable Bluetooth stack" config BTC_TASK_STACK_SIZE - int "BT event (callback to application) task stack size" + int "Bluetooth event (callback to application) task stack size" + depends on BT_ENABLED default 3072 help This select btc task stack size config BLUEDROID_MEM_DEBUG bool "Bluedroid memory debug" - default no + depends on BT_ENABLED + default n help Bluedroid memory debug -#config BT_BTLE -# bool "Enable BTLE" -# depends on BT_ENABLED -# help -# This compiles BTLE support -# -#config BT_BT -# bool "Enable classic BT" -# depends on BT_ENABLED -# help -# This enables classic BT support - endmenu # Memory reserved at start of DRAM for Bluetooth stack config BT_RESERVE_DRAM hex - default 0x10000 if MEMMAP_BT + default 0x10000 if BT_ENABLED default 0 diff --git a/components/bt/component.mk b/components/bt/component.mk index a51478af93..12cc088412 100644 --- a/components/bt/component.mk +++ b/components/bt/component.mk @@ -1,6 +1,7 @@ # # Component Makefile # +ifdef CONFIG_BT_ENABLED COMPONENT_ADD_INCLUDEDIRS := bluedroid/bta/include \ bluedroid/bta/sys/include \ @@ -71,3 +72,5 @@ COMPONENT_SRCDIRS := bluedroid/bta/dm \ . COMPONENT_SUBMODULES += lib + +endif diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 2a27c4df39..d07d0fe846 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -20,42 +20,6 @@ config ESP32_DEFAULT_CPU_FREQ_MHZ default 160 if ESP32_DEFAULT_CPU_FREQ_160 default 240 if ESP32_DEFAULT_CPU_FREQ_240 -#choice ESP32_WIFI_OR_BT -# prompt "Select stack to enable (WiFi or BT)" -# default ESP32_ENABLE_WIFI -# help -# Temporarily, WiFi and BT stacks can not be used at the same time. -# Select which stack to enable. - -config ESP32_ENABLE_STACK_WIFI - bool "WiFi" - select WIFI_ENABLED if ESP32_ENABLE_STACK_WIFI -config ESP32_ENABLE_STACK_BT - bool "BT" - select MEMMAP_BT if ESP32_ENABLE_STACK_BT - select BT_ENABLED if ESP32_ENABLE_STACK_BT -#config ESP32_ENABLE_STACK_NONE -# bool "None" -#endchoice - -config SW_COEXIST_ENABLE - bool "Software controls WiFi/BT coexistence" - depends on ESP32_ENABLE_STACK_BT && ESP32_ENABLE_STACK_WIFI - default "n" - help - If enabled, WiFi & BT coexistence is controlled by software rather than hardware. - Recommended for heavy traffic scenarios. Both coexistence configuration options are - automatically managed, no user intervention is required. - -config MEMMAP_BT - bool - depends on ESP32_ENABLE_STACK_BT - help - The Bluetooth stack uses memory that cannot be used as generic memory anymore. This - reserves the space for that within the memory map of the compiled binary. - This option is required to enable BT stack. - Temporarily, this option is not compatible with WiFi stack. - config MEMMAP_SMP bool "Reserve memory for two cores" default "y" @@ -96,19 +60,10 @@ config MEMMAP_SPISRAM depends on ESP32_NEEDS_NEW_SILICON_REV default "n" help - The ESP32 can control an external SPI SRAM chip, adding the memory it contains to the + The ESP32 can control an external SPI SRAM chip, adding the memory it contains to the main memory map. Enable this if you have this hardware and want to use it in the same way as on-chip RAM. -config WIFI_ENABLED - bool - default "y" - depends on ESP32_ENABLE_STACK_WIFI - help - This compiles in the low-level WiFi stack. - - Temporarily, this option is not compatible with BT stack. - config SYSTEM_EVENT_QUEUE_SIZE int "System event queue size" default 32 @@ -146,7 +101,7 @@ config NEWLIB_NANO_FORMAT ESP32 ROM contains parts of newlib C library, including printf/scanf family of functions. These functions have been compiled with so-called "nano" formatting option. This option doesn't support 64-bit integer formats and C99 - features, such as positional arguments. + features, such as positional arguments. For more details about "nano" formatting option, please see newlib readme file, search for '--enable-newlib-nano-formatted-io': @@ -181,42 +136,42 @@ config CONSOLE_UART_NONE endchoice choice CONSOLE_UART_NUM - prompt "UART peripheral to use for console output (0-1)" - depends on CONSOLE_UART_CUSTOM + prompt "UART peripheral to use for console output (0-1)" + depends on CONSOLE_UART_CUSTOM default CONSOLE_UART_CUSTOM_NUM_0 help - Due of a ROM bug, UART2 is not supported for console output - via ets_printf. + Due of a ROM bug, UART2 is not supported for console output + via ets_printf. config CONSOLE_UART_CUSTOM_NUM_0 - bool "UART0" + bool "UART0" config CONSOLE_UART_CUSTOM_NUM_1 - bool "UART1" + bool "UART1" endchoice config CONSOLE_UART_NUM - int - default 0 if CONSOLE_UART_DEFAULT || CONSOLE_UART_NONE - default 0 if CONSOLE_UART_CUSTOM_NUM_0 - default 1 if CONSOLE_UART_CUSTOM_NUM_1 + int + default 0 if CONSOLE_UART_DEFAULT || CONSOLE_UART_NONE + default 0 if CONSOLE_UART_CUSTOM_NUM_0 + default 1 if CONSOLE_UART_CUSTOM_NUM_1 config CONSOLE_UART_TX_GPIO - int "UART TX on GPIO#" - depends on CONSOLE_UART_CUSTOM - range 0 33 - default 19 + int "UART TX on GPIO#" + depends on CONSOLE_UART_CUSTOM + range 0 33 + default 19 config CONSOLE_UART_RX_GPIO - int "UART RX on GPIO#" - depends on CONSOLE_UART_CUSTOM - range 0 39 - default 21 + int "UART RX on GPIO#" + depends on CONSOLE_UART_CUSTOM + range 0 39 + default 21 config CONSOLE_UART_BAUDRATE - int "UART console baud rate" - depends on !CONSOLE_UART_NONE - default 115200 - range 1200 4000000 + int "UART console baud rate" + depends on !CONSOLE_UART_NONE + default 115200 + range 1200 4000000 config ULP_COPROC_ENABLED bool "Enable Ultra Low Power (ULP) Coprocessor" @@ -247,13 +202,13 @@ choice ESP32_PANIC prompt "Panic handler behaviour" default ESP32_PANIC_PRINT_REBOOT help - If FreeRTOS detects unexpected behaviour or an unhandled exception, the panic handler is + If FreeRTOS detects unexpected behaviour or an unhandled exception, the panic handler is invoked. Configure the panic handlers action here. config ESP32_PANIC_PRINT_HALT bool "Print registers and halt" help - Outputs the relevant registers over the serial port and halt the + Outputs the relevant registers over the serial port and halt the processor. Needs a manual reset to restart. config ESP32_PANIC_PRINT_REBOOT @@ -336,8 +291,8 @@ config TASK_WDT_CHECK_IDLE_TASK help With this turned on, the task WDT can detect if the idle task is not called within the task watchdog timeout period. The idle task not being called usually is a symptom of another - task hoarding the CPU. It is also a bad thing because FreeRTOS household tasks depend on the - idle task getting some runtime every now and then. Take Care: With this disabled, this + task hoarding the CPU. It is also a bad thing because FreeRTOS household tasks depend on the + idle task getting some runtime every now and then. Take Care: With this disabled, this watchdog will trigger if no tasks register themselves within the timeout value. config TASK_WDT_CHECK_IDLE_TASK_CPU1 @@ -354,7 +309,7 @@ config BROWNOUT_DET default y depends on NEEDS_ESP32_NEW_SILICON_REV help - The ESP32 has a built-in brownout detector which can detect if the voltage is lower than + The ESP32 has a built-in brownout detector which can detect if the voltage is lower than a specific value. If this happens, it will reset the chip in order to prevent unintended behaviour. @@ -408,25 +363,25 @@ config BROWNOUT_DET_RESETDELAY choice ESP32_TIME_SYSCALL - prompt "Timers used for gettimeofday function" - default ESP32_TIME_SYSCALL_USE_RTC_FRC1 - help - This setting defines which hardware timers are used to - implement 'gettimeofday' and 'time' functions in C library. - - - If only FRC1 timer is used, gettimeofday will provide time at - microsecond resolution. Time will not be preserved when going - into deep sleep mode. - - If both FRC1 and RTC timers are used, timekeeping will - continue in deep sleep. Time will be reported at 1 microsecond - resolution. - - If only RTC timer is used, timekeeping will continue in - deep sleep, but time will be measured at 6.(6) microsecond - resolution. Also the gettimeofday function itself may take - longer to run. - - If no timers are used, gettimeofday and time functions - return -1 and set errno to ENOSYS. - + prompt "Timers used for gettimeofday function" + default ESP32_TIME_SYSCALL_USE_RTC_FRC1 + help + This setting defines which hardware timers are used to + implement 'gettimeofday' and 'time' functions in C library. + + - If only FRC1 timer is used, gettimeofday will provide time at + microsecond resolution. Time will not be preserved when going + into deep sleep mode. + - If both FRC1 and RTC timers are used, timekeeping will + continue in deep sleep. Time will be reported at 1 microsecond + resolution. + - If only RTC timer is used, timekeeping will continue in + deep sleep, but time will be measured at 6.(6) microsecond + resolution. Also the gettimeofday function itself may take + longer to run. + - If no timers are used, gettimeofday and time functions + return -1 and set errno to ENOSYS. + config ESP32_TIME_SYSCALL_USE_RTC bool "RTC" config ESP32_TIME_SYSCALL_USE_RTC_FRC1 @@ -438,85 +393,104 @@ config ESP32_TIME_SYSCALL_USE_NONE endchoice choice ESP32_RTC_CLOCK_SOURCE - prompt "RTC clock source" - default ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC - help - Choose which clock is used as RTC clock source. - The only available option for now is to use internal - 150kHz RC oscillator. + prompt "RTC clock source" + default ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC + help + Choose which clock is used as RTC clock source. + The only available option for now is to use internal + 150kHz RC oscillator. config ESP32_RTC_CLOCK_SOURCE_INTERNAL_RC - bool "Internal RC" + bool "Internal RC" config ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL - bool "External 32kHz crystal" - depends on DOCUMENTATION_FOR_RTC_CNTL + bool "External 32kHz crystal" + depends on DOCUMENTATION_FOR_RTC_CNTL endchoice - config ESP32_DEEP_SLEEP_WAKEUP_DELAY - int "Extra delay in deep sleep wake stub (in us)" - default 0 - range 0 5000 - help - When ESP32 exits deep sleep, the CPU and the flash chip are powered on - at the same time. CPU will run deep sleep stub first, and then - proceed to load code from flash. Some flash chips need sufficient - time to pass between power on and first read operation. By default, - without any extra delay, this time is approximately 900us. - - If you are using a flash chip which needs more than 900us to become - ready after power on, set this parameter to add extra delay - to the default deep sleep stub. - - If you are seeing "flash read err, 1000" message printed to the - console after deep sleep reset, try increasing this value. + int "Extra delay in deep sleep wake stub (in us)" + default 0 + range 0 5000 + help + When ESP32 exits deep sleep, the CPU and the flash chip are powered on + at the same time. CPU will run deep sleep stub first, and then + proceed to load code from flash. Some flash chips need sufficient + time to pass between power on and first read operation. By default, + without any extra delay, this time is approximately 900us. + If you are using a flash chip which needs more than 900us to become + ready after power on, set this parameter to add extra delay + to the default deep sleep stub. -config ESP32_PHY_AUTO_INIT - bool "Initialize PHY in startup code" - default y - help - If enabled, PHY will be initialized in startup code, before - app_main function runs. - If this is undesired, disable this option and call esp_phy_init - from the application before enabling WiFi or BT. - - If this option is enabled, startup code will also initialize - NVS prior to initializing PHY. - - If unsure, choose 'y'. - -config ESP32_PHY_INIT_DATA_IN_PARTITION - bool "Use a partition to store PHY init data" - default n - help - If enabled, PHY init data will be loaded from a partition. - When using a custom partition table, make sure that PHY data - partition is included (type: 'data', subtype: 'phy'). - With default partition tables, this is done automatically. - If PHY init data is stored in a partition, it has to be flashed there, - otherwise runtime error will occur. - - If this option is not enabled, PHY init data will be embedded - into the application binary. - - If unsure, choose 'n'. - -config ESP32_PHY_MAX_TX_POWER - int "Max TX power (dBm)" - range 0 20 - default 20 - help - Set maximum transmit power. Actual transmit power for high - data rates may be lower than this setting. - -config ESP32_WIFI_RX_BUFFER_NUM - int "Max number of WiFi RX buffers" - range 2 25 - default 25 - help - Set the number of WiFi rx buffers. Each buffer takes approximately 1.6KB of RAM. - Larger number for higher throughput but more memory. Smaller number for lower - throughput but less memory. + If you are seeing "flash read err, 1000" message printed to the + console after deep sleep reset, try increasing this value. endmenu + +menu "WiFi" + +config WIFI_ENABLED + bool "Enable WiFi stack" + default y + +config SW_COEXIST_ENABLE + bool "Software controls WiFi/Bluetooth coexistence" + depends on WIFI_ENABLED && BT_ENABLED + default n + help + If enabled, WiFi & Bluetooth coexistence is controlled by software rather than hardware. + Recommended for heavy traffic scenarios. Both coexistence configuration options are + automatically managed, no user intervention is required. + +config ESP32_PHY_AUTO_INIT + bool "Initialize PHY in startup code" + depends on WIFI_ENABLED + default y + help + If enabled, PHY will be initialized in startup code, before + app_main function runs. + If this is undesired, disable this option and call esp_phy_init + from the application before enabling WiFi or BT. + + If this option is enabled, startup code will also initialize + NVS prior to initializing PHY. + + If unsure, choose 'y'. + +config ESP32_PHY_INIT_DATA_IN_PARTITION + bool "Use a partition to store PHY init data" + depends on WIFI_ENABLED + default n + help + If enabled, PHY init data will be loaded from a partition. + When using a custom partition table, make sure that PHY data + partition is included (type: 'data', subtype: 'phy'). + With default partition tables, this is done automatically. + If PHY init data is stored in a partition, it has to be flashed there, + otherwise runtime error will occur. + + If this option is not enabled, PHY init data will be embedded + into the application binary. + + If unsure, choose 'n'. + +config ESP32_PHY_MAX_TX_POWER + int "Max TX power (dBm)" + range 0 20 + default 20 + depends on WIFI_ENABLED + help + Set maximum transmit power. Actual transmit power for high + data rates may be lower than this setting. + +config ESP32_WIFI_RX_BUFFER_NUM + int "Max number of WiFi RX buffers" + depends on WIFI_ENABLED + range 2 25 + default 25 + help + Set the number of WiFi rx buffers. Each buffer takes approximately 1.6KB of RAM. + Larger number for higher throughput but more memory. Smaller number for lower + throughput but less memory. + +endmenu \ No newline at end of file diff --git a/components/esp32/heap_alloc_caps.c b/components/esp32/heap_alloc_caps.c index a89455835c..a4ff870f39 100644 --- a/components/esp32/heap_alloc_caps.c +++ b/components/esp32/heap_alloc_caps.c @@ -192,7 +192,7 @@ void heap_alloc_caps_init() { // TODO: this region should be checked, since we don't need to knock out all region finally disable_mem_region((void*)0x3ffe0000, (void*)0x3ffe8000); //knock out ROM data region -#if CONFIG_MEMMAP_BT +#if CONFIG_BT_ENABLED disable_mem_region((void*)0x3ffb0000, (void*)0x3ffc0000); //knock out BT data region #endif diff --git a/examples/05_ble_adv/sdkconfig.defaults b/examples/05_ble_adv/sdkconfig.defaults index e435f383c8..dcf4ad2c2d 100644 --- a/examples/05_ble_adv/sdkconfig.defaults +++ b/examples/05_ble_adv/sdkconfig.defaults @@ -5,10 +5,3 @@ # BT config # CONFIG_BT_ENABLED=y - -# -# ESP32-specific config -# -CONFIG_ESP32_ENABLE_STACK_BT=y -# CONFIG_ESP32_ENABLE_STACK_NONE is not set -CONFIG_MEMMAP_BT=y diff --git a/examples/12_blufi/sdkconfig.defaults b/examples/12_blufi/sdkconfig.defaults index e435f383c8..dcf4ad2c2d 100644 --- a/examples/12_blufi/sdkconfig.defaults +++ b/examples/12_blufi/sdkconfig.defaults @@ -5,10 +5,3 @@ # BT config # CONFIG_BT_ENABLED=y - -# -# ESP32-specific config -# -CONFIG_ESP32_ENABLE_STACK_BT=y -# CONFIG_ESP32_ENABLE_STACK_NONE is not set -CONFIG_MEMMAP_BT=y diff --git a/examples/14_gatt_server/sdkconfig.defaults b/examples/14_gatt_server/sdkconfig.defaults index e435f383c8..dcf4ad2c2d 100644 --- a/examples/14_gatt_server/sdkconfig.defaults +++ b/examples/14_gatt_server/sdkconfig.defaults @@ -5,10 +5,3 @@ # BT config # CONFIG_BT_ENABLED=y - -# -# ESP32-specific config -# -CONFIG_ESP32_ENABLE_STACK_BT=y -# CONFIG_ESP32_ENABLE_STACK_NONE is not set -CONFIG_MEMMAP_BT=y diff --git a/examples/15_gatt_client/sdkconfig.defaults b/examples/15_gatt_client/sdkconfig.defaults index e435f383c8..dcf4ad2c2d 100644 --- a/examples/15_gatt_client/sdkconfig.defaults +++ b/examples/15_gatt_client/sdkconfig.defaults @@ -5,10 +5,3 @@ # BT config # CONFIG_BT_ENABLED=y - -# -# ESP32-specific config -# -CONFIG_ESP32_ENABLE_STACK_BT=y -# CONFIG_ESP32_ENABLE_STACK_NONE is not set -CONFIG_MEMMAP_BT=y From 881157e1ed5e7e789be9ba6f4cb63fd4f7232961 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Tue, 10 Jan 2017 13:05:19 +0800 Subject: [PATCH 110/167] Add documentation to panic handler functions, move watchpoint stuff from tasks.c to port.c, account for non-32-bytes-aligned stacks when setting watchpoint, add debug reason explanation to panic handler --- components/esp32/include/esp_panic.h | 33 ++++++++++++++++- components/esp32/panic.c | 37 +++++++++++++++++-- .../freertos/include/freertos/portable.h | 7 ++++ components/freertos/port.c | 16 ++++++++ components/freertos/tasks.c | 3 +- 5 files changed, 89 insertions(+), 7 deletions(-) diff --git a/components/esp32/include/esp_panic.h b/components/esp32/include/esp_panic.h index ce4ea3c501..aa83c6d381 100644 --- a/components/esp32/include/esp_panic.h +++ b/components/esp32/include/esp_panic.h @@ -16,16 +16,47 @@ #include "esp_err.h" + +/** + * @brief If an OCD is connected over JTAG. set breakpoint 0 to the given function + * address. Do nothing otherwise. + * @param data Pointer to the target breakpoint position + */ + void esp_set_breakpoint_if_jtag(void *fn); #define ESP_WATCHPOINT_LOAD 0x40000000 #define ESP_WATCHPOINT_STORE 0x80000000 #define ESP_WATCHPOINT_ACCESS 0xC0000000 +/** + * @brief Set a watchpoint to break/panic when a certain memory range is accessed. + * + * @param no Watchpoint number. On the ESP32, this can be 0 or 1. + * @param adr Base address to watch + * @param size Size of the region, starting at the base address, to watch. Must + * be one of 2^n, with n in [0..6]. + * @param flags One of ESP_WATCHPOINT_* flags + * + * @return ESP_ERR_INVALID_ARG on invalid arg, ESP_OK otherwise + * + * @warning The ESP32 watchpoint hardware watches a region of bytes by effectively + * masking away the lower n bits for a region with size 2^n. If adr does + * not have zero for these lower n bits, you may not be watching the + * region you intended. + */ esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags); + + +/** + * @brief Clear a watchpoint + * + * @param no Watchpoint to clear + * + */ void esp_clear_watchpoint(int no); #endif -#endif \ No newline at end of file +#endif diff --git a/components/esp32/panic.c b/components/esp32/panic.c index b4a84aee8e..c5b18870a9 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -164,8 +164,37 @@ void panicHandler(XtExcFrame *frame) panicPutStr("Guru Meditation Error: Core "); panicPutDec(xPortGetCoreID()); panicPutStr(" panic'ed ("); - panicPutStr(reason); - panicPutStr(")\r\n"); + if (!abort_called) { + panicPutStr(reason); + panicPutStr(")\r\n"); + if (regs[20]==PANIC_RSN_DEBUGEXCEPTION) { + int debugRsn; + asm("rsr.debugcause %0":"=r"(debugRsn)); + panicPutStr("Debug exception reason: "); + if (debugRsn&XCHAL_DEBUGCAUSE_ICOUNT_MASK) panicPutStr("SingleStep "); + if (debugRsn&XCHAL_DEBUGCAUSE_IBREAK_MASK) panicPutStr("HwBreakpoint "); + if (debugRsn&XCHAL_DEBUGCAUSE_DBREAK_MASK) { + //Unlike what the ISA manual says, this core seemingly distinguishes from a DBREAK + //reason caused by watchdog 0 and one caused by watchdog 1 by setting bit 8 of the + //debugcause if the cause is watchdog 1 and clearing it if it's watchdog 0. + if (debugRsn&(1<<8)) { +#if CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK + panicPutStr("Stack canary watchpoint triggered "); +#else + panicPutStr("Watchpoint 1 triggered "); +#endif + } else { + panicPutStr("Watchpoint 0 triggered "); + } + } + if (debugRsn&XCHAL_DEBUGCAUSE_BREAK_MASK) panicPutStr("BREAK instr "); + if (debugRsn&XCHAL_DEBUGCAUSE_BREAKN_MASK) panicPutStr("BREAKN instr "); + if (debugRsn&XCHAL_DEBUGCAUSE_DEBUGINT_MASK) panicPutStr("DebugIntr "); + panicPutStr("\r\n"); + } + } else { + panicPutStr("abort)\r\n"); + } if (esp_cpu_in_ocd_debug_mode()) { asm("break.n 1"); @@ -363,11 +392,11 @@ esp_err_t esp_set_watchpoint(int no, void *adr, int size, int flags) if (flags&(~0xC0000000)) return ESP_ERR_INVALID_ARG; int dbreakc=0x3F; //We support watching 2^n byte values, from 1 to 64. Calculate the mask for that. - for (x=0; x<6; x++) { + for (x=0; x<7; x++) { if (size==(1<pxStack, 32, ESP_WATCHPOINT_STORE); + vPortSetStackWatchpoint(pxCurrentTCB[xPortGetCoreID()]->pxStack); #endif From 26d1a23308bbe61ed0d9f7a1da3657f0bafcf009 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 10 Jan 2017 16:39:43 +1100 Subject: [PATCH 111/167] config: Move WiFi & BT toggles to Components menu, same as Ethernet --- components/bt/Kconfig | 10 ++++------ components/esp32/Kconfig | 10 ++++------ components/ethernet/Kconfig | 4 ++-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/components/bt/Kconfig b/components/bt/Kconfig index 204ccd1e5e..d2227868ce 100644 --- a/components/bt/Kconfig +++ b/components/bt/Kconfig @@ -1,7 +1,7 @@ -menu "Bluetooth" - -config BT_ENABLED - bool "Enable Bluetooth stack" +menuconfig BT_ENABLED + bool "Bluetooth" + help + Select this option to enable Bluetooth stack and show the submenu with Bluetooth configuration choices. config BTC_TASK_STACK_SIZE int "Bluetooth event (callback to application) task stack size" @@ -17,8 +17,6 @@ config BLUEDROID_MEM_DEBUG help Bluedroid memory debug -endmenu - # Memory reserved at start of DRAM for Bluetooth stack config BT_RESERVE_DRAM hex diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index d07d0fe846..bc35ca0e8f 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -427,11 +427,11 @@ config ESP32_DEEP_SLEEP_WAKEUP_DELAY endmenu -menu "WiFi" - -config WIFI_ENABLED - bool "Enable WiFi stack" +menuconfig WIFI_ENABLED + bool "WiFi" default y + help + Select this option to enable WiFi stack and show the submenu with WiFi configuration choices. config SW_COEXIST_ENABLE bool "Software controls WiFi/Bluetooth coexistence" @@ -492,5 +492,3 @@ config ESP32_WIFI_RX_BUFFER_NUM Set the number of WiFi rx buffers. Each buffer takes approximately 1.6KB of RAM. Larger number for higher throughput but more memory. Smaller number for lower throughput but less memory. - -endmenu \ No newline at end of file diff --git a/components/ethernet/Kconfig b/components/ethernet/Kconfig index 9eb897cbba..0ea94eaafd 100644 --- a/components/ethernet/Kconfig +++ b/components/ethernet/Kconfig @@ -1,8 +1,8 @@ menuconfig ETHERNET - bool "Enable Ethernet" + bool "Ethernet" default n help - Enable this option to enable ethernet driver and show the menu with ethernet features. + Select this option to enable ethernet driver and show the submenu with ethernet features. config DMA_RX_BUF_NUM int "DMA Rx Buf Num" From 5eb8eb385599ee459655516e10ca168309e5a5e0 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Tue, 10 Jan 2017 14:41:12 +0800 Subject: [PATCH 112/167] SPI master: rename transaction flags from SPI_* to SPI_TRANS_*, like the documentation says. Also add some explanation about the SPI signals in the documentation --- components/driver/include/driver/spi_master.h | 10 +++---- components/driver/spi_master.c | 28 +++++++++---------- docs/api/spi_master.rst | 21 ++++++++++---- examples/26_spi_master/main/spi_master.c | 4 +-- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/components/driver/include/driver/spi_master.h b/components/driver/include/driver/spi_master.h index 4dd8738ad7..ea901b9924 100644 --- a/components/driver/include/driver/spi_master.h +++ b/components/driver/include/driver/spi_master.h @@ -86,11 +86,11 @@ typedef struct { } spi_device_interface_config_t; -#define SPI_MODE_DIO (1<<0) ///< Transmit/receive data in 2-bit mode -#define SPI_MODE_QIO (1<<1) ///< Transmit/receive data in 4-bit mode -#define SPI_MODE_DIOQIO_ADDR (1<<2) ///< Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO -#define SPI_USE_RXDATA (1<<2) ///< Receive into rx_data member of spi_transaction_t instead into memory at rx_buffer. -#define SPI_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_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<<2) ///< 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. /** * This structure describes one SPI transaction diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index fd4c5b2e29..1f1ea1ef08 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -452,10 +452,10 @@ static void IRAM_ATTR spi_intr(void *arg) if (host->cur_trans) { //Okay, transaction is done. - if ((host->cur_trans->rx_buffer || (host->cur_trans->flags & SPI_USE_RXDATA)) && host->cur_trans->rxlength<=THRESH_DMA_TRANS) { + if ((host->cur_trans->rx_buffer || (host->cur_trans->flags & SPI_TRANS_USE_RXDATA)) && host->cur_trans->rxlength<=THRESH_DMA_TRANS) { //Need to copy from SPI regs to result buffer. uint32_t *data; - if (host->cur_trans->flags & SPI_USE_RXDATA) { + if (host->cur_trans->flags & SPI_TRANS_USE_RXDATA) { data=(uint32_t*)&host->cur_trans->rx_data[0]; } else { data=(uint32_t*)host->cur_trans->rx_buffer; @@ -557,8 +557,8 @@ static void IRAM_ATTR spi_intr(void *arg) //QIO/DIO host->hw->ctrl.val &= ~(SPI_FREAD_DUAL|SPI_FREAD_QUAD|SPI_FREAD_DIO|SPI_FREAD_QIO); host->hw->user.val &= ~(SPI_FWRITE_DUAL|SPI_FWRITE_QUAD|SPI_FWRITE_DIO|SPI_FWRITE_QIO); - if (trans->flags & SPI_MODE_DIO) { - if (trans->flags & SPI_MODE_DIOQIO_ADDR) { + if (trans->flags & SPI_TRANS_MODE_DIO) { + if (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR) { host->hw->ctrl.fread_dio=1; host->hw->user.fwrite_dio=1; } else { @@ -566,8 +566,8 @@ static void IRAM_ATTR spi_intr(void *arg) host->hw->user.fwrite_dual=1; } host->hw->ctrl.fastrd_mode=1; - } else if (trans->flags & SPI_MODE_QIO) { - if (trans->flags & SPI_MODE_DIOQIO_ADDR) { + } else if (trans->flags & SPI_TRANS_MODE_QIO) { + if (trans->flags & SPI_TRANS_MODE_DIOQIO_ADDR) { host->hw->ctrl.fread_qio=1; host->hw->user.fwrite_qio=1; } else { @@ -579,9 +579,9 @@ static void IRAM_ATTR spi_intr(void *arg) //Fill DMA descriptors - if (trans->rx_buffer || (trans->flags & SPI_USE_RXDATA)) { + if (trans->rx_buffer || (trans->flags & SPI_TRANS_USE_RXDATA)) { uint32_t *data; - if (trans->flags & SPI_USE_RXDATA) { + if (trans->flags & SPI_TRANS_USE_RXDATA) { data=(uint32_t *)&trans->rx_data[0]; } else { data=trans->rx_buffer; @@ -604,9 +604,9 @@ static void IRAM_ATTR spi_intr(void *arg) host->hw->user.usr_miso=0; } - if (trans->tx_buffer || (trans->flags & SPI_USE_TXDATA)) { + if (trans->tx_buffer || (trans->flags & SPI_TRANS_USE_TXDATA)) { uint32_t *data; - if (trans->flags & SPI_USE_TXDATA) { + if (trans->flags & SPI_TRANS_USE_TXDATA) { data=(uint32_t *)&trans->tx_data[0]; } else { data=(uint32_t *)trans->tx_buffer; @@ -657,10 +657,10 @@ esp_err_t spi_device_queue_trans(spi_device_handle_t handle, spi_transaction_t * { BaseType_t r; SPI_CHECK(handle!=NULL, "invalid dev handle", ESP_ERR_INVALID_ARG); - SPI_CHECK((trans_desc->flags & SPI_USE_RXDATA)==0 ||trans_desc->length <= 32, "rxdata transfer > 32bytes", ESP_ERR_INVALID_ARG); - SPI_CHECK((trans_desc->flags & SPI_USE_TXDATA)==0 ||trans_desc->length <= 32, "txdata transfer > 32bytes", ESP_ERR_INVALID_ARG); - SPI_CHECK(!((trans_desc->flags & (SPI_MODE_DIO|SPI_MODE_QIO)) && (handle->cfg.flags & SPI_DEVICE_3WIRE)), "incompatible iface params", ESP_ERR_INVALID_ARG); - SPI_CHECK(!((trans_desc->flags & (SPI_MODE_DIO|SPI_MODE_QIO)) && (!(handle->cfg.flags & SPI_DEVICE_HALFDUPLEX))), "incompatible iface params", ESP_ERR_INVALID_ARG); + SPI_CHECK((trans_desc->flags & SPI_TRANS_USE_RXDATA)==0 ||trans_desc->length <= 32, "rxdata transfer > 32bytes", ESP_ERR_INVALID_ARG); + SPI_CHECK((trans_desc->flags & SPI_TRANS_USE_TXDATA)==0 ||trans_desc->length <= 32, "txdata transfer > 32bytes", ESP_ERR_INVALID_ARG); + SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO|SPI_TRANS_MODE_QIO)) && (handle->cfg.flags & SPI_DEVICE_3WIRE)), "incompatible iface params", ESP_ERR_INVALID_ARG); + SPI_CHECK(!((trans_desc->flags & (SPI_TRANS_MODE_DIO|SPI_TRANS_MODE_QIO)) && (!(handle->cfg.flags & SPI_DEVICE_HALFDUPLEX))), "incompatible iface params", ESP_ERR_INVALID_ARG); r=xQueueSend(handle->trans_queue, (void*)&trans_desc, ticks_to_wait); if (!r) return ESP_ERR_TIMEOUT; esp_intr_enable(handle->host->intr); diff --git a/docs/api/spi_master.rst b/docs/api/spi_master.rst index 99f2f6a7ca..4d7693c07c 100644 --- a/docs/api/spi_master.rst +++ b/docs/api/spi_master.rst @@ -29,6 +29,17 @@ The spi_master driver uses the following terms: * Bus: The SPI bus, common to all SPI devices connected to one host. In general the bus consists of the spid, spiq, spiclk and optionally spiwp and spihd signals. The SPI slaves are connected to these signals in parallel. + + - spiq - Also known as MISO, this is the input of the serial stream into the ESP32 + + - spid - Also known as MOSI, this is the output of the serial stream from the ESP32 + + - spiclk - Clock signal. Each data bit is clocked out or in on the positive or negative edge of this signal + + - spiwp - Write Protect signal. Only used for 4-bit (qio/qout) transactions. + + - spihd - Hold signal. Only used for 4-bit (qio/qout) transactions. + * Device: A SPI slave. Each SPI slave has its own chip select (CS) line, which is made active when a transmission to/from the SPI slave occurs. * Transaction: One instance of CS going active, data transfer from and/or to a device happening, and @@ -113,11 +124,11 @@ Macros .. doxygendefine:: SPI_DEVICE_HALFDUPLEX .. doxygendefine:: SPI_DEVICE_CLK_AS_CS -.. doxygendefine:: SPI_MODE_DIO -.. doxygendefine:: SPI_MODE_QIO -.. doxygendefine:: SPI_MODE_DIOQIO_ADDR -.. doxygendefine:: SPI_USE_RXDATA -.. doxygendefine:: SPI_USE_TXDATA +.. doxygendefine:: SPI_TRANS_MODE_DIO +.. doxygendefine:: SPI_TRANS_MODE_QIO +.. doxygendefine:: SPI_TRANS_MODE_DIOQIO_ADDR +.. doxygendefine:: SPI_TRANS_USE_RXDATA +.. doxygendefine:: SPI_TRANS_USE_TXDATA Type Definitions ^^^^^^^^^^^^^^^^ diff --git a/examples/26_spi_master/main/spi_master.c b/examples/26_spi_master/main/spi_master.c index 1cdbf72ad5..2013dfd9c9 100644 --- a/examples/26_spi_master/main/spi_master.c +++ b/examples/26_spi_master/main/spi_master.c @@ -168,7 +168,7 @@ void send_line(spi_device_handle_t spi, int ypos, uint16_t *line) trans[x].length=8*4; trans[x].user=(void*)1; } - trans[x].flags=SPI_USE_TXDATA; + trans[x].flags=SPI_TRANS_USE_TXDATA; } trans[0].tx_data[0]=0x2A; //Column Address Set trans[1].tx_data[0]=0; //Start Col High @@ -183,7 +183,7 @@ void send_line(spi_device_handle_t spi, int ypos, uint16_t *line) trans[4].tx_data[0]=0x2C; //memory write trans[5].tx_buffer=line; //finally send the line data trans[5].length=320*2*8; //Data length, in bits - trans[5].flags=0; //undo SPI_USE_TXDATA flag + trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag //Queue all transactions. for (x=0; x<6; x++) { From 0e701e1caccff50c6ed91f8cfd2a9b359cdbf35c Mon Sep 17 00:00:00 2001 From: shangke Date: Fri, 6 Jan 2017 13:49:42 +0800 Subject: [PATCH 113/167] ethernet: support flow control --- components/esp32/include/soc/dport_reg.h | 18 ++- components/esp32/phy_init.c | 4 +- components/esp32/system_api.c | 10 +- components/ethernet/emac_common.h | 10 ++ components/ethernet/emac_dev.c | 31 ++++- components/ethernet/emac_dev.h | 21 +++- components/ethernet/emac_main.c | 137 ++++++++++++++++------ components/ethernet/include/esp_eth.h | 4 + docs/api/esp_eth.rst | 1 + examples/17_ethernet/main/ethernet_main.c | 25 +++- examples/17_ethernet/main/tlk110_phy.h | 10 +- 11 files changed, 211 insertions(+), 60 deletions(-) diff --git a/components/esp32/include/soc/dport_reg.h b/components/esp32/include/soc/dport_reg.h index f84346717c..ef231e316e 100644 --- a/components/esp32/include/soc/dport_reg.h +++ b/components/esp32/include/soc/dport_reg.h @@ -1035,14 +1035,20 @@ #define DPORT_WIFI_CLK_EN_V 0xFFFFFFFF #define DPORT_WIFI_CLK_EN_S 0 -#define DPORT_WIFI_RST_EN_REG (DR_REG_DPORT_BASE + 0x0D0) -/* DPORT_WIFI_RST : R/W ;bitpos:[31:0] ;default: 32'h0 ; */ +#define DPORT_CORE_RST_EN_REG (DR_REG_DPORT_BASE + 0x0D0) +/* DPORT_CORE_RST : R/W ;bitpos:[31:0] ;default: 32'h0 ; */ /*description: */ +#define DPROT_RW_BTLP_RST (BIT(10)) +#define DPROT_RW_BTMAC_RST (BIT(9)) +#define DPORT_MACPWR_RST (BIT(8)) +#define DPORT_EMAC_RST (BIT(7)) +#define DPORT_SDIO_HOST_RST (BIT(6)) +#define DPORT_SDIO_RST (BIT(5)) +#define DPORT_BTMAC_RST (BIT(4)) +#define DPORT_BT_RST (BIT(3)) #define DPORT_MAC_RST (BIT(2)) -#define DPORT_WIFI_RST 0xFFFFFFFF -#define DPORT_WIFI_RST_M ((DPORT_WIFI_RST_V)<<(DPORT_WIFI_RST_S)) -#define DPORT_WIFI_RST_V 0xFFFFFFFF -#define DPORT_WIFI_RST_S 0 +#define DPORT_FE_RST (BIT(1)) +#define DPORT_BB_RST (BIT(0)) #define DPORT_BT_LPCK_DIV_INT_REG (DR_REG_DPORT_BASE + 0x0D4) /* DPORT_BTEXTWAKEUP_REQ : R/W ;bitpos:[12] ;default: 1'b0 ; */ diff --git a/components/esp32/phy_init.c b/components/esp32/phy_init.c index 6154beae90..bfc5a15f5e 100644 --- a/components/esp32/phy_init.c +++ b/components/esp32/phy_init.c @@ -39,8 +39,8 @@ esp_err_t esp_phy_init(const esp_phy_init_data_t* init_data, assert(calibration_data); // Initialize PHY pointer table phy_get_romfunc_addr(); - REG_SET_BIT(DPORT_WIFI_RST_EN_REG, DPORT_MAC_RST); - REG_CLR_BIT(DPORT_WIFI_RST_EN_REG, DPORT_MAC_RST); + REG_SET_BIT(DPORT_CORE_RST_EN_REG, DPORT_MAC_RST); + REG_CLR_BIT(DPORT_CORE_RST_EN_REG, DPORT_MAC_RST); // Enable WiFi peripheral clock SET_PERI_REG_MASK(DPORT_WIFI_CLK_EN_REG, 0x87cf); ESP_LOGV(TAG, "register_chipv7_phy, init_data=%p, cal_data=%p, mode=%d", diff --git a/components/esp32/system_api.c b/components/esp32/system_api.c index 63a28de533..60fa1796cd 100644 --- a/components/esp32/system_api.c +++ b/components/esp32/system_api.c @@ -111,9 +111,13 @@ void IRAM_ATTR esp_restart(void) uart_tx_wait_idle(1); uart_tx_wait_idle(2); - // Reset wifi/bluetooth (bb/mac) - SET_PERI_REG_MASK(DPORT_WIFI_RST_EN_REG, 0x1f); - REG_WRITE(DPORT_WIFI_RST_EN_REG, 0); + // Reset wifi/bluetooth/ethernet/sdio (bb/mac) + SET_PERI_REG_MASK(DPORT_CORE_RST_EN_REG, + DPORT_BB_RST | DPORT_FE_RST | DPORT_MAC_RST | + DPORT_BT_RST | DPORT_BTMAC_RST | DPORT_SDIO_RST | + DPORT_SDIO_HOST_RST | DPORT_EMAC_RST | DPORT_MACPWR_RST | + DPROT_RW_BTMAC_RST | DPROT_RW_BTLP_RST); + REG_WRITE(DPORT_CORE_RST_EN_REG, 0); // Reset timer/spi/uart SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, diff --git a/components/ethernet/emac_common.h b/components/ethernet/emac_common.h index 774c287ad5..957337d2a7 100644 --- a/components/ethernet/emac_common.h +++ b/components/ethernet/emac_common.h @@ -72,6 +72,9 @@ struct emac_config_data { eth_phy_check_init_func emac_phy_check_init; eth_phy_get_speed_mode_func emac_phy_get_speed_mode; eth_phy_get_duplex_mode_func emac_phy_get_duplex_mode; + bool emac_flow_ctrl_enable; + bool emac_flow_ctrl_partner_support; + eth_phy_get_partner_pause_enable_func emac_phy_get_partner_pause_enable; }; enum emac_post_type { @@ -109,6 +112,13 @@ struct emac_close_cmd { #define DMA_RX_BUF_SIZE 1600 #define DMA_TX_BUF_SIZE 1600 +//rest buf num +#define FLOW_CONTROL_HIGH_WATERMARK 3 +//used buf num +#define FLOW_CONTROL_LOW_WATERMARK 6 + +#define PHY_LINK_CHECK_NUM 5 + #define EMAC_CMD_OK 0 #define EMAC_CMD_FAIL -1 diff --git a/components/ethernet/emac_dev.c b/components/ethernet/emac_dev.c index 244da08432..ba61bb750b 100644 --- a/components/ethernet/emac_dev.c +++ b/components/ethernet/emac_dev.c @@ -34,6 +34,24 @@ static const char *TAG = "emac"; +void emac_enable_flowctrl(void) +{ + REG_SET_BIT(EMAC_GMACFLOWCONTROL_REG, EMAC_TRANSMIT_FLOW_CONTROL_ENABLE); + REG_SET_BIT(EMAC_GMACFLOWCONTROL_REG, EMAC_RECEIVE_FLOW_CONTROL_ENABLE); + REG_CLR_BIT(EMAC_GMACFLOWCONTROL_REG, EMAC_DISABLE_ZERO_QUANTA_PAUSE); + REG_SET_FIELD(EMAC_GMACFLOWCONTROL_REG, EMAC_PAUSE_TIME, 0x1648); + REG_SET_FIELD(EMAC_GMACFLOWCONTROL_REG, EMAC_PAUSE_LOW_THRESHOLD, 0x1); +} + +void emac_disable_flowctrl(void) +{ + REG_CLR_BIT(EMAC_GMACFLOWCONTROL_REG, EMAC_TRANSMIT_FLOW_CONTROL_ENABLE); + REG_CLR_BIT(EMAC_GMACFLOWCONTROL_REG, EMAC_RECEIVE_FLOW_CONTROL_ENABLE); + REG_CLR_BIT(EMAC_GMACFLOWCONTROL_REG, EMAC_DISABLE_ZERO_QUANTA_PAUSE); + REG_SET_FIELD(EMAC_GMACFLOWCONTROL_REG, EMAC_PAUSE_TIME, 0); + REG_SET_FIELD(EMAC_GMACFLOWCONTROL_REG, EMAC_PAUSE_LOW_THRESHOLD, 0); +} + void emac_enable_dma_tx(void) { REG_SET_BIT(EMAC_DMAOPERATION_MODE_REG, EMAC_START_STOP_TRANSMISSION_COMMAND); @@ -100,18 +118,21 @@ void emac_dma_init(void) REG_SET_BIT(EMAC_DMAOPERATION_MODE_REG, EMAC_FORWARD_UNDERSIZED_GOOD_FRAMES); REG_SET_BIT(EMAC_DMAOPERATION_MODE_REG, EMAC_OPERATE_SECOND_FRAME); REG_SET_FIELD(EMAC_DMABUSMODE_REG, EMAC_PROG_BURST_LEN, 4); - REG_SET_BIT(EMAC_DMAOPERATION_MODE_REG,EMAC_DMAOPERATION_MODE_REG); + REG_SET_BIT(EMAC_DMAOPERATION_MODE_REG, EMAC_DMAOPERATION_MODE_REG); +} + +void emac_mac_enable_txrx(void) +{ + REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACRX); + REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACTX); } void emac_mac_init(void) { REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACDUPLEX); REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACMIIGMII); - REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACFESPEED); + REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACFESPEED); REG_SET_BIT(EMAC_GMACFRAMEFILTER_REG, EMAC_PROMISCUOUS_MODE); - - REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACRX); - REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACTX); } void emac_set_clk_rmii(void) diff --git a/components/ethernet/emac_dev.h b/components/ethernet/emac_dev.h index bc04d8d9e2..dc1045b92f 100644 --- a/components/ethernet/emac_dev.h +++ b/components/ethernet/emac_dev.h @@ -52,6 +52,9 @@ void emac_enable_dma_tx(void); void emac_enable_dma_rx(void); void emac_disable_dma_tx(void); void emac_disable_dma_rx(void); +void emac_enable_flowctrl(void); +void emac_disable_flowctrl(void); +void emac_mac_enable_txrx(void); uint32_t inline emac_read_tx_cur_reg(void) { @@ -77,22 +80,32 @@ void inline emac_poll_rx_cmd(void) void inline emac_disable_rx_intr(void) { - REG_CLR_BIT(EMAC_DMAINTERRUPT_EN_REG,EMAC_RECEIVE_INTERRUPT_ENABLE); + REG_CLR_BIT(EMAC_DMAINTERRUPT_EN_REG, EMAC_RECEIVE_INTERRUPT_ENABLE); } void inline emac_enable_rx_intr(void) { - REG_SET_BIT(EMAC_DMAINTERRUPT_EN_REG,EMAC_RECEIVE_INTERRUPT_ENABLE); + REG_SET_BIT(EMAC_DMAINTERRUPT_EN_REG, EMAC_RECEIVE_INTERRUPT_ENABLE); } void inline emac_disable_rx_unavail_intr(void) { - REG_CLR_BIT(EMAC_DMAINTERRUPT_EN_REG,EMAC_RECEIVE_BUFFER_UNAVAILABLE_ENABLE); + REG_CLR_BIT(EMAC_DMAINTERRUPT_EN_REG, EMAC_RECEIVE_BUFFER_UNAVAILABLE_ENABLE); } void inline emac_enable_rx_unavail_intr(void) { - REG_SET_BIT(EMAC_DMAINTERRUPT_EN_REG,EMAC_RECEIVE_BUFFER_UNAVAILABLE_ENABLE); + REG_SET_BIT(EMAC_DMAINTERRUPT_EN_REG, EMAC_RECEIVE_BUFFER_UNAVAILABLE_ENABLE); +} + +void IRAM_ATTR inline emac_send_pause_frame_enable(void) +{ + REG_SET_BIT(EMAC_EX_PHYINF_CONF_REG, EMAC_EX_SBD_FLOWCTRL); +} + +void inline emac_send_pause_zero_frame_enable(void) +{ + REG_CLR_BIT(EMAC_EX_PHYINF_CONF_REG, EMAC_EX_SBD_FLOWCTRL); } #ifdef __cplusplus diff --git a/components/ethernet/emac_main.c b/components/ethernet/emac_main.c index 06e641453b..9446e8ee35 100644 --- a/components/ethernet/emac_main.c +++ b/components/ethernet/emac_main.c @@ -68,6 +68,7 @@ static TimerHandle_t emac_timer = NULL; static SemaphoreHandle_t emac_rx_xMutex = NULL; static SemaphoreHandle_t emac_tx_xMutex = NULL; static const char *TAG = "emac"; +static bool pause_send = false; static esp_err_t emac_ioctl(emac_sig_t sig, emac_par_t par); esp_err_t emac_post(emac_sig_t sig, emac_par_t par); @@ -96,9 +97,9 @@ static void emac_clean_tx_desc(struct dma_extended_desc *tx_desc) tx_desc->basic.desc0 = 0; } -static void emac_clean_rx_desc(struct dma_extended_desc *rx_desc ,uint32_t buf_ptr) +static void emac_clean_rx_desc(struct dma_extended_desc *rx_desc , uint32_t buf_ptr) { - if(buf_ptr != 0) { + if (buf_ptr != 0) { rx_desc->basic.desc2 = buf_ptr; } rx_desc->basic.desc1 = EMAC_DESC_RX_SECOND_ADDR_CHAIN | DMA_RX_BUF_SIZE; @@ -215,6 +216,8 @@ static void emac_set_user_config_data(eth_config_t *config ) emac_config.emac_phy_check_init = config->phy_check_init; emac_config.emac_phy_get_speed_mode = config->phy_get_speed_mode; emac_config.emac_phy_get_duplex_mode = config->phy_get_duplex_mode; + emac_config.emac_flow_ctrl_enable = config->flow_ctrl_enable; + emac_config.emac_phy_get_partner_pause_enable = config->phy_get_partner_pause_enable; } static void emac_enable_intr() @@ -276,6 +279,11 @@ static esp_err_t emac_verify_args(void) ret = ESP_FAIL; } + if (emac_config.emac_flow_ctrl_enable == true && emac_config.emac_phy_get_partner_pause_enable == NULL) { + ESP_LOGE(TAG, "phy get partner pause enable func is null"); + ret = ESP_FAIL; + } + return ret; } @@ -293,13 +301,13 @@ static void emac_process_tx(void) { uint32_t cur_tx_desc = emac_read_tx_cur_reg(); - if(emac_config.emac_status == EMAC_RUNTIME_STOP) { + if (emac_config.emac_status == EMAC_RUNTIME_STOP) { return; } xSemaphoreTakeRecursive( emac_tx_xMutex, ( TickType_t ) portMAX_DELAY ); - while (((uint32_t) &(emac_config.dma_etx[emac_config.dirty_tx].basic.desc0) != cur_tx_desc)) { + while (((uint32_t) & (emac_config.dma_etx[emac_config.dirty_tx].basic.desc0) != cur_tx_desc)) { emac_clean_tx_desc(&(emac_config.dma_etx[emac_config.dirty_tx])); emac_config.dirty_tx = (emac_config.dirty_tx + 1) % DMA_TX_BUF_NUM; emac_config.cnt_tx --; @@ -317,21 +325,43 @@ void esp_eth_free_rx_buf(void *buf) { xSemaphoreTakeRecursive( emac_rx_xMutex, ( TickType_t ) portMAX_DELAY ); - emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.cur_rx]),(uint32_t) buf); + emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.cur_rx]), (uint32_t) buf); emac_config.cur_rx = (emac_config.cur_rx + 1) % DMA_RX_BUF_NUM; emac_config.cnt_rx--; - if(emac_config.cnt_rx < 0) { - ESP_LOGE(TAG, "emac rx buf err!!\n"); + if (emac_config.cnt_rx < 0) { + ESP_LOGE(TAG, "emac rx buf err!!\n"); } emac_poll_rx_cmd(); xSemaphoreGiveRecursive( emac_rx_xMutex ); + + if (emac_config.emac_flow_ctrl_partner_support == true) { + portENTER_CRITICAL(&g_emac_mux); + if (pause_send == true && emac_config.cnt_rx < FLOW_CONTROL_LOW_WATERMARK) { + emac_send_pause_zero_frame_enable(); + pause_send = false; + } + portEXIT_CRITICAL(&g_emac_mux); + } +} + +static uint32_t IRAM_ATTR emac_get_rxbuf_count_in_intr(void) +{ + uint32_t cnt = 0; + uint32_t cur_rx_desc = emac_read_rx_cur_reg(); + struct dma_extended_desc *cur_desc = (struct dma_extended_desc *)cur_rx_desc; + + while (cur_desc->basic.desc0 == EMAC_DESC_RX_OWN) { + cnt++; + cur_desc = (struct dma_extended_desc *)cur_desc->basic.desc3; + } + return cnt; } #if CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE static void emac_process_rx(void) { - if(emac_config.emac_status == EMAC_RUNTIME_STOP) { + if (emac_config.emac_status == EMAC_RUNTIME_STOP) { return; } uint32_t cur_rx_desc = emac_read_rx_cur_reg(); @@ -341,7 +371,7 @@ static void emac_process_rx(void) emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2), (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); - emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.dirty_rx]),(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2)); + emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.dirty_rx]), (emac_config.dma_erx[emac_config.dirty_rx].basic.desc2)); emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; //if open this ,one intr can do many intrs ? @@ -353,14 +383,14 @@ static void emac_process_rx(void) static void emac_process_rx_unavail(void) { - if(emac_config.emac_status == EMAC_RUNTIME_STOP) { + if (emac_config.emac_status == EMAC_RUNTIME_STOP) { return; } uint32_t dirty_cnt = 0; while (dirty_cnt < DMA_RX_BUF_NUM) { - if(emac_config.dma_erx[emac_config.dirty_rx].basic.desc0 == EMAC_DESC_RX_OWN) { + if (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0 == EMAC_DESC_RX_OWN) { break; } @@ -369,7 +399,7 @@ static void emac_process_rx_unavail(void) emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2), (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); - emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.dirty_rx]),(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2)); + emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.dirty_rx]), (emac_config.dma_erx[emac_config.dirty_rx].basic.desc2)); emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; } emac_enable_rx_intr(); @@ -380,7 +410,7 @@ static void emac_process_rx_unavail(void) #else static void emac_process_rx_unavail(void) { - if(emac_config.emac_status == EMAC_RUNTIME_STOP) { + if (emac_config.emac_status == EMAC_RUNTIME_STOP) { return; } @@ -388,11 +418,11 @@ static void emac_process_rx_unavail(void) while (emac_config.cnt_rx < DMA_RX_BUF_NUM) { - //copy data to lwip + //copy data to lwip emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2), (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); emac_config.cnt_rx++; - if(emac_config.cnt_rx > DMA_RX_BUF_NUM) { + if (emac_config.cnt_rx > DMA_RX_BUF_NUM) { ESP_LOGE(TAG, "emac rx unavail buf err !!\n"); } emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; @@ -404,7 +434,7 @@ static void emac_process_rx_unavail(void) static void emac_process_rx(void) { - if(emac_config.emac_status == EMAC_RUNTIME_STOP) { + if (emac_config.emac_status == EMAC_RUNTIME_STOP) { return; } @@ -412,16 +442,16 @@ static void emac_process_rx(void) xSemaphoreTakeRecursive( emac_rx_xMutex, ( TickType_t ) portMAX_DELAY ); - if(((uint32_t) &(emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) != cur_rx_desc)) { + if (((uint32_t) & (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) != cur_rx_desc)) { - while (((uint32_t) &(emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) != cur_rx_desc) && emac_config.cnt_rx < DMA_RX_BUF_NUM ) { + while (((uint32_t) & (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) != cur_rx_desc) && emac_config.cnt_rx < DMA_RX_BUF_NUM ) { //copy data to lwip emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2), - (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); + (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); emac_config.cnt_rx++; - if(emac_config.cnt_rx > DMA_RX_BUF_NUM ) { + if (emac_config.cnt_rx > DMA_RX_BUF_NUM ) { ESP_LOGE(TAG, "emac rx buf err!!\n"); } emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; @@ -429,16 +459,16 @@ static void emac_process_rx(void) cur_rx_desc = emac_read_rx_cur_reg(); } } else { - if(emac_config.cnt_rx < DMA_RX_BUF_NUM) { - if((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0 & EMAC_DESC_RX_OWN) == 0) { + if (emac_config.cnt_rx < DMA_RX_BUF_NUM) { + if ((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0 & EMAC_DESC_RX_OWN) == 0) { while (emac_config.cnt_rx < DMA_RX_BUF_NUM) { //copy data to lwip emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2), - (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); + (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); emac_config.cnt_rx++; - if(emac_config.cnt_rx > DMA_RX_BUF_NUM) { - ESP_LOGE(TAG,"emac rx buf err!!!\n"); + if (emac_config.cnt_rx > DMA_RX_BUF_NUM) { + ESP_LOGE(TAG, "emac rx buf err!!!\n"); } emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; @@ -462,12 +492,18 @@ static void IRAM_ATTR emac_process_intr(void *arg) if (event & EMAC_RECV_INT) { emac_disable_rx_intr(); + if (emac_config.emac_flow_ctrl_partner_support == true) { + if (emac_get_rxbuf_count_in_intr() < FLOW_CONTROL_HIGH_WATERMARK && pause_send == false ) { + pause_send = true; + emac_send_pause_frame_enable(); + } + } emac_post(SIG_EMAC_RX_DONE, 0); } if (event & EMAC_RECV_BUF_UNAVAIL) { emac_disable_rx_unavail_intr(); - emac_post(SIG_EMAC_RX_UNAVAIL,0); + emac_post(SIG_EMAC_RX_UNAVAIL, 0); } if (event & EMAC_TRANS_INT) { @@ -475,25 +511,43 @@ static void IRAM_ATTR emac_process_intr(void *arg) } } +static void emac_set_macaddr_reg(void) +{ + REG_SET_FIELD(EMAC_GMACADDR0HIGH_REG, EMAC_MAC_ADDRESS0_HI, (emac_config.macaddr[0] << 8) | (emac_config.macaddr[1])); + REG_WRITE(EMAC_GMACADDR0LOW_REG, (emac_config.macaddr[2] << 24) | (emac_config.macaddr[3] << 16) | (emac_config.macaddr[4] << 8) | (emac_config.macaddr[5])); +} + static void emac_check_phy_init(void) { emac_config.emac_phy_check_init(); - if(emac_config.emac_phy_get_duplex_mode() == ETH_MDOE_FULLDUPLEX) { + if (emac_config.emac_phy_get_duplex_mode() == ETH_MDOE_FULLDUPLEX) { REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACDUPLEX); } else { REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACDUPLEX); } - if(emac_config.emac_phy_get_speed_mode() == ETH_SPEED_MODE_100M) { + if (emac_config.emac_phy_get_speed_mode() == ETH_SPEED_MODE_100M) { REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACFESPEED); } else { REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACFESPEED); } - - emac_mac_init(); + if (emac_config.emac_flow_ctrl_enable == true) { + if (emac_config.emac_phy_get_partner_pause_enable() == true && emac_config.emac_phy_get_duplex_mode() == ETH_MDOE_FULLDUPLEX) { + emac_enable_flowctrl(); + emac_config.emac_flow_ctrl_partner_support = true; + } else { + emac_disable_flowctrl(); + emac_config.emac_flow_ctrl_partner_support = false; + } + } else { + emac_disable_flowctrl(); + emac_config.emac_flow_ctrl_partner_support = false; + } + emac_mac_enable_txrx(); } static void emac_process_link_updown(bool link_status) { system_event_t evt; + uint8_t i = 0; emac_config.phy_link_up = link_status; @@ -502,6 +556,10 @@ static void emac_process_link_updown(bool link_status) ESP_LOGI(TAG, "eth link_up!!!"); emac_enable_dma_tx(); emac_enable_dma_rx(); + for (i = 0; i < PHY_LINK_CHECK_NUM; i++) { + emac_check_phy_init(); + } + evt.event_id = SYSTEM_EVENT_ETH_CONNECTED; } else { ESP_LOGI(TAG, "eth link_down!!!"); @@ -534,7 +592,7 @@ esp_err_t esp_eth_tx(uint8_t *buf, uint16_t size) } xSemaphoreTakeRecursive( emac_tx_xMutex, ( TickType_t ) portMAX_DELAY ); - if (emac_config.cnt_tx == DMA_TX_BUF_NUM -1) { + if (emac_config.cnt_tx == DMA_TX_BUF_NUM - 1) { ESP_LOGD(TAG, "tx buf full"); ret = ERR_MEM; goto _exit; @@ -557,13 +615,13 @@ _exit: static void emac_init_default_data(void) { - memset((uint8_t *)&emac_config, 0,sizeof(struct emac_config_data)); + memset((uint8_t *)&emac_config, 0, sizeof(struct emac_config_data)); } -void emac_process_link_check(void) +void emac_process_link_check(void) { if (emac_config.emac_status != EMAC_RUNTIME_START || - emac_config.emac_status == EMAC_RUNTIME_NOT_INIT) { + emac_config.emac_status == EMAC_RUNTIME_NOT_INIT) { return; } @@ -580,7 +638,7 @@ void emac_process_link_check(void) void emac_link_check_func(void *pv_parameters) { - emac_post(SIG_EMAC_CHECK_LINK,0); + emac_post(SIG_EMAC_CHECK_LINK, 0); } static bool emac_link_check_timer_init(void) @@ -634,10 +692,13 @@ static void emac_start(void *param) emac_check_mac_addr(); emac_set_mac_addr(); + emac_set_macaddr_reg(); emac_set_tx_base_reg(); emac_set_rx_base_reg(); + emac_mac_init(); + emac_config.phy_init(); //ptp TODO @@ -818,7 +879,7 @@ void emac_task(void *pv) esp_err_t IRAM_ATTR emac_post(emac_sig_t sig, emac_par_t par) { - if(sig <= SIG_EMAC_RX_DONE) { + if (sig <= SIG_EMAC_RX_DONE) { if (emac_sig_cnt[sig]) { return ESP_OK; } else { @@ -831,11 +892,11 @@ esp_err_t IRAM_ATTR emac_post(emac_sig_t sig, emac_par_t par) ret = xQueueSendFromISR(emac_xqueue, &evt, &tmp); - if(tmp != pdFALSE) { + if (tmp != pdFALSE) { portYIELD_FROM_ISR(); } - if(ret != pdPASS) { + if (ret != pdPASS) { return ESP_FAIL; } } diff --git a/components/ethernet/include/esp_eth.h b/components/ethernet/include/esp_eth.h index 2aae9d2024..e36c3ebfe6 100644 --- a/components/ethernet/include/esp_eth.h +++ b/components/ethernet/include/esp_eth.h @@ -79,6 +79,7 @@ typedef eth_duplex_mode_t (*eth_phy_get_duplex_mode_func)(void); typedef void (*eth_phy_func)(void); typedef esp_err_t (*eth_tcpip_input_func)(void *buffer, uint16_t len, void *eb); typedef void (*eth_gpio_config_func)(void); +typedef bool (*eth_phy_get_partner_pause_enable_func)(void); /** @@ -95,6 +96,9 @@ typedef struct { eth_phy_get_speed_mode_func phy_get_speed_mode; /*!< phy check init func */ eth_phy_get_duplex_mode_func phy_get_duplex_mode; /*!< phy check init func */ eth_gpio_config_func gpio_config; /*!< gpio config func */ + bool flow_ctrl_enable; /*!< flag of flow ctrl enable */ + eth_phy_get_partner_pause_enable_func phy_get_partner_pause_enable; /*!< get partner pause enable */ + } eth_config_t; /** diff --git a/docs/api/esp_eth.rst b/docs/api/esp_eth.rst index 1fcb1c2351..371aa5b233 100644 --- a/docs/api/esp_eth.rst +++ b/docs/api/esp_eth.rst @@ -28,6 +28,7 @@ Type Definitions .. doxygentypedef:: eth_phy_func .. doxygentypedef:: eth_tcpip_input_func .. doxygentypedef:: eth_gpio_config_func +.. doxygentypedef:: eth_phy_get_partner_pause_enable_func Enumerations ^^^^^^^^^^^^ diff --git a/examples/17_ethernet/main/ethernet_main.c b/examples/17_ethernet/main/ethernet_main.c index fc4347f7d8..57c6f97357 100644 --- a/examples/17_ethernet/main/ethernet_main.c +++ b/examples/17_ethernet/main/ethernet_main.c @@ -71,6 +71,22 @@ bool phy_tlk110_check_phy_link_status(void) return ((esp_eth_smi_read(BASIC_MODE_STATUS_REG) & LINK_STATUS) == LINK_STATUS ); } +bool phy_tlk110_get_partner_pause_enable(void) +{ + if((esp_eth_smi_read(PHY_LINK_PARTNER_ABILITY_REG) & PARTNER_PAUSE) == PARTNER_PAUSE) { + return true; + } else { + return false; + } +} + +void phy_enable_flow_ctrl(void) +{ + uint32_t data = 0; + data = esp_eth_smi_read(AUTO_NEG_ADVERTISEMENT_REG); + esp_eth_smi_write(AUTO_NEG_ADVERTISEMENT_REG,data|ASM_DIR|PAUSE); +} + void phy_tlk110_init(void) { esp_eth_smi_write(PHY_RESET_CONTROL_REG, SOFTWARE_RESET); @@ -78,8 +94,12 @@ void phy_tlk110_init(void) while (esp_eth_smi_read(PHY_IDENTIFIER_REG) != OUI_MSB_21TO6_DEF) { } - esp_eth_smi_write(SOFTWARE_STAP_CONTROL_REG, DEFAULT_PHY_CONFIG |SW_STRAP_CONFIG_DONE); + esp_eth_smi_write(SOFTWARE_STRAP_CONTROL_REG, DEFAULT_PHY_CONFIG |SW_STRAP_CONFIG_DONE); + ets_delay_us(300); + + //if config.flow_ctrl_enable == true ,enable this + phy_enable_flow_ctrl(); } void eth_gpio_config_rmii(void) @@ -140,6 +160,9 @@ void app_main() config.phy_check_link = phy_tlk110_check_phy_link_status; config.phy_get_speed_mode = phy_tlk110_get_speed_mode; config.phy_get_duplex_mode = phy_tlk110_get_duplex_mode; + //Only FULLDUPLEX mode support flow ctrl now! + config.flow_ctrl_enable = true; + config.phy_get_partner_pause_enable = phy_tlk110_get_partner_pause_enable; ret = esp_eth_init(&config); diff --git a/examples/17_ethernet/main/tlk110_phy.h b/examples/17_ethernet/main/tlk110_phy.h index 5f2ca644dc..a21bc57aea 100644 --- a/examples/17_ethernet/main/tlk110_phy.h +++ b/examples/17_ethernet/main/tlk110_phy.h @@ -5,7 +5,15 @@ #define PHY_IDENTIFIER_REG (0x2) #define OUI_MSB_21TO6_DEF 0x2000 -#define SOFTWARE_STAP_CONTROL_REG (0x9) +#define AUTO_NEG_ADVERTISEMENT_REG (0x4) +#define ASM_DIR BIT(11) +#define PAUSE BIT(10) + +#define PHY_LINK_PARTNER_ABILITY_REG (0x5) +#define PARTNER_ASM_DIR BIT(11) +#define PARTNER_PAUSE BIT(10) + +#define SOFTWARE_STRAP_CONTROL_REG (0x9) #define SW_STRAP_CONFIG_DONE BIT(15) #define AUTO_MDIX_ENABLE BIT(14) #define AUTO_NEGOTIATION_ENABLE BIT(13) From 2e06c6ba382cda6313441b46610a30be17153b56 Mon Sep 17 00:00:00 2001 From: shangke Date: Tue, 10 Jan 2017 21:54:53 +0800 Subject: [PATCH 114/167] dhcp: fix dhcp err when wifi and ethernet coexist --- components/tcpip_adapter/tcpip_adapter_lwip.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index 03f364fe67..b2725e3447 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -529,7 +529,12 @@ static void tcpip_adapter_dhcpc_cb(struct netif *netif) } if ( !ip4_addr_cmp(ip_2_ip4(&netif->ip_addr), IP4_ADDR_ANY) ) { - tcpip_adapter_ip_info_t *ip_info = &esp_ip[TCPIP_ADAPTER_IF_STA]; + tcpip_adapter_ip_info_t *ip_info = NULL; + if( netif == esp_netif[TCPIP_ADAPTER_IF_STA] ) { + ip_info = &esp_ip[TCPIP_ADAPTER_IF_STA]; + } else if(netif == esp_netif[TCPIP_ADAPTER_IF_ETH] ) { + ip_info = &esp_ip[TCPIP_ADAPTER_IF_ETH]; + } //check whether IP is changed if ( !ip4_addr_cmp(ip_2_ip4(&netif->ip_addr), &ip_info->ip) || From 89e0ecc2720b5edad45a706d7c9d67b06ab5bcde Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 11 Jan 2017 00:24:50 +0800 Subject: [PATCH 115/167] build system: add IDF_VER environment variable and preprocessor define --- components/bootloader/src/main/bootloader_config.h | 1 - components/bootloader/src/main/bootloader_start.c | 2 +- docs/build_system.rst | 9 +++++++++ make/project.mk | 6 +++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/components/bootloader/src/main/bootloader_config.h b/components/bootloader/src/main/bootloader_config.h index d097083d2a..6b37899ac3 100644 --- a/components/bootloader/src/main/bootloader_config.h +++ b/components/bootloader/src/main/bootloader_config.h @@ -23,7 +23,6 @@ extern "C" #include "esp_flash_data_types.h" -#define BOOT_VERSION "V0.1" #define SPI_SEC_SIZE 0x1000 #define IROM_LOW 0x400D0000 #define IROM_HIGH 0x40400000 diff --git a/components/bootloader/src/main/bootloader_start.c b/components/bootloader/src/main/bootloader_start.c index e5fda9cf53..102e70c3de 100644 --- a/components/bootloader/src/main/bootloader_start.c +++ b/components/bootloader/src/main/bootloader_start.c @@ -233,7 +233,7 @@ static bool ota_select_valid(const esp_ota_select_entry_t *s) void bootloader_main() { uart_console_configure(); - ESP_LOGI(TAG, "Espressif ESP32 2nd stage bootloader v. %s", BOOT_VERSION); + ESP_LOGI(TAG, "ESP-IDF %s 2nd stage bootloader", IDF_VER); #if defined(CONFIG_SECURE_BOOT_ENABLED) || defined(CONFIG_FLASH_ENCRYPTION_ENABLED) esp_err_t err; #endif diff --git a/docs/build_system.rst b/docs/build_system.rst index 2e388bd495..7a33a41256 100644 --- a/docs/build_system.rst +++ b/docs/build_system.rst @@ -166,6 +166,7 @@ The following variables are set at the project level, but exported for use in th - ``CONFIG_*``: Each value in the project configuration has a corresponding variable available in make. All names begin with ``CONFIG_``. - ``CC``, ``LD``, ``AR``, ``OBJCOPY``: Full paths to each tool from the gcc xtensa cross-toolchain. - ``HOSTCC``, ``HOSTLD``, ``HOSTAR``: Full names of each tool from the host native toolchain. +- ``IDF_VER``: Git version of ESP-IDF (produced by ``git describe``) If you modify any of these variables inside ``component.mk`` then this will not prevent other components from building but it may make your component hard to build and/or debug. @@ -267,6 +268,14 @@ To create a component KConfig file, it is easiest to start with one of the KConf For an example, see `Adding conditional configuration`. +Preprocessor Definitions +------------------------ + +ESP-IDF build systems adds the following C preprocessor definitions on the command line: + +- ``ESP_PLATFORM`` — Can be used to detect that build happens within ESP-IDF. +- ``IDF_VER`` — Defined to a git version string. E.g. ``v2.0`` for a tagged release or ``v1.0-275-g0efaa4f`` for an arbitrary commit. + Build Process Internals ----------------------- diff --git a/make/project.mk b/make/project.mk index e2a93ce119..6d9feaa7c4 100644 --- a/make/project.mk +++ b/make/project.mk @@ -176,6 +176,10 @@ else endif @echo $(ESPTOOLPY_WRITE_FLASH) $(ESPTOOL_ALL_FLASH_ARGS) + +# Git version of ESP-IDF (of the form v1.0-285-g5c4f707) +IDF_VER := $(shell git describe) + # Set default LDFLAGS LDFLAGS ?= -nostdlib \ @@ -200,7 +204,7 @@ LDFLAGS ?= -nostdlib \ # CPPFLAGS used by C preprocessor # If any flags are defined in application Makefile, add them at the end. -CPPFLAGS := -DESP_PLATFORM -MMD -MP $(CPPFLAGS) $(EXTRA_CPPFLAGS) +CPPFLAGS := -DESP_PLATFORM -D IDF_VER=\"$(IDF_VER)\" -MMD -MP $(CPPFLAGS) $(EXTRA_CPPFLAGS) # Warnings-related flags relevant both for C and C++ COMMON_WARNING_FLAGS = -Wall -Werror=all \ From 833102cbf325c1a50b8a6a1a6f4ccd6af45bdf65 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 11 Jan 2017 00:52:45 +0800 Subject: [PATCH 116/167] esp32: place cross-core interrupt handler into IRAM esp_intr_alloc is called with ESP_INTR_FLAG_IRAM, so the ISR should be in IRAM. --- components/esp32/crosscore_int.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/crosscore_int.c b/components/esp32/crosscore_int.c index f7ea4f6a74..b58d3d62c5 100644 --- a/components/esp32/crosscore_int.c +++ b/components/esp32/crosscore_int.c @@ -44,7 +44,7 @@ static volatile uint32_t reason[ portNUM_PROCESSORS ]; ToDo: There is a small chance the CPU already has yielded when this ISR is serviced. In that case, it's running the intended task but the ISR will cause it to switch _away_ from it. portYIELD_FROM_ISR will probably just schedule the task again, but have to check that. */ -static void esp_crosscore_isr(void *arg) { +static void IRAM_ATTR esp_crosscore_isr(void *arg) { uint32_t myReasonVal; //A pointer to the correct reason array item is passed to this ISR. volatile uint32_t *myReason=arg; From 6c86586e975eb262d28332b148c655cd0be542e5 Mon Sep 17 00:00:00 2001 From: Liu Han Date: Wed, 7 Dec 2016 17:34:26 +0800 Subject: [PATCH 117/167] components/lwip: Set the ping target info Add API for set the ping target info and get the ping result --- components/lwip/apps/ping/esp_ping.c | 131 +++++++ components/lwip/apps/ping/esp_ping.h | 94 +++++ components/lwip/apps/ping/ping.c | 366 +++++++++++++++++++ components/lwip/apps/ping/ping.h | 56 +++ components/lwip/component.mk | 4 +- components/lwip/include/lwip/port/lwipopts.h | 25 ++ 6 files changed, 674 insertions(+), 2 deletions(-) create mode 100644 components/lwip/apps/ping/esp_ping.c create mode 100644 components/lwip/apps/ping/esp_ping.h create mode 100644 components/lwip/apps/ping/ping.c create mode 100644 components/lwip/apps/ping/ping.h mode change 100755 => 100644 components/lwip/include/lwip/port/lwipopts.h diff --git a/components/lwip/apps/ping/esp_ping.c b/components/lwip/apps/ping/esp_ping.c new file mode 100644 index 0000000000..56ab6a985a --- /dev/null +++ b/components/lwip/apps/ping/esp_ping.c @@ -0,0 +1,131 @@ +// 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 +#include "esp_ping.h" + +#include "lwip/ip_addr.h" + +typedef struct _ping_option { + ip4_addr_t ping_target; + uint32_t ping_count; + uint32_t ping_rcv_timeout; + uint32_t ping_delay; + uint16_t ping_id; + esp_ping_found_fn ping_res_fn; + esp_ping_found ping_res; + void *ping_reserve; +} ping_option; + +static ping_option ping_option_info[1]; + +esp_err_t esp_ping_set_target(ping_target_id_t opt_id, void *opt_val, uint32_t opt_len) +{ + esp_err_t ret = ESP_OK; + + if (opt_val == NULL) { + return ESP_ERR_PING_INVALID_PARAMS; + } + + switch (opt_id) { + case PING_TARGET_IP_ADDRESS: + ESP_PING_CHECK_OPTLEN(opt_len, uint32_t); + ping_option_info->ping_target.addr = *(uint32_t *)opt_val; + break; + case PING_TARGET_IP_ADDRESS_COUNT: + ESP_PING_CHECK_OPTLEN(opt_len, uint32_t); + ping_option_info->ping_count = *(uint32_t *)opt_val; + break; + case PING_TARGET_RCV_TIMEO: + ESP_PING_CHECK_OPTLEN(opt_len, uint32_t); + ping_option_info->ping_rcv_timeout = (*(uint32_t *)opt_val) * 1000; + break; + case PING_TARGET_DELAY_TIME: + ESP_PING_CHECK_OPTLEN(opt_len, uint32_t); + ping_option_info->ping_delay = (*(uint32_t *)opt_val) * 1000; + break; + case PING_TARGET_ID: + ESP_PING_CHECK_OPTLEN(opt_len, uint16_t); + ping_option_info->ping_id = *(uint16_t *)opt_val; + break; + case PING_TARGET_RES_FN: + ping_option_info->ping_res_fn = opt_val; + break; + default: + ret = ESP_ERR_PING_INVALID_PARAMS; + break; + } + + return ret; +} + +esp_err_t esp_ping_get_target(ping_target_id_t opt_id, void *opt_val, uint32_t opt_len) +{ + esp_err_t ret = ESP_OK; + + if (opt_val == NULL) { + return ESP_ERR_PING_INVALID_PARAMS; + } + + switch (opt_id) { + case PING_TARGET_IP_ADDRESS: + ESP_PING_CHECK_OPTLEN(opt_len, uint32_t); + *(uint32_t *)opt_val = ping_option_info->ping_target.addr; + break; + case PING_TARGET_IP_ADDRESS_COUNT: + ESP_PING_CHECK_OPTLEN(opt_len, uint32_t); + *(uint32_t *)opt_val = ping_option_info->ping_count; + break; + case PING_TARGET_RCV_TIMEO: + ESP_PING_CHECK_OPTLEN(opt_len, uint32_t); + *(uint32_t *)opt_val = ping_option_info->ping_rcv_timeout; + break; + case PING_TARGET_DELAY_TIME: + ESP_PING_CHECK_OPTLEN(opt_len, uint32_t); + *(uint32_t *)opt_val = ping_option_info->ping_delay; + break; + case PING_TARGET_ID: + ESP_PING_CHECK_OPTLEN(opt_len, uint16_t); + *(uint16_t *)opt_val = ping_option_info->ping_id; + break; + default: + ret = ESP_ERR_PING_INVALID_PARAMS; + break; + } + + return ret; +} + +esp_err_t esp_ping_result(uint8_t res_val, uint16_t ping_len, uint32_t ping_time) +{ + esp_err_t ret = ESP_OK; + + ping_option_info->ping_res.bytes = ping_len; + ping_option_info->ping_res.ping_err = res_val; + ping_option_info->ping_res.resp_time = ping_time; + ping_option_info->ping_res.total_time += ping_time; + ping_option_info->ping_res.total_bytes += ping_len; + + if (res_val == 0) { + ping_option_info->ping_res.timeout_count ++; + } + + if (--ping_option_info->ping_count != 0 && ping_option_info->ping_res_fn) { + ping_option_info->ping_res_fn(PING_TARGET_RES_FN, &ping_option_info->ping_res); + } else { + memset(&ping_option_info->ping_res, 0, sizeof(esp_ping_found)); + } + + return ret; +} diff --git a/components/lwip/apps/ping/esp_ping.h b/components/lwip/apps/ping/esp_ping.h new file mode 100644 index 0000000000..ba9bec2d63 --- /dev/null +++ b/components/lwip/apps/ping/esp_ping.h @@ -0,0 +1,94 @@ +// 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 ESP_PING_H_ +#define ESP_PING_H_ + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_ERR_PING_BASE 0x5000 + +#define ESP_ERR_PING_INVALID_PARAMS ESP_ERR_PING_BASE + 0x00 +#define ESP_ERR_PING_NO_MEM ESP_ERR_PING_BASE + 0x01 + +#define ESP_PING_CHECK_OPTLEN(optlen, opttype) do { if ((optlen) < sizeof(opttype)) { return ESP_ERR_PING_INVALID_PARAMS; }}while(0) + +typedef struct _ping_found { + uint32_t resp_time; + uint32_t timeout_count; + uint32_t bytes; + uint32_t total_bytes; + uint32_t total_time; + int8_t ping_err; +} esp_ping_found; + +typedef enum { + PING_TARGET_IP_ADDRESS = 50, /**< target IP address */ + PING_TARGET_IP_ADDRESS_COUNT = 51, /**< target IP address total counter */ + PING_TARGET_RCV_TIMEO = 52, /**< receive timeout */ + PING_TARGET_DELAY_TIME = 53, /**< delay time */ + PING_TARGET_ID = 54, /**< identifier */ + PING_TARGET_RES_FN = 55 +} ping_target_id_t; + +typedef void (* esp_ping_found_fn)(ping_target_id_t found_id, esp_ping_found *found_val); + +/** + * @brief Set PING function option + * + * @param[in] opt_id: option index, 50 for IP, 51 for COUNT, 52 for RCV TIMEOUT, 53 for DELAY TIME, 54 for ID + * @param[in] opt_val: option parameter + * @param[in] opt_len: option length + * + * @return + * - ESP_OK + * - ESP_ERR_PING_INVALID_PARAMS + */ +esp_err_t esp_ping_set_target(ping_target_id_t opt_id, void *opt_val, uint32_t opt_len); + +/** + * @brief Get PING function option + * + * @param[in] opt_id: option index, 50 for IP, 51 for COUNT, 52 for RCV TIMEOUT, 53 for DELAY TIME, 54 for ID + * @param[in] opt_val: option parameter + * @param[in] opt_len: option length + * + * @return + * - ESP_OK + * - ESP_ERR_PING_INVALID_PARAMS + */ +esp_err_t esp_ping_get_target(ping_target_id_t opt_id, void *opt_val, uint32_t opt_len); + +/** + * @brief Get PING function result action + * + * @param[in] res_val: ping function action, 1 for successful, 0 for fail. + * res_len: response bytes + * res_time: response time + * + * @return + * - ESP_OK + * - ESP_ERR_PING_INVALID_PARAMS + */ +esp_err_t esp_ping_result(uint8_t res_val, uint16_t res_len, uint32_t res_time); + +#ifdef __cplusplus +} +#endif + +#endif /* ESP_PING_H_ */ diff --git a/components/lwip/apps/ping/ping.c b/components/lwip/apps/ping/ping.c new file mode 100644 index 0000000000..014d4c6574 --- /dev/null +++ b/components/lwip/apps/ping/ping.c @@ -0,0 +1,366 @@ +/** + * @file + * Ping sender module + * + */ + +/* + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + */ + +/** + * This is an example of a "ping" sender (with raw API and socket API). + * It can be used as a start point to maintain opened a network connection, or + * like a network "watchdog" for your device. + * + */ + +#include "lwip/opt.h" + +#if LWIP_IPV4 && LWIP_RAW /* don't build if not configured for use in lwipopts.h */ + +#include "ping.h" + +#include "lwip/mem.h" +#include "lwip/raw.h" +#include "lwip/icmp.h" +#include "lwip/netif.h" +#include "lwip/sys.h" +#include "lwip/timers.h" +#include "lwip/inet_chksum.h" + +#if PING_USE_SOCKETS +#include "lwip/sockets.h" +#include "lwip/inet.h" +#endif /* PING_USE_SOCKETS */ + +#ifdef ESP_LWIP +#include "esp_ping.h" +#include "lwip/ip_addr.h" +#endif +/** + * PING_DEBUG: Enable debugging for PING. + */ +#ifndef PING_DEBUG +#define PING_DEBUG LWIP_DBG_ON +#endif + +/** ping target - should be an "ip4_addr_t" */ +#ifndef PING_TARGET +#define PING_TARGET (netif_default ? *netif_ip4_gw(netif_default) : (*IP4_ADDR_ANY)) +#endif + +/** ping receive timeout - in milliseconds */ +#ifndef PING_RCV_TIMEO +#define PING_RCV_TIMEO 1000 +#endif + +/** ping delay - in milliseconds */ +#ifndef PING_DELAY +#define PING_DELAY 1000 +#endif + +/** ping identifier - must fit on a u16_t */ +#ifndef PING_ID +#define PING_ID 0xAFAF +#endif + +/** ping additional data size to include in the packet */ +#ifndef PING_DATA_SIZE +#define PING_DATA_SIZE 32 +#endif + +/** ping result action - no default action */ +#ifndef PING_RESULT +#define PING_RESULT(ping_ok) +#endif + +/* ping variables */ +static u16_t ping_seq_num; +static u32_t ping_time; +#if !PING_USE_SOCKETS +static struct raw_pcb *ping_pcb; +#endif /* PING_USE_SOCKETS */ + +/** Prepare a echo ICMP request */ +static void +ping_prepare_echo( struct icmp_echo_hdr *iecho, u16_t len) +{ + size_t i; + size_t data_len = len - sizeof(struct icmp_echo_hdr); + + ICMPH_TYPE_SET(iecho, ICMP_ECHO); + ICMPH_CODE_SET(iecho, 0); + iecho->chksum = 0; + iecho->id = PING_ID; + iecho->seqno = lwip_htons(++ping_seq_num); + + /* fill the additional data buffer with some data */ + for(i = 0; i < data_len; i++) { + ((char*)iecho)[sizeof(struct icmp_echo_hdr) + i] = (char)i; + } + + iecho->chksum = inet_chksum(iecho, len); +} + +#if PING_USE_SOCKETS + +/* Ping using the socket ip */ +static err_t +ping_send(int s, ip_addr_t *addr) +{ + int err; + struct icmp_echo_hdr *iecho; + struct sockaddr_in to; + size_t ping_size = sizeof(struct icmp_echo_hdr) + PING_DATA_SIZE; + LWIP_ASSERT("ping_size is too big", ping_size <= 0xffff); + LWIP_ASSERT("ping: expect IPv4 address", !IP_IS_V6(addr)); + + iecho = (struct icmp_echo_hdr *)mem_malloc((mem_size_t)ping_size); + if (!iecho) { + return ERR_MEM; + } + + ping_prepare_echo(iecho, (u16_t)ping_size); + + to.sin_len = sizeof(to); + 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)); + + mem_free(iecho); + + return (err ? ERR_OK : ERR_VAL); +} + +static void +ping_recv(int s) +{ + char buf[64]; + int len; + struct sockaddr_in from; + struct ip_hdr *iphdr; + struct icmp_echo_hdr *iecho; + int fromlen = sizeof(from); + + while((len = lwip_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 */ + LWIP_DEBUGF( PING_DEBUG, ("ping: invalid sin_family\n")); + } else { + ip4_addr_t fromaddr; + inet_addr_to_ipaddr(&fromaddr, &from.sin_addr); + LWIP_DEBUGF( PING_DEBUG, ("ping: recv ")); + ip4_addr_debug_print(PING_DEBUG, &fromaddr); + LWIP_DEBUGF( PING_DEBUG, (" %"U32_F" ms\n", (sys_now() - ping_time))); + + iphdr = (struct ip_hdr *)buf; + iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4)); + if ((iecho->id == PING_ID) && (iecho->seqno == lwip_htons(ping_seq_num))) { + /* do some ping result processing */ +#ifdef ESP_LWIP + esp_ping_result((ICMPH_TYPE(iecho) == ICMP_ER), len, (sys_now() - ping_time)); +#else + PING_RESULT((ICMPH_TYPE(iecho) == ICMP_ER)); +#endif + return; + } else { + LWIP_DEBUGF( PING_DEBUG, ("ping: drop\n")); + } + } + } + fromlen = sizeof(from); + } + + if (len == 0) { + LWIP_DEBUGF( PING_DEBUG, ("ping: recv - %"U32_F" ms - timeout\n", (sys_now()-ping_time))); + } + + /* do some ping result processing */ +#ifdef ESP_LWIP + esp_ping_result(0, len, (sys_now()-ping_time)); +#else + PING_RESULT(0); +#endif +} + +static void +ping_thread(void *arg) +{ + int s; + int ret; + ip_addr_t ping_target; +#if LWIP_SO_SNDRCVTIMEO_NONSTANDARD + int timeout = PING_RCV_TIMEO; +#else + struct timeval timeout; + timeout.tv_sec = PING_RCV_TIMEO/1000; + timeout.tv_usec = (PING_RCV_TIMEO%1000)*1000; +#endif + LWIP_UNUSED_ARG(arg); + + if ((s = lwip_socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) { + return; + } + + ret = lwip_setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + LWIP_ASSERT("setting receive timeout failed", ret == 0); + LWIP_UNUSED_ARG(ret); + + while (1) { +#ifdef ESP_LWIP + ip4_addr_t ipaddr; + esp_ping_get_target(PING_TARGET_IP_ADDRESS, &ipaddr.addr, sizeof(uint32_t)); + ip_addr_copy_from_ip4(ping_target, ipaddr); +#else + ip_addr_copy_from_ip4(ping_target, PING_TARGET); +#endif + + if (ping_send(s, &ping_target) == ERR_OK) { + LWIP_DEBUGF( PING_DEBUG, ("ping: send ")); + ip_addr_debug_print(PING_DEBUG, &ping_target); + LWIP_DEBUGF( PING_DEBUG, ("\n")); + + ping_time = sys_now(); + ping_recv(s); + } else { + LWIP_DEBUGF( PING_DEBUG, ("ping: send ")); + ip_addr_debug_print(PING_DEBUG, &ping_target); + LWIP_DEBUGF( PING_DEBUG, (" - error\n")); + } + sys_msleep(PING_DELAY); + } +} + +#else /* PING_USE_SOCKETS */ + +/* Ping using the raw ip */ +static u8_t +ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr) +{ + struct icmp_echo_hdr *iecho; + LWIP_UNUSED_ARG(arg); + LWIP_UNUSED_ARG(pcb); + LWIP_UNUSED_ARG(addr); + LWIP_ASSERT("p != NULL", p != NULL); + + if ((p->tot_len >= (PBUF_IP_HLEN + sizeof(struct icmp_echo_hdr))) && + 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))) { + LWIP_DEBUGF( PING_DEBUG, ("ping: recv ")); + ip_addr_debug_print(PING_DEBUG, addr); + LWIP_DEBUGF( PING_DEBUG, (" %"U32_F" ms\n", (sys_now()-ping_time))); + + /* do some ping result processing */ + PING_RESULT(1); + pbuf_free(p); + return 1; /* eat the packet */ + } + /* not eaten, restore original packet */ + pbuf_header(p, PBUF_IP_HLEN); + } + + return 0; /* don't eat the packet */ +} + +static void +ping_send(struct raw_pcb *raw, ip_addr_t *addr) +{ + struct pbuf *p; + struct icmp_echo_hdr *iecho; + size_t ping_size = sizeof(struct icmp_echo_hdr) + PING_DATA_SIZE; + + LWIP_DEBUGF( PING_DEBUG, ("ping: send ")); + ip_addr_debug_print(PING_DEBUG, addr); + LWIP_DEBUGF( PING_DEBUG, ("\n")); + LWIP_ASSERT("ping_size <= 0xffff", ping_size <= 0xffff); + + p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM); + if (!p) { + return; + } + if ((p->len == p->tot_len) && (p->next == NULL)) { + iecho = (struct icmp_echo_hdr *)p->payload; + + ping_prepare_echo(iecho, (u16_t)ping_size); + + raw_sendto(raw, p, addr); + ping_time = sys_now(); + } + pbuf_free(p); +} + +static void +ping_timeout(void *arg) +{ + struct raw_pcb *pcb = (struct raw_pcb*)arg; + ip_addr_t ping_target; + + LWIP_ASSERT("ping_timeout: no pcb given!", pcb != NULL); + + ip_addr_copy_from_ip4(ping_target, PING_TARGET); + ping_send(pcb, &ping_target); + + sys_timeout(PING_DELAY, ping_timeout, pcb); +} + +static void +ping_raw_init(void) +{ + ping_pcb = raw_new(IP_PROTO_ICMP); + LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL); + + raw_recv(ping_pcb, ping_recv, NULL); + raw_bind(ping_pcb, IP_ADDR_ANY); + sys_timeout(PING_DELAY, ping_timeout, ping_pcb); +} + +void +ping_send_now(void) +{ + ip_addr_t ping_target; + LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL); + ip_addr_copy_from_ip4(ping_target, PING_TARGET); + ping_send(ping_pcb, &ping_target); +} + +#endif /* PING_USE_SOCKETS */ + +void +ping_init(void) +{ +#if PING_USE_SOCKETS + sys_thread_new("ping_thread", ping_thread, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO); +#else /* PING_USE_SOCKETS */ + ping_raw_init(); +#endif /* PING_USE_SOCKETS */ +} + +#endif /* LWIP_IPV4 && LWIP_RAW */ diff --git a/components/lwip/apps/ping/ping.h b/components/lwip/apps/ping/ping.h new file mode 100644 index 0000000000..fa2f7aae16 --- /dev/null +++ b/components/lwip/apps/ping/ping.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2001-2004 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + */ + +#ifndef LWIP_PING_H +#define LWIP_PING_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * PING_USE_SOCKETS: Set to 1 to use sockets, otherwise the raw api is used + */ +#ifndef PING_USE_SOCKETS +#define PING_USE_SOCKETS LWIP_SOCKET +#endif + + +void ping_init(void); + +#if !PING_USE_SOCKETS +void ping_send_now(void); +#endif /* !PING_USE_SOCKETS */ + +#ifdef __cplusplus +} +#endif + +#endif /* LWIP_PING_H */ diff --git a/components/lwip/component.mk b/components/lwip/component.mk index df116f1a06..9444d229e8 100644 --- a/components/lwip/component.mk +++ b/components/lwip/component.mk @@ -2,9 +2,9 @@ # Component Makefile # -COMPONENT_ADD_INCLUDEDIRS := include/lwip include/lwip/port include/lwip/posix +COMPONENT_ADD_INCLUDEDIRS := include/lwip include/lwip/port include/lwip/posix apps/ping -COMPONENT_SRCDIRS := api apps/sntp apps core/ipv4 core/ipv6 core netif port/freertos port/netif port +COMPONENT_SRCDIRS := api apps/sntp apps/ping apps core/ipv4 core/ipv6 core netif port/freertos port/netif port CFLAGS += -Wno-address # lots of LWIP source files evaluate macros that check address of stack variables diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h old mode 100755 new mode 100644 index 14ec0e67a4..6d1dfbd497 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -189,6 +189,10 @@ ---------- RAW options ---------- --------------------------------- */ +/** + * LWIP_RAW==1: Enable application layer to hook into the IP layer itself. + */ +#define LWIP_RAW 1 /* ---------------------------------- @@ -402,6 +406,27 @@ */ #define DEFAULT_ACCEPTMBOX_SIZE 6 +/** + * DEFAULT_THREAD_STACKSIZE: The stack size used by any other lwIP thread. + * The stack size value itself is platform-dependent, but is passed to + * sys_thread_new() when the thread is created. + */ +#define DEFAULT_THREAD_STACKSIZE TCPIP_THREAD_STACKSIZE + +/** + * DEFAULT_THREAD_PRIO: The priority assigned to any other lwIP thread. + * The priority value itself is platform-dependent, but is passed to + * sys_thread_new() when the thread is created. + */ +#define DEFAULT_THREAD_PRIO TCPIP_THREAD_PRIO + +/** + * DEFAULT_RAW_RECVMBOX_SIZE: The mailbox size for the incoming packets on a + * NETCONN_RAW. The queue size value itself is platform-dependent, but is passed + * to sys_mbox_new() when the recvmbox is created. + */ +#define DEFAULT_RAW_RECVMBOX_SIZE 6 + /* ---------------------------------------------- ---------- Sequential layer options ---------- From 59540eeae12fd2dcbcc1ad6346d026c56fa12b34 Mon Sep 17 00:00:00 2001 From: wangmengyang Date: Wed, 11 Jan 2017 11:04:41 +0800 Subject: [PATCH 118/167] component/bt: bug fix of lack of checking bluetooth stack status inside API functions --- components/bt/bluedroid/api/esp_blufi_api.c | 16 ++++ components/bt/bluedroid/api/esp_bt_device.c | 4 + components/bt/bluedroid/api/esp_bt_main.c | 15 +++- components/bt/bluedroid/api/esp_gap_ble_api.c | 58 +++++++++++++-- components/bt/bluedroid/api/esp_gattc_api.c | 73 +++++++++++++++++++ components/bt/bluedroid/api/esp_gatts_api.c | 56 ++++++++++++++ .../bt/bluedroid/api/include/esp_bt_device.h | 2 +- .../bt/bluedroid/api/include/esp_bt_main.h | 17 +++++ components/bt/bluedroid/btc/core/btc_main.c | 4 +- docs/api/bt.rst | 1 - docs/api/esp_blufi.rst | 6 +- docs/api/esp_bt_main.rst | 3 + 12 files changed, 242 insertions(+), 13 deletions(-) diff --git a/components/bt/bluedroid/api/esp_blufi_api.c b/components/bt/bluedroid/api/esp_blufi_api.c index 094fbfae5f..ac84ee3fc2 100644 --- a/components/bt/bluedroid/api/esp_blufi_api.c +++ b/components/bt/bluedroid/api/esp_blufi_api.c @@ -24,6 +24,10 @@ esp_err_t esp_blufi_register_callbacks(esp_blufi_callbacks_t *callbacks) { + if (ESP_BLUEDROID_STATUS_UNINITIALIZED == esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + if (callbacks == NULL) { return ESP_FAIL; } @@ -37,6 +41,10 @@ esp_err_t esp_blufi_send_wifi_conn_report(wifi_mode_t opmode, esp_blufi_sta_conn btc_msg_t msg; btc_blufi_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_BLUFI; msg.act = BTC_BLUFI_ACT_SEND_CFG_REPORT; @@ -53,6 +61,10 @@ esp_err_t esp_blufi_profile_init(void) { btc_msg_t msg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_BLUFI; msg.act = BTC_BLUFI_ACT_INIT; @@ -64,6 +76,10 @@ esp_err_t esp_blufi_profile_deinit(void) { btc_msg_t msg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_BLUFI; msg.act = BTC_BLUFI_ACT_DEINIT; diff --git a/components/bt/bluedroid/api/esp_bt_device.c b/components/bt/bluedroid/api/esp_bt_device.c index 745e446c23..1eb48c98ac 100644 --- a/components/bt/bluedroid/api/esp_bt_device.c +++ b/components/bt/bluedroid/api/esp_bt_device.c @@ -14,9 +14,13 @@ #include "esp_bt_device.h" +#include "esp_bt_main.h" #include "controller.h" const uint8_t *esp_bt_dev_get_address(void) { + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return NULL; + } return controller_get_interface()->get_address()->address; } diff --git a/components/bt/bluedroid/api/esp_bt_main.c b/components/bt/bluedroid/api/esp_bt_main.c index 2dd9166d22..3093b56403 100644 --- a/components/bt/bluedroid/api/esp_bt_main.c +++ b/components/bt/bluedroid/api/esp_bt_main.c @@ -21,6 +21,19 @@ static bool esp_already_enable = false; static bool esp_already_init = false; +esp_bluedroid_status_t esp_bluedroid_get_status(void) +{ + if (esp_already_init) { + if (esp_already_enable) { + return ESP_BLUEDROID_STATUS_ENABLED; + } else { + return ESP_BLUEDROID_STATUS_INITIALIZED; + } + } else { + return ESP_BLUEDROID_STATUS_UNINITIALIZED; + } +} + esp_err_t esp_bluedroid_enable(void) { btc_msg_t msg; @@ -114,7 +127,7 @@ esp_err_t esp_bluedroid_init(void) return ESP_FAIL; } - esp_already_init = true;; + esp_already_init = true; return ESP_OK; } diff --git a/components/bt/bluedroid/api/esp_gap_ble_api.c b/components/bt/bluedroid/api/esp_gap_ble_api.c index 6e18f65822..34914f8b4d 100644 --- a/components/bt/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/bluedroid/api/esp_gap_ble_api.c @@ -14,6 +14,7 @@ #include +#include "esp_bt_main.h" #include "esp_gap_ble_api.h" #include "bta_api.h" #include "bt_trace.h" @@ -23,6 +24,9 @@ esp_err_t esp_ble_gap_register_callback(esp_gap_ble_cb_t callback) { + if (ESP_BLUEDROID_STATUS_UNINITIALIZED == esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } return (btc_profile_cb_set(BTC_PID_GAP_BLE, callback) == 0 ? ESP_OK : ESP_FAIL); } @@ -31,7 +35,11 @@ esp_err_t esp_ble_gap_config_adv_data(esp_ble_adv_data_t *adv_data) { btc_msg_t msg; btc_ble_gap_args_t arg; - + + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + if (adv_data == NULL) { return ESP_ERR_INVALID_ARG; } @@ -54,7 +62,11 @@ esp_err_t esp_ble_gap_set_scan_params(esp_ble_scan_params_t *scan_params) { btc_msg_t msg; btc_ble_gap_args_t arg; - + + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + if (scan_params == NULL) { return ESP_ERR_INVALID_ARG; } @@ -72,6 +84,10 @@ esp_err_t esp_ble_gap_start_scanning(uint32_t duration) btc_msg_t msg; btc_ble_gap_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_START_SCAN; @@ -84,7 +100,11 @@ esp_err_t esp_ble_gap_start_scanning(uint32_t duration) esp_err_t esp_ble_gap_stop_scanning(void) { btc_msg_t msg; - + + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_STOP_SCAN; @@ -96,6 +116,10 @@ esp_err_t esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params) btc_msg_t msg; btc_ble_gap_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_START_ADV; @@ -108,6 +132,10 @@ esp_err_t esp_ble_gap_stop_advertising(void) { btc_msg_t msg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_STOP_ADV; @@ -121,6 +149,10 @@ esp_err_t esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params) btc_msg_t msg; btc_ble_gap_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_UPDATE_CONN_PARAM; @@ -133,7 +165,11 @@ esp_err_t esp_ble_gap_set_pkt_data_len(esp_bd_addr_t remote_device, uint16_t tx_ { btc_msg_t msg; btc_ble_gap_args_t arg; - + + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_SET_PKT_DATA_LEN; @@ -148,7 +184,11 @@ esp_err_t esp_ble_gap_set_rand_addr(esp_bd_addr_t rand_addr) { btc_msg_t msg; btc_ble_gap_args_t arg; - + + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_SET_RAND_ADDRESS; @@ -163,6 +203,10 @@ esp_err_t esp_ble_gap_config_local_privacy (bool privacy_enable) btc_msg_t msg; btc_ble_gap_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_CONFIG_LOCAL_PRIVACY; @@ -176,6 +220,10 @@ esp_err_t esp_ble_gap_set_device_name(const char *name) btc_msg_t msg; btc_ble_gap_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + if (strlen(name) > ESP_GAP_DEVICE_NAME_MAX) { return ESP_ERR_INVALID_ARG; } diff --git a/components/bt/bluedroid/api/esp_gattc_api.c b/components/bt/bluedroid/api/esp_gattc_api.c index 28fc429575..4baa0a77fc 100644 --- a/components/bt/bluedroid/api/esp_gattc_api.c +++ b/components/bt/bluedroid/api/esp_gattc_api.c @@ -15,12 +15,17 @@ #include #include "esp_gattc_api.h" +#include "esp_bt_main.h" #include "btc_manage.h" #include "btc_gattc.h" #include "btc_gatt_util.h" esp_err_t esp_ble_gattc_register_callback(esp_gattc_cb_t callback) { + if (ESP_BLUEDROID_STATUS_UNINITIALIZED == esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + if (callback == NULL) { return ESP_FAIL; } @@ -34,6 +39,10 @@ esp_err_t esp_ble_gattc_app_register(uint16_t app_id) btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + if (app_id > ESP_APP_ID_MAX) { return ESP_ERR_INVALID_ARG; } @@ -51,6 +60,10 @@ esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gattc_if) btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_APP_UNREGISTER; @@ -64,6 +77,10 @@ esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, b btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_OPEN; @@ -79,6 +96,10 @@ esp_err_t esp_ble_gattc_close (esp_gatt_if_t gattc_if, uint16_t conn_id) btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_CLOSE; @@ -92,6 +113,10 @@ esp_err_t esp_ble_gattc_config_mtu (esp_gatt_if_t gattc_if, uint16_t conn_id, ui btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + if ((mtu < ESP_GATT_DEF_BLE_MTU_SIZE) || (mtu > ESP_GATT_MAX_MTU_SIZE)) { return ESP_GATT_ILLEGAL_PARAMETER; } @@ -110,6 +135,10 @@ esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_SEARCH_SERVICE; @@ -133,6 +162,10 @@ esp_err_t esp_ble_gattc_get_characteristic(esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; if (start_char_id) { @@ -158,6 +191,10 @@ esp_err_t esp_ble_gattc_get_descriptor(esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; @@ -185,6 +222,10 @@ esp_err_t esp_ble_gattc_get_included_service(esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; @@ -211,6 +252,10 @@ esp_err_t esp_ble_gattc_read_char (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_READ_CHAR; @@ -232,6 +277,10 @@ esp_err_t esp_ble_gattc_read_char_descr (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_READ_CHAR_DESCR; @@ -256,6 +305,10 @@ esp_err_t esp_ble_gattc_write_char( esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_WRITE_CHAR; @@ -283,6 +336,10 @@ esp_err_t esp_ble_gattc_write_char_descr (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_WRITE_CHAR_DESCR; @@ -311,6 +368,10 @@ esp_err_t esp_ble_gattc_prepare_write(esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_PREPARE_WRITE; @@ -330,6 +391,10 @@ esp_err_t esp_ble_gattc_execute_write (esp_gatt_if_t gattc_if, uint16_t conn_id, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_EXECUTE_WRITE; @@ -347,6 +412,10 @@ esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_REG_FOR_NOTIFY; @@ -366,6 +435,10 @@ esp_gatt_status_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_UNREG_FOR_NOTIFY; diff --git a/components/bt/bluedroid/api/esp_gatts_api.c b/components/bt/bluedroid/api/esp_gatts_api.c index 2504e58f8f..1f0d668e5a 100644 --- a/components/bt/bluedroid/api/esp_gatts_api.c +++ b/components/bt/bluedroid/api/esp_gatts_api.c @@ -15,6 +15,7 @@ #include "string.h" #include "esp_gatt_defs.h" #include "esp_gatts_api.h" +#include "esp_bt_main.h" #include "btc_manage.h" #include "btc_gatts.h" #include "btc_gatt_util.h" @@ -23,6 +24,9 @@ esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback) { + if (ESP_BLUEDROID_STATUS_UNINITIALIZED == esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } return (btc_profile_cb_set(BTC_PID_GATTS, callback) == 0 ? ESP_OK : ESP_FAIL); } @@ -31,6 +35,10 @@ esp_err_t esp_ble_gatts_app_register(uint16_t app_id) btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + //if (app_id < ESP_APP_ID_MIN || app_id > ESP_APP_ID_MAX) { if (app_id > ESP_APP_ID_MAX) { return ESP_ERR_INVALID_ARG; @@ -50,6 +58,10 @@ esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatts_if) btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_APP_UNREGISTER; @@ -64,6 +76,10 @@ esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_CREATE_SERVICE; @@ -80,6 +96,10 @@ esp_err_t esp_ble_gatts_add_included_service(uint16_t service_handle, uint16_t i btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_ADD_INCLUDE_SERVICE; @@ -96,6 +116,10 @@ esp_err_t esp_ble_gatts_add_char(uint16_t service_handle, esp_bt_uuid_t *char_ btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_ADD_CHAR; @@ -115,6 +139,10 @@ esp_err_t esp_ble_gatts_add_char_descr (uint16_t service_handle, btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_ADD_CHAR_DESCR; @@ -130,6 +158,10 @@ esp_err_t esp_ble_gatts_delete_service(uint16_t service_handle) btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_DELETE_SERVICE; @@ -143,6 +175,10 @@ esp_err_t esp_ble_gatts_start_service(uint16_t service_handle) btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_START_SERVICE; @@ -156,6 +192,10 @@ esp_err_t esp_ble_gatts_stop_service(uint16_t service_handle) btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_STOP_SERVICE; @@ -171,6 +211,10 @@ esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_SEND_INDICATE; @@ -189,6 +233,10 @@ esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_SEND_RESPONSE; @@ -205,6 +253,10 @@ esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatts_if, esp_bd_addr_t remote_bda, b btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_OPEN; @@ -220,6 +272,10 @@ esp_err_t esp_ble_gatts_close(esp_gatt_if_t gatts_if, uint16_t conn_id) btc_msg_t msg; btc_ble_gatts_args_t arg; + if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + return ESP_ERR_INVALID_STATE; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_CLOSE; diff --git a/components/bt/bluedroid/api/include/esp_bt_device.h b/components/bt/bluedroid/api/include/esp_bt_device.h index 51c24f2881..c84d042d66 100644 --- a/components/bt/bluedroid/api/include/esp_bt_device.h +++ b/components/bt/bluedroid/api/include/esp_bt_device.h @@ -26,7 +26,7 @@ extern "C" { * * @brief Get bluetooth device address. Must use after "esp_bluedroid_enable". * - * @return bluetooth device address (six bytes) + * @return bluetooth device address (six bytes), or NULL if bluetooth stack is not enabled */ const uint8_t *esp_bt_dev_get_address(void); diff --git a/components/bt/bluedroid/api/include/esp_bt_main.h b/components/bt/bluedroid/api/include/esp_bt_main.h index b24daf33cd..fad010d2c2 100644 --- a/components/bt/bluedroid/api/include/esp_bt_main.h +++ b/components/bt/bluedroid/api/include/esp_bt_main.h @@ -21,6 +21,23 @@ extern "C" { #endif +/** + * @brief Bluetooth stack status type, to indicate whether the bluetooth stack is ready + */ +typedef enum { + ESP_BLUEDROID_STATUS_UNINITIALIZED = 0, /*!< Bluetooth not initialized */ + ESP_BLUEDROID_STATUS_INITIALIZED, /*!< Bluetooth initialized but not enabled */ + ESP_BLUEDROID_STATUS_ENABLED /*!< Bluetooth initialized and enabled */ +} esp_bluedroid_status_t; + +/** + * @brief Get bluetooth stack status + * + * @return Bluetooth stack status + * + */ +esp_bluedroid_status_t esp_bluedroid_get_status(void); + /** * @brief Enable bluetooth, must after esp_bluedroid_init() * diff --git a/components/bt/bluedroid/btc/core/btc_main.c b/components/bt/bluedroid/btc/core/btc_main.c index 6fae3af435..323a81f75a 100644 --- a/components/bt/bluedroid/btc/core/btc_main.c +++ b/components/bt/bluedroid/btc/core/btc_main.c @@ -42,14 +42,14 @@ static void btc_sec_callback(tBTA_DM_SEC_EVT event, tBTA_DM_SEC *p_data) static void btc_enable_bluetooth(void) { if (BTA_EnableBluetooth(btc_sec_callback) != BTA_SUCCESS) { - future_ready(*btc_main_get_future_p(BTC_MAIN_ENABLE_FUTURE), FUTURE_SUCCESS); + future_ready(*btc_main_get_future_p(BTC_MAIN_ENABLE_FUTURE), FUTURE_FAIL); } } static void btc_disable_bluetooth(void) { if (BTA_DisableBluetooth() != BTA_SUCCESS) { - future_ready(*btc_main_get_future_p(BTC_MAIN_DISABLE_FUTURE), FUTURE_SUCCESS); + future_ready(*btc_main_get_future_p(BTC_MAIN_DISABLE_FUTURE), FUTURE_FAIL); } } diff --git a/docs/api/bt.rst b/docs/api/bt.rst index 6a14d40684..2eae5dd4cf 100644 --- a/docs/api/bt.rst +++ b/docs/api/bt.rst @@ -6,5 +6,4 @@ Bluetooth Bluetooth Controller && VHCI Bluetooth Common - Bluetooth Classic Bluetooth LE diff --git a/docs/api/esp_blufi.rst b/docs/api/esp_blufi.rst index f620edeb83..9dd8085166 100644 --- a/docs/api/esp_blufi.rst +++ b/docs/api/esp_blufi.rst @@ -5,9 +5,9 @@ Overview -------- BLUFI is a profile based GATT to config ESP32 WIFI to connect/disconnect AP or setup a softap and etc. Use should concern these things: - 1. The event sent from profile. Then you need to do something as the event indicate. - 2. Security reference. You can write your own Security functions such as symmetrical encryption/decryption and checksum functions. - Even you can define the "Key Exchange/Negotiation" procedure. +1. The event sent from profile. Then you need to do something as the event indicate. +2. Security reference. You can write your own Security functions such as symmetrical encryption/decryption and checksum functions. +Even you can define the "Key Exchange/Negotiation" procedure. Application Example ------------------- diff --git a/docs/api/esp_bt_main.rst b/docs/api/esp_bt_main.rst index cc6e80b2e5..48bb0c9cc0 100644 --- a/docs/api/esp_bt_main.rst +++ b/docs/api/esp_bt_main.rst @@ -34,6 +34,8 @@ Type Definitions Enumerations ^^^^^^^^^^^^ +.. doxygenenum:: esp_bluedroid_status_t + Structures ^^^^^^^^^^ @@ -42,6 +44,7 @@ Structures Functions ^^^^^^^^^ +.. doxygenfunction:: esp_bluedroid_get_status .. doxygenfunction:: esp_bluedroid_enable .. doxygenfunction:: esp_bluedroid_disable .. doxygenfunction:: esp_bluedroid_init From ee59fa75f4edaf5124369a7751382ece9c6e32ef Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 11 Jan 2017 11:25:56 +0800 Subject: [PATCH 119/167] Rename SPI Master IO pins to more common terminology, add better explanation to queue_length initialization value --- components/driver/include/driver/spi_master.h | 12 +-- components/driver/spi_master.c | 78 +++++++++---------- components/driver/test/test_spi_master.c | 12 ++- examples/26_spi_master/main/spi_master.c | 10 +-- 4 files changed, 55 insertions(+), 57 deletions(-) diff --git a/components/driver/include/driver/spi_master.h b/components/driver/include/driver/spi_master.h index ea901b9924..83110d9d05 100644 --- a/components/driver/include/driver/spi_master.h +++ b/components/driver/include/driver/spi_master.h @@ -46,11 +46,11 @@ typedef enum { * the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds. */ typedef struct { - int spid_io_num; ///< GPIO pin for spi_d (=MOSI)signal, or -1 if not used. - int spiq_io_num; ///< GPIO pin for spi_q (=MISO) signal, or -1 if not used. - int spiclk_io_num; ///< GPIO pin for spi_clk signal, or -1 if not used. - int spiwp_io_num; ///< GPIO pin for spi_wp signal, or -1 if not used. - int spihd_io_num; ///< GPIO pin for spi_hd signal, or -1 if not used. + int mosi_io_num; ///< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used. + int miso_io_num; ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used. + int sclk_io_num; ///< GPIO pin for Spi CLocK signal, or -1 if not used. + int quadwp_io_num; ///< GPIO pin for WP (Write Protect) signal which is used as D2 in 4-bit communication modes, or -1 if not used. + int quadhd_io_num; ///< GPIO pin for HD (HolD) signal which is used as D3 in 4-bit communication modes, or -1 if not used. } spi_bus_config_t; @@ -80,7 +80,7 @@ typedef struct { int clock_speed_hz; ///< Clock speed, in Hz int spics_io_num; ///< CS GPIO pin for this device, or -1 if not used uint32_t flags; ///< Bitwise OR of SPI_DEVICE_* flags - int queue_size; ///< Transaction queue size + int queue_size; ///< Transaction queue size. This sets how many transactions can be 'in the air' (queued using spi_device_queue_trans but not yet finished using spi_device_get_trans_result) at the same time transaction_cb_t pre_cb; ///< Callback to be called before a transmission is started. This callback is called within interrupt context. transaction_cb_t post_cb; ///< Callback to be called after a transmission has completed. This callback is called within interrupt context. } spi_device_interface_config_t; diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index 1f1ea1ef08..e9b8fb3cb0 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -200,11 +200,11 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, spi_bus_config_t *bus_confi SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG); SPI_CHECK(spihost[host]==NULL, "host already in use", ESP_ERR_INVALID_STATE); - SPI_CHECK(bus_config->spid_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spid_io_num), "spid pin invalid", ESP_ERR_INVALID_ARG); - SPI_CHECK(bus_config->spiclk_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spiclk_io_num), "spiclk pin invalid", ESP_ERR_INVALID_ARG); - SPI_CHECK(bus_config->spiq_io_num<0 || GPIO_IS_VALID_GPIO(bus_config->spiq_io_num), "spiq pin invalid", ESP_ERR_INVALID_ARG); - SPI_CHECK(bus_config->spiwp_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spiwp_io_num), "spiwp pin invalid", ESP_ERR_INVALID_ARG); - SPI_CHECK(bus_config->spihd_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->spihd_io_num), "spihd pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->mosi_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->mosi_io_num), "spid pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->sclk_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->sclk_io_num), "spiclk pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->miso_io_num<0 || GPIO_IS_VALID_GPIO(bus_config->miso_io_num), "spiq pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->quadwp_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadwp_io_num), "spiwp pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(bus_config->quadhd_io_num<0 || GPIO_IS_VALID_OUTPUT_GPIO(bus_config->quadhd_io_num), "spihd pin invalid", ESP_ERR_INVALID_ARG); //The host struct contains two dma descriptors, so we need DMA'able memory for this. spihost[host]=pvPortMallocCaps(sizeof(spi_host_t), MALLOC_CAP_DMA); @@ -212,51 +212,51 @@ esp_err_t spi_bus_initialize(spi_host_device_t host, spi_bus_config_t *bus_confi memset(spihost[host], 0, sizeof(spi_host_t)); //Check if the selected pins correspond to the native pins of the peripheral - if (bus_config->spid_io_num >= 0 && bus_config->spid_io_num!=io_signal[host].spid_native) native=false; - if (bus_config->spiq_io_num >= 0 && bus_config->spiq_io_num!=io_signal[host].spiq_native) native=false; - if (bus_config->spiclk_io_num >= 0 && bus_config->spiclk_io_num!=io_signal[host].spiclk_native) native=false; - if (bus_config->spiwp_io_num >= 0 && bus_config->spiwp_io_num!=io_signal[host].spiwp_native) native=false; - if (bus_config->spihd_io_num >= 0 && bus_config->spihd_io_num!=io_signal[host].spihd_native) native=false; + if (bus_config->mosi_io_num >= 0 && bus_config->mosi_io_num!=io_signal[host].spid_native) native=false; + if (bus_config->miso_io_num >= 0 && bus_config->miso_io_num!=io_signal[host].spiq_native) native=false; + if (bus_config->sclk_io_num >= 0 && bus_config->sclk_io_num!=io_signal[host].spiclk_native) native=false; + if (bus_config->quadwp_io_num >= 0 && bus_config->quadwp_io_num!=io_signal[host].spiwp_native) native=false; + if (bus_config->quadhd_io_num >= 0 && bus_config->quadhd_io_num!=io_signal[host].spihd_native) native=false; spihost[host]->no_gpio_matrix=native; 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->spid_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spid_io_num], 1); - if (bus_config->spiq_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiq_io_num], 1); - if (bus_config->spiwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiwp_io_num], 1); - if (bus_config->spihd_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spihd_io_num], 1); - if (bus_config->spiclk_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiclk_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 (bus_config->quadwp_io_num > 0) PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->quadwp_io_num], 1); + if (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->spid_io_num>0) { - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spid_io_num], PIN_FUNC_GPIO); - gpio_set_direction(bus_config->spid_io_num, GPIO_MODE_OUTPUT); - gpio_matrix_out(bus_config->spid_io_num, io_signal[host].spid_out, false, false); - gpio_matrix_in(bus_config->spid_io_num, io_signal[host].spid_in, false); + 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_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->spiq_io_num>0) { - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiq_io_num], PIN_FUNC_GPIO); - gpio_set_direction(bus_config->spiq_io_num, GPIO_MODE_INPUT); - gpio_matrix_out(bus_config->spiq_io_num, io_signal[host].spiq_out, false, false); - gpio_matrix_in(bus_config->spiq_io_num, io_signal[host].spiq_in, false); + 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); + 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 (bus_config->spiwp_io_num>0) { - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiwp_io_num], PIN_FUNC_GPIO); - gpio_set_direction(bus_config->spiwp_io_num, GPIO_MODE_OUTPUT); - gpio_matrix_out(bus_config->spiwp_io_num, io_signal[host].spiwp_out, false, false); - gpio_matrix_in(bus_config->spiwp_io_num, io_signal[host].spiwp_in, false); + if (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_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 (bus_config->spihd_io_num>0) { - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spihd_io_num], PIN_FUNC_GPIO); - gpio_set_direction(bus_config->spihd_io_num, GPIO_MODE_OUTPUT); - gpio_matrix_out(bus_config->spihd_io_num, io_signal[host].spihd_out, false, false); - gpio_matrix_in(bus_config->spihd_io_num, io_signal[host].spihd_in, false); + if (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_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->spiclk_io_num>0) { - PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[bus_config->spiclk_io_num], PIN_FUNC_GPIO); - gpio_set_direction(bus_config->spiclk_io_num, GPIO_MODE_OUTPUT); - gpio_matrix_out(bus_config->spiclk_io_num, io_signal[host].spiclk_out, false, false); + 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_OUTPUT); + gpio_matrix_out(bus_config->sclk_io_num, io_signal[host].spiclk_out, false, false); } } periph_module_enable(io_signal[host].module); diff --git a/components/driver/test/test_spi_master.c b/components/driver/test/test_spi_master.c index 0fd47cbdba..119c94bc57 100644 --- a/components/driver/test/test_spi_master.c +++ b/components/driver/test/test_spi_master.c @@ -21,11 +21,11 @@ TEST_CASE("SPI Master test", "[spi]") { spi_bus_config_t buscfg={ - .spid_io_num=4, - .spiq_io_num=16, - .spiclk_io_num=25, - .spiwp_io_num=-1, - .spihd_io_num=-1 + .mosi_io_num=4, + .miso_io_num=16, + .sclk_io_num=25, + .quadwp_io_num=-1, + .quadhd_io_num=-1 }; spi_device_interface_config_t devcfg={ .command_bits=8, @@ -33,8 +33,6 @@ TEST_CASE("SPI Master test", "[spi]") .dummy_bits=0, .clock_speed_hz=8000, .duty_cycle_pos=128, - .cs_ena_pretrans=7, - .cs_ena_posttrans=7, .mode=0, .spics_io_num=21, .queue_size=3 diff --git a/examples/26_spi_master/main/spi_master.c b/examples/26_spi_master/main/spi_master.c index 2013dfd9c9..577cada4c2 100644 --- a/examples/26_spi_master/main/spi_master.c +++ b/examples/26_spi_master/main/spi_master.c @@ -249,11 +249,11 @@ void app_main() esp_err_t ret; spi_device_handle_t spi; spi_bus_config_t buscfg={ - .spiq_io_num=PIN_NUM_MISO, - .spid_io_num=PIN_NUM_MOSI, - .spiclk_io_num=PIN_NUM_CLK, - .spiwp_io_num=-1, - .spihd_io_num=-1 + .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=10000000, //Clock out at 10 MHz From 0c31bdf643e1cdb67c2120407cd94453aa1d01c9 Mon Sep 17 00:00:00 2001 From: wangmengyang Date: Wed, 11 Jan 2017 11:31:35 +0800 Subject: [PATCH 120/167] component/bt: check for registration status of callback function before using it --- .../bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c | 7 ++++++- .../bt/bluedroid/btc/profile/std/gap/btc_gap_ble.c | 8 ++++++-- .../bt/bluedroid/btc/profile/std/gatt/btc_gattc.c | 8 +++++++- .../bt/bluedroid/btc/profile/std/gatt/btc_gatts.c | 13 +++++++++---- 4 files changed, 28 insertions(+), 8 deletions(-) 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 c126273a72..85be60b7b2 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c @@ -35,7 +35,12 @@ #include "esp_blufi_api.h" -#define BTC_BLUFI_CB_TO_APP(event, param) ((esp_blufi_event_cb_t)btc_profile_cb_get(BTC_PID_BLUFI))((event), (param)) +#define BTC_BLUFI_CB_TO_APP(event, param) do { \ + esp_blufi_event_cb_t btc_blufi_cb = (esp_blufi_event_cb_t)btc_profile_cb_get(BTC_PID_BLUFI); \ + if (btc_blufi_cb) { \ + btc_blufi_cb(event, param); \ + } \ + } while (0) #define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" #define BT_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] 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 ede530e772..e2f4815087 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 @@ -26,8 +26,12 @@ static tBTA_BLE_ADV_DATA gl_bta_adv_data; static tBTA_BLE_ADV_DATA gl_bta_scan_rsp_data; -#define BTC_GAP_BLE_CB_TO_APP(event, param) ((esp_gap_ble_cb_t)btc_profile_cb_get(BTC_PID_GAP_BLE))((event), (param)) - +#define BTC_GAP_BLE_CB_TO_APP(event, param) do { \ + esp_gap_ble_cb_t btc_gap_ble_cb = (esp_gap_ble_cb_t)btc_profile_cb_get(BTC_PID_GAP_BLE); \ + if (btc_gap_ble_cb) { \ + btc_gap_ble_cb(event, param); \ + } \ + } while (0) static void btc_gap_adv_point_cleanup(void **buf) { diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c index 9bc7b0d2bd..4b6947940f 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c @@ -22,7 +22,13 @@ #include "bt_trace.h" #include "esp_gattc_api.h" -#define BTC_GATTC_CB_TO_APP(event, gattc_if, param) ((esp_gattc_cb_t )btc_profile_cb_get(BTC_PID_GATTC))((event), (gattc_if), (param)) +#define BTC_GATTC_CB_TO_APP(event, gattc_if, param) do { \ + esp_gattc_cb_t btc_gattc_cb = (esp_gattc_cb_t )btc_profile_cb_get(BTC_PID_GATTC); \ + if (btc_gattc_cb) { \ + btc_gattc_cb(event, gattc_if, param); \ + } \ + } while (0) + void btc_gattc_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c index c7afabfa2a..3a988fc3b1 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c @@ -23,7 +23,12 @@ #include "esp_gatts_api.h" -#define BTC_GATTS_CB_TO_APP(event, gatts_if, param) ((esp_gatts_cb_t)btc_profile_cb_get(BTC_PID_GATTS))((event), (gatts_if), (param)) +#define BTC_GATTS_CB_TO_APP(event, gatts_if, param) do { \ + esp_gatts_cb_t btc_gatts_cb = (esp_gatts_cb_t)btc_profile_cb_get(BTC_PID_GATTS); \ + if (btc_gatts_cb) { \ + btc_gatts_cb(event, gatts_if, param); \ + } \ + } while (0) #define A2C_GATTS_EVT(_bta_event) (_bta_event) //BTA TO BTC EVT #define C2A_GATTS_EVT(_btc_event) (_btc_event) //BTC TO BTA EVT @@ -299,9 +304,9 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.read.conn_id = BTC_GATT_GET_CONN_ID(p_data->req_data.conn_id); param.read.trans_id = p_data->req_data.trans_id; memcpy(param.read.bda, p_data->req_data.remote_bda, ESP_BD_ADDR_LEN); - param.read.handle = p_data->req_data.p_data->read_req.handle, - param.read.offset = p_data->req_data.p_data->read_req.offset, - param.read.is_long = p_data->req_data.p_data->read_req.is_long, + param.read.handle = p_data->req_data.p_data->read_req.handle; + param.read.offset = p_data->req_data.p_data->read_req.offset; + param.read.is_long = p_data->req_data.p_data->read_req.is_long; BTC_GATTS_CB_TO_APP(ESP_GATTS_READ_EVT, gatts_if, ¶m); break; From a2e0c2432e4874a0346aa587885657217e965fcb Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 11 Jan 2017 01:14:18 +0800 Subject: [PATCH 121/167] esp32: sanity check ISR handler address passed into esp_intr_alloc Return ESP_ERR_INVALID_ARG if the handler is not in IRAM (or RTC fast memory) --- components/esp32/crosscore_int.c | 6 +++-- components/esp32/include/esp_intr_alloc.h | 5 ++++- components/esp32/intr_alloc.c | 6 +++++ components/esp32/test/test_intr_alloc.c | 27 +++++++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/components/esp32/crosscore_int.c b/components/esp32/crosscore_int.c index b58d3d62c5..1e131eeef7 100644 --- a/components/esp32/crosscore_int.c +++ b/components/esp32/crosscore_int.c @@ -73,11 +73,13 @@ void esp_crosscore_int_init() { portENTER_CRITICAL(&reasonSpinlock); reason[xPortGetCoreID()]=0; portEXIT_CRITICAL(&reasonSpinlock); + esp_err_t err; if (xPortGetCoreID()==0) { - esp_intr_alloc(ETS_FROM_CPU_INTR0_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()], NULL); + err = esp_intr_alloc(ETS_FROM_CPU_INTR0_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()], NULL); } else { - esp_intr_alloc(ETS_FROM_CPU_INTR1_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()], NULL); + err = esp_intr_alloc(ETS_FROM_CPU_INTR1_SOURCE, ESP_INTR_FLAG_IRAM, esp_crosscore_isr, (void*)&reason[xPortGetCoreID()], NULL); } + assert(err == ESP_OK); } void esp_crosscore_int_send_yield(int coreId) { diff --git a/components/esp32/include/esp_intr_alloc.h b/components/esp32/include/esp_intr_alloc.h index c1f91dd2e3..7195d07d87 100644 --- a/components/esp32/include/esp_intr_alloc.h +++ b/components/esp32/include/esp_intr_alloc.h @@ -124,6 +124,9 @@ esp_err_t esp_intr_reserve(int intno, int cpu); * * The interrupt will always be allocated on the core that runs this function. * + * If ESP_INTR_FLAG_IRAM flag is used, and handler address is not in IRAM or + * RTC_FAST_MEM, then ESP_ERR_INVALID_ARG is returned. + * * @param source The interrupt source. One of the ETS_*_INTR_SOURCE interrupt mux * sources, as defined in soc/soc.h, or one of the internal * ETS_INTERNAL_*_INTR_SOURCE sources as defined in this header. @@ -264,4 +267,4 @@ void esp_intr_noniram_enable(); } #endif -#endif \ No newline at end of file +#endif diff --git a/components/esp32/intr_alloc.c b/components/esp32/intr_alloc.c index 4fdda2f396..1cb2fba5d9 100644 --- a/components/esp32/intr_alloc.c +++ b/components/esp32/intr_alloc.c @@ -446,6 +446,12 @@ esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusre if ((flags&ESP_INTR_FLAG_SHARED) && (!handler || source<0)) return ESP_ERR_INVALID_ARG; //Statusreg should have a mask if (intrstatusreg && !intrstatusmask) return ESP_ERR_INVALID_ARG; + //If the ISR is marked to be IRAM-resident, the handler must not be in the cached region + if ((flags&ESP_INTR_FLAG_IRAM) && + (ptrdiff_t) handler >= 0x400C0000 && + (ptrdiff_t) handler < 0x50000000 ) { + return ESP_ERR_INVALID_ARG; + } //Default to prio 1 for shared interrupts. Default to prio 1, 2 or 3 for non-shared interrupts. if ((flags&ESP_INTR_FLAG_LEVELMASK)==0) { diff --git a/components/esp32/test/test_intr_alloc.c b/components/esp32/test/test_intr_alloc.c index 329fc9a9e4..a5e6f06834 100644 --- a/components/esp32/test/test_intr_alloc.c +++ b/components/esp32/test/test_intr_alloc.c @@ -201,3 +201,30 @@ TEST_CASE("Intr_alloc test, shared ints", "[esp32]") { timer_test(ESP_INTR_FLAG_SHARED); } + +TEST_CASE("Can allocate IRAM int only with an IRAM handler", "[esp32]") +{ + void dummy(void* arg) + { + } + IRAM_ATTR void dummy_iram(void* arg) + { + } + RTC_IRAM_ATTR void dummy_rtc(void* arg) + { + } + intr_handle_t ih; + esp_err_t err = esp_intr_alloc(ETS_INTERNAL_PROFILING_INTR_SOURCE, + ESP_INTR_FLAG_IRAM, &dummy, NULL, &ih); + TEST_ASSERT_EQUAL_INT(ESP_ERR_INVALID_ARG, err); + err = esp_intr_alloc(ETS_INTERNAL_PROFILING_INTR_SOURCE, + ESP_INTR_FLAG_IRAM, &dummy_iram, NULL, &ih); + TEST_ESP_OK(err); + err = esp_intr_free(ih); + TEST_ESP_OK(err); + err = esp_intr_alloc(ETS_INTERNAL_PROFILING_INTR_SOURCE, + ESP_INTR_FLAG_IRAM, &dummy_rtc, NULL, &ih); + TEST_ESP_OK(err); + err = esp_intr_free(ih); + TEST_ESP_OK(err); +} From 69a7cfa9766e6b712fd7aa2f2a9e356423b19fe2 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 11 Jan 2017 11:55:23 +0800 Subject: [PATCH 122/167] Also update documentation to new conventions --- docs/api/spi_master.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api/spi_master.rst b/docs/api/spi_master.rst index 4d7693c07c..5b57d85f42 100644 --- a/docs/api/spi_master.rst +++ b/docs/api/spi_master.rst @@ -27,18 +27,18 @@ The spi_master driver uses the following terms: now, only HSPI or VSPI are actually supported in the driver; it will support all 3 peripherals somewhere in the future.) * Bus: The SPI bus, common to all SPI devices connected to one host. In general the bus consists of the - spid, spiq, spiclk and optionally spiwp and spihd signals. The SPI slaves are connected to these + miso, mosi, sclk and optionally quadwp and quadhd signals. The SPI slaves are connected to these signals in parallel. - - spiq - Also known as MISO, this is the input of the serial stream into the ESP32 + - miso - Also known as q, this is the input of the serial stream into the ESP32 - - spid - Also known as MOSI, this is the output of the serial stream from the ESP32 + - mosi - Also known as d, this is the output of the serial stream from the ESP32 - - spiclk - Clock signal. Each data bit is clocked out or in on the positive or negative edge of this signal + - sclk - Clock signal. Each data bit is clocked out or in on the positive or negative edge of this signal - - spiwp - Write Protect signal. Only used for 4-bit (qio/qout) transactions. + - quadwp - Write Protect signal. Only used for 4-bit (qio/qout) transactions. - - spihd - Hold signal. Only used for 4-bit (qio/qout) transactions. + - quadhd - Hold signal. Only used for 4-bit (qio/qout) transactions. * Device: A SPI slave. Each SPI slave has its own chip select (CS) line, which is made active when a transmission to/from the SPI slave occurs. From b455b0246c09cc27589228b7222da8ada0a83f12 Mon Sep 17 00:00:00 2001 From: XiaXiaotian Date: Tue, 10 Jan 2017 17:50:32 +0800 Subject: [PATCH 123/167] add wpa2 set id api and modify null data tx description 1. wpa2: add wpa2 set id api 2. low rate: modify null data tx description --- components/esp32/include/esp_wpa2.h | 101 ++++++++++++++++++---------- components/esp32/lib | 2 +- 2 files changed, 66 insertions(+), 37 deletions(-) diff --git a/components/esp32/include/esp_wpa2.h b/components/esp32/include/esp_wpa2.h index 1857d19a16..e33e0e6dcf 100644 --- a/components/esp32/include/esp_wpa2.h +++ b/components/esp32/include/esp_wpa2.h @@ -24,35 +24,58 @@ extern "C" { /** * @brief Enable wpa2 enterprise authentication. * - * @attention wpa2 enterprise authentication can only be used when ESP32 station is enabled. - * wpa2 enterprise authentication can only support TLS, PEAP-MSCHAPv2 and TTLS-MSCHAPv2 method. + * @attention 1. wpa2 enterprise authentication can only be used when ESP32 station is enabled. + * @attention 2. wpa2 enterprise authentication can only support TLS, PEAP-MSCHAPv2 and TTLS-MSCHAPv2 method. * - * @return ESP_ERR_WIFI_OK: succeed. - * ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) + * @return + * - ESP_ERR_WIFI_OK: succeed. + * - ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) */ esp_err_t esp_wifi_sta_wpa2_ent_enable(void); /** * @brief Disable wpa2 enterprise authentication. * - * @attention wpa2 enterprise authentication can only be used when ESP32 station is enabled. - * wpa2 enterprise authentication can only support TLS, PEAP-MSCHAPv2 and TTLS-MSCHAPv2 method. + * @attention 1. wpa2 enterprise authentication can only be used when ESP32 station is enabled. + * @attention 2. wpa2 enterprise authentication can only support TLS, PEAP-MSCHAPv2 and TTLS-MSCHAPv2 method. * - * @return ESP_ERR_WIFI_OK: succeed. + * @return + * - ESP_ERR_WIFI_OK: succeed. */ esp_err_t esp_wifi_sta_wpa2_ent_disable(void); +/** + * @brief Set identity for PEAP/TTLS method. + * + * @attention The API only passes the parameter identity to the global pointer variable in wpa2 enterprise module. + * + * @param identity: point to address where stores the identity; + * @param len: length of identity, limited to 1~127 + * + * @return + * - ESP_ERR_WIFI_OK: succeed + * - ESP_ERR_WIFI_ARG: fail(len <= 0 or len >= 128) + * - ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) + */ +esp_err_t esp_wifi_sta_wpa2_ent_set_identity(unsigned char *identity, int len); + +/** + * @brief Clear identity for PEAP/TTLS method. + */ +void esp_wifi_sta_wpa2_ent_clear_identity(void); + /** * @brief Set username for PEAP/TTLS method. * * @attention The API only passes the parameter username to the global pointer variable in wpa2 enterprise module. * * @param username: point to address where stores the username; - * len: length of username, limited to 1~127 + * @param len: length of username, limited to 1~127 * - * @return ESP_ERR_WIFI_OK: succeed - * ESP_ERR_WIFI_ARG: fail(len <= 0 or len >= 128) - * ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) + * @return + * - ESP_ERR_WIFI_OK: succeed + * - ESP_ERR_WIFI_ARG: fail(len <= 0 or len >= 128) + * - ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) */ esp_err_t esp_wifi_sta_wpa2_ent_set_username(unsigned char *username, int len); @@ -67,11 +90,12 @@ void esp_wifi_sta_wpa2_ent_clear_username(void); * @attention The API only passes the parameter password to the global pointer variable in wpa2 enterprise module. * * @param password: point to address where stores the password; - * len: length of password(len > 0) + * @param len: length of password(len > 0) * - * @return ESP_ERR_WIFI_OK: succeed - * ESP_ERR_WIFI_ARG: fail(len <= 0) - * ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) + * @return + * - ESP_ERR_WIFI_OK: succeed + * - ESP_ERR_WIFI_ARG: fail(len <= 0) + * - ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) */ esp_err_t esp_wifi_sta_wpa2_ent_set_password(unsigned char *password, int len); @@ -83,15 +107,16 @@ void esp_wifi_sta_wpa2_ent_clear_password(void); /** * @brief Set new password for MSCHAPv2 method.. * - * @attention The API only passes the parameter password to the global pointer variable in wpa2 enterprise module. - * The new password is used to substitute the old password when eap-mschapv2 failure request message with error code ERROR_PASSWD_EXPIRED is received. + * @attention 1. The API only passes the parameter password to the global pointer variable in wpa2 enterprise module. + * @attention 2. The new password is used to substitute the old password when eap-mschapv2 failure request message with error code ERROR_PASSWD_EXPIRED is received. * * @param password: point to address where stores the password; - * len: length of password + * @param len: length of password * - * @return ESP_ERR_WIFI_OK: succeed - * ESP_ERR_WIFI_ARG: fail(len <= 0) - * ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) + * @return + * - ESP_ERR_WIFI_OK: succeed + * - ESP_ERR_WIFI_ARG: fail(len <= 0) + * - ESP_ERR_WIFI_NO_MEM: fail(internal memory malloc fail) */ esp_err_t esp_wifi_sta_wpa2_ent_set_new_password(unsigned char *password, int len); @@ -104,13 +129,14 @@ void esp_wifi_sta_wpa2_ent_clear_new_password(void); /** * @brief Set CA certificate for PEAP/TTLS method. * - * @attention The API only passes the parameter ca_cert to the global pointer variable in wpa2 enterprise module. - * The ca_cert should be zero terminated. + * @attention 1. The API only passes the parameter ca_cert to the global pointer variable in wpa2 enterprise module. + * @attention 2. The ca_cert should be zero terminated. * * @param ca_cert: point to address where stores the CA certificate; - * len: length of ca_cert + * @param len: length of ca_cert * - * @return ESP_ERR_WIFI_OK: succeed + * @return + * - ESP_ERR_WIFI_OK: succeed */ esp_err_t esp_wifi_sta_wpa2_ent_set_ca_cert(unsigned char *ca_cert, int len); @@ -122,17 +148,18 @@ void esp_wifi_sta_wpa2_ent_clear_ca_cert(void); /** * @brief Set client certificate and key. * - * @attention The API only passes the parameter client_cert, private_key and private_key_passwd to the global pointer variable in wpa2 enterprise module. - * The client_cert, private_key and private_key_passwd should be zero terminated. + * @attention 1. The API only passes the parameter client_cert, private_key and private_key_passwd to the global pointer variable in wpa2 enterprise module. + * @attention 2. The client_cert, private_key and private_key_passwd should be zero terminated. * * @param client_cert: point to address where stores the client certificate; - * client_cert_len: length of client certificate; - * private_key: point to address where stores the private key; - * private_key_len: length of private key, limited to 1~2048; - * private_key_password: point to address where stores the private key password; - * private_key_password_len: length of private key password; + * @param client_cert_len: length of client certificate; + * @param private_key: point to address where stores the private key; + * @param private_key_len: length of private key, limited to 1~2048; + * @param private_key_password: point to address where stores the private key password; + * @param private_key_password_len: length of private key password; * - * @return ESP_ERR_WIFI_OK: succeed + * @return + * - ESP_ERR_WIFI_OK: succeed */ esp_err_t esp_wifi_sta_wpa2_ent_set_cert_key(unsigned char *client_cert, int client_cert_len, unsigned char *private_key, int private_key_len, unsigned char *private_key_passwd, int private_key_passwd_len); @@ -145,9 +172,10 @@ void esp_wifi_sta_wpa2_ent_clear_cert_key(void); * @brief Set wpa2 enterprise certs time check(disable or not). * * @param true: disable wpa2 enterprise certs time check - * false: enable wpa2 enterprise certs time check + * @param false: enable wpa2 enterprise certs time check * - * @return ESP_OK: succeed + * @return + * - ESP_OK: succeed */ esp_err_t esp_wifi_sta_wpa2_ent_set_disable_time_check(bool disable); @@ -156,7 +184,8 @@ esp_err_t esp_wifi_sta_wpa2_ent_set_disable_time_check(bool disable); * * @param disable: store disable value * - * @return ESP_OK: succeed + * @return + * - ESP_OK: succeed */ esp_err_t esp_wifi_sta_wpa2_ent_get_disable_time_check(bool *disable); diff --git a/components/esp32/lib b/components/esp32/lib index 231ee92755..f688a5e1b2 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 231ee92755a41c92f6243e0550557ce9e1131744 +Subproject commit f688a5e1b2f5e4cb8dd2cdbd8dedf63a74b1d063 From a98d07d65027ea8e8e3bfc81203c6e8893aab869 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 11 Jan 2017 13:01:48 +0800 Subject: [PATCH 124/167] Fix clock divider calculation --- components/driver/spi_master.c | 60 ++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index e9b8fb3cb0..204e577eda 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -389,39 +389,51 @@ esp_err_t spi_bus_remove_device(spi_device_handle_t handle) return ESP_OK; } +static int spi_freq_for_pre_n(int fapb, int pre, int n) { + return (fapb / (pre * n)); +} + static void spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) { int pre, n, h, l; - //In hw, n, h and l are 1-32, pre is 0-8K. Value written to register is one lower than used value. - if (hz>(fapb/2)) { - //Can only solve this using fapb directly. + + //In hw, n, h and l are 1-32, pre is 1-8K. Value written to register is one lower than used value. + if (hz>((fapb/4)*3)) { + //Using Fapb directly will give us the best result here. hw->clock.clkcnt_l=0; hw->clock.clkcnt_h=0; hw->clock.clkcnt_n=0; hw->clock.clkdiv_pre=0; hw->clock.clk_equ_sysclk=1; } else { - //For best duty cycle resolution, we want n to be as close to 32 as possible. - //ToDo: - //This algo could use some tweaking; at the moment it either fixes n to 32 and - //uses the prescaler to get a suitable division factor, or sets the prescaler to 0 - //and uses n to set a value. In practice, sometimes a better result can be - //obtained by setting both n and pre to well-chosen valued... ToDo: fix up some algo to - //do this automatically (worst-case: bruteforce n/pre combo's) - JD - //Also ToDo: - //The ESP32 has a SPI_CK_OUT_HIGH_MODE and SPI_CK_OUT_LOW_MODE register; it looks like we can - //use those to specify the duty cycle in a more precise way. Figure out how to use these. - JD - n=(fapb/(hz*32)); - if (n>32) { - //Need to use prescaler - n=32; + //For best duty cycle resolution, we want n to be as close to 32 as possible, but + //we also need a pre/n combo that gets us as close as possible to the intended freq. + //To do this, we bruteforce n and calculate the best pre to go along with that. + //If there's a choice between pre/n combos that give the same result, use the one + //with the higher n. + int bestn=-1; + int bestpre=-1; + int besterr=hz; + int errval; + for (n=1; n<33; n++) { + //Effectively, this does pre=round((fapb/n)/hz). + pre=((fapb/n)+(hz/2))/hz; + if (pre<0) pre=0; + if (pre>8192) pre=8192; + errval=abs(spi_freq_for_pre_n(fapb, pre, n)-hz); + if (errval<=besterr) { + besterr=errval; + bestn=n; + bestpre=pre; + } } - if (n<32) { - //No need for prescaler. - n=(fapb/hz); - } - pre=(fapb/n)/hz; - h=n; - l=(((256-duty_cycle)*n+127)/256); + + n=bestn; + pre=bestpre; + l=n; + //This effectively does round((duty_cycle*n)/256) + h=(duty_cycle*n+127)/256; + if (h<=0) h=1; + hw->clock.clk_equ_sysclk=0; hw->clock.clkcnt_n=n-1; hw->clock.clkdiv_pre=pre-1; From c5927146791cd166edd86d8a4b30c78c14dc215c Mon Sep 17 00:00:00 2001 From: wangmengyang Date: Wed, 11 Jan 2017 13:36:48 +0800 Subject: [PATCH 125/167] component/bt: fix typos in "if" condition espressions --- components/bt/bluedroid/api/esp_blufi_api.c | 8 ++--- components/bt/bluedroid/api/esp_bt_device.c | 2 +- components/bt/bluedroid/api/esp_gap_ble_api.c | 30 ++++++++-------- components/bt/bluedroid/api/esp_gattc_api.c | 36 +++++++++---------- components/bt/bluedroid/api/esp_gatts_api.c | 28 +++++++-------- 5 files changed, 52 insertions(+), 52 deletions(-) diff --git a/components/bt/bluedroid/api/esp_blufi_api.c b/components/bt/bluedroid/api/esp_blufi_api.c index ac84ee3fc2..70f5c9ce8f 100644 --- a/components/bt/bluedroid/api/esp_blufi_api.c +++ b/components/bt/bluedroid/api/esp_blufi_api.c @@ -24,7 +24,7 @@ esp_err_t esp_blufi_register_callbacks(esp_blufi_callbacks_t *callbacks) { - if (ESP_BLUEDROID_STATUS_UNINITIALIZED == esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { return ESP_ERR_INVALID_STATE; } @@ -41,7 +41,7 @@ esp_err_t esp_blufi_send_wifi_conn_report(wifi_mode_t opmode, esp_blufi_sta_conn btc_msg_t msg; btc_blufi_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -61,7 +61,7 @@ esp_err_t esp_blufi_profile_init(void) { btc_msg_t msg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -76,7 +76,7 @@ esp_err_t esp_blufi_profile_deinit(void) { btc_msg_t msg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } diff --git a/components/bt/bluedroid/api/esp_bt_device.c b/components/bt/bluedroid/api/esp_bt_device.c index 1eb48c98ac..dffcbe80b2 100644 --- a/components/bt/bluedroid/api/esp_bt_device.c +++ b/components/bt/bluedroid/api/esp_bt_device.c @@ -19,7 +19,7 @@ const uint8_t *esp_bt_dev_get_address(void) { - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return NULL; } return controller_get_interface()->get_address()->address; diff --git a/components/bt/bluedroid/api/esp_gap_ble_api.c b/components/bt/bluedroid/api/esp_gap_ble_api.c index 34914f8b4d..f9401d8c42 100644 --- a/components/bt/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/bluedroid/api/esp_gap_ble_api.c @@ -24,7 +24,7 @@ esp_err_t esp_ble_gap_register_callback(esp_gap_ble_cb_t callback) { - if (ESP_BLUEDROID_STATUS_UNINITIALIZED == esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { return ESP_ERR_INVALID_STATE; } return (btc_profile_cb_set(BTC_PID_GAP_BLE, callback) == 0 ? ESP_OK : ESP_FAIL); @@ -36,7 +36,7 @@ esp_err_t esp_ble_gap_config_adv_data(esp_ble_adv_data_t *adv_data) btc_msg_t msg; btc_ble_gap_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -63,7 +63,7 @@ esp_err_t esp_ble_gap_set_scan_params(esp_ble_scan_params_t *scan_params) btc_msg_t msg; btc_ble_gap_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -84,7 +84,7 @@ esp_err_t esp_ble_gap_start_scanning(uint32_t duration) btc_msg_t msg; btc_ble_gap_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -100,8 +100,8 @@ esp_err_t esp_ble_gap_start_scanning(uint32_t duration) esp_err_t esp_ble_gap_stop_scanning(void) { btc_msg_t msg; - - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -116,7 +116,7 @@ esp_err_t esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params) btc_msg_t msg; btc_ble_gap_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -132,7 +132,7 @@ esp_err_t esp_ble_gap_stop_advertising(void) { btc_msg_t msg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -149,7 +149,7 @@ esp_err_t esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params) btc_msg_t msg; btc_ble_gap_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -165,8 +165,8 @@ esp_err_t esp_ble_gap_set_pkt_data_len(esp_bd_addr_t remote_device, uint16_t tx_ { btc_msg_t msg; btc_ble_gap_args_t arg; - - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -184,8 +184,8 @@ esp_err_t esp_ble_gap_set_rand_addr(esp_bd_addr_t rand_addr) { btc_msg_t msg; btc_ble_gap_args_t arg; - - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -203,7 +203,7 @@ esp_err_t esp_ble_gap_config_local_privacy (bool privacy_enable) btc_msg_t msg; btc_ble_gap_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -220,7 +220,7 @@ esp_err_t esp_ble_gap_set_device_name(const char *name) btc_msg_t msg; btc_ble_gap_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } diff --git a/components/bt/bluedroid/api/esp_gattc_api.c b/components/bt/bluedroid/api/esp_gattc_api.c index 4baa0a77fc..8b9cc99e87 100644 --- a/components/bt/bluedroid/api/esp_gattc_api.c +++ b/components/bt/bluedroid/api/esp_gattc_api.c @@ -22,7 +22,7 @@ esp_err_t esp_ble_gattc_register_callback(esp_gattc_cb_t callback) { - if (ESP_BLUEDROID_STATUS_UNINITIALIZED == esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { return ESP_ERR_INVALID_STATE; } @@ -39,7 +39,7 @@ esp_err_t esp_ble_gattc_app_register(uint16_t app_id) btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -60,7 +60,7 @@ esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gattc_if) btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -77,7 +77,7 @@ esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, b btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -96,7 +96,7 @@ esp_err_t esp_ble_gattc_close (esp_gatt_if_t gattc_if, uint16_t conn_id) btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -113,7 +113,7 @@ esp_err_t esp_ble_gattc_config_mtu (esp_gatt_if_t gattc_if, uint16_t conn_id, ui btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -135,7 +135,7 @@ esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -162,7 +162,7 @@ esp_err_t esp_ble_gattc_get_characteristic(esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -191,7 +191,7 @@ esp_err_t esp_ble_gattc_get_descriptor(esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -222,7 +222,7 @@ esp_err_t esp_ble_gattc_get_included_service(esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -252,7 +252,7 @@ esp_err_t esp_ble_gattc_read_char (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -277,7 +277,7 @@ esp_err_t esp_ble_gattc_read_char_descr (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -305,7 +305,7 @@ esp_err_t esp_ble_gattc_write_char( esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -336,7 +336,7 @@ esp_err_t esp_ble_gattc_write_char_descr (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -368,7 +368,7 @@ esp_err_t esp_ble_gattc_prepare_write(esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -391,7 +391,7 @@ esp_err_t esp_ble_gattc_execute_write (esp_gatt_if_t gattc_if, uint16_t conn_id, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -412,7 +412,7 @@ esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -435,7 +435,7 @@ esp_gatt_status_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gattc_if, btc_msg_t msg; btc_ble_gattc_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } diff --git a/components/bt/bluedroid/api/esp_gatts_api.c b/components/bt/bluedroid/api/esp_gatts_api.c index 1f0d668e5a..71b5a4338c 100644 --- a/components/bt/bluedroid/api/esp_gatts_api.c +++ b/components/bt/bluedroid/api/esp_gatts_api.c @@ -24,7 +24,7 @@ esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback) { - if (ESP_BLUEDROID_STATUS_UNINITIALIZED == esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { return ESP_ERR_INVALID_STATE; } return (btc_profile_cb_set(BTC_PID_GATTS, callback) == 0 ? ESP_OK : ESP_FAIL); @@ -35,7 +35,7 @@ esp_err_t esp_ble_gatts_app_register(uint16_t app_id) btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -58,7 +58,7 @@ esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatts_if) btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -76,7 +76,7 @@ esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -96,7 +96,7 @@ esp_err_t esp_ble_gatts_add_included_service(uint16_t service_handle, uint16_t i btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -116,7 +116,7 @@ esp_err_t esp_ble_gatts_add_char(uint16_t service_handle, esp_bt_uuid_t *char_ btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -139,7 +139,7 @@ esp_err_t esp_ble_gatts_add_char_descr (uint16_t service_handle, btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -158,7 +158,7 @@ esp_err_t esp_ble_gatts_delete_service(uint16_t service_handle) btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -175,7 +175,7 @@ esp_err_t esp_ble_gatts_start_service(uint16_t service_handle) btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -192,7 +192,7 @@ esp_err_t esp_ble_gatts_stop_service(uint16_t service_handle) btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -211,7 +211,7 @@ esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -233,7 +233,7 @@ esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -253,7 +253,7 @@ esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatts_if, esp_bd_addr_t remote_bda, b btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } @@ -272,7 +272,7 @@ esp_err_t esp_ble_gatts_close(esp_gatt_if_t gatts_if, uint16_t conn_id) btc_msg_t msg; btc_ble_gatts_args_t arg; - if (ESP_BLUEDROID_STATUS_ENABLED != esp_bluedroid_get_status()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } From babeed417050584c83eea21306723bd53770914e Mon Sep 17 00:00:00 2001 From: wangmengyang Date: Wed, 11 Jan 2017 14:08:16 +0800 Subject: [PATCH 126/167] component/bt: change MACRO to inline function when invoking callbacks --- .../btc/profile/esp/blufi/blufi_prf.c | 61 ++++++++++--------- .../btc/profile/std/gap/btc_gap_ble.c | 21 ++++--- .../btc/profile/std/gatt/btc_gattc.c | 60 +++++++++--------- .../btc/profile/std/gatt/btc_gatts.c | 51 ++++++++-------- 4 files changed, 98 insertions(+), 95 deletions(-) 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 85be60b7b2..e88c775f5f 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c @@ -35,13 +35,6 @@ #include "esp_blufi_api.h" -#define BTC_BLUFI_CB_TO_APP(event, param) do { \ - esp_blufi_event_cb_t btc_blufi_cb = (esp_blufi_event_cb_t)btc_profile_cb_get(BTC_PID_BLUFI); \ - if (btc_blufi_cb) { \ - btc_blufi_cb(event, param); \ - } \ - } while (0) - #define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" #define BT_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] @@ -71,6 +64,14 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data); static void btc_blufi_recv_handler(uint8_t *data, int len); static void btc_blufi_send_ack(uint8_t seq); +static inline void btc_blufi_cb_to_app(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param) +{ + esp_blufi_event_cb_t btc_blufi_cb = (esp_blufi_event_cb_t)btc_profile_cb_get(BTC_PID_BLUFI); + if (btc_blufi_cb) { + btc_blufi_cb(event, param); + } +} + static void blufi_create_service(void) { if (!blufi_env.enabled) { @@ -682,75 +683,75 @@ void btc_blufi_cb_handler(btc_msg_t *msg) switch (msg->act) { case ESP_BLUFI_EVENT_INIT_FINISH: { - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_INIT_FINISH, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_INIT_FINISH, param); break; } case ESP_BLUFI_EVENT_DEINIT_FINISH: { - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_DEINIT_FINISH, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_DEINIT_FINISH, param); break; } case ESP_BLUFI_EVENT_BLE_CONNECT: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_BLE_CONNECT, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_BLE_CONNECT, param); break; case ESP_BLUFI_EVENT_BLE_DISCONNECT: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_BLE_DISCONNECT, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_BLE_DISCONNECT, param); break; case ESP_BLUFI_EVENT_SET_WIFI_OPMODE: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_SET_WIFI_OPMODE, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_SET_WIFI_OPMODE, param); break; case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP, NULL); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP, NULL); break; case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP, NULL); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP, NULL); break; case ESP_BLUFI_EVENT_GET_WIFI_STATUS: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_GET_WIFI_STATUS, NULL); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_GET_WIFI_STATUS, NULL); break; case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_DEAUTHENTICATE_STA, NULL); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_DEAUTHENTICATE_STA, NULL); break; case ESP_BLUFI_EVENT_RECV_STA_BSSID: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_STA_BSSID, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_STA_BSSID, param); break; case ESP_BLUFI_EVENT_RECV_STA_SSID: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_STA_SSID, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_STA_SSID, param); break; case ESP_BLUFI_EVENT_RECV_STA_PASSWD: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_STA_PASSWD, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_STA_PASSWD, param); break; case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_SSID, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_SOFTAP_SSID, param); break; case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD, param); break; case ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM, param); break; case ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE, param); break; case ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL, param); break; case ESP_BLUFI_EVENT_RECV_USERNAME: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_USERNAME, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_USERNAME, param); break; case ESP_BLUFI_EVENT_RECV_CA_CERT: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_CA_CERT, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_CA_CERT, param); break; case ESP_BLUFI_EVENT_RECV_CLIENT_CERT: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_CLIENT_CERT, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_CLIENT_CERT, param); break; case ESP_BLUFI_EVENT_RECV_SERVER_CERT: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SERVER_CERT, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_SERVER_CERT, param); break; case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY, param); break; case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY: - BTC_BLUFI_CB_TO_APP(ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY, param); + btc_blufi_cb_to_app(ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY, param); break; default: LOG_ERROR("%s UNKNOWN %d\n", __func__, msg->act); 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 e2f4815087..4161567538 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 @@ -26,12 +26,13 @@ static tBTA_BLE_ADV_DATA gl_bta_adv_data; static tBTA_BLE_ADV_DATA gl_bta_scan_rsp_data; -#define BTC_GAP_BLE_CB_TO_APP(event, param) do { \ - esp_gap_ble_cb_t btc_gap_ble_cb = (esp_gap_ble_cb_t)btc_profile_cb_get(BTC_PID_GAP_BLE); \ - if (btc_gap_ble_cb) { \ - btc_gap_ble_cb(event, param); \ - } \ - } while (0) +static inline void btc_gap_ble_cb_to_app(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + esp_gap_ble_cb_t btc_gap_ble_cb = (esp_gap_ble_cb_t)btc_profile_cb_get(BTC_PID_GAP_BLE); + if (btc_gap_ble_cb) { + btc_gap_ble_cb(event, param); + } +} static void btc_gap_adv_point_cleanup(void **buf) { @@ -508,16 +509,16 @@ void btc_gap_ble_cb_handler(btc_msg_t *msg) switch (msg->act) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: - BTC_GAP_BLE_CB_TO_APP(ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT, param); + btc_gap_ble_cb_to_app(ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT, param); break; case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT : - BTC_GAP_BLE_CB_TO_APP(ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT, param); + btc_gap_ble_cb_to_app(ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT, param); break; case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: - BTC_GAP_BLE_CB_TO_APP(ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, param); + btc_gap_ble_cb_to_app(ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, param); break; case ESP_GAP_BLE_SCAN_RESULT_EVT: - BTC_GAP_BLE_CB_TO_APP(ESP_GAP_BLE_SCAN_RESULT_EVT, param); + btc_gap_ble_cb_to_app(ESP_GAP_BLE_SCAN_RESULT_EVT, param); break; default: break; diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c index 4b6947940f..537be9092c 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c @@ -22,13 +22,13 @@ #include "bt_trace.h" #include "esp_gattc_api.h" -#define BTC_GATTC_CB_TO_APP(event, gattc_if, param) do { \ - esp_gattc_cb_t btc_gattc_cb = (esp_gattc_cb_t )btc_profile_cb_get(BTC_PID_GATTC); \ - if (btc_gattc_cb) { \ - btc_gattc_cb(event, gattc_if, param); \ - } \ - } while (0) - +static inline void btc_gattc_cb_to_app(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_gattc_cb_t btc_gattc_cb = (esp_gattc_cb_t )btc_profile_cb_get(BTC_PID_GATTC); + if (btc_gattc_cb) { + btc_gattc_cb(event, gattc_if, param); + } +} void btc_gattc_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { @@ -203,7 +203,7 @@ static void btc_gattc_get_first_char(btc_ble_gattc_args_t *arg) memcpy(¶m.get_char.srvc_id, &arg->get_first_char.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_char.char_id, &char_id, sizeof(esp_gatt_id_t)); param.get_char.char_prop = out_char_prop; - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_CHAR_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_GET_CHAR_EVT, gattc_if, ¶m); } static void btc_gattc_get_next_char(btc_ble_gattc_args_t *arg) @@ -233,7 +233,7 @@ static void btc_gattc_get_next_char(btc_ble_gattc_args_t *arg) memcpy(¶m.get_char.srvc_id, &arg->get_next_char.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_char.char_id, &char_id, sizeof(esp_gatt_id_t)); param.get_char.char_prop = out_char_prop; - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_CHAR_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_GET_CHAR_EVT, gattc_if, ¶m); } static void btc_gattc_get_first_descr(btc_ble_gattc_args_t *arg) @@ -262,7 +262,7 @@ static void btc_gattc_get_first_descr(btc_ble_gattc_args_t *arg) memcpy(¶m.get_descr.srvc_id, &arg->get_first_descr.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_descr.char_id, &arg->get_first_descr.char_id, sizeof(esp_gatt_id_t)); memcpy(¶m.get_descr.descr_id, &descr_id, sizeof(esp_gatt_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_DESCR_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_GET_DESCR_EVT, gattc_if, ¶m); } static void btc_gattc_get_next_descr(btc_ble_gattc_args_t *arg) @@ -291,7 +291,7 @@ static void btc_gattc_get_next_descr(btc_ble_gattc_args_t *arg) memcpy(¶m.get_descr.srvc_id, &arg->get_next_descr.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_descr.char_id, &arg->get_next_descr.char_id, sizeof(esp_gatt_id_t)); memcpy(¶m.get_descr.descr_id, &descr_id, sizeof(esp_gatt_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_DESCR_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_GET_DESCR_EVT, gattc_if, ¶m); } static void btc_gattc_get_first_incl_service(btc_ble_gattc_args_t *arg) @@ -316,7 +316,7 @@ static void btc_gattc_get_first_incl_service(btc_ble_gattc_args_t *arg) param.get_incl_srvc.status = status; memcpy(¶m.get_incl_srvc.srvc_id, &arg->get_first_incl_srvc.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_incl_srvc.incl_srvc_id, &incl_srvc_id, sizeof(esp_gatt_srvc_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_INCL_SRVC_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_GET_INCL_SRVC_EVT, gattc_if, ¶m); } static void btc_gattc_get_next_incl_service(btc_ble_gattc_args_t *arg) @@ -342,7 +342,7 @@ static void btc_gattc_get_next_incl_service(btc_ble_gattc_args_t *arg) param.get_incl_srvc.status = status; memcpy(¶m.get_incl_srvc.srvc_id, &arg->get_next_incl_srvc.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.get_incl_srvc.incl_srvc_id, &incl_srvc_id, sizeof(esp_gatt_srvc_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_GET_INCL_SRVC_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_GET_INCL_SRVC_EVT, gattc_if, ¶m); } static void btc_gattc_read_char(btc_ble_gattc_args_t *arg) @@ -429,7 +429,7 @@ static void btc_gattc_reg_for_notify(btc_ble_gattc_args_t *arg) param.reg_for_notify.status = status; memcpy(¶m.reg_for_notify.srvc_id, &arg->reg_for_notify.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.reg_for_notify.char_id, &arg->reg_for_notify.char_id, sizeof(esp_gatt_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_REG_FOR_NOTIFY_EVT, arg->reg_for_notify.gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_REG_FOR_NOTIFY_EVT, arg->reg_for_notify.gattc_if, ¶m); } static void btc_gattc_unreg_for_notify(btc_ble_gattc_args_t *arg) @@ -449,7 +449,7 @@ static void btc_gattc_unreg_for_notify(btc_ble_gattc_args_t *arg) param.unreg_for_notify.status = status; memcpy(¶m.unreg_for_notify.srvc_id, &arg->unreg_for_notify.service_id, sizeof(esp_gatt_srvc_id_t)); memcpy(¶m.unreg_for_notify.char_id, &arg->unreg_for_notify.service_id, sizeof(esp_gatt_id_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_UNREG_FOR_NOTIFY_EVT, arg->unreg_for_notify.gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_UNREG_FOR_NOTIFY_EVT, arg->unreg_for_notify.gattc_if, ¶m); } void btc_gattc_call_handler(btc_msg_t *msg) @@ -539,19 +539,19 @@ void btc_gattc_cb_handler(btc_msg_t *msg) gattc_if = reg_oper->client_if; param.reg.status = reg_oper->status; param.reg.app_id = reg_oper->app_uuid.uu.uuid16; - BTC_GATTC_CB_TO_APP(ESP_GATTC_REG_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_REG_EVT, gattc_if, ¶m); break; } case BTA_GATTC_DEREG_EVT: { tBTA_GATTC_REG *reg_oper = &arg->reg_oper; gattc_if = reg_oper->client_if; - BTC_GATTC_CB_TO_APP(ESP_GATTC_UNREG_EVT, gattc_if, NULL); + btc_gattc_cb_to_app(ESP_GATTC_UNREG_EVT, gattc_if, NULL); break; } case BTA_GATTC_READ_CHAR_EVT: { set_read_value(&gattc_if, ¶m, &arg->read); - BTC_GATTC_CB_TO_APP(ESP_GATTC_READ_CHAR_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_READ_CHAR_EVT, gattc_if, ¶m); break; } case BTA_GATTC_WRITE_CHAR_EVT: @@ -565,7 +565,7 @@ void btc_gattc_cb_handler(btc_msg_t *msg) param.write.status = write->status; bta_to_btc_srvc_id(¶m.write.srvc_id, &write->srvc_id); bta_to_btc_gatt_id(¶m.write.char_id, &write->char_id); - BTC_GATTC_CB_TO_APP(ret_evt, gattc_if, ¶m); + btc_gattc_cb_to_app(ret_evt, gattc_if, ¶m); break; } @@ -575,7 +575,7 @@ void btc_gattc_cb_handler(btc_msg_t *msg) gattc_if = BTC_GATT_GET_GATT_IF(exec_cmpl->conn_id); param.exec_cmpl.conn_id = BTC_GATT_GET_CONN_ID(exec_cmpl->conn_id); param.exec_cmpl.status = exec_cmpl->status; - BTC_GATTC_CB_TO_APP(ESP_GATTC_EXEC_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_EXEC_EVT, gattc_if, ¶m); break; } @@ -585,7 +585,7 @@ void btc_gattc_cb_handler(btc_msg_t *msg) gattc_if = BTC_GATT_GET_GATT_IF(search_cmpl->conn_id); param.search_cmpl.conn_id = BTC_GATT_GET_CONN_ID(search_cmpl->conn_id); param.search_cmpl.status = search_cmpl->status; - BTC_GATTC_CB_TO_APP(ESP_GATTC_SEARCH_CMPL_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_SEARCH_CMPL_EVT, gattc_if, ¶m); break; } case BTA_GATTC_SEARCH_RES_EVT: { @@ -594,12 +594,12 @@ void btc_gattc_cb_handler(btc_msg_t *msg) gattc_if = BTC_GATT_GET_GATT_IF(srvc_res->conn_id); param.search_res.conn_id = BTC_GATT_GET_CONN_ID(srvc_res->conn_id); bta_to_btc_srvc_id(¶m.search_res.srvc_id, &srvc_res->service_uuid); - BTC_GATTC_CB_TO_APP(ESP_GATTC_SEARCH_RES_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_SEARCH_RES_EVT, gattc_if, ¶m); break; } case BTA_GATTC_READ_DESCR_EVT: { set_read_value(&gattc_if, ¶m, &arg->read); - BTC_GATTC_CB_TO_APP(ESP_GATTC_READ_DESCR_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_READ_DESCR_EVT, gattc_if, ¶m); break; } case BTA_GATTC_WRITE_DESCR_EVT: { @@ -611,7 +611,7 @@ void btc_gattc_cb_handler(btc_msg_t *msg) bta_to_btc_srvc_id(¶m.write.srvc_id, &write->srvc_id); bta_to_btc_gatt_id(¶m.write.char_id, &write->char_id); bta_to_btc_gatt_id(¶m.write.descr_id, &write->descr_type); - BTC_GATTC_CB_TO_APP(ESP_GATTC_WRITE_DESCR_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_WRITE_DESCR_EVT, gattc_if, ¶m); break; } case BTA_GATTC_NOTIF_EVT: { @@ -632,7 +632,7 @@ void btc_gattc_cb_handler(btc_msg_t *msg) BTA_GATTC_SendIndConfirm(notify->conn_id, ¬ify->char_id); } - BTC_GATTC_CB_TO_APP(ESP_GATTC_NOTIFY_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_NOTIFY_EVT, gattc_if, ¶m); break; } case BTA_GATTC_OPEN_EVT: { @@ -643,7 +643,7 @@ void btc_gattc_cb_handler(btc_msg_t *msg) param.open.conn_id = BTC_GATT_GET_CONN_ID(open->conn_id); memcpy(param.open.remote_bda, open->remote_bda, sizeof(esp_bd_addr_t)); param.open.mtu = open->mtu; - BTC_GATTC_CB_TO_APP(ESP_GATTC_OPEN_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_OPEN_EVT, gattc_if, ¶m); break; } case BTA_GATTC_CLOSE_EVT: { @@ -654,7 +654,7 @@ void btc_gattc_cb_handler(btc_msg_t *msg) param.close.conn_id = BTC_GATT_GET_CONN_ID(close->conn_id); memcpy(param.close.remote_bda, close->remote_bda, sizeof(esp_bd_addr_t)); param.close.reason = close->reason; - BTC_GATTC_CB_TO_APP(ESP_GATTC_CLOSE_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_CLOSE_EVT, gattc_if, ¶m); break; } @@ -665,7 +665,7 @@ void btc_gattc_cb_handler(btc_msg_t *msg) param.cfg_mtu.conn_id = BTC_GATT_GET_CONN_ID(cfg_mtu->conn_id); param.cfg_mtu.status = cfg_mtu->status; param.cfg_mtu.mtu = cfg_mtu->mtu; - BTC_GATTC_CB_TO_APP(ESP_GATTC_CFG_MTU_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_CFG_MTU_EVT, gattc_if, ¶m); break; } @@ -683,12 +683,12 @@ void btc_gattc_cb_handler(btc_msg_t *msg) gattc_if = BTC_GATT_GET_GATT_IF(congest->conn_id); param.congest.conn_id = BTC_GATT_GET_CONN_ID(congest->conn_id); param.congest.congested = (congest->congested == TRUE) ? true : false; - BTC_GATTC_CB_TO_APP(ESP_GATTC_CONGEST_EVT, gattc_if, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_CONGEST_EVT, gattc_if, ¶m); break; } case BTA_GATTC_SRVC_CHG_EVT: { memcpy(param.srvc_chg.remote_bda, arg->remote_bda, sizeof(esp_bd_addr_t)); - BTC_GATTC_CB_TO_APP(ESP_GATTC_SRVC_CHG_EVT, ESP_GATT_IF_NONE, ¶m); + btc_gattc_cb_to_app(ESP_GATTC_SRVC_CHG_EVT, ESP_GATT_IF_NONE, ¶m); break; } default: diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c index 3a988fc3b1..9bc41d2ab1 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c @@ -23,16 +23,17 @@ #include "esp_gatts_api.h" -#define BTC_GATTS_CB_TO_APP(event, gatts_if, param) do { \ - esp_gatts_cb_t btc_gatts_cb = (esp_gatts_cb_t)btc_profile_cb_get(BTC_PID_GATTS); \ - if (btc_gatts_cb) { \ - btc_gatts_cb(event, gatts_if, param); \ - } \ - } while (0) - #define A2C_GATTS_EVT(_bta_event) (_bta_event) //BTA TO BTC EVT #define C2A_GATTS_EVT(_btc_event) (_btc_event) //BTC TO BTA EVT +static inline void btc_gatts_cb_to_app(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + esp_gatts_cb_t btc_gatts_cb = (esp_gatts_cb_t)btc_profile_cb_get(BTC_PID_GATTS); + if (btc_gatts_cb) { + btc_gatts_cb(event, gatts_if, param); + } +} + void btc_gatts_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { btc_ble_gatts_args_t *dst = (btc_ble_gatts_args_t *) p_dest; @@ -232,7 +233,7 @@ void btc_gatts_call_handler(btc_msg_t *msg) } param.rsp.status = 0; - BTC_GATTS_CB_TO_APP(ESP_GATTS_RESPONSE_EVT, BTC_GATT_GET_GATT_IF(arg->send_rsp.conn_id), ¶m); + btc_gatts_cb_to_app(ESP_GATTS_RESPONSE_EVT, BTC_GATT_GET_GATT_IF(arg->send_rsp.conn_id), ¶m); break; } case BTC_GATTS_ACT_OPEN: { @@ -291,12 +292,12 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.reg.status = p_data->reg_oper.status; param.reg.app_id = p_data->reg_oper.uuid.uu.uuid16; - BTC_GATTS_CB_TO_APP(ESP_GATTS_REG_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_REG_EVT, gatts_if, ¶m); break; } case BTA_GATTS_DEREG_EVT: { gatts_if = p_data->reg_oper.server_if; - BTC_GATTS_CB_TO_APP(ESP_GATTS_UNREG_EVT, gatts_if, NULL); + btc_gatts_cb_to_app(ESP_GATTS_UNREG_EVT, gatts_if, NULL); break; } case BTA_GATTS_READ_EVT: { @@ -308,7 +309,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.read.offset = p_data->req_data.p_data->read_req.offset; param.read.is_long = p_data->req_data.p_data->read_req.is_long; - BTC_GATTS_CB_TO_APP(ESP_GATTS_READ_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_READ_EVT, gatts_if, ¶m); break; } case BTA_GATTS_WRITE_EVT: { @@ -323,7 +324,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.write.len = p_data->req_data.p_data->write_req.len; param.write.value = p_data->req_data.p_data->write_req.value; - BTC_GATTS_CB_TO_APP(ESP_GATTS_WRITE_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_WRITE_EVT, gatts_if, ¶m); break; } @@ -334,7 +335,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) memcpy(param.exec_write.bda, p_data->req_data.remote_bda, ESP_BD_ADDR_LEN); param.exec_write.exec_write_flag = p_data->req_data.p_data->exec_write; - BTC_GATTS_CB_TO_APP(ESP_GATTS_EXEC_WRITE_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_EXEC_WRITE_EVT, gatts_if, ¶m); break; } case BTA_GATTS_MTU_EVT: @@ -342,14 +343,14 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.mtu.conn_id = BTC_GATT_GET_CONN_ID(p_data->req_data.conn_id); param.mtu.mtu = p_data->req_data.p_data->mtu; - BTC_GATTS_CB_TO_APP(ESP_GATTS_MTU_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_MTU_EVT, gatts_if, ¶m); break; case BTA_GATTS_CONF_EVT: gatts_if = BTC_GATT_GET_GATT_IF(p_data->req_data.conn_id); param.conf.conn_id = BTC_GATT_GET_CONN_ID(p_data->req_data.conn_id); param.conf.status = p_data->req_data.status; - BTC_GATTS_CB_TO_APP(ESP_GATTS_CONF_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_CONF_EVT, gatts_if, ¶m); break; case BTA_GATTS_CREATE_EVT: gatts_if = p_data->create.server_if; @@ -358,7 +359,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.create.service_id.is_primary = p_data->create.is_primary; param.create.service_id.id.inst_id = p_data->create.svc_instance; bta_to_btc_uuid(¶m.create.service_id.id.uuid, &p_data->create.uuid); - BTC_GATTS_CB_TO_APP(ESP_GATTS_CREATE_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_CREATE_EVT, gatts_if, ¶m); break; case BTA_GATTS_ADD_INCL_SRVC_EVT: gatts_if = p_data->add_result.server_if; @@ -366,7 +367,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.add_incl_srvc.attr_handle = p_data->add_result.attr_id; param.add_incl_srvc.service_handle = p_data->add_result.service_id; - BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_INCL_SRVC_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_ADD_INCL_SRVC_EVT, gatts_if, ¶m); break; case BTA_GATTS_ADD_CHAR_EVT: gatts_if = p_data->add_result.server_if; @@ -375,7 +376,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.add_char.service_handle = p_data->add_result.service_id; bta_to_btc_uuid(¶m.add_char.char_uuid, &p_data->add_result.char_uuid); - BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_CHAR_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_ADD_CHAR_EVT, gatts_if, ¶m); break; case BTA_GATTS_ADD_CHAR_DESCR_EVT: gatts_if = p_data->add_result.server_if; @@ -384,27 +385,27 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.add_char_descr.service_handle = p_data->add_result.service_id; bta_to_btc_uuid(¶m.add_char_descr.char_uuid, &p_data->add_result.char_uuid); - BTC_GATTS_CB_TO_APP(ESP_GATTS_ADD_CHAR_DESCR_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_ADD_CHAR_DESCR_EVT, gatts_if, ¶m); break; case BTA_GATTS_DELELTE_EVT: gatts_if = p_data->srvc_oper.server_if; param.del.status = p_data->srvc_oper.status; param.del.service_handle = p_data->srvc_oper.service_id; - BTC_GATTS_CB_TO_APP(ESP_GATTS_DELETE_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_DELETE_EVT, gatts_if, ¶m); break; case BTA_GATTS_START_EVT: gatts_if = p_data->srvc_oper.server_if; param.start.status = p_data->srvc_oper.status; param.start.service_handle = p_data->srvc_oper.service_id; - BTC_GATTS_CB_TO_APP(ESP_GATTS_START_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_START_EVT, gatts_if, ¶m); break; case BTA_GATTS_STOP_EVT: gatts_if = p_data->srvc_oper.server_if; param.stop.status = p_data->srvc_oper.status; param.stop.service_handle = p_data->srvc_oper.service_id; - BTC_GATTS_CB_TO_APP(ESP_GATTS_STOP_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_STOP_EVT, gatts_if, ¶m); break; case BTA_GATTS_CONNECT_EVT: gatts_if = p_data->conn.server_if; @@ -412,7 +413,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.connect.is_connected = true; memcpy(param.connect.remote_bda, p_data->conn.remote_bda, ESP_BD_ADDR_LEN); - BTC_GATTS_CB_TO_APP(ESP_GATTS_CONNECT_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_CONNECT_EVT, gatts_if, ¶m); break; case BTA_GATTS_DISCONNECT_EVT: gatts_if = p_data->conn.server_if; @@ -420,7 +421,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.disconnect.is_connected = false; memcpy(param.disconnect.remote_bda, p_data->conn.remote_bda, ESP_BD_ADDR_LEN); - BTC_GATTS_CB_TO_APP(ESP_GATTS_DISCONNECT_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_DISCONNECT_EVT, gatts_if, ¶m); break; case BTA_GATTS_OPEN_EVT: // do nothing @@ -435,7 +436,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) gatts_if = BTC_GATT_GET_GATT_IF(p_data->congest.conn_id); param.congest.conn_id = BTC_GATT_GET_CONN_ID(p_data->congest.conn_id); param.congest.congested = p_data->congest.congested; - BTC_GATTS_CB_TO_APP(ESP_GATTS_CONGEST_EVT, gatts_if, ¶m); + btc_gatts_cb_to_app(ESP_GATTS_CONGEST_EVT, gatts_if, ¶m); break; default: // do nothing From daa2b7cbc968feaa0f259e6b52020e954dde6ffb Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 11 Jan 2017 14:13:37 +0800 Subject: [PATCH 127/167] n, h and l actually are 6-bit; they go from 1 to 64. --- components/driver/spi_master.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index 204e577eda..53be8b29de 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -396,7 +396,7 @@ static int spi_freq_for_pre_n(int fapb, int pre, int n) { static void spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) { int pre, n, h, l; - //In hw, n, h and l are 1-32, pre is 1-8K. Value written to register is one lower than used value. + //In hw, n, h and l are 1-64, pre is 1-8K. Value written to register is one lower than used value. if (hz>((fapb/4)*3)) { //Using Fapb directly will give us the best result here. hw->clock.clkcnt_l=0; @@ -414,7 +414,7 @@ static void spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) { int bestpre=-1; int besterr=hz; int errval; - for (n=1; n<33; n++) { + for (n=1; n<=64; n++) { //Effectively, this does pre=round((fapb/n)/hz). pre=((fapb/n)+(hz/2))/hz; if (pre<0) pre=0; From 23448e7121f8bb2ba8553706ef9c209d187c4b6f Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 10 Jan 2017 23:43:18 +0800 Subject: [PATCH 128/167] newlib: build with wcsftime function --- components/newlib/lib/libc.a | Bin 4957212 -> 5022558 bytes components/newlib/lib/libc_nano.a | Bin 3867014 -> 3569308 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/components/newlib/lib/libc.a b/components/newlib/lib/libc.a index 4377fa252f5a9b3870fddc8773a74cb6d59c4d38..36a1cc60784a9e8f16051db824fd87eaad84a16d 100644 GIT binary patch delta 815349 zcmcG%2Yggj`aXWnoqJ~{nMp{(klr&Sp(l_=4>bV-gsLJ!LI)`UQLqs}R7AnxMP5Zf zKm;q&bQBQ*(N$CgyVwF^SyvHktFGn$y!W0P*j<6${r-RV^Lgh!r`-OY_dMr4Wmdnq zMf>Q5?OI)uyCAQyU2);^jD*_4l5}gU3Kv3KCq!Y45WQZvW%zL+#&r&R!q^|%PvCCJ}C4qCxzbsb)k2mycy~)Y zVSR-sc9ig>br+se>{+I~ZOe+4!n6CJ@ElJR-tgKH!rNxMEtf77-br`Zvf_~Nvh0~+ z%Q0X`Slo15%D)w1j@14r!dMl<-0PNgHe_8zB5FHN$OL`=#)+yqs#w_X~v| z5|Pu~mf_Py1j~|4Tb}w+MC|KpNu<_V)bdYc&RSb4_KQfCHIV10*z(135qbKcC5>Vi z3QNkCiAF5TJJ|BfEYXPN_(@Bmn#2lAy6+KDER)*W^5NSe+SSFD!f+8?7+x!)FZtG% zdB2EgmJNv_`rWm*d^thH`1br~a#IHp!?N=iTVC5PV&28EKX0(4adLNIN!Qk*G0Qj& zQj4EkQX3>t{iQgkeBD+A&XOi=2a6^ZH;E=AHk?;tb0S59bm|~t9jUrW#MVyVFJc!q zx8=blB6j;Vwj7-mcH;`(4D{ z`htjCrHQyrABnioN?YDf6>*Swy}K>V!$mwx_gE2s?fwR-O{`ig5+^SdiC2Cp5^p|e z%bE!yk!3gHSl%3NNs@cGNQ!t!Bn5sKNy!65Qq~5M)ci-0)V8lkDh(G&RSQMZ%;vV- zohXtvpR@(XC$aprN+f%)63HlW$(@g~)Y@R&Ns)XNB9~?Uck;;|k^Fy>+CM)*%9>PR z$zMi^ltbG^%FzKLwdH2Z%J5nZ0dUxmKjH zJhfe<)xNk#q@BFUlJvysBE4gGTSm1N=`0Hn$FjM|mRI|V^f%Xv^k0sOj7BF#MqGDW zI;Dz?9JPc%qwk4%v}@uoOcb-+ z|CuO$w1X{g3>L*KzaFxrB)hw?q++lrVYza;Eo+yF5+xmaPZ4KJ$M%~9OQ#>d{3q#r zGO!MGeL|dkf5q+-jE&9}T`Dc0QD$(cTwwCmb?JN2=-6Z-JWs1Jj z28h1beJlDtIK`Haj*7nDj1c{@!$rRinWA5xE}~z}A<=IE;#fA{VavM@iGD0!X(FrN zFWW^$c&w;M=q@U9$B2sJR9hy@5)~}VQg`ePk57ymJ$mfysWZlkIb*MwFnjW}@jKoL zzAd#jZOm=it&H?`MR|EUj%Sxd7*?VgQE)QNBDn>5>FI@eZNYKd0;Q+t=j9a@6r`sY zw53tQWpf#%)OW=bV>IjYv+}`(~Nz*^Ln}= zmrFN#b4d5RjGe9<7b8-Ot+kRoRfuzMz>imI1~Z)Z6F8C%Zmc}KLgyR4QY%lo;uDql%eoc;YSE5{E{z^~Uc%iLO&?`{Mf z`Lp?XQJ1hHWMR?h*yDPuJ5G7XH^v)b&M5xD^C?G*`)TIdhz#?#l4Sp|E5^^dqRr&1 zs;9RZZ*D6I=&i;~o(>QqZD3)hd8B>Uh&F$khfc35EHv*e@tRQ`CerpBwZ`UM9rB`Q zOdmad?&R5{t7lETLX4g;{R&!eX-uE+_N^vX=eOrCMEQ-IjHAlAQT|X~Vw_i_y}kV2 z9UD9E)y8bc%}EVE*mgr=6yC3}3;P!p4#WPC!jag≻nbM-^_2eZ9h|*q;zI{i)bx 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 z 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%0Dyzhcb#PxRa^*l7}RJ`5JfxobG;jip6_*=UY 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 diff --git a/components/newlib/lib/libc_nano.a b/components/newlib/lib/libc_nano.a index 2a99a9c490e5a4398a25674419e7a146f790c230..9afd3798782c23c019833df6f472063fbc31fea0 100644 GIT binary patch delta 819350 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≫iG(Ibg4%XL+i<*Kid{^MuRcXmF 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

Q(}W)fR}aF8EHDgzjKgD42;2-Ft-lgBJ-81hXHP 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 C7mVxVq*}#kyW&<-(nC%O{B0_z(FIjr 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 U{xXd(shVCIBC3H&5F(%>V!Z delta 1110267 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�l) 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≫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
2# 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 From f04e62e6ccf752883bbd3a23cf6d62a403b6a2a9 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 10 Jan 2017 23:45:44 +0800 Subject: [PATCH 129/167] cxx: add iostreams test Checks that libstdc++ can use newlib IO functions --- components/cxx/test/test_cxx.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/cxx/test/test_cxx.cpp b/components/cxx/test/test_cxx.cpp index 8b790783ed..3b653361f4 100644 --- a/components/cxx/test/test_cxx.cpp +++ b/components/cxx/test/test_cxx.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "unity.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" @@ -201,3 +202,9 @@ TEST_CASE("before scheduler has started, static initializers work correctly", "[ TEST_ASSERT_EQUAL(1, g_static_init_test3.index); TEST_ASSERT_EQUAL(2, StaticInitTestBeforeScheduler::order); } + + +TEST_CASE("can use iostreams", "[cxx]") +{ + std::cout << "hello world"; +} From 8d2199e36c068cffa7e935e371aa591227fdd2e3 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Sat, 3 Dec 2016 23:02:02 -0800 Subject: [PATCH 130/167] docs: update toolchain version --- docs/linux-setup.rst | 22 ++++++++++++---------- docs/macos-setup.rst | 13 +++++++------ docs/windows-setup.rst | 4 ++-- make/project.mk | 27 +++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/docs/linux-setup.rst b/docs/linux-setup.rst index 20f460aa62..94d77f3605 100644 --- a/docs/linux-setup.rst +++ b/docs/linux-setup.rst @@ -10,11 +10,11 @@ Install some packages To compile with ESP-IDF you need to get the following packages: - Ubuntu and Debian:: - - sudo apt-get install git wget make libncurses-dev flex bison gperf python python-serial + + 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 Step 1: Download binary toolchain for the ESP32 @@ -24,17 +24,17 @@ ESP32 toolchain for Linux is available for download from Espressif website: - for 64-bit Linux:: - https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-59.tar.gz + https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-61-gab8375a-5.2.0.tar.gz - for 32-bit Linux:: - https://dl.espressif.com/dl/xtensa-esp32-elf-linux32-1.22.0-59.tar.gz + https://dl.espressif.com/dl/xtensa-esp32-elf-linux32-1.22.0-61-gab8375a-5.2.0.tar.gz Download this file, then extract it to the location you prefer, for example:: mkdir -p ~/esp cd ~/esp - tar -xzf ~/Downloads/xtensa-esp32-elf-linux64-1.22.0-59.tar.gz + tar -xzf ~/Downloads/xtensa-esp32-elf-linux64-1.22.0-61-gab8375a-5.2.0.tar.gz The toolchain will be extracted into ``~/esp/xtensa-esp32-elf/`` directory. @@ -61,12 +61,14 @@ To run the precompiled gdb (xtensa-esp32-elf-gdb) in Arch Linux requires ncurses Alternative Step 1: Compile the toolchain from source using crosstool-NG ======================================================================== -Instead of downloading binary toolchain from Espressif website (Step 1 above) you may build the toolchain yourself. +Instead of downloading binary toolchain from Espressif website (Step 1 above) you may build the toolchain yourself. If you can't think of a reason why you need to build it yourself, then probably it's better to stick with the binary version. However, here are some of the reasons why you might want to compile it from source: - if you want to customize toolchain build configuration +- if you want to use a different GCC version (such as 4.8.5) + - if you want to hack gcc or newlib or libstdc++ - if you are curious and/or have time to spare @@ -79,7 +81,7 @@ In any case, here are the steps to compile the toolchain yourself. - Ubuntu:: - sudo apt-get install gawk gperf grep gettext ncurses python python-dev automake bison flex texinfo help2man libtool + sudo apt-get install gawk gperf grep gettext libncurses-dev python python-dev automake bison flex texinfo help2man libtool - Debian:: @@ -113,7 +115,7 @@ Open terminal, navigate to the directory you want to clone ESP-IDF and clone it git clone --recursive https://github.com/espressif/esp-idf.git -ESP-IDF will be downloaded into ``~/esp/esp-idf``. +ESP-IDF will be downloaded into ``~/esp/esp-idf``. Note the ``--recursive`` option! If you have already cloned ESP-IDF without this option, run another command to get all the submodules:: @@ -142,7 +144,7 @@ In terminal, go to the application directory which was obtained on the previous cd ~/esp/myapp -Type a command like this to set the path to ESP-IDF directory:: +Type a command like this to set the path to ESP-IDF directory:: export IDF_PATH=~/esp/esp-idf diff --git a/docs/macos-setup.rst b/docs/macos-setup.rst index 67a1fa9906..ac772c5298 100644 --- a/docs/macos-setup.rst +++ b/docs/macos-setup.rst @@ -23,13 +23,13 @@ Step 1: Download binary toolchain for the ESP32 ESP32 toolchain for macOS is available for download from Espressif website: -https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-59.tar.gz +https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-61-gab8375a-5.2.0.tar.gz Download this file, then extract it to the location you prefer, for example:: mkdir -p ~/esp cd ~/esp - tar -xzf ~/Downloads/xtensa-esp32-elf-osx-1.22.0-59.tar.gz + tar -xzf ~/Downloads/xtensa-esp32-elf-osx-1.22.0-61-gab8375a-5.2.0.tar.gz The toolchain will be extracted into ``~/esp/xtensa-esp32-elf/`` directory. @@ -46,12 +46,14 @@ Then when you need the toolchain you can type ``get_esp32`` on the command line Alternative Step 1: Compile the toolchain from source using crosstool-NG ======================================================================== -Instead of downloading binary toolchain from Espressif website (Step 1 above) you may build the toolchain yourself. +Instead of downloading binary toolchain from Espressif website (Step 1 above) you may build the toolchain yourself. If you can't think of a reason why you need to build it yourself, then probably it's better to stick with the binary version. However, here are some of the reasons why you might want to compile it from source: - if you want to customize toolchain build configuration +- if you want to use a different GCC version (such as 4.8.5) + - if you want to hack gcc or newlib or libstdc++ - if you are curious and/or have time to spare @@ -107,7 +109,7 @@ Open Terminal.app, navigate to the directory you want to clone ESP-IDF and clone git clone --recursive https://github.com/espressif/esp-idf.git -ESP-IDF will be downloaded into ``~/esp/esp-idf``. +ESP-IDF will be downloaded into ``~/esp/esp-idf``. Note the ``--recursive`` option! If you have already cloned ESP-IDF without this option, run another command to get all the submodules:: @@ -136,7 +138,7 @@ In Terminal.app, go to the application directory which was obtained on the previ cd ~/esp/myapp -Type a command like this to set the path to ESP-IDF directory:: +Type a command like this to set the path to ESP-IDF directory:: export IDF_PATH=~/esp/esp-idf @@ -160,4 +162,3 @@ Further reading =============== If you'd like to use the Eclipse IDE instead of running ``make``, check out the Eclipse setup guide in this directory. - diff --git a/docs/windows-setup.rst b/docs/windows-setup.rst index a425f5b3a0..00205fb78b 100644 --- a/docs/windows-setup.rst +++ b/docs/windows-setup.rst @@ -9,7 +9,7 @@ Windows doesn't have a built-in "make" environment, so as well as installing the The quick setup is to download the Windows all-in-one toolchain & MSYS zip file from dl.espressif.com: -https://dl.espressif.com/dl/esp32_win32_msys2_environment_and_toolchain-20160816.zip +https://dl.espressif.com/dl/esp32_win32_msys2_environment_and_toolchain-20170111.zip Unzip the zip file to C:\ and it will create an "msys32" directory with a pre-prepared environment. @@ -38,7 +38,7 @@ Another Alternative Step 1: Just download a toolchain If you already have an MSYS2 install or want to do things differently, you can download just the toolchain here: -https://dl.espressif.com/dl/xtensa-esp32-elf-win32-1.22.0-59.zip +https://dl.espressif.com/dl/xtensa-esp32-elf-win32-1.22.0-61-gab8375a-5.2.0.zip If you followed one of the above options for Step 1, you won't need this download. diff --git a/make/project.mk b/make/project.mk index ee8c077943..f41e434393 100644 --- a/make/project.mk +++ b/make/project.mk @@ -421,3 +421,30 @@ endef # filter/subst in expression ensures all submodule paths begin with $(IDF_PATH), and then strips that prefix # so the argument is suitable for use with 'git submodule' commands $(foreach submodule,$(subst $(IDF_PATH)/,,$(filter $(IDF_PATH)/%,$(COMPONENT_SUBMODULES))),$(eval $(call GenerateSubmoduleCheckTarget,$(submodule)))) + + +# Check toolchain version using the output of xtensa-esp32-elf-gcc --version command. +# The output normally looks as follows +# xtensa-esp32-elf-gcc (crosstool-NG crosstool-ng-1.22.0-59-ga194053) 4.8.5 +# The part in brackets is extracted into TOOLCHAIN_COMMIT_DESC variable, +# the part after the brackets is extracted into TOOLCHAIN_GCC_VER. +ifndef MAKE_RESTARTS +TOOLCHAIN_COMMIT_DESC := $(shell $(CC) --version | sed -E -n 's|xtensa-esp32-elf-gcc\ \(([^)]*).*|\1|gp') +TOOLCHAIN_GCC_VER := $(shell $(CC) --version | sed -E -n 's|xtensa-esp32-elf-gcc\ \(.*\)\ (.*)|\1|gp') + +# Officially supported version(s) +SUPPORTED_TOOLCHAIN_COMMIT_DESC := crosstool-NG crosstool-ng-1.22.0-61-gab8375a +SUPPORTED_TOOLCHAIN_GCC_VERSIONS := 5.2.0 + +ifneq ($(TOOLCHAIN_COMMIT_DESC), $(SUPPORTED_TOOLCHAIN_COMMIT_DESC)) +$(info WARNING: Toolchain version is not supported: $(TOOLCHAIN_COMMIT_DESC)) +$(info Expected to see version: $(SUPPORTED_TOOLCHAIN_COMMIT_DESC)) +$(info Please check ESP-IDF setup instructions and update the toolchain, or proceed at your own risk.) +endif +ifeq (,$(findstring $(TOOLCHAIN_GCC_VER), $(SUPPORTED_TOOLCHAIN_GCC_VERSIONS))) +$(warning WARNING: Compiler version is not supported: $(TOOLCHAIN_GCC_VER)) +$(info Expected to see version(s): $(SUPPORTED_TOOLCHAIN_GCC_VERSIONS)) +$(info Please check ESP-IDF setup instructions and update the toolchain, or proceed at your own risk.) +endif +endif #MAKE_RESTARTS + From 356e01545c08888463442fa3c3c7adbec1658a97 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Wed, 11 Jan 2017 16:13:33 +0800 Subject: [PATCH 131/167] Add test for spi clock, fix corner cases) --- components/driver/spi_master.c | 7 +-- components/driver/test/test_spi_master.c | 62 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/components/driver/spi_master.c b/components/driver/spi_master.c index 53be8b29de..6162edba36 100644 --- a/components/driver/spi_master.c +++ b/components/driver/spi_master.c @@ -318,6 +318,7 @@ esp_err_t spi_bus_add_device(spi_host_device_t host, spi_device_interface_config SPI_CHECK(host>=SPI_HOST && host<=VSPI_HOST, "invalid host", ESP_ERR_INVALID_ARG); SPI_CHECK(spihost[host]!=NULL, "host not initialized", ESP_ERR_INVALID_STATE); SPI_CHECK(dev_config->spics_io_num < 0 || GPIO_IS_VALID_OUTPUT_GPIO(dev_config->spics_io_num), "spics pin invalid", ESP_ERR_INVALID_ARG); + SPI_CHECK(dev_config->clock_speed_hz > 0, "invalid sclk speed", ESP_ERR_INVALID_ARG); for (freecs=0; freecsdevice[freecs], NULL, (spi_device_t *)1)) break; @@ -412,15 +413,15 @@ static void spi_set_clock(spi_dev_t *hw, int fapb, int hz, int duty_cycle) { //with the higher n. int bestn=-1; int bestpre=-1; - int besterr=hz; + int besterr=0; int errval; for (n=1; n<=64; n++) { //Effectively, this does pre=round((fapb/n)/hz). pre=((fapb/n)+(hz/2))/hz; - if (pre<0) pre=0; + if (pre<=0) pre=1; if (pre>8192) pre=8192; errval=abs(spi_freq_for_pre_n(fapb, pre, n)-hz); - if (errval<=besterr) { + if (bestn==-1 || errval<=besterr) { besterr=errval; bestn=n; bestpre=pre; diff --git a/components/driver/test/test_spi_master.c b/components/driver/test/test_spi_master.c index 119c94bc57..d54cc5a633 100644 --- a/components/driver/test/test_spi_master.c +++ b/components/driver/test/test_spi_master.c @@ -15,8 +15,70 @@ #include "freertos/xtensa_api.h" #include "unity.h" #include "driver/spi_master.h" +#include "soc/dport_reg.h" +#include "soc/spi_reg.h" +#include "soc/spi_struct.h" +static void check_spi_pre_n_for(int clk, int pre, int n) +{ + esp_err_t ret; + spi_device_handle_t handle; + + spi_device_interface_config_t devcfg={ + .command_bits=0, + .address_bits=0, + .dummy_bits=0, + .clock_speed_hz=clk, + .duty_cycle_pos=128, + .mode=0, + .spics_io_num=21, + .queue_size=3 + }; + char sendbuf[16]=""; + spi_transaction_t t; + memset(&t, 0, sizeof(t)); + + ret=spi_bus_add_device(HSPI_HOST, &devcfg, &handle); + TEST_ASSERT(ret==ESP_OK); + + t.length=16*8; + t.tx_buffer=sendbuf; + ret=spi_device_transmit(handle, &t); + + printf("Checking clk rate %dHz. expect pre %d n %d, got pre %d n %d\n", clk, pre, n, SPI2.clock.clkdiv_pre+1, SPI2.clock.clkcnt_n+1); + + TEST_ASSERT(SPI2.clock.clkcnt_n+1==n); + TEST_ASSERT(SPI2.clock.clkdiv_pre+1==pre); + + ret=spi_bus_remove_device(handle); + TEST_ASSERT(ret==ESP_OK); +} + + +TEST_CASE("SPI Master clockdiv calculation routines", "[spi]") +{ + spi_bus_config_t buscfg={ + .mosi_io_num=4, + .miso_io_num=16, + .sclk_io_num=25, + .quadwp_io_num=-1, + .quadhd_io_num=-1 + }; + esp_err_t ret; + ret=spi_bus_initialize(HSPI_HOST, &buscfg, 1); + TEST_ASSERT(ret==ESP_OK); + + check_spi_pre_n_for(8000000, 1, 10); + check_spi_pre_n_for(800000, 2, 50); + check_spi_pre_n_for(100000, 16, 50); + check_spi_pre_n_for(333333, 4, 60); + check_spi_pre_n_for(1, 8192, 64); //Actually should generate the minimum clock speed, 152Hz + + ret=spi_bus_free(HSPI_HOST); + TEST_ASSERT(ret==ESP_OK); +} + TEST_CASE("SPI Master test", "[spi]") { From d512d6100cb65804305b4f33084df0142811f771 Mon Sep 17 00:00:00 2001 From: Yulong Date: Fri, 23 Dec 2016 11:28:47 -0500 Subject: [PATCH 132/167] component bt:Added the create attribute table method to the new API --- components/bt/bluedroid/api/esp_blufi_api.c | 14 +- components/bt/bluedroid/api/esp_bt_main.c | 12 +- components/bt/bluedroid/api/esp_gap_ble_api.c | 28 +- components/bt/bluedroid/api/esp_gattc_api.c | 50 +-- components/bt/bluedroid/api/esp_gatts_api.c | 124 +++++-- .../bluedroid/api/include/esp_gap_ble_api.h | 225 ++++++------ .../bt/bluedroid/api/include/esp_gatt_defs.h | 290 ++++++++++----- .../bt/bluedroid/api/include/esp_gatts_api.h | 276 ++++++++------ .../bt/bluedroid/bta/gatt/bta_gatts_act.c | 61 +++- .../bt/bluedroid/bta/gatt/bta_gatts_api.c | 66 +++- .../bt/bluedroid/bta/gatt/bta_gatts_main.c | 13 +- .../bt/bluedroid/bta/include/bta_gatt_api.h | 174 +++++---- .../bt/bluedroid/bta/include/bta_gatts_int.h | 24 +- .../btc/profile/esp/blufi/blufi_prf.c | 10 +- .../btc/profile/std/gatt/btc_gatts.c | 291 ++++++++++++++- .../btc/profile/std/include/btc_gatts.h | 37 +- .../bt/bluedroid/hci/packet_fragmenter.c | 9 +- components/bt/bluedroid/stack/gap/gap_ble.c | 12 +- components/bt/bluedroid/stack/gatt/gatt_api.c | 112 ++++-- .../bt/bluedroid/stack/gatt/gatt_attr.c | 11 +- components/bt/bluedroid/stack/gatt/gatt_db.c | 333 +++++++++++++++-- .../bt/bluedroid/stack/gatt/gatt_main.c | 6 +- components/bt/bluedroid/stack/gatt/gatt_sr.c | 96 +++-- .../bt/bluedroid/stack/gatt/gatt_utils.c | 30 +- .../bluedroid/stack/gatt/include/gatt_int.h | 120 ++++--- .../bt/bluedroid/stack/include/gatt_api.h | 91 +++-- components/bt/bluedroid/stack/smp/smp_main.c | 2 +- docs/api/esp_blufi.rst | 3 +- docs/api/esp_gatt_defs.rst | 23 ++ docs/api/esp_gatts.rst | 9 + examples/14_gatt_server/main/gatts_demo.c | 39 +- examples/30_gatt_server_table_create/Makefile | 11 + .../30_gatt_server_table_create/README.rst | 10 + .../main/component.mk | 8 + .../main/gatts_table_creat_demo.c | 340 ++++++++++++++++++ .../main/gatts_table_creat_demo.h | 57 +++ .../sdkconfig.defaults | 14 + 37 files changed, 2360 insertions(+), 671 deletions(-) create mode 100644 examples/30_gatt_server_table_create/Makefile create mode 100644 examples/30_gatt_server_table_create/README.rst create mode 100644 examples/30_gatt_server_table_create/main/component.mk create mode 100644 examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c create mode 100644 examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h create mode 100644 examples/30_gatt_server_table_create/sdkconfig.defaults diff --git a/components/bt/bluedroid/api/esp_blufi_api.c b/components/bt/bluedroid/api/esp_blufi_api.c index 70f5c9ce8f..00fbeffbc9 100644 --- a/components/bt/bluedroid/api/esp_blufi_api.c +++ b/components/bt/bluedroid/api/esp_blufi_api.c @@ -25,9 +25,9 @@ esp_err_t esp_blufi_register_callbacks(esp_blufi_callbacks_t *callbacks) { if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + if (callbacks == NULL) { return ESP_FAIL; } @@ -42,9 +42,9 @@ esp_err_t esp_blufi_send_wifi_conn_report(wifi_mode_t opmode, esp_blufi_sta_conn btc_blufi_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_BLUFI; msg.act = BTC_BLUFI_ACT_SEND_CFG_REPORT; @@ -62,7 +62,7 @@ esp_err_t esp_blufi_profile_init(void) btc_msg_t msg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -77,9 +77,9 @@ esp_err_t esp_blufi_profile_deinit(void) btc_msg_t msg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_BLUFI; msg.act = BTC_BLUFI_ACT_DEINIT; diff --git a/components/bt/bluedroid/api/esp_bt_main.c b/components/bt/bluedroid/api/esp_bt_main.c index 3093b56403..c9fe4fc060 100644 --- a/components/bt/bluedroid/api/esp_bt_main.c +++ b/components/bt/bluedroid/api/esp_bt_main.c @@ -24,13 +24,13 @@ static bool esp_already_init = false; esp_bluedroid_status_t esp_bluedroid_get_status(void) { if (esp_already_init) { - if (esp_already_enable) { - return ESP_BLUEDROID_STATUS_ENABLED; - } else { - return ESP_BLUEDROID_STATUS_INITIALIZED; - } + if (esp_already_enable) { + return ESP_BLUEDROID_STATUS_ENABLED; + } else { + return ESP_BLUEDROID_STATUS_INITIALIZED; + } } else { - return ESP_BLUEDROID_STATUS_UNINITIALIZED; + return ESP_BLUEDROID_STATUS_UNINITIALIZED; } } diff --git a/components/bt/bluedroid/api/esp_gap_ble_api.c b/components/bt/bluedroid/api/esp_gap_ble_api.c index f9401d8c42..56bedc4242 100644 --- a/components/bt/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/bluedroid/api/esp_gap_ble_api.c @@ -25,7 +25,7 @@ esp_err_t esp_ble_gap_register_callback(esp_gap_ble_cb_t callback) { if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } return (btc_profile_cb_set(BTC_PID_GAP_BLE, callback) == 0 ? ESP_OK : ESP_FAIL); } @@ -37,7 +37,7 @@ esp_err_t esp_ble_gap_config_adv_data(esp_ble_adv_data_t *adv_data) btc_ble_gap_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } if (adv_data == NULL) { @@ -64,7 +64,7 @@ esp_err_t esp_ble_gap_set_scan_params(esp_ble_scan_params_t *scan_params) btc_ble_gap_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } if (scan_params == NULL) { @@ -85,7 +85,7 @@ esp_err_t esp_ble_gap_start_scanning(uint32_t duration) btc_ble_gap_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -102,7 +102,7 @@ esp_err_t esp_ble_gap_stop_scanning(void) btc_msg_t msg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -116,8 +116,8 @@ esp_err_t esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params) btc_msg_t msg; btc_ble_gap_args_t arg; - if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -133,7 +133,7 @@ esp_err_t esp_ble_gap_stop_advertising(void) btc_msg_t msg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -150,7 +150,7 @@ esp_err_t esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params) btc_ble_gap_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -167,7 +167,7 @@ esp_err_t esp_ble_gap_set_pkt_data_len(esp_bd_addr_t remote_device, uint16_t tx_ btc_ble_gap_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -186,7 +186,7 @@ esp_err_t esp_ble_gap_set_rand_addr(esp_bd_addr_t rand_addr) btc_ble_gap_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -204,7 +204,7 @@ esp_err_t esp_ble_gap_config_local_privacy (bool privacy_enable) btc_ble_gap_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -221,9 +221,9 @@ esp_err_t esp_ble_gap_set_device_name(const char *name) btc_ble_gap_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + if (strlen(name) > ESP_GAP_DEVICE_NAME_MAX) { return ESP_ERR_INVALID_ARG; } diff --git a/components/bt/bluedroid/api/esp_gattc_api.c b/components/bt/bluedroid/api/esp_gattc_api.c index 8b9cc99e87..4f68d81cc0 100644 --- a/components/bt/bluedroid/api/esp_gattc_api.c +++ b/components/bt/bluedroid/api/esp_gattc_api.c @@ -23,9 +23,9 @@ esp_err_t esp_ble_gattc_register_callback(esp_gattc_cb_t callback) { if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + if (callback == NULL) { return ESP_FAIL; } @@ -40,9 +40,9 @@ esp_err_t esp_ble_gattc_app_register(uint16_t app_id) btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + if (app_id > ESP_APP_ID_MAX) { return ESP_ERR_INVALID_ARG; } @@ -61,7 +61,7 @@ esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gattc_if) btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -78,9 +78,9 @@ esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, b btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_OPEN; @@ -97,7 +97,7 @@ esp_err_t esp_ble_gattc_close (esp_gatt_if_t gattc_if, uint16_t conn_id) btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -114,7 +114,7 @@ esp_err_t esp_ble_gattc_config_mtu (esp_gatt_if_t gattc_if, uint16_t conn_id, ui btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } if ((mtu < ESP_GATT_DEF_BLE_MTU_SIZE) || (mtu > ESP_GATT_MAX_MTU_SIZE)) { @@ -136,7 +136,7 @@ esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -163,7 +163,7 @@ esp_err_t esp_ble_gattc_get_characteristic(esp_gatt_if_t gattc_if, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -192,9 +192,9 @@ esp_err_t esp_ble_gattc_get_descriptor(esp_gatt_if_t gattc_if, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; @@ -223,7 +223,7 @@ esp_err_t esp_ble_gattc_get_included_service(esp_gatt_if_t gattc_if, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -253,7 +253,7 @@ esp_err_t esp_ble_gattc_read_char (esp_gatt_if_t gattc_if, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -278,7 +278,7 @@ esp_err_t esp_ble_gattc_read_char_descr (esp_gatt_if_t gattc_if, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -299,14 +299,14 @@ esp_err_t esp_ble_gattc_write_char( esp_gatt_if_t gattc_if, esp_gatt_id_t *char_id, uint16_t value_len, uint8_t *value, - esp_gatt_write_type_t write_type, + esp_gatt_write_type_t write_type, esp_gatt_auth_req_t auth_req) { btc_msg_t msg; btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -330,16 +330,16 @@ esp_err_t esp_ble_gattc_write_char_descr (esp_gatt_if_t gattc_if, esp_gatt_id_t *descr_id, uint16_t value_len, uint8_t *value, - esp_gatt_write_type_t write_type, + esp_gatt_write_type_t write_type, esp_gatt_auth_req_t auth_req) { btc_msg_t msg; btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_WRITE_CHAR_DESCR; @@ -369,7 +369,7 @@ esp_err_t esp_ble_gattc_prepare_write(esp_gatt_if_t gattc_if, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -392,7 +392,7 @@ esp_err_t esp_ble_gattc_execute_write (esp_gatt_if_t gattc_if, uint16_t conn_id, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -413,7 +413,7 @@ esp_gatt_status_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gattc_if, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -436,7 +436,7 @@ esp_gatt_status_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gattc_if, btc_ble_gattc_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; diff --git a/components/bt/bluedroid/api/esp_gatts_api.c b/components/bt/bluedroid/api/esp_gatts_api.c index 71b5a4338c..f5ebe59a2d 100644 --- a/components/bt/bluedroid/api/esp_gatts_api.c +++ b/components/bt/bluedroid/api/esp_gatts_api.c @@ -22,10 +22,11 @@ #define COPY_TO_GATTS_ARGS(_gatt_args, _arg, _arg_type) memcpy(_gatt_args, _arg, sizeof(_arg_type)) + esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback) { if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } return (btc_profile_cb_set(BTC_PID_GATTS, callback) == 0 ? ESP_OK : ESP_FAIL); } @@ -36,7 +37,7 @@ esp_err_t esp_ble_gatts_app_register(uint16_t app_id) btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } //if (app_id < ESP_APP_ID_MIN || app_id > ESP_APP_ID_MAX) { @@ -59,9 +60,9 @@ esp_err_t esp_ble_gatts_app_unregister(esp_gatt_if_t gatts_if) btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_APP_UNREGISTER; @@ -77,7 +78,7 @@ esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -90,6 +91,26 @@ esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } +esp_err_t esp_ble_gatts_create_attr_tab(const esp_gatts_attr_db_t *gatts_attr_db, + esp_gatt_if_t gatts_if, + uint8_t max_nb_attr, + uint8_t srvc_inst_id) +{ + btc_msg_t msg; + btc_ble_gatts_args_t arg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GATTS; + msg.act = BTC_GATTS_ACT_CREATE_ATTR_TAB; + arg.create_attr_tab.gatts_if = gatts_if; + arg.create_attr_tab.max_nb_attr = max_nb_attr; + arg.create_attr_tab.srvc_inst_id = srvc_inst_id; + arg.create_attr_tab.gatts_attr_db = (esp_gatts_attr_db_t *)gatts_attr_db; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), btc_gatts_arg_deep_copy) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + esp_err_t esp_ble_gatts_add_included_service(uint16_t service_handle, uint16_t included_service_handle) { @@ -97,7 +118,7 @@ esp_err_t esp_ble_gatts_add_included_service(uint16_t service_handle, uint16_t i btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -111,46 +132,69 @@ esp_err_t esp_ble_gatts_add_included_service(uint16_t service_handle, uint16_t i esp_err_t esp_ble_gatts_add_char(uint16_t service_handle, esp_bt_uuid_t *char_uuid, - esp_gatt_perm_t perm, esp_gatt_char_prop_t property) + esp_gatt_perm_t perm, esp_gatt_char_prop_t property, esp_attr_value_t *char_val, + esp_attr_control_t *control) { btc_msg_t msg; btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + + memset(&arg, 0, sizeof(btc_ble_gatts_args_t)); msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_ADD_CHAR; arg.add_char.service_handle = service_handle; arg.add_char.perm = perm; arg.add_char.property = property; + if (char_val != NULL) { + arg.add_char.char_val.attr_max_len = char_val->attr_max_len; + arg.add_char.char_val.attr_len = char_val->attr_len; + arg.add_char.char_val.attr_value = char_val->attr_value; + } + + if (control != NULL) { + arg.add_char.attr_control.auto_rsp = control->auto_rsp; + } memcpy(&arg.add_char.char_uuid, char_uuid, sizeof(esp_bt_uuid_t)); - return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } esp_err_t esp_ble_gatts_add_char_descr (uint16_t service_handle, esp_bt_uuid_t *descr_uuid, - esp_gatt_perm_t perm) + esp_gatt_perm_t perm, esp_attr_value_t *char_descr_val, + esp_attr_control_t *control) { btc_msg_t msg; btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } + memset(&arg, 0, sizeof(btc_ble_gatts_args_t)); msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_ADD_CHAR_DESCR; arg.add_descr.service_handle = service_handle; arg.add_descr.perm = perm; + + if (char_descr_val != NULL) { + arg.add_descr.descr_val.attr_max_len = char_descr_val->attr_max_len; + arg.add_descr.descr_val.attr_len = char_descr_val->attr_len; + arg.add_descr.descr_val.attr_value = char_descr_val->attr_value; + } + + if (control != NULL) { + arg.add_descr.attr_control.auto_rsp = control->auto_rsp; + } memcpy(&arg.add_descr.descr_uuid, descr_uuid, sizeof(esp_bt_uuid_t)); - return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } esp_err_t esp_ble_gatts_delete_service(uint16_t service_handle) @@ -159,7 +203,7 @@ esp_err_t esp_ble_gatts_delete_service(uint16_t service_handle) btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -176,7 +220,7 @@ esp_err_t esp_ble_gatts_start_service(uint16_t service_handle) btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -193,9 +237,9 @@ esp_err_t esp_ble_gatts_stop_service(uint16_t service_handle) btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_STOP_SERVICE; @@ -212,7 +256,7 @@ esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -224,7 +268,8 @@ esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, arg.send_ind.value_len = value_len; arg.send_ind.value = value; - return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), + btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, uint32_t trans_id, @@ -234,7 +279,7 @@ esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -245,7 +290,32 @@ esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, arg.send_rsp.status = status; arg.send_rsp.rsp = rsp; - return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), + btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_gatts_set_attr_value(uint16_t attr_handle, uint16_t length, const uint8_t *value) +{ + btc_msg_t msg; + btc_ble_gatts_args_t arg; + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GATTS; + msg.act = BTC_GATTS_ACT_SET_ATTR_VALUE; + arg.set_attr_val.length = length; + arg.set_attr_val.value = (uint8_t *)value; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), + btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value) +{ + if (attr_handle == ESP_GATT_ILLEGAL_HANDLE) { + return ESP_FAIL; + } + btc_gatts_get_attr_value(attr_handle, length, (uint8_t **)value); + return ESP_OK; } esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatts_if, esp_bd_addr_t remote_bda, bool is_direct) @@ -254,7 +324,7 @@ esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatts_if, esp_bd_addr_t remote_bda, b btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } msg.sig = BTC_SIG_API_CALL; @@ -264,7 +334,8 @@ esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatts_if, esp_bd_addr_t remote_bda, b arg.open.is_direct = is_direct; memcpy(&arg.open.remote_bda, remote_bda, sizeof(esp_bd_addr_t)); - return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } esp_err_t esp_ble_gatts_close(esp_gatt_if_t gatts_if, uint16_t conn_id) @@ -273,13 +344,14 @@ esp_err_t esp_ble_gatts_close(esp_gatt_if_t gatts_if, uint16_t conn_id) btc_ble_gatts_args_t arg; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { - return ESP_ERR_INVALID_STATE; + return ESP_ERR_INVALID_STATE; } - + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_CLOSE; 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); + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t), NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } diff --git a/components/bt/bluedroid/api/include/esp_gap_ble_api.h b/components/bt/bluedroid/api/include/esp_gap_ble_api.h index f92143e6af..1d27edb881 100644 --- a/components/bt/bluedroid/api/include/esp_gap_ble_api.h +++ b/components/bt/bluedroid/api/include/esp_gap_ble_api.h @@ -40,42 +40,44 @@ extern "C" { /// GAP BLE callback event type typedef enum { - ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT = 0, /*!< When advertising data set complete, the event comes */ - ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT , /*!< When scan response data set complete, the event comes */ - ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, /*!< When scan parameters set complete, the event comes */ - ESP_GAP_BLE_SCAN_RESULT_EVT, /*!< When one scan result ready, the event comes each time */ + ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT = 0, /*!< When advertising data set complete, the event comes */ + ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT, /*!< When scan response data set complete, the event comes */ + ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, /*!< When scan parameters set complete, the event comes */ + ESP_GAP_BLE_SCAN_RESULT_EVT, /*!< When one scan result ready, the event comes each time */ } esp_gap_ble_cb_event_t; /// Advertising data maximum length -#define ESP_BLE_ADV_DATA_LEN_MAX 31 +#define ESP_BLE_ADV_DATA_LEN_MAX 31 +/// Scan response data maximum length +#define ESP_BLE_SCAN_RSP_DATA_LEN_MAX 31 /// The type of advertising data(not adv_type) typedef enum { - ESP_BLE_AD_TYPE_FLAG = 0x01, - ESP_BLE_AD_TYPE_16SRV_PART = 0x02, - ESP_BLE_AD_TYPE_16SRV_CMPL = 0x03, - ESP_BLE_AD_TYPE_32SRV_PART = 0x04, - ESP_BLE_AD_TYPE_32SRV_CMPL = 0x05, - ESP_BLE_AD_TYPE_128SRV_PART = 0x06, - ESP_BLE_AD_TYPE_128SRV_CMPL = 0x07, - ESP_BLE_AD_TYPE_NAME_SHORT = 0x08, - ESP_BLE_AD_TYPE_NAME_CMPL = 0x09, - ESP_BLE_AD_TYPE_TX_PWR = 0x0A, - ESP_BLE_AD_TYPE_DEV_CLASS = 0x0D, - ESP_BLE_AD_TYPE_SM_TK = 0x10, - ESP_BLE_AD_TYPE_SM_OOB_FLAG = 0x11, - ESP_BLE_AD_TYPE_INT_RANGE = 0x12, - ESP_BLE_AD_TYPE_SOL_SRV_UUID = 0x14, - ESP_BLE_AD_TYPE_128SOL_SRV_UUID = 0x15, - ESP_BLE_AD_TYPE_SERVICE_DATA = 0x16, - ESP_BLE_AD_TYPE_PUBLIC_TARGET = 0x17, - ESP_BLE_AD_TYPE_RANDOM_TARGET = 0x18, - ESP_BLE_AD_TYPE_APPEARANCE = 0x19, - ESP_BLE_AD_TYPE_ADV_INT = 0x1A, - ESP_BLE_AD_TYPE_32SOL_SRV_UUID = 0x1B, - ESP_BLE_AD_TYPE_32SERVICE_DATA = 0x1C, - ESP_BLE_AD_TYPE_128SERVICE_DATA = 0x1D, - ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE = 0xFF, + ESP_BLE_AD_TYPE_FLAG = 0x01, + ESP_BLE_AD_TYPE_16SRV_PART = 0x02, + ESP_BLE_AD_TYPE_16SRV_CMPL = 0x03, + ESP_BLE_AD_TYPE_32SRV_PART = 0x04, + ESP_BLE_AD_TYPE_32SRV_CMPL = 0x05, + ESP_BLE_AD_TYPE_128SRV_PART = 0x06, + ESP_BLE_AD_TYPE_128SRV_CMPL = 0x07, + ESP_BLE_AD_TYPE_NAME_SHORT = 0x08, + ESP_BLE_AD_TYPE_NAME_CMPL = 0x09, + ESP_BLE_AD_TYPE_TX_PWR = 0x0A, + ESP_BLE_AD_TYPE_DEV_CLASS = 0x0D, + ESP_BLE_AD_TYPE_SM_TK = 0x10, + ESP_BLE_AD_TYPE_SM_OOB_FLAG = 0x11, + ESP_BLE_AD_TYPE_INT_RANGE = 0x12, + ESP_BLE_AD_TYPE_SOL_SRV_UUID = 0x14, + ESP_BLE_AD_TYPE_128SOL_SRV_UUID = 0x15, + ESP_BLE_AD_TYPE_SERVICE_DATA = 0x16, + ESP_BLE_AD_TYPE_PUBLIC_TARGET = 0x17, + ESP_BLE_AD_TYPE_RANDOM_TARGET = 0x18, + ESP_BLE_AD_TYPE_APPEARANCE = 0x19, + ESP_BLE_AD_TYPE_ADV_INT = 0x1A, + ESP_BLE_AD_TYPE_32SOL_SRV_UUID = 0x1B, + ESP_BLE_AD_TYPE_32SERVICE_DATA = 0x1C, + ESP_BLE_AD_TYPE_128SERVICE_DATA = 0x1D, + ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE = 0xFF, } esp_ble_adv_data_type; /// Advertising mode @@ -109,37 +111,37 @@ typedef enum { /// 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 Advertising max interval */ - 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 Advertising max interval */ + 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; /// Advertising data content, according to "Supplement to the Bluetooth Core Specification" 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 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 */ - 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 */ + 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 */ + 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 */ } esp_ble_adv_data_t; /// Own BD address source of the device @@ -160,53 +162,53 @@ typedef enum { /// Ble scan type typedef enum { - BLE_SCAN_TYPE_PASSIVE = 0x0, /*!< Passive scan */ - BLE_SCAN_TYPE_ACTIVE = 0x1, /*!< Active scan */ + BLE_SCAN_TYPE_PASSIVE = 0x0, /*!< Passive scan */ + BLE_SCAN_TYPE_ACTIVE = 0x1, /*!< Active scan */ } esp_ble_scan_type_t; /// Ble scan filter type typedef enum { - BLE_SCAN_FILTER_ALLOW_ALL = 0x0, /*!< Accept all : - 1. advertisement packets except directed advertising packets not addressed to this device (default). */ + BLE_SCAN_FILTER_ALLOW_ALL = 0x0, /*!< Accept all : + 1. advertisement packets except directed advertising packets not addressed to this device (default). */ BLE_SCAN_FILTER_ALLOW_ONLY_WLST = 0x1, /*!< Accept only : - 1. advertisement packets from devices where the advertiser’s address is in the White list. - 2. Directed advertising packets which are not addressed for this device shall be ignored. */ + 1. advertisement packets from devices where the advertiser’s address is in the White list. + 2. Directed advertising packets which are not addressed for this device shall be ignored. */ BLE_SCAN_FILTER_ALLOW_UND_RPA_DIR = 0x2, /*!< Accept all : - 1. undirected advertisement packets, and - 2. directed advertising packets where the initiator address is a resolvable private address, and - 3. directed advertising packets addressed to this device. */ + 1. undirected advertisement packets, and + 2. directed advertising packets where the initiator address is a resolvable private address, and + 3. directed advertising packets addressed to this device. */ BLE_SCAN_FILTER_ALLOW_WLIST_PRA_DIR = 0x3, /*!< Accept all : - 1. advertisement packets from devices where the advertiser’s address is in the White list, and - 2. directed advertising packets where the initiator address is a resolvable private address, and - 3. directed advertising packets addressed to this device.*/ + 1. advertisement packets from devices where the advertiser’s address is in the White list, and + 2. directed advertising packets where the initiator address is a resolvable private address, and + 3. directed advertising packets addressed to this device.*/ } esp_ble_scan_filter_t; /// Ble scan parameters 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 */ + 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 */ } esp_ble_scan_params_t; /// Connection update parameters typedef struct { - esp_bd_addr_t bda; /*!< Bluetooth device address */ - uint16_t min_int; /*!< Min connection interval */ - uint16_t max_int; /*!< Max connection interval */ - uint16_t latency; /*!< Slave latency for the connection in number of connection events. Range: 0x0000 to 0x01F3 */ - uint16_t timeout; /*!< Supervision timeout for the LE Link. Range: 0x000A to 0x0C80. - Mandatory Range: 0x000A to 0x0C80 Time = N * 10 msec - Time Range: 100 msec to 32 seconds */ + esp_bd_addr_t bda; /*!< Bluetooth device address */ + uint16_t min_int; /*!< Min connection interval */ + uint16_t max_int; /*!< Max connection interval */ + uint16_t latency; /*!< Slave latency for the connection in number of connection events. Range: 0x0000 to 0x01F3 */ + uint16_t timeout; /*!< Supervision timeout for the LE Link. Range: 0x000A to 0x0C80. + Mandatory Range: 0x000A to 0x0C80 Time = N * 10 msec + Time Range: 100 msec to 32 seconds */ } esp_ble_conn_update_params_t; /// Sub Event of ESP_GAP_BLE_SCAN_RESULT_EVT @@ -225,11 +227,11 @@ typedef enum { * result is scan response or advertising data or other */ typedef enum { - ESP_BLE_EVT_CONN_ADV = 0x00, /*!< Connectable undirected advertising (ADV_IND) */ - ESP_BLE_EVT_CONN_DIR_ADV = 0x01, /*!< Connectable directed advertising (ADV_DIRECT_IND) */ - ESP_BLE_EVT_DISC_ADV = 0x02, /*!< Scannable undirected advertising (ADV_SCAN_IND) */ - ESP_BLE_EVT_NON_CONN_ADV = 0x03, /*!< Non connectable undirected advertising (ADV_NONCONN_IND) */ - ESP_BLE_EVT_SCAN_RSP = 0x04, /*!< Scan Response (SCAN_RSP) */ + ESP_BLE_EVT_CONN_ADV = 0x00, /*!< Connectable undirected advertising (ADV_IND) */ + ESP_BLE_EVT_CONN_DIR_ADV = 0x01, /*!< Connectable directed advertising (ADV_DIRECT_IND) */ + ESP_BLE_EVT_DISC_ADV = 0x02, /*!< Scannable undirected advertising (ADV_SCAN_IND) */ + ESP_BLE_EVT_NON_CONN_ADV = 0x03, /*!< Non connectable undirected advertising (ADV_NONCONN_IND) */ + ESP_BLE_EVT_SCAN_RSP = 0x04, /*!< Scan Response (SCAN_RSP) */ } esp_ble_evt_type_t; /** @@ -240,34 +242,34 @@ typedef union { * @brief ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT */ struct ble_adv_data_cmpl_evt_param { - esp_bt_status_t status; /*!< Indicate the set advertising data operation success status */ - } adv_data_cmpl; /*!< Event parameter of ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT */ + esp_bt_status_t status; /*!< Indicate the set advertising data operation success status */ + } adv_data_cmpl; /*!< Event parameter of ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT */ /** * @brief ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT */ struct ble_scan_rsp_data_cmpl_evt_param { - esp_bt_status_t status; /*!< Indicate the set scan response data operation success status */ - } scan_rsp_data_cmpl; /*!< Event parameter of ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT */ + esp_bt_status_t status; /*!< Indicate the set scan response data operation success status */ + } scan_rsp_data_cmpl; /*!< Event parameter of ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT */ /** * @brief ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT */ struct ble_scan_param_cmpl_evt_param { - esp_bt_status_t status; /*!< Indicate the set scan param operation success status */ - } scan_param_cmpl; /*!< Event parameter of ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT */ + esp_bt_status_t status; /*!< Indicate the set scan param operation success status */ + } scan_param_cmpl; /*!< Event parameter of ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT */ /** * @brief ESP_GAP_BLE_SCAN_RESULT_EVT */ 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_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]; /*!< Received EIR */ - int flag; /*!< Advertising data flag bit */ - int num_resps; /*!< Scan result number */ - } scan_rst; /*!< Event parameter of ESP_GAP_BLE_SCAN_RESULT_EVT */ + esp_gap_search_evt_t search_evt; /*!< Search event type */ + 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 */ + int flag; /*!< Advertising data flag bit */ + int num_resps; /*!< Scan result number */ + } scan_rst; /*!< Event parameter of ESP_GAP_BLE_SCAN_RESULT_EVT */ } esp_ble_gap_cb_param_t; /** @@ -440,7 +442,8 @@ esp_err_t esp_ble_gap_set_device_name(const char *name); * @param[in] type - finding ADV data type * @param[out] length - return the length of ADV data not including type * - * @return pointer of ADV data + * @return - ESP_OK : success + * - other : failed * */ uint8_t *esp_ble_resolve_adv_data(uint8_t *adv_data, uint8_t type, uint8_t *length); diff --git a/components/bt/bluedroid/api/include/esp_gatt_defs.h b/components/bt/bluedroid/api/include/esp_gatt_defs.h index 7b119a62cd..f8b2302111 100644 --- a/components/bt/bluedroid/api/include/esp_gatt_defs.h +++ b/components/bt/bluedroid/api/include/esp_gatt_defs.h @@ -22,106 +22,141 @@ extern "C" { #endif /// GATT INVALID UUID -#define ESP_GATT_ILLEGAL_UUID 0 +#define ESP_GATT_ILLEGAL_UUID 0 +/// GATT INVALID HANDLE +#define ESP_GATT_ILLEGAL_HANDLE 0 +/// GATT attribute max handle +#define ESP_GATT_ATTR_HANDLE_MAX 100 + /**@{ * All "ESP_GATT_UUID_xxx" is attribute types */ -#define ESP_GATT_UUID_PRI_SERVICE 0x2800 -#define ESP_GATT_UUID_SEC_SERVICE 0x2801 -#define ESP_GATT_UUID_INCLUDE_SERVICE 0x2802 -#define ESP_GATT_UUID_CHAR_DECLARE 0x2803 /* Characteristic Declaration*/ +#define ESP_GATT_UUID_IMMEDIATE_ALERT_SVC 0x1802 /* Immediate alert Service*/ +#define ESP_GATT_UUID_LINK_LOSS_SVC 0x1803 /* Link Loss Service*/ +#define ESP_GATT_UUID_TX_POWER_SVC 0x1804 /* TX Power Service*/ +#define ESP_GATT_UUID_CURRENT_TIME_SVC 0x1805 /* Current Time Service Service*/ +#define ESP_GATT_UUID_REF_TIME_UPDATE_SVC 0x1806 /* Reference Time Update Service*/ +#define ESP_GATT_UUID_NEXT_DST_CHANGE_SVC 0x1807 /* Next DST Change Service*/ +#define ESP_GATT_UUID_GLUCOSE_SVC 0x1808 /* Glucose Service*/ +#define ESP_GATT_UUID_HEALTH_THERMOM_SVC 0x1809 /* Health Thermometer Service*/ +#define ESP_GATT_UUID_DEVICE_INFO_SVC 0x180A /* Device Information Service*/ +#define ESP_GATT_UUID_HEART_RATE_SVC 0x180D /* Heart Rate Service*/ +#define ESP_GATT_UUID_PHONE_ALERT_STATUS_SVC 0x180E /* Phone Alert Status Service*/ +#define ESP_GATT_UUID_BATTERY_SERVICE_SVC 0x180F /* Battery Service*/ +#define ESP_GATT_UUID_BLOOD_PRESSURE_SVC 0x1810 /* Blood Pressure Service*/ +#define ESP_GATT_UUID_ALERT_NTF_SVC 0x1811 /* Alert Notification Service*/ +#define ESP_GATT_UUID_HID_SVC 0x1812 /* HID Service*/ +#define ESP_GATT_UUID_SCAN_PARAMETERS_SVC 0x1813 /* Scan Parameters Service*/ +#define ESP_GATT_UUID_RUNNING_SPEED_CADENCE_SVC 0x1814 /* Running Speed and Cadence Service*/ +#define ESP_GATT_UUID_CYCLING_SPEED_CADENCE_SVC 0x1816 /* Cycling Speed and Cadence Service*/ +#define ESP_GATT_UUID_CYCLING_POWER_SVC 0x1818 /* Cycling Power Service*/ +#define ESP_GATT_UUID_LOCATION_AND_NAVIGATION_SVC 0x1819 /* Location and Navigation Service*/ +#define ESP_GATT_UUID_USER_DATA_SVC 0x181C /* User Data Service*/ +#define ESP_GATT_UUID_WEIGHT_SCALE_SVC 0x181D /* Weight Scale Service*/ -#define ESP_GATT_UUID_CHAR_EXT_PROP 0x2900 /* Characteristic Extended Properties */ -#define ESP_GATT_UUID_CHAR_DESCRIPTION 0x2901 /* Characteristic User Description*/ -#define ESP_GATT_UUID_CHAR_CLIENT_CONFIG 0x2902 /* Client Characteristic Configuration */ -#define ESP_GATT_UUID_CHAR_SRVR_CONFIG 0x2903 /* Server Characteristic Configuration */ -#define ESP_GATT_UUID_CHAR_PRESENT_FORMAT 0x2904 /* Characteristic Presentation Format*/ -#define ESP_GATT_UUID_CHAR_AGG_FORMAT 0x2905 /* Characteristic Aggregate Format*/ -#define ESP_GATT_UUID_CHAR_VALID_RANGE 0x2906 /* Characteristic Valid Range */ -#define ESP_GATT_UUID_EXT_RPT_REF_DESCR 0x2907 -#define ESP_GATT_UUID_RPT_REF_DESCR 0x2908 +#define ESP_GATT_UUID_PRI_SERVICE 0x2800 +#define ESP_GATT_UUID_SEC_SERVICE 0x2801 +#define ESP_GATT_UUID_INCLUDE_SERVICE 0x2802 +#define ESP_GATT_UUID_CHAR_DECLARE 0x2803 /* Characteristic Declaration*/ + +#define ESP_GATT_UUID_CHAR_EXT_PROP 0x2900 /* Characteristic Extended Properties */ +#define ESP_GATT_UUID_CHAR_DESCRIPTION 0x2901 /* Characteristic User Description*/ +#define ESP_GATT_UUID_CHAR_CLIENT_CONFIG 0x2902 /* Client Characteristic Configuration */ +#define ESP_GATT_UUID_CHAR_SRVR_CONFIG 0x2903 /* Server Characteristic Configuration */ +#define ESP_GATT_UUID_CHAR_PRESENT_FORMAT 0x2904 /* Characteristic Presentation Format*/ +#define ESP_GATT_UUID_CHAR_AGG_FORMAT 0x2905 /* Characteristic Aggregate Format*/ +#define ESP_GATT_UUID_CHAR_VALID_RANGE 0x2906 /* Characteristic Valid Range */ +#define ESP_GATT_UUID_EXT_RPT_REF_DESCR 0x2907 +#define ESP_GATT_UUID_RPT_REF_DESCR 0x2908 /* GAP Profile Attributes */ -#define ESP_GATT_UUID_GAP_DEVICE_NAME 0x2A00 -#define ESP_GATT_UUID_GAP_ICON 0x2A01 -#define ESP_GATT_UUID_GAP_PREF_CONN_PARAM 0x2A04 -#define ESP_GATT_UUID_GAP_CENTRAL_ADDR_RESOL 0x2AA6 +#define ESP_GATT_UUID_GAP_DEVICE_NAME 0x2A00 +#define ESP_GATT_UUID_GAP_ICON 0x2A01 +#define ESP_GATT_UUID_GAP_PREF_CONN_PARAM 0x2A04 +#define ESP_GATT_UUID_GAP_CENTRAL_ADDR_RESOL 0x2AA6 /* Attribute Profile Attribute UUID */ -#define ESP_GATT_UUID_GATT_SRV_CHGD 0x2A05 +#define ESP_GATT_UUID_GATT_SRV_CHGD 0x2A05 /* Link ESP_Loss Service */ -#define ESP_GATT_UUID_ALERT_LEVEL 0x2A06 /* Alert Level */ -#define ESP_GATT_UUID_TX_POWER_LEVEL 0x2A07 /* TX power level */ +#define ESP_GATT_UUID_ALERT_LEVEL 0x2A06 /* Alert Level */ +#define ESP_GATT_UUID_TX_POWER_LEVEL 0x2A07 /* TX power level */ /* Current Time Service */ -#define ESP_GATT_UUID_CURRENT_TIME 0x2A2B /* Current Time */ -#define ESP_GATT_UUID_LOCAL_TIME_INFO 0x2A0F /* Local time info */ -#define ESP_GATT_UUID_REF_TIME_INFO 0x2A14 /* reference time information */ +#define ESP_GATT_UUID_CURRENT_TIME 0x2A2B /* Current Time */ +#define ESP_GATT_UUID_LOCAL_TIME_INFO 0x2A0F /* Local time info */ +#define ESP_GATT_UUID_REF_TIME_INFO 0x2A14 /* reference time information */ /* Network availability Profile */ -#define ESP_GATT_UUID_NW_STATUS 0x2A18 /* network availability status */ -#define ESP_GATT_UUID_NW_TRIGGER 0x2A1A /* Network availability trigger */ +#define ESP_GATT_UUID_NW_STATUS 0x2A18 /* network availability status */ +#define ESP_GATT_UUID_NW_TRIGGER 0x2A1A /* Network availability trigger */ /* Phone alert */ -#define ESP_GATT_UUID_ALERT_STATUS 0x2A3F /* alert status */ -#define ESP_GATT_UUID_RINGER_CP 0x2A40 /* ringer control point */ -#define ESP_GATT_UUID_RINGER_SETTING 0x2A41 /* ringer setting */ +#define ESP_GATT_UUID_ALERT_STATUS 0x2A3F /* alert status */ +#define ESP_GATT_UUID_RINGER_CP 0x2A40 /* ringer control point */ +#define ESP_GATT_UUID_RINGER_SETTING 0x2A41 /* ringer setting */ /* Glucose Service */ -#define ESP_GATT_UUID_GM_MEASUREMENT 0x2A18 -#define ESP_GATT_UUID_GM_CONTEXT 0x2A34 -#define ESP_GATT_UUID_GM_CONTROL_POINT 0x2A52 -#define ESP_GATT_UUID_GM_FEATURE 0x2A51 +#define ESP_GATT_UUID_GM_MEASUREMENT 0x2A18 +#define ESP_GATT_UUID_GM_CONTEXT 0x2A34 +#define ESP_GATT_UUID_GM_CONTROL_POINT 0x2A52 +#define ESP_GATT_UUID_GM_FEATURE 0x2A51 /* device information characteristic */ -#define ESP_GATT_UUID_SYSTEM_ID 0x2A23 -#define ESP_GATT_UUID_MODEL_NUMBER_STR 0x2A24 -#define ESP_GATT_UUID_SERIAL_NUMBER_STR 0x2A25 -#define ESP_GATT_UUID_FW_VERSION_STR 0x2A26 -#define ESP_GATT_UUID_HW_VERSION_STR 0x2A27 -#define ESP_GATT_UUID_SW_VERSION_STR 0x2A28 -#define ESP_GATT_UUID_MANU_NAME 0x2A29 -#define ESP_GATT_UUID_IEEE_DATA 0x2A2A -#define ESP_GATT_UUID_PNP_ID 0x2A50 +#define ESP_GATT_UUID_SYSTEM_ID 0x2A23 +#define ESP_GATT_UUID_MODEL_NUMBER_STR 0x2A24 +#define ESP_GATT_UUID_SERIAL_NUMBER_STR 0x2A25 +#define ESP_GATT_UUID_FW_VERSION_STR 0x2A26 +#define ESP_GATT_UUID_HW_VERSION_STR 0x2A27 +#define ESP_GATT_UUID_SW_VERSION_STR 0x2A28 +#define ESP_GATT_UUID_MANU_NAME 0x2A29 +#define ESP_GATT_UUID_IEEE_DATA 0x2A2A +#define ESP_GATT_UUID_PNP_ID 0x2A50 /* HID characteristics */ -#define ESP_GATT_UUID_HID_INFORMATION 0x2A4A -#define ESP_GATT_UUID_HID_REPORT_MAP 0x2A4B -#define ESP_GATT_UUID_HID_CONTROL_POINT 0x2A4C -#define ESP_GATT_UUID_HID_REPORT 0x2A4D -#define ESP_GATT_UUID_HID_PROTO_MODE 0x2A4E -#define ESP_GATT_UUID_HID_BT_KB_INPUT 0x2A22 -#define ESP_GATT_UUID_HID_BT_KB_OUTPUT 0x2A32 -#define ESP_GATT_UUID_HID_BT_MOUSE_INPUT 0x2A33 +#define ESP_GATT_UUID_HID_INFORMATION 0x2A4A +#define ESP_GATT_UUID_HID_REPORT_MAP 0x2A4B +#define ESP_GATT_UUID_HID_CONTROL_POINT 0x2A4C +#define ESP_GATT_UUID_HID_REPORT 0x2A4D +#define ESP_GATT_UUID_HID_PROTO_MODE 0x2A4E +#define ESP_GATT_UUID_HID_BT_KB_INPUT 0x2A22 +#define ESP_GATT_UUID_HID_BT_KB_OUTPUT 0x2A32 +#define ESP_GATT_UUID_HID_BT_MOUSE_INPUT 0x2A33 + + /// Heart Rate Measurement +#define ESP_GATT_HEART_RATE_MEAS 0x2A37 +/// Body Sensor Location +#define ESP_GATT_BODY_SENSOR_LOCATION 0x2A38 +/// Heart Rate Control Point +#define ESP_GATT_HEART_RATE_CNTL_POINT 0x2A39 /* Battery Service characteristics */ -#define ESP_GATT_UUID_BATTERY_LEVEL 0x2A19 +#define ESP_GATT_UUID_BATTERY_LEVEL 0x2A19 /* Sensor Service */ -#define ESP_GATT_UUID_SC_CONTROL_POINT 0x2A55 -#define ESP_GATT_UUID_SENSOR_LOCATION 0x2A5D +#define ESP_GATT_UUID_SC_CONTROL_POINT 0x2A55 +#define ESP_GATT_UUID_SENSOR_LOCATION 0x2A5D /* Runners speed and cadence service */ -#define ESP_GATT_UUID_RSC_MEASUREMENT 0x2A53 -#define ESP_GATT_UUID_RSC_FEATURE 0x2A54 +#define ESP_GATT_UUID_RSC_MEASUREMENT 0x2A53 +#define ESP_GATT_UUID_RSC_FEATURE 0x2A54 /* Cycling speed and cadence service */ -#define ESP_GATT_UUID_CSC_MEASUREMENT 0x2A5B -#define ESP_GATT_UUID_CSC_FEATURE 0x2A5C +#define ESP_GATT_UUID_CSC_MEASUREMENT 0x2A5B +#define ESP_GATT_UUID_CSC_FEATURE 0x2A5C /* Scan ESP_Parameter characteristics */ -#define ESP_GATT_UUID_SCAN_INT_WINDOW 0x2A4F -#define ESP_GATT_UUID_SCAN_REFRESH 0x2A31 +#define ESP_GATT_UUID_SCAN_INT_WINDOW 0x2A4F +#define ESP_GATT_UUID_SCAN_REFRESH 0x2A31 /** * @} */ /// Attribute write data type from the client typedef enum { - ESP_GATT_PREP_WRITE_CANCEL = 0x00, /*!< Prepare write cancel */ - ESP_GATT_PREP_WRITE_EXEC = 0x01, /*!< Prepare write execute */ + ESP_GATT_PREP_WRITE_CANCEL = 0x00, /*!< Prepare write cancel */ + ESP_GATT_PREP_WRITE_EXEC = 0x01, /*!< Prepare write execute */ } esp_gatt_prep_write_type; /** @@ -178,23 +213,23 @@ typedef enum { * @brief Gatt Connection reason enum */ typedef enum { - ESP_GATT_CONN_UNKNOWN = 0, /*!< Gatt connection unknown */ - ESP_GATT_CONN_L2C_FAILURE = 1, /*!< General L2cap failure */ - ESP_GATT_CONN_TIMEOUT = 0x08, /*!< Connection timeout */ - ESP_GATT_CONN_TERMINATE_PEER_USER = 0x13, /*!< Connection terminate by peer user */ - ESP_GATT_CONN_TERMINATE_LOCAL_HOST = 0x16, /*!< Connectionterminated by local host */ - ESP_GATT_CONN_FAIL_ESTABLISH = 0x3e, /*!< Connection fail to establish */ - ESP_GATT_CONN_LMP_TIMEOUT = 0x22, /*!< Connection fail for LMP response tout */ - ESP_GATT_CONN_CONN_CANCEL = 0x0100, /*!< L2CAP connection cancelled */ - ESP_GATT_CONN_NONE = 0x0101 /*!< No connection to cancel */ + ESP_GATT_CONN_UNKNOWN = 0, /*!< Gatt connection unknown */ + ESP_GATT_CONN_L2C_FAILURE = 1, /*!< General L2cap failure */ + ESP_GATT_CONN_TIMEOUT = 0x08, /*!< Connection timeout */ + ESP_GATT_CONN_TERMINATE_PEER_USER = 0x13, /*!< Connection terminate by peer user */ + ESP_GATT_CONN_TERMINATE_LOCAL_HOST = 0x16, /*!< Connectionterminated by local host */ + ESP_GATT_CONN_FAIL_ESTABLISH = 0x3e, /*!< Connection fail to establish */ + ESP_GATT_CONN_LMP_TIMEOUT = 0x22, /*!< Connection fail for LMP response tout */ + ESP_GATT_CONN_CONN_CANCEL = 0x0100, /*!< L2CAP connection cancelled */ + ESP_GATT_CONN_NONE = 0x0101 /*!< No connection to cancel */ } esp_gatt_conn_reason_t; /** * @brief Gatt id, include uuid and instance id */ typedef struct { - esp_bt_uuid_t uuid; /*!< UUID */ - uint8_t inst_id; /*!< Instance id */ + esp_bt_uuid_t uuid; /*!< UUID */ + uint8_t inst_id; /*!< Instance id */ } __attribute__((packed)) esp_gatt_id_t; /** @@ -202,19 +237,19 @@ typedef struct { * (uuid and instance id) and primary flag */ typedef struct { - esp_gatt_id_t id; /*!< Gatt id, include uuid and instance */ - bool is_primary; /*!< This service is primary or not */ + esp_gatt_id_t id; /*!< Gatt id, include uuid and instance */ + bool is_primary; /*!< This service is primary or not */ } __attribute__((packed)) esp_gatt_srvc_id_t; /** * @brief Gatt authentication request type */ typedef enum { - ESP_GATT_AUTH_REQ_NONE = 0, - ESP_GATT_AUTH_REQ_NO_MITM = 1, /* unauthenticated encryption */ - ESP_GATT_AUTH_REQ_MITM = 2, /* authenticated encryption */ - ESP_GATT_AUTH_REQ_SIGNED_NO_MITM = 3, - ESP_GATT_AUTH_REQ_SIGNED_MITM = 4, + ESP_GATT_AUTH_REQ_NONE = 0, + ESP_GATT_AUTH_REQ_NO_MITM = 1, /* unauthenticated encryption */ + ESP_GATT_AUTH_REQ_MITM = 2, /* authenticated encryption */ + ESP_GATT_AUTH_REQ_SIGNED_NO_MITM = 3, + ESP_GATT_AUTH_REQ_SIGNED_MITM = 4, } esp_gatt_auth_req_t; /** @@ -246,32 +281,109 @@ typedef enum { /// GATT maximum attribute length #define ESP_GATT_MAX_ATTR_LEN 600 //as same as GATT_MAX_ATTR_LEN + +/** + * @brief Attribute description (used to create database) + */ + typedef struct + { + esp_bt_uuid_t uuid; /*!< Element UUID */ + 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*/ + }esp_attr_desc_t; + + +/** + * @brief attribute auto respose flag + */ +typedef struct +{ +#define ESP_GATT_RSP_BY_APP 0 +#define ESP_GATT_AUTO_RSP 1 + uint8_t auto_rsp; /*!< need the app response to the client if need_rsp set to 1*/ +}esp_attr_control_t; + + +/** + * @brief attribute type added to the gatt server database + */ +typedef struct +{ + esp_attr_control_t attr_control; /*!< The attribue control type*/ + esp_attr_desc_t att_desc; /*!< The attribue type*/ +}esp_gatts_attr_db_t; + + +/** + * @brief set the attribute value type + */ +typedef struct +{ + uint16_t attr_max_len; /*!< attribute max value length */ + uint16_t attr_len; /*!< attribute current value length */ + uint8_t *attr_value; /*!< the pointer to attribute value */ +}esp_attr_value_t; + + +/** + * @brief Gatt include service entry element + */ +typedef struct +{ + uint16_t start_hdl; /*!< Gatt start handle value of included service */ + uint16_t end_hdl; /*!< Gatt end handle value of included service */ + uint16_t uuid; /*!< Gatt attribute value UUID of included service */ +}esp_gatts_incl_svc_desc_t; /*!< Gatt include service entry element */ + +/** + * @brief Gatt include 128 bit service entry element + */ +typedef struct +{ + uint16_t start_hdl; /*!< Gatt start handle value of included 128 bit service */ + uint16_t end_hdl; /*!< Gatt end handle value of included 128 bit service */ +}esp_gatts_incl128_svc_desc_t; /*!< Gatt include 128 bit service entry element */ + + +/** + * @brief Gatt characteristic entry element + */ +typedef struct +{ + uint8_t prop; /*!< Gatt attribute properties */ + uint16_t attr_hdl; /*!< Gatt attribute handle */ + esp_bt_uuid_t attr_uuid; /*!< Gatt attribute uuid typedle */ +}esp_gatts_char_desc_t; /*!< Gatt characteristic value descriptor */ + + /// Gatt attribute value typedef struct { - uint8_t value[ESP_GATT_MAX_ATTR_LEN]; /*!< Gatt attribute value */ - uint16_t handle; /*!< Gatt attribute handle */ - uint16_t offset; /*!< Gatt attribute value offset */ - uint16_t len; /*!< Gatt attribute value length */ - uint8_t auth_req; /*!< Gatt authentication request */ + uint8_t value[ESP_GATT_MAX_ATTR_LEN]; /*!< Gatt attribute value */ + uint16_t handle; /*!< Gatt attribute handle */ + uint16_t offset; /*!< Gatt attribute value offset */ + uint16_t len; /*!< Gatt attribute value length */ + uint8_t auth_req; /*!< Gatt authentication request */ } esp_gatt_value_t; /// GATT remote read request response type typedef union { - esp_gatt_value_t attr_value; /*!< Gatt attribute structure */ - uint16_t handle; /*!< Gatt attribute handle */ + esp_gatt_value_t attr_value; /*!< Gatt attribute structure */ + uint16_t handle; /*!< Gatt attribute handle */ } esp_gatt_rsp_t; /** * @brief Gatt write type */ typedef enum { - ESP_GATT_WRITE_TYPE_NO_RSP = 1, /*!< Gatt write attribute need no response */ - ESP_GATT_WRITE_TYPE_RSP, /*!< Gatt write attribute need remote response */ + ESP_GATT_WRITE_TYPE_NO_RSP = 1, /*!< Gatt write attribute need no response */ + ESP_GATT_WRITE_TYPE_RSP, /*!< Gatt write attribute need remote response */ } esp_gatt_write_type_t; #define ESP_GATT_IF_NONE 0xff /*!< If callback report gattc_if/gatts_if as this macro, means this event is not correspond to any app */ -typedef uint8_t esp_gatt_if_t; /*!< Gatt interface type, different application on GATT client use different gatt_if */ +typedef uint8_t esp_gatt_if_t; /*!< Gatt interface type, different application on GATT client use different gatt_if */ #ifdef __cplusplus } diff --git a/components/bt/bluedroid/api/include/esp_gatts_api.h b/components/bt/bluedroid/api/include/esp_gatts_api.h index d0fc055a7f..86a539597a 100644 --- a/components/bt/bluedroid/api/include/esp_gatts_api.h +++ b/components/bt/bluedroid/api/include/esp_gatts_api.h @@ -25,29 +25,31 @@ extern "C" { /// GATT Server callback function events typedef enum { - ESP_GATTS_REG_EVT = 0, /*!< When register application id, the event comes */ - ESP_GATTS_READ_EVT = 1, /*!< When gatt client request read operation, the event comes */ - ESP_GATTS_WRITE_EVT = 2, /*!< When gatt client request write operation, the event comes */ - ESP_GATTS_EXEC_WRITE_EVT = 3, /*!< When gatt client request execute write, the event comes */ - ESP_GATTS_MTU_EVT = 4, /*!< When set mtu complete, the event comes */ - ESP_GATTS_CONF_EVT = 5, /*!< When receive confirm, the event comes */ - ESP_GATTS_UNREG_EVT = 6, /*!< When unregister application id, the event comes */ - ESP_GATTS_CREATE_EVT = 7, /*!< When create service complete, the event comes */ - ESP_GATTS_ADD_INCL_SRVC_EVT = 8, /*!< When add included service complete, the event comes */ - ESP_GATTS_ADD_CHAR_EVT = 9, /*!< When add characteristic complete, the event comes */ - ESP_GATTS_ADD_CHAR_DESCR_EVT = 10, /*!< When add descriptor complete, the event comes */ - ESP_GATTS_DELETE_EVT = 11, /*!< When delete service complete, the event comes */ - ESP_GATTS_START_EVT = 12, /*!< When start service complete, the event comes */ - ESP_GATTS_STOP_EVT = 13, /*!< When stop service complete, the event comes */ - ESP_GATTS_CONNECT_EVT = 14, /*!< When gatt client connect, the event comes */ - ESP_GATTS_DISCONNECT_EVT = 15, /*!< When gatt client disconnect, the event comes */ - ESP_GATTS_OPEN_EVT = 16, /*!< When connect to peer, the event comes */ - ESP_GATTS_CANCEL_OPEN_EVT = 17, /*!< When disconnect from peer, the event comes */ - ESP_GATTS_CLOSE_EVT = 18, /*!< When gatt server close, the event comes */ - ESP_GATTS_LISTEN_EVT = 19, /*!< When gatt listen to be connected the event comes */ - ESP_GATTS_CONGEST_EVT = 20, /*!< When congest happen, the event comes */ - /* following is extra event */ - ESP_GATTS_RESPONSE_EVT = 21, /*!< When gatt send response complete, the event comes */ + ESP_GATTS_REG_EVT = 0, /*!< When register application id, the event comes */ + ESP_GATTS_READ_EVT = 1, /*!< When gatt client request read operation, the event comes */ + ESP_GATTS_WRITE_EVT = 2, /*!< When gatt client request write operation, the event comes */ + ESP_GATTS_EXEC_WRITE_EVT = 3, /*!< When gatt client request execute write, the event comes */ + ESP_GATTS_MTU_EVT = 4, /*!< When set mtu complete, the event comes */ + ESP_GATTS_CONF_EVT = 5, /*!< When receive confirm, the event comes */ + ESP_GATTS_UNREG_EVT = 6, /*!< When unregister application id, the event comes */ + ESP_GATTS_CREATE_EVT = 7, /*!< When create service complete, the event comes */ + ESP_GATTS_ADD_INCL_SRVC_EVT = 8, /*!< When add included service complete, the event comes */ + ESP_GATTS_ADD_CHAR_EVT = 9, /*!< When add characteristic complete, the event comes */ + ESP_GATTS_ADD_CHAR_DESCR_EVT = 10, /*!< When add descriptor complete, the event comes */ + ESP_GATTS_DELETE_EVT = 11, /*!< When delete service complete, the event comes */ + ESP_GATTS_START_EVT = 12, /*!< When start service complete, the event comes */ + ESP_GATTS_STOP_EVT = 13, /*!< When stop service complete, the event comes */ + ESP_GATTS_CONNECT_EVT = 14, /*!< When gatt client connect, the event comes */ + ESP_GATTS_DISCONNECT_EVT = 15, /*!< When gatt client disconnect, the event comes */ + ESP_GATTS_OPEN_EVT = 16, /*!< When connect to peer, the event comes */ + ESP_GATTS_CANCEL_OPEN_EVT = 17, /*!< When disconnect from peer, the event comes */ + ESP_GATTS_CLOSE_EVT = 18, /*!< When gatt server close, the event comes */ + ESP_GATTS_LISTEN_EVT = 19, /*!< When gatt listen to be connected the event comes */ + ESP_GATTS_CONGEST_EVT = 20, /*!< When congest happen, the event comes */ + /* following is extra event */ + ESP_GATTS_RESPONSE_EVT = 21, /*!< When gatt send response complete, the event comes */ + ESP_GATTS_CREAT_ATTR_TAB_EVT = 22, + ESP_GATTS_SET_ATTR_VAL_EVT = 23, } esp_gatts_cb_event_t; /** @@ -58,64 +60,64 @@ typedef union { * @brief ESP_GATTS_REG_EVT */ struct gatts_reg_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t app_id; /*!< Application id which input in register API */ - } reg; /*!< Gatt server callback param of ESP_GATTS_REG_EVT */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t app_id; /*!< Application id which input in register API */ + } reg; /*!< Gatt server callback param of ESP_GATTS_REG_EVT */ /** * @brief ESP_GATTS_READ_EVT */ struct gatts_read_evt_param { - 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 */ - } read; /*!< Gatt server callback param of ESP_GATTS_READ_EVT */ + 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 */ + } read; /*!< Gatt server callback param of ESP_GATTS_READ_EVT */ /** * @brief ESP_GATTS_WRITE_EVT */ struct gatts_write_evt_param { - 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 */ - } write; /*!< Gatt server callback param of ESP_GATTS_WRITE_EVT */ + 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 */ + } write; /*!< Gatt server callback param of ESP_GATTS_WRITE_EVT */ /** * @brief ESP_GATTS_EXEC_WRITE_EVT */ struct gatts_exec_write_evt_param { - uint16_t conn_id; /*!< Connection id */ - uint32_t trans_id; /*!< Transfer id */ - esp_bd_addr_t bda; /*!< The bluetooth device address which been written */ -#define ESP_GATT_PREP_WRITE_CANCEL 0x00 -#define ESP_GATT_PREP_WRITE_EXEC 0x01 - uint8_t exec_write_flag; /*!< Execute write flag */ - } exec_write; /*!< Gatt server callback param of ESP_GATTS_EXEC_WRITE_EVT */ + uint16_t conn_id; /*!< Connection id */ + uint32_t trans_id; /*!< Transfer id */ + esp_bd_addr_t bda; /*!< The bluetooth device address which been written */ +#define ESP_GATT_PREP_WRITE_CANCEL 0x00 /*!< Prepare write flag to indicate cancel prepare write */ +#define ESP_GATT_PREP_WRITE_EXEC 0x01 /*!< Prepare write flag to indicate execute prepare write */ + uint8_t exec_write_flag; /*!< Execute write flag */ + } exec_write; /*!< Gatt server callback param of ESP_GATTS_EXEC_WRITE_EVT */ /** * @brief ESP_GATTS_MTU_EVT */ struct gatts_mtu_evt_param { - uint16_t conn_id; /*!< Connection id */ - uint16_t mtu; /*!< MTU size */ - } mtu; /*!< Gatt server callback param of ESP_GATTS_MTU_EVT */ + uint16_t conn_id; /*!< Connection id */ + uint16_t mtu; /*!< MTU size */ + } mtu; /*!< Gatt server callback param of ESP_GATTS_MTU_EVT */ /** * @brief ESP_GATTS_CONF_EVT */ struct gatts_conf_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t conn_id; /*!< Connection id */ - } conf; /*!< Gatt server callback param of ESP_GATTS_CONF_EVT (confirm) */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t conn_id; /*!< Connection id */ + } conf; /*!< Gatt server callback param of ESP_GATTS_CONF_EVT (confirm) */ /** * @brief ESP_GATTS_UNREG_EVT @@ -125,81 +127,81 @@ typedef union { * @brief ESP_GATTS_CREATE_EVT */ struct gatts_create_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t service_handle; /*!< Service attribute handle */ - esp_gatt_srvc_id_t service_id; /*!< Service id, include service uuid and other information */ - } create; /*!< Gatt server callback param of ESP_GATTS_CREATE_EVT */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t service_handle; /*!< Service attribute handle */ + esp_gatt_srvc_id_t service_id; /*!< Service id, include service uuid and other information */ + } create; /*!< Gatt server callback param of ESP_GATTS_CREATE_EVT */ /** * @brief ESP_GATTS_ADD_INCL_SRVC_EVT */ struct gatts_add_incl_srvc_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t attr_handle; /*!< Included service attribute handle */ - uint16_t service_handle; /*!< Service attribute handle */ - } add_incl_srvc; /*!< Gatt server callback param of ESP_GATTS_ADD_INCL_SRVC_EVT */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t attr_handle; /*!< Included service attribute handle */ + uint16_t service_handle; /*!< Service attribute handle */ + } add_incl_srvc; /*!< Gatt server callback param of ESP_GATTS_ADD_INCL_SRVC_EVT */ /** * @brief ESP_GATTS_ADD_CHAR_EVT */ struct gatts_add_char_evt_param { - 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 */ - } add_char; /*!< Gatt server callback param of ESP_GATTS_ADD_CHAR_EVT */ + 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 */ + } add_char; /*!< Gatt server callback param of ESP_GATTS_ADD_CHAR_EVT */ /** * @brief ESP_GATTS_ADD_CHAR_DESCR_EVT */ struct gatts_add_char_descr_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t attr_handle; /*!< Descriptor attribute handle */ - uint16_t service_handle; /*!< Service attribute handle */ - esp_bt_uuid_t char_uuid; /*!< Characteristic uuid */ - } add_char_descr; /*!< Gatt server callback param of ESP_GATTS_ADD_CHAR_DESCR_EVT */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t attr_handle; /*!< Descriptor attribute handle */ + uint16_t service_handle; /*!< Service attribute handle */ + esp_bt_uuid_t char_uuid; /*!< Characteristic uuid */ + } add_char_descr; /*!< Gatt server callback param of ESP_GATTS_ADD_CHAR_DESCR_EVT */ /** * @brief ESP_GATTS_DELETE_EVT */ struct gatts_delete_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t service_handle; /*!< Service attribute handle */ - } del; /*!< Gatt server callback param of ESP_GATTS_DELETE_EVT */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t service_handle; /*!< Service attribute handle */ + } del; /*!< Gatt server callback param of ESP_GATTS_DELETE_EVT */ /** * @brief ESP_GATTS_START_EVT */ struct gatts_start_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t service_handle; /*!< Service attribute handle */ - } start; /*!< Gatt server callback param of ESP_GATTS_START_EVT */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t service_handle; /*!< Service attribute handle */ + } start; /*!< Gatt server callback param of ESP_GATTS_START_EVT */ /** * @brief ESP_GATTS_STOP_EVT */ struct gatts_stop_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t service_handle; /*!< Service attribute handle */ - } stop; /*!< Gatt server callback param of ESP_GATTS_STOP_EVT */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t service_handle; /*!< Service attribute handle */ + } stop; /*!< Gatt server callback param of ESP_GATTS_STOP_EVT */ /** * @brief ESP_GATTS_CONNECT_EVT */ struct gatts_connect_evt_param { - uint16_t conn_id; /*!< Connection id */ - esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */ - bool is_connected; /*!< Indicate it is connected or not */ - } connect; /*!< Gatt server callback param of ESP_GATTS_CONNECT_EVT */ + uint16_t conn_id; /*!< Connection id */ + esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */ + bool is_connected; /*!< Indicate it is connected or not */ + } connect; /*!< Gatt server callback param of ESP_GATTS_CONNECT_EVT */ /** * @brief ESP_GATTS_DISCONNECT_EVT */ struct gatts_disconnect_evt_param { - uint16_t conn_id; /*!< Connection id */ - esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */ - bool is_connected; /*!< Indicate it is connected or not */ - } disconnect; /*!< Gatt server callback param of ESP_GATTS_DISCONNECT_EVT */ + uint16_t conn_id; /*!< Connection id */ + esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */ + bool is_connected; /*!< Indicate it is connected or not */ + } disconnect; /*!< Gatt server callback param of ESP_GATTS_DISCONNECT_EVT */ /** * @brief ESP_GATTS_OPEN_EVT @@ -217,17 +219,38 @@ typedef union { * @brief ESP_GATTS_CONGEST_EVT */ struct gatts_congest_evt_param { - uint16_t conn_id; /*!< Connection id */ - bool congested; /*!< Congested or not */ - } congest; /*!< Gatt server callback param of ESP_GATTS_CONGEST_EVT */ + uint16_t conn_id; /*!< Connection id */ + bool congested; /*!< Congested or not */ + } congest; /*!< Gatt server callback param of ESP_GATTS_CONGEST_EVT */ /** * @brief ESP_GATTS_RESPONSE_EVT */ struct gatts_rsp_evt_param { - esp_gatt_status_t status; /*!< Operation status */ - uint16_t handle; /*!< Attribute handle which send response */ - } rsp; /*!< Gatt server callback param of ESP_GATTS_RESPONSE_EVT */ + esp_gatt_status_t status; /*!< Operation status */ + uint16_t handle; /*!< Attribute handle which send response */ + } rsp; /*!< Gatt server callback param of ESP_GATTS_RESPONSE_EVT */ + + /** + * @brief ESP_GATTS_CREAT_ATTR_TAB_EVT + */ + struct gatts_add_attr_tab_evt_param{ + 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 */ + } add_attr_tab; /*!< Gatt server callback param of ESP_GATTS_CREAT_ATTR_TAB_EVT */ + + + /** + * @brief ESP_GATTS_SET_ATTR_VAL_EVT + */ + struct gatts_set_attr_val_evt_param{ + uint16_t srvc_handle; /*!< The service handle */ + uint16_t attr_handle; /*!< The attribute handle */ + esp_gatt_status_t status; /*!< Operation status*/ + } set_attr_val; /*!< Gatt server callback param of ESP_GATTS_SET_ATTR_VAL_EVT */ + } esp_ble_gatts_cb_param_t; /** @@ -294,7 +317,22 @@ esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, esp_gatt_srvc_id_t *service_id, uint16_t num_handle); - +/** + * @brief Create a service attribute tab. + * @param[in] gatts_attr_db: the pointer to the service attr tab + * @param[in] gatts_if: GATT server access interface + * @param[in] max_nb_attr: the number of attribute to be added to the service database. + * @param[in] srvc_inst_id: the instance id of the service + * + * @return + * - ESP_OK : success + * - other : failed + * + */ +esp_err_t esp_ble_gatts_create_attr_tab(const esp_gatts_attr_db_t *gatts_attr_db, + esp_gatt_if_t gatts_if, + uint8_t max_nb_attr, + uint8_t srvc_inst_id); /** * @brief This function is called to add an included service. After included * service is included, a callback event BTA_GATTS_ADD_INCL_SRVC_EVT @@ -321,6 +359,8 @@ esp_err_t esp_ble_gatts_add_included_service(uint16_t service_handle, uint16_t i * @param[in] char_uuid : Characteristic UUID. * @param[in] perm : Characteristic value declaration attribute permission. * @param[in] property : Characteristic Properties + * @param[in] char_val : Characteristic value + * @param[in] control : attribute response control byte * * @return * - ESP_OK : success @@ -328,7 +368,8 @@ esp_err_t esp_ble_gatts_add_included_service(uint16_t service_handle, uint16_t i * */ esp_err_t esp_ble_gatts_add_char(uint16_t service_handle, esp_bt_uuid_t *char_uuid, - esp_gatt_perm_t perm, esp_gatt_char_prop_t property); + esp_gatt_perm_t perm, esp_gatt_char_prop_t property, esp_attr_value_t *char_val, + esp_attr_control_t *control); /** @@ -340,15 +381,17 @@ esp_err_t esp_ble_gatts_add_char(uint16_t service_handle, esp_bt_uuid_t *char_ * be added. * @param[in] perm: descriptor access permission. * @param[in] descr_uuid: descriptor UUID. - * + * @param[in] char_descr_val : Characteristic descriptor value + * @param[in] control : attribute response control byte * @return * - ESP_OK : success * - other : failed * */ esp_err_t esp_ble_gatts_add_char_descr (uint16_t service_handle, - esp_bt_uuid_t *descr_uuid, - esp_gatt_perm_t perm); + esp_bt_uuid_t *descr_uuid, + esp_gatt_perm_t perm, esp_attr_value_t *char_descr_val, + esp_attr_control_t *control); @@ -432,6 +475,35 @@ esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, esp_gatt_status_t status, esp_gatt_rsp_t *rsp); +/** + * @brief This function is called to set the attribute value by the application + * + * @param[in] attr_handle: the attribute handle which to be set + * @param[in] length: the value length + * @param[in] value: the pointer to the attribute value + * + * @return + * - ESP_OK : success + * - other : failed + * + */ +esp_err_t esp_ble_gatts_set_attr_value(uint16_t attr_handle, uint16_t length, const uint8_t *value); + +/** + * @brief Retrieve attribute value + * + * @param[in] attr_handle: Attribute handle. + * @param[out] length: pointer to the attribute value length + * @param[out] value: Pointer to attribute value payload, the value cannot be modified by user + * + * @return + * - ESP_OK : success + * - other : failed + * + */ +esp_err_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value); + + /** * @brief Open a direct open connection or add a background auto connection * diff --git a/components/bt/bluedroid/bta/gatt/bta_gatts_act.c b/components/bt/bluedroid/bta/gatt/bta_gatts_act.c index 04bb3ee7cb..9dd2ea4039 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gatts_act.c +++ b/components/bt/bluedroid/bta/gatt/bta_gatts_act.c @@ -34,7 +34,6 @@ #include "bta_gatts_int.h" #include "bta_gatts_co.h" #include "btm_ble_api.h" -// #include "btif/include/btif_debug_conn.h" #include static void bta_gatts_nv_save_cback(BOOLEAN is_saved, tGATTS_HNDL_RANGE *p_hndl_range); @@ -404,10 +403,20 @@ void bta_gatts_add_char(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) UINT16 attr_id = 0; tBTA_GATTS cb_data; + tGATT_ATTR_VAL *p_attr_val = NULL; + tGATTS_ATTR_CONTROL *p_control = NULL; + + if (p_msg->api_add_char_descr.attr_val.attr_max_len != 0) { + p_attr_val = &p_msg->api_add_char_descr.attr_val; + } + + if (p_msg->api_add_char_descr.control.auto_rsp != 0) { + p_control = &p_msg->api_add_char_descr.control; + } attr_id = GATTS_AddCharacteristic(p_msg->api_add_char.hdr.layer_specific, &p_msg->api_add_char.char_uuid, p_msg->api_add_char.perm, - p_msg->api_add_char.property); + p_msg->api_add_char.property, p_attr_val, p_control); cb_data.add_result.server_if = p_rcb->gatt_if; cb_data.add_result.service_id = p_msg->api_add_incl_srvc.hdr.layer_specific; cb_data.add_result.attr_id = attr_id; @@ -425,6 +434,7 @@ void bta_gatts_add_char(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) (*p_rcb->p_cback)(BTA_GATTS_ADD_CHAR_EVT, &cb_data); } } + /******************************************************************************* ** ** Function bta_gatts_add_char_descr @@ -439,10 +449,20 @@ void bta_gatts_add_char_descr(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; UINT16 attr_id = 0; tBTA_GATTS cb_data; + tGATT_ATTR_VAL *p_attr_val = NULL; + tGATTS_ATTR_CONTROL *p_control = NULL; + if (p_msg->api_add_char_descr.attr_val.attr_max_len != 0) { + p_attr_val = &p_msg->api_add_char_descr.attr_val; + } + + if (p_msg->api_add_char_descr.control.auto_rsp != 0) { + p_control = &p_msg->api_add_char_descr.control; + } attr_id = GATTS_AddCharDescriptor(p_msg->api_add_char_descr.hdr.layer_specific, p_msg->api_add_char_descr.perm, - &p_msg->api_add_char_descr.descr_uuid); + &p_msg->api_add_char_descr.descr_uuid, p_attr_val, + p_control); cb_data.add_result.server_if = p_rcb->gatt_if; cb_data.add_result.service_id = p_msg->api_add_incl_srvc.hdr.layer_specific; @@ -462,6 +482,41 @@ void bta_gatts_add_char_descr(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ } } + +/******************************************************************************* +** +** Function bta_gatts_add_char_descr +** +** Description action function to add characteristic descriptor. +** +** Returns none. +** +*******************************************************************************/ +void bta_gatts_set_attr_value(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) +{ + tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; + UINT16 attr_id = 0; + tBTA_GATTS cb_data; + tBTA_GATT_STATUS gatts_status; + gatts_status = GATTS_SetAttributeValue(p_msg->api_add_char_descr.hdr.layer_specific, + p_msg->api_set_val.length, + p_msg->api_set_val.value); + + cb_data.attr_val.server_if = p_rcb->gatt_if; + cb_data.attr_val.service_id = p_msg->api_set_val.hdr.layer_specific; + cb_data.attr_val.attr_id = attr_id; + cb_data.attr_val.status = gatts_status; + + if (p_rcb->p_cback) { + (*p_rcb->p_cback)(BTA_GATTS_SET_ATTR_VAL_EVT, &cb_data); + } +} + +void bta_gatts_get_attr_value(UINT16 attr_handle, UINT16 *length, UINT8 **value) +{ + GATTS_GetAttributeValue(attr_handle, length, value); +} + /******************************************************************************* ** ** Function bta_gatts_delete_service diff --git a/components/bt/bluedroid/bta/gatt/bta_gatts_api.c b/components/bt/bluedroid/bta/gatt/bta_gatts_api.c index d54935120c..58e226c839 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gatts_api.c +++ b/components/bt/bluedroid/bta/gatt/bta_gatts_api.c @@ -215,10 +215,14 @@ void BTA_GATTS_AddIncludeService(UINT16 service_id, UINT16 included_service_id) ** *******************************************************************************/ void BTA_GATTS_AddCharacteristic (UINT16 service_id, tBT_UUID *p_char_uuid, - tBTA_GATT_PERM perm, tBTA_GATT_CHAR_PROP property) + tBTA_GATT_PERM perm, tBTA_GATT_CHAR_PROP property, tGATT_ATTR_VAL *attr_val, + tBTA_GATTS_ATTR_CONTROL *control) { tBTA_GATTS_API_ADD_CHAR *p_buf; - + UINT16 len = 0; + if(attr_val != NULL){ + len = attr_val->attr_len; + } if ((p_buf = (tBTA_GATTS_API_ADD_CHAR *) GKI_getbuf(sizeof(tBTA_GATTS_API_ADD_CHAR))) != NULL) { memset(p_buf, 0, sizeof(tBTA_GATTS_API_ADD_CHAR)); @@ -226,6 +230,19 @@ void BTA_GATTS_AddCharacteristic (UINT16 service_id, tBT_UUID *p_char_uuid, p_buf->hdr.layer_specific = service_id; p_buf->perm = perm; p_buf->property = property; + if(control !=NULL){ + p_buf->control.auto_rsp = control->auto_rsp; + } + if(attr_val != NULL){ + APPL_TRACE_DEBUG("!!!!!!attr_val->attr_len = %x\n",attr_val->attr_len); + APPL_TRACE_DEBUG("!!!!!!!attr_val->attr_max_len = %x\n",attr_val->attr_max_len); + p_buf->attr_val.attr_len = attr_val->attr_len; + p_buf->attr_val.attr_max_len = attr_val->attr_max_len; + p_buf->attr_val.attr_val = (uint8_t *)GKI_getbuf(len); + if(p_buf->attr_val.attr_val != NULL){ + memcpy(p_buf->attr_val.attr_val, attr_val->attr_val, attr_val->attr_len); + } + } if (p_char_uuid) { memcpy(&p_buf->char_uuid, p_char_uuid, sizeof(tBT_UUID)); @@ -253,11 +270,16 @@ void BTA_GATTS_AddCharacteristic (UINT16 service_id, tBT_UUID *p_char_uuid, *******************************************************************************/ void BTA_GATTS_AddCharDescriptor (UINT16 service_id, tBTA_GATT_PERM perm, - tBT_UUID *p_descr_uuid) + tBT_UUID *p_descr_uuid, tBTA_GATT_ATTR_VAL *attr_val, + tBTA_GATTS_ATTR_CONTROL *control) { tBTA_GATTS_API_ADD_DESCR *p_buf; - UINT16 len = sizeof(tBTA_GATTS_API_ADD_DESCR); - + UINT16 len = 0; + if(attr_val != NULL) { + len = sizeof(tBTA_GATTS_API_ADD_DESCR) + attr_val->attr_len; + } else { + len = sizeof(tBTA_GATTS_API_ADD_DESCR); + } if ((p_buf = (tBTA_GATTS_API_ADD_DESCR *) GKI_getbuf(len)) != NULL) { memset(p_buf, 0, len); @@ -269,10 +291,19 @@ void BTA_GATTS_AddCharDescriptor (UINT16 service_id, if (p_descr_uuid) { memcpy(&p_buf->descr_uuid, p_descr_uuid, sizeof(tBT_UUID)); } + + if(attr_val->attr_len != 0) { + p_buf->attr_val.attr_len = attr_val->attr_len; + p_buf->attr_val.attr_max_len= attr_val->attr_max_len; + memcpy(p_buf->attr_val.attr_val, attr_val->attr_val, attr_val->attr_len); + } + + if(control != NULL) { + p_buf->control.auto_rsp = control->auto_rsp; + } bta_sys_sendmsg(p_buf); } return; - } /******************************************************************************* @@ -433,6 +464,29 @@ void BTA_GATTS_SendRsp (UINT16 conn_id, UINT32 trans_id, } +void BTA_SetAttributeValue(UINT16 attr_handle, UINT16 length, UINT8 *value) +{ + tBTA_GATTS_API_SET_ATTR_VAL *p_buf; + if((p_buf = (tBTA_GATTS_API_SET_ATTR_VAL *)GKI_getbuf( + sizeof(tBTA_GATTS_API_SET_ATTR_VAL))) != NULL){ + p_buf->hdr.event = BTA_GATTS_API_SET_ATTR_VAL_EVT; + p_buf->hdr.layer_specific = attr_handle; + p_buf->length = length; + if(value != NULL){ + if((p_buf->value = (UINT8 *)GKI_getbuf(length)) != NULL){ + memcpy(p_buf->value, value, length); + } + } + + bta_sys_sendmsg(p_buf); + } + +} + +void BTA_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value) +{ + bta_gatts_get_attr_value(attr_handle, length, value); +} /******************************************************************************* ** diff --git a/components/bt/bluedroid/bta/gatt/bta_gatts_main.c b/components/bt/bluedroid/bta/gatt/bta_gatts_main.c index d59115d42d..4306709c8c 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gatts_main.c +++ b/components/bt/bluedroid/bta/gatt/bta_gatts_main.c @@ -104,29 +104,30 @@ BOOLEAN bta_gatts_hdl_event(BT_HDR *p_msg) case BTA_GATTS_API_RSP_EVT: bta_gatts_send_rsp(p_cb, (tBTA_GATTS_DATA *) p_msg); break; - + case BTA_GATTS_API_SET_ATTR_VAL_EVT:{ + UINT16 attr_id = ((tBTA_GATTS_DATA *) p_msg)->api_set_val.hdr.layer_specific; + p_srvc_cb = bta_gatts_find_srvc_cb_by_attr_id(p_cb, attr_id); + bta_gatts_set_attr_value(p_srvc_cb, (tBTA_GATTS_DATA *) p_msg); + break; + } case BTA_GATTS_API_LISTEN_EVT: bta_gatts_listen(p_cb, (tBTA_GATTS_DATA *) p_msg); break; - - case BTA_GATTS_API_ADD_INCL_SRVC_EVT: case BTA_GATTS_API_ADD_CHAR_EVT: case BTA_GATTS_API_ADD_DESCR_EVT: case BTA_GATTS_API_DEL_SRVC_EVT: case BTA_GATTS_API_START_SRVC_EVT: case BTA_GATTS_API_STOP_SRVC_EVT: - p_srvc_cb = bta_gatts_find_srvc_cb_by_srvc_id(p_cb, ((tBTA_GATTS_DATA *)p_msg)->api_add_incl_srvc.hdr.layer_specific); if (p_srvc_cb != NULL) { bta_gatts_srvc_build_act[p_msg->event - BTA_GATTS_API_ADD_INCL_SRVC_EVT](p_srvc_cb, (tBTA_GATTS_DATA *) p_msg); } else { - APPL_TRACE_ERROR("service not created"); + APPL_TRACE_ERROR("service not created\n"); } break; - default: break; } diff --git a/components/bt/bluedroid/bta/include/bta_gatt_api.h b/components/bt/bluedroid/bta/include/bta_gatt_api.h index d10ca46aee..f4c0e06f07 100644 --- a/components/bt/bluedroid/bta/include/bta_gatt_api.h +++ b/components/bt/bluedroid/bta/include/bta_gatt_api.h @@ -109,41 +109,41 @@ typedef UINT8 tBTA_GATT_STATUS; /* Client callback function events */ -#define BTA_GATTC_REG_EVT 0 /* GATT client is registered. */ -#define BTA_GATTC_DEREG_EVT 1 /* GATT client deregistered event */ -#define BTA_GATTC_OPEN_EVT 2 /* GATTC open request status event */ -#define BTA_GATTC_READ_CHAR_EVT 3 /* GATT read characteristic event */ -#define BTA_GATTC_WRITE_CHAR_EVT 4 /* GATT write characteristic or char descriptor event */ -#define BTA_GATTC_CLOSE_EVT 5 /* GATTC close request status event */ -#define BTA_GATTC_SEARCH_CMPL_EVT 6 /* GATT discovery complete event */ -#define BTA_GATTC_SEARCH_RES_EVT 7 /* GATT discovery result event */ -#define BTA_GATTC_READ_DESCR_EVT 8 /* GATT read characterisitc descriptor event */ -#define BTA_GATTC_WRITE_DESCR_EVT 9 /* GATT write characteristic descriptor event */ -#define BTA_GATTC_NOTIF_EVT 10 /* GATT attribute notification event */ -#define BTA_GATTC_PREP_WRITE_EVT 11 /* GATT prepare write event */ -#define BTA_GATTC_EXEC_EVT 12 /* execute write complete event */ -#define BTA_GATTC_ACL_EVT 13 /* ACL up event */ -#define BTA_GATTC_CANCEL_OPEN_EVT 14 /* cancel open event */ -#define BTA_GATTC_SRVC_CHG_EVT 15 /* service change event */ -#define BTA_GATTC_LISTEN_EVT 16 /* listen event */ -#define BTA_GATTC_ENC_CMPL_CB_EVT 17 /* encryption complete callback event */ -#define BTA_GATTC_CFG_MTU_EVT 18 /* configure MTU complete event */ -#define BTA_GATTC_ADV_DATA_EVT 19 /* ADV data event */ -#define BTA_GATTC_MULT_ADV_ENB_EVT 20 /* Enable Multi ADV event */ -#define BTA_GATTC_MULT_ADV_UPD_EVT 21 /* Update parameter event */ -#define BTA_GATTC_MULT_ADV_DATA_EVT 22 /* Multi ADV data event */ -#define BTA_GATTC_MULT_ADV_DIS_EVT 23 /* Disable Multi ADV event */ -#define BTA_GATTC_CONGEST_EVT 24 /* Congestion event */ -#define BTA_GATTC_BTH_SCAN_ENB_EVT 25 /* Enable batch scan event */ -#define BTA_GATTC_BTH_SCAN_CFG_EVT 26 /* Config storage event */ -#define BTA_GATTC_BTH_SCAN_RD_EVT 27 /* Batch scan reports read event */ -#define BTA_GATTC_BTH_SCAN_THR_EVT 28 /* Batch scan threshold event */ -#define BTA_GATTC_BTH_SCAN_PARAM_EVT 29 /* Batch scan param event */ -#define BTA_GATTC_BTH_SCAN_DIS_EVT 30 /* Disable batch scan event */ -#define BTA_GATTC_SCAN_FLT_CFG_EVT 31 /* Scan filter config event */ -#define BTA_GATTC_SCAN_FLT_PARAM_EVT 32 /* Param filter event */ -#define BTA_GATTC_SCAN_FLT_STATUS_EVT 33 /* Filter status event */ -#define BTA_GATTC_ADV_VSC_EVT 34 /* ADV VSC event */ +#define BTA_GATTC_REG_EVT 0 /* GATT client is registered. */ +#define BTA_GATTC_DEREG_EVT 1 /* GATT client deregistered event */ +#define BTA_GATTC_OPEN_EVT 2 /* GATTC open request status event */ +#define BTA_GATTC_READ_CHAR_EVT 3 /* GATT read characteristic event */ +#define BTA_GATTC_WRITE_CHAR_EVT 4 /* GATT write characteristic or char descriptor event */ +#define BTA_GATTC_CLOSE_EVT 5 /* GATTC close request status event */ +#define BTA_GATTC_SEARCH_CMPL_EVT 6 /* GATT discovery complete event */ +#define BTA_GATTC_SEARCH_RES_EVT 7 /* GATT discovery result event */ +#define BTA_GATTC_READ_DESCR_EVT 8 /* GATT read characterisitc descriptor event */ +#define BTA_GATTC_WRITE_DESCR_EVT 9 /* GATT write characteristic descriptor event */ +#define BTA_GATTC_NOTIF_EVT 10 /* GATT attribute notification event */ +#define BTA_GATTC_PREP_WRITE_EVT 11 /* GATT prepare write event */ +#define BTA_GATTC_EXEC_EVT 12 /* execute write complete event */ +#define BTA_GATTC_ACL_EVT 13 /* ACL up event */ +#define BTA_GATTC_CANCEL_OPEN_EVT 14 /* cancel open event */ +#define BTA_GATTC_SRVC_CHG_EVT 15 /* service change event */ +#define BTA_GATTC_LISTEN_EVT 16 /* listen event */ +#define BTA_GATTC_ENC_CMPL_CB_EVT 17 /* encryption complete callback event */ +#define BTA_GATTC_CFG_MTU_EVT 18 /* configure MTU complete event */ +#define BTA_GATTC_ADV_DATA_EVT 19 /* ADV data event */ +#define BTA_GATTC_MULT_ADV_ENB_EVT 20 /* Enable Multi ADV event */ +#define BTA_GATTC_MULT_ADV_UPD_EVT 21 /* Update parameter event */ +#define BTA_GATTC_MULT_ADV_DATA_EVT 22 /* Multi ADV data event */ +#define BTA_GATTC_MULT_ADV_DIS_EVT 23 /* Disable Multi ADV event */ +#define BTA_GATTC_CONGEST_EVT 24 /* Congestion event */ +#define BTA_GATTC_BTH_SCAN_ENB_EVT 25 /* Enable batch scan event */ +#define BTA_GATTC_BTH_SCAN_CFG_EVT 26 /* Config storage event */ +#define BTA_GATTC_BTH_SCAN_RD_EVT 27 /* Batch scan reports read event */ +#define BTA_GATTC_BTH_SCAN_THR_EVT 28 /* Batch scan threshold event */ +#define BTA_GATTC_BTH_SCAN_PARAM_EVT 29 /* Batch scan param event */ +#define BTA_GATTC_BTH_SCAN_DIS_EVT 30 /* Disable batch scan event */ +#define BTA_GATTC_SCAN_FLT_CFG_EVT 31 /* Scan filter config event */ +#define BTA_GATTC_SCAN_FLT_PARAM_EVT 32 /* Param filter event */ +#define BTA_GATTC_SCAN_FLT_STATUS_EVT 33 /* Filter status event */ +#define BTA_GATTC_ADV_VSC_EVT 34 /* ADV VSC event */ typedef UINT8 tBTA_GATTC_EVT; @@ -151,7 +151,7 @@ typedef tGATT_IF tBTA_GATTC_IF; typedef struct { UINT16 unit; /* as UUIUD defined by SIG */ - UINT16 descr; /* as UUID as defined by SIG */ + UINT16 descr; /* as UUID as defined by SIG */ tGATT_FORMAT format; INT8 exp; UINT8 name_spc; /* The name space of the description */ @@ -165,7 +165,7 @@ typedef UINT16 tBTA_GATT_CLT_CHAR_CONFIG; /* characteristic descriptor: server configuration value */ #define BTA_GATT_SVR_CONFIG_NONE GATT_SVR_CONFIG_NONE /* 0x0000 */ -#define BTA_GATT_SVR_CONFIG_BROADCAST GATT_SVR_CONFIG_BROADCAST /* 0x0001 */ +#define BTA_GATT_SVR_CONFIG_BROADCAST GATT_SVR_CONFIG_BROADCAST /* 0x0001 */ typedef UINT16 tBTA_GATT_SVR_CHAR_CONFIG; /* Characteristic Aggregate Format attribute value @@ -367,8 +367,8 @@ typedef struct { // btla-specific -- typedef struct { - tBTA_GATTC_IF client_if; - BD_ADDR remote_bda; + tBTA_GATTC_IF client_if; + BD_ADDR remote_bda; } tBTA_GATTC_ENC_CMPL_CB; typedef union { @@ -395,7 +395,6 @@ typedef void (tBTA_GATTC_ENB_CBACK)(tBTA_GATT_STATUS status); /* Client callback function */ typedef void (tBTA_GATTC_CBACK)(tBTA_GATTC_EVT event, tBTA_GATTC *p_data); - /* GATT Server Data Structure */ /* Server callback function events */ #define BTA_GATTS_REG_EVT 0 @@ -419,6 +418,7 @@ typedef void (tBTA_GATTC_CBACK)(tBTA_GATTC_EVT event, tBTA_GATTC *p_data); #define BTA_GATTS_CLOSE_EVT 18 #define BTA_GATTS_LISTEN_EVT 19 #define BTA_GATTS_CONGEST_EVT 20 +#define BTA_GATTS_SET_ATTR_VAL_EVT 21 typedef UINT8 tBTA_GATTS_EVT; typedef tGATT_IF tBTA_GATTS_IF; @@ -434,20 +434,22 @@ typedef tGATT_IF tBTA_GATTS_IF; #define BTA_GATT_PERM_WRITE_SIGNED GATT_PERM_WRITE_SIGNED /* bit 7 - 0x0080 */ #define BTA_GATT_PERM_WRITE_SIGNED_MITM GATT_PERM_WRITE_SIGNED_MITM /* bit 8 - 0x0100 */ typedef UINT16 tBTA_GATT_PERM; +typedef tGATT_ATTR_VAL tBTA_GATT_ATTR_VAL; +typedef tGATTS_ATTR_CONTROL tBTA_GATTS_ATTR_CONTROL; #define BTA_GATTS_INVALID_APP 0xff #define BTA_GATTS_INVALID_IF 0 /* definition of characteristic properties */ -#define BTA_GATT_CHAR_PROP_BIT_BROADCAST GATT_CHAR_PROP_BIT_BROADCAST /* 0x01 */ -#define BTA_GATT_CHAR_PROP_BIT_READ GATT_CHAR_PROP_BIT_READ /* 0x02 */ -#define BTA_GATT_CHAR_PROP_BIT_WRITE_NR GATT_CHAR_PROP_BIT_WRITE_NR /* 0x04 */ -#define BTA_GATT_CHAR_PROP_BIT_WRITE GATT_CHAR_PROP_BIT_WRITE /* 0x08 */ -#define BTA_GATT_CHAR_PROP_BIT_NOTIFY GATT_CHAR_PROP_BIT_NOTIFY /* 0x10 */ -#define BTA_GATT_CHAR_PROP_BIT_INDICATE GATT_CHAR_PROP_BIT_INDICATE /* 0x20 */ -#define BTA_GATT_CHAR_PROP_BIT_AUTH GATT_CHAR_PROP_BIT_AUTH /* 0x40 */ -#define BTA_GATT_CHAR_PROP_BIT_EXT_PROP GATT_CHAR_PROP_BIT_EXT_PROP /* 0x80 */ +#define BTA_GATT_CHAR_PROP_BIT_BROADCAST GATT_CHAR_PROP_BIT_BROADCAST /* 0x01 */ +#define BTA_GATT_CHAR_PROP_BIT_READ GATT_CHAR_PROP_BIT_READ /* 0x02 */ +#define BTA_GATT_CHAR_PROP_BIT_WRITE_NR GATT_CHAR_PROP_BIT_WRITE_NR /* 0x04 */ +#define BTA_GATT_CHAR_PROP_BIT_WRITE GATT_CHAR_PROP_BIT_WRITE /* 0x08 */ +#define BTA_GATT_CHAR_PROP_BIT_NOTIFY GATT_CHAR_PROP_BIT_NOTIFY /* 0x10 */ +#define BTA_GATT_CHAR_PROP_BIT_INDICATE GATT_CHAR_PROP_BIT_INDICATE /* 0x20 */ +#define BTA_GATT_CHAR_PROP_BIT_AUTH GATT_CHAR_PROP_BIT_AUTH /* 0x40 */ +#define BTA_GATT_CHAR_PROP_BIT_EXT_PROP GATT_CHAR_PROP_BIT_EXT_PROP /* 0x80 */ typedef UINT8 tBTA_GATT_CHAR_PROP; #ifndef BTA_GATTC_CHAR_DESCR_MAX @@ -476,8 +478,8 @@ typedef tGATTS_SRV_CHG tBTA_GATTS_SRV_CHG; typedef tGATTS_SRV_CHG_REQ tBTA_GATTS_SRV_CHG_REQ; typedef tGATTS_SRV_CHG_RSP tBTA_GATTS_SRV_CHG_RSP; -#define BTA_GATT_TRANSPORT_LE GATT_TRANSPORT_LE -#define BTA_GATT_TRANSPORT_BR_EDR GATT_TRANSPORT_BR_EDR +#define BTA_GATT_TRANSPORT_LE GATT_TRANSPORT_LE +#define BTA_GATT_TRANSPORT_BR_EDR GATT_TRANSPORT_BR_EDR #define BTA_GATT_TRANSPORT_LE_BR_EDR GATT_TRANSPORT_LE_BR_EDR typedef UINT8 tBTA_GATT_TRANSPORT; @@ -539,6 +541,13 @@ typedef struct { // btla-specific -- } tBTA_GATTS_ADD_RESULT; +typedef struct{ + tBTA_GATTS_IF server_if; + UINT16 service_id; + UINT16 attr_id; + tBTA_GATT_STATUS status; +}tBAT_GATTS_ATTR_VAL_RESULT; + typedef struct { tBTA_GATTS_IF server_if; UINT16 service_id; @@ -566,17 +575,18 @@ typedef struct { /* GATTS callback data */ typedef union { - tBTA_GATTS_REG_OPER reg_oper; - tBTA_GATTS_CREATE create; - tBTA_GATTS_SRVC_OPER srvc_oper; - tBTA_GATT_STATUS status; /* BTA_GATTS_LISTEN_EVT */ - tBTA_GATTS_ADD_RESULT add_result; /* add included service: BTA_GATTS_ADD_INCL_SRVC_EVT - add char : BTA_GATTS_ADD_CHAR_EVT - add char descriptor: BTA_GATTS_ADD_CHAR_DESCR_EVT */ - tBTA_GATTS_REQ req_data; - tBTA_GATTS_CONN conn; /* BTA_GATTS_CONN_EVT */ - tBTA_GATTS_CONGEST congest; /* BTA_GATTS_CONGEST_EVT callback data */ - tBTA_GATTS_CONF confirm; /* BTA_GATTS_CONF_EVT callback data */ + tBTA_GATTS_REG_OPER reg_oper; + tBTA_GATTS_CREATE create; + tBTA_GATTS_SRVC_OPER srvc_oper; + tBTA_GATT_STATUS status; /* BTA_GATTS_LISTEN_EVT */ + tBTA_GATTS_ADD_RESULT add_result; /* add included service: BTA_GATTS_ADD_INCL_SRVC_EVT + add char : BTA_GATTS_ADD_CHAR_EVT + add char descriptor: BTA_GATTS_ADD_CHAR_DESCR_EVT */ + tBAT_GATTS_ATTR_VAL_RESULT attr_val; + tBTA_GATTS_REQ req_data; + tBTA_GATTS_CONN conn; /* BTA_GATTS_CONN_EVT */ + tBTA_GATTS_CONGEST congest; /* BTA_GATTS_CONGEST_EVT callback data */ + tBTA_GATTS_CONF confirm; /* BTA_GATTS_CONF_EVT callback data */ } tBTA_GATTS; /* GATTS enable callback function */ @@ -1193,8 +1203,9 @@ extern void BTA_GATTS_AddIncludeService(UINT16 service_id, UINT16 included_servi ** Returns None ** *******************************************************************************/ -extern void BTA_GATTS_AddCharacteristic (UINT16 service_id, tBT_UUID *p_char_uuid, - tBTA_GATT_PERM perm, tBTA_GATT_CHAR_PROP property); +extern void BTA_GATTS_AddCharacteristic (UINT16 service_id, tBT_UUID *p_char_uuid, + tBTA_GATT_PERM perm, tBTA_GATT_CHAR_PROP property, tGATT_ATTR_VAL *attr_val, + tBTA_GATTS_ATTR_CONTROL *control); /******************************************************************************* ** @@ -1214,8 +1225,9 @@ extern void BTA_GATTS_AddCharacteristic (UINT16 service_id, tBT_UUID *p_char_ ** *******************************************************************************/ extern void BTA_GATTS_AddCharDescriptor (UINT16 service_id, - tBTA_GATT_PERM perm, - tBT_UUID *p_descr_uuid); + tBTA_GATT_PERM perm, + tBT_UUID *p_descr_uuid, tBTA_GATT_ATTR_VAL *attr_val, + tBTA_GATTS_ATTR_CONTROL *control); /******************************************************************************* ** @@ -1296,6 +1308,38 @@ extern void BTA_GATTS_SendRsp (UINT16 conn_id, UINT32 trans_id, tBTA_GATT_STATUS status, tBTA_GATTS_RSP *p_msg); + +/******************************************************************************* +** +** Function BTA_SetAttributeValue +** +** Description This function is called to set the attribute value in the gatt database +** +** Parameters attr_handle - the attribute value handle. +** length - the value length which has been set to the attribute. +** value - the pointer to the value +** +** Returns None +** +*******************************************************************************/ +extern void BTA_SetAttributeValue(UINT16 attr_handle, UINT16 length, UINT8 *value); + + +/******************************************************************************* +** +** Function BTA_GetAttributeValue +** +** Description This function is called to get the attribute value in the gatt database +** +** Parameters attr_handle - the attribute value handle. +** length - the value length which has been set to the attribute. +** value - the pointer to the value +** +** Returns None +** +*******************************************************************************/ +extern void BTA_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value); + /******************************************************************************* ** ** Function BTA_GATTS_Open diff --git a/components/bt/bluedroid/bta/include/bta_gatts_int.h b/components/bt/bluedroid/bta/include/bta_gatts_int.h index f934c4ac6a..0f9e689ace 100644 --- a/components/bt/bluedroid/bta/include/bta_gatts_int.h +++ b/components/bt/bluedroid/bta/include/bta_gatts_int.h @@ -48,6 +48,7 @@ enum { BTA_GATTS_API_START_SRVC_EVT, BTA_GATTS_API_STOP_SRVC_EVT, BTA_GATTS_API_RSP_EVT, + BTA_GATTS_API_SET_ATTR_VAL_EVT, BTA_GATTS_API_OPEN_EVT, BTA_GATTS_API_CANCEL_OPEN_EVT, BTA_GATTS_API_CLOSE_EVT, @@ -91,19 +92,21 @@ typedef struct { tBT_UUID char_uuid; tBTA_GATT_PERM perm; tBTA_GATT_CHAR_PROP property; - + tBTA_GATTS_ATTR_CONTROL control; + tBTA_GATT_ATTR_VAL attr_val; } tBTA_GATTS_API_ADD_CHAR; typedef struct { BT_HDR hdr; UINT16 included_service_id; - } tBTA_GATTS_API_ADD_INCL_SRVC; typedef struct { - BT_HDR hdr; - tBT_UUID descr_uuid; - tBTA_GATT_PERM perm; + BT_HDR hdr; + tBT_UUID descr_uuid; + tBTA_GATT_PERM perm; + tBTA_GATTS_ATTR_CONTROL control; + tBTA_GATT_ATTR_VAL attr_val; } tBTA_GATTS_API_ADD_DESCR; typedef struct { @@ -121,6 +124,12 @@ typedef struct { tBTA_GATTS_RSP *p_rsp; } tBTA_GATTS_API_RSP; +typedef struct{ + BT_HDR hdr; + UINT16 length; + UINT8 *value; +}tBTA_GATTS_API_SET_ATTR_VAL; + typedef struct { BT_HDR hdr; tBTA_GATT_TRANSPORT transport; @@ -156,6 +165,7 @@ typedef union { tBTA_GATTS_API_START api_start; tBTA_GATTS_API_INDICATION api_indicate; tBTA_GATTS_API_RSP api_rsp; + tBTA_GATTS_API_SET_ATTR_VAL api_set_val; tBTA_GATTS_API_OPEN api_open; tBTA_GATTS_API_CANCEL_OPEN api_cancel_open; @@ -169,7 +179,7 @@ typedef struct { BOOLEAN in_use; tBT_UUID app_uuid; tBTA_GATTS_CBACK *p_cback; - tBTA_GATTS_IF gatt_if; + tBTA_GATTS_IF gatt_if; } tBTA_GATTS_RCB; /* service registration control block */ @@ -219,6 +229,8 @@ extern void bta_gatts_create_srvc(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg); extern void bta_gatts_add_include_srvc(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg); extern void bta_gatts_add_char(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg); extern void bta_gatts_add_char_descr(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg); +extern void bta_gatts_set_attr_value(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg); +extern void bta_gatts_get_attr_value(UINT16 attr_handle, UINT16 *length, UINT8 **value); extern void bta_gatts_delete_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg); extern void bta_gatts_start_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg); extern void bta_gatts_stop_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg); 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 e88c775f5f..7866ca0f80 100644 --- a/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c +++ b/components/bt/bluedroid/btc/profile/esp/blufi/blufi_prf.c @@ -88,6 +88,7 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) tBTA_GATTS_RSP rsp; LOG_DEBUG("blufi profile cb event = %x\n", event); + switch (event) { case BTA_GATTS_REG_EVT: LOG_DEBUG("REG: status %d, app_uuid %04x, gatt_if %d\n", p_data->reg_oper.status, p_data->reg_oper.uuid.uu.uuid16, p_data->reg_oper.server_if); @@ -187,7 +188,8 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) //add the frist blufi characteristic --> write characteristic BTA_GATTS_AddCharacteristic(blufi_env.handle_srvc, &blufi_char_uuid_p2e, (GATT_PERM_WRITE), - (GATT_CHAR_PROP_BIT_WRITE)); + (GATT_CHAR_PROP_BIT_WRITE), + NULL, NULL); break; case BTA_GATTS_ADD_CHAR_EVT: switch (p_data->add_result.char_uuid.uu.uuid16) { @@ -196,14 +198,16 @@ static void blufi_profile_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) BTA_GATTS_AddCharacteristic(blufi_env.handle_srvc, &blufi_char_uuid_e2p, (GATT_PERM_READ), - (GATT_PERM_READ | GATT_CHAR_PROP_BIT_NOTIFY)); + (GATT_PERM_READ | GATT_CHAR_PROP_BIT_NOTIFY), + NULL, NULL); break; case BLUFI_CHAR_E2P_UUID: /* ESP32 to Phone */ blufi_env.handle_char_e2p = p_data->add_result.attr_id; BTA_GATTS_AddCharDescriptor (blufi_env.handle_srvc, (GATT_PERM_READ | GATT_PERM_WRITE), - &blufi_descr_uuid_e2p); + &blufi_descr_uuid_e2p, + NULL, NULL); break; default: break; diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c index 9bc41d2ab1..255484fbc4 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c @@ -20,17 +20,30 @@ #include "btc_manage.h" #include "btc_gatts.h" #include "btc_gatt_util.h" - +#include "future.h" +#include "btc_main.h" #include "esp_gatts_api.h" #define A2C_GATTS_EVT(_bta_event) (_bta_event) //BTA TO BTC EVT #define C2A_GATTS_EVT(_btc_event) (_btc_event) //BTC TO BTA EVT +typedef struct { + future_t *complete_future; + uint16_t svc_start_hdl; + esp_bt_uuid_t svc_uuid; + bool is_tab_creat_svc; + uint8_t num_handle; + uint8_t handle_idx; + uint16_t handles[ESP_GATT_ATTR_HANDLE_MAX]; +} esp_btc_creat_tab_t; + +static esp_btc_creat_tab_t btc_creat_tab_env; + static inline void btc_gatts_cb_to_app(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { esp_gatts_cb_t btc_gatts_cb = (esp_gatts_cb_t)btc_profile_cb_get(BTC_PID_GATTS); if (btc_gatts_cb) { - btc_gatts_cb(event, gatts_if, param); + btc_gatts_cb(event, gatts_if, param); } } @@ -59,6 +72,56 @@ void btc_gatts_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) } } break; + + } + case BTC_GATTS_ACT_ADD_CHAR:{ + if (src->add_char.char_val.attr_value != NULL){ + dst->add_char.char_val.attr_value = (uint8_t *)GKI_getbuf(src->add_char.char_val.attr_len); + if(dst->add_char.char_val.attr_value != NULL){ + memcpy(dst->add_char.char_val.attr_value, src->add_char.char_val.attr_value, + src->add_char.char_val.attr_len); + }else{ + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + } + break; + } + case BTC_GATTS_ACT_ADD_CHAR_DESCR:{ + if(src->add_descr.descr_val.attr_value != NULL){ + dst->add_descr.descr_val.attr_value = (uint8_t *)GKI_getbuf(src->add_descr.descr_val.attr_len); + if(dst->add_descr.descr_val.attr_value != NULL){ + memcpy(dst->add_descr.descr_val.attr_value, src->add_descr.descr_val.attr_value, + src->add_descr.descr_val.attr_len); + }else{ + LOG_ERROR("%s %d no mem\n", __func__, msg->act); + } + } + break; + } + case BTC_GATTS_ACT_CREATE_ATTR_TAB:{ + uint8_t num_attr = src->create_attr_tab.max_nb_attr; + if(src->create_attr_tab.gatts_attr_db != NULL){ + dst->create_attr_tab.gatts_attr_db = (esp_gatts_attr_db_t *)GKI_getbuf(sizeof(esp_gatts_attr_db_t)*num_attr); + if(dst->create_attr_tab.gatts_attr_db != NULL){ + memcpy(dst->create_attr_tab.gatts_attr_db, src->create_attr_tab.gatts_attr_db, + sizeof(esp_gatts_attr_db_t)*num_attr); + }else{ + LOG_ERROR("%s %d no mem\n",__func__, msg->act); + } + } + break; + } + case BTC_GATTS_ACT_SET_ATTR_VALUE:{ + uint8_t len = src->set_attr_val.length; + if(src->set_attr_val.value){ + dst->set_attr_val.value = (uint8_t *)GKI_getbuf(len); + if(dst->set_attr_val.value != NULL){ + memcpy(dst->set_attr_val.value, src->set_attr_val.value, len); + }else{ + LOG_ERROR("%s %d no mem\n",__func__, msg->act); + } + } + break; } default: LOG_DEBUG("%s Unhandled deep copy %d\n", __func__, msg->act); @@ -91,6 +154,163 @@ void btc_gatts_arg_deep_free(btc_msg_t *msg) } +static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, + esp_gatt_if_t gatts_if, + uint8_t max_nb_attr, + uint8_t srvc_inst_id) +{ + uint16_t uuid = 0; + future_t *future_p; + esp_ble_gatts_cb_param_t param; + + //set the attribute table create service flag to ture + btc_creat_tab_env.is_tab_creat_svc = true; + btc_creat_tab_env.num_handle = max_nb_attr; + for(int i = 0; i < max_nb_attr; i++){ + if(gatts_attr_db[i].att_desc.uuid.len == ESP_UUID_LEN_16){ + uuid = gatts_attr_db[i].att_desc.uuid.uuid.uuid16; + } + future_p = future_new(); + if (future_p == NULL) { + LOG_ERROR("%s failed:no mem\n", __func__); + return ; + } + btc_creat_tab_env.complete_future = future_p; + btc_creat_tab_env.handle_idx = i; + switch(uuid) + { + case ESP_GATT_UUID_PRI_SERVICE:{ + tBTA_GATT_SRVC_ID srvc_id; + esp_gatt_srvc_id_t esp_srvc_id; + + esp_srvc_id.id.inst_id = srvc_inst_id; + memcpy(&esp_srvc_id.id.uuid, &gatts_attr_db[i].att_desc.uuid, sizeof(esp_bt_uuid_t)); + btc_to_bta_srvc_id(&srvc_id, &esp_srvc_id); + BTA_GATTS_CreateService(gatts_if, &srvc_id.id.uuid, + srvc_inst_id, max_nb_attr, true); + + if (future_await(future_p) == FUTURE_FAIL) { + LOG_ERROR("%s failed\n", __func__); + return; + } + break; + } + case ESP_GATT_UUID_SEC_SERVICE:{ + tBTA_GATT_SRVC_ID srvc_id; + esp_gatt_srvc_id_t esp_srvc_id; + + esp_srvc_id.id.inst_id = srvc_inst_id; + memcpy(&esp_srvc_id.id.uuid, &gatts_attr_db[i].att_desc.uuid, sizeof(esp_bt_uuid_t)); + btc_to_bta_srvc_id(&srvc_id, &esp_srvc_id); + BTA_GATTS_CreateService(gatts_if, &srvc_id.id.uuid, + srvc_inst_id, max_nb_attr, false); + if (future_await(future_p) == FUTURE_FAIL) { + LOG_ERROR("%s failed\n", __func__); + return; + } + break; + } + case ESP_GATT_UUID_INCLUDE_SERVICE:{ + esp_gatts_incl_svc_desc_t *incl_svc_desc = (esp_gatts_incl_svc_desc_t *)gatts_attr_db[i].att_desc.value; + + if(incl_svc_desc!= NULL){ + if(btc_creat_tab_env.svc_start_hdl != 0){ + BTA_GATTS_AddIncludeService(btc_creat_tab_env.svc_start_hdl, + incl_svc_desc->start_hdl); + + if (future_await(future_p) == FUTURE_FAIL) { + LOG_ERROR("%s failed\n", __func__); + return; + } + } + } + break; + } + case ESP_GATT_UUID_CHAR_DECLARE:{ + uint16_t svc_hal = 0; + tBT_UUID bta_char_uuid; + tGATT_ATTR_VAL attr_val; + tBTA_GATT_PERM perm; + tBTA_GATTS_ATTR_CONTROL control; + + if(btc_creat_tab_env.svc_start_hdl != 0){ + svc_hal = btc_creat_tab_env.svc_start_hdl; + esp_gatts_char_desc_t *char_desc = (esp_gatts_char_desc_t *)gatts_attr_db[i].att_desc.value; + if(char_desc != NULL){ + perm = gatts_attr_db[i+1].att_desc.perm; + attr_val.attr_len = gatts_attr_db[i+1].att_desc.length; + attr_val.attr_max_len = gatts_attr_db[i+1].att_desc.max_length; + btc_to_bta_uuid(&bta_char_uuid, &char_desc->attr_uuid); + attr_val.attr_val = gatts_attr_db[i+1].att_desc.value; + control.auto_rsp = gatts_attr_db[i+1].attr_control.auto_rsp; + BTA_GATTS_AddCharacteristic (svc_hal, &bta_char_uuid, + perm, char_desc->prop, &attr_val, &control); + + if (future_await(future_p) == FUTURE_FAIL) { + LOG_ERROR("%s failed\n", __func__); + return; + } + } + } + + break; + } + case ESP_GATT_UUID_CHAR_EXT_PROP: + case ESP_GATT_UUID_CHAR_DESCRIPTION: + case ESP_GATT_UUID_CHAR_CLIENT_CONFIG: + case ESP_GATT_UUID_CHAR_SRVR_CONFIG: + case ESP_GATT_UUID_CHAR_PRESENT_FORMAT: + case ESP_GATT_UUID_CHAR_AGG_FORMAT: + case ESP_GATT_UUID_CHAR_VALID_RANGE: + case ESP_GATT_UUID_EXT_RPT_REF_DESCR: + case ESP_GATT_UUID_RPT_REF_DESCR:{ + uint16_t svc_hal = btc_creat_tab_env.svc_start_hdl; + tBT_UUID bta_char_uuid; + tGATT_ATTR_VAL attr_val; + tBTA_GATT_PERM perm = gatts_attr_db[i].att_desc.perm; + tBTA_GATTS_ATTR_CONTROL control; + + if(svc_hal != 0){ + attr_val.attr_len = gatts_attr_db[i].att_desc.length; + attr_val.attr_max_len = gatts_attr_db[i].att_desc.max_length; + attr_val.attr_val = gatts_attr_db[i].att_desc.value; + btc_to_bta_uuid(&bta_char_uuid, &gatts_attr_db[i].att_desc.uuid); + control.auto_rsp = gatts_attr_db[i].attr_control.auto_rsp; + BTA_GATTS_AddCharDescriptor(svc_hal, perm, &bta_char_uuid, &attr_val, &control); + + if (future_await(future_p) == FUTURE_FAIL) { + LOG_ERROR("%s failed\n", __func__); + return; + } + } + break; + } + default: + break; + } + + + } + + param.add_attr_tab.status = ESP_GATT_OK; + param.add_attr_tab.num_handle = max_nb_attr; + param.add_attr_tab.handles = btc_creat_tab_env.handles; + memcpy(¶m.add_attr_tab.svc_uuid, &btc_creat_tab_env.svc_uuid, sizeof(esp_bt_uuid_t)); + + btc_gatts_cb_to_app(ESP_GATTS_CREAT_ATTR_TAB_EVT, gatts_if, ¶m); + //reset the env after sent the data to app + memset(&btc_creat_tab_env, 0, sizeof(esp_btc_creat_tab_t)); + + //release the flag vaule to false after finish the service created. + btc_creat_tab_env.is_tab_creat_svc = false; +} + +void btc_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, uint8_t **value) +{ + + BTA_GetAttributeValue(attr_handle, length, value); +} + static void btc_gatts_cb_param_copy_req(btc_msg_t *msg, void *p_dest, void *p_src) { @@ -137,7 +357,6 @@ static void btc_gatts_cb_param_copy_free(btc_msg_t *msg, tBTA_GATTS *p_data) GKI_freebuf(p_data->req_data.p_data); } break; - default: break; } @@ -148,11 +367,43 @@ static void btc_gatts_inter_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) { bt_status_t status; btc_msg_t msg; - + msg.sig = BTC_SIG_API_CB; msg.pid = BTC_PID_GATTS; msg.act = event; + if(btc_creat_tab_env.is_tab_creat_svc && btc_creat_tab_env.complete_future){ + switch(event){ + case BTA_GATTS_CREATE_EVT:{ + //save the service handle to the btc module after used + //the attribute table method to creat a service + bta_to_btc_uuid(&btc_creat_tab_env.svc_uuid, &p_data->create.uuid); + uint8_t index = btc_creat_tab_env.handle_idx; + btc_creat_tab_env.svc_start_hdl = p_data->create.service_id; + btc_creat_tab_env.handles[index] = p_data->create.service_id; + break; + } + case BTA_GATTS_ADD_INCL_SRVC_EVT:{ + uint8_t index = btc_creat_tab_env.handle_idx; + btc_creat_tab_env.handles[index] = p_data->add_result.attr_id; + break; + } + case BTA_GATTS_ADD_CHAR_EVT:{ + uint8_t index = btc_creat_tab_env.handle_idx; + btc_creat_tab_env.handles[index] = p_data->add_result.attr_id - 1; + btc_creat_tab_env.handles[index+1] = p_data->add_result.attr_id; + break; + } + case BTA_GATTS_ADD_CHAR_DESCR_EVT:{ + uint8_t index = btc_creat_tab_env.handle_idx; + btc_creat_tab_env.handles[index] = p_data->add_result.attr_id; + break; + } + default: + break; + } + future_ready(btc_creat_tab_env.complete_future, FUTURE_SUCCESS); + } status = btc_transfer_context(&msg, p_data, sizeof(tBTA_GATTS), btc_gatts_cb_param_copy_req); @@ -187,6 +438,12 @@ void btc_gatts_call_handler(btc_msg_t *msg) srvc_id.is_primary); break; } + case BTC_GATTS_ACT_CREATE_ATTR_TAB: + btc_gatts_act_create_attr_tab(arg->create_attr_tab.gatts_attr_db, + arg->create_attr_tab.gatts_if, + arg->create_attr_tab.max_nb_attr, + arg->create_attr_tab.srvc_inst_id); + break; case BTC_GATTS_ACT_DELETE_SERVICE: BTA_GATTS_DeleteService(arg->delete_srvc.service_handle); break; @@ -204,13 +461,17 @@ void btc_gatts_call_handler(btc_msg_t *msg) btc_to_bta_uuid(&uuid, &arg->add_char.char_uuid); BTA_GATTS_AddCharacteristic(arg->add_char.service_handle, &uuid, - arg->add_char.perm, arg->add_char.property); + arg->add_char.perm, arg->add_char.property, + (tGATT_ATTR_VAL *)&arg->add_char.char_val, + (tBTA_GATTS_ATTR_CONTROL *)&arg->add_char.attr_control); break; } case BTC_GATTS_ACT_ADD_CHAR_DESCR: { tBT_UUID uuid; btc_to_bta_uuid(&uuid, &arg->add_descr.descr_uuid); - BTA_GATTS_AddCharDescriptor(arg->add_descr.service_handle, arg->add_descr.perm, &uuid); + BTA_GATTS_AddCharDescriptor(arg->add_descr.service_handle, arg->add_descr.perm, &uuid, + (tBTA_GATT_ATTR_VAL *)&arg->add_descr.descr_val, + (tBTA_GATTS_ATTR_CONTROL *)&arg->add_descr.attr_control); break; } case BTC_GATTS_ACT_SEND_INDICATE: @@ -236,6 +497,9 @@ void btc_gatts_call_handler(btc_msg_t *msg) btc_gatts_cb_to_app(ESP_GATTS_RESPONSE_EVT, BTC_GATT_GET_GATT_IF(arg->send_rsp.conn_id), ¶m); break; } + case BTC_GATTS_ACT_SET_ATTR_VALUE: + + break; case BTC_GATTS_ACT_OPEN: { // Ensure device is in inquiry database tBTA_GATT_TRANSPORT transport = BTA_GATT_TRANSPORT_LE; @@ -359,6 +623,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.create.service_id.is_primary = p_data->create.is_primary; param.create.service_id.id.inst_id = p_data->create.svc_instance; bta_to_btc_uuid(¶m.create.service_id.id.uuid, &p_data->create.uuid); + btc_gatts_cb_to_app(ESP_GATTS_CREATE_EVT, gatts_if, ¶m); break; case BTA_GATTS_ADD_INCL_SRVC_EVT: @@ -391,6 +656,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) gatts_if = p_data->srvc_oper.server_if; param.del.status = p_data->srvc_oper.status; param.del.service_handle = p_data->srvc_oper.service_id; + btc_gatts_cb_to_app(ESP_GATTS_DELETE_EVT, gatts_if, ¶m); break; case BTA_GATTS_START_EVT: @@ -424,11 +690,11 @@ void btc_gatts_cb_handler(btc_msg_t *msg) btc_gatts_cb_to_app(ESP_GATTS_DISCONNECT_EVT, gatts_if, ¶m); break; case BTA_GATTS_OPEN_EVT: - // do nothing + // do nothing case BTA_GATTS_CANCEL_OPEN_EVT: - // do nothing + // do nothing case BTA_GATTS_CLOSE_EVT: - // do nothing + // do nothing case BTA_GATTS_LISTEN_EVT: // do nothing break; @@ -438,6 +704,13 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.congest.congested = p_data->congest.congested; btc_gatts_cb_to_app(ESP_GATTS_CONGEST_EVT, gatts_if, ¶m); break; + case BTA_GATTS_SET_ATTR_VAL_EVT: + gatts_if = p_data->attr_val.server_if; + param.set_attr_val.srvc_handle = p_data->attr_val.service_id; + param.set_attr_val.attr_handle = p_data->attr_val.attr_id; + param.set_attr_val.status = p_data->attr_val.status; + btc_gatts_cb_to_app(ESP_GATTS_SET_ATTR_VAL_EVT, gatts_if, ¶m); + break; default: // do nothing break; diff --git a/components/bt/bluedroid/btc/profile/std/include/btc_gatts.h b/components/bt/bluedroid/btc/profile/std/include/btc_gatts.h index 4ad276f3ec..caae44de44 100644 --- a/components/bt/bluedroid/btc/profile/std/include/btc_gatts.h +++ b/components/bt/bluedroid/btc/profile/std/include/btc_gatts.h @@ -24,6 +24,7 @@ typedef enum { BTC_GATTS_ACT_APP_REGISTER = 0, BTC_GATTS_ACT_APP_UNREGISTER, BTC_GATTS_ACT_CREATE_SERVICE, + BTC_GATTS_ACT_CREATE_ATTR_TAB, BTC_GATTS_ACT_DELETE_SERVICE, BTC_GATTS_ACT_START_SERVICE, BTC_GATTS_ACT_STOP_SERVICE, @@ -32,6 +33,7 @@ typedef enum { BTC_GATTS_ACT_ADD_CHAR_DESCR, BTC_GATTS_ACT_SEND_INDICATE, BTC_GATTS_ACT_SEND_RESPONSE, + BTC_GATTS_ACT_SET_ATTR_VALUE, BTC_GATTS_ACT_OPEN, BTC_GATTS_ACT_CLOSE, } btc_gatts_act_t; @@ -42,46 +44,67 @@ typedef union { struct app_reg_args { uint16_t app_id; } app_reg; + //BTC_GATTS_ACT_APP_UNREGISTER, struct app_unreg_args { esp_gatt_if_t gatts_if; } app_unreg; + //BTC_GATTS_ACT_CREATE_SERVICE, struct create_srvc_args { esp_gatt_if_t gatts_if; esp_gatt_srvc_id_t service_id; uint16_t num_handle; } create_srvc; + + //BTC_GATTS_ACT_CREATE_ATTR_TAB + struct create_attr_tab_args{ + esp_gatt_if_t gatts_if; + uint8_t srvc_inst_id; + uint8_t max_nb_attr; + esp_gatts_attr_db_t *gatts_attr_db; + }create_attr_tab; + //BTC_GATTS_ACT_DELETE_SERVICE, struct delete_srvc_args { uint16_t service_handle; } delete_srvc; + //BTC_GATTS_ACT_START_SERVICE, struct start_srvc_args { uint16_t service_handle; } start_srvc; + //BTC_GATTS_ACT_STOP_SERVICE, struct stop_srvc_args { uint16_t service_handle; } stop_srvc; + //BTC_GATTS_ACT_ADD_INCLUDE_SERVICE, struct add_incl_srvc_args { uint16_t service_handle; uint16_t included_service_handle; } add_incl_srvc; + //BTC_GATTS_ACT_ADD_CHAR, struct add_char_args { uint16_t service_handle; esp_bt_uuid_t char_uuid; esp_gatt_perm_t perm; esp_gatt_char_prop_t property; + esp_attr_control_t attr_control; + esp_attr_value_t char_val; } add_char; + //BTC_GATTS_ACT_ADD_CHAR_DESCR, struct add_descr_args { - uint16_t service_handle; + uint16_t service_handle; esp_bt_uuid_t descr_uuid; esp_gatt_perm_t perm; + esp_attr_control_t attr_control; + esp_attr_value_t descr_val; } add_descr; + //BTC_GATTS_ACT_SEND_INDICATE, struct send_indicate_args { uint16_t conn_id; @@ -90,6 +113,7 @@ typedef union { uint16_t value_len; uint8_t *value; } send_ind; + //BTC_GATTS_ACT_SEND_RESPONSE, struct send_rsp_args { uint16_t conn_id; @@ -97,21 +121,32 @@ typedef union { esp_gatt_status_t status; esp_gatt_rsp_t *rsp; } send_rsp; + + //BTC_GATTS_SET_ATTR_VALUE + struct set_attr_val_args{ + uint16_t length; + uint8_t *value; + } set_attr_val; + //BTC_GATTS_ACT_OPEN, struct open_args { esp_gatt_if_t gatts_if; esp_bd_addr_t remote_bda; bool is_direct; } open; + //BTC_GATTS_ACT_CLOSE, struct close_args { uint16_t conn_id; } close; + } btc_ble_gatts_args_t; void btc_gatts_call_handler(btc_msg_t *msg); void btc_gatts_cb_handler(btc_msg_t *msg); void btc_gatts_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); +void btc_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, uint8_t **value); + #endif /* __BTC_GATTS_H__ */ diff --git a/components/bt/bluedroid/hci/packet_fragmenter.c b/components/bt/bluedroid/hci/packet_fragmenter.c index bd46041c3b..9294cb5bbb 100644 --- a/components/bt/bluedroid/hci/packet_fragmenter.c +++ b/components/bt/bluedroid/hci/packet_fragmenter.c @@ -150,13 +150,10 @@ static void reassemble_and_dispatch(BT_HDR *packet) LOG_DEBUG("%s found unfinished packet for handle with start packet. Dropping old.\n", __func__); LOG_DEBUG("partial_packet->len = %x, offset = %x\n", partial_packet->len, partial_packet->len); - //for (int i = 0; i < partial_packet->len; i++) { - // LOG_ERROR("%x", partial_packet->data[i]); - //} - //LOG_ERROR("\n"); + hash_map_erase(partial_packets, (void *)(uintptr_t)handle); - //buffer_allocator->free(partial_packet); - //LOG_ERROR("+++++++++++++++++++\n"); + + } uint16_t full_length = l2cap_length + L2CAP_HEADER_SIZE + HCI_ACL_PREAMBLE_SIZE; diff --git a/components/bt/bluedroid/stack/gap/gap_ble.c b/components/bt/bluedroid/stack/gap/gap_ble.c index b560fad651..bf9ea08369 100644 --- a/components/bt/bluedroid/stack/gap/gap_ble.c +++ b/components/bt/bluedroid/stack/gap/gap_ble.c @@ -392,7 +392,8 @@ void gap_attr_db_init(void) */ uuid.len = LEN_UUID_16; uuid.uu.uuid16 = p_db_attr->uuid = GATT_UUID_GAP_DEVICE_NAME; - p_db_attr->handle = GATTS_AddCharacteristic(service_handle, &uuid, GATT_PERM_READ, GATT_CHAR_PROP_BIT_READ); + p_db_attr->handle = GATTS_AddCharacteristic(service_handle, &uuid, GATT_PERM_READ, GATT_CHAR_PROP_BIT_READ, + NULL, NULL); p_db_attr ++; /* add Icon characteristic @@ -401,7 +402,8 @@ void gap_attr_db_init(void) p_db_attr->handle = GATTS_AddCharacteristic(service_handle, &uuid, GATT_PERM_READ, - GATT_CHAR_PROP_BIT_READ); + GATT_CHAR_PROP_BIT_READ, + NULL, NULL); p_db_attr ++; #if ((defined BTM_PERIPHERAL_ENABLED) && (BTM_PERIPHERAL_ENABLED == TRUE)) @@ -416,7 +418,8 @@ void gap_attr_db_init(void) p_db_attr->handle = GATTS_AddCharacteristic(service_handle, &uuid, GATT_PERM_READ, - GATT_CHAR_PROP_BIT_READ); + GATT_CHAR_PROP_BIT_READ, + NULL, NULL); p_db_attr ++; #endif @@ -424,7 +427,8 @@ void gap_attr_db_init(void) uuid.len = LEN_UUID_16; uuid.uu.uuid16 = p_db_attr->uuid = GATT_UUID_GAP_CENTRAL_ADDR_RESOL; p_db_attr->handle = GATTS_AddCharacteristic(service_handle, &uuid, - GATT_PERM_READ, GATT_CHAR_PROP_BIT_READ); + GATT_PERM_READ, GATT_CHAR_PROP_BIT_READ, + NULL, NULL); p_db_attr->attr_value.addr_resolution = 0; p_db_attr++; diff --git a/components/bt/bluedroid/stack/gatt/gatt_api.c b/components/bt/bluedroid/stack/gatt/gatt_api.c index c464508da9..7a9d125a97 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_api.c +++ b/components/bt/bluedroid/stack/gatt/gatt_api.c @@ -151,10 +151,10 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, tBT_UUID *p_app_uuid128; - GATT_TRACE_API ("GATTS_CreateService" ); + GATT_TRACE_API ("GATTS_CreateService\n" ); if (p_reg == NULL) { - GATT_TRACE_ERROR ("Inavlid gatt_if=%d", gatt_if); + GATT_TRACE_ERROR ("Inavlid gatt_if=%d\n", gatt_if); return (0); } @@ -162,7 +162,7 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, if ((p_list = gatt_find_hdl_buffer_by_app_id(p_app_uuid128, p_svc_uuid, svc_inst)) != NULL) { s_hdl = p_list->asgn_range.s_handle; - GATT_TRACE_DEBUG ("Service already been created!!"); + GATT_TRACE_DEBUG ("Service already been created!!\n"); } else { if ( (p_svc_uuid->len == LEN_UUID_16) && (p_svc_uuid->uu.uuid16 == UUID_SERVCLASS_GATT_SERVER)) { s_hdl = gatt_cb.hdl_cfg.gatt_start_hdl; @@ -184,13 +184,13 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, /* check for space */ if (num_handles > (0xFFFF - s_hdl + 1)) { - GATT_TRACE_ERROR ("GATTS_ReserveHandles: no handles, s_hdl: %u needed: %u", s_hdl, num_handles); + GATT_TRACE_ERROR ("GATTS_ReserveHandles: no handles, s_hdl: %u needed: %u\n", s_hdl, num_handles); return (0); } if ( (p_list = gatt_alloc_hdl_buffer()) == NULL) { /* No free entry */ - GATT_TRACE_ERROR ("GATTS_ReserveHandles: no free handle blocks"); + GATT_TRACE_ERROR ("GATTS_ReserveHandles: no free handle blocks\n"); return (0); } @@ -210,7 +210,7 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, /* add a pending new service change item to the list */ if ( (p_buf = gatt_add_pending_new_srv_start(&p_list->asgn_range)) == NULL) { /* No free entry */ - GATT_TRACE_ERROR ("gatt_add_pending_new_srv_start: no free blocks"); + GATT_TRACE_ERROR ("gatt_add_pending_new_srv_start: no free blocks\n"); if (p_list) { gatt_remove_an_item_from_list(p_list_info, p_list); @@ -219,12 +219,12 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, return (0); } - GATT_TRACE_DEBUG ("Add a new srv chg item"); + GATT_TRACE_DEBUG ("Add a new srv chg item\n"); } } if (!gatts_init_service_db(&p_list->svc_db, p_svc_uuid, is_pri, s_hdl , num_handles)) { - GATT_TRACE_ERROR ("GATTS_ReserveHandles: service DB initialization failed"); + GATT_TRACE_ERROR ("GATTS_ReserveHandles: service DB initialization failed\n"); if (p_list) { gatt_remove_an_item_from_list(p_list_info, p_list); gatt_free_hdl_buffer(p_list); @@ -236,12 +236,6 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, return (0); } - GATT_TRACE_DEBUG ("GATTS_CreateService(success): handles needed:%u s_hdl=%u e_hdl=%u %s[%x] is_primary=%d", - num_handles, p_list->asgn_range.s_handle , p_list->asgn_range.e_handle, - ((p_list->asgn_range.svc_uuid.len == 2) ? "uuid16" : "uuid128" ), - p_list->asgn_range.svc_uuid.uu.uuid16, - p_list->asgn_range.is_primary); - return (s_hdl); } @@ -295,25 +289,27 @@ UINT16 GATTS_AddIncludeService (UINT16 service_handle, UINT16 include_svc_handle ** *******************************************************************************/ UINT16 GATTS_AddCharacteristic (UINT16 service_handle, tBT_UUID *p_char_uuid, - tGATT_PERM perm, tGATT_CHAR_PROP property) + tGATT_PERM perm, tGATT_CHAR_PROP property, + tGATT_ATTR_VAL *attr_val, tGATTS_ATTR_CONTROL *control) { tGATT_HDL_LIST_ELEM *p_decl; if ((p_decl = gatt_find_hdl_buffer_by_handle(service_handle)) == NULL) { - GATT_TRACE_DEBUG("Service not created"); + GATT_TRACE_DEBUG("Service not created\n"); return 0; } /* data validity checking */ if ( ((property & GATT_CHAR_PROP_BIT_AUTH) && !(perm & GATT_WRITE_SIGNED_PERM)) || ((perm & GATT_WRITE_SIGNED_PERM) && !(property & GATT_CHAR_PROP_BIT_AUTH)) ) { - GATT_TRACE_DEBUG("Invalid configuration property=0x%x perm=0x%x ", property, perm); + GATT_TRACE_DEBUG("Invalid configuration property=0x%x perm=0x%x\n ", property, perm); return 0; } return gatts_add_characteristic(&p_decl->svc_db, perm, property, - p_char_uuid); + p_char_uuid, + attr_val, control); } /******************************************************************************* ** @@ -336,7 +332,7 @@ UINT16 GATTS_AddCharacteristic (UINT16 service_handle, tBT_UUID *p_char_uuid, *******************************************************************************/ UINT16 GATTS_AddCharDescriptor (UINT16 service_handle, tGATT_PERM perm, - tBT_UUID *p_descr_uuid) + tBT_UUID *p_descr_uuid, tGATT_ATTR_VAL *attr_val, tGATTS_ATTR_CONTROL *control) { tGATT_HDL_LIST_ELEM *p_decl; @@ -353,7 +349,8 @@ UINT16 GATTS_AddCharDescriptor (UINT16 service_handle, return gatts_add_char_descr(&p_decl->svc_db, perm, - p_descr_uuid); + p_descr_uuid, + attr_val, control); } /******************************************************************************* @@ -493,9 +490,9 @@ tGATT_STATUS GATTS_StartService (tGATT_IF gatt_if, UINT16 service_handle, gatt_add_a_srv_to_list(&gatt_cb.srv_list_info, &gatt_cb.srv_list[i_sreg]); - GATT_TRACE_DEBUG ("allocated i_sreg=%d ", i_sreg); + GATT_TRACE_DEBUG ("allocated i_sreg=%d\n", i_sreg); - GATT_TRACE_DEBUG ("s_hdl=%d e_hdl=%d type=0x%x svc_inst=%d sdp_hdl=0x%x", + GATT_TRACE_DEBUG ("s_hdl=%d e_hdl=%d type=0x%x svc_inst=%d sdp_hdl=0x%x\n", p_sreg->s_hdl, p_sreg->e_hdl, p_sreg->type, p_sreg->service_instance, p_sreg->sdp_handle); @@ -676,16 +673,16 @@ tGATT_STATUS GATTS_SendRsp (UINT16 conn_id, UINT32 trans_id, tGATT_REG *p_reg = gatt_get_regcb(gatt_if); tGATT_TCB *p_tcb = gatt_get_tcb_by_idx(tcb_idx); - GATT_TRACE_API ("GATTS_SendRsp: conn_id: %u trans_id: %u Status: 0x%04x", + GATT_TRACE_API ("GATTS_SendRsp: conn_id: %u trans_id: %u Status: 0x%04x\n", conn_id, trans_id, status); if ( (p_reg == NULL) || (p_tcb == NULL)) { - GATT_TRACE_ERROR ("GATTS_SendRsp Unknown conn_id: %u ", conn_id); + GATT_TRACE_ERROR ("GATTS_SendRsp Unknown conn_id: %u\n", conn_id); return (tGATT_STATUS) GATT_INVALID_CONN_ID; } if (p_tcb->sr_cmd.trans_id != trans_id) { - GATT_TRACE_ERROR ("GATTS_SendRsp conn_id: %u waiting for op_code = %02x", + GATT_TRACE_ERROR ("GATTS_SendRsp conn_id: %u waiting for op_code = %02x\n", conn_id, p_tcb->sr_cmd.op_code); return (GATT_WRONG_STATE); @@ -696,6 +693,69 @@ tGATT_STATUS GATTS_SendRsp (UINT16 conn_id, UINT32 trans_id, return cmd_sent; } + +/******************************************************************************* +** +** Function GATTS_SetAttributeValue +** +** Description This function sends to set the attribute value . +** +** Parameter attr_handle:the attribute handle +** length: the attribute length +** value: the value to be set to the attribute in the database +** +** Returns GATT_SUCCESS if successfully sent; otherwise error code. +** +*******************************************************************************/ +tGATT_STATUS GATTS_SetAttributeValue(UINT16 attr_handle, UINT16 length, UINT8 *value) +{ + tGATT_STATUS status; + tGATT_HDL_LIST_ELEM *p_decl = NULL; + + GATT_TRACE_DEBUG("GATTS_SetAttributeValue: attr_handle: %u length: %u \n", + attr_handle, length); + + if ((p_decl = gatt_find_hdl_buffer_by_attr_handle(attr_handle)) == NULL) { + GATT_TRACE_DEBUG("Service not created\n"); + return GATT_INVALID_HANDLE; + } + + status = gatts_set_attribute_value(&p_decl->svc_db, attr_handle, length, value); + return status; + +} + + +/******************************************************************************* +** +** Function GATTS_GetAttributeValue +** +** Description This function sends to set the attribute value . +** +** Parameter attr_handle: the attribute handle +** length:the attribute value length in the database +** value: the attribute value out put +** +** Returns GATT_SUCCESS if successfully sent; otherwise error code. +** +*******************************************************************************/ +tGATT_STATUS GATTS_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value) +{ + tGATT_STATUS status; + tGATT_HDL_LIST_ELEM *p_decl; + + GATT_TRACE_DEBUG("GATTS_GetAttributeValue: attr_handle: %u\n", + attr_handle); + + if ((p_decl = gatt_find_hdl_buffer_by_attr_handle(attr_handle)) == NULL) { + GATT_TRACE_ERROR("Service not created\n"); + return GATT_INVALID_HANDLE; + } + + status = gatts_get_attribute_value(&p_decl->svc_db, attr_handle, length, value); + return status; +} + /*******************************************************************************/ /* GATT Profile Srvr Functions */ /*******************************************************************************/ @@ -1132,7 +1192,7 @@ tGATT_IF GATT_Register (tBT_UUID *p_app_uuid128, tGATT_CBACK *p_cb_info) break; } } - GATT_TRACE_API ("allocated gatt_if=%d", gatt_if); + GATT_TRACE_API ("allocated gatt_if=%d\n", gatt_if); return gatt_if; } diff --git a/components/bt/bluedroid/stack/gatt/gatt_attr.c b/components/bt/bluedroid/stack/gatt/gatt_attr.c index 4968828394..421b17cab7 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_attr.c +++ b/components/bt/bluedroid/stack/gatt/gatt_attr.c @@ -279,21 +279,24 @@ void gatt_profile_db_init (void) GATT_StartIf(gatt_cb.gatt_if); service_handle = GATTS_CreateService (gatt_cb.gatt_if , &uuid, 0, GATTP_MAX_ATTR_NUM, TRUE); + GATT_TRACE_ERROR ("GATTS_CreateService: handle of service handle%x", service_handle); + /* add Service Changed characteristic */ uuid.uu.uuid16 = gatt_cb.gattp_attr.uuid = GATT_UUID_GATT_SRV_CHGD; gatt_cb.gattp_attr.service_change = 0; gatt_cb.gattp_attr.handle = - gatt_cb.handle_of_h_r = GATTS_AddCharacteristic(service_handle, &uuid, 0, GATT_CHAR_PROP_BIT_INDICATE); + gatt_cb.handle_of_h_r = GATTS_AddCharacteristic(service_handle, &uuid, 0, GATT_CHAR_PROP_BIT_INDICATE, + NULL, NULL); - GATT_TRACE_DEBUG ("gatt_profile_db_init: handle of service changed%d", - gatt_cb.handle_of_h_r ); + GATT_TRACE_DEBUG ("gatt_profile_db_init: handle of service changed%d\n", + gatt_cb.handle_of_h_r); /* start service */ status = GATTS_StartService (gatt_cb.gatt_if, service_handle, GATTP_TRANSPORT_SUPPORTED ); - GATT_TRACE_DEBUG ("gatt_profile_db_init: gatt_if=%d start status%d", + GATT_TRACE_DEBUG ("gatt_profile_db_init: gatt_if=%d start status%d\n", gatt_cb.gatt_if, status); } diff --git a/components/bt/bluedroid/stack/gatt/gatt_db.c b/components/bt/bluedroid/stack/gatt/gatt_db.c index 2e74c7d2df..b95934a8fa 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_db.c +++ b/components/bt/bluedroid/stack/gatt/gatt_db.c @@ -65,12 +65,12 @@ BOOLEAN gatts_init_service_db (tGATT_SVC_DB *p_db, tBT_UUID *p_service, BOOLEAN GKI_init_q(&p_db->svc_buffer); if (!allocate_svc_db_buf(p_db)) { - GATT_TRACE_ERROR("gatts_init_service_db failed, no resources"); + GATT_TRACE_ERROR("gatts_init_service_db failed, no resources\n"); return FALSE; } - GATT_TRACE_DEBUG("gatts_init_service_db"); - GATT_TRACE_DEBUG("s_hdl = %d num_handle = %d", s_hdl, num_handle ); + GATT_TRACE_DEBUG("gatts_init_service_db\n"); + GATT_TRACE_DEBUG("s_hdl = %d num_handle = %d\n", s_hdl, num_handle ); /* update service database information */ p_db->next_handle = s_hdl; @@ -94,7 +94,7 @@ BOOLEAN gatts_init_service_db (tGATT_SVC_DB *p_db, tBT_UUID *p_service, BOOLEAN tBT_UUID *gatts_get_service_uuid (tGATT_SVC_DB *p_db) { if (!p_db || !p_db->p_attr_list) { - GATT_TRACE_ERROR("service DB empty"); + GATT_TRACE_ERROR("service DB empty\n"); return NULL; } else { @@ -127,28 +127,28 @@ static tGATT_STATUS gatts_check_attr_readability(tGATT_ATTR16 *p_attr, } if (!(perm & GATT_READ_ALLOWED)) { - GATT_TRACE_ERROR( "GATT_READ_NOT_PERMIT"); + GATT_TRACE_ERROR( "GATT_READ_NOT_PERMIT\n"); return GATT_READ_NOT_PERMIT; } if ((perm & GATT_READ_AUTH_REQUIRED ) && !(sec_flag & GATT_SEC_FLAG_LKEY_UNAUTHED) && !(sec_flag & BTM_SEC_FLAG_ENCRYPTED)) { - GATT_TRACE_ERROR( "GATT_INSUF_AUTHENTICATION"); + GATT_TRACE_ERROR( "GATT_INSUF_AUTHENTICATION\n"); return GATT_INSUF_AUTHENTICATION; } if ((perm & GATT_READ_MITM_REQUIRED ) && !(sec_flag & GATT_SEC_FLAG_LKEY_AUTHED)) { - GATT_TRACE_ERROR( "GATT_INSUF_AUTHENTICATION: MITM Required"); + GATT_TRACE_ERROR( "GATT_INSUF_AUTHENTICATION: MITM Required\n"); return GATT_INSUF_AUTHENTICATION; } if ((perm & GATT_READ_ENCRYPTED_REQUIRED ) && !(sec_flag & GATT_SEC_FLAG_ENCRYPTED)) { - GATT_TRACE_ERROR( "GATT_INSUF_ENCRYPTION"); + GATT_TRACE_ERROR( "GATT_INSUF_ENCRYPTION\n"); return GATT_INSUF_ENCRYPTION; } if ( (perm & GATT_READ_ENCRYPTED_REQUIRED) && (sec_flag & GATT_SEC_FLAG_ENCRYPTED) && (key_size < min_key_size)) { - GATT_TRACE_ERROR( "GATT_INSUF_KEY_SIZE"); + GATT_TRACE_ERROR( "GATT_INSUF_KEY_SIZE\n"); return GATT_INSUF_KEY_SIZE; } @@ -163,7 +163,7 @@ static tGATT_STATUS gatts_check_attr_readability(tGATT_ATTR16 *p_attr, case GATT_UUID_CHAR_CLIENT_CONFIG: case GATT_UUID_CHAR_SRVR_CONFIG: case GATT_UUID_CHAR_PRESENT_FORMAT: - GATT_TRACE_ERROR("GATT_NOT_LONG"); + GATT_TRACE_ERROR("GATT_NOT_LONG\n"); return GATT_NOT_LONG; default: @@ -206,7 +206,7 @@ static tGATT_STATUS read_attr_value (void *p_attr, tGATT_STATUS status; tGATT_ATTR16 *p_attr16 = (tGATT_ATTR16 *)p_attr; - GATT_TRACE_DEBUG("read_attr_value uuid=0x%04x perm=0x%0x sec_flag=0x%x offset=%d read_long=%d", + GATT_TRACE_DEBUG("read_attr_value uuid=0x%04x perm=0x%0x sec_flag=0x%x offset=%d read_long=%d\n", p_attr16->uuid, p_attr16->permission, sec_flag, @@ -268,7 +268,22 @@ static tGATT_STATUS read_attr_value (void *p_attr, status = GATT_SUCCESS; } } else { /* characteristic description or characteristic value */ - status = GATT_PENDING; + if (p_attr16->control.auto_rsp == GATT_RSP_BY_STACK) { + GATT_TRACE_DEBUG("before characteristic description or characteristic value\n"); + if (p_attr16->p_value != NULL && p_attr16->p_value->attr_val.attr_val != NULL) { + uint8_t *value = p_attr16->p_value->attr_val.attr_val + offset; + GATT_TRACE_DEBUG("after characteristic description or characteristic value\n"); + if (mtu >= p_attr16->p_value->attr_val.attr_len) { + ARRAY_TO_STREAM(p, value, p_attr16->p_value->attr_val.attr_len); + } else { + ARRAY_TO_STREAM(p, value, mtu); + } + } + status = GATT_STACK_RSP; + + } else { + status = GATT_PENDING; + } } *p_len = len; @@ -341,7 +356,7 @@ tGATT_STATUS gatts_db_read_attr_value_by_type (tGATT_TCB *p_tcb, status = read_attr_value ((void *)p_attr, 0, &p, FALSE, (UINT16)(*p_len - 2), &len, sec_flag, key_size); - if (status == GATT_PENDING) { + if (status == GATT_PENDING || status == GATT_STACK_RSP) { status = gatts_send_app_read_request(p_tcb, op_code, p_attr->handle, 0, trans_id); /* one callback at a time */ @@ -445,12 +460,12 @@ UINT16 gatts_add_included_service (tGATT_SVC_DB *p_db, UINT16 s_handle, UINT16 e *******************************************************************************/ UINT16 gatts_add_characteristic (tGATT_SVC_DB *p_db, tGATT_PERM perm, tGATT_CHAR_PROP property, - tBT_UUID *p_char_uuid) + tBT_UUID *p_char_uuid, tGATT_ATTR_VAL *attr_val, tGATTS_ATTR_CONTROL *control) { tGATT_ATTR16 *p_char_decl, *p_char_val; tBT_UUID uuid = {LEN_UUID_16, {GATT_UUID_CHAR_DECLARE}}; - GATT_TRACE_DEBUG("gatts_add_characteristic perm=0x%0x property=0x%0x", perm, property); + GATT_TRACE_DEBUG("gatts_add_characteristic perm=0x%0x property=0x%0x\n", perm, property); if ((p_char_decl = (tGATT_ATTR16 *)allocate_attr_in_db(p_db, &uuid, GATT_PERM_READ)) != NULL) { if (!copy_extra_byte_in_db(p_db, (void **)&p_char_decl->p_value, sizeof(tGATT_CHAR_DECL))) { @@ -467,8 +482,30 @@ UINT16 gatts_add_characteristic (tGATT_SVC_DB *p_db, tGATT_PERM perm, p_char_decl->p_value->char_decl.property = property; p_char_decl->p_value->char_decl.char_val_handle = p_char_val->handle; + if (control != NULL) { + p_char_val->control.auto_rsp = control->auto_rsp; + } else { + p_char_val->control.auto_rsp = GATT_RSP_DEFAULT; - p_char_val->p_value = NULL; + } + + if (attr_val != NULL) { + if (!copy_extra_byte_in_db(p_db, (void **)&p_char_val->p_value, sizeof(tGATT_ATTR_VAL))) { + deallocate_attr_in_db(p_db, p_char_val); + return 0; + } + GATT_TRACE_DEBUG("attr_val->attr_len = %x, attr_val->attr_max_len = %x\n", attr_val->attr_len, attr_val->attr_max_len); + GATT_TRACE_DEBUG("attribute handle = %x\n", p_char_val->handle); + p_char_val->p_value->attr_val.attr_len = attr_val->attr_len; + p_char_val->p_value->attr_val.attr_max_len = attr_val->attr_max_len; + p_char_val->p_value->attr_val.attr_val = GKI_getbuf(attr_val->attr_max_len); + if (p_char_val->p_value->attr_val.attr_val != NULL) { + GATT_TRACE_DEBUG("attribute value not NULL"); + memcpy(p_char_val->p_value->attr_val.attr_val, attr_val->attr_val, attr_val->attr_len); + } + } else { + p_char_val->p_value = NULL; + } return p_char_val->handle; } @@ -542,24 +579,221 @@ UINT8 gatt_convertchar_descr_type(tBT_UUID *p_descr_uuid) ** *******************************************************************************/ UINT16 gatts_add_char_descr (tGATT_SVC_DB *p_db, tGATT_PERM perm, - tBT_UUID *p_descr_uuid) + tBT_UUID *p_descr_uuid, tGATT_ATTR_VAL *attr_val, tGATTS_ATTR_CONTROL *control) { tGATT_ATTR16 *p_char_dscptr; - GATT_TRACE_DEBUG("gatts_add_char_descr uuid=0x%04x", p_descr_uuid->uu.uuid16); + GATT_TRACE_DEBUG("gatts_add_char_descr uuid=0x%04x\n", p_descr_uuid->uu.uuid16); /* Add characteristic descriptors */ - if ((p_char_dscptr = (tGATT_ATTR16 *)allocate_attr_in_db(p_db, - p_descr_uuid, - perm)) - == NULL) { + if ((p_char_dscptr = (tGATT_ATTR16 *)allocate_attr_in_db(p_db, p_descr_uuid, perm)) == NULL) { GATT_TRACE_DEBUG("gatts_add_char_descr Fail for adding char descriptors."); return 0; } else { + if (control != NULL) { + p_char_dscptr->control.auto_rsp = control->auto_rsp; + } + if (attr_val != NULL) { + if (!copy_extra_byte_in_db(p_db, (void **)&p_char_dscptr->p_value, sizeof(tGATT_ATTR_VAL))) { + deallocate_attr_in_db(p_db, p_char_dscptr); + return 0; + } + p_char_dscptr->p_value->attr_val.attr_len = attr_val->attr_len; + p_char_dscptr->p_value->attr_val.attr_max_len = attr_val->attr_max_len; + if (attr_val->attr_val != NULL) { + p_char_dscptr->p_value->attr_val.attr_val = GKI_getbuf(attr_val->attr_max_len); + if (p_char_dscptr->p_value->attr_val.attr_val != NULL) { + memset(p_char_dscptr->p_value->attr_val.attr_val, 0, attr_val->attr_max_len); + memcpy(p_char_dscptr->p_value->attr_val.attr_val, attr_val->attr_val, attr_val->attr_len); + } + } + } return p_char_dscptr->handle; } } + +/******************************************************************************* +** +** Function gatts_set_attribute_value +** +** Description This function add the attribute value in the database +** +** Parameter p_db: database pointer. +** attr_handle: the attribute handle +** length: the attribute value length +** value: the pointer to the data to be set to the attribute value in the database +** +** Returns Status of the operation. +** +*******************************************************************************/ +tGATT_STATUS gatts_set_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, + UINT16 length, UINT8 *value) +{ + tGATT_ATTR16 *p_cur, *p_next; + + if (p_db == NULL) { + GATT_TRACE_DEBUG("gatts_set_attribute_value Fail:p_db is NULL.\n"); + return GATT_INVALID_PDU; + } + if (p_db->p_attr_list == NULL) { + GATT_TRACE_DEBUG("gatts_set_attribute_value Fail:p_db->p_attr_list is NULL.\n"); + return GATT_INVALID_PDU; + } + + p_cur = (tGATT_ATTR16 *) p_db->p_attr_list; + p_next = (tGATT_ATTR16 *) p_cur->p_next; + + + for (; p_cur != NULL; p_cur = p_next, p_next = (tGATT_ATTR16 *)p_next->p_next) { + if (p_cur->handle == attr_handle) { + if (p_cur->uuid_type == GATT_ATTR_UUID_TYPE_16) { + switch (p_cur->uuid) { + case GATT_UUID_CHAR_DECLARE: + case GATT_UUID_INCLUDE_SERVICE: + return GATT_NOT_FOUND; + default: + if (p_cur->p_value->attr_val.attr_max_len < length) { + GATT_TRACE_ERROR("gatts_set_attribute_vaule failt:Invalid value length"); + } else { + memcpy(p_cur->p_value->attr_val.attr_val, value, length); + p_cur->p_value->attr_val.attr_len = length; + } + break; + } + } else { + if (p_cur->p_value->attr_val.attr_max_len < length) { + GATT_TRACE_ERROR("gatts_set_attribute_vaule failt:Invalid value length"); + } else { + memcpy(p_cur->p_value->attr_val.attr_val, value, length); + p_cur->p_value->attr_val.attr_len = length; + } + } + break; + } + } + + return GATT_SUCCESS; +} + + +/******************************************************************************* +** +** Function gatts_get_attribute_value +** +** Description This function get the attribute value in the database +** +** Parameter p_db: database pointer. +** attr_handle: the attribute handle +** length: the attribute value length +** value: the pointer to the data to be get to the attribute value in the database +** +** Returns Status of the operation. +** +*******************************************************************************/ +tGATT_STATUS gatts_get_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, + UINT16 *length, UINT8 **value) +{ + tGATT_ATTR16 *p_cur, *p_next; + GATT_TRACE_DEBUG("***********%s*************\n", __func__); + GATT_TRACE_DEBUG("attr_handle = %x\n", attr_handle); + if (p_db == NULL) { + GATT_TRACE_ERROR("gatts_get_attribute_value Fail:p_db is NULL.\n"); + return GATT_INVALID_PDU; + } + if (p_db->p_attr_list == NULL) { + GATT_TRACE_ERROR("gatts_get_attribute_value Fail:p_db->p_attr_list is NULL.\n"); + return GATT_INVALID_PDU; + } + + p_cur = (tGATT_ATTR16 *) p_db->p_attr_list; + p_next = (tGATT_ATTR16 *) p_cur->p_next; + + + for (; p_cur != NULL; p_cur = p_next, p_next = (tGATT_ATTR16 *)p_next->p_next) { + LOG_ERROR("p_ur->handle = %x\n", p_cur->handle); + if (p_cur->handle == attr_handle) { + + if (p_cur->uuid_type == GATT_ATTR_UUID_TYPE_16) { + switch (p_cur->uuid) { + case GATT_UUID_CHAR_DECLARE: + case GATT_UUID_INCLUDE_SERVICE: + break; + default: + if (p_cur->p_value->attr_val.attr_len != 0) { + *length = p_cur->p_value->attr_val.attr_len; + *value = p_cur->p_value->attr_val.attr_val; + return GATT_SUCCESS; + } else { + GATT_TRACE_ERROR("gatts_get_attribute_vaule failt:the value length is 0"); + return GATT_INVALID_ATTR_LEN; + } + break; + } + } else { + if (p_cur->p_value->attr_val.attr_len != 0) { + *length = p_cur->p_value->attr_val.attr_len; + *value = p_cur->p_value->attr_val.attr_val; + return GATT_SUCCESS; + } else { + GATT_TRACE_ERROR("gatts_get_attribute_vaule failt:the value length is 0"); + return GATT_INVALID_ATTR_LEN; + } + + } + + break; + + } + + + } + + return GATT_SUCCESS; +} + +BOOLEAN gatts_is_auto_response(UINT16 attr_handle) +{ + tGATT_HDL_LIST_ELEM *p_decl = NULL; + BOOLEAN rsp = FALSE; + tGATT_SVC_DB *p_db = NULL; + if ((p_decl = gatt_find_hdl_buffer_by_attr_handle(attr_handle)) == NULL) { + GATT_TRACE_DEBUG("Service not created\n"); + return rsp; + } + + p_db = &p_decl->svc_db; + + tGATT_ATTR16 *p_cur, *p_next; + + if (p_db == NULL) { + GATT_TRACE_DEBUG("gatts_get_attribute_value Fail:p_db is NULL.\n"); + return rsp; + } + if (p_db->p_attr_list == NULL) { + GATT_TRACE_DEBUG("gatts_get_attribute_value Fail:p_db->p_attr_list is NULL.\n"); + return rsp; + } + + p_cur = (tGATT_ATTR16 *) p_db->p_attr_list; + p_next = (tGATT_ATTR16 *) p_cur->p_next; + + for (; p_cur != NULL && p_next != NULL; + p_cur = p_next, p_next = (tGATT_ATTR16 *)p_next->p_next) { + if (p_cur->handle == attr_handle) { + if (p_cur->p_value != NULL && p_cur->control.auto_rsp == GATT_RSP_BY_STACK) { + rsp = true; + return rsp; + } + + } + + } + + return rsp; + +} + /*******************************************************************************/ /* Service Attribute Database Query Utility Functions */ /*******************************************************************************/ @@ -617,6 +851,41 @@ tGATT_STATUS gatts_read_attr_value_by_handle(tGATT_TCB *p_tcb, return status; } +tGATT_STATUS gatts_write_attr_value_by_handle(tGATT_SVC_DB *p_db, + UINT16 handle, UINT16 offset, + UINT8 *p_value, UINT16 len) +{ + tGATT_STATUS status = GATT_NOT_FOUND; + tGATT_ATTR16 *p_attr; + + if (p_db && p_db->p_attr_list) { + p_attr = (tGATT_ATTR16 *)p_db->p_attr_list; + + while (p_attr && handle >= p_attr->handle) { + if (p_attr->handle == handle ) { + if (p_attr->control.auto_rsp == GATT_RSP_BY_APP) { + return GATT_APP_RSP; + } + + if (p_attr->p_value != NULL && (p_attr->p_value->attr_val.attr_max_len > + offset + len)) { + memcpy(p_attr->p_value->attr_val.attr_val + offset, p_value, len); + p_attr->p_value->attr_val.attr_len = len + offset; + return GATT_SUCCESS; + } else { + return GATT_NOT_LONG; + } + } + + p_attr = (tGATT_ATTR16 *)p_attr->p_next; + + } + + } + + return status; +} + /******************************************************************************* ** ** Function gatts_read_attr_perm_check @@ -661,6 +930,8 @@ tGATT_STATUS gatts_read_attr_perm_check(tGATT_SVC_DB *p_db, return status; } + + /******************************************************************************* ** ** Function gatts_write_attr_perm_check @@ -835,7 +1106,7 @@ static void *allocate_attr_in_db(tGATT_SVC_DB *p_db, tBT_UUID *p_uuid, tGATT_PER UINT16 len = sizeof(tGATT_ATTR128); if (p_uuid == NULL) { - GATT_TRACE_ERROR("illegal UUID"); + GATT_TRACE_ERROR("illegal UUID\n"); return NULL; } @@ -845,17 +1116,17 @@ static void *allocate_attr_in_db(tGATT_SVC_DB *p_db, tBT_UUID *p_uuid, tGATT_PER len = sizeof(tGATT_ATTR32); } - GATT_TRACE_DEBUG("allocate attr %d bytes ", len); + GATT_TRACE_DEBUG("allocate attr %d bytes\n", len); if (p_db->end_handle <= p_db->next_handle) { - GATT_TRACE_DEBUG("handle space full. handle_max = %d next_handle = %d", + GATT_TRACE_DEBUG("handle space full. handle_max = %d next_handle = %d\n", p_db->end_handle, p_db->next_handle); return NULL; } if (p_db->mem_free < len) { if (!allocate_svc_db_buf(p_db)) { - GATT_TRACE_ERROR("allocate_attr_in_db failed, no resources"); + GATT_TRACE_ERROR("allocate_attr_in_db failed, no resources\n"); return NULL; } } @@ -896,19 +1167,21 @@ static void *allocate_attr_in_db(tGATT_SVC_DB *p_db, tBT_UUID *p_uuid, tGATT_PER } if (p_attr16->uuid_type == GATT_ATTR_UUID_TYPE_16) { - GATT_TRACE_DEBUG("=====> handle = [0x%04x] uuid16 = [0x%04x] perm=0x%02x ", + GATT_TRACE_DEBUG("=====> handle = [0x%04x] uuid16 = [0x%04x] perm=0x%02x\n", p_attr16->handle, p_attr16->uuid, p_attr16->permission); } else if (p_attr16->uuid_type == GATT_ATTR_UUID_TYPE_32) { - GATT_TRACE_DEBUG("=====> handle = [0x%04x] uuid32 = [0x%08x] perm=0x%02x ", + GATT_TRACE_DEBUG("=====> handle = [0x%04x] uuid32 = [0x%08x] perm=0x%02x\n", p_attr32->handle, p_attr32->uuid, p_attr32->permission); } else { - GATT_TRACE_DEBUG("=====> handle = [0x%04x] uuid128 = [0x%02x:0x%02x] perm=0x%02x ", + GATT_TRACE_DEBUG("=====> handle = [0x%04x] uuid128 = [0x%02x:0x%02x] perm=0x%02x\n", p_attr128->handle, p_attr128->uuid[0], p_attr128->uuid[1], p_attr128->permission); } return (void *)p_attr16; } + + /******************************************************************************* ** ** Function deallocate_attr_in_db @@ -974,7 +1247,7 @@ static BOOLEAN copy_extra_byte_in_db(tGATT_SVC_DB *p_db, void **p_dst, UINT16 le if (p_db->mem_free < len) { if (!allocate_svc_db_buf(p_db)) { - GATT_TRACE_ERROR("copy_extra_byte_in_db failed, no resources"); + GATT_TRACE_ERROR("copy_extra_byte_in_db failed, no resources\n"); return FALSE; } } diff --git a/components/bt/bluedroid/stack/gatt/gatt_main.c b/components/bt/bluedroid/stack/gatt/gatt_main.c index 8b40ed0e51..d7997a847f 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_main.c +++ b/components/bt/bluedroid/stack/gatt/gatt_main.c @@ -508,7 +508,7 @@ static void gatt_le_data_ind (UINT16 chan, BD_ADDR bd_addr, BT_HDR *p_buf) GKI_freebuf (p_buf); if (p_tcb != NULL) { - GATT_TRACE_WARNING ("ATT - Ignored L2CAP data while in state: %d", + GATT_TRACE_WARNING ("ATT - Ignored L2CAP data while in state: %d\n", gatt_get_ch_state(p_tcb)); } } @@ -906,10 +906,10 @@ void gatt_data_process (tGATT_TCB *p_tcb, BT_HDR *p_buf) } } } else { - GATT_TRACE_ERROR ("ATT - Rcvd L2CAP data, unknown cmd: 0x%x", op_code); + GATT_TRACE_ERROR ("ATT - Rcvd L2CAP data, unknown cmd: 0x%x\n", op_code); } } else { - GATT_TRACE_ERROR ("invalid data length, ignore"); + GATT_TRACE_ERROR ("invalid data length, ignore\n"); } GKI_freebuf (p_buf); diff --git a/components/bt/bluedroid/stack/gatt/gatt_sr.c b/components/bt/bluedroid/stack/gatt/gatt_sr.c index 4846d4ad3e..6f21e8c45d 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_sr.c +++ b/components/bt/bluedroid/stack/gatt/gatt_sr.c @@ -239,7 +239,7 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if, tGATT_STATUS ret_code = GATT_SUCCESS; UNUSED(trans_id); - GATT_TRACE_DEBUG("gatt_sr_process_app_rsp gatt_if=%d", gatt_if); + GATT_TRACE_DEBUG("gatt_sr_process_app_rsp gatt_if=%d\n", gatt_if); gatt_sr_update_cback_cnt(p_tcb, gatt_if, FALSE, FALSE); @@ -264,7 +264,7 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if, if (p_tcb->sr_cmd.p_rsp_msg == NULL) { p_tcb->sr_cmd.p_rsp_msg = attp_build_sr_msg (p_tcb, (UINT8)(op_code + 1), (tGATT_SR_MSG *)p_msg); } else { - GATT_TRACE_ERROR("Exception!!! already has respond message"); + GATT_TRACE_ERROR("Exception!!! already has respond message\n"); } } } @@ -279,7 +279,7 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if, gatt_dequeue_sr_cmd(p_tcb); } - GATT_TRACE_DEBUG("gatt_sr_process_app_rsp ret_code=%d", ret_code); + GATT_TRACE_DEBUG("gatt_sr_process_app_rsp ret_code=%d\n", ret_code); return ret_code; } @@ -371,7 +371,7 @@ void gatt_process_read_multi_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U #if GATT_CONFORMANCE_TESTING == TRUE if (gatt_cb.enable_err_rsp && gatt_cb.req_op_code == op_code) { - GATT_TRACE_DEBUG("Conformance tst: forced err rspvofr ReadMultiple: error status=%d", gatt_cb.err_status); + GATT_TRACE_DEBUG("Conformance tst: forced err rspvofr ReadMultiple: error status=%d\n", gatt_cb.err_status); STREAM_TO_UINT16(handle, p); @@ -871,7 +871,7 @@ static void gatts_process_mtu_req (tGATT_TCB *p_tcb, UINT16 len, UINT8 *p_data) ** - discover characteristic by UUID ** - relationship discovery ** -** Returns void +** Returns void ** *******************************************************************************/ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, UINT8 *p_data) @@ -889,10 +889,10 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, tGATT_SRV_LIST_ELEM *p_srv = NULL; reason = gatts_validate_packet_format(op_code, &len, &p_data, &uuid, &s_hdl, &e_hdl); - + GATT_TRACE_DEBUG("%s, op_code =%x, len = %x\n", __func__, op_code, len); #if GATT_CONFORMANCE_TESTING == TRUE if (gatt_cb.enable_err_rsp && gatt_cb.req_op_code == op_code) { - GATT_TRACE_DEBUG("Conformance tst: forced err rsp for ReadByType: error status=%d", gatt_cb.err_status); + GATT_TRACE_DEBUG("Conformance tst: forced err rsp for ReadByType: error status=%d\n", gatt_cb.err_status); gatt_send_error_rsp (p_tcb, gatt_cb.err_status, gatt_cb.req_op_code, s_hdl, FALSE); @@ -902,7 +902,7 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, if (reason == GATT_SUCCESS) { if ((p_msg = (BT_HDR *)GKI_getbuf(msg_len)) == NULL) { - GATT_TRACE_ERROR("gatts_process_find_info failed. no resources."); + GATT_TRACE_ERROR("gatts_process_find_info failed. no resources.\n"); reason = GATT_NO_RESOURCES; } else { @@ -959,7 +959,7 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, p_msg->offset = L2CAP_MIN_OFFSET; } } - if (reason != GATT_SUCCESS) { + if (reason != GATT_SUCCESS && reason != GATT_STACK_RSP) { if (p_msg) { GKI_freebuf(p_msg); } @@ -987,19 +987,32 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, UINT8 op_code, UINT16 len, UINT8 *p_data) { - tGATTS_DATA sr_data; - UINT32 trans_id; - tGATT_STATUS status; - UINT8 sec_flag, key_size, *p = p_data; - tGATT_SR_REG *p_sreg; - UINT16 conn_id; - + UINT16 buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET); + tGATTS_DATA sr_data; + UINT32 trans_id; + tGATT_STATUS status; + UINT8 sec_flag, key_size, *p = p_data, *p_m; + tGATT_SR_REG *p_sreg; + UINT16 conn_id, offset = 0; + BT_HDR *p_msg = NULL; memset(&sr_data, 0, sizeof(tGATTS_DATA)); + if ((p_msg = (BT_HDR *)GKI_getbuf(buf_len)) == NULL) { + GATT_TRACE_ERROR("gatts_process_write_req failed. no resources.\n"); + } + + memset(p_msg, 0, buf_len); + p_m = (UINT8 *)(p_msg + 1) + L2CAP_MIN_OFFSET; + *p_m ++ = op_code + 1; + p_msg->len = 1; + buf_len = p_tcb->payload_size - 1; + switch (op_code) { case GATT_REQ_PREPARE_WRITE: sr_data.write_req.is_prep = TRUE; STREAM_TO_UINT16(sr_data.write_req.offset, p); + UINT16_TO_STREAM(p_m, sr_data.write_req.is_prep); + offset = sr_data.write_req.offset; len -= 2; /* fall through */ case GATT_SIGN_CMD_WRITE: @@ -1012,11 +1025,16 @@ void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, case GATT_REQ_WRITE: if (op_code == GATT_REQ_WRITE || op_code == GATT_REQ_PREPARE_WRITE) { sr_data.write_req.need_rsp = TRUE; + if(op_code == GATT_REQ_PREPARE_WRITE){ + memcpy(p_m, p, len); + p_msg->len += len; + } } sr_data.write_req.handle = handle; sr_data.write_req.len = len; if (len != 0 && p != NULL) { memcpy (sr_data.write_req.value, p, len); + } break; } @@ -1035,18 +1053,26 @@ void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, sec_flag, key_size); + p_msg->len += len; if (status == GATT_SUCCESS) { if ((trans_id = gatt_sr_enqueue_cmd(p_tcb, op_code, handle)) != 0) { p_sreg = &gatt_cb.sr_reg[i_rcb]; conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, p_sreg->gatt_if); + status = gatts_write_attr_value_by_handle(gatt_cb.sr_reg[i_rcb].p_db, + handle, offset, p, len); gatt_sr_send_req_callback(conn_id, trans_id, GATTS_REQ_TYPE_WRITE, &sr_data); - - status = GATT_PENDING; + + if(status == GATT_SUCCESS){ + attp_send_sr_msg(p_tcb, p_msg); + }else if(status == GATT_NOT_LONG){ + gatt_send_error_rsp (p_tcb, status, op_code, handle, FALSE); + } + status = GATT_PENDING; } else { - GATT_TRACE_ERROR("max pending command, send error"); + GATT_TRACE_ERROR("max pending command, send error\n"); status = GATT_BUSY; /* max pending command, application error */ } } @@ -1072,15 +1098,15 @@ void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, static void gatts_process_read_req(tGATT_TCB *p_tcb, tGATT_SR_REG *p_rcb, UINT8 op_code, UINT16 handle, UINT16 len, UINT8 *p_data) { - UINT16 buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET); - tGATT_STATUS reason; - BT_HDR *p_msg = NULL; - UINT8 sec_flag, key_size, *p; - UINT16 offset = 0, value_len = 0; + UINT16 buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET); + tGATT_STATUS reason; + BT_HDR *p_msg = NULL; + UINT8 sec_flag, key_size, *p; + UINT16 offset = 0, value_len = 0; UNUSED (len); if ((p_msg = (BT_HDR *)GKI_getbuf(buf_len)) == NULL) { - GATT_TRACE_ERROR("gatts_process_find_info failed. no resources."); + GATT_TRACE_ERROR("gatts_process_find_info failed. no resources.\n"); reason = GATT_NO_RESOURCES; } else { @@ -1114,13 +1140,13 @@ static void gatts_process_read_req(tGATT_TCB *p_tcb, tGATT_SR_REG *p_rcb, UINT8 p_msg->len += value_len; } - if (reason != GATT_SUCCESS) { + if (reason != GATT_SUCCESS && reason != GATT_PENDING) { if (p_msg) { GKI_freebuf(p_msg); } /* in theroy BUSY is not possible(should already been checked), protected check */ - if (reason != GATT_PENDING && reason != GATT_BUSY) { + if (reason != GATT_BUSY) { gatt_send_error_rsp (p_tcb, reason, op_code, handle, FALSE); } } else { @@ -1149,7 +1175,7 @@ void gatts_process_attribute_req (tGATT_TCB *p_tcb, UINT8 op_code, tGATT_ATTR16 *p_attr; if (len < 2) { - GATT_TRACE_ERROR("Illegal PDU length, discard request"); + GATT_TRACE_ERROR("Illegal PDU length, discard request\n"); status = GATT_INVALID_PDU; } else { STREAM_TO_UINT16(handle, p); @@ -1159,7 +1185,7 @@ void gatts_process_attribute_req (tGATT_TCB *p_tcb, UINT8 op_code, #if GATT_CONFORMANCE_TESTING == TRUE gatt_cb.handle = handle; if (gatt_cb.enable_err_rsp && gatt_cb.req_op_code == op_code) { - GATT_TRACE_DEBUG("Conformance tst: forced err rsp: error status=%d", gatt_cb.err_status); + GATT_TRACE_DEBUG("Conformance tst: forced err rsp: error status=%d\n", gatt_cb.err_status); gatt_send_error_rsp (p_tcb, gatt_cb.err_status, gatt_cb.req_op_code, handle, FALSE); @@ -1350,24 +1376,24 @@ void gatt_server_handle_client_req (tGATT_TCB *p_tcb, UINT8 op_code, /* otherwise, ignore the pkt */ } else { switch (op_code) { - case GATT_REQ_READ_BY_GRP_TYPE: /* discover primary services */ - case GATT_REQ_FIND_TYPE_VALUE: /* discover service by UUID */ + case GATT_REQ_READ_BY_GRP_TYPE: /* discover primary services */ + case GATT_REQ_FIND_TYPE_VALUE: /* discover service by UUID */ gatts_process_primary_service_req (p_tcb, op_code, len, p_data); break; - case GATT_REQ_FIND_INFO:/* discover char descrptor */ + case GATT_REQ_FIND_INFO: /* discover char descrptor */ gatts_process_find_info(p_tcb, op_code, len, p_data); break; - case GATT_REQ_READ_BY_TYPE: /* read characteristic value, char descriptor value */ + case GATT_REQ_READ_BY_TYPE: /* read characteristic value, char descriptor value */ /* discover characteristic, discover char by UUID */ gatts_process_read_by_type_req(p_tcb, op_code, len, p_data); break; - case GATT_REQ_READ: /* read char/char descriptor value */ + case GATT_REQ_READ: /* read char/char descriptor value */ case GATT_REQ_READ_BLOB: - case GATT_REQ_WRITE: /* write char/char descriptor value */ + case GATT_REQ_WRITE: /* write char/char descriptor value */ case GATT_CMD_WRITE: case GATT_SIGN_CMD_WRITE: case GATT_REQ_PREPARE_WRITE: diff --git a/components/bt/bluedroid/stack/gatt/gatt_utils.c b/components/bt/bluedroid/stack/gatt/gatt_utils.c index 7b55a097c9..09f4e8df2f 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_utils.c +++ b/components/bt/bluedroid/stack/gatt/gatt_utils.c @@ -318,6 +318,34 @@ tGATT_HDL_LIST_ELEM *gatt_find_hdl_buffer_by_handle(UINT16 handle) } return NULL; } + +/******************************************************************************* +** +** Function gatt_find_hdl_buffer_by_attr_handle +** +** Description Find handle range buffer by attribute handle. +** +** Returns Pointer to the buffer, NULL no buffer available +** +*******************************************************************************/ +tGATT_HDL_LIST_ELEM *gatt_find_hdl_buffer_by_attr_handle(UINT16 attr_handle) +{ + tGATT_HDL_LIST_INFO *p_list_info = &gatt_cb.hdl_list_info; + tGATT_HDL_LIST_ELEM *p_list = NULL; + + p_list = p_list_info->p_first; + + while (p_list != NULL) { + if (p_list->in_use && (p_list->asgn_range.s_handle <= attr_handle) + && (p_list->asgn_range.e_handle >= attr_handle)) { + return (p_list); + } + p_list = p_list->p_next; + } + return NULL; +} + + /******************************************************************************* ** ** Function gatt_find_hdl_buffer_by_app_id @@ -1476,7 +1504,7 @@ tGATT_REG *gatt_get_regcb (tGATT_IF gatt_if) p_reg = &gatt_cb.cl_rcb[ii - 1]; if (!p_reg->in_use) { - GATT_TRACE_WARNING("gatt_if found but not in use."); + GATT_TRACE_WARNING("gatt_if found but not in use.\n"); return NULL; } diff --git a/components/bt/bluedroid/stack/gatt/include/gatt_int.h b/components/bt/bluedroid/stack/gatt/include/gatt_int.h index 1ac8431b3b..c9622a24c1 100644 --- a/components/bt/bluedroid/stack/gatt/include/gatt_int.h +++ b/components/bt/bluedroid/stack/gatt/include/gatt_int.h @@ -35,7 +35,10 @@ #define GATT_GET_GATT_IF(conn_id) ((tGATT_IF)((UINT8) (conn_id))) #define GATT_GET_SR_REG_PTR(index) (&gatt_cb.sr_reg[(UINT8) (index)]); -#define GATT_TRANS_ID_MAX 0x0fffffff /* 4 MSB is reserved */ +#define GATT_TRANS_ID_MAX 0x0fffffff /* 4 MSB is reserved */ +#define GATT_RSP_BY_APP 0x00 +#define GATT_RSP_BY_STACK 0x01 +#define GATT_RSP_DEFAULT GATT_RSP_BY_APP //need to rsp by the app. /* security action for GATT write and read request */ #define GATT_SEC_NONE 0 @@ -61,16 +64,16 @@ typedef UINT8 tGATT_SEC_ACTION; #define GATT_ATTR_OP_SPT_PREP_WRITE (0x00000001 << 10) #define GATT_ATTR_OP_SPT_EXE_WRITE (0x00000001 << 11) #define GATT_ATTR_OP_SPT_HDL_VALUE_CONF (0x00000001 << 12) -#define GATT_ATTR_OP_SP_SIGN_WRITE (0x00000001 << 13) +#define GATT_ATTR_OP_SP_SIGN_WRITE (0x00000001 << 13) -#define GATT_INDEX_INVALID 0xff +#define GATT_INDEX_INVALID 0xff -#define GATT_PENDING_REQ_NONE 0 +#define GATT_PENDING_REQ_NONE 0 -#define GATT_WRITE_CMD_MASK 0xc0 /*0x1100-0000*/ -#define GATT_AUTH_SIGN_MASK 0x80 /*0x1000-0000*/ -#define GATT_AUTH_SIGN_LEN 12 +#define GATT_WRITE_CMD_MASK 0xc0 /*0x1100-0000*/ +#define GATT_AUTH_SIGN_MASK 0x80 /*0x1000-0000*/ +#define GATT_AUTH_SIGN_LEN 12 #define GATT_HDR_SIZE 3 /* 1B opcode + 2B handle */ @@ -154,7 +157,7 @@ typedef union { tBT_UUID uuid; /* service declaration */ tGATT_CHAR_DECL char_decl; /* characteristic declaration */ tGATT_INCL_SRVC incl_handle; /* included service */ - + tGATT_ATTR_VAL attr_val; } tGATT_ATTR_VALUE; /* Attribute UUID type @@ -167,50 +170,49 @@ typedef UINT8 tGATT_ATTR_UUID_TYPE; /* 16 bits UUID Attribute in server database */ typedef struct { - void *p_next; /* pointer to the next attribute, - either tGATT_ATTR16 or tGATT_ATTR128 */ - tGATT_ATTR_VALUE *p_value; - tGATT_ATTR_UUID_TYPE uuid_type; - tGATT_PERM permission; - UINT16 handle; - UINT16 uuid; + void *p_next; /* pointer to the next attribute, either tGATT_ATTR16 or tGATT_ATTR128 */ + tGATT_ATTR_VALUE *p_value; + tGATT_ATTR_UUID_TYPE uuid_type; + tGATT_PERM permission; + tGATTS_ATTR_CONTROL control; + UINT16 handle; + UINT16 uuid; } tGATT_ATTR16; /* 32 bits UUID Attribute in server database */ typedef struct { - void *p_next; /* pointer to the next attribute, - either tGATT_ATTR16, tGATT_ATTR32 or tGATT_ATTR128 */ - tGATT_ATTR_VALUE *p_value; - tGATT_ATTR_UUID_TYPE uuid_type; - tGATT_PERM permission; - UINT16 handle; - UINT32 uuid; + void *p_next; /* pointer to the next attribute, either tGATT_ATTR16, tGATT_ATTR32 or tGATT_ATTR128 */ + tGATT_ATTR_VALUE *p_value; + tGATT_ATTR_UUID_TYPE uuid_type; + tGATT_PERM permission; + tGATTS_ATTR_CONTROL control; + UINT16 handle; + UINT32 uuid; } tGATT_ATTR32; /* 128 bits UUID Attribute in server database */ typedef struct { - void *p_next; /* pointer to the next attribute, - either tGATT_ATTR16 or tGATT_ATTR128 */ - tGATT_ATTR_VALUE *p_value; - tGATT_ATTR_UUID_TYPE uuid_type; - tGATT_PERM permission; - UINT16 handle; - UINT8 uuid[LEN_UUID_128]; + void *p_next; /* pointer to the next attribute, either tGATT_ATTR16 or tGATT_ATTR128 */ + tGATT_ATTR_VALUE *p_value; + tGATT_ATTR_UUID_TYPE uuid_type; + tGATT_PERM permission; + tGATTS_ATTR_CONTROL control; + UINT16 handle; + UINT8 uuid[LEN_UUID_128]; } tGATT_ATTR128; /* Service Database definition */ typedef struct { - void *p_attr_list; /* pointer to the first attribute, - either tGATT_ATTR16 or tGATT_ATTR128 */ - UINT8 *p_free_mem; /* Pointer to free memory */ - BUFFER_Q svc_buffer; /* buffer queue used for service database */ - UINT32 mem_free; /* Memory still available */ - UINT16 end_handle; /* Last handle number */ - UINT16 next_handle; /* Next usable handle value */ + void *p_attr_list; /* pointer to the first attribute, either tGATT_ATTR16 or tGATT_ATTR128 */ + UINT8 *p_free_mem; /* Pointer to free memory */ + BUFFER_Q svc_buffer; /* buffer queue used for service database */ + UINT32 mem_free; /* Memory still available */ + UINT16 end_handle; /* Last handle number */ + UINT16 next_handle; /* Next usable handle value */ } tGATT_SVC_DB; /* Data Structure used for GATT server */ @@ -218,14 +220,14 @@ typedef struct { /* A service registration information record consists of beginning and ending */ /* attribute handle, service UUID and a set of GATT server callback. */ typedef struct { - tGATT_SVC_DB *p_db; /* pointer to the service database */ + tGATT_SVC_DB *p_db; /* pointer to the service database */ tBT_UUID app_uuid; /* applicatino UUID */ - UINT32 sdp_handle; /* primamry service SDP handle */ + UINT32 sdp_handle; /* primamry service SDP handle */ UINT16 service_instance; /* service instance number */ - UINT16 type; /* service type UUID, primary or secondary */ - UINT16 s_hdl; /* service starting handle */ - UINT16 e_hdl; /* service ending handle */ - tGATT_IF gatt_if; /* this service is belong to which application */ + UINT16 type; /* service type UUID, primary or secondary */ + UINT16 s_hdl; /* service starting handle */ + UINT16 e_hdl; /* service ending handle */ + tGATT_IF gatt_if; /* this service is belong to which application */ BOOLEAN in_use; } tGATT_SR_REG; @@ -340,7 +342,7 @@ typedef struct { tGATT_CH_STATE ch_state; UINT8 ch_flags; - tGATT_IF app_hold_link[GATT_MAX_APPS]; + tGATT_IF app_hold_link[GATT_MAX_APPS]; /* server needs */ /* server response data */ @@ -350,13 +352,13 @@ typedef struct { TIMER_LIST_ENT conf_timer_ent; /* peer confirm to indication timer */ - UINT8 prep_cnt[GATT_MAX_APPS]; - UINT8 ind_count; + UINT8 prep_cnt[GATT_MAX_APPS]; + UINT8 ind_count; - tGATT_CMD_Q cl_cmd_q[GATT_CL_MAX_LCB]; - TIMER_LIST_ENT ind_ack_timer_ent; /* local app confirm to indication timer */ - UINT8 pending_cl_req; - UINT8 next_slot_inq; /* index of next available slot in queue */ + tGATT_CMD_Q cl_cmd_q[GATT_CL_MAX_LCB]; + TIMER_LIST_ENT ind_ack_timer_ent; /* local app confirm to indication timer */ + UINT8 pending_cl_req; + UINT8 next_slot_inq; /* index of next available slot in queue */ BOOLEAN in_use; UINT8 tcb_idx; @@ -579,6 +581,7 @@ extern BOOLEAN gatt_cl_send_next_cmd_inq(tGATT_TCB *p_tcb); /* reserved handle list */ extern tGATT_HDL_LIST_ELEM *gatt_find_hdl_buffer_by_app_id (tBT_UUID *p_app_uuid128, tBT_UUID *p_svc_uuid, UINT16 svc_inst); extern tGATT_HDL_LIST_ELEM *gatt_find_hdl_buffer_by_handle(UINT16 handle); +extern tGATT_HDL_LIST_ELEM *gatt_find_hdl_buffer_by_attr_handle(UINT16 attr_handle); extern tGATT_HDL_LIST_ELEM *gatt_alloc_hdl_buffer(void); extern void gatt_free_hdl_buffer(tGATT_HDL_LIST_ELEM *p); extern BOOLEAN gatt_is_last_attribute(tGATT_SRV_LIST_INFO *p_list, tGATT_SRV_LIST_ELEM *p_start, tBT_UUID value); @@ -664,12 +667,27 @@ extern void gatt_set_sec_act(tGATT_TCB *p_tcb, tGATT_SEC_ACTION sec_act); /* gatt_db.c */ extern BOOLEAN gatts_init_service_db (tGATT_SVC_DB *p_db, tBT_UUID *p_service, BOOLEAN is_pri, UINT16 s_hdl, UINT16 num_handle); extern UINT16 gatts_add_included_service (tGATT_SVC_DB *p_db, UINT16 s_handle, UINT16 e_handle, tBT_UUID service); -extern UINT16 gatts_add_characteristic (tGATT_SVC_DB *p_db, tGATT_PERM perm, tGATT_CHAR_PROP property, tBT_UUID *p_char_uuid); -extern UINT16 gatts_add_char_descr (tGATT_SVC_DB *p_db, tGATT_PERM perm, tBT_UUID *p_dscp_uuid); +extern UINT16 gatts_add_characteristic (tGATT_SVC_DB *p_db, tGATT_PERM perm, + tGATT_CHAR_PROP property, + tBT_UUID *p_char_uuid, tGATT_ATTR_VAL *attr_val, + tGATTS_ATTR_CONTROL *control); +extern UINT16 gatts_add_char_descr (tGATT_SVC_DB *p_db, tGATT_PERM perm, + tBT_UUID *p_dscp_uuid, tGATT_ATTR_VAL *attr_val, + tGATTS_ATTR_CONTROL *control); + +extern tGATT_STATUS gatts_set_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, + UINT16 length, UINT8 *value); + +extern tGATT_STATUS gatts_get_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, + UINT16 *length, UINT8 **value); +extern BOOLEAN gatts_is_auto_response(UINT16 attr_handle); extern tGATT_STATUS gatts_db_read_attr_value_by_type (tGATT_TCB *p_tcb, tGATT_SVC_DB *p_db, UINT8 op_code, BT_HDR *p_rsp, UINT16 s_handle, UINT16 e_handle, tBT_UUID type, UINT16 *p_len, tGATT_SEC_FLAG sec_flag, UINT8 key_size, UINT32 trans_id, UINT16 *p_cur_handle); extern tGATT_STATUS gatts_read_attr_value_by_handle(tGATT_TCB *p_tcb, tGATT_SVC_DB *p_db, UINT8 op_code, UINT16 handle, UINT16 offset, UINT8 *p_value, UINT16 *p_len, UINT16 mtu, tGATT_SEC_FLAG sec_flag, UINT8 key_size, UINT32 trans_id); +extern tGATT_STATUS gatts_write_attr_value_by_handle(tGATT_SVC_DB *p_db, + UINT16 handle, UINT16 offset, + UINT8 *p_value, UINT16 len); extern tGATT_STATUS gatts_write_attr_perm_check (tGATT_SVC_DB *p_db, UINT8 op_code, UINT16 handle, UINT16 offset, UINT8 *p_data, UINT16 len, tGATT_SEC_FLAG sec_flag, UINT8 key_size); extern tGATT_STATUS gatts_read_attr_perm_check(tGATT_SVC_DB *p_db, BOOLEAN is_long, UINT16 handle, tGATT_SEC_FLAG sec_flag, UINT8 key_size); diff --git a/components/bt/bluedroid/stack/include/gatt_api.h b/components/bt/bluedroid/stack/include/gatt_api.h index a365ca9dee..c460a65538 100644 --- a/components/bt/bluedroid/stack/include/gatt_api.h +++ b/components/bt/bluedroid/stack/include/gatt_api.h @@ -63,6 +63,8 @@ #define GATT_ENCRYPED_NO_MITM 0x8d #define GATT_NOT_ENCRYPTED 0x8e #define GATT_CONGESTED 0x8f +#define GATT_STACK_RSP 0x90 +#define GATT_APP_RSP 0x91 /* 0xE0 ~ 0xFC reserved for future use */ #define GATT_CCC_CFG_ERR 0xFD /* Client Characteristic Configuration Descriptor Improperly Configured */ @@ -117,36 +119,36 @@ typedef UINT16 tGATT_DISCONN_REASON; /* MAX GATT MTU size */ #ifndef GATT_MAX_MTU_SIZE -#define GATT_MAX_MTU_SIZE 517 +#define GATT_MAX_MTU_SIZE 517 #endif /* max legth of an attribute value */ #ifndef GATT_MAX_ATTR_LEN -#define GATT_MAX_ATTR_LEN 600 +#define GATT_MAX_ATTR_LEN 600 #endif /* default GATT MTU size over LE link */ -#define GATT_DEF_BLE_MTU_SIZE 23 +#define GATT_DEF_BLE_MTU_SIZE 23 /* invalid connection ID */ -#define GATT_INVALID_CONN_ID 0xFFFF +#define GATT_INVALID_CONN_ID 0xFFFF #ifndef GATT_CL_MAX_LCB -#define GATT_CL_MAX_LCB 12 // 22 +#define GATT_CL_MAX_LCB 12 // 22 #endif #ifndef GATT_MAX_SCCB -#define GATT_MAX_SCCB 10 +#define GATT_MAX_SCCB 10 #endif /* GATT notification caching timer, default to be three seconds */ #ifndef GATTC_NOTIF_TIMEOUT -#define GATTC_NOTIF_TIMEOUT 3 +#define GATTC_NOTIF_TIMEOUT 3 #endif /***************************************************************************** @@ -155,22 +157,22 @@ typedef UINT16 tGATT_DISCONN_REASON; /* Attribute permissions */ -#define GATT_PERM_READ (1 << 0) /* bit 0 */ -#define GATT_PERM_READ_ENCRYPTED (1 << 1) /* bit 1 */ -#define GATT_PERM_READ_ENC_MITM (1 << 2) /* bit 2 */ -#define GATT_PERM_WRITE (1 << 4) /* bit 4 */ -#define GATT_PERM_WRITE_ENCRYPTED (1 << 5) /* bit 5 */ -#define GATT_PERM_WRITE_ENC_MITM (1 << 6) /* bit 6 */ -#define GATT_PERM_WRITE_SIGNED (1 << 7) /* bit 7 */ -#define GATT_PERM_WRITE_SIGNED_MITM (1 << 8) /* bit 8 */ +#define GATT_PERM_READ (1 << 0) /* bit 0 */ +#define GATT_PERM_READ_ENCRYPTED (1 << 1) /* bit 1 */ +#define GATT_PERM_READ_ENC_MITM (1 << 2) /* bit 2 */ +#define GATT_PERM_WRITE (1 << 4) /* bit 4 */ +#define GATT_PERM_WRITE_ENCRYPTED (1 << 5) /* bit 5 */ +#define GATT_PERM_WRITE_ENC_MITM (1 << 6) /* bit 6 */ +#define GATT_PERM_WRITE_SIGNED (1 << 7) /* bit 7 */ +#define GATT_PERM_WRITE_SIGNED_MITM (1 << 8) /* bit 8 */ typedef UINT16 tGATT_PERM; #define GATT_ENCRYPT_KEY_SIZE_MASK (0xF000) /* the MS nibble of tGATT_PERM; key size 7=0; size 16=9 */ -#define GATT_READ_ALLOWED (GATT_PERM_READ | GATT_PERM_READ_ENCRYPTED | GATT_PERM_READ_ENC_MITM) -#define GATT_READ_AUTH_REQUIRED (GATT_PERM_READ_ENCRYPTED) -#define GATT_READ_MITM_REQUIRED (GATT_PERM_READ_ENC_MITM) -#define GATT_READ_ENCRYPTED_REQUIRED (GATT_PERM_READ_ENCRYPTED | GATT_PERM_READ_ENC_MITM) +#define GATT_READ_ALLOWED (GATT_PERM_READ | GATT_PERM_READ_ENCRYPTED | GATT_PERM_READ_ENC_MITM) +#define GATT_READ_AUTH_REQUIRED (GATT_PERM_READ_ENCRYPTED) +#define GATT_READ_MITM_REQUIRED (GATT_PERM_READ_ENC_MITM) +#define GATT_READ_ENCRYPTED_REQUIRED (GATT_PERM_READ_ENCRYPTED | GATT_PERM_READ_ENC_MITM) #define GATT_WRITE_ALLOWED (GATT_PERM_WRITE | GATT_PERM_WRITE_ENCRYPTED | GATT_PERM_WRITE_ENC_MITM | \ @@ -312,6 +314,16 @@ typedef struct { UINT8 value[GATT_MAX_ATTR_LEN]; /* the actual attribute value */ } tGATT_VALUE; +typedef struct{ + UINT16 attr_max_len; + UINT16 attr_len; + UINT8 *attr_val; +}tGATT_ATTR_VAL; + +typedef struct{ + uint8_t auto_rsp; +}tGATTS_ATTR_CONTROL; + /* Union of the event data which is used in the server respond API to carry the server response information */ typedef union { @@ -740,8 +752,9 @@ extern UINT16 GATTS_AddIncludeService (UINT16 service_handle, ** characteristic failed. ** *******************************************************************************/ -extern UINT16 GATTS_AddCharacteristic (UINT16 service_handle, tBT_UUID *char_uuid, - tGATT_PERM perm, tGATT_CHAR_PROP property); +extern UINT16 GATTS_AddCharacteristic (UINT16 service_handle, tBT_UUID *p_char_uuid, + tGATT_PERM perm, tGATT_CHAR_PROP property, + tGATT_ATTR_VAL *attr_val, tGATTS_ATTR_CONTROL *control); /******************************************************************************* ** @@ -763,7 +776,8 @@ extern UINT16 GATTS_AddCharacteristic (UINT16 service_handle, tBT_UUID *char_uui ** *******************************************************************************/ extern UINT16 GATTS_AddCharDescriptor (UINT16 service_handle, tGATT_PERM perm, - tBT_UUID *p_descr_uuid); + tBT_UUID *p_descr_uuid, tGATT_ATTR_VAL *attr_val, + tGATTS_ATTR_CONTROL *control); /******************************************************************************* ** @@ -866,6 +880,39 @@ extern tGATT_STATUS GATTS_SendRsp (UINT16 conn_id, UINT32 trans_id, tGATT_STATUS status, tGATTS_RSP *p_msg); +/******************************************************************************* +** +** Function GATTS_SetAttributeValue +** +** Description This function sends to set the attribute value . +** +** Parameter attr_handle:the attribute handle +** length: the attribute length +** value: the value to be set to the attribute in the database +** +** Returns GATT_SUCCESS if sucessfully sent; otherwise error code. +** +*******************************************************************************/ +tGATT_STATUS GATTS_SetAttributeValue(UINT16 attr_handle, UINT16 length, UINT8 *value); + + +/******************************************************************************* +** +** Function GATTS_GetAttributeValue +** +** Description This function sends to set the attribute value . +** +** Parameter attr_handle: the attribute handle +** length:the attribute value length in the database +** value: the attribute value out put +** +** Returns GATT_SUCCESS if sucessfully sent; otherwise error code. +** +*******************************************************************************/ +tGATT_STATUS GATTS_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value); + + + /*******************************************************************************/ /* GATT Profile Client Functions */ /*******************************************************************************/ diff --git a/components/bt/bluedroid/stack/smp/smp_main.c b/components/bt/bluedroid/stack/smp/smp_main.c index 85b232aea8..cad6427464 100644 --- a/components/bt/bluedroid/stack/smp/smp_main.c +++ b/components/bt/bluedroid/stack/smp/smp_main.c @@ -419,7 +419,7 @@ static const UINT8 smp_master_create_local_sec_conn_oob_data[][SMP_SM_NUM_COLS] static const UINT8 smp_slave_entry_map[][SMP_STATE_MAX] = { /* state name: Idle WaitApp SecReq Pair Wait Confirm Rand PublKey SCPhs1 Wait Wait SCPhs2 Wait DHKChk Enc Bond CrLocSc Rsp Pend ReqRsp Cfm Exch Strt Cmtm Nonce Strt DHKChk Pend Pend OobData */ - /* PAIR_REQ */{ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* PAIR_REQ */{ 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* PAIR_RSP */{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* CONFIRM */{ 0, 4, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* RAND */{ 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, diff --git a/docs/api/esp_blufi.rst b/docs/api/esp_blufi.rst index 9dd8085166..13432dc154 100644 --- a/docs/api/esp_blufi.rst +++ b/docs/api/esp_blufi.rst @@ -6,8 +6,7 @@ Overview BLUFI is a profile based GATT to config ESP32 WIFI to connect/disconnect AP or setup a softap and etc. Use should concern these things: 1. The event sent from profile. Then you need to do something as the event indicate. -2. Security reference. You can write your own Security functions such as symmetrical encryption/decryption and checksum functions. -Even you can define the "Key Exchange/Negotiation" procedure. +2. Security reference. You can write your own Security functions such as symmetrical encryption/decryption and checksum functions. Even you can define the "Key Exchange/Negotiation" procedure. Application Example ------------------- diff --git a/docs/api/esp_gatt_defs.rst b/docs/api/esp_gatt_defs.rst index 637acaab99..9eeac6193c 100644 --- a/docs/api/esp_gatt_defs.rst +++ b/docs/api/esp_gatt_defs.rst @@ -84,6 +84,8 @@ Macros .. doxygendefine:: ESP_GATT_UUID_SCAN_INT_WINDOW .. doxygendefine:: ESP_GATT_UUID_SCAN_REFRESH .. doxygendefine:: ESP_GATT_ILLEGAL_UUID +.. doxygendefine:: ESP_GATT_ILLEGAL_HANDLE +.. doxygendefine:: ESP_GATT_ATTR_HANDLE_MAX .. doxygendefine:: ESP_GATT_MAX_ATTR_LEN .. doxygendefine:: ESP_GATT_IF_NONE @@ -106,6 +108,27 @@ Enumerations Structures ^^^^^^^^^^ +.. doxygenstruct:: esp_attr_desc_t + :members: + +.. doxygenstruct:: esp_attr_control_t + :members: + +.. doxygenstruct:: esp_gatts_attr_db_t + :members: + +.. doxygenstruct:: esp_attr_value_t + :members: + +.. doxygenstruct:: esp_gatts_incl_svc_desc_t + :members: + +.. doxygenstruct:: esp_gatts_incl128_svc_desc_t + :members: + +.. doxygenstruct:: esp_gatts_char_desc_t + :members: + .. doxygenstruct:: esp_gatt_value_t :members: diff --git a/docs/api/esp_gatts.rst b/docs/api/esp_gatts.rst index fbaa1c236d..9e3749df0a 100644 --- a/docs/api/esp_gatts.rst +++ b/docs/api/esp_gatts.rst @@ -80,6 +80,9 @@ Structures .. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_add_char_descr_evt_param :members: +.. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_add_attr_tab_evt_param + :members: + .. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_delete_evt_param :members: @@ -101,6 +104,9 @@ Structures .. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_rsp_evt_param :members: +.. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_set_attr_val_evt_param + :members: + Functions ^^^^^^^^^ @@ -109,6 +115,7 @@ Functions .. doxygenfunction:: esp_ble_gatts_app_register .. doxygenfunction:: esp_ble_gatts_app_unregister .. doxygenfunction:: esp_ble_gatts_create_service +.. doxygenfunction:: esp_ble_gatts_create_attr_tab .. doxygenfunction:: esp_ble_gatts_add_included_service .. doxygenfunction:: esp_ble_gatts_add_char .. doxygenfunction:: esp_ble_gatts_add_char_descr @@ -117,6 +124,8 @@ Functions .. doxygenfunction:: esp_ble_gatts_stop_service .. doxygenfunction:: esp_ble_gatts_send_indicate .. doxygenfunction:: esp_ble_gatts_send_response +.. doxygenfunction:: esp_ble_gatts_set_attr_value +.. doxygenfunction:: esp_ble_gatts_get_attr_value .. doxygenfunction:: esp_ble_gatts_open .. doxygenfunction:: esp_ble_gatts_close diff --git a/examples/14_gatt_server/main/gatts_demo.c b/examples/14_gatt_server/main/gatts_demo.c index 9b3b6cc802..98feaa2240 100644 --- a/examples/14_gatt_server/main/gatts_demo.c +++ b/examples/14_gatt_server/main/gatts_demo.c @@ -48,6 +48,19 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i #define TEST_DEVICE_NAME "ESP_GATTS_DEMO" #define TEST_MANUFACTURER_DATA_LEN 17 + +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 + +uint8_t char1_str[] ={0x11,0x22,0x33}; + +esp_attr_value_t gatts_demo_char1_val = +{ + .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX, + .attr_len = sizeof(char1_str), + .attr_value = char1_str, +}; + + static uint8_t test_service_uuid128[32] = { /* LSB <--------------------------------------------------------------------------------> MSB */ //first uuid, 16bit, [12],[13] is the value @@ -175,20 +188,30 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, - ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY, + &gatts_demo_char1_val, NULL); break; case ESP_GATTS_ADD_INCL_SRVC_EVT: break; - case ESP_GATTS_ADD_CHAR_EVT: - ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", - param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + case ESP_GATTS_ADD_CHAR_EVT: { + uint16_t length = 0; + const uint8_t *prf_char; + ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_ble_gatts_get_attr_value(param->add_char.attr_handle, &length, &prf_char); + + ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x\n", length); + for(int i = 0; i < length; i++){ + ESP_LOGI(GATTS_TAG, "prf_char[%x] =%x\n",i,prf_char[i]); + } esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid, - ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL); break; + } case ESP_GATTS_ADD_CHAR_DESCR_EVT: ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); @@ -269,7 +292,8 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, - ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY, + NULL, NULL); break; case ESP_GATTS_ADD_INCL_SRVC_EVT: break; @@ -281,7 +305,8 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid, - ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE); + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + NULL, NULL); break; case ESP_GATTS_ADD_CHAR_DESCR_EVT: ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", diff --git a/examples/30_gatt_server_table_create/Makefile b/examples/30_gatt_server_table_create/Makefile new file mode 100644 index 0000000000..a6e41ff33a --- /dev/null +++ b/examples/30_gatt_server_table_create/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := gatt_server_table_creat_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/30_gatt_server_table_create/README.rst b/examples/30_gatt_server_table_create/README.rst new file mode 100644 index 0000000000..7a763998d7 --- /dev/null +++ b/examples/30_gatt_server_table_create/README.rst @@ -0,0 +1,10 @@ +ESP-IDF GATT SERVER create attribute 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. + diff --git a/examples/30_gatt_server_table_create/main/component.mk b/examples/30_gatt_server_table_create/main/component.mk new file mode 100644 index 0000000000..79edf031d3 --- /dev/null +++ b/examples/30_gatt_server_table_create/main/component.mk @@ -0,0 +1,8 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c b/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c new file mode 100644 index 0000000000..f4511f68a5 --- /dev/null +++ b/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c @@ -0,0 +1,340 @@ +// 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "bt.h" +#include "bta_api.h" + +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "esp_bt_main.h" +#include "gatts_table_creat_demo.h" + + +#define HEART_PROFILE_NUM 1 +#define HEART_PROFILE_APP_IDX 0 +#define ESP_HEART_RATE_APP_ID 0x55 +#define SAMPLE_DEVICE_NAME "ESP_HEART_RATE" +#define SAMPLE_MANUFACTURER_DATA_LEN 17 +#define HEART_RATE_SVC_INST_ID 0 + +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 + +uint8_t char1_str[] ={0x11,0x22,0x33}; + +uint16_t heart_rate_handle_table[HRS_IDX_NB]; + +esp_attr_value_t gatts_demo_char1_val = +{ + .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX, + .attr_len = sizeof(char1_str), + .attr_value = char1_str, +}; + + +static uint8_t heart_rate_service_uuid[16] = { + /* LSB <--------------------------------------------------------------------------------> MSB */ + //first uuid, 16bit, [12],[13] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x18, 0x0D, 0x00, 0x00, +}; + + +static esp_ble_adv_data_t heart_rate_adv_config = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x20, + .max_interval = 0x40, + .appearance = 0x00, + .manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN, + .p_manufacturer_data = NULL, //&test_manufacturer[0], + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = 32, + .p_service_uuid = heart_rate_service_uuid, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; + +static esp_ble_adv_params_t heart_rate_adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + //.peer_addr = + //.peer_addr_type = + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +struct gatts_profile_inst { + esp_gatts_cb_t gatts_cb; + uint16_t gatts_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_handle; + esp_gatt_srvc_id_t service_id; + uint16_t char_handle; + esp_bt_uuid_t char_uuid; + esp_gatt_perm_t perm; + esp_gatt_char_prop_t property; + uint16_t descr_handle; + esp_bt_uuid_t descr_uuid; +}; + +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); + +/* 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] = { + .gatts_cb = gatts_profile_event_handler, + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + +}; + +/* + * HTPT PROFILE ATTRIBUTES + **************************************************************************************** + */ + +/// Full HRS Database Description - Used to add attributes into the database +const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] = +{ + // Heart Rate Service Declaration + [HRS_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, { + {ESP_UUID_LEN_16, {ESP_GATT_UUID_PRI_SERVICE}}, + ESP_GATT_PERM_READ, + sizeof(heart_rate_svc), + sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}}, + + // Heart Rate Measurement Characteristic Declaration + [HRS_IDX_HR_MEAS_CHAR] = {{ESP_GATT_AUTO_RSP}, { + {ESP_UUID_LEN_16, {ESP_GATT_UUID_CHAR_DECLARE}}, + ESP_GATT_PERM_READ, + sizeof(heart_rate_meas_char), + sizeof(heart_rate_meas_char), (uint8_t *)&heart_rate_meas_char}}, + // Heart Rate Measurement Characteristic Value + [HRS_IDX_HR_MEAS_VAL] = {{ESP_GATT_AUTO_RSP}, { + {ESP_UUID_LEN_16, {ESP_GATT_HEART_RATE_MEAS}}, + ESP_GATT_PERM_READ, + HRPS_HT_MEAS_MAX_LEN, + 0, NULL}}, + + // Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor + [HRS_IDX_HR_MEAS_NTF_CFG] = {{ESP_GATT_AUTO_RSP}, { + {ESP_UUID_LEN_16, {ESP_GATT_UUID_CHAR_SRVR_CONFIG}}, + ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, + sizeof(uint16_t), + 0, NULL}}, + + // Body Sensor Location Characteristic Declaration + [HRS_IDX_BOBY_SENSOR_LOC_CHAR] = {{ESP_GATT_AUTO_RSP}, { + {ESP_UUID_LEN_16, {ESP_GATT_UUID_CHAR_DECLARE}}, + ESP_GATT_PERM_READ, + sizeof(heart_rate_body_sensor_loc_char), + sizeof(heart_rate_body_sensor_loc_char), (uint8_t *)&heart_rate_body_sensor_loc_char}}, + + // Body Sensor Location Characteristic Value + [HRS_IDX_BOBY_SENSOR_LOC_VAL] = {{ESP_GATT_AUTO_RSP}, { + {ESP_UUID_LEN_16, {ESP_GATT_BODY_SENSOR_LOCATION}}, + ESP_GATT_PERM_READ, + sizeof(uint8_t), + 0, NULL}}, + + // Heart Rate Control Point Characteristic Declaration + [HRS_IDX_HR_CTNL_PT_CHAR] = {{ESP_GATT_AUTO_RSP}, { + {ESP_UUID_LEN_16, {ESP_GATT_UUID_CHAR_DECLARE}}, + ESP_GATT_PERM_READ, + sizeof(heart_rate_cntl_point_char), + sizeof(heart_rate_cntl_point_char), (uint8_t *)&heart_rate_cntl_point_char}}, + + // Heart Rate Control Point Characteristic Value + [HRS_IDX_HR_CTNL_PT_VAL] = {{ESP_GATT_AUTO_RSP}, { + {ESP_UUID_LEN_16, {ESP_GATT_HEART_RATE_CNTL_POINT}}, + ESP_GATT_PERM_WRITE, + sizeof(uint8_t), + 0, NULL}}, +}; + +/* + * Heart Rate PROFILE ATTRIBUTES + **************************************************************************************** + */ + +/// Heart Rate Sensor Service +const uint16_t heart_rate_svc = ESP_GATT_UUID_HEART_RATE_SVC; + +/// Heart Rate Sensor Service - Heart Rate Measurement Characteristic +const esp_gatts_char_desc_t heart_rate_meas_char = +{ + .prop = ESP_GATT_CHAR_PROP_BIT_NOTIFY, + .attr_hdl = 0, + .attr_uuid = {ESP_UUID_LEN_16, {ESP_GATT_HEART_RATE_MEAS}}, +}; + +/// Heart Rate Sensor Service -Body Sensor Location characteristic +const esp_gatts_char_desc_t heart_rate_body_sensor_loc_char = +{ + .prop = ESP_GATT_CHAR_PROP_BIT_READ, + .attr_hdl = 0, + .attr_uuid = {ESP_UUID_LEN_16, {ESP_GATT_BODY_SENSOR_LOCATION}}, +}; + +/// Heart Rate Sensor Service - Heart Rate Control Point characteristic +const esp_gatts_char_desc_t heart_rate_cntl_point_char = +{ + .prop = ESP_GATT_CHAR_PROP_BIT_WRITE, + .attr_hdl = 0, + .attr_uuid = {ESP_UUID_LEN_16, {ESP_GATT_HEART_RATE_CNTL_POINT}}, +}; + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + LOG_ERROR("GAP_EVT, event %d\n", event); + + switch (event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + esp_ble_gap_start_advertising(&heart_rate_adv_params); + break; + default: + break; + } +} + +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) +{ + LOG_ERROR("event = %x\n",event); + switch (event) { + case ESP_GATTS_REG_EVT: + LOG_INFO("%s %d\n", __func__, __LINE__); + esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME); + LOG_INFO("%s %d\n", __func__, __LINE__); + esp_ble_gap_config_adv_data(&heart_rate_adv_config); + + LOG_INFO("%s %d\n", __func__, __LINE__); + esp_ble_gatts_create_attr_tab(heart_rate_gatt_db, gatts_if, + HRS_IDX_NB, HEART_RATE_SVC_INST_ID); + break; + case ESP_GATTS_READ_EVT: + + break; + case ESP_GATTS_WRITE_EVT: + break; + case ESP_GATTS_EXEC_WRITE_EVT: + break; + case ESP_GATTS_MTU_EVT: + break; + case ESP_GATTS_CONF_EVT: + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: + break; + case ESP_GATTS_DISCONNECT_EVT: + break; + case ESP_GATTS_OPEN_EVT: + break; + case ESP_GATTS_CANCEL_OPEN_EVT: + break; + case ESP_GATTS_CLOSE_EVT: + break; + case ESP_GATTS_LISTEN_EVT: + break; + case ESP_GATTS_CONGEST_EVT: + break; + case ESP_GATTS_CREAT_ATTR_TAB_EVT:{ + LOG_ERROR("The number handle =%x\n",param->add_attr_tab.num_handle); + if(param->add_attr_tab.num_handle == HRS_IDX_NB){ + memcpy(heart_rate_handle_table, param->add_attr_tab.handles, + sizeof(heart_rate_handle_table)); + esp_ble_gatts_start_service(heart_rate_handle_table[HRS_IDX_SVC]); + } + + break; + } + + default: + break; + } +} + + +static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) +{ + LOG_INFO("EVT %d, gatts if %d\n", event, gatts_if); + + /* If event is register event, store the gatts_if for each profile */ + if (event == ESP_GATTS_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + heart_rate_profile_tab[HEART_PROFILE_APP_IDX].gatts_if = gatts_if; + } else { + LOG_INFO("Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + do { + 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) { + if (heart_rate_profile_tab[idx].gatts_cb) { + heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param); + } + } + } + } while (0); +} + +void app_main() +{ + esp_err_t ret; + + esp_bt_controller_init(); + LOG_INFO("%s init bluetooth\n", __func__); + ret = esp_bluedroid_init(); + if (ret) { + LOG_ERROR("%s init bluetooth failed\n", __func__); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + LOG_ERROR("%s enable bluetooth failed\n", __func__); + return; + } + + esp_ble_gatts_register_callback(gatts_event_handler); + esp_ble_gap_register_callback(gap_event_handler); + esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID); + return; +} diff --git a/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h b/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h new file mode 100644 index 0000000000..ecce340df7 --- /dev/null +++ b/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h @@ -0,0 +1,57 @@ +// 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 +#include +#include + +/* + * DEFINES + **************************************************************************************** + */ + +#define HRPS_HT_MEAS_MAX_LEN (13) + +#define HRPS_MANDATORY_MASK (0x0F) +#define HRPS_BODY_SENSOR_LOC_MASK (0x30) +#define HRPS_HR_CTNL_PT_MASK (0xC0) + + +///Attributes State Machine +enum +{ + HRS_IDX_SVC, + + HRS_IDX_HR_MEAS_CHAR, + HRS_IDX_HR_MEAS_VAL, + HRS_IDX_HR_MEAS_NTF_CFG, + + HRS_IDX_BOBY_SENSOR_LOC_CHAR, + HRS_IDX_BOBY_SENSOR_LOC_VAL, + + HRS_IDX_HR_CTNL_PT_CHAR, + HRS_IDX_HR_CTNL_PT_VAL, + + HRS_IDX_NB, +}; + + +extern const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB]; +/// Heart Rate Sensor Service - only one instance for now +extern const uint16_t heart_rate_svc; + +extern const esp_gatts_char_desc_t heart_rate_meas_char; +extern const esp_gatts_char_desc_t heart_rate_body_sensor_loc_char; +extern const esp_gatts_char_desc_t heart_rate_cntl_point_char; diff --git a/examples/30_gatt_server_table_create/sdkconfig.defaults b/examples/30_gatt_server_table_create/sdkconfig.defaults new file mode 100644 index 0000000000..e435f383c8 --- /dev/null +++ b/examples/30_gatt_server_table_create/sdkconfig.defaults @@ -0,0 +1,14 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y + +# +# ESP32-specific config +# +CONFIG_ESP32_ENABLE_STACK_BT=y +# CONFIG_ESP32_ENABLE_STACK_NONE is not set +CONFIG_MEMMAP_BT=y From 1eed54c7cd0d69378c24640739460b203d5297b3 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Wed, 11 Jan 2017 17:19:25 +0800 Subject: [PATCH 133/167] component/bt : modify bluedroid task to core0 --- components/bt/bluedroid/btc/core/btc_task.c | 2 +- components/bt/bluedroid/hci/hci_hal_h4.c | 2 +- components/bt/bluedroid/hci/hci_layer.c | 2 +- components/bt/bluedroid/stack/btu/btu_init.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/bt/bluedroid/btc/core/btc_task.c b/components/bt/bluedroid/btc/core/btc_task.c index 773d7889bb..a245f54d28 100644 --- a/components/bt/bluedroid/btc/core/btc_task.c +++ b/components/bt/bluedroid/btc/core/btc_task.c @@ -120,7 +120,7 @@ bt_status_t btc_transfer_context(btc_msg_t *msg, void *arg, int arg_len, btc_arg int btc_init(void) { xBtcQueue = xQueueCreate(BTC_TASK_QUEUE_NUM, sizeof(btc_msg_t)); - xTaskCreate(btc_task, "Btc_task", BTC_TASK_STACK_SIZE, NULL, BTC_TASK_PRIO, &xBtcTaskHandle); + xTaskCreatePinnedToCore(btc_task, "Btc_task", BTC_TASK_STACK_SIZE, NULL, BTC_TASK_PRIO, &xBtcTaskHandle, 0); /* TODO: initial the profile_tab */ diff --git a/components/bt/bluedroid/hci/hci_hal_h4.c b/components/bt/bluedroid/hci/hci_hal_h4.c index eb78cab5d3..e621450423 100644 --- a/components/bt/bluedroid/hci/hci_hal_h4.c +++ b/components/bt/bluedroid/hci/hci_hal_h4.c @@ -102,7 +102,7 @@ static bool hal_open(const hci_hal_callbacks_t *upper_callbacks) hci_hal_env_init(HCI_HAL_SERIAL_BUFFER_SIZE, SIZE_MAX); xHciH4Queue = xQueueCreate(HCI_H4_QUEUE_NUM, sizeof(BtTaskEvt_t)); - xTaskCreate(hci_hal_h4_rx_handler, HCI_H4_TASK_NAME, HCI_H4_TASK_STACK_SIZE, NULL, HCI_H4_TASK_PRIO, &xHciH4TaskHandle); + xTaskCreatePinnedToCore(hci_hal_h4_rx_handler, HCI_H4_TASK_NAME, HCI_H4_TASK_STACK_SIZE, NULL, HCI_H4_TASK_PRIO, &xHciH4TaskHandle, 0); //register vhci host cb esp_vhci_host_register_callback(&vhci_host_cb); diff --git a/components/bt/bluedroid/hci/hci_layer.c b/components/bt/bluedroid/hci/hci_layer.c index 747a582953..14a36eac81 100644 --- a/components/bt/bluedroid/hci/hci_layer.c +++ b/components/bt/bluedroid/hci/hci_layer.c @@ -109,7 +109,7 @@ int hci_start_up(void) } xHciHostQueue = xQueueCreate(HCI_HOST_QUEUE_NUM, sizeof(BtTaskEvt_t)); - xTaskCreate(hci_host_thread_handler, HCI_HOST_TASK_NAME, HCI_HOST_TASK_STACK_SIZE, NULL, HCI_HOST_TASK_PRIO, &xHciHostTaskHandle); + xTaskCreatePinnedToCore(hci_host_thread_handler, HCI_HOST_TASK_NAME, HCI_HOST_TASK_STACK_SIZE, NULL, HCI_HOST_TASK_PRIO, &xHciHostTaskHandle, 0); packet_fragmenter->init(&packet_fragmenter_callbacks); hal->open(&hal_callbacks); diff --git a/components/bt/bluedroid/stack/btu/btu_init.c b/components/bt/bluedroid/stack/btu/btu_init.c index 0f13e318a4..2ed561bcf2 100644 --- a/components/bt/bluedroid/stack/btu/btu_init.c +++ b/components/bt/bluedroid/stack/btu/btu_init.c @@ -201,7 +201,7 @@ void BTU_StartUp(void) } xBtuQueue = xQueueCreate(BTU_QUEUE_NUM, sizeof(BtTaskEvt_t)); - xTaskCreate(btu_task_thread_handler, BTU_TASK_NAME, BTU_TASK_STACK_SIZE, NULL, BTU_TASK_PRIO, &xBtuTaskHandle); + xTaskCreatePinnedToCore(btu_task_thread_handler, BTU_TASK_NAME, BTU_TASK_STACK_SIZE, NULL, BTU_TASK_PRIO, &xBtuTaskHandle, 0); btu_task_post(SIG_BTU_START_UP); /* // Continue startup on bt workqueue thread. From 2d8a8bc141a8f098f36f5a9de095e9185eeaee31 Mon Sep 17 00:00:00 2001 From: XiaXiaotian Date: Wed, 11 Jan 2017 17:21:10 +0800 Subject: [PATCH 134/167] esp32: fixs about auto-reconnect and ap ssid configuration 1. Remove auto-reconnect 2. Use strnlen instead strlen to calculate ssid len in wifi lib --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index f688a5e1b2..0135161682 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit f688a5e1b2f5e4cb8dd2cdbd8dedf63a74b1d063 +Subproject commit 01351616820811bc08e7f7bd24e448df7897eedf From d6842e537c1830b2d38b23a46effa2368ea5ad40 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 11 Jan 2017 21:30:23 +0800 Subject: [PATCH 135/167] mbedtls: give SHA test slightly more time to run --- components/mbedtls/test/test_mbedtls_sha.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/mbedtls/test/test_mbedtls_sha.c b/components/mbedtls/test/test_mbedtls_sha.c index 721c59b761..b18169827b 100644 --- a/components/mbedtls/test/test_mbedtls_sha.c +++ b/components/mbedtls/test/test_mbedtls_sha.c @@ -166,7 +166,7 @@ TEST_CASE("mbedtls SHA self-tests multithreaded", "[mbedtls]") xTaskCreate(tskRunSHASelftests, "SHASelftests2", 8192, NULL, 3, NULL); for(int i = 0; i < 2; i++) { - if(!xSemaphoreTake(done_sem, 10000/portTICK_PERIOD_MS)) { + if(!xSemaphoreTake(done_sem, 12000/portTICK_PERIOD_MS)) { TEST_FAIL_MESSAGE("done_sem not released by test task"); } } From ff4a5a14061f4add94f05e416c286096b2dd3edd Mon Sep 17 00:00:00 2001 From: XiaXiaotian Date: Wed, 11 Jan 2017 18:20:01 +0800 Subject: [PATCH 136/167] wpa2 enterprise: add example for wpa2 enterprise --- components/esp32/lib | 2 +- examples/31_wpa2_enterprise/Makefile | 9 + examples/31_wpa2_enterprise/README.md | 76 +++++++++ .../31_wpa2_enterprise/main/Kconfig.projbuild | 34 ++++ examples/31_wpa2_enterprise/main/component.mk | 12 ++ examples/31_wpa2_enterprise/main/wpa2_ca.pem | 23 +++ .../31_wpa2_enterprise/main/wpa2_client.crt | 70 ++++++++ .../31_wpa2_enterprise/main/wpa2_client.key | 27 +++ .../31_wpa2_enterprise/main/wpa2_client.pem | 57 +++++++ .../main/wpa2_enterprise_main.c | 154 ++++++++++++++++++ .../31_wpa2_enterprise/main/wpa2_server.crt | 70 ++++++++ .../31_wpa2_enterprise/main/wpa2_server.key | 27 +++ .../31_wpa2_enterprise/main/wpa2_server.pem | 57 +++++++ 13 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 examples/31_wpa2_enterprise/Makefile create mode 100644 examples/31_wpa2_enterprise/README.md create mode 100644 examples/31_wpa2_enterprise/main/Kconfig.projbuild create mode 100644 examples/31_wpa2_enterprise/main/component.mk create mode 100644 examples/31_wpa2_enterprise/main/wpa2_ca.pem create mode 100644 examples/31_wpa2_enterprise/main/wpa2_client.crt create mode 100644 examples/31_wpa2_enterprise/main/wpa2_client.key create mode 100644 examples/31_wpa2_enterprise/main/wpa2_client.pem create mode 100644 examples/31_wpa2_enterprise/main/wpa2_enterprise_main.c create mode 100644 examples/31_wpa2_enterprise/main/wpa2_server.crt create mode 100644 examples/31_wpa2_enterprise/main/wpa2_server.key create mode 100644 examples/31_wpa2_enterprise/main/wpa2_server.pem diff --git a/components/esp32/lib b/components/esp32/lib index 0135161682..c01bfe9038 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit 01351616820811bc08e7f7bd24e448df7897eedf +Subproject commit c01bfe9038e59fc0dc15947c1bf4616de006e103 diff --git a/examples/31_wpa2_enterprise/Makefile b/examples/31_wpa2_enterprise/Makefile new file mode 100644 index 0000000000..ff23a93934 --- /dev/null +++ b/examples/31_wpa2_enterprise/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 := wpa2-enterprise + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/31_wpa2_enterprise/README.md b/examples/31_wpa2_enterprise/README.md new file mode 100644 index 0000000000..f84f9e070a --- /dev/null +++ b/examples/31_wpa2_enterprise/README.md @@ -0,0 +1,76 @@ +# WPA2 Enterprise Example + +This example shows how ESP32 connects to AP with wpa2 enterprise encryption. Example does the following steps: + +1. Install CA certificate which is optional. +2. Install client certificate and client key which is required in TLS method and optional in PEAP and TTLS methods. +3. Set identity of phase 1 which is optional. +4. Set user name and password of phase 2 which is required in PEAP and TTLS methods. +5. Enable wpa2 enterprise. +6. Connect to AP. + +*Note:* certificate currently is generated when compiling the example and then stored in flash. + +## The file wpa2_ca.pem, wpa2_ca.key, wpa2_server.pem, wpa2_server.crt and wpa2_server.key can be used to configure AP with + wpa2 enterprise encryption. The steps how to generate new certificates and keys using openssl is as follows: + +1. wpa2_ca.pem wpa2_ca.key: + openssl req -new -x509 -keyout wpa2_ca.key -out wpa2_ca.pem +2. wpa2_server.key: + openssl req -new -key wpa2_server.key -out wpa2_server.csr +3. wpa2_csr: + openssl req -new -key server.key -out server.csr +4. wpa2_server.crt: + openssl ca -batch -keyfile wpa2_ca.key -cert wpa2_ca.pem -in wpa2_server.csr -key ca1234 -out wpa2_server.crt -extensions xpserver_ext -extfile xpextensions +5. wpa2_server.p12: + openssl pkcs12 -export -in wpa2_server.crt -inkey wpa2_server.key -out wpa2_server.p12 -passin pass:sv1234 -passout pass:sv1234 +6. wpa2_server.pem: + openssl pkcs12 -in wpa2_server.p12 -out wpa2_server.pem -passin pass:sv1234 -passout pass:sv1234 +7. wpa2_client.key: + openssl genrsa -out wpa2_client.key 1024 +8. wpa2_client.csr: + openssl req -new -key wpa2_client.key -out wpa2_client.csr +9. wpa2_client.crt: + openssl ca -batch -keyfile wpa2_ca.key -cert wpa2_ca.pem -in wpa2_client.csr -key ca1234 -out wpa2_client.crt -extensions xpclient_ext -extfile xpextensions +10. wpa2_client.p12: + openssl pkcs12 -export -in wpa2_client.crt -inkey wpa2_client.key -out wpa2_client.p12 +11. wpa2_client.pem: + openssl pkcs12 -in wpa2_client.p12 -out wpa2_client.pem + +### Example output + +Here is an example of wpa2 enterprise(PEAP method) console output. + +I (1352) example: Setting WiFi configuration SSID wpa2_test... +I (1362) wpa: WPA2 ENTERPRISE VERSION: [v2.0] enable + +I (1362) wifi: rx_ba=1 tx_ba=1 + +I (1372) wifi: mode : sta (24:0a:c4:03:b8:dc) +I (3002) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:11 +I (3642) wifi: state: init -> auth (b0) +I (3642) wifi: state: auth -> assoc (0) +I (3652) wifi: state: assoc -> run (10) +I (3652) wpa: wpa2_task prio:24, stack:6144 + +I (3972) wpa: >>>>>wpa2 FINISH + +I (3982) wpa: wpa2 task delete + +I (3992) wifi: connected with wpa2_test, channel 11 +I (5372) example: ~~~~~~~~~~~ +I (5372) example: IP:0.0.0.0 +I (5372) example: MASK:0.0.0.0 +I (5372) example: GW:0.0.0.0 +I (5372) example: ~~~~~~~~~~~ +I (6832) event: ip: 192.168.1.112, mask: 255.255.255.0, gw: 192.168.1.1 +I (7372) example: ~~~~~~~~~~~ +I (7372) example: IP:192.168.1.112 +I (7372) example: MASK:255.255.255.0 +I (7372) example: GW:192.168.1.1 +I (7372) example: ~~~~~~~~~~~ +I (9372) example: ~~~~~~~~~~~ +I (9372) example: IP:192.168.1.112 +I (9372) example: MASK:255.255.255.0 +I (9372) example: GW:192.168.1.1 +I (9372) example: ~~~~~~~~~~~ diff --git a/examples/31_wpa2_enterprise/main/Kconfig.projbuild b/examples/31_wpa2_enterprise/main/Kconfig.projbuild new file mode 100644 index 0000000000..06fab0bfa1 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/Kconfig.projbuild @@ -0,0 +1,34 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "wpa2_test" + help + SSID (network name) for the example to connect to. + +config EAP_METHOD + int "EAP METHOD" + default 1 + help + EAP method (TLS, PEAP or TTLS) for the example to use. + TLS: 0, PEAP: 1, TTLS: 2 + +config EAP_ID + string "EAP ID" + default "example@espressif.com" + help + Identity in phase 1 of EAP procedure. + +config EAP_USERNAME + string "EAP USERNAME" + default "espressif" + help + Username for EAP method (PEAP and TTLS). + +config EAP_PASSWORD + string "EAP PASSWORD" + default "test11" + help + Password for EAP method (PEAP and TTLS). + +endmenu \ No newline at end of file diff --git a/examples/31_wpa2_enterprise/main/component.mk b/examples/31_wpa2_enterprise/main/component.mk new file mode 100644 index 0000000000..aab8ff8f38 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/component.mk @@ -0,0 +1,12 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +# embed files from the "certs" directory as binary data symbols +# in the app +COMPONENT_EMBED_TXTFILES := wpa2_ca.pem +COMPONENT_EMBED_TXTFILES += wpa2_client.crt +COMPONENT_EMBED_TXTFILES += wpa2_client.key + + diff --git a/examples/31_wpa2_enterprise/main/wpa2_ca.pem b/examples/31_wpa2_enterprise/main/wpa2_ca.pem new file mode 100644 index 0000000000..c36b97e974 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/wpa2_ca.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID3DCCA0WgAwIBAgIJANe5ZSCKoB8fMA0GCSqGSIb3DQEBCwUAMIGTMQswCQYD +VQQGEwJGUjEPMA0GA1UECAwGUmFkaXVzMRIwEAYDVQQHDAlTb21ld2hlcmUxFTAT +BgNVBAoMDEV4YW1wbGUgSW5jLjEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBs +ZS5jb20xJjAkBgNVBAMMHUV4YW1wbGUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4X +DTE2MTEyMzAyNTUwN1oXDTE3MDEyMjAyNTUwN1owgZMxCzAJBgNVBAYTAkZSMQ8w +DQYDVQQIDAZSYWRpdXMxEjAQBgNVBAcMCVNvbWV3aGVyZTEVMBMGA1UECgwMRXhh +bXBsZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLmNvbTEmMCQG +A1UEAwwdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwgZ8wDQYJKoZIhvcN +AQEBBQADgY0AMIGJAoGBAL03y7N2GvNDO9BN8fVtdNonp0bMiqpj1D0He5+OTM+9 +3ZTIsJCNrbzhLQrRI3vMW7UDy8U7GeWORN9W4dWYlYiy/NFRp3hNMrbePhVmNIOV +ww4ovGzbD+Xo31gPVkhzQ8I5/jbOIQBmgKMAMZyOMlG9VD6yMmAeYqnZYz68WHKt +AgMBAAGjggE0MIIBMDAdBgNVHQ4EFgQUf1MLQIzAEZcRsgZlS8sosfmVI+UwgcgG +A1UdIwSBwDCBvYAUf1MLQIzAEZcRsgZlS8sosfmVI+WhgZmkgZYwgZMxCzAJBgNV +BAYTAkZSMQ8wDQYDVQQIDAZSYWRpdXMxEjAQBgNVBAcMCVNvbWV3aGVyZTEVMBMG +A1UECgwMRXhhbXBsZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxl +LmNvbTEmMCQGA1UEAwwdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCCQDX +uWUgiqAfHzAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly93 +d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcNAQELBQADgYEA +GepHc7TE/P+5t/cZPn5TTQkWQ/4/1lgQd82lF36RYWSIW3BdAc0zwYWYZaWixxyp +s0YOqwz6PZAGRV+SlYO2f8Kf+C3aZs4YHB0GsmksmFOb8r9d7xcDuOKHoA+QV0Zw +RaK6pttsBAxy7rw3kX/CgTp0Y2puaLdMXv/v9FisCP8= +-----END CERTIFICATE----- diff --git a/examples/31_wpa2_enterprise/main/wpa2_client.crt b/examples/31_wpa2_enterprise/main/wpa2_client.crt new file mode 100644 index 0000000000..7499e6967a --- /dev/null +++ b/examples/31_wpa2_enterprise/main/wpa2_client.crt @@ -0,0 +1,70 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 44 (0x2c) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=FR, ST=Radius, L=Somewhere, O=Example Inc./emailAddress=admin@example.com, CN=Example Certificate Authority + Validity + Not Before: Nov 23 02:55:07 2016 GMT + Not After : Jan 22 02:55:07 2017 GMT + Subject: C=FR, ST=Radius, O=Example Inc., CN=user@example.com/emailAddress=user@example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:ac:41:d4:a2:46:0c:dc:67:1d:7b:89:36:7c:15: + be:a2:c1:fe:4c:f2:fa:af:5d:76:0e:ee:b5:ca:d4: + d3:01:c8:6b:30:50:df:2d:57:17:f4:43:47:97:ca: + f1:8d:f7:c0:9d:56:b3:e7:17:7c:58:59:de:f3:be: + b5:08:5d:f8:3a:ad:83:44:0d:31:c9:f1:3d:f1:9a: + cf:84:0c:ad:d3:be:5c:bd:3d:58:b5:1d:2c:fe:70: + 8d:c5:b0:17:87:d4:8e:85:f7:51:4c:0f:d1:e0:8c: + 7b:a0:25:ab:91:7c:7f:eb:47:73:c9:4b:6c:8b:e6: + c1:06:d5:94:30:63:ec:45:1a:f5:7f:46:2f:b3:84: + 78:5d:1c:37:1a:fa:57:ea:45:5e:45:40:ab:14:c7: + 81:b0:26:3d:7e:cf:da:db:f0:f1:40:a7:a1:4b:54: + f3:96:1b:c9:30:3c:3c:d8:19:ba:c7:df:b1:ad:a2: + d6:17:0a:d6:ed:31:b5:cb:12:39:f5:6e:92:6b:85: + f2:9e:c7:06:6b:bb:89:ed:a7:5f:ec:56:12:46:fd: + 3a:74:d1:d2:31:30:1d:58:19:25:33:ff:11:ea:3a: + 52:33:b1:fb:d3:75:8d:1f:5e:36:a5:35:e0:11:5a: + 4a:2d:97:58:2c:3d:62:3c:32:af:83:69:a9:1a:32: + 1b:b7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 CRL Distribution Points: + + Full Name: + URI:http://www.example.com/example_ca.crl + + Signature Algorithm: sha1WithRSAEncryption + 8b:8d:b6:19:ce:6f:6b:9e:1d:03:8b:6b:10:fc:99:d0:7a:2f: + e0:37:ce:b8:a4:e4:b9:a1:c2:36:ff:76:b2:ad:d7:d0:df:d1: + 03:27:93:a7:4e:1e:bf:ed:d2:b7:65:2a:c9:c3:ab:20:aa:e3: + 10:4c:75:3b:c4:02:ab:34:08:6e:61:91:cf:e3:02:35:6a:e5: + f3:25:96:51:92:82:6e:52:81:c1:f1:7b:68:02:b0:ce:f4:ba: + fd:6e:68:35:b3:7e:77:cb:a0:1e:11:5e:58:bf:f3:2a:ed:b3: + 4c:82:21:5e:1b:47:b6:2f:f3:f5:c9:1b:6a:70:44:6d:ff:ad: + a6:e3 +-----BEGIN CERTIFICATE----- +MIIDTjCCAregAwIBAgIBLDANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UEBhMCRlIx +DzANBgNVBAgMBlJhZGl1czESMBAGA1UEBwwJU29tZXdoZXJlMRUwEwYDVQQKDAxF +eGFtcGxlIEluYy4xIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMSYw +JAYDVQQDDB1FeGFtcGxlIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNjExMjMw +MjU1MDdaFw0xNzAxMjIwMjU1MDdaMHExCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZS +YWRpdXMxFTATBgNVBAoMDEV4YW1wbGUgSW5jLjEZMBcGA1UEAwwQdXNlckBleGFt +cGxlLmNvbTEfMB0GCSqGSIb3DQEJARYQdXNlckBleGFtcGxlLmNvbTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKxB1KJGDNxnHXuJNnwVvqLB/kzy+q9d +dg7utcrU0wHIazBQ3y1XF/RDR5fK8Y33wJ1Ws+cXfFhZ3vO+tQhd+Dqtg0QNMcnx +PfGaz4QMrdO+XL09WLUdLP5wjcWwF4fUjoX3UUwP0eCMe6Alq5F8f+tHc8lLbIvm +wQbVlDBj7EUa9X9GL7OEeF0cNxr6V+pFXkVAqxTHgbAmPX7P2tvw8UCnoUtU85Yb +yTA8PNgZusffsa2i1hcK1u0xtcsSOfVukmuF8p7HBmu7ie2nX+xWEkb9OnTR0jEw +HVgZJTP/Eeo6UjOx+9N1jR9eNqU14BFaSi2XWCw9Yjwyr4NpqRoyG7cCAwEAAaNP +ME0wEwYDVR0lBAwwCgYIKwYBBQUHAwIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDov +L3d3dy5leGFtcGxlLmNvbS9leGFtcGxlX2NhLmNybDANBgkqhkiG9w0BAQUFAAOB +gQCLjbYZzm9rnh0Di2sQ/JnQei/gN864pOS5ocI2/3ayrdfQ39EDJ5OnTh6/7dK3 +ZSrJw6sgquMQTHU7xAKrNAhuYZHP4wI1auXzJZZRkoJuUoHB8XtoArDO9Lr9bmg1 +s353y6AeEV5Yv/Mq7bNMgiFeG0e2L/P1yRtqcERt/62m4w== +-----END CERTIFICATE----- diff --git a/examples/31_wpa2_enterprise/main/wpa2_client.key b/examples/31_wpa2_enterprise/main/wpa2_client.key new file mode 100644 index 0000000000..0b8f0d0199 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/wpa2_client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEArEHUokYM3Gcde4k2fBW+osH+TPL6r112Du61ytTTAchrMFDf +LVcX9ENHl8rxjffAnVaz5xd8WFne8761CF34Oq2DRA0xyfE98ZrPhAyt075cvT1Y +tR0s/nCNxbAXh9SOhfdRTA/R4Ix7oCWrkXx/60dzyUtsi+bBBtWUMGPsRRr1f0Yv +s4R4XRw3GvpX6kVeRUCrFMeBsCY9fs/a2/DxQKehS1TzlhvJMDw82Bm6x9+xraLW +FwrW7TG1yxI59W6Sa4XynscGa7uJ7adf7FYSRv06dNHSMTAdWBklM/8R6jpSM7H7 +03WNH142pTXgEVpKLZdYLD1iPDKvg2mpGjIbtwIDAQABAoIBAQCMhO9GqUpYia2d +VyOhOcPX1dTzRMuHPwDN0aFvIwo2zB3UvkQxInkiA7hldWJz44W3VEFR5PDEyht8 +Tzgy6SVUCLOqUfEpwag8bYOXPxiWQRY6Mc8pf/FyZrLgb3PilFznoAcru0QEn9VB +oTlCZ4OalSE5NlQIFGemgZhvmTPmcm4OwPW2diBjLtb3AA8eaaw8okWZwr8g4Bcd +el5KX6pZpDRpGQueh3iKaKxYWbxLYK+c30gKWD65tsAqKyVg2Tm1R2c+kFXgizZt +EexD95SGMjSkGg3R05sKv6m71iJhlOzVQ4ZCKm18Kqa7wZuZ4SIehVmKIV0gaupz +gjyr7+NBAoGBAOGjjGI3nxJTZY3O+KeaQo/jqrKowqZxzMOsCgAvW56xDuAvx9TJ +m4428NGubMl/0RwX6TnxJDm6oe+tnOxLIgE/VnsQLiNzQuFJxrs5JYctdGc4uvk2 +KuXDr7tPEYlU/7OLRReov9emydIXJnsGejkIPllUj+DGNjNFqtXh2VoHAoGBAMNv +eSgJSkcM6AUaDuUKaXBL2nkKHNoTtRQ0eCEUds6arKyMo0mSP753FNEuOWToVz1O +oaddSFw81J9t+Xd6XSRbhMj63bQ9nvFKBA1lJfLu+xe3ts0f+vmp1PguOuUHsgNP +aAm/gLPSKUpBO46NG6KhUrZ2ej6AEg7SuGXrDITRAoGBAKK7s6m6d81dvGZ0GT23 +sb3Y8ul7cTdd59JPp77OaQOgqxvhGfxLkxcUZMa1R9xjhMsAK8MQOZIxGk2kJwL8 +hP/lUFfdKYmDvX6CGQQ6iOhfTg6MCb1m5bVkVr9+nSUw2mIBVclkeUftEK2m6Kfd +2hR774u5wzLXgYuk+TrcckfNAoGBAJ9X8hacjH0lnr8aIe7I8HLoxbZOcnuz+b4B +kbiW8M8++W6uNCw2G9b1THnJEG6fqRGJXPASdH8P8eQTTIUHtY2BOOCM+dqNK1xc +FrW9NJXAF+WcmmTgoEaTG9tGBirafV+JjK/1/b+fqJ6sVRzDHDcbBU9ThhQTY6XG +VSZz4H8hAoGBAMeQQjiUlKBnpGt1oTgKDZo58b7ui61yftg+dEAwIKs6eb5X20vZ +Ca4v/zg06k9lKTzyspQjJZuzpMjFUvDK4ReamEvmwQTIc+oYVJm9Af1HUytzrHJH +u0/dDt0eYpZpzrFqxlP+0oXxlegD8REMVvwNCy+4isyCvjogDaYRfJqi +-----END RSA PRIVATE KEY----- diff --git a/examples/31_wpa2_enterprise/main/wpa2_client.pem b/examples/31_wpa2_enterprise/main/wpa2_client.pem new file mode 100644 index 0000000000..37bf709195 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/wpa2_client.pem @@ -0,0 +1,57 @@ +Bag Attributes + localKeyID: E1 2F DD 9A 78 71 54 6D 59 57 AA 6A 9F 92 3B 5C CC AB A3 64 +subject=/C=FR/ST=Radius/O=Example Inc./CN=user@example.com/emailAddress=user@example.com +issuer=/C=FR/ST=Radius/L=Somewhere/O=Example Inc./emailAddress=admin@example.com/CN=Example Certificate Authority +-----BEGIN CERTIFICATE----- +MIIDTjCCAregAwIBAgIBLDANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UEBhMCRlIx +DzANBgNVBAgMBlJhZGl1czESMBAGA1UEBwwJU29tZXdoZXJlMRUwEwYDVQQKDAxF +eGFtcGxlIEluYy4xIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMSYw +JAYDVQQDDB1FeGFtcGxlIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNjExMjMw +MjU1MDdaFw0xNzAxMjIwMjU1MDdaMHExCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZS +YWRpdXMxFTATBgNVBAoMDEV4YW1wbGUgSW5jLjEZMBcGA1UEAwwQdXNlckBleGFt +cGxlLmNvbTEfMB0GCSqGSIb3DQEJARYQdXNlckBleGFtcGxlLmNvbTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKxB1KJGDNxnHXuJNnwVvqLB/kzy+q9d +dg7utcrU0wHIazBQ3y1XF/RDR5fK8Y33wJ1Ws+cXfFhZ3vO+tQhd+Dqtg0QNMcnx +PfGaz4QMrdO+XL09WLUdLP5wjcWwF4fUjoX3UUwP0eCMe6Alq5F8f+tHc8lLbIvm +wQbVlDBj7EUa9X9GL7OEeF0cNxr6V+pFXkVAqxTHgbAmPX7P2tvw8UCnoUtU85Yb +yTA8PNgZusffsa2i1hcK1u0xtcsSOfVukmuF8p7HBmu7ie2nX+xWEkb9OnTR0jEw +HVgZJTP/Eeo6UjOx+9N1jR9eNqU14BFaSi2XWCw9Yjwyr4NpqRoyG7cCAwEAAaNP +ME0wEwYDVR0lBAwwCgYIKwYBBQUHAwIwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDov +L3d3dy5leGFtcGxlLmNvbS9leGFtcGxlX2NhLmNybDANBgkqhkiG9w0BAQUFAAOB +gQCLjbYZzm9rnh0Di2sQ/JnQei/gN864pOS5ocI2/3ayrdfQ39EDJ5OnTh6/7dK3 +ZSrJw6sgquMQTHU7xAKrNAhuYZHP4wI1auXzJZZRkoJuUoHB8XtoArDO9Lr9bmg1 +s353y6AeEV5Yv/Mq7bNMgiFeG0e2L/P1yRtqcERt/62m4w== +-----END CERTIFICATE----- +Bag Attributes + localKeyID: E1 2F DD 9A 78 71 54 6D 59 57 AA 6A 9F 92 3B 5C CC AB A3 64 +Key Attributes: +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIdQ9mH9ZzfJECAggA +MBQGCCqGSIb3DQMHBAhDtvRRe/rZHgSCBMhNLBUX5dw3KAHBXkTJDqa9EjOCbD0i +kMGxvoVE3Vrotoh5rMiGlPg+qOTaKT7kd3Yl6bwxF1sV0GyzuSJn+legjV9oXuVK +rI3NWeZr7KG18IP+ZGPs4fDRKnCiKK6cCopF1mzvRhzts5stlOqQkp1Dv9+A7Gyp +OarBK6x3p5db1mznOMdwzD8vyOxGhE98Zq6yDaDz1yKm6mtOLc8RQ1zbwEE1nkcc +J1R73A77E8dY6Rq7E4ec6d+rHki79yskq6eu30ECzC3VUSn6wdKXKKJgvn1V3dNf +QyRdMwXFVXySaGcBaq3zQp9qW+ISesvEstH9fN/GSzoDqe1OqTbe9pLCUtmVksAg +9z4et+OHYdk1c6X+5VI0ywg4t0qjOCOTacJSzw0/lC8OhNTH0jQDFAoIMOIvLuyO +pdNUcaGiWPKS1WjWEMiPPusrPiDXZ5T88go35rGnZJRUK6ighSdtKPKG3qPAslMo +Rn+eOiA2YJ5AorfkR63PI9MfCJbNVfmeTV/VoPXgjrtVNGtvrV54KeHwwLSJtyfj +xqS1g7aSgTgzfoTgYtzxVwy1g3jJZjYlOd6hKBj+Zzl/7C4cyilI3rrKU1OCokAn +xXFwKu2clrh1n3VHD+TdUP1lJD44uHuhgev2MmwOBGKGGNsGNqK9Jo7PCBeWyBSq +d0vzlbo95mMJ5BxKEJex3pxNeTTtfRBjr0AJLuneUBDvjSfhltIvOeMXEfIPQCtU +MTqjGmpgOTbMI4fEgprH7dULOHyn+Mn37sBMbtYHHk2D4FSJD2FkIP3KrudDf/Yr +ePn2ACne2ot9mW9McvDZGDOq0fyrIMcCNTwxP52Z3CuJucwuAoTwiwSg9ZM3t4E9 +K1Fz10xwzctFip9XxpxADA9M/QRa9VBgt7pt114Z2y+/ba0hOZ9mCrdpcHGH8xkO +kJhcCCg1tOLVl2AmUIVtdMDaJMgskq8tfeRTD6qp/JzKQCiz2OvcOG5ixgAhuOSn +9WjCnM4hARhZ/OVDYvsugwELil6E4zpc7mawtNiMj4P7ad1O+/hDN5yVQiU/XzIg +OJEV//FfnApcigay5Ne/n9K2morPIIDSQdet/2L6Y6DmtwM9B8qbzb/DjHfXK4nf +hvvPXD+DUCOfrj3VdkgwY338anWFFQpYbju8nrIv7+vINys+y8HeiPQp5I9zw1XF +q5tfEt4YfTO8hwqBNZL9yhS46CLgvvWavP1ZfvknvNqcvVvRMo60j0DDeZBW676v +U251knuWH16m+/cozbelscwZxikhzxdYzudWjFlH+phOIIvnmCgEZKtN8OWxFoN/ +6YBLPO1jOkibGKV1GHVg0PHdSwYmc1H5CefPobow9XP/RCd8C9+eBmynzErMmeme +4R2etPlsBt3mpakFoG7U+iNu89e4eOK2pzSrydfBSS6tUFXg5L7W5UrdWQ8vRyuc +aLkwuzdVbM8adlcAyb1MVc+1G5JMJVRfPNNAfJkSti7VUqONoOnoUAUSXGpNwoXa +ddctpxLmwsfjYEJ3OCLjFy06A2ZlikqtMi5H4sNytSp0Mfr06J4ZZmL8T1GHxYSP +Xf51VEqiZpKHeBo7ZqrxKvGvFxzm6mGMy8LPpRfSy88z4rPjmP5qrXTbo9qBeo9G +GlY= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/examples/31_wpa2_enterprise/main/wpa2_enterprise_main.c b/examples/31_wpa2_enterprise/main/wpa2_enterprise_main.c new file mode 100644 index 0000000000..7d325c76a0 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/wpa2_enterprise_main.c @@ -0,0 +1,154 @@ +/* WiFi Connection Example using WPA2 Enterprise + * + * Original Copyright (C) 2006-2016, ARM Limited, All Rights Reserved, Apache 2.0 License. + * Additions Copyright (C) Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD, Apache 2.0 License. + * + * + * 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 +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_wifi.h" +#include "esp_wpa2.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "tcpip_adapter.h" + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" + + You can choose EAP method via 'make menuconfig' according to the + configuration of AP. +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_EAP_METHOD CONFIG_EAP_METHOD + +#define EXAMPLE_EAP_ID CONFIG_EAP_ID +#define EXAMPLE_EAP_USERNAME CONFIG_EAP_USERNAME +#define EXAMPLE_EAP_PASSWORD CONFIG_EAP_PASSWORD + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +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; + +/* Constants that aren't configurable in menuconfig */ +#define EAP_PEAP 1 +#define EAP_TTLS 2 + +static const char *TAG = "example"; + +/* CA cert, taken from wpa2_ca.pem + Client cert, taken from wpa2_client.crt + Client key, taken from wpa2_client.key + + The PEM, CRT and KEY file were provided by the person or organization + who configured the AP with wpa2 enterprise. + + To embed it in the app binary, the PEM, CRT and KEY file is named + in the component.mk COMPONENT_EMBED_TXTFILES variable. +*/ +extern uint8_t ca_pem_start[] asm("_binary_wpa2_ca_pem_start"); +extern uint8_t ca_pem_end[] asm("_binary_wpa2_ca_pem_end"); +extern uint8_t client_crt_start[] asm("_binary_wpa2_client_crt_start"); +extern uint8_t client_crt_end[] asm("_binary_wpa2_client_crt_end"); +extern uint8_t client_key_start[] asm("_binary_wpa2_client_key_start"); +extern uint8_t client_key_end[] asm("_binary_wpa2_client_key_end"); + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + unsigned int ca_pem_bytes = ca_pem_end - ca_pem_start; + unsigned int client_crt_bytes = client_crt_end - client_crt_start; + unsigned int client_key_bytes = client_key_end - client_key_start; + + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_ca_cert(ca_pem_start, ca_pem_bytes) ); + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_cert_key(client_crt_start, client_crt_bytes,\ + client_key_start, client_key_bytes, NULL, 0) ); + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_identity((uint8_t *)EXAMPLE_EAP_ID, strlen(EXAMPLE_EAP_ID)) ); + if (EXAMPLE_EAP_METHOD == EAP_PEAP || EXAMPLE_EAP_METHOD == EAP_TTLS) { + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_username((uint8_t *)EXAMPLE_EAP_USERNAME, strlen(EXAMPLE_EAP_USERNAME)) ); + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_set_password((uint8_t *)EXAMPLE_EAP_PASSWORD, strlen(EXAMPLE_EAP_PASSWORD)) ); + } + ESP_ERROR_CHECK( esp_wifi_sta_wpa2_ent_enable() ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +static void wpa2_enterprise_task(void *pvParameters) +{ + tcpip_adapter_ip_info_t ip; + memset(&ip, 0, sizeof(tcpip_adapter_ip_info_t)); + vTaskDelay(2000 / portTICK_PERIOD_MS); + + while (1) { + vTaskDelay(2000 / portTICK_PERIOD_MS); + + if (tcpip_adapter_get_ip_info(ESP_IF_WIFI_STA, &ip) == 0) { + ESP_LOGI(TAG, "~~~~~~~~~~~"); + ESP_LOGI(TAG, "IP:"IPSTR, IP2STR(&ip.ip)); + ESP_LOGI(TAG, "MASK:"IPSTR, IP2STR(&ip.netmask)); + ESP_LOGI(TAG, "GW:"IPSTR, IP2STR(&ip.gw)); + ESP_LOGI(TAG, "~~~~~~~~~~~"); + } + } +} + +void app_main() +{ + nvs_flash_init(); + initialise_wifi(); + xTaskCreate(&wpa2_enterprise_task, "wpa2_enterprise_task", 4096, NULL, 5, NULL); +} diff --git a/examples/31_wpa2_enterprise/main/wpa2_server.crt b/examples/31_wpa2_enterprise/main/wpa2_server.crt new file mode 100644 index 0000000000..312a7d6aa8 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/wpa2_server.crt @@ -0,0 +1,70 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 43 (0x2b) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=FR, ST=Radius, L=Somewhere, O=Example Inc./emailAddress=admin@example.com, CN=Example Certificate Authority + Validity + Not Before: Nov 23 02:55:07 2016 GMT + Not After : Jan 22 02:55:07 2017 GMT + Subject: C=FR, ST=Radius, O=Example Inc., CN=Example Server Certificate/emailAddress=admin@example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:ca:5d:86:b7:7a:3a:bc:f4:4d:d8:69:8c:25:bf: + d1:d7:33:b7:76:ea:d9:ae:b6:78:14:d6:d5:ca:67: + fd:f4:5c:13:d3:01:b4:bc:92:d5:a0:51:f5:fe:81: + 6f:da:28:33:07:08:20:e9:26:27:c6:ab:43:0b:7f: + ce:7c:3b:c6:9c:a4:6c:85:22:3d:40:84:27:32:d6: + a2:94:ed:14:29:4c:ef:d9:ac:d5:a3:ea:7d:47:76: + 18:57:7c:0a:4a:fe:ba:8f:b8:44:44:a5:62:e8:b0: + dd:59:6b:d2:20:69:f1:64:e1:f6:d0:e5:9e:88:da: + 10:e5:58:18:fc:87:ce:2f:67:f6:9d:f8:ac:da:0f: + 2b:f5:58:30:04:13:1c:b5:71:ce:3d:26:c7:34:03: + 66:38:ca:8d:11:75:f0:0b:14:ab:98:b1:dc:cd:81: + d2:68:33:96:d6:50:4f:a7:19:d0:20:15:5e:e0:18: + 8b:07:83:11:2d:3d:51:14:68:73:cd:f2:70:c6:59: + 50:cf:e1:f5:12:88:d5:71:de:1d:92:2e:7d:d1:8b: + 09:fe:b4:17:bd:7e:73:07:c0:a1:6a:f3:af:80:3b: + e4:d7:62:6d:1c:15:93:92:47:25:bd:f6:50:02:3e: + 9c:00:7d:15:89:f2:38:10:95:f2:ef:09:fa:b5:cf: + 90:63 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 CRL Distribution Points: + + Full Name: + URI:http://www.example.com/example_ca.crl + + Signature Algorithm: sha1WithRSAEncryption + 92:f8:06:4b:32:4f:f1:22:18:9c:67:dc:28:03:82:ee:14:0a: + 59:a9:07:bb:1e:44:80:8e:a0:69:28:03:bd:04:87:5f:0c:86: + 80:63:db:47:ea:06:25:9e:7f:67:ef:16:77:37:02:8f:00:6a: + 59:cd:00:06:19:b0:43:34:8c:14:bf:29:fd:e0:8c:57:0f:63: + 7b:73:68:70:8a:13:ff:2d:39:a5:a7:69:fb:7b:13:3a:2c:09: + b5:c1:15:d8:7a:2f:77:33:16:7f:41:08:be:23:61:ac:aa:92: + 3f:38:40:06:87:3c:63:86:16:ba:4a:2d:ea:04:36:5a:fd:c7: + 80:8d +-----BEGIN CERTIFICATE----- +MIIDWTCCAsKgAwIBAgIBKzANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UEBhMCRlIx +DzANBgNVBAgMBlJhZGl1czESMBAGA1UEBwwJU29tZXdoZXJlMRUwEwYDVQQKDAxF +eGFtcGxlIEluYy4xIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMSYw +JAYDVQQDDB1FeGFtcGxlIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNjExMjMw +MjU1MDdaFw0xNzAxMjIwMjU1MDdaMHwxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZS +YWRpdXMxFTATBgNVBAoMDEV4YW1wbGUgSW5jLjEjMCEGA1UEAwwaRXhhbXBsZSBT +ZXJ2ZXIgQ2VydGlmaWNhdGUxIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUu +Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyl2Gt3o6vPRN2GmM +Jb/R1zO3durZrrZ4FNbVymf99FwT0wG0vJLVoFH1/oFv2igzBwgg6SYnxqtDC3/O +fDvGnKRshSI9QIQnMtailO0UKUzv2azVo+p9R3YYV3wKSv66j7hERKVi6LDdWWvS +IGnxZOH20OWeiNoQ5VgY/IfOL2f2nfis2g8r9VgwBBMctXHOPSbHNANmOMqNEXXw +CxSrmLHczYHSaDOW1lBPpxnQIBVe4BiLB4MRLT1RFGhzzfJwxllQz+H1EojVcd4d +ki590YsJ/rQXvX5zB8ChavOvgDvk12JtHBWTkkclvfZQAj6cAH0VifI4EJXy7wn6 +tc+QYwIDAQABo08wTTATBgNVHSUEDDAKBggrBgEFBQcDATA2BgNVHR8ELzAtMCug +KaAnhiVodHRwOi8vd3d3LmV4YW1wbGUuY29tL2V4YW1wbGVfY2EuY3JsMA0GCSqG +SIb3DQEBBQUAA4GBAJL4BksyT/EiGJxn3CgDgu4UClmpB7seRICOoGkoA70Eh18M +hoBj20fqBiWef2fvFnc3Ao8AalnNAAYZsEM0jBS/Kf3gjFcPY3tzaHCKE/8tOaWn +aft7EzosCbXBFdh6L3czFn9BCL4jYayqkj84QAaHPGOGFrpKLeoENlr9x4CN +-----END CERTIFICATE----- diff --git a/examples/31_wpa2_enterprise/main/wpa2_server.key b/examples/31_wpa2_enterprise/main/wpa2_server.key new file mode 100644 index 0000000000..e7d9d26200 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/wpa2_server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAyl2Gt3o6vPRN2GmMJb/R1zO3durZrrZ4FNbVymf99FwT0wG0 +vJLVoFH1/oFv2igzBwgg6SYnxqtDC3/OfDvGnKRshSI9QIQnMtailO0UKUzv2azV +o+p9R3YYV3wKSv66j7hERKVi6LDdWWvSIGnxZOH20OWeiNoQ5VgY/IfOL2f2nfis +2g8r9VgwBBMctXHOPSbHNANmOMqNEXXwCxSrmLHczYHSaDOW1lBPpxnQIBVe4BiL +B4MRLT1RFGhzzfJwxllQz+H1EojVcd4dki590YsJ/rQXvX5zB8ChavOvgDvk12Jt +HBWTkkclvfZQAj6cAH0VifI4EJXy7wn6tc+QYwIDAQABAoIBAHN/BZBaNoP5eyR0 +uQQigoUsgx1f1HWettJN+x7WU17P3pCjfJ/TvhEONjPXdBsyrkzpGr92g2UkAdPi +Udsd0rE8NqOVcxgLVMPzND+DP+qCerHlrtkqz+2lPSdRKB69W4UDShNTwSvFJdAP +dZICZT+Kp+953g2FR/ddXIVkjQ0IaKN8gD4j+JKpfVGMpKlYdUf8gK6Ag71VtUa5 +XzRDS4A9ytrpNcmaXaOhCVPdgCg20CULgpg6B6dG3XWzC16Erf8rMl0fsAB31K9a +qF87QS14JavdW1vdUFXCyqre9N58NUKxQjuhNADSB2sJEXkP2uhPcL+72Li8deRO +8MJgckECgYEA8ryoRI7bKn907Kx4Go7G4NS3hwuTd9jhhS8iQ1tatkQdqnBXnuWU +X20i02vYQGoZGsOl+DTgY2IWRYBPkGT+RwklJxCvYkZvOCgHaipoWsF+EOptv5Au +4ZERlBCWXzWdEgnx5nCFJQFfWBm77iC+muPpB+SryQmTld/RwvihxBsCgYEA1Wwp +Qq/5urpzz/uwNdMiMJ1lePi2HBvJeZOnw0LU+xUqXUkt6291CLhHNn8okPVeoNZP +iKP0Lid6IO2yp/3iCT5w9NNOMFlyhrVMAxYOkrM8AxlYnCwoCOoqN5x+4RrJLVFL +zrg+VN9vexfkOWdH9t8g+0gNn7MCX3adqy1/WVkCgYA4yXIEN/eGBbNw+xhN/kEA +sEMPUOH7E74OzmwRnfmm0mCuUwHspoEDoiCXnY9F4oxk+oiFfLlSBsx3DgGPIlFq +hPUwInMlZpz2Ykb5y1oGgWXgxzdNrYmKM8oM/aRwOba5VaJF6uT7N0r67WpN11NA +ITmPIywdKCRi163XExulKQKBgHyMgI/AbrbANPH9adofeuZwFFXCn1RMCwn+V3sm +N3DH609Bc6DgDKaoFDcDgkMGTtECAKw3Mjr1ItqwnQBYs169p+HYptqkeKeQiemL +J7oJC06rrgCF7F83eKe3lnv7y8e8l8bt0sJpGn/1c2TklyTFFlROulSmfQ4FBQJu +rNERAoGAec+0Wi5qYT917CPHqXcCUTg35kvtlLlgGdX6kNZRNszZUIF7O+wH4EJx +yxu3cgxZ2FL95Kf/oyOOnlOkRJ/clJbNBVSEHvJh64GL0PZ5V5szsscoGr6KY7SO +/kkJKC3OS/3fpto1/9yjJpoqJp9pzGU48PM0IKgd3ITQE6oOCCg= +-----END RSA PRIVATE KEY----- diff --git a/examples/31_wpa2_enterprise/main/wpa2_server.pem b/examples/31_wpa2_enterprise/main/wpa2_server.pem new file mode 100644 index 0000000000..97d16aec59 --- /dev/null +++ b/examples/31_wpa2_enterprise/main/wpa2_server.pem @@ -0,0 +1,57 @@ +Bag Attributes + localKeyID: 4E 12 CF 3A FA D4 03 64 00 BB 98 1C 78 35 56 4A AC C3 1E 17 +subject=/C=FR/ST=Radius/O=Example Inc./CN=Example Server Certificate/emailAddress=admin@example.com +issuer=/C=FR/ST=Radius/L=Somewhere/O=Example Inc./emailAddress=admin@example.com/CN=Example Certificate Authority +-----BEGIN CERTIFICATE----- +MIIDWTCCAsKgAwIBAgIBKzANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UEBhMCRlIx +DzANBgNVBAgMBlJhZGl1czESMBAGA1UEBwwJU29tZXdoZXJlMRUwEwYDVQQKDAxF +eGFtcGxlIEluYy4xIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMSYw +JAYDVQQDDB1FeGFtcGxlIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xNjExMjMw +MjU1MDdaFw0xNzAxMjIwMjU1MDdaMHwxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIDAZS +YWRpdXMxFTATBgNVBAoMDEV4YW1wbGUgSW5jLjEjMCEGA1UEAwwaRXhhbXBsZSBT +ZXJ2ZXIgQ2VydGlmaWNhdGUxIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUu +Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyl2Gt3o6vPRN2GmM +Jb/R1zO3durZrrZ4FNbVymf99FwT0wG0vJLVoFH1/oFv2igzBwgg6SYnxqtDC3/O +fDvGnKRshSI9QIQnMtailO0UKUzv2azVo+p9R3YYV3wKSv66j7hERKVi6LDdWWvS +IGnxZOH20OWeiNoQ5VgY/IfOL2f2nfis2g8r9VgwBBMctXHOPSbHNANmOMqNEXXw +CxSrmLHczYHSaDOW1lBPpxnQIBVe4BiLB4MRLT1RFGhzzfJwxllQz+H1EojVcd4d +ki590YsJ/rQXvX5zB8ChavOvgDvk12JtHBWTkkclvfZQAj6cAH0VifI4EJXy7wn6 +tc+QYwIDAQABo08wTTATBgNVHSUEDDAKBggrBgEFBQcDATA2BgNVHR8ELzAtMCug +KaAnhiVodHRwOi8vd3d3LmV4YW1wbGUuY29tL2V4YW1wbGVfY2EuY3JsMA0GCSqG +SIb3DQEBBQUAA4GBAJL4BksyT/EiGJxn3CgDgu4UClmpB7seRICOoGkoA70Eh18M +hoBj20fqBiWef2fvFnc3Ao8AalnNAAYZsEM0jBS/Kf3gjFcPY3tzaHCKE/8tOaWn +aft7EzosCbXBFdh6L3czFn9BCL4jYayqkj84QAaHPGOGFrpKLeoENlr9x4CN +-----END CERTIFICATE----- +Bag Attributes + localKeyID: 4E 12 CF 3A FA D4 03 64 00 BB 98 1C 78 35 56 4A AC C3 1E 17 +Key Attributes: +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIxxT4lUY0dvgCAggA +MBQGCCqGSIb3DQMHBAiQ8/bzpj1InASCBMjU0Nl0/CMHnNAWZ6t1yb93vHJUHHsv +0acQNqeN8ToS1Wz0delbNnJMZ5lkO3ei542d2HwbGW4KYgWuMBPh2qgpdnnUfBPE +C6iCtGCDugVgZl3W7VPjQzMjsExuwYaZf7dhBkQEbuzuGXDrsZL3sauAeOla8V34 +yYESS9P7Jyd0prMgDG5yap9acorjiSLpqHsCogp/vlERSl8f/+yxY5dJuLo+54Z6 +Q8GjlpoiEUijy/Gs/BKcyTX3zddJm/yA3PF0JM8ZSH9K0pBa3l2VJYdizWGl3G59 +uozlMVZrg/KxDgfoe7QGit/WUGiq4fYXIUVKSKOfRmpD8YaTaUkGtoao9VjUYXul +01yU3DSRct9O/r1MG9QQjmYSi05t+Cohp/FNd0WqLlKPilDK86Wu4xECjOI+KsOe +kccUDBuaTMxdoYsq5Ge+V79YR1yABYtgkGymR9mS357Za9IlXxExhDBb2ky0MMlx +DfgimcWOlc4IWGrhheezZaTjgUO/Q0izrxD/ZBYNUmSnYDpRj4mk/sxQ6aDytlEu +ZSnTo+jbyXTh8nVwxhOB2PdWNmFOkRcGOJci8MErd8ArdWniw68MoYwhHTabDpwo +0nEs1MhqoiMAgn7iluN+cscV7pz4n5zriZX3Rw1ivcUPQ8RR6y5h/nR9Du5fCyRm +v5vF01w+o+XejU54DyqRptWiR6yHDJer2TIUqTjKt4NQENZyXEMh+ls6arjZ0mRp +i8rv3M/Z8NLcTQ7lD/gAGTWxzsNIfMcBQX9X9R6V4BmzhmQQA+/pbLh2IOOVPXO3 +sUGXChyQSa5xE1VpVle28Q3GqKq03W0W+8EtGy889px9/MGWLfCMrJNCv4zH7d/j +FTRkiNdtBZrmWTyUgBVgQoTVPxyzBBC11HXzRGxiEQft/NFwd0dzKoNfNsgGGa0Q +AzKmPJselaJdqol961z3RbzEpfyqvSnMbui/iIMV2lTEc/EQWoaQ3SGHf3qdhlQ5 +MScGocq+hskrMmgW1XVG3HDowdC5K5sKXUYJmixNGHWXFao/FZUAVBmQD+290Qkl +EMKQ2xw3PJ2tk47EpdscixayDpiOJQEdgys3oA1W06bIpxNaERYObqp8M62fet9I +wuRZyNWuKVyyilyB9sTjmgD55BXt6B+jkmkwHq15RA85AbsHHttugyMA//V+oiYt +B0BmSKpzSr6nEnr+0NiWybWlN97yLHUrtFiLMyKb6+qAMbxdsET6vfWsASuC21Lq +ZHgW3EofIEDz7r59w4NHwCLGBttpgKLkmrvjt17cMdmsEXbWFgw+9pi/uqEaRaq5 +/ekb9uPzbS7o4ArGQ+WBFUA6ONV98i2ZMOmfvq/dgBAQcRLlI7Cp+yikcMbzJ3Aq +VJc53y0Gl8awDXMbOiH0l1ij+3mQ5xPZbuv2ofcg+4enoK1cclG6ryWWGPMDcAg2 +JivgEzn6eFsiPRnlidrJUy6zJYxCsjSPodcbFH7DXnwPA8+C9P1it2bnqPdQWzXA +JiUFtvmRgEVmOAVCbZLkNPa+K0K8Ymzu3ZYchVMduxJh1xNKId+FM2BGOmlYqDUJ +jnGqHciOxd+0crjaPd0isxUgS7bTd3XdQEIkT/yESS9aJEHsnBFkEXsXSDpxpf7a +dE4= +-----END ENCRYPTED PRIVATE KEY----- From dd73a405561ba41649252f87a40eb2285d8061cd Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 11 Jan 2017 22:47:44 +0800 Subject: [PATCH 137/167] build system: fix IDF_VER for projects outside of ESP-IDF tree --- make/project.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/project.mk b/make/project.mk index ee8c077943..43dab767a4 100644 --- a/make/project.mk +++ b/make/project.mk @@ -178,7 +178,7 @@ endif # Git version of ESP-IDF (of the form v1.0-285-g5c4f707) -IDF_VER := $(shell git describe) +IDF_VER := $(shell git -C $(IDF_PATH) describe) # Set default LDFLAGS From 4a3e160888792809996c5325a1d6fd9d652b3252 Mon Sep 17 00:00:00 2001 From: Alexey Gerenkov Date: Thu, 22 Dec 2016 02:56:23 +0300 Subject: [PATCH 138/167] esp32: Add core dump saving to flash feature Complimentary changes: 1) Partition table definitions files with core dump partition 2) Special sub-type for core dump partition 3) Special version of spi_flash_xxx 4) espcoredump.py is script to get core dump from flash and print useful info 5) FreeRTOS API was extended to get tasks snapshots --- components/esp32/Kconfig | 18 + components/esp32/core_dump.c | 276 +++++ components/esp32/cpu_start.c | 5 + components/esp32/include/esp_core_dump.h | 21 + components/esp32/include/esp_panic.h | 4 + components/esp32/panic.c | 125 +- components/espcoredump/espcoredump.py | 1013 +++++++++++++++++ .../freertos/include/freertos/FreeRTOS.h | 4 +- .../include/freertos/FreeRTOSConfig.h | 4 +- components/freertos/include/freertos/task.h | 15 + components/freertos/tasks.c | 104 +- components/partition_table/Kconfig.projbuild | 6 +- .../partitions_singleapp_coredump.csv | 6 + .../partitions_two_ota_coredump.csv | 9 + components/spi_flash/cache_utils.c | 39 + components/spi_flash/cache_utils.h | 9 + components/spi_flash/flash_ops.c | 82 +- components/spi_flash/include/esp_partition.h | 1 + components/spi_flash/include/esp_spi_flash.h | 50 + 19 files changed, 1715 insertions(+), 76 deletions(-) create mode 100644 components/esp32/core_dump.c create mode 100644 components/esp32/include/esp_core_dump.h create mode 100755 components/espcoredump/espcoredump.py create mode 100644 components/partition_table/partitions_singleapp_coredump.csv create mode 100644 components/partition_table/partitions_two_ota_coredump.csv diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index bc35ca0e8f..9811585140 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -54,6 +54,24 @@ config TRACEMEM_RESERVE_DRAM default 0x4000 if MEMMAP_TRACEMEM && !MEMMAP_TRACEMEM_TWOBANKS default 0x0 +choice ESP32_COREDUMP_TO_FLASH_OR_UART + prompt "Core dump destination" + default ESP32_ENABLE_COREDUMP_TO_NONE + help + Select place to store core dump: flash, uart or none (to disable core dumps generation). + + If core dump is configured to be stored in flash and custom partition table is used add + corresponding entry to your CSV. For examples, please see predefined partition table CSV descriptions + in the components/partition_table directory. + +config ESP32_ENABLE_COREDUMP_TO_FLASH + bool "Flash" +config ESP32_ENABLE_COREDUMP_TO_UART + bool "UART" +config ESP32_ENABLE_COREDUMP_TO_NONE + bool "None" +endchoice + # Not implemented and/or needs new silicon rev to work config MEMMAP_SPISRAM bool "Use external SPI SRAM chip as main memory" diff --git a/components/esp32/core_dump.c b/components/esp32/core_dump.c new file mode 100644 index 0000000000..165ec74192 --- /dev/null +++ b/components/esp32/core_dump.c @@ -0,0 +1,276 @@ +// 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" + +//#include "esp_attr.h" +#include "esp_panic.h" +#include "esp_partition.h" + +#ifdef ESP_PLATFORM +// Uncomment this line to force output from this module +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#include "esp_log.h" +static const char* TAG = "esp_core_dump_init"; +#else +#define ESP_LOGD(...) +#endif + +// TODO: allow user to set this in menuconfig or get tasks iteratively +#define COREDUMP_MAX_TASKS_NUM 32 + +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + +// magic numbers to control core dump data consistency +#define COREDUMP_FLASH_MAGIC_START 0xDEADBEEFUL +#define COREDUMP_FLASH_MAGIC_END 0xACDCFEEDUL + +// core dump partition start +static uint32_t s_core_part_start; +// core dump partition size +static uint32_t s_core_part_size; + +static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint32_t data_size) +{ + esp_err_t err; + uint32_t data_len = 0, k, len; + union + { + uint8_t data8[4]; + uint32_t data32; + } rom_data; + + data_len = (data_size / sizeof(uint32_t)) * sizeof(uint32_t); + err = spi_flash_write_panic(off, data, data_len); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to write data"); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return 0; + } + + len = data_size % sizeof(uint32_t); + if (len) { + // write last bytes with padding, actual TCB len can be retrieved by esptool from core dump header + rom_data.data32 = 0; + for (k = 0; k < len; k++) + rom_data.data8[k] = *(data + data_len + k); + err = spi_flash_write_panic(off + data_len, &rom_data, sizeof(uint32_t)); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to write data end"); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return 0; + } + data_len += sizeof(uint32_t); + } + + return data_len; +} + +/* + * | MAGIC1 | + * | TOTAL_LEN | TASKS_NUM | TCB_SIZE | + * | TCB_ADDR_1 | STACK_TOP_1 | STACK_END_1 | TCB_1 | STACK_1 | + * . . . . + * . . . . + * | TCB_ADDR_N | STACK_TOP_N | STACK_END_N | TCB_N | STACK_N | + * | MAGIC2 | + */ +void esp_core_dump_to_flash(XtExcFrame *frame) +{ + union + { + uint8_t data8[16]; + uint32_t data32[4]; + } rom_data; + //const esp_partition_t *core_part; + esp_err_t err; + TaskSnapshot_t tasks[COREDUMP_MAX_TASKS_NUM]; + UBaseType_t tcb_sz, task_num; + uint32_t data_len = 0, i, len, sec_num; + size_t off; + + esp_panicPutStr("Save core dump to flash...\r\n"); + task_num = uxTaskGetSnapshotAll(tasks, COREDUMP_MAX_TASKS_NUM, &tcb_sz); + // take TCB padding into account, actual TCB size will be stored in header + if (tcb_sz % sizeof(uint32_t)) + len = (tcb_sz / sizeof(uint32_t) + 1) * sizeof(uint32_t); + else + len = tcb_sz; + // header + magic2 + tasknum*(tcb + stack start/end + tcb addr) + data_len = 5*sizeof(uint32_t) + task_num*(len + 2*sizeof(uint32_t) + sizeof(uint32_t *)); + for (i = 0; i < task_num; i++) { + if (tasks[i].pxTCB == xTaskGetCurrentTaskHandle()) { + // set correct stack top for current task + tasks[i].pxTopOfStack = (StackType_t *)frame; + esp_panicPutStr("Current task PC/A0/SP "); + esp_panicPutHex(frame->pc); + esp_panicPutStr(" "); + esp_panicPutHex(frame->a0); + esp_panicPutStr(" "); + esp_panicPutHex(frame->a1); + esp_panicPutStr("\r\n"); + } +#if( portSTACK_GROWTH < 0 ) + len = (uint32_t)tasks[i].pxEndOfStack - (uint32_t)tasks[i].pxTopOfStack; +#else + len = (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack; +#endif + esp_panicPutStr("stack len = "); + esp_panicPutHex(len); + esp_panicPutStr(" "); + esp_panicPutHex((int)tasks[i].pxTopOfStack); + esp_panicPutStr(" "); + esp_panicPutHex((int)tasks[i].pxEndOfStack); + esp_panicPutStr("\r\n"); + // take stack padding into account + if (len % sizeof(uint32_t)) + len = (len / sizeof(uint32_t) + 1) * sizeof(uint32_t); + data_len += len; + } + esp_panicPutStr("Core dump len ="); + esp_panicPutHex(data_len); + esp_panicPutStr("\r\n"); + if (data_len > s_core_part_size) { + esp_panicPutStr("ERROR: Not enough space to save core dump!"); + return; + } + + // TEST READ START + err = spi_flash_read_panic(s_core_part_start + 0, &rom_data, sizeof(rom_data)); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to read flash "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + else { + esp_panicPutStr("Data from flash:\r\n"); + for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) { + esp_panicPutHex(rom_data.data32[i]); + esp_panicPutStr("\r\n"); + } +// rom_data[4] = 0; +// esp_panicPutStr(rom_data); +// esp_panicPutStr("\r\n"); + } + // TEST READ END + + sec_num = data_len / SPI_FLASH_SEC_SIZE; + if (data_len % SPI_FLASH_SEC_SIZE) + sec_num++; + err = spi_flash_erase_range_panic(s_core_part_start + 0, sec_num * SPI_FLASH_SEC_SIZE); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to erase flash "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + + rom_data.data32[0] = COREDUMP_FLASH_MAGIC_START; + rom_data.data32[1] = data_len; + rom_data.data32[2] = task_num; + rom_data.data32[3] = tcb_sz; + err = spi_flash_write_panic(s_core_part_start + 0, &rom_data, sizeof(rom_data)); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to write core dump header "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + off = sizeof(rom_data); + + for (i = 0; i < task_num; i++) { + esp_panicPutStr("Dump task "); + esp_panicPutHex((int)tasks[i].pxTCB); + esp_panicPutStr("\r\n"); + + // save TCB address, stack base and stack top addr + rom_data.data32[0] = (uint32_t)tasks[i].pxTCB; + rom_data.data32[1] = (uint32_t)tasks[i].pxTopOfStack; + rom_data.data32[2] = (uint32_t)tasks[i].pxEndOfStack; + err = spi_flash_write_panic(s_core_part_start + off, &rom_data, 3*sizeof(uint32_t)); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to write task header "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + off += 3*sizeof(uint32_t); + // save TCB + len = esp_core_dump_write_flash_padded(s_core_part_start + off, tasks[i].pxTCB, tcb_sz); + if (len == 0) + return; + off += len; + // save task stack + /*int k; + for (k = 0; k < 8*4; k++) { + esp_panicPutStr("stack["); + esp_panicPutDec(k); + esp_panicPutStr("] = "); + esp_panicPutHex(((uint8_t *)tasks[i].pxTopOfStack)[k]); + esp_panicPutStr("\r\n"); + }*/ + len = esp_core_dump_write_flash_padded(s_core_part_start + off, +#if( portSTACK_GROWTH < 0 ) + tasks[i].pxTopOfStack, + (uint32_t)tasks[i].pxEndOfStack - (uint32_t)tasks[i].pxTopOfStack +#else + tasks[i].pxEndOfStack, + (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack +#endif + ); + if (len == 0) + return; + off += len; + } + + rom_data.data32[0] = COREDUMP_FLASH_MAGIC_END; + err = spi_flash_write_panic(s_core_part_start + off, &rom_data, sizeof(uint32_t)); + if (err != ESP_OK) { + esp_panicPutStr("Failed to write to flash "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + + esp_panicPutStr("Core dump has been saved to flash partition.\r\n"); +} +#endif + +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART +void esp_core_dump_to_uart(XtExcFrame *frame) +{ +} +#endif + +void esp_core_dump_init() +{ +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + const esp_partition_t *core_part; + + core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL); + if (!core_part) { + ESP_LOGE(TAG, "No core dump partition found!"); + return; + } + ESP_LOGI(TAG, "Found partition '%s' @ %x %d bytes", core_part->label, core_part->address, core_part->size); + s_core_part_start = core_part->address; + s_core_part_size = core_part->size; +#endif +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART +#endif +} + diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 864a17a1b6..95d5c5e6a2 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -55,6 +55,7 @@ #include "esp_task_wdt.h" #include "esp_phy_init.h" #include "esp_coexist.h" +#include "esp_core_dump.h" #include "trax.h" #define STRINGIFY(s) STRINGIFY2(s) @@ -214,6 +215,10 @@ void start_cpu0_default(void) } #endif +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + esp_core_dump_init(); +#endif + xTaskCreatePinnedToCore(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL, 0); diff --git a/components/esp32/include/esp_core_dump.h b/components/esp32/include/esp_core_dump.h new file mode 100644 index 0000000000..d130aa237b --- /dev/null +++ b/components/esp32/include/esp_core_dump.h @@ -0,0 +1,21 @@ +// 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 ESP_CORE_DUMP_H_ +#define ESP_CORE_DUMP_H_ + +void esp_core_dump_init(); +void esp_core_dump_to_flash(); +void esp_core_dump_to_uart(); + +#endif diff --git a/components/esp32/include/esp_panic.h b/components/esp32/include/esp_panic.h index aa83c6d381..25816d31f2 100644 --- a/components/esp32/include/esp_panic.h +++ b/components/esp32/include/esp_panic.h @@ -24,6 +24,10 @@ */ void esp_set_breakpoint_if_jtag(void *fn); +void esp_panicPutchar(char c); +void esp_panicPutStr(const char *c); +void esp_panicPutHex(int a); +void esp_panicPutDec(int a); #define ESP_WATCHPOINT_LOAD 0x40000000 #define ESP_WATCHPOINT_STORE 0x80000000 diff --git a/components/esp32/panic.c b/components/esp32/panic.c index c5b18870a9..94aebfbac4 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -33,6 +33,7 @@ #include "esp_panic.h" #include "esp_attr.h" #include "esp_err.h" +#include "esp_core_dump.h" /* Panic handlers; these get called when an unhandled exception occurs or the assembly-level @@ -46,61 +47,61 @@ #if !CONFIG_ESP32_PANIC_SILENT_REBOOT //printf may be broken, so we fix our own printing fns... -inline static void panicPutChar(char c) +void esp_panicPutChar(char c) { while (((READ_PERI_REG(UART_STATUS_REG(0)) >> UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT) >= 126) ; WRITE_PERI_REG(UART_FIFO_REG(0), c); } -inline static void panicPutStr(const char *c) +void esp_panicPutStr(const char *c) { int x = 0; while (c[x] != 0) { - panicPutChar(c[x]); + esp_panicPutChar(c[x]); x++; } } -inline static void panicPutHex(int a) +void esp_panicPutHex(int a) { int x; int c; for (x = 0; x < 8; x++) { c = (a >> 28) & 0xf; if (c < 10) { - panicPutChar('0' + c); + esp_panicPutChar('0' + c); } else { - panicPutChar('a' + c - 10); + esp_panicPutChar('a' + c - 10); } a <<= 4; } } -inline static void panicPutDec(int a) +void esp_panicPutDec(int a) { int n1, n2; n1 = a % 10; n2 = a / 10; if (n2 == 0) { - panicPutChar(' '); + esp_panicPutChar(' '); } else { - panicPutChar(n2 + '0'); + esp_panicPutChar(n2 + '0'); } - panicPutChar(n1 + '0'); + esp_panicPutChar(n1 + '0'); } #else //No printing wanted. Stub out these functions. -inline static void panicPutChar(char c) { } -inline static void panicPutStr(const char *c) { } -inline static void panicPutHex(int a) { } -inline static void panicPutDec(int a) { } +void esp_panicPutChar(char c) { } +void esp_panicPutStr(const char *c) { } +void esp_panicPutHex(int a) { } +void esp_panicPutDec(int a) { } #endif void __attribute__((weak)) vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName ) { - panicPutStr("***ERROR*** A stack overflow in task "); - panicPutStr((char *)pcTaskName); - panicPutStr(" has been detected.\r\n"); + esp_panicPutStr("***ERROR*** A stack overflow in task "); + esp_panicPutStr((char *)pcTaskName); + esp_panicPutStr(" has been detected.\r\n"); abort(); } @@ -161,39 +162,39 @@ void panicHandler(XtExcFrame *frame) reason = reasons[regs[20]]; } haltOtherCore(); - panicPutStr("Guru Meditation Error: Core "); - panicPutDec(xPortGetCoreID()); - panicPutStr(" panic'ed ("); + esp_panicPutStr("Guru Meditation Error: Core "); + esp_panicPutDec(xPortGetCoreID()); + esp_panicPutStr(" panic'ed ("); if (!abort_called) { - panicPutStr(reason); - panicPutStr(")\r\n"); + esp_panicPutStr(reason); + esp_panicPutStr(")\r\n"); if (regs[20]==PANIC_RSN_DEBUGEXCEPTION) { int debugRsn; asm("rsr.debugcause %0":"=r"(debugRsn)); - panicPutStr("Debug exception reason: "); - if (debugRsn&XCHAL_DEBUGCAUSE_ICOUNT_MASK) panicPutStr("SingleStep "); - if (debugRsn&XCHAL_DEBUGCAUSE_IBREAK_MASK) panicPutStr("HwBreakpoint "); + esp_panicPutStr("Debug exception reason: "); + if (debugRsn&XCHAL_DEBUGCAUSE_ICOUNT_MASK) esp_panicPutStr("SingleStep "); + if (debugRsn&XCHAL_DEBUGCAUSE_IBREAK_MASK) esp_panicPutStr("HwBreakpoint "); if (debugRsn&XCHAL_DEBUGCAUSE_DBREAK_MASK) { //Unlike what the ISA manual says, this core seemingly distinguishes from a DBREAK //reason caused by watchdog 0 and one caused by watchdog 1 by setting bit 8 of the //debugcause if the cause is watchdog 1 and clearing it if it's watchdog 0. if (debugRsn&(1<<8)) { #if CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK - panicPutStr("Stack canary watchpoint triggered "); + esp_panicPutStr("Stack canary watchpoint triggered "); #else - panicPutStr("Watchpoint 1 triggered "); + esp_panicPutStr("Watchpoint 1 triggered "); #endif } else { - panicPutStr("Watchpoint 0 triggered "); + esp_panicPutStr("Watchpoint 0 triggered "); } } - if (debugRsn&XCHAL_DEBUGCAUSE_BREAK_MASK) panicPutStr("BREAK instr "); - if (debugRsn&XCHAL_DEBUGCAUSE_BREAKN_MASK) panicPutStr("BREAKN instr "); - if (debugRsn&XCHAL_DEBUGCAUSE_DEBUGINT_MASK) panicPutStr("DebugIntr "); - panicPutStr("\r\n"); + if (debugRsn&XCHAL_DEBUGCAUSE_BREAK_MASK) esp_panicPutStr("BREAK instr "); + if (debugRsn&XCHAL_DEBUGCAUSE_BREAKN_MASK) esp_panicPutStr("BREAKN instr "); + if (debugRsn&XCHAL_DEBUGCAUSE_DEBUGINT_MASK) esp_panicPutStr("DebugIntr "); + esp_panicPutStr("\r\n"); } } else { - panicPutStr("abort)\r\n"); + esp_panicPutStr("abort)\r\n"); } if (esp_cpu_in_ocd_debug_mode()) { @@ -219,25 +220,25 @@ void xt_unhandled_exception(XtExcFrame *frame) int x; haltOtherCore(); - panicPutStr("Guru Meditation Error of type "); + esp_panicPutStr("Guru Meditation Error of type "); x = regs[20]; if (x < 40) { - panicPutStr(edesc[x]); + esp_panicPutStr(edesc[x]); } else { - panicPutStr("Unknown"); + esp_panicPutStr("Unknown"); } - panicPutStr(" occurred on core "); - panicPutDec(xPortGetCoreID()); + esp_panicPutStr(" occurred on core "); + esp_panicPutDec(xPortGetCoreID()); if (esp_cpu_in_ocd_debug_mode()) { - panicPutStr(" at pc="); - panicPutHex(regs[1]); - panicPutStr(". Setting bp and returning..\r\n"); + esp_panicPutStr(" at pc="); + esp_panicPutHex(regs[1]); + esp_panicPutStr(". Setting bp and returning..\r\n"); //Stick a hardware breakpoint on the address the handler returns to. This way, the OCD debugger //will kick in exactly at the context the error happened. setFirstBreakpoint(regs[1]); return; } - panicPutStr(". Exception was unhandled.\r\n"); + esp_panicPutStr(". Exception was unhandled.\r\n"); commonErrorHandler(frame); } @@ -292,16 +293,16 @@ static void putEntry(uint32_t pc, uint32_t sp) if (pc & 0x80000000) { pc = (pc & 0x3fffffff) | 0x40000000; } - panicPutStr(" 0x"); - panicPutHex(pc); - panicPutStr(":0x"); - panicPutHex(sp); + esp_panicPutStr(" 0x"); + esp_panicPutHex(pc); + esp_panicPutStr(":0x"); + esp_panicPutHex(sp); } static void doBacktrace(XtExcFrame *frame) { uint32_t i = 0, pc = frame->pc, sp = frame->a1; - panicPutStr("\nBacktrace:"); + esp_panicPutStr("\r\nBacktrace:"); /* Do not check sanity on first entry, PC could be smashed. */ putEntry(pc, sp); pc = frame->a0; @@ -317,7 +318,7 @@ static void doBacktrace(XtExcFrame *frame) break; } } - panicPutStr("\n\n"); + esp_panicPutStr("\r\n\r\n"); } /* @@ -341,18 +342,18 @@ static void commonErrorHandler(XtExcFrame *frame) the register window is no longer useful. */ if (!abort_called) { - panicPutStr("Register dump:\r\n"); + esp_panicPutStr("Register dump:\r\n"); for (x = 0; x < 24; x += 4) { for (y = 0; y < 4; y++) { if (sdesc[x + y][0] != 0) { - panicPutStr(sdesc[x + y]); - panicPutStr(": 0x"); - panicPutHex(regs[x + y + 1]); - panicPutStr(" "); + esp_panicPutStr(sdesc[x + y]); + esp_panicPutStr(": 0x"); + esp_panicPutHex(regs[x + y + 1]); + esp_panicPutStr(" "); } + esp_panicPutStr("\r\n"); } - panicPutStr("\r\n"); } } @@ -361,19 +362,27 @@ static void commonErrorHandler(XtExcFrame *frame) #if CONFIG_ESP32_PANIC_GDBSTUB disableAllWdts(); - panicPutStr("Entering gdb stub now.\r\n"); + esp_panicPutStr("Entering gdb stub now.\r\n"); esp_gdbstub_panic_handler(frame); -#elif CONFIG_ESP32_PANIC_PRINT_REBOOT || CONFIG_ESP32_PANIC_SILENT_REBOOT - panicPutStr("Rebooting...\r\n"); +#else +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + esp_core_dump_to_flash(frame); +#endif +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART && !CONFIG_ESP32_PANIC_SILENT_REBOOT + esp_core_dump_to_uart(frame); +#endif +#if CONFIG_ESP32_PANIC_PRINT_REBOOT || CONFIG_ESP32_PANIC_SILENT_REBOOT + esp_panicPutStr("Rebooting...\r\n"); for (x = 0; x < 100; x++) { ets_delay_us(1000); } software_reset(); #else disableAllWdts(); - panicPutStr("CPU halted.\r\n"); + esp_panicPutStr("CPU halted.\r\n"); while (1); #endif +#endif } diff --git a/components/espcoredump/espcoredump.py b/components/espcoredump/espcoredump.py new file mode 100755 index 0000000000..065b323aa1 --- /dev/null +++ b/components/espcoredump/espcoredump.py @@ -0,0 +1,1013 @@ +#!/usr/bin/env python +# +# ESP32 core dump Utility + +import sys +import os +import argparse +import subprocess +import tempfile +import struct +import array +import errno + +try: + import esptool +except ImportError: + idf_path = os.getenv('IDF_PATH') + if idf_path is None: + print "Esptool is not found! Install it or set proper $IDF_PATH in environment." + sys.exit(2) + sys.path.append('%s/components/esptool_py/esptool' % idf_path) + import esptool + +__version__ = "0.1-dev" + +ESP32_COREDUMP_HDR_FMT = '<4L' +ESP32_COREDUMP_FLASH_MAGIC_START = 0xDEADBEEF +ESP32_COREDUMP_FLASH_MAGIC_END = 0xACDCFEED + + +class Struct(object): + def __init__(self, buf=None): + if buf is None: + buf = b'\0' * self.sizeof() + fields = struct.unpack(self.__class__.fmt, buf[:self.sizeof()]) + self.__dict__.update(zip(self.__class__.fields, fields)) + + def sizeof(self): + return struct.calcsize(self.__class__.fmt) + + def dump(self): + keys = self.__class__.fields + if sys.version_info > (3, 0): + # Convert strings into bytearrays if this is Python 3 + for k in keys: + if type(self.__dict__[k]) is str: + self.__dict__[k] = bytearray(self.__dict__[k], encoding='ascii') + return struct.pack(self.__class__.fmt, *(self.__dict__[k] for k in keys)) + + def __str__(self): + keys = self.__class__.fields + return (self.__class__.__name__ + "({" + + ", ".join("%s:%r" % (k, self.__dict__[k]) for k in keys) + + "})") + + +class Elf32FileHeader(Struct): + """ELF32 File header""" + fields = ("e_ident", + "e_type", + "e_machine", + "e_version", + "e_entry", + "e_phoff", + "e_shoff", + "e_flags", + "e_ehsize", + "e_phentsize", + "e_phnum", + "e_shentsize", + "e_shnum", + "e_shstrndx") + fmt = "<16sHHLLLLLHHHHHH" + + def __init__(self, buf=None): + super(Elf32FileHeader, self).__init__(buf) + if buf is None: + # Fill in sane ELF header for LSB32 + self.e_ident = "\x7fELF\1\1\1\0\0\0\0\0\0\0\0\0" + self.e_version = ESPCoreDumpFile.EV_CURRENT + self.e_ehsize = self.sizeof() + + +class Elf32ProgramHeader(Struct): + """ELF32 Program Header""" + fields = ("p_type", + "p_offset", + "p_vaddr", + "p_paddr", + "p_filesz", + "p_memsz", + "p_flags", + "p_align") + fmt = " 0: + self._read_sections(f, shoff, shstrndx) + else: + self.sections = [] + if phnum > 0: + self._read_program_segments(f, phoff, phentsize, phnum) + else: + self.program_segments = [] + + def _read_sections(self, f, section_header_offs, shstrndx): + f.seek(section_header_offs) + section_header = f.read() + LEN_SEC_HEADER = 0x28 + if len(section_header) == 0: + raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs) + if len(section_header) % LEN_SEC_HEADER != 0: + print 'WARNING: Unexpected ELF section header length %04x is not mod-%02x' % (len(section_header),LEN_SEC_HEADER) + + # walk through the section header and extract all sections + section_header_offsets = range(0, len(section_header), LEN_SEC_HEADER) + + def read_section_header(offs): + name_offs,sec_type,flags,lma,sec_offs,size = struct.unpack_from("= ps.addr and addr < (ps.addr + seg_len): + raise FatalError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % + (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) + if (addr + data_sz) > ps.addr and (addr + data_sz) <= (ps.addr + seg_len): + raise FatalError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % + (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) + # append + self.program_segments.append(ESPCoreDumpSegment(addr, data, type, flags)) + + # currently dumps only program segments. + # dumping sections is not supported yet + def dump(self, f): + print "dump to '%s'" % f + # write ELF header + ehdr = Elf32FileHeader() + ehdr.e_type = self.e_type + ehdr.e_machine = self.e_machine + ehdr.e_entry = 0 + ehdr.e_phoff = ehdr.sizeof() + ehdr.e_shoff = 0 + ehdr.e_flags = 0 + ehdr.e_phentsize = Elf32ProgramHeader().sizeof() + ehdr.e_phnum = len(self.program_segments) + ehdr.e_shentsize = 0 + ehdr.e_shnum = 0 + ehdr.e_shstrndx = self.SHN_UNDEF + f.write(ehdr.dump()) + # write program header table + cur_off = ehdr.e_ehsize + ehdr.e_phnum * ehdr.e_phentsize +# print "" % (ehdr.e_ehsize, ehdr.e_phnum, ehdr.e_phentsize) + for i in range(len(self.program_segments)): + print "dump header for seg '%s'" % self.program_segments[i] + phdr = Elf32ProgramHeader() + phdr.p_type = self.program_segments[i].type + phdr.p_offset = cur_off + phdr.p_vaddr = self.program_segments[i].addr + phdr.p_paddr = phdr.p_vaddr # TODO + phdr.p_filesz = len(self.program_segments[i].data) + phdr.p_memsz = phdr.p_filesz # TODO + phdr.p_flags = self.program_segments[i].flags + phdr.p_align = 0 # TODO +# print "header '%s'" % phdr + f.write(phdr.dump()) + cur_off += phdr.p_filesz + # write program segments + for i in range(len(self.program_segments)): + print "dump seg '%s'" % self.program_segments[i] + f.write(self.program_segments[i].data) + + +class ESPCoreDumpError(RuntimeError): + """ + TBD + """ + def __init__(self, message): + super(ESPCoreDumpError, self).__init__(message) + + +class ESPCoreDumpLoaderError(ESPCoreDumpError): + """ + TBD + """ + def __init__(self, message): + super(ESPCoreDumpLoaderError, self).__init__(message) + + +class ESPCoreDumpLoader(object): + """ + TBD + """ + FLASH_READ_BLOCK_SZ = 0x2000 + def __init__(self, off, path=None, chip='esp32', port=None, baud=None): +# print "esptool.__file__ %s" % esptool.__file__ + if not path: + self.path = esptool.__file__ + self.path = self.path[:-1] + else: + self.path = path + self.port = port + self.baud = baud + self.chip = chip + self.fcores = [] + self.fgdbcore = None + self._load_coredump(off) + + def _load_coredump(self, off): + args = [self.path, '-c', self.chip] + if self.port: + args.extend(['-p', self.port]) + if self.baud: + args.extend(['-b', str(self.baud)]) + read_sz = self.FLASH_READ_BLOCK_SZ + read_off = off + args.extend(['read_flash', str(read_off), str(read_sz), '']) + try: + dump_sz = 0 + tot_len = 0 + while True: + fhnd,fname = tempfile.mkstemp() +# print "tmpname %s" % fname +# os.close(fhnd) + args[-1] = fname + et_out = subprocess.check_output(args) + print et_out + # data = os.fdopen(fhnd, 'r').read(sz) + self.fcores.append(os.fdopen(fhnd, 'r')) + if dump_sz == 0: + # read dump length from the first block + dump_sz = self._read_core_dump_length(self.fcores[0]) + tot_len += read_sz + if tot_len >= dump_sz: + break + read_off += read_sz + if dump_sz - tot_len >= self.FLASH_READ_BLOCK_SZ: + read_sz = self.FLASH_READ_BLOCK_SZ + else: + read_sz = dump_sz - tot_len + args[-3] = str(read_off) + args[-2] = str(read_sz) + + except subprocess.CalledProcessError as e: + print "esptool script execution failed with err %d" % e.returncode + print "Command ran: '%s'" % e.cmd + print "Command out:" + print e.output + self.cleanup() + return [] + + def _read_core_dump_length(self, f): + global ESP32_COREDUMP_HDR_FMT + global ESP32_COREDUMP_FLASH_MAGIC_START + print "Read core dump header from '%s'" % f.name + data = f.read(4*4) + mag1,tot_len,task_num,tcbsz = struct.unpack_from(ESP32_COREDUMP_HDR_FMT, data) + if mag1 != ESP32_COREDUMP_FLASH_MAGIC_START: + raise ESPCoreDumpLoaderError("Invalid start magic number!") + return tot_len + + def remove_tmp_file(self, fname): + try: + os.remove(fname) + except OSError as e: + if e.errno != errno.ENOENT: + print "Warning failed to remove temp file '%s'!" % fname + + def _get_registers_from_stack(self, data, grows_down): + # from "gdb/xtensa-tdep.h" + # typedef struct + # { + #0 xtensa_elf_greg_t pc; + #1 xtensa_elf_greg_t ps; + #2 xtensa_elf_greg_t lbeg; + #3 xtensa_elf_greg_t lend; + #4 xtensa_elf_greg_t lcount; + #5 xtensa_elf_greg_t sar; + #6 xtensa_elf_greg_t windowstart; + #7 xtensa_elf_greg_t windowbase; + #8..63 xtensa_elf_greg_t reserved[8+48]; + #64 xtensa_elf_greg_t ar[64]; + # } xtensa_elf_gregset_t; + REG_PC_IDX=0 + REG_PS_IDX=1 + REG_LB_IDX=2 + REG_LE_IDX=3 + REG_LC_IDX=4 + REG_SAR_IDX=5 + REG_WS_IDX=6 + REG_WB_IDX=7 + REG_AR_START_IDX=64 + REG_AR_NUM=64 + # FIXME: acc to xtensa_elf_gregset_t number of regs must be 128, + # but gdb complanis when it less then 129 + REG_NUM=129 + + XT_SOL_EXIT=0 + XT_SOL_PC=1 + XT_SOL_PS=2 + XT_SOL_NEXT=3 + XT_SOL_AR_START=4 + XT_SOL_AR_NUM=4 + XT_SOL_FRMSZ=8 + + XT_STK_EXIT=0 + XT_STK_PC=1 + XT_STK_PS=2 + XT_STK_AR_START=3 + XT_STK_AR_NUM=16 + XT_STK_SAR=19 + XT_STK_EXCCAUSE=20 + XT_STK_EXCVADDR=21 + XT_STK_LBEG=22 + XT_STK_LEND=23 + XT_STK_LCOUNT=24 + XT_STK_FRMSZ=25 + + regs = [0] * REG_NUM + # TODO: support for growing up stacks + if not grows_down: + print "Growing up stacks are not supported for now!" + return regs + # for i in range(REG_NUM): + # regs[i] = i + # return regs + ex_struct = "<%dL" % XT_STK_FRMSZ + if len(data) < struct.calcsize(ex_struct): + print "Too small stack to keep frame: %d bytes!" % len(data) + return regs + + stack = struct.unpack(ex_struct, data[:struct.calcsize(ex_struct)]) + # Stack frame type indicator is always the first item + rc = stack[XT_STK_EXIT] + if rc != 0: + print "EXCSTACKFRAME %d" % rc + regs[REG_PC_IDX] = stack[XT_STK_PC] + regs[REG_PS_IDX] = stack[XT_STK_PS] + for i in range(XT_STK_AR_NUM): + regs[REG_AR_START_IDX + i] = stack[XT_STK_AR_START + i] + regs[REG_SAR_IDX] = stack[XT_STK_SAR] + regs[REG_LB_IDX] = stack[XT_STK_LBEG] + regs[REG_LE_IDX] = stack[XT_STK_LEND] + regs[REG_LC_IDX] = stack[XT_STK_LCOUNT] + print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % ( + regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0], + regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3]) + else: + print "SOLSTACKFRAME %d" % rc + regs[REG_PC_IDX] = stack[XT_SOL_PC] + regs[REG_PS_IDX] = stack[XT_SOL_PS] + for i in range(XT_SOL_AR_NUM): + regs[REG_AR_START_IDX + i] = stack[XT_SOL_AR_START + i] + nxt = stack[XT_SOL_NEXT] + print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % ( + regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0], + regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3]) + + # TODO: remove magic hack with saved PC to get proper value + regs[REG_PC_IDX] = ((regs[REG_PC_IDX] & 0x3FFFFFFF) | 0x40000000) + if regs[REG_PC_IDX] & 0x80000000: + regs[REG_PC_IDX] = (regs[REG_PC_IDX] & 0x3fffffff) | 0x40000000; + if regs[REG_AR_START_IDX + 0] & 0x80000000: + regs[REG_AR_START_IDX + 0] = (regs[REG_AR_START_IDX + 0] & 0x3fffffff) | 0x40000000; + return regs + + def cleanup(self): +# if self.fgdbcore: +# self.fgdbcore.close() +# self.remove_tmp_file(self.fgdbcore.name) + for f in self.fcores: + if f: + f.close() + self.remove_tmp_file(f.name) + + def get_corefile_from_flash(self): + """ TBD + """ + global ESP32_COREDUMP_HDR_FMT + ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT) + ESP32_COREDUMP_TSK_HDR_FMT = ' stack_top: + stack_len = stack_end - stack_top + stack_base = stack_top + else: + stack_len = stack_top - stack_end + stack_base = stack_end + print "tcb_addr=%x, stack_top=%x, stack_end=%x, stack_len=%d" % (tcb_addr,stack_top,stack_end,stack_len) + + stack_len_aligned = stack_len + if stack_len_aligned % 4: + stack_len_aligned = 4*(stack_len_aligned/4 + 1) + + core_off += ESP32_COREDUMP_TSK_HDR_SZ + print "Read task[%d] TCB" % i + data = self.read_flash(core_off, tcbsz_aligned, flash_progress) + if tcbsz != tcbsz_aligned: + core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + else: + core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + # print "tcb=%s" % data + core_off += tcbsz_aligned + print "Read task[%d] stack %d bytes" % (i,stack_len) + data = self.read_flash(core_off, stack_len_aligned, flash_progress) + # print "stk=%s" % data + if stack_len != stack_len_aligned: + data = data[:stack_len - stack_len_aligned] + core_elf.add_program_segment(stack_base, data, ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + core_off += stack_len_aligned + + task_regs = self._get_registers_from_stack(data, stack_end > stack_top) + prstatus = XtensaPrStatus() + prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task + prstatus.pr_pid = i # TODO: use pid assigned by OS + note = Elf32NoteDesc("CORE", 1, prstatus.dump() + struct.pack("<%dL" % len(task_regs), *task_regs)).dump() + print "NOTE_LEN %d" % len(note) + notes += note + + print "Read core dump endmarker" + data = self.read_flash(core_off, ESP32_COREDUMP_MAGIC_SZ, flash_progress) + mag = struct.unpack_from(ESP32_COREDUMP_MAGIC_FMT, data) + print "mag2=%x" % (mag) + + # add notes + core_elf.add_program_segment(0, notes, ESPCoreDumpFile.PT_NOTE, 0) + + core_elf.e_type = ESPCoreDumpFile.ET_CORE + core_elf.e_machine = ESPCoreDumpFile.EM_XTENSA + fhnd,fname = tempfile.mkstemp() + self.fgdbcore = os.fdopen(fhnd, 'wb') + core_elf.dump(self.fgdbcore) + return fname + ######################### END ########################### + + def read_flash(self, off, sz, progress=None): +# print "read_flash: %x %d" % (off, sz) + id = off / self.FLASH_READ_BLOCK_SZ + if id >= len(self.fcores): + return '' + self.fcores[id].seek(off % self.FLASH_READ_BLOCK_SZ) + data = self.fcores[id].read(sz) +# print "data1: %s" % data + return data + +class GDBMIOutRecordHandler(object): + """ TBD + """ + TAG = '' + + def __init__(self, f, verbose=False): + self.verbose = verbose + + def execute(self, ln): + if self.verbose: + print "%s.execute '%s'" % (self.__class__.__name__, ln) + + +class GDBMIOutStreamHandler(GDBMIOutRecordHandler): + """ TBD + """ + def __init__(self, f, verbose=False): + super(GDBMIOutStreamHandler, self).__init__(None, verbose) + self.func = f + + def execute(self, ln): + GDBMIOutRecordHandler.execute(self, ln) + if self.func: + # remove TAG / quotes and replace c-string \n with actual NL + self.func(ln[1:].strip('"').replace('\\n', '\n').replace('\\t', '\t')) + + +class GDBMIResultHandler(GDBMIOutRecordHandler): + """ TBD + """ + TAG = '^' + RC_DONE = 'done' + RC_RUNNING = 'running' + RC_CONNECTED = 'connected' + RC_ERROR = 'error' + RC_EXIT = 'exit' + + def __init__(self, verbose=False): + super(GDBMIResultHandler, self).__init__(None, verbose) + self.result_class = None + self.result_str = None + + def _parse_rc(self, ln, rc): + rc_str = "{0}{1}".format(self.TAG, rc) + if ln.startswith(rc_str): + self.result_class = rc + sl = len(rc_str) + if len(ln) > sl: + self.result_str = ln[sl:] + if self.result_str.startswith(','): + self.result_str = self.result_str[1:] + else: + print "Invalid result format: '%s'" % ln + else: + self.result_str = '' + return True + return False + + def execute(self, ln): + GDBMIOutRecordHandler.execute(self, ln) + if self._parse_rc(ln, self.RC_DONE): + return + if self._parse_rc(ln, self.RC_RUNNING): + return + if self._parse_rc(ln, self.RC_CONNECTED): + return + if self._parse_rc(ln, self.RC_ERROR): + return + if self._parse_rc(ln, self.RC_EXIT): + return + print "Unknown result: '%s'" % ln + + +class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler): + """ TBD + """ + TAG = '~' + + +def dbg_corefile(args): + """ TBD + """ + print "dbg_corefile %s %s %s" % (args.gdb, args.prog, args.core) + loader = None + if not args.core: + loader = ESPCoreDumpLoader(args.off, port=args.port) + core_fname = loader.get_corefile_from_flash() + loader.fgdbcore.close() +# core_fname = 'esp_core.elf' + else: + core_fname = args.core +# print core_fname +# return + + p = subprocess.Popen( + bufsize = 0, + args = [args.gdb, + '--nw', # ignore .gdbinit + '--core=%s' % core_fname, # core file + args.prog], + stdin = None, stdout = None, stderr = None, + close_fds = True + ) + p.wait() + if loader: + loader.remove_tmp_file(loader.fgdbcore.name) + loader.cleanup() + print 'Done!' + + +def info_corefile(args): +# def info_corefile(args): + """ TBD + """ + print "info_corefile %s %s %s" % (args.gdb, args.prog, args.core) + + + def gdbmi_console_stream_handler(ln): + # print ln + sys.stdout.write(ln) + sys.stdout.flush() + + + def gdbmi_read2prompt(f, out_handlers=None): + """ TBD + """ + while True: + ln = f.readline().rstrip(' \n') + # print "LINE='{0}'".format(ln) + if ln == '(gdb)': + break + elif len(ln) == 0: + break + elif out_handlers: + for h in out_handlers: + if ln.startswith(out_handlers[h].TAG): + out_handlers[h].execute(ln) + break + + loader = None + if not args.core: + loader = ESPCoreDumpLoader(args.off, port=args.port) + core_fname = loader.get_corefile_from_flash() + loader.fgdbcore.close() + else: + core_fname = args.core + + handlers = {} + handlers[GDBMIResultHandler.TAG] = GDBMIResultHandler(verbose=False) + handlers[GDBMIStreamConsoleHandler.TAG] = GDBMIStreamConsoleHandler(None, verbose=False) + p = subprocess.Popen( + bufsize = 0, + args = [args.gdb, + '--quiet', # inhibit dumping info at start-up + '--nx', # inhibit window interface + '--nw', # ignore .gdbinit + '--interpreter=mi2', # use GDB/MI v2 + '--core=%s' % core_fname, # core file + args.prog], +# ], + stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, + close_fds = True + ) + + gdbmi_read2prompt(p.stdout, handlers) + exe_elf = ESPCoreDumpFile(args.prog) + core_elf = ESPCoreDumpFile(core_fname) + merged_segs = []#[(s, 0) for s in exe_elf.sections if s.flags & (esptool.ELFSection.SHF_ALLOC | esptool.ELFSection.SHF_WRITE)] + for s in exe_elf.sections: + merged = False + for ps in core_elf.program_segments: + if ps.addr <= s.addr and ps.addr + len(ps.data) >= s.addr: + # sec: |XXXXXXXXXX| + # seg: |...XXX.............| + seg_addr = ps.addr + if ps.addr + len(ps.data) <= s.addr + len(s.data): + # sec: |XXXXXXXXXX| + # seg: |XXXXXXXXXXX...| + # merged: |XXXXXXXXXXXXXX| + seg_len = len(s.data) + (s.addr - ps.addr) + else: + # sec: |XXXXXXXXXX| + # seg: |XXXXXXXXXXXXXXXXX| + # merged: |XXXXXXXXXXXXXXXXX| + seg_len = len(ps.data) + merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True)) + merged = True + elif ps.addr >= s.addr and ps.addr <= s.addr + len(s.data): + # sec: |XXXXXXXXXX| + # seg: |...XXX.............| + seg_addr = s.addr + if (ps.addr + len(ps.data)) >= (s.addr + len(s.data)): + # sec: |XXXXXXXXXX| + # seg: |..XXXXXXXXXXX| + # merged: |XXXXXXXXXXXXX| + seg_len = len(s.data) + (ps.addr + len(ps.data)) - (s.addr + len(s.data)) + else: + # sec: |XXXXXXXXXX| + # seg: |XXXXXX| + # merged: |XXXXXXXXXX| + seg_len = len(s.data) + merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True)) + merged = True + + if not merged: + merged_segs.append((s.name, s.addr, len(s.data), s.attr_str(), False)) +# merged_segs.append(('None', ps.addr, len(ps.data), 'None')) + + print "===============================================================" + print "==================== ESP32 CORE DUMP START ====================" + + handlers[GDBMIResultHandler.TAG].result_class = None + handlers[GDBMIStreamConsoleHandler.TAG].func = gdbmi_console_stream_handler + print "\n================== CURRENT THREAD REGISTERS ===================" + p.stdin.write("-interpreter-exec console \"info registers\"\n") + gdbmi_read2prompt(p.stdout, handlers) + if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: + print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) + print "\n==================== CURRENT THREAD STACK =====================" + p.stdin.write("-interpreter-exec console \"bt\"\n") + gdbmi_read2prompt(p.stdout, handlers) + if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: + print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) + print "\n======================== THREADS INFO =========================" + p.stdin.write("-interpreter-exec console \"info threads\"\n") + gdbmi_read2prompt(p.stdout, handlers) + if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: + print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) + print "\n======================= MEMORY REGIONS ========================" + print "Name Address Size Attrs" + for ms in merged_segs: + print "%s 0x%x 0x%x %s" % (ms[0], ms[1], ms[2], ms[3]) + if args.print_mem: + print "\n====================== MEMORY CONTENTS ========================" + for ms in merged_segs: +# if ms[3].find('W') == -1: + if not ms[4]: + continue + print "%s 0x%x 0x%x %s" % (ms[0], ms[1], ms[2], ms[3]) + p.stdin.write("-interpreter-exec console \"x/%dx 0x%x\"\n" % (ms[2]/4, ms[1])) + gdbmi_read2prompt(p.stdout, handlers) + if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: + print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) + + print "\n===================== ESP32 CORE DUMP END =====================" + print "===============================================================" + + p.terminate() + p.stdin.close() + p.stdout.close() + if loader: + loader.remove_tmp_file(loader.fgdbcore.name) + loader.cleanup() + + +def main(): + parser = argparse.ArgumentParser(description='coredumper.py v%s - ESP32 Core Dump Utility' % __version__, prog='coredumper') + + parser.add_argument('--chip', '-c', + help='Target chip type', + choices=['auto', 'esp32'], + default=os.environ.get('ESPTOOL_CHIP', 'auto')) + + parser.add_argument( + '--port', '-p', + help='Serial port device', + default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT)) + + parser.add_argument( + '--baud', '-b', + help='Serial port baud rate used when flashing/reading', + type=int, + default=os.environ.get('ESPTOOL_BAUD', esptool.ESPLoader.ESP_ROM_BAUD)) + +# parser.add_argument( +# '--no-stub', +# help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.", +# action='store_true') + + subparsers = parser.add_subparsers( + dest='operation', + help='Run coredumper {command} -h for additional help') + + parser_debug_coredump = subparsers.add_parser( + 'dbg_corefile', + help='Starts GDB debugging session with specified corefile') + parser_debug_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb') + parser_debug_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str) + parser_debug_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000) + parser_debug_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str) + + parser_info_coredump = subparsers.add_parser( + 'info_corefile', + help='Print core dump info from file') + parser_info_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb') + parser_info_coredump.add_argument('--print-mem', '-m', help='Print memory dump', action='store_true') + parser_info_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str) + parser_info_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000) + parser_info_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str) + + # internal sanity check - every operation matches a module function of the same name + for operation in subparsers.choices.keys(): + assert operation in globals(), "%s should be a module function" % operation + + args = parser.parse_args() + + print 'coredumper.py v%s' % __version__ + + # operation function can take 1 arg (args), 2 args (esp, arg) + # or be a member function of the ESPLoader class. + + operation_func = globals()[args.operation] + operation_func(args) + + +if __name__ == '__main__': + try: + main() + except ESPCoreDumpError as e: + print '\nA fatal error occurred: %s' % e + sys.exit(2) diff --git a/components/freertos/include/freertos/FreeRTOS.h b/components/freertos/include/freertos/FreeRTOS.h index 4c60308f78..0e93acf271 100644 --- a/components/freertos/include/freertos/FreeRTOS.h +++ b/components/freertos/include/freertos/FreeRTOS.h @@ -863,8 +863,8 @@ typedef struct xSTATIC_TCB void *pxDummy6; uint8_t ucDummy7[ configMAX_TASK_NAME_LEN ]; UBaseType_t uxDummyCoreId; - #if ( portSTACK_GROWTH > 0 ) - void *pxDummy8; + #if ( portSTACK_GROWTH > 0 || configENABLE_TASK_SNAPSHOT == 1 ) + void *pxDummy8; #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxDummy9; diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index 4f1012657a..2bedaa02e8 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -268,7 +268,9 @@ #define configXT_BOARD 1 /* Board mode */ #define configXT_SIMULATOR 0 - +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH | CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH +#define configENABLE_TASK_SNAPSHOT 1 +#endif #endif /* FREERTOS_CONFIG_H */ diff --git a/components/freertos/include/freertos/task.h b/components/freertos/include/freertos/task.h index f7b9181fcf..c6896e5386 100644 --- a/components/freertos/include/freertos/task.h +++ b/components/freertos/include/freertos/task.h @@ -181,6 +181,18 @@ typedef struct xTASK_STATUS uint16_t usStackHighWaterMark; /* The minimum amount of stack space that has remained for the task since the task was created. The closer this value is to zero the closer the task has come to overflowing its stack. */ } TaskStatus_t; +/* + * Used with the uxTaskGetSnapshotAll() function to save memory snapshot of each task in the system. + * We need this struct because TCB_t is defined (hidden) in tasks.c. + */ +typedef struct xTASK_SNAPSHOT +{ + void *pxTCB; /* Address of task control block. */ + StackType_t *pxTopOfStack; /* Points to the location of the last item placed on the tasks stack. */ + StackType_t *pxEndOfStack; /* Points to the end of the stack. pxTopOfStack < pxEndOfStack, stack grows hi2lo + pxTopOfStack > pxEndOfStack, stack grows lo2hi*/ +} TaskSnapshot_t; + /* Possible return values for eTaskConfirmSleepModeStatus(). */ typedef enum { @@ -2173,6 +2185,9 @@ eSleepModeStatus eTaskConfirmSleepModeStatus( void ) PRIVILEGED_FUNCTION; */ void *pvTaskIncrementMutexHeldCount( void ); +/* Used by core dump facility to get list of task handles. */ +UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz ); + #ifdef __cplusplus } #endif diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index 64031cfbca..100b6e470b 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -181,7 +181,7 @@ typedef struct tskTaskControlBlock char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ BaseType_t xCoreID; /*< Core this task is pinned to */ /* If this moves around (other than pcTaskName size changes), please change the define in xtensa_vectors.S as well. */ - #if ( portSTACK_GROWTH > 0 ) + #if ( portSTACK_GROWTH > 0 || configENABLE_TASK_SNAPSHOT == 1 ) StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */ #endif @@ -885,6 +885,12 @@ UBaseType_t x; /* Check the alignment of the calculated top of stack is correct. */ configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); + #if ( configENABLE_TASK_SNAPSHOT == 1 ) + { + /* need stack end for core dumps */ + pxNewTCB->pxEndOfStack = pxTopOfStack; + } +#endif } #else /* portSTACK_GROWTH */ { @@ -4912,6 +4918,102 @@ TickType_t uxReturn; #endif /* configUSE_TASK_NOTIFICATIONS */ +#if ( configENABLE_TASK_SNAPSHOT == 1 ) + + static void prvTaskGetSnapshotsFromList( TaskSnapshot_t *pxTaskSnapshotArray, UBaseType_t *uxTask, const UBaseType_t uxArraySize, List_t *pxList ) + { + TCB_t *pxNextTCB, *pxFirstTCB; + + if( listCURRENT_LIST_LENGTH( pxList ) > ( UBaseType_t ) 0 ) + { + listGET_OWNER_OF_NEXT_ENTRY( pxFirstTCB, pxList ); + do + { + listGET_OWNER_OF_NEXT_ENTRY( pxNextTCB, pxList ); + + if( *uxTask >= uxArraySize ) + break; + + pxTaskSnapshotArray[ *uxTask ].pxTCB = pxNextTCB; + pxTaskSnapshotArray[ *uxTask ].pxTopOfStack = (StackType_t *)pxNextTCB->pxTopOfStack; + #if( portSTACK_GROWTH < 0 ) + { + pxTaskSnapshotArray[ *uxTask ].pxEndOfStack = pxNextTCB->pxEndOfStack; + } + #else + { + pxTaskSnapshotArray[ *uxTask ].pxEndOfStack = pxNextTCB->pxStack; + } + #endif + + (*uxTask)++; + + } while( pxNextTCB != pxFirstTCB ); + } + else + { + mtCOVERAGE_TEST_MARKER(); + } + } + + UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz ) + { + UBaseType_t uxTask = 0, i = 0; + + *pxTcbSz = sizeof(TCB_t); + + //vTaskSuspendAll(); //WARNING: This only suspends one CPU. ToDo: suspend others as well. Mux using taskQueueMutex maybe? + { + /* Fill in an TaskStatus_t structure with information on each + task in the Ready state. */ + i = configMAX_PRIORITIES; + do + { + i--; + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &( pxReadyTasksLists[ i ] ) ); + } while( i > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ + + /* Fill in an TaskStatus_t structure with information on each + task in the Blocked state. */ + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, ( List_t * ) pxDelayedTaskList ); + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, ( List_t * ) pxOverflowDelayedTaskList ); + + #if( INCLUDE_vTaskDelete == 1 ) + { + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &xTasksWaitingTermination ); + } + #endif + + #if ( INCLUDE_vTaskSuspend == 1 ) + { + prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &xSuspendedTaskList ); + } + #endif + } + //( void ) xTaskResumeAll(); +#if 0 + /* Convention: First num_cpus slots will have current task for that cpu. */ + for (i = 0; i < portNUM_PROCESSORS; i++) { + if (pxCurrentTCB[i] == NULL || pxCurrentTCB == pxTaskSnapshotArray[i]) { + continue; + } else { + UBaseType_t j; + for (j = i; j < uxTask; j++) { + if (pxTaskSnapshotArray[j] == pxCurrentTCB[i]) { + TaskHandle_t tmp = pxTaskSnapshotArray[i]; + pxTaskSnapshotArray[i] = pxTaskSnapshotArray[j]; + pxTaskSnapshotArray[j] = tmp; + break; + } + } + } + } +#endif + return uxTask; + } + +#endif + #ifdef FREERTOS_MODULE_TEST #include "tasks_test_access_functions.h" #endif diff --git a/components/partition_table/Kconfig.projbuild b/components/partition_table/Kconfig.projbuild index 1f019a6e3f..36e435ba2f 100644 --- a/components/partition_table/Kconfig.projbuild +++ b/components/partition_table/Kconfig.projbuild @@ -45,8 +45,10 @@ config PARTITION_TABLE_CUSTOM_PHY_DATA_OFFSET config PARTITION_TABLE_FILENAME string - default partitions_singleapp.csv if PARTITION_TABLE_SINGLE_APP - default partitions_two_ota.csv if PARTITION_TABLE_TWO_OTA + default partitions_singleapp.csv if PARTITION_TABLE_SINGLE_APP && !ESP32_ENABLE_COREDUMP_TO_FLASH + default partitions_singleapp_coredump.csv if PARTITION_TABLE_SINGLE_APP && ESP32_ENABLE_COREDUMP_TO_FLASH + default partitions_two_ota.csv if PARTITION_TABLE_TWO_OTA && !ESP32_ENABLE_COREDUMP_TO_FLASH + default partitions_two_ota_coredump.csv if PARTITION_TABLE_TWO_OTA && ESP32_ENABLE_COREDUMP_TO_FLASH default PARTITION_TABLE_CUSTOM_FILENAME if PARTITION_TABLE_CUSTOM config APP_OFFSET diff --git a/components/partition_table/partitions_singleapp_coredump.csv b/components/partition_table/partitions_singleapp_coredump.csv new file mode 100644 index 0000000000..96719a425a --- /dev/null +++ b/components/partition_table/partitions_singleapp_coredump.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000 +phy_init, data, phy, 0xf000, 0x1000 +factory, app, factory, 0x10000, 1M +coredump, data, 3, , 64K diff --git a/components/partition_table/partitions_two_ota_coredump.csv b/components/partition_table/partitions_two_ota_coredump.csv new file mode 100644 index 0000000000..3d2b34ec9a --- /dev/null +++ b/components/partition_table/partitions_two_ota_coredump.csv @@ -0,0 +1,9 @@ +# Name, Type, SubType, Offset, Size +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x4000 +otadata, data, ota, 0xd000, 0x2000 +phy_init, data, phy, 0xf000, 0x1000 +factory, 0, 0, 0x10000, 1M +coredump, data, 3, , 64K +ota_0, 0, ota_0, , 1M +ota_1, 0, ota_1, , 1M diff --git a/components/spi_flash/cache_utils.c b/components/spi_flash/cache_utils.c index f30db80cd8..748ccf9042 100644 --- a/components/spi_flash/cache_utils.c +++ b/components/spi_flash/cache_utils.c @@ -141,6 +141,29 @@ void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() esp_intr_noniram_enable(); } +void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu_panic() +{ + const uint32_t cpuid = xPortGetCoreID(); + const uint32_t other_cpuid = (cpuid == 0) ? 1 : 0; + + // do not care about other CPU, it was halted upon entering panic handler + spi_flash_disable_cache(other_cpuid, &s_flash_op_cache_state[other_cpuid]); + // Kill interrupts that aren't located in IRAM + esp_intr_noniram_disable(); + // Disable cache on this CPU as well + spi_flash_disable_cache(cpuid, &s_flash_op_cache_state[cpuid]); +} + +void IRAM_ATTR spi_flash_enable_interrupts_caches_panic() +{ + const uint32_t cpuid = xPortGetCoreID(); + + // Re-enable cache on this CPU + spi_flash_restore_cache(cpuid, s_flash_op_cache_state[cpuid]); + // Re-enable non-iram interrupts + esp_intr_noniram_enable(); +} + #else // CONFIG_FREERTOS_UNICORE void spi_flash_init_lock() @@ -172,6 +195,22 @@ void IRAM_ATTR spi_flash_enable_interrupts_caches_and_other_cpu() esp_intr_noniram_enable(); } +void IRAM_ATTR spi_flash_disable_interrupts_caches_and_other_cpu_panic() +{ + // Kill interrupts that aren't located in IRAM + esp_intr_noniram_disable(); + // Disable cache on this CPU as well + spi_flash_disable_cache(0, &s_flash_op_cache_state[0]); +} + +void IRAM_ATTR spi_flash_enable_interrupts_caches_panic() +{ + // Re-enable cache on this CPU + spi_flash_restore_cache(0, s_flash_op_cache_state[0]); + // Re-enable non-iram interrupts + esp_intr_noniram_enable(); +} + #endif // CONFIG_FREERTOS_UNICORE /** diff --git a/components/spi_flash/cache_utils.h b/components/spi_flash/cache_utils.h index 899a31c651..a40bab0465 100644 --- a/components/spi_flash/cache_utils.h +++ b/components/spi_flash/cache_utils.h @@ -40,5 +40,14 @@ void spi_flash_disable_interrupts_caches_and_other_cpu(); // Enable cache, enable interrupts (to be added in future), resume scheduler void spi_flash_enable_interrupts_caches_and_other_cpu(); +// Disables non-IRAM interrupt handlers on current CPU and caches on both CPUs. +// This function is implied to be called from panic handler +// when non-current CPU is halted and can not execute code from flash. +void spi_flash_disable_interrupts_caches_and_other_cpu_panic(); + +// Enable cache, enable interrupts (to be added in future) on current CPU. +// This function is implied to be called from panic handler +// when non-current CPU is halted and can not execute code from flash. +void spi_flash_enable_interrupts_caches_panic(); #endif //ESP_SPI_FLASH_CACHE_UTILS_H diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c index 993e68a572..7f2d2d4d18 100644 --- a/components/spi_flash/flash_ops.c +++ b/components/spi_flash/flash_ops.c @@ -58,7 +58,35 @@ static spi_flash_counters_t s_flash_stats; #endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS +/* SPI flash access critical section management functions */ +typedef void (*spi_flash_guard_start_func_t)(void); +typedef void (*spi_flash_guard_end_func_t)(void); + +/** + * Structure holding SPI flash access critical section management functions + */ +typedef struct { + spi_flash_guard_start_func_t start; // critical section start func + spi_flash_guard_end_func_t end; // critical section end func +} spi_flash_guard_funcs_t; + +#define FLASH_GUARD_START(_gp_) do{if((_gp_)) (_gp_)->start();}while(0) +#define FLASH_GUARD_END(_gp_) do{if((_gp_)) (_gp_)->end();}while(0) + static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc); +static esp_err_t spi_flash_erase_range_internal(uint32_t start_addr, uint32_t size, const spi_flash_guard_funcs_t *flash_guard); +static esp_err_t spi_flash_write_internal(size_t dst, const void *srcv, size_t size, const spi_flash_guard_funcs_t *flash_guard); +static esp_err_t spi_flash_read_internal(size_t src, void *dstv, size_t size, const spi_flash_guard_funcs_t *flash_guard); + +const DRAM_ATTR spi_flash_guard_funcs_t s_flash_guard_ops = { + .start = spi_flash_disable_interrupts_caches_and_other_cpu, + .end = spi_flash_enable_interrupts_caches_and_other_cpu +}; + +const DRAM_ATTR spi_flash_guard_funcs_t s_flash_guard_panic_ops = { + .start = spi_flash_disable_interrupts_caches_and_other_cpu_panic, + .end = spi_flash_enable_interrupts_caches_panic +}; void spi_flash_init() { @@ -92,6 +120,16 @@ esp_err_t IRAM_ATTR spi_flash_erase_sector(size_t sec) } esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) +{ + return spi_flash_erase_range_internal(start_addr, size, &s_flash_guard_ops); +} + +esp_err_t IRAM_ATTR spi_flash_erase_range_panic(uint32_t start_addr, uint32_t size) +{ + return spi_flash_erase_range_internal(start_addr, size, &s_flash_guard_panic_ops); +} + +static esp_err_t IRAM_ATTR spi_flash_erase_range_internal(uint32_t start_addr, uint32_t size, const spi_flash_guard_funcs_t *flash_guard) { if (start_addr % SPI_FLASH_SEC_SIZE != 0) { return ESP_ERR_INVALID_ARG; @@ -106,7 +144,7 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) size_t end = start + size / SPI_FLASH_SEC_SIZE; const size_t sectors_per_block = BLOCK_ERASE_SIZE / SPI_FLASH_SEC_SIZE; COUNTER_START(); - spi_flash_disable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_START(flash_guard); SpiFlashOpResult rc; rc = spi_flash_unlock(); if (rc == SPI_FLASH_RESULT_OK) { @@ -122,12 +160,22 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) } } } - spi_flash_enable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_END(flash_guard); COUNTER_STOP(erase); return spi_flash_translate_rc(rc); } esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) +{ + return spi_flash_write_internal(dst, srcv, size, &s_flash_guard_ops); +} + +esp_err_t IRAM_ATTR spi_flash_write_panic(size_t dst, const void *srcv, size_t size) +{ + return spi_flash_write_internal(dst, srcv, size, &s_flash_guard_panic_ops); +} + +static esp_err_t IRAM_ATTR spi_flash_write_internal(size_t dst, const void *srcv, size_t size, const spi_flash_guard_funcs_t *flash_guard) { // Out of bound writes are checked in ROM code, but we can give better // error code here @@ -160,9 +208,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) if (left_size > 0) { uint32_t t = 0xffffffff; memcpy(((uint8_t *) &t) + (dst - left_off), srcc, left_size); - spi_flash_disable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_START(flash_guard); rc = SPIWrite(left_off, &t, 4); - spi_flash_enable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_END(flash_guard); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -178,9 +226,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) bool in_dram = true; #endif if (in_dram && (((uintptr_t) srcc) + mid_off) % 4 == 0) { - spi_flash_disable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_START(flash_guard); rc = SPIWrite(dst + mid_off, (const uint32_t *) (srcc + mid_off), mid_size); - spi_flash_enable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_END(flash_guard); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -194,9 +242,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) uint32_t t[8]; uint32_t write_size = MIN(mid_size, sizeof(t)); memcpy(t, srcc + mid_off, write_size); - spi_flash_disable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_START(flash_guard); rc = SPIWrite(dst + mid_off, t, write_size); - spi_flash_enable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_END(flash_guard); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -209,9 +257,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) if (right_size > 0) { uint32_t t = 0xffffffff; memcpy(&t, srcc + right_off, right_size); - spi_flash_disable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_START(flash_guard); rc = SPIWrite(dst + right_off, &t, 4); - spi_flash_enable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_END(flash_guard); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -259,6 +307,16 @@ esp_err_t IRAM_ATTR spi_flash_write_encrypted(size_t dest_addr, const void *src, } esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size) +{ + return spi_flash_read_internal(src, dstv, size, &s_flash_guard_ops); +} + +esp_err_t IRAM_ATTR spi_flash_read_panic(size_t src, void *dstv, size_t size) +{ + return spi_flash_read_internal(src, dstv, size, &s_flash_guard_panic_ops); +} + +static esp_err_t IRAM_ATTR spi_flash_read_internal(size_t src, void *dstv, size_t size, const spi_flash_guard_funcs_t *flash_guard) { // Out of bound reads are checked in ROM code, but we can give better // error code here @@ -271,7 +329,7 @@ esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size) SpiFlashOpResult rc = SPI_FLASH_RESULT_OK; COUNTER_START(); - spi_flash_disable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_START(flash_guard); /* To simplify boundary checks below, we handle small reads separately. */ if (size < 16) { uint32_t t[6]; /* Enough for 16 bytes + 4 on either side for padding. */ @@ -345,7 +403,7 @@ esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size) memcpy(dstc + pad_right_off, t, pad_right_size); } out: - spi_flash_enable_interrupts_caches_and_other_cpu(); + FLASH_GUARD_END(flash_guard); COUNTER_STOP(read); return spi_flash_translate_rc(rc); } diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index b67891ae15..b149e10234 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -69,6 +69,7 @@ typedef enum { ESP_PARTITION_SUBTYPE_DATA_OTA = 0x00, //!< OTA selection partition ESP_PARTITION_SUBTYPE_DATA_PHY = 0x01, //!< PHY init data partition ESP_PARTITION_SUBTYPE_DATA_NVS = 0x02, //!< NVS partition + ESP_PARTITION_SUBTYPE_DATA_COREDUMP = 0x03, //!< COREDUMP partition ESP_PARTITION_SUBTYPE_DATA_ESPHTTPD = 0x80, //!< ESPHTTPD partition ESP_PARTITION_SUBTYPE_DATA_FAT = 0x81, //!< FAT partition diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index bb3ec39b45..e78c389b4e 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -173,6 +173,56 @@ void spi_flash_munmap(spi_flash_mmap_handle_t handle); */ void spi_flash_mmap_dump(); +/** + * @brief Erase a range of flash sectors. + * + * @note This version of function is to be called from panic handler. + * It does not use any OS primitives and IPC and implies that + * only calling CPU is active. + * + * @param start_address Address where erase operation has to start. + * Must be 4kB-aligned + * @param size Size of erased range, in bytes. Must be divisible by 4kB. + * + * @return esp_err_t + */ +esp_err_t spi_flash_erase_range_panic(size_t start_address, size_t size); + + +/** + * @brief Write data to Flash. + * + * @note This version of function is to be called from panic handler. + * It does not use any OS primitives and IPC and implies that + * only calling CPU is active. + + * @note If source address is in DROM, this function will return + * ESP_ERR_INVALID_ARG. + * + * @param dest destination address in Flash. Must be a multiple of 4 bytes. + * @param src pointer to the source buffer. + * @param size length of data, in bytes. Must be a multiple of 4 bytes. + * + * @return esp_err_t + */ +esp_err_t spi_flash_write_panic(size_t dest, const void *src, size_t size); + + +/** + * @brief Read data from Flash. + * + * @note This version of function is to be called from panic handler. + * It does not use any OS primitives and IPC and implies that + * only calling CPU is active. + * + * @param src source address of the data in Flash. + * @param dest pointer to the destination buffer + * @param size length of data + * + * @return esp_err_t + */ +esp_err_t spi_flash_read_panic(size_t src, void *dest, size_t size); + #if CONFIG_SPI_FLASH_ENABLE_COUNTERS /** From 23f836659d1d46e579ddb57bdcbb66ac51b0b048 Mon Sep 17 00:00:00 2001 From: Alexey Gerenkov Date: Tue, 3 Jan 2017 14:16:41 +0300 Subject: [PATCH 139/167] esp32: Add core dump printing to UART feature --- components/esp32/core_dump.c | 456 +++++++++++++++++- components/esp32/cpu_start.c | 2 +- components/esp32/include/esp_panic.h | 2 +- components/espcoredump/espcoredump.py | 365 +++++++------- .../include/freertos/FreeRTOSConfig.h | 2 +- 5 files changed, 654 insertions(+), 173 deletions(-) diff --git a/components/esp32/core_dump.c b/components/esp32/core_dump.c index 165ec74192..eaab1871c3 100644 --- a/components/esp32/core_dump.c +++ b/components/esp32/core_dump.c @@ -11,6 +11,7 @@ // 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 "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -18,24 +19,199 @@ #include "esp_panic.h" #include "esp_partition.h" -#ifdef ESP_PLATFORM -// Uncomment this line to force output from this module -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH || CONFIG_ESP32_ENABLE_COREDUMP_TO_UART #include "esp_log.h" -static const char* TAG = "esp_core_dump_init"; -#else -#define ESP_LOGD(...) -#endif +const static char *TAG = "esp_core_dump"; // TODO: allow user to set this in menuconfig or get tasks iteratively #define COREDUMP_MAX_TASKS_NUM 32 +typedef esp_err_t (*esp_core_dump_write_prepare_t)(void *priv, uint32_t *data_len); +typedef esp_err_t (*esp_core_dump_write_start_t)(void *priv); +typedef esp_err_t (*esp_core_dump_write_end_t)(void *priv); +typedef esp_err_t (*esp_core_dump_flash_write_data_t)(void *priv, void * data, uint32_t data_len); + +typedef struct _core_dump_write_config_t +{ + esp_core_dump_write_prepare_t prepare; + esp_core_dump_write_start_t start; + esp_core_dump_write_end_t end; + esp_core_dump_flash_write_data_t write; + void * priv; + +} core_dump_write_config_t; + +static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *write_cfg, int verb) +{ + union + { + uint8_t data8[12]; + uint32_t data32[3]; + } rom_data; + //const esp_partition_t *core_part; + esp_err_t err; + TaskSnapshot_t tasks[COREDUMP_MAX_TASKS_NUM]; + UBaseType_t tcb_sz, task_num; + uint32_t data_len = 0, i, len; + //size_t off; + + task_num = uxTaskGetSnapshotAll(tasks, COREDUMP_MAX_TASKS_NUM, &tcb_sz); + // take TCB padding into account, actual TCB size will be stored in header + if (tcb_sz % sizeof(uint32_t)) + len = (tcb_sz / sizeof(uint32_t) + 1) * sizeof(uint32_t); + else + len = tcb_sz; + // header + tasknum*(tcb + stack start/end + tcb addr) + data_len = 3*sizeof(uint32_t) + task_num*(len + 2*sizeof(uint32_t) + sizeof(uint32_t *)); + for (i = 0; i < task_num; i++) { + if (tasks[i].pxTCB == xTaskGetCurrentTaskHandle()) { + // set correct stack top for current task + tasks[i].pxTopOfStack = (StackType_t *)frame; + if (verb) { + esp_panicPutStr("Current task PC/A0/SP "); + esp_panicPutHex(frame->pc); + esp_panicPutStr(" "); + esp_panicPutHex(frame->a0); + esp_panicPutStr(" "); + esp_panicPutHex(frame->a1); + esp_panicPutStr("\r\n"); + } + } +#if( portSTACK_GROWTH < 0 ) + len = (uint32_t)tasks[i].pxEndOfStack - (uint32_t)tasks[i].pxTopOfStack; +#else + len = (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack; +#endif + if (verb) { + esp_panicPutStr("stack len = "); + esp_panicPutHex(len); + esp_panicPutStr(" "); + esp_panicPutHex((int)tasks[i].pxTopOfStack); + esp_panicPutStr(" "); + esp_panicPutHex((int)tasks[i].pxEndOfStack); + esp_panicPutStr("\r\n"); + } + // take stack padding into account + if (len % sizeof(uint32_t)) + len = (len / sizeof(uint32_t) + 1) * sizeof(uint32_t); + data_len += len; + } + + // prepare write + if (write_cfg->prepare) { + err = write_cfg->prepare(write_cfg->priv, &data_len); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to prepare core dump "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + } + + if (verb) { + esp_panicPutStr("Core dump len ="); + esp_panicPutHex(data_len); + esp_panicPutStr("\r\n"); + } + + // write start marker + if (write_cfg->start) { + err = write_cfg->start(write_cfg->priv); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to start core dump "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + } + + // write header + rom_data.data32[0] = data_len; + rom_data.data32[1] = task_num; + rom_data.data32[2] = tcb_sz; + err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t)); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to write core dump header "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + + // write tasks + for (i = 0; i < task_num; i++) { + if (verb) { + esp_panicPutStr("Dump task "); + esp_panicPutHex((int)tasks[i].pxTCB); + esp_panicPutStr("\r\n"); + } + // save TCB address, stack base and stack top addr + rom_data.data32[0] = (uint32_t)tasks[i].pxTCB; + rom_data.data32[1] = (uint32_t)tasks[i].pxTopOfStack; + rom_data.data32[2] = (uint32_t)tasks[i].pxEndOfStack; + err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t)); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to write task header "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + // save TCB + err = write_cfg->write(write_cfg->priv, tasks[i].pxTCB, tcb_sz); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to write task header "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + // save task stack + /*int k; + for (k = 0; k < 8*4; k++) { + esp_panicPutStr("stack["); + esp_panicPutDec(k); + esp_panicPutStr("] = "); + esp_panicPutHex(((uint8_t *)tasks[i].pxTopOfStack)[k]); + esp_panicPutStr("\r\n"); + }*/ + err = write_cfg->write(write_cfg->priv, +#if( portSTACK_GROWTH < 0 ) + tasks[i].pxTopOfStack, + (uint32_t)tasks[i].pxEndOfStack - (uint32_t)tasks[i].pxTopOfStack +#else + tasks[i].pxEndOfStack, + (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack +#endif + ); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to write task header "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + } + + // write end marker + if (write_cfg->end) { + err = write_cfg->end(write_cfg->priv); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to end core dump "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return; + } + } +} + #if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH // magic numbers to control core dump data consistency #define COREDUMP_FLASH_MAGIC_START 0xDEADBEEFUL #define COREDUMP_FLASH_MAGIC_END 0xACDCFEEDUL +typedef struct _core_dump_write_flash_data_t +{ + uint32_t off; +} core_dump_write_flash_data_t; + // core dump partition start static uint32_t s_core_part_start; // core dump partition size @@ -79,6 +255,115 @@ static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint return data_len; } +static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_len) +{ + esp_err_t err; + uint32_t sec_num; + core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + + esp_panicPutStr("Core dump len1 = "); + esp_panicPutHex(*data_len); + esp_panicPutStr("\r\n"); + + // add space for 2 magics. TODO: change to CRC + *data_len += 2*sizeof(uint32_t); + if (*data_len > s_core_part_size) { + esp_panicPutStr("ERROR: Not enough space to save core dump!"); + return ESP_ERR_NO_MEM; + } + + esp_panicPutStr("Core dump len2 = "); + esp_panicPutHex(*data_len); + esp_panicPutStr("\r\n"); + + wr_data->off = 0; + + sec_num = *data_len / SPI_FLASH_SEC_SIZE; + if (*data_len % SPI_FLASH_SEC_SIZE) + sec_num++; + err = spi_flash_erase_range_panic(s_core_part_start + 0, sec_num * SPI_FLASH_SEC_SIZE); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to erase flash "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return err; + } + + return err; +} + +static esp_err_t esp_core_dump_flash_write_word(core_dump_write_flash_data_t *wr_data, uint32_t word) +{ + esp_err_t err = ESP_OK; + uint32_t data32 = word; + + err = spi_flash_write_panic(s_core_part_start + wr_data->off, &data32, sizeof(uint32_t)); + if (err != ESP_OK) { + esp_panicPutStr("Failed to write to flash "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return err; + } + wr_data->off += sizeof(uint32_t); + + return err; +} + +static esp_err_t esp_core_dump_flash_write_start(void *priv) +{ + core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + // save magic 1 + return esp_core_dump_flash_write_word(wr_data, COREDUMP_FLASH_MAGIC_START); +} + +static esp_err_t esp_core_dump_flash_write_end(void *priv) +{ + core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + uint32_t i; + union + { + uint8_t data8[16]; + uint32_t data32[4]; + } rom_data; + + // TEST READ START + esp_err_t err = spi_flash_read_panic(s_core_part_start + 0, &rom_data, sizeof(rom_data)); + if (err != ESP_OK) { + esp_panicPutStr("ERROR: Failed to read flash "); + esp_panicPutHex(err); + esp_panicPutStr("!\r\n"); + return err; + } + else { + esp_panicPutStr("Data from flash:\r\n"); + for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) { + esp_panicPutHex(rom_data.data32[i]); + esp_panicPutStr("\r\n"); + } +// rom_data[4] = 0; +// esp_panicPutStr(rom_data); +// esp_panicPutStr("\r\n"); + } + // TEST READ END + + // save magic 2 + return esp_core_dump_flash_write_word(wr_data, COREDUMP_FLASH_MAGIC_END); +} + +static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_t data_len) +{ + esp_err_t err = ESP_OK; + core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + + uint32_t len = esp_core_dump_write_flash_padded(s_core_part_start + wr_data->off, data, data_len); + if (len != data_len) + return ESP_FAIL; + + wr_data->off += len; + + return err; +} + /* * | MAGIC1 | * | TOTAL_LEN | TASKS_NUM | TCB_SIZE | @@ -90,6 +375,19 @@ static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint */ void esp_core_dump_to_flash(XtExcFrame *frame) { +#if 1 + core_dump_write_config_t wr_cfg; + core_dump_write_flash_data_t wr_data; + + wr_cfg.prepare = esp_core_dump_flash_write_prepare; + wr_cfg.start = esp_core_dump_flash_write_start; + wr_cfg.end = esp_core_dump_flash_write_end; + wr_cfg.write = esp_core_dump_flash_write_data; + wr_cfg.priv = &wr_data; + + esp_panicPutStr("Save core dump to flash...\r\n"); + esp_core_dump_write(frame, &wr_cfg, 1); +#else union { uint8_t data8[16]; @@ -245,14 +543,150 @@ void esp_core_dump_to_flash(XtExcFrame *frame) esp_panicPutStr("!\r\n"); return; } - - esp_panicPutStr("Core dump has been saved to flash partition.\r\n"); +#endif + esp_panicPutStr("Core dump has been saved to flash.\r\n"); } #endif #if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART +#if 0 +#define BASE64_ENCODE_BODY(_src, _src_len, _dst) \ + do { \ + static const char *b64 = \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; \ + int i, j, a, b, c; \ + \ + for (i = j = 0; i < _src_len; i += 3) { \ + a = _src[i]; \ + b = i + 1 >= _src_len ? 0 : _src[i + 1]; \ + c = i + 2 >= _src_len ? 0 : _src[i + 2]; \ + \ + /*BASE64_OUT(b64[a >> 2], _dst[j]);*/ \ + _dst[j++] = b64[a >> 2]; \ + /*BASE64_OUT(b64[((a & 3) << 4) | (b >> 4)], _dst[j]);*/ \ + _dst[j++] = b64[((a & 3) << 4) | (b >> 4)]; \ + j++; \ + if (i + 1 < _src_len) { \ + BASE64_OUT(b64[(b & 15) << 2 | (c >> 6)], _dst[j]); \ + j++; \ + } \ + if (i + 2 < _src_len) { \ + BASE64_OUT(b64[c & 63], _dst[j]); \ + j++; \ + } \ + } \ + \ + while (j % 4 != 0) { \ + BASE64_OUT('=', _dst); \ + } \ + BASE64_FLUSH(_dst) \ + } while(0) + +#define BASE64_OUT(ch, _dst) \ + do { \ + _dst = (ch); \ + } while (0) + +#define BASE64_FLUSH(_dst) \ + do { \ + _dst = '\0'; \ + } while (0) +#endif +static void esp_core_dump_b64_encode(const uint8_t *src, uint32_t src_len, uint8_t *dst) { +// BASE64_ENCODE_BODY(src, src_len, dst); + static const char *b64 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + int i, j, a, b, c; + + for (i = j = 0; i < src_len; i += 3) { + a = src[i]; + b = i + 1 >= src_len ? 0 : src[i + 1]; + c = i + 2 >= src_len ? 0 : src[i + 2]; + + dst[j++] = b64[a >> 2]; + dst[j++] = b64[((a & 3) << 4) | (b >> 4)]; + if (i + 1 < src_len) { + dst[j++] = b64[(b & 0x0F) << 2 | (c >> 6)]; + } + if (i + 2 < src_len) { + dst[j++] = b64[c & 0x3F]; + } + } + while (j % 4 != 0) { + dst[j++] = '='; + } + dst[j++] = '\0'; +} + +/*static esp_err_t esp_core_dump_uart_write_prepare(void *priv, uint32_t *data_len) +{ + esp_err_t err = ESP_OK; + return err; +}*/ + +static esp_err_t esp_core_dump_uart_write_start(void *priv) +{ +// core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + esp_err_t err = ESP_OK; + esp_panicPutStr("================= CORE DUMP START =================\r\n"); + return err; +} + +static esp_err_t esp_core_dump_uart_write_end(void *priv) +{ +// core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + esp_err_t err = ESP_OK; + esp_panicPutStr("================= CORE DUMP END =================\r\n"); + return err; +} + +static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t data_len) +{ +// core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; + esp_err_t err = ESP_OK; + char buf[64 + 4], *addr = data; + char *end = addr + data_len; + +// esp_panicPutStr("CORE DUMP SEC: "); +// esp_panicPutDec(data_len); +// esp_panicPutStr("bytes\r\n"); + + while (addr < end) { + size_t len = end - addr; + if (len > 48) len = 48; + /* Copy to stack to avoid alignment restrictions. */ + char *tmp = buf + (sizeof(buf) - len); + memcpy(tmp, addr, len); + esp_core_dump_b64_encode((const uint8_t *)tmp, len, (uint8_t *)buf); + addr += len; + esp_panicPutStr(buf); +// for (size_t i = 0; buf[i] != '\0'; i++) { +// panicPutChar(buf[i]); +// } + //if (addr % 96 == 0) + esp_panicPutStr("\r\n"); + /* Feed the Cerberus. */ +// TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE; +// TIMERG0.wdt_feed = 1; + } + + return err; +} + void esp_core_dump_to_uart(XtExcFrame *frame) { + core_dump_write_config_t wr_cfg; + //core_dump_write_flash_data_t wr_data; + + wr_cfg.prepare = NULL;//esp_core_dump_uart_write_prepare; + wr_cfg.start = esp_core_dump_uart_write_start; + wr_cfg.end = esp_core_dump_uart_write_end; + wr_cfg.write = esp_core_dump_uart_write_data; + wr_cfg.priv = NULL; + + esp_panicPutStr("Save core dump to flash...\r\n"); + esp_core_dump_write(frame, &wr_cfg, 0); + esp_panicPutStr("Core dump has been written to uart.\r\n"); } #endif @@ -261,6 +695,7 @@ void esp_core_dump_init() #if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH const esp_partition_t *core_part; + ESP_LOGI(TAG, "Init core dump to flash"); core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL); if (!core_part) { ESP_LOGE(TAG, "No core dump partition found!"); @@ -271,6 +706,9 @@ void esp_core_dump_init() s_core_part_size = core_part->size; #endif #if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART + ESP_LOGI(TAG, "Init core dump to UART"); #endif } +#endif + diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 95d5c5e6a2..1b8a95b75c 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -215,7 +215,7 @@ void start_cpu0_default(void) } #endif -#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH || CONFIG_ESP32_ENABLE_COREDUMP_TO_UART esp_core_dump_init(); #endif diff --git a/components/esp32/include/esp_panic.h b/components/esp32/include/esp_panic.h index 25816d31f2..3dd5d4b185 100644 --- a/components/esp32/include/esp_panic.h +++ b/components/esp32/include/esp_panic.h @@ -24,7 +24,7 @@ */ void esp_set_breakpoint_if_jtag(void *fn); -void esp_panicPutchar(char c); +void esp_panicPutChar(char c); void esp_panicPutStr(const char *c); void esp_panicPutHex(int a); void esp_panicPutDec(int a); diff --git a/components/espcoredump/espcoredump.py b/components/espcoredump/espcoredump.py index 065b323aa1..2f31d3f292 100755 --- a/components/espcoredump/espcoredump.py +++ b/components/espcoredump/espcoredump.py @@ -10,6 +10,7 @@ import tempfile import struct import array import errno +import base64 try: import esptool @@ -23,10 +24,6 @@ except ImportError: __version__ = "0.1-dev" -ESP32_COREDUMP_HDR_FMT = '<4L' -ESP32_COREDUMP_FLASH_MAGIC_START = 0xDEADBEEF -ESP32_COREDUMP_FLASH_MAGIC_END = 0xACDCFEED - class Struct(object): def __init__(self, buf=None): @@ -77,7 +74,7 @@ class Elf32FileHeader(Struct): if buf is None: # Fill in sane ELF header for LSB32 self.e_ident = "\x7fELF\1\1\1\0\0\0\0\0\0\0\0\0" - self.e_version = ESPCoreDumpFile.EV_CURRENT + self.e_version = ESPCoreDumpElfFile.EV_CURRENT self.e_ehsize = self.sizeof() @@ -200,7 +197,7 @@ class ESPCoreDumpSection(esptool.ELFSection): return str -class ESPCoreDumpFile(esptool.ELFFile): +class ESPCoreDumpElfFile(esptool.ELFFile): # ELF file type ET_NONE = 0x0 # No file type ET_REL = 0x1 # Relocatable file @@ -229,7 +226,7 @@ class ESPCoreDumpFile(esptool.ELFFile): def __init__(self, name=None): if name: - super(ESPCoreDumpFile, self).__init__(name) + super(ESPCoreDumpElfFile, self).__init__(name) else: self.sections = [] self.program_segments = [] @@ -409,80 +406,12 @@ class ESPCoreDumpLoader(object): """ TBD """ - FLASH_READ_BLOCK_SZ = 0x2000 - def __init__(self, off, path=None, chip='esp32', port=None, baud=None): -# print "esptool.__file__ %s" % esptool.__file__ - if not path: - self.path = esptool.__file__ - self.path = self.path[:-1] - else: - self.path = path - self.port = port - self.baud = baud - self.chip = chip - self.fcores = [] - self.fgdbcore = None - self._load_coredump(off) - - def _load_coredump(self, off): - args = [self.path, '-c', self.chip] - if self.port: - args.extend(['-p', self.port]) - if self.baud: - args.extend(['-b', str(self.baud)]) - read_sz = self.FLASH_READ_BLOCK_SZ - read_off = off - args.extend(['read_flash', str(read_off), str(read_sz), '']) - try: - dump_sz = 0 - tot_len = 0 - while True: - fhnd,fname = tempfile.mkstemp() -# print "tmpname %s" % fname -# os.close(fhnd) - args[-1] = fname - et_out = subprocess.check_output(args) - print et_out - # data = os.fdopen(fhnd, 'r').read(sz) - self.fcores.append(os.fdopen(fhnd, 'r')) - if dump_sz == 0: - # read dump length from the first block - dump_sz = self._read_core_dump_length(self.fcores[0]) - tot_len += read_sz - if tot_len >= dump_sz: - break - read_off += read_sz - if dump_sz - tot_len >= self.FLASH_READ_BLOCK_SZ: - read_sz = self.FLASH_READ_BLOCK_SZ - else: - read_sz = dump_sz - tot_len - args[-3] = str(read_off) - args[-2] = str(read_sz) - - except subprocess.CalledProcessError as e: - print "esptool script execution failed with err %d" % e.returncode - print "Command ran: '%s'" % e.cmd - print "Command out:" - print e.output - self.cleanup() - return [] - - def _read_core_dump_length(self, f): - global ESP32_COREDUMP_HDR_FMT - global ESP32_COREDUMP_FLASH_MAGIC_START - print "Read core dump header from '%s'" % f.name - data = f.read(4*4) - mag1,tot_len,task_num,tcbsz = struct.unpack_from(ESP32_COREDUMP_HDR_FMT, data) - if mag1 != ESP32_COREDUMP_FLASH_MAGIC_START: - raise ESPCoreDumpLoaderError("Invalid start magic number!") - return tot_len - - def remove_tmp_file(self, fname): - try: - os.remove(fname) - except OSError as e: - if e.errno != errno.ENOENT: - print "Warning failed to remove temp file '%s'!" % fname + ESP32_COREDUMP_HDR_FMT = '<3L' + ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT) + ESP32_COREDUMP_TSK_HDR_FMT = '<3L' + ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT) + def __init__(self): + self.fcore = None def _get_registers_from_stack(self, data, grows_down): # from "gdb/xtensa-tdep.h" @@ -582,51 +511,36 @@ class ESPCoreDumpLoader(object): regs[REG_AR_START_IDX + 0] = (regs[REG_AR_START_IDX + 0] & 0x3fffffff) | 0x40000000; return regs - def cleanup(self): -# if self.fgdbcore: -# self.fgdbcore.close() -# self.remove_tmp_file(self.fgdbcore.name) - for f in self.fcores: - if f: - f.close() - self.remove_tmp_file(f.name) + def remove_tmp_file(self, fname): + try: + os.remove(fname) + except OSError as e: + if e.errno != errno.ENOENT: + print "Warning failed to remove temp file '%s'!" % fname - def get_corefile_from_flash(self): + def cleanup(self): + if self.fcore: + self.fcore.close() + self.remove_tmp_file(self.fcore.name) + + def create_corefile(self, core_fname=None, off=0): """ TBD """ - global ESP32_COREDUMP_HDR_FMT - ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT) - ESP32_COREDUMP_TSK_HDR_FMT = ' stack_top: stack_len = stack_end - stack_top stack_base = stack_top @@ -639,23 +553,23 @@ class ESPCoreDumpLoader(object): if stack_len_aligned % 4: stack_len_aligned = 4*(stack_len_aligned/4 + 1) - core_off += ESP32_COREDUMP_TSK_HDR_SZ + core_off += self.ESP32_COREDUMP_TSK_HDR_SZ print "Read task[%d] TCB" % i - data = self.read_flash(core_off, tcbsz_aligned, flash_progress) + data = self.read_data(core_off, tcbsz_aligned) if tcbsz != tcbsz_aligned: - core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) else: - core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) # print "tcb=%s" % data core_off += tcbsz_aligned print "Read task[%d] stack %d bytes" % (i,stack_len) - data = self.read_flash(core_off, stack_len_aligned, flash_progress) + data = self.read_data(core_off, stack_len_aligned) # print "stk=%s" % data if stack_len != stack_len_aligned: data = data[:stack_len - stack_len_aligned] - core_elf.add_program_segment(stack_base, data, ESPCoreDumpFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) + core_elf.add_program_segment(stack_base, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) core_off += stack_len_aligned - + task_regs = self._get_registers_from_stack(data, stack_end > stack_top) prstatus = XtensaPrStatus() prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task @@ -663,33 +577,140 @@ class ESPCoreDumpLoader(object): note = Elf32NoteDesc("CORE", 1, prstatus.dump() + struct.pack("<%dL" % len(task_regs), *task_regs)).dump() print "NOTE_LEN %d" % len(note) notes += note - - print "Read core dump endmarker" - data = self.read_flash(core_off, ESP32_COREDUMP_MAGIC_SZ, flash_progress) - mag = struct.unpack_from(ESP32_COREDUMP_MAGIC_FMT, data) - print "mag2=%x" % (mag) - - # add notes - core_elf.add_program_segment(0, notes, ESPCoreDumpFile.PT_NOTE, 0) - - core_elf.e_type = ESPCoreDumpFile.ET_CORE - core_elf.e_machine = ESPCoreDumpFile.EM_XTENSA - fhnd,fname = tempfile.mkstemp() - self.fgdbcore = os.fdopen(fhnd, 'wb') - core_elf.dump(self.fgdbcore) - return fname - ######################### END ########################### - def read_flash(self, off, sz, progress=None): -# print "read_flash: %x %d" % (off, sz) - id = off / self.FLASH_READ_BLOCK_SZ - if id >= len(self.fcores): - return '' - self.fcores[id].seek(off % self.FLASH_READ_BLOCK_SZ) - data = self.fcores[id].read(sz) + # add notes + core_elf.add_program_segment(0, notes, ESPCoreDumpElfFile.PT_NOTE, 0) + + core_elf.e_type = ESPCoreDumpElfFile.ET_CORE + core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA + if core_fname: + fce = open(core_fname, 'wb') + else: + fhnd,core_fname = tempfile.mkstemp() + fce = os.fdopen(fhnd, 'wb') + core_elf.dump(fce) + fce.close() + return core_fname + + def read_data(self, off, sz): +# print "read_data: %x %d" % (off, sz) + self.fcore.seek(off) + data = self.fcore.read(sz) # print "data1: %s" % data return data + +class ESPCoreDumpFileLoader(ESPCoreDumpLoader): + """ TBD + """ + def __init__(self, path, b64 = False): + super(ESPCoreDumpFileLoader, self).__init__() + self.fcore = self._load_coredump(path, b64) + + def _load_coredump(self, path, b64): + if b64: + fhnd,fname = tempfile.mkstemp() + print "tmpname %s" % fname + fcore = os.fdopen(fhnd, 'wb') + fb64 = open(path, 'r') + try: + while True: + line = fb64.readline() + if len(line) == 0: + break + data = base64.b64decode(line.rstrip('\r\n'))#, validate=True) + fcore.write(data) + fcore.close() + fcore = open(fname, 'r') + finally: + fb64.close() + else: + fcore = open(path, 'r') + return fcore + + +class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): + """ TBD + """ + ESP32_COREDUMP_FLASH_MAGIC_START = 0xDEADBEEF + ESP32_COREDUMP_FLASH_MAGIC_END = 0xACDCFEED + ESP32_COREDUMP_FLASH_MAGIC_FMT = ' Date: Tue, 3 Jan 2017 22:01:40 +0300 Subject: [PATCH 140/167] esp32: Fixes several issues in core dump feature 1) PS is fixed up to allow GDB backtrace to work properly 2) MR!341 discussion: in core dump module: esp_panicPutXXX was replaced by ets_printf. 3) MR!341 discussion: core dump flash magic number was changed. 4) MR!341 discussion: SPI flash access API was redesigned to allow flexible critical section management. 5) test app for core dump feature was added 6) fixed base64 file reading issues on Windows platform 7) now raw bin core file is deleted upon core loader failure by epscoredump.py --- components/esp32/core_dump.c | 427 ++++--------------- components/esp32/cpu_start.c | 2 + components/esp32/include/esp_panic.h | 4 - components/esp32/panic.c | 82 ++-- components/espcoredump/espcoredump.py | 113 ++--- components/freertos/tasks.c | 20 - components/spi_flash/cache_utils.c | 8 +- components/spi_flash/cache_utils.h | 8 +- components/spi_flash/flash_ops.c | 84 +--- components/spi_flash/include/esp_spi_flash.h | 56 +-- 10 files changed, 241 insertions(+), 563 deletions(-) diff --git a/components/esp32/core_dump.c b/components/esp32/core_dump.c index eaab1871c3..e12fc1d71c 100644 --- a/components/esp32/core_dump.c +++ b/components/esp32/core_dump.c @@ -15,7 +15,6 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -//#include "esp_attr.h" #include "esp_panic.h" #include "esp_partition.h" @@ -26,10 +25,10 @@ const static char *TAG = "esp_core_dump"; // TODO: allow user to set this in menuconfig or get tasks iteratively #define COREDUMP_MAX_TASKS_NUM 32 -typedef esp_err_t (*esp_core_dump_write_prepare_t)(void *priv, uint32_t *data_len); -typedef esp_err_t (*esp_core_dump_write_start_t)(void *priv); -typedef esp_err_t (*esp_core_dump_write_end_t)(void *priv); -typedef esp_err_t (*esp_core_dump_flash_write_data_t)(void *priv, void * data, uint32_t data_len); +typedef esp_err_t (*esp_core_dump_write_prepare_t)(void *priv, uint32_t *data_len, int verb); +typedef esp_err_t (*esp_core_dump_write_start_t)(void *priv, int verb); +typedef esp_err_t (*esp_core_dump_write_end_t)(void *priv, int verb); +typedef esp_err_t (*esp_core_dump_flash_write_data_t)(void *priv, void * data, uint32_t data_len, int verb); typedef struct _core_dump_write_config_t { @@ -67,14 +66,19 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri if (tasks[i].pxTCB == xTaskGetCurrentTaskHandle()) { // set correct stack top for current task tasks[i].pxTopOfStack = (StackType_t *)frame; + if (verb) + ets_printf("Current task EXIT/PC/PS/A0/SP %x %x %x %x %x\r\n", frame->exit, frame->pc, frame->ps, frame->a0, frame->a1); + } + else { if (verb) { - esp_panicPutStr("Current task PC/A0/SP "); - esp_panicPutHex(frame->pc); - esp_panicPutStr(" "); - esp_panicPutHex(frame->a0); - esp_panicPutStr(" "); - esp_panicPutHex(frame->a1); - esp_panicPutStr("\r\n"); + XtSolFrame *task_frame = (XtSolFrame *)tasks[i].pxTopOfStack; + if (task_frame->exit == 0) { + ets_printf("Task EXIT/PC/PS/A0/SP %x %x %x %x %x\r\n", task_frame->exit, task_frame->pc, task_frame->ps, task_frame->a0, task_frame->a1); + } + else { + XtExcFrame *task_frame2 = (XtExcFrame *)tasks[i].pxTopOfStack; + ets_printf("Task EXIT/PC/PS/A0/SP %x %x %x %x %x\r\n", task_frame2->exit, task_frame2->pc, task_frame2->ps, task_frame2->a0, task_frame2->a1); + } } } #if( portSTACK_GROWTH < 0 ) @@ -83,13 +87,7 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri len = (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack; #endif if (verb) { - esp_panicPutStr("stack len = "); - esp_panicPutHex(len); - esp_panicPutStr(" "); - esp_panicPutHex((int)tasks[i].pxTopOfStack); - esp_panicPutStr(" "); - esp_panicPutHex((int)tasks[i].pxEndOfStack); - esp_panicPutStr("\r\n"); + ets_printf("Stack len = %lu (%x %x)\r\n", len, tasks[i].pxTopOfStack, tasks[i].pxEndOfStack); } // take stack padding into account if (len % sizeof(uint32_t)) @@ -99,28 +97,22 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri // prepare write if (write_cfg->prepare) { - err = write_cfg->prepare(write_cfg->priv, &data_len); + err = write_cfg->prepare(write_cfg->priv, &data_len, verb); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to prepare core dump "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to prepare core dump (%d)!\r\n", err); return; } } if (verb) { - esp_panicPutStr("Core dump len ="); - esp_panicPutHex(data_len); - esp_panicPutStr("\r\n"); + ets_printf("Core dump len = %lu\r\n", data_len); } - // write start marker + // write start if (write_cfg->start) { - err = write_cfg->start(write_cfg->priv); + err = write_cfg->start(write_cfg->priv, verb); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to start core dump "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to start core dump (%d)!\r\n", err); return; } } @@ -129,49 +121,33 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri rom_data.data32[0] = data_len; rom_data.data32[1] = task_num; rom_data.data32[2] = tcb_sz; - err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t)); + err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t), verb); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to write core dump header "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to write core dump header (%d)!\r\n", err); return; } // write tasks for (i = 0; i < task_num; i++) { if (verb) { - esp_panicPutStr("Dump task "); - esp_panicPutHex((int)tasks[i].pxTCB); - esp_panicPutStr("\r\n"); + ets_printf("Dump task %x\r\n", tasks[i].pxTCB); } // save TCB address, stack base and stack top addr rom_data.data32[0] = (uint32_t)tasks[i].pxTCB; rom_data.data32[1] = (uint32_t)tasks[i].pxTopOfStack; rom_data.data32[2] = (uint32_t)tasks[i].pxEndOfStack; - err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t)); + err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t), verb); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to write task header "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to write task header (%d)!\r\n", err); return; } // save TCB - err = write_cfg->write(write_cfg->priv, tasks[i].pxTCB, tcb_sz); + err = write_cfg->write(write_cfg->priv, tasks[i].pxTCB, tcb_sz, verb); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to write task header "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to write TCB (%d)!\r\n", err); return; } // save task stack - /*int k; - for (k = 0; k < 8*4; k++) { - esp_panicPutStr("stack["); - esp_panicPutDec(k); - esp_panicPutStr("] = "); - esp_panicPutHex(((uint8_t *)tasks[i].pxTopOfStack)[k]); - esp_panicPutStr("\r\n"); - }*/ err = write_cfg->write(write_cfg->priv, #if( portSTACK_GROWTH < 0 ) tasks[i].pxTopOfStack, @@ -180,22 +156,18 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri tasks[i].pxEndOfStack, (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack #endif - ); + , verb); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to write task header "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to write task stack (%d)!\r\n", err); return; } } - // write end marker + // write end if (write_cfg->end) { - err = write_cfg->end(write_cfg->priv); + err = write_cfg->end(write_cfg->priv, verb); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to end core dump "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to end core dump (%d)!\r\n", err); return; } } @@ -204,8 +176,8 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri #if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH // magic numbers to control core dump data consistency -#define COREDUMP_FLASH_MAGIC_START 0xDEADBEEFUL -#define COREDUMP_FLASH_MAGIC_END 0xACDCFEEDUL +#define COREDUMP_FLASH_MAGIC_START 0xE32C04EDUL +#define COREDUMP_FLASH_MAGIC_END 0xE32C04EDUL typedef struct _core_dump_write_flash_data_t { @@ -228,11 +200,9 @@ static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint } rom_data; data_len = (data_size / sizeof(uint32_t)) * sizeof(uint32_t); - err = spi_flash_write_panic(off, data, data_len); + err = spi_flash_write(off, data, data_len); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to write data"); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to write data to flash (%d)!\r\n", err); return 0; } @@ -242,11 +212,9 @@ static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint rom_data.data32 = 0; for (k = 0; k < len; k++) rom_data.data8[k] = *(data + data_len + k); - err = spi_flash_write_panic(off + data_len, &rom_data, sizeof(uint32_t)); + err = spi_flash_write(off + data_len, &rom_data, sizeof(uint32_t)); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to write data end"); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to finish write data to flash (%d)!\r\n", err); return 0; } data_len += sizeof(uint32_t); @@ -255,37 +223,27 @@ static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint return data_len; } -static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_len) +static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_len, int verb) { esp_err_t err; uint32_t sec_num; core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; - esp_panicPutStr("Core dump len1 = "); - esp_panicPutHex(*data_len); - esp_panicPutStr("\r\n"); - // add space for 2 magics. TODO: change to CRC - *data_len += 2*sizeof(uint32_t); - if (*data_len > s_core_part_size) { - esp_panicPutStr("ERROR: Not enough space to save core dump!"); + if ((*data_len + 2*sizeof(uint32_t)) > s_core_part_size) { + ets_printf("ERROR: Not enough space to save core dump!\r\n"); return ESP_ERR_NO_MEM; } - - esp_panicPutStr("Core dump len2 = "); - esp_panicPutHex(*data_len); - esp_panicPutStr("\r\n"); + *data_len += 2*sizeof(uint32_t); wr_data->off = 0; sec_num = *data_len / SPI_FLASH_SEC_SIZE; if (*data_len % SPI_FLASH_SEC_SIZE) sec_num++; - err = spi_flash_erase_range_panic(s_core_part_start + 0, sec_num * SPI_FLASH_SEC_SIZE); + err = spi_flash_erase_range(s_core_part_start + 0, sec_num * SPI_FLASH_SEC_SIZE); if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to erase flash "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to erase flash (%d)!\r\n", err); return err; } @@ -297,11 +255,9 @@ static esp_err_t esp_core_dump_flash_write_word(core_dump_write_flash_data_t *wr esp_err_t err = ESP_OK; uint32_t data32 = word; - err = spi_flash_write_panic(s_core_part_start + wr_data->off, &data32, sizeof(uint32_t)); + err = spi_flash_write(s_core_part_start + wr_data->off, &data32, sizeof(uint32_t)); if (err != ESP_OK) { - esp_panicPutStr("Failed to write to flash "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); + ets_printf("ERROR: Failed to write to flash (%d)!\r\n", err); return err; } wr_data->off += sizeof(uint32_t); @@ -309,14 +265,14 @@ static esp_err_t esp_core_dump_flash_write_word(core_dump_write_flash_data_t *wr return err; } -static esp_err_t esp_core_dump_flash_write_start(void *priv) +static esp_err_t esp_core_dump_flash_write_start(void *priv, int verb) { core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; // save magic 1 return esp_core_dump_flash_write_word(wr_data, COREDUMP_FLASH_MAGIC_START); } -static esp_err_t esp_core_dump_flash_write_end(void *priv) +static esp_err_t esp_core_dump_flash_write_end(void *priv, int verb) { core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; uint32_t i; @@ -326,31 +282,27 @@ static esp_err_t esp_core_dump_flash_write_end(void *priv) uint32_t data32[4]; } rom_data; - // TEST READ START - esp_err_t err = spi_flash_read_panic(s_core_part_start + 0, &rom_data, sizeof(rom_data)); - if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to read flash "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); - return err; - } - else { - esp_panicPutStr("Data from flash:\r\n"); - for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) { - esp_panicPutHex(rom_data.data32[i]); - esp_panicPutStr("\r\n"); + if (verb) { + // TEST READ START + esp_err_t err = spi_flash_read(s_core_part_start + 0, &rom_data, sizeof(rom_data)); + if (err != ESP_OK) { + ets_printf("ERROR: Failed to read flash (%d)!\r\n", err); + return err; } -// rom_data[4] = 0; -// esp_panicPutStr(rom_data); -// esp_panicPutStr("\r\n"); + else { + ets_printf("Data from flash:\r\n"); + for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) { + ets_printf("%x\r\n", rom_data.data32[i]); + } + } + // TEST READ END } - // TEST READ END // save magic 2 return esp_core_dump_flash_write_word(wr_data, COREDUMP_FLASH_MAGIC_END); } -static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_t data_len) +static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_t data_len, int verb) { esp_err_t err = ESP_OK; core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; @@ -375,223 +327,25 @@ static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_ */ void esp_core_dump_to_flash(XtExcFrame *frame) { -#if 1 core_dump_write_config_t wr_cfg; core_dump_write_flash_data_t wr_data; + /* init non-OS flash access critical section */ + spi_flash_guard_set(&g_flash_guard_no_os_ops); + wr_cfg.prepare = esp_core_dump_flash_write_prepare; wr_cfg.start = esp_core_dump_flash_write_start; wr_cfg.end = esp_core_dump_flash_write_end; wr_cfg.write = esp_core_dump_flash_write_data; wr_cfg.priv = &wr_data; - esp_panicPutStr("Save core dump to flash...\r\n"); - esp_core_dump_write(frame, &wr_cfg, 1); -#else - union - { - uint8_t data8[16]; - uint32_t data32[4]; - } rom_data; - //const esp_partition_t *core_part; - esp_err_t err; - TaskSnapshot_t tasks[COREDUMP_MAX_TASKS_NUM]; - UBaseType_t tcb_sz, task_num; - uint32_t data_len = 0, i, len, sec_num; - size_t off; - - esp_panicPutStr("Save core dump to flash...\r\n"); - task_num = uxTaskGetSnapshotAll(tasks, COREDUMP_MAX_TASKS_NUM, &tcb_sz); - // take TCB padding into account, actual TCB size will be stored in header - if (tcb_sz % sizeof(uint32_t)) - len = (tcb_sz / sizeof(uint32_t) + 1) * sizeof(uint32_t); - else - len = tcb_sz; - // header + magic2 + tasknum*(tcb + stack start/end + tcb addr) - data_len = 5*sizeof(uint32_t) + task_num*(len + 2*sizeof(uint32_t) + sizeof(uint32_t *)); - for (i = 0; i < task_num; i++) { - if (tasks[i].pxTCB == xTaskGetCurrentTaskHandle()) { - // set correct stack top for current task - tasks[i].pxTopOfStack = (StackType_t *)frame; - esp_panicPutStr("Current task PC/A0/SP "); - esp_panicPutHex(frame->pc); - esp_panicPutStr(" "); - esp_panicPutHex(frame->a0); - esp_panicPutStr(" "); - esp_panicPutHex(frame->a1); - esp_panicPutStr("\r\n"); - } -#if( portSTACK_GROWTH < 0 ) - len = (uint32_t)tasks[i].pxEndOfStack - (uint32_t)tasks[i].pxTopOfStack; -#else - len = (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack; -#endif - esp_panicPutStr("stack len = "); - esp_panicPutHex(len); - esp_panicPutStr(" "); - esp_panicPutHex((int)tasks[i].pxTopOfStack); - esp_panicPutStr(" "); - esp_panicPutHex((int)tasks[i].pxEndOfStack); - esp_panicPutStr("\r\n"); - // take stack padding into account - if (len % sizeof(uint32_t)) - len = (len / sizeof(uint32_t) + 1) * sizeof(uint32_t); - data_len += len; - } - esp_panicPutStr("Core dump len ="); - esp_panicPutHex(data_len); - esp_panicPutStr("\r\n"); - if (data_len > s_core_part_size) { - esp_panicPutStr("ERROR: Not enough space to save core dump!"); - return; - } - - // TEST READ START - err = spi_flash_read_panic(s_core_part_start + 0, &rom_data, sizeof(rom_data)); - if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to read flash "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); - return; - } - else { - esp_panicPutStr("Data from flash:\r\n"); - for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) { - esp_panicPutHex(rom_data.data32[i]); - esp_panicPutStr("\r\n"); - } -// rom_data[4] = 0; -// esp_panicPutStr(rom_data); -// esp_panicPutStr("\r\n"); - } - // TEST READ END - - sec_num = data_len / SPI_FLASH_SEC_SIZE; - if (data_len % SPI_FLASH_SEC_SIZE) - sec_num++; - err = spi_flash_erase_range_panic(s_core_part_start + 0, sec_num * SPI_FLASH_SEC_SIZE); - if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to erase flash "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); - return; - } - - rom_data.data32[0] = COREDUMP_FLASH_MAGIC_START; - rom_data.data32[1] = data_len; - rom_data.data32[2] = task_num; - rom_data.data32[3] = tcb_sz; - err = spi_flash_write_panic(s_core_part_start + 0, &rom_data, sizeof(rom_data)); - if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to write core dump header "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); - return; - } - off = sizeof(rom_data); - - for (i = 0; i < task_num; i++) { - esp_panicPutStr("Dump task "); - esp_panicPutHex((int)tasks[i].pxTCB); - esp_panicPutStr("\r\n"); - - // save TCB address, stack base and stack top addr - rom_data.data32[0] = (uint32_t)tasks[i].pxTCB; - rom_data.data32[1] = (uint32_t)tasks[i].pxTopOfStack; - rom_data.data32[2] = (uint32_t)tasks[i].pxEndOfStack; - err = spi_flash_write_panic(s_core_part_start + off, &rom_data, 3*sizeof(uint32_t)); - if (err != ESP_OK) { - esp_panicPutStr("ERROR: Failed to write task header "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); - return; - } - off += 3*sizeof(uint32_t); - // save TCB - len = esp_core_dump_write_flash_padded(s_core_part_start + off, tasks[i].pxTCB, tcb_sz); - if (len == 0) - return; - off += len; - // save task stack - /*int k; - for (k = 0; k < 8*4; k++) { - esp_panicPutStr("stack["); - esp_panicPutDec(k); - esp_panicPutStr("] = "); - esp_panicPutHex(((uint8_t *)tasks[i].pxTopOfStack)[k]); - esp_panicPutStr("\r\n"); - }*/ - len = esp_core_dump_write_flash_padded(s_core_part_start + off, -#if( portSTACK_GROWTH < 0 ) - tasks[i].pxTopOfStack, - (uint32_t)tasks[i].pxEndOfStack - (uint32_t)tasks[i].pxTopOfStack -#else - tasks[i].pxEndOfStack, - (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack -#endif - ); - if (len == 0) - return; - off += len; - } - - rom_data.data32[0] = COREDUMP_FLASH_MAGIC_END; - err = spi_flash_write_panic(s_core_part_start + off, &rom_data, sizeof(uint32_t)); - if (err != ESP_OK) { - esp_panicPutStr("Failed to write to flash "); - esp_panicPutHex(err); - esp_panicPutStr("!\r\n"); - return; - } -#endif - esp_panicPutStr("Core dump has been saved to flash.\r\n"); + ets_printf("Save core dump to flash...\r\n"); + esp_core_dump_write(frame, &wr_cfg, 0); + ets_printf("Core dump has been saved to flash.\r\n"); } #endif #if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART -#if 0 -#define BASE64_ENCODE_BODY(_src, _src_len, _dst) \ - do { \ - static const char *b64 = \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; \ - int i, j, a, b, c; \ - \ - for (i = j = 0; i < _src_len; i += 3) { \ - a = _src[i]; \ - b = i + 1 >= _src_len ? 0 : _src[i + 1]; \ - c = i + 2 >= _src_len ? 0 : _src[i + 2]; \ - \ - /*BASE64_OUT(b64[a >> 2], _dst[j]);*/ \ - _dst[j++] = b64[a >> 2]; \ - /*BASE64_OUT(b64[((a & 3) << 4) | (b >> 4)], _dst[j]);*/ \ - _dst[j++] = b64[((a & 3) << 4) | (b >> 4)]; \ - j++; \ - if (i + 1 < _src_len) { \ - BASE64_OUT(b64[(b & 15) << 2 | (c >> 6)], _dst[j]); \ - j++; \ - } \ - if (i + 2 < _src_len) { \ - BASE64_OUT(b64[c & 63], _dst[j]); \ - j++; \ - } \ - } \ - \ - while (j % 4 != 0) { \ - BASE64_OUT('=', _dst); \ - } \ - BASE64_FLUSH(_dst) \ - } while(0) - -#define BASE64_OUT(ch, _dst) \ - do { \ - _dst = (ch); \ - } while (0) - -#define BASE64_FLUSH(_dst) \ - do { \ - _dst = '\0'; \ - } while (0) -#endif static void esp_core_dump_b64_encode(const uint8_t *src, uint32_t src_len, uint8_t *dst) { // BASE64_ENCODE_BODY(src, src_len, dst); static const char *b64 = @@ -618,38 +372,26 @@ static void esp_core_dump_b64_encode(const uint8_t *src, uint32_t src_len, uint8 dst[j++] = '\0'; } -/*static esp_err_t esp_core_dump_uart_write_prepare(void *priv, uint32_t *data_len) +static esp_err_t esp_core_dump_uart_write_start(void *priv, int verb) { esp_err_t err = ESP_OK; - return err; -}*/ - -static esp_err_t esp_core_dump_uart_write_start(void *priv) -{ -// core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; - esp_err_t err = ESP_OK; - esp_panicPutStr("================= CORE DUMP START =================\r\n"); + ets_printf("================= CORE DUMP START =================\r\n"); return err; } -static esp_err_t esp_core_dump_uart_write_end(void *priv) +static esp_err_t esp_core_dump_uart_write_end(void *priv, int verb) { -// core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; esp_err_t err = ESP_OK; - esp_panicPutStr("================= CORE DUMP END =================\r\n"); + ets_printf("================= CORE DUMP END =================\r\n"); return err; } -static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t data_len) +static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t data_len, int verb) { -// core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; esp_err_t err = ESP_OK; char buf[64 + 4], *addr = data; char *end = addr + data_len; -// esp_panicPutStr("CORE DUMP SEC: "); -// esp_panicPutDec(data_len); -// esp_panicPutStr("bytes\r\n"); while (addr < end) { size_t len = end - addr; @@ -659,15 +401,7 @@ static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t memcpy(tmp, addr, len); esp_core_dump_b64_encode((const uint8_t *)tmp, len, (uint8_t *)buf); addr += len; - esp_panicPutStr(buf); -// for (size_t i = 0; buf[i] != '\0'; i++) { -// panicPutChar(buf[i]); -// } - //if (addr % 96 == 0) - esp_panicPutStr("\r\n"); - /* Feed the Cerberus. */ -// TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE; -// TIMERG0.wdt_feed = 1; + ets_printf("%s\r\n", buf); } return err; @@ -676,17 +410,16 @@ static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t void esp_core_dump_to_uart(XtExcFrame *frame) { core_dump_write_config_t wr_cfg; - //core_dump_write_flash_data_t wr_data; - wr_cfg.prepare = NULL;//esp_core_dump_uart_write_prepare; + wr_cfg.prepare = NULL; wr_cfg.start = esp_core_dump_uart_write_start; wr_cfg.end = esp_core_dump_uart_write_end; wr_cfg.write = esp_core_dump_uart_write_data; wr_cfg.priv = NULL; - esp_panicPutStr("Save core dump to flash...\r\n"); + ets_printf("Print core dump to uart...\r\n"); esp_core_dump_write(frame, &wr_cfg, 0); - esp_panicPutStr("Core dump has been written to uart.\r\n"); + ets_printf("Core dump has been written to uart.\r\n"); } #endif diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 1b8a95b75c..2ae4260bd5 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -203,6 +203,8 @@ void start_cpu0_default(void) #endif esp_ipc_init(); spi_flash_init(); + /* init default OS-aware flash access critical section */ + spi_flash_guard_set(&g_flash_guard_default_ops); #if CONFIG_ESP32_PHY_AUTO_INIT nvs_flash_init(); diff --git a/components/esp32/include/esp_panic.h b/components/esp32/include/esp_panic.h index 3dd5d4b185..aa83c6d381 100644 --- a/components/esp32/include/esp_panic.h +++ b/components/esp32/include/esp_panic.h @@ -24,10 +24,6 @@ */ void esp_set_breakpoint_if_jtag(void *fn); -void esp_panicPutChar(char c); -void esp_panicPutStr(const char *c); -void esp_panicPutHex(int a); -void esp_panicPutDec(int a); #define ESP_WATCHPOINT_LOAD 0x40000000 #define ESP_WATCHPOINT_STORE 0x80000000 diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 94aebfbac4..09ce520ded 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -47,61 +47,61 @@ #if !CONFIG_ESP32_PANIC_SILENT_REBOOT //printf may be broken, so we fix our own printing fns... -void esp_panicPutChar(char c) +static void panicPutChar(char c) { while (((READ_PERI_REG(UART_STATUS_REG(0)) >> UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT) >= 126) ; WRITE_PERI_REG(UART_FIFO_REG(0), c); } -void esp_panicPutStr(const char *c) +static void panicPutStr(const char *c) { int x = 0; while (c[x] != 0) { - esp_panicPutChar(c[x]); + panicPutChar(c[x]); x++; } } -void esp_panicPutHex(int a) +static void panicPutHex(int a) { int x; int c; for (x = 0; x < 8; x++) { c = (a >> 28) & 0xf; if (c < 10) { - esp_panicPutChar('0' + c); + panicPutChar('0' + c); } else { - esp_panicPutChar('a' + c - 10); + panicPutChar('a' + c - 10); } a <<= 4; } } -void esp_panicPutDec(int a) +static void panicPutDec(int a) { int n1, n2; n1 = a % 10; n2 = a / 10; if (n2 == 0) { - esp_panicPutChar(' '); + panicPutChar(' '); } else { - esp_panicPutChar(n2 + '0'); + panicPutChar(n2 + '0'); } - esp_panicPutChar(n1 + '0'); + panicPutChar(n1 + '0'); } #else //No printing wanted. Stub out these functions. -void esp_panicPutChar(char c) { } -void esp_panicPutStr(const char *c) { } -void esp_panicPutHex(int a) { } -void esp_panicPutDec(int a) { } +static void panicPutChar(char c) { } +static void panicPutStr(const char *c) { } +static void panicPutHex(int a) { } +static void panicPutDec(int a) { } #endif void __attribute__((weak)) vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName ) { - esp_panicPutStr("***ERROR*** A stack overflow in task "); - esp_panicPutStr((char *)pcTaskName); - esp_panicPutStr(" has been detected.\r\n"); + panicPutStr("***ERROR*** A stack overflow in task "); + panicPutStr((char *)pcTaskName); + panicPutStr(" has been detected.\r\n"); abort(); } @@ -220,25 +220,25 @@ void xt_unhandled_exception(XtExcFrame *frame) int x; haltOtherCore(); - esp_panicPutStr("Guru Meditation Error of type "); + panicPutStr("Guru Meditation Error of type "); x = regs[20]; if (x < 40) { - esp_panicPutStr(edesc[x]); + panicPutStr(edesc[x]); } else { - esp_panicPutStr("Unknown"); + panicPutStr("Unknown"); } - esp_panicPutStr(" occurred on core "); - esp_panicPutDec(xPortGetCoreID()); + panicPutStr(" occurred on core "); + panicPutDec(xPortGetCoreID()); if (esp_cpu_in_ocd_debug_mode()) { - esp_panicPutStr(" at pc="); - esp_panicPutHex(regs[1]); - esp_panicPutStr(". Setting bp and returning..\r\n"); + panicPutStr(" at pc="); + panicPutHex(regs[1]); + panicPutStr(". Setting bp and returning..\r\n"); //Stick a hardware breakpoint on the address the handler returns to. This way, the OCD debugger //will kick in exactly at the context the error happened. setFirstBreakpoint(regs[1]); return; } - esp_panicPutStr(". Exception was unhandled.\r\n"); + panicPutStr(". Exception was unhandled.\r\n"); commonErrorHandler(frame); } @@ -293,16 +293,16 @@ static void putEntry(uint32_t pc, uint32_t sp) if (pc & 0x80000000) { pc = (pc & 0x3fffffff) | 0x40000000; } - esp_panicPutStr(" 0x"); - esp_panicPutHex(pc); - esp_panicPutStr(":0x"); - esp_panicPutHex(sp); + panicPutStr(" 0x"); + panicPutHex(pc); + panicPutStr(":0x"); + panicPutHex(sp); } static void doBacktrace(XtExcFrame *frame) { uint32_t i = 0, pc = frame->pc, sp = frame->a1; - esp_panicPutStr("\r\nBacktrace:"); + panicPutStr("\r\nBacktrace:"); /* Do not check sanity on first entry, PC could be smashed. */ putEntry(pc, sp); pc = frame->a0; @@ -318,7 +318,7 @@ static void doBacktrace(XtExcFrame *frame) break; } } - esp_panicPutStr("\r\n\r\n"); + panicPutStr("\r\n\r\n"); } /* @@ -342,17 +342,17 @@ static void commonErrorHandler(XtExcFrame *frame) the register window is no longer useful. */ if (!abort_called) { - esp_panicPutStr("Register dump:\r\n"); + panicPutStr("Register dump:\r\n"); for (x = 0; x < 24; x += 4) { for (y = 0; y < 4; y++) { if (sdesc[x + y][0] != 0) { - esp_panicPutStr(sdesc[x + y]); - esp_panicPutStr(": 0x"); - esp_panicPutHex(regs[x + y + 1]); - esp_panicPutStr(" "); + panicPutStr(sdesc[x + y]); + panicPutStr(": 0x"); + panicPutHex(regs[x + y + 1]); + panicPutStr(" "); } - esp_panicPutStr("\r\n"); + panicPutStr("\r\n"); } } } @@ -362,7 +362,7 @@ static void commonErrorHandler(XtExcFrame *frame) #if CONFIG_ESP32_PANIC_GDBSTUB disableAllWdts(); - esp_panicPutStr("Entering gdb stub now.\r\n"); + panicPutStr("Entering gdb stub now.\r\n"); esp_gdbstub_panic_handler(frame); #else #if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH @@ -372,14 +372,14 @@ static void commonErrorHandler(XtExcFrame *frame) esp_core_dump_to_uart(frame); #endif #if CONFIG_ESP32_PANIC_PRINT_REBOOT || CONFIG_ESP32_PANIC_SILENT_REBOOT - esp_panicPutStr("Rebooting...\r\n"); + panicPutStr("Rebooting...\r\n"); for (x = 0; x < 100; x++) { ets_delay_us(1000); } software_reset(); #else disableAllWdts(); - esp_panicPutStr("CPU halted.\r\n"); + panicPutStr("CPU halted.\r\n"); while (1); #endif #endif diff --git a/components/espcoredump/espcoredump.py b/components/espcoredump/espcoredump.py index 2f31d3f292..2942c609f8 100755 --- a/components/espcoredump/espcoredump.py +++ b/components/espcoredump/espcoredump.py @@ -24,6 +24,11 @@ except ImportError: __version__ = "0.1-dev" +if os.name == 'nt': + CLOSE_FDS = False +else: + CLOSE_FDS = True + class Struct(object): def __init__(self, buf=None): @@ -492,6 +497,10 @@ class ESPCoreDumpLoader(object): print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % ( regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0], regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3]) + # FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set + # and GDB can not unwind callstack properly (it implies not windowed call0) + if regs[REG_PS_IDX] & (1 << 5): + regs[REG_PS_IDX] &= ~(1 << 4) else: print "SOLSTACKFRAME %d" % rc regs[REG_PC_IDX] = stack[XT_SOL_PC] @@ -516,18 +525,18 @@ class ESPCoreDumpLoader(object): os.remove(fname) except OSError as e: if e.errno != errno.ENOENT: - print "Warning failed to remove temp file '%s'!" % fname + print "Warning failed to remove temp file '%s' (%d)!" % (fname, e.errno) def cleanup(self): if self.fcore: self.fcore.close() - self.remove_tmp_file(self.fcore.name) + if self.fcore_name: + self.remove_tmp_file(self.fcore_name) def create_corefile(self, core_fname=None, off=0): """ TBD """ core_off = off - print "Read core dump header" data = self.read_data(core_off, self.ESP32_COREDUMP_HDR_SZ) tot_len,task_num,tcbsz = struct.unpack_from(self.ESP32_COREDUMP_HDR_FMT, data) tcbsz_aligned = tcbsz @@ -538,7 +547,6 @@ class ESPCoreDumpLoader(object): core_elf = ESPCoreDumpElfFile() notes = b'' for i in range(task_num): - print "Read task[%d] header" % i data = self.read_data(core_off, self.ESP32_COREDUMP_TSK_HDR_SZ) tcb_addr,stack_top,stack_end = struct.unpack_from(self.ESP32_COREDUMP_TSK_HDR_FMT, data) if stack_end > stack_top: @@ -554,7 +562,6 @@ class ESPCoreDumpLoader(object): stack_len_aligned = 4*(stack_len_aligned/4 + 1) core_off += self.ESP32_COREDUMP_TSK_HDR_SZ - print "Read task[%d] TCB" % i data = self.read_data(core_off, tcbsz_aligned) if tcbsz != tcbsz_aligned: core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) @@ -562,15 +569,17 @@ class ESPCoreDumpLoader(object): core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) # print "tcb=%s" % data core_off += tcbsz_aligned - print "Read task[%d] stack %d bytes" % (i,stack_len) data = self.read_data(core_off, stack_len_aligned) # print "stk=%s" % data if stack_len != stack_len_aligned: data = data[:stack_len - stack_len_aligned] core_elf.add_program_segment(stack_base, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) core_off += stack_len_aligned - - task_regs = self._get_registers_from_stack(data, stack_end > stack_top) + try: + task_regs = self._get_registers_from_stack(data, stack_end > stack_top) + except Exception as e: + print e + return None prstatus = XtensaPrStatus() prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task prstatus.pr_pid = i # TODO: use pid assigned by OS @@ -608,32 +617,38 @@ class ESPCoreDumpFileLoader(ESPCoreDumpLoader): self.fcore = self._load_coredump(path, b64) def _load_coredump(self, path, b64): + """Loads core dump from (raw binary or base64-encoded) file + """ + self.fcore_name = None if b64: - fhnd,fname = tempfile.mkstemp() - print "tmpname %s" % fname + fhnd,self.fcore_name = tempfile.mkstemp() fcore = os.fdopen(fhnd, 'wb') - fb64 = open(path, 'r') + fb64 = open(path, 'rb') try: while True: line = fb64.readline() if len(line) == 0: break - data = base64.b64decode(line.rstrip('\r\n'))#, validate=True) + data = base64.standard_b64decode(line.rstrip('\r\n')) fcore.write(data) fcore.close() - fcore = open(fname, 'r') + fcore = open(self.fcore_name, 'rb') + except Exception as e: + if self.fcore_name: + self.remove_tmp_file(self.fcore_name) + raise e finally: fb64.close() else: - fcore = open(path, 'r') + fcore = open(path, 'rb') return fcore class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): """ TBD """ - ESP32_COREDUMP_FLASH_MAGIC_START = 0xDEADBEEF - ESP32_COREDUMP_FLASH_MAGIC_END = 0xACDCFEED + ESP32_COREDUMP_FLASH_MAGIC_START = 0xE32C04ED + ESP32_COREDUMP_FLASH_MAGIC_END = 0xE32C04ED ESP32_COREDUMP_FLASH_MAGIC_FMT = 'start();}while(0) #define FLASH_GUARD_END(_gp_) do{if((_gp_)) (_gp_)->end();}while(0) static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc); -static esp_err_t spi_flash_erase_range_internal(uint32_t start_addr, uint32_t size, const spi_flash_guard_funcs_t *flash_guard); -static esp_err_t spi_flash_write_internal(size_t dst, const void *srcv, size_t size, const spi_flash_guard_funcs_t *flash_guard); -static esp_err_t spi_flash_read_internal(size_t src, void *dstv, size_t size, const spi_flash_guard_funcs_t *flash_guard); -const DRAM_ATTR spi_flash_guard_funcs_t s_flash_guard_ops = { +const DRAM_ATTR spi_flash_guard_funcs_t g_flash_guard_default_ops = { .start = spi_flash_disable_interrupts_caches_and_other_cpu, .end = spi_flash_enable_interrupts_caches_and_other_cpu }; -const DRAM_ATTR spi_flash_guard_funcs_t s_flash_guard_panic_ops = { - .start = spi_flash_disable_interrupts_caches_and_other_cpu_panic, - .end = spi_flash_enable_interrupts_caches_panic +const DRAM_ATTR spi_flash_guard_funcs_t g_flash_guard_no_os_ops = { + .start = spi_flash_disable_interrupts_caches_and_other_cpu_no_os, + .end = spi_flash_enable_interrupts_caches_no_os }; +static const spi_flash_guard_funcs_t *s_flash_guard_ops; + void spi_flash_init() { spi_flash_init_lock(); @@ -96,6 +83,11 @@ void spi_flash_init() #endif } +void spi_flash_guard_set(const spi_flash_guard_funcs_t* funcs) +{ + s_flash_guard_ops = funcs; +} + size_t spi_flash_get_chip_size() { return g_rom_flashchip.chip_size; @@ -120,16 +112,6 @@ esp_err_t IRAM_ATTR spi_flash_erase_sector(size_t sec) } esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) -{ - return spi_flash_erase_range_internal(start_addr, size, &s_flash_guard_ops); -} - -esp_err_t IRAM_ATTR spi_flash_erase_range_panic(uint32_t start_addr, uint32_t size) -{ - return spi_flash_erase_range_internal(start_addr, size, &s_flash_guard_panic_ops); -} - -static esp_err_t IRAM_ATTR spi_flash_erase_range_internal(uint32_t start_addr, uint32_t size, const spi_flash_guard_funcs_t *flash_guard) { if (start_addr % SPI_FLASH_SEC_SIZE != 0) { return ESP_ERR_INVALID_ARG; @@ -144,7 +126,7 @@ static esp_err_t IRAM_ATTR spi_flash_erase_range_internal(uint32_t start_addr, u size_t end = start + size / SPI_FLASH_SEC_SIZE; const size_t sectors_per_block = BLOCK_ERASE_SIZE / SPI_FLASH_SEC_SIZE; COUNTER_START(); - FLASH_GUARD_START(flash_guard); + FLASH_GUARD_START(s_flash_guard_ops); SpiFlashOpResult rc; rc = spi_flash_unlock(); if (rc == SPI_FLASH_RESULT_OK) { @@ -160,22 +142,12 @@ static esp_err_t IRAM_ATTR spi_flash_erase_range_internal(uint32_t start_addr, u } } } - FLASH_GUARD_END(flash_guard); + FLASH_GUARD_END(s_flash_guard_ops); COUNTER_STOP(erase); return spi_flash_translate_rc(rc); } esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) -{ - return spi_flash_write_internal(dst, srcv, size, &s_flash_guard_ops); -} - -esp_err_t IRAM_ATTR spi_flash_write_panic(size_t dst, const void *srcv, size_t size) -{ - return spi_flash_write_internal(dst, srcv, size, &s_flash_guard_panic_ops); -} - -static esp_err_t IRAM_ATTR spi_flash_write_internal(size_t dst, const void *srcv, size_t size, const spi_flash_guard_funcs_t *flash_guard) { // Out of bound writes are checked in ROM code, but we can give better // error code here @@ -208,9 +180,9 @@ static esp_err_t IRAM_ATTR spi_flash_write_internal(size_t dst, const void *srcv if (left_size > 0) { uint32_t t = 0xffffffff; memcpy(((uint8_t *) &t) + (dst - left_off), srcc, left_size); - FLASH_GUARD_START(flash_guard); + FLASH_GUARD_START(s_flash_guard_ops); rc = SPIWrite(left_off, &t, 4); - FLASH_GUARD_END(flash_guard); + FLASH_GUARD_END(s_flash_guard_ops); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -226,9 +198,9 @@ static esp_err_t IRAM_ATTR spi_flash_write_internal(size_t dst, const void *srcv bool in_dram = true; #endif if (in_dram && (((uintptr_t) srcc) + mid_off) % 4 == 0) { - FLASH_GUARD_START(flash_guard); + FLASH_GUARD_START(s_flash_guard_ops); rc = SPIWrite(dst + mid_off, (const uint32_t *) (srcc + mid_off), mid_size); - FLASH_GUARD_END(flash_guard); + FLASH_GUARD_END(s_flash_guard_ops); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -242,9 +214,9 @@ static esp_err_t IRAM_ATTR spi_flash_write_internal(size_t dst, const void *srcv uint32_t t[8]; uint32_t write_size = MIN(mid_size, sizeof(t)); memcpy(t, srcc + mid_off, write_size); - FLASH_GUARD_START(flash_guard); + FLASH_GUARD_START(s_flash_guard_ops); rc = SPIWrite(dst + mid_off, t, write_size); - FLASH_GUARD_END(flash_guard); + FLASH_GUARD_END(s_flash_guard_ops); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -257,9 +229,9 @@ static esp_err_t IRAM_ATTR spi_flash_write_internal(size_t dst, const void *srcv if (right_size > 0) { uint32_t t = 0xffffffff; memcpy(&t, srcc + right_off, right_size); - FLASH_GUARD_START(flash_guard); + FLASH_GUARD_START(s_flash_guard_ops); rc = SPIWrite(dst + right_off, &t, 4); - FLASH_GUARD_END(flash_guard); + FLASH_GUARD_END(s_flash_guard_ops); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -307,16 +279,6 @@ esp_err_t IRAM_ATTR spi_flash_write_encrypted(size_t dest_addr, const void *src, } esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size) -{ - return spi_flash_read_internal(src, dstv, size, &s_flash_guard_ops); -} - -esp_err_t IRAM_ATTR spi_flash_read_panic(size_t src, void *dstv, size_t size) -{ - return spi_flash_read_internal(src, dstv, size, &s_flash_guard_panic_ops); -} - -static esp_err_t IRAM_ATTR spi_flash_read_internal(size_t src, void *dstv, size_t size, const spi_flash_guard_funcs_t *flash_guard) { // Out of bound reads are checked in ROM code, but we can give better // error code here @@ -329,7 +291,7 @@ static esp_err_t IRAM_ATTR spi_flash_read_internal(size_t src, void *dstv, size_ SpiFlashOpResult rc = SPI_FLASH_RESULT_OK; COUNTER_START(); - FLASH_GUARD_START(flash_guard); + FLASH_GUARD_START(s_flash_guard_ops); /* To simplify boundary checks below, we handle small reads separately. */ if (size < 16) { uint32_t t[6]; /* Enough for 16 bytes + 4 on either side for padding. */ @@ -403,7 +365,7 @@ static esp_err_t IRAM_ATTR spi_flash_read_internal(size_t src, void *dstv, size_ memcpy(dstc + pad_right_off, t, pad_right_size); } out: - FLASH_GUARD_END(flash_guard); + FLASH_GUARD_END(s_flash_guard_ops); COUNTER_STOP(read); return spi_flash_translate_rc(rc); } diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index e78c389b4e..402cd6e6f7 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -173,12 +173,26 @@ void spi_flash_munmap(spi_flash_mmap_handle_t handle); */ void spi_flash_mmap_dump(); +/** + * @brief SPI flash critical section enter function. + */ +typedef void (*spi_flash_guard_start_func_t)(void); +/** + * @brief SPI flash critical section exit function. + */ +typedef void (*spi_flash_guard_end_func_t)(void); + +/** + * Structure holding SPI flash access critical section management functions + */ +typedef struct { + spi_flash_guard_start_func_t start; /**< critical section start func */ + spi_flash_guard_end_func_t end; /**< critical section end func */ +} spi_flash_guard_funcs_t; + /** * @brief Erase a range of flash sectors. * - * @note This version of function is to be called from panic handler. - * It does not use any OS primitives and IPC and implies that - * only calling CPU is active. * * @param start_address Address where erase operation has to start. * Must be 4kB-aligned @@ -186,42 +200,18 @@ void spi_flash_mmap_dump(); * * @return esp_err_t */ -esp_err_t spi_flash_erase_range_panic(size_t start_address, size_t size); +void spi_flash_guard_set(const spi_flash_guard_funcs_t* funcs); +/** Default OS-aware flash access critical section functions */ +extern const spi_flash_guard_funcs_t g_flash_guard_default_ops; -/** - * @brief Write data to Flash. +/** Non-OS flash access critical section functions * - * @note This version of function is to be called from panic handler. + * @note This version of functions is to be used when no OS is present or from panic handler. * It does not use any OS primitives and IPC and implies that * only calling CPU is active. - - * @note If source address is in DROM, this function will return - * ESP_ERR_INVALID_ARG. - * - * @param dest destination address in Flash. Must be a multiple of 4 bytes. - * @param src pointer to the source buffer. - * @param size length of data, in bytes. Must be a multiple of 4 bytes. - * - * @return esp_err_t */ -esp_err_t spi_flash_write_panic(size_t dest, const void *src, size_t size); - - -/** - * @brief Read data from Flash. - * - * @note This version of function is to be called from panic handler. - * It does not use any OS primitives and IPC and implies that - * only calling CPU is active. - * - * @param src source address of the data in Flash. - * @param dest pointer to the destination buffer - * @param size length of data - * - * @return esp_err_t - */ -esp_err_t spi_flash_read_panic(size_t src, void *dest, size_t size); +extern const spi_flash_guard_funcs_t g_flash_guard_no_os_ops; #if CONFIG_SPI_FLASH_ENABLE_COUNTERS From 50b3ce616f5132ccb8b25162932129e2f6d33aa0 Mon Sep 17 00:00:00 2001 From: Alexey Gerenkov Date: Fri, 6 Jan 2017 13:06:43 +0300 Subject: [PATCH 141/167] esp32: Adds documentation and comments to core dump feature files --- components/esp32/core_dump.c | 10 - components/esp32/include/esp_core_dump.h | 43 +++ components/espcoredump/espcoredump.py | 344 ++++++++++--------- components/freertos/include/freertos/task.h | 10 +- components/spi_flash/cache_utils.h | 10 +- components/spi_flash/include/esp_spi_flash.h | 25 +- docs/Doxyfile | 3 +- docs/core_dump.rst | 127 +++++++ 8 files changed, 391 insertions(+), 181 deletions(-) create mode 100644 docs/core_dump.rst diff --git a/components/esp32/core_dump.c b/components/esp32/core_dump.c index e12fc1d71c..13519badb7 100644 --- a/components/esp32/core_dump.c +++ b/components/esp32/core_dump.c @@ -316,15 +316,6 @@ static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_ return err; } -/* - * | MAGIC1 | - * | TOTAL_LEN | TASKS_NUM | TCB_SIZE | - * | TCB_ADDR_1 | STACK_TOP_1 | STACK_END_1 | TCB_1 | STACK_1 | - * . . . . - * . . . . - * | TCB_ADDR_N | STACK_TOP_N | STACK_END_N | TCB_N | STACK_N | - * | MAGIC2 | - */ void esp_core_dump_to_flash(XtExcFrame *frame) { core_dump_write_config_t wr_cfg; @@ -347,7 +338,6 @@ void esp_core_dump_to_flash(XtExcFrame *frame) #if CONFIG_ESP32_ENABLE_COREDUMP_TO_UART static void esp_core_dump_b64_encode(const uint8_t *src, uint32_t src_len, uint8_t *dst) { -// BASE64_ENCODE_BODY(src, src_len, dst); static const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int i, j, a, b, c; diff --git a/components/esp32/include/esp_core_dump.h b/components/esp32/include/esp_core_dump.h index d130aa237b..c6634364c5 100644 --- a/components/esp32/include/esp_core_dump.h +++ b/components/esp32/include/esp_core_dump.h @@ -14,8 +14,51 @@ #ifndef ESP_CORE_DUMP_H_ #define ESP_CORE_DUMP_H_ +/** + * @brief Initializes core dump module internal data. + * + * @note Should be called at system startup. + */ void esp_core_dump_init(); + +/** + * @brief Saves core dump to flash. + * + * The structure of data stored in flash is as follows: + * | MAGIC1 | + * | TOTAL_LEN | TASKS_NUM | TCB_SIZE | + * | TCB_ADDR_1 | STACK_TOP_1 | STACK_END_1 | TCB_1 | STACK_1 | + * . . . . + * . . . . + * | TCB_ADDR_N | STACK_TOP_N | STACK_END_N | TCB_N | STACK_N | + * | MAGIC2 | + * Core dump in flash consists of header and data for every task in the system at the moment of crash. + * For flash data integrity control two magic numbers are used at the beginning and the end of core dump. + * The structure of core dump data is described below in details. + * 1) MAGIC1 and MAGIC2 are special numbers stored at the beginning and the end of core dump. + * They are used to control core dump data integrity. Size of every number is 4 bytes. + * 2) Core dump starts with header: + * 2.1) TOTAL_LEN is total length of core dump data in flash including magic numbers. Size is 4 bytes. + * 2.2) TASKS_NUM is the number of tasks for which data are stored. Size is 4 bytes. + * 2.3) TCB_SIZE is the size of task's TCB structure. Size is 4 bytes. + * 3) Core dump header is followed by the data for every task in the system. + * Task data are started with task header: + * 3.1) TCB_ADDR is the address of TCB in memory. Size is 4 bytes. + * 3.2) STACK_TOP is the top of task's stack (address of the topmost stack item). Size is 4 bytes. + * 3.2) STACK_END is the end of task's stack (address from which task's stack starts). Size is 4 bytes. + * 4) Task header is followed by TCB data. Size is TCB_SIZE bytes. + * 5) Task's stack is placed after TCB data. Size is (STACK_END - STACK_TOP) bytes. + */ void esp_core_dump_to_flash(); + +/** + * @brief Print base64-encoded core dump to UART. + * + * The structure of core dump data is the same as for data stored in flash (@see esp_core_dump_to_flash) with some notes: + * 1) Magic numbers are not present in core dump printed to UART. + * 2) Since magic numbers are omitted TOTAL_LEN does not include their size. + * 3) Printed base64 data are surrounded with special messages to help user recognize the start and end of actual data. + */ void esp_core_dump_to_uart(); #endif diff --git a/components/espcoredump/espcoredump.py b/components/espcoredump/espcoredump.py index 2942c609f8..1589a362b9 100755 --- a/components/espcoredump/espcoredump.py +++ b/components/espcoredump/espcoredump.py @@ -30,34 +30,63 @@ else: CLOSE_FDS = True -class Struct(object): +class ESPCoreDumpError(RuntimeError): + """Core dump runtime error class + """ + def __init__(self, message): + """Constructor for core dump error + """ + super(ESPCoreDumpError, self).__init__(message) + +class BinStruct(object): + """Binary structure representation + + Subclasses must specify actual structure layout using 'fields' and 'format' members. + For example, the following subclass represents structure with two fields: + f1 of size 2 bytes and 4 bytes f2. Little endian. + class SomeStruct(BinStruct): + fields = ("f1", + "f2") + format = " (3, 0): - # Convert strings into bytearrays if this is Python 3 - for k in keys: - if type(self.__dict__[k]) is str: - self.__dict__[k] = bytearray(self.__dict__[k], encoding='ascii') - return struct.pack(self.__class__.fmt, *(self.__dict__[k] for k in keys)) + return struct.pack(self.__class__.format, *(self.__dict__[k] for k in keys)) - def __str__(self): - keys = self.__class__.fields - return (self.__class__.__name__ + "({" + - ", ".join("%s:%r" % (k, self.__dict__[k]) for k in keys) + - "})") +# def __str__(self): +# keys = self.__class__.fields +# return (self.__class__.__name__ + "({" + +# ", ".join("%s:%r" % (k, self.__dict__[k]) for k in keys) + +# "})") -class Elf32FileHeader(Struct): - """ELF32 File header""" +class Elf32FileHeader(BinStruct): + """ELF32 file header + """ fields = ("e_ident", "e_type", "e_machine", @@ -72,9 +101,11 @@ class Elf32FileHeader(Struct): "e_shentsize", "e_shnum", "e_shstrndx") - fmt = "<16sHHLLLLLHHHHHH" + format = "<16sHHLLLLLHHHHHH" def __init__(self, buf=None): + """Constructor for ELF32 file header structure + """ super(Elf32FileHeader, self).__init__(buf) if buf is None: # Fill in sane ELF header for LSB32 @@ -83,8 +114,9 @@ class Elf32FileHeader(Struct): self.e_ehsize = self.sizeof() -class Elf32ProgramHeader(Struct): - """ELF32 Program Header""" +class Elf32ProgramHeader(BinStruct): + """ELF32 program header + """ fields = ("p_type", "p_offset", "p_vaddr", @@ -93,39 +125,37 @@ class Elf32ProgramHeader(Struct): "p_memsz", "p_flags", "p_align") - fmt = " 0: @@ -265,11 +314,13 @@ class ESPCoreDumpElfFile(esptool.ELFFile): self.program_segments = [] def _read_sections(self, f, section_header_offs, shstrndx): + """Reads core dump sections from ELF file + """ f.seek(section_header_offs) section_header = f.read() LEN_SEC_HEADER = 0x28 if len(section_header) == 0: - raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs) + raise ESPCoreDumpError("No section header found at offset %04x in ELF file." % section_header_offs) if len(section_header) % LEN_SEC_HEADER != 0: print 'WARNING: Unexpected ELF section header length %04x is not mod-%02x' % (len(section_header),LEN_SEC_HEADER) @@ -284,7 +335,7 @@ class ESPCoreDumpElfFile(esptool.ELFFile): # search for the string table section if not shstrndx * LEN_SEC_HEADER in section_header_offsets: - raise FatalError("ELF file has no STRTAB section at shstrndx %d" % shstrndx) + raise ESPCoreDumpError("ELF file has no STRTAB section at shstrndx %d" % shstrndx) _,sec_type,_,_,sec_size,sec_offs = read_section_header(shstrndx * LEN_SEC_HEADER) if sec_type != esptool.ELFFile.SEC_TYPE_STRTAB: print 'WARNING: ELF file has incorrect STRTAB section type 0x%02x' % sec_type @@ -306,11 +357,13 @@ class ESPCoreDumpElfFile(esptool.ELFFile): self.sections = prog_sections def _read_program_segments(self, f, seg_table_offs, entsz, num): + """Reads core dump program segments from ELF file + """ f.seek(seg_table_offs) seg_table = f.read(entsz*num) LEN_SEG_HEADER = 0x20 if len(seg_table) == 0: - raise FatalError("No program header table found at offset %04x in ELF file." % seg_table_offs) + raise ESPCoreDumpError("No program header table found at offset %04x in ELF file." % seg_table_offs) if len(seg_table) % LEN_SEG_HEADER != 0: print 'WARNING: Unexpected ELF program header table length %04x is not mod-%02x' % (len(seg_table),LEN_SEG_HEADER) @@ -318,8 +371,8 @@ class ESPCoreDumpElfFile(esptool.ELFFile): seg_table_offs = range(0, len(seg_table), LEN_SEG_HEADER) def read_program_header(offs): - type,offset,vaddr,_paddr,filesz,_memsz,_flags,_align = struct.unpack_from("= ps.addr and addr < (ps.addr + seg_len): - raise FatalError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % + raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) if (addr + data_sz) > ps.addr and (addr + data_sz) <= (ps.addr + seg_len): - raise FatalError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % + raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." % (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1)) # append self.program_segments.append(ESPCoreDumpSegment(addr, data, type, flags)) - # currently dumps only program segments. - # dumping sections is not supported yet def dump(self, f): - print "dump to '%s'" % f + """Write core dump contents to file + """ + # TODO: currently dumps only program segments. + # dumping sections is not supported yet # write ELF header ehdr = Elf32FileHeader() ehdr.e_type = self.e_type @@ -370,9 +424,7 @@ class ESPCoreDumpElfFile(esptool.ELFFile): f.write(ehdr.dump()) # write program header table cur_off = ehdr.e_ehsize + ehdr.e_phnum * ehdr.e_phentsize -# print "" % (ehdr.e_ehsize, ehdr.e_phnum, ehdr.e_phentsize) for i in range(len(self.program_segments)): - print "dump header for seg '%s'" % self.program_segments[i] phdr = Elf32ProgramHeader() phdr.p_type = self.program_segments[i].type phdr.p_offset = cur_off @@ -382,57 +434,52 @@ class ESPCoreDumpElfFile(esptool.ELFFile): phdr.p_memsz = phdr.p_filesz # TODO phdr.p_flags = self.program_segments[i].flags phdr.p_align = 0 # TODO -# print "header '%s'" % phdr f.write(phdr.dump()) cur_off += phdr.p_filesz # write program segments for i in range(len(self.program_segments)): - print "dump seg '%s'" % self.program_segments[i] f.write(self.program_segments[i].data) -class ESPCoreDumpError(RuntimeError): - """ - TBD - """ - def __init__(self, message): - super(ESPCoreDumpError, self).__init__(message) - - class ESPCoreDumpLoaderError(ESPCoreDumpError): - """ - TBD + """Core dump loader error class """ def __init__(self, message): + """Constructor for core dump loader error + """ super(ESPCoreDumpLoaderError, self).__init__(message) class ESPCoreDumpLoader(object): - """ - TBD + """Core dump loader base class """ ESP32_COREDUMP_HDR_FMT = '<3L' ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT) ESP32_COREDUMP_TSK_HDR_FMT = '<3L' ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT) + def __init__(self): + """Base constructor for core dump loader + """ self.fcore = None def _get_registers_from_stack(self, data, grows_down): - # from "gdb/xtensa-tdep.h" - # typedef struct - # { - #0 xtensa_elf_greg_t pc; - #1 xtensa_elf_greg_t ps; - #2 xtensa_elf_greg_t lbeg; - #3 xtensa_elf_greg_t lend; - #4 xtensa_elf_greg_t lcount; - #5 xtensa_elf_greg_t sar; - #6 xtensa_elf_greg_t windowstart; - #7 xtensa_elf_greg_t windowbase; - #8..63 xtensa_elf_greg_t reserved[8+48]; - #64 xtensa_elf_greg_t ar[64]; - # } xtensa_elf_gregset_t; + """Returns list of registers (in GDB format) from xtensa stack frame + """ + # from "gdb/xtensa-tdep.h" + # typedef struct + # { + #0 xtensa_elf_greg_t pc; + #1 xtensa_elf_greg_t ps; + #2 xtensa_elf_greg_t lbeg; + #3 xtensa_elf_greg_t lend; + #4 xtensa_elf_greg_t lcount; + #5 xtensa_elf_greg_t sar; + #6 xtensa_elf_greg_t windowstart; + #7 xtensa_elf_greg_t windowbase; + #8..63 xtensa_elf_greg_t reserved[8+48]; + #64 xtensa_elf_greg_t ar[64]; + # } xtensa_elf_gregset_t; REG_PC_IDX=0 REG_PS_IDX=1 REG_LB_IDX=2 @@ -446,7 +493,7 @@ class ESPCoreDumpLoader(object): # FIXME: acc to xtensa_elf_gregset_t number of regs must be 128, # but gdb complanis when it less then 129 REG_NUM=129 - + XT_SOL_EXIT=0 XT_SOL_PC=1 XT_SOL_PS=2 @@ -454,7 +501,7 @@ class ESPCoreDumpLoader(object): XT_SOL_AR_START=4 XT_SOL_AR_NUM=4 XT_SOL_FRMSZ=8 - + XT_STK_EXIT=0 XT_STK_PC=1 XT_STK_PS=2 @@ -467,25 +514,19 @@ class ESPCoreDumpLoader(object): XT_STK_LEND=23 XT_STK_LCOUNT=24 XT_STK_FRMSZ=25 - + regs = [0] * REG_NUM # TODO: support for growing up stacks if not grows_down: - print "Growing up stacks are not supported for now!" - return regs - # for i in range(REG_NUM): - # regs[i] = i - # return regs + raise ESPCoreDumpLoaderError("Growing up stacks are not supported for now!") ex_struct = "<%dL" % XT_STK_FRMSZ if len(data) < struct.calcsize(ex_struct): - print "Too small stack to keep frame: %d bytes!" % len(data) - return regs - + raise ESPCoreDumpLoaderError("Too small stack to keep frame: %d bytes!" % len(data)) + stack = struct.unpack(ex_struct, data[:struct.calcsize(ex_struct)]) # Stack frame type indicator is always the first item rc = stack[XT_STK_EXIT] if rc != 0: - print "EXCSTACKFRAME %d" % rc regs[REG_PC_IDX] = stack[XT_STK_PC] regs[REG_PS_IDX] = stack[XT_STK_PS] for i in range(XT_STK_AR_NUM): @@ -494,23 +535,16 @@ class ESPCoreDumpLoader(object): regs[REG_LB_IDX] = stack[XT_STK_LBEG] regs[REG_LE_IDX] = stack[XT_STK_LEND] regs[REG_LC_IDX] = stack[XT_STK_LCOUNT] - print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % ( - regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0], - regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3]) # FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set # and GDB can not unwind callstack properly (it implies not windowed call0) if regs[REG_PS_IDX] & (1 << 5): regs[REG_PS_IDX] &= ~(1 << 4) else: - print "SOLSTACKFRAME %d" % rc regs[REG_PC_IDX] = stack[XT_SOL_PC] regs[REG_PS_IDX] = stack[XT_SOL_PS] for i in range(XT_SOL_AR_NUM): regs[REG_AR_START_IDX + i] = stack[XT_SOL_AR_START + i] nxt = stack[XT_SOL_NEXT] - print "get_registers_from_stack: pc %x ps %x a0 %x a1 %x a2 %x a3 %x" % ( - regs[REG_PC_IDX], regs[REG_PS_IDX], regs[REG_AR_NUM + 0], - regs[REG_AR_NUM + 1], regs[REG_AR_NUM + 2], regs[REG_AR_NUM + 3]) # TODO: remove magic hack with saved PC to get proper value regs[REG_PC_IDX] = ((regs[REG_PC_IDX] & 0x3FFFFFFF) | 0x40000000) @@ -521,6 +555,8 @@ class ESPCoreDumpLoader(object): return regs def remove_tmp_file(self, fname): + """Silently removes temporary file + """ try: os.remove(fname) except OSError as e: @@ -528,13 +564,15 @@ class ESPCoreDumpLoader(object): print "Warning failed to remove temp file '%s' (%d)!" % (fname, e.errno) def cleanup(self): + """Cleans up loader resources + """ if self.fcore: self.fcore.close() if self.fcore_name: self.remove_tmp_file(self.fcore_name) def create_corefile(self, core_fname=None, off=0): - """ TBD + """Creates core dump ELF file """ core_off = off data = self.read_data(core_off, self.ESP32_COREDUMP_HDR_SZ) @@ -542,7 +580,6 @@ class ESPCoreDumpLoader(object): tcbsz_aligned = tcbsz if tcbsz_aligned % 4: tcbsz_aligned = 4*(tcbsz_aligned/4 + 1) - print "tot_len=%d, task_num=%d, tcbsz=%d" % (tot_len,task_num,tcbsz) core_off += self.ESP32_COREDUMP_HDR_SZ core_elf = ESPCoreDumpElfFile() notes = b'' @@ -555,7 +592,6 @@ class ESPCoreDumpLoader(object): else: stack_len = stack_top - stack_end stack_base = stack_end - print "tcb_addr=%x, stack_top=%x, stack_end=%x, stack_len=%d" % (tcb_addr,stack_top,stack_end,stack_len) stack_len_aligned = stack_len if stack_len_aligned % 4: @@ -567,10 +603,8 @@ class ESPCoreDumpLoader(object): core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) else: core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) - # print "tcb=%s" % data core_off += tcbsz_aligned data = self.read_data(core_off, stack_len_aligned) - # print "stk=%s" % data if stack_len != stack_len_aligned: data = data[:stack_len - stack_len_aligned] core_elf.add_program_segment(stack_base, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W) @@ -584,7 +618,6 @@ class ESPCoreDumpLoader(object): prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task prstatus.pr_pid = i # TODO: use pid assigned by OS note = Elf32NoteDesc("CORE", 1, prstatus.dump() + struct.pack("<%dL" % len(task_regs), *task_regs)).dump() - print "NOTE_LEN %d" % len(note) notes += note # add notes @@ -602,20 +635,22 @@ class ESPCoreDumpLoader(object): return core_fname def read_data(self, off, sz): -# print "read_data: %x %d" % (off, sz) + """Reads data from raw core dump got from flash or UART + """ self.fcore.seek(off) data = self.fcore.read(sz) -# print "data1: %s" % data return data class ESPCoreDumpFileLoader(ESPCoreDumpLoader): - """ TBD + """Core dump file loader class """ def __init__(self, path, b64 = False): + """Constructor for core dump file loader + """ super(ESPCoreDumpFileLoader, self).__init__() self.fcore = self._load_coredump(path, b64) - + def _load_coredump(self, path, b64): """Loads core dump from (raw binary or base64-encoded) file """ @@ -645,7 +680,7 @@ class ESPCoreDumpFileLoader(ESPCoreDumpLoader): class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): - """ TBD + """Core dump flash loader class """ ESP32_COREDUMP_FLASH_MAGIC_START = 0xE32C04ED ESP32_COREDUMP_FLASH_MAGIC_END = 0xE32C04ED @@ -655,7 +690,8 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): ESP32_COREDUMP_FLASH_HDR_SZ = struct.calcsize(ESP32_COREDUMP_FLASH_HDR_FMT) def __init__(self, off, tool_path=None, chip='esp32', port=None, baud=None): -# print "esptool.__file__ %s" % esptool.__file__ + """Constructor for core dump flash loader + """ super(ESPCoreDumpFlashLoader, self).__init__() if not tool_path: self.path = esptool.__file__ @@ -667,7 +703,7 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): self.chip = chip self.dump_sz = 0 self.fcore = self._load_coredump(off) - + def _load_coredump(self, off): """Loads core dump from flash """ @@ -702,7 +738,8 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): return f def _read_core_dump_length(self, f): - print "Read core dump header from '%s'" % f.name + """Reads core dump length + """ data = f.read(4*4) mag1,tot_len,task_num,tcbsz = struct.unpack_from(self.ESP32_COREDUMP_FLASH_HDR_FMT, data) if mag1 != self.ESP32_COREDUMP_FLASH_MAGIC_START: @@ -710,44 +747,50 @@ class ESPCoreDumpFlashLoader(ESPCoreDumpLoader): return tot_len def create_corefile(self, core_fname=None): - """ TBD + """Checks flash coredump data integrity and creates ELF file """ data = self.read_data(0, self.ESP32_COREDUMP_FLASH_MAGIC_SZ) mag1, = struct.unpack_from(self.ESP32_COREDUMP_FLASH_MAGIC_FMT, data) if mag1 != self.ESP32_COREDUMP_FLASH_MAGIC_START: - print "Invalid start marker %x" % mag1 - return None + raise ESPCoreDumpLoaderError("Invalid start marker %x" % mag1) data = self.read_data(self.dump_sz-self.ESP32_COREDUMP_FLASH_MAGIC_SZ, self.ESP32_COREDUMP_FLASH_MAGIC_SZ) mag2, = struct.unpack_from(self.ESP32_COREDUMP_FLASH_MAGIC_FMT, data) if mag2 != self.ESP32_COREDUMP_FLASH_MAGIC_END: - print "Invalid end marker %x" % mag2 - return None + raise ESPCoreDumpLoaderError("Invalid end marker %x" % mag2) return super(ESPCoreDumpFlashLoader, self).create_corefile(core_fname, off=self.ESP32_COREDUMP_FLASH_MAGIC_SZ) class GDBMIOutRecordHandler(object): - """ TBD + """GDB/MI output record handler base class """ TAG = '' def __init__(self, f, verbose=False): + """Base constructor for GDB/MI output record handler + """ self.verbose = verbose def execute(self, ln): + """Base method to execute GDB/MI output record handler function + """ if self.verbose: print "%s.execute '%s'" % (self.__class__.__name__, ln) class GDBMIOutStreamHandler(GDBMIOutRecordHandler): - """ TBD + """GDB/MI output stream handler class """ def __init__(self, f, verbose=False): + """Constructor for GDB/MI output stream handler + """ super(GDBMIOutStreamHandler, self).__init__(None, verbose) self.func = f def execute(self, ln): + """Executes GDB/MI output stream handler function + """ GDBMIOutRecordHandler.execute(self, ln) if self.func: # remove TAG / quotes and replace c-string \n with actual NL @@ -755,7 +798,7 @@ class GDBMIOutStreamHandler(GDBMIOutRecordHandler): class GDBMIResultHandler(GDBMIOutRecordHandler): - """ TBD + """GDB/MI result handler class """ TAG = '^' RC_DONE = 'done' @@ -765,11 +808,15 @@ class GDBMIResultHandler(GDBMIOutRecordHandler): RC_EXIT = 'exit' def __init__(self, verbose=False): + """Constructor for GDB/MI result handler + """ super(GDBMIResultHandler, self).__init__(None, verbose) self.result_class = None self.result_str = None def _parse_rc(self, ln, rc): + """Parses result code + """ rc_str = "{0}{1}".format(self.TAG, rc) if ln.startswith(rc_str): self.result_class = rc @@ -786,6 +833,8 @@ class GDBMIResultHandler(GDBMIOutRecordHandler): return False def execute(self, ln): + """Executes GDB/MI result handler function + """ GDBMIOutRecordHandler.execute(self, ln) if self._parse_rc(ln, self.RC_DONE): return @@ -797,17 +846,17 @@ class GDBMIResultHandler(GDBMIOutRecordHandler): return if self._parse_rc(ln, self.RC_EXIT): return - print "Unknown result: '%s'" % ln + print "Unknown GDB/MI result: '%s'" % ln class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler): - """ TBD + """GDB/MI console stream handler class """ TAG = '~' def dbg_corefile(args): - """ TBD + """ Command to load core dump from file or flash and run GDB debug session with it """ global CLOSE_FDS loader = None @@ -847,19 +896,14 @@ def dbg_corefile(args): def info_corefile(args): -# def info_corefile(args): - """ TBD + """ Command to load core dump from file or flash and print it's data in user friendly form """ global CLOSE_FDS def gdbmi_console_stream_handler(ln): - # print ln sys.stdout.write(ln) sys.stdout.flush() - def gdbmi_read2prompt(f, out_handlers=None): - """ TBD - """ while True: ln = f.readline().rstrip(' \r\n') if ln == '(gdb)': @@ -908,10 +952,11 @@ def info_corefile(args): gdbmi_read2prompt(p.stdout, handlers) exe_elf = ESPCoreDumpElfFile(args.prog) core_elf = ESPCoreDumpElfFile(core_fname) - merged_segs = []#[(s, 0) for s in exe_elf.sections if s.flags & (esptool.ELFSection.SHF_ALLOC | esptool.ELFSection.SHF_WRITE)] + merged_segs = [] + core_segs = core_elf.program_segments for s in exe_elf.sections: merged = False - for ps in core_elf.program_segments: + for ps in core_segs: if ps.addr <= s.addr and ps.addr + len(ps.data) >= s.addr: # sec: |XXXXXXXXXX| # seg: |...XXX.............| @@ -927,6 +972,7 @@ def info_corefile(args): # merged: |XXXXXXXXXXXXXXXXX| seg_len = len(ps.data) merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True)) + core_segs.remove(ps) merged = True elif ps.addr >= s.addr and ps.addr <= s.addr + len(s.data): # sec: |XXXXXXXXXX| @@ -943,11 +989,11 @@ def info_corefile(args): # merged: |XXXXXXXXXX| seg_len = len(s.data) merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True)) + core_segs.remove(ps) merged = True if not merged: merged_segs.append((s.name, s.addr, len(s.data), s.attr_str(), False)) -# merged_segs.append(('None', ps.addr, len(ps.data), 'None')) print "===============================================================" print "==================== ESP32 CORE DUMP START ====================" @@ -969,7 +1015,7 @@ def info_corefile(args): gdbmi_read2prompt(p.stdout, handlers) if handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE: print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str) - print "\n======================= MEMORY REGIONS ========================" + print "\n======================= ALL MEMORY REGIONS ========================" print "Name Address Size Attrs" for ms in merged_segs: print "%s 0x%x 0x%x %s" % (ms[0], ms[1], ms[2], ms[3]) @@ -1018,11 +1064,6 @@ def main(): type=int, default=os.environ.get('ESPTOOL_BAUD', esptool.ESPLoader.ESP_ROM_BAUD)) -# parser.add_argument( -# '--no-stub', -# help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.", -# action='store_true') - subparsers = parser.add_subparsers( dest='operation', help='Run coredumper {command} -h for additional help') @@ -1054,10 +1095,7 @@ def main(): args = parser.parse_args() - print 'coredumper.py v%s' % __version__ - - # operation function can take 1 arg (args), 2 args (esp, arg) - # or be a member function of the ESPLoader class. + print 'espcoredump.py v%s' % __version__ operation_func = globals()[args.operation] operation_func(args) diff --git a/components/freertos/include/freertos/task.h b/components/freertos/include/freertos/task.h index c6896e5386..590b07a2a4 100644 --- a/components/freertos/include/freertos/task.h +++ b/components/freertos/include/freertos/task.h @@ -2185,7 +2185,15 @@ eSleepModeStatus eTaskConfirmSleepModeStatus( void ) PRIVILEGED_FUNCTION; */ void *pvTaskIncrementMutexHeldCount( void ); -/* Used by core dump facility to get list of task handles. */ +/* + * This function fills array with TaskSnapshot_t structures for every task in the system. + * Used by core dump facility to get snapshots of all tasks in the system. + * Only available when configENABLE_TASK_SNAPSHOT is set to 1. + * @param pxTaskSnapshotArray Pointer to array of TaskSnapshot_t structures to store tasks snapshot data. + * @param uxArraySize Size of tasks snapshots array. + * @param pxTcbSz Pointer to store size of TCB. + * @return Number of elements stored in array. + */ UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz ); #ifdef __cplusplus diff --git a/components/spi_flash/cache_utils.h b/components/spi_flash/cache_utils.h index 4ec1f3203a..a5442d1af7 100644 --- a/components/spi_flash/cache_utils.h +++ b/components/spi_flash/cache_utils.h @@ -41,13 +41,13 @@ void spi_flash_disable_interrupts_caches_and_other_cpu(); void spi_flash_enable_interrupts_caches_and_other_cpu(); // Disables non-IRAM interrupt handlers on current CPU and caches on both CPUs. -// This function is implied to be called from panic handler or when no OS is present -// when non-current CPU is halted and can not execute code from flash. +// This function is implied to be called from panic handler +// (when non-current CPU is halted and can not execute code from flash) or when no OS is present. void spi_flash_disable_interrupts_caches_and_other_cpu_no_os(); -// Enable cache, enable interrupts (to be added in future) on current CPU. -// This function is implied to be called from panic handler or when no OS is present -// when non-current CPU is halted and can not execute code from flash. +// Enable cache, enable interrupts on current CPU. +// This function is implied to be called from panic handler +// (when non-current CPU is halted and can not execute code from flash) or when no OS is present. void spi_flash_enable_interrupts_caches_no_os(); #endif //ESP_SPI_FLASH_CACHE_UTILS_H diff --git a/components/spi_flash/include/esp_spi_flash.h b/components/spi_flash/include/esp_spi_flash.h index 402cd6e6f7..bf897e8995 100644 --- a/components/spi_flash/include/esp_spi_flash.h +++ b/components/spi_flash/include/esp_spi_flash.h @@ -184,6 +184,9 @@ typedef void (*spi_flash_guard_end_func_t)(void); /** * Structure holding SPI flash access critical section management functions + * + * @note Structure and corresponding guard functions should not reside in flash. + * For example structure can be placed in DRAM and functions in IRAM sections. */ typedef struct { spi_flash_guard_start_func_t start; /**< critical section start func */ @@ -191,25 +194,25 @@ typedef struct { } spi_flash_guard_funcs_t; /** - * @brief Erase a range of flash sectors. + * @brief Sets guard functions to access flash. * + * @note Pointed structure and corresponding guard functions should not reside in flash. + * For example structure can be placed in DRAM and functions in IRAM sections. * - * @param start_address Address where erase operation has to start. - * Must be 4kB-aligned - * @param size Size of erased range, in bytes. Must be divisible by 4kB. - * - * @return esp_err_t + * @param funcs pointer to structure holding flash access guard functions. */ void spi_flash_guard_set(const spi_flash_guard_funcs_t* funcs); -/** Default OS-aware flash access critical section functions */ +/** + * @brief Default OS-aware flash access guard functions + */ extern const spi_flash_guard_funcs_t g_flash_guard_default_ops; -/** Non-OS flash access critical section functions +/** + * @brief Non-OS flash access guard functions * - * @note This version of functions is to be used when no OS is present or from panic handler. - * It does not use any OS primitives and IPC and implies that - * only calling CPU is active. + * @note This version of flash guard functions is to be used when no OS is present or from panic handler. + * It does not use any OS primitives and IPC and implies that only calling CPU is active. */ extern const spi_flash_guard_funcs_t g_flash_guard_no_os_ops; diff --git a/docs/Doxyfile b/docs/Doxyfile index 7dbd1cc8d1..a3fc5faa12 100755 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -35,7 +35,8 @@ INPUT = ../components/esp32/include/esp_wifi.h \ ../components/esp32/include/esp_deep_sleep.h \ ../components/sdmmc/include/sdmmc_cmd.h \ ../components/fatfs/src/esp_vfs_fat.h \ - ../components/fatfs/src/diskio.h + ../components/fatfs/src/diskio.h \ + ../components/esp32/include/esp_core_dump.h ## Get warnings for functions that have no documentation for their parameters or return value ## diff --git a/docs/core_dump.rst b/docs/core_dump.rst new file mode 100644 index 0000000000..0d0624cc89 --- /dev/null +++ b/docs/core_dump.rst @@ -0,0 +1,127 @@ +ESP32 Core Dump +================ + +Overview +-------- + +ESP-IDF provides support to generate core dumps on unrecoverable software errors. This useful technique allows post-mortem analisys of software state at the moment of failure. +Upon the crash system enters panic state, prints some information and halts or reboots depending configuration. User can choose to generate core dump in order to analyse +the reason of failure on PC later on. Core dump contains snapshots of all tasks in the system at the moment of failure. Snapshots include tasks control blocks (TCB) and stacks. +So it is possible to find out what task, at what instruction (line of code) and what callstack of that task lead to the crash. +ESP-IDF provides special script `espcoredump.py` to help users to retrieve and analyse core dumps. This tool provides two commands for core dumps analysis: + +* info_corefile - prints crashed task's registers, callstack, list of available tasks in the system, memory regions and contents of memory stored in core dump (TCBs and stacks) +* dbg_corefile - creates core dump ELF file and runs GDB debug session with this file. User can examine memory, variables and tasks states manually. Note that since not all memory is saved in core dump only values of variables allocated on stack will be meaningfull + +Configuration +------------- + +Currently there are three options related to core dump generation which user can choose in configuration menu of the application (`make menuconfig`): + +* Disable core dump generation +* Save core dump to flash +* Print core dump to UART + +These options can be choosen in Components -> ESP32-specific config -> Core dump destination menu item. + +Save core dump to flash +----------------------- + +When this option is selected core dumps are saved to special partition on flash. When using default partition table files which are provided with ESP-IDF it automatically +allocates necessary space on flash, But if user wants to use its own layout file together with core dump feature it should define separate partition for core dump +as it is shown below:: + + # Name, Type, SubType, Offset, Size + # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild + nvs, data, nvs, 0x9000, 0x6000 + phy_init, data, phy, 0xf000, 0x1000 + factory, app, factory, 0x10000, 1M + coredump, data, 3, , 64K + +There are no special requrements for partition name. It can be choosen according to the user application needs, but partition type should be 'data' and +sub-type should be 3. Also when choosing partition size note that core dump data structure introduces constant overhead of 20 bytes and per-task overhead of 12 bytes. +This overhead does not include size of TCB and stack for every task. So partirion size should be at least 20 + max tasks number x (12 + TCB size + max task stack size) bytes. + +The example of generic command to analyze core dump from flash is: `espcoredump.py -p info_corefile ` +or `espcoredump.py -p dbg_corefile ` + +Print core dump to UART +----------------------- + +When this option is selected base64-encoded core dumps are printed on UART upon system panic. In this case user should save core dump text body to some file manually and +then run the following command: `espcoredump.py -p info_corefile -t b64 -c ` +or `espcoredump.py -p dbg_corefile -t b64 -c ` + +Base64-encoded body of core dump will be between the following header and footer:: + + ================= CORE DUMP START ================= + + ================= CORE DUMP END =================== + +Command Options For 'espcoredump.py' +-------------------------------------------- + +usage: coredumper [-h] [--chip {auto,esp32}] [--port PORT] [--baud BAUD] + {dbg_corefile,info_corefile} ... + +espcoredump.py v0.1-dev - ESP32 Core Dump Utility + +positional arguments: + {dbg_corefile,info_corefile} + Run coredumper {command} -h for additional help + dbg_corefile Starts GDB debugging session with specified corefile + info_corefile Print core dump info from file + +optional arguments: + -h, --help show this help message and exit + --chip {auto,esp32}, -c {auto,esp32} + Target chip type + --port PORT, -p PORT Serial port device + --baud BAUD, -b BAUD Serial port baud rate used when flashing/reading + + +usage: coredumper info_corefile [-h] [--gdb GDB] [--core CORE] + [--core-format CORE_FORMAT] [--off OFF] + [--save-core SAVE_CORE] [--print-mem] + prog + +positional arguments: + prog Path to program's ELF binary + +optional arguments: + -h, --help show this help message and exit + --gdb GDB, -g GDB Path to gdb + --core CORE, -c CORE Path to core dump file (if skipped core dump will be + read from flash) + --core-format CORE_FORMAT, -t CORE_FORMAT + (elf, raw or b64). File specified with "-c" is an ELF + ("elf"), raw (raw) or base64-encoded (b64) binary + --off OFF, -o OFF Ofsset of coredump partition in flash (type "make + partition_table" to see). + --save-core SAVE_CORE, -s SAVE_CORE + Save core to file. Othwerwise temporary core file will + be deleted. Does not work with "-c" + --print-mem, -m Print memory dump + + +usage: coredumper dbg_corefile [-h] [--gdb GDB] [--core CORE] + [--core-format CORE_FORMAT] [--off OFF] + [--save-core SAVE_CORE] + prog + +positional arguments: + prog Path to program's ELF binary + +optional arguments: + -h, --help show this help message and exit + --gdb GDB, -g GDB Path to gdb + --core CORE, -c CORE Path to core dump file (if skipped core dump will be + read from flash) + --core-format CORE_FORMAT, -t CORE_FORMAT + (elf, raw or b64). File specified with "-c" is an ELF + ("elf"), raw (raw) or base64-encoded (b64) binary + --off OFF, -o OFF Ofsset of coredump partition in flash (type "make + partition_table" to see). + --save-core SAVE_CORE, -s SAVE_CORE + Save core to file. Othwerwise temporary core file will + be deleted. Ignored with "-c" From 9f151d745ea6b4c5204f3e1e6b66d2d048214d4d Mon Sep 17 00:00:00 2001 From: Island Date: Thu, 12 Jan 2017 00:48:48 +0800 Subject: [PATCH 142/167] component/bt : Optimize Gatt attr table and fix some bugs 1. Optimize GATT attribute table structure 2. fix read/write bugs 3. add docs --- .../bt/bluedroid/api/include/esp_gatt_defs.h | 32 ++-- .../bt/bluedroid/api/include/esp_gatts_api.h | 4 +- .../bt/bluedroid/bta/gatt/bta_gatts_act.c | 18 ++- .../bt/bluedroid/bta/gatt/bta_gatts_api.c | 51 ++++--- .../btc/profile/std/gatt/btc_gatts.c | 56 +++++-- components/bt/bluedroid/stack/gatt/gatt_db.c | 52 ++++--- components/bt/bluedroid/stack/gatt/gatt_sr.c | 103 +++++++------ .../bt/bluedroid/stack/include/gatt_api.h | 1 + docs/api/esp_gatt_defs.rst | 31 +++- docs/api/esp_gatts.rst | 6 +- .../main/gatts_table_creat_demo.c | 144 ++++++++---------- .../main/gatts_table_creat_demo.h | 9 -- 12 files changed, 284 insertions(+), 223 deletions(-) diff --git a/components/bt/bluedroid/api/include/esp_gatt_defs.h b/components/bt/bluedroid/api/include/esp_gatt_defs.h index f8b2302111..153a702735 100644 --- a/components/bt/bluedroid/api/include/esp_gatt_defs.h +++ b/components/bt/bluedroid/api/include/esp_gatt_defs.h @@ -286,13 +286,14 @@ typedef enum { * @brief Attribute description (used to create database) */ typedef struct - { - esp_bt_uuid_t uuid; /*!< Element UUID */ + { + 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*/ - }esp_attr_desc_t; + uint8_t *value; /*!< Element value array*/ + } esp_attr_desc_t; /** @@ -303,7 +304,7 @@ typedef struct #define ESP_GATT_RSP_BY_APP 0 #define ESP_GATT_AUTO_RSP 1 uint8_t auto_rsp; /*!< need the app response to the client if need_rsp set to 1*/ -}esp_attr_control_t; +} esp_attr_control_t; /** @@ -312,8 +313,8 @@ typedef struct typedef struct { esp_attr_control_t attr_control; /*!< The attribue control type*/ - esp_attr_desc_t att_desc; /*!< The attribue type*/ -}esp_gatts_attr_db_t; + esp_attr_desc_t att_desc; /*!< The attribue type*/ +} esp_gatts_attr_db_t; /** @@ -323,8 +324,8 @@ typedef struct { uint16_t attr_max_len; /*!< attribute max value length */ uint16_t attr_len; /*!< attribute current value length */ - uint8_t *attr_value; /*!< the pointer to attribute value */ -}esp_attr_value_t; + uint8_t *attr_value; /*!< the pointer to attribute value */ +} esp_attr_value_t; /** @@ -335,7 +336,7 @@ typedef struct uint16_t start_hdl; /*!< Gatt start handle value of included service */ uint16_t end_hdl; /*!< Gatt end handle value of included service */ uint16_t uuid; /*!< Gatt attribute value UUID of included service */ -}esp_gatts_incl_svc_desc_t; /*!< Gatt include service entry element */ +} esp_gatts_incl_svc_desc_t; /*!< Gatt include service entry element */ /** * @brief Gatt include 128 bit service entry element @@ -344,18 +345,9 @@ typedef struct { uint16_t start_hdl; /*!< Gatt start handle value of included 128 bit service */ uint16_t end_hdl; /*!< Gatt end handle value of included 128 bit service */ -}esp_gatts_incl128_svc_desc_t; /*!< Gatt include 128 bit service entry element */ +} esp_gatts_incl128_svc_desc_t; /*!< Gatt include 128 bit service entry element */ -/** - * @brief Gatt characteristic entry element - */ -typedef struct -{ - uint8_t prop; /*!< Gatt attribute properties */ - uint16_t attr_hdl; /*!< Gatt attribute handle */ - esp_bt_uuid_t attr_uuid; /*!< Gatt attribute uuid typedle */ -}esp_gatts_char_desc_t; /*!< Gatt characteristic value descriptor */ /// Gatt attribute value diff --git a/components/bt/bluedroid/api/include/esp_gatts_api.h b/components/bt/bluedroid/api/include/esp_gatts_api.h index 86a539597a..8d51ac7375 100644 --- a/components/bt/bluedroid/api/include/esp_gatts_api.h +++ b/components/bt/bluedroid/api/include/esp_gatts_api.h @@ -74,8 +74,10 @@ typedef union { 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 */ } read; /*!< Gatt server callback param of ESP_GATTS_READ_EVT */ + /** * @brief ESP_GATTS_WRITE_EVT */ @@ -227,7 +229,7 @@ typedef union { * @brief ESP_GATTS_RESPONSE_EVT */ struct gatts_rsp_evt_param { - esp_gatt_status_t status; /*!< Operation status */ + esp_gatt_status_t status; /*!< Operation status */ uint16_t handle; /*!< Attribute handle which send response */ } rsp; /*!< Gatt server callback param of ESP_GATTS_RESPONSE_EVT */ diff --git a/components/bt/bluedroid/bta/gatt/bta_gatts_act.c b/components/bt/bluedroid/bta/gatt/bta_gatts_act.c index 9dd2ea4039..83d5e4d7d7 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gatts_act.c +++ b/components/bt/bluedroid/bta/gatt/bta_gatts_act.c @@ -403,16 +403,18 @@ void bta_gatts_add_char(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) UINT16 attr_id = 0; tBTA_GATTS cb_data; - tGATT_ATTR_VAL *p_attr_val = NULL; + tGATT_ATTR_VAL *p_attr_val = NULL; tGATTS_ATTR_CONTROL *p_control = NULL; - if (p_msg->api_add_char_descr.attr_val.attr_max_len != 0) { - p_attr_val = &p_msg->api_add_char_descr.attr_val; + if(p_msg->api_add_char.attr_val.attr_max_len != 0){ + p_attr_val = &p_msg->api_add_char.attr_val; } - if (p_msg->api_add_char_descr.control.auto_rsp != 0) { - p_control = &p_msg->api_add_char_descr.control; + if(p_msg->api_add_char.control.auto_rsp != 0){ + p_control = &p_msg->api_add_char.control; } + + attr_id = GATTS_AddCharacteristic(p_msg->api_add_char.hdr.layer_specific, &p_msg->api_add_char.char_uuid, p_msg->api_add_char.perm, @@ -429,6 +431,9 @@ void bta_gatts_add_char(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) } else { cb_data.add_result.status = BTA_GATT_ERROR; } + if((p_attr_val != NULL) && (p_attr_val->attr_val != NULL)){ + GKI_freebuf(p_attr_val->attr_val); + } if (p_rcb->p_cback) { (*p_rcb->p_cback)(BTA_GATTS_ADD_CHAR_EVT, &cb_data); @@ -476,6 +481,9 @@ void bta_gatts_add_char_descr(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ } else { cb_data.add_result.status = BTA_GATT_ERROR; } + if((p_attr_val != NULL) && (p_attr_val->attr_val != NULL)){ + GKI_freebuf(p_attr_val->attr_val); + } if (p_rcb->p_cback) { (*p_rcb->p_cback)(BTA_GATTS_ADD_CHAR_DESCR_EVT, &cb_data); diff --git a/components/bt/bluedroid/bta/gatt/bta_gatts_api.c b/components/bt/bluedroid/bta/gatt/bta_gatts_api.c index 58e226c839..94f1d407e8 100644 --- a/components/bt/bluedroid/bta/gatt/bta_gatts_api.c +++ b/components/bt/bluedroid/bta/gatt/bta_gatts_api.c @@ -274,47 +274,54 @@ void BTA_GATTS_AddCharDescriptor (UINT16 service_id, tBTA_GATTS_ATTR_CONTROL *control) { tBTA_GATTS_API_ADD_DESCR *p_buf; - UINT16 len = 0; - if(attr_val != NULL) { - len = sizeof(tBTA_GATTS_API_ADD_DESCR) + attr_val->attr_len; - } else { - len = sizeof(tBTA_GATTS_API_ADD_DESCR); - } + UINT16 value_len = 0; - if ((p_buf = (tBTA_GATTS_API_ADD_DESCR *) GKI_getbuf(len)) != NULL) { - memset(p_buf, 0, len); + if ((p_buf = (tBTA_GATTS_API_ADD_DESCR *) GKI_getbuf(sizeof(tBTA_GATTS_API_ADD_DESCR))) != NULL) { + memset(p_buf, 0, sizeof(tBTA_GATTS_API_ADD_DESCR)); p_buf->hdr.event = BTA_GATTS_API_ADD_DESCR_EVT; p_buf->hdr.layer_specific = service_id; p_buf->perm = perm; + if(control != NULL){ + p_buf->control.auto_rsp = control->auto_rsp; + } + if (p_descr_uuid) { memcpy(&p_buf->descr_uuid, p_descr_uuid, sizeof(tBT_UUID)); } - if(attr_val->attr_len != 0) { + if(attr_val != NULL){ p_buf->attr_val.attr_len = attr_val->attr_len; - p_buf->attr_val.attr_max_len= attr_val->attr_max_len; - memcpy(p_buf->attr_val.attr_val, attr_val->attr_val, attr_val->attr_len); + p_buf->attr_val.attr_max_len = attr_val->attr_max_len; + value_len = attr_val->attr_len; + if (value_len != 0){ + p_buf->attr_val.attr_val = (uint8_t*)GKI_getbuf(value_len); + if(p_buf->attr_val.attr_val != NULL){ + memcpy(p_buf->attr_val.attr_val, attr_val->attr_val, value_len); + } + else{ + APPL_TRACE_ERROR("Allocate fail for %s\n", __func__); + + } + } } - if(control != NULL) { - p_buf->control.auto_rsp = control->auto_rsp; - } bta_sys_sendmsg(p_buf); } return; + } /******************************************************************************* -** -** Function BTA_GATTS_DeleteService -** -** Description This function is called to delete a service. When this is done, -** a callback event BTA_GATTS_DELETE_EVT is report with the status. -** -** Parameters service_id: service_id to be deleted. -** + ** + ** Function BTA_GATTS_DeleteService + ** + ** Description This function is called to delete a service. When this is done, + ** a callback event BTA_GATTS_DELETE_EVT is report with the status. + ** + ** Parameters service_id: service_id to be deleted. + ** ** Returns returns none. ** *******************************************************************************/ diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c index 255484fbc4..a5631f5b7a 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c @@ -47,6 +47,25 @@ static inline void btc_gatts_cb_to_app(esp_gatts_cb_event_t event, esp_gatt_if_t } } +static inline void btc_gatts_uuid_format_convert(esp_bt_uuid_t* dest_uuid, uint16_t src_uuid_len, uint8_t* src_uuid_p) +{ + dest_uuid->len = src_uuid_len; + if(src_uuid_len == ESP_UUID_LEN_16){ + dest_uuid->uuid.uuid16 = src_uuid_p[0] + (src_uuid_p[1]<<8); + } + else if(src_uuid_len == ESP_UUID_LEN_32){ + dest_uuid->uuid.uuid32 = src_uuid_p[0] + (src_uuid_p[1]<<8) + (src_uuid_p[2]<<16) + (src_uuid_p[3]<<24); + } + else if(src_uuid_len == ESP_UUID_LEN_128){ + memcpy(dest_uuid->uuid.uuid128, src_uuid_p, src_uuid_len); + } + else{ + LOG_ERROR("%s wrong uuid length %d\n", __func__, src_uuid_len); + } + +} + + void btc_gatts_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { btc_ble_gatts_args_t *dst = (btc_ble_gatts_args_t *) p_dest; @@ -167,8 +186,8 @@ static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, btc_creat_tab_env.is_tab_creat_svc = true; btc_creat_tab_env.num_handle = max_nb_attr; for(int i = 0; i < max_nb_attr; i++){ - if(gatts_attr_db[i].att_desc.uuid.len == ESP_UUID_LEN_16){ - uuid = gatts_attr_db[i].att_desc.uuid.uuid.uuid16; + if(gatts_attr_db[i].att_desc.uuid_length== ESP_UUID_LEN_16){ + uuid = (gatts_attr_db[i].att_desc.uuid_p[1] << 8) + (gatts_attr_db[i].att_desc.uuid_p[0]); } future_p = future_new(); if (future_p == NULL) { @@ -184,8 +203,10 @@ static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, esp_gatt_srvc_id_t esp_srvc_id; esp_srvc_id.id.inst_id = srvc_inst_id; - memcpy(&esp_srvc_id.id.uuid, &gatts_attr_db[i].att_desc.uuid, sizeof(esp_bt_uuid_t)); - btc_to_bta_srvc_id(&srvc_id, &esp_srvc_id); + btc_gatts_uuid_format_convert(&esp_srvc_id.id.uuid,gatts_attr_db[i].att_desc.uuid_length, + gatts_attr_db[i].att_desc.uuid_p); + + btc_to_bta_srvc_id(&srvc_id, &esp_srvc_id); BTA_GATTS_CreateService(gatts_if, &srvc_id.id.uuid, srvc_inst_id, max_nb_attr, true); @@ -200,8 +221,9 @@ static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, esp_gatt_srvc_id_t esp_srvc_id; esp_srvc_id.id.inst_id = srvc_inst_id; - memcpy(&esp_srvc_id.id.uuid, &gatts_attr_db[i].att_desc.uuid, sizeof(esp_bt_uuid_t)); - btc_to_bta_srvc_id(&srvc_id, &esp_srvc_id); + btc_gatts_uuid_format_convert(&esp_srvc_id.id.uuid,gatts_attr_db[i].att_desc.uuid_length, + gatts_attr_db[i].att_desc.uuid_p); + btc_to_bta_srvc_id(&srvc_id, &esp_srvc_id); BTA_GATTS_CreateService(gatts_if, &srvc_id.id.uuid, srvc_inst_id, max_nb_attr, false); if (future_await(future_p) == FUTURE_FAIL) { @@ -230,27 +252,33 @@ static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, uint16_t svc_hal = 0; tBT_UUID bta_char_uuid; tGATT_ATTR_VAL attr_val; + esp_bt_uuid_t uuid_temp; tBTA_GATT_PERM perm; tBTA_GATTS_ATTR_CONTROL control; + uint8_t char_property; if(btc_creat_tab_env.svc_start_hdl != 0){ svc_hal = btc_creat_tab_env.svc_start_hdl; - esp_gatts_char_desc_t *char_desc = (esp_gatts_char_desc_t *)gatts_attr_db[i].att_desc.value; - if(char_desc != NULL){ + if((gatts_attr_db[i].att_desc.value) == NULL){ + LOG_ERROR("%s Characteristic declaration should not be NULL\n", __func__); + } + else{ + char_property = (uint8_t)(*(uint8_t*)(gatts_attr_db[i].att_desc.value)); perm = gatts_attr_db[i+1].att_desc.perm; attr_val.attr_len = gatts_attr_db[i+1].att_desc.length; attr_val.attr_max_len = gatts_attr_db[i+1].att_desc.max_length; - btc_to_bta_uuid(&bta_char_uuid, &char_desc->attr_uuid); + btc_gatts_uuid_format_convert(&uuid_temp, gatts_attr_db[i+1].att_desc.uuid_length,gatts_attr_db[i+1].att_desc.uuid_p); + btc_to_bta_uuid(&bta_char_uuid, &uuid_temp); attr_val.attr_val = gatts_attr_db[i+1].att_desc.value; control.auto_rsp = gatts_attr_db[i+1].attr_control.auto_rsp; BTA_GATTS_AddCharacteristic (svc_hal, &bta_char_uuid, - perm, char_desc->prop, &attr_val, &control); + perm, char_property, &attr_val, &control); if (future_await(future_p) == FUTURE_FAIL) { LOG_ERROR("%s failed\n", __func__); return; } - } + } } break; @@ -266,6 +294,7 @@ static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, case ESP_GATT_UUID_RPT_REF_DESCR:{ uint16_t svc_hal = btc_creat_tab_env.svc_start_hdl; tBT_UUID bta_char_uuid; + esp_bt_uuid_t uuid_temp; tGATT_ATTR_VAL attr_val; tBTA_GATT_PERM perm = gatts_attr_db[i].att_desc.perm; tBTA_GATTS_ATTR_CONTROL control; @@ -274,7 +303,9 @@ static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, attr_val.attr_len = gatts_attr_db[i].att_desc.length; attr_val.attr_max_len = gatts_attr_db[i].att_desc.max_length; attr_val.attr_val = gatts_attr_db[i].att_desc.value; - btc_to_bta_uuid(&bta_char_uuid, &gatts_attr_db[i].att_desc.uuid); + btc_gatts_uuid_format_convert(&uuid_temp, gatts_attr_db[i].att_desc.uuid_length, + gatts_attr_db[i].att_desc.uuid_p); + btc_to_bta_uuid(&bta_char_uuid, &uuid_temp); control.auto_rsp = gatts_attr_db[i].attr_control.auto_rsp; BTA_GATTS_AddCharDescriptor(svc_hal, perm, &bta_char_uuid, &attr_val, &control); @@ -573,6 +604,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) param.read.offset = p_data->req_data.p_data->read_req.offset; param.read.is_long = p_data->req_data.p_data->read_req.is_long; + param.read.need_rsp = p_data->req_data.p_data->read_req.need_rsp; btc_gatts_cb_to_app(ESP_GATTS_READ_EVT, gatts_if, ¶m); break; } diff --git a/components/bt/bluedroid/stack/gatt/gatt_db.c b/components/bt/bluedroid/stack/gatt/gatt_db.c index b95934a8fa..fd27959d96 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_db.c +++ b/components/bt/bluedroid/stack/gatt/gatt_db.c @@ -45,7 +45,7 @@ static BOOLEAN copy_extra_byte_in_db(tGATT_SVC_DB *p_db, void **p_dst, UINT16 le static BOOLEAN gatts_db_add_service_declaration(tGATT_SVC_DB *p_db, tBT_UUID *p_service, BOOLEAN is_pri); static tGATT_STATUS gatts_send_app_read_request(tGATT_TCB *p_tcb, UINT8 op_code, - UINT16 handle, UINT16 offset, UINT32 trans_id); + UINT16 handle, UINT16 offset, UINT32 trans_id, BOOLEAN need_rsp); /******************************************************************************* ** @@ -268,16 +268,12 @@ static tGATT_STATUS read_attr_value (void *p_attr, status = GATT_SUCCESS; } } else { /* characteristic description or characteristic value */ + if (p_attr16->control.auto_rsp == GATT_RSP_BY_STACK) { - GATT_TRACE_DEBUG("before characteristic description or characteristic value\n"); if (p_attr16->p_value != NULL && p_attr16->p_value->attr_val.attr_val != NULL) { uint8_t *value = p_attr16->p_value->attr_val.attr_val + offset; - GATT_TRACE_DEBUG("after characteristic description or characteristic value\n"); - if (mtu >= p_attr16->p_value->attr_val.attr_len) { - ARRAY_TO_STREAM(p, value, p_attr16->p_value->attr_val.attr_len); - } else { - ARRAY_TO_STREAM(p, value, mtu); - } + len = (mtu >= p_attr16->p_value->attr_val.attr_len) ? (p_attr16->p_value->attr_val.attr_len) : mtu; + ARRAY_TO_STREAM(p, value, len); } status = GATT_STACK_RSP; @@ -357,7 +353,8 @@ tGATT_STATUS gatts_db_read_attr_value_by_type (tGATT_TCB *p_tcb, status = read_attr_value ((void *)p_attr, 0, &p, FALSE, (UINT16)(*p_len - 2), &len, sec_flag, key_size); if (status == GATT_PENDING || status == GATT_STACK_RSP) { - status = gatts_send_app_read_request(p_tcb, op_code, p_attr->handle, 0, trans_id); + BOOLEAN need_rsp = (status != GATT_STACK_RSP); + status = gatts_send_app_read_request(p_tcb, op_code, p_attr->handle, 0, trans_id, need_rsp); /* one callback at a time */ break; @@ -630,7 +627,7 @@ UINT16 gatts_add_char_descr (tGATT_SVC_DB *p_db, tGATT_PERM perm, tGATT_STATUS gatts_set_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, UINT16 length, UINT8 *value) { - tGATT_ATTR16 *p_cur, *p_next; + tGATT_ATTR16 *p_cur; if (p_db == NULL) { GATT_TRACE_DEBUG("gatts_set_attribute_value Fail:p_db is NULL.\n"); @@ -642,19 +639,21 @@ tGATT_STATUS gatts_set_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, } p_cur = (tGATT_ATTR16 *) p_db->p_attr_list; - p_next = (tGATT_ATTR16 *) p_cur->p_next; - - for (; p_cur != NULL; p_cur = p_next, p_next = (tGATT_ATTR16 *)p_next->p_next) { + while (p_cur != NULL) { if (p_cur->handle == attr_handle) { + if (p_cur->uuid_type == GATT_ATTR_UUID_TYPE_16) { switch (p_cur->uuid) { + case GATT_UUID_PRI_SERVICE: + case GATT_UUID_SEC_SERVICE: case GATT_UUID_CHAR_DECLARE: case GATT_UUID_INCLUDE_SERVICE: return GATT_NOT_FOUND; default: if (p_cur->p_value->attr_val.attr_max_len < length) { GATT_TRACE_ERROR("gatts_set_attribute_vaule failt:Invalid value length"); + return GATT_INVALID_ATTR_LEN; } else { memcpy(p_cur->p_value->attr_val.attr_val, value, length); p_cur->p_value->attr_val.attr_len = length; @@ -668,9 +667,14 @@ tGATT_STATUS gatts_set_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, memcpy(p_cur->p_value->attr_val.attr_val, value, length); p_cur->p_value->attr_val.attr_len = length; } + } + break; + } + + p_cur = p_cur->p_next; } return GATT_SUCCESS; @@ -694,7 +698,7 @@ tGATT_STATUS gatts_set_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, tGATT_STATUS gatts_get_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, UINT16 *length, UINT8 **value) { - tGATT_ATTR16 *p_cur, *p_next; + tGATT_ATTR16 *p_cur; GATT_TRACE_DEBUG("***********%s*************\n", __func__); GATT_TRACE_DEBUG("attr_handle = %x\n", attr_handle); if (p_db == NULL) { @@ -707,10 +711,8 @@ tGATT_STATUS gatts_get_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, } p_cur = (tGATT_ATTR16 *) p_db->p_attr_list; - p_next = (tGATT_ATTR16 *) p_cur->p_next; - - for (; p_cur != NULL; p_cur = p_next, p_next = (tGATT_ATTR16 *)p_next->p_next) { + while (p_cur != NULL) { LOG_ERROR("p_ur->handle = %x\n", p_cur->handle); if (p_cur->handle == attr_handle) { @@ -736,7 +738,7 @@ tGATT_STATUS gatts_get_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, *value = p_cur->p_value->attr_val.attr_val; return GATT_SUCCESS; } else { - GATT_TRACE_ERROR("gatts_get_attribute_vaule failt:the value length is 0"); + GATT_TRACE_ERROR("gatts_get_attribute_vaule failed:the value length is 0"); return GATT_INVALID_ATTR_LEN; } @@ -747,9 +749,10 @@ tGATT_STATUS gatts_get_attribute_value(tGATT_SVC_DB *p_db, UINT16 attr_handle, } + p_cur = p_cur->p_next; } - return GATT_SUCCESS; + return GATT_NOT_FOUND; } BOOLEAN gatts_is_auto_response(UINT16 attr_handle) @@ -839,8 +842,9 @@ tGATT_STATUS gatts_read_attr_value_by_handle(tGATT_TCB *p_tcb, (BOOLEAN)(op_code == GATT_REQ_READ_BLOB), mtu, p_len, sec_flag, key_size); - if (status == GATT_PENDING) { - status = gatts_send_app_read_request(p_tcb, op_code, p_attr->handle, offset, trans_id); + if ((status == GATT_PENDING) || (status == GATT_STACK_RSP)) { + BOOLEAN need_rsp = (status != GATT_STACK_RSP); + status = gatts_send_app_read_request(p_tcb, op_code, p_attr->handle, offset, trans_id, need_rsp); } break; } @@ -848,6 +852,7 @@ tGATT_STATUS gatts_read_attr_value_by_handle(tGATT_TCB *p_tcb, } } + return status; } @@ -867,7 +872,7 @@ tGATT_STATUS gatts_write_attr_value_by_handle(tGATT_SVC_DB *p_db, return GATT_APP_RSP; } - if (p_attr->p_value != NULL && (p_attr->p_value->attr_val.attr_max_len > + if (p_attr->p_value != NULL && (p_attr->p_value->attr_val.attr_max_len >= offset + len)) { memcpy(p_attr->p_value->attr_val.attr_val + offset, p_value, len); p_attr->p_value->attr_val.attr_len = len + offset; @@ -1301,7 +1306,7 @@ static BOOLEAN allocate_svc_db_buf(tGATT_SVC_DB *p_db) ** *******************************************************************************/ static tGATT_STATUS gatts_send_app_read_request(tGATT_TCB *p_tcb, UINT8 op_code, - UINT16 handle, UINT16 offset, UINT32 trans_id) + UINT16 handle, UINT16 offset, UINT32 trans_id, BOOLEAN need_rsp) { tGATTS_DATA sr_data; UINT8 i_rcb; @@ -1323,6 +1328,7 @@ static tGATT_STATUS gatts_send_app_read_request(tGATT_TCB *p_tcb, UINT8 op_code, sr_data.read_req.handle = handle; sr_data.read_req.is_long = (BOOLEAN)(op_code == GATT_REQ_READ_BLOB); sr_data.read_req.offset = offset; + sr_data.read_req.need_rsp = need_rsp; gatt_sr_send_req_callback(conn_id, trans_id, GATTS_REQ_TYPE_READ, &sr_data); diff --git a/components/bt/bluedroid/stack/gatt/gatt_sr.c b/components/bt/bluedroid/stack/gatt/gatt_sr.c index 6f21e8c45d..a817ad1478 100644 --- a/components/bt/bluedroid/stack/gatt/gatt_sr.c +++ b/components/bt/bluedroid/stack/gatt/gatt_sr.c @@ -987,14 +987,14 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, UINT8 op_code, UINT16 len, UINT8 *p_data) { - UINT16 buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET); - tGATTS_DATA sr_data; - UINT32 trans_id; - tGATT_STATUS status; - UINT8 sec_flag, key_size, *p = p_data, *p_m; - tGATT_SR_REG *p_sreg; - UINT16 conn_id, offset = 0; - BT_HDR *p_msg = NULL; + UINT16 buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET); + tGATTS_DATA sr_data; + UINT32 trans_id; + tGATT_STATUS status; + UINT8 sec_flag, key_size, *p = p_data, *p_m; + tGATT_SR_REG *p_sreg; + UINT16 conn_id, offset = 0; + BT_HDR *p_msg = NULL; memset(&sr_data, 0, sizeof(tGATTS_DATA)); if ((p_msg = (BT_HDR *)GKI_getbuf(buf_len)) == NULL) { @@ -1011,10 +1011,10 @@ void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, case GATT_REQ_PREPARE_WRITE: sr_data.write_req.is_prep = TRUE; STREAM_TO_UINT16(sr_data.write_req.offset, p); - UINT16_TO_STREAM(p_m, sr_data.write_req.is_prep); - offset = sr_data.write_req.offset; + UINT16_TO_STREAM(p_m, sr_data.write_req.is_prep); + offset = sr_data.write_req.offset; len -= 2; - /* fall through */ + /* fall through */ case GATT_SIGN_CMD_WRITE: if (op_code == GATT_SIGN_CMD_WRITE) { GATT_TRACE_DEBUG("Write CMD with data sigining" ); @@ -1025,16 +1025,16 @@ void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, case GATT_REQ_WRITE: if (op_code == GATT_REQ_WRITE || op_code == GATT_REQ_PREPARE_WRITE) { sr_data.write_req.need_rsp = TRUE; - if(op_code == GATT_REQ_PREPARE_WRITE){ - memcpy(p_m, p, len); - p_msg->len += len; - } + if(op_code == GATT_REQ_PREPARE_WRITE){ + memcpy(p_m, p, len); + p_msg->len += len; + } } sr_data.write_req.handle = handle; sr_data.write_req.len = len; if (len != 0 && p != NULL) { memcpy (sr_data.write_req.value, p, len); - + } break; } @@ -1053,24 +1053,31 @@ void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, sec_flag, key_size); - p_msg->len += len; if (status == GATT_SUCCESS) { if ((trans_id = gatt_sr_enqueue_cmd(p_tcb, op_code, handle)) != 0) { p_sreg = &gatt_cb.sr_reg[i_rcb]; conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, p_sreg->gatt_if); - status = gatts_write_attr_value_by_handle(gatt_cb.sr_reg[i_rcb].p_db, - handle, offset, p, len); + status = gatts_write_attr_value_by_handle(gatt_cb.sr_reg[i_rcb].p_db, + handle, offset, p, len); + if((sr_data.write_req.need_rsp == TRUE) && (status == GATT_APP_RSP)){ + sr_data.write_req.need_rsp = TRUE; + status = GATT_PENDING; + } + + else{ + sr_data.write_req.need_rsp = FALSE; + } + gatt_sr_send_req_callback(conn_id, - trans_id, - GATTS_REQ_TYPE_WRITE, - &sr_data); - - if(status == GATT_SUCCESS){ - attp_send_sr_msg(p_tcb, p_msg); - }else if(status == GATT_NOT_LONG){ - gatt_send_error_rsp (p_tcb, status, op_code, handle, FALSE); - } - status = GATT_PENDING; + trans_id, + GATTS_REQ_TYPE_WRITE, + &sr_data); + + if(status == GATT_SUCCESS){ + attp_send_sr_msg(p_tcb, p_msg); + gatt_dequeue_sr_cmd(p_tcb); + } + } else { GATT_TRACE_ERROR("max pending command, send error\n"); status = GATT_BUSY; /* max pending command, application error */ @@ -1078,23 +1085,24 @@ void gatts_process_write_req (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 handle, } /* in theroy BUSY is not possible(should already been checked), protected check */ - if (status != GATT_PENDING && status != GATT_BUSY && + if (status != GATT_PENDING && status != GATT_BUSY && status != GATT_SUCCESS && (op_code == GATT_REQ_PREPARE_WRITE || op_code == GATT_REQ_WRITE)) { gatt_send_error_rsp (p_tcb, status, op_code, handle, FALSE); + gatt_dequeue_sr_cmd(p_tcb); } return; } /******************************************************************************* -** -** Function gatts_process_read_req -** -** Description This function is called to process the read request -** from client. -** -** Returns void -** -*******************************************************************************/ + ** + ** Function gatts_process_read_req + ** + ** Description This function is called to process the read request + ** from client. + ** + ** Returns void + ** + *******************************************************************************/ static void gatts_process_read_req(tGATT_TCB *p_tcb, tGATT_SR_REG *p_rcb, UINT8 op_code, UINT16 handle, UINT16 len, UINT8 *p_data) { @@ -1140,7 +1148,8 @@ static void gatts_process_read_req(tGATT_TCB *p_tcb, tGATT_SR_REG *p_rcb, UINT8 p_msg->len += value_len; } - if (reason != GATT_SUCCESS && reason != GATT_PENDING) { + + if (reason != GATT_SUCCESS && reason != GATT_PENDING && reason != GATT_STACK_RSP) { if (p_msg) { GKI_freebuf(p_msg); } @@ -1148,9 +1157,11 @@ static void gatts_process_read_req(tGATT_TCB *p_tcb, tGATT_SR_REG *p_rcb, UINT8 /* in theroy BUSY is not possible(should already been checked), protected check */ if (reason != GATT_BUSY) { gatt_send_error_rsp (p_tcb, reason, op_code, handle, FALSE); + gatt_dequeue_sr_cmd(p_tcb); } } else { attp_send_sr_msg(p_tcb, p_msg); + gatt_dequeue_sr_cmd(p_tcb); } } @@ -1376,24 +1387,24 @@ void gatt_server_handle_client_req (tGATT_TCB *p_tcb, UINT8 op_code, /* otherwise, ignore the pkt */ } else { switch (op_code) { - case GATT_REQ_READ_BY_GRP_TYPE: /* discover primary services */ - case GATT_REQ_FIND_TYPE_VALUE: /* discover service by UUID */ + case GATT_REQ_READ_BY_GRP_TYPE: /* discover primary services */ + case GATT_REQ_FIND_TYPE_VALUE: /* discover service by UUID */ gatts_process_primary_service_req (p_tcb, op_code, len, p_data); break; - case GATT_REQ_FIND_INFO: /* discover char descrptor */ + case GATT_REQ_FIND_INFO: /* discover char descrptor */ gatts_process_find_info(p_tcb, op_code, len, p_data); break; - case GATT_REQ_READ_BY_TYPE: /* read characteristic value, char descriptor value */ + case GATT_REQ_READ_BY_TYPE: /* read characteristic value, char descriptor value */ /* discover characteristic, discover char by UUID */ gatts_process_read_by_type_req(p_tcb, op_code, len, p_data); break; - case GATT_REQ_READ: /* read char/char descriptor value */ + case GATT_REQ_READ: /* read char/char descriptor value */ case GATT_REQ_READ_BLOB: - case GATT_REQ_WRITE: /* write char/char descriptor value */ + case GATT_REQ_WRITE: /* write char/char descriptor value */ case GATT_CMD_WRITE: case GATT_SIGN_CMD_WRITE: case GATT_REQ_PREPARE_WRITE: diff --git a/components/bt/bluedroid/stack/include/gatt_api.h b/components/bt/bluedroid/stack/include/gatt_api.h index c460a65538..9360d4fbf3 100644 --- a/components/bt/bluedroid/stack/include/gatt_api.h +++ b/components/bt/bluedroid/stack/include/gatt_api.h @@ -349,6 +349,7 @@ typedef struct { UINT16 handle; UINT16 offset; BOOLEAN is_long; + BOOLEAN need_rsp; } tGATT_READ_REQ; /* write request data */ diff --git a/docs/api/esp_gatt_defs.rst b/docs/api/esp_gatt_defs.rst index 9eeac6193c..70e808a5d7 100644 --- a/docs/api/esp_gatt_defs.rst +++ b/docs/api/esp_gatt_defs.rst @@ -25,6 +25,29 @@ Header Files Macros ^^^^^^ + +.. doxygendefine:: ESP_GATT_UUID_IMMEDIATE_ALERT_SVC +.. doxygendefine:: ESP_GATT_UUID_LINK_LOSS_SVC +.. doxygendefine:: ESP_GATT_UUID_TX_POWER_SVC +.. doxygendefine:: ESP_GATT_UUID_CURRENT_TIME_SVC +.. doxygendefine:: ESP_GATT_UUID_REF_TIME_UPDATE_SVC +.. doxygendefine:: ESP_GATT_UUID_NEXT_DST_CHANGE_SVC +.. doxygendefine:: ESP_GATT_UUID_GLUCOSE_SVC +.. doxygendefine:: ESP_GATT_UUID_HEALTH_THERMOM_SVC +.. doxygendefine:: ESP_GATT_UUID_DEVICE_INFO_SVC +.. doxygendefine:: ESP_GATT_UUID_HEART_RATE_SVC +.. doxygendefine:: ESP_GATT_UUID_PHONE_ALERT_STATUS_SVC +.. doxygendefine:: ESP_GATT_UUID_BATTERY_SERVICE_SVC +.. doxygendefine:: ESP_GATT_UUID_BLOOD_PRESSURE_SVC +.. doxygendefine:: ESP_GATT_UUID_ALERT_NTF_SVC +.. doxygendefine:: ESP_GATT_UUID_HID_SVC +.. doxygendefine:: ESP_GATT_UUID_SCAN_PARAMETERS_SVC +.. doxygendefine:: ESP_GATT_UUID_RUNNING_SPEED_CADENCE_SVC +.. doxygendefine:: ESP_GATT_UUID_CYCLING_SPEED_CADENCE_SVC +.. doxygendefine:: ESP_GATT_UUID_CYCLING_POWER_SVC +.. doxygendefine:: ESP_GATT_UUID_LOCATION_AND_NAVIGATION_SVC +.. doxygendefine:: ESP_GATT_UUID_USER_DATA_SVC +.. doxygendefine:: ESP_GATT_UUID_WEIGHT_SCALE_SVC .. doxygendefine:: ESP_GATT_UUID_PRI_SERVICE .. doxygendefine:: ESP_GATT_UUID_SEC_SERVICE .. doxygendefine:: ESP_GATT_UUID_INCLUDE_SERVICE @@ -74,6 +97,9 @@ Macros .. doxygendefine:: ESP_GATT_UUID_HID_BT_KB_INPUT .. doxygendefine:: ESP_GATT_UUID_HID_BT_KB_OUTPUT .. doxygendefine:: ESP_GATT_UUID_HID_BT_MOUSE_INPUT +.. doxygendefine:: ESP_GATT_HEART_RATE_MEAS +.. doxygendefine:: ESP_GATT_BODY_SENSOR_LOCATION +.. doxygendefine:: ESP_GATT_HEART_RATE_CNTL_POINT .. doxygendefine:: ESP_GATT_UUID_BATTERY_LEVEL .. doxygendefine:: ESP_GATT_UUID_SC_CONTROL_POINT .. doxygendefine:: ESP_GATT_UUID_SENSOR_LOCATION @@ -87,6 +113,8 @@ Macros .. doxygendefine:: ESP_GATT_ILLEGAL_HANDLE .. doxygendefine:: ESP_GATT_ATTR_HANDLE_MAX .. doxygendefine:: ESP_GATT_MAX_ATTR_LEN +.. doxygendefine:: ESP_GATT_RSP_BY_APP +.. doxygendefine:: ESP_GATT_AUTO_RSP .. doxygendefine:: ESP_GATT_IF_NONE Type Definitions @@ -126,9 +154,6 @@ Structures .. doxygenstruct:: esp_gatts_incl128_svc_desc_t :members: -.. doxygenstruct:: esp_gatts_char_desc_t - :members: - .. doxygenstruct:: esp_gatt_value_t :members: diff --git a/docs/api/esp_gatts.rst b/docs/api/esp_gatts.rst index 9e3749df0a..4278e6b33e 100644 --- a/docs/api/esp_gatts.rst +++ b/docs/api/esp_gatts.rst @@ -80,9 +80,6 @@ Structures .. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_add_char_descr_evt_param :members: -.. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_add_attr_tab_evt_param - :members: - .. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_delete_evt_param :members: @@ -104,6 +101,9 @@ Structures .. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_rsp_evt_param :members: +.. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_add_attr_tab_evt_param + :members: + .. doxygenstruct:: esp_ble_gatts_cb_param_t::gatts_set_attr_val_evt_param :members: diff --git a/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c b/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c index f4511f68a5..024362c5b7 100644 --- a/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c +++ b/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c @@ -117,64 +117,6 @@ static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = { **************************************************************************************** */ -/// Full HRS Database Description - Used to add attributes into the database -const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] = -{ - // Heart Rate Service Declaration - [HRS_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, { - {ESP_UUID_LEN_16, {ESP_GATT_UUID_PRI_SERVICE}}, - ESP_GATT_PERM_READ, - sizeof(heart_rate_svc), - sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}}, - - // Heart Rate Measurement Characteristic Declaration - [HRS_IDX_HR_MEAS_CHAR] = {{ESP_GATT_AUTO_RSP}, { - {ESP_UUID_LEN_16, {ESP_GATT_UUID_CHAR_DECLARE}}, - ESP_GATT_PERM_READ, - sizeof(heart_rate_meas_char), - sizeof(heart_rate_meas_char), (uint8_t *)&heart_rate_meas_char}}, - // Heart Rate Measurement Characteristic Value - [HRS_IDX_HR_MEAS_VAL] = {{ESP_GATT_AUTO_RSP}, { - {ESP_UUID_LEN_16, {ESP_GATT_HEART_RATE_MEAS}}, - ESP_GATT_PERM_READ, - HRPS_HT_MEAS_MAX_LEN, - 0, NULL}}, - - // Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor - [HRS_IDX_HR_MEAS_NTF_CFG] = {{ESP_GATT_AUTO_RSP}, { - {ESP_UUID_LEN_16, {ESP_GATT_UUID_CHAR_SRVR_CONFIG}}, - ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, - sizeof(uint16_t), - 0, NULL}}, - - // Body Sensor Location Characteristic Declaration - [HRS_IDX_BOBY_SENSOR_LOC_CHAR] = {{ESP_GATT_AUTO_RSP}, { - {ESP_UUID_LEN_16, {ESP_GATT_UUID_CHAR_DECLARE}}, - ESP_GATT_PERM_READ, - sizeof(heart_rate_body_sensor_loc_char), - sizeof(heart_rate_body_sensor_loc_char), (uint8_t *)&heart_rate_body_sensor_loc_char}}, - - // Body Sensor Location Characteristic Value - [HRS_IDX_BOBY_SENSOR_LOC_VAL] = {{ESP_GATT_AUTO_RSP}, { - {ESP_UUID_LEN_16, {ESP_GATT_BODY_SENSOR_LOCATION}}, - ESP_GATT_PERM_READ, - sizeof(uint8_t), - 0, NULL}}, - - // Heart Rate Control Point Characteristic Declaration - [HRS_IDX_HR_CTNL_PT_CHAR] = {{ESP_GATT_AUTO_RSP}, { - {ESP_UUID_LEN_16, {ESP_GATT_UUID_CHAR_DECLARE}}, - ESP_GATT_PERM_READ, - sizeof(heart_rate_cntl_point_char), - sizeof(heart_rate_cntl_point_char), (uint8_t *)&heart_rate_cntl_point_char}}, - - // Heart Rate Control Point Characteristic Value - [HRS_IDX_HR_CTNL_PT_VAL] = {{ESP_GATT_AUTO_RSP}, { - {ESP_UUID_LEN_16, {ESP_GATT_HEART_RATE_CNTL_POINT}}, - ESP_GATT_PERM_WRITE, - sizeof(uint8_t), - 0, NULL}}, -}; /* * Heart Rate PROFILE ATTRIBUTES @@ -182,31 +124,75 @@ const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] = */ /// Heart Rate Sensor Service -const uint16_t heart_rate_svc = ESP_GATT_UUID_HEART_RATE_SVC; +static const uint16_t heart_rate_svc = ESP_GATT_UUID_HEART_RATE_SVC; -/// Heart Rate Sensor Service - Heart Rate Measurement Characteristic -const esp_gatts_char_desc_t heart_rate_meas_char = +#define CHAR_DECLARATION_SIZE (sizeof(uint8_t)) +static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE; +static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE; +static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; +static const uint8_t char_prop_notify = ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ; +static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_READ; + +/// Heart Rate Sensor Service - Heart Rate Measurement Characteristic, notify +static const uint16_t heart_rate_meas_uuid = ESP_GATT_HEART_RATE_MEAS; +static const uint8_t heart_measurement_ccc[2] ={ 0x00, 0x00}; + + +/// Heart Rate Sensor Service -Body Sensor Location characteristic, read +static const uint16_t body_sensor_location_uuid = ESP_GATT_BODY_SENSOR_LOCATION; +static const uint8_t body_sensor_loc_val[1] = {0x00}; + + +/// Heart Rate Sensor Service - Heart Rate Control Point characteristic, write&read +static const uint16_t heart_rate_ctrl_point = ESP_GATT_HEART_RATE_CNTL_POINT; +static const uint8_t heart_ctrl_point[1] = {0x00}; + +/// 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] = { - .prop = ESP_GATT_CHAR_PROP_BIT_NOTIFY, - .attr_hdl = 0, - .attr_uuid = {ESP_UUID_LEN_16, {ESP_GATT_HEART_RATE_MEAS}}, + // Heart Rate Service Declaration + [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}}, + + // Heart Rate Measurement Characteristic Declaration + [HRS_IDX_HR_MEAS_CHAR] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, + CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_notify}}, + + // Heart Rate Measurement Characteristic Value + [HRS_IDX_HR_MEAS_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_meas_uuid, ESP_GATT_PERM_READ, + HRPS_HT_MEAS_MAX_LEN,0, NULL}}, + + // Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor + [HRS_IDX_HR_MEAS_NTF_CFG] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, + sizeof(uint16_t),sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}}, + + // Body Sensor Location Characteristic Declaration + [HRS_IDX_BOBY_SENSOR_LOC_CHAR] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, + CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}}, + + // Body Sensor Location Characteristic Value + [HRS_IDX_BOBY_SENSOR_LOC_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&body_sensor_location_uuid, ESP_GATT_PERM_READ, + sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val}}, + + // Heart Rate Control Point Characteristic Declaration + [HRS_IDX_HR_CTNL_PT_CHAR] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, + CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}}, + + // Heart Rate Control Point Characteristic Value + [HRS_IDX_HR_CTNL_PT_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_ctrl_point, ESP_GATT_PERM_WRITE|ESP_GATT_PERM_READ, + sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}}, }; -/// Heart Rate Sensor Service -Body Sensor Location characteristic -const esp_gatts_char_desc_t heart_rate_body_sensor_loc_char = -{ - .prop = ESP_GATT_CHAR_PROP_BIT_READ, - .attr_hdl = 0, - .attr_uuid = {ESP_UUID_LEN_16, {ESP_GATT_BODY_SENSOR_LOCATION}}, -}; -/// Heart Rate Sensor Service - Heart Rate Control Point characteristic -const esp_gatts_char_desc_t heart_rate_cntl_point_char = -{ - .prop = ESP_GATT_CHAR_PROP_BIT_WRITE, - .attr_hdl = 0, - .attr_uuid = {ESP_UUID_LEN_16, {ESP_GATT_HEART_RATE_CNTL_POINT}}, -}; static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { @@ -223,7 +209,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_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) -{ +{ LOG_ERROR("event = %x\n",event); switch (event) { case ESP_GATTS_REG_EVT: diff --git a/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h b/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h index ecce340df7..c12d1aa59b 100644 --- a/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h +++ b/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h @@ -46,12 +46,3 @@ enum HRS_IDX_NB, }; - - -extern const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB]; -/// Heart Rate Sensor Service - only one instance for now -extern const uint16_t heart_rate_svc; - -extern const esp_gatts_char_desc_t heart_rate_meas_char; -extern const esp_gatts_char_desc_t heart_rate_body_sensor_loc_char; -extern const esp_gatts_char_desc_t heart_rate_cntl_point_char; From e4811216ffdd19e2a72ff60cf791245caf087091 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 11 Jan 2017 17:17:13 +0800 Subject: [PATCH 143/167] deep sleep: fix regression due to moving ets_update_cpu_frequency into IRAM Deep sleep stub may call ets_update_cpu_frequency, which has been moved from ROM to IRAM. Restore the ROM version in the linker script, call it ets_update_cpu_frequency_rom, use it in the deep sleep stub. --- components/esp32/deep_sleep.c | 2 +- components/esp32/include/rom/ets_sys.h | 12 ++++++++++++ components/esp32/ld/esp32.rom.ld | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/components/esp32/deep_sleep.c b/components/esp32/deep_sleep.c index 7f59c21635..774fd4ce7a 100644 --- a/components/esp32/deep_sleep.c +++ b/components/esp32/deep_sleep.c @@ -91,7 +91,7 @@ void RTC_IRAM_ATTR esp_default_wake_deep_sleep(void) { #if CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY > 0 // ROM code has not started yet, so we need to set delay factor // used by ets_delay_us first. - ets_update_cpu_frequency(ets_get_detected_xtal_freq() / 1000000); + ets_update_cpu_frequency_rom(ets_get_detected_xtal_freq() / 1000000); // This delay is configured in menuconfig, it can be used to give // the flash chip some time to become ready. ets_delay_us(CONFIG_ESP32_DEEP_SLEEP_WAKEUP_DELAY); diff --git a/components/esp32/include/rom/ets_sys.h b/components/esp32/include/rom/ets_sys.h index 690691675a..0f972f2c33 100644 --- a/components/esp32/include/rom/ets_sys.h +++ b/components/esp32/include/rom/ets_sys.h @@ -383,6 +383,18 @@ void ets_delay_us(uint32_t us); */ void ets_update_cpu_frequency(uint32_t ticks_per_us); +/** + * @brief Set the real CPU ticks per us to the ets, so that ets_delay_us will be accurate. + * + * @note This function only sets the tick rate for the current CPU. It is located in ROM, + * so the deep sleep stub can use it even if IRAM is not initialized yet. + * + * @param uint32_t ticks_per_us : CPU ticks per us. + * + * @return None + */ +void ets_update_cpu_frequency_rom(uint32_t ticks_per_us); + /** * @brief Get the real CPU ticks per us to the ets. * This function do not return real CPU ticks per us, just the record in ets. It can be used to check with the real CPU frequency. diff --git a/components/esp32/ld/esp32.rom.ld b/components/esp32/ld/esp32.rom.ld index 33309894dc..7543fa42aa 100644 --- a/components/esp32/ld/esp32.rom.ld +++ b/components/esp32/ld/esp32.rom.ld @@ -202,7 +202,7 @@ PROVIDE ( ets_timer_init = 0x400084e8 ); PROVIDE ( ets_timer_setfn = 0x40008350 ); PROVIDE ( ets_unpack_flash_code = 0x40007018 ); PROVIDE ( ets_unpack_flash_code_legacy = 0x4000694c ); -/* PROVIDE ( ets_update_cpu_frequency = 0x40008550 ); */ /* Updates g_ticks_per_us on the current CPU only; not on the other core */ +PROVIDE ( ets_update_cpu_frequency_rom = 0x40008550 ); /* Updates g_ticks_per_us on the current CPU only; not on the other core */ PROVIDE ( ets_waiti0 = 0x400067d8 ); PROVIDE ( exc_cause_table = 0x3ff991d0 ); PROVIDE ( _exit_r = 0x4000bd28 ); From 9aa0e290796c2ee2142dac2c92dcbb1dc8a2d9f3 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 11 Jan 2017 17:23:23 +0800 Subject: [PATCH 144/167] deep sleep: keep RTC_SLOW_MEM powered on if data is placed into RTC slow memory --- components/esp32/deep_sleep.c | 18 ++++++++++++------ components/esp32/include/esp_attr.h | 2 +- components/esp32/ld/esp32.common.ld | 2 ++ docs/api/deep_sleep.rst | 2 ++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/components/esp32/deep_sleep.c b/components/esp32/deep_sleep.c index 774fd4ce7a..3eef7ca29d 100644 --- a/components/esp32/deep_sleep.c +++ b/components/esp32/deep_sleep.c @@ -302,12 +302,18 @@ static uint32_t get_power_down_flags() { // Where needed, convert AUTO options to ON. Later interpret AUTO as OFF. - // RTC_SLOW_MEM is needed only for the ULP. - // If RTC_SLOW_MEM is Auto, and ULP wakeup isn't enabled, power down RTC_SLOW_MEM. - if (s_config.pd_options[ESP_PD_DOMAIN_RTC_SLOW_MEM] == ESP_PD_OPTION_AUTO) { - if (s_config.wakeup_triggers & RTC_SAR_TRIG_EN) { - s_config.pd_options[ESP_PD_DOMAIN_RTC_SLOW_MEM] = ESP_PD_OPTION_ON; - } + // RTC_SLOW_MEM is needed for the ULP, so keep RTC_SLOW_MEM powered up if ULP + // is used and RTC_SLOW_MEM is Auto. + // If there is any data placed into .rtc.data or .rtc.bss segments, and + // RTC_SLOW_MEM is Auto, keep it powered up as well. + + // These labels are defined in the linker script: + extern int _rtc_data_start, _rtc_data_end, _rtc_bss_start, _rtc_bss_end; + + if (s_config.pd_options[ESP_PD_DOMAIN_RTC_SLOW_MEM] == ESP_PD_OPTION_AUTO || + &_rtc_data_end > &_rtc_data_start || + &_rtc_bss_end > &_rtc_bss_start) { + s_config.pd_options[ESP_PD_DOMAIN_RTC_SLOW_MEM] = ESP_PD_OPTION_ON; } // RTC_FAST_MEM is needed for deep sleep stub. diff --git a/components/esp32/include/esp_attr.h b/components/esp32/include/esp_attr.h index 7ef2920d98..911201aace 100644 --- a/components/esp32/include/esp_attr.h +++ b/components/esp32/include/esp_attr.h @@ -26,7 +26,7 @@ // Forces data into DRAM instead of flash #define DRAM_ATTR __attribute__((section(".dram1"))) -// Forces a string into DRAM instrad of flash +// 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/ld/esp32.common.ld b/components/esp32/ld/esp32.common.ld index 833eb90969..8cc3b5b22e 100644 --- a/components/esp32/ld/esp32.common.ld +++ b/components/esp32/ld/esp32.common.ld @@ -19,9 +19,11 @@ SECTIONS */ .rtc.data : { + _rtc_data_start = ABSOLUTE(.); *(.rtc.data) *(.rtc.rodata) *rtc_wake_stub*.o(.data .rodata .data.* .rodata.* .bss .bss.*) + _rtc_data_end = ABSOLUTE(.); } > rtc_slow_seg /* RTC bss, from any source file named rtc_wake_stub*.c */ diff --git a/docs/api/deep_sleep.rst b/docs/api/deep_sleep.rst index 3a458fb4a8..9e6642fd90 100644 --- a/docs/api/deep_sleep.rst +++ b/docs/api/deep_sleep.rst @@ -73,6 +73,8 @@ By default, ``esp_deep_sleep_start`` function will power down all RTC power doma Note: on the first revision of the ESP32, RTC fast memory will always be kept enabled in deep sleep, so that the deep sleep stub can run after reset. This can be overriden, if the application doesn't need clean reset behaviour after deep sleep. +If some variables in the program are placed into RTC slow memory (for example, using ``RTC_DATA_ATTR`` attribute), RTC slow memory will be kept powered on by default. This can be overriden using ``esp_deep_sleep_pd_config`` function, if desired. + .. doxygenfunction:: esp_deep_sleep_pd_config .. doxygenenum:: esp_deep_sleep_pd_domain_t .. doxygenenum:: esp_deep_sleep_pd_option_t From b24ac487cb49148ec17f2ab077fe72439de0f8e1 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 11 Jan 2017 17:28:09 +0800 Subject: [PATCH 145/167] newlib: use RTC_STORE registers to keep boot time instead of RTC_SLOW_MEM This allows RTC_SLOW_MEM to be powered down in deep sleep if no other variables are placed into RTC_SLOW_MEM. --- components/esp32/Kconfig | 2 ++ components/esp32/include/rom/rtc.h | 14 ++++++---- components/newlib/time.c | 44 +++++++++++++++++++++--------- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index bc35ca0e8f..f0f74e9895 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -381,6 +381,8 @@ choice ESP32_TIME_SYSCALL longer to run. - If no timers are used, gettimeofday and time functions return -1 and set errno to ENOSYS. + - When RTC is used for timekeeping, two RTC_STORE registers are + used to keep time in deep sleep mode. config ESP32_TIME_SYSCALL_USE_RTC bool "RTC" diff --git a/components/esp32/include/rom/rtc.h b/components/esp32/include/rom/rtc.h index 1ff7f033b9..e3c031775d 100644 --- a/components/esp32/include/rom/rtc.h +++ b/components/esp32/include/rom/rtc.h @@ -53,16 +53,18 @@ extern "C" { * Rtc store registers usage * RTC_CNTL_STORE0_REG * RTC_CNTL_STORE1_REG - * RTC_CNTL_STORE2_REG - * RTC_CNTL_STORE3_REG - * RTC_CNTL_STORE4_REG Reserved - * RTC_CNTL_STORE5_REG External Xtal Frequency + * RTC_CNTL_STORE2_REG Boot time, low word + * RTC_CNTL_STORE3_REG Boot time, high word + * RTC_CNTL_STORE4_REG External XTAL frequency + * RTC_CNTL_STORE5_REG APB bus frequency * RTC_CNTL_STORE6_REG FAST_RTC_MEMORY_ENTRY * RTC_CNTL_STORE7_REG FAST_RTC_MEMORY_CRC ************************************************************************************* */ -#define RTC_ENTRY_ADDR_REG RTC_CNTL_STORE6_REG -#define RTC_MEMORY_CRC_REG RTC_CNTL_STORE7_REG +#define RTC_BOOT_TIME_LOW_REG RTC_CNTL_STORE2_REG +#define RTC_BOOT_TIME_HIGH_REG RTC_CNTL_STORE3_REG +#define RTC_ENTRY_ADDR_REG RTC_CNTL_STORE6_REG +#define RTC_MEMORY_CRC_REG RTC_CNTL_STORE7_REG typedef enum { diff --git a/components/newlib/time.c b/components/newlib/time.c index 363e17b3eb..c9fa72eeee 100644 --- a/components/newlib/time.c +++ b/components/newlib/time.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "esp_attr.h" #include "esp_intr_alloc.h" #include "soc/soc.h" @@ -58,9 +59,9 @@ static uint64_t get_rtc_time_us() // s_boot_time: time from Epoch to the first boot time #ifdef WITH_RTC -static RTC_DATA_ATTR struct timeval s_boot_time; +// when RTC is used to persist time, two RTC_STORE registers are used to store boot time #elif defined(WITH_FRC1) -static struct timeval s_boot_time; +static uint64_t s_boot_time; #endif #if defined(WITH_RTC) || defined(WITH_FRC1) @@ -88,6 +89,31 @@ static void IRAM_ATTR frc_timer_isr() #endif // WITH_FRC1 +static void set_boot_time(uint64_t time_us) +{ + _lock_acquire(&s_boot_time_lock); +#ifdef WITH_RTC + REG_WRITE(RTC_BOOT_TIME_LOW_REG, (uint32_t) (time_us & 0xffffffff)); + REG_WRITE(RTC_BOOT_TIME_HIGH_REG, (uint32_t) (time_us >> 32)); +#else + s_boot_time = time_us; +#endif + _lock_release(&s_boot_time_lock); +} + +static uint64_t get_boot_time() +{ + uint64_t result; + _lock_acquire(&s_boot_time_lock); +#ifdef WITH_RTC + result = ((uint64_t) REG_READ(RTC_BOOT_TIME_LOW_REG)) + (((uint64_t) REG_READ(RTC_BOOT_TIME_HIGH_REG)) << 32); +#else + result = s_boot_time; +#endif + _lock_release(&s_boot_time_lock); + return result; +} + void esp_setup_time_syscalls() { #if defined( WITH_FRC1 ) @@ -148,13 +174,10 @@ int IRAM_ATTR _gettimeofday_r(struct _reent *r, struct timeval *tv, void *tz) { (void) tz; #if defined( WITH_FRC1 ) || defined( WITH_RTC ) - uint64_t microseconds = get_time_since_boot(); if (tv) { - _lock_acquire(&s_boot_time_lock); - microseconds += s_boot_time.tv_usec; - tv->tv_sec = s_boot_time.tv_sec + microseconds / 1000000; + uint64_t microseconds = get_boot_time() + get_time_since_boot(); + tv->tv_sec = microseconds / 1000000; tv->tv_usec = microseconds % 1000000; - _lock_release(&s_boot_time_lock); } return 0; #else @@ -168,14 +191,9 @@ int settimeofday(const struct timeval *tv, const struct timezone *tz) (void) tz; #if defined( WITH_FRC1 ) || defined( WITH_RTC ) if (tv) { - _lock_acquire(&s_boot_time_lock); uint64_t now = ((uint64_t) tv->tv_sec) * 1000000LL + tv->tv_usec; uint64_t since_boot = get_time_since_boot(); - uint64_t boot_time = now - since_boot; - - s_boot_time.tv_sec = boot_time / 1000000; - s_boot_time.tv_usec = boot_time % 1000000; - _lock_release(&s_boot_time_lock); + set_boot_time(now - since_boot); } return 0; #else From 142756615b5eb4d985b731eb89b7d2b097f9c609 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Thu, 12 Jan 2017 14:44:26 +0800 Subject: [PATCH 146/167] component/bt : mv demo name 1. mv demo name 2. fix a docs --- components/bt/bluedroid/api/esp_blufi_api.c | 8 ++++---- components/bt/bluedroid/api/include/esp_gatts_api.h | 3 ++- .../Makefile | 2 +- .../README.rst | 0 .../main/component.mk | 0 .../main/gatts_table_creat_demo.c | 0 .../main/gatts_table_creat_demo.h | 0 .../sdkconfig.defaults | 0 8 files changed, 7 insertions(+), 6 deletions(-) rename examples/{30_gatt_server_table_create => 33_gatt_server_service_table}/Makefile (81%) rename examples/{30_gatt_server_table_create => 33_gatt_server_service_table}/README.rst (100%) rename examples/{30_gatt_server_table_create => 33_gatt_server_service_table}/main/component.mk (100%) rename examples/{30_gatt_server_table_create => 33_gatt_server_service_table}/main/gatts_table_creat_demo.c (100%) rename examples/{30_gatt_server_table_create => 33_gatt_server_service_table}/main/gatts_table_creat_demo.h (100%) rename examples/{30_gatt_server_table_create => 33_gatt_server_service_table}/sdkconfig.defaults (100%) diff --git a/components/bt/bluedroid/api/esp_blufi_api.c b/components/bt/bluedroid/api/esp_blufi_api.c index 00fbeffbc9..2fcbbab794 100644 --- a/components/bt/bluedroid/api/esp_blufi_api.c +++ b/components/bt/bluedroid/api/esp_blufi_api.c @@ -27,7 +27,7 @@ esp_err_t esp_blufi_register_callbacks(esp_blufi_callbacks_t *callbacks) if (esp_bluedroid_get_status() == ESP_BLUEDROID_STATUS_UNINITIALIZED) { return ESP_ERR_INVALID_STATE; } - + if (callbacks == NULL) { return ESP_FAIL; } @@ -44,7 +44,7 @@ esp_err_t esp_blufi_send_wifi_conn_report(wifi_mode_t opmode, esp_blufi_sta_conn 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_CFG_REPORT; @@ -64,7 +64,7 @@ esp_err_t esp_blufi_profile_init(void) 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_INIT; @@ -79,7 +79,7 @@ esp_err_t esp_blufi_profile_deinit(void) 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_DEINIT; diff --git a/components/bt/bluedroid/api/include/esp_gatts_api.h b/components/bt/bluedroid/api/include/esp_gatts_api.h index 8d51ac7375..b18039ce79 100644 --- a/components/bt/bluedroid/api/include/esp_gatts_api.h +++ b/components/bt/bluedroid/api/include/esp_gatts_api.h @@ -441,7 +441,8 @@ esp_err_t esp_ble_gatts_stop_service(uint16_t service_handle); /** - * @brief This function is called to read a characteristics descriptor. + * @brief Send indicate or notify to GATT client. + * Set param need_confirm as false will send notification, otherwise indication. * * @param[in] gatts_if: GATT server access interface * @param[in] conn_id - connection id to indicate. diff --git a/examples/30_gatt_server_table_create/Makefile b/examples/33_gatt_server_service_table/Makefile similarity index 81% rename from examples/30_gatt_server_table_create/Makefile rename to examples/33_gatt_server_service_table/Makefile index a6e41ff33a..2ff9c6eaee 100644 --- a/examples/30_gatt_server_table_create/Makefile +++ b/examples/33_gatt_server_service_table/Makefile @@ -3,7 +3,7 @@ # project subdirectory. # -PROJECT_NAME := gatt_server_table_creat_demo +PROJECT_NAME := gatt_server_service_table_demo COMPONENT_ADD_INCLUDEDIRS := components/include diff --git a/examples/30_gatt_server_table_create/README.rst b/examples/33_gatt_server_service_table/README.rst similarity index 100% rename from examples/30_gatt_server_table_create/README.rst rename to examples/33_gatt_server_service_table/README.rst diff --git a/examples/30_gatt_server_table_create/main/component.mk b/examples/33_gatt_server_service_table/main/component.mk similarity index 100% rename from examples/30_gatt_server_table_create/main/component.mk rename to examples/33_gatt_server_service_table/main/component.mk diff --git a/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c b/examples/33_gatt_server_service_table/main/gatts_table_creat_demo.c similarity index 100% rename from examples/30_gatt_server_table_create/main/gatts_table_creat_demo.c rename to examples/33_gatt_server_service_table/main/gatts_table_creat_demo.c diff --git a/examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h b/examples/33_gatt_server_service_table/main/gatts_table_creat_demo.h similarity index 100% rename from examples/30_gatt_server_table_create/main/gatts_table_creat_demo.h rename to examples/33_gatt_server_service_table/main/gatts_table_creat_demo.h diff --git a/examples/30_gatt_server_table_create/sdkconfig.defaults b/examples/33_gatt_server_service_table/sdkconfig.defaults similarity index 100% rename from examples/30_gatt_server_table_create/sdkconfig.defaults rename to examples/33_gatt_server_service_table/sdkconfig.defaults From fec2f534d5e2ebe9f7472e125215f6841b1f61c7 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Thu, 12 Jan 2017 16:25:09 +0800 Subject: [PATCH 147/167] component/bt : gatt server table fix uuid discovery bug --- components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c index a5631f5b7a..2ba3ec0f14 100644 --- a/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c +++ b/components/bt/bluedroid/btc/profile/std/gatt/btc_gatts.c @@ -203,8 +203,8 @@ static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, esp_gatt_srvc_id_t esp_srvc_id; esp_srvc_id.id.inst_id = srvc_inst_id; - btc_gatts_uuid_format_convert(&esp_srvc_id.id.uuid,gatts_attr_db[i].att_desc.uuid_length, - gatts_attr_db[i].att_desc.uuid_p); + btc_gatts_uuid_format_convert(&esp_srvc_id.id.uuid,gatts_attr_db[i].att_desc.length, + gatts_attr_db[i].att_desc.value); btc_to_bta_srvc_id(&srvc_id, &esp_srvc_id); BTA_GATTS_CreateService(gatts_if, &srvc_id.id.uuid, From 1895460406163bf7570992d975525ba03a1f6535 Mon Sep 17 00:00:00 2001 From: Tian Hao Date: Thu, 12 Jan 2017 16:29:07 +0800 Subject: [PATCH 148/167] component/bt : fix adv stop bug --- components/bt/bluedroid/stack/btm/btm_ble_gap.c | 1 - 1 file changed, 1 deletion(-) diff --git a/components/bt/bluedroid/stack/btm/btm_ble_gap.c b/components/bt/bluedroid/stack/btm/btm_ble_gap.c index 4ded30de83..98a8a23689 100644 --- a/components/bt/bluedroid/stack/btm/btm_ble_gap.c +++ b/components/bt/bluedroid/stack/btm/btm_ble_gap.c @@ -1089,7 +1089,6 @@ tBTM_STATUS BTM_BleSetAdvParamsStartAdv(UINT16 adv_int_min, UINT16 adv_int_max, p_cb->adv_chnl_map = chnl_map; p_addr_cb->own_addr_type = own_bda_type; p_cb->evt_type = adv_type; - p_cb->adv_mode = BTM_BLE_ADV_ENABLE; p_cb->afp = afp; if (p_dir_bda) { From 6eedbfa9beb290d36a4c53ea698c66a6443d0710 Mon Sep 17 00:00:00 2001 From: XiaXiaotian Date: Thu, 12 Jan 2017 20:39:36 +0800 Subject: [PATCH 149/167] tw9503: fix ets timer crash bug 1. tw9503: call ets_timer_disarm before calling ets_timer_setfn 2. change CHECK_AP_CONN to CHECK_STA_CONN --- components/esp32/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/lib b/components/esp32/lib index c01bfe9038..b26cd21764 160000 --- a/components/esp32/lib +++ b/components/esp32/lib @@ -1 +1 @@ -Subproject commit c01bfe9038e59fc0dc15947c1bf4616de006e103 +Subproject commit b26cd217641acf08da932f4de8e858083ea83113 From ad66fbe5ada66eb2ce8c7f6dee2421560fbf8d89 Mon Sep 17 00:00:00 2001 From: Alexey Gerenkov Date: Tue, 10 Jan 2017 14:48:47 +0300 Subject: [PATCH 150/167] esp32: Fixes issues discussed during code review of MR!341 The following issues mentioned during MR!341 review were fixed: 1) Core dump test application description 2) Usage of CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH and CONFIG_ESP32_ENABLE_COREDUMP_TO_UART 3) FLASH_GUARD_START macro usage is fixed in flash API 4) Core dump module logging facility 5) cache util functions doc updated 6) interactive delay before print core dump to uart 7) core dump partion support in build system --- components/esp32/Kconfig | 23 +++ components/esp32/core_dump.c | 189 +++++++++++------- components/esp32/cpu_start.c | 2 +- components/esp32/panic.c | 32 +-- components/espcoredump/test/component.mk | 0 components/espcoredump/test/test_core_dump.c | 105 ++++++++++ .../include/freertos/FreeRTOSConfig.h | 2 +- components/log/include/esp_log.h | 10 + components/partition_table/gen_esp32part.py | 1 + .../partitions_singleapp_coredump.csv | 2 +- .../partitions_two_ota_coredump.csv | 2 +- components/spi_flash/cache_utils.h | 6 +- components/spi_flash/flash_ops.c | 39 ++-- docs/core_dump.rst | 96 +++------ docs/index.rst | 1 + 15 files changed, 324 insertions(+), 186 deletions(-) create mode 100644 components/espcoredump/test/component.mk create mode 100644 components/espcoredump/test/test_core_dump.c diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 9811585140..3bdfd230cb 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -66,12 +66,35 @@ choice ESP32_COREDUMP_TO_FLASH_OR_UART config ESP32_ENABLE_COREDUMP_TO_FLASH bool "Flash" + select ESP32_ENABLE_COREDUMP config ESP32_ENABLE_COREDUMP_TO_UART bool "UART" + select ESP32_ENABLE_COREDUMP config ESP32_ENABLE_COREDUMP_TO_NONE bool "None" endchoice +config ESP32_ENABLE_COREDUMP + bool + default F + help + Enables/disable core dump module. + +config ESP32_CORE_DUMP_UART_DELAY + int "Core dump print to UART delay" + depends on ESP32_ENABLE_COREDUMP_TO_UART + default 0 + help + Config delay (in ms) before printing core dump to UART. + Delay can be interrupted by pressing Enter key. + +config ESP32_CORE_DUMP_LOG_LEVEL + int "Core dump module logging level" + depends on ESP32_ENABLE_COREDUMP + default 1 + help + Config core dump module logging level (0-5). + # Not implemented and/or needs new silicon rev to work config MEMMAP_SPISRAM bool "Use external SPI SRAM chip as main memory" diff --git a/components/esp32/core_dump.c b/components/esp32/core_dump.c index 13519badb7..85301f4dd6 100644 --- a/components/esp32/core_dump.c +++ b/components/esp32/core_dump.c @@ -14,21 +14,40 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "soc/uart_reg.h" +#include "soc/io_mux_reg.h" +#include "soc/timer_group_struct.h" +#include "soc/timer_group_reg.h" +#include "driver/gpio.h" #include "esp_panic.h" #include "esp_partition.h" -#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH || CONFIG_ESP32_ENABLE_COREDUMP_TO_UART +#if CONFIG_ESP32_ENABLE_COREDUMP +#define LOG_LOCAL_LEVEL CONFIG_ESP32_CORE_DUMP_LOG_LEVEL #include "esp_log.h" const static char *TAG = "esp_core_dump"; +#define ESP_COREDUMP_LOGE( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_ERROR) { ets_printf(LOG_FORMAT(E, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#define ESP_COREDUMP_LOGW( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_WARN) { ets_printf(LOG_FORMAT(W, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#define ESP_COREDUMP_LOGI( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) { ets_printf(LOG_FORMAT(I, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#define ESP_COREDUMP_LOGD( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG) { ets_printf(LOG_FORMAT(D, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#define ESP_COREDUMP_LOGV( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_VERBOSE) { ets_printf(LOG_FORMAT(V, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } + +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH +#define ESP_COREDUMP_LOG_PROCESS( format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG) { ets_printf(LOG_FORMAT(D, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); } +#else +#define ESP_COREDUMP_LOG_PROCESS( format, ... ) do{/*(__VA_ARGS__);*/}while(0) +#endif + + // TODO: allow user to set this in menuconfig or get tasks iteratively #define COREDUMP_MAX_TASKS_NUM 32 -typedef esp_err_t (*esp_core_dump_write_prepare_t)(void *priv, uint32_t *data_len, int verb); -typedef esp_err_t (*esp_core_dump_write_start_t)(void *priv, int verb); -typedef esp_err_t (*esp_core_dump_write_end_t)(void *priv, int verb); -typedef esp_err_t (*esp_core_dump_flash_write_data_t)(void *priv, void * data, uint32_t data_len, int verb); +typedef esp_err_t (*esp_core_dump_write_prepare_t)(void *priv, uint32_t *data_len); +typedef esp_err_t (*esp_core_dump_write_start_t)(void *priv); +typedef esp_err_t (*esp_core_dump_write_end_t)(void *priv); +typedef esp_err_t (*esp_core_dump_flash_write_data_t)(void *priv, void * data, uint32_t data_len); typedef struct _core_dump_write_config_t { @@ -40,19 +59,17 @@ typedef struct _core_dump_write_config_t } core_dump_write_config_t; -static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *write_cfg, int verb) +static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *write_cfg) { union { uint8_t data8[12]; uint32_t data32[3]; } rom_data; - //const esp_partition_t *core_part; esp_err_t err; TaskSnapshot_t tasks[COREDUMP_MAX_TASKS_NUM]; UBaseType_t tcb_sz, task_num; uint32_t data_len = 0, i, len; - //size_t off; task_num = uxTaskGetSnapshotAll(tasks, COREDUMP_MAX_TASKS_NUM, &tcb_sz); // take TCB padding into account, actual TCB size will be stored in header @@ -63,22 +80,21 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri // header + tasknum*(tcb + stack start/end + tcb addr) data_len = 3*sizeof(uint32_t) + task_num*(len + 2*sizeof(uint32_t) + sizeof(uint32_t *)); for (i = 0; i < task_num; i++) { - if (tasks[i].pxTCB == xTaskGetCurrentTaskHandle()) { + if (tasks[i].pxTCB == xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID())) { // set correct stack top for current task tasks[i].pxTopOfStack = (StackType_t *)frame; - if (verb) - ets_printf("Current task EXIT/PC/PS/A0/SP %x %x %x %x %x\r\n", frame->exit, frame->pc, frame->ps, frame->a0, frame->a1); + ESP_COREDUMP_LOG_PROCESS("Current task EXIT/PC/PS/A0/SP %x %x %x %x %x", frame->exit, frame->pc, frame->ps, frame->a0, frame->a1); } else { - if (verb) { - XtSolFrame *task_frame = (XtSolFrame *)tasks[i].pxTopOfStack; - if (task_frame->exit == 0) { - ets_printf("Task EXIT/PC/PS/A0/SP %x %x %x %x %x\r\n", task_frame->exit, task_frame->pc, task_frame->ps, task_frame->a0, task_frame->a1); - } - else { - XtExcFrame *task_frame2 = (XtExcFrame *)tasks[i].pxTopOfStack; - ets_printf("Task EXIT/PC/PS/A0/SP %x %x %x %x %x\r\n", task_frame2->exit, task_frame2->pc, task_frame2->ps, task_frame2->a0, task_frame2->a1); - } + XtSolFrame *task_frame = (XtSolFrame *)tasks[i].pxTopOfStack; + if (task_frame->exit == 0) { + ESP_COREDUMP_LOG_PROCESS("Task EXIT/PC/PS/A0/SP %x %x %x %x %x", task_frame->exit, task_frame->pc, task_frame->ps, task_frame->a0, task_frame->a1); + } + else { +#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH + XtExcFrame *task_frame2 = (XtExcFrame *)tasks[i].pxTopOfStack; +#endif + ESP_COREDUMP_LOG_PROCESS("Task EXIT/PC/PS/A0/SP %x %x %x %x %x", task_frame2->exit, task_frame2->pc, task_frame2->ps, task_frame2->a0, task_frame2->a1); } } #if( portSTACK_GROWTH < 0 ) @@ -86,9 +102,7 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri #else len = (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack; #endif - if (verb) { - ets_printf("Stack len = %lu (%x %x)\r\n", len, tasks[i].pxTopOfStack, tasks[i].pxEndOfStack); - } + ESP_COREDUMP_LOG_PROCESS("Stack len = %lu (%x %x)", len, tasks[i].pxTopOfStack, tasks[i].pxEndOfStack); // take stack padding into account if (len % sizeof(uint32_t)) len = (len / sizeof(uint32_t) + 1) * sizeof(uint32_t); @@ -97,22 +111,20 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri // prepare write if (write_cfg->prepare) { - err = write_cfg->prepare(write_cfg->priv, &data_len, verb); + err = write_cfg->prepare(write_cfg->priv, &data_len); if (err != ESP_OK) { - ets_printf("ERROR: Failed to prepare core dump (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to prepare core dump (%d)!", err); return; } } - if (verb) { - ets_printf("Core dump len = %lu\r\n", data_len); - } + ESP_COREDUMP_LOG_PROCESS("Core dump len = %lu", data_len); // write start if (write_cfg->start) { - err = write_cfg->start(write_cfg->priv, verb); + err = write_cfg->start(write_cfg->priv); if (err != ESP_OK) { - ets_printf("ERROR: Failed to start core dump (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to start core dump (%d)!", err); return; } } @@ -121,30 +133,28 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri rom_data.data32[0] = data_len; rom_data.data32[1] = task_num; rom_data.data32[2] = tcb_sz; - err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t), verb); + err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t)); if (err != ESP_OK) { - ets_printf("ERROR: Failed to write core dump header (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to write core dump header (%d)!", err); return; } // write tasks for (i = 0; i < task_num; i++) { - if (verb) { - ets_printf("Dump task %x\r\n", tasks[i].pxTCB); - } + ESP_COREDUMP_LOG_PROCESS("Dump task %x", tasks[i].pxTCB); // save TCB address, stack base and stack top addr rom_data.data32[0] = (uint32_t)tasks[i].pxTCB; rom_data.data32[1] = (uint32_t)tasks[i].pxTopOfStack; rom_data.data32[2] = (uint32_t)tasks[i].pxEndOfStack; - err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t), verb); + err = write_cfg->write(write_cfg->priv, &rom_data, 3*sizeof(uint32_t)); if (err != ESP_OK) { - ets_printf("ERROR: Failed to write task header (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to write task header (%d)!", err); return; } // save TCB - err = write_cfg->write(write_cfg->priv, tasks[i].pxTCB, tcb_sz, verb); + err = write_cfg->write(write_cfg->priv, tasks[i].pxTCB, tcb_sz); if (err != ESP_OK) { - ets_printf("ERROR: Failed to write TCB (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to write TCB (%d)!", err); return; } // save task stack @@ -156,18 +166,18 @@ static void esp_core_dump_write(XtExcFrame *frame, core_dump_write_config_t *wri tasks[i].pxEndOfStack, (uint32_t)tasks[i].pxTopOfStack - (uint32_t)tasks[i].pxEndOfStack #endif - , verb); + ); if (err != ESP_OK) { - ets_printf("ERROR: Failed to write task stack (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to write task stack (%d)!", err); return; } } // write end if (write_cfg->end) { - err = write_cfg->end(write_cfg->priv, verb); + err = write_cfg->end(write_cfg->priv); if (err != ESP_OK) { - ets_printf("ERROR: Failed to end core dump (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to end core dump (%d)!", err); return; } } @@ -202,7 +212,7 @@ static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint data_len = (data_size / sizeof(uint32_t)) * sizeof(uint32_t); err = spi_flash_write(off, data, data_len); if (err != ESP_OK) { - ets_printf("ERROR: Failed to write data to flash (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to write data to flash (%d)!", err); return 0; } @@ -214,7 +224,7 @@ static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint rom_data.data8[k] = *(data + data_len + k); err = spi_flash_write(off + data_len, &rom_data, sizeof(uint32_t)); if (err != ESP_OK) { - ets_printf("ERROR: Failed to finish write data to flash (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to finish write data to flash (%d)!", err); return 0; } data_len += sizeof(uint32_t); @@ -223,7 +233,7 @@ static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint return data_len; } -static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_len, int verb) +static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_len) { esp_err_t err; uint32_t sec_num; @@ -231,7 +241,7 @@ static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_le // add space for 2 magics. TODO: change to CRC if ((*data_len + 2*sizeof(uint32_t)) > s_core_part_size) { - ets_printf("ERROR: Not enough space to save core dump!\r\n"); + ESP_COREDUMP_LOGE("Not enough space to save core dump!"); return ESP_ERR_NO_MEM; } *data_len += 2*sizeof(uint32_t); @@ -243,7 +253,7 @@ static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_le sec_num++; err = spi_flash_erase_range(s_core_part_start + 0, sec_num * SPI_FLASH_SEC_SIZE); if (err != ESP_OK) { - ets_printf("ERROR: Failed to erase flash (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to erase flash (%d)!", err); return err; } @@ -257,7 +267,7 @@ static esp_err_t esp_core_dump_flash_write_word(core_dump_write_flash_data_t *wr err = spi_flash_write(s_core_part_start + wr_data->off, &data32, sizeof(uint32_t)); if (err != ESP_OK) { - ets_printf("ERROR: Failed to write to flash (%d)!\r\n", err); + ESP_COREDUMP_LOGE("Failed to write to flash (%d)!", err); return err; } wr_data->off += sizeof(uint32_t); @@ -265,16 +275,17 @@ static esp_err_t esp_core_dump_flash_write_word(core_dump_write_flash_data_t *wr return err; } -static esp_err_t esp_core_dump_flash_write_start(void *priv, int verb) +static esp_err_t esp_core_dump_flash_write_start(void *priv) { core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; // save magic 1 return esp_core_dump_flash_write_word(wr_data, COREDUMP_FLASH_MAGIC_START); } -static esp_err_t esp_core_dump_flash_write_end(void *priv, int verb) +static esp_err_t esp_core_dump_flash_write_end(void *priv) { core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; +#if LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG uint32_t i; union { @@ -282,27 +293,24 @@ static esp_err_t esp_core_dump_flash_write_end(void *priv, int verb) uint32_t data32[4]; } rom_data; - if (verb) { - // TEST READ START - esp_err_t err = spi_flash_read(s_core_part_start + 0, &rom_data, sizeof(rom_data)); - if (err != ESP_OK) { - ets_printf("ERROR: Failed to read flash (%d)!\r\n", err); - return err; - } - else { - ets_printf("Data from flash:\r\n"); - for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) { - ets_printf("%x\r\n", rom_data.data32[i]); - } - } - // TEST READ END + esp_err_t err = spi_flash_read(s_core_part_start + 0, &rom_data, sizeof(rom_data)); + if (err != ESP_OK) { + ESP_COREDUMP_LOGE("Failed to read flash (%d)!", err); + return err; } + else { + ESP_COREDUMP_LOG_PROCESS("Data from flash:"); + for (i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) { + ESP_COREDUMP_LOG_PROCESS("%x", rom_data.data32[i]); + } + } +#endif // save magic 2 return esp_core_dump_flash_write_word(wr_data, COREDUMP_FLASH_MAGIC_END); } -static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_t data_len, int verb) +static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_t data_len) { esp_err_t err = ESP_OK; core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv; @@ -330,9 +338,9 @@ void esp_core_dump_to_flash(XtExcFrame *frame) wr_cfg.write = esp_core_dump_flash_write_data; wr_cfg.priv = &wr_data; - ets_printf("Save core dump to flash...\r\n"); - esp_core_dump_write(frame, &wr_cfg, 0); - ets_printf("Core dump has been saved to flash.\r\n"); + ESP_COREDUMP_LOGI("Save core dump to flash..."); + esp_core_dump_write(frame, &wr_cfg); + ESP_COREDUMP_LOGI("Core dump has been saved to flash."); } #endif @@ -362,27 +370,26 @@ static void esp_core_dump_b64_encode(const uint8_t *src, uint32_t src_len, uint8 dst[j++] = '\0'; } -static esp_err_t esp_core_dump_uart_write_start(void *priv, int verb) +static esp_err_t esp_core_dump_uart_write_start(void *priv) { esp_err_t err = ESP_OK; ets_printf("================= CORE DUMP START =================\r\n"); return err; } -static esp_err_t esp_core_dump_uart_write_end(void *priv, int verb) +static esp_err_t esp_core_dump_uart_write_end(void *priv) { esp_err_t err = ESP_OK; ets_printf("================= CORE DUMP END =================\r\n"); return err; } -static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t data_len, int verb) +static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t data_len) { esp_err_t err = ESP_OK; char buf[64 + 4], *addr = data; char *end = addr + data_len; - while (addr < end) { size_t len = end - addr; if (len > 48) len = 48; @@ -397,9 +404,21 @@ static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t return err; } +static int esp_core_dump_uart_get_char() { + int i; + uint32_t reg = (READ_PERI_REG(UART_STATUS_REG(0)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT; + if (reg) + i = READ_PERI_REG(UART_FIFO_REG(0)); + else + i = -1; + return i; +} + void esp_core_dump_to_uart(XtExcFrame *frame) { core_dump_write_config_t wr_cfg; + uint32_t tm_end, tm_cur; + int ch; wr_cfg.prepare = NULL; wr_cfg.start = esp_core_dump_uart_write_start; @@ -407,9 +426,27 @@ void esp_core_dump_to_uart(XtExcFrame *frame) wr_cfg.write = esp_core_dump_uart_write_data; wr_cfg.priv = NULL; - ets_printf("Print core dump to uart...\r\n"); - esp_core_dump_write(frame, &wr_cfg, 0); - ets_printf("Core dump has been written to uart.\r\n"); + //Make sure txd/rxd are enabled + gpio_pullup_dis(1); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_U0RXD); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_U0TXD); + + ESP_COREDUMP_LOGI("Press Enter to print core dump to UART..."); + tm_end = xthal_get_ccount() / (XT_CLOCK_FREQ / 1000) + CONFIG_ESP32_CORE_DUMP_UART_DELAY; + ch = esp_core_dump_uart_get_char(); + while (!(ch == '\n' || ch == '\r')) { + tm_cur = xthal_get_ccount() / (XT_CLOCK_FREQ / 1000); + if (tm_cur >= tm_end) + break; + /* Feed the Cerberus. */ + TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE; + TIMERG0.wdt_feed = 1; + TIMERG0.wdt_wprotect = 0; + ch = esp_core_dump_uart_get_char(); + } + ESP_COREDUMP_LOGI("Print core dump to uart..."); + esp_core_dump_write(frame, &wr_cfg); + ESP_COREDUMP_LOGI("Core dump has been written to uart."); } #endif diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 2ae4260bd5..5ae68fc643 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -217,7 +217,7 @@ void start_cpu0_default(void) } #endif -#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH || CONFIG_ESP32_ENABLE_COREDUMP_TO_UART +#if CONFIG_ESP32_ENABLE_COREDUMP esp_core_dump_init(); #endif diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 09ce520ded..3db09370ac 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -162,39 +162,39 @@ void panicHandler(XtExcFrame *frame) reason = reasons[regs[20]]; } haltOtherCore(); - esp_panicPutStr("Guru Meditation Error: Core "); - esp_panicPutDec(xPortGetCoreID()); - esp_panicPutStr(" panic'ed ("); + panicPutStr("Guru Meditation Error: Core "); + panicPutDec(xPortGetCoreID()); + panicPutStr(" panic'ed ("); if (!abort_called) { - esp_panicPutStr(reason); - esp_panicPutStr(")\r\n"); + panicPutStr(reason); + panicPutStr(")\r\n"); if (regs[20]==PANIC_RSN_DEBUGEXCEPTION) { int debugRsn; asm("rsr.debugcause %0":"=r"(debugRsn)); - esp_panicPutStr("Debug exception reason: "); - if (debugRsn&XCHAL_DEBUGCAUSE_ICOUNT_MASK) esp_panicPutStr("SingleStep "); - if (debugRsn&XCHAL_DEBUGCAUSE_IBREAK_MASK) esp_panicPutStr("HwBreakpoint "); + panicPutStr("Debug exception reason: "); + if (debugRsn&XCHAL_DEBUGCAUSE_ICOUNT_MASK) panicPutStr("SingleStep "); + if (debugRsn&XCHAL_DEBUGCAUSE_IBREAK_MASK) panicPutStr("HwBreakpoint "); if (debugRsn&XCHAL_DEBUGCAUSE_DBREAK_MASK) { //Unlike what the ISA manual says, this core seemingly distinguishes from a DBREAK //reason caused by watchdog 0 and one caused by watchdog 1 by setting bit 8 of the //debugcause if the cause is watchdog 1 and clearing it if it's watchdog 0. if (debugRsn&(1<<8)) { #if CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK - esp_panicPutStr("Stack canary watchpoint triggered "); + panicPutStr("Stack canary watchpoint triggered "); #else - esp_panicPutStr("Watchpoint 1 triggered "); + panicPutStr("Watchpoint 1 triggered "); #endif } else { - esp_panicPutStr("Watchpoint 0 triggered "); + panicPutStr("Watchpoint 0 triggered "); } } - if (debugRsn&XCHAL_DEBUGCAUSE_BREAK_MASK) esp_panicPutStr("BREAK instr "); - if (debugRsn&XCHAL_DEBUGCAUSE_BREAKN_MASK) esp_panicPutStr("BREAKN instr "); - if (debugRsn&XCHAL_DEBUGCAUSE_DEBUGINT_MASK) esp_panicPutStr("DebugIntr "); - esp_panicPutStr("\r\n"); + if (debugRsn&XCHAL_DEBUGCAUSE_BREAK_MASK) panicPutStr("BREAK instr "); + if (debugRsn&XCHAL_DEBUGCAUSE_BREAKN_MASK) panicPutStr("BREAKN instr "); + if (debugRsn&XCHAL_DEBUGCAUSE_DEBUGINT_MASK) panicPutStr("DebugIntr "); + panicPutStr("\r\n"); } } else { - esp_panicPutStr("abort)\r\n"); + panicPutStr("abort)\r\n"); } if (esp_cpu_in_ocd_debug_mode()) { diff --git a/components/espcoredump/test/component.mk b/components/espcoredump/test/component.mk new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/espcoredump/test/test_core_dump.c b/components/espcoredump/test/test_core_dump.c new file mode 100644 index 0000000000..41137f9302 --- /dev/null +++ b/components/espcoredump/test/test_core_dump.c @@ -0,0 +1,105 @@ +/* Application For Core Dumps Generation + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "nvs_flash.h" + + +// task crash indicators +#define TCI_NULL_PTR 0x1 +#define TCI_UNALIGN_PTR 0x2 +#define TCI_FAIL_ASSERT 0x4 + +volatile unsigned long crash_flags = TCI_UNALIGN_PTR; + +void bad_ptr_func() +{ + unsigned long *ptr = (unsigned long *)0; + volatile int cnt; + int i = 0; + + for (i = 0; i < 1000; i++) { + cnt++; + } + + if(crash_flags & TCI_NULL_PTR) { + printf("Write to bad address 0x%lx.\n", (unsigned long)ptr); + *ptr = 0xDEADBEEF; + } +} + +void bad_ptr_task(void *pvParameter) +{ + printf("Task 'bad_ptr_task' start.\n"); + while (1) { + vTaskDelay(1000 / portTICK_RATE_MS); + printf("Task 'bad_ptr_task' run.\n"); + bad_ptr_func(); + } + fflush(stdout); +} + +void recur_func() +{ + static int rec_cnt; + unsigned short *ptr = (unsigned short *)0x5; + volatile int cnt; + int i = 0; + + if (rec_cnt++ > 2) { + return; + } + for (i = 0; i < 4; i++) { + cnt++; + if(i == 2) { + recur_func(); + break; + } + } + + if(crash_flags & TCI_UNALIGN_PTR) { + printf("Write to unaligned address 0x%lx.\n", (unsigned long)ptr); + *ptr = 0xDEAD; + } +} + +void unaligned_ptr_task(void *pvParameter) +{ + printf("Task 'unaligned_ptr_task' start.\n"); + while (1) { + vTaskDelay(1000 / portTICK_RATE_MS); + printf("Task 'unaligned_ptr_task' run.\n"); + recur_func(); + } + fflush(stdout); +} + +void failed_assert_task(void *pvParameter) +{ + printf("Task 'failed_assert_task' start.\n"); + while (1) { + vTaskDelay(1000 / portTICK_RATE_MS); + printf("Task 'failed_assert_task' run.\n"); + if(crash_flags & TCI_FAIL_ASSERT) { + printf("Assert.\n"); + assert(0); + } + } + fflush(stdout); +} + +void app_main() +{ + nvs_flash_init(); + xTaskCreate(&bad_ptr_task, "bad_ptr_task", 2048, NULL, 5, NULL); + xTaskCreatePinnedToCore(&unaligned_ptr_task, "unaligned_ptr_task", 2048, NULL, 7, NULL, 1); + xTaskCreatePinnedToCore(&failed_assert_task, "failed_assert_task", 2048, NULL, 10, NULL, 0); +} diff --git a/components/freertos/include/freertos/FreeRTOSConfig.h b/components/freertos/include/freertos/FreeRTOSConfig.h index 7c9d8fd972..b2fc077bc3 100644 --- a/components/freertos/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/include/freertos/FreeRTOSConfig.h @@ -268,7 +268,7 @@ #define configXT_BOARD 1 /* Board mode */ #define configXT_SIMULATOR 0 -#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH | CONFIG_ESP32_ENABLE_COREDUMP_TO_UART +#if CONFIG_ESP32_ENABLE_COREDUMP #define configENABLE_TASK_SNAPSHOT 1 #endif diff --git a/components/log/include/esp_log.h b/components/log/include/esp_log.h index 33bc10b42a..6fda8d6054 100644 --- a/components/log/include/esp_log.h +++ b/components/log/include/esp_log.h @@ -75,6 +75,16 @@ void esp_log_set_vprintf(vprintf_like_t func); */ uint32_t esp_log_timestamp(void); +/** + * @brief Function which returns timestamp to be used in log output + * + * This function uses HW cycle counter and does not depend on OS, + * so it can be safely used after application crash. + * + * @return timestamp, in milliseconds + */ +uint32_t esp_log_early_timestamp(void); + /** * @brief Write message into the log * diff --git a/components/partition_table/gen_esp32part.py b/components/partition_table/gen_esp32part.py index a4412ad45b..0491204885 100755 --- a/components/partition_table/gen_esp32part.py +++ b/components/partition_table/gen_esp32part.py @@ -127,6 +127,7 @@ class PartitionDefinition(object): "ota" : 0x00, "phy" : 0x01, "nvs" : 0x02, + "coredump" : 0x03, "esphttpd" : 0x80, "fat" : 0x81, "spiffs" : 0x82, diff --git a/components/partition_table/partitions_singleapp_coredump.csv b/components/partition_table/partitions_singleapp_coredump.csv index 96719a425a..a9f12c0fd3 100644 --- a/components/partition_table/partitions_singleapp_coredump.csv +++ b/components/partition_table/partitions_singleapp_coredump.csv @@ -3,4 +3,4 @@ nvs, data, nvs, 0x9000, 0x6000 phy_init, data, phy, 0xf000, 0x1000 factory, app, factory, 0x10000, 1M -coredump, data, 3, , 64K +coredump, data, coredump,, 64K diff --git a/components/partition_table/partitions_two_ota_coredump.csv b/components/partition_table/partitions_two_ota_coredump.csv index 3d2b34ec9a..64d70b0d8e 100644 --- a/components/partition_table/partitions_two_ota_coredump.csv +++ b/components/partition_table/partitions_two_ota_coredump.csv @@ -4,6 +4,6 @@ nvs, data, nvs, 0x9000, 0x4000 otadata, data, ota, 0xd000, 0x2000 phy_init, data, phy, 0xf000, 0x1000 factory, 0, 0, 0x10000, 1M -coredump, data, 3, , 64K +coredump, data, coredump,, 64K ota_0, 0, ota_0, , 1M ota_1, 0, ota_1, , 1M diff --git a/components/spi_flash/cache_utils.h b/components/spi_flash/cache_utils.h index a5442d1af7..598b8fba77 100644 --- a/components/spi_flash/cache_utils.h +++ b/components/spi_flash/cache_utils.h @@ -41,13 +41,11 @@ void spi_flash_disable_interrupts_caches_and_other_cpu(); void spi_flash_enable_interrupts_caches_and_other_cpu(); // Disables non-IRAM interrupt handlers on current CPU and caches on both CPUs. -// This function is implied to be called from panic handler -// (when non-current CPU is halted and can not execute code from flash) or when no OS is present. +// This function is implied to be called when other CPU is not running or running code from IRAM. void spi_flash_disable_interrupts_caches_and_other_cpu_no_os(); // Enable cache, enable interrupts on current CPU. -// This function is implied to be called from panic handler -// (when non-current CPU is halted and can not execute code from flash) or when no OS is present. +// This function is implied to be called when other CPU is not running or running code from IRAM. void spi_flash_enable_interrupts_caches_no_os(); #endif //ESP_SPI_FLASH_CACHE_UTILS_H diff --git a/components/spi_flash/flash_ops.c b/components/spi_flash/flash_ops.c index 831679f437..fffe487bd1 100644 --- a/components/spi_flash/flash_ops.c +++ b/components/spi_flash/flash_ops.c @@ -58,9 +58,6 @@ static spi_flash_counters_t s_flash_stats; #endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS -#define FLASH_GUARD_START(_gp_) do{if((_gp_)) (_gp_)->start();}while(0) -#define FLASH_GUARD_END(_gp_) do{if((_gp_)) (_gp_)->end();}while(0) - static esp_err_t spi_flash_translate_rc(SpiFlashOpResult rc); const DRAM_ATTR spi_flash_guard_funcs_t g_flash_guard_default_ops = { @@ -106,6 +103,18 @@ SpiFlashOpResult IRAM_ATTR spi_flash_unlock() return SPI_FLASH_RESULT_OK; } +static inline void spi_flash_guard_start() +{ + if (s_flash_guard_ops) + s_flash_guard_ops->start(); +} + +static inline void spi_flash_guard_end() +{ + if (s_flash_guard_ops) + s_flash_guard_ops->end(); +} + esp_err_t IRAM_ATTR spi_flash_erase_sector(size_t sec) { return spi_flash_erase_range(sec * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE); @@ -126,7 +135,7 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) size_t end = start + size / SPI_FLASH_SEC_SIZE; const size_t sectors_per_block = BLOCK_ERASE_SIZE / SPI_FLASH_SEC_SIZE; COUNTER_START(); - FLASH_GUARD_START(s_flash_guard_ops); + spi_flash_guard_start(); SpiFlashOpResult rc; rc = spi_flash_unlock(); if (rc == SPI_FLASH_RESULT_OK) { @@ -142,7 +151,7 @@ esp_err_t IRAM_ATTR spi_flash_erase_range(uint32_t start_addr, uint32_t size) } } } - FLASH_GUARD_END(s_flash_guard_ops); + spi_flash_guard_end(); COUNTER_STOP(erase); return spi_flash_translate_rc(rc); } @@ -180,9 +189,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) if (left_size > 0) { uint32_t t = 0xffffffff; memcpy(((uint8_t *) &t) + (dst - left_off), srcc, left_size); - FLASH_GUARD_START(s_flash_guard_ops); + spi_flash_guard_start(); rc = SPIWrite(left_off, &t, 4); - FLASH_GUARD_END(s_flash_guard_ops); + spi_flash_guard_end(); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -198,9 +207,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) bool in_dram = true; #endif if (in_dram && (((uintptr_t) srcc) + mid_off) % 4 == 0) { - FLASH_GUARD_START(s_flash_guard_ops); + spi_flash_guard_start(); rc = SPIWrite(dst + mid_off, (const uint32_t *) (srcc + mid_off), mid_size); - FLASH_GUARD_END(s_flash_guard_ops); + spi_flash_guard_end(); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -214,9 +223,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) uint32_t t[8]; uint32_t write_size = MIN(mid_size, sizeof(t)); memcpy(t, srcc + mid_off, write_size); - FLASH_GUARD_START(s_flash_guard_ops); + spi_flash_guard_start(); rc = SPIWrite(dst + mid_off, t, write_size); - FLASH_GUARD_END(s_flash_guard_ops); + spi_flash_guard_end(); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -229,9 +238,9 @@ esp_err_t IRAM_ATTR spi_flash_write(size_t dst, const void *srcv, size_t size) if (right_size > 0) { uint32_t t = 0xffffffff; memcpy(&t, srcc + right_off, right_size); - FLASH_GUARD_START(s_flash_guard_ops); + spi_flash_guard_start(); rc = SPIWrite(dst + right_off, &t, 4); - FLASH_GUARD_END(s_flash_guard_ops); + spi_flash_guard_end(); if (rc != SPI_FLASH_RESULT_OK) { goto out; } @@ -291,7 +300,7 @@ esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size) SpiFlashOpResult rc = SPI_FLASH_RESULT_OK; COUNTER_START(); - FLASH_GUARD_START(s_flash_guard_ops); + spi_flash_guard_start(); /* To simplify boundary checks below, we handle small reads separately. */ if (size < 16) { uint32_t t[6]; /* Enough for 16 bytes + 4 on either side for padding. */ @@ -365,7 +374,7 @@ esp_err_t IRAM_ATTR spi_flash_read(size_t src, void *dstv, size_t size) memcpy(dstc + pad_right_off, t, pad_right_size); } out: - FLASH_GUARD_END(s_flash_guard_ops); + spi_flash_guard_end(); COUNTER_STOP(read); return spi_flash_translate_rc(rc); } diff --git a/docs/core_dump.rst b/docs/core_dump.rst index 0d0624cc89..a8e328996f 100644 --- a/docs/core_dump.rst +++ b/docs/core_dump.rst @@ -1,10 +1,10 @@ ESP32 Core Dump -================ +=============== Overview -------- -ESP-IDF provides support to generate core dumps on unrecoverable software errors. This useful technique allows post-mortem analisys of software state at the moment of failure. +ESP-IDF provides support to generate core dumps on unrecoverable software errors. This useful technique allows post-mortem analysis of software state at the moment of failure. Upon the crash system enters panic state, prints some information and halts or reboots depending configuration. User can choose to generate core dump in order to analyse the reason of failure on PC later on. Core dump contains snapshots of all tasks in the system at the moment of failure. Snapshots include tasks control blocks (TCB) and stacks. So it is possible to find out what task, at what instruction (line of code) and what callstack of that task lead to the crash. @@ -30,16 +30,16 @@ Save core dump to flash When this option is selected core dumps are saved to special partition on flash. When using default partition table files which are provided with ESP-IDF it automatically allocates necessary space on flash, But if user wants to use its own layout file together with core dump feature it should define separate partition for core dump as it is shown below:: - + # Name, Type, SubType, Offset, Size # Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild nvs, data, nvs, 0x9000, 0x6000 phy_init, data, phy, 0xf000, 0x1000 factory, app, factory, 0x10000, 1M - coredump, data, 3, , 64K - + coredump, data, coredump,, 64K + There are no special requrements for partition name. It can be choosen according to the user application needs, but partition type should be 'data' and -sub-type should be 3. Also when choosing partition size note that core dump data structure introduces constant overhead of 20 bytes and per-task overhead of 12 bytes. +sub-type should be 'coredump'. Also when choosing partition size note that core dump data structure introduces constant overhead of 20 bytes and per-task overhead of 12 bytes. This overhead does not include size of TCB and stack for every task. So partirion size should be at least 20 + max tasks number x (12 + TCB size + max task stack size) bytes. The example of generic command to analyze core dump from flash is: `espcoredump.py -p info_corefile ` @@ -55,73 +55,27 @@ or `espcoredump.py -p dbg_corefile -t b64 -c + ================= CORE DUMP END =================== -Command Options For 'espcoredump.py' --------------------------------------------- +Running 'espcoredump.py' +------------------------------------ -usage: coredumper [-h] [--chip {auto,esp32}] [--port PORT] [--baud BAUD] - {dbg_corefile,info_corefile} ... +Generic command syntax: -espcoredump.py v0.1-dev - ESP32 Core Dump Utility +`espcoredump.py [options] command [args]` -positional arguments: - {dbg_corefile,info_corefile} - Run coredumper {command} -h for additional help - dbg_corefile Starts GDB debugging session with specified corefile - info_corefile Print core dump info from file - -optional arguments: - -h, --help show this help message and exit - --chip {auto,esp32}, -c {auto,esp32} - Target chip type - --port PORT, -p PORT Serial port device - --baud BAUD, -b BAUD Serial port baud rate used when flashing/reading - - -usage: coredumper info_corefile [-h] [--gdb GDB] [--core CORE] - [--core-format CORE_FORMAT] [--off OFF] - [--save-core SAVE_CORE] [--print-mem] - prog - -positional arguments: - prog Path to program's ELF binary - -optional arguments: - -h, --help show this help message and exit - --gdb GDB, -g GDB Path to gdb - --core CORE, -c CORE Path to core dump file (if skipped core dump will be - read from flash) - --core-format CORE_FORMAT, -t CORE_FORMAT - (elf, raw or b64). File specified with "-c" is an ELF - ("elf"), raw (raw) or base64-encoded (b64) binary - --off OFF, -o OFF Ofsset of coredump partition in flash (type "make - partition_table" to see). - --save-core SAVE_CORE, -s SAVE_CORE - Save core to file. Othwerwise temporary core file will - be deleted. Does not work with "-c" - --print-mem, -m Print memory dump - - -usage: coredumper dbg_corefile [-h] [--gdb GDB] [--core CORE] - [--core-format CORE_FORMAT] [--off OFF] - [--save-core SAVE_CORE] - prog - -positional arguments: - prog Path to program's ELF binary - -optional arguments: - -h, --help show this help message and exit - --gdb GDB, -g GDB Path to gdb - --core CORE, -c CORE Path to core dump file (if skipped core dump will be - read from flash) - --core-format CORE_FORMAT, -t CORE_FORMAT - (elf, raw or b64). File specified with "-c" is an ELF - ("elf"), raw (raw) or base64-encoded (b64) binary - --off OFF, -o OFF Ofsset of coredump partition in flash (type "make - partition_table" to see). - --save-core SAVE_CORE, -s SAVE_CORE - Save core to file. Othwerwise temporary core file will - be deleted. Ignored with "-c" +:Script Options: + * --chip,-c {auto,esp32}. Target chip type. Supported values are `auto` and `esp32`. + * --port,-p PORT. Serial port device. + * --baud,-b BAUD. Serial port baud rate used when flashing/reading. +:Commands: + * info_corefile. Retrieve core dump and print useful info. + * dbg_corefile. Retrieve core dump and start GDB session with it. +:Command Arguments: + * --gdb,-g GDB. Path to gdb to use for data retrieval. + * --core,-c CORE. Path to core dump file to use (if skipped core dump will be read from flash). + * --core-format,-t CORE_FORMAT. Specifies that file passed with "-c" is an ELF ("elf"), dumped raw binary ("raw") or base64-encoded ("b64") format. + * --off,-o OFF. Ofsset of coredump partition in flash (type "make partition_table" to see it). + * --save-core,-s SAVE_CORE. Save core to file. Othwerwise temporary core file will be deleted. Ignored with "-c". + * --print-mem,-m Print memory dump. Used only with "info_corefile". diff --git a/docs/index.rst b/docs/index.rst index 2d9a62f14b..b994648848 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,6 +35,7 @@ Contents: partition-tables build_system openocd + core_dump Flash encryption Secure Boot ULP coprocessor From 7d40f17d1dab9a7f721afb21451e522c6948dee1 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 13 Jan 2017 10:26:58 +1100 Subject: [PATCH 151/167] bootloader_random: Restore all SARADC/I2S registers to reset values Fix for issue with I2S0 not being usable after bootloader_random_enable() --- components/bootloader_support/src/bootloader_random.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/bootloader_support/src/bootloader_random.c b/components/bootloader_support/src/bootloader_random.c index c8b6c24b1f..b58ebe941d 100644 --- a/components/bootloader_support/src/bootloader_random.c +++ b/components/bootloader_support/src/bootloader_random.c @@ -124,10 +124,13 @@ void bootloader_random_disable(void) /* Restore SYSCON mode registers */ CLEAR_PERI_REG_MASK(SENS_SAR_READ_CTRL_REG, SENS_SAR1_DIG_FORCE); CLEAR_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DIG_FORCE); - CLEAR_PERI_REG_MASK(SYSCON_SARADC_CTRL_REG, SYSCON_SARADC_SAR2_MUX | SYSCON_SARADC_SAR_SEL); /* Restore SAR ADC mode */ CLEAR_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_SAR2_EN_TEST); + CLEAR_PERI_REG_MASK(SYSCON_SARADC_CTRL_REG, SYSCON_SARADC_SAR2_MUX + | SYSCON_SARADC_SAR_SEL | SYSCON_SARADC_DATA_TO_I2S); + SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 0, SENS_FORCE_XPD_SAR_S); + SET_PERI_REG_BITS(SYSCON_SARADC_FSM_REG, SYSCON_SARADC_START_WAIT, 8, SYSCON_SARADC_START_WAIT_S); /* Reset i2s peripheral */ SET_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_I2S0_RST); From 233fde166b1d55fdcf0b0f42c715d1d965b98409 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 13 Jan 2017 13:48:33 +1100 Subject: [PATCH 152/167] Toolchain detection: Allow for Windows executable name and not-yet-configured toolchain path Windows executable name based on fix suggested by @krzysztof --- make/project.mk | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/make/project.mk b/make/project.mk index d30d007dc5..3de9361c82 100644 --- a/make/project.mk +++ b/make/project.mk @@ -429,22 +429,26 @@ $(foreach submodule,$(subst $(IDF_PATH)/,,$(filter $(IDF_PATH)/%,$(COMPONENT_SUB # The part in brackets is extracted into TOOLCHAIN_COMMIT_DESC variable, # the part after the brackets is extracted into TOOLCHAIN_GCC_VER. ifndef MAKE_RESTARTS -TOOLCHAIN_COMMIT_DESC := $(shell $(CC) --version | sed -E -n 's|xtensa-esp32-elf-gcc\ \(([^)]*).*|\1|gp') -TOOLCHAIN_GCC_VER := $(shell $(CC) --version | sed -E -n 's|xtensa-esp32-elf-gcc\ \(.*\)\ (.*)|\1|gp') +TOOLCHAIN_COMMIT_DESC := $(shell $(CC) --version | sed -E -n 's|xtensa-esp32-elf-gcc.*?\ \(([^)]*).*|\1|gp') +TOOLCHAIN_GCC_VER := $(shell $(CC) --version | sed -E -n 's|xtensa-esp32-elf-gcc.*?\ \(.*\)\ (.*)|\1|gp') # Officially supported version(s) SUPPORTED_TOOLCHAIN_COMMIT_DESC := crosstool-NG crosstool-ng-1.22.0-61-gab8375a SUPPORTED_TOOLCHAIN_GCC_VERSIONS := 5.2.0 +ifdef TOOLCHAIN_COMMIT_DESC ifneq ($(TOOLCHAIN_COMMIT_DESC), $(SUPPORTED_TOOLCHAIN_COMMIT_DESC)) $(info WARNING: Toolchain version is not supported: $(TOOLCHAIN_COMMIT_DESC)) $(info Expected to see version: $(SUPPORTED_TOOLCHAIN_COMMIT_DESC)) $(info Please check ESP-IDF setup instructions and update the toolchain, or proceed at your own risk.) endif ifeq (,$(findstring $(TOOLCHAIN_GCC_VER), $(SUPPORTED_TOOLCHAIN_GCC_VERSIONS))) -$(warning WARNING: Compiler version is not supported: $(TOOLCHAIN_GCC_VER)) +$(info WARNING: Compiler version is not supported: $(TOOLCHAIN_GCC_VER)) $(info Expected to see version(s): $(SUPPORTED_TOOLCHAIN_GCC_VERSIONS)) $(info Please check ESP-IDF setup instructions and update the toolchain, or proceed at your own risk.) endif -endif #MAKE_RESTARTS +else +$(info WARNING: Failed to find Xtensa toolchain, may need to alter PATH or set one in the configuration menu) +endif # TOOLCHAIN_COMMIT_DESC +endif #MAKE_RESTARTS From 5c9c08eabb88de10a220ef7458bfc7a4093367a9 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 13 Jan 2017 14:09:01 +1100 Subject: [PATCH 153/167] Toolchain detection: Fix issue when run in a clean project If the makefile config entry hasn't been generated yet, don't test the toolchain. Closes #226 https://github.com/espressif/esp-idf/issues/226 --- make/project.mk | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/make/project.mk b/make/project.mk index 3de9361c82..c4c3022a6b 100644 --- a/make/project.mk +++ b/make/project.mk @@ -428,6 +428,7 @@ $(foreach submodule,$(subst $(IDF_PATH)/,,$(filter $(IDF_PATH)/%,$(COMPONENT_SUB # xtensa-esp32-elf-gcc (crosstool-NG crosstool-ng-1.22.0-59-ga194053) 4.8.5 # The part in brackets is extracted into TOOLCHAIN_COMMIT_DESC variable, # the part after the brackets is extracted into TOOLCHAIN_GCC_VER. +ifdef CONFIG_TOOLPREFIX ifndef MAKE_RESTARTS TOOLCHAIN_COMMIT_DESC := $(shell $(CC) --version | sed -E -n 's|xtensa-esp32-elf-gcc.*?\ \(([^)]*).*|\1|gp') TOOLCHAIN_GCC_VER := $(shell $(CC) --version | sed -E -n 's|xtensa-esp32-elf-gcc.*?\ \(.*\)\ (.*)|\1|gp') @@ -450,5 +451,6 @@ endif else $(info WARNING: Failed to find Xtensa toolchain, may need to alter PATH or set one in the configuration menu) endif # TOOLCHAIN_COMMIT_DESC -endif #MAKE_RESTARTS +endif #MAKE_RESTARTS +endif #CONFIG_TOOLPREFIX From e53d307814a9cb5c980c4e6d77496e8635d27c38 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 13 Jan 2017 14:30:00 +1100 Subject: [PATCH 154/167] Panic handler: Use same reset path as esp_restart(), disabling hardware Closes #223 https://github.com/espressif/esp-idf/issues/223 --- components/esp32/include/rom/rtc.h | 6 ++++++ components/esp32/panic.c | 7 +++---- components/esp32/system_api.c | 13 +++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/components/esp32/include/rom/rtc.h b/components/esp32/include/rom/rtc.h index e3c031775d..9bcd9bd7e0 100644 --- a/components/esp32/include/rom/rtc.h +++ b/components/esp32/include/rom/rtc.h @@ -181,6 +181,9 @@ void set_rtc_memory_crc(void); /** * @brief Software Reset digital core. * + * It is not recommended to use this function in esp-idf, use + * esp_restart() instead. + * * @param None * * @return None @@ -190,6 +193,9 @@ void software_reset(void); /** * @brief Software Reset digital core. * + * It is not recommended to use this function in esp-idf, use + * esp_restart() instead. + * * @param int cpu_no : The CPU to reset, 0 for PRO CPU, 1 for APP CPU. * * @return None diff --git a/components/esp32/panic.c b/components/esp32/panic.c index c5b18870a9..dcf7ba8d7e 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -320,6 +320,8 @@ static void doBacktrace(XtExcFrame *frame) panicPutStr("\n\n"); } +void esp_restart_noos() __attribute__ ((noreturn)); + /* 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. @@ -365,10 +367,7 @@ static void commonErrorHandler(XtExcFrame *frame) esp_gdbstub_panic_handler(frame); #elif CONFIG_ESP32_PANIC_PRINT_REBOOT || CONFIG_ESP32_PANIC_SILENT_REBOOT panicPutStr("Rebooting...\r\n"); - for (x = 0; x < 100; x++) { - ets_delay_us(1000); - } - software_reset(); + esp_restart_noos(); #else disableAllWdts(); panicPutStr("CPU halted.\r\n"); diff --git a/components/esp32/system_api.c b/components/esp32/system_api.c index 60fa1796cd..55fe026591 100644 --- a/components/esp32/system_api.c +++ b/components/esp32/system_api.c @@ -72,12 +72,25 @@ esp_err_t esp_efuse_read_mac(uint8_t* mac) esp_err_t system_efuse_read_mac(uint8_t mac[6]) __attribute__((alias("esp_efuse_read_mac"))); +void esp_restart_noos() __attribute__ ((noreturn)); + void IRAM_ATTR esp_restart(void) { esp_wifi_stop(); // Disable scheduler on this core. vTaskSuspendAll(); + + esp_restart_noos(); +} + +/* "inner" restart function for after RTOS, interrupts & anything else on this + * core are already stopped. Stalls other core, resets hardware, + * triggers restart. +*/ +void IRAM_ATTR esp_restart_noos() +{ + const uint32_t core_id = xPortGetCoreID(); const uint32_t other_core_id = core_id == 0 ? 1 : 0; esp_cpu_stall(other_core_id); From dd3f18d2d88ee78909d4af2840dfdf0b9f715f28 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Wed, 4 Jan 2017 18:54:07 +0200 Subject: [PATCH 155/167] Initial mDNS component and example --- components/mdns/component.mk | 0 components/mdns/include/mdns.h | 235 +++ components/mdns/mdns.c | 1861 +++++++++++++++++ .../tcpip_adapter/include/tcpip_adapter.h | 11 + docs/Doxyfile | 3 +- docs/api/mdns.rst | 215 ++ docs/index.rst | 1 + examples/30_mdns_example/Makefile | 9 + examples/30_mdns_example/README.md | 5 + .../30_mdns_example/main/Kconfig.projbuild | 29 + examples/30_mdns_example/main/component.mk | 4 + .../30_mdns_example/main/mdns_example_main.c | 184 ++ 12 files changed, 2556 insertions(+), 1 deletion(-) create mode 100644 components/mdns/component.mk create mode 100644 components/mdns/include/mdns.h create mode 100644 components/mdns/mdns.c create mode 100644 docs/api/mdns.rst create mode 100644 examples/30_mdns_example/Makefile create mode 100644 examples/30_mdns_example/README.md create mode 100644 examples/30_mdns_example/main/Kconfig.projbuild create mode 100644 examples/30_mdns_example/main/component.mk create mode 100644 examples/30_mdns_example/main/mdns_example_main.c diff --git a/components/mdns/component.mk b/components/mdns/component.mk new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/mdns/include/mdns.h b/components/mdns/include/mdns.h new file mode 100644 index 0000000000..58e588e3e6 --- /dev/null +++ b/components/mdns/include/mdns.h @@ -0,0 +1,235 @@ +// 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 ESP_MDNS_H_ +#define ESP_MDNS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct mdns_server_s; +typedef struct mdns_server_s mdns_server_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 */ +} 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 + * - ESP_ERR_INVALID_STATE when the network returned error + * - 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); + +/** + * @brief Stop and free mDNS server + * + * @param server mDNS Server to free + * + */ +void mdns_free(mdns_server_t * server); + +/** + * @brief Set the hostname for mDNS server + * + * @param server mDNS Server + * @param hostname Hostname to set + * + * @return + * - ESP_OK success + * - 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); + +/** + * @brief Set the default instance name for mDNS server + * + * @param server mDNS Server + * @param instance 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); + +/** + * @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 + * + * @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); + +/** + * @brief Remove service from mDNS server + * + * @param server mDNS Server + * @param service service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + * - 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); + +/** + * @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 + * + * @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_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); + +/** + * @brief Set service port + * + * @param server mDNS Server + * @param service service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param port service port + * + * @return + * - ESP_OK success + * - 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); + +/** + * @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); + +/** + * @brief Query mDNS for host or service + * + * @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 + * + * @return + * - ESP_OK success + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t mdns_result_free(mdns_server_t * server); + +#ifdef __cplusplus +} +#endif + +#endif /* ESP_MDNS_H_ */ diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c new file mode 100644 index 0000000000..d636134c7d --- /dev/null +++ b/components/mdns/mdns.c @@ -0,0 +1,1861 @@ +// 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 "mdns.h" + +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "lwip/ip_addr.h" +#include "lwip/pbuf.h" +#include "lwip/igmp.h" +#include "lwip/udp.h" +#include "esp_wifi.h" + +#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_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; +}; + +#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 TaskHandle_t _mdns_service_task_handle = NULL; +static QueueSetHandle_t _mdns_queue_set = NULL; + +static xSemaphoreHandle _mdns_service_semaphore = NULL; +#define MDNS_SERVICE_LOCK() xSemaphoreTake(_mdns_service_semaphore, portMAX_DELAY) +#define MDNS_SERVICE_UNLOCK() xSemaphoreGive(_mdns_service_semaphore) + +/* + * MDNS Server Networking + * */ + +/** + * @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) +{ + 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) { + pbuf_free(this_pb); + } + } +} + +/** + * @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 + */ +esp_err_t _mdns_server_init(mdns_server_t * server) +{ + esp_err_t err = ESP_OK; + + tcpip_adapter_ip_info_t if_ip_info; + err = tcpip_adapter_get_ip_info(server->tcpip_if, &if_ip_info); + if (err) { + return err; + } + + 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)) { + return ESP_ERR_INVALID_STATE; + } + + struct udp_pcb * pcb = udp_new(); + if (!pcb) { + return ESP_ERR_NO_MEM; + } + + pcb->remote_port = MDNS_SERVICE_PORT; + + if (udp_bind(pcb, &multicast_if_addr, pcb->remote_port) != 0) { + udp_remove(pcb); + 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); + + server->pcb = pcb; + udp_recv(pcb, &_mdns_server_recv, server); + return err; +} + +/** + * @brief stop the network of MDNS server + * + * @param server The server + * + * @return ESP_OK + */ +esp_err_t _mdns_server_deinit(mdns_server_t * server) +{ + if (server->pcb) { + udp_recv(server->pcb, NULL, NULL); + udp_disconnect(server->pcb); + udp_remove(server->pcb); + server->pcb = NULL; + } + return ESP_OK; +} + +/** + * @brief send packet over UDP + * + * @param server The server + * @param data byte array containing the packet data + * @param len length of the packet data + * + * @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) +{ + struct pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + if (pbt != NULL) { + uint8_t* dst = (uint8_t *)pbt->payload; + memcpy(dst, data, len); + err_t err = udp_sendto(server->pcb, pbt, &(server->pcb->remote_ip), server->pcb->remote_port); + pbuf_free(pbt); + if (err) { + return 0; + } + return len; + } + return 0; +} + +/* + * MDNS Servers + * */ + +static 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; iqueue == queue) { + MDNS_MUTEX_LOCK(); + _mdns_parse_packet(server, (uint8_t*)pb->payload, pb->len); + MDNS_MUTEX_UNLOCK(); + break; + } + } + pbuf_free(pb); + } + } +} + +/** + * @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) +{ + 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); + if (err) { + return err; + } + + _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) +{ + //stop UDP + _mdns_server_deinit(server); + + _mdns_servers[server->tcpip_if] = NULL; + + if (xQueueRemoveFromSet(server->queue, _mdns_queue_set) != pdPASS) { + return ESP_FAIL; + } + + uint8_t i; + for(i=0; iservice == 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; +} + +/** + * @brief reads MDNS FQDN into mdns_name_t structure + * FQDN is in format: [hostname.|[instance.]_service._proto.]local. + * + * @param packet MDNS packet + * @param start Starting point of FQDN + * @param name mdns_name_t structure to populate + * @param buf temporary char buffer + * + * @return the address after the parsed FQDN in the packet or NULL on error + */ +static const uint8_t * _mdns_read_fqdn(const uint8_t * packet, const uint8_t * start, mdns_name_t * name, char * buf) +{ + size_t index = 0; + while(start[index]) { + if (name->parts == 4) { + return NULL; + } + uint8_t len = start[index++]; + if ((len & 0xC0) == 0) { + if (len > 64) { + //length can not be more than 64 + return NULL; + } + uint8_t i; + for(i=0; iparts == 1 && buf[0] != '_' + && (strcmp(buf, MDNS_DEFAULT_DOMAIN) != 0) + && (strcmp(buf, "ip6") != 0) + && (strcmp(buf, "in-addr") != 0)) { + sprintf((char*)name, "%s.%s", name->host, buf); + } else if (strcmp(buf, MDNS_SUB_STR) == 0) { + name->sub = 1; + } else { + memcpy((uint8_t*)name + (name->parts++ * (MDNS_NAME_BUF_LEN)), buf, len+1); + } + } else { + size_t address = (((uint16_t)len & 0x3F) << 8) | start[index++]; + if ((packet + address) > start) { + //reference address can not be after where we are + return NULL; + } + if (_mdns_read_fqdn(packet, packet + address, name, buf)) { + return start + index; + } + return NULL; + } + } + 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 + * + * @param packet MDNS packet + * @param index offset of uint16_t value + * @param value the value to set + */ +static inline void _mdns_set_u16(uint8_t * packet, uint16_t index, uint16_t value) +{ + if ((index + 1) >= MDNS_MAX_PACKET_SIZE) { + return; + } + packet[index] = (value >> 8) & 0xFF; + packet[index+1] = value & 0xFF; +} + +/** + * @brief appends byte in a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param value the value to set + * + * @return length of added data: 0 on error or 1 on success + */ +static inline uint8_t _mdns_append_u8(uint8_t * packet, uint16_t * index, uint8_t value) +{ + if (*index >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + packet[*index] = value; + *index += 1; + return 1; +} + +/** + * @brief appends uint16_t in a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param value the value to set + * + * @return length of added data: 0 on error or 2 on success + */ +static inline uint8_t _mdns_append_u16(uint8_t * packet, uint16_t * index, uint16_t value) +{ + if ((*index + 1) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + _mdns_append_u8(packet, index, (value >> 8) & 0xFF); + _mdns_append_u8(packet, index, value & 0xFF); + return 2; +} + +/** + * @brief appends uint32_t in a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param value the value to set + * + * @return length of added data: 0 on error or 4 on success + */ +static inline uint8_t _mdns_append_u32(uint8_t * packet, uint16_t * index, uint32_t value) +{ + if ((*index + 3) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + _mdns_append_u8(packet, index, (value >> 24) & 0xFF); + _mdns_append_u8(packet, index, (value >> 16) & 0xFF); + _mdns_append_u8(packet, index, (value >> 8) & 0xFF); + _mdns_append_u8(packet, index, value & 0xFF); + return 4; +} + +/** + * @brief appends answer type, class, ttl and data length to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param type answer type + * @param ttl answer ttl + * + * @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) +{ + if ((*index + 10) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + if (type == MDNS_ANSWER_PTR) { + _mdns_append_u16(packet, index, MDNS_TYPE_PTR); + _mdns_append_u16(packet, index, MDNS_CLASS_IN); + } else if (type == MDNS_ANSWER_TXT) { + _mdns_append_u16(packet, index, MDNS_TYPE_TXT); + _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE); + } else if (type == MDNS_ANSWER_SRV) { + _mdns_append_u16(packet, index, MDNS_TYPE_SRV); + _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE); + } else if (type == MDNS_ANSWER_A) { + _mdns_append_u16(packet, index, MDNS_TYPE_A); + _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE); + } else if (type == MDNS_ANSWER_AAAA) { + _mdns_append_u16(packet, index, MDNS_TYPE_AAAA); + _mdns_append_u16(packet, index, MDNS_CLASS_IN_FLUSH_CACHE); + } else { + return 0; + } + _mdns_append_u32(packet, index, ttl); + _mdns_append_u16(packet, index, 0); + return 10; +} + +/** + * @brief appends single string to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param string the string to append + * + * @return length of added data: 0 on error or length of the string + 1 on success + */ +static inline uint8_t _mdns_append_string(uint8_t * packet, uint16_t * index, const char * string) +{ + uint8_t len = strlen(string); + if ((*index + len + 1) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + _mdns_append_u8(packet, index, len); + memcpy(packet + *index, string, len); + *index += len; + return len + 1; +} + +/** + * @brief appends FQDN to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param strings string array containing the parts of the FQDN + * @param count number of strings in the array + * + * @return length of added data: 0 on error or length on success + */ +static uint16_t _mdns_append_fqdn(uint8_t * packet, uint16_t * index, const char * strings[], uint8_t count) +{ + if (!count) { + return _mdns_append_u8(packet, index, 0); + } + mdns_name_t name; + static char buf[MDNS_NAME_BUF_LEN]; + uint8_t len = strlen(strings[0]); + 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 +search_next: + len_location = (uint8_t *)memchr(len_location+1, (char)len, *index - (len_location+1 - packet)); + continue; + } + //read string into name and compare + name.parts = 0; + name.sub = 0; + name.host[0] = 0; + name.service[0] = 0; + name.proto[0] = 0; + name.domain[0] = 0; + const uint8_t * content = _mdns_read_fqdn(packet, len_location, &name, buf); + if (!content) { + return 0; + } + if (name.parts == count) { + uint8_t i; + for(i=0; iinstance)?service->instance + :(server->instance)?server->instance + :server->hostname; + str[1] = service->service; + str[2] = service->proto; + str[3] = MDNS_DEFAULT_DOMAIN; + + part_length = _mdns_append_fqdn(packet, index, str + 1, 3); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, MDNS_ANSWER_PTR_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + part_length = _mdns_append_fqdn(packet, index, str, 4); + if (!part_length) { + return 0; + } + _mdns_set_u16(packet, data_len_location, part_length); + record_length += part_length; + return record_length; +} + +/** + * @brief appends TXT record for service to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param server the server that is hosting the service + * @param service the service to add record for + * + * @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) +{ + 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[1] = service->service; + str[2] = service->proto; + str[3] = MDNS_DEFAULT_DOMAIN; + + part_length = _mdns_append_fqdn(packet, index, str, 4); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_TXT, MDNS_ANSWER_TXT_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + 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; iinstance)?service->instance + :(server->instance)?server->instance + :server->hostname; + str[1] = service->service; + str[2] = service->proto; + str[3] = MDNS_DEFAULT_DOMAIN; + + part_length = _mdns_append_fqdn(packet, index, str, 4); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_SRV, MDNS_ANSWER_SRV_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + + part_length = 0; + part_length += _mdns_append_u16(packet, index, service->priority); + part_length += _mdns_append_u16(packet, index, service->weight); + part_length += _mdns_append_u16(packet, index, service->port); + if (part_length != 6) { + return 0; + } + + str[0] = server->hostname; + str[1] = MDNS_DEFAULT_DOMAIN; + part_length = _mdns_append_fqdn(packet, index, str, 2); + if (!part_length) { + return 0; + } + _mdns_set_u16(packet, data_len_location, part_length + 6); + + record_length += part_length + 6; + return record_length; +} + +/** + * @brief appends A record to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param server the server + * @param ip the IP address to add + * + * @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) +{ + const char * str[2]; + uint16_t record_length = 0; + uint8_t part_length; + + str[0] = server->hostname; + str[1] = MDNS_DEFAULT_DOMAIN; + + part_length = _mdns_append_fqdn(packet, index, str, 2); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_A, MDNS_ANSWER_A_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + + if ((*index + 3) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + _mdns_append_u8(packet, index, ip & 0xFF); + _mdns_append_u8(packet, index, (ip >> 8) & 0xFF); + _mdns_append_u8(packet, index, (ip >> 16) & 0xFF); + _mdns_append_u8(packet, index, (ip >> 24) & 0xFF); + _mdns_set_u16(packet, data_len_location, 4); + + record_length += 4; + return record_length; +} + +/** + * @brief appends AAAA record to a packet, incrementing the index + * + * @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) +{ + const char * str[2]; + uint16_t record_length = 0; + uint8_t part_length; + + str[0] = server->hostname; + str[1] = MDNS_DEFAULT_DOMAIN; + + part_length = _mdns_append_fqdn(packet, index, str, 2); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_AAAA, MDNS_ANSWER_AAAA_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + + if ((*index + 15) >= MDNS_MAX_PACKET_SIZE) { + return 0; + } + + part_length = sizeof(ip6_addr_t); + memcpy(packet + *index, ipv6, part_length); + *index += part_length; + _mdns_set_u16(packet, data_len_location, part_length); + record_length += part_length; + return record_length; +} + +/** + * @brief sends all collected answers + * + * @param server the server + * @param answers linked list of answers + */ +static void _mdns_send_answers(mdns_server_t * server, mdns_answer_item_t * answers) +{ + 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_set_u16(packet, MDNS_HEAD_FLAGS_OFFSET, MDNS_FLAGS_AUTHORITATIVE); + + while(answers) { + if (answers->answer & MDNS_ANSWER_A) { + answers->answer &= ~MDNS_ANSWER_A; + send_ip = true; + } + 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; + } + } + mdns_answer_item_t * a = answers; + answers = answers->next; + free(a); + } + if (send_ip) { + tcpip_adapter_ip_info_t if_ip_info; + tcpip_adapter_get_ip_info(server->tcpip_if, &if_ip_info); + + 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;ipriority = 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; +} + +/** + * @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(mdns_server_t * server, const char * service, const char * proto) +{ + mdns_srv_item_t * s = server->services; + while(s) { + if (!strcmp(s->service->service, service) && !strcmp(s->service->proto, proto)) { + return s; + } + s = s->next; + } + return NULL; +} + +/** + * @brief creates/allocates new service + * @param service service type + * @param proto service proto + * @param port service port + * + * @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) +{ + mdns_service_t * s = (mdns_service_t *)malloc(sizeof(mdns_service_t)); + if (!s) { + return NULL; + } + + s->priority = 0; + s->weight = 0; + s->txt_num_items = 0; + s->instance = NULL; + s->txt = NULL; + s->port = port; + + s->service = strdup(service); + if (!s->service) { + free(s); + return NULL; + } + + s->proto = strdup(proto); + if (!s->proto) { + free((char *)s->service); + free(s); + return NULL; + } + + return s; +} + +/** + * @brief free service memory + * + * @param service the service + */ +static void _mdns_free_service(mdns_service_t * service) +{ + if (!service) { + return; + } + free((char *)service->instance); + free((char *)service->service); + free((char *)service->proto); + if (service->txt_num_items) { + uint8_t i; + for(i=0; itxt_num_items; i++) { + free((char *)service->txt[i]); + } + } + free(service->txt); + free(service); +} + +/** + * @brief read uint16_t from a packet + * @param packet the packet + * @param index index in the packet where the value starts + * + * @return the value + */ +static inline uint16_t _mdns_read_u16(const uint8_t * packet, uint16_t index) +{ + return (uint16_t)(packet[index]) << 8 | packet[index+1]; +} + +/** + * @brief main packet parser + * + * @param server the server + * @param data byte array holding the packet data + * @param len length of the byte array + */ +static void _mdns_parse_packet(mdns_server_t * server, const uint8_t * data, size_t len) +{ + static mdns_name_t n; + static mdns_result_temp_t a; + + const uint8_t * content = data + MDNS_HEAD_LEN; + 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); + + if (questions) { + uint8_t qs = questions; + mdns_answer_item_t * answers = NULL; + + while(qs--) { + content = _mdns_parse_fqdn(data, content, name); + if (!content) { + break;//error + } + + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + 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)) { + answers = _mdns_add_answer(answers, NULL, MDNS_ANSWER_A); + } + } + continue; + } + + if (name->sub) { + continue; + } + + mdns_srv_item_t * si = _mdns_get_service_item(server, name->service, name->proto); + if (!si) { + //service not found + continue; + } + + if (type == MDNS_TYPE_PTR) { + answers = _mdns_add_answer(answers, 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; + } + answers = _mdns_add_answer(answers, 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; + } + answers = _mdns_add_answer(answers, 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)) { + answers = _mdns_add_answer(answers, si->service, MDNS_ANSWER_ALL); + } + } + } + if (answers) { + _mdns_send_answers(server, answers); + } + } + + if (server->search.running && (answers || additional)) { + mdns_result_temp_t * answer = &a; + memset(answer, 0, sizeof(mdns_result_temp_t)); + + while(content < (data + len)) { + content = _mdns_parse_fqdn(data, content, name); + if (!content) { + break;//error + } + uint16_t type = _mdns_read_u16(content, MDNS_TYPE_OFFSET); + uint16_t data_len = _mdns_read_u16(content, MDNS_LEN_OFFSET); + const uint8_t * data_ptr = content + MDNS_DATA_OFFSET; + + content = data_ptr + data_len; + + if (type == MDNS_TYPE_PTR) { + if (!_mdns_parse_fqdn(data, data_ptr, name)) { + continue;//error + } + 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 + } + sprintf(answer->instance, "%s", name->host); + } else if (type == MDNS_TYPE_SRV) { + 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 + } + 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 + } + } else { + sprintf(answer->instance, "%s", name->host); + } + //parse record value + if (!_mdns_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name)) { + continue;//error + } + + 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; + sprintf(answer->host, "%s", name->host); + } + } else { + sprintf(answer->host, "%s", name->host); + } + } else if (type == MDNS_TYPE_TXT) { + uint16_t i=0,b=0, y; + while(i < data_len) { + uint8_t partLen = data_ptr[i++]; + for(y=0; ytxt[b++] = d; + } + if (itxt[b++] = '&'; + } + } + answer->txt[b] = 0; + } else if (type == MDNS_TYPE_AAAA) { + if (server->search.host[0]) { + if (strcmp(name->host, server->search.host) != 0) { + continue;//wrong host + } + } else if (!answer->ptr) { + sprintf(answer->host, "%s", name->host); + } 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]) { + if (strcmp(name->host, server->search.host) != 0) { + continue;//wrong host + } + } else if (!answer->ptr) { + sprintf(answer->host, "%s", name->host); + } 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 + } +} + + + +/* + * Public Methods + * */ +esp_err_t mdns_init(tcpip_adapter_if_t tcpip_if, mdns_server_t ** mdns_server) +{ + esp_err_t err = ESP_OK; + + if (tcpip_if >= TCPIP_ADAPTER_IF_MAX) { + return ESP_ERR_INVALID_ARG; + } + + if (_mdns_server_get(tcpip_if)) { + return ESP_ERR_INVALID_STATE; + } + + uint8_t mode; + err = esp_wifi_get_mode((wifi_mode_t*)&mode); + if (err) { + return err; + } + + 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; + } + + mdns_server_t * server = (mdns_server_t *)malloc(sizeof(mdns_server_t)); + if (!server) { + return ESP_ERR_NO_MEM; + } + + 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); + return ESP_FAIL; + } + + const char * hostname = NULL; + tcpip_adapter_get_hostname(server->tcpip_if, &hostname); + mdns_set_hostname(server, hostname); + + *mdns_server = server; + + return ESP_OK; +} + +void mdns_free(mdns_server_t * server) +{ + 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); + } + 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); +} + +esp_err_t mdns_set_hostname(mdns_server_t * server, const char * hostname) +{ + if (!server) { + return ESP_ERR_INVALID_ARG; + } + MDNS_MUTEX_LOCK(); + free((char*)server->hostname); + server->hostname = (char *)malloc(strlen(hostname)+1); + if (!server->hostname) { + MDNS_MUTEX_UNLOCK(); + return ESP_ERR_NO_MEM; + } + sprintf((char *)server->hostname, "%s", hostname); + MDNS_MUTEX_UNLOCK(); + return ERR_OK; +} + +esp_err_t mdns_set_instance(mdns_server_t * server, const char * instance) +{ + if (!server) { + return ESP_ERR_INVALID_ARG; + } + MDNS_MUTEX_LOCK(); + free((char*)server->instance); + server->instance = (char *)malloc(strlen(instance)+1); + if (!server->instance) { + MDNS_MUTEX_UNLOCK(); + return ESP_ERR_NO_MEM; + } + sprintf((char *)server->instance, "%s", instance); + MDNS_MUTEX_UNLOCK(); + return ERR_OK; +} + +/* + * MDNS SERVICES + * */ + +esp_err_t mdns_service_add(mdns_server_t * server, const char * service, const char * proto, uint16_t port) +{ + if (!server || !service || !proto || !port) { + //bad argument + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * item = _mdns_get_service_item(server, service, proto); + if (item) { + //we already have that service + return mdns_service_port_set(server, service, proto, port); + } + + mdns_service_t * s = _mdns_create_service(service, proto, port); + if (!s) { + return ESP_ERR_NO_MEM; + } + + item = (mdns_srv_item_t *)malloc(sizeof(mdns_srv_item_t)); + if (!item) { + return ESP_ERR_NO_MEM; + } + + item->service = s; + item->next = server->services; + server->services = item; + return ESP_OK; +} + +esp_err_t mdns_service_port_set(mdns_server_t * server, const char * service, const char * proto, uint16_t port) +{ + if (!server || !server->services || !service || !proto || !port) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + MDNS_MUTEX_LOCK(); + s->service->port = port; + MDNS_MUTEX_UNLOCK(); + 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) +{ + if (!server || !server->services || !service || !proto) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(server, service, proto); + if (!s) { + return ESP_ERR_NOT_FOUND; + } + MDNS_MUTEX_LOCK(); + if (s->service->txt_num_items) { + uint8_t i; + for(i=0; iservice->txt_num_items; i++) { + free((char *)s->service->txt[i]); + } + } + 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; iservice->txt[i] = strdup(txt[i]); + if (!s->service->txt[i]) { + s->service->txt_num_items = i; + goto fail; + } + } + } + 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) +{ + if (!server || !server->services || !service || !proto) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(server, 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(); + 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) +{ + if (!server || !server->services || !service || !proto) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item(server, 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; + } + //not first item + mdns_srv_item_t * a = server->services; + while(a->next && a->next != s) { + a = a->next; + } + //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; +} + +esp_err_t mdns_service_remove_all(mdns_server_t * server) +{ + if (!server) { + return ESP_ERR_INVALID_ARG; + } + if (!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_MUTEX_UNLOCK(); + return ESP_OK; +} + +/* + * MDNS QUERY + * */ + +uint32_t mdns_query(mdns_server_t * server, const char * service, const char * proto, uint32_t timeout) +{ + 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; + snprintf(server->search.service, MDNS_NAME_MAX_LEN, "%s", service); + snprintf(server->search.proto, MDNS_NAME_MAX_LEN, "%s", proto); + } else { + snprintf(server->search.host, MDNS_NAME_MAX_LEN, "%s", service); + server->search.service[0] = 0; + server->search.proto[0] = 0; + qtype = MDNS_TYPE_A; + } + + 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); + + 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; + } + + 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); + } + server->search.running = false; + MDNS_SEARCH_UNLOCK(); + return mdns_result_get_count(server); + } + return 0; +} + +size_t mdns_query_end(mdns_server_t * server) +{ + if (!server || !server->search.running) { + return 0; + } + server->search.running = false; + MDNS_SEARCH_UNLOCK(); + return mdns_result_get_count(server); +} + +esp_err_t mdns_result_free(mdns_server_t * server) +{ + if (!server || server->search.running || !server->search.results) { + 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); + } + server->search.results = NULL; + return ESP_OK; +} + +size_t mdns_result_get_count(mdns_server_t * server) +{ + if (!server || !server->search.results) { + return 0; + } + size_t len = 0; + const mdns_result_t * r = server->search.results; + while(r) { + len++; + r = r->next; + } + return len; +} + +const mdns_result_t * mdns_result_get(mdns_server_t * server, size_t num) +{ + if (!server || !server->search.results) { + return NULL; + } + size_t len = 0; + const mdns_result_t * r = server->search.results; + while(r) { + if (len++ == num) { + return r; + } + r = r->next; + } + return NULL; +} diff --git a/components/tcpip_adapter/include/tcpip_adapter.h b/components/tcpip_adapter/include/tcpip_adapter.h index 07bdc12f91..4f3b49ed27 100644 --- a/components/tcpip_adapter/include/tcpip_adapter.h +++ b/components/tcpip_adapter/include/tcpip_adapter.h @@ -58,6 +58,17 @@ extern "C" { #define IPSTR "%d.%d.%d.%d" +#define IPV62STR(ipaddr) IP6_ADDR_BLOCK1(&(ipaddr)), \ + IP6_ADDR_BLOCK2(&(ipaddr)), \ + IP6_ADDR_BLOCK3(&(ipaddr)), \ + IP6_ADDR_BLOCK4(&(ipaddr)), \ + IP6_ADDR_BLOCK5(&(ipaddr)), \ + IP6_ADDR_BLOCK6(&(ipaddr)), \ + IP6_ADDR_BLOCK7(&(ipaddr)), \ + IP6_ADDR_BLOCK8(&(ipaddr)) + +#define IPV6STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" + typedef struct { ip4_addr_t ip; ip4_addr_t netmask; diff --git a/docs/Doxyfile b/docs/Doxyfile index a3fc5faa12..aa6c87476e 100755 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -36,7 +36,8 @@ INPUT = ../components/esp32/include/esp_wifi.h \ ../components/sdmmc/include/sdmmc_cmd.h \ ../components/fatfs/src/esp_vfs_fat.h \ ../components/fatfs/src/diskio.h \ - ../components/esp32/include/esp_core_dump.h + ../components/esp32/include/esp_core_dump.h \ + ../components/mdns/include/mdns.h ## Get warnings for functions that have no documentation for their parameters or return value ## diff --git a/docs/api/mdns.rst b/docs/api/mdns.rst new file mode 100644 index 0000000000..95789444f5 --- /dev/null +++ b/docs/api/mdns.rst @@ -0,0 +1,215 @@ +mDNS Service +============ + +Overview +-------- + +mDNS is a multicast UDP service that is used to provide local network service and host discovery. + +mDNS is installed by default on most operating systems or is available as separate package. On ``Mac OS`` it is installed by default and is called ``Bonjour``. Apple releases an installer for ``Windows`` that can be found `on Apple's support page `_. On ``Linux``, mDNS is provided by `avahi `_ and is usually installed by default. + +mDNS Properties +^^^^^^^^^^^^^^^ + + * ``hostname``: the hostname that the device will respond to. If not set, the ``hostname`` will be read from the interface. Example: ``my-esp32`` will resolve to ``my-esp32.local`` + * ``default_instance``: friendly name for your device, like ``Jhon's ESP32 Thing``. If not set, ``hostname`` will be used. + +Example method to start mDNS for the STA interface and set ``hostname`` and ``default_instance``: + + :: + + 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); + if (err) { + printf("MDNS Init failed: %d\n", err); + return; + } + + //set hostname + mdns_set_hostname(mdns, "my-esp32"); + //set default instance + mdns_set_instance(mdns, "Jhon's ESP32 Thing"); + } + +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 `_. + * ``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 + +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); + + //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"); + + const char * arduTxtData[4] = { + "board=esp32", + "tcp_check=no", + "ssh_upload=no", + "auth_upload=no" + }; + //set txt data for service (will free and replace current data) + mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData); + + //change service port + mdns_service_port_set(mdns, "_myservice", "_udp", 4321); + } + +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. + +Example method to resolve host IPs: + + :: + + void resolve_mdns_host(const char * hostname) + { + 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; + } + //free the results from memory + mdns_result_free(mdns); + } else { + //host was not found + printf(" Host Not Found\n"); + } + } + +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; + } + //free the results from memory + mdns_result_free(mdns); + } else { + //service was not found + printf(" Service Not Found\n"); + } + } + +Example of using the methods above: + + :: + + void my_app_some_method(){ + //search for esp32-mdns.local + resolve_mdns_host("esp32-mdns"); + + //search for HTTP servers + find_mdns_service("_http", "_tcp"); + //or file servers + find_mdns_service("_smb", "_tcp"); //windows sharing + find_mdns_service("_afpovertcp", "_tcp"); //apple sharing + find_mdns_service("_nfs", "_tcp"); //NFS server + find_mdns_service("_ftp", "_tcp"); //FTP server + //or networked printer + find_mdns_service("_printer", "_tcp"); + find_mdns_service("_ipp", "_tcp"); + } + +Application Example +------------------- + +mDNS server/scanner example: `examples/30_mdns_example `_. + +API Reference +------------- + +Header Files +^^^^^^^^^^^^ + + * `components/mdns/include/mdns.h `_ + +Macros +^^^^^^ + + +Type Definitions +^^^^^^^^^^^^^^^^ + +.. doxygentypedef:: mdns_server_t +.. doxygentypedef:: mdns_result_t + +Enumerations +^^^^^^^^^^^^ + + +Structures +^^^^^^^^^^ + +.. doxygenstruct:: mdns_result_s + :members: + + +Functions +^^^^^^^^^ + +.. doxygenfunction:: mdns_init +.. doxygenfunction:: mdns_free +.. doxygenfunction:: mdns_set_hostname +.. doxygenfunction:: mdns_set_instance +.. doxygenfunction:: mdns_service_add +.. doxygenfunction:: mdns_service_remove +.. doxygenfunction:: mdns_service_instance_set +.. doxygenfunction:: mdns_service_txt_set +.. doxygenfunction:: mdns_service_port_set +.. doxygenfunction:: mdns_service_remove_all +.. doxygenfunction:: mdns_query +.. doxygenfunction:: mdns_query_end +.. doxygenfunction:: mdns_result_get_count +.. doxygenfunction:: mdns_result_get +.. doxygenfunction:: mdns_result_free + diff --git a/docs/index.rst b/docs/index.rst index b994648848..bcc3c923a2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -124,6 +124,7 @@ Contents: Deep Sleep deep-sleep-stub + mDNS Template .. toctree:: diff --git a/examples/30_mdns_example/Makefile b/examples/30_mdns_example/Makefile new file mode 100644 index 0000000000..0353c51c0e --- /dev/null +++ b/examples/30_mdns_example/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 := mdns-test + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/30_mdns_example/README.md b/examples/30_mdns_example/README.md new file mode 100644 index 0000000000..1c298604b8 --- /dev/null +++ b/examples/30_mdns_example/README.md @@ -0,0 +1,5 @@ +# 30_mdns example + +Shows how to use mDNS to advertise lookup services and hosts + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/30_mdns_example/main/Kconfig.projbuild b/examples/30_mdns_example/main/Kconfig.projbuild new file mode 100644 index 0000000000..3122e0309c --- /dev/null +++ b/examples/30_mdns_example/main/Kconfig.projbuild @@ -0,0 +1,29 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "myssid" + help + WiFi password (WPA or WPA2) for the example to use. + + Can be left blank if the network has no security set. + +config MDNS_HOSTNAME + string "mDNS Hostname" + default "esp32-mdns" + help + mDNS Hostname for example to use + +config MDNS_INSTANCE + string "mDNS Instance Name" + default "ESP32 with mDNS" + help + mDNS Instance Name for example to use + +endmenu \ No newline at end of file diff --git a/examples/30_mdns_example/main/component.mk b/examples/30_mdns_example/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/30_mdns_example/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/30_mdns_example/main/mdns_example_main.c b/examples/30_mdns_example/main/mdns_example_main.c new file mode 100644 index 0000000000..d19fd1ae68 --- /dev/null +++ b/examples/30_mdns_example/main/mdns_example_main.c @@ -0,0 +1,184 @@ +/* MDNS-SD Query and advertise Example + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "mdns.h" + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#define EXAMPLE_MDNS_HOSTNAME CONFIG_MDNS_HOSTNAME +#define EXAMPLE_MDNS_INSTANCE CONFIG_MDNS_INSTANCE + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +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; + +static const char *TAG = "mdns-test"; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_CONNECTED: + /* enable ipv6 */ + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, 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); + break; + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +static void query_mdns_service(mdns_server_t * mdns, const char * service, const char * proto) +{ + if(!mdns) { + 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); + } + } +} + +static void mdns_task(void *pvParameters) +{ + mdns_server_t * mdns = NULL; + 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!"); + } +} + +void app_main() +{ + nvs_flash_init(); + initialise_wifi(); + xTaskCreate(&mdns_task, "mdns_task", 2048, NULL, 5, NULL); +} From 08f11e4c537191796166e07a882f9d9c577a4c84 Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Sun, 15 Jan 2017 16:49:18 +0800 Subject: [PATCH 156/167] Fix panic register dump format --- components/esp32/panic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp32/panic.c b/components/esp32/panic.c index 3db09370ac..22a515a352 100644 --- a/components/esp32/panic.c +++ b/components/esp32/panic.c @@ -352,8 +352,8 @@ static void commonErrorHandler(XtExcFrame *frame) panicPutHex(regs[x + y + 1]); panicPutStr(" "); } - panicPutStr("\r\n"); } + panicPutStr("\r\n"); } } From a8b75ed974c0c2b4d8bd5b61bb3dcb4ba15e5642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lourens=20Naud=C3=A9?= Date: Sat, 31 Dec 2016 17:54:40 +0000 Subject: [PATCH 157/167] log: fix esp_log_level_set function name in docs Fix documentation and comments to reflect the new esp_log_level_set API (and not deprecated esp_log_set_level) --- components/log/README.rst | 12 ++++++------ components/log/log.c | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/log/README.rst b/components/log/README.rst index d378179c8f..002dadf812 100644 --- a/components/log/README.rst +++ b/components/log/README.rst @@ -4,11 +4,11 @@ Logging library Overview -------- -Log library has two ways of managing log verbosity: compile time, set via menuconfig; and runtime, using ``esp_log_set_level`` function. +Log library has two ways of managing log verbosity: compile time, set via menuconfig; and runtime, using ``esp_log_level_set`` function. At compile time, filtering is done using ``CONFIG_LOG_DEFAULT_LEVEL`` macro, set via menuconfig. All logging statments for levels higher than ``CONFIG_LOG_DEFAULT_LEVEL`` will be removed by the preprocessor. -At run time, all logs below ``CONFIG_LOG_DEFAULT_LEVEL`` are enabled by default. ``esp_log_set_level`` function may be used to set logging level per module. Modules are identified by their tags, which are human-readable ASCII zero-terminated strings. +At run time, all logs below ``CONFIG_LOG_DEFAULT_LEVEL`` are enabled by default. ``esp_log_level_set`` function may be used to set logging level per module. Modules are identified by their tags, which are human-readable ASCII zero-terminated strings. How to use this library ----------------------- @@ -51,11 +51,11 @@ At component scope, define it in component makefile: CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG -To configure logging output per module at runtime, add calls to ``esp_log_set_level`` function: +To configure logging output per module at runtime, add calls to ``esp_log_level_set`` function: .. code-block:: c - esp_log_set_level("*", ESP_LOG_ERROR); // set all components to ERROR level - esp_log_set_level("wifi", ESP_LOG_WARN); // enable WARN logs from WiFi stack - esp_log_set_level("dhcpc", ESP_LOG_INFO); // enable INFO logs from DHCP client + esp_log_level_set("*", ESP_LOG_ERROR); // set all components to ERROR level + esp_log_level_set("wifi", ESP_LOG_WARN); // enable WARN logs from WiFi stack + esp_log_level_set("dhcpc", ESP_LOG_INFO); // enable INFO logs from DHCP client diff --git a/components/log/log.c b/components/log/log.c index 9670b82dfd..3826e173ba 100644 --- a/components/log/log.c +++ b/components/log/log.c @@ -15,7 +15,7 @@ /* * Log library — implementation notes. * - * Log library stores all tags provided to esp_log_set_level as a linked + * Log library stores all tags provided to esp_log_level_set as a linked * list. See uncached_tag_entry_t structure. * * To avoid looking up log level for given tag each time message is From d8fb30c930ba10ed7054d5f8669a230d95053245 Mon Sep 17 00:00:00 2001 From: devsaurus Date: Sat, 7 Jan 2017 13:14:40 +0100 Subject: [PATCH 158/167] i2c: fix i2c_get_period name rename i2s_get_period to i2c_get_period --- components/driver/i2c.c | 2 +- components/driver/include/driver/i2c.h | 2 +- docs/api/i2c.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/driver/i2c.c b/components/driver/i2c.c index 311f01516c..39eb877349 100644 --- a/components/driver/i2c.c +++ b/components/driver/i2c.c @@ -494,7 +494,7 @@ esp_err_t i2c_set_period(i2c_port_t i2c_num, int high_period, int low_period) return ESP_OK; } -esp_err_t i2s_get_period(i2c_port_t i2c_num, int* high_period, int* low_period) +esp_err_t i2c_get_period(i2c_port_t i2c_num, int* high_period, int* low_period) { I2C_CHECK(i2c_num < I2C_NUM_MAX, I2C_NUM_ERROR_STR, ESP_ERR_INVALID_ARG); I2C_ENTER_CRITICAL(&i2c_spinlock[i2c_num]); diff --git a/components/driver/include/driver/i2c.h b/components/driver/include/driver/i2c.h index 69b80b1e67..960ec61d27 100644 --- a/components/driver/include/driver/i2c.h +++ b/components/driver/include/driver/i2c.h @@ -401,7 +401,7 @@ esp_err_t i2c_set_period(i2c_port_t i2c_num, int high_period, int low_period); * - ESP_OK Success * - ESP_ERR_INVALID_ARG Parameter error */ -esp_err_t i2s_get_period(i2c_port_t i2c_num, int* high_period, int* low_period); +esp_err_t i2c_get_period(i2c_port_t i2c_num, int* high_period, int* low_period); /** * @brief set I2C master start signal timing diff --git a/docs/api/i2c.rst b/docs/api/i2c.rst index 1186e0583b..2f89681f1d 100644 --- a/docs/api/i2c.rst +++ b/docs/api/i2c.rst @@ -68,7 +68,7 @@ Functions .. doxygenfunction:: i2c_slave_write_buffer .. doxygenfunction:: i2c_slave_read .. doxygenfunction:: i2c_set_period -.. doxygenfunction:: i2s_get_period +.. doxygenfunction:: i2c_get_period .. doxygenfunction:: i2c_set_start_timing .. doxygenfunction:: i2c_get_start_timing .. doxygenfunction:: i2c_set_stop_timing From e5feeb195f1588ffc26a5235f21cfaaa14863385 Mon Sep 17 00:00:00 2001 From: "rudi ;-)" Date: Wed, 11 Jan 2017 21:47:38 +0100 Subject: [PATCH 159/167] sdmmc: change idx num of example --- docs/api/sdmmc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/sdmmc.rst b/docs/api/sdmmc.rst index 126be86576..e751852f95 100644 --- a/docs/api/sdmmc.rst +++ b/docs/api/sdmmc.rst @@ -15,7 +15,7 @@ Protocol layer works with the host via ``sdmmc_host_t`` structure. This structur Application Example ------------------- -An example which combines SDMMC driver with FATFS library is provided in ``examples/24_sd_card`` directory. This example initializes the card, writes and reads data from it using POSIX and C library APIs. See README.md file in the example directory for more information. +An example which combines SDMMC driver with FATFS library is provided in ``examples/27_sd_card`` directory. This example initializes the card, writes and reads data from it using POSIX and C library APIs. See README.md file in the example directory for more information. Protocol layer APIs From a2ace698edc44271d8cdcb26e9aee8d957ecda4d Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 16 Jan 2017 01:19:14 +0800 Subject: [PATCH 160/167] sdmmc: fix explanation of flash voltage regulator configuration efuses - low level on GPIO12 corresponds to 3.3V output, not 1.8V - add espefuse.py commands to burn efuses - add XPD_SDIO_REG to the list of efuses which must be set Reported on the forum: http://esp32.com/viewtopic.php?f=2&t=849&start=10#p4170 --- examples/27_sd_card/README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/examples/27_sd_card/README.md b/examples/27_sd_card/README.md index 4053ad2609..bfc6a39fe9 100644 --- a/examples/27_sd_card/README.md +++ b/examples/27_sd_card/README.md @@ -40,9 +40,20 @@ GPIO12 is used as a bootstrapping pin to select output voltage of an internal re - For boards which don't use the internal regulator, GPIO12 can be pulled high. - On boards which use the internal regulator and a 3.3V flash chip, GPIO12 should be pulled up high, which is compatible with SD card operation. -- For boards which use 1.8V flash chip, GPIO12 needs to be low at reset. +- For boards which use 3.3V flash chip, GPIO12 needs to be low at reset. * In this case, internal pullup can be enabled using a `gpio_pullup_en(GPIO_NUM_12);` call. Most SD cards work fine when an internal pullup on GPIO12 line is enabled. Note that if ESP32 experiences a power-on reset while the SD card is sending data, high level on GPIO12 can be latched into the bootstrapping register, and ESP32 will enter a boot loop until external reset with correct GPIO12 level is applied. - * Another option is to program flash voltage selection efuses: set `SDIO_TIEH=0` and `SDIO_FORCE=1`. This will permanently select 1.8v output voltage for the internal regulator, and GPIO12 will not be used as a bootstrapping pin anymore. Then it is safe to connect a pullup resistor to GPIO12. This option is suggested for production use. + * Another option is to program flash voltage selection efuses: set `XPD_SDIO_TIEH=1`, `XPD_SDIO_FORCE=1`, and `XPD_SDIO_REG = 1`. This will permanently select 3.3V output voltage for the internal regulator, and GPIO12 will not be used as a bootstrapping pin anymore. Then it is safe to connect a pullup resistor to GPIO12. This option is suggested for production use. + +The following commands can be used to program flash voltage selection efuses **to 3.3V**: + +```sh + # Override flash regulator configuration using efuses + components/esptool_py/esptool/espefuse.py burn_efuse XPD_SDIO_FORCE + # Select 3.3V output voltage + components/esptool_py/esptool/espefuse.py burn_efuse XPD_SDIO_TIEH + # Enable internal voltage regulator + components/esptool_py/esptool/espefuse.py burn_efuse XPD_SDIO_REG +``` ## 4-line and 1-line modes From 3efbbab2cfd4a6766c1d9ffe182d447ec58f247a Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Sun, 1 Jan 2017 23:51:48 -0600 Subject: [PATCH 161/167] wifi: fix typos, rename ESP_ERR_WIFI_NOT_START to ESP_ERR_WIFI_NOT_STARTED --- components/esp32/include/esp_wifi.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/components/esp32/include/esp_wifi.h b/components/esp32/include/esp_wifi.h index c835c071c7..197a04fd3b 100755 --- a/components/esp32/include/esp_wifi.h +++ b/components/esp32/include/esp_wifi.h @@ -76,8 +76,8 @@ extern "C" { #define ESP_ERR_WIFI_ARG ESP_ERR_INVALID_ARG /*!< Invalid argument */ #define ESP_ERR_WIFI_NOT_SUPPORT ESP_ERR_NOT_SUPPORTED /*!< Indicates that API is not supported yet */ -#define ESP_ERR_WIFI_NOT_INIT (ESP_ERR_WIFI_BASE + 1) /*!< WiFi driver is not installed by esp_wifi_init */ -#define ESP_ERR_WIFI_NOT_START (ESP_ERR_WIFI_BASE + 2) /*!< WiFi driver is not started by esp_wifi_start */ +#define ESP_ERR_WIFI_NOT_INIT (ESP_ERR_WIFI_BASE + 1) /*!< WiFi driver was not installed by esp_wifi_init */ +#define ESP_ERR_WIFI_NOT_STARTED (ESP_ERR_WIFI_BASE + 2) /*!< WiFi driver was not started by esp_wifi_start */ #define ESP_ERR_WIFI_IF (ESP_ERR_WIFI_BASE + 3) /*!< WiFi interface error */ #define ESP_ERR_WIFI_MODE (ESP_ERR_WIFI_BASE + 4) /*!< WiFi mode error */ #define ESP_ERR_WIFI_STATE (ESP_ERR_WIFI_BASE + 5) /*!< WiFi internal state error */ @@ -85,7 +85,7 @@ extern "C" { #define ESP_ERR_WIFI_NVS (ESP_ERR_WIFI_BASE + 7) /*!< WiFi internal NVS module error */ #define ESP_ERR_WIFI_MAC (ESP_ERR_WIFI_BASE + 8) /*!< MAC address is invalid */ #define ESP_ERR_WIFI_SSID (ESP_ERR_WIFI_BASE + 9) /*!< SSID is invalid */ -#define ESP_ERR_WIFI_PASSWORD (ESP_ERR_WIFI_BASE + 10) /*!< Passord is invalid */ +#define ESP_ERR_WIFI_PASSWORD (ESP_ERR_WIFI_BASE + 10) /*!< Password is invalid */ #define ESP_ERR_WIFI_TIMEOUT (ESP_ERR_WIFI_BASE + 11) /*!< Timeout error */ #define ESP_ERR_WIFI_WAKE_FAIL (ESP_ERR_WIFI_BASE + 12) /*!< WiFi is in sleep state(RF closed) and wakeup fail */ @@ -222,8 +222,8 @@ esp_err_t esp_wifi_connect(void); * * @return * - ESP_OK: succeed - * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init - * - ESP_ERR_WIFI_NOT_START: WiFi is not started by esp_wifi_start + * - ESP_ERR_WIFI_NOT_INIT: WiFi was not initialized by eps_wifi_init + * - ESP_ERR_WIFI_NOT_STARTED: WiFi was not started by esp_wifi_start * - ESP_ERR_WIFI_FAIL: other WiFi internal errors */ esp_err_t esp_wifi_disconnect(void); @@ -246,7 +246,7 @@ esp_err_t esp_wifi_clear_fast_connect(void); * @return * - ESP_OK: succeed * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init - * - ESP_ERR_WIFI_NOT_START: WiFi is not started by esp_wifi_start + * - ESP_ERR_WIFI_NOT_STARTED: WiFi was not started by esp_wifi_start * - ESP_ERR_WIFI_ARG: invalid argument * - ESP_ERR_WIFI_MODE: WiFi mode is wrong */ @@ -266,7 +266,7 @@ esp_err_t esp_wifi_deauth_sta(uint16_t aid); * @return * - ESP_OK: succeed * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init - * - ESP_ERR_WIFI_NOT_START: WiFi is not started by esp_wifi_start + * - ESP_ERR_WIFI_NOT_STARTED: WiFi was not started by esp_wifi_start * - ESP_ERR_WIFI_TIMEOUT: blocking scan is timeout * - others: refer to error code in esp_err.h */ @@ -278,7 +278,7 @@ esp_err_t esp_wifi_scan_start(wifi_scan_config_t *config, bool block); * @return * - ESP_OK: succeed * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init - * - ESP_ERR_WIFI_NOT_START: WiFi is not started by esp_wifi_start + * - ESP_ERR_WIFI_NOT_STARTED: WiFi is not started by esp_wifi_start */ esp_err_t esp_wifi_scan_stop(void); @@ -292,7 +292,7 @@ esp_err_t esp_wifi_scan_stop(void); * @return * - ESP_OK: succeed * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init - * - ESP_ERR_WIFI_NOT_START: WiFi is not started by esp_wifi_start + * - ESP_ERR_WIFI_NOT_STARTED: WiFi is not started by esp_wifi_start * - ESP_ERR_WIFI_ARG: invalid argument */ esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number); @@ -307,7 +307,7 @@ esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number); * @return * - ESP_OK: succeed * - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by eps_wifi_init - * - ESP_ERR_WIFI_NOT_START: WiFi is not started by esp_wifi_start + * - ESP_ERR_WIFI_NOT_STARTED: WiFi is not started by esp_wifi_start * - ESP_ERR_WIFI_ARG: invalid argument * - ESP_ERR_WIFI_NO_MEM: out of memory */ From 4b71d9cb351666ae6405f85b48da1c5b0197b934 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 16 Jan 2017 01:48:47 +0800 Subject: [PATCH 162/167] docs: update index, improve structure of the readme --- README.md | 44 ++++++++++++++++++++++++-------------------- docs/index.rst | 10 +++------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 5290aa7981..64fe3ed365 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,36 @@ -# Using Espressif IoT Development Framework with the ESP32 +# Espressif IoT Development Framework [![alt text](https://readthedocs.org/projects/docs/badge/?version=latest "Documentation Status")](http://esp-idf.readthedocs.io/en/latest/?badge=latest) -# Setting Up ESP-IDF +ESP-IDF is the official development framework for the `ESP32 `_ chip. -In the [docs](docs) directory you will find per-platform setup guides: +# Developing With the ESP-IDF -* [Windows Setup Guide](docs/windows-setup.rst) -* [Mac OS Setup Guide](docs/macos-setup.rst) -* [Linux Setup Guide](docs/linux-setup.rst) +## Setting Up ESP-IDF -# Finding A Project +See setup guides for detailed instructions to set up the ESP-IDF: -As well as the [esp-idf-template](https://github.com/espressif/esp-idf-template) project mentioned in the setup guide, esp-idf comes with some example projects in the [examples](examples) directory. +* [Windows Setup Guide](http://esp-idf.readthedocs.io/en/latest/windows-setup.html) +* [Mac OS Setup Guide](http://esp-idf.readthedocs.io/en/latest/macos-setup.html) +* [Linux Setup Guide](http://esp-idf.readthedocs.io/en/latest/linux-setup.html) -Once you've found the project you want to work with, change to its directory and you can configure and build it: +## Finding a Project -# Configuring your project +As well as the [esp-idf-template](https://github.com/espressif/esp-idf-template) project mentioned in the setup guide, ESP-IDF comes with some example projects in the [examples](examples) directory. + +Once you've found the project you want to work with, change to its directory and you can configure and build it. + +## Configuring the Project `make menuconfig` -# Compiling your project +## Compiling the Project `make all` ... will compile app, bootloader and generate a partition table based on the config. -# Flashing your project +## Flashing the Project When `make all` finishes, it will print a command line to use esptool.py to flash the chip. However you can also do this from make by running: @@ -36,7 +40,7 @@ This will flash the entire project (app, bootloader and partition table) to a ne You don't need to run `make all` before running `make flash`, `make flash` will automatically rebuild anything which needs it. -# Viewing Serial Output +## Viewing Serial Output The `make monitor` target will use the already-installed [miniterm](http://pyserial.readthedocs.io/en/latest/tools.html#module-serial.tools.miniterm) (a part of pyserial) to display serial output from the ESP32 on the terminal console. @@ -46,7 +50,7 @@ To flash and monitor output in one pass, you can run: `make flash monitor` -# Compiling & Flashing Just the App +## Compiling & Flashing Just the App After the initial flash, you may just want to build and flash just your app, not the bootloader and partition table: @@ -57,9 +61,9 @@ After the initial flash, you may just want to build and flash just your app, not (There's no downside to reflashing the bootloader and partition table each time, if they haven't changed.) -# Parallel Builds +## Parallel Builds -esp-idf supports compiling multiple files in parallel, so all of the above commands can be run as `make -jN` where `N` is the number of parallel make processes to run (generally N should be equal to or one more than the number of CPU cores in your system.) +ESP-IDF supports compiling multiple files in parallel, so all of the above commands can be run as `make -jN` where `N` is the number of parallel make processes to run (generally N should be equal to or one more than the number of CPU cores in your system.) Multiple make functions can be combined into one. For example: to build the app & bootloader using 5 jobs in parallel, then flash everything, and then display serial output from the ESP32 run: @@ -67,7 +71,7 @@ Multiple make functions can be combined into one. For example: to build the app make -j5 flash monitor ``` -# The Partition Table +## The Partition Table Once you've compiled your project, the "build" directory will contain a binary file with a name like "my_app.bin". This is an ESP32 image binary that can be loaded by the bootloader. @@ -84,7 +88,7 @@ In both cases the factory app is flashed at offset 0x10000. If you `make partiti For more details about partition tables and how to create custom variations, view the `docs/partition-tables.rst` file. -# Erasing Flash +## Erasing Flash The `make flash` target does not erase the entire flash contents. However it is sometimes useful to set the device back to a totally erased state, particularly when making partition table changes or OTA app updates. To erase the entire flash, run `make erase_flash`. @@ -92,11 +96,11 @@ This can be combined with other targets, ie `make erase_flash flash` will erase # Resources -* The [docs directory of the esp-idf repository](docs) contains source of [esp-idf](http://esp-idf.readthedocs.io/) documentation. +* Documentation for the latest version: http://esp-idf.readthedocs.io/. This documentation is built from the [docs directory](docs) of this repository. * The [esp32.com forum](http://esp32.com/) is a place to ask questions and find community resources. * [Check the Issues section on github](https://github.com/espressif/esp-idf/issues) if you find a bug or have a feature request. Please check existing Issues before opening a new one. -* If you're interested in contributing to esp-idf, please check the [Contributions Guide](http://esp-idf.readthedocs.io/en/latest/contributing.html>). +* If you're interested in contributing to ESP-IDF, please check the [Contributions Guide](http://esp-idf.readthedocs.io/en/latest/contributing.html>). diff --git a/docs/index.rst b/docs/index.rst index bcc3c923a2..eafe102447 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,11 +1,7 @@ -ESP32 Programming Guide -======================= +ESP-IDF Programming Guide +========================= -.. caution:: - - Until ESP-IDF release 1.0, this documentation is a draft. It is incomplete and may have mistakes. Please mind your step! - -Documentation adressed to developers of applications for `ESP32 `_ by `Espressif `_ using `esp-idf `_. +This is the documentation for Espressif IoT Developement Framework (`esp-idf `_). ESP-IDF is the official development framework for the `ESP32 `_ chip. Contents: From 4c0f90bfaeeae4696bfac0cadd1380a90b873345 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 16 Jan 2017 02:07:46 +0800 Subject: [PATCH 163/167] docs: fix some of the Sphinx warnings --- docs/api/ledc.rst | 9 --------- docs/api/sigmadelta.rst | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/docs/api/ledc.rst b/docs/api/ledc.rst index 40855ddb47..12d7cd16d0 100644 --- a/docs/api/ledc.rst +++ b/docs/api/ledc.rst @@ -22,15 +22,6 @@ Header Files * `driver/include/driver/ledc.h `_ -Data Structures -^^^^^^^^^^^^^^^ - -.. doxygenstruct:: ledc_channel_config_t - :members: - -.. doxygenstruct:: ledc_timer_config_t - :members: - Macros ^^^^^^ diff --git a/docs/api/sigmadelta.rst b/docs/api/sigmadelta.rst index acfdaaa27d..b03f049421 100644 --- a/docs/api/sigmadelta.rst +++ b/docs/api/sigmadelta.rst @@ -1,5 +1,5 @@ Sigma-delta Modulation -======== +====================== Overview -------- From 786573fb2d9b4d0beac8b12d792b65ba656ee648 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 16 Jan 2017 02:18:42 +0800 Subject: [PATCH 164/167] docs: fix macOS setup instructions Most of the packages mentioned are only needed for building the toolchain using crosstool-NG, not for the normal ESP-IDF environment. Mention that pyserial needs to be installed (fixes https://github.com/espressif/esp-idf/issues/229). --- docs/macos-setup.rst | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/macos-setup.rst b/docs/macos-setup.rst index ac772c5298..eeed9e4e26 100644 --- a/docs/macos-setup.rst +++ b/docs/macos-setup.rst @@ -4,19 +4,13 @@ Set up of Toolchain for Mac OS Step 0: Prerequisites ===================== -Getting MacPorts or homebrew ----------------------------- +- install pip:: -Whether you compile the toolchain from source or download binary toolchain, there are some dependencies which need to be installed on macOS first. These dependencies are installed with one of the package managers: homebrew or MacPorts. If you have these already, you can skip the following instructions. + sudo easy_install pip -- Install XCode from Mac App Store -- Open Terminal.app and run ``xcode-select --install`` -- Run ``sudo xcodebuild -license`` and agree to XCode license -- Install MacPorts_ or homebrew_ - -.. _homebrew: http://brew.sh/ -.. _MacPorts: https://www.macports.org/install.php +- install pyserial + sudo pip install pyserial Step 1: Download binary toolchain for the ESP32 ================================================== @@ -64,13 +58,18 @@ In any case, here are the steps to compile the toolchain yourself. - Install dependencies: + - Install either MacPorts_ or homebrew_ package manager. MacPorts needs a full XCode installation, while homebrew only needs XCode command line tools. + + .. _homebrew: http://brew.sh/ + .. _MacPorts: https://www.macports.org/install.php + - with MacPorts:: - sudo port install gsed gawk binutils gperf grep gettext ncurses + sudo port install gsed gawk binutils gperf grep gettext wget libtool autoconf automake - - with homebrew + - with homebrew:: - brew install gnu-sed gawk binutils gperf grep gettext ncurses + brew install gnu-sed gawk binutils gperftools gettext wget help2man libtool autoconf automake Create a case-sensitive filesystem image:: From f20135af5400f78ea09a69f178d72e66020cceac Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 16 Jan 2017 02:33:16 +0800 Subject: [PATCH 165/167] esp32: compile PHY-related code only when WiFi is enabled Fixes https://github.com/espressif/esp-idf/issues/230, https://github.com/espressif/esp-idf/issues/237 --- components/esp32/cpu_start.c | 7 ++++++- components/esp32/phy_init.c | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 5ae68fc643..b5e896e573 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -71,9 +71,11 @@ static bool app_cpu_started = false; #endif //!CONFIG_FREERTOS_UNICORE static void do_global_ctors(void); -static void do_phy_init(); static void main_task(void* args); extern void app_main(void); +#if CONFIG_ESP32_PHY_AUTO_INIT +static void do_phy_init(); +#endif extern int _bss_start; extern int _bss_end; @@ -264,6 +266,7 @@ static void main_task(void* args) vTaskDelete(NULL); } +#if CONFIG_ESP32_PHY_AUTO_INIT static void do_phy_init() { esp_phy_calibration_mode_t calibration_mode = PHY_RF_CAL_PARTIAL; @@ -297,3 +300,5 @@ static void do_phy_init() esp_phy_release_init_data(init_data); free(cal_data); // PHY maintains a copy of calibration data, so we can free this } +#endif //CONFIG_ESP32_PHY_AUTO_INIT + diff --git a/components/esp32/phy_init.c b/components/esp32/phy_init.c index bfc5a15f5e..07a455d501 100644 --- a/components/esp32/phy_init.c +++ b/components/esp32/phy_init.c @@ -23,10 +23,12 @@ #include "esp_err.h" #include "esp_phy_init.h" #include "esp_system.h" -#include "phy.h" #include "esp_log.h" #include "nvs.h" #include "sdkconfig.h" + +#ifdef CONFIG_WIFI_ENABLED +#include "phy.h" #include "phy_init_data.h" static const char* TAG = "phy_init"; @@ -219,6 +221,4 @@ static esp_err_t store_cal_data_to_nvs_handle(nvs_handle handle, return err; } -void register_chipv7_phy_stub() -{ -} +#endif // CONFIG_WIFI_ENABLED From ea19838d3a9e4dc100a90209fa4e4084577521d0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Mon, 16 Jan 2017 11:52:15 +1100 Subject: [PATCH 166/167] Build: Handle WiFi & BT enabled/disabled combos gracefully as possible If using WIFI_INIT_CONFIG_DEFAULT, error message will point out lack of WiFi. Otherwise linker errors on WiFi symbols should help give a clue as to what is broken. Piggy-backs on changes in !420, ref github #230 #237 --- components/esp32/component.mk | 9 ++++++++- components/esp32/include/esp_wifi.h | 6 +++++- components/esp32/system_api.c | 3 +++ examples/02_blink/sdkconfig.defaults | 2 ++ examples/14_gatt_server/sdkconfig.defaults | 7 ++----- examples/15_gatt_client/sdkconfig.defaults | 7 ++----- 6 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 examples/02_blink/sdkconfig.defaults diff --git a/components/esp32/component.mk b/components/esp32/component.mk index 4ceb13e837..e1cd2c6524 100644 --- a/components/esp32/component.mk +++ b/components/esp32/component.mk @@ -3,8 +3,15 @@ # COMPONENT_SRCDIRS := . hwcrypto +LIBS := core rtc phy +ifdef CONFIG_BT_ENABLED +LIBS += coexist +endif +ifdef CONFIG_WIFI_ENABLED +LIBS += net80211 pp wpa smartconfig coexist wps wpa2 +endif -LIBS := core net80211 phy rtc pp wpa smartconfig coexist wps wpa2 +LIBS := $(sort $(LIBS)) # de-duplicate, we can handle different orders here LINKER_SCRIPTS += esp32.common.ld esp32.rom.ld esp32.peripherals.ld diff --git a/components/esp32/include/esp_wifi.h b/components/esp32/include/esp_wifi.h index 197a04fd3b..8d4fa17bb7 100755 --- a/components/esp32/include/esp_wifi.h +++ b/components/esp32/include/esp_wifi.h @@ -62,6 +62,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include "rom/queue.h" +#include "sdkconfig.h" #include "esp_err.h" #include "esp_wifi_types.h" #include "esp_event.h" @@ -97,11 +98,14 @@ typedef struct { uint32_t rx_buf_num; /**< WiFi RX buffer number */ } wifi_init_config_t; - +#ifdef CONFIG_WIFI_ENABLED #define WIFI_INIT_CONFIG_DEFAULT() { \ .event_handler = &esp_event_send, \ .rx_buf_num = CONFIG_ESP32_WIFI_RX_BUFFER_NUM, \ }; +#else +#define WIFI_INIT_CONFIG_DEFAULT #error Wifi is disabled in config, WIFI_INIT_CONFIG_DEFAULT will not work +#endif /** * @brief Init WiFi diff --git a/components/esp32/system_api.c b/components/esp32/system_api.c index 60fa1796cd..bfc0f07527 100644 --- a/components/esp32/system_api.c +++ b/components/esp32/system_api.c @@ -17,6 +17,7 @@ #include "esp_wifi.h" #include "esp_wifi_internal.h" #include "esp_log.h" +#include "sdkconfig.h" #include "rom/efuse.h" #include "rom/cache.h" #include "rom/uart.h" @@ -74,7 +75,9 @@ esp_err_t system_efuse_read_mac(uint8_t mac[6]) __attribute__((alias("esp_efuse_ void IRAM_ATTR esp_restart(void) { +#ifdef CONFIG_WIFI_ENABLED esp_wifi_stop(); +#endif // Disable scheduler on this core. vTaskSuspendAll(); diff --git a/examples/02_blink/sdkconfig.defaults b/examples/02_blink/sdkconfig.defaults new file mode 100644 index 0000000000..8c618fb9b4 --- /dev/null +++ b/examples/02_blink/sdkconfig.defaults @@ -0,0 +1,2 @@ +# Disable WiFi stack by default +CONFIG_WIFI_ENABLED=n diff --git a/examples/14_gatt_server/sdkconfig.defaults b/examples/14_gatt_server/sdkconfig.defaults index dcf4ad2c2d..9d51df5ee5 100644 --- a/examples/14_gatt_server/sdkconfig.defaults +++ b/examples/14_gatt_server/sdkconfig.defaults @@ -1,7 +1,4 @@ # Override some defaults so BT stack is enabled -# in this example - -# -# BT config -# +# and WiFi disabled by default in this example CONFIG_BT_ENABLED=y +CONFIG_WIFI_ENABLED=n diff --git a/examples/15_gatt_client/sdkconfig.defaults b/examples/15_gatt_client/sdkconfig.defaults index dcf4ad2c2d..9d51df5ee5 100644 --- a/examples/15_gatt_client/sdkconfig.defaults +++ b/examples/15_gatt_client/sdkconfig.defaults @@ -1,7 +1,4 @@ # Override some defaults so BT stack is enabled -# in this example - -# -# BT config -# +# and WiFi disabled by default in this example CONFIG_BT_ENABLED=y +CONFIG_WIFI_ENABLED=n From 0d00a1ba01fd5a774e4da65970a88c535585ebb2 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Jan 2017 22:50:42 +0800 Subject: [PATCH 167/167] vfs: implement reading from UART --- components/esp32/Kconfig | 5 ++ components/vfs/README.rst | 32 +++++-- components/vfs/test/component.mk | 1 + components/vfs/test/test_vfs_uart.c | 124 ++++++++++++++++++++++++++++ components/vfs/vfs_uart.c | 54 +++++++++++- 5 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 components/vfs/test/component.mk create mode 100644 components/vfs/test/test_vfs_uart.c diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 5a36b30a5d..0afe5ba0f8 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -134,6 +134,11 @@ config NEWLIB_STDOUT_ADDCR cursor one line down, not also move it to the beginning of the line. This is usually done by an added CR character. Enabling this will make the standard output code automatically add a CR character before a LF. + + With this option enabled, C standard library functions which read from UART + (like scanf) will convert "\r\n" character sequences back to "\n". + + This option doesn't affect behavior of the UART driver (drivers/uart.h). config NEWLIB_NANO_FORMAT bool "Enable 'nano' formatting options for printf/scanf family" diff --git a/components/vfs/README.rst b/components/vfs/README.rst index c58161c900..2ad7aa7ec2 100644 --- a/components/vfs/README.rst +++ b/components/vfs/README.rst @@ -23,11 +23,6 @@ To register an FS driver, application needs to define in instance of esp_vfs_t s .fstat = &myfs_fstat, .close = &myfs_close, .read = &myfs_read, - .lseek = NULL, - .stat = NULL, - .link = NULL, - .unlink = NULL, - .rename = NULL }; ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); @@ -125,4 +120,31 @@ When VFS component receives a call from newlib which has a file descriptor, this +-------------+ +Standard IO streams (stdin, stdout, stderr) +------------------------------------------- + +If "UART for console output" menuconfig option is not set to "None", then ``stdin``, ``stdout``, and ``stderr`` are configured to read from, and write to, a UART. It is possible to use UART0 or UART1 for standard IO. By default, UART0 is used, with 115200 baud rate, TX pin is GPIO1 and RX pin is GPIO3. These parameters can be changed in menuconfig. + +Writing to ``stdout`` or ``stderr`` will send characters to the UART transmit FIFO. Reading from ``stdin`` will retrieve characters from the UART receive FIFO. + +Note that while writing to ``stdout`` or ``stderr`` will block until all characters are put into the FIFO, reading from ``stdin`` is non-blocking. The function which reads from UART will get all the characters present in the FIFO (if any), and return. I.e. doing ``fscanf("%d\n", &var);`` may not have desired results. This is a temporary limitation which will be removed once ``fcntl`` is added to the VFS interface. + +Standard streams and FreeRTOS tasks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``FILE`` objects for ``stdin``, ``stdout``, and ``stderr`` are shared between all FreeRTOS tasks, but the pointers to these objects are are stored in per-task ``struct _reent``. The following code:: + + fprintf(stderr, "42\n"); + +actually is translated to to this (by the preprocessor): + + fprintf(__getreent()->_stderr, "42\n"); + +where the ``__getreent()`` function returns a per-task pointer to ``struct _reent`` (`source `_). This structure is allocated on the TCB of each task. When a task is initialized, ``_stdin``, ``_stdout`` and ``_stderr`` members of ``struct _reent`` are set to the values of ``_stdin``, ``_stdout`` and ``_stderr`` of ``_GLOBAL_REENT`` (i.e. the structure which is used before FreeRTOS is started). + +Such a design has the following consequences: + +- It is possible to set ``stdin``, ``stdout``, and ``stderr`` for any given task without affecting other tasks, e.g. by doing ``stdin = fopen("/dev/uart/1", "r")``. +- Closing default ``stdin``, ``stdout``, or ``stderr`` using ``fclose`` will close the ``FILE`` stream object — this will affect all other tasks. +- To change the default ``stdin``, ``stdout``, ``stderr`` streams for new tasks, modify ``_GLOBAL_REENT->_stdin`` (``_stdout``, ``_stderr``) before creating the task. diff --git a/components/vfs/test/component.mk b/components/vfs/test/component.mk new file mode 100644 index 0000000000..ce464a212a --- /dev/null +++ b/components/vfs/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/vfs/test/test_vfs_uart.c b/components/vfs/test/test_vfs_uart.c new file mode 100644 index 0000000000..892ca527c6 --- /dev/null +++ b/components/vfs/test/test_vfs_uart.c @@ -0,0 +1,124 @@ +// 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 +#include +#include +#include "unity.h" +#include "rom/uart.h" +#include "soc/uart_struct.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +static void fwrite_str_loopback(const char* str, size_t size) +{ + uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM); + UART0.conf0.loopback = 1; + fwrite(str, 1, size, stdout); + fflush(stdout); + uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM); + vTaskDelay(2 / portTICK_PERIOD_MS); + UART0.conf0.loopback = 0; +} + +static void flush_stdin_stdout() +{ + vTaskDelay(10 / portTICK_PERIOD_MS); + char *bitbucket = (char*) 0x3f000000; + while (fread(bitbucket, 1, 128, stdin) > 0) { + ; + } + fflush(stdout); + uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM); +} + +TEST_CASE("can read from stdin", "[vfs]") +{ + flush_stdin_stdout(); + + const size_t count = 12; + srand(count); + char* data = (char*) calloc(1, count * 8 + 1); + char* buf = (char*) calloc(1, count * 8 + 1); + char* p = data; + for (size_t i = 0; i < count; ++i) { + p += sprintf(p, "%08x", rand()); + } + p += sprintf(p, "\n"); + size_t len = p - data; + fwrite_str_loopback(data, len); + size_t cb = fread(buf, 1, len, stdin); + TEST_ASSERT_EQUAL(len, cb); + TEST_ASSERT_EQUAL_UINT8_ARRAY(data, buf, len); +} + + +#if CONFIG_NEWLIB_STDOUT_ADDCR + +TEST_CASE("CRs are removed from the stdin correctly", "[vfs]") +{ + flush_stdin_stdout(); + const char* send_str = "1234567890\n\r123\r\n4\n"; + /* with CONFIG_NEWLIB_STDOUT_ADDCR, the following will be sent on the wire. + * (last character of each part is marked with a hat) + * + * 1234567890\r\n\r123\r\r\n4\r\n + * ^ ^^ ^ + */ + char buf[128]; + char* dst = buf; + + fwrite_str_loopback(send_str, 11); // send up to the first \n + size_t rb = fread(dst, 1, 5, stdin); // read first 5 + TEST_ASSERT_EQUAL(5, rb); + dst += rb; + + rb = fread(dst, 1, 6, stdin); // ask for 6 + TEST_ASSERT_EQUAL(6, rb); // get 6 + + TEST_ASSERT_EQUAL_UINT8_ARRAY("1234567890\n", buf, 11); + dst += rb; + + rb = fread(dst, 1, 2, stdin); // any more characters? + TEST_ASSERT_EQUAL(0, rb); // nothing + + fwrite_str_loopback(send_str + 11, 1); // send the '\r' + vTaskDelay(10 / portTICK_PERIOD_MS); + + rb = fread(dst, 1, 2, stdin); // try to get somthing + TEST_ASSERT_EQUAL(0, rb); // still nothing (\r is buffered) + + fwrite_str_loopback(send_str + 12, 1); // Now send the '1' + vTaskDelay(10 / portTICK_PERIOD_MS); + rb = fread(dst, 1, 2, stdin); // try again + TEST_ASSERT_EQUAL(2, rb); // get two characters + TEST_ASSERT_EQUAL_UINT8_ARRAY("\r1", dst, 2); + dst += rb; + + fwrite_str_loopback(send_str + 13, 6); // Send the rest + vTaskDelay(10 / portTICK_PERIOD_MS); + + rb = fread(dst, 1, 4, stdin); // consume "23\r\n" + TEST_ASSERT_EQUAL(4, rb); + TEST_ASSERT_EQUAL_UINT8_ARRAY("23\r\n", dst, 4); + dst += rb; + + rb = fread(dst, 1, 4, stdin); // ask for more than the remainder + TEST_ASSERT_EQUAL(2, rb); + TEST_ASSERT_EQUAL_UINT8_ARRAY("4\n", dst, 2); +} + +#endif //CONFIG_NEWLIB_STDOUT_ADDCR + diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index d9d755f9be..545a5474fa 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -70,6 +70,58 @@ static size_t IRAM_ATTR uart_write(int fd, const void * data, size_t size) return size; } +static ssize_t IRAM_ATTR uart_read(int fd, void* data, size_t size) +{ + assert(fd >=0 && fd < 3); + uint8_t *data_c = (uint8_t *) data; + uart_dev_t* uart = s_uarts[fd]; + size_t received = 0; + _lock_acquire_recursive(&s_uart_locks[fd]); + while (uart->status.rxfifo_cnt > 0 && received < size) { + uint8_t c = uart->fifo.rw_byte; +#if CONFIG_NEWLIB_STDOUT_ADDCR + /* Convert \r\n sequences to \n. + * If \r is received, it is put into 'buffered_char' until the next + * character is received. Then depending on the character, we either + * drop \r (if the next one is \n) or output \r and then proceed to output + * the new character. + */ + const int NONE = -1; + static int buffered_char = NONE; + if (buffered_char != NONE) { + if (buffered_char == '\r' && c == '\n') { + buffered_char = NONE; + } else { + data_c[received] = buffered_char; + buffered_char = NONE; + ++received; + if (received == size) { + /* We have placed the buffered character into the output buffer + * but there won't be enough space for the newly received one. + * Keep the new character in buffered_char until read is called + * again. + */ + buffered_char = c; + break; + } + } + } + if (c == '\r') { + buffered_char = c; + continue; + } +#endif //CONFIG_NEWLIB_STDOUT_ADDCR + data_c[received] = c; + ++received; + } + _lock_release_recursive(&s_uart_locks[fd]); + if (received > 0) { + return received; + } + errno = EWOULDBLOCK; + return -1; +} + static int IRAM_ATTR uart_fstat(int fd, struct stat * st) { assert(fd >=0 && fd < 3); @@ -92,7 +144,7 @@ void esp_vfs_dev_uart_register() .open = &uart_open, .fstat = &uart_fstat, .close = &uart_close, - .read = NULL, // TODO: implement reading from UART + .read = &uart_read, .lseek = NULL, .stat = NULL, .link = NULL,

qXBX&-GMt)s%M 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 delta 773183 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