svn commit: r343523 - in stable/12/sys: dev/pci kern sys x86/x86

Andriy Gapon avg at FreeBSD.org
Mon Jan 28 09:45:31 UTC 2019


Author: avg
Date: Mon Jan 28 09:45:28 2019
New Revision: 343523
URL: https://svnweb.freebsd.org/changeset/base/343523

Log:
  MFC r342170: add support for marking interrupt handlers as suspended
  
  The goal of this change is to fix a problem with PCI shared interrupts
  during suspend and resume.

Modified:
  stable/12/sys/dev/pci/pci.c
  stable/12/sys/kern/bus_if.m
  stable/12/sys/kern/kern_intr.c
  stable/12/sys/kern/subr_bus.c
  stable/12/sys/kern/subr_rman.c
  stable/12/sys/sys/bus.h
  stable/12/sys/sys/interrupt.h
  stable/12/sys/sys/rman.h
  stable/12/sys/x86/x86/nexus.c
Directory Properties:
  stable/12/   (props changed)

Modified: stable/12/sys/dev/pci/pci.c
==============================================================================
--- stable/12/sys/dev/pci/pci.c	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/dev/pci/pci.c	Mon Jan 28 09:45:28 2019	(r343523)
@@ -4457,6 +4457,7 @@ int
 pci_suspend_child(device_t dev, device_t child)
 {
 	struct pci_devinfo *dinfo;
+	struct resource_list_entry *rle;
 	int error;
 
 	dinfo = device_get_ivars(child);
@@ -4473,8 +4474,20 @@ pci_suspend_child(device_t dev, device_t child)
 	if (error)
 		return (error);
 
-	if (pci_do_power_suspend)
+	if (pci_do_power_suspend) {
+		/*
+		 * Make sure this device's interrupt handler is not invoked
+		 * in the case the device uses a shared interrupt that can
+		 * be raised by some other device.
+		 * This is applicable only to regular (legacy) PCI interrupts
+		 * as MSI/MSI-X interrupts are never shared.
+		 */
+		rle = resource_list_find(&dinfo->resources,
+		    SYS_RES_IRQ, 0);
+		if (rle != NULL && rle->res != NULL)
+			(void)bus_suspend_intr(child, rle->res);
 		pci_set_power_child(dev, child, PCI_POWERSTATE_D3);
+	}
 
 	return (0);
 }
@@ -4483,6 +4496,7 @@ int
 pci_resume_child(device_t dev, device_t child)
 {
 	struct pci_devinfo *dinfo;
+	struct resource_list_entry *rle;
 
 	if (pci_do_power_resume)
 		pci_set_power_child(dev, child, PCI_POWERSTATE_D0);
@@ -4493,6 +4507,16 @@ pci_resume_child(device_t dev, device_t child)
 		pci_cfg_save(child, dinfo, 1);
 
 	bus_generic_resume_child(dev, child);
+
+	/*
+	 * Allow interrupts only after fully resuming the driver and hardware.
+	 */
+	if (pci_do_power_suspend) {
+		/* See pci_suspend_child for details. */
+		rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, 0);
+		if (rle != NULL && rle->res != NULL)
+			(void)bus_resume_intr(child, rle->res);
+	}
 
 	return (0);
 }

