/*	$OpenBSD: pcibios.c,v 1.46 2015/07/17 21:36:56 mlarkin Exp $	*/
/*	$NetBSD: pcibios.c,v 1.5 2000/08/01 05:23:59 uch Exp $	*/

/*
 * Copyright (c) 2000 Michael Shalayeff
 * 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 ``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 HIS RELATIVES 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 MIND, 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.
 */
/*-
 * Copyright (c) 1999 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
 * NASA Ames Research Center.
 *
 * 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.
 */
/*
 * Copyright (c) 1999, by UCHIYAMA Yasushi
 * 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. The name of the developer may NOT be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * 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. 
 */

/*
 * Interface to the PCI BIOS and PCI Interrupt Routing table.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>

#include <dev/isa/isareg.h>
#include <i386/isa/isa_machdep.h>

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

#include <i386/pci/pcibiosvar.h>

#include <machine/biosvar.h>

int pcibios_flags;
int pcibios_present;

struct pcibios_pir_header pcibios_pir_header;
struct pcibios_intr_routing *pcibios_pir_table;
int pcibios_pir_table_nentries;
int pcibios_flags = 0;

struct bios32_entry pcibios_entry;
struct bios32_entry_info pcibios_entry_info;

struct pcibios_intr_routing *pcibios_pir_init(struct pcibios_softc *);

int	pcibios_get_status(struct pcibios_softc *,
	    u_int32_t *, u_int32_t *, u_int32_t *,
	    u_int32_t *, u_int32_t *, u_int32_t *, u_int32_t *);
int	pcibios_get_intr_routing(struct pcibios_softc *,
	    struct pcibios_intr_routing *, int *, u_int16_t *);

int	pcibios_return_code(struct pcibios_softc *, u_int16_t, const char *);

void	pcibios_print_exclirq(struct pcibios_softc *);
void	pcibios_print_pir_table(void);

#define	PCI_IRQ_TABLE_START	0xf0000
#define	PCI_IRQ_TABLE_END	0xfffff

struct cfdriver pcibios_cd = {
	NULL, "pcibios", DV_DULL
};

int pcibiosprobe(struct device *, void *, void *);
void pcibiosattach(struct device *, struct device *, void *);

struct cfattach pcibios_ca = {
	sizeof(struct pcibios_softc), pcibiosprobe, pcibiosattach
};

int
pcibiosprobe(struct device *parent, void *match, void *aux)
{
	struct bios_attach_args *ba = aux;
	u_int32_t rev_maj, rev_min, mech1, mech2, scmech1, scmech2, maxbus;
	int rv;

	if (strcmp(ba->ba_name, "pcibios"))
		return 0;

	rv = bios32_service(PCIBIOS_SIGNATURE, &pcibios_entry,
		&pcibios_entry_info);

	PCIBIOS_PRINTV(("pcibiosprobe: 0x%hx:0x%x at 0x%x[0x%x]\n",
	    pcibios_entry.segment, pcibios_entry.offset,
	    pcibios_entry_info.bei_base, pcibios_entry_info.bei_size));

	return rv &&
	    pcibios_get_status(NULL, &rev_maj, &rev_min, &mech1, &mech2,
	        &scmech1, &scmech2, &maxbus) == PCIBIOS_SUCCESS;
}

void
pcibiosattach(struct device *parent, struct device *self, void *aux)
{
	struct pcibios_softc *sc = (struct pcibios_softc *)self;
	u_int32_t rev_maj, rev_min, mech1, mech2, scmech1, scmech2;

	pcibios_flags = sc->sc_dev.dv_cfdata->cf_flags;

	pcibios_get_status((struct pcibios_softc *)self, &rev_maj,
	    &rev_min, &mech1, &mech2,
	    &scmech1, &scmech2, &sc->max_bus);

	printf(": rev %d.%d @ 0x%x/0x%x\n",
	    rev_maj, rev_min >> 4, pcibios_entry_info.bei_base,
	    pcibios_entry_info.bei_size);

	PCIBIOS_PRINTV(("%s: config mechanism %s%s, special cycles %s%s, "
	    "last bus %d\n", sc->sc_dev.dv_xname,
	    mech1 ? "[1]" : "[x]", mech2 ? "[2]" : "[x]",
	    scmech1 ? "[1]" : "[x]", scmech2 ? "[2]" : "[x]", sc->max_bus));

	/*
	 * The PCI BIOS tells us the config mechanism; fill it in now
	 * so that pci_mode_detect() doesn't have to look for it.
	 */
	pci_mode = mech1 ? 1 : 2;

	pcibios_present = 1;

	/*
	 * Find the PCI IRQ Routing table.
	 */

	if (!(pcibios_flags & PCIBIOS_INTR_FIXUP) &&
	    pcibios_pir_init((struct pcibios_softc *)self) != NULL) {
		int rv;

		/*
		 * Fixup interrupt routing.
		 */
		rv = pci_intr_fixup(sc, NULL, I386_BUS_SPACE_IO);
		switch (rv) {
		case -1:
			/* Non-fatal error. */
			printf("%s: Warning, unable to fix up PCI interrupt "
			    "routing\n", sc->sc_dev.dv_xname);
			break;

		case 1:
			/* Fatal error. */
			printf("%s: interrupt fixup failed\n", sc->sc_dev.dv_xname);
			return;
		}

		/*
		 * XXX Clear `pciirq' from the ISA interrupt allocation
		 * XXX mask.
		 */
	}

	if (!(pcibios_flags & PCIBIOS_BUS_FIXUP)) {
		sc->max_bus = pci_bus_fixup(NULL, 0);
		printf("%s: PCI bus #%d is the last bus\n",
		    sc->sc_dev.dv_xname, sc->max_bus);
	}

	if (!(pcibios_flags & PCIBIOS_ADDR_FIXUP))
		pci_addr_fixup(sc, NULL, sc->max_bus);

	bios32_cleanup();
}

