extending the framework available USB interfaces with a vendor (bulk endpoints) interface

rodv92

Member
Hi, I started recently to port an Arduino Due based bulk USB interface to TeensyDuino for Teensy 4.1
The main difference seems that Arduino supported the PluggableUSB class that allows to extend the USB device with interfaces and endpoints and its associated required functions for enumerations as such :
Code:
class DataBulk_: public PluggableUSBModule {
  public:
    DataBulk_();
 
    //uint32_t available();
    uint32_t write(void *buf, size_t len);
 
  protected:
    virtual bool setup(USBSetup &setup) override;
    virtual int getInterface(uint8_t *interfaceCount) override;
    virtual int getDescriptor(USBSetup &setup) override;
    virtual uint8_t getShortName(char *name) override;
 
  private:
    uint32_t epType[2];
  };

While for teensyduino, it's all inside the framework cores source. (at least for now?)
So I tried to extend it with a "vendor bulk" interface on top of Serial, and it seems ok from the enumeration standpoint, I still will need to benchmark it and troubleshoot using a tester and host based libusb code.

I think it could be nice to extend the framework for a CDC ACM serial (status EP and IN/OUT endpoint) + Vendor Bulk (EP IN/OUT endpoints, 512 MxPacketSize, High speed USB).
That would provide a reasonable general use case for a large number of data transfer oriented projects, where the CDC ACM is then repurposed for telemetry, control, and connection control (such as linestate etc), and the vendor interface and its endpoints for fast data transfer (as it allows to get rid of all host serial associated clutter such as line discipline, special character management, cdc_acm driver binding, internal buffering "black box" management, which can be cumbersome, at least on linux)

The extension was done like this :

Code:
In usb.c :

37d36
< #include "usb_vendor.h"
489,491d487
<         #endif
<         #if defined(VENDOR_INTERFACE)
<         usb_vendor_configure();

Code:
In usb_desc.c :

87,100d86
< // OPTIONAL: uncommenting the following comment block makes the device entirely transparent to cdc_acm
< // driver on linux, and would require manual binding of the kernel driver.
< /*
< #ifdef VENDOR_DESCRIPTOR
< #ifndef DEVICE_CLASS
< #define DEVICE_CLASS 0xFF
< #endif
< #ifndef DEVICE_SUBCLASS
< #define DEVICE_SUBCLASS 0x00
< #endif
< #ifndef DEVICE_PROTOCOL
< #define DEVICE_PROTOCOL 0x00
< #endif
< #endif
< */
<// END OPTIONAL.
<
601,608c587
< #define VENDOR_INTERFACE_DESC_POS    MIDI_INTERFACE_DESC_POS+MIDI_INTERFACE_DESC_SIZE
< #ifdef  VENDOR_INTERFACE
< #define VENDOR_INTERFACE_DESC_SIZE    9+7+7
< #else
< #define VENDOR_INTERFACE_DESC_SIZE    0     
< #endif
<
< #define KEYBOARD_INTERFACE_DESC_POS    VENDOR_INTERFACE_DESC_POS+VENDOR_INTERFACE_DESC_SIZE
---
> #define KEYBOARD_INTERFACE_DESC_POS    MIDI_INTERFACE_DESC_POS+MIDI_INTERFACE_DESC_SIZE
1172,1199d1150
<
< #ifdef VENDOR_INTERFACE
<         // configuration for 480 Mbit/sec speed
<         // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
<         9,                                      // bLength
<         4,                                      // bDescriptorType
<         VENDOR_INTERFACE,                       // bInterfaceNumber
<         0,                                      // bAlternateSetting
<         2,                                      // bNumEndpoints
<         0xFF,                                   // bInterfaceClass (0xFF = vendor)
<         0x00,                                   // bInterfaceSubClass
<         0x00,                                   // bInterfaceProtocol
<         0,                                      // iInterface
<         // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
<         7,                                      // bLength
<         5,                                      // bDescriptorType
<         VENDOR_RX_ENDPOINT | 0x80,              // bEndpointAddress
<         0x02,  //USB_ENDPOINT_TYPE_BULK                                 // bmAttributes (0x02=bulk)
<         LSB(VENDOR_RX_SIZE),MSB(VENDOR_RX_SIZE),// wMaxPacketSize
<         0,                                      // bInterval
<         // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
<         7,                                      // bLength
<         5,                                      // bDescriptorType
<         VENDOR_TX_ENDPOINT,                     // bEndpointAddress
<         0x02,  //USB_ENDPOINT_TYPE_BULK                                 // bmAttributes (0x02=bulk)
<         LSB(VENDOR_TX_SIZE),MSB(VENDOR_TX_SIZE),// wMaxPacketSize
<         0,                                      // bInterval
< #endif // VENDOR INTERFACE

