#!/bin/sh
#
# Copyright (c) 2026 DisplayLink (UK) Ltd.
#
# This file is subject to the terms and conditions of the GNU General Public
# License v2. See the file COPYING in the main directory of this archive for
# more details.
#
# Compile-time kernel feature detection for the evdi module.
#
# To support a new kernel API change, add a compile_test block below and use
# the resulting EVDI_HAVE_* macro in the source. Never add another distro flag.
#
# Usage: conftest.sh "<CC>" "<CFLAGS>" <output-header>

set -u

CC="$1"
OUT="$2"
shift 2

# The kernel cflags are passed as separate arguments (unquoted in the Makefile
# recipe) so the shell tokenises them honouring kbuild's own quoting -- e.g. on
# arm64 -DARM64_ASM_ARCH='"armv8.5-a"'. Wrapping them in one quoted argument
# would let that embedded " break the quoting and corrupt every probe. We
# requote each one here so it can be replayed verbatim through eval.
requote() {
	for _a in "$@"; do
		printf "'"
		printf '%s' "$_a" | sed "s/'/'\\\\''/g"
		printf "' "
	done
}
CFLAGS=$(requote "$@")

WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
SRC="$WORKDIR/conftest.c"

# compile_test <MACRO>  (snippet read from stdin)
#
# Defines <MACRO> to 1 if the snippet compiles cleanly against the kernel
# headers, otherwise emits a comment so the generated header is self-documenting.
# -fsyntax-only does a full parse + typecheck (no codegen): it catches missing
# headers, unknown struct members, wrong argument counts and -- together with
# -Werror=implicit-function-declaration -- calls to functions that do not exist.
compile_test() {
	macro="$1"
	cat > "$SRC"
	# CFLAGS comes from the kernel build and can contain -D options whose
	# values carry embedded shell quoting (e.g. on arm64
	# -DARM64_ASM_ARCH='"armv8.5-a"'). Re-parse via eval so that quoting is
	# honoured exactly as kbuild intended; expanding $CFLAGS unquoted would
	# mangle it and break every probe on such architectures.
	if eval "$CC $CFLAGS -Werror=implicit-function-declaration \
	         -Werror=incompatible-pointer-types \
	         -fsyntax-only $SRC" >/dev/null 2>&1; then
		printf '#define %s 1\n' "$macro" >> "$OUT"
	else
		printf '/* %s not detected */\n' "$macro" >> "$OUT"
	fi
}

{
	echo "/* Automatically generated by conftest.sh -- do not edit. */"
	echo "#ifndef EVDI_DETECT_H"
	echo "#define EVDI_DETECT_H"
} > "$OUT"

# ---------------------------------------------------------------------------
# Feature probes
# ---------------------------------------------------------------------------

# <drm/drmP.h> was removed in 5.5. Present only on old kernels.
compile_test EVDI_HAVE_DRMP_H <<'EOF'
#include <drm/drmP.h>
static void conftest(void) { }
EOF

# <linux/minmax.h> arrived in 5.10.
compile_test EVDI_HAVE_MINMAX_H <<'EOF'
#include <linux/minmax.h>
static void conftest(void) { }
EOF

# <drm/drm_gem_ttm_helper.h> arrived in 5.5.
compile_test EVDI_HAVE_DRM_GEM_TTM_HELPER_H <<'EOF'
#include <drm/drm_gem_ttm_helper.h>
static void conftest(void) { }
EOF

# <drm/drm_damage_helper.h> arrived in 5.0.
compile_test EVDI_HAVE_DRM_DAMAGE_HELPER_H <<'EOF'
#include <drm/drm_damage_helper.h>
static void conftest(void) { }
EOF

# drm_atomic_helper_dirtyfb() and <drm/drm_damage_helper.h> arrived in 5.0.
compile_test EVDI_HAVE_ATOMIC_DIRTYFB <<'EOF'
#include <drm/drm_framebuffer.h>
#include <drm/drm_damage_helper.h>
void *conftest = (void *)&drm_atomic_helper_dirtyfb;
EOF

# drm_gem_object_put_unlocked() was folded into drm_gem_object_put() in 5.9.
# Present only on old kernels.
compile_test EVDI_HAVE_GEM_OBJECT_PUT_UNLOCKED <<'EOF'
#include <drm/drm_gem.h>
static void conftest(struct drm_gem_object *obj) { drm_gem_object_put_unlocked(obj); }
EOF

# kzalloc_obj() became a kernel-provided helper in 6.20; before that evdi
# defines its own fallback.
compile_test EVDI_HAVE_KZALLOC_OBJ <<'EOF'
#include <linux/slab.h>
static void conftest(void) { int *p = kzalloc_obj(*p, 0); (void)p; }
EOF

