2015-06-25 11:21:02 +03:00
/*
* NVDIMM Block Window Driver
* Copyright ( c ) 2014 , Intel Corporation .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope 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 .
*/
# include <linux/blkdev.h>
# include <linux/fs.h>
# include <linux/genhd.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/nd.h>
# include <linux/sizes.h>
# include "nd.h"
2016-03-18 21:27:36 +03:00
static u32 nsblk_meta_size ( struct nd_namespace_blk * nsblk )
{
return nsblk - > lbasize - ( ( nsblk - > lbasize > = 4096 ) ? 4096 : 512 ) ;
}
2015-06-25 11:21:02 +03:00
2016-03-18 21:27:36 +03:00
static u32 nsblk_internal_lbasize ( struct nd_namespace_blk * nsblk )
2015-06-25 11:22:39 +03:00
{
2016-03-18 21:27:36 +03:00
return roundup ( nsblk - > lbasize , INT_LBASIZE_ALIGNMENT ) ;
}
static u32 nsblk_sector_size ( struct nd_namespace_blk * nsblk )
{
return nsblk - > lbasize - nsblk_meta_size ( nsblk ) ;
2015-06-25 11:22:39 +03:00
}
2015-06-25 11:21:02 +03:00
static resource_size_t to_dev_offset ( struct nd_namespace_blk * nsblk ,
resource_size_t ns_offset , unsigned int len )
{
int i ;
for ( i = 0 ; i < nsblk - > num_resources ; i + + ) {
if ( ns_offset < resource_size ( nsblk - > res [ i ] ) ) {
if ( ns_offset + len > resource_size ( nsblk - > res [ i ] ) ) {
dev_WARN_ONCE ( & nsblk - > common . dev , 1 ,
" illegal request \n " ) ;
return SIZE_MAX ;
}
return nsblk - > res [ i ] - > start + ns_offset ;
}
ns_offset - = resource_size ( nsblk - > res [ i ] ) ;
}
dev_WARN_ONCE ( & nsblk - > common . dev , 1 , " request out of range \n " ) ;
return SIZE_MAX ;
}
2016-03-18 21:27:36 +03:00
static struct nd_blk_region * to_ndbr ( struct nd_namespace_blk * nsblk )
{
struct nd_region * nd_region ;
struct device * parent ;
parent = nsblk - > common . dev . parent ;
nd_region = container_of ( parent , struct nd_region , dev ) ;
return container_of ( nd_region , struct nd_blk_region , nd_region ) ;
}
2015-06-25 11:22:39 +03:00
# ifdef CONFIG_BLK_DEV_INTEGRITY
2016-03-18 21:27:36 +03:00
static int nd_blk_rw_integrity ( struct nd_namespace_blk * nsblk ,
struct bio_integrity_payload * bip , u64 lba , int rw )
2015-06-25 11:22:39 +03:00
{
2016-03-18 21:27:36 +03:00
struct nd_blk_region * ndbr = to_ndbr ( nsblk ) ;
unsigned int len = nsblk_meta_size ( nsblk ) ;
2015-06-25 11:22:39 +03:00
resource_size_t dev_offset , ns_offset ;
2016-03-18 21:27:36 +03:00
u32 internal_lbasize , sector_size ;
2015-06-25 11:22:39 +03:00
int err = 0 ;
2016-03-18 21:27:36 +03:00
internal_lbasize = nsblk_internal_lbasize ( nsblk ) ;
sector_size = nsblk_sector_size ( nsblk ) ;
ns_offset = lba * internal_lbasize + sector_size ;
2015-06-25 11:22:39 +03:00
dev_offset = to_dev_offset ( nsblk , ns_offset , len ) ;
if ( dev_offset = = SIZE_MAX )
return - EIO ;
while ( len ) {
unsigned int cur_len ;
struct bio_vec bv ;
void * iobuf ;
bv = bvec_iter_bvec ( bip - > bip_vec , bip - > bip_iter ) ;
/*
* The ' bv ' obtained from bvec_iter_bvec has its . bv_len and
* . bv_offset already adjusted for iter - > bi_bvec_done , and we
* can use those directly
*/
cur_len = min ( len , bv . bv_len ) ;
iobuf = kmap_atomic ( bv . bv_page ) ;
err = ndbr - > do_io ( ndbr , dev_offset , iobuf + bv . bv_offset ,
cur_len , rw ) ;
kunmap_atomic ( iobuf ) ;
if ( err )
return err ;
len - = cur_len ;
dev_offset + = cur_len ;
2017-06-29 21:31:13 +03:00
if ( ! bvec_iter_advance ( bip - > bip_vec , & bip - > bip_iter , cur_len ) )
return - EIO ;
2015-06-25 11:22:39 +03:00
}
return err ;
}
# else /* CONFIG_BLK_DEV_INTEGRITY */
2016-03-18 21:27:36 +03:00
static int nd_blk_rw_integrity ( struct nd_namespace_blk * nsblk ,
struct bio_integrity_payload * bip , u64 lba , int rw )
2015-06-25 11:22:39 +03:00
{
return 0 ;
}
# endif
2016-03-18 21:27:36 +03:00
static int nsblk_do_bvec ( struct nd_namespace_blk * nsblk ,
struct bio_integrity_payload * bip , struct page * page ,
unsigned int len , unsigned int off , int rw , sector_t sector )
2015-06-25 11:22:39 +03:00
{
2016-03-18 21:27:36 +03:00
struct nd_blk_region * ndbr = to_ndbr ( nsblk ) ;
2015-06-25 11:22:39 +03:00
resource_size_t dev_offset , ns_offset ;
2016-03-18 21:27:36 +03:00
u32 internal_lbasize , sector_size ;
2015-06-25 11:22:39 +03:00
int err = 0 ;
void * iobuf ;
u64 lba ;
2016-03-18 21:27:36 +03:00
internal_lbasize = nsblk_internal_lbasize ( nsblk ) ;
sector_size = nsblk_sector_size ( nsblk ) ;
2015-06-25 11:22:39 +03:00
while ( len ) {
unsigned int cur_len ;
/*
* If we don ' t have an integrity payload , we don ' t have to
* split the bvec into sectors , as this would cause unnecessary
* Block Window setup / move steps . the do_io routine is capable
* of handling len < = PAGE_SIZE .
*/
2016-03-18 21:27:36 +03:00
cur_len = bip ? min ( len , sector_size ) : len ;
2015-06-25 11:22:39 +03:00
2016-03-18 21:27:36 +03:00
lba = div_u64 ( sector < < SECTOR_SHIFT , sector_size ) ;
ns_offset = lba * internal_lbasize ;
dev_offset = to_dev_offset ( nsblk , ns_offset , cur_len ) ;
2015-06-25 11:22:39 +03:00
if ( dev_offset = = SIZE_MAX )
return - EIO ;
iobuf = kmap_atomic ( page ) ;
err = ndbr - > do_io ( ndbr , dev_offset , iobuf + off , cur_len , rw ) ;
kunmap_atomic ( iobuf ) ;
if ( err )
return err ;
if ( bip ) {
2016-03-18 21:27:36 +03:00
err = nd_blk_rw_integrity ( nsblk , bip , lba , rw ) ;
2015-06-25 11:22:39 +03:00
if ( err )
return err ;
}
len - = cur_len ;
off + = cur_len ;
2016-03-18 21:27:36 +03:00
sector + = sector_size > > SECTOR_SHIFT ;
2015-06-25 11:22:39 +03:00
}
return err ;
}
2015-11-05 20:41:16 +03:00
static blk_qc_t nd_blk_make_request ( struct request_queue * q , struct bio * bio )
2015-06-25 11:21:02 +03:00
{
2015-06-25 11:22:39 +03:00
struct bio_integrity_payload * bip ;
2016-03-18 21:27:36 +03:00
struct nd_namespace_blk * nsblk ;
2015-06-25 11:21:02 +03:00
struct bvec_iter iter ;
2015-05-16 19:28:53 +03:00
unsigned long start ;
2015-06-25 11:21:02 +03:00
struct bio_vec bvec ;
int err = 0 , rw ;
2015-05-16 19:28:53 +03:00
bool do_acct ;
2015-06-25 11:21:02 +03:00
2017-06-29 21:31:11 +03:00
if ( ! bio_integrity_prep ( bio ) )
return BLK_QC_T_NONE ;
2015-06-25 11:22:39 +03:00
bip = bio_integrity ( bio ) ;
2016-03-18 21:27:36 +03:00
nsblk = q - > queuedata ;
2015-06-25 11:21:02 +03:00
rw = bio_data_dir ( bio ) ;
2015-05-16 19:28:53 +03:00
do_acct = nd_iostat_start ( bio , & start ) ;
2015-06-25 11:21:02 +03:00
bio_for_each_segment ( bvec , bio , iter ) {
unsigned int len = bvec . bv_len ;
BUG_ON ( len > PAGE_SIZE ) ;
2016-03-18 21:27:36 +03:00
err = nsblk_do_bvec ( nsblk , bip , bvec . bv_page , len ,
bvec . bv_offset , rw , iter . bi_sector ) ;
2015-06-25 11:22:39 +03:00
if ( err ) {
2016-03-18 21:27:36 +03:00
dev_dbg ( & nsblk - > common . dev ,
2015-06-25 11:22:39 +03:00
" io error in %s sector %lld, len %d, \n " ,
( rw = = READ ) ? " READ " : " WRITE " ,
( unsigned long long ) iter . bi_sector , len ) ;
2017-06-03 10:38:06 +03:00
bio - > bi_status = errno_to_blk_status ( err ) ;
2015-05-16 19:28:53 +03:00
break ;
2015-06-25 11:21:02 +03:00
}
}
2015-05-16 19:28:53 +03:00
if ( do_acct )
nd_iostat_end ( bio , start ) ;
2015-06-25 11:21:02 +03:00
2015-07-20 16:29:37 +03:00
bio_endio ( bio ) ;
2015-11-05 20:41:16 +03:00
return BLK_QC_T_NONE ;
2015-06-25 11:21:02 +03:00
}
2016-03-18 21:27:36 +03:00
static int nsblk_rw_bytes ( struct nd_namespace_common * ndns ,
2017-05-11 00:01:30 +03:00
resource_size_t offset , void * iobuf , size_t n , int rw ,
unsigned long flags )
2015-06-25 11:21:02 +03:00
{
2016-03-18 21:27:36 +03:00
struct nd_namespace_blk * nsblk = to_nd_namespace_blk ( & ndns - > dev ) ;
struct nd_blk_region * ndbr = to_ndbr ( nsblk ) ;
2015-06-25 11:21:02 +03:00
resource_size_t dev_offset ;
dev_offset = to_dev_offset ( nsblk , offset , n ) ;
2016-03-18 21:27:36 +03:00
if ( unlikely ( offset + n > nsblk - > size ) ) {
2015-06-25 11:21:02 +03:00
dev_WARN_ONCE ( & ndns - > dev , 1 , " request out of range \n " ) ;
return - EFAULT ;
}
if ( dev_offset = = SIZE_MAX )
return - EIO ;
return ndbr - > do_io ( ndbr , dev_offset , iobuf , n , rw ) ;
}
static const struct block_device_operations nd_blk_fops = {
. owner = THIS_MODULE ,
2015-06-24 03:08:34 +03:00
. revalidate_disk = nvdimm_revalidate_disk ,
2015-06-25 11:21:02 +03:00
} ;
2016-03-18 06:08:28 +03:00
static void nd_blk_release_queue ( void * q )
{
blk_cleanup_queue ( q ) ;
}
static void nd_blk_release_disk ( void * disk )
{
del_gendisk ( disk ) ;
put_disk ( disk ) ;
}
2016-03-18 21:27:36 +03:00
static int nsblk_attach_disk ( struct nd_namespace_blk * nsblk )
2015-06-25 11:21:02 +03:00
{
2016-03-18 21:27:36 +03:00
struct device * dev = & nsblk - > common . dev ;
2015-06-25 11:22:39 +03:00
resource_size_t available_disk_size ;
2016-03-18 06:08:28 +03:00
struct request_queue * q ;
2015-06-25 11:21:02 +03:00
struct gendisk * disk ;
2015-06-25 11:22:39 +03:00
u64 internal_nlba ;
2016-03-18 21:27:36 +03:00
internal_nlba = div_u64 ( nsblk - > size , nsblk_internal_lbasize ( nsblk ) ) ;
available_disk_size = internal_nlba * nsblk_sector_size ( nsblk ) ;
2015-06-25 11:21:02 +03:00
2016-03-18 06:08:28 +03:00
q = blk_alloc_queue ( GFP_KERNEL ) ;
if ( ! q )
return - ENOMEM ;
2016-06-16 00:59:17 +03:00
if ( devm_add_action_or_reset ( dev , nd_blk_release_queue , q ) )
2015-06-25 11:21:02 +03:00
return - ENOMEM ;
2016-03-18 06:08:28 +03:00
blk_queue_make_request ( q , nd_blk_make_request ) ;
blk_queue_max_hw_sectors ( q , UINT_MAX ) ;
2016-03-18 21:27:36 +03:00
blk_queue_logical_block_size ( q , nsblk_sector_size ( nsblk ) ) ;
2016-03-18 06:08:28 +03:00
queue_flag_set_unlocked ( QUEUE_FLAG_NONROT , q ) ;
2016-03-18 21:27:36 +03:00
q - > queuedata = nsblk ;
2015-06-25 11:21:02 +03:00
2016-03-18 06:08:28 +03:00
disk = alloc_disk ( 0 ) ;
if ( ! disk )
return - ENOMEM ;
2015-06-25 11:21:02 +03:00
disk - > first_minor = 0 ;
disk - > fops = & nd_blk_fops ;
2016-03-18 06:08:28 +03:00
disk - > queue = q ;
2015-06-25 11:21:02 +03:00
disk - > flags = GENHD_FL_EXT_DEVT ;
2016-03-18 21:27:36 +03:00
nvdimm_namespace_disk_name ( & nsblk - > common , disk - > disk_name ) ;
2015-06-25 11:22:39 +03:00
set_capacity ( disk , 0 ) ;
2016-06-16 05:44:20 +03:00
device_add_disk ( dev , disk ) ;
2015-06-25 11:21:02 +03:00
2016-06-16 00:59:17 +03:00
if ( devm_add_action_or_reset ( dev , nd_blk_release_disk , disk ) )
return - ENOMEM ;
2016-03-18 21:27:36 +03:00
if ( nsblk_meta_size ( nsblk ) ) {
int rc = nd_integrity_init ( disk , nsblk_meta_size ( nsblk ) ) ;
2015-06-25 11:22:39 +03:00
2016-03-18 06:08:28 +03:00
if ( rc )
2015-06-25 11:22:39 +03:00
return rc ;
}
set_capacity ( disk , available_disk_size > > SECTOR_SHIFT ) ;
2015-06-24 03:08:34 +03:00
revalidate_disk ( disk ) ;
2015-06-25 11:21:02 +03:00
return 0 ;
}
static int nd_blk_probe ( struct device * dev )
{
struct nd_namespace_common * ndns ;
2015-06-25 11:22:39 +03:00
struct nd_namespace_blk * nsblk ;
2015-06-25 11:21:02 +03:00
ndns = nvdimm_namespace_common_probe ( dev ) ;
if ( IS_ERR ( ndns ) )
return PTR_ERR ( ndns ) ;
2015-06-25 11:22:39 +03:00
nsblk = to_nd_namespace_blk ( & ndns - > dev ) ;
2016-03-18 21:27:36 +03:00
nsblk - > size = nvdimm_namespace_capacity ( ndns ) ;
dev_set_drvdata ( dev , nsblk ) ;
ndns - > rw_bytes = nsblk_rw_bytes ;
2015-06-25 11:21:02 +03:00
if ( is_nd_btt ( dev ) )
2016-03-18 06:08:28 +03:00
return nvdimm_namespace_attach_btt ( ndns ) ;
2016-03-22 10:22:16 +03:00
else if ( nd_btt_probe ( dev , ndns ) = = 0 ) {
2015-06-25 11:21:02 +03:00
/* we'll come back as btt-blk */
2016-03-18 06:08:28 +03:00
return - ENXIO ;
2015-06-25 11:21:02 +03:00
} else
2016-03-18 21:27:36 +03:00
return nsblk_attach_disk ( nsblk ) ;
2015-06-25 11:21:02 +03:00
}
static int nd_blk_remove ( struct device * dev )
{
if ( is_nd_btt ( dev ) )
2016-03-16 02:41:04 +03:00
nvdimm_namespace_detach_btt ( to_nd_btt ( dev ) ) ;
2015-06-25 11:21:02 +03:00
return 0 ;
}
static struct nd_device_driver nd_blk_driver = {
. probe = nd_blk_probe ,
. remove = nd_blk_remove ,
. drv = {
. name = " nd_blk " ,
} ,
. type = ND_DRIVER_NAMESPACE_BLK ,
} ;
static int __init nd_blk_init ( void )
{
2016-03-10 00:59:28 +03:00
return nd_driver_register ( & nd_blk_driver ) ;
2015-06-25 11:21:02 +03:00
}
static void __exit nd_blk_exit ( void )
{
driver_unregister ( & nd_blk_driver . drv ) ;
}
MODULE_AUTHOR ( " Ross Zwisler <ross.zwisler@linux.intel.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS_ND_DEVICE ( ND_DEVICE_NAMESPACE_BLK ) ;
module_init ( nd_blk_init ) ;
module_exit ( nd_blk_exit ) ;