struct pcibios_intr_routing *
pcibios_pir_init(struct pcibios_softc *sc)
{
	paddr_t pa;

	pcibios_pir_table = NULL;
	for (pa = PCI_IRQ_TABLE_START; pa < PCI_IRQ_TABLE_END; pa += 16) {
		u_int8_t *p, cksum;
		struct pcibios_pir_header *pirh;
		int i;

		p = ISA_HOLE_VADDR(pa);
		pirh = (struct pcibios_pir_header *)p;
		/*
		 * Some laptops (such as the Toshiba Libretto L series)
		 * use _PIR instead of the standard $PIR for the signature
		 * so we check for that too.
		 */
		if (pirh->signature != BIOS32_MAKESIG('$', 'P', 'I', 'R') &&
		    pirh->signature != BIOS32_MAKESIG('_', 'P', 'I', 'R'))
			continue;
		
		if (pirh->tablesize < sizeof(*pirh))
			continue;

		cksum = 0;
		for (i = 0; i < pirh->tablesize; i++)
			cksum += p[i];

		printf("%s: PCI IRQ Routing Table rev %d.%d @ 0x%lx/%d "
		    "(%zd entries)\n", sc->sc_dev.dv_xname,
		    pirh->version >> 8, pirh->version & 0xff, pa,
		    pirh->tablesize, (pirh->tablesize - sizeof(*pirh)) / 16);

		if (cksum != 0) {
			printf("%s: bad IRQ table checksum\n",
			    sc->sc_dev.dv_xname);
			continue;
		}

		if (pirh->tablesize % 16 != 0) {
			printf("%s: bad IRQ table size\n", sc->sc_dev.dv_xname);
			continue;
		}

		if (pirh->version != 0x0100) {
			printf("%s: unsupported IRQ table version\n",
			    sc->sc_dev.dv_xname);
			continue;
		}

		/*
		 * We can handle this table!  Make a copy of it.
		 */
		pcibios_pir_header = *pirh;
		pcibios_pir_table =
		    malloc(pirh->tablesize - sizeof(*pirh), M_DEVBUF, M_NOWAIT);
		if (pcibios_pir_table == NULL) {
			printf("%s: no memory for $PIR\n", sc->sc_dev.dv_xname);
			return NULL;
		}
		bcopy(p + sizeof(*pirh), pcibios_pir_table,
		    pirh->tablesize - sizeof(*pirh));
		pcibios_pir_table_nentries =
		    (pirh->tablesize - sizeof(*pirh)) / 16;

	}

	/*
	 * If there was no PIR table found, try using the PCI BIOS
	 * Get Interrupt Routing call.
	 *
	 * XXX The interface to this call sucks; just allocate enough
	 * XXX room for 32 entries.
	 */
	if (pcibios_pir_table == NULL) {

		pcibios_pir_table_nentries = 32;
		pcibios_pir_table = malloc(pcibios_pir_table_nentries *
		    sizeof(*pcibios_pir_table), M_DEVBUF, M_NOWAIT);
		if (pcibios_pir_table == NULL) {
			printf("%s: no memory for $PIR\n", sc->sc_dev.dv_xname);
			return NULL;
		}
		if (pcibios_get_intr_routing(sc, pcibios_pir_table,
		    &pcibios_pir_table_nentries,
		    &pcibios_pir_header.exclusive_irq) != PCIBIOS_SUCCESS) {
			printf("%s: PCI IRQ Routing information unavailable.\n",
			    sc->sc_dev.dv_xname);
			free(pcibios_pir_table, M_DEVBUF, 0);
			pcibios_pir_table = NULL;
			pcibios_pir_table_nentries = 0;
			return NULL;
		}
		printf("%s: PCI BIOS has %d Interrupt Routing table entries\n",
		    sc->sc_dev.dv_xname, pcibios_pir_table_nentries);
	}

	pcibios_print_exclirq(sc);
	if (pcibios_flags & PCIBIOS_INTRDEBUG)
		pcibios_print_pir_table();
	return pcibios_pir_table;
}

