Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  huge_memory.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  Copyright (C) 2009  Red Hat, Inc.
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/sched/mm.h>
#include <linux/sched/numa_balancing.h>
#include <linux/highmem.h>
#include <linux/hugetlb.h>
#include <linux/mmu_notifier.h>
#include <linux/rmap.h>
#include <linux/swap.h>
#include <linux/shrinker.h>
#include <linux/mm_inline.h>
#include <linux/swapops.h>
#include <linux/backing-dev.h>
#include <linux/dax.h>
#include <linux/mm_types.h>
#include <linux/khugepaged.h>
#include <linux/freezer.h>
#include <linux/mman.h>
#include <linux/memremap.h>
#include <linux/pagemap.h>
#include <linux/debugfs.h>
#include <linux/migrate.h>
#include <linux/hashtable.h>
#include <linux/userfaultfd_k.h>
#include <linux/page_idle.h>
#include <linux/shmem_fs.h>
#include <linux/oom.h>
#include <linux/numa.h>
#include <linux/page_owner.h>
#include <linux/sched/sysctl.h>
#include <linux/memory-tiers.h>
#include <linux/compat.h>
#include <linux/pgalloc_tag.h>
#include <linux/pagewalk.h>

#include <asm/tlb.h>
#include <asm/pgalloc.h>
#include "internal.h"
#include "swap.h"

#define CREATE_TRACE_POINTS
#include <trace/events/thp.h>

/*
 * By default, transparent hugepage support is disabled in order to avoid
 * risking an increased memory footprint for applications that are not
 * guaranteed to benefit from it. When transparent hugepage support is
 * enabled, it is for all mappings, and khugepaged scans all mappings.
 * Defrag is invoked by khugepaged hugepage allocations and by page faults
 * for all hugepage allocations.
 */

unsigned long transparent_hugepage_flags __read_mostly =
#ifdef CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS
 (1<<TRANSPARENT_HUGEPAGE_FLAG)|
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE_MADVISE
 (1<<TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG)|
#endif
 (1<<TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG)|
 (1<<TRANSPARENT_HUGEPAGE_DEFRAG_KHUGEPAGED_FLAG)|
 (1<<TRANSPARENT_HUGEPAGE_USE_ZERO_PAGE_FLAG);

static struct shrinker *deferred_split_shrinker;
static unsigned long deferred_split_count(struct shrinker *shrink,
       struct shrink_control *sc);
static unsigned long deferred_split_scan(struct shrinker *shrink,
      struct shrink_control *sc);
static bool split_underused_thp = true;

static atomic_t huge_zero_refcount;
struct folio *huge_zero_folio __read_mostly;
unsigned long huge_zero_pfn __read_mostly = ~0UL;
unsigned long huge_anon_orders_always __read_mostly;
unsigned long huge_anon_orders_madvise __read_mostly;
unsigned long huge_anon_orders_inherit __read_mostly;
static bool anon_orders_configured __initdata;

static inline bool file_thp_enabled(struct vm_area_struct *vma)
{
 struct inode *inode;

 if (!IS_ENABLED(CONFIG_READ_ONLY_THP_FOR_FS))
  return false;

 if (!vma->vm_file)
  return false;

 inode = file_inode(vma->vm_file);

 return !inode_is_open_for_write(inode) && S_ISREG(inode->i_mode);
}

unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
      vm_flags_t vm_flags,
      unsigned long tva_flags,
      unsigned long orders)
{
 bool smaps = tva_flags & TVA_SMAPS;
 bool in_pf = tva_flags & TVA_IN_PF;
 bool enforce_sysfs = tva_flags & TVA_ENFORCE_SYSFS;
 unsigned long supported_orders;

 /* Check the intersection of requested and supported orders. */
 if (vma_is_anonymous(vma))
  supported_orders = THP_ORDERS_ALL_ANON;
 else if (vma_is_special_huge(vma))
  supported_orders = THP_ORDERS_ALL_SPECIAL;
 else
  supported_orders = THP_ORDERS_ALL_FILE_DEFAULT;

 orders &= supported_orders;
 if (!orders)
  return 0;

 if (!vma->vm_mm)  /* vdso */
  return 0;

 if (thp_disabled_by_hw() || vma_thp_disabled(vma, vm_flags))
  return 0;

 /* khugepaged doesn't collapse DAX vma, but page fault is fine. */
 if (vma_is_dax(vma))
  return in_pf ? orders : 0;

 /*
 * khugepaged special VMA and hugetlb VMA.
 * Must be checked after dax since some dax mappings may have
 * VM_MIXEDMAP set.
 */

 if (!in_pf && !smaps && (vm_flags & VM_NO_KHUGEPAGED))
  return 0;

 /*
 * Check alignment for file vma and size for both file and anon vma by
 * filtering out the unsuitable orders.
 *
 * Skip the check for page fault. Huge fault does the check in fault
 * handlers.
 */

 if (!in_pf) {
  int order = highest_order(orders);
  unsigned long addr;

  while (orders) {
   addr = vma->vm_end - (PAGE_SIZE << order);
   if (thp_vma_suitable_order(vma, addr, order))
    break;
   order = next_order(&orders, order);
  }

  if (!orders)
   return 0;
 }

 /*
 * Enabled via shmem mount options or sysfs settings.
 * Must be done before hugepage flags check since shmem has its
 * own flags.
 */

 if (!in_pf && shmem_file(vma->vm_file))
  return orders & shmem_allowable_huge_orders(file_inode(vma->vm_file),
         vma, vma->vm_pgoff, 0,
         !enforce_sysfs);

 if (!vma_is_anonymous(vma)) {
  /*
 * Enforce sysfs THP requirements as necessary. Anonymous vmas
 * were already handled in thp_vma_allowable_orders().
 */

  if (enforce_sysfs &&
      (!hugepage_global_enabled() || (!(vm_flags & VM_HUGEPAGE) &&
          !hugepage_global_always())))
   return 0;

  /*
 * Trust that ->huge_fault() handlers know what they are doing
 * in fault path.
 */

  if (((in_pf || smaps)) && vma->vm_ops->huge_fault)
   return orders;
  /* Only regular file is valid in collapse path */
  if (((!in_pf || smaps)) && file_thp_enabled(vma))
   return orders;
  return 0;
 }

 if (vma_is_temporary_stack(vma))
  return 0;

 /*
 * THPeligible bit of smaps should show 1 for proper VMAs even
 * though anon_vma is not initialized yet.
 *
 * Allow page fault since anon_vma may be not initialized until
 * the first page fault.
 */

 if (!vma->anon_vma)
  return (smaps || in_pf) ? orders : 0;

 return orders;
}

static bool get_huge_zero_page(void)
{
 struct folio *zero_folio;
retry:
 if (likely(atomic_inc_not_zero(&huge_zero_refcount)))
  return true;

 zero_folio = folio_alloc((GFP_TRANSHUGE | __GFP_ZERO) & ~__GFP_MOVABLE,
   HPAGE_PMD_ORDER);
 if (!zero_folio) {
  count_vm_event(THP_ZERO_PAGE_ALLOC_FAILED);
  return false;
 }
 /* Ensure zero folio won't have large_rmappable flag set. */
 folio_clear_large_rmappable(zero_folio);
 preempt_disable();
 if (cmpxchg(&huge_zero_folio, NULL, zero_folio)) {
  preempt_enable();
  folio_put(zero_folio);
  goto retry;
 }
 WRITE_ONCE(huge_zero_pfn, folio_pfn(zero_folio));

 /* We take additional reference here. It will be put back by shrinker */
 atomic_set(&huge_zero_refcount, 2);
 preempt_enable();
 count_vm_event(THP_ZERO_PAGE_ALLOC);
 return true;
}

static void put_huge_zero_page(void)
{
 /*
 * Counter should never go to zero here. Only shrinker can put
 * last reference.
 */

 BUG_ON(atomic_dec_and_test(&huge_zero_refcount));
}

struct folio *mm_get_huge_zero_folio(struct mm_struct *mm)
{
 if (test_bit(MMF_HUGE_ZERO_PAGE, &mm->flags))
  return READ_ONCE(huge_zero_folio);

 if (!get_huge_zero_page())
  return NULL;

 if (test_and_set_bit(MMF_HUGE_ZERO_PAGE, &mm->flags))
  put_huge_zero_page();

 return READ_ONCE(huge_zero_folio);
}

void mm_put_huge_zero_folio(struct mm_struct *mm)
{
 if (test_bit(MMF_HUGE_ZERO_PAGE, &mm->flags))
  put_huge_zero_page();
}

static unsigned long shrink_huge_zero_page_count(struct shrinker *shrink,
     struct shrink_control *sc)
{
 /* we can free zero page only if last reference remains */
 return atomic_read(&huge_zero_refcount) == 1 ? HPAGE_PMD_NR : 0;
}

static unsigned long shrink_huge_zero_page_scan(struct shrinker *shrink,
           struct shrink_control *sc)
{
 if (atomic_cmpxchg(&huge_zero_refcount, 1, 0) == 1) {
  struct folio *zero_folio = xchg(&huge_zero_folio, NULL);
  BUG_ON(zero_folio == NULL);
  WRITE_ONCE(huge_zero_pfn, ~0UL);
  folio_put(zero_folio);
  return HPAGE_PMD_NR;
 }

 return 0;
}

static struct shrinker *huge_zero_page_shrinker;

#ifdef CONFIG_SYSFS
static ssize_t enabled_show(struct kobject *kobj,
       struct kobj_attribute *attr, char *buf)
{
 const char *output;

 if (test_bit(TRANSPARENT_HUGEPAGE_FLAG, &transparent_hugepage_flags))
  output = "[always] madvise never";
 else if (test_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG,
     &transparent_hugepage_flags))
  output = "always [madvise] never";
 else
  output = "always madvise [never]";

 return sysfs_emit(buf, "%s\n", output);
}

static ssize_t enabled_store(struct kobject *kobj,
        struct kobj_attribute *attr,
        const char *buf, size_t count)
{
 ssize_t ret = count;

 if (sysfs_streq(buf, "always")) {
  clear_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG, &transparent_hugepage_flags);
  set_bit(TRANSPARENT_HUGEPAGE_FLAG, &transparent_hugepage_flags);
 } else if (sysfs_streq(buf, "madvise")) {
  clear_bit(TRANSPARENT_HUGEPAGE_FLAG, &transparent_hugepage_flags);
  set_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG, &transparent_hugepage_flags);
 } else if (sysfs_streq(buf, "never")) {
  clear_bit(TRANSPARENT_HUGEPAGE_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG, &transparent_hugepage_flags);
 } else
  ret = -EINVAL;

 if (ret > 0) {
  int err = start_stop_khugepaged();
  if (err)
   ret = err;
 }
 return ret;
}

