2021-07-30 10:05:35

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 0/9] fsdax: introduce fs query to support reflink

This patchset is aimed to support shared pages tracking for fsdax.

Changes for RESEND:
- update the title of patch 6
- update the wrong email address of one receiver

Changes from V5:
- fix dax_load_pfn(), take locked,empty,zero dax entry into
consideration
- fix the usage of rwsem lock for dax_device's holder
- fix build error reported by kernelbot
- keep functionality of dax_{,dis}assocaite_entry() for filesystems
doesn't have rmapbt feature
- Rebased to v5.14-rc3

This patchset moves owner tracking from dax_assocaite_entry() to pmem
device driver, by introducing an interface ->memory_failure() for struct
pagemap. This interface is called by memory_failure() in mm, and
implemented by pmem device.

Then call holder operations to find the filesystem which the corrupted
data located in, and call filesystem handler to track files or metadata
associated with this page.

Finally we are able to try to fix the corrupted data in filesystem and
do other necessary processing, such as killing processes who are using
the files affected.

The call trace is like this:
memory_failure()
|* fsdax case
|------------
|pgmap->ops->memory_failure() => pmem_pgmap_memory_failure()
| dax_holder_notify_failure() =>
| dax_device->holder_ops->notify_failure() =>
| - xfs_dax_notify_failure()
| - md_dax_notify_failure()
| |* xfs_dax_notify_failure()
| |--------------------------
| | xfs_rmap_query_range()
| | xfs_currupt_helper()
| | * corrupted on metadata
| | try to recover data, call xfs_force_shutdown()
| | * corrupted on file data
| | try to recover data, call mf_dax_kill_procs()
| |* md_dax_notify_failure()
| |-------------------------
| md_targets->iterate_devices()
| md_targets->rmap() => linear_rmap()
| dax_holder_notify_failure()
|* normal case
|-------------
mf_generic_kill_procs()

The fsdax & reflink support for XFS is not contained in this patchset.

(Rebased on v5.14-rc3)
==

Shiyang Ruan (9):
pagemap: Introduce ->memory_failure()
dax: Introduce holder for dax_device
mm: factor helpers for memory_failure_dev_pagemap
pmem,mm: Implement ->memory_failure in pmem driver
mm: Introduce mf_dax_kill_procs() for fsdax case
xfs: Implement ->notify_failure() for XFS
dm: Introduce ->rmap() to find bdev offset
md: Implement dax_holder_operations
fsdax: add exception for reflinked files

block/genhd.c | 56 +++++++++++
drivers/dax/super.c | 58 ++++++++++++
drivers/md/dm-linear.c | 20 ++++
drivers/md/dm.c | 126 ++++++++++++++++++++++++-
drivers/nvdimm/pmem.c | 13 +++
fs/dax.c | 59 ++++++++----
fs/xfs/xfs_fsops.c | 5 +
fs/xfs/xfs_mount.h | 1 +
fs/xfs/xfs_super.c | 135 +++++++++++++++++++++++++++
include/linux/dax.h | 46 +++++++++
include/linux/device-mapper.h | 5 +
include/linux/genhd.h | 1 +
include/linux/memremap.h | 9 ++
include/linux/mm.h | 10 ++
mm/memory-failure.c | 169 +++++++++++++++++++++++-----------
15 files changed, 641 insertions(+), 72 deletions(-)

--
2.32.0





2021-07-30 10:05:57

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 2/9] dax: Introduce holder for dax_device

To easily track filesystem from a pmem device, we introduce a holder for
dax_device structure, and also its operation. This holder is used to
remember who is using this dax_device:
- When it is the backend of a filesystem, the holder will be the
superblock of this filesystem.
- When this pmem device is one of the targets in a mapped device, the
holder will be this mapped device. In this case, the mapped device
has its own dax_device and it will follow the first rule. So that we
can finally track to the filesystem we needed.

The holder and holder_ops will be set when filesystem is being mounted,
or an target device is being activated.

Signed-off-by: Shiyang Ruan <[email protected]>
---
drivers/dax/super.c | 46 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 17 +++++++++++++++++
2 files changed, 63 insertions(+)

diff --git a/drivers/dax/super.c b/drivers/dax/super.c
index 5fa6ae9dbc8b..00c32dfa5665 100644
--- a/drivers/dax/super.c
+++ b/drivers/dax/super.c
@@ -214,6 +214,8 @@ enum dax_device_flags {
* @cdev: optional character interface for "device dax"
* @host: optional name for lookups where the device path is not available
* @private: dax driver private data
+ * @holder_rwsem: prevent unregistration while holder_ops is in progress
+ * @holder_data: holder of a dax_device: could be filesystem or mapped device
* @flags: state and boolean properties
*/
struct dax_device {
@@ -222,8 +224,11 @@ struct dax_device {
struct cdev cdev;
const char *host;
void *private;
+ struct rw_semaphore holder_rwsem;
+ void *holder_data;
unsigned long flags;
const struct dax_operations *ops;
+ const struct dax_holder_operations *holder_ops;
};

static ssize_t write_cache_show(struct device *dev,
@@ -373,6 +378,25 @@ int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
}
EXPORT_SYMBOL_GPL(dax_zero_page_range);

+int dax_holder_notify_failure(struct dax_device *dax_dev, loff_t offset,
+ size_t size, void *data)
+{
+ int rc;
+
+ if (!dax_dev)
+ return -ENXIO;
+
+ if (!dax_dev->holder_data)
+ return -EOPNOTSUPP;
+
+ down_read(&dax_dev->holder_rwsem);
+ rc = dax_dev->holder_ops->notify_failure(dax_dev, offset,
+ size, data);
+ up_read(&dax_dev->holder_rwsem);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dax_holder_notify_failure);
+
#ifdef CONFIG_ARCH_HAS_PMEM_API
void arch_wb_cache_pmem(void *addr, size_t size);
void dax_flush(struct dax_device *dax_dev, void *addr, size_t size)
@@ -603,6 +627,7 @@ struct dax_device *alloc_dax(void *private, const char *__host,
dax_add_host(dax_dev, host);
dax_dev->ops = ops;
dax_dev->private = private;
+ init_rwsem(&dax_dev->holder_rwsem);
if (flags & DAXDEV_F_SYNC)
set_dax_synchronous(dax_dev);

@@ -624,6 +649,27 @@ void put_dax(struct dax_device *dax_dev)
}
EXPORT_SYMBOL_GPL(put_dax);

+void dax_set_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops)
+{
+ if (!dax_dev)
+ return;
+ down_write(&dax_dev->holder_rwsem);
+ dax_dev->holder_data = holder;
+ dax_dev->holder_ops = ops;
+ up_write(&dax_dev->holder_rwsem);
+}
+EXPORT_SYMBOL_GPL(dax_set_holder);
+
+void *dax_get_holder(struct dax_device *dax_dev)
+{
+ if (!dax_dev)
+ return NULL;
+
+ return dax_dev->holder_data;
+}
+EXPORT_SYMBOL_GPL(dax_get_holder);
+
/**
* dax_get_by_host() - temporary lookup mechanism for filesystem-dax
* @host: alternate name for the device registered by a dax driver
diff --git a/include/linux/dax.h b/include/linux/dax.h
index b52f084aa643..6f4b5c97ceb0 100644
--- a/include/linux/dax.h
+++ b/include/linux/dax.h
@@ -38,10 +38,17 @@ struct dax_operations {
int (*zero_page_range)(struct dax_device *, pgoff_t, size_t);
};

+struct dax_holder_operations {
+ int (*notify_failure)(struct dax_device *, loff_t, size_t, void *);
+};
+
extern struct attribute_group dax_attribute_group;

#if IS_ENABLED(CONFIG_DAX)
struct dax_device *dax_get_by_host(const char *host);
+void dax_set_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops);
+void *dax_get_holder(struct dax_device *dax_dev);
struct dax_device *alloc_dax(void *private, const char *host,
const struct dax_operations *ops, unsigned long flags);
void put_dax(struct dax_device *dax_dev);
@@ -77,6 +84,14 @@ static inline struct dax_device *dax_get_by_host(const char *host)
{
return NULL;
}
+static inline void dax_set_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops)
+{
+}
+static inline void *dax_get_holder(struct dax_device *dax_dev)
+{
+ return NULL;
+}
static inline struct dax_device *alloc_dax(void *private, const char *host,
const struct dax_operations *ops, unsigned long flags)
{
@@ -226,6 +241,8 @@ size_t dax_copy_to_iter(struct dax_device *dax_dev, pgoff_t pgoff, void *addr,
size_t bytes, struct iov_iter *i);
int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
size_t nr_pages);
+int dax_holder_notify_failure(struct dax_device *dax_dev, loff_t offset,
+ size_t size, void *data);
void dax_flush(struct dax_device *dax_dev, void *addr, size_t size);

ssize_t dax_iomap_rw(struct kiocb *iocb, struct iov_iter *iter,
--
2.32.0




2021-07-30 10:05:59

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 4/9] pmem,mm: Implement ->memory_failure in pmem driver

With dax_holder notify support, we are able to notify the memory failure
from pmem driver to upper layers. If there is something not support in
the notify routine, memory_failure will fall back to the generic hanlder.

Signed-off-by: Shiyang Ruan <[email protected]>
---
drivers/nvdimm/pmem.c | 13 +++++++++++++
mm/memory-failure.c | 14 ++++++++++++++
2 files changed, 27 insertions(+)

diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c
index 1e0615b8565e..fea4ffc333b8 100644
--- a/drivers/nvdimm/pmem.c
+++ b/drivers/nvdimm/pmem.c
@@ -362,9 +362,22 @@ static void pmem_release_disk(void *__pmem)
del_gendisk(pmem->disk);
}

+static int pmem_pagemap_memory_failure(struct dev_pagemap *pgmap,
+ unsigned long pfn, unsigned long nr_pfns, int flags)
+{
+ struct pmem_device *pmem =
+ container_of(pgmap, struct pmem_device, pgmap);
+ loff_t offset = PFN_PHYS(pfn) - pmem->phys_addr - pmem->data_offset;
+
+ return dax_holder_notify_failure(pmem->dax_dev, offset,
+ page_size(pfn_to_page(pfn)) * nr_pfns,
+ &flags);
+}
+
static const struct dev_pagemap_ops fsdax_pagemap_ops = {
.kill = pmem_pagemap_kill,
.cleanup = pmem_pagemap_cleanup,
+ .memory_failure = pmem_pagemap_memory_failure,
};

static int pmem_attach_disk(struct device *dev,
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 3bdfcb45f66e..ab3eda335acd 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1600,6 +1600,20 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
*/
SetPageHWPoison(page);

+ /*
+ * Call driver's implementation to handle the memory failure, otherwise
+ * fall back to generic handler.
+ */
+ if (pgmap->ops->memory_failure) {
+ rc = pgmap->ops->memory_failure(pgmap, pfn, 1, flags);
+ /*
+ * Fall back to generic handler too if operation is not
+ * supported inside the driver/device/filesystem.
+ */
+ if (rc != EOPNOTSUPP)
+ goto out;
+ }
+
mf_generic_kill_procs(pfn, flags);
out:
/* drop pgmap ref acquired in caller */
--
2.32.0




2021-07-30 10:06:25

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 5/9] mm: Introduce mf_dax_kill_procs() for fsdax case

This function is called at the end of RMAP routine, i.e. filesystem
recovery function. The difference between mf_generic_kill_procs() is,
mf_dax_kill_procs() accepts file mapping and offset instead of struct
page. It is because that different file mappings and offsets may share
the same page in fsdax mode. So, it is called when filesystem RMAP
results are found.

Signed-off-by: Shiyang Ruan <[email protected]>
---
fs/dax.c | 45 ++++++++++++++++++++++++-------
include/linux/dax.h | 16 ++++++++++++
include/linux/mm.h | 10 +++++++
mm/memory-failure.c | 64 +++++++++++++++++++++++++++++++++------------
4 files changed, 109 insertions(+), 26 deletions(-)

diff --git a/fs/dax.c b/fs/dax.c
index da41f9363568..dce6307a12eb 100644
--- a/fs/dax.c
+++ b/fs/dax.c
@@ -389,6 +389,41 @@ static struct page *dax_busy_page(void *entry)
return NULL;
}

