[BACK]Return to evtchn.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / sys / arch / xen / xen

File: [cvs.NetBSD.org] / src / sys / arch / xen / xen / evtchn.c (download)

Revision 1.4, Thu Mar 17 15:32:38 2005 UTC (19 years, 1 month ago) by bouyer
Branch: MAIN
CVS Tags: yamt-km-base4, yamt-km-base3
Branch point for: ktrace-lwp
Changes since 1.3: +24 -6 lines

Protect various IRQ and event allocation/deallocations with splhigh().
Print the IRQ used for debug and misdirect events.

/*	$NetBSD: evtchn.c,v 1.4 2005/03/17 15:32:38 bouyer Exp $	*/

/*
 *
 * Copyright (c) 2004 Christian Limpach.
 * Copyright (c) 2004, K A Fraser.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Christian Limpach.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: evtchn.c,v 1.4 2005/03/17 15:32:38 bouyer Exp $");

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/malloc.h>
#include <sys/reboot.h>

#include <uvm/uvm.h>

#include <machine/intrdefs.h>

#include <machine/xen.h>
#include <machine/hypervisor.h>
#include <machine/evtchn.h>
#include <machine/ctrl_if.h>
#include <machine/xenfunc.h>

#include "opt_xen.h"

struct pic xenev_pic = {
	.pic_dev = {
		.dv_xname = "xen_fakepic",
	},
	.pic_type = PIC_XEN,
	.pic_lock = __SIMPLELOCK_UNLOCKED,
};

/*
 * This lock protects updates to the following mapping and reference-count
 * arrays. The lock does not need to be acquired to read the mapping tables.
 */
static struct simplelock irq_mapping_update_lock = SIMPLELOCK_INITIALIZER;

/* IRQ <-> event-channel mappings. */
int evtchn_to_irq[NR_EVENT_CHANNELS];
int irq_to_evtchn[NR_IRQS];

/* IRQ <-> VIRQ mapping. */
static int virq_to_irq[NR_VIRQS];


#ifdef DOM0OPS
/* IRQ <-> PIRQ mapping */
static int pirq_to_irq[NR_PIRQS];
/* PIRQ needing notify */
static u_int32_t irq_needs_unmask_notify[NR_IRQS / 32];
int pirq_interrupt(void *);
void pirq_notify(int);
physdev_op_t physdev_op_notify = {
	.cmd = PHYSDEVOP_IRQ_UNMASK_NOTIFY,
};
#endif

/* Reference counts for bindings to IRQs. */
static int irq_bindcount[NR_IRQS];

#if 0
static int xen_die_handler(void *);
#endif
static int xen_debug_handler(void *);
static int xen_misdirect_handler(void *);

/* #define IRQ_DEBUG -1 */

void
events_default_setup()
{
	int i;

	/* No VIRQ -> IRQ mappings. */
	for (i = 0; i < NR_VIRQS; i++)
		virq_to_irq[i] = -1;

#ifdef DOM0OPS
	/* No PIRQ -> IRQ mappings. */
	for (i = 0; i < NR_PIRQS; i++)
		pirq_to_irq[i] = -1;
	for (i = 0; i < NR_IRQS / 32; i++)
		irq_needs_unmask_notify[i] = 0;
#endif

	/* No event-channel -> IRQ mappings. */
	for (i = 0; i < NR_EVENT_CHANNELS; i++) {
		evtchn_to_irq[i] = -1;
		hypervisor_mask_event(i); /* No event channels are 'live' right now. */
	}

	/* No IRQ -> event-channel mappings. */
	for (i = 0; i < NR_IRQS; i++)
		irq_to_evtchn[i] = -1;
}

