1
2 /*
3 * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include "unity.h"
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <assert.h>
13 #include <unistd.h>
14 #include <stdbool.h>
15 #include "freertos/FreeRTOS.h"
16 #include "freertos/task.h"
17 #include "freertos/queue.h"
18 #include "freertos/semphr.h"
19 #include "esp_err.h"
20 #include "esp_log.h"
21 #include "esp_private/usb_phy.h"
22 #include "usb/usb_host.h"
23 #include "msc_host.h"
24 #include "msc_host_vfs.h"
25 #include "ffconf.h"
26 #include "ff.h"
27 #include "esp_vfs.h"
28 #include "test_common.h"
29 #include "soc/usb_wrap_struct.h"
30 #include "soc/soc_caps.h"
31
32 #if SOC_USB_OTG_SUPPORTED
33
34 static const char *TAG = "APP";
35
36 #define ESP_OK_ASSERT(exp) TEST_ASSERT_EQUAL(ESP_OK, exp)
37
38 static esp_vfs_fat_mount_config_t mount_config = {
39 .format_if_mount_failed = false,
40 .max_files = 3,
41 .allocation_unit_size = 1024,
42 };
43
44 static QueueHandle_t app_queue;
45 static SemaphoreHandle_t ready_to_deinit_usb;
46 static msc_host_device_handle_t device;
47 static msc_host_vfs_handle_t vfs_handle;
48 static volatile bool waiting_for_sudden_disconnect;
49 static usb_phy_handle_t phy_hdl = NULL;
50
force_conn_state(bool connected,TickType_t delay_ticks)51 static void force_conn_state(bool connected, TickType_t delay_ticks)
52 {
53 TEST_ASSERT_NOT_EQUAL(NULL, phy_hdl);
54 if (delay_ticks > 0) {
55 //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
56 vTaskDelay(delay_ticks);
57 }
58 ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN));
59 }
60
msc_event_cb(const msc_host_event_t * event,void * arg)61 static void msc_event_cb(const msc_host_event_t *event, void *arg)
62 {
63 if (waiting_for_sudden_disconnect) {
64 waiting_for_sudden_disconnect = false;
65 TEST_ASSERT(event->event == MSC_DEVICE_DISCONNECTED);
66 }
67
68 if (event->event == MSC_DEVICE_CONNECTED) {
69 printf("MSC_DEVICE_CONNECTED\n");
70 } else {
71 printf("MSC_DEVICE_DISCONNECTED\n");
72 }
73
74 xQueueSend(app_queue, event, 10);
75 }
76
77 static const char *TEST_STRING = "Hello World!";
78 static const char *FILE_NAME = "/usb/ESP32.txt";
79
write_read_file(const char * file_path)80 static void write_read_file(const char *file_path)
81 {
82 char line[64];
83
84 ESP_LOGI(TAG, "Writing file");
85 FILE *f = fopen(file_path, "w");
86 TEST_ASSERT( f != NULL);
87 fprintf(f, TEST_STRING);
88 fclose(f);
89
90 ESP_LOGI(TAG, "Reading file");
91 TEST_ASSERT( fopen(file_path, "r") != NULL);
92 fgets(line, sizeof(line), f);
93 fclose(f);
94 // strip newline
95 char *pos = strchr(line, '\n');
96 if (pos) {
97 *pos = '\0';
98 }
99 TEST_ASSERT_EQUAL_STRING(line, TEST_STRING);
100 ESP_LOGI(TAG, "Done");
101 }
102
file_exists(const char * file_path)103 static bool file_exists(const char *file_path)
104 {
105 return ( access(file_path, F_OK) == 0 );
106 }
107
108 // Handles common USB host library events
handle_usb_events(void * args)109 static void handle_usb_events(void *args)
110 {
111 uint32_t end_flags = 0;
112
113 while (1) {
114 uint32_t event_flags;
115 usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
116 // Release devices once all clients has deregistered
117 if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
118 printf("USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS\n");
119 usb_host_device_free_all();
120 end_flags |= 1;
121 }
122 // Give ready_to_deinit_usb semaphore to indicate that USB Host library
123 // can be deinitialized, and terminate this task.
124 if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
125 printf("USB_HOST_LIB_EVENT_FLAGS_ALL_FREE\n");
126 end_flags |= 2;
127 }
128
129 if (end_flags == 3) {
130 xSemaphoreGive(ready_to_deinit_usb);
131 break;
132 }
133 }
134 vTaskDelete(NULL);
135 }
136
check_file_content(const char * file_path,const char * expected)137 static void check_file_content(const char *file_path, const char *expected)
138 {
139 ESP_LOGI(TAG, "Reading %s:", file_path);
140 FILE *file = fopen(file_path, "r");
141 TEST_ASSERT(file != NULL)
142
143 char content[200];
144 fread(content, 1, sizeof(content), file);
145 TEST_ASSERT_EQUAL_STRING(content, expected);
146 fclose(file);
147 }
148
check_sudden_disconnect(void)149 static void check_sudden_disconnect(void)
150 {
151 uint8_t data[512];
152 const size_t DATA_SIZE = sizeof(data);
153
154 ESP_LOGI(TAG, "Creating test.tx");
155 FILE *file = fopen("/usb/test.txt", "w");
156 TEST_ASSERT( file != NULL);
157
158 ESP_LOGI(TAG, "Write data");
159 TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) == DATA_SIZE );
160 TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) == DATA_SIZE );
161 TEST_ASSERT( fflush(file) == 0 );
162
163 ESP_LOGI(TAG, "Trigger a disconnect");
164 //Trigger a disconnect
165 waiting_for_sudden_disconnect = true;
166 force_conn_state(false, 0);
167
168 // Make sure flag was leared in callback
169 vTaskDelay( pdMS_TO_TICKS(100) );
170 TEST_ASSERT( waiting_for_sudden_disconnect == false );
171
172 ESP_LOGI(TAG, "Write data after disconnect");
173 TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) != DATA_SIZE );
174
175 fclose(file);
176 }
177
msc_setup(void)178 static void msc_setup(void)
179 {
180 BaseType_t task_created;
181
182 ready_to_deinit_usb = xSemaphoreCreateBinary();
183
184 TEST_ASSERT( app_queue = xQueueCreate(5, sizeof(msc_host_event_t)) );
185
186 //Initialize the internal USB PHY to connect to the USB OTG peripheral. We manually install the USB PHY for testing
187 usb_phy_config_t phy_config = {
188 .controller = USB_PHY_CTRL_OTG,
189 .target = USB_PHY_TARGET_INT,
190 .otg_mode = USB_OTG_MODE_HOST,
191 .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device
192 .gpio_conf = NULL,
193 };
194 TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl));
195 const usb_host_config_t host_config = {
196 .skip_phy_setup = true,
197 .intr_flags = ESP_INTR_FLAG_LEVEL1,
198 };
199 ESP_OK_ASSERT( usb_host_install(&host_config) );
200
201 task_created = xTaskCreate(handle_usb_events, "usb_events", 2048, NULL, 2, NULL);
202 TEST_ASSERT(task_created);
203
204 const msc_host_driver_config_t msc_config = {
205 .create_backround_task = true,
206 .callback = msc_event_cb,
207 .stack_size = 4096,
208 .task_priority = 5,
209 };
210 ESP_OK_ASSERT( msc_host_install(&msc_config) );
211
212 ESP_LOGI(TAG, "Waiting for USB stick to be connected");
213 msc_host_event_t app_event;
214 xQueueReceive(app_queue, &app_event, portMAX_DELAY);
215 TEST_ASSERT( app_event.event == MSC_DEVICE_CONNECTED );
216 uint8_t device_addr = app_event.device.address;
217
218 ESP_OK_ASSERT( msc_host_install_device(device_addr, &device) );
219 ESP_OK_ASSERT( msc_host_vfs_register(device, "/usb", &mount_config, &vfs_handle) );
220 }
221
msc_teardown(void)222 static void msc_teardown(void)
223 {
224 // Wait to finish any ongoing USB operations
225 vTaskDelay(100);
226
227 ESP_OK_ASSERT( msc_host_vfs_unregister(vfs_handle) );
228 ESP_OK_ASSERT( msc_host_uninstall_device(device) );
229 ESP_OK_ASSERT( msc_host_uninstall() );
230
231 xSemaphoreTake(ready_to_deinit_usb, portMAX_DELAY);
232 vSemaphoreDelete(ready_to_deinit_usb);
233 ESP_OK_ASSERT( usb_host_uninstall() );
234 //Tear down USB PHY
235 TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl));
236 phy_hdl = NULL;
237
238 vQueueDelete(app_queue);
239 }
240
write_read_sectors(void)241 static void write_read_sectors(void)
242 {
243 uint8_t write_data[DISK_BLOCK_SIZE];
244 uint8_t read_data[DISK_BLOCK_SIZE];
245
246 memset(write_data, 0x55, DISK_BLOCK_SIZE);
247 memset(read_data, 0, DISK_BLOCK_SIZE);
248
249 msc_host_write_sector(device, 10, write_data, DISK_BLOCK_SIZE);
250 msc_host_read_sector(device, 10, read_data, DISK_BLOCK_SIZE);
251
252 TEST_ASSERT_EQUAL_MEMORY(write_data, read_data, DISK_BLOCK_SIZE);
253 }
254
erase_storage(void)255 static void erase_storage(void)
256 {
257 uint8_t data[DISK_BLOCK_SIZE];
258 memset(data, 0xFF, DISK_BLOCK_SIZE);
259
260 for (int block = 0; block < DISK_BLOCK_NUM; block++) {
261 msc_host_write_sector(device, block, data, DISK_BLOCK_SIZE);
262 }
263 }
264
check_readme_content(void)265 static void check_readme_content(void)
266 {
267 msc_setup();
268 check_file_content("/usb/README.TXT", README_CONTENTS);
269 msc_teardown();
270 }
271
272 TEST_CASE("Write and read file", "[usb_msc][ignore]")
273 {
274 msc_setup();
275 write_read_file(FILE_NAME);
276 msc_teardown();
277 }
278
279 TEST_CASE("Sudden disconnect", "[usb_msc][ignore]")
280 {
281 msc_setup();
282 check_sudden_disconnect();
283 msc_teardown();
284 }
285
read_write_sectors(void)286 void read_write_sectors(void)
287 {
288 msc_setup();
289 write_read_sectors();
290 msc_teardown();
291 }
292
check_formatting(void)293 void check_formatting(void)
294 {
295 printf("Create file\n");
296 msc_setup();
297 write_read_file(FILE_NAME);
298 msc_teardown();
299
300 printf("File exists after mounting again\n");
301 msc_setup();
302 TEST_ASSERT( file_exists(FILE_NAME) );
303 printf("Erase storage device\n");
304 erase_storage();
305 msc_teardown();
306
307 printf("Check file does not exist after formatting\n");
308 mount_config.format_if_mount_failed = true;
309 msc_setup();
310 TEST_ASSERT( !file_exists(FILE_NAME) );
311 msc_teardown();
312 mount_config.format_if_mount_failed = false;
313 }
314
315 TEST_CASE_MULTIPLE_DEVICES("Sectors can be written and read", "[usb_msc][ignore]", read_write_sectors, device_app);
316
317 TEST_CASE_MULTIPLE_DEVICES("Can be Formated", "[usb_msc][ignore]", check_formatting, device_app);
318
319 TEST_CASE_MULTIPLE_DEVICES("Check README content", "[usb_msc][ignore]", check_readme_content, device_app);
320
321 #endif
322