}
 EXPORT_SYMBOL(file_remove_privs);
 
+/**
+ * current_time - Return FS time (possibly fine-grained)
+ * @inode: inode.
+ *
+ * Return the current time truncated to the time granularity supported by
+ * the fs, as suitable for a ctime/mtime change. If the ctime is flagged
+ * as having been QUERIED, get a fine-grained timestamp, but don't update
+ * the floor.
+ *
+ * For a multigrain inode, this is effectively an estimate of the timestamp
+ * that a file would receive. An actual update must go through
+ * inode_set_ctime_current().
+ */
+struct timespec64 current_time(struct inode *inode)
+{
+       struct timespec64 now;
+       u32 cns;
+
+       ktime_get_coarse_real_ts64_mg(&now);
+
+       if (!is_mgtime(inode))
+               goto out;
+
+       /* If nothing has queried it, then coarse time is fine */
+       cns = smp_load_acquire(&inode->i_ctime_nsec);
+       if (cns & I_CTIME_QUERIED) {
+               /*
+                * If there is no apparent change, then get a fine-grained
+                * timestamp.
+                */
+               if (now.tv_nsec == (cns & ~I_CTIME_QUERIED))
+                       ktime_get_real_ts64(&now);
+       }
+out:
+       return timestamp_truncate(now, inode);
+}
+EXPORT_SYMBOL(current_time);
+
 static int inode_needs_update_time(struct inode *inode)
 {
+       struct timespec64 now, ts;
        int sync_it = 0;
-       struct timespec64 now = current_time(inode);
-       struct timespec64 ts;
 
        /* First try to exhaust all avenues to not sync */
        if (IS_NOCMTIME(inode))
                return 0;
 
+       now = current_time(inode);
+
        ts = inode_get_mtime(inode);
        if (!timespec64_equal(&ts, &now))
-               sync_it = S_MTIME;
+               sync_it |= S_MTIME;
 
        ts = inode_get_ctime(inode);
        if (!timespec64_equal(&ts, &now))
 }
 EXPORT_SYMBOL(inode_nohighmem);
 
+struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts)
+{
+       set_normalized_timespec64(&ts, ts.tv_sec, ts.tv_nsec);
+       inode->i_ctime_sec = ts.tv_sec;
+       inode->i_ctime_nsec = ts.tv_nsec;
+       return ts;
+}
+EXPORT_SYMBOL(inode_set_ctime_to_ts);
+
 /**
  * timestamp_truncate - Truncate timespec to a granularity
  * @t: Timespec
 EXPORT_SYMBOL(timestamp_truncate);
 
 /**
- * current_time - Return FS time
- * @inode: inode.
+ * inode_set_ctime_current - set the ctime to current_time
+ * @inode: inode
  *
- * Return the current time truncated to the time granularity supported by
- * the fs.
+ * Set the inode's ctime to the current value for the inode. Returns the
+ * current value that was assigned. If this is not a multigrain inode, then we
+ * set it to the later of the coarse time and floor value.
  *
- * Note that inode and inode->sb cannot be NULL.
- * Otherwise, the function warns and returns time without truncation.
+ * If it is multigrain, then we first see if the coarse-grained timestamp is
+ * distinct from what is already there. If so, then use that. Otherwise, get a
+ * fine-grained timestamp.
+ *
+ * After that, try to swap the new value into i_ctime_nsec. Accept the
+ * resulting ctime, regardless of the outcome of the swap. If it has
+ * already been replaced, then that timestamp is later than the earlier
+ * unacceptable one, and is thus acceptable.
  */
-struct timespec64 current_time(struct inode *inode)
+struct timespec64 inode_set_ctime_current(struct inode *inode)
 {
        struct timespec64 now;
+       u32 cns, cur;
 
-       ktime_get_coarse_real_ts64(&now);
-       return timestamp_truncate(now, inode);
-}
-EXPORT_SYMBOL(current_time);
+       ktime_get_coarse_real_ts64_mg(&now);
+       now = timestamp_truncate(now, inode);
 
