/* SPDX-License-Identifier: GPL-2.0-only */

#include <console/console.h>
#include <cpu/x86/lapic.h>
#include <delay.h>
#include <halt.h>

/**
 * Sending INIT IPI to self is equivalent of asserting #INIT with a bit of
 * delay.
 * An undefined number of instruction cycles will complete. All global locks
 * must be released before INIT IPI and no printk is allowed after this.
 * De-asserting INIT IPI is a no-op on later Intel CPUs.
 *
 * If you set DEBUG_HALT_SELF to 1, printk's after INIT IPI are enabled
 * but running thread may halt without releasing the lock and effectively
 * deadlock other CPUs.
 */
#define DEBUG_HALT_SELF 0

#if DEBUG_HALT_SELF
#define dprintk(LEVEL, args...) do { printk(LEVEL, ##args); } while (0)
#else
#define dprintk(LEVEL, args...) do { } while (0)
#endif

static void wait_for_ipi_completion_without_printk(const int timeout_ms)
{
	int loops = timeout_ms * 10;
	uint32_t send_status;

	/* wait for the ipi send to finish */
	dprintk(BIOS_SPEW, "Waiting for send to finish...\n");
	do {
		dprintk(BIOS_SPEW, "+");
		udelay(100);
		send_status = lapic_busy();
	} while (send_status && (--loops > 0));

	if (send_status)
		dprintk(BIOS_ERR, "timed out\n");
}

/**
 * Normally this function is defined in lapic.h as an always inline function
 * that just keeps the CPU in a hlt() loop. This does not work on all CPUs.
 * I think all hyperthreading CPUs might need this version, but I could only
 * verify this on the Intel Core Duo
 */
void stop_this_cpu(void)
{
	const int timeout_100ms = 100;
	unsigned long id = lapicid();

	printk(BIOS_DEBUG, "CPU %ld going down...\n", id);

	/* send an LAPIC INIT to myself */
	lapic_send_ipi_self(LAPIC_INT_LEVELTRIG | LAPIC_INT_ASSERT | LAPIC_DM_INIT);
	wait_for_ipi_completion_without_printk(timeout_100ms);

	mdelay(10);

	dprintk(BIOS_SPEW, "Deasserting INIT.\n");

	/* Deassert the LAPIC INIT */
	lapic_send_ipi_self(LAPIC_INT_LEVELTRIG | LAPIC_DM_INIT);
	wait_for_ipi_completion_without_printk(timeout_100ms);

	halt();
}
