/*
 * df4945_harness.c — unit-level proof of the hw_translate_dce120 uninit-*id defect
 * (DragonFlyBSD pass-2 audit, file:
 *   sys/dev/drm/amd/display/dc/gpio/dce120/hw_translate_dce120.c)
 *
 * offset_to_id() below is a VERBATIM copy from hw_translate_dce120.c lines 61-201
 * with exactly one substitution: ASSERT_CRITICAL(false) maps to a printf marker.
 * Register constants are the real DCE12/Vega10 values:
 *   REG(x) = DCE_BASE__INST0_SEG2 (0x34C0) + mm x      (vega10_ip_offset.h:277,
 *   dce_12_0_offset.h: BASE_IDX 2 for all DC_GPIO regs — see file lines 44-56).
 * Enums are the real gpio_types.h values.
 *
 * Note: unlike dce80, the DCE12 offset_to_id has NO mmGPIOPAD_A case and
 * id_to_offset has no GPIO_ID_GPIO_PAD case — the only cross-domain en
 * producer missing; the DDC-family *id-left-unwritten defect is identical.
 */
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

#define ASSERT_CRITICAL(x) printf("    [ASSERT_CRITICAL hit]\n")

enum gpio_id {
	GPIO_ID_UNKNOWN = (-1),
	GPIO_ID_DDC_DATA, GPIO_ID_DDC_CLOCK, GPIO_ID_GENERIC, GPIO_ID_HPD,
	GPIO_ID_GPIO_PAD, GPIO_ID_VIP_PAD, GPIO_ID_SYNC, GPIO_ID_GSL,
	GPIO_ID_COUNT,
};
#define GPIO_ENUM_UNKNOWN 32
struct gpio_pin_info {
	uint32_t offset, offset_y, offset_en, offset_mask;
	uint32_t mask, mask_y, mask_en, mask_mask;
};
enum gpio_generic { GPIO_GENERIC_UNKNOWN=(-1), GPIO_GENERIC_A, GPIO_GENERIC_B,
	GPIO_GENERIC_C, GPIO_GENERIC_D, GPIO_GENERIC_E, GPIO_GENERIC_F,
	GPIO_GENERIC_G, GPIO_GENERIC_COUNT };
enum gpio_hpd { GPIO_HPD_UNKNOWN=(-1), GPIO_HPD_1, GPIO_HPD_2, GPIO_HPD_3,
	GPIO_HPD_4, GPIO_HPD_5, GPIO_HPD_6, GPIO_HPD_COUNT };
enum gpio_sync { GPIO_SYNC_UNKNOWN=(-1), GPIO_SYNC_HSYNC_A, GPIO_SYNC_VSYNC_A,
	GPIO_SYNC_HSYNC_B, GPIO_SYNC_VSYNC_B, GPIO_SYNC_COUNT };
enum gpio_gsl { GPIO_GSL_UNKNOWN=(-1), GPIO_GSL_GENLOCK_CLOCK,
	GPIO_GSL_GENLOCK_VSYNC, GPIO_GSL_SWAPLOCK_A, GPIO_GSL_SWAPLOCK_B,
	GPIO_GSL_COUNT };
enum gpio_ddc_line { GPIO_DDC_LINE_UNKNOWN=(-1), GPIO_DDC_LINE_DDC1,
	GPIO_DDC_LINE_DDC2, GPIO_DDC_LINE_DDC3, GPIO_DDC_LINE_DDC4,
	GPIO_DDC_LINE_DDC5, GPIO_DDC_LINE_DDC6, GPIO_DDC_LINE_DDC_VGA,
	GPIO_DDC_LINE_VIP_PAD, GPIO_DDC_LINE_I2C_PAD = GPIO_DDC_LINE_VIP_PAD,
	GPIO_DDC_LINE_COUNT };

/* REG() arithmetic per hw_translate_dce120.c:44-56 */
#define BASE_INNER(seg)	DCE_BASE__INST0_SEG ## seg
#define BASE(seg)	BASE_INNER(seg)
#define REG(reg_name)	BASE(mm ## reg_name ## _BASE_IDX) + mm ## reg_name

