Handling Non-Standard USB Setup Requests on ESP32-S3 with TinyUSB HID

adasiek1221
Posts: 2
Joined: Sun Sep 15, 2024 2:52 pm

Handling Non-Standard USB Setup Requests on ESP32-S3 with TinyUSB HID

Postby adasiek1221 » Sun Sep 15, 2024 3:07 pm

Hi,

I’m developing a project using the ESP32-S3 with TinyUSB to implement a USB HID interface. While the basic HID communication works perfectly, I’m having trouble handling non-standard USB setup requests (custom bRequest values) sent from the host. Specifically, I want to process and respond to these custom control requests.

Hardware:
ESP32-S2
TinyUSB
Problem:
I’m receiving a custom USB setup packet from the host, but my device is stalling the endpoint instead of processing the request. Here’s an example of the setup packet I'm receiving:


USBD Setup Received 41 00 FD FD 0D 03 00 00
Stall EP0
Image
It appears that my HID implementation is not handling non-standard requests properly, and I need to process these custom bRequest values.

Code Example:
Below is a simplified version of my HID setup. The standard HID functionality is working well, but I’m unsure how to modify the code to capture and handle these non-standard setup requests.

Code: Select all

/*
#include <stdlib.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "class/hid/hid_device.h"
#include "driver/gpio.h"

#define APP_BUTTON (GPIO_NUM_0)
static const char *TAG = "example";

/************* TinyUSB descriptors ****************/
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN)
#define VENDOR_ID  0x0b49
#define PRODUCT_ID 0x064f
#ifdef CFG_TUSB_DEBUG
#undef CFG_TUSB_DEBUG
#endif

#define CFG_TUSB_DEBUG 3  // Poziom debugowania, gdzie 3 to najbardziej szczegółow
// Deskryptor HID
const uint8_t hid_report_descriptor[] = {
    TUD_HID_REPORT_DESC_GENERIC_INOUT(8)
};

// Deskryptor stringów
const char* hid_string_descriptor[5] = {
    (char[]){0x09, 0x04},  // Język angielski
    "ASCII CORPORATION",    // Producent
    "Drmn4ea Tech",         // Produkt
    "123456",               // Numer seryjny
    "ASCII Vib",            // Nazwa interfejsu
};

// Deskryptor urządzenia
static const tusb_desc_device_t hid_device_descriptor = {
    .bLength = sizeof(tusb_desc_device_t),
    .bDescriptorType = TUSB_DESC_DEVICE,
    .bcdUSB = 0x0100,      // USB 1.1
    .bDeviceClass = 0x00,
    .bDeviceSubClass = 0,
    .bDeviceProtocol = 0,
    .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
    .idVendor = VENDOR_ID,
    .idProduct = PRODUCT_ID,
    .bcdDevice = 0x0100,
    .iManufacturer = 1,
    .iProduct = 2,
    .iSerialNumber = 3,
    .bNumConfigurations = 1
};


// Funkcja obsługująca wszystkie żądania SETUP na EP0
bool tud_control_request_cb(uint8_t rhport, tusb_control_request_t const *request)
{
    ESP_LOGI(TAG, "Obsługa Setup request: bmRequestType=0x%02X, bRequest=0x%02X, wValue=0x%04X, wIndex=0x%04X",
             request->bmRequestType, request->bRequest, request->wValue, request->wIndex);

    // Sprawdzamy, czy jest to Vendor-specific request (bmRequestType = 0x41)
    if (request->bmRequestType == 0x21) {
        ESP_LOGI(TAG, "Vendor-specific request otrzymano!");

        // Odpowiadamy na żądanie kontrolne
        if (request->bRequest == 0x00) {
            // Na przykład: Zwróć jakiś niestandardowy raport lub konfigurację
            const char* response = "Vendor-specific response";
            tud_control_xfer(rhport, request, (void*)response, strlen(response));
            return true;
        }
    }

    // Jeżeli żądanie nie jest obsługiwane, wysyłamy STALL
    return false;
}




// Funkcja do obsługi GET_REPORT
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) {
    ESP_LOGI(TAG, "GET_REPORT request: instance=%d, report_id=%d, report_type=%d", instance, report_id, report_type);
    
    // Wypełniamy bufor zerami, aby zwrócić pusty raport
    memset(buffer, 0, reqlen);
    
    // Zwracamy ilość danych do hosta
    return reqlen;
}

// Deskryptor konfiguracji
static const uint8_t hid_configuration_descriptor[] = {
    TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, 0x80, 98),
    TUD_HID_DESCRIPTOR(0, 4, false, sizeof(hid_report_descriptor), 0x81, 8, 10),  // IN (0x81)
    TUD_HID_DESCRIPTOR(1, 4, false, sizeof(hid_report_descriptor), 0x00, 8, 10)   // OUT (0x02)
};

/********* TinyUSB HID callbacks ***************/

// Deskryptor raportu HID
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
    return hid_report_descriptor;
}

// Odbieranie danych od hosta (OUT endpoint)
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) {
    ESP_LOGI(TAG, "Odebrano dane od hosta: %.*s", bufsize, buffer);
}

/********* Application ***************/

