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