static struct kobj_attribute enabled_attr = __ATTR_RW(enabled);

ssize_t single_hugepage_flag_show(struct kobject *kobj,
      struct kobj_attribute *attr, char *buf,
      enum transparent_hugepage_flag flag)
{
 return sysfs_emit(buf, "%d\n",
     !!test_bit(flag, &transparent_hugepage_flags));
}

ssize_t single_hugepage_flag_store(struct kobject *kobj,
     struct kobj_attribute *attr,
     const char *buf, size_t count,
     enum transparent_hugepage_flag flag)
{
 unsigned long value;
 int ret;

 ret = kstrtoul(buf, 10, &value);
 if (ret < 0)
  return ret;
 if (value > 1)
  return -EINVAL;

 if (value)
  set_bit(flag, &transparent_hugepage_flags);
 else
  clear_bit(flag, &transparent_hugepage_flags);

 return count;
}

static ssize_t defrag_show(struct kobject *kobj,
      struct kobj_attribute *attr, char *buf)
{
 const char *output;

 if (test_bit(TRANSPARENT_HUGEPAGE_DEFRAG_DIRECT_FLAG,
       &transparent_hugepage_flags))
  output = "[always] defer defer+madvise madvise never";
 else if (test_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_FLAG,
     &transparent_hugepage_flags))
  output = "always [defer] defer+madvise madvise never";
 else if (test_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_OR_MADV_FLAG,
     &transparent_hugepage_flags))
  output = "always defer [defer+madvise] madvise never";
 else if (test_bit(TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG,
     &transparent_hugepage_flags))
  output = "always defer defer+madvise [madvise] never";
 else
  output = "always defer defer+madvise madvise [never]";

 return sysfs_emit(buf, "%s\n", output);
}

static ssize_t defrag_store(struct kobject *kobj,
       struct kobj_attribute *attr,
       const char *buf, size_t count)
{
 if (sysfs_streq(buf, "always")) {
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_OR_MADV_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG, &transparent_hugepage_flags);
  set_bit(TRANSPARENT_HUGEPAGE_DEFRAG_DIRECT_FLAG, &transparent_hugepage_flags);
 } else if (sysfs_streq(buf, "defer+madvise")) {
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_DIRECT_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG, &transparent_hugepage_flags);
  set_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_OR_MADV_FLAG, &transparent_hugepage_flags);
 } else if (sysfs_streq(buf, "defer")) {
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_DIRECT_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_OR_MADV_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG, &transparent_hugepage_flags);
  set_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_FLAG, &transparent_hugepage_flags);
 } else if (sysfs_streq(buf, "madvise")) {
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_DIRECT_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_OR_MADV_FLAG, &transparent_hugepage_flags);
  set_bit(TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG, &transparent_hugepage_flags);
 } else if (sysfs_streq(buf, "never")) {
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_DIRECT_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_OR_MADV_FLAG, &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG, &transparent_hugepage_flags);
 } else
  return -EINVAL;

 return count;
}
static struct kobj_attribute defrag_attr = __ATTR_RW(defrag);

static ssize_t use_zero_page_show(struct kobject *kobj,
      struct kobj_attribute *attr, char *buf)
{
 return single_hugepage_flag_show(kobj, attr, buf,
      TRANSPARENT_HUGEPAGE_USE_ZERO_PAGE_FLAG);
}
static ssize_t use_zero_page_store(struct kobject *kobj,
  struct kobj_attribute *attr, const char *buf, size_t count)
{
 return single_hugepage_flag_store(kobj, attr, buf, count,
     TRANSPARENT_HUGEPAGE_USE_ZERO_PAGE_FLAG);
}
static struct kobj_attribute use_zero_page_attr = __ATTR_RW(use_zero_page);

static ssize_t hpage_pmd_size_show(struct kobject *kobj,
       struct kobj_attribute *attr, char *buf)
{
 return sysfs_emit(buf, "%lu\n", HPAGE_PMD_SIZE);
}
static struct kobj_attribute hpage_pmd_size_attr =
 __ATTR_RO(hpage_pmd_size);

static ssize_t split_underused_thp_show(struct kobject *kobj,
       struct kobj_attribute *attr, char *buf)
{
 return sysfs_emit(buf, "%d\n", split_underused_thp);
}

static ssize_t split_underused_thp_store(struct kobject *kobj,
        struct kobj_attribute *attr,
        const char *buf, size_t count)
{
 int err = kstrtobool(buf, &split_underused_thp);

 if (err < 0)
  return err;

 return count;
}

static struct kobj_attribute split_underused_thp_attr = __ATTR(
 shrink_underused, 0644, split_underused_thp_show, split_underused_thp_store);

static struct attribute *hugepage_attr[] = {
 &enabled_attr.attr,
 &defrag_attr.attr,
 &use_zero_page_attr.attr,
 &hpage_pmd_size_attr.attr,
#ifdef CONFIG_SHMEM
 &shmem_enabled_attr.attr,
#endif
 &split_underused_thp_attr.attr,
 NULL,
};

static const struct attribute_group hugepage_attr_group = {
 .attrs = hugepage_attr,
};

static void hugepage_exit_sysfs(struct kobject *hugepage_kobj);
static void thpsize_release(struct kobject *kobj);
static DEFINE_SPINLOCK(huge_anon_orders_lock);
static LIST_HEAD(thpsize_list);

static ssize_t anon_enabled_show(struct kobject *kobj,
     struct kobj_attribute *attr, char *buf)
{
 int order = to_thpsize(kobj)->order;
 const char *output;

 if (test_bit(order, &huge_anon_orders_always))
  output = "[always] inherit madvise never";
 else if (test_bit(order, &huge_anon_orders_inherit))
  output = "always [inherit] madvise never";
 else if (test_bit(order, &huge_anon_orders_madvise))
  output = "always inherit [madvise] never";
 else
  output = "always inherit madvise [never]";

 return sysfs_emit(buf, "%s\n", output);
}

static ssize_t anon_enabled_store(struct kobject *kobj,
      struct kobj_attribute *attr,
      const char *buf, size_t count)
{
 int order = to_thpsize(kobj)->order;
 ssize_t ret = count;

 if (sysfs_streq(buf, "always")) {
  spin_lock(&huge_anon_orders_lock);
  clear_bit(order, &huge_anon_orders_inherit);
  clear_bit(order, &huge_anon_orders_madvise);
  set_bit(order, &huge_anon_orders_always);
  spin_unlock(&huge_anon_orders_lock);
 } else if (sysfs_streq(buf, "inherit")) {
  spin_lock(&huge_anon_orders_lock);
  clear_bit(order, &huge_anon_orders_always);
  clear_bit(order, &huge_anon_orders_madvise);
  set_bit(order, &huge_anon_orders_inherit);
  spin_unlock(&huge_anon_orders_lock);
 } else if (sysfs_streq(buf, "madvise")) {
  spin_lock(&huge_anon_orders_lock);
  clear_bit(order, &huge_anon_orders_always);
  clear_bit(order, &huge_anon_orders_inherit);
  set_bit(order, &huge_anon_orders_madvise);
  spin_unlock(&huge_anon_orders_lock);
 } else if (sysfs_streq(buf, "never")) {
  spin_lock(&huge_anon_orders_lock);
  clear_bit(order, &huge_anon_orders_always);
  clear_bit(order, &huge_anon_orders_inherit);
  clear_bit(order, &huge_anon_orders_madvise);
  spin_unlock(&huge_anon_orders_lock);
 } else
  ret = -EINVAL;

 if (ret > 0) {
  int err;

  err = start_stop_khugepaged();
  if (err)
   ret = err;
 }
 return ret;
}

static struct kobj_attribute anon_enabled_attr =
 __ATTR(enabled, 0644, anon_enabled_show, anon_enabled_store);

static struct attribute *anon_ctrl_attrs[] = {
 &anon_enabled_attr.attr,
 NULL,
};

static const struct attribute_group anon_ctrl_attr_grp = {
 .attrs = anon_ctrl_attrs,
};

static struct attribute *file_ctrl_attrs[] = {
#ifdef CONFIG_SHMEM
 &thpsize_shmem_enabled_attr.attr,
#endif
 NULL,
};

static const struct attribute_group file_ctrl_attr_grp = {
 .attrs = file_ctrl_attrs,
};

static struct attribute *any_ctrl_attrs[] = {
 NULL,
};

static const struct attribute_group any_ctrl_attr_grp = {
 .attrs = any_ctrl_attrs,
};

static const struct kobj_type thpsize_ktype = {
 .release = &thpsize_release,
 .sysfs_ops = &kobj_sysfs_ops,
};

DEFINE_PER_CPU(struct mthp_stat, mthp_stats) = {{{0}}};

static unsigned long sum_mthp_stat(int order, enum mthp_stat_item item)
{
 unsigned long sum = 0;
 int cpu;

 for_each_possible_cpu(cpu) {
  struct mthp_stat *this = &per_cpu(mthp_stats, cpu);

  sum += this->stats[order][item];
 }

 return sum;
}

#define DEFINE_MTHP_STAT_ATTR(_name, _index)    \
static ssize_t _name##_show(struct kobject *kobj,   \
   struct kobj_attribute *attr, char *buf)  \
{         \
 int order = to_thpsize(kobj)->order;    \
         \
 return sysfs_emit(buf, "%lu\n", sum_mthp_stat(order, _index)); \
}         \
static struct kobj_attribute _name##_attr = __ATTR_RO(_name)

