blob: dd76b3c7e6362d580a5f66d4b5e8c69abd91a8d1 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.
*/
#include "chpp/platform/chpp_uart_link_manager.h"
#include "aoc.h"
#include "chpp/log.h"
#include "chpp/macros.h"
#include "chre/platform/fatal_error.h"
#include "chre/platform/system_time.h"
#include "core_monitor.h"
#include "efw/include/interrupt_controller.h"
#include "efw/include/processor.h"
#include "efw/include/timer.h"
#include "ipc-regions.h"
using chre::SystemTime;
namespace chpp {
namespace {
void onUartRxInterrupt(void *context) {
UartLinkManager *manager = static_cast<UartLinkManager *>(context);
manager->getUart()->DisableRxInterrupt();
manager->pullRxSamples();
chppWorkThreadSignalFromLink(&manager->getTransportContext()->linkParams,
CHPP_TRANSPORT_SIGNAL_LINK_RX_PROCESS);
if (!manager->isRxBufferFull()) {
manager->getUart()->EnableRxInterrupt();
}
}
//! This is the interrupt to use when handling requests from the remote
//! to start a transaction.
void onTransactionRequestInterrupt(void *context, uint32_t /* interrupt */) {
UartLinkManager *manager = static_cast<UartLinkManager *>(context);
manager->getWakeInGpi()->SetTriggerFunction(GPIAoC::GPI_DISABLE);
manager->getWakeInGpi()->ClearInterrupt();
manager->prepareForTransaction();
chppWorkThreadSignalFromLink(&manager->getTransportContext()->linkParams,
CHPP_TRANSPORT_SIGNAL_LINK_WAKE_IN_IRQ);
}
//! This is the interrupt to use when handling signal handshaking during
//! an on-going transaction.
void onTransactionHandshakeInterrupt(void *context, uint32_t /* interrupt */) {
UartLinkManager *manager = static_cast<UartLinkManager *>(context);
manager->getWakeInGpi()->SetTriggerFunction(GPIAoC::GPI_DISABLE);
manager->getWakeInGpi()->ClearInterrupt();
BaseType_t xHigherPriorityTaskWoken = 0;
xTaskNotifyFromISR(manager->getTaskHandle(),
CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ, eSetBits,
&xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
bool setTimer(uint64_t timeoutNs, timerCallback callback, void *context,
void **timerHandle) {
Timer *timer = Timer::Instance();
const TickType_t timeoutTicks =
static_cast<TickType_t>(timer->NsToTicks(timeoutNs));
return timer->EventAddAtOffset(timeoutTicks, callback, context,
timerHandle) == 0;
}
} // anonymous namespace
UartLinkManager::UartLinkManager(struct ChppTransportState *context, UART *uart,
uint8_t wakeOutPinNumber,
uint8_t wakeInGpiNumber,
enum PreventCoreMonitorMask mask,
bool wakeHandshakeEnable)
: mTransportContext(context),
mUart(uart),
mWakeOutGpio(wakeOutPinNumber, IP_LOCK_GPIO),
mWakeInGpi(wakeInGpiNumber),
mCoreMonitorMask(mask),
mWakeHandshakeEnabled(wakeHandshakeEnable) {
mWakeOutGpio.SetDirection(GPIO::DIRECTION::OUTPUT);
mWakeOutGpio.Clear();
}
void UartLinkManager::init(TaskHandle_t handle) {
mUart->RegisterRxCallback(onUartRxInterrupt, this);
mUart->EnableRxInterrupt();
mTaskHandle = handle;
mWakeInGpi.SetInterruptHandler(onTransactionRequestInterrupt, this);
mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_RISING_EDGE);
InterruptController::Instance()->InterruptEnable(
IRQ_GPI0 + mWakeInGpi.GetGpiNumber(), Processor::Instance()->CoreID(),
true /* enable */);
}
void UartLinkManager::deinit() {
clearTxPacket();
mWakeOutGpio.Clear();
mUart->DisableRxInterrupt();
InterruptController::Instance()->InterruptEnable(
IRQ_GPI0 + mWakeInGpi.GetGpiNumber(), Processor::Instance()->CoreID(),
false /* enable */);
mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_DISABLE);
mCoreMonitorRefCount.store(0);
allowCoreMonitor();
}
bool UartLinkManager::prepareTxPacket(uint8_t *buf, size_t len) {
bool success = !hasTxPacket();
if (!success) {
CHPP_LOGE("Cannot prepare packet while one pending");
} else {
mCurrentBuffer = buf;
mCurrentBufferLen = len;
prepareForTransaction();
}
return success;
}
void UartLinkManager::prepareForTransaction() {
mTransactionPending = true;
CoreMonitor::Instance()->Prevent(mCoreMonitorMask);
}
void UartLinkManager::onCoreMonitorAllowEvent() {
if (mCoreMonitorRefCount.load() > 0 &&
mCoreMonitorRefCount.fetch_decrement() == 1) {
chppWorkThreadSignalFromLink(&getTransportContext()->linkParams,
CHPP_TRANSPORT_SIGNAL_LINK_CORE_MONITOR);
}
}
void UartLinkManager::completeTransaction() {
auto callback = [](void *context) {
UartLinkManager *manager = static_cast<UartLinkManager *>(context);
manager->onCoreMonitorAllowEvent();
return false; // one-shot
};
// Set a timer to avoid thrashing in and out of core monitor in cases where
// transactions occur quickly one after another.
void *timerHandle;
mCoreMonitorRefCount.fetch_increment();
uint64_t suspendTimeoutNs = kSuspendTimeoutNs;
if (!setTimer(suspendTimeoutNs, callback, this, &timerHandle)) {
CHPP_LOGE("Failed to set core monitor timer");
// Enter critical section to avoid race conditions with timer interrupt.
taskENTER_CRITICAL();
onCoreMonitorAllowEvent();
taskEXIT_CRITICAL();
}
mTransactionPending = false;
}
bool UartLinkManager::waitForHandshakeIrq(uint64_t timeoutNs) {
bool success = true;
if (mWakeHandshakeEnabled) {
success = false;
// Treat as if the IRQ occurred in the timeout case. We check if the timer
// was successfully cancelled to determine if we timed out or not.
auto timeoutCallback = [](void *context) -> bool {
onTransactionHandshakeInterrupt(context, 0);
return false; // one-shot
};
void *timerHandle;
if (!setTimer(timeoutNs, timeoutCallback, this, &timerHandle)) {
CHPP_LOGE("Failed to set handshake timeout timer");
} else {
uint32_t signal = 0;
while ((signal & CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ) == 0) {
xTaskNotifyWait(
0 /* ulBitsToClearOnEntry */,
CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ /* ulBitsToClearOnExit */,
&signal, portMAX_DELAY /* xTicksToWait */);
}
// If we cleared the notification while another one is in progress,
// re-notify the task so it doesn't get lost.
if ((signal & ~(CHPP_TRANSPORT_SIGNAL_LINK_HANDSHAKE_IRQ)) != 0) {
xTaskNotify(mTaskHandle, 0, eNoAction);
}
// EventRemove() will not return 0 if the timer already fired.
success = (Timer::Instance()->EventRemove(timerHandle) == 0);
}
}
return success;
}
bool UartLinkManager::startTransaction() {
bool success = true;
mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_DISABLE);
// Check if a transaction is pending (either from an IRQ or from a pending
// TX packet) before starting one. This check is required to avoid attempting
// to start a transaction when it has already been handled (e.g. an IRQ has
// signalled the transport layer, but it already had a packet to send and
// handled them together).
if (mTransactionPending.load()) {
mWakeOutGpio.Set(true /* set */);
uint64_t pulseEndTimeNs =
SystemTime::getMonotonicTime().toRawNanoseconds() + kPulseTimeNs;
if (mWakeHandshakeEnabled) {
mWakeInGpi.SetInterruptHandler(onTransactionHandshakeInterrupt, this);
mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_LEVEL_ACTIVE_HIGH);
}
if (waitForHandshakeIrq(kStartTimeoutNs)) {
if (hasTxPacket()) {
int bytesTransmitted = mUart->Tx(mCurrentBuffer, mCurrentBufferLen);
if (static_cast<size_t>(bytesTransmitted) != mCurrentBufferLen) {
CHPP_LOGE("Failed to transmit data");
success = false;
}
} else {
uint64_t now = SystemTime::getMonotonicTime().toRawNanoseconds();
if (now < pulseEndTimeNs) {
mTaskUtil.suspend(pulseEndTimeNs - now);
}
}
if (mWakeHandshakeEnabled) {
mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_LEVEL_ACTIVE_LOW);
if (!waitForHandshakeIrq(kEndTimeoutNs)) {
CHPP_LOGE("Wake handshaking end timed out");
success = false;
}
}
} else {
CHPP_LOGE("Wake handshaking start timed out");
success = false;
}
completeTransaction();
mWakeOutGpio.Clear();
}
// Re-enable the interrupt to handle transaction requests.
mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_DISABLE);
mWakeInGpi.SetInterruptHandler(onTransactionRequestInterrupt, this);
// TODO: Handle potential cases where we miss a rising edge
mWakeInGpi.SetTriggerFunction(GPIAoC::GPI_RISING_EDGE);
// Remove the TX packet to allow subsequent transmission.
clearTxPacket();
return success;
}
void UartLinkManager::pullRxSamples() {
int ch;
while ((ch = mUart->GetChar()) != EOF && mRxBufIndex < kRxBufSize) {
mRxBuf[mRxBufIndex++] = static_cast<uint8_t>(ch);
}
}
void UartLinkManager::processRxSamples() {
mUart->DisableRxInterrupt();
pullRxSamples();
chppRxDataCb(mTransportContext, const_cast<uint8_t *>(mRxBuf), mRxBufIndex);
mRxBufIndex = 0;
mUart->EnableRxInterrupt();
}
void UartLinkManager::allowCoreMonitor() {
taskENTER_CRITICAL();
if (mCoreMonitorRefCount.load() == 0 && !mTransactionPending.load()) {
CoreMonitor::Instance()->Allow(mCoreMonitorMask);
}
taskEXIT_CRITICAL();
}
bool UartLinkManager::hasTxPacket() const {
return mCurrentBuffer != nullptr && mUart != nullptr;
}
void UartLinkManager::clearTxPacket() {
mCurrentBuffer = nullptr;
}
} // namespace chpp