Code:
in usb_desc.h
587,622d586
<   #elif defined(USB_VENDOR)
<
<   #define VENDOR_ID        0x16C0
<   #define PRODUCT_ID        0x0486
<   #define MANUFACTURER_NAME    {'V','E','N','D','O','R'}
<   #define MANUFACTURER_NAME_LEN    6
<   #define PRODUCT_NAME        {'D','E','V','I','C','E'}
<   #define PRODUCT_NAME_LEN    6
<   #define EP0_SIZE        64
<   #define NUM_ENDPOINTS         4
<   #define NUM_INTERFACE        3
<   #define VENDOR_INTERFACE      2
<   #define VENDOR_TX_ENDPOINT    6
<   #define VENDOR_TX_SIZE        512
<   #define VENDOR_TX_INTERVAL    1     // TODO: is this ok for 480 Mbit speed
<   #define VENDOR_RX_ENDPOINT    5
<   #define VENDOR_RX_SIZE        512
<   #define VENDOR_RX_INTERVAL    1     // TODO: is this ok for 480 Mbit speed
<   #define VENDOR_DESCRIPTOR    1       // Vendor-specific device descriptor
<   #define CDC_STATUS_INTERFACE    0
<   #define CDC_DATA_INTERFACE    1
<   #define CDC_ACM_ENDPOINT    2
<   #define CDC_RX_ENDPOINT       3
<   #define CDC_TX_ENDPOINT       4
<   #define CDC_ACM_SIZE          16
<   #define CDC_RX_SIZE_480       512
<   #define CDC_TX_SIZE_480       512
<   #define CDC_RX_SIZE_12        64
<   #define CDC_TX_SIZE_12        64
<
<   #define ENDPOINT2_CONFIG    ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT // EP0?
<   #define ENDPOINT3_CONFIG    ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_UNUSED // CDC ACM RX EP
<   #define ENDPOINT4_CONFIG      ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_BULK // CDC ACM TX EP
<   #define ENDPOINT5_CONFIG    ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_UNUSED // VENDOR RX EP
<   #define ENDPOINT6_CONFIG      ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_BULK // VENDOR TX EP
<

Code:
//new file usb_vendor.c, based entirely on usb_rawhid.c

#include "usb_dev.h"
#include "usb_vendor.h"
#include "avr/pgmspace.h" // for PROGMEM, DMAMEM, FASTRUN
#include "core_pins.h" // for yield(), millis()
#include <string.h>    // for memcpy()
//#include "HardwareSerial.h"

#include "debug/printf.h"

#ifdef VENDOR_INTERFACE // defined by usb_dev.h -> usb_desc.h

#define TX_NUM   4
static transfer_t tx_transfer[TX_NUM] __attribute__ ((used, aligned(32)));
DMAMEM static uint8_t txbuffer[VENDOR_TX_SIZE * TX_NUM];
static uint8_t tx_head=0;

#define RX_NUM  4
static transfer_t rx_transfer[RX_NUM] __attribute__ ((used, aligned(32)));
DMAMEM static uint8_t rx_buffer[VENDOR_RX_SIZE * RX_NUM] __attribute__ ((aligned(32)));
static volatile uint8_t rx_head;
static volatile uint8_t rx_tail;
static uint8_t rx_list[RX_NUM + 1];
static volatile uint32_t rx_available;
static void rx_queue_transfer(int i);
static void rx_event(transfer_t *t);
extern volatile uint8_t usb_configuration;


void usb_vendor_configure(void)
{
    printf("usb_vendor_configure\n");
    memset(tx_transfer, 0, sizeof(tx_transfer));
    memset(rx_transfer, 0, sizeof(rx_transfer));
    tx_head = 0;
    rx_head = 0;
    rx_tail = 0;
    usb_config_tx(VENDOR_TX_ENDPOINT, VENDOR_TX_SIZE, 0, NULL);
    usb_config_rx(VENDOR_RX_ENDPOINT, VENDOR_RX_SIZE, 0, rx_event);
    int i;
    for (i=0; i < RX_NUM; i++) rx_queue_transfer(i);
}

/*************************************************************************/
/**                               Receive                               **/
/*************************************************************************/

static void rx_queue_transfer(int i)
{
    void *buffer = rx_buffer + i * VENDOR_RX_SIZE;
    arm_dcache_delete(buffer, VENDOR_RX_SIZE);
    //memset(buffer, )
    NVIC_DISABLE_IRQ(IRQ_USB1);
    usb_prepare_transfer(rx_transfer + i, buffer, VENDOR_RX_SIZE, i);
    usb_receive(VENDOR_RX_ENDPOINT, rx_transfer + i);
    NVIC_ENABLE_IRQ(IRQ_USB1);
}