DEFINE_MTHP_STAT_ATTR(anon_fault_alloc, MTHP_STAT_ANON_FAULT_ALLOC);
DEFINE_MTHP_STAT_ATTR(anon_fault_fallback, MTHP_STAT_ANON_FAULT_FALLBACK);
DEFINE_MTHP_STAT_ATTR(anon_fault_fallback_charge, MTHP_STAT_ANON_FAULT_FALLBACK_CHARGE);
DEFINE_MTHP_STAT_ATTR(zswpout, MTHP_STAT_ZSWPOUT);
DEFINE_MTHP_STAT_ATTR(swpin, MTHP_STAT_SWPIN);
DEFINE_MTHP_STAT_ATTR(swpin_fallback, MTHP_STAT_SWPIN_FALLBACK);
DEFINE_MTHP_STAT_ATTR(swpin_fallback_charge, MTHP_STAT_SWPIN_FALLBACK_CHARGE);
DEFINE_MTHP_STAT_ATTR(swpout, MTHP_STAT_SWPOUT);
DEFINE_MTHP_STAT_ATTR(swpout_fallback, MTHP_STAT_SWPOUT_FALLBACK);
#ifdef CONFIG_SHMEM
DEFINE_MTHP_STAT_ATTR(shmem_alloc, MTHP_STAT_SHMEM_ALLOC);
DEFINE_MTHP_STAT_ATTR(shmem_fallback, MTHP_STAT_SHMEM_FALLBACK);
DEFINE_MTHP_STAT_ATTR(shmem_fallback_charge, MTHP_STAT_SHMEM_FALLBACK_CHARGE);
#endif
DEFINE_MTHP_STAT_ATTR(split, MTHP_STAT_SPLIT);
DEFINE_MTHP_STAT_ATTR(split_failed, MTHP_STAT_SPLIT_FAILED);
DEFINE_MTHP_STAT_ATTR(split_deferred, MTHP_STAT_SPLIT_DEFERRED);
DEFINE_MTHP_STAT_ATTR(nr_anon, MTHP_STAT_NR_ANON);
DEFINE_MTHP_STAT_ATTR(nr_anon_partially_mapped, MTHP_STAT_NR_ANON_PARTIALLY_MAPPED);

static struct attribute *anon_stats_attrs[] = {
 &anon_fault_alloc_attr.attr,
 &anon_fault_fallback_attr.attr,
 &anon_fault_fallback_charge_attr.attr,
#ifndef CONFIG_SHMEM
 &zswpout_attr.attr,
 &swpin_attr.attr,
 &swpin_fallback_attr.attr,
 &swpin_fallback_charge_attr.attr,
 &swpout_attr.attr,
 &swpout_fallback_attr.attr,
#endif
 &split_deferred_attr.attr,
 &nr_anon_attr.attr,
 &nr_anon_partially_mapped_attr.attr,
 NULL,
};

static struct attribute_group anon_stats_attr_grp = {
 .name = "stats",
 .attrs = anon_stats_attrs,
};

static struct attribute *file_stats_attrs[] = {
#ifdef CONFIG_SHMEM
 &shmem_alloc_attr.attr,
 &shmem_fallback_attr.attr,
 &shmem_fallback_charge_attr.attr,
#endif
 NULL,
};

static struct attribute_group file_stats_attr_grp = {
 .name = "stats",
 .attrs = file_stats_attrs,
};

static struct attribute *any_stats_attrs[] = {
#ifdef CONFIG_SHMEM
 &zswpout_attr.attr,
 &swpin_attr.attr,
 &swpin_fallback_attr.attr,
 &swpin_fallback_charge_attr.attr,
 &swpout_attr.attr,
 &swpout_fallback_attr.attr,
#endif
 &split_attr.attr,
 &split_failed_attr.attr,
 NULL,
};

static struct attribute_group any_stats_attr_grp = {
 .name = "stats",
 .attrs = any_stats_attrs,
};

static int sysfs_add_group(struct kobject *kobj,
      const struct attribute_group *grp)
{
 int ret = -ENOENT;

 /*
 * If the group is named, try to merge first, assuming the subdirectory
 * was already created. This avoids the warning emitted by
 * sysfs_create_group() if the directory already exists.
 */

 if (grp->name)
  ret = sysfs_merge_group(kobj, grp);
 if (ret)
  ret = sysfs_create_group(kobj, grp);

 return ret;
}

static struct thpsize *thpsize_create(int order, struct kobject *parent)
{
 unsigned long size = (PAGE_SIZE << order) / SZ_1K;
 struct thpsize *thpsize;
 int ret = -ENOMEM;

 thpsize = kzalloc(sizeof(*thpsize), GFP_KERNEL);
 if (!thpsize)
  goto err;

 thpsize->order = order;

 ret = kobject_init_and_add(&thpsize->kobj, &thpsize_ktype, parent,
       "hugepages-%lukB", size);
 if (ret) {
  kfree(thpsize);
  goto err;
 }


 ret = sysfs_add_group(&thpsize->kobj, &any_ctrl_attr_grp);
 if (ret)
  goto err_put;

 ret = sysfs_add_group(&thpsize->kobj, &any_stats_attr_grp);
 if (ret)
  goto err_put;

 if (BIT(order) & THP_ORDERS_ALL_ANON) {
  ret = sysfs_add_group(&thpsize->kobj, &anon_ctrl_attr_grp);
  if (ret)
   goto err_put;

  ret = sysfs_add_group(&thpsize->kobj, &anon_stats_attr_grp);
  if (ret)
   goto err_put;
 }

 if (BIT(order) & THP_ORDERS_ALL_FILE_DEFAULT) {
  ret = sysfs_add_group(&thpsize->kobj, &file_ctrl_attr_grp);
  if (ret)
   goto err_put;

  ret = sysfs_add_group(&thpsize->kobj, &file_stats_attr_grp);
  if (ret)
   goto err_put;
 }

 return thpsize;
err_put:
 kobject_put(&thpsize->kobj);
err:
 return ERR_PTR(ret);
}

static void thpsize_release(struct kobject *kobj)
{
 kfree(to_thpsize(kobj));
}

static int __init hugepage_init_sysfs(struct kobject **hugepage_kobj)
{
 int err;
 struct thpsize *thpsize;
 unsigned long orders;
 int order;

 /*
 * Default to setting PMD-sized THP to inherit the global setting and
 * disable all other sizes. powerpc's PMD_ORDER isn't a compile-time
 * constant so we have to do this here.
 */

 if (!anon_orders_configured)
  huge_anon_orders_inherit = BIT(PMD_ORDER);

 *hugepage_kobj = kobject_create_and_add("transparent_hugepage", mm_kobj);
 if (unlikely(!*hugepage_kobj)) {
  pr_err("failed to create transparent hugepage kobject\n");
  return -ENOMEM;
 }

 err = sysfs_create_group(*hugepage_kobj, &hugepage_attr_group);
 if (err) {
  pr_err("failed to register transparent hugepage group\n");
  goto delete_obj;
 }

 err = sysfs_create_group(*hugepage_kobj, &khugepaged_attr_group);
 if (err) {
  pr_err("failed to register transparent hugepage group\n");
  goto remove_hp_group;
 }

 orders = THP_ORDERS_ALL_ANON | THP_ORDERS_ALL_FILE_DEFAULT;
 order = highest_order(orders);
 while (orders) {
  thpsize = thpsize_create(order, *hugepage_kobj);
  if (IS_ERR(thpsize)) {
   pr_err("failed to create thpsize for order %d\n", order);
   err = PTR_ERR(thpsize);
   goto remove_all;
  }
  list_add(&thpsize->node, &thpsize_list);
  order = next_order(&orders, order);
 }

 return 0;

remove_all:
 hugepage_exit_sysfs(*hugepage_kobj);
 return err;
remove_hp_group:
 sysfs_remove_group(*hugepage_kobj, &hugepage_attr_group);
delete_obj:
 kobject_put(*hugepage_kobj);
 return err;
}

static void __init hugepage_exit_sysfs(struct kobject *hugepage_kobj)
{
 struct thpsize *thpsize, *tmp;

 list_for_each_entry_safe(thpsize, tmp, &thpsize_list, node) {
  list_del(&thpsize->node);
  kobject_put(&thpsize->kobj);
 }

 sysfs_remove_group(hugepage_kobj, &khugepaged_attr_group);
 sysfs_remove_group(hugepage_kobj, &hugepage_attr_group);
 kobject_put(hugepage_kobj);
}
#else
static inline int hugepage_init_sysfs(struct kobject **hugepage_kobj)
{
 return 0;
}

static inline void hugepage_exit_sysfs(struct kobject *hugepage_kobj)
{
}
#endif /* CONFIG_SYSFS */

static int __init thp_shrinker_init(void)
{
 huge_zero_page_shrinker = shrinker_alloc(0, "thp-zero");
 if (!huge_zero_page_shrinker)
  return -ENOMEM;

 deferred_split_shrinker = shrinker_alloc(SHRINKER_NUMA_AWARE |
       SHRINKER_MEMCG_AWARE |
       SHRINKER_NONSLAB,
       "thp-deferred_split");
 if (!deferred_split_shrinker) {
  shrinker_free(huge_zero_page_shrinker);
  return -ENOMEM;
 }

 huge_zero_page_shrinker->count_objects = shrink_huge_zero_page_count;
 huge_zero_page_shrinker->scan_objects = shrink_huge_zero_page_scan;
 shrinker_register(huge_zero_page_shrinker);

 deferred_split_shrinker->count_objects = deferred_split_count;
 deferred_split_shrinker->scan_objects = deferred_split_scan;
 shrinker_register(deferred_split_shrinker);

 return 0;
}

static void __init thp_shrinker_exit(void)
{
 shrinker_free(huge_zero_page_shrinker);
 shrinker_free(deferred_split_shrinker);
}

static int __init hugepage_init(void)
{
 int err;
 struct kobject *hugepage_kobj;

 if (!has_transparent_hugepage()) {
  transparent_hugepage_flags = 1 << TRANSPARENT_HUGEPAGE_UNSUPPORTED;
  return -EINVAL;
 }

 /*
 * hugepages can't be allocated by the buddy allocator
 */

 MAYBE_BUILD_BUG_ON(HPAGE_PMD_ORDER > MAX_PAGE_ORDER);

 err = hugepage_init_sysfs(&hugepage_kobj);
 if (err)
  goto err_sysfs;

 err = khugepaged_init();
 if (err)
  goto err_slab;

 err = thp_shrinker_init();
 if (err)
  goto err_shrinker;

 /*
 * By default disable transparent hugepages on smaller systems,
 * where the extra memory used could hurt more than TLB overhead
 * is likely to save.  The admin can still enable it through /sys.
 */

 if (totalram_pages() < (512 << (20 - PAGE_SHIFT))) {
  transparent_hugepage_flags = 0;
  return 0;
 }

 err = start_stop_khugepaged();
 if (err)
  goto err_khugepaged;

 return 0;
err_khugepaged:
 thp_shrinker_exit();
err_shrinker:
 khugepaged_destroy();
err_slab:
 hugepage_exit_sysfs(hugepage_kobj);
err_sysfs:
 return err;
}
subsys_initcall(hugepage_init);