# The atomic helper callbacks switched to taking a struct drm_atomic_state *:
# the CRTC callbacks (.atomic_flush) in 5.11, the plane callbacks
# (.atomic_update) in 5.13. Detected via the vtable field signatures (some
# vendor/RPi kernels backport these independently).
compile_test EVDI_HAVE_CRTC_ATOMIC_STATE_ARG <<'EOF'
#include <drm/drm_modeset_helper_vtables.h>
static void conftest_flush(struct drm_crtc *c, struct drm_atomic_state *s) { }
struct drm_crtc_helper_funcs conftest = { .atomic_flush = conftest_flush };
EOF

# The atomic helper callbacks switched to taking a struct drm_atomic_commit *:
# the CRTC callbacks (.atomic_flush) in 7.2
compile_test EVDI_HAVE_CRTC_ATOMIC_COMMIT_ARG <<'EOF'
#include <drm/drm_modeset_helper_vtables.h>
static void conftest_flush(struct drm_crtc *c, struct drm_atomic_commit *s) { }
struct drm_crtc_helper_funcs conftest = { .atomic_flush = conftest_flush };
EOF

compile_test EVDI_HAVE_PLANE_ATOMIC_STATE_ARG <<'EOF'
#include <drm/drm_modeset_helper_vtables.h>
static void conftest_update(struct drm_plane *p, struct drm_atomic_state *s) { }
struct drm_plane_helper_funcs conftest = { .atomic_update = conftest_update };
EOF


# drm_gem_plane_helper_prepare_fb() (in <drm/drm_gem_atomic_helper.h>) replaced
# drm_gem_fb_prepare_fb in 5.13.
compile_test EVDI_HAVE_DRM_GEM_PLANE_HELPER_PREPARE_FB <<'EOF'
#include <drm/drm_gem_atomic_helper.h>
void *conftest = (void *)&drm_gem_plane_helper_prepare_fb;
EOF

# drm_mode_config_funcs lost its .output_poll_changed member in 6.12.
compile_test EVDI_HAVE_OUTPUT_POLL_CHANGED <<'EOF'
#include <drm/drm_mode_config.h>
static void conftest(struct drm_mode_config_funcs *f) { f->output_poll_changed = (void *)0; }
EOF

# DRIVER_PRIME feature flag was removed in 5.4 (PRIME is always available now).
compile_test EVDI_HAVE_DRIVER_PRIME <<'EOF'
#include <drm/drm_drv.h>
static unsigned int conftest(void) { return DRIVER_PRIME; }
EOF

# drmm_add_action_or_reset() / <drm/drm_managed.h> arrived in 5.8 (managed
# device cleanup); before it the release callback is invoked by hand.
compile_test EVDI_HAVE_DRMM_ADD_ACTION <<'EOF'
#include <drm/drm_managed.h>
static void conftest_cb(struct drm_device *dev, void *p) { }
static void conftest(struct drm_device *dev) { (void)drmm_add_action_or_reset(dev, conftest_cb, NULL); }
EOF

# DRM_UNLOCKED was removed in 6.8 (all ioctls are unlocked). Present on old kernels.
compile_test EVDI_HAVE_DRM_UNLOCKED <<'EOF'
#include <drm/drm_ioctl.h>
static unsigned int conftest(void) { return DRM_UNLOCKED; }
EOF

# struct drm_driver lost the GEM/dumb callbacks as they moved to
# drm_gem_object_funcs: .gem_free_object in 5.9, .dumb_destroy in 5.12.
compile_test EVDI_HAVE_DRM_DRIVER_GEM_FREE <<'EOF'
#include <drm/drm_drv.h>
static void conftest(struct drm_driver *d) { (void)d->gem_free_object; }
EOF
compile_test EVDI_HAVE_DRM_DRIVER_DUMB_DESTROY <<'EOF'
#include <drm/drm_drv.h>
#include <drm/drm_gem.h>
struct drm_driver conftest = { .dumb_destroy = drm_gem_dumb_destroy };
EOF

# In 6.6 drm_gem_prime_fd_to_handle/_handle_to_fd became the implicit defaults,
# so evdi stopped assigning them. Both helper and field still exist upstream,
# leaving no removal to detect, so we set them whenever the helper is available
# (redundant-but-correct where defaulted). REVISIT during EL9/CentOS testing.
compile_test EVDI_HAVE_GEM_PRIME_FD_TO_HANDLE <<'EOF'
#include <drm/drm_prime.h>
static void conftest(struct drm_device *dev, struct drm_file *f, int fd, uint32_t *h)
{
	(void)drm_gem_prime_fd_to_handle(dev, f, fd, h);
}
EOF