+/**
+ * dax_load_pfn - Load pfn of the DAX entry corresponding to a page
+ * @mapping: The file whose entry we want to load
+ * @index: offset where the DAX entry located in
+ *
+ * Return: pfn number of the DAX entry
+ */
+unsigned long dax_load_pfn(struct address_space *mapping, unsigned long index)
+{
+ XA_STATE(xas, &mapping->i_pages, index);
+ void *entry;
+ unsigned long pfn;
+
+ rcu_read_lock();
+ for (;;) {
+ xas_lock_irq(&xas);
+ entry = xas_load(&xas);
+ if (dax_is_locked(entry)) {
+ rcu_read_unlock();
+ wait_entry_unlocked(&xas, entry);
+ rcu_read_lock();
+ continue;
+ }
+
+ if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry))
+ pfn = 0;
+ else
+ pfn = dax_to_pfn(entry);
+ xas_unlock_irq(&xas);
+ break;
+ }
+ rcu_read_unlock();
+ return pfn;
+}
+
/*
* dax_lock_mapping_entry - Lock the DAX entry corresponding to a page
* @page: The page whose entry we want to lock
@@ -790,16 +825,6 @@ static void *dax_insert_entry(struct xa_state *xas,
return entry;
}

-static inline
-unsigned long pgoff_address(pgoff_t pgoff, struct vm_area_struct *vma)
-{
- unsigned long address;
-
- address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
- VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
- return address;
-}
-
/* Walk all mappings of a given index of a file and writeprotect them */
static void dax_entry_mkclean(struct address_space *mapping, pgoff_t index,
unsigned long pfn)
diff --git a/include/linux/dax.h b/include/linux/dax.h
index 6f4b5c97ceb0..359e809516b8 100644
--- a/include/linux/dax.h
+++ b/include/linux/dax.h
@@ -165,6 +165,7 @@ int dax_writeback_mapping_range(struct address_space *mapping,

struct page *dax_layout_busy_page(struct address_space *mapping);
struct page *dax_layout_busy_page_range(struct address_space *mapping, loff_t start, loff_t end);
+unsigned long dax_load_pfn(struct address_space *mapping, unsigned long index);
dax_entry_t dax_lock_page(struct page *page);
void dax_unlock_page(struct page *page, dax_entry_t cookie);
#else
@@ -206,6 +207,12 @@ static inline int dax_writeback_mapping_range(struct address_space *mapping,
return -EOPNOTSUPP;
}

+static inline unsigned long dax_load_pfn(struct address_space *mapping,
+ unsigned long index)
+{
+ return 0;
+}
+
static inline dax_entry_t dax_lock_page(struct page *page)
{
if (IS_DAX(page->mapping->host))
@@ -259,6 +266,15 @@ static inline bool dax_mapping(struct address_space *mapping)
{
return mapping->host && IS_DAX(mapping->host);
}
+static inline unsigned long pgoff_address(pgoff_t pgoff,
+ struct vm_area_struct *vma)
+{
+ unsigned long address;
+
+ address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
+ VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
+ return address;
+}

#ifdef CONFIG_DEV_DAX_HMEM_DEVICES
void hmem_register_device(int target_nid, struct resource *r);
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 7ca22e6e694a..530aaf7a6eb2 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1190,6 +1190,14 @@ static inline bool is_device_private_page(const struct page *page)
page->pgmap->type == MEMORY_DEVICE_PRIVATE;
}

+static inline bool is_device_fsdax_page(const struct page *page)
+{
+ return IS_ENABLED(CONFIG_DEV_PAGEMAP_OPS) &&
+ IS_ENABLED(CONFIG_FS_DAX) &&
+ is_zone_device_page(page) &&
+ page->pgmap->type == MEMORY_DEVICE_FS_DAX;
+}
+
static inline bool is_pci_p2pdma_page(const struct page *page)
{
return IS_ENABLED(CONFIG_DEV_PAGEMAP_OPS) &&
@@ -3113,6 +3121,8 @@ enum mf_flags {
MF_MUST_KILL = 1 << 2,
MF_SOFT_OFFLINE = 1 << 3,
};
+extern int mf_dax_kill_procs(struct address_space *mapping, pgoff_t index,
+ int flags);
extern int memory_failure(unsigned long pfn, int flags);
extern void memory_failure_queue(unsigned long pfn, int flags);
extern void memory_failure_queue_kick(int cpu);
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index ab3eda335acd..520664c405fc 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -134,6 +134,12 @@ static int hwpoison_filter_dev(struct page *p)
if (PageSlab(p))
return -EINVAL;

+ if (pfn_valid(page_to_pfn(p))) {
+ if (is_device_fsdax_page(p))
+ return 0;
+ } else
+ return -EINVAL;
+
mapping = page_mapping(p);
if (mapping == NULL || mapping->host == NULL)
return -EINVAL;
@@ -304,10 +310,9 @@ void shake_page(struct page *p, int access)
}
EXPORT_SYMBOL_GPL(shake_page);

-static unsigned long dev_pagemap_mapping_shift(struct page *page,
+static unsigned long dev_pagemap_mapping_shift(unsigned long address,
struct vm_area_struct *vma)
{
- unsigned long address = vma_address(page, vma);
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
@@ -347,7 +352,7 @@ static unsigned long dev_pagemap_mapping_shift(struct page *page,
* Schedule a process for later kill.
* Uses GFP_ATOMIC allocations to avoid potential recursions in the VM.
*/
-static void add_to_kill(struct task_struct *tsk, struct page *p,
+static void add_to_kill(struct task_struct *tsk, struct page *p, pgoff_t pgoff,
struct vm_area_struct *vma,
struct list_head *to_kill)
{
@@ -360,9 +365,14 @@ static void add_to_kill(struct task_struct *tsk, struct page *p,
}

tk->addr = page_address_in_vma(p, vma);
- if (is_zone_device_page(p))
- tk->size_shift = dev_pagemap_mapping_shift(p, vma);
- else
+ if (is_zone_device_page(p)) {
+ /* Since page->mapping is no more used for fsdax, we should
+ * calculate the address in a fsdax way.
+ */
+ if (is_device_fsdax_page(p))
+ tk->addr = pgoff_address(pgoff, vma);
+ tk->size_shift = dev_pagemap_mapping_shift(tk->addr, vma);
+ } else
tk->size_shift = page_shift(compound_head(p));

/*
@@ -510,7 +520,7 @@ static void collect_procs_anon(struct page *page, struct list_head *to_kill,
if (!page_mapped_in_vma(page, vma))
continue;
if (vma->vm_mm == t->mm)
- add_to_kill(t, page, vma, to_kill);
+ add_to_kill(t, page, 0, vma, to_kill);
}
}
read_unlock(&tasklist_lock);
@@ -520,24 +530,20 @@ static void collect_procs_anon(struct page *page, struct list_head *to_kill,
/*
* Collect processes when the error hit a file mapped page.
*/
-static void collect_procs_file(struct page *page, struct list_head *to_kill,
- int force_early)
+static void collect_procs_file(struct page *page, struct address_space *mapping,
+ pgoff_t pgoff, struct list_head *to_kill, int force_early)
{
struct vm_area_struct *vma;
struct task_struct *tsk;
- struct address_space *mapping = page->mapping;
- pgoff_t pgoff;

i_mmap_lock_read(mapping);
read_lock(&tasklist_lock);
- pgoff = page_to_pgoff(page);
for_each_process(tsk) {
struct task_struct *t = task_early_kill(tsk, force_early);

if (!t)
continue;
- vma_interval_tree_foreach(vma, &mapping->i_mmap, pgoff,
- pgoff) {
+ vma_interval_tree_foreach(vma, &mapping->i_mmap, pgoff, pgoff) {
/*
* Send early kill signal to tasks where a vma covers
* the page but the corrupted page is not necessarily
@@ -546,7 +552,7 @@ static void collect_procs_file(struct page *page, struct list_head *to_kill,
* to be informed of all such data corruptions.
*/
if (vma->vm_mm == t->mm)
- add_to_kill(t, page, vma, to_kill);
+ add_to_kill(t, page, pgoff, vma, to_kill);
}
}
read_unlock(&tasklist_lock);
@@ -565,7 +571,8 @@ static void collect_procs(struct page *page, struct list_head *tokill,
if (PageAnon(page))
collect_procs_anon(page, tokill, force_early);
else
- collect_procs_file(page, tokill, force_early);
+ collect_procs_file(page, page->mapping, page->index, tokill,
+ force_early);
}

struct hwp_walk {
@@ -1477,6 +1484,31 @@ static int mf_generic_kill_procs(unsigned long long pfn, int flags)
return 0;
}

+int mf_dax_kill_procs(struct address_space *mapping, pgoff_t index, int flags)
+{
+ LIST_HEAD(to_kill);
+ /* load the pfn of the dax mapping file */
+ unsigned long pfn = dax_load_pfn(mapping, index);
+
+ /* the failure pfn may not actually be mmapped, so no need to
+ * unmap and kill procs */
+ if (!pfn)
+ return 0;
+
+ /*
+ * Unlike System-RAM there is no possibility to swap in a
+ * different physical page at a given virtual address, so all
+ * userspace consumption of ZONE_DEVICE memory necessitates
+ * SIGBUS (i.e. MF_MUST_KILL)
+ */
+ flags |= MF_ACTION_REQUIRED | MF_MUST_KILL;
+ collect_procs_file(pfn_to_page(pfn), mapping, index, &to_kill, true);
+
+ unmap_and_kill(&to_kill, pfn, mapping, index, flags);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mf_dax_kill_procs);
+
static int memory_failure_hugetlb(unsigned long pfn, int flags)
{
struct page *p = pfn_to_page(pfn);
--
2.32.0




2021-07-30 10:06:30

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 9/9] fsdax: add exception for reflinked files

For reflinked files, one dax page may be associated more than once with
different fime mapping and index. It will report warning. Now, since
we have introduced dax-RMAP for this case and also have to keep its
functionality for other filesystems who are not support rmap, I add this
exception here.

Signed-off-by: Shiyang Ruan <[email protected]>
---
fs/dax.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/fs/dax.c b/fs/dax.c
index dce6307a12eb..f5910d178695 100644
--- a/fs/dax.c
+++ b/fs/dax.c
@@ -352,9 +352,10 @@ static void dax_associate_entry(void *entry, struct address_space *mapping,
for_each_mapped_pfn(entry, pfn) {
struct page *page = pfn_to_page(pfn);

- WARN_ON_ONCE(page->mapping);
- page->mapping = mapping;
- page->index = index + i++;
+ if (!page->mapping) {
+ page->mapping = mapping;
+ page->index = index + i++;
+ }
}
}

@@ -370,9 +371,10 @@ static void dax_disassociate_entry(void *entry, struct address_space *mapping,
struct page *page = pfn_to_page(pfn);

WARN_ON_ONCE(trunc && page_ref_count(page) > 1);
- WARN_ON_ONCE(page->mapping && page->mapping != mapping);
- page->mapping = NULL;
- page->index = 0;
+ if (page->mapping == mapping) {
+ page->mapping = NULL;
+ page->index = 0;
+ }
}
}

--
2.32.0




2021-07-30 10:06:44

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 8/9] md: Implement dax_holder_operations

This is the case where the holder represents a mapped device, or a list
of mapped devices more exactly(because it is possible to create more
than one mapped device on one pmem device).

Find out which mapped device the offset belongs to, and translate the
offset from target device to mapped device. When it is done, call
dax_corrupted_range() for the holder of this mapped device.

Signed-off-by: Shiyang Ruan <[email protected]>
---
drivers/md/dm.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 125 insertions(+), 1 deletion(-)

diff --git a/drivers/md/dm.c b/drivers/md/dm.c
index 2c5f9e585211..a35b9a97a73f 100644
--- a/drivers/md/dm.c
+++ b/drivers/md/dm.c
@@ -626,7 +626,11 @@ static void dm_put_live_table_fast(struct mapped_device *md) __releases(RCU)
}

static char *_dm_claim_ptr = "I belong to device-mapper";
-
+static const struct dax_holder_operations dm_dax_holder_ops;
+struct dm_holder {
+ struct list_head list;
+ struct mapped_device *md;
+};
/*
* Open a table device so we can use it as a map destination.
*/
@@ -634,6 +638,8 @@ static int open_table_device(struct table_device *td, dev_t dev,
struct mapped_device *md)
{
struct block_device *bdev;
+ struct list_head *holders;
+ struct dm_holder *holder;

int r;

@@ -651,6 +657,19 @@ static int open_table_device(struct table_device *td, dev_t dev,

td->dm_dev.bdev = bdev;
td->dm_dev.dax_dev = dax_get_by_host(bdev->bd_disk->disk_name);
+ if (!td->dm_dev.dax_dev)
+ return 0;
+
+ holders = dax_get_holder(td->dm_dev.dax_dev);
+ if (!holders) {
+ holders = kmalloc(sizeof(*holders), GFP_KERNEL);
+ INIT_LIST_HEAD(holders);
+ dax_set_holder(td->dm_dev.dax_dev, holders, &dm_dax_holder_ops);
+ }
+ holder = kmalloc(sizeof(*holder), GFP_KERNEL);
+ holder->md = md;
+ list_add_tail(&holder->list, holders);
+
return 0;
}

@@ -659,9 +678,27 @@ static int open_table_device(struct table_device *td, dev_t dev,
*/
static void close_table_device(struct table_device *td, struct mapped_device *md)
{
+ struct list_head *holders;
+ struct dm_holder *holder, *n;
+
if (!td->dm_dev.bdev)
return;

+ holders = dax_get_holder(td->dm_dev.dax_dev);
+ if (holders) {
+ list_for_each_entry_safe(holder, n, holders, list) {
+ if (holder->md == md) {
+ list_del(&holder->list);
+ kfree(holder);
+ }
+ }
+ if (list_empty(holders)) {
+ kfree(holders);
+ /* unset dax_device's holder_data */
+ dax_set_holder(td->dm_dev.dax_dev, NULL, NULL);
+ }
+ }
+
bd_unlink_disk_holder(td->dm_dev.bdev, dm_disk(md));
blkdev_put(td->dm_dev.bdev, td->dm_dev.mode | FMODE_EXCL);
put_dax(td->dm_dev.dax_dev);
@@ -1115,6 +1152,89 @@ static int dm_dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
return ret;
}

+#if IS_ENABLED(CONFIG_DAX_DRIVER)
+struct corrupted_hit_info {
+ struct dax_device *dax_dev;
+ sector_t offset;
+};
+
+static int dm_blk_corrupted_hit(struct dm_target *ti, struct dm_dev *dev,
+ sector_t start, sector_t count, void *data)
+{
+ struct corrupted_hit_info *bc = data;
+
+ return bc->dax_dev == (void *)dev->dax_dev &&
+ (start <= bc->offset && bc->offset < start + count);
+}
+
+struct corrupted_do_info {
+ size_t length;
+ void *data;
+};
+
+static int dm_blk_corrupted_do(struct dm_target *ti, struct block_device *bdev,
+ sector_t sector, void *data)
+{
+ struct mapped_device *md = ti->table->md;
+ struct corrupted_do_info *bc = data;
+
+ return dax_holder_notify_failure(md->dax_dev, to_bytes(sector),
+ bc->length, bc->data);
+}
+
+static int dm_dax_notify_failure_one(struct mapped_device *md,
+ struct dax_device *dax_dev,
+ loff_t offset, size_t length, void *data)
+{
+ struct dm_table *map;
+ struct dm_target *ti;
+ sector_t sect = to_sector(offset);
+ struct corrupted_hit_info hi = {dax_dev, sect};
+ struct corrupted_do_info di = {length, data};
+ int srcu_idx, i, rc = -ENODEV;
+
+ map = dm_get_live_table(md, &srcu_idx);
+ if (!map)
+ return rc;
+
+ /*
+ * find the target device, and then translate the offset of this target
+ * to the whole mapped device.
+ */
+ for (i = 0; i < dm_table_get_num_targets(map); i++) {
+ ti = dm_table_get_target(map, i);
+ if (!(ti->type->iterate_devices && ti->type->rmap))
+ continue;
+ if (!ti->type->iterate_devices(ti, dm_blk_corrupted_hit, &hi))
+ continue;
+
+ rc = ti->type->rmap(ti, sect, dm_blk_corrupted_do, &di);
+ break;
+ }
+
+ dm_put_live_table(md, srcu_idx);
+ return rc;
+}
+
+static int dm_dax_notify_failure(struct dax_device *dax_dev,
+ loff_t offset, size_t length, void *data)
+{
+ struct dm_holder *holder;
+ struct list_head *holders = dax_get_holder(dax_dev);
+ int rc = -ENODEV;
+
+ list_for_each_entry(holder, holders, list) {
+ rc = dm_dax_notify_failure_one(holder->md, dax_dev, offset,
+ length, data);
+ if (rc != -ENODEV)
+ break;
+ }
+ return rc;
+}
+#else
+#define dm_dax_notify_failure NULL
+#endif
+
/*
* A target may call dm_accept_partial_bio only from the map routine. It is
* allowed for all bio types except REQ_PREFLUSH, REQ_OP_ZONE_* zone management
@@ -3057,6 +3177,10 @@ static const struct dax_operations dm_dax_ops = {
.zero_page_range = dm_dax_zero_page_range,
};

+static const struct dax_holder_operations dm_dax_holder_ops = {
+ .notify_failure = dm_dax_notify_failure,
+};
+
/*
* module hooks
*/
--
2.32.0




2021-07-30 10:07:00

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 7/9] dm: Introduce ->rmap() to find bdev offset

Pmem device could be a target of mapped device. In order to find out
the global location on a mapped device, we introduce this to translate
offset from target device to mapped device.

Currently, we implement it on linear target, which is easy to do the
translation. Other targets will be supported in the future. However,
some targets may not support it because of the non-linear mapping.

Signed-off-by: Shiyang Ruan <[email protected]>
---
block/genhd.c | 56 +++++++++++++++++++++++++++++++++++
drivers/md/dm-linear.c | 20 +++++++++++++
include/linux/device-mapper.h | 5 ++++
include/linux/genhd.h | 1 +
4 files changed, 82 insertions(+)

diff --git a/block/genhd.c b/block/genhd.c
index af4d2ab4a633..7a595da0cbec 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -669,6 +669,62 @@ void blk_request_module(dev_t devt)
request_module("block-major-%d", MAJOR(devt));
}

+/*
+ * bdget_disk - do bdget() by gendisk and partition number
+ * @disk: gendisk of interest
+ * @partno: partition number
+ *
+ * Find partition @partno from @disk, do bdget() on it.
+ *
+ * CONTEXT:
+ * Don't care.
+ *
+ * RETURNS:
+ * Resulting block_device on success, NULL on failure.
+ */
+static inline struct block_device *bdget_disk(struct gendisk *disk, int partno)
+{
+ struct block_device *bdev = NULL;
+
+ rcu_read_lock();
+ bdev = xa_load(&disk->part_tbl, partno);
+ if (bdev && !bdgrab(bdev))
+ bdev = NULL;
+ rcu_read_unlock();
+
+ return bdev;
+}
+
+/**
+ * bdget_disk_sector - get block device by given sector number
+ * @disk: gendisk of interest
+ * @sector: sector number
+ *
+ * RETURNS: the found block device where sector locates in
+ */
+struct block_device *bdget_disk_sector(struct gendisk *disk, sector_t sector)
+{
+ struct block_device *part = NULL, *p;
+ unsigned long idx;
+
+ rcu_read_lock();
+ xa_for_each(&disk->part_tbl, idx, p) {
+ if (p->bd_partno == 0)
+ continue;
+ if (p->bd_start_sect <= sector &&
+ sector < p->bd_start_sect + bdev_nr_sectors(p)) {
+ part = p;
+ break;
+ }
+ }
+ rcu_read_unlock();
+ if (!part)
+ part = disk->part0;
+
+ return bdget_disk(disk, part->bd_partno);
+}
+EXPORT_SYMBOL(bdget_disk_sector);
+
/*
* print a full list of all partitions - intended for places where the root
* filesystem can't be mounted and thus to give the victim some idea of what
diff --git a/drivers/md/dm-linear.c b/drivers/md/dm-linear.c
index c91f1e2e2f65..d28577bd358b 100644
--- a/drivers/md/dm-linear.c
+++ b/drivers/md/dm-linear.c
@@ -5,6 +5,7 @@
*/

#include "dm.h"
+#include "dm-core.h"
#include <linux/module.h>
#include <linux/init.h>
#include <linux/blkdev.h>
@@ -119,6 +120,24 @@ static void linear_status(struct dm_target *ti, status_type_t type,
}
}

+static int linear_rmap(struct dm_target *ti, sector_t offset,
+ rmap_callout_fn fn, void *data)
+{
+ struct linear_c *lc = (struct linear_c *) ti->private;
+ struct mapped_device *md = ti->table->md;
+ struct block_device *bdev;
+ sector_t disk_sect = offset - dm_target_offset(ti, lc->start);
+ int rc = -ENODEV;
+
+ bdev = bdget_disk_sector(md->disk, offset);
+ if (!bdev)
+ return rc;
+
+ rc = fn(ti, bdev, disk_sect, data);
+ bdput(bdev);
+ return rc;
+}
+
static int linear_prepare_ioctl(struct dm_target *ti, struct block_device **bdev)
{
struct linear_c *lc = (struct linear_c *) ti->private;
@@ -235,6 +254,7 @@ static struct target_type linear_target = {
.ctr = linear_ctr,
.dtr = linear_dtr,
.map = linear_map,
+ .rmap = linear_rmap,
.status = linear_status,
.prepare_ioctl = linear_prepare_ioctl,
.iterate_devices = linear_iterate_devices,
diff --git a/include/linux/device-mapper.h b/include/linux/device-mapper.h
index 7457d49acf9a..4069983c4618 100644
--- a/include/linux/device-mapper.h
+++ b/include/linux/device-mapper.h
@@ -58,6 +58,10 @@ typedef void (*dm_dtr_fn) (struct dm_target *ti);
* = 2: The target wants to push back the io
*/
typedef int (*dm_map_fn) (struct dm_target *ti, struct bio *bio);
+typedef int (*rmap_callout_fn) (struct dm_target *ti, struct block_device *bdev,
+ sector_t sect, void *data);
+typedef int (*dm_rmap_fn) (struct dm_target *ti, sector_t offset,
+ rmap_callout_fn fn, void *data);
typedef int (*dm_clone_and_map_request_fn) (struct dm_target *ti,
struct request *rq,
union map_info *map_context,
@@ -184,6 +188,7 @@ struct target_type {
dm_ctr_fn ctr;
dm_dtr_fn dtr;
dm_map_fn map;
+ dm_rmap_fn rmap;
dm_clone_and_map_request_fn clone_and_map_rq;
dm_release_clone_request_fn release_clone_rq;
dm_endio_fn end_io;
diff --git a/include/linux/genhd.h b/include/linux/genhd.h
index 13b34177cc85..7de6fdc14de6 100644
--- a/include/linux/genhd.h
+++ b/include/linux/genhd.h
@@ -223,6 +223,7 @@ static inline void add_disk_no_queue_reg(struct gendisk *disk)
}

extern void del_gendisk(struct gendisk *gp);
+extern struct block_device *bdget_disk_sector(struct gendisk *disk, sector_t sector);

void set_disk_ro(struct gendisk *disk, bool read_only);

--
2.32.0




2021-07-30 10:07:48

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

When memory-failure occurs, we call this function which is implemented
by each kind of devices. For the fsdax case, pmem device driver
implements it. Pmem device driver will find out the filesystem in which
the corrupted page located in. And finally call filesystem handler to
deal with this error.

The filesystem will try to recover the corrupted data if necessary.

Signed-off-by: Shiyang Ruan <[email protected]>
---
include/linux/memremap.h | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/include/linux/memremap.h b/include/linux/memremap.h
index c0e9d35889e8..ed6980087db5 100644
--- a/include/linux/memremap.h
+++ b/include/linux/memremap.h
@@ -87,6 +87,15 @@ struct dev_pagemap_ops {
* the page back to a CPU accessible page.
*/
vm_fault_t (*migrate_to_ram)(struct vm_fault *vmf);
+
+ /*
+ * Handle the memory failure happens on a range of pfns. Notify the
+ * processes who are using these pfns, and try to recover the data on
+ * them if necessary. The flag is finally passed to the recover
+ * function through the whole notify routine.
+ */
+ int (*memory_failure)(struct dev_pagemap *pgmap, unsigned long pfn,
+ unsigned long nr_pfns, int flags);
};

#define PGMAP_ALTMAP_VALID (1 << 0)
--
2.32.0




2021-07-30 10:08:28

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 3/9] mm: factor helpers for memory_failure_dev_pagemap

memory_failure_dev_pagemap code is a bit complex before introduce RMAP
feature for fsdax. So it is needed to factor some helper functions to
simplify these code.

Signed-off-by: Shiyang Ruan <[email protected]>
---
mm/memory-failure.c | 101 +++++++++++++++++++++++++-------------------
1 file changed, 57 insertions(+), 44 deletions(-)

diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index eefd823deb67..3bdfcb45f66e 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1423,6 +1423,60 @@ static int try_to_split_thp_page(struct page *page, const char *msg)
return 0;
}

+static void unmap_and_kill(struct list_head *to_kill, unsigned long pfn,
+ struct address_space *mapping, pgoff_t index, int flags)
+{
+ struct to_kill *tk;
+ unsigned long size = 0;
+
+ list_for_each_entry(tk, to_kill, nd)
+ if (tk->size_shift)
+ size = max(size, 1UL << tk->size_shift);
+ if (size) {
+ /*
+ * Unmap the largest mapping to avoid breaking up device-dax
+ * mappings which are constant size. The actual size of the
+ * mapping being torn down is communicated in siginfo, see
+ * kill_proc()
+ */
+ loff_t start = (index << PAGE_SHIFT) & ~(size - 1);
+
+ unmap_mapping_range(mapping, start, size, 0);
+ }
+
+ kill_procs(to_kill, flags & MF_MUST_KILL, false, pfn, flags);
+}
+
+static int mf_generic_kill_procs(unsigned long long pfn, int flags)
+{
+ struct page *page = pfn_to_page(pfn);
+ LIST_HEAD(to_kill);
+ dax_entry_t cookie;
+
+ /*
+ * Prevent the inode from being freed while we are interrogating
+ * the address_space, typically this would be handled by
+ * lock_page(), but dax pages do not use the page lock. This
+ * also prevents changes to the mapping of this pfn until
+ * poison signaling is complete.
+ */
+ cookie = dax_lock_page(page);
+ if (!cookie)
+ return -EBUSY;
+ /*
+ * Unlike System-RAM there is no possibility to swap in a
+ * different physical page at a given virtual address, so all
+ * userspace consumption of ZONE_DEVICE memory necessitates
+ * SIGBUS (i.e. MF_MUST_KILL)
+ */
+ flags |= MF_ACTION_REQUIRED | MF_MUST_KILL;
+ collect_procs(page, &to_kill, true);
+
+ unmap_and_kill(&to_kill, pfn, page->mapping, page->index, flags);
+ dax_unlock_page(page, cookie);
+ return 0;
+}
+
static int memory_failure_hugetlb(unsigned long pfn, int flags)
{
struct page *p = pfn_to_page(pfn);
@@ -1512,13 +1566,8 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
struct dev_pagemap *pgmap)
{
struct page *page = pfn_to_page(pfn);
- const bool unmap_success = true;
- unsigned long size = 0;
- struct to_kill *tk;
LIST_HEAD(tokill);
int rc = -EBUSY;
- loff_t start;
- dax_entry_t cookie;

if (flags & MF_COUNT_INCREASED)
/*
@@ -1532,20 +1581,9 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
goto out;
}

- /*
- * Prevent the inode from being freed while we are interrogating
- * the address_space, typically this would be handled by
- * lock_page(), but dax pages do not use the page lock. This
- * also prevents changes to the mapping of this pfn until
- * poison signaling is complete.
- */
- cookie = dax_lock_page(page);
- if (!cookie)
- goto out;
-
if (hwpoison_filter(page)) {
rc = 0;
- goto unlock;
+ goto out;
}

if (pgmap->type == MEMORY_DEVICE_PRIVATE) {
@@ -1553,7 +1591,7 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
* TODO: Handle HMM pages which may need coordination
* with device-side memory.
*/
- goto unlock;
+ goto out;
}

/*
@@ -1562,32 +1600,7 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
*/
SetPageHWPoison(page);

- /*
- * Unlike System-RAM there is no possibility to swap in a
- * different physical page at a given virtual address, so all
- * userspace consumption of ZONE_DEVICE memory necessitates
- * SIGBUS (i.e. MF_MUST_KILL)
- */
- flags |= MF_ACTION_REQUIRED | MF_MUST_KILL;
- collect_procs(page, &tokill, flags & MF_ACTION_REQUIRED);
-
- list_for_each_entry(tk, &tokill, nd)
- if (tk->size_shift)
- size = max(size, 1UL << tk->size_shift);
- if (size) {
- /*
- * Unmap the largest mapping to avoid breaking up
- * device-dax mappings which are constant size. The
- * actual size of the mapping being torn down is
- * communicated in siginfo, see kill_proc()
- */
- start = (page->index << PAGE_SHIFT) & ~(size - 1);
- unmap_mapping_range(page->mapping, start, size, 0);
- }
- kill_procs(&tokill, flags & MF_MUST_KILL, !unmap_success, pfn, flags);
- rc = 0;
-unlock:
- dax_unlock_page(page, cookie);
+ mf_generic_kill_procs(pfn, flags);
out:
/* drop pgmap ref acquired in caller */
put_dev_pagemap(pgmap);
--
2.32.0




2021-07-30 10:09:04

by Shiyang Ruan

[permalink] [raw]
Subject: [PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS

This function is used to handle errors which may cause data lost in
filesystem. Such as memory failure in fsdax mode.

If the rmap feature of XFS enabled, we can query it to find files and
metadata which are associated with the corrupt data. For now all we do
is kill processes with that file mapped into their address spaces, but
future patches could actually do something about corrupt metadata.

After that, the memory failure needs to notify the processes who are
using those files.

Signed-off-by: Shiyang Ruan <[email protected]>
---
drivers/dax/super.c | 12 ++++
fs/xfs/xfs_fsops.c | 5 ++
fs/xfs/xfs_mount.h | 1 +
fs/xfs/xfs_super.c | 135 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 13 +++++
5 files changed, 166 insertions(+)

diff --git a/drivers/dax/super.c b/drivers/dax/super.c
index 00c32dfa5665..63f7b63d078d 100644
--- a/drivers/dax/super.c
+++ b/drivers/dax/super.c
@@ -65,6 +65,18 @@ struct dax_device *fs_dax_get_by_bdev(struct block_device *bdev)
return dax_get_by_host(bdev->bd_disk->disk_name);
}
EXPORT_SYMBOL_GPL(fs_dax_get_by_bdev);
+
+void fs_dax_set_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops)
+{
+ dax_set_holder(dax_dev, holder, ops);
+}
+EXPORT_SYMBOL_GPL(fs_dax_set_holder);
+void *fs_dax_get_holder(struct dax_device *dax_dev)
+{
+ return dax_get_holder(dax_dev);
+}
+EXPORT_SYMBOL_GPL(fs_dax_get_holder);
#endif

bool __generic_fsdax_supported(struct dax_device *dax_dev,
diff --git a/fs/xfs/xfs_fsops.c b/fs/xfs/xfs_fsops.c
index 6ed29b158312..e96ddb5c28bc 100644
--- a/fs/xfs/xfs_fsops.c
+++ b/fs/xfs/xfs_fsops.c
@@ -549,6 +549,11 @@ xfs_do_force_shutdown(
flags, __return_address, fname, lnnum);
if (XFS_ERRLEVEL_HIGH <= xfs_error_level)
xfs_stack_trace();
+ } else if (flags & SHUTDOWN_CORRUPT_META) {
+ xfs_alert_tag(mp, XFS_PTAG_SHUTDOWN_CORRUPT,
+"Corruption of on-disk metadata detected. Shutting down filesystem");
+ if (XFS_ERRLEVEL_HIGH <= xfs_error_level)
+ xfs_stack_trace();
} else if (logerror) {
xfs_alert_tag(mp, XFS_PTAG_SHUTDOWN_LOGERROR,
"Log I/O error (0x%x) detected at %pS (%s:%d). Shutting down filesystem",
diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
index c78b63fe779a..203eb62d16d0 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -277,6 +277,7 @@ void xfs_do_force_shutdown(struct xfs_mount *mp, int flags, char *fname,
#define SHUTDOWN_LOG_IO_ERROR 0x0002 /* write attempt to the log failed */
#define SHUTDOWN_FORCE_UMOUNT 0x0004 /* shutdown from a forced unmount */
#define SHUTDOWN_CORRUPT_INCORE 0x0008 /* corrupt in-memory data structures */
+#define SHUTDOWN_CORRUPT_META 0x0010 /* corrupt metadata on device */

/*
* Flags for xfs_mountfs
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 2c9e26a44546..4a362e14318d 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -37,11 +37,19 @@
#include "xfs_reflink.h"
#include "xfs_pwork.h"
#include "xfs_ag.h"
+#include "xfs_alloc.h"
+#include "xfs_rmap.h"
+#include "xfs_rmap_btree.h"
+#include "xfs_rtalloc.h"
+#include "xfs_bit.h"

#include <linux/magic.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
+#include <linux/mm.h>
+#include <linux/dax.h>

+static const struct dax_holder_operations xfs_dax_holder_operations;
static const struct super_operations xfs_super_operations;

static struct kset *xfs_kset; /* top-level xfs sysfs dir */
@@ -352,6 +360,7 @@ xfs_close_devices(

xfs_free_buftarg(mp->m_logdev_targp);
xfs_blkdev_put(logdev);
+ fs_dax_set_holder(dax_logdev, NULL, NULL);
fs_put_dax(dax_logdev);
}
if (mp->m_rtdev_targp) {
@@ -360,9 +369,11 @@ xfs_close_devices(

xfs_free_buftarg(mp->m_rtdev_targp);
xfs_blkdev_put(rtdev);
+ fs_dax_set_holder(dax_rtdev, NULL, NULL);
fs_put_dax(dax_rtdev);
}
xfs_free_buftarg(mp->m_ddev_targp);
+ fs_dax_set_holder(dax_ddev, NULL, NULL);
fs_put_dax(dax_ddev);
}

@@ -386,6 +397,7 @@ xfs_open_devices(
struct block_device *logdev = NULL, *rtdev = NULL;
int error;

+ fs_dax_set_holder(dax_ddev, mp, &xfs_dax_holder_operations);
/*
* Open real time and log devices - order is important.
*/
@@ -394,6 +406,9 @@ xfs_open_devices(
if (error)
goto out;
dax_logdev = fs_dax_get_by_bdev(logdev);
+ if (dax_logdev != dax_ddev)
+ fs_dax_set_holder(dax_logdev, mp,
+ &xfs_dax_holder_operations);
}

if (mp->m_rtname) {
@@ -408,6 +423,7 @@ xfs_open_devices(
goto out_close_rtdev;
}
dax_rtdev = fs_dax_get_by_bdev(rtdev);
+ fs_dax_set_holder(dax_rtdev, mp, &xfs_dax_holder_operations);
}

/*
@@ -1070,6 +1086,125 @@ xfs_fs_free_cached_objects(
return xfs_reclaim_inodes_nr(XFS_M(sb), sc->nr_to_scan);
}

+static int
+xfs_corrupt_helper(
+ struct xfs_btree_cur *cur,
+ struct xfs_rmap_irec *rec,
+ void *data)
+{
+ struct xfs_inode *ip;
+ struct address_space *mapping;
+ int error = 0, *flags = data, i;
+
+ if (XFS_RMAP_NON_INODE_OWNER(rec->rm_owner) ||
+ (rec->rm_flags & (XFS_RMAP_ATTR_FORK | XFS_RMAP_BMBT_BLOCK))) {
+ // TODO check and try to fix metadata
+ xfs_force_shutdown(cur->bc_mp, SHUTDOWN_CORRUPT_META);
+ return -EFSCORRUPTED;
+ }
+
+ /* Get files that incore, filter out others that are not in use. */
+ error = xfs_iget(cur->bc_mp, cur->bc_tp, rec->rm_owner, XFS_IGET_INCORE,
+ 0, &ip);
+ if (error)
+ return error;
+
+ mapping = VFS_I(ip)->i_mapping;
+ if (IS_ENABLED(CONFIG_MEMORY_FAILURE)) {
+ for (i = 0; i < rec->rm_blockcount; i++) {
+ error = mf_dax_kill_procs(mapping, rec->rm_offset + i,
+ *flags);
+ if (error)
+ break;
+ }
+ }
+ // TODO try to fix data
+ xfs_irele(ip);
+
+ return error;
+}
+
+static loff_t
+xfs_dax_bdev_offset(
+ struct xfs_mount *mp,
+ struct dax_device *dax_dev,
+ loff_t disk_offset)
+{
+ struct block_device *bdev;
+
+ if (mp->m_ddev_targp->bt_daxdev == dax_dev)
+ bdev = mp->m_ddev_targp->bt_bdev;
+ else if (mp->m_logdev_targp->bt_daxdev == dax_dev)
+ bdev = mp->m_logdev_targp->bt_bdev;
+ else
+ bdev = mp->m_rtdev_targp->bt_bdev;
+
+ return disk_offset - (get_start_sect(bdev) << SECTOR_SHIFT);
+}
+
+static int
+xfs_dax_notify_failure(
+ struct dax_device *dax_dev,
+ loff_t offset,
+ size_t len,
+ void *data)
+{
+ struct xfs_mount *mp = fs_dax_get_holder(dax_dev);
+ struct xfs_trans *tp = NULL;
+ struct xfs_btree_cur *cur = NULL;
+ struct xfs_buf *agf_bp = NULL;
+ struct xfs_rmap_irec rmap_low, rmap_high;
+ loff_t bdev_offset = xfs_dax_bdev_offset(mp, dax_dev,
+ offset);
+ xfs_fsblock_t fsbno = XFS_B_TO_FSB(mp, bdev_offset);
+ xfs_filblks_t bcnt = XFS_B_TO_FSB(mp, len);
+ xfs_agnumber_t agno = XFS_FSB_TO_AGNO(mp, fsbno);
+ xfs_agblock_t agbno = XFS_FSB_TO_AGBNO(mp, fsbno);
+ int error = 0;
+
+ if (mp->m_logdev_targp && mp->m_logdev_targp->bt_daxdev == dax_dev &&
+ mp->m_logdev_targp != mp->m_ddev_targp) {
+ xfs_err(mp, "ondisk log corrupt, shutting down fs!");
+ xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_META);
+ return -EFSCORRUPTED;
+ }
+
+ if (!xfs_sb_version_hasrmapbt(&mp->m_sb)) {
+ xfs_warn(mp, "notify_failure() needs rmapbt enabled!");
+ return -EOPNOTSUPP;
+ }
+
+ error = xfs_trans_alloc_empty(mp, &tp);
+ if (error)
+ return error;
+
+ error = xfs_alloc_read_agf(mp, tp, agno, 0, &agf_bp);
+ if (error)
+ goto out_cancel_tp;
+
+ cur = xfs_rmapbt_init_cursor(mp, tp, agf_bp, agf_bp->b_pag);
+
+ /* Construct a range for rmap query */
+ memset(&rmap_low, 0, sizeof(rmap_low));
+ memset(&rmap_high, 0xFF, sizeof(rmap_high));
+ rmap_low.rm_startblock = rmap_high.rm_startblock = agbno;
+ rmap_low.rm_blockcount = rmap_high.rm_blockcount = bcnt;
+
+ error = xfs_rmap_query_range(cur, &rmap_low, &rmap_high,
+ xfs_corrupt_helper, data);
+
+ xfs_btree_del_cursor(cur, error);
+ xfs_trans_brelse(tp, agf_bp);
+
+out_cancel_tp:
+ xfs_trans_cancel(tp);
+ return error;
+}
+
+static const struct dax_holder_operations xfs_dax_holder_operations = {
+ .notify_failure = xfs_dax_notify_failure,
+};
+
static const struct super_operations xfs_super_operations = {
.alloc_inode = xfs_fs_alloc_inode,
.destroy_inode = xfs_fs_destroy_inode,
diff --git a/include/linux/dax.h b/include/linux/dax.h
index 359e809516b8..c8a188b76031 100644
--- a/include/linux/dax.h
+++ b/include/linux/dax.h
@@ -160,6 +160,9 @@ static inline void fs_put_dax(struct dax_device *dax_dev)
}

struct dax_device *fs_dax_get_by_bdev(struct block_device *bdev);
+void fs_dax_set_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops);
+void *fs_dax_get_holder(struct dax_device *dax_dev);
int dax_writeback_mapping_range(struct address_space *mapping,
struct dax_device *dax_dev, struct writeback_control *wbc);

@@ -191,6 +194,16 @@ static inline struct dax_device *fs_dax_get_by_bdev(struct block_device *bdev)
return NULL;
}

+static inline void fs_dax_set_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops)
+{
+}
+
+static inline void *fs_dax_get_holder(struct dax_device *dax_dev)
+{
+ return NULL;
+}
+
static inline struct page *dax_layout_busy_page(struct address_space *mapping)
{
return NULL;
--
2.32.0




2021-08-06 02:47:39

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 8/9] md: Implement dax_holder_operations

On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> This is the case where the holder represents a mapped device, or a list
> of mapped devices more exactly(because it is possible to create more
> than one mapped device on one pmem device).

Could you share how do you test this scenario?

thanks,
-jane

>
> Find out which mapped device the offset belongs to, and translate the
> offset from target device to mapped device. When it is done, call
> dax_corrupted_range() for the holder of this mapped device.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
> ---
> drivers/md/dm.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 125 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/md/dm.c b/drivers/md/dm.c
> index 2c5f9e585211..a35b9a97a73f 100644
> --- a/drivers/md/dm.c
> +++ b/drivers/md/dm.c
> @@ -626,7 +626,11 @@ static void dm_put_live_table_fast(struct mapped_device *md) __releases(RCU)
> }
>
> static char *_dm_claim_ptr = "I belong to device-mapper";
> -
> +static const struct dax_holder_operations dm_dax_holder_ops;
> +struct dm_holder {
> + struct list_head list;
> + struct mapped_device *md;
> +};
> /*
> * Open a table device so we can use it as a map destination.
> */
> @@ -634,6 +638,8 @@ static int open_table_device(struct table_device *td, dev_t dev,
> struct mapped_device *md)
> {
> struct block_device *bdev;
> + struct list_head *holders;
> + struct dm_holder *holder;
>
> int r;
>
> @@ -651,6 +657,19 @@ static int open_table_device(struct table_device *td, dev_t dev,
>
> td->dm_dev.bdev = bdev;
> td->dm_dev.dax_dev = dax_get_by_host(bdev->bd_disk->disk_name);
> + if (!td->dm_dev.dax_dev)
> + return 0;
> +
> + holders = dax_get_holder(td->dm_dev.dax_dev);
> + if (!holders) {
> + holders = kmalloc(sizeof(*holders), GFP_KERNEL);
> + INIT_LIST_HEAD(holders);
> + dax_set_holder(td->dm_dev.dax_dev, holders, &dm_dax_holder_ops);
> + }
> + holder = kmalloc(sizeof(*holder), GFP_KERNEL);
> + holder->md = md;
> + list_add_tail(&holder->list, holders);
> +
> return 0;
> }
>
> @@ -659,9 +678,27 @@ static int open_table_device(struct table_device *td, dev_t dev,
> */
> static void close_table_device(struct table_device *td, struct mapped_device *md)
> {
> + struct list_head *holders;
> + struct dm_holder *holder, *n;
> +
> if (!td->dm_dev.bdev)
> return;
>
> + holders = dax_get_holder(td->dm_dev.dax_dev);
> + if (holders) {
> + list_for_each_entry_safe(holder, n, holders, list) {
> + if (holder->md == md) {
> + list_del(&holder->list);
> + kfree(holder);
> + }
> + }
> + if (list_empty(holders)) {
> + kfree(holders);
> + /* unset dax_device's holder_data */
> + dax_set_holder(td->dm_dev.dax_dev, NULL, NULL);
> + }
> + }
> +
> bd_unlink_disk_holder(td->dm_dev.bdev, dm_disk(md));
> blkdev_put(td->dm_dev.bdev, td->dm_dev.mode | FMODE_EXCL);
> put_dax(td->dm_dev.dax_dev);
> @@ -1115,6 +1152,89 @@ static int dm_dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
> return ret;
> }
>
> +#if IS_ENABLED(CONFIG_DAX_DRIVER)
> +struct corrupted_hit_info {
> + struct dax_device *dax_dev;
> + sector_t offset;
> +};
> +
> +static int dm_blk_corrupted_hit(struct dm_target *ti, struct dm_dev *dev,
> + sector_t start, sector_t count, void *data)
> +{
> + struct corrupted_hit_info *bc = data;
> +
> + return bc->dax_dev == (void *)dev->dax_dev &&
> + (start <= bc->offset && bc->offset < start + count);
> +}
> +
> +struct corrupted_do_info {
> + size_t length;
> + void *data;
> +};
> +
> +static int dm_blk_corrupted_do(struct dm_target *ti, struct block_device *bdev,
> + sector_t sector, void *data)
> +{
> + struct mapped_device *md = ti->table->md;
> + struct corrupted_do_info *bc = data;
> +
> + return dax_holder_notify_failure(md->dax_dev, to_bytes(sector),
> + bc->length, bc->data);
> +}
> +
> +static int dm_dax_notify_failure_one(struct mapped_device *md,
> + struct dax_device *dax_dev,
> + loff_t offset, size_t length, void *data)
> +{
> + struct dm_table *map;
> + struct dm_target *ti;
> + sector_t sect = to_sector(offset);
> + struct corrupted_hit_info hi = {dax_dev, sect};
> + struct corrupted_do_info di = {length, data};
> + int srcu_idx, i, rc = -ENODEV;
> +
> + map = dm_get_live_table(md, &srcu_idx);
> + if (!map)
> + return rc;
> +
> + /*
> + * find the target device, and then translate the offset of this target
> + * to the whole mapped device.
> + */
> + for (i = 0; i < dm_table_get_num_targets(map); i++) {
> + ti = dm_table_get_target(map, i);
> + if (!(ti->type->iterate_devices && ti->type->rmap))
> + continue;
> + if (!ti->type->iterate_devices(ti, dm_blk_corrupted_hit, &hi))
> + continue;
> +
> + rc = ti->type->rmap(ti, sect, dm_blk_corrupted_do, &di);
> + break;
> + }
> +
> + dm_put_live_table(md, srcu_idx);
> + return rc;
> +}
> +
> +static int dm_dax_notify_failure(struct dax_device *dax_dev,
> + loff_t offset, size_t length, void *data)
> +{
> + struct dm_holder *holder;
> + struct list_head *holders = dax_get_holder(dax_dev);
> + int rc = -ENODEV;
> +
> + list_for_each_entry(holder, holders, list) {
> + rc = dm_dax_notify_failure_one(holder->md, dax_dev, offset,
> + length, data);
> + if (rc != -ENODEV)
> + break;
> + }
> + return rc;
> +}
> +#else
> +#define dm_dax_notify_failure NULL
> +#endif
> +
> /*
> * A target may call dm_accept_partial_bio only from the map routine. It is
> * allowed for all bio types except REQ_PREFLUSH, REQ_OP_ZONE_* zone management
> @@ -3057,6 +3177,10 @@ static const struct dax_operations dm_dax_ops = {
> .zero_page_range = dm_dax_zero_page_range,
> };
>
> +static const struct dax_holder_operations dm_dax_holder_ops = {
> + .notify_failure = dm_dax_notify_failure,
> +};
> +
> /*
> * module hooks
> */
>

2021-08-06 02:49:07

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 2/9] dax: Introduce holder for dax_device


On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> --- a/drivers/dax/super.c
> +++ b/drivers/dax/super.c
> @@ -214,6 +214,8 @@ enum dax_device_flags {
> * @cdev: optional character interface for "device dax"
> * @host: optional name for lookups where the device path is not available
> * @private: dax driver private data
> + * @holder_rwsem: prevent unregistration while holder_ops is in progress
> + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> * @flags: state and boolean properties

Perhaps add two documentary lines for @ops and @holder_ops?
> */
> struct dax_device {
> @@ -222,8 +224,11 @@ struct dax_device {
> struct cdev cdev;
> const char *host;
> void *private;
> + struct rw_semaphore holder_rwsem;
> + void *holder_data;
> unsigned long flags;
> const struct dax_operations *ops;
> + const struct dax_holder_operations *holder_ops;
> };

thanks,
-jane

2021-08-06 02:50:15

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS


On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> + mapping = VFS_I(ip)->i_mapping;
> + if (IS_ENABLED(CONFIG_MEMORY_FAILURE)) {
> + for (i = 0; i < rec->rm_blockcount; i++) {
> + error = mf_dax_kill_procs(mapping, rec->rm_offset + i,
> + *flags);
> + if (error)
> + break;
> + }
> + }

If a poison is injected to a PMD dax page, after consuming the poison,
how many SIGBUS signals are expected to be sent to the process?

thanks,
-jane

2021-08-06 02:51:19

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

The filesystem part of the pmem failure handling is at minimum built
on PAGE_SIZE granularity - an inheritance from general memory_failure
handling. However, with Intel's DCPMEM technology, the error blast
radius is no more than 256bytes, and might get smaller with future
hardware generation, also advanced atomic 64B write to clear the poison.
But I don't see any of that could be incorporated in, given that the
filesystem is notified a corruption with pfn, rather than an exact
address.

So I guess this question is also for Dan: how to avoid unnecessarily
repairing a PMD range for a 256B corrupt range going forward?

thanks,
-jane


On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> When memory-failure occurs, we call this function which is implemented
> by each kind of devices. For the fsdax case, pmem device driver
> implements it. Pmem device driver will find out the filesystem in which
> the corrupted page located in. And finally call filesystem handler to
> deal with this error.
>
> The filesystem will try to recover the corrupted data if necessary.

2021-08-06 07:04:44

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 9/9] fsdax: add exception for reflinked files


On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> For reflinked files, one dax page may be associated more than once with
> different fime mapping and index. It will report warning. Now, since ^^^typo here?

> we have introduced dax-RMAP for this case and also have to keep its
> functionality for other filesystems who are not support rmap, I add this
> exception here.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
> ---
> fs/dax.c | 14 ++++++++------
> 1 file changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/fs/dax.c b/fs/dax.c
> index dce6307a12eb..f5910d178695 100644
> --- a/fs/dax.c
> +++ b/fs/dax.c
> @@ -352,9 +352,10 @@ static void dax_associate_entry(void *entry, struct address_space *mapping,
> for_each_mapped_pfn(entry, pfn) {
> struct page *page = pfn_to_page(pfn);
>
> - WARN_ON_ONCE(page->mapping);
> - page->mapping = mapping;
> - page->index = index + i++;
> + if (!page->mapping) {
> + page->mapping = mapping;
> + page->index = index + i++;
> + }
> }
> }
>
> @@ -370,9 +371,10 @@ static void dax_disassociate_entry(void *entry, struct address_space *mapping,
> struct page *page = pfn_to_page(pfn);
>
> WARN_ON_ONCE(trunc && page_ref_count(page) > 1);
> - WARN_ON_ONCE(page->mapping && page->mapping != mapping);
> - page->mapping = NULL;
> - page->index = 0;
> + if (page->mapping == mapping) {
> + page->mapping = NULL;
> + page->index = 0;
> + }
> }
> }
>
>

2021-08-06 07:13:57

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 5/9] mm: Introduce mf_dax_kill_procs() for fsdax case

> @@ -134,6 +134,12 @@ static int hwpoison_filter_dev(struct page *p)
> if (PageSlab(p))
> return -EINVAL;
>
> + if (pfn_valid(page_to_pfn(p))) {
> + if (is_device_fsdax_page(p))
> + return 0;
> + } else
> + return -EINVAL;
> +

Just curious, what is the rational for preventing dax pages from
participating in hwpoison_filter test?

> +int mf_dax_kill_procs(struct address_space *mapping, pgoff_t index, int flags)
> +{
> + LIST_HEAD(to_kill);
> + /* load the pfn of the dax mapping file */
> + unsigned long pfn = dax_load_pfn(mapping, index);
> +
> + /* the failure pfn may not actually be mmapped, so no need to
> + * unmap and kill procs */
> + if (!pfn)
> + return 0;

what if a process has mapped the file but never faulted in, but did ask
for early signal, will it be excluded due to lack of the 'pfn' association?

thanks,
-jane

2021-08-06 07:20:17

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 3/9] mm: factor helpers for memory_failure_dev_pagemap


On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> - /*
> - * Prevent the inode from being freed while we are interrogating
> - * the address_space, typically this would be handled by
> - * lock_page(), but dax pages do not use the page lock. This
> - * also prevents changes to the mapping of this pfn until
> - * poison signaling is complete.
> - */
> - cookie = dax_lock_page(page);
> - if (!cookie)
> - goto out;
> -
> if (hwpoison_filter(page)) {
> rc = 0;
> - goto unlock;
> + goto out;
> }

why isn't dax_lock_page() needed for hwpoison_filter() check?

thanks,
-jane

2021-08-16 17:22:56

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

Hi, ShiYang,

So I applied the v6 patch series to my 5.14-rc3 as it's what you
indicated is what v6 was based at, and injected a hardware poison.

I'm seeing the same problem that was reported a while ago after the
poison was consumed - in the SIGBUS payload, the si_addr is missing:

** SIGBUS(7): canjmp=1, whichstep=0, **
** si_addr(0x(nil)), si_lsb(0xC), si_code(0x4, BUS_MCEERR_AR) **

The si_addr ought to be 0x7f6568000000 - the vaddr of the first page
in this case.

Something is not right...

thanks,
-jane


On 8/5/2021 6:17 PM, Jane Chu wrote:
> The filesystem part of the pmem failure handling is at minimum built
> on PAGE_SIZE granularity - an inheritance from general memory_failure
> handling.  However, with Intel's DCPMEM technology, the error blast
> radius is no more than 256bytes, and might get smaller with future
> hardware generation, also advanced atomic 64B write to clear the poison.
> But I don't see any of that could be incorporated in, given that the
> filesystem is notified a corruption with pfn, rather than an exact
> address.
>
> So I guess this question is also for Dan: how to avoid unnecessarily
> repairing a PMD range for a 256B corrupt range going forward?
>
> thanks,
> -jane
>
>
> On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
>> When memory-failure occurs, we call this function which is implemented
>> by each kind of devices.  For the fsdax case, pmem device driver
>> implements it.  Pmem device driver will find out the filesystem in which
>> the corrupted page located in.  And finally call filesystem handler to
>> deal with this error.
>>
>> The filesystem will try to recover the corrupted data if necessary.
>

2021-08-17 01:45:53

by Shiyang Ruan

[permalink] [raw]
Subject: RE: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

> -----Original Message-----
> From: Jane Chu <[email protected]>
> Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()
>
> Hi, ShiYang,
>
> So I applied the v6 patch series to my 5.14-rc3 as it's what you indicated is what
> v6 was based at, and injected a hardware poison.
>
> I'm seeing the same problem that was reported a while ago after the poison
> was consumed - in the SIGBUS payload, the si_addr is missing:
>
> ** SIGBUS(7): canjmp=1, whichstep=0, **
> ** si_addr(0x(nil)), si_lsb(0xC), si_code(0x4, BUS_MCEERR_AR) **
>
> The si_addr ought to be 0x7f6568000000 - the vaddr of the first page in this
> case.
>
> Something is not right...

Hi Jane,

Sorry for late reply. Thanks for testing. This address should have been reported in my code. I'll check why it's finally nil.


--
Thanks.
Ruan.

>
> thanks,
> -jane
>
>
> On 8/5/2021 6:17 PM, Jane Chu wrote:
> > The filesystem part of the pmem failure handling is at minimum built
> > on PAGE_SIZE granularity - an inheritance from general memory_failure
> > handling.  However, with Intel's DCPMEM technology, the error blast
> > radius is no more than 256bytes, and might get smaller with future
> > hardware generation, also advanced atomic 64B write to clear the poison.
> > But I don't see any of that could be incorporated in, given that the
> > filesystem is notified a corruption with pfn, rather than an exact
> > address.
> >
> > So I guess this question is also for Dan: how to avoid unnecessarily
> > repairing a PMD range for a 256B corrupt range going forward?
> >
> > thanks,
> > -jane
> >
> >
> > On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> >> When memory-failure occurs, we call this function which is
> >> implemented by each kind of devices.  For the fsdax case, pmem device
> >> driver implements it.  Pmem device driver will find out the
> >> filesystem in which the corrupted page located in.  And finally call
> >> filesystem handler to deal with this error.
> >>
> >> The filesystem will try to recover the corrupted data if necessary.
> >

2021-08-17 01:53:40

by Shiyang Ruan

[permalink] [raw]
Subject: RE: [PATCH RESEND v6 2/9] dax: Introduce holder for dax_device


> -----Original Message-----
> From: Jane Chu <[email protected]>
> Subject: Re: [PATCH RESEND v6 2/9] dax: Introduce holder for dax_device
>
>
> On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> > --- a/drivers/dax/super.c
> > +++ b/drivers/dax/super.c
> > @@ -214,6 +214,8 @@ enum dax_device_flags {
> > * @cdev: optional character interface for "device dax"
> > * @host: optional name for lookups where the device path is not available
> > * @private: dax driver private data
> > + * @holder_rwsem: prevent unregistration while holder_ops is in progress
> > + * @holder_data: holder of a dax_device: could be filesystem or mapped
> device
> > * @flags: state and boolean properties
>
> Perhaps add two documentary lines for @ops and @holder_ops?

OK. I'll add them in next version.

--
Thanks,
Ruan.

> > */
> > struct dax_device {
> > @@ -222,8 +224,11 @@ struct dax_device {
> > struct cdev cdev;
> > const char *host;
> > void *private;
> > + struct rw_semaphore holder_rwsem;
> > + void *holder_data;
> > unsigned long flags;
> > const struct dax_operations *ops;
> > + const struct dax_holder_operations *holder_ops;
> > };
>
> thanks,
> -jane

2021-08-17 02:01:01

by Shiyang Ruan

[permalink] [raw]
Subject: RE: [PATCH RESEND v6 8/9] md: Implement dax_holder_operations

> -----Original Message-----
> From: Jane Chu <[email protected]>
> Subject: Re: [PATCH RESEND v6 8/9] md: Implement dax_holder_operations
>
> On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> > This is the case where the holder represents a mapped device, or a
> > list of mapped devices more exactly(because it is possible to create
> > more than one mapped device on one pmem device).
>
> Could you share how do you test this scenario?

Do you mean "more than one mapped device on one pmem device"?

1. Create 2 partitions on a pmem device(fsdax mode).
2. Create LVM(one LV) on each partition.
3. Create xfs filesystem on each LVM.
4. Memory failure on this pmem.

In this case, there are 2 LVMs on one pmem device. So we should register this 2 LVMs in dax_holder, and iterate them when notifying the failure.

--
Thanks,
Ruan.

>
> thanks,
> -jane
>
> >
> > Find out which mapped device the offset belongs to, and translate the
> > offset from target device to mapped device. When it is done, call
> > dax_corrupted_range() for the holder of this mapped device.
> >
> > Signed-off-by: Shiyang Ruan <[email protected]>
> > ---
> > drivers/md/dm.c | 126
> +++++++++++++++++++++++++++++++++++++++++++++++-
> > 1 file changed, 125 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/md/dm.c b/drivers/md/dm.c index
> > 2c5f9e585211..a35b9a97a73f 100644
> > --- a/drivers/md/dm.c
> > +++ b/drivers/md/dm.c
> > @@ -626,7 +626,11 @@ static void dm_put_live_table_fast(struct
> mapped_device *md) __releases(RCU)
> > }
> >
> > static char *_dm_claim_ptr = "I belong to device-mapper";
> > -
> > +static const struct dax_holder_operations dm_dax_holder_ops; struct
> > +dm_holder {
> > + struct list_head list;
> > + struct mapped_device *md;
> > +};
> > /*
> > * Open a table device so we can use it as a map destination.
> > */
> > @@ -634,6 +638,8 @@ static int open_table_device(struct table_device *td,
> dev_t dev,
> > struct mapped_device *md)
> > {
> > struct block_device *bdev;
> > + struct list_head *holders;
> > + struct dm_holder *holder;
> >
> > int r;
> >
> > @@ -651,6 +657,19 @@ static int open_table_device(struct table_device
> > *td, dev_t dev,
> >
> > td->dm_dev.bdev = bdev;
> > td->dm_dev.dax_dev = dax_get_by_host(bdev->bd_disk->disk_name);
> > + if (!td->dm_dev.dax_dev)
> > + return 0;
> > +
> > + holders = dax_get_holder(td->dm_dev.dax_dev);
> > + if (!holders) {
> > + holders = kmalloc(sizeof(*holders), GFP_KERNEL);
> > + INIT_LIST_HEAD(holders);
> > + dax_set_holder(td->dm_dev.dax_dev, holders, &dm_dax_holder_ops);
> > + }
> > + holder = kmalloc(sizeof(*holder), GFP_KERNEL);
> > + holder->md = md;
> > + list_add_tail(&holder->list, holders);
> > +
> > return 0;
> > }
> >
> > @@ -659,9 +678,27 @@ static int open_table_device(struct table_device *td,
> dev_t dev,
> > */
> > static void close_table_device(struct table_device *td, struct
> mapped_device *md)
> > {
> > + struct list_head *holders;
> > + struct dm_holder *holder, *n;
> > +
> > if (!td->dm_dev.bdev)
> > return;
> >
> > + holders = dax_get_holder(td->dm_dev.dax_dev);
> > + if (holders) {
> > + list_for_each_entry_safe(holder, n, holders, list) {
> > + if (holder->md == md) {
> > + list_del(&holder->list);
> > + kfree(holder);
> > + }
> > + }
> > + if (list_empty(holders)) {
> > + kfree(holders);
> > + /* unset dax_device's holder_data */
> > + dax_set_holder(td->dm_dev.dax_dev, NULL, NULL);
> > + }
> > + }
> > +
> > bd_unlink_disk_holder(td->dm_dev.bdev, dm_disk(md));
> > blkdev_put(td->dm_dev.bdev, td->dm_dev.mode | FMODE_EXCL);
> > put_dax(td->dm_dev.dax_dev);
> > @@ -1115,6 +1152,89 @@ static int dm_dax_zero_page_range(struct
> dax_device *dax_dev, pgoff_t pgoff,
> > return ret;
> > }
> >
> > +#if IS_ENABLED(CONFIG_DAX_DRIVER)
> > +struct corrupted_hit_info {
> > + struct dax_device *dax_dev;
> > + sector_t offset;
> > +};
> > +
> > +static int dm_blk_corrupted_hit(struct dm_target *ti, struct dm_dev *dev,
> > + sector_t start, sector_t count, void *data) {
> > + struct corrupted_hit_info *bc = data;
> > +
> > + return bc->dax_dev == (void *)dev->dax_dev &&
> > + (start <= bc->offset && bc->offset < start + count); }
> > +
> > +struct corrupted_do_info {
> > + size_t length;
> > + void *data;
> > +};
> > +
> > +static int dm_blk_corrupted_do(struct dm_target *ti, struct block_device
> *bdev,
> > + sector_t sector, void *data) {
> > + struct mapped_device *md = ti->table->md;
> > + struct corrupted_do_info *bc = data;
> > +
> > + return dax_holder_notify_failure(md->dax_dev, to_bytes(sector),
> > + bc->length, bc->data);
> > +}
> > +
> > +static int dm_dax_notify_failure_one(struct mapped_device *md,
> > + struct dax_device *dax_dev,
> > + loff_t offset, size_t length, void *data) {
> > + struct dm_table *map;
> > + struct dm_target *ti;
> > + sector_t sect = to_sector(offset);
> > + struct corrupted_hit_info hi = {dax_dev, sect};
> > + struct corrupted_do_info di = {length, data};
> > + int srcu_idx, i, rc = -ENODEV;
> > +
> > + map = dm_get_live_table(md, &srcu_idx);
> > + if (!map)
> > + return rc;
> > +
> > + /*
> > + * find the target device, and then translate the offset of this target
> > + * to the whole mapped device.
> > + */
> > + for (i = 0; i < dm_table_get_num_targets(map); i++) {
> > + ti = dm_table_get_target(map, i);
> > + if (!(ti->type->iterate_devices && ti->type->rmap))
> > + continue;
> > + if (!ti->type->iterate_devices(ti, dm_blk_corrupted_hit, &hi))
> > + continue;
> > +
> > + rc = ti->type->rmap(ti, sect, dm_blk_corrupted_do, &di);
> > + break;
> > + }
> > +
> > + dm_put_live_table(md, srcu_idx);
> > + return rc;
> > +}
> > +
> > +static int dm_dax_notify_failure(struct dax_device *dax_dev,
> > + loff_t offset, size_t length, void *data) {
> > + struct dm_holder *holder;
> > + struct list_head *holders = dax_get_holder(dax_dev);
> > + int rc = -ENODEV;
> > +
> > + list_for_each_entry(holder, holders, list) {
> > + rc = dm_dax_notify_failure_one(holder->md, dax_dev, offset,
> > + length, data);
> > + if (rc != -ENODEV)
> > + break;
> > + }
> > + return rc;
> > +}
> > +#else
> > +#define dm_dax_notify_failure NULL
> > +#endif
> > +
> > /*
> > * A target may call dm_accept_partial_bio only from the map routine.
> It is
> > * allowed for all bio types except REQ_PREFLUSH, REQ_OP_ZONE_* zone
> > management @@ -3057,6 +3177,10 @@ static const struct dax_operations
> dm_dax_ops = {
> > .zero_page_range = dm_dax_zero_page_range,
> > };
> >
> > +static const struct dax_holder_operations dm_dax_holder_ops = {
> > + .notify_failure = dm_dax_notify_failure, };
> > +
> > /*
> > * module hooks
> > */
> >

2021-08-18 05:45:33

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

More information -

On 8/16/2021 10:20 AM, Jane Chu wrote:
> Hi, ShiYang,
>
> So I applied the v6 patch series to my 5.14-rc3 as it's what you
> indicated is what v6 was based at, and injected a hardware poison.
>
> I'm seeing the same problem that was reported a while ago after the
> poison was consumed - in the SIGBUS payload, the si_addr is missing:
>
> ** SIGBUS(7): canjmp=1, whichstep=0, **
> ** si_addr(0x(nil)), si_lsb(0xC), si_code(0x4, BUS_MCEERR_AR) **
>
> The si_addr ought to be 0x7f6568000000 - the vaddr of the first page
> in this case.

The failure came from here :

[PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS

+static int
+xfs_dax_notify_failure(
...
+ if (!xfs_sb_version_hasrmapbt(&mp->m_sb)) {
+ xfs_warn(mp, "notify_failure() needs rmapbt enabled!");
+ return -EOPNOTSUPP;
+ }

I am not familiar with XFS, but I have a few questions I hope to get
answers -

1) What does it take and cost to make
xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?

2) For a running environment that fails the above check, is it
okay to leave the poison handle in limbo and why?

3) If the above regression is not acceptable, any potential remedy?

thanks!
-jane


>
> Something is not right...
>
> thanks,
> -jane
>
>
> On 8/5/2021 6:17 PM, Jane Chu wrote:
>> The filesystem part of the pmem failure handling is at minimum built
>> on PAGE_SIZE granularity - an inheritance from general memory_failure
>> handling.  However, with Intel's DCPMEM technology, the error blast
>> radius is no more than 256bytes, and might get smaller with future
>> hardware generation, also advanced atomic 64B write to clear the poison.
>> But I don't see any of that could be incorporated in, given that the
>> filesystem is notified a corruption with pfn, rather than an exact
>> address.
>>
>> So I guess this question is also for Dan: how to avoid unnecessarily
>> repairing a PMD range for a 256B corrupt range going forward?
>>
>> thanks,
>> -jane
>>
>>
>> On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
>>> When memory-failure occurs, we call this function which is implemented
>>> by each kind of devices.  For the fsdax case, pmem device driver
>>> implements it.  Pmem device driver will find out the filesystem in which
>>> the corrupted page located in.  And finally call filesystem handler to
>>> deal with this error.
>>>
>>> The filesystem will try to recover the corrupted data if necessary.
>>
>

2021-08-18 06:11:07

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()


On 8/17/2021 10:43 PM, Jane Chu wrote:
> More information -
>
> On 8/16/2021 10:20 AM, Jane Chu wrote:
>> Hi, ShiYang,
>>
>> So I applied the v6 patch series to my 5.14-rc3 as it's what you
>> indicated is what v6 was based at, and injected a hardware poison.
>>
>> I'm seeing the same problem that was reported a while ago after the
>> poison was consumed - in the SIGBUS payload, the si_addr is missing:
>>
>> ** SIGBUS(7): canjmp=1, whichstep=0, **
>> ** si_addr(0x(nil)), si_lsb(0xC), si_code(0x4, BUS_MCEERR_AR) **
>>
>> The si_addr ought to be 0x7f6568000000 - the vaddr of the first page
>> in this case.
>
> The failure came from here :
>
> [PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS
>
> +static int
> +xfs_dax_notify_failure(
> ...
> +    if (!xfs_sb_version_hasrmapbt(&mp->m_sb)) {
> +        xfs_warn(mp, "notify_failure() needs rmapbt enabled!");
> +        return -EOPNOTSUPP;
> +    }
>
> I am not familiar with XFS, but I have a few questions I hope to get
> answers -
>
> 1) What does it take and cost to make
>    xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?
>
> 2) For a running environment that fails the above check, is it
>    okay to leave the poison handle in limbo and why?
>
> 3) If the above regression is not acceptable, any potential remedy?

How about moving the check to prior to the notifier registration?
And register only if the check is passed? This seems better
than an alternative which is to fall back to the legacy memory_failure
handling in case the filesystem returns -EOPNOTSUPP.

thanks,
-jane

>
> thanks!
> -jane
>
>
>>
>> Something is not right...
>>
>> thanks,
>> -jane
>>
>>
>> On 8/5/2021 6:17 PM, Jane Chu wrote:
>>> The filesystem part of the pmem failure handling is at minimum built
>>> on PAGE_SIZE granularity - an inheritance from general memory_failure
>>> handling.  However, with Intel's DCPMEM technology, the error blast
>>> radius is no more than 256bytes, and might get smaller with future
>>> hardware generation, also advanced atomic 64B write to clear the poison.
>>> But I don't see any of that could be incorporated in, given that the
>>> filesystem is notified a corruption with pfn, rather than an exact
>>> address.
>>>
>>> So I guess this question is also for Dan: how to avoid unnecessarily
>>> repairing a PMD range for a 256B corrupt range going forward?
>>>
>>> thanks,
>>> -jane
>>>
>>>
>>> On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
>>>> When memory-failure occurs, we call this function which is implemented
>>>> by each kind of devices.  For the fsdax case, pmem device driver
>>>> implements it.  Pmem device driver will find out the filesystem in
>>>> which
>>>> the corrupted page located in.  And finally call filesystem handler to
>>>> deal with this error.
>>>>
>>>> The filesystem will try to recover the corrupted data if necessary.
>>>
>>
>

2021-08-18 07:53:37

by Shiyang Ruan

[permalink] [raw]
Subject: RE: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()



> -----Original Message-----
> From: Jane Chu <[email protected]>
> Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()
>
>
> On 8/17/2021 10:43 PM, Jane Chu wrote:
> > More information -
> >
> > On 8/16/2021 10:20 AM, Jane Chu wrote:
> >> Hi, ShiYang,
> >>
> >> So I applied the v6 patch series to my 5.14-rc3 as it's what you
> >> indicated is what v6 was based at, and injected a hardware poison.
> >>
> >> I'm seeing the same problem that was reported a while ago after the
> >> poison was consumed - in the SIGBUS payload, the si_addr is missing:
> >>
> >> ** SIGBUS(7): canjmp=1, whichstep=0, **
> >> ** si_addr(0x(nil)), si_lsb(0xC), si_code(0x4, BUS_MCEERR_AR) **
> >>
> >> The si_addr ought to be 0x7f6568000000 - the vaddr of the first page
> >> in this case.
> >
> > The failure came from here :
> >
> > [PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS
> >
> > +static int
> > +xfs_dax_notify_failure(
> > ...
> > +    if (!xfs_sb_version_hasrmapbt(&mp->m_sb)) {
> > +        xfs_warn(mp, "notify_failure() needs rmapbt enabled!");
> > +        return -EOPNOTSUPP;
> > +    }
> >
> > I am not familiar with XFS, but I have a few questions I hope to get
> > answers -
> >
> > 1) What does it take and cost to make
> >    xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?

Enable rmpabt feature when making xfs filesystem
`mkfs.xfs -m rmapbt=1 /path/to/device`
BTW, reflink is enabled by default.

> >
> > 2) For a running environment that fails the above check, is it
> >    okay to leave the poison handle in limbo and why?
It will fall back to the old handler. I think you have already known it.

> >
> > 3) If the above regression is not acceptable, any potential remedy?
>
> How about moving the check to prior to the notifier registration?
> And register only if the check is passed? This seems better than an
> alternative which is to fall back to the legacy memory_failure handling in case
> the filesystem returns -EOPNOTSUPP.

Sounds like a nice solution. I think I can add an is_notify_supported() interface in dax_holder_ops and check it when register dax_holder.

--
Thanks,
Ruan.
>
> thanks,
> -jane
>
> >
> > thanks!
> > -jane
> >
> >
> >>
> >> Something is not right...
> >>
> >> thanks,
> >> -jane
> >>
> >>
> >> On 8/5/2021 6:17 PM, Jane Chu wrote:
> >>> The filesystem part of the pmem failure handling is at minimum built
> >>> on PAGE_SIZE granularity - an inheritance from general
> >>> memory_failure handling.  However, with Intel's DCPMEM technology,
> >>> the error blast radius is no more than 256bytes, and might get
> >>> smaller with future hardware generation, also advanced atomic 64B write
> to clear the poison.
> >>> But I don't see any of that could be incorporated in, given that the
> >>> filesystem is notified a corruption with pfn, rather than an exact
> >>> address.
> >>>
> >>> So I guess this question is also for Dan: how to avoid unnecessarily
> >>> repairing a PMD range for a 256B corrupt range going forward?
> >>>
> >>> thanks,
> >>> -jane
> >>>
> >>>
> >>> On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> >>>> When memory-failure occurs, we call this function which is
> >>>> implemented by each kind of devices.  For the fsdax case, pmem
> >>>> device driver implements it.  Pmem device driver will find out the
> >>>> filesystem in which the corrupted page located in.  And finally
> >>>> call filesystem handler to deal with this error.
> >>>>
> >>>> The filesystem will try to recover the corrupted data if necessary.
> >>>
> >>
> >

2021-08-18 15:57:12

by Darrick J. Wong

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

On Tue, Aug 17, 2021 at 11:08:40PM -0700, Jane Chu wrote:
>
> On 8/17/2021 10:43 PM, Jane Chu wrote:
> > More information -
> >
> > On 8/16/2021 10:20 AM, Jane Chu wrote:
> > > Hi, ShiYang,
> > >
> > > So I applied the v6 patch series to my 5.14-rc3 as it's what you
> > > indicated is what v6 was based at, and injected a hardware poison.
> > >
> > > I'm seeing the same problem that was reported a while ago after the
> > > poison was consumed - in the SIGBUS payload, the si_addr is missing:
> > >
> > > ** SIGBUS(7): canjmp=1, whichstep=0, **
> > > ** si_addr(0x(nil)), si_lsb(0xC), si_code(0x4, BUS_MCEERR_AR) **
> > >
> > > The si_addr ought to be 0x7f6568000000 - the vaddr of the first page
> > > in this case.
> >
> > The failure came from here :
> >
> > [PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS
> >
> > +static int
> > +xfs_dax_notify_failure(
> > ...
> > +??? if (!xfs_sb_version_hasrmapbt(&mp->m_sb)) {
> > +??????? xfs_warn(mp, "notify_failure() needs rmapbt enabled!");
> > +??????? return -EOPNOTSUPP;
> > +??? }
> >
> > I am not familiar with XFS, but I have a few questions I hope to get
> > answers -
> >
> > 1) What does it take and cost to make
> > ?? xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?

mkfs.xfs -m rmapbt=1

> > 2) For a running environment that fails the above check, is it
> > ?? okay to leave the poison handle in limbo and why?
> >
> > 3) If the above regression is not acceptable, any potential remedy?
>
> How about moving the check to prior to the notifier registration?
> And register only if the check is passed? This seems better
> than an alternative which is to fall back to the legacy memory_failure
> handling in case the filesystem returns -EOPNOTSUPP.

"return -EOPNOTSUPP;" is the branching point where a future patch could
probe the (DRAM) buffer cache to bwrite the contents to restore the pmem
contents. Right now the focus should be on landing the core code
changes without drawing any more NAKs from Dan.

--D

> thanks,
> -jane
>
> >
> > thanks!
> > -jane
> >
> >
> > >
> > > Something is not right...
> > >
> > > thanks,
> > > -jane
> > >
> > >
> > > On 8/5/2021 6:17 PM, Jane Chu wrote:
> > > > The filesystem part of the pmem failure handling is at minimum built
> > > > on PAGE_SIZE granularity - an inheritance from general
> > > > memory_failure handling.? However, with Intel's DCPMEM
> > > > technology, the error blast
> > > > radius is no more than 256bytes, and might get smaller with future
> > > > hardware generation, also advanced atomic 64B write to clear the poison.
> > > > But I don't see any of that could be incorporated in, given that the
> > > > filesystem is notified a corruption with pfn, rather than an exact
> > > > address.
> > > >
> > > > So I guess this question is also for Dan: how to avoid unnecessarily
> > > > repairing a PMD range for a 256B corrupt range going forward?
> > > >
> > > > thanks,
> > > > -jane
> > > >
> > > >
> > > > On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> > > > > When memory-failure occurs, we call this function which is implemented
> > > > > by each kind of devices.? For the fsdax case, pmem device driver
> > > > > implements it.? Pmem device driver will find out the
> > > > > filesystem in which
> > > > > the corrupted page located in.? And finally call filesystem handler to
> > > > > deal with this error.
> > > > >
> > > > > The filesystem will try to recover the corrupted data if necessary.
> > > >
> > >
> >

2021-08-18 17:12:22

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

On Wed, Aug 18, 2021 at 12:52 AM [email protected]
<[email protected]> wrote:
>
>
>
> > -----Original Message-----
> > From: Jane Chu <[email protected]>
> > Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()
> >
> >
> > On 8/17/2021 10:43 PM, Jane Chu wrote:
> > > More information -
> > >
> > > On 8/16/2021 10:20 AM, Jane Chu wrote:
> > >> Hi, ShiYang,
> > >>
> > >> So I applied the v6 patch series to my 5.14-rc3 as it's what you
> > >> indicated is what v6 was based at, and injected a hardware poison.
> > >>
> > >> I'm seeing the same problem that was reported a while ago after the
> > >> poison was consumed - in the SIGBUS payload, the si_addr is missing:
> > >>
> > >> ** SIGBUS(7): canjmp=1, whichstep=0, **
> > >> ** si_addr(0x(nil)), si_lsb(0xC), si_code(0x4, BUS_MCEERR_AR) **
> > >>
> > >> The si_addr ought to be 0x7f6568000000 - the vaddr of the first page
> > >> in this case.
> > >
> > > The failure came from here :
> > >
> > > [PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS
> > >
> > > +static int
> > > +xfs_dax_notify_failure(
> > > ...
> > > + if (!xfs_sb_version_hasrmapbt(&mp->m_sb)) {
> > > + xfs_warn(mp, "notify_failure() needs rmapbt enabled!");
> > > + return -EOPNOTSUPP;
> > > + }
> > >
> > > I am not familiar with XFS, but I have a few questions I hope to get
> > > answers -
> > >
> > > 1) What does it take and cost to make
> > > xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?
>
> Enable rmpabt feature when making xfs filesystem
> `mkfs.xfs -m rmapbt=1 /path/to/device`
> BTW, reflink is enabled by default.
>
> > >
> > > 2) For a running environment that fails the above check, is it
> > > okay to leave the poison handle in limbo and why?
> It will fall back to the old handler. I think you have already known it.
>
> > >
> > > 3) If the above regression is not acceptable, any potential remedy?
> >
> > How about moving the check to prior to the notifier registration?
> > And register only if the check is passed? This seems better than an
> > alternative which is to fall back to the legacy memory_failure handling in case
> > the filesystem returns -EOPNOTSUPP.
>
> Sounds like a nice solution. I think I can add an is_notify_supported() interface in dax_holder_ops and check it when register dax_holder.

Shouldn't the fs avoid registering a memory failure handler if it is
not prepared to take over? For example, shouldn't this case behave
identically to ext4 that will not even register a callback?

2021-08-19 07:21:44

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

Hi, Shiyang,

> > > 1) What does it take and cost to make
> > > xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?
>
> Enable rmpabt feature when making xfs filesystem
> `mkfs.xfs -m rmapbt=1 /path/to/device`
> BTW, reflink is enabled by default.

Thanks! I tried
mkfs.xfs -d agcount=2,extszinherit=512,su=2m,sw=1 -m reflink=0 -m
rmapbt=1 -f /dev/pmem0

Again, injected a HW poison to the first page in a dax-file, had
the poison consumed and received a SIGBUS. The result is better -

** SIGBUS(7): canjmp=1, whichstep=0, **
** si_addr(0x0x7ff2d8800000), si_lsb(0x15), si_code(0x4, BUS_MCEERR_AR) **

The SIGBUS payload looks correct.

However, "dmesg" has 2048 lines on sending SIGBUS, one per 512bytes -

[ 7003.482326] Memory failure: 0x1850600: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
[ 7003.507956] Memory failure: 0x1850800: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
[ 7003.531681] Memory failure: 0x1850a00: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
[ 7003.554190] Memory failure: 0x1850c00: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
[ 7003.575831] Memory failure: 0x1850e00: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
[ 7003.596796] Memory failure: 0x1851000: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
....
[ 7045.738270] Memory failure: 0x194fe00: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
[ 7045.758885] Memory failure: 0x1950000: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
[ 7045.779495] Memory failure: 0x1950200: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption
[ 7045.800106] Memory failure: 0x1950400: Sending SIGBUS to
fsdax_poison_v1:4109 due to hardware memory corruption

That's too much for a single process dealing with a single
poison in a PMD page. If nothing else, given an .si_addr_lsb being 0x15,
it doesn't make sense to send a SIGBUS per 512B block.

Could you determine the user process' mapping size from the filesystem,
and take that as a hint to determine how many iterations to call
mf_dax_kill_procs() ?

thanks!
-jane



2021-08-19 08:17:42

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

Sorry, correction in line.

On 8/19/2021 12:18 AM, Jane Chu wrote:
> Hi, Shiyang,
>
> >  > > 1) What does it take and cost to make
> >  > >     xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?
> >
> > Enable rmpabt feature when making xfs filesystem
> >     `mkfs.xfs -m rmapbt=1 /path/to/device`
> > BTW, reflink is enabled by default.
>
> Thanks!  I tried
> mkfs.xfs -d agcount=2,extszinherit=512,su=2m,sw=1 -m reflink=0 -m
> rmapbt=1 -f /dev/pmem0
>
> Again, injected a HW poison to the first page in a dax-file, had
> the poison consumed and received a SIGBUS. The result is better -
>
> ** SIGBUS(7): canjmp=1, whichstep=0, **
> ** si_addr(0x0x7ff2d8800000), si_lsb(0x15), si_code(0x4, BUS_MCEERR_AR) **
>
> The SIGBUS payload looks correct.
>
> However, "dmesg" has 2048 lines on sending SIGBUS, one per 512bytes -

