blob: 97292604bb560d3d317b1a799ad52d36ce60d5e6 [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 "devfs_vnode.h"
#include <lib/ddk/device.h>
#include <string_view>
#include <fbl/string_buffer.h>
#include "driver_host.h"
#include "src/lib/storage/vfs/cpp/vfs_types.h"
namespace {
namespace statecontrol_fidl = fuchsia_hardware_power_statecontrol;
} // namespace
zx_status_t DevfsVnode::OpenNode(fs::Vnode::ValidatedOptions options,
fbl::RefPtr<Vnode>* out_redirect) {
if (dev_->Unbound()) {
return ZX_ERR_IO_NOT_PRESENT;
}
fbl::RefPtr<zx_device_t> new_dev;
zx_status_t status = device_open(dev_, &new_dev, static_cast<uint32_t>(options->ToIoV1Flags()));
if (status != ZX_OK) {
return status;
}
if (new_dev != dev_) {
*out_redirect = new_dev->vnode;
}
return ZX_OK;
}
zx_status_t DevfsVnode::CloseNode() {
zx_status_t status = device_close(dev_, 0);
// If this vnode is for an instance device, drop its reference on close to break
// the reference cycle. This is handled for non-instance devices during the device
// remove path.
if (dev_->flags() & DEV_FLAG_INSTANCE) {
dev_->vnode.reset();
}
return status;
}
zx_status_t DevfsVnode::GetAttributes(fs::VnodeAttributes* a) {
a->mode = V_TYPE_CDEV | V_IRUSR | V_IWUSR;
a->content_size = dev_->GetSizeOp();
a->link_count = 1;
return ZX_OK;
}
fs::VnodeProtocolSet DevfsVnode::GetProtocols() const { return fs::VnodeProtocol::kDevice; }
zx_status_t DevfsVnode::GetNodeInfoForProtocol(fs::VnodeProtocol protocol, fs::Rights rights,
fs::VnodeRepresentation* info) {
if (protocol == fs::VnodeProtocol::kDevice) {
*info = fs::VnodeRepresentation::Device{};
return ZX_OK;
}
return ZX_ERR_NOT_SUPPORTED;
}
void DevfsVnode::HandleFsSpecificMessage(fidl::IncomingMessage& msg, fidl::Transaction* txn) {
if (dev_->Unbound()) {
txn->Close(ZX_ERR_IO_NOT_PRESENT);
return;
}
::fidl::DispatchResult dispatch_result =
fidl::WireTryDispatch<fuchsia_device::Controller>(this, msg, txn);
if (dispatch_result == ::fidl::DispatchResult::kFound) {
return;
}
fidl_incoming_msg_t c_msg = std::move(msg).ReleaseToEncodedCMessage();
auto ddk_txn = MakeDdkInternalTransaction(txn);
zx_status_t status = dev_->MessageOp(&c_msg, ddk_txn.Txn());
if (status != ZX_OK && status != ZX_ERR_ASYNC) {
// Close the connection on any error
txn->Close(status);
}
}
zx_status_t DevfsVnode::Read(void* data, size_t len, size_t off, size_t* out_actual) {
if (dev_->Unbound()) {
return ZX_ERR_IO_NOT_PRESENT;
}
return dev_->ReadOp(data, len, off, out_actual);
}
zx_status_t DevfsVnode::Write(const void* data, size_t len, size_t off, size_t* out_actual) {
if (dev_->Unbound()) {
return ZX_ERR_IO_NOT_PRESENT;
}
return dev_->WriteOp(data, len, off, out_actual);
}
void DevfsVnode::Bind(BindRequestView request, BindCompleter::Sync& completer) {
zx_status_t status =
device_bind(dev_, std::string(request->driver.data(), request->driver.size()).c_str());
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
dev_->set_bind_conn([completer = completer.ToAsync()](zx_status_t status) mutable {
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
completer.ReplySuccess();
}
});
}
}
void DevfsVnode::GetCurrentPerformanceState(GetCurrentPerformanceStateRequestView request,
GetCurrentPerformanceStateCompleter::Sync& completer) {
completer.Reply(dev_->current_performance_state());
}
void DevfsVnode::Rebind(RebindRequestView request, RebindCompleter::Sync& completer) {
dev_->set_rebind_drv_name(std::string(request->driver.data(), request->driver.size()).c_str());
zx_status_t status = device_rebind(dev_.get());
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
// These will be set, until device is unbound and then bound again.
dev_->set_rebind_conn([completer = completer.ToAsync()](zx_status_t status) mutable {
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
completer.ReplySuccess();
}
});
}
}
void DevfsVnode::UnbindChildren(UnbindChildrenRequestView request,
UnbindChildrenCompleter::Sync& completer) {
zx_status_t status = device_schedule_unbind_children(dev_);
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
// The unbind conn will be set until all the children of this device are unbound.
dev_->set_unbind_children_conn([completer = completer.ToAsync()](zx_status_t status) mutable {
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
completer.ReplySuccess();
}
});
}
}
void DevfsVnode::ScheduleUnbind(ScheduleUnbindRequestView request,
ScheduleUnbindCompleter::Sync& completer) {
zx_status_t status = device_schedule_remove(dev_, true /* unbind_self */);
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
completer.ReplySuccess();
}
}
void DevfsVnode::GetTopologicalPath(GetTopologicalPathRequestView request,
GetTopologicalPathCompleter::Sync& completer) {
char buf[fuchsia_device::wire::kMaxDevicePathLen + 1];
size_t actual;
zx_status_t status = dev_->driver_host_context()->GetTopoPath(dev_, buf, sizeof(buf), &actual);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
if (actual > 0) {
// Remove the accounting for the null byte
actual--;
}
auto path = ::fidl::StringView(buf, actual);
completer.ReplySuccess(std::move(path));
}
void DevfsVnode::GetMinDriverLogSeverity(GetMinDriverLogSeverityRequestView request,
GetMinDriverLogSeverityCompleter::Sync& completer) {
if (!dev_->driver) {
completer.Reply(ZX_ERR_UNAVAILABLE, 0);
return;
}
uint8_t severity = fx_logger_get_min_severity(dev_->zx_driver()->logger());
completer.Reply(ZX_OK, severity);
}
void DevfsVnode::SetMinDriverLogSeverity(SetMinDriverLogSeverityRequestView request,
SetMinDriverLogSeverityCompleter::Sync& completer) {
if (!dev_->driver) {
completer.Reply(ZX_ERR_UNAVAILABLE);
return;
}
auto status = dev_->zx_driver()->set_driver_min_log_severity(request->severity);
completer.Reply(status);
}
void DevfsVnode::SetPerformanceState(SetPerformanceStateRequestView request,
SetPerformanceStateCompleter::Sync& completer) {
uint32_t out_state;
zx_status_t status = dev_->driver_host_context()->DeviceSetPerformanceState(
dev_, request->requested_state, &out_state);
completer.Reply(status, out_state);
}
namespace {
// Reply originating from driver.
zx_status_t DdkReply(fidl_txn_t* txn, const fidl_outgoing_msg_t* msg) {
auto message = fidl::OutgoingMessage::FromEncodedCMessage(msg);
// If FromDdkInternalTransaction returns a unique_ptr variant, it will be destroyed when exiting
// this scope.
auto fidl_txn = FromDdkInternalTransaction(ddk::internal::Transaction::FromTxn(txn));
std::visit([&](auto&& arg) { arg->Reply(&message); }, fidl_txn);
return ZX_OK;
}
// Bitmask for checking if a pointer stashed in a ddk::internal::Transaction is from the heap or
// not. This is safe to use on our pointers, because fidl::Transactions are always aligned to more
// than one byte.
static_assert(alignof(fidl::Transaction) > 1);
constexpr uintptr_t kTransactionIsBoxed = 0x1;
} // namespace
ddk::internal::Transaction MakeDdkInternalTransaction(fidl::Transaction* txn) {
device_fidl_txn_t fidl_txn = {};
fidl_txn.txn = {
.reply = DdkReply,
};
fidl_txn.driver_host_context = reinterpret_cast<uintptr_t>(txn);
return ddk::internal::Transaction(fidl_txn);
}
ddk::internal::Transaction MakeDdkInternalTransaction(std::unique_ptr<fidl::Transaction> txn) {
device_fidl_txn_t fidl_txn = {};
fidl_txn.txn = {
.reply = DdkReply,
};
fidl_txn.driver_host_context = reinterpret_cast<uintptr_t>(txn.release()) | kTransactionIsBoxed;
return ddk::internal::Transaction(fidl_txn);
}
std::variant<fidl::Transaction*, std::unique_ptr<fidl::Transaction>> FromDdkInternalTransaction(
ddk::internal::Transaction* txn) {
uintptr_t raw = txn->DriverHostCtx();
ZX_ASSERT_MSG(raw != 0, "Reused a fidl_txn_t!\n");
// Invalidate the source transaction
txn->DeviceFidlTxn()->driver_host_context = 0;
auto ptr = reinterpret_cast<fidl::Transaction*>(raw & ~kTransactionIsBoxed);
if (raw & kTransactionIsBoxed) {
return std::unique_ptr<fidl::Transaction>(ptr);
}
return ptr;
}