普通文本  |  438行  |  15.03 KB

/*
 * 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.
 */

#include <inttypes.h>
#include <limits.h>

#include "qurt.h"

#include "chre/core/event_loop_manager.h"
#include "chre/core/host_comms_manager.h"
#include "chre/platform/context.h"
#include "chre/platform/memory.h"
#include "chre/platform/log.h"
#include "chre/platform/shared/host_protocol_chre.h"
#include "chre/platform/slpi/fastrpc.h"
#include "chre/util/fixed_size_blocking_queue.h"
#include "chre/util/macros.h"
#include "chre/util/unique_ptr.h"
#include "chre_api/chre/version.h"

using flatbuffers::FlatBufferBuilder;

namespace chre {

namespace {

constexpr size_t kOutboundQueueSize = 32;

//! Used to pass the client ID through the user data pointer in deferCallback
union HostClientIdCallbackData {
  uint16_t hostClientId;
  void *ptr;
};
static_assert(sizeof(uint16_t) <= sizeof(void*),
              "Pointer must at least fit a u16 for passing the host client ID");

struct LoadNanoappCallbackData {
  uint64_t appId;
  uint32_t transactionId;
  uint16_t hostClientId;
  UniquePtr<Nanoapp> nanoapp = MakeUnique<Nanoapp>();
};

enum class PendingMessageType {
  Shutdown,
  NanoappMessageToHost,
  HubInfoResponse,
  NanoappListResponse,
  LoadNanoappResponse,
};

struct PendingMessage {
  PendingMessage(PendingMessageType msgType, uint16_t hostClientId) {
    type = msgType;
    data.hostClientId = hostClientId;
  }

  PendingMessage(PendingMessageType msgType,
                 const MessageToHost *msgToHost = nullptr) {
    type = msgType;
    data.msgToHost = msgToHost;
  }

  PendingMessage(PendingMessageType msgType, FlatBufferBuilder *builder) {
    type = msgType;
    data.builder = builder;
  }