Actually that's one per 2MB, even though the poison is located
in pfn 0x1850600 only.

>
> [ 7003.482326] Memory failure: 0x1850600: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> [ 7003.507956] Memory failure: 0x1850800: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> [ 7003.531681] Memory failure: 0x1850a00: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> [ 7003.554190] Memory failure: 0x1850c00: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> [ 7003.575831] Memory failure: 0x1850e00: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> [ 7003.596796] Memory failure: 0x1851000: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> ....
> [ 7045.738270] Memory failure: 0x194fe00: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> [ 7045.758885] Memory failure: 0x1950000: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> [ 7045.779495] Memory failure: 0x1950200: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
> [ 7045.800106] Memory failure: 0x1950400: Sending SIGBUS to
> fsdax_poison_v1:4109 due to hardware memory corruption
>
> That's too much for a single process dealing with a single
> poison in a PMD page. If nothing else, given an .si_addr_lsb being 0x15,
> it doesn't make sense to send a SIGBUS per 512B block.
>
> Could you determine the user process' mapping size from the filesystem,
> and take that as a hint to determine how many iterations to call
> mf_dax_kill_procs() ?

Sorry, scratch the 512byte stuff... the filesystem has been
notified the length of the poison blast radius, could it take clue
from that?

thanks,
-jane