static int __init setup_transparent_hugepage(char *str)
{
 int ret = 0;
 if (!str)
  goto out;
 if (!strcmp(str, "always")) {
  set_bit(TRANSPARENT_HUGEPAGE_FLAG,
   &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG,
     &transparent_hugepage_flags);
  ret = 1;
 } else if (!strcmp(str, "madvise")) {
  clear_bit(TRANSPARENT_HUGEPAGE_FLAG,
     &transparent_hugepage_flags);
  set_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG,
   &transparent_hugepage_flags);
  ret = 1;
 } else if (!strcmp(str, "never")) {
  clear_bit(TRANSPARENT_HUGEPAGE_FLAG,
     &transparent_hugepage_flags);
  clear_bit(TRANSPARENT_HUGEPAGE_REQ_MADV_FLAG,
     &transparent_hugepage_flags);
  ret = 1;
 }
out:
 if (!ret)
  pr_warn("transparent_hugepage= cannot parse, ignored\n");
 return ret;
}
__setup("transparent_hugepage=", setup_transparent_hugepage);

static char str_dup[PAGE_SIZE] __initdata;
static int __init setup_thp_anon(char *str)
{
 char *token, *range, *policy, *subtoken;
 unsigned long always, inherit, madvise;
 char *start_size, *end_size;
 int start, end, nr;
 char *p;

 if (!str || strlen(str) + 1 > PAGE_SIZE)
  goto err;
 strscpy(str_dup, str);

 always = huge_anon_orders_always;
 madvise = huge_anon_orders_madvise;
 inherit = huge_anon_orders_inherit;
 p = str_dup;
 while ((token = strsep(&p, ";")) != NULL) {
  range = strsep(&token, ":");
  policy = token;

  if (!policy)
   goto err;

  while ((subtoken = strsep(&range, ",")) != NULL) {
   if (strchr(subtoken, '-')) {
    start_size = strsep(&subtoken, "-");
    end_size = subtoken;

    start = get_order_from_str(start_size, THP_ORDERS_ALL_ANON);
    end = get_order_from_str(end_size, THP_ORDERS_ALL_ANON);
   } else {
    start_size = end_size = subtoken;
    start = end = get_order_from_str(subtoken,
         THP_ORDERS_ALL_ANON);
   }

   if (start == -EINVAL) {
    pr_err("invalid size %s in thp_anon boot parameter\n", start_size);
    goto err;
   }

   if (end == -EINVAL) {
    pr_err("invalid size %s in thp_anon boot parameter\n", end_size);
    goto err;
   }

   if (start < 0 || end < 0 || start > end)
    goto err;

   nr = end - start + 1;
   if (!strcmp(policy, "always")) {
    bitmap_set(&always, start, nr);
    bitmap_clear(&inherit, start, nr);
    bitmap_clear(&madvise, start, nr);
   } else if (!strcmp(policy, "madvise")) {
    bitmap_set(&madvise, start, nr);
    bitmap_clear(&inherit, start, nr);
    bitmap_clear(&always, start, nr);
   } else if (!strcmp(policy, "inherit")) {
    bitmap_set(&inherit, start, nr);
    bitmap_clear(&madvise, start, nr);
    bitmap_clear(&always, start, nr);
   } else if (!strcmp(policy, "never")) {
    bitmap_clear(&inherit, start, nr);
    bitmap_clear(&madvise, start, nr);
    bitmap_clear(&always, start, nr);
   } else {
    pr_err("invalid policy %s in thp_anon boot parameter\n", policy);
    goto err;
   }
  }
 }

 huge_anon_orders_always = always;
 huge_anon_orders_madvise = madvise;
 huge_anon_orders_inherit = inherit;
 anon_orders_configured = true;
 return 1;

err:
 pr_warn("thp_anon=%s: error parsing string, ignoring setting\n", str);
 return 0;
}
__setup("thp_anon=", setup_thp_anon);

pmd_t maybe_pmd_mkwrite(pmd_t pmd, struct vm_area_struct *vma)
{
 if (likely(vma->vm_flags & VM_WRITE))
  pmd = pmd_mkwrite(pmd, vma);
 return pmd;
}

#ifdef CONFIG_MEMCG
static inline
struct deferred_split *get_deferred_split_queue(struct folio *folio)
{
 struct mem_cgroup *memcg = folio_memcg(folio);
 struct pglist_data *pgdat = NODE_DATA(folio_nid(folio));

 if (memcg)
  return &memcg->deferred_split_queue;
 else
  return &pgdat->deferred_split_queue;
}
#else
static inline
struct deferred_split *get_deferred_split_queue(struct folio *folio)
{
 struct pglist_data *pgdat = NODE_DATA(folio_nid(folio));

 return &pgdat->deferred_split_queue;
}
#endif

static inline bool is_transparent_hugepage(const struct folio *folio)
{
 if (!folio_test_large(folio))
  return false;

 return is_huge_zero_folio(folio) ||
  folio_test_large_rmappable(folio);
}

static unsigned long __thp_get_unmapped_area(struct file *filp,
  unsigned long addr, unsigned long len,
  loff_t off, unsigned long flags, unsigned long size,
  vm_flags_t vm_flags)
{
 loff_t off_end = off + len;
 loff_t off_align = round_up(off, size);
 unsigned long len_pad, ret, off_sub;

 if (!IS_ENABLED(CONFIG_64BIT) || in_compat_syscall())
  return 0;

 if (off_end <= off_align || (off_end - off_align) < size)
  return 0;

 len_pad = len + size;
 if (len_pad < len || (off + len_pad) < off)
  return 0;

 ret = mm_get_unmapped_area_vmflags(current->mm, filp, addr, len_pad,
        off >> PAGE_SHIFT, flags, vm_flags);

 /*
 * The failure might be due to length padding. The caller will retry
 * without the padding.
 */

 if (IS_ERR_VALUE(ret))
  return 0;

 /*
 * Do not try to align to THP boundary if allocation at the address
 * hint succeeds.
 */

 if (ret == addr)
  return addr;

 off_sub = (off - ret) & (size - 1);

 if (test_bit(MMF_TOPDOWN, ¤t->mm->flags) && !off_sub)
  return ret + size;

 ret += off_sub;
 return ret;
}

unsigned long thp_get_unmapped_area_vmflags(struct file *filp, unsigned long addr,
  unsigned long len, unsigned long pgoff, unsigned long flags,
  vm_flags_t vm_flags)
{
 unsigned long ret;
 loff_t off = (loff_t)pgoff << PAGE_SHIFT;

 ret = __thp_get_unmapped_area(filp, addr, len, off, flags, PMD_SIZE, vm_flags);
 if (ret)
  return ret;

 return mm_get_unmapped_area_vmflags(current->mm, filp, addr, len, pgoff, flags,
         vm_flags);
}

unsigned long thp_get_unmapped_area(struct file *filp, unsigned long addr,
  unsigned long len, unsigned long pgoff, unsigned long flags)
{
 return thp_get_unmapped_area_vmflags(filp, addr, len, pgoff, flags, 0);
}
EXPORT_SYMBOL_GPL(thp_get_unmapped_area);

static struct folio *vma_alloc_anon_folio_pmd(struct vm_area_struct *vma,
  unsigned long addr)
{
 gfp_t gfp = vma_thp_gfp_mask(vma);
 const int order = HPAGE_PMD_ORDER;
 struct folio *folio;

 folio = vma_alloc_folio(gfp, order, vma, addr & HPAGE_PMD_MASK);

 if (unlikely(!folio)) {
  count_vm_event(THP_FAULT_FALLBACK);
  count_mthp_stat(order, MTHP_STAT_ANON_FAULT_FALLBACK);
  return NULL;
 }

 VM_BUG_ON_FOLIO(!folio_test_large(folio), folio);
 if (mem_cgroup_charge(folio, vma->vm_mm, gfp)) {
  folio_put(folio);
  count_vm_event(THP_FAULT_FALLBACK);
  count_vm_event(THP_FAULT_FALLBACK_CHARGE);
  count_mthp_stat(order, MTHP_STAT_ANON_FAULT_FALLBACK);
  count_mthp_stat(order, MTHP_STAT_ANON_FAULT_FALLBACK_CHARGE);
  return NULL;
 }
 folio_throttle_swaprate(folio, gfp);

       /*
* When a folio is not zeroed during allocation (__GFP_ZERO not used)
* or user folios require special handling, folio_zero_user() is used to
* make sure that the page corresponding to the faulting address will be
* hot in the cache after zeroing.
*/

 if (user_alloc_needs_zeroing())
  folio_zero_user(folio, addr);
 /*
 * The memory barrier inside __folio_mark_uptodate makes sure that
 * folio_zero_user writes become visible before the set_pmd_at()
 * write.
 */

 __folio_mark_uptodate(folio);
 return folio;
}

static void map_anon_folio_pmd(struct folio *folio, pmd_t *pmd,
  struct vm_area_struct *vma, unsigned long haddr)
{
 pmd_t entry;

 entry = folio_mk_pmd(folio, vma->vm_page_prot);
 entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
 folio_add_new_anon_rmap(folio, vma, haddr, RMAP_EXCLUSIVE);
 folio_add_lru_vma(folio, vma);
 set_pmd_at(vma->vm_mm, haddr, pmd, entry);
 update_mmu_cache_pmd(vma, haddr, pmd);
 add_mm_counter(vma->vm_mm, MM_ANONPAGES, HPAGE_PMD_NR);
 count_vm_event(THP_FAULT_ALLOC);
 count_mthp_stat(HPAGE_PMD_ORDER, MTHP_STAT_ANON_FAULT_ALLOC);
 count_memcg_event_mm(vma->vm_mm, THP_FAULT_ALLOC);
}

static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf)
{
 unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
 struct vm_area_struct *vma = vmf->vma;
 struct folio *folio;
 pgtable_t pgtable;
 vm_fault_t ret = 0;

 folio = vma_alloc_anon_folio_pmd(vma, vmf->address);
 if (unlikely(!folio))
  return VM_FAULT_FALLBACK;

 pgtable = pte_alloc_one(vma->vm_mm);
 if (unlikely(!pgtable)) {
  ret = VM_FAULT_OOM;
  goto release;
 }

 vmf->ptl = pmd_lock(vma->vm_mm, vmf->pmd);
 if (unlikely(!pmd_none(*vmf->pmd))) {
  goto unlock_release;
 } else {
  ret = check_stable_address_space(vma->vm_mm);
  if (ret)
   goto unlock_release;

  /* Deliver the page fault to userland */
  if (userfaultfd_missing(vma)) {
   spin_unlock(vmf->ptl);
   folio_put(folio);
   pte_free(vma->vm_mm, pgtable);
   ret = handle_userfault(vmf, VM_UFFD_MISSING);
   VM_BUG_ON(ret & VM_FAULT_FALLBACK);
   return ret;
  }
  pgtable_trans_huge_deposit(vma->vm_mm, vmf->pmd, pgtable);
  map_anon_folio_pmd(folio, vmf->pmd, vma, haddr);
  mm_inc_nr_ptes(vma->vm_mm);
  deferred_split_folio(folio, false);
  spin_unlock(vmf->ptl);
 }

 return 0;
unlock_release:
 spin_unlock(vmf->ptl);
release:
 if (pgtable)
  pte_free(vma->vm_mm, pgtable);
 folio_put(folio);
 return ret;

}

