/*      $NetBSD: acpi_pci_link.c,v 1.29 2021/12/20 12:01:01 skrll Exp $ */

/*-
* Copyright (c) 2002 Mitsuru IWASAKI <[email protected]>
* All rights reserved.
*
* 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 AUTHOR 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 AUTHOR 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: acpi_pci_link.c,v 1.29 2021/12/20 12:01:01 skrll Exp $");

#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <sys/reboot.h>
#include <sys/systm.h>

#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>

#include <dev/pci/pcireg.h>

#include "opt_acpi.h"


#define _COMPONENT          ACPI_BUS_COMPONENT
ACPI_MODULE_NAME            ("acpi_pci_link")

MALLOC_DECLARE(M_ACPI);

#define NUM_ISA_INTERRUPTS      16
#define NUM_ACPI_INTERRUPTS     256

#define PCI_INVALID_IRQ 255
#define PCI_INTERRUPT_VALID(x) ((x) != PCI_INVALID_IRQ && (x) != 0)

#define ACPI_SERIAL_BEGIN(x)
#define ACPI_SERIAL_END(x)

/*
* An ACPI PCI link device may contain multiple links.  Each link has its
* own ACPI resource.  _PRT entries specify which link is being used via
* the Source Index.
*
* XXX: A note about Source Indices and DPFs:  Currently we assume that
* the DPF start and end tags are not counted towards the index that
* Source Index corresponds to.  Also, we assume that when DPFs are in use
* they various sets overlap in terms of Indices.  Here's an example
* resource list indicating these assumptions:
*
* Resource             Index
* --------             -----
* I/O Port             0
* Start DPF            -
* IRQ                  1
* MemIO                2
* Start DPF            -
* IRQ                  1
* MemIO                2
* End DPF              -
* DMA Channel          3
*
* The XXX is because I'm not sure if this is a valid assumption to make.
*/

/* States during DPF processing. */
#define DPF_OUTSIDE     0
#define DPF_FIRST       1
#define DPF_IGNORE      2

struct link;

struct acpi_pci_link_softc {
       int     pl_num_links;
       int     pl_crs_bad;
       struct link *pl_links;
       char pl_name[32];
       ACPI_HANDLE pl_handle;
       TAILQ_ENTRY(acpi_pci_link_softc) pl_list;
};

static TAILQ_HEAD(, acpi_pci_link_softc) acpi_pci_linkdevs =
   TAILQ_HEAD_INITIALIZER(acpi_pci_linkdevs);


struct link {
       struct acpi_pci_link_softc *l_sc;
       uint8_t l_bios_irq;
       uint8_t l_irq;
       uint8_t l_trig;
       uint8_t l_pol;
       uint8_t l_initial_irq;
       int     l_res_index;
       int     l_num_irqs;
       int     *l_irqs;
       int     l_references;
       int     l_dev_count;
       pcitag_t *l_devices;
       u_int   l_routed:1;
       u_int   l_isa_irq:1;
       ACPI_RESOURCE l_prs_template;
};

struct link_count_request {
       int     in_dpf;
       int     count;
};

struct link_res_request {
       struct acpi_pci_link_softc *sc;
       int     in_dpf;
       int     res_index;
       int     link_index;
};

static int pci_link_interrupt_weights[NUM_ACPI_INTERRUPTS];
static int pci_link_bios_isa_irqs;

static ACPI_STATUS acpi_count_irq_resources(ACPI_RESOURCE *, void *);
static ACPI_STATUS link_add_crs(ACPI_RESOURCE *, void *);
static ACPI_STATUS link_add_prs(ACPI_RESOURCE *, void *);
static int link_valid_irq(struct link *, int);
static void acpi_pci_link_dump(struct acpi_pci_link_softc *);
static int acpi_pci_link_attach(struct acpi_pci_link_softc *);
static uint8_t acpi_pci_link_search_irq(struct acpi_pci_link_softc *,
                                       pci_chipset_tag_t, int, int, int);
static struct link *acpi_pci_link_lookup(struct acpi_pci_link_softc *, int);
static ACPI_STATUS acpi_pci_link_srs(struct acpi_pci_link_softc *,
                                    ACPI_BUFFER *);
static ACPI_STATUS acpi_AppendBufferResource(ACPI_BUFFER *, ACPI_RESOURCE *);

static ACPI_STATUS
acpi_count_irq_resources(ACPI_RESOURCE *res, void *context)
{
       struct link_count_request *req;

       req = (struct link_count_request *)context;
       switch (res->Type) {
       case ACPI_RESOURCE_TYPE_START_DEPENDENT:
               switch (req->in_dpf) {
               case DPF_OUTSIDE:
                       /* We've started the first DPF. */
                       req->in_dpf = DPF_FIRST;
                       break;
               case DPF_FIRST:
                       /* We've started the second DPF. */
                       req->in_dpf = DPF_IGNORE;
                       break;
               }
               break;
       case ACPI_RESOURCE_TYPE_END_DEPENDENT:
               /* We are finished with DPF parsing. */
               KASSERT(req->in_dpf != DPF_OUTSIDE);
               req->in_dpf = DPF_OUTSIDE;
               break;
       case ACPI_RESOURCE_TYPE_IRQ:
       case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
               /*
                * Don't count resources if we are in a DPF set that we are
                * ignoring.
                */
               if (req->in_dpf != DPF_IGNORE)
                       req->count++;
       }
       return AE_OK;
}

