| // Copyright 2018 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 <endian.h> |
| #include <inttypes.h> |
| #include <zircon/compiler.h> |
| #include <zircon/hw/i2c.h> |
| #include <zircon/syscalls/resource.h> |
| #include <zircon/types.h> |
| |
| #include <acpica/acpi.h> |
| #include <ddk/debug.h> |
| #include <ddk/protocol/auxdata.h> |
| #include <ddk/protocol/pciroot.h> |
| #include <ddk/protocol/sysmem.h> |
| #include <pci/pio.h> |
| |
| #include "acpi-private.h" |
| #include "dev.h" |
| #include "errors.h" |
| #include "iommu.h" |
| #include "pci.h" |
| #include "pci_allocators.h" |
| |
| static ACPI_STATUS find_pci_child_callback(ACPI_HANDLE object, uint32_t nesting_level, |
| void* context, void** out_value) { |
| ACPI_DEVICE_INFO* info; |
| ACPI_STATUS acpi_status = AcpiGetObjectInfo(object, &info); |
| if (acpi_status != AE_OK) { |
| zxlogf(TRACE, "bus-acpi: AcpiGetObjectInfo failed %d\n", acpi_status); |
| return acpi_status; |
| } |
| ACPI_FREE(info); |
| ACPI_OBJECT obj = { |
| .Type = ACPI_TYPE_INTEGER, |
| }; |
| ACPI_BUFFER buffer = { |
| .Length = sizeof(obj), |
| .Pointer = &obj, |
| }; |
| acpi_status = AcpiEvaluateObject(object, (char*)"_ADR", NULL, &buffer); |
| if (acpi_status != AE_OK) { |
| return AE_OK; |
| } |
| uint32_t addr = *(uint32_t*)context; |
| ACPI_HANDLE* out_handle = (ACPI_HANDLE*)out_value; |
| if (addr == obj.Integer.Value) { |
| *out_handle = object; |
| return AE_CTRL_TERMINATE; |
| } else { |
| return AE_OK; |
| } |
| } |
| |
| static ACPI_STATUS pci_child_data_resources_callback(ACPI_RESOURCE* res, void* context) { |
| pci_child_auxdata_ctx_t* ctx = (pci_child_auxdata_ctx_t*)context; |
| auxdata_i2c_device_t* child = ctx->data + ctx->i; |
| |
| if (res->Type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { |
| return AE_NOT_FOUND; |
| } |
| if (res->Data.I2cSerialBus.Type != ACPI_RESOURCE_SERIAL_TYPE_I2C) { |
| return AE_NOT_FOUND; |
| } |
| |
| ACPI_RESOURCE_I2C_SERIALBUS* i2c = &res->Data.I2cSerialBus; |
| child->is_bus_controller = i2c->SlaveMode; |
| child->ten_bit = i2c->AccessMode; |
| child->address = i2c->SlaveAddress; |
| child->bus_speed = i2c->ConnectionSpeed; |
| |
| return AE_CTRL_TERMINATE; |
| } |
| |
| static ACPI_STATUS pci_child_data_callback(ACPI_HANDLE object, uint32_t nesting_level, |
| void* context, void** out_value) { |
| pci_child_auxdata_ctx_t* ctx = (pci_child_auxdata_ctx_t*)context; |
| if ((ctx->i + 1) > ctx->max) { |
| return AE_CTRL_TERMINATE; |
| } |
| |
| auxdata_i2c_device_t* data = ctx->data + ctx->i; |
| data->protocol_id = ZX_PROTOCOL_I2C; |
| |
| ACPI_DEVICE_INFO* info = NULL; |
| ACPI_STATUS acpi_status = AcpiGetObjectInfo(object, &info); |
| if (acpi_status == AE_OK) { |
| // These length fields count the trailing NUL. |
| // Publish HID |
| if ((info->Valid & ACPI_VALID_HID) && info->HardwareId.Length <= HID_LENGTH + 1) { |
| const char* hid = info->HardwareId.String; |
| data->props[data->propcount].id = BIND_ACPI_HID_0_3; |
| data->props[data->propcount++].value = htobe32(*((uint32_t*)(hid))); |
| data->props[data->propcount].id = BIND_ACPI_HID_4_7; |
| data->props[data->propcount++].value = htobe32(*((uint32_t*)(hid + 4))); |
| } |
| // Check for I2C HID devices via CID |
| if ((info->Valid & ACPI_VALID_CID) && info->CompatibleIdList.Count > 0) { |
| ACPI_PNP_DEVICE_ID* cid = &info->CompatibleIdList.Ids[0]; |
| if (cid->Length <= CID_LENGTH + 1) { |
| if (!strncmp(cid->String, I2C_HID_CID_STRING, CID_LENGTH)) { |
| data->props[data->propcount].id = BIND_I2C_CLASS; |
| data->props[data->propcount++].value = I2C_CLASS_HID; |
| } |
| data->props[data->propcount].id = BIND_ACPI_CID_0_3; |
| data->props[data->propcount++].value = htobe32(*((uint32_t*)(cid->String))); |
| data->props[data->propcount].id = BIND_ACPI_CID_4_7; |
| data->props[data->propcount++].value = htobe32(*((uint32_t*)(cid->String + 4))); |
| } |
| } |
| ACPI_FREE(info); |
| } |
| ZX_ASSERT(data->propcount <= AUXDATA_MAX_DEVPROPS); |
| |
| // call _CRS to get i2c info |
| acpi_status = AcpiWalkResources(object, (char*)"_CRS", pci_child_data_resources_callback, ctx); |
| if ((acpi_status == AE_OK) || (acpi_status == AE_CTRL_TERMINATE)) { |
| ctx->i++; |
| } |
| return AE_OK; |
| } |
| |
| static zx_status_t pciroot_op_get_auxdata(void* context, const char* args, void* data, size_t bytes, |
| size_t* actual) { |
| acpi_device_t* dev = (acpi_device_t*)context; |
| |
| char type[16]; |
| uint32_t bus_id, dev_id, func_id; |
| int n; |
| if ((n = sscanf(args, "%[^,],%02x:%02x:%02x", type, &bus_id, &dev_id, &func_id)) != 4) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zxlogf(SPEW, "bus-acpi: get_auxdata type '%s' device %02x:%02x:%02x\n", type, bus_id, dev_id, |
| func_id); |
| |
| if (strcmp(type, "i2c-child")) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if (bytes < (2 * sizeof(uint32_t))) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| ACPI_HANDLE pci_node = NULL; |
| uint32_t addr = (dev_id << 16) | func_id; |
| |
| // Look for the child node with this device and function id |
| ACPI_STATUS acpi_status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, dev->ns_node, 1, |
| find_pci_child_callback, NULL, &addr, &pci_node); |
| if ((acpi_status != AE_OK) && (acpi_status != AE_CTRL_TERMINATE)) { |
| return acpi_to_zx_status(acpi_status); |
| } |
| if (pci_node == NULL) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| memset(data, 0, bytes); |
| |
| // Look for as many children as can fit in the provided buffer |
| pci_child_auxdata_ctx_t ctx = { |
| .max = static_cast<uint8_t>(bytes / sizeof(auxdata_i2c_device_t)), |
| .i = 0, |
| .data = static_cast<auxdata_i2c_device_t*>(data), |
| }; |
| |
| acpi_status = |
| AcpiWalkNamespace(ACPI_TYPE_DEVICE, pci_node, 1, pci_child_data_callback, NULL, &ctx, NULL); |
| if ((acpi_status != AE_OK) && (acpi_status != AE_CTRL_TERMINATE)) { |
| *actual = 0; |
| return acpi_to_zx_status(acpi_status); |
| } |
| |
| *actual = ctx.i * sizeof(auxdata_i2c_device_t); |
| |
| zxlogf(SPEW, "bus-acpi: get_auxdata '%s' %u devs actual %zu\n", args, ctx.i, *actual); |
| |
| return ZX_OK; |
| } |
| |
| static zx_status_t pciroot_op_get_bti(void* context, uint32_t bdf, uint32_t index, |
| zx_handle_t* bti) { |
| // The x86 IOMMU world uses PCI BDFs as the hardware identifiers, so there |
| // will only be one BTI per device. |
| if (index != 0) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| // For dummy IOMMUs, the bti_id just needs to be unique. For Intel IOMMUs, |
| // the bti_ids correspond to PCI BDFs. |
| zx_handle_t iommu_handle; |
| zx_status_t status = iommu_manager_iommu_for_bdf(bdf, &iommu_handle); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return zx_bti_create(iommu_handle, 0, bdf, bti); |
| } |
| |
| static zx_status_t pciroot_op_connect_sysmem(void* context, zx_handle_t handle) { |
| acpi_device_t* dev = (acpi_device_t*)context; |
| sysmem_protocol_t sysmem; |
| zx_status_t status = device_get_protocol(dev->platform_bus, ZX_PROTOCOL_SYSMEM, &sysmem); |
| if (status != ZX_OK) { |
| zx_handle_close(handle); |
| return status; |
| } |
| return sysmem_connect(&sysmem, handle); |
| } |
| |
| #ifdef ENABLE_USER_PCI |
| zx_status_t Pciroot::PcirootGetAuxdata(const char* args, void* data, size_t bytes, size_t* actual) { |
| return pciroot_op_get_auxdata(c_context(), args, data, bytes, actual); |
| } |
| |
| zx_status_t Pciroot::PcirootGetBti(uint32_t bdf, uint32_t index, zx::bti* bti) { |
| return pciroot_op_get_bti(c_context(), bdf, index, bti->reset_and_get_address()); |
| } |
| |
| zx_status_t Pciroot::PcirootConnectSysmem(zx::handle handle) { |
| sysmem_protocol_t sysmem; |
| zx_status_t status = device_get_protocol(platform_bus_, ZX_PROTOCOL_SYSMEM, &sysmem); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return sysmem_connect(&sysmem, handle.release()); |
| } |
| |
| zx_status_t Pciroot::PcirootGetPciPlatformInfo(pci_platform_info_t* info) { |
| *info = ctx_->info; |
| return ZX_OK; |
| } |
| |
| zx_status_t Pciroot::PcirootGetPciIrqInfo(pci_irq_info_t* info) { return ZX_ERR_NOT_SUPPORTED; } |
| |
| bool Pciroot::PcirootDriverShouldProxyConfig(void) { |
| // If we have no mcfg then all config access will need to be through IOports which |
| // are proxied over pciroot. |
| return !pci_platform_has_mcfg(); |
| } |
| |
| zx_status_t Pciroot::PcirootConfigRead8(const pci_bdf_t* address, uint16_t offset, uint8_t* value) { |
| return pci_pio_read8(*address, static_cast<uint8_t>(offset), value); |
| } |
| |
| zx_status_t Pciroot::PcirootConfigRead16(const pci_bdf_t* address, uint16_t offset, |
| uint16_t* value) { |
| return pci_pio_read16(*address, static_cast<uint8_t>(offset), value); |
| } |
| |
| zx_status_t Pciroot::PcirootConfigRead32(const pci_bdf_t* address, uint16_t offset, |
| uint32_t* value) { |
| return pci_pio_read32(*address, static_cast<uint8_t>(offset), value); |
| } |
| |
| zx_status_t Pciroot::PcirootConfigWrite8(const pci_bdf_t* address, uint16_t offset, uint8_t value) { |
| return pci_pio_write8(*address, static_cast<uint8_t>(offset), value); |
| } |
| |
| zx_status_t Pciroot::PcirootConfigWrite16(const pci_bdf_t* address, uint16_t offset, |
| uint16_t value) { |
| return pci_pio_write16(*address, static_cast<uint8_t>(offset), value); |
| } |
| |
| zx_status_t Pciroot::PcirootConfigWrite32(const pci_bdf_t* address, uint16_t offset, |
| uint32_t value) { |
| return pci_pio_write32(*address, static_cast<uint8_t>(offset), value); |
| } |
| |
| zx_status_t Pciroot::PcirootAllocMsiBlock(uint64_t requested_irqs, bool can_target_64bit, |
| msi_block_t* out_block) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Pciroot::PcirootFreeMsiBlock(const msi_block_t* block) { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t Pciroot::PcirootMaskUnmaskMsi(uint64_t msi_id, bool mask) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Pciroot::PcirootGetAddressSpace(size_t size, zx_paddr_t in_base, |
| pci_address_space_t type, bool low, |
| zx_paddr_t* out_base, zx::resource* out_resource) { |
| RegionAllocator* alloc = nullptr; |
| uint32_t rsrc_kind = ZX_RSRC_KIND_MMIO; |
| // Grab the correct allocator and check for overflow conditions at the same time |
| // because compiler overflow detecting deduces the check based on type sizes. |
| if (type == PCI_ADDRESS_SPACE_MMIO) { |
| if (low || in_base + size < UINT32_MAX) { |
| uint32_t overflow; |
| if (in_base && add_overflow(in_base, size, &overflow)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| alloc = Get32BitMmioAllocator(); |
| } else { |
| uint64_t overflow; |
| if (in_base && add_overflow(in_base, size, &overflow)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| alloc = Get64BitMmioAllocator(); |
| } |
| } else { |
| rsrc_kind = ZX_RSRC_KIND_IOPORT; |
| alloc = GetIoAllocator(); |
| } |
| |
| // If |out_base| is set then we have been requested to find address space |
| // starting at a given |base|. |
| RegionAllocator::Region::UPtr region_uptr; |
| zx_status_t status; |
| const ralloc_region_t region = { |
| .base = in_base, |
| .size = size, |
| }; |
| |
| // Some address space requests will want a given address / size because they are for |
| // devices already configured by the bios at boot. |
| if (in_base) { |
| status = alloc->GetRegion(region, region_uptr); |
| } else { |
| status = alloc->GetRegion(static_cast<uint64_t>(size), region_uptr); |
| } |
| |
| if (status != ZX_OK) { |
| zxlogf(TRACE, "pciroot: failed to get region { %#lx-%#lx, type = %s, low = %d }: %d.\n", |
| in_base, in_base + size, (type == PCI_ADDRESS_SPACE_MMIO) ? "mmio" : "io", low, status); |
| return status; |
| } |
| |
| // Names will be generated in the format of: PCI### [mm]io ##bit |
| char name[ZX_MAX_NAME_LEN] = {}; |
| snprintf(name, sizeof(name), "%s %s", ctx_->name, |
| (type == PCI_ADDRESS_SPACE_MMIO) ? ((low) ? "mmio 32bit" : "mmio 64bit") : "io"); |
| // Craft a resource handle for the other end. This handle will be held |
| // within the Root allocation in the pci bus driver will encompass the |
| // entirety of the address space it requested. |
| // Please do not use get_root_resource() in new code. See ZX-1467. |
| status = zx_resource_create(get_root_resource(), rsrc_kind | ZX_RSRC_FLAG_EXCLUSIVE, |
| region_uptr->base, region_uptr->size, name, sizeof(name), |
| out_resource->reset_and_get_address()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| *out_base = region_uptr->base; |
| // Discard the lifecycle aspect of the returned pointer, we'll be tracking it on the bus |
| // side of things. |
| region_uptr.release(); |
| zxlogf(TRACE, "pciroot: assigned [ %#lx-%#lx, type = %s, size = %#lx ] to bus driver.\n", |
| *out_base, *out_base + size, (type == PCI_ADDRESS_SPACE_MMIO) ? "mmio" : "io", size); |
| return ZX_OK; |
| } |
| |
| zx_status_t Pciroot::PcirootFreeAddressSpace(uint64_t base, size_t len, pci_address_space_t type) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Pciroot::Create(fbl::unique_ptr<pciroot_ctx_t> ctx, zx_device_t* parent, |
| zx_device_t* platform_bus, const char* name) { |
| fbl::AllocChecker ac; |
| auto pciroot = new (&ac) Pciroot(std::move(ctx), parent, platform_bus, name); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| return pciroot->DdkAdd(name); |
| } |
| |
| #else // TODO(cja): remove after the switch to userspace pci |
| static zx_status_t pciroot_op_get_pci_platform_info(void*, pci_platform_info_t*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_get_pci_irq_info(void*, pci_irq_info_t*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static bool pciroot_op_driver_should_proxy_config(void* ctx) { return false; } |
| |
| static zx_status_t pciroot_op_config_read8(void*, const pci_bdf_t*, uint16_t, uint8_t*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_config_read16(void*, const pci_bdf_t*, uint16_t, uint16_t*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_config_read32(void*, const pci_bdf_t*, uint16_t, uint32_t*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_config_write8(void*, const pci_bdf_t*, uint16_t, uint8_t) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_config_write16(void*, const pci_bdf_t*, uint16_t, uint16_t) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_config_write32(void*, const pci_bdf_t*, uint16_t, uint32_t) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_alloc_msi_block(void*, uint64_t, bool, msi_block_t*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_free_msi_block(void*, const msi_block_t*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_mask_unmask_msi(void*, uint64_t, bool) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_get_address_space(void*, size_t, zx_paddr_t, pci_address_space_t, |
| bool, zx_paddr_t*, zx_handle_t*) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t pciroot_op_free_address_space(void*, zx_paddr_t, size_t, pci_address_space_t) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static pciroot_protocol_ops_t pciroot_proto = { |
| .get_auxdata = pciroot_op_get_auxdata, |
| .get_bti = pciroot_op_get_bti, |
| .connect_sysmem = pciroot_op_connect_sysmem, |
| .get_pci_platform_info = pciroot_op_get_pci_platform_info, |
| .get_pci_irq_info = pciroot_op_get_pci_irq_info, |
| .driver_should_proxy_config = pciroot_op_driver_should_proxy_config, |
| .config_read8 = pciroot_op_config_read8, |
| .config_read16 = pciroot_op_config_read16, |
| .config_read32 = pciroot_op_config_read32, |
| .config_write8 = pciroot_op_config_write8, |
| .config_write16 = pciroot_op_config_write16, |
| .config_write32 = pciroot_op_config_write32, |
| .alloc_msi_block = pciroot_op_alloc_msi_block, |
| .free_msi_block = pciroot_op_free_msi_block, |
| .mask_unmask_msi = pciroot_op_mask_unmask_msi, |
| .get_address_space = pciroot_op_get_address_space, |
| .free_address_space = pciroot_op_free_address_space, |
| }; |
| |
| pciroot_protocol_ops_t* get_pciroot_ops(void) { return &pciroot_proto; } |
| |
| #endif // ENABLE_USER_PCI |