/*
 * always: directly stall for all thp allocations
 * defer: wake kswapd and fail if not immediately available
 * defer+madvise: wake kswapd and directly stall for MADV_HUGEPAGE, otherwise
 *   fail if not immediately available
 * madvise: directly stall for MADV_HUGEPAGE, otherwise fail if not immediately
 *     available
 * never: never stall for any thp allocation
 */

gfp_t vma_thp_gfp_mask(struct vm_area_struct *vma)
{
 const bool vma_madvised = vma && (vma->vm_flags & VM_HUGEPAGE);

 /* Always do synchronous compaction */
 if (test_bit(TRANSPARENT_HUGEPAGE_DEFRAG_DIRECT_FLAG, &transparent_hugepage_flags))
  return GFP_TRANSHUGE | (vma_madvised ? 0 : __GFP_NORETRY);

 /* Kick kcompactd and fail quickly */
 if (test_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_FLAG, &transparent_hugepage_flags))
  return GFP_TRANSHUGE_LIGHT | __GFP_KSWAPD_RECLAIM;

 /* Synchronous compaction if madvised, otherwise kick kcompactd */
 if (test_bit(TRANSPARENT_HUGEPAGE_DEFRAG_KSWAPD_OR_MADV_FLAG, &transparent_hugepage_flags))
  return GFP_TRANSHUGE_LIGHT |
   (vma_madvised ? __GFP_DIRECT_RECLAIM :
     __GFP_KSWAPD_RECLAIM);

 /* Only do synchronous compaction if madvised */
 if (test_bit(TRANSPARENT_HUGEPAGE_DEFRAG_REQ_MADV_FLAG, &transparent_hugepage_flags))
  return GFP_TRANSHUGE_LIGHT |
         (vma_madvised ? __GFP_DIRECT_RECLAIM : 0);

 return GFP_TRANSHUGE_LIGHT;
}

/* Caller must hold page table lock. */
static void set_huge_zero_folio(pgtable_t pgtable, struct mm_struct *mm,
  struct vm_area_struct *vma, unsigned long haddr, pmd_t *pmd,
  struct folio *zero_folio)
{
 pmd_t entry;
 entry = folio_mk_pmd(zero_folio, vma->vm_page_prot);
 pgtable_trans_huge_deposit(mm, pmd, pgtable);
 set_pmd_at(mm, haddr, pmd, entry);
 mm_inc_nr_ptes(mm);
}

vm_fault_t do_huge_pmd_anonymous_page(struct vm_fault *vmf)
{
 struct vm_area_struct *vma = vmf->vma;
 unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
 vm_fault_t ret;

 if (!thp_vma_suitable_order(vma, haddr, PMD_ORDER))
  return VM_FAULT_FALLBACK;
 ret = vmf_anon_prepare(vmf);
 if (ret)
  return ret;
 khugepaged_enter_vma(vma, vma->vm_flags);

 if (!(vmf->flags & FAULT_FLAG_WRITE) &&
   !mm_forbids_zeropage(vma->vm_mm) &&
   transparent_hugepage_use_zero_page()) {
  pgtable_t pgtable;
  struct folio *zero_folio;
  vm_fault_t ret;

  pgtable = pte_alloc_one(vma->vm_mm);
  if (unlikely(!pgtable))
   return VM_FAULT_OOM;
  zero_folio = mm_get_huge_zero_folio(vma->vm_mm);
  if (unlikely(!zero_folio)) {
   pte_free(vma->vm_mm, pgtable);
   count_vm_event(THP_FAULT_FALLBACK);
   return VM_FAULT_FALLBACK;
  }
  vmf->ptl = pmd_lock(vma->vm_mm, vmf->pmd);
  ret = 0;
  if (pmd_none(*vmf->pmd)) {
   ret = check_stable_address_space(vma->vm_mm);
   if (ret) {
    spin_unlock(vmf->ptl);
    pte_free(vma->vm_mm, pgtable);
   } else if (userfaultfd_missing(vma)) {
    spin_unlock(vmf->ptl);
    pte_free(vma->vm_mm, pgtable);
    ret = handle_userfault(vmf, VM_UFFD_MISSING);
    VM_BUG_ON(ret & VM_FAULT_FALLBACK);
   } else {
    set_huge_zero_folio(pgtable, vma->vm_mm, vma,
         haddr, vmf->pmd, zero_folio);
    update_mmu_cache_pmd(vma, vmf->address, vmf->pmd);
    spin_unlock(vmf->ptl);
   }
  } else {
   spin_unlock(vmf->ptl);
   pte_free(vma->vm_mm, pgtable);
  }
  return ret;
 }

 return __do_huge_pmd_anonymous_page(vmf);
}

struct folio_or_pfn {
 union {
  struct folio *folio;
  unsigned long pfn;
 };
 bool is_folio;
};

static int insert_pmd(struct vm_area_struct *vma, unsigned long addr,
  pmd_t *pmd, struct folio_or_pfn fop, pgprot_t prot,
  bool write, pgtable_t pgtable)
{
 struct mm_struct *mm = vma->vm_mm;
 pmd_t entry;

 lockdep_assert_held(pmd_lockptr(mm, pmd));

 if (!pmd_none(*pmd)) {
  const unsigned long pfn = fop.is_folio ? folio_pfn(fop.folio) :
       fop.pfn;

  if (write) {
   if (pmd_pfn(*pmd) != pfn) {
    WARN_ON_ONCE(!is_huge_zero_pmd(*pmd));
    return -EEXIST;
   }
   entry = pmd_mkyoung(*pmd);
   entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
   if (pmdp_set_access_flags(vma, addr, pmd, entry, 1))
    update_mmu_cache_pmd(vma, addr, pmd);
  }

  return -EEXIST;
 }

 if (fop.is_folio) {
  entry = folio_mk_pmd(fop.folio, vma->vm_page_prot);

  folio_get(fop.folio);
  folio_add_file_rmap_pmd(fop.folio, &fop.folio->page, vma);
  add_mm_counter(mm, mm_counter_file(fop.folio), HPAGE_PMD_NR);
 } else {
  entry = pmd_mkhuge(pfn_pmd(fop.pfn, prot));
  entry = pmd_mkspecial(entry);
 }
 if (write) {
  entry = pmd_mkyoung(pmd_mkdirty(entry));
  entry = maybe_pmd_mkwrite(entry, vma);
 }

 if (pgtable) {
  pgtable_trans_huge_deposit(mm, pmd, pgtable);
  mm_inc_nr_ptes(mm);
 }

 set_pmd_at(mm, addr, pmd, entry);
 update_mmu_cache_pmd(vma, addr, pmd);
 return 0;
}

/**
 * vmf_insert_pfn_pmd - insert a pmd size pfn
 * @vmf: Structure describing the fault
 * @pfn: pfn to insert
 * @write: whether it's a write fault
 *
 * Insert a pmd size pfn. See vmf_insert_pfn() for additional info.
 *
 * Return: vm_fault_t value.
 */

vm_fault_t vmf_insert_pfn_pmd(struct vm_fault *vmf, unsigned long pfn,
         bool write)
{
 unsigned long addr = vmf->address & PMD_MASK;
 struct vm_area_struct *vma = vmf->vma;
 pgprot_t pgprot = vma->vm_page_prot;
 struct folio_or_pfn fop = {
  .pfn = pfn,
 };
 pgtable_t pgtable = NULL;
 spinlock_t *ptl;
 int error;

 /*
 * If we had pmd_special, we could avoid all these restrictions,
 * but we need to be consistent with PTEs and architectures that
 * can't support a 'special' bit.
 */

 BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)));
 BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) ==
      (VM_PFNMAP|VM_MIXEDMAP));
 BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));

 if (addr < vma->vm_start || addr >= vma->vm_end)
  return VM_FAULT_SIGBUS;

 if (arch_needs_pgtable_deposit()) {
  pgtable = pte_alloc_one(vma->vm_mm);
  if (!pgtable)
   return VM_FAULT_OOM;
 }

 pfnmap_setup_cachemode_pfn(pfn, &pgprot);

 ptl = pmd_lock(vma->vm_mm, vmf->pmd);
 error = insert_pmd(vma, addr, vmf->pmd, fop, pgprot, write,
      pgtable);
 spin_unlock(ptl);
 if (error && pgtable)
  pte_free(vma->vm_mm, pgtable);

 return VM_FAULT_NOPAGE;
}
EXPORT_SYMBOL_GPL(vmf_insert_pfn_pmd);

vm_fault_t vmf_insert_folio_pmd(struct vm_fault *vmf, struct folio *folio,
    bool write)
{
 struct vm_area_struct *vma = vmf->vma;
 unsigned long addr = vmf->address & PMD_MASK;
 struct mm_struct *mm = vma->vm_mm;
 struct folio_or_pfn fop = {
  .folio = folio,
  .is_folio = true,
 };
 spinlock_t *ptl;
 pgtable_t pgtable = NULL;
 int error;

 if (addr < vma->vm_start || addr >= vma->vm_end)
  return VM_FAULT_SIGBUS;

 if (WARN_ON_ONCE(folio_order(folio) != PMD_ORDER))
  return VM_FAULT_SIGBUS;

 if (arch_needs_pgtable_deposit()) {
  pgtable = pte_alloc_one(vma->vm_mm);
  if (!pgtable)
   return VM_FAULT_OOM;
 }

 ptl = pmd_lock(mm, vmf->pmd);
 error = insert_pmd(vma, addr, vmf->pmd, fop, vma->vm_page_prot,
      write, pgtable);
 spin_unlock(ptl);
 if (error && pgtable)
  pte_free(mm, pgtable);

 return VM_FAULT_NOPAGE;
}
EXPORT_SYMBOL_GPL(vmf_insert_folio_pmd);

#ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD
static pud_t maybe_pud_mkwrite(pud_t pud, struct vm_area_struct *vma)
{
 if (likely(vma->vm_flags & VM_WRITE))
  pud = pud_mkwrite(pud);
 return pud;
}