>
> thanks!
> -jane
>
>
>

2021-08-19 09:13:07

by Shiyang Ruan

[permalink] [raw]
Subject: RE: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

> From: Jane Chu <[email protected]>
> Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()
>
> Sorry, correction in line.
>
> On 8/19/2021 12:18 AM, Jane Chu wrote:
> > Hi, Shiyang,
> >
> > >  > > 1) What does it take and cost to make >  > >
> > xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?
> > >
> > > Enable rmpabt feature when making xfs filesystem >     `mkfs.xfs
> > -m rmapbt=1 /path/to/device` > BTW, reflink is enabled by default.
> >
> > Thanks!  I tried
> > mkfs.xfs -d agcount=2,extszinherit=512,su=2m,sw=1 -m reflink=0 -m
> > rmapbt=1 -f /dev/pmem0
> >
> > Again, injected a HW poison to the first page in a dax-file, had the
> > poison consumed and received a SIGBUS. The result is better -
> >
> > ** SIGBUS(7): canjmp=1, whichstep=0, **
> > ** si_addr(0x0x7ff2d8800000), si_lsb(0x15), si_code(0x4,
> > BUS_MCEERR_AR) **
> >
> > The SIGBUS payload looks correct.
> >
> > However, "dmesg" has 2048 lines on sending SIGBUS, one per 512bytes -
>
> Actually that's one per 2MB, even though the poison is located in pfn 0x1850600
> only.
>
> >
> > [ 7003.482326] Memory failure: 0x1850600: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.507956]
> > Memory failure: 0x1850800: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.531681]
> > Memory failure: 0x1850a00: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.554190]
> > Memory failure: 0x1850c00: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.575831]
> > Memory failure: 0x1850e00: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.596796]
> > Memory failure: 0x1851000: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption ....
> > [ 7045.738270] Memory failure: 0x194fe00: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption [ 7045.758885]
> > Memory failure: 0x1950000: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption [ 7045.779495]
> > Memory failure: 0x1950200: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption [ 7045.800106]
> > Memory failure: 0x1950400: Sending SIGBUS to
> > fsdax_poison_v1:4109 due to hardware memory corruption
> >
> > That's too much for a single process dealing with a single poison in a
> > PMD page. If nothing else, given an .si_addr_lsb being 0x15, it
> > doesn't make sense to send a SIGBUS per 512B block.
> >
> > Could you determine the user process' mapping size from the
> > filesystem, and take that as a hint to determine how many iterations
> > to call
> > mf_dax_kill_procs() ?
>
> Sorry, scratch the 512byte stuff... the filesystem has been notified the length of
> the poison blast radius, could it take clue from that?

