/*
 * dcnauto_harness.c — DF-4199 evidence harness.
 *
 * Compiles and drives the REAL kernel sources:
 *   calcs/dcn_calc_auto.c  (the DCN1 "HW gospel" bandwidth calculator)
 *   calcs/dcn_calc_math.c  (dcn_bw_min2/ceil2/pow/log helpers)
 * exactly the way the kernel does (calcs/dcn_calcs.c:701-1035):
 *   memset(v, 0, sizeof(*v))                              (dcn_calcs.c:716)
 *   + DCN1 socbb/ip defaults  (dcn10_soc_defaults / dcn10_ip_defaults,
 *     transcribed verbatim from dcn_calcs.c:57-157)
 *   + per-plane stream/plane parameters (dcn_calcs.c:845-984)
 *   then scaler_settings_calculation()
 *        mode_support_and_system_configuration()
 *        display_pipe_configuration()
 *        dispclkdppclkdcfclk_deep_sleep_prefetch_parameters_watermarks_
 *        and_performance_calculation()
 *
 * Tests:
 *   dcc        — DF-4199: two byte-identical planes, plane0 dcc_enable=yes,
 *                plane1 dcc_enable=no.  read_bandwidth[0] == read_bandwidth[1]
 *                EXACTLY  <=>  the DCC overhead factor (1 + 1/256) at
 *                dcn_calc_auto.c:178 is integer 1 (dead), not 1.00390625.
 *   pte        — same isolation for the PTE factors (1 + 1/64) at :181,
 *                (1 + 1/256) at :184, (1 + 1/512) at :187.
 *   degen      — checklist (a)+(d) negative results: pixel_clock==0,
 *                htotal==0, all-zero soc clocks; assert NO SIGFPE/#DE,
 *                validation fails safe, and voltage_level stays in [0,5]
 *                (the only float->int index conversion feeding
 *                dcfclk_per_state[] indexing at dcn_calc_auto.c:981).
 *   log0       — checklist (b): real dcn_bw_log(0.0f, 2.0f) value and the
 *                real dpte path with viewport_width==0 -> garbage floats,
 *                no trap.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <math.h>

#include "dm_services.h"	/* stub first: types for bw_fixed.h inlines */
#include "dcn_calc_auto.h"
#include "dcn_calc_math.h"

unsigned long g_assert_count;
unsigned long g_dtrace_count;