void
init_events()
{
	int irq;

	irq = bind_virq_to_irq(VIRQ_DEBUG);
	aprint_verbose("debug events interrupting at irq %d\n", irq);
	event_set_handler(irq, &xen_debug_handler, NULL, IPL_DEBUG);
	hypervisor_enable_irq(irq);

	irq = bind_virq_to_irq(VIRQ_MISDIRECT);
	event_set_handler(irq, &xen_misdirect_handler, NULL, IPL_DIE);
	aprint_verbose("misdirect events interrupting at irq %d\n", irq);
	hypervisor_enable_irq(irq);

	/* This needs to be done early, but after the IRQ subsystem is
	 * alive. */
	ctrl_if_init();

	enable_intr();		/* at long last... */
}

unsigned int
do_event(int irq, struct intrframe *regs)
{
	struct cpu_info *ci;
	int ilevel;
	struct intrhand *ih;
	int	(*ih_fun)(void *, void *);
	extern struct uvmexp uvmexp;

	if (irq >= NR_IRQS) {
#ifdef DIAGNOSTIC
		printf("event irq number %d > NR_IRQS\n", irq);
#endif
		return ENOENT;
	}

#ifdef IRQ_DEBUG
	if (irq == IRQ_DEBUG)
		printf("do_event: irq %d\n", irq);
#endif
#if 0 /* DDD */
	if (irq >= 3 && irq != 7) {
		ci = &cpu_info_primary;
		printf("do_event %d/%d called, ilevel %d\n", irq,
		       irq_to_evtchn[irq], ci->ci_ilevel);
	}
#endif

	ci = &cpu_info_primary;

	hypervisor_acknowledge_irq(irq);
	if (ci->ci_isources[irq] == NULL) {
		printf("ci_isources[%d] is NULL\n", irq);
		hypervisor_enable_irq(irq);
		return 0;
	}
	uvmexp.intrs++;
	ci->ci_isources[irq]->is_evcnt.ev_count++;
	ilevel = ci->ci_ilevel;
	if (ci->ci_isources[irq]->is_maxlevel <= ilevel) {
#ifdef IRQ_DEBUG
		if (irq == IRQ_DEBUG)
		    printf("ci_isources[%d]->is_maxlevel %d <= ilevel %d\n", irq,
		    ci->ci_isources[irq]->is_maxlevel, ilevel);
#endif
		ci->ci_ipending |= 1 << irq;
		/* leave masked */
		return 0;
	}
	ci->ci_ilevel = ci->ci_isources[irq]->is_maxlevel;
	/* sti */
	ci->ci_idepth++;
#ifdef MULTIPROCESSOR
	x86_intlock(regs);
#endif
	ih = ci->ci_isources[irq]->is_handlers;
	while (ih != NULL) {
		if (ih->ih_level <= ilevel) {
#ifdef IRQ_DEBUG
		if (irq == IRQ_DEBUG)
		    printf("ih->ih_level %d <= ilevel %d\n", ih->ih_level, ilevel);
#endif
#ifdef MULTIPROCESSOR
			x86_intunlock(regs);
#endif
			ci->ci_ipending |= 1 << irq;
			/* leave masked */
			ci->ci_idepth--;
			splx(ilevel);
			return 0;
		}
		ci->ci_ilevel = ih->ih_level;
		ih_fun = (void *)ih->ih_fun;
		ih_fun(ih->ih_arg, regs);
		ih = ih->ih_next;
	}
#ifdef MULTIPROCESSOR
	x86_intunlock(regs);
#endif
	hypervisor_enable_irq(irq);
	ci->ci_idepth--;
	splx(ilevel);

#if 0 /* DDD */
	if (irq >= 3)
		if (irq != 7) printf("do_event %d done, ipending %08x\n", irq,
		    ci->ci_ipending);
#endif

	return 0;
}

static int
find_unbound_irq(void)
{
	int irq;

	for (irq = 0; irq < NR_IRQS; irq++)
		if (irq_bindcount[irq] == 0)
			break;

	if (irq == NR_IRQS)
		panic("No available IRQ to bind to: increase NR_IRQS!\n");

	return irq;
}