I think this is caused by a mistake I made in the 6th patch: xfs handler iterates the file range in block size(4k here) even though it is a PMD page. That's why so many message shows when poison on a PMD page. I'll fix it in next version.


--
Thanks,
Ruan.

>
> thanks,
> -jane
>
> >
> > thanks!
> > -jane
> >
> >
> >

2021-08-19 20:52:45

by Jane Chu

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()


On 8/19/2021 2:10 AM, [email protected] wrote:
>> From: Jane Chu <[email protected]>
>> Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()
>>
>> Sorry, correction in line.
>>
>> On 8/19/2021 12:18 AM, Jane Chu wrote:
>>> Hi, Shiyang,
>>>
>>> >  > > 1) What does it take and cost to make >  > >
>>> xfs_sb_version_hasrmapbt(&mp->m_sb) to return true?
>>> >
>>> > Enable rmpabt feature when making xfs filesystem >     `mkfs.xfs
>>> -m rmapbt=1 /path/to/device` > BTW, reflink is enabled by default.
>>>
>>> Thanks!  I tried
>>> mkfs.xfs -d agcount=2,extszinherit=512,su=2m,sw=1 -m reflink=0 -m
>>> rmapbt=1 -f /dev/pmem0
>>>
>>> Again, injected a HW poison to the first page in a dax-file, had the
>>> poison consumed and received a SIGBUS. The result is better -
>>>
>>> ** SIGBUS(7): canjmp=1, whichstep=0, **
>>> ** si_addr(0x0x7ff2d8800000), si_lsb(0x15), si_code(0x4,
>>> BUS_MCEERR_AR) **
>>>
>>> The SIGBUS payload looks correct.
>>>
>>> However, "dmesg" has 2048 lines on sending SIGBUS, one per 512bytes -
>>
>> Actually that's one per 2MB, even though the poison is located in pfn 0x1850600
>> only.
>>
>>>
>>> [ 7003.482326] Memory failure: 0x1850600: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.507956]
>>> Memory failure: 0x1850800: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.531681]
>>> Memory failure: 0x1850a00: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.554190]
>>> Memory failure: 0x1850c00: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.575831]
>>> Memory failure: 0x1850e00: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption [ 7003.596796]
>>> Memory failure: 0x1851000: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption ....
>>> [ 7045.738270] Memory failure: 0x194fe00: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption [ 7045.758885]
>>> Memory failure: 0x1950000: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption [ 7045.779495]
>>> Memory failure: 0x1950200: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption [ 7045.800106]
>>> Memory failure: 0x1950400: Sending SIGBUS to
>>> fsdax_poison_v1:4109 due to hardware memory corruption
>>>
>>> That's too much for a single process dealing with a single poison in a
>>> PMD page. If nothing else, given an .si_addr_lsb being 0x15, it
>>> doesn't make sense to send a SIGBUS per 512B block.
>>>
>>> Could you determine the user process' mapping size from the
>>> filesystem, and take that as a hint to determine how many iterations
>>> to call
>>> mf_dax_kill_procs() ?
>>
>> Sorry, scratch the 512byte stuff... the filesystem has been notified the length of
>> the poison blast radius, could it take clue from that?
>
> I think this is caused by a mistake I made in the 6th patch: xfs handler iterates the file range in block size(4k here) even though it is a PMD page. That's why so many message shows when poison on a PMD page. I'll fix it in next version.
>