static void rx_event(transfer_t *t)
{
    int i = t->callback_param;
    //printf("rx event i=%d\n", i);
    // received a packet with data
    uint32_t head = rx_head;
    if (++head > RX_NUM) head = 0;
    rx_list[head] = i;
    rx_head = head;
}


int usb_vendor_recv(void *buffer, uint32_t timeout)
{
    uint32_t wait_begin_at = systick_millis_count;
    uint32_t tail = rx_tail;
    while (1) {
        if (!usb_configuration) return -1; // usb not enumerated by host
        if (tail != rx_head) break;
        if ((systick_millis_count - wait_begin_at > timeout) || !timeout) {
            return 0;
        }
        yield();
    }
//    digitalWriteFast(0, LOW);
    if (++tail > RX_NUM) tail = 0;
    uint32_t i = rx_list[tail];
    rx_tail = tail;

    memcpy(buffer,  rx_buffer + i * VENDOR_RX_SIZE, VENDOR_RX_SIZE);
    rx_queue_transfer(i);
    //memset(rx_transfer, 0, sizeof(rx_transfer));
    //usb_prepare_transfer(rx_transfer + 0, rx_buffer, VENDOR_RX_SIZE, 0);
    //usb_receive(VENDOR_RX_ENDPOINT, rx_transfer + 0);
    return VENDOR_RX_SIZE;
}

int usb_vendor_send(const void *buffer, uint32_t timeout)
{
    transfer_t *xfer = tx_transfer + tx_head;
    uint32_t wait_begin_at = systick_millis_count;

    while (1) {
        if (!usb_configuration) return -1; // usb not enumerated by host
        uint32_t status = usb_transfer_status(xfer);
        if (!(status & 0x80)) break; // transfer descriptor ready
        if (systick_millis_count - wait_begin_at > timeout) return 0;
        yield();
    }
    uint8_t *txdata = txbuffer + (tx_head * VENDOR_TX_SIZE);
    memcpy(txdata, buffer, VENDOR_TX_SIZE);
    arm_dcache_flush_delete(txdata, VENDOR_TX_SIZE );
    usb_prepare_transfer(xfer, txdata, VENDOR_TX_SIZE, 0);
    usb_transmit(VENDOR_TX_ENDPOINT, xfer);
    if (++tx_head >= TX_NUM) tx_head = 0;
    return VENDOR_TX_SIZE;
}

int usb_vendor_available(void)
{
    if (!usb_configuration) return 0;
    if (rx_head != rx_tail) return VENDOR_RX_SIZE;
    //if (!(usb_transfer_status(rx_transfer) & 0x80)) return VENDOR_RX_SIZE;
    return 0;
}

#endif // VENDOR_INTERFACE

Code:
//And finally, usb_vendor.h, also based entirely on usb_rawhid.h


#pragma once

#include "usb_desc.h"

#if defined(VENDOR_INTERFACE)

#include <inttypes.h>


// C language implementation
#ifdef __cplusplus
extern "C" {
#endif
void usb_vendor_configure(void);
int usb_vendor_recv(void *buffer, uint32_t timeout);
int usb_vendor_available(void);
int usb_vendor_send(const void *buffer, uint32_t timeout);
#ifdef __cplusplus
}
#endif


// C++ interface
#ifdef __cplusplus



class usb_vendor_class
{
public:
    int available(void) {return usb_vendor_available(); }
    int recv(void *buffer, uint16_t timeout) { return usb_vendor_recv(buffer, timeout); }
    int send(const void *buffer, uint16_t timeout) { return usb_vendor_send(buffer, timeout); }
};

extern usb_vendor_class Vendor;

#endif // __cplusplus

#endif // VENDOR_INTERFACE


The only trouble i am having is making a c++ usb_vendor_class object available for sketches as a Vendor instance, as usb_inst.cpp refuses to compile, so i commented it out :

Code:
#ifdef VENDOR_INTERFACE
//usb_vendor_class Vendor;
#endif

I get :
Code:
/home/rd/.platformio/packages/framework-arduinoteensy/cores/teensy4/usb_inst.cpp:59:1: error: 'usb_vendor_class' does not name a type; did you mean 'usb_serial_class'?
   59 | usb_vendor_class Vendor;
      | ^~~~~~~~~~~~~~~~
      | usb_serial_class
*** [.pio/build/teensy41/FrameworkArduino/usb_inst.cpp.o] Error 1

I probably forgot some code patching somewhere ?

