| // Copyright 2017 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. |
| |
| #ifndef SRC_VIRTUALIZATION_BIN_VMM_VIRTIO_DEVICE_H_ |
| #define SRC_VIRTUALIZATION_BIN_VMM_VIRTIO_DEVICE_H_ |
| |
| #include <fidl/fuchsia.virtualization.hardware/cpp/fidl.h> |
| #include <fuchsia/component/cpp/fidl.h> |
| #include <fuchsia/virtualization/hardware/cpp/fidl.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/trace-engine/types.h> |
| #include <lib/trace/event.h> |
| |
| #include <atomic> |
| |
| #include "src/lib/fsl/handles/object_info.h" |
| #include "src/virtualization/bin/vmm/device/config.h" |
| #include "src/virtualization/bin/vmm/device/virtio_queue.h" |
| #include "src/virtualization/bin/vmm/virtio_pci.h" |
| |
| // Set of features that are supported transparently for all devices. |
| static constexpr uint32_t kVirtioFeatures = 0; |
| |
| constexpr zx_status_t noop_notify_queue(uint16_t queue) { return ZX_OK; } |
| constexpr zx_status_t noop_config_device(uint64_t addr, const IoValue& value) { return ZX_OK; } |
| |
| // Interface for all virtio device components. |
| template <uint8_t DeviceId, uint16_t NumQueues, typename ConfigType> |
| class VirtioComponentDevice { |
| public: |
| PciDevice* pci_device() { return &pci_; } |
| |
| protected: |
| VirtioComponentDevice(std::string_view name, const PhysMem& phys_mem, uint32_t device_features, |
| VirtioDeviceConfig::ConfigQueueFn config_queue, |
| VirtioDeviceConfig::ConfigDeviceFn config_device, |
| VirtioDeviceConfig::ReadyDeviceFn ready_device) |
| : phys_mem_(phys_mem), |
| device_config_{ |
| .device_id = DeviceId, |
| // Advertise support for common/bus features. |
| .device_features = device_features | kVirtioFeatures, |
| .config = &config_, |
| .config_size = sizeof(ConfigType), |
| .queue_configs = queue_configs_, |
| .num_queues = NumQueues, |
| .config_queue = std::move(config_queue), |
| .notify_queue = noop_notify_queue, |
| .config_device = std::move(config_device), |
| .ready_device = std::move(ready_device), |
| }, |
| pci_(&device_config_, name) { |
| zx_status_t status = zx::event::create(0, &event_); |
| FX_CHECK(status == ZX_OK) << "Failed to create event"; |
| event_koid_ = fsl::GetKoid(event_.get()); |
| wait_.set_object(event_.get()); |
| wait_.set_trigger(ZX_USER_SIGNAL_ALL); |
| } |
| |
| VirtioComponentDevice(std::string_view name, const PhysMem& phys_mem, uint32_t device_features, |
| VirtioDeviceConfig::ConfigQueueFn config_queue, |
| VirtioDeviceConfig::ReadyDeviceFn ready_device) |
| : VirtioComponentDevice(name, phys_mem, device_features, std::move(config_queue), |
| noop_config_device, std::move(ready_device)) {} |
| |
| ~VirtioComponentDevice() { |
| if (realm_.is_bound()) { |
| ::fuchsia::component::Realm_DestroyChild_Result result; |
| realm_->DestroyChild({.name = component_name_, .collection = collection_name_}, &result); |
| } |
| } |
| |
| zx_status_t PrepStart(const zx::guest& guest, async_dispatcher_t* dispatcher, |
| fuchsia::virtualization::hardware::StartInfo* start_info) { |
| zx_status_t status = wait_.Begin(dispatcher); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Communicate the allocated notify BAR address/size to the component. |
| const PciBar& bar = this->pci_.notify_bar(); |
| ZX_DEBUG_ASSERT(bar.addr() != 0); // BAR address should have been allocated by now. |
| start_info->trap = {.addr = bar.addr(), .size = align(bar.size(), PAGE_SIZE)}; |
| |
| // Give the component access to the guest and guest memory. |
| status = guest.duplicate(ZX_RIGHT_TRANSFER | ZX_RIGHT_WRITE, &start_info->guest); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = event().duplicate(ZX_RIGHT_TRANSFER | ZX_RIGHT_SIGNAL, &start_info->event); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return this->phys_mem_.vmo().duplicate( |
| ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHTS_IO | ZX_RIGHT_MAP, &start_info->vmo); |
| } |
| |
| zx_status_t PrepStart(const zx::guest& guest, async_dispatcher_t* dispatcher, |
| fuchsia_virtualization_hardware::wire::StartInfo* start_info) { |
| fuchsia::virtualization::hardware::StartInfo hlcpp_start_info; |
| zx_status_t status = PrepStart(guest, dispatcher, &hlcpp_start_info); |
| if (status == ZX_OK) { |
| start_info->trap.addr = hlcpp_start_info.trap.addr; |
| start_info->trap.size = hlcpp_start_info.trap.size; |
| start_info->guest = std::move(hlcpp_start_info.guest); |
| start_info->event = std::move(hlcpp_start_info.event); |
| start_info->vmo = std::move(hlcpp_start_info.vmo); |
| } |
| return status; |
| } |
| |
| const zx::event& event() const { return event_; } |
| |
| // Sets interrupt flag, and possibly sends interrupt to the driver. |
| zx_status_t Interrupt(uint8_t actions) { |
| if (actions & VirtioQueue::SET_QUEUE) { |
| pci_.add_isr_flags(VirtioPci::ISR_QUEUE); |
| } |
| if (actions & VirtioQueue::SET_CONFIG) { |
| pci_.add_isr_flags(VirtioPci::ISR_CONFIG); |
| } |
| if (actions & VirtioQueue::TRY_INTERRUPT) { |
| return pci_.Interrupt(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t CreateDynamicComponent( |
| ::sys::ComponentContext* context, const char* collection_name, const char* component_name, |
| const char* component_url, |
| fit::function<zx_status_t(std::shared_ptr<sys::ServiceDirectory> services)> callback) { |
| component_name_ = component_name; |
| collection_name_ = collection_name; |
| |
| zx_status_t status = context->svc()->Connect(realm_.NewRequest()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Virtio device controller failed to connect to realm"; |
| return status; |
| } |
| |
| fuchsia::component::decl::Child child_decl; |
| child_decl.set_name(component_name) |
| .set_url(component_url) |
| .set_startup(fuchsia::component::decl::StartupMode::LAZY) |
| .set_on_terminate(fuchsia::component::decl::OnTerminate::NONE); |
| |
| fuchsia::component::Realm_CreateChild_Result create_res; |
| status = realm_->CreateChild({.name = collection_name}, std::move(child_decl), |
| fuchsia::component::CreateChildArgs(), &create_res); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to send CreateChild message"; |
| return status; |
| } |
| |
| if (create_res.is_err()) { |
| FX_LOGS(ERROR) << "Failed to create dynamic child: " |
| << static_cast<int64_t>(create_res.err()); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| fuchsia::component::Realm_OpenExposedDir_Result open_res; |
| fidl::InterfaceHandle<fuchsia::io::Directory> exposed_dir; |
| status = realm_->OpenExposedDir({.name = component_name, .collection = collection_name}, |
| exposed_dir.NewRequest(), &open_res); |
| if (status != ZX_OK || open_res.is_err()) { |
| FX_PLOGS(ERROR, status) |
| << "Failed to OpenExposedDir on dynamic child. Realm_OpenExposedDir_Result: " |
| << static_cast<int64_t>(open_res.err()); |
| if (status == ZX_OK) { |
| status = ZX_ERR_NOT_FOUND; |
| } |
| return status; |
| } |
| |
| auto child_services = std::make_shared<sys::ServiceDirectory>(std::move(exposed_dir)); |
| return callback(std::move(child_services)); |
| } |
| |
| const PhysMem& phys_mem_; |
| ConfigType config_ __TA_GUARDED(device_config_.mutex) = {}; |
| VirtioDeviceConfig device_config_; |
| VirtioPci pci_; |
| VirtioQueueConfig queue_configs_[NumQueues] __TA_GUARDED(device_config_.mutex) = {}; |
| |
| private: |
| void OnInterrupt(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| if (status != ZX_OK) { |
| return; |
| } |
| TRACE_FLOW_END("machina", "device:interrupt", event_koid_); |
| status = event_.signal(signal->trigger, 0); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to clear interrupt signal " << status; |
| return; |
| } |
| status = this->Interrupt(signal->observed >> kDeviceInterruptShift); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to raise device interrupt " << status; |
| return; |
| } |
| status = wait->Begin(dispatcher); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to wait for interrupt " << status; |
| } |
| } |
| |
| zx::event event_; |
| zx_koid_t event_koid_; |
| async::WaitMethod<VirtioComponentDevice, &VirtioComponentDevice::OnInterrupt> wait_{this}; |
| |
| std::string component_name_; |
| std::string collection_name_; |
| ::fuchsia::component::RealmSyncPtr realm_; |
| }; |
| |
| #endif // SRC_VIRTUALIZATION_BIN_VMM_VIRTIO_DEVICE_H_ |