blob: bfab021d9b444f7bb1182589e2703745e535a21c [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "app.h"
#include <alarm.h>
#include <lib/async/cpp/task.h>
#include <lib/fidl/llcpp/server.h>
#include <lib/service/llcpp/service.h>
#include <lib/svc/dir.h>
#include <radio.h>
#include <zircon/compiler.h>
#include <zircon/status.h>
#include <fbl/auto_lock.h>
#include <openthread/tasklet.h>
#include <src/lib/files/file.h>
#define OT_STACK_ASSERT assert
namespace otstack {
namespace {
constexpr char kMigrationConfigPath[] = "/config/data/migration_config.json";
constexpr uint8_t kSpinelResetFrame[]{0x80, 0x06, 0x0};
} // namespace
OtStackApp::LowpanSpinelDeviceFidlImpl::LowpanSpinelDeviceFidlImpl(OtStackApp& app) : app_(app) {}
void OtStackApp::ClientAllowanceInit() {
client_outbound_allowance_ = kOutboundAllowanceInit;
client_inbound_allowance_ = 0;
(*binding_)->OnReadyForSendFrames(kOutboundAllowanceInit);
}
void OtStackApp::RadioAllowanceInit() {
radio_inbound_allowance_ = kInboundAllowanceInit;
radio_outbound_allowance_ = 0;
// try to open the device
auto fidl_result = device_client_ptr_->Open();
if (fidl_result.status() != ZX_OK) {
FX_LOGS(ERROR) << "FIDL error while sending open() req to ot-radio";
Shutdown();
return;
}
auto& result = fidl_result.value().result;
if (result.is_err()) {
FX_LOGS(DEBUG) << "ot-stack: radio returned err in spinel Open(): "
<< static_cast<uint32_t>(result.err());
return;
}
// send inbound allowance
device_client_ptr_->ReadyToReceiveFrames(kInboundAllowanceInit);
}
void OtStackApp::HandleRadioOnReadyForSendFrame(uint32_t allowance) {
radio_outbound_allowance_ += allowance;
}
void OtStackApp::HandleClientReadyToReceiveFrames(uint32_t allowance) {
if (client_inbound_allowance_ == 0 && !client_inbound_queue_.empty()) {
async::PostTask(loop_.dispatcher(), [this]() { this->SendOneFrameToClient(); });
}
client_inbound_allowance_ += allowance;
}
void OtStackApp::UpdateRadioOutboundAllowance() {
OT_STACK_ASSERT(radio_outbound_allowance_ > 0);
radio_outbound_allowance_--;
radio_outbound_cnt++;
FX_LOGS(DEBUG) << "ot-stack: updated radio_outbound_allowance_:" << radio_outbound_allowance_;
}
void OtStackApp::UpdateRadioInboundAllowance() {
OT_STACK_ASSERT(radio_inbound_allowance_ > 0);
radio_inbound_allowance_--;
radio_inbound_cnt++;
if (((radio_inbound_allowance_ & 1) == 0) && device_client_ptr_) {
device_client_ptr_->ReadyToReceiveFrames(kInboundAllowanceInc);
radio_inbound_allowance_ += kInboundAllowanceInc;
}
FX_LOGS(DEBUG) << "ot-stack: updated radio_inbound_allowance_:" << radio_inbound_allowance_;
}
void OtStackApp::UpdateClientOutboundAllowance() {
OT_STACK_ASSERT(client_outbound_allowance_ > 0);
client_outbound_allowance_--;
client_outbound_cnt++;
if (((client_outbound_allowance_ & 1) == 0) && device_client_ptr_) {
FX_LOGS(DEBUG) << "ot-stack: OnReadyForSendFrames: " << client_outbound_allowance_;
(*binding_)->OnReadyForSendFrames(kOutboundAllowanceInc);
client_outbound_allowance_ += kOutboundAllowanceInc;
}
FX_LOGS(DEBUG) << "ot-stack: updated client_outbound_allowance_:" << client_outbound_allowance_;
}
void OtStackApp::UpdateClientInboundAllowance() {
OT_STACK_ASSERT(client_inbound_allowance_ > 0);
client_inbound_allowance_--;
client_inbound_cnt++;
FX_LOGS(DEBUG) << "ot-stack: updated client_inbound_allowance_:" << client_inbound_allowance_;
}
void OtStackApp::LowpanSpinelDeviceFidlImpl::Open(OpenCompleter::Sync& completer) {
if (!app_.connected_to_device_) {
FX_LOGS(ERROR) << "ot-radio not connected when client called Open()";
completer.ReplyError(fidl_spinel::wire::Error::UNSPECIFIED);
app_.Shutdown();
return;
}
FX_LOGS(INFO) << "FIDL request Open got";
async::PostTask(app_.loop_.dispatcher(), [this]() {
otInstanceFinalize(static_cast<otInstance*>(app_.ot_instance_ptr_.value()));
app_.ot_instance_ptr_.reset();
otSysDeinit();
this->app_.InitOpenThreadLibrary(true);
});
app_.ClientAllowanceInit();
// Send out the reset frame
app_.client_inbound_queue_.push_back(std::vector<uint8_t>{0x80, 0x06, 0x0, 0x70});
completer.ReplySuccess();
}
void OtStackApp::LowpanSpinelDeviceFidlImpl::Close(CloseCompleter::Sync& completer) {
if (!app_.connected_to_device_) {
FX_LOGS(ERROR) << "ot-radio not connected";
completer.ReplyError(fidl_spinel::wire::Error::UNSPECIFIED);
app_.Shutdown();
return;
}
auto fidl_result = app_.device_client_ptr_->Close();
if (fidl_result.status() != ZX_OK) {
FX_LOGS(ERROR) << "FIDL error while sending req to ot-radio";
completer.ReplyError(fidl_spinel::wire::Error::UNSPECIFIED);
app_.Shutdown();
return;
}
completer.Reply(std::move(fidl_result.value().result));
}
void OtStackApp::LowpanSpinelDeviceFidlImpl::GetMaxFrameSize(
GetMaxFrameSizeCompleter::Sync& completer) {
if (!app_.connected_to_device_) {
FX_LOGS(ERROR) << "ot-stack: ot-radio not connected";
app_.Shutdown();
return;
}
auto fidl_result = app_.device_client_ptr_->GetMaxFrameSize();
if (fidl_result.status() != ZX_OK) {
FX_LOGS(ERROR) << "ot-stack: FIDL error while sending req to ot-radio";
app_.Shutdown();
return;
}
completer.Reply(fidl_result.value().size);
}
void OtStackApp::PushFrameToOtLib() {
assert(!client_outbound_queue_.empty());
ot::Ncp::otNcpGetInstance()->HandleFidlReceiveDone(client_outbound_queue_.front().data(),
client_outbound_queue_.front().size());
client_outbound_queue_.pop_front();
}
void OtStackApp::LowpanSpinelDeviceFidlImpl::SendFrame(::fidl::VectorView<uint8_t> data,
SendFrameCompleter::Sync& completer) {
if (!app_.connected_to_device_) {
FX_LOGS(ERROR) << "ot-radio not connected";
return;
}
FX_LOGS(DEBUG) << "ot-stack: SendFrame() received";
app_.UpdateClientOutboundAllowance();
// Invoke ot-lib
app_.client_outbound_queue_.emplace_back(data.cbegin(), data.cend());
async::PostTask(app_.loop_.dispatcher(), [this]() { this->app_.PushFrameToOtLib(); });
}
void OtStackApp::LowpanSpinelDeviceFidlImpl::ReadyToReceiveFrames(
uint32_t number_of_frames, ReadyToReceiveFramesCompleter::Sync& completer) {
if (!app_.connected_to_device_) {
FX_LOGS(ERROR) << "ot-radio not connected";
return;
}
app_.HandleClientReadyToReceiveFrames(number_of_frames);
}
OtStackApp::OtStackCallBackImpl::OtStackCallBackImpl(OtStackApp& app) : app_(app) {}
// TODO (jiamingw): flow control, and timeout when it is unable to send out the packet
void OtStackApp::OtStackCallBackImpl::SendOneFrameToRadio(uint8_t* buffer, uint32_t size) {
auto data = fidl::VectorView<uint8_t>::FromExternal(buffer, size);
if (app_.radio_outbound_allowance_ == 0) {
FX_LOGS(ERROR) << "ot-stack: radio_outbound_allowance_ is 0, cannot send packet";
return;
}
app_.device_client_ptr_->SendFrame(std::move(data));
app_.UpdateRadioOutboundAllowance();
}
std::vector<uint8_t> OtStackApp::OtStackCallBackImpl::WaitForFrameFromRadio(uint64_t timeout_us) {
FX_LOGS(DEBUG) << "ot-stack-callbackform: radio-callback: waiting for frame";
{
fbl::AutoLock lock(&app_.radio_q_mtx_);
if (app_.radio_inbound_queue_.empty()) {
sync_completion_reset(&app_.radio_rx_complete_);
} else {
std::vector<uint8_t> vec = std::move(app_.radio_inbound_queue_.front());
app_.radio_inbound_queue_.pop_front();
return vec;
}
}
zx_status_t res = sync_completion_wait(&app_.radio_rx_complete_, ZX_USEC(timeout_us));
sync_completion_reset(&app_.radio_rx_complete_);
if (res == ZX_ERR_TIMED_OUT) {
// This method will be called multiple times by ot-lib. It is okay to timeout here.
return std::vector<uint8_t>{};
}
if (res != ZX_OK) {
FX_PLOGS(ERROR, res) << "ot-stack-callbackform: radio-callback: waiting frame end with err";
return std::vector<uint8_t>{};
}
fbl::AutoLock lock0(&app_.radio_q_mtx_);
assert(!app_.radio_inbound_queue_.empty());
std::vector<uint8_t> vec = std::move(app_.radio_inbound_queue_.front());
app_.radio_inbound_queue_.pop_front();
return vec;
}
std::vector<uint8_t> OtStackApp::OtStackCallBackImpl::Process() {
std::vector<uint8_t> vec;
fbl::AutoLock lock(&app_.radio_q_mtx_);
if (!app_.radio_inbound_queue_.empty()) {
vec = std::move(app_.radio_inbound_queue_.front());
app_.radio_inbound_queue_.pop_front();
FX_LOGS(DEBUG) << "ot-stack-callbackform: radio-callback: check for frame: new frame";
}
return vec;
}
void OtStackApp::OtStackCallBackImpl::SendOneFrameToClient(uint8_t* buffer, uint32_t size) {
if (memcmp(buffer, kSpinelResetFrame, sizeof(kSpinelResetFrame)) == 0) {
// Reset frame
FX_LOGS(WARNING) << "ot-stack: reset frame received from ot-radio";
return;
}
app_.client_inbound_queue_.emplace_back(buffer, buffer + size);
app_.SendOneFrameToClient();
}
void OtStackApp::OtStackCallBackImpl::PostNcpFidlInboundTask() {
async::PostTask(app_.loop_.dispatcher(),
[]() { ot::Ncp::otNcpGetInstance()->HandleFrameAddedToNcpBuffer(); });
}
void OtStackApp::OtStackCallBackImpl::PostOtLibTaskletProcessTask() {
async::PostTask(app_.loop_.dispatcher(), [this]() {
otTaskletsProcess(static_cast<otInstance*>(this->app_.ot_instance_ptr_.value()));
});
}
void OtStackApp::OtStackCallBackImpl::PostDelayedAlarmTask(zx::duration delay) {
async::PostDelayedTask(
app_.loop_.dispatcher(), [this]() { this->app_.AlarmTask(); }, delay);
}
zx_status_t OtStackApp::InitOutgoingAndServe() {
// Ensure that outgoing_ is not already initialized.
// Current code cannot result in outgoing_ being reinitialized.
// If there is a change in future and this gets called, that
// may be a bug, as it may result in a unexpected behavior.
OT_STACK_ASSERT(!outgoing_);
async_dispatcher_t* dispatcher = loop_.dispatcher();
outgoing_ = std::make_unique<svc::Outgoing>(dispatcher);
return outgoing_->ServeFromStartupInfo();
}
zx_status_t OtStackApp::SetupBootstrapFidlService() {
zx_status_t status = InitOutgoingAndServe();
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Init outgoing failed during SetupBootstrapFidlService: " << status
<< std::endl;
return status;
}
// Now add entry for bootstrap fidl:
bootstrap_impl_ = std::make_unique<ot::Fuchsia::BootstrapThreadImpl>();
status = outgoing_->svc_dir()->AddEntry(
fidl::DiscoverableProtocolName<fuchsia_lowpan_bootstrap::Thread>,
fbl::MakeRefCounted<fs::Service>(
[this](fidl::ServerEnd<fuchsia_lowpan_bootstrap::Thread> request) {
zx_status_t status =
bootstrap_impl_->Bind(std::move(request), loop_.dispatcher(), outgoing_->svc_dir());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "error binding new server: " << status << std::endl;
}
return status;
}));
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Error adding service in ot-stack for bootstrap fidl";
return status;
}
return ZX_OK;
}
// Setup FIDL server side which handle requests from upper layer components.
zx_status_t OtStackApp::SetupFidlService() {
zx_status_t status = InitOutgoingAndServe();
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Init outgoing failed during SetupFidlService: " << status << std::endl;
return status;
}
fidl_request_handler_ptr_ = std::make_unique<LowpanSpinelDeviceFidlImpl>(*this);
status = outgoing_->svc_dir()->AddEntry(
fidl::DiscoverableProtocolName<fidl_spinel::Device>,
fbl::MakeRefCounted<fs::Service>([this](fidl::ServerEnd<fidl_spinel::Device> request) {
if (binding_) { // TODO (jiamingw) add support for multiple clients
FX_LOGS(ERROR) << "FIDL connect request rejected: already bound";
return ZX_ERR_ALREADY_BOUND;
}
binding_ = fidl::BindServer(
loop_.dispatcher(), std::move(request), fidl_request_handler_ptr_.get(),
[](LowpanSpinelDeviceFidlImpl* /*unused*/, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_lowpan_spinel::Device> /*unused*/) {
FX_LOGS(INFO) << "channel handle unbound with reason: "
<< static_cast<uint32_t>(info.reason);
});
return ZX_OK;
}));
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Error adding service in ot-stack";
return status;
}
return ZX_OK;
}
void OtStackApp::SendOneFrameToClient() {
if (!binding_.has_value()) {
FX_LOGS(ERROR) << "ot-stack: Sending frame to client, but client is not connected";
assert(0);
}
if (!client_inbound_queue_.empty() && client_inbound_allowance_ > 0) {
auto data = fidl::VectorView<uint8_t>::FromExternal(client_inbound_queue_.front());
(*binding_)->OnReceiveFrame(std::move(data));
UpdateClientInboundAllowance();
client_inbound_queue_.pop_front();
if (!client_inbound_queue_.empty() && client_inbound_allowance_ > 0) {
async::PostTask(loop_.dispatcher(), [this]() { this->SendOneFrameToClient(); });
}
FX_LOGS(DEBUG) << "ot-stack: sent one frame to the client of ot-stack";
} else {
FX_LOGS(WARNING) << "ot-stack: unable to sent one frame to the client of ot-stack, q size:"
<< client_inbound_queue_.size()
<< " client_inbound_allowance_:" << client_inbound_allowance_;
}
}
zx_status_t OtStackApp::InitRadioDriver() {
zx_status_t result = ConnectToOtRadioDev();
if (result != ZX_OK) {
return result;
}
RadioAllowanceInit();
return ZX_OK;
}
void OtStackApp::InitOpenThreadLibrary(bool reset_rcp) {
FX_LOGS(INFO) << "init ot-lib";
otPlatformConfig config;
config.callback_ptr = lowpan_spinel_ptr_.get();
config.m_speed_up_factor = 1;
config.reset_rcp = reset_rcp;
ot_instance_ptr_ = static_cast<void*>(otSysInit(&config));
ot::Ncp::otNcpInit(static_cast<otInstance*>(ot_instance_ptr_.value()));
ot::Ncp::otNcpGetInstance()->Init(lowpan_spinel_ptr_.get());
}
zx_status_t OtStackApp::Init(const std::string& path, bool is_test_env) {
is_test_env_ = is_test_env;
device_path_ = path;
bool bootstrap_only = files::IsFile(kMigrationConfigPath);
if (bootstrap_only) {
return SetupBootstrapFidlService();
}
lowpan_spinel_ptr_ = std::make_unique<OtStackCallBackImpl>(*this);
zx_status_t status = InitRadioDriver();
if (status != ZX_OK) {
return status;
}
InitOpenThreadLibrary(false);
status = SetupFidlService();
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
void OtStackApp::AlarmTask() {
zx_time_t remaining;
platformAlarmUpdateTimeout(&remaining);
if (remaining == 0) {
FX_LOGS(DEBUG) << "ot-stack: calling platformAlarmProcess()";
platformAlarmProcess(static_cast<otInstance*>(ot_instance_ptr_.value()));
} else {
// If remaining is not 0, then the alarm is likely already being reset.
// do not need to do anything here
FX_LOGS(DEBUG) << "ot-stack: alarm process not called, remaining: " << remaining;
}
}
// Connect to ot-radio device driver which allows ot-stack to talk to lower layer
zx_status_t OtStackApp::ConnectToOtRadioDev() {
zx_status_t result = ZX_OK;
if (is_test_env_) {
result = SetDeviceSetupClientInIsolatedDevmgr(device_path_);
} else {
result = SetDeviceSetupClientInDevmgr(device_path_);
}
if (result != ZX_OK) {
FX_LOGS(ERROR) << "failed to set device setup client";
return result;
}
return SetupOtRadioDev();
}
// Get the spinel setup client from a file path. Set `client_ptr_` on success.
zx_status_t OtStackApp::SetDeviceSetupClientInDevmgr(const std::string& path) {
auto client_end = service::Connect<fidl_spinel::DeviceSetup>(path.c_str());
if (client_end.is_error()) {
FX_LOGS(ERROR) << "failed to connect to device: " << client_end.status_string();
return client_end.status_value();
}
device_setup_client_ptr_ =
std::make_unique<fidl::WireSyncClient<fidl_spinel::DeviceSetup>>(std::move(*client_end));
return ZX_OK;
}
// Get the spinel setup client from a file path. Set `client_ptr_` on success.
zx_status_t OtStackApp::SetDeviceSetupClientInIsolatedDevmgr(const std::string& path) {
auto isolated_devfs = service::Connect<fuchsia_openthread_devmgr::IsolatedDevmgr>();
if (isolated_devfs.is_error()) {
FX_LOGS(ERROR) << "failed to connect to isolated devmgr: " << isolated_devfs.status_string();
return isolated_devfs.status_value();
}
// IsolatedDevmgr composes fuchsia.io.Directory, but FIDL bindings don't know.
auto client_end = service::ConnectAt<fidl_spinel::DeviceSetup>(
fidl::UnownedClientEnd<fuchsia_io::Directory>(isolated_devfs->channel().borrow()),
path.c_str());
if (client_end.is_error()) {
FX_LOGS(ERROR) << "failed to connect to device setup: " << client_end.status_string();
return client_end.status_value();
}
device_setup_client_ptr_ =
std::make_unique<fidl::WireSyncClient<fidl_spinel::DeviceSetup>>(std::move(*client_end));
return ZX_OK;
}
zx_status_t OtStackApp::SetupOtRadioDev() {
if (device_setup_client_ptr_ == nullptr) {
return ZX_ERR_BAD_STATE;
}
auto endpoints = fidl::CreateEndpoints<fidl_spinel::Device>();
if (endpoints.is_error()) {
return endpoints.status_value();
}
auto [client_end, server_end] = std::move(endpoints.value());
auto fidl_result = device_setup_client_ptr_->SetChannel(std::move(server_end));
if (fidl_result.status() != ZX_OK) {
FX_LOGS(ERROR) << "Cannot set the channel to device: " << fidl_result.status_string();
return fidl_result.status();
}
auto& result = fidl_result.value().result;
if (result.is_err()) {
FX_LOGS(ERROR) << "Cannot set the channel to device: " << static_cast<uint32_t>(result.err());
return ZX_ERR_INTERNAL;
}
FX_LOGS(INFO) << "successfully connected to driver";
event_thread_ = std::thread(&OtStackApp::EventThread, this);
zx_status_t status = zx::port::create(0, &port_);
if (status != ZX_OK) {
return status;
}
device_channel_ = zx::unowned_channel(client_end.channel());
device_client_ptr_ =
std::make_unique<fidl::WireSyncClient<fidl_spinel::Device>>(std::move(client_end));
connected_to_device_ = true;
status = device_channel_->wait_async(port_, kPortRadioChannelRead,
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "failed to wait for events";
}
return status;
}
void OtStackApp::EventLoopHandleInboundFrame(::fidl::VectorView<uint8_t> data) {
fbl::AutoLock lock(&radio_q_mtx_);
radio_inbound_queue_.emplace_back(data.cbegin(), data.cend());
sync_completion_signal(&radio_rx_complete_);
async::PostTask(loop_.dispatcher(), [this]() {
platformRadioProcess(static_cast<otInstance*>(this->ot_instance_ptr_.value()));
});
}
void OtStackApp::OnReadyForSendFrames(
fidl::WireResponse<fidl_spinel::Device::OnReadyForSendFrames>* event) {
HandleRadioOnReadyForSendFrame(event->number_of_frames);
}
void OtStackApp::OnReceiveFrame(fidl::WireResponse<fidl_spinel::Device::OnReceiveFrame>* event) {
EventLoopHandleInboundFrame(std::move(event->data));
UpdateRadioInboundAllowance();
}
void OtStackApp::OnError(fidl::WireResponse<fidl_spinel::Device::OnError>* event) {
handler_status_ = (*binding_)->OnError(event->error, event->did_close);
}
zx_status_t OtStackApp::Unknown() {
(*binding_)->OnError(fidl_spinel::wire::Error::IO_ERROR, true);
DisconnectDevice();
return ZX_ERR_IO;
}
void OtStackApp::EventThread() {
while (true) {
zx_port_packet_t packet = {};
port_.wait(zx::time::infinite(), &packet);
switch (packet.key) {
case kPortRadioChannelRead: {
if (packet.signal.observed & ZX_CHANNEL_PEER_CLOSED) {
FX_LOGS(ERROR) << "ot-radio channel closed, terminating event thread";
return;
}
::fidl::Result result = HandleOneEvent(device_client_ptr_->client_end());
if (!result.ok() || (handler_status_ != ZX_OK)) {
FX_PLOGS(ERROR, result.ok() ? handler_status_ : result.status())
<< "error calling fidl::WireSyncClient<fidl_spinel::Device>::HandleEvents(), "
"terminating event "
"thread";
DisconnectDevice();
loop_.Shutdown();
return;
}
zx_status_t status = device_channel_->wait_async(
port_, kPortRadioChannelRead, ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "failed to wait for events, terminating event thread";
return;
}
} break;
case kPortTerminate:
FX_LOGS(INFO) << "terminating event thread";
return;
}
}
}
void OtStackApp::TerminateEventThread() {
zx_port_packet packet = {kPortTerminate, ZX_PKT_TYPE_USER, ZX_OK, {}};
port_.queue(&packet);
event_thread_.join();
}
void OtStackApp::DisconnectDevice() {
device_channel_ = zx::unowned_channel(ZX_HANDLE_INVALID);
device_client_ptr_.reset();
device_setup_client_ptr_.reset();
connected_to_device_ = false;
}
void OtStackApp::Shutdown() {
FX_LOGS(ERROR) << "terminating message loop in ot-stack";
if (binding_) {
binding_->Close(ZX_ERR_INTERNAL);
}
TerminateEventThread();
DisconnectDevice();
loop_.Quit();
}
} // namespace otstack