| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (C) 2024, Alibaba Cloud | |
| */ | |
| #include "internal.h" | |
| #include <trace/events/erofs.h> | |
| struct erofs_fileio_rq { | |
| struct bio_vec bvecs[16]; | |
| struct bio bio; | |
| struct kiocb iocb; | |
| struct super_block *sb; | |
| refcount_t ref; | |
| }; | |
| struct erofs_fileio { | |
| struct erofs_map_blocks map; | |
| struct erofs_map_dev dev; | |
| struct erofs_fileio_rq *rq; | |
| }; | |
| static void erofs_fileio_ki_complete(struct kiocb *iocb, long ret) | |
| { | |
| struct erofs_fileio_rq *rq = | |
| container_of(iocb, struct erofs_fileio_rq, iocb); | |
| struct folio_iter fi; | |
| if (ret >= 0 && ret != rq->bio.bi_iter.bi_size) | |
| ret = -EIO; | |
| if (!rq->bio.bi_end_io) { | |
| bio_for_each_folio_all(fi, &rq->bio) { | |
| DBG_BUGON(folio_test_uptodate(fi.folio)); | |
| erofs_onlinefolio_end(fi.folio, ret < 0, false); | |
| } | |
| } else if (ret < 0 && !rq->bio.bi_status) { | |
| rq->bio.bi_status = errno_to_blk_status(ret); | |
| } | |
| bio_endio(&rq->bio); | |
| bio_uninit(&rq->bio); | |
| if (refcount_dec_and_test(&rq->ref)) | |
| kfree(rq); | |
| } | |
| static void erofs_fileio_rq_submit(struct erofs_fileio_rq *rq) | |
| { | |
| struct iov_iter iter; | |
| ssize_t ret; | |
| if (!rq) | |
| return; | |
| rq->iocb.ki_pos = rq->bio.bi_iter.bi_sector << SECTOR_SHIFT; | |
| rq->iocb.ki_ioprio = get_current_ioprio(); | |
| rq->iocb.ki_complete = erofs_fileio_ki_complete; | |
| if (test_opt(&EROFS_SB(rq->sb)->opt, DIRECT_IO) && | |
| rq->iocb.ki_filp->f_mode & FMODE_CAN_ODIRECT) | |
| rq->iocb.ki_flags = IOCB_DIRECT; | |
| iov_iter_bvec(&iter, ITER_DEST, rq->bvecs, rq->bio.bi_vcnt, | |
| rq->bio.bi_iter.bi_size); | |
| scoped_with_creds(rq->iocb.ki_filp->f_cred) | |
| ret = vfs_iocb_iter_read(rq->iocb.ki_filp, &rq->iocb, &iter); | |
| if (ret != -EIOCBQUEUED) | |
| erofs_fileio_ki_complete(&rq->iocb, ret); | |
| if (refcount_dec_and_test(&rq->ref)) | |
| kfree(rq); | |
| } | |
| static struct erofs_fileio_rq *erofs_fileio_rq_alloc(struct erofs_map_dev *mdev) | |
| { | |
| struct erofs_fileio_rq *rq = kzalloc_obj(*rq, GFP_KERNEL | __GFP_NOFAIL); | |
| bio_init(&rq->bio, NULL, rq->bvecs, ARRAY_SIZE(rq->bvecs), REQ_OP_READ); | |
| rq->iocb.ki_filp = mdev->m_dif->file; | |
| rq->sb = mdev->m_sb; | |
| refcount_set(&rq->ref, 2); | |
| return rq; | |
| } | |
| struct bio *erofs_fileio_bio_alloc(struct erofs_map_dev *mdev) | |
| { | |
| return &erofs_fileio_rq_alloc(mdev)->bio; | |
| } | |
| void erofs_fileio_submit_bio(struct bio *bio) | |
| { | |
| return erofs_fileio_rq_submit(container_of(bio, struct erofs_fileio_rq, | |
| bio)); | |
| } | |
| static int erofs_fileio_scan_folio(struct erofs_fileio *io, | |
| struct inode *inode, struct folio *folio) | |
| { | |
| struct erofs_map_blocks *map = &io->map; | |
| unsigned int cur = 0, end = folio_size(folio), len, attached = 0; | |
| loff_t pos = folio_pos(folio), ofs; | |
| int err = 0; | |
| erofs_onlinefolio_init(folio); | |
| while (cur < end) { | |
| if (!in_range(pos + cur, map->m_la, map->m_llen)) { | |
| map->m_la = pos + cur; | |
| map->m_llen = end - cur; | |
| err = erofs_map_blocks(inode, map); | |
| if (err) | |
| break; | |
| } | |
| ofs = folio_pos(folio) + cur - map->m_la; | |
| len = min_t(loff_t, map->m_llen - ofs, end - cur); | |
| if (map->m_flags & EROFS_MAP_META) { | |
| struct erofs_buf buf = __EROFS_BUF_INITIALIZER; | |
| void *src; | |
| src = erofs_read_metabuf(&buf, inode->i_sb, | |
| map->m_pa + ofs, erofs_inode_in_metabox(inode)); | |
| if (IS_ERR(src)) { | |
| err = PTR_ERR(src); | |
| break; | |
| } | |
| memcpy_to_folio(folio, cur, src, len); | |
| erofs_put_metabuf(&buf); | |
| } else if (!(map->m_flags & EROFS_MAP_MAPPED)) { | |
| folio_zero_segment(folio, cur, cur + len); | |
| attached = 0; | |
| } else { | |
| if (io->rq && (map->m_pa + ofs != io->dev.m_pa || | |
| map->m_deviceid != io->dev.m_deviceid)) { | |
| io_retry: | |
| erofs_fileio_rq_submit(io->rq); | |
| io->rq = NULL; | |
| } | |
| if (!io->rq) { | |
| io->dev = (struct erofs_map_dev) { | |
| .m_pa = io->map.m_pa + ofs, | |
| .m_deviceid = io->map.m_deviceid, | |
| }; | |
| err = erofs_map_dev(inode->i_sb, &io->dev); | |
| if (err) | |
| break; | |
| io->rq = erofs_fileio_rq_alloc(&io->dev); | |
| io->rq->bio.bi_iter.bi_sector = | |
| (io->dev.m_dif->fsoff + io->dev.m_pa) >> 9; | |
| attached = 0; | |
| } | |
| if (!bio_add_folio(&io->rq->bio, folio, len, cur)) | |
| goto io_retry; | |
| if (!attached++) | |
| erofs_onlinefolio_split(folio); | |
| io->dev.m_pa += len; | |
| } | |
| cur += len; | |
| } | |
| erofs_onlinefolio_end(folio, err, false); | |
| return err; | |
| } | |
| static int erofs_fileio_read_folio(struct file *file, struct folio *folio) | |
| { | |
| bool need_iput; | |
| struct inode *realinode = erofs_real_inode(folio_inode(folio), &need_iput); | |
| struct erofs_fileio io = {}; | |
| int err; | |
| trace_erofs_read_folio(realinode, folio, true); | |
| err = erofs_fileio_scan_folio(&io, realinode, folio); | |
| erofs_fileio_rq_submit(io.rq); | |
| if (need_iput) | |
| iput(realinode); | |
| return err; | |
| } | |
| static void erofs_fileio_readahead(struct readahead_control *rac) | |
| { | |
| bool need_iput; | |
| struct inode *realinode = erofs_real_inode(rac->mapping->host, &need_iput); | |
| struct erofs_fileio io = {}; | |
| struct folio *folio; | |
| int err; | |
| trace_erofs_readahead(realinode, readahead_index(rac), | |
| readahead_count(rac), true); | |
| while ((folio = readahead_folio(rac))) { | |
| err = erofs_fileio_scan_folio(&io, realinode, folio); | |
| if (err && err != -EINTR) | |
| erofs_err(realinode->i_sb, "readahead error at folio %lu @ nid %llu", | |
| folio->index, EROFS_I(realinode)->nid); | |
| } | |
| erofs_fileio_rq_submit(io.rq); | |
| if (need_iput) | |
| iput(realinode); | |
| } | |
| const struct address_space_operations erofs_fileio_aops = { | |
| .read_folio = erofs_fileio_read_folio, | |
| .readahead = erofs_fileio_readahead, | |
| }; | |
| int erofs_read_meta_folio(struct file *file, struct folio *folio) | |
| { | |
| struct erofs_fileio io = { | |
| .dev = { .m_pa = folio_pos(folio), }, | |
| }; | |
| struct inode *inode = folio_inode(folio); | |
| int err; | |
| err = erofs_map_dev(inode->i_sb, &io.dev); | |
| if (err) | |
| return err; | |
| io.rq = erofs_fileio_rq_alloc(&io.dev); | |
| io.rq->bio.bi_iter.bi_sector = | |
| (io.dev.m_dif->fsoff + io.dev.m_pa) >> 9; | |
| erofs_onlinefolio_init(folio); | |
| bio_add_folio_nofail(&io.rq->bio, folio, folio_size(folio), 0); | |
| erofs_fileio_rq_submit(io.rq); | |
| return 0; | |
| } |