/* ---- DCN1 defaults, transcribed from calcs/dcn_calcs.c:57-157 ---- */
static void fill_socbb_ip(struct dcn_bw_internal_vars *v)
{
	/* soc defaults (dcn10_soc_defaults) */
	v->sr_exit_time = 17;			/* us */
	v->sr_enter_plus_exit_time = 19;	/* us */
	v->urgent_latency = 4;			/* us */
	v->dram_clock_change_latency = 17;	/* us */
	v->write_back_latency = 12;		/* us */
	v->percent_of_ideal_drambw_received_after_urg_latency = 80; /* % */
	v->dcfclkv_max0p9 = 655;
	v->dcfclkv_nom0p8 = 626;
	v->dcfclkv_mid0p72 = 600;
	v->dcfclkv_min0p65 = 300;
	v->max_dispclk_vmax0p9 = 1108;
	v->max_dispclk_vnom0p8 = 1029;
	v->max_dispclk_vmid0p72 = 960;
	v->max_dispclk_vmin0p65 = 626;
	v->max_dppclk_vmax0p9 = 720;
	v->max_dppclk_vnom0p8 = 686;
	v->max_dppclk_vmid0p72 = 626;
	v->max_dppclk_vmin0p65 = 400;
	v->phyclkv_max0p9 = 900;
	v->phyclkv_nom0p8 = 847;
	v->phyclkv_mid0p72 = 800;
	v->phyclkv_min0p65 = 600;
	v->fabric_and_dram_bandwidth_vmax0p9 = 38.4f;
	v->fabric_and_dram_bandwidth_vnom0p8 = 34.133f;
	v->fabric_and_dram_bandwidth_vmid0p72 = 29.866f;
	v->fabric_and_dram_bandwidth_vmin0p65 = 12.8f;
	v->number_of_channels = 2;
	v->socclk = 208;
	v->downspreading = 0.5f;
	v->round_trip_ping_latency_cycles = 128;
	v->urgent_out_of_order_return_per_channel = 256;
	v->vmm_page_size = 4096;
	v->return_bus_width = 64;

	/* ip defaults (dcn10_ip_defaults) */
	v->rob_buffer_size_in_kbyte = 64;
	v->det_buffer_size_in_kbyte = 164;
	v->dpp_output_buffer_pixels = 2560;
	v->opp_output_buffer_lines = 1;
	v->pixel_chunk_size_in_kbyte = 8;
	v->pte_enable = dcn_bw_yes;
	v->pte_chunk_size = 2;
	v->meta_chunk_size = 2;
	v->writeback_chunk_size = 2;
	v->odm_capability = dcn_bw_no;
	v->dsc_capability = dcn_bw_no;
	v->line_buffer_size = 589824;
	v->max_line_buffer_lines = 12;
	v->is_line_buffer_bpp_fixed = dcn_bw_no;
	v->line_buffer_fixed_bpp = dcn_bw_na;
	v->writeback_luma_buffer_size = 12;
	v->writeback_chroma_buffer_size = 8;
	v->max_num_dpp = 4;
	v->max_num_writeback = 2;
	v->max_dchub_topscl_throughput = 4;
	v->max_pscl_tolb_throughput = 2;
	v->max_lb_tovscl_throughput = 4;
	v->max_vscl_tohscl_throughput = 4;
	v->max_hscl_ratio = 4;
	v->max_vscl_ratio = 4;
	v->max_hscl_taps = 8;
	v->max_vscl_taps = 8;
	v->pte_buffer_size_in_requests = 42;
	v->dispclk_ramping_margin = 1;
	v->under_scan_factor = 1.11f;
	v->max_inter_dcn_tile_repeaters = 8;
	v->can_vstartup_lines_exceed_vsync_plus_back_porch_lines_minus_one = dcn_bw_no;
	v->bug_forcing_luma_and_chroma_request_to_same_size_fixed = dcn_bw_no;

	/* per-state tables exactly as dcn_calcs.c:796-831 fills them */
	v->voltage[5] = dcn_bw_no_support;
	v->voltage[4] = dcn_bw_v_max0p9;
	v->voltage[3] = dcn_bw_v_max0p9;
	v->voltage[2] = dcn_bw_v_nom0p8;
	v->voltage[1] = dcn_bw_v_mid0p72;
	v->voltage[0] = dcn_bw_v_min0p65;
	v->fabric_and_dram_bandwidth_per_state[5] = v->fabric_and_dram_bandwidth_vmax0p9;
	v->fabric_and_dram_bandwidth_per_state[4] = v->fabric_and_dram_bandwidth_vmax0p9;
	v->fabric_and_dram_bandwidth_per_state[3] = v->fabric_and_dram_bandwidth_vmax0p9;
	v->fabric_and_dram_bandwidth_per_state[2] = v->fabric_and_dram_bandwidth_vnom0p8;
	v->fabric_and_dram_bandwidth_per_state[1] = v->fabric_and_dram_bandwidth_vmid0p72;
	v->fabric_and_dram_bandwidth_per_state[0] = v->fabric_and_dram_bandwidth_vmin0p65;
	v->dcfclk_per_state[5] = v->dcfclkv_max0p9;
	v->dcfclk_per_state[4] = v->dcfclkv_max0p9;
	v->dcfclk_per_state[3] = v->dcfclkv_max0p9;
	v->dcfclk_per_state[2] = v->dcfclkv_nom0p8;
	v->dcfclk_per_state[1] = v->dcfclkv_mid0p72;
	v->dcfclk_per_state[0] = v->dcfclkv_min0p65;
	v->max_dispclk[5] = v->max_dispclk_vmax0p9;
	v->max_dispclk[4] = v->max_dispclk_vmax0p9;
	v->max_dispclk[3] = v->max_dispclk_vmax0p9;
	v->max_dispclk[2] = v->max_dispclk_vnom0p8;
	v->max_dispclk[1] = v->max_dispclk_vmid0p72;
	v->max_dispclk[0] = v->max_dispclk_vmin0p65;
	v->max_dppclk[5] = v->max_dppclk_vmax0p9;
	v->max_dppclk[4] = v->max_dppclk_vmax0p9;
	v->max_dppclk[3] = v->max_dppclk_vmax0p9;
	v->max_dppclk[2] = v->max_dppclk_vnom0p8;
	v->max_dppclk[1] = v->max_dppclk_vmid0p72;
	v->max_dppclk[0] = v->max_dppclk_vmin0p65;
	v->phyclk_per_state[5] = v->phyclkv_max0p9;
	v->phyclk_per_state[4] = v->phyclkv_max0p9;
	v->phyclk_per_state[3] = v->phyclkv_max0p9;
	v->phyclk_per_state[2] = v->phyclkv_nom0p8;
	v->phyclk_per_state[1] = v->phyclkv_mid0p72;
	v->phyclk_per_state[0] = v->phyclkv_min0p65;
	v->synchronized_vblank = dcn_bw_no;
	v->ta_pscalculation = dcn_bw_override;
	v->allow_different_hratio_vratio = dcn_bw_yes;
}

