/*
 * uaftrig.ko - PoC stand-in for drm.ko's LIVE vga_switcheroo call-in sites
 * (sys/dev/drm/drm_edid.c:1770 drm_get_edid_switcheroo ->
 *  vga_switcheroo_lock_ddc()/vga_switcheroo_unlock_ddc(),
 *  sys/dev/drm/i915/i915_drv.c:742 vga_switcheroo_force_migd()).
 *
 * Writing /dev/uaftrig invokes vga_switcheroo_lock_ddc()/_unlock_ddc()
 * exactly as the DRM EDID probe path does.  The module resolves those
 * symbols at load time from the loaded vga_switcheroo.ko (same as drm.ko
 * would).  After `kldunload vga_switcheroo`, the same call goes through
 * a dangling relocated reference into freed module memory.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bio.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/rman.h>
#include <sys/uio.h>
#include <sys/errno.h>

#include <linux/pci.h>

int	vga_switcheroo_lock_ddc(struct pci_dev *pdev);
int	vga_switcheroo_unlock_ddc(struct pci_dev *pdev);

static d_open_t		uaftrig_open;
static d_close_t	uaftrig_close;
static d_write_t	uaftrig_write;

static struct dev_ops uaftrig_ops = {
	{ "uaftrig", 0, 0 },
	.d_open		= uaftrig_open,
	.d_close	= uaftrig_close,
	.d_write	= uaftrig_write,
};

static cdev_t uaftrig_dev;

static int
uaftrig_open(struct dev_open_args *ap __unused)
{
	return (0);
}

static int
uaftrig_close(struct dev_close_args *ap __unused)
{
	return (0);
}

static int
uaftrig_write(struct dev_write_args *ap __unused)
{
	int r1, r2;

	r1 = vga_switcheroo_lock_ddc(NULL);
	kprintf("uaftrig: vga_switcheroo_lock_ddc(NULL) = %d\n", r1);
	r2 = vga_switcheroo_unlock_ddc(NULL);
	kprintf("uaftrig: vga_switcheroo_unlock_ddc(NULL) = %d\n", r2);

	return (0);
}

static int
uaftrig_modhandler(struct module *m __unused, int what, void *arg __unused)
{
	switch (what) {
	case MOD_LOAD:
		uaftrig_dev = make_dev(&uaftrig_ops, 0, UID_ROOT, GID_WHEEL,
				       0600, "uaftrig");
		kprintf("uaftrig: loaded /dev/uaftrig (stand-in for drm.ko's "
			"vga_switcheroo references)\n");
		break;
	case MOD_UNLOAD:
		if (uaftrig_dev != NULL)
			destroy_dev(uaftrig_dev);
		break;
	case MOD_SHUTDOWN:
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

DEV_MODULE(uaftrig, uaftrig_modhandler, NULL);

/* Models the MODULE_DEPEND(drm/i915/amdgpu, vga_switcheroo, ...) fix */
MODULE_DEPEND(uaftrig, vga_switcheroo, 1, 1, 1);
