/** ---------------------------------------------------------------------- * * Copyright (C) 2016 ST Microelectronics S.A. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * 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. * * ----------------------------------------------------------------------*/ #define LOG_TAG "NfcHal" #include #include "halcore_private.h" #include "android_logmsg.h" #include #include #include #include pthread_mutex_t debugOutputSem = PTHREAD_MUTEX_INITIALIZER; bool halTraceMask = true; extern int I2cWriteCmd(const uint8_t* x, size_t len); extern void DispHal(const char* title, const void* data, size_t length); extern uint32_t ScrProtocolTraceFlag; // = SCR_PROTO_TRACE_ALL; // HAL WRAPPER static void HalStopTimer(HalInstance* inst); typedef struct { struct nfc_nci_device nci_device; // nci_device must be first struct member // below declarations are private variables within HAL nfc_stack_callback_t* p_cback; nfc_stack_data_callback_t* p_data_cback; HALHANDLE hHAL; } st21nfc_dev_t; // beware, is a duplication of structure in nfc_nci_st21nfc.c /************************************************************************************************** * * Private API Declaration * **************************************************************************************************/ static void* HalWorkerThread(void* arg); static inline int sem_wait_nointr(sem_t *sem); static void HalOnNewUpstreamFrame(HalInstance* inst, const uint8_t* data, size_t length); static void HalTriggerNextDsPacket(HalInstance* inst); static bool HalEnqueueThreadMessage(HalInstance* inst, ThreadMesssage* msg); static bool HalDequeueThreadMessage(HalInstance* inst, ThreadMesssage* msg); static HalBuffer* HalAllocBuffer(HalInstance* inst); static HalBuffer* HalFreeBuffer(HalInstance* inst, HalBuffer* b); static uint32_t HalSemWait(sem_t* pSemaphore, uint32_t timeout); /************************************************************************************************** * * Public API Entry-Points * **************************************************************************************************/ /** * Callback of HAL Core protocol layer. * Invoked by HAL worker thread according to if message is received from NCI * stack or posted by * I2C worker thread. *