static ACPI_STATUS
link_add_crs(ACPI_RESOURCE *res, void *context)
{
       struct link_res_request *req;
       struct link *link;

       req = (struct link_res_request *)context;
       switch (res->Type) {
       case ACPI_RESOURCE_TYPE_START_DEPENDENT:
               switch (req->in_dpf) {
               case DPF_OUTSIDE:
                       /* We've started the first DPF. */
                       req->in_dpf = DPF_FIRST;
                       break;
               case DPF_FIRST:
                       /* We've started the second DPF. */
                       panic(
               "%s: Multiple dependent functions within a current resource",
                           __func__);
                       break;
               }
               break;
       case ACPI_RESOURCE_TYPE_END_DEPENDENT:
               /* We are finished with DPF parsing. */
               KASSERT(req->in_dpf != DPF_OUTSIDE);
               req->in_dpf = DPF_OUTSIDE;
               break;
       case ACPI_RESOURCE_TYPE_IRQ:
       case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
               KASSERT(req->link_index < req->sc->pl_num_links);
               link = &req->sc->pl_links[req->link_index];
               link->l_res_index = req->res_index;
               req->link_index++;
               req->res_index++;

               /*
                * Only use the current value if there's one IRQ.  Some
                * systems return multiple IRQs (which is nonsense for _CRS)
                * when the link hasn't been programmed.
                */
               if (res->Type == ACPI_RESOURCE_TYPE_IRQ) {
                       if (res->Data.Irq.InterruptCount == 1) {
                               link->l_irq = res->Data.Irq.Interrupts[0];
                               link->l_trig = res->Data.Irq.Triggering;
                               link->l_pol = res->Data.Irq.Polarity;
                       }
               } else if (res->Data.ExtendedIrq.InterruptCount == 1) {
                       link->l_irq = res->Data.ExtendedIrq.Interrupts[0];
                       link->l_trig = res->Data.ExtendedIrq.Triggering;
                       link->l_pol = res->Data.ExtendedIrq.Polarity;
               }

               /*
                * An IRQ of zero means that the link isn't routed.
                */
               if (link->l_irq == 0)
                       link->l_irq = PCI_INVALID_IRQ;
               break;
       default:
               req->res_index++;
       }
       return AE_OK;
}

/*
* Populate the set of possible IRQs for each device.
*/
static ACPI_STATUS
link_add_prs(ACPI_RESOURCE *res, void *context)
{
       ACPI_RESOURCE *tmp;
       struct link_res_request *req;
       struct link *link;
       uint8_t *irqs = NULL;
       uint32_t *ext_irqs = NULL;
       int i, is_ext_irq = 1;

       req = (struct link_res_request *)context;
       switch (res->Type) {
       case ACPI_RESOURCE_TYPE_START_DEPENDENT:
               switch (req->in_dpf) {
               case DPF_OUTSIDE:
                       /* We've started the first DPF. */
                       req->in_dpf = DPF_FIRST;
                       break;
               case DPF_FIRST:
                       /* We've started the second DPF. */
                       req->in_dpf = DPF_IGNORE;
                       break;
               }
               break;
       case ACPI_RESOURCE_TYPE_END_DEPENDENT:
               /* We are finished with DPF parsing. */
               KASSERT(req->in_dpf != DPF_OUTSIDE);
               req->in_dpf = DPF_OUTSIDE;
               break;
       case ACPI_RESOURCE_TYPE_IRQ:
               is_ext_irq = 0;
               /* fall through */
       case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
               /*
                * Don't parse resources if we are in a DPF set that we are
                * ignoring.
                */
               if (req->in_dpf == DPF_IGNORE)
                       break;

               KASSERT(req->link_index < req->sc->pl_num_links);
               link = &req->sc->pl_links[req->link_index];
               if (link->l_res_index == -1) {
                       KASSERT(req->sc->pl_crs_bad);
                       link->l_res_index = req->res_index;
               }
               req->link_index++;
               req->res_index++;

               /*
                * Stash a copy of the resource for later use when doing
                * _SRS.
                */
               tmp = &link->l_prs_template;
               if (is_ext_irq) {
                       memcpy(tmp, res, ACPI_RS_SIZE(tmp->Data.ExtendedIrq));

                       /*
                        * XXX acpi_AppendBufferResource() cannot handle
                        * optional data.
                        */
                       memset(&tmp->Data.ExtendedIrq.ResourceSource, 0,
                           sizeof(tmp->Data.ExtendedIrq.ResourceSource));
                       tmp->Length = ACPI_RS_SIZE(tmp->Data.ExtendedIrq);

                       link->l_num_irqs =
                           res->Data.ExtendedIrq.InterruptCount;
                       link->l_trig = res->Data.ExtendedIrq.Triggering;
                       link->l_pol = res->Data.ExtendedIrq.Polarity;
                       ext_irqs = res->Data.ExtendedIrq.Interrupts;
               } else {
                       memcpy(tmp, res, ACPI_RS_SIZE(tmp->Data.Irq));
                       link->l_num_irqs = res->Data.Irq.InterruptCount;
                       link->l_trig = res->Data.Irq.Triggering;
                       link->l_pol = res->Data.Irq.Polarity;
                       irqs = res->Data.Irq.Interrupts;
               }
               if (link->l_num_irqs == 0)
                       break;

               /*
                * Save a list of the valid IRQs.  Also, if all of the
                * valid IRQs are ISA IRQs, then mark this link as
                * routed via an ISA interrupt.
                */
               link->l_isa_irq = TRUE;
               link->l_irqs = malloc(sizeof(int) * link->l_num_irqs,
                   M_ACPI, M_WAITOK | M_ZERO);
               for (i = 0; i < link->l_num_irqs; i++) {
                       if (is_ext_irq) {
                               link->l_irqs[i] = ext_irqs[i];
                               if (ext_irqs[i] >= NUM_ISA_INTERRUPTS)
                                       link->l_isa_irq = FALSE;
                       } else {
                               link->l_irqs[i] = irqs[i];
                               if (irqs[i] >= NUM_ISA_INTERRUPTS)
                                       link->l_isa_irq = FALSE;
                       }
               }
               break;
       default:
               if (req->in_dpf == DPF_IGNORE)
                       break;
               if (req->sc->pl_crs_bad)
                       aprint_normal("%s: Warning: possible resource %d "
                              "will be lost during _SRS\n", req->sc->pl_name,
                              req->res_index);
               req->res_index++;
       }
       return AE_OK;
}

