blob: cd2f09304281c981353f59e42df7b02ef28bb587 [file] [log] [blame]
// Copyright 2018 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.h"
#include <fuchsia/hardware/serial/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl-utils/bind.h>
#include <lib/zx/handle.h>
#include <lib/zx/time.h>
#include <zircon/status.h>
#include <zircon/threads.h>
#include <memory>
#include <ddk/debug.h>
#include <fbl/auto_lock.h>
#include <fbl/function.h>
#include "src/devices/serial/drivers/serial-async/serial_bind.h"
namespace serial {
namespace fuchsia = ::llcpp::fuchsia;
zx_status_t SerialDevice::SerialGetInfo(serial_port_info_t* info) { return serial_.GetInfo(info); }
zx_status_t SerialDevice::SerialConfig(uint32_t baud_rate, uint32_t flags) {
return serial_.Config(baud_rate, flags);
}
void SerialDevice::GetClass(GetClassCompleter::Sync& completer) {
completer.Reply(static_cast<fuchsia::hardware::serial::Class>(serial_class_));
}
void SerialDevice::Read(ReadCompleter::Sync& completer) {
if (read_completer_.has_value()) {
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
read_completer_ = completer.ToAsync();
serial_.ReadAsync(
[](void* ctx, zx_status_t status, const void* buffer, size_t length) {
if (status) {
auto completer = std::move(static_cast<SerialDevice*>(ctx)->read_completer_);
static_cast<SerialDevice*>(ctx)->read_completer_.reset();
completer->ReplyError(status);
} else {
auto view = fidl::VectorView<uint8_t>(
fidl::unowned_ptr(const_cast<uint8_t*>(static_cast<const uint8_t*>(buffer))), length);
auto completer = std::move(static_cast<SerialDevice*>(ctx)->read_completer_);
static_cast<SerialDevice*>(ctx)->read_completer_.reset();
completer->ReplySuccess(std::move(view));
}
},
this);
}
void SerialDevice::GetChannel(zx::channel req, GetChannelCompleter::Sync& completer) {
if (loop_.has_value()) {
if (loop_->GetState() == ASYNC_LOOP_SHUTDOWN) {
loop_.reset();
} else {
completer.Close(ZX_ERR_BAD_STATE);
return;
}
}
loop_.emplace(&kAsyncLoopConfigNoAttachToCurrentThread);
loop_->StartThread("serial-thread");
// Invoked when the channel is closed or on any binding-related error.
fidl::OnUnboundFn<llcpp::fuchsia::hardware::serial::NewDevice::Interface> unbound_fn(
[](llcpp::fuchsia::hardware::serial::NewDevice::Interface* dev, fidl::UnbindInfo,
zx::channel) {
auto* device = static_cast<SerialDevice*>(dev);
device->loop_->Quit();
// Unblock DdkRelease() if it was invoked.
sync_completion_signal(&device->on_unbind_);
});
auto binding =
fidl::BindServer(loop_->dispatcher(), std::move(req),
static_cast<llcpp::fuchsia::hardware::serial::NewDevice::Interface*>(this),
std::move(unbound_fn));
if (binding.is_error()) {
loop_.reset();
return;
}
binding_.emplace(std::move(binding.value()));
}
void SerialDevice::Write(fidl::VectorView<uint8_t> data, WriteCompleter::Sync& completer) {
if (write_completer_.has_value()) {
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
write_completer_ = completer.ToAsync();
serial_.WriteAsync(
data.data(), data.count(),
[](void* ctx, zx_status_t status) {
if (status) {
auto completer = std::move(static_cast<SerialDevice*>(ctx)->write_completer_);
static_cast<SerialDevice*>(ctx)->write_completer_.reset();
completer->ReplyError(status);
} else {
auto completer = std::move(static_cast<SerialDevice*>(ctx)->write_completer_);
static_cast<SerialDevice*>(ctx)->write_completer_.reset();
completer->ReplySuccess();
}
},
this);
}
void SerialDevice::SetConfig(fuchsia::hardware::serial::Config config,
SetConfigCompleter::Sync& completer) {
using fuchsia::hardware::serial::CharacterWidth;
using fuchsia::hardware::serial::FlowControl;
using fuchsia::hardware::serial::Parity;
using fuchsia::hardware::serial::StopWidth;
uint32_t flags = 0;
switch (config.character_width) {
case CharacterWidth::BITS_5:
flags |= SERIAL_DATA_BITS_5;
break;
case CharacterWidth::BITS_6:
flags |= SERIAL_DATA_BITS_6;
break;
case CharacterWidth::BITS_7:
flags |= SERIAL_DATA_BITS_7;
break;
case CharacterWidth::BITS_8:
flags |= SERIAL_DATA_BITS_8;
break;
}
switch (config.stop_width) {
case StopWidth::BITS_1:
flags |= SERIAL_STOP_BITS_1;
break;
case StopWidth::BITS_2:
flags |= SERIAL_STOP_BITS_2;
break;
}
switch (config.parity) {
case Parity::NONE:
flags |= SERIAL_PARITY_NONE;
break;
case Parity::EVEN:
flags |= SERIAL_PARITY_EVEN;
break;
case Parity::ODD:
flags |= SERIAL_PARITY_ODD;
break;
}
switch (config.control_flow) {
case FlowControl::NONE:
flags |= SERIAL_FLOW_CTRL_NONE;
break;
case FlowControl::CTS_RTS:
flags |= SERIAL_FLOW_CTRL_CTS_RTS;
break;
}
zx_status_t status = SerialConfig(config.baud_rate, flags);
completer.Reply(status);
}
zx_status_t SerialDevice::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
fuchsia::hardware::serial::NewDeviceProxy::Dispatch(this, msg, &transaction);
return transaction.Status();
}
void SerialDevice::DdkRelease() {
serial_.Enable(false);
if (binding_) {
binding_->Unbind();
sync_completion_wait(&on_unbind_, ZX_TIME_INFINITE);
}
delete this;
}
zx_status_t SerialDevice::Create(void* ctx, zx_device_t* dev) {
std::unique_ptr<SerialDevice> sdev = std::make_unique<SerialDevice>(dev);
zx_status_t status;
if ((status = sdev->Init()) != ZX_OK) {
return status;
}
if ((status = sdev->Bind()) != ZX_OK) {
zxlogf(ERROR, "SerialDevice::Create: Bind failed");
sdev.release()->DdkRelease();
return status;
}
// devmgr is now in charge of the device.
__UNUSED auto* dummy = sdev.release();
return ZX_OK;
}
zx_status_t SerialDevice::Init() {
if (!serial_.is_valid()) {
zxlogf(ERROR, "SerialDevice::Init: ZX_PROTOCOL_SERIAL_IMPL_ASYNC not available");
return ZX_ERR_NOT_SUPPORTED;
}
serial_port_info_t info;
zx_status_t status = serial_.GetInfo(&info);
if (status != ZX_OK) {
zxlogf(ERROR, "SerialDevice::Init: SerialImpl::GetInfo failed %d", status);
return status;
}
serial_class_ = info.serial_class;
return ZX_OK;
}
zx_status_t SerialDevice::Bind() {
zx_device_prop_t props[] = {
{BIND_PROTOCOL, 0, ZX_PROTOCOL_SERIAL},
{BIND_SERIAL_CLASS, 0, serial_class_},
};
return DdkAdd(ddk::DeviceAddArgs("serial-async").set_props(props));
}
static constexpr zx_driver_ops_t serial_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = SerialDevice::Create;
return ops;
}();
} // namespace serial
// The formatter does not play nice with these macros.
// clang-format off
ZIRCON_DRIVER(serial, serial::serial_driver_ops, "zircon", "*0.1");
// clang-format on