/*
* 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 <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <hardware/context_hub.h>
#include <hardware/hardware.h>
#include <utils/Log.h>
#include <cutils/properties.h>
#include <nanohub/nanohub.h>
#include <cinttypes>
#include <iomanip>
#include <sstream>
#include "nanohub_perdevice.h"
#include "system_comms.h"
#include "nanohubhal.h"
#define NANOHUB_LOCK_DIR "/data/vendor/sensor/nanohub_lock"
#define NANOHUB_LOCK_FILE NANOHUB_LOCK_DIR "/lock"
#define NANOHUB_LOCK_DIR_PERMS (S_IRUSR | S_IWUSR | S_IXUSR)
namespace android {
namespace nanohub {
inline std::ostream &operator << (std::ostream &os, const hub_app_name_t &appId)
{
char vendor[6];
__be64 beAppId = htobe64(appId.id);
uint32_t seqId = appId.id & NANOAPP_VENDOR_ALL_APPS;
std::ios::fmtflags f(os.flags());
memcpy(vendor, (void*)&beAppId, sizeof(vendor) - 1);
vendor[sizeof(vendor) - 1] = 0;
if (strlen(vendor) == 5)
os << vendor << ", " << std::hex << std::setw(6) << seqId;
else
os << "#" << std::hex << appId.id;
os.flags(f);
return os;
}
void dumpBuffer(const char *pfx, const hub_app_name_t &appId, uint32_t evtId, const void *data, size_t len, int status)
{
std::ostringstream os;
const uint8_t *p = static_cast<const uint8_t *>(data);
os << pfx << ": [ID=" << appId << "; SZ=" << std::dec << len;
if (evtId)
os << "; EVT=" << std::hex << evtId;
os << "]:" << std::hex;
for (size_t i = 0; i < len; ++i) {
os << " " << std::setfill('0') << std::setw(2) << (unsigned int)p[i];
}
if (status) {
os << "; status=" << status << " [" << std::setfill('0') << std::setw(8) << status << "]";
}
ALOGI("%s", os.str().c_str());
}
static int rwrite(int fd, const void *buf, int len)
{
int ret;
do {
ret = write(fd, buf, len);
} while (ret < 0 && errno == EINTR);
if (ret != len) {
return errno ? -errno : -EIO;
}
return 0;
}
static int rread(int fd, void *buf, int len)
{
int ret;
do {
ret = read(fd, buf, len);
} while (ret < 0 && errno == EINTR);
return ret;
}
static bool init_inotify(pollfd *pfd) {
bool success = false;
mkdir(NANOHUB_LOCK_DIR, NANOHUB_LOCK_DIR_PERMS);
pfd->fd = inotify_init1(IN_NONBLOCK);
if (pfd->fd < 0) {
ALOGE("Couldn't initialize inotify: %s", strerror(errno));
} else if (inotify_add_watch(pfd->fd, NANOHUB_LOCK_DIR, IN_CREATE | IN_DELETE) < 0) {
ALOGE("Couldn't add inotify watch: %s", strerror(errno));
close(pfd->fd);
} else {
pfd->events = POLLIN;
success = true;
}
return success;
}
static void discard_inotify_evt(pollfd &pfd) {
if ((pfd.revents & POLLIN)) {
char buf[sizeof(inotify_event) + NAME_MAX + 1];
int ret = read(pfd.fd, buf, sizeof(buf));
ALOGD("Discarded %d bytes of inotify data", ret);
}
}
static void wait_on_dev_lock(pollfd &pfd) {
// While the lock file exists, poll on the inotify fd (with timeout)
discard_inotify_evt(pfd);
while (access(NANOHUB_LOCK_FILE, F_OK) == 0) {
ALOGW("Nanohub is locked; blocking read thread");
int ret = poll(&pfd, 1, 5000);
if (ret > 0) {
discard_inotify_evt(pfd);
}
}
}
NanoHub::NanoHub() {
reset();
}
NanoHub::~NanoHub() {
if (mMsgCbkFunc) {
ALOGD("Shutting down");
closeHub();
}
}
int NanoHub::doSendToDevice(const hub_app_name_t name, const void *data, uint32_t len, uint32_t messageType)
{
if (len > MAX_RX_PACKET) {
return -EINVAL;
}
// transmit message to FW in CHRE format
nano_message_chre msg = {
.hdr = {
.eventId = APP_FROM_HOST_CHRE_EVENT_ID,
.appId = name.id,
.len = static_cast<uint8_t>(len),
.appEventId = messageType,
},
};
memcpy(&msg.data[0], data, len);
return rwrite(mFd, &msg, len + sizeof(msg.hdr));
}
void NanoHub::doSendToApp(HubMessage &&msg)
{
std::unique_lock<std::mutex> lk(mAppTxLock);
mAppTxQueue.push_back((HubMessage &&)msg);
lk.unlock();
mAppTxCond.notify_all();
}
void* NanoHub::runAppTx()
{
std::unique_lock<std::mutex> lk(mAppTxLock);
while(true) {
mAppTxCond.wait(lk, [this] { return !mAppTxQueue.empty() || mAppQuit; });
if (mAppQuit) {
break;
}
HubMessage &m = mAppTxQueue.front();
lk.unlock();
mMsgCbkFunc(0, &m, mMsgCbkData);
lk.lock();
mAppTxQueue.pop_front();
};
return NULL;
}
void* NanoHub::runDeviceRx()
{
enum {
IDX_NANOHUB,
IDX_CLOSE_PIPE,
IDX_INOTIFY
};
pollfd myFds[3] = {
[IDX_NANOHUB] = { .fd = mFd, .events = POLLIN, },
[IDX_CLOSE_PIPE] = { .fd = mThreadClosingPipe[0], .events = POLLIN, },
};
pollfd &inotifyFd = myFds[IDX_INOTIFY];
bool hasInotify = false;
int numPollFds = 2;
if (init_inotify(&inotifyFd)) {
numPollFds++;
hasInotify = true;
}
setDebugFlags(property_get_int32("persist.nanohub.debug", 0));
while (1) {
int ret = poll(myFds, numPollFds, -1);
if (ret <= 0) {
ALOGD("poll returned with an error: %s", strerror(errno));
continue;
}
if (hasInotify) {
wait_on_dev_lock(inotifyFd);
}
if (myFds[IDX_NANOHUB].revents & POLLIN) { // we have data
nano_message msg;
ret = rread(mFd, &msg, sizeof(msg));
if (ret <= 0) {
ALOGE("read failed with %d", ret);
break;
}
if (ret < (int)sizeof(msg.hdr)) {
ALOGE("Only read %d bytes", ret);
break;
}
uint32_t len = msg.hdr.len;
if (len > sizeof(msg.data)) {
ALOGE("malformed packet with len %" PRIu32, len);
break;
}
// receive message from FW in legacy format
if (ret != (int)(sizeof(msg.hdr) + len)) {
ALOGE("Expected %zu bytes, read %d bytes", sizeof(msg.hdr) + len, ret);
break;
}
ret = SystemComm::handleRx(&msg);
if (ret < 0) {
ALOGE("SystemComm::handleRx() returned %d", ret);
} else if (ret) {
hub_app_name_t app_name = { .id = msg.hdr.appId };
if (messageTracingEnabled()) {
dumpBuffer("DEV -> APP", app_name, msg.hdr.eventId, &msg.data[0], msg.hdr.len);
}
doSendToApp(HubMessage(&app_name, msg.hdr.eventId, &msg.data[0], msg.hdr.len));
}
}
if (myFds[IDX_CLOSE_PIPE].revents & POLLIN) { // we have been asked to die
ALOGD("thread exiting");
break;
}
}
close(mFd);
return NULL;
}
int NanoHub::openHub()
{
int ret = 0;
mFd = open(get_devnode_path(), O_RDWR);
if (mFd < 0) {
ALOGE("cannot find hub devnode '%s'", get_devnode_path());
ret = -errno;
goto fail_open;
}
if (pipe(mThreadClosingPipe)) {
ALOGE("failed to create signal pipe");
ret = -errno;
goto fail_pipe;
}
mPollThread = std::thread([this] { runDeviceRx(); });
mAppThread = std::thread([this] { runAppTx(); });
return 0;
fail_pipe:
close(mFd);
fail_open:
return ret;
}
int NanoHub::closeHub(void)
{
char zero = 0;
// stop mPollThread
while(write(mThreadClosingPipe[1], &zero, 1) != 1) {
continue;
}
// stop mAppThread
{
std::unique_lock<std::mutex> lk(mAppTxLock);
mAppQuit = true;
lk.unlock();
mAppTxCond.notify_all();
}
//wait
if (mPollThread.joinable()) {
mPollThread.join();
}
//wait
if (mAppThread.joinable()) {
mAppThread.join();
}
//cleanup
::close(mThreadClosingPipe[0]);
::close(mThreadClosingPipe[1]);
::close(mFd);
reset();
return 0;
}
int NanoHub::doSubscribeMessages(uint32_t hub_id, context_hub_callback *cbk, void *cookie)
{
if (hub_id) {
return -ENODEV;
}
std::lock_guard<std::mutex> _l(mLock);
int ret = 0;
if (!mMsgCbkFunc && !cbk) { //we're off and staying off - do nothing
ALOGD("staying off");
} else if (cbk && mMsgCbkFunc) { //new callback but staying on
ALOGD("staying on");
} else if (mMsgCbkFunc) { //we were on but turning off
ALOGD("turning off");
ret = closeHub();
} else if (cbk) { //we're turning on
ALOGD("turning on");
ret = openHub();
}
mMsgCbkFunc = cbk;
mMsgCbkData = cookie;
return ret;
}
int NanoHub::doSendToNanohub(uint32_t hub_id, const hub_message_t *msg)
{
if (hub_id) {
return -ENODEV;
}
int ret = 0;
std::lock_guard<std::mutex> _l(mLock);
if (!mMsgCbkFunc) {
ALOGW("refusing to send a message when nobody around to get a reply!");
ret = -EIO;
} else {
if (!msg || !msg->message) {
ALOGW("not sending invalid message 1");
ret = -EINVAL;
} else if (get_hub_info()->os_app_name == msg->app_name) {
//messages to the "system" app are special - hal handles them
if (messageTracingEnabled()) {
dumpBuffer("APP -> HAL", msg->app_name, msg->message_type, msg->message, msg->message_len);
}
ret = SystemComm::handleTx(msg);
} else if (msg->message_len > MAX_RX_PACKET) {
ALOGW("not sending invalid message 2");
ret = -EINVAL;
} else {
if (messageTracingEnabled()) {
dumpBuffer("APP -> DEV", msg->app_name, msg->message_type, msg->message, msg->message_len);
}
ret = doSendToDevice(msg->app_name, msg->message, msg->message_len, msg->message_type);
}
}
return ret;
}
static int hal_get_hubs(context_hub_module_t*, const context_hub_t ** list)
{
*list = get_hub_info();
return 1; /* we have one hub */
}
}; // namespace nanohub
}; // namespace android
context_hub_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = CONTEXT_HUB_DEVICE_API_VERSION_1_0,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = CONTEXT_HUB_MODULE_ID,
.name = "Nanohub HAL",
.author = "Google",
},
.get_hubs = android::nanohub::hal_get_hubs,
.subscribe_messages = android::nanohub::NanoHub::subscribeMessages,
.send_message = android::nanohub::NanoHub::sendToNanohub,
};