/* * 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 #include #include #include #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 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 headerBuffer; std::vector 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 &header, const std::vector &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(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 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 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 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 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 values(3); values[0].set( mPreloadedNanoappPendingTransaction.nanoappId); values[1].set( Atoms::ChreHalNanoappLoadFailed::TYPE_PRELOADED); values[2].set( 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