/*
* Copyright (C) 2016 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.
*/
#define LOG_TAG "NanohubHAL"
#include <cassert>
#include <cerrno>
#include <cinttypes>
#include <endian.h>
#include <vector>
#include <utils/Log.h>
#include <endian.h>
#include <hardware/context_hub.h>
#include "nanohub_perdevice.h"
#include "system_comms.h"
#include "nanohubhal.h"
namespace android {
namespace nanohub {
static void readAppName(MessageBuf &buf, hub_app_name_t &name)
{
name.id = buf.readU64();
}
static void writeAppName(MessageBuf &buf, const hub_app_name_t &name)
{
buf.writeU64(name.id);
}
static void readNanohubAppInfo(MessageBuf &buf, NanohubAppInfo &info)
{
size_t pos = buf.getPos();
readAppName(buf, info.name);
info.version = buf.readU32();
info.flashUse = buf.readU32();
info.ramUse = buf.readU32();
if ((buf.getPos() - pos) != sizeof(info)) {
ALOGE("%s: failed to read object", __func__);
}
}
static void readNanohubMemInfo(MessageBuf &buf, NanohubMemInfo &mi)
{
size_t pos = buf.getPos();
mi.flashSz = buf.readU32();
mi.blSz = buf.readU32();
mi.osSz = buf.readU32();
mi.sharedSz = buf.readU32();
mi.eeSz = buf.readU32();
mi.ramSz = buf.readU32();
mi.blUse = buf.readU32();
mi.osUse = buf.readU32();
mi.sharedUse = buf.readU32();
mi.eeUse = buf.readU32();
mi.ramUse = buf.readU32();
if ((buf.getPos() - pos) != sizeof(mi)) {
ALOGE("%s: failed to read object", __func__);
}
}
NanohubRsp::NanohubRsp(MessageBuf &buf, bool no_status)
{
// all responses start with command
// most of them have 4-byte status (result code)
buf.reset();
cmd = buf.readU8();
if (!buf.getSize()) {
status = -EINVAL;
} else if (no_status) {
status = 0;
} else {
status = buf.readU32();
}
}
int SystemComm::sendToSystem(const void *data, size_t len)
{
if (NanoHub::messageTracingEnabled()) {
dumpBuffer("HAL -> SYS", getSystem()->mHostIfAppName, 0, data, len);
}
return NanoHub::sendToDevice(&getSystem()->mHostIfAppName, data, len);
}
int SystemComm::AppInfoSession::setup(const hub_message_t *)
{
std::lock_guard<std::mutex> _l(mLock);
int suggestedSize = mAppInfo.size() ? mAppInfo.size() : 20;
mAppInfo.clear();
mAppInfo.reserve(suggestedSize);
setState(SESSION_USER);
return requestNext();
}
inline hub_app_name_t deviceAppNameToHost(const hub_app_name_t src)
{
hub_app_name_t res = { .id = le64toh(src.id) };
return res;
}
inline hub_app_name_t hostAppNameToDevice(const hub_app_name_t src)
{
hub_app_name_t res = { .id = htole64(src.id) };
return res;
}
int SystemComm::AppInfoSession::handleRx(MessageBuf &buf)
{
std::lock_guard<std::mutex> _l(mLock);
NanohubRsp rsp(buf, true);
if (rsp.cmd != NANOHUB_QUERY_APPS) {
return 1;
}
size_t len = buf.getRoom();
if (len != sizeof(NanohubAppInfo) && len) {
ALOGE("%s: Invalid data size; have %zu, need %zu", __func__,
len, sizeof(NanohubAppInfo));
return -EINVAL;
}
if (getState() != SESSION_USER) {
ALOGE("%s: Invalid state; have %d, need %d", __func__, getState(), SESSION_USER);
return -EINVAL;
}
if (len) {
NanohubAppInfo info;
readNanohubAppInfo(buf, info);
hub_app_info appInfo;
appInfo.num_mem_ranges = 0;
if (info.flashUse != NANOHUB_MEM_SZ_UNKNOWN) {
mem_range_t &range = appInfo.mem_usage[appInfo.num_mem_ranges++];
range.type = HUB_MEM_TYPE_MAIN;
range.total_bytes = info.flashUse;
}
if (info.ramUse != NANOHUB_MEM_SZ_UNKNOWN) {
mem_range_t &range = appInfo.mem_usage[appInfo.num_mem_ranges++];
range.type = HUB_MEM_TYPE_RAM;
range.total_bytes = info.ramUse;
}
appInfo.app_name = info.name;
appInfo.version = info.version;
mAppInfo.push_back(appInfo);
return requestNext();
} else {
sendToApp(CONTEXT_HUB_QUERY_APPS,
static_cast<const void *>(mAppInfo.data()),
mAppInfo.size() * sizeof(mAppInfo[0]));
complete();
}
return 0;
}
int SystemComm::AppInfoSession::requestNext()
{
char data[MAX_RX_PACKET];
MessageBuf buf(data, sizeof(data));
buf.writeU8(NANOHUB_QUERY_APPS);
buf.writeU32(mAppInfo.size());
return sendToSystem(buf.getData(), buf.getPos());
}
int SystemComm::MemInfoSession::setup(const hub_message_t *)
{
std::lock_guard<std::mutex> _l(mLock);
char data[MAX_RX_PACKET];
MessageBuf buf(data, sizeof(data));
buf.writeU8(NANOHUB_QUERY_MEMINFO);
setState(SESSION_USER);
return sendToSystem(buf.getData(), buf.getPos());
}
int SystemComm::MemInfoSession::handleRx(MessageBuf &buf)
{
std::lock_guard<std::mutex> _l(mLock);
NanohubRsp rsp(buf, true);
if (rsp.cmd != NANOHUB_QUERY_MEMINFO)
return 1;
size_t len = buf.getRoom();
if (len != sizeof(NanohubMemInfo)) {
ALOGE("%s: Invalid data size: %zu", __func__, len);
return -EINVAL;
}
if (getState() != SESSION_USER) {
ALOGE("%s: Invalid state; have %d, need %d", __func__, getState(), SESSION_USER);
return -EINVAL;
}
NanohubMemInfo mi;
readNanohubMemInfo(buf, mi);
std::vector<mem_range_t> ranges;
ranges.reserve(4);
//if each is valid, copy to output area
if (mi.sharedSz != NANOHUB_MEM_SZ_UNKNOWN &&
mi.sharedUse != NANOHUB_MEM_SZ_UNKNOWN)
ranges.push_back({
.type = HUB_MEM_TYPE_MAIN,
.total_bytes = mi.sharedSz,
.free_bytes = mi.sharedSz - mi.sharedUse,
});
if (mi.osSz != NANOHUB_MEM_SZ_UNKNOWN &&
mi.osUse != NANOHUB_MEM_SZ_UNKNOWN)
ranges.push_back({
.type = HUB_MEM_TYPE_OS,
.total_bytes = mi.osSz,
.free_bytes = mi.osSz - mi.osUse,
});
if (mi.eeSz != NANOHUB_MEM_SZ_UNKNOWN &&
mi.eeUse != NANOHUB_MEM_SZ_UNKNOWN)
ranges.push_back({
.type = HUB_MEM_TYPE_EEDATA,
.total_bytes = mi.eeSz,
.free_bytes = mi.eeSz - mi.eeUse,
});
if (mi.ramSz != NANOHUB_MEM_SZ_UNKNOWN &&
mi.ramUse != NANOHUB_MEM_SZ_UNKNOWN)
ranges.push_back({
.type = HUB_MEM_TYPE_RAM,
.total_bytes = mi.ramSz,
.free_bytes = mi.ramSz - mi.ramUse,
});
//send it out
sendToApp(CONTEXT_HUB_QUERY_MEMORY,
static_cast<const void *>(ranges.data()),
ranges.size() * sizeof(ranges[0]));
complete();
return 0;
}
int SystemComm::AppMgmtSession::setup(const hub_message_t *appMsg)
{
std::lock_guard<std::mutex> _l(mLock);
char data[MAX_RX_PACKET];
MessageBuf buf(data, sizeof(data));
const uint8_t *msgData = static_cast<const uint8_t*>(appMsg->message);
mCmd = appMsg->message_type;
mLen = appMsg->message_len;
mPos = 0;
switch (mCmd) {
case CONTEXT_HUB_APPS_ENABLE:
return setupMgmt(appMsg, NANOHUB_EXT_APPS_ON);
case CONTEXT_HUB_APPS_DISABLE:
return setupMgmt(appMsg, NANOHUB_EXT_APPS_OFF);
case CONTEXT_HUB_UNLOAD_APP:
return setupMgmt(appMsg, NANOHUB_EXT_APP_DELETE);
case CONTEXT_HUB_LOAD_APP:
{
mData.clear();
mData = std::vector<uint8_t>(msgData, msgData + mLen);
const load_app_request_t *appReq = static_cast<const load_app_request_t*>(appMsg->message);
if (appReq == nullptr || mLen <= sizeof(*appReq)) {
ALOGE("%s: Invalid app header: too short\n", __func__);
return -EINVAL;
}
mAppName = appReq->app_binary.app_id;
setState(TRANSFER);
buf.writeU8(NANOHUB_START_UPLOAD);
buf.writeU8(0);
buf.writeU32(mLen);
return sendToSystem(buf.getData(), buf.getPos());
}
case CONTEXT_HUB_OS_REBOOT:
setState(REBOOT);
buf.writeU8(NANOHUB_REBOOT);
return sendToSystem(buf.getData(), buf.getPos());
}
return -EINVAL;
}
int SystemComm::AppMgmtSession::setupMgmt(const hub_message_t *appMsg, uint32_t cmd)
{
const hub_app_name_t &appName = *static_cast<const hub_app_name_t*>(appMsg->message);
if (appMsg->message_len != sizeof(appName)) {
return -EINVAL;
}
char data[MAX_RX_PACKET];
MessageBuf buf(data, sizeof(data));
buf.writeU8(cmd);
writeAppName(buf, appName);
setState(MGMT);
return sendToSystem(buf.getData(), buf.getPos());
}
int SystemComm::AppMgmtSession::handleRx(MessageBuf &buf)
{
int ret = 0;
std::lock_guard<std::mutex> _l(mLock);
NanohubRsp rsp(buf);
switch (getState()) {
case TRANSFER:
ret = handleTransfer(rsp);
break;
case FINISH:
ret = handleFinish(rsp);
break;
case RUN:
ret = handleRun(rsp);
break;
case RUN_FAILED:
ret = handleRunFailed(rsp);
break;
case REBOOT:
ret = handleReboot(rsp);
break;
case MGMT:
ret = handleMgmt(rsp);
break;
}
return ret;
}
int SystemComm::AppMgmtSession::handleTransfer(NanohubRsp &rsp)
{
if (rsp.cmd != NANOHUB_CONT_UPLOAD && rsp.cmd != NANOHUB_START_UPLOAD)
return 1;
char data[MAX_RX_PACKET];
MessageBuf buf(data, sizeof(data));
static_assert(NANOHUB_UPLOAD_CHUNK_SZ_MAX <= (MAX_RX_PACKET-5),
"Invalid chunk size");
if (mPos < mLen) {
uint32_t chunkSize = mLen - mPos;
if (chunkSize > NANOHUB_UPLOAD_CHUNK_SZ_MAX) {
chunkSize = NANOHUB_UPLOAD_CHUNK_SZ_MAX;
}
buf.writeU8(NANOHUB_CONT_UPLOAD);
buf.writeU32(mPos);
buf.writeRaw(&mData[mPos], chunkSize);
mPos += chunkSize;
} else {
buf.writeU8(NANOHUB_FINISH_UPLOAD);
setState(FINISH);
}
return sendToSystem(buf.getData(), buf.getPos());
}
int SystemComm::AppMgmtSession::handleFinish(NanohubRsp &rsp)
{
if (rsp.cmd != NANOHUB_FINISH_UPLOAD)
return 1;
int ret = 0;
const bool success = rsp.status != 0;
mData.clear();
if (success) {
char data[MAX_RX_PACKET];
MessageBuf buf(data, sizeof(data));
buf.writeU8(NANOHUB_EXT_APPS_ON);
writeAppName(buf, mAppName);
setState(RUN);
ret = sendToSystem(buf.getData(), buf.getPos());
} else {
int32_t result = NANOHUB_APP_NOT_LOADED;
sendToApp(mCmd, &result, sizeof(result));
complete();
}
return ret;
}
int SystemComm::AppMgmtSession::handleRun(NanohubRsp &rsp)
{
if (rsp.cmd != NANOHUB_EXT_APPS_ON)
return 1;
MgmtStatus sts = { .value = (uint32_t)rsp.status };
// op counter returns number of nanoapps that were started as result of the command
// for successful start command it must be > 0
int32_t result = sts.value > 0 && sts.op > 0 && sts.op <= 0x7F ? 0 : -1;
ALOGI("Nanohub NEW APP START: %08" PRIX32 "\n", rsp.status);
if (result != 0) {
// if nanoapp failed to start we have to unload it
char data[MAX_RX_PACKET];
MessageBuf buf(data, sizeof(data));
buf.writeU8(NANOHUB_EXT_APP_DELETE);
writeAppName(buf, mAppName);
if (sendToSystem(buf.getData(), buf.getPos()) == 0) {
setState(RUN_FAILED);
return 0;
}
ALOGE("%s: failed to send DELETE for failed app\n", __func__);
}
// it is either success, and we report it, or
// it is a failure to load, and also failure to send erase command
sendToApp(mCmd, &result, sizeof(result));
complete();
return 0;
}
int SystemComm::AppMgmtSession::handleRunFailed(NanohubRsp &rsp)
{
if (rsp.cmd != NANOHUB_EXT_APP_DELETE)
return 1;
int32_t result = -1;
ALOGI("%s: APP DELETE [because it failed]: %08" PRIX32 "\n", __func__, rsp.status);
sendToApp(mCmd, &result, sizeof(result));
complete();
return 0;
}
/* reboot notification, when triggered by App request */
int SystemComm::AppMgmtSession::handleReboot(NanohubRsp &rsp)
{
if (rsp.cmd != NANOHUB_REBOOT)
return 1;
ALOGI("Nanohub reboot status [USER REQ]: %08" PRIX32 "\n", rsp.status);
// reboot notification is sent by SessionManager
complete();
return 0;
}
int SystemComm::AppMgmtSession::handleMgmt(NanohubRsp &rsp)
{
bool valid = false;
int32_t result = rsp.status;
// TODO: remove this when context hub service can handle non-zero success status
if (result > 0) {
// something happened; assume it worked
result = 0;
} else if (result == 0) {
// nothing happened; this is provably an error
result = -1;
}
switch (rsp.cmd) {
case NANOHUB_EXT_APPS_OFF:
valid = mCmd == CONTEXT_HUB_APPS_DISABLE;
break;
case NANOHUB_EXT_APPS_ON:
valid = mCmd == CONTEXT_HUB_APPS_ENABLE;
break;
case NANOHUB_EXT_APP_DELETE:
valid = mCmd == CONTEXT_HUB_UNLOAD_APP;
break;
default:
return 1;
}
ALOGI("Nanohub MGMT response: CMD=%02X; STATUS=%08" PRIX32, rsp.cmd, rsp.status);
if (!valid) {
ALOGE("Invalid response for this state: APP CMD=%02X", mCmd);
return -EINVAL;
}
sendToApp(mCmd, &result, sizeof(result));
complete();
return 0;
}
int SystemComm::KeyInfoSession::setup(const hub_message_t *) {
std::lock_guard<std::mutex> _l(mLock);
mRsaKeyData.clear();
setState(SESSION_USER);
mStatus = -EBUSY;
return requestRsaKeys();
}
int SystemComm::KeyInfoSession::handleRx(MessageBuf &buf)
{
std::lock_guard<std::mutex> _l(mLock);
NanohubRsp rsp(buf, true);
if (getState() != SESSION_USER) {
// invalid state
mStatus = -EFAULT;
return mStatus;
}
if (buf.getRoom()) {
mRsaKeyData.insert(mRsaKeyData.end(),
buf.getData() + buf.getPos(),
buf.getData() + buf.getSize());
return requestRsaKeys();
} else {
mStatus = 0;
complete();
return 0;
}
}
int SystemComm::KeyInfoSession::requestRsaKeys(void)
{
char data[MAX_RX_PACKET];
MessageBuf buf(data, sizeof(data));
buf.writeU8(NANOHUB_QUERY_APPS);
buf.writeU32(mRsaKeyData.size());
return sendToSystem(buf.getData(), buf.getPos());
}
int SystemComm::doHandleRx(const nano_message *msg)
{
//we only care for messages from HostIF
if (msg->hdr.appId != mHostIfAppName.id)
return 1;
//they must all be at least 1 byte long
if (!msg->hdr.len) {
return -EINVAL;
}
MessageBuf buf(reinterpret_cast<const char*>(msg->data), msg->hdr.len);
if (NanoHub::messageTracingEnabled()) {
dumpBuffer("SYS -> HAL", mHostIfAppName, 0, buf.getData(), buf.getSize());
}
int status = mSessions.handleRx(buf);
if (status) {
// provide default handler for any system message, that is not properly handled
dumpBuffer(status > 0 ? "HAL (not handled)" : "HAL (error)",
mHostIfAppName, 0, buf.getData(), buf.getSize(), status);
status = status > 0 ? 0 : status;
}
return status;
}
int SystemComm::SessionManager::handleRx(MessageBuf &buf)
{
int status = 1;
std::unique_lock<std::mutex> lk(lock);
// pass message to all active sessions, in arbitrary order
// 1st session that handles the message terminates the loop
for (auto pos = sessions_.begin(); pos != sessions_.end() && status > 0; next(pos)) {
if (!isActive(pos)) {
continue;
}
Session *session = pos->second;
status = session->handleRx(buf);
if (status < 0) {
session->complete();
}
}
NanohubRsp rsp(buf);
if (rsp.cmd == NANOHUB_REBOOT) {
// if this is reboot notification, kill all sessions
for (auto pos = sessions_.begin(); pos != sessions_.end(); next(pos)) {
if (!isActive(pos)) {
continue;
}
Session *session = pos->second;
session->abort(-EINTR);
}
lk.unlock();
// log the reboot event, if not handled
if (status > 0) {
ALOGW("Nanohub reboot status [UNSOLICITED]: %08" PRIX32, rsp.status);
status = 0;
}
// report to java apps
sendToApp(CONTEXT_HUB_OS_REBOOT, &rsp.status, sizeof(rsp.status));
}
return status;
}
int SystemComm::SessionManager::setup_and_add(int id, Session *session, const hub_message_t *appMsg)
{
std::lock_guard<std::mutex> _l(lock);
// scan sessions to release those that are already done
for (auto pos = sessions_.begin(); pos != sessions_.end(); next(pos)) {
continue;
}
if (sessions_.count(id) == 0 && !session->isRunning()) {
sessions_[id] = session;
int ret = session->setup(appMsg);
if (ret < 0) {
session->complete();
}
return ret;
}
return -EBUSY;
}
int SystemComm::doHandleTx(const hub_message_t *appMsg)
{
int status = 0;
switch (appMsg->message_type) {
case CONTEXT_HUB_LOAD_APP:
if (!mKeySession.haveKeys()) {
status = mSessions.setup_and_add(CONTEXT_HUB_LOAD_APP, &mKeySession, appMsg);
if (status < 0) {
break;
}
mKeySession.wait();
status = mKeySession.getStatus();
if (status < 0) {
break;
}
}
status = mSessions.setup_and_add(CONTEXT_HUB_LOAD_APP, &mAppMgmtSession, appMsg);
break;
case CONTEXT_HUB_APPS_ENABLE:
case CONTEXT_HUB_APPS_DISABLE:
case CONTEXT_HUB_UNLOAD_APP:
// all APP-modifying commands share session key, to ensure they can't happen at the same time
status = mSessions.setup_and_add(CONTEXT_HUB_LOAD_APP, &mAppMgmtSession, appMsg);
break;
case CONTEXT_HUB_QUERY_APPS:
status = mSessions.setup_and_add(CONTEXT_HUB_QUERY_APPS, &mAppInfoSession, appMsg);
break;
case CONTEXT_HUB_QUERY_MEMORY:
status = mSessions.setup_and_add(CONTEXT_HUB_QUERY_MEMORY, &mMemInfoSession, appMsg);
break;
default:
ALOGW("Unknown os message type %u\n", appMsg->message_type);
return -EINVAL;
}
return status;
}
}; // namespace nanohub
}; // namespace android