#define DCE_BASE__INST0_SEG2 0x34C0

/* dce_12_0_offset.h (all BASE_IDX 2) */
#define mmDC_GPIO_GENERIC_A	0x20df
#define mmDC_GPIO_GENERIC_A_BASE_IDX	2
#define mmDC_GPIO_DDC1_A	0x20e7
#define mmDC_GPIO_DDC1_A_BASE_IDX	2
#define mmDC_GPIO_DDC2_A	0x20eb
#define mmDC_GPIO_DDC2_A_BASE_IDX	2
#define mmDC_GPIO_DDC3_A	0x20ef
#define mmDC_GPIO_DDC3_A_BASE_IDX	2
#define mmDC_GPIO_DDC4_A	0x20f3
#define mmDC_GPIO_DDC4_A_BASE_IDX	2
#define mmDC_GPIO_DDC5_A	0x20f7
#define mmDC_GPIO_DDC5_A_BASE_IDX	2
#define mmDC_GPIO_DDC6_A	0x20fb
#define mmDC_GPIO_DDC6_A_BASE_IDX	2
#define mmDC_GPIO_SYNCA_A	0x2103
#define mmDC_GPIO_SYNCA_A_BASE_IDX	2
#define mmDC_GPIO_GENLK_A	0x2107
#define mmDC_GPIO_GENLK_A_BASE_IDX	2
#define mmDC_GPIO_HPD_A	0x210b
#define mmDC_GPIO_HPD_A_BASE_IDX	2
#define mmDC_GPIO_DDCVGA_A	0x20ff
#define mmDC_GPIO_DDCVGA_A_BASE_IDX	2
#define mmDC_GPIO_I2CPAD_A	0x2117
#define mmDC_GPIO_I2CPAD_A_BASE_IDX	2
#define mmDC_GPIO_PWRSEQ_A	0x210f
#define mmDC_GPIO_PWRSEQ_A_BASE_IDX	2
#define mmDC_GPIO_PAD_STRENGTH_1	0x2112
#define mmDC_GPIO_PAD_STRENGTH_1_BASE_IDX	2
#define mmDC_GPIO_PAD_STRENGTH_2	0x2113
#define mmDC_GPIO_PAD_STRENGTH_2_BASE_IDX	2
#define mmDC_GPIO_DEBUG	0x2082
#define mmDC_GPIO_DEBUG_BASE_IDX	2

/* dce_12_0_sh_mask.h */
#define DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK 0x00000001L
#define DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK 0x00000100L
#define DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK 0x00010000L
#define DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK 0x00100000L
#define DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK 0x00200000L
#define DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK 0x00400000L
#define DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK 0x00800000L
#define DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK 0x00000001L
#define DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK 0x00000100L
#define DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK 0x00010000L
#define DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK 0x01000000L
#define DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK 0x04000000L
#define DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK 0x10000000L
#define DC_GPIO_SYNCA_A__DC_GPIO_HSYNCA_A_MASK 0x00000001L
#define DC_GPIO_SYNCA_A__DC_GPIO_VSYNCA_A_MASK 0x00000100L
#define DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK 0x00000001L
#define DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK 0x00000100L
#define DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK 0x00010000L
#define DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK 0x01000000L