@param context NFC callbacks for control/data * @param event Next HAL state machine action (send msg to I2C layer or report * data/control/error * to NFC task) * @param length Configure if debug and trace allowed, trace level */ void HalCoreCallback(void* context, uint32_t event, const void* d, size_t length) { const uint8_t* data = (const uint8_t*)d; uint8_t cmd = 'W'; st21nfc_dev_t* dev = (st21nfc_dev_t*)context; switch (event) { case HAL_EVENT_DSWRITE: STLOG_HAL_V("!! got event HAL_EVENT_DSWRITE for %zu bytes\n", length); DispHal("TX DATA", (data), length); // Send write command to IO thread cmd = 'W'; I2cWriteCmd(&cmd, sizeof(cmd)); I2cWriteCmd((const uint8_t*)&length, sizeof(length)); I2cWriteCmd(data, length); break; case HAL_EVENT_DATAIND: STLOG_HAL_V("!! got event HAL_EVENT_DATAIND for %zu bytes\n", length); if ((length >= 3) && (data[2] != (length - 3))) { STLOG_HAL_W("length is illogical. Header length is %d, packet length %zu\n", data[2], length); } dev->p_data_cback(length, (uint8_t*)data); break; case HAL_EVENT_ERROR: STLOG_HAL_E("!! got event HAL_EVENT_ERROR\n"); DispHal("Received unexpected HAL message !!!", data, length); break; case HAL_EVENT_LINKLOST: STLOG_HAL_E("!! got event HAL_EVENT_LINKLOST or HAL_EVENT_ERROR\n"); dev->p_cback(HAL_NFC_ERROR_EVT, HAL_NFC_STATUS_ERR_CMD_TIMEOUT); // Write terminate command cmd = 'X'; I2cWriteCmd(&cmd, sizeof(cmd)); break; case HAL_EVENT_TIMER_TIMEOUT: STLOG_HAL_D("!! got event HAL_EVENT_TIMER_TIMEOUT \n"); dev->p_cback(HAL_WRAPPER_TIMEOUT_EVT, HAL_NFC_STATUS_OK); // dev->p_data_cback(0, NULL); break; } } /** * Connection to the HAL Core layer. * Set-up HAL context and create HAL worker thread. *

@param context NFC NCI device context, NFC callbacks for control/data, HAL * handle * @param callback HAL callback function pointer * @param flags Configure if debug and trace allowed, trace level */ HALHANDLE HalCreate(void* context, HAL_CALLBACK callback, uint32_t flags) { halTraceMask = true; if (flags & HAL_FLAG_NO_DEBUG) { halTraceMask = false; } STLOG_HAL_V("HalCreate enter\n"); HalInstance* inst = calloc(1, sizeof(HalInstance)); if (!inst) { STLOG_HAL_E("!out of memory\n"); return NULL; } // We need a semaphore to wakeup our protocol thread if (0 != sem_init(&inst->semaphore, 0, 0)) { STLOG_HAL_E("!sem_init failed\n"); free(inst); return NULL; } // We need a semaphore to manage buffers if (0 != sem_init(&inst->bufferResourceSem, 0, NUM_BUFFERS)) { STLOG_HAL_E("!sem_init failed\n"); sem_destroy(&inst->semaphore); free(inst); return NULL; } // We need a semaphore to block upstream data indications if (0 != sem_init(&inst->upstreamBlock, 0, 0)) { STLOG_HAL_E("!sem_init failed\n"); sem_destroy(&inst->semaphore); sem_destroy(&inst->bufferResourceSem); free(inst); return NULL; } // Initialize remaining data-members inst->context = context; inst->callback = callback; inst->flags = flags; inst->freeBufferList = 0; inst->pendingNciList = 0; inst->nciBuffer = 0; inst->ringReadPos = 0; inst->ringWritePos = 0; inst->timeout = HAL_SLEEP_TIMER_DURATION; inst->bufferData = calloc(NUM_BUFFERS, sizeof(HalBuffer)); if (!inst->bufferData) { STLOG_HAL_E("!failed to allocate memory\n"); sem_destroy(&inst->semaphore); sem_destroy(&inst->bufferResourceSem); sem_destroy(&inst->upstreamBlock); free(inst); return NULL; } // Concatenate the buffers into a linked list for easy access size_t i; for (i = 0; i < NUM_BUFFERS; i++) { HalBuffer* b = &inst->bufferData[i]; b->next = inst->freeBufferList; inst->freeBufferList = b; } if (0 != pthread_mutex_init(&inst->hMutex, 0)) { STLOG_HAL_E("!failed to initialize Mutex \n"); sem_destroy(&inst->semaphore); sem_destroy(&inst->bufferResourceSem); sem_destroy(&inst->upstreamBlock); free(inst->bufferData); free(inst); return NULL; } // Spawn the thread if (0 != pthread_create(&inst->thread, NULL, HalWorkerThread, inst)) { STLOG_HAL_E("!failed to spawn workerthread \n"); sem_destroy(&inst->semaphore); sem_destroy(&inst->bufferResourceSem); sem_destroy(&inst->upstreamBlock); pthread_mutex_destroy(&inst->hMutex); free(inst->bufferData); free(inst); return NULL; } STLOG_HAL_V("HalCreate exit\n"); return (HALHANDLE)inst; } /** * Disconnection of the HAL protocol layer. * Send message to stop the HAL worker thread and wait for it to finish. Free * resources. * @param hHAL HAL handle */ void HalDestroy(HALHANDLE hHAL) { HalInstance* inst = (HalInstance*)hHAL; // Tell the thread that we want to finish ThreadMesssage msg; msg.command = MSG_EXIT_REQUEST; msg.payload = 0; msg.length = 0; HalEnqueueThreadMessage(inst, &msg); // Wait for thread to finish pthread_join(inst->thread, NULL); // Cleanup and exit sem_destroy(&inst->semaphore); sem_destroy(&inst->upstreamBlock); sem_destroy(&inst->bufferResourceSem); pthread_mutex_destroy(&inst->hMutex); // Free resources free(inst->bufferData); free(inst); STLOG_HAL_V("HalDestroy done\n"); } /** * Send an NCI message downstream to HAL protocol layer (DH->NFCC transfer). * Block if more than NUM_BUFFERS (10) transfers are outstanding, otherwise will return immediately. * @param hHAL HAL handle * @param data Data message * @param size Message size */ bool HalSendDownstream(HALHANDLE hHAL, const uint8_t* data, size_t size) { // Send an NCI frame downstream. will HalInstance* inst = (HalInstance*)hHAL; if ((size <= MAX_BUFFER_SIZE) && (size > 0)) { ThreadMesssage msg; HalBuffer* b = HalAllocBuffer(inst); if (!b) { // Should never be reachable return false; } memcpy(b->data, data, size); b->length = size; msg.command = MSG_TX_DATA; msg.payload = 0; msg.length = 0; msg.buffer = b; return HalEnqueueThreadMessage(inst, &msg); } else { STLOG_HAL_E("HalSendDownstream size to large %zu instead of %d\n", size, MAX_BUFFER_SIZE); return false; } } // HAL WRAPPER /** * Send an NCI message downstream to HAL protocol layer (DH->NFCC transfer). * Block if more than NUM_BUFFERS (10) transfers are outstanding, otherwise will return immediately. * @param hHAL HAL handle * @param data Data message * @param size Message size */ bool HalSendDownstreamTimer(HALHANDLE hHAL, const uint8_t* data, size_t size, uint8_t duration) { // Send an NCI frame downstream. will HalInstance* inst = (HalInstance*)hHAL; if ((size <= MAX_BUFFER_SIZE) && (size > 0)) { ThreadMesssage msg; HalBuffer* b = HalAllocBuffer(inst); if (!b) { // Should never be reachable return false; } memcpy(b->data, data, size); b->length = size; msg.command = MSG_TX_DATA_TIMER_START; msg.payload = 0; msg.length = duration; msg.buffer = b; return HalEnqueueThreadMessage(inst, &msg); } else { STLOG_HAL_E("HalSendDownstreamTimer size to large %zu instead of %d\n", size, MAX_BUFFER_SIZE); return false; } } /** * Send an NCI message downstream to HAL protocol layer (DH->NFCC transfer). * Block if more than NUM_BUFFERS (10) transfers are outstanding, otherwise will * return immediately. * @param hHAL HAL handle * @param data Data message * @param size Message size */ bool HalSendDownstreamStopTimer(HALHANDLE hHAL) { // Send an NCI frame downstream. will HalInstance* inst = (HalInstance*)hHAL; HalStopTimer(inst); return 1; } /** * Send an NCI message upstream to NFC NCI layer (NFCC->DH transfer). * @param hHAL HAL handle * @param data Data message * @param size Message size */ bool HalSendUpstream(HALHANDLE hHAL, const uint8_t* data, size_t size) { HalInstance* inst = (HalInstance*)hHAL; if ((size <= MAX_BUFFER_SIZE) && (size > 0)) { ThreadMesssage msg; msg.command = MSG_RX_DATA; msg.payload = data; msg.length = size; if (HalEnqueueThreadMessage(inst, &msg)) { // Block until the protocol has taken a copy of the data sem_wait_nointr(&inst->upstreamBlock); return true; } return false; } else { STLOG_HAL_E("HalSendUpstream size to large %zu instead of %d\n", size, MAX_BUFFER_SIZE); return false; } } /************************************************************************************************** * * Private API Definition * **************************************************************************************************/ /* * Get current time stamp */ struct timespec HalGetTimestamp(void) { struct timespec tm; clock_gettime(CLOCK_REALTIME, &tm); return tm; } int HalTimeDiffInMs(struct timespec start, struct timespec end) { struct timespec temp; if ((end.tv_nsec - start.tv_nsec) < 0) { temp.tv_sec = end.tv_sec - start.tv_sec - 1; temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec; } else { temp.tv_sec = end.tv_sec - start.tv_sec; temp.tv_nsec = end.tv_nsec - start.tv_nsec; } return (temp.tv_nsec / 1000000) + (temp.tv_sec * 1000); } /** * Determine the next shortest sleep to fulfill the pending timer requirements. * @param inst HAL instance * @param now timespec structure for time definition */ static uint32_t HalCalcSemWaitingTime(HalInstance* inst, struct timespec* now) { // Default to infinite wait time uint32_t result = OS_SYNC_INFINITE; if (inst->timer.active) { int delta = inst->timer.duration - HalTimeDiffInMs(inst->timer.startTime, *now); if (delta < 0) { // If we have a timer that has already expired, pick a zero wait time result = 0; } else if ((uint32_t)delta < result) { // Smaller time difference? If so take it result = delta; } } if (result != OS_SYNC_INFINITE) { // Add one millisecond on top of that, so the waiting semaphore will time // out just a moment // after the timer should expire result += 1; } return result; } /************************************************************************************************** * * Timer Management * **************************************************************************************************/ static void HalStopTimer(HalInstance* inst) { inst->timer.active = false; STLOG_HAL_D("HalStopTimer \n"); } static void HalStartTimer(HalInstance* inst, uint32_t duration) { STLOG_HAL_D("HalStartTimer \n"); inst->timer.startTime = HalGetTimestamp(); inst->timer.active = true; inst->timer.duration = duration; } /************************************************************************************************** * * Thread Message Queue * **************************************************************************************************/ /** * Write message pointer to small ring buffer for queuing HAL messages. * @param inst HAL instance * @param msg Message to send * @return true if message properly copied in ring buffer */ static bool HalEnqueueThreadMessage(HalInstance* inst, ThreadMesssage* msg) { // Put a message to the queue int nextWriteSlot; bool result = true; pthread_mutex_lock(&inst->hMutex); nextWriteSlot = inst->ringWritePos + 1; if (nextWriteSlot == HAL_QUEUE_MAX) { nextWriteSlot = 0; } // Check that we don't overflow the queue entries if (nextWriteSlot == inst->ringReadPos) { STLOG_HAL_E("HAL thread message ring: RNR (implement me!!)"); result = false; } if (result) { // inst->ring[nextWriteSlot] = *msg; memcpy(&(inst->ring[nextWriteSlot]), msg, sizeof(ThreadMesssage)); inst->ringWritePos = nextWriteSlot; } pthread_mutex_unlock(&inst->hMutex); if (result) { sem_post(&inst->semaphore); } return result; } /** * Remove message pointer from stored ring buffer. * @param inst HAL instance * @param msg Message received * @return true if there is a new message to pull, false otherwise. */ static bool HalDequeueThreadMessage(HalInstance* inst, ThreadMesssage* msg) { int nextCmdIndex; bool result = true; // New data available pthread_mutex_lock(&inst->hMutex); // Get new timer read index nextCmdIndex = inst->ringReadPos + 1; if (nextCmdIndex == HAL_QUEUE_MAX) { nextCmdIndex = 0; } //check if ring buffer is empty if (inst->ringReadPos == inst->ringWritePos) { STLOG_HAL_E("HAL thread message ring: already read last valid data"); result = false; } // Get new element from ringbuffer if (result) { memcpy(msg, &(inst->ring[nextCmdIndex]), sizeof(ThreadMesssage)); inst->ringReadPos = nextCmdIndex; } pthread_mutex_unlock(&inst->hMutex); return result; } /************************************************************************************************** * * Buffer/Memory Management * **************************************************************************************************/ /** * Allocate buffer from pre-allocated pool. * @param inst HAL instance * @return Pointer to allocated HAL buffer */ static HalBuffer* HalAllocBuffer(HalInstance* inst) { HalBuffer* b; // Wait until we have a buffer resource sem_wait_nointr(&inst->bufferResourceSem); pthread_mutex_lock(&inst->hMutex); b = inst->freeBufferList; if (b) { inst->freeBufferList = b->next; b->next = 0; } pthread_mutex_unlock(&inst->hMutex); if (!b) { STLOG_HAL_E( "! unable to allocate buffer resource." "check bufferResourceSem\n"); } return b; } /** * Return buffer to pool. * @param inst HAL instance * @param b Pointer of HAL buffer to free * @return Pointer of freed HAL buffer */ static HalBuffer* HalFreeBuffer(HalInstance* inst, HalBuffer* b) { pthread_mutex_lock(&inst->hMutex); b->next = inst->freeBufferList; inst->freeBufferList = b; pthread_mutex_unlock(&inst->hMutex); // Unblock treads waiting for a buffer sem_post(&inst->bufferResourceSem); return b; } /************************************************************************************************** * * State Machine * **************************************************************************************************/ /** * Event handler for HAL message * @param inst HAL instance * @param e HAL event */ static void Hal_event_handler(HalInstance* inst, HalEvent e) { switch (e) { case EVT_RX_DATA: { // New data packet arrived const uint8_t* nciData; size_t nciLength; // Extract raw NCI data from frame nciData = inst->lastUsFrame; nciLength = inst->lastUsFrameSize; // Pass received raw NCI data to stack inst->callback(inst->context, HAL_EVENT_DATAIND, nciData, nciLength); } break; case EVT_TX_DATA: // NCI data arrived from stack // Send data inst->callback(inst->context, HAL_EVENT_DSWRITE, inst->nciBuffer->data, inst->nciBuffer->length); // Free the buffer HalFreeBuffer(inst, inst->nciBuffer); inst->nciBuffer = 0; break; // HAL WRAPPER case EVT_TIMER: inst->callback(inst->context, HAL_EVENT_TIMER_TIMEOUT, NULL, 0); break; } } /************************************************************************************************** * * HAL Worker Thread * **************************************************************************************************/ /** * HAL worker thread to serialize all actions into a single thread. * RX/TX/TIMER are dispatched from here. * @param arg HAL instance arguments */ static void* HalWorkerThread(void* arg) { HalInstance* inst = (HalInstance*)arg; inst->exitRequest = false; STLOG_HAL_V("thread running\n"); while (!inst->exitRequest) { struct timespec now = HalGetTimestamp(); uint32_t waitResult = HalSemWait(&inst->semaphore, HalCalcSemWaitingTime(inst, &now)); switch (waitResult) { case OS_SYNC_TIMEOUT: { // One or more times have expired STLOG_HAL_W("OS_SYNC_TIMEOUT\n"); now = HalGetTimestamp(); // HAL WRAPPER // callback to hal wrapper // Unblock sem_post(&inst->upstreamBlock); // Data frame Hal_event_handler(inst, EVT_TIMER); } break; case OS_SYNC_RELEASED: { // A message arrived ThreadMesssage msg; if (HalDequeueThreadMessage(inst, &msg)) { switch (msg.command) { case MSG_EXIT_REQUEST: STLOG_HAL_V("received exit request from upper layer\n"); inst->exitRequest = true; break; case MSG_TX_DATA: STLOG_HAL_V("received new NCI data from stack\n"); // Attack to end of list if (!inst->pendingNciList) { inst->pendingNciList = msg.buffer; inst->pendingNciList->next = 0; } else { // Find last element of the list. b->next is zero for this // element HalBuffer* b; for (b = inst->pendingNciList; b->next; b = b->next) { }; // Concatenate to list b->next = msg.buffer; msg.buffer->next = 0; } // Start transmitting if we're in the correct state HalTriggerNextDsPacket(inst); break; // HAL WRAPPER case MSG_TX_DATA_TIMER_START: STLOG_HAL_V("received new NCI data from stack, need timer start\n"); // Attack to end of list if (!inst->pendingNciList) { inst->pendingNciList = msg.buffer; inst->pendingNciList->next = 0; } else { // Find last element of the list. b->next is zero for this // element HalBuffer* b; for (b = inst->pendingNciList; b->next; b = b->next) { }; // Concatenate to list b->next = msg.buffer; msg.buffer->next = 0; } // Start timer HalStartTimer(inst, msg.length); // Start transmitting if we're in the correct state HalTriggerNextDsPacket(inst); break; case MSG_RX_DATA: STLOG_HAL_D("received new data from CLF\n"); HalOnNewUpstreamFrame(inst, msg.payload, msg.length); break; default: STLOG_HAL_E("!received unkown thread message?\n"); break; } } else { STLOG_HAL_E("!got wakeup in workerthread, but no message here? ?\n"); } } break; case OS_SYNC_FAILED: STLOG_HAL_E( "!Something went horribly wrong.. The semaphore wait function " "failed\n"); inst->exitRequest = true; break; } } STLOG_HAL_D("thread about to exit\n"); return NULL; } /************************************************************************************************** * * Misc. Functions * **************************************************************************************************/ /** * helper to make sem_t interrupt safe * @param sem_t semaphore * @return sem_wait return value. */ static inline int sem_wait_nointr(sem_t *sem) { while (sem_wait(sem)) if (errno == EINTR) errno = 0; else return -1; return 0; } /** * Handle RX frames here first in HAL context. * @param inst HAL instance * @param data HAL data received from I2C worker thread * @param length Size of HAL data */ static void HalOnNewUpstreamFrame(HalInstance* inst, const uint8_t* data, size_t length) { memcpy(inst->lastUsFrame, data, length); inst->lastUsFrameSize = length; // Data frame Hal_event_handler(inst, EVT_RX_DATA); // Allow the I2C thread to get the next message (if done early, it may // overwrite before handled) sem_post(&inst->upstreamBlock); } /** * Send out the next queued up buffer for TX if any. * @param inst HAL instance */ static void HalTriggerNextDsPacket(HalInstance* inst) { // Check if we have something to transmit downstream HalBuffer* b = inst->pendingNciList; if (b) { // Get the buffer from the pending list inst->pendingNciList = b->next; inst->nciBuffer = b; STLOG_HAL_V("trigger transport of next NCI data downstream\n"); // Process the new nci frame Hal_event_handler(inst, EVT_TX_DATA); } else { STLOG_HAL_V("no new NCI data to transmit, enter wait..\n"); } } /* * Wait for given semaphore signaling a specific time or ever * param sem_t * pSemaphore * param uint32_t timeout * return uint32_t */ static uint32_t HalSemWait(sem_t* pSemaphore, uint32_t timeout) { uint32_t result = OS_SYNC_RELEASED; bool gotResult = false; if (timeout == OS_SYNC_INFINITE) { while (!gotResult) { if (sem_wait(pSemaphore) == -1) { int e = errno; char msg[200]; if (e == EINTR) { STLOG_HAL_W( "! semaphore (infin) wait interrupted by system signal. re-enter " "wait"); continue; } strerror_r(e, msg, sizeof(msg) - 1); STLOG_HAL_E("! semaphore (infin) wait failed. sem=0x%p, %s", pSemaphore, msg); gotResult = true; result = OS_SYNC_FAILED; } else { gotResult = true; } }; } else { struct timespec tm; long oneSecInNs = (int)1e9; clock_gettime(CLOCK_REALTIME, &tm); /* add timeout (can't overflow): */ tm.tv_sec += (timeout / 1000); tm.tv_nsec += ((timeout % 1000) * 1000000); /* make sure nanoseconds are below a million */ if (tm.tv_nsec >= oneSecInNs) { tm.tv_sec++; tm.tv_nsec -= oneSecInNs; } while (!gotResult) { if (sem_timedwait(pSemaphore, &tm) == -1) { int e = errno; if (e == EINTR) { /* interrupted by signal? repeat sem_wait again */ continue; } if (e == ETIMEDOUT) { result = OS_SYNC_TIMEOUT; gotResult = true; } else { result = OS_SYNC_FAILED; gotResult = true; } } else { gotResult = true; } } } return result; }