int
pcibios_get_status(struct pcibios_softc *sc, u_int32_t *rev_maj,
    u_int32_t *rev_min, u_int32_t *mech1, u_int32_t *mech2, u_int32_t *scmech1,
    u_int32_t *scmech2, u_int32_t *maxbus)
{
	u_int32_t ax, bx, cx, edx;
	int rv;

	__asm volatile("pushl	%%es\n\t"
			 "pushl	%%ds\n\t"
			 "movw	4(%%edi), %%cx\n\t"
			 "movl	%%ecx, %%ds\n\t"
			 "lcall	*%%cs:(%%edi)\n\t"
			 "pop	%%ds\n\t"
			 "pop	%%es\n\t"
			 "jc	1f\n\t"
			 "xor	%%ah, %%ah\n"
		    "1:"
		: "=a" (ax), "=b" (bx), "=c" (cx), "=d" (edx)
		: "0" (0xb101), "D" (&pcibios_entry)
		: "cc", "memory");

	rv = pcibios_return_code(sc, ax, "pcibios_get_status");
	if (rv != PCIBIOS_SUCCESS)
		return (rv);

	if (edx != BIOS32_MAKESIG('P', 'C', 'I', ' '))
		return (PCIBIOS_SERVICE_NOT_PRESENT);	/* XXX */

	/*
	 * Fill in the various pieces of info we're looking for.
	 */
	*mech1 = ax & 1;
	*mech2 = ax & (1 << 1);
	*scmech1 = ax & (1 << 4);
	*scmech2 = ax & (1 << 5);
	*rev_maj = (bx >> 8) & 0xff;
	*rev_min = bx & 0xff;
	*maxbus = cx & 0xff;

	return (PCIBIOS_SUCCESS);
}

int
pcibios_get_intr_routing(struct pcibios_softc *sc,
    struct pcibios_intr_routing *table, int *nentries, u_int16_t *exclirq)
{
	u_int32_t ax, bx;
	int rv;
	struct {
		u_int16_t size;
		u_int32_t offset;
		u_int16_t segment;
	} __packed args;

	args.size = *nentries * sizeof(*table);
	args.offset = (u_int32_t)table;
	args.segment = GSEL(GDATA_SEL, SEL_KPL);

	memset(table, 0, args.size);

	__asm volatile("pushl	%%es\n\t"
			 "pushl	%%ds\n\t"
			 "movw	4(%%esi), %%cx\n\t"
			 "movl	%%ecx, %%ds\n\t"
			 "lcall	*%%cs:(%%esi)\n\t"
			 "popl	%%ds\n\t"
			 "popl	%%es\n\t"
			 "jc	1f\n\t"
			 "xor	%%ah, %%ah\n"
		    "1:\n"
		: "=a" (ax), "=b" (bx)
		: "0" (0xb10e), "1" (0), "D" (&args), "S" (&pcibios_entry)
		: "%ecx", "%edx", "cc", "memory");

	rv = pcibios_return_code(sc, ax, "pcibios_get_intr_routing");
	if (rv != PCIBIOS_SUCCESS)
		return (rv);

	*nentries = args.size / sizeof(*table);
	*exclirq |= bx;

	return (PCIBIOS_SUCCESS);
}