/* one ordinary 1080p-style plane/stream, mirroring dcn_calcs.c:845-984 */
static void fill_plane(struct dcn_bw_internal_vars *v, int k)
{
	v->underscan_output[k] = false;
	v->interlace_output[k] = false;
	v->htotal[k] = 2200;
	v->vtotal[k] = 1111;
	v->vactive[k] = 1080;
	v->v_sync_plus_back_porch[k] = 29;
	v->pixel_clock[k] = 148.5f;
	v->dcc_enable[k] = dcn_bw_no;
	v->source_pixel_format[k] = dcn_bw_rgb_sub_32;
	v->source_surface_mode[k] = dcn_bw_sw_64_kb_s;
	v->lb_bit_per_pixel[k] = 30;
	v->viewport_width[k] = 1920;
	v->viewport_height[k] = 1080;
	v->scaler_rec_out_width[k] = 1920;
	v->scaler_recout_height[k] = 1080;
	v->override_hta_ps[k] = 1;
	v->override_vta_ps[k] = 1;
	v->override_hta_pschroma[k] = 2;
	v->override_vta_pschroma[k] = 2;
	v->source_scan[k] = dcn_bw_hor;
	v->dcc_rate[k] = 1;
	v->output_format[k] = dcn_bw_444;
	v->output[k] = dcn_bw_dp;
	v->output_deep_color[k] = dcn_bw_encoder_8bpc;
}

/* run the kernel's full call sequence (dcn_calcs.c:987-1035) */
static void run_all(struct dcn_bw_internal_vars *v)
{
	scaler_settings_calculation(v);
	mode_support_and_system_configuration(v);
	display_pipe_configuration(v);
	dispclkdppclkdcfclk_deep_sleep_prefetch_parameters_watermarks_and_performance_calculation(v);
}

static void show_voltage(const char *tag, struct dcn_bw_internal_vars *v)
{
	/* dcn_calc_auto.c:975-981: float -> int conversion then array index */
	int vl = (int)v->voltage_level;
	printf("  [%s] voltage_level=%d (must be 0..5)  mode_support[0][0]=%s\n",
	       tag, vl,
	       v->mode_support_with_immediate_flip[0][0] == dcn_bw_yes ? "yes" : "no");
	if (vl < 0 || vl > 5) {
		printf("  !! INDEX OUT OF RANGE — OOB read at dcn_calc_auto.c:981\n");
		exit(3);
	}
}

static int test_dcc(struct dcn_bw_internal_vars *v)
{
	printf("== test dcc: plane0 dcc=yes vs plane1 dcc=no, otherwise identical ==\n");
	memset(v, 0, sizeof(*v));
	fill_socbb_ip(v);
	v->number_of_active_planes = 2;
	fill_plane(v, 0);
	fill_plane(v, 1);
	v->dcc_enable[0] = dcn_bw_yes;	/* the only difference */
	scaler_settings_calculation(v);
	mode_support_and_system_configuration(v);

	float b0 = v->read_bandwidth[0];	/* got *(1 + 1/256)  :178 */
	float b1 = v->read_bandwidth[1];	/* no DCC factor      */
	printf("  read_bw[dcc]=%.6f  read_bw[nodcc]=%.6f  ratio=%.9f\n",
	       b0, b1, b1 != 0.0f ? b0 / b1 : 0.0f);
	if (b0 == b1) {
		printf("  DEFECT PRESENT: factor (1 + 1/256) evaluated to exactly 1\n"
		       "  (C integer constant division: 1/256 == 0)\n");
		return 1;
	}
	if (fabsf(b0 / b1 - (1.0f + 1.0f / 256.0f)) < 1e-6f) {
		printf("  FIXED: factor is (1.0 + 1.0/256.0) = 1.00390625\n");
		return 0;
	}
	printf("  UNEXPECTED ratio\n");
	return 2;
}

