blob: 25a5d8d9876611758a437587e09cb2c73334e5a6 [file] [log] [blame]
// Copyright 2022 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 "token_manager.h"
#include <lib/zx/channel.h>
#include "src/devices/bin/driver_runtime/dispatcher.h"
#include "src/devices/bin/driver_runtime/handle.h"
namespace driver_runtime {
namespace {
// Verifies |token| is a valid channel handle, and returns the corresponding token id.
// If |use_primary_koid| is true, the token id will be the koid of |token|, otherwise it will
// be the koid of the peer of |token|.
zx::result<TokenManager::TokenId> ValidateToken(zx_handle_t token, bool use_primary_koid) {
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(token, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), NULL, NULL);
if (status != ZX_OK) {
return zx::error(ZX_ERR_BAD_HANDLE);
}
if (info.type != ZX_OBJ_TYPE_CHANNEL) {
return zx::error(ZX_ERR_BAD_HANDLE);
}
TokenManager::TokenId token_id = use_primary_koid ? info.koid : info.related_koid;
return zx::ok(token_id);
}
} // namespace
zx_status_t TokenManager::Register(zx_handle_t token, fdf_dispatcher_t* dispatcher,
fdf_token_t* fdf_token) {
auto validate_status = ValidateToken(token, true /* use_primary_koid */);
if (!validate_status.is_ok()) {
zx_handle_close(token);
return validate_status.status_value();
}
TokenId token_id = *validate_status;
zx::channel validated_token(token);
if (!dispatcher || !fdf_token) {
return ZX_ERR_INVALID_ARGS;
}
fbl::AutoLock lock(&lock_);
// If registering with the dispatcher fails, we will drop our token handle,
// and the peer token handle will be notified of ZX_CHANNEL_PEER_CLOSED,
zx_status_t status = dispatcher->RegisterPendingToken(fdf_token);
if (status != ZX_OK) {
return status;
}
auto request = pending_tokens_.find(token_id);
if (request.IsValid()) {
// A transfer request matching our |token_id| was previously requested,
// schedule the transfer callback to be called now.
auto pending_token_info = pending_tokens_.erase(request);
ZX_ASSERT(pending_token_info);
ZX_ASSERT(pending_token_info->state() == PendingTokenInfo::State::kTransferRequested);
return pending_token_info->OnCallbackRegister(dispatcher, fdf_token);
}
// No transfer has been requested for this |token_id| yet.
auto pending_token_info = std::make_unique<RegisteredCallback>(
token_id, std::move(validated_token), dispatcher, fdf_token);
// Listen for peer token handle closed in case they drop their token.
// It is safe to do this before inserting |pending_token_info| into the map as we are holding
// |lock_|.
status = WaitOnPeerClosedLocked(pending_token_info.get());
if (status != ZX_OK) {
return status;
}
pending_tokens_.insert(std::move(pending_token_info));
return ZX_OK;
}
zx_status_t TokenManager::Transfer(zx_handle_t token, fdf_handle_t handle) {
// Retrieve the token id using the koid of the channel peer, so we can locate the
// corresponding registered protocol.
auto validate_status = ValidateToken(token, false /* use_primary_koid */);
if (!validate_status.is_ok()) {
zx_handle_close(token);
fdf_handle_close(handle);
return validate_status.status_value();
}
TokenId token_id = *validate_status;
zx::channel validated_token(token);
fbl::AutoLock lock(&lock_);
// TODO(https://fxbug.dev/42167305): we should also check the correct driver owns the handle once possible.
if (!Handle::HandleExists(handle)) {
return ZX_ERR_BAD_HANDLE;
}
// TODO(https://fxbug.dev/42056822): replace fdf::Channel with a generic C++ handle type when available.
fdf::Channel validated_fdf_channel = fdf::Channel(handle);
auto registered_callback = pending_tokens_.find(token_id);
if (registered_callback.IsValid()) {
// A token transfer callback matching our token was previously registered, schedule it to
// be called.
auto pending_token_info = pending_tokens_.erase(registered_callback);
ZX_ASSERT(pending_token_info);
ZX_ASSERT(pending_token_info->state() == PendingTokenInfo::State::kCallbackRegistered);
return pending_token_info->OnTransferRequest(std::move(validated_fdf_channel));
}
auto pending_token_info = std::make_unique<TransferRequest>(token_id, std::move(validated_token),
std::move(validated_fdf_channel));
// Listen for peer token handle closed in case they drop their token.
// It is safe to do this before inserting |pending_token_info| into the map as we are holding
// |lock_|.
zx_status_t status = WaitOnPeerClosedLocked(pending_token_info.get());
if (status != ZX_OK) {
return status;
}
pending_tokens_.insert(std::move(pending_token_info));
return ZX_OK;
}
zx_status_t TokenManager::WaitOnPeerClosedLocked(PendingTokenInfo* pending_token) {
// For token transfer callback registrations, we want to use the dispatcher provided,
// so that we will be automatically notified if the dispatcher shuts down.
// For token transfer requests, no dispatcher is provided, so we use the global dispatcher.
async_dispatcher_t* dispatcher =
pending_token->dispatcher() ? pending_token->dispatcher() : global_dispatcher();
ZX_ASSERT(dispatcher != nullptr);
pending_token->wait().set_handler([this, pending_token](async_dispatcher_t* dispatcher,
async::Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
fbl::AutoLock lock(&lock_);
if (status == ZX_OK) {
if (signal->trigger & ZX_CHANNEL_PEER_CLOSED) {
pending_token->OnPeerClosed();
}
} else {
ZX_ASSERT_MSG(status == ZX_ERR_CANCELED, "WaitOnPeerClosed got unexpected error %d", status);
// If the wait is cancelled due to a dispatcher shutting down, the dispatcher will
// handle calling the client's handler in |Dispatcher::CompleteShutdown|.
}
ZX_ASSERT(pending_tokens_.erase(pending_token->token_id()) != nullptr);
});
return pending_token->wait().Begin(dispatcher);
}
void TokenManager::RegisteredCallback::OnPeerClosed() {
ZX_ASSERT(fdf_token_ != nullptr);
ZX_ASSERT(dispatcher_ != nullptr);
auto status = dispatcher_->ScheduleTokenCallback(fdf_token_, ZX_ERR_CANCELED, fdf::Channel());
// This may fail if the dispatcher is shutting down. In that case the dispatcher is
// going to send the cancellation callback in |CompleteShutdown|.
ZX_ASSERT((status == ZX_OK) || (status == ZX_ERR_BAD_STATE));
}
zx_status_t TokenManager::RegisteredCallback::OnTransferRequest(fdf::Channel channel) {
ZX_ASSERT(channel.is_valid());
ZX_ASSERT(fdf_token_ != nullptr);
ZX_ASSERT(dispatcher_ != nullptr);
return dispatcher_->ScheduleTokenCallback(fdf_token_, ZX_OK, std::move(channel));
}
zx_status_t TokenManager::TransferRequest::OnCallbackRegister(fdf_dispatcher_t* dispatcher,
fdf_token_t* fdf_token) {
ZX_ASSERT(channel_.is_valid());
ZX_ASSERT(fdf_token != nullptr);
ZX_ASSERT(dispatcher != nullptr);
return dispatcher->ScheduleTokenCallback(fdf_token, ZX_OK, std::move(channel_));
}
} // namespace driver_runtime