Many updates for USBTMC.

This commit is contained in:
Nathan Conrad 2019-09-17 13:32:54 -04:00
commit f9a2e8e405
6 changed files with 507 additions and 169 deletions

36
docs/concurrency.md Normal file
View File

@ -0,0 +1,36 @@
# Concurrency
The TinyUSB library is designed to operate on single-core MCUs with multi-threaded applications in mind. Interaction with interrupts is especially important to pay attention to.
It is compatible with optionally using a RTOS.
## General
When writing code, keep in mind that the OS (if using a RTOS) may swap out your code at any time. Also, your code can be preempted by an interrupt at any time.
## Application Code
The USB core does not execute application callbacks while in an interrupt context. Calls to application code are from within the USB core task context. Note that the application core will call class drivers from within their own task.
## Class Drivers
Class driver code should never be called from an interrupt context by the USB core, though the application is allowed to call class driver functions from interrupts. USB core functions may be called simultaneously by multiple tasks. Use care that proper locking is used to guard the USBD core functions from this case.
Class drivers are allowed to call `usbd_*` functions, but not `dcd_*` functions.
## USB Core
All functions that may be called from an (USB core) interrupt context have a `bool in_isr` parameter to remind the implementer that special care must be taken.
Interrupt handlers must not directly call class driver code, they must pass a message to the USB core's task.
`usbd_*` functions may be called from interrupts without any notice. They may also be called simultaneously by multiple tasks.
## Device Drivers
Much of the processing of the USB stack is done in an interrupt context, and care must be taken in order to ensure variables are handled in the appropriate ways by the compiler and optimizer.
In particular:
- Ensure that all memory-mapped registers (including packet memory) are marked as volatile. GCC's optimizer will even combine memory access (like two 16-bit to be a 32-bit) if you don't mark the pointers as volatile. On some architectures, this can use macros like `_I`, `_O`, or `_IO'.
- All defined global variables are marked as `static`.

View File

@ -106,7 +106,7 @@ uint8_t const * tud_hid_descriptor_report_cb(void)
#if defined(CFG_TUD_USBTMC)
# define USBTMC_DESC_MAIN(_itfnum,_bNumEndpoints) \
USBTMC_IF_DESCRIPTOR(_itfnum, _bNumEndpoints, /*_stridx = */ 7u, USBTMC_PROTOCOL_USB488), \
USBTMC_IF_DESCRIPTOR(_itfnum, _bNumEndpoints, /*_stridx = */ 4u, USBTMC_PROTOCOL_USB488), \
USBTMC_BULK_DESCRIPTORS(/* OUT = */0x03, /* IN = */ 0x83)
#if defined(CFG_TUD_USBTMC_ENABLE_INT_EP)
@ -209,10 +209,7 @@ char const* string_desc_arr [] =
"TinyUSB", // 1: Manufacturer
"TinyUSB Device", // 2: Product
"123456", // 3: Serials, should use chip ID
"TinyUSB CDC", // 4: CDC Interface
"TinyUSB MSC", // 5: MSC Interface
"TinyUSB HID", // 6: HID
"TinyUSB USBTMC", // 7: USBTMC
"TinyUSB USBTMC", // 4: USBTMC
};
static uint16_t _desc_str[32];
@ -227,7 +224,8 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index)
{
memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1;
}else
}
else
{
// Convert ASCII string into UTF-16

View File

@ -64,8 +64,8 @@ usbtmcd_app_capabilities =
}
#endif
};
static const char idn[] = "TinyUSB,ModelNumber,SerialNumber,FirmwareVer\n";
//static const char idn[] = "TinyUSB,ModelNumber,SerialNumber,FirmwareVer";
static const char idn[] = "TinyUSB,ModelNumber,SerialNumber,FirmwareVer and a bunch of other text to make it longer than a packet, perhaps?\n";
static volatile uint8_t status;
// 0=not query, 1=queried, 2=delay,set(MAV), 3=delay 4=ready?
@ -86,6 +86,7 @@ bool usbtmcd_app_msgBulkOut_start(uint8_t rhport, usbtmc_msg_request_dev_dep_out
{
(void)rhport;
(void)msgHeader;
uart_tx_str_sync("MSG_OUT_DATA: start\r\n");
return true;
}
bool usbtmcd_app_msg_trigger(uint8_t rhport, usbtmc_msg_generic_t* msg) {
@ -97,7 +98,14 @@ bool usbtmcd_app_msg_trigger(uint8_t rhport, usbtmc_msg_generic_t* msg) {
bool usbtmcd_app_msg_data(uint8_t rhport, void *data, size_t len, bool transfer_complete)
{
(void)rhport;
(void)transfer_complete;
// If transfer isn't finished, we just ignore it (for now)
uart_tx_str_sync("MSG_OUT_DATA: <<<");
uart_tx_sync(data,len);
uart_tx_str_sync(">>>\r\n");
if(transfer_complete)
uart_tx_str_sync("MSG_OUT_DATA: Complete\r\n");
if(transfer_complete && (len >=4) && !strncasecmp("*idn?",data,4)) {
queryState = 1;
}
@ -107,26 +115,30 @@ bool usbtmcd_app_msg_data(uint8_t rhport, void *data, size_t len, bool transfer_
bool usbtmcd_app_msgBulkIn_complete(uint8_t rhport)
{
(void)rhport;
status &= (uint8_t)~(0x10u); // clear MAV
return true;
}
static uint8_t noQueryMsg[] = "ERR: No query\n";
static unsigned int msgReqLen;
bool usbtmcd_app_msgBulkIn_request(uint8_t rhport, usbtmc_msg_request_dev_dep_in const * request)
{
(void)rhport;
rspMsg.header.MsgID = request->header.MsgID,
rspMsg.header.bTag = request->header.bTag,
rspMsg.header.bTagInverse = request->header.bTagInverse;
if(queryState != 0)
{
TU_ASSERT(bulkInStarted == 0);
bulkInStarted = 1;
}
else
{
rspMsg.TransferSize = sizeof(noQueryMsg)-1;
usbtmcd_transmit_dev_msg_data(rhport, &rspMsg, noQueryMsg);
}
msgReqLen = request->TransferSize;
uart_tx_str_sync("MSG_IN_DATA: Requested!\r\n");
TU_ASSERT(bulkInStarted == 0);
bulkInStarted = 1;
// > If a USBTMC interface receives a Bulk-IN request prior to receiving a USBTMC command message
// that expects a response, the device must NAK the request
// Always return true indicating not to stall the EP.
return true;
}
@ -135,19 +147,21 @@ void usbtmc_app_task_iter(void) {
uint8_t const rhport = 0;
switch(queryState) {
case 0:
break;
case 1:
queryDelayStart = board_millis();
queryState = 2;
break;
case 2:
if( (board_millis() - queryDelayStart) > 1000u) {
if( (board_millis() - queryDelayStart) > 5u) {
queryDelayStart = board_millis();
queryState=3;
status |= 0x10u; // MAV
}
break;
case 3:
if( (board_millis() - queryDelayStart) > 1000u) {
if( (board_millis() - queryDelayStart) > 10u) {
queryState = 4;
}
break;
@ -155,14 +169,46 @@ void usbtmc_app_task_iter(void) {
if(bulkInStarted) {
queryState = 0;
bulkInStarted = 0;
rspMsg.TransferSize = sizeof(idn)-1;
usbtmcd_transmit_dev_msg_data(rhport, &rspMsg, idn);
status &= ~(0x10u); // MAV
usbtmcd_transmit_dev_msg_data(rhport, idn, tu_min32(sizeof(idn)-1,msgReqLen),false);
// MAV is cleared in the transfer complete callback.
}
break;
default:
TU_ASSERT(false,);
return;
}
}
bool usbtmcd_app_initiate_clear(uint8_t rhport, uint8_t *tmcResult)
{
(void)rhport;
*tmcResult = USBTMC_STATUS_SUCCESS;
queryState = 0;
bulkInStarted = false;
status = 0;
return true;
}
bool usbtmcd_app_get_clear_status(uint8_t rhport, usbtmc_get_clear_status_rsp_t *rsp)
{
(void)rhport;
queryState = 0;
bulkInStarted = false;
status = 0;
rsp->USBTMC_status = USBTMC_STATUS_SUCCESS;
rsp->bmClear.BulkInFifoBytes = 0u;
return true;
}
void usmtmcd_app_bulkIn_clearFeature(uint8_t rhport)
{
(void)rhport;
}
void usmtmcd_app_bulkOut_clearFeature(uint8_t rhport)
{
(void)rhport;
}
// Return status byte, but put the transfer result status code in the rspResult argument.
uint8_t usbtmcd_app_get_stb(uint8_t rhport, uint8_t *tmcResult)
{

View File

@ -73,53 +73,67 @@ typedef struct TU_ATTR_PACKED
typedef struct TU_ATTR_PACKED {
usbtmc_msg_header_t header ; ///< Header
uint32_t TransferSize ; ///< Transfer size; LSB first
struct {
uint8_t EOM : 1 ; ///< EOM set on last byte
struct TU_ATTR_PACKED
{
unsigned int EOM : 1 ; ///< EOM set on last byte
} bmTransferAttributes;
uint8_t _reserved[3];
} usbtmc_msg_request_dev_dep_out;
TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_dev_dep_out) == 12u, "struct wrong length");
// Next 8 bytes are message-specific
typedef struct TU_ATTR_PACKED {
typedef struct TU_ATTR_PACKED
{
usbtmc_msg_header_t header ; ///< Header
uint32_t TransferSize ; ///< Transfer size; LSB first
struct {
uint8_t : 0;
uint8_t TermCharEnabled : 1 ; ///< "The Bulk-IN transfer must terminate on the specified TermChar."; CAPABILITIES must list TermChar
struct TU_ATTR_PACKED
{
unsigned int TermCharEnabled : 1 ; ///< "The Bulk-IN transfer must terminate on the specified TermChar."; CAPABILITIES must list TermChar
} bmTransferAttributes;
uint8_t TermChar;
uint8_t _reserved[2];
} usbtmc_msg_request_dev_dep_in;
TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_dev_dep_in) == 12u, "struct wrong length");
/* Bulk-in headers */
typedef struct TU_ATTR_PACKED
{
usbtmc_msg_header_t header;
uint32_t TransferSize;
struct {
struct TU_ATTR_PACKED
{
uint8_t EOM: 1; ///< Last byte of transfer is the end of the message
uint8_t UsingTermChar: 1; ///< Support TermChar && Request.TermCharEnabled && last char in transfer is TermChar
} bmTransferAttributes;
uint8_t _reserved[3];
} usbtmc_msg_dev_dep_msg_in_header_t;
TU_VERIFY_STATIC(sizeof(usbtmc_msg_dev_dep_msg_in_header_t) == 12u, "struct wrong length");
/* Unsupported vendor things.... Are these ever used?*/
typedef struct TU_ATTR_PACKED {
typedef struct TU_ATTR_PACKED
{
usbtmc_msg_header_t header ; ///< Header
uint32_t TransferSize ; ///< Transfer size; LSB first
uint8_t _reserved[4];
} usbtmc_msg_request_vendor_specific_out;
typedef struct TU_ATTR_PACKED {
TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_vendor_specific_out) == 12u, "struct wrong length");
typedef struct TU_ATTR_PACKED
{
usbtmc_msg_header_t header ; ///< Header
uint32_t TransferSize ; ///< Transfer size; LSB first
uint8_t _reserved[4];
} usbtmc_msg_request_vendor_specific_in;
TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_vendor_specific_in) == 12u, "struct wrong length");
// Control request type should use tusb_control_request_t
/*
@ -187,13 +201,15 @@ typedef struct TU_ATTR_PACKED {
uint8_t _reserved;
uint16_t bcdUSBTMC; ///< USBTMC_VERSION
struct {
uint8_t listenOnly :1;
uint8_t talkOnly :1;
uint8_t supportsIndicatorPulse :1;
struct TU_ATTR_PACKED
{
unsigned int listenOnly :1;
unsigned int talkOnly :1;
unsigned int supportsIndicatorPulse :1;
} bmIntfcCapabilities;
struct {
uint8_t canEndBulkInOnTermChar :1;
struct TU_ATTR_PACKED
{
unsigned int canEndBulkInOnTermChar :1;
} bmDevCapabilities;
uint8_t _reserved2[6];
uint8_t _reserved3[12];
@ -201,40 +217,60 @@ typedef struct TU_ATTR_PACKED {
TU_VERIFY_STATIC(sizeof(usbtmc_response_capabilities_t) == 0x18, "struct wrong length");
typedef struct TU_ATTR_PACKED
{
uint8_t USBTMC_status;
struct TU_ATTR_PACKED
{
unsigned int BulkInFifoBytes :1;
} bmClear;
} usbtmc_get_clear_status_rsp_t;
TU_VERIFY_STATIC(sizeof(usbtmc_get_clear_status_rsp_t) == 2u, "struct wrong length");
// Used for both abort bulk IN and bulk OUT
typedef struct TU_ATTR_PACKED
{
uint8_t USBTMC_status;
uint8_t bTag;
} usbtmc_initiate_abort_rsp_t;
TU_VERIFY_STATIC(sizeof(usbtmc_get_clear_status_rsp_t) == 2u, "struct wrong length");
typedef struct TU_ATTR_PACKED
{
uint8_t USBTMC_status; ///< usbtmc_status_enum
uint8_t _reserved;
uint16_t bcdUSBTMC; ///< USBTMC_VERSION
struct
struct TU_ATTR_PACKED
{
uint8_t listenOnly :1;
uint8_t talkOnly :1;
uint8_t supportsIndicatorPulse :1;
unsigned int listenOnly :1;
unsigned int talkOnly :1;
unsigned int supportsIndicatorPulse :1;
} bmIntfcCapabilities;
struct
struct TU_ATTR_PACKED
{
uint8_t canEndBulkInOnTermChar :1;
unsigned int canEndBulkInOnTermChar :1;
} bmDevCapabilities;
uint8_t _reserved2[6];
uint16_t bcdUSB488;
struct
struct TU_ATTR_PACKED
{
uint8_t is488_2 :1;
uint8_t supportsREN_GTL_LLO :1;
uint8_t supportsTrigger :1;
unsigned int is488_2 :1;
unsigned int supportsREN_GTL_LLO :1;
unsigned int supportsTrigger :1;
} bmIntfcCapabilities488;
struct
struct TU_ATTR_PACKED
{
uint8_t SCPI :1;
uint8_t SR1 :1;
uint8_t RL1 :1;
uint8_t DT1 :1;
unsigned int SCPI :1;
unsigned int SR1 :1;
unsigned int RL1 :1;
unsigned int DT1 :1;
} bmDevCapabilities488;
uint8_t _reserved3[8];
} usbtmc_response_capabilities_488_t;
@ -252,15 +288,14 @@ TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_rsp_488_t) == 3u, "struct wrong length")
typedef struct TU_ATTR_PACKET
{
union {
struct {
uint8_t bTag : 7;
uint8_t one : 1;
} bNotify1Struct;
uint8_t bNotify1;
};
struct TU_ATTR_PACKED
{
unsigned int bTag : 7;
unsigned int one : 1;
} bNotify1;
uint8_t StatusByte;
} usbtmc_read_stb_interrupt_488_t;
TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_interrupt_488_t) == 2u, "struct wrong length");
#endif

View File

@ -8,7 +8,7 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 N Conrad
* Copyright (c) 2019 Nathan Conrad
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -33,31 +33,53 @@
#include "tusb_option.h"
// We don't do any cross-task anything here (everything is in tud or interrupt context).
// You must ensure thread safety in your own app.
// Synchronization is needed in some spots.
// These functions should NOT be called from interrupts.
/* The library is designed that its functions can be called by any user task, with need for
* additional locking. In the case of "no OS", this task is never preempted other than by
* interrupts, and the USBTMC code isn't called by interrupts, so all is OK. In the case
* of an OS, this class driver uses the OSAL to perform locking. The code uses a single lock
* and does not call outside of this class with a lock held, so deadlocks won't happen.
*
* This module's application-facing functions are not reentrant. The application must
* only call them from a single thread (or implement its own locking).
*/
//Limitations (not planned to be implemented):
// "vendor-specific" commands are not handled
//Limitations:
// "vendor-specific" commands are not handled.
// Dealing with "termchar" must be handled by the application layer,
// though additional error checking is does in this module.
// talkOnly and listenOnly are NOT supported. They're no permitted
// in USB488, anyway.
/* Supported:
*
* Notification pulse
* Trigger
* Read status byte (both by interrupt endpoint and control message)
*
*/
// TODO:
// USBTMC 3.2.2 error conditions not strictly followed
// No local lock-out, REN, or GTL.
// Cannot handle clear.
// Not all "capabilities" supported
// Clear message available status byte at the correct time? (488 4.3.1.3)
// Split transfers
// Abort bulk in/out
// No CLEAR_FEATURE/HALT no EP (yet)
// No aborting transfers.
#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_USBTMC)
#include <string.h>
#include "usbtmc.h"
#include "usbtmc_device.h"
#include "device/dcd.h"
#include "device/usbd.h"
#include "uart_util.h"
static char logMsg[150];
// FIXME: I shouldn't need to include _pvt headers.
#include "device/usbd_pvt.h"
@ -70,21 +92,31 @@ typedef enum
STATE_IDLE,
STATE_RCV,
STATE_TX_REQUESTED,
STATE_TX_INITIATED
STATE_TX_INITIATED,
STATE_CLEARING,
STATE_ABORTING_BULK_IN,
STATE_ABORTING_BULK_OUT,
STATE_NUM_STATES
} usbtmcd_state_enum;
typedef struct
{
usbtmcd_state_enum state;
volatile usbtmcd_state_enum state;
uint8_t itf_id;
uint8_t ep_bulk_in;
uint8_t ep_bulk_out;
uint8_t ep_int_in;
uint8_t ep_bulk_in_buf[64];
uint8_t ep_bulk_out_buf[64];
uint8_t lastTag;
// IN buffer is only used for first packet, not the remainder
// in order to deal with prepending header
uint8_t ep_bulk_in_buf[USBTMCD_MAX_PACKET_SIZE];
// OUT buffer receives one packet at a time
uint8_t ep_bulk_out_buf[USBTMCD_MAX_PACKET_SIZE];
uint32_t transfer_size_remaining; // also used for requested length for bulk IN.
uint8_t lastBulkOutTag; // used for aborts (mostly)
uint8_t lastBulkInTag; // used for aborts (mostly)
uint32_t transfer_size_remaining;
uint8_t const * devInBuffer;
} usbtmc_interface_state_t;
@ -97,38 +129,63 @@ static usbtmc_interface_state_t usbtmc_state =
.ep_int_in = 0
};
// We want everything to fit nicely in a single packet, so lets require EP size >32
// I'm not sure if this is really necessary, though.
// We need all headers to fit in a single packet in this implementation.
TU_VERIFY_STATIC(USBTMCD_MAX_PACKET_SIZE >= 32u,"USBTMC dev EP packet size too small");
TU_VERIFY_STATIC(
(sizeof(usbtmc_state.ep_bulk_in_buf) % USBTMCD_MAX_PACKET_SIZE) == 0,
"packet buffer must be a multiple of the packet size");
static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len);
static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen);
osal_mutex_def_t usbtmcLockBuffer;
static osal_mutex_t usbtmcLock;
// Our own private lock, mostly for the state variable.
#define criticalEnter() do {osal_mutex_lock(usbtmcLock,OSAL_TIMEOUT_WAIT_FOREVER); } while (0)
#define criticalLeave() do {osal_mutex_unlock(usbtmcLock); } while (0)
// called from app
// We keep a reference to the buffer, so it MUST not change until the app is
// notified that the transfer is complete.
// length of data is specified in the hdr.
// We can't just send the whole thing at once because we need to concatanate the
// header with the data.
bool usbtmcd_transmit_dev_msg_data(
uint8_t rhport,
usbtmc_msg_dev_dep_msg_in_header_t const * hdr,
const void *data)
const void * data, size_t len,
bool usingTermChar)
{
TU_ASSERT(usbtmc_state.state == STATE_TX_REQUESTED);
TU_ASSERT(hdr->TransferSize > 0u);
const unsigned int txBufLen = sizeof(usbtmc_state.ep_bulk_in_buf);
if(hdr->bmTransferAttributes.UsingTermChar)
#ifndef NDEBUG
TU_ASSERT(len > 0u);
TU_ASSERT(len <= usbtmc_state.transfer_size_remaining);
if(usingTermChar)
{
TU_ASSERT(usbtmcd_app_capabilities.bmDevCapabilities.canEndBulkInOnTermChar);
TU_ASSERT(termCharRequested);
TU_ASSERT(((uint8_t*)data)[hdr->TransferSize-1] == termChar);
TU_ASSERT(((uint8_t*)data)[len-1] == termChar);
}
#endif
TU_VERIFY(usbtmc_state.state == STATE_TX_REQUESTED);
usbtmc_msg_dev_dep_msg_in_header_t *hdr = (usbtmc_msg_dev_dep_msg_in_header_t*)usbtmc_state.ep_bulk_in_buf;
memset(hdr, 0x00, sizeof(*hdr));
hdr->header.MsgID = USBTMC_MSGID_DEV_DEP_MSG_IN;
hdr->header.bTag = usbtmc_state.lastBulkInTag;
hdr->header.bTagInverse = (uint8_t)~(usbtmc_state.lastBulkInTag);
hdr->TransferSize = len;
hdr->bmTransferAttributes.EOM = 1u;
hdr->bmTransferAttributes.UsingTermChar = usingTermChar;
// Copy in the header
memcpy(usbtmc_state.ep_bulk_in_buf, hdr, sizeof(*hdr));
uint packetLen = sizeof(*hdr);
// Single-packet transfer
if((packetLen + hdr->TransferSize) <= USBTMCD_MAX_PACKET_SIZE)
size_t packetLen = sizeof(*hdr);
// If it fits in a single trasnmission:
if((packetLen + hdr->TransferSize) <= txBufLen)
{
memcpy((uint8_t*)(usbtmc_state.ep_bulk_in_buf) + packetLen, data, hdr->TransferSize);
packetLen = (uint16_t)(packetLen+ hdr->TransferSize);
@ -141,27 +198,41 @@ bool usbtmcd_transmit_dev_msg_data(
usbtmc_state.transfer_size_remaining = 0;
usbtmc_state.devInBuffer = NULL;
}
else
else /* partial packet */
{
memcpy((uint8_t*)(usbtmc_state.ep_bulk_in_buf) + packetLen, data, USBTMCD_MAX_PACKET_SIZE - packetLen);
usbtmc_state.transfer_size_remaining = hdr->TransferSize - (USBTMCD_MAX_PACKET_SIZE - packetLen);
usbtmc_state.devInBuffer += (USBTMCD_MAX_PACKET_SIZE - packetLen);
packetLen = USBTMCD_MAX_PACKET_SIZE;
memcpy((uint8_t*)(usbtmc_state.ep_bulk_in_buf) + packetLen, data, txBufLen - packetLen);
usbtmc_state.devInBuffer += txBufLen - packetLen;
usbtmc_state.transfer_size_remaining = hdr->TransferSize - (txBufLen - packetLen);
packetLen = txBufLen;
}
usbtmc_state.state = STATE_TX_INITIATED;
TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,(uint16_t)packetLen));
criticalEnter();
{
TU_VERIFY(usbtmc_state.state == STATE_TX_REQUESTED);
usbtmc_state.state = STATE_TX_INITIATED;
}
criticalLeave();
TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf, (uint16_t)packetLen));
return true;
}
void usbtmcd_init(void)
{
#if USBTMC_CFG_ENABLE_488
#ifndef NDEBUG
# if USBTMC_CFG_ENABLE_488
if(usbtmcd_app_capabilities.bmIntfcCapabilities488.supportsTrigger)
TU_ASSERT(&usbtmcd_app_msg_trigger != NULL,);
// Per USB488 spec: table 8
TU_ASSERT(!usbtmcd_app_capabilities.bmIntfcCapabilities.listenOnly,);
TU_ASSERT(!usbtmcd_app_capabilities.bmIntfcCapabilities.talkOnly,);
# endif
if(usbtmcd_app_capabilities.bmIntfcCapabilities.supportsIndicatorPulse)
TU_ASSERT(&usbtmcd_app_indicator_pluse != NULL,);
#endif
if(usbtmcd_app_capabilities.bmIntfcCapabilities.supportsIndicatorPulse)
TU_ASSERT(&usbtmcd_app_indicator_pluse != NULL,);
usbtmcLock = osal_mutex_create(&usbtmcLockBuffer);
}
bool usbtmcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t *p_length)
@ -170,6 +241,9 @@ bool usbtmcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16
uint8_t const * p_desc;
uint8_t found_endpoints = 0;
usbtmcd_reset(rhport);
// Perhaps there are other application specific class drivers, so don't assert here.
if( itf_desc->bInterfaceClass != USBTMC_APP_CLASS)
return false;
@ -201,8 +275,10 @@ bool usbtmcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16
break;
case TUSB_XFER_INTERRUPT:
#ifndef NDEBUG
TU_ASSERT(tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN);
TU_ASSERT(usbtmc_state.ep_int_in == 0);
#endif
usbtmc_state.ep_int_in = ep_desc->bEndpointAddress;
break;
default:
@ -216,15 +292,24 @@ bool usbtmcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16
}
// bulk endpoints are required, but interrupt IN is optional
#ifndef NDEBUG
TU_ASSERT(usbtmc_state.ep_bulk_in != 0);
TU_ASSERT(usbtmc_state.ep_bulk_out != 0);
if (itf_desc->bNumEndpoints == 2) {
if (itf_desc->bNumEndpoints == 2)
{
TU_ASSERT(usbtmc_state.ep_int_in == 0);
}
else if (itf_desc->bNumEndpoints == 2)
else if (itf_desc->bNumEndpoints == 3)
{
TU_ASSERT(usbtmc_state.ep_int_in != 0);
}
if(usbtmcd_app_capabilities.bmIntfcCapabilities488.is488_2 ||
usbtmcd_app_capabilities.bmDevCapabilities488.SR1)
{
TU_ASSERT(usbtmc_state.ep_int_in != 0);
}
#endif
TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, 64));
return true;
@ -232,6 +317,12 @@ bool usbtmcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16
void usbtmcd_reset(uint8_t rhport)
{
// FIXME: Do endpoints need to be closed here?
usbtmc_state.state = STATE_IDLE;
usbtmc_state.itf_id = 0xFF;
usbtmc_state.ep_bulk_in = 0;
usbtmc_state.ep_bulk_out = 0;
usbtmc_state.ep_int_in = 0;
(void)rhport;
}
@ -271,12 +362,22 @@ static bool handle_devMsgIn(uint8_t rhport, void *data, size_t len)
{
TU_VERIFY(len == sizeof(usbtmc_msg_request_dev_dep_in));
usbtmc_msg_request_dev_dep_in *msg = (usbtmc_msg_request_dev_dep_in*)data;
TU_VERIFY(usbtmc_state.state == STATE_IDLE);
usbtmc_state.state = STATE_TX_REQUESTED;
usbtmc_state.transfer_size_remaining = msg->TransferSize;
sprintf(logMsg," handle_devMsgIn len=%ul\r\n",len);
uart_tx_str_sync(logMsg);
criticalEnter();
{
TU_VERIFY(usbtmc_state.state == STATE_IDLE);
usbtmc_state.state = STATE_TX_REQUESTED;
usbtmc_state.lastBulkInTag = msg->header.bTag;
usbtmc_state.transfer_size_remaining = msg->TransferSize;
}
criticalLeave();
termCharRequested = msg->bmTransferAttributes.TermCharEnabled;
termChar = msg->TermChar;
if(termCharRequested)
TU_VERIFY(usbtmcd_app_capabilities.bmDevCapabilities.canEndBulkInOnTermChar);
@ -287,8 +388,17 @@ static bool handle_devMsgIn(uint8_t rhport, void *data, size_t len)
bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
{
TU_VERIFY(result == XFER_RESULT_SUCCESS);
uart_tx_str_sync("USBTMC Xfer CB" );
sprintf(logMsg," STATE=%lu ", (uint32_t)usbtmc_state.state);
uart_tx_str_sync(logMsg);
if(usbtmc_state.state == STATE_CLEARING) {
return true; /* I think we can ignore everything here */
}
if(ep_addr == usbtmc_state.ep_bulk_out)
{
uart_tx_str_sync("OUT");
switch(usbtmc_state.state)
{
case STATE_IDLE:
@ -297,15 +407,19 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
uint8_t invInvTag = (uint8_t)~(msg->header.bTagInverse);
TU_VERIFY(msg->header.bTag == invInvTag);
TU_VERIFY(msg->header.bTag != 0x00);
usbtmc_state.lastTag = msg->header.bTag;
sprintf(logMsg," type=%lu\r\n",(uint32_t)msg->header.MsgID);
uart_tx_str_sync(logMsg);
switch(msg->header.MsgID) {
case USBTMC_MSGID_DEV_DEP_MSG_OUT:
TU_VERIFY(handle_devMsgOutStart(rhport, msg, xferred_bytes));
TU_VERIFY(usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, USBTMCD_MAX_PACKET_SIZE));
usbtmc_state.lastBulkOutTag = msg->header.bTag;
break;
case USBTMC_MSGID_DEV_DEP_MSG_IN:
uart_tx_sync("Handling msg in req\r\n", 21);
TU_VERIFY(handle_devMsgIn(rhport, msg, xferred_bytes));
break;
@ -321,6 +435,7 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
case USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT:
case USBTMC_MSGID_VENDOR_SPECIFIC_IN:
default:
TU_VERIFY(false);
return false;
}
@ -332,12 +447,22 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
case STATE_TX_REQUESTED:
case STATE_TX_INITIATED:
case STATE_ABORTING_BULK_IN:
case STATE_ABORTING_BULK_OUT:
default:
if(msg == NULL)
sprintf(logMsg," Unknown received control?\r\n ");
else {
sprintf(logMsg," msg=%lu\r\n ", (uint32_t)msg->header.MsgID);
}
uart_tx_str_sync(logMsg);
TU_VERIFY(false);
}
}
else if(ep_addr == usbtmc_state.ep_bulk_in)
{
sprintf(logMsg,"IN\r\n");
uart_tx_str_sync(logMsg);
TU_ASSERT(usbtmc_state.state == STATE_TX_INITIATED);
if(usbtmc_state.transfer_size_remaining == 0)
{
@ -345,16 +470,16 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
TU_VERIFY(usbtmcd_app_msgBulkIn_complete(rhport));
TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, USBTMCD_MAX_PACKET_SIZE));
}
else if(usbtmc_state.transfer_size_remaining >= USBTMCD_MAX_PACKET_SIZE)
else if(usbtmc_state.transfer_size_remaining > sizeof(usbtmc_state.devInBuffer))
{
memcpy(usbtmc_state.ep_bulk_in_buf, usbtmc_state.devInBuffer, USBTMCD_MAX_PACKET_SIZE);
usbtmc_state.devInBuffer += USBTMCD_MAX_PACKET_SIZE;
usbtmc_state.transfer_size_remaining -= USBTMCD_MAX_PACKET_SIZE;
TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,USBTMCD_MAX_PACKET_SIZE));
memcpy(usbtmc_state.ep_bulk_in_buf, usbtmc_state.devInBuffer, sizeof(usbtmc_state.ep_bulk_in_buf));
usbtmc_state.devInBuffer += sizeof(usbtmc_state.devInBuffer);
usbtmc_state.transfer_size_remaining -= sizeof(usbtmc_state.devInBuffer);
TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,sizeof(usbtmc_state.devInBuffer)));
}
else // short packet
else // last packet
{
uint packetLen = usbtmc_state.transfer_size_remaining;
size_t packetLen = usbtmc_state.transfer_size_remaining;
memcpy(usbtmc_state.ep_bulk_in_buf, usbtmc_state.devInBuffer, usbtmc_state.transfer_size_remaining);
while((packetLen % 4) != 0)
{
@ -376,94 +501,188 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint
bool usbtmcd_control_request(uint8_t rhport, tusb_control_request_t const * request) {
uint8_t tmcStatusCode = USBTMC_STATUS_FAILED;
#if (USBTMC_CFG_ENABLE_488)
ushort bTag;
uint8_t bTag;
#endif
if((request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) &&
(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_ENDPOINT) &&
(request->bRequest == TUSB_REQ_CLEAR_FEATURE) &&
(request->wValue == TUSB_REQ_FEATURE_EDPT_HALT))
{
uart_tx_str_sync("feature clear\r\n");
if((request->wIndex) == usbtmc_state.ep_bulk_out)
{
usmtmcd_app_bulkOut_clearFeature(rhport);
}
else if ((request->wIndex) == usbtmc_state.ep_bulk_in)
{
usmtmcd_app_bulkIn_clearFeature(rhport);
}
return false; // We want USBD core to handle sending the status response, and clear the stall condition
}
// We only handle class requests, IN direction.
// (for now)
if(request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS)
{
return false;
}
// Verification that we own the interface is unneeded since it's been routed to us specifically.
switch(request->bRequest)
{
// USBTMC required requests
case USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT:
case USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS:
case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN:
case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS:
{
TU_VERIFY(request->bmRequestType == 0xA2); // in,class,EP
TU_VERIFY(false);
break;
TU_VERIFY(request->wLength == 1u);
tmcStatusCode = USBTMC_STATUS_FAILED;
usbd_edpt_xfer(rhport, 0u, (void*)&tmcStatusCode,sizeof(tmcStatusCode));
return true;
}
case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS:
{
TU_VERIFY(request->bmRequestType == 0xA2); // in,class,EP
TU_VERIFY(request->wLength == 1u);
usbtmc_get_clear_status_rsp_t clearStatusRsp = {0};
tmcStatusCode = USBTMC_STATUS_FAILED;
usbd_edpt_xfer(rhport, 0u, (void*)&tmcStatusCode,sizeof(tmcStatusCode));
return true;
}
case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN:
{
usbtmc_initiate_abort_rsp_t rsp = {0};
uart_tx_str_sync("init abort bulk in\r\n");
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(request->wLength == sizeof(tmcStatusCode));
TU_VERIFY(request->wIndex == usbtmc_state.ep_int_in);
// wValue is the requested bTag to abort
usbtmc_state.transfer_size_remaining = 0;
usbtmc_state.state = STATE_ABORTING_BULK_IN;
TU_VERIFY(usbtmcd_app_initiate_clear(rhport, &tmcStatusCode));
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode,sizeof(tmcStatusCode)));
return true;
}
case USBTMC_bREQUEST_INITIATE_CLEAR:
{
uart_tx_str_sync("init clear\r\n");
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(request->wLength == sizeof(tmcStatusCode));
// After receiving an INITIATE_CLEAR request, the device must Halt the Bulk-OUT endpoint, queue the
// control endpoint response shown in Table 31, and clear all input buffers and output buffers.
usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out);
usbtmc_state.transfer_size_remaining = 0;
usbtmc_state.state = STATE_CLEARING;
TU_VERIFY(usbtmcd_app_initiate_clear(rhport, &tmcStatusCode));
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode,sizeof(tmcStatusCode)));
return true;
}
case USBTMC_bREQUEST_CHECK_CLEAR_STATUS:
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(false);
break;
{
uart_tx_str_sync("check clear\r\n");
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
usbtmc_get_clear_status_rsp_t clearStatusRsp = {0};
TU_VERIFY(request->wLength == sizeof(clearStatusRsp));
if(usbd_edpt_busy(rhport, usbtmc_state.ep_bulk_in))
{
// Stuff stuck in TX buffer?
clearStatusRsp.bmClear.BulkInFifoBytes = 1;
clearStatusRsp.USBTMC_status = USBTMC_STATUS_PENDING;
}
else
{
// Let app check if it's clear
TU_VERIFY(usbtmcd_app_get_clear_status(rhport, &clearStatusRsp));
}
if(clearStatusRsp.USBTMC_status == USBTMC_STATUS_SUCCESS)
usbtmc_state.state = STATE_IDLE;
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&clearStatusRsp,sizeof(clearStatusRsp)));
return true;
}
case USBTMC_bREQUEST_GET_CAPABILITIES:
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(request->wValue == 0x0000);
TU_VERIFY(request->wIndex == usbtmc_state.itf_id);
TU_VERIFY(request->wLength == sizeof(usbtmcd_app_capabilities));
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&usbtmcd_app_capabilities, sizeof(usbtmcd_app_capabilities)));
return true;
{
uart_tx_str_sync("get capabilities\r\n");
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(request->wLength == sizeof(usbtmcd_app_capabilities));
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&usbtmcd_app_capabilities, sizeof(usbtmcd_app_capabilities)));
return true;
}
// USBTMC Optional Requests
case USBTMC_bREQUEST_INDICATOR_PULSE: // Optional
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(usbtmcd_app_capabilities.bmIntfcCapabilities.supportsIndicatorPulse);
uint8_t tmcResult;
TU_VERIFY(usbtmcd_app_indicator_pluse(rhport, request, &tmcResult));
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcResult, sizeof(tmcResult)));
return true;
{
uart_tx_str_sync("indicate\r\n");
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(request->wLength == sizeof(tmcStatusCode));
TU_VERIFY(usbtmcd_app_capabilities.bmIntfcCapabilities.supportsIndicatorPulse);
TU_VERIFY(usbtmcd_app_indicator_pluse(rhport, request, &tmcStatusCode));
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode, sizeof(tmcStatusCode)));
return true;
}
#if (USBTMC_CFG_ENABLE_488)
// USB488 required requests
case USBTMC488_bREQUEST_READ_STATUS_BYTE:
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
bTag = request->wValue & 0x7F;
TU_VERIFY(request->bmRequestType == 0xA1);
TU_VERIFY((request->wValue & (~0x7F)) == 0u); // Other bits are required to be zero
TU_VERIFY(bTag >= 0x02 && bTag <= 127);
TU_VERIFY(request->wIndex == usbtmc_state.itf_id);
TU_VERIFY(request->wLength == 0x0003);
usbtmc_read_stb_rsp_488_t rsp;
rsp.bTag = (uint8_t)bTag;
if(usbtmc_state.ep_int_in != 0)
{
rsp.USBTMC_status = USBTMC_STATUS_SUCCESS;
rsp.statusByte = 0x00; // Use interrupt endpoint, instead.
uart_tx_str_sync("read stb\r\n");
usbtmc_read_stb_rsp_488_t rsp;
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(request->wLength == sizeof(rsp)); // in,class,interface
usbtmc_read_stb_interrupt_488_t intMsg =
bTag = request->wValue & 0x7F;
TU_VERIFY(request->bmRequestType == 0xA1);
TU_VERIFY((request->wValue & (~0x7F)) == 0u); // Other bits are required to be zero
TU_VERIFY(bTag >= 0x02 && bTag <= 127);
TU_VERIFY(request->wIndex == usbtmc_state.itf_id);
TU_VERIFY(request->wLength == 0x0003);
rsp.bTag = (uint8_t)bTag;
if(usbtmc_state.ep_int_in != 0)
{
.bNotify1 = (uint8_t)(0x80 | bTag),
rsp.USBTMC_status = USBTMC_STATUS_SUCCESS;
rsp.statusByte = 0x00; // Use interrupt endpoint, instead.
usbtmc_read_stb_interrupt_488_t intMsg =
{
.bNotify1 = {
.one = 1,
.bTag = bTag & 0x7Fu,
},
.StatusByte = usbtmcd_app_get_stb(rhport, &(rsp.USBTMC_status))
};
usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void*)&intMsg,sizeof(intMsg));
};
usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void*)&intMsg,sizeof(intMsg));
}
else
{
rsp.statusByte = usbtmcd_app_get_stb(rhport, &(rsp.USBTMC_status));
}
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp, sizeof(rsp)));
return true;
}
else
{
rsp.statusByte = usbtmcd_app_get_stb(rhport, &(rsp.USBTMC_status));
}
TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp, sizeof(rsp)));
return true;
// USB488 optional requests
case USBTMC488_bREQUEST_REN_CONTROL:
case USBTMC488_bREQUEST_GO_TO_LOCAL:
case USBTMC488_bREQUEST_LOCAL_LOCKOUT:
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(false);
return false;
{
uart_tx_str_sync("Unsupported REN/GTL/LLO\r\n");
TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface
TU_VERIFY(false);
return false;
}
#endif
default:
uart_tx_str_sync("Default CTRL handler\r\n");
TU_VERIFY(false);
return false;
}
TU_VERIFY(false);
}

View File

@ -56,13 +56,17 @@ extern usbtmc_response_capabilities_t const usbtmcd_app_capabilities;
#endif
bool usbtmcd_app_msgBulkOut_start(uint8_t rhport, usbtmc_msg_request_dev_dep_out const * msgHeader);
// transfer_complete does not imply that a message is complete.
bool usbtmcd_app_msg_data(uint8_t rhport, void *data, size_t len, bool transfer_complete);
void usmtmcd_app_bulkOut_clearFeature(uint8_t rhport); // Notice to clear and abort the pending BULK out transfer
bool usbtmcd_app_msgBulkIn_request(uint8_t rhport, usbtmc_msg_request_dev_dep_in const * request);
bool usbtmcd_app_msgBulkIn_complete(uint8_t rhport);
void usmtmcd_app_bulkIn_clearFeature(uint8_t rhport); // Notice to clear and abort the pending BULK out transfer
bool usbtmcd_app_initiate_clear(uint8_t rhport, uint8_t *tmcResult);
bool usbtmcd_app_get_clear_status(uint8_t rhport, usbtmc_get_clear_status_rsp_t *rsp);
// Indicator pulse should be 0.5 to 1.0 seconds long
TU_ATTR_WEAK bool usbtmcd_app_indicator_pluse(uint8_t rhport, tusb_control_request_t const * msg, uint8_t *tmcResult);
@ -82,8 +86,8 @@ TU_ATTR_WEAK bool usbtmcd_app_msg_trigger(uint8_t rhport, usbtmc_msg_generic_t*
bool usbtmcd_transmit_dev_msg_data(
uint8_t rhport,
usbtmc_msg_dev_dep_msg_in_header_t const * hdr,
const void *data);
const void * data, size_t len,
bool usingTermChar);
/* "callbacks" from USB device core */
@ -100,10 +104,10 @@ void usbtmcd_init(void);
*************************************************************/
#define USBTMC_APP_CLASS TUSB_CLASS_APPLICATION_SPECIFIC
#define USBTMC_APP_SUBCLASS 0x03
#define USBTMC_APP_SUBCLASS 0x03u
#define USBTMC_PROTOCOL_STD 0x00
#define USBTMC_PROTOCOL_USB488 0x01
#define USBTMC_PROTOCOL_STD 0x00u
#define USBTMC_PROTOCOL_USB488 0x01u
// Interface number, number of endpoints, EP string index, USB_TMC_PROTOCOL*, bulk-out endpoint ID,
// bulk-in endpoint ID