/* ============ VERBATIM from hw_translate_dce120.c:61-201 ============ */
static bool offset_to_id(
	uint32_t offset,
	uint32_t mask,
	enum gpio_id *id,
	uint32_t *en)
{
	switch (offset) {
	/* GENERIC */
	case REG(DC_GPIO_GENERIC_A):
		*id = GPIO_ID_GENERIC;
		switch (mask) {
		case DC_GPIO_GENERIC_A__DC_GPIO_GENERICA_A_MASK:
			*en = GPIO_GENERIC_A;
			return true;
		case DC_GPIO_GENERIC_A__DC_GPIO_GENERICB_A_MASK:
			*en = GPIO_GENERIC_B;
			return true;
		case DC_GPIO_GENERIC_A__DC_GPIO_GENERICC_A_MASK:
			*en = GPIO_GENERIC_C;
			return true;
		case DC_GPIO_GENERIC_A__DC_GPIO_GENERICD_A_MASK:
			*en = GPIO_GENERIC_D;
			return true;
		case DC_GPIO_GENERIC_A__DC_GPIO_GENERICE_A_MASK:
			*en = GPIO_GENERIC_E;
			return true;
		case DC_GPIO_GENERIC_A__DC_GPIO_GENERICF_A_MASK:
			*en = GPIO_GENERIC_F;
			return true;
		case DC_GPIO_GENERIC_A__DC_GPIO_GENERICG_A_MASK:
			*en = GPIO_GENERIC_G;
			return true;
		default:
			ASSERT_CRITICAL(false);
			return false;
		}
	break;
	/* HPD */
	case REG(DC_GPIO_HPD_A):
		*id = GPIO_ID_HPD;
		switch (mask) {
		case DC_GPIO_HPD_A__DC_GPIO_HPD1_A_MASK:
			*en = GPIO_HPD_1;
			return true;
		case DC_GPIO_HPD_A__DC_GPIO_HPD2_A_MASK:
			*en = GPIO_HPD_2;
			return true;
		case DC_GPIO_HPD_A__DC_GPIO_HPD3_A_MASK:
			*en = GPIO_HPD_3;
			return true;
		case DC_GPIO_HPD_A__DC_GPIO_HPD4_A_MASK:
			*en = GPIO_HPD_4;
			return true;
		case DC_GPIO_HPD_A__DC_GPIO_HPD5_A_MASK:
			*en = GPIO_HPD_5;
			return true;
		case DC_GPIO_HPD_A__DC_GPIO_HPD6_A_MASK:
			*en = GPIO_HPD_6;
			return true;
		default:
			ASSERT_CRITICAL(false);
			return false;
		}
	break;
	/* SYNCA */
	case REG(DC_GPIO_SYNCA_A):
		*id = GPIO_ID_SYNC;
		switch (mask) {
		case DC_GPIO_SYNCA_A__DC_GPIO_HSYNCA_A_MASK:
			*en = GPIO_SYNC_HSYNC_A;
			return true;
		case DC_GPIO_SYNCA_A__DC_GPIO_VSYNCA_A_MASK:
			*en = GPIO_SYNC_VSYNC_A;
			return true;
		default:
			ASSERT_CRITICAL(false);
			return false;
		}
	break;
	/* REG(DC_GPIO_GENLK_MASK */
	case REG(DC_GPIO_GENLK_A):
		*id = GPIO_ID_GSL;
		switch (mask) {
		case DC_GPIO_GENLK_A__DC_GPIO_GENLK_CLK_A_MASK:
			*en = GPIO_GSL_GENLOCK_CLOCK;
			return true;
		case DC_GPIO_GENLK_A__DC_GPIO_GENLK_VSYNC_A_MASK:
			*en = GPIO_GSL_GENLOCK_VSYNC;
			return true;
		case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_A_A_MASK:
			*en = GPIO_GSL_SWAPLOCK_A;
			return true;
		case DC_GPIO_GENLK_A__DC_GPIO_SWAPLOCK_B_A_MASK:
			*en = GPIO_GSL_SWAPLOCK_B;
			return true;
		default:
			ASSERT_CRITICAL(false);
			return false;
		}
	break;
	/* DDC */
	/* we don't care about the GPIO_ID for DDC
	 * in DdcHandle it will use GPIO_ID_DDC_DATA/GPIO_ID_DDC_CLOCK
	 * directly in the create method */
	case REG(DC_GPIO_DDC1_A):
		*en = GPIO_DDC_LINE_DDC1;
		return true;
	case REG(DC_GPIO_DDC2_A):
		*en = GPIO_DDC_LINE_DDC2;
		return true;
	case REG(DC_GPIO_DDC3_A):
		*en = GPIO_DDC_LINE_DDC3;
		return true;
	case REG(DC_GPIO_DDC4_A):
		*en = GPIO_DDC_LINE_DDC4;
		return true;
	case REG(DC_GPIO_DDC5_A):
		*en = GPIO_DDC_LINE_DDC5;
		return true;
	case REG(DC_GPIO_DDC6_A):
		*en = GPIO_DDC_LINE_DDC6;
		return true;
	case REG(DC_GPIO_DDCVGA_A):
		*en = GPIO_DDC_LINE_DDC_VGA;
		return true;
	/* GPIO_I2CPAD */
	case REG(DC_GPIO_I2CPAD_A):
		*en = GPIO_DDC_LINE_I2C_PAD;
		return true;
	/* Not implemented */
	case REG(DC_GPIO_PWRSEQ_A):
	case REG(DC_GPIO_PAD_STRENGTH_1):
	case REG(DC_GPIO_PAD_STRENGTH_2):
	case REG(DC_GPIO_DEBUG):
		return false;
	/* UNEXPECTED */
	default:
		ASSERT_CRITICAL(false);
		return false;
	}
}

