UPSTREAM: timekeeping: Add a fast and NMI safe boot clock
[firefly-linux-kernel-4.4.55.git] / kernel / power / wakeup_reason.c
index 085c99edca06fac67365cfbb2d3a4e1e414d1a6b..252611fad2fee221c0cee0f3dc563bdd88a4fc3a 100644 (file)
@@ -36,6 +36,11 @@ static char abort_reason[MAX_SUSPEND_ABORT_LEN];
 static struct kobject *wakeup_reason;
 static DEFINE_SPINLOCK(resume_reason_lock);
 
+static ktime_t last_monotime; /* monotonic time before last suspend */
+static ktime_t curr_monotime; /* monotonic time after last suspend */
+static ktime_t last_stime; /* monotonic boottime offset before last suspend */
+static ktime_t curr_stime; /* monotonic boottime offset after last suspend */
+
 static ssize_t last_resume_reason_show(struct kobject *kobj, struct kobj_attribute *attr,
                char *buf)
 {
@@ -59,10 +64,40 @@ static ssize_t last_resume_reason_show(struct kobject *kobj, struct kobj_attribu
        return buf_offset;
 }
 
+static ssize_t last_suspend_time_show(struct kobject *kobj,
+                       struct kobj_attribute *attr, char *buf)
+{
+       struct timespec sleep_time;
+       struct timespec total_time;
+       struct timespec suspend_resume_time;
+
+       /*
+        * total_time is calculated from monotonic bootoffsets because
+        * unlike CLOCK_MONOTONIC it include the time spent in suspend state.
+        */
+       total_time = ktime_to_timespec(ktime_sub(curr_stime, last_stime));
+
+       /*
+        * suspend_resume_time is calculated as monotonic (CLOCK_MONOTONIC)
+        * time interval before entering suspend and post suspend.
+        */
+       suspend_resume_time = ktime_to_timespec(ktime_sub(curr_monotime, last_monotime));
+
+       /* sleep_time = total_time - suspend_resume_time */
+       sleep_time = timespec_sub(total_time, suspend_resume_time);
+
+       /* Export suspend_resume_time and sleep_time in pair here. */
+       return sprintf(buf, "%lu.%09lu %lu.%09lu\n",
+                               suspend_resume_time.tv_sec, suspend_resume_time.tv_nsec,
+                               sleep_time.tv_sec, sleep_time.tv_nsec);
+}
+
 static struct kobj_attribute resume_reason = __ATTR_RO(last_resume_reason);
+static struct kobj_attribute suspend_time = __ATTR_RO(last_suspend_time);
 
 static struct attribute *attrs[] = {
        &resume_reason.attr,
+       &suspend_time.attr,
        NULL,
 };
 static struct attribute_group attr_group = {
@@ -124,7 +159,7 @@ void log_suspend_abort_reason(const char *fmt, ...)
 
        suspend_abort = true;
        va_start(args, fmt);
-       snprintf(abort_reason, MAX_SUSPEND_ABORT_LEN, fmt, args);
+       vsnprintf(abort_reason, MAX_SUSPEND_ABORT_LEN, fmt, args);
        va_end(args);
        spin_unlock(&resume_reason_lock);
 }
@@ -139,6 +174,16 @@ static int wakeup_reason_pm_event(struct notifier_block *notifier,
                irqcount = 0;
                suspend_abort = false;
                spin_unlock(&resume_reason_lock);
+               /* monotonic time since boot */
+               last_monotime = ktime_get();
+               /* monotonic time since boot including the time spent in suspend */
+               last_stime = ktime_get_boottime();
+               break;
+       case PM_POST_SUSPEND:
+               /* monotonic time since boot */
+               curr_monotime = ktime_get();
+               /* monotonic time since boot including the time spent in suspend */
+               curr_stime = ktime_get_boottime();
                break;
        default:
                break;