| /* |
| * ANT Stack |
| * |
| * Copyright 2011 Dynastream Innovations |
| * |
| * 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. |
| */ |
| /******************************************************************************\ |
| * |
| * FILE NAME: ant_rx_chardev.c |
| * |
| * BRIEF: |
| * This file implements the receive thread function which will loop reading |
| * ANT messages until told to exit. |
| * |
| * |
| \******************************************************************************/ |
| |
| #include <errno.h> |
| #include <poll.h> |
| #include <pthread.h> |
| #include <stdint.h> /* for uint64_t */ |
| |
| #include "ant_types.h" |
| #include "antradio_power.h" |
| #include "ant_rx_chardev.h" |
| #include "ant_hci_defines.h" |
| #include "ant_log.h" |
| #include "ant_native.h" // ANT_HCI_MAX_MSG_SIZE, ANT_MSG_ID_OFFSET, ANT_MSG_DATA_OFFSET, |
| // ant_radio_enabled_status() |
| |
| extern ANTStatus ant_tx_message_flowcontrol_none(ant_channel_type eTxPath, ANT_U8 ucMessageLength, ANT_U8 *pucTxMessage); |
| |
| #undef LOG_TAG |
| #define LOG_TAG "antradio_rx" |
| |
| #define ANT_POLL_TIMEOUT ((int)30000) |
| |
| static ANT_U8 aucRxBuffer[NUM_ANT_CHANNELS][ANT_HCI_MAX_MSG_SIZE]; |
| |
| #ifdef ANT_DEVICE_NAME // Single transport path |
| static int iRxBufferLength[NUM_ANT_CHANNELS] = {0}; |
| #else |
| static int iRxBufferLength[NUM_ANT_CHANNELS] = {0, 0}; |
| #endif // |
| |
| // Defines for use with the poll() call |
| #define EVENT_DATA_AVAILABLE (POLLIN|POLLRDNORM) |
| #define EVENT_DISABLE (POLLHUP) |
| #define EVENT_HARD_RESET (POLLERR|POLLPRI|POLLRDHUP) |
| |
| #define EVENTS_TO_LISTEN_FOR (EVENT_DATA_AVAILABLE|EVENT_DISABLE|EVENT_HARD_RESET) |
| |
| // Plus one is for the eventfd shutdown signal. |
| #define NUM_POLL_FDS (NUM_ANT_CHANNELS + 1) |
| #define EVENTFD_IDX NUM_ANT_CHANNELS |
| |
| void doReset(ant_rx_thread_info_t *stRxThreadInfo); |
| int readChannelMsg(ant_channel_type eChannel, ant_channel_info_t *pstChnlInfo); |
| |
| /* |
| * Function to check that all given flags are set in a particular value. |
| * Designed for use with the revents field of pollfds filled out by poll(). |
| * |
| * Parameters: |
| * - value: The value that will be checked to contain all flags. |
| * - flags: Bitwise-or of the flags that value should be checked for. |
| * |
| * Returns: |
| * - true IFF all the bits that are set in 'flags' are also set in 'value' |
| */ |
| ANT_BOOL areAllFlagsSet(short value, short flags) |
| { |
| value &= flags; |
| return (value == flags); |
| } |
| |
| /* |
| * This thread waits for ANT messages from a VFS file. |
| */ |
| void *fnRxThread(void *ant_rx_thread_info) |
| { |
| int iMutexLockResult; |
| int iPollRet; |
| ant_rx_thread_info_t *stRxThreadInfo; |
| struct pollfd astPollFd[NUM_POLL_FDS]; |
| ant_channel_type eChannel; |
| ANT_FUNC_START(); |
| |
| stRxThreadInfo = (ant_rx_thread_info_t *)ant_rx_thread_info; |
| for (eChannel = 0; eChannel < NUM_ANT_CHANNELS; eChannel++) { |
| astPollFd[eChannel].fd = stRxThreadInfo->astChannels[eChannel].iFd; |
| astPollFd[eChannel].events = EVENTS_TO_LISTEN_FOR; |
| } |
| // Fill out poll request for the shutdown signaller. |
| astPollFd[EVENTFD_IDX].fd = stRxThreadInfo->iRxShutdownEventFd; |
| astPollFd[EVENTFD_IDX].events = POLL_IN; |
| |
| /* continue running as long as not terminated */ |
| while (stRxThreadInfo->ucRunThread) { |
| /* Wait for data available on any file (transport path) */ |
| iPollRet = poll(astPollFd, NUM_POLL_FDS, ANT_POLL_TIMEOUT); |
| if (!iPollRet) { |
| ANT_DEBUG_V("poll timed out, checking exit cond"); |
| } else if (iPollRet < 0) { |
| ANT_ERROR("unhandled error: %s, attempting recovery.", strerror(errno)); |
| doReset(stRxThreadInfo); |
| goto out; |
| } else { |
| for (eChannel = 0; eChannel < NUM_ANT_CHANNELS; eChannel++) { |
| if (areAllFlagsSet(astPollFd[eChannel].revents, EVENT_HARD_RESET)) { |
| ANT_ERROR("Hard reset indicated by %s. Attempting recovery.", |
| stRxThreadInfo->astChannels[eChannel].pcDevicePath); |
| doReset(stRxThreadInfo); |
| goto out; |
| } else if (areAllFlagsSet(astPollFd[eChannel].revents, EVENT_DISABLE)) { |
| /* chip reported it was disabled, either unexpectedly or due to us closing the file */ |
| ANT_DEBUG_D( |
| "poll hang-up from %s. exiting rx thread", stRxThreadInfo->astChannels[eChannel].pcDevicePath); |
| |
| // set flag to exit out of Rx Loop |
| stRxThreadInfo->ucRunThread = 0; |
| |
| } else if (areAllFlagsSet(astPollFd[eChannel].revents, EVENT_DATA_AVAILABLE)) { |
| ANT_DEBUG_D("data on %s. reading it", |
| stRxThreadInfo->astChannels[eChannel].pcDevicePath); |
| |
| if (readChannelMsg(eChannel, &stRxThreadInfo->astChannels[eChannel]) < 0) { |
| // set flag to exit out of Rx Loop |
| stRxThreadInfo->ucRunThread = 0; |
| } |
| } else if (areAllFlagsSet(astPollFd[eChannel].revents, POLLNVAL)) { |
| ANT_ERROR("poll was called on invalid file descriptor %s. Attempting recovery.", |
| stRxThreadInfo->astChannels[eChannel].pcDevicePath); |
| doReset(stRxThreadInfo); |
| goto out; |
| } else if (areAllFlagsSet(astPollFd[eChannel].revents, POLLERR)) { |
| ANT_ERROR("Unknown error from %s. Attempting recovery.", |
| stRxThreadInfo->astChannels[eChannel].pcDevicePath); |
| doReset(stRxThreadInfo); |
| goto out; |
| } else if (astPollFd[eChannel].revents) { |
| ANT_DEBUG_W("unhandled poll result %#x from %s", |
| astPollFd[eChannel].revents, |
| stRxThreadInfo->astChannels[eChannel].pcDevicePath); |
| } |
| } |
| // Now check for shutdown signal |
| if(areAllFlagsSet(astPollFd[EVENTFD_IDX].revents, POLLIN)) |
| { |
| ANT_DEBUG_I("rx thread caught shutdown signal."); |
| // reset the counter by reading. |
| uint64_t counter; |
| read(stRxThreadInfo->iRxShutdownEventFd, &counter, sizeof(counter)); |
| // don't care if read error, going to close the thread anyways. |
| stRxThreadInfo->ucRunThread = 0; |
| } else if (astPollFd[EVENTFD_IDX].revents != 0) { |
| ANT_ERROR("Shutdown event descriptor had unexpected event: %#x. exiting rx thread.", |
| astPollFd[EVENTFD_IDX].revents); |
| stRxThreadInfo->ucRunThread = 0; |
| } |
| } |
| } |
| |
| /* disable ANT radio if not already disabling */ |
| // Try to get stEnabledStatusLock. |
| // if you get it then no one is enabling or disabling |
| // if you can't get it assume something made you exit |
| ANT_DEBUG_V("try getting stEnabledStatusLock in %s", __FUNCTION__); |
| iMutexLockResult = pthread_mutex_trylock(stRxThreadInfo->pstEnabledStatusLock); |
| if (!iMutexLockResult) { |
| ANT_DEBUG_V("got stEnabledStatusLock in %s", __FUNCTION__); |
| ANT_WARN("rx thread has unexpectedly crashed, cleaning up"); |
| |
| // spoof our handle as closed so we don't try to join ourselves in disable |
| stRxThreadInfo->stRxThread = 0; |
| |
| if (g_fnStateCallback) { |
| g_fnStateCallback(RADIO_STATUS_DISABLING); |
| } |
| |
| ant_disable(); |
| |
| if (g_fnStateCallback) { |
| g_fnStateCallback(ant_radio_enabled_status()); |
| } |
| |
| ANT_DEBUG_V("releasing stEnabledStatusLock in %s", __FUNCTION__); |
| pthread_mutex_unlock(stRxThreadInfo->pstEnabledStatusLock); |
| ANT_DEBUG_V("released stEnabledStatusLock in %s", __FUNCTION__); |
| } else if (iMutexLockResult != EBUSY) { |
| ANT_ERROR("rx thread closing code, trylock on state lock failed: %s", |
| strerror(iMutexLockResult)); |
| } else { |
| ANT_DEBUG_V("stEnabledStatusLock busy"); |
| } |
| |
| out: |
| ANT_FUNC_END(); |
| #ifdef ANDROID |
| return NULL; |
| #endif |
| } |
| |
| void doReset(ant_rx_thread_info_t *stRxThreadInfo) |
| { |
| int iMutexLockResult; |
| |
| ANT_FUNC_START(); |
| /* Chip was reset or other error, only way to recover is to |
| * close and open ANT chardev */ |
| stRxThreadInfo->ucChipResetting = 1; |
| |
| if (g_fnStateCallback) { |
| g_fnStateCallback(RADIO_STATUS_RESETTING); |
| } |
| |
| stRxThreadInfo->ucRunThread = 0; |
| |
| ANT_DEBUG_V("getting stEnabledStatusLock in %s", __FUNCTION__); |
| iMutexLockResult = pthread_mutex_lock(stRxThreadInfo->pstEnabledStatusLock); |
| if (iMutexLockResult < 0) { |
| ANT_ERROR("chip was reset, getting state mutex failed: %s", |
| strerror(iMutexLockResult)); |
| stRxThreadInfo->stRxThread = 0; |
| } else { |
| ANT_DEBUG_V("got stEnabledStatusLock in %s", __FUNCTION__); |
| |
| stRxThreadInfo->stRxThread = 0; /* spoof our handle as closed so we don't |
| * try to join ourselves in disable */ |
| |
| ant_disable(); |
| |
| if (ant_enable()) { /* failed */ |
| if (g_fnStateCallback) { |
| g_fnStateCallback(RADIO_STATUS_DISABLED); |
| } |
| } else { /* success */ |
| if (g_fnStateCallback) { |
| g_fnStateCallback(RADIO_STATUS_RESET); |
| } |
| } |
| |
| ANT_DEBUG_V("releasing stEnabledStatusLock in %s", __FUNCTION__); |
| pthread_mutex_unlock(stRxThreadInfo->pstEnabledStatusLock); |
| ANT_DEBUG_V("released stEnabledStatusLock in %s", __FUNCTION__); |
| } |
| |
| stRxThreadInfo->ucChipResetting = 0; |
| |
| ANT_FUNC_END(); |
| } |
| |
| //////////////////////////////////////////////////////////////////// |
| // setFlowControl |
| // |
| // Sets the flow control "flag" to the value provided and signals the transmit |
| // thread to check the value. |
| // |
| // Parameters: |
| // pstChnlInfo the details of the channel being updated |
| // ucFlowSetting the value to use |
| // |
| // Returns: |
| // Success: |
| // 0 |
| // Failure: |
| // -1 |
| //////////////////////////////////////////////////////////////////// |
| int setFlowControl(ant_channel_info_t *pstChnlInfo, ANT_U8 ucFlowSetting) |
| { |
| int iRet = -1; |
| int iMutexResult; |
| ANT_FUNC_START(); |
| |
| ANT_DEBUG_V("getting stFlowControlLock in %s", __FUNCTION__); |
| iMutexResult = pthread_mutex_lock(pstChnlInfo->pstFlowControlLock); |
| if (iMutexResult) { |
| ANT_ERROR("failed to lock flow control mutex during response: %s", strerror(iMutexResult)); |
| } else { |
| ANT_DEBUG_V("got stFlowControlLock in %s", __FUNCTION__); |
| |
| pstChnlInfo->ucFlowControlResp = ucFlowSetting; |
| |
| ANT_DEBUG_V("releasing stFlowControlLock in %s", __FUNCTION__); |
| pthread_mutex_unlock(pstChnlInfo->pstFlowControlLock); |
| ANT_DEBUG_V("released stFlowControlLock in %s", __FUNCTION__); |
| |
| pthread_cond_signal(pstChnlInfo->pstFlowControlCond); |
| |
| iRet = 0; |
| } |
| |
| ANT_FUNC_END(); |
| return iRet; |
| } |
| |
| int readChannelMsg(ant_channel_type eChannel, ant_channel_info_t *pstChnlInfo) |
| { |
| int iRet = -1; |
| int iRxLenRead; |
| int iCurrentHciPacketOffset; |
| int iHciDataSize; |
| ANT_FUNC_START(); |
| |
| // Keep trying to read while there is an error, and that error is EAGAIN |
| while (((iRxLenRead = read(pstChnlInfo->iFd, &aucRxBuffer[eChannel][iRxBufferLength[eChannel]], (sizeof(aucRxBuffer[eChannel]) - iRxBufferLength[eChannel]))) < 0) |
| && errno == EAGAIN) |
| ; |
| |
| if (iRxLenRead < 0) { |
| if (errno == ENODEV) { |
| ANT_ERROR("%s not enabled, exiting rx thread", |
| pstChnlInfo->pcDevicePath); |
| |
| goto out; |
| } else if (errno == ENXIO) { |
| ANT_ERROR("%s there is no physical ANT device connected", |
| pstChnlInfo->pcDevicePath); |
| |
| goto out; |
| } else { |
| ANT_ERROR("%s read thread exiting, unhandled error: %s", |
| pstChnlInfo->pcDevicePath, strerror(errno)); |
| |
| goto out; |
| } |
| } else { |
| ANT_SERIAL(aucRxBuffer[eChannel], iRxLenRead, 'R'); |
| |
| iRxLenRead += iRxBufferLength[eChannel]; // add existing data on |
| |
| // if we didn't get a full packet, then just exit |
| if (iRxLenRead < (aucRxBuffer[eChannel][ANT_HCI_SIZE_OFFSET] + ANT_HCI_HEADER_SIZE + ANT_HCI_FOOTER_SIZE)) { |
| iRxBufferLength[eChannel] = iRxLenRead; |
| iRet = 0; |
| goto out; |
| } |
| |
| iRxBufferLength[eChannel] = 0; // reset buffer length here since we should have a full packet |
| |
| #if ANT_HCI_OPCODE_SIZE == 1 // Check the different message types by opcode |
| ANT_U8 opcode = aucRxBuffer[eChannel][ANT_HCI_OPCODE_OFFSET]; |
| |
| if(ANT_HCI_OPCODE_COMMAND_COMPLETE == opcode) { |
| // Command Complete, so signal a FLOW_GO |
| if(setFlowControl(pstChnlInfo, ANT_FLOW_GO)) { |
| goto out; |
| } |
| } else if(ANT_HCI_OPCODE_FLOW_ON == opcode) { |
| // FLow On, so resend the last Tx |
| #ifdef ANT_FLOW_RESEND |
| // Check if there is a message to resend |
| if(pstChnlInfo->ucResendMessageLength > 0) { |
| ant_tx_message_flowcontrol_none(eChannel, pstChnlInfo->ucResendMessageLength, pstChnlInfo->pucResendMessage); |
| } else { |
| ANT_DEBUG_D("Resend requested by chip, but tx request cancelled"); |
| } |
| #endif // ANT_FLOW_RESEND |
| } else if(ANT_HCI_OPCODE_ANT_EVENT == opcode) |
| // ANT Event, send ANT packet to Rx Callback |
| #endif // ANT_HCI_OPCODE_SIZE == 1 |
| { |
| // Received an ANT packet |
| iCurrentHciPacketOffset = 0; |
| |
| while(iCurrentHciPacketOffset < iRxLenRead) { |
| |
| // TODO Allow HCI Packet Size value to be larger than 1 byte |
| // This currently works as no size value is greater than 255, and little endian |
| iHciDataSize = aucRxBuffer[eChannel][iCurrentHciPacketOffset + ANT_HCI_SIZE_OFFSET]; |
| |
| if ((iHciDataSize + ANT_HCI_HEADER_SIZE + ANT_HCI_FOOTER_SIZE + iCurrentHciPacketOffset) > |
| iRxLenRead) { |
| // we don't have a whole packet |
| iRxBufferLength[eChannel] = iRxLenRead - iCurrentHciPacketOffset; |
| memcpy(aucRxBuffer[eChannel], &aucRxBuffer[eChannel][iCurrentHciPacketOffset], iRxBufferLength[eChannel]); |
| // the increment at the end should push us out of the while loop |
| } else |
| #ifdef ANT_MESG_FLOW_CONTROL |
| if (aucRxBuffer[eChannel][iCurrentHciPacketOffset + ANT_HCI_DATA_OFFSET + ANT_MSG_ID_OFFSET] == |
| ANT_MESG_FLOW_CONTROL) { |
| // This is a flow control packet, not a standard ANT message |
| if(setFlowControl(pstChnlInfo, \ |
| aucRxBuffer[eChannel][iCurrentHciPacketOffset + ANT_HCI_DATA_OFFSET + ANT_MSG_DATA_OFFSET])) { |
| goto out; |
| } |
| } else |
| #endif // ANT_MESG_FLOW_CONTROL |
| { |
| if (pstChnlInfo->fnRxCallback != NULL) { |
| |
| // Loop through read data until all HCI packets are written to callback |
| pstChnlInfo->fnRxCallback(iHciDataSize, \ |
| &aucRxBuffer[eChannel][iCurrentHciPacketOffset + ANT_HCI_DATA_OFFSET]); |
| } else { |
| ANT_WARN("%s rx callback is null", pstChnlInfo->pcDevicePath); |
| } |
| } |
| |
| iCurrentHciPacketOffset = iCurrentHciPacketOffset + ANT_HCI_HEADER_SIZE + ANT_HCI_FOOTER_SIZE + iHciDataSize; |
| } |
| } |
| |
| iRet = 0; |
| } |
| |
| out: |
| ANT_FUNC_END(); |
| return iRet; |
| } |