| /* |
| * Copyright (C) 2017 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. |
| */ |
| |
| /** |
| * @file |
| * The daemon that hosts CHRE on a hexagon DSP via FastRPC. This is typically |
| * the SLPI but could be the ADSP or another DSP that supports FastRPC. |
| * |
| * Several threads are required for this functionality: |
| * - Main thread: blocked waiting on SIGINT/SIGTERM, and requests graceful |
| * shutdown of CHRE when caught |
| * - Monitor thread: persistently blocked in a FastRPC call to the DSP that |
| * only returns when CHRE exits or the DSP crashes |
| * - TODO: see whether we can merge this with the RX thread |
| * - Reverse monitor thread: after initializing the DSP-side monitor for this |
| * process, blocks on a condition variable. If this thread exits, CHRE on |
| * the DSP side will be notified and shut down (this is only possible if |
| * this thread is not blocked in a FastRPC call). |
| * - TODO: confirm this and see whether we can merge this responsibility |
| * into the TX thread |
| * - Message to host (RX) thread: blocks in FastRPC call, waiting on incoming |
| * message from CHRE |
| * - Message to CHRE (TX) thread: blocks waiting on outbound queue, delivers |
| * messages to CHRE over FastRPC |
| * |
| * TODO: This file originated from an implementation for another device, and was |
| * written in C, but then it was converted to C++ when adding socket support. It |
| * should be fully converted to C++. |
| */ |
| |
| // Disable verbose logging |
| // TODO: use property_get_bool to make verbose logging runtime configurable |
| // #define LOG_NDEBUG 0 |
| |
| #include <ctype.h> |
| #include <inttypes.h> |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <fstream> |
| #include <queue> |
| #include <string> |
| |
| #include "chre/platform/slpi/fastrpc.h" |
| #include "chre_host/host_protocol_host.h" |
| #include "chre_host/log.h" |
| #include "chre_host/socket_server.h" |
| #include "generated/chre_slpi.h" |
| |
| #include <android-base/logging.h> |
| #include <json/json.h> |
| #include <utils/SystemClock.h> |
| |
| #ifdef CHRE_USE_TOKENIZED_LOGGING |
| #include "pw_tokenizer/detokenize.h" |
| using pw::tokenizer::DetokenizedString; |
| using pw::tokenizer::Detokenizer; |
| #endif |
| |
| #ifdef CHRE_DAEMON_LOAD_INTO_SENSORSPD |
| #include "remote.h" |
| |
| #define ITRANSPORT_PREFIX "'\":;./\\" |
| #endif // CHRE_DAEMON_LOAD_INTO_SENSORSPD |
| |
| //! The format string to use for logs from the CHRE implementation. |
| #define HUB_LOG_FORMAT_STR "@ %3" PRIu32 ".%03" PRIu32 ": [CHRE] %s" |
| |
| #ifndef UNUSED_VAR |
| #define UNUSED_VAR(var) ((void)(var)) |
| #endif |
| |
| #ifdef CHRE_DAEMON_LPMA_ENABLED |
| #include <android/hardware/soundtrigger/2.0/ISoundTriggerHw.h> |
| #include <hardware_legacy/power.h> |
| |
| using android::sp; |
| using android::wp; |
| using android::hardware::Return; |
| using android::hardware::soundtrigger::V2_0::ISoundTriggerHw; |
| using android::hardware::soundtrigger::V2_0::SoundModelHandle; |
| using android::hardware::soundtrigger::V2_0::SoundModelType; |
| #endif // CHRE_DAEMON_LPMA_ENABLED |
| |
| using android::elapsedRealtimeNano; |
| using android::chre::FragmentedLoadTransaction; |
| using android::chre::HostProtocolHost; |
| |
| // Aliased for consistency with the way these symbols are referenced in |
| // CHRE-side code |
| namespace fbs = ::chre::fbs; |
| |
| typedef void *(thread_entry_point_f)(void *); |
| |
| struct reverse_monitor_thread_data { |
| pthread_t thread; |
| pthread_mutex_t mutex; |
| pthread_cond_t cond; |
| }; |
| |
| static void *chre_message_to_host_thread(void *arg); |
| static void *chre_monitor_thread(void *arg); |
| static bool start_thread(pthread_t *thread_handle, |
| thread_entry_point_f *thread_entry, void *arg); |
| |
| #ifdef CHRE_DAEMON_LPMA_ENABLED |
| //! The name of the wakelock to use for the CHRE daemon. |
| static const char kWakeLockName[] = "chre_daemon"; |
| |
| //! Forward declarations |
| static void onStHalServiceDeath(); |
| |
| //! Class to handle when a connected ST HAL service dies. |
| class StHalDeathRecipient : public android::hardware::hidl_death_recipient { |
| virtual void serviceDied( |
| uint64_t /* cookie */, |
| const wp<::android::hidl::base::V1_0::IBase> & /* who */) override { |
| LOGE("ST HAL service died."); |
| onStHalServiceDeath(); |
| } |
| }; |
| |
| struct LpmaEnableThreadData { |
| pthread_t thread; |
| pthread_mutex_t mutex; |
| pthread_cond_t cond; |
| bool currentLpmaEnabled; |
| bool targetLpmaEnabled; |
| bool connectedToService; |
| sp<StHalDeathRecipient> deathRecipient = new StHalDeathRecipient(); |
| sp<ISoundTriggerHw> stHalService; |
| }; |
| |
| static LpmaEnableThreadData lpmaEnableThread; |
| |
| #endif // CHRE_DAEMON_LPMA_ENABLED |
| |
| //! The host ID to use when preloading nanoapps. This is used before the server |
| //! is started and is sufficiently high enough so as to not collide with any |
| //! clients after the server starts. |
| static const uint16_t kHostClientIdDaemon = UINT16_MAX; |
| |
| //! Contains a set of transaction IDs used to load the preloaded nanoapps. |
| //! The IDs are stored in the order they are sent. |
| static std::queue<uint32_t> gPreloadedNanoappPendingTransactionIds; |
| |
| //! Set to true when we request a graceful shutdown of CHRE |
| static volatile bool chre_shutdown_requested = false; |
| |
| #if !defined(LOG_NDEBUG) || LOG_NDEBUG != 0 |
| static void log_buffer(const uint8_t * /*buffer*/, size_t /*size*/) {} |
| #else |
| static void log_buffer(const uint8_t *buffer, size_t size) { |
| char line[32]; |
| int offset = 0; |
| char line_chars[32]; |
| int offset_chars = 0; |
| |
| size_t orig_size = size; |
| if (size > 128) { |
| size = 128; |
| LOGV("Dumping first 128 bytes of buffer of size %zu", orig_size); |
| } else { |
| LOGV("Dumping buffer of size %zu bytes", size); |
| } |
| for (size_t i = 1; i <= size; ++i) { |
| offset += |
| snprintf(&line[offset], sizeof(line) - offset, "%02x ", buffer[i - 1]); |
| offset_chars += |
| snprintf(&line_chars[offset_chars], sizeof(line_chars) - offset_chars, |
| "%c", (isprint(buffer[i - 1])) ? buffer[i - 1] : '.'); |
| if ((i % 8) == 0) { |
| LOGV(" %s\t%s", line, line_chars); |
| offset = 0; |
| offset_chars = 0; |
| } else if ((i % 4) == 0) { |
| offset += snprintf(&line[offset], sizeof(line) - offset, " "); |
| } |
| } |
| |
| if (offset > 0) { |
| char tabs[8]; |
| char *pos = tabs; |
| while (offset < 28) { |
| *pos++ = '\t'; |
| offset += 8; |
| } |
| *pos = '\0'; |
| LOGV(" %s%s%s", line, tabs, line_chars); |
| } |
| } |
| #endif |
| |
| #ifdef CHRE_USE_TOKENIZED_LOGGING |
| static android_LogPriority chreLogLevelToAndroidLogPriority(uint8_t level) { |
| switch (level) { |
| case CHRE_LOG_LEVEL_ERROR: |
| return ANDROID_LOG_ERROR; |
| case CHRE_LOG_LEVEL_WARN: |
| return ANDROID_LOG_WARN; |
| case CHRE_LOG_LEVEL_INFO: |
| return ANDROID_LOG_INFO; |
| case CHRE_LOG_LEVEL_DEBUG: |
| return ANDROID_LOG_DEBUG; |
| default: |
| return ANDROID_LOG_SILENT; |
| } |
| } |
| |
| void emitLogMessage(uint8_t level, uint64_t timestampNanos, const char *log) { |
| constexpr const char kLogTag[] = "CHRE"; |
| constexpr const uint64_t kNanosPerSec = 1e9; |
| constexpr const uint64_t kNanosPerMsec = 1e6; |
| |
| uint32_t timeSec = timestampNanos / kNanosPerSec; |
| timestampNanos -= (timeSec * kNanosPerSec); |
| |
| uint32_t timeMsec = timestampNanos / kNanosPerMsec; |
| |
| android_LogPriority priority = chreLogLevelToAndroidLogPriority(level); |
| LOG_PRI(priority, kLogTag, HUB_LOG_FORMAT_STR, timeSec, timeMsec, log); |
| } |
| |
| void parseAndEmitTokenizedLogMessages(unsigned char *message, |
| unsigned int messageLen, |
| const Detokenizer *detokenizer) { |
| if (detokenizer != nullptr) { |
| // TODO: Pull out common code from the tokenized/standard log |
| // parser functions when we implement batching for tokenized |
| // logs (b/148873804) |
| constexpr size_t kLogMessageHeaderSize = |
| 1 /*logLevel*/ + sizeof(uint64_t) /*timestamp*/; |
| |
| const fbs::MessageContainer *container = fbs::GetMessageContainer(message); |
| const auto *logMessage = |
| static_cast<const fbs::LogMessage *>(container->message()); |
| |
| const flatbuffers::Vector<int8_t> &logData = *logMessage->buffer(); |
| const uint8_t *log = reinterpret_cast<const uint8_t *>(logData.data()); |
| uint8_t level = *log; |
| ++log; |
| |
| uint64_t timestampNanos; |
| memcpy(×tampNanos, log, sizeof(uint64_t)); |
| timestampNanos = le64toh(timestampNanos); |
| log += sizeof(uint64_t); |
| |
| DetokenizedString detokenizedLog = |
| detokenizer->Detokenize(log, messageLen - kLogMessageHeaderSize); |
| std::string decodedLog = detokenizedLog.BestStringWithErrors(); |
| emitLogMessage(level, timestampNanos, decodedLog.c_str()); |
| } else { |
| // log an error and risk log spam? fail silently? log once? |
| } |
| } |
| |
| #endif |
| |
| static int64_t getTimeOffset(bool *success) { |
| int64_t timeOffset = 0; |
| |
| #if defined(__aarch64__) |
| // Reads the system time counter (CNTVCT) and its frequency (CNTFRQ) |
| // CNTVCT is used in the sensors HAL for time synchronization. |
| // More information can be found in the ARM reference manual |
| // (http://infocenter.arm.com/help/index.jsp?topic= |
| // /com.arm.doc.100048_0002_05_en/jfa1406793266982.html) |
| // Use uint64_t to store since the MRS instruction uses 64 bit (X) registers |
| // (http://infocenter.arm.com/help/topic/ |
| // com.arm.doc.den0024a/ch06s05s02.html) |
| uint64_t qTimerCount = 0, qTimerFreq = 0; |
| uint64_t hostTimeNano = elapsedRealtimeNano(); |
| asm volatile("mrs %0, cntvct_el0" : "=r"(qTimerCount)); |
| asm volatile("mrs %0, cntfrq_el0" : "=r"(qTimerFreq)); |
| |
| constexpr uint64_t kOneSecondInNanoseconds = 1000000000; |
| if (qTimerFreq != 0) { |
| // Get the seconds part first, then convert the remainder to prevent |
| // overflow |
| uint64_t qTimerNanos = (qTimerCount / qTimerFreq); |
| if (qTimerNanos > UINT64_MAX / kOneSecondInNanoseconds) { |
| LOGE( |
| "CNTVCT_EL0 conversion to nanoseconds overflowed during time sync." |
| " Aborting time sync."); |
| *success = false; |
| } else { |
| qTimerNanos *= kOneSecondInNanoseconds; |
| |
| // Round the remainder portion to the nearest nanosecond |
| uint64_t remainder = (qTimerCount % qTimerFreq); |
| qTimerNanos += |
| (remainder * kOneSecondInNanoseconds + qTimerFreq / 2) / qTimerFreq; |
| |
| timeOffset = hostTimeNano - qTimerNanos; |
| *success = true; |
| } |
| } else { |
| LOGE("CNTFRQ_EL0 had 0 value. Aborting time sync."); |
| *success = false; |
| } |
| #else |
| #error "Unsupported CPU architecture type" |
| #endif |
| |
| return timeOffset; |
| } |
| |
| /** |
| * @param logOnError If true, logs an error message on failure. |
| * |
| * @return true if the time sync message was successfully sent to CHRE. |
| */ |
| static bool sendTimeSyncMessage(bool logOnError) { |
| bool timeSyncSuccess = true; |
| int64_t timeOffset = getTimeOffset(&timeSyncSuccess); |
| |
| if (timeSyncSuccess) { |
| flatbuffers::FlatBufferBuilder builder(64); |
| HostProtocolHost::encodeTimeSyncMessage(builder, timeOffset); |
| int success = chre_slpi_deliver_message_from_host( |
| static_cast<const unsigned char *>(builder.GetBufferPointer()), |
| static_cast<int>(builder.GetSize())); |
| |
| if (success != 0) { |
| if (logOnError) { |
| LOGE("Failed to deliver time sync message from host to CHRE: %d", |
| success); |
| } |
| timeSyncSuccess = false; |
| } |
| } |
| |
| return timeSyncSuccess; |
| } |
| |
| /** |
| * Sends a time sync message to CHRE, retrying a specified time until success. |
| * |
| * @param maxNumRetries The number of times to retry sending the message |
| * |
| * @return true if the time sync message was successfully sent to CHRE. |
| */ |
| static bool sendTimeSyncMessageRetry(size_t maxNumRetries) { |
| size_t numRetries = 0; |
| useconds_t retryDelayUs = 50000; // 50 ms initially |
| bool success = sendTimeSyncMessage(numRetries == maxNumRetries); |
| while (!success && numRetries < maxNumRetries) { |
| usleep(retryDelayUs); |
| numRetries++; |
| retryDelayUs *= 2; |
| success = sendTimeSyncMessage(numRetries == maxNumRetries); |
| } |
| |
| return success; |
| } |
| |
| #ifdef CHRE_DAEMON_LPMA_ENABLED |
| |
| static void acquireWakeLock() { |
| if (acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakeLockName) != 0) { |
| LOGE("Failed to acquire wakelock"); |
| } |
| } |
| |
| static void releaseWakeLock() { |
| static bool initialRelease = true; |
| |
| // It's expected to get an error when we first try to release the wakelock |
| // as it won't exist unless it was leaked previously - don't output a |
| // false warning for this case |
| if (release_wake_lock(kWakeLockName) != 0 && !initialRelease) { |
| LOGE("Failed to release wakelock"); |
| } |
| |
| initialRelease = false; |
| } |
| |
| /** |
| * Sets the target state for LPMA to be enabled. This triggers another thread to |
| * perform the async operation of enabling or disabling the LPMA use case. |
| * |
| * @param enabled Whether LPMA is to be enabled or disabled. |
| */ |
| static void setLpmaState(bool enabled) { |
| pthread_mutex_lock(&lpmaEnableThread.mutex); |
| lpmaEnableThread.targetLpmaEnabled = enabled; |
| pthread_mutex_unlock(&lpmaEnableThread.mutex); |
| pthread_cond_signal(&lpmaEnableThread.cond); |
| } |
| |
| static void onStHalServiceDeath() { |
| pthread_mutex_lock(&lpmaEnableThread.mutex); |
| lpmaEnableThread.connectedToService = false; |
| if (lpmaEnableThread.targetLpmaEnabled) { |
| // ST HAL has died, so assume that the sound model is no longer active, |
| // and trigger a reload of the sound model. |
| lpmaEnableThread.currentLpmaEnabled = false; |
| pthread_cond_signal(&lpmaEnableThread.cond); |
| } |
| pthread_mutex_unlock(&lpmaEnableThread.mutex); |
| } |
| |
| /** |
| * Connects to the ST HAL service, if not already. This method should only |
| * be invoked after acquiring the lpmaEnableThread.mutex lock. |
| * |
| * @return true if successfully connected to the HAL. |
| */ |
| static bool connectToStHalServiceLocked() { |
| if (!lpmaEnableThread.connectedToService) { |
| lpmaEnableThread.stHalService = ISoundTriggerHw::getService(); |
| if (lpmaEnableThread.stHalService != nullptr) { |
| LOGI("Connected to ST HAL service"); |
| lpmaEnableThread.connectedToService = true; |
| lpmaEnableThread.stHalService->linkToDeath( |
| lpmaEnableThread.deathRecipient, 0 /* flags */); |
| } |
| } |
| |
| return lpmaEnableThread.connectedToService; |
| } |
| |
| /** |
| * Loads the LPMA use case via the SoundTrigger HAL HIDL service. |
| * |
| * @param lpmaHandle The handle that was generated as a result of enabling |
| * the LPMA use case successfully. |
| * @return true if LPMA was enabled successfully, false otherwise. |
| */ |
| static bool loadLpma(SoundModelHandle *lpmaHandle) { |
| LOGD("Loading LPMA"); |
| |
| ISoundTriggerHw::SoundModel soundModel; |
| soundModel.type = SoundModelType::GENERIC; |
| soundModel.vendorUuid.timeLow = 0x57CADDB1; |
| soundModel.vendorUuid.timeMid = 0xACDB; |
| soundModel.vendorUuid.versionAndTimeHigh = 0x4DCE; |
| soundModel.vendorUuid.variantAndClockSeqHigh = 0x8CB0; |
| |
| const uint8_t uuidNode[6] = {0x2E, 0x95, 0xA2, 0x31, 0x3A, 0xEE}; |
| memcpy(&soundModel.vendorUuid.node[0], uuidNode, sizeof(uuidNode)); |
| soundModel.data.resize(1); // Insert a dummy byte to bypass HAL NULL checks. |
| |
| bool loaded = false; |
| if (!connectToStHalServiceLocked()) { |
| LOGE("Failed to get ST HAL service for LPMA load"); |
| } else { |
| int32_t loadResult; |
| Return<void> hidlResult = lpmaEnableThread.stHalService->loadSoundModel( |
| soundModel, NULL /* callback */, 0 /* cookie */, |
| [&](int32_t retval, SoundModelHandle handle) { |
| loadResult = retval; |
| *lpmaHandle = handle; |
| }); |
| |
| if (hidlResult.isOk()) { |
| if (loadResult == 0) { |
| LOGI("Loaded LPMA"); |
| loaded = true; |
| } else { |
| LOGE("Failed to load LPMA with %" PRId32, loadResult); |
| } |
| } else { |
| LOGE("Failed to load LPMA due to hidl error %s", |
| hidlResult.description().c_str()); |
| } |
| } |
| |
| return loaded; |
| } |
| |
| /** |
| * Unloads the LPMA use case via the SoundTrigger HAL HIDL service. This |
| * function does not indicate success/failure as it is expected that even in the |
| * event of a failure to unload, the use case will be unloaded. As long as the |
| * sound trigger HAL received the request we can be assured that the use case |
| * will be unloaded (even if it means reseting the codec or otherwise). |
| * |
| * @param lpmaHandle A handle that was previously produced by the setLpmaEnabled |
| * function. This is the handle that is unloaded from the ST HAL to |
| * disable LPMA. |
| */ |
| static void unloadLpma(SoundModelHandle lpmaHandle) { |
| LOGD("Unloading LPMA"); |
| |
| if (!connectToStHalServiceLocked()) { |
| LOGE("Failed to get ST HAL service for LPMA unload"); |
| } else { |
| Return<int32_t> hidlResult = |
| lpmaEnableThread.stHalService->unloadSoundModel(lpmaHandle); |
| |
| if (hidlResult.isOk()) { |
| if (hidlResult == 0) { |
| LOGI("Unloaded LPMA"); |
| } else { |
| LOGE("Failed to unload LPMA with %" PRId32, int32_t(hidlResult)); |
| } |
| } else { |
| LOGE("Failed to unload LPMA due to hidl error %s", |
| hidlResult.description().c_str()); |
| } |
| } |
| } |
| |
| static void *chreLpmaEnableThread(void *arg) { |
| auto *state = static_cast<LpmaEnableThreadData *>(arg); |
| |
| const useconds_t kInitialRetryDelayUs = 500000; |
| const int kRetryGrowthFactor = 2; |
| const int kRetryGrowthLimit = 5; // Terminates at 8s retry interval. |
| const int kRetryWakeLockLimit = 10; // Retry with a wakelock 10 times. |
| |
| int retryCount = 0; |
| useconds_t retryDelay = 0; |
| SoundModelHandle lpmaHandle; |
| |
| while (true) { |
| pthread_mutex_lock(&state->mutex); |
| if (state->currentLpmaEnabled == state->targetLpmaEnabled) { |
| retryCount = 0; |
| retryDelay = 0; |
| releaseWakeLock(); // Allow the system to suspend while waiting. |
| pthread_cond_wait(&state->cond, &state->mutex); |
| acquireWakeLock(); // Ensure the system stays up while retrying. |
| } else if (state->targetLpmaEnabled && loadLpma(&lpmaHandle)) { |
| state->currentLpmaEnabled = state->targetLpmaEnabled; |
| } else if (!state->targetLpmaEnabled) { |
| // Regardless of whether the use case fails to unload, set the |
| // currentLpmaEnabled to the targetLpmaEnabled. This will allow the next |
| // enable request to proceed. After a failure to unload occurs, the |
| // supplied handle is invalid and should not be unloaded again. |
| unloadLpma(lpmaHandle); |
| state->currentLpmaEnabled = state->targetLpmaEnabled; |
| } else { |
| // Unlock while delaying to avoid blocking the client thread. No shared |
| // state is modified here. |
| pthread_mutex_unlock(&state->mutex); |
| |
| if (retryDelay == 0) { |
| retryDelay = kInitialRetryDelayUs; |
| } else if (retryCount < kRetryGrowthLimit) { |
| retryDelay *= kRetryGrowthFactor; |
| } |
| |
| LOGD("Delaying retry %d for %uus", retryCount, retryDelay); |
| usleep(retryDelay); |
| |
| retryCount++; |
| if (retryCount > kRetryWakeLockLimit) { |
| releaseWakeLock(); |
| } |
| |
| pthread_mutex_lock(&state->mutex); |
| } |
| |
| pthread_mutex_unlock(&state->mutex); |
| } |
| |
| LOGV("LPMA enable thread exited"); |
| return NULL; |
| } |
| |
| /** |
| * Initializes the data shared with the LPMA enable thread and starts the |
| * thread. |
| * |
| * @param data Pointer to structure containing the (uninitialized) condition |
| * variable and associated data passed to the LPMA enable thread. |
| * @return true on success, false otherwise. |
| */ |
| static bool initLpmaEnableThread(LpmaEnableThreadData *data) { |
| bool success = false; |
| int ret; |
| |
| if ((ret = pthread_mutex_init(&data->mutex, NULL)) != 0) { |
| LOG_ERROR("Failed to initialize lpma enable mutex", ret); |
| } else if ((ret = pthread_cond_init(&data->cond, NULL)) != 0) { |
| LOG_ERROR("Failed to initialize lpma enable condition variable", ret); |
| } else if (!start_thread(&data->thread, chreLpmaEnableThread, data)) { |
| LOGE("Couldn't start lpma enable thread"); |
| } else { |
| data->currentLpmaEnabled = false; |
| data->targetLpmaEnabled = false; |
| success = true; |
| } |
| |
| return success; |
| } |
| |
| #endif // CHRE_DAEMON_LPMA_ENABLED |
| |
| /** |
| * Sends a message to CHRE. |
| * |
| * @param clientId The client ID that this message originates from. |
| * @param data The data to pass down. |
| * @param length The size of the data to send. |
| * @return true if successful, false otherwise. |
| */ |
| static bool sendMessageToChre(uint16_t clientId, void *data, size_t length) { |
| constexpr size_t kMaxPayloadSize = 1024 * 1024; // 1 MiB |
| |
| // This limitation is due to FastRPC, but there's no case where we should come |
| // close to this limit... |
| static_assert(kMaxPayloadSize <= INT32_MAX, |
| "DSP uses 32-bit signed integers to represent message size"); |
| |
| bool success = false; |
| if (length > kMaxPayloadSize) { |
| LOGE("Message too large (got %zu, max %zu bytes)", length, kMaxPayloadSize); |
| } else if (!HostProtocolHost::mutateHostClientId(data, length, clientId)) { |
| LOGE("Couldn't set host client ID in message container!"); |
| } else { |
| LOGV("Delivering message from host (size %zu)", length); |
| log_buffer(static_cast<const uint8_t *>(data), length); |
| int ret = chre_slpi_deliver_message_from_host( |
| static_cast<const unsigned char *>(data), static_cast<int>(length)); |
| if (ret != 0) { |
| LOGE("Failed to deliver message from host to CHRE: %d", ret); |
| } else { |
| success = true; |
| } |
| } |
| |
| return success; |
| } |
| |
| /** |
| * Loads a nanoapp by sending the nanoapp filename to the CHRE framework. This |
| * method will return after sending the request so no guarantee is made that |
| * the nanoapp is loaded until after the response is received. |
| * |
| * @param appId The ID of the nanoapp to load. |
| * @param appVersion The version of the nanoapp to load. |
| * @param appTargetApiVersion The version of the CHRE API that the app targets. |
| * @param appBinaryName The name of the binary as stored in the filesystem. This |
| * will be used to load the nanoapp into CHRE. |
| * @param transactionId The transaction ID to use when loading. |
| * @return true if a request was successfully sent, false otherwise. |
| */ |
| static bool sendNanoappLoad(uint64_t appId, uint32_t appVersion, |
| uint32_t appTargetApiVersion, |
| const std::string &appBinaryName, |
| uint32_t transactionId) { |
| flatbuffers::FlatBufferBuilder builder; |
| HostProtocolHost::encodeLoadNanoappRequestForFile( |
| builder, transactionId, appId, appVersion, appTargetApiVersion, |
| appBinaryName.c_str()); |
| |
| bool success = sendMessageToChre( |
| kHostClientIdDaemon, builder.GetBufferPointer(), builder.GetSize()); |
| |
| if (!success) { |
| LOGE("Failed to send nanoapp filename."); |
| } else { |
| gPreloadedNanoappPendingTransactionIds.push(transactionId); |
| } |
| |
| return success; |
| } |
| |
| /** |
| * Sends a preloaded nanoapp filename / metadata to CHRE. |
| * |
| * @param header The nanoapp header binary blob. |
| * @param nanoappName The filename of the nanoapp to be loaded. |
| * @param transactionId The transaction ID to use when loading the app. |
| * @return true if successful, false otherwise. |
| */ |
| static bool loadNanoapp(const std::vector<uint8_t> &header, |
| const std::string &nanoappName, |
| 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 = sendNanoappLoad(appHeader->appId, appHeader->appVersion, |
| targetApiVersion, nanoappName, transactionId); |
| } |
| |
| return success; |
| } |
| |
| /** |
| * Loads the supplied file into the provided buffer. |
| * |
| * @param filename The name of the file to load. |
| * @param buffer The buffer to load into. |
| * @return true if successful, false otherwise. |
| */ |
| static bool readFileContents(const char *filename, |
| std::vector<uint8_t> *buffer) { |
| bool success = false; |
| std::ifstream file(filename, std::ios::binary | std::ios::ate); |
| if (!file) { |
| LOGE("Couldn't open file '%s': %d (%s)", filename, errno, strerror(errno)); |
| } else { |
| ssize_t size = file.tellg(); |
| file.seekg(0, std::ios::beg); |
| |
| buffer->resize(size); |
| if (!file.read(reinterpret_cast<char *>(buffer->data()), size)) { |
| LOGE("Couldn't read from file '%s': %d (%s)", filename, errno, |
| strerror(errno)); |
| } else { |
| success = true; |
| } |
| } |
| |
| return success; |
| } |
| |
| /** |
| * Loads a preloaded nanoapp given a filename to load from. Allows the |
| * transaction to complete before the nanoapp starts so the server can start |
| * serving requests as soon as possible. |
| * |
| * @param directory The directory to load the nanoapp from. |
| * @param name The filename of the nanoapp to load. |
| * @param transactionId The transaction ID to use when loading the app. |
| */ |
| static void loadPreloadedNanoapp(const std::string &directory, |
| const std::string &name, |
| uint32_t transactionId) { |
| std::vector<uint8_t> headerBuffer; |
| |
| std::string headerFile = directory + "/" + name + ".napp_header"; |
| |
| // Only create the nanoapp filename as the CHRE framework will load from |
| // within the directory its own binary resides in. |
| std::string nanoappFilename = name + ".so"; |
| |
| if (readFileContents(headerFile.c_str(), &headerBuffer) && |
| !loadNanoapp(headerBuffer, nanoappFilename, transactionId)) { |
| LOGE("Failed to load nanoapp: '%s'", name.c_str()); |
| } |
| } |
| |
| /** |
| * Attempts to load all preloaded nanoapps from a config file. The config file |
| * is expected to be valid JSON with the following structure: |
| * |
| * { "nanoapps": [ |
| * "/path/to/nanoapp_1", |
| * "/path/to/nanoapp_2" |
| * ]} |
| * |
| * The napp_header and so files will both be loaded. All errors are logged. |
| */ |
| static void loadPreloadedNanoapps() { |
| constexpr char kPreloadedNanoappsConfigPath[] = |
| "/vendor/etc/chre/preloaded_nanoapps.json"; |
| std::ifstream configFileStream(kPreloadedNanoappsConfigPath); |
| |
| Json::Reader reader; |
| Json::Value config; |
| if (!configFileStream) { |
| LOGE("Failed to open config file '%s': %d (%s)", |
| kPreloadedNanoappsConfigPath, errno, strerror(errno)); |
| } else if (!reader.parse(configFileStream, config)) { |
| LOGE("Failed to parse nanoapp config file"); |
| } else if (!config.isMember("nanoapps") || !config.isMember("source_dir")) { |
| LOGE("Malformed preloaded nanoapps config"); |
| } else { |
| const Json::Value &directory = config["source_dir"]; |
| for (Json::ArrayIndex i = 0; i < config["nanoapps"].size(); i++) { |
| const Json::Value &nanoapp = config["nanoapps"][i]; |
| loadPreloadedNanoapp(directory.asString(), nanoapp.asString(), |
| static_cast<uint32_t>(i)); |
| } |
| } |
| } |
| |
| /** |
| * Handles a message that is directed towards the daemon. |
| * |
| * @param message The message sent to the daemon. |
| */ |
| static void 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(); |
| if (gPreloadedNanoappPendingTransactionIds.empty()) { |
| LOGE("Received nanoapp load response with no pending load"); |
| } else if (gPreloadedNanoappPendingTransactionIds.front() != |
| response->transaction_id) { |
| LOGE("Received nanoapp load response with ID %" PRIu32 |
| " expected transaction id %" PRIu32, |
| response->transaction_id, |
| gPreloadedNanoappPendingTransactionIds.front()); |
| } else { |
| if (!response->success) { |
| LOGE("Received unsuccessful nanoapp load response with ID %" PRIu32, |
| gPreloadedNanoappPendingTransactionIds.front()); |
| } |
| gPreloadedNanoappPendingTransactionIds.pop(); |
| } |
| } |
| } |
| |
| #ifdef CHRE_USE_TOKENIZED_LOGGING |
| /** |
| * Initialize the Log Detokenizer |
| * |
| * The log detokenizer reads a binary database file that contains key value |
| * pairs of hash-keys <--> Decoded log messages, and creates an instance |
| * of the Detokenizer. |
| * |
| * @return an instance of the Detokenizer |
| */ |
| static std::unique_ptr<Detokenizer> logDetokenizerInit() { |
| constexpr const char kLogDatabaseFilePath[] = |
| "/vendor/etc/chre/libchre_log_database.bin"; |
| std::vector<uint8_t> tokenData; |
| if (readFileContents(kLogDatabaseFilePath, &tokenData)) { |
| pw::tokenizer::TokenDatabase database = |
| pw::tokenizer::TokenDatabase::Create(tokenData); |
| if (database.ok()) { |
| return std::make_unique<Detokenizer>(database); |
| } else { |
| LOGE("CHRE Token database creation not OK"); |
| } |
| } else { |
| LOGE("Failed to read CHRE Token database file"); |
| } |
| return std::unique_ptr<Detokenizer>(nullptr); |
| } |
| #endif |
| |
| /** |
| * Entry point for the thread that receives messages sent by CHRE. |
| * |
| * @return always returns NULL |
| */ |
| static void *chre_message_to_host_thread(void *arg) { |
| unsigned char messageBuffer[4096]; |
| unsigned int messageLen; |
| int result = 0; |
| auto *server = static_cast<::android::chre::SocketServer *>(arg); |
| |
| #ifdef CHRE_USE_TOKENIZED_LOGGING |
| std::unique_ptr<Detokenizer> detokenizer = logDetokenizerInit(); |
| #endif |
| |
| while (true) { |
| messageLen = 0; |
| LOGV("Calling into chre_slpi_get_message_to_host"); |
| result = chre_slpi_get_message_to_host(messageBuffer, sizeof(messageBuffer), |
| &messageLen); |
| LOGV("Got message from CHRE with size %u (result %d)", messageLen, result); |
| |
| if (result == CHRE_FASTRPC_ERROR_SHUTTING_DOWN) { |
| LOGD("CHRE shutting down, exiting CHRE->Host message thread"); |
| break; |
| } else if (result == CHRE_FASTRPC_SUCCESS && messageLen > 0) { |
| log_buffer(messageBuffer, messageLen); |
| uint16_t hostClientId; |
| fbs::ChreMessage messageType; |
| if (!HostProtocolHost::extractHostClientIdAndType( |
| messageBuffer, messageLen, &hostClientId, &messageType)) { |
| LOGW( |
| "Failed to extract host client ID from message - sending " |
| "broadcast"); |
| hostClientId = chre::kHostClientIdUnspecified; |
| } |
| |
| if (messageType == fbs::ChreMessage::LogMessage) { |
| // Log messages are routed through ashLog if tokenized logging |
| // is disabled, so only parse tokenized log messages here. |
| #ifdef CHRE_USE_TOKENIZED_LOGGING |
| parseAndEmitTokenizedLogMessages(messageBuffer, messageLen, |
| detokenizer.get()); |
| #endif |
| } else if (messageType == fbs::ChreMessage::TimeSyncRequest) { |
| sendTimeSyncMessage(true /* logOnError */); |
| #ifdef CHRE_DAEMON_LPMA_ENABLED |
| } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRequest) { |
| setLpmaState(true); |
| } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRelease) { |
| setLpmaState(false); |
| #endif // CHRE_DAEMON_LPMA_ENABLED |
| } else if (hostClientId == kHostClientIdDaemon) { |
| handleDaemonMessage(messageBuffer); |
| } else if (hostClientId == chre::kHostClientIdUnspecified) { |
| server->sendToAllClients(messageBuffer, |
| static_cast<size_t>(messageLen)); |
| } else { |
| server->sendToClientById(messageBuffer, static_cast<size_t>(messageLen), |
| hostClientId); |
| } |
| } else if (!chre_shutdown_requested) { |
| LOGE( |
| "Received an unknown result (%d) and no shutdown was requested. " |
| "Quitting", |
| result); |
| exit(-1); |
| } else { |
| // Received an unknown result but a shutdown was requested. Break from the |
| // loop to allow the daemon to cleanup. |
| break; |
| } |
| } |
| |
| LOGV("Message to host thread exited"); |
| return NULL; |
| } |
| |
| /** |
| * Entry point for the thread that blocks in a FastRPC call to monitor for |
| * abnormal exit of CHRE or reboot of the DSP. |
| * |
| * @return always returns NULL |
| */ |
| static void *chre_monitor_thread(void *arg) { |
| (void)arg; |
| int ret = chre_slpi_wait_on_thread_exit(); |
| if (!chre_shutdown_requested) { |
| LOGE("Detected unexpected CHRE thread exit (%d)\n", ret); |
| exit(EXIT_FAILURE); |
| } |
| |
| LOGV("Monitor thread exited"); |
| return NULL; |
| } |
| |
| /** |
| * Start a thread with default attributes, or log an error on failure |
| * |
| * @return bool true if the thread was successfully started |
| */ |
| static bool start_thread(pthread_t *thread_handle, |
| thread_entry_point_f *thread_entry, void *arg) { |
| int ret = pthread_create(thread_handle, NULL, thread_entry, arg); |
| if (ret != 0) { |
| LOG_ERROR("pthread_create failed", ret); |
| } |
| return (ret == 0); |
| } |
| |
| namespace { |
| |
| void onMessageReceivedFromClient(uint16_t clientId, void *data, size_t length) { |
| sendMessageToChre(clientId, data, length); |
| } |
| |
| } // anonymous namespace |
| |
| int main() { |
| int ret = -1; |
| pthread_t monitor_thread; |
| pthread_t msg_to_host_thread; |
| |
| ::android::chre::SocketServer server; |
| |
| #ifdef CHRE_DAEMON_LOAD_INTO_SENSORSPD |
| remote_handle remote_handle_fd = 0xFFFFFFFF; |
| if (remote_handle_open(ITRANSPORT_PREFIX "createstaticpd:sensorspd", |
| &remote_handle_fd)) { |
| LOGE("Failed to open remote handle for sensorspd"); |
| } else { |
| LOGV("Successfully opened remote handle for sensorspd"); |
| } |
| #endif // CHRE_DAEMON_LOAD_INTO_SENSORSPD |
| |
| // Send time sync message before nanoapps start, retrying a few times |
| // in case the SLPI is not ready yet. This retry logic must be placed before |
| // any of the other FastRPC method invocations. |
| constexpr size_t kMaxNumRetries = 5; |
| if (!sendTimeSyncMessageRetry(kMaxNumRetries)) { |
| LOGE("Failed to send initial time sync message"); |
| // Reverse monitor invokes a FastRPC method to allow the code running in |
| // CHRE to detect abnormal shutdown of the host-side daemon and perform a |
| // gracefull cleanup. |
| // ToDo: consolidate the chre_slpi_initialize_reverse_monitor() logic into |
| // an always-running daemon thread to eliminate the FastRPC call and save |
| // space in the CHRE framework. |
| } else if ((ret = chre_slpi_initialize_reverse_monitor()) != |
| CHRE_FASTRPC_SUCCESS) { |
| LOGE("Failed to initialize reverse monitor: %d", ret); |
| #ifdef CHRE_DAEMON_LPMA_ENABLED |
| } else if (!initLpmaEnableThread(&lpmaEnableThread)) { |
| LOGE("Couldn't initialize LPMA enable thread"); |
| #endif // CHRE_DAEMON_LPMA_ENABLED |
| } else { |
| if ((ret = chre_slpi_start_thread()) != CHRE_FASTRPC_SUCCESS) { |
| LOGE("Failed to start CHRE: %d", ret); |
| } else { |
| if (!start_thread(&monitor_thread, chre_monitor_thread, NULL)) { |
| LOGE("Couldn't start monitor thread"); |
| } else if (!start_thread(&msg_to_host_thread, chre_message_to_host_thread, |
| &server)) { |
| LOGE("Couldn't start CHRE->Host message thread"); |
| } else { |
| LOGI("CHRE started"); |
| loadPreloadedNanoapps(); |
| |
| // TODO: take 2nd argument as command-line parameter |
| server.run("chre", true, onMessageReceivedFromClient); |
| } |
| |
| chre_shutdown_requested = true; |
| ret = chre_slpi_stop_thread(); |
| if (ret != CHRE_FASTRPC_SUCCESS) { |
| LOGE("Failed to stop CHRE: %d", ret); |
| } else { |
| // TODO: don't call pthread_join if the thread failed to start |
| LOGV("Joining monitor thread"); |
| ret = pthread_join(monitor_thread, NULL); |
| if (ret != 0) { |
| LOG_ERROR("Join on monitor thread failed", ret); |
| } |
| |
| LOGV("Joining message to host thread"); |
| ret = pthread_join(msg_to_host_thread, NULL); |
| if (ret != 0) { |
| LOG_ERROR("Join on monitor thread failed", ret); |
| } |
| |
| LOGI("Shutdown complete"); |
| } |
| } |
| } |
| |
| return ret; |
| } |