int
bind_virq_to_irq(int virq)
{
	evtchn_op_t op;
	int evtchn, irq, s;

	s = splhigh();
	simple_lock(&irq_mapping_update_lock);

	irq = virq_to_irq[virq];
	if (irq == -1) {
		op.cmd = EVTCHNOP_bind_virq;
		op.u.bind_virq.virq = virq;
		if (HYPERVISOR_event_channel_op(&op) != 0)
			panic("Failed to bind virtual IRQ %d\n", virq);
		evtchn = op.u.bind_virq.port;

		irq = find_unbound_irq();
		evtchn_to_irq[evtchn] = irq;
		irq_to_evtchn[irq] = evtchn;
		virq_to_irq[virq] = irq;
	}

	irq_bindcount[irq]++;

	simple_unlock(&irq_mapping_update_lock);
	splx(s);
    
	return irq;
}

void
unbind_virq_from_irq(int virq)
{
	evtchn_op_t op;
	int irq = virq_to_irq[virq];
	int evtchn = irq_to_evtchn[irq];
	int s = splhigh();

	simple_lock(&irq_mapping_update_lock);

	irq_bindcount[irq]--;
	if (irq_bindcount[irq] == 0) {
		op.cmd = EVTCHNOP_close;
		op.u.close.dom = DOMID_SELF;
		op.u.close.port = evtchn;
		if (HYPERVISOR_event_channel_op(&op) != 0)
			panic("Failed to unbind virtual IRQ %d\n", virq);

		evtchn_to_irq[evtchn] = -1;
		irq_to_evtchn[irq] = -1;
		virq_to_irq[virq] = -1;
	}

	simple_unlock(&irq_mapping_update_lock);
	splx(s);
}

#ifdef DOM0OPS
int
bind_pirq_to_irq(int pirq)
{
	evtchn_op_t op;
	int evtchn, irq, s;

	if (pirq >= NR_PIRQS) {
		panic("pirq %d out of bound, increase NR_PIRQS", pirq);
	}

	s = splhigh();
	simple_lock(&irq_mapping_update_lock);

	irq = pirq_to_irq[pirq];
	if (irq == -1) {
		op.cmd = EVTCHNOP_bind_pirq;
		op.u.bind_pirq.pirq = pirq;
		op.u.bind_pirq.flags = BIND_PIRQ__WILL_SHARE;
		if (HYPERVISOR_event_channel_op(&op) != 0)
			panic("Failed to bind physical IRQ %d\n", pirq);
		evtchn = op.u.bind_pirq.port;

		irq = find_unbound_irq();
#ifdef IRQ_DEBUG
		printf("pirq %d irq %d evtchn %d\n", pirq, irq, evtchn);
#endif
		evtchn_to_irq[evtchn] = irq;
		irq_to_evtchn[irq] = evtchn;
		pirq_to_irq[pirq] = irq;
	}

	irq_bindcount[irq]++;

	simple_unlock(&irq_mapping_update_lock);
	splx(s);
    
	return irq;
}

void
unbind_pirq_from_irq(int pirq)
{
	evtchn_op_t op;
	int irq = pirq_to_irq[pirq];
	int evtchn = irq_to_evtchn[irq];
	int s = splhigh();

	simple_lock(&irq_mapping_update_lock);

	irq_bindcount[irq]--;
	if (irq_bindcount[irq] == 0) {
		op.cmd = EVTCHNOP_close;
		op.u.close.dom = DOMID_SELF;
		op.u.close.port = evtchn;
		if (HYPERVISOR_event_channel_op(&op) != 0)
			panic("Failed to unbind physical IRQ %d\n", pirq);

		evtchn_to_irq[evtchn] = -1;
		irq_to_evtchn[irq] = -1;
		pirq_to_irq[pirq] = -1;
	}

	simple_unlock(&irq_mapping_update_lock);
	splx(s);
}

