/*
 * pstprobe.c -- live introspection KLD for the pass-2 audit of
 * sys/platform/pc64/x86_64/cpufreq_machdep.c (DF-5622/DF-5623 evidence).
 *
 * Safe by construction: only calls acpi_pst_md_probe() (pure CPUID/global
 * reads; no MSR access anywhere in the probe path) and prints addresses of
 * the amd0f_* helper symbols compiled into the running GENERIC kernel.
 *
 * NEVER calls amd0f_set_fidvid/amd0f_get_fidvid/amd0f_fidvid_limit:
 * they begin with rdmsr(0xc0010042) which raises #GP on non-AMD-0F CPUs
 * (that is the DF-5484 class, not what we are probing here).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>

#include <machine/md_var.h>	/* cpu_vendor_id, cpu_id, cpu_exthigh */
#include <machine/cpufunc.h>	/* do_cpuid */
#include <machine/specialreg.h>	/* CPUID_TO_FAMILY, CPUID_TO_MODEL */
#include <machine/cpufreq.h>	/* amd0f_* prototypes */

/* global in acpi_pstate_machdep.c, no public header */
const struct acpi_pst_md *acpi_pst_md_probe(void);

/* minimal local mirror of struct acpi_pst_md (sys/dev/acpica/acpi_cpu_pstate.h):
 * five function pointers; only pmd_set_pstate (3rd) is dereferenced, guarded */
struct pst_md_probe_view {
	void *pmd_check_csr;
	void *pmd_check_pstates;
	void *pmd_set_pstate;
	void *pmd_get_pstate;
	void *pmd_init;
};

static void
pstprobe_load(void *dummy __unused)
{
	const struct acpi_pst_md *md;
	uint32_t regs[4];

	kprintf("pstprobe: cpu_vendor_id=%#x (AMD=%#x INTEL=%#x) "
	    "family=%#x model=%#x\n",
	    cpu_vendor_id, CPU_VENDOR_AMD, CPU_VENDOR_INTEL,
	    CPUID_TO_FAMILY(cpu_id), CPUID_TO_MODEL(cpu_id));

	/* exact gate from acpi_pstate_machdep.c:acpi_pst_amd_probe() */
	if (cpu_exthigh >= 0x80000007) {
		do_cpuid(0x80000007, regs);
		kprintf("pstprobe: APMI(0x80000007) edx=%#x "
		    "(amd0f needs (edx & 0x06) == 0x06)\n", regs[3]);
	} else {
		kprintf("pstprobe: no 0x80000007 leaf (cpu_exthigh=%#x) "
		    "-> amd0f probe returns NULL\n", cpu_exthigh);
	}

	md = acpi_pst_md_probe();
	if (md == NULL) {
		kprintf("pstprobe: acpi_pst_md_probe() = NULL: no P-state "
		    "machdep; acpi_pst_amd0f ops NEVER selected -> "
		    "amd0f_set_fidvid UNREACHABLE on this CPU\n");
	} else {
		const struct pst_md_probe_view *v =
		    (const struct pst_md_probe_view *)md;
		kprintf("pstprobe: acpi_pst_md_probe() = %p "
		    "pmd_set_pstate=%p (amd0f backend would be a static fn "
		    "calling amd0f_set_fidvid=%p)\n",
		    md, v->pmd_set_pstate, &amd0f_set_fidvid);
	}
	kprintf("pstprobe: symbols in running kernel: "
	    "amd0f_set_fidvid=%p amd0f_get_fidvid=%p amd0f_fidvid_limit=%p\n",
	    &amd0f_set_fidvid, &amd0f_get_fidvid, &amd0f_fidvid_limit);
}

SYSINIT(pstprobe, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, pstprobe_load, NULL);

static int
pstprobe_modevent(module_t mod __unused, int type, void *data __unused)
{
	switch (type) {
	case MOD_LOAD:
		/* SYSINIT above runs at SI_SUB_CONFIGURE; nothing here */
		return 0;
	default:
		return EOPNOTSUPP;
	}
}
static moduledata_t pstprobe_mod = { "pstprobe", pstprobe_modevent, NULL };
DECLARE_MODULE(pstprobe, pstprobe_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(pstprobe, 1);
