/*
 * null_helper_dispatch.c - userland model of the DF-4047 dispatch:
 * crafted/corrupt VBIOS command revisions on DCE10/11/11.2 select
 * command_table.c handlers whose first helper-callback is NULL in the
 * DCE110/DCE112 command_table_helper tables -> kernel NULL function
 * pointer call.
 *
 * Models (source of truth, this tree):
 *  - sys/dev/drm/amd/display/dc/bios/dce110/command_table_helper_dce110.c
 *      .assign_control_parameter       = NULL
 *      .clock_source_id_to_ref_clk_src = NULL
 *      .transmitter_bp_to_atom         = NULL
 *  - dce112/command_table_helper_dce112.c: same three NULLs
 *  - dce80/command_table_helper_dce80.c:  none NULL
 *  - command_table.c dispatch:
 *      init_dig_encoder_control  :120-141  (DIGx rev not in {2,4,5} -> v1
 *                                     path; DIG1/DIG2 rev 1 -> dig1/dig2_v1)
 *      encoder_control_dig1_v1   :197-210  (:204 assign_control_parameter)
 *      encoder_control_dig2_v1   :212-225  (:219 assign_control_parameter)
 *      init_transmitter_control  :367-396  (crev 2 -> v2, 4 -> v4; crev is
 *                                     UNINITIALIZED stack if the command
 *                                     is absent - amdgpu_atom_parse_cmd_header
 *                                     returns false without writing it,
 *                                     atom.c:1405-1420)
 *      transmitter_control_v2    :398-516  (:507 transmitter_bp_to_atom)
 *      transmitter_control_v4    :652-773  (:678 clock_source_id_to_ref_clk_src)
 */
#include <stdio.h>

typedef int (*helper_fn)(void);

struct command_table_helper {
	helper_fn controller_id_to_atom;
	helper_fn encoder_action_to_atom;
	helper_fn engine_bp_to_atom;
	helper_fn clock_source_id_to_atom;
	helper_fn clock_source_id_to_atom_phy_clk_src_id;
	helper_fn signal_type_to_atom_dig_mode;
	helper_fn hpd_sel_to_atom;
	helper_fn dig_encoder_sel_to_atom;
	helper_fn phy_id_to_atom;
	helper_fn disp_power_gating_action_to_atom;
	helper_fn assign_control_parameter;
	helper_fn clock_source_id_to_ref_clk_src;
	helper_fn transmitter_bp_to_atom;
	helper_fn encoder_id_to_atom;
	helper_fn encoder_mode_bp_to_atom;
};

static int stub(void) { return 0; }

/* dce80: fully populated (dce80/command_table_helper_dce80.c) */
static const struct command_table_helper helper_dce80 = {
	stub, stub, stub, stub, stub, stub, stub, stub, stub, stub,
	stub, stub, stub, stub, stub
};

/* dce110 (DCE10, DCE11.0) and dce112 (DCE11.2/11.22): three NULLs */
static const struct command_table_helper helper_dce110 = {
	stub, stub, stub, stub, stub, stub, stub, stub, stub, stub,
	NULL,	/* .assign_control_parameter */
	NULL,	/* .clock_source_id_to_ref_clk_src */
	NULL,	/* .transmitter_bp_to_atom */
	stub, stub
};

/* returns the name of the NULL helper the modeled call site would hit,
 * or NULL if the selected path never calls a NULL helper */
static const char *
model_encoder_control(const struct command_table_helper *h, int digx_rev,
		      int dig1_rev)
{
	/* init_dig_encoder_control, command_table.c:120-141 */
	if (digx_rev == 2 || digx_rev == 4 || digx_rev == 5)
		return NULL;		/* digx_v3/v4/v5: no NULL helpers */
	/* default -> init_encoder_control_dig_v1, command_table.c:153-168 */
	if (dig1_rev == 1 && h->assign_control_parameter == NULL)
		return "assign_control_parameter"; /* :204 via encoder_control_dig1_v1 */
	return NULL;
}

static const char *
model_transmitter_control(const struct command_table_helper *h, int crev)
{
	/* init_transmitter_control, command_table.c:375-395 */
	switch (crev) {
	case 2:	return h->transmitter_bp_to_atom == NULL ?
			"transmitter_bp_to_atom" : NULL;	/* :507, v2 */
	case 3:	return NULL;				/* v3 uses non-NULL helpers */
	case 4:	return h->clock_source_id_to_ref_clk_src == NULL ?
			"clock_source_id_to_ref_clk_src" : NULL; /* :678, v4 */
	case 5:	case 6:	return NULL;			/* v1_5/v1_6: non-NULL */
	default: return NULL;				/* handler left NULL */
	}
}

int main(void)
{
	const char *e, *t;

	printf("== DF-4047 model: crafted-VBIOS revision -> NULL helper call ==\n");
	printf("(call *NULL)(...) in the kernel == immediate page-fault panic)\n\n");

	e = model_encoder_control(&helper_dce110, 0, 1);
	printf("DCE11.0, DIGx rev absent/0 + DIG1 crev=1  -> %s\n",
	    e ? e : "no NULL call");
	t = model_transmitter_control(&helper_dce110, 2);
	printf("DCE11.0, UNIPHYTransmitterControl crev=2 -> %s\n",
	    t ? t : "no NULL call");
	t = model_transmitter_control(&helper_dce110, 4);
	printf("DCE11.0, UNIPHYTransmitterControl crev=4 -> %s\n",
	    t ? t : "no NULL call");
	e = model_encoder_control(&helper_dce110, 0, 1);
	printf("DCE11.2, DIGx rev 0 + DIG2 crev=1        -> %s\n",
	    e ? e : "no NULL call");

	/* sanity: honest DCE11 VBIOS (DIGx v4/v5, UNIPHY v5/v6) and DCE80 */
	printf("DCE11.0 honest (DIGx v4, UNIPHY v5)     -> %s\n",
	    (model_encoder_control(&helper_dce110, 4, 1) ||
	     model_transmitter_control(&helper_dce110, 5)) ? "NULL!" :
	    "safe (no NULL call)");
	printf("DCE8.0  crafted (DIGx 0/DIG1 v1, UNIPHY v2/v4) -> %s\n",
	    (model_encoder_control(&helper_dce80, 0, 1) ||
	     model_transmitter_control(&helper_dce80, 2) ||
	     model_transmitter_control(&helper_dce80, 4)) ? "NULL!" :
	    "safe (dce80 table fully populated)");

	printf("\nexpected NULL-call sites in command_table.c:\n");
	printf("  :204 bp->cmd_helper->assign_control_parameter (encoder_control_dig1_v1)\n");
	printf("  :219 bp->cmd_helper->assign_control_parameter (encoder_control_dig2_v1)\n");
	printf("  :507 bp->cmd_helper->transmitter_bp_to_atom   (transmitter_control_v2)\n");
	printf("  :678 cmd->clock_source_id_to_ref_clk_src      (transmitter_control_v4)\n");
	printf("  :367-375 uninit 'crev' steering when UNIPHY cmd absent (CWE-457)\n");
	return 0;
}
