blob: f3806a45bbeb100a44e4430c1196a35562d3d27a [file] [log] [blame]
/*
* Copyright (c) 2017, FUJITSU LIMITED. All rights reserved.
*
* 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"
#include <ndctl/libndctl-nfit.h>
/**
* ndctl_bus_is_nfit_cmd_supported - ask nfit command is supported on @bus.
* @bus: ndctl_bus instance
* @cmd: nfit command number (defined as NFIT_CMD_XXX in libndctl-nfit.h)
*
* Return 1: command is supported. Return 0: command is not supported.
*
*/
NDCTL_EXPORT int ndctl_bus_is_nfit_cmd_supported(struct ndctl_bus *bus,
int cmd)
{
return !!(bus->nfit_dsm_mask & (1ULL << cmd));
}
static int bus_has_translate_spa(struct ndctl_bus *bus)
{
if (!ndctl_bus_has_nfit(bus))
return 0;
return ndctl_bus_is_nfit_cmd_supported(bus, NFIT_CMD_TRANSLATE_SPA);
}
static struct ndctl_cmd *ndctl_bus_cmd_new_translate_spa(struct ndctl_bus *bus)
{
struct ndctl_cmd *cmd;
struct nd_cmd_pkg *pkg;
struct nd_cmd_translate_spa *translate_spa;
size_t size, spa_length;
spa_length = sizeof(struct nd_cmd_translate_spa)
+ sizeof(struct nd_nvdimm_device);
size = sizeof(*cmd) + sizeof(*pkg) + spa_length;
cmd = calloc(1, size);
if (!cmd)
return NULL;
cmd->bus = bus;
ndctl_cmd_ref(cmd);
cmd->type = ND_CMD_CALL;
cmd->size = size;
cmd->status = 1;
pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0];
pkg->nd_command = NFIT_CMD_TRANSLATE_SPA;
pkg->nd_size_in = sizeof(unsigned long long);
pkg->nd_size_out = spa_length;
pkg->nd_fw_size = spa_length;
translate_spa = (struct nd_cmd_translate_spa *)&pkg->nd_payload[0];
cmd->firmware_status = &translate_spa->status;
translate_spa->translate_length = spa_length;
return cmd;
}
static int ndctl_bus_cmd_get_translate_spa(struct ndctl_cmd *cmd,
unsigned int *handle, unsigned long long *dpa)
{
struct nd_cmd_pkg *pkg;
struct nd_cmd_translate_spa *translate_spa;
pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0];
translate_spa = (struct nd_cmd_translate_spa *)&pkg->nd_payload[0];
if (translate_spa->status == ND_TRANSLATE_SPA_STATUS_INVALID_SPA)
return -EINVAL;
/*
* XXX: Currently NVDIMM mirroring is not supported.
* Even if ACPI returned plural dimms due to mirroring,
* this function returns just the first dimm.
*/
*handle = translate_spa->devices[0].nfit_device_handle;
*dpa = translate_spa->devices[0].dpa;
return 0;
}
static int is_valid_spa(struct ndctl_bus *bus, unsigned long long spa)
{
return !!ndctl_bus_get_region_by_physical_address(bus, spa);
}
/**
* ndctl_bus_nfit_translate_spa - call translate spa.
* @bus: bus which belongs to.
* @address: address (System Physical Address)
* @handle: pointer to return dimm handle
* @dpa: pointer to return Dimm Physical address
*
* If success, returns zero, store dimm's @handle, and @dpa.
*/
int ndctl_bus_nfit_translate_spa(struct ndctl_bus *bus,
unsigned long long address, unsigned int *handle, unsigned long long *dpa)
{
struct ndctl_cmd *cmd;
struct nd_cmd_pkg *pkg;
struct nd_cmd_translate_spa *translate_spa;
int rc;
if (!bus || !handle || !dpa)
return -EINVAL;
if (!bus_has_translate_spa(bus))
return -ENOTTY;
if (!is_valid_spa(bus, address))
return -EINVAL;
cmd = ndctl_bus_cmd_new_translate_spa(bus);
if (!cmd)
return -ENOMEM;
pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0];
translate_spa = (struct nd_cmd_translate_spa *)&pkg->nd_payload[0];
translate_spa->spa = address;
rc = ndctl_cmd_submit(cmd);
if (rc) {
ndctl_cmd_unref(cmd);
return rc;
}
rc = ndctl_bus_cmd_get_translate_spa(cmd, handle, dpa);
ndctl_cmd_unref(cmd);
return rc;
}