# The drm_mode_ prefix was dropped from these connector helpers in 4.19
# (drm_mode_connector_update_edid_property -> drm_connector_update_edid_property,
# and likewise for attach_encoder). Probed separately though they share a release.
compile_test EVDI_HAVE_DRM_CONNECTOR_UPDATE_EDID <<'EOF'
#include <drm/drm_connector.h>
static void conftest(struct drm_connector *c) { (void)drm_connector_update_edid_property(c, NULL); }
EOF
compile_test EVDI_HAVE_DRM_CONNECTOR_ATTACH_ENCODER <<'EOF'
#include <drm/drm_connector.h>
static void conftest(struct drm_connector *c, struct drm_encoder *e) { (void)drm_connector_attach_encoder(c, e); }
EOF

# The connector helper .mode_valid callback took a const drm_display_mode * from 6.15.
compile_test EVDI_HAVE_CONNECTOR_MODE_VALID_CONST <<'EOF'
#include <drm/drm_modeset_helper_vtables.h>
static enum drm_mode_status conftest_mv(struct drm_connector *c,
					const struct drm_display_mode *m) { return MODE_OK; }
struct drm_connector_helper_funcs conftest = { .mode_valid = conftest_mv };
EOF

# drm_connector_for_each_possible_encoder() (2-arg form) replaced
# connector->encoder_ids[] iteration in 5.5.
compile_test EVDI_HAVE_CONNECTOR_FOR_EACH_ENCODER <<'EOF'
#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>
static void conftest(struct drm_connector *c)
{
	struct drm_encoder *e;

	drm_connector_for_each_possible_encoder(c, e) { }
}
EOF

# vm_flags_mod() (6.3) is required once vma->vm_flags became immutable. Note
# some vendor kernels (EL8) reverted this, hence detection over version.
compile_test EVDI_HAVE_VM_FLAGS_MOD <<'EOF'
#include <linux/mm.h>
static void conftest(struct vm_area_struct *vma) { vm_flags_mod(vma, 0, 0); }
EOF

# dma_buf_vmap_unlocked() / dma_buf_vunmap_unlocked() were added in 6.2.
compile_test EVDI_HAVE_DMA_BUF_VMAP_UNLOCKED <<'EOF'
#include <linux/dma-buf.h>
static void conftest(struct dma_buf *db, struct iosys_map *map)
{
	(void)dma_buf_vmap_unlocked(db, map);
}
EOF

# for_each_sgtable_page() was added in 5.8 (open-coded via for_each_sg_page
# before that).
compile_test EVDI_HAVE_FOR_EACH_SGTABLE_PAGE <<'EOF'
#include <linux/scatterlist.h>
static void conftest(struct sg_table *sgt)
{
	struct sg_page_iter it;

	for_each_sgtable_page(sgt, &it, 0) { }
}
EOF

# drm_prime_sg_to_page_addr_arrays() was renamed drm_prime_sg_to_page_array()
# in 5.12.
compile_test EVDI_HAVE_DRM_PRIME_SG_TO_PAGE_ARRAY <<'EOF'
#include <drm/drm_prime.h>
static void conftest(struct sg_table *sg, struct page **pages, unsigned int n)
{
	(void)drm_prime_sg_to_page_array(sg, pages, n);
}
EOF

# <linux/compiler_attributes.h> (4.20) and the fallthrough pseudo-keyword it
# provides (5.4) -- probed separately because the header predates the macro.
compile_test EVDI_HAVE_COMPILER_ATTRIBUTES_H <<'EOF'
#include <linux/compiler_attributes.h>
static void conftest(void) { }
EOF
compile_test EVDI_HAVE_FALLTHROUGH <<'EOF'
#include <linux/compiler.h>
static void conftest(int x) { switch (x) { case 0: fallthrough; default: break; } }
EOF

# MODULE_IMPORT_NS took a bare token until 6.13, when it began requiring a
# string literal. The bare-token form stops compiling at 6.13, so it is the
# discriminator; the string form alone tells old (<5.4, macro absent) apart.
compile_test EVDI_HAVE_MODULE_IMPORT_NS <<'EOF'
#include <linux/module.h>
MODULE_IMPORT_NS(DMA_BUF);
EOF
compile_test EVDI_HAVE_MODULE_IMPORT_NS_STRING <<'EOF'
#include <linux/module.h>
MODULE_IMPORT_NS("DMA_BUF");
EOF

# <drm/drm_probe_helper.h> was split out in 5.1.
compile_test EVDI_HAVE_DRM_PROBE_HELPER_H <<'EOF'
#include <drm/drm_probe_helper.h>
static void conftest(void) { }
EOF