Sorry, just to clarify, it looks like XFS has iterated through out the
entire file in 2MiB stride. The test file size is 4GiB, that explains
'dmesg' showing 2048 line about sending SIGBUS.

thanks,
-jane


>
> --
> Thanks,
> Ruan.
>
>>
>> thanks,
>> -jane
>>
>>>
>>> thanks!
>>> -jane
>>>
>>>
>>>

2021-08-20 16:12:58

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 2/9] dax: Introduce holder for dax_device

On Fri, Jul 30, 2021 at 3:02 AM Shiyang Ruan <[email protected]> wrote:
>
> To easily track filesystem from a pmem device, we introduce a holder for
> dax_device structure, and also its operation. This holder is used to
> remember who is using this dax_device:
> - When it is the backend of a filesystem, the holder will be the
> superblock of this filesystem.
> - When this pmem device is one of the targets in a mapped device, the
> holder will be this mapped device. In this case, the mapped device
> has its own dax_device and it will follow the first rule. So that we
> can finally track to the filesystem we needed.
>
> The holder and holder_ops will be set when filesystem is being mounted,
> or an target device is being activated.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
> ---
> drivers/dax/super.c | 46 +++++++++++++++++++++++++++++++++++++++++++++
> include/linux/dax.h | 17 +++++++++++++++++
> 2 files changed, 63 insertions(+)
>
> diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> index 5fa6ae9dbc8b..00c32dfa5665 100644
> --- a/drivers/dax/super.c
> +++ b/drivers/dax/super.c
> @@ -214,6 +214,8 @@ enum dax_device_flags {
> * @cdev: optional character interface for "device dax"
> * @host: optional name for lookups where the device path is not available
> * @private: dax driver private data
> + * @holder_rwsem: prevent unregistration while holder_ops is in progress
> + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> * @flags: state and boolean properties
> */
> struct dax_device {
> @@ -222,8 +224,11 @@ struct dax_device {
> struct cdev cdev;
> const char *host;
> void *private;
> + struct rw_semaphore holder_rwsem;

Given the rarity of notification failures and the infrequency of
registration events I think it would be ok for this to be a global
lock rather than per-device. In fact there is already a global dax
lock, see dax_read_lock(). Let's convert that from srcu to rwsem and
add a dax_write_lock().

> + void *holder_data;
> unsigned long flags;
> const struct dax_operations *ops;
> + const struct dax_holder_operations *holder_ops;
> };
>
> static ssize_t write_cache_show(struct device *dev,
> @@ -373,6 +378,25 @@ int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
> }
> EXPORT_SYMBOL_GPL(dax_zero_page_range);
>
> +int dax_holder_notify_failure(struct dax_device *dax_dev, loff_t offset,
> + size_t size, void *data)
> +{
> + int rc;
> +
> + if (!dax_dev)
> + return -ENXIO;

There also needs to be a dax_dev->alive check, which is only valid to
be checked under dax_read_lock().

Who would ever pass NULL to this function?

> +
> + if (!dax_dev->holder_data)
> + return -EOPNOTSUPP;
> +
> + down_read(&dax_dev->holder_rwsem);
> + rc = dax_dev->holder_ops->notify_failure(dax_dev, offset,
> + size, data);
> + up_read(&dax_dev->holder_rwsem);



> + return rc;
> +}
> +EXPORT_SYMBOL_GPL(dax_holder_notify_failure);
> +
> #ifdef CONFIG_ARCH_HAS_PMEM_API
> void arch_wb_cache_pmem(void *addr, size_t size);
> void dax_flush(struct dax_device *dax_dev, void *addr, size_t size)
> @@ -603,6 +627,7 @@ struct dax_device *alloc_dax(void *private, const char *__host,
> dax_add_host(dax_dev, host);
> dax_dev->ops = ops;
> dax_dev->private = private;
> + init_rwsem(&dax_dev->holder_rwsem);
> if (flags & DAXDEV_F_SYNC)
> set_dax_synchronous(dax_dev);
>
> @@ -624,6 +649,27 @@ void put_dax(struct dax_device *dax_dev)
> }
> EXPORT_SYMBOL_GPL(put_dax);
>
> +void dax_set_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops)
> +{
> + if (!dax_dev)

Same questions about NULL dax dev and ->alive checking.

> + return;
> + down_write(&dax_dev->holder_rwsem);
> + dax_dev->holder_data = holder;
> + dax_dev->holder_ops = ops;
> + up_write(&dax_dev->holder_rwsem);
> +}
> +EXPORT_SYMBOL_GPL(dax_set_holder);
> +
> +void *dax_get_holder(struct dax_device *dax_dev)
> +{
> + if (!dax_dev)
> + return NULL;

Where is this API used? This result is not valid unless the caller is
holding the read lock.

> +
> + return dax_dev->holder_data;
> +}
> +EXPORT_SYMBOL_GPL(dax_get_holder);
> +
> /**
> * dax_get_by_host() - temporary lookup mechanism for filesystem-dax
> * @host: alternate name for the device registered by a dax driver
> diff --git a/include/linux/dax.h b/include/linux/dax.h
> index b52f084aa643..6f4b5c97ceb0 100644
> --- a/include/linux/dax.h
> +++ b/include/linux/dax.h
> @@ -38,10 +38,17 @@ struct dax_operations {
> int (*zero_page_range)(struct dax_device *, pgoff_t, size_t);
> };
>
> +struct dax_holder_operations {
> + int (*notify_failure)(struct dax_device *, loff_t, size_t, void *);
> +};
> +
> extern struct attribute_group dax_attribute_group;
>
> #if IS_ENABLED(CONFIG_DAX)
> struct dax_device *dax_get_by_host(const char *host);
> +void dax_set_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops);
> +void *dax_get_holder(struct dax_device *dax_dev);
> struct dax_device *alloc_dax(void *private, const char *host,
> const struct dax_operations *ops, unsigned long flags);
> void put_dax(struct dax_device *dax_dev);
> @@ -77,6 +84,14 @@ static inline struct dax_device *dax_get_by_host(const char *host)
> {
> return NULL;
> }
> +static inline void dax_set_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops)
> +{
> +}
> +static inline void *dax_get_holder(struct dax_device *dax_dev)
> +{
> + return NULL;
> +}
> static inline struct dax_device *alloc_dax(void *private, const char *host,
> const struct dax_operations *ops, unsigned long flags)
> {
> @@ -226,6 +241,8 @@ size_t dax_copy_to_iter(struct dax_device *dax_dev, pgoff_t pgoff, void *addr,
> size_t bytes, struct iov_iter *i);
> int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
> size_t nr_pages);
> +int dax_holder_notify_failure(struct dax_device *dax_dev, loff_t offset,
> + size_t size, void *data);
> void dax_flush(struct dax_device *dax_dev, void *addr, size_t size);
>
> ssize_t dax_iomap_rw(struct kiocb *iocb, struct iov_iter *iter,
> --
> 2.32.0
>
>
>

2021-08-20 16:13:08

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

On Fri, Jul 30, 2021 at 3:02 AM Shiyang Ruan <[email protected]> wrote:
>
> When memory-failure occurs, we call this function which is implemented
> by each kind of devices. For the fsdax case, pmem device driver
> implements it. Pmem device driver will find out the filesystem in which
> the corrupted page located in. And finally call filesystem handler to
> deal with this error.
>
> The filesystem will try to recover the corrupted data if necessary.

This patch looks good to me, but I would fold it into the patch that
first populates ->memory_failure().

2021-08-20 16:59:09

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 3/9] mm: factor helpers for memory_failure_dev_pagemap

On Thu, Aug 5, 2021 at 6:01 PM Jane Chu <[email protected]> wrote:
>
>
> On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> > - /*
> > - * Prevent the inode from being freed while we are interrogating
> > - * the address_space, typically this would be handled by
> > - * lock_page(), but dax pages do not use the page lock. This
> > - * also prevents changes to the mapping of this pfn until
> > - * poison signaling is complete.
> > - */
> > - cookie = dax_lock_page(page);
> > - if (!cookie)
> > - goto out;
> > -
> > if (hwpoison_filter(page)) {
> > rc = 0;
> > - goto unlock;
> > + goto out;
> > }
>
> why isn't dax_lock_page() needed for hwpoison_filter() check?

Good catch. hwpoison_filter() is indeed consulting page->mapping->host
which needs to be synchronized against inode lifetime.

2021-08-20 20:21:28

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 2/9] dax: Introduce holder for dax_device

