diff --git a/fs/dax.c b/fs/dax.c index 289e6254aa30..87bed6de920d 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -378,6 +378,58 @@ static void dax_folio_make_shared(struct folio *folio) folio->share = 1; } +/** + * dax_folio_reset_order - Reset a compound DAX folio to order-0 pages + * @folio: The folio to reset + * + * Splits a compound folio back into individual order-0 pages, + * clearing compound state and restoring pgmap pointers. + * + * Returns: the original folio order (0 if already order-0) + */ +int dax_folio_reset_order(struct folio *folio) +{ + struct dev_pagemap *pgmap = page_pgmap(&folio->page); + int order = folio_order(folio); + + /* + * DAX maintains the invariant that folio->share != 0 only when + * folio->mapping == NULL (enforced by dax_folio_make_shared()). + * Equivalently: folio->mapping != NULL implies folio->share == 0. + * Callers ensure share has been decremented to zero before + * calling here, so unconditionally clearing both fields is + * correct. + */ + folio->mapping = NULL; + folio->share = 0; + + if (!order) { + /* + * Restore pgmap explicitly even for order-0 folios. For the + * dax_folio_put() caller this is a no-op (same value), but + * fsdev_clear_folio_state() may call this on folios that + * were previously compound and need pgmap re-established. + */ + folio->pgmap = pgmap; + return 0; + } + + folio_reset_order(folio); + + for (int i = 0; i < (1UL << order); i++) { + struct page *page = folio_page(folio, i); + struct folio *f = (struct folio *)page; + + ClearPageHead(page); + clear_compound_head(page); + f->mapping = NULL; + f->share = 0; + f->pgmap = pgmap; + } + + return order; +} + static inline unsigned long dax_folio_put(struct folio *folio) { unsigned long ref; @@ -391,28 +443,13 @@ static inline unsigned long dax_folio_put(struct folio *folio) if (ref) return ref; - folio->mapping = NULL; - order = folio_order(folio); - if (!order) - return 0; - folio_reset_order(folio); + order = dax_folio_reset_order(folio); + /* Debug check: verify refcounts are zero for all sub-folios */ for (i = 0; i < (1UL << order); i++) { - struct dev_pagemap *pgmap = page_pgmap(&folio->page); struct page *page = folio_page(folio, i); - struct folio *new_folio = (struct folio *)page; - ClearPageHead(page); - clear_compound_head(page); - - new_folio->mapping = NULL; - /* - * Reset pgmap which was over-written by - * prep_compound_page(). - */ - new_folio->pgmap = pgmap; - new_folio->share = 0; - WARN_ON_ONCE(folio_ref_count(new_folio)); + WARN_ON_ONCE(folio_ref_count((struct folio *)page)); } return ref; diff --git a/include/linux/dax.h b/include/linux/dax.h index bf103f317cac..73cfc1a7c8f1 100644 --- a/include/linux/dax.h +++ b/include/linux/dax.h @@ -153,6 +153,7 @@ static inline void fs_put_dax(struct dax_device *dax_dev, void *holder) #if IS_ENABLED(CONFIG_FS_DAX) int dax_writeback_mapping_range(struct address_space *mapping, struct dax_device *dax_dev, struct writeback_control *wbc); +int dax_folio_reset_order(struct folio *folio); 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);