  PendingMessageType type;
  union {
    const MessageToHost *msgToHost;
    uint16_t hostClientId;
    FlatBufferBuilder *builder;
  } data;
};

FixedSizeBlockingQueue<PendingMessage, kOutboundQueueSize>
    gOutboundQueue;

int copyToHostBuffer(const FlatBufferBuilder& builder, unsigned char *buffer,
                     size_t bufferSize, unsigned int *messageLen) {
  uint8_t *data = builder.GetBufferPointer();
  size_t size = builder.GetSize();
  int result;

  if (size > bufferSize) {
    LOGE("Encoded structure size %zu too big for host buffer %zu; dropping",
         size, bufferSize);
    result = CHRE_FASTRPC_ERROR;
  } else {
    memcpy(buffer, data, size);
    *messageLen = size;
    result = CHRE_FASTRPC_SUCCESS;
  }

  return result;
}

void constructNanoappListCallback(uint16_t /*eventType*/, void *deferCbData) {
  constexpr size_t kFixedOverhead = 56;
  constexpr size_t kPerNanoappSize = 16;

  HostClientIdCallbackData clientIdCbData;
  clientIdCbData.ptr = deferCbData;

  // TODO: need to add support for getting apps from multiple event loops
  bool pushed = false;
  EventLoop *eventLoop = getCurrentEventLoop();
  size_t expectedNanoappCount = eventLoop->getNanoappCount();
  DynamicVector<NanoappListEntryOffset> nanoappEntries;

  auto *builder = memoryAlloc<FlatBufferBuilder>(
      kFixedOverhead + expectedNanoappCount * kPerNanoappSize);
  if (builder == nullptr) {
    LOGE("Couldn't allocate builder for nanoapp list response");
  } else if (!nanoappEntries.reserve(expectedNanoappCount)) {
    LOGE("Couldn't reserve space for list of nanoapp offsets");
  } else {
    struct CallbackData {
      CallbackData(FlatBufferBuilder& builder_,
                   DynamicVector<NanoappListEntryOffset>& nanoappEntries_)
          : builder(builder_), nanoappEntries(nanoappEntries_) {}

      FlatBufferBuilder& builder;
      DynamicVector<NanoappListEntryOffset>& nanoappEntries;
    };

    auto callback = [](const Nanoapp *nanoapp, void *untypedData) {
      auto *data = static_cast<CallbackData *>(untypedData);
      HostProtocolChre::addNanoappListEntry(
          data->builder, data->nanoappEntries, nanoapp->getAppId(),
          nanoapp->getAppVersion(), true /*enabled*/,
          nanoapp->isSystemNanoapp());
    };

    // Add a NanoappListEntry to the FlatBuffer for each nanoapp
    CallbackData cbData(*builder, nanoappEntries);
    eventLoop->forEachNanoapp(callback, &cbData);
    HostProtocolChre::finishNanoappListResponse(*builder, nanoappEntries,
                                                clientIdCbData.hostClientId);

    pushed = gOutboundQueue.push(
        PendingMessage(PendingMessageType::NanoappListResponse, builder));
    if (!pushed) {
      LOGE("Couldn't push list response");
    }
  }

  if (!pushed && builder != nullptr) {
    memoryFree(builder);
  }
}

void finishLoadingNanoappCallback(uint16_t /*eventType*/, void *data) {
  UniquePtr<LoadNanoappCallbackData> cbData(
      static_cast<LoadNanoappCallbackData *>(data));

  EventLoop *eventLoop = getCurrentEventLoop();
  bool startedSuccessfully = (cbData->nanoapp->isLoaded()) ?
      eventLoop->startNanoapp(cbData->nanoapp) : false;

  constexpr size_t kInitialBufferSize = 48;
  auto *builder = memoryAlloc<FlatBufferBuilder>(kInitialBufferSize);
  if (builder == nullptr) {
    LOGE("Couldn't allocate memory for load nanoapp response");
  } else {
    HostProtocolChre::encodeLoadNanoappResponse(
        *builder, cbData->hostClientId, cbData->transactionId,
        startedSuccessfully);

    // TODO: if this fails, ideally we should block for some timeout until
    // there's space in the queue (like up to 1 second)
    if (!gOutboundQueue.push(PendingMessage(
            PendingMessageType::LoadNanoappResponse, builder))) {
      LOGE("Couldn't push load nanoapp response to outbound queue");
      memoryFree(builder);
    }
  }
}

int generateMessageToHost(const MessageToHost *msgToHost, unsigned char *buffer,
                          size_t bufferSize, unsigned int *messageLen) {
  // TODO: ideally we'd construct our flatbuffer directly in the
  // host-supplied buffer
  constexpr size_t kFixedSizePortion = 56;
  FlatBufferBuilder builder(msgToHost->message.size() + kFixedSizePortion);
  HostProtocolChre::encodeNanoappMessage(
    builder, msgToHost->appId, msgToHost->toHostData.messageType,
    msgToHost->toHostData.hostEndpoint, msgToHost->message.data(),
    msgToHost->message.size());

  int result = copyToHostBuffer(builder, buffer, bufferSize, messageLen);

  auto& hostCommsManager =
      EventLoopManagerSingleton::get()->getHostCommsManager();
  hostCommsManager.onMessageToHostComplete(msgToHost);

  return result;
}

int generateHubInfoResponse(uint16_t hostClientId, unsigned char *buffer,
                            size_t bufferSize, unsigned int *messageLen) {
  constexpr size_t kInitialBufferSize = 192;

  constexpr char kHubName[] = "CHRE on SLPI";
  constexpr char kVendor[] = "Google";
  constexpr char kToolchain[] = "Hexagon Tools 8.0 (clang "
    STRINGIFY(__clang_major__) "."
    STRINGIFY(__clang_minor__) "."
    STRINGIFY(__clang_patchlevel__) ")";
  constexpr uint32_t kLegacyPlatformVersion = 0;
  constexpr uint32_t kLegacyToolchainVersion =
    ((__clang_major__ & 0xFF) << 24) |
    ((__clang_minor__ & 0xFF) << 16) |
    (__clang_patchlevel__ & 0xFFFF);
  constexpr float kPeakMips = 350;
  constexpr float kStoppedPower = 0;
  constexpr float kSleepPower = 1;
  constexpr float kPeakPower = 15;

  FlatBufferBuilder builder(kInitialBufferSize);
  HostProtocolChre::encodeHubInfoResponse(
      builder, kHubName, kVendor, kToolchain, kLegacyPlatformVersion,
      kLegacyToolchainVersion, kPeakMips, kStoppedPower, kSleepPower,
      kPeakPower, CHRE_MESSAGE_TO_HOST_MAX_SIZE, chreGetPlatformId(),
      chreGetVersion(), hostClientId);

  return copyToHostBuffer(builder, buffer, bufferSize, messageLen);
}

int generateMessageFromBuilder(
    FlatBufferBuilder *builder, unsigned char *buffer, size_t bufferSize,
    unsigned int *messageLen) {
  CHRE_ASSERT(builder != nullptr);
  int result = copyToHostBuffer(*builder, buffer, bufferSize, messageLen);
  memoryFree(builder);
  return result;
}

/**
 * FastRPC method invoked by the host to block on messages
 *
 * @param buffer Output buffer to populate with message data
 * @param bufferLen Size of the buffer, in bytes
 * @param messageLen Output parameter to populate with the size of the message
 *        in bytes upon success
 *
 * @return 0 on success, nonzero on failure
 */
extern "C" int chre_slpi_get_message_to_host(
    unsigned char *buffer, int bufferLen, unsigned int *messageLen) {
  CHRE_ASSERT(buffer != nullptr);
  CHRE_ASSERT(bufferLen > 0);
  CHRE_ASSERT(messageLen != nullptr);
  int result = CHRE_FASTRPC_ERROR;

  if (bufferLen <= 0 || buffer == nullptr || messageLen == nullptr) {
    // Note that we can't use regular logs here as they can result in sending
    // a message, leading to an infinite loop if the error is persistent
    FARF(FATAL, "Invalid buffer size %d or bad pointers (buf %d len %d)",
         bufferLen, (buffer == nullptr), (messageLen == nullptr));
  } else {
    size_t bufferSize = static_cast<size_t>(bufferLen);
    PendingMessage pendingMsg = gOutboundQueue.pop();

    switch (pendingMsg.type) {
      case PendingMessageType::Shutdown:
        result = CHRE_FASTRPC_ERROR_SHUTTING_DOWN;
        break;

      case PendingMessageType::NanoappMessageToHost:
        result = generateMessageToHost(pendingMsg.data.msgToHost, buffer,
                                       bufferSize, messageLen);
        break;

      case PendingMessageType::HubInfoResponse:
        result = generateHubInfoResponse(pendingMsg.data.hostClientId, buffer,
                                         bufferSize, messageLen);
        break;

      case PendingMessageType::NanoappListResponse:
      case PendingMessageType::LoadNanoappResponse:
        result = generateMessageFromBuilder(pendingMsg.data.builder,
                                            buffer, bufferSize, messageLen);
        break;

      default:
        CHRE_ASSERT_LOG(false, "Unexpected pending message type");
    }
  }

  LOGD("Returning message to host (result %d length %u)", result, *messageLen);
  return result;
}

/**
 * FastRPC method invoked by the host to send a message to the system
 *
 * @param buffer
 * @param size
 *
 * @return 0 on success, nonzero on failure
 */
extern "C" int chre_slpi_deliver_message_from_host(const unsigned char *message,
                                                   int messageLen) {
  CHRE_ASSERT(message != nullptr);
  CHRE_ASSERT(messageLen > 0);
  int result = CHRE_FASTRPC_ERROR;

  if (message == nullptr || messageLen <= 0) {
    LOGE("Got null or invalid size (%d) message from host", messageLen);
  } else if (!HostProtocolChre::decodeMessageFromHost(
      message, static_cast<size_t>(messageLen))) {
    LOGE("Failed to decode/handle message");
  } else {
    result = CHRE_FASTRPC_SUCCESS;
  }

  return result;
}

}  // anonymous namespace

bool HostLink::sendMessage(const MessageToHost *message) {
  return gOutboundQueue.push(
      PendingMessage(PendingMessageType::NanoappMessageToHost, message));
}

void HostLinkBase::shutdown() {
  constexpr qurt_timer_duration_t kPollingIntervalUsec = 5000;

  // Push a null message so the blocking call in chre_slpi_get_message_to_host()
  // returns and the host can exit cleanly. If the queue is full, try again to
  // avoid getting stuck (no other new messages should be entering the queue at
  // this time). Don't wait too long as the host-side binary may have died in
  // a state where it's not blocked in chre_slpi_get_message_to_host().
  int retryCount = 5;
  FARF(MEDIUM, "Shutting down host link");
  while (!gOutboundQueue.push(PendingMessage(PendingMessageType::Shutdown))
         && --retryCount > 0) {
    qurt_timer_sleep(kPollingIntervalUsec);
  }

  if (retryCount <= 0) {
    // Don't use LOGE, as it may involve trying to send a message
    FARF(ERROR, "No room in outbound queue for shutdown message and host not "
         "draining queue!");
  } else {
    FARF(MEDIUM, "Draining message queue");

    // We were able to push the shutdown message. Wait for the queue to
    // completely flush before returning.
    int waitCount = 5;
    while (!gOutboundQueue.empty() && --waitCount > 0) {
      qurt_timer_sleep(kPollingIntervalUsec);
    }

    if (waitCount <= 0) {
      FARF(ERROR, "Host took too long to drain outbound queue; exiting anyway");
    } else {
      FARF(MEDIUM, "Finished draining queue");
    }
  }
}

void HostMessageHandlers::handleNanoappMessage(
    uint64_t appId, uint32_t messageType, uint16_t hostEndpoint,
    const void *messageData, size_t messageDataLen) {
  LOGD("Parsed nanoapp message from host: app ID 0x%016" PRIx64 ", endpoint "
       "0x%" PRIx16 ", msgType %" PRIu32 ", payload size %zu",
       appId, hostEndpoint, messageType, messageDataLen);

  HostCommsManager& manager =
      EventLoopManagerSingleton::get()->getHostCommsManager();
  manager.sendMessageToNanoappFromHost(
      appId, messageType, hostEndpoint, messageData, messageDataLen);
}

void HostMessageHandlers::handleHubInfoRequest(uint16_t hostClientId) {
  // We generate the response in the context of chre_slpi_get_message_to_host
  LOGD("Got hub info request from client ID %" PRIu16, hostClientId);
  gOutboundQueue.push(PendingMessage(
      PendingMessageType::HubInfoResponse, hostClientId));
}

void HostMessageHandlers::handleNanoappListRequest(uint16_t hostClientId) {
  LOGD("Got nanoapp list request from client ID %" PRIu16, hostClientId);
  HostClientIdCallbackData cbData = {};
  cbData.hostClientId = hostClientId;
  EventLoopManagerSingleton::get()->deferCallback(
      SystemCallbackType::NanoappListResponse, cbData.ptr,
      constructNanoappListCallback);
}

void HostMessageHandlers::handleLoadNanoappRequest(
    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
    uint32_t appVersion, uint32_t targetApiVersion, const void *appBinary,
    size_t appBinaryLen) {
  auto cbData = MakeUnique<LoadNanoappCallbackData>();

  LOGD("Got load nanoapp request (txnId %" PRIu32 ") for appId 0x%016" PRIx64
       " version 0x%" PRIx32 " target API version 0x%08" PRIx32 " size %zu",
       transactionId, appId, appVersion, targetApiVersion, appBinaryLen);
  if (cbData.isNull() || cbData->nanoapp.isNull()) {
    LOGE("Couldn't allocate load nanoapp callback data");
  } else {
    cbData->transactionId = transactionId;
    cbData->hostClientId  = hostClientId;
    cbData->appId = appId;

    // Note that if this fails, we'll generate the error response in
    // the normal deferred callback
    cbData->nanoapp->loadFromBuffer(appId, appVersion, appBinary, appBinaryLen);
    if (!EventLoopManagerSingleton::get()->deferCallback(
            SystemCallbackType::FinishLoadingNanoapp, cbData.get(),
            finishLoadingNanoappCallback)) {
      LOGE("Couldn't post callback to finish loading nanoapp");
    } else {
      cbData.release();
    }
  }
}

}  // namespace chre