#define HPD_REGS_SIZE 6 /* hw_factory_dce120.c hpd_regs[] */

int main(void)
{
	int fails = 0;
	enum gpio_id id;
	uint32_t en;
	bool r;

	printf("== DCE12 REG() domain: REG(DC_GPIO_DDC1_A)=0x%x REG(DC_GPIO_DDCVGA_A)=0x%x REG(DC_GPIO_I2CPAD_A)=0x%x ==\n",
		REG(DC_GPIO_DDC1_A), REG(DC_GPIO_DDCVGA_A), REG(DC_GPIO_I2CPAD_A));
	printf("(VBIOS gpio_pin_lut data_a_reg_index stores these full offsets — bios_parser2.c:537-539)\n\n");

	printf("== T1: DDC/VGA/I2CPAD offsets return success WITHOUT writing *id (DF-4945) ==\n");
	printf("(dal_gpio_service_create_irq gpio_service.c:148 declares 'enum gpio_id id;' uninitialized)\n");
	id = (enum gpio_id)0x7B7B7B7B;
	r = offset_to_id(REG(DC_GPIO_DDCVGA_A), 0x100, &id, &en);
	printf("offset_to_id(REG(DDCVGA),0x100) = %s en=%d(id==0x%x UNTOUCHED)\n",
		r ? "true" : "false", en, (unsigned)id);
	if (r && id == (enum gpio_id)0x7B7B7B7B) {
		printf("  -> BUG(DF-4945): success, *id never written; garbage id==GPIO_ID_HPD(3)\n");
		printf("     => dal_gpio_create(HPD, en=%d) with en>=%d indexes hpd_regs[%d] OOB\n",
			en, HPD_REGS_SIZE, en);
		fails++;
	}
	id = (enum gpio_id)0x7B7B7B7B;
	r = offset_to_id(REG(DC_GPIO_I2CPAD_A), 0x1, &id, &en);
	printf("offset_to_id(REG(I2CPAD),0x1)    = %s en=%d(id==0x%x UNTOUCHED)\n",
		r ? "true" : "false", en, (unsigned)id);
	if (r && id == (enum gpio_id)0x7B7B7B7B) {
		printf("  -> BUG(DF-4945): en=7 >= hpd_regs[%d]: garbage-id==HPD path is OOB\n",
			HPD_REGS_SIZE);
		fails++;
	}

	printf("\n== T2 (negative): no GPIOPAD case in DCE12 translate ==\n");
	id = (enum gpio_id)0x7B7B7B7B;
	r = offset_to_id(0x183 /* mmGPIOPAD_A raw */, 1u << 20, &id, &en);
	printf("offset_to_id(0x183,1<<20) = %s (no GPIOPAD mapping -> rejected: OK;"
	       " the dce80 cross-domain producer does not exist here)\n",
		r ? "true" : "false");
	if (r) fails++;

	printf("\n%d contract-violation reproductions\n", fails);
	return (fails > 0) ? 0 : 1;
}