static int
link_valid_irq(struct link *link, int irq)
{
       int i;

       /* Invalid interrupts are never valid. */
       if (!PCI_INTERRUPT_VALID(irq))
               return FALSE;

       /* Any interrupt in the list of possible interrupts is valid. */
       for (i = 0; i < link->l_num_irqs; i++)
               if (link->l_irqs[i] == irq)
                        return TRUE;

       /*
        * For links routed via an ISA interrupt, if the SCI is routed via
        * an ISA interrupt, the SCI is always treated as a valid IRQ.
        */
       if (link->l_isa_irq && AcpiGbl_FADT.SciInterrupt == irq &&
           irq < NUM_ISA_INTERRUPTS)
               return TRUE;

       /* If the interrupt wasn't found in the list it is not valid. */
       return FALSE;
}

void
acpi_pci_link_state(void)
{
       struct acpi_pci_link_softc *sc;

       TAILQ_FOREACH(sc, &acpi_pci_linkdevs, pl_list) {
               acpi_pci_link_dump(sc);
       }
}

static void
acpi_pci_link_dump(struct acpi_pci_link_softc *sc)
{
       struct link *link;
       int i, j;

       printf("Link Device %s:\n", sc->pl_name);
       printf("Index  IRQ  Rtd  Ref  IRQs\n");
       for (i = 0; i < sc->pl_num_links; i++) {
               link = &sc->pl_links[i];
               printf("%5d  %3d   %c   %3d ", i, link->l_irq,
                   link->l_routed ? 'Y' : 'N',  link->l_references);
               if (link->l_num_irqs == 0)
                       printf(" none");
               else for (j = 0; j < link->l_num_irqs; j++)
                       printf(" %d", link->l_irqs[j]);
               printf(" polarity %u trigger %u\n", link->l_pol, link->l_trig);
       }
       printf("\n");
}

static int
acpi_pci_link_attach(struct acpi_pci_link_softc *sc)
{
       struct link_count_request creq;
       struct link_res_request rreq;
       ACPI_STATUS status;
       int i;

       ACPI_SERIAL_BEGIN(pci_link);

       /*
        * Count the number of current resources so we know how big of
        * a link array to allocate.  On some systems, _CRS is broken,
        * so for those systems try to derive the count from _PRS instead.
        */
       creq.in_dpf = DPF_OUTSIDE;
       creq.count = 0;
       status = AcpiWalkResources(sc->pl_handle, "_CRS",
           acpi_count_irq_resources, &creq);
       sc->pl_crs_bad = ACPI_FAILURE(status);
       if (sc->pl_crs_bad) {
               creq.in_dpf = DPF_OUTSIDE;
               creq.count = 0;
               status = AcpiWalkResources(sc->pl_handle, "_PRS",
                   acpi_count_irq_resources, &creq);
               if (ACPI_FAILURE(status)) {
                       aprint_error("%s: Unable to parse _CRS or _PRS: %s\n",
                           sc->pl_name, AcpiFormatException(status));
                       ACPI_SERIAL_END(pci_link);
                       return ENXIO;
               }
       }
       sc->pl_num_links = creq.count;
       if (creq.count == 0) {
               ACPI_SERIAL_END(pci_link);
               return 0;
       }
       sc->pl_links = malloc(sizeof(struct link) * sc->pl_num_links,
           M_ACPI, M_WAITOK | M_ZERO);

       /* Initialize the child links. */
       for (i = 0; i < sc->pl_num_links; i++) {
               sc->pl_links[i].l_irq = PCI_INVALID_IRQ;
               sc->pl_links[i].l_bios_irq = PCI_INVALID_IRQ;
               sc->pl_links[i].l_sc = sc;
               sc->pl_links[i].l_isa_irq = FALSE;
               sc->pl_links[i].l_res_index = -1;
               sc->pl_links[i].l_dev_count = 0;
               sc->pl_links[i].l_devices = NULL;
       }

       /* Try to read the current settings from _CRS if it is valid. */
       if (!sc->pl_crs_bad) {
               rreq.in_dpf = DPF_OUTSIDE;
               rreq.link_index = 0;
               rreq.res_index = 0;
               rreq.sc = sc;
               status = AcpiWalkResources(sc->pl_handle, "_CRS",
                   link_add_crs, &rreq);
               if (ACPI_FAILURE(status)) {
                       aprint_error("%s: Unable to parse _CRS: %s\n",
                           sc->pl_name, AcpiFormatException(status));
                       goto fail;
               }
       }

       /*
        * Try to read the possible settings from _PRS.  Note that if the
        * _CRS is toast, we depend on having a working _PRS.  However, if
        * _CRS works, then it is ok for _PRS to be missing.
        */
       rreq.in_dpf = DPF_OUTSIDE;
       rreq.link_index = 0;
       rreq.res_index = 0;
       rreq.sc = sc;
       status = AcpiWalkResources(sc->pl_handle, "_PRS",
           link_add_prs, &rreq);
       if (ACPI_FAILURE(status) &&
           (status != AE_NOT_FOUND || sc->pl_crs_bad)) {
               aprint_error("%s: Unable to parse _PRS: %s\n",
                   sc->pl_name, AcpiFormatException(status));
               goto fail;
       }
       if (boothowto & AB_VERBOSE) {
               aprint_normal("%s: Links after initial probe:\n", sc->pl_name);
               acpi_pci_link_dump(sc);
       }

       /* Verify initial IRQs if we have _PRS. */
       if (status != AE_NOT_FOUND)
               for (i = 0; i < sc->pl_num_links; i++)
                       if (!link_valid_irq(&sc->pl_links[i],
                           sc->pl_links[i].l_irq))
                               sc->pl_links[i].l_irq = PCI_INVALID_IRQ;
       if (boothowto & AB_VERBOSE) {
               printf("%s: Links after initial validation:\n", sc->pl_name);
               acpi_pci_link_dump(sc);
       }

       /* Save initial IRQs. */
       for (i = 0; i < sc->pl_num_links; i++)
               sc->pl_links[i].l_initial_irq = sc->pl_links[i].l_irq;

       /*
        * Try to disable this link.  If successful, set the current IRQ to
        * zero and flags to indicate this link is not routed.  If we can't
        * run _DIS (i.e., the method doesn't exist), assume the initial
        * IRQ was routed by the BIOS.
        */
#ifndef ACPI__DIS_IS_BROKEN
       if (ACPI_SUCCESS(AcpiEvaluateObject(sc->pl_handle, "_DIS", NULL,
           NULL)))
               for (i = 0; i < sc->pl_num_links; i++)
                       sc->pl_links[i].l_irq = PCI_INVALID_IRQ;
       else
#endif
               for (i = 0; i < sc->pl_num_links; i++)
                       if (PCI_INTERRUPT_VALID(sc->pl_links[i].l_irq))
                               sc->pl_links[i].l_routed = TRUE;
       if (boothowto & AB_VERBOSE) {
               printf("%s: Links after disable:\n", sc->pl_name);
               acpi_pci_link_dump(sc);
       }
       ACPI_SERIAL_END(pci_link);
       return 0;
fail:
       ACPI_SERIAL_END(pci_link);
       for (i = 0; i < sc->pl_num_links; i++) {
               if (sc->pl_links[i].l_irqs != NULL)
                       free(sc->pl_links[i].l_irqs, M_ACPI);
               if (sc->pl_links[i].l_devices != NULL)
                       free(sc->pl_links[i].l_devices, M_ACPI);
       }
       free(sc->pl_links, M_ACPI);
       return ENXIO;
}

