root/sys/arch/arm/acpi/acpipchb.c
/* $NetBSD: acpipchb.c,v 1.33 2025/01/11 11:40:43 jmcneill Exp $ */

/*-
 * Copyright (c) 2018 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jared McNeill <jmcneill@invisible.ca>.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: acpipchb.c,v 1.33 2025/01/11 11:40:43 jmcneill Exp $");

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/queue.h>
#include <sys/mutex.h>
#include <sys/kmem.h>
#include <sys/cpu.h>

#include <arm/cpufunc.h>
#include <arm/bootconfig.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pci_resource.h>

#include <dev/acpi/acpivar.h>
#include <dev/acpi/acpi_pci.h>
#include <dev/acpi/acpi_mcfg.h>

#include <arm/acpi/acpi_pci_machdep.h>

#define ACPIPCHB_MAX_RANGES     64      /* XXX arbitrary limit */

struct acpipchb_bus_range {
        bus_addr_t              min;
        bus_addr_t              max;
        bus_addr_t              offset;
};

struct acpipchb_bus_space {
        struct bus_space        bs;

        struct acpipchb_bus_range range[ACPIPCHB_MAX_RANGES];
        int                     nrange;

        int                     (*map)(void *, bus_addr_t, bus_size_t,
                                       int, bus_space_handle_t *);

        int                     flags;
};

struct acpipchb_softc {
        device_t                sc_dev;

        bus_space_tag_t         sc_memt;

        ACPI_HANDLE             sc_handle;
        ACPI_INTEGER            sc_bus;

        struct acpipchb_bus_space sc_pcimem_bst;
        struct acpipchb_bus_space sc_pciio_bst;
};

static int      acpipchb_match(device_t, cfdata_t, void *);
static void     acpipchb_attach(device_t, device_t, void *);

static void     acpipchb_configure_bus(struct acpipchb_softc *, struct pcibus_attach_args *);
static void     acpipchb_setup_ranges(struct acpipchb_softc *,
                                      struct pcibus_attach_args *);
static void     acpipchb_setup_quirks(struct acpipchb_softc *,
                                      struct pcibus_attach_args *);

CFATTACH_DECL_NEW(acpipchb, sizeof(struct acpipchb_softc),
        acpipchb_match, acpipchb_attach, NULL, NULL);

static const char * const compatible[] = {
        "PNP0A08",
        NULL
};

static int
acpipchb_match(device_t parent, cfdata_t cf, void *aux)
{
        struct acpi_attach_args *aa = aux;

        if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
                return 0;

        return acpi_match_hid(aa->aa_node->ad_devinfo, compatible);
}

static void
acpipchb_attach(device_t parent, device_t self, void *aux)
{
        struct acpipchb_softc * const sc = device_private(self);
        struct acpi_attach_args *aa = aux;
        struct pcibus_attach_args pba;
        ACPI_INTEGER seg, nomsi;
        ACPI_STATUS rv;
        uint16_t bus_start;
        int val;

        sc->sc_dev = self;
        sc->sc_memt = aa->aa_memt;
        sc->sc_handle = aa->aa_node->ad_handle;

        /*
         * First try to derive the base bus number from _CRS. If that fails,
         * try _BBN. If that fails too, assume bus 0.
         */
        if (ACPI_SUCCESS(acpi_pcidev_pciroot_bus(sc->sc_handle, &bus_start))) {
                sc->sc_bus = bus_start;
        } else {
                rv = acpi_eval_integer(sc->sc_handle, "_BBN", &sc->sc_bus);
                if (ACPI_FAILURE(rv)) {
                        sc->sc_bus = 0;
                }
        }

        if (ACPI_FAILURE(acpi_eval_integer(sc->sc_handle, "_SEG", &seg))) {
                seg = 0;
        }

        if (ACPI_FAILURE(acpi_dsd_integer(sc->sc_handle, "linux,pcie-nomsi",
            &nomsi))) {
                nomsi = 0;
        }
        if (get_bootconf_option(boot_args, "nopcimsi",
                                BOOTOPT_TYPE_BOOLEAN, &val) && val) {
                nomsi = 1;
        }

        aprint_naive("\n");
        aprint_normal(": PCI Express Host Bridge\n");

        acpi_claim_childdevs(self, aa->aa_node, NULL);

        memset(&pba, 0, sizeof(pba));
        pba.pba_flags = aa->aa_pciflags &
                        ~(PCI_FLAGS_MEM_OKAY | PCI_FLAGS_IO_OKAY);
        if (nomsi) {
                pba.pba_flags &= ~(PCI_FLAGS_MSI_OKAY | PCI_FLAGS_MSIX_OKAY);
        }
        pba.pba_memt = 0;
        pba.pba_iot = 0;
        pba.pba_dmat = aa->aa_dmat;
#ifdef _PCI_HAVE_DMA64
        pba.pba_dmat64 = aa->aa_dmat64;
#endif
        pba.pba_pc = aa->aa_pc;
        pba.pba_bus = sc->sc_bus;

        acpipchb_setup_ranges(sc, &pba);
        acpipchb_setup_quirks(sc, &pba);

        acpipchb_configure_bus(sc, &pba);

        config_found(self, &pba, pcibusprint,
            CFARGS(.devhandle = device_handle(self)));
}

