| // Copyright 2023 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 "pciroot.h" |
| |
| #include <fidl/fuchsia.hardware.pci/cpp/wire.h> |
| #include <fidl/fuchsia.hardware.platform.bus/cpp/driver/fidl.h> |
| #include <fidl/fuchsia.hardware.platform.bus/cpp/fidl.h> |
| #include <fuchsia/hardware/pciroot/c/banjo.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/driver.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/zx/result.h> |
| #include <lib/zx/vmo.h> |
| #include <stdint.h> |
| #include <zircon/errors.h> |
| #include <zircon/rights.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls/types.h> |
| |
| #include <array> |
| #include <limits> |
| |
| #include <fbl/alloc_checker.h> |
| #include <region-alloc/region-alloc.h> |
| |
| #include "qemu-riscv64.h" |
| |
| namespace fpci = fuchsia_hardware_pci::wire; |
| namespace board_qemu_riscv64 { |
| |
| namespace { |
| |
| // PCIE IRQs are [32, 35] from qemu/hw/riscv/virt.h. |
| constexpr uint8_t kVirtPcieIrqBase = 0x20; |
| constexpr uint8_t kVirtPcieIrqCount = 0x4; |
| |
| // Values per the `virt_memmap` MemMapEntry in qemu/hw/riscv/virt.c. |
| constexpr ralloc_region_t kVirtPcieEcam = {.base = 0x3000'0000, .size = 0x1000'0000}; |
| constexpr ralloc_region_t kVirtPcieMmio = {.base = 0x4000'0000, .size = 0x4000'0000}; |
| constexpr ralloc_region_t kVirtPciePio = {.base = 0x300'0000, .size = 0x10000}; |
| constexpr uint32_t kBytesPerPciBus = |
| fpci::kExtendedConfigSize * FUNCTIONS_PER_DEVICE * DEVICES_PER_BUS; |
| constexpr uint16_t kEndBusNumber = (kVirtPcieEcam.size / kBytesPerPciBus) - 1; |
| constexpr char kPcirootName[] = "PCI0"; |
| |
| constexpr McfgAllocation kVirtPcieMcfg = { |
| .address = kVirtPcieEcam.base, |
| .pci_segment = 0, |
| .start_bus_number = 0, |
| .end_bus_number = kEndBusNumber, |
| }; |
| } // namespace |
| |
| zx_status_t QemuRiscv64Pciroot::PcirootGetBti(uint32_t bdf, uint32_t index, zx::bti* bti) { |
| return iommu_.GetBti(/*iommu_index=*/index, /*bti_id=*/bdf, /*out_handle=*/bti); |
| } |
| |
| zx_status_t QemuRiscv64Pciroot::PcirootGetPciPlatformInfo(pci_platform_info_t* info) { |
| info->start_bus_num = kVirtPcieMcfg.start_bus_number; |
| info->end_bus_num = kVirtPcieMcfg.end_bus_number; |
| info->segment_group = kVirtPcieMcfg.pci_segment; |
| info->legacy_irqs_list = interrupts_.data(); |
| info->legacy_irqs_count = interrupts_.size(); |
| info->irq_routing_list = irq_routing_entries_.data(); |
| info->irq_routing_count = irq_routing_entries_.size(); |
| info->acpi_bdfs_count = 0; |
| strncpy(info->name, kPcirootName, sizeof(kPcirootName)); |
| |
| zx::vmo ecam; |
| if (zx_status_t status = context_.ecam.duplicate(ZX_RIGHT_SAME_RIGHTS, &ecam); status != ZX_OK) { |
| zxlogf(WARNING, "couldn't duplicate ecam handle: %s", zx_status_get_string(status)); |
| } |
| |
| info->ecam_vmo = ecam.release(); |
| return ZX_OK; |
| } |
| |
| zx::result<> QemuRiscv64Pciroot::Create(PciRootHost* root_host, QemuRiscv64Pciroot::Context ctx, |
| zx_device_t* parent, const char* name) { |
| auto pciroot = std::unique_ptr<QemuRiscv64Pciroot>( |
| new QemuRiscv64Pciroot(root_host, std::move(ctx), parent, name)); |
| if (auto result = pciroot->CreateInterrupts(); result.is_error()) { |
| return result.take_error(); |
| } |
| |
| zx_status_t status = |
| pciroot->DdkAdd(ddk::DeviceAddArgs(name).set_inspect_vmo(pciroot->inspect().DuplicateVmo())); |
| if (status == ZX_OK) { |
| [[maybe_unused]] auto ptr = pciroot.release(); |
| } |
| |
| return zx::make_result(status); |
| } |
| |
| zx::result<> QemuRiscv64::PcirootInit() { |
| zx::unowned_resource mmio_resource(get_mmio_resource(parent())); |
| |
| pci_root_host_.mcfgs().push_back(kVirtPcieMcfg); |
| pci_root_host_.Io().AddRegion(kVirtPciePio); |
| pci_root_host_.Mmio64().AddRegion(kVirtPcieMmio); |
| |
| QemuRiscv64Pciroot::Context context{}; |
| zx_status_t status = |
| zx::vmo::create_physical(/*resource=*/*mmio_resource, /*paddr=*/kVirtPcieEcam.base, |
| /*size=*/kVirtPcieEcam.size, /*result=*/&context.ecam); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to allocate ecam vmo for [%#lx, %#lx): %s", kVirtPcieEcam.base, |
| kVirtPcieEcam.base + kVirtPcieEcam.size, zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| |
| return QemuRiscv64Pciroot::Create(&pci_root_host_, std::move(context), parent(), kPcirootName); |
| } |
| |
| zx::result<> QemuRiscv64Pciroot::CreateInterrupts() { |
| zx::unowned_resource irq_resource(get_irq_resource(parent())); |
| |
| for (uint32_t vector_offset = 0; vector_offset < kVirtPcieIrqCount; vector_offset++) { |
| zx::interrupt interrupt; |
| zx_status_t status = zx::interrupt::create(/*resource=*/*irq_resource, |
| /*vector=*/kVirtPcieIrqBase + vector_offset, |
| /*options=*/0, /*result=*/&interrupt); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| interrupts_.push_back(pci_legacy_irq_t{ |
| .interrupt = interrupt.release(), |
| .vector = kVirtPcieIrqBase + vector_offset, |
| }); |
| |
| // All QEMU devices are plugged into the root port and use a pre-defined pin swizzle. |
| for (uint8_t device_id = 0; device_id < DEVICES_PER_BUS; device_id++) { |
| pci_irq_routing_entry_t entry = {.port_device_id = PCI_IRQ_ROUTING_NO_PARENT, |
| .port_function_id = PCI_IRQ_ROUTING_NO_PARENT, |
| .device_id = device_id}; |
| for (uint32_t pin = 0; pin < PINS_PER_FUNCTION; pin++) { |
| entry.pins[pin] = kVirtPcieIrqBase + ((pin + device_id) % PINS_PER_FUNCTION); |
| } |
| irq_routing_entries_.push_back(entry); |
| } |
| } |
| |
| return zx::ok(); |
| } |
| |
| } // namespace board_qemu_riscv64 |