static void
acpi_pci_link_add_functions(struct acpi_pci_link_softc *sc, struct link *link,
   pci_chipset_tag_t pc, int bus, int device, int pin)
{
       uint32_t value;
       uint8_t func, maxfunc, ipin;
       pcitag_t tag;

       tag = pci_make_tag(pc, bus, device, 0);
       /* See if we have a valid device at function 0. */
       value = pci_conf_read(pc, tag,  PCI_BHLC_REG);
       if (PCI_HDRTYPE_TYPE(value) > PCI_HDRTYPE_PCB)
               return;
       if (PCI_HDRTYPE_MULTIFN(value))
               maxfunc = 7;
       else
               maxfunc = 0;

       /* Scan all possible functions at this device. */
       for (func = 0; func <= maxfunc; func++) {
               tag = pci_make_tag(pc, bus, device, func);
               value = pci_conf_read(pc, tag, PCI_ID_REG);
               if (PCI_VENDOR(value) == 0xffff)
                       continue;
               value = pci_conf_read(pc, tag,
                   PCI_INTERRUPT_REG);
               ipin = PCI_INTERRUPT_PIN(value);
               /*
                * See if it uses the pin in question.  Note that the passed
                * in pin uses 0 for A, .. 3 for D whereas the intpin
                * register uses 0 for no interrupt, 1 for A, .. 4 for D.
                */
               if (ipin != pin + 1)
                       continue;

               link->l_devices = realloc(link->l_devices,
                   sizeof(pcitag_t) * (link->l_dev_count + 1),
                   M_ACPI, M_WAITOK);
               link->l_devices[link->l_dev_count] = tag;
               ++link->l_dev_count;
       }
}

static uint8_t
acpi_pci_link_search_irq(struct acpi_pci_link_softc *sc, pci_chipset_tag_t pc,
   int bus, int device, int pin)
{
       uint32_t value;
       uint8_t func, maxfunc, ipin, iline;
       pcitag_t tag;

       tag = pci_make_tag(pc, bus, device, 0);
       /* See if we have a valid device at function 0. */
       value = pci_conf_read(pc, tag,  PCI_BHLC_REG);
       if (PCI_HDRTYPE_TYPE(value) > PCI_HDRTYPE_PCB)
               return PCI_INVALID_IRQ;
       if (PCI_HDRTYPE_MULTIFN(value))
               maxfunc = 7;
       else
               maxfunc = 0;

       /* Scan all possible functions at this device. */
       for (func = 0; func <= maxfunc; func++) {
               tag = pci_make_tag(pc, bus, device, func);
               value = pci_conf_read(pc, tag, PCI_ID_REG);
               if (PCI_VENDOR(value) == 0xffff)
                       continue;
               value = pci_conf_read(pc, tag,
                   PCI_INTERRUPT_REG);
               ipin = PCI_INTERRUPT_PIN(value);
               iline = PCI_INTERRUPT_LINE(value);

               /*
                * See if it uses the pin in question.  Note that the passed
                * in pin uses 0 for A, .. 3 for D whereas the intpin
                * register uses 0 for no interrupt, 1 for A, .. 4 for D.
                */
               if (ipin != pin + 1)
                       continue;
               aprint_verbose(
                   "%s: ACPI: Found matching pin for %d.%d.INT%c"
                   " at func %d: %d\n",
                           sc->pl_name, bus, device, pin + 'A', func, iline);
               if (PCI_INTERRUPT_VALID(iline))
                       return iline;
       }
       return PCI_INVALID_IRQ;
}

