blob: 5b9b68edd44360511a50c77efd7a86aed8fa2b06 [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/ddk/debug.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/fit/defer.h>
#include <lib/zx/time.h>
#include <zircon/hw/usb.h>
#include <ddktl/fidl.h>
#include "lib/common/ppp.h"
#include "lib/fit/result.h"
#include "lib/hdlc/frame.h"
#include "src/connectivity/ppp/drivers/serial-ppp/serial-ppp-bind.h"
namespace ppp {
namespace netdev = fuchsia_hardware_network;
static constexpr zx_driver_ops_t driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = SerialPpp::Create,
};
constexpr uint64_t kTriggerTxKey = 1;
constexpr uint64_t kTriggerRxKey = 2;
constexpr uint64_t kExitKey = 3;
constexpr uint64_t kWaitSocketReadableKey = 4;
constexpr uint64_t kWaitSocketWritableKey = 5;
static constexpr uint8_t kRxFrameTypes[] = {static_cast<uint8_t>(netdev::wire::FrameType::kIpv4),
static_cast<uint8_t>(netdev::wire::FrameType::kIpv6)};
static constexpr tx_support_t kTxFrameSupport[] = {
{.type = static_cast<uint8_t>(netdev::wire::FrameType::kIpv4),
.features = netdev::wire::kFrameFeaturesRaw,
.supported_flags = 0},
{.type = static_cast<uint8_t>(netdev::wire::FrameType::kIpv4),
.features = netdev::wire::kFrameFeaturesRaw,
.supported_flags = 0}};
SerialPpp::SerialPpp(zx_device_t* parent) : SerialPpp(parent, parent) {}
SerialPpp::SerialPpp(zx_device_t* parent, ddk::SerialProtocolClient serial)
: DeviceType(parent),
serial_protocol_(serial),
vmos_(vmo_store::Options{
.map = vmo_store::MapOptions{.vm_option = ZX_VM_PERM_WRITE | ZX_VM_PERM_READ}}) {}
zx_status_t SerialPpp::Create(void* /*ctx*/, zx_device_t* parent) {
auto dev = std::make_unique<SerialPpp>(parent);
dev->DdkAdd("ppp");
// Release because devmgr is now in charge of the device.
static_cast<void>(dev.release());
return ZX_OK;
}
void SerialPpp::DdkRelease() { delete this; }
void SerialPpp::DdkUnbind(ddk::UnbindTxn txn) {
Shutdown();
txn.Reply();
}
void SerialPpp::Shutdown() {
fbl::AutoLock lock(&state_lock_);
if (!port_.is_valid()) {
return;
}
zx_port_packet_t packet = {.key = kExitKey, .type = ZX_PKT_TYPE_USER, .status = ZX_OK};
zx_status_t status = port_.queue(&packet);
ZX_ASSERT_MSG(status == ZX_OK, "failed to enqueue exit packet: %s", zx_status_get_string(status));
thread_.join();
serial_.reset();
port_.reset();
}
void SerialPpp::WorkerLoop() {
rx_frame_buffer_offset_ = rx_frame_buffer_.begin();
*rx_frame_buffer_offset_++ = kFlagSequence;
bool waiting_readable = false;
zx::time wait_deadline = zx::time::infinite();
auto wait_readable = [&waiting_readable, &wait_deadline, this]() {
if (waiting_readable) {
return;
}
zx_status_t status = serial_.wait_async(port_, kWaitSocketReadableKey, ZX_SOCKET_READABLE, 0);
if (status != ZX_OK) {
zxlogf(WARNING, "failed to wait for readable on serial socket: %s",
zx_status_get_string(status));
return;
}
waiting_readable = true;
// If we have data in our buffer, establish a deadline for more data to come in over serial.
// Silence over serial should be considered a dropped message, i.e. the deadline encodes the
// serial line's "character distance".
if (enable_rx_timeout_ && rx_frame_buffer_offset_ > rx_frame_buffer_.begin() + 1) {
wait_deadline = zx::deadline_after(kSerialTimeout);
} else {
wait_deadline = zx::time::infinite();
}
};
bool waiting_writable = false;
auto wait_writable = [&waiting_writable, this]() {
if (waiting_writable) {
return;
}
zx_status_t status = serial_.wait_async(port_, kWaitSocketWritableKey, ZX_SOCKET_WRITABLE, 0);
if (status != ZX_OK) {
zxlogf(WARNING, "failed to wait for writable on serial socket: %s",
zx_status_get_string(status));
return;
}
waiting_writable = true;
};
for (;;) {
zx_port_packet_t packet;
zx_status_t status = port_.wait(wait_deadline, &packet);
switch (status) {
case ZX_OK:
break;
case ZX_ERR_TIMED_OUT:
// Clear rx buffers if we hit the rx wait deadline. Silence over serial implies dropped
// message.
zxlogf(DEBUG, "rx wait timed out");
wait_deadline = zx::time::infinite();
rx_frame_buffer_offset_ = rx_frame_buffer_.begin();
continue;
default:
zxlogf(ERROR, "failed to wait on port: %s", zx_status_get_string(status));
return;
}
switch (packet.key) {
case kTriggerRxKey: {
// Consume from serial buffer when buffers become available.
if (ConsumeSerial(false)) {
wait_readable();
}
} break;
case kTriggerTxKey: {
wait_writable();
} break;
case kWaitSocketReadableKey: {
waiting_readable = false;
// Consume serial fetching data from socket.
if (ConsumeSerial(true)) {
wait_readable();
}
} break;
case kWaitSocketWritableKey: {
waiting_writable = false;
PendingBuffer buffer;
bool has_more_tx;
{
fbl::AutoLock lock(&tx_lock_);
if (pending_tx_.empty()) {
// No data to send, continue without waiting for writable.
continue;
}
buffer = pending_tx_.front();
pending_tx_.pop();
has_more_tx = !pending_tx_.empty();
}
Protocol protocol;
switch (netdev::wire::FrameType(buffer.type)) {
case netdev::wire::FrameType::kIpv4:
protocol = Protocol::Ipv4;
break;
case netdev::wire::FrameType::kIpv6:
protocol = Protocol::Ipv6;
break;
default:
ZX_PANIC("bad tx frame type %d", buffer.type);
}
// WriteFramed will block until the entire frame is written on the serial socket.
// TODO(fxbug.dev/60230) consider exposing the state machine so we can keep operating on the
// rx side.
zx_status_t status = WriteFramed(FrameView(protocol, buffer.data));
if (status != ZX_OK) {
zxlogf(ERROR, "failed to write tx frame: %s", zx_status_get_string(status));
}
tx_result_t result = {
.id = buffer.id,
.status = status,
};
netdevice_protocol_.CompleteTx(&result, 1);
if (has_more_tx) {
wait_writable();
}
} break;
case kExitKey:
return;
default:
zxlogf(ERROR, "received invalid port packet: %ld", packet.key);
return;
}
}
}
bool SerialPpp::ConsumeSerial(bool fetch_from_socket) {
// Invariant: rx_frame_buffer_ always contains a flag in its first position. |DeserializeFrame|
// requires that the span it receives has a flag in the first and last positions.
ZX_DEBUG_ASSERT(rx_frame_buffer_[0] == kFlagSequence);
bool has_more_rx_space;
{
fbl::AutoLock lock(&rx_lock_);
// Nowhere to put data, continue without waiting for the socket to become readable.
if (rx_space_.empty()) {
return false;
}
has_more_rx_space = true;
}
size_t actual = 0;
if (fetch_from_socket) {
zx_status_t status = serial_.read(0, rx_frame_buffer_offset_,
rx_frame_buffer_.end() - rx_frame_buffer_offset_, &actual);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to read from serial: %s", zx_status_get_string(status));
return true;
}
}
auto limit = rx_frame_buffer_offset_ + actual;
auto frame_begin = rx_frame_buffer_.begin();
for (auto i = rx_frame_buffer_.begin() + 1; i < limit; i++) {
if (*i != kFlagSequence) {
continue;
}
if (*(i - 1) == kFlagSequence) {
frame_begin = i;
continue;
}
// We have a full frame.
auto maybe_frame = DeserializeFrame(fbl::Span(frame_begin, i + 1));
// Data has been copied to target buffer if successful, either way discard from rx buffer.
limit = std::copy(i, limit, rx_frame_buffer_.begin());
ZX_DEBUG_ASSERT(rx_frame_buffer_[0] == kFlagSequence);
frame_begin = rx_frame_buffer_.begin();
i = frame_begin;
if (maybe_frame.is_error()) {
zxlogf(DEBUG, "failed to deserialize frame: %d", maybe_frame.error());
continue;
}
auto frame = maybe_frame.take_value();
netdev::wire::FrameType frame_type;
switch (frame.protocol) {
case Protocol::Ipv4:
frame_type = netdev::wire::FrameType::kIpv4;
break;
case Protocol::Ipv6:
frame_type = netdev::wire::FrameType::kIpv6;
break;
default: {
// Ignore all other protocols.
zxlogf(DEBUG, "ignoring frame for protocol 0x%04X", frame.protocol);
continue;
}
}
rx_buffer_t complete;
{
fbl::AutoLock lock(&rx_lock_);
auto& buffer = rx_space_.front();
auto copied =
std::copy(frame.information.begin(), frame.information.end(), buffer.data.begin());
complete = {.id = buffer.id,
.total_length = static_cast<uint64_t>(copied - buffer.data.begin()),
.meta = buffer_metadata{
.info = frame_info_t{},
.info_type = 0,
.flags = 0,
.frame_type = static_cast<uint8_t>(frame_type),
}};
rx_space_.pop();
has_more_rx_space = !rx_space_.empty();
}
netdevice_protocol_.CompleteRx(&complete, 1);
if (!has_more_rx_space) {
// If we run out of rx space, stop consuming the buffer and save for the next read.
break;
}
}
// Get rid of empty sequences if any.
// Data has been copied to target buffer if successful, either way discard from rx buffer.
if (frame_begin != rx_frame_buffer_.begin()) {
limit = std::copy(frame_begin, limit, rx_frame_buffer_.begin());
}
rx_frame_buffer_offset_ = limit;
if (rx_frame_buffer_offset_ >= rx_frame_buffer_.end()) {
// Discard data if the buffer is full and we weren't able to find frame boundaries.
rx_frame_buffer_offset_ = rx_frame_buffer_.begin() + 1;
}
return has_more_rx_space;
}
zx_status_t SerialPpp::WriteFramed(FrameView frame) {
if (frame.information.size() >= kDefaultMtu) {
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;
}
zx_status_t SerialPpp::NetworkDeviceImplInit(const network_device_ifc_protocol_t* iface) {
zx_status_t status = vmos_.Reserve(MAX_VMOS);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to allocate VMO storage: %s", zx_status_get_string(status));
return status;
}
if (netdevice_protocol_.is_valid()) {
return ZX_ERR_BAD_STATE;
}
netdevice_protocol_ = ddk::NetworkDeviceIfcProtocolClient(iface);
return ZX_OK;
}
void SerialPpp::NetworkDeviceImplStart(network_device_impl_start_callback callback, void* cookie) {
auto complete = fit::defer([callback, cookie]() { callback(cookie); });
zx::socket serial;
zx::port port;
zx_status_t status = serial_protocol_.OpenSocket(&serial);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to open serial connection: %s", zx_status_get_string(status));
return;
}
status = zx::port::create(0, &port);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to create signalling port: %s", zx_status_get_string(status));
return;
}
{
fbl::AutoLock lock(&state_lock_);
serial_ = std::move(serial);
port_ = std::move(port);
thread_ = std::thread([&] { WorkerLoop(); });
}
status_t new_status = {.mtu = kDefaultMtu,
.flags = static_cast<uint32_t>(netdev::wire::StatusFlags::kOnline)};
netdevice_protocol_.StatusChanged(&new_status);
}
void SerialPpp::NetworkDeviceImplStop(network_device_impl_stop_callback callback, void* cookie) {
auto complete = fit::defer([callback, cookie]() { callback(cookie); });
Shutdown();
status_t new_status = {.mtu = kDefaultMtu, .flags = 0};
netdevice_protocol_.StatusChanged(&new_status);
// Clear pending buffers.
{
fbl::AutoLock lock(&rx_lock_);
rx_space_ = {};
}
{
fbl::AutoLock lock(&tx_lock_);
pending_tx_ = {};
}
callback(cookie);
}
void SerialPpp::NetworkDeviceImplGetInfo(device_info_t* out_info) {
*out_info = device_info_t{
.tx_depth = kFifoDepth,
.rx_depth = kFifoDepth,
.rx_threshold = kFifoDepth / 2,
.device_class = static_cast<uint8_t>(netdev::wire::DeviceClass::kPpp),
.rx_types_list = kRxFrameTypes,
.rx_types_count = sizeof(kRxFrameTypes) / sizeof(uint8_t),
.tx_types_list = kTxFrameSupport,
.tx_types_count = sizeof(kTxFrameSupport) / sizeof(tx_support_t),
.max_buffer_length = kMaxBufferSize,
.buffer_alignment = 1,
.min_rx_buffer_length = kDefaultMtu,
};
}
void SerialPpp::NetworkDeviceImplGetStatus(status_t* out_status) {
*out_status = status_t{
.mtu = kDefaultMtu,
.flags = serial_.is_valid() ? static_cast<uint32_t>(netdev::wire::StatusFlags::kOnline) : 0};
}
void SerialPpp::NetworkDeviceImplQueueTx(const tx_buffer_t* buf_list, size_t buf_count) {
std::array<tx_result_t, kFifoDepth> inline_return;
uint32_t inline_return_count = 0;
{
fbl::AutoLock lock(&tx_lock_);
for (auto buffer : fbl::Span(buf_list, buf_count)) {
zx_status_t status = ZX_OK;
if (buffer.data.parts_count == 1) {
auto& data = *buffer.data.parts_list;
auto* stored_vmo = vmos_.GetVmo(buffer.data.vmo_id);
if (stored_vmo) {
pending_tx_.push(
PendingBuffer{.id = buffer.id,
.data = stored_vmo->data().subspan(data.offset, data.length),
.type = buffer.meta.frame_type});
} else {
status = ZX_ERR_INVALID_ARGS;
}
} else {
status = ZX_ERR_NOT_SUPPORTED;
}
if (status != ZX_OK) {
auto& r = inline_return[inline_return_count++];
r.id = buffer.id;
r.status = status;
}
}
}
if (inline_return_count != 0) {
// Immediately return failed tx buffers.
netdevice_protocol_.CompleteTx(inline_return.data(), inline_return_count);
}
if (inline_return_count != buf_count) {
zx_port_packet_t packet = {.key = kTriggerTxKey, .type = ZX_PKT_TYPE_USER, .status = ZX_OK};
zx_status_t status = port_.queue(&packet);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to trigger tx on port: %s", zx_status_get_string(status));
}
}
}
void SerialPpp::NetworkDeviceImplQueueRxSpace(const rx_space_buffer_t* buf_list, size_t buf_count) {
{
fbl::AutoLock lock(&rx_lock_);
for (auto buffer : fbl::Span(buf_list, buf_count)) {
if (buffer.data.parts_count != 1) {
zxlogf(WARNING, "ignoring scatter-gather rx space buffer (%ld parts)",
buffer.data.parts_count);
continue;
}
auto& data = *buffer.data.parts_list;
if (data.length < kDefaultMtu) {
zxlogf(WARNING, "ignoring small rx buffer with size %ld", data.length);
continue;
}
auto* vmo = vmos_.GetVmo(buffer.data.vmo_id);
if (!vmo) {
zxlogf(WARNING, "ignoring rx buffer with invalid VMO id: %d", buffer.data.vmo_id);
continue;
}
rx_space_.push(
PendingBuffer{.id = buffer.id, .data = vmo->data().subspan(data.offset, data.length)});
}
}
zx_port_packet_t packet = {.key = kTriggerRxKey, .type = ZX_PKT_TYPE_USER, .status = ZX_OK};
zx_status_t status = port_.queue(&packet);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to trigger rx on port: %s", zx_status_get_string(status));
}
}
void SerialPpp::NetworkDeviceImplPrepareVmo(uint8_t vmo_id, zx::vmo vmo) {
zx_status_t status = vmos_.RegisterWithKey(vmo_id, std::move(vmo));
if (status != ZX_OK) {
zxlogf(ERROR, "failed to register VMO %d: %s", vmo_id, zx_status_get_string(status));
}
}
void SerialPpp::NetworkDeviceImplReleaseVmo(uint8_t vmo_id) {
zx::status result = vmos_.Unregister(vmo_id);
if (result.is_error()) {
zxlogf(WARNING, "failed to unregister VMO %d: %s", vmo_id, result.status_string());
}
}
void SerialPpp::NetworkDeviceImplSetSnoop(bool snoop) {
// ignored, we're using auto-snoop
}
} // namespace ppp
// clang-format off
ZIRCON_DRIVER(serial-ppp, ppp::driver_ops, "zircon", "0.1");