/*
 * DF-5082 mechanism harness for
 * sys/dev/raid/hptrr/hptrr_os_bsd.c (DragonFly 6.5-DEVELOPMENT guest).
 *
 * Defect: os_map_pci_bar() (hptrr_os_bsd.c:85-106) never checks the
 * return of bus_alloc_resource_any() (hptrr_os_bsd.c:101).  When the
 * BAR allocation fails, rman_get_virtual(NULL) (hptrr_os_bsd.c:104)
 * dereferences NULL: rman_get_virtual(r) is ((r)->r_virtual)
 * (sys/sys/rman.h:149) and offsetof(struct resource, r_virtual)==0x40
 * on amd64 -> "Fatal trap 12: page fault", fault VA 0x40, at device
 * attach time.  Twin of DF-5039 (hpt27xx_os_bsd.c:113).
 *
 * This harness embeds a VERBATIM copy of hptrr_os_bsd.c:85-106
 * (rename-only: df5082_os_map_pci_bar_copy) plus the same function
 * with the proposed fix.diff guard applied
 * (df5082_os_map_pci_bar_fixed).  The HBA/pcibar types come from the
 * REAL driver header <dev/raid/hptrr/os_bsd.h>, so the
 * hba->pcibar[index] accesses have the production layout; CTASSERTs
 * prove it.
 *
 * The allocation failure is produced through the real newbus/pci/rman
 * stack, exactly as in production: the victim is a REAL PCI device
 * whose chosen BAR register reads 0 (unimplemented/unassigned BAR).
 * For such a BAR pci_add_map() (sys/bus/pci/pci.c:2525) returns
 * without resource_list_add(), so resource_list_alloc() takes its
 * "!rle -> return 0" path (sys/kern/subr_bus.c:2673) and
 * bus_alloc_resource_any() returns NULL.
 *
 * sysctl df5082.trigger:
 *   2 = fixed variant  -> expected: "guarded, no panic", guest survives
 *   1 = verbatim copy  -> expected: Fatal trap 12, fault VA 0x40
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/types.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <bus/pci/pcivar.h>
#include <bus/pci/pcireg.h>

#include <dev/raid/hptrr/hptrr_config.h>
#include <dev/raid/hptrr/os_bsd.h>

/*--------------------------------------------------------------------
 * Layout proof (real header, amd64). If any of these fire the static
 * analysis is wrong and the finding must be withdrawn.
 *------------------------------------------------------------------*/
CTASSERT(sizeof(HBA) > 0);
CTASSERT(offsetof(HBA, pcidev) + sizeof(device_t) <= sizeof(HBA));
CTASSERT(offsetof(HBA, pcibar) + 6 * sizeof(((PHBA)0)->pcibar[0]) <=
    sizeof(HBA));
CTASSERT(sizeof(((PHBA)0)->pcibar[0]) ==
    sizeof(struct { struct resource *res; int type; int rid; void *base; }));

/*--------------------------------------------------------------------
 * VERBATIM hptrr_os_bsd.c:85-106 (rename only).
 *------------------------------------------------------------------*/
static void *
df5082_os_map_pci_bar_copy(void *osext, int index, HPT_U32 offset,
    HPT_U32 length)
{
	PHBA hba = (PHBA)osext;

	hba->pcibar[index].rid = 0x10 + index * 4;

	if (pci_read_config(hba->pcidev, hba->pcibar[index].rid, 4) & 1)
		hba->pcibar[index].type = SYS_RES_IOPORT;
	else
		hba->pcibar[index].type = SYS_RES_MEMORY;

	hba->pcibar[index].res = bus_alloc_resource_any(hba->pcidev,
			hba->pcibar[index].type, &hba->pcibar[index].rid, RF_ACTIVE);

	hba->pcibar[index].base = (char *)rman_get_virtual(hba->pcibar[index].res) + offset;
	return hba->pcibar[index].base;
}

/*--------------------------------------------------------------------
 * Same function with fix.diff applied (NULL guard + safe return).
 *------------------------------------------------------------------*/
