blob: b2e74810eb593034a5415763deecdf5199da73c5 [file] [log] [blame]
/*
* Copyright (C) 2022 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 "exynos_daemon.h"
#include <sys/epoll.h>
#include <utils/SystemClock.h>
#include <array>
#include <csignal>
#include "chre_host/file_stream.h"
// Aliased for consistency with the way these symbols are referenced in
// CHRE-side code
namespace fbs = ::chre::fbs;
namespace android {
namespace chre {
namespace {
int createEpollFd(int fdToEpoll) {
struct epoll_event event;
event.data.fd = fdToEpoll;
event.events = EPOLLIN | EPOLLWAKEUP;
int epollFd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_ctl(epollFd, EPOLL_CTL_ADD, event.data.fd, &event) != 0) {
LOGE("Failed to add control interface to msg read fd errno: %s",
strerror(errno));
epollFd = -1;
}
return epollFd;
}
} // anonymous namespace
ExynosDaemon::ExynosDaemon() : mLpmaHandler(true /* LPMA enabled */) {
// TODO(b/235631242): Implement this.
}
bool ExynosDaemon::init() {
constexpr size_t kMaxTimeSyncRetries = 5;
constexpr useconds_t kTimeSyncRetryDelayUs = 50000; // 50 ms
bool success = false;
mNativeThreadHandle = 0;
siginterrupt(SIGINT, true);
std::signal(SIGINT, signalHandler);
if ((mCommsReadFd = open(kCommsDeviceFilename, O_RDONLY | O_CLOEXEC)) < 0) {
LOGE("Read FD open failed: %s", strerror(errno));
} else if ((mCommsWriteFd =
open(kCommsDeviceFilename, O_WRONLY | O_CLOEXEC)) < 0) {
LOGE("Write FD open failed: %s", strerror(errno));
} else {
mProcessThreadRunning = true;
mIncomingMsgProcessThread =
std::thread([&] { this->processIncomingMsgs(); });
mNativeThreadHandle = mIncomingMsgProcessThread.native_handle();
if (!sendTimeSyncWithRetry(kMaxTimeSyncRetries, kTimeSyncRetryDelayUs,
true /* logOnError */)) {
LOGE("Failed to send initial time sync message");
} else {
loadPreloadedNanoapps();
success = true;
LOGD("CHRE daemon initialized successfully");
}
}
return success;
}
void ExynosDaemon::deinit() {
stopMsgProcessingThread();
close(mCommsWriteFd);
mCommsWriteFd = kInvalidFd;
close(mCommsReadFd);
mCommsReadFd = kInvalidFd;
}
void ExynosDaemon::run() {
constexpr char kChreSocketName[] = "chre";
auto serverCb = [&](uint16_t clientId, void *data, size_t len) {
sendMessageToChre(clientId, data, len);
};
mServer.run(kChreSocketName, true /* allowSocketCreation */, serverCb);
}
void ExynosDaemon::stopMsgProcessingThread() {
if (mProcessThreadRunning) {
mProcessThreadRunning = false;
pthread_kill(mNativeThreadHandle, SIGINT);
if (mIncomingMsgProcessThread.joinable()) {
mIncomingMsgProcessThread.join();
}
}
}
void ExynosDaemon::processIncomingMsgs() {
std::array<uint8_t, kIpcMsgSizeMax> message;
int epollFd = createEpollFd(mCommsReadFd);
while (mProcessThreadRunning) {
struct epoll_event retEvent;
int nEvents = epoll_wait(epollFd, &retEvent, 1 /* maxEvents */,
-1 /* infinite timeout */);
if (nEvents < 0) {
// epoll_wait will get interrupted if the CHRE daemon is shutting down,
// check this condition before logging an error.
if (mProcessThreadRunning) {
LOGE("Epolling failed: %s", strerror(errno));
}
} else if (nEvents == 0) {
LOGW("Epoll returned with 0 FDs ready despite no timeout (errno: %s)",
strerror(errno));
} else {
int bytesRead = read(mCommsReadFd, message.data(), message.size());
if (bytesRead < 0) {
LOGE("Failed to read from fd: %s", strerror(errno));
} else if (bytesRead == 0) {
LOGE("Read 0 bytes from fd");
} else {
onMessageReceived(message.data(), bytesRead);
}
}
}
}
bool ExynosDaemon::doSendMessage(void *data, size_t length) {
bool success = false;
if (length > kIpcMsgSizeMax) {
LOGE("Msg size %zu larger than max msg size %zu", length, kIpcMsgSizeMax);
} else {
ssize_t rv = write(mCommsWriteFd, data, length);
if (rv < 0) {
LOGE("Failed to send message: %s", strerror(errno));
} else if (rv != length) {
LOGW("Msg send data loss: %zd of %zu bytes were written", rv, length);
} else {
success = true;
}
}
return success;
}
int64_t ExynosDaemon::getTimeOffset(bool *success) {
// TODO(b/235631242): Implement this.
*success = false;
return 0;
}
void ExynosDaemon::loadPreloadedNanoapp(const std::string &directory,
const std::string &name,
uint32_t transactionId) {
std::vector<uint8_t> headerBuffer;
std::vector<uint8_t> nanoappBuffer;
std::string headerFilename = directory + "/" + name + ".napp_header";
std::string nanoappFilename = directory + "/" + name + ".so";
if (readFileContents(headerFilename.c_str(), headerBuffer) &&
readFileContents(nanoappFilename.c_str(), nanoappBuffer) &&
!loadNanoapp(headerBuffer, nanoappBuffer, transactionId)) {
LOGE("Failed to load nanoapp: '%s'", name.c_str());
}
}
bool ExynosDaemon::loadNanoapp(const std::vector<uint8_t> &header,
const std::vector<uint8_t> &nanoapp,
uint32_t transactionId) {
// This struct comes from build/build_template.mk and must not be modified.
// Refer to that file for more details.
struct NanoAppBinaryHeader {
uint32_t headerVersion;
uint32_t magic;
uint64_t appId;
uint32_t appVersion;
uint32_t flags;
uint64_t hwHubType;
uint8_t targetChreApiMajorVersion;
uint8_t targetChreApiMinorVersion;
uint8_t reserved[6];
} __attribute__((packed));
bool success = false;
if (header.size() != sizeof(NanoAppBinaryHeader)) {
LOGE("Header size mismatch");
} else {
// The header blob contains the struct above.
const auto *appHeader =
reinterpret_cast<const NanoAppBinaryHeader *>(header.data());
// Build the target API version from major and minor.
uint32_t targetApiVersion = (appHeader->targetChreApiMajorVersion << 24) |
(appHeader->targetChreApiMinorVersion << 16);
success = sendFragmentedNanoappLoad(
appHeader->appId, appHeader->appVersion, appHeader->flags,
targetApiVersion, nanoapp.data(), nanoapp.size(), transactionId);
}
return success;
}
bool ExynosDaemon::sendFragmentedNanoappLoad(
uint64_t appId, uint32_t appVersion, uint32_t appFlags,
uint32_t appTargetApiVersion, const uint8_t *appBinary, size_t appSize,
uint32_t transactionId) {
std::vector<uint8_t> binary(appSize);
std::copy(appBinary, appBinary + appSize, binary.begin());
FragmentedLoadTransaction transaction(transactionId, appId, appVersion,
appFlags, appTargetApiVersion, binary);
bool success = true;
while (success && !transaction.isComplete()) {
// Pad the builder to avoid allocation churn.
const auto &fragment = transaction.getNextRequest();
flatbuffers::FlatBufferBuilder builder(fragment.binary.size() + 128);
HostProtocolHost::encodeFragmentedLoadNanoappRequest(
builder, fragment, true /* respondBeforeStart */);
success = sendFragmentAndWaitOnResponse(transactionId, builder,
fragment.fragmentId, appId);
}
return success;
}
bool ExynosDaemon::sendFragmentAndWaitOnResponse(
uint32_t transactionId, flatbuffers::FlatBufferBuilder &builder,
uint32_t fragmentId, uint64_t appId) {
bool success = true;
std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
mPreloadedNanoappPendingTransaction = {
.transactionId = transactionId,
.fragmentId = fragmentId,
.nanoappId = appId,
};
mPreloadedNanoappPending = sendMessageToChre(
kHostClientIdDaemon, builder.GetBufferPointer(), builder.GetSize());
if (!mPreloadedNanoappPending) {
LOGE("Failed to send nanoapp fragment");
success = false;
} else {
std::chrono::seconds timeout(2);
bool signaled = mPreloadedNanoappsCond.wait_for(
lock, timeout, [this] { return !mPreloadedNanoappPending; });
if (!signaled) {
LOGE("Nanoapp fragment load timed out");
success = false;
}
}
return success;
}
void ExynosDaemon::handleDaemonMessage(const uint8_t *message) {
std::unique_ptr<fbs::MessageContainerT> container =
fbs::UnPackMessageContainer(message);
if (container->message.type != fbs::ChreMessage::LoadNanoappResponse) {
LOGE("Invalid message from CHRE directed to daemon");
} else {
const auto *response = container->message.AsLoadNanoappResponse();
std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
if (!mPreloadedNanoappPending) {
LOGE("Received nanoapp load response with no pending load");
} else if (mPreloadedNanoappPendingTransaction.transactionId !=
response->transaction_id) {
LOGE("Received nanoapp load response with invalid transaction id");
} else if (mPreloadedNanoappPendingTransaction.fragmentId !=
response->fragment_id) {
LOGE("Received nanoapp load response with invalid fragment id");
} else if (!response->success) {
#ifdef CHRE_DAEMON_METRIC_ENABLED
std::vector<VendorAtomValue> values(3);
values[0].set<VendorAtomValue::longValue>(
mPreloadedNanoappPendingTransaction.nanoappId);
values[1].set<VendorAtomValue::intValue>(
Atoms::ChreHalNanoappLoadFailed::TYPE_PRELOADED);
values[2].set<VendorAtomValue::intValue>(
Atoms::ChreHalNanoappLoadFailed::REASON_ERROR_GENERIC);
const VendorAtom atom{
.atomId = Atoms::CHRE_HAL_NANOAPP_LOAD_FAILED,
.values{std::move(values)},
};
ChreDaemonBase::reportMetric(atom);
#endif // CHRE_DAEMON_METRIC_ENABLED
} else {
mPreloadedNanoappPending = false;
}
mPreloadedNanoappsCond.notify_all();
}
}
} // namespace chre
} // namespace android