| // 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 |