-/**
- * inode_set_ctime_current - set the ctime to current_time
- * @inode: inode
- *
- * Set the inode->i_ctime to the current value for the inode. Returns
- * the current value that was assigned to i_ctime.
- */
-struct timespec64 inode_set_ctime_current(struct inode *inode)
-{
-       struct timespec64 now = current_time(inode);
+       /* Just return that if this is not a multigrain fs */
+       if (!is_mgtime(inode)) {
+               inode_set_ctime_to_ts(inode, now);
+               goto out;
+       }
 
-       inode_set_ctime_to_ts(inode, now);
+       /*
+        * A fine-grained time is only needed if someone has queried
+        * for timestamps, and the current coarse grained time isn't
+        * later than what's already there.
+        */
+       cns = smp_load_acquire(&inode->i_ctime_nsec);
+       if (cns & I_CTIME_QUERIED) {
+               struct timespec64 ctime = { .tv_sec = inode->i_ctime_sec,
+                                           .tv_nsec = cns & ~I_CTIME_QUERIED };
+
+               if (timespec64_compare(&now, &ctime) <= 0) {
+                       ktime_get_real_ts64_mg(&now);
+                       now = timestamp_truncate(now, inode);
+               }
+       }
+
+       /* No need to cmpxchg if it's exactly the same */
+       if (cns == now.tv_nsec && inode->i_ctime_sec == now.tv_sec)
+               goto out;
+       cur = cns;
+retry:
+       /* Try to swap the nsec value into place. */
+       if (try_cmpxchg(&inode->i_ctime_nsec, &cur, now.tv_nsec)) {
+               /* If swap occurred, then we're (mostly) done */
+               inode->i_ctime_sec = now.tv_sec;
+       } else {
+               /*
+                * Was the change due to someone marking the old ctime QUERIED?
+                * If so then retry the swap. This can only happen once since
+                * the only way to clear I_CTIME_QUERIED is to stamp the inode
+                * with a new ctime.
+                */
+               if (!(cns & I_CTIME_QUERIED) && (cns | I_CTIME_QUERIED) == cur) {
+                       cns = cur;
+                       goto retry;
+               }
+               /* Otherwise, keep the existing ctime */
+               now.tv_sec = inode->i_ctime_sec;
+               now.tv_nsec = cur & ~I_CTIME_QUERIED;
+       }
+out:
        return now;
 }
 EXPORT_SYMBOL(inode_set_ctime_current);
 
 #include "internal.h"
 #include "mount.h"
 