# struct drm_device gained a .debugfs_root member in 6.7 (along with
# debugfs_lookup_and_remove) -- gates the measure_copy debugfs feature.
compile_test EVDI_HAVE_DRM_DEBUGFS_ROOT <<'EOF'
#include <drm/drm_device.h>
static void conftest(struct drm_device *dev) { (void)dev->debugfs_root; }
EOF

# drm_prime_pages_to_sg() gained a leading struct drm_device * in 5.10. Also
# used as the marker for the 5.10 DRM line by the cursor-plane num_dirts logic,
# which has no API surface of its own but shares this exact boundary.
compile_test EVDI_HAVE_DRM_PRIME_PAGES_TO_SG_DEV <<'EOF'
#include <drm/drm_prime.h>
static void conftest(struct drm_device *dev, struct page **pages, unsigned int n)
{
	(void)drm_prime_pages_to_sg(dev, pages, n);
}
EOF

# struct drm_format_name_buf and drm_get_format_name() were removed in 5.14
# when the %p4cc printf format took over. Present only on old kernels.
compile_test EVDI_HAVE_DRM_FORMAT_NAME_BUF <<'EOF'
#include <drm/drm_fourcc.h>
struct drm_format_name_buf conftest;
EOF

# <drm/drm_irq.h> was removed in 5.15. Present only on old kernels.
compile_test EVDI_HAVE_DRM_IRQ_H <<'EOF'
#include <drm/drm_irq.h>
static void conftest(void) { }
EOF

# The GEM vmap callback moved to a map descriptor: struct dma_buf_map in 5.11,
# renamed struct iosys_map in 5.18. Either implies the vmap_is_iomem path.
compile_test EVDI_HAVE_IOSYS_MAP <<'EOF'
#include <linux/iosys-map.h>
struct iosys_map conftest;
EOF
compile_test EVDI_HAVE_DMA_BUF_MAP <<'EOF'
#include <linux/dma-buf-map.h>
struct dma_buf_map conftest;
EOF

# vm_fault_t was introduced in 4.17 (fault handlers returned int before).
compile_test EVDI_HAVE_VM_FAULT_T <<'EOF'
#include <linux/mm.h>
vm_fault_t conftest;
EOF

# The platform_driver .remove callback returns void since 6.11 (it returned
# int before). Detect the actual signature rather than the version -- some
# vendor kernels (e.g. EL9) backport this independently of the version number.
compile_test EVDI_HAVE_PLATFORM_REMOVE_VOID <<'EOF'
#include <linux/platform_device.h>
static void conftest_remove(struct platform_device *pdev) { }
struct platform_driver conftest = { .remove = conftest_remove };
EOF

# struct dev_archdata gained/kept an .iommu member on x86 before 5.9; the
# Intel-IOMMU dummy-domain workaround only applies where that field exists.
compile_test EVDI_HAVE_DEV_ARCHDATA_IOMMU <<'EOF'
#include <linux/platform_device.h>
static void conftest(struct platform_device *pdev) { pdev->dev.archdata.iommu = (void *)0; }
EOF

# struct drm_driver lost its .date member in 6.14 (DRIVER_DATE removal).
# Present only on old kernels.
compile_test EVDI_HAVE_DRM_DRIVER_DATE <<'EOF'
#include <drm/drm_drv.h>
static void conftest(struct drm_driver *drv) { (void)drv->date; }
EOF

# drm_ioctl_compat_t moved into <drm/drm_ioctl.h> in 5.16. When this is not
# available the typedef comes from <drm/drmP.h> (<5.5) or the default include
# chain (5.5-5.15).
compile_test EVDI_HAVE_DRM_IOCTL_COMPAT_T <<'EOF'
#include <drm/drm_ioctl.h>
drm_ioctl_compat_t *conftest;
EOF

# I2C_CLASS_DDC was removed in 6.8. Note the struct i2c_adapter .class member
# itself outlived it, so probe the constant the code actually assigns, not the
# field. Present only on old kernels.
compile_test EVDI_HAVE_I2C_CLASS_DDC <<'EOF'
#include <linux/i2c.h>
static unsigned int conftest(void) { return I2C_CLASS_DDC; }
EOF

# drm_helper_mode_fill_fb_struct() and the .fb_create hook gained a
# "const struct drm_format_info *" parameter in 6.17.
compile_test EVDI_HAVE_FB_FORMAT_INFO <<'EOF'
#include <drm/drm_crtc.h>
#include <drm/drm_modeset_helper.h>
static void conftest(struct drm_device *dev, struct drm_framebuffer *fb,
	      const struct drm_format_info *info,
	      const struct drm_mode_fb_cmd2 *mode_cmd)
{
	drm_helper_mode_fill_fb_struct(dev, fb, info, mode_cmd);
}
EOF

echo "#endif /* EVDI_DETECT_H */" >> "$OUT"
