/* * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /************************************************************************/ /* */ /* PROJECT : exFAT & FAT12/16/32 File System */ /* FILE : blkdev.c */ /* PURPOSE : sdFAT Block Device Driver Glue Layer */ /* */ /*----------------------------------------------------------------------*/ /* NOTES */ /* */ /************************************************************************/ #include #include #include #include "sdfat.h" /*----------------------------------------------------------------------*/ /* Constant & Macro Definitions */ /*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/ /* Global Variable Definitions */ /*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/ /* Local Variable Definitions */ /*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/ /* FUNCTIONS WHICH HAS KERNEL VERSION DEPENDENCY */ /************************************************************************/ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0) /* EMPTY */ #else /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) */ static struct backing_dev_info *inode_to_bdi(struct inode *bd_inode) { return bd_inode->i_mapping->backing_dev_info; } #endif /*======================================================================*/ /* Function Definitions */ /*======================================================================*/ s32 bdev_open_dev(struct super_block *sb) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); if (fsi->bd_opened) return 0; fsi->bd_opened = true; return 0; } s32 bdev_close_dev(struct super_block *sb) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); fsi->bd_opened = false; return 0; } static inline s32 block_device_ejected(struct super_block *sb) { struct inode *bd_inode = sb->s_bdev->bd_inode; struct backing_dev_info *bdi = inode_to_bdi(bd_inode); return (bdi->dev == NULL); } s32 bdev_check_bdi_valid(struct super_block *sb) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); if (block_device_ejected(sb)) { if (!(fsi->prev_eio & SDFAT_EIO_BDI)) { fsi->prev_eio |= SDFAT_EIO_BDI; sdfat_log_msg(sb, KERN_ERR, "%s: block device is " "eliminated.(bdi:%p)", __func__, sb->s_bdi); } return -ENXIO; } return 0; } #if IS_BUILTIN(CONFIG_SDFAT_FS) static void __bdev_readahead(struct super_block *sb, u64 secno, u64 num_secs) { u32 sects_per_page = (PAGE_SIZE >> sb->s_blocksize_bits); struct blk_plug plug; u64 i; blk_start_plug(&plug); for (i = 0; i < num_secs; i++) { if (i && !(i & (sects_per_page - 1))) blk_flush_plug(current); sb_breadahead(sb, (sector_t)(secno + i)); } blk_finish_plug(&plug); } #else static void __bdev_readahead(struct super_block *sb, u64 secno, u64 num_secs) { u64 i; for (i = 0; i < num_secs; i++) sb_breadahead(sb, (sector_t)(secno + i)); } #endif /* Make a readahead request */ s32 bdev_readahead(struct super_block *sb, u64 secno, u64 num_secs) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); if (!fsi->bd_opened) return -EIO; __bdev_readahead(sb, secno, num_secs); return 0; } s32 bdev_mread(struct super_block *sb, u64 secno, struct buffer_head **bh, u64 num_secs, s32 read) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); u8 blksize_bits = sb->s_blocksize_bits; #ifdef CONFIG_SDFAT_DBG_IOCTL struct sdfat_sb_info *sbi = SDFAT_SB(sb); long flags = sbi->debug_flags; if (flags & SDFAT_DEBUGFLAGS_ERROR_RW) return -EIO; #endif /* CONFIG_SDFAT_DBG_IOCTL */ if (!fsi->bd_opened) return -EIO; brelse(*bh); if (read) *bh = __bread(sb->s_bdev, (sector_t)secno, num_secs << blksize_bits); else *bh = __getblk(sb->s_bdev, (sector_t)secno, num_secs << blksize_bits); /* read successfully */ if (*bh) return 0; /* * patch 1.2.4 : reset ONCE warning message per volume. */ if (!(fsi->prev_eio & SDFAT_EIO_READ)) { fsi->prev_eio |= SDFAT_EIO_READ; sdfat_log_msg(sb, KERN_ERR, "%s: No bh. I/O error.", __func__); sdfat_debug_warn_on(1); } return -EIO; } s32 bdev_mwrite(struct super_block *sb, u64 secno, struct buffer_head *bh, u64 num_secs, s32 sync) { u64 count; struct buffer_head *bh2; FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); #ifdef CONFIG_SDFAT_DBG_IOCTL struct sdfat_sb_info *sbi = SDFAT_SB(sb); long flags = sbi->debug_flags; if (flags & SDFAT_DEBUGFLAGS_ERROR_RW) return -EIO; #endif /* CONFIG_SDFAT_DBG_IOCTL */ if (!fsi->bd_opened) return -EIO; if (secno == bh->b_blocknr) { set_buffer_uptodate(bh); mark_buffer_dirty(bh); if (sync && (sync_dirty_buffer(bh) != 0)) return -EIO; } else { count = num_secs << sb->s_blocksize_bits; bh2 = __getblk(sb->s_bdev, (sector_t)secno, count); if (!bh2) goto no_bh; lock_buffer(bh2); memcpy(bh2->b_data, bh->b_data, count); set_buffer_uptodate(bh2); mark_buffer_dirty(bh2); unlock_buffer(bh2); if (sync && (sync_dirty_buffer(bh2) != 0)) { __brelse(bh2); goto no_bh; } __brelse(bh2); } return 0; no_bh: /* * patch 1.2.4 : reset ONCE warning message per volume. */ if (!(fsi->prev_eio & SDFAT_EIO_WRITE)) { fsi->prev_eio |= SDFAT_EIO_WRITE; sdfat_log_msg(sb, KERN_ERR, "%s: No bh. I/O error.", __func__); sdfat_debug_warn_on(1); } return -EIO; } s32 bdev_sync_all(struct super_block *sb) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); #ifdef CONFIG_SDFAT_DBG_IOCTL struct sdfat_sb_info *sbi = SDFAT_SB(sb); long flags = sbi->debug_flags; if (flags & SDFAT_DEBUGFLAGS_ERROR_RW) return -EIO; #endif /* CONFIG_SDFAT_DBG_IOCTL */ if (!fsi->bd_opened) return -EIO; return sync_blockdev(sb->s_bdev); } /* * Sector Read/Write Functions */ s32 read_sect(struct super_block *sb, u64 sec, struct buffer_head **bh, s32 read) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); BUG_ON(!bh); if ((sec >= fsi->num_sectors) && (fsi->num_sectors > 0)) { sdfat_fs_error_ratelimit(sb, "%s: out of range (sect:%llu)", __func__, sec); return -EIO; } if (bdev_mread(sb, sec, bh, 1, read)) { sdfat_fs_error_ratelimit(sb, "%s: I/O error (sect:%llu)", __func__, sec); return -EIO; } return 0; } s32 write_sect(struct super_block *sb, u64 sec, struct buffer_head *bh, s32 sync) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); BUG_ON(!bh); if ((sec >= fsi->num_sectors) && (fsi->num_sectors > 0)) { sdfat_fs_error_ratelimit(sb, "%s: out of range (sect:%llu)", __func__, sec); return -EIO; } if (bdev_mwrite(sb, sec, bh, 1, sync)) { sdfat_fs_error_ratelimit(sb, "%s: I/O error (sect:%llu)", __func__, sec); return -EIO; } return 0; } s32 read_msect(struct super_block *sb, u64 sec, struct buffer_head **bh, u64 num_secs, s32 read) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); BUG_ON(!bh); if (((sec+num_secs) > fsi->num_sectors) && (fsi->num_sectors > 0)) { sdfat_fs_error_ratelimit(sb, "%s: out of range(sect:%llu len:%llu)", __func__, sec, num_secs); return -EIO; } if (bdev_mread(sb, sec, bh, num_secs, read)) { sdfat_fs_error_ratelimit(sb, "%s: I/O error (sect:%llu len:%llu)", __func__, sec, num_secs); return -EIO; } return 0; } s32 write_msect(struct super_block *sb, u64 sec, struct buffer_head *bh, u64 num_secs, s32 sync) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); BUG_ON(!bh); if (((sec+num_secs) > fsi->num_sectors) && (fsi->num_sectors > 0)) { sdfat_fs_error_ratelimit(sb, "%s: out of range(sect:%llu len:%llu)", __func__, sec, num_secs); return -EIO; } if (bdev_mwrite(sb, sec, bh, num_secs, sync)) { sdfat_fs_error_ratelimit(sb, "%s: I/O error (sect:%llu len:%llu)", __func__, sec, num_secs); return -EIO; } return 0; } static inline void __blkdev_write_bhs(struct buffer_head **bhs, s32 nr_bhs) { s32 i; for (i = 0; i < nr_bhs; i++) write_dirty_buffer(bhs[i], WRITE); } static inline s32 __blkdev_sync_bhs(struct buffer_head **bhs, s32 nr_bhs) { s32 i, err = 0; for (i = 0; i < nr_bhs; i++) { wait_on_buffer(bhs[i]); if (!err && !buffer_uptodate(bhs[i])) err = -EIO; } return err; } static inline s32 __buffer_zeroed(struct super_block *sb, u64 blknr, u64 num_secs) { struct buffer_head *bhs[MAX_BUF_PER_PAGE]; s32 nr_bhs = MAX_BUF_PER_PAGE; u64 last_blknr = blknr + num_secs; s32 err, i, n; struct blk_plug plug; /* Zeroing the unused blocks on this cluster */ n = 0; blk_start_plug(&plug); while (blknr < last_blknr) { bhs[n] = sb_getblk(sb, (sector_t)blknr); if (!bhs[n]) { err = -ENOMEM; blk_finish_plug(&plug); goto error; } memset(bhs[n]->b_data, 0, sb->s_blocksize); set_buffer_uptodate(bhs[n]); mark_buffer_dirty(bhs[n]); n++; blknr++; if (blknr == last_blknr) break; if (n == nr_bhs) { __blkdev_write_bhs(bhs, n); for (i = 0; i < n; i++) brelse(bhs[i]); n = 0; } } __blkdev_write_bhs(bhs, n); blk_finish_plug(&plug); err = __blkdev_sync_bhs(bhs, n); if (err) goto error; for (i = 0; i < n; i++) brelse(bhs[i]); return 0; error: EMSG("%s: failed zeroed sect %llu\n", __func__, blknr); for (i = 0; i < n; i++) bforget(bhs[i]); return err; } s32 write_msect_zero(struct super_block *sb, u64 sec, u64 num_secs) { FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); if (((sec+num_secs) > fsi->num_sectors) && (fsi->num_sectors > 0)) { sdfat_fs_error_ratelimit(sb, "%s: out of range(sect:%llu len:%llu)", __func__, sec, num_secs); return -EIO; } /* Just return -EAGAIN if it is failed */ if (__buffer_zeroed(sb, sec, num_secs)) return -EAGAIN; return 0; } /* end of write_msect_zero */ /* end of blkdev.c */