blob: 9136adf9cc94b221d3cd85cf219fcade9a0f5ca9 [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 <errno.h>
#include <fcntl.h>
#include <fidl/fuchsia.hardware.sysmem/cpp/fidl.h>
#include <fidl/fuchsia.sysmem/cpp/fidl.h>
#include <fidl/fuchsia.sysmem2/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/async/cpp/wait.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/watcher.h>
#include <lib/fidl/cpp/channel.h>
#include <lib/fit/function.h>
#include <lib/sysmem-connector/sysmem-connector.h>
#include <lib/zx/channel.h>
#include <lib/zx/process.h>
#include <stdio.h>
#include <threads.h>
#include <queue>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <fbl/unique_fd.h>
// The actual sysmem FIDL server is in the sysmem driver. The code that watches
// for the driver and sends sysmem service requests to the driver is in
// ulib/sysmem-connector. The code here just needs to queue requests to
// sysmem-connector.
// Clients interact with sysmem-connector using a C ABI, but we want to use C++
// for the implementation. We have SysmemConnector inherit from an empty
// sysmem_connector struct just to make the SysmemConnector class officially be
// a class not a struct, and to make the SysmemConnector name consistent with
// C++ coding conventions, and have member names with "_" at the end consistent
// with coding conventions, etc.
//
// Every instance of sysmem_connector is actually a SysmemConnector and vice
// versa.
struct sysmem_connector {
// Intentionally declared as empty; never instantiated; see SysmemConnector.
};
class SysmemConnector : public sysmem_connector {
// public in this case just means public to this file. The interface is via
// the functions declared in lib/sysmem-connector/sysmem-connector.h.
public:
SysmemConnector(const char* sysmem_directory_path, bool terminate_on_sysmem_connection_failure);
zx_status_t Start();
using QueueItem = std::variant<fidl::ServerEnd<fuchsia_sysmem::Allocator>,
fidl::ServerEnd<fuchsia_sysmem2::Allocator>,
fidl::ClientEnd<fuchsia_io::Directory>>;
void Queue(QueueItem&& queue_item);
void Stop();
private:
void Post(fit::closure to_run);
zx_status_t DeviceAdded(int dirfd, int event, const char* filename);
bool ConnectToSysmemDriver();
void ProcessQueue();
void OnSysmemPeerClosed(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal);
//
// Set once during construction + Start(), never set again.
//
// directory of device instances
const char* sysmem_directory_path_{};
async::Loop process_queue_loop_;
thrd_t process_queue_thrd_{};
bool terminate_on_sysmem_connection_failure_ = false;
//
// Only touched from process_queue_loop_'s one thread.
//
fidl::ClientEnd<fuchsia_hardware_sysmem::DriverConnector> driver_connector_client_;
async::WaitMethod<SysmemConnector, &SysmemConnector::OnSysmemPeerClosed> wait_sysmem_peer_closed_;
//
// Synchronized using lock_.
//
fbl::Mutex lock_;
std::queue<QueueItem> connection_requests_ __TA_GUARDED(lock_);
};
SysmemConnector::SysmemConnector(const char* sysmem_directory_path,
bool terminate_on_sysmem_connection_failure)
: sysmem_directory_path_(sysmem_directory_path),
process_queue_loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
terminate_on_sysmem_connection_failure_(terminate_on_sysmem_connection_failure),
wait_sysmem_peer_closed_(this) {
ZX_DEBUG_ASSERT(sysmem_directory_path_);
}
zx_status_t SysmemConnector::Start() {
// The process_queue_thrd_ is filled out before any code that checks it runs on the thread,
// because the current thread is the only thread that triggers anything to happen on the thread
// being created here, and that is only done after process_queue_thrd_ is filled out by the
// current thread.
const zx_status_t status =
process_queue_loop_.StartThread("SysmemConnector-ProcessQueue", &process_queue_thrd_);
if (status != ZX_OK) {
printf("sysmem-connector: process_queue_loop_.StartThread(): %s\n",
zx_status_get_string(status));
return status;
}
// Establish initial connection to sysmem driver async.
Post([this] { ConnectToSysmemDriver(); });
return ZX_OK;
}
void SysmemConnector::Queue(QueueItem&& queue_item) {
ZX_DEBUG_ASSERT(thrd_current() != process_queue_thrd_);
bool trigger_needed;
{ // scope lock
const fbl::AutoLock lock(&lock_);
trigger_needed = connection_requests_.empty();
connection_requests_.emplace(std::move(queue_item));
} // ~lock
if (trigger_needed) {
Post([this] { ProcessQueue(); });
}
}
void SysmemConnector::Stop() {
ZX_DEBUG_ASSERT(thrd_current() != process_queue_thrd_);
process_queue_loop_.Quit();
process_queue_loop_.JoinThreads();
process_queue_loop_.Shutdown();
}
void SysmemConnector::Post(fit::closure to_run) {
const zx_status_t status = async::PostTask(process_queue_loop_.dispatcher(), std::move(to_run));
// We don't expect this post to ever fail.
ZX_ASSERT_MSG(status == ZX_OK, "%s", zx_status_get_string(status));
}
zx_status_t SysmemConnector::DeviceAdded(int dirfd, int event, const char* filename) {
ZX_DEBUG_ASSERT(thrd_current() == process_queue_thrd_);
if (std::string_view{filename} == ".") {
return ZX_OK;
}
if (event != WATCH_EVENT_ADD_FILE) {
// Keep going on IDLE or REMOVE. There's nothing else useful that the
// current thread can do until a sysmem device instance is available,
// and we don't have any reason to attempt to directly handle any
// REMOVE(s) since we'll do fdio_watch_directory() again later from
// scratch instead.
return ZX_OK;
}
{
const fdio_cpp::UnownedFdioCaller caller(dirfd);
zx::result status = component::ConnectAt<fuchsia_hardware_sysmem::DriverConnector>(
caller.borrow_as<fuchsia_io::Directory>(), filename);
if (status.is_error()) {
printf("sysmem-connector: component::ConnectAt(%s, %s): %s\n", sysmem_directory_path_,
filename, status.status_string());
// If somehow connecting to this device instance fails, keep watching for
// another device instance.
return ZX_OK;
}
driver_connector_client_ = std::move(status.value());
}
if (terminate_on_sysmem_connection_failure_) {
wait_sysmem_peer_closed_.set_trigger(ZX_CHANNEL_PEER_CLOSED);
wait_sysmem_peer_closed_.set_object(driver_connector_client_.channel().get());
const zx_status_t status = wait_sysmem_peer_closed_.Begin(process_queue_loop_.dispatcher());
ZX_ASSERT_MSG(status == ZX_OK, "async::WaitMethod<OnSysmemPeerClosed>::Begin: %s",
zx_status_get_string(status));
// Cancel() doesn't need to be called anywhere because this process will
// terminate immediately if the wait ever completes.
}
char process_name[ZX_MAX_NAME_LEN];
const zx_status_t status =
zx::process::self()->get_property(ZX_PROP_NAME, process_name, sizeof(process_name));
ZX_ASSERT_MSG(status == ZX_OK, "%s", zx_status_get_string(status));
printf("sysmem-connector: %s connected to sysmem driver %s\n", process_name, filename);
return ZX_ERR_STOP;
}
bool SysmemConnector::ConnectToSysmemDriver() {
ZX_DEBUG_ASSERT(thrd_current() == process_queue_thrd_);
ZX_DEBUG_ASSERT(!driver_connector_client_);
fbl::unique_fd sysmem_dir_fd;
{
const int fd = open(sysmem_directory_path_, O_DIRECTORY | O_RDONLY);
if (fd < 0) {
if (terminate_on_sysmem_connection_failure_) {
ZX_PANIC("open(%s): %s", sysmem_directory_path_, strerror(errno));
} else {
printf("sysmem-connector: open(%s): %s\n", sysmem_directory_path_, strerror(errno));
}
return false;
}
sysmem_dir_fd.reset(fd);
}
ZX_DEBUG_ASSERT(sysmem_dir_fd);
// Returns ZX_ERR_STOP as soon as one of the 000, 001 device instances is
// found. We rely on those to go away if the corresponding sysmem instance
// is no longer operational, so that we don't find them when we call
// ConnectToSysmemDriver() again upon discovering that we can't send to a
// previous device instance. When terminate_on_sysmem_connection_failure_,
// there won't be any instances after 000 fails because sysmem_connector will
// terminate and sysmem_connector is a critical process.
//
// TODO(dustingreen): Currently if this watch never finds a sysmem device
// instance, then sysmem_connector_release() will block forever. This can
// be fixed once it's feasible to use DeviceWatcher (or similar) here
// instead (currently DeviceWatcher is in garnet not zircon).
const zx_status_t status = fdio_watch_directory(
sysmem_dir_fd.get(),
[](int dirfd, int event, const char* fn, void* cookie) {
ZX_DEBUG_ASSERT(cookie);
SysmemConnector* connector = static_cast<SysmemConnector*>(cookie);
return connector->DeviceAdded(dirfd, event, fn);
},
ZX_TIME_INFINITE, this);
if (status != ZX_ERR_STOP) {
if (terminate_on_sysmem_connection_failure_) {
ZX_PANIC("fdio_watch_directory(%s): %s", sysmem_directory_path_,
zx_status_get_string(status));
} else {
printf("sysmem-connector: fdio_watch_directory(%s): %s\n", sysmem_directory_path_,
zx_status_get_string(status));
}
return false;
}
ZX_DEBUG_ASSERT(driver_connector_client_);
return true;
}
namespace {
// Helpers from the reference documentation for std::visit<>, to allow
// visit-by-overload of the std::variant<> returned by GetLastReference():
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20)
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
} // namespace
void SysmemConnector::ProcessQueue() {
ZX_DEBUG_ASSERT(thrd_current() == process_queue_thrd_);
while (true) {
QueueItem queue_item;
{ // scope lock
const fbl::AutoLock lock(&lock_);
if (connection_requests_.empty()) {
return;
}
queue_item = std::move(connection_requests_.front());
connection_requests_.pop();
} // ~lock
if (!driver_connector_client_) {
if (!ConnectToSysmemDriver()) {
// ~queue_item - we'll try again to connect to a sysmem instance next
// time a request comes in, but any given request gets a max of one
// attempt to connect to a sysmem device instance, in case attempts to
// find a sysmem device instance are just failing.
return;
}
}
ZX_DEBUG_ASSERT(driver_connector_client_);
const auto [name, status] = std::visit(
overloaded{
[this](fidl::ServerEnd<fuchsia_sysmem::Allocator> allocator_request) {
return std::make_pair("ConnectV1", fidl::WireCall(driver_connector_client_)
->ConnectV1(std::move(allocator_request))
.status());
},
[this](fidl::ServerEnd<fuchsia_sysmem2::Allocator> allocator_request) {
return std::make_pair("ConnectV2", fidl::WireCall(driver_connector_client_)
->ConnectV2(std::move(allocator_request))
.status());
},
[this](fidl::ClientEnd<fuchsia_io::Directory> service_directory) {
return std::make_pair("SetAuxServiceDirectory",
fidl::WireCall(driver_connector_client_)
->SetAuxServiceDirectory(std::move(service_directory))
.status());
}},
std::move(queue_item));
if (status != ZX_OK) {
printf("sysmem-connector: fuchsia.sysmem/DriverConnect.%s: %s\n", name,
zx_status_get_string(status));
driver_connector_client_.reset();
}
}
}
void SysmemConnector::OnSysmemPeerClosed(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
// Else we wouldn't have started the wait that is now completing.
ZX_ASSERT(terminate_on_sysmem_connection_failure_);
// Any other wait status is unexpected, so terminate this process.
ZX_ASSERT_MSG(status == ZX_OK, "%s", zx_status_get_string(status));
// This signal is set because we only waited on this signal.
ZX_ASSERT_MSG((signal->observed & ZX_CHANNEL_PEER_CLOSED) != 0, "0x%x", signal->observed);
// Terminate sysmem_connector, which is a critical process, so this will do a hard reboot.
ZX_PANIC(
"sysmem_connector's connection to sysmem has closed; sysmem driver failed - "
"terminating process to trigger reboot.");
}
zx_status_t sysmem_connector_init(const char* sysmem_directory_path,
bool terminate_on_sysmem_connection_failure,
sysmem_connector_t** out_connector) {
SysmemConnector* connector =
new SysmemConnector(sysmem_directory_path, terminate_on_sysmem_connection_failure);
const zx_status_t status = connector->Start();
if (status != ZX_OK) {
printf("sysmem_connector_init() connector->Start() failed - status: %s\n",
zx_status_get_string(status));
return status;
}
*out_connector = connector;
return ZX_OK;
}
void sysmem_connector_queue_connection_request_v1(sysmem_connector_t* connector_param,
zx_handle_t allocator_request_param) {
zx::channel allocator_request(allocator_request_param);
ZX_DEBUG_ASSERT(connector_param);
ZX_DEBUG_ASSERT(allocator_request);
SysmemConnector* connector = static_cast<SysmemConnector*>(connector_param);
connector->Queue(fidl::ServerEnd<fuchsia_sysmem::Allocator>{std::move(allocator_request)});
}
void sysmem_connector_queue_connection_request_v2(sysmem_connector_t* connector_param,
zx_handle_t allocator_request_param) {
zx::channel allocator_request(allocator_request_param);
ZX_DEBUG_ASSERT(connector_param);
ZX_DEBUG_ASSERT(allocator_request);
SysmemConnector* connector = static_cast<SysmemConnector*>(connector_param);
connector->Queue(fidl::ServerEnd<fuchsia_sysmem2::Allocator>{std::move(allocator_request)});
}
void sysmem_connector_queue_service_directory(sysmem_connector_t* connector_param,
zx_handle_t service_directory_param) {
printf("sysmem_connector_queue_service_directory\n");
zx::channel service_directory(service_directory_param);
ZX_DEBUG_ASSERT(connector_param);
ZX_DEBUG_ASSERT(service_directory);
SysmemConnector* connector = static_cast<SysmemConnector*>(connector_param);
connector->Queue(fidl::ClientEnd<fuchsia_io::Directory>{std::move(service_directory)});
}
void sysmem_connector_release(sysmem_connector_t* connector_param) {
ZX_DEBUG_ASSERT(connector_param);
SysmemConnector* connector = static_cast<SysmemConnector*>(connector_param);
connector->Stop();
delete connector;
}