diff options
| author | Gleb Natapov <gleb@redhat.com> | 2013-04-28 12:50:07 +0300 | 
|---|---|---|
| committer | Gleb Natapov <gleb@redhat.com> | 2013-04-28 12:50:07 +0300 | 
| commit | 064d1afaa5a60fc391d0b4b77599fc8f63f99cd3 (patch) | |
| tree | 2e640cdfa50b0048c52e021f07a8b24560251b26 /arch/powerpc/kvm/book3s_hv_rm_xics.c | |
| parent | 730dca42c1d363c939da18c1499c7327c66e2b37 (diff) | |
| parent | 8b78645c93b5d469e8006d68dbc92edc2640c654 (diff) | |
| download | olio-linux-3.10-064d1afaa5a60fc391d0b4b77599fc8f63f99cd3.tar.xz olio-linux-3.10-064d1afaa5a60fc391d0b4b77599fc8f63f99cd3.zip  | |
Merge git://github.com/agraf/linux-2.6.git kvm-ppc-next into queue
Diffstat (limited to 'arch/powerpc/kvm/book3s_hv_rm_xics.c')
| -rw-r--r-- | arch/powerpc/kvm/book3s_hv_rm_xics.c | 406 | 
1 files changed, 406 insertions, 0 deletions
diff --git a/arch/powerpc/kvm/book3s_hv_rm_xics.c b/arch/powerpc/kvm/book3s_hv_rm_xics.c new file mode 100644 index 00000000000..b4b0082f761 --- /dev/null +++ b/arch/powerpc/kvm/book3s_hv_rm_xics.c @@ -0,0 +1,406 @@ +/* + * Copyright 2012 Michael Ellerman, IBM Corporation. + * Copyright 2012 Benjamin Herrenschmidt, IBM Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/kvm_host.h> +#include <linux/err.h> + +#include <asm/kvm_book3s.h> +#include <asm/kvm_ppc.h> +#include <asm/hvcall.h> +#include <asm/xics.h> +#include <asm/debug.h> +#include <asm/synch.h> +#include <asm/ppc-opcode.h> + +#include "book3s_xics.h" + +#define DEBUG_PASSUP + +static inline void rm_writeb(unsigned long paddr, u8 val) +{ +	__asm__ __volatile__("sync; stbcix %0,0,%1" +		: : "r" (val), "r" (paddr) : "memory"); +} + +static void icp_rm_set_vcpu_irq(struct kvm_vcpu *vcpu, +				struct kvm_vcpu *this_vcpu) +{ +	struct kvmppc_icp *this_icp = this_vcpu->arch.icp; +	unsigned long xics_phys; +	int cpu; + +	/* Mark the target VCPU as having an interrupt pending */ +	vcpu->stat.queue_intr++; +	set_bit(BOOK3S_IRQPRIO_EXTERNAL_LEVEL, &vcpu->arch.pending_exceptions); + +	/* Kick self ? Just set MER and return */ +	if (vcpu == this_vcpu) { +		mtspr(SPRN_LPCR, mfspr(SPRN_LPCR) | LPCR_MER); +		return; +	} + +	/* Check if the core is loaded, if not, too hard */ +	cpu = vcpu->cpu; +	if (cpu < 0 || cpu >= nr_cpu_ids) { +		this_icp->rm_action |= XICS_RM_KICK_VCPU; +		this_icp->rm_kick_target = vcpu; +		return; +	} +	/* In SMT cpu will always point to thread 0, we adjust it */ +	cpu += vcpu->arch.ptid; + +	/* Not too hard, then poke the target */ +	xics_phys = paca[cpu].kvm_hstate.xics_phys; +	rm_writeb(xics_phys + XICS_MFRR, IPI_PRIORITY); +} + +static void icp_rm_clr_vcpu_irq(struct kvm_vcpu *vcpu) +{ +	/* Note: Only called on self ! */ +	clear_bit(BOOK3S_IRQPRIO_EXTERNAL_LEVEL, +		  &vcpu->arch.pending_exceptions); +	mtspr(SPRN_LPCR, mfspr(SPRN_LPCR) & ~LPCR_MER); +} + +static inline bool icp_rm_try_update(struct kvmppc_icp *icp, +				     union kvmppc_icp_state old, +				     union kvmppc_icp_state new) +{ +	struct kvm_vcpu *this_vcpu = local_paca->kvm_hstate.kvm_vcpu; +	bool success; + +	/* Calculate new output value */ +	new.out_ee = (new.xisr && (new.pending_pri < new.cppr)); + +	/* Attempt atomic update */ +	success = cmpxchg64(&icp->state.raw, old.raw, new.raw) == old.raw; +	if (!success) +		goto bail; + +	/* +	 * Check for output state update +	 * +	 * Note that this is racy since another processor could be updating +	 * the state already. This is why we never clear the interrupt output +	 * here, we only ever set it. The clear only happens prior to doing +	 * an update and only by the processor itself. Currently we do it +	 * in Accept (H_XIRR) and Up_Cppr (H_XPPR). +	 * +	 * We also do not try to figure out whether the EE state has changed, +	 * we unconditionally set it if the new state calls for it. The reason +	 * for that is that we opportunistically remove the pending interrupt +	 * flag when raising CPPR, so we need to set it back here if an +	 * interrupt is still pending. +	 */ +	if (new.out_ee) +		icp_rm_set_vcpu_irq(icp->vcpu, this_vcpu); + +	/* Expose the state change for debug purposes */ +	this_vcpu->arch.icp->rm_dbgstate = new; +	this_vcpu->arch.icp->rm_dbgtgt = icp->vcpu; + + bail: +	return success; +} + +static inline int check_too_hard(struct kvmppc_xics *xics, +				 struct kvmppc_icp *icp) +{ +	return (xics->real_mode_dbg || icp->rm_action) ? H_TOO_HARD : H_SUCCESS; +} + +static void icp_rm_down_cppr(struct kvmppc_xics *xics, struct kvmppc_icp *icp, +			     u8 new_cppr) +{ +	union kvmppc_icp_state old_state, new_state; +	bool resend; + +	/* +	 * This handles several related states in one operation: +	 * +	 * ICP State: Down_CPPR +	 * +	 * Load CPPR with new value and if the XISR is 0 +	 * then check for resends: +	 * +	 * ICP State: Resend +	 * +	 * If MFRR is more favored than CPPR, check for IPIs +	 * and notify ICS of a potential resend. This is done +	 * asynchronously (when used in real mode, we will have +	 * to exit here). +	 * +	 * We do not handle the complete Check_IPI as documented +	 * here. In the PAPR, this state will be used for both +	 * Set_MFRR and Down_CPPR. However, we know that we aren't +	 * changing the MFRR state here so we don't need to handle +	 * the case of an MFRR causing a reject of a pending irq, +	 * this will have been handled when the MFRR was set in the +	 * first place. +	 * +	 * Thus we don't have to handle rejects, only resends. +	 * +	 * When implementing real mode for HV KVM, resend will lead to +	 * a H_TOO_HARD return and the whole transaction will be handled +	 * in virtual mode. +	 */ +	do { +		old_state = new_state = ACCESS_ONCE(icp->state); + +		/* Down_CPPR */ +		new_state.cppr = new_cppr; + +		/* +		 * Cut down Resend / Check_IPI / IPI +		 * +		 * The logic is that we cannot have a pending interrupt +		 * trumped by an IPI at this point (see above), so we +		 * know that either the pending interrupt is already an +		 * IPI (in which case we don't care to override it) or +		 * it's either more favored than us or non existent +		 */ +		if (new_state.mfrr < new_cppr && +		    new_state.mfrr <= new_state.pending_pri) { +			new_state.pending_pri = new_state.mfrr; +			new_state.xisr = XICS_IPI; +		} + +		/* Latch/clear resend bit */ +		resend = new_state.need_resend; +		new_state.need_resend = 0; + +	} while (!icp_rm_try_update(icp, old_state, new_state)); + +	/* +	 * Now handle resend checks. Those are asynchronous to the ICP +	 * state update in HW (ie bus transactions) so we can handle them +	 * separately here as well. +	 */ +	if (resend) +		icp->rm_action |= XICS_RM_CHECK_RESEND; +} + + +unsigned long kvmppc_rm_h_xirr(struct kvm_vcpu *vcpu) +{ +	union kvmppc_icp_state old_state, new_state; +	struct kvmppc_xics *xics = vcpu->kvm->arch.xics; +	struct kvmppc_icp *icp = vcpu->arch.icp; +	u32 xirr; + +	if (!xics || !xics->real_mode) +		return H_TOO_HARD; + +	/* First clear the interrupt */ +	icp_rm_clr_vcpu_irq(icp->vcpu); + +	/* +	 * ICP State: Accept_Interrupt +	 * +	 * Return the pending interrupt (if any) along with the +	 * current CPPR, then clear the XISR & set CPPR to the +	 * pending priority +	 */ +	do { +		old_state = new_state = ACCESS_ONCE(icp->state); + +		xirr = old_state.xisr | (((u32)old_state.cppr) << 24); +		if (!old_state.xisr) +			break; +		new_state.cppr = new_state.pending_pri; +		new_state.pending_pri = 0xff; +		new_state.xisr = 0; + +	} while (!icp_rm_try_update(icp, old_state, new_state)); + +	/* Return the result in GPR4 */ +	vcpu->arch.gpr[4] = xirr; + +	return check_too_hard(xics, icp); +} + +int kvmppc_rm_h_ipi(struct kvm_vcpu *vcpu, unsigned long server, +		    unsigned long mfrr) +{ +	union kvmppc_icp_state old_state, new_state; +	struct kvmppc_xics *xics = vcpu->kvm->arch.xics; +	struct kvmppc_icp *icp, *this_icp = vcpu->arch.icp; +	u32 reject; +	bool resend; +	bool local; + +	if (!xics || !xics->real_mode) +		return H_TOO_HARD; + +	local = this_icp->server_num == server; +	if (local) +		icp = this_icp; +	else +		icp = kvmppc_xics_find_server(vcpu->kvm, server); +	if (!icp) +		return H_PARAMETER; + +	/* +	 * ICP state: Set_MFRR +	 * +	 * If the CPPR is more favored than the new MFRR, then +	 * nothing needs to be done as there can be no XISR to +	 * reject. +	 * +	 * If the CPPR is less favored, then we might be replacing +	 * an interrupt, and thus need to possibly reject it as in +	 * +	 * ICP state: Check_IPI +	 */ +	do { +		old_state = new_state = ACCESS_ONCE(icp->state); + +		/* Set_MFRR */ +		new_state.mfrr = mfrr; + +		/* Check_IPI */ +		reject = 0; +		resend = false; +		if (mfrr < new_state.cppr) { +			/* Reject a pending interrupt if not an IPI */ +			if (mfrr <= new_state.pending_pri) +				reject = new_state.xisr; +			new_state.pending_pri = mfrr; +			new_state.xisr = XICS_IPI; +		} + +		if (mfrr > old_state.mfrr && mfrr > new_state.cppr) { +			resend = new_state.need_resend; +			new_state.need_resend = 0; +		} +	} while (!icp_rm_try_update(icp, old_state, new_state)); + +	/* Pass rejects to virtual mode */ +	if (reject && reject != XICS_IPI) { +		this_icp->rm_action |= XICS_RM_REJECT; +		this_icp->rm_reject = reject; +	} + +	/* Pass resends to virtual mode */ +	if (resend) +		this_icp->rm_action |= XICS_RM_CHECK_RESEND; + +	return check_too_hard(xics, this_icp); +} + +int kvmppc_rm_h_cppr(struct kvm_vcpu *vcpu, unsigned long cppr) +{ +	union kvmppc_icp_state old_state, new_state; +	struct kvmppc_xics *xics = vcpu->kvm->arch.xics; +	struct kvmppc_icp *icp = vcpu->arch.icp; +	u32 reject; + +	if (!xics || !xics->real_mode) +		return H_TOO_HARD; + +	/* +	 * ICP State: Set_CPPR +	 * +	 * We can safely compare the new value with the current +	 * value outside of the transaction as the CPPR is only +	 * ever changed by the processor on itself +	 */ +	if (cppr > icp->state.cppr) { +		icp_rm_down_cppr(xics, icp, cppr); +		goto bail; +	} else if (cppr == icp->state.cppr) +		return H_SUCCESS; + +	/* +	 * ICP State: Up_CPPR +	 * +	 * The processor is raising its priority, this can result +	 * in a rejection of a pending interrupt: +	 * +	 * ICP State: Reject_Current +	 * +	 * We can remove EE from the current processor, the update +	 * transaction will set it again if needed +	 */ +	icp_rm_clr_vcpu_irq(icp->vcpu); + +	do { +		old_state = new_state = ACCESS_ONCE(icp->state); + +		reject = 0; +		new_state.cppr = cppr; + +		if (cppr <= new_state.pending_pri) { +			reject = new_state.xisr; +			new_state.xisr = 0; +			new_state.pending_pri = 0xff; +		} + +	} while (!icp_rm_try_update(icp, old_state, new_state)); + +	/* Pass rejects to virtual mode */ +	if (reject && reject != XICS_IPI) { +		icp->rm_action |= XICS_RM_REJECT; +		icp->rm_reject = reject; +	} + bail: +	return check_too_hard(xics, icp); +} + +int kvmppc_rm_h_eoi(struct kvm_vcpu *vcpu, unsigned long xirr) +{ +	struct kvmppc_xics *xics = vcpu->kvm->arch.xics; +	struct kvmppc_icp *icp = vcpu->arch.icp; +	struct kvmppc_ics *ics; +	struct ics_irq_state *state; +	u32 irq = xirr & 0x00ffffff; +	u16 src; + +	if (!xics || !xics->real_mode) +		return H_TOO_HARD; + +	/* +	 * ICP State: EOI +	 * +	 * Note: If EOI is incorrectly used by SW to lower the CPPR +	 * value (ie more favored), we do not check for rejection of +	 * a pending interrupt, this is a SW error and PAPR sepcifies +	 * that we don't have to deal with it. +	 * +	 * The sending of an EOI to the ICS is handled after the +	 * CPPR update +	 * +	 * ICP State: Down_CPPR which we handle +	 * in a separate function as it's shared with H_CPPR. +	 */ +	icp_rm_down_cppr(xics, icp, xirr >> 24); + +	/* IPIs have no EOI */ +	if (irq == XICS_IPI) +		goto bail; +	/* +	 * EOI handling: If the interrupt is still asserted, we need to +	 * resend it. We can take a lockless "peek" at the ICS state here. +	 * +	 * "Message" interrupts will never have "asserted" set +	 */ +	ics = kvmppc_xics_find_ics(xics, irq, &src); +	if (!ics) +		goto bail; +	state = &ics->irq_state[src]; + +	/* Still asserted, resend it, we make it look like a reject */ +	if (state->asserted) { +		icp->rm_action |= XICS_RM_REJECT; +		icp->rm_reject = irq; +	} + bail: +	return check_too_hard(xics, icp); +}  |