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
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to