Modified: stable/12/sys/kern/bus_if.m
==============================================================================
--- stable/12/sys/kern/bus_if.m	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/kern/bus_if.m	Mon Jan 28 09:45:28 2019	(r343523)
@@ -472,6 +472,44 @@ METHOD int teardown_intr {
 };
 
 /**
+ * @brief Suspend an interrupt handler
+ *
+ * This method is used to mark a handler as suspended in the case
+ * that the associated device is powered down and cannot be a source
+ * for the, typically shared, interrupt.
+ * The value of @p _irq must be the interrupt resource passed
+ * to a previous call to BUS_SETUP_INTR().
+ * 
+ * @param _dev		the parent device of @p _child
+ * @param _child	the device which allocated the resource
+ * @param _irq		the resource representing the interrupt
+ */
+METHOD int suspend_intr {
+	device_t	_dev;
+	device_t	_child;
+	struct resource *_irq;
+} DEFAULT bus_generic_suspend_intr;
+
+/**
+ * @brief Resume an interrupt handler
+ *
+ * This method is used to clear suspended state of a handler when
+ * the associated device is powered up and can be an interrupt source
+ * again.
+ * The value of @p _irq must be the interrupt resource passed
+ * to a previous call to BUS_SETUP_INTR().
+ * 
+ * @param _dev		the parent device of @p _child
+ * @param _child	the device which allocated the resource
+ * @param _irq		the resource representing the interrupt
+ */
+METHOD int resume_intr {
+	device_t	_dev;
+	device_t	_child;
+	struct resource *_irq;
+} DEFAULT bus_generic_resume_intr;
+
+/**
  * @brief Define a resource which can be allocated with
  * BUS_ALLOC_RESOURCE().
  *

Modified: stable/12/sys/kern/kern_intr.c
==============================================================================
--- stable/12/sys/kern/kern_intr.c	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/kern/kern_intr.c	Mon Jan 28 09:45:28 2019	(r343523)
@@ -721,6 +721,28 @@ intr_event_barrier(struct intr_event *ie)
 	atomic_thread_fence_acq();
 }
 
+static void
+intr_handler_barrier(struct intr_handler *handler)
+{
+	struct intr_event *ie;
+
+	ie = handler->ih_event;
+	mtx_assert(&ie->ie_lock, MA_OWNED);
+	KASSERT((handler->ih_flags & IH_DEAD) == 0,
+	    ("update for a removed handler"));
+
+	if (ie->ie_thread == NULL) {
+		intr_event_barrier(ie);
+		return;
+	}
+	if ((handler->ih_flags & IH_CHANGED) == 0) {
+		handler->ih_flags |= IH_CHANGED;
+		intr_event_schedule_thread(ie);
+	}
+	while ((handler->ih_flags & IH_CHANGED) != 0)
+		msleep(handler, &ie->ie_lock, 0, "ih_barr", 0);
+}
+
 /*
  * Sleep until an ithread finishes executing an interrupt handler.
  *
@@ -842,6 +864,49 @@ intr_event_remove_handler(void *cookie)
 	return (0);
 }
 
+int
+intr_event_suspend_handler(void *cookie)
+{
+	struct intr_handler *handler = (struct intr_handler *)cookie;
+	struct intr_event *ie;
+
+	if (handler == NULL)
+		return (EINVAL);
+	ie = handler->ih_event;
+	KASSERT(ie != NULL,
+	    ("interrupt handler \"%s\" has a NULL interrupt event",
+	    handler->ih_name));
+	mtx_lock(&ie->ie_lock);
+	handler->ih_flags |= IH_SUSP;
+	intr_handler_barrier(handler);
+	mtx_unlock(&ie->ie_lock);
+	return (0);
+}
+
+int
+intr_event_resume_handler(void *cookie)
+{
+	struct intr_handler *handler = (struct intr_handler *)cookie;
+	struct intr_event *ie;
+
+	if (handler == NULL)
+		return (EINVAL);
+	ie = handler->ih_event;
+	KASSERT(ie != NULL,
+	    ("interrupt handler \"%s\" has a NULL interrupt event",
+	    handler->ih_name));
+
+	/*
+	 * intr_handler_barrier() acts not only as a barrier,
+	 * it also allows to check for any pending interrupts.
+	 */
+	mtx_lock(&ie->ie_lock);
+	handler->ih_flags &= ~IH_SUSP;
+	intr_handler_barrier(handler);
+	mtx_unlock(&ie->ie_lock);
+	return (0);
+}
+
 static int
 intr_event_schedule_thread(struct intr_event *ie)
 {
@@ -1016,10 +1081,21 @@ intr_event_execute_handlers(struct proc *p, struct int
 		 */
 		ihp = ih;
 
+		if ((ih->ih_flags & IH_CHANGED) != 0) {
+			mtx_lock(&ie->ie_lock);
+			ih->ih_flags &= ~IH_CHANGED;
+			wakeup(ih);
+			mtx_unlock(&ie->ie_lock);
+		}
+
 		/* Skip filter only handlers */
 		if (ih->ih_handler == NULL)
 			continue;
 
+		/* Skip suspended handlers */
+		if ((ih->ih_flags & IH_SUSP) != 0)
+			continue;
+
 		/*
 		 * For software interrupt threads, we only execute
 		 * handlers that have their need flag set.  Hardware
@@ -1178,8 +1254,9 @@ intr_event_handle(struct intr_event *ie, struct trapfr
 	struct intr_handler *ih;
 	struct trapframe *oldframe;
 	struct thread *td;
-	int ret, thread;
 	int phase;
+	int ret;
+	bool filter, thread;
 
 	td = curthread;
 
@@ -1198,7 +1275,8 @@ intr_event_handle(struct intr_event *ie, struct trapfr
 	 * a trapframe as its argument.
 	 */
 	td->td_intr_nesting_level++;
-	thread = 0;
+	filter = false;
+	thread = false;
 	ret = 0;
 	critical_enter();
 	oldframe = td->td_intr_frame;
@@ -1214,8 +1292,10 @@ intr_event_handle(struct intr_event *ie, struct trapfr
 	atomic_thread_fence_seq_cst();
 
 	CK_SLIST_FOREACH(ih, &ie->ie_handlers, ih_next) {
+		if ((ih->ih_flags & IH_SUSP) != 0)
+			continue;
 		if (ih->ih_filter == NULL) {
-			thread = 1;
+			thread = true;
 			continue;
 		}
 		CTR4(KTR_INTR, "%s: exec %p(%p) for %s", __func__,
@@ -1230,24 +1310,25 @@ intr_event_handle(struct intr_event *ie, struct trapfr
 		    (ret & ~(FILTER_SCHEDULE_THREAD | FILTER_HANDLED)) == 0),
 		    ("%s: incorrect return value %#x from %s", __func__, ret,
 		    ih->ih_name));
+		filter = filter || ret == FILTER_HANDLED;
 
-		/* 
+		/*
 		 * Wrapper handler special handling:
 		 *
-		 * in some particular cases (like pccard and pccbb), 
+		 * in some particular cases (like pccard and pccbb),
 		 * the _real_ device handler is wrapped in a couple of
 		 * functions - a filter wrapper and an ithread wrapper.
-		 * In this case (and just in this case), the filter wrapper 
+		 * In this case (and just in this case), the filter wrapper
 		 * could ask the system to schedule the ithread and mask
 		 * the interrupt source if the wrapped handler is composed
 		 * of just an ithread handler.
 		 *
-		 * TODO: write a generic wrapper to avoid people rolling 
-		 * their own
+		 * TODO: write a generic wrapper to avoid people rolling
+		 * their own.
 		 */
 		if (!thread) {
 			if (ret == FILTER_SCHEDULE_THREAD)
-				thread = 1;
+				thread = true;
 		}
 	}
 	atomic_add_rel_int(&ie->ie_active[phase], -1);
@@ -1271,6 +1352,11 @@ intr_event_handle(struct intr_event *ie, struct trapfr
 	}
 	critical_exit();
 	td->td_intr_nesting_level--;
+#ifdef notyet
+	/* The interrupt is not aknowledged by any filter and has no ithread. */
+	if (!thread && !filter)
+		return (EINVAL);
+#endif
 	return (0);
 }
 

Modified: stable/12/sys/kern/subr_bus.c
==============================================================================
--- stable/12/sys/kern/subr_bus.c	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/kern/subr_bus.c	Mon Jan 28 09:45:28 2019	(r343523)
@@ -4047,6 +4047,36 @@ bus_generic_teardown_intr(device_t dev, device_t child
 }
 
 /**
+ * @brief Helper function for implementing BUS_SUSPEND_INTR().
+ *
+ * This simple implementation of BUS_SUSPEND_INTR() simply calls the
+ * BUS_SUSPEND_INTR() method of the parent of @p dev.
+ */
+int
+bus_generic_suspend_intr(device_t dev, device_t child, struct resource *irq)
+{
+	/* Propagate up the bus hierarchy until someone handles it. */
+	if (dev->parent)
+		return (BUS_SUSPEND_INTR(dev->parent, child, irq));
+	return (EINVAL);
+}
+
+/**
+ * @brief Helper function for implementing BUS_RESUME_INTR().
+ *
+ * This simple implementation of BUS_RESUME_INTR() simply calls the
+ * BUS_RESUME_INTR() method of the parent of @p dev.
+ */
+int
+bus_generic_resume_intr(device_t dev, device_t child, struct resource *irq)
+{
+	/* Propagate up the bus hierarchy until someone handles it. */
+	if (dev->parent)
+		return (BUS_RESUME_INTR(dev->parent, child, irq));
+	return (EINVAL);
+}
+
+/**
  * @brief Helper function for implementing BUS_ADJUST_RESOURCE().
  *
  * This simple implementation of BUS_ADJUST_RESOURCE() simply calls the
@@ -4611,6 +4641,34 @@ bus_teardown_intr(device_t dev, struct resource *r, vo
 	if (dev->parent == NULL)
 		return (EINVAL);
 	return (BUS_TEARDOWN_INTR(dev->parent, dev, r, cookie));
+}
+
+/**
+ * @brief Wrapper function for BUS_SUSPEND_INTR().
+ *
+ * This function simply calls the BUS_SUSPEND_INTR() method of the
+ * parent of @p dev.
+ */
+int
+bus_suspend_intr(device_t dev, struct resource *r)
+{
+	if (dev->parent == NULL)
+		return (EINVAL);
+	return (BUS_SUSPEND_INTR(dev->parent, dev, r));
+}
+
+/**
+ * @brief Wrapper function for BUS_RESUME_INTR().
+ *
+ * This function simply calls the BUS_RESUME_INTR() method of the
+ * parent of @p dev.
+ */
+int
+bus_resume_intr(device_t dev, struct resource *r)
+{
+	if (dev->parent == NULL)
+		return (EINVAL);
+	return (BUS_RESUME_INTR(dev->parent, dev, r));
 }
 
 /**

Modified: stable/12/sys/kern/subr_rman.c
==============================================================================
--- stable/12/sys/kern/subr_rman.c	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/kern/subr_rman.c	Mon Jan 28 09:45:28 2019	(r343523)
@@ -94,6 +94,7 @@ struct resource_i {
 	rman_res_t	r_end;		/* index of the last entry (inclusive) */
 	u_int	r_flags;
 	void	*r_virtual;	/* virtual address of this resource */
+	void	*r_irq_cookie;	/* interrupt cookie for this (interrupt) resource */
 	device_t r_dev;	/* device which has allocated this resource */
 	struct rman *r_rm;	/* resource manager from whence this came */
 	int	r_rid;		/* optional rid for this resource. */
@@ -866,6 +867,20 @@ rman_get_virtual(struct resource *r)
 {
 
 	return (r->__r_i->r_virtual);
+}
+
+void
+rman_set_irq_cookie(struct resource *r, void *c)
+{
+
+	r->__r_i->r_irq_cookie = c;
+}
+
+void *
+rman_get_irq_cookie(struct resource *r)
+{
+
+	return (r->__r_i->r_irq_cookie);
 }
 
 void

Modified: stable/12/sys/sys/bus.h
==============================================================================
--- stable/12/sys/sys/bus.h	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/sys/bus.h	Mon Jan 28 09:45:28 2019	(r343523)
@@ -485,6 +485,10 @@ int	bus_generic_suspend(device_t dev);
 int	bus_generic_suspend_child(device_t dev, device_t child);
 int	bus_generic_teardown_intr(device_t dev, device_t child,
 				  struct resource *irq, void *cookie);
+int	bus_generic_suspend_intr(device_t dev, device_t child,
+				  struct resource *irq);
+int	bus_generic_resume_intr(device_t dev, device_t child,
+				  struct resource *irq);
 int	bus_generic_unmap_resource(device_t dev, device_t child, int type,
 				   struct resource *r,
 				   struct resource_map *map);
@@ -535,6 +539,8 @@ int	bus_setup_intr(device_t dev, struct resource *r, i
 		       driver_filter_t filter, driver_intr_t handler, 
 		       void *arg, void **cookiep);
 int	bus_teardown_intr(device_t dev, struct resource *r, void *cookie);
+int	bus_suspend_intr(device_t dev, struct resource *r);
+int	bus_resume_intr(device_t dev, struct resource *r);
 int	bus_bind_intr(device_t dev, struct resource *r, int cpu);
 int	bus_describe_intr(device_t dev, struct resource *irq, void *cookie,
 			  const char *fmt, ...) __printflike(4, 5);

Modified: stable/12/sys/sys/interrupt.h
==============================================================================
--- stable/12/sys/sys/interrupt.h	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/sys/interrupt.h	Mon Jan 28 09:45:28 2019	(r343523)
@@ -62,6 +62,8 @@ struct intr_handler {
 #define	IH_EXCLUSIVE	0x00000002	/* Exclusive interrupt. */
 #define	IH_ENTROPY	0x00000004	/* Device is a good entropy source. */
 #define	IH_DEAD		0x00000008	/* Handler should be removed. */
+#define	IH_SUSP		0x00000010	/* Device is powered down. */
+#define	IH_CHANGED	0x40000000	/* Handler state is changed. */
 #define	IH_MPSAFE	0x80000000	/* Handler does not need Giant. */
 
 /*
@@ -184,6 +186,8 @@ int	intr_event_describe_handler(struct intr_event *ie,
 int	intr_event_destroy(struct intr_event *ie);
 int	intr_event_handle(struct intr_event *ie, struct trapframe *frame);
 int	intr_event_remove_handler(void *cookie);
+int	intr_event_suspend_handler(void *cookie);
+int	intr_event_resume_handler(void *cookie);
 int	intr_getaffinity(int irq, int mode, void *mask);
 void	*intr_handler_source(void *cookie);
 int	intr_setaffinity(int irq, int mode, void *mask);

Modified: stable/12/sys/sys/rman.h
==============================================================================
--- stable/12/sys/sys/rman.h	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/sys/rman.h	Mon Jan 28 09:45:28 2019	(r343523)
@@ -131,6 +131,7 @@ bus_space_tag_t rman_get_bustag(struct resource *);
 rman_res_t	rman_get_end(struct resource *);
 device_t rman_get_device(struct resource *);
 u_int	rman_get_flags(struct resource *);
+void   *rman_get_irq_cookie(struct resource *);
 void	rman_get_mapping(struct resource *, struct resource_map *);
 int	rman_get_rid(struct resource *);
 rman_res_t	rman_get_size(struct resource *);
@@ -155,6 +156,7 @@ void	rman_set_bushandle(struct resource *_r, bus_space
 void	rman_set_bustag(struct resource *_r, bus_space_tag_t _t);
 void	rman_set_device(struct resource *_r, device_t _dev);
 void	rman_set_end(struct resource *_r, rman_res_t _end);
+void	rman_set_irq_cookie(struct resource *_r, void *_c);
 void	rman_set_mapping(struct resource *, struct resource_map *);
 void	rman_set_rid(struct resource *_r, int _rid);
 void	rman_set_start(struct resource *_r, rman_res_t _start);

Modified: stable/12/sys/x86/x86/nexus.c
==============================================================================
--- stable/12/sys/x86/x86/nexus.c	Mon Jan 28 09:27:28 2019	(r343522)
+++ stable/12/sys/x86/x86/nexus.c	Mon Jan 28 09:45:28 2019	(r343523)
@@ -124,6 +124,8 @@ static	int nexus_setup_intr(device_t, device_t, struct
 			      void **);
 static	int nexus_teardown_intr(device_t, device_t, struct resource *,
 				void *);
+static	int nexus_suspend_intr(device_t, device_t, struct resource *);
+static	int nexus_resume_intr(device_t, device_t, struct resource *);
 static struct resource_list *nexus_get_reslist(device_t dev, device_t child);
 static	int nexus_set_resource(device_t, device_t, int, int,
 			       rman_res_t, rman_res_t);
@@ -161,6 +163,8 @@ static device_method_t nexus_methods[] = {
 	DEVMETHOD(bus_unmap_resource,	nexus_unmap_resource),
 	DEVMETHOD(bus_setup_intr,	nexus_setup_intr),
 	DEVMETHOD(bus_teardown_intr,	nexus_teardown_intr),
+	DEVMETHOD(bus_suspend_intr,	nexus_suspend_intr),
+	DEVMETHOD(bus_resume_intr,	nexus_resume_intr),
 #ifdef SMP
 	DEVMETHOD(bus_bind_intr,	nexus_bind_intr),
 #endif
@@ -595,6 +599,8 @@ nexus_setup_intr(device_t bus, device_t child, struct 
 
 	error = intr_add_handler(device_get_nameunit(child),
 	    rman_get_start(irq), filter, ihand, arg, flags, cookiep, domain);
+	if (error == 0)
+		rman_set_irq_cookie(irq, *cookiep);
 
 	return (error);
 }
@@ -602,7 +608,24 @@ nexus_setup_intr(device_t bus, device_t child, struct 
 static int
 nexus_teardown_intr(device_t dev, device_t child, struct resource *r, void *ih)
 {
-	return (intr_remove_handler(ih));
+	int error;
+
+	error = intr_remove_handler(ih);
+	if (error == 0)
+		rman_set_irq_cookie(r, NULL);
+	return (error);
+}
+
+static int
+nexus_suspend_intr(device_t dev, device_t child, struct resource *irq)
+{
+	return (intr_event_suspend_handler(rman_get_irq_cookie(irq)));
+}
+
+static int
+nexus_resume_intr(device_t dev, device_t child, struct resource *irq)
+{
+	return (intr_event_resume_handler(rman_get_irq_cookie(irq)));
 }
 
 #ifdef SMP


More information about the svn-src-all mailing list