static void *
df5082_os_map_pci_bar_fixed(void *osext, int index, HPT_U32 offset,
    HPT_U32 length)
{
	PHBA hba = (PHBA)osext;

	hba->pcibar[index].rid = 0x10 + index * 4;

	if (pci_read_config(hba->pcidev, hba->pcibar[index].rid, 4) & 1)
		hba->pcibar[index].type = SYS_RES_IOPORT;
	else
		hba->pcibar[index].type = SYS_RES_MEMORY;

	hba->pcibar[index].res = bus_alloc_resource_any(hba->pcidev,
			hba->pcibar[index].type, &hba->pcibar[index].rid, RF_ACTIVE);

	if (hba->pcibar[index].res == NULL) {	/* fix.diff */
		kprintf("DF5082-fixed: bus_alloc_resource_any(rid 0x%02x) "
		    "returned NULL; guarded, no panic\n",
		    hba->pcibar[index].rid);
		hba->pcibar[index].base = NULL;
		return NULL;
	}

	hba->pcibar[index].base = (char *)rman_get_virtual(hba->pcibar[index].res) + offset;
	return hba->pcibar[index].base;
}

/*--------------------------------------------------------------------
 * Victim: first PCI child with a BAR register that reads 0.
 * Such a BAR never got a resource_list entry (pci_add_map returns
 * early), so bus_alloc_resource_any() fails -- the production shape
 * of "BAR could not be assigned".
 *------------------------------------------------------------------*/
static device_t
df5082_find_victim(int *indexp)
{
	devclass_t dc = devclass_find("pci");
	int nunits, u;

	if (dc == NULL)
		return NULL;
	nunits = devclass_get_maxunit(dc);
	for (u = 0; u < nunits; u++) {
		device_t bus = devclass_get_device(dc, u);
		device_t *ch;
		int nch, c, idx;

		if (bus == NULL)
			continue;
		if (device_get_children(bus, &ch, &nch) != 0)
			continue;
		for (c = 0; c < nch; c++) {
			for (idx = 0; idx < 6; idx++) {
				int rid = 0x10 + idx * 4;

				if (pci_read_config(ch[c], rid, 4) == 0) {
					device_t victim = ch[c];

					*indexp = idx;
					kfree(ch, M_TEMP);
					return victim;
				}
			}
		}
		kfree(ch, M_TEMP);
	}
	return NULL;
}

static int
df5082_trigger(SYSCTL_HANDLER_ARGS)
{
	int error, t = 0, idx = -1;
	device_t victim;
	HBA hba;
	void *r;

	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error || req->newptr == NULL)
		return error;

	victim = df5082_find_victim(&idx);
	if (victim == NULL) {
		kprintf("DF5082: no victim with an unimplemented BAR found\n");
		return 0;
	}

	memset(&hba, 0, sizeof(hba));
	hba.ext_type = EXT_TYPE_HBA;
	hba.pcidev = victim;

	kprintf("DF5082: victim %s rid 0x%02x (BAR reads 0 -> "
	    "bus_alloc_resource_any will fail)\n",
	    device_get_nameunit(victim), 0x10 + idx * 4);

	if (t == 2) {
		r = df5082_os_map_pci_bar_fixed(&hba, idx, 0, 0x100);
		kprintf("DF5082: fixed variant returned %p; guest still alive\n", r);
	} else {
		kprintf("DF5082: calling VERBATIM os_map_pci_bar ...\n");
		r = df5082_os_map_pci_bar_copy(&hba, idx, 0, 0x100);
		kprintf("DF5082: UNREACHABLE verbatim returned %p\n", r);
	}
	return 0;
}

SYSCTL_NODE(, OID_AUTO, df5082, CTLFLAG_RW, 0, "DF-5082 harness");
SYSCTL_PROC(_df5082, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df5082_trigger, "I",
    "2=fixed variant (no panic), 1=verbatim copy (expect Fatal trap 12)");

static int
df5082_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("DF5082 harness loaded (sysctl df5082.trigger=N)\n");
		return 0;
	default:
		return 0;
	}
}

static moduledata_t df5082_mod = { "df5082", df5082_modevent, 0 };
DECLARE_MODULE(df5082, df5082_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
