| /* |
| * 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; |
| } |