/*
* Find the link structure that corresponds to the resource index passed in
* via 'source_index'.
*/
static struct link *
acpi_pci_link_lookup(struct acpi_pci_link_softc *sc, int source_index)
{
       int i;

       for (i = 0; i < sc->pl_num_links; i++)
               if (sc->pl_links[i].l_res_index == source_index)
                       return &sc->pl_links[i];
       return NULL;
}

void
acpi_pci_link_add_reference(void *v, pci_chipset_tag_t pc, int index,
   int bus, int slot, int pin)
{
       struct acpi_pci_link_softc *sc = v;
       struct link *link;
       uint8_t bios_irq;

       /* Bump the reference count. */
       ACPI_SERIAL_BEGIN(pci_link);
       link = acpi_pci_link_lookup(sc, index);
       if (link == NULL) {
               printf("%s: apparently invalid index %d\n", sc->pl_name, index);
               ACPI_SERIAL_END(pci_link);
               return;
       }
       link->l_references++;
       acpi_pci_link_add_functions(sc, link, pc, bus, slot, pin);
       if (link->l_routed)
               pci_link_interrupt_weights[link->l_irq]++;

       /*
        * The BIOS only routes interrupts via ISA IRQs using the ATPICs
        * (8259As).  Thus, if this link is routed via an ISA IRQ, go
        * look to see if the BIOS routed an IRQ for this link at the
        * indicated (bus, slot, pin).  If so, we prefer that IRQ for
        * this link and add that IRQ to our list of known-good IRQs.
        * This provides a good work-around for link devices whose _CRS
        * method is either broken or bogus.  We only use the value
        * returned by _CRS if we can't find a valid IRQ via this method
        * in fact.
        *
        * If this link is not routed via an ISA IRQ (because we are using
        * APIC for example), then don't bother looking up the BIOS IRQ
        * as if we find one it won't be valid anyway.
        */
       if (!link->l_isa_irq) {
               ACPI_SERIAL_END(pci_link);
               return;
       }

       /* Try to find a BIOS IRQ setting from any matching devices. */
       bios_irq = acpi_pci_link_search_irq(sc, pc, bus, slot, pin);
       if (!PCI_INTERRUPT_VALID(bios_irq)) {
               ACPI_SERIAL_END(pci_link);
               return;
       }

       /* Validate the BIOS IRQ. */
       if (!link_valid_irq(link, bios_irq)) {
               printf("%s: BIOS IRQ %u for %d.%d.INT%c is invalid\n",
                   sc->pl_name, bios_irq, (int)bus, slot, pin + 'A');
       } else if (!PCI_INTERRUPT_VALID(link->l_bios_irq)) {
               link->l_bios_irq = bios_irq;
               if (bios_irq < NUM_ISA_INTERRUPTS)
                       pci_link_bios_isa_irqs |= (1 << bios_irq);
               if (bios_irq != link->l_initial_irq &&
                   PCI_INTERRUPT_VALID(link->l_initial_irq))
                       printf(
                           "%s: BIOS IRQ %u does not match initial IRQ %u\n",
                           sc->pl_name, bios_irq, link->l_initial_irq);
       } else if (bios_irq != link->l_bios_irq)
               printf(
           "%s: BIOS IRQ %u for %d.%d.INT%c does not match "
           "previous BIOS IRQ %u\n",
                   sc->pl_name, bios_irq, (int)bus, slot, pin + 'A',
                   link->l_bios_irq);
       ACPI_SERIAL_END(pci_link);
}

static ACPI_STATUS
acpi_pci_link_srs_from_crs(struct acpi_pci_link_softc *sc, ACPI_BUFFER *srsbuf)
{
       ACPI_RESOURCE *end, *res;
       ACPI_STATUS status;
       struct link *link;
       int i, in_dpf;

       /* Fetch the _CRS. */
       srsbuf->Pointer = NULL;
       srsbuf->Length = ACPI_ALLOCATE_BUFFER;
       status = AcpiGetCurrentResources(sc->pl_handle, srsbuf);
       if (ACPI_SUCCESS(status) && srsbuf->Pointer == NULL)
               status = AE_NO_MEMORY;
       if (ACPI_FAILURE(status)) {
               aprint_verbose("%s: Unable to fetch current resources: %s\n",
                   sc->pl_name, AcpiFormatException(status));
               return status;
       }

       /* Fill in IRQ resources via link structures. */
       link = sc->pl_links;
       i = 0;
       in_dpf = DPF_OUTSIDE;
       res = (ACPI_RESOURCE *)srsbuf->Pointer;
       end = (ACPI_RESOURCE *)((char *)srsbuf->Pointer + srsbuf->Length);
       for (;;) {
               switch (res->Type) {
               case ACPI_RESOURCE_TYPE_START_DEPENDENT:
                       switch (in_dpf) {
                       case DPF_OUTSIDE:
                               /* We've started the first DPF. */
                               in_dpf = DPF_FIRST;
                               break;
                       case DPF_FIRST:
                               /* We've started the second DPF. */
                               panic(
               "%s: Multiple dependent functions within a current resource",
                                   __func__);
                               break;
                       }
                       break;
               case ACPI_RESOURCE_TYPE_END_DEPENDENT:
                       /* We are finished with DPF parsing. */
                       KASSERT(in_dpf != DPF_OUTSIDE);
                       in_dpf = DPF_OUTSIDE;
                       break;
               case ACPI_RESOURCE_TYPE_IRQ:
                       res->Data.Irq.InterruptCount = 1;
                       if (PCI_INTERRUPT_VALID(link->l_irq)) {
                               KASSERT(link->l_irq < NUM_ISA_INTERRUPTS);
                               res->Data.Irq.Interrupts[0] = link->l_irq;
                               res->Data.Irq.Triggering = link->l_trig;
                               res->Data.Irq.Polarity = link->l_pol;
                       } else
                               res->Data.Irq.Interrupts[0] = 0;
                       link++;
                       i++;
                       break;
               case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
                       res->Data.ExtendedIrq.InterruptCount = 1;
                       if (PCI_INTERRUPT_VALID(link->l_irq)) {
                               res->Data.ExtendedIrq.Interrupts[0] =
                                   link->l_irq;
                               res->Data.ExtendedIrq.Triggering =
                                   link->l_trig;
                               res->Data.ExtendedIrq.Polarity = link->l_pol;
                       } else
                               res->Data.ExtendedIrq.Interrupts[0] = 0;
                       link++;
                       i++;
                       break;
               }
               if (res->Type == ACPI_RESOURCE_TYPE_END_TAG)
                       break;
               res = ACPI_NEXT_RESOURCE(res);
               if (res >= end)
                       break;
       }
       return AE_OK;
}