struct pintrhand *
pirq_establish(int pirq, int irq, int (*func)(void *), void *arg, int level)
{
	struct pintrhand *ih;
	physdev_op_t physdev_op;

	ih = malloc(sizeof *ih, M_DEVBUF, cold ? M_NOWAIT : M_WAITOK);
	if (ih == NULL) {
		printf("pirq_establish: can't malloc handler info\n");
		return NULL;
	}
	if (event_set_handler(irq, pirq_interrupt, ih, level) != 0) {
		free(ih, M_DEVBUF);
		return NULL;
	}
	ih->pirq = pirq;
	ih->irq = irq;
	ih->func = func;
	ih->arg = arg;

	physdev_op.cmd = PHYSDEVOP_IRQ_STATUS_QUERY;
	physdev_op.u.irq_status_query.irq = pirq;
	if (HYPERVISOR_physdev_op(&physdev_op) < 0)
		panic("HYPERVISOR_physdev_op(PHYSDEVOP_IRQ_STATUS_QUERY)");
	if (physdev_op.u.irq_status_query.flags &
	    PHYSDEVOP_IRQ_NEEDS_UNMASK_NOTIFY) {
		irq_needs_unmask_notify[irq >> 5] |= (1 << (irq & 0x1f));
#ifdef IRQ_DEBUG
		printf("pirq %d needs notify\n", pirq);
#endif
	}
	hypervisor_enable_irq(irq);
	return ih;
}

int
pirq_interrupt(void *arg)
{
	struct pintrhand *ih = arg;
	int ret;


	ret = ih->func(ih->arg);
#ifdef IRQ_DEBUG
	if (ih->irq == IRQ_DEBUG)
	    printf("pirq_interrupt irq %d/%d ret %d\n", ih->irq, ih->pirq, ret);
#endif
	return ret;
}

void
pirq_notify(int irq)
{

	if (irq_needs_unmask_notify[irq >> 5] & (1 << (irq & 0x1f))) {
#ifdef  IRQ_DEBUG
		if (irq == IRQ_DEBUG)
		    printf("pirq_notify(%d)\n", irq);
#endif
		(void)HYPERVISOR_physdev_op(&physdev_op_notify);
	}
}

#endif /* DOM0OPS */

int
bind_evtchn_to_irq(int evtchn)
{
	int irq;
	int s = splhigh();

	simple_lock(&irq_mapping_update_lock);

	irq = evtchn_to_irq[evtchn];
	if (irq == -1) {
		irq = find_unbound_irq();
		evtchn_to_irq[evtchn] = irq;
		irq_to_evtchn[irq] = evtchn;
	}

	irq_bindcount[irq]++;

	simple_unlock(&irq_mapping_update_lock);
	splx(s);
    
	return irq;
}

int
unbind_evtchn_to_irq(int evtchn)
{
	int irq;
	int s = splhigh();

	simple_lock(&irq_mapping_update_lock);

	irq = evtchn_to_irq[evtchn];
	if (irq == -1) {
		simple_unlock(&irq_mapping_update_lock);
		splx(s);
		return ENOENT;
	}
	irq_bindcount[irq]--;
	if (irq_bindcount[irq] == 0) {
		evtchn_to_irq[evtchn] = -1;
		irq_to_evtchn[irq] = -1;
	}

	simple_unlock(&irq_mapping_update_lock);
	splx(s);
    
	return irq;
}

