diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c --- a/sys/dev/acpica/acpi.c +++ b/sys/dev/acpica/acpi.c @@ -193,6 +193,7 @@ static void acpi_hint_device_unit(device_t acdev, device_t child, const char *name, int *unitp); static void acpi_reset_interfaces(device_t dev); +static bus_dma_tag_t acpi_get_dma_tag(device_t dev, device_t child); static device_method_t acpi_methods[] = { /* Device interface */ @@ -229,6 +230,7 @@ DEVMETHOD(bus_get_domain, acpi_get_domain), DEVMETHOD(bus_get_property, acpi_bus_get_prop), DEVMETHOD(bus_get_device_path, acpi_get_device_path), + DEVMETHOD(bus_get_dma_tag, acpi_get_dma_tag), /* ACPI bus */ DEVMETHOD(acpi_id_probe, acpi_device_id_probe), @@ -451,6 +453,139 @@ return (0); } +struct dma_limits { + bus_addr_t lowaddr; +}; + +static ACPI_STATUS +dma_on_resource(ACPI_RESOURCE *res, void *arg) +{ + struct dma_limits *limits = arg; + bus_addr_t min, max; + + /* + * Note: + * + * BUS_SPACE_MAXADDR used in .lowaddr of dma_limits is the last-address of the + * last page of the range, such as 0xffffffff. dma_on_resource has been setup + * to match the BUS_SPACE_MAXADDR convention. + * + * On FreeBSD this leads to the likes of sysctl hw.busdma.zone0.lowaddr showing + * showing values like 0xbfffffff, as needed/expected, not 0xc0000000. + */ + + /* + * _DMA interpetation notes: + * + * The minimum and maximum are device-side. To get the matching CPU-side figure, + * we add the translation offset. This can overflow to signify lower addresses + * on the CPU than the device, e.g. "Bus 0xC0000000 -> CPU 0x00000000" is + * represented as 0xC0000000 min + 0xFFFFFFFF40000000 offset. + */ + + /* + * Overall summary: + * + * Set limits->lowaddr to the .Address.Maximum translated + * to the CPU-side. + * + * But also validate the implementation limitation that the + * CPU-side minimum ends up at 0x0. + */ + + switch (res->Type) { + case ACPI_RESOURCE_TYPE_ADDRESS16: + min = (uint16_t)(res->Data.Address16.Address.Minimum + + res->Data.Address16.Address.TranslationOffset); + max = (uint16_t)(res->Data.Address16.Address.Maximum + + res->Data.Address16.Address.TranslationOffset); + break; + case ACPI_RESOURCE_TYPE_ADDRESS32: + min = (uint32_t)(res->Data.Address32.Address.Minimum + + res->Data.Address32.Address.TranslationOffset); + max = (uint32_t)(res->Data.Address32.Address.Maximum + + res->Data.Address32.Address.TranslationOffset); + break; + case ACPI_RESOURCE_TYPE_ADDRESS64: + min = (uint64_t)(res->Data.Address64.Address.Minimum + + res->Data.Address64.Address.TranslationOffset); + max = (uint64_t)(res->Data.Address64.Address.Maximum + + res->Data.Address64.Address.TranslationOffset); + break; + case ACPI_RESOURCE_TYPE_END_TAG: + return (AE_OK); + default: + printf("ACPI: warning: DMA limit with unsupported resource type %d\n", + res->Type); + return (AE_OK); + } + + if (min != 0) + printf("ACPI: warning: DMA limit with non-zero minimum address" + " not supported yet\n"); + + limits->lowaddr = MIN(limits->lowaddr, max); + + return (AE_OK); +} + +static int +get_dma_tag(ACPI_HANDLE handle, bus_dma_tag_t *result) +{ + ACPI_HANDLE parent; + unsigned int coherent; + struct dma_limits limits = { + .lowaddr = BUS_SPACE_MAXADDR, + }; + + /* + * Note: + * + * BUS_SPACE_MAXADDR used in .lowaddr of dma_limits is the last-address of the + * last page of the range, such as 0xffffffff. Follow the BUS_SPACE_MAXADDR + * like last-address convention. + * + * On FreeBSD this leads to the likes of sysctl hw.busdma.zone0.lowaddr showing + * showing values like 0xbfffffff, as needed/expected, not 0xc0000000. + */ + + if (ACPI_FAILURE(AcpiWalkResources(handle, "_DMA", + dma_on_resource, (void *)&limits))) { + /* Inherit resources from parent handle if we don't have our own */ + if (ACPI_SUCCESS(AcpiGetParent(handle, &parent))) + return (get_dma_tag(parent, result)); + + /* The root (which has no parent) has no restrictions */ + *result = NULL; + return (0); + } + + if (ACPI_FAILURE(acpi_GetInteger(handle, "_CCA", &coherent))) + coherent = 0; + + if (bus_dma_tag_create(NULL, 1, 0, + limits.lowaddr, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE, BUS_SPACE_UNRESTRICTED, BUS_SPACE_MAXSIZE, + coherent ? BUS_DMA_COHERENT : 0, NULL, NULL, + result) != 0) + return (ENOMEM); + + return (0); +} + +static bus_dma_tag_t +acpi_get_dma_tag(device_t dev, device_t child) +{ + bus_dma_tag_t result; + + if (get_dma_tag(acpi_get_handle(child), &result) != 0) { + device_printf(child, "could not get ACPI DMA limits\n"); + return (NULL); + } + + return (result); +} + /* * Fetch some descriptive data from ACPI to put in our attach message. */