static void
acpipchb_configure_bus(struct acpipchb_softc *sc, struct pcibus_attach_args *pba)
{
        struct arm32_pci_chipset *md_pc =
            (struct arm32_pci_chipset *)pba->pba_pc;
        struct acpi_pci_context *ap = md_pc->pc_conf_v;
        const bool mapcfgspace = (ap->ap_flags & ACPI_PCI_FLAG_NO_MCFG) == 0;
        int error, val;

        if (get_bootconf_option(boot_args, "nopciconf",
                                BOOTOPT_TYPE_BOOLEAN, &val) && val) {
                return;
        }

        error = acpimcfg_configure_bus(sc->sc_dev, pba->pba_pc, sc->sc_handle,
            sc->sc_bus, mapcfgspace);
        if (error != 0) {
                aprint_error_dev(sc->sc_dev, "failed to configure bus, error %d\n",
                    error);
        }
}

struct acpipchb_setup_ranges_args {
        struct acpipchb_softc *sc;
        struct pcibus_attach_args *pba;
};

static int
acpipchb_bus_space_map(void *t, bus_addr_t bpa, bus_size_t size, int flag,
    bus_space_handle_t *bshp)
{
        struct acpipchb_bus_space * const abs = t;
        int i;

        if (size == 0)
                return ERANGE;

        if ((abs->flags & PCI_FLAGS_IO_OKAY) != 0) {
                /* Force strongly ordered mapping for all I/O space */
                flag = BUS_SPACE_MAP_NONPOSTED;
        }

        for (i = 0; i < abs->nrange; i++) {
                struct acpipchb_bus_range * const range = &abs->range[i];
                if (bpa >= range->min && bpa + size - 1 <= range->max) {
                        return abs->map(t, bpa + range->offset, size,
                                        flag, bshp);
                }
        }

        return ERANGE;
}

static ACPI_STATUS
acpipchb_setup_ranges_cb(ACPI_RESOURCE *res, void *ctx)
{
        struct acpipchb_setup_ranges_args * const args = ctx;
        struct acpipchb_softc * const sc = args->sc;
        struct pcibus_attach_args *pba = args->pba;
        struct acpipchb_bus_space *abs;
        struct acpipchb_bus_range *range;
        const char *range_type;
        u_int pci_flags;

        if (res->Type != ACPI_RESOURCE_TYPE_ADDRESS32 &&
            res->Type != ACPI_RESOURCE_TYPE_ADDRESS64) {
                return AE_OK;
        }

        switch (res->Data.Address.ResourceType) {
        case ACPI_IO_RANGE:
                abs = &sc->sc_pciio_bst;
                range_type = "I/O";
                pci_flags = PCI_FLAGS_IO_OKAY;
                break;
        case ACPI_MEMORY_RANGE:
                abs = &sc->sc_pcimem_bst;
                range_type = "MEM";
                pci_flags = PCI_FLAGS_MEM_OKAY;
                break;
        default:
                return AE_OK;
        }

        if (abs->nrange == ACPIPCHB_MAX_RANGES) {
                aprint_error_dev(sc->sc_dev,
                    "maximum number of ranges reached (ACPIPCHB_MAX_RANGES)\n");
                return AE_LIMIT;
        }

        range = &abs->range[abs->nrange];
        switch (res->Type) {
        case ACPI_RESOURCE_TYPE_ADDRESS32:
                range->min = res->Data.Address32.Address.Minimum;
                range->max = res->Data.Address32.Address.Maximum;
                range->offset = res->Data.Address32.Address.TranslationOffset;
                break;
        case ACPI_RESOURCE_TYPE_ADDRESS64:
                range->min = res->Data.Address64.Address.Minimum;
                range->max = res->Data.Address64.Address.Maximum;
                range->offset = res->Data.Address64.Address.TranslationOffset;
                break;
        default:
                return AE_OK;
        }
        abs->nrange++;

        aprint_debug_dev(sc->sc_dev, "PCI %s [%#lx-%#lx] -> %#lx\n",
            range_type, range->min, range->max, range->offset);

        if ((pba->pba_flags & pci_flags) == 0) {
                abs->bs = *sc->sc_memt;
                abs->bs.bs_cookie = abs;
                abs->map = abs->bs.bs_map;
                abs->flags = pci_flags;
                abs->bs.bs_map = acpipchb_bus_space_map;
                if ((pci_flags & PCI_FLAGS_IO_OKAY) != 0) {
                        pba->pba_iot = &abs->bs;
                } else if ((pci_flags & PCI_FLAGS_MEM_OKAY) != 0) {
                        pba->pba_memt = &abs->bs;
                }
                pba->pba_flags |= pci_flags;
        }

        return AE_OK;
}

static void
acpipchb_setup_ranges(struct acpipchb_softc *sc, struct pcibus_attach_args *pba)
{
        struct acpipchb_setup_ranges_args args;

        args.sc = sc;
        args.pba = pba;

        AcpiWalkResources(sc->sc_handle, "_CRS", acpipchb_setup_ranges_cb,
            &args);
}

static void
acpipchb_setup_quirks(struct acpipchb_softc *sc, struct pcibus_attach_args *pba)
{
        struct arm32_pci_chipset *md_pc =
            (struct arm32_pci_chipset *)pba->pba_pc;
        struct acpi_pci_context *ap = md_pc->pc_conf_v;

        pba->pba_flags &= ~ap->ap_pciflags_clear;
}