/*
* Block Translation Table
* Copyright (c) 2014-2015, 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/highmem.h>
#include <linux/debugfs.h>
#include <linux/blkdev.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/hdreg.h>
#include <linux/genhd.h>
#include <linux/sizes.h>
#include <linux/ndctl.h>
#include <linux/fs.h>
#include <linux/nd.h>
#include "btt.h"
#include "nd.h"
enum log_ent_request {
LOG_NEW_ENT = 0,
LOG_OLD_ENT
};
static int arena_read_bytes(struct arena_info *arena, resource_size_t offset,
void *buf, size_t n)
{
struct nd_btt *nd_btt = arena->nd_btt;
struct nd_namespace_common *ndns = nd_btt->ndns;
/* arena offsets are 4K from the base of the device */
offset += SZ_4K;
return nvdimm_read_bytes(ndns, offset, buf, n);
}
static int arena_write_bytes(struct arena_info *arena, resource_size_t offset,
void *buf, size_t n)
{
struct nd_btt *nd_btt = arena->nd_btt;
struct nd_namespace_common *ndns = nd_btt->ndns;
/* arena offsets are 4K from the base of the device */
offset += SZ_4K;
return nvdimm_write_bytes(ndns, offset, buf, n);
}
static int btt_info_write(struct arena_info *arena, struct btt_sb *super)
{
int ret;
ret = arena_write_bytes(arena, arena->info2off, super,
sizeof(struct btt_sb));
if (ret)
return ret;
return arena_write_bytes(arena, arena->infooff, super,
sizeof(struct btt_sb));
}
static int btt_info_read(struct arena_info *arena, struct btt_sb *super)
{
WARN_ON(!super);
return arena_read_bytes(arena, arena->infooff, super,
sizeof(struct btt_sb));
}
/*
* 'raw' version of btt_map write
* Assumptions:
* mapping is in little-endian
* mapping contains 'E' and 'Z' flags as desired
*/
static int __btt_map_write(struct arena_info *arena, u32 lba, __le32 mapping)
{
u64 ns_off = arena->mapoff + (lba * MAP_ENT_SIZE);
WARN_ON(lba >= arena->external_nlba);
return arena_write_bytes(arena, ns_off, &mapping, MAP_ENT_SIZE);
}
static int btt_map_write(struct arena_info *arena, u32 lba, u32 mapping,
u32 z_flag, u32 e_flag)
{
u32 ze;
__le32 mapping_le;
/*
* This 'mapping' is supposed to be just the LBA mapping, without
* any flags set, so strip the flag bits.
*/
mapping &= MAP_LBA_MASK;
ze = (z_flag << 1) + e_flag;
switch (ze) {
case 0:
/*
* We want to set neither of the Z or E flags, and
* in the actual layout, this means setting the bit
* positions of both to '1' to indicate a 'normal'
* map entry
*/
mapping |= MAP_ENT_NORMAL;
break;
case 1:
mapping |= (1 << MAP_ERR_SHIFT);
break;
case 2:
mapping |= (1 << MAP_TRIM_SHIFT);
break;
default:
/*
* The case where Z and E are both sent in as '1' could be
* construed as a valid 'normal' case, but we decide not to,
* to avoid confusion
*/
WARN_ONCE(1, "Invalid use of Z and E flags\n");
return -EIO;
}
mapping_le = cpu_to_le32(mapping);
return __btt_map_write(arena, lba, mapping_le);
}
static int btt_map_read(struct arena_info *arena, u32 lba, u32 *mapping,
int *trim, int *error)
{
int ret;
__le32 in;
u32 raw_mapping, postmap, ze, z_flag, e_flag;
u64 ns_off = arena->mapoff + (lba * MAP_ENT_SIZE);
WARN_ON(lba >= arena->external_nlba);
ret = arena_read_bytes(arena, ns_off, &in, MAP_ENT_SIZE);
if (ret)
return ret;
raw_mapping = le32_to_cpu(in);
z_flag = (raw_mapping & MAP_TRIM_MASK) >> MAP_TRIM_SHIFT;
e_flag = (raw_mapping & MAP_ERR_MASK) >> MAP_ERR_SHIFT;
ze = (z_flag << 1) + e_flag;
postmap = raw_mapping & MAP_LBA_MASK;
/* Reuse the {z,e}_flag variables for *trim and *error */
z_flag = 0;
e_flag = 0;
switch (ze) {
case 0:
/* Initial state. Return postmap = premap */
*mapping = lba;
break;
case 1:
*mapping = postmap;
e_flag = 1;
break;
case 2:
*mapping = postmap;
z_flag = 1;
break;
case 3:
*mapping = postmap;
break;
default:
return -EIO;
}
if (trim)
*trim = z_flag;
if (error)
*error = e_flag;
return ret;
}
static int btt_log_group_read(struct arena_info *arena, u32 lane,
struct log_group *log)
{
WARN_ON(!log);
return arena_read_bytes(arena,
arena->logoff + (lane * LOG_GRP_SIZE), log,
LOG_GRP_SIZE);
}
static struct dentry *debugfs_root;
static void arena_debugfs_init(struct arena_info *a, struct dentry *parent,
int idx)
{
char dirname[32];
struct dentry *d;
/* If for some reason, parent bttN was not created, exit */
if (!parent)
return;
snprintf(dirname, 32, "arena%d", idx);
d = debugfs_create_dir(dirname, parent);
if (IS_ERR_OR_NULL(d))
return;
a->debugfs_dir = d;
debugfs_create_x64("size", S_IRUGO, d, &a->size);
debugfs_create_x64("external_lba_start", S_IRUGO, d,
&a->external_lba_start);
debugfs_create_x32("internal_nlba", S_IRUGO, d, &a->internal_nlba);
debugfs_create_u32("internal_lbasize", S_IRUGO, d,
&a->internal_lbasize);
debugfs_create_x32("external_nlba", S_IRUGO, d, &a->external_nlba);
debugfs_create_u32("external_lbasize", S_IRUGO, d,
&a->external_lbasize);
debugfs_create_u32("nfree", S_IRUGO, d, &a->nfree);
debugfs_create_u16("version_major", S_IRUGO, d, &a->version_major);
debugfs_create_u16("version_minor", S_IRUGO, d, &a->version_minor);
debugfs_create_x64("nextoff", S_IRUGO, d, &a->nextoff);
debugfs_create_x64("infooff", S_IRUGO, d, &a->infooff);
debugfs_create_x64("dataoff", S_IRUGO, d, &a->dataoff);
debugfs_create_x64("mapoff", S_IRUGO, d, &a->mapoff);
debugfs_create_x64("logoff", S_IRUGO, d, &a->logoff);
debugfs_create_x64("info2off", S_IRUGO, d, &a->info2off);
debugfs_create_x32("flags", S_IRUGO, d, &a->flags);
debugfs_create_u32("log_index_0", S_IRUGO, d, &a->log_index[0]);
debugfs_create_u32("log_index_1", S_IRUGO, d, &a->log_index[1]);
}
static void btt_
|