static int test_pte(struct dcn_bw_internal_vars *v)
{
	printf("== test pte: PTE row overhead factors :181/:184/:187 ==\n");
	memset(v, 0, sizeof(*v));
	fill_socbb_ip(v);
	v->number_of_active_planes = 2;
	fill_plane(v, 0);
	fill_plane(v, 1);
	/* vert scan + 4KB_S RGB32 -> :181 branch (1 + 1/64) on plane0 only */
	v->source_scan[0] = dcn_bw_vert;
	v->source_surface_mode[0] = dcn_bw_sw_4_kb_s;
	v->source_scan[1] = dcn_bw_vert;
	v->source_surface_mode[1] = dcn_bw_sw_4_kb_s;
	v->pte_enable = dcn_bw_yes;
	scaler_settings_calculation(v);
	mode_support_and_system_configuration(v);

	/* isolate the pte factor: plane1 pte off is not selectable (global), so
	 * compare plane0 (4KB_S vert -> :181 applies) against a third run with
	 * sw_var_d (falls to generic :187 (1 + 1/512)).  Both planes below use
	 * the identical swath math, so the ratio isolates the constant. */
	float base41 = v->read_bandwidth[0];
	memset(v, 0, sizeof(*v));
	fill_socbb_ip(v);
	v->number_of_active_planes = 1;
	fill_plane(v, 0);
	v->source_scan[0] = dcn_bw_vert;
	v->source_surface_mode[0] = dcn_bw_sw_var_d;
	scaler_settings_calculation(v);
	mode_support_and_system_configuration(v);
	float base_var = v->read_bandwidth[0];
	printf("  4KB_S-vert read_bw=%.6f  var_d-vert read_bw=%.6f (expect equal:\n"
	       "  (1+1/64) and (1+1/512) both == 1 as compiled)\n", base41, base_var);
	return base41 == base_var ? 1 : 0;
}

static void test_degenerate(const char *tag, void (*mutate)(struct dcn_bw_internal_vars *))
{
	struct dcn_bw_internal_vars *v = calloc(1, sizeof(*v));
	printf("== degen %s ==\n", tag);
	memset(v, 0, sizeof(*v));
	fill_socbb_ip(v);
	v->number_of_active_planes = 1;
	fill_plane(v, 0);
	mutate(v);
	run_all(v);
	show_voltage(tag, v);
	printf("  no SIGFPE / no #DE: all divisions are IEEE-754 float\n");
	free(v);
}

static void m_pixelclock0(struct dcn_bw_internal_vars *v) { v->pixel_clock[0] = 0; }
static void m_htotal0(struct dcn_bw_internal_vars *v) { v->htotal[0] = 0; }
static void m_zeroclocks(struct dcn_bw_internal_vars *v)
{
	int i;
	for (i = 0; i <= number_of_states_plus_one; i++) {
		v->dcfclk_per_state[i] = 0;
		v->fabric_and_dram_bandwidth_per_state[i] = 0;
		v->phyclk_per_state[i] = 0;
	}
	v->urgent_latency = 0;
	v->dispclk_dppclk_ratio = 0;	/* also poison the ratio used at :1198 */
}

static void test_log0(void)
{
	printf("== log0: real dcn_bw_log(0.0f, 2.0f) and dcn_bw_pow garbage ==\n");
	float l = dcn_bw_log(0.0f, 2.0f);
	printf("  dcn_bw_log(0.0f,2.0f) = %.1f  (dml_log2(0) twin: -127)\n", l);
	printf("  dcn_bw_pow(2.0f, %.1ff) = %.9g (subnormal, no trap)\n",
	       l, dcn_bw_pow(2.0f, dcn_bw_floor2(l, 1.0f)));

	struct dcn_bw_internal_vars *v = calloc(1, sizeof(*v));
	memset(v, 0, sizeof(*v));
	fill_socbb_ip(v);
	v->number_of_active_planes = 1;
	fill_plane(v, 0);
	v->viewport_width[0] = 0;	/* degenerate plane: arg to dcn_bw_log -> 0 */
	run_all(v);
	show_voltage("viewport0", v);
	printf("  dpte path with viewport_width=0: garbage floats only, no trap\n");
	free(v);
}

int main(int argc, char **argv)
{
	struct dcn_bw_internal_vars *v = calloc(1, sizeof(*v));
	const char *mode = argc > 1 ? argv[1] : "all";
	int rc = 0;

	if (!strcmp(mode, "dcc") || !strcmp(mode, "all"))
		rc |= test_dcc(v) ? 1 : 0;
	if (!strcmp(mode, "pte") || !strcmp(mode, "all"))
		rc |= test_pte(v) ? 1 : 0;
	if (!strcmp(mode, "degen") || !strcmp(mode, "all")) {
		test_degenerate("pixel_clock=0", m_pixelclock0);
		test_degenerate("htotal=0", m_htotal0);
		test_degenerate("zero-socbb-clocks", m_zeroclocks);
	}
	if (!strcmp(mode, "log0") || !strcmp(mode, "all"))
		test_log0();

	free(v);
	printf("harness rc=%d (1 == defect demonstrated at baseline)\n", rc);
	return rc;
}
