intechstudio / knot

GNU General Public License v3.0
44 stars 6 forks source link

Possible to make USB HOST driver "pluggable"? #3

Closed marchingband closed 1 year ago

marchingband commented 1 year ago

When I unplug a USB device, the application crashes.

E (10592) USBH: Device 1 gone
ESP_ERROR_CHECK failed: esp_err_t 0x103 (ESP_ERR_INVALID_STATE) at 0x40378f54

PS. I am fan-boy-ing out over here, all your products are fantastic <3

marchingband commented 1 year ago

I see in the example code:

        if (driver_obj.actions == 0) {
            usb_host_client_handle_events(driver_obj.client_hdl, portMAX_DELAY);
        } else {
            ... we handle events
        }

but in yours:

        usb_host_client_handle_events(driver_obj.client_hdl, 10);
        ... we handle events

Perhaps this leaves the driver in an invalid state such that it cannot be deregistered / uninstalled?

marchingband commented 1 year ago

running the example code from esp-idf, when I unplug a device I get:

E (259720) USBH: Device 1 gone
I (259720) CLASS: Deregistering Client
I (259720) DAEMON: No more clients and devices

So the daemon task is seeing that the client is gone and can close well, which does not happen in yours, this 3rd log never occurs.

marchingband commented 1 year ago

I see you replaced driver_obj->actions &= ~ACTION_GET_STR_DESC; in the get_string_descriptor callback with a loop counter. I wonder if you could help me understand why this is setup the way it is. I would love to help get this running as a pluggable driver. I believe yours is the only example of usb midi host online, so it's possible yours is the only esp32s3 midi usb host in the world :)

marchingband commented 1 year ago

Ok I have fixed it but there are some major changes, l'll outline them here, just let me know if you'd like a PR, or if you want to discuss, or if you don't care that's totally understandable too, I realize that your device works perfectly just crashing and starting from the beginning. I will leave some code here to help anyone else who may end up here.

aciton_close_dev() deletes some info that is needed to close the host, so I don't call that, instead, when the callback receives ACTION_CLOSE_DEV, just break

            if (driver_obj.actions & ACTION_CLOSE_DEV) {
                // aciton_close_dev(&driver_obj);
                break;
            }

and then here is the closing code which I add at the end of class_driver_task()

    // Cancel polling of BULK IN and OUT endpoints
    transfer->callback = NULL;
    in_transfer->callback = NULL;

    // Reset the endpoints
    ESP_ERROR_CHECK(usb_host_endpoint_halt(driver_obj.dev_hdl, transfer->bEndpointAddress));
    ESP_ERROR_CHECK(usb_host_endpoint_flush(driver_obj.dev_hdl, transfer->bEndpointAddress));

    ESP_ERROR_CHECK(usb_host_endpoint_halt(driver_obj.dev_hdl, in_transfer->bEndpointAddress));
    ESP_ERROR_CHECK(usb_host_endpoint_flush(driver_obj.dev_hdl, in_transfer->bEndpointAddress));

    ESP_LOGI(TAG, "Free transfers");
    ESP_ERROR_CHECK(usb_host_transfer_free(transfer));

    ESP_LOGI(TAG, "Free in_transfers");
    ESP_ERROR_CHECK(usb_host_transfer_free(in_transfer));

    // Release the host
    ESP_ERROR_CHECK(usb_host_interface_release(driver_obj.client_hdl, driver_obj.dev_hdl, interface_num ));

    // Close the device
    ESP_ERROR_CHECK(usb_host_device_close(driver_obj.client_hdl, driver_obj.dev_hdl));

    ESP_LOGI(TAG, "Deregistering Client");
    ESP_ERROR_CHECK(usb_host_client_deregister(driver_obj.client_hdl));

    //Wait to be deleted
    ESP_LOGI(TAG, "Giving semaphore");
    xSemaphoreGive(signaling_sem);

    ESP_LOGI(TAG, "Suspending self");
    vTaskSuspend(NULL);

The only special value in here is interface_num which I declare globally

uint8_t interface_num = 0;

And inside action_get_config_desc() I added one line

    ESP_LOGI(TAG, "SUKU claim interface %d nice", intf_num);
    ESP_ERROR_CHECK(usb_host_interface_claim(driver_obj->client_hdl, driver_obj->dev_hdl, intf_num, 0));

    // save the intf_num for deregistration
    interface_num = intf_num;

    usb_host_transfer_alloc(4, 0, &transfer);

This code closes the host driver, after which we can run everything from scratch, and wait for a new device. How to best handle this plug/unplug cycle is up for debate, but I have a task that manages the plugging. It starts the tasks, waits on the semaphore, and then deletes the tasks, in a loop.

static void usb_h_task(void *arg)
{
    while(1)
    {
        ESP_LOGI(TAG, "creating usb host driver tasks");
        SemaphoreHandle_t signaling_sem = xSemaphoreCreateBinary();

        TaskHandle_t daemon_task_hdl;
        TaskHandle_t class_driver_task_hdl;

        //Create daemon task
        xTaskCreatePinnedToCore(host_lib_daemon_task,
                                "usb_h_daemon",
                                4096,
                                (void *)signaling_sem,
                                DAEMON_TASK_PRIORITY,
                                &daemon_task_hdl,
                                0);

        //Create the class driver task
        xTaskCreatePinnedToCore(class_driver_task,
                                "usb_h_class",
                                4096,
                                (void *)signaling_sem,
                                CLASS_TASK_PRIORITY,
                                &class_driver_task_hdl,
                                0);

        vTaskDelay(10);     //Add a short delay to let the tasks run

        //Wait for the tasks to complete
        xSemaphoreTake(signaling_sem, portMAX_DELAY);

        ESP_LOGI(TAG, "deleteting usb host tasks");
        //Delete the tasks
        vTaskDelete(class_driver_task_hdl);
        vTaskDelete(daemon_task_hdl);
    }

}

I imagine there are some memory leaks in here, and maybe some unnecessary steps or easier ways to do things, but, I plugged and unplugged like a crazy person and it seems to work well. Ok over and out <3

marchingband commented 1 year ago

The end product: https://github.com/ultrapalace/mainframe/blob/main/main/usb_h.c https://github.com/ultrapalace/mainframe/blob/main/main/usb_h_class.c