This patchset is aimed to support shared pages tracking for fsdax.
Changes from V8 Resend:
- Fix usage of dax write/read lock
- Remove fsdax/xfs register/unregister wrappers
- Move unrelated fixes into separate patchset
- Fix code style
Changes from V8:
- Rebased to "decouple DAX from block devices v2"
- Patch8(implementation in XFS): Separate dax part to Patch7
- Patch9: add FS_DAX_MAPPING_COW flag to distinguish CoW with normal
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()
| |* xfs_dax_notify_failure()
| |--------------------------
| | xfs_rmap_query_range()
| | xfs_dax_failure_fn()
| | * corrupted on metadata
| | try to recover data, call xfs_force_shutdown()
| | * corrupted on file data
| | try to recover data, call mf_dax_kill_procs()
|* normal case
|-------------
|mf_generic_kill_procs()
==
Shiyang Ruan (10):
dax: Use percpu rwsem for dax_{read,write}_lock()
dax: Introduce holder for dax_device
mm: factor helpers for memory_failure_dev_pagemap
pagemap,pmem: Introduce ->memory_failure()
fsdax: fix function description
fsdax: Introduce dax_lock_mapping_entry()
mm: move pgoff_address() to vma_pgoff_address()
mm: Introduce mf_dax_kill_procs() for fsdax case
xfs: Implement ->notify_failure() for XFS
fsdax: set a CoW flag when associate reflink mappings
drivers/dax/device.c | 11 +-
drivers/dax/super.c | 104 ++++++++++++++--
drivers/md/dm-writecache.c | 7 +-
drivers/nvdimm/pmem.c | 16 +++
fs/dax.c | 174 +++++++++++++++++++++------
fs/fuse/dax.c | 6 +-
fs/xfs/Makefile | 1 +
fs/xfs/xfs_buf.c | 15 +++
fs/xfs/xfs_fsops.c | 3 +
fs/xfs/xfs_mount.h | 1 +
fs/xfs/xfs_notify_failure.c | 189 +++++++++++++++++++++++++++++
fs/xfs/xfs_notify_failure.h | 10 ++
include/linux/dax.h | 63 +++++++++-
include/linux/memremap.h | 9 ++
include/linux/mm.h | 15 +++
mm/memory-failure.c | 232 +++++++++++++++++++++++++-----------
16 files changed, 719 insertions(+), 137 deletions(-)
create mode 100644 fs/xfs/xfs_notify_failure.c
create mode 100644 fs/xfs/xfs_notify_failure.h
--
2.34.1
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
instance 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 | 62 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 29 +++++++++++++++++++++
2 files changed, 91 insertions(+)
diff --git a/drivers/dax/super.c b/drivers/dax/super.c
index c46f56e33d40..94c51f2ee133 100644
--- a/drivers/dax/super.c
+++ b/drivers/dax/super.c
@@ -20,15 +20,20 @@
* @inode: core vfs
* @cdev: optional character interface for "device dax"
* @private: dax driver private data
+ * @holder_data: holder of a dax_device: could be filesystem or mapped device
* @flags: state and boolean properties
+ * @ops: operations for dax_device
+ * @holder_ops: operations for the inner holder
*/
struct dax_device {
struct inode inode;
struct cdev cdev;
void *private;
struct percpu_rw_semaphore rwsem;
+ void *holder_data;
unsigned long flags;
const struct dax_operations *ops;
+ const struct dax_holder_operations *holder_ops;
};
static dev_t dax_devt;
@@ -192,6 +197,29 @@ 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, u64 off,
+ u64 len, int mf_flags)
+{
+ int rc;
+
+ dax_read_lock(dax_dev);
+ if (!dax_alive(dax_dev)) {
+ rc = -ENXIO;
+ goto out;
+ }
+
+ if (!dax_dev->holder_ops) {
+ rc = -EOPNOTSUPP;
+ goto out;
+ }
+
+ rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
+out:
+ dax_read_unlock(dax_dev);
+ 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)
@@ -254,6 +282,10 @@ void kill_dax(struct dax_device *dax_dev)
return;
dax_write_lock(dax_dev);
clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
+
+ /* clear holder data */
+ dax_dev->holder_ops = NULL;
+ dax_dev->holder_data = NULL;
dax_write_unlock(dax_dev);
}
EXPORT_SYMBOL_GPL(kill_dax);
@@ -401,6 +433,36 @@ void put_dax(struct dax_device *dax_dev)
}
EXPORT_SYMBOL_GPL(put_dax);
+void dax_register_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops)
+{
+ if (!dax_alive(dax_dev))
+ return;
+
+ dax_dev->holder_data = holder;
+ dax_dev->holder_ops = ops;
+}
+EXPORT_SYMBOL_GPL(dax_register_holder);
+
+void dax_unregister_holder(struct dax_device *dax_dev)
+{
+ if (!dax_alive(dax_dev))
+ return;
+
+ dax_dev->holder_data = NULL;
+ dax_dev->holder_ops = NULL;
+}
+EXPORT_SYMBOL_GPL(dax_unregister_holder);
+
+void *dax_get_holder(struct dax_device *dax_dev)
+{
+ if (!dax_alive(dax_dev))
+ return NULL;
+
+ return dax_dev->holder_data;
+}
+EXPORT_SYMBOL_GPL(dax_get_holder);
+
/**
* inode_dax: convert a public inode into its dax_dev
* @inode: An inode with i_cdev pointing to a dax_dev
diff --git a/include/linux/dax.h b/include/linux/dax.h
index a146bfb80804..e16a9e0ee857 100644
--- a/include/linux/dax.h
+++ b/include/linux/dax.h
@@ -44,6 +44,22 @@ struct dax_operations {
#if IS_ENABLED(CONFIG_DAX)
struct dax_device *alloc_dax(void *private, const struct dax_operations *ops,
unsigned long flags);
+struct dax_holder_operations {
+ /*
+ * notify_failure - notify memory failure into inner holder device
+ * @dax_dev: the dax device which contains the holder
+ * @offset: offset on this dax device where memory failure occurs
+ * @len: length of this memory failure event
+ * @flags: action flags for memory failure handler
+ */
+ int (*notify_failure)(struct dax_device *dax_dev, u64 offset,
+ u64 len, int mf_flags);
+};
+
+void dax_register_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops);
+void dax_unregister_holder(struct dax_device *dax_dev);
+void *dax_get_holder(struct dax_device *dax_dev);
void put_dax(struct dax_device *dax_dev);
void kill_dax(struct dax_device *dax_dev);
void dax_write_cache(struct dax_device *dax_dev, bool wc);
@@ -71,6 +87,17 @@ static inline bool daxdev_mapping_supported(struct vm_area_struct *vma,
return dax_synchronous(dax_dev);
}
#else
+static inline void dax_register_holder(struct dax_device *dax_dev, void *holder,
+ const struct dax_holder_operations *ops)
+{
+}
+static inline void dax_unregister_holder(struct dax_device *dax_dev)
+{
+}
+static inline void *dax_get_holder(struct dax_device *dax_dev)
+{
+ return NULL;
+}
static inline struct dax_device *alloc_dax(void *private,
const struct dax_operations *ops, unsigned long flags)
{
@@ -209,6 +236,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, u64 off, u64 len,
+ int mf_flags);
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.34.1
In order to introduce dax holder registration, we need a write lock for
dax. The write operation is per dax device job. So, the global lock is
not suitable. Change the current lock to percpu_rw_semaphore and introduce
a write lock for registration.
Signed-off-by: Shiyang Ruan <[email protected]>
---
drivers/dax/device.c | 11 +++++-----
drivers/dax/super.c | 42 ++++++++++++++++++++++++++------------
drivers/md/dm-writecache.c | 7 +++----
fs/dax.c | 31 ++++++++++++++--------------
fs/fuse/dax.c | 6 +++---
include/linux/dax.h | 19 ++++++++++++-----
6 files changed, 69 insertions(+), 47 deletions(-)
diff --git a/drivers/dax/device.c b/drivers/dax/device.c
index dd8222a42808..041345f9956d 100644
--- a/drivers/dax/device.c
+++ b/drivers/dax/device.c
@@ -198,7 +198,6 @@ static vm_fault_t dev_dax_huge_fault(struct vm_fault *vmf,
struct file *filp = vmf->vma->vm_file;
unsigned long fault_size;
vm_fault_t rc = VM_FAULT_SIGBUS;
- int id;
pfn_t pfn;
struct dev_dax *dev_dax = filp->private_data;
@@ -206,7 +205,7 @@ static vm_fault_t dev_dax_huge_fault(struct vm_fault *vmf,
(vmf->flags & FAULT_FLAG_WRITE) ? "write" : "read",
vmf->vma->vm_start, vmf->vma->vm_end, pe_size);
- id = dax_read_lock();
+ dax_read_lock(dev_dax->dax_dev);
switch (pe_size) {
case PE_SIZE_PTE:
fault_size = PAGE_SIZE;
@@ -246,7 +245,7 @@ static vm_fault_t dev_dax_huge_fault(struct vm_fault *vmf,
page->index = pgoff + i;
}
}
- dax_read_unlock(id);
+ dax_read_unlock(dev_dax->dax_dev);
return rc;
}
@@ -284,7 +283,7 @@ static const struct vm_operations_struct dax_vm_ops = {
static int dax_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct dev_dax *dev_dax = filp->private_data;
- int rc, id;
+ int rc;
dev_dbg(&dev_dax->dev, "trace\n");
@@ -292,9 +291,9 @@ static int dax_mmap(struct file *filp, struct vm_area_struct *vma)
* We lock to check dax_dev liveness and will re-check at
* fault time.
*/
- id = dax_read_lock();
+ dax_read_lock(dev_dax->dax_dev);
rc = check_vma(dev_dax, vma, __func__);
- dax_read_unlock(id);
+ dax_read_unlock(dev_dax->dax_dev);
if (rc)
return rc;
diff --git a/drivers/dax/super.c b/drivers/dax/super.c
index e7152a6c4cc4..c46f56e33d40 100644
--- a/drivers/dax/super.c
+++ b/drivers/dax/super.c
@@ -26,29 +26,41 @@ struct dax_device {
struct inode inode;
struct cdev cdev;
void *private;
+ struct percpu_rw_semaphore rwsem;
unsigned long flags;
const struct dax_operations *ops;
};
static dev_t dax_devt;
-DEFINE_STATIC_SRCU(dax_srcu);
static struct vfsmount *dax_mnt;
static DEFINE_IDA(dax_minor_ida);
static struct kmem_cache *dax_cache __read_mostly;
static struct super_block *dax_superblock __read_mostly;
-int dax_read_lock(void)
+void dax_read_lock(struct dax_device *dax_dev)
{
- return srcu_read_lock(&dax_srcu);
+ percpu_down_read(&dax_dev->rwsem);
}
EXPORT_SYMBOL_GPL(dax_read_lock);
-void dax_read_unlock(int id)
+void dax_read_unlock(struct dax_device *dax_dev)
{
- srcu_read_unlock(&dax_srcu, id);
+ percpu_up_read(&dax_dev->rwsem);
}
EXPORT_SYMBOL_GPL(dax_read_unlock);
+void dax_write_lock(struct dax_device *dax_dev)
+{
+ percpu_down_write(&dax_dev->rwsem);
+}
+EXPORT_SYMBOL_GPL(dax_write_lock);
+
+void dax_write_unlock(struct dax_device *dax_dev)
+{
+ percpu_up_write(&dax_dev->rwsem);
+}
+EXPORT_SYMBOL_GPL(dax_write_unlock);
+
#if defined(CONFIG_BLOCK) && defined(CONFIG_FS_DAX)
#include <linux/blkdev.h>
@@ -75,7 +87,7 @@ struct dax_device *fs_dax_get_by_bdev(struct block_device *bdev, u64 *start_off)
{
struct dax_device *dax_dev;
u64 part_size;
- int id;
+ bool not_found;
if (!blk_queue_dax(bdev->bd_disk->queue))
return NULL;
@@ -87,11 +99,14 @@ struct dax_device *fs_dax_get_by_bdev(struct block_device *bdev, u64 *start_off)
return NULL;
}
- id = dax_read_lock();
dax_dev = xa_load(&dax_hosts, (unsigned long)bdev->bd_disk);
- if (!dax_dev || !dax_alive(dax_dev) || !igrab(&dax_dev->inode))
- dax_dev = NULL;
- dax_read_unlock(id);
+ if (dax_dev) {
+ dax_read_lock(dax_dev);
+ not_found = !dax_alive(dax_dev) || !igrab(&dax_dev->inode);
+ dax_read_unlock(dax_dev);
+ if (not_found)
+ dax_dev = NULL;
+ }
return dax_dev;
}
@@ -222,7 +237,7 @@ EXPORT_SYMBOL_GPL(__set_dax_synchronous);
bool dax_alive(struct dax_device *dax_dev)
{
- lockdep_assert_held(&dax_srcu);
+ lockdep_assert_held(&dax_dev->rwsem);
return test_bit(DAXDEV_ALIVE, &dax_dev->flags);
}
EXPORT_SYMBOL_GPL(dax_alive);
@@ -237,9 +252,9 @@ void kill_dax(struct dax_device *dax_dev)
{
if (!dax_dev)
return;
-
+ dax_write_lock(dax_dev);
clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
- synchronize_srcu(&dax_srcu);
+ dax_write_unlock(dax_dev);
}
EXPORT_SYMBOL_GPL(kill_dax);
@@ -366,6 +381,7 @@ struct dax_device *alloc_dax(void *private, const struct dax_operations *ops,
dax_dev->ops = ops;
dax_dev->private = private;
+ percpu_init_rwsem(&dax_dev->rwsem);
if (flags & DAXDEV_F_SYNC)
set_dax_synchronous(dax_dev);
diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c
index 4f31591d2d25..ebe1ec345d1d 100644
--- a/drivers/md/dm-writecache.c
+++ b/drivers/md/dm-writecache.c
@@ -260,7 +260,6 @@ static int persistent_memory_claim(struct dm_writecache *wc)
loff_t s;
long p, da;
pfn_t pfn;
- int id;
struct page **pages;
sector_t offset;
@@ -284,7 +283,7 @@ static int persistent_memory_claim(struct dm_writecache *wc)
}
offset >>= PAGE_SHIFT - 9;
- id = dax_read_lock();
+ dax_read_lock(wc->ssd_dev->dax_dev);
da = dax_direct_access(wc->ssd_dev->dax_dev, offset, p, &wc->memory_map, &pfn);
if (da < 0) {
@@ -334,7 +333,7 @@ static int persistent_memory_claim(struct dm_writecache *wc)
wc->memory_vmapped = true;
}
- dax_read_unlock(id);
+ dax_read_unlock(wc->ssd_dev->dax_dev);
wc->memory_map += (size_t)wc->start_sector << SECTOR_SHIFT;
wc->memory_map_size -= (size_t)wc->start_sector << SECTOR_SHIFT;
@@ -343,7 +342,7 @@ static int persistent_memory_claim(struct dm_writecache *wc)
err3:
kvfree(pages);
err2:
- dax_read_unlock(id);
+ dax_read_unlock(wc->ssd_dev->dax_dev);
err1:
return r;
}
diff --git a/fs/dax.c b/fs/dax.c
index e0eecd8e3a8f..1f46810d4b68 100644
--- a/fs/dax.c
+++ b/fs/dax.c
@@ -717,20 +717,20 @@ static pgoff_t dax_iomap_pgoff(const struct iomap *iomap, loff_t pos)
static int copy_cow_page_dax(struct vm_fault *vmf, const struct iomap_iter *iter)
{
pgoff_t pgoff = dax_iomap_pgoff(&iter->iomap, iter->pos);
+ struct dax_device *dax_dev = iter->iomap.dax_dev;
void *vto, *kaddr;
long rc;
- int id;
- id = dax_read_lock();
- rc = dax_direct_access(iter->iomap.dax_dev, pgoff, 1, &kaddr, NULL);
+ dax_read_lock(dax_dev);
+ rc = dax_direct_access(dax_dev, pgoff, 1, &kaddr, NULL);
if (rc < 0) {
- dax_read_unlock(id);
+ dax_read_unlock(dax_dev);
return rc;
}
vto = kmap_atomic(vmf->cow_page);
copy_user_page(vto, kaddr, vmf->address, vmf->cow_page);
kunmap_atomic(vto);
- dax_read_unlock(id);
+ dax_read_unlock(dax_dev);
return 0;
}
@@ -1009,10 +1009,10 @@ static int dax_iomap_pfn(const struct iomap *iomap, loff_t pos, size_t size,
pfn_t *pfnp)
{
pgoff_t pgoff = dax_iomap_pgoff(iomap, pos);
- int id, rc;
+ int rc;
long length;
- id = dax_read_lock();
+ dax_read_lock(iomap->dax_dev);
length = dax_direct_access(iomap->dax_dev, pgoff, PHYS_PFN(size),
NULL, pfnp);
if (length < 0) {
@@ -1029,7 +1029,7 @@ static int dax_iomap_pfn(const struct iomap *iomap, loff_t pos, size_t size,
goto out;
rc = 0;
out:
- dax_read_unlock(id);
+ dax_read_unlock(iomap->dax_dev);
return rc;
}
@@ -1135,6 +1135,7 @@ static s64 dax_zero_iter(struct iomap_iter *iter, bool *did_zero)
{
const struct iomap *iomap = &iter->iomap;
const struct iomap *srcmap = iomap_iter_srcmap(iter);
+ struct dax_device *dax_dev = iomap->dax_dev;
loff_t pos = iter->pos;
u64 length = iomap_length(iter);
s64 written = 0;
@@ -1148,14 +1149,13 @@ static s64 dax_zero_iter(struct iomap_iter *iter, bool *did_zero)
unsigned size = min_t(u64, PAGE_SIZE - offset, length);
pgoff_t pgoff = dax_iomap_pgoff(iomap, pos);
long rc;
- int id;
- id = dax_read_lock();
+ dax_read_lock(dax_dev);
if (IS_ALIGNED(pos, PAGE_SIZE) && size == PAGE_SIZE)
- rc = dax_zero_page_range(iomap->dax_dev, pgoff, 1);
+ rc = dax_zero_page_range(dax_dev, pgoff, 1);
else
- rc = dax_memzero(iomap->dax_dev, pgoff, offset, size);
- dax_read_unlock(id);
+ rc = dax_memzero(dax_dev, pgoff, offset, size);
+ dax_read_unlock(dax_dev);
if (rc < 0)
return rc;
@@ -1209,7 +1209,6 @@ static loff_t dax_iomap_iter(const struct iomap_iter *iomi,
loff_t end = pos + length, done = 0;
ssize_t ret = 0;
size_t xfer;
- int id;
if (iov_iter_rw(iter) == READ) {
end = min(end, i_size_read(iomi->inode));
@@ -1234,7 +1233,7 @@ static loff_t dax_iomap_iter(const struct iomap_iter *iomi,
(end - 1) >> PAGE_SHIFT);
}
- id = dax_read_lock();
+ dax_read_lock(dax_dev);
while (pos < end) {
unsigned offset = pos & (PAGE_SIZE - 1);
const size_t size = ALIGN(length + offset, PAGE_SIZE);
@@ -1281,7 +1280,7 @@ static loff_t dax_iomap_iter(const struct iomap_iter *iomi,
if (xfer < map_len)
break;
}
- dax_read_unlock(id);
+ dax_read_unlock(dax_dev);
return done ? done : ret;
}
diff --git a/fs/fuse/dax.c b/fs/fuse/dax.c
index 713818d74de6..00a419acab79 100644
--- a/fs/fuse/dax.c
+++ b/fs/fuse/dax.c
@@ -1231,7 +1231,7 @@ static int fuse_dax_mem_range_init(struct fuse_conn_dax *fcd)
{
long nr_pages, nr_ranges;
struct fuse_dax_mapping *range;
- int ret, id;
+ int ret;
size_t dax_size = -1;
unsigned long i;
@@ -1240,10 +1240,10 @@ static int fuse_dax_mem_range_init(struct fuse_conn_dax *fcd)
INIT_LIST_HEAD(&fcd->busy_ranges);
INIT_DELAYED_WORK(&fcd->free_work, fuse_dax_free_mem_worker);
- id = dax_read_lock();
+ dax_read_lock(fcd->dev);
nr_pages = dax_direct_access(fcd->dev, 0, PHYS_PFN(dax_size), NULL,
NULL);
- dax_read_unlock(id);
+ dax_read_unlock(fcd->dev);
if (nr_pages < 0) {
pr_debug("dax_direct_access() returned %ld\n", nr_pages);
return nr_pages;
diff --git a/include/linux/dax.h b/include/linux/dax.h
index 87ae4c9b1d65..a146bfb80804 100644
--- a/include/linux/dax.h
+++ b/include/linux/dax.h
@@ -178,15 +178,24 @@ int dax_truncate_page(struct inode *inode, loff_t pos, bool *did_zero,
const struct iomap_ops *ops);
#if IS_ENABLED(CONFIG_DAX)
-int dax_read_lock(void);
-void dax_read_unlock(int id);
+void dax_read_lock(struct dax_device *dax_dev);
+void dax_read_unlock(struct dax_device *dax_dev);
+void dax_write_lock(struct dax_device *dax_dev);
+void dax_write_unlock(struct dax_device *dax_dev);
#else
-static inline int dax_read_lock(void)
+static inline void dax_read_lock(struct dax_device *dax_dev)
+{
+}
+
+static inline void dax_read_unlock(struct dax_device *dax_dev)
+{
+}
+
+static inline void dax_write_lock(struct dax_device *dax_dev)
{
- return 0;
}
-static inline void dax_read_unlock(int id)
+static inline void dax_write_unlock(struct dax_device *dax_dev)
{
}
#endif /* CONFIG_DAX */
--
2.34.1
Introduce a FS_DAX_MAPPING_COW flag to support association with CoW file
mappings. In this case, the dax-RMAP already takes the responsibility
to look up for shared files by given dax page. The page->mapping is no
longer to used for rmap but for marking that this dax page is shared.
And to make sure disassociation works fine, we use page->index as
refcount, and clear page->mapping to the initial state when page->index
is decreased to 0.
With the help of this new flag, it is able to distinguish normal case
and CoW case, and keep the warning in normal case.
Signed-off-by: Shiyang Ruan <[email protected]>
---
fs/dax.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 57 insertions(+), 9 deletions(-)
diff --git a/fs/dax.c b/fs/dax.c
index ad8ceea1f54c..e72ec712c002 100644
--- a/fs/dax.c
+++ b/fs/dax.c
@@ -335,12 +335,46 @@ static unsigned long dax_end_pfn(void *entry)
pfn < dax_end_pfn(entry); pfn++)
/*
- * TODO: for reflink+dax we need a way to associate a single page with
- * multiple address_space instances at different linear_page_index()
- * offsets.
+ * Set FS_DAX_MAPPING_COW flag on the last bit of page->mapping to indicate that
+ * this is a reflink case. In this case, we associate this page->mapping with
+ * file mapping at the first time and only once.
+ */
+#define FS_DAX_MAPPING_COW 1UL
+
+#define MAPPING_SET_COW(m) (m = (struct address_space *)FS_DAX_MAPPING_COW)
+#define MAPPING_TEST_COW(m) (((unsigned long)m & FS_DAX_MAPPING_COW) == \
+ FS_DAX_MAPPING_COW)
+
+/*
+ * Set or Update the page->mapping with FS_DAX_MAPPING_COW flag.
+ * Return true if it is an Update.
+ */
+static inline bool dax_mapping_set_cow(struct page *page)
+{
+ if (page->mapping) {
+ /* flag already set */
+ if (MAPPING_TEST_COW(page->mapping))
+ return false;
+
+ /*
+ * This page has been mapped even before it is shared, just
+ * need to set this FS_DAX_MAPPING_COW flag.
+ */
+ MAPPING_SET_COW(page->mapping);
+ return true;
+ }
+ /* Newly associate CoW mapping */
+ MAPPING_SET_COW(page->mapping);
+ return false;
+}
+
+/*
+ * When it is called in dax_insert_entry(), the cow flag will indicate that
+ * whether this entry is shared by multiple files. If so, set the page->mapping
+ * to be FS_DAX_MAPPING_COW, and use page->index as refcount.
*/
static void dax_associate_entry(void *entry, struct address_space *mapping,
- struct vm_area_struct *vma, unsigned long address)
+ struct vm_area_struct *vma, unsigned long address, bool cow)
{
unsigned long size = dax_entry_size(entry), pfn, index;
int i = 0;
@@ -352,9 +386,17 @@ 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 (cow) {
+ if (dax_mapping_set_cow(page)) {
+ /* Was normal, now updated to CoW */
+ page->index = 2;
+ } else
+ page->index++;
+ } else {
+ WARN_ON_ONCE(page->mapping);
+ page->mapping = mapping;
+ page->index = index + i++;
+ }
}
}
@@ -370,7 +412,12 @@ 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);
+ if (!MAPPING_TEST_COW(page->mapping)) {
+ /* keep the CoW flag if this page is still shared */
+ if (page->index-- > 0)
+ continue;
+ } else
+ WARN_ON_ONCE(page->mapping && page->mapping != mapping);
page->mapping = NULL;
page->index = 0;
}
@@ -829,7 +876,8 @@ static void *dax_insert_entry(struct xa_state *xas,
void *old;
dax_disassociate_entry(entry, mapping, false);
- dax_associate_entry(new_entry, mapping, vmf->vma, vmf->address);
+ dax_associate_entry(new_entry, mapping, vmf->vma, vmf->address,
+ false);
/*
* Only swap our new entry into the page cache if the current
* entry is a zero page or an empty entry. If a normal PTE or
--
2.34.1
The function name has been changed, so the description should be updated
too.
Signed-off-by: Shiyang Ruan <[email protected]>
---
fs/dax.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/dax.c b/fs/dax.c
index 1f46810d4b68..2ee2d5a525ee 100644
--- a/fs/dax.c
+++ b/fs/dax.c
@@ -390,7 +390,7 @@ static struct page *dax_busy_page(void *entry)
}
/*
- * dax_lock_mapping_entry - Lock the DAX entry corresponding to a page
+ * dax_lock_page - Lock the DAX entry corresponding to a page
* @page: The page whose entry we want to lock
*
* Context: Process context.
--
2.34.1
Introduce xfs_notify_failure.c to handle failure related works, such as
implement ->notify_failure(), register/unregister dax holder in xfs, and
so on.
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]>
---
fs/xfs/Makefile | 1 +
fs/xfs/xfs_buf.c | 15 +++
fs/xfs/xfs_fsops.c | 3 +
fs/xfs/xfs_mount.h | 1 +
fs/xfs/xfs_notify_failure.c | 189 ++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_notify_failure.h | 10 ++
6 files changed, 219 insertions(+)
create mode 100644 fs/xfs/xfs_notify_failure.c
create mode 100644 fs/xfs/xfs_notify_failure.h
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 04611a1068b4..389970b3e13b 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -84,6 +84,7 @@ xfs-y += xfs_aops.o \
xfs_message.o \
xfs_mount.o \
xfs_mru_cache.o \
+ xfs_notify_failure.o \
xfs_pwork.o \
xfs_reflink.o \
xfs_stats.o \
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index bbb0fbd34e64..d0df7604fa9e 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -19,6 +19,7 @@
#include "xfs_errortag.h"
#include "xfs_error.h"
#include "xfs_ag.h"
+#include "xfs_notify_failure.h"
static struct kmem_cache *xfs_buf_cache;
@@ -1892,6 +1893,8 @@ xfs_free_buftarg(
list_lru_destroy(&btp->bt_lru);
blkdev_issue_flush(btp->bt_bdev);
+ if (btp->bt_daxdev)
+ dax_unregister_holder(btp->bt_daxdev);
fs_put_dax(btp->bt_daxdev);
kmem_free(btp);
@@ -1946,6 +1949,18 @@ xfs_alloc_buftarg(
btp->bt_dev = bdev->bd_dev;
btp->bt_bdev = bdev;
btp->bt_daxdev = fs_dax_get_by_bdev(bdev, &btp->bt_dax_part_off);
+ if (btp->bt_daxdev) {
+ dax_write_lock(btp->bt_daxdev);
+ if (dax_get_holder(btp->bt_daxdev)) {
+ dax_write_unlock(btp->bt_daxdev);
+ xfs_err(mp, "DAX device already in use?!");
+ goto error_free;
+ }
+
+ dax_register_holder(btp->bt_daxdev, mp,
+ &xfs_dax_holder_operations);
+ dax_write_unlock(btp->bt_daxdev);
+ }
/*
* Buffer IO error rate limiting. Limit it to no more than 10 messages
diff --git a/fs/xfs/xfs_fsops.c b/fs/xfs/xfs_fsops.c
index 33e26690a8c4..d4d36c5bef11 100644
--- a/fs/xfs/xfs_fsops.c
+++ b/fs/xfs/xfs_fsops.c
@@ -542,6 +542,9 @@ xfs_do_force_shutdown(
} else if (flags & SHUTDOWN_CORRUPT_INCORE) {
tag = XFS_PTAG_SHUTDOWN_CORRUPT;
why = "Corruption of in-memory data";
+ } else if (flags & SHUTDOWN_CORRUPT_ONDISK) {
+ tag = XFS_PTAG_SHUTDOWN_CORRUPT;
+ why = "Corruption of on-disk metadata";
} else {
tag = XFS_PTAG_SHUTDOWN_IOERROR;
why = "Metadata I/O Error";
diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
index 00720a02e761..47ff4ac53c4c 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -435,6 +435,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_ONDISK 0x0010 /* corrupt metadata on device */
#define XFS_SHUTDOWN_STRINGS \
{ SHUTDOWN_META_IO_ERROR, "metadata_io" }, \
diff --git a/fs/xfs/xfs_notify_failure.c b/fs/xfs/xfs_notify_failure.c
new file mode 100644
index 000000000000..a87bd08365f4
--- /dev/null
+++ b/fs/xfs/xfs_notify_failure.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Fujitsu. All Rights Reserved.
+ */
+
+#include "xfs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_log_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_alloc.h"
+#include "xfs_bit.h"
+#include "xfs_btree.h"
+#include "xfs_inode.h"
+#include "xfs_icache.h"
+#include "xfs_rmap.h"
+#include "xfs_rmap_btree.h"
+#include "xfs_rtalloc.h"
+#include "xfs_trans.h"
+
+#include <linux/mm.h>
+#include <linux/dax.h>
+
+struct failure_info {
+ xfs_agblock_t startblock;
+ xfs_filblks_t blockcount;
+ int mf_flags;
+};
+
+static pgoff_t
+xfs_failure_pgoff(
+ struct xfs_mount *mp,
+ const struct xfs_rmap_irec *rec,
+ const struct failure_info *notify)
+{
+ uint64_t pos = rec->rm_offset;
+
+ if (notify->startblock > rec->rm_startblock)
+ pos += XFS_FSB_TO_B(mp,
+ notify->startblock - rec->rm_startblock);
+ return pos >> PAGE_SHIFT;
+}
+
+static unsigned long
+xfs_failure_pgcnt(
+ struct xfs_mount *mp,
+ const struct xfs_rmap_irec *rec,
+ const struct failure_info *notify)
+{
+ xfs_agblock_t start_rec = rec->rm_startblock;
+ xfs_agblock_t end_rec = rec->rm_startblock + rec->rm_blockcount;
+ xfs_agblock_t start_notify = notify->startblock;
+ xfs_agblock_t end_notify = notify->startblock + notify->blockcount;
+ xfs_agblock_t start_cross = max(start_rec, start_notify);
+ xfs_agblock_t end_cross = min(end_rec, end_notify);
+
+ return XFS_FSB_TO_B(mp, end_cross - start_cross) >> PAGE_SHIFT;
+}
+
+static int
+xfs_dax_failure_fn(
+ struct xfs_btree_cur *cur,
+ const struct xfs_rmap_irec *rec,
+ void *data)
+{
+ struct xfs_mount *mp = cur->bc_mp;
+ struct xfs_inode *ip;
+ struct address_space *mapping;
+ struct failure_info *notify = data;
+ int error = 0;
+
+ 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(mp, SHUTDOWN_CORRUPT_ONDISK);
+ return -EFSCORRUPTED;
+ }
+
+ /* Get files that incore, filter out others that are not in use. */
+ error = xfs_iget(mp, cur->bc_tp, rec->rm_owner, XFS_IGET_INCORE,
+ 0, &ip);
+ /* Continue the rmap query if the inode isn't incore */
+ if (error == -ENODATA)
+ return 0;
+ if (error)
+ return error;
+
+ mapping = VFS_I(ip)->i_mapping;
+ if (IS_ENABLED(CONFIG_MEMORY_FAILURE)) {
+ pgoff_t off = xfs_failure_pgoff(mp, rec, notify);
+ unsigned long cnt = xfs_failure_pgcnt(mp, rec, notify);
+
+ error = mf_dax_kill_procs(mapping, off, cnt, notify->mf_flags);
+ }
+ /* TODO try to fix data */
+ xfs_irele(ip);
+
+ return error;
+}
+
+static int
+xfs_dax_notify_ddev_failure(
+ struct xfs_mount *mp,
+ xfs_daddr_t daddr,
+ xfs_daddr_t bblen,
+ int mf_flags)
+{
+ struct xfs_trans *tp = NULL;
+ struct xfs_btree_cur *cur = NULL;
+ struct xfs_buf *agf_bp = NULL;
+ struct failure_info notify = { .mf_flags = mf_flags };
+ int error = 0;
+ xfs_fsblock_t fsbno = XFS_DADDR_TO_FSB(mp, daddr);
+ xfs_agnumber_t agno = XFS_FSB_TO_AGNO(mp, fsbno);
+ xfs_fsblock_t end_fsbno = XFS_DADDR_TO_FSB(mp, daddr + bblen);
+ xfs_agnumber_t end_agno = XFS_FSB_TO_AGNO(mp, end_fsbno);
+
+ error = xfs_trans_alloc_empty(mp, &tp);
+ if (error)
+ return error;
+
+ for (; agno <= end_agno; agno++) {
+ struct xfs_rmap_irec ri_low = { };
+ struct xfs_rmap_irec ri_high;
+
+ notify.startblock = XFS_FSB_TO_AGBNO(mp, fsbno);
+ notify.blockcount = XFS_BB_TO_FSB(mp, bblen);
+
+ error = xfs_alloc_read_agf(mp, tp, agno, 0, &agf_bp);
+ if (error)
+ break;
+
+ cur = xfs_rmapbt_init_cursor(mp, tp, agf_bp, agf_bp->b_pag);
+
+ memset(&ri_high, 0xFF, sizeof(ri_high));
+ ri_low.rm_startblock = XFS_FSB_TO_AGBNO(mp, fsbno);
+ if (agno == end_agno)
+ ri_high.rm_startblock = XFS_FSB_TO_AGBNO(mp, end_fsbno);
+
+ error = xfs_rmap_query_range(cur, &ri_low, &ri_high,
+ xfs_dax_failure_fn, ¬ify);
+ xfs_btree_del_cursor(cur, error);
+ xfs_trans_brelse(tp, agf_bp);
+ if (error)
+ break;
+
+ fsbno = XFS_AGB_TO_FSB(mp, agno + 1, 0);
+ }
+
+ xfs_trans_cancel(tp);
+ return error;
+}
+
+static int
+xfs_dax_notify_failure(
+ struct dax_device *dax_dev,
+ u64 offset,
+ u64 len,
+ int mf_flags)
+{
+ struct xfs_mount *mp = dax_get_holder(dax_dev);
+
+ if (mp->m_rtdev_targp && mp->m_rtdev_targp->bt_daxdev == dax_dev) {
+ xfs_warn(mp,
+ "notify_failure() not supported on realtime device!");
+ return -EOPNOTSUPP;
+ }
+
+ 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_ONDISK);
+ return -EFSCORRUPTED;
+ }
+
+ if (!xfs_has_rmapbt(mp)) {
+ xfs_warn(mp, "notify_failure() needs rmapbt enabled!");
+ return -EOPNOTSUPP;
+ }
+
+ offset -= mp->m_ddev_targp->bt_dax_part_off;
+ return xfs_dax_notify_ddev_failure(mp, BTOBB(offset), BTOBB(len),
+ mf_flags);
+}
+
+const struct dax_holder_operations xfs_dax_holder_operations = {
+ .notify_failure = xfs_dax_notify_failure,
+};
diff --git a/fs/xfs/xfs_notify_failure.h b/fs/xfs/xfs_notify_failure.h
new file mode 100644
index 000000000000..f40cb315e7ce
--- /dev/null
+++ b/fs/xfs/xfs_notify_failure.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Fujitsu. All Rights Reserved.
+ */
+#ifndef __XFS_NOTIFY_FAILURE_H__
+#define __XFS_NOTIFY_FAILURE_H__
+
+extern const struct dax_holder_operations xfs_dax_holder_operations;
+
+#endif /* __XFS_NOTIFY_FAILURE_H__ */
--
2.34.1
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.
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]>
Reviewed-by: Christoph Hellwig <[email protected]>
---
drivers/nvdimm/pmem.c | 16 ++++++++++++++++
include/linux/memremap.h | 9 +++++++++
mm/memory-failure.c | 14 ++++++++++++++
3 files changed, 39 insertions(+)
diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c
index 4190c8c46ca8..2114554358eb 100644
--- a/drivers/nvdimm/pmem.c
+++ b/drivers/nvdimm/pmem.c
@@ -386,6 +386,20 @@ static void pmem_release_disk(void *__pmem)
blk_cleanup_disk(pmem->disk);
}
+static int pmem_pagemap_memory_failure(struct dev_pagemap *pgmap,
+ unsigned long pfn, u64 len, int mf_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, len, mf_flags);
+}
+
+static const struct dev_pagemap_ops fsdax_pagemap_ops = {
+ .memory_failure = pmem_pagemap_memory_failure,
+};
+
static int pmem_attach_disk(struct device *dev,
struct nd_namespace_common *ndns)
{
@@ -448,6 +462,7 @@ static int pmem_attach_disk(struct device *dev,
pmem->pfn_flags = PFN_DEV;
if (is_nd_pfn(dev)) {
pmem->pgmap.type = MEMORY_DEVICE_FS_DAX;
+ pmem->pgmap.ops = &fsdax_pagemap_ops;
addr = devm_memremap_pages(dev, &pmem->pgmap);
pfn_sb = nd_pfn->pfn_sb;
pmem->data_offset = le64_to_cpu(pfn_sb->dataoff);
@@ -461,6 +476,7 @@ static int pmem_attach_disk(struct device *dev,
pmem->pgmap.range.end = res->end;
pmem->pgmap.nr_range = 1;
pmem->pgmap.type = MEMORY_DEVICE_FS_DAX;
+ pmem->pgmap.ops = &fsdax_pagemap_ops;
addr = devm_memremap_pages(dev, &pmem->pgmap);
pmem->pfn_flags |= PFN_MAP;
bb_range = pmem->pgmap.range;
diff --git a/include/linux/memremap.h b/include/linux/memremap.h
index c0e9d35889e8..820c2f33b163 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 mf_flags is finally passed to the recover
+ * function through the whole notify routine.
+ */
+ int (*memory_failure)(struct dev_pagemap *pgmap, unsigned long pfn,
+ u64 len, int mf_flags);
};
#define PGMAP_ALTMAP_VALID (1 << 0)
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 1ee7d626fed7..3cc612b29f89 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1625,6 +1625,20 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
if (!pgmap_pfn_valid(pgmap, pfn))
goto out;
+ /*
+ * 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, PAGE_SIZE, flags);
+ /*
+ * Fall back to generic handler too if operation is not
+ * supported inside the driver/device/filesystem.
+ */
+ if (rc != -EOPNOTSUPP)
+ goto out;
+ }
+
rc = mf_generic_kill_procs(pfn, flags, pgmap);
out:
/* drop pgmap ref acquired in caller */
--
2.34.1
The current dax_lock_page() locks dax entry by obtaining mapping and
index in page. To support 1-to-N RMAP in NVDIMM, we need a new function
to lock a specific dax entry corresponding to this file's mapping,index.
And output the page corresponding to the specific dax entry for caller
use.
Signed-off-by: Shiyang Ruan <[email protected]>
Reviewed-by: Christoph Hellwig <[email protected]>
---
fs/dax.c | 63 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 15 +++++++++++
2 files changed, 78 insertions(+)
diff --git a/fs/dax.c b/fs/dax.c
index 2ee2d5a525ee..b3c737aff9de 100644
--- a/fs/dax.c
+++ b/fs/dax.c
@@ -455,6 +455,69 @@ void dax_unlock_page(struct page *page, dax_entry_t cookie)
dax_unlock_entry(&xas, (void *)cookie);
}
+/*
+ * dax_lock_mapping_entry - Lock the DAX entry corresponding to a mapping
+ * @mapping: the file's mapping whose entry we want to lock
+ * @index: the offset within this file
+ * @page: output the dax page corresponding to this dax entry
+ *
+ * Return: A cookie to pass to dax_unlock_mapping_entry() or 0 if the entry
+ * could not be locked.
+ */
+dax_entry_t dax_lock_mapping_entry(struct address_space *mapping, pgoff_t index,
+ struct page **page)
+{
+ XA_STATE(xas, NULL, 0);
+ void *entry;
+
+ rcu_read_lock();
+ for (;;) {
+ entry = NULL;
+ if (!dax_mapping(mapping))
+ break;
+
+ xas.xa = &mapping->i_pages;
+ xas_lock_irq(&xas);
+ xas_set(&xas, index);
+ entry = xas_load(&xas);
+ if (dax_is_locked(entry)) {
+ rcu_read_unlock();
+ wait_entry_unlocked(&xas, entry);
+ rcu_read_lock();
+ continue;
+ }
+ if (!entry ||
+ dax_is_zero_entry(entry) || dax_is_empty_entry(entry)) {
+ /*
+ * Because we are looking for entry from file's mapping
+ * and index, so the entry may not be inserted for now,
+ * or even a zero/empty entry. We don't think this is
+ * an error case. So, return a special value and do
+ * not output @page.
+ */
+ entry = (void *)~0UL;
+ } else {
+ *page = pfn_to_page(dax_to_pfn(entry));
+ dax_lock_entry(&xas, entry);
+ }
+ xas_unlock_irq(&xas);
+ break;
+ }
+ rcu_read_unlock();
+ return (dax_entry_t)entry;
+}
+
+void dax_unlock_mapping_entry(struct address_space *mapping, pgoff_t index,
+ dax_entry_t cookie)
+{
+ XA_STATE(xas, &mapping->i_pages, index);
+
+ if (cookie == ~0UL)
+ return;
+
+ dax_unlock_entry(&xas, (void *)cookie);
+}
+
/*
* Find page cache entry at given index. If it is a DAX entry, return it
* with the entry locked. If the page cache doesn't contain an entry at
diff --git a/include/linux/dax.h b/include/linux/dax.h
index e16a9e0ee857..f602a1c462d3 100644
--- a/include/linux/dax.h
+++ b/include/linux/dax.h
@@ -170,6 +170,10 @@ 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);
dax_entry_t dax_lock_page(struct page *page);
void dax_unlock_page(struct page *page, dax_entry_t cookie);
+dax_entry_t dax_lock_mapping_entry(struct address_space *mapping,
+ unsigned long index, struct page **page);
+void dax_unlock_mapping_entry(struct address_space *mapping,
+ unsigned long index, dax_entry_t cookie);
#else
static inline struct page *dax_layout_busy_page(struct address_space *mapping)
{
@@ -197,6 +201,17 @@ static inline dax_entry_t dax_lock_page(struct page *page)
static inline void dax_unlock_page(struct page *page, dax_entry_t cookie)
{
}
+
+static inline dax_entry_t dax_lock_mapping_entry(struct address_space *mapping,
+ unsigned long index, struct page **page)
+{
+ return 0;
+}
+
+static inline void dax_unlock_mapping_entry(struct address_space *mapping,
+ unsigned long index, dax_entry_t cookie)
+{
+}
#endif
int dax_zero_range(struct inode *inode, loff_t pos, loff_t len, bool *did_zero,
--
2.34.1
This function is called at the end of RMAP routine, i.e. filesystem
recovery function, to collect and kill processes using a shared page of
DAX file. The difference with mf_generic_kill_procs() is, it accepts
file's (mapping,offset) instead of struct page because different files'
mappings and offsets may share the same page in fsdax mode.
It will be called when filesystem's RMAP results are found.
Signed-off-by: Shiyang Ruan <[email protected]>
---
include/linux/mm.h | 2 +
mm/memory-failure.c | 89 +++++++++++++++++++++++++++++++++++++++------
2 files changed, 80 insertions(+), 11 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 3f44cd9e296c..15212a78bf1d 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3234,6 +3234,8 @@ enum mf_flags {
MF_MUST_KILL = 1 << 2,
MF_SOFT_OFFLINE = 1 << 3,
};
+int mf_dax_kill_procs(struct address_space *mapping, pgoff_t index,
+ unsigned long count, int mf_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 3cc612b29f89..dc61f97bba2f 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -303,10 +303,9 @@ void shake_page(struct page *p)
}
EXPORT_SYMBOL_GPL(shake_page);
-static unsigned long dev_pagemap_mapping_shift(struct page *page,
- struct vm_area_struct *vma)
+static unsigned long dev_pagemap_mapping_shift(struct vm_area_struct *vma,
+ unsigned long address)
{
- unsigned long address = vma_address(page, vma);
unsigned long ret = 0;
pgd_t *pgd;
p4d_t *p4d;
@@ -346,9 +345,8 @@ 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,
- struct vm_area_struct *vma,
- struct list_head *to_kill)
+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)
{
struct to_kill *tk;
@@ -359,9 +357,15 @@ 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 not used for fsdax, we need
+ * calculate the address based on the vma.
+ */
+ if (p->pgmap->type == MEMORY_DEVICE_FS_DAX)
+ tk->addr = vma_pgoff_address(vma, pgoff);
+ tk->size_shift = dev_pagemap_mapping_shift(vma, tk->addr);
+ } else
tk->size_shift = page_shift(compound_head(p));
/*
@@ -509,7 +513,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);
@@ -545,7 +549,33 @@ 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, 0, vma, to_kill);
+ }
+ }
+ read_unlock(&tasklist_lock);
+ i_mmap_unlock_read(mapping);
+}
+
+/*
+ * Collect processes when the error hit a fsdax page.
+ */
+static void collect_procs_fsdax(struct page *page,
+ struct address_space *mapping, pgoff_t pgoff,
+ struct list_head *to_kill)
+{
+ struct vm_area_struct *vma;
+ struct task_struct *tsk;
+
+ i_mmap_lock_read(mapping);
+ read_lock(&tasklist_lock);
+ for_each_process(tsk) {
+ struct task_struct *t = task_early_kill(tsk, true);
+
+ if (!t)
+ continue;
+ vma_interval_tree_foreach(vma, &mapping->i_mmap, pgoff, pgoff) {
+ if (vma->vm_mm == t->mm)
+ add_to_kill(t, page, pgoff, vma, to_kill);
}
}
read_unlock(&tasklist_lock);
@@ -1523,6 +1553,43 @@ static int mf_generic_kill_procs(unsigned long long pfn, int flags,
return 0;
}
+/**
+ * mf_dax_kill_procs - Collect and kill processes who are using this file range
+ * @mapping: the file in use
+ * @index: start pgoff of the range within the file
+ * @count: length of the range, in unit of PAGE_SIZE
+ * @mf_flags: memory failure flags
+ */
+int mf_dax_kill_procs(struct address_space *mapping, pgoff_t index,
+ unsigned long count, int mf_flags)
+{
+ LIST_HEAD(to_kill);
+ dax_entry_t cookie;
+ struct page *page;
+ size_t end = index + count;
+
+ mf_flags |= MF_ACTION_REQUIRED | MF_MUST_KILL;
+
+ for (; index < end; index++) {
+ page = NULL;
+ cookie = dax_lock_mapping_entry(mapping, index, &page);
+ if (!cookie)
+ return -EBUSY;
+ if (!page)
+ goto unlock;
+
+ SetPageHWPoison(page);
+
+ collect_procs_fsdax(page, mapping, index, &to_kill);
+ unmap_and_kill(&to_kill, page_to_pfn(page), mapping,
+ index, mf_flags);
+unlock:
+ dax_unlock_mapping_entry(mapping, index, cookie);
+ }
+ 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.34.1
Since it is not a DAX-specific function, move it into mm and rename it
to be a generic helper.
Signed-off-by: Shiyang Ruan <[email protected]>
---
fs/dax.c | 12 +-----------
include/linux/mm.h | 13 +++++++++++++
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/fs/dax.c b/fs/dax.c
index b3c737aff9de..ad8ceea1f54c 100644
--- a/fs/dax.c
+++ b/fs/dax.c
@@ -853,16 +853,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)
@@ -882,7 +872,7 @@ static void dax_entry_mkclean(struct address_space *mapping, pgoff_t index,
if (!(vma->vm_flags & VM_SHARED))
continue;
- address = pgoff_address(index, vma);
+ address = vma_pgoff_address(vma, index);
/*
* follow_invalidate_pte() will use the range to call
diff --git a/include/linux/mm.h b/include/linux/mm.h
index a7e4a9e7d807..3f44cd9e296c 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2855,6 +2855,19 @@ static inline unsigned long vma_pages(struct vm_area_struct *vma)
return (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
}
+/*
+ * Get user virtual address at the specific offset within a vma.
+ */
+static inline unsigned long vma_pgoff_address(struct vm_area_struct *vma,
+ pgoff_t pgoff)
+{
+ 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;
+}
+
/* Look up the first VMA which exactly match the interval vm_start ... vm_end */
static inline struct vm_area_struct *find_exact_vma(struct mm_struct *mm,
unsigned long vm_start, unsigned long vm_end)
--
2.34.1
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]>
Reviewed-by: Darrick J. Wong <[email protected]>
Reviewed-by: Christoph Hellwig <[email protected]>
---
mm/memory-failure.c | 141 ++++++++++++++++++++++++--------------------
1 file changed, 77 insertions(+), 64 deletions(-)
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index 07c875fdeaf0..1ee7d626fed7 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -1449,6 +1449,80 @@ 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 dev_pagemap *pgmap)
+{
+ 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;
+
+ if (hwpoison_filter(page))
+ return 0;
+
+ if (pgmap->type == MEMORY_DEVICE_PRIVATE) {
+ /*
+ * TODO: Handle HMM pages which may need coordination
+ * with device-side memory.
+ */
+ return -EBUSY;
+ }
+
+ /*
+ * Use this flag as an indication that the dax page has been
+ * remapped UC to prevent speculative consumption of poison.
+ */
+ 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, &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);
@@ -1538,12 +1612,8 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
struct dev_pagemap *pgmap)
{
struct page *page = pfn_to_page(pfn);
- unsigned long size = 0;
- struct to_kill *tk;
LIST_HEAD(tokill);
- int rc = -EBUSY;
- loff_t start;
- dax_entry_t cookie;
+ int rc = -ENXIO;
if (flags & MF_COUNT_INCREASED)
/*
@@ -1552,67 +1622,10 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
put_page(page);
/* device metadata space is not recoverable */
- if (!pgmap_pfn_valid(pgmap, pfn)) {
- rc = -ENXIO;
- 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)
+ if (!pgmap_pfn_valid(pgmap, pfn))
goto out;
- if (hwpoison_filter(page)) {
- rc = 0;
- goto unlock;
- }
-
- if (pgmap->type == MEMORY_DEVICE_PRIVATE) {
- /*
- * TODO: Handle HMM pages which may need coordination
- * with device-side memory.
- */
- goto unlock;
- }
-
- /*
- * Use this flag as an indication that the dax page has been
- * remapped UC to prevent speculative consumption of poison.
- */
- 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, false, pfn, flags);
- rc = 0;
-unlock:
- dax_unlock_page(page, cookie);
+ rc = mf_generic_kill_procs(pfn, flags, pgmap);
out:
/* drop pgmap ref acquired in caller */
put_dev_pagemap(pgmap);
--
2.34.1
On Sun, Dec 26, 2021 at 6:35 AM Shiyang Ruan <[email protected]> wrote:
>
> In order to introduce dax holder registration, we need a write lock for
> dax.
As far as I can see, no, a write lock is not needed while the holder
is being registered.
The synchronization that is needed is to make sure that the device
stays live over the registration event, and that any in-flight holder
operations are flushed before the device transitions from live to
dead, and that in turn relates to the live state of the pgmap.
The dax device cannot switch from live to dead without first flushing
all readers, so holding dax_read_lock() over the register holder event
should be sufficient. If you are worried about 2 or more potential
holders colliding at registration time, I would expect that's already
prevented by block device exclusive holder synchronization, but you
could also use cmpxchg and a single pointer to a 'struct dax_holder {
void *holder_data, struct dax_holder_operations *holder_ops }'. If you
are worried about memory_failure triggering while the filesystem is
shutting down it can do a synchronize_srcu(&dax_srcu) if it really
needs to ensure that the notify path is idle after removing the holder
registration.
...are there any cases remaining not covered by the above suggestions?
On Sun, Dec 26, 2021 at 6:35 AM Shiyang Ruan <[email protected]> wrote:
>
> The current dax_lock_page() locks dax entry by obtaining mapping and
> index in page. To support 1-to-N RMAP in NVDIMM, we need a new function
> to lock a specific dax entry corresponding to this file's mapping,index.
> And output the page corresponding to the specific dax entry for caller
> use.
Is this necessary? The point of dax_lock_page() is to ensure that the
fs does not destroy the address_space, or remap the pfn while
memory_failure() is operating on the pfn. In the notify_failure case
control is handed to the fs so I expect it can make those guarantees
itself, no?
On Tue, Jan 04, 2022 at 02:44:08PM -0800, Dan Williams wrote:
> On Sun, Dec 26, 2021 at 6:35 AM Shiyang Ruan <[email protected]> wrote:
> >
> > In order to introduce dax holder registration, we need a write lock for
> > dax.
>
> As far as I can see, no, a write lock is not needed while the holder
> is being registered.
>
> The synchronization that is needed is to make sure that the device
> stays live over the registration event, and that any in-flight holder
> operations are flushed before the device transitions from live to
> dead, and that in turn relates to the live state of the pgmap.
>
> The dax device cannot switch from live to dead without first flushing
> all readers, so holding dax_read_lock() over the register holder event
> should be sufficient.
...and perhaps add a comment describing that this is what the
synchronization primitive is really protecting against? The first time
I read through this patchset, I assumed the rwsem was protecting
&dax_hosts and was confused when I saw the one use of dax_write_lock.
--D
> If you are worried about 2 or more potential
> holders colliding at registration time, I would expect that's already
> prevented by block device exclusive holder synchronization, but you
> could also use cmpxchg and a single pointer to a 'struct dax_holder {
> void *holder_data, struct dax_holder_operations *holder_ops }'. If you
> are worried about memory_failure triggering while the filesystem is
> shutting down it can do a synchronize_srcu(&dax_srcu) if it really
> needs to ensure that the notify path is idle after removing the holder
> registration.
>
> ...are there any cases remaining not covered by the above suggestions?
On Sun, Dec 26, 2021 at 10:34:34PM +0800, Shiyang Ruan wrote:
> The function name has been changed, so the description should be updated
> too.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
Reviewed-by: Darrick J. Wong <[email protected]>
--D
> ---
> fs/dax.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/fs/dax.c b/fs/dax.c
> index 1f46810d4b68..2ee2d5a525ee 100644
> --- a/fs/dax.c
> +++ b/fs/dax.c
> @@ -390,7 +390,7 @@ static struct page *dax_busy_page(void *entry)
> }
>
> /*
> - * dax_lock_mapping_entry - Lock the DAX entry corresponding to a page
> + * dax_lock_page - Lock the DAX entry corresponding to a page
> * @page: The page whose entry we want to lock
> *
> * Context: Process context.
> --
> 2.34.1
>
>
>
On Sun, Dec 26, 2021 at 10:34:31PM +0800, Shiyang Ruan 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
> instance 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 | 62 +++++++++++++++++++++++++++++++++++++++++++++
> include/linux/dax.h | 29 +++++++++++++++++++++
> 2 files changed, 91 insertions(+)
>
> diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> index c46f56e33d40..94c51f2ee133 100644
> --- a/drivers/dax/super.c
> +++ b/drivers/dax/super.c
> @@ -20,15 +20,20 @@
> * @inode: core vfs
> * @cdev: optional character interface for "device dax"
> * @private: dax driver private data
> + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> * @flags: state and boolean properties
> + * @ops: operations for dax_device
> + * @holder_ops: operations for the inner holder
> */
> struct dax_device {
> struct inode inode;
> struct cdev cdev;
> void *private;
> struct percpu_rw_semaphore rwsem;
> + void *holder_data;
> unsigned long flags;
> const struct dax_operations *ops;
> + const struct dax_holder_operations *holder_ops;
> };
>
> static dev_t dax_devt;
> @@ -192,6 +197,29 @@ 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, u64 off,
> + u64 len, int mf_flags)
> +{
> + int rc;
> +
> + dax_read_lock(dax_dev);
> + if (!dax_alive(dax_dev)) {
> + rc = -ENXIO;
> + goto out;
> + }
> +
> + if (!dax_dev->holder_ops) {
> + rc = -EOPNOTSUPP;
> + goto out;
> + }
> +
> + rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
> +out:
> + dax_read_unlock(dax_dev);
> + 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)
> @@ -254,6 +282,10 @@ void kill_dax(struct dax_device *dax_dev)
> return;
> dax_write_lock(dax_dev);
> clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
> +
> + /* clear holder data */
> + dax_dev->holder_ops = NULL;
> + dax_dev->holder_data = NULL;
> dax_write_unlock(dax_dev);
> }
> EXPORT_SYMBOL_GPL(kill_dax);
> @@ -401,6 +433,36 @@ void put_dax(struct dax_device *dax_dev)
> }
> EXPORT_SYMBOL_GPL(put_dax);
>
> +void dax_register_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops)
> +{
> + if (!dax_alive(dax_dev))
> + return;
> +
> + dax_dev->holder_data = holder;
> + dax_dev->holder_ops = ops;
Shouldn't this return an error code if the dax device is dead or if
someone already registered a holder? I'm pretty sure XFS should not
bind to a dax device if someone else already registered for it...
...unless you want to use a notifier chain for failure events so that
there can be multiple consumers of dax failure events?
--D
> +}
> +EXPORT_SYMBOL_GPL(dax_register_holder);
> +
> +void dax_unregister_holder(struct dax_device *dax_dev)
> +{
> + if (!dax_alive(dax_dev))
> + return;
> +
> + dax_dev->holder_data = NULL;
> + dax_dev->holder_ops = NULL;
> +}
> +EXPORT_SYMBOL_GPL(dax_unregister_holder);
> +
> +void *dax_get_holder(struct dax_device *dax_dev)
> +{
> + if (!dax_alive(dax_dev))
> + return NULL;
> +
> + return dax_dev->holder_data;
> +}
> +EXPORT_SYMBOL_GPL(dax_get_holder);
> +
> /**
> * inode_dax: convert a public inode into its dax_dev
> * @inode: An inode with i_cdev pointing to a dax_dev
> diff --git a/include/linux/dax.h b/include/linux/dax.h
> index a146bfb80804..e16a9e0ee857 100644
> --- a/include/linux/dax.h
> +++ b/include/linux/dax.h
> @@ -44,6 +44,22 @@ struct dax_operations {
> #if IS_ENABLED(CONFIG_DAX)
> struct dax_device *alloc_dax(void *private, const struct dax_operations *ops,
> unsigned long flags);
> +struct dax_holder_operations {
> + /*
> + * notify_failure - notify memory failure into inner holder device
> + * @dax_dev: the dax device which contains the holder
> + * @offset: offset on this dax device where memory failure occurs
> + * @len: length of this memory failure event
> + * @flags: action flags for memory failure handler
> + */
> + int (*notify_failure)(struct dax_device *dax_dev, u64 offset,
> + u64 len, int mf_flags);
> +};
> +
> +void dax_register_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops);
> +void dax_unregister_holder(struct dax_device *dax_dev);
> +void *dax_get_holder(struct dax_device *dax_dev);
> void put_dax(struct dax_device *dax_dev);
> void kill_dax(struct dax_device *dax_dev);
> void dax_write_cache(struct dax_device *dax_dev, bool wc);
> @@ -71,6 +87,17 @@ static inline bool daxdev_mapping_supported(struct vm_area_struct *vma,
> return dax_synchronous(dax_dev);
> }
> #else
> +static inline void dax_register_holder(struct dax_device *dax_dev, void *holder,
> + const struct dax_holder_operations *ops)
> +{
> +}
> +static inline void dax_unregister_holder(struct dax_device *dax_dev)
> +{
> +}
> +static inline void *dax_get_holder(struct dax_device *dax_dev)
> +{
> + return NULL;
> +}
> static inline struct dax_device *alloc_dax(void *private,
> const struct dax_operations *ops, unsigned long flags)
> {
> @@ -209,6 +236,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, u64 off, u64 len,
> + int mf_flags);
> 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.34.1
>
>
>
On Wed, Jan 5, 2022 at 10:12 AM Darrick J. Wong <[email protected]> wrote:
>
> On Sun, Dec 26, 2021 at 10:34:31PM +0800, Shiyang Ruan 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
> > instance 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 | 62 +++++++++++++++++++++++++++++++++++++++++++++
> > include/linux/dax.h | 29 +++++++++++++++++++++
> > 2 files changed, 91 insertions(+)
> >
> > diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> > index c46f56e33d40..94c51f2ee133 100644
> > --- a/drivers/dax/super.c
> > +++ b/drivers/dax/super.c
> > @@ -20,15 +20,20 @@
> > * @inode: core vfs
> > * @cdev: optional character interface for "device dax"
> > * @private: dax driver private data
> > + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> > * @flags: state and boolean properties
> > + * @ops: operations for dax_device
> > + * @holder_ops: operations for the inner holder
> > */
> > struct dax_device {
> > struct inode inode;
> > struct cdev cdev;
> > void *private;
> > struct percpu_rw_semaphore rwsem;
> > + void *holder_data;
> > unsigned long flags;
> > const struct dax_operations *ops;
> > + const struct dax_holder_operations *holder_ops;
> > };
> >
> > static dev_t dax_devt;
> > @@ -192,6 +197,29 @@ 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, u64 off,
> > + u64 len, int mf_flags)
> > +{
> > + int rc;
> > +
> > + dax_read_lock(dax_dev);
> > + if (!dax_alive(dax_dev)) {
> > + rc = -ENXIO;
> > + goto out;
> > + }
> > +
> > + if (!dax_dev->holder_ops) {
> > + rc = -EOPNOTSUPP;
> > + goto out;
> > + }
> > +
> > + rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
> > +out:
> > + dax_read_unlock(dax_dev);
> > + 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)
> > @@ -254,6 +282,10 @@ void kill_dax(struct dax_device *dax_dev)
> > return;
> > dax_write_lock(dax_dev);
> > clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
> > +
> > + /* clear holder data */
> > + dax_dev->holder_ops = NULL;
> > + dax_dev->holder_data = NULL;
> > dax_write_unlock(dax_dev);
> > }
> > EXPORT_SYMBOL_GPL(kill_dax);
> > @@ -401,6 +433,36 @@ void put_dax(struct dax_device *dax_dev)
> > }
> > EXPORT_SYMBOL_GPL(put_dax);
> >
> > +void dax_register_holder(struct dax_device *dax_dev, void *holder,
> > + const struct dax_holder_operations *ops)
> > +{
> > + if (!dax_alive(dax_dev))
> > + return;
> > +
> > + dax_dev->holder_data = holder;
> > + dax_dev->holder_ops = ops;
>
> Shouldn't this return an error code if the dax device is dead or if
> someone already registered a holder? I'm pretty sure XFS should not
> bind to a dax device if someone else already registered for it...
Agree, yes.
>
> ...unless you want to use a notifier chain for failure events so that
> there can be multiple consumers of dax failure events?
No, I would hope not. It should be 1:1 holders to dax-devices. Similar
ownership semantics like bd_prepare_to_claim().
On Sun, Dec 26, 2021 at 10:34:38PM +0800, Shiyang Ruan wrote:
> Introduce xfs_notify_failure.c to handle failure related works, such as
> implement ->notify_failure(), register/unregister dax holder in xfs, and
> so on.
>
> 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]>
> ---
> fs/xfs/Makefile | 1 +
> fs/xfs/xfs_buf.c | 15 +++
> fs/xfs/xfs_fsops.c | 3 +
> fs/xfs/xfs_mount.h | 1 +
> fs/xfs/xfs_notify_failure.c | 189 ++++++++++++++++++++++++++++++++++++
> fs/xfs/xfs_notify_failure.h | 10 ++
> 6 files changed, 219 insertions(+)
> create mode 100644 fs/xfs/xfs_notify_failure.c
> create mode 100644 fs/xfs/xfs_notify_failure.h
>
> diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
> index 04611a1068b4..389970b3e13b 100644
> --- a/fs/xfs/Makefile
> +++ b/fs/xfs/Makefile
> @@ -84,6 +84,7 @@ xfs-y += xfs_aops.o \
> xfs_message.o \
> xfs_mount.o \
> xfs_mru_cache.o \
> + xfs_notify_failure.o \
> xfs_pwork.o \
> xfs_reflink.o \
> xfs_stats.o \
> diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
> index bbb0fbd34e64..d0df7604fa9e 100644
> --- a/fs/xfs/xfs_buf.c
> +++ b/fs/xfs/xfs_buf.c
> @@ -19,6 +19,7 @@
> #include "xfs_errortag.h"
> #include "xfs_error.h"
> #include "xfs_ag.h"
> +#include "xfs_notify_failure.h"
>
> static struct kmem_cache *xfs_buf_cache;
>
> @@ -1892,6 +1893,8 @@ xfs_free_buftarg(
> list_lru_destroy(&btp->bt_lru);
>
> blkdev_issue_flush(btp->bt_bdev);
> + if (btp->bt_daxdev)
> + dax_unregister_holder(btp->bt_daxdev);
> fs_put_dax(btp->bt_daxdev);
>
> kmem_free(btp);
> @@ -1946,6 +1949,18 @@ xfs_alloc_buftarg(
> btp->bt_dev = bdev->bd_dev;
> btp->bt_bdev = bdev;
> btp->bt_daxdev = fs_dax_get_by_bdev(bdev, &btp->bt_dax_part_off);
> + if (btp->bt_daxdev) {
> + dax_write_lock(btp->bt_daxdev);
> + if (dax_get_holder(btp->bt_daxdev)) {
> + dax_write_unlock(btp->bt_daxdev);
> + xfs_err(mp, "DAX device already in use?!");
> + goto error_free;
> + }
> +
> + dax_register_holder(btp->bt_daxdev, mp,
> + &xfs_dax_holder_operations);
> + dax_write_unlock(btp->bt_daxdev);
> + }
>
> /*
> * Buffer IO error rate limiting. Limit it to no more than 10 messages
> diff --git a/fs/xfs/xfs_fsops.c b/fs/xfs/xfs_fsops.c
> index 33e26690a8c4..d4d36c5bef11 100644
> --- a/fs/xfs/xfs_fsops.c
> +++ b/fs/xfs/xfs_fsops.c
> @@ -542,6 +542,9 @@ xfs_do_force_shutdown(
> } else if (flags & SHUTDOWN_CORRUPT_INCORE) {
> tag = XFS_PTAG_SHUTDOWN_CORRUPT;
> why = "Corruption of in-memory data";
> + } else if (flags & SHUTDOWN_CORRUPT_ONDISK) {
> + tag = XFS_PTAG_SHUTDOWN_CORRUPT;
> + why = "Corruption of on-disk metadata";
> } else {
> tag = XFS_PTAG_SHUTDOWN_IOERROR;
> why = "Metadata I/O Error";
> diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
> index 00720a02e761..47ff4ac53c4c 100644
> --- a/fs/xfs/xfs_mount.h
> +++ b/fs/xfs/xfs_mount.h
> @@ -435,6 +435,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_ONDISK 0x0010 /* corrupt metadata on device */
>
> #define XFS_SHUTDOWN_STRINGS \
> { SHUTDOWN_META_IO_ERROR, "metadata_io" }, \
> diff --git a/fs/xfs/xfs_notify_failure.c b/fs/xfs/xfs_notify_failure.c
> new file mode 100644
> index 000000000000..a87bd08365f4
> --- /dev/null
> +++ b/fs/xfs/xfs_notify_failure.c
> @@ -0,0 +1,189 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2021 Fujitsu. All Rights Reserved.
> + */
> +
> +#include "xfs.h"
> +#include "xfs_shared.h"
> +#include "xfs_format.h"
> +#include "xfs_log_format.h"
> +#include "xfs_trans_resv.h"
> +#include "xfs_mount.h"
> +#include "xfs_alloc.h"
> +#include "xfs_bit.h"
> +#include "xfs_btree.h"
> +#include "xfs_inode.h"
> +#include "xfs_icache.h"
> +#include "xfs_rmap.h"
> +#include "xfs_rmap_btree.h"
> +#include "xfs_rtalloc.h"
> +#include "xfs_trans.h"
> +
> +#include <linux/mm.h>
> +#include <linux/dax.h>
> +
> +struct failure_info {
> + xfs_agblock_t startblock;
> + xfs_filblks_t blockcount;
> + int mf_flags;
Why is blockcount a 64-bit quantity, when the failure information is
dealt with on a per-AG basis? I think "xfs_extlen_t blockcount" should
be large enough here. (I'll get back to this further down.)
> +};
> +
> +static pgoff_t
> +xfs_failure_pgoff(
> + struct xfs_mount *mp,
> + const struct xfs_rmap_irec *rec,
> + const struct failure_info *notify)
> +{
> + uint64_t pos = rec->rm_offset;
Nit: indenting ^^^^^ here.
> +
> + if (notify->startblock > rec->rm_startblock)
> + pos += XFS_FSB_TO_B(mp,
> + notify->startblock - rec->rm_startblock);
> + return pos >> PAGE_SHIFT;
> +}
> +
> +static unsigned long
> +xfs_failure_pgcnt(
> + struct xfs_mount *mp,
> + const struct xfs_rmap_irec *rec,
> + const struct failure_info *notify)
> +{
> + xfs_agblock_t start_rec = rec->rm_startblock;
> + xfs_agblock_t end_rec = rec->rm_startblock + rec->rm_blockcount;
> + xfs_agblock_t start_notify = notify->startblock;
> + xfs_agblock_t end_notify = notify->startblock + notify->blockcount;
> + xfs_agblock_t start_cross = max(start_rec, start_notify);
> + xfs_agblock_t end_cross = min(end_rec, end_notify);
Indenting and rather more local variables than we need?
static unsigned long
xfs_failure_pgcnt(
struct xfs_mount *mp,
const struct xfs_rmap_irec *rec,
const struct failure_info *notify)
{
xfs_agblock_t end_rec;
xfs_agblock_t end_notify;
xfs_agblock_t start_cross;
xfs_agblock_t end_cross;
start_cross = max(rec->rm_startblock, notify->startblock);
end_rec = rec->rm_startblock + rec->rm_blockcount;
end_notify = notify->startblock + notify->blockcount;
end_cross = min(end_rec, end_notify);
return XFS_FSB_TO_B(mp, end_cross - start_cross) >> PAGE_SHIFT;
}
> +
> + return XFS_FSB_TO_B(mp, end_cross - start_cross) >> PAGE_SHIFT;
> +}
> +
> +static int
> +xfs_dax_failure_fn(
> + struct xfs_btree_cur *cur,
> + const struct xfs_rmap_irec *rec,
> + void *data)
> +{
> + struct xfs_mount *mp = cur->bc_mp;
> + struct xfs_inode *ip;
> + struct address_space *mapping;
> + struct failure_info *notify = data;
> + int error = 0;
> +
> + 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(mp, SHUTDOWN_CORRUPT_ONDISK);
> + return -EFSCORRUPTED;
> + }
> +
> + /* Get files that incore, filter out others that are not in use. */
> + error = xfs_iget(mp, cur->bc_tp, rec->rm_owner, XFS_IGET_INCORE,
> + 0, &ip);
> + /* Continue the rmap query if the inode isn't incore */
> + if (error == -ENODATA)
> + return 0;
> + if (error)
> + return error;
> +
> + mapping = VFS_I(ip)->i_mapping;
> + if (IS_ENABLED(CONFIG_MEMORY_FAILURE)) {
Is there a situation where we can receive media failure notices from DAX
but CONFIG_MEMORY_FAILURE is not enabled? (I think the answer is yes?)
> + pgoff_t off = xfs_failure_pgoff(mp, rec, notify);
> + unsigned long cnt = xfs_failure_pgcnt(mp, rec, notify);
> +
> + error = mf_dax_kill_procs(mapping, off, cnt, notify->mf_flags);
> + }
If so, then we ought to do /something/ besides silently dropping the
error, right? Even if that something is rudely shutting down the fs,
like we do for attr/bmbt mappings above?
What I'm getting at is that I think this function should be:
#if IS_ENABLED(CONFIG_MEMORY_FAILURE)
static int
xfs_dax_failure_fn(
struct xfs_btree_cur *cur,
const struct xfs_rmap_irec *rec,
void *data)
{
/* shut down if attr/bmbt record like above */
error = xfs_iget(...);
if (error == -ENODATA)
return 0;
if (error)
return error;
off = xfs_failure_pgoff(mp, rec, notify);
cnt = xfs_failure_pgcnt(mp, rec, notify);
error = mf_dax_kill_procs(mapping, off, cnt, notify->mf_flags);
xfs_irele(ip);
return error;
}
#else
static int
xfs_dax_failure_fn(
struct xfs_btree_cur *cur,
const struct xfs_rmap_irec *rec,
void *data)
{
/* No other option besides shutting down the fs. */
xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_ONDISK);
return -EFSCORRUPTED;
}
#endif /* CONFIG_MEMORY_FAILURE */
> + /* TODO try to fix data */
> + xfs_irele(ip);
> +
> + return error;
> +}
> +
> +static int
> +xfs_dax_notify_ddev_failure(
> + struct xfs_mount *mp,
> + xfs_daddr_t daddr,
> + xfs_daddr_t bblen,
> + int mf_flags)
> +{
> + struct xfs_trans *tp = NULL;
> + struct xfs_btree_cur *cur = NULL;
> + struct xfs_buf *agf_bp = NULL;
> + struct failure_info notify = { .mf_flags = mf_flags };
> + int error = 0;
> + xfs_fsblock_t fsbno = XFS_DADDR_TO_FSB(mp, daddr);
> + xfs_agnumber_t agno = XFS_FSB_TO_AGNO(mp, fsbno);
> + xfs_fsblock_t end_fsbno = XFS_DADDR_TO_FSB(mp, daddr + bblen);
> + xfs_agnumber_t end_agno = XFS_FSB_TO_AGNO(mp, end_fsbno);
> +
> + error = xfs_trans_alloc_empty(mp, &tp);
> + if (error)
> + return error;
> +
> + for (; agno <= end_agno; agno++) {
> + struct xfs_rmap_irec ri_low = { };
> + struct xfs_rmap_irec ri_high;
> +
> + notify.startblock = XFS_FSB_TO_AGBNO(mp, fsbno);
> + notify.blockcount = XFS_BB_TO_FSB(mp, bblen);
This isn't correct. This sets notify.blockcount to the fsbcount of the
entire failed area, but it sets notify.startblock either to the start
of the failed area OR the start of some AG within the failed area.
If the failed area was blocks 80-119 and each AG is 100 blocks, then
this means we'll probe AG 0 (blocks 0-99) with notify spanning 80-119.
Those last 20 blocks are outside AG 0, but the rmap query range won't
return anything outside that range, so it doesn't really matter.
Next time through the loop, though, we're dealing with AG 1 (blocks
100-199). Now notify spans blocks 100-139, because bblen hasn't been
updated! If there's a file with an extent that maps blocks 115-125 and
a process that has only block 124 mmap'd, we'll kill that process
incorrectly because of the accounting error.
> +
> + error = xfs_alloc_read_agf(mp, tp, agno, 0, &agf_bp);
> + if (error)
> + break;
> +
> + cur = xfs_rmapbt_init_cursor(mp, tp, agf_bp, agf_bp->b_pag);
> +
> + memset(&ri_high, 0xFF, sizeof(ri_high));
> + ri_low.rm_startblock = XFS_FSB_TO_AGBNO(mp, fsbno);
> + if (agno == end_agno)
> + ri_high.rm_startblock = XFS_FSB_TO_AGBNO(mp, end_fsbno);
I think what you really want is to set notify.blockcount to
min(agf_length, ri_high.rm_startblock). That also means that
notify.blockcount can be xfs_extlen_t, which is the norm for per-AG
extent operations.
> +
> + error = xfs_rmap_query_range(cur, &ri_low, &ri_high,
> + xfs_dax_failure_fn, ¬ify);
> + xfs_btree_del_cursor(cur, error);
> + xfs_trans_brelse(tp, agf_bp);
> + if (error)
> + break;
> +
> + fsbno = XFS_AGB_TO_FSB(mp, agno + 1, 0);
> + }
> +
> + xfs_trans_cancel(tp);
> + return error;
> +}
> +
> +static int
> +xfs_dax_notify_failure(
> + struct dax_device *dax_dev,
> + u64 offset,
> + u64 len,
> + int mf_flags)
> +{
> + struct xfs_mount *mp = dax_get_holder(dax_dev);
> +
> + if (mp->m_rtdev_targp && mp->m_rtdev_targp->bt_daxdev == dax_dev) {
> + xfs_warn(mp,
> + "notify_failure() not supported on realtime device!");
> + return -EOPNOTSUPP;
> + }
> +
> + if (mp->m_logdev_targp && mp->m_logdev_targp->bt_daxdev == dax_dev &&
> + mp->m_logdev_targp != mp->m_ddev_targp) {
Technically speaking, if offset/len are beyond mp->m_sb.sb_logblocks
then we can return 0 since the log isn't using the failed part of the
external log.
Buuuut there are a lot of subtleties to the log, so maybe we (that is,
one of the more experienced xfs people) should implement a generic
handler for the log that will DTRT.
> + xfs_err(mp, "ondisk log corrupt, shutting down fs!");
> + xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_ONDISK);
> + return -EFSCORRUPTED;
> + }
> +
> + if (!xfs_has_rmapbt(mp)) {
> + xfs_warn(mp, "notify_failure() needs rmapbt enabled!");
> + return -EOPNOTSUPP;
> + }
> +
> + offset -= mp->m_ddev_targp->bt_dax_part_off;
Don't we need to check offset/len to make sure they're still within the
boundaries of the data device?
--D
> + return xfs_dax_notify_ddev_failure(mp, BTOBB(offset), BTOBB(len),
> + mf_flags);
> +}
> +
> +const struct dax_holder_operations xfs_dax_holder_operations = {
> + .notify_failure = xfs_dax_notify_failure,
> +};
> diff --git a/fs/xfs/xfs_notify_failure.h b/fs/xfs/xfs_notify_failure.h
> new file mode 100644
> index 000000000000..f40cb315e7ce
> --- /dev/null
> +++ b/fs/xfs/xfs_notify_failure.h
> @@ -0,0 +1,10 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2021 Fujitsu. All Rights Reserved.
> + */
> +#ifndef __XFS_NOTIFY_FAILURE_H__
> +#define __XFS_NOTIFY_FAILURE_H__
> +
> +extern const struct dax_holder_operations xfs_dax_holder_operations;
> +
> +#endif /* __XFS_NOTIFY_FAILURE_H__ */
> --
> 2.34.1
>
>
>
On Wed, Jan 05, 2022 at 10:23:08AM -0800, Dan Williams wrote:
> On Wed, Jan 5, 2022 at 10:12 AM Darrick J. Wong <[email protected]> wrote:
> >
> > On Sun, Dec 26, 2021 at 10:34:31PM +0800, Shiyang Ruan 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
> > > instance 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 | 62 +++++++++++++++++++++++++++++++++++++++++++++
> > > include/linux/dax.h | 29 +++++++++++++++++++++
> > > 2 files changed, 91 insertions(+)
> > >
> > > diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> > > index c46f56e33d40..94c51f2ee133 100644
> > > --- a/drivers/dax/super.c
> > > +++ b/drivers/dax/super.c
> > > @@ -20,15 +20,20 @@
> > > * @inode: core vfs
> > > * @cdev: optional character interface for "device dax"
> > > * @private: dax driver private data
> > > + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> > > * @flags: state and boolean properties
> > > + * @ops: operations for dax_device
> > > + * @holder_ops: operations for the inner holder
> > > */
> > > struct dax_device {
> > > struct inode inode;
> > > struct cdev cdev;
> > > void *private;
> > > struct percpu_rw_semaphore rwsem;
> > > + void *holder_data;
> > > unsigned long flags;
> > > const struct dax_operations *ops;
> > > + const struct dax_holder_operations *holder_ops;
> > > };
> > >
> > > static dev_t dax_devt;
> > > @@ -192,6 +197,29 @@ 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, u64 off,
> > > + u64 len, int mf_flags)
> > > +{
> > > + int rc;
> > > +
> > > + dax_read_lock(dax_dev);
> > > + if (!dax_alive(dax_dev)) {
> > > + rc = -ENXIO;
> > > + goto out;
> > > + }
> > > +
> > > + if (!dax_dev->holder_ops) {
> > > + rc = -EOPNOTSUPP;
> > > + goto out;
> > > + }
> > > +
> > > + rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
> > > +out:
> > > + dax_read_unlock(dax_dev);
> > > + 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)
> > > @@ -254,6 +282,10 @@ void kill_dax(struct dax_device *dax_dev)
> > > return;
> > > dax_write_lock(dax_dev);
> > > clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
> > > +
> > > + /* clear holder data */
> > > + dax_dev->holder_ops = NULL;
> > > + dax_dev->holder_data = NULL;
> > > dax_write_unlock(dax_dev);
> > > }
> > > EXPORT_SYMBOL_GPL(kill_dax);
> > > @@ -401,6 +433,36 @@ void put_dax(struct dax_device *dax_dev)
> > > }
> > > EXPORT_SYMBOL_GPL(put_dax);
> > >
> > > +void dax_register_holder(struct dax_device *dax_dev, void *holder,
> > > + const struct dax_holder_operations *ops)
> > > +{
> > > + if (!dax_alive(dax_dev))
> > > + return;
> > > +
> > > + dax_dev->holder_data = holder;
> > > + dax_dev->holder_ops = ops;
> >
> > Shouldn't this return an error code if the dax device is dead or if
> > someone already registered a holder? I'm pretty sure XFS should not
> > bind to a dax device if someone else already registered for it...
>
> Agree, yes.
>
> >
> > ...unless you want to use a notifier chain for failure events so that
> > there can be multiple consumers of dax failure events?
>
> No, I would hope not. It should be 1:1 holders to dax-devices. Similar
> ownership semantics like bd_prepare_to_claim().
Does each partition on a pmem device still have its own dax_device?
--D
On Sun, Dec 26, 2021 at 10:34:33PM +0800, 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.
>
> 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]>
> Reviewed-by: Christoph Hellwig <[email protected]>
> ---
> drivers/nvdimm/pmem.c | 16 ++++++++++++++++
> include/linux/memremap.h | 9 +++++++++
> mm/memory-failure.c | 14 ++++++++++++++
> 3 files changed, 39 insertions(+)
>
> diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c
> index 4190c8c46ca8..2114554358eb 100644
> --- a/drivers/nvdimm/pmem.c
> +++ b/drivers/nvdimm/pmem.c
> @@ -386,6 +386,20 @@ static void pmem_release_disk(void *__pmem)
> blk_cleanup_disk(pmem->disk);
> }
>
> +static int pmem_pagemap_memory_failure(struct dev_pagemap *pgmap,
> + unsigned long pfn, u64 len, int mf_flags)
> +{
> + struct pmem_device *pmem =
> + container_of(pgmap, struct pmem_device, pgmap);
> + loff_t offset = PFN_PHYS(pfn) - pmem->phys_addr - pmem->data_offset;
Use u64 here ^^^ because this isn't a file offset, this is a physical
offset. Also, loff_t is signed, which you probably don't want.
> +
> + return dax_holder_notify_failure(pmem->dax_dev, offset, len, mf_flags);
> +}
> +
> +static const struct dev_pagemap_ops fsdax_pagemap_ops = {
> + .memory_failure = pmem_pagemap_memory_failure,
> +};
> +
> static int pmem_attach_disk(struct device *dev,
> struct nd_namespace_common *ndns)
> {
> @@ -448,6 +462,7 @@ static int pmem_attach_disk(struct device *dev,
> pmem->pfn_flags = PFN_DEV;
> if (is_nd_pfn(dev)) {
> pmem->pgmap.type = MEMORY_DEVICE_FS_DAX;
> + pmem->pgmap.ops = &fsdax_pagemap_ops;
> addr = devm_memremap_pages(dev, &pmem->pgmap);
> pfn_sb = nd_pfn->pfn_sb;
> pmem->data_offset = le64_to_cpu(pfn_sb->dataoff);
> @@ -461,6 +476,7 @@ static int pmem_attach_disk(struct device *dev,
> pmem->pgmap.range.end = res->end;
> pmem->pgmap.nr_range = 1;
> pmem->pgmap.type = MEMORY_DEVICE_FS_DAX;
> + pmem->pgmap.ops = &fsdax_pagemap_ops;
> addr = devm_memremap_pages(dev, &pmem->pgmap);
> pmem->pfn_flags |= PFN_MAP;
> bb_range = pmem->pgmap.range;
> diff --git a/include/linux/memremap.h b/include/linux/memremap.h
> index c0e9d35889e8..820c2f33b163 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 mf_flags is finally passed to the recover
> + * function through the whole notify routine.
Might want to state here that the generic implementation will be used if
->memory_failure is NULL or calling the function returns -EOPNOTSUPP.
--D
> + */
> + int (*memory_failure)(struct dev_pagemap *pgmap, unsigned long pfn,
> + u64 len, int mf_flags);
> };
>
> #define PGMAP_ALTMAP_VALID (1 << 0)
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index 1ee7d626fed7..3cc612b29f89 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -1625,6 +1625,20 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,
> if (!pgmap_pfn_valid(pgmap, pfn))
> goto out;
>
> + /*
> + * 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, PAGE_SIZE, flags);
> + /*
> + * Fall back to generic handler too if operation is not
> + * supported inside the driver/device/filesystem.
> + */
> + if (rc != -EOPNOTSUPP)
> + goto out;
> + }
> +
> rc = mf_generic_kill_procs(pfn, flags, pgmap);
> out:
> /* drop pgmap ref acquired in caller */
> --
> 2.34.1
>
>
>
On Wed, Jan 5, 2022 at 10:56 AM Darrick J. Wong <[email protected]> wrote:
>
> On Wed, Jan 05, 2022 at 10:23:08AM -0800, Dan Williams wrote:
> > On Wed, Jan 5, 2022 at 10:12 AM Darrick J. Wong <[email protected]> wrote:
> > >
> > > On Sun, Dec 26, 2021 at 10:34:31PM +0800, Shiyang Ruan 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
> > > > instance 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 | 62 +++++++++++++++++++++++++++++++++++++++++++++
> > > > include/linux/dax.h | 29 +++++++++++++++++++++
> > > > 2 files changed, 91 insertions(+)
> > > >
> > > > diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> > > > index c46f56e33d40..94c51f2ee133 100644
> > > > --- a/drivers/dax/super.c
> > > > +++ b/drivers/dax/super.c
> > > > @@ -20,15 +20,20 @@
> > > > * @inode: core vfs
> > > > * @cdev: optional character interface for "device dax"
> > > > * @private: dax driver private data
> > > > + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> > > > * @flags: state and boolean properties
> > > > + * @ops: operations for dax_device
> > > > + * @holder_ops: operations for the inner holder
> > > > */
> > > > struct dax_device {
> > > > struct inode inode;
> > > > struct cdev cdev;
> > > > void *private;
> > > > struct percpu_rw_semaphore rwsem;
> > > > + void *holder_data;
> > > > unsigned long flags;
> > > > const struct dax_operations *ops;
> > > > + const struct dax_holder_operations *holder_ops;
> > > > };
> > > >
> > > > static dev_t dax_devt;
> > > > @@ -192,6 +197,29 @@ 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, u64 off,
> > > > + u64 len, int mf_flags)
> > > > +{
> > > > + int rc;
> > > > +
> > > > + dax_read_lock(dax_dev);
> > > > + if (!dax_alive(dax_dev)) {
> > > > + rc = -ENXIO;
> > > > + goto out;
> > > > + }
> > > > +
> > > > + if (!dax_dev->holder_ops) {
> > > > + rc = -EOPNOTSUPP;
> > > > + goto out;
> > > > + }
> > > > +
> > > > + rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
> > > > +out:
> > > > + dax_read_unlock(dax_dev);
> > > > + 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)
> > > > @@ -254,6 +282,10 @@ void kill_dax(struct dax_device *dax_dev)
> > > > return;
> > > > dax_write_lock(dax_dev);
> > > > clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
> > > > +
> > > > + /* clear holder data */
> > > > + dax_dev->holder_ops = NULL;
> > > > + dax_dev->holder_data = NULL;
> > > > dax_write_unlock(dax_dev);
> > > > }
> > > > EXPORT_SYMBOL_GPL(kill_dax);
> > > > @@ -401,6 +433,36 @@ void put_dax(struct dax_device *dax_dev)
> > > > }
> > > > EXPORT_SYMBOL_GPL(put_dax);
> > > >
> > > > +void dax_register_holder(struct dax_device *dax_dev, void *holder,
> > > > + const struct dax_holder_operations *ops)
> > > > +{
> > > > + if (!dax_alive(dax_dev))
> > > > + return;
> > > > +
> > > > + dax_dev->holder_data = holder;
> > > > + dax_dev->holder_ops = ops;
> > >
> > > Shouldn't this return an error code if the dax device is dead or if
> > > someone already registered a holder? I'm pretty sure XFS should not
> > > bind to a dax device if someone else already registered for it...
> >
> > Agree, yes.
> >
> > >
> > > ...unless you want to use a notifier chain for failure events so that
> > > there can be multiple consumers of dax failure events?
> >
> > No, I would hope not. It should be 1:1 holders to dax-devices. Similar
> > ownership semantics like bd_prepare_to_claim().
>
> Does each partition on a pmem device still have its own dax_device?
No, it never did...
Just as before, each dax-device is still associated with a gendisk /
whole-block_device. The recent change is that instead of needing that
partition-block_device plumbed to convert a relative block number to
its absolute whole-block_device offset the filesystem now handles that
at iomap_begin() time. See:
if (mapping_flags & IOMAP_DAX)
iomap->addr += target->bt_dax_part_off;
...in xfs_bmbt_to_iomap() (in -next). I.e. bdev_dax_pgoff() is gone
with the lead-in reworks.
On Wed, Jan 5, 2022 at 10:53 AM Darrick J. Wong <[email protected]> wrote:
>
> On Sun, Dec 26, 2021 at 10:34:38PM +0800, Shiyang Ruan wrote:
> > Introduce xfs_notify_failure.c to handle failure related works, such as
> > implement ->notify_failure(), register/unregister dax holder in xfs, and
> > so on.
> >
> > 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]>
> > ---
> > fs/xfs/Makefile | 1 +
> > fs/xfs/xfs_buf.c | 15 +++
> > fs/xfs/xfs_fsops.c | 3 +
> > fs/xfs/xfs_mount.h | 1 +
> > fs/xfs/xfs_notify_failure.c | 189 ++++++++++++++++++++++++++++++++++++
> > fs/xfs/xfs_notify_failure.h | 10 ++
> > 6 files changed, 219 insertions(+)
> > create mode 100644 fs/xfs/xfs_notify_failure.c
> > create mode 100644 fs/xfs/xfs_notify_failure.h
> >
> > diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
> > index 04611a1068b4..389970b3e13b 100644
> > --- a/fs/xfs/Makefile
> > +++ b/fs/xfs/Makefile
> > @@ -84,6 +84,7 @@ xfs-y += xfs_aops.o \
> > xfs_message.o \
> > xfs_mount.o \
> > xfs_mru_cache.o \
> > + xfs_notify_failure.o \
> > xfs_pwork.o \
> > xfs_reflink.o \
> > xfs_stats.o \
> > diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
> > index bbb0fbd34e64..d0df7604fa9e 100644
> > --- a/fs/xfs/xfs_buf.c
> > +++ b/fs/xfs/xfs_buf.c
> > @@ -19,6 +19,7 @@
> > #include "xfs_errortag.h"
> > #include "xfs_error.h"
> > #include "xfs_ag.h"
> > +#include "xfs_notify_failure.h"
> >
> > static struct kmem_cache *xfs_buf_cache;
> >
> > @@ -1892,6 +1893,8 @@ xfs_free_buftarg(
> > list_lru_destroy(&btp->bt_lru);
> >
> > blkdev_issue_flush(btp->bt_bdev);
> > + if (btp->bt_daxdev)
> > + dax_unregister_holder(btp->bt_daxdev);
> > fs_put_dax(btp->bt_daxdev);
> >
> > kmem_free(btp);
> > @@ -1946,6 +1949,18 @@ xfs_alloc_buftarg(
> > btp->bt_dev = bdev->bd_dev;
> > btp->bt_bdev = bdev;
> > btp->bt_daxdev = fs_dax_get_by_bdev(bdev, &btp->bt_dax_part_off);
> > + if (btp->bt_daxdev) {
> > + dax_write_lock(btp->bt_daxdev);
> > + if (dax_get_holder(btp->bt_daxdev)) {
> > + dax_write_unlock(btp->bt_daxdev);
> > + xfs_err(mp, "DAX device already in use?!");
> > + goto error_free;
> > + }
> > +
> > + dax_register_holder(btp->bt_daxdev, mp,
> > + &xfs_dax_holder_operations);
> > + dax_write_unlock(btp->bt_daxdev);
> > + }
> >
> > /*
> > * Buffer IO error rate limiting. Limit it to no more than 10 messages
> > diff --git a/fs/xfs/xfs_fsops.c b/fs/xfs/xfs_fsops.c
> > index 33e26690a8c4..d4d36c5bef11 100644
> > --- a/fs/xfs/xfs_fsops.c
> > +++ b/fs/xfs/xfs_fsops.c
> > @@ -542,6 +542,9 @@ xfs_do_force_shutdown(
> > } else if (flags & SHUTDOWN_CORRUPT_INCORE) {
> > tag = XFS_PTAG_SHUTDOWN_CORRUPT;
> > why = "Corruption of in-memory data";
> > + } else if (flags & SHUTDOWN_CORRUPT_ONDISK) {
> > + tag = XFS_PTAG_SHUTDOWN_CORRUPT;
> > + why = "Corruption of on-disk metadata";
> > } else {
> > tag = XFS_PTAG_SHUTDOWN_IOERROR;
> > why = "Metadata I/O Error";
> > diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
> > index 00720a02e761..47ff4ac53c4c 100644
> > --- a/fs/xfs/xfs_mount.h
> > +++ b/fs/xfs/xfs_mount.h
> > @@ -435,6 +435,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_ONDISK 0x0010 /* corrupt metadata on device */
> >
> > #define XFS_SHUTDOWN_STRINGS \
> > { SHUTDOWN_META_IO_ERROR, "metadata_io" }, \
> > diff --git a/fs/xfs/xfs_notify_failure.c b/fs/xfs/xfs_notify_failure.c
> > new file mode 100644
> > index 000000000000..a87bd08365f4
> > --- /dev/null
> > +++ b/fs/xfs/xfs_notify_failure.c
> > @@ -0,0 +1,189 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2021 Fujitsu. All Rights Reserved.
> > + */
> > +
> > +#include "xfs.h"
> > +#include "xfs_shared.h"
> > +#include "xfs_format.h"
> > +#include "xfs_log_format.h"
> > +#include "xfs_trans_resv.h"
> > +#include "xfs_mount.h"
> > +#include "xfs_alloc.h"
> > +#include "xfs_bit.h"
> > +#include "xfs_btree.h"
> > +#include "xfs_inode.h"
> > +#include "xfs_icache.h"
> > +#include "xfs_rmap.h"
> > +#include "xfs_rmap_btree.h"
> > +#include "xfs_rtalloc.h"
> > +#include "xfs_trans.h"
> > +
> > +#include <linux/mm.h>
> > +#include <linux/dax.h>
> > +
> > +struct failure_info {
> > + xfs_agblock_t startblock;
> > + xfs_filblks_t blockcount;
> > + int mf_flags;
>
> Why is blockcount a 64-bit quantity, when the failure information is
> dealt with on a per-AG basis? I think "xfs_extlen_t blockcount" should
> be large enough here. (I'll get back to this further down.)
>
> > +};
> > +
> > +static pgoff_t
> > +xfs_failure_pgoff(
> > + struct xfs_mount *mp,
> > + const struct xfs_rmap_irec *rec,
> > + const struct failure_info *notify)
> > +{
> > + uint64_t pos = rec->rm_offset;
>
> Nit: indenting ^^^^^ here.
>
> > +
> > + if (notify->startblock > rec->rm_startblock)
> > + pos += XFS_FSB_TO_B(mp,
> > + notify->startblock - rec->rm_startblock);
> > + return pos >> PAGE_SHIFT;
> > +}
> > +
> > +static unsigned long
> > +xfs_failure_pgcnt(
> > + struct xfs_mount *mp,
> > + const struct xfs_rmap_irec *rec,
> > + const struct failure_info *notify)
> > +{
> > + xfs_agblock_t start_rec = rec->rm_startblock;
> > + xfs_agblock_t end_rec = rec->rm_startblock + rec->rm_blockcount;
> > + xfs_agblock_t start_notify = notify->startblock;
> > + xfs_agblock_t end_notify = notify->startblock + notify->blockcount;
> > + xfs_agblock_t start_cross = max(start_rec, start_notify);
> > + xfs_agblock_t end_cross = min(end_rec, end_notify);
>
> Indenting and rather more local variables than we need?
>
> static unsigned long
> xfs_failure_pgcnt(
> struct xfs_mount *mp,
> const struct xfs_rmap_irec *rec,
> const struct failure_info *notify)
> {
> xfs_agblock_t end_rec;
> xfs_agblock_t end_notify;
> xfs_agblock_t start_cross;
> xfs_agblock_t end_cross;
>
> start_cross = max(rec->rm_startblock, notify->startblock);
>
> end_rec = rec->rm_startblock + rec->rm_blockcount;
> end_notify = notify->startblock + notify->blockcount;
> end_cross = min(end_rec, end_notify);
>
> return XFS_FSB_TO_B(mp, end_cross - start_cross) >> PAGE_SHIFT;
> }
>
> > +
> > + return XFS_FSB_TO_B(mp, end_cross - start_cross) >> PAGE_SHIFT;
> > +}
> > +
> > +static int
> > +xfs_dax_failure_fn(
> > + struct xfs_btree_cur *cur,
> > + const struct xfs_rmap_irec *rec,
> > + void *data)
> > +{
> > + struct xfs_mount *mp = cur->bc_mp;
> > + struct xfs_inode *ip;
> > + struct address_space *mapping;
> > + struct failure_info *notify = data;
> > + int error = 0;
> > +
> > + 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(mp, SHUTDOWN_CORRUPT_ONDISK);
> > + return -EFSCORRUPTED;
> > + }
> > +
> > + /* Get files that incore, filter out others that are not in use. */
> > + error = xfs_iget(mp, cur->bc_tp, rec->rm_owner, XFS_IGET_INCORE,
> > + 0, &ip);
> > + /* Continue the rmap query if the inode isn't incore */
> > + if (error == -ENODATA)
> > + return 0;
> > + if (error)
> > + return error;
> > +
> > + mapping = VFS_I(ip)->i_mapping;
> > + if (IS_ENABLED(CONFIG_MEMORY_FAILURE)) {
>
> Is there a situation where we can receive media failure notices from DAX
> but CONFIG_MEMORY_FAILURE is not enabled? (I think the answer is yes?)
Good catch, yes, I was planning to reuse this notification
infrastructure for the "whoops you ripped out your CXL card that was
being used with FSDAX" case. Although, if someone builds the kernel
with CONFIG_MEMORY_FAILURE=n then I think a lack of notification for
that case is to be expected? Perhaps CONFIG_FSDAX should just depend
on CONFIG_MEMORY_FAILURE when that "hot remove" failure case is added.
For now, CONFIG_MEMORY_FAILURE is the only source of errors.
>
> > + pgoff_t off = xfs_failure_pgoff(mp, rec, notify);
> > + unsigned long cnt = xfs_failure_pgcnt(mp, rec, notify);
> > +
> > + error = mf_dax_kill_procs(mapping, off, cnt, notify->mf_flags);
> > + }
>
> If so, then we ought to do /something/ besides silently dropping the
> error, right? Even if that something is rudely shutting down the fs,
> like we do for attr/bmbt mappings above?
>
> What I'm getting at is that I think this function should be:
>
> #if IS_ENABLED(CONFIG_MEMORY_FAILURE)
> static int
> xfs_dax_failure_fn(
> struct xfs_btree_cur *cur,
> const struct xfs_rmap_irec *rec,
> void *data)
> {
> /* shut down if attr/bmbt record like above */
>
> error = xfs_iget(...);
> if (error == -ENODATA)
> return 0;
> if (error)
> return error;
>
> off = xfs_failure_pgoff(mp, rec, notify);
> cnt = xfs_failure_pgcnt(mp, rec, notify);
>
> error = mf_dax_kill_procs(mapping, off, cnt, notify->mf_flags);
> xfs_irele(ip);
> return error;
> }
> #else
> static int
> xfs_dax_failure_fn(
> struct xfs_btree_cur *cur,
> const struct xfs_rmap_irec *rec,
> void *data)
> {
> /* No other option besides shutting down the fs. */
> xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_ONDISK);
> return -EFSCORRUPTED;
> }
> #endif /* CONFIG_MEMORY_FAILURE */
Oh, yeah that makes sense to me.
On Wed, Jan 05, 2022 at 11:20:12AM -0800, Dan Williams wrote:
> On Wed, Jan 5, 2022 at 10:56 AM Darrick J. Wong <[email protected]> wrote:
> >
> > On Wed, Jan 05, 2022 at 10:23:08AM -0800, Dan Williams wrote:
> > > On Wed, Jan 5, 2022 at 10:12 AM Darrick J. Wong <[email protected]> wrote:
> > > >
> > > > On Sun, Dec 26, 2021 at 10:34:31PM +0800, Shiyang Ruan 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
> > > > > instance 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 | 62 +++++++++++++++++++++++++++++++++++++++++++++
> > > > > include/linux/dax.h | 29 +++++++++++++++++++++
> > > > > 2 files changed, 91 insertions(+)
> > > > >
> > > > > diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> > > > > index c46f56e33d40..94c51f2ee133 100644
> > > > > --- a/drivers/dax/super.c
> > > > > +++ b/drivers/dax/super.c
> > > > > @@ -20,15 +20,20 @@
> > > > > * @inode: core vfs
> > > > > * @cdev: optional character interface for "device dax"
> > > > > * @private: dax driver private data
> > > > > + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> > > > > * @flags: state and boolean properties
> > > > > + * @ops: operations for dax_device
> > > > > + * @holder_ops: operations for the inner holder
> > > > > */
> > > > > struct dax_device {
> > > > > struct inode inode;
> > > > > struct cdev cdev;
> > > > > void *private;
> > > > > struct percpu_rw_semaphore rwsem;
> > > > > + void *holder_data;
> > > > > unsigned long flags;
> > > > > const struct dax_operations *ops;
> > > > > + const struct dax_holder_operations *holder_ops;
> > > > > };
> > > > >
> > > > > static dev_t dax_devt;
> > > > > @@ -192,6 +197,29 @@ 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, u64 off,
> > > > > + u64 len, int mf_flags)
> > > > > +{
> > > > > + int rc;
> > > > > +
> > > > > + dax_read_lock(dax_dev);
> > > > > + if (!dax_alive(dax_dev)) {
> > > > > + rc = -ENXIO;
> > > > > + goto out;
> > > > > + }
> > > > > +
> > > > > + if (!dax_dev->holder_ops) {
> > > > > + rc = -EOPNOTSUPP;
> > > > > + goto out;
> > > > > + }
> > > > > +
> > > > > + rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
> > > > > +out:
> > > > > + dax_read_unlock(dax_dev);
> > > > > + 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)
> > > > > @@ -254,6 +282,10 @@ void kill_dax(struct dax_device *dax_dev)
> > > > > return;
> > > > > dax_write_lock(dax_dev);
> > > > > clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
> > > > > +
> > > > > + /* clear holder data */
> > > > > + dax_dev->holder_ops = NULL;
> > > > > + dax_dev->holder_data = NULL;
> > > > > dax_write_unlock(dax_dev);
> > > > > }
> > > > > EXPORT_SYMBOL_GPL(kill_dax);
> > > > > @@ -401,6 +433,36 @@ void put_dax(struct dax_device *dax_dev)
> > > > > }
> > > > > EXPORT_SYMBOL_GPL(put_dax);
> > > > >
> > > > > +void dax_register_holder(struct dax_device *dax_dev, void *holder,
> > > > > + const struct dax_holder_operations *ops)
> > > > > +{
> > > > > + if (!dax_alive(dax_dev))
> > > > > + return;
> > > > > +
> > > > > + dax_dev->holder_data = holder;
> > > > > + dax_dev->holder_ops = ops;
> > > >
> > > > Shouldn't this return an error code if the dax device is dead or if
> > > > someone already registered a holder? I'm pretty sure XFS should not
> > > > bind to a dax device if someone else already registered for it...
> > >
> > > Agree, yes.
> > >
> > > >
> > > > ...unless you want to use a notifier chain for failure events so that
> > > > there can be multiple consumers of dax failure events?
> > >
> > > No, I would hope not. It should be 1:1 holders to dax-devices. Similar
> > > ownership semantics like bd_prepare_to_claim().
> >
> > Does each partition on a pmem device still have its own dax_device?
>
> No, it never did...
>
> Just as before, each dax-device is still associated with a gendisk /
> whole-block_device. The recent change is that instead of needing that
> partition-block_device plumbed to convert a relative block number to
> its absolute whole-block_device offset the filesystem now handles that
> at iomap_begin() time. See:
>
> if (mapping_flags & IOMAP_DAX)
> iomap->addr += target->bt_dax_part_off;
>
> ...in xfs_bmbt_to_iomap() (in -next). I.e. bdev_dax_pgoff() is gone
> with the lead-in reworks.
OH. Crap, Dan's right:
XFS (pmem0p1): ddev dax = 0xffff88800304ed00 bdev = 0xffff8880480d6180
XFS (pmem0p2): ddev dax = 0xffff88800304ed00 bdev = 0xffff8880480d4380
Unless you're planning to remove partition support too, this patch needs
to be reworked so that multiple filesystems in separate partitions can
each call dax_register_holder to receive memory_failure notifications
for their partition.
/methinks this sharing is all a little scary...
--D
On Wed, Jan 5, 2022 at 2:47 PM Darrick J. Wong <[email protected]> wrote:
>
> On Wed, Jan 05, 2022 at 11:20:12AM -0800, Dan Williams wrote:
> > On Wed, Jan 5, 2022 at 10:56 AM Darrick J. Wong <[email protected]> wrote:
> > >
> > > On Wed, Jan 05, 2022 at 10:23:08AM -0800, Dan Williams wrote:
> > > > On Wed, Jan 5, 2022 at 10:12 AM Darrick J. Wong <[email protected]> wrote:
> > > > >
> > > > > On Sun, Dec 26, 2021 at 10:34:31PM +0800, Shiyang Ruan 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
> > > > > > instance 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 | 62 +++++++++++++++++++++++++++++++++++++++++++++
> > > > > > include/linux/dax.h | 29 +++++++++++++++++++++
> > > > > > 2 files changed, 91 insertions(+)
> > > > > >
> > > > > > diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> > > > > > index c46f56e33d40..94c51f2ee133 100644
> > > > > > --- a/drivers/dax/super.c
> > > > > > +++ b/drivers/dax/super.c
> > > > > > @@ -20,15 +20,20 @@
> > > > > > * @inode: core vfs
> > > > > > * @cdev: optional character interface for "device dax"
> > > > > > * @private: dax driver private data
> > > > > > + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> > > > > > * @flags: state and boolean properties
> > > > > > + * @ops: operations for dax_device
> > > > > > + * @holder_ops: operations for the inner holder
> > > > > > */
> > > > > > struct dax_device {
> > > > > > struct inode inode;
> > > > > > struct cdev cdev;
> > > > > > void *private;
> > > > > > struct percpu_rw_semaphore rwsem;
> > > > > > + void *holder_data;
> > > > > > unsigned long flags;
> > > > > > const struct dax_operations *ops;
> > > > > > + const struct dax_holder_operations *holder_ops;
> > > > > > };
> > > > > >
> > > > > > static dev_t dax_devt;
> > > > > > @@ -192,6 +197,29 @@ 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, u64 off,
> > > > > > + u64 len, int mf_flags)
> > > > > > +{
> > > > > > + int rc;
> > > > > > +
> > > > > > + dax_read_lock(dax_dev);
> > > > > > + if (!dax_alive(dax_dev)) {
> > > > > > + rc = -ENXIO;
> > > > > > + goto out;
> > > > > > + }
> > > > > > +
> > > > > > + if (!dax_dev->holder_ops) {
> > > > > > + rc = -EOPNOTSUPP;
> > > > > > + goto out;
> > > > > > + }
> > > > > > +
> > > > > > + rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
> > > > > > +out:
> > > > > > + dax_read_unlock(dax_dev);
> > > > > > + 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)
> > > > > > @@ -254,6 +282,10 @@ void kill_dax(struct dax_device *dax_dev)
> > > > > > return;
> > > > > > dax_write_lock(dax_dev);
> > > > > > clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
> > > > > > +
> > > > > > + /* clear holder data */
> > > > > > + dax_dev->holder_ops = NULL;
> > > > > > + dax_dev->holder_data = NULL;
> > > > > > dax_write_unlock(dax_dev);
> > > > > > }
> > > > > > EXPORT_SYMBOL_GPL(kill_dax);
> > > > > > @@ -401,6 +433,36 @@ void put_dax(struct dax_device *dax_dev)
> > > > > > }
> > > > > > EXPORT_SYMBOL_GPL(put_dax);
> > > > > >
> > > > > > +void dax_register_holder(struct dax_device *dax_dev, void *holder,
> > > > > > + const struct dax_holder_operations *ops)
> > > > > > +{
> > > > > > + if (!dax_alive(dax_dev))
> > > > > > + return;
> > > > > > +
> > > > > > + dax_dev->holder_data = holder;
> > > > > > + dax_dev->holder_ops = ops;
> > > > >
> > > > > Shouldn't this return an error code if the dax device is dead or if
> > > > > someone already registered a holder? I'm pretty sure XFS should not
> > > > > bind to a dax device if someone else already registered for it...
> > > >
> > > > Agree, yes.
> > > >
> > > > >
> > > > > ...unless you want to use a notifier chain for failure events so that
> > > > > there can be multiple consumers of dax failure events?
> > > >
> > > > No, I would hope not. It should be 1:1 holders to dax-devices. Similar
> > > > ownership semantics like bd_prepare_to_claim().
> > >
> > > Does each partition on a pmem device still have its own dax_device?
> >
> > No, it never did...
> >
> > Just as before, each dax-device is still associated with a gendisk /
> > whole-block_device. The recent change is that instead of needing that
> > partition-block_device plumbed to convert a relative block number to
> > its absolute whole-block_device offset the filesystem now handles that
> > at iomap_begin() time. See:
> >
> > if (mapping_flags & IOMAP_DAX)
> > iomap->addr += target->bt_dax_part_off;
> >
> > ...in xfs_bmbt_to_iomap() (in -next). I.e. bdev_dax_pgoff() is gone
> > with the lead-in reworks.
>
> OH. Crap, Dan's right:
>
> XFS (pmem0p1): ddev dax = 0xffff88800304ed00 bdev = 0xffff8880480d6180
> XFS (pmem0p2): ddev dax = 0xffff88800304ed00 bdev = 0xffff8880480d4380
>
> Unless you're planning to remove partition support too, this patch needs
> to be reworked so that multiple filesystems in separate partitions can
> each call dax_register_holder to receive memory_failure notifications
> for their partition.
Oh, crap indeed. I think this gets back to the slow tip-toeing away
from dax + partition support. While FSDAX can continue to support
"legacy/experimental" operation on partitions of DAX capable block
devices, I think failure notification + reflink support is where we
draw the line and say "DAX on partitions was a mistake, it's too late
to undo that mistake, but going forward for new FSDAX features it
requires switching from block-device partitions to pmem-namespaces if
you need sub-division support and new DAX features."
> /methinks this sharing is all a little scary...
Yes, I think we should just fail the holder registration and
DAX+reflink unless the FS being mounted on a whole device. I know Ted
and others had reservations about moving filesystems to be mounted on
dax-devices directly, but hopefully the whole-block_device requirement
is a workable middle ground?
On Wed, Jan 05, 2022 at 03:01:22PM -0800, Dan Williams wrote:
> On Wed, Jan 5, 2022 at 2:47 PM Darrick J. Wong <[email protected]> wrote:
> >
> > On Wed, Jan 05, 2022 at 11:20:12AM -0800, Dan Williams wrote:
> > > On Wed, Jan 5, 2022 at 10:56 AM Darrick J. Wong <[email protected]> wrote:
> > > >
> > > > On Wed, Jan 05, 2022 at 10:23:08AM -0800, Dan Williams wrote:
> > > > > On Wed, Jan 5, 2022 at 10:12 AM Darrick J. Wong <[email protected]> wrote:
> > > > > >
> > > > > > On Sun, Dec 26, 2021 at 10:34:31PM +0800, Shiyang Ruan 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
> > > > > > > instance 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 | 62 +++++++++++++++++++++++++++++++++++++++++++++
> > > > > > > include/linux/dax.h | 29 +++++++++++++++++++++
> > > > > > > 2 files changed, 91 insertions(+)
> > > > > > >
> > > > > > > diff --git a/drivers/dax/super.c b/drivers/dax/super.c
> > > > > > > index c46f56e33d40..94c51f2ee133 100644
> > > > > > > --- a/drivers/dax/super.c
> > > > > > > +++ b/drivers/dax/super.c
> > > > > > > @@ -20,15 +20,20 @@
> > > > > > > * @inode: core vfs
> > > > > > > * @cdev: optional character interface for "device dax"
> > > > > > > * @private: dax driver private data
> > > > > > > + * @holder_data: holder of a dax_device: could be filesystem or mapped device
> > > > > > > * @flags: state and boolean properties
> > > > > > > + * @ops: operations for dax_device
> > > > > > > + * @holder_ops: operations for the inner holder
> > > > > > > */
> > > > > > > struct dax_device {
> > > > > > > struct inode inode;
> > > > > > > struct cdev cdev;
> > > > > > > void *private;
> > > > > > > struct percpu_rw_semaphore rwsem;
> > > > > > > + void *holder_data;
> > > > > > > unsigned long flags;
> > > > > > > const struct dax_operations *ops;
> > > > > > > + const struct dax_holder_operations *holder_ops;
> > > > > > > };
> > > > > > >
> > > > > > > static dev_t dax_devt;
> > > > > > > @@ -192,6 +197,29 @@ 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, u64 off,
> > > > > > > + u64 len, int mf_flags)
> > > > > > > +{
> > > > > > > + int rc;
> > > > > > > +
> > > > > > > + dax_read_lock(dax_dev);
> > > > > > > + if (!dax_alive(dax_dev)) {
> > > > > > > + rc = -ENXIO;
> > > > > > > + goto out;
> > > > > > > + }
> > > > > > > +
> > > > > > > + if (!dax_dev->holder_ops) {
> > > > > > > + rc = -EOPNOTSUPP;
> > > > > > > + goto out;
> > > > > > > + }
> > > > > > > +
> > > > > > > + rc = dax_dev->holder_ops->notify_failure(dax_dev, off, len, mf_flags);
> > > > > > > +out:
> > > > > > > + dax_read_unlock(dax_dev);
> > > > > > > + 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)
> > > > > > > @@ -254,6 +282,10 @@ void kill_dax(struct dax_device *dax_dev)
> > > > > > > return;
> > > > > > > dax_write_lock(dax_dev);
> > > > > > > clear_bit(DAXDEV_ALIVE, &dax_dev->flags);
> > > > > > > +
> > > > > > > + /* clear holder data */
> > > > > > > + dax_dev->holder_ops = NULL;
> > > > > > > + dax_dev->holder_data = NULL;
> > > > > > > dax_write_unlock(dax_dev);
> > > > > > > }
> > > > > > > EXPORT_SYMBOL_GPL(kill_dax);
> > > > > > > @@ -401,6 +433,36 @@ void put_dax(struct dax_device *dax_dev)
> > > > > > > }
> > > > > > > EXPORT_SYMBOL_GPL(put_dax);
> > > > > > >
> > > > > > > +void dax_register_holder(struct dax_device *dax_dev, void *holder,
> > > > > > > + const struct dax_holder_operations *ops)
> > > > > > > +{
> > > > > > > + if (!dax_alive(dax_dev))
> > > > > > > + return;
> > > > > > > +
> > > > > > > + dax_dev->holder_data = holder;
> > > > > > > + dax_dev->holder_ops = ops;
> > > > > >
> > > > > > Shouldn't this return an error code if the dax device is dead or if
> > > > > > someone already registered a holder? I'm pretty sure XFS should not
> > > > > > bind to a dax device if someone else already registered for it...
> > > > >
> > > > > Agree, yes.
> > > > >
> > > > > >
> > > > > > ...unless you want to use a notifier chain for failure events so that
> > > > > > there can be multiple consumers of dax failure events?
> > > > >
> > > > > No, I would hope not. It should be 1:1 holders to dax-devices. Similar
> > > > > ownership semantics like bd_prepare_to_claim().
> > > >
> > > > Does each partition on a pmem device still have its own dax_device?
> > >
> > > No, it never did...
> > >
> > > Just as before, each dax-device is still associated with a gendisk /
> > > whole-block_device. The recent change is that instead of needing that
> > > partition-block_device plumbed to convert a relative block number to
> > > its absolute whole-block_device offset the filesystem now handles that
> > > at iomap_begin() time. See:
> > >
> > > if (mapping_flags & IOMAP_DAX)
> > > iomap->addr += target->bt_dax_part_off;
> > >
> > > ...in xfs_bmbt_to_iomap() (in -next). I.e. bdev_dax_pgoff() is gone
> > > with the lead-in reworks.
> >
> > OH. Crap, Dan's right:
> >
> > XFS (pmem0p1): ddev dax = 0xffff88800304ed00 bdev = 0xffff8880480d6180
> > XFS (pmem0p2): ddev dax = 0xffff88800304ed00 bdev = 0xffff8880480d4380
> >
> > Unless you're planning to remove partition support too, this patch needs
> > to be reworked so that multiple filesystems in separate partitions can
> > each call dax_register_holder to receive memory_failure notifications
> > for their partition.
>
> Oh, crap indeed. I think this gets back to the slow tip-toeing away
> from dax + partition support. While FSDAX can continue to support
> "legacy/experimental" operation on partitions of DAX capable block
> devices, I think failure notification + reflink support is where we
> draw the line and say "DAX on partitions was a mistake, it's too late
> to undo that mistake, but going forward for new FSDAX features it
> requires switching from block-device partitions to pmem-namespaces if
> you need sub-division support and new DAX features."
>
> > /methinks this sharing is all a little scary...
>
> Yes, I think we should just fail the holder registration and
> DAX+reflink unless the FS being mounted on a whole device. I know Ted
> and others had reservations about moving filesystems to be mounted on
> dax-devices directly, but hopefully the whole-block_device requirement
> is a workable middle ground?
I think you have to be /very/ careful about that kind of statement --
Take ext4 for example. It has a lot of statically allocated ondisk
metadata. Someone could decide that it's a good idea to wire up a media
failure notification so that we shut down the fs if (say) a giant hole
opens up in the middle of the inode table. However, registering any
kind of media failure handler brings along this requirement for not
having partitions.
This means that if ext4 finds a filesystem on a partition on a pmem
device and someone else has already registered a media failure handler,
it will have to choose between foregoing media failure notifications or
failing the mount outright.
Or you could support notification call chains...
--D
PS: I was about to say that ext4 lacks reverse mapping and reflink, but
I forgot that ext4 *does* support reflink now. It just doesn't support
copy on write.
On Wed, Jan 5, 2022 at 3:54 PM Darrick J. Wong <[email protected]> wrote:
[..]
> > Yes, I think we should just fail the holder registration and
> > DAX+reflink unless the FS being mounted on a whole device. I know Ted
> > and others had reservations about moving filesystems to be mounted on
> > dax-devices directly, but hopefully the whole-block_device requirement
> > is a workable middle ground?
>
> I think you have to be /very/ careful about that kind of statement --
>
> Take ext4 for example. It has a lot of statically allocated ondisk
> metadata. Someone could decide that it's a good idea to wire up a media
> failure notification so that we shut down the fs if (say) a giant hole
> opens up in the middle of the inode table. However, registering any
> kind of media failure handler brings along this requirement for not
> having partitions.
>
> This means that if ext4 finds a filesystem on a partition on a pmem
> device and someone else has already registered a media failure handler,
> it will have to choose between foregoing media failure notifications or
> failing the mount outright.
...good example.
> Or you could support notification call chains...
We ended up with explicit callbacks after hch balked at a notifier
call-chain, but I think we're back to that now. The partition mistake
might be unfixable, but at least bdev_dax_pgoff() is dead. Notifier
call chains have their own locking so, Ruan, this still does not need
to touch dax_read_lock().
在 2022/1/5 6:44, Dan Williams 写道:
> On Sun, Dec 26, 2021 at 6:35 AM Shiyang Ruan <[email protected]> wrote:
>>
>> In order to introduce dax holder registration, we need a write lock for
>> dax.
>
> As far as I can see, no, a write lock is not needed while the holder
> is being registered.
>
> The synchronization that is needed is to make sure that the device
> stays live over the registration event, and that any in-flight holder
> operations are flushed before the device transitions from live to
> dead, and that in turn relates to the live state of the pgmap.
>
> The dax device cannot switch from live to dead without first flushing
> all readers, so holding dax_read_lock() over the register holder event
> should be sufficient. If you are worried about 2 or more potential
> holders colliding at registration time, I would expect that's already
> prevented by block device exclusive holder synchronization, but you
> could also use cmpxchg and a single pointer to a 'struct dax_holder {
> void *holder_data, struct dax_holder_operations *holder_ops }'. If you
> are worried about memory_failure triggering while the filesystem is
> shutting down it can do a synchronize_srcu(&dax_srcu) if it really
> needs to ensure that the notify path is idle after removing the holder
> registration.
>
> ...are there any cases remaining not covered by the above suggestions?
OK, I think I didn't get what actual role does the dax lock play
before... So, the modification of the lock is unnecessary. I'll take
your two suggestions into consideration.
--
Thanks,
Ruan.
On Sun, Dec 26, 2021 at 10:34:36PM +0800, Shiyang Ruan wrote:
> Since it is not a DAX-specific function, move it into mm and rename it
> to be a generic helper.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
Looks good,
Reviewed-by: Christoph Hellwig <[email protected]>
On Wed, Jan 05, 2022 at 04:12:04PM -0800, Dan Williams wrote:
> We ended up with explicit callbacks after hch balked at a notifier
> call-chain, but I think we're back to that now. The partition mistake
> might be unfixable, but at least bdev_dax_pgoff() is dead. Notifier
> call chains have their own locking so, Ruan, this still does not need
> to touch dax_read_lock().
I think we have a few options here:
(1) don't allow error notifications on partitions. And error return from
the holder registration with proper error handling in the file
system would give us that
(2) extent the holder mechanism to cover a range
(3) bite the bullet and create a new stacked dax_device for each
partition
I think (1) is the best option for now. If people really do need
partitions we'll have to go for (3)
On Sun, Dec 26, 2021 at 10:34:39PM +0800, Shiyang Ruan wrote:
> +#define FS_DAX_MAPPING_COW 1UL
> +
> +#define MAPPING_SET_COW(m) (m = (struct address_space *)FS_DAX_MAPPING_COW)
> +#define MAPPING_TEST_COW(m) (((unsigned long)m & FS_DAX_MAPPING_COW) == \
> + FS_DAX_MAPPING_COW)
These really should be inline functions and probably use lower case
names.
But different question, how does this not conflict with:
#define PAGE_MAPPING_ANON 0x1
in page-flags.h?
Either way I think this flag should move to page-flags.h and be
integrated with the PAGE_MAPPING_FLAGS infrastucture.
On Sun, Dec 26, 2021 at 10:34:34PM +0800, Shiyang Ruan wrote:
> The function name has been changed, so the description should be updated
> too.
>
> Signed-off-by: Shiyang Ruan <[email protected]>
Looks good,
Reviewed-by: Christoph Hellwig <[email protected]>
Dan, can you send this to Linux for 5.17 so that we can get it out of
the way?
Please only build the new DAX code if CONFIG_FS_DAX is set.
在 2022/1/20 16:46, Christoph Hellwig 写道:
> On Wed, Jan 05, 2022 at 04:12:04PM -0800, Dan Williams wrote:
>> We ended up with explicit callbacks after hch balked at a notifier
>> call-chain, but I think we're back to that now. The partition mistake
>> might be unfixable, but at least bdev_dax_pgoff() is dead. Notifier
>> call chains have their own locking so, Ruan, this still does not need
>> to touch dax_read_lock().
>
> I think we have a few options here:
>
> (1) don't allow error notifications on partitions. And error return from
> the holder registration with proper error handling in the file
> system would give us that
> (2) extent the holder mechanism to cover a range
> (3) bite the bullet and create a new stacked dax_device for each
> partition
>
> I think (1) is the best option for now. If people really do need
> partitions we'll have to go for (3)
Yes, I agree. I'm doing it the first way right now.
I think that since we can use namespace to divide a big NVDIMM into
multiple pmems, partition on a pmem seems not so meaningful.
--
Thanks,
Ruan.
在 2022/1/20 16:59, Christoph Hellwig 写道:
> On Sun, Dec 26, 2021 at 10:34:39PM +0800, Shiyang Ruan wrote:
>> +#define FS_DAX_MAPPING_COW 1UL
>> +
>> +#define MAPPING_SET_COW(m) (m = (struct address_space *)FS_DAX_MAPPING_COW)
>> +#define MAPPING_TEST_COW(m) (((unsigned long)m & FS_DAX_MAPPING_COW) == \
>> + FS_DAX_MAPPING_COW)
>
> These really should be inline functions and probably use lower case
> names.
OK.
>
> But different question, how does this not conflict with:
>
> #define PAGE_MAPPING_ANON 0x1
>
> in page-flags.h?
Now we are treating dax pages, so I think its flags should be different
from normal page. In another word, PAGE_MAPPING_ANON is a flag of rmap
mechanism for normal page, it doesn't work for dax page. And now, we
have dax rmap for dax page. So, I think this two kinds of flags are
supposed to be used in different mechanisms and won't conflect.
>
> Either way I think this flag should move to page-flags.h and be
> integrated with the PAGE_MAPPING_FLAGS infrastucture.
And that's why I keep them in this dax.c file.
--
Thanks,
Ruan.
On Fri, Jan 21, 2022 at 09:26:52AM +0800, Shiyang Ruan wrote:
>
>
> 在 2022/1/20 16:46, Christoph Hellwig 写道:
> > On Wed, Jan 05, 2022 at 04:12:04PM -0800, Dan Williams wrote:
> > > We ended up with explicit callbacks after hch balked at a notifier
> > > call-chain, but I think we're back to that now. The partition mistake
> > > might be unfixable, but at least bdev_dax_pgoff() is dead. Notifier
> > > call chains have their own locking so, Ruan, this still does not need
> > > to touch dax_read_lock().
> >
> > I think we have a few options here:
> >
> > (1) don't allow error notifications on partitions. And error return from
> > the holder registration with proper error handling in the file
> > system would give us that
Hm, so that means XFS can only support dax+pmem when there aren't
partitions in use? Ew.
> > (2) extent the holder mechanism to cover a rangeo
I don't think I was around for the part where "hch balked at a notifier
call chain" -- what were the objections there, specifically? I would
hope that pmem problems would be infrequent enough that the locking
contention (or rcu expiration) wouldn't be an issue...?
> > (3) bite the bullet and create a new stacked dax_device for each
> > partition
> >
> > I think (1) is the best option for now. If people really do need
> > partitions we'll have to go for (3)
>
> Yes, I agree. I'm doing it the first way right now.
>
> I think that since we can use namespace to divide a big NVDIMM into multiple
> pmems, partition on a pmem seems not so meaningful.
I'll try to find out what will happen if pmem suddenly stops supporting
partitions...
--D
>
> --
> Thanks,
> Ruan.
>
>
On Thu, Jan 20, 2022 at 06:22:00PM -0800, Darrick J. Wong wrote:
> Hm, so that means XFS can only support dax+pmem when there aren't
> partitions in use? Ew.
Yes. Or any sensible DAX usage going forward for that matter.
>
> > > (2) extent the holder mechanism to cover a rangeo
>
> I don't think I was around for the part where "hch balked at a notifier
> call chain" -- what were the objections there, specifically? I would
> hope that pmem problems would be infrequent enough that the locking
> contention (or rcu expiration) wouldn't be an issue...?
notifiers are a nightmare untype API leading to tons of boilerplate
code. Open coding the notification is almost always a better idea.
On Fri, Jan 21, 2022 at 10:33:58AM +0800, Shiyang Ruan wrote:
> >
> > But different question, how does this not conflict with:
> >
> > #define PAGE_MAPPING_ANON 0x1
> >
> > in page-flags.h?
>
> Now we are treating dax pages, so I think its flags should be different from
> normal page. In another word, PAGE_MAPPING_ANON is a flag of rmap mechanism
> for normal page, it doesn't work for dax page. And now, we have dax rmap
> for dax page. So, I think this two kinds of flags are supposed to be used
> in different mechanisms and won't conflect.
It just needs someone to use folio_test_anon in a place where a DAX
folio can be passed. This probably should not happen, but we need to
clearly document that.
> > Either way I think this flag should move to page-flags.h and be
> > integrated with the PAGE_MAPPING_FLAGS infrastucture.
>
> And that's why I keep them in this dax.c file.
But that does not integrate it with the infrastructure. For people
to debug things it needs to be next to PAGE_MAPPING_ANON and have
documentation explaining why they are exclusive.
在 2022/1/21 15:16, Christoph Hellwig 写道:
> On Fri, Jan 21, 2022 at 10:33:58AM +0800, Shiyang Ruan wrote:
>>>
>>> But different question, how does this not conflict with:
>>>
>>> #define PAGE_MAPPING_ANON 0x1
>>>
>>> in page-flags.h?
>>
>> Now we are treating dax pages, so I think its flags should be different from
>> normal page. In another word, PAGE_MAPPING_ANON is a flag of rmap mechanism
>> for normal page, it doesn't work for dax page. And now, we have dax rmap
>> for dax page. So, I think this two kinds of flags are supposed to be used
>> in different mechanisms and won't conflect.
>
> It just needs someone to use folio_test_anon in a place where a DAX
> folio can be passed. This probably should not happen, but we need to
> clearly document that.
>
>>> Either way I think this flag should move to page-flags.h and be
>>> integrated with the PAGE_MAPPING_FLAGS infrastucture.
>>
>> And that's why I keep them in this dax.c file.
>
> But that does not integrate it with the infrastructure. For people
> to debug things it needs to be next to PAGE_MAPPING_ANON and have
> documentation explaining why they are exclusive.
Ok, understood.
--
Thanks,
Ruan.
On Thu, Jan 20, 2022 at 6:22 PM Darrick J. Wong <[email protected]> wrote:
>
> On Fri, Jan 21, 2022 at 09:26:52AM +0800, Shiyang Ruan wrote:
> >
> >
> > 在 2022/1/20 16:46, Christoph Hellwig 写道:
> > > On Wed, Jan 05, 2022 at 04:12:04PM -0800, Dan Williams wrote:
> > > > We ended up with explicit callbacks after hch balked at a notifier
> > > > call-chain, but I think we're back to that now. The partition mistake
> > > > might be unfixable, but at least bdev_dax_pgoff() is dead. Notifier
> > > > call chains have their own locking so, Ruan, this still does not need
> > > > to touch dax_read_lock().
> > >
> > > I think we have a few options here:
> > >
> > > (1) don't allow error notifications on partitions. And error return from
> > > the holder registration with proper error handling in the file
> > > system would give us that
>
> Hm, so that means XFS can only support dax+pmem when there aren't
> partitions in use? Ew.
>
> > > (2) extent the holder mechanism to cover a rangeo
>
> I don't think I was around for the part where "hch balked at a notifier
> call chain" -- what were the objections there, specifically? I would
> hope that pmem problems would be infrequent enough that the locking
> contention (or rcu expiration) wouldn't be an issue...?
>
> > > (3) bite the bullet and create a new stacked dax_device for each
> > > partition
> > >
> > > I think (1) is the best option for now. If people really do need
> > > partitions we'll have to go for (3)
> >
> > Yes, I agree. I'm doing it the first way right now.
> >
> > I think that since we can use namespace to divide a big NVDIMM into multiple
> > pmems, partition on a pmem seems not so meaningful.
>
> I'll try to find out what will happen if pmem suddenly stops supporting
> partitions...
Finally catching up with this thread...
Given that XFS already has the policy of disabling DAX rather than
failing the mount in some cases, I think it is workable for XFS to
fail a DAX mount if reflink is enabled on a partition. This should not
regress anyone's current setup since the FS will not even mount with
dax+reflink today. As to the specific concern about registering
failure handlers for other purposes I expect that can be done by
registering failure notification handlers on block devices, not dax
devices.
So it's not that pmem will suddenly stop supporting partitions, dax
will simply never gain support for reflink in the presence of
partitions.