#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: acpi_pci.c,v 1.37 2022/10/14 22:10:15 jmcneill Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/kmem.h>
#include <sys/systm.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/ppbreg.h>
#include <dev/pci/pci_calls.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpi_pci.h>
#include "locators.h"
#define _COMPONENT ACPI_BUS_COMPONENT
ACPI_MODULE_NAME ("acpi_pci")
#define ACPI_HILODWORD(x) ACPI_HIWORD(ACPI_LODWORD((x)))
#define ACPI_LOLODWORD(x) ACPI_LOWORD(ACPI_LODWORD((x)))
static ACPI_STATUS acpi_pcidev_pciroot_bus_callback(ACPI_RESOURCE *,
void *);
ACPI_STATUS
acpi_pcidev_pciroot_bus(ACPI_HANDLE handle, uint16_t *busp)
{
ACPI_STATUS rv;
int32_t bus;
bus = -1;
rv = AcpiWalkResources(handle, "_CRS",
acpi_pcidev_pciroot_bus_callback, &bus);
if (ACPI_FAILURE(rv))
return rv;
if (bus == -1)
return AE_NOT_EXIST;
*busp = (uint16_t)bus;
return rv;
}
static ACPI_STATUS
acpi_pcidev_pciroot_bus_callback(ACPI_RESOURCE *res, void *context)
{
ACPI_RESOURCE_ADDRESS64 addr64;
int32_t *bus = context;
if ((res->Type != ACPI_RESOURCE_TYPE_ADDRESS16) &&
(res->Type != ACPI_RESOURCE_TYPE_ADDRESS32) &&
(res->Type != ACPI_RESOURCE_TYPE_ADDRESS64))
return AE_OK;
if (ACPI_FAILURE(AcpiResourceToAddress64(res, &addr64)))
return AE_OK;
if (addr64.ResourceType != ACPI_BUS_NUMBER_RANGE)
return AE_OK;
if (*bus != -1)
return AE_ALREADY_EXISTS;
if (addr64.Address.Minimum > 0xFFFF)
return AE_BAD_DATA;
*bus = (int32_t)addr64.Address.Minimum;
return AE_OK;
}
ACPI_STATUS
acpi_pcidev_scan(struct acpi_devnode *ad)
{
struct acpi_devnode *child;
struct acpi_pci_info *ap;
ACPI_INTEGER val;
ACPI_STATUS rv;
ad->ad_pciinfo = NULL;
if (ad->ad_devinfo->Type != ACPI_TYPE_DEVICE)
goto rec;
if (!acpi_device_present(ad->ad_handle))
goto rec;
if (ad->ad_devinfo->Flags & ACPI_PCI_ROOT_BRIDGE) {
ap = kmem_zalloc(sizeof(*ap), KM_SLEEP);
rv = acpi_eval_integer(ad->ad_handle, "_SEG", &val);
if (ACPI_SUCCESS(rv))
ap->ap_segment = ACPI_LOWORD(val);
rv = acpi_pcidev_pciroot_bus(ad->ad_handle, &ap->ap_downbus);
if (ACPI_FAILURE(rv)) {
rv = acpi_eval_integer(ad->ad_handle, "_BBN", &val);
if (ACPI_SUCCESS(rv))
ap->ap_downbus = ACPI_LOWORD(val);
}
if (ap->ap_downbus > 255) {
aprint_error_dev(ad->ad_root,
"invalid PCI downstream bus for %s\n", ad->ad_name);
kmem_free(ap, sizeof(*ap));
goto rec;
}
ap->ap_flags |= ACPI_PCI_INFO_BRIDGE;
ap->ap_pc = acpi_get_pci_chipset_tag(acpi_softc, ap->ap_segment, ap->ap_downbus);
if (ad->ad_devinfo->Valid & ACPI_VALID_ADR) {
ap->ap_bus = ap->ap_downbus;
ap->ap_device =
ACPI_HILODWORD(ad->ad_devinfo->Address);
ap->ap_function =
ACPI_LOLODWORD(ad->ad_devinfo->Address);
if (ap->ap_device > 31 ||
(ap->ap_function > 7 && ap->ap_function != 0xFFFF))
aprint_error_dev(ad->ad_root,
"invalid PCI address for %s\n", ad->ad_name);
else
ap->ap_flags |= ACPI_PCI_INFO_DEVICE;
}
ad->ad_pciinfo = ap;
goto rec;
}
if ((ad->ad_parent != NULL) &&
(ad->ad_parent->ad_pciinfo != NULL) &&
(ad->ad_parent->ad_pciinfo->ap_flags & ACPI_PCI_INFO_BRIDGE) &&
(ad->ad_devinfo->Valid & ACPI_VALID_ADR)) {
ap = kmem_zalloc(sizeof(*ap), KM_SLEEP);
ap->ap_pc = ad->ad_parent->ad_pciinfo->ap_pc;
ap->ap_segment = ad->ad_parent->ad_pciinfo->ap_segment;
ap->ap_bus = ad->ad_parent->ad_pciinfo->ap_downbus;
ap->ap_device = ACPI_HILODWORD(ad->ad_devinfo->Address);
ap->ap_function = ACPI_LOLODWORD(ad->ad_devinfo->Address);
if (ap->ap_device > 31 ||
(ap->ap_function > 7 && ap->ap_function != 0xFFFF)) {
aprint_error_dev(ad->ad_root,
"invalid PCI address for %s\n", ad->ad_name);
kmem_free(ap, sizeof(*ap));
goto rec;
}
ap->ap_flags |= ACPI_PCI_INFO_DEVICE;
if (ap->ap_function == 0xFFFF) {
} else {
rv = acpi_pcidev_ppb_downbus(
ad->ad_parent->ad_pciinfo->ap_pc,
ap->ap_segment, ap->ap_bus, ap->ap_device,
ap->ap_function, &ap->ap_downbus);
if (ACPI_SUCCESS(rv))
ap->ap_flags |= ACPI_PCI_INFO_BRIDGE;
}
ad->ad_pciinfo = ap;
goto rec;
}
rec:
SIMPLEQ_FOREACH(child, &ad->ad_child_head, ad_child_list) {
rv = acpi_pcidev_scan(child);
if (ACPI_FAILURE(rv))
return rv;
}
return AE_OK;
}
ACPI_STATUS
acpi_pcidev_ppb_downbus(pci_chipset_tag_t pc, uint16_t segment, uint16_t bus,
uint16_t device, uint16_t function, uint16_t *downbus)
{
pcitag_t tag;
pcireg_t val;
if (bus > 255 || device > 31 || function > 7)
return AE_BAD_PARAMETER;
tag = pci_make_tag(pc, bus, device, function);
val = pci_conf_read(pc, tag, PCI_ID_REG);
if (PCI_VENDOR(val) == PCI_VENDOR_INVALID ||
PCI_VENDOR(val) == 0)
return AE_NOT_EXIST;
val = pci_conf_read(pc, tag, PCI_BHLC_REG);
if (PCI_HDRTYPE_TYPE(val) != PCI_HDRTYPE_PPB)
return AE_TYPE;
val = pci_conf_read(pc, tag, PCI_BRIDGE_BUS_REG);
*downbus = PCI_BRIDGE_BUS_NUM_SECONDARY(val);
return AE_OK;
}
struct acpi_devnode *
acpi_pcidev_find(uint16_t segment, uint16_t bus,
uint16_t device, uint16_t function)
{
struct acpi_softc *sc = acpi_softc;
struct acpi_devnode *ad;
if (sc == NULL)
return NULL;
SIMPLEQ_FOREACH(ad, &sc->sc_head, ad_list) {
if (ad->ad_pciinfo != NULL &&
(ad->ad_pciinfo->ap_flags & ACPI_PCI_INFO_DEVICE) &&
ad->ad_pciinfo->ap_segment == segment &&
ad->ad_pciinfo->ap_bus == bus &&
ad->ad_pciinfo->ap_device == device &&
ad->ad_pciinfo->ap_function == function)
return ad;
}
return NULL;
}
pci_chipset_tag_t
acpi_pcidev_get_tag(uint16_t segment, uint16_t bus,
uint16_t device, uint16_t function)
{
struct acpi_devnode *ad;
ad = acpi_pcidev_find(segment, bus, device, function);
if (ad == NULL || ad->ad_pciinfo == NULL)
return NULL;
return ad->ad_pciinfo->ap_pc;
}
struct acpi_devnode *
acpi_pciroot_find(uint16_t segment, uint16_t bus)
{
struct acpi_softc *sc = acpi_softc;
struct acpi_devnode *ad;
if (sc == NULL)
return NULL;
SIMPLEQ_FOREACH(ad, &sc->sc_head, ad_list) {
if (ad->ad_pciinfo != NULL &&
(ad->ad_pciinfo->ap_flags & ACPI_PCI_INFO_BRIDGE) &&
ad->ad_pciinfo->ap_segment == segment &&
ad->ad_pciinfo->ap_bus == bus)
return ad;
}
return NULL;
}
device_t
acpi_pcidev_find_dev(struct acpi_devnode *ad)
{
struct acpi_pci_info *ap;
struct pci_softc *pci;
device_t dv, pr;
deviter_t di;
if (ad == NULL)
return NULL;
if (ad->ad_pciinfo == NULL)
return NULL;
ap = ad->ad_pciinfo;
if (ap->ap_function == 0xFFFF)
return NULL;
for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST);
dv != NULL; dv = deviter_next(&di)) {
pr = device_parent(dv);
if (pr == NULL || device_is_a(pr, "pci") != true)
continue;
pci = device_private(pr);
if (pci->sc_bus == ap->ap_bus &&
device_locator(dv, PCICF_DEV) == ap->ap_device &&
device_locator(dv, PCICF_FUNCTION) == ap->ap_function)
break;
}
deviter_release(&di);
return dv;
}
static int
acpi_pci_bus_get_child_devhandle(device_t dev, devhandle_t call_handle, void *v)
{
struct pci_bus_get_child_devhandle_args *args = v;
struct acpi_devnode *ad;
ACPI_HANDLE hdl;
int b, d, f;
u_int segment;
segment = pci_get_segment(args->pc);
pci_decompose_tag(args->pc, args->tag, &b, &d, &f);
ad = acpi_pcidev_find(segment, b, d, f);
if (ad != NULL && (hdl = ad->ad_handle) != NULL) {
args->devhandle = devhandle_from_acpi(call_handle, hdl);
return 0;
}
return ENODEV;
}
ACPI_DEVICE_CALL_REGISTER(PCI_BUS_GET_CHILD_DEVHANDLE_STR,
acpi_pci_bus_get_child_devhandle)