2015-07-31 00:57:47 +03:00
/*
* Copyright ( c ) 2013 - 2015 Intel Corporation . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation .
*
* 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 .
*/
# include <linux/blkdev.h>
# include <linux/device.h>
# include <linux/genhd.h>
# include <linux/sizes.h>
# include <linux/slab.h>
# include <linux/fs.h>
# include <linux/mm.h>
# include "nd-core.h"
# include "pfn.h"
# include "nd.h"
static void nd_pfn_release ( struct device * dev )
{
struct nd_region * nd_region = to_nd_region ( dev - > parent ) ;
struct nd_pfn * nd_pfn = to_nd_pfn ( dev ) ;
dev_dbg ( dev , " %s \n " , __func__ ) ;
nd_detach_ndns ( & nd_pfn - > dev , & nd_pfn - > ndns ) ;
ida_simple_remove ( & nd_region - > pfn_ida , nd_pfn - > id ) ;
kfree ( nd_pfn - > uuid ) ;
kfree ( nd_pfn ) ;
}
static struct device_type nd_pfn_device_type = {
. name = " nd_pfn " ,
. release = nd_pfn_release ,
} ;
bool is_nd_pfn ( struct device * dev )
{
return dev ? dev - > type = = & nd_pfn_device_type : false ;
}
EXPORT_SYMBOL ( is_nd_pfn ) ;
struct nd_pfn * to_nd_pfn ( struct device * dev )
{
struct nd_pfn * nd_pfn = container_of ( dev , struct nd_pfn , dev ) ;
WARN_ON ( ! is_nd_pfn ( dev ) ) ;
return nd_pfn ;
}
EXPORT_SYMBOL ( to_nd_pfn ) ;
static ssize_t mode_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct nd_pfn * nd_pfn = to_nd_pfn ( dev ) ;
switch ( nd_pfn - > mode ) {
case PFN_MODE_RAM :
return sprintf ( buf , " ram \n " ) ;
case PFN_MODE_PMEM :
return sprintf ( buf , " pmem \n " ) ;
default :
return sprintf ( buf , " none \n " ) ;
}
}
static ssize_t mode_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t len )
{
struct nd_pfn * nd_pfn = to_nd_pfn ( dev ) ;
ssize_t rc = 0 ;
device_lock ( dev ) ;
nvdimm_bus_lock ( dev ) ;
if ( dev - > driver )
rc = - EBUSY ;
else {
size_t n = len - 1 ;
if ( strncmp ( buf , " pmem \n " , n ) = = 0
| | strncmp ( buf , " pmem " , n ) = = 0 ) {
/* TODO: allocate from PMEM support */
rc = - ENOTTY ;
} else if ( strncmp ( buf , " ram \n " , n ) = = 0
| | strncmp ( buf , " ram " , n ) = = 0 )
nd_pfn - > mode = PFN_MODE_RAM ;
else if ( strncmp ( buf , " none \n " , n ) = = 0
| | strncmp ( buf , " none " , n ) = = 0 )
nd_pfn - > mode = PFN_MODE_NONE ;
else
rc = - EINVAL ;
}
dev_dbg ( dev , " %s: result: %zd wrote: %s%s " , __func__ ,
rc , buf , buf [ len - 1 ] = = ' \n ' ? " " : " \n " ) ;
nvdimm_bus_unlock ( dev ) ;
device_unlock ( dev ) ;
return rc ? rc : len ;
}
static DEVICE_ATTR_RW ( mode ) ;
static ssize_t uuid_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct nd_pfn * nd_pfn = to_nd_pfn ( dev ) ;
if ( nd_pfn - > uuid )
return sprintf ( buf , " %pUb \n " , nd_pfn - > uuid ) ;
return sprintf ( buf , " \n " ) ;
}
static ssize_t uuid_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t len )
{
struct nd_pfn * nd_pfn = to_nd_pfn ( dev ) ;
ssize_t rc ;
device_lock ( dev ) ;
rc = nd_uuid_store ( dev , & nd_pfn - > uuid , buf , len ) ;
dev_dbg ( dev , " %s: result: %zd wrote: %s%s " , __func__ ,
rc , buf , buf [ len - 1 ] = = ' \n ' ? " " : " \n " ) ;
device_unlock ( dev ) ;
return rc ? rc : len ;
}
static DEVICE_ATTR_RW ( uuid ) ;
static ssize_t namespace_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct nd_pfn * nd_pfn = to_nd_pfn ( dev ) ;
ssize_t rc ;
nvdimm_bus_lock ( dev ) ;
rc = sprintf ( buf , " %s \n " , nd_pfn - > ndns
? dev_name ( & nd_pfn - > ndns - > dev ) : " " ) ;
nvdimm_bus_unlock ( dev ) ;
return rc ;
}
static ssize_t namespace_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t len )
{
struct nd_pfn * nd_pfn = to_nd_pfn ( dev ) ;
ssize_t rc ;
nvdimm_bus_lock ( dev ) ;
device_lock ( dev ) ;
rc = nd_namespace_store ( dev , & nd_pfn - > ndns , buf , len ) ;
dev_dbg ( dev , " %s: result: %zd wrote: %s%s " , __func__ ,
rc , buf , buf [ len - 1 ] = = ' \n ' ? " " : " \n " ) ;
device_unlock ( dev ) ;
nvdimm_bus_unlock ( dev ) ;
return rc ;
}
static DEVICE_ATTR_RW ( namespace ) ;
static struct attribute * nd_pfn_attributes [ ] = {
& dev_attr_mode . attr ,
& dev_attr_namespace . attr ,
& dev_attr_uuid . attr ,
NULL ,
} ;
static struct attribute_group nd_pfn_attribute_group = {
. attrs = nd_pfn_attributes ,
} ;
static const struct attribute_group * nd_pfn_attribute_groups [ ] = {
& nd_pfn_attribute_group ,
& nd_device_attribute_group ,
& nd_numa_attribute_group ,
NULL ,
} ;
static struct device * __nd_pfn_create ( struct nd_region * nd_region ,
u8 * uuid , enum nd_pfn_mode mode ,
struct nd_namespace_common * ndns )
{
struct nd_pfn * nd_pfn ;
struct device * dev ;
/* we can only create pages for contiguous ranged of pmem */
if ( ! is_nd_pmem ( & nd_region - > dev ) )
return NULL ;
nd_pfn = kzalloc ( sizeof ( * nd_pfn ) , GFP_KERNEL ) ;
if ( ! nd_pfn )
return NULL ;
nd_pfn - > id = ida_simple_get ( & nd_region - > pfn_ida , 0 , 0 , GFP_KERNEL ) ;
if ( nd_pfn - > id < 0 ) {
kfree ( nd_pfn ) ;
return NULL ;
}
nd_pfn - > mode = mode ;
if ( uuid )
uuid = kmemdup ( uuid , 16 , GFP_KERNEL ) ;
nd_pfn - > uuid = uuid ;
dev = & nd_pfn - > dev ;
dev_set_name ( dev , " pfn%d.%d " , nd_region - > id , nd_pfn - > id ) ;
dev - > parent = & nd_region - > dev ;
dev - > type = & nd_pfn_device_type ;
dev - > groups = nd_pfn_attribute_groups ;
device_initialize ( & nd_pfn - > dev ) ;
if ( ndns & & ! __nd_attach_ndns ( & nd_pfn - > dev , ndns , & nd_pfn - > ndns ) ) {
dev_dbg ( & ndns - > dev , " %s failed, already claimed by %s \n " ,
__func__ , dev_name ( ndns - > claim ) ) ;
put_device ( dev ) ;
return NULL ;
}
return dev ;
}
struct device * nd_pfn_create ( struct nd_region * nd_region )
{
struct device * dev = __nd_pfn_create ( nd_region , NULL , PFN_MODE_NONE ,
NULL ) ;
if ( dev )
__nd_device_register ( dev ) ;
return dev ;
}
2015-08-01 09:16:37 +03:00
int nd_pfn_validate ( struct nd_pfn * nd_pfn )
2015-07-31 00:57:47 +03:00
{
struct nd_namespace_common * ndns = nd_pfn - > ndns ;
struct nd_pfn_sb * pfn_sb = nd_pfn - > pfn_sb ;
struct nd_namespace_io * nsio ;
u64 checksum , offset ;
if ( ! pfn_sb | | ! ndns )
return - ENODEV ;
if ( ! is_nd_pmem ( nd_pfn - > dev . parent ) )
return - ENODEV ;
/* section alignment for simple hotplug */
if ( nvdimm_namespace_capacity ( ndns ) < ND_PFN_ALIGN )
return - ENODEV ;
if ( nvdimm_read_bytes ( ndns , SZ_4K , pfn_sb , sizeof ( * pfn_sb ) ) )
return - ENXIO ;
if ( memcmp ( pfn_sb - > signature , PFN_SIG , PFN_SIG_LEN ) ! = 0 )
return - ENODEV ;
checksum = le64_to_cpu ( pfn_sb - > checksum ) ;
pfn_sb - > checksum = 0 ;
if ( checksum ! = nd_sb_checksum ( ( struct nd_gen_sb * ) pfn_sb ) )
return - ENODEV ;
pfn_sb - > checksum = cpu_to_le64 ( checksum ) ;
switch ( le32_to_cpu ( pfn_sb - > mode ) ) {
case PFN_MODE_RAM :
break ;
case PFN_MODE_PMEM :
/* TODO: allocate from PMEM support */
return - ENOTTY ;
default :
return - ENXIO ;
}
if ( ! nd_pfn - > uuid ) {
/* from probe we allocate */
nd_pfn - > uuid = kmemdup ( pfn_sb - > uuid , 16 , GFP_KERNEL ) ;
if ( ! nd_pfn - > uuid )
return - ENOMEM ;
} else {
/* from init we validate */
if ( memcmp ( nd_pfn - > uuid , pfn_sb - > uuid , 16 ) ! = 0 )
return - EINVAL ;
}
/*
* These warnings are verbose because they can only trigger in
* the case where the physical address alignment of the
* namespace has changed since the pfn superblock was
* established .
*/
offset = le64_to_cpu ( pfn_sb - > dataoff ) ;
nsio = to_nd_namespace_io ( & ndns - > dev ) ;
2015-08-01 09:16:37 +03:00
if ( nsio - > res . start & ND_PFN_MASK ) {
2015-07-31 00:57:47 +03:00
dev_err ( & nd_pfn - > dev ,
2015-08-01 09:16:37 +03:00
" init failed: %s not section aligned \n " ,
dev_name ( & ndns - > dev ) ) ;
2015-07-31 00:57:47 +03:00
return - EBUSY ;
} else if ( offset > = resource_size ( & nsio - > res ) ) {
dev_err ( & nd_pfn - > dev , " pfn array size exceeds capacity of %s \n " ,
dev_name ( & ndns - > dev ) ) ;
return - EBUSY ;
}
return 0 ;
}
2015-08-01 09:16:37 +03:00
EXPORT_SYMBOL ( nd_pfn_validate ) ;
2015-07-31 00:57:47 +03:00
int nd_pfn_probe ( struct nd_namespace_common * ndns , void * drvdata )
{
int rc ;
struct device * dev ;
struct nd_pfn * nd_pfn ;
struct nd_pfn_sb * pfn_sb ;
struct nd_region * nd_region = to_nd_region ( ndns - > dev . parent ) ;
if ( ndns - > force_raw )
return - ENODEV ;
nvdimm_bus_lock ( & ndns - > dev ) ;
dev = __nd_pfn_create ( nd_region , NULL , PFN_MODE_NONE , ndns ) ;
nvdimm_bus_unlock ( & ndns - > dev ) ;
if ( ! dev )
return - ENOMEM ;
dev_set_drvdata ( dev , drvdata ) ;
pfn_sb = kzalloc ( sizeof ( * pfn_sb ) , GFP_KERNEL ) ;
nd_pfn = to_nd_pfn ( dev ) ;
nd_pfn - > pfn_sb = pfn_sb ;
rc = nd_pfn_validate ( nd_pfn ) ;
nd_pfn - > pfn_sb = NULL ;
kfree ( pfn_sb ) ;
dev_dbg ( & ndns - > dev , " %s: pfn: %s \n " , __func__ ,
rc = = 0 ? dev_name ( dev ) : " <none> " ) ;
if ( rc < 0 ) {
__nd_detach_ndns ( dev , & nd_pfn - > ndns ) ;
put_device ( dev ) ;
} else
__nd_device_register ( & nd_pfn - > dev ) ;
return rc ;
}
EXPORT_SYMBOL ( nd_pfn_probe ) ;