static ACPI_STATUS
acpi_pci_link_srs_from_links(struct acpi_pci_link_softc *sc,
   ACPI_BUFFER *srsbuf)
{
       ACPI_RESOURCE newres;
       ACPI_STATUS status;
       struct link *link;
       int i;

       /* Start off with an empty buffer. */
       srsbuf->Pointer = NULL;
       link = sc->pl_links;
       for (i = 0; i < sc->pl_num_links; i++) {

               /* Add a new IRQ resource from each link. */
               link = &sc->pl_links[i];
               if (link->l_prs_template.Type == ACPI_RESOURCE_TYPE_IRQ) {

                       /* Build an IRQ resource. */
                       bcopy(&link->l_prs_template, &newres,
                           ACPI_RS_SIZE(newres.Data.Irq));
                       newres.Data.Irq.InterruptCount = 1;
                       if (PCI_INTERRUPT_VALID(link->l_irq)) {
                               KASSERT(link->l_irq < NUM_ISA_INTERRUPTS);
                               newres.Data.Irq.Interrupts[0] = link->l_irq;
                               newres.Data.Irq.Triggering = link->l_trig;
                               newres.Data.Irq.Polarity = link->l_pol;
                       } else
                               newres.Data.Irq.Interrupts[0] = 0;
               } else {

                       /* Build an ExtIRQ resuorce. */
                       bcopy(&link->l_prs_template, &newres,
                           ACPI_RS_SIZE(newres.Data.ExtendedIrq));
                       newres.Data.ExtendedIrq.InterruptCount = 1;
                       if (PCI_INTERRUPT_VALID(link->l_irq)) {
                               newres.Data.ExtendedIrq.Interrupts[0] =
                                   link->l_irq;
                               newres.Data.ExtendedIrq.Triggering =
                                   link->l_trig;
                               newres.Data.ExtendedIrq.Polarity =
                                   link->l_pol;
                       } else {
                               newres.Data.ExtendedIrq.Interrupts[0] = 0;
                       }
               }

               /* Add the new resource to the end of the _SRS buffer. */
               status = acpi_AppendBufferResource(srsbuf, &newres);
               if (ACPI_FAILURE(status)) {
                       printf("%s: Unable to build resources: %s\n",
                           sc->pl_name, AcpiFormatException(status));
                       if (srsbuf->Pointer != NULL)
                               ACPI_FREE(srsbuf->Pointer);
                       return status;
               }
       }
       return AE_OK;
}

static ACPI_STATUS
acpi_pci_link_srs(struct acpi_pci_link_softc *sc, ACPI_BUFFER *srsbuf)
{
       ACPI_STATUS status;

       if (sc->pl_crs_bad)
               status = acpi_pci_link_srs_from_links(sc, srsbuf);
       else
               status = acpi_pci_link_srs_from_crs(sc, srsbuf);

       if (ACPI_FAILURE(status))
               printf("%s: Unable to find link srs : %s\n",
                   sc->pl_name, AcpiFormatException(status));

       /* Write out new resources via _SRS. */
       return AcpiSetCurrentResources(sc->pl_handle, srsbuf);
}

static ACPI_STATUS
acpi_pci_link_route_irqs(struct acpi_pci_link_softc *sc, int *irq, int *pol,
                        int *trig)
{
       ACPI_RESOURCE *resource, *end;
       ACPI_BUFFER srsbuf;
       ACPI_STATUS status;
       struct link *link;
       int i, is_ext = 0;

       status = acpi_pci_link_srs(sc, &srsbuf);
       if (ACPI_FAILURE(status)) {
               printf("%s: _SRS failed: %s\n",
                   sc->pl_name, AcpiFormatException(status));
               return status;
       }
       /*
        * Perform acpi_config_intr() on each IRQ resource if it was just
        * routed for the first time.
        */
       link = sc->pl_links;
       i = 0;
       resource = (ACPI_RESOURCE *)srsbuf.Pointer;
       end = (ACPI_RESOURCE *)((char *)srsbuf.Pointer + srsbuf.Length);
       for (;;) {
               if (resource->Type == ACPI_RESOURCE_TYPE_END_TAG)
                       break;
               switch (resource->Type) {
               case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
                       is_ext = 1;
                       /* FALLTHROUGH */
               case ACPI_RESOURCE_TYPE_IRQ:
                       /*
                        * Only configure the interrupt and update the
                        * weights if this link has a valid IRQ and was
                        * previously unrouted.
                        */
                       if (!link->l_routed &&
                           PCI_INTERRUPT_VALID(link->l_irq)) {
                               *trig = is_ext ?
                                   resource->Data.ExtendedIrq.Triggering :
                                   resource->Data.Irq.Triggering;
                               *pol = is_ext ?
                                   resource->Data.ExtendedIrq.Polarity :
                                   resource->Data.Irq.Polarity;
                               *irq = is_ext ?
                                   resource->Data.ExtendedIrq.Interrupts[0] :
                                   resource->Data.Irq.Interrupts[0];
                               link->l_routed = TRUE;
                               pci_link_interrupt_weights[link->l_irq] +=
                                   link->l_references;
                       }
                       link++;
                       i++;
                       break;
               }
               resource = ACPI_NEXT_RESOURCE(resource);
               if (resource >= end)
                       break;
       }
       ACPI_FREE(srsbuf.Pointer);
       return AE_OK;
}

