diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d78ac28..c548516 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -3,6 +3,11 @@ list(APPEND incs "../components/system/include") list(APPEND incs "../components/system/source") list(APPEND incs "../components/system/source/k_kit") list(APPEND incs "../components/system/source/shell") +list(APPEND incs "drivers/data_port/usb-host") +list(APPEND incs "drivers/data_port/ble_spp") +list(APPEND incs "drivers/data_port/socket_inet") + + list(APPEND srcs "app_main.c") list(APPEND srcs "app_info.c") @@ -17,8 +22,38 @@ list(APPEND srcs "config/board_config.c") list(APPEND srcs "config/app_config.c") list(APPEND srcs "utils/crc.c") +if(CONFIG_BUILD_BLE) +list(APPEND srcs "drivers/data_port/ble_spp/ble_spp_server.c") +list(APPEND srcs "drivers/data_port/ble_spp/ble_spp_server_shell.c") +endif() + +if(CONFIG_BUILD_WIFI) +list(APPEND srcs "drivers/data_port/socket_inet/wifi.c") +list(APPEND srcs "drivers/data_port/socket_inet/wifi_shell.c") +list(APPEND srcs "drivers/data_port/socket_inet/socket_inet.c") +list(APPEND srcs "drivers/data_port/socket_inet/socket_inet_server.c") +list(APPEND srcs "drivers/data_port/socket_inet/socket_inet_server_shell.c") +endif() + +if(CONFIG_IDF_TARGET_ESP32S3) +list(APPEND srcs "drivers/data_port/usb-host/usbport.c") +list(APPEND srcs "drivers/data_port/usb-host/msc/diskio_usb.c") +list(APPEND srcs "drivers/data_port/usb-host/msc/msc_host_vfs.c") +list(APPEND srcs "drivers/data_port/usb-host/msc/msc_host.c") +list(APPEND srcs "drivers/data_port/usb-host/msc/msc_scsi_bot.c") +endif() + idf_component_register( INCLUDE_DIRS ${incs} SRCS ${srcs} - REQUIRES driver nvs_flash + REQUIRES + driver + nvs_flash + app_update + esp_wifi + mbedtls + bt + usb + fatfs + vfs ) diff --git a/app/drivers/data_port/ble_spp/ble_spp_client.c b/app/drivers/data_port/ble_spp/ble_spp_client.c new file mode 100644 index 0000000..5377bde --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_client.c @@ -0,0 +1,970 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#include "esp_bt.h" +#include "esp_bt_device.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_system.h" +#include "esp_gatt_common_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" + +#include "drivers/data_port/ble_spp/ble_spp_client.h" +#include "os/os.h" + +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF +#define SYS_LOG_DOMAIN "GATTC_SPP" +#define CONS_ABORT() +#include "sys_log.h" + +typedef struct +{ + os_pipe_t pipe_obj; +} __ble_port_data_t; + +static __ble_port_data_t s_data; +static ble_client_status_t s_status; +static ble_client_connect_cb s_connect_cb; +static bool s_scan_start_en; +static bool s_scan_start_flag; + +#define SYS_LOG_BDA(BDA, SIZE) \ + do \ + { \ + char buf[0x40]; \ + int len = 0; \ + for (int i = 0; i < SIZE - 1; i++) \ + { \ + len += SYS_SPRINT(&buf[len], "%02X", BDA[i]); \ + if (len >= sizeof(buf) - 1) \ + { \ + break; \ + } \ + if (i + 1 < SIZE) \ + { \ + len += SYS_SPRINT(&buf[len], ":"); \ + } \ + } \ + buf[len] = '\0'; \ + SYS_LOG_DBG("%s", buf); \ + } while (0) + +#define SYS_LOG_STR(STR, LEN) \ + do \ + { \ + char buf[0x40]; \ + int len = sizeof(buf) - 1 < LEN ? sizeof(buf) - 1 : LEN; \ + memcpy(buf, STR, len); \ + buf[len] = '\0'; \ + SYS_LOG_DBG("%s", buf); \ + } while (0) + +#define PROFILE_NUM 1 +#define PROFILE_APP_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] +#define ESP_GATT_SPP_SERVICE_UUID 0xABF0 +#define SCAN_ALL_THE_TIME 0 + +struct gattc_profile_inst +{ + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t char_handle; + esp_bd_addr_t remote_bda; +}; + +enum +{ + SPP_IDX_SVC, + + SPP_IDX_SPP_DATA_RECV_VAL, + + SPP_IDX_SPP_DATA_NTY_VAL, + SPP_IDX_SPP_DATA_NTF_CFG, + + SPP_IDX_SPP_COMMAND_VAL, + + SPP_IDX_SPP_STATUS_VAL, + SPP_IDX_SPP_STATUS_CFG, + +#ifdef SUPPORT_HEARTBEAT + SPP_IDX_SPP_HEARTBEAT_VAL, + SPP_IDX_SPP_HEARTBEAT_CFG, +#endif + + SPP_IDX_NB, +}; + +/** + * @brief 当 ble_client_start() 设置的回调函数为 NULL 时默认的回调函数 + * + * @param status + */ +static void _cb_hdl_default(ble_client_status_t status) +{ + static char *const str[] = { + [BLE_CLIENT_STATUS_STOP] = "stop", + [BLE_CLIENT_STATUS_SCANNING] = "scanning", + [BLE_CLIENT_STATUS_CONNECTED] = "connected", + [BLE_CLIENT_STATUS_DISCONNECTED] = "disconnected", + }; + if (status < sizeof(str) / sizeof(str[0]) && str[status] != NULL) + { + SYS_LOG_DBG("connect status: %s", str[status]); + } + else + { + SYS_LOG_WRN("unknow status: %d", status); + } +} + +/** + * @brief 更新连接状态并并执行由 ble_client_start() 设置的回调函数 + * + * @param new_status 设置新的连接状态 + */ +static void _port_cb(ble_client_status_t new_status) +{ + if (s_status != new_status) + { + s_status = new_status; + ble_client_connect_cb cb = s_connect_cb; + if (cb) + { + cb(s_status); + } + else + { + _cb_hdl_default(s_status); + } + } +} + +/** + * @brief 启动扫描 + * + * @param duration 单位为秒 + */ +static void _gap_start(uint32_t duration) +{ + if (s_scan_start_en && s_scan_start_flag) + { + if (s_status != BLE_CLIENT_STATUS_SCANNING) + { + esp_ble_gap_start_scanning(duration); + _port_cb(BLE_CLIENT_STATUS_SCANNING); + } + } + else + { + _port_cb(BLE_CLIENT_STATUS_STOP); + } +} + +/** + * @brief 关闭扫描 + */ +static void _gap_stop(void) +{ + if (s_status == BLE_CLIENT_STATUS_SCANNING) + { + s_status = BLE_CLIENT_STATUS_STOP; + esp_ble_gap_stop_scanning(); + } +} + +/// 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_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *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_APP_ID] = { + .gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE}; + +static char *s_device_name; +static bool is_connect = false; +static uint16_t spp_conn_id = 0; +static uint16_t spp_mtu_size = 23; +static uint16_t cmd = 0; +static uint16_t spp_srv_start_handle = 0; +static uint16_t spp_srv_end_handle = 0; +static uint16_t spp_gattc_if = 0xff; +static char *notify_value_p = NULL; +static int notify_value_offset = 0; +static int notify_value_count = 0; +static uint16_t count = SPP_IDX_NB; +static esp_gattc_db_elem_t *db = NULL; +static esp_ble_gap_cb_param_t scan_rst; +static QueueHandle_t cmd_reg_queue = NULL; +QueueHandle_t spp_uart_queue = NULL; + +#ifdef SUPPORT_HEARTBEAT +static uint8_t heartbeat_s[9] = {'E', 's', 'p', 'r', 'e', 's', 's', 'i', 'f'}; +static QueueHandle_t cmd_heartbeat_queue = NULL; +#endif + +static esp_bt_uuid_t spp_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = { + .uuid16 = ESP_GATT_SPP_SERVICE_UUID, + }, +}; + +static void notify_event_handler(esp_ble_gattc_cb_param_t *p_data) +{ + uint8_t handle = 0; + + if (p_data->notify.is_notify == true) + { + SYS_LOG_DBG("+NOTIFY:handle = %d,length = %d ", p_data->notify.handle, p_data->notify.value_len); + } + else + { + SYS_LOG_DBG("+INDICATE:handle = %d,length = %d ", p_data->notify.handle, p_data->notify.value_len); + } + handle = p_data->notify.handle; + if (db == NULL) + { + SYS_LOG_ERR(" db is NULL"); + return; + } + if (handle == db[SPP_IDX_SPP_DATA_NTY_VAL].attribute_handle) + { + SYS_LOG_DUMP(p_data->notify.value, p_data->notify.value_len, 1, 0); + os_pipe_fifo_fill(&s_data.pipe_obj, p_data->notify.value, p_data->notify.value_len); + } + else if (handle == ((db + SPP_IDX_SPP_STATUS_VAL)->attribute_handle)) + { + SYS_LOG_STR((char *)p_data->notify.value, p_data->notify.value_len); + // TODO:server notify status characteristic + } + else + { + SYS_LOG_STR((char *)p_data->notify.value, p_data->notify.value_len); + } +} + +static void free_gattc_srv_db(void) +{ + is_connect = false; + spp_gattc_if = 0xff; + spp_conn_id = 0; + spp_mtu_size = 23; + cmd = 0; + spp_srv_start_handle = 0; + spp_srv_end_handle = 0; + notify_value_p = NULL; + notify_value_offset = 0; + notify_value_count = 0; + if (db) + { + free(db); + db = NULL; + } +} + +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; + esp_err_t err; + + switch (event) + { + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: + { + SYS_LOG_DBG("ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT"); + if ((err = param->scan_param_cmpl.status) != ESP_BT_STATUS_SUCCESS) + { + SYS_LOG_ERR("Scan param set failed: %s", esp_err_to_name(err)); + break; + } + s_scan_start_en = true; + _gap_start(0xFFFF); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + // scan start complete event to indicate scan start successfully or failed + if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) + { + SYS_LOG_ERR("ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: Scan start failed: %s", esp_err_to_name(err)); + break; + } + SYS_LOG_DBG("ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: Scan start successed. Target name: '%s'", s_device_name); + break; + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if ((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) + { + SYS_LOG_ERR("ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: Scan stop failed: %s", esp_err_to_name(err)); + break; + } + if (is_connect == false) + { + esp_ble_gattc_open(gl_profile_tab[PROFILE_APP_ID].gattc_if, scan_rst.scan_rst.bda, scan_rst.scan_rst.ble_addr_type, true); + SYS_LOG_DBG("ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: Scan stop successed"); + SYS_LOG_DBG("Connect to the remote device."); + } + else + { + _port_cb(BLE_CLIENT_STATUS_STOP); + } + break; + case ESP_GAP_BLE_SCAN_RESULT_EVT: + { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + + static uint8_t con_scan_count; + + switch (scan_result->scan_rst.search_evt) + { + case ESP_GAP_SEARCH_INQ_RES_EVT: + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + if (s_device_name != NULL) + { + if (adv_name != NULL && strlen(s_device_name) == adv_name_len) + { + if (strncmp((char *)adv_name, s_device_name, adv_name_len) == 0) + { + memcpy(&(scan_rst), scan_result, sizeof(esp_ble_gap_cb_param_t)); + _gap_stop(); + SYS_LOG_DBG(""); + SYS_LOG_BDA(scan_result->scan_rst.bda, 6); + SYS_LOG_DBG("Searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); + SYS_LOG_DBG("Searched Device Name Len %d", adv_name_len); + SYS_LOG_STR(adv_name, adv_name_len); + } + } + } + else + { + _gap_stop(); + } + if (con_scan_count++ == 100) + { + os_thread_sleep(100); + } + break; + case ESP_GAP_SEARCH_INQ_CMPL_EVT: + SYS_LOG_DBG("ESP_GAP_SEARCH_INQ_CMPL_EVT"); + break; + default: + SYS_LOG_DBG("scan_result->scan_rst.search_evt = %d", scan_result->scan_rst.search_evt); + break; + } + break; + } + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) + { + SYS_LOG_ERR("ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: Adv stop failed: %s", esp_err_to_name(err)); + } + else + { + SYS_LOG_DBG("ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: Stop adv successfully"); + } + break; + default: + SYS_LOG_DBG("event = %d", event); + break; + } +} + +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + SYS_LOG_DBG("EVT %d, gattc if %d", 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 + { + SYS_LOG_DBG("Reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status); + return; + } + } + /* 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); + + if (event == ESP_GATTC_CLOSE_EVT) + { + SYS_LOG_DBG("ESP_GATTC_CLOSE_EVT"); + if (s_scan_start_flag) + { + _port_cb(BLE_CLIENT_STATUS_DISCONNECTED); + } + _gap_start(SCAN_ALL_THE_TIME); + } +} + +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) + { + case ESP_GATTC_REG_EVT: + SYS_LOG_DBG("ESP_GATTC_REG_EVT: set scan params"); + esp_ble_gap_set_scan_params(&ble_scan_params); + break; + case ESP_GATTC_CONNECT_EVT: + SYS_LOG_DBG("ESP_GATTC_CONNECT_EVT: conn_id=%d, gatt_if = %d", spp_conn_id, gattc_if); + SYS_LOG_DBG("REMOTE BDA:"); + SYS_LOG_BDA(gl_profile_tab[PROFILE_APP_ID].remote_bda, sizeof(esp_bd_addr_t)); + spp_gattc_if = gattc_if; + is_connect = true; + spp_conn_id = p_data->connect.conn_id; + memcpy(gl_profile_tab[PROFILE_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); + esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid); + _port_cb(BLE_CLIENT_STATUS_CONNECTED); + break; + case ESP_GATTC_DISCONNECT_EVT: + SYS_LOG_DBG("ESP_GATTC_DISCONNECT_EVT: disconnect"); + free_gattc_srv_db(); + break; + case ESP_GATTC_SEARCH_RES_EVT: + SYS_LOG_DBG("ESP_GATTC_SEARCH_RES_EVT: start_handle = %d, end_handle = %d, UUID:0x%04x", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.uuid.uuid.uuid16); + spp_srv_start_handle = p_data->search_res.start_handle; + spp_srv_end_handle = p_data->search_res.end_handle; + break; + case ESP_GATTC_SEARCH_CMPL_EVT: + SYS_LOG_DBG("ESP_GATTC_SEARCH_CMPL_EVT: conn_id = %x, status %d", spp_conn_id, p_data->search_cmpl.status); + esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id); + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: + { + if (p_data->reg_for_notify.status == ESP_GATT_OK) + { + SYS_LOG_DBG("ESP_GATTC_REG_FOR_NOTIFY_EVT: Index = %d,status = %d,handle = %d", cmd, p_data->reg_for_notify.status, p_data->reg_for_notify.handle); + } + else + { + SYS_LOG_ERR("ESP_GATTC_REG_FOR_NOTIFY_EVT, status = %d", p_data->reg_for_notify.status); + break; + } + uint16_t notify_en = 1; + esp_ble_gattc_write_char_descr( + spp_gattc_if, + spp_conn_id, + (db + cmd + 1)->attribute_handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + + break; + } + case ESP_GATTC_NOTIFY_EVT: + SYS_LOG_DBG("ESP_GATTC_NOTIFY_EVT"); + notify_event_handler(p_data); + break; + case ESP_GATTC_READ_CHAR_EVT: + SYS_LOG_DBG("ESP_GATTC_READ_CHAR_EVT"); + break; + case ESP_GATTC_WRITE_CHAR_EVT: + if (param->write.status == ESP_GATT_OK) + { + SYS_LOG_DBG("ESP_GATTC_WRITE_CHAR_EVT: status = %d, handle = %d", param->write.status, param->write.handle); + } + else + { + SYS_LOG_ERR("ESP_GATTC_WRITE_CHAR_EVT, error status = %d", p_data->write.status); + _gap_start(SCAN_ALL_THE_TIME); + } + break; + case ESP_GATTC_PREP_WRITE_EVT: + SYS_LOG_DBG("ESP_GATTC_PREP_WRITE_EVT"); + break; + case ESP_GATTC_EXEC_EVT: + SYS_LOG_DBG("ESP_GATTC_EXEC_EVT"); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + if (p_data->write.status == ESP_GATT_OK) + { + SYS_LOG_DBG("ESP_GATTC_WRITE_DESCR_EVT: status =%d, handle = %d ", p_data->write.status, p_data->write.handle); + } + else + { + SYS_LOG_ERR("ESP_GATTC_WRITE_DESCR_EVT, error status = %d", p_data->write.status); + break; + } + switch (cmd) + { + case SPP_IDX_SPP_DATA_NTY_VAL: + cmd = SPP_IDX_SPP_STATUS_VAL; + xQueueSend(cmd_reg_queue, &cmd, 10 / portTICK_PERIOD_MS); + break; + case SPP_IDX_SPP_STATUS_VAL: +#ifdef SUPPORT_HEARTBEAT + cmd = SPP_IDX_SPP_HEARTBEAT_VAL; + xQueueSend(cmd_reg_queue, &cmd, 10 / portTICK_PERIOD_MS); +#endif + break; +#ifdef SUPPORT_HEARTBEAT + case SPP_IDX_SPP_HEARTBEAT_VAL: + xQueueSend(cmd_heartbeat_queue, &cmd, 10 / portTICK_PERIOD_MS); + break; +#endif + default: + break; + }; + break; + case ESP_GATTC_CFG_MTU_EVT: + if (p_data->cfg_mtu.status != ESP_OK) + { + break; + } + SYS_LOG_DBG("ESP_GATTC_CFG_MTU_EVT: +MTU:%d", p_data->cfg_mtu.mtu); + spp_mtu_size = p_data->cfg_mtu.mtu; + + db = (esp_gattc_db_elem_t *)malloc(count * sizeof(esp_gattc_db_elem_t)); + if (db == NULL) + { + SYS_LOG_ERR("malloc db falied"); + break; + } + if (esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK) + { + SYS_LOG_ERR("get db falied"); + break; + } + if (count != SPP_IDX_NB) + { + SYS_LOG_ERR("get db count != SPP_IDX_NB, count = %d, SPP_IDX_NB = %d", count, SPP_IDX_NB); + break; + } + for (int i = 0; i < SPP_IDX_NB; i++) + { + switch ((db + i)->type) + { + case ESP_GATT_DB_PRIMARY_SERVICE: + SYS_LOG_DBG("attr_type = PRIMARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x", + (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_SECONDARY_SERVICE: + SYS_LOG_DBG("attr_type = SECONDARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x", + (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_CHARACTERISTIC: + SYS_LOG_DBG("attr_type = CHARACTERISTIC,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x", + (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_DESCRIPTOR: + SYS_LOG_DBG("attr_type = DESCRIPTOR,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x", + (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_INCLUDED_SERVICE: + SYS_LOG_DBG("attr_type = INCLUDED_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x", + (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_ALL: + SYS_LOG_DBG("attr_type = ESP_GATT_DB_ALL,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x", + (db + i)->attribute_handle, (db + i)->start_handle, (db + i)->end_handle, (db + i)->properties, (db + i)->uuid.uuid.uuid16); + break; + default: + break; + } + } + cmd = SPP_IDX_SPP_DATA_NTY_VAL; + xQueueSend(cmd_reg_queue, &cmd, 10 / portTICK_PERIOD_MS); + break; + case ESP_GATTC_SRVC_CHG_EVT: + SYS_LOG_DBG("ESP_GATTC_SRVC_CHG_EVT"); + break; + default: + break; + } +} + +void spp_client_reg_task(void *arg) +{ + uint16_t cmd_id; + for (;;) + { + vTaskDelay(100 / portTICK_PERIOD_MS); + if (xQueueReceive(cmd_reg_queue, &cmd_id, portMAX_DELAY)) + { + if (db != NULL) + { + if (cmd_id == SPP_IDX_SPP_DATA_NTY_VAL) + { + SYS_LOG_DBG("SPP_IDX_SPP_DATA_NTY_VAL: Index = %d,UUID = 0x%04x, handle = %d ", cmd_id, (db + SPP_IDX_SPP_DATA_NTY_VAL)->uuid.uuid.uuid16, (db + SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle); + esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db + SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle); + } + else if (cmd_id == SPP_IDX_SPP_STATUS_VAL) + { + SYS_LOG_DBG("SPP_IDX_SPP_STATUS_VAL: Index = %d,UUID = 0x%04x, handle = %d ", cmd_id, (db + SPP_IDX_SPP_STATUS_VAL)->uuid.uuid.uuid16, (db + SPP_IDX_SPP_STATUS_VAL)->attribute_handle); + esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db + SPP_IDX_SPP_STATUS_VAL)->attribute_handle); + } +#ifdef SUPPORT_HEARTBEAT + else if (cmd_id == SPP_IDX_SPP_HEARTBEAT_VAL) + { + SYS_LOG_DBG("SPP_IDX_SPP_HEARTBEAT_VAL: Index = %d,UUID = 0x%04x, handle = %d ", cmd_id, (db + SPP_IDX_SPP_HEARTBEAT_VAL)->uuid.uuid.uuid16, (db + SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle); + esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db + SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle); + } +#endif + } + } + } +} + +#ifdef SUPPORT_HEARTBEAT +void spp_heart_beat_task(void *arg) +{ + uint16_t cmd_id; + + for (;;) + { + vTaskDelay(50 / portTICK_PERIOD_MS); + if (xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY)) + { + while (1) + { + if ((is_connect == true) && (db != NULL) && ((db + SPP_IDX_SPP_HEARTBEAT_VAL)->properties & (ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_WRITE))) + { + esp_ble_gattc_write_char(spp_gattc_if, + spp_conn_id, + (db + SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle, + sizeof(heartbeat_s), + (uint8_t *)heartbeat_s, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + vTaskDelay(5000 / portTICK_PERIOD_MS); + } + else + { + SYS_LOG_DBG("disconnect"); + break; + } + } + } + } +} +#endif + +void ble_client_appRegister(void) +{ + esp_err_t status; + char err_msg[20]; + + SYS_LOG_DBG("register callback"); + + // register the scan callback function to the gap module + if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) + { + SYS_LOG_ERR("gap register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg))); + return; + } + // register the callback function to the gattc module + if ((status = esp_ble_gattc_register_callback(esp_gattc_cb)) != ESP_OK) + { + SYS_LOG_ERR("gattc register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg))); + return; + } + esp_ble_gattc_app_register(PROFILE_APP_ID); + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(200); + if (local_mtu_ret) + { + SYS_LOG_ERR("set local MTU failed: %s", esp_err_to_name_r(local_mtu_ret, err_msg, sizeof(err_msg))); + } + + cmd_reg_queue = xQueueCreate(10, sizeof(uint32_t)); + xTaskCreate(spp_client_reg_task, "spp_client_reg_task", 2048, NULL, 10, NULL); + +#ifdef SUPPORT_HEARTBEAT + cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t)); + xTaskCreate(spp_heart_beat_task, "spp_heart_beat_task", 2048, NULL, 10, NULL); +#endif +} + +static void _ble_spp_client_init(void) +{ + esp_err_t ret; + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + + ret = esp_bt_controller_init(&bt_cfg); + if (ret) + { + SYS_LOG_ERR("enable controller failed: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) + { + SYS_LOG_ERR("enable controller failed: %s", esp_err_to_name(ret)); + return; + } + + SYS_LOG_DBG("init bluetooth"); + ret = esp_bluedroid_init(); + if (ret) + { + SYS_LOG_ERR("init bluetooth failed: %s", esp_err_to_name(ret)); + return; + } + ret = esp_bluedroid_enable(); + if (ret) + { + SYS_LOG_ERR("enable bluetooth failed: %s", esp_err_to_name(ret)); + return; + } + + ble_client_appRegister(); +} + +/* port ------------------------------------------------------------------------------------------------------------*/ + +typedef struct +{ + const char *tar_name; + ble_client_connect_cb cb; +} __ble_start_opt_t; + +static sb_data_port_t s_port; + +static void _port_init(sb_data_port_t *port); +static void _port_uninit(sb_data_port_t *port); +static void _port_start(sb_data_port_t *port, SB_DATA_PORT_RXMODE mode, const void *opt); +static void _port_stop(sb_data_port_t *port); +static int _port_write(sb_data_port_t *port, const void *data, uint32_t size, uint32_t wait_ms); +static int _port_read(sb_data_port_t *port, void *data, uint32_t size, uint32_t wait_ms); +static void *_port_get_recv_pipe(sb_data_port_t *port); + +static sb_data_port_vtable_t const s_port_vtable = { + .init = _port_init, + .uninit = _port_uninit, + .start = _port_start, + .stop = _port_stop, + .write = _port_write, + .read = _port_read, +}; + +static void _port_init(sb_data_port_t *port) +{ + _ble_spp_client_init(); +} + +static void _port_uninit(sb_data_port_t *port) +{ +} + +static void _port_start(sb_data_port_t *port, SB_DATA_PORT_RXMODE mode, const void *opt) +{ + const __ble_start_opt_t *p = opt; + + do + { + if (s_device_name) + { + if (is_connect != false) + { + ble_client_stop(); + } + for (int i = 0; i < 100; i++) + { + if (is_connect == false) + { + os_thread_sleep(100); + break; + } + os_thread_sleep(10); + } + + if (is_connect != false) + { + SYS_LOG_WRN("can not stop gap scanning!!"); + break; + } + + os_free(s_device_name); + } + + int len = strlen(p->tar_name); + s_device_name = os_malloc(len + 1); + if (s_device_name == NULL) + { + SYS_LOG_WRN("insufficient memory"); + break; + } + + strcpy(s_device_name, p->tar_name); + s_connect_cb = p->cb; + + s_scan_start_flag = true; + _gap_start(0xFFFF); + + } while (0); +} + +static void _port_stop(sb_data_port_t *port) +{ + if (s_scan_start_flag) + { + s_scan_start_flag = false; + _gap_stop(); + if (is_connect == true) + { + esp_ble_gap_disconnect(gl_profile_tab[PROFILE_APP_ID].remote_bda); + } + } +} + +static int _port_write(sb_data_port_t *port, const void *data, uint32_t size, uint32_t wait_ms) +{ + int wtotal = size; + while (size) + { + int wsize = size; + if (wsize > (1 << sizeof(uint16_t) * 2) - 1) + { + wsize = (1 << sizeof(uint16_t) * 2) - 1; + } + + if ((is_connect == true) && (db != NULL) && ((db + SPP_IDX_SPP_DATA_RECV_VAL)->properties & (ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_WRITE))) + { + if (esp_ble_gattc_write_char(spp_gattc_if, + spp_conn_id, + (db + SPP_IDX_SPP_DATA_RECV_VAL)->attribute_handle, + wsize, + (void *)data, + ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE) != ESP_OK) + { + return -1; + } + } + else + { + return -1; + } + + size -= wsize; + data = &((uint8_t *)data)[wsize]; + } + return wtotal; +} + +static int _port_read(sb_data_port_t *port, void *data, uint32_t size, uint32_t wait_ms) +{ + SYS_LOG_ERR("TODO"); + return -1; +} + +static void *_port_get_recv_pipe(sb_data_port_t *port) +{ + __ble_port_data_t *data = port->data; + return &data->pipe_obj; +} + +sb_data_port_t *sb_ble_client_port_init(uint32_t buffer_size, int rx_task_priority) +{ + /* 设置 s_port */ + s_port.vtable = &s_port_vtable; + s_port.data = &s_data; + + /* s_data::pipe_obj */ + if (os_pipe_is_valid(&s_data.pipe_obj)) + { + SYS_LOG_WRN("ble initialize again!"); + return &s_port; + } + os_pipe_create(&s_data.pipe_obj, buffer_size); + + s_port.vtable->init(&s_port); + + return &s_port; +} + +sb_data_port_t *ble_client_port_bind(void) +{ + return &s_port; +} + +/** + * @brief 开始扫描指定的设备名并连接 + * + * @param tar_name 目标设备名 + * @param cb 回调函数, NULL 值可用 + */ +void ble_client_start(const char *tar_name, ble_client_connect_cb cb) +{ + __ble_start_opt_t opt = { + .tar_name = tar_name, + .cb = cb, + }; + s_port_vtable.start(&s_port, SB_DATA_PORT_RXMODE_AUTO_PULL_DATA, &opt); +} + +/** + * @brief 停止扫描,断开连接 + */ +void ble_client_stop(void) +{ + s_port_vtable.stop(&s_port); +} + +/** + * @brief 获取当前的链接状态 + * + * @return ble_client_status_t + */ +ble_client_status_t ble_client_get_gap_status(void) +{ + return s_status; +} diff --git a/app/drivers/data_port/ble_spp/ble_spp_client.h b/app/drivers/data_port/ble_spp/ble_spp_client.h new file mode 100644 index 0000000..030994d --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_client.h @@ -0,0 +1,32 @@ +/** + * @file ble_spp_client.h + * @author LokLiang + * @brief BLE 主机接口(客户端) + * @version 0.1 + * @date 2023-09-21 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include "drivers/data_port/sb_data_port.h" +#include "os/os.h" + +typedef enum +{ + BLE_CLIENT_STATUS_STOP, + BLE_CLIENT_STATUS_SCANNING, + BLE_CLIENT_STATUS_CONNECTED, + BLE_CLIENT_STATUS_DISCONNECTED, +} ble_client_status_t; + +typedef void (*ble_client_connect_cb)(ble_client_status_t); + +sb_data_port_t *sb_ble_client_port_init(uint32_t buffer_size, int rx_task_priority); +sb_data_port_t *ble_client_port_bind(void); + +void ble_client_start(const char *tar_name, ble_client_connect_cb cb); +void ble_client_stop(void); +ble_client_status_t ble_client_get_gap_status(void); diff --git a/app/drivers/data_port/ble_spp/ble_spp_client_shell.c b/app/drivers/data_port/ble_spp/ble_spp_client_shell.c new file mode 100644 index 0000000..6a2ab3d --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_client_shell.c @@ -0,0 +1,136 @@ +#include "ble_spp_client_shell.h" +#include "ble_spp_client.h" +#include "shell/sh.h" + +#include + +#include "drivers/data_port/sb_data_port.h" + +static sh_cp_param_t const s_param_profile[] = { + {"\"SpeedyBee F405 Mini\"", "SpeedyBee F405 Mini"}, + {NULL}, +}; + +static void _ble_connect_cb(ble_client_status_t status); + +SH_CMD_FN(_start); +SH_CMD_FN(_stop); +SH_CMD_FN(_status); +SH_CMD_FN(_write); + +SH_CMD_CP_FN(_start_cp); + +SH_DEF_SUB_CMD( + sub_ble, + SH_SETUP_CMD("start", "开始扫描指定的设备名并连接 <设备名>", _start, _start_cp), // + SH_SETUP_CMD("stop", "停止扫描,断开连接", _stop, NULL), // + SH_SETUP_CMD("status", "获取当前连接状态", _status, NULL), // + SH_SETUP_CMD("write", "发送数据 <数值|字符串>", _write, NULL), // +); + +SH_DEF_CMD( + _register_cmd_ble, + SH_SETUP_CMD("ble", "操作 BLE 透传数据接口", NULL, sub_ble), // +); + +void ble_client_shell_register(void) +{ + sh_register_cmd(&_register_cmd_ble); +} + +void ble_client_shell_unregister(void) +{ + sh_unregister_cmd(&_register_cmd_ble); +} + +static void _ble_connect_cb(ble_client_status_t status) +{ + sh_echo(&g_uart_handle_vt100, "\r\n"); + _status(&g_uart_handle_vt100, 0, NULL); +} + +SH_CMD_FN(_start) +{ + if (argc < 1) + { + sh_echo(sh_hdl, "缺少参数\r\n"); + return -1; + } + + if (argc > 1) + { + sh_echo(sh_hdl, "参数中有空格。如果目标设备名中包含空格,请使用引号表示\r\n"); + return -1; + } + + ble_client_start(argv[0], _ble_connect_cb); + return 0; +} + +SH_CMD_FN(_stop) +{ + ble_client_stop(); + os_thread_sleep(100); + return 0; +} + +SH_CMD_FN(_status) +{ + static char *const str[] = { + [BLE_CLIENT_STATUS_STOP] = "stop", + [BLE_CLIENT_STATUS_SCANNING] = "scanning", + [BLE_CLIENT_STATUS_CONNECTED] = "connected", + [BLE_CLIENT_STATUS_DISCONNECTED] = "disconnected", + }; + ble_client_status_t status = ble_client_get_gap_status(); + if (status < sizeof(str) / sizeof(str[0]) && str[status] != NULL) + { + sh_echo(sh_hdl, "connect status: %s\r\n", str[status]); + } + else + { + sh_echo(sh_hdl, "unknow status: %d\r\n", status); + } + return 0; +} + +SH_CMD_FN(_write) +{ + int ret = -1; + if (argc < 1) + { + sh_echo(sh_hdl, "缺少参数\r\n"); + return -1; + } + + sh_parse_t pv = sh_parse_value(argv[0]); + switch (pv.type) + { + case _PARSE_TYPE_STRING: // 字符串 + ret = sb_data_port_write(ble_client_port_bind(), pv.value.val_string, strlen(pv.value.val_string), 0); + break; + case _PARSE_TYPE_INTEGER: // 带符号整型 + ret = sb_data_port_write(ble_client_port_bind(), &pv.value.val_integer, sizeof(pv.value.val_integer), 0); + break; + case _PARSE_TYPE_UNSIGNED: // 无符号整型 + ret = sb_data_port_write(ble_client_port_bind(), &pv.value.val_unsigned, sizeof(pv.value.val_unsigned), 0); + break; + case _PARSE_TYPE_FLOAT: // 浮点 + ret = sb_data_port_write(ble_client_port_bind(), &pv.value.val_float, sizeof(pv.value.val_float), 0); + break; + + default: + return -1; + } + + os_thread_sleep(50); + return ret; +} + +SH_CMD_CP_FN(_start_cp) +{ + if (argc + flag == 1) + { + sh_completion_param(sh_hdl, s_param_profile); + } +} diff --git a/app/drivers/data_port/ble_spp/ble_spp_client_shell.h b/app/drivers/data_port/ble_spp/ble_spp_client_shell.h new file mode 100644 index 0000000..be73d75 --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_client_shell.h @@ -0,0 +1,4 @@ +#pragma once + +void ble_client_shell_register(void); +void ble_client_shell_unregister(void); diff --git a/app/drivers/data_port/ble_spp/ble_spp_server.c b/app/drivers/data_port/ble_spp/ble_spp_server.c new file mode 100644 index 0000000..635555d --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_server.c @@ -0,0 +1,1196 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_bt_main.h" +#include "esp_system.h" +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_bt_device.h" +#include "driver/uart.h" +#include "string.h" + +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "ble_spp_server_define.h" + +#include "ble_spp_server.h" +#include "os/os.h" + +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_WRN +#define SYS_LOG_DOMAIN "GATTS_SPP" +#define CONS_ABORT() +#include "sys_log.h" + +#define _MAX_FIFO_ALLOC_CNT 100 /* 允许最大申请内存的次数 */ +#define SPP_MTU_HEAD_SIZE 5 + +typedef struct +{ + uint32_t buffer_size; // 指定接收缓存长度 + os_work_t *wkup_work_hdl; // 当接收非空时唤醒的工作项, NULL 值可用 + os_pipe_t pipe_obj; +} __ble_port_data_t; + +typedef struct +{ + uint32_t len; + uint8_t data[0]; +} __ble_fifo_t; + +static sb_data_port_t s_port_cmd; // 命令通道数据接口 +static sb_data_port_t s_port_val; // 数据通道数据接口 +static uint32_t s_val_fifo_cnt; // 用于限制 fifo 申请的次数 +static os_fifo_t s_val_fifo_hdl; // 数据通道的缓存队列句柄 +static struct +{ + char device_name[20]; // 设备名 + ble_server_gen_manufacturer_data_cb manufacturer_data; // 回调函数,生成 manufacturer[20] + ble_server_connect_cb connect_cb; // 回调函数, NULL 值可用 +} s_init_param; // 初始化参数 + +#define SPP_PROFILE_NUM 1 +#define SPP_PROFILE_APP_IDX 0 +#define ESP_SPP_APP_ID 0x56 +#define SPP_SVC_INST_ID 0 + +/// SPP Service +static const uint16_t spp_service_uuid = 0xABF0; +/// Characteristic UUID +#define ESP_GATT_UUID_SPP_DATA_RECEIVE 0xABF1 // 透传数据接收 +#define ESP_GATT_UUID_SPP_DATA_NOTIFY 0xABF2 // 透传数据发送 +#define ESP_GATT_UUID_SPP_COMMAND_RECEIVE 0xABF3 // 命令接收 +#define ESP_GATT_UUID_SPP_COMMAND_NOTIFY 0xABF4 // 命令响应的返回。 + +#define ADV_CONFIG_FLAG (1 << 0) +#define SCAN_RSP_CONFIG_FLAG (1 << 1) + +static uint8_t s_manufacturer_data[20]; +static uint8_t s_mac[6]; +static uint8_t adv_config_done = 0; + +static uint8_t const spp_adv_data[23] = { + /* Flags */ + 0x02, 0x01, 0x06, + /* Complete List of 16-bit Service Class UUIDs */ + 0x03, 0x03, 0xF0, 0xAB, + /* Complete Local Name in advertising */ + 0x0F, 0x09, 'E', 'S', 'P', '_', 'S', 'P', 'P', '_', 'S', 'E', 'R', 'V', 'E', 'R'}; + +static uint8_t const 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, 0xFF, 0x00, 0x00, 0x00}; + +/* The length of adv data must be less than 31 bytes */ +static esp_ble_adv_data_t adv_data = { + .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, // s_manufacturer_data, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = sizeof(service_uuid), + .p_service_uuid = (uint8_t *)service_uuid, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; + +// scan response data +static esp_ble_adv_data_t scan_rsp_data = { + .set_scan_rsp = true, + .include_name = false, + .include_txpower = false, + .min_interval = 0x20, + .max_interval = 0x40, + .appearance = 0x00, + .manufacturer_len = sizeof(s_manufacturer_data), + .p_manufacturer_data = s_manufacturer_data, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = 0, + .p_service_uuid = NULL, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; + +static uint16_t spp_mtu_size = 23; +static uint16_t spp_conn_id = 0xffff; +static esp_gatt_if_t spp_gatts_if = 0xff; + +#ifdef SUPPORT_HEARTBEAT +#define ESP_GATT_UUID_SPP_HEARTBEAT 0xABF5 +static QueueHandle_t cmd_heartbeat_queue = NULL; +static uint8_t heartbeat_s[9] = {'E', 's', 'p', 'r', 'e', 's', 's', 'i', 'f'}; +static bool enable_heart_ntf = false; // 是否启用notify +static uint8_t heartbeat_count_num = 0; +#endif + +static bool enable_data_ntf = false; // 是否启用notify +static bool is_connected = false; // 蓝牙有无连接 + +// 远程蓝牙设备地址 +static esp_bd_addr_t spp_remote_bda = { + 0x0, +}; + +static uint16_t spp_handle_table[SPP_IDX_NB]; // 特征值表 + +static esp_ble_adv_params_t spp_adv_params = { + // 广播出去的信息 + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .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; +}; + +typedef struct spp_receive_data_node +{ + int32_t len; + uint8_t *node_buff; + struct spp_receive_data_node *next_node; +} spp_receive_data_node_t; + +typedef struct spp_receive_data_buff +{ + int32_t node_num; + int32_t buff_size; + spp_receive_data_node_t *first_node; +} spp_receive_data_buff_t; + +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 spp_profile_tab[SPP_PROFILE_NUM] = { + [SPP_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 */ + }, +}; + +/* + * SPP PROFILE ATTRIBUTES + **************************************************************************************** + */ + +#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_read_notify = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_READ; + +#ifdef SUPPORT_HEARTBEAT +static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_NOTIFY; +#endif + +/// SPP Service - data receive characteristic, read&write without response +static const uint16_t spp_data_receive_uuid = ESP_GATT_UUID_SPP_DATA_RECEIVE; +static const uint8_t spp_data_receive_val[20] = {0x00}; + +/// SPP Service - data notify characteristic, notify&read +static const uint16_t spp_data_notify_uuid = ESP_GATT_UUID_SPP_DATA_NOTIFY; +static const uint8_t spp_data_notify_val[20] = {0x00}; +static const uint8_t spp_data_notify_ccc[2] = {0x00, 0x00}; + +/// SPP Service - command characteristic, read&write without response +static const uint16_t spp_command_uuid = ESP_GATT_UUID_SPP_COMMAND_RECEIVE; +static const uint8_t spp_command_val[10] = {0x00}; + +/// SPP Service - status characteristic, notify&read +static const uint16_t spp_status_uuid = ESP_GATT_UUID_SPP_COMMAND_NOTIFY; +static const uint8_t spp_status_val[10] = {0x00}; +static const uint8_t spp_status_ccc[2] = {0x00, 0x00}; + +#ifdef SUPPORT_HEARTBEAT +/// SPP Server - Heart beat characteristic, notify&write&read +static const uint16_t spp_heart_beat_uuid = ESP_GATT_UUID_SPP_HEARTBEAT; +static const uint8_t spp_heart_beat_val[2] = {0x00, 0x00}; +static const uint8_t spp_heart_beat_ccc[2] = {0x00, 0x00}; +#endif + +/// Full HRS Database Description - Used to add attributes into the database +static esp_gatts_attr_db_t const spp_gatt_db[SPP_IDX_NB] = { + // SPP - Service Declaration + [SPP_IDX_SVC] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, // 服务的类型 0X2800 + sizeof(spp_service_uuid), sizeof(spp_service_uuid), (uint8_t *)&spp_service_uuid}}, // 服务的ID + + // SPP - data receive characteristic Declaration + [SPP_IDX_SPP_DATA_RECV_CHAR] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, // 特征值声明的类型 0X2803 + CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}}, // 特征值属性 + + // SPP - data receive characteristic Value + [SPP_IDX_SPP_DATA_RECV_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_receive_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, // 特征值value的UUID + SPP_DATA_MAX_LEN, sizeof(spp_data_receive_val), (uint8_t *)spp_data_receive_val}}, // 对应的变量 + + // SPP - data notify characteristic Declaration + [SPP_IDX_SPP_DATA_NOTIFY_CHAR] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, // 特征值声明的类型 0X2803 + CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}}, // 特征值属性 + + // SPP - data notify characteristic Value + [SPP_IDX_SPP_DATA_NTY_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_notify_uuid, ESP_GATT_PERM_READ, // 特征值声明的类型 0X2803 + SPP_DATA_MAX_LEN, sizeof(spp_data_notify_val), (uint8_t *)spp_data_notify_val}}, // 对应的变量 + + // SPP - data notify characteristic - Client Characteristic Configuration Descriptor + [SPP_IDX_SPP_DATA_NTF_CFG] = // Client characteristic configuration UUID 0X2902 + {{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(spp_data_notify_ccc), (uint8_t *)spp_data_notify_ccc}}, + + // SPP - command characteristic Declaration + [SPP_IDX_SPP_COMMAND_CHAR] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, // 特征值声明的类型 0X2803 + CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}}, + + // SPP - command characteristic Value + [SPP_IDX_SPP_COMMAND_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_command_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, // 特征值value的UUID + SPP_CMD_MAX_LEN, sizeof(spp_command_val), (uint8_t *)spp_command_val}}, + + // SPP - status characteristic Declaration + [SPP_IDX_SPP_STATUS_CHAR] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, // 特征值声明的类型 0X2803 + CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}}, + + // SPP - status characteristic Value + [SPP_IDX_SPP_STATUS_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_status_uuid, ESP_GATT_PERM_READ, // 特征值value的UUID + SPP_STATUS_MAX_LEN, sizeof(spp_status_val), (uint8_t *)spp_status_val}}, + + // SPP - status characteristic - Client Characteristic Configuration Descriptor + [SPP_IDX_SPP_STATUS_CFG] = // Client characteristic configuration UUID 0X2902 + {{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(spp_status_ccc), (uint8_t *)spp_status_ccc}}, + +#ifdef SUPPORT_HEARTBEAT + // SPP - Heart beat characteristic Declaration + [SPP_IDX_SPP_HEARTBEAT_CHAR] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, // 特征值声明的类型 0X2803 + CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}}, + + // SPP - Heart beat characteristic Value + [SPP_IDX_SPP_HEARTBEAT_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_heart_beat_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, sizeof(spp_heart_beat_val), sizeof(spp_heart_beat_val), (uint8_t *)spp_heart_beat_val}}, + + // SPP - Heart beat characteristic - Client Characteristic Configuration Descriptor + [SPP_IDX_SPP_HEARTBEAT_CFG] = // Client characteristic configuration UUID 0X2902 + {{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(spp_data_notify_ccc), (uint8_t *)spp_heart_beat_ccc}}, +#endif +}; + +static uint8_t find_char_and_desr_index(uint16_t handle) +{ + uint8_t error = 0xff; + + for (int i = 0; i < SPP_IDX_NB; i++) + { + if (handle == spp_handle_table[i]) + { + return i; + } + } + + return error; +} + +#ifdef SUPPORT_HEARTBEAT +void spp_heartbeat_task(void *arg) +{ + uint16_t cmd_id; + + for (;;) + { + vTaskDelay(50 / portTICK_PERIOD_MS); + if (xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY)) + { + while (1) + { + heartbeat_count_num++; + vTaskDelay(5000 / portTICK_PERIOD_MS); + if ((heartbeat_count_num > 3) && (is_connected)) + { + esp_ble_gap_disconnect(spp_remote_bda); // 主动断开连接 + } + if (is_connected && enable_heart_ntf) + { + esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_HEARTBEAT_VAL], sizeof(heartbeat_s), heartbeat_s, false); + } + else if (!is_connected) + { + break; + } + } + } + } + vTaskDelete(NULL); +} +#endif + +static void spp_task_init(void) +{ +#ifdef SUPPORT_HEARTBEAT + if (cmd_heartbeat_queue == NULL) + { + cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t)); + xTaskCreate(spp_heartbeat_task, "spp_heartbeat_task", 2048, NULL, 10, NULL); + } +#endif +} + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + esp_err_t err; + SYS_LOG_DBG("GAP_EVT, event %d", event); + + switch (event) + { + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + SYS_LOG_INF("ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT"); + adv_config_done &= (~ADV_CONFIG_FLAG); + if (adv_config_done == 0) + { + esp_ble_gap_start_advertising(&spp_adv_params); + } + break; + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + SYS_LOG_INF("ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT"); + adv_config_done &= (~ADV_CONFIG_FLAG); + if (adv_config_done == 0) + { + esp_ble_gap_start_advertising(&spp_adv_params); + } + break; + case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: + SYS_LOG_INF("ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT"); + adv_config_done &= (~SCAN_RSP_CONFIG_FLAG); + if (adv_config_done == 0) + { + esp_ble_gap_start_advertising(&spp_adv_params); + } + break; + + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + // advertising start complete event to indicate advertising start successfully or failed + if ((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) + { + SYS_LOG_ERR("Advertising start failed: %s", esp_err_to_name(err)); + } + 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) +{ + esp_ble_gatts_cb_param_t *p_data = (esp_ble_gatts_cb_param_t *)param; + uint8_t res = 0xff; + + SYS_LOG_DBG("event = %d", event); + switch (event) + { + case ESP_GATTS_REG_EVT: + SYS_LOG_INF("ESP_GATTS_REG_EVT: device_name = '%s'", s_init_param.device_name); + esp_ble_gap_set_device_name(s_init_param.device_name); + + if (0) + { + /* 弃用 来自 demo: examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.c */ + esp_ble_gap_config_adv_data_raw((uint8_t *)spp_adv_data, sizeof(spp_adv_data)); + } + else + { + esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data); + if (ret) + { + SYS_LOG_ERR("config adv data failed, error code = %d", ret); + } + adv_config_done |= ADV_CONFIG_FLAG; + + // config scan response data + ret = esp_ble_gap_config_adv_data(&scan_rsp_data); + if (ret) + { + SYS_LOG_ERR("config scan response data failed, error code = %d", ret); + } + adv_config_done |= SCAN_RSP_CONFIG_FLAG; + } + + esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID); + break; + case ESP_GATTS_READ_EVT: + res = find_char_and_desr_index(p_data->read.handle); + if (res == SPP_IDX_SPP_STATUS_VAL) + { // 读状态特征 + // TODO:client read the status characteristic + } + break; + case ESP_GATTS_WRITE_EVT: + { + res = find_char_and_desr_index(p_data->write.handle); // 哪个特征值引发event + if (p_data->write.is_prep == false) + { + if (res == SPP_IDX_SPP_COMMAND_VAL) // 收到命令 + { + SYS_LOG_INF("ESP_GATTS_WRITE_EVT : do os_pipe_fifo_fill()"); + SYS_LOG_DUMP(p_data->write.value, p_data->write.len, 0, 0); + __ble_port_data_t *port_data = s_port_cmd.data; + size_t len = os_pipe_fifo_fill(&port_data->pipe_obj, p_data->write.value, p_data->write.len); + if (len < p_data->write.len) + { + SYS_LOG_WRN("Pipeline cache exceeded %d bytes", p_data->write.len - len); + } + } + else if (res == SPP_IDX_SPP_DATA_NTF_CFG) // 收到客户端发过来的设置信息 + { + if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x01) && (p_data->write.value[1] == 0x00)) + { + enable_data_ntf = true; + } + else if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x00) && (p_data->write.value[1] == 0x00)) + { + enable_data_ntf = false; + } + } +#ifdef SUPPORT_HEARTBEAT + else if (res == SPP_IDX_SPP_HEARTBEAT_CFG) // 开启心跳 + { + if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x01) && (p_data->write.value[1] == 0x00)) + { + enable_heart_ntf = true; + } + else if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x00) && (p_data->write.value[1] == 0x00)) + { + enable_heart_ntf = false; + } + } + else if (res == SPP_IDX_SPP_HEARTBEAT_VAL) + { + if ((p_data->write.len == sizeof(heartbeat_s)) && (memcmp(heartbeat_s, p_data->write.value, sizeof(heartbeat_s)) == 0)) + { + heartbeat_count_num = 0; + } + } +#endif + else if (res == SPP_IDX_SPP_DATA_RECV_VAL) // 收到客户端发过来的数据 + { + SYS_LOG_INF("ESP_GATTS_WRITE_EVT : do os_pipe_fifo_fill()"); + SYS_LOG_DUMP(p_data->write.value, p_data->write.len, 0, 0); + __ble_port_data_t *port_data = s_port_val.data; + size_t len = os_pipe_fifo_fill(&port_data->pipe_obj, p_data->write.value, p_data->write.len); + if (len < p_data->write.len) + { + SYS_LOG_WRN("Pipeline cache exceeded %d bytes", p_data->write.len - len); + } + } + else + { + // TODO: + } + } + else if ((p_data->write.is_prep == true) && (res == SPP_IDX_SPP_DATA_RECV_VAL)) + { + SYS_LOG_INF("ESP_GATTS_WRITE_EVT : do os_fifo_put()"); + SYS_LOG_DUMP(p_data->write.value, p_data->write.len, 0, 0); + + if (s_val_fifo_cnt < _MAX_FIFO_ALLOC_CNT) + { + __ble_fifo_t *fifo = os_fifo_alloc(sizeof(__ble_fifo_t) + p_data->write.len); + if (fifo) + { + os_interrupt_disable(); + s_val_fifo_cnt++; + os_interrupt_enable(); + + fifo->len = p_data->write.len; + memcpy(fifo->data, p_data->write.value, p_data->write.len); + os_fifo_put(&s_val_fifo_hdl, fifo); + } + } + else + { + SYS_LOG_WRN("s_val_fifo_cnt: %d >= _MAX_FIFO_ALLOC_CNT: %d", s_val_fifo_cnt, _MAX_FIFO_ALLOC_CNT); + } + } + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: // 执行写 + { + SYS_LOG_INF("ESP_GATTS_EXEC_WRITE_EVT: do os_fifo_take()"); + if (p_data->exec_write.exec_write_flag) + { + for (;;) + { + __ble_fifo_t *fifo = os_fifo_take(&s_val_fifo_hdl, 0); + if (fifo) + { + __ble_port_data_t *port_data = s_port_val.data; + size_t len = os_pipe_fifo_fill(&port_data->pipe_obj, fifo->data, fifo->len); + if (len < p_data->write.len) + { + SYS_LOG_WRN("Pipeline cache exceeded %d bytes", p_data->write.len - len); + } + os_fifo_free(fifo); + + os_interrupt_disable(); + s_val_fifo_cnt--; + os_interrupt_enable(); + } + else + { + break; + } + } + } + break; + } + case ESP_GATTS_MTU_EVT: + spp_mtu_size = p_data->mtu.mtu; + SYS_LOG_WRN("spp_mtu_size set to %d", spp_mtu_size); + 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: // 客户端连接 + SYS_LOG_INF("ESP_GATTS_CONNECT_EVT"); + spp_conn_id = p_data->connect.conn_id; + spp_gatts_if = gatts_if; + is_connected = true; + memcpy(&spp_remote_bda, &p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); +#ifdef SUPPORT_HEARTBEAT + uint16_t cmd = 0; + xQueueSend(cmd_heartbeat_queue, &cmd, 10 / portTICK_PERIOD_MS); +#endif + if (s_init_param.connect_cb) + { + s_init_param.connect_cb(ble_server_get_gap_status()); + } + break; + case ESP_GATTS_DISCONNECT_EVT: // 断开连接 + SYS_LOG_INF("ESP_GATTS_DISCONNECT_EVT"); + is_connected = false; + enable_data_ntf = false; + spp_mtu_size = 23; +#ifdef SUPPORT_HEARTBEAT + enable_heart_ntf = false; + heartbeat_count_num = 0; +#endif + esp_ble_gap_start_advertising(&spp_adv_params); // 开启广播 + if (s_init_param.connect_cb) + { + s_init_param.connect_cb(ble_server_get_gap_status()); + } + 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: + { + SYS_LOG_DBG("The number handle = 0x%X", param->add_attr_tab.num_handle); + if (param->add_attr_tab.status != ESP_GATT_OK) + { + SYS_LOG_ERR("Create attribute table failed, error code=0x%x", param->add_attr_tab.status); + } + else if (param->add_attr_tab.num_handle != SPP_IDX_NB) + { + SYS_LOG_ERR("Create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, SPP_IDX_NB); + } + else + { + memcpy(spp_handle_table, param->add_attr_tab.handles, sizeof(spp_handle_table)); + esp_ble_gatts_start_service(spp_handle_table[SPP_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) +{ + SYS_LOG_DBG("EVT %d, gatts if %d", 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) + { + spp_profile_tab[SPP_PROFILE_APP_IDX].gatts_if = gatts_if; + } + else + { + SYS_LOG_DBG("Reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status); + return; + } + } + + do + { + int idx; + for (idx = 0; idx < SPP_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 == spp_profile_tab[idx].gatts_if) + { + if (spp_profile_tab[idx].gatts_cb) + { + spp_profile_tab[idx].gatts_cb(event, gatts_if, param); // 调用注册的回调函数 + } + } + } + } while (0); +} + +static void _ble_spp_server_init(void) +{ + esp_err_t ret; + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + ret = esp_bt_controller_init(&bt_cfg); + if (ret) + { + SYS_LOG_ERR("enable controller failed: %s", esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) + { + SYS_LOG_ERR("enable controller failed: %s", esp_err_to_name(ret)); + return; + } + + SYS_LOG_DBG("init bluetooth"); + esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); + ret = esp_bluedroid_init_with_cfg(&bluedroid_cfg); + if (ret) + { + SYS_LOG_ERR("init bluetooth failed: %s", esp_err_to_name(ret)); + return; + } + ret = esp_bluedroid_enable(); + if (ret) + { + SYS_LOG_ERR("enable bluetooth failed: %s", esp_err_to_name(ret)); + return; + } + + memcpy(s_mac, esp_bt_dev_get_address(), sizeof(s_mac)); + + if (s_init_param.manufacturer_data) + { + s_init_param.manufacturer_data(s_manufacturer_data); + } + else + { + for (int i = 0; i < sizeof(s_manufacturer_data); i++) + { + s_manufacturer_data[i] = i + 1; + } + } + + esp_ble_gatts_register_callback(gatts_event_handler); + esp_ble_gap_register_callback(gap_event_handler); + esp_ble_gatts_app_register(ESP_SPP_APP_ID); + + spp_task_init(); + + return; +} + +/* port ------------------------------------------------------------------------------------------------------------*/ + +static int _port_start_cmd(sb_data_port_t *port); +static int _port_stop_cmd(sb_data_port_t *port); +static int _port_write_cmd(sb_data_port_t *port, const void *data, uint32_t size); +static int _port_read_cmd(sb_data_port_t *port, void *data, uint32_t size); +static bool _port_cmd_is_started(sb_data_port_t *port); +static uint32_t _port_cmd_get_rx_length(sb_data_port_t *port); + +static int _port_start_val(sb_data_port_t *port); +static int _port_stop_val(sb_data_port_t *port); +static int _port_write_val(sb_data_port_t *port, const void *data, uint32_t size); +static int _port_read_val(sb_data_port_t *port, void *data, uint32_t size); +static bool _port_val_is_started(sb_data_port_t *port); +static uint32_t _port_val_get_rx_length(sb_data_port_t *port); + +static sb_data_port_vtable_t const s_port_vtable_cmd = { + .start = _port_start_cmd, // 由具体驱动实现的,以达到节省资源为主要目的,启动数据接口 + .stop = _port_stop_cmd, // 由具体驱动实现的,以达到节省资源为主要目的,关闭数据接口 + .write = _port_write_cmd, // 由具体驱动实现的,写数据到对应的接口。 + .read = _port_read_cmd, // 由具体驱动实现的,从数据接口中读取已缓存的数据。 + .is_started = _port_cmd_is_started, // 由具体驱动实现的,获取当前数据接口是否可用(是否已启动) + .get_rx_length = _port_cmd_get_rx_length, // 由具体驱动实现的,获取当前数据接口的本次可读长度 +}; + +static sb_data_port_vtable_t const s_port_vtable_val = { + .start = _port_start_val, // 由具体驱动实现的,以达到节省资源为主要目的,启动数据接口 + .stop = _port_stop_val, // 由具体驱动实现的,以达到节省资源为主要目的,关闭数据接口 + .write = _port_write_val, // 由具体驱动实现的,写数据到对应的接口。 + .read = _port_read_val, // 由具体驱动实现的,从数据接口中读取已缓存的数据。 + .is_started = _port_val_is_started, // 由具体驱动实现的,获取当前数据接口是否可用(是否已启动) + .get_rx_length = _port_val_get_rx_length, // 由具体驱动实现的,获取当前数据接口的本次可读长度 +}; + +static int _port_start_cmd(sb_data_port_t *port) +{ + ble_server_status_t status = ble_server_get_gap_status(); + + do + { + /* port_data->pipe_obj */ + __ble_port_data_t *port_data = s_port_cmd.data; + if (!os_pipe_is_valid(&port_data->pipe_obj)) + { + os_pipe_create(&port_data->pipe_obj, port_data->buffer_size); + os_pipe_regist(&port_data->pipe_obj, port_data->wkup_work_hdl, 0); + } + + } while (0); + + if (status == BLE_SERVER_STATUS_STOP) + { + _ble_spp_server_init(); + + if (s_init_param.connect_cb) + { + s_init_param.connect_cb(ble_server_get_gap_status()); + } + } + + return 0; +} + +static int _port_stop_cmd(sb_data_port_t *port) +{ + bool deinit_flag = false; + + if (!_port_cmd_is_started(&s_port_val)) + { + deinit_flag = true; + } + + if (deinit_flag) + { + if (is_connected) + { + esp_ble_gap_disconnect(spp_remote_bda); + is_connected = false; + } + + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); + } + + do + { + /* port_data->pipe_obj */ + __ble_port_data_t *port_data = s_port_cmd.data; + if (os_pipe_is_valid(&port_data->pipe_obj)) + { + os_pipe_delete(&port_data->pipe_obj); + } + + } while (0); + + if (deinit_flag) + { + if (s_init_param.connect_cb) + { + s_init_param.connect_cb(ble_server_get_gap_status()); + } + } + + return 0; +} + +static int _port_write_cmd(sb_data_port_t *port, const void *data, uint32_t size) +{ + int wtotal = size; + while (size) + { + int wsize = size; + if (wsize > spp_mtu_size - SPP_MTU_HEAD_SIZE) + { + wsize = spp_mtu_size - SPP_MTU_HEAD_SIZE; + } + + if (is_connected) + { + if (esp_ble_gatts_send_indicate(spp_gatts_if, + spp_conn_id, + spp_handle_table[SPP_IDX_SPP_STATUS_VAL], + wsize, + (uint8_t *)data, + false) != ESP_OK) + { + SYS_LOG_WRN("esp_ble_gatts_send_indicate() fail"); + return -1; + } + } + else + { + return -1; + } + + size -= wsize; + data = &((uint8_t *)data)[wsize]; + } + return wtotal; +} + +static int _port_read_cmd(sb_data_port_t *port, void *data, uint32_t size) +{ + __ble_port_data_t *port_data = port->data; + return os_pipe_fifo_read(&port_data->pipe_obj, data, size); +} + +static bool _port_cmd_is_started(sb_data_port_t *port) +{ + __ble_port_data_t *port_data = s_port_cmd.data; + if (port_data == NULL) + { + return false; + } + return os_pipe_is_valid(&port_data->pipe_obj); +} + +static uint32_t _port_cmd_get_rx_length(sb_data_port_t *port) +{ + __ble_port_data_t *port_data = port->data; + return os_pipe_get_valid_size(&port_data->pipe_obj); +} + +static int _port_start_val(sb_data_port_t *port) +{ + ble_server_status_t status = ble_server_get_gap_status(); + + do + { + /* 设置数据通道 FIFO */ + if (!os_fifo_q_is_valid(&s_val_fifo_hdl)) + { + s_val_fifo_cnt = 0; + os_fifo_q_create(&s_val_fifo_hdl); + } + + /* port_data->pipe_obj */ + __ble_port_data_t *port_data = s_port_val.data; + if (!os_pipe_is_valid(&port_data->pipe_obj)) + { + os_pipe_create(&port_data->pipe_obj, port_data->buffer_size); + os_pipe_regist(&port_data->pipe_obj, port_data->wkup_work_hdl, 0); + } + + } while (0); + + if (status == BLE_SERVER_STATUS_STOP) + { + _ble_spp_server_init(); + + if (s_init_param.connect_cb) + { + s_init_param.connect_cb(ble_server_get_gap_status()); + } + } + + return 0; +} + +static int _port_stop_val(sb_data_port_t *port) +{ + bool deinit_flag = false; + + if (!_port_cmd_is_started(&s_port_cmd)) + { + deinit_flag = true; + } + + if (deinit_flag) + { + if (is_connected) + { + esp_ble_gap_disconnect(spp_remote_bda); + is_connected = false; + } + + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); + } + + do + { + /* 设置数据通道 FIFO */ + if (os_fifo_q_is_valid(&s_val_fifo_hdl)) + { + os_fifo_q_delete(&s_val_fifo_hdl); + } + + /* port_data->pipe_obj */ + __ble_port_data_t *port_data = s_port_val.data; + if (os_pipe_is_valid(&port_data->pipe_obj)) + { + os_pipe_delete(&port_data->pipe_obj); + } + + } while (0); + + if (deinit_flag) + { + if (s_init_param.connect_cb) + { + s_init_param.connect_cb(ble_server_get_gap_status()); + } + } + + return 0; +} + +static int _port_write_val(sb_data_port_t *port, const void *data, uint32_t size) +{ + int wtotal = size; + + if (is_connected) + { + while (size) + { + uint32_t wsize = size; + if (wsize > spp_mtu_size - SPP_MTU_HEAD_SIZE) + { + wsize = spp_mtu_size - SPP_MTU_HEAD_SIZE; + } + + if (is_connected) + { + if (esp_ble_gatts_send_indicate(spp_gatts_if, + spp_conn_id, + spp_handle_table[SPP_IDX_SPP_DATA_NTY_VAL], + wsize, + (uint8_t *)data, + false) != ESP_OK) + { + SYS_LOG_WRN("esp_ble_gatts_send_indicate() fail"); + return -1; + } + } + else + { + return -1; + } + + size -= wsize; + data = &((uint8_t *)data)[wsize]; + } + } + + return wtotal; +} + +static int _port_read_val(sb_data_port_t *port, void *data, uint32_t size) +{ + if (data == NULL) // 值为 NULL 表示仅释放空间 + { + os_fifo_q_clr(&s_val_fifo_hdl); + os_interrupt_disable(); + s_val_fifo_cnt = 0; + os_interrupt_enable(); + } + __ble_port_data_t *port_data = port->data; + return os_pipe_fifo_read(&port_data->pipe_obj, data, size); +} + +static bool _port_val_is_started(sb_data_port_t *port) +{ + __ble_port_data_t *port_data = s_port_val.data; + if (port_data == NULL) + { + return false; + } + return os_pipe_is_valid(&port_data->pipe_obj); +} + +static uint32_t _port_val_get_rx_length(sb_data_port_t *port) +{ + __ble_port_data_t *port_data = port->data; + return os_pipe_get_valid_size(&port_data->pipe_obj); +} + +/** + * @brief 初始化数据接口 + * + * @param init_param 初始化参数 + * @return 0 + */ +int sb_ble_server_port_init(ble_server_init_t *init_param) +{ + /* 设置 s_init_param.device_name */ + s_init_param.device_name[sizeof(s_init_param.device_name) - 1] = '\0'; + strncpy(s_init_param.device_name, init_param->device_name, sizeof(s_init_param.device_name) - 1); + + /* 设置剩余的初始化参数 */ + s_init_param.manufacturer_data = init_param->manufacturer_data; + s_init_param.connect_cb = init_param->connect_cb; + + return 0; +} + +/** + * @brief + * + * @param buffer_size 指定接收缓存长度 + * @param wkup_work_hdl 当接收非空时唤醒的工作项, NULL 值可用 + * @return sb_data_port_t* + */ +sb_data_port_t *ble_server_port_bind_cmd(uint32_t buffer_size, os_work_t *wkup_work_hdl) +{ + static __ble_port_data_t s_cmd_data; + + SYS_ASSERT(s_port_cmd.data == NULL, "The interface has already been bound"); + + __ble_port_data_t *port_data = &s_cmd_data; + port_data->buffer_size = buffer_size; + port_data->wkup_work_hdl = wkup_work_hdl; + + s_port_cmd.vtable = &s_port_vtable_cmd; + s_port_cmd.data = &s_cmd_data; + + return &s_port_cmd; +} + +/** + * @brief + * + * @param buffer_size 指定接收缓存长度 + * @param wkup_work_hdl 当接收非空时唤醒的工作项, NULL 值可用 + * @return sb_data_port_t* + */ +sb_data_port_t *ble_server_port_bind_val(uint32_t buffer_size, os_work_t *wkup_work_hdl) +{ + static __ble_port_data_t s_val_data; + + SYS_ASSERT(s_port_val.data == NULL, "The interface has already been bound"); + + __ble_port_data_t *port_data = &s_val_data; + port_data->buffer_size = buffer_size; + port_data->wkup_work_hdl = wkup_work_hdl; + + s_port_val.vtable = &s_port_vtable_val; + s_port_val.data = &s_val_data; + + return &s_port_val; +} + +void ble_server_port_unbind_cmd(sb_data_port_t *port) +{ + if (_port_cmd_is_started(port)) + { + _port_stop_cmd(port); + } + s_port_cmd.data = NULL; +} + +void ble_server_port_unbind_val(sb_data_port_t *port) +{ + if (_port_val_is_started(port)) + { + _port_stop_val(port); + } + s_port_val.data = NULL; +} + +/** + * @brief 获取当前的链接状态 + * + * @return ble_server_status_t + */ +ble_server_status_t ble_server_get_gap_status(void) +{ + if (!_port_cmd_is_started(&s_port_cmd) && !_port_cmd_is_started(&s_port_val)) + { + return BLE_SERVER_STATUS_STOP; + } + + if (is_connected) + { + return BLE_SERVER_STATUS_CONNECTED; + } + + return BLE_SERVER_STATUS_DISCONNECTED; +} + +/** + * @brief 获取 MTU + * + * @return uint32_t + */ +uint32_t ble_server_get_mtu(void) +{ + if (!is_connected) + { + return 0; + } + + return spp_mtu_size - SPP_MTU_HEAD_SIZE; +} + +/** + * @brief 获取 MAC 地址 + * + * @return const uint8_t MAC[6] + */ +const uint8_t *ble_server_get_mac(void) +{ + return s_mac; +} diff --git a/app/drivers/data_port/ble_spp/ble_spp_server.h b/app/drivers/data_port/ble_spp/ble_spp_server.h new file mode 100644 index 0000000..4589e49 --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_server.h @@ -0,0 +1,50 @@ +/** + * @file ble_spp_server.h + * @author LokLiang + * @brief BLE 从机接口(服务端) + * @version 0.1 + * @date 2023-11-01 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include "drivers/data_port/sb_data_port.h" +#include "os/os.h" + +typedef enum +{ + BLE_SERVER_STATUS_STOP, + BLE_SERVER_STATUS_CONNECTED, + BLE_SERVER_STATUS_DISCONNECTED, +} ble_server_status_t; + +typedef void (*ble_server_gen_manufacturer_data_cb)(uint8_t manufacturer_data[20]); +typedef void (*ble_server_connect_cb)(ble_server_status_t); + +typedef struct +{ + const char *device_name; // 设备名 + ble_server_gen_manufacturer_data_cb manufacturer_data; // 回调函数,生成 manufacturer[20] + ble_server_connect_cb connect_cb; // 回调函数, NULL 值可用 +} ble_server_init_t; + +typedef struct +{ + uint32_t buffer_size; // 指定接收缓存长度 + os_work_t *wkup_work_hdl; // 当接收非空时唤醒的工作项, NULL 值可用 +} ble_server_param_t; + +int sb_ble_server_port_init(ble_server_init_t *init_param); + +sb_data_port_t *ble_server_port_bind_cmd(uint32_t buffer_size, os_work_t *wkup_work_hdl); +sb_data_port_t *ble_server_port_bind_val(uint32_t buffer_size, os_work_t *wkup_work_hdl); +void ble_server_port_unbind_cmd(sb_data_port_t *port); +void ble_server_port_unbind_val(sb_data_port_t *port); + +ble_server_status_t ble_server_get_gap_status(void); +uint32_t ble_server_get_mtu(void); + +const uint8_t *ble_server_get_mac(void); diff --git a/app/drivers/data_port/ble_spp/ble_spp_server_define.h b/app/drivers/data_port/ble_spp/ble_spp_server_define.h new file mode 100644 index 0000000..eb648f8 --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_server_define.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + + +#include +#include +#include + +/* + * DEFINES + **************************************************************************************** + */ +//#define SUPPORT_HEARTBEAT +//#define SPP_DEBUG_MODE + +#define SPP_DATA_MAX_LEN (512) +#define SPP_CMD_MAX_LEN (512) +#define SPP_STATUS_MAX_LEN (512) +#define SPP_DATA_BUFF_MAX_LEN (2*1024) +///Attributes State Machine +enum +{ + SPP_IDX_SVC, + + SPP_IDX_SPP_DATA_RECV_CHAR, + SPP_IDX_SPP_DATA_RECV_VAL, + + SPP_IDX_SPP_DATA_NOTIFY_CHAR, + SPP_IDX_SPP_DATA_NTY_VAL, + SPP_IDX_SPP_DATA_NTF_CFG, + + SPP_IDX_SPP_COMMAND_CHAR, + SPP_IDX_SPP_COMMAND_VAL, + + SPP_IDX_SPP_STATUS_CHAR, + SPP_IDX_SPP_STATUS_VAL, + SPP_IDX_SPP_STATUS_CFG, + +#ifdef SUPPORT_HEARTBEAT + SPP_IDX_SPP_HEARTBEAT_CHAR, + SPP_IDX_SPP_HEARTBEAT_VAL, + SPP_IDX_SPP_HEARTBEAT_CFG, +#endif + + SPP_IDX_NB, +}; diff --git a/app/drivers/data_port/ble_spp/ble_spp_server_shell.c b/app/drivers/data_port/ble_spp/ble_spp_server_shell.c new file mode 100644 index 0000000..7675857 --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_server_shell.c @@ -0,0 +1,334 @@ +#include "ble_spp_server_shell.h" +#include "ble_spp_server.h" +#include "shell/sh.h" + +#include +#include "os/os.h" + +#include "drivers/data_port/sb_data_port.h" + +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF +#define SYS_LOG_DOMAIN "SH" +#include "sys_log.h" + +static void _sb_manufacturer_encode(uint8_t manufacturer_data[20]); +static void _ble_connect_cb(ble_server_status_t status); +static void _work_speed_handler(void *arg); +static void _work_read_handler(void *arg); +static int _write_process(sb_data_port_t *data_port, uint32_t size); + +static struct +{ + sb_data_port_t *port_ble_cmd; + sb_data_port_t *port_ble_val; + os_work_t s_work_hdl_cmd; + os_work_t s_work_hdl_val; + os_work_t s_speed_work_hdl; + int s_size_total; + int s_recv_total_cmd; + int s_recv_total_val; +} s_cm; + +SH_CMD_FN(_start); +SH_CMD_FN(_stop); +SH_CMD_FN(_status); +SH_CMD_FN(_write_cmd); +SH_CMD_FN(_write_val); + +SH_DEF_SUB_CMD( + sub_ble, + SH_SETUP_CMD("start", "开始广播 <设备名>", _start, NULL), // + SH_SETUP_CMD("stop", "断开连接并停止广播", _stop, NULL), // + SH_SETUP_CMD("status", "获取当前连接状态", _status, NULL), // + SH_SETUP_CMD("write-cmd", "发送数据到命令通道 <数值|字符串>", _write_cmd, NULL), // + SH_SETUP_CMD("write-val", "发送数据到数据通道 <数值|字符串>", _write_val, NULL), // +); + +SH_DEF_CMD( + _register_cmd_ble, + SH_SETUP_CMD("ble", "操作 BLE 透传数据接口", NULL, sub_ble), // +); + +void ble_server_shell_register(void) +{ + sh_register_cmd(&_register_cmd_ble); + + ble_server_init_t ble_init_param = { + .device_name = "12345", // 设备名 + .manufacturer_data = _sb_manufacturer_encode, // 回调函数,生成 manufacturer[20] + .connect_cb = _ble_connect_cb, // 回调函数, NULL 值可用 + }; + sb_ble_server_port_init(&ble_init_param); + s_cm.port_ble_cmd = ble_server_port_bind_cmd(0x2000, &s_cm.s_work_hdl_cmd); + s_cm.port_ble_val = ble_server_port_bind_val(0x400, &s_cm.s_work_hdl_val); + + sb_data_port_start(s_cm.port_ble_cmd); + sb_data_port_start(s_cm.port_ble_val); + + os_work_create(&s_cm.s_work_hdl_cmd, "work-read", _work_read_handler, s_cm.port_ble_cmd, OS_PRIORITY_LOWEST); + os_work_submit(default_os_work_q_hdl, &s_cm.s_work_hdl_cmd, 0); + + os_work_create(&s_cm.s_work_hdl_val, "work-read", _work_read_handler, s_cm.port_ble_val, OS_PRIORITY_LOWEST); + os_work_submit(default_os_work_q_hdl, &s_cm.s_work_hdl_val, 0); + + os_work_create(&s_cm.s_speed_work_hdl, "work-speed", _work_speed_handler, NULL, OS_PRIORITY_LOWEST); + os_work_submit(default_os_work_q_hdl, &s_cm.s_speed_work_hdl, 0); +} + +void ble_server_shell_unregister(void) +{ + sh_unregister_cmd(&_register_cmd_ble); +} + +static void _sb_manufacturer_encode(uint8_t manufacturer_data[20]) +{ + for (int i = 0; i < 20; i++) + { + manufacturer_data[i] = i + 1; + } +} + +static void _ble_connect_cb(ble_server_status_t status) +{ + sh_echo(&g_uart_handle_vt100, "\r\n"); + _status(&g_uart_handle_vt100, 0, NULL); +} + +static void _work_speed_handler(void *arg) +{ + static os_time_t last_time; + os_time_t curr_time = os_get_sys_time(); + int time_cost = curr_time - last_time; + + os_work_later(1000); + + time_cost += !time_cost; + if (sb_data_port_is_started(s_cm.port_ble_cmd)) + { + if (s_cm.s_size_total) + { + int result = s_cm.s_size_total * 1000 / time_cost; + s_cm.s_recv_total_cmd += s_cm.s_size_total; + SYS_LOG_INF("cmd trans speed: %2u.%02u KiB/S, recv total: %d bytes", result / 0x400, result % 0x400 * 100 / 0x400, s_cm.s_recv_total_cmd); + } + else + { + s_cm.s_recv_total_cmd = 0; + } + s_cm.s_size_total = 0; + } + + if (sb_data_port_is_started(s_cm.port_ble_val)) + { + if (s_cm.s_size_total) + { + int result = s_cm.s_size_total * 1000 / time_cost; + s_cm.s_recv_total_val += s_cm.s_size_total; + SYS_LOG_INF("val trans speed: %2u.%02u KiB/S, recv total: %d bytes", result / 0x400, result % 0x400 * 100 / 0x400, s_cm.s_recv_total_val); + } + else + { + s_cm.s_recv_total_val = 0; + } + s_cm.s_size_total = 0; + } + + last_time = curr_time; +} + +static void _work_read_handler(void *arg) +{ + static uint8_t val_log = '0'; + + while (sb_data_port_is_started(arg)) + { + uint8_t buf[0x100]; + int r_size = sb_data_port_read(arg, buf, sizeof(buf), 0); + if (r_size <= 0) + { + break; + } + else + { + s_cm.s_size_total += r_size; + for (int i = 0; i < r_size; i++) + { + if (buf[i] != val_log) + { + SYS_LOG_WRN("buf[i]: %d != val_log: %d", buf[i], val_log); + val_log = buf[i]; + } + val_log = '0' + (val_log - '0' + 1) % 10; + } + } + } +} + +SH_CMD_FN(_start) +{ + if (argc < 1) + { + sh_echo(sh_hdl, "缺少参数\r\n"); + return -1; + } + + if (argc > 1) + { + sh_echo(sh_hdl, "参数中有空格。如果目标设备名中包含空格,请使用引号表示\r\n"); + return -1; + } + + sb_data_port_start(s_cm.port_ble_cmd); + sb_data_port_start(s_cm.port_ble_val); + return 0; +} + +SH_CMD_FN(_stop) +{ + sb_data_port_stop(s_cm.port_ble_cmd); + sb_data_port_stop(s_cm.port_ble_val); + os_thread_sleep(100); + return 0; +} + +SH_CMD_FN(_status) +{ + static char *const str[] = { + [BLE_SERVER_STATUS_STOP] = "stop", + [BLE_SERVER_STATUS_CONNECTED] = "connected", + [BLE_SERVER_STATUS_DISCONNECTED] = "disconnected", + }; + ble_server_status_t status = ble_server_get_gap_status(); + if (status < sizeof(str) / sizeof(str[0]) && str[status] != NULL) + { + sh_echo(sh_hdl, "connect status: %s\r\n", str[status]); + } + else + { + sh_echo(sh_hdl, "unknow status: %d\r\n", status); + } + return 0; +} + +SH_CMD_FN(_write_cmd) +{ + int ret = -1; + if (argc < 1) + { + sh_echo(sh_hdl, "缺少参数\r\n"); + return -1; + } + + sh_parse_t pv = sh_parse_value(argv[0]); + switch (pv.type) + { + case _PARSE_TYPE_STRING: // 字符串 + ret = sb_data_port_write(s_cm.port_ble_cmd, pv.value.val_string, strlen(pv.value.val_string), 0); + break; + case _PARSE_TYPE_INTEGER: // 带符号整型 + ret = sb_data_port_write(s_cm.port_ble_cmd, &pv.value.val_integer, sizeof(pv.value.val_integer), 0); + break; + case _PARSE_TYPE_UNSIGNED: // 无符号整型 + { + ret = _write_process(s_cm.port_ble_cmd, pv.value.val_unsigned); + break; + } + case _PARSE_TYPE_FLOAT: // 浮点 + ret = sb_data_port_write(s_cm.port_ble_cmd, &pv.value.val_float, sizeof(pv.value.val_float), 0); + break; + + default: + return -1; + } + + os_thread_sleep(50); + return ret; +} + +SH_CMD_FN(_write_val) +{ + int ret = -1; + if (argc < 1) + { + sh_echo(sh_hdl, "缺少参数\r\n"); + return -1; + } + + sh_parse_t pv = sh_parse_value(argv[0]); + switch (pv.type) + { + case _PARSE_TYPE_STRING: // 字符串 + ret = sb_data_port_write(s_cm.port_ble_val, pv.value.val_string, strlen(pv.value.val_string), 0); + break; + case _PARSE_TYPE_INTEGER: // 带符号整型 + ret = sb_data_port_write(s_cm.port_ble_val, &pv.value.val_integer, sizeof(pv.value.val_integer), 0); + break; + case _PARSE_TYPE_UNSIGNED: // 无符号整型 + { + ret = _write_process(s_cm.port_ble_val, pv.value.val_unsigned); + break; + } + case _PARSE_TYPE_FLOAT: // 浮点 + ret = sb_data_port_write(s_cm.port_ble_val, &pv.value.val_float, sizeof(pv.value.val_float), 0); + break; + + default: + return -1; + } + + os_thread_sleep(50); + return ret; +} + +static int _write_process(sb_data_port_t *data_port, uint32_t size) +{ + int ret = -1; + char start_char = '0'; + int w_total = 0; + + while (size) + { + char buf[0x200]; + int mtu = ble_server_get_mtu(); + int w_size = size; + + if (w_size > mtu) + { + w_size = mtu; + } + + for (int i = 0; i < w_size; i++) + { + buf[i] = start_char; + start_char = '0' + (start_char - '0' + 1) % 10; + } + + ret = sb_data_port_write(data_port, &buf, w_size, 0); + if (ret <= 0) + { + for (int r = 0; r < 3; r++) + { + ret = sb_data_port_write(data_port, &buf, w_size, 0); + SYS_LOG_WRN("write success = %u, retry %d", w_total, r + 1); + if (ret > 0) + { + break; + } + os_thread_sleep(50 * (r + 1)); + } + if (ret <= 0) + { + break; + } + } + else + { + os_thread_sleep(20); + } + + size -= w_size; + w_total += w_size; + } + + return ret; +} \ No newline at end of file diff --git a/app/drivers/data_port/ble_spp/ble_spp_server_shell.h b/app/drivers/data_port/ble_spp/ble_spp_server_shell.h new file mode 100644 index 0000000..6a1d524 --- /dev/null +++ b/app/drivers/data_port/ble_spp/ble_spp_server_shell.h @@ -0,0 +1,4 @@ +#pragma once + +void ble_server_shell_register(void); +void ble_server_shell_unregister(void); diff --git a/app/drivers/data_port/socket_inet/socket_inet.c b/app/drivers/data_port/socket_inet/socket_inet.c new file mode 100644 index 0000000..c752b34 --- /dev/null +++ b/app/drivers/data_port/socket_inet/socket_inet.c @@ -0,0 +1,15 @@ +#include "socket_inet.h" + +/** + * @brief 初始化数据接口。 + * 这将一同初始化 ESP32 的 WIFI + * + * @return 0 + */ +int socket_inet_init(void) +{ + extern void socket_inet_server_init(void); + socket_inet_server_init(); + + return 0; +} diff --git a/app/drivers/data_port/socket_inet/socket_inet.h b/app/drivers/data_port/socket_inet/socket_inet.h new file mode 100644 index 0000000..3f9b7a1 --- /dev/null +++ b/app/drivers/data_port/socket_inet/socket_inet.h @@ -0,0 +1,41 @@ +/** + * @file socket_inet.h + * @author LokLiang + * @brief 主要把 sockets 标准接口封装为 sb_data_port.h::sb_data_port_t 接口 + * @version 0.1 + * @date 2023-11-10 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include +#include "drivers/data_port/sb_data_port.h" +#include "os/os.h" + +typedef void *socket_listen_t; +typedef socket_listen_t socket_listen_tcp_t; + +typedef void (*connect_cb)(bool is_connect, sb_data_port_t *port); +typedef void (*rx_event_cb)(sb_data_port_t *port); + +typedef struct +{ + uint16_t rx_buf_size; // 为该接口提供多大的接收缓存(建议取值 256..512,更多依赖协议栈的缓存) + rx_event_cb rx_event; // 当该接口收到数据时执行的回调函数,可取值为 NULL + os_work_t *rx_resume_work; // 当该接口收到数据时唤醒的工作项,可取值为 NULL +} socket_server_bind_t; + +int socket_inet_init(void); + +sb_data_port_t *socket_inet_server_bind_udp(uint16_t listen_port, const socket_server_bind_t *bind_param); +void socket_inet_server_unbind_udp(sb_data_port_t *port); + +int socket_inet_server_udp_read_addr(sb_data_port_t *port, in_addr_t *s_addr, in_port_t *sin_port); +int socket_inet_server_udp_broadcast(sb_data_port_t *port, uint8_t ip_v4[4], uint16_t broadcast_port, const void *data, uint32_t size); + +socket_listen_tcp_t socket_inet_server_listen_tcp(uint16_t listen_port, uint8_t tcp_backlog, const connect_cb connect); +sb_data_port_t *socket_inet_server_bind_tcp(socket_listen_tcp_t listen_tcp, const socket_server_bind_t *bind_param); +void socket_inet_server_unbind_tcp(socket_listen_tcp_t listen_tcp); diff --git a/app/drivers/data_port/socket_inet/socket_inet_server.c b/app/drivers/data_port/socket_inet/socket_inet_server.c new file mode 100644 index 0000000..b4c9c61 --- /dev/null +++ b/app/drivers/data_port/socket_inet/socket_inet_server.c @@ -0,0 +1,1503 @@ +#include "socket_inet.h" + +#include +#include +#include +#include +#include + +#include "os/os.h" +#include "list/slist.h" + +#include "wifi.h" + +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_WRN +#define SYS_LOG_DOMAIN "SOCKET" +#define CONS_ABORT() +#include "sys_log.h" + +#define _THREAD_PRIOR 21 + +/* port ------------------------------------------------------------------------------------------------------------*/ + +typedef struct +{ + struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6 + uint16_t size; + uint8_t data[0]; +} __udp_rx_fifo_t; + +static struct +{ + bool wifi_open_flag; + os_mutex_t mutex_hdl; + slist_t udp_list; // 已启动的 udp 的链,节点 &__port_data_t::start_node + slist_t tcp_list; // 已启动的 tcp 的链,节点 &__port_data_t::start_node +} *p_cm; + +typedef struct +{ + sb_data_port_t port; + slist_t *p_status_list; // 指向 &__port_data_t::bind_list, &__port_data_t::idle_list, &__port_data_t::recv_list, &__port_data_t::close_list + slist_node_t status_node; // 链表 __port_tcp_t::p_status_list + int client_sock; + os_pipe_t rx_pipe; + os_sem_t rx_sem; + socket_server_bind_t local; // 绑定时的设置信息 +} __port_tcp_t; + +typedef struct +{ + sb_data_port_t port; // 数据接口框架 + uint16_t listen_port; // 监听的端口 + int sock; // 监听套接字 + os_thread_t thread_hdl_listen; // 监听服务线程 + slist_node_t start_node; // 用于判断在何时启动或关闭 wifi 服务,链表 &p_cm->udp_list, &p_cm->tcp_list + volatile bool unbind_flag; // 置位时表示在监听线程中回收资源 + + union + { + struct + { + os_fifo_t rx_fifo; // udp 接收缓存 + os_sem_t rx_sem; // 接收缓存读取后唤醒接收线程 + volatile int32_t rx_cnt; // rx_fifo 已缓存的报文数据的总长度 + socket_server_bind_t local; // 绑定时的设置信息 + struct sockaddr_storage dest_addr; // Large enough for both IPv4 or IPv6 + } udp; + + struct + { + slist_t bind_list; // tcp 已绑定链 + slist_t idle_list; // tcp 已设置启动的但未连接的空闲链 + slist_t recv_list; // tcp 已连接的链 + slist_t close_list; // 正在执行关闭数据接口 + os_sem_t idle_sem; // 当 idle_list 被更新时释放信号 + uint16_t backlog_cnt; // tcp 已设置启动的连接计数 + uint16_t tcp_backlog; // 为此套接字维护的最大连接个数 + connect_cb connect; // 连接状态通知 + } tcp; + }; + + __port_tcp_t *tcp_port; + +} __port_data_t; + +static int _port_udp_start(sb_data_port_t *port); +static int _port_udp_stop(sb_data_port_t *port); +static int _port_udp_write(sb_data_port_t *port, const void *data, uint32_t size); +static int _port_udp_read(sb_data_port_t *port, void *data, uint32_t size); +static bool _port_udp_is_started(sb_data_port_t *port); +static uint32_t _port_udp_get_rx_length(sb_data_port_t *port); + +static int _port_tcp_start(sb_data_port_t *port); +static int _port_tcp_stop(sb_data_port_t *port); +static int _port_tcp_write(sb_data_port_t *port, const void *data, uint32_t size); +static int _port_tcp_read(sb_data_port_t *port, void *data, uint32_t size); +static bool _port_tcp_is_started(sb_data_port_t *port); +static uint32_t _port_tcp_get_rx_length(sb_data_port_t *port); + +static sb_data_port_vtable_t const s_port_vtable_udp = { + .start = _port_udp_start, + .stop = _port_udp_stop, + .write = _port_udp_write, + .read = _port_udp_read, + .is_started = _port_udp_is_started, + .get_rx_length = _port_udp_get_rx_length, +}; + +static sb_data_port_vtable_t const s_port_vtable_tcp = { + .start = _port_tcp_start, + .stop = _port_tcp_stop, + .write = _port_tcp_write, + .read = _port_tcp_read, + .is_started = _port_tcp_is_started, + .get_rx_length = _port_tcp_get_rx_length, +}; + +static void _wifi_open(void); +static void _wifi_close(void); +static int _socket_init_udp(__port_data_t *port_data); +static int _socket_init_tcp(__port_data_t *port_data); +static void _socket_close(__port_data_t *port_data); +static int _do_tcp_recv_start(__port_tcp_t *tcp_port); +static void _do_tcp_recv_stop(__port_tcp_t *tcp_port); +static sb_data_port_t *_find_hdl_by_port(uint16_t listen_port); +static sb_data_port_t *_create_port_data(const sb_data_port_vtable_t *vtable, uint16_t listen_port); +static void _thread_handler_udp_listen(void *arg); +static void _thread_handler_tcp_listen(void *arg); +static void _thread_handler_tcp_recv(void *arg); + +static int _port_udp_start(sb_data_port_t *port) +{ + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + int ret = -1; + + __port_data_t *port_data = port->data; + + do + { + if (slist_is_pending(&port_data->start_node)) + { + SYS_LOG_WRN("udp data port already started"); + break; + } + + if (_find_hdl_by_port(port_data->listen_port)) + { + SYS_LOG_WRN("The local listening port (%u) is already occupied", port_data->listen_port); + break; + } + + /* 打开 wifi */ + _wifi_open(); + + /* 创建监听服务线程 */ + if (os_thread_create(&port_data->thread_hdl_listen, + "udp-listen", + _thread_handler_udp_listen, + port_data, + 0x1000, + _THREAD_PRIOR) != OS_OK) + { + SYS_LOG_WRN("_thread_handler_udp_listen() create fail"); + break; + } + + /* 插入已启动的 udp 的链 */ + slist_insert_tail(&p_cm->udp_list, &port_data->start_node); + SYS_LOG_INF("success. stype: udp"); + + /* 操作成功 */ + ret = 0; + + } while (0); + + if (ret != 0) + { + /* 关闭 wifi */ + _wifi_close(); + } + + os_mutex_unlock(&p_cm->mutex_hdl); + + return ret; +} + +static int _port_udp_stop(sb_data_port_t *port) +{ + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + __port_data_t *port_data = port->data; + _socket_close(port_data); + + os_mutex_unlock(&p_cm->mutex_hdl); + + return 0; +} + +static int _port_udp_write(sb_data_port_t *port, const void *data, uint32_t size) +{ + __port_data_t *port_data = port->data; + int err = sendto(port_data->sock, data, size, 0, (struct sockaddr *)&port_data->udp.dest_addr, sizeof(port_data->udp.dest_addr)); + if (err < 0) + { + SYS_LOG_WRN("Error occurred during sending: errno %d", errno); + return err; + } + else + { + SYS_LOG_DBG("size: %d", size); + return size; + } +} + +static int _port_udp_read(sb_data_port_t *port, void *data, uint32_t size) +{ + __port_data_t *port_data = port->data; + if (os_fifo_q_is_valid(&port_data->udp.rx_fifo)) + { + __udp_rx_fifo_t *fifo = os_fifo_take(&port_data->udp.rx_fifo, 0); + if (fifo == NULL) + { + return 0; + } + else + { + if (size > fifo->size) + { + size = fifo->size; + } + if (data) + { + memcpy(data, fifo->data, size); + } + port_data->udp.dest_addr = fifo->source_addr; + os_fifo_free(fifo); + + os_scheduler_suspend(); + port_data->udp.rx_cnt -= fifo->size; + os_scheduler_resume(); + + os_sem_release(&port_data->udp.rx_sem); + return size; + } + } + else + { + return -1; + } +} + +static bool _port_udp_is_started(sb_data_port_t *port) +{ + if (port == NULL) + { + return false; + } + + __port_data_t *port_data = port->data; + if (port_data == NULL) + { + return false; + } + + return (bool)!!slist_is_pending(&port_data->start_node); +} + +static uint32_t _port_udp_get_rx_length(sb_data_port_t *port) +{ + __port_data_t *port_data = port->data; + if (os_fifo_q_is_valid(&port_data->udp.rx_fifo)) + { + __udp_rx_fifo_t *fifo = os_fifo_peek_head(&port_data->udp.rx_fifo); + if (fifo) + { + return fifo->size; + } + } + return 0; +} + +/** + * @brief 打开一个 tcp 数据接口,如果 + * + * @param port + * @return int + */ +static int _port_tcp_start(sb_data_port_t *port) +{ + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + int ret = -1; + + __port_data_t *port_data = port->data; + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(port, __port_tcp_t, port); + + do + { + if (tcp_port->p_status_list != &port_data->tcp.bind_list) + { + SYS_LOG_WRN("tcp data port already started"); + break; + } + + /* 打开 wifi */ + _wifi_open(); + + if (_do_tcp_recv_start(tcp_port) != 0) + { + break; + } + + /* 创建监听服务线程 */ + if (!os_thread_is_valid(&port_data->thread_hdl_listen)) + { + if (_find_hdl_by_port(port_data->listen_port)) + { + SYS_LOG_WRN("The local listening port (%u) is already occupied", port_data->listen_port); + break; + } + + /* 创建接收线程状态信号量 */ + if (os_sem_create(&port_data->tcp.idle_sem, 0, 1) != OS_OK) // 当 idle_list 被更新时释放信号 + break; + + if (os_thread_create(&port_data->thread_hdl_listen, + "tcp-listen", + _thread_handler_tcp_listen, + port_data, + 2400, + _THREAD_PRIOR) != OS_OK) + { + SYS_LOG_WRN("_thread_handler_tcp_listen() create fail"); + /* 删除接收线程状态信号量 */ + os_sem_delete(&port_data->tcp.idle_sem); + break; + } + + /* 插入已启动的 tcp 的链 */ + slist_insert_tail(&p_cm->tcp_list, &port_data->start_node); + SYS_LOG_INF("success. stype: tcp"); + } + + /* 操作成功 */ + ret = 0; + + } while (0); + + if (ret != 0) + { + _do_tcp_recv_stop(tcp_port); + + /* 关闭 wifi */ + _wifi_close(); + } + + os_mutex_unlock(&p_cm->mutex_hdl); + + return ret; +} + +static int _port_tcp_stop(sb_data_port_t *port) +{ + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(port, __port_tcp_t, port); + _do_tcp_recv_stop(tcp_port); + + os_mutex_unlock(&p_cm->mutex_hdl); + + return 0; +} + +static int _port_tcp_write(sb_data_port_t *port, const void *data, uint32_t size) +{ + __port_data_t *port_data = port->data; + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(port, __port_tcp_t, port); + + if (tcp_port->p_status_list != &port_data->tcp.recv_list) + { + return 0; + } + + if (tcp_port->client_sock != -1) + { + int err = send(tcp_port->client_sock, data, size, 0); + if (err < 0) + { + SYS_LOG_WRN("Error occurred during sending: errno %d", errno); + return err; + } + else + { + SYS_LOG_DBG("size: %d", size); + return size; + } + } + return -1; +} + +static int _port_tcp_read(sb_data_port_t *port, void *data, uint32_t size) +{ + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(port, __port_tcp_t, port); + if (os_pipe_is_valid(&tcp_port->rx_pipe)) + { + int ret = os_pipe_fifo_read(&tcp_port->rx_pipe, data, size); + if (ret > 0) + { + os_sem_release(&tcp_port->rx_sem); + } + return ret; + } + else + { + return -1; + } +} + +static bool _port_tcp_is_started(sb_data_port_t *port) +{ + if (port == NULL) + { + return false; + } + + __port_data_t *port_data = port->data; + if (port_data == NULL) + { + return false; + } + + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(port, __port_tcp_t, port); + if (tcp_port->p_status_list == &port_data->tcp.recv_list || + tcp_port->p_status_list == &port_data->tcp.idle_list) + { + return true; + } + else + { + return false; + } +} + +static uint32_t _port_tcp_get_rx_length(sb_data_port_t *port) +{ + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(port, __port_tcp_t, port); + if (os_pipe_is_valid(&tcp_port->rx_pipe)) + { + return os_pipe_get_valid_size(&tcp_port->rx_pipe); + } + else + { + return 0; + } +} + +/** + * @brief 根据 p_cm->udp_list 和 p_cm->tcp_list 的情况确定打开 wifi + * + */ +static void _wifi_open(void) +{ + if (p_cm->wifi_open_flag == false) + { + if (slist_peek_head(&p_cm->udp_list) == NULL && slist_peek_head(&p_cm->tcp_list) == NULL) + { + p_cm->wifi_open_flag = true; + wifi_start(); + } + } +} + +/** + * @brief 根据 p_cm->udp_list 和 p_cm->tcp_list 的情况确定打关闭 wifi + * + */ +static void _wifi_close(void) +{ + if (p_cm->wifi_open_flag != false) + { + if (slist_peek_head(&p_cm->udp_list) == NULL && slist_peek_head(&p_cm->tcp_list) == NULL) + { + p_cm->wifi_open_flag = false; + wifi_stop(); + } + } +} + +/** + * @brief 初始化 socket 并绑定本地地址 + * + * @param port + * @return int + */ +static int _socket_init_udp(__port_data_t *port_data) +{ + SYS_ASSERT(port_data->sock == -1, ""); + + struct sockaddr_storage dest_addr; + memset(&dest_addr, 0, sizeof(dest_addr)); + struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr; + dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY); + dest_addr_ip4->sin_family = AF_INET; + dest_addr_ip4->sin_port = htons(port_data->listen_port); + + port_data->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (port_data->sock < 0) + { + SYS_LOG_ERR("Unable to create socket: errno %d", errno); + port_data->sock = -1; + return -1; + } + SYS_LOG_INF("Socket created. sock = %u", port_data->sock); + + int err = bind(port_data->sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (err < 0) + { + SYS_LOG_ERR("Socket unable to bind: errno %d", errno); + close(port_data->sock); + port_data->sock = -1; + return -1; + } + SYS_LOG_INF("Socket bound, port %d", port_data->listen_port); + + return 0; +} + +/** + * @brief 初始化 socket 并绑定本地地址 + * + * @param port + * @return int + */ +static int _socket_init_tcp(__port_data_t *port_data) +{ + SYS_ASSERT(port_data->sock == -1, ""); + + struct sockaddr_storage dest_addr; + memset(&dest_addr, 0, sizeof(dest_addr)); + struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr; + dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY); + dest_addr_ip4->sin_family = AF_INET; + dest_addr_ip4->sin_port = htons(port_data->listen_port); + + port_data->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (port_data->sock < 0) + { + SYS_LOG_ERR("Unable to create socket: errno %d", errno); + port_data->sock = -1; + return -1; + } + setsockopt(port_data->sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); + + SYS_LOG_INF("Socket created. sock = %u", port_data->sock); + + int err = bind(port_data->sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); + if (err != 0) + { + SYS_LOG_ERR("Socket unable to bind: errno %d", errno); + close(port_data->sock); + port_data->sock = -1; + return -1; + } + SYS_LOG_INF("Socket bound, port %d", port_data->listen_port); + + err = listen(port_data->sock, port_data->tcp.tcp_backlog); + if (err != 0) + { + SYS_LOG_ERR("Error occurred during listen: errno %d", errno); + close(port_data->sock); + port_data->sock = -1; + return -1; + } + + return 0; +} + +/** + * @brief 关闭 socket + * + * @param port_data + */ +static void _socket_close(__port_data_t *port_data) +{ + if (port_data->sock != -1) + { + SYS_LOG_INF("close socket %d", port_data->sock); + close(port_data->sock); + port_data->sock = -1; + } +} + +static int _do_tcp_recv_start(__port_tcp_t *tcp_port) +{ + __port_data_t *port_data = tcp_port->port.data; + + /* 创建接收线程缓存信号量 */ + if (os_sem_create(&tcp_port->rx_sem, 0, 1) != OS_OK) + return -1; + + /* 创建接收线程内部缓存 */ + if (os_pipe_create(&tcp_port->rx_pipe, tcp_port->local.rx_buf_size) != OS_OK) + return -1; + os_pipe_regist(&tcp_port->rx_pipe, tcp_port->local.rx_resume_work, 0); + + /* 初始化连接套按字 */ + tcp_port->client_sock = -1; + + /* 插入到空闲链 */ + slist_remove(tcp_port->p_status_list, &tcp_port->status_node); + slist_insert_tail(&port_data->tcp.idle_list, &tcp_port->status_node); + tcp_port->p_status_list = &port_data->tcp.idle_list; + + if (os_sem_is_valid(&port_data->tcp.idle_sem)) + os_sem_release(&port_data->tcp.idle_sem); // 释放状态变更信号 + + return 0; +} + +static void _do_tcp_recv_stop(__port_tcp_t *tcp_port) +{ + __port_data_t *port_data = tcp_port->port.data; + + slist_remove(tcp_port->p_status_list, &tcp_port->status_node); + if (tcp_port->p_status_list != &port_data->tcp.close_list) + { + slist_insert_tail(&port_data->tcp.bind_list, &tcp_port->status_node); + tcp_port->p_status_list = &port_data->tcp.bind_list; + } + + if (os_sem_is_valid(&port_data->tcp.idle_sem)) + os_sem_release(&port_data->tcp.idle_sem); // 释放状态变更信号 + + /* 关闭连接套按字 */ + if (tcp_port->client_sock != -1) + { + SYS_LOG_WRN("shutdown client sock %d", tcp_port->client_sock); + shutdown(tcp_port->client_sock, SHUT_RDWR); + } + else // 未在 _thread_handler_tcp_listen() 中建立接收连接,直接删除内部缓存 + { + /* 删除接收线程内部缓存 */ + if (os_pipe_is_valid(&tcp_port->rx_pipe)) + os_pipe_delete(&tcp_port->rx_pipe); + + /* 删除接收线程缓存信号量 */ + if (os_sem_is_valid(&tcp_port->rx_sem)) + os_sem_delete(&tcp_port->rx_sem); + } + + if (slist_peek_head(&port_data->tcp.recv_list) == NULL && slist_peek_head(&port_data->tcp.idle_list) == NULL) // 所有接口已关闭 + { + /* 关闭 socket */ + _socket_close(port_data); + } +} + +/** + * @brief 内部函数,查询本地商品对应的句柄 + * + * @param listen_port + * @retval sb_data_port_t 数据接口 + */ +static sb_data_port_t *_find_hdl_by_port(uint16_t listen_port) +{ + sb_data_port_t *port = NULL; + __port_data_t *port_data; + + SYS_ASSERT(p_cm, ""); + + if (port == NULL) + { + SLIST_FOR_EACH_CONTAINER(&p_cm->udp_list, port_data, start_node) + { + if (port_data->listen_port == listen_port) + { + port = SLIST_CONTAINER_OF(port_data, sb_data_port_t, data); + break; + } + } + } + + if (port == NULL) + { + SLIST_FOR_EACH_CONTAINER(&p_cm->tcp_list, port_data, start_node) + { + if (port_data->listen_port == listen_port) + { + port = SLIST_CONTAINER_OF(port_data, sb_data_port_t, data); + break; + } + } + } + + return port; +} + +/** + * @brief 插入到绑定链中 + * + * @param port_data + * @return sb_data_port_t * + */ +static sb_data_port_t *_create_port_data(const sb_data_port_vtable_t *vtable, uint16_t listen_port) +{ + sb_data_port_t *port = NULL; + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + __port_data_t *port_data = os_malloc(sizeof(__port_data_t)); + if (port_data) + { + memset(port_data, 0, sizeof(__port_data_t)); + + port_data->port.vtable = vtable; + port_data->port.data = port_data; + port_data->listen_port = listen_port; + port_data->sock = -1; + slist_init_node(&port_data->start_node); + + port = &port_data->port; + } + + os_mutex_unlock(&p_cm->mutex_hdl); + + return port; +} + +/** + * @brief 内部线程, udp 的服务线程,将用于轮询接收到的数据 + * + * @param arg __port_data_t *port_data + */ +static void _thread_handler_udp_listen(void *arg) +{ + __port_data_t *port_data = arg; + uint8_t rx_buffer[1500]; + int err = -1; + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + do + { + /* 初始化 socket */ + if (_socket_init_udp(port_data) != 0) + { + break; + } + + /* 创建信号量 */ + if (os_sem_create(&port_data->udp.rx_sem, 0, 1) != OS_OK) + { + break; + } + + /* 创建内部缓存 */ + if (os_fifo_q_create(&port_data->udp.rx_fifo) != OS_OK) + { + break; + } + os_fifo_q_regist(&port_data->udp.rx_fifo, port_data->udp.local.rx_resume_work, 0); + port_data->udp.rx_cnt = 0; + + err = 0; + + } while (0); + + os_mutex_unlock(&p_cm->mutex_hdl); + + if (err == 0) + { + SYS_LOG_INF("start"); + + for (;;) + { + struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6 + socklen_t socklen = sizeof(source_addr); + SYS_LOG_DBG("Waiting for data"); + int len = recvfrom(port_data->sock, rx_buffer, sizeof(rx_buffer), 0, (struct sockaddr *)&source_addr, &socklen); + + // Error occurred during receiving + if (len < 0) + { + if (errno != 128) // #define EKEYREVOKED 128 /* Key has been revoked */ + { + SYS_LOG_ERR("abort receiving: errno %d", errno); + } + break; + } + else // Data received + { + /* 打印调试信息 */ + struct + { + uint8_t ip_v4[4]; + uint16_t port; + } remote_info; + struct sockaddr_in *source_addr_ipv4 = (struct sockaddr_in *)&source_addr; + *(uint32_t *)remote_info.ip_v4 = *(uint32_t *)&source_addr_ipv4->sin_addr; + remote_info.port = htons(source_addr_ipv4->sin_port); + SYS_LOG_DBG("Received %d bytes from %u.%u.%u.%u:%u", + len, + remote_info.ip_v4[0], + remote_info.ip_v4[1], + remote_info.ip_v4[2], + remote_info.ip_v4[3], + remote_info.port); + SYS_LOG_DUMP(rx_buffer, len, 0, 0); + + /* 等待接收缓存可用 */ + while (port_data->udp.rx_cnt >= port_data->udp.local.rx_buf_size) + { + os_sem_take(&port_data->udp.rx_sem, OS_WAIT_FOREVER); + } + + /* 保存接收数据 */ + __udp_rx_fifo_t *fifo = os_fifo_alloc(sizeof(__udp_rx_fifo_t) + len); + if (fifo) + { + os_scheduler_suspend(); + port_data->udp.rx_cnt += len; + os_scheduler_resume(); + + fifo->source_addr = source_addr; + fifo->size = len; + memcpy(fifo->data, rx_buffer, len); + if (os_fifo_put(&port_data->udp.rx_fifo, fifo) != OS_OK) + { + os_fifo_free(fifo); + } + + if (port_data->udp.local.rx_event) + { + port_data->udp.local.rx_event(&port_data->port); + } + } + } + } + } + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + SYS_LOG_INF("close"); + + /* 从已启动的 udp 的链移除节点 */ + if (slist_is_pending(&port_data->start_node)) + slist_remove(&p_cm->udp_list, &port_data->start_node); + + /* 删除内部缓存 */ + if (os_fifo_q_is_valid(&port_data->udp.rx_fifo)) + os_fifo_q_delete(&port_data->udp.rx_fifo); + + /* 删除信号量 */ + if (os_sem_is_valid(&port_data->udp.rx_sem)) + os_sem_delete(&port_data->udp.rx_sem); + + /* 关闭 wifi */ + _wifi_close(); + + if (port_data->unbind_flag) + { + SYS_LOG_INF("free binding memory"); + port_data->unbind_flag = false; + os_free(port_data); + } + + os_mutex_unlock(&p_cm->mutex_hdl); + + os_thread_delete(NULL); +} + +/** + * @brief 内部线程, tcp 的服务线程,将用于轮询接收到的数据 + * + * @param arg __port_data_t *port_data + */ +static void _thread_handler_tcp_listen(void *arg) +{ + __port_data_t *port_data = arg; + int err = -1; + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + do + { + /* 初始化 socket */ + if (_socket_init_tcp(port_data) != 0) + { + break; + } + + err = 0; + + } while (0); + + os_mutex_unlock(&p_cm->mutex_hdl); + + if (err == 0) + { + SYS_LOG_INF("start"); + + for (;;) + { + __port_tcp_t *tcp_port = NULL; + + /* 等待有空闲的线程 */ + for (;;) + { + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + slist_node_t *status_node = slist_peek_head(&port_data->tcp.idle_list); + if (status_node) + { + tcp_port = SLIST_CONTAINER_OF(status_node, __port_tcp_t, status_node); + os_mutex_unlock(&p_cm->mutex_hdl); + break; + } + else + { + if (slist_peek_head(&port_data->tcp.recv_list)) + { + os_mutex_unlock(&p_cm->mutex_hdl); + + os_sem_take(&port_data->tcp.idle_sem, OS_WAIT_FOREVER); + } + else // 所有接口已关闭 + { + SYS_LOG_WRN("all tcp recv has been stoped"); + os_mutex_unlock(&p_cm->mutex_hdl); + break; + } + } + } + + if (port_data->sock == -1) + { + SYS_LOG_WRN("socket has been closed"); + break; + } + + struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6 + socklen_t socklen = sizeof(source_addr); + SYS_LOG_INF("Socket listening"); + int client_sock = accept(port_data->sock, (struct sockaddr *)&source_addr, &socklen); + if (client_sock < 0) + { + if (errno != 128) // #define EKEYREVOKED 128 /* Key has been revoked */ + { + SYS_LOG_ERR("abort listening: errno %d", errno); + } + break; + } + else + { + /* Set tcp keepalive option */ + setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, &(int){1}, sizeof(int)); + // setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPIDLE, &(int){1}, sizeof(int)); + // setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPINTVL, &(int){1}, sizeof(int)); + // setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPCNT, &(int){1}, sizeof(int)); + + /* 打印调试信息 */ + struct + { + uint8_t ip_v4[4]; + uint16_t port; + } remote_info; + struct sockaddr_in *source_addr_ipv4 = (struct sockaddr_in *)&source_addr; + *(uint32_t *)remote_info.ip_v4 = *(uint32_t *)&source_addr_ipv4->sin_addr; + remote_info.port = htons(source_addr_ipv4->sin_port); + SYS_LOG_INF("Socket accepted client socket %d, ip address: %u.%u.%u.%u:%u", + client_sock, + remote_info.ip_v4[0], + remote_info.ip_v4[1], + remote_info.ip_v4[2], + remote_info.ip_v4[3], + remote_info.port); + + /* 等待有空闲的线程 */ + for (;;) + { + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + slist_node_t *status_node = slist_peek_head(&port_data->tcp.idle_list); + if (status_node) + { + tcp_port = SLIST_CONTAINER_OF(status_node, __port_tcp_t, status_node); + + /* 设置连接套接字 */ + tcp_port->client_sock = client_sock; + + /* 创建接收线程 */ + if (os_thread_create(NULL, + "tcp-recv", + _thread_handler_tcp_recv, + tcp_port, + 2400, + _THREAD_PRIOR) == OS_OK) + { + /* 设为接收状态 */ + slist_remove(tcp_port->p_status_list, &tcp_port->status_node); + slist_insert_tail(&port_data->tcp.recv_list, &tcp_port->status_node); + tcp_port->p_status_list = &port_data->tcp.recv_list; + } + else + { + SYS_LOG_WRN("_thread_handler_tcp_recv() create fail"); + + /* 重置连接套接字 */ + tcp_port->client_sock = -1; + } + + os_mutex_unlock(&p_cm->mutex_hdl); + break; + } + else + { + if (slist_peek_head(&port_data->tcp.recv_list)) + { + os_mutex_unlock(&p_cm->mutex_hdl); + + os_sem_take(&port_data->tcp.idle_sem, OS_WAIT_FOREVER); + } + else // 所有接口已关闭 + { + SYS_LOG_WRN("all tcp recv has been stoped, force shutdown and close client sock %d", client_sock); + shutdown(client_sock, SHUT_RDWR); + close(client_sock); + os_mutex_unlock(&p_cm->mutex_hdl); + break; + } + } + } + } + } + } + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + SYS_LOG_INF("close"); + + /* 从已启动的 tcp 的链移除节点 */ + if (slist_is_pending(&port_data->start_node)) + slist_remove(&p_cm->tcp_list, &port_data->start_node); + + /* 关闭 socket */ + _socket_close(port_data); + + /* 关闭 wifi */ + _wifi_close(); + + /* 删除信号量 */ + if (os_sem_is_valid(&port_data->tcp.idle_sem)) + os_sem_delete(&port_data->tcp.idle_sem); + + if (port_data->unbind_flag) + { + SYS_LOG_INF("free binding memory"); + port_data->unbind_flag = false; + os_free(port_data); + } + + os_mutex_unlock(&p_cm->mutex_hdl); + + os_thread_delete(NULL); +} + +/** + * @brief 内部函数,由 _thread_handler_tcp_listen() 在获得一个连接时创建 + * + * @param arg __port_tcp_t *tcp_port = arg + */ +static void _thread_handler_tcp_recv(void *arg) +{ + __port_tcp_t *tcp_port = arg; + __port_data_t *port_data = tcp_port->port.data; + int err = 0; + + SYS_LOG_INF("start receiving client socket %d", tcp_port->client_sock); + + if (err == 0) + { + if (port_data->tcp.connect) + { + port_data->tcp.connect(true, &tcp_port->port); + } + + for (;;) + { + void *empty_base; + size_t empty_size; + do + { + os_pipe_peek_empty(&tcp_port->rx_pipe, &empty_base, &empty_size); + if (empty_size == 0) + { + os_sem_take(&tcp_port->rx_sem, OS_WAIT_FOREVER); + } + } while (empty_size == 0); + + int len = recv(tcp_port->client_sock, empty_base, empty_size, 0); + if (len > 0) + { + os_pipe_fifo_fill(&tcp_port->rx_pipe, NULL, len); + + if (tcp_port->local.rx_event) + { + tcp_port->local.rx_event(&tcp_port->port); + } + } + else if (len == 0) + { + SYS_LOG_INF("Connection closed"); + break; + } + else + { + if (errno != 128) // #define EKEYREVOKED 128 /* Key has been revoked */ + { + SYS_LOG_ERR("abort receiving: errno %d", errno); + } + break; + } + } + + if (port_data->tcp.connect) + { + port_data->tcp.connect(false, &tcp_port->port); + } + } + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + /* 关闭连接套接字 */ + if (tcp_port->client_sock != -1) + { + close(tcp_port->client_sock); + tcp_port->client_sock = -1; + } + + /* 设置状态 */ + do + { + slist_remove(tcp_port->p_status_list, &tcp_port->status_node); + if (tcp_port->p_status_list == &port_data->tcp.recv_list) // 连接被无端正常关闭 + { + slist_insert_tail(&port_data->tcp.idle_list, &tcp_port->status_node); + tcp_port->p_status_list = &port_data->tcp.idle_list; + break; + } + else if (tcp_port->p_status_list == &port_data->tcp.bind_list) // 已设置停止 + { + slist_insert_tail(&port_data->tcp.bind_list, &tcp_port->status_node); + tcp_port->p_status_list = &port_data->tcp.bind_list; + } + else if (tcp_port->p_status_list == &port_data->tcp.close_list) // 已设置关闭 + { + /* 执行 socket_inet_server_bind_tcp() 的反过程 */ + os_free(tcp_port); + port_data->tcp.backlog_cnt--; + } + + /* 删除接收线程内部缓存 */ + if (os_pipe_is_valid(&tcp_port->rx_pipe)) + os_pipe_delete(&tcp_port->rx_pipe); + + /* 删除接收线程缓存信号量 */ + if (os_sem_is_valid(&tcp_port->rx_sem)) + os_sem_delete(&tcp_port->rx_sem); + + } while (0); + + if (slist_peek_head(&port_data->tcp.recv_list) == NULL && slist_peek_head(&port_data->tcp.idle_list) == NULL) // 所有接口已关闭 + { + /* 关闭 socket */ + _socket_close(port_data); + } + + if (os_sem_is_valid(&port_data->tcp.idle_sem)) + os_sem_release(&port_data->tcp.idle_sem); // 释放状态变更信号 + + os_mutex_unlock(&p_cm->mutex_hdl); + + os_thread_delete(NULL); +} + +/* export ----------------------------------------------------------------------------------------------------------*/ + +/** + * @brief 由 socket_inet.c 调用 + */ +void socket_inet_server_init(void) +{ + if (p_cm) + { + SYS_LOG_WRN("repeated init"); + return; + } + p_cm = os_malloc(sizeof(*p_cm)); + if (p_cm == NULL) + { + SYS_LOG_WRN("Insufficient memory"); + return; + } + memset(p_cm, 0, sizeof(*p_cm)); + + os_mutex_create(&p_cm->mutex_hdl); + slist_init_list(&p_cm->udp_list); + slist_init_list(&p_cm->tcp_list); +} + +/** + * @brief 创建一个 udp 本地服务监听端口,同时为已启动监听的端口绑定一个数据接口。 + * 可监听的本地端口数量不限,但不能与任何已监听的本地端口重复。 + * 每个本地端口对应一个线程 _thread_handler_udp_listen() ,每当收到的客户端的报文时,远端信息和报文数据都在这个线程中记录并缓存到内部已绑定的的 fifo 中, + * 注意:使用 sb_data_port_read() 读取报文数据时,不管指定的缓存是否能全部读取完成,这个报文数据都会被释放。 + * + * @param listen_port 本地监听端口 + * @param bind_param + * @return sb_data_port_t* + */ +sb_data_port_t *socket_inet_server_bind_udp(uint16_t listen_port, const socket_server_bind_t *bind_param) +{ + SYS_ASSERT(p_cm, "Initialization not performed using socket_inet_init()"); + + sb_data_port_t *port = _create_port_data(&s_port_vtable_udp, listen_port); + if (port) + { + __port_data_t *port_data = port->data; + port_data->udp.local = *bind_param; // 绑定时的设置信息 + + SYS_LOG_INF("bind udp: %p", port); + } + return port; +} + +/** + * @brief 关闭由 socket_inet_server_bind_udp() 创建监听的端口。 + * + * @param port + */ +void socket_inet_server_unbind_udp(sb_data_port_t *port) +{ + if (port) + { + __port_data_t *port_data = SLIST_CONTAINER_OF(port, __port_data_t, port); + port_data->unbind_flag = true; + + if (port->vtable->is_started(port)) + { + SYS_LOG_WRN("Data interface started, forcing close udp %p", port); + + port->vtable->stop(port); + } + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + if (!os_thread_is_valid(&port_data->thread_hdl_listen)) + { + if (port_data->unbind_flag) + { + port_data->unbind_flag = false; + os_free(port_data); + } + } + + os_mutex_unlock(&p_cm->mutex_hdl); + } +} + +/** + * @brief 获取上次通过 sb_data_port_read() 读取到的远端的地址信息 + * + * @param port 通过 socket_inet_server_unbind_udp() 绑定的数据接口 + * @param s_addr[out] 输出远端 IP + * @param sin_port[out] 输出远端端口 + * @retval 0 成功 + * @retval -1 接口未启动 + */ +int socket_inet_server_udp_read_addr(sb_data_port_t *port, in_addr_t *s_addr, in_port_t *sin_port) +{ + if (_port_udp_is_started(port)) + { + __port_data_t *port_data = port->data; + struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&port_data->udp.dest_addr; + if (s_addr) + *s_addr = dest_addr_ip4->sin_addr.s_addr; + if (sin_port) + *sin_port = dest_addr_ip4->sin_port; + return 0; + } + else + { + return -1; + } +} + +/** + * @brief 发送广播包 + * + * @param port 通过 socket_inet_server_unbind_udp() 绑定的数据接口 + * @param ip_v4 远端 IP 地址 + * @param broadcast_port 远端端口 + * @param data 数据 + * @param size 数据长度(字节) + * @retval >= 0 发送长度 + * @retval < 0 数据接口未启动或错误 + */ +int socket_inet_server_udp_broadcast(sb_data_port_t *port, uint8_t ip_v4[4], uint16_t broadcast_port, const void *data, uint32_t size) +{ + if (_port_udp_is_started(port)) + { + if (size == 0) + { + return 0; + } + + struct sockaddr_storage dest_addr; // Large enough for both IPv4 or IPv6 + socklen_t socklen = sizeof(dest_addr); + struct sockaddr_in *dest_addr_ipv4 = (struct sockaddr_in *)&dest_addr; + dest_addr_ipv4->sin_family = AF_INET; + dest_addr_ipv4->sin_port = htons(broadcast_port); + dest_addr_ipv4->sin_len = sizeof(struct sockaddr_in); + *(uint32_t *)&dest_addr_ipv4->sin_addr.s_addr = *(uint32_t *)ip_v4; + + __port_data_t *port_data = port->data; + int err = sendto(port_data->sock, data, size, 0, (struct sockaddr *)&dest_addr, socklen); + if (err < 0) + { + return -1; + } + else + { + SYS_LOG_DBG("size: %d", size); + return size; + } + } + else + { + return -1; + } +} + +/** + * @brief 创建一个 tcp 本地服务监听端口。 + * 每个已监听的本地端口仅允许使用 socket_inet_server_bind_tcp() 绑定一个接收接口,数量由 tcp_backlog 确定。 + * 可监听的本地端口数量不限,但不能与任何已监听的本地端口重复。 + * 每个本地端口对应一个线程 _thread_handler_tcp_listen() ,每当收到客户端的连接请求时,内部将根据 socket_inet_server_bind_tcp() 的次数,自动创建一个用于接收数据的线程 _thread_handler_tcp_recv(). + * 当客户端断开连接时 _thread_handler_tcp_recv() 自动关闭。 + * + * @param listen_port 本地监听端口 + * @param tcp_backlog 允许这个端口的最大连个接数 + * @param connect 当监听到连接被变更时执行的回调函数,可设为 NULL 。其中参数 is_connect 为真值时表示已连接,为假值时表示连接已断开 + * + * @return socket_listen_tcp_t * + */ +socket_listen_tcp_t socket_inet_server_listen_tcp(uint16_t listen_port, uint8_t tcp_backlog, const connect_cb connect) +{ + SYS_ASSERT(p_cm, "Initialization not performed using socket_inet_init()"); + + if (tcp_backlog == 0) + { + SYS_LOG_WRN("fail. tcp_backlog = 0"); + return NULL; + } + sb_data_port_t *port = _create_port_data(&s_port_vtable_tcp, listen_port); + if (port != NULL) + { + __port_data_t *port_data = port->data; + slist_init_list(&port_data->tcp.bind_list); // tcp 已绑定链 + slist_init_list(&port_data->tcp.idle_list); // tcp 已设置启动的但未连接的空闲链 + slist_init_list(&port_data->tcp.recv_list); // tcp 已设连接的链 + slist_init_list(&port_data->tcp.close_list); // 正在执行关闭数据接口 + port_data->tcp.tcp_backlog = tcp_backlog; // 为此套接字维护的最大连接个数 + port_data->tcp.connect = connect; // 连接状态通知 + } + return (socket_listen_tcp_t)port; +} + +/** + * @brief 为已启动监听的端口绑定一个数据接口。可绑定的数量由 socket_inet_server_listen_tcp() 确定 + * + * @param listen_tcp 由 socket_inet_server_listen_tcp() 创建的监听端口 + * @param bind_param + * @return sb_data_port_t* + */ +sb_data_port_t *socket_inet_server_bind_tcp(socket_listen_tcp_t listen_tcp, const socket_server_bind_t *bind_param) +{ + SYS_ASSERT(listen_tcp, "Not initialized using socket socket_inet_server_listen_tcp()"); + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + sb_data_port_t *port = listen_tcp; + __port_data_t *port_data = port->data; + __port_tcp_t *tcp_port = NULL; + if (port_data->tcp.backlog_cnt < port_data->tcp.tcp_backlog) + { + port_data->tcp.backlog_cnt++; + + tcp_port = os_malloc(sizeof(__port_tcp_t)); + if (tcp_port) + { + memset(tcp_port, 0, sizeof(*tcp_port)); + + slist_init_node(&tcp_port->status_node); + tcp_port->port.vtable = port->vtable; + tcp_port->port.data = port_data; + tcp_port->client_sock = -1; + tcp_port->local = *bind_param; // 绑定时的设置信息 + + slist_insert_tail(&port_data->tcp.bind_list, &tcp_port->status_node); + tcp_port->p_status_list = &port_data->tcp.bind_list; + + SYS_LOG_INF("bind tcp %u: %p", port_data->tcp.backlog_cnt, tcp_port); + } + } + else + { + SYS_LOG_ERR("Exceeding the set quantity: tcp_backlog = %u", port_data->tcp.tcp_backlog); + } + + os_mutex_unlock(&p_cm->mutex_hdl); + + return (sb_data_port_t *)tcp_port; +} + +/** + * @brief 关闭由 socket_inet_server_listen_tcp() 创建监听的端口。 + * + * @param listen_tcp + */ +void socket_inet_server_unbind_tcp(socket_listen_tcp_t listen_tcp) +{ + sb_data_port_t *port = listen_tcp; + if (port) + { + __port_data_t *port_data = port->data; + port_data->unbind_flag = true; + + os_mutex_lock(&p_cm->mutex_hdl, OS_WAIT_FOREVER); + + for (;;) + { + slist_node_t *status_node = slist_take_head(&port_data->tcp.bind_list); + if (status_node) + { + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(status_node, __port_tcp_t, status_node); + SYS_LOG_INF("closing stoped tcp port %p", tcp_port); + _do_tcp_recv_stop(tcp_port); + os_free(tcp_port); + } + else + { + break; + } + } + + for (;;) + { + slist_node_t *status_node = slist_take_head(&port_data->tcp.idle_list); + if (status_node) + { + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(status_node, __port_tcp_t, status_node); + SYS_LOG_INF("closing idle tcp port %p", tcp_port); + _do_tcp_recv_stop(tcp_port); + os_free(tcp_port); + } + else + { + break; + } + } + + for (;;) + { + slist_node_t *status_node = slist_take_head(&port_data->tcp.recv_list); + if (status_node) + { + __port_tcp_t *tcp_port = SLIST_CONTAINER_OF(status_node, __port_tcp_t, status_node); + SYS_LOG_WRN("Data interface started, forcing close tcp %p", tcp_port); + slist_insert_tail(&port_data->tcp.close_list, &tcp_port->status_node); + tcp_port->p_status_list = &port_data->tcp.close_list; + _do_tcp_recv_stop(tcp_port); + } + else + { + break; + } + } + + /* 关闭 socket */ + _socket_close(port_data); + + if (!os_thread_is_valid(&port_data->thread_hdl_listen)) + { + if (port_data->unbind_flag) + { + port_data->unbind_flag = false; + os_free(port_data); + } + } + + os_mutex_unlock(&p_cm->mutex_hdl); + } +} diff --git a/app/drivers/data_port/socket_inet/socket_inet_server_shell.c b/app/drivers/data_port/socket_inet/socket_inet_server_shell.c new file mode 100644 index 0000000..11e8afe --- /dev/null +++ b/app/drivers/data_port/socket_inet/socket_inet_server_shell.c @@ -0,0 +1,265 @@ +/** + * @file socket_inet_server_shell.c + * @author LokLiang + * @brief + * @version 0.1 + * @date 2023-11-28 + * + * @copyright Copyright (c) 2024 + * + * 用于测试 socket 数据接口。 + * 注意事项: + * 需要配合 wifi_shell 模块使用。 + * 操作: + * 通过命令行,输入 inet open 打开连接,输入 wifi oa 或 wifi os 选择 wifi 模式; + * 在 wifi_shell.c::wifi_shell_register() 部分包含了连接信息,可根据这些信息连接或修改必要参数; + * 无线连接成功后,可通过工具测试发送和接收的速度等。 + */ + +#include "socket_inet_server_shell.h" +#include "socket_inet.h" +#include "shell/sh.h" + +#include +#include "os/os.h" + +#include "drivers/data_port/sb_data_port.h" + +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF +#define SYS_LOG_DOMAIN "SH" +#include "sys_log.h" + +static void _server_tcp_connect_cb(bool is_connect, sb_data_port_t *port); +static void _bind_and_open_tcp(uint8_t hdl_id); +static void _bind_and_open_udp(uint8_t hdl_id, uint16_t listen_port); +static void _unbind_and_close(uint8_t hdl_id); +static void _work_speed_handler(void *arg); +static void _work_read_handler(void *arg); + +static struct +{ + socket_listen_tcp_t tcp_listen; + + struct + { + sb_data_port_t *data_port; + os_work_t work_hdl; + int stype; // 0: udp, 1: tcp + } hdl[4]; + + os_work_t s_speed_work_hdl; + int recv_size; + int err_size; + int s_recv_total_tcp; + int recv_total; +} s_cm; + +SH_CMD_FN(_open); +SH_CMD_FN(_close); + +SH_DEF_SUB_CMD( + sub_wifi, + SH_SETUP_CMD("open", "打开接口", _open, NULL), // + SH_SETUP_CMD("close", "关闭接口", _close, NULL), // +); + +SH_DEF_CMD( + _register_tcp_wifi, + SH_SETUP_CMD("inet", "操作 WIFI 数据接口", NULL, sub_wifi), // +); + +void socket_server_shell_register(void) +{ + sh_register_cmd(&_register_tcp_wifi); + + socket_inet_init(); + + os_work_create(&s_cm.s_speed_work_hdl, "work-speed", _work_speed_handler, NULL, OS_PRIORITY_LOWEST); + os_work_submit(default_os_work_q_hdl, &s_cm.s_speed_work_hdl, 0); + + s_cm.tcp_listen = socket_inet_server_listen_tcp(4278, 2, _server_tcp_connect_cb); +} + +void socket_server_shell_unregister(void) +{ + sh_unregister_cmd(&_register_tcp_wifi); +} + +static void _server_tcp_connect_cb(bool is_connect, sb_data_port_t *port) +{ + SYS_LOG_INF("%s", is_connect ? "connected" : "disconnected"); + if (is_connect) + { + os_thread_sleep(10); + sb_data_port_write(port, "hello\r\n", 7, 0); + } +} + +static void _bind_and_open_tcp(uint8_t hdl_id) +{ + SYS_ASSERT(hdl_id < __ARRAY_SIZE(s_cm.hdl), ""); + + if (s_cm.hdl[hdl_id].data_port == NULL) + { + socket_server_bind_t bind_param = { + .rx_buf_size = 0x1000, + .rx_event = NULL, + .rx_resume_work = &s_cm.hdl[hdl_id].work_hdl, + }; + s_cm.hdl[hdl_id].data_port = socket_inet_server_bind_tcp(s_cm.tcp_listen, &bind_param); + s_cm.hdl[hdl_id].stype = 1; + } + + if (s_cm.hdl[hdl_id].data_port) + { + sb_data_port_start(s_cm.hdl[hdl_id].data_port); + + os_work_create(&s_cm.hdl[hdl_id].work_hdl, "work-read", _work_read_handler, s_cm.hdl[hdl_id].data_port, OS_PRIORITY_LOWEST); + os_work_submit(default_os_work_q_hdl, &s_cm.hdl[hdl_id].work_hdl, 0); + } +} + +static void _bind_and_open_udp(uint8_t hdl_id, uint16_t listen_port) +{ + SYS_ASSERT(hdl_id < __ARRAY_SIZE(s_cm.hdl), ""); + + if (s_cm.hdl[hdl_id].data_port) + { + return; + } + + socket_server_bind_t bind_param = { + .rx_buf_size = 0x1000, + .rx_event = NULL, + .rx_resume_work = &s_cm.hdl[hdl_id].work_hdl, + }; + s_cm.hdl[hdl_id].data_port = socket_inet_server_bind_udp(listen_port, &bind_param); + s_cm.hdl[hdl_id].stype = 0; + + if (s_cm.hdl[hdl_id].data_port) + { + sb_data_port_start(s_cm.hdl[hdl_id].data_port); + + os_work_create(&s_cm.hdl[hdl_id].work_hdl, "work-read", _work_read_handler, s_cm.hdl[hdl_id].data_port, OS_PRIORITY_LOWEST); + os_work_submit(default_os_work_q_hdl, &s_cm.hdl[hdl_id].work_hdl, 0); + } +} + +static void _unbind_and_close(uint8_t hdl_id) +{ + SYS_ASSERT(hdl_id < __ARRAY_SIZE(s_cm.hdl), ""); + + if (os_work_is_valid(&s_cm.hdl[hdl_id].work_hdl)) + os_work_delete(&s_cm.hdl[hdl_id].work_hdl); + + if (s_cm.hdl[hdl_id].data_port) + { + if (s_cm.hdl[hdl_id].stype == 0) + { + socket_inet_server_unbind_udp(s_cm.hdl[hdl_id].data_port); + s_cm.hdl[hdl_id].data_port = NULL; + } + else + { + sb_data_port_stop(s_cm.hdl[hdl_id].data_port); + } + } +} + +static void _work_speed_handler(void *arg) +{ + static os_time_t last_time; + os_time_t curr_time = os_get_sys_time(); + int time_cost = curr_time - last_time; + bool recv_flag = false; + + os_work_later(1000); + + time_cost += !time_cost; + + for (int i = 0; i < __ARRAY_SIZE(s_cm.hdl); i++) + { + sb_data_port_t *port = s_cm.hdl[i].data_port; + if (port && sb_data_port_is_started(port)) + { + if (s_cm.recv_size) + { + int result = s_cm.recv_size * 1000 / time_cost; + s_cm.recv_total += s_cm.recv_size; + s_cm.recv_size = 0; + SYS_LOG_INF("val trans speed: %2u.%02u KiB/S, recv total: %d bytes", result / 0x400, result % 0x400 * 100 / 0x400, s_cm.recv_total); + if (s_cm.err_size) + { + SYS_LOG_DBG("err total: %d times", s_cm.err_size); + s_cm.err_size = 0; + } + recv_flag = true; + } + } + } + + if (recv_flag == false) + { + s_cm.recv_total = 0; + } + + last_time = curr_time; +} + +static void _work_read_handler(void *arg) +{ + sb_data_port_t *port = arg; + static uint8_t val_log = '0'; + + while (sb_data_port_is_started(port)) + { + uint8_t buf[1500]; + int r_size = sb_data_port_read(port, buf, sizeof(buf), 0); + if (r_size <= 0) + { + break; + } + else + { + sb_data_port_write(port, buf, r_size, 0); + + s_cm.recv_size += r_size; + SYS_LOG_DUMP(buf, r_size, 0, 0); + for (int i = 0; i < r_size; i++) + { + if (buf[i] != val_log) + { + s_cm.err_size++; + val_log = buf[i]; + } + val_log = '0' + (val_log - '0' + 1) % 10; + } + + os_work_later(0); + } + } +} + +SH_CMD_FN(_open) +{ + _bind_and_open_udp(0, 14550); + _bind_and_open_udp(1, 14550 + 1); + _bind_and_open_tcp(2); + _bind_and_open_tcp(3); + return 0; +} + +SH_CMD_FN(_close) +{ + _unbind_and_close(0); + _unbind_and_close(1); + _unbind_and_close(2); + _unbind_and_close(3); + return 0; +} + +/* +soc base free +wifi open +wifi close +*/ diff --git a/app/drivers/data_port/socket_inet/socket_inet_server_shell.h b/app/drivers/data_port/socket_inet/socket_inet_server_shell.h new file mode 100644 index 0000000..e2467a3 --- /dev/null +++ b/app/drivers/data_port/socket_inet/socket_inet_server_shell.h @@ -0,0 +1,4 @@ +#pragma once + +void socket_server_shell_register(void); +void socket_server_shell_unregister(void); diff --git a/app/drivers/data_port/socket_inet/wifi.c b/app/drivers/data_port/socket_inet/wifi.c new file mode 100644 index 0000000..be298dd --- /dev/null +++ b/app/drivers/data_port/socket_inet/wifi.c @@ -0,0 +1,405 @@ +#include "wifi.h" + +#include + +#include "esp_mac.h" +#include "esp_wifi.h" +#include "esp_netif_types.h" +#include "esp_err.h" + +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF +#define SYS_LOG_DOMAIN "WIFI" +#include "sys_log.h" + +static struct +{ + uint8_t init; + uint8_t curr_mode; // 0: 已关闭, 1: ap, 2: sta, 3: ap+sta + wifi_netif_mode_t cfg_mode; + wifi_init_t init_param; + esp_netif_t *netif; + uint8_t mac[6]; + union + { + wifi_ap_connect_status_t ap_status; + wifi_sta_connect_status_t sta_status; + }; +} s_cm; + +static void _wifi_start_ap(void); +static void _wifi_start_sta(void); +static void _wiri_stop_ap(void); +static void _wiri_stop_sta(void); +static void _wifi_event_handler_ap(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); // 由 _wifi_start_ap() 启动时设置的事件回调函数,处理 AP 模式下客户端的连接状态 +static void _wifi_event_handler_sta(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); // 由 _wifi_start_sta() 启动时设置的事件回调函数,处理 STA 模式下的连接状态 + +/** + * @brief 初始化设置。可重复设置 + * + * @param init_struct + */ +void wifi_netif_init(wifi_init_t *init_struct) +{ + if (s_cm.init == 0) + { + s_cm.init = 1; + ESP_ERROR_CHECK(esp_netif_init()); + } + s_cm.init_param = *init_struct; +} + +/** + * @brief 设置 WIFI 模式。在下次执行 wifi_start() 时根据此模式启动 WIFI + * + * @param mode @ref wifi_netif_mode_t + */ +void wifi_set_mode(wifi_netif_mode_t mode) +{ + s_cm.cfg_mode = mode; +} + +void wifi_start(void) +{ + switch (s_cm.cfg_mode) + { + case WIFI_NETIF_MODE_AP: + _wifi_start_ap(); + break; + + case WIFI_NETIF_MODE_STA: + default: + _wifi_start_sta(); + break; + } +} + +/** + * @brief 关闭 WIFI + * + */ +void wifi_stop(void) +{ + if (s_cm.curr_mode == 0) + { + SYS_LOG_WRN("wifi has been stoped"); + return; + } + + if (s_cm.curr_mode == 1) + { + _wiri_stop_ap(); + } + else if (s_cm.curr_mode == 2) + { + _wiri_stop_sta(); + } + + s_cm.curr_mode = 0; +} + +/** + * @brief (仅在 AP 模式下)获取已连接的工作站信息 + * + * @param netif_sta_list + * @return int + */ +int wifi_get_sta_list(wifi_sta_mac_ip_list_t *netif_sta_list) +{ + wifi_sta_list_t sta_list; + memset(&sta_list, 0, sizeof(sta_list)); + memset(netif_sta_list, 0, sizeof(wifi_sta_mac_ip_list_t)); + esp_err_t err = esp_wifi_ap_get_sta_list(&sta_list); + if (err != ESP_OK) + { + return err; + } + + return esp_wifi_ap_get_sta_list_with_ip(&sta_list, netif_sta_list); +} + +/** + * @brief (仅在 AP 模式下)获取已连接的 AP 信息 + * + * @param netif_ap[out] + * @return int + */ +int wifi_get_ap_info(esp_netif_ip_info_t *netif_ap) +{ + if (s_cm.curr_mode == 2) + { + return esp_netif_get_ip_info(s_cm.netif, netif_ap); + } + return -1; +} + +/** + * @brief 初始化 WIFI 协议栈,启动 WIFI 的 AP 模式,启动 DHCP 服务 + * + */ +static void _wifi_start_ap(void) +{ + if (s_cm.curr_mode == 1) + { + SYS_LOG_WRN("wifi has been started"); + return; + } + + if (s_cm.curr_mode) + { + wifi_stop(); + } + + s_cm.curr_mode = 1; + + // Initialize networking stack + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // start the DHCP server + if (s_cm.netif == NULL) + { + s_cm.netif = esp_netif_create_default_wifi_ap(); + assert(s_cm.netif); + // stop DHCP server + ESP_ERROR_CHECK(esp_netif_dhcps_stop(s_cm.netif)); + // assign a static IP to the network interface + esp_netif_ip_info_t info; + memset(&info, 0, sizeof(info)); + IP4_ADDR(&info.ip, s_cm.init_param.ap.ip_v4[0], s_cm.init_param.ap.ip_v4[1], s_cm.init_param.ap.ip_v4[2], s_cm.init_param.ap.ip_v4[3]); + IP4_ADDR(&info.gw, s_cm.init_param.ap.gw_v4[0], s_cm.init_param.ap.gw_v4[1], s_cm.init_param.ap.gw_v4[2], s_cm.init_param.ap.gw_v4[3]); // ESP acts as router, so gw addr will be its own addr + IP4_ADDR(&info.netmask, s_cm.init_param.ap.mask_v4[0], s_cm.init_param.ap.mask_v4[1], s_cm.init_param.ap.mask_v4[2], s_cm.init_param.ap.mask_v4[3]); + ESP_ERROR_CHECK(esp_netif_set_ip_info(s_cm.netif, &info)); + ESP_ERROR_CHECK(esp_netif_dhcps_start(s_cm.netif)); + } + + /* Initialise ESP32 in SoftAP curr_mode */ + const char *ssid = s_cm.init_param.ap.ssid; + const char *password = s_cm.init_param.ap.password; + wifi_config_t wifi_config; + memset(&wifi_config, 0, sizeof(wifi_config_t)); + memcpy(wifi_config.ap.ssid, ssid, strlen(ssid)); + wifi_config.ap.ssid_len = strlen(ssid); + wifi_config.ap.max_connection = s_cm.init_param.ap.max_connection; + if (strlen(password) == 0) + { + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } + else + { + strcpy((char *)wifi_config.ap.password, password); + wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; + } + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, _wifi_event_handler_ap, NULL)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + wifi_ap_connect_cb connect_cb = s_cm.init_param.ap.connect_cb; + if (connect_cb) + { + s_cm.ap_status = WIFI_AP_CONNECT_GOT_STA; + connect_cb(s_cm.ap_status, 0); + } + + SYS_LOG_INF("done"); +} + +/** + * @brief 初始化 WIFI 协议栈,启动 WIFI 的 STA 模式 + * + */ +static void _wifi_start_sta(void) +{ + if (s_cm.curr_mode == 2) + { + SYS_LOG_WRN("wifi has been started"); + return; + } + + if (s_cm.curr_mode) + { + wifi_stop(); + } + + s_cm.curr_mode = 2; + + /* 参考自 examples/wifi/fast_scan/main/fast_scan.c */ + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // Initialize default station as network interface instance (esp-netif) + if (s_cm.netif == NULL) + { + s_cm.netif = esp_netif_create_default_wifi_sta(); + assert(s_cm.netif); + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &_wifi_event_handler_sta, s_cm.netif, NULL)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &_wifi_event_handler_sta, s_cm.netif, NULL)); + } + + // Initialize and start WiFi + const char *ssid = s_cm.init_param.sta.ssid; + const char *password = s_cm.init_param.sta.password; + wifi_config_t wifi_config; + memset(&wifi_config, 0, sizeof(wifi_config_t)); + strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); + strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password)); + wifi_config.sta.scan_method = WIFI_FAST_SCAN; + wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; + wifi_config.sta.threshold.rssi = -127; + wifi_config.sta.threshold.authmode = WIFI_AUTH_OPEN; + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + 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()); + + wifi_sta_connect_cb connect_cb = s_cm.init_param.sta.connect_cb; + if (connect_cb) + { + uint8_t ip_v4[4]; + memset(ip_v4, 0, sizeof(ip_v4)); + s_cm.sta_status = WIFI_STA_CONNECT_CONNECTING; + connect_cb(s_cm.sta_status, ip_v4); + } + + SYS_LOG_INF("done"); +} + +static void _wiri_stop_ap(void) +{ + /* Deinitialise ESP32 netif */ + ESP_ERROR_CHECK(esp_wifi_stop()); + if (s_cm.netif) + { + esp_netif_dhcp_status_t status; + ESP_ERROR_CHECK(esp_netif_dhcps_get_status(s_cm.netif, &status)); + if (ESP_NETIF_DHCP_STARTED == status) + { + ESP_ERROR_CHECK(esp_netif_dhcps_stop(s_cm.netif)); + } + esp_netif_destroy_default_wifi(s_cm.netif); + s_cm.netif = NULL; + } + ESP_ERROR_CHECK(esp_wifi_deinit()); + + // Deinitialize networking stack + ESP_ERROR_CHECK(esp_event_loop_delete_default()); + + wifi_ap_connect_cb connect_cb = s_cm.init_param.ap.connect_cb; + if (connect_cb) + { + s_cm.ap_status = WIFI_AP_CONNECT_STOPED; + connect_cb(s_cm.ap_status, 0); + } + + SYS_LOG_INF("done"); + + s_cm.curr_mode = 0; +} + +static void _wiri_stop_sta(void) +{ + /* Deinitialise ESP32 netif */ + ESP_ERROR_CHECK(esp_wifi_stop()); + if (s_cm.netif) + { + esp_netif_destroy_default_wifi(s_cm.netif); + s_cm.netif = NULL; + } + ESP_ERROR_CHECK(esp_wifi_deinit()); + + // Deinitialize networking stack + ESP_ERROR_CHECK(esp_event_loop_delete_default()); + + wifi_sta_connect_cb connect_cb = s_cm.init_param.sta.connect_cb; + if (connect_cb) + { + uint8_t ip_v4[4]; + memset(ip_v4, 0, sizeof(ip_v4)); + s_cm.sta_status = WIFI_STA_CONNECT_STOPED; + connect_cb(s_cm.sta_status, ip_v4); + } + + SYS_LOG_INF("done"); + + s_cm.curr_mode = 0; +} + +static void _wifi_event_handler_ap(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + switch (event_id) + { + case WIFI_EVENT_AP_STACONNECTED: + { + wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data; + SYS_LOG_INF("station " MACSTR " join, AID=%d", + MAC2STR(event->mac), event->aid); + memcpy(s_cm.mac, event->mac, 6); + break; + } + + case WIFI_EVENT_AP_STADISCONNECTED: + { + wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data; + SYS_LOG_INF("station " MACSTR " leave, AID=%d", + MAC2STR(event->mac), event->aid); + break; + } + + default: + return; + } + + wifi_ap_connect_cb connect_cb = s_cm.init_param.ap.connect_cb; + if (connect_cb) + { + wifi_sta_mac_ip_list_t netif_sta_list; + wifi_get_sta_list(&netif_sta_list); + s_cm.ap_status = WIFI_AP_CONNECT_GOT_STA; + connect_cb(s_cm.ap_status, netif_sta_list.num); + } +} + +void get_wifi_mac(uint8_t *mac) +{ + memcpy(mac, s_cm.mac, 6); +} + +static void _wifi_event_handler_sta(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + wifi_sta_connect_status_t status; + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) + { + status = WIFI_STA_CONNECT_CONNECTING; + esp_wifi_connect(); + } + else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) + { + status = WIFI_STA_CONNECT_CONNECTING; + esp_wifi_connect(); + } + else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) + { + status = WIFI_STA_CONNECT_GOT_IP; + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + SYS_LOG_INF("got ip: " IPSTR, IP2STR(&event->ip_info.ip)); + } + else + { + return; + } + + wifi_sta_connect_cb connect_cb = s_cm.init_param.sta.connect_cb; + if (connect_cb) + { + uint8_t ip_v4[4]; + memset(ip_v4, 0, sizeof(ip_v4)); + if (s_cm.sta_status != status) + { + s_cm.sta_status = status; + connect_cb(s_cm.sta_status, ip_v4); + } + } +} diff --git a/app/drivers/data_port/socket_inet/wifi.h b/app/drivers/data_port/socket_inet/wifi.h new file mode 100644 index 0000000..4ad6918 --- /dev/null +++ b/app/drivers/data_port/socket_inet/wifi.h @@ -0,0 +1,78 @@ +/** + * @file wifi.h + * @author LokLiang + * @brief 操作 ESP32 WIFI 的两种基本模式的开关 + * @version 0.1 + * @date 2023-11-24 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include +#include "esp_wifi_ap_get_sta_list.h" + +typedef enum +{ + WIFI_AP_CONNECT_STOPED, + WIFI_AP_CONNECT_GOT_STA, +} wifi_ap_connect_status_t; + +typedef enum +{ + WIFI_STA_CONNECT_STOPED, + WIFI_STA_CONNECT_CONNECTING, + WIFI_STA_CONNECT_GOT_IP, +} wifi_sta_connect_status_t; + +/** + * @brief AP 模式下的连接状态回调函数。 + * 提示:可使用 wifi_get_sta_list() 读出所有已连接的客户端信息 + */ +typedef void (*wifi_ap_connect_cb)(wifi_ap_connect_status_t status, uint8_t connect_cnt); + +/** + * @brief STA 模式下的连接状态回调函数。 + */ +typedef void (*wifi_sta_connect_cb)(wifi_sta_connect_status_t status, uint8_t ip_v4[4]); + +typedef struct +{ + struct + { + char ssid[33]; /**< SSID of soft-AP. If ssid_len field is 0, this must be a Null terminated string. Otherwise, length is set according to ssid_len. */ + char password[65]; /**< Password of soft-AP. */ + uint8_t ip_v4[4]; + uint8_t gw_v4[4]; + uint8_t mask_v4[4]; + uint8_t max_connection; // 最大连接数 + wifi_ap_connect_cb connect_cb; + } ap; + struct + { + char ssid[33]; + char password[65]; + wifi_sta_connect_cb connect_cb; + } sta; +} wifi_init_t; + +typedef enum +{ + WIFI_NETIF_MODE_AP, + WIFI_NETIF_MODE_STA, +} wifi_netif_mode_t; + +void wifi_netif_init(wifi_init_t *init_struct); + +void wifi_set_mode(wifi_netif_mode_t mode); + +void wifi_start(void); +void wifi_stop(void); + +int wifi_get_sta_list(wifi_sta_mac_ip_list_t *netif_sta_list); + +int wifi_get_ap_info(esp_netif_ip_info_t *netif_ap); + +void get_wifi_mac(uint8_t *mac); diff --git a/app/drivers/data_port/socket_inet/wifi_shell.c b/app/drivers/data_port/socket_inet/wifi_shell.c new file mode 100644 index 0000000..0cb3e38 --- /dev/null +++ b/app/drivers/data_port/socket_inet/wifi_shell.c @@ -0,0 +1,82 @@ +#include "wifi_shell.h" +#include "wifi.h" +#include "shell/sh.h" + +#include +#include "os/os.h" + +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF +#define SYS_LOG_DOMAIN "SH" +#include "sys_log.h" + +SH_CMD_FN(_open_ap); +SH_CMD_FN(_open_sta); +SH_CMD_FN(_close); + +SH_DEF_SUB_CMD( + sub_wifi, + SH_SETUP_CMD("oa", "打开 wifi ap 模式", _open_ap, NULL), // + SH_SETUP_CMD("os", "打开 wifi sta 模式", _open_sta, NULL), // + SH_SETUP_CMD("close", "关闭 wifi", _close, NULL), // +); + +SH_DEF_CMD( + _register_tcp_wifi, + SH_SETUP_CMD("wifi", "操作 WIFI 接口", NULL, sub_wifi), // +); + +void wifi_shell_register(void) +{ + sh_register_cmd(&_register_tcp_wifi); + + /* 初始化 WIFI */ + wifi_init_t init_struct = { + .ap = { + .ssid = "wifi-test", /**< SSID of soft-AP. If ssid_len field is 0, this must be a Null terminated string. Otherwise, length is set according to ssid_len. */ + .password = "12345678", /**< Password of soft-AP. */ + .ip_v4 = {192, 168, 1, 1}, + .gw_v4 = {192, 168, 1, 1}, + .mask_v4 = {255, 255, 255, 0}, + .max_connection = 1, + .connect_cb = NULL, + }, + .sta = { +#if 0 + .ssid = "KFXJ", + .password = "Kfdx201*", +#else + .ssid = "SDWAN", + .password = "Dxltkj201", +#endif + .connect_cb = NULL, + }, + }; + wifi_netif_init(&init_struct); + + wifi_set_mode(WIFI_NETIF_MODE_STA); +} + +void wifi_shell_unregister(void) +{ + sh_unregister_cmd(&_register_tcp_wifi); +} + +SH_CMD_FN(_open_ap) +{ + wifi_set_mode(WIFI_NETIF_MODE_AP); + wifi_start(); + return 0; +} + +SH_CMD_FN(_open_sta) +{ + wifi_set_mode(WIFI_NETIF_MODE_STA); + wifi_start(); + return 0; +} + +SH_CMD_FN(_close) +{ + wifi_stop(); + return 0; +} diff --git a/app/drivers/data_port/socket_inet/wifi_shell.h b/app/drivers/data_port/socket_inet/wifi_shell.h new file mode 100644 index 0000000..c5d3e39 --- /dev/null +++ b/app/drivers/data_port/socket_inet/wifi_shell.h @@ -0,0 +1,4 @@ +#pragma once + +void wifi_shell_register(void); +void wifi_shell_unregister(void); diff --git a/app/drivers/data_port/uart/uart_port.c b/app/drivers/data_port/uart/uart_port.c index d885d65..fd66f54 100755 --- a/app/drivers/data_port/uart/uart_port.c +++ b/app/drivers/data_port/uart/uart_port.c @@ -7,6 +7,7 @@ #include "uart_port.h" #include "driver/uart.h" +#include "hal/uart_ll.h" #include "drivers/data_port/sb_data_port.h" @@ -17,7 +18,7 @@ #define _MAX_UART_NUM 3 -#define UART_IN_TAST_STK_SIZE 2048 +#define UART_IN_TAST_STK_SIZE 2304 typedef struct { @@ -228,6 +229,8 @@ static void uart_port_initialize(sb_data_port_t *port) return; } + uart_disable_intr_mask(uart_data->uart_num, UART_INTR_BRK_DET); // 关闭检测UART_BREAK中断,防止过多的UART_BREAK导致的程序异常 + SYS_LOG_INF("uart_driver_install ok. (%d, %d, %d, %d, %d, %d)", uart_data->uart_num, 0x400, 0x400, 3, (uint32_t)&uart_data->event_queue, 0); } @@ -460,6 +463,7 @@ sb_data_port_t *sb_uart_port_bind(int uart_num, SYS_ASSERT(uart_num < _MAX_UART_NUM, ""); SYS_ASSERT(port->data == NULL, "The interface has already been bound"); + // SYS_ASSERT(baudrate > 0, ""); uart_data->uart_num = uart_num; uart_data->baudrate = baudrate; diff --git a/app/drivers/data_port/uart/uart_port.h b/app/drivers/data_port/uart/uart_port.h index 3310977..2b9d2be 100755 --- a/app/drivers/data_port/uart/uart_port.h +++ b/app/drivers/data_port/uart/uart_port.h @@ -11,7 +11,7 @@ #pragma once -#include "../sb_data_port.h" +#include "drivers/data_port/sb_data_port.h" #include "os/os.h" int sb_uart_port_init(void); diff --git a/app/drivers/data_port/usb-host/cdc/cdc_acm_host/CMakeLists.txt b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/CMakeLists.txt new file mode 100644 index 0000000..39bec48 --- /dev/null +++ b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "cdc_acm_host.c" + INCLUDE_DIRS "include" + REQUIRES usb) diff --git a/app/drivers/data_port/usb-host/cdc/cdc_acm_host/README.md b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/README.md new file mode 100644 index 0000000..aeb28b1 --- /dev/null +++ b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/README.md @@ -0,0 +1,46 @@ +# USB Host CDC-ACM Class Driver + +This directory contains an implementation of a USB CDC-ACM Host Class Driver that is implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html). + +## Supported Devices + +The CDC-ACM Host driver supports the following types of CDC devices: + +1. CDC-ACM devices +2. CDC-like vendor specific devices (usually found on USB to UART bridge devices) + +### CDC-ACM Devices + +The CDC-ACM Class driver supports CDC-ACM devices that meet the following requirements: +- The device class code must be set to the CDC class `0x02` or implement Interface Association Descriptor (IAD) +- The CDC-ACM must contain the following interfaces: + - A Communication Class Interface containing a management element (EP0) and may also contain a notification element (an interrupt endpoint). The driver will check this interface for CDC Functional Descriptors. + - A Data Class Interface with two BULK endpoints (IN and OUT). Other transfer types are not supported by the driver + +### CDC-Like Vendor Specific Devices + +The CDC-ACM Class driver supports CDC-like devices that meet the following requirements: +- The device class code must be set to the vendor specific class code `0xFF` +- The device needs to provide and interface containing the following endpoints: + - (Mandatory) Two Bulk endpoints (IN and OUT) for data + - (Optional) An interrupt endpoint (IN) for the notification element + +For CDC-like devices, users are responsible for ensuring that they only call APIs (e.g., `cdc_acm_host_send_break()`) that are supported by the target device. + + +## Usage + +The following steps outline the typical API call pattern of the CDC-ACM Class Driver + +1. Install the USB Host Library via `usb_host_install()` +2. Install the CDC-ACM driver via `cdc_acm_host_install()` +3. Call `cdc_acm_host_open()`/`cdc_acm_host_open_vendor_specific()` to open a target CDC-ACM/CDC-like device. These functions will block until the target device is connected +4. To transmit data, call `cdc_acm_host_data_tx_blocking()` +5. When data is received, the driver will automatically run the receive data callback +6. An opened device can be closed via `cdc_acm_host_close()` +7. The CDC-ACM driver can be uninstalled via `cdc_acm_host_uninstall()` + +## Examples + +- For an example with a CDC-ACM device, refer to [cdc_acm_host](../../cdc_acm_host) +- For an example with a CDC-like device, refer to [cdc_acm_host_bg96](../../cdc_acm_bg96) diff --git a/app/drivers/data_port/usb-host/cdc/cdc_acm_host/cdc_acm_host.c b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/cdc_acm_host.c new file mode 100644 index 0000000..f1cc12e --- /dev/null +++ b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/cdc_acm_host.c @@ -0,0 +1,1180 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include +#include +#include +#include "usb/usb_host.h" +#include "usb/cdc_acm_host.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" +#include "esp_check.h" +#include "esp_system.h" + +#define TAG "cdc_acm" + +// CDC devices often implement Interface Association Descriptor (IAD). Parse IAD only when +// bDeviceClass = 0xEF (Miscellaneous Device Class), bDeviceSubClass = 0x02 (Common Class), bDeviceProtocol = 0x01 (Interface Association Descriptor) +// @see USB Interface Association Descriptor: Device Class Code and Use Model rev 1.0, Table 1-1 +#define USB_SUBCLASS_COMMON 0x02 +#define USB_DEVICE_PROTOCOL_IAD 0x01 + +// CDC-ACM spinlock +static portMUX_TYPE cdc_acm_lock = portMUX_INITIALIZER_UNLOCKED; +#define CDC_ACM_ENTER_CRITICAL() portENTER_CRITICAL(&cdc_acm_lock) +#define CDC_ACM_EXIT_CRITICAL() portEXIT_CRITICAL(&cdc_acm_lock) + +// CDC-ACM events +#define CDC_ACM_TEARDOWN BIT0 +#define CDC_ACM_TEARDOWN_COMPLETE BIT1 + +// CDC-ACM check macros +#define CDC_ACM_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) + +#define CDC_ACM_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + CDC_ACM_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +// CDC-ACM driver object +typedef struct { + usb_host_client_handle_t cdc_acm_client_hdl; /*!< USB Host handle reused for all CDC-ACM devices in the system */ + SemaphoreHandle_t open_close_mutex; + EventGroupHandle_t event_group; + SLIST_HEAD(list_dev, cdc_dev_s) + cdc_devices_list; /*!< List of open pseudo devices */ +} cdc_acm_obj_t; + +static cdc_acm_obj_t *p_cdc_acm_obj = NULL; + +/** + * @brief Default CDC-ACM driver configuration + * + * This configuration is used when user passes NULL to config pointer during device open. + */ +static const cdc_acm_host_driver_config_t cdc_acm_driver_config_default = { + .driver_task_stack_size = 4096, + .driver_task_priority = 10, + .xCoreID = 0}; + +/** + * @brief USB CDC PSTN Call Descriptor + * + * @see Table 3, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; + const cdc_desc_subtype_t bDescriptorSubtype; + union { + struct { + uint8_t call_management : 1; // Device handles call management itself + uint8_t call_over_data_if : 1; // Device sends/receives call management information over Data Class interface + uint8_t reserved : 6; + }; + uint8_t val; + } bmCapabilities; + uint8_t bDataInterface; // Interface number of Data Class interface optionally used for call management +} __attribute__((packed)) cdc_acm_call_desc_t; + +/** + * @brief USB CDC PSTN Abstract Control Model Descriptor + * + * @see Table 4, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; + const cdc_desc_subtype_t bDescriptorSubtype; + union { + struct { + uint8_t feature : 1; // Device supports Set/Clear/Get_Comm_Feature requests + uint8_t serial : 1; // Device supports Set/Get_Line_Coding, Set_Control_Line_State and Serial_State request and notifications + uint8_t send_break : 1; // Device supports Send_Break request + uint8_t network : 1; // Device supports Network_Connection notification + uint8_t reserved : 4; + }; + uint8_t val; + } bmCapabilities; +} __attribute__((packed)) cdc_acm_acm_desc_t; + +typedef struct cdc_dev_s cdc_dev_t; +struct cdc_dev_s { + usb_device_handle_t dev_hdl; // USB device handle + void *cb_arg; // Common argument for user's callbacks (data IN and Notification) + struct { + usb_transfer_t *out_xfer; // OUT data transfer + usb_transfer_t *in_xfer; // IN data transfer + cdc_acm_data_callback_t in_cb; // User's callback for async (non-blocking) data IN + const usb_intf_desc_t *intf_desc; // Pointer to data interface descriptor + SemaphoreHandle_t out_mux; // OUT mutex + } data; + + struct { + usb_transfer_t *xfer; // IN notification transfer + const usb_intf_desc_t *intf_desc; // Pointer to notification interface descriptor, can be NULL if there is no notification channel in the device + cdc_acm_host_dev_callback_t cb; // User's callback for device events + } notif; // Structure with Notif pipe data + + usb_transfer_t *ctrl_transfer; // CTRL (endpoint 0) transfer + SemaphoreHandle_t ctrl_mux; // CTRL mutex + cdc_acm_uart_state_t serial_state; // Serial State + cdc_comm_protocol_t comm_protocol; + cdc_data_protocol_t data_protocol; + int num_cdc_intf_desc; // Number of CDC Interface descriptors in following array + const usb_standard_desc_t **cdc_intf_desc; // CDC Interface descriptors + SLIST_ENTRY(cdc_dev_s) + list_entry; +}; + +/** + * @brief Notification received callback + * + * Notification (interrupt) IN transfer is submitted at the end of this function to ensure periodic poll of IN endpoint. + * + * @param[in] transfer Transfer that triggered the callback + */ +static void notif_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief Data received callback + * + * Data (bulk) IN transfer is submitted at the end of this function to ensure continuous poll of IN endpoint. + * + * @param[in] transfer Transfer that triggered the callback + */ +static void in_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief Data send callback + * + * Reused for bulk OUT and CTRL transfers + * + * @param[in] transfer Transfer that triggered the callback + */ +static void out_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief USB Host Client event callback + * + * Handling of USB device connection/disconnection to/from root HUB. + * + * @param[in] event_msg Event message type + * @param[in] arg Caller's argument (not used in this driver) + */ +static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg); + +/** + * @brief Send CDC specific request + * + * Helper function that will send CDC specific request to default endpoint. + * Both IN and OUT requests are sent through this API, depending on the in_transfer parameter. + * + * @see Chapter 6.2, USB CDC specification rev. 1.2 + * @note CDC specific requests are only supported by devices that have dedicated management element. + * + * @param[in] cdc_dev Pointer to CDC device + * @param[in] in_transfer Direction of data phase. true: IN, false: OUT + * @param[in] request CDC request code + * @param[inout] data Pointer to data buffer. Input for OUT transfers, output for IN transfers. + * @param[in] data_len Length of data buffer + * @param[in] value Value to be set in bValue of Setup packet + * @return esp_err_t + */ +static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_request_code_t request, uint8_t *data, uint16_t data_len, uint16_t value); + +/** + * @brief CDC-ACM driver handling task + * + * USB host client registration and deregistration is handled here. + * + * @param[in] arg User's argument. Handle of a task that started this task. + */ +static void cdc_acm_client_task(void *arg) +{ + vTaskSuspend(NULL); // Task will be resumed from cdc_acm_host_install() + cdc_acm_obj_t *cdc_acm_obj = p_cdc_acm_obj; // Make local copy of the driver's handle + assert(cdc_acm_obj->cdc_acm_client_hdl); + + // Start handling client's events + while (1) { + usb_host_client_handle_events(cdc_acm_obj->cdc_acm_client_hdl, portMAX_DELAY); + EventBits_t events = xEventGroupGetBits(cdc_acm_obj->event_group); + if (events & CDC_ACM_TEARDOWN) { + break; + } + } + + ESP_LOGD(TAG, "Deregistering client"); + ESP_ERROR_CHECK(usb_host_client_deregister(cdc_acm_obj->cdc_acm_client_hdl)); + xEventGroupSetBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN_COMPLETE); + vTaskDelete(NULL); +} + +/** + * @brief Cancel transfer and reset endpoint + * + * This function will cancel ongoing transfer a reset its endpoint to ready state. + * + * @param[in] dev_hdl USB device handle + * @param[in] transfer Transfer to be cancelled + * @return esp_err_t + */ +static esp_err_t cdc_acm_reset_transfer_endpoint(usb_device_handle_t dev_hdl, usb_transfer_t *transfer) +{ + assert(dev_hdl); + assert(transfer); + + ESP_RETURN_ON_ERROR(usb_host_endpoint_halt(dev_hdl, transfer->bEndpointAddress), TAG, ); + ESP_RETURN_ON_ERROR(usb_host_endpoint_flush(dev_hdl, transfer->bEndpointAddress), TAG, ); + usb_host_endpoint_clear(dev_hdl, transfer->bEndpointAddress); + return ESP_OK; +} + +/** + * @brief Start CDC device + * + * After this call, USB host peripheral will continuously poll IN endpoints. + * + * @param cdc_dev + * @param[in] event_cb Device event callback + * @param[in] in_cb Data received callback + * @param[in] user_arg Optional user's argument, that will be passed to the callbacks + * @return esp_err_t + */ +static esp_err_t cdc_acm_start(cdc_dev_t *cdc_dev, cdc_acm_host_dev_callback_t event_cb, cdc_acm_data_callback_t in_cb, void *user_arg) +{ + esp_err_t ret = ESP_OK; + assert(cdc_dev); + + CDC_ACM_ENTER_CRITICAL(); + cdc_dev->notif.cb = event_cb; + cdc_dev->data.in_cb = in_cb; + cdc_dev->cb_arg = user_arg; + CDC_ACM_EXIT_CRITICAL(); + + // Claim data interface and start polling its IN endpoint + ESP_GOTO_ON_ERROR(usb_host_interface_claim(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber, 0), err, TAG, ); + ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->data.in_xfer)); + + // If notification are supported, claim its interface and start polling its IN endpoint + if (cdc_dev->notif.intf_desc != NULL) { + if (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc) { + ESP_GOTO_ON_ERROR(usb_host_interface_claim(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber, 0), err, TAG, ); + } + ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer"); + ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->notif.xfer)); + } + + // Everything OK, add the device into list and return + CDC_ACM_ENTER_CRITICAL(); + SLIST_INSERT_HEAD(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, list_entry); + CDC_ACM_EXIT_CRITICAL(); + return ret; + +err: + usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber); + if (cdc_dev->notif.intf_desc != NULL) { + usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber); + } + return ret; +} + +static void cdc_acm_transfers_free(cdc_dev_t *cdc_dev); +/** + * @brief Helper function that releases resources claimed by CDC device + * + * Close underlying USB device, free device driver memory + * + * @note All interfaces claimed by this device must be release before calling this function + * @param cdc_dev CDC device handle to be removed + */ +static void cdc_acm_device_remove(cdc_dev_t *cdc_dev) +{ + assert(cdc_dev); + cdc_acm_transfers_free(cdc_dev); + free(cdc_dev->cdc_intf_desc); + // We don't check the error code of usb_host_device_close, as the close might fail, if someone else is still using the device (not all interfaces are released) + usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl); // Gracefully continue on error + free(cdc_dev); +} + +/** + * @brief Open USB device with requested VID/PID + * + * This function has two regular return paths: + * 1. USB device with matching VID/PID is already opened by this driver: allocate new CDC device on top of the already opened USB device. + * 2. USB device with matching VID/PID is NOT opened by this driver yet: poll USB connected devices until it is found. + * + * @note This function will block for timeout_ms, if the device is not enumerated at the moment of calling this function. + * @param[in] vid Vendor ID + * @param[in] pid Product ID + * @param[in] timeout_ms Connection timeout [ms] + * @param[out] dev CDC-ACM device + * @return esp_err_t + */ +static esp_err_t cdc_acm_find_and_open_usb_device(uint16_t vid, uint16_t pid, int timeout_ms, cdc_dev_t **dev) +{ + // assert(p_cdc_acm_obj); + assert(dev); + + *dev = calloc(1, sizeof(cdc_dev_t)); + if (*dev == NULL) { + return ESP_ERR_NO_MEM; + } + + // // First, check list of already opened CDC devices + // ESP_LOGD(TAG, "Checking list of opened USB devices"); + // cdc_dev_t *cdc_dev; + // SLIST_FOREACH(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry) + // { + // const usb_device_desc_t *device_desc; + // ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + // if (device_desc->idVendor == vid && device_desc->idProduct == pid) { + // // Return path 1: + // (*dev)->dev_hdl = cdc_dev->dev_hdl; + // return ESP_OK; + // } + // } + + // Second, poll connected devices until new device is connected or timeout + TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + TimeOut_t connection_timeout; + vTaskSetTimeOutState(&connection_timeout); + + while (true) { + ESP_LOGD(TAG, "Checking list of connected USB devices"); + uint8_t dev_addr_list[10]; + int num_of_devices; + ESP_ERROR_CHECK(usb_host_device_addr_list_fill(sizeof(dev_addr_list), dev_addr_list, &num_of_devices)); + + // Go through device address list and find the one we are looking for + for (int i = 0; i < num_of_devices; i++) { + usb_device_handle_t current_device; + // Open USB device + if (usb_host_device_open(p_cdc_acm_obj->cdc_acm_client_hdl, dev_addr_list[i], ¤t_device) != ESP_OK) { + continue; // In case we failed to open this device, continue with next one in the list + } + assert(current_device); + const usb_device_desc_t *device_desc; + ESP_ERROR_CHECK(usb_host_get_device_descriptor(current_device, &device_desc)); + if (device_desc->idVendor == vid && device_desc->idProduct == pid) { + // Return path 2: + (*dev)->dev_hdl = current_device; + return ESP_OK; + } + usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, current_device); + } + + if (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) != pdFALSE) { + break; // Timeout elapsed and the device is not connected + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + + // Timeout was reached, clean-up + free(*dev); + *dev = NULL; + return ESP_ERR_NOT_FOUND; +} + +esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config) +{ + CDC_ACM_CHECK(!p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + + // Check driver configuration, use default if NULL is passed + if (driver_config == NULL) { + driver_config = &cdc_acm_driver_config_default; + } + + // Allocate all we need for this driver + esp_err_t ret; + cdc_acm_obj_t *cdc_acm_obj = heap_caps_calloc(1, sizeof(cdc_acm_obj_t), MALLOC_CAP_DEFAULT); + EventGroupHandle_t event_group = xEventGroupCreate(); + SemaphoreHandle_t mutex = xSemaphoreCreateMutex(); + TaskHandle_t driver_task_h = NULL; + xTaskCreatePinnedToCore( + cdc_acm_client_task, "USB-CDC", driver_config->driver_task_stack_size, NULL, driver_config->driver_task_priority, &driver_task_h, driver_config->xCoreID); + + if (cdc_acm_obj == NULL || driver_task_h == NULL || event_group == NULL || mutex == NULL) { + ret = ESP_ERR_NO_MEM; + goto err; + } + + // Register USB Host client + usb_host_client_handle_t usb_client = NULL; + const usb_host_client_config_t client_config = { + .is_synchronous = false, + .max_num_event_msg = 3, + .async.client_event_callback = usb_event_cb, + .async.callback_arg = NULL}; + ESP_GOTO_ON_ERROR(usb_host_client_register(&client_config, &usb_client), err, TAG, "Failed to register USB host client"); + + // Initialize CDC-ACM driver structure + SLIST_INIT(&(cdc_acm_obj->cdc_devices_list)); + cdc_acm_obj->event_group = event_group; + cdc_acm_obj->open_close_mutex = mutex; + cdc_acm_obj->cdc_acm_client_hdl = usb_client; + + // Between 1st call of this function and following section, another task might try to install this driver: + // Make sure that there is only one instance of this driver in the system + CDC_ACM_ENTER_CRITICAL(); + if (p_cdc_acm_obj) { + // Already created + ret = ESP_ERR_INVALID_STATE; + CDC_ACM_EXIT_CRITICAL(); + goto client_err; + } else { + p_cdc_acm_obj = cdc_acm_obj; + } + CDC_ACM_EXIT_CRITICAL(); + + // Everything OK: Start CDC-Driver task and return + vTaskResume(driver_task_h); + return ESP_OK; + +client_err: + usb_host_client_deregister(usb_client); +err: // Clean-up + free(cdc_acm_obj); + if (event_group) { + vEventGroupDelete(event_group); + } + if (driver_task_h) { + vTaskDelete(driver_task_h); + } + if (mutex) { + vSemaphoreDelete(mutex); + } + return ret; +} + +esp_err_t cdc_acm_host_uninstall() +{ + esp_err_t ret; + + CDC_ACM_ENTER_CRITICAL(); + CDC_ACM_CHECK_FROM_CRIT(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + cdc_acm_obj_t *cdc_acm_obj = p_cdc_acm_obj; // Save Driver's handle to temporary handle + CDC_ACM_EXIT_CRITICAL(); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); // Wait for all open/close calls to finish + + CDC_ACM_ENTER_CRITICAL(); + if (SLIST_EMPTY(&p_cdc_acm_obj->cdc_devices_list)) { // Check that device list is empty (all devices closed) + p_cdc_acm_obj = NULL; // NULL static driver pointer: No open/close calls form this point + } else { + ret = ESP_ERR_INVALID_STATE; + CDC_ACM_EXIT_CRITICAL(); + goto unblock; + } + CDC_ACM_EXIT_CRITICAL(); + + // Signal to CDC task to stop, unblock it and wait for its deletion + xEventGroupSetBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN); + usb_host_client_unblock(cdc_acm_obj->cdc_acm_client_hdl); + ESP_GOTO_ON_FALSE( + xEventGroupWaitBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN_COMPLETE, pdFALSE, pdFALSE, pdMS_TO_TICKS(100)), + ESP_ERR_NOT_FINISHED, + unblock, + TAG, ); + + // Free remaining resources and return + vEventGroupDelete(cdc_acm_obj->event_group); + xSemaphoreGive(cdc_acm_obj->open_close_mutex); + vSemaphoreDelete(cdc_acm_obj->open_close_mutex); + free(cdc_acm_obj); + return ESP_OK; + +unblock: + xSemaphoreGive(cdc_acm_obj->open_close_mutex); + return ret; +} + +/** + * @brief Free USB transfers used by this device + * + * @note There can be no transfers in flight, at the moment of calling this function. + * @param[in] cdc_dev Pointer to CDC device + */ +static void cdc_acm_transfers_free(cdc_dev_t *cdc_dev) +{ + assert(cdc_dev); + usb_host_transfer_free(cdc_dev->notif.xfer); + usb_host_transfer_free(cdc_dev->data.in_xfer); + if (cdc_dev->data.out_xfer != NULL) { + if (cdc_dev->data.out_xfer->context != NULL) { + vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->data.out_xfer->context); + } + if (cdc_dev->data.out_mux != NULL) { + vSemaphoreDelete(cdc_dev->data.out_mux); + } + usb_host_transfer_free(cdc_dev->data.out_xfer); + } + if (cdc_dev->ctrl_transfer != NULL) { + if (cdc_dev->ctrl_transfer->context != NULL) { + vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context); + } + if (cdc_dev->ctrl_mux != NULL) { + vSemaphoreDelete(cdc_dev->ctrl_mux); + } + usb_host_transfer_free(cdc_dev->ctrl_transfer); + } +} + +/** + * @brief Allocate CDC transfers + * + * @param[in] cdc_dev Pointer to CDC device + * @param[in] notif_ep_desc Pointer to notification EP descriptor + * @param[in] in_ep_desc- Pointer to data IN EP descriptor + * @param[in] out_ep_desc Pointer to data OUT EP descriptor + * @param[in] out_buf_len Length of data OUT buffer + * @return esp_err_t + */ +static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_desc_t *notif_ep_desc, const usb_ep_desc_t *in_ep_desc, const usb_ep_desc_t *out_ep_desc, size_t out_buf_len) +{ + esp_err_t ret; + + // 1. Setup notification and control transfers if they are supported + if (notif_ep_desc) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(notif_ep_desc), 0, &cdc_dev->notif.xfer), + err, + TAG, ); + cdc_dev->notif.xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->notif.xfer->bEndpointAddress = notif_ep_desc->bEndpointAddress; + cdc_dev->notif.xfer->callback = notif_xfer_cb; + cdc_dev->notif.xfer->context = cdc_dev; + cdc_dev->notif.xfer->num_bytes = USB_EP_DESC_GET_MPS(notif_ep_desc); + + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(cdc_dev->dev_hdl, &dev_info)); + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(dev_info.bMaxPacketSize0, 0, &cdc_dev->ctrl_transfer), + err, + TAG, ); + cdc_dev->ctrl_transfer->timeout_ms = 1000; + cdc_dev->ctrl_transfer->bEndpointAddress = 0; + cdc_dev->ctrl_transfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->ctrl_transfer->context = cdc_dev; + cdc_dev->ctrl_transfer->callback = out_xfer_cb; + cdc_dev->ctrl_transfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->context, ESP_ERR_NO_MEM, err, TAG, ); + cdc_dev->ctrl_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_mux, ESP_ERR_NO_MEM, err, TAG, ); + } + + // 2. Setup IN data transfer + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(in_ep_desc), 0, &cdc_dev->data.in_xfer), + err, + TAG, ); + assert(cdc_dev->data.in_xfer); + cdc_dev->data.in_xfer->callback = in_xfer_cb; + cdc_dev->data.in_xfer->num_bytes = USB_EP_DESC_GET_MPS(in_ep_desc); + cdc_dev->data.in_xfer->bEndpointAddress = in_ep_desc->bEndpointAddress; + cdc_dev->data.in_xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->data.in_xfer->context = cdc_dev; + + // 3. Setup OUT bulk transfer (if it is required (out_buf_len > 0)) + if (out_buf_len != 0) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(out_buf_len, 0, &cdc_dev->data.out_xfer), + err, + TAG, ); + assert(cdc_dev->data.out_xfer); + cdc_dev->data.out_xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->data.out_xfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->context, ESP_ERR_NO_MEM, err, TAG, ); + cdc_dev->data.out_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_mux, ESP_ERR_NO_MEM, err, TAG, ); + cdc_dev->data.out_xfer->bEndpointAddress = out_ep_desc->bEndpointAddress; + cdc_dev->data.out_xfer->callback = out_xfer_cb; + } + return ESP_OK; + +err: + cdc_acm_transfers_free(cdc_dev); + return ret; +} + +/** + * @brief Find CDC interface descriptor and its endpoint descriptors + * + * @note This function is called in open procedure of CDC compliant devices only. + * @param[in] cdc_dev Pointer to CDC device + * @param[in] intf_idx Index of CDC interface that should be used for this device + * @param[out] notif_ep Pointer to notification EP descriptor + * @param[out] in_ep Pointer to data IN EP descriptor + * @param[out] out_ep Pointer to data OUT EP descriptor + * @return esp_err_t + */ +static esp_err_t cdc_acm_find_intf_and_ep_desc(cdc_dev_t *cdc_dev, uint8_t intf_idx, const usb_ep_desc_t **notif_ep, const usb_ep_desc_t **in_ep, const usb_ep_desc_t **out_ep) +{ + bool interface_found = false; + const usb_config_desc_t *config_desc; + const usb_device_desc_t *device_desc; + int data_intf_idx, notif_intf_idx; + int desc_offset = 0; + + // Get required descriptors + ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + + if ((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) && (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) { + // This is a composite device, that uses Interface Association Descriptor + const usb_standard_desc_t *this_desc = (const usb_standard_desc_t *)config_desc; + do { + this_desc = usb_parse_next_descriptor_of_type( + this_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset); + + if (this_desc == NULL) + break; // Reached end of configuration descriptor + + const usb_iad_desc_t *iad_desc = (const usb_iad_desc_t *)this_desc; + if (iad_desc->bFirstInterface == intf_idx) { + // IAD with correct interface number was found: Check Class/Subclass codes, save Interface indexes + assert(iad_desc->bInterfaceCount == 2); + assert(iad_desc->bFunctionClass == USB_CLASS_COMM); + assert(iad_desc->bFunctionSubClass == CDC_SUBCLASS_ACM); + notif_intf_idx = iad_desc->bFirstInterface; + data_intf_idx = iad_desc->bFirstInterface + 1; + interface_found = true; + } + } while (!interface_found); + } else if ((device_desc->bDeviceClass == USB_CLASS_COMM) && (intf_idx == 0)) { + // This is a Communication Device Class + notif_intf_idx = 0; + data_intf_idx = 1; + interface_found = true; + } + + // Save found interfaces descriptors: + if (interface_found) { + // Notification IF and EP + cdc_dev->notif.intf_desc = usb_parse_interface_descriptor(config_desc, notif_intf_idx, 0, &desc_offset); + assert(cdc_dev->notif.intf_desc); + + // CDC specific descriptors should be right after CDC-Communication interface descriptor + // Note: That's why we use usb_parse_next_descriptor instead of usb_parse_next_descriptor_of_type. + // The latter could return CDC specific descriptors that don't belong to this interface + const usb_standard_desc_t *cdc_desc = (usb_standard_desc_t *)cdc_dev->notif.intf_desc; + do { + cdc_desc = usb_parse_next_descriptor(cdc_desc, config_desc->wTotalLength, &desc_offset); + if ((cdc_desc == NULL) || (cdc_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_W_VALUE_DT_INTERFACE))) + break; // We found all CDC specific descriptors + cdc_dev->num_cdc_intf_desc++; + cdc_dev->cdc_intf_desc = + realloc(cdc_dev->cdc_intf_desc, cdc_dev->num_cdc_intf_desc * (sizeof(usb_standard_desc_t *))); + assert(cdc_dev->cdc_intf_desc); + cdc_dev->cdc_intf_desc[cdc_dev->num_cdc_intf_desc - 1] = cdc_desc; + } while (1); + *notif_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->notif.intf_desc, 0, config_desc->wTotalLength, &desc_offset); + assert(notif_ep); + + // Data IF and EP + cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, data_intf_idx, 0, &desc_offset); + assert(cdc_dev->data.intf_desc); + int temp_offset = desc_offset; + for (int i = 0; i < 2; i++) { + const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset); + assert(this_ep); + if (USB_EP_DESC_GET_EP_DIR(this_ep)) { + *in_ep = this_ep; + } else { + *out_ep = this_ep; + } + desc_offset = temp_offset; + } + return ESP_OK; + } + return ESP_ERR_NOT_FOUND; +} + +esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret) +{ + esp_err_t ret; + // CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG); + + // xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + // Find underlying USB device + cdc_dev_t *cdc_dev; + ESP_GOTO_ON_ERROR( + cdc_acm_find_and_open_usb_device(vid, pid, dev_config->connection_timeout_ms, &cdc_dev), + exit, + TAG, + "USB device with VID: 0x%04X, PID: 0x%04X not found", + vid, + pid); + + // Find and save relevant interface and endpoint descriptors + const usb_ep_desc_t *notif_ep = NULL; + const usb_ep_desc_t *in_ep = NULL; + const usb_ep_desc_t *out_ep = NULL; + ESP_GOTO_ON_ERROR( + cdc_acm_find_intf_and_ep_desc(cdc_dev, interface_idx, ¬if_ep, &in_ep, &out_ep), + err, + TAG, + "Could not find required interface"); + + // Check whether found Interfaces are really CDC-ACM + assert(cdc_dev->notif.intf_desc->bInterfaceClass == USB_CLASS_COMM); + assert(cdc_dev->notif.intf_desc->bInterfaceSubClass == CDC_SUBCLASS_ACM); + assert(cdc_dev->notif.intf_desc->bNumEndpoints == 1); + assert(cdc_dev->data.intf_desc->bInterfaceClass == USB_CLASS_CDC_DATA); + assert(cdc_dev->data.intf_desc->bNumEndpoints == 2); + + // Save Communication and Data protocols + cdc_dev->comm_protocol = (cdc_comm_protocol_t)cdc_dev->notif.intf_desc->bInterfaceProtocol; + cdc_dev->data_protocol = (cdc_data_protocol_t)cdc_dev->data.intf_desc->bInterfaceProtocol; + + // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle + ESP_GOTO_ON_ERROR(cdc_acm_transfers_allocate(cdc_dev, notif_ep, in_ep, out_ep, dev_config->out_buffer_size), err, TAG, ); + ESP_GOTO_ON_ERROR(cdc_acm_start(cdc_dev, dev_config->event_cb, dev_config->data_cb, dev_config->user_arg), err, TAG, ); + *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev; + // xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; + +err: + cdc_acm_device_remove(cdc_dev); +exit: + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + *cdc_hdl_ret = NULL; + return ret; +} + +esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_num, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret) +{ + esp_err_t ret; + CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + + // Find underlying USB device + cdc_dev_t *cdc_dev; + ESP_GOTO_ON_ERROR( + cdc_acm_find_and_open_usb_device(vid, pid, dev_config->connection_timeout_ms, &cdc_dev), + exit, + TAG, + "USB device with VID: 0x%04X, PID: 0x%04X not found", + vid, + pid); + + // Open procedure for CDC-ACM non-compliant devices: + const usb_config_desc_t *config_desc; + int desc_offset; + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, interface_num, 0, &desc_offset); + const int temp_offset = desc_offset; // Save this offset for later + assert(cdc_dev->data.intf_desc); + + // The interface can have 2-3 endpoints. 2 for data and 1 optional for notifications + const usb_ep_desc_t *in_ep = NULL; + const usb_ep_desc_t *out_ep = NULL; + const usb_ep_desc_t *notif_ep = NULL; + int ep_idx = 0; + if (cdc_dev->data.intf_desc->bNumEndpoints == 3) { + // Notification channel does not have its dedicated interface (data and notif interface is the same) + // First endpoint of this interface is used as notification channel + cdc_dev->notif.intf_desc = cdc_dev->data.intf_desc; + notif_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, 0, config_desc->wTotalLength, &desc_offset); + desc_offset = temp_offset; + ep_idx++; + } + + for (int i = ep_idx; i < ep_idx + 2; i++) { + const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset); + assert(this_ep); + if (USB_EP_DESC_GET_EP_DIR(this_ep)) { + in_ep = this_ep; + } else { + out_ep = this_ep; + } + desc_offset = temp_offset; + } + + // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle + ESP_GOTO_ON_ERROR(cdc_acm_transfers_allocate(cdc_dev, notif_ep, in_ep, out_ep, dev_config->out_buffer_size), err, TAG, ); + ESP_GOTO_ON_ERROR(cdc_acm_start(cdc_dev, dev_config->event_cb, dev_config->data_cb, dev_config->user_arg), err, TAG, ); + *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev; + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; +err: + cdc_acm_device_remove(cdc_dev); +exit: + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ret; +} + +esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl) +{ + CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + // Cancel polling of BULK IN and INTERRUPT IN endpoints + cdc_dev->notif.cb = NULL; + cdc_dev->data.in_cb = NULL; + ESP_ERROR_CHECK(cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.in_xfer)); + if (cdc_dev->notif.intf_desc != NULL) { + ESP_ERROR_CHECK(cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->notif.xfer)); + } + + // Release all interfaces + ESP_ERROR_CHECK(usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber)); + if ((cdc_dev->notif.intf_desc != NULL) && (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc)) { + ESP_ERROR_CHECK(usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber)); + } + + CDC_ACM_ENTER_CRITICAL(); + SLIST_REMOVE(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, cdc_dev_s, list_entry); + CDC_ACM_EXIT_CRITICAL(); + + cdc_acm_device_remove(cdc_dev); + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; +} + +void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl) +{ + assert(cdc_hdl); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + ESP_RETURN_ON_FALSE(cdc_dev->num_cdc_intf_desc > 0, , TAG, "No CDC-ACM specific descriptors found"); + + for (int i = 0; i < cdc_dev->num_cdc_intf_desc; i++) { + switch (((cdc_header_desc_t *)cdc_dev->cdc_intf_desc[i])->bDescriptorSubtype) { + case CDC_DESC_SUBTYPE_HEADER: { + cdc_header_desc_t *desc = (cdc_header_desc_t *)cdc_dev->cdc_intf_desc[i]; + printf("CDC Header Descriptor:\n"); + printf("\tbcdCDC: %d.%d0\n", ((desc->bcdCDC >> 8) & 0xF), ((desc->bcdCDC >> 4) & 0xF)); + break; + } + case CDC_DESC_SUBTYPE_CALL: { + cdc_acm_call_desc_t *desc = (cdc_acm_call_desc_t *)cdc_dev->cdc_intf_desc[i]; + printf("CDC Call Descriptor:\n"); + printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val); + printf("\tbDataInterface: %d\n", desc->bDataInterface); + break; + } + case CDC_DESC_SUBTYPE_ACM: { + cdc_acm_acm_desc_t *desc = (cdc_acm_acm_desc_t *)cdc_dev->cdc_intf_desc[i]; + printf("CDC ACM Descriptor:\n"); + printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val); + break; + } + case CDC_DESC_SUBTYPE_UNION: { + cdc_union_desc_t *desc = (cdc_union_desc_t *)cdc_dev->cdc_intf_desc[i]; + printf("CDC Union Descriptor:\n"); + printf("\tbControlInterface: %d\n", desc->bControlInterface); + printf("\tbSubordinateInterface[0]: %d\n", desc->bSubordinateInterface[0]); + break; + } + default: + ESP_LOGW(TAG, "Unsupported CDC specific descriptor"); + break; + } + } +} + +/** + * @brief Check finished transfer status + * + * Return to on transfer completed OK. + * Cancel the transfer and issue user's callback in case of an error. + * + * @param[in] transfer Transfer to be checked + * @return true Transfer completed + * @return false Transfer NOT completed + */ +static bool cdc_acm_is_transfer_completed(usb_transfer_t *transfer) +{ + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + bool completed = false; + + switch (transfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: + completed = true; + break; + case USB_TRANSFER_STATUS_NO_DEVICE: // User is notified about device disconnection from usb_event_cb + case USB_TRANSFER_STATUS_CANCELED: + break; + case USB_TRANSFER_STATUS_ERROR: + case USB_TRANSFER_STATUS_TIMED_OUT: + case USB_TRANSFER_STATUS_STALL: + case USB_TRANSFER_STATUS_OVERFLOW: + case USB_TRANSFER_STATUS_SKIPPED: + default: + // Transfer was not completed or cancelled by user. Inform user about this + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t error_event = { + .type = CDC_ACM_HOST_ERROR, + .data.error = (int)transfer->status}; + cdc_dev->notif.cb((cdc_acm_dev_hdl_t)cdc_dev, &error_event, cdc_dev->cb_arg); + } + } + return completed; +} + +static void in_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "in xfer cb"); + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + + if (cdc_acm_is_transfer_completed(transfer)) { + if (cdc_dev->data.in_cb) { + cdc_dev->data.in_cb(transfer->data_buffer, transfer->actual_num_bytes, cdc_dev->cb_arg); + } + + ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + usb_host_transfer_submit(cdc_dev->data.in_xfer); + } +} + +static void notif_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "notif xfer cb"); + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + + if (cdc_acm_is_transfer_completed(transfer)) { + cdc_notification_t *notif = (cdc_notification_t *)transfer->data_buffer; + switch (notif->bNotificationCode) { + case CDC_NOTIF_NETWORK_CONNECTION: { + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t net_conn_event = { + .type = CDC_ACM_HOST_NETWORK_CONNECTION, + .data.network_connected = (bool)notif->wValue}; + cdc_dev->notif.cb((cdc_acm_dev_hdl_t)cdc_dev, &net_conn_event, cdc_dev->cb_arg); + } + break; + } + case CDC_NOTIF_SERIAL_STATE: { + cdc_dev->serial_state.val = *((uint16_t *)notif->Data); + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t serial_state_event = { + .type = CDC_ACM_HOST_SERIAL_STATE, + .data.serial_state = cdc_dev->serial_state}; + cdc_dev->notif.cb((cdc_acm_dev_hdl_t)cdc_dev, &serial_state_event, cdc_dev->cb_arg); + } + break; + } + case CDC_NOTIF_RESPONSE_AVAILABLE: // Encapsulated commands not implemented - fallthrough + default: + ESP_LOGW("CDC_ACM", "Unsupported notification type 0x%02X", notif->bNotificationCode); + ESP_LOG_BUFFER_HEX("CDC_ACM", transfer->data_buffer, transfer->actual_num_bytes); + break; + } + + // Start polling for new data again + ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer"); + usb_host_transfer_submit(cdc_dev->notif.xfer); + } +} + +static void out_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "out/ctrl xfer cb"); + assert(transfer->context); + xSemaphoreGive((SemaphoreHandle_t)transfer->context); +} + +static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + ESP_LOGI(TAG, "New device connected"); + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: { + ESP_LOGI(TAG, "Device suddenly disconnected"); + // Find CDC pseudo-devices associated with this USB device and close them + cdc_dev_t *cdc_dev; + cdc_dev_t *tcdc_dev; + // We are using 'SAFE' version of 'SLIST_FOREACH' which enables user to close the disconnected device in the callback + SLIST_FOREACH_SAFE(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry, tcdc_dev) + { + if (cdc_dev->dev_hdl == event_msg->dev_gone.dev_hdl && cdc_dev->notif.cb) { + // The suddenly disconnected device was opened by this driver: inform user about this + const cdc_acm_host_dev_event_data_t disconn_event = { + .type = CDC_ACM_HOST_DEVICE_DISCONNECTED, + }; + cdc_dev->notif.cb((cdc_acm_dev_hdl_t)cdc_dev, &disconn_event, cdc_dev->cb_arg); + } + } + break; + } + default: + assert(false); + break; + } +} + +esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms) +{ + esp_err_t ret; + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + CDC_ACM_CHECK(data && (data_len > 0), ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_dev->data.out_xfer, ESP_ERR_NOT_SUPPORTED); // Device was opened as read-only. + CDC_ACM_CHECK(data_len <= cdc_dev->data.out_xfer->data_buffer_size, ESP_ERR_INVALID_SIZE); + + // Take OUT mutex and fill the OUT transfer + BaseType_t taken = xSemaphoreTake(cdc_dev->data.out_mux, pdMS_TO_TICKS(timeout_ms)); + if (taken != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + ESP_LOGD("CDC_ACM", "Submitting BULK OUT transfer"); + memcpy(cdc_dev->data.out_xfer->data_buffer, data, data_len); + cdc_dev->data.out_xfer->num_bytes = data_len; + cdc_dev->data.out_xfer->timeout_ms = timeout_ms; + ESP_GOTO_ON_ERROR(usb_host_transfer_submit(cdc_dev->data.out_xfer), unblock, TAG, ); + + // Wait for OUT transfer completion + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->data.out_xfer->context, pdMS_TO_TICKS(timeout_ms)); + if (!taken) { + // Reset the endpoint + cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.out_xfer); + ret = ESP_ERR_TIMEOUT; + goto unblock; + } + + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Bulk OUT transfer error"); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->actual_num_bytes == data_len, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->data.out_mux); + return ret; +} + +esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding) +{ + CDC_ACM_CHECK(cdc_hdl && line_coding, ESP_ERR_INVALID_ARG); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, true, CDC_REQ_GET_LINE_CODING, (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), 0), + TAG, ); + ESP_LOGD(TAG, "Line Get: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding->dwDTERate, line_coding->bCharFormat, line_coding->bParityType, line_coding->bDataBits); + return ESP_OK; +} + +esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding) +{ + CDC_ACM_CHECK(cdc_hdl && line_coding, ESP_ERR_INVALID_ARG); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, false, CDC_REQ_SET_LINE_CODING, (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), 0), + TAG, ); + ESP_LOGD(TAG, "Line Set: Rate: %d, Stop bits: %d, Parity: %d, Databits: %d", line_coding->dwDTERate, line_coding->bCharFormat, line_coding->bParityType, line_coding->bDataBits); + return ESP_OK; +} + +esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts) +{ + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + + const uint16_t ctrl_bitmap = (uint16_t)dtr | ((uint16_t)rts << 1); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, false, CDC_REQ_SET_CONTROL_LINE_STATE, NULL, 0, ctrl_bitmap), + TAG, ); + ESP_LOGD(TAG, "Control Line Set: DTR: %d, RTS: %d", dtr, rts); + return ESP_OK; +} + +esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms) +{ + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, false, CDC_REQ_SEND_BREAK, NULL, 0, duration_ms), + TAG, ); + + // Block until break is deasserted + vTaskDelay(pdMS_TO_TICKS(duration_ms + 1)); + return ESP_OK; +} + +static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_request_code_t request, uint8_t *data, uint16_t data_len, uint16_t value) +{ + esp_err_t ret; + CDC_ACM_CHECK(cdc_dev->ctrl_transfer, ESP_ERR_NOT_SUPPORTED); + CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= data_len, ESP_ERR_INVALID_SIZE); + + // Take Mutex and fill the CTRL request + BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(1000)); + if (!taken) { + return ESP_ERR_TIMEOUT; + } + usb_setup_packet_t *req = (usb_setup_packet_t *)(cdc_dev->ctrl_transfer->data_buffer); + uint8_t *start_of_data = (uint8_t *)req + sizeof(usb_setup_packet_t); + req->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + req->bRequest = request; + req->wValue = value; + req->wIndex = cdc_dev->notif.intf_desc->bInterfaceNumber; + req->wLength = data_len; + + if (in_transfer) { + req->bmRequestType |= USB_BM_REQUEST_TYPE_DIR_IN; + } else { + memcpy(start_of_data, data, data_len); + } + + cdc_dev->ctrl_transfer->num_bytes = data_len + sizeof(usb_setup_packet_t); + ESP_GOTO_ON_ERROR( + usb_host_transfer_submit_control(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->ctrl_transfer), + unblock, + TAG, + "CTRL transfer failed"); + + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(1000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 1 second + if (!taken) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->ctrl_transfer); + ret = ESP_ERR_TIMEOUT; + goto unblock; + } + + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Control transfer error"); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->actual_num_bytes == cdc_dev->ctrl_transfer->num_bytes, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + + if (in_transfer) { + memcpy(data, start_of_data, data_len); + } + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->ctrl_mux); + return ret; +} + +esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data) +{ + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + if (comm != NULL) *comm = cdc_dev->comm_protocol; + if (data != NULL) *data = cdc_dev->data_protocol; + return ESP_OK; +} diff --git a/app/drivers/data_port/usb-host/cdc/cdc_acm_host/include/usb/cdc_acm_host.h b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/include/usb/cdc_acm_host.h new file mode 100644 index 0000000..f077fe7 --- /dev/null +++ b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/include/usb/cdc_acm_host.h @@ -0,0 +1,306 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "usb_types_cdc.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct cdc_dev_s *cdc_acm_dev_hdl_t; + +/** + * @brief Line Coding structure + * @see Table 17, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint32_t dwDTERate; // in bits per second + uint8_t bCharFormat; // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits + uint8_t bParityType; // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space + uint8_t bDataBits; // 5, 6, 7, 8 or 16 +} __attribute__((packed)) cdc_acm_line_coding_t; + +/** + * @brief UART State Bitmap + * @see Table 31, USB CDC-PSTN specification rev. 1.2 + */ +typedef union { + struct { + uint16_t bRxCarrier : 1; // State of receiver carrier detection mechanism of device. This signal corresponds to V.24 signal 109 and RS-232 signal DCD. + uint16_t bTxCarrier : 1; // State of transmission carrier. This signal corresponds to V.24 signal 106 and RS-232 signal DSR. + uint16_t bBreak : 1; // State of break detection mechanism of the device. + uint16_t bRingSignal : 1; // State of ring signal detection of the device. + uint16_t bFraming : 1; // A framing error has occurred. + uint16_t bParity : 1; // A parity error has occurred. + uint16_t bOverRun : 1; // Received data has been discarded due to overrun in the device. + uint16_t reserved : 9; + }; + uint16_t val; +} cdc_acm_uart_state_t; + +/** + * @brief CDC-ACM Device Event types to upper layer + * + */ +typedef enum { + CDC_ACM_HOST_ERROR, + CDC_ACM_HOST_SERIAL_STATE, + CDC_ACM_HOST_NETWORK_CONNECTION, + CDC_ACM_HOST_DEVICE_DISCONNECTED +} cdc_acm_host_dev_event_t; + +/** + * @brief CDC-ACM Device Event data structure + * + */ +typedef struct { + cdc_acm_host_dev_event_t type; + union { + int error; // Error code from USB Host + cdc_acm_uart_state_t serial_state; // Serial (UART) state + bool network_connected; // Network connection event + } data; +} cdc_acm_host_dev_event_data_t; + +/** + * @brief Data receive callback type + */ +typedef void (*cdc_acm_data_callback_t)(uint8_t *data, size_t data_len, void *user_arg); + +/** + * @brief Device event callback type + * @see cdc_acm_host_dev_event_t + */ +typedef void (*cdc_acm_host_dev_callback_t)(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx); + +/** + * @brief Configuration structure of USB Host CDC-ACM driver + * + */ +typedef struct { + size_t driver_task_stack_size; /**< Stack size of the driver's task */ + unsigned driver_task_priority; /**< Priority of the driver's task */ + int xCoreID; /**< Core affinity of the driver's task */ +} cdc_acm_host_driver_config_t; + +/** + * @brief Configuration structure of CDC-ACM device + * + */ +typedef struct { + uint32_t connection_timeout_ms; /**< Timeout for USB device connection in [ms] */ + size_t out_buffer_size; /**< Maximum size of USB bulk out transfer, set to 0 for read-only devices */ + cdc_acm_host_dev_callback_t event_cb; /**< Device's event callback function. Can be NULL */ + cdc_acm_data_callback_t data_cb; /**< Device's data RX callback function. Can be NULL for write-only devices */ + void *user_arg; /**< User's argument that will be passed to the callbacks */ +} cdc_acm_host_device_config_t; + +/** + * @brief Install CDC-ACM driver + * + * - USB Host Library must already be installed before calling this function (via usb_host_install()) + * - This function should be called before calling any other CDC driver functions + * + * @param[in] driver_config Driver configuration structure. If set to NULL, a default configuration will be used. + * @return esp_err_t + */ +esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config); + +/** + * @brief Uninstall CDC-ACM driver + * + * - Users must ensure that all CDC devices must be closed via cdc_acm_host_close() before calling this function + * + * @return esp_err_t + */ +esp_err_t cdc_acm_host_uninstall(void); + +/** + * @brief Open CDC-ACM compliant device + * + * CDC-ACM compliant device must contain either an Interface Association Descriptor or CDC-Union descriptor, + * which are used for the driver's configuration. + * + * @param[in] vid Device's Vendor ID + * @param[in] pid Device's Product ID + * @param[in] interface_idx Index of device's interface used for CDC-ACM communication + * @param[in] dev_config Configuration structure of the device + * @param[out] cdc_hdl_ret CDC device handle + * @return esp_err_t + */ +esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret); + +/** + * @brief Open CDC-ACM non-compliant device + * + * CDC-ACM non-compliant device acts as CDC-ACM device but doesn't support all its features. + * User must provide the interface index that will be used (zero for non-composite devices). + * + * @param[in] vid Device's Vendor ID + * @param[in] pid Device's Product ID + * @param[in] interface_idx Index of device's interface used for CDC-ACM like communication + * @param[in] dev_config Configuration structure of the device + * @param[out] cdc_hdl_ret CDC device handle + * @return esp_err_t + */ +esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_num, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret); + +/** + * @brief Close CDC device and release its resources + * + * @note All in-flight transfers will be prematurely canceled. + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @return esp_err_t + */ +esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl); + +/** + * @brief Transmit data - blocking mode + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] data Data to be sent + * @param[in] data_len Data length + * @param[in] timeout_ms Timeout in [ms] + * @return esp_err_t + */ +esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms); + +/** + * @brief SetLineCoding function + * + * @see Chapter 6.3.10, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ +esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding); + +/** + * @brief GetLineCoding function + * + * @see Chapter 6.3.11, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[out] line_coding Line Coding structure to be filled + * @return esp_err_t + */ +esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding); + +/** + * @brief SetControlLineState function + * + * @see Chapter 6.3.12, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ +esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts); + +/** + * @brief SendBreak function + * + * This function will block until the duration_ms has passed. + * + * @see Chapter 6.3.13, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] duration_ms Duration of the Break signal in [ms] + * @return esp_err_t + */ +esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms); + +/** + * @brief Print CDC-ACM specific descriptors + * + * Descriptors are printed in human readable format to stdout. + * Intended for debugging and for CDC-ACM compliant devices only. + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + */ +void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl); + +/** + * @brief Get protocols defined in USB-CDC interface descriptors + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[out] comm Communication protocol + * @param[out] data Data protocol + * @return esp_err_t + */ +esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data); + +#ifdef __cplusplus +} +class CdcAcmDevice +{ +public: + // Operators + CdcAcmDevice() : + cdc_hdl(NULL){}; + ~CdcAcmDevice() + { + // Close CDC-ACM device, if it wasn't explicitly closed + if (this->cdc_hdl != NULL) { + this->close(); + } + } + + inline esp_err_t tx_blocking(uint8_t *data, size_t len, uint32_t timeout_ms = 100) + { + return cdc_acm_host_data_tx_blocking(this->cdc_hdl, data, len, timeout_ms); + } + + inline esp_err_t open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config) + { + return cdc_acm_host_open(vid, pid, interface_idx, dev_config, &this->cdc_hdl); + } + + inline esp_err_t open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config) + { + return cdc_acm_host_open_vendor_specific(vid, pid, interface_idx, dev_config, &this->cdc_hdl); + } + + inline void close() + { + cdc_acm_host_close(this->cdc_hdl); + this->cdc_hdl = NULL; + } + + inline esp_err_t line_coding_get(cdc_acm_line_coding_t *line_coding) + { + return cdc_acm_host_line_coding_get(this->cdc_hdl, line_coding); + } + + inline esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding) + { + return cdc_acm_host_line_coding_set(this->cdc_hdl, line_coding); + } + + inline esp_err_t set_control_line_state(bool dtr, bool rts) + { + return cdc_acm_host_set_control_line_state(this->cdc_hdl, dtr, rts); + } + + inline esp_err_t send_break(uint16_t duration_ms) + { + return cdc_acm_host_send_break(this->cdc_hdl, duration_ms); + } + +private: + CdcAcmDevice(const CdcAcmDevice &Copy); + CdcAcmDevice &operator=(const CdcAcmDevice &Copy); + bool operator==(const CdcAcmDevice ¶m) const; + bool operator!=(const CdcAcmDevice ¶m) const; + cdc_acm_dev_hdl_t cdc_hdl; +}; +#endif diff --git a/app/drivers/data_port/usb-host/cdc/cdc_acm_host/include/usb/usb_types_cdc.h b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/include/usb/usb_types_cdc.h new file mode 100644 index 0000000..242d4e2 --- /dev/null +++ b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/include/usb/usb_types_cdc.h @@ -0,0 +1,206 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include + +/** + * @brief USB CDC Descriptor Subtypes + * + * @see Table 13, USB CDC specification rev. 1.2 + */ +typedef enum { + CDC_DESC_SUBTYPE_HEADER = 0x00, // Header Functional Descriptor + CDC_DESC_SUBTYPE_CALL = 0x01, // Call Management Functional Descriptor + CDC_DESC_SUBTYPE_ACM = 0x02, // Abstract Control Management Functional Descriptor + CDC_DESC_SUBTYPE_DLM = 0x03, // Direct Line Management Functional Descriptor + CDC_DESC_SUBTYPE_TEL_RINGER = 0x04, // Telephone Ringer Functional Descriptor + CDC_DESC_SUBTYPE_TEL_CLSR = 0x05, // Telephone Call and Line State Reporting Capabilities Functional Descriptor + CDC_DESC_SUBTYPE_UNION = 0x06, // Union Functional Descriptor + CDC_DESC_SUBTYPE_COUNTRY = 0x07, // Country Selection Functional Descriptor + CDC_DESC_SUBTYPE_TEL_MODE = 0x08, // Telephone Operational Modes Functional Descriptor + CDC_DESC_SUBTYPE_TERMINAL = 0x09, // USB Terminal + CDC_DESC_SUBTYPE_NCHT = 0x0A, // Network Channel Terminal + CDC_DESC_SUBTYPE_PROTOCOL = 0x08, // Protocol Unit + CDC_DESC_SUBTYPE_EXTENSION = 0x0C, // Extension Unit + CDC_DESC_SUBTYPE_MULTI_CHAN = 0x0D, // Multi-Channel Management Functional Descriptor + CDC_DESC_SUBTYPE_CAPI = 0x0E, // CAPI Control + CDC_DESC_SUBTYPE_ETH = 0x0F, // Ethernet Networking + CDC_DESC_SUBTYPE_ATM = 0x10, // ATM Networking + CDC_DESC_SUBTYPE_WHANDSET = 0x11, // Wireless Handset Control Model Functional Descriptor + CDC_DESC_SUBTYPE_MDLM = 0x12, // Mobile Direct Line Model + CDC_DESC_SUBTYPE_MDLM_DETAIL = 0x13, // MDLM Detail + CDC_DESC_SUBTYPE_DMM = 0x14, // Device Management Model + CDC_DESC_SUBTYPE_OBEX = 0x15, // OBEX Functional + CDC_DESC_SUBTYPE_COMMAND_SET = 0x16, // Command Set + CDC_DESC_SUBTYPE_COMMAND_SET_DETAIL = 0x17, // Command Set Detail Functional Descriptor + CDC_DESC_SUBTYPE_TEL_CM = 0x18, // Telephone Control Model Functional Descriptor + CDC_DESC_SUBTYPE_OBEX_SERVICE = 0x19, // OBEX Service Identifier Functional Descriptor + CDC_DESC_SUBTYPE_NCM = 0x1A // NCM Functional Descriptor +} __attribute__((packed)) cdc_desc_subtype_t; + +/** + * @brief USB CDC Subclass codes + * + * @see Table 4, USB CDC specification rev. 1.2 + */ +typedef enum { + CDC_SUBCLASS_DLCM = 0x01, // Direct Line Control Model + CDC_SUBCLASS_ACM = 0x02, // Abstract Control Model + CDC_SUBCLASS_TCM = 0x03, // Telephone Control Model + CDC_SUBCLASS_MCHCM = 0x04, // Multi-Channel Control Model + CDC_SUBCLASS_CAPI = 0x05, // CAPI Control Model + CDC_SUBCLASS_ECM = 0x06, // Ethernet Networking Control Model + CDC_SUBCLASS_ATM = 0x07, // ATM Networking Model + CDC_SUBCLASS_HANDSET = 0x08, // Wireless Handset Control Model + CDC_SUBCLASS_DEV_MAN = 0x09, // Device Management + CDC_SUBCLASS_MOBILE = 0x0A, // Mobile Direct Line Model + CDC_SUBCLASS_OBEX = 0x0B, // OBEX + CDC_SUBCLASS_EEM = 0x0C, // Ethernet Emulation Model + CDC_SUBCLASS_NCM = 0x0D // Network Control Model +} __attribute__((packed)) cdc_subclass_t; + +/** + * @brief USB CDC Communications Protocol Codes + * + * @see Table 5, USB CDC specification rev. 1.2 + */ +typedef enum { + CDC_COMM_PROTOCOL_NONE = 0x00, // No class specific protocol required + CDC_COMM_PROTOCOL_V250 = 0x01, // AT Commands: V.250 etc + CDC_COMM_PROTOCOL_PCAA = 0x02, // AT Commands defined by PCCA-101 + CDC_COMM_PROTOCOL_PCAA_A = 0x03, // AT Commands defined by PCAA-101 & Annex O + CDC_COMM_PROTOCOL_GSM = 0x04, // AT Commands defined by GSM 07.07 + CDC_COMM_PROTOCOL_3GPP = 0x05, // AT Commands defined by 3GPP 27.007 + CDC_COMM_PROTOCOL_TIA = 0x06, // AT Commands defined by TIA for CDMA + CDC_COMM_PROTOCOL_EEM = 0x07, // Ethernet Emulation Model + CDC_COMM_PROTOCOL_EXT = 0xFE, // External Protocol: Commands defined by Command Set Functional Descriptor + CDC_COMM_PROTOCOL_VENDOR = 0xFF // Vendor-specific +} __attribute__((packed)) cdc_comm_protocol_t; + +/** + * @brief USB CDC Data Protocol Codes + * + * @see Table 7, USB CDC specification rev. 1.2 + */ +typedef enum { + CDC_DATA_PROTOCOL_NONE = 0x00, // No class specific protocol required + CDC_DATA_PROTOCOL_NCM = 0x01, // Network Transfer Block + CDC_DATA_PROTOCOL_I430 = 0x30, // Physical interface protocol for ISDN BRI + CDC_DATA_PROTOCOL_HDLC = 0x31, // HDLC + CDC_DATA_PROTOCOL_Q921M = 0x50, // Management protocol for Q.921 data link protocol + CDC_DATA_PROTOCOL_Q921 = 0x51, // Data link protocol for Q.931 + CDC_DATA_PROTOCOL_Q921TM = 0x52, // TEI-multiplexor for Q.921 data link protocol + CDC_DATA_PROTOCOL_V42BIS = 0x90, // Data compression procedures + CDC_DATA_PROTOCOL_Q931 = 0x91, // Euro-ISDN protocol control + CDC_DATA_PROTOCOL_V120 = 0x92, // V.24 rate adaptation to ISDN + CDC_DATA_PROTOCOL_CAPI = 0x93, // CAPI Commands + CDC_DATA_PROTOCOL_VENDOR = 0xFF // Vendor-specific +} __attribute__((packed)) cdc_data_protocol_t; + +/** + * @brief USB CDC Request Codes + * + * @see Table 19, USB CDC specification rev. 1.2 + */ +typedef enum { + CDC_REQ_SEND_ENCAPSULATED_COMMAND = 0x00, + CDC_REQ_GET_ENCAPSULATED_RESPONSE = 0x01, + CDC_REQ_SET_COMM_FEATURE = 0x02, + CDC_REQ_GET_COMM_FEATURE = 0x03, + CDC_REQ_CLEAR_COMM_FEATURE = 0x04, + CDC_REQ_SET_AUX_LINE_STATE = 0x10, + CDC_REQ_SET_HOOK_STATE = 0x11, + CDC_REQ_PULSE_SETUP = 0x12, + CDC_REQ_SEND_PULSE = 0x13, + CDC_REQ_SET_PULSE_TIME = 0x14, + CDC_REQ_RING_AUX_JACK = 0x15, + CDC_REQ_SET_LINE_CODING = 0x20, + CDC_REQ_GET_LINE_CODING = 0x21, + CDC_REQ_SET_CONTROL_LINE_STATE = 0x22, + CDC_REQ_SEND_BREAK = 0x23, + CDC_REQ_SET_RINGER_PARMS = 0x30, + CDC_REQ_GET_RINGER_PARMS = 0x31, + CDC_REQ_SET_OPERATION_PARMS = 0x32, + CDC_REQ_GET_OPERATION_PARMS = 0x33, + CDC_REQ_SET_LINE_PARMS = 0x34, + CDC_REQ_GET_LINE_PARMS = 0x35, + CDC_REQ_DIAL_DIGITS = 0x36, + CDC_REQ_SET_UNIT_PARAMETER = 0x37, + CDC_REQ_GET_UNIT_PARAMETER = 0x38, + CDC_REQ_CLEAR_UNIT_PARAMETER = 0x39, + CDC_REQ_GET_PROFILE = 0x3A, + CDC_REQ_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + CDC_REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + CDC_REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + CDC_REQ_SET_ETHERNET_PACKET_FILTER = 0x43, + CDC_REQ_GET_ETHERNET_STATISTIC = 0x44, + CDC_REQ_SET_ATM_DATA_FORMAT = 0x50, + CDC_REQ_GET_ATM_DEVICE_STATISTICS = 0x51, + CDC_REQ_SET_ATM_DEFAULT_VC = 0x52, + CDC_REQ_GET_ATM_VC_STATISTICS = 0x53, + CDC_REQ_GET_NTB_PARAMETERS = 0x80, + CDC_REQ_GET_NET_ADDRESS = 0x81, + CDC_REQ_SET_NET_ADDRESS = 0x82, + CDC_REQ_GET_NTB_FORMAT = 0x83, + CDC_REQ_SET_NTB_FORMAT = 0x84, + CDC_REQ_GET_NTB_INPUT_SIZE = 0x85, + CDC_REQ_SET_NTB_INPUT_SIZE = 0x86, + CDC_REQ_GET_MAX_DATAGRAM_SIZE = 0x87, + CDC_REQ_SET_MAX_DATAGRAM_SIZE = 0x88, + CDC_REQ_GET_CRC_MODE = 0x89, + CDC_REQ_SET_CRC_MODE = 0x8A +} __attribute__((packed)) cdc_request_code_t; + +/** + * @brief USB CDC Notification Codes + * + * @see Table 20, USB CDC specification rev. 1.2 + */ +typedef enum { + CDC_NOTIF_NETWORK_CONNECTION = 0x00, + CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, + CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08, + CDC_NOTIF_RING_DETECT = 0x09, + CDC_NOTIF_SERIAL_STATE = 0x20, + CDC_NOTIF_CALL_STATE_CHANGE = 0x28, + CDC_NOTIF_LINE_STATE_CHANGE = 0x29, + CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A +} __attribute__((packed)) cdc_notification_code_t; + +typedef struct { + uint8_t bmRequestType; + cdc_notification_code_t bNotificationCode; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint8_t Data[]; +} __attribute__((packed)) cdc_notification_t; + +/** + * @brief USB CDC Header Functional Descriptor + * + * @see Table 15, USB CDC specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05 + const cdc_desc_subtype_t bDescriptorSubtype; + uint16_t bcdCDC; // CDC version as binary-coded decimal. This driver is written for version 1.2 +} __attribute__((packed)) cdc_header_desc_t; + +/** + * @brief USB CDC Union Functional Descriptor + * + * @see Table 16, USB CDC specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05 + const cdc_desc_subtype_t bDescriptorSubtype; + const uint8_t bControlInterface; // Master/controlling interface + uint8_t bSubordinateInterface[]; // Slave/subordinate interfaces +} __attribute__((packed)) cdc_union_desc_t; diff --git a/app/drivers/data_port/usb-host/cdc/cdc_acm_host/test/CMakeLists.txt b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/test/CMakeLists.txt new file mode 100644 index 0000000..067cc9b --- /dev/null +++ b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/test/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "test_cdc_acm_host.c" + INCLUDE_DIRS "." + REQUIRES cdc_acm_host unity) diff --git a/app/drivers/data_port/usb-host/cdc/cdc_acm_host/test/test_cdc_acm_host.c b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/test/test_cdc_acm_host.c new file mode 100644 index 0000000..b1bf076 --- /dev/null +++ b/app/drivers/data_port/usb-host/cdc/cdc_acm_host/test/test_cdc_acm_host.c @@ -0,0 +1,378 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_USB_OTG_SUPPORTED + +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" + +#include "esp_private/usb_phy.h" +#include "usb/usb_host.h" +#include "usb/cdc_acm_host.h" +#include + +#include "esp_intr_alloc.h" + +#include "unity.h" +#include "soc/usb_wrap_struct.h" + +static uint8_t tx_buf[] = "HELLO"; +static uint8_t tx_buf2[] = "WORLD"; +static int nb_of_responses; +static int nb_of_responses2; +static usb_phy_handle_t phy_hdl = NULL; + +static void force_conn_state(bool connected, TickType_t delay_ticks) +{ + TEST_ASSERT_NOT_EQUAL(NULL, phy_hdl); + if (delay_ticks > 0) { + //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. + vTaskDelay(delay_ticks); + } + ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN)); +} + +void usb_lib_task(void *arg) +{ + //Initialize the internal USB PHY to connect to the USB OTG peripheral. We manually install the USB PHY for testing + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device + .gpio_conf = NULL, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl)); + // Install USB Host driver. Should only be called once in entire application + const usb_host_config_t host_config = { + .skip_phy_setup = true, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config)); + printf("USB Host installed\n"); + xTaskNotifyGive(arg); + + while (1) { + // Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + printf("No more clients: clean up\n"); + // The device should not have been freed yet, so we expect an ESP_ERR_NOT_FINISHED + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FINISHED, usb_host_device_free_all()); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + printf("All free: uninstall USB lib\n"); + break; + } + } + + // Clean up USB Host + vTaskDelay(10); // Short delay to allow clients clean-up + usb_host_lib_handle_events(0, NULL); // Make sure there are now pending events + TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall()); + //Tear down USB PHY + TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl)); + phy_hdl = NULL; + vTaskDelete(NULL); +} + +void test_install_cdc_driver(void) +{ + // Create a task that will handle USB library events + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(usb_lib_task, "usb_lib", 4*4096, xTaskGetCurrentTaskHandle(), 10, NULL)); + ulTaskNotifyTake(false, 1000); + + printf("Installing CDC-ACM driver\n"); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_install(NULL)); +} + +/* ------------------------------- Callbacks -------------------------------- */ +static void handle_rx(uint8_t *data, size_t data_len, void *arg) +{ + printf("Data received\n"); + nb_of_responses++; + TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len); +} + +static void handle_rx2(uint8_t *data, size_t data_len, void *arg) +{ + printf("Data received 2\n"); + nb_of_responses2++; + TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len); +} + +static void notif_cb(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx) +{ + switch (event->type) { + case CDC_ACM_HOST_ERROR: + printf("Error event %d\n", event->data.error); + break; + case CDC_ACM_HOST_SERIAL_STATE: + break; + case CDC_ACM_HOST_NETWORK_CONNECTION: + break; + case CDC_ACM_HOST_DEVICE_DISCONNECTED: + printf("Disconnection event\n"); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_hdl)); + xTaskNotifyGive(user_ctx); + break; + default: + assert(false); + } +} + +/* Basic test to check CDC communication: + * open/read/write/close device + * CDC-ACM specific commands: set/get_line_coding, set_control_line_state */ +TEST_CASE("USB Host CDC-ACM driver: Basic test", "[cdc_acm][ignore]") +{ + nb_of_responses = 0; + cdc_acm_dev_hdl_t cdc_dev = NULL; + + test_install_cdc_driver(); + + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 500, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx, + .user_arg = tx_buf, + }; + + printf("Opening CDC-ACM device\n"); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); // 0x303A:0x4002 (TinyUSB Dual CDC device) + TEST_ASSERT_NOT_NULL(cdc_dev); + cdc_acm_host_desc_print(cdc_dev); + vTaskDelay(100); + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000)); + vTaskDelay(100); // Wait until responses are processed + + // We sent two messages, should get two responses + TEST_ASSERT_EQUAL(2, nb_of_responses); + + cdc_acm_line_coding_t line_coding_get; + const cdc_acm_line_coding_t line_coding_set = { + .dwDTERate = 9600, + .bDataBits = 7, + .bParityType = 1, + .bCharFormat = 1, + }; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_set(cdc_dev, &line_coding_set)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_get(cdc_dev, &line_coding_get)); + TEST_ASSERT_EQUAL_MEMORY(&line_coding_set, &line_coding_get, sizeof(cdc_acm_line_coding_t)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_set_control_line_state(cdc_dev, true, false)); + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + + vTaskDelay(20); //Short delay to allow task to be cleaned up +} + +/* Test communication with multiple CDC-ACM devices from one thread */ +TEST_CASE("USB Host CDC-ACM driver: Multiple devices test", "[cdc_acm][ignore]") +{ + nb_of_responses = 0; + nb_of_responses2 = 0; + + test_install_cdc_driver(); + + printf("Opening 2 CDC-ACM devices\n"); + cdc_acm_dev_hdl_t cdc_dev1, cdc_dev2; + cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 1000, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx, + .user_arg = tx_buf, + }; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev1)); // 0x303A:0x4002 (TinyUSB Dual CDC device) + dev_config.data_cb = handle_rx2; + dev_config.user_arg = tx_buf2; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 2, &dev_config, &cdc_dev2)); // 0x303A:0x4002 (TinyUSB Dual CDC device) + TEST_ASSERT_NOT_NULL(cdc_dev1); + TEST_ASSERT_NOT_NULL(cdc_dev2); + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev1, tx_buf, sizeof(tx_buf), 1000)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev2, tx_buf2, sizeof(tx_buf2), 1000)); + + vTaskDelay(100); // Wait for RX callbacks + + // We sent two messages, should get two responses + TEST_ASSERT_EQUAL(1, nb_of_responses); + TEST_ASSERT_EQUAL(1, nb_of_responses2); + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev1)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev2)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + + //Short delay to allow task to be cleaned up + vTaskDelay(20); +} + +#define MULTIPLE_THREADS_TRANSFERS_NUM 5 +#define MULTIPLE_THREADS_TASKS_NUM 4 +void tx_task(void *arg) +{ + cdc_acm_dev_hdl_t cdc_dev = (cdc_acm_dev_hdl_t) arg; + // Send multiple transfers to make sure that some of them will run at the same time + for (int i = 0; i < MULTIPLE_THREADS_TRANSFERS_NUM; i++) { + // BULK endpoints + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000)); + + // CTRL endpoints + cdc_acm_line_coding_t line_coding_get; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_get(cdc_dev, &line_coding_get)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_set_control_line_state(cdc_dev, true, false)); + } + vTaskDelete(NULL); +} + +/** + * @brief Multiple threads test + * + * In this test, one CDC device is accessed from multiple threads. + * It has to be opened/closed just once, though. + */ +TEST_CASE("USB Host CDC-ACM driver: Multiple threads test", "[cdc_acm][ignore]") +{ + nb_of_responses = 0; + cdc_acm_dev_hdl_t cdc_dev; + test_install_cdc_driver(); + + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 5000, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx, + .user_arg = tx_buf, + }; + + printf("Opening CDC-ACM device\n"); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); // 0x303A:0x4002 (TinyUSB Dual CDC device) + TEST_ASSERT_NOT_NULL(cdc_dev); + + // Create two tasks that will try to access cdc_dev + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(tx_task, "CDC TX", 4096, cdc_dev, i + 3, NULL)); + } + + // Wait until all tasks finish + vTaskDelay(pdMS_TO_TICKS(500)); + TEST_ASSERT_EQUAL(MULTIPLE_THREADS_TASKS_NUM * MULTIPLE_THREADS_TRANSFERS_NUM, nb_of_responses); + + // Clean-up + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); +} + +/* Test CDC driver reaction to USB device sudden disconnection */ +TEST_CASE("USB Host CDC-ACM driver: Sudden disconnection test", "[cdc_acm][ignore]") +{ + test_install_cdc_driver(); + + cdc_acm_dev_hdl_t cdc_dev; + cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 1000, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx + }; + dev_config.user_arg = xTaskGetCurrentTaskHandle(); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + TEST_ASSERT_NOT_NULL(cdc_dev); + + force_conn_state(false, pdMS_TO_TICKS(10)); + // Notify will succeed only if CDC_ACM_HOST_DEVICE_DISCONNECTED notification was generated + TEST_ASSERT_EQUAL(1, ulTaskNotifyTake(false, pdMS_TO_TICKS(100))); + + force_conn_state(true, 0); // Switch back to real PHY + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); //Short delay to allow task to be cleaned up +} + +/** + * @brief CDC-ACM error handling test + * + * There are multiple erroneous scenarios checked in this test: + * + * -# Install CDC-ACM driver without USB Host + * -# Open device without installed driver + * -# Uninstall driver before installing it + * -# Open non-existent device + * -# Open the same device twice + * -# Uninstall driver with open devices + * -# Send data that is too large + * -# Send unsupported CDC request + * -# Write to read-only device + */ +TEST_CASE("USB Host CDC-ACM driver: Error handling", "[cdc_acm][ignore]") +{ + cdc_acm_dev_hdl_t cdc_dev; + cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 500, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx + }; + + // Install CDC-ACM driver without USB Host + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_install(NULL)); + + // Open device without installed driver + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + + // Uninstall driver before installing it + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_uninstall()); + + // Properly install USB and CDC drivers + test_install_cdc_driver(); + + // Open non-existent device + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, cdc_acm_host_open(0x303A, 0x1234, 0, &dev_config, &cdc_dev)); // 0x303A:0x1234 this device is not connected to USB Host + TEST_ASSERT_NULL(cdc_dev); + + // Open regular device + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + TEST_ASSERT_NOT_NULL(cdc_dev); + + // Open one CDC-ACM device twice //@todo this test is commented out due to bug in usb_host + //cdc_acm_dev_hdl_t cdc_dev_test; + //TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev_test)); + //TEST_ASSERT_NULL(cdc_dev_test); + + // Uninstall driver with open devices + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_uninstall()); + + // Send data that is too large and NULL data + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, 1024, 1000)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, cdc_acm_host_data_tx_blocking(cdc_dev, NULL, 10, 1000)); + + // Change mode to read-only and try to write to it + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + dev_config.out_buffer_size = 0; // Read-only device + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + TEST_ASSERT_NOT_NULL(cdc_dev); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000)); + + // Send unsupported CDC request (TinyUSB accepts SendBreak command, eventhough it doesn't support it) + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_send_break(cdc_dev, 100)); + + // Clean-up + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); +} + +#endif diff --git a/app/drivers/data_port/usb-host/component.mk b/app/drivers/data_port/usb-host/component.mk new file mode 100755 index 0000000..a98f634 --- /dev/null +++ b/app/drivers/data_port/usb-host/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/app/drivers/data_port/usb-host/config/hardware_define.h b/app/drivers/data_port/usb-host/config/hardware_define.h new file mode 100644 index 0000000..3b40e8d --- /dev/null +++ b/app/drivers/data_port/usb-host/config/hardware_define.h @@ -0,0 +1,11 @@ +#pragma once + +#define SBTASK_PRIORITY_USB_HOST 14 +#define SBTASK_PRIORITY_USB_DEVICE 15 +#define SBTASK_PRIORITY_USB_CDC 17 +#define SBTASK_PRIORITY_USB_MSC 18 + +#define SBTASK_CORE_INDEX_USB_HOST 0 +#define SBTASK_CORE_INDEX_USB_DEVICE 0 +#define SBTASK_CORE_INDEX_USB_CDC 0 +#define SBTASK_CORE_INDEX_USB_MSC 0 diff --git a/app/drivers/data_port/usb-host/esp_usbh_cdc.h b/app/drivers/data_port/usb-host/esp_usbh_cdc.h new file mode 100644 index 0000000..819d8c5 --- /dev/null +++ b/app/drivers/data_port/usb-host/esp_usbh_cdc.h @@ -0,0 +1,109 @@ +// Copyright 2019-2021 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 "stdio.h" +#include "freertos/FreeRTOS.h" +#include "esp_err.h" +#include "hal/usbh_ll.h" +#include "hcd.h" + +/** + * @brief USB host CDC configuration type, callbacks should not in block state + */ +typedef struct usbh_cdc_config +{ + uint8_t bulk_in_ep_addr; /*!< USB CDC bulk in endpoint address, will be overwritten if bulk_in_ep is specified */ + uint8_t bulk_out_ep_addr; /*!< USB CDC bulk out endpoint address, will be overwritten if bulk_out_ep is specified */ + int rx_buffer_size; /*!< USB receive/in pipe_obj size */ + int tx_buffer_size; /*!< USB transport/out pipe_obj size */ + usb_ep_desc_t *bulk_in_ep; /*!< USB CDC bulk in endpoint descriptor, set NULL if using default param */ + usb_ep_desc_t *bulk_out_ep; /*!< USB CDC bulk out endpoint descriptor, set NULL if using default param */ + usbh_cdc_cb_t conn_callback; /*!< USB connect calllback, set NULL if not use */ + usbh_cdc_cb_t disconn_callback; /*!< USB disconnect calllback, usb cdc driver reset to connect waitting after disconnect, set NULL if not use */ + usbh_cdc_cb_t rx_callback; /*!< packet receive callback, set NULL if not use */ + void *conn_callback_arg; /*!< USB connect calllback args, set NULL if not use */ + void *disconn_callback_arg; /*!< USB disconnect calllback args, set NULL if not use */ + void *rx_callback_arg; /*!< packet receive callback args, set NULL if not use */ +} usbh_cdc_config_t; + +/** + * @brief Install USB CDC driver. + * + * @param config USB Host CDC configs + * @return + * ESP_ERR_INVALID_STATE driver has been installed + * ESP_ERR_INVALID_ARG args not supported + * ESP_FAIL driver install failed + * ESP_OK driver install succeed + */ +esp_err_t usbh_cdc_driver_install(const usbh_cdc_config_t *config); + +/** + * @brief Uninstall USB driver. + * + * @return + * ESP_ERR_INVALID_STATE driver not installed + * ESP_OK start succeed + */ +esp_err_t usbh_cdc_driver_delete(void); + +/** + * @brief Waitting until CDC device connect + * + * @param ticks_to_wait Wait timeout value, count in RTOS ticks + * @return + * ESP_ERR_INVALID_STATE driver not installed + * ESP_ERR_TIMEOUT wait timeout + * ESP_OK device connect succeed + */ +esp_err_t usbh_cdc_wait_connect(TickType_t ticks_to_wait); + +/** + * @brief Send data to connected USB device from a given buffer and length, + * this function will return after copying all the data to tx ring buffer. + * + * @param buf data buffer address + * @param length data length to send + * @return int The number of bytes pushed to the tx buffer + */ +int usbh_cdc_write_bytes(const uint8_t *buf, size_t length); + +/** + * @brief Get USB receive ring buffer cached data length. + * + * @param size + * @return + * ESP_ERR_INVALID_STATE cdc not configured, or not running + * ESP_ERR_INVALID_ARG args not supported + * ESP_FAIL start failed + * ESP_OK start succeed + */ +esp_err_t usbh_cdc_get_buffered_data_len(size_t *size); + +/** + * @brief Read data bytes from USB receive/in buffer. + * + * @param buf data buffer address + * @param length data length to read + * @param ticks_to_wait sTimeout, count in RTOS ticks + * @return int The number of bytes read from USB FIFO + */ +int usbh_cdc_read_bytes(uint8_t *buf, size_t length, TickType_t ticks_to_wait); + +/** + * @brief print internal memory usage for debug + * @return void + */ +void usbh_cdc_print_buffer_msg(void); diff --git a/app/drivers/data_port/usb-host/msc/diskio_usb.c b/app/drivers/data_port/usb-host/msc/diskio_usb.c new file mode 100755 index 0000000..7ee91c9 --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/diskio_usb.c @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "diskio_impl.h" +#include "ffconf.h" +#include "ff.h" +#include "esp_log.h" +#include "private_include/diskio_usb.h" +#include "private_include/msc_scsi_bot.h" +#include "private_include/msc_common.h" +#include "usb/usb_types_stack.h" + +static usb_disk_t *s_disks[FF_VOLUMES] = { NULL }; + +static const char *TAG = "diskio_usb"; + +static DSTATUS usb_disk_initialize (BYTE pdrv) +{ + return RES_OK; +} + +static DSTATUS usb_disk_status (BYTE pdrv) +{ + return RES_OK; +} + +static DRESULT usb_disk_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count) +{ + assert(pdrv < FF_VOLUMES); + assert(s_disks[pdrv]); + + esp_err_t err; + usb_disk_t *disk = s_disks[pdrv]; + size_t sector_size = disk->block_size; + msc_device_t *dev = __containerof(disk, msc_device_t, disk); + + for (int i = 0; i < count; i++) { + err = scsi_cmd_read10(dev, &buff[i * sector_size], sector + i, 1, sector_size); + if (err != ESP_OK) { + ESP_LOGE(TAG, "scsi_cmd_read10 failed (%d)", err); + return RES_ERROR; + } + + } + + return RES_OK; +} + +static DRESULT usb_disk_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) +{ + assert(pdrv < FF_VOLUMES); + assert(s_disks[pdrv]); + + esp_err_t err; + usb_disk_t *disk = s_disks[pdrv]; + size_t sector_size = disk->block_size; + msc_device_t *dev = __containerof(disk, msc_device_t, disk); + + for (int i = 0; i < count; i++) { + err = scsi_cmd_write10(dev, &buff[i * sector_size], sector + i, 1, sector_size); + if (err != ESP_OK) { + ESP_LOGE(TAG, "scsi_cmd_write10 failed (%d)", err); + return RES_ERROR; + } + + } + return RES_OK; +} + +static DRESULT usb_disk_ioctl (BYTE pdrv, BYTE cmd, void *buff) +{ + assert(pdrv < FF_VOLUMES); + assert(s_disks[pdrv]); + + usb_disk_t *disk = s_disks[pdrv]; + + switch (cmd) { + case CTRL_SYNC: + return RES_OK; + case GET_SECTOR_COUNT: + *((DWORD *) buff) = disk->block_count; + return RES_OK; + case GET_SECTOR_SIZE: + *((WORD *) buff) = disk->block_size; + return RES_OK; + case GET_BLOCK_SIZE: + return RES_ERROR; + } + return RES_ERROR; +} + +void ff_diskio_register_msc(BYTE pdrv, usb_disk_t *disk) +{ + assert(pdrv < FF_VOLUMES); + + static const ff_diskio_impl_t usb_disk_impl = { + .init = &usb_disk_initialize, + .status = &usb_disk_status, + .read = &usb_disk_read, + .write = &usb_disk_write, + .ioctl = &usb_disk_ioctl + }; + s_disks[pdrv] = disk; + ff_diskio_register(pdrv, &usb_disk_impl); +} + +BYTE ff_diskio_get_pdrv_disk(const usb_disk_t *disk) +{ + for (int i = 0; i < FF_VOLUMES; i++) { + if (disk == s_disks[i]) { + return i; + } + } + return 0xff; +} diff --git a/app/drivers/data_port/usb-host/msc/include/msc_host.h b/app/drivers/data_port/usb-host/msc/include/msc_host.h new file mode 100755 index 0000000..6ed67d4 --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/include/msc_host.h @@ -0,0 +1,308 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_ERR_MSC_HOST_BASE 0x1700 /*!< MSC host error code base */ +#define ESP_ERR_MSC_MOUNT_FAILED (ESP_ERR_MSC_HOST_BASE + 1) /*!< Failed to mount storage */ +#define ESP_ERR_MSC_FORMAT_FAILED (ESP_ERR_MSC_HOST_BASE + 2) /*!< Failed to format storage */ +#define ESP_ERR_MSC_INTERNAL (ESP_ERR_MSC_HOST_BASE + 3) /*!< MSC host internal error */ + +#define MSC_STR_DESC_SIZE 32 + +typedef struct msc_host_device *msc_host_device_handle_t; /**< Handle to a Mass Storage Device */ + +/** + * @brief USB Mass Storage event containing event type and associated device handle. + */ +typedef struct { + enum { + MSC_DEVICE_CONNECTED, /**< MSC device has been connected to the system.*/ + MSC_DEVICE_DISCONNECTED, /**< MSC device has been disconnected from the system.*/ + + CDC_DEVICE_CONNECTED, + CDC_DEVICE_DISCONNECTED, + + DFU_DEVICE_CONNECTED, + DFU_DEVICE_DISCONNECTED, + } event; + union { + uint8_t address; /**< Address of connected MSC device.*/ + msc_host_device_handle_t handle; /**< MSC device handle to disconnected device.*/ + } device; + + uint16_t vendor_id; + uint16_t product_id; +} msc_host_event_t; + +/** + * @brief USB Mass Storage event callback. + * + * @param[in] event mass storage event + */ +typedef void (*msc_host_event_cb_t)(const msc_host_event_t *event, void *arg); + +/** + * @brief MSC configuration structure. + */ +typedef struct { + bool create_backround_task; /**< When set to true, background task handling usb events is created. + Otherwise user has to periodically call msc_host_handle_events function */ + size_t task_priority; /**< Task priority of crated background task */ + size_t stack_size; /**< Stack size of crated background task */ + BaseType_t core_id; /**< Select core on which background task will run or tskNO_AFFINITY */ + msc_host_event_cb_t callback; /**< Callback invoked when MSC event occurs. Must not be NULL. */ + void *callback_arg; /**< User provided argument passed to callback */ +} msc_host_driver_config_t; + +/** + * @brief MSC device info. + */ +typedef struct { + uint32_t sector_count; + uint32_t sector_size; + uint16_t idProduct; + uint16_t idVendor; + wchar_t iManufacturer[MSC_STR_DESC_SIZE]; + wchar_t iProduct[MSC_STR_DESC_SIZE]; + wchar_t iSerialNumber[MSC_STR_DESC_SIZE]; +} msc_host_device_info_t; + +//////cdc + +typedef struct cdc_dev_s *cdc_acm_dev_hdl_t; + +/** + * @brief Line Coding structure + * @see Table 17, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint32_t dwDTERate; // in bits per second + uint8_t bCharFormat; // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits + uint8_t bParityType; // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space + uint8_t bDataBits; // 5, 6, 7, 8 or 16 +} __attribute__((packed)) cdc_acm_line_coding_t; + +/** + * @brief UART State Bitmap + * @see Table 31, USB CDC-PSTN specification rev. 1.2 + */ +typedef union { + struct { + uint16_t bRxCarrier : 1; // State of receiver carrier detection mechanism of device. This signal corresponds to V.24 signal 109 and RS-232 signal DCD. + uint16_t bTxCarrier : 1; // State of transmission carrier. This signal corresponds to V.24 signal 106 and RS-232 signal DSR. + uint16_t bBreak : 1; // State of break detection mechanism of the device. + uint16_t bRingSignal : 1; // State of ring signal detection of the device. + uint16_t bFraming : 1; // A framing error has occurred. + uint16_t bParity : 1; // A parity error has occurred. + uint16_t bOverRun : 1; // Received data has been discarded due to overrun in the device. + uint16_t reserved : 9; + }; + uint16_t val; +} cdc_acm_uart_state_t; + +/** + * @brief CDC-ACM Device Event types to upper layer + * + */ +typedef enum { + CDC_ACM_HOST_ERROR, + CDC_ACM_HOST_SERIAL_STATE, + CDC_ACM_HOST_NETWORK_CONNECTION, + CDC_ACM_HOST_DEVICE_DISCONNECTED +} cdc_acm_host_dev_event_t; + +/** + * @brief CDC-ACM Device Event data structure + * + */ +typedef struct { + cdc_acm_host_dev_event_t type; + union { + int error; // Error code from USB Host + cdc_acm_uart_state_t serial_state; // Serial (UART) state + bool network_connected; // Network connection event + } data; +} cdc_acm_host_dev_event_data_t; + +/** + * @brief Data receive callback type + */ +typedef void (*cdc_acm_data_callback_t)(uint8_t *data, size_t data_len, void *user_arg); + +/** + * @brief Device event callback type + * @see cdc_acm_host_dev_event_t + */ +typedef void (*cdc_acm_host_dev_callback_t)(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx); + +/** + * @brief Configuration structure of USB Host CDC-ACM driver + * + */ +typedef struct { + size_t driver_task_stack_size; /**< Stack size of the driver's task */ + unsigned driver_task_priority; /**< Priority of the driver's task */ + int xCoreID; /**< Core affinity of the driver's task */ +} cdc_acm_host_driver_config_t; + +/** + * @brief Configuration structure of CDC-ACM device + * + */ +typedef struct { + uint32_t connection_timeout_ms; /**< Timeout for USB device connection in [ms] */ + size_t out_buffer_size; /**< Maximum size of USB bulk out transfer, set to 0 for read-only devices */ + cdc_acm_host_dev_callback_t event_cb; /**< Device's event callback function. Can be NULL */ + cdc_acm_data_callback_t data_cb; /**< Device's data RX callback function. Can be NULL for write-only devices */ + void *user_arg; /**< User's argument that will be passed to the callbacks */ +} cdc_acm_host_device_config_t; + +/////end cdc + +// stm32 dfu update + +#define STM32_DFU_REQUEST_DETACH 0x00 +#define STM32_DFU_REQUEST_DNLOAD 0x01 +#define STM32_DFU_REQUEST_UPLOAD 0x02 +#define STM32_DFU_REQUEST_GETSTATUS 0x03 +#define STM32_DFU_REQUEST_CLRSTATUS 0x04 +#define STM32_DFU_REQUEST_GETSTATE 0x05 +#define STM32_DFU_REQUEST_ABORT 0x06 + +#define STM32_DFU_STATE_APP_IDLE 0 +#define STM32_DFU_STATE_APP_DETACH 1 +#define STM32_DFU_STATE_DFU_IDLE 2 +#define STM32_DFU_STATE_DNLOAD_SYNC 3 +#define STM32_DFU_STATE_DNBUSY 4 +#define STM32_DFU_STATE_DNLOAD_IDLE 5 +#define STM32_DFU_STATE_MAINFES_SYNC 6 +#define STM32_DFU_STATE_MAINFEST 7 +#define STM32_DFU_STATE_MAINFEST_WAIT_RESET 8 +#define STM32_DFU_STATE_UPLOAD_IDLE 9 +#define STM32_DFU_STATE_ERROR 10 + +//// + +/** + * @brief Install USB Host Mass Storage Class driver + * + * @param[in] config configuration structure MSC to create + * @return esp_err_r + */ +esp_err_t msc_host_install(const msc_host_driver_config_t *config); + +/** + * @brief Uninstall Mass Storage Class driver + * @return esp_err_t + */ +esp_err_t msc_host_uninstall(void); + +/** + * @brief Initialization of MSC device. + * + * @param[in] device_address Device address obtained from MSC callback provided upon connection and enumeration + * @param[out] device Mass storage device handle to be used for subsequent calls. + * @return esp_err_t + */ +esp_err_t msc_host_install_device(uint8_t device_address, msc_host_device_handle_t *device); + +/** + * @brief Deinitialization of MSC device. + * + * @param[in] device Device handle obtained from msc_host_install_device function + * @return esp_err_t + */ +esp_err_t msc_host_uninstall_device(msc_host_device_handle_t device); + +/** + * @brief Helper function for reading sector from mass storage device. + * + * @warning This call is not thread safe and should not be combined + * with accesses to storage through file system. + * + * @note Provided sector and size cannot exceed + * sector_count and sector_size obtained from msc_host_device_info_t + * + * @param[in] device Device handle + * @param[in] sector Number of sector to be read + * @param[out] data Buffer into which data will be written + * @param[in] size Number of bytes to be read + * @return esp_err_t + */ +esp_err_t msc_host_read_sector(msc_host_device_handle_t device, size_t sector, void *data, size_t size); + +/** + * @brief Helper function for writing sector to mass storage device. + * + * @warning This call is not thread safe and should not be combined + * with accesses to storare through file system. + * + * @note Provided sector and size cannot exceed + * sector_count and sector_size obtained from msc_host_device_info_t + * + * @param[in] device Device handle + * @param[in] sector Number of sector to be read + * @param[in] data Data to be written to the sector + * @param[in] size Number of bytes to be written + * @return esp_err_t + */ +esp_err_t msc_host_write_sector(msc_host_device_handle_t device, size_t sector, const void *data, size_t size); + +/** + * @brief Handle MSC HOST events. + * + * @param[in] timeout_ms Timeout in miliseconds + * @return esp_err_t + */ +esp_err_t msc_host_handle_events(uint32_t timeout_ms); + +/** + * @brief Gets devices information. + * + * @warning This call is not thread safe and should not be combined + * with accesses to storare through file system. + * + * @param[in] device Handle to device + * @param[out] info Structure to be populated with device info + * @return esp_err_t + */ +esp_err_t msc_host_get_device_info(msc_host_device_handle_t device, msc_host_device_info_t *info); + +/** + * @brief Print configuration descriptor. + * + * @param[in] device Handle of MSC device + * @return esp_err_t + */ +esp_err_t msc_host_print_descriptors(msc_host_device_handle_t device); + +esp_err_t cdc_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret); +void cdc_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl); +void cdc_submit_transfer_in(cdc_acm_dev_hdl_t cdc_hdl); +int cdc_write_bytes(const uint8_t *buf, size_t length); +esp_err_t cdc_host_close(cdc_acm_dev_hdl_t cdc_hdl); + +esp_err_t dfu_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret); +esp_err_t dfu_host_close(cdc_acm_dev_hdl_t cdc_hdl); + +esp_err_t usbh_stm32_get_status_ex(uint8_t *out_result_data /*[6]*/, uint16_t timeout); +esp_err_t usbh_stm32_get_status(uint8_t *out_result_data /*[6]*/); +void usbh_stm32_get_chipinfo(char *descriptors, uint8_t count, uint8_t *actual_desc_count); +uint16_t usbh_stm32_get_transfer_block_size(); + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/app/drivers/data_port/usb-host/msc/include/msc_host_vfs.h b/app/drivers/data_port/usb-host/msc/include/msc_host_vfs.h new file mode 100755 index 0000000..af9137f --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/include/msc_host_vfs.h @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_vfs_fat.h" +#include "msc_host.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct msc_host_vfs *msc_host_vfs_handle_t; /**< VFS handle to attached Mass Storage device */ + +/** + * @brief Register MSC device to Virtual filesystem. + * + * @param[in] device Device handle obtained from MSC callback provided upon initialization + * @param[in] base_path Base VFS path to be used to access file storage + * @param[in] mount_config Mount configuration. + * @param[out] vfs_handle Handle to MSC device associated with registered VFS + * @return esp_err_t + */ +esp_err_t msc_host_vfs_register(msc_host_device_handle_t device, + const char *base_path, + const esp_vfs_fat_mount_config_t *mount_config, + msc_host_vfs_handle_t *vfs_handle); + + +/** + * @brief Unregister MSC device from Virtual filesystem. + * + * @param[in] vfs_handle VFS handle obtained from MSC callback provided upon initialization + * @return esp_err_t + */ +esp_err_t msc_host_vfs_unregister(msc_host_vfs_handle_t vfs_handle); + +#ifdef __cplusplus +} +#endif diff --git a/app/drivers/data_port/usb-host/msc/msc_host.c b/app/drivers/data_port/usb-host/msc/msc_host.c new file mode 100755 index 0000000..aff928b --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/msc_host.c @@ -0,0 +1,2701 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/ringbuf.h" +#include "freertos/event_groups.h" +#include "usb/usb_host.h" +#include "private_include/diskio_usb.h" +#include "private_include/msc_common.h" +#include "include/msc_host.h" +#include "private_include/msc_scsi_bot.h" +#include "usb/usb_types_ch9.h" +#include "usb/usb_helpers.h" +#include "cdc/cdc_acm_host/include/usb/usb_types_cdc.h" +#include "config/hardware_define.h" +#include "os/os.h" + +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_WRN +#define SYS_LOG_DOMAIN "USBH" +#include "sys_log.h" + +static portMUX_TYPE msc_lock = portMUX_INITIALIZER_UNLOCKED; + +#define MSC_ENTER_CRITICAL() portENTER_CRITICAL(&msc_lock) +#define MSC_EXIT_CRITICAL() portEXIT_CRITICAL(&msc_lock) + +#define MSC_GOTO_ON_FALSE_CRITICAL(exp, err) \ + do { \ + if (!(exp)) { \ + MSC_EXIT_CRITICAL(); \ + ret = err; \ + goto fail; \ + } \ + } while (0) + +#define MSC_RETURN_ON_FALSE_CRITICAL(exp, err) \ + do { \ + if (!(exp)) { \ + MSC_EXIT_CRITICAL(); \ + return err; \ + } \ + } while (0) + +#define WAIT_FOR_READY_TIMEOUT_MS 3000 +#define TAG "USB_MSC" + +#define SCSI_COMMAND_SET 0x06 +#define BULK_ONLY_TRANSFER 0x50 +#define MSC_NO_SENSE 0x00 +#define MSC_NOT_READY 0x02 +#define MSC_UNIT_ATTENTION 0x06 + +typedef struct { + usb_host_client_handle_t client_handle; + msc_host_event_cb_t user_cb; + void *user_arg; + SemaphoreHandle_t all_events_handled; + volatile bool end_client_event_handling; +} msc_driver_t; + +static msc_driver_t *s_msc_driver; + +STAILQ_HEAD(devices, msc_host_device) +devices_tailq; + +typedef struct { + uint8_t type; // usbport_status_e + uint32_t vendor_id; + uint32_t product_id; +} usb_device_id_t; + +typedef enum { + USB_DEVICE_TYPE_NONE = 0, // 没有外设 + USB_DEVICE_TYPE_CDC = 1, // 外设处于cdc状态 + USB_DEVICE_TYPE_DFU = 2, // 外设处于dfu状态 + USB_DEVICE_TYPE_MSC = 3, // 外设处于dfu状态 +} usb_device_type_e; + +/////// cdc + +#define CDC_CHECK(a, str, ret) \ + if (!(a)) { \ + SYS_LOG_ERR("%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + return (ret); \ + } + +#define CDC_CHECK_GOTO(a, str, lable) \ + if (!(a)) { \ + SYS_LOG_ERR("%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + goto lable; \ + } + +#define CDC_DATA_TASK_NAME "cdc-data" +#define CDC_DATA_TASK_PRIORITY (5) +#define CDC_DATA_TASK_STACK_SIZE 3072 +#define CDC_DATA_TASK_CORE 0 + +// CDC devices often implement Interface Association Descriptor (IAD). Parse IAD only when +// bDeviceClass = 0xEF (Miscellaneous Device Class), bDeviceSubClass = 0x02 (Common Class), bDeviceProtocol = 0x01 (Interface Association Descriptor) +// @see USB Interface Association Descriptor: Device Class Code and Use Model rev 1.0, Table 1-1 +#define USB_SUBCLASS_COMMON 0x02 +#define USB_DEVICE_PROTOCOL_IAD 0x01 + +// CDC-ACM spinlock +static portMUX_TYPE cdc_acm_lock = portMUX_INITIALIZER_UNLOCKED; +#define CDC_ACM_ENTER_CRITICAL() portENTER_CRITICAL(&cdc_acm_lock) +#define CDC_ACM_EXIT_CRITICAL() portEXIT_CRITICAL(&cdc_acm_lock) + +// CDC-ACM events +#define CDC_ACM_TEARDOWN BIT0 +#define CDC_ACM_TEARDOWN_COMPLETE BIT1 + +// CDC-ACM check macros +#define CDC_ACM_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) + +#define CDC_ACM_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + CDC_ACM_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +// CDC-ACM driver object +typedef struct { + usb_host_client_handle_t cdc_acm_client_hdl; /*!< USB Host handle reused for all CDC-ACM devices in the system */ + SemaphoreHandle_t open_close_mutex; + EventGroupHandle_t event_group; + SLIST_HEAD(list_dev, cdc_dev_s) + cdc_devices_list; /*!< List of open pseudo devices */ +} cdc_acm_obj_t; + +// static cdc_acm_obj_t *p_cdc_acm_obj = NULL; + +/** + * @brief USB CDC PSTN Call Descriptor + * + * @see Table 3, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; + const cdc_desc_subtype_t bDescriptorSubtype; + union { + struct { + uint8_t call_management : 1; // Device handles call management itself + uint8_t call_over_data_if : 1; // Device sends/receives call management information over Data Class interface + uint8_t reserved : 6; + }; + uint8_t val; + } bmCapabilities; + uint8_t bDataInterface; // Interface number of Data Class interface optionally used for call management +} __attribute__((packed)) cdc_acm_call_desc_t; + +/** + * @brief USB CDC PSTN Abstract Control Model Descriptor + * + * @see Table 4, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; + const cdc_desc_subtype_t bDescriptorSubtype; + union { + struct { + uint8_t feature : 1; // Device supports Set/Clear/Get_Comm_Feature requests + uint8_t serial : 1; // Device supports Set/Get_Line_Coding, Set_Control_Line_State and Serial_State request and notifications + uint8_t send_break : 1; // Device supports Send_Break request + uint8_t network : 1; // Device supports Network_Connection notification + uint8_t reserved : 4; + }; + uint8_t val; + } bmCapabilities; +} __attribute__((packed)) cdc_acm_acm_desc_t; + +typedef struct cdc_dev_s cdc_dev_t; +struct cdc_dev_s { + usb_device_handle_t dev_hdl; // USB device handle + void *cb_arg; // Common argument for user's callbacks (data IN and Notification) + struct { + usb_transfer_t *out_xfer; // OUT data transfer + usb_transfer_t *in_xfer; // IN data transfer + cdc_acm_data_callback_t in_cb; // User's callback for async (non-blocking) data IN + const usb_intf_desc_t *intf_desc; // Pointer to data interface descriptor + SemaphoreHandle_t out_mux; // OUT mutex + } data; + + struct { + usb_transfer_t *xfer; // IN notification transfer + const usb_intf_desc_t *intf_desc; // Pointer to notification interface descriptor, can be NULL if there is no notification channel in the device + cdc_acm_host_dev_callback_t cb; // User's callback for device events + } notif; // Structure with Notif pipe data + + usb_transfer_t *ctrl_transfer; // CTRL (endpoint 0) transfer + SemaphoreHandle_t ctrl_mux; // CTRL mutex + cdc_acm_uart_state_t serial_state; // Serial State + cdc_comm_protocol_t comm_protocol; + cdc_data_protocol_t data_protocol; + int num_cdc_intf_desc; // Number of CDC Interface descriptors in following array + const usb_standard_desc_t **cdc_intf_desc; // CDC Interface descriptors + SLIST_ENTRY(cdc_dev_s) + list_entry; +}; + +cdc_dev_t *g_cdc_dev = NULL; + +/** + * @brief Notification received callback + * + * Notification (interrupt) IN transfer is submitted at the end of this function to ensure periodic poll of IN endpoint. + * + * @param[in] transfer Transfer that triggered the callback + */ +static void notif_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief Data received callback + * + * Data (bulk) IN transfer is submitted at the end of this function to ensure continuous poll of IN endpoint. + * + * @param[in] transfer Transfer that triggered the callback + */ +static void in_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief Data send callback + * + * Reused for bulk OUT and CTRL transfers + * + * @param[in] transfer Transfer that triggered the callback + */ +static void out_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief USB Host Client event callback + * + * Handling of USB device connection/disconnection to/from root HUB. + * + * @param[in] event_msg Event message type + * @param[in] arg Caller's argument (not used in this driver) + */ +// static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg); + +static EventGroupHandle_t s_usb_event_group = NULL; +static RingbufHandle_t s_in_ringbuf_handle = NULL; +static RingbufHandle_t s_out_ringbuf_handle = NULL; +static SemaphoreHandle_t s_usb_read_mux = NULL; +static SemaphoreHandle_t s_usb_write_mux = NULL; +// static TaskHandle_t s_usb_processing_task_hdl = NULL; +// static portMUX_TYPE s_in_ringbuf_mux = portMUX_INITIALIZER_UNLOCKED; +static portMUX_TYPE s_out_ringbuf_mux = portMUX_INITIALIZER_UNLOCKED; +volatile static int s_in_buffered_data_len = 0; +volatile static int s_out_buffered_data_len = 0; + +#define USB_TASK_KILL_BIT BIT1 +#define CDC_DATA_TASK_KILL_BIT BIT4 +#define CDC_DEVICE_READY_BIT BIT19 +#define TIMEOUT_USB_RINGBUF_MS 200 // Timeout for Ring Buffer push + +/////// end cdc + +///// stm32 dfu update + +/** + * @brief Initializer for a request to get a device's device descriptor + */ +#define USB_SETUP_PACKET_INIT_GET_FUNCTION_DESC(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \ + (setup_pkt_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \ + (setup_pkt_ptr)->wValue = 0x2100; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 255; \ +}) + +#define USB_SETUP_PACKET_INIT_GET_STATUS(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \ + (setup_pkt_ptr)->bRequest = STM32_DFU_REQUEST_GETSTATUS; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 6; \ +}) + +#define USB_SETUP_PACKET_INIT_CLEAR_STATUS(setup_pkt_ptr) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \ + (setup_pkt_ptr)->bRequest = STM32_DFU_REQUEST_CLRSTATUS; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 0; \ +}) + +#define USB_SETUP_PACKET_INIT_LOAD_ADDRESS(setup_pkt_ptr, pkt_data) ({ \ + (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \ + (setup_pkt_ptr)->bRequest = STM32_DFU_REQUEST_DNLOAD; \ + (setup_pkt_ptr)->wValue = 0; \ + (setup_pkt_ptr)->wIndex = 0; \ + (setup_pkt_ptr)->wLength = 5; \ + memcpy(((uint8_t *)(setup_pkt_ptr)) + sizeof(usb_setup_packet_t), pkt_data, 5); \ +}) + +typedef union { + struct { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bmAttributes; + uint16_t wDetachTimeOut; + uint16_t wTransferSize; + uint8_t bcdDFUVersion; + } __attribute__((packed)); + uint8_t val[USB_SETUP_PACKET_SIZE]; +} usb_function_desc_packet_t; + +#define ENUM_CTRL_TRANSFER_MAX_LEN 256 + +#define STM32_DFU_REQUEST_DETACH 0x00 +#define STM32_DFU_REQUEST_DNLOAD 0x01 +#define STM32_DFU_REQUEST_UPLOAD 0x02 +#define STM32_DFU_REQUEST_GETSTATUS 0x03 +#define STM32_DFU_REQUEST_CLRSTATUS 0x04 +#define STM32_DFU_REQUEST_GETSTATE 0x05 +#define STM32_DFU_REQUEST_ABORT 0x06 + +#define STM32_DFU_STATE_APP_IDLE 0 +#define STM32_DFU_STATE_APP_DETACH 1 +#define STM32_DFU_STATE_DFU_IDLE 2 +#define STM32_DFU_STATE_DNLOAD_SYNC 3 +#define STM32_DFU_STATE_DNBUSY 4 +#define STM32_DFU_STATE_DNLOAD_IDLE 5 +#define STM32_DFU_STATE_MAINFES_SYNC 6 +#define STM32_DFU_STATE_MAINFEST 7 +#define STM32_DFU_STATE_MAINFEST_WAIT_RESET 8 +#define STM32_DFU_STATE_UPLOAD_IDLE 9 +#define STM32_DFU_STATE_ERROR 10 + +///// end stm32 dfu update + +// 兼容USB设备 +static const usb_device_id_t g_compatiable_usb_device_list[] = { + {.type = USB_DEVICE_TYPE_CDC, .vendor_id = 0x1209, .product_id = 0x5741}, + {.type = USB_DEVICE_TYPE_CDC, .vendor_id = 0x483, .product_id = 0x5740}, + {.type = USB_DEVICE_TYPE_DFU, .vendor_id = 0x483, .product_id = 0xDF11}}; + +static uint8_t g_current_device_type = USB_DEVICE_TYPE_NONE; + +static const usb_standard_desc_t *next_interface_desc(const usb_standard_desc_t *desc, size_t len, size_t *offset) +{ + return usb_parse_next_descriptor_of_type(desc, len, USB_W_VALUE_DT_INTERFACE, (int *)offset); +} + +static const usb_standard_desc_t *next_endpoint_desc(const usb_standard_desc_t *desc, size_t len, size_t *offset) +{ + return usb_parse_next_descriptor_of_type(desc, len, USB_B_DESCRIPTOR_TYPE_ENDPOINT, (int *)offset); +} + +static const usb_intf_desc_t *find_msc_interface(const usb_config_desc_t *config_desc, size_t *offset) +{ + size_t total_length = config_desc->wTotalLength; + const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)config_desc; + + next_desc = next_interface_desc(next_desc, total_length, offset); + + while (next_desc) { + const usb_intf_desc_t *ifc_desc = (const usb_intf_desc_t *)next_desc; + + if (ifc_desc->bInterfaceClass == USB_CLASS_MASS_STORAGE && ifc_desc->bInterfaceSubClass == SCSI_COMMAND_SET && ifc_desc->bInterfaceProtocol == BULK_ONLY_TRANSFER) { + return ifc_desc; + } + + next_desc = next_interface_desc(next_desc, total_length, offset); + }; + return NULL; +} + +/** + * @brief Extracts configuration from configuration descriptor. + * + * @note Passes interface and endpoint descriptors to obtain: + + * - interface number, IN endpoint, OUT endpoint, max. packet size + * + * @param[in] cfg_desc Configuration descriptor + * @param[out] cfg Obtained configuration + * @return esp_err_t + */ +static esp_err_t extract_config_from_descriptor(const usb_config_desc_t *cfg_desc, msc_config_t *cfg) +{ + size_t offset = 0; + size_t total_len = cfg_desc->wTotalLength; + const usb_intf_desc_t *ifc_desc = find_msc_interface(cfg_desc, &offset); + assert(ifc_desc); + const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)ifc_desc; + const usb_ep_desc_t *ep_desc = NULL; + + cfg->iface_num = ifc_desc->bInterfaceNumber; + + next_desc = next_endpoint_desc(next_desc, total_len, &offset); + MSC_RETURN_ON_FALSE(next_desc, ESP_ERR_NOT_SUPPORTED); + ep_desc = (const usb_ep_desc_t *)next_desc; + + if (ep_desc->bEndpointAddress & 0x80) { + cfg->bulk_in_ep = ep_desc->bEndpointAddress; + cfg->bulk_in_mps = ep_desc->wMaxPacketSize; + } else { + cfg->bulk_out_ep = ep_desc->bEndpointAddress; + } + + next_desc = next_endpoint_desc(next_desc, total_len, &offset); + MSC_RETURN_ON_FALSE(next_desc, ESP_ERR_NOT_SUPPORTED); + ep_desc = (const usb_ep_desc_t *)next_desc; + + if (ep_desc->bEndpointAddress & 0x80) { + cfg->bulk_in_ep = ep_desc->bEndpointAddress; + cfg->bulk_in_mps = ep_desc->wMaxPacketSize; + } else { + cfg->bulk_out_ep = ep_desc->bEndpointAddress; + } + + return ESP_OK; +} + +static esp_err_t msc_deinit_device(msc_device_t *dev, bool install_failed) +{ + MSC_ENTER_CRITICAL(); + MSC_RETURN_ON_FALSE_CRITICAL(dev, ESP_ERR_INVALID_STATE); + STAILQ_REMOVE(&devices_tailq, dev, msc_host_device, tailq_entry); + MSC_EXIT_CRITICAL(); + + if (dev->transfer_done) { + vSemaphoreDelete(dev->transfer_done); + } + if (install_failed) { + // Error code is unchecked, as it's unknown at what point installation failed. + usb_host_interface_release(s_msc_driver->client_handle, dev->handle, dev->config.iface_num); + usb_host_device_close(s_msc_driver->client_handle, dev->handle); + usb_host_transfer_free(dev->xfer); + } else { + MSC_RETURN_ON_ERROR(usb_host_interface_release(s_msc_driver->client_handle, dev->handle, dev->config.iface_num)); + MSC_RETURN_ON_ERROR(usb_host_device_close(s_msc_driver->client_handle, dev->handle)); + MSC_RETURN_ON_ERROR(usb_host_transfer_free(dev->xfer)); + } + + free(dev); + return ESP_OK; +} + +// Some MSC devices requires to change its internal state from non-ready to ready +static esp_err_t msc_wait_for_ready_state(msc_device_t *dev, size_t timeout_ms) +{ + esp_err_t err; + scsi_sense_data_t sense; + uint32_t trials = MAX(1, timeout_ms / 100); + + do { + err = scsi_cmd_unit_ready(dev); + if (err != ESP_OK) { + MSC_RETURN_ON_ERROR(scsi_cmd_sense(dev, &sense)); + if (sense.key != MSC_NOT_READY && sense.key != MSC_UNIT_ATTENTION && sense.key != MSC_NO_SENSE) { + return ESP_ERR_MSC_INTERNAL; + } + } + vTaskDelay(pdMS_TO_TICKS(100)); + } while (trials-- && err); + + return err; +} + +static bool is_mass_storage_device(uint8_t dev_addr) +{ + size_t dummy = 0; + bool is_msc_device = false; + usb_device_handle_t device; + const usb_config_desc_t *config_desc; + + if (usb_host_device_open(s_msc_driver->client_handle, dev_addr, &device) == ESP_OK) { + if (usb_host_get_active_config_descriptor(device, &config_desc) == ESP_OK) { + if (find_msc_interface(config_desc, &dummy)) { + is_msc_device = true; + } else { + SYS_LOG_DBG("Connected USB device is not MSC"); + } + } + usb_host_device_close(s_msc_driver->client_handle, device); + } + + return is_msc_device; +} + +static void event_handler_task(void *arg) +{ + while (1) { + usb_host_client_handle_events(s_msc_driver->client_handle, pdMS_TO_TICKS(50)); + + if (s_msc_driver->end_client_event_handling) { + break; + } + } + usb_host_client_unblock(s_msc_driver->client_handle); + ESP_ERROR_CHECK(usb_host_client_deregister(s_msc_driver->client_handle)); + xSemaphoreGive(s_msc_driver->all_events_handled); + vTaskDelete(NULL); +} + +static msc_device_t *find_msc_device(usb_device_handle_t device_handle) +{ + msc_host_device_handle_t device; + + STAILQ_FOREACH(device, &devices_tailq, tailq_entry) + { + if (device_handle == device->handle) { + return device; + } + } + + return NULL; +} + +static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg) +{ + SYS_LOG_INF("CLIENT EVENT DID RECEIVED"); + if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) { + if (is_mass_storage_device(event->new_dev.address)) { + const msc_host_event_t msc_event = { + .event = MSC_DEVICE_CONNECTED, + .device.address = event->new_dev.address, + }; + g_current_device_type = USB_DEVICE_TYPE_MSC; + s_msc_driver->user_cb(&msc_event, s_msc_driver->user_arg); + } else { + // 检查vendor id和product id,判断是cdc还是dfu设备 + usb_device_handle_t device; + const usb_device_desc_t *device_desc; + + uint8_t event_type = 0; + uint16_t vendor_id = 0; + uint16_t product_id = 0; + if (usb_host_device_open(s_msc_driver->client_handle, event->new_dev.address, &device) == ESP_OK) { + if (usb_host_get_device_descriptor(device, &device_desc) == ESP_OK) { + const int compatiable_dev_count = + sizeof(g_compatiable_usb_device_list) / sizeof(usb_device_id_t); + for (int j = 0; j < compatiable_dev_count; j++) { + const usb_device_id_t *comp_dev_info = + &(g_compatiable_usb_device_list[j]); + SYS_LOG_INF("loop comp dev: 0x%08x, 0x%08x, %d", comp_dev_info->vendor_id, comp_dev_info->product_id, comp_dev_info->type); + if (device_desc->idVendor == comp_dev_info->vendor_id && device_desc->idProduct == comp_dev_info->product_id) { + if (comp_dev_info->type == USB_DEVICE_TYPE_CDC) { + event_type = CDC_DEVICE_CONNECTED; + g_current_device_type = USB_DEVICE_TYPE_CDC; + } else if (comp_dev_info->type == USB_DEVICE_TYPE_DFU) { + event_type = DFU_DEVICE_CONNECTED; + g_current_device_type = USB_DEVICE_TYPE_DFU; + } + + vendor_id = device_desc->idVendor; + product_id = device_desc->idProduct; + SYS_LOG_INF("get device event:%d", event_type); + break; + } + } + } + usb_host_device_close(s_msc_driver->client_handle, device); + } + + if (event_type != 0) { + const msc_host_event_t msc_event = { + .event = event_type, + .device.address = event->new_dev.address, + .vendor_id = vendor_id, + .product_id = product_id, + }; + + s_msc_driver->user_cb(&msc_event, s_msc_driver->user_arg); + } + } + } else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) { + msc_device_t *usb_device = find_msc_device(event->dev_gone.dev_hdl); + int event = 0; + SYS_LOG_INF("DEV GONE:%d", g_current_device_type); + + switch (g_current_device_type) { + case USB_DEVICE_TYPE_CDC: + event = CDC_DEVICE_DISCONNECTED; + + // 退出任务 + xEventGroupSetBits(s_usb_event_group, CDC_DATA_TASK_KILL_BIT); + SYS_LOG_WRN("Waitting for CDC task delete"); + while (xEventGroupGetBits(s_usb_event_group) & CDC_DATA_TASK_KILL_BIT) { + vTaskDelay(10 / portTICK_PERIOD_MS); + } + break; + case USB_DEVICE_TYPE_DFU: + event = DFU_DEVICE_DISCONNECTED; + break; + case USB_DEVICE_TYPE_MSC: + event = MSC_DEVICE_DISCONNECTED; + break; + } + + if (event != 0) { + const msc_host_event_t msc_event = { + .event = event, + .device.handle = usb_device, + }; + s_msc_driver->user_cb(&msc_event, s_msc_driver->user_arg); + } + } +} + +esp_err_t msc_host_install(const msc_host_driver_config_t *config) +{ + esp_err_t ret; + + MSC_RETURN_ON_INVALID_ARG(config); + MSC_RETURN_ON_INVALID_ARG(config->callback); + if (config->create_backround_task) { + MSC_RETURN_ON_FALSE(config->stack_size != 0, ESP_ERR_INVALID_ARG); + MSC_RETURN_ON_FALSE(config->task_priority != 0, ESP_ERR_INVALID_ARG); + } + MSC_RETURN_ON_FALSE(!s_msc_driver, ESP_ERR_INVALID_STATE); + + msc_driver_t *driver = calloc(1, sizeof(msc_driver_t)); + MSC_RETURN_ON_FALSE(driver, ESP_ERR_NO_MEM); + driver->user_cb = config->callback; + driver->user_arg = config->callback_arg; + + usb_host_client_config_t client_config = { + .async.client_event_callback = client_event_cb, + .async.callback_arg = NULL, + .max_num_event_msg = 10, + }; + + driver->end_client_event_handling = false; + driver->all_events_handled = xSemaphoreCreateBinary(); + MSC_GOTO_ON_FALSE(driver->all_events_handled, ESP_ERR_NO_MEM); + + MSC_GOTO_ON_ERROR(usb_host_client_register(&client_config, &driver->client_handle)); + + MSC_ENTER_CRITICAL(); + MSC_GOTO_ON_FALSE_CRITICAL(!s_msc_driver, ESP_ERR_INVALID_STATE); + s_msc_driver = driver; + STAILQ_INIT(&devices_tailq); + MSC_EXIT_CRITICAL(); + + if (config->create_backround_task) { + BaseType_t task_created = xTaskCreatePinnedToCore( + event_handler_task, "USB MSC", config->stack_size, NULL, SBTASK_PRIORITY_USB_MSC, NULL, SBTASK_CORE_INDEX_USB_MSC); + MSC_GOTO_ON_FALSE(task_created, ESP_ERR_NO_MEM); + } + + return ESP_OK; + +fail: + s_msc_driver = NULL; + usb_host_client_deregister(driver->client_handle); + if (driver->all_events_handled) { + vSemaphoreDelete(driver->all_events_handled); + } + free(driver); + return ret; +} + +esp_err_t msc_host_uninstall(void) +{ + // Make sure msc driver is installed, + // not being uninstalled from other task + // and no msc device is registered + MSC_ENTER_CRITICAL(); + MSC_RETURN_ON_FALSE_CRITICAL(s_msc_driver != NULL, ESP_ERR_INVALID_STATE); + MSC_RETURN_ON_FALSE_CRITICAL(!s_msc_driver->end_client_event_handling, ESP_ERR_INVALID_STATE); + MSC_RETURN_ON_FALSE_CRITICAL(STAILQ_EMPTY(&devices_tailq), ESP_ERR_INVALID_STATE); + s_msc_driver->end_client_event_handling = true; + MSC_EXIT_CRITICAL(); + + xSemaphoreTake(s_msc_driver->all_events_handled, portMAX_DELAY); + vSemaphoreDelete(s_msc_driver->all_events_handled); + free(s_msc_driver); + s_msc_driver = NULL; + return ESP_OK; +} + +esp_err_t msc_host_install_device(uint8_t device_address, msc_host_device_handle_t *msc_device_handle) +{ + esp_err_t ret; + uint32_t block_size, block_count; + const usb_config_desc_t *config_desc; + msc_device_t *msc_device; + // uint8_t lun; + size_t transfer_size = 512; // Normally the smallest block size + + MSC_GOTO_ON_FALSE(msc_device = calloc(1, sizeof(msc_device_t)), ESP_ERR_NO_MEM); + + MSC_ENTER_CRITICAL(); + MSC_GOTO_ON_FALSE_CRITICAL(s_msc_driver, ESP_ERR_INVALID_STATE); + MSC_GOTO_ON_FALSE_CRITICAL(s_msc_driver->client_handle, ESP_ERR_INVALID_STATE); + STAILQ_INSERT_TAIL(&devices_tailq, msc_device, tailq_entry); + MSC_EXIT_CRITICAL(); + + MSC_GOTO_ON_FALSE(msc_device->transfer_done = xSemaphoreCreateBinary(), ESP_ERR_NO_MEM); + MSC_GOTO_ON_ERROR(usb_host_device_open(s_msc_driver->client_handle, device_address, &msc_device->handle)); + MSC_GOTO_ON_ERROR(usb_host_get_active_config_descriptor(msc_device->handle, &config_desc)); + MSC_GOTO_ON_ERROR(extract_config_from_descriptor(config_desc, &msc_device->config)); + MSC_GOTO_ON_ERROR(usb_host_transfer_alloc(transfer_size, 0, &msc_device->xfer)); + MSC_GOTO_ON_ERROR(usb_host_interface_claim(s_msc_driver->client_handle, + msc_device->handle, + msc_device->config.iface_num, + 0)); + + MSC_GOTO_ON_ERROR(msc_mass_reset(msc_device)); + MSC_GOTO_ON_ERROR(scsi_cmd_inquiry(msc_device)); + MSC_GOTO_ON_ERROR(msc_wait_for_ready_state(msc_device, WAIT_FOR_READY_TIMEOUT_MS)); + MSC_GOTO_ON_ERROR(scsi_cmd_read_capacity(msc_device, &block_size, &block_count)); + + // Configuration descriptor size of simple MSC device is 32 bytes. + if (config_desc->wTotalLength != 32) { + SYS_LOG_ERR("COMPOSITE DEVICES UNSUPPORTED"); + } + + msc_device->disk.block_size = block_size; + msc_device->disk.block_count = block_count; + + if (block_size > transfer_size) { + usb_transfer_t *larger_xfer; + MSC_GOTO_ON_ERROR(usb_host_transfer_alloc(block_size, 0, &larger_xfer)); + usb_host_transfer_free(msc_device->xfer); + msc_device->xfer = larger_xfer; + } + + *msc_device_handle = msc_device; + + return ESP_OK; + +fail: + msc_deinit_device(msc_device, true); + return ret; +} + +esp_err_t msc_host_uninstall_device(msc_host_device_handle_t device) +{ + MSC_RETURN_ON_INVALID_ARG(device); + return msc_deinit_device((msc_device_t *)device, false); +} + +esp_err_t msc_host_read_sector(msc_host_device_handle_t device, size_t sector, void *data, size_t size) +{ + MSC_RETURN_ON_INVALID_ARG(device); + msc_device_t *dev = (msc_device_t *)device; + + return scsi_cmd_read10(dev, data, sector, 1, dev->disk.block_size); +} + +esp_err_t msc_host_write_sector(msc_host_device_handle_t device, size_t sector, const void *data, size_t size) +{ + MSC_RETURN_ON_INVALID_ARG(device); + msc_device_t *dev = (msc_device_t *)device; + + return scsi_cmd_write10(dev, data, sector, 1, dev->disk.block_size); +} + +esp_err_t msc_host_handle_events(uint32_t timeout_ms) +{ + MSC_RETURN_ON_FALSE(s_msc_driver != NULL, ESP_ERR_INVALID_STATE); + + return usb_host_client_handle_events(s_msc_driver->client_handle, timeout_ms); +} + +static esp_err_t msc_read_string_desc(msc_device_t *dev, uint8_t index, wchar_t *str) +{ + if (index == 0) { + // String descriptor not available + str[0] = 0; + return ESP_OK; + } + + usb_transfer_t *xfer = dev->xfer; + USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)xfer->data_buffer, index, 0x409, 64); + MSC_RETURN_ON_ERROR(msc_control_transfer(dev, xfer, USB_SETUP_PACKET_SIZE + 64)); + + usb_standard_desc_t *desc = (usb_standard_desc_t *)(xfer->data_buffer + USB_SETUP_PACKET_SIZE); + wchar_t *data = (wchar_t *)(xfer->data_buffer + USB_SETUP_PACKET_SIZE + 2); + size_t len = MIN((desc->bLength - USB_STANDARD_DESC_SIZE) / 2, MSC_STR_DESC_SIZE - 1); + + wcsncpy(str, data, len); + str[len] = 0; + + return ESP_OK; +} + +esp_err_t msc_host_get_device_info(msc_host_device_handle_t device, msc_host_device_info_t *info) +{ + MSC_RETURN_ON_INVALID_ARG(device); + MSC_RETURN_ON_INVALID_ARG(info); + + msc_device_t *dev = (msc_device_t *)device; + const usb_device_desc_t *desc; + + MSC_RETURN_ON_ERROR(usb_host_get_device_descriptor(dev->handle, &desc)); + + info->idProduct = desc->idProduct; + info->idVendor = desc->idVendor; + info->sector_size = dev->disk.block_size; + info->sector_count = dev->disk.block_count; + + MSC_RETURN_ON_ERROR(msc_read_string_desc(dev, desc->iManufacturer, info->iManufacturer)); + MSC_RETURN_ON_ERROR(msc_read_string_desc(dev, desc->iProduct, info->iProduct)); + MSC_RETURN_ON_ERROR(msc_read_string_desc(dev, desc->iSerialNumber, info->iSerialNumber)); + + return ESP_OK; +} + +esp_err_t msc_host_print_descriptors(msc_host_device_handle_t device) +{ + msc_device_t *dev = (msc_device_t *)device; + const usb_device_desc_t *device_desc; + const usb_config_desc_t *config_desc; + MSC_RETURN_ON_ERROR(usb_host_get_device_descriptor(dev->handle, &device_desc)); + MSC_RETURN_ON_ERROR(usb_host_get_active_config_descriptor(dev->handle, &config_desc)); + + if (CONFIG_SYS_LOG_LEVEL >= SYS_LOG_LEVEL_INF) + { + usb_print_device_descriptor(device_desc); + usb_print_config_descriptor(config_desc, NULL); + } + return ESP_OK; +} + +static void transfer_callback(usb_transfer_t *transfer) +{ + msc_device_t *device = (msc_device_t *)transfer->context; + + if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) { + ESP_LOGE("Transfer failed", "Status %d", transfer->status); + } + + device->transfer_status = transfer->status; + xSemaphoreGive(device->transfer_done); +} + +static esp_err_t wait_for_transfer_done(usb_transfer_t *xfer) +{ + msc_device_t *device = (msc_device_t *)xfer->context; + BaseType_t received = xSemaphoreTake(device->transfer_done, pdMS_TO_TICKS(xfer->timeout_ms)); + + if (received != pdTRUE) { + usb_host_endpoint_halt(xfer->device_handle, xfer->bEndpointAddress); + usb_host_endpoint_flush(xfer->device_handle, xfer->bEndpointAddress); + xSemaphoreTake(device->transfer_done, portMAX_DELAY); + return ESP_ERR_TIMEOUT; + } + + return (device->transfer_status == USB_TRANSFER_STATUS_COMPLETED) ? ESP_OK : ESP_FAIL; +} + +static inline bool is_in_endpoint(uint8_t endpoint) +{ + return endpoint & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK ? true : false; +} + +esp_err_t msc_bulk_transfer(msc_device_t *device, uint8_t *data, size_t size, msc_endpoint_t ep) +{ + usb_transfer_t *xfer = device->xfer; + MSC_RETURN_ON_FALSE(size <= xfer->data_buffer_size, ESP_ERR_INVALID_SIZE); + uint8_t endpoint = (ep == MSC_EP_IN) ? device->config.bulk_in_ep : device->config.bulk_out_ep; + + if (is_in_endpoint(endpoint)) { + xfer->num_bytes = usb_round_up_to_mps(size, device->config.bulk_in_mps); + } else { + memcpy(xfer->data_buffer, data, size); + xfer->num_bytes = size; + } + + xfer->device_handle = device->handle; + xfer->bEndpointAddress = endpoint; + xfer->callback = transfer_callback; + xfer->timeout_ms = 1000; + xfer->context = device; + + MSC_RETURN_ON_ERROR(usb_host_transfer_submit(xfer)); + MSC_RETURN_ON_ERROR(wait_for_transfer_done(xfer)); + + if (is_in_endpoint(endpoint)) { + memcpy(data, xfer->data_buffer, size); + } + + return ESP_OK; +} + +esp_err_t msc_control_transfer(msc_device_t *device, usb_transfer_t *xfer, size_t len) +{ + xfer->device_handle = device->handle; + xfer->bEndpointAddress = 0; + xfer->callback = transfer_callback; + xfer->timeout_ms = 1000; + xfer->num_bytes = len; + xfer->context = device; + + MSC_RETURN_ON_ERROR(usb_host_transfer_submit_control(s_msc_driver->client_handle, xfer)); + return wait_for_transfer_done(xfer); +} + +////// CDC接口实现 + +static esp_err_t usb_out_ringbuf_push(const uint8_t *buf, size_t write_bytes, TickType_t xTicksToWait) +{ + int res = + xRingbufferSend(s_out_ringbuf_handle, buf, write_bytes, xTicksToWait); + + if (res != pdTRUE) { + SYS_LOG_WRN("The out buffer is too small, the data has been lost %u", write_bytes); + return ESP_FAIL; + } + + portENTER_CRITICAL(&s_out_ringbuf_mux); + s_out_buffered_data_len += write_bytes; +#ifdef CONFIG_CDC_USE_TRACE_FACILITY + s_ringbuf_out_max = s_out_buffered_data_len > s_ringbuf_out_max ? s_out_buffered_data_len : s_ringbuf_out_max; +#endif + portEXIT_CRITICAL(&s_out_ringbuf_mux); + return ESP_OK; +} + +static esp_err_t usb_out_ringbuf_pop(uint8_t *buf, size_t req_bytes, size_t *read_bytes, TickType_t ticks_to_wait) +{ + uint8_t *buf_rcv = xRingbufferReceiveUpTo(s_out_ringbuf_handle, read_bytes, ticks_to_wait, req_bytes); + + if (buf_rcv) { + memcpy(buf, buf_rcv, *read_bytes); + vRingbufferReturnItem(s_out_ringbuf_handle, (void *)(buf_rcv)); + portENTER_CRITICAL(&s_out_ringbuf_mux); + s_out_buffered_data_len -= *read_bytes; + portEXIT_CRITICAL(&s_out_ringbuf_mux); + return ESP_OK; + } else { + return ESP_ERR_NO_MEM; + } +} + +static bool _cdc_driver_is_init(void) +{ + if (s_usb_event_group == NULL) { + return false; + } + + return true; +} + +static bool _if_device_ready() +{ + if (!s_usb_event_group) + return false; + return xEventGroupGetBits(s_usb_event_group) & CDC_DEVICE_READY_BIT; +} + +void cdc_submit_transfer_in(cdc_acm_dev_hdl_t cdc_hdl) +{ + assert(cdc_hdl); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + usb_host_transfer_submit(cdc_dev->data.in_xfer); +} + +int cdc_write_bytes(const uint8_t *buf, size_t length) +{ + // SYS_LOG_INF("write data to cdc devi11111"); + CDC_CHECK(buf != NULL, "invalid args", -1); + int tx_data_size = 0; + + if (_cdc_driver_is_init() == false) { + SYS_LOG_INF("CDC Driver not installed"); + return -1; + } + + if (_if_device_ready() == false) { + SYS_LOG_INF("Device not connected or not ready"); + return -1; + } + + // SYS_LOG_INF("write data to cdc devi12222"); + xSemaphoreTake(s_usb_write_mux, portMAX_DELAY); + // SYS_LOG_INF("write data to cdc devi3333"); + esp_err_t ret = usb_out_ringbuf_push(buf, length, pdMS_TO_TICKS(TIMEOUT_USB_RINGBUF_MS)); + + if (ret != ESP_OK) { + xSemaphoreGive(s_usb_write_mux); + SYS_LOG_DBG("Write ringbuffer failed"); + return -1; + } + + tx_data_size = length; + xSemaphoreGive(s_usb_write_mux); + return tx_data_size; +} + +static esp_err_t cdc_reset_transfer_endpoint(usb_device_handle_t dev_hdl, usb_transfer_t *transfer); + +static void _cdc_data_task(void *arg) +{ + cdc_dev_t *cdc_dev = (cdc_dev_t *)arg; + const uint32_t timeout_ms = 100; + + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + xEventGroupSetBits(s_usb_event_group, CDC_DEVICE_READY_BIT); + SYS_LOG_INF("CDC TASK RUNNING"); + while (!(xEventGroupGetBits(s_usb_event_group) & CDC_DATA_TASK_KILL_BIT)) { + size_t num_bytes_to_send = 0; + // SYS_LOG_INF("buffer addr:%08x, buffer size:%d", (uint32_t)cdc_dev->data.out_xfer->data_buffer, cdc_dev->data.out_xfer->data_buffer_size); + esp_err_t ret = usb_out_ringbuf_pop(cdc_dev->data.out_xfer->data_buffer, + cdc_dev->data.out_xfer->data_buffer_size, + &num_bytes_to_send, + 0); + if (ret != ESP_OK || num_bytes_to_send == 0) { + vTaskDelay(pdMS_TO_TICKS(20)); + continue; + } + + // Take OUT mutex and fill the OUT transfer + BaseType_t taken = xSemaphoreTake(cdc_dev->data.out_mux, pdMS_TO_TICKS(timeout_ms)); + if (taken != pdTRUE) { + vTaskDelay(pdMS_TO_TICKS(20)); + continue; + } + + ESP_LOGD("CDC_ACM", "Submitting BULK OUT transfer"); + // memcpy(cdc_dev->data.out_xfer->data_buffer, data, data_len); + cdc_dev->data.out_xfer->num_bytes = num_bytes_to_send; + cdc_dev->data.out_xfer->timeout_ms = 100; + ESP_GOTO_ON_ERROR(usb_host_transfer_submit(cdc_dev->data.out_xfer), unblock, TAG, ); + + // Wait for OUT transfer completion + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->data.out_xfer->context, pdMS_TO_TICKS(timeout_ms)); + if (!taken) { + // Reset the endpoint + cdc_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.out_xfer); + ret = ESP_ERR_TIMEOUT; + goto unblock; + } + + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Bulk OUT transfer error"); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->actual_num_bytes == num_bytes_to_send, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + ret = ESP_OK; + + unblock: + xSemaphoreGive(cdc_dev->data.out_mux); + } + + xEventGroupClearBits(s_usb_event_group, CDC_DATA_TASK_KILL_BIT); + SYS_LOG_INF("CDC task deleted"); + vTaskDelete(NULL); + + // esp_err_t ret; + // CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + // cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + // CDC_ACM_CHECK(data && (data_len > 0), ESP_ERR_INVALID_ARG); + // CDC_ACM_CHECK(cdc_dev->data.out_xfer, ESP_ERR_NOT_SUPPORTED); // Device was opened as read-only. + // CDC_ACM_CHECK(data_len <= cdc_dev->data.out_xfer->data_buffer_size, ESP_ERR_INVALID_SIZE); + + // return ret; +} + +/** + * @brief Free USB transfers used by this device + * + * @note There can be no transfers in flight, at the moment of calling this function. + * @param[in] cdc_dev Pointer to CDC device + */ +static void cdc_transfers_free(cdc_dev_t *cdc_dev) +{ + assert(cdc_dev); + usb_host_transfer_free(cdc_dev->notif.xfer); + usb_host_transfer_free(cdc_dev->data.in_xfer); + if (cdc_dev->data.out_xfer != NULL) { + if (cdc_dev->data.out_xfer->context != NULL) { + vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->data.out_xfer->context); + } + if (cdc_dev->data.out_mux != NULL) { + vSemaphoreDelete(cdc_dev->data.out_mux); + } + usb_host_transfer_free(cdc_dev->data.out_xfer); + } + if (cdc_dev->ctrl_transfer != NULL) { + if (cdc_dev->ctrl_transfer->context != NULL) { + vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context); + } + if (cdc_dev->ctrl_mux != NULL) { + vSemaphoreDelete(cdc_dev->ctrl_mux); + } + usb_host_transfer_free(cdc_dev->ctrl_transfer); + } +} + +/** + * @brief Allocate CDC transfers + * + * @param[in] cdc_dev Pointer to CDC device + * @param[in] notif_ep_desc Pointer to notification EP descriptor + * @param[in] in_ep_desc- Pointer to data IN EP descriptor + * @param[in] out_ep_desc Pointer to data OUT EP descriptor + * @param[in] out_buf_len Length of data OUT buffer + * @return esp_err_t + */ +static esp_err_t cdc_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_desc_t *notif_ep_desc, const usb_ep_desc_t *in_ep_desc, const usb_ep_desc_t *out_ep_desc, size_t out_buf_len) +{ + esp_err_t ret; + + // 1. Setup notification and control transfers if they are supported + if (notif_ep_desc) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(notif_ep_desc), 0, &cdc_dev->notif.xfer), + err, + TAG, ); + cdc_dev->notif.xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->notif.xfer->bEndpointAddress = notif_ep_desc->bEndpointAddress; + cdc_dev->notif.xfer->callback = notif_xfer_cb; + cdc_dev->notif.xfer->context = cdc_dev; + cdc_dev->notif.xfer->num_bytes = USB_EP_DESC_GET_MPS(notif_ep_desc); + + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(cdc_dev->dev_hdl, &dev_info)); + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(dev_info.bMaxPacketSize0, 0, &cdc_dev->ctrl_transfer), + err, + TAG, ); + cdc_dev->ctrl_transfer->timeout_ms = 1000; + cdc_dev->ctrl_transfer->bEndpointAddress = 0; + cdc_dev->ctrl_transfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->ctrl_transfer->context = cdc_dev; + cdc_dev->ctrl_transfer->callback = out_xfer_cb; + cdc_dev->ctrl_transfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->context, ESP_ERR_NO_MEM, err, TAG, ); + cdc_dev->ctrl_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_mux, ESP_ERR_NO_MEM, err, TAG, ); + } + + // 2. Setup IN data transfer + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(1024 * 20, 0, &cdc_dev->data.in_xfer), + err, + TAG, ); + assert(cdc_dev->data.in_xfer); + cdc_dev->data.in_xfer->callback = in_xfer_cb; + cdc_dev->data.in_xfer->num_bytes = 1024 * 20; + cdc_dev->data.in_xfer->bEndpointAddress = in_ep_desc->bEndpointAddress; + cdc_dev->data.in_xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->data.in_xfer->context = cdc_dev; + + // 3. Setup OUT bulk transfer (if it is required (out_buf_len > 0)) + if (out_buf_len != 0) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(out_buf_len, 0, &cdc_dev->data.out_xfer), + err, + TAG, ); + assert(cdc_dev->data.out_xfer); + cdc_dev->data.out_xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->data.out_xfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->context, ESP_ERR_NO_MEM, err, TAG, ); + cdc_dev->data.out_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_mux, ESP_ERR_NO_MEM, err, TAG, ); + cdc_dev->data.out_xfer->bEndpointAddress = out_ep_desc->bEndpointAddress; + cdc_dev->data.out_xfer->callback = out_xfer_cb; + } + return ESP_OK; + +err: + cdc_transfers_free(cdc_dev); + return ret; +} + +/** + * @brief Find CDC interface descriptor and its endpoint descriptors + * + * @note This function is called in open procedure of CDC compliant devices only. + * @param[in] cdc_dev Pointer to CDC device + * @param[in] intf_idx Index of CDC interface that should be used for this device + * @param[out] notif_ep Pointer to notification EP descriptor + * @param[out] in_ep Pointer to data IN EP descriptor + * @param[out] out_ep Pointer to data OUT EP descriptor + * @return esp_err_t + */ +static esp_err_t cdc_find_intf_and_ep_desc(cdc_dev_t *cdc_dev, uint8_t intf_idx, const usb_ep_desc_t **notif_ep, const usb_ep_desc_t **in_ep, const usb_ep_desc_t **out_ep) +{ + bool interface_found = false; + const usb_config_desc_t *config_desc; + const usb_device_desc_t *device_desc; + int data_intf_idx, notif_intf_idx; + int desc_offset = 0; + // Get required descriptors + ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + if ((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) && (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) { + // This is a composite device, that uses Interface Association Descriptor + SYS_LOG_INF("found 111"); + const usb_standard_desc_t *this_desc = (const usb_standard_desc_t *)config_desc; + do { + this_desc = usb_parse_next_descriptor_of_type( + this_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset); + SYS_LOG_INF("BBBBBFFFF 111"); + if (this_desc == NULL) + break; // Reached end of configuration descriptor + + const usb_iad_desc_t *iad_desc = (const usb_iad_desc_t *)this_desc; + SYS_LOG_INF("iad_desc->bFirstInterface:%d", iad_desc->bFirstInterface); + if (iad_desc->bFirstInterface == intf_idx) { + // IAD with correct interface number was found: Check Class/Subclass codes, save Interface indexes + assert(iad_desc->bInterfaceCount == 2); + assert(iad_desc->bFunctionClass == USB_CLASS_COMM); + assert(iad_desc->bFunctionSubClass == CDC_SUBCLASS_ACM); + notif_intf_idx = iad_desc->bFirstInterface; + data_intf_idx = iad_desc->bFirstInterface + 1; + interface_found = true; + } + } while (!interface_found); + } else if ((device_desc->bDeviceClass == USB_CLASS_COMM) && (intf_idx == 0)) { + SYS_LOG_INF("device_desc->bDeviceClass:%d, device_desc->bDeviceSubClass:%d, device_desc->bDeviceProtocol:%d", device_desc->bDeviceClass, device_desc->bDeviceSubClass, device_desc->bDeviceProtocol); + // This is a Communication Device Class + notif_intf_idx = 0; + data_intf_idx = 1; + interface_found = true; + SYS_LOG_INF("found 222"); + } + + // Save found interfaces descriptors: + if (interface_found) { + // Notification IF and EP + cdc_dev->notif.intf_desc = usb_parse_interface_descriptor(config_desc, notif_intf_idx, 0, &desc_offset); + assert(cdc_dev->notif.intf_desc); + + // CDC specific descriptors should be right after CDC-Communication interface descriptor + // Note: That's why we use usb_parse_next_descriptor instead of usb_parse_next_descriptor_of_type. + // The latter could return CDC specific descriptors that don't belong to this interface + const usb_standard_desc_t *cdc_desc = (usb_standard_desc_t *)cdc_dev->notif.intf_desc; + do { + cdc_desc = usb_parse_next_descriptor(cdc_desc, config_desc->wTotalLength, &desc_offset); + if ((cdc_desc == NULL) || (cdc_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_W_VALUE_DT_INTERFACE))) + break; // We found all CDC specific descriptors + cdc_dev->num_cdc_intf_desc++; + cdc_dev->cdc_intf_desc = + realloc(cdc_dev->cdc_intf_desc, cdc_dev->num_cdc_intf_desc * (sizeof(usb_standard_desc_t *))); + assert(cdc_dev->cdc_intf_desc); + cdc_dev->cdc_intf_desc[cdc_dev->num_cdc_intf_desc - 1] = cdc_desc; + } while (1); + *notif_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->notif.intf_desc, 0, config_desc->wTotalLength, &desc_offset); + assert(notif_ep); + + // Data IF and EP + cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, data_intf_idx, 0, &desc_offset); + assert(cdc_dev->data.intf_desc); + int temp_offset = desc_offset; + for (int i = 0; i < 2; i++) { + const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset); + assert(this_ep); + if (USB_EP_DESC_GET_EP_DIR(this_ep)) { + *in_ep = this_ep; + } else { + *out_ep = this_ep; + } + desc_offset = temp_offset; + } + return ESP_OK; + } + return ESP_ERR_NOT_FOUND; +} + +/** + * @brief Start CDC device + * + * After this call, USB host peripheral will continuously poll IN endpoints. + * + * @param cdc_dev + * @param[in] event_cb Device event callback + * @param[in] in_cb Data received callback + * @param[in] user_arg Optional user's argument, that will be passed to the callbacks + * @return esp_err_t + */ +static esp_err_t cdc_start(cdc_dev_t *cdc_dev, cdc_acm_host_dev_callback_t event_cb, cdc_acm_data_callback_t in_cb, void *user_arg) +{ + esp_err_t ret = ESP_OK; + assert(cdc_dev); + + CDC_ACM_ENTER_CRITICAL(); + cdc_dev->notif.cb = event_cb; + cdc_dev->data.in_cb = in_cb; + cdc_dev->cb_arg = user_arg; + CDC_ACM_EXIT_CRITICAL(); + + // Claim data interface and start polling its IN endpoint + SYS_LOG_INF("interface claim for cdc bulk in"); + ESP_GOTO_ON_ERROR(usb_host_interface_claim(s_msc_driver->client_handle, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber, 0), err, TAG, ); + ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->data.in_xfer)); + + // If notification are supported, claim its interface and start polling its IN endpoint + // if (cdc_dev->notif.intf_desc != NULL) { + // if (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc) { + // esp_err_t result = usb_host_interface_claim(s_msc_driver->client_handle, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber, 0); + // SYS_LOG_INF("CLAIM INTERFACE RESULT:%d", result); + // ESP_GOTO_ON_ERROR(result, err, TAG, ); + // } + // ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer"); + // ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->notif.xfer)); + // } + + // Everything OK, add the device into list and return + // CDC_ACM_ENTER_CRITICAL(); + // SLIST_INSERT_HEAD(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, list_entry); + // CDC_ACM_EXIT_CRITICAL(); + return ret; + +err: + usb_host_interface_release(s_msc_driver->client_handle, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber); + if (cdc_dev->notif.intf_desc != NULL) { + usb_host_interface_release(s_msc_driver->client_handle, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber); + } + return ret; +} + +/** + * @brief Open USB device with requested VID/PID + * + * This function has two regular return paths: + * 1. USB device with matching VID/PID is already opened by this driver: allocate new CDC device on top of the already opened USB device. + * 2. USB device with matching VID/PID is NOT opened by this driver yet: poll USB connected devices until it is found. + * + * @note This function will block for timeout_ms, if the device is not enumerated at the moment of calling this function. + * @param[in] vid Vendor ID + * @param[in] pid Product ID + * @param[in] timeout_ms Connection timeout [ms] + * @param[out] dev CDC-ACM device + * @return esp_err_t + */ +static esp_err_t cdc_find_and_open_usb_device(uint16_t vid, uint16_t pid, int timeout_ms, cdc_dev_t **dev) +{ + // assert(p_cdc_acm_obj); + assert(dev); + + *dev = calloc(1, sizeof(cdc_dev_t)); + if (*dev == NULL) { + return ESP_ERR_NO_MEM; + } + + // // First, check list of already opened CDC devices + // SYS_LOG_DBG("Checking list of opened USB devices"); + // cdc_dev_t *cdc_dev; + // SLIST_FOREACH(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry) + // { + // const usb_device_desc_t *device_desc; + // ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + // if (device_desc->idVendor == vid && device_desc->idProduct == pid) { + // // Return path 1: + // (*dev)->dev_hdl = cdc_dev->dev_hdl; + // return ESP_OK; + // } + // } + + // Second, poll connected devices until new device is connected or timeout + TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + TimeOut_t connection_timeout; + vTaskSetTimeOutState(&connection_timeout); + + while (true) { + SYS_LOG_DBG("Checking list of connected USB devices"); + uint8_t dev_addr_list[10]; + int num_of_devices; + ESP_ERROR_CHECK(usb_host_device_addr_list_fill(sizeof(dev_addr_list), dev_addr_list, &num_of_devices)); + + // Go through device address list and find the one we are looking for + for (int i = 0; i < num_of_devices; i++) { + usb_device_handle_t current_device; + // Open USB device + if (usb_host_device_open(s_msc_driver->client_handle, dev_addr_list[i], ¤t_device) != ESP_OK) { + continue; // In case we failed to open this device, continue with next one in the list + } + assert(current_device); + const usb_device_desc_t *device_desc; + ESP_ERROR_CHECK(usb_host_get_device_descriptor(current_device, &device_desc)); + if (device_desc->idVendor == vid && device_desc->idProduct == pid) { + // Return path 2: + (*dev)->dev_hdl = current_device; + return ESP_OK; + } + usb_host_device_close(s_msc_driver->client_handle, current_device); + } + + if (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) != pdFALSE) { + break; // Timeout elapsed and the device is not connected + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + + // Timeout was reached, clean-up + free(*dev); + *dev = NULL; + return ESP_ERR_NOT_FOUND; +} + +/** + * @brief Helper function that releases resources claimed by CDC device + * + * Close underlying USB device, free device driver memory + * + * @note All interfaces claimed by this device must be release before calling this function + * @param cdc_dev CDC device handle to be removed + */ +static void cdc_device_remove(cdc_dev_t *cdc_dev) +{ + assert(cdc_dev); + cdc_transfers_free(cdc_dev); + free(cdc_dev->cdc_intf_desc); + // We don't check the error code of usb_host_device_close, as the close might fail, if someone else is still using the device (not all interfaces are released) + usb_host_device_close(s_msc_driver->client_handle, cdc_dev->dev_hdl); // Gracefully continue on error + free(cdc_dev); +} + +esp_err_t cdc_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret) +{ + esp_err_t ret = ESP_FAIL; + // CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG); + + // xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + // Find underlying USB device + cdc_dev_t *cdc_dev; + ESP_GOTO_ON_ERROR( + cdc_find_and_open_usb_device(vid, pid, dev_config->connection_timeout_ms, &cdc_dev), + exit, + TAG, + "USB device with VID: 0x%04X, PID: 0x%04X not found", + vid, + pid); + + // Find and save relevant interface and endpoint descriptors + const usb_ep_desc_t *notif_ep = NULL; + const usb_ep_desc_t *in_ep = NULL; + const usb_ep_desc_t *out_ep = NULL; + ESP_GOTO_ON_ERROR( + cdc_find_intf_and_ep_desc(cdc_dev, interface_idx, ¬if_ep, &in_ep, &out_ep), + err, + TAG, + "Could not find required interface"); + + // Check whether found Interfaces are really CDC-ACM + assert(cdc_dev->notif.intf_desc->bInterfaceClass == USB_CLASS_COMM); + assert(cdc_dev->notif.intf_desc->bInterfaceSubClass == CDC_SUBCLASS_ACM); + assert(cdc_dev->notif.intf_desc->bNumEndpoints == 1); + assert(cdc_dev->data.intf_desc->bInterfaceClass == USB_CLASS_CDC_DATA); + assert(cdc_dev->data.intf_desc->bNumEndpoints == 2); + + // Save Communication and Data protocols + cdc_dev->comm_protocol = (cdc_comm_protocol_t)cdc_dev->notif.intf_desc->bInterfaceProtocol; + cdc_dev->data_protocol = (cdc_data_protocol_t)cdc_dev->data.intf_desc->bInterfaceProtocol; + + // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle + ESP_GOTO_ON_ERROR(cdc_transfers_allocate(cdc_dev, notif_ep, in_ep, out_ep, dev_config->out_buffer_size), err, TAG, ); + ESP_GOTO_ON_ERROR(cdc_start(cdc_dev, dev_config->event_cb, dev_config->data_cb, dev_config->user_arg), err, TAG, ); + *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev; + // xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + + // cdc_task_args.bulk_in_ep_desc = in_ep; + // cdc_task_args.bulk_out_ep_desc = out_ep; + SYS_LOG_INF("s_usb_event_group created"); + s_usb_event_group = xEventGroupCreate(); + CDC_CHECK(s_usb_event_group != NULL, "Create event group failed", ESP_FAIL); + + if (s_out_ringbuf_handle == NULL) { + // s_in_ringbuf_handle = + // xRingbufferCreate(4096, RINGBUF_TYPE_BYTEBUF); + // CDC_CHECK_GOTO(s_in_ringbuf_handle != NULL, "Create in ringbuffer failed", delete_resource_); + s_out_ringbuf_handle = + xRingbufferCreate(1024, RINGBUF_TYPE_BYTEBUF); + CDC_CHECK_GOTO(s_out_ringbuf_handle != NULL, "Create out ringbuffer failed", delete_resource_); + + s_usb_read_mux = xSemaphoreCreateMutex(); + CDC_CHECK_GOTO(s_usb_read_mux != NULL, "Create read mutex failed", delete_resource_); + s_usb_write_mux = xSemaphoreCreateMutex(); + CDC_CHECK_GOTO(s_usb_write_mux != NULL, "Create write mutex failed", delete_resource_); + } + + TaskHandle_t cdc_data_task_hdl = NULL; + // _cdc_data_task_args_t cdc_task_args = { + // .dev_addr = 0, + // // .bulk_in_ep_desc = *(cdc_config->bulk_in_ep), + // // .bulk_out_ep_desc = *(cdc_config->bulk_out_ep), + // .event_group_hdl = s_usb_event_group, + // .rx_callback = NULL, + // .rx_callback_arg = NULL, + // }; + + xTaskCreatePinnedToCore( + _cdc_data_task, + CDC_DATA_TASK_NAME, + CDC_DATA_TASK_STACK_SIZE, + (void *)(cdc_dev), + SBTASK_PRIORITY_USB_CDC, + &cdc_data_task_hdl, + CDC_DATA_TASK_CORE); + + vTaskDelay(50 / portTICK_PERIOD_MS); + xTaskNotifyGive(cdc_data_task_hdl); + + g_cdc_dev = cdc_dev; + SYS_LOG_INF("g_cdc_dev:%08x", (uint32_t)g_cdc_dev); + + return ESP_OK; + +err: + cdc_device_remove(cdc_dev); +exit: + // xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + *cdc_hdl_ret = NULL; + return ret; + +delete_resource_: + if (s_usb_write_mux) + vSemaphoreDelete(s_usb_write_mux); + if (s_usb_read_mux) + vSemaphoreDelete(s_usb_read_mux); + if (s_out_ringbuf_handle) + vRingbufferDelete(s_out_ringbuf_handle); + if (s_in_ringbuf_handle) + vRingbufferDelete(s_in_ringbuf_handle); + if (s_usb_event_group) + vEventGroupDelete(s_usb_event_group); + s_usb_event_group = NULL; + s_in_ringbuf_handle = NULL; + s_out_ringbuf_handle = NULL; + s_usb_read_mux = NULL; + s_usb_write_mux = NULL; + + *cdc_hdl_ret = NULL; + return ret; +} + +/** + * @brief Check finished transfer status + * + * Return to on transfer completed OK. + * Cancel the transfer and issue user's callback in case of an error. + * + * @param[in] transfer Transfer to be checked + * @return true Transfer completed + * @return false Transfer NOT completed + */ +static bool cdc_is_transfer_completed(usb_transfer_t *transfer) +{ + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + bool completed = false; + + switch (transfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: + completed = true; + break; + case USB_TRANSFER_STATUS_NO_DEVICE: // User is notified about device disconnection from usb_event_cb + case USB_TRANSFER_STATUS_CANCELED: + break; + case USB_TRANSFER_STATUS_ERROR: + case USB_TRANSFER_STATUS_TIMED_OUT: + case USB_TRANSFER_STATUS_STALL: + case USB_TRANSFER_STATUS_OVERFLOW: + case USB_TRANSFER_STATUS_SKIPPED: + default: + // Transfer was not completed or cancelled by user. Inform user about this + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t error_event = { + .type = CDC_ACM_HOST_ERROR, + .data.error = (int)transfer->status}; + cdc_dev->notif.cb((cdc_acm_dev_hdl_t)cdc_dev, &error_event, cdc_dev->cb_arg); + } + } + return completed; +} + +static void in_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "in xfer cb"); + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + + if (cdc_is_transfer_completed(transfer)) { + if (cdc_dev->data.in_cb) { + cdc_dev->data.in_cb(transfer->data_buffer, transfer->actual_num_bytes, cdc_dev->cb_arg); + } + + /* 通过 cdc_submit_transfer_in() 使能传输 */ + // ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + // usb_host_transfer_submit(cdc_dev->data.in_xfer); + } else { + SYS_LOG_INF("IN COMPLETE"); + } +} + +static void notif_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "notif xfer cb"); + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + + if (cdc_is_transfer_completed(transfer)) { + cdc_notification_t *notif = (cdc_notification_t *)transfer->data_buffer; + switch (notif->bNotificationCode) { + case CDC_NOTIF_NETWORK_CONNECTION: { + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t net_conn_event = { + .type = CDC_ACM_HOST_NETWORK_CONNECTION, + .data.network_connected = (bool)notif->wValue}; + cdc_dev->notif.cb((cdc_acm_dev_hdl_t)cdc_dev, &net_conn_event, cdc_dev->cb_arg); + } + break; + } + case CDC_NOTIF_SERIAL_STATE: { + cdc_dev->serial_state.val = *((uint16_t *)notif->Data); + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t serial_state_event = { + .type = CDC_ACM_HOST_SERIAL_STATE, + .data.serial_state = cdc_dev->serial_state}; + cdc_dev->notif.cb((cdc_acm_dev_hdl_t)cdc_dev, &serial_state_event, cdc_dev->cb_arg); + } + break; + } + case CDC_NOTIF_RESPONSE_AVAILABLE: // Encapsulated commands not implemented - fallthrough + default: + ESP_LOGW("CDC_ACM", "Unsupported notification type 0x%02X", notif->bNotificationCode); + ESP_LOG_BUFFER_HEX("CDC_ACM", transfer->data_buffer, transfer->actual_num_bytes); + break; + } + + // Start polling for new data again + ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer"); + usb_host_transfer_submit(cdc_dev->notif.xfer); + } +} + +static void out_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "out/ctrl xfer cb"); + assert(transfer->context); + xSemaphoreGive((SemaphoreHandle_t)transfer->context); +} + +void cdc_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl) +{ + assert(cdc_hdl); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + ESP_RETURN_ON_FALSE(cdc_dev->num_cdc_intf_desc > 0, , TAG, "No CDC-ACM specific descriptors found"); + + for (int i = 0; i < cdc_dev->num_cdc_intf_desc; i++) { + switch (((cdc_header_desc_t *)cdc_dev->cdc_intf_desc[i])->bDescriptorSubtype) { + case CDC_DESC_SUBTYPE_HEADER: { + cdc_header_desc_t *desc = (cdc_header_desc_t *)cdc_dev->cdc_intf_desc[i]; + SYS_PRINT("CDC Header Descriptor:\n"); + SYS_PRINT("\tbcdCDC: %d.%d0\n", ((desc->bcdCDC >> 8) & 0xF), ((desc->bcdCDC >> 4) & 0xF)); + break; + } + case CDC_DESC_SUBTYPE_CALL: { + cdc_acm_call_desc_t *desc = (cdc_acm_call_desc_t *)cdc_dev->cdc_intf_desc[i]; + SYS_PRINT("CDC Call Descriptor:\n"); + SYS_PRINT("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val); + SYS_PRINT("\tbDataInterface: %d\n", desc->bDataInterface); + break; + } + case CDC_DESC_SUBTYPE_ACM: { + cdc_acm_acm_desc_t *desc = (cdc_acm_acm_desc_t *)cdc_dev->cdc_intf_desc[i]; + SYS_PRINT("CDC ACM Descriptor:\n"); + SYS_PRINT("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val); + break; + } + case CDC_DESC_SUBTYPE_UNION: { + cdc_union_desc_t *desc = (cdc_union_desc_t *)cdc_dev->cdc_intf_desc[i]; + SYS_PRINT("CDC Union Descriptor:\n"); + SYS_PRINT("\tbControlInterface: %d\n", desc->bControlInterface); + SYS_PRINT("\tbSubordinateInterface[0]: %d\n", desc->bSubordinateInterface[0]); + break; + } + default: + SYS_LOG_WRN("Unsupported CDC specific descriptor"); + break; + } + } +} + +/** + * @brief Cancel transfer and reset endpoint + * + * This function will cancel ongoing transfer a reset its endpoint to ready state. + * + * @param[in] dev_hdl USB device handle + * @param[in] transfer Transfer to be cancelled + * @return esp_err_t + */ +static esp_err_t cdc_reset_transfer_endpoint(usb_device_handle_t dev_hdl, usb_transfer_t *transfer) +{ + assert(dev_hdl); + assert(transfer); + + ESP_RETURN_ON_ERROR(usb_host_endpoint_halt(dev_hdl, transfer->bEndpointAddress), TAG, ); + ESP_RETURN_ON_ERROR(usb_host_endpoint_flush(dev_hdl, transfer->bEndpointAddress), TAG, ); + usb_host_endpoint_clear(dev_hdl, transfer->bEndpointAddress); + return ESP_OK; +} + +esp_err_t cdc_host_close(cdc_acm_dev_hdl_t cdc_hdl) +{ + // CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + + // xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + // Cancel polling of BULK IN and INTERRUPT IN endpoints + cdc_dev->notif.cb = NULL; + cdc_dev->data.in_cb = NULL; + ESP_ERROR_CHECK(cdc_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.in_xfer)); + if (cdc_dev->notif.intf_desc != NULL) { + // ESP_ERROR_CHECK(cdc_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->notif.xfer)); + } + + // Release all interfaces + ESP_ERROR_CHECK(usb_host_interface_release(s_msc_driver->client_handle, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber)); + if ((cdc_dev->notif.intf_desc != NULL) && (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc)) { + // ESP_ERROR_CHECK(usb_host_interface_release(s_msc_driver->client_handle, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber)); + } + + // CDC_ACM_ENTER_CRITICAL(); + // SLIST_REMOVE(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, cdc_dev_s, list_entry); + // CDC_ACM_EXIT_CRITICAL(); + + cdc_device_remove(cdc_dev); + // xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; +} + +///////// dfu 设备处理 + +/** + * @brief Allocate CDC transfers + * + * @param[in] cdc_dev Pointer to CDC device + * @param[in] notif_ep_desc Pointer to notification EP descriptor + * @param[in] in_ep_desc- Pointer to data IN EP descriptor + * @param[in] out_ep_desc Pointer to data OUT EP descriptor + * @param[in] out_buf_len Length of data OUT buffer + * @return esp_err_t + */ +static esp_err_t dfu_transfers_allocate(cdc_dev_t *cdc_dev) +{ + esp_err_t ret; + + // 1. Setup notification and control transfers if they are supported + SYS_LOG_INF("PRE DFU dfu_transfers_allocate"); + // if (notif_ep_desc) { + SYS_LOG_INF("IN DFU dfu_transfers_allocate"); + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(512, 0, &cdc_dev->notif.xfer), + err, + TAG, ); + cdc_dev->notif.xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->notif.xfer->bEndpointAddress = 0; + cdc_dev->notif.xfer->callback = notif_xfer_cb; + cdc_dev->notif.xfer->context = cdc_dev; + cdc_dev->notif.xfer->num_bytes = 512; + + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(cdc_dev->dev_hdl, &dev_info)); + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(4096, 0, &cdc_dev->ctrl_transfer), + err, + TAG, ); + cdc_dev->ctrl_transfer->timeout_ms = 1000; + cdc_dev->ctrl_transfer->bEndpointAddress = 0; + cdc_dev->ctrl_transfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->ctrl_transfer->context = cdc_dev; + cdc_dev->ctrl_transfer->callback = out_xfer_cb; + cdc_dev->ctrl_transfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->context, ESP_ERR_NO_MEM, err, TAG, ); + cdc_dev->ctrl_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_mux, ESP_ERR_NO_MEM, err, TAG, ); + // } + + // // 2. Setup IN data transfer + // ESP_GOTO_ON_ERROR( + // usb_host_transfer_alloc(512, 0, &cdc_dev->data.in_xfer), + // err, + // TAG, ); + // assert(cdc_dev->data.in_xfer); + // cdc_dev->data.in_xfer->callback = in_xfer_cb; + // cdc_dev->data.in_xfer->num_bytes = 512; + // cdc_dev->data.in_xfer->bEndpointAddress = in_ep_desc->bEndpointAddress; + // cdc_dev->data.in_xfer->device_handle = cdc_dev->dev_hdl; + // cdc_dev->data.in_xfer->context = cdc_dev; + + // // 3. Setup OUT bulk transfer (if it is required (out_buf_len > 0)) + // if (out_buf_len != 0) { + // ESP_GOTO_ON_ERROR( + // usb_host_transfer_alloc(out_buf_len, 0, &cdc_dev->data.out_xfer), + // err, + // TAG, ); + // assert(cdc_dev->data.out_xfer); + // cdc_dev->data.out_xfer->device_handle = cdc_dev->dev_hdl; + // cdc_dev->data.out_xfer->context = xSemaphoreCreateBinary(); + // ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->context, ESP_ERR_NO_MEM, err, TAG, ); + // cdc_dev->data.out_mux = xSemaphoreCreateMutex(); + // ESP_GOTO_ON_FALSE(cdc_dev->data.out_mux, ESP_ERR_NO_MEM, err, TAG, ); + // cdc_dev->data.out_xfer->bEndpointAddress = out_ep_desc->bEndpointAddress; + // cdc_dev->data.out_xfer->callback = out_xfer_cb; + // } + return ESP_OK; + +err: + cdc_transfers_free(cdc_dev); + return ret; +} + +/** + * @brief Find CDC interface descriptor and its endpoint descriptors + * + * @note This function is called in open procedure of CDC compliant devices only. + * @param[in] cdc_dev Pointer to CDC device + * @param[in] intf_idx Index of CDC interface that should be used for this device + * @param[out] notif_ep Pointer to notification EP descriptor + * @param[out] in_ep Pointer to data IN EP descriptor + * @param[out] out_ep Pointer to data OUT EP descriptor + * @return esp_err_t + */ +// static esp_err_t dfu_find_intf_and_ep_desc(cdc_dev_t *cdc_dev, uint8_t intf_idx, const usb_ep_desc_t **notif_ep) +// { +// bool interface_found = false; +// const usb_config_desc_t *config_desc; +// const usb_device_desc_t *device_desc; +// int data_intf_idx, notif_intf_idx; +// int desc_offset = 0; +// // Get required descriptors +// ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); +// ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); +// SYS_LOG_INF("get inpoint step0, device_desc->bDeviceClass:%d, device_desc->bDeviceSubClass:%d, device_desc->bDeviceProtocol:%d", device_desc->bDeviceClass, device_desc->bDeviceSubClass, device_desc->bDeviceProtocol); +// // if ((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) && (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) { +// // This is a composite device, that uses Interface Association Descriptor +// SYS_LOG_INF("found 111"); +// // const usb_standard_desc_t *this_desc = (const usb_standard_desc_t *)config_desc; +// // do { +// // SYS_LOG_INF("config_desc:%08x", (uint32_t)config_desc); +// // this_desc = usb_parse_next_descriptor_of_type( +// // this_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &desc_offset); +// // SYS_LOG_INF("fjdlkfladjfkldasjkfl:%d, this_desc:%08x", config_desc->wTotalLength, (uint32_t)this_desc); +// // if (this_desc == NULL) +// // break; // Reached end of configuration descriptor + +// // const usb_iad_desc_t *iad_desc = (const usb_iad_desc_t *)this_desc; +// // SYS_LOG_INF("iad_desc->bFirstInterfa:%d", iad_desc->bFirstInterface); +// // if (iad_desc->bFirstInterface == intf_idx) { +// // // IAD with correct interface number was found: Check Class/Subclass codes, save Interface indexes +// // SYS_LOG_INF() +// // assert(iad_desc->bInterfaceCount == 2); +// // assert(iad_desc->bFunctionClass == USB_CLASS_COMM); +// // assert(iad_desc->bFunctionSubClass == CDC_SUBCLASS_ACM); +// // notif_intf_idx = iad_desc->bFirstInterface; +// // data_intf_idx = iad_desc->bFirstInterface + 1; +// // interface_found = true; +// // } +// // } while (!interface_found); +// // // } else if ((device_desc->bDeviceClass == USB_CLASS_COMM) && (intf_idx == 0)) { +// // // // This is a Communication Device Class +// // // notif_intf_idx = 0; +// // // data_intf_idx = 1; +// // // interface_found = true; +// // // SYS_LOG_INF("found 222"); +// // // } + +// // Save found interfaces descriptors: +// interface_found = true; +// if (interface_found) { +// // Notification IF and EP +// cdc_dev->notif.intf_desc = usb_parse_interface_descriptor(config_desc, 0, 0, &desc_offset); +// assert(cdc_dev->notif.intf_desc); + +// // CDC specific descriptors should be right after CDC-Communication interface descriptor +// // Note: That's why we use usb_parse_next_descriptor instead of usb_parse_next_descriptor_of_type. +// // The latter could return CDC specific descriptors that don't belong to this interface +// const usb_standard_desc_t *cdc_desc = (usb_standard_desc_t *)cdc_dev->notif.intf_desc; +// do { +// cdc_desc = usb_parse_next_descriptor(cdc_desc, config_desc->wTotalLength, &desc_offset); +// if ((cdc_desc == NULL) || (cdc_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_W_VALUE_DT_INTERFACE))) +// break; // We found all CDC specific descriptors +// cdc_dev->num_cdc_intf_desc++; +// cdc_dev->cdc_intf_desc = +// realloc(cdc_dev->cdc_intf_desc, cdc_dev->num_cdc_intf_desc * (sizeof(usb_standard_desc_t *))); +// assert(cdc_dev->cdc_intf_desc); +// cdc_dev->cdc_intf_desc[cdc_dev->num_cdc_intf_desc - 1] = cdc_desc; +// } while (1); +// SYS_LOG_INF("BEFORE NOTIFY EP SETUP DONE"); +// *notif_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->notif.intf_desc, 0, config_desc->wTotalLength, &desc_offset); +// assert(notif_ep); + +// // // Data IF and EP +// // cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, data_intf_idx, 0, &desc_offset); +// // assert(cdc_dev->data.intf_desc); +// // int temp_offset = desc_offset; +// // for (int i = 0; i < 2; i++) { +// // const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset); +// // assert(this_ep); +// // if (USB_EP_DESC_GET_EP_DIR(this_ep)) { +// // *in_ep = this_ep; +// // } else { +// // *out_ep = this_ep; +// // } +// // desc_offset = temp_offset; +// // } +// return ESP_OK; +// } +// return ESP_ERR_NOT_FOUND; +// } + +esp_err_t dfu_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret) +{ + esp_err_t ret; + // CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG); + + // xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + // Find underlying USB device + cdc_dev_t *cdc_dev; + ESP_GOTO_ON_ERROR( + cdc_find_and_open_usb_device(vid, pid, dev_config->connection_timeout_ms, &cdc_dev), + exit, + TAG, + "USB device with VID: 0x%04X, PID: 0x%04X not found", + vid, + pid); + + // Find and save relevant interface and endpoint descriptors + // const usb_ep_desc_t *notif_ep = NULL; + // const usb_ep_desc_t *in_ep = NULL; + // const usb_ep_desc_t *out_ep = NULL; + // ESP_GOTO_ON_ERROR( + // dfu_find_intf_and_ep_desc(cdc_dev, interface_idx, ¬if_ep), + // err, + // TAG, + // "Could not find required interface"); + + // // Check whether found Interfaces are really CDC-ACM + // assert(cdc_dev->notif.intf_desc->bInterfaceClass == USB_CLASS_COMM); + // assert(cdc_dev->notif.intf_desc->bInterfaceSubClass == CDC_SUBCLASS_ACM); + // assert(cdc_dev->notif.intf_desc->bNumEndpoints == 1); + // assert(cdc_dev->data.intf_desc->bInterfaceClass == USB_CLASS_CDC_DATA); + // assert(cdc_dev->data.intf_desc->bNumEndpoints == 2); + + // // Save Communication and Data protocols + // cdc_dev->comm_protocol = (cdc_comm_protocol_t)cdc_dev->notif.intf_desc->bInterfaceProtocol; + // cdc_dev->data_protocol = (cdc_data_protocol_t)cdc_dev->data.intf_desc->bInterfaceProtocol; + + // // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle + ESP_GOTO_ON_ERROR(dfu_transfers_allocate(cdc_dev), err, TAG, ); + // ESP_GOTO_ON_ERROR(cdc_start(cdc_dev, dev_config->event_cb, dev_config->data_cb, dev_config->user_arg), err, TAG, ); + *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev; + + g_cdc_dev = cdc_dev; + SYS_LOG_INF("g_cdc_dev:%08x", (uint32_t)g_cdc_dev); + // xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; + +err: + cdc_device_remove(cdc_dev); +exit: + // xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + *cdc_hdl_ret = NULL; + return ret; +} + +esp_err_t dfu_host_close(cdc_acm_dev_hdl_t cdc_hdl) +{ + // CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + + // xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + // // Cancel polling of BULK IN and INTERRUPT IN endpoints + // cdc_dev->notif.cb = NULL; + // cdc_dev->data.in_cb = NULL; + // ESP_ERROR_CHECK(cdc_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.in_xfer)); + // if (cdc_dev->notif.intf_desc != NULL) { + // ESP_ERROR_CHECK(cdc_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->notif.xfer)); + // } + + // // Release all interfaces + // ESP_ERROR_CHECK(usb_host_interface_release(s_msc_driver->client_handle, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber)); + // if ((cdc_dev->notif.intf_desc != NULL) && (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc)) { + // ESP_ERROR_CHECK(usb_host_interface_release(s_msc_driver->client_handle, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber)); + // } + + // // CDC_ACM_ENTER_CRITICAL(); + // // SLIST_REMOVE(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, cdc_dev_s, list_entry); + // // CDC_ACM_EXIT_CRITICAL(); + + cdc_device_remove(cdc_dev); + // xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; +} + +///// stm32固件升级 +static esp_err_t _usb_control_transfer_ex(cdc_dev_t *cdc_dev, + uint8_t direction, + uint8_t request, + uint16_t value, + uint16_t interface, + uint16_t length, + uint8_t *packet_data, + uint8_t *out_buffer, + uint16_t out_buffer_size, + uint16_t *actual_result_size, + uint16_t timeout) +{ + esp_err_t ret = ESP_FAIL; + CDC_ACM_CHECK(cdc_dev->ctrl_transfer, ESP_ERR_NOT_SUPPORTED); + CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= length, ESP_ERR_INVALID_SIZE); + + // Take Mutex and fill the CTRL request + BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(1000)); + if (!taken) { + return ESP_ERR_TIMEOUT; + } + usb_setup_packet_t *req = (usb_setup_packet_t *)(cdc_dev->ctrl_transfer->data_buffer); + uint8_t *start_of_data = (uint8_t *)req + sizeof(usb_setup_packet_t); + req->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + req->bRequest = request; + req->wValue = value; + req->wIndex = interface; + req->wLength = length; + + if (direction == USB_BM_REQUEST_TYPE_DIR_IN) { + req->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + } else { + memcpy(start_of_data, packet_data, length); + } + + cdc_dev->ctrl_transfer->num_bytes = length + sizeof(usb_setup_packet_t); + ESP_GOTO_ON_ERROR( + usb_host_transfer_submit_control(s_msc_driver->client_handle, cdc_dev->ctrl_transfer), + unblock, + TAG, + "CTRL transfer failed"); + + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(timeout)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 1 second + if (!taken) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + cdc_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->ctrl_transfer); + ret = ESP_ERR_TIMEOUT; + goto unblock; + } + + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Control transfer error"); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->actual_num_bytes == cdc_dev->ctrl_transfer->num_bytes, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + + if (direction == USB_BM_REQUEST_TYPE_DIR_IN) { + uint16_t result_data_length = cdc_dev->ctrl_transfer->actual_num_bytes - sizeof(usb_setup_packet_t); + if (result_data_length > out_buffer_size) { + result_data_length = out_buffer_size; + } + memcpy(out_buffer, start_of_data, result_data_length); + + if (actual_result_size) { + *actual_result_size = result_data_length; + } + } + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->ctrl_mux); + return ret; +} + +static esp_err_t _usb_control_transfer(cdc_dev_t *cdc_dev, + uint8_t direction, + uint8_t request, + uint16_t value, + uint16_t interface, + uint16_t length, + uint8_t *packet_data, + uint8_t *out_buffer, + uint16_t out_buffer_size, + uint16_t *actual_result_size) +{ + esp_err_t ret = _usb_control_transfer_ex(cdc_dev, + direction, + request, + value, + interface, + length, + packet_data, + out_buffer, + out_buffer_size, + actual_result_size, + 5000); + return ret; +} + +static esp_err_t _usb_get_status_ex(cdc_dev_t *cdc_dev, uint8_t *out_result_data /*[6]*/, uint16_t timeout) +{ + return _usb_control_transfer_ex(cdc_dev, + USB_BM_REQUEST_TYPE_DIR_IN, + STM32_DFU_REQUEST_GETSTATUS, + 0, + 0, + 6, + 0, + out_result_data, + 6, + NULL, + timeout); +} + +esp_err_t usbh_stm32_get_status_ex(uint8_t *out_result_data /*[6]*/, uint16_t timeout) +{ + if (g_cdc_dev != NULL) { + return _usb_get_status_ex(g_cdc_dev, out_result_data, timeout); + } + + return ESP_FAIL; +} + +esp_err_t usbh_stm32_get_status(uint8_t *out_result_data /*[6]*/) +{ + // return _usbh_ + return usbh_stm32_get_status_ex(out_result_data, 500); +} + +static esp_err_t _usb_get_string(cdc_dev_t *cdc_dev, + uint8_t index, + char *out_string, + uint16_t max_length, + uint16_t *actual_length) +{ + esp_err_t ret; + SYS_LOG_INF("GET STRING 00000"); + CDC_ACM_CHECK(cdc_dev->ctrl_transfer, ESP_ERR_NOT_SUPPORTED); + // CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= data_len, ESP_ERR_INVALID_SIZE); + + // Take Mutex and fill the CTRL request + SYS_LOG_INF("GET STRING 1111"); + BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(1000)); + if (!taken) { + return ESP_ERR_TIMEOUT; + } + + SYS_LOG_INF("GET STRING 2222"); + // usb_setup_packet_t *req = (usb_setup_packet_t *)(cdc_dev->ctrl_transfer->data_buffer); + // uint8_t *start_of_data = (uint8_t *)cdc_dev->ctrl_transfer->data_buffer; + USB_SETUP_PACKET_INIT_GET_STR_DESC( + (usb_setup_packet_t *)cdc_dev->ctrl_transfer->data_buffer, index, 0x409, ENUM_CTRL_TRANSFER_MAX_LEN); + cdc_dev->ctrl_transfer->num_bytes = sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN; + // req->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + // req->bRequest = request; + // req->wValue = value; + // req->wIndex = cdc_dev->notif.intf_desc->bInterfaceNumber; + // req->wLength = data_len; + + cdc_dev->ctrl_transfer->num_bytes = ENUM_CTRL_TRANSFER_MAX_LEN + sizeof(usb_setup_packet_t); + SYS_LOG_INF("cdc_dev->ctrl_transfer->num_bytes:%d, cdc_dev->ctrl_transfer->data_buffer_size:%d", cdc_dev->ctrl_transfer->num_bytes, cdc_dev->ctrl_transfer->data_buffer_size); + ESP_GOTO_ON_ERROR( + usb_host_transfer_submit_control(s_msc_driver->client_handle, cdc_dev->ctrl_transfer), + unblock, + TAG, + "CTRL transfer failed"); + SYS_LOG_INF("GET STRING 33333"); + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(1000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 1 second + if (!taken) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + cdc_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->ctrl_transfer); + ret = ESP_ERR_TIMEOUT; + SYS_LOG_INF("GET STRING 5555"); + goto unblock; + } + + SYS_LOG_INF("GET STRING 6666"); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Control transfer error"); + SYS_LOG_INF("GET STRING 7777"); + // ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->actual_num_bytes == cdc_dev->ctrl_transfer->num_bytes, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + SYS_LOG_INF("GET STRING 88888:%d", cdc_dev->ctrl_transfer->actual_num_bytes); + uint16_t actual_string_length = 0; + for (int i = 10; i < cdc_dev->ctrl_transfer->actual_num_bytes; i += 2) { + if (actual_string_length > max_length) { + break; + } + out_string[actual_string_length] = cdc_dev->ctrl_transfer->data_buffer[i]; + actual_string_length += 1; + } + SYS_LOG_INF("GET STRING 9999:%s", out_string); + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->ctrl_mux); + return ret; +} + +static void _usb_get_interface_descriptors(cdc_dev_t *cdc_dev, + int interface_num, + char *descriptors, + uint8_t max_descriptors_count, + uint8_t *actual_descriptors_count) +{ + const usb_config_desc_t *config_desc; + int i = 0; + const usb_standard_desc_t *this_desc = NULL; + + SYS_LOG_INF("pre get active config desc done:%d", (uint32_t)cdc_dev->dev_hdl); + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + this_desc = (const usb_standard_desc_t *)config_desc; + + int desc_offset = 0; + SYS_LOG_INF("get active config desc done"); + do { + this_desc = usb_parse_next_descriptor_of_type( + this_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &desc_offset); + if (this_desc == NULL) { + break; // Reached end of configuration descriptor + } + + const usb_intf_desc_t *intf_desc = (const usb_intf_desc_t *)this_desc; + char *result_string = descriptors + i * 255; + memset(result_string, 0, 255); + _usb_get_string(cdc_dev, intf_desc->iInterface, result_string, 255, NULL); + i += 1; + + if (i >= max_descriptors_count) { + break; + } + } while (true); + + for (int k = 0; k < i; k++) { + SYS_LOG_INF("index:%d, descriptor:%s", k, descriptors + k * 255); + } + + if (actual_descriptors_count) { + *actual_descriptors_count = i; + } +} + +void usbh_stm32_get_chipinfo(char *descriptors, uint8_t count, uint8_t *actual_desc_count) +{ + SYS_LOG_INF("before usbh_stm32_get_chipinfo:%d", (uint32_t)g_cdc_dev); + if (g_cdc_dev != NULL) { + _usb_get_interface_descriptors(g_cdc_dev, 0, (char *)descriptors, count, actual_desc_count); + } +} + +static esp_err_t _usb_get_function_descriptors(cdc_dev_t *cdc_dev, int interface_num, usb_function_desc_packet_t *out_function_desc) +{ + esp_err_t ret; + SYS_LOG_INF("GET function 00000"); + CDC_ACM_CHECK(cdc_dev->ctrl_transfer, ESP_ERR_NOT_SUPPORTED); + // CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= data_len, ESP_ERR_INVALID_SIZE); + + // Take Mutex and fill the CTRL request + SYS_LOG_INF("GET function 1111"); + BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(1000)); + if (!taken) { + return ESP_ERR_TIMEOUT; + } + + SYS_LOG_INF("GET function 2222"); + // usb_setup_packet_t *req = (usb_setup_packet_t *)(cdc_dev->ctrl_transfer->data_buffer); + // uint8_t *start_of_data = (uint8_t *)cdc_dev->ctrl_transfer->data_buffer; + USB_SETUP_PACKET_INIT_GET_FUNCTION_DESC((usb_setup_packet_t *)cdc_dev->ctrl_transfer->data_buffer); + // cdc_dev->ctrl_transfer->num_bytes = 4096; // sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN; + // req->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + // req->bRequest = request; + // req->wValue = value; + // req->wIndex = cdc_dev->notif.intf_desc->bInterfaceNumber; + // req->wLength = data_len; + + cdc_dev->ctrl_transfer->num_bytes = 255 + sizeof(usb_setup_packet_t); + SYS_LOG_INF("cdc_dev->ctrl_transfer->num_bytes:%d, cdc_dev->ctrl_transfer->data_buffer_size:%d", cdc_dev->ctrl_transfer->num_bytes, cdc_dev->ctrl_transfer->data_buffer_size); + ESP_GOTO_ON_ERROR( + usb_host_transfer_submit_control(s_msc_driver->client_handle, cdc_dev->ctrl_transfer), + unblock, + TAG, + "get function transfer failed"); + SYS_LOG_INF("GET function 33333"); + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(1000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 1 second + if (!taken) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + cdc_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->ctrl_transfer); + ret = ESP_ERR_TIMEOUT; + SYS_LOG_INF("GET function 5555"); + goto unblock; + } + + SYS_LOG_INF("GET function 6666"); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Control transfer error"); + SYS_LOG_INF("GET function 7777"); + // ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->actual_num_bytes == cdc_dev->ctrl_transfer->num_bytes, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + SYS_LOG_INF("GET function 88888:%d", cdc_dev->ctrl_transfer->actual_num_bytes); + const usb_function_desc_packet_t *function_desc = + (const usb_function_desc_packet_t *)(cdc_dev->ctrl_transfer->data_buffer + sizeof(usb_setup_packet_t)); + memcpy(out_function_desc, function_desc, sizeof(usb_function_desc_packet_t)); + SYS_LOG_INF("GET function 9999"); + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->ctrl_mux); + return ret; +} + +uint16_t usbh_stm32_get_transfer_block_size() +{ + if (g_cdc_dev != NULL) { + usb_function_desc_packet_t function_desc_packet; + esp_err_t ret = _usb_get_function_descriptors(g_cdc_dev, 0, &function_desc_packet); + if (ret != ESP_OK) { + return 2048; + } else { + return function_desc_packet.wTransferSize; + } + } + + return 0; +} + +esp_err_t _usbh_stm32_control_transfer(uint8_t direction, + uint8_t request, + uint16_t value, + uint16_t interface, + uint16_t length, + uint8_t *packet_data, + uint8_t *out_buffer, + uint16_t out_buffer_size, + uint16_t *actual_result_size) +{ + if (g_cdc_dev != NULL) { + esp_err_t ret = _usb_control_transfer(g_cdc_dev, direction, request, value, interface, length, packet_data, out_buffer, out_buffer_size, actual_result_size); + return ret; + } + + return ESP_FAIL; +} + +esp_err_t _usbh_stm32_control_transfer_ex(uint8_t direction, + uint8_t request, + uint16_t value, + uint16_t interface, + uint16_t length, + uint8_t *packet_data, + uint8_t *out_buffer, + uint16_t out_buffer_size, + uint16_t *actual_result_size, + uint16_t timeout) +{ + if (g_cdc_dev != NULL) { + return _usb_control_transfer_ex(g_cdc_dev, + direction, + request, + value, + interface, + length, + packet_data, + out_buffer, + out_buffer_size, + actual_result_size, + timeout); + } + + return ESP_FAIL; +} + +static esp_err_t _usb_get_status(cdc_dev_t *cdc_dev, uint8_t *out_result_data /*[6]*/) +{ + return _usb_get_status_ex(cdc_dev, out_result_data, 500); + ; +} + +static int _get_tick_by_append_ms(unsigned int ms) +{ + return os_get_sys_ticks() + os_calc_msec_to_ticks(ms); +} + +static esp_err_t _usb_clear_status(cdc_dev_t *cdc_dev) +{ + _usb_control_transfer(cdc_dev, USB_BM_REQUEST_TYPE_DIR_OUT, STM32_DFU_REQUEST_CLRSTATUS, 0, 0, 0, 0, NULL, 0, NULL); + + uint8_t status[6]; + memset(status, 0, 6); + esp_err_t ret = _usb_get_status(cdc_dev, status); + if (ESP_OK != ret) { + SYS_LOG_ERR("failed to get status"); + return ret; + } + + if (status[4] != STM32_DFU_STATE_DFU_IDLE) { + uint32_t delay_ms = status[1] | (status[2] << 8) | (status[3] << 16); + uint32_t wait_timeout_ticks = + _get_tick_by_append_ms(delay_ms); + while (os_get_sys_ticks() < wait_timeout_ticks) { + os_thread_sleep(50); + } + + ret = _usb_clear_status(cdc_dev); + } + + return ret; +} + +static esp_err_t _usb_try_read_ob(cdc_dev_t *cdc_dev, uint16_t ob_data_size) +{ + // uint16_t ob_data_size = g_option_bytes.total_size; + uint8_t *ob_data = malloc(ob_data_size); + memset(ob_data, 0, ob_data_size); + uint16_t actual_ob_data_length = 0; + esp_err_t ret = _usb_control_transfer(cdc_dev, USB_BM_REQUEST_TYPE_DIR_IN, STM32_DFU_REQUEST_UPLOAD, 2, 0, ob_data_size, NULL, ob_data, ob_data_size, &actual_ob_data_length); + if (ret != ESP_OK) { + return ret; + } + SYS_LOG_INF("ob data(%d,%d):", ob_data_size, actual_ob_data_length); + ESP_LOG_BUFFER_HEX(TAG, ob_data, ob_data_size); + + uint8_t status[6]; + memset(status, 0, 6); + ret = _usb_get_status_ex(cdc_dev, status, 2000); + CDC_CHECK(ESP_OK == ret, "get status failed on load address", ret); + ESP_LOG_BUFFER_HEX(TAG, status, 6); + if (status[4] == STM32_DFU_STATE_UPLOAD_IDLE && actual_ob_data_length == ob_data_size) { + ret = _usb_clear_status(cdc_dev); + } else { + SYS_LOG_ERR("failed to get status on read ob"); + ret = ESP_FAIL; + } + + free(ob_data); + + return ret; +} + +esp_err_t _usbh_stm32_try_read_ob(uint16_t ob_data_size) +{ + if (g_cdc_dev != NULL) { + return _usb_try_read_ob(g_cdc_dev, ob_data_size); + } + + return ESP_FAIL; +} + +static esp_err_t _usb_unprotect(cdc_dev_t *cdc_dev) +{ + SYS_LOG_INF("unprotect"); + uint8_t unprotect_command = 0x92; + esp_err_t ret = _usb_control_transfer(cdc_dev, USB_BM_REQUEST_TYPE_DIR_OUT, STM32_DFU_REQUEST_DNLOAD, 0, 0, 1, &unprotect_command, NULL, 0, NULL); + if (ret != ESP_OK) { + SYS_LOG_ERR("failed to send unprotect command"); + return ret; + } + + uint8_t status[6]; + memset(status, 0, 6); + ret = _usb_get_status(cdc_dev, status); + if (ret != ESP_OK) { + SYS_LOG_ERR("failed to get status"); + return ret; + } + + if (status[4] == STM32_DFU_STATE_DNBUSY) { + uint32_t delay_ms = 20000 + (status[1] | (status[2] << 8) | (status[3] << 16)); // 等待STM32解除读保护,时间为20秒+delay + SYS_LOG_INF("flash erase is running, wait the flash:%d ms", delay_ms); + uint32_t wait_timeout_ticks = + _get_tick_by_append_ms(delay_ms); + while (os_get_sys_ticks() < wait_timeout_ticks) { + SYS_LOG_INF("waiting flash erase"); + os_thread_sleep(1000); + } + + SYS_LOG_INF("reget status"); + ret = _usb_get_status(cdc_dev, status); + if (ret != ESP_OK) { + SYS_LOG_INF("unprotect mcu done"); + return ESP_OK; // unprotect done(flash was erased), tell the user replug the FC with DFU mode + } else { + SYS_LOG_ERR("failed to unprotect"); + return ESP_FAIL; + } + } + + return ret; +} + +esp_err_t _usbh_stm32_unprotect() +{ + if (g_cdc_dev != NULL) { + return _usb_unprotect(g_cdc_dev); + } + + return ESP_FAIL; +} + +esp_err_t _usbh_stm32_leave_dfu() +{ + if (g_cdc_dev == NULL) { + return ESP_FAIL; + } + + esp_err_t ret; + SYS_LOG_INF("leave dfu 00000"); + CDC_ACM_CHECK(g_cdc_dev->ctrl_transfer, ESP_ERR_NOT_SUPPORTED); + // CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= data_len, ESP_ERR_INVALID_SIZE); + + // Take Mutex and fill the CTRL request + SYS_LOG_INF("leave dfu 1111"); + BaseType_t taken = xSemaphoreTake(g_cdc_dev->ctrl_mux, pdMS_TO_TICKS(1000)); + if (!taken) { + return ESP_ERR_TIMEOUT; + } + + SYS_LOG_INF("leave dfu 2222"); + usb_setup_packet_t *setup_packet = (usb_setup_packet_t *)(g_cdc_dev->ctrl_transfer->data_buffer); + // uint8_t *start_of_data = (uint8_t *)g_cdc_dev->ctrl_transfer->data_buffer; + + setup_packet->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + setup_packet->bRequest = STM32_DFU_REQUEST_GETSTATUS; + setup_packet->wValue = 0; + setup_packet->wIndex = 0; + setup_packet->wLength = 6; + g_cdc_dev->ctrl_transfer->num_bytes = sizeof(usb_setup_packet_t) + 6; + // req->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + // req->bRequest = request; + // req->wValue = value; + // req->wIndex = cdc_dev->notif.intf_desc->bInterfaceNumber; + // req->wLength = data_len; + + // g_cdc_dev->ctrl_transfer->num_bytes = ENUM_CTRL_TRANSFER_MAX_LEN + sizeof(usb_setup_packet_t); + SYS_LOG_INF("g_cdc_dev->ctrl_transfer->num_bytes:%d, g_cdc_dev->ctrl_transfer->data_buffer_size:%d", g_cdc_dev->ctrl_transfer->num_bytes, g_cdc_dev->ctrl_transfer->data_buffer_size); + ESP_GOTO_ON_ERROR( + usb_host_transfer_submit_control(s_msc_driver->client_handle, g_cdc_dev->ctrl_transfer), + unblock, + TAG, + "leave dfu failed"); + SYS_LOG_INF("leave dfu 33333"); + taken = xSemaphoreTake((SemaphoreHandle_t)g_cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(10000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 1 second + if (!taken) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + cdc_reset_transfer_endpoint(g_cdc_dev->dev_hdl, g_cdc_dev->ctrl_transfer); + ret = ESP_ERR_TIMEOUT; + SYS_LOG_INF("leave dfu 5555"); + goto unblock; + } + + SYS_LOG_INF("leave dfu 6666"); + if (g_cdc_dev->ctrl_transfer->status != USB_TRANSFER_STATUS_COMPLETED) { + SYS_LOG_WRN("FAILED TO GET STATUS FOR LEAVE DFU"); + } else { + SYS_LOG_INF("leave dfu 88888:%d", g_cdc_dev->ctrl_transfer->actual_num_bytes); + } + + SYS_LOG_INF("leave dfu 9999"); + ret = ESP_OK; + +unblock: + xSemaphoreGive(g_cdc_dev->ctrl_mux); + return ret; + + // // _usb_control_transfer_ex(pipe_handle, + // // USB_BM_REQUEST_TYPE_DIR_IN, + // // STM32_DFU_REQUEST_GETSTATUS, + // // 0, + // // 0, + // // 6, + // // 0, + // // out_result_data, + // // 6, + // // NULL, + // // timeout); + + // uint8_t direction = USB_BM_REQUEST_TYPE_DIR_IN; + // uint8_t request = STM32_DFU_REQUEST_GETSTATUS; + // uint16_t value = 0; + // uint16_t interface = 0; + // uint16_t length = 6; + // uint8_t *packet_data = NULL; + // uint8_t *out_buffer = NULL; + // uint16_t out_buffer_size = 0; + // uint16_t *actual_result_size = NULL; + // uint16_t timeout = 500; + + // CDC_CHECK(g_cdc_dev != NULL, "g_cdc_dev can't be NULL", ESP_ERR_INVALID_ARG); + // // malloc URB for default control + // uint16_t packet_data_size = ENUM_CTRL_TRANSFER_MAX_LEN; + // if (length > packet_data_size) { + // packet_data_size = length; + // } + // urb_t *urb_ctrl = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + packet_data_size, NULL); + // CDC_CHECK(urb_ctrl != NULL, "alloc urb failed", ESP_ERR_NO_MEM); + + // usb_setup_packet_t *setup_packet = (usb_setup_packet_t *)urb_ctrl->transfer.data_buffer; + // setup_packet->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + // setup_packet->bRequest = request; + // setup_packet->wValue = value; + // setup_packet->wIndex = interface; + // setup_packet->wLength = length; + // urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + length; + + // // Enqueue it + // esp_err_t ret = hcd_urb_enqueue(g_cdc_dev, urb_ctrl); + // CDC_CHECK_GOTO(ESP_OK == ret, "urb enqueue failed", free_urb_); + // SYS_LOG_INF("urb request timeout:%d ms, and becuase it used for leave dfu, so dont wait response", timeout); + + // goto free_urb_; + + // free_urb_: + // _usb_pipe_flush(g_cdc_dev, 1); + // _usb_urb_free(urb_ctrl); + // return ret; +} + +esp_err_t usbh_stm32_clear_status() +{ + // return _usbh_ + if (g_cdc_dev != NULL) { + return _usb_clear_status(g_cdc_dev); + } + + return ESP_FAIL; +} + +static esp_err_t _usb_load_address(cdc_dev_t *cdc_dev, uint32_t address) +{ + // CDC_CHECK(pipe_handle != NULL, "pipe_handle can't be NULL", ESP_ERR_INVALID_ARG); + // // malloc URB for default control + // urb_t *urb_ctrl = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + ENUM_CTRL_TRANSFER_MAX_LEN, NULL); + // CDC_CHECK(urb_ctrl != NULL, "alloc urb failed", ESP_ERR_NO_MEM); + + uint8_t packet_data[5]; + packet_data[0] = 0x21; + packet_data[1] = address & 0xff; + packet_data[2] = (address >> 8) & 0xff; + packet_data[3] = (address >> 16) & 0xff; + packet_data[4] = (address >> 24) & 0xff; + // USB_SETUP_PACKET_INIT_LOAD_ADDRESS((usb_setup_packet_t *)urb_ctrl->transfer.data_buffer, packet_data); + // urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + 5; + // SYS_LOG_INF("load address urb data, original address:%d", address); + // ESP_LOG_BUFFER_HEX(TAG, urb_ctrl->transfer.data_buffer, sizeof(usb_setup_packet_t) + 5); + // // Enqueue it + // esp_err_t ret = hcd_urb_enqueue(pipe_handle, urb_ctrl); + // CDC_CHECK_GOTO(ESP_OK == ret, "urb enqueue failed", free_urb_); + // ret = _default_pipe_event_wait_until(pipe_handle, HCD_PIPE_EVENT_URB_DONE, pdMS_TO_TICKS(500)); + // CDC_CHECK_GOTO(ESP_OK == ret, "urb event error", flush_urb_); + // urb_t *urb_done = hcd_urb_dequeue(pipe_handle); + // CDC_CHECK_GOTO(urb_done == urb_ctrl, "urb status: not same", free_urb_); + // CDC_CHECK_GOTO(USB_TRANSFER_STATUS_COMPLETED == urb_done->transfer.status, + // "urb status: not complete", + // free_urb_); + // CDC_CHECK_GOTO(urb_ctrl->transfer.actual_num_bytes >= sizeof(usb_setup_packet_t), + // "clear status descriptor too short", + // free_urb_); + + esp_err_t ret = _usbh_stm32_control_transfer(USB_BM_REQUEST_TYPE_DIR_OUT, STM32_DFU_REQUEST_DNLOAD, 0, 0, 5, packet_data, NULL, 0, NULL); + CDC_CHECK(ESP_OK == ret, "failed to load address", ret); + + uint8_t status[6]; + memset(status, 0, 6); + ret = _usb_get_status(cdc_dev, status); + CDC_CHECK(ESP_OK == ret, "get status failed on load address", ret); + if (status[4] == STM32_DFU_STATE_DNBUSY) { + uint32_t delay_ms = status[1] | (status[2] << 8) | (status[3] << 16); + uint32_t wait_timeout_ticks = + _get_tick_by_append_ms(delay_ms); + while (os_get_sys_ticks() < wait_timeout_ticks) { + os_thread_sleep(20); + } + + ret = _usb_get_status(cdc_dev, status); + CDC_CHECK(ESP_OK == ret, "get status failed on load address", ret); + if (status[4] != STM32_DFU_STATE_DNLOAD_IDLE) { + SYS_LOG_ERR("unexpeceted dfu status:%d", status[4]); + return ESP_FAIL; + } + } else { + SYS_LOG_INF("result of get status"); + return ESP_FAIL; + } + + return ret; +} + +esp_err_t _usbh_stm32_load_address(uint32_t address) +{ + if (g_cdc_dev != NULL) { + return _usb_load_address(g_cdc_dev, address); + } + + return ESP_FAIL; +} diff --git a/app/drivers/data_port/usb-host/msc/msc_host_vfs.c b/app/drivers/data_port/usb-host/msc/msc_host_vfs.c new file mode 100755 index 0000000..77bb8c7 --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/msc_host_vfs.c @@ -0,0 +1,125 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "private_include/msc_common.h" +#include "include/msc_host_vfs.h" +#include "diskio_impl.h" +#include "ffconf.h" +#include "ff.h" +#include "vfs_fat_internal.h" + +#define DRIVE_STR_LEN 3 + +typedef struct msc_host_vfs { + char drive[DRIVE_STR_LEN]; + char *base_path; + uint8_t pdrv; +} msc_host_vfs_t; + +static const char *TAG = "MSC VFS"; + +static esp_err_t msc_format_storage(size_t block_size, size_t allocation_size, const char *drv, const esp_vfs_fat_mount_config_t *mount_config) +{ + void *workbuf = NULL; + const size_t workbuf_size = 4096; + + MSC_RETURN_ON_FALSE( workbuf = ff_memalloc(workbuf_size), ESP_ERR_NO_MEM ); + + size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(CONFIG_WL_SECTOR_SIZE, mount_config->allocation_unit_size); + ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size); + const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, alloc_unit_size}; + FRESULT err = f_mkfs(drv, &opt, workbuf, workbuf_size); + if (err) { + ESP_LOGE(TAG, "Formatting failed with error: %d", err); + free(workbuf); + return ESP_ERR_MSC_FORMAT_FAILED; + } + + free(workbuf); + return ESP_OK; +} + +static void dealloc_msc_vfs(msc_host_vfs_t *vfs) +{ + free(vfs->base_path); + free(vfs); +} + +esp_err_t msc_host_vfs_register(msc_host_device_handle_t device, + const char *base_path, + const esp_vfs_fat_mount_config_t *mount_config, + msc_host_vfs_handle_t *vfs_handle) +{ + MSC_RETURN_ON_INVALID_ARG(device); + MSC_RETURN_ON_INVALID_ARG(base_path); + MSC_RETURN_ON_INVALID_ARG(mount_config); + MSC_RETURN_ON_INVALID_ARG(vfs_handle); + + FATFS *fs = NULL; + BYTE pdrv; + bool diskio_registered = false; + esp_err_t ret = ESP_ERR_MSC_MOUNT_FAILED; + msc_device_t *dev = (msc_device_t *)device; + size_t block_size = dev->disk.block_size; + size_t alloc_size = mount_config->allocation_unit_size; + + msc_host_vfs_t *vfs = calloc(1, sizeof(msc_host_vfs_t)); + MSC_RETURN_ON_FALSE(vfs != NULL, ESP_ERR_NO_MEM); + + MSC_GOTO_ON_ERROR( ff_diskio_get_drive(&pdrv) ); + + ff_diskio_register_msc(pdrv, &dev->disk); + char drive[DRIVE_STR_LEN] = {(char)('0' + pdrv), ':', 0}; + diskio_registered = true; + + strncpy(vfs->drive, drive, DRIVE_STR_LEN); + MSC_GOTO_ON_FALSE( vfs->base_path = strdup(base_path), ESP_ERR_NO_MEM ); + vfs->pdrv = pdrv; + + MSC_GOTO_ON_ERROR( esp_vfs_fat_register(base_path, drive, mount_config->max_files, &fs) ); + + FRESULT fresult = f_mount(fs, drive, 1); + + if ( fresult != FR_OK) { + if (mount_config->format_if_mount_failed && + (fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR)) { + MSC_GOTO_ON_ERROR( msc_format_storage(block_size, alloc_size, drive, mount_config) ); + MSC_GOTO_ON_FALSE( f_mount(fs, drive, 0) == FR_OK, ESP_ERR_MSC_MOUNT_FAILED ); + } else { + goto fail; + } + } + + *vfs_handle = vfs; + return ESP_OK; + +fail: + if (diskio_registered) { + ff_diskio_unregister(pdrv); + } + esp_vfs_fat_unregister_path(base_path); + if(fs) { + f_mount(NULL, drive, 0); + } + dealloc_msc_vfs(vfs); + return ret; +} + +esp_err_t msc_host_vfs_unregister(msc_host_vfs_handle_t vfs_handle) +{ + MSC_RETURN_ON_INVALID_ARG(vfs_handle); + msc_host_vfs_t *vfs = (msc_host_vfs_t *)vfs_handle; + + f_mount(NULL, vfs->drive, 0); + ff_diskio_unregister(vfs->pdrv); + esp_vfs_fat_unregister_path(vfs->base_path); + dealloc_msc_vfs(vfs); + return ESP_OK; +} diff --git a/app/drivers/data_port/usb-host/msc/msc_scsi_bot.c b/app/drivers/data_port/usb-host/msc/msc_scsi_bot.c new file mode 100755 index 0000000..0c70822 --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/msc_scsi_bot.c @@ -0,0 +1,434 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_log.h" +#include +#include +#include +#include +#include "esp_check.h" +#include "esp_log.h" +#include "private_include/msc_common.h" +#include "private_include/msc_scsi_bot.h" + +#define TAG "USB_MSC_SCSI" + +/* --------------------------- SCSI Definitions ----------------------------- */ +#define CMD_SENSE_VALID_BIT (1 << 7) +#define SCSI_FLAG_DPO (1<<4) +#define SCSI_FLAG_FUA (1<<3) + +#define SCSI_CMD_FORMAT_UNIT 0x04 +#define SCSI_CMD_INQUIRY 0x12 +#define SCSI_CMD_MODE_SELECT 0x55 +#define SCSI_CMD_MODE_SENSE 0x5A +#define SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E +#define SCSI_CMD_READ10 0x28 +#define SCSI_CMD_READ12 0xA8 +#define SCSI_CMD_READ_CAPACITY 0x25 +#define SCSI_CMD_READ_FORMAT_CAPACITIES 0x23 +#define SCSI_CMD_REQUEST_SENSE 0x03 +#define SCSI_CMD_REZERO 0x01 +#define SCSI_CMD_SEEK10 0x2B +#define SCSI_CMD_SEND_DIAGNOSTIC 0x1D +#define SCSI_CMD_START_STOP Unit 0x1B +#define SCSI_CMD_TEST_UNIT_READY 0x00 +#define SCSI_CMD_VERIFY 0x2F +#define SCSI_CMD_WRITE10 0x2A +#define SCSI_CMD_WRITE12 0xAA +#define SCSI_CMD_WRITE_AND_VERIFY 0x2E + +#define IN_DIR CWB_FLAG_DIRECTION_IN +#define OUT_DIR 0 + +#define INQUIRY_VID_SIZE 8 +#define INQUIRY_PID_SIZE 16 +#define INQUIRY_REV_SIZE 4 + +#define CBW_CMD_SIZE(cmd) (sizeof(cmd) - sizeof(msc_cbw_t)) + +#define CBW_BASE_INIT(dir, cbw_len, data_len) \ + .base = { \ + .signature = 0x43425355, \ + .tag = ++cbw_tag, \ + .flags = dir, \ + .lun = 0, \ + .data_length = data_len, \ + .cbw_length = cbw_len, \ + } + +#define FEATURE_SELECTOR_ENDPOINT 0 +#define CSW_SIGNATURE 0x53425355 +#define CBW_SIZE 31 + +#define USB_MASS_REQ_INIT_RESET(ctrl_req_ptr, intf_num) ({ \ + (ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | \ + USB_BM_REQUEST_TYPE_TYPE_CLASS | \ + USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \ + (ctrl_req_ptr)->bRequest = 0xFF; \ + (ctrl_req_ptr)->wValue = 0; \ + (ctrl_req_ptr)->wIndex = (intf_num); \ + (ctrl_req_ptr)->wLength = 0; \ +}) + +#define USB_MASS_REQ_INIT_GET_MAX_LUN(ctrl_req_ptr, intf_num) ({ \ + (ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | \ + USB_BM_REQUEST_TYPE_TYPE_CLASS | \ + USB_BM_REQUEST_TYPE_RECIP_INTERFACE; \ + (ctrl_req_ptr)->bRequest = 0xFE; \ + (ctrl_req_ptr)->wValue = 0; \ + (ctrl_req_ptr)->wIndex = (intf_num); \ + (ctrl_req_ptr)->wLength = 1; \ +}) + +#define USB_SETUP_PACKET_INIT_CLEAR_FEATURE_EP(ctrl_req_ptr, ep_num) ({ \ + (ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | \ + USB_BM_REQUEST_TYPE_TYPE_STANDARD | \ + USB_BM_REQUEST_TYPE_RECIP_ENDPOINT; \ + (ctrl_req_ptr)->bRequest = USB_B_REQUEST_CLEAR_FEATURE; \ + (ctrl_req_ptr)->wValue = FEATURE_SELECTOR_ENDPOINT; \ + (ctrl_req_ptr)->wIndex = (ep_num); \ + (ctrl_req_ptr)->wLength = 0; \ +}) + +#define CWB_FLAG_DIRECTION_IN (1<<7) // device -> host + +/** + * @brief Command Block Wrapper structure + */ +typedef struct __attribute__((packed)) +{ + uint32_t signature; + uint32_t tag; + uint32_t data_length; + uint8_t flags; + uint8_t lun; + uint8_t cbw_length; +} msc_cbw_t; + +/** + * @brief Command Status Wrapper structure + */ +typedef struct __attribute__((packed)) +{ + uint32_t signature; + uint32_t tag; + uint32_t dataResidue; + uint8_t status; +} msc_csw_t; + +typedef struct __attribute__((packed)) +{ + msc_cbw_t base; + uint8_t opcode; + uint8_t flags; + uint32_t address; + uint8_t reserved1; + uint16_t length; + uint8_t reserved2[3]; +} cbw_read10_t; + +typedef struct __attribute__((packed)) +{ + msc_cbw_t base; + uint8_t opcode; + uint8_t flags; + uint32_t address; + uint8_t reserved1; + uint16_t length; + uint8_t reserved2[1]; +} cbw_write10_t; + +typedef struct __attribute__((packed)) +{ + msc_cbw_t base; + uint8_t opcode; + uint8_t flags; + uint32_t address; + uint8_t reserved[6]; +} cbw_read_capacity_t; + +typedef struct __attribute__((packed)) +{ + uint32_t block_count; + uint32_t block_size; +} cbw_read_capacity_response_t; + +typedef struct __attribute__((packed)) +{ + msc_cbw_t base; + uint8_t opcode; + uint8_t flags; + uint8_t reserved[10]; +} cbw_unit_ready_t; + +typedef struct __attribute__((packed)) +{ + msc_cbw_t base; + uint8_t opcode; + uint8_t flags; + uint8_t reserved_0[2]; + uint8_t allocation_length; + uint8_t reserved_1[7]; +} cbw_sense_t; + +typedef struct __attribute__((packed)) +{ + uint8_t error_code; + uint8_t reserved_0; + uint8_t sense_key; + uint32_t info; + uint8_t sense_len; + uint32_t reserved_1; + uint8_t sense_code; + uint8_t sense_code_qualifier; + uint32_t reserved_2; +} cbw_sense_response_t; + +typedef struct __attribute__((packed)) +{ + msc_cbw_t base; + uint8_t opcode; + uint8_t flags; + uint8_t page_code; + uint8_t reserved_0; + uint8_t allocation_length; + uint8_t reserved_1[7]; +} cbw_inquiry_t; + +typedef struct __attribute__((packed)) +{ + msc_cbw_t base; + uint8_t opcode; + uint8_t flags; + uint8_t pc_page_code; + uint8_t reserved_1[4]; + uint16_t parameter_list_length; + uint8_t reserved_2[3]; +} mode_sense_t; + +typedef struct __attribute__((packed)) +{ + uint8_t data[8]; +} mode_sense_response_t; + +typedef struct __attribute__((packed)) +{ + msc_cbw_t base; + uint8_t opcode; + uint8_t flags; + uint8_t reserved_1[2]; + uint8_t prevent; + uint8_t reserved_2[7]; +} prevent_allow_medium_removal_t; + +typedef struct __attribute__((packed)) +{ + uint8_t data[36]; +} cbw_inquiry_response_t; + +// Unique number based on which MSC protocol pairs request and response +static uint32_t cbw_tag; + +static esp_err_t check_csw(msc_csw_t *csw, uint32_t tag) +{ + bool csw_ok = csw->signature == CSW_SIGNATURE && csw->tag == tag && + csw->dataResidue == 0 && csw->status == 0; + + if (!csw_ok) { + ESP_LOGD(TAG, "CSW failed: status %d", csw->status); + } + + return csw_ok ? ESP_OK : ESP_FAIL; +} + +static esp_err_t clear_feature(msc_device_t *device, uint8_t endpoint) +{ + usb_device_handle_t dev = device->handle; + usb_transfer_t *xfer = device->xfer; + + MSC_RETURN_ON_ERROR( usb_host_endpoint_clear(dev, endpoint) ); + USB_SETUP_PACKET_INIT_CLEAR_FEATURE_EP((usb_setup_packet_t *)xfer->data_buffer, endpoint); + MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE) ); + + return ESP_OK; +} + +esp_err_t msc_mass_reset(msc_device_t *device) +{ + usb_transfer_t *xfer = device->xfer; + + USB_MASS_REQ_INIT_RESET((usb_setup_packet_t *)xfer->data_buffer, 0); + MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE) ); + + return ESP_OK; +} + +esp_err_t msc_get_max_lun(msc_device_t *device, uint8_t *lun) +{ + usb_transfer_t *xfer = device->xfer; + + USB_MASS_REQ_INIT_GET_MAX_LUN((usb_setup_packet_t *)xfer->data_buffer, 0); + MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE + 1) ); + + *lun = xfer->data_buffer[USB_SETUP_PACKET_SIZE]; + + return ESP_OK; +} + +static esp_err_t bot_execute_command(msc_device_t *device, msc_cbw_t *cbw, void *data, size_t size) +{ + msc_csw_t csw; + msc_endpoint_t ep = (cbw->flags & CWB_FLAG_DIRECTION_IN) ? MSC_EP_IN : MSC_EP_OUT; + + MSC_RETURN_ON_ERROR( msc_bulk_transfer(device, (uint8_t *)cbw, CBW_SIZE, MSC_EP_OUT) ); + + if (data) { + MSC_RETURN_ON_ERROR( msc_bulk_transfer(device, (uint8_t *)data, size, ep) ); + } + + esp_err_t err = msc_bulk_transfer(device, (uint8_t *)&csw, sizeof(msc_csw_t), MSC_EP_IN); + + if (err == ESP_FAIL && device->transfer_status == USB_TRANSFER_STATUS_STALL) { + ESP_RETURN_ON_ERROR( clear_feature(device, MSC_EP_IN), TAG, "Clear feature failed" ); + // Try to read csw again after clearing feature + err = msc_bulk_transfer(device, (uint8_t *)&csw, sizeof(msc_csw_t), MSC_EP_IN); + if (err) { + ESP_RETURN_ON_ERROR( clear_feature(device, MSC_EP_IN), TAG, "Clear feature failed" ); + ESP_RETURN_ON_ERROR( msc_mass_reset(device), TAG, "Mass reset failed" ); + return ESP_FAIL; + } + } + + MSC_RETURN_ON_ERROR(err); + + return check_csw(&csw, cbw->tag); +} + + +esp_err_t scsi_cmd_read10(msc_device_t *device, + uint8_t *data, + uint32_t sector_address, + uint32_t num_sectors, + uint32_t sector_size) +{ + cbw_read10_t cbw = { + CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_read10_t), num_sectors * sector_size), + .opcode = SCSI_CMD_READ10, + .flags = 0, // lun + .address = __builtin_bswap32(sector_address), + .length = __builtin_bswap16(num_sectors), + }; + + return bot_execute_command(device, &cbw.base, data, num_sectors * sector_size); +} + +esp_err_t scsi_cmd_write10(msc_device_t *device, + const uint8_t *data, + uint32_t sector_address, + uint32_t num_sectors, + uint32_t sector_size) +{ + cbw_write10_t cbw = { + CBW_BASE_INIT(OUT_DIR, CBW_CMD_SIZE(cbw_write10_t), num_sectors * sector_size), + .opcode = SCSI_CMD_WRITE10, + .address = __builtin_bswap32(sector_address), + .length = __builtin_bswap16(num_sectors), + }; + + return bot_execute_command(device, &cbw.base, (void *)data, num_sectors * sector_size); +} + +esp_err_t scsi_cmd_read_capacity(msc_device_t *device, uint32_t *block_size, uint32_t *block_count) +{ + cbw_read_capacity_response_t response; + + cbw_read_capacity_t cbw = { + CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_read_capacity_t), sizeof(response)), + .opcode = SCSI_CMD_READ_CAPACITY, + }; + + MSC_RETURN_ON_ERROR( bot_execute_command(device, &cbw.base, &response, sizeof(response)) ); + + *block_count = __builtin_bswap32(response.block_count); + *block_size = __builtin_bswap32(response.block_size); + + return ESP_OK; +} + +esp_err_t scsi_cmd_unit_ready(msc_device_t *device) +{ + cbw_unit_ready_t cbw = { + CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_unit_ready_t), 0), + .opcode = SCSI_CMD_TEST_UNIT_READY, + }; + + return bot_execute_command(device, &cbw.base, NULL, 0); +} + +esp_err_t scsi_cmd_sense(msc_device_t *device, scsi_sense_data_t *sense) +{ + cbw_sense_response_t response; + + cbw_sense_t cbw = { + CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_sense_t), sizeof(response)), + .opcode = SCSI_CMD_REQUEST_SENSE, + .allocation_length = sizeof(response), + }; + + MSC_RETURN_ON_ERROR( bot_execute_command(device, &cbw.base, &response, sizeof(response)) ); + + if (sense->key) { + ESP_LOGD(TAG, "sense_key: 0x%02X, code: 0x%02X, qualifier: 0x%02X", + response.sense_key, response.sense_code, response.sense_code_qualifier); + } + + sense->key = response.sense_key; + sense->code = response.sense_code; + sense->code_q = response.sense_code_qualifier; + + return ESP_OK; +} + +esp_err_t scsi_cmd_inquiry(msc_device_t *device) +{ + cbw_inquiry_response_t response = { 0 }; + + cbw_inquiry_t cbw = { + CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_inquiry_t), sizeof(response)), + .opcode = SCSI_CMD_INQUIRY, + .allocation_length = sizeof(response), + }; + + return bot_execute_command(device, &cbw.base, &response, sizeof(response) ); +} + +esp_err_t scsi_cmd_mode_sense(msc_device_t *device) +{ + mode_sense_response_t response = { 0 }; + + mode_sense_t cbw = { + CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(mode_sense_t), sizeof(response)), + .opcode = SCSI_CMD_MODE_SENSE, + .pc_page_code = 0x3F, + .parameter_list_length = sizeof(response), + }; + + return bot_execute_command(device, &cbw.base, &response, sizeof(response) ); +} + +esp_err_t scsi_cmd_prevent_removal(msc_device_t *device, bool prevent) +{ + prevent_allow_medium_removal_t cbw = { + CBW_BASE_INIT(OUT_DIR, CBW_CMD_SIZE(prevent_allow_medium_removal_t), 0), + .opcode = SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL, + .prevent = 1, + }; + + return bot_execute_command(device, &cbw.base, NULL, 0); +} diff --git a/app/drivers/data_port/usb-host/msc/private_include/diskio_usb.h b/app/drivers/data_port/usb-host/msc/private_include/diskio_usb.h new file mode 100755 index 0000000..6327d6e --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/private_include/diskio_usb.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Mass storage disk initialization structure + */ +typedef struct { + uint32_t block_size; /**< Block size */ + uint32_t block_count; /**< Block count */ +} usb_disk_t; + +/** + * @brief Register mass storage disk to fat file system + * + * @param[in] pdrv Number of free drive obtained from ff_diskio_get_drive() function + * @param[in] disk usb_disk_t structure + */ +void ff_diskio_register_msc(uint8_t pdrv, usb_disk_t *disk); + +/** + * @brief Obtains number of drive assigned to usb disk upon calling ff_diskio_register_msc() + * + * @param[in] disk usb_disk_t structure + * @return Drive number + */ +uint8_t ff_diskio_get_pdrv_disk(const usb_disk_t *disk); + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/app/drivers/data_port/usb-host/msc/private_include/msc_common.h b/app/drivers/data_port/usb-host/msc/private_include/msc_common.h new file mode 100755 index 0000000..a5bed31 --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/private_include/msc_common.h @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "esp_check.h" +#include "diskio_usb.h" +#include "usb/usb_host.h" +#include "usb/usb_types_stack.h" +#include "freertos/semphr.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef enum { + MSC_EP_OUT, + MSC_EP_IN +} msc_endpoint_t; + +typedef struct { + uint16_t bulk_in_mps; + uint8_t bulk_in_ep; + uint8_t bulk_out_ep; + uint8_t iface_num; +} msc_config_t; + +typedef struct msc_host_device { + STAILQ_ENTRY(msc_host_device) tailq_entry; + usb_transfer_status_t transfer_status; + SemaphoreHandle_t transfer_done; + usb_device_handle_t handle; + usb_transfer_t *xfer; + msc_config_t config; + usb_disk_t disk; +} msc_device_t; + +esp_err_t msc_bulk_transfer(msc_device_t *device_handle, uint8_t *data, size_t size, msc_endpoint_t ep); + +esp_err_t msc_control_transfer(msc_device_t *device_handle, usb_transfer_t *xfer, size_t len); + +#define MSC_GOTO_ON_ERROR(exp) ESP_GOTO_ON_ERROR(exp, fail, TAG, "") + +#define MSC_GOTO_ON_FALSE(exp, err) ESP_GOTO_ON_FALSE( (exp), err, fail, TAG, "" ) + +#define MSC_RETURN_ON_ERROR(exp) ESP_RETURN_ON_ERROR((exp), TAG, "") + +#define MSC_RETURN_ON_FALSE(exp, err) ESP_RETURN_ON_FALSE( (exp), (err), TAG, "") + +#define MSC_RETURN_ON_INVALID_ARG(exp) ESP_RETURN_ON_FALSE((exp) != NULL, ESP_ERR_INVALID_ARG, TAG, "") + +#ifdef __cplusplus +} +#endif diff --git a/app/drivers/data_port/usb-host/msc/private_include/msc_scsi_bot.h b/app/drivers/data_port/usb-host/msc/private_include/msc_scsi_bot.h new file mode 100755 index 0000000..d4ee210 --- /dev/null +++ b/app/drivers/data_port/usb-host/msc/private_include/msc_scsi_bot.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "msc_common.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct { + uint8_t key; + uint8_t code; + uint8_t code_q; +} scsi_sense_data_t; + +esp_err_t scsi_cmd_read10(msc_device_t *device, + uint8_t *data, + uint32_t sector_address, + uint32_t num_sectors, + uint32_t sector_size); + +esp_err_t scsi_cmd_write10(msc_device_t *device, + const uint8_t *data, + uint32_t sector_address, + uint32_t num_sectors, + uint32_t sector_size); + +esp_err_t scsi_cmd_read_capacity(msc_device_t *device, + uint32_t *block_size, + uint32_t *block_count); + +esp_err_t scsi_cmd_sense(msc_device_t *device, scsi_sense_data_t *sense); + +esp_err_t scsi_cmd_unit_ready(msc_device_t *device); + +esp_err_t scsi_cmd_inquiry(msc_device_t *device); + +esp_err_t scsi_cmd_prevent_removal(msc_device_t *device, bool prevent); + +esp_err_t scsi_cmd_mode_sense(msc_device_t *device); + +esp_err_t msc_mass_reset(msc_device_t *device); + +esp_err_t msc_get_max_lun(msc_device_t *device, uint8_t *lun); + +#ifdef __cplusplus +} +#endif diff --git a/app/drivers/data_port/usb-host/usb_types_cdc.h b/app/drivers/data_port/usb-host/usb_types_cdc.h new file mode 100644 index 0000000..eeb8c09 --- /dev/null +++ b/app/drivers/data_port/usb-host/usb_types_cdc.h @@ -0,0 +1,217 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include + +/** + * @brief USB CDC Descriptor Subtypes + * + * @see Table 13, USB CDC specification rev. 1.2 + */ +typedef enum +{ + CDC_DESC_SUBTYPE_HEADER = 0x00, // Header Functional Descriptor + CDC_DESC_SUBTYPE_CALL = 0x01, // Call Management Functional Descriptor + CDC_DESC_SUBTYPE_ACM = 0x02, // Abstract Control Management Functional Descriptor + CDC_DESC_SUBTYPE_DLM = 0x03, // Direct Line Management Functional Descriptor + CDC_DESC_SUBTYPE_TEL_RINGER = 0x04, // Telephone Ringer Functional Descriptor + CDC_DESC_SUBTYPE_TEL_CLSR = 0x05, // Telephone Call and Line State Reporting Capabilities Functional Descriptor + CDC_DESC_SUBTYPE_UNION = 0x06, // Union Functional Descriptor + CDC_DESC_SUBTYPE_COUNTRY = 0x07, // Country Selection Functional Descriptor + CDC_DESC_SUBTYPE_TEL_MODE = 0x08, // Telephone Operational Modes Functional Descriptor + CDC_DESC_SUBTYPE_TERMINAL = 0x09, // USB Terminal + CDC_DESC_SUBTYPE_NCHT = 0x0A, // Network Channel Terminal + CDC_DESC_SUBTYPE_PROTOCOL = 0x08, // Protocol Unit + CDC_DESC_SUBTYPE_EXTENSION = 0x0C, // Extension Unit + CDC_DESC_SUBTYPE_MULTI_CHAN = 0x0D, // Multi-Channel Management Functional Descriptor + CDC_DESC_SUBTYPE_CAPI = 0x0E, // CAPI Control + CDC_DESC_SUBTYPE_ETH = 0x0F, // Ethernet Networking + CDC_DESC_SUBTYPE_ATM = 0x10, // ATM Networking + CDC_DESC_SUBTYPE_WHANDSET = 0x11, // Wireless Handset Control Model Functional Descriptor + CDC_DESC_SUBTYPE_MDLM = 0x12, // Mobile Direct Line Model + CDC_DESC_SUBTYPE_MDLM_DETAIL = 0x13, // MDLM Detail + CDC_DESC_SUBTYPE_DMM = 0x14, // Device Management Model + CDC_DESC_SUBTYPE_OBEX = 0x15, // OBEX Functional + CDC_DESC_SUBTYPE_COMMAND_SET = 0x16, // Command Set + CDC_DESC_SUBTYPE_COMMAND_SET_DETAIL = 0x17, // Command Set Detail Functional Descriptor + CDC_DESC_SUBTYPE_TEL_CM = 0x18, // Telephone Control Model Functional Descriptor + CDC_DESC_SUBTYPE_OBEX_SERVICE = 0x19, // OBEX Service Identifier Functional Descriptor + CDC_DESC_SUBTYPE_NCM = 0x1A // NCM Functional Descriptor +} __attribute__((packed)) cdc_desc_subtype_t; + +/** + * @brief USB CDC Subclass codes + * + * @see Table 4, USB CDC specification rev. 1.2 + */ +typedef enum +{ + CDC_SUBCLASS_DLCM = 0x01, // Direct Line Control Model + CDC_SUBCLASS_ACM = 0x02, // Abstract Control Model + CDC_SUBCLASS_TCM = 0x03, // Telephone Control Model + CDC_SUBCLASS_MCHCM = 0x04, // Multi-Channel Control Model + CDC_SUBCLASS_CAPI = 0x05, // CAPI Control Model + CDC_SUBCLASS_ECM = 0x06, // Ethernet Networking Control Model + CDC_SUBCLASS_ATM = 0x07, // ATM Networking Model + CDC_SUBCLASS_HANDSET = 0x08, // Wireless Handset Control Model + CDC_SUBCLASS_DEV_MAN = 0x09, // Device Management + CDC_SUBCLASS_MOBILE = 0x0A, // Mobile Direct Line Model + CDC_SUBCLASS_OBEX = 0x0B, // OBEX + CDC_SUBCLASS_EEM = 0x0C, // Ethernet Emulation Model + CDC_SUBCLASS_NCM = 0x0D // Network Control Model +} __attribute__((packed)) cdc_subclass_t; + +/** + * @brief USB CDC Communications Protocol Codes + * + * @see Table 5, USB CDC specification rev. 1.2 + */ +typedef enum +{ + CDC_COMM_PROTOCOL_NONE = 0x00, // No class specific protocol required + CDC_COMM_PROTOCOL_V250 = 0x01, // AT Commands: V.250 etc + CDC_COMM_PROTOCOL_PCAA = 0x02, // AT Commands defined by PCCA-101 + CDC_COMM_PROTOCOL_PCAA_A = 0x03, // AT Commands defined by PCAA-101 & Annex O + CDC_COMM_PROTOCOL_GSM = 0x04, // AT Commands defined by GSM 07.07 + CDC_COMM_PROTOCOL_3GPP = 0x05, // AT Commands defined by 3GPP 27.007 + CDC_COMM_PROTOCOL_TIA = 0x06, // AT Commands defined by TIA for CDMA + CDC_COMM_PROTOCOL_EEM = 0x07, // Ethernet Emulation Model + CDC_COMM_PROTOCOL_EXT = 0xFE, // External Protocol: Commands defined by Command Set Functional Descriptor + CDC_COMM_PROTOCOL_VENDOR = 0xFF // Vendor-specific +} __attribute__((packed)) cdc_comm_protocol_t; + +/** + * @brief USB CDC Data Protocol Codes + * + * @see Table 7, USB CDC specification rev. 1.2 + */ +typedef enum +{ + CDC_DATA_PROTOCOL_NONE = 0x00, // No class specific protocol required + CDC_DATA_PROTOCOL_NCM = 0x01, // Network Transfer Block + CDC_DATA_PROTOCOL_I430 = 0x30, // Physical interface protocol for ISDN BRI + CDC_DATA_PROTOCOL_HDLC = 0x31, // HDLC + CDC_DATA_PROTOCOL_Q921M = 0x50, // Management protocol for Q.921 data link protocol + CDC_DATA_PROTOCOL_Q921 = 0x51, // Data link protocol for Q.931 + CDC_DATA_PROTOCOL_Q921TM = 0x52, // TEI-multiplexor for Q.921 data link protocol + CDC_DATA_PROTOCOL_V42BIS = 0x90, // Data compression procedures + CDC_DATA_PROTOCOL_Q931 = 0x91, // Euro-ISDN protocol control + CDC_DATA_PROTOCOL_V120 = 0x92, // V.24 rate adaptation to ISDN + CDC_DATA_PROTOCOL_CAPI = 0x93, // CAPI Commands + CDC_DATA_PROTOCOL_VENDOR = 0xFF // Vendor-specific +} __attribute__((packed)) cdc_data_protocol_t; + +/** + * @brief USB CDC Request Codes + * + * @see Table 19, USB CDC specification rev. 1.2 + */ +typedef enum +{ + CDC_REQ_SEND_ENCAPSULATED_COMMAND = 0x00, + CDC_REQ_GET_ENCAPSULATED_RESPONSE = 0x01, + CDC_REQ_SET_COMM_FEATURE = 0x02, + CDC_REQ_GET_COMM_FEATURE = 0x03, + CDC_REQ_CLEAR_COMM_FEATURE = 0x04, + CDC_REQ_SET_AUX_LINE_STATE = 0x10, + CDC_REQ_SET_HOOK_STATE = 0x11, + CDC_REQ_PULSE_SETUP = 0x12, + CDC_REQ_SEND_PULSE = 0x13, + CDC_REQ_SET_PULSE_TIME = 0x14, + CDC_REQ_RING_AUX_JACK = 0x15, + CDC_REQ_SET_LINE_CODING = 0x20, + CDC_REQ_GET_LINE_CODING = 0x21, + CDC_REQ_SET_CONTROL_LINE_STATE = 0x22, + CDC_REQ_SEND_BREAK = 0x23, + CDC_REQ_SET_RINGER_PARMS = 0x30, + CDC_REQ_GET_RINGER_PARMS = 0x31, + CDC_REQ_SET_OPERATION_PARMS = 0x32, + CDC_REQ_GET_OPERATION_PARMS = 0x33, + CDC_REQ_SET_LINE_PARMS = 0x34, + CDC_REQ_GET_LINE_PARMS = 0x35, + CDC_REQ_DIAL_DIGITS = 0x36, + CDC_REQ_SET_UNIT_PARAMETER = 0x37, + CDC_REQ_GET_UNIT_PARAMETER = 0x38, + CDC_REQ_CLEAR_UNIT_PARAMETER = 0x39, + CDC_REQ_GET_PROFILE = 0x3A, + CDC_REQ_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + CDC_REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + CDC_REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + CDC_REQ_SET_ETHERNET_PACKET_FILTER = 0x43, + CDC_REQ_GET_ETHERNET_STATISTIC = 0x44, + CDC_REQ_SET_ATM_DATA_FORMAT = 0x50, + CDC_REQ_GET_ATM_DEVICE_STATISTICS = 0x51, + CDC_REQ_SET_ATM_DEFAULT_VC = 0x52, + CDC_REQ_GET_ATM_VC_STATISTICS = 0x53, + CDC_REQ_GET_NTB_PARAMETERS = 0x80, + CDC_REQ_GET_NET_ADDRESS = 0x81, + CDC_REQ_SET_NET_ADDRESS = 0x82, + CDC_REQ_GET_NTB_FORMAT = 0x83, + CDC_REQ_SET_NTB_FORMAT = 0x84, + CDC_REQ_GET_NTB_INPUT_SIZE = 0x85, + CDC_REQ_SET_NTB_INPUT_SIZE = 0x86, + CDC_REQ_GET_MAX_DATAGRAM_SIZE = 0x87, + CDC_REQ_SET_MAX_DATAGRAM_SIZE = 0x88, + CDC_REQ_GET_CRC_MODE = 0x89, + CDC_REQ_SET_CRC_MODE = 0x8A +} __attribute__((packed)) cdc_request_code_t; + +/** + * @brief USB CDC Notification Codes + * + * @see Table 20, USB CDC specification rev. 1.2 + */ +typedef enum +{ + CDC_NOTIF_NETWORK_CONNECTION = 0x00, + CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, + CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08, + CDC_NOTIF_RING_DETECT = 0x09, + CDC_NOTIF_SERIAL_STATE = 0x20, + CDC_NOTIF_CALL_STATE_CHANGE = 0x28, + CDC_NOTIF_LINE_STATE_CHANGE = 0x29, + CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A +} __attribute__((packed)) cdc_notification_code_t; + +typedef struct +{ + uint8_t bmRequestType; + cdc_notification_code_t bNotificationCode; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint8_t Data[]; +} __attribute__((packed)) cdc_notification_t; + +/** + * @brief USB CDC Header Functional Descriptor + * + * @see Table 15, USB CDC specification rev. 1.2 + */ +typedef struct +{ + uint8_t bFunctionLength; + const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05 + const cdc_desc_subtype_t bDescriptorSubtype; + uint16_t bcdCDC; // CDC version as binary-coded decimal. This driver is written for version 1.2 +} __attribute__((packed)) cdc_header_desc_t; + +/** + * @brief USB CDC Union Functional Descriptor + * + * @see Table 16, USB CDC specification rev. 1.2 + */ +typedef struct +{ + uint8_t bFunctionLength; + const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05 + const cdc_desc_subtype_t bDescriptorSubtype; + const uint8_t bControlInterface; // Master/controlling interface + uint8_t bSubordinateInterface[]; // Slave/subordinate interfaces +} __attribute__((packed)) cdc_union_desc_t; + +typedef void (*usbh_cdc_cb_t)(void *arg); \ No newline at end of file diff --git a/app/drivers/data_port/usb-host/usbport.c b/app/drivers/data_port/usb-host/usbport.c new file mode 100644 index 0000000..14af70c --- /dev/null +++ b/app/drivers/data_port/usb-host/usbport.c @@ -0,0 +1,787 @@ +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "freertos/ringbuf.h" +#include "esp_err.h" +#include "esp_log.h" +#include "usb/usb_host.h" +#include "msc/include/msc_host.h" +#include "msc/include/msc_host_vfs.h" +#include "ffconf.h" +#include "ff.h" +#include "errno.h" +#include "esp_vfs.h" + +#include "config/hardware_define.h" +// #include "usb/cdc_acm_host.h" + +#include "usbport.h" + +#define CONFIG_SYS_LOG_DUMP_ON 0 +#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_WRN +#define SYS_LOG_DOMAIN "USB" +#include "sys_log.h" + +#define IN_RINGBUF_SIZE (1024 * 1) +#define OUT_RINGBUF_SIZE (1024 * 1) + +#define CDC_CHECK(a, str, ret) \ + do \ + { \ + if (!(a)) \ + { \ + SYS_LOG_ERR(str); \ + return (ret); \ + } \ + } while (0) + +#define CDC_CHECK_GOTO(a, str, lable) \ + do \ + { \ + if (!(a)) \ + { \ + SYS_LOG_ERR(str); \ + goto lable; \ + } \ + } while (0) + +typedef struct +{ + os_pipe_t pipe_obj; + os_work_t *rx_resume_work; +} __usb_data_t; + +static QueueHandle_t app_queue; +static SemaphoreHandle_t ready_to_uninstall_usb; +static msc_host_vfs_handle_t vfs_handle; + +static usbport_status_e g_usb_status = USBPORT_STATUS_NO_CONNECTION; +static __usb_data_t s_usb_data; +static sb_data_port_t s_port; +static msc_host_device_handle_t g_connected_usb_device = NULL; +static cdc_acm_dev_hdl_t g_cdc_dev; + +//////////// CDC 全局变量 //////////// + +#define BULK_OUT_URB_NUM 2 +#define BULK_IN_URB_NUM 2 +#define BUFFER_SIZE_BULK_OUT 512 +#define BUFFER_SIZE_BULK_IN 512 +#define USB_TASK_KILL_BIT BIT1 +#define CDC_DATA_TASK_KILL_BIT BIT4 +#define CDC_DEVICE_READY_BIT BIT19 +#define TIMEOUT_USB_RINGBUF_MS 200 // Timeout for Ring Buffer push + +static EventGroupHandle_t s_usb_event_group = NULL; +// static RingbufHandle_t s_in_ringbuf_handle = NULL; +static RingbufHandle_t s_out_ringbuf_handle = NULL; +// static SemaphoreHandle_t s_usb_read_mux = NULL; +static SemaphoreHandle_t s_usb_write_mux = NULL; +// static TaskHandle_t s_usb_processing_task_hdl = NULL; + +// static portMUX_TYPE s_in_ringbuf_mux = portMUX_INITIALIZER_UNLOCKED; +static portMUX_TYPE s_out_ringbuf_mux = portMUX_INITIALIZER_UNLOCKED; +volatile static int s_in_buffered_data_len = 0; +volatile static int s_out_buffered_data_len = 0; + +//////////// End CDC 全局变量 //////////// + +//////////// MSC 全局变量 //////////////// + +static msc_file_info g_msc_file_lists[MAX_FILE_COUNT]; +// static excluding_file_item_t msc_excluding_files[MAX_EXCLUDING_FILE_COUNT]; +static uint16_t g_msc_file_count = 0; + +//////////// End MSC 全局变量 //////////// + +static void usb_device_event_cb(const msc_host_event_t *event, void *arg) +{ + switch (event->event) + { + case MSC_DEVICE_CONNECTED: + SYS_LOG_INF("MSC device connected"); + break; + case MSC_DEVICE_DISCONNECTED: + SYS_LOG_INF("MSC device disconnected"); + break; + case CDC_DEVICE_CONNECTED: + SYS_LOG_INF("CDC device connected"); + break; + case CDC_DEVICE_DISCONNECTED: + SYS_LOG_INF("CDC device disconnected"); + break; + case DFU_DEVICE_CONNECTED: + SYS_LOG_INF("DFU device connected"); + break; + case DFU_DEVICE_DISCONNECTED: + SYS_LOG_INF("DFU device disconnected"); + break; + } + + xQueueSend(app_queue, event, 10); +} + +// Handles common USB host library events +static void handle_usb_events(void *args) +{ + while (1) + { + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + // Release devices once all clients has deregistered + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) + { + usb_host_device_free_all(); + } + // Give ready_to_uninstall_usb semaphore to indicate that USB Host library + // can be deinitialized, and terminate this task. + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) + { + xSemaphoreGive(ready_to_uninstall_usb); + } + } + + vTaskDelete(NULL); +} + +static void print_device_info(msc_host_device_info_t *info) +{ + const size_t megabyte = 1024 * 1024; + uint64_t capacity = ((uint64_t)info->sector_size * info->sector_count) / megabyte; + + printf("Device info:\n"); + printf("\t Capacity: %llu MB\n", capacity); + printf("\t Sector size: %u\n", info->sector_size); + printf("\t Sector count: %u\n", info->sector_count); + printf("\t PID: 0x%4X \n", info->idProduct); + printf("\t VID: 0x%4X \n", info->idVendor); + wprintf(L"\t iProduct: %S \n", info->iProduct); + wprintf(L"\t iManufacturer: %S \n", info->iManufacturer); + wprintf(L"\t iSerialNumber: %S \n", info->iSerialNumber); +} + +/* ------------------------------- Callbacks -------------------------------- */ +static void handle_rx(uint8_t *data, size_t data_len, void *arg) +{ + SYS_LOG_INF("Data received %d bytes", data_len); + // ESP_LOG_BUFFER_HEXDUMP(data, data_len, ESP_LOG_INFO); + + int write_result = os_pipe_fifo_fill(&s_usb_data.pipe_obj, data, data_len); + if (write_result != data_len) + { + SYS_LOG_WRN("[ERROR]write data to pipe_obj failed, remain: %d bytes, data_len:%d!!", + write_result, + data_len); + } + + if (os_pipe_get_empty_size(&s_usb_data.pipe_obj) >= BUFFER_SIZE_BULK_IN) + { + cdc_submit_transfer_in(g_cdc_dev); + } +} + +static uint8_t check_file_is_in_excluding_list(const char *filename, uint8_t is_dir, const excluding_file_item_t *excluding_files, uint8_t excluding_file_count) +{ + if (excluding_file_count == 0 || excluding_files == NULL) + { + SYS_LOG_INF("excluding file count is empty, break check"); + return 0; + } + + int source_str_len = strlen(filename); + int i = 0; + for (i = 0; i < excluding_file_count; i++) + { + const excluding_file_item_t *file_item = &(excluding_files[i]); + if (is_dir != file_item->is_dir) + { + SYS_LOG_INF("src is dir:%d, target is dir:%d", is_dir, file_item->is_dir); + continue; + } + + uint8_t is_same = 0; + if (file_item->compare_type == EXCLUDING_FILE_COMPARE_ALL) + { + is_same = strcmp(filename, file_item->filename) == 0; + } + else if (file_item->compare_type == EXCLUDING_FILE_COMPARE_PREFIX) + { + int len = strlen(file_item->filename); + if (source_str_len < len) + { + // 如果文件名小于要比较的字符串,则忽略检查 + SYS_LOG_INF("SOURCE LEN:%d, target len:%d", source_str_len, len); + continue; + } + + SYS_LOG_INF("CHECKING, SRC:%s, target:%s, len:%d", filename, file_item->filename, len); + is_same = strncmp(filename, file_item->filename, len) == 0; + } + + if (is_same) + { + SYS_LOG_INF("FOUND THE EXCLUD FILE:%s", filename); + return 1; // 这个文件是要排除的 + } + } + + return 0; +} + +static void list_all_files(const char *dirpath, uint16_t *current_file_index, const excluding_file_item_t *excluding_files, uint8_t excluding_file_count) +{ + // 递归枚举所有文件 + struct dirent *entry; + struct stat entry_stat; + char entrypath[MAX_FILE_PATH]; + char entrysize[16]; + const char *entrytype; + size_t dirpath_len = strlen(dirpath); + DIR *dir = opendir(dirpath); + strlcpy(entrypath, dirpath, sizeof(entrypath)); + if (entrypath[dirpath_len - 1] != '/') + { + entrypath[dirpath_len] = '/'; + dirpath_len += 1; + } + + uint16_t *file_index = current_file_index; + while ((entry = readdir(dir)) != NULL) + { + entrytype = (entry->d_type == DT_DIR ? "directory" : "file"); + + strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len); + if (entry->d_type != DT_DIR) + { + if (check_file_is_in_excluding_list(entry->d_name, 0, excluding_files, excluding_file_count)) + { + continue; + } + + SYS_LOG_INF("file path:%s", entrypath); + if (esp_vfs_stat(__getreent(), entrypath, &entry_stat) == -1) + { + SYS_LOG_ERR("Failed to stat %s : %s", entrytype, entry->d_name); + continue; + } + + sprintf(entrysize, "%ld", entry_stat.st_size); + SYS_LOG_INF("Found %s : %s (%s bytes)", entrytype, entry->d_name, entrysize); + + msc_file_info *file_info = &(g_msc_file_lists[*file_index]); + strlcpy(file_info->file_path, entrypath, MAX_FILE_PATH); + file_info->file_size = entry_stat.st_size; + *file_index = *file_index + 1; + } + else + { + if (check_file_is_in_excluding_list(entry->d_name, 1, excluding_files, excluding_file_count)) + { + continue; + } + + // 检查文件名是否以.号开始,是的话则忽略这个文件夹 + SYS_LOG_INF("ITEM NAME:%s", entry->d_name); + if (strncmp(entry->d_name, "SPOTLI~", 7) == 0) + { + // 这个文件夹是MAC系统自动生成的,用于spotlight搜索的索引文件夹,忽略它,否则会导致opendir失败 + continue; + } + else + { + SYS_LOG_INF("ENTER SUB DIR:%s", entrypath); + list_all_files(entrypath, file_index, excluding_files, excluding_file_count); + } + } + } + closedir(dir); +} + +static void handle_device_events(void *args) +{ + msc_host_event_t app_event; + while (1) + { + xQueueReceive(app_queue, &app_event, portMAX_DELAY); + + switch (app_event.event) + { + case MSC_DEVICE_CONNECTED: + { + uint8_t device_address = app_event.device.address; + ESP_ERROR_CHECK(msc_host_install_device(device_address, &g_connected_usb_device)); + msc_host_print_descriptors(g_connected_usb_device); + msc_host_device_info_t info; + ESP_ERROR_CHECK(msc_host_get_device_info(g_connected_usb_device, &info)); + print_device_info(&info); + const esp_vfs_fat_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 1, + .allocation_unit_size = 1024, + }; + ESP_ERROR_CHECK(msc_host_vfs_register(g_connected_usb_device, "/usb", &mount_config, &vfs_handle)); + + for (int i = 0; i < g_msc_file_count; i++) + { + msc_file_info *file_info = &g_msc_file_lists[i]; + + SYS_LOG_INF("file path: %s, file_size:%d", file_info->file_path, file_info->file_size); + } + + g_usb_status = USBPORT_STATUS_MSC; + } + break; + + case MSC_DEVICE_DISCONNECTED: + ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle)); + vfs_handle = NULL; + ESP_ERROR_CHECK(msc_host_uninstall_device(g_connected_usb_device)); + g_connected_usb_device = NULL; + g_usb_status = USBPORT_STATUS_NO_CONNECTION; + break; + + case CDC_DEVICE_CONNECTED: + { + SYS_LOG_INF("cdc device connected"); + + // SYS_LOG_INF("Installing CDC-ACM driver"); + // ESP_ERROR_CHECK(cdc_acm_host_install(NULL)); + + // cdc_acm_dev_hdl_t cdc_dev; + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 5000, + .out_buffer_size = 64, + .user_arg = NULL, + .event_cb = NULL, + .data_cb = handle_rx}; + cdc_host_open(app_event.vendor_id, app_event.product_id, 0, &dev_config, &g_cdc_dev); + assert(g_cdc_dev); + cdc_host_desc_print(g_cdc_dev); + g_usb_status = USBPORT_STATUS_CDC; + // g_connected_usb_device = cdc_dev; + } + break; + case CDC_DEVICE_DISCONNECTED: + SYS_LOG_INF("cdc device dconnected"); + cdc_host_close(g_cdc_dev); + g_usb_status = USBPORT_STATUS_NO_CONNECTION; + break; + case DFU_DEVICE_CONNECTED: + SYS_LOG_INF("DFU DEVICE CONNECTED"); + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 5000, + .out_buffer_size = 64, + .user_arg = NULL, + .event_cb = NULL, + .data_cb = handle_rx}; + dfu_host_open(app_event.vendor_id, app_event.product_id, 0, &dev_config, &g_cdc_dev); + + g_usb_status = USBPORT_STATUS_DFU; + break; + case DFU_DEVICE_DISCONNECTED: + SYS_LOG_INF("DFU DEVICE DISCONNECTED"); + dfu_host_close(g_cdc_dev); + g_usb_status = USBPORT_STATUS_NO_CONNECTION; + break; + default: + assert(0); + break; + } + + /* code */ + } + + vTaskDelete(NULL); +} + +//////////////////////////////////////////////// +//////////// CDC设备透传的相关的函数 ////////////// +//////////////////////////////////////////////// + +static bool _if_device_ready() +{ + if (!s_usb_event_group) + return false; + return xEventGroupGetBits(s_usb_event_group) & CDC_DEVICE_READY_BIT; +} + +static bool _cdc_driver_is_init(void) +{ + if (s_usb_event_group == NULL) + { + return false; + } + + return true; +} + +static esp_err_t usb_out_ringbuf_push(const uint8_t *buf, size_t write_bytes, TickType_t xTicksToWait) +{ + int res = + xRingbufferSend(s_out_ringbuf_handle, buf, write_bytes, xTicksToWait); + + if (res != pdTRUE) + { + SYS_LOG_WRN("The out buffer is too small, the data has been lost %u", write_bytes); + return ESP_FAIL; + } + + portENTER_CRITICAL(&s_out_ringbuf_mux); + s_out_buffered_data_len += write_bytes; +#ifdef CONFIG_CDC_USE_TRACE_FACILITY + s_ringbuf_out_max = s_out_buffered_data_len > s_ringbuf_out_max ? s_out_buffered_data_len : s_ringbuf_out_max; +#endif + portEXIT_CRITICAL(&s_out_ringbuf_mux); + return ESP_OK; +} + +int usbh_cdc_write_bytes(const uint8_t *buf, size_t length) +{ + SYS_LOG_INF("write data to cdc devi11111"); + CDC_CHECK(buf != NULL, "invalid args", -1); + int tx_data_size = 0; + + if (_cdc_driver_is_init() == false) + { + SYS_LOG_INF("CDC Driver not installed"); + return -1; + } + + if (_if_device_ready() == false) + { + SYS_LOG_INF("Device not connected or not ready"); + return -1; + } + + SYS_LOG_INF("write data to cdc devie2222"); + xSemaphoreTake(s_usb_write_mux, portMAX_DELAY); + esp_err_t ret = usb_out_ringbuf_push(buf, length, pdMS_TO_TICKS(TIMEOUT_USB_RINGBUF_MS)); + + if (ret != ESP_OK) + { + xSemaphoreGive(s_usb_write_mux); + SYS_LOG_DBG("Write pipe_obj failed"); + return -1; + } + + tx_data_size = length; + xSemaphoreGive(s_usb_write_mux); + return tx_data_size; +} + +static int _usbhost_port_write(sb_data_port_t *port, const void *data, uint32_t size) +{ + int result = cdc_write_bytes(data, size); + return result; +} + +static int _usbhost_port_read(sb_data_port_t *port, void *buffer, uint32_t length) +{ + if (os_pipe_is_valid(&s_usb_data.pipe_obj)) + { + int ret = os_pipe_fifo_read(&s_usb_data.pipe_obj, buffer, length); + if (ret > 0 && os_pipe_get_empty_size(&s_usb_data.pipe_obj) >= BUFFER_SIZE_BULK_IN) + { + cdc_submit_transfer_in(g_cdc_dev); + } + + SYS_LOG_INF("Data readed %d bytes", ret); + + return ret; + } + else + { + return -1; + } +} + +static int _usbhost_start(sb_data_port_t *port) +{ + return 0; +} + +static int _usbhost_stop(sb_data_port_t *port) +{ + return 0; +} + +static bool _usbhost_is_started(sb_data_port_t *port) +{ + return true; +} + +static uint32_t _usbhost_get_rx_length(sb_data_port_t *port) +{ + return os_pipe_get_valid_size(&s_usb_data.pipe_obj); +} + +static sb_data_port_vtable_t const usbhost_port_vtable = { + .start = _usbhost_start, // 由具体驱动实现的,以达到节省资源为主要目的,启动数据接口 + .stop = _usbhost_stop, // 由具体驱动实现的,以达到节省资源为主要目的,关闭数据接口 + .write = _usbhost_port_write, // 由具体驱动实现的,写数据到对应的接口。 + .read = _usbhost_port_read, // 由具体驱动实现的,从数据接口中读取已缓存的数据。 + .is_started = _usbhost_is_started, // 由具体驱动实现的,获取当前数据接口是否可用(是否已启动) + .get_rx_length = _usbhost_get_rx_length, // 由具体驱动实现的,获取当前数据接口的本次可读长度 +}; + +//////////// CDC设备透传的相关的函数结束 /////////// + +//////////////////////////////////////////////// +//////////// STM32固件升级相关的函数 ////////////// +//////////////////////////////////////////////// + +// esp_err_t _usbh_stm32_control_transfer(uint8_t direction, +// uint8_t request, +// uint16_t value, +// uint16_t interface, +// uint16_t length, +// uint8_t *packet_data, +// uint8_t *out_buffer, +// uint16_t out_buffer_size, +// uint16_t *actual_result_size) +// { +// // if (g_pipe_hdl_dflt != NULL) { +// // return _usb_control_transfer(g_pipe_hdl_dflt, direction, request, value, interface, length, packet_data, out_buffer, out_buffer_size, actual_result_size); +// // } + +// return ESP_FAIL; +// } + +// esp_err_t _usbh_stm32_control_transfer_ex(uint8_t direction, +// uint8_t request, +// uint16_t value, +// uint16_t interface, +// uint16_t length, +// uint8_t *packet_data, +// uint8_t *out_buffer, +// uint16_t out_buffer_size, +// uint16_t *actual_result_size, +// uint16_t timeout) +// { +// // if (g_pipe_hdl_dflt != NULL) { +// // return _usb_control_transfer_ex(g_pipe_hdl_dflt, +// // direction, +// // request, +// // value, +// // interface, +// // length, +// // packet_data, +// // out_buffer, +// // out_buffer_size, +// // actual_result_size, +// // timeout); +// // } + +// return ESP_FAIL; +// } + +// esp_err_t _usbh_stm32_try_read_ob(uint16_t ob_data_size) +// { +// // if (g_pipe_hdl_dflt != NULL) { +// // return _usb_try_read_ob(g_pipe_hdl_dflt, ob_data_size); +// // } + +// return ESP_FAIL; +// } + +// esp_err_t _usbh_stm32_unprotect() +// { +// // if (g_pipe_hdl_dflt != NULL) { +// // return _usb_unprotect(g_pipe_hdl_dflt); +// // } + +// return ESP_FAIL; +// } + +// esp_err_t _usbh_stm32_leave_dfu() +// { +// return ESP_OK; +// // if (g_pipe_hdl_dflt == NULL) { +// // return ESP_FAIL; +// // } + +// // // _usb_control_transfer_ex(pipe_handle, +// // // USB_BM_REQUEST_TYPE_DIR_IN, +// // // STM32_DFU_REQUEST_GETSTATUS, +// // // 0, +// // // 0, +// // // 6, +// // // 0, +// // // out_result_data, +// // // 6, +// // // NULL, +// // // timeout); + +// // uint8_t direction = USB_BM_REQUEST_TYPE_DIR_IN; +// // uint8_t request = STM32_DFU_REQUEST_GETSTATUS; +// // uint16_t value = 0; +// // uint16_t interface = 0; +// // uint16_t length = 6; +// // uint8_t *packet_data = NULL; +// // uint8_t *out_buffer = NULL; +// // uint16_t out_buffer_size = 0; +// // uint16_t *actual_result_size = NULL; +// // uint16_t timeout = 500; + +// // CDC_CHECK(g_pipe_hdl_dflt != NULL, "g_pipe_hdl_dflt can't be NULL", ESP_ERR_INVALID_ARG); +// // // malloc URB for default control +// // uint16_t packet_data_size = ENUM_CTRL_TRANSFER_MAX_LEN; +// // if (length > packet_data_size) { +// // packet_data_size = length; +// // } +// // urb_t *urb_ctrl = _usb_urb_alloc(0, sizeof(usb_setup_packet_t) + packet_data_size, NULL); +// // CDC_CHECK(urb_ctrl != NULL, "alloc urb failed", ESP_ERR_NO_MEM); + +// // usb_setup_packet_t *setup_packet = (usb_setup_packet_t *)urb_ctrl->transfer.data_buffer; +// // if (direction == USB_BM_REQUEST_TYPE_DIR_IN) { +// // setup_packet->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; +// // setup_packet->bRequest = request; +// // setup_packet->wValue = value; +// // setup_packet->wIndex = interface; +// // setup_packet->wLength = length; +// // urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + length; +// // } else if (direction == USB_BM_REQUEST_TYPE_DIR_OUT) { +// // setup_packet->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; +// // setup_packet->bRequest = request; +// // setup_packet->wValue = value; +// // setup_packet->wIndex = interface; +// // setup_packet->wLength = length; +// // memcpy(((uint8_t *)setup_packet) + sizeof(usb_setup_packet_t), packet_data, length); +// // urb_ctrl->transfer.num_bytes = sizeof(usb_setup_packet_t) + length; +// // } + +// // // Enqueue it +// // esp_err_t ret = hcd_urb_enqueue(g_pipe_hdl_dflt, urb_ctrl); +// // CDC_CHECK_GOTO(ESP_OK == ret, "urb enqueue failed", free_urb_); +// // SYS_LOG_INF("urb request timeout:%d ms, and becuase it used for leave dfu, so dont wait response", timeout); + +// // goto free_urb_; + +// // free_urb_: +// // _usb_pipe_flush(g_pipe_hdl_dflt, 1); +// // _usb_urb_free(urb_ctrl); +// // return ret; +// } + +// uint16_t usbh_stm32_get_transfer_block_size() +// { +// // if (g_pipe_hdl_dflt != NULL) { +// // usb_function_desc_packet_t function_desc_packet; +// // esp_err_t ret = _usb_get_function_descriptors(g_pipe_hdl_dflt, 0, &function_desc_packet); +// // if (ret != ESP_OK) { +// // return 2048; +// // } else { +// // return function_desc_packet.wTransferSize; +// // } +// // } + +// return 0; +// } + +// esp_err_t usbh_stm32_clear_status() +// { +// // // return _usbh_ +// // if (g_pipe_hdl_dflt != NULL) { +// // return _usb_clear_status(g_pipe_hdl_dflt); +// // } + +// return ESP_FAIL; +// } + +// esp_err_t _usbh_stm32_load_address(uint32_t address) +// { +// // if (g_pipe_hdl_dflt != NULL) { +// // return _usb_load_address(g_pipe_hdl_dflt, address); +// // } + +// return ESP_FAIL; +// } + +//////////// STM32固件升级相关的函数结束 ////////////// + +int usbport_init(void) +{ + if (s_port.vtable) + { + SYS_LOG_WRN("repeated initialize"); + return -1; + } + + s_port.vtable = &usbhost_port_vtable; + s_port.data = &s_usb_data; + + TaskHandle_t usb_task_handle, device_task_handle; //, workthread_handle; + + memset(&s_usb_data, 0, sizeof(__usb_data_t)); + os_pipe_create(&s_usb_data.pipe_obj, 1024 * 2); + + ready_to_uninstall_usb = xSemaphoreCreateBinary(); + + app_queue = xQueueCreate(3, sizeof(msc_host_event_t)); + assert(app_queue); + + static usb_host_config_t const host_config = { + // .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + + xTaskCreatePinnedToCore(handle_usb_events, "usb_events", 4096, NULL, SBTASK_PRIORITY_USB_HOST, &usb_task_handle, SBTASK_CORE_INDEX_USB_HOST); + assert(usb_task_handle); + xTaskCreatePinnedToCore(handle_device_events, "device_events", 4096, NULL, SBTASK_PRIORITY_USB_DEVICE, &device_task_handle, SBTASK_CORE_INDEX_USB_DEVICE); + assert(device_task_handle); + + const msc_host_driver_config_t msc_config = { + .create_backround_task = true, + .task_priority = SBTASK_PRIORITY_USB_MSC, + .stack_size = 2048 * 2, + .callback = usb_device_event_cb, + .core_id = 0}; + ESP_ERROR_CHECK(msc_host_install(&msc_config)); + + return 0; +} + +sb_data_port_t *usbport_bind(os_work_t *rx_resume_work) +{ + if (s_port.data == NULL) + { + SYS_LOG_WRN("usb host not initialized"); + return NULL; + } + + __usb_data_t *usb_data = s_port.data; + usb_data->rx_resume_work = rx_resume_work; + + os_pipe_regist(&usb_data->pipe_obj, rx_resume_work, 0); + + return &s_port; +} + +usbport_status_e usbport_get_state(void) +{ + return g_usb_status; +} + +msc_file_info *msc_get_file_list(uint16_t *out_file_count, const excluding_file_item_t *excluding_files, uint8_t excluding_file_count) +{ + g_msc_file_count = 0; + list_all_files("/usb/", &g_msc_file_count, excluding_files, excluding_file_count); + + return msc_get_cached_file_list(out_file_count); +} + +msc_file_info *msc_get_cached_file_list(uint16_t *out_file_count) +{ + if (out_file_count) + { + *out_file_count = g_msc_file_count; + } + return g_msc_file_lists; +} diff --git a/app/drivers/data_port/usb-host/usbport.h b/app/drivers/data_port/usb-host/usbport.h new file mode 100644 index 0000000..4b27c21 --- /dev/null +++ b/app/drivers/data_port/usb-host/usbport.h @@ -0,0 +1,113 @@ +/** + * @file usbport.h + * @author your name (you@domain.com) + * @brief + * @version 0.1 + * @date 2023-09-04 + * + * @copyright Copyright (c) 2023 + * + */ + +#pragma once + +#include +#include +#include + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "os/os.h" + +#include "config/hardware_define.h" +#include "drivers/data_port/sb_data_port.h" + +#include "usb/usb_types_stack.h" + +#include "os/os.h" + +#define MAX_FILE_COUNT 255 +#define MAX_EXCLUDING_FILE_COUNT 20 +#define MAX_FILE_PATH 50 + +typedef enum +{ + USBPORT_STATUS_NO_CONNECTION = 0, // 没有外设 + USBPORT_STATUS_CDC = 1, // 外设处于cdc状态 + USBPORT_STATUS_DFU = 2, // 外设处于dfu状态 + USBPORT_STATUS_MSC = 3, // 外设处于msc状态 +} usbport_status_e; + +typedef enum +{ + EXCLUDING_FILE_COMPARE_ALL = 0, // 比较整个文名名,任意部分与指定字符串不同,则认为不相同 + EXCLUDING_FILE_COMPARE_PREFIX = 1, // 只比较文件名的前面部分是否与指定的字符串相同 +} excluding_file_compare_type; + +typedef struct +{ + const char filename[MAX_FILE_PATH]; + uint8_t is_dir; + uint8_t compare_type; +} excluding_file_item_t; + +#pragma pack(push, 1) +typedef struct +{ + char file_path[MAX_FILE_PATH]; + uint32_t file_size; +} msc_file_info; +#pragma pack(pop) + +int usbport_init(void); +sb_data_port_t *usbport_bind(os_work_t *rx_resume_work); +usbport_status_e usbport_get_state(void); + +// 读取flash layout信息 +void usbh_stm32_get_chipinfo(char *descriptors, uint8_t count, uint8_t *actual_desc_count); +// 读取传输块大小 +// uint16_t usbh_stm32_get_transfer_block_size(); +// clear status +esp_err_t usbh_stm32_clear_status(void); +// get status +esp_err_t usbh_stm32_get_status(uint8_t *out_result_data /*[6]*/); +esp_err_t usbh_stm32_get_status_ex(uint8_t *out_result_data /*[6]*/, uint16_t timeout); +// 设置当前地址 +esp_err_t _usbh_stm32_load_address(uint32_t address); +// 控制指令传输 +esp_err_t _usbh_stm32_control_transfer(uint8_t direction, + uint8_t request, + uint16_t value, + uint16_t interface, + uint16_t length, + uint8_t *packet_data, + uint8_t *out_buffer, + uint16_t out_buffer_size, + uint16_t *actual_result_size); + +esp_err_t _usbh_stm32_control_transfer_ex(uint8_t direction, + uint8_t request, + uint16_t value, + uint16_t interface, + uint16_t length, + uint8_t *packet_data, + uint8_t *out_buffer, + uint16_t out_buffer_size, + uint16_t *actual_result_size, + uint16_t timeout); +// 解除读保护 +esp_err_t _usbh_stm32_unprotect(void); + +// 读取option byte区别 +esp_err_t _usbh_stm32_try_read_ob(uint16_t ob_data_size); + +// 退出dfu状态 +esp_err_t _usbh_stm32_leave_dfu(void); + +// 断开usb设备的连接 +void _usbh_pre_disconnect_deivce(void); + +msc_file_info *msc_get_file_list(uint16_t *out_file_count, const excluding_file_item_t *excluding_files, uint8_t excluding_file_count); +msc_file_info *msc_get_cached_file_list(uint16_t *out_file_count);