xref: /kernel/linux/linux-6.6/arch/x86/xen/spinlock.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Split spinlock implementation out into its own file, so it can be
462306a36Sopenharmony_ci * compiled in a FTRACE-compatible way.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/kernel.h>
762306a36Sopenharmony_ci#include <linux/spinlock.h>
862306a36Sopenharmony_ci#include <linux/slab.h>
962306a36Sopenharmony_ci#include <linux/atomic.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <asm/paravirt.h>
1262306a36Sopenharmony_ci#include <asm/qspinlock.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <xen/events.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "xen-ops.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic DEFINE_PER_CPU(int, lock_kicker_irq) = -1;
1962306a36Sopenharmony_cistatic DEFINE_PER_CPU(char *, irq_name);
2062306a36Sopenharmony_cistatic DEFINE_PER_CPU(atomic_t, xen_qlock_wait_nest);
2162306a36Sopenharmony_cistatic bool xen_pvspin = true;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic void xen_qlock_kick(int cpu)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	int irq = per_cpu(lock_kicker_irq, cpu);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	/* Don't kick if the target's kicker interrupt is not initialized. */
2862306a36Sopenharmony_ci	if (irq == -1)
2962306a36Sopenharmony_ci		return;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	xen_send_IPI_one(cpu, XEN_SPIN_UNLOCK_VECTOR);
3262306a36Sopenharmony_ci}
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/*
3562306a36Sopenharmony_ci * Halt the current CPU & release it back to the host
3662306a36Sopenharmony_ci */
3762306a36Sopenharmony_cistatic void xen_qlock_wait(u8 *byte, u8 val)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	int irq = __this_cpu_read(lock_kicker_irq);
4062306a36Sopenharmony_ci	atomic_t *nest_cnt = this_cpu_ptr(&xen_qlock_wait_nest);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	/* If kicker interrupts not initialized yet, just spin */
4362306a36Sopenharmony_ci	if (irq == -1 || in_nmi())
4462306a36Sopenharmony_ci		return;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	/* Detect reentry. */
4762306a36Sopenharmony_ci	atomic_inc(nest_cnt);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	/* If irq pending already and no nested call clear it. */
5062306a36Sopenharmony_ci	if (atomic_read(nest_cnt) == 1 && xen_test_irq_pending(irq)) {
5162306a36Sopenharmony_ci		xen_clear_irq_pending(irq);
5262306a36Sopenharmony_ci	} else if (READ_ONCE(*byte) == val) {
5362306a36Sopenharmony_ci		/* Block until irq becomes pending (or a spurious wakeup) */
5462306a36Sopenharmony_ci		xen_poll_irq(irq);
5562306a36Sopenharmony_ci	}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	atomic_dec(nest_cnt);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic irqreturn_t dummy_handler(int irq, void *dev_id)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	BUG();
6362306a36Sopenharmony_ci	return IRQ_HANDLED;
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_civoid xen_init_lock_cpu(int cpu)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	int irq;
6962306a36Sopenharmony_ci	char *name;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (!xen_pvspin)
7262306a36Sopenharmony_ci		return;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	WARN(per_cpu(lock_kicker_irq, cpu) >= 0, "spinlock on CPU%d exists on IRQ%d!\n",
7562306a36Sopenharmony_ci	     cpu, per_cpu(lock_kicker_irq, cpu));
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	name = kasprintf(GFP_KERNEL, "spinlock%d", cpu);
7862306a36Sopenharmony_ci	per_cpu(irq_name, cpu) = name;
7962306a36Sopenharmony_ci	irq = bind_ipi_to_irqhandler(XEN_SPIN_UNLOCK_VECTOR,
8062306a36Sopenharmony_ci				     cpu,
8162306a36Sopenharmony_ci				     dummy_handler,
8262306a36Sopenharmony_ci				     IRQF_PERCPU|IRQF_NOBALANCING,
8362306a36Sopenharmony_ci				     name,
8462306a36Sopenharmony_ci				     NULL);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (irq >= 0) {
8762306a36Sopenharmony_ci		disable_irq(irq); /* make sure it's never delivered */
8862306a36Sopenharmony_ci		per_cpu(lock_kicker_irq, cpu) = irq;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	printk("cpu %d spinlock event irq %d\n", cpu, irq);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_civoid xen_uninit_lock_cpu(int cpu)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	int irq;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (!xen_pvspin)
9962306a36Sopenharmony_ci		return;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	kfree(per_cpu(irq_name, cpu));
10262306a36Sopenharmony_ci	per_cpu(irq_name, cpu) = NULL;
10362306a36Sopenharmony_ci	/*
10462306a36Sopenharmony_ci	 * When booting the kernel with 'mitigations=auto,nosmt', the secondary
10562306a36Sopenharmony_ci	 * CPUs are not activated, and lock_kicker_irq is not initialized.
10662306a36Sopenharmony_ci	 */
10762306a36Sopenharmony_ci	irq = per_cpu(lock_kicker_irq, cpu);
10862306a36Sopenharmony_ci	if (irq == -1)
10962306a36Sopenharmony_ci		return;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	unbind_from_irqhandler(irq, NULL);
11262306a36Sopenharmony_ci	per_cpu(lock_kicker_irq, cpu) = -1;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ciPV_CALLEE_SAVE_REGS_THUNK(xen_vcpu_stolen);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci/*
11862306a36Sopenharmony_ci * Our init of PV spinlocks is split in two init functions due to us
11962306a36Sopenharmony_ci * using paravirt patching and jump labels patching and having to do
12062306a36Sopenharmony_ci * all of this before SMP code is invoked.
12162306a36Sopenharmony_ci *
12262306a36Sopenharmony_ci * The paravirt patching needs to be done _before_ the alternative asm code
12362306a36Sopenharmony_ci * is started, otherwise we would not patch the core kernel code.
12462306a36Sopenharmony_ci */
12562306a36Sopenharmony_civoid __init xen_init_spinlocks(void)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	/*  Don't need to use pvqspinlock code if there is only 1 vCPU. */
12862306a36Sopenharmony_ci	if (num_possible_cpus() == 1 || nopvspin)
12962306a36Sopenharmony_ci		xen_pvspin = false;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	if (!xen_pvspin) {
13262306a36Sopenharmony_ci		printk(KERN_DEBUG "xen: PV spinlocks disabled\n");
13362306a36Sopenharmony_ci		static_branch_disable(&virt_spin_lock_key);
13462306a36Sopenharmony_ci		return;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci	printk(KERN_DEBUG "xen: PV spinlocks enabled\n");
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	__pv_init_lock_hash();
13962306a36Sopenharmony_ci	pv_ops.lock.queued_spin_lock_slowpath = __pv_queued_spin_lock_slowpath;
14062306a36Sopenharmony_ci	pv_ops.lock.queued_spin_unlock =
14162306a36Sopenharmony_ci		PV_CALLEE_SAVE(__pv_queued_spin_unlock);
14262306a36Sopenharmony_ci	pv_ops.lock.wait = xen_qlock_wait;
14362306a36Sopenharmony_ci	pv_ops.lock.kick = xen_qlock_kick;
14462306a36Sopenharmony_ci	pv_ops.lock.vcpu_is_preempted = PV_CALLEE_SAVE(xen_vcpu_stolen);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic __init int xen_parse_nopvspin(char *arg)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	pr_notice("\"xen_nopvspin\" is deprecated, please use \"nopvspin\" instead\n");
15062306a36Sopenharmony_ci	xen_pvspin = false;
15162306a36Sopenharmony_ci	return 0;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ciearly_param("xen_nopvspin", xen_parse_nopvspin);
15462306a36Sopenharmony_ci
155