/*
* Pick an IRQ to use for this unrouted link.
*/
static uint8_t
acpi_pci_link_choose_irq(struct acpi_pci_link_softc *sc, struct link *link)
{
       u_int8_t best_irq, pos_irq;
       int best_weight, pos_weight, i;

       KASSERT(!link->l_routed);
       KASSERT(!PCI_INTERRUPT_VALID(link->l_irq));

       /*
        * If we have a valid BIOS IRQ, use that.  We trust what the BIOS
        * says it routed over what _CRS says the link thinks is routed.
        */
       if (PCI_INTERRUPT_VALID(link->l_bios_irq))
               return link->l_bios_irq;

       /*
        * If we don't have a BIOS IRQ but do have a valid IRQ from _CRS,
        * then use that.
        */
       if (PCI_INTERRUPT_VALID(link->l_initial_irq))
               return link->l_initial_irq;

       /*
        * Ok, we have no useful hints, so we have to pick from the
        * possible IRQs.  For ISA IRQs we only use interrupts that
        * have already been used by the BIOS.
        */
       best_irq = PCI_INVALID_IRQ;
       best_weight = INT_MAX;
       for (i = 0; i < link->l_num_irqs; i++) {
               pos_irq = link->l_irqs[i];
               if (pos_irq < NUM_ISA_INTERRUPTS &&
                   (pci_link_bios_isa_irqs & 1 << pos_irq) == 0)
                       continue;
               pos_weight = pci_link_interrupt_weights[pos_irq];
               if (pos_weight < best_weight) {
                       best_weight = pos_weight;
                       best_irq = pos_irq;
               }
       }

       /*
        * If this is an ISA IRQ, try using the SCI if it is also an ISA
        * interrupt as a fallback.
        */
       if (link->l_isa_irq && !PCI_INTERRUPT_VALID(best_irq)) {
               pos_irq = AcpiGbl_FADT.SciInterrupt;
               pos_weight = pci_link_interrupt_weights[pos_irq];
               if (pos_weight < best_weight) {
                       best_weight = pos_weight;
                       best_irq = pos_irq;
               }
       }

       if (PCI_INTERRUPT_VALID(best_irq)) {
               aprint_verbose("%s: Picked IRQ %u with weight %d\n",
                   sc->pl_name, best_irq, best_weight);
       } else
               printf("%s: Unable to choose an IRQ\n", sc->pl_name);
       return best_irq;
}

int
acpi_pci_link_route_interrupt(void *v, pci_chipset_tag_t pc, int index,
   int *irq, int *pol, int *trig)
{
       struct acpi_pci_link_softc *sc = v;
       struct link *link;
       int i;
       pcireg_t reg;

       ACPI_SERIAL_BEGIN(pci_link);
       link = acpi_pci_link_lookup(sc, index);
       if (link == NULL)
               panic("%s: apparently invalid index %d", __func__, index);

       /*
        * If this link device is already routed to an interrupt, just return
        * the interrupt it is routed to.
        */
       if (link->l_routed) {
               KASSERT(PCI_INTERRUPT_VALID(link->l_irq));
               ACPI_SERIAL_END(pci_link);
               *irq = link->l_irq;
               *pol = link->l_pol;
               *trig = link->l_trig;
               return link->l_irq;
       }

       if (PCI_INTERRUPT_VALID(link->l_irq)) {
               *irq = link->l_irq;
               *pol = link->l_pol;
               *trig = link->l_trig;
               goto done;
       }

       /* The link device doesn't have an interrupt, so try to choose one. */
       link->l_irq = acpi_pci_link_choose_irq(sc, link);
       if (!PCI_INTERRUPT_VALID(link->l_irq))
               goto done;

       /*
        * Try to route the interrupt we picked.  If it fails, then
        * assume the interrupt is not routed.
        */
       acpi_pci_link_route_irqs(sc, irq, pol, trig);
       if (!link->l_routed) {
               link->l_irq = PCI_INVALID_IRQ;
               goto done;
       }

       link->l_pol = *pol;
       link->l_trig = *trig;
       for (i = 0; i < link->l_dev_count; ++i) {
               reg = pci_conf_read(pc, link->l_devices[i],
                   PCI_INTERRUPT_REG);
               reg &= ~(PCI_INTERRUPT_LINE_MASK << PCI_INTERRUPT_LINE_SHIFT);
               reg |= link->l_irq << PCI_INTERRUPT_LINE_SHIFT;
               pci_conf_write(pc, link->l_devices[i],
                   PCI_INTERRUPT_REG, reg);
       }

done:
       ACPI_SERIAL_END(pci_link);

       return link->l_irq;
}