static void insert_pud(struct vm_area_struct *vma, unsigned long addr,
  pud_t *pud, struct folio_or_pfn fop, pgprot_t prot, bool write)
{
 struct mm_struct *mm = vma->vm_mm;
 pud_t entry;

 if (!pud_none(*pud)) {
  const unsigned long pfn = fop.is_folio ? folio_pfn(fop.folio) :
       fop.pfn;

  if (write) {
   if (WARN_ON_ONCE(pud_pfn(*pud) != pfn))
    return;
   entry = pud_mkyoung(*pud);
   entry = maybe_pud_mkwrite(pud_mkdirty(entry), vma);
   if (pudp_set_access_flags(vma, addr, pud, entry, 1))
    update_mmu_cache_pud(vma, addr, pud);
  }
  return;
 }

 if (fop.is_folio) {
  entry = folio_mk_pud(fop.folio, vma->vm_page_prot);

  folio_get(fop.folio);
  folio_add_file_rmap_pud(fop.folio, &fop.folio->page, vma);
  add_mm_counter(mm, mm_counter_file(fop.folio), HPAGE_PUD_NR);
 } else {
  entry = pud_mkhuge(pfn_pud(fop.pfn, prot));
  entry = pud_mkspecial(entry);
 }
 if (write) {
  entry = pud_mkyoung(pud_mkdirty(entry));
  entry = maybe_pud_mkwrite(entry, vma);
 }
 set_pud_at(mm, addr, pud, entry);
 update_mmu_cache_pud(vma, addr, pud);
}

/**
 * vmf_insert_pfn_pud - insert a pud size pfn
 * @vmf: Structure describing the fault
 * @pfn: pfn to insert
 * @write: whether it's a write fault
 *
 * Insert a pud size pfn. See vmf_insert_pfn() for additional info.
 *
 * Return: vm_fault_t value.
 */

vm_fault_t vmf_insert_pfn_pud(struct vm_fault *vmf, unsigned long pfn,
         bool write)
{
 unsigned long addr = vmf->address & PUD_MASK;
 struct vm_area_struct *vma = vmf->vma;
 pgprot_t pgprot = vma->vm_page_prot;
 struct folio_or_pfn fop = {
  .pfn = pfn,
 };
 spinlock_t *ptl;

 /*
 * If we had pud_special, we could avoid all these restrictions,
 * but we need to be consistent with PTEs and architectures that
 * can't support a 'special' bit.
 */

 BUG_ON(!(vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)));
 BUG_ON((vma->vm_flags & (VM_PFNMAP|VM_MIXEDMAP)) ==
      (VM_PFNMAP|VM_MIXEDMAP));
 BUG_ON((vma->vm_flags & VM_PFNMAP) && is_cow_mapping(vma->vm_flags));

 if (addr < vma->vm_start || addr >= vma->vm_end)
  return VM_FAULT_SIGBUS;

 pfnmap_setup_cachemode_pfn(pfn, &pgprot);

 ptl = pud_lock(vma->vm_mm, vmf->pud);
 insert_pud(vma, addr, vmf->pud, fop, pgprot, write);
 spin_unlock(ptl);

 return VM_FAULT_NOPAGE;
}
EXPORT_SYMBOL_GPL(vmf_insert_pfn_pud);

/**
 * vmf_insert_folio_pud - insert a pud size folio mapped by a pud entry
 * @vmf: Structure describing the fault
 * @folio: folio to insert
 * @write: whether it's a write fault
 *
 * Return: vm_fault_t value.
 */

vm_fault_t vmf_insert_folio_pud(struct vm_fault *vmf, struct folio *folio,
    bool write)
{
 struct vm_area_struct *vma = vmf->vma;
 unsigned long addr = vmf->address & PUD_MASK;
 pud_t *pud = vmf->pud;
 struct mm_struct *mm = vma->vm_mm;
 struct folio_or_pfn fop = {
  .folio = folio,
  .is_folio = true,
 };
 spinlock_t *ptl;

 if (addr < vma->vm_start || addr >= vma->vm_end)
  return VM_FAULT_SIGBUS;

 if (WARN_ON_ONCE(folio_order(folio) != PUD_ORDER))
  return VM_FAULT_SIGBUS;

 ptl = pud_lock(mm, pud);
 insert_pud(vma, addr, vmf->pud, fop, vma->vm_page_prot, write);
 spin_unlock(ptl);

 return VM_FAULT_NOPAGE;
}
EXPORT_SYMBOL_GPL(vmf_insert_folio_pud);
#endif /* CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD */

void touch_pmd(struct vm_area_struct *vma, unsigned long addr,
        pmd_t *pmd, bool write)
{
 pmd_t _pmd;

 _pmd = pmd_mkyoung(*pmd);
 if (write)
  _pmd = pmd_mkdirty(_pmd);
 if (pmdp_set_access_flags(vma, addr & HPAGE_PMD_MASK,
      pmd, _pmd, write))
  update_mmu_cache_pmd(vma, addr, pmd);
}

int copy_huge_pmd(struct mm_struct *dst_mm, struct mm_struct *src_mm,
    pmd_t *dst_pmd, pmd_t *src_pmd, unsigned long addr,
    struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma)
{
 spinlock_t *dst_ptl, *src_ptl;
 struct page *src_page;
 struct folio *src_folio;
 pmd_t pmd;
 pgtable_t pgtable = NULL;
 int ret = -ENOMEM;

 pmd = pmdp_get_lockless(src_pmd);
 if (unlikely(pmd_present(pmd) && pmd_special(pmd))) {
  dst_ptl = pmd_lock(dst_mm, dst_pmd);
  src_ptl = pmd_lockptr(src_mm, src_pmd);
  spin_lock_nested(src_ptl, SINGLE_DEPTH_NESTING);
  /*
 * No need to recheck the pmd, it can't change with write
 * mmap lock held here.
 *
 * Meanwhile, making sure it's not a CoW VMA with writable
 * mapping, otherwise it means either the anon page wrongly
 * applied special bit, or we made the PRIVATE mapping be
 * able to wrongly write to the backend MMIO.
 */

  VM_WARN_ON_ONCE(is_cow_mapping(src_vma->vm_flags) && pmd_write(pmd));
  goto set_pmd;
 }

 /* Skip if can be re-fill on fault */
 if (!vma_is_anonymous(dst_vma))
  return 0;

 pgtable = pte_alloc_one(dst_mm);
 if (unlikely(!pgtable))
  goto out;

 dst_ptl = pmd_lock(dst_mm, dst_pmd);
 src_ptl = pmd_lockptr(src_mm, src_pmd);
 spin_lock_nested(src_ptl, SINGLE_DEPTH_NESTING);

 ret = -EAGAIN;
 pmd = *src_pmd;

#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION
 if (unlikely(is_swap_pmd(pmd))) {
  swp_entry_t entry = pmd_to_swp_entry(pmd);

  VM_BUG_ON(!is_pmd_migration_entry(pmd));
  if (!is_readable_migration_entry(entry)) {
   entry = make_readable_migration_entry(
       swp_offset(entry));
   pmd = swp_entry_to_pmd(entry);
   if (pmd_swp_soft_dirty(*src_pmd))
    pmd = pmd_swp_mksoft_dirty(pmd);
   if (pmd_swp_uffd_wp(*src_pmd))
    pmd = pmd_swp_mkuffd_wp(pmd);
   set_pmd_at(src_mm, addr, src_pmd, pmd);
  }
  add_mm_counter(dst_mm, MM_ANONPAGES, HPAGE_PMD_NR);
  mm_inc_nr_ptes(dst_mm);
  pgtable_trans_huge_deposit(dst_mm, dst_pmd, pgtable);
  if (!userfaultfd_wp(dst_vma))
   pmd = pmd_swp_clear_uffd_wp(pmd);
  set_pmd_at(dst_mm, addr, dst_pmd, pmd);
  ret = 0;
  goto out_unlock;
 }
#endif

 if (unlikely(!pmd_trans_huge(pmd))) {
  pte_free(dst_mm, pgtable);
  goto out_unlock;
 }
 /*
 * When page table lock is held, the huge zero pmd should not be
 * under splitting since we don't split the page itself, only pmd to
 * a page table.
 */

 if (is_huge_zero_pmd(pmd)) {
  /*
 * mm_get_huge_zero_folio() will never allocate a new
 * folio here, since we already have a zero page to
 * copy. It just takes a reference.
 */

  mm_get_huge_zero_folio(dst_mm);
  goto out_zero_page;
 }

 src_page = pmd_page(pmd);
 VM_BUG_ON_PAGE(!PageHead(src_page), src_page);
 src_folio = page_folio(src_page);

 folio_get(src_folio);
 if (unlikely(folio_try_dup_anon_rmap_pmd(src_folio, src_page, dst_vma, src_vma))) {
  /* Page maybe pinned: split and retry the fault on PTEs. */
  folio_put(src_folio);
  pte_free(dst_mm, pgtable);
  spin_unlock(src_ptl);
  spin_unlock(dst_ptl);
  __split_huge_pmd(src_vma, src_pmd, addr, false);
  return -EAGAIN;
 }
 add_mm_counter(dst_mm, MM_ANONPAGES, HPAGE_PMD_NR);
out_zero_page:
 mm_inc_nr_ptes(dst_mm);
 pgtable_trans_huge_deposit(dst_mm, dst_pmd, pgtable);
 pmdp_set_wrprotect(src_mm, addr, src_pmd);
 if (!userfaultfd_wp(dst_vma))
  pmd = pmd_clear_uffd_wp(pmd);
 pmd = pmd_wrprotect(pmd);
set_pmd:
 pmd = pmd_mkold(pmd);
 set_pmd_at(dst_mm, addr, dst_pmd, pmd);

 ret = 0;
out_unlock:
 spin_unlock(src_ptl);
 spin_unlock(dst_ptl);
out:
 return ret;
}

#ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD
void touch_pud(struct vm_area_struct *vma, unsigned long addr,
        pud_t *pud, bool write)
{
 pud_t _pud;

 _pud = pud_mkyoung(*pud);
 if (write)
  _pud = pud_mkdirty(_pud);
 if (pudp_set_access_flags(vma, addr & HPAGE_PUD_MASK,
      pud, _pud, write))
  update_mmu_cache_pud(vma, addr, pud);
}

int copy_huge_pud(struct mm_struct *dst_mm, struct mm_struct *src_mm,
    pud_t *dst_pud, pud_t *src_pud, unsigned long addr,
    struct vm_area_struct *vma)
{
 spinlock_t *dst_ptl, *src_ptl;
 pud_t pud;
 int ret;