On Fri, Jul 30, 2021 at 3:02 AM Shiyang Ruan <[email protected]> wrote:
>
> To easily track filesystem from a pmem device, we introduce a holder for
> dax_device structure, and also its operation. This holder is used to
> remember who is using this dax_device:
> - When it is the backend of a filesystem, the holder will be the
> superblock of this filesystem.
> - When this pmem device is one of the targets in a mapped device, the
> holder will be this mapped device. In this case, the mapped device
> has its own dax_device and it will follow the first rule. So that we
> can finally track to the filesystem we needed.
>
> The holder and holder_ops will be set when filesystem is being mounted,
> or an target device is being activated.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
> ---
> drivers/dax/super.c | 46 +++++++++++++++++++++++++++++++++++++++++++++
> include/linux/dax.h | 17 +++++++++++++++++
> 2 files changed, 63 insertions(+)
>
> diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> index 5fa6ae9dbc8b..00c32dfa5665 100644
> --- a/drivers/dax/super.c
> +++ b/drivers/dax/super.c
> @@ -214,6 +214,8 @@ enum dax_device_flags {
> * @cdev: optional character interface for "device dax"
> * @host: optional name for lookups where the device path is not available
> * @private: dax driver private data
> + * @holder_rwsem: prevent unregistration while holder_ops is in progress
> + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> * @flags: state and boolean properties
> */
> struct dax_device {
> @@ -222,8 +224,11 @@ struct dax_device {
> struct cdev cdev;
> const char *host;
> void *private;
> + struct rw_semaphore holder_rwsem;
> + void *holder_data;
> unsigned long flags;
> const struct dax_operations *ops;
> + const struct dax_holder_operations *holder_ops;
> };
>
> static ssize_t write_cache_show(struct device *dev,
> @@ -373,6 +378,25 @@ int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
> }
> EXPORT_SYMBOL_GPL(dax_zero_page_range);
>
> +int dax_holder_notify_failure(struct dax_device *dax_dev, loff_t offset,
> + size_t size, void *data)
I took a look at patch3 and had some questions about the api.

Can you add kernel-doc for this api and specifically clarify what is
@data used for vs dax_dev->holder_data?

I also think the holder needs to know whether this failure is being
signaled synchronously. or asynchronously. In the synchronous case a
process has consumed poison and action needs to be taken immediately.
In the asynchronous case the driver stack has encountered failed
address ranges and is notifying the holder to avoid those ranges, but
no immediate action needs to be taken to shoot down mappings. For
example, I would use the synchronous notification when
memory_failure() is invoked with the "action required" indication, and
the asynchronous notification when an NVDIMM_REVALIDATE_POISON event
fires, or the "action optional" memory_failure() case.

In short I think the interface just needs a flags argument.


> +{
> + int rc;
> +
> + if (!dax_dev)
> + return -ENXIO;
> +
> + if (!dax_dev->holder_data)
> + return -EOPNOTSUPP;
> +
> + down_read(&dax_dev->holder_rwsem);
> + rc = dax_dev->holder_ops->notify_failure(dax_dev, offset,
> + size, data);
> + up_read(&dax_dev->holder_rwsem);
> + return rc;
> +}
> +EXPORT_SYMBOL_GPL(dax_holder_notify_failure);
> +
> #ifdef CONFIG_ARCH_HAS_PMEM_API
> void arch_wb_cache_pmem(void *addr, size_t size);
> void dax_flush(struct dax_device *dax_dev, void *addr, size_t size)
> @@ -603,6 +627,7 @@ struct dax_device *alloc_dax(void *private, const char *__host,
> dax_add_host(dax_dev, host);
> dax_dev->ops = ops;
> dax_dev->private = private;
> + init_rwsem(&dax_dev->holder_rwsem);
> if (flags & DAXDEV_F_SYNC)
> set_dax_synchronous(dax_dev);
>
> @@ -624,6 +649,27 @@ void put_dax(struct dax_device *dax_dev)
> }
> EXPORT_SYMBOL_GPL(put_dax);
>
> +void dax_set_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops)
> +{
> + if (!dax_dev)
> + return;
> + down_write(&dax_dev->holder_rwsem);
> + dax_dev->holder_data = holder;
> + dax_dev->holder_ops = ops;
> + up_write(&dax_dev->holder_rwsem);
> +}
> +EXPORT_SYMBOL_GPL(dax_set_holder);
> +
> +void *dax_get_holder(struct dax_device *dax_dev)
> +{
> + if (!dax_dev)
> + return NULL;
> +
> + return dax_dev->holder_data;
> +}
> +EXPORT_SYMBOL_GPL(dax_get_holder);
> +
> /**
> * dax_get_by_host() - temporary lookup mechanism for filesystem-dax
> * @host: alternate name for the device registered by a dax driver
> diff --git a/include/linux/dax.h b/include/linux/dax.h
> index b52f084aa643..6f4b5c97ceb0 100644
> --- a/include/linux/dax.h
> +++ b/include/linux/dax.h
> @@ -38,10 +38,17 @@ struct dax_operations {
> int (*zero_page_range)(struct dax_device *, pgoff_t, size_t);
> };
>
> +struct dax_holder_operations {
> + int (*notify_failure)(struct dax_device *, loff_t, size_t, void *);
> +};
> +
> extern struct attribute_group dax_attribute_group;
>
> #if IS_ENABLED(CONFIG_DAX)
> struct dax_device *dax_get_by_host(const char *host);
> +void dax_set_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops);
> +void *dax_get_holder(struct dax_device *dax_dev);
> struct dax_device *alloc_dax(void *private, const char *host,
> const struct dax_operations *ops, unsigned long flags);
> void put_dax(struct dax_device *dax_dev);
> @@ -77,6 +84,14 @@ static inline struct dax_device *dax_get_by_host(const char *host)
> {
> return NULL;
> }
> +static inline void dax_set_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops)
> +{
> +}
> +static inline void *dax_get_holder(struct dax_device *dax_dev)
> +{
> + return NULL;
> +}
> static inline struct dax_device *alloc_dax(void *private, const char *host,
> const struct dax_operations *ops, unsigned long flags)
> {
> @@ -226,6 +241,8 @@ size_t dax_copy_to_iter(struct dax_device *dax_dev, pgoff_t pgoff, void *addr,
> size_t bytes, struct iov_iter *i);
> int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,
> size_t nr_pages);
> +int dax_holder_notify_failure(struct dax_device *dax_dev, loff_t offset,
> + size_t size, void *data);
> void dax_flush(struct dax_device *dax_dev, void *addr, size_t size);
>
> ssize_t dax_iomap_rw(struct kiocb *iocb, struct iov_iter *iter,
> --
> 2.32.0
>
>
>

2021-08-20 20:54:14

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 4/9] pmem,mm: Implement ->memory_failure in pmem driver

On Fri, Jul 30, 2021 at 3:02 AM Shiyang Ruan <[email protected]> wrote:
>
> With dax_holder notify support, we are able to notify the memory failure
> from pmem driver to upper layers. If there is something not support in
> the notify routine, memory_failure will fall back to the generic hanlder.

How about:

"Any layer can return -EOPNOTSUPP to force memory_failure() to fall
back to its generic implementation."


>
> Signed-off-by: Shiyang Ruan <[email protected]>
> ---
> drivers/nvdimm/pmem.c | 13 +++++++++++++
> mm/memory-failure.c | 14 ++++++++++++++
> 2 files changed, 27 insertions(+)
>
> diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c
> index 1e0615b8565e..fea4ffc333b8 100644
> --- a/drivers/nvdimm/pmem.c
> +++ b/drivers/nvdimm/pmem.c
> @@ -362,9 +362,22 @@ static void pmem_release_disk(void *__pmem)
> del_gendisk(pmem->disk);
> }
>
> +static int pmem_pagemap_memory_failure(struct dev_pagemap *pgmap,
> + unsigned long pfn, unsigned long nr_pfns, int flags)
> +{
> + struct pmem_device *pmem =
> + container_of(pgmap, struct pmem_device, pgmap);
> + loff_t offset = PFN_PHYS(pfn) - pmem->phys_addr - pmem->data_offset;
> +
> + return dax_holder_notify_failure(pmem->dax_dev, offset,
> + page_size(pfn_to_page(pfn)) * nr_pfns,

I do not understand the usage of page_size() here? memory_failure()
assumes PAGE_SIZE pages. DAX pages also do not populate the compound
metadata yet, but even if they did I would expect memory_failure() to
be responsible for doing something like:

pgmap->ops->memory_failure(pgmap, pfn, size >> PAGE_SHIFT, flags);

...where @size is calculated from dev_pagemap_mapping_shift().

> + &flags);

Why is the local flags variable passed by reference? At a minimum the
memory_failure() flags should be translated to a new set dax-notify
flags, because memory_failure() will not be the only user of this
notification interface. See NVDIMM_REVALIDATE_POISON, and the
discussion Dave and I had about using this notification to signal
unsafe hot-removal of a memory device.


> +}
> +
> static const struct dev_pagemap_ops fsdax_pagemap_ops = {
> .kill = pmem_pagemap_kill,
> .cleanup = pmem_pagemap_cleanup,
> + .memory_failure = pmem_pagemap_memory_failure,
> };
>
> static int pmem_attach_disk(struct device *dev,
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index 3bdfcb45f66e..ab3eda335acd 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -1600,6 +1600,20 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
> */
> SetPageHWPoison(page);
>
> + /*
> + * Call driver's implementation to handle the memory failure, otherwise
> + * fall back to generic handler.
> + */
> + if (pgmap->ops->memory_failure) {
> + rc = pgmap->ops->memory_failure(pgmap, pfn, 1, flags);
> + /*
> + * Fall back to generic handler too if operation is not
> + * supported inside the driver/device/filesystem.
> + */
> + if (rc != EOPNOTSUPP)
> + goto out;
> + }
> +
> mf_generic_kill_procs(pfn, flags);
> out:
> /* drop pgmap ref acquired in caller */
> --
> 2.32.0
>
>
>

2021-08-20 22:43:27

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 5/9] mm: Introduce mf_dax_kill_procs() for fsdax case

