]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
dtrace: LOW level cyclics should use workqueues
authorTomas Jedlicka <tomas.jedlicka@oracle.com>
Fri, 30 Jun 2017 13:17:06 +0000 (09:17 -0400)
committerTomas Jedlicka <tomas.jedlicka@oracle.com>
Wed, 12 Jul 2017 19:36:28 +0000 (21:36 +0200)
The HIGH level cyclics are meant to be run from interrupt handler. This works on
Linux because the hrtimer is scheduled as tasklet. The LOW level cyclics must be
interruptible and should not be scheduled as tasklests.

DTrace is currently relying on being able to call dtrace_sync() from within a cyclic
handler. On Linux it is not safe to try send IPIs from within interrupt/bottom half
handlers.

This fix changes LOW level cyclics to use workqueues. At the moment we are using
shared system workqueue but it may be required to allocate our owns if this causes
big latency in our timer routines.

Orabug: 26384779

Signed-off-by: Tomas Jedlicka <tomas.jedlicka@oracle.com>
Reviewed-by: Kris Van Hees <kris.van.hees@oracle.com>
Acked-by: Chuck Anderson <chuck.anderson@oracle.com>
kernel/dtrace/cyclic.c

index 1c90421a9c10c26a3cd4c972e57a6f4bf9857743..2df05bcfe1bd854c47312fa6f3495d5e0e8051d4 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
+#include <linux/workqueue.h>
 
 static DEFINE_SPINLOCK(cyclic_lock);
 static int             omni_enabled = 0;
@@ -22,7 +23,14 @@ static int           omni_enabled = 0;
 #define _CYCLIC_CPU_OMNI               (-2)
 #define CYCLIC_IS_OMNI(cyc)            ((cyc)->cpu == _CYCLIC_CPU_OMNI)
 
-typedef struct cyclic {
+typedef struct cyclic cyclic_t;
+
+typedef struct cyclic_work {
+       struct work_struct      work;
+       struct cyclic           *cyc;
+} cyclic_work_t;
+
+struct cyclic {
        struct list_head                list;
        int                             cpu;
        union {
@@ -31,20 +39,21 @@ typedef struct cyclic {
                        cyc_handler_t           hdlr;
                        uint32_t                pend;
                        struct hrtimer          timr;
-                       struct tasklet_struct   task;
+                       cyclic_work_t           work;
                } cyc;
                struct {
                        cyc_omni_handler_t      hdlr;
                        struct list_head        cycl;
                } omni;
        };
-} cyclic_t;
+};
 
 static LIST_HEAD(cyclics);
 
-static void cyclic_fire(uintptr_t arg)
+static void cyclic_fire(struct work_struct *work)
 {
-       cyclic_t        *cyc = (cyclic_t *)arg;
+       cyclic_work_t   *cwork = (cyclic_work_t *)work;
+       cyclic_t        *cyc = cwork->cyc;
        uint32_t        cpnd, npnd;
 
        do {
@@ -79,21 +88,21 @@ again:
  * be able to perform a variety of tasks (including calling functions that
  * could sleep), and therefore they cannot be called from interrupt context.
  *
- * We schedule a tasklet to do the actual work.
+ * We schedule a workqueue to do the actual work.
  *
  * But... under heavy load it is possible that the hrtimer will expire again
- * before the tasklet had a chance to run.  That would lead to missed events
+ * before the workqueu had a chance to run.  That would lead to missed events
  * which isn't quite acceptable.  Therefore, we use a counter to record how
  * many times the timer has expired vs how many times the handler has been
  * called.  The counter is incremented by this function upon hrtimer expiration
- * and decremented by the tasklet.  Note that the tasklet is responsible for
- * calling the handler multiple times if the counter indicates that multiple
+ * and decremented by the cyclic_fire.  Note that the workqueue is responsible
+ * for calling the handler multiple times if the counter indicates that multiple
  * invocation are pending.
  *
  * This function is called as hrtimer handler, and therefore runs in interrupt
  * context, which by definition will ensure that manipulation of the 'pend'
  * counter in the cyclic can be done without locking, and changes will appear
- * atomic to the tasklet.
+ * atomic to the cyclic_fire().
  *
  * Moral of the story: the handler may not get called at the absolute times as
  * requested, but it will be called the correct number of times.
@@ -114,13 +123,13 @@ static enum hrtimer_restart cyclic_expire(struct hrtimer *timr)
        }
 
        /*
-        * Increment the 'pend' counter, in case the tasklet is already set to
+        * Increment the 'pend' counter, in case the work is already set to
         * run.  If the counter was 0 upon entry, we need to schedule the
-        * tasklet.  If the increment wraps the counter back to 0, we admit
+        * work.  If the increment wraps the counter back to 0, we admit
         * defeat, and reset it to its max value.
         */
        if (cyc->cyc.pend++ == 0)
-               tasklet_hi_schedule(&cyc->cyc.task);
+               schedule_work((struct work_struct *)&cyc->cyc.work);
        else if (cyc->cyc.pend == 0)
                cyc->cyc.pend = UINT_MAX;
 
@@ -148,7 +157,8 @@ cyclic_t *cyclic_new(int omni)
                cyc->cyc.pend = 0;
                hrtimer_init(&cyc->cyc.timr, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
                cyc->cyc.timr.function = cyclic_expire;
-               tasklet_init(&cyc->cyc.task, cyclic_fire, (uintptr_t)cyc);
+               cyc->cyc.work.cyc = cyc;
+               INIT_WORK((struct work_struct *)&cyc->cyc.work, cyclic_fire);
        } else {
                cyc->cpu = _CYCLIC_CPU_OMNI;
                INIT_LIST_HEAD(&cyc->omni.cycl);
@@ -359,15 +369,15 @@ void cyclic_remove(cyclic_id_t id)
                 * making this call.  It is therefore guaranteed that 'pend'
                 * will no longer get incremented.
                 *
-                * The call to tasklet_kill() will wait for the tasklet handler
-                * to finish also, and since the handler always brings 'pend'
-                * down to zero prior to returning, it is guaranteed that
+                * The call to cancel_work_sync() will wait for the workqueue
+                * handler to finish also, and since the handler always brings
+                * 'pend' down to zero prior to returning, it is guaranteed that
                 * (1) all pending handler calls will be made before
                 *     cyclic_remove() returns
                 * (2) the amount of work to do before returning is finite.
                 */
                hrtimer_cancel(&cyc->cyc.timr);
-               tasklet_kill(&cyc->cyc.task);
+               cancel_work_sync((struct work_struct *)&cyc->cyc.work);
        }
 
        list_del(&cyc->list);