From patchwork Thu Jun 9 03:08:58 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Derek Basehore X-Patchwork-Id: 9166157 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id B049960572 for ; Thu, 9 Jun 2016 03:09:41 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A5C022656B for ; Thu, 9 Jun 2016 03:09:41 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 9A58F28294; Thu, 9 Jun 2016 03:09:41 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E73B72656B for ; Thu, 9 Jun 2016 03:09:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933758AbcFIDJW (ORCPT ); Wed, 8 Jun 2016 23:09:22 -0400 Received: from mail-pf0-f177.google.com ([209.85.192.177]:35791 "EHLO mail-pf0-f177.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933863AbcFIDJJ (ORCPT ); Wed, 8 Jun 2016 23:09:09 -0400 Received: by mail-pf0-f177.google.com with SMTP id c2so8685659pfa.2 for ; Wed, 08 Jun 2016 20:09:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=WvPA8VNc8MmlM1xnzLjHr9Jh1ayrSG9zuxxWd6FnzR0=; b=DYOtK+768rs+DQmYWt5yGzPbVukcZVgdZYoOXOXxw5xBvZr9qr3zhpeVJum/M47fRl PGLaTo4u/V+vyc7Q/6+lVjUg21k5zyOzQle2asIYEWhtikCETvmZZhb98/BY/e9gCPhR jHt6lG1pW9e3UNP76qazI3Sneherren0RncUw= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=WvPA8VNc8MmlM1xnzLjHr9Jh1ayrSG9zuxxWd6FnzR0=; b=OwS+6F4dUUd9bp3mM2/4bSl2uGhgSjZflEwJKL/OcRJMthJqiM7lKGR6/AfgDJx9s7 i4MQqhqsQDOgMoTH7dH7sZtF/fSfny59YrZYBPKLWP0PI5+F/sstntqNpkJjFHI1PL6d uGu+OFaDMHzkRn4TetNiYIgXB0eKYUqQHi8c5jUYdXDKGSdnC6hURmrlatBJAxuuH8Hf YKI2cAJdjgNB7j4rh6ahbwfnFTTvA1Qv4zHxdWFSjLQ9I8lAQiWbAYJTy053qRJekF3G 3t6tRmjiEICJySXquPQr9arvuz1NpQcVWKZBjNvzAkqUXGrVpKBtNGVLwRbSQoMpkZCP QkNw== X-Gm-Message-State: ALyK8tISFQwsiEHDfVJ64whb7UWt1/PeSTZ5op13S3nCLcnFEwn8vSjQTfWkT9OxD11sgLQc X-Received: by 10.98.18.131 with SMTP id 3mr2100148pfs.102.1465441748196; Wed, 08 Jun 2016 20:09:08 -0700 (PDT) Received: from ketosis.mtv.corp.google.com ([172.22.65.104]) by smtp.gmail.com with ESMTPSA id b186sm5482186pfa.61.2016.06.08.20.09.07 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 08 Jun 2016 20:09:07 -0700 (PDT) From: dbasehore@chromium.org To: linux-kernel@vger.kernel.org Cc: dbasehore@chromium.org, linux-pm@vger.kernel.org, rjw@rjwysocki.net, pavel@ucw.cz, len.brown@intel.com, tglx@linutronix.de, gnomes@lxorguk.ukuu.org.uk, peterz@infradead.org Subject: [PATCH v3 5/5] intel_idle: Add S0ix validation Date: Wed, 8 Jun 2016 20:08:58 -0700 Message-Id: <1465441738-7972-6-git-send-email-dbasehore@chromium.org> X-Mailer: git-send-email 2.8.0.rc3.226.g39d4020 In-Reply-To: <1465441738-7972-1-git-send-email-dbasehore@chromium.org> References: <1465441738-7972-1-git-send-email-dbasehore@chromium.org> Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Derek Basehore This adds validation of S0ix entry and enables it on Skylake. Using the new timed_freeze function, we program the CPU to wake up X seconds after entering freeze. After X seconds, it will wake the CPU to check the S0ix residency counters and make sure we entered the lowest power state for suspend-to-idle. It exits freeze and reports an error to userspace when the SoC does not enter S0ix on suspend-to-idle. One example of a bug that can prevent a Skylake CPU from entering S0ix (suspend-to-idle) is a leaked reference count to one of the i915 power wells. The CPU will not be able to enter Package C10 and will therefore use about 4x as much power for the entire system. The issue is not specific to the i915 power wells though. Signed-off-by: Derek Basehore --- drivers/idle/intel_idle.c | 153 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 8 deletions(-) diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index 98565de..85dcbc6 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -61,9 +61,11 @@ #include #include #include +#include #include #include #include +#include #define INTEL_IDLE_VERSION "0.4.1" #define PREFIX "intel_idle: " @@ -93,12 +95,36 @@ struct idle_cpu { bool disable_promotion_to_c1e; }; +/* + * The limit for the exponential backoff for the freeze duration. At this point, + * power impact is is far from measurable. An estimate is about 3uW based on + * scaling from waking up 10 times a second. + */ +#define MAX_SLP_S0_SECONDS 1000 +#define SLP_S0_EXP_BASE 10 + +struct timed_freeze_data { + u32 slp_s0_saved_count; + struct cpuidle_device *dev; + struct cpuidle_driver *drv; + int index; +}; + +static bool slp_s0_check; +static unsigned int slp_s0_seconds; + +static DEFINE_SPINLOCK(slp_s0_check_lock); +static unsigned int slp_s0_num_cpus; +static bool slp_s0_check_inprogress; + static const struct idle_cpu *icpu; static struct cpuidle_device __percpu *intel_idle_cpuidle_devices; static int intel_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index); static int intel_idle_freeze(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index); +static int intel_idle_freeze_and_check(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index); static int intel_idle_cpu_init(int cpu); static struct cpuidle_state *cpuidle_state_table; @@ -599,7 +625,7 @@ static struct cpuidle_state skl_cstates[] = { .exit_latency = 2, .target_residency = 2, .enter = &intel_idle, - .enter_freeze = intel_idle_freeze, }, + .enter_freeze = intel_idle_freeze_and_check, }, { .name = "C1E-SKL", .desc = "MWAIT 0x01", @@ -607,7 +633,7 @@ static struct cpuidle_state skl_cstates[] = { .exit_latency = 10, .target_residency = 20, .enter = &intel_idle, - .enter_freeze = intel_idle_freeze, }, + .enter_freeze = intel_idle_freeze_and_check, }, { .name = "C3-SKL", .desc = "MWAIT 0x10", @@ -615,7 +641,7 @@ static struct cpuidle_state skl_cstates[] = { .exit_latency = 70, .target_residency = 100, .enter = &intel_idle, - .enter_freeze = intel_idle_freeze, }, + .enter_freeze = intel_idle_freeze_and_check, }, { .name = "C6-SKL", .desc = "MWAIT 0x20", @@ -623,7 +649,7 @@ static struct cpuidle_state skl_cstates[] = { .exit_latency = 85, .target_residency = 200, .enter = &intel_idle, - .enter_freeze = intel_idle_freeze, }, + .enter_freeze = intel_idle_freeze_and_check, }, { .name = "C7s-SKL", .desc = "MWAIT 0x33", @@ -631,7 +657,7 @@ static struct cpuidle_state skl_cstates[] = { .exit_latency = 124, .target_residency = 800, .enter = &intel_idle, - .enter_freeze = intel_idle_freeze, }, + .enter_freeze = intel_idle_freeze_and_check, }, { .name = "C8-SKL", .desc = "MWAIT 0x40", @@ -639,7 +665,7 @@ static struct cpuidle_state skl_cstates[] = { .exit_latency = 200, .target_residency = 800, .enter = &intel_idle, - .enter_freeze = intel_idle_freeze, }, + .enter_freeze = intel_idle_freeze_and_check, }, { .name = "C9-SKL", .desc = "MWAIT 0x50", @@ -647,7 +673,7 @@ static struct cpuidle_state skl_cstates[] = { .exit_latency = 480, .target_residency = 5000, .enter = &intel_idle, - .enter_freeze = intel_idle_freeze, }, + .enter_freeze = intel_idle_freeze_and_check, }, { .name = "C10-SKL", .desc = "MWAIT 0x60", @@ -655,7 +681,7 @@ static struct cpuidle_state skl_cstates[] = { .exit_latency = 890, .target_residency = 5000, .enter = &intel_idle, - .enter_freeze = intel_idle_freeze, }, + .enter_freeze = intel_idle_freeze_and_check, }, { .enter = NULL } }; @@ -869,6 +895,8 @@ static int intel_idle(struct cpuidle_device *dev, * @dev: cpuidle_device * @drv: cpuidle driver * @index: state index + * + * @return 0 for success, no failure state */ static int intel_idle_freeze(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) @@ -881,6 +909,105 @@ static int intel_idle_freeze(struct cpuidle_device *dev, return 0; } +static int intel_idle_freeze_wrapper(void *data) +{ + struct timed_freeze_data *tfd = data; + + return intel_idle_freeze(tfd->dev, tfd->drv, tfd->index); +} + +static int check_slp_s0(void *data) +{ + struct timed_freeze_data *tfd = data; + u32 slp_s0_new_count; + + if (intel_pmc_slp_s0_counter_read(&slp_s0_new_count)) { + pr_warn("Unable to read SLP S0 residency counter\n"); + return -EIO; + } + + if (tfd->slp_s0_saved_count == slp_s0_new_count) { + pr_warn("CPU did not enter SLP S0 for suspend-to-idle.\n"); + return -EIO; + } + + return 0; +} + +/** + * intel_idle_freeze_and_check - enters suspend-to-idle and validates the power + * state + * + * This function enters suspend-to-idle with intel_idle_freeze, but also sets up + * a timer to check that S0ix (low power state for suspend-to-idle on Intel + * CPUs) is properly entered. + * + * @dev: cpuidle_device + * @drv: cpuidle_driver + * @index: state index + * @return 0 for success, -EERROR if S0ix was not entered. + */ +static int intel_idle_freeze_and_check(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + bool check_on_this_cpu = false; + struct timed_freeze_ops ops; + struct timed_freeze_data tfd; + unsigned long flags; + int ret = 0; + + /* The last CPU to freeze sets up checking SLP S0 assertion. */ + spin_lock_irqsave(&slp_s0_check_lock, flags); + if (slp_s0_seconds && + ++slp_s0_num_cpus == num_online_cpus() && + !slp_s0_check_inprogress && + !intel_pmc_slp_s0_counter_read(&tfd.slp_s0_saved_count)) { + tfd.dev = dev; + tfd.drv = drv; + tfd.index = index; + ops.enter_freeze = intel_idle_freeze_wrapper; + ops.callback = check_slp_s0; + check_on_this_cpu = true; + /* + * Make sure check_slp_s0 isn't scheduled on another CPU if it + * were to leave freeze and enter it again before this CPU + * leaves freeze. + */ + slp_s0_check_inprogress = true; + } + spin_unlock_irqrestore(&slp_s0_check_lock, flags); + + if (check_on_this_cpu) + ret = timed_freeze(&ops, &tfd, ktime_set(slp_s0_seconds, 0)); + else + ret = intel_idle_freeze(dev, drv, index); + + spin_lock_irqsave(&slp_s0_check_lock, flags); + slp_s0_num_cpus--; + if (check_on_this_cpu) { + slp_s0_check_inprogress = false; + slp_s0_seconds = min_t(unsigned int, + SLP_S0_EXP_BASE * slp_s0_seconds, + MAX_SLP_S0_SECONDS); + } + + spin_unlock_irqrestore(&slp_s0_check_lock, flags); + return ret; +} + +static int slp_s0_check_prepare(struct notifier_block *nb, unsigned long action, + void *data) +{ + if (action == PM_SUSPEND_PREPARE) + slp_s0_seconds = slp_s0_check ? 1 : 0; + + return NOTIFY_DONE; +} + +static struct notifier_block intel_slp_s0_check_nb = { + .notifier_call = slp_s0_check_prepare, +}; + static void __setup_broadcast_timer(void *arg) { unsigned long on = (unsigned long)arg; @@ -1391,6 +1518,13 @@ static int __init intel_idle_init(void) return retval; } + retval = register_pm_notifier(&intel_slp_s0_check_nb); + if (retval) { + free_percpu(intel_idle_cpuidle_devices); + cpuidle_unregister_driver(&intel_idle_driver); + return retval; + } + cpu_notifier_register_begin(); for_each_online_cpu(i) { @@ -1398,6 +1532,7 @@ static int __init intel_idle_init(void) if (retval) { intel_idle_cpuidle_devices_uninit(); cpu_notifier_register_done(); + unregister_pm_notifier(&intel_slp_s0_check_nb); cpuidle_unregister_driver(&intel_idle_driver); free_percpu(intel_idle_cpuidle_devices); return retval; @@ -1436,6 +1571,7 @@ static void __exit intel_idle_exit(void) cpu_notifier_register_done(); + unregister_pm_notifier(&intel_slp_s0_check_nb); cpuidle_unregister_driver(&intel_idle_driver); free_percpu(intel_idle_cpuidle_devices); } @@ -1444,6 +1580,7 @@ module_init(intel_idle_init); module_exit(intel_idle_exit); module_param(max_cstate, int, 0444); +module_param(slp_s0_check, bool, 0644); MODULE_AUTHOR("Len Brown "); MODULE_DESCRIPTION("Cpuidle driver for Intel Hardware v" INTEL_IDLE_VERSION);