Message ID | 20221211213111.30085-4-fmdefrancesco@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | fs/ufs: replace kmap() with kmap_local_page() | expand |
On Sun, Dec 11, 2022 at 10:31:11PM +0100, Fabio M. De Francesco wrote: > +/* > + * Calls to ufs_get_page()/ufs_put_page() must be nested according to the > + * rules documented in kmap_local_page()/kunmap_local(). > + * > + * NOTE: ufs_find_entry() and ufs_dotdot() act as calls to ufs_get_page() > + * and must be treated accordingly for nesting purposes. > + */ > static void *ufs_get_page(struct inode *dir, unsigned long n, struct page **page) > { > + char *kaddr; > + > struct address_space *mapping = dir->i_mapping; > *page = read_mapping_page(mapping, n, NULL); > if (!IS_ERR(*page)) { > - kmap(*page); > + kmap_local_page(*page); > if (unlikely(!PageChecked(*page))) { > - if (!ufs_check_page(*page)) > + if (!ufs_check_page(*page, kaddr)) Er... Building the patched tree is occasionally useful. Here kaddr is obviously uninitialized and compiler would've probably caught that. And return value of kmap_local_page() is lost, which is related to the previous issue ;-) > goto fail; > } > } > - return page; > + return *page; Hell, no. Callers expect the pointer to the first byte of your page. What it should return is kaddr. > @@ -388,7 +406,8 @@ int ufs_add_link(struct dentry *dentry, struct inode *inode) > mark_inode_dirty(dir); > /* OFFSET_CACHE */ > out_put: > - ufs_put_page(page); > + ufs_put_page(page, kaddr); > + return 0; > out_unlock: > unlock_page(page); > goto out_put; That can't be right. Places like if (err) goto out_unlock; do not expect err to be lost. You end up returning 0 now. Something strange happened here (in the previous commit, perhaps?)
On domenica 11 dicembre 2022 23:39:44 CET Al Viro wrote: > On Sun, Dec 11, 2022 at 10:31:11PM +0100, Fabio M. De Francesco wrote: > > +/* > > + * Calls to ufs_get_page()/ufs_put_page() must be nested according to the > > + * rules documented in kmap_local_page()/kunmap_local(). > > + * > > + * NOTE: ufs_find_entry() and ufs_dotdot() act as calls to ufs_get_page() > > + * and must be treated accordingly for nesting purposes. > > + */ > > > > static void *ufs_get_page(struct inode *dir, unsigned long n, struct page > > **page) { > > > > + char *kaddr; > > + > > > > struct address_space *mapping = dir->i_mapping; > > *page = read_mapping_page(mapping, n, NULL); > > if (!IS_ERR(*page)) { > > > > - kmap(*page); > > + kmap_local_page(*page); > > > > if (unlikely(!PageChecked(*page))) { > > > > - if (!ufs_check_page(*page)) > > + if (!ufs_check_page(*page, kaddr)) > > Er... Building the patched tree is occasionally useful. > I don't know why gcc didn't catch this (gcc version 12.2.1 20221020 [revision 0aaef83351473e8f4eb774f8f999bbe87a4866d7] (SUSE Linux)): setarch i686 make ARCH=i386 O=../build-linux-x86_32-debug/ -j12 make[1]: Entering directory '/usr/src/git/kernels/build-linux-x86_32-debug' GEN Makefile DESCEND bpf/resolve_btfids CALL /usr/src/git/kernels/linux/scripts/checksyscalls.sh CC [M] fs/ufs/dir.o LD [M] fs/ufs/ufs.o MODPOST Module.symvers Kernel: arch/x86/boot/bzImage is ready (#3) LD [M] fs/ufs/ufs.ko BTF [M] fs/ufs/ufs.ko make[1]: Leaving directory '/usr/src/git/kernels/build-linux-x86_32-debug' > Here kaddr is obviously uninitialized and compiler would've > probably caught that. > I'd better use option W=1 next time. > > And return value of kmap_local_page() is lost, which > is related to the previous issue ;-) > > > goto fail; > > > > } > > > > } > > > > - return page; > > + return *page; > > Hell, no. Callers expect the pointer to the first byte of > your page. What it should return is kaddr. > I'm sorry that I entirely missed this :-( > > > @@ -388,7 +406,8 @@ int ufs_add_link(struct dentry *dentry, struct inode > > *inode)> > > mark_inode_dirty(dir); > > /* OFFSET_CACHE */ > > > > out_put: > > - ufs_put_page(page); > > + ufs_put_page(page, kaddr); > > + return 0; > > > > out_unlock: > > unlock_page(page); > > goto out_put; > > That can't be right. Places like > if (err) > goto out_unlock; > do not expect err to be lost. You end up returning 0 now. Something strange > happened here (in the previous commit, perhaps?) > I don't yet know. Maybe that it is related to a copy-paste error or something like that... As said, I'll send next version ASAP. Again thanks for your kind help, Fabio
Hi Fabio, Thank you for the patch! Perhaps something to improve: [auto build test WARNING on viro-vfs/for-next] [also build test WARNING on linus/master v6.1 next-20221208] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Fabio-M-De-Francesco/fs-ufs-replace-kmap-with-kmap_local_page/20221212-053252 base: https://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs.git for-next patch link: https://lore.kernel.org/r/20221211213111.30085-4-fmdefrancesco%40gmail.com patch subject: [PATCH 3/3] fs/ufs: Replace kmap() with kmap_local_page() config: s390-buildonly-randconfig-r006-20221211 compiler: clang version 16.0.0 (https://github.com/llvm/llvm-project 6e4cea55f0d1104408b26ac574566a0e4de48036) reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # install s390 cross compiling tool for clang build # apt-get install binutils-s390x-linux-gnu # https://github.com/intel-lab-lkp/linux/commit/8d46ed8ea1eddf657427f71a7d7f52a7767186dd git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Fabio-M-De-Francesco/fs-ufs-replace-kmap-with-kmap_local_page/20221212-053252 git checkout 8d46ed8ea1eddf657427f71a7d7f52a7767186dd # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash fs/ufs/ If you fix the issue, kindly add following tag where applicable | Reported-by: kernel test robot <lkp@intel.com> All warnings (new ones prefixed by >>): >> fs/ufs/dir.c:210:22: warning: variable 'kaddr' is uninitialized when used here [-Wuninitialized] ufs_put_page(*page, kaddr); ^~~~~ fs/ufs/dir.c:196:13: note: initialize the variable 'kaddr' to silence this warning char *kaddr; ^ = NULL 1 warning generated. Kconfig warnings: (for reference only) WARNING: unmet direct dependencies detected for DEBUG_MAPLE_TREE Depends on [n]: DEBUG_KERNEL [=n] Selected by [m]: - TEST_MAPLE_TREE [=m] && RUNTIME_TESTING_MENU [=y] vim +/kaddr +210 fs/ufs/dir.c 186 187 /* 188 * Calls to ufs_get_page()/ufs_put_page() must be nested according to the 189 * rules documented in kmap_local_page()/kunmap_local(). 190 * 191 * NOTE: ufs_find_entry() and ufs_dotdot() act as calls to ufs_get_page() 192 * and must be treated accordingly for nesting purposes. 193 */ 194 static void *ufs_get_page(struct inode *dir, unsigned long n, struct page **page) 195 { 196 char *kaddr; 197 198 struct address_space *mapping = dir->i_mapping; 199 *page = read_mapping_page(mapping, n, NULL); 200 if (!IS_ERR(*page)) { 201 kmap_local_page(*page); 202 if (unlikely(!PageChecked(*page))) { 203 if (!ufs_check_page(*page, kaddr)) 204 goto fail; 205 } 206 } 207 return *page; 208 209 fail: > 210 ufs_put_page(*page, kaddr); 211 return ERR_PTR(-EIO); 212 } 213
On Mon, Dec 12, 2022 at 08:52:47AM +0800, kernel test robot wrote: > >> fs/ufs/dir.c:210:22: warning: variable 'kaddr' is uninitialized when used here [-Wuninitialized] > ufs_put_page(*page, kaddr); > ^~~~~ > fs/ufs/dir.c:196:13: note: initialize the variable 'kaddr' to silence this warning > char *kaddr; > ^ > = NULL > 1 warning generated. Warning is right, suggestion - worse than useless...
diff --git a/fs/ufs/dir.c b/fs/ufs/dir.c index bb6b29ce1d3a..fd57d8171c88 100644 --- a/fs/ufs/dir.c +++ b/fs/ufs/dir.c @@ -61,9 +61,9 @@ static int ufs_commit_chunk(struct page *page, loff_t pos, unsigned len) return err; } -static inline void ufs_put_page(struct page *page) +static inline void ufs_put_page(struct page *page, void *page_addr) { - kunmap(page); + kunmap((void *)((unsigned long)page_addr & PAGE_MASK)); put_page(page); } @@ -76,7 +76,7 @@ ino_t ufs_inode_by_name(struct inode *dir, const struct qstr *qstr) de = ufs_find_entry(dir, qstr, &page); if (de) { res = fs32_to_cpu(dir->i_sb, de->d_ino); - ufs_put_page(page); + ufs_put_page(page, de); } return res; } @@ -99,18 +99,17 @@ void ufs_set_link(struct inode *dir, struct ufs_dir_entry *de, ufs_set_de_type(dir->i_sb, de, inode->i_mode); err = ufs_commit_chunk(page, pos, len); - ufs_put_page(page); + ufs_put_page(page, de); if (update_times) dir->i_mtime = dir->i_ctime = current_time(dir); mark_inode_dirty(dir); } -static bool ufs_check_page(struct page *page) +static bool ufs_check_page(struct page *page, char *kaddr) { struct inode *dir = page->mapping->host; struct super_block *sb = dir->i_sb; - char *kaddr = page_address(page); unsigned offs, rec_len; unsigned limit = PAGE_SIZE; const unsigned chunk_mask = UFS_SB(sb)->s_uspi->s_dirblksize - 1; @@ -185,21 +184,30 @@ static bool ufs_check_page(struct page *page) return false; } +/* + * Calls to ufs_get_page()/ufs_put_page() must be nested according to the + * rules documented in kmap_local_page()/kunmap_local(). + * + * NOTE: ufs_find_entry() and ufs_dotdot() act as calls to ufs_get_page() + * and must be treated accordingly for nesting purposes. + */ static void *ufs_get_page(struct inode *dir, unsigned long n, struct page **page) { + char *kaddr; + struct address_space *mapping = dir->i_mapping; *page = read_mapping_page(mapping, n, NULL); if (!IS_ERR(*page)) { - kmap(*page); + kmap_local_page(*page); if (unlikely(!PageChecked(*page))) { - if (!ufs_check_page(*page)) + if (!ufs_check_page(*page, kaddr)) goto fail; } } - return page; + return *page; fail: - ufs_put_page(*page); + ufs_put_page(*page, kaddr); return ERR_PTR(-EIO); } @@ -225,6 +233,13 @@ ufs_next_entry(struct super_block *sb, struct ufs_dir_entry *p) fs16_to_cpu(sb, p->d_reclen)); } +/* + * Calls to ufs_get_page()/ufs_put_page() must be nested according to the + * rules documented in kmap_local_page()/kunmap_local(). + * + * ufs_dotdot() acts as a call to ufs_get_page() and must be treated + * accordingly for nesting purposes. + */ struct ufs_dir_entry *ufs_dotdot(struct inode *dir, struct page **p) { struct ufs_dir_entry *de = ufs_get_page(dir, 0, p); @@ -236,12 +251,15 @@ struct ufs_dir_entry *ufs_dotdot(struct inode *dir, struct page **p) } /* - * ufs_find_entry() + * Finds an entry in the specified directory with the wanted name. It returns a + * pointer to the directory's entry. The page in which the entry was found is + * in the res_page out parameter. The page is returned mapped and unlocked. + * The entry is guaranteed to be valid. + * + * On Success ufs_put_page() should be called on *res_page. * - * finds an entry in the specified directory with the wanted name. It - * returns the page in which the entry was found, and the entry itself - * (as a parameter - res_dir). Page is returned mapped and unlocked. - * Entry is guaranteed to be valid. + * ufs_find_entry() acts as a call to ufs_get_page() and must be treated + * accordingly for nesting purposes. */ struct ufs_dir_entry *ufs_find_entry(struct inode *dir, const struct qstr *qstr, struct page **res_page) @@ -280,7 +298,7 @@ struct ufs_dir_entry *ufs_find_entry(struct inode *dir, const struct qstr *qstr, goto found; de = ufs_next_entry(sb, de); } - ufs_put_page(page); + ufs_put_page(page, kaddr); } if (++n >= npages) n = 0; @@ -358,7 +376,7 @@ int ufs_add_link(struct dentry *dentry, struct inode *inode) de = (struct ufs_dir_entry *) ((char *) de + rec_len); } unlock_page(page); - ufs_put_page(page); + ufs_put_page(page, kaddr); } BUG(); return -EINVAL; @@ -388,7 +406,8 @@ int ufs_add_link(struct dentry *dentry, struct inode *inode) mark_inode_dirty(dir); /* OFFSET_CACHE */ out_put: - ufs_put_page(page); + ufs_put_page(page, kaddr); + return 0; out_unlock: unlock_page(page); goto out_put; @@ -465,13 +484,13 @@ ufs_readdir(struct file *file, struct dir_context *ctx) ufs_get_de_namlen(sb, de), fs32_to_cpu(sb, de->d_ino), d_type)) { - ufs_put_page(page); + ufs_put_page(page, kaddr); return 0; } } ctx->pos += fs16_to_cpu(sb, de->d_reclen); } - ufs_put_page(page); + ufs_put_page(page, kaddr); } return 0; } @@ -482,10 +501,10 @@ ufs_readdir(struct file *file, struct dir_context *ctx) * previous entry. */ int ufs_delete_entry(struct inode *inode, struct ufs_dir_entry *dir, - struct page * page) + struct page *page) { struct super_block *sb = inode->i_sb; - char *kaddr = page_address(page); + char *kaddr = (char *)((unsigned long)dir & PAGE_MASK); unsigned int from = offset_in_page(dir) & ~(UFS_SB(sb)->s_uspi->s_dirblksize - 1); unsigned int to = offset_in_page(dir) + fs16_to_cpu(sb, dir->d_reclen); loff_t pos; @@ -524,7 +543,7 @@ int ufs_delete_entry(struct inode *inode, struct ufs_dir_entry *dir, inode->i_ctime = inode->i_mtime = current_time(inode); mark_inode_dirty(inode); out: - ufs_put_page(page); + ufs_put_page(page, kaddr); UFSD("EXIT\n"); return err; } @@ -548,8 +567,7 @@ int ufs_make_empty(struct inode * inode, struct inode *dir) goto fail; } - kmap(page); - base = (char*)page_address(page); + base = kmap_local_page(page); memset(base, 0, PAGE_SIZE); de = (struct ufs_dir_entry *) base; @@ -566,7 +584,7 @@ int ufs_make_empty(struct inode * inode, struct inode *dir) de->d_reclen = cpu_to_fs16(sb, chunk_size - UFS_DIR_REC_LEN(1)); ufs_set_de_namlen(sb, de, 2); strcpy (de->d_name, ".."); - kunmap(page); + kunmap_local(base); err = ufs_commit_chunk(page, 0, chunk_size); fail: @@ -582,9 +600,9 @@ int ufs_empty_dir(struct inode * inode) struct super_block *sb = inode->i_sb; struct page *page = NULL; unsigned long i, npages = dir_pages(inode); + char *kaddr; for (i = 0; i < npages; i++) { - char *kaddr; struct ufs_dir_entry *de; kaddr = ufs_get_page(inode, i, &page); @@ -617,12 +635,12 @@ int ufs_empty_dir(struct inode * inode) } de = ufs_next_entry(sb, de); } - ufs_put_page(page); + ufs_put_page(page, kaddr); } return 1; not_empty: - ufs_put_page(page); + ufs_put_page(page, kaddr); return 0; }
kmap() is being deprecated in favor of kmap_local_page(). There are two main problems with kmap(): (1) It comes with an overhead as the mapping space is restricted and protected by a global lock for synchronization and (2) it also requires global TLB invalidation when the kmap’s pool wraps and it might block when the mapping space is fully utilized until a slot becomes available. With kmap_local_page() the mappings are per thread, CPU local, can take page faults, and can be called from any context (including interrupts). It is faster than kmap() in kernels with HIGHMEM enabled. Furthermore, the tasks can be preempted and, when they are scheduled to run again, the kernel virtual addresses are restored and still valid. Since its use in fs/ufs is safe everywhere, it should be preferred. Therefore, replace kmap() with kmap_local_page() in fs/ufs. kunmap_local() requires the mapping address, so return that address from ufs_get_page() to be used in ufs_put_page(). Suggested-by: Al Viro <viro@zeniv.linux.org.uk> Suggested-by: Ira Weiny <ira.weiny@intel.com> Signed-off-by: Fabio M. De Francesco <fmdefrancesco@gmail.com> --- fs/ufs/dir.c | 76 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 29 deletions(-)