void app_main(void) {
    ESP_LOGI(TAG, "Inicjalizacja USB HID");

    const tinyusb_config_t tusb_cfg = {
        .device_descriptor = &hid_device_descriptor,
        .string_descriptor = hid_string_descriptor,
        .string_descriptor_count = sizeof(hid_string_descriptor) / sizeof(hid_string_descriptor[0]),
        .external_phy = false,
        .configuration_descriptor = hid_configuration_descriptor,
    };

    ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));

    ESP_LOGI(TAG, "USB HID zainicjalizowane");

    while (1) {
        if (tud_mounted()) {
            //ESP_LOGI(TAG, "Urządzenie HID podłączone i działa, oczekiwanie na dane...");
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

Question:
Is there a way to capture and handle non-standard USB setup requests using TinyUSB in HID mode? Specifically, I want to intercept and process custom bRequest values in the setup packets (for example: 0x41 00 FD FD 0D 03 00 00). How can I extend my HID implementation to handle these custom requests?

Any help or guidance on how to implement this would be greatly appreciated.

Thanks!
Attachments
tusb_hid.zip
(23.87 MiB) Downloaded 152 times

adasiek1221
Posts: 2
Joined: Sun Sep 15, 2024 2:52 pm

Re: Handling Non-Standard USB Setup Requests on ESP32-S3 with TinyUSB HID

Postby adasiek1221 » Thu Sep 19, 2024 11:39 am

I solved the problem less professionally by modifying liblary usbtinyusb catching the case responsilbe for setup request in class usbd.c

Code: Select all

case DCD_EVENT_SETUP_RECEIVED:
    TU_LOG_PTR(USBD_DBG, &event.setup_received);
    TU_LOG(USBD_DBG, "\r\n");

    // Mark as connected after receiving the 1st setup packet.
    _usbd_dev.connected = 1;

    // Clear busy and claimed flags for control endpoints
    _usbd_dev.ep_status[0][TUSB_DIR_OUT].busy = 0;
    _usbd_dev.ep_status[0][TUSB_DIR_OUT].claimed = 0;
    _usbd_dev.ep_status[0][TUSB_DIR_IN].busy = 0;
    _usbd_dev.ep_status[0][TUSB_DIR_IN].claimed = 0;

    // Sprawdzenie, czy to żądanie niestandardowe (0x41 wskazuje na niestandardowy request)
    if (event.setup_received.bmRequestType == 0x41) // Niestandardowy request od hosta
    {
        TU_LOG(USBD_DBG, "Odebrano vendor-specific żądanie\n");

        // Bufor na dane z event.setup_received
        char buffer[128]; // Dostosuj rozmiar bufora do swoich potrzeb

        // Przekonwertuj dane na format heksadecymalny (8 bajtów, zgodnie z Twoim przykładem)
        snprintf(buffer, sizeof(buffer), 
                 "%02X %02X %04X %04X %04X", 
                 event.setup_received.bmRequestType, 
                 event.setup_received.bRequest, 
                 event.setup_received.wValue, 
                 event.setup_received.wIndex, 
                 event.setup_received.wLength);

        // Wyświetlaj dane w prostszym formacie
        TU_LOG(USBD_DBG, "Zawartość bufora: %s", buffer);

        // Wysyłanie bufora jako odpowiedź do hosta
        tud_control_xfer(event.rhport, &event.setup_received, buffer, strlen(buffer));

        // Wywołanie callbacka tud_hid_set_report_cb, aby przetworzyć dane
        tud_hid_set_report_cb(0, event.setup_received.bRequest, HID_REPORT_TYPE_OUTPUT, (uint8_t*)buffer, strlen(buffer));

        break;
    }

An0n4533
Posts: 2
Joined: Tue Nov 25, 2025 8:58 am

Re: Handling Non-Standard USB Setup Requests on ESP32-S3 with TinyUSB HID

Postby An0n4533 » Tue Nov 25, 2025 9:45 am

A little late to the party and not familiar with the tinyusb codebase (or usage) but..

You added your of code inside a default event handler. Presumably (based on how the function is defined) the function pointer to that handler is either entered as a value in a field of some struct used in setting up the device/usb stack or there's a way to redefine it later. You can then create your own handler as a wrapper that tests for the specific case you need (and then acts accordingly) but otherwise simply calls the default handler. You could then install your this handler in the default handler's place by providing its pointer during the setup instead of the default one.

Even if the library doesn't provide a way to set the event handler, I'd argue it is cleaner to only modify the (presumably) single line of setup in the existing code (and only add your own code) than to stick a larger swath of your own code inside an existing library function. Or maybe you could, instead of calling the tinyusb's setup function, copy-paste its contents and stick the function pointer of your own handler/wrapper there.

Of course, if there is no way to install a custom handler (which doesn't seem probable), the nicest way to do it would be to add the setup function/code path/interface allowing custom handlers to be used (e.g. by exposing the whatever setup struct used that defines the handlers) and then maybe refactor the default path to use that in the default setup. You could then send it as a patch to the library maintainers for others to take advantage of too.

I suspect, though, that there is a way to install custom handlers, even if it is by setting new values in global structs or variables before a specific phase in the library setup.

An0n4533
Posts: 2
Joined: Tue Nov 25, 2025 8:58 am

Re: Handling Non-Standard USB Setup Requests on ESP32-S3 with TinyUSB HID

Postby An0n4533 » Tue Nov 25, 2025 1:12 pm

Looking at the sources a bit further, my previous post is wrong.
Then again, on receiving that event, the handler calls process_control_request(), which has at the very beginning a place marked with a comment as "Vendor request", that checks for a specific vendor id and sets a handler and calls tud_vendor_control_xfer_cb(). That function is a WEAK stub and AFAICT can be overridden for the very purpose of vendor-specific stuff.

Who is online

Users browsing this forum: ChatGPT-User and 3 guests