esp32_BNO08x/SH2/shtp.c

820 lines
23 KiB
C
Raw Normal View History

2024-11-19 21:24:24 +00:00
/*
* Copyright 2015-18 Hillcrest Laboratories, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License and
* any applicable agreements you may have with Hillcrest Laboratories, Inc.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Hillcrest Sensor Hub Transport Protocol (SHTP) API
*/
#include "shtp.h"
#include "sh2_err.h"
#include "sh2_util.h"
#include <string.h>
// ------------------------------------------------------------------------
// Private types
#define SH2_MAX_APPS (5)
#define SHTP_APP_NAME_LEN (32)
#define SH2_MAX_CHANS (8)
#define SHTP_CHAN_NAME_LEN (32)
// Defined Globally Unique Identifiers
#define GUID_SHTP (0)
// Command Channel commands and parameters
#define SHTP_CHAN_COMMAND (0)
#define CMD_ADVERTISE (0)
#define CMD_ADVERTISE_SHTP (0)
#define CMD_ADVERTISE_ALL (1)
#define RESP_ADVERTISE (0)
#define SHTP_HDR_LEN (4)
#define TAG_SHTP_VERSION 0x80
typedef struct shtp_App_s {
uint32_t guid;
char appName[SHTP_APP_NAME_LEN];
} shtp_App_t;
typedef struct shtp_AppListener_s {
uint16_t guid;
shtp_AdvertCallback_t *callback;
void *cookie;
} shtp_AppListener_t;
typedef struct shtp_ChanListener_s {
uint16_t guid;
char chanName[SHTP_CHAN_NAME_LEN];
shtp_Callback_t *callback;
void *cookie;
} shtp_ChanListener_t;
typedef struct shtp_Channel_s {
uint8_t nextOutSeq;
uint8_t nextInSeq;
uint32_t guid; // app id
char chanName[SHTP_CHAN_NAME_LEN];
bool wake;
shtp_Callback_t *callback;
void *cookie;
} shtp_Channel_t;
typedef enum {
ADVERT_NEEDED,
ADVERT_REQUESTED,
ADVERT_IDLE,
} advert_phase_t;
// Per-instance data for SHTP
typedef struct shtp_s {
// Associated SHTP HAL
// If 0, this indicates the SHTP instance is available for new opens
sh2_Hal_t *pHal;
// Asynchronous Event callback and it's cookie
shtp_EventCallback_t *eventCallback;
void * eventCookie;
// Data from adverts
char shtpVersion[8];
uint16_t outMaxPayload;
uint16_t outMaxTransfer;
// Transmit support
uint8_t outTransfer[SH2_HAL_MAX_TRANSFER_OUT];
// Receive support
uint16_t inMaxTransfer;
uint16_t inRemaining;
uint8_t inChan;
uint8_t inPayload[SH2_HAL_MAX_PAYLOAD_IN];
uint16_t inCursor;
uint32_t inTimestamp;
uint8_t inTransfer[SH2_HAL_MAX_TRANSFER_IN];
// What stage of advertisement processing are we in.
advert_phase_t advertPhase;
// Applications
shtp_App_t app[SH2_MAX_APPS];
uint8_t nextApp;
// Advert registrations
uint8_t nextAppListener;
shtp_AppListener_t appListener[SH2_MAX_APPS];
// SHTP Channels
shtp_Channel_t chan[SH2_MAX_CHANS];
// Channel listeners
shtp_ChanListener_t chanListener[SH2_MAX_CHANS];
uint8_t nextChanListener;
// Stats
uint32_t txDiscards;
uint32_t shortFragments;
uint32_t tooLargePayloads;
uint32_t badRxChan;
uint32_t badTxChan;
} shtp_t;
// ------------------------------------------------------------------------
// Private data
// Advertisement request
static const uint8_t advertise[] = {
CMD_ADVERTISE,
CMD_ADVERTISE_ALL
};
#define MAX_INSTANCES (1)
static shtp_t instances[MAX_INSTANCES];
static bool shtp_initialized = false;
// ------------------------------------------------------------------------
// Private functions
static void shtp_init(void)
{
// clear instance memory.
// In particular, this clears the pHal pointers which are used
// to determine if an instance is open and in-use.
memset(instances, 0, sizeof(instances));
// Set the initialized flag so this doesn't happen again.
shtp_initialized = true;
}
static shtp_t *getInstance(void)
{
for (int n = 0; n < MAX_INSTANCES; n++) {
if (instances[n].pHal == 0) {
// This instance is free
return &instances[n];
}
}
// Can't give an instance, none are free
return 0;
}
// Register a listener for an app (advertisement listener)
static void addAdvertListener(shtp_t *pShtp, uint16_t guid,
shtp_AdvertCallback_t *callback, void * cookie)
{
shtp_AppListener_t *pAppListener = 0;
// Bail out if no space for more apps
if (pShtp->nextAppListener >= SH2_MAX_APPS) return;
// Register this app
pAppListener = &pShtp->appListener[pShtp->nextAppListener];
pShtp->nextAppListener++;
pAppListener->guid = guid;
pAppListener->callback = callback;
pAppListener->cookie = cookie;
}
// Try to match registered listeners with their channels.
// This is performed every time the underlying Channel, App, Listener data structures are updated.
// As a result, channel number to callback association is fast when receiving packets
static void updateCallbacks(shtp_t *pShtp)
{
// Figure out which callback is associated with each channel.
// Channel -> (GUID, Chan name).
// GUID -> App name.
// (App name, Chan name) -> Callback
uint32_t guid;
const char * chanName = 0;
for (int chanNo = 0; chanNo < SH2_MAX_CHANS; chanNo++) {
// Reset callback for this channel until we find the right one.
pShtp->chan[chanNo].callback = 0;
if (pShtp->chan[chanNo].guid == 0xFFFFFFFF) {
// This channel entry not used.
continue;
}
// Get GUID and Channel Name for this channel
guid = pShtp->chan[chanNo].guid;
chanName = pShtp->chan[chanNo].chanName;
// Look for a listener registered with this guid, channel name
for (int listenerNo = 0; listenerNo < SH2_MAX_CHANS; listenerNo++)
{
if ((pShtp->chanListener[listenerNo].callback != 0) &&
(pShtp->chanListener[listenerNo].guid == guid) &&
(strcmp(chanName, pShtp->chanListener[listenerNo].chanName) == 0))
{
// This listener is the one for this channel
pShtp->chan[chanNo].callback = pShtp->chanListener[listenerNo].callback;
pShtp->chan[chanNo].cookie = pShtp->chanListener[listenerNo].cookie;
break;
}
}
}
}
// Register a new channel listener
static int addChanListener(shtp_t *pShtp,
uint16_t guid, const char * chanName,
shtp_Callback_t *callback, void *cookie)
{
shtp_ChanListener_t *pListener = 0;
// Bail out if there are too many listeners registered
if (pShtp->nextChanListener >= SH2_MAX_CHANS) return SH2_ERR;
// Register channel listener
pListener = &pShtp->chanListener[pShtp->nextChanListener];
pShtp->nextChanListener++;
pListener->guid = guid;
strcpy(pListener->chanName, chanName);
pListener->callback = callback;
pListener->cookie = cookie;
// re-evaluate channel callbacks
updateCallbacks(pShtp);
return SH2_OK;
}
static inline uint16_t min_u16(uint16_t a, uint16_t b)
{
if (a < b) {
return a;
}
else {
return b;
}
}
// Send a cargo as a sequence of transports
static int txProcess(shtp_t *pShtp, uint8_t chan, const uint8_t* pData, uint32_t len)
{
int status = SH2_OK;
bool continuation = false;
uint16_t cursor = 0;
uint16_t remaining;
uint16_t transferLen; // length of transfer, minus the header
uint16_t lenField;
cursor = 0;
remaining = len;
while (remaining > 0) {
// How much data (not header) can we send in next transfer
transferLen = min_u16(remaining, pShtp->outMaxTransfer-SHTP_HDR_LEN);
// Length field will be transferLen + SHTP_HDR_LEN
lenField = transferLen + SHTP_HDR_LEN;
// Put the header in the out buffer
pShtp->outTransfer[0] = lenField & 0xFF;
pShtp->outTransfer[1] = (lenField >> 8) & 0xFF;
if (continuation) {
pShtp->outTransfer[1] |= 0x80;
}
pShtp->outTransfer[2] = chan;
pShtp->outTransfer[3] = pShtp->chan[chan].nextOutSeq++;
// Stage one tranfer in the out buffer
memcpy(pShtp->outTransfer+SHTP_HDR_LEN, pData+cursor, transferLen);
remaining -= transferLen;
cursor += transferLen;
// Transmit (try repeatedly while HAL write returns 0)
status = pShtp->pHal->write(pShtp->pHal, pShtp->outTransfer, lenField);
while (status == 0)
{
shtp_service(pShtp);
status = pShtp->pHal->write(pShtp->pHal, pShtp->outTransfer, lenField);
}
if (status < 0)
{
// Error, throw away this cargo
pShtp->txDiscards++;
return status;
}
// For the rest of this transmission, packets are continuations.
continuation = true;
}
return SH2_OK;
}
// Callback for SHTP app-specific advertisement tags
static void shtpAdvertHdlr(void *cookie, uint8_t tag, uint8_t len, uint8_t *val)
{
shtp_t *pShtp = (shtp_t *)cookie;
switch (tag) {
case TAG_SHTP_VERSION:
if (strlen((const char *)val) < sizeof(pShtp->shtpVersion)) {
strcpy(pShtp->shtpVersion, (const char *)val);
}
break;
default:
break;
}
}
// Add one to the set of known Apps
static void addApp(shtp_t *pShtp, uint32_t guid)
{
shtp_App_t *pApp = 0;
// Bail out if this GUID is already registered
for (int n = 0; n < pShtp->nextApp; n++) {
if (pShtp->app[n].guid == guid) return;
}
// Bail out if no space for more apps
if (pShtp->nextApp >= SH2_MAX_APPS) return;
// Register this app
pApp = &pShtp->app[pShtp->nextApp];
pShtp->nextApp++;
pApp->guid = guid;
strcpy(pApp->appName, "");
// Re-evaluate channel callbacks
updateCallbacks(pShtp);
}
static void setAppName(shtp_t *pShtp, uint32_t guid, const char * appName)
{
shtp_App_t *pApp = 0;
// Find the app entry with this GUID
for (unsigned n = 0; n < pShtp->nextApp; n++) {
if (pShtp->app[n].guid == guid) {
pApp = &pShtp->app[n];
strcpy(pApp->appName, appName);
return;
}
}
}
// Add one to the set of known channels
static void addChannel(shtp_t *pShtp, uint8_t chanNo, uint32_t guid, const char * chanName, bool wake)
{
if (chanNo >= SH2_MAX_CHANS) return;
shtp_Channel_t * pChan = &pShtp->chan[chanNo];
// Store channel definition
pChan->guid = guid;
strcpy(pChan->chanName, chanName);
pChan->wake = wake;
// Init channel-associated data
pChan->nextOutSeq = 0;
pChan->nextInSeq = 0;
pChan->callback = 0;
pChan->cookie = 0;
// Re-evaluate channel callbacks
updateCallbacks(pShtp);
}
static void callAdvertHandler(shtp_t *pShtp, uint32_t guid,
uint8_t tag, uint8_t len, uint8_t *val)
{
// Find listener for this app
for (int n = 0; n < SH2_MAX_APPS; n++)
{
if (pShtp->appListener[n].guid == guid) {
// Found matching App entry
if (pShtp->appListener[n].callback != 0) {
pShtp->appListener[n].callback(pShtp->appListener[n].cookie, tag, len, val);
return;
}
}
}
}
static void processAdvertisement(shtp_t *pShtp, uint8_t *payload, uint16_t payloadLen)
{
uint16_t x;
uint8_t tag;
uint8_t len;
uint8_t *val;
uint16_t cursor = 1;
uint32_t guid = 0;
char appName[SHTP_APP_NAME_LEN];
char chanName[SHTP_CHAN_NAME_LEN];
uint8_t chanNo = 0;
bool wake = false;
strcpy(appName, "");
strcpy(chanName, "");
pShtp->advertPhase = ADVERT_IDLE;
while (cursor < payloadLen) {
tag = payload[cursor++];
len = payload[cursor++];
val = payload+cursor;
cursor += len;
// Process tag
switch (tag) {
case TAG_NULL:
// Reserved value, not a valid tag.
break;
case TAG_GUID:
// A new GUID is being established so terminate advertisement process with earlier app, if any.
callAdvertHandler(pShtp, guid, TAG_NULL, 0, 0);
guid = readu32(val);
addApp(pShtp, guid);
strcpy(appName, "");
strcpy(chanName, "");
break;
case TAG_MAX_CARGO_PLUS_HEADER_WRITE:
x = readu16(val) - SHTP_HDR_LEN;
if (x < SH2_HAL_MAX_PAYLOAD_OUT) {
pShtp->outMaxPayload = x;
}
break;
case TAG_MAX_CARGO_PLUS_HEADER_READ:
x = readu16(val) - SHTP_HDR_LEN;
// No need to store this!
break;
case TAG_MAX_TRANSFER_WRITE:
x = readu16(val) - SHTP_HDR_LEN;
if (x < SH2_HAL_MAX_TRANSFER_OUT) {
pShtp->outMaxTransfer = x;
} else {
pShtp->outMaxTransfer = SH2_HAL_MAX_TRANSFER_OUT;
}
break;
case TAG_MAX_TRANSFER_READ:
x = readu16(val) - SHTP_HDR_LEN;
if (x < SH2_HAL_MAX_TRANSFER_IN) {
pShtp->inMaxTransfer = x;
}
break;
case TAG_NORMAL_CHANNEL:
chanNo = readu8(val);
wake = false;
break;
case TAG_WAKE_CHANNEL:
chanNo = readu8(val);
wake = true;
break;
case TAG_APP_NAME:
strcpy(appName, (const char *)val);
setAppName(pShtp, guid, appName);
break;
case TAG_CHANNEL_NAME:
strcpy(chanName, (const char *)val);
addChannel(pShtp, chanNo, guid, (const char *)val, wake);
// Store channel metadata
if (chanNo < SH2_MAX_CHANS) {
pShtp->chan[chanNo].guid = guid;
strcpy(pShtp->chan[chanNo].chanName, chanName);
pShtp->chan[chanNo].wake = wake;
}
break;
case TAG_ADV_COUNT:
// Not yet supported.
break;
default:
// Nothing special needs to be done with this tag.
break;
}
// Deliver a TLV entry to the app's handler
callAdvertHandler(pShtp, guid, tag, len, val);
}
// terminate advertisement process with last app
callAdvertHandler(pShtp, guid, TAG_NULL, 0, 0);
}
// Callback for SHTP command channel
static void shtpCmdListener(void *cookie, uint8_t *payload, uint16_t len, uint32_t timestamp)
{
shtp_t *pShtp = (shtp_t *)cookie;
if ((payload == 0) || (len == 0)) return;
uint8_t response = payload[0];
switch (response) {
case RESP_ADVERTISE:
processAdvertisement(pShtp, payload, len);
break;
default:
// unknown response
break;
}
}
static void rxAssemble(shtp_t *pShtp, uint8_t *in, uint16_t len, uint32_t t_us)
{
uint16_t payloadLen;
bool continuation;
uint8_t chan = 0;
uint8_t seq = 0;
// discard invalid short fragments
if (len < SHTP_HDR_LEN) {
pShtp->shortFragments++;
return;
}
// Interpret header fields
payloadLen = (in[0] + (in[1] << 8)) & (~0x8000);
continuation = ((in[1] & 0x80) != 0);
chan = in[2];
seq = in[3];
if (payloadLen < SHTP_HDR_LEN) {
pShtp->shortFragments++;
if (pShtp->eventCallback) {
pShtp->eventCallback(pShtp->eventCookie, SHTP_SHORT_FRAGMENT);
}
return;
}
if ((chan >= SH2_MAX_CHANS) ||
(chan >= pShtp->nextChanListener)) {
// Invalid channel id.
pShtp->badRxChan++;
if (pShtp->eventCallback) {
pShtp->eventCallback(pShtp->eventCookie, SHTP_BAD_RX_CHAN);
}
return;
}
// Discard earlier assembly in progress if the received data doesn't match it.
if (pShtp->inRemaining) {
// Check this against previously received data.
if (!continuation ||
(chan != pShtp->inChan) ||
(seq != pShtp->chan[chan].nextInSeq)) {
// This fragment doesn't fit with previous one, discard earlier data
pShtp->inRemaining = 0;
}
}
if (pShtp->inRemaining == 0) {
if (payloadLen > sizeof(pShtp->inPayload)) {
// Error: This payload won't fit! Discard it.
pShtp->tooLargePayloads++;
if (pShtp->eventCallback) {
pShtp->eventCallback(pShtp->eventCookie, SHTP_TOO_LARGE_PAYLOADS);
}
return;
}
// This represents a new payload
// Store timestamp
pShtp->inTimestamp = t_us;
// Start a new assembly.
pShtp->inCursor = 0;
pShtp->inChan = chan;
}
// Append the new fragment to the payload under construction.
if (len > payloadLen) {
// Only use the valid portion of the transfer
len = payloadLen;
}
memcpy(pShtp->inPayload + pShtp->inCursor, in+SHTP_HDR_LEN, len-SHTP_HDR_LEN);
pShtp->inCursor += len-SHTP_HDR_LEN;
pShtp->inRemaining = payloadLen - len;
// If whole payload received, deliver it to channel listener.
if (pShtp->inRemaining == 0) {
// Call callback if there is one.
if (pShtp->chan[chan].callback != 0) {
pShtp->chan[chan].callback(pShtp->chan[chan].cookie,
pShtp->inPayload, pShtp->inCursor,
pShtp->inTimestamp);
}
}
// Remember next sequence number we expect for this channel.
pShtp->chan[chan].nextInSeq = seq + 1;
}
// ------------------------------------------------------------------------
// Public functions
// Takes HAL pointer, returns shtp ID for use in future calls.
// HAL will be opened by this call.
void *shtp_open(sh2_Hal_t *pHal)
{
if (!shtp_initialized) {
// Perform one-time module initialization
shtp_init();
}
// Validate params
if (pHal == 0) {
// Error
return 0;
}
// Find an available instance for this open
shtp_t *pShtp = getInstance();
if (pShtp == 0) {
// No instances available, return error
return 0;
}
// Clear the SHTP instance as a shortcut to initializing all fields
memset(pShtp, 0, sizeof(shtp_t));
// Store reference to the HAL
pShtp->pHal = pHal;
// Clear the asynchronous event callback point
pShtp->eventCallback = 0;
pShtp->eventCookie = 0;
// Initialize state vars (be prepared for adverts)
pShtp->outMaxPayload = SH2_HAL_MAX_PAYLOAD_OUT;
pShtp->outMaxTransfer = SH2_HAL_MAX_TRANSFER_OUT;
// Establish SHTP App and command channel a priori
addApp(pShtp, GUID_SHTP);
addChannel(pShtp, 0, GUID_SHTP, "command", false);
// Register SHTP advert listener and command channel listener
shtp_listenAdvert(pShtp, GUID_SHTP, shtpAdvertHdlr, pShtp);
shtp_listenChan(pShtp, GUID_SHTP, "command", shtpCmdListener, pShtp);
// When we open the HAL, it resets the device and adverts are sent automatically.
// So we go to ADVERT_REQUESTED state. They are on the way.
pShtp->advertPhase = ADVERT_REQUESTED;
// Open HAL
pHal->open(pHal);
return pShtp;
}
// Releases resources associated with this SHTP instance.
// HAL will not be closed.
void shtp_close(void *pInstance)
{
shtp_t *pShtp = (shtp_t *)pInstance;
pShtp->pHal->close(pShtp->pHal);
// Clear pShtp
// (Resetting pShtp->pHal to 0, returns this instance to the free pool)
memset(pShtp, 0, sizeof(shtp_t));
}
// Register the pointer of the callback function for reporting asynchronous events
void shtp_setEventCallback(void *pInstance,
shtp_EventCallback_t * eventCallback,
void *eventCookie) {
shtp_t *pShtp = (shtp_t *)pInstance;
pShtp->eventCallback = eventCallback;
pShtp->eventCookie = eventCookie;
}
// Register a listener for an SHTP channel
int shtp_listenChan(void *pInstance,
uint16_t guid, const char * chan,
shtp_Callback_t *callback, void * cookie)
{
shtp_t *pShtp = (shtp_t *)pInstance;
// Balk if channel name isn't valid
if ((chan == 0) || (strlen(chan) == 0)) return SH2_ERR_BAD_PARAM;
return addChanListener(pShtp, guid, chan, callback, cookie);
}
// Register a listener for SHTP advertisements
int shtp_listenAdvert(void *pInstance,
uint16_t guid,
shtp_AdvertCallback_t *advertCallback, void * cookie)
{
shtp_t *pShtp = (shtp_t *)pInstance;
// Register the advert listener
addAdvertListener(pShtp, guid, advertCallback, cookie);
// Arrange for a new set of advertisements, for this listener
if (pShtp->advertPhase == ADVERT_IDLE) {
pShtp->advertPhase = ADVERT_NEEDED;
}
return SH2_OK;
}
// Look up the channel number for a particular app, channel.
uint8_t shtp_chanNo(void *pInstance,
const char * appName, const char * chanName)
{
shtp_t *pShtp = (shtp_t *)pInstance;
int chan = 0;
uint32_t guid = 0xFFFFFFFF;
// Determine GUID for this appname
for (int n = 0; n < SH2_MAX_APPS; n++) {
if (strcmp(pShtp->app[n].appName, appName) == 0) {
guid = pShtp->app[n].guid;
break;
}
}
if (guid == 0xFFFFFFFF) return -1;
for (chan = 0; chan < SH2_MAX_CHANS; chan++) {
if ((strcmp(pShtp->chan[chan].chanName, chanName) == 0) &&
pShtp->chan[chan].guid == guid) {
// Found match
return chan;
}
}
// Not found
return 0xFF;
}
// Send an SHTP payload on a particular channel
int shtp_send(void *pInstance,
uint8_t channel, const uint8_t *payload, uint16_t len)
{
shtp_t *pShtp = (shtp_t *)pInstance;
int ret = SH2_OK;
if (len > pShtp->outMaxPayload) {
return SH2_ERR_BAD_PARAM;
}
if (channel >= SH2_MAX_CHANS) {
pShtp->badTxChan++;
return SH2_ERR_BAD_PARAM;
}
ret = txProcess(pShtp, channel, payload, len);
return ret;
}
// Check for received data and process it.
void shtp_service(void *pInstance)
{
shtp_t *pShtp = (shtp_t *)pInstance;
uint32_t t_us = 0;
if (pShtp->advertPhase == ADVERT_NEEDED) {
pShtp->advertPhase = ADVERT_REQUESTED; // do this before send, to avoid recursion.
int status = shtp_send(pShtp, SHTP_CHAN_COMMAND, advertise, sizeof(advertise));
if (status != SH2_OK) {
// Oops, advert request failed. Go back to needing one.
pShtp->advertPhase = ADVERT_NEEDED;
}
}
int len = pShtp->pHal->read(pShtp->pHal, pShtp->inTransfer, sizeof(pShtp->inTransfer), &t_us);
if (len) {
rxAssemble(pShtp, pShtp->inTransfer, len, t_us);
}
}