On Fri, Jul 30, 2021 at 3:02 AM Shiyang Ruan <[email protected]> wrote:
>
> This function is called at the end of RMAP routine, i.e. filesystem
> recovery function. The difference between mf_generic_kill_procs() is,
> mf_dax_kill_procs() accepts file mapping and offset instead of struct
> page. It is because that different file mappings and offsets may share
> the same page in fsdax mode. So, it is called when filesystem RMAP
> results are found.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
> ---
> fs/dax.c | 45 ++++++++++++++++++++++++-------
> include/linux/dax.h | 16 ++++++++++++
> include/linux/mm.h | 10 +++++++
> mm/memory-failure.c | 64 +++++++++++++++++++++++++++++++++------------
> 4 files changed, 109 insertions(+), 26 deletions(-)
>
> diff --git a/fs/dax.c b/fs/dax.c
> index da41f9363568..dce6307a12eb 100644
> --- a/fs/dax.c
> +++ b/fs/dax.c
> @@ -389,6 +389,41 @@ static struct page *dax_busy_page(void *entry)
> return NULL;
> }
>
> +/**
> + * dax_load_pfn - Load pfn of the DAX entry corresponding to a page
> + * @mapping: The file whose entry we want to load
> + * @index: offset where the DAX entry located in
> + *
> + * Return: pfn number of the DAX entry
> + */
> +unsigned long dax_load_pfn(struct address_space *mapping, unsigned long index)
> +{
> + XA_STATE(xas, &mapping->i_pages, index);
> + void *entry;
> + unsigned long pfn;
> +
> + rcu_read_lock();
> + for (;;) {
> + xas_lock_irq(&xas);
> + entry = xas_load(&xas);
> + if (dax_is_locked(entry)) {
> + rcu_read_unlock();
> + wait_entry_unlocked(&xas, entry);
> + rcu_read_lock();
> + continue;
> + }
> +
> + if (dax_is_zero_entry(entry) || dax_is_empty_entry(entry))
> + pfn = 0;
> + else
> + pfn = dax_to_pfn(entry);
> + xas_unlock_irq(&xas);
> + break;
> + }
> + rcu_read_unlock();
> + return pfn;

Instead of this I think you want a version of dax_lock_page() that
takes a mapping and index. Otherwise I don't see how this function
protects against races to teardown mapping->host, or to invalidate the
association of the mapping to the pfn.

> +}
> +
> /*
> * dax_lock_mapping_entry - Lock the DAX entry corresponding to a page
> * @page: The page whose entry we want to lock
> @@ -790,16 +825,6 @@ static void *dax_insert_entry(struct xa_state *xas,
> return entry;
> }
>
> -static inline
> -unsigned long pgoff_address(pgoff_t pgoff, struct vm_area_struct *vma)
> -{
> - unsigned long address;
> -
> - address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
> - VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
> - return address;
> -}
> -
> /* Walk all mappings of a given index of a file and writeprotect them */
> static void dax_entry_mkclean(struct address_space *mapping, pgoff_t index,
> unsigned long pfn)
> diff --git a/include/linux/dax.h b/include/linux/dax.h
> index 6f4b5c97ceb0..359e809516b8 100644
> --- a/include/linux/dax.h
> +++ b/include/linux/dax.h
> @@ -165,6 +165,7 @@ int dax_writeback_mapping_range(struct address_space *mapping,
>
> struct page *dax_layout_busy_page(struct address_space *mapping);
> struct page *dax_layout_busy_page_range(struct address_space *mapping, loff_t start, loff_t end);
> +unsigned long dax_load_pfn(struct address_space *mapping, unsigned long index);
> dax_entry_t dax_lock_page(struct page *page);
> void dax_unlock_page(struct page *page, dax_entry_t cookie);
> #else
> @@ -206,6 +207,12 @@ static inline int dax_writeback_mapping_range(struct address_space *mapping,
> return -EOPNOTSUPP;
> }
>
> +static inline unsigned long dax_load_pfn(struct address_space *mapping,
> + unsigned long index)
> +{
> + return 0;
> +}
> +
> static inline dax_entry_t dax_lock_page(struct page *page)
> {
> if (IS_DAX(page->mapping->host))
> @@ -259,6 +266,15 @@ static inline bool dax_mapping(struct address_space *mapping)
> {
> return mapping->host && IS_DAX(mapping->host);
> }
> +static inline unsigned long pgoff_address(pgoff_t pgoff,
> + struct vm_area_struct *vma)
> +{
> + unsigned long address;
> +
> + address = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
> + VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);
> + return address;
> +}
>
> #ifdef CONFIG_DEV_DAX_HMEM_DEVICES
> void hmem_register_device(int target_nid, struct resource *r);
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 7ca22e6e694a..530aaf7a6eb2 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -1190,6 +1190,14 @@ static inline bool is_device_private_page(const struct page *page)
> page->pgmap->type == MEMORY_DEVICE_PRIVATE;
> }
>
> +static inline bool is_device_fsdax_page(const struct page *page)
> +{
> + return IS_ENABLED(CONFIG_DEV_PAGEMAP_OPS) &&
> + IS_ENABLED(CONFIG_FS_DAX) &&
> + is_zone_device_page(page) &&
> + page->pgmap->type == MEMORY_DEVICE_FS_DAX;
> +}

The value of this helper is unclear to me. The MEMORY_DEVICE_FS_DAX
indication is for communicating page-idle notifications to filesystem
code.

> +
> static inline bool is_pci_p2pdma_page(const struct page *page)
> {
> return IS_ENABLED(CONFIG_DEV_PAGEMAP_OPS) &&
> @@ -3113,6 +3121,8 @@ enum mf_flags {
> MF_MUST_KILL = 1 << 2,
> MF_SOFT_OFFLINE = 1 << 3,
> };
> +extern int mf_dax_kill_procs(struct address_space *mapping, pgoff_t index,
> + int flags);
> extern int memory_failure(unsigned long pfn, int flags);
> extern void memory_failure_queue(unsigned long pfn, int flags);
> extern void memory_failure_queue_kick(int cpu);
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index ab3eda335acd..520664c405fc 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -134,6 +134,12 @@ static int hwpoison_filter_dev(struct page *p)
> if (PageSlab(p))
> return -EINVAL;
>
> + if (pfn_valid(page_to_pfn(p))) {
> + if (is_device_fsdax_page(p))

hwpoison_filter was built to test triggering failures in hard to reach
places of the page cache. I think you can make an argument the
hwpoison_filter does not apply to DAX since DAX by definition
eliminates page cache. So, I'd consult pgmap->memory_failure()
*instead* of the hwpoison_filter, don't teach the filter to ignore
fsdax pages. By definition if the ->memory_failure() says -EOPNOTSUPP
then hwpoison_filter can be consulted per usual.

> + return 0;
> + } else
> + return -EINVAL;
> +
> mapping = page_mapping(p);
> if (mapping == NULL || mapping->host == NULL)
> return -EINVAL;
> @@ -304,10 +310,9 @@ void shake_page(struct page *p, int access)
> }
> EXPORT_SYMBOL_GPL(shake_page);
>
> -static unsigned long dev_pagemap_mapping_shift(struct page *page,
> +static unsigned long dev_pagemap_mapping_shift(unsigned long address,
> struct vm_area_struct *vma)
> {
> - unsigned long address = vma_address(page, vma);
> pgd_t *pgd;
> p4d_t *p4d;
> pud_t *pud;
> @@ -347,7 +352,7 @@ static unsigned long dev_pagemap_mapping_shift(struct page *page,
> * Schedule a process for later kill.
> * Uses GFP_ATOMIC allocations to avoid potential recursions in the VM.
> */
> -static void add_to_kill(struct task_struct *tsk, struct page *p,
> +static void add_to_kill(struct task_struct *tsk, struct page *p, pgoff_t pgoff,
> struct vm_area_struct *vma,
> struct list_head *to_kill)
> {
> @@ -360,9 +365,14 @@ static void add_to_kill(struct task_struct *tsk, struct page *p,
> }
>
> tk->addr = page_address_in_vma(p, vma);
> - if (is_zone_device_page(p))
> - tk->size_shift = dev_pagemap_mapping_shift(p, vma);
> - else
> + if (is_zone_device_page(p)) {
> + /* Since page->mapping is no more used for fsdax, we should
> + * calculate the address in a fsdax way.
> + */
> + if (is_device_fsdax_page(p))
> + tk->addr = pgoff_address(pgoff, vma);
> + tk->size_shift = dev_pagemap_mapping_shift(tk->addr, vma);
> + } else
> tk->size_shift = page_shift(compound_head(p));
>
> /*
> @@ -510,7 +520,7 @@ static void collect_procs_anon(struct page *page, struct list_head *to_kill,
> if (!page_mapped_in_vma(page, vma))
> continue;
> if (vma->vm_mm == t->mm)
> - add_to_kill(t, page, vma, to_kill);
> + add_to_kill(t, page, 0, vma, to_kill);
> }
> }
> read_unlock(&tasklist_lock);
> @@ -520,24 +530,20 @@ static void collect_procs_anon(struct page *page, struct list_head *to_kill,
> /*
> * Collect processes when the error hit a file mapped page.
> */
> -static void collect_procs_file(struct page *page, struct list_head *to_kill,
> - int force_early)
> +static void collect_procs_file(struct page *page, struct address_space *mapping,
> + pgoff_t pgoff, struct list_head *to_kill, int force_early)
> {
> struct vm_area_struct *vma;
> struct task_struct *tsk;
> - struct address_space *mapping = page->mapping;
> - pgoff_t pgoff;
>
> i_mmap_lock_read(mapping);
> read_lock(&tasklist_lock);
> - pgoff = page_to_pgoff(page);
> for_each_process(tsk) {
> struct task_struct *t = task_early_kill(tsk, force_early);
>
> if (!t)
> continue;
> - vma_interval_tree_foreach(vma, &mapping->i_mmap, pgoff,
> - pgoff) {
> + vma_interval_tree_foreach(vma, &mapping->i_mmap, pgoff, pgoff) {
> /*
> * Send early kill signal to tasks where a vma covers
> * the page but the corrupted page is not necessarily
> @@ -546,7 +552,7 @@ static void collect_procs_file(struct page *page, struct list_head *to_kill,
> * to be informed of all such data corruptions.
> */
> if (vma->vm_mm == t->mm)
> - add_to_kill(t, page, vma, to_kill);
> + add_to_kill(t, page, pgoff, vma, to_kill);
> }
> }
> read_unlock(&tasklist_lock);
> @@ -565,7 +571,8 @@ static void collect_procs(struct page *page, struct list_head *tokill,
> if (PageAnon(page))
> collect_procs_anon(page, tokill, force_early);
> else
> - collect_procs_file(page, tokill, force_early);
> + collect_procs_file(page, page->mapping, page->index, tokill,
> + force_early);
> }
>
> struct hwp_walk {
> @@ -1477,6 +1484,31 @@ static int mf_generic_kill_procs(unsigned long long pfn, int flags)
> return 0;
> }
>
> +int mf_dax_kill_procs(struct address_space *mapping, pgoff_t index, int flags)
> +{
> + LIST_HEAD(to_kill);
> + /* load the pfn of the dax mapping file */
> + unsigned long pfn = dax_load_pfn(mapping, index);
> +
> + /* the failure pfn may not actually be mmapped, so no need to
> + * unmap and kill procs */
> + if (!pfn)

pfn-0 is a valid pfn. I think you should use a cookie value like
dax_load_page() to indicate failure.

> + return 0;
> +
> + /*
> + * Unlike System-RAM there is no possibility to swap in a
> + * different physical page at a given virtual address, so all
> + * userspace consumption of ZONE_DEVICE memory necessitates
> + * SIGBUS (i.e. MF_MUST_KILL)
> + */
> + flags |= MF_ACTION_REQUIRED | MF_MUST_KILL;
> + collect_procs_file(pfn_to_page(pfn), mapping, index, &to_kill, true);
> +
> + unmap_and_kill(&to_kill, pfn, mapping, index, flags);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(mf_dax_kill_procs);
> +
> static int memory_failure_hugetlb(unsigned long pfn, int flags)
> {
> struct page *p = pfn_to_page(pfn);
> --
> 2.32.0
>
>
>

2021-08-20 22:59:50

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS

On Thu, Aug 5, 2021 at 5:50 PM Jane Chu <[email protected]> wrote:
>
>
> On 7/30/2021 3:01 AM, Shiyang Ruan wrote:
> > + mapping = VFS_I(ip)->i_mapping;
> > + if (IS_ENABLED(CONFIG_MEMORY_FAILURE)) {
> > + for (i = 0; i < rec->rm_blockcount; i++) {
> > + error = mf_dax_kill_procs(mapping, rec->rm_offset + i,
> > + *flags);
> > + if (error)
> > + break;
> > + }
> > + }
>
> If a poison is injected to a PMD dax page, after consuming the poison,
> how many SIGBUS signals are expected to be sent to the process?

I think it should only get one. I.e. just like the the generic code
does one shootdown per mapped page regardless of whether that page is
4K, 2M, or 1G. Once the application is notified it should be able to
query the filesystem to determine the full extent of the damage to
files.

2021-08-20 23:02:20

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 6/9] xfs: Implement ->notify_failure() for XFS

On Fri, Jul 30, 2021 at 3:02 AM Shiyang Ruan <[email protected]> wrote:
>
> This function is used to handle errors which may cause data lost in
> filesystem. Such as memory failure in fsdax mode.
>
> If the rmap feature of XFS enabled, we can query it to find files and
> metadata which are associated with the corrupt data. For now all we do
> is kill processes with that file mapped into their address spaces, but
> future patches could actually do something about corrupt metadata.
>
> After that, the memory failure needs to notify the processes who are
> using those files.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
> ---
> drivers/dax/super.c | 12 ++++
> fs/xfs/xfs_fsops.c | 5 ++
> fs/xfs/xfs_mount.h | 1 +
> fs/xfs/xfs_super.c | 135 ++++++++++++++++++++++++++++++++++++++++++++
> include/linux/dax.h | 13 +++++
> 5 files changed, 166 insertions(+)
>
> diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> index 00c32dfa5665..63f7b63d078d 100644
> --- a/drivers/dax/super.c
> +++ b/drivers/dax/super.c
> @@ -65,6 +65,18 @@ struct dax_device *fs_dax_get_by_bdev(struct block_device *bdev)
> return dax_get_by_host(bdev->bd_disk->disk_name);
> }
> EXPORT_SYMBOL_GPL(fs_dax_get_by_bdev);
> +
> +void fs_dax_set_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops)
> +{
> + dax_set_holder(dax_dev, holder, ops);
> +}
> +EXPORT_SYMBOL_GPL(fs_dax_set_holder);

Small style issue, I'd prefer a pair of functions:

fs_dax_register_holder(struct dax_device *dax_dev, void *holder, const
struct dax_holder_operations *ops)
fs_dax_unregister_holder(struct dax_device *dax_dev)

...rather than open coding unregister as a special set that passes
NULL arguments.

> +void *fs_dax_get_holder(struct dax_device *dax_dev)
> +{
> + return dax_get_holder(dax_dev);

Does dax_get_holder() have a lockdep_assert to check that the caller
has at least a read_lock? Please add kernel-doc for this api to
indicate the locking context expectations.

The rest of this looks plausibly ok to me, but it would be up to xfs
folks to comment on the details. I'm not entirely comfortable with
these handlers assuming DAX, i.e. they should also one day be useful
for page cache memory failure notifications, but that support can come
later.

2021-08-20 23:49:34

by Dan Williams

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 7/9] dm: Introduce ->rmap() to find bdev offset

On Fri, Jul 30, 2021 at 3:02 AM Shiyang Ruan <[email protected]> wrote:
>
> Pmem device could be a target of mapped device. In order to find out
> the global location on a mapped device, we introduce this to translate
> offset from target device to mapped device.
>
> Currently, we implement it on linear target, which is easy to do the
> translation. Other targets will be supported in the future. However,
> some targets may not support it because of the non-linear mapping.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
> ---
> block/genhd.c | 56 +++++++++++++++++++++++++++++++++++
> drivers/md/dm-linear.c | 20 +++++++++++++
> include/linux/device-mapper.h | 5 ++++
> include/linux/genhd.h | 1 +
> 4 files changed, 82 insertions(+)

This might be where dax-device support needs to part ways with the block layer.

As Christoph has mentioned before the long term goal for dax-devices
(direct mapped byte-addressable media) is to have filesystems mount on
them directly and abandon block-layer entanglements. This patch goes
the opposite direct and adds more block layer infrastructure to
support a dax-device need. Now, I'm not opposed to this moving
forward, but I'm not sure block and DM maintainers will be excited
about this additional maintenance burden.

At the same time a lot of effort has been poured into dax-reflink and
I want that support to move forward. So, my proposal while we figure
out what to do about device-mapper rmap is to have
fs_dax_register_holder() fail on device-mapper dax-devices until we
get wider agreement amongst all involved that this is an additional
burden worth carrying. In the meantime XFS on PMEM will see
fs_dax_register_holder() succeed and DAX reflink support can be gated
on whether the dax-device allowed the notify failure handler to be
registered.

Now, there may be room to allow reflink on device-mapper-dax for
CONFIG_MEMORY_FAILURE=n builds, but that would collide with future
work to use notify_failure for more than memory_failure, but also
NVDIMM_REVALIDATE_POISON, and surprise memory-device-remove events.

The code in this patch looks ok to me, just not the direction the
dax-device layer was looking to go. It might be time to revive the
discussions around support for concatenation and striping in the pmem
driver itself, especially as the CXL label specification is already
adding support for physically discontiguous namespaces.

At a minimum if the patch set is organized to support XFS-reflink on
PMEM-DAX and later XFS-reflink on DM-DAX some progress can be made
without waiting for the whole set to be accepted.

2021-08-23 13:23:52

by Christoph Hellwig

[permalink] [raw]
Subject: Re: [PATCH RESEND v6 1/9] pagemap: Introduce ->memory_failure()

On Wed, Aug 18, 2021 at 10:10:51AM -0700, Dan Williams wrote:
> > Sounds like a nice solution. I think I can add an is_notify_supported() interface in dax_holder_ops and check it when register dax_holder.
>
> Shouldn't the fs avoid registering a memory failure handler if it is
> not prepared to take over? For example, shouldn't this case behave
> identically to ext4 that will not even register a callback?

Yes.