blob: bd75131081ba5f22220845952e1e00239d62fea3 [file] [log] [blame]
/*
* Copyright (c) 2014-2016, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, 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 Lesser General Public License for
* more details.
*/
#include <stdlib.h>
#include <ndctl/libndctl.h>
#include "private.h"
NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_cap(struct ndctl_bus *bus,
unsigned long long address, unsigned long long len)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
struct ndctl_cmd *cmd;
size_t size;
if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_CAP)) {
dbg(ctx, "unsupported cmd\n");
return NULL;
}
size = sizeof(*cmd) + sizeof(struct nd_cmd_ars_cap);
cmd = calloc(1, size);
if (!cmd)
return NULL;
cmd->bus = bus;
ndctl_cmd_ref(cmd);
cmd->type = ND_CMD_ARS_CAP;
cmd->size = size;
cmd->status = 1;
cmd->firmware_status = &cmd->ars_cap->status;
cmd->ars_cap->address = address;
cmd->ars_cap->length = len;
return cmd;
}
static bool is_power_of_2(unsigned int v)
{
return v && ((v & (v - 1)) == 0);
}
static bool validate_clear_error(struct ndctl_cmd *ars_cap)
{
if (!is_power_of_2(ars_cap->ars_cap->clear_err_unit))
return false;
return true;
}
static bool __validate_ars_cap(struct ndctl_cmd *ars_cap)
{
if (ars_cap->type != ND_CMD_ARS_CAP || ars_cap->status != 0)
return false;
if ((*ars_cap->firmware_status & ARS_STATUS_MASK) != 0)
return false;
return validate_clear_error(ars_cap);
}
#define validate_ars_cap(ctx, ars_cap) \
({ \
bool __valid = __validate_ars_cap(ars_cap); \
if (!__valid) \
dbg(ctx, "expected sucessfully completed ars_cap command\n"); \
__valid; \
})
NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_start(struct ndctl_cmd *ars_cap,
int type)
{
struct ndctl_bus *bus = ars_cap->bus;
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
struct ndctl_cmd *cmd;
size_t size;
if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_START)) {
dbg(ctx, "unsupported cmd\n");
return NULL;
}
if (!validate_ars_cap(ctx, ars_cap))
return NULL;
if (!(*ars_cap->firmware_status >> ARS_EXT_STATUS_SHIFT & type)) {
dbg(ctx, "ars_cap does not show requested type as supported\n");
return NULL;
}
size = sizeof(*cmd) + sizeof(struct nd_cmd_ars_start);
cmd = calloc(1, size);
if (!cmd)
return NULL;
cmd->bus = bus;
ndctl_cmd_ref(cmd);
cmd->type = ND_CMD_ARS_START;
cmd->size = size;
cmd->status = 1;
cmd->firmware_status = &cmd->ars_start->status;
cmd->ars_start->address = ars_cap->ars_cap->address;
cmd->ars_start->length = ars_cap->ars_cap->length;
cmd->ars_start->type = type;
return cmd;
}
NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_status(struct ndctl_cmd *ars_cap)
{
struct nd_cmd_ars_cap *ars_cap_cmd = ars_cap->ars_cap;
struct ndctl_bus *bus = ars_cap->bus;
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
struct ndctl_cmd *cmd;
size_t size;
if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_STATUS)) {
dbg(ctx, "unsupported cmd\n");
return NULL;
}
if (!validate_ars_cap(ctx, ars_cap))
return NULL;
if (ars_cap_cmd->max_ars_out == 0) {
dbg(ctx, "invalid ars_cap\n");
return NULL;
}
size = sizeof(*cmd) + ars_cap_cmd->max_ars_out;
/*
* Older kernels have a bug that miscalculates the output length of the
* ars status and will overrun the provided buffer by 4 bytes,
* corrupting the memory. Add an additional 4 bytes in the allocation
* size to prevent that corruption. See kernel patch for more details:
*
* https://patchwork.kernel.org/patch/10563103/
*/
cmd = calloc(1, size + 4);
if (!cmd)
return NULL;
cmd->bus = bus;
ndctl_cmd_ref(cmd);
cmd->type = ND_CMD_ARS_STATUS;
cmd->size = size;
cmd->status = 1;
cmd->firmware_status = &cmd->ars_status->status;
cmd->ars_status->out_length = ars_cap_cmd->max_ars_out;
return cmd;
}
NDCTL_EXPORT unsigned int ndctl_cmd_ars_cap_get_size(struct ndctl_cmd *ars_cap)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_cap));
if (ars_cap->type == ND_CMD_ARS_CAP && ars_cap->status == 0) {
dbg(ctx, "max_ars_out: %d\n",
ars_cap->ars_cap->max_ars_out);
return ars_cap->ars_cap->max_ars_out;
}
dbg(ctx, "invalid ars_cap\n");
return 0;
}
NDCTL_EXPORT int ndctl_cmd_ars_cap_get_range(struct ndctl_cmd *ars_cap,
struct ndctl_range *range)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_cap));
if (range && ars_cap->type == ND_CMD_ARS_CAP && ars_cap->status == 0) {
struct nd_cmd_ars_cap *cap = ars_cap->ars_cap;
dbg(ctx, "range: %llx - %llx\n", cap->address, cap->length);
range->address = cap->address;
range->length = cap->length;
return 0;
}
dbg(ctx, "invalid ars_cap\n");
return -EINVAL;
}
NDCTL_EXPORT unsigned int ndctl_cmd_ars_cap_get_clear_unit(
struct ndctl_cmd *ars_cap)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_cap));
if (ars_cap->type == ND_CMD_ARS_CAP && ars_cap->status == 0) {
dbg(ctx, "clear_err_unit: %d\n",
ars_cap->ars_cap->clear_err_unit);
return ars_cap->ars_cap->clear_err_unit;
}
dbg(ctx, "invalid ars_cap\n");
return 0;
}
static bool __validate_ars_stat(struct ndctl_cmd *ars_stat)
{
/*
* A positive status indicates an underrun, but that is fine since
* the firmware is not expected to completely fill the max_ars_out
* sized buffer.
*/
if (ars_stat->type != ND_CMD_ARS_STATUS || ars_stat->status < 0)
return false;
if ((ndctl_cmd_get_firmware_status(ars_stat) & ARS_STATUS_MASK) != 0)
return false;
return true;
}
#define validate_ars_stat(ctx, ars_stat) \
({ \
bool __valid = __validate_ars_stat(ars_stat); \
if (!__valid) \
dbg(ctx, "expected sucessfully completed ars_stat command\n"); \
__valid; \
})
NDCTL_EXPORT int ndctl_cmd_ars_in_progress(struct ndctl_cmd *cmd)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(cmd));
if (!validate_ars_stat(ctx, cmd))
return 0;
return (ndctl_cmd_get_firmware_status(cmd) == 1 << 16);
}
NDCTL_EXPORT unsigned int ndctl_cmd_ars_num_records(struct ndctl_cmd *ars_stat)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat));
if (!validate_ars_stat(ctx, ars_stat))
return 0;
return ars_stat->ars_status->num_records;
}
NDCTL_EXPORT unsigned long long ndctl_cmd_ars_get_record_addr(
struct ndctl_cmd *ars_stat, unsigned int rec_index)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat));
if (!validate_ars_stat(ctx, ars_stat))
return 0;
if (rec_index >= ars_stat->ars_status->num_records) {
dbg(ctx, "invalid record index\n");
return 0;
}
return ars_stat->ars_status->records[rec_index].err_address;
}
NDCTL_EXPORT unsigned long long ndctl_cmd_ars_get_record_len(
struct ndctl_cmd *ars_stat, unsigned int rec_index)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat));
if (!validate_ars_stat(ctx, ars_stat))
return 0;
if (rec_index >= ars_stat->ars_status->num_records) {
dbg(ctx, "invalid record index\n");
return 0;
}
return ars_stat->ars_status->records[rec_index].length;
}
NDCTL_EXPORT int ndctl_cmd_ars_stat_get_flag_overflow(
struct ndctl_cmd *ars_stat)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat));
if (!validate_ars_stat(ctx, ars_stat))
return -EINVAL;
return !!(ars_stat->ars_status->flags & ND_ARS_STAT_FLAG_OVERFLOW);
}
NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_clear_error(
unsigned long long address, unsigned long long len,
struct ndctl_cmd *ars_cap)
{
size_t size;
unsigned int mask;
struct nd_cmd_ars_cap *cap;
struct ndctl_cmd *clear_err;
struct ndctl_bus *bus = ars_cap->bus;
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_STATUS)) {
dbg(ctx, "unsupported cmd\n");
return NULL;
}
if (!validate_ars_cap(ctx, ars_cap))
return NULL;
cap = ars_cap->ars_cap;
if (address < cap->address || address > (cap->address + cap->length)
|| address + len > (cap->address + cap->length)) {
dbg(ctx, "request out of range (relative to ars_cap)\n");
return NULL;
}
mask = cap->clear_err_unit - 1;
if ((address | len) & mask) {
dbg(ctx, "request unaligned\n");
return NULL;
}
size = sizeof(*clear_err) + sizeof(struct nd_cmd_clear_error);
clear_err = calloc(1, size);
if (!clear_err)
return NULL;
ndctl_cmd_ref(clear_err);
clear_err->bus = bus;
clear_err->type = ND_CMD_CLEAR_ERROR;
clear_err->size = size;
clear_err->status = 1;
clear_err->firmware_status = &clear_err->clear_err->status;
clear_err->clear_err->address = address;
clear_err->clear_err->length = len;
return clear_err;
}
NDCTL_EXPORT unsigned long long ndctl_cmd_clear_error_get_cleared(
struct ndctl_cmd *clear_err)
{
struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(clear_err));
if (clear_err->type == ND_CMD_CLEAR_ERROR && clear_err->status == 0)
return clear_err->clear_err->cleared;
dbg(ctx, "invalid clear_err\n");
return 0;
}