 dst_ptl = pud_lock(dst_mm, dst_pud);
 src_ptl = pud_lockptr(src_mm, src_pud);
 spin_lock_nested(src_ptl, SINGLE_DEPTH_NESTING);

 ret = -EAGAIN;
 pud = *src_pud;
 if (unlikely(!pud_trans_huge(pud)))
  goto out_unlock;

 /*
 * TODO: once we support anonymous pages, use
 * folio_try_dup_anon_rmap_*() and split if duplicating fails.
 */

 if (is_cow_mapping(vma->vm_flags) && pud_write(pud)) {
  pudp_set_wrprotect(src_mm, addr, src_pud);
  pud = pud_wrprotect(pud);
 }
 pud = pud_mkold(pud);
 set_pud_at(dst_mm, addr, dst_pud, pud);

 ret = 0;
out_unlock:
 spin_unlock(src_ptl);
 spin_unlock(dst_ptl);
 return ret;
}

void huge_pud_set_accessed(struct vm_fault *vmf, pud_t orig_pud)
{
 bool write = vmf->flags & FAULT_FLAG_WRITE;

 vmf->ptl = pud_lock(vmf->vma->vm_mm, vmf->pud);
 if (unlikely(!pud_same(*vmf->pud, orig_pud)))
  goto unlock;

 touch_pud(vmf->vma, vmf->address, vmf->pud, write);
unlock:
 spin_unlock(vmf->ptl);
}
#endif /* CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD */

void huge_pmd_set_accessed(struct vm_fault *vmf)
{
 bool write = vmf->flags & FAULT_FLAG_WRITE;

 vmf->ptl = pmd_lock(vmf->vma->vm_mm, vmf->pmd);
 if (unlikely(!pmd_same(*vmf->pmd, vmf->orig_pmd)))
  goto unlock;

 touch_pmd(vmf->vma, vmf->address, vmf->pmd, write);

unlock:
 spin_unlock(vmf->ptl);
}

static vm_fault_t do_huge_zero_wp_pmd(struct vm_fault *vmf)
{
 unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
 struct vm_area_struct *vma = vmf->vma;
 struct mmu_notifier_range range;
 struct folio *folio;
 vm_fault_t ret = 0;

 folio = vma_alloc_anon_folio_pmd(vma, vmf->address);
 if (unlikely(!folio))
  return VM_FAULT_FALLBACK;

 mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma->vm_mm, haddr,
    haddr + HPAGE_PMD_SIZE);
 mmu_notifier_invalidate_range_start(&range);
 vmf->ptl = pmd_lock(vma->vm_mm, vmf->pmd);
 if (unlikely(!pmd_same(pmdp_get(vmf->pmd), vmf->orig_pmd)))
  goto release;
 ret = check_stable_address_space(vma->vm_mm);
 if (ret)
  goto release;
 (void)pmdp_huge_clear_flush(vma, haddr, vmf->pmd);
 map_anon_folio_pmd(folio, vmf->pmd, vma, haddr);
 goto unlock;
release:
 folio_put(folio);
unlock:
 spin_unlock(vmf->ptl);
 mmu_notifier_invalidate_range_end(&range);
 return ret;
}

vm_fault_t do_huge_pmd_wp_page(struct vm_fault *vmf)
{
 const bool unshare = vmf->flags & FAULT_FLAG_UNSHARE;
 struct vm_area_struct *vma = vmf->vma;
 struct folio *folio;
 struct page *page;
 unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
 pmd_t orig_pmd = vmf->orig_pmd;

 vmf->ptl = pmd_lockptr(vma->vm_mm, vmf->pmd);
 VM_BUG_ON_VMA(!vma->anon_vma, vma);

 if (is_huge_zero_pmd(orig_pmd)) {
  vm_fault_t ret = do_huge_zero_wp_pmd(vmf);

  if (!(ret & VM_FAULT_FALLBACK))
   return ret;

  /* Fallback to splitting PMD if THP cannot be allocated */
  goto fallback;
 }

 spin_lock(vmf->ptl);

 if (unlikely(!pmd_same(*vmf->pmd, orig_pmd))) {
  spin_unlock(vmf->ptl);
  return 0;
 }

 page = pmd_page(orig_pmd);
 folio = page_folio(page);
 VM_BUG_ON_PAGE(!PageHead(page), page);

 /* Early check when only holding the PT lock. */
 if (PageAnonExclusive(page))
  goto reuse;

 if (!folio_trylock(folio)) {
  folio_get(folio);
  spin_unlock(vmf->ptl);
  folio_lock(folio);
  spin_lock(vmf->ptl);
  if (unlikely(!pmd_same(*vmf->pmd, orig_pmd))) {
   spin_unlock(vmf->ptl);
   folio_unlock(folio);
   folio_put(folio);
   return 0;
  }
  folio_put(folio);
 }

 /* Recheck after temporarily dropping the PT lock. */
 if (PageAnonExclusive(page)) {
  folio_unlock(folio);
  goto reuse;
 }

 /*
 * See do_wp_page(): we can only reuse the folio exclusively if
 * there are no additional references. Note that we always drain
 * the LRU cache immediately after adding a THP.
 */

 if (folio_ref_count(folio) >
   1 + folio_test_swapcache(folio) * folio_nr_pages(folio))
  goto unlock_fallback;
 if (folio_test_swapcache(folio))
  folio_free_swap(folio);
 if (folio_ref_count(folio) == 1) {
  pmd_t entry;

  folio_move_anon_rmap(folio, vma);
  SetPageAnonExclusive(page);
  folio_unlock(folio);
reuse:
  if (unlikely(unshare)) {
   spin_unlock(vmf->ptl);
   return 0;
  }
  entry = pmd_mkyoung(orig_pmd);
  entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
  if (pmdp_set_access_flags(vma, haddr, vmf->pmd, entry, 1))
   update_mmu_cache_pmd(vma, vmf->address, vmf->pmd);
  spin_unlock(vmf->ptl);
  return 0;
 }

unlock_fallback:
 folio_unlock(folio);
 spin_unlock(vmf->ptl);
fallback:
 __split_huge_pmd(vma, vmf->pmd, vmf->address, false);
 return VM_FAULT_FALLBACK;
}

static inline bool can_change_pmd_writable(struct vm_area_struct *vma,
        unsigned long addr, pmd_t pmd)
{
 struct page *page;

 if (WARN_ON_ONCE(!(vma->vm_flags & VM_WRITE)))
  return false;

 /* Don't touch entries that are not even readable (NUMA hinting). */
 if (pmd_protnone(pmd))
  return false;

 /* Do we need write faults for softdirty tracking? */
 if (pmd_needs_soft_dirty_wp(vma, pmd))
  return false;

 /* Do we need write faults for uffd-wp tracking? */
 if (userfaultfd_huge_pmd_wp(vma, pmd))
  return false;

 if (!(vma->vm_flags & VM_SHARED)) {
  /* See can_change_pte_writable(). */
  page = vm_normal_page_pmd(vma, addr, pmd);
  return page && PageAnon(page) && PageAnonExclusive(page);
 }

 /* See can_change_pte_writable(). */
 return pmd_dirty(pmd);
}

/* NUMA hinting page fault entry point for trans huge pmds */
vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf)
{
 struct vm_area_struct *vma = vmf->vma;
 struct folio *folio;
 unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
 int nid = NUMA_NO_NODE;
 int target_nid, last_cpupid;
 pmd_t pmd, old_pmd;
 bool writable = false;
 int flags = 0;

 vmf->ptl = pmd_lock(vma->vm_mm, vmf->pmd);
 old_pmd = pmdp_get(vmf->pmd);

 if (unlikely(!pmd_same(old_pmd, vmf->orig_pmd))) {
  spin_unlock(vmf->ptl);
  return 0;
 }

 pmd = pmd_modify(old_pmd, vma->vm_page_prot);

 /*
 * Detect now whether the PMD could be writable; this information
 * is only valid while holding the PT lock.
 */

 writable = pmd_write(pmd);
 if (!writable && vma_wants_manual_pte_write_upgrade(vma) &&
     can_change_pmd_writable(vma, vmf->address, pmd))
  writable = true;

 folio = vm_normal_folio_pmd(vma, haddr, pmd);
 if (!folio)
  goto out_map;

 nid = folio_nid(folio);

 target_nid = numa_migrate_check(folio, vmf, haddr, &flags, writable,
     &last_cpupid);
 if (target_nid == NUMA_NO_NODE)
  goto out_map;
 if (migrate_misplaced_folio_prepare(folio, vma, target_nid)) {
  flags |= TNF_MIGRATE_FAIL;
  goto out_map;
 }
 /* The folio is isolated and isolation code holds a folio reference. */
 spin_unlock(vmf->ptl);
 writable = false;

 if (!migrate_misplaced_folio(folio, target_nid)) {
  flags |= TNF_MIGRATED;
  nid = target_nid;
  task_numa_fault(last_cpupid, nid, HPAGE_PMD_NR, flags);
  return 0;
 }

 flags |= TNF_MIGRATE_FAIL;
 vmf->ptl = pmd_lock(vma->vm_mm, vmf->pmd);
 if (unlikely(!pmd_same(pmdp_get(vmf->pmd), vmf->orig_pmd))) {
  spin_unlock(vmf->ptl);
  return 0;
 }
out_map:
 /* Restore the PMD */
 pmd = pmd_modify(pmdp_get(vmf->pmd), vma->vm_page_prot);
 pmd = pmd_mkyoung(pmd);
 if (writable)
  pmd = pmd_mkwrite(pmd, vma);
 set_pmd_at(vma->vm_mm, haddr, vmf->pmd, pmd);
 update_mmu_cache_pmd(vma, vmf->address, vmf->pmd);
 spin_unlock(vmf->ptl);

 if (nid != NUMA_NO_NODE)
  task_numa_fault(last_cpupid, nid, HPAGE_PMD_NR, flags);
 return 0;
}

/*
 * Return true if we do MADV_FREE successfully on entire pmd page.
 * Otherwise, return false.
 */

