| /* |
| * Copyright (C) 2016-2017 Dell, Inc. |
| * Copyright (C) 2016 Hewlett Packard Enterprise Development LP |
| * Copyright (c) 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 <limits.h> |
| #include <util/log.h> |
| #include <ndctl/libndctl.h> |
| #include "private.h" |
| #include "msft.h" |
| |
| #define CMD_MSFT(_c) ((_c)->msft) |
| #define CMD_MSFT_SMART(_c) (CMD_MSFT(_c)->u.smart.data) |
| |
| static struct ndctl_cmd *msft_dimm_cmd_new_smart(struct ndctl_dimm *dimm) |
| { |
| struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); |
| struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); |
| struct ndctl_cmd *cmd; |
| size_t size; |
| struct ndn_pkg_msft *msft; |
| |
| if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) { |
| dbg(ctx, "unsupported cmd\n"); |
| return NULL; |
| } |
| |
| size = sizeof(*cmd) + sizeof(struct ndn_pkg_msft); |
| cmd = calloc(1, size); |
| if (!cmd) |
| return NULL; |
| |
| cmd->dimm = dimm; |
| ndctl_cmd_ref(cmd); |
| cmd->type = ND_CMD_CALL; |
| cmd->size = size; |
| cmd->status = 1; |
| |
| msft = CMD_MSFT(cmd); |
| msft->gen.nd_family = NVDIMM_FAMILY_MSFT; |
| msft->gen.nd_command = NDN_MSFT_CMD_SMART; |
| msft->gen.nd_fw_size = 0; |
| msft->gen.nd_size_in = offsetof(struct ndn_msft_smart, status); |
| msft->gen.nd_size_out = sizeof(msft->u.smart); |
| msft->u.smart.status = 0; |
| |
| cmd->firmware_status = &msft->u.smart.status; |
| |
| return cmd; |
| } |
| |
| static int msft_smart_valid(struct ndctl_cmd *cmd) |
| { |
| if (cmd->type != ND_CMD_CALL || |
| cmd->size != sizeof(*cmd) + sizeof(struct ndn_pkg_msft) || |
| CMD_MSFT(cmd)->gen.nd_family != NVDIMM_FAMILY_MSFT || |
| CMD_MSFT(cmd)->gen.nd_command != NDN_MSFT_CMD_SMART || |
| cmd->status != 0) |
| return cmd->status < 0 ? cmd->status : -EINVAL; |
| return 0; |
| } |
| |
| static unsigned int msft_cmd_smart_get_flags(struct ndctl_cmd *cmd) |
| { |
| if (msft_smart_valid(cmd) < 0) |
| return UINT_MAX; |
| |
| /* below health data can be retrieved via MSFT _DSM function 11 */ |
| return NDN_MSFT_SMART_HEALTH_VALID | |
| NDN_MSFT_SMART_TEMP_VALID | |
| NDN_MSFT_SMART_USED_VALID; |
| } |
| |
| static unsigned int num_set_bit_health(__u16 num) |
| { |
| int i; |
| __u16 n = num & 0x7FFF; |
| unsigned int count = 0; |
| |
| for (i = 0; i < 15; i++) |
| if (!!(n & (1 << i))) |
| count++; |
| |
| return count; |
| } |
| |
| static unsigned int msft_cmd_smart_get_health(struct ndctl_cmd *cmd) |
| { |
| unsigned int health; |
| unsigned int num; |
| |
| if (msft_smart_valid(cmd) < 0) |
| return UINT_MAX; |
| |
| num = num_set_bit_health(CMD_MSFT_SMART(cmd)->health); |
| if (num == 0) |
| health = 0; |
| else if (num < 2) |
| health = ND_SMART_NON_CRITICAL_HEALTH; |
| else if (num < 3) |
| health = ND_SMART_CRITICAL_HEALTH; |
| else |
| health = ND_SMART_FATAL_HEALTH; |
| |
| return health; |
| } |
| |
| static unsigned int msft_cmd_smart_get_temperature(struct ndctl_cmd *cmd) |
| { |
| if (msft_smart_valid(cmd) < 0) |
| return UINT_MAX; |
| |
| return CMD_MSFT_SMART(cmd)->temp * 16; |
| } |
| |
| static unsigned int msft_cmd_smart_get_life_used(struct ndctl_cmd *cmd) |
| { |
| if (msft_smart_valid(cmd) < 0) |
| return UINT_MAX; |
| |
| return 100 - CMD_MSFT_SMART(cmd)->nvm_lifetime; |
| } |
| |
| struct ndctl_smart_ops * const msft_smart_ops = &(struct ndctl_smart_ops) { |
| .new_smart = msft_dimm_cmd_new_smart, |
| .smart_get_flags = msft_cmd_smart_get_flags, |
| .smart_get_health = msft_cmd_smart_get_health, |
| .smart_get_temperature = msft_cmd_smart_get_temperature, |
| .smart_get_life_used = msft_cmd_smart_get_life_used, |
| }; |