int
event_set_handler(int irq, ev_handler_t handler, void *arg, int level)
{
	struct intrsource *isp;
	struct intrhand *ih;
	struct cpu_info *ci;
	int s;

#ifdef IRQ_DEBUG
	printf("event_set_handler IRQ %d handler %p\n", irq, handler);
#endif

	if (irq >= NR_IRQS) {
#ifdef DIAGNOSTIC
		printf("irq number %d > NR_IRQS\n", irq);
#endif
		return ENOENT;
	}

#if 0
	printf("event_set_handler irq %d/%d handler %p level %d\n", irq,
	       irq_to_evtchn[irq], handler, level);
#endif
	MALLOC(ih, struct intrhand *, sizeof (struct intrhand), M_DEVBUF,
	    M_WAITOK|M_ZERO);
	if (ih == NULL)
		panic("can't allocate fixed interrupt source");


	ih->ih_level = level;
	ih->ih_fun = handler;
	ih->ih_arg = arg;
	ih->ih_next = NULL;

	ci = &cpu_info_primary;
	s = splhigh();
	if (ci->ci_isources[irq] == NULL) {
		MALLOC(isp, struct intrsource *, sizeof (struct intrsource),
		    M_DEVBUF, M_WAITOK|M_ZERO);
		if (isp == NULL)
			panic("can't allocate fixed interrupt source");
		isp->is_recurse = xenev_stubs[irq].ist_recurse;
		isp->is_resume = xenev_stubs[irq].ist_resume;
		isp->is_handlers = ih;
		isp->is_pic = &xenev_pic;
		ci->ci_isources[irq] = isp;
		snprintf(isp->is_evname, sizeof(isp->is_evname), "irq%d", irq);
		evcnt_attach_dynamic(&isp->is_evcnt, EVCNT_TYPE_INTR, NULL,
		    ci->ci_dev->dv_xname, isp->is_evname);
	} else {
		isp = ci->ci_isources[irq];
		ih->ih_next = isp->is_handlers;
		isp->is_handlers = ih;
	}

	intr_calculatemasks(ci);
	splx(s);

	return 0;
}

int
event_remove_handler(int irq, ev_handler_t handler, void *arg)
{
	struct intrsource *isp;
	struct intrhand *ih;
	struct intrhand **ihp;
	struct cpu_info *ci = &cpu_info_primary;

	isp = ci->ci_isources[irq];
	if (isp == NULL)
		return ENOENT;

	for (ihp = &isp->is_handlers, ih = isp->is_handlers;
	    ih != NULL;
	    ihp = &ih->ih_next, ih = ih->ih_next) {
		if (ih->ih_fun == handler && ih->ih_arg == arg)
			break;
	}
	if (ih == NULL)
		return ENOENT;
	*ihp = ih->ih_next;
	FREE(ih, M_DEVBUF);
	if (isp->is_handlers == NULL) {
		evcnt_detach(&isp->is_evcnt);
		FREE(isp, M_DEVBUF);
		ci->ci_isources[irq] = NULL;
	}
	intr_calculatemasks(ci);
	return 0;
}

void
hypervisor_enable_irq(unsigned int irq)
{
#ifdef IRQ_DEBUG
	if (irq == IRQ_DEBUG)
		printf("hypervisor_enable_irq: irq %d\n", irq);
#endif

	hypervisor_unmask_event(irq_to_evtchn[irq]);
#ifdef DOM0OPS
	pirq_notify(irq);
#endif
}

void
hypervisor_disable_irq(unsigned int irq)
{
#ifdef IRQ_DEBUG
	if (irq == IRQ_DEBUG)
		printf("hypervisor_disable_irq: irq %d\n", irq);
#endif

	hypervisor_mask_event(irq_to_evtchn[irq]);
}

void
hypervisor_acknowledge_irq(unsigned int irq)
{
#ifdef IRQ_DEBUG
	if (irq == IRQ_DEBUG)
		printf("hypervisor_acknowledge_irq: irq %d\n", irq);
#endif
	hypervisor_mask_event(irq_to_evtchn[irq]);
	hypervisor_clear_event(irq_to_evtchn[irq]);
}

#if 0
static int
xen_die_handler(void *arg)
{
	printf("hypervisor: DIE event received...\n");
	cpu_reboot(0, NULL);
	/* NOTREACHED */
	return 0;
}
#endif

static int
xen_debug_handler(void *arg)
{
	printf("debug event\n");
	return 0;
}

static int
xen_misdirect_handler(void *arg)
{
#if 0
	char *msg = "misdirect\n";
	(void)HYPERVISOR_console_io(CONSOLEIO_write, strlen(msg), msg);
#endif
	return 0;
}