blob: cab4618736361a12a580768805642cd3dee8f2d8 [file] [log] [blame]
// Copyright 2019 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 "serial-ppp.h"
#include <lib/fidl-async/cpp/bind.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <zircon/hw/usb.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddktl/fidl.h>
#include "lib/common/ppp.h"
#include "lib/fit/result.h"
#include "lib/hdlc/frame.h"
namespace ppp {
namespace fppp = llcpp::fuchsia::net::ppp;
static constexpr zx_driver_ops_t driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = SerialPpp::Create,
};
static constexpr uint16_t kDefaultMru = 1500;
static constexpr size_t kMaxBufferSize = 2 * kDefaultMru + 8;
static constexpr size_t kMaxQueueSize = 32;
static constexpr zx::duration kSerialTimeout = zx::msec(10);
static constexpr fppp::Info kDefaultInfo = {
.mtu = kDefaultMru,
};
SerialPpp::SerialPpp() : DeviceType(nullptr) {}
SerialPpp::SerialPpp(zx_device_t* parent) : DeviceType(parent), serial_protocol_(parent) {}
zx_status_t SerialPpp::Create(void* /*ctx*/, zx_device_t* parent) {
auto dev = std::make_unique<SerialPpp>(parent);
auto status = dev->Init();
if (status != ZX_OK) {
zxlogf(ERROR, "%s: Init failed", __func__);
return status;
}
dev->DdkAdd("ppp");
// Release because devmgr is now in charge of the device.
static_cast<void>(dev.release());
return ZX_OK;
}
zx_status_t SerialPpp::Init() { return loop_.StartThread(); }
void SerialPpp::WaitCallbacks() {
while (true) {
{
std::lock_guard<std::mutex> guard(ipv4_mutex_);
if (!ipv4_callback_) {
break;
}
}
}
while (true) {
{
std::lock_guard<std::mutex> guard(ipv6_mutex_);
if (!ipv6_callback_) {
break;
}
}
}
while (true) {
{
std::lock_guard<std::mutex> guard(control_mutex_);
if (!control_callback_) {
break;
}
}
}
}
void SerialPpp::DdkRelease() {
loop_.Shutdown();
Enable(false);
{
std::lock_guard<std::mutex> guard(ipv4_mutex_);
if (ipv4_callback_) {
ipv4_callback_(fit::error(ZX_ERR_CANCELED));
}
}
{
std::lock_guard<std::mutex> guard(ipv6_mutex_);
if (ipv6_callback_) {
ipv6_callback_(fit::error(ZX_ERR_CANCELED));
}
}
{
std::lock_guard<std::mutex> guard(control_mutex_);
if (control_callback_) {
control_callback_(fit::error(ZX_ERR_CANCELED));
}
}
delete this;
}
void SerialPpp::DdkUnbindNew(ddk::UnbindTxn txn) { txn.Reply(); }
zx_status_t SerialPpp::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
fppp::DeviceBootstrap::Dispatch(&bootstrap_server_, msg, &transaction);
return transaction.Status();
}
void SerialPpp::Rx(fppp::ProtocolType protocol,
fit::callback<void(fit::result<Frame, zx_status_t>)> callback) {
switch (protocol) {
case fppp::ProtocolType::IPV4: {
std::lock_guard<std::mutex> guard(ipv4_mutex_);
if (ipv4_callback_) {
callback(fit::error(ZX_ERR_SHOULD_WAIT));
return;
}
if (!ipv4_up_) {
callback(fit::error(ZX_ERR_NOT_CONNECTED));
return;
}
ipv4_callback_ = std::move(callback);
return;
}
case fppp::ProtocolType::IPV6: {
std::lock_guard<std::mutex> guard(ipv6_mutex_);
if (ipv6_callback_) {
callback(fit::error(ZX_ERR_SHOULD_WAIT));
return;
}
if (!ipv6_up_) {
callback(fit::error(ZX_ERR_NOT_CONNECTED));
return;
}
ipv6_callback_ = std::move(callback);
return;
}
case fppp::ProtocolType::CONTROL: {
std::lock_guard<std::mutex> guard(control_mutex_);
if (control_callback_) {
callback(fit::error(ZX_ERR_SHOULD_WAIT));
return;
}
control_callback_ = std::move(callback);
return;
}
default:
callback(fit::error(ZX_ERR_INVALID_ARGS));
return;
}
}
zx_status_t SerialPpp::Tx(fppp::ProtocolType protocol, fbl::Span<const uint8_t> data) {
switch (protocol) {
case fppp::ProtocolType::IPV4: {
if (!ipv4_up_) {
return ZX_ERR_NOT_CONNECTED;
}
return WriteFramed(FrameView{Protocol::Ipv4, data});
}
case fppp::ProtocolType::IPV6: {
if (!ipv6_up_) {
return ZX_ERR_NOT_CONNECTED;
}
return WriteFramed(FrameView{Protocol::Ipv6, data});
}
case fppp::ProtocolType::CONTROL:
if (data.size() >= 2) {
// For ProtocolType::Control, the first two bytes of data encode (in network
// byte order) the PPP control protocol.
const auto protocol = static_cast<Protocol>(data[0] << 8 | data[1]);
return WriteFramed(FrameView(protocol, data.subspan(2)));
} else {
return ZX_ERR_OUT_OF_RANGE;
}
default:
return ZX_ERR_INVALID_ARGS;
}
}
fppp::Info SerialPpp::GetInfo() { return kDefaultInfo; }
bool SerialPpp::GetStatus(fppp::ProtocolType protocol) {
switch (protocol) {
case fppp::ProtocolType::CONTROL:
return true;
case fppp::ProtocolType::IPV4:
return ipv4_up_;
case fppp::ProtocolType::IPV6:
return ipv6_up_;
default:
return false;
}
}
void SerialPpp::SetStatus(fppp::ProtocolType protocol, bool up) {
switch (protocol) {
case fppp::ProtocolType::IPV4: {
ipv4_up_ = up;
if (!up) {
std::lock_guard<std::mutex> guard(ipv4_mutex_);
// std::queue doesn't have .clear().
ipv4_frames_ = {};
}
break;
}
case fppp::ProtocolType::IPV6: {
ipv6_up_ = up;
if (!up) {
std::lock_guard<std::mutex> guard(ipv6_mutex_);
ipv6_frames_ = {};
}
break;
}
default:
break;
}
}
zx_status_t SerialPpp::Enable(bool up) {
if (enabled_ && !up) {
// down
serial_.reset();
enabled_ = false;
rx_thread_.join();
} else if (!enabled_ && up) {
// up
auto status = serial_protocol_.OpenSocket(&serial_);
if (status != ZX_OK) {
return status;
}
enabled_ = true;
rx_thread_ = std::thread([&] { RxLoop(); });
}
return ZX_OK;
}
zx_status_t SerialPpp::Enable(bool up, zx::socket socket) {
if (enabled_ && !up) {
// down
serial_.reset();
enabled_ = false;
rx_thread_.join();
} else if (!enabled_ && up) {
// up
serial_ = std::move(socket);
enabled_ = true;
rx_thread_ = std::thread([&] { RxLoop(); });
}
return ZX_OK;
}
zx::channel SerialPpp::GetInstance() {
zx::channel local;
zx::channel remote;
zx::channel::create(0, &local, &remote);
auto status = fidl::Bind(loop_.dispatcher(), std::move(local), &server_);
if (status != ZX_OK) {
return zx::channel();
}
return remote;
}
SerialPpp::FrameDeviceServer::FrameDeviceServer(SerialPpp* dev) : dev_(dev) {}
void SerialPpp::FrameDeviceServer::Rx(fppp::ProtocolType protocol, RxCompleter::Sync completer) {
auto callback = [completer = completer.ToAsync()](auto frame) mutable {
fppp::Device_Rx_Result result;
if (frame.is_ok()) {
fppp::Device_Rx_Response response;
auto [protocol, data] = frame.take_value();
response.data = fidl::VectorView<uint8_t>(fidl::unowned_ptr(data.data()), data.size());
result.set_response(fidl::unowned_ptr(&response));
completer.Reply(std::move(result));
} else if (frame.is_error()) {
zx_status_t status = frame.take_error();
result.set_err(fidl::unowned_ptr(&status));
completer.Reply(std::move(result));
} else {
zx_status_t status = ZX_ERR_INTERNAL;
result.set_err(fidl::unowned_ptr(&status));
completer.Reply(std::move(result));
}
};
dev_->Rx(protocol, std::move(callback));
}
void SerialPpp::FrameDeviceServer::Tx(fppp::ProtocolType protocol, fidl::VectorView<uint8_t> data,
TxCompleter::Sync completer) {
auto status = dev_->Tx(protocol, fbl::Span(data.data(), data.count()));
fppp::Device_Tx_Result result;
fidl::aligned<fppp::Device_Tx_Response> response;
if (status == ZX_OK) {
result.set_response(fidl::unowned_ptr(&response));
} else {
result.set_err(fidl::unowned_ptr(&status));
}
completer.Reply(std::move(result));
}
void SerialPpp::FrameDeviceServer::GetInfo(GetInfoCompleter::Sync completer) {
completer.Reply(dev_->GetInfo());
}
void SerialPpp::FrameDeviceServer::GetStatus(fppp::ProtocolType protocol,
GetStatusCompleter::Sync completer) {
completer.Reply(dev_->GetStatus(protocol));
}
void SerialPpp::FrameDeviceServer::SetStatus(fppp::ProtocolType protocol, bool up,
SetStatusCompleter::Sync /*completer*/) {
dev_->SetStatus(protocol, up);
}
void SerialPpp::FrameDeviceServer::Enable(bool up, EnableCompleter::Sync completer) {
auto status = dev_->Enable(up);
fppp::Device_Enable_Result result;
fidl::aligned<fppp::Device_Enable_Response> response;
if (status == ZX_OK) {
result.set_response(fidl::unowned_ptr(&response));
} else {
result.set_err(fidl::unowned_ptr(&status));
}
completer.Reply(std::move(result));
}
SerialPpp::FrameDeviceBootstrapServer::FrameDeviceBootstrapServer(SerialPpp* dev) : dev_(dev) {}
void SerialPpp::FrameDeviceBootstrapServer::GetInstance(GetInstanceCompleter::Sync completer) {
completer.Reply(dev_->GetInstance());
}
void SerialPpp::RxLoop() {
std::vector<uint8_t> raw_frame;
raw_frame.reserve(kMaxBufferSize);
while (enabled_) {
{
std::lock_guard<std::mutex> guard(ipv4_mutex_);
if (ipv4_callback_ && !ipv4_frames_.empty()) {
auto frame = std::move(ipv4_frames_.front());
ipv4_frames_.pop();
ipv4_callback_(fit::ok(std::move(frame)));
}
}
{
std::lock_guard<std::mutex> guard(ipv6_mutex_);
if (ipv6_callback_ && !ipv6_frames_.empty()) {
auto frame = std::move(ipv6_frames_.front());
ipv6_frames_.pop();
ipv6_callback_(fit::ok(std::move(frame)));
}
}
{
std::lock_guard<std::mutex> guard(control_mutex_);
if (control_callback_ && !control_frames_.empty()) {
auto frame = std::move(control_frames_.front());
control_frames_.pop();
control_callback_(fit::ok(std::move(frame)));
}
}
auto maybe_frame = ReadFramed(&raw_frame);
if (!maybe_frame.is_ok()) {
continue;
}
auto frame = maybe_frame.take_value();
switch (frame.protocol) {
case Protocol::Ipv4: {
if (!ipv4_up_) {
break;
}
{
std::lock_guard<std::mutex> guard(ipv4_mutex_);
ipv4_frames_.push(std::move(frame));
if (ipv4_frames_.size() > kMaxQueueSize) {
ipv4_frames_.pop();
}
}
break;
}
case Protocol::Ipv6: {
if (!ipv6_up_) {
break;
}
{
std::lock_guard<std::mutex> guard(ipv6_mutex_);
ipv6_frames_.push(std::move(frame));
if (ipv6_frames_.size() > kMaxQueueSize) {
ipv6_frames_.pop();
}
}
break;
}
default: {
const auto protocol_upper =
static_cast<uint8_t>(static_cast<uint16_t>(frame.protocol) >> 8);
const auto protocol_lower = static_cast<uint8_t>(frame.protocol);
frame.information.insert(frame.information.begin(), {protocol_upper, protocol_lower});
{
std::lock_guard<std::mutex> guard(control_mutex_);
control_frames_.push(std::move(frame));
if (control_frames_.size() > kMaxQueueSize) {
control_frames_.pop();
}
}
break;
}
}
}
}
fit::result<Frame, zx_status_t> SerialPpp::ReadFramed(std::vector<uint8_t>* raw_frame) {
raw_frame->clear();
raw_frame->push_back(kFlagSequence);
while (raw_frame->size() != kMaxBufferSize) {
uint8_t b = kFlagSequence;
size_t actual = 0;
const auto status = serial_.read(0, &b, 1, &actual);
if (status == ZX_ERR_SHOULD_WAIT) {
const auto wait_status =
serial_.wait_one(ZX_SOCKET_READABLE, zx::deadline_after(kSerialTimeout), nullptr);
if (wait_status != ZX_OK) {
return fit::error(wait_status);
}
} else if (status != ZX_OK) {
return fit::error(status);
}
if (b == kFlagSequence) {
if (raw_frame->back() != kFlagSequence) {
raw_frame->push_back(b);
break;
}
} else {
raw_frame->push_back(b);
}
}
auto result = DeserializeFrame(*raw_frame);
if (result.is_ok()) {
return result.take_ok_result();
}
return fit::error(ZX_ERR_IO);
}
zx_status_t SerialPpp::WriteFramed(FrameView frame) {
std::lock_guard<std::mutex> lock_guard(write_mutex_);
if (frame.information.size() >= kDefaultMru) {
return ZX_ERR_OUT_OF_RANGE;
}
const auto serialized = SerializeFrame(frame);
auto data = serialized.data();
auto to_write = serialized.size();
while (to_write != 0) {
size_t actual = 0;
const auto status = serial_.write(0, data, to_write, &actual);
if (status == ZX_ERR_SHOULD_WAIT) {
const auto wait_status =
serial_.wait_one(ZX_SOCKET_WRITABLE, zx::deadline_after(kSerialTimeout), nullptr);
if (wait_status != ZX_OK) {
return wait_status;
}
} else if (status != ZX_OK) {
return status;
} else {
data += actual;
to_write -= actual;
}
}
return ZX_OK;
}
} // namespace ppp
// clang-format off
ZIRCON_DRIVER_BEGIN(serial-ppp, ppp::driver_ops, "zircon", "0.1", 1)
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_SERIAL),
ZIRCON_DRIVER_END(serial-ppp)