int
pcibios_return_code(struct pcibios_softc *sc, u_int16_t ax, const char *func)
{
	const char *errstr;
	int rv = ax >> 8;
	char *nam;

	if (sc)
		nam = sc->sc_dev.dv_xname;
	else
		nam = "pcibios0";

	switch (rv) {
	case PCIBIOS_SUCCESS:
		return (PCIBIOS_SUCCESS);

	case PCIBIOS_SERVICE_NOT_PRESENT:
		errstr = "service not present";
		break;

	case PCIBIOS_FUNCTION_NOT_SUPPORTED:
		errstr = "function not supported";
		break;

	case PCIBIOS_BAD_VENDOR_ID:
		errstr = "bad vendor ID";
		break;

	case PCIBIOS_DEVICE_NOT_FOUND:
		errstr = "device not found";
		break;

	case PCIBIOS_BAD_REGISTER_NUMBER:
		errstr = "bad register number";
		break;

	case PCIBIOS_SET_FAILED:
		errstr = "set failed";
		break;

	case PCIBIOS_BUFFER_TOO_SMALL:
		errstr = "buffer too small";
		break;

	default:
		printf("%s: %s - unknown return code 0x%x\n",
		    nam, func, rv);
		return (rv);
	}

	printf("%s: %s - %s\n", nam, func, errstr);
	return (rv);
}

void
pcibios_print_exclirq(struct pcibios_softc *sc)
{
	int i;

	if (pcibios_pir_header.exclusive_irq) {
		printf("%s: PCI Exclusive IRQs:", sc->sc_dev.dv_xname);
		for (i = 0; i < 16; i++) {
			if (pcibios_pir_header.exclusive_irq & (1 << i))
				printf(" %d", i);
		}
		printf("\n");
	}
}

void
pcibios_print_pir_table(void)
{
	int i, j;

	for (i = 0; i < pcibios_pir_table_nentries; i++) {
		printf("PIR Entry %d:\n", i);
		printf("\tBus: %d  Device: %d\n",
		    pcibios_pir_table[i].bus,
		    PIR_DEVFUNC_DEVICE(pcibios_pir_table[i].device));
		for (j = 0; j < 4; j++) {
			printf("\t\tINT%c: link 0x%02x bitmap 0x%04x\n",
			    'A' + j,
			    pcibios_pir_table[i].linkmap[j].link,
			    pcibios_pir_table[i].linkmap[j].bitmap);
		}
	}
}

void
pci_device_foreach(struct pcibios_softc *sc, pci_chipset_tag_t pc, int maxbus,
    void (*func)(struct pcibios_softc *, pci_chipset_tag_t, pcitag_t))
{
	const struct pci_quirkdata *qd;
	int bus, device, function, maxdevs, nfuncs;
	pcireg_t id, bhlcr;
	pcitag_t tag;

	for (bus = 0; bus <= maxbus; bus++) {
		maxdevs = pci_bus_maxdevs(pc, bus);
		for (device = 0; device < maxdevs; device++) {
			tag = pci_make_tag(pc, bus, device, 0);
			id = pci_conf_read(pc, tag, PCI_ID_REG);

			/* Invalid vendor ID value? */
			if (PCI_VENDOR(id) == PCI_VENDOR_INVALID)
				continue;
			/* XXX Not invalid, but we've done this ~forever. */
			if (PCI_VENDOR(id) == 0)
				continue;

			qd = pci_lookup_quirkdata(PCI_VENDOR(id),
			    PCI_PRODUCT(id));

			bhlcr = pci_conf_read(pc, tag, PCI_BHLC_REG);
			if (PCI_HDRTYPE_MULTIFN(bhlcr) ||
			    (qd != NULL &&
			     (qd->quirks & PCI_QUIRK_MULTIFUNCTION) != 0))
				nfuncs = 8;
			else
				nfuncs = 1;

			for (function = 0; function < nfuncs; function++) {
				tag = pci_make_tag(pc, bus, device, function);
				id = pci_conf_read(pc, tag, PCI_ID_REG);

				/* Invalid vendor ID value? */
				if (PCI_VENDOR(id) == PCI_VENDOR_INVALID)
					continue;
				/*
				 * XXX Not invalid, but we've done this
				 * ~forever.
				 */
				if (PCI_VENDOR(id) == 0)
					continue;
				(*func)(sc, pc, tag);
			}
		}
	}
}