And finally some little issue in enumeration :
When building with the vanilla USB_SERIAL compile flag this gives this enumeration on linux (see file usb_serial.png)
When building with the USB_VENDOR compile flag this gives this enumeration on linux (see file usb_vendor.png)

There is a mysterious interface that gets overwritten with the vendor interface i just added.
Sub=6a, Prot=c7.

What is that interface for ?

I will probably have to figure the interface ID and interface num defines and endpoint defines so as not overwrite it, unless someone knowledgeable as an idea.
ps : i am using 96 bytes tx maxkpacket sizes and 64 bytes rx maxpacketsize in the screenshots for my use case instead of the more general 512 byte size I provided in the code sample of this post.[/CODE]
 

Attachments

  • usb_serial.png
    usb_serial.png
    68.6 KB · Views: 18
  • usb_vendor.png
    usb_vendor.png
    62.8 KB · Views: 18
Last edited:
ps : i am using 96 bytes tx maxkpacket sizes and 64 bytes rx maxpacketsize in the screenshots for my use case instead of the more general 512 byte size I provided in the code sample of this post.

Not sure if you care about compliance to the USB spec, but if that's a concern you should probably be aware 512 byte max packet size is required for bulk endpoints when using 480 Mbit/sec speed.

usb20bulksize.png
 
Indeed, Paul.
I successfully used 512 bytes RX maxPacketSize on the Arduino Due, although my transfers are fixed size 96 bytes, and i would prefer 96 bytes packets only on the bus. That seems to be a "short packet" in USB parlance.
Looking at the usb_vendor_send transfer mechanism, it seems that it would send a 96 byte packet if VENDOR_TX_SIZE was set at 96, and i could define a VENDOR_TX_MAXPACKETSIZE to the standard 512 bytes in the endpoint definition.
It seems that the txbuffer is larger than VENDOR_TX_SIZE (product of VENDOR_TX_SIZE and TX_NUM), but usb_vendor_send would still send a short packet of VENDOR_TX_SIZE at each call ? i can't see any tx_tail so i assume that txbuffer just holds 'stale' data.
I am not familliar with USB Teensy 4.1 usb buffering internals however, and I hope the fact that 512 Bytes is not a multiple of 96 bytes will not create issues, although i could pad to 128 bytes if that were the case.
 
