return false;
 
        /* flip prio, so high prio is leftmost */
-       if (prio_less(b, a, task_rq(a)->core->core_forceidle))
+       if (prio_less(b, a, !!task_rq(a)->core->core_forceidle_count))
                return true;
 
        return false;
        rb_add(&p->core_node, &rq->core_tree, rb_sched_core_less);
 }
 
-void sched_core_dequeue(struct rq *rq, struct task_struct *p)
+void sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags)
 {
        rq->core->core_task_seq++;
 
-       if (!sched_core_enqueued(p))
-               return;
+       if (sched_core_enqueued(p)) {
+               rb_erase(&p->core_node, &rq->core_tree);
+               RB_CLEAR_NODE(&p->core_node);
+       }
 
-       rb_erase(&p->core_node, &rq->core_tree);
-       RB_CLEAR_NODE(&p->core_node);
+       /*
+        * Migrating the last task off the cpu, with the cpu in forced idle
+        * state. Reschedule to create an accounting edge for forced idle,
+        * and re-examine whether the core is still in forced idle state.
+        */
+       if (!(flags & DEQUEUE_SAVE) && rq->nr_running == 1 &&
+           rq->core->core_forceidle_count && rq->curr == rq->idle)
+               resched_curr(rq);
 }
 
 /*
                for_each_cpu(t, smt_mask)
                        cpu_rq(t)->core_enabled = enabled;
 
+               cpu_rq(cpu)->core->core_forceidle_start = 0;
+
                sched_core_unlock(cpu, &flags);
 
                cpumask_andnot(&sched_core_mask, &sched_core_mask, smt_mask);
 #else /* !CONFIG_SCHED_CORE */
 
 static inline void sched_core_enqueue(struct rq *rq, struct task_struct *p) { }
