2016-01-12 09:49:36 +03:00
/*
* Copyright ( C ) 2015 Matias Bjorling . All rights reserved .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License version
* 2 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; see the file COPYING . If not , write to
* the Free Software Foundation , 675 Mass Ave , Cambridge , MA 0213 9 ,
* USA .
*
*/
# include <linux/lightnvm.h>
# define MAX_SYSBLKS 3 /* remember to update mapping scheme on change */
# define MAX_BLKS_PR_SYSBLK 2 / * 2 blks with 256 pages and 3000 erases
* enables ~ 1.5 M updates per sysblk unit
*/
struct sysblk_scan {
/* A row is a collection of flash blocks for a system block. */
int nr_rows ;
int row ;
int act_blk [ MAX_SYSBLKS ] ;
int nr_ppas ;
struct ppa_addr ppas [ MAX_SYSBLKS * MAX_BLKS_PR_SYSBLK ] ; /* all sysblks */
} ;
static inline int scan_ppa_idx ( int row , int blkid )
{
return ( row * MAX_BLKS_PR_SYSBLK ) + blkid ;
}
2016-07-07 10:54:11 +03:00
static void nvm_sysblk_to_cpu ( struct nvm_sb_info * info ,
struct nvm_system_block * sb )
2016-01-12 09:49:36 +03:00
{
info - > seqnr = be32_to_cpu ( sb - > seqnr ) ;
info - > erase_cnt = be32_to_cpu ( sb - > erase_cnt ) ;
info - > version = be16_to_cpu ( sb - > version ) ;
strncpy ( info - > mmtype , sb - > mmtype , NVM_MMTYPE_LEN ) ;
info - > fs_ppa . ppa = be64_to_cpu ( sb - > fs_ppa ) ;
}
2016-07-07 10:54:11 +03:00
static void nvm_cpu_to_sysblk ( struct nvm_system_block * sb ,
struct nvm_sb_info * info )
2016-01-12 09:49:36 +03:00
{
sb - > magic = cpu_to_be32 ( NVM_SYSBLK_MAGIC ) ;
sb - > seqnr = cpu_to_be32 ( info - > seqnr ) ;
sb - > erase_cnt = cpu_to_be32 ( info - > erase_cnt ) ;
sb - > version = cpu_to_be16 ( info - > version ) ;
strncpy ( sb - > mmtype , info - > mmtype , NVM_MMTYPE_LEN ) ;
sb - > fs_ppa = cpu_to_be64 ( info - > fs_ppa . ppa ) ;
}
static int nvm_setup_sysblks ( struct nvm_dev * dev , struct ppa_addr * sysblk_ppas )
{
int nr_rows = min_t ( int , MAX_SYSBLKS , dev - > nr_chnls ) ;
int i ;
for ( i = 0 ; i < nr_rows ; i + + )
sysblk_ppas [ i ] . ppa = 0 ;
/* if possible, place sysblk at first channel, middle channel and last
* channel of the device . If not , create only one or two sys blocks
*/
switch ( dev - > nr_chnls ) {
case 2 :
sysblk_ppas [ 1 ] . g . ch = 1 ;
/* fall-through */
case 1 :
sysblk_ppas [ 0 ] . g . ch = 0 ;
break ;
default :
sysblk_ppas [ 0 ] . g . ch = 0 ;
sysblk_ppas [ 1 ] . g . ch = dev - > nr_chnls / 2 ;
sysblk_ppas [ 2 ] . g . ch = dev - > nr_chnls - 1 ;
break ;
}
return nr_rows ;
}
2016-07-07 10:54:11 +03:00
static void nvm_setup_sysblk_scan ( struct nvm_dev * dev , struct sysblk_scan * s ,
2016-01-12 09:49:36 +03:00
struct ppa_addr * sysblk_ppas )
{
memset ( s , 0 , sizeof ( struct sysblk_scan ) ) ;
s - > nr_rows = nvm_setup_sysblks ( dev , sysblk_ppas ) ;
}
2016-05-06 21:03:05 +03:00
static int sysblk_get_free_blks ( struct nvm_dev * dev , struct ppa_addr ppa ,
u8 * blks , int nr_blks ,
struct sysblk_scan * s )
{
struct ppa_addr * sppa ;
int i , blkid = 0 ;
nr_blks = nvm_bb_tbl_fold ( dev , blks , nr_blks ) ;
if ( nr_blks < 0 )
return nr_blks ;
for ( i = 0 ; i < nr_blks ; i + + ) {
if ( blks [ i ] = = NVM_BLK_T_HOST )
return - EEXIST ;
if ( blks [ i ] ! = NVM_BLK_T_FREE )
continue ;
sppa = & s - > ppas [ scan_ppa_idx ( s - > row , blkid ) ] ;
sppa - > g . ch = ppa . g . ch ;
sppa - > g . lun = ppa . g . lun ;
sppa - > g . blk = i ;
s - > nr_ppas + + ;
blkid + + ;
pr_debug ( " nvm: use (%u %u %u) as sysblk \n " ,
sppa - > g . ch , sppa - > g . lun , sppa - > g . blk ) ;
if ( blkid > MAX_BLKS_PR_SYSBLK - 1 )
return 0 ;
}
pr_err ( " nvm: sysblk failed get sysblk \n " ) ;
return - EINVAL ;
}
2016-05-06 21:02:58 +03:00
static int sysblk_get_host_blks ( struct nvm_dev * dev , struct ppa_addr ppa ,
2016-05-06 21:03:05 +03:00
u8 * blks , int nr_blks ,
struct sysblk_scan * s )
2016-01-12 09:49:36 +03:00
{
int i , nr_sysblk = 0 ;
2016-05-06 21:02:58 +03:00
nr_blks = nvm_bb_tbl_fold ( dev , blks , nr_blks ) ;
if ( nr_blks < 0 )
return nr_blks ;
2016-01-12 09:49:36 +03:00
for ( i = 0 ; i < nr_blks ; i + + ) {
if ( blks [ i ] ! = NVM_BLK_T_HOST )
continue ;
if ( s - > nr_ppas = = MAX_BLKS_PR_SYSBLK * MAX_SYSBLKS ) {
pr_err ( " nvm: too many host blks \n " ) ;
return - EINVAL ;
}
ppa . g . blk = i ;
s - > ppas [ scan_ppa_idx ( s - > row , nr_sysblk ) ] = ppa ;
s - > nr_ppas + + ;
nr_sysblk + + ;
}
return 0 ;
}
static int nvm_get_all_sysblks ( struct nvm_dev * dev , struct sysblk_scan * s ,
2016-05-06 21:03:05 +03:00
struct ppa_addr * ppas , int get_free )
2016-01-12 09:49:36 +03:00
{
2016-05-06 21:03:05 +03:00
int i , nr_blks , ret = 0 ;
u8 * blks ;
2016-01-12 09:49:36 +03:00
s - > nr_ppas = 0 ;
2016-05-06 21:03:05 +03:00
nr_blks = dev - > blks_per_lun * dev - > plane_mode ;
blks = kmalloc ( nr_blks , GFP_KERNEL ) ;
if ( ! blks )
return - ENOMEM ;
2016-01-12 09:49:36 +03:00
for ( i = 0 ; i < s - > nr_rows ; i + + ) {
s - > row = i ;
2016-05-06 21:03:05 +03:00
ret = nvm_get_bb_tbl ( dev , ppas [ i ] , blks ) ;
2016-01-12 09:49:36 +03:00
if ( ret ) {
pr_err ( " nvm: failed bb tbl for ppa (%u %u) \n " ,
ppas [ i ] . g . ch ,
ppas [ i ] . g . blk ) ;
2016-05-06 21:03:05 +03:00
goto err_get ;
2016-01-12 09:49:36 +03:00
}
2016-05-06 21:03:05 +03:00
if ( get_free )
ret = sysblk_get_free_blks ( dev , ppas [ i ] , blks , nr_blks ,
s ) ;
else
ret = sysblk_get_host_blks ( dev , ppas [ i ] , blks , nr_blks ,
s ) ;
if ( ret )
goto err_get ;
2016-01-12 09:49:36 +03:00
}
2016-05-06 21:03:05 +03:00
err_get :
kfree ( blks ) ;
2016-01-12 09:49:36 +03:00
return ret ;
}
/*
* scans a block for latest sysblk .
* Returns :
* 0 - newer sysblk not found . PPA is updated to latest page .
* 1 - newer sysblk found and stored in * cur . PPA is updated to
* next valid page .
* < 0 - error .
*/
static int nvm_scan_block ( struct nvm_dev * dev , struct ppa_addr * ppa ,
struct nvm_system_block * sblk )
{
struct nvm_system_block * cur ;
2016-05-06 21:02:57 +03:00
int pg , ret , found = 0 ;
2016-01-12 09:49:36 +03:00
/* the full buffer for a flash page is allocated. Only the first of it
* contains the system block information
*/
2016-05-06 21:02:57 +03:00
cur = kmalloc ( dev - > pfpg_size , GFP_KERNEL ) ;
2016-01-12 09:49:36 +03:00
if ( ! cur )
return - ENOMEM ;
/* perform linear scan through the block */
for ( pg = 0 ; pg < dev - > lps_per_blk ; pg + + ) {
ppa - > g . pg = ppa_to_slc ( dev , pg ) ;
ret = nvm_submit_ppa ( dev , ppa , 1 , NVM_OP_PREAD , NVM_IO_SLC_MODE ,
2016-05-06 21:02:57 +03:00
cur , dev - > pfpg_size ) ;
2016-01-12 09:49:36 +03:00
if ( ret ) {
if ( ret = = NVM_RSP_ERR_EMPTYPAGE ) {
pr_debug ( " nvm: sysblk scan empty ppa (%u %u %u %u) \n " ,
ppa - > g . ch ,
ppa - > g . lun ,
ppa - > g . blk ,
ppa - > g . pg ) ;
break ;
}
pr_err ( " nvm: read failed (%x) for ppa (%u %u %u %u) " ,
ret ,
ppa - > g . ch ,
ppa - > g . lun ,
ppa - > g . blk ,
ppa - > g . pg ) ;
break ; /* if we can't read a page, continue to the
* next blk
*/
}
if ( be32_to_cpu ( cur - > magic ) ! = NVM_SYSBLK_MAGIC ) {
pr_debug ( " nvm: scan break for ppa (%u %u %u %u) \n " ,
ppa - > g . ch ,
ppa - > g . lun ,
ppa - > g . blk ,
ppa - > g . pg ) ;
break ; /* last valid page already found */
}
if ( be32_to_cpu ( cur - > seqnr ) < be32_to_cpu ( sblk - > seqnr ) )
continue ;
memcpy ( sblk , cur , sizeof ( struct nvm_system_block ) ) ;
found = 1 ;
}
kfree ( cur ) ;
return found ;
}
static int nvm_set_bb_tbl ( struct nvm_dev * dev , struct sysblk_scan * s , int type )
{
struct nvm_rq rqd ;
int ret ;
if ( s - > nr_ppas > dev - > ops - > max_phys_sect ) {
pr_err ( " nvm: unable to update all sysblocks atomically \n " ) ;
return - EINVAL ;
}
memset ( & rqd , 0 , sizeof ( struct nvm_rq ) ) ;
2016-05-06 21:03:07 +03:00
nvm_set_rqd_ppalist ( dev , & rqd , s - > ppas , s - > nr_ppas , 1 ) ;
2016-01-12 09:49:36 +03:00
nvm_generic_to_addr_mode ( dev , & rqd ) ;
2016-05-06 21:03:20 +03:00
ret = dev - > ops - > set_bb_tbl ( dev , & rqd . ppa_addr , rqd . nr_ppas , type ) ;
2016-01-12 09:49:36 +03:00
nvm_free_rqd_ppalist ( dev , & rqd ) ;
if ( ret ) {
pr_err ( " nvm: sysblk failed bb mark \n " ) ;
return - EINVAL ;
}
return 0 ;
}
static int nvm_write_and_verify ( struct nvm_dev * dev , struct nvm_sb_info * info ,
struct sysblk_scan * s )
{
struct nvm_system_block nvmsb ;
void * buf ;
2016-05-06 21:02:57 +03:00
int i , sect , ret = 0 ;
2016-01-12 09:49:36 +03:00
struct ppa_addr * ppas ;
nvm_cpu_to_sysblk ( & nvmsb , info ) ;
2016-05-06 21:02:57 +03:00
buf = kzalloc ( dev - > pfpg_size , GFP_KERNEL ) ;
2016-01-12 09:49:36 +03:00
if ( ! buf )
return - ENOMEM ;
memcpy ( buf , & nvmsb , sizeof ( struct nvm_system_block ) ) ;
ppas = kcalloc ( dev - > sec_per_pg , sizeof ( struct ppa_addr ) , GFP_KERNEL ) ;
if ( ! ppas ) {
ret = - ENOMEM ;
goto err ;
}
/* Write and verify */
for ( i = 0 ; i < s - > nr_rows ; i + + ) {
ppas [ 0 ] = s - > ppas [ scan_ppa_idx ( i , s - > act_blk [ i ] ) ] ;
pr_debug ( " nvm: writing sysblk to ppa (%u %u %u %u) \n " ,
ppas [ 0 ] . g . ch ,
ppas [ 0 ] . g . lun ,
ppas [ 0 ] . g . blk ,
ppas [ 0 ] . g . pg ) ;
/* Expand to all sectors within a flash page */
if ( dev - > sec_per_pg > 1 ) {
for ( sect = 1 ; sect < dev - > sec_per_pg ; sect + + ) {
ppas [ sect ] . ppa = ppas [ 0 ] . ppa ;
ppas [ sect ] . g . sec = sect ;
}
}
ret = nvm_submit_ppa ( dev , ppas , dev - > sec_per_pg , NVM_OP_PWRITE ,
2016-05-06 21:02:57 +03:00
NVM_IO_SLC_MODE , buf , dev - > pfpg_size ) ;
2016-01-12 09:49:36 +03:00
if ( ret ) {
pr_err ( " nvm: sysblk failed program (%u %u %u) \n " ,
ppas [ 0 ] . g . ch ,
ppas [ 0 ] . g . lun ,
ppas [ 0 ] . g . blk ) ;
break ;
}
ret = nvm_submit_ppa ( dev , ppas , dev - > sec_per_pg , NVM_OP_PREAD ,
2016-05-06 21:02:57 +03:00
NVM_IO_SLC_MODE , buf , dev - > pfpg_size ) ;
2016-01-12 09:49:36 +03:00
if ( ret ) {
pr_err ( " nvm: sysblk failed read (%u %u %u) \n " ,
ppas [ 0 ] . g . ch ,
ppas [ 0 ] . g . lun ,
ppas [ 0 ] . g . blk ) ;
break ;
}
if ( memcmp ( buf , & nvmsb , sizeof ( struct nvm_system_block ) ) ) {
pr_err ( " nvm: sysblk failed verify (%u %u %u) \n " ,
ppas [ 0 ] . g . ch ,
ppas [ 0 ] . g . lun ,
ppas [ 0 ] . g . blk ) ;
ret = - EINVAL ;
break ;
}
}
kfree ( ppas ) ;
err :
kfree ( buf ) ;
return ret ;
}
static int nvm_prepare_new_sysblks ( struct nvm_dev * dev , struct sysblk_scan * s )
{
int i , ret ;
unsigned long nxt_blk ;
struct ppa_addr * ppa ;
for ( i = 0 ; i < s - > nr_rows ; i + + ) {
nxt_blk = ( s - > act_blk [ i ] + 1 ) % MAX_BLKS_PR_SYSBLK ;
ppa = & s - > ppas [ scan_ppa_idx ( i , nxt_blk ) ] ;
ppa - > g . pg = ppa_to_slc ( dev , 0 ) ;
ret = nvm_erase_ppa ( dev , ppa , 1 ) ;
if ( ret )
return ret ;
s - > act_blk [ i ] = nxt_blk ;
}
return 0 ;
}
int nvm_get_sysblock ( struct nvm_dev * dev , struct nvm_sb_info * info )
{
struct ppa_addr sysblk_ppas [ MAX_SYSBLKS ] ;
struct sysblk_scan s ;
struct nvm_system_block * cur ;
int i , j , found = 0 ;
int ret = - ENOMEM ;
/*
* 1. setup sysblk locations
* 2. get bad block list
* 3. filter on host - specific ( type 3 )
* 4. iterate through all and find the highest seq nr .
* 5. return superblock information
*/
if ( ! dev - > ops - > get_bb_tbl )
return - EINVAL ;
nvm_setup_sysblk_scan ( dev , & s , sysblk_ppas ) ;
mutex_lock ( & dev - > mlock ) ;
2016-05-06 21:03:05 +03:00
ret = nvm_get_all_sysblks ( dev , & s , sysblk_ppas , 0 ) ;
2016-01-12 09:49:36 +03:00
if ( ret )
goto err_sysblk ;
/* no sysblocks initialized */
if ( ! s . nr_ppas )
goto err_sysblk ;
cur = kzalloc ( sizeof ( struct nvm_system_block ) , GFP_KERNEL ) ;
if ( ! cur )
goto err_sysblk ;
/* find the latest block across all sysblocks */
for ( i = 0 ; i < s . nr_rows ; i + + ) {
for ( j = 0 ; j < MAX_BLKS_PR_SYSBLK ; j + + ) {
struct ppa_addr ppa = s . ppas [ scan_ppa_idx ( i , j ) ] ;
ret = nvm_scan_block ( dev , & ppa , cur ) ;
if ( ret > 0 )
found = 1 ;
else if ( ret < 0 )
break ;
}
}
nvm_sysblk_to_cpu ( info , cur ) ;
kfree ( cur ) ;
err_sysblk :
mutex_unlock ( & dev - > mlock ) ;
if ( found )
return 1 ;
return ret ;
}
int nvm_update_sysblock ( struct nvm_dev * dev , struct nvm_sb_info * new )
{
/* 1. for each latest superblock
* 2. if room
* a . write new flash page entry with the updated information
* 3. if no room
* a . find next available block on lun ( linear search )
* if none , continue to next lun
* if none at all , report error . also report that it wasn ' t
* possible to write to all superblocks .
* c . write data to block .
*/
struct ppa_addr sysblk_ppas [ MAX_SYSBLKS ] ;
struct sysblk_scan s ;
struct nvm_system_block * cur ;
int i , j , ppaidx , found = 0 ;
int ret = - ENOMEM ;
if ( ! dev - > ops - > get_bb_tbl )
return - EINVAL ;
nvm_setup_sysblk_scan ( dev , & s , sysblk_ppas ) ;
mutex_lock ( & dev - > mlock ) ;
2016-05-06 21:03:05 +03:00
ret = nvm_get_all_sysblks ( dev , & s , sysblk_ppas , 0 ) ;
2016-01-12 09:49:36 +03:00
if ( ret )
goto err_sysblk ;
cur = kzalloc ( sizeof ( struct nvm_system_block ) , GFP_KERNEL ) ;
if ( ! cur )
goto err_sysblk ;
/* Get the latest sysblk for each sysblk row */
for ( i = 0 ; i < s . nr_rows ; i + + ) {
found = 0 ;
for ( j = 0 ; j < MAX_BLKS_PR_SYSBLK ; j + + ) {
ppaidx = scan_ppa_idx ( i , j ) ;
ret = nvm_scan_block ( dev , & s . ppas [ ppaidx ] , cur ) ;
if ( ret > 0 ) {
s . act_blk [ i ] = j ;
found = 1 ;
} else if ( ret < 0 )
break ;
}
}
if ( ! found ) {
pr_err ( " nvm: no valid sysblks found to update \n " ) ;
ret = - EINVAL ;
goto err_cur ;
}
/*
* All sysblocks found . Check that they have same page id in their flash
* blocks
*/
for ( i = 1 ; i < s . nr_rows ; i + + ) {
struct ppa_addr l = s . ppas [ scan_ppa_idx ( 0 , s . act_blk [ 0 ] ) ] ;
struct ppa_addr r = s . ppas [ scan_ppa_idx ( i , s . act_blk [ i ] ) ] ;
if ( l . g . pg ! = r . g . pg ) {
pr_err ( " nvm: sysblks not on same page. Previous update failed. \n " ) ;
ret = - EINVAL ;
goto err_cur ;
}
}
/*
* Check that there haven ' t been another update to the seqnr since we
* began
*/
if ( ( new - > seqnr - 1 ) ! = be32_to_cpu ( cur - > seqnr ) ) {
pr_err ( " nvm: seq is not sequential \n " ) ;
ret = - EINVAL ;
goto err_cur ;
}
/*
* When all pages in a block has been written , a new block is selected
* and writing is performed on the new block .
*/
if ( s . ppas [ scan_ppa_idx ( 0 , s . act_blk [ 0 ] ) ] . g . pg = =
dev - > lps_per_blk - 1 ) {
ret = nvm_prepare_new_sysblks ( dev , & s ) ;
if ( ret )
goto err_cur ;
}
ret = nvm_write_and_verify ( dev , new , & s ) ;
err_cur :
kfree ( cur ) ;
err_sysblk :
mutex_unlock ( & dev - > mlock ) ;
return ret ;
}
int nvm_init_sysblock ( struct nvm_dev * dev , struct nvm_sb_info * info )
{
struct ppa_addr sysblk_ppas [ MAX_SYSBLKS ] ;
struct sysblk_scan s ;
int ret ;
/*
* 1. select master blocks and select first available blks
* 2. get bad block list
* 3. mark MAX_SYSBLKS block as host - based device allocated .
* 4. write and verify data to block
*/
if ( ! dev - > ops - > get_bb_tbl | | ! dev - > ops - > set_bb_tbl )
return - EINVAL ;
if ( ! ( dev - > mccap & NVM_ID_CAP_SLC ) | | ! dev - > lps_per_blk ) {
pr_err ( " nvm: memory does not support SLC access \n " ) ;
return - EINVAL ;
}
/* Index all sysblocks and mark them as host-driven */
nvm_setup_sysblk_scan ( dev , & s , sysblk_ppas ) ;
mutex_lock ( & dev - > mlock ) ;
2016-05-06 21:03:05 +03:00
ret = nvm_get_all_sysblks ( dev , & s , sysblk_ppas , 1 ) ;
2016-01-12 09:49:36 +03:00
if ( ret )
goto err_mark ;
ret = nvm_set_bb_tbl ( dev , & s , NVM_BLK_T_HOST ) ;
if ( ret )
goto err_mark ;
/* Write to the first block of each row */
ret = nvm_write_and_verify ( dev , info , & s ) ;
err_mark :
mutex_unlock ( & dev - > mlock ) ;
return ret ;
}
2016-01-12 09:49:39 +03:00
static int factory_nblks ( int nblks )
{
/* Round up to nearest BITS_PER_LONG */
return ( nblks + ( BITS_PER_LONG - 1 ) ) & ~ ( BITS_PER_LONG - 1 ) ;
}
2016-05-06 21:03:06 +03:00
static unsigned int factory_blk_offset ( struct nvm_dev * dev , struct ppa_addr ppa )
2016-01-12 09:49:39 +03:00
{
int nblks = factory_nblks ( dev - > blks_per_lun ) ;
2016-05-06 21:03:06 +03:00
return ( ( ppa . g . ch * dev - > luns_per_chnl * nblks ) + ( ppa . g . lun * nblks ) ) /
2016-01-12 09:49:39 +03:00
BITS_PER_LONG ;
}
2016-05-06 21:02:58 +03:00
static int nvm_factory_blks ( struct nvm_dev * dev , struct ppa_addr ppa ,
2016-05-06 21:03:05 +03:00
u8 * blks , int nr_blks ,
2016-05-06 21:03:06 +03:00
unsigned long * blk_bitmap , int flags )
2016-01-12 09:49:39 +03:00
{
int i , lunoff ;
2016-05-06 21:02:58 +03:00
nr_blks = nvm_bb_tbl_fold ( dev , blks , nr_blks ) ;
if ( nr_blks < 0 )
return nr_blks ;
2016-05-06 21:03:06 +03:00
lunoff = factory_blk_offset ( dev , ppa ) ;
2016-01-12 09:49:39 +03:00
/* non-set bits correspond to the block must be erased */
for ( i = 0 ; i < nr_blks ; i + + ) {
switch ( blks [ i ] ) {
case NVM_BLK_T_FREE :
2016-05-06 21:03:06 +03:00
if ( flags & NVM_FACTORY_ERASE_ONLY_USER )
set_bit ( i , & blk_bitmap [ lunoff ] ) ;
2016-01-12 09:49:39 +03:00
break ;
case NVM_BLK_T_HOST :
2016-05-06 21:03:06 +03:00
if ( ! ( flags & NVM_FACTORY_RESET_HOST_BLKS ) )
set_bit ( i , & blk_bitmap [ lunoff ] ) ;
2016-01-12 09:49:39 +03:00
break ;
case NVM_BLK_T_GRWN_BAD :
2016-05-06 21:03:06 +03:00
if ( ! ( flags & NVM_FACTORY_RESET_GRWN_BBLKS ) )
set_bit ( i , & blk_bitmap [ lunoff ] ) ;
2016-01-12 09:49:39 +03:00
break ;
default :
2016-05-06 21:03:06 +03:00
set_bit ( i , & blk_bitmap [ lunoff ] ) ;
2016-01-12 09:49:39 +03:00
break ;
}
}
return 0 ;
}
static int nvm_fact_get_blks ( struct nvm_dev * dev , struct ppa_addr * erase_list ,
2016-05-06 21:03:06 +03:00
int max_ppas , unsigned long * blk_bitmap )
2016-01-12 09:49:39 +03:00
{
struct ppa_addr ppa ;
int ch , lun , blkid , idx , done = 0 , ppa_cnt = 0 ;
unsigned long * offset ;
while ( ! done ) {
done = 1 ;
2016-05-06 21:03:04 +03:00
nvm_for_each_lun_ppa ( dev , ppa , ch , lun ) {
2016-05-06 21:03:06 +03:00
idx = factory_blk_offset ( dev , ppa ) ;
offset = & blk_bitmap [ idx ] ;
2016-05-06 21:03:04 +03:00
blkid = find_first_zero_bit ( offset ,
dev - > blks_per_lun ) ;
if ( blkid > = dev - > blks_per_lun )
continue ;
set_bit ( blkid , offset ) ;
ppa . g . blk = blkid ;
pr_debug ( " nvm: erase ppa (%u %u %u) \n " ,
ppa . g . ch ,
ppa . g . lun ,
ppa . g . blk ) ;
erase_list [ ppa_cnt ] = ppa ;
ppa_cnt + + ;
done = 0 ;
if ( ppa_cnt = = max_ppas )
return ppa_cnt ;
2016-01-12 09:49:39 +03:00
}
}
return ppa_cnt ;
}
2016-05-06 21:03:06 +03:00
static int nvm_fact_select_blks ( struct nvm_dev * dev , unsigned long * blk_bitmap ,
int flags )
2016-01-12 09:49:39 +03:00
{
struct ppa_addr ppa ;
2016-05-06 21:03:06 +03:00
int ch , lun , nr_blks , ret = 0 ;
2016-05-06 21:03:05 +03:00
u8 * blks ;
nr_blks = dev - > blks_per_lun * dev - > plane_mode ;
blks = kmalloc ( nr_blks , GFP_KERNEL ) ;
if ( ! blks )
return - ENOMEM ;
2016-01-12 09:49:39 +03:00
2016-05-06 21:03:04 +03:00
nvm_for_each_lun_ppa ( dev , ppa , ch , lun ) {
2016-05-06 21:03:05 +03:00
ret = nvm_get_bb_tbl ( dev , ppa , blks ) ;
if ( ret )
pr_err ( " nvm: failed bb tbl for ch%u lun%u \n " ,
ppa . g . ch , ppa . g . blk ) ;
2016-05-06 21:03:06 +03:00
ret = nvm_factory_blks ( dev , ppa , blks , nr_blks , blk_bitmap ,
flags ) ;
2016-05-06 21:03:04 +03:00
if ( ret )
2016-05-06 21:03:06 +03:00
break ;
2016-01-12 09:49:39 +03:00
}
2016-05-06 21:03:05 +03:00
kfree ( blks ) ;
2016-05-06 21:03:06 +03:00
return ret ;
2016-01-12 09:49:39 +03:00
}
int nvm_dev_factory ( struct nvm_dev * dev , int flags )
{
struct ppa_addr * ppas ;
int ppa_cnt , ret = - ENOMEM ;
int max_ppas = dev - > ops - > max_phys_sect / dev - > nr_planes ;
struct ppa_addr sysblk_ppas [ MAX_SYSBLKS ] ;
struct sysblk_scan s ;
2016-05-06 21:03:06 +03:00
unsigned long * blk_bitmap ;
2016-01-12 09:49:39 +03:00
2016-05-06 21:03:06 +03:00
blk_bitmap = kzalloc ( factory_nblks ( dev - > blks_per_lun ) * dev - > nr_luns ,
2016-01-12 09:49:39 +03:00
GFP_KERNEL ) ;
2016-05-06 21:03:06 +03:00
if ( ! blk_bitmap )
2016-01-12 09:49:39 +03:00
return ret ;
ppas = kcalloc ( max_ppas , sizeof ( struct ppa_addr ) , GFP_KERNEL ) ;
if ( ! ppas )
goto err_blks ;
/* create list of blks to be erased */
2016-05-06 21:03:06 +03:00
ret = nvm_fact_select_blks ( dev , blk_bitmap , flags ) ;
2016-01-12 09:49:39 +03:00
if ( ret )
goto err_ppas ;
/* continue to erase until list of blks until empty */
2016-05-06 21:03:06 +03:00
while ( ( ppa_cnt =
nvm_fact_get_blks ( dev , ppas , max_ppas , blk_bitmap ) ) > 0 )
2016-01-12 09:49:39 +03:00
nvm_erase_ppa ( dev , ppas , ppa_cnt ) ;
/* mark host reserved blocks free */
if ( flags & NVM_FACTORY_RESET_HOST_BLKS ) {
nvm_setup_sysblk_scan ( dev , & s , sysblk_ppas ) ;
mutex_lock ( & dev - > mlock ) ;
2016-05-06 21:03:05 +03:00
ret = nvm_get_all_sysblks ( dev , & s , sysblk_ppas , 0 ) ;
2016-01-12 09:49:39 +03:00
if ( ! ret )
ret = nvm_set_bb_tbl ( dev , & s , NVM_BLK_T_FREE ) ;
mutex_unlock ( & dev - > mlock ) ;
}
err_ppas :
kfree ( ppas ) ;
err_blks :
2016-05-06 21:03:06 +03:00
kfree ( blk_bitmap ) ;
2016-01-12 09:49:39 +03:00
return ret ;
}
EXPORT_SYMBOL ( nvm_dev_factory ) ;