Thanks Paul,
Please disregard my latest post, i figured why the need for txdata and tx_head buffering, as the transfers are scheduled, and not guaranteed to happen synchronously.
I am now testing with 512 byte max packet size and a TX_SIZE of the same size or less. I figured out that ZLP packets are configurable in usb_config_tx too if needed.
Seems however that there is no data sent on the bus with my setup as wireshark doesn't catch any traffic from the device to host on the new interface and endpoints. (serial works ok)
I made a mistake on the NUM_ENDPOINTS in the original post that was set too low.
I tried a lone interface and two endpoints (no Serial / CDC ACM whatsoever to rule out any conflict but to no avail. I'll keep digging the usb_serial.c code as a work basis and post again if i am really stuck or if I find the issue in the meantime.
 
It works now, basically my original post had some errors.

The interface is now as follows, I had swapped VENDOR_TX_ENDPOINT and VENDOR_RX_ENDPOINT

Code:
#ifdef VENDOR_INTERFACE
        // configuration for 480 Mbit/sec speed
        // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
        9,                                      // bLength
        4,                                      // bDescriptorType
        VENDOR_INTERFACE,                       // bInterfaceNumber
        0,                                      // bAlternateSetting
        2,                                      // bNumEndpoints
        0xFF,                                   // bInterfaceClass (0xFF = vendor)
        0x00,                                   // bInterfaceSubClass
        0x00,                                   // bInterfaceProtocol
        0,                                      // iInterface
        // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
        7,                                      // bLength
        5,                                      // bDescriptorType
        VENDOR_RX_ENDPOINT,                     // bEndpointAddress
        0x02,  //USB_ENDPOINT_TYPE_BULK                                 // bmAttributes (0x02=bulk)
        LSB(VENDOR_TX_SIZE),MSB(VENDOR_TX_SIZE),// wMaxPacketSize
        0,                                      // bInterval
        // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
        7,                                      // bLength
        5,                                      // bDescriptorType
        VENDOR_TX_ENDPOINT | 0x80,              // bEndpointAddress
        0x02,  //USB_ENDPOINT_TYPE_BULK                                 // bmAttributes (0x02=bulk)
        LSB(VENDOR_RX_SIZE),MSB(VENDOR_RX_SIZE),// wMaxPacketSize
        0,                                      // bInterval
#endif // VENDOR INTERFACE

and the accurate defines are :

Code:
  #elif defined(USB_VENDOR)

  #define VENDOR_ID        0x16C0
  #define PRODUCT_ID        0x0486
  #define MANUFACTURER_NAME    {'V','E','N','D','O','R'}
  #define MANUFACTURER_NAME_LEN    6
  #define PRODUCT_NAME        {'P','R','O','D','U','C','T'}
  #define PRODUCT_NAME_LEN    7
  #define EP0_SIZE        64
 
 
  #define NUM_ENDPOINTS         6 // NUM_ENDPOINTS should be equal to the highest endpoint id
  #define NUM_INTERFACE        3
  #define VENDOR_INTERFACE      2    // Vendor Bulk
  #define VENDOR_TX_ENDPOINT    6
 
  #define VENDOR_TX_SIZE        512
  #define VENDOR_TX_INTERVAL    1     // TODO: is this ok for 480 Mbit speed
  #define VENDOR_RX_ENDPOINT    5
 
  #define VENDOR_RX_SIZE        512
  #define VENDOR_RX_INTERVAL    1     // TODO: is this ok for 480 Mbit speed
  #define VENDOR_DESCRIPTOR    1       // Vendor-specific device descriptor
  #define CDC_STATUS_INTERFACE    0
  #define CDC_DATA_INTERFACE    1
  #define CDC_ACM_ENDPOINT    2
  #define CDC_RX_ENDPOINT       3
  #define CDC_TX_ENDPOINT       4
  #define CDC_ACM_SIZE          16
  #define CDC_RX_SIZE_480       512
  #define CDC_TX_SIZE_480       512
  #define CDC_RX_SIZE_12        64
  #define CDC_TX_SIZE_12        64

  #define ENDPOINT2_CONFIG    ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT
  #define ENDPOINT3_CONFIG    ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_UNUSED
  #define ENDPOINT4_CONFIG      ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_BULK
  #define ENDPOINT5_CONFIG    ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_UNUSED
  #define ENDPOINT6_CONFIG      ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_BULK

in usb_vendor.c, I enabled ZLP, third parameter of usb_config_tx, in the case if the data message size is a multiple of maxpacketsize.

Code:
void usb_vendor_configure(void)
{
    printf("usb_vendor_configure\n");
    memset(tx_transfer, 0, sizeof(tx_transfer));
    memset(rx_transfer, 0, sizeof(rx_transfer));
    tx_head = 0;
    rx_head = 0;
    rx_tail = 0;
    usb_config_rx(VENDOR_RX_ENDPOINT, VENDOR_RX_SIZE, 0, rx_event);
    usb_config_tx(VENDOR_TX_ENDPOINT, VENDOR_TX_SIZE, 1, NULL);
    int i;
    for (i=0; i < RX_NUM_VENDOR; i++) rx_queue_transfer(i);
}

Also, added in usb_vendor.c, to reflect that i use short packets and to decouple from VENDOR_TX_SIZE and VENDOR_RX_SIZE (that represent the MaxPacketSize set at 512 bytes for USB High speed)
I added VENDOR_TX_PACKETSIZE and VENDOR_RX_PACKETSIZE

Note that I did not bother to check the device bus speed to adjust dynamically the max packet size between 64 and 512

Code:
#define VENDOR_TX_PACKETSIZE 96 // represents the fixed length short packet i use
#define VENDOR_RX_PACKETSIZE 64

/*************************************************************************/
/**                               Receive                               **/
/*************************************************************************/

static void rx_queue_transfer(int i)
{
    void *buffer = rx_buffer + i * VENDOR_RX_PACKETSIZE;
    arm_dcache_delete(buffer, VENDOR_RX_PACKETSIZE);
    //memset(buffer, )
    NVIC_DISABLE_IRQ(IRQ_USB1);
    usb_prepare_transfer(rx_transfer + i, buffer, VENDOR_RX_PACKETSIZE, i);
    usb_receive(VENDOR_RX_ENDPOINT, rx_transfer + i);
    NVIC_ENABLE_IRQ(IRQ_USB1);
}

static void rx_event(transfer_t *t)
{
    int i = t->callback_param;
    //printf("rx event i=%d\n", i);
    // received a packet with data
    uint32_t head = rx_head;
    if (++head > RX_NUM_VENDOR) head = 0;
    rx_list[head] = i;
    rx_head = head;
}


int usb_vendor_recv(void *buffer, uint32_t timeout)
{
    uint32_t wait_begin_at = systick_millis_count;
    uint32_t tail = rx_tail;
    while (1) {
        if (!usb_configuration) return -1; // usb not enumerated by host
        if (tail != rx_head) break;
        if ((systick_millis_count - wait_begin_at > timeout) || !timeout) {
            return 0;
        }
        yield();
    }
//    digitalWriteFast(0, LOW);
    if (++tail > RX_NUM_VENDOR) tail = 0;
    uint32_t i = rx_list[tail];
    rx_tail = tail;

    memcpy(buffer,  rx_buffer + i * VENDOR_RX_PACKETSIZE, VENDOR_RX_PACKETSIZE);
    rx_queue_transfer(i);
    //memset(rx_transfer, 0, sizeof(rx_transfer));
    //usb_prepare_transfer(rx_transfer + 0, rx_buffer, RAWHID_RX_SIZE, 0);
    //usb_receive(RAWHID_RX_ENDPOINT, rx_transfer + 0);
    return VENDOR_RX_PACKETSIZE;
}

int usb_vendor_send(const void *buffer, uint32_t timeout)
{
    //usb_start_sof_interrupts(VENDOR_INTERFACE);
    transfer_t *xfer = tx_transfer + tx_head;
    uint32_t wait_begin_at = systick_millis_count;
    uint32_t status;
    
    while (1) {
        if (!usb_configuration) return -1; // usb not enumerated by host
         status = usb_transfer_status(xfer);
        if (!(status & 0x80)) break; // transfer descriptor ready
        if (systick_millis_count - wait_begin_at > timeout) return 0;
        yield();
    }
    
    uint8_t *txdata = txbuffer + (tx_head * VENDOR_TX_PACKETSIZE);
    memcpy(txdata, buffer, VENDOR_TX_PACKETSIZE);
    usb_prepare_transfer(xfer, txdata, VENDOR_TX_PACKETSIZE, 0);
    arm_dcache_flush_delete(txdata, VENDOR_TX_PACKETSIZE );

    usb_transmit(VENDOR_TX_ENDPOINT, xfer);
    if (++tx_head >= TX_NUM_VENDOR) tx_head = 0;
    //usb_stop_sof_interrupts(VENDOR_INTERFACE);
    return VENDOR_TX_PACKETSIZE;

}

int usb_vendor_available(void)
{
    if (!usb_configuration) return 0;
    if (rx_head != rx_tail) return VENDOR_RX_PACKETSIZE;
    //if (!(usb_transfer_status(rx_transfer) & 0x80)) return VENDOR_RX_PACKETSIZE;
    return 0;
}

Now it also seems that wireshark does not show traffic until the packets are acknowledged, so a working libusb client needs to talk to the device.
Finally a libusb client that i am using for benchmarking :

Code:
// BULK USB VENDOR MODE
#include <libusb.h>

// THREADING
#include <pthread.h>
#include <shared_mutex>
#include <mutex>

//STDIO
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <string>
#include <iostream>
#include <iomanip>
#include <atomic>

#include <chrono>

#include <sys/mman.h>

#include <unistd.h>
#include <bits/stdc++.h>
#include <unordered_map>

using namespace std;

#ifndef DEBUGLEVEL
#define DEBUGLEVEL 0
#endif

//defines for bulk interface
#define USB_BUFFER_SIZE 96
#define URB_SIZE USB_BUFFER_SIZE
#define N_URBS 32
#define VID 0x16C0
#define PID 0x0486
#define USB_BULK_IFACE 0x2
#define EP_IN 0x86
#define EP_OUT 0x5
#define USE_BULK 1

#define NCURSES false

#define dbgstr(x) __func__ , std::to_string(__LINE__) , x

struct  libusb_transfer *usbxfer[N_URBS];
uint8_t  *usbbuf[N_URBS];
libusb_context *ctx;
libusb_device_handle *h;


std::atomic<int> usb_pending_transfers{N_URBS};
std::atomic<bool> USB_transfer_active{true};
std::atomic_bool readUSB_exit(false);

std::mutex mtx3;

FILE  *fid_perf;

typedef float SAMPLE;
typedef double SAMPLE2;

template <typename T>
inline void DebugPrint(std::string func, std::string line, std::string str, T const &value, uint8_t level)
{

     using namespace std::chrono_literals;
    #if (!NCURSES)   
    std::lock_guard<std::mutex> lock(mtx3); // prevent stream interleaving (no different thread may write a line at the same time)   
    if(level == 0)
    {
        std::cerr << func << "\t";
        std::cerr << line << "\t";
        std::cerr << std::hash<std::thread::id>{}(std::this_thread::get_id()) << "\t";
        std::cerr << str << "\t";
        if (std::is_same<T, SAMPLE2>::value)
        {
            std::cerr << fixed; 
            std::cerr << setprecision(6) << value << std::endl;
        }
        else
        {
            std::cerr << value << std::endl;
        }

    }
    else if (level <= DEBUGLEVEL)
    {

        std::cout << func << "\t";
        std::cout << line << "\t";
        std::cout << std::hash<std::thread::id>{}(std::this_thread::get_id()) << "\t";
        std::cout << str << "\t";
        //if (std::is_same<T, SAMPLE2>::value)
        //{
            std::cout << fixed; 
            std::cout << setprecision(6) << value << std::endl;
        //}
        //else
        //{
        //    std::cout << value << "\n";
        //}
    }
    #endif                   
}

inline void DebugPrint(std::string func, std::string line, std::string str, int level)
{

    using namespace std::chrono_literals;
    #if (!NCURSES)
    std::lock_guard<std::mutex> lock(mtx3); // prevent stream interleaving (no different thread may write a line at the same time)
    if(level == 0)
    {
        std::cerr << func << "\t";
        std::cerr << line << "\t";
        std::cerr << std::hash<std::thread::id>{}(std::this_thread::get_id()) << "\t";
        std::cerr << str << std::endl;
    }

    else if (level <= DEBUGLEVEL)
    {
        std::cout << func << "\t";
        std::cout << line << "\t";
        std::cout << std::hash<std::thread::id>{}(std::this_thread::get_id()) << "\t";
        std::cout << str << std::endl;
    }
    #endif                   
}

static void rx_usb_bulk(struct libusb_transfer *t)
{
 

    //DebugPrint(dbgstr("rx_usb_bulk"),0);

    switch (t->status) {
        case LIBUSB_TRANSFER_COMPLETED:
        if(t->actual_length > 0)
        {

           // DebugPrint(dbgstr("Incoming transfer bytes:"),t->actual_length,0);

            fwrite(t->buffer,sizeof(char),t->actual_length,fid_perf); // raw sample buffer (w/o timestamp)
            fflush(fid_perf);

            if(t->actual_length != USB_BUFFER_SIZE)
            {
                DebugPrint(dbgstr("Processing of USB buffer,t->actual_length != USB_BUFFER_SIZE"),t->actual_length,0);
            }   
            if (USB_transfer_active.load())
            {
                libusb_submit_transfer(t);
                return;
            }
    
        }
        break;
        
        //case LIBUSB_TRANSFER_CANCELLED:
        case LIBUSB_TRANSFER_ERROR:
        DebugPrint(dbgstr("transfer signalling LIBUSB_TRANSFER_ERROR"),0);
        break;
        case LIBUSB_TRANSFER_TIMED_OUT:
        DebugPrint(dbgstr("transfer signalling LIBUSB_TRANSFER_TIMED_OUT"),0);
        break;
        case LIBUSB_TRANSFER_STALL:
        DebugPrint(dbgstr("transfer signalling LIBUSB_TRANSFER_STALL"),0);
        break;
        case LIBUSB_TRANSFER_NO_DEVICE:
        DebugPrint(dbgstr("transfer signalling LIBUSB_TRANSFER_NO_DEVICE"),0);
        break;
        case LIBUSB_TRANSFER_OVERFLOW:
        DebugPrint(dbgstr("transfer signalling LIBUSB_TRANSFER_OVERFLOW"),0);
        break;       
    }

    usb_pending_transfers--;

    if (usb_pending_transfers == 0)
        {
            // All transfers have completed or been cancelled, we can exit the drain loop
            DebugPrint(dbgstr("All USB transfers completed or cancelled or in error"),0);
        }
        
}


void * ReadUSBThread(void * arg)
{
    mlockall(MCL_CURRENT | MCL_FUTURE);

    
    DebugPrint(dbgstr("enter ReadUSBThread"),0);
              
    for(int i = 0; i < N_URBS; i++)
    {
        libusb_submit_transfer(usbxfer[i]);
    }

    DebugPrint(dbgstr("submit transfers"),0);

    while(!(readUSB_exit.load()))
    {
        //usleep(100);
        libusb_handle_events(ctx);   // or libusb_handle_events_completed()

        //DebugPrint(dbgstr("PutUSBSamples ok"),0);
 
        sched_yield();
    }

    while (usb_pending_transfers > 0)
    {
        libusb_handle_events(ctx);   // or libusb_handle_events_completed()
        usleep(10);
        DebugPrint(dbgstr("Waiting for USB transfers to complete, pending transfers:"),usb_pending_transfers,0);
        sched_yield();
    }


    return 0;
}

inline uint16_t initUSBBulk()
{

    libusb_init(&ctx);
    libusb_set_option(ctx,LIBUSB_OPTION_LOG_LEVEL,LIBUSB_LOG_LEVEL_WARNING);

    h = libusb_open_device_with_vid_pid(ctx,VID,PID);
    if (!h)
    {
        DebugPrint(dbgstr("USB open device failed, is it plugged ?"),0);
        fflush(stdout);
        return 1;
    }
    DebugPrint(dbgstr("USB open device OK"),0);
        

    if(libusb_kernel_driver_active(h,USB_BULK_IFACE) == 1)
    {
        libusb_detach_kernel_driver(h,USB_BULK_IFACE);
        DebugPrint(dbgstr("kernel driver detach OK (should not happen)."),0);
    }

    if(libusb_claim_interface(h,USB_BULK_IFACE) < 0)
    {
        DebugPrint(dbgstr("USB claim interface failed."),0);
        fflush(stdout);
        return 1;
    }
    DebugPrint(dbgstr("USB claim interface OK."),0);


    for(uint8_t i = 0; i < N_URBS; i++)
    {
        usbbuf[i] = (uint8_t *) aligned_alloc(64,URB_SIZE);
        usbxfer[i] = libusb_alloc_transfer(0);
        libusb_fill_bulk_transfer(
            usbxfer[i],
            h,
            EP_IN,
            usbbuf[i],
            URB_SIZE,
            rx_usb_bulk,
            NULL,
            0
        );
    }

    return 0;

}

int main()
{


    if(initUSBBulk())
    {
        DebugPrint(dbgstr("USB bulk init failed."),0);
        return 1;
    }

    #if USE_BULK
    fid_perf = fopen("teensy41_testUSBperf.raw","wb");

    if(fid_perf == NULL)
    {
        perror( "Error opening file" );
        printf( "Error code opening file: %d\n", errno );
        printf( "Error opening file: %s\n", strerror( errno ) );
        exit(-1);
    }
    DebugPrint(dbgstr("fid_perf"),fid_perf,0);
    #endif

    pthread_t USBThread;
    int result = pthread_create(&USBThread, NULL, ReadUSBThread, NULL);
    if (result != 0) {
        DebugPrint(dbgstr("Error creating USB thread"),result,0);
        exit(-1);
    }

    // Simulate some work in the main thread
    for (int i = 0; i < 60; i++) {
        DebugPrint(dbgstr("Main thread working..."),i,0);
        sleep(1);
    }

    fclose(fid_perf);
    USB_transfer_active.store(false);
    DebugPrint(dbgstr("setting USB_transfer_active to false (won't submit new transfers)"),0);
    // Signal the USB thread to exit
    readUSB_exit.store(true);

    // Wait for the USB thread to finish
    pthread_join(USBThread, NULL);

    // Clean up USB resources
    for(int i = 0; i < N_URBS; i++)
    {
        libusb_cancel_transfer(usbxfer[i]);
        libusb_free_transfer(usbxfer[i]);
        free(usbbuf[i]);
    }
    libusb_release_interface(h, USB_BULK_IFACE);
    libusb_close(h);
    libusb_exit(ctx);

    return 0;
}
 
Great Job! Do you also have a simple test file of test.ino, which is used to test the vendor interface. And if yes, could you please to share it. Thank you.
 
yes.
This is a simple sketch that fetches 8 samples from precomputed sin tables (ADC 16 bit emulation), with arbitrary sample offset between channels, wrap ups 7 consecutive chunks of these 8 samples in a buffer and appends a 64 bit timestamp representing teensy 4.1 CPU cycles, with room for two more uint16_t variables labeled debug1 and debug2, set to 0, thus acting as padding.
Giving a fixed USB packet of 124 bytes, sent every 140 us.
that gives a net byte rate of around 885.7 KB/s (over 60 sec runtime)
The output can be examined using hexdump on teensy41_testusbperf.raw generated by the above libusb code.

Note that for the testing sketch i supplied, VENDOR_TX_PACKETSIZE should be set to 124 bytes in usb_vendor.c that i previously mentioned.

the libusb test client can be compiled using the following command : ./make_testusbperf.sh 0
The bash script argument '0' is the debuglevel for stdout/stderr output.

content of the make_testusbperf.sh script :
Bash:
#set -march to your architecture
#RYZEN optimized build:
g++ --verbose -std=c++2a -Wall testUSBperf.cpp -o testUSBperf -I. -I/usr/include/libusb-1.0 -g -O3 -march=znver1 -msse4.2 -fopenmp -lrt -lm -lusb-1.0 -pthread -DDEBUG -DDEBUGLEVEL=$1
date
 

Attachments

  • main.ino
    4.3 KB · Views: 18
  • testUSBperf.cpp
    8.3 KB · Views: 17
Last edited:
Back
Top