/*
 * DF-4738 -- replica of the CardBus secondary-bus allocator in
 * sys/dev/pccard/pccbb/pccbb_pci.c:299,344-359 (cbb_pci_attach).
 *
 * Replicated verbatim (DEVPRINTF dropped): the static curr_bus_number
 * shared across every cbb instance, and the renumbering block.
 *
 * Demonstrates, deterministically, on fixture inputs that a real BIOS
 * can produce:
 *   (1) COLLISION -- firmware already assigned bus 2 to another bridge
 *       (e.g. an AGP/PCIe root port); a cbb socket whose SECBUS was left
 *       0 by firmware is renumbered onto that same bus 2.  No check of
 *       bus numbers already owned anywhere in the tree exists.
 *   (2) WRAP -- after 126 sockets (252 bus numbers), curr_bus_number
 *       exceeds 255 and truncates into the u_int8_t secbus/subbus
 *       (pccbbvar.h:68-69), yielding secbus 0 / duplicate 0x100 values
 *       written to PCIR_SECBUS_2/PCIR_SUBBUS_2.  No free-number check
 *       exists.
 */
#include <stdio.h>
#include <stdint.h>

/* pccbb_pci.c:299 */
static int curr_bus_number = 2;

struct cbb_fixture {
	const char	*name;
	int		 secbus_reg;	/* PCIR_SECBUS_2 as read */
	int		 subbus_reg;	/* PCIR_SUBBUS_2 as read */
	int		 pribus_reg;	/* PCIR_PRIBUS_2 as read */
	int		 pribus_actual;	/* pcib_get_bus(parent) */
	uint8_t		 secbus;	/* cbbvar.h:68 u_int8_t */
	uint8_t		 subbus;	/* cbbvar.h:69 u_int8_t */
};

/* verbatim pccbb_pci.c:343-359 */
static void
cbb_pci_attach_renumber(struct cbb_fixture *sc)
{
	int pribus;

	pribus = sc->pribus_reg;
	if (sc->secbus_reg == 0 || sc->pribus_actual != pribus) {
		if (curr_bus_number <= sc->pribus_actual)
			curr_bus_number = sc->pribus_actual + 1;
		if (pribus != sc->pribus_actual) {
			/* pci_write_config(brdev, PCIR_PRIBUS_2, pribus) */
		}
		sc->secbus_reg = curr_bus_number++;
		sc->subbus_reg = curr_bus_number++;
		/* pci_write_config(brdev, PCIR_SECBUS_2 / SUBBUS_2, 1) */
	}
	sc->secbus = (uint8_t)sc->secbus_reg;	/* softc fields are u_int8_t */
	sc->subbus = (uint8_t)sc->subbus_reg;
}

/* a firmware-configured non-cbb bridge (AGP/PCIe root port) */
static struct cbb_fixture pcie_root = { "pcie0 (firmware-configured)",
					/*sec*/2, /*sub*/2, /*pri*/0, 0, 0, 0 };

int
main(void)
{
	int i;
	int zeroed = 0;

	/*
	 * Case 1: ordinary laptop -- firmware gave the PCIe root port
	 * secondary bus 2, but left the CardBus socket unprogrammed
	 * (SECBUS=0), a state the in-tree comment anticipates.
	 */
	printf("before: %s owns secondary bus %d\n",
	    pcie_root.name, pcie_root.secbus_reg);

	struct cbb_fixture cbb0 = { "cbb0 (SECBUS=0 from firmware)",
				    0, 0, 0, 0, 0, 0 };
	cbb_pci_attach_renumber(&cbb0);
	printf("after : %s renumbered to secbus=%d subbus=%d\n",
	    cbb0.name, cbb0.secbus, cbb0.subbus);
	if (cbb0.secbus == pcie_root.secbus_reg)
		printf("=> COLLISION: two bridges now own bus %d; type-1 config\n"
		       "   cycles for the CardBus card are also claimed by %s\n"
		       "   (config reads/writes can hit an unrelated device).\n",
		    cbb0.secbus, pcie_root.name);

	/*
	 * Case 2: exhaust the 8-bit space -- attach more sockets.
	 */
	struct cbb_fixture extra;
	extra.pribus_actual = 0;
	for (i = 0; i < 128; i++) {
		extra.secbus_reg = 0;
		extra.subbus_reg = 0;
		extra.pribus_reg = 0;
		cbb_pci_attach_renumber(&extra);
		if (extra.secbus_reg >= 256)
			printf("socket #%d: secbus_reg=%d -> u_int8_t secbus=%d, "
			    "subbus_reg=%d -> u_int8_t subbus=%d\n",
			    i + 1, extra.secbus_reg, extra.secbus,
			    extra.subbus_reg, extra.subbus);
		if (extra.secbus_reg == 256)
			zeroed = 1;
	}
	printf("\ncurr_bus_number=%d after %d sockets: no >255 check exists;\n",
	    curr_bus_number, i);
	printf("socket #127 got secbus_reg=256 -> u_int8_t secbus=0: the very\n"
	       "'unconfigured' sentinel (secbus==0) that pccbb_pci.c:345 treats as\n"
	       "invalid -- re-triggers renumbering on resume/re-probe and aliases\n"
	       "the host bus (bus 0) for CardBus config cycles.\n");

	return (cbb0.secbus == 2 && zeroed) ? 0 : 1;
}