bool madvise_free_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
  pmd_t *pmd, unsigned long addr, unsigned long next)
{
 spinlock_t *ptl;
 pmd_t orig_pmd;
 struct folio *folio;
 struct mm_struct *mm = tlb->mm;
 bool ret = false;

 tlb_change_page_size(tlb, HPAGE_PMD_SIZE);

 ptl = pmd_trans_huge_lock(pmd, vma);
 if (!ptl)
  goto out_unlocked;

 orig_pmd = *pmd;
 if (is_huge_zero_pmd(orig_pmd))
  goto out;

 if (unlikely(!pmd_present(orig_pmd))) {
  VM_BUG_ON(thp_migration_supported() &&
      !is_pmd_migration_entry(orig_pmd));
  goto out;
 }

 folio = pmd_folio(orig_pmd);
 /*
 * If other processes are mapping this folio, we couldn't discard
 * the folio unless they all do MADV_FREE so let's skip the folio.
 */

 if (folio_maybe_mapped_shared(folio))
  goto out;

 if (!folio_trylock(folio))
  goto out;

 /*
 * If user want to discard part-pages of THP, split it so MADV_FREE
 * will deactivate only them.
 */

 if (next - addr != HPAGE_PMD_SIZE) {
  folio_get(folio);
  spin_unlock(ptl);
  split_folio(folio);
  folio_unlock(folio);
  folio_put(folio);
  goto out_unlocked;
 }

 if (folio_test_dirty(folio))
  folio_clear_dirty(folio);
 folio_unlock(folio);

 if (pmd_young(orig_pmd) || pmd_dirty(orig_pmd)) {
  pmdp_invalidate(vma, addr, pmd);
  orig_pmd = pmd_mkold(orig_pmd);
  orig_pmd = pmd_mkclean(orig_pmd);

  set_pmd_at(mm, addr, pmd, orig_pmd);
  tlb_remove_pmd_tlb_entry(tlb, pmd, addr);
 }

 folio_mark_lazyfree(folio);
 ret = true;
out:
 spin_unlock(ptl);
out_unlocked:
 return ret;
}

static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)
{
 pgtable_t pgtable;

 pgtable = pgtable_trans_huge_withdraw(mm, pmd);
 pte_free(mm, pgtable);
 mm_dec_nr_ptes(mm);
}

int zap_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
   pmd_t *pmd, unsigned long addr)
{
 pmd_t orig_pmd;
 spinlock_t *ptl;

 tlb_change_page_size(tlb, HPAGE_PMD_SIZE);

 ptl = __pmd_trans_huge_lock(pmd, vma);
 if (!ptl)
  return 0;
 /*
 * For architectures like ppc64 we look at deposited pgtable
 * when calling pmdp_huge_get_and_clear. So do the
 * pgtable_trans_huge_withdraw after finishing pmdp related
 * operations.
 */

 orig_pmd = pmdp_huge_get_and_clear_full(vma, addr, pmd,
      tlb->fullmm);
 arch_check_zapped_pmd(vma, orig_pmd);
 tlb_remove_pmd_tlb_entry(tlb, pmd, addr);
 if (!vma_is_dax(vma) && vma_is_special_huge(vma)) {
  if (arch_needs_pgtable_deposit())
   zap_deposited_table(tlb->mm, pmd);
  spin_unlock(ptl);
 } else if (is_huge_zero_pmd(orig_pmd)) {
  if (!vma_is_dax(vma) || arch_needs_pgtable_deposit())
   zap_deposited_table(tlb->mm, pmd);
  spin_unlock(ptl);
 } else {
  struct folio *folio = NULL;
  int flush_needed = 1;

  if (pmd_present(orig_pmd)) {
   struct page *page = pmd_page(orig_pmd);

   folio = page_folio(page);
   folio_remove_rmap_pmd(folio, page, vma);
   WARN_ON_ONCE(folio_mapcount(folio) < 0);
   VM_BUG_ON_PAGE(!PageHead(page), page);
  } else if (thp_migration_supported()) {
   swp_entry_t entry;

   VM_BUG_ON(!is_pmd_migration_entry(orig_pmd));
   entry = pmd_to_swp_entry(orig_pmd);
   folio = pfn_swap_entry_folio(entry);
   flush_needed = 0;
  } else
   WARN_ONCE(1, "Non present huge pmd without pmd migration enabled!");

  if (folio_test_anon(folio)) {
   zap_deposited_table(tlb->mm, pmd);
   add_mm_counter(tlb->mm, MM_ANONPAGES, -HPAGE_PMD_NR);
  } else {
   if (arch_needs_pgtable_deposit())
    zap_deposited_table(tlb->mm, pmd);
   add_mm_counter(tlb->mm, mm_counter_file(folio),
           -HPAGE_PMD_NR);

   /*
 * Use flush_needed to indicate whether the PMD entry
 * is present, instead of checking pmd_present() again.
 */

   if (flush_needed && pmd_young(orig_pmd) &&
       likely(vma_has_recency(vma)))
    folio_mark_accessed(folio);
  }

  spin_unlock(ptl);
  if (flush_needed)
   tlb_remove_page_size(tlb, &folio->page, HPAGE_PMD_SIZE);
 }
 return 1;
}

#ifndef pmd_move_must_withdraw
static inline int pmd_move_must_withdraw(spinlock_t *new_pmd_ptl,
      spinlock_t *old_pmd_ptl,
      struct vm_area_struct *vma)
{
 /*
 * With split pmd lock we also need to move preallocated
 * PTE page table if new_pmd is on different PMD page table.
 *
 * We also don't deposit and withdraw tables for file pages.
 */

 return (new_pmd_ptl != old_pmd_ptl) && vma_is_anonymous(vma);
}
#endif

static pmd_t move_soft_dirty_pmd(pmd_t pmd)
{
#ifdef CONFIG_MEM_SOFT_DIRTY
 if (unlikely(is_pmd_migration_entry(pmd)))
  pmd = pmd_swp_mksoft_dirty(pmd);
 else if (pmd_present(pmd))
  pmd = pmd_mksoft_dirty(pmd);
#endif
 return pmd;
}

static pmd_t clear_uffd_wp_pmd(pmd_t pmd)
{
 if (pmd_present(pmd))
  pmd = pmd_clear_uffd_wp(pmd);
 else if (is_swap_pmd(pmd))
  pmd = pmd_swp_clear_uffd_wp(pmd);

 return pmd;
}

bool move_huge_pmd(struct vm_area_struct *vma, unsigned long old_addr,
    unsigned long new_addr, pmd_t *old_pmd, pmd_t *new_pmd)
{
 spinlock_t *old_ptl, *new_ptl;
 pmd_t pmd;
 struct mm_struct *mm = vma->vm_mm;
 bool force_flush = false;

 /*
 * The destination pmd shouldn't be established, free_pgtables()
 * should have released it; but move_page_tables() might have already
 * inserted a page table, if racing against shmem/file collapse.
 */

 if (!pmd_none(*new_pmd)) {
  VM_BUG_ON(pmd_trans_huge(*new_pmd));
  return false;
 }

 /*
 * We don't have to worry about the ordering of src and dst
 * ptlocks because exclusive mmap_lock prevents deadlock.
 */

 old_ptl = __pmd_trans_huge_lock(old_pmd, vma);
 if (old_ptl) {
  new_ptl = pmd_lockptr(mm, new_pmd);
  if (new_ptl != old_ptl)
   spin_lock_nested(new_ptl, SINGLE_DEPTH_NESTING);
  pmd = pmdp_huge_get_and_clear(mm, old_addr, old_pmd);
  if (pmd_present(pmd))
   force_flush = true;
  VM_BUG_ON(!pmd_none(*new_pmd));

  if (pmd_move_must_withdraw(new_ptl, old_ptl, vma)) {
   pgtable_t pgtable;
   pgtable = pgtable_trans_huge_withdraw(mm, old_pmd);
   pgtable_trans_huge_deposit(mm, new_pmd, pgtable);
  }
  pmd = move_soft_dirty_pmd(pmd);
  if (vma_has_uffd_without_event_remap(vma))
   pmd = clear_uffd_wp_pmd(pmd);
  set_pmd_at(mm, new_addr, new_pmd, pmd);
  if (force_flush)
   flush_pmd_tlb_range(vma, old_addr, old_addr + PMD_SIZE);
  if (new_ptl != old_ptl)
   spin_unlock(new_ptl);
  spin_unlock(old_ptl);
  return true;
 }
 return false;
}

/*
 * Returns
 *  - 0 if PMD could not be locked
 *  - 1 if PMD was locked but protections unchanged and TLB flush unnecessary
 *      or if prot_numa but THP migration is not supported
 *  - HPAGE_PMD_NR if protections changed and TLB flush necessary
 */

int change_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
      pmd_t *pmd, unsigned long addr, pgprot_t newprot,
      unsigned long cp_flags)
{
 struct mm_struct *mm = vma->vm_mm;
 spinlock_t *ptl;
 pmd_t oldpmd, entry;
 bool prot_numa = cp_flags & MM_CP_PROT_NUMA;
 bool uffd_wp = cp_flags & MM_CP_UFFD_WP;
 bool uffd_wp_resolve = cp_flags & MM_CP_UFFD_WP_RESOLVE;
 int ret = 1;

 tlb_change_page_size(tlb, HPAGE_PMD_SIZE);

 if (prot_numa && !thp_migration_supported())
  return 1;

 ptl = __pmd_trans_huge_lock(pmd, vma);
 if (!ptl)
  return 0;

#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION
 if (is_swap_pmd(*pmd)) {
  swp_entry_t entry = pmd_to_swp_entry(*pmd);
  struct folio *folio = pfn_swap_entry_folio(entry);
  pmd_t newpmd;

  VM_BUG_ON(!is_pmd_migration_entry(*pmd));
  if (is_writable_migration_entry(entry)) {
   /*
 * A protection check is difficult so
 * just be safe and disable write
 */

   if (folio_test_anon(folio))
    entry = make_readable_exclusive_migration_entry(swp_offset(entry));
   else
    entry = make_readable_migration_entry(swp_offset(entry));
   newpmd = swp_entry_to_pmd(entry);
   if (pmd_swp_soft_dirty(*pmd))
    newpmd = pmd_swp_mksoft_dirty(newpmd);
  } else {
   newpmd = *pmd;
  }

  if (uffd_wp)
   newpmd = pmd_swp_mkuffd_wp(newpmd);
  else if (uffd_wp_resolve)
   newpmd = pmd_swp_clear_uffd_wp(newpmd);
  if (!pmd_same(*pmd, newpmd))
   set_pmd_at(mm, addr, pmd, newpmd);
  goto unlock;
 }
#endif

 if (prot_numa) {
  struct folio *folio;
  bool toptier;
  /*
 * Avoid trapping faults against the zero page. The read-only
 * data is likely to be read-cached on the local CPU and
 * local/remote hits to the zero page are not interesting.
 */

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=90 G=94

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge