/*
 * DF-5776 probe: pi_reg has no upper bound at the /dev/pci ioctl layer.
 *
 * sys/bus/pci/pci_user.c:602-614 (PCIOCREAD/PCIOCWRITE) validates only
 *   pi_reg < 0  and  pi_reg & (pi_width - 1)
 * so any aligned non-negative int (0 .. 0x7FFFFFFC) is accepted and passed
 * down to PCIB_READ/WRITE_CONFIG -> pci_cfgreg{read,write}.  The only net is
 * the MD cfgreg layer:
 *   type-1: sys/bus/pci/x86_64/pci_cfgreg.c:187-198 pci_cfgenable() rejects
 *           (unsigned)reg > PCI_REGMAX(255) by returning dataport 0 ->
 *           pcireg_cfgread/cfgwrite silently do NOTHING and the ioctl still
 *           returns error=0 (pci_user.c:658).
 *   ECAM:   pciereg_cfgread/cfgwrite reject reg > PCIE_REGMAX(4095).
 *
 * => out-of-range config reads/writes report SUCCESS (rc=0) with data=0.
 *
 * Run as root on the guest.  Target: virtio NIC 0:3:0 (not the root disk).
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/pciio.h>

static struct pcisel sel = { 0, 0, 3, 0 };	/* bus 0, dev 3, func 0 */

static int rd(int fd, int reg, int width, uint32_t *out, const char *tag)
{
	struct pci_io io;
	int rc;

	memset(&io, 0, sizeof(io));
	io.pi_sel = sel;
	io.pi_reg = reg;
	io.pi_width = width;
	errno = 0;
	rc = ioctl(fd, PCIOCREAD, &io);
	if (out)
		*out = io.pi_data;
	printf("%-34s PCIOCREAD reg=%-11d w=%d rc=%-3d errno=%-3d data=0x%08x\n",
	       tag, reg, width, rc, errno, io.pi_data);
	return rc;
}

static int wr(int fd, int reg, int width, uint32_t val, const char *tag)
{
	struct pci_io io;
	int rc;

	memset(&io, 0, sizeof(io));
	io.pi_sel = sel;
	io.pi_reg = reg;
	io.pi_width = width;
	io.pi_data = val;
	errno = 0;
	rc = ioctl(fd, PCIOCWRITE, &io);
	printf("%-34s PCIOCWRITE reg=%-11d w=%d val=0x%08x rc=%-3d errno=%d\n",
	       tag, reg, width, val, rc, errno);
	return rc;
}

int main(void)
{
	uint32_t v0, v;
	int fd, rc, i;

	fd = open("/dev/pci", O_RDWR);
	if (fd < 0) {
		printf("open(/dev/pci, O_RDWR) FAILED: %s\n", strerror(errno));
		return 1;
	}
	printf("open(/dev/pci, O_RDWR) = %d (root, securelevel<0)\n", fd);

	puts("\n== 1. sanity: in-range registers work (0:3:0 virtio NIC) ==");
	rc = rd(fd, 0x00, 4, &v0, "in-range reg 0x00");
	if (rc != 0 || v0 != 0x10001af4u)
		printf("   (expected 0x10001af4 got 0x%08x rc=%d)\n", v0, rc);

	puts("\n== 2. pi_reg past legacy space (256..4095: ECAM-only) ==");
	rd(fd, 0x100, 4, &v, "reg 0x100 (one past REGMAX)");
	rd(fd, 0xFFC, 4, &v, "reg 0xFFC (last ECAM dword)");

	puts("\n== 3. pi_reg past EVERY mechanism (>4095) ==");
	rd(fd, 0x1000, 4, &v, "reg 0x1000 (PCIE_REGMAX+1)");
	rd(fd, 0x10000, 4, &v, "reg 0x10000");
	rd(fd, 0x7FFFFFFC, 4, &v, "reg 0x7FFFFFFC (INT_MAX-3)");

	puts("\n== 4. the ONLY validations at the ioctl layer ==");
	rd(fd, -4, 4, &v, "reg -4 (negative)");
	rd(fd, 2, 4, &v, "reg 2 w=4 (misaligned)");

	puts("\n== 5. PCIOCWRITE: dropped silently, reports success ==");
	wr(fd, 0x100, 4, 0xDEADBEEF, "write reg 0x100");
	wr(fd, 0x1000, 4, 0xDEADBEEF, "write reg 0x1000");
	wr(fd, 0x7FFFFFFC, 4, 0xDEADBEEF, "write reg 0x7FFFFFFC");
	rd(fd, 0x100, 4, &v, "readback reg 0x100");
	printf("   readback of dropped write: 0x%08x (0 = never landed)\n", v);

	puts("\n== 6. guest health: device config intact ==");
	rd(fd, 0x00, 4, &v, "reg 0x00 after writes");
	printf("   vendor/dev unchanged: %s\n",
	       v == v0 ? "YES" : "NO (!!)");

	puts("\n== 7. ENODEV control (bad function) ==");
	{
		struct pci_io io;
		memset(&io, 0, sizeof(io));
		io.pi_sel = sel;
		io.pi_sel.pc_func = 7;
		io.pi_reg = 0;
		io.pi_width = 4;
		errno = 0;
		rc = ioctl(fd, PCIOCREAD, &io);
		printf("%-34s rc=%d errno=%d (expect rc=-1 ENODEV)\n",
		       "0:3:7 (no such func)", rc, errno);
	}

	puts("\n== 8. PCIOCGETCONF counter/pagination sanity (negative probe) ==");
	{
		struct pci_conf_io cio;
		struct pci_conf matches[8];
		memset(&cio, 0, sizeof(cio));
		memset(matches, 0, sizeof(matches));
		cio.pat_buf_len = 0;
		cio.num_patterns = 0;
		cio.patterns = NULL;
		cio.match_buf_len = 0xFFFFFFFF;	/* huge: must be clamped */
		cio.matches = matches;
		cio.offset = 0;
		cio.generation = 0;
		errno = 0;
		rc = ioctl(fd, PCIOCGETCONF, &cio);
		printf("%-34s rc=%d num_matches=%u offset=%u gen=%u status=%d\n",
		       "match_buf_len=0xFFFFFFFF", rc, cio.num_matches,
		       cio.offset, cio.generation, (int)cio.status);

		/* pagination: 2 records at a time */
		cio.match_buf_len = 2 * sizeof(struct pci_conf);
		cio.offset = 0;
		cio.generation = 0;
		rc = ioctl(fd, PCIOCGETCONF, &cio);
		printf("%-34s rc=%d num_matches=%u offset=%u status=%d\n",
		       "page size 2 (1st)", rc, cio.num_matches,
		       cio.offset, (int)cio.status);
		i = cio.offset;
		rc = ioctl(fd, PCIOCGETCONF, &cio);
		printf("%-34s rc=%d num_matches=%u offset=%u status=%d\n",
		       "page size 2 (2nd)", rc, cio.num_matches,
		       cio.offset, (int)cio.status);
		if (cio.offset <= i)
			printf("   (!) offset did not advance\n");

		/* bogus pattern length -> EINVAL, no kernel overflow */
		{
			struct pci_match_conf pat;
			memset(&pat, 0, sizeof(pat));
			memset(&cio, 0, sizeof(cio));
			cio.pat_buf_len = sizeof(pat) + 1;	/* != n*sizeof */
			cio.num_patterns = 1;
			cio.patterns = &pat;
			cio.match_buf_len = sizeof(matches);
			cio.matches = matches;
			errno = 0;
			rc = ioctl(fd, PCIOCGETCONF, &cio);
			printf("%-34s rc=%d errno=%d status=%d (expect EINVAL)\n",
			       "pat_buf_len=n*sizeof+1", rc, errno,
			       (int)cio.status);
		}

		/* offset past end */
		memset(&cio, 0, sizeof(cio));
		cio.match_buf_len = sizeof(matches);
		cio.matches = matches;
		cio.offset = 0xFFFFFF;
		cio.generation = 0;
		errno = 0;
		rc = ioctl(fd, PCIOCGETCONF, &cio);
		printf("%-34s rc=%d status=%d (expect LAST_DEVICE=0)\n",
		       "offset=0xFFFFFF", rc, (int)cio.status);
	}

	puts("\n== 9. PCIOCGETBAR pbi_reg unvalidated (negative probe) ==");
	{
		struct pci_bar_io bio;
		memset(&bio, 0, sizeof(bio));
		bio.pbi_sel.pc_bus = 0;
		bio.pbi_sel.pc_dev = 2;		/* vgapci0 */
		bio.pbi_reg = 0x10;		/* real BAR */
		errno = 0;
		rc = ioctl(fd, PCIOCGETBAR, &bio);
		printf("%-34s rc=%d errno=%d base=0x%jx len=%ju\n",
		       "GETBAR 0:2:0 reg 0x10", rc, errno,
		       (uintmax_t)bio.pbi_base, (uintmax_t)bio.pbi_length);
		bio.pbi_reg = 1;		/* misaligned junk */
		errno = 0;
		rc = ioctl(fd, PCIOCGETBAR, &bio);
		printf("%-34s rc=%d errno=%d (rle lookup contains it)\n",
		       "GETBAR reg 1 (misaligned)", rc, errno);
		bio.pbi_reg = -1;
		errno = 0;
		rc = ioctl(fd, PCIOCGETBAR, &bio);
		printf("%-34s rc=%d errno=%d\n",
		       "GETBAR reg -1 (negative)", rc, errno);
	}

	close(fd);
	puts("\nPROBE COMPLETE - guest still alive");
	return 0;
}
