blob: 4675cc85b72894cdbb5e8c5eed33cc3db4226c54 [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 "devfs-connection.h"
#include <fuchsia/io/c/fidl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#include <src/storage/deprecated-fs-fidl-handler/fidl-handler.h>
#include "devhost.h"
#include "log.h"
#include "zircon/system/ulib/fbl/include/fbl/ref_ptr.h"
namespace devmgr {
namespace {
zx_status_t Reply(fidl_txn_t* txn, const fidl_msg_t* msg) {
auto* connection = Connection::FromTxn(txn);
if (connection->devfs_connection()->last_txid == connection->Txid()) {
connection->devfs_connection()->reply_called = true;
}
auto header = reinterpret_cast<fidl_message_header_t*>(msg->bytes);
header->txid = connection->Txid();
return connection->channel()->write(0, msg->bytes, msg->num_bytes, msg->handles,
msg->num_handles);
}
// Reply originating from driver.
zx_status_t DdkReply(fidl_txn_t* txn, const fidl_msg_t* msg) {
auto connection = Connection(ddk::Connection::FromTxn(txn));
return Reply(connection.Txn(), msg);
}
// Don't actually send anything on a channel when completing this operation.
// This is useful for testing "close" requests.
zx_status_t NullReply(fidl_txn_t* reply, const fidl_msg_t* msg) { return ZX_OK; }
} // namespace
Connection::Connection(fidl_txn_t txn, zx_txid_t txid, fbl::RefPtr<DevfsConnection> conn)
: txn_(std::move(txn)), txid_(std::move(txid)), conn_(conn) {
conn_->dev->outstanding_transactions++;
}
Connection::Connection(const Connection& other)
: txn_(other.txn_), txid_(other.txid_), conn_(other.conn_) {
conn_->dev->outstanding_transactions++;
}
Connection& Connection::operator=(const Connection& other) {
txn_ = other.txn_;
txid_ = other.txid_;
conn_ = other.conn_;
conn_->dev->outstanding_transactions++;
return *this;
}
Connection::Connection(const ddk::Connection* conn)
: txn_(*conn->Txn()),
txid_(conn->Txid()),
conn_(fbl::ImportFromRawPtr(reinterpret_cast<DevfsConnection*>(conn->DevhostContext()))) {}
Connection::~Connection() {
if (conn_.get() != nullptr) {
conn_->dev->outstanding_transactions--;
}
}
ddk::Connection Connection::ToDdkConnection() {
fidl_txn_t txn = {
.reply = DdkReply,
};
ZX_ASSERT(conn_.get() != nullptr);
auto internal = reinterpret_cast<uintptr_t>(fbl::ExportToRawPtr(&conn_));
return ddk::Connection(txn, txid_, internal);
}
void DevfsConnection::Bind(::fidl::StringView driver, BindCompleter::Sync completer) {
char drv_libname[fuchsia_device_MAX_DRIVER_PATH_LEN + 1];
memcpy(drv_libname, driver.data(), driver.size());
drv_libname[driver.size()] = 0;
zx_status_t status = device_bind(dev, drv_libname);
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 DevfsConnection::GetDevicePerformanceStates(
GetDevicePerformanceStatesCompleter::Sync completer) {
auto& perf_states = dev->GetPerformanceStates();
ZX_DEBUG_ASSERT(perf_states.size() == fuchsia_device_MAX_DEVICE_PERFORMANCE_STATES);
::fidl::Array<::llcpp::fuchsia::device::DevicePerformanceStateInfo, 20> states{};
for (size_t i = 0; i < fuchsia_device_MAX_DEVICE_PERFORMANCE_STATES; i++) {
states[i] = perf_states[i];
}
completer.Reply(states, ZX_OK);
}
void DevfsConnection::Rebind(::fidl::StringView driver, RebindCompleter::Sync completer) {
char drv_libname[fuchsia_device_MAX_DRIVER_PATH_LEN + 1];
memcpy(drv_libname, driver.data(), driver.size());
drv_libname[driver.size()] = 0;
dev->set_rebind_drv_name(drv_libname);
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 DevfsConnection::ScheduleUnbind(ScheduleUnbindCompleter::Sync completer) {
zx_status_t status = device_schedule_remove(this->dev, true /* unbind_self */);
if (status != ZX_OK) {
completer.ReplyError(status);
} else {
completer.ReplySuccess();
}
}
void DevfsConnection::GetDriverName(GetDriverNameCompleter::Sync completer) {
if (!this->dev->driver) {
return completer.Reply(ZX_ERR_NOT_SUPPORTED, {});
}
const char* name = this->dev->driver->name();
if (name == nullptr) {
name = "unknown";
}
completer.Reply(ZX_OK, {name, strlen(name)});
}
void DevfsConnection::GetDeviceName(GetDeviceNameCompleter::Sync completer) {
completer.Reply({this->dev->name, strlen(this->dev->name)});
}
void DevfsConnection::GetTopologicalPath(GetTopologicalPathCompleter::Sync completer) {
char buf[fuchsia_device_MAX_DEVICE_PATH_LEN + 1];
size_t actual;
zx_status_t status = devhost_get_topo_path(this->dev, buf, sizeof(buf), &actual);
if (status != ZX_OK) {
return completer.ReplyError(status);
}
if (actual > 0) {
// Remove the accounting for the null byte
actual--;
}
auto path = ::fidl::StringView(buf, actual);
completer.ReplySuccess(path);
}
void DevfsConnection::GetEventHandle(GetEventHandleCompleter::Sync completer) {
zx::eventpair event;
zx_status_t status = this->dev->event.duplicate(ZX_RIGHTS_BASIC, &event);
static_assert(fuchsia_device_DEVICE_SIGNAL_READABLE == DEV_STATE_READABLE);
static_assert(fuchsia_device_DEVICE_SIGNAL_WRITABLE == DEV_STATE_WRITABLE);
static_assert(fuchsia_device_DEVICE_SIGNAL_ERROR == DEV_STATE_ERROR);
static_assert(fuchsia_device_DEVICE_SIGNAL_HANGUP == DEV_STATE_HANGUP);
static_assert(fuchsia_device_DEVICE_SIGNAL_OOB == DEV_STATE_OOB);
// TODO(teisenbe): The FIDL definition erroneously describes this as an event rather than
// eventpair.
completer.Reply(status, zx::event(event.release()));
}
void DevfsConnection::GetDriverLogFlags(GetDriverLogFlagsCompleter::Sync completer) {
if (!this->dev->driver) {
return completer.Reply(ZX_ERR_UNAVAILABLE, 0);
}
uint32_t flags = this->dev->driver->driver_rec()->log_flags;
completer.Reply(ZX_OK, flags);
}
void DevfsConnection::SetDriverLogFlags(uint32_t clear_flags, uint32_t set_flags,
SetDriverLogFlagsCompleter::Sync completer) {
if (!this->dev->driver) {
return completer.Reply(ZX_ERR_UNAVAILABLE);
}
uint32_t flags = this->dev->driver->driver_rec()->log_flags;
flags &= ~clear_flags;
flags |= set_flags;
this->dev->driver->driver_rec()->log_flags = flags;
completer.Reply(ZX_OK);
}
void DevfsConnection::DebugResume(DebugResumeCompleter::Sync completer) {
completer.Reply(this->dev->ResumeOp(0));
}
void DevfsConnection::RunCompatibilityTests(int64_t hook_wait_time,
RunCompatibilityTestsCompleter::Sync completer) {
auto status = device_run_compatibility_tests(dev, hook_wait_time);
if (status) {
dev->PushTestCompatibilityConn([completer = completer.ToAsync()](zx_status_t status) mutable {
completer.Reply(std::move(status));
});
}
};
void DevfsConnection::GetDevicePowerCaps(GetDevicePowerCapsCompleter::Sync completer) {
// For now, the result is always a successful response because the device itself is not added
// without power states validation. In future, we may add more checks for validation, and the
// error result will be put to use.
::llcpp::fuchsia::device::Controller_GetDevicePowerCaps_Response response{};
auto& states = dev->GetPowerStates();
ZX_DEBUG_ASSERT(states.size() == fuchsia_device_MAX_DEVICE_POWER_STATES);
for (size_t i = 0; i < fuchsia_device_MAX_DEVICE_POWER_STATES; i++) {
response.dpstates[i] = states[i];
}
completer.Reply(
::llcpp::fuchsia::device::Controller_GetDevicePowerCaps_Result::WithResponse(&response));
};
void DevfsConnection::SetPerformanceState(uint32_t requested_state,
SetPerformanceStateCompleter::Sync completer) {
uint32_t out_state;
zx_status_t status = devhost_device_set_performance_state(dev, requested_state, &out_state);
return completer.Reply(status, out_state);
}
void DevfsConnection::ConfigureAutoSuspend(
bool enable, ::llcpp::fuchsia::device::DevicePowerState requested_state,
ConfigureAutoSuspendCompleter::Sync completer) {
zx_status_t status = devhost_device_configure_auto_suspend(dev, enable, requested_state);
return completer.Reply(status);
}
void DevfsConnection::UpdatePowerStateMapping(
::fidl::Array<::llcpp::fuchsia::device::SystemPowerStateInfo,
::llcpp::fuchsia::device::manager::MAX_SYSTEM_POWER_STATES>
mapping,
UpdatePowerStateMappingCompleter::Sync completer) {
::llcpp::fuchsia::device::Controller_UpdatePowerStateMapping_Response response;
std::array<::llcpp::fuchsia::device::SystemPowerStateInfo,
::llcpp::fuchsia::device::manager::MAX_SYSTEM_POWER_STATES>
states_mapping;
for (size_t i = 0; i < fuchsia_device_manager_MAX_SYSTEM_POWER_STATES; i++) {
states_mapping[i] = mapping[i];
}
zx_status_t status = dev->SetSystemPowerStateMapping(states_mapping);
if (status != ZX_OK) {
return completer.Reply(
::llcpp::fuchsia::device::Controller_UpdatePowerStateMapping_Result::WithErr(&status));
}
completer.Reply(
::llcpp::fuchsia::device::Controller_UpdatePowerStateMapping_Result::WithResponse(&response));
}
void DevfsConnection::GetPowerStateMapping(GetPowerStateMappingCompleter::Sync completer) {
::llcpp::fuchsia::device::Controller_GetPowerStateMapping_Response response;
auto& mapping = dev->GetSystemPowerStateMapping();
ZX_DEBUG_ASSERT(mapping.size() == fuchsia_device_manager_MAX_SYSTEM_POWER_STATES);
for (size_t i = 0; i < fuchsia_device_manager_MAX_SYSTEM_POWER_STATES; i++) {
response.mapping[i] = mapping[i];
}
completer.Reply(
::llcpp::fuchsia::device::Controller_GetPowerStateMapping_Result::WithResponse(&response));
};
void DevfsConnection::Suspend(::llcpp::fuchsia::device::DevicePowerState requested_state,
SuspendCompleter::Sync completer) {
dev->suspend_cb = [completer = completer.ToAsync()](zx_status_t status,
uint8_t out_state) mutable {
completer.Reply(status, static_cast<::llcpp::fuchsia::device::DevicePowerState>(out_state));
};
devhost_device_suspend_new(dev, requested_state);
}
void DevfsConnection::Resume(::llcpp::fuchsia::device::DevicePowerState requested_state,
ResumeCompleter::Sync completer) {
::llcpp::fuchsia::device::DevicePowerState out_state;
zx_status_t status = devhost_device_resume_new(this->dev, requested_state, &out_state);
if (status != ZX_OK) {
return completer.Reply(::llcpp::fuchsia::device::Controller_Resume_Result::WithErr(&status));
}
::llcpp::fuchsia::device::Controller_Resume_Response response;
response.out_state = out_state;
completer.Reply(::llcpp::fuchsia::device::Controller_Resume_Result::WithResponse(&response));
}
void DevfsConnection::HandleRpc(fbl::RefPtr<DevfsConnection>&& conn, async_dispatcher_t* dispatcher,
async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
log(ERROR, "devhost: devfs conn wait error: %d\n", status);
return;
}
if (signal->observed & ZX_CHANNEL_READABLE) {
status = conn->ReadMessage([&conn](fidl_msg_t* msg, Connection* txn) {
zx_status_t s = devhost_fidl_handler(msg, txn->Txn(), conn.get());
return s;
});
if (status == ZX_OK) {
// Stop accepting new requests once we are unbound.
if (!(conn->dev->flags & DEV_FLAG_UNBOUND)) {
BeginWait(std::move(conn), dispatcher);
}
return;
}
} else if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
conn->CloseMessage([&conn](fidl_msg_t* msg, Connection* txn) {
return devhost_fidl_handler(msg, txn->Txn(), conn.get());
});
} else {
printf("dh_handle_fidl_rpc: invalid signals %x\n", signal->observed);
abort();
}
// We arrive here if devhost_fidl_handler was a clean close (ERR_DISPATCHER_DONE),
// or close-due-to-error (non-ZX_OK), or if the channel was closed
// out from under us. In all cases, we are done with this connection, so we
// will destroy it by letting it leave scope.
log(TRACE, "devhost: destroying devfs conn %p\n", conn.get());
}
zx_status_t DevfsConnection::ReadMessage(FidlDispatchFunction dispatch) {
ZX_ASSERT(channel()->get_info(ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr) == ZX_OK);
uint8_t bytes[ZXFIDL_MAX_MSG_BYTES];
zx_handle_t handles[ZXFIDL_MAX_MSG_HANDLES];
fidl_msg_t msg = {
.bytes = bytes,
.handles = handles,
.num_bytes = 0,
.num_handles = 0,
};
zx_status_t r = channel()->read(0, bytes, handles, countof(bytes), countof(handles),
&msg.num_bytes, &msg.num_handles);
if (r != ZX_OK) {
return r;
}
if (msg.num_bytes < sizeof(fidl_message_header_t)) {
zx_handle_close_many(msg.handles, msg.num_handles);
return ZX_ERR_IO;
}
auto header = reinterpret_cast<fidl_message_header_t*>(msg.bytes);
fidl_txn_t txn = {
.reply = Reply,
};
Connection connection(txn, header->txid, fbl::RefPtr(this));
this->last_txid = header->txid;
this->reply_called = false;
// Callback is responsible for decoding the message, and closing
// any associated handles.
r = dispatch(&msg, &connection);
if (r != ZX_OK && r != ZX_ERR_ASYNC && !this->reply_called) {
// The transaction wasn't handed back to us, so we must manually remove reference count to
// prevent leak.
ZX_ASSERT(Release() == false);
dev->outstanding_transactions--;
}
return (r == ZX_ERR_ASYNC) ? ZX_OK : r;
}
zx_status_t DevfsConnection::CloseMessage(FidlDispatchFunction dispatch) {
fuchsia_io_NodeCloseRequest request;
memset(&request, 0, sizeof(request));
fidl_init_txn_header(&request.hdr, 0, fuchsia_io_NodeCloseGenOrdinal);
fidl_msg_t msg = {
.bytes = &request,
.handles = NULL,
.num_bytes = sizeof(request),
.num_handles = 0u,
};
fidl_txn_t txn = {
.reply = NullReply,
};
Connection connection(txn, 0, fbl::RefPtr(this));
// Remote side was closed.
dispatch(&msg, &connection);
return ERR_DISPATCHER_DONE;
}
} // namespace devmgr