/*
 * DF-3856 harness - BIOS-controlled shift counts >= 32 (undefined behavior)
 *                   used to build GPIO/PLL write masks.
 *
 * Kernel sites (all take an unchecked BIOS byte as the shift count):
 *   sys/dev/drm/radeon/radeon_combios.c:3103-3108  combios_parse_pll_table()
 *        case 1: shift = RBIOS8(offset) * 8;          [0..2040]
 *                and_mask = RBIOS8(offset) << shift;
 *                and_mask |= ~(0xff << shift);
 *                or_mask  = RBIOS8(offset) << shift;
 *                ... WREG32_PLL(addr, tmp)            [garbage masked]
 *   sys/dev/drm/radeon/radeon_combios.c:2752/2760    voltage gpio mask
 *        mask = (1 << tmp);        tmp = RBIOS8(...)  -> r100_pm_misc sink
 *   sys/dev/drm/radeon/radeon_combios.c:654-658       i2c gpiopad masks
 *        (1 << clk), (1 << data)   clk/data = RBIOS8(...)
 *   sys/dev/drm/radeon/radeon_combios.c:2687          thermal clk/data bits
 *        (1 << clk_bit), (1 << data_bit)
 *
 * C99 6.5.7p3: shifting a 32-bit value by >= 32 is undefined. This harness
 * shows the concrete misbehaviour on the audit toolchain (gcc 8.3, x86-64):
 * the count is silently taken mod 32, so a BIOS byte of 32..255 produces a
 * WRONG mask that is then written to PLL/GPIO registers - no trap, no
 * warning at runtime, purely wrong hardware programming. (The compiler is
 * entitled to do anything here; that is the point.)
 */
#include <stdio.h>
#include <stdint.h>

int main(void)
{
	volatile unsigned shift_byte, mask_byte, tmp;

	printf("== DF-3856: unchecked BIOS shift counts (C99 6.5.7p3 UB) ==\n\n");

	/* --- radeon_combios.c:3103-3108, pll table case 1 --- */
	shift_byte = 3;                        /* sane: shift = 24            */
	tmp = 0x0f;
	{
		uint32_t shift = shift_byte * 8;
		uint32_t and_mask = (uint32_t)tmp << shift;
		and_mask |= ~(0xffu << shift);
		printf("[pll case1] shift_byte=%u -> shift=%u: and_mask=0x%08x (correct: byte lane 3 kept 0x0f)\n",
		       shift_byte, shift, and_mask);
	}
	shift_byte = 4;                        /* BIOS byte 4 -> shift = 32: UB */
	tmp = 0x0f;
	{
		uint32_t shift = shift_byte * 8;
		uint32_t and_mask = (uint32_t)tmp << shift;       /* UB */
		and_mask |= ~(0xffu << shift);                    /* UB */
		printf("[pll case1] shift_byte=%u -> shift=%u: and_mask=0x%08x (UB! count taken mod 32 -> lane 0, not lane 3; expected 0x0fffffff)\n",
		       shift_byte, shift, and_mask);
	}

	/* --- radeon_combios.c:2752/2760/654-658/2687, mask = (1 << tmp) --- */
	mask_byte = 9;
	printf("[gpio mask] tmp=%u -> 1<<tmp = 0x%08x (intended single bit)\n",
	       mask_byte, (unsigned)(1u << mask_byte));
	mask_byte = 32;                        /* BIOS byte 32+: UB */
	printf("[gpio mask] tmp=%u -> 1<<tmp = 0x%08x (UB! mod-32 -> bit 0; on other arch/codegen: anything)\n",
	       mask_byte, (unsigned)(1u << mask_byte));
	mask_byte = 200;
	printf("[gpio mask] tmp=%u -> 1<<tmp = 0x%08x (UB! mod-32 -> bit 8)\n",
	       mask_byte, (unsigned)(1u << mask_byte));

	printf("\nall three sites feed these masks straight into WREG32_PLL / "
	       "r100_pm_misc() RMW / GPIO set_masks with no range check.\n");
	return 0;
}