-static inline void sched_core_dequeue(struct rq *rq, struct task_struct *p) { }
+static inline void
+sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags) { }
 
 #endif /* CONFIG_SCHED_CORE */
 
 static inline void dequeue_task(struct rq *rq, struct task_struct *p, int flags)
 {
        if (sched_core_enabled(rq))
-               sched_core_dequeue(rq, p);
+               sched_core_dequeue(rq, p, flags);
 
        if (!(flags & DEQUEUE_NOCLOCK))
                update_rq_clock(rq);
        if (sched_feat(LATENCY_WARN))
                resched_latency = cpu_resched_latency(rq);
        calc_global_load_tick(rq);
+       sched_core_tick(rq);
 
        rq_unlock(rq, &rf);
 
        struct task_struct *next, *p, *max = NULL;
        const struct cpumask *smt_mask;
        bool fi_before = false;
+       bool core_clock_updated = (rq == rq->core);
        unsigned long cookie;
        int i, cpu, occ = 0;
        struct rq *rq_i;
 
        /* reset state */
        rq->core->core_cookie = 0UL;
-       if (rq->core->core_forceidle) {
+       if (rq->core->core_forceidle_count) {
+               if (!core_clock_updated) {
+                       update_rq_clock(rq->core);
+                       core_clock_updated = true;
+               }
+               sched_core_account_forceidle(rq);
+               /* reset after accounting force idle */
+               rq->core->core_forceidle_start = 0;
+               rq->core->core_forceidle_count = 0;
+               rq->core->core_forceidle_occupation = 0;
                need_sync = true;
                fi_before = true;
-               rq->core->core_forceidle = false;
        }
 
        /*
        for_each_cpu_wrap(i, smt_mask, cpu) {
                rq_i = cpu_rq(i);
 
-               if (i != cpu)
+               /*
+                * Current cpu always has its clock updated on entrance to
+                * pick_next_task(). If the current cpu is not the core,
+                * the core may also have been updated above.
+                */
+               if (i != cpu && (rq_i != rq->core || !core_clock_updated))
                        update_rq_clock(rq_i);
 
                p = rq_i->core_pick = pick_task(rq_i);
 
                if (p == rq_i->idle) {
                        if (rq_i->nr_running) {
-                               rq->core->core_forceidle = true;
+                               rq->core->core_forceidle_count++;
                                if (!fi_before)
                                        rq->core->core_forceidle_seq++;
                        }
                }
        }
 
+       if (schedstat_enabled() && rq->core->core_forceidle_count) {
+               if (cookie)
+                       rq->core->core_forceidle_start = rq_clock(rq->core);
+               rq->core->core_forceidle_occupation = occ;
+       }
+
        rq->core->core_pick_seq = rq->core->core_task_seq;
        next = rq->core_pick;
        rq->core_sched_seq = rq->core->core_pick_seq;
                 *  1            0       1
                 *  1            1       0
                 */
-               if (!(fi_before && rq->core->core_forceidle))
-                       task_vruntime_update(rq_i, rq_i->core_pick, rq->core->core_forceidle);
+               if (!(fi_before && rq->core->core_forceidle_count))
+                       task_vruntime_update(rq_i, rq_i->core_pick, !!rq->core->core_forceidle_count);
 
                rq_i->core_pick->core_occupation = occ;
 
                goto unlock;
 
        /* copy the shared state to the new leader */
-       core_rq->core_task_seq      = rq->core_task_seq;
-       core_rq->core_pick_seq      = rq->core_pick_seq;
-       core_rq->core_cookie        = rq->core_cookie;
-       core_rq->core_forceidle     = rq->core_forceidle;
-       core_rq->core_forceidle_seq = rq->core_forceidle_seq;
+       core_rq->core_task_seq             = rq->core_task_seq;
+       core_rq->core_pick_seq             = rq->core_pick_seq;
+       core_rq->core_cookie               = rq->core_cookie;
+       core_rq->core_forceidle_count      = rq->core_forceidle_count;
+       core_rq->core_forceidle_seq        = rq->core_forceidle_seq;
+       core_rq->core_forceidle_occupation = rq->core_forceidle_occupation;
+
+       /*
+        * Accounting edge for forced idle is handled in pick_next_task().
+        * Don't need another one here, since the hotplug thread shouldn't
+        * have a cookie.
+        */
+       core_rq->core_forceidle_start = 0;
 
        /* install new leader */
        for_each_cpu(t, smt_mask) {
                rq->core_pick = NULL;
                rq->core_enabled = 0;
                rq->core_tree = RB_ROOT;
-               rq->core_forceidle = false;
+               rq->core_forceidle_count = 0;
+               rq->core_forceidle_occupation = 0;
+               rq->core_forceidle_start = 0;
 
                rq->core_cookie = 0UL;
 #endif
 
 
        enqueued = sched_core_enqueued(p);
        if (enqueued)
-               sched_core_dequeue(rq, p);
+               sched_core_dequeue(rq, p, DEQUEUE_SAVE);
 
        old_cookie = p->core_cookie;
        p->core_cookie = cookie;
         * If task is currently running, it may not be compatible anymore after
         * the cookie change, so enter the scheduler on its CPU to schedule it
         * away.
+        *
+        * Note that it is possible that as a result of this cookie change, the
+        * core has now entered/left forced idle state. Defer accounting to the
+        * next scheduling edge, rather than always forcing a reschedule here.
         */
        if (task_running(rq, p))
                resched_curr(rq);
        return err;
 }
 
+#ifdef CONFIG_SCHEDSTATS
+
+/* REQUIRES: rq->core's clock recently updated. */
+void __sched_core_account_forceidle(struct rq *rq)
+{
+       const struct cpumask *smt_mask = cpu_smt_mask(cpu_of(rq));
+       u64 delta, now = rq_clock(rq->core);
+       struct rq *rq_i;
+       struct task_struct *p;
+       int i;
+
+       lockdep_assert_rq_held(rq);
+
+       WARN_ON_ONCE(!rq->core->core_forceidle_count);
+
+       if (rq->core->core_forceidle_start == 0)
+               return;
+
+       delta = now - rq->core->core_forceidle_start;
+       if (unlikely((s64)delta <= 0))
+               return;
+
+       rq->core->core_forceidle_start = now;
+
+       if (WARN_ON_ONCE(!rq->core->core_forceidle_occupation)) {
+               /* can't be forced idle without a running task */
+       } else if (rq->core->core_forceidle_count > 1 ||
+                  rq->core->core_forceidle_occupation > 1) {
+               /*
+                * For larger SMT configurations, we need to scale the charged
+                * forced idle amount since there can be more than one forced
+                * idle sibling and more than one running cookied task.
+                */
+               delta *= rq->core->core_forceidle_count;
+               delta = div_u64(delta, rq->core->core_forceidle_occupation);
+       }
+
+       for_each_cpu(i, smt_mask) {
+               rq_i = cpu_rq(i);
+               p = rq_i->core_pick ?: rq_i->curr;
+
+               if (!p->core_cookie)
+                       continue;
+
+               __schedstat_add(p->stats.core_forceidle_sum, delta);
+       }
+}
+
+void __sched_core_tick(struct rq *rq)
+{
+       if (!rq->core->core_forceidle_count)
+               return;
+
+       if (rq != rq->core)
+               update_rq_clock(rq->core);
+
+       __sched_core_account_forceidle(rq);
+}
+
+#endif /* CONFIG_SCHEDSTATS */
 
        unsigned int            core_task_seq;
        unsigned int            core_pick_seq;
        unsigned long           core_cookie;
-       unsigned char           core_forceidle;
+       unsigned int            core_forceidle_count;
        unsigned int            core_forceidle_seq;
+       unsigned int            core_forceidle_occupation;
+       u64                     core_forceidle_start;
 #endif
 };
 
 }
 
 extern void sched_core_enqueue(struct rq *rq, struct task_struct *p);
-extern void sched_core_dequeue(struct rq *rq, struct task_struct *p);
+extern void sched_core_dequeue(struct rq *rq, struct task_struct *p, int flags);
 
 extern void sched_core_get(void);
 extern void sched_core_put(void);
 #include "stats.h"
 #include "autogroup.h"
 
+#if defined(CONFIG_SCHED_CORE) && defined(CONFIG_SCHEDSTATS)
+
+extern void __sched_core_account_forceidle(struct rq *rq);
+
+static inline void sched_core_account_forceidle(struct rq *rq)
+{
+       if (schedstat_enabled())
+               __sched_core_account_forceidle(rq);
+}
+
+extern void __sched_core_tick(struct rq *rq);
+
+static inline void sched_core_tick(struct rq *rq)
+{
+       if (sched_core_enabled(rq) && schedstat_enabled())
+               __sched_core_tick(rq);
+}
+
+#else
+
+static inline void sched_core_account_forceidle(struct rq *rq) {}
+
+static inline void sched_core_tick(struct rq *rq) {}
+
+#endif /* CONFIG_SCHED_CORE && CONFIG_SCHEDSTATS */
+
 #ifdef CONFIG_CGROUP_SCHED
 
 /*