diff options
Diffstat (limited to 'fs/block_dev.c')
| -rw-r--r-- | fs/block_dev.c | 68 | 
1 files changed, 66 insertions, 2 deletions
diff --git a/fs/block_dev.c b/fs/block_dev.c index 38e721b35d4..b3c1d3dae77 100644 --- a/fs/block_dev.c +++ b/fs/block_dev.c @@ -116,6 +116,8 @@ EXPORT_SYMBOL(invalidate_bdev);  int set_blocksize(struct block_device *bdev, int size)  { +	struct address_space *mapping; +  	/* Size must be a power of two, and between 512 and PAGE_SIZE */  	if (size > PAGE_SIZE || size < 512 || !is_power_of_2(size))  		return -EINVAL; @@ -124,6 +126,19 @@ int set_blocksize(struct block_device *bdev, int size)  	if (size < bdev_logical_block_size(bdev))  		return -EINVAL; +	/* Prevent starting I/O or mapping the device */ +	percpu_down_write(&bdev->bd_block_size_semaphore); + +	/* Check that the block device is not memory mapped */ +	mapping = bdev->bd_inode->i_mapping; +	mutex_lock(&mapping->i_mmap_mutex); +	if (mapping_mapped(mapping)) { +		mutex_unlock(&mapping->i_mmap_mutex); +		percpu_up_write(&bdev->bd_block_size_semaphore); +		return -EBUSY; +	} +	mutex_unlock(&mapping->i_mmap_mutex); +  	/* Don't change the size if it is same as current */  	if (bdev->bd_block_size != size) {  		sync_blockdev(bdev); @@ -131,6 +146,9 @@ int set_blocksize(struct block_device *bdev, int size)  		bdev->bd_inode->i_blkbits = blksize_bits(size);  		kill_bdev(bdev);  	} + +	percpu_up_write(&bdev->bd_block_size_semaphore); +  	return 0;  } @@ -441,6 +459,12 @@ static struct inode *bdev_alloc_inode(struct super_block *sb)  	struct bdev_inode *ei = kmem_cache_alloc(bdev_cachep, GFP_KERNEL);  	if (!ei)  		return NULL; + +	if (unlikely(percpu_init_rwsem(&ei->bdev.bd_block_size_semaphore))) { +		kmem_cache_free(bdev_cachep, ei); +		return NULL; +	} +  	return &ei->vfs_inode;  } @@ -449,6 +473,8 @@ static void bdev_i_callback(struct rcu_head *head)  	struct inode *inode = container_of(head, struct inode, i_rcu);  	struct bdev_inode *bdi = BDEV_I(inode); +	percpu_free_rwsem(&bdi->bdev.bd_block_size_semaphore); +  	kmem_cache_free(bdev_cachep, bdi);  } @@ -1567,6 +1593,22 @@ static long block_ioctl(struct file *file, unsigned cmd, unsigned long arg)  	return blkdev_ioctl(bdev, mode, cmd, arg);  } +ssize_t blkdev_aio_read(struct kiocb *iocb, const struct iovec *iov, +			unsigned long nr_segs, loff_t pos) +{ +	ssize_t ret; +	struct block_device *bdev = I_BDEV(iocb->ki_filp->f_mapping->host); + +	percpu_down_read(&bdev->bd_block_size_semaphore); + +	ret = generic_file_aio_read(iocb, iov, nr_segs, pos); + +	percpu_up_read(&bdev->bd_block_size_semaphore); + +	return ret; +} +EXPORT_SYMBOL_GPL(blkdev_aio_read); +  /*   * Write data to the block device.  Only intended for the block device itself   * and the raw driver which basically is a fake block device. @@ -1578,12 +1620,16 @@ ssize_t blkdev_aio_write(struct kiocb *iocb, const struct iovec *iov,  			 unsigned long nr_segs, loff_t pos)  {  	struct file *file = iocb->ki_filp; +	struct block_device *bdev = I_BDEV(file->f_mapping->host);  	struct blk_plug plug;  	ssize_t ret;  	BUG_ON(iocb->ki_pos != pos);  	blk_start_plug(&plug); + +	percpu_down_read(&bdev->bd_block_size_semaphore); +  	ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);  	if (ret > 0 || ret == -EIOCBQUEUED) {  		ssize_t err; @@ -1592,11 +1638,29 @@ ssize_t blkdev_aio_write(struct kiocb *iocb, const struct iovec *iov,  		if (err < 0 && ret > 0)  			ret = err;  	} + +	percpu_up_read(&bdev->bd_block_size_semaphore); +  	blk_finish_plug(&plug); +  	return ret;  }  EXPORT_SYMBOL_GPL(blkdev_aio_write); +static int blkdev_mmap(struct file *file, struct vm_area_struct *vma) +{ +	int ret; +	struct block_device *bdev = I_BDEV(file->f_mapping->host); + +	percpu_down_read(&bdev->bd_block_size_semaphore); + +	ret = generic_file_mmap(file, vma); + +	percpu_up_read(&bdev->bd_block_size_semaphore); + +	return ret; +} +  /*   * Try to release a page associated with block device when the system   * is under memory pressure. @@ -1627,9 +1691,9 @@ const struct file_operations def_blk_fops = {  	.llseek		= block_llseek,  	.read		= do_sync_read,  	.write		= do_sync_write, -  	.aio_read	= generic_file_aio_read, +  	.aio_read	= blkdev_aio_read,  	.aio_write	= blkdev_aio_write, -	.mmap		= generic_file_mmap, +	.mmap		= blkdev_mmap,  	.fsync		= blkdev_fsync,  	.unlocked_ioctl	= block_ioctl,  #ifdef CONFIG_COMPAT  |