+/**
+ * fill_mg_cmtime - Fill in the mtime and ctime and flag ctime as QUERIED
+ * @stat: where to store the resulting values
+ * @request_mask: STATX_* values requested
+ * @inode: inode from which to grab the c/mtime
+ *
+ * Given @inode, grab the ctime and mtime out if it and store the result
+ * in @stat. When fetching the value, flag it as QUERIED (if not already)
+ * so the next write will record a distinct timestamp.
+ *
+ * NB: The QUERIED flag is tracked in the ctime, but we set it there even
+ * if only the mtime was requested, as that ensures that the next mtime
+ * change will be distinct.
+ */
+void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode)
+{
+       atomic_t *pcn = (atomic_t *)&inode->i_ctime_nsec;
+
+       /* If neither time was requested, then don't report them */
+       if (!(request_mask & (STATX_CTIME|STATX_MTIME))) {
+               stat->result_mask &= ~(STATX_CTIME|STATX_MTIME);
+               return;
+       }
+
+       stat->mtime = inode_get_mtime(inode);
+       stat->ctime.tv_sec = inode->i_ctime_sec;
+       stat->ctime.tv_nsec = (u32)atomic_read(pcn);
+       if (!(stat->ctime.tv_nsec & I_CTIME_QUERIED))
+               stat->ctime.tv_nsec = ((u32)atomic_fetch_or(I_CTIME_QUERIED, pcn));
+       stat->ctime.tv_nsec &= ~I_CTIME_QUERIED;
+}
+EXPORT_SYMBOL(fill_mg_cmtime);
+
 /**
  * generic_fillattr - Fill in the basic attributes from the inode struct
  * @idmap:             idmap of the mount the inode was found from
        stat->rdev = inode->i_rdev;
        stat->size = i_size_read(inode);
        stat->atime = inode_get_atime(inode);
-       stat->mtime = inode_get_mtime(inode);
-       stat->ctime = inode_get_ctime(inode);
+
+       if (is_mgtime(inode)) {
+               fill_mg_cmtime(stat, request_mask, inode);
+       } else {
+               stat->ctime = inode_get_ctime(inode);
+               stat->mtime = inode_get_mtime(inode);
+       }
+
        stat->blksize = i_blocksize(inode);
        stat->blocks = inode->i_blocks;
 
 
        return inode_set_mtime_to_ts(inode, ts);
 }
 
+/*
+ * Multigrain timestamps
+ *
+ * Conditionally use fine-grained ctime and mtime timestamps when there
+ * are users actively observing them via getattr. The primary use-case
+ * for this is NFS clients that use the ctime to distinguish between
+ * different states of the file, and that are often fooled by multiple
+ * operations that occur in the same coarse-grained timer tick.
+ */
+#define I_CTIME_QUERIED                ((u32)BIT(31))
+
 static inline time64_t inode_get_ctime_sec(const struct inode *inode)
 {
        return inode->i_ctime_sec;
 
 static inline long inode_get_ctime_nsec(const struct inode *inode)
 {
-       return inode->i_ctime_nsec;
+       return inode->i_ctime_nsec & ~I_CTIME_QUERIED;
 }
 
 static inline struct timespec64 inode_get_ctime(const struct inode *inode)
        return ts;
 }
 
-static inline struct timespec64 inode_set_ctime_to_ts(struct inode *inode,
-                                                     struct timespec64 ts)
-{
-       inode->i_ctime_sec = ts.tv_sec;
-       inode->i_ctime_nsec = ts.tv_nsec;
-       return ts;
-}
+struct timespec64 inode_set_ctime_to_ts(struct inode *inode, struct timespec64 ts);
 
 /**
  * inode_set_ctime - set the ctime in the inode
 #define FS_USERNS_MOUNT                8       /* Can be mounted by userns root */
 #define FS_DISALLOW_NOTIFY_PERM        16      /* Disable fanotify permission events */
 #define FS_ALLOW_IDMAP         32      /* FS has been updated to handle vfs idmappings. */
+#define FS_MGTIME              64      /* FS uses multigrain timestamps */
 #define FS_RENAME_DOES_D_MOVE  32768   /* FS will handle d_move() during rename() internally. */
        int (*init_fs_context)(struct fs_context *);
        const struct fs_parameter_spec *parameters;
 
 #define MODULE_ALIAS_FS(NAME) MODULE_ALIAS("fs-" NAME)
 
+/**
+ * is_mgtime: is this inode using multigrain timestamps
+ * @inode: inode to test for multigrain timestamps
+ *
+ * Return true if the inode uses multigrain timestamps, false otherwise.
+ */
+static inline bool is_mgtime(const struct inode *inode)
+{
+       return inode->i_sb->s_type->fs_flags & FS_MGTIME;
+}
+
 extern struct dentry *mount_bdev(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *data,
        int (*fill_super)(struct super_block *, void *, int));
 extern int page_symlink(struct inode *inode, const char *symname, int len);
 extern const struct inode_operations page_symlink_inode_operations;
 extern void kfree_link(void *);
+void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode);
 void generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *);
 void generic_fill_statx_attr(struct inode *inode, struct kstat *stat);
 void generic_fill_statx_atomic_writes(struct kstat *stat,