This patchset is aimed to support shared pages tracking for fsdax.
Changes from [V6 RESEND]:
- Move ->memory_failure() into the patch who implements it
- Change the parameter of ->memory_failure():
unsigned long nr_pfns -> size_t size
- Remove changes(2 patches) for mapped device
- Add some necessary comments for functions or interfaces
- P1: Make a pre-patch for changes for dax_{read,write}_lock()
- P2: Change the parameter of ->notify_failure():
void *data -> int flags
- P3: keep the original dax_lock_page() logic
- P5: Rewrite the lock function for file's mapping and index:
dax_lock_mapping_entry()
- P6: use the new dax_lock_mapping_entry() to lock dax entry, and add
size parameter to handle a range of failure
- P7: add a cross range calculation between memory_failure range and
founded extent range
- Rebased to v5.15-rc1
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_notify_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()
The fsdax & reflink support for XFS is not contained in this patchset.
(Rebased on v5.15-rc1)
==
Shiyang Ruan (8):
dax: Use 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: Introduce dax_lock_mapping_entry()
mm: Introduce mf_dax_kill_procs() for fsdax case
xfs: Implement ->notify_failure() for XFS
fsdax: add exception for reflinked files
drivers/dax/device.c | 11 +-
drivers/dax/super.c | 121 +++++++++++++++++---
drivers/md/dm-writecache.c | 7 +-
drivers/nvdimm/pmem.c | 11 ++
fs/dax.c | 115 ++++++++++++++-----
fs/xfs/xfs_fsops.c | 3 +
fs/xfs/xfs_mount.h | 1 +
fs/xfs/xfs_super.c | 188 +++++++++++++++++++++++++++++++
include/linux/dax.h | 80 ++++++++++++-
include/linux/memremap.h | 9 ++
include/linux/mm.h | 2 +
mm/memory-failure.c | 225 ++++++++++++++++++++++++++-----------
12 files changed, 643 insertions(+), 130 deletions(-)
--
2.33.0
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 <redacted>
---
mm/memory-failure.c | 140 ++++++++++++++++++++++++--------------------
1 file changed, 76 insertions(+), 64 deletions(-)
@@ -1519,12 +1592,8 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,structdev_pagemap*pgmap){structpage*page=pfn_to_page(pfn);-unsignedlongsize=0;-structto_kill*tk;LIST_HEAD(tokill);-intrc=-EBUSY;-loff_tstart;-dax_entry_tcookie;+intrc=-ENXIO;if(flags&MF_COUNT_INCREASED)/*
@@ -1533,67 +1602,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;-gotoout;-}--/*-*Preventtheinodefrombeingfreedwhileweareinterrogating-*theaddress_space,typicallythiswouldbehandledby-*lock_page(),butdaxpagesdonotusethepagelock.This-*alsopreventschangestothemappingofthispfnuntil-*poisonsignalingiscomplete.-*/-cookie=dax_lock_page(page);-if(!cookie)+if(!pgmap_pfn_valid(pgmap,pfn))gotoout;-if(hwpoison_filter(page)){-rc=0;-gotounlock;-}--if(pgmap->type==MEMORY_DEVICE_PRIVATE){-/*-*TODO:HandleHMMpageswhichmayneedcoordination-*withdevice-sidememory.-*/-gotounlock;-}--/*-*Usethisflagasanindicationthatthedaxpagehasbeen-*remappedUCtopreventspeculativeconsumptionofpoison.-*/-SetPageHWPoison(page);--/*-*UnlikeSystem-RAMthereisnopossibilitytoswapina-*differentphysicalpageatagivenvirtualaddress,soall-*userspaceconsumptionofZONE_DEVICEmemorynecessitates-*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){-/*-*Unmapthelargestmappingtoavoidbreakingup-*device-daxmappingswhichareconstantsize.The-*actualsizeofthemappingbeingtorndownis-*communicatedinsiginfo,seekill_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);
To easily track filesystem from a pmem device, we introduce a holder for
dax_device structure, and also its operation. This holder is used to
remember who is using this dax_device:
- When it is the backend of a filesystem, the holder will be the
superblock of this filesystem.
- When this pmem device is one of the targets in a mapped device, the
holder will be this mapped device. In this case, the mapped device
has its own dax_device and it will follow the first rule. So that we
can finally track to the filesystem we needed.
The holder and holder_ops will be set when filesystem is being mounted,
or an target device is being activated.
Signed-off-by: Shiyang Ruan <redacted>
---
drivers/dax/super.c | 59 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 29 ++++++++++++++++++++++
2 files changed, 88 insertions(+)
In order to introduce dax holder registration, we need a write lock for
dax. Because of the rarity of notification failures and the infrequency
of registration events, it would be better to be a global lock rather
than per-device. So, change the current lock to rwsem and introduce a
write lock for registration.
Signed-off-by: Shiyang Ruan <redacted>
---
drivers/dax/device.c | 11 +++++-----
drivers/dax/super.c | 43 ++++++++++++++++++++++----------------
drivers/md/dm-writecache.c | 7 +++----
fs/dax.c | 26 +++++++++++------------
include/linux/dax.h | 9 ++++----
5 files changed, 49 insertions(+), 47 deletions(-)
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 BTW, output the page corresponding to the specific dax entry for
caller use.
Signed-off-by: Shiyang Ruan <redacted>
---
fs/dax.c | 65 ++++++++++++++++++++++++++++++++++++++++++++-
include/linux/dax.h | 15 +++++++++++
2 files changed, 79 insertions(+), 1 deletion(-)
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 <redacted>
---
drivers/nvdimm/pmem.c | 11 +++++++++++
include/linux/memremap.h | 9 +++++++++
mm/memory-failure.c | 14 ++++++++++++++
3 files changed, 34 insertions(+)
@@ -1605,6 +1605,20 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,if(!pgmap_pfn_valid(pgmap,pfn))gotoout;+/*+*Calldriver'simplementationtohandlethememoryfailure,otherwise+*fallbacktogenerichandler.+*/+if(pgmap->ops->memory_failure){+rc=pgmap->ops->memory_failure(pgmap,pfn,PAGE_SIZE,flags);+/*+*Fallbacktogenerichandlertooifoperationisnot+*supportedinsidethedriver/device/filesystem.+*/+if(rc!=EOPNOTSUPP)+gotoout;+}+rc=mf_generic_kill_procs(pfn,flags,pgmap);out:/* drop pgmap ref acquired in caller */
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 between mf_generic_kill_procs() is,
it accepts file's mapping,offset instead of struct page. Because
different file's mappings and offsets may share the same page in fsdax
mode. So, it is called when filesystem RMAP results are found.
Signed-off-by: Shiyang Ruan <redacted>
---
fs/dax.c | 10 ------
include/linux/dax.h | 9 +++++
include/linux/mm.h | 2 ++
mm/memory-failure.c | 83 ++++++++++++++++++++++++++++++++++++++++-----
4 files changed, 86 insertions(+), 18 deletions(-)
@@ -852,16 +852,6 @@ static void *dax_insert_entry(struct xa_state *xas,returnentry;}-staticinline-unsignedlongpgoff_address(pgoff_tpgoff,structvm_area_struct*vma)-{-unsignedlongaddress;--address=vma->vm_start+((pgoff-vma->vm_pgoff)<<PAGE_SHIFT);-VM_BUG_ON_VMA(address<vma->vm_start||address>=vma->vm_end,vma);-returnaddress;-}-/* Walk all mappings of a given index of a file and writeprotect them */staticvoiddax_entry_mkclean(structaddress_space*mapping,pgoff_tindex,unsignedlongpfn)
@@ -1503,6 +1533,43 @@ static int mf_generic_kill_procs(unsigned long long pfn, int flags,return0;}+/**+*mf_dax_kill_procs-Collectandkillprocesseswhoareusingthisfilerange+*@mapping:thefileinuse+*@index:startoffsetoftherange+*@size:lengthoftherange+*@flags:memoryfailureflags+*/+intmf_dax_kill_procs(structaddress_space*mapping,pgoff_tindex,+size_tsize,intflags)+{+LIST_HEAD(to_kill);+dax_entry_tcookie;+structpage*page;+size_tend=(index<<PAGE_SHIFT)+size;++flags|=MF_ACTION_REQUIRED|MF_MUST_KILL;++for(;(index<<PAGE_SHIFT)<end;index++){+page=NULL;+cookie=dax_lock_mapping_entry(mapping,index,&page);+if(!cookie)+return-EBUSY;+if(!page)+gotounlock;++SetPageHWPoison(page);++collect_procs_fsdax(page,mapping,index,&to_kill);+unmap_and_kill(&to_kill,page_to_pfn(page),mapping,+index,flags);+unlock:+dax_unlock_mapping_entry(mapping,index,cookie);+}+return0;+}+EXPORT_SYMBOL_GPL(mf_dax_kill_procs);+staticintmemory_failure_hugetlb(unsignedlongpfn,intflags){structpage*p=pfn_to_page(pfn);
This function is used to handle errors which may cause data lost in
filesystem. Such as memory failure in fsdax mode.
If the rmap feature of XFS enabled, we can query it to find files and
metadata which are associated with the corrupt data. For now all we do
is kill processes with that file mapped into their address spaces, but
future patches could actually do something about corrupt metadata.
After that, the memory failure needs to notify the processes who are
using those files.
Signed-off-by: Shiyang Ruan <redacted>
---
drivers/dax/super.c | 19 +++++
fs/xfs/xfs_fsops.c | 3 +
fs/xfs/xfs_mount.h | 1 +
fs/xfs/xfs_super.c | 188 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 18 +++++
5 files changed, 229 insertions(+)
@@ -1110,6 +1133,171 @@ xfs_fs_free_cached_objects(returnxfs_reclaim_inodes_nr(XFS_M(sb),sc->nr_to_scan);}+structnotify_failure_info{+xfs_agblock_tstartblock;+xfs_filblks_tblockcount;+intflags;+};++staticloff_t+xfs_notify_failure_start(+structxfs_mount*mp,+conststructxfs_rmap_irec*rec,+conststructnotify_failure_info*notify)+{+loff_tstart=rec->rm_offset;++if(notify->startblock>rec->rm_startblock)+start+=XFS_FSB_TO_B(mp,+notify->startblock-rec->rm_startblock);+returnstart;+}++staticsize_t+xfs_notify_failure_size(+structxfs_mount*mp,+conststructxfs_rmap_irec*rec,+conststructnotify_failure_info*notify)+{+xfs_agblock_trec_start=rec->rm_startblock;+xfs_agblock_trec_end=rec->rm_startblock+rec->rm_blockcount;+xfs_agblock_tnotify_start=notify->startblock;+xfs_agblock_tnotify_end=notify->startblock+notify->blockcount;+xfs_agblock_tcross_start=max(rec_start,notify_start);+xfs_agblock_tcross_end=min(rec_end,notify_end);++returnXFS_FSB_TO_B(mp,cross_end-cross_start);+}++staticint+xfs_dax_notify_failure_fn(+structxfs_btree_cur*cur,+conststructxfs_rmap_irec*rec,+void*data)+{+structxfs_mount*mp=cur->bc_mp;+structxfs_inode*ip;+structaddress_space*mapping;+structnotify_failure_info*notify=data;+interror=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_META);+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);+if(error)+returnerror;++mapping=VFS_I(ip)->i_mapping;+if(IS_ENABLED(CONFIG_MEMORY_FAILURE)){+loff_toffset=xfs_notify_failure_start(mp,rec,notify);+size_tsize=xfs_notify_failure_size(mp,rec,notify);++error=mf_dax_kill_procs(mapping,offset>>PAGE_SHIFT,size,+notify->flags);+}+// TODO try to fix data+xfs_irele(ip);++returnerror;+}++staticloff_t+xfs_dax_bdev_offset(+structxfs_mount*mp,+structdax_device*dax_dev,+loff_tdisk_offset)+{+structblock_device*bdev;++if(mp->m_ddev_targp->bt_daxdev==dax_dev)+bdev=mp->m_ddev_targp->bt_bdev;+elseif(mp->m_logdev_targp->bt_daxdev==dax_dev)+bdev=mp->m_logdev_targp->bt_bdev;+else+bdev=mp->m_rtdev_targp->bt_bdev;++returndisk_offset-(get_start_sect(bdev)<<SECTOR_SHIFT);+}++staticint+xfs_dax_notify_failure(+structdax_device*dax_dev,+loff_toffset,+size_tlen,+intflags)+{+structxfs_mount*mp=fs_dax_get_holder(dax_dev);+structxfs_trans*tp=NULL;+structxfs_btree_cur*cur=NULL;+structxfs_buf*agf_bp=NULL;+structxfs_rmap_irecrmap_low,rmap_high;+loff_tbdev_offset=xfs_dax_bdev_offset(mp,dax_dev,+offset);+xfs_fsblock_tfsbno=XFS_B_TO_FSB(mp,bdev_offset);+xfs_agnumber_tagno=XFS_FSB_TO_AGNO(mp,fsbno);+interror=0;+structnotify_failure_infonotify={+.startblock=XFS_FSB_TO_AGBNO(mp,fsbno),+.blockcount=XFS_B_TO_FSB(mp,len),+.flags=flags,+};++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_META);+return-EFSCORRUPTED;+}++if(!xfs_has_rmapbt(mp)){+xfs_warn(mp,"notify_failure() needs rmapbt enabled!");+return-EOPNOTSUPP;+}++error=xfs_trans_alloc_empty(mp,&tp);+if(error)+returnerror;++error=xfs_alloc_read_agf(mp,tp,agno,0,&agf_bp);+if(error)+gotoout_cancel_tp;++cur=xfs_rmapbt_init_cursor(mp,tp,agf_bp,agf_bp->b_pag);++/* Construct a range for rmap query */+memset(&rmap_low,0,sizeof(rmap_low));+memset(&rmap_high,0xFF,sizeof(rmap_high));+rmap_low.rm_startblock=rmap_high.rm_startblock=notify.startblock;+rmap_low.rm_blockcount=rmap_high.rm_blockcount=notify.blockcount;++error=xfs_rmap_query_range(cur,&rmap_low,&rmap_high,+xfs_dax_notify_failure_fn,¬ify);++xfs_btree_del_cursor(cur,error);+xfs_trans_brelse(tp,agf_bp);++out_cancel_tp:+xfs_trans_cancel(tp);+returnerror;+}++staticconststructdax_holder_operationsxfs_dax_holder_operations={+.notify_failure=xfs_dax_notify_failure,+};+staticconststructsuper_operationsxfs_super_operations={.alloc_inode=xfs_fs_alloc_inode,.destroy_inode=xfs_fs_destroy_inode,
For reflinked files, one dax page may be associated more than once with
different fime mapping and index. It will report warning. Now, since
we have introduced dax-RMAP for this case and also have to keep its
functionality for other filesystems who are not support rmap, I add this
exception here.
Signed-off-by: Shiyang Ruan <redacted>
---
fs/dax.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2021-10-14 17:48:10
On Fri, Sep 24, 2021 at 09:09:52PM +0800, Shiyang Ruan wrote:
In order to introduce dax holder registration, we need a write lock for
dax. Because of the rarity of notification failures and the infrequency
of registration events, it would be better to be a global lock rather
than per-device. So, change the current lock to rwsem and introduce a
write lock for registration.
Urgh, I totally thought dax_read_lock was a global lock on something
relating to the global dax_device state until I noticed this comment
above kill_dax():
/*
* Note, rcu is not protecting the liveness of dax_dev, rcu is ensuring
* that any fault handlers or operations that might have seen
* dax_alive(), have completed. Any operations that start after
* synchronize_srcu() has run will abort upon seeing !dax_alive().
*/
So dax_srcu ensures stability in the dax_device's ALIVE state while any
code that relies on that aliveness runs. As a side effect, it'll block
kill_dax (and I guess run_dax) while those functions run. It doesn't
protect any global state at all... but this isn't made obvious in the
API by (for example) passing the dax_device into dax_read_lock.
IOWs, It's not protecting against the dax_device getting freed or
anything resembling global state. So that's probably why you note above
that this /could/ be a per-device synchronization primitive, right?
If that's the case, then why shouldn't this be a per-device item? As
written here, any code that takes dax_write_lock() will block every dax
device in the system while it does some work on a single dax device.
Being an rwsem, it will also have to wait for every other dax device
access to complete before it can begin. That seems excessive,
particularly if in the future we start hooking up lots of pmem to a
single host.
I have more to say around kill_dax() below.
Shouldn't you take the dax_write_lock() around the clear_bit call to
maintain the behavior that kill_dax can't proceed until all the
functions that depend on DAXDEV_ALIVE state have finished?
--D
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2021-10-14 18:00:31
On Fri, Sep 24, 2021 at 09:09:53PM +0800, Shiyang Ruan wrote:
quoted hunk
To easily track filesystem from a pmem device, we introduce a holder for
dax_device structure, and also its operation. This holder is used to
remember who is using this dax_device:
- When it is the backend of a filesystem, the holder will be the
superblock of this filesystem.
- When this pmem device is one of the targets in a mapped device, the
holder will be this mapped device. In this case, the mapped device
has its own dax_device and it will follow the first rule. So that we
can finally track to the filesystem we needed.
The holder and holder_ops will be set when filesystem is being mounted,
or an target device is being activated.
Signed-off-by: Shiyang Ruan <redacted>
---
drivers/dax/super.c | 59 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 29 ++++++++++++++++++++++
2 files changed, 88 insertions(+)
@@ -374,6 +379,29 @@ int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,}EXPORT_SYMBOL_GPL(dax_zero_page_range);+intdax_holder_notify_failure(structdax_device*dax_dev,loff_toffset,+size_tsize,intflags)+{+intrc;++dax_read_lock();+if(!dax_alive(dax_dev)){+rc=-ENXIO;+gotoout;+}++if(!dax_dev->holder_data){+rc=-EOPNOTSUPP;+gotoout;+}++rc=dax_dev->holder_ops->notify_failure(dax_dev,offset,size,flags);
Shouldn't this check if dax_dev->holder_ops != NULL before dereferencing
it for the function call? Imagine an implementation that wants to
attach a ->notify_failure function to a dax_device, maintains its own
lookup table, and decides that it doesn't need to set holder_data.
(Or, imagine someone who writes a garbage into holder_data and *boom*)
How does the locking work here? If there's a media failure, we'll take
dax_rwsem and call ->notify_failure. If the ->notify_failure function
wants to access the pmem to handle the error by calling back into the
dax code, will that cause nested locking on dax_rwsem?
Jumping ahead a bit, I think the rmap btree accesses that the xfs
implementation performs can cause xfs_buf(fer) cache IO, which would
trigger that if the buffers aren't already in memory, if I'm reading
this correctly?
I guess this means that the holder has to detach itself before anyone
calls kill_dax, or else a dead dax device ends up with a dangling
reference to the holder?
quoted hunk
+}
+EXPORT_SYMBOL_GPL(dax_set_holder);
+
+void *dax_get_holder(struct dax_device *dax_dev)
+{
+ void *holder;
+
+ dax_read_lock();
+ if (!dax_alive(dax_dev)) {
+ dax_read_unlock();
+ return NULL;
+ }
+
+ holder = dax_dev->holder_data;
+ dax_read_unlock();
+ return holder;
+}
+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
Shouldn't size be u64 or something? Let's say that 8GB of your pmem go
bad, wouldn't you want a single call? Though I guess the current
implementation only goes a single page at a time, doesn't it?
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2021-10-14 18:02:35
On Fri, Sep 24, 2021 at 09:09:54PM +0800, Shiyang Ruan wrote:
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 <redacted>
This looks like a reasonable hoist...
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
--D
@@ -1519,12 +1592,8 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,structdev_pagemap*pgmap){structpage*page=pfn_to_page(pfn);-unsignedlongsize=0;-structto_kill*tk;LIST_HEAD(tokill);-intrc=-EBUSY;-loff_tstart;-dax_entry_tcookie;+intrc=-ENXIO;if(flags&MF_COUNT_INCREASED)/*
@@ -1533,67 +1602,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;-gotoout;-}--/*-*Preventtheinodefrombeingfreedwhileweareinterrogating-*theaddress_space,typicallythiswouldbehandledby-*lock_page(),butdaxpagesdonotusethepagelock.This-*alsopreventschangestothemappingofthispfnuntil-*poisonsignalingiscomplete.-*/-cookie=dax_lock_page(page);-if(!cookie)+if(!pgmap_pfn_valid(pgmap,pfn))gotoout;-if(hwpoison_filter(page)){-rc=0;-gotounlock;-}--if(pgmap->type==MEMORY_DEVICE_PRIVATE){-/*-*TODO:HandleHMMpageswhichmayneedcoordination-*withdevice-sidememory.-*/-gotounlock;-}--/*-*Usethisflagasanindicationthatthedaxpagehasbeen-*remappedUCtopreventspeculativeconsumptionofpoison.-*/-SetPageHWPoison(page);--/*-*UnlikeSystem-RAMthereisnopossibilitytoswapina-*differentphysicalpageatagivenvirtualaddress,soall-*userspaceconsumptionofZONE_DEVICEmemorynecessitates-*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){-/*-*Unmapthelargestmappingtoavoidbreakingup-*device-daxmappingswhichareconstantsize.The-*actualsizeofthemappingbeingtorndownis-*communicatedinsiginfo,seekill_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);
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2021-10-14 18:05:09
On Fri, Sep 24, 2021 at 09:09:55PM +0800, Shiyang Ruan wrote:
quoted hunk
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 <redacted>
---
drivers/nvdimm/pmem.c | 11 +++++++++++
include/linux/memremap.h | 9 +++++++++
mm/memory-failure.c | 14 ++++++++++++++
3 files changed, 34 insertions(+)
@@ -1605,6 +1605,20 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,if(!pgmap_pfn_valid(pgmap,pfn))gotoout;+/*+*Calldriver'simplementationtohandlethememoryfailure,otherwise+*fallbacktogenerichandler.+*/+if(pgmap->ops->memory_failure){+rc=pgmap->ops->memory_failure(pgmap,pfn,PAGE_SIZE,flags);+/*+*Fallbacktogenerichandlertooifoperationisnot+*supportedinsidethedriver/device/filesystem.+*/+if(rc!=EOPNOTSUPP)
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2021-10-14 18:17:07
On Fri, Sep 24, 2021 at 09:09:56PM +0800, Shiyang Ruan wrote:
quoted hunk
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 BTW, output the page corresponding to the specific dax entry for
caller use.
Signed-off-by: Shiyang Ruan <redacted>
---
fs/dax.c | 65 ++++++++++++++++++++++++++++++++++++++++++++-
include/linux/dax.h | 15 +++++++++++
2 files changed, 79 insertions(+), 1 deletion(-)
I kinda wonder if these open-coded magic values ~0UL (no entry) and 0
(cannot lock) should be #defines that force-cast the magic value to
dax_entry_t...
...but then I'm not really an expert in the design behind fs/dax.c --
this part looks reasonable enough to me, but I think Dan or Matthew
ought to look this over.
--D
quoted hunk
+ } 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
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2021-10-14 19:21:54
On Fri, Sep 24, 2021 at 09:09:58PM +0800, Shiyang Ruan wrote:
quoted hunk
This function is used to handle errors which may cause data lost in
filesystem. Such as memory failure in fsdax mode.
If the rmap feature of XFS enabled, we can query it to find files and
metadata which are associated with the corrupt data. For now all we do
is kill processes with that file mapped into their address spaces, but
future patches could actually do something about corrupt metadata.
After that, the memory failure needs to notify the processes who are
using those files.
Signed-off-by: Shiyang Ruan <redacted>
---
drivers/dax/super.c | 19 +++++
fs/xfs/xfs_fsops.c | 3 +
fs/xfs/xfs_mount.h | 1 +
fs/xfs/xfs_super.c | 188 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 18 +++++
5 files changed, 229 insertions(+)
I'm confused, is this supposed to return the file pos(ition) of the
failed range in units of bytes or in fs blocks?
If it's units of bytes (like the loff_t return value implies) then this
should be called xfs_notify_failure_pos.
These are "next" variables, not "end". The end of the record is
startblock + blockcount - 1.
+ xfs_agblock_t notify_start = notify->startblock;
+ xfs_agblock_t notify_end = notify->startblock + notify->blockcount;
+ xfs_agblock_t cross_start = max(rec_start, notify_start);
+ xfs_agblock_t cross_end = min(rec_end, notify_end);
+
+ return XFS_FSB_TO_B(mp, cross_end - cross_start);
+}
+
+static int
+xfs_dax_notify_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 notify_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_META);
+ 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);
If you're going to use _INCORE then you probably want to filter out the
-ENODATA or whatever error code means "inode wasn't loaded", because
returning any nonzero value to rmap_query_range causes it to stop
iterating.
Are @flags supposed to contain the MF_ flags that were passed to
memory_failure()? The variable name should probably be @mf_flags
throughout the patchset if that's the case.
I don't like using loff_t to represent byte offsets into the physical
device. loff_t should be used only for file byte offsets, and that's
not what we're storing here.
I think this is still wrong, since fsblocks are segmented (nonlinear)
addresses. Pass daddr into xfs_dax_notify_ddev_failure like I lay out
below, and then you can do:
start_fsbno = XFS_DADDR_TO_FSB(mp, daddr);
agno = XFS_FSB_TO_AGNO(mp, fsbno);
notify.startblock = XFS_FSB_TO_AGBNO(mp, fsbno);
notify.blockcount = XFS_BB_TO_FSB(mp, bblen);
(More on this below)
Urk. xfs_dax_notify_failure should be a short function to dispatch the
notification to the proper handler. Everything from here down should be
in a separate function xfs_dax_notify_ddev_failure, so that the next
line of xfs_dax_notify_failure is just:
return xfs_dax_notify_ddev_failure(mp, BTOBB(offset),
BTOBB(len), flags);
And then we have
static int
xfs_dax_notify_ddev_failure(
struct xfs_mount *mp,
xfs_daddr_t daddr,
xfs_daddr_t bblen,
int mf_flags)
{
if (!xfs_has_rmapbt(mp)) {
xfs_warn(...);
Because otherwise xfs_dax_notify_failure gets cluttered.
What happens if the failure range spans multiple AGs? I suppose it's
not technically possible today since we only report a single page at a
time. But for the general case, I think we actually want (building off
the sample code above) this function to do something like this:
start_fsbno = XFS_DADDR_TO_FSB(mp, daddr);
agno = XFS_FSB_TO_AGNO(mp, fsbno);
end_fsbno = XFS_DADDR_TO_FSB(mp, daddr + bblen);
end_agno = XFS_FSB_TO_AGNO(mp, end_fsbno);
for (; agno <= end_agbno; agno++) {
struct xfs_rmap_irec rmap_low = { };
struct xfs_rmap_irec rmap_high;
notify.startblock = XFS_FSB_TO_AGBNO(mp, fsbno);
notify.blockcount = XFS_BB_TO_FSB(mp, bblen);
/*
* init transaction, read agf, init cursor...
*/
memset(&rmap_high, 0xFF, sizeof(rmap_high));
rmap_low.rm_startblock = XFS_FSB_TO_AGBNO(mp, fsbno);
if (agno == end_agbno)
rmap_high.rm_startblock = XFS_FSB_TO_AGBNO(mp,
end_fsbno);
error = xfs_rmap_query_range(...);
if (error)
fail;
fsbno = XFS_AGB_TO_FSB(mp, agno + 1, 0);
}
--D
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2021-10-14 19:24:52
On Fri, Sep 24, 2021 at 09:09:59PM +0800, Shiyang Ruan wrote:
quoted hunk
For reflinked files, one dax page may be associated more than once with
different fime mapping and index. It will report warning. Now, since
we have introduced dax-RMAP for this case and also have to keep its
functionality for other filesystems who are not support rmap, I add this
exception here.
Signed-off-by: Shiyang Ruan <redacted>
---
fs/dax.c | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
It feels a little dangerous to have page->mapping for shared storage
point to an actual address_space when there are really multiple
potential address_spaces out there. If the mm or dax folks are ok with
doing this this way then I'll live with it, but it seems like you'd want
to leave /some/ kind of marker once you know that the page has multiple
owners and therefore regular mm rmap via page->mapping won't work.
--D
From: "Darrick J. Wong" <djwong@kernel.org> Date: 2021-10-14 19:32:43
On Fri, Sep 24, 2021 at 09:09:57PM +0800, Shiyang Ruan wrote:
quoted hunk
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 between mf_generic_kill_procs() is,
it accepts file's mapping,offset instead of struct page. Because
different file's mappings and offsets may share the same page in fsdax
mode. So, it is called when filesystem RMAP results are found.
Signed-off-by: Shiyang Ruan <redacted>
---
fs/dax.c | 10 ------
include/linux/dax.h | 9 +++++
include/linux/mm.h | 2 ++
mm/memory-failure.c | 83 ++++++++++++++++++++++++++++++++++++++++-----
4 files changed, 86 insertions(+), 18 deletions(-)
@@ -852,16 +852,6 @@ static void *dax_insert_entry(struct xa_state *xas,returnentry;}-staticinline-unsignedlongpgoff_address(pgoff_tpgoff,structvm_area_struct*vma)-{-unsignedlongaddress;--address=vma->vm_start+((pgoff-vma->vm_pgoff)<<PAGE_SHIFT);-VM_BUG_ON_VMA(address<vma->vm_start||address>=vma->vm_end,vma);-returnaddress;-}-/* Walk all mappings of a given index of a file and writeprotect them */staticvoiddax_entry_mkclean(structaddress_space*mapping,pgoff_tindex,unsignedlongpfn)
@@ -544,7 +549,32 @@ 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);
@@ -1503,6 +1533,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 offset of the range+ * @size: length of the range
It feels odd that one argument is in units of pgoff_t but the other is
in bytes.
Hm. What flags will we be passing to the xfs_dax_notify_failure_fn?
Does XFS itself have to care about what's in the flags values, or is it
really just a magic cookie to be passed from the mm layer into the fs
and back to mf_dax_kill_procs?
--D
From: Christoph Hellwig <hch@infradead.org> Date: 2021-10-15 06:30:09
On Fri, Sep 24, 2021 at 09:09:52PM +0800, Shiyang Ruan wrote:
In order to introduce dax holder registration, we need a write lock for
dax. Because of the rarity of notification failures and the infrequency
of registration events, it would be better to be a global lock rather
than per-device. So, change the current lock to rwsem and introduce a
write lock for registration.
I don't think taking the rw_semaphore everywhere will scale, as
basically any DAX based I/O will take it (in read mode).
So at a minimum we'd need a per-device percpu_rw_semaphore if we want
to go there.
From: Christoph Hellwig <hch@infradead.org> Date: 2021-10-15 06:33:27
On Fri, Sep 24, 2021 at 09:09:54PM +0800, Shiyang Ruan wrote:
quoted hunk
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 <redacted>
---
mm/memory-failure.c | 140 ++++++++++++++++++++++++--------------------
1 file changed, 76 insertions(+), 64 deletions(-)
Nit: an empty line here would be nice for readability.
+ if (pgmap->type == MEMORY_DEVICE_PRIVATE) {
+ /*
+ * TODO: Handle HMM pages which may need coordination
+ * with device-side memory.
+ */
+ return -EBUSY;
We've got rid of the HMM terminology for device private memory, so
I'd reword this update the comment to follow that while you're at it.
Otherwise looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
From: Christoph Hellwig <hch@infradead.org> Date: 2021-10-15 06:38:29
On Thu, Oct 14, 2021 at 12:24:50PM -0700, Darrick J. Wong wrote:
It feels a little dangerous to have page->mapping for shared storage
point to an actual address_space when there are really multiple
potential address_spaces out there. If the mm or dax folks are ok with
doing this this way then I'll live with it, but it seems like you'd want
to leave /some/ kind of marker once you know that the page has multiple
owners and therefore regular mm rmap via page->mapping won't work.
Yes, I thing poisoning page->mapping for the rmap enabled case seems
like a better idea.
On Fri, Sep 24, 2021 at 09:09:52PM +0800, Shiyang Ruan wrote:
quoted
In order to introduce dax holder registration, we need a write lock for
dax. Because of the rarity of notification failures and the infrequency
of registration events, it would be better to be a global lock rather
than per-device. So, change the current lock to rwsem and introduce a
write lock for registration.
Urgh, I totally thought dax_read_lock was a global lock on something
relating to the global dax_device state until I noticed this comment
above kill_dax():
/*
* Note, rcu is not protecting the liveness of dax_dev, rcu is ensuring
* that any fault handlers or operations that might have seen
* dax_alive(), have completed. Any operations that start after
* synchronize_srcu() has run will abort upon seeing !dax_alive().
*/
So dax_srcu ensures stability in the dax_device's ALIVE state while any
code that relies on that aliveness runs. As a side effect, it'll block
kill_dax (and I guess run_dax) while those functions run. It doesn't
protect any global state at all... but this isn't made obvious in the
API by (for example) passing the dax_device into dax_read_lock.
IOWs, It's not protecting against the dax_device getting freed or
anything resembling global state. So that's probably why you note above
that this /could/ be a per-device synchronization primitive, right?
If that's the case, then why shouldn't this be a per-device item? As
written here, any code that takes dax_write_lock() will block every dax
device in the system while it does some work on a single dax device.
Being an rwsem, it will also have to wait for every other dax device
access to complete before it can begin. That seems excessive,
particularly if in the future we start hooking up lots of pmem to a
single host.
I have more to say around kill_dax() below.
Shouldn't you take the dax_write_lock() around the clear_bit call to
maintain the behavior that kill_dax can't proceed until all the
functions that depend on DAXDEV_ALIVE state have finished?
Yes, I understood now. I'll change it to a per-device
percpu_rw_semaphore. The global rw_sem is not so good.
--
Thanks,
Ruan
On Fri, Sep 24, 2021 at 09:09:55PM +0800, Shiyang Ruan wrote:
quoted
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 <redacted>
---
drivers/nvdimm/pmem.c | 11 +++++++++++
include/linux/memremap.h | 9 +++++++++
mm/memory-failure.c | 14 ++++++++++++++
3 files changed, 34 insertions(+)
@@ -1605,6 +1605,20 @@ static int memory_failure_dev_pagemap(unsigned long pfn, int flags,if(!pgmap_pfn_valid(pgmap,pfn))gotoout;+/*+*Calldriver'simplementationtohandlethememoryfailure,otherwise+*fallbacktogenerichandler.+*/+if(pgmap->ops->memory_failure){+rc=pgmap->ops->memory_failure(pgmap,pfn,PAGE_SIZE,flags);+/*+*Fallbacktogenerichandlertooifoperationisnot+*supportedinsidethedriver/device/filesystem.+*/+if(rc!=EOPNOTSUPP)
-EOPNOTSUPP? (negative errno)
Yes, my mistake. Thanks for pointing out.
--
Thanks,
Ruan.
On Fri, Sep 24, 2021 at 09:09:57PM +0800, Shiyang Ruan wrote:
quoted
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 between mf_generic_kill_procs() is,
it accepts file's mapping,offset instead of struct page. Because
different file's mappings and offsets may share the same page in fsdax
mode. So, it is called when filesystem RMAP results are found.
Signed-off-by: Shiyang Ruan <redacted>
---
fs/dax.c | 10 ------
include/linux/dax.h | 9 +++++
include/linux/mm.h | 2 ++
mm/memory-failure.c | 83 ++++++++++++++++++++++++++++++++++++++++-----
4 files changed, 86 insertions(+), 18 deletions(-)
@@ -852,16 +852,6 @@ static void *dax_insert_entry(struct xa_state *xas,returnentry;}-staticinline-unsignedlongpgoff_address(pgoff_tpgoff,structvm_area_struct*vma)-{-unsignedlongaddress;--address=vma->vm_start+((pgoff-vma->vm_pgoff)<<PAGE_SHIFT);-VM_BUG_ON_VMA(address<vma->vm_start||address>=vma->vm_end,vma);-returnaddress;-}-/* Walk all mappings of a given index of a file and writeprotect them */staticvoiddax_entry_mkclean(structaddress_space*mapping,pgoff_tindex,unsignedlongpfn)
@@ -544,7 +549,32 @@ 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);
@@ -1503,6 +1533,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 offset of the range+ * @size: length of the range
It feels odd that one argument is in units of pgoff_t but the other is
in bytes.
The index is page aligned but @size may not be. I will explain it in
detail in the comments.
Hm. What flags will we be passing to the xfs_dax_notify_failure_fn?
Does XFS itself have to care about what's in the flags values, or is it
really just a magic cookie to be passed from the mm layer into the fs
and back to mf_dax_kill_procs?
Just to pass the flag from mm layer to mf_dax_kill_procs(). No one
inside this RMAP progress will care about or change it. As you
mentioned in the next patch, I think this should be named with a "mf_"
prefix to make it easier to understand.
--
Thanks,
Ruan.
On Fri, Sep 24, 2021 at 09:09:53PM +0800, Shiyang Ruan wrote:
quoted
To easily track filesystem from a pmem device, we introduce a holder for
dax_device structure, and also its operation. This holder is used to
remember who is using this dax_device:
- When it is the backend of a filesystem, the holder will be the
superblock of this filesystem.
- When this pmem device is one of the targets in a mapped device, the
holder will be this mapped device. In this case, the mapped device
has its own dax_device and it will follow the first rule. So that we
can finally track to the filesystem we needed.
The holder and holder_ops will be set when filesystem is being mounted,
or an target device is being activated.
Signed-off-by: Shiyang Ruan <redacted>
---
drivers/dax/super.c | 59 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/dax.h | 29 ++++++++++++++++++++++
2 files changed, 88 insertions(+)
@@ -374,6 +379,29 @@ int dax_zero_page_range(struct dax_device *dax_dev, pgoff_t pgoff,}EXPORT_SYMBOL_GPL(dax_zero_page_range);+intdax_holder_notify_failure(structdax_device*dax_dev,loff_toffset,+size_tsize,intflags)+{+intrc;++dax_read_lock();+if(!dax_alive(dax_dev)){+rc=-ENXIO;+gotoout;+}++if(!dax_dev->holder_data){+rc=-EOPNOTSUPP;+gotoout;+}++rc=dax_dev->holder_ops->notify_failure(dax_dev,offset,size,flags);
Shouldn't this check if dax_dev->holder_ops != NULL before dereferencing
it for the function call? Imagine an implementation that wants to
attach a ->notify_failure function to a dax_device, maintains its own
lookup table, and decides that it doesn't need to set holder_data.
(Or, imagine someone who writes a garbage into holder_data and *boom*)
My mistake. I should check @holder_ops instead of @holder_data.
How does the locking work here? If there's a media failure, we'll take
dax_rwsem and call ->notify_failure. If the ->notify_failure function
wants to access the pmem to handle the error by calling back into the
dax code, will that cause nested locking on dax_rwsem?
Won't for now. I have tested it with my simple testcases.
Jumping ahead a bit, I think the rmap btree accesses that the xfs
implementation performs can cause xfs_buf(fer) cache IO, which would
trigger that if the buffers aren't already in memory, if I'm reading
this correctly?
I didn't think of this case. But I think this uses read lock too. It
won't be blocked. Only dax_set_holder() takes write lock.
I guess this means that the holder has to detach itself before anyone
calls kill_dax, or else a dead dax device ends up with a dangling
reference to the holder?
Yes.
quoted
+}
+EXPORT_SYMBOL_GPL(dax_set_holder);
+
+void *dax_get_holder(struct dax_device *dax_dev)
+{
+ void *holder;
+
+ dax_read_lock();
+ if (!dax_alive(dax_dev)) {
+ dax_read_unlock();
+ return NULL;
+ }
+
+ holder = dax_dev->holder_data;
+ dax_read_unlock();
+ return holder;
+}
+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
Shouldn't size be u64 or something? Let's say that 8GB of your pmem go
bad, wouldn't you want a single call? Though I guess the current
implementation only goes a single page at a time, doesn't it?