diff options
Diffstat (limited to 'virt/kvm/ioapic.c')
| -rw-r--r-- | virt/kvm/ioapic.c | 329 | 
1 files changed, 267 insertions, 62 deletions
diff --git a/virt/kvm/ioapic.c b/virt/kvm/ioapic.c index 0b9df8303dc..2458a1dc2ba 100644 --- a/virt/kvm/ioapic.c +++ b/virt/kvm/ioapic.c @@ -35,6 +35,7 @@  #include <linux/hrtimer.h>  #include <linux/io.h>  #include <linux/slab.h> +#include <linux/export.h>  #include <asm/processor.h>  #include <asm/page.h>  #include <asm/current.h> @@ -49,7 +50,8 @@  #else  #define ioapic_debug(fmt, arg...)  #endif -static int ioapic_deliver(struct kvm_ioapic *vioapic, int irq); +static int ioapic_service(struct kvm_ioapic *vioapic, int irq, +		bool line_status);  static unsigned long ioapic_read_indirect(struct kvm_ioapic *ioapic,  					  unsigned long addr, @@ -73,9 +75,12 @@ static unsigned long ioapic_read_indirect(struct kvm_ioapic *ioapic,  			u32 redir_index = (ioapic->ioregsel - 0x10) >> 1;  			u64 redir_content; -			ASSERT(redir_index < IOAPIC_NUM_PINS); +			if (redir_index < IOAPIC_NUM_PINS) +				redir_content = +					ioapic->redirtbl[redir_index].bits; +			else +				redir_content = ~0ULL; -			redir_content = ioapic->redirtbl[redir_index].bits;  			result = (ioapic->ioregsel & 0x1) ?  			    (redir_content >> 32) & 0xffffffff :  			    redir_content & 0xffffffff; @@ -86,22 +91,146 @@ static unsigned long ioapic_read_indirect(struct kvm_ioapic *ioapic,  	return result;  } -static int ioapic_service(struct kvm_ioapic *ioapic, unsigned int idx) +static void rtc_irq_eoi_tracking_reset(struct kvm_ioapic *ioapic) +{ +	ioapic->rtc_status.pending_eoi = 0; +	bitmap_zero(ioapic->rtc_status.dest_map, KVM_MAX_VCPUS); +} + +static void kvm_rtc_eoi_tracking_restore_all(struct kvm_ioapic *ioapic); + +static void rtc_status_pending_eoi_check_valid(struct kvm_ioapic *ioapic) +{ +	if (WARN_ON(ioapic->rtc_status.pending_eoi < 0)) +		kvm_rtc_eoi_tracking_restore_all(ioapic); +} + +static void __rtc_irq_eoi_tracking_restore_one(struct kvm_vcpu *vcpu)  { -	union kvm_ioapic_redirect_entry *pent; -	int injected = -1; +	bool new_val, old_val; +	struct kvm_ioapic *ioapic = vcpu->kvm->arch.vioapic; +	union kvm_ioapic_redirect_entry *e; + +	e = &ioapic->redirtbl[RTC_GSI]; +	if (!kvm_apic_match_dest(vcpu, NULL, 0,	e->fields.dest_id, +				e->fields.dest_mode)) +		return; + +	new_val = kvm_apic_pending_eoi(vcpu, e->fields.vector); +	old_val = test_bit(vcpu->vcpu_id, ioapic->rtc_status.dest_map); -	pent = &ioapic->redirtbl[idx]; +	if (new_val == old_val) +		return; -	if (!pent->fields.mask) { -		injected = ioapic_deliver(ioapic, idx); -		if (injected && pent->fields.trig_mode == IOAPIC_LEVEL_TRIG) -			pent->fields.remote_irr = 1; +	if (new_val) { +		__set_bit(vcpu->vcpu_id, ioapic->rtc_status.dest_map); +		ioapic->rtc_status.pending_eoi++; +	} else { +		__clear_bit(vcpu->vcpu_id, ioapic->rtc_status.dest_map); +		ioapic->rtc_status.pending_eoi--; +		rtc_status_pending_eoi_check_valid(ioapic);  	} +} + +void kvm_rtc_eoi_tracking_restore_one(struct kvm_vcpu *vcpu) +{ +	struct kvm_ioapic *ioapic = vcpu->kvm->arch.vioapic; -	return injected; +	spin_lock(&ioapic->lock); +	__rtc_irq_eoi_tracking_restore_one(vcpu); +	spin_unlock(&ioapic->lock);  } +static void kvm_rtc_eoi_tracking_restore_all(struct kvm_ioapic *ioapic) +{ +	struct kvm_vcpu *vcpu; +	int i; + +	if (RTC_GSI >= IOAPIC_NUM_PINS) +		return; + +	rtc_irq_eoi_tracking_reset(ioapic); +	kvm_for_each_vcpu(i, vcpu, ioapic->kvm) +	    __rtc_irq_eoi_tracking_restore_one(vcpu); +} + +static void rtc_irq_eoi(struct kvm_ioapic *ioapic, struct kvm_vcpu *vcpu) +{ +	if (test_and_clear_bit(vcpu->vcpu_id, ioapic->rtc_status.dest_map)) { +		--ioapic->rtc_status.pending_eoi; +		rtc_status_pending_eoi_check_valid(ioapic); +	} +} + +static bool rtc_irq_check_coalesced(struct kvm_ioapic *ioapic) +{ +	if (ioapic->rtc_status.pending_eoi > 0) +		return true; /* coalesced */ + +	return false; +} + +static int ioapic_set_irq(struct kvm_ioapic *ioapic, unsigned int irq, +		int irq_level, bool line_status) +{ +	union kvm_ioapic_redirect_entry entry; +	u32 mask = 1 << irq; +	u32 old_irr; +	int edge, ret; + +	entry = ioapic->redirtbl[irq]; +	edge = (entry.fields.trig_mode == IOAPIC_EDGE_TRIG); + +	if (!irq_level) { +		ioapic->irr &= ~mask; +		ret = 1; +		goto out; +	} + +	/* +	 * Return 0 for coalesced interrupts; for edge-triggered interrupts, +	 * this only happens if a previous edge has not been delivered due +	 * do masking.  For level interrupts, the remote_irr field tells +	 * us if the interrupt is waiting for an EOI. +	 * +	 * RTC is special: it is edge-triggered, but userspace likes to know +	 * if it has been already ack-ed via EOI because coalesced RTC +	 * interrupts lead to time drift in Windows guests.  So we track +	 * EOI manually for the RTC interrupt. +	 */ +	if (irq == RTC_GSI && line_status && +		rtc_irq_check_coalesced(ioapic)) { +		ret = 0; +		goto out; +	} + +	old_irr = ioapic->irr; +	ioapic->irr |= mask; +	if ((edge && old_irr == ioapic->irr) || +	    (!edge && entry.fields.remote_irr)) { +		ret = 0; +		goto out; +	} + +	ret = ioapic_service(ioapic, irq, line_status); + +out: +	trace_kvm_ioapic_set_irq(entry.bits, irq, ret == 0); +	return ret; +} + +static void kvm_ioapic_inject_all(struct kvm_ioapic *ioapic, unsigned long irr) +{ +	u32 idx; + +	rtc_irq_eoi_tracking_reset(ioapic); +	for_each_set_bit(idx, &irr, IOAPIC_NUM_PINS) +		ioapic_set_irq(ioapic, idx, 1, true); + +	kvm_rtc_eoi_tracking_restore_all(ioapic); +} + +  static void update_handled_vectors(struct kvm_ioapic *ioapic)  {  	DECLARE_BITMAP(handled_vectors, 256); @@ -115,6 +244,49 @@ static void update_handled_vectors(struct kvm_ioapic *ioapic)  	smp_wmb();  } +void kvm_ioapic_scan_entry(struct kvm_vcpu *vcpu, u64 *eoi_exit_bitmap, +			u32 *tmr) +{ +	struct kvm_ioapic *ioapic = vcpu->kvm->arch.vioapic; +	union kvm_ioapic_redirect_entry *e; +	int index; + +	spin_lock(&ioapic->lock); +	for (index = 0; index < IOAPIC_NUM_PINS; index++) { +		e = &ioapic->redirtbl[index]; +		if (!e->fields.mask && +			(e->fields.trig_mode == IOAPIC_LEVEL_TRIG || +			 kvm_irq_has_notifier(ioapic->kvm, KVM_IRQCHIP_IOAPIC, +				 index) || index == RTC_GSI)) { +			if (kvm_apic_match_dest(vcpu, NULL, 0, +				e->fields.dest_id, e->fields.dest_mode)) { +				__set_bit(e->fields.vector, +					(unsigned long *)eoi_exit_bitmap); +				if (e->fields.trig_mode == IOAPIC_LEVEL_TRIG) +					__set_bit(e->fields.vector, +						(unsigned long *)tmr); +			} +		} +	} +	spin_unlock(&ioapic->lock); +} + +#ifdef CONFIG_X86 +void kvm_vcpu_request_scan_ioapic(struct kvm *kvm) +{ +	struct kvm_ioapic *ioapic = kvm->arch.vioapic; + +	if (!ioapic) +		return; +	kvm_make_scan_ioapic_request(kvm); +} +#else +void kvm_vcpu_request_scan_ioapic(struct kvm *kvm) +{ +	return; +} +#endif +  static void ioapic_write_indirect(struct kvm_ioapic *ioapic, u32 val)  {  	unsigned index; @@ -155,19 +327,24 @@ static void ioapic_write_indirect(struct kvm_ioapic *ioapic, u32 val)  			kvm_fire_mask_notifiers(ioapic->kvm, KVM_IRQCHIP_IOAPIC, index, mask_after);  		if (e->fields.trig_mode == IOAPIC_LEVEL_TRIG  		    && ioapic->irr & (1 << index)) -			ioapic_service(ioapic, index); +			ioapic_service(ioapic, index, false); +		kvm_vcpu_request_scan_ioapic(ioapic->kvm);  		break;  	}  } -static int ioapic_deliver(struct kvm_ioapic *ioapic, int irq) +static int ioapic_service(struct kvm_ioapic *ioapic, int irq, bool line_status)  {  	union kvm_ioapic_redirect_entry *entry = &ioapic->redirtbl[irq];  	struct kvm_lapic_irq irqe; +	int ret; + +	if (entry->fields.mask) +		return -1;  	ioapic_debug("dest=%x dest_mode=%x delivery_mode=%x "  		     "vector=%x trig_mode=%x\n", -		     entry->fields.dest, entry->fields.dest_mode, +		     entry->fields.dest_id, entry->fields.dest_mode,  		     entry->fields.delivery_mode, entry->fields.vector,  		     entry->fields.trig_mode); @@ -179,50 +356,58 @@ static int ioapic_deliver(struct kvm_ioapic *ioapic, int irq)  	irqe.level = 1;  	irqe.shorthand = 0; -#ifdef CONFIG_X86 -	/* Always delivery PIT interrupt to vcpu 0 */ -	if (irq == 0) { -		irqe.dest_mode = 0; /* Physical mode. */ -		/* need to read apic_id from apic regiest since -		 * it can be rewritten */ -		irqe.dest_id = ioapic->kvm->bsp_vcpu->vcpu_id; -	} -#endif -	return kvm_irq_delivery_to_apic(ioapic->kvm, NULL, &irqe); +	if (irqe.trig_mode == IOAPIC_EDGE_TRIG) +		ioapic->irr &= ~(1 << irq); + +	if (irq == RTC_GSI && line_status) { +		/* +		 * pending_eoi cannot ever become negative (see +		 * rtc_status_pending_eoi_check_valid) and the caller +		 * ensures that it is only called if it is >= zero, namely +		 * if rtc_irq_check_coalesced returns false). +		 */ +		BUG_ON(ioapic->rtc_status.pending_eoi != 0); +		ret = kvm_irq_delivery_to_apic(ioapic->kvm, NULL, &irqe, +				ioapic->rtc_status.dest_map); +		ioapic->rtc_status.pending_eoi = (ret < 0 ? 0 : ret); +	} else +		ret = kvm_irq_delivery_to_apic(ioapic->kvm, NULL, &irqe, NULL); + +	if (ret && irqe.trig_mode == IOAPIC_LEVEL_TRIG) +		entry->fields.remote_irr = 1; + +	return ret;  } -int kvm_ioapic_set_irq(struct kvm_ioapic *ioapic, int irq, int level) +int kvm_ioapic_set_irq(struct kvm_ioapic *ioapic, int irq, int irq_source_id, +		       int level, bool line_status)  { -	u32 old_irr; -	u32 mask = 1 << irq; -	union kvm_ioapic_redirect_entry entry; -	int ret = 1; +	int ret, irq_level; + +	BUG_ON(irq < 0 || irq >= IOAPIC_NUM_PINS);  	spin_lock(&ioapic->lock); -	old_irr = ioapic->irr; -	if (irq >= 0 && irq < IOAPIC_NUM_PINS) { -		entry = ioapic->redirtbl[irq]; -		level ^= entry.fields.polarity; -		if (!level) -			ioapic->irr &= ~mask; -		else { -			int edge = (entry.fields.trig_mode == IOAPIC_EDGE_TRIG); -			ioapic->irr |= mask; -			if ((edge && old_irr != ioapic->irr) || -			    (!edge && !entry.fields.remote_irr)) -				ret = ioapic_service(ioapic, irq); -			else -				ret = 0; /* report coalesced interrupt */ -		} -		trace_kvm_ioapic_set_irq(entry.bits, irq, ret == 0); -	} +	irq_level = __kvm_irq_line_state(&ioapic->irq_states[irq], +					 irq_source_id, level); +	ret = ioapic_set_irq(ioapic, irq, irq_level, line_status); +  	spin_unlock(&ioapic->lock);  	return ret;  } -static void __kvm_ioapic_update_eoi(struct kvm_ioapic *ioapic, int vector, -				     int trigger_mode) +void kvm_ioapic_clear_all(struct kvm_ioapic *ioapic, int irq_source_id) +{ +	int i; + +	spin_lock(&ioapic->lock); +	for (i = 0; i < KVM_IOAPIC_NUM_PINS; i++) +		__clear_bit(irq_source_id, &ioapic->irq_states[i]); +	spin_unlock(&ioapic->lock); +} + +static void __kvm_ioapic_update_eoi(struct kvm_vcpu *vcpu, +			struct kvm_ioapic *ioapic, int vector, int trigger_mode)  {  	int i; @@ -232,6 +417,8 @@ static void __kvm_ioapic_update_eoi(struct kvm_ioapic *ioapic, int vector,  		if (ent->fields.vector != vector)  			continue; +		if (i == RTC_GSI) +			rtc_irq_eoi(ioapic, vcpu);  		/*  		 * We are dropping lock while calling ack notifiers because ack  		 * notifier callbacks for assigned devices call into IOAPIC @@ -249,20 +436,24 @@ static void __kvm_ioapic_update_eoi(struct kvm_ioapic *ioapic, int vector,  		ASSERT(ent->fields.trig_mode == IOAPIC_LEVEL_TRIG);  		ent->fields.remote_irr = 0; -		if (!ent->fields.mask && (ioapic->irr & (1 << i))) -			ioapic_service(ioapic, i); +		if (ioapic->irr & (1 << i)) +			ioapic_service(ioapic, i, false);  	}  } -void kvm_ioapic_update_eoi(struct kvm *kvm, int vector, int trigger_mode) +bool kvm_ioapic_handles_vector(struct kvm *kvm, int vector)  {  	struct kvm_ioapic *ioapic = kvm->arch.vioapic; -  	smp_rmb(); -	if (!test_bit(vector, ioapic->handled_vectors)) -		return; +	return test_bit(vector, ioapic->handled_vectors); +} + +void kvm_ioapic_update_eoi(struct kvm_vcpu *vcpu, int vector, int trigger_mode) +{ +	struct kvm_ioapic *ioapic = vcpu->kvm->arch.vioapic; +  	spin_lock(&ioapic->lock); -	__kvm_ioapic_update_eoi(ioapic, vector, trigger_mode); +	__kvm_ioapic_update_eoi(vcpu, ioapic, vector, trigger_mode);  	spin_unlock(&ioapic->lock);  } @@ -332,9 +523,18 @@ static int ioapic_mmio_write(struct kvm_io_device *this, gpa_t addr, int len,  		     (void*)addr, len, val);  	ASSERT(!(addr & 0xf));	/* check alignment */ -	if (len == 4 || len == 8) +	switch (len) { +	case 8: +	case 4:  		data = *(u32 *) val; -	else { +		break; +	case 2: +		data = *(u16 *) val; +		break; +	case 1: +		data = *(u8  *) val; +		break; +	default:  		printk(KERN_WARNING "ioapic: Unsupported size %d\n", len);  		return 0;  	} @@ -343,7 +543,7 @@ static int ioapic_mmio_write(struct kvm_io_device *this, gpa_t addr, int len,  	spin_lock(&ioapic->lock);  	switch (addr) {  	case IOAPIC_REG_SELECT: -		ioapic->ioregsel = data; +		ioapic->ioregsel = data & 0xFF; /* 8-bit register */  		break;  	case IOAPIC_REG_WINDOW: @@ -351,7 +551,7 @@ static int ioapic_mmio_write(struct kvm_io_device *this, gpa_t addr, int len,  		break;  #ifdef	CONFIG_IA64  	case IOAPIC_REG_EOI: -		__kvm_ioapic_update_eoi(ioapic, data, IOAPIC_LEVEL_TRIG); +		__kvm_ioapic_update_eoi(NULL, ioapic, data, IOAPIC_LEVEL_TRIG);  		break;  #endif @@ -362,7 +562,7 @@ static int ioapic_mmio_write(struct kvm_io_device *this, gpa_t addr, int len,  	return 0;  } -void kvm_ioapic_reset(struct kvm_ioapic *ioapic) +static void kvm_ioapic_reset(struct kvm_ioapic *ioapic)  {  	int i; @@ -372,6 +572,7 @@ void kvm_ioapic_reset(struct kvm_ioapic *ioapic)  	ioapic->ioregsel = 0;  	ioapic->irr = 0;  	ioapic->id = 0; +	rtc_irq_eoi_tracking_reset(ioapic);  	update_handled_vectors(ioapic);  } @@ -394,7 +595,8 @@ int kvm_ioapic_init(struct kvm *kvm)  	kvm_iodevice_init(&ioapic->dev, &ioapic_mmio_ops);  	ioapic->kvm = kvm;  	mutex_lock(&kvm->slots_lock); -	ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, &ioapic->dev); +	ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, ioapic->base_address, +				      IOAPIC_MEM_LENGTH, &ioapic->dev);  	mutex_unlock(&kvm->slots_lock);  	if (ret < 0) {  		kvm->arch.vioapic = NULL; @@ -435,7 +637,10 @@ int kvm_set_ioapic(struct kvm *kvm, struct kvm_ioapic_state *state)  	spin_lock(&ioapic->lock);  	memcpy(ioapic, state, sizeof(struct kvm_ioapic_state)); +	ioapic->irr = 0;  	update_handled_vectors(ioapic); +	kvm_vcpu_request_scan_ioapic(kvm); +	kvm_ioapic_inject_all(ioapic, state->irr);  	spin_unlock(&ioapic->lock);  	return 0;  }  | 