/*
* This is gross, but we abuse the identify routine to perform one-time
* SYSINIT() style initialization for the driver.
*/
static void
acpi_pci_link_init(struct acpi_pci_link_softc *sc)
{
       ACPI_BUFFER buf;

       /*
        * If the SCI is an ISA IRQ, add it to the bitmask of known good
        * ISA IRQs.
        *
        * XXX: If we are using the APIC, the SCI might have been
        * rerouted to an APIC pin in which case this is invalid.  However,
        * if we are using the APIC, we also shouldn't be having any PCI
        * interrupts routed via ISA IRQs, so this is probably ok.
        */
       if (AcpiGbl_FADT.SciInterrupt < NUM_ISA_INTERRUPTS)
               pci_link_bios_isa_irqs |= (1 << AcpiGbl_FADT.SciInterrupt);

       buf.Length = sizeof (sc->pl_name);
       buf.Pointer = sc->pl_name;

       if (ACPI_FAILURE(AcpiGetName(sc->pl_handle, ACPI_SINGLE_NAME, &buf)))
               snprintf(sc->pl_name, sizeof (sc->pl_name), "%s",
                   "ACPI link device");

       acpi_pci_link_attach(sc);
}

void *
acpi_pci_link_devbyhandle(ACPI_HANDLE handle)
{
       struct acpi_pci_link_softc *sc;

       TAILQ_FOREACH(sc, &acpi_pci_linkdevs, pl_list) {
               if (sc->pl_handle == handle)
                       return sc;
       }

       sc = malloc(sizeof (*sc), M_ACPI, M_WAITOK | M_ZERO);
       sc->pl_handle = handle;

       acpi_pci_link_init(sc);

       TAILQ_INSERT_TAIL(&acpi_pci_linkdevs, sc, pl_list);

       return (void *)sc;
}

void
acpi_pci_link_resume(void)
{
       struct acpi_pci_link_softc *sc;
       ACPI_BUFFER srsbuf;

       TAILQ_FOREACH(sc, &acpi_pci_linkdevs, pl_list) {
               ACPI_SERIAL_BEGIN(pci_link);
               if (ACPI_SUCCESS(acpi_pci_link_srs(sc, &srsbuf)))
                       ACPI_FREE(srsbuf.Pointer);
               ACPI_SERIAL_END(pci_link);
       }
}

ACPI_HANDLE
acpi_pci_link_handle(void *v)
{
       struct acpi_pci_link_softc *sc = v;

       return sc->pl_handle;
}

char *
acpi_pci_link_name(void *v)
{
       struct acpi_pci_link_softc *sc = v;

       return sc->pl_name;
}


/*
* Append an ACPI_RESOURCE to an ACPI_BUFFER.
*
* Given a pointer to an ACPI_RESOURCE structure, expand the ACPI_BUFFER
* provided to contain it.  If the ACPI_BUFFER is empty, allocate a sensible
* backing block.  If the ACPI_RESOURCE is NULL, return an empty set of
* resources.
*/
#define ACPI_INITIAL_RESOURCE_BUFFER_SIZE       512

static ACPI_STATUS
acpi_AppendBufferResource(ACPI_BUFFER *buf, ACPI_RESOURCE *res)
{
       ACPI_RESOURCE   *rp;
       void            *newp;

       /* Initialise the buffer if necessary. */
       if (buf->Pointer == NULL) {
       buf->Length = ACPI_INITIAL_RESOURCE_BUFFER_SIZE;
       if ((buf->Pointer = ACPI_ALLOCATE(buf->Length)) == NULL)
               return (AE_NO_MEMORY);
       rp = (ACPI_RESOURCE *)buf->Pointer;
       rp->Type =  ACPI_RESOURCE_TYPE_END_TAG;
       rp->Length = 0;
       }

       if (res == NULL)
               return AE_OK;

       /*
        * Scan the current buffer looking for the terminator.
        * This will either find the terminator or hit the end
        * of the buffer and return an error.
        */
       rp = (ACPI_RESOURCE *)buf->Pointer;
       for (;;) {
               /* Range check, don't go outside the buffer */
               if (rp >= (ACPI_RESOURCE *)((u_int8_t *)buf->Pointer +
                   buf->Length))
                       return AE_BAD_PARAMETER;
               if (rp->Type ==  ACPI_RESOURCE_TYPE_END_TAG || rp->Length == 0)
                       break;
               rp = ACPI_NEXT_RESOURCE(rp);
       }

       /*
        * Check the size of the buffer and expand if required.
        *
        * Required size is:
        *      size of existing resources before terminator +
        *      size of new resource and header +
        *      size of terminator.
        *
        * Note that this loop should really only run once, unless
        * for some reason we are stuffing a *really* huge resource.
        */
       while ((((u_int8_t *)rp - (u_int8_t *)buf->Pointer) +
           res->Length + ACPI_RS_SIZE_NO_DATA +
           ACPI_RS_SIZE_MIN) >= buf->Length) {
               if ((newp = ACPI_ALLOCATE(buf->Length * 2)) == NULL)
                       return AE_NO_MEMORY;
               memcpy(newp, buf->Pointer, buf->Length);
               rp = (ACPI_RESOURCE *)((u_int8_t *)newp +
                  ((u_int8_t *)rp - (u_int8_t *)buf->Pointer));
               ACPI_FREE(buf->Pointer);
               buf->Pointer = newp;
               buf->Length += buf->Length;
       }

       /* Insert the new resource. */
       memcpy(rp, res, res->Length);

       /* And add the terminator. */
       rp = ACPI_NEXT_RESOURCE(rp);
       rp->Type =  ACPI_RESOURCE_TYPE_END_TAG;
       rp->Length = 0;

       return AE_OK;
}