blob: e7ec45611b92388c57aef3611ede576fefb8646b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020 Google Inc.
*/
#include <common.h>
#include <blk.h>
#include <dm.h>
#include <cros/health_info.h>
#include <cros/storage_info.h>
#include <cros/vboot.h>
#include <linux/log2.h>
typedef struct {
uint64_t lo;
uint64_t hi;
} u128;
static inline u128 le128_to_u128(const uint8_t val[16])
{
u128 ret;
uint64_t *lo_ptr = (uint64_t *)val;
uint64_t *hi_ptr = (uint64_t *)(val + 8);
ret.lo = le32_to_cpu(*lo_ptr);
ret.hi = le32_to_cpu(*hi_ptr);
return ret;
}
// Round up to the multiple of base
static inline int _round_up(int val, int base)
{
int diff = base - val % base;
return val % base ? val + diff : val;
}
// Return the upper 64 non-zero bit and provide the shift.
static uint64_t u128_to_u64(const u128 val, int *shift_ret)
{
int shift = (val.hi >> 32) ? 64 - clz(val.hi >> 32) : 32 - clz(val.hi);
// Round up to the multiple of 10
shift = _round_up(shift, 10);
uint64_t value = 0;
if (shift > 0)
value |= val.hi << (64 - shift);
if (shift < 64)
value |= val.lo >> shift;
if (shift_ret)
*shift_ret = shift;
return value;
}
// Stringify u128 integer with upper 64-bit precision.
static const char *u128_to_str(const u128 val)
{
int shift;
uint64_t value = u128_to_u64(val, &shift);
// Calculate the padding 0, using dividing 1000 instead of 1024 as
// approximate.
shift = shift / 10 * 3;
static char str[64];
if (shift > 0)
snprintf(str, sizeof(str), "~%" PRIu64 "%0*d", value, shift, 0);
else
snprintf(str, sizeof(str), "%" PRIu64, value);
return str;
}
// Stringify u128 integer with Binary prefix (KiB, MiB...).
static const char *u128_to_capacity_str(const u128 unit_val,
const int bytes_per_unit)
{
const uint64_t base = 1024; // 2^10
int shift;
uint64_t value = u128_to_u64(unit_val, &shift);
/* clz(x) are the amount of bits "free" in x. The goal is to get enough
* free bits in value to fit multiplying bytes_per_unit in without
* overflowing. */
int need_adjust = ilog2(bytes_per_unit) + 1 - clz(value >> 32);
if (need_adjust > 0) {
/* round up to multiple of 10 */
need_adjust = _round_up(need_adjust, 10);
shift += need_adjust;
value >>= need_adjust;
}
value *= bytes_per_unit;
// Compute "xxx.yyy binary_prefix".
while (value >= base * base) {
value /= base;
shift += 10;
}
int hi, lo;
if (value >= base) {
hi = value / base;
lo = ((value % base) * 1000) / base;
shift += 10;
} else {
hi = value;
lo = 0;
}
assert(shift % 10 == 0);
// ISO prefixes is only defined for <= 2^80.
static const char prefixes[9][4] = {
"B",
"KiB",
"MiB",
"GiB",
"TiB",
"PiB",
"EiB",
"ZiB",
"YiB"
};
static char str[64];
if (shift <= 80)
snprintf(str, sizeof(str), "%d.%03d %s", hi, lo,
prefixes[shift / 10]);
else
snprintf(str, sizeof(str), "%d.%03d 2^%d %s", hi, lo, shift,
prefixes[0]);
return str;
}
// Kelvin temperature value to Celsius
static inline int kelvin_to_celsius(int k) {
return k - 273;
}
static char *stringify_nvme_smart(char *buf, const char *end,
const NvmeSmartLogData *smart_log)
{
int show_all = 1;
buf += snprintf(buf, end - buf,
"SMART/Health Information (NVMe Log 0x02)\n");
buf += snprintf(buf, end - buf,
"Critical Warning: 0x%02x\n",
smart_log->critical_warning);
buf += snprintf(buf, end - buf,
"Temperature: %d Celsius\n",
kelvin_to_celsius(smart_log->temperature));
buf += snprintf(buf, end - buf,
"Available Spare: %u%%\n",
smart_log->avail_spare);
buf += snprintf(buf, end - buf,
"Available Spare Threshold: %u%%\n",
smart_log->spare_thresh);
buf += snprintf(buf, end - buf,
"Percentage Used: %u%%\n",
smart_log->percent_used);
{
// data_units_read/data_units_written is reported in thousands
// where 1 unit = 512 bytes.
const int bytes_per_unit = 1000 * 512;
u128 val;
val = le128_to_u128(smart_log->data_units_read);
buf += snprintf(buf, end - buf,
"Data Units Read: %s [%s]\n",
u128_to_str(val),
u128_to_capacity_str(val, bytes_per_unit));
val = le128_to_u128(smart_log->data_units_written);
buf += snprintf(buf, end - buf,
"Data Units Written: %s [%s]\n",
u128_to_str(val),
u128_to_capacity_str(val, bytes_per_unit));
}
buf += snprintf(buf, end - buf,
"Host Read Commands: %s\n",
u128_to_str(le128_to_u128(smart_log->host_reads)));
buf += snprintf(buf, end - buf,
"Host Write Commands: %s\n",
u128_to_str(le128_to_u128(smart_log->host_writes)));
buf += snprintf(buf, end - buf,
"Controller Busy Time: %s\n",
u128_to_str(le128_to_u128(smart_log->ctrl_busy_time)));
buf += snprintf(buf, end - buf,
"Power Cycles: %s\n",
u128_to_str(le128_to_u128(smart_log->power_cycles)));
buf += snprintf(buf, end - buf,
"Power On Hours: %s\n",
u128_to_str(le128_to_u128(smart_log->power_on_hours)));
buf += snprintf(
buf, end - buf, "Unsafe Shutdowns: %s\n",
u128_to_str(le128_to_u128(smart_log->unsafe_shutdowns)));
buf += snprintf(buf, end - buf,
"Media and Data Integrity Errors: %s\n",
u128_to_str(le128_to_u128(smart_log->media_errors)));
buf += snprintf(
buf, end - buf, "Error Information Log Entries: %s\n",
u128_to_str(le128_to_u128(smart_log->num_err_log_entries)));
// Temperature thresholds are optional
if (smart_log->warning_temp_time) {
buf += snprintf(buf, end - buf,
"Warning Comp. Temperature Time: %d\n",
smart_log->warning_temp_time);
}
if (smart_log->critical_comp_time) {
buf += snprintf(buf, end - buf,
"Critical Comp. Temperature Time: %d\n",
smart_log->critical_comp_time);
}
// Temperature sensors are optional
for (int i = 0; i < 8; i++) {
int k = smart_log->temp_sensor[i];
if (k) {
buf += snprintf(buf, end - buf,
"Temperature Sensor %d: "
"%d Celsius\n",
i + 1, kelvin_to_celsius(k));
}
}
if (show_all || smart_log->thm_temp1_trans_count)
buf += snprintf(buf, end - buf,
"Thermal Temp. 1 Transition Count: %d\n",
smart_log->thm_temp1_trans_count);
if (show_all || smart_log->thm_temp2_trans_count)
buf += snprintf(buf, end - buf,
"Thermal Temp. 2 Transition Count: %d\n",
smart_log->thm_temp2_trans_count);
if (show_all || smart_log->thm_temp1_total_time)
buf += snprintf(buf, end - buf,
"Thermal Temp. 1 Total Time: %d\n",
smart_log->thm_temp1_total_time);
if (show_all || smart_log->thm_temp2_total_time)
buf += snprintf(buf, end - buf,
"Thermal Temp. 2 Total Time: %d\n",
smart_log->thm_temp2_total_time);
return buf;
}
static inline char *stringify_mmc_device_lifetime(char *buf, size_t len,
uint8_t life_used)
{
switch (life_used) {
case 0x0:
buf += snprintf(buf, len, "Not defined");
break;
case 0x1 ... 0xa:
buf += snprintf(buf, len, "%d%% - %d%% device life time used",
(life_used - 1) * 10, life_used * 10);
break;
case 0xb:
buf += snprintf(
buf, len,
"Exceeded its maximum estimated device life time");
break;
default:
buf += snprintf(buf, len, "Unknown");
break;
}
return buf;
}
static inline const char *mmc_eol_info_to_str(uint8_t eol_info)
{
switch (eol_info) {
case 0x0:
return "Not defined";
case 0x1:
return "Normal";
case 0x2:
return "Warning (Consumed 80% of reserved blocks)";
case 0x3:
return "Urgent";
default:
return "Unknown";
}
}
static char *stringify_mmc_health(char *buf, const char *end,
const MmcHealthData *data)
{
const char *const ver_str[] = {
[EXT_CSD_REV_1_0] = "4.0",
[EXT_CSD_REV_1_1] = "4.1",
[EXT_CSD_REV_1_2] = "4.2",
[EXT_CSD_REV_1_3] = "4.3",
[EXT_CSD_REV_1_4] = "Obsolete",
[EXT_CSD_REV_1_5] = "4.41",
[EXT_CSD_REV_1_6] = "4.5",
[EXT_CSD_REV_1_7] = "5.0",
[EXT_CSD_REV_1_8] = "5.1",
};
if (data->csd_rev >= ARRAY_SIZE(ver_str) ||
data->csd_rev == EXT_CSD_REV_1_4) {
buf += snprintf(buf, end - buf,
"Unsupported Extended CSD rev 1.%d\n",
data->csd_rev);
return buf;
}
buf += snprintf(buf, end - buf, "Extended CSD rev 1.%d (MMC %s)\n",
data->csd_rev, ver_str[data->csd_rev]);
/* >= eMMC 5.0 */
if (data->csd_rev >= EXT_CSD_REV_1_7) {
buf += snprintf(buf, end - buf,
"eMMC Life Time Estimation A "
"[EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A]: %#02x\n"
" i.e. ",
data->device_life_time_est_type_a);
buf = stringify_mmc_device_lifetime(
buf, end - buf, data->device_life_time_est_type_a);
buf += snprintf(buf, end - buf, "\n");
buf += snprintf(buf, end - buf,
"eMMC Life Time Estimation B "
"[EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B]: %#02x\n"
" i.e. ",
data->device_life_time_est_type_b);
buf = stringify_mmc_device_lifetime(
buf, end - buf, data->device_life_time_est_type_b);
buf += snprintf(buf, end - buf, "\n");
buf += snprintf(buf, end - buf,
"eMMC Pre EOL information "
"[EXT_CSD_PRE_EOL_INFO]: %#02x\n"
" i.e. %s\n",
data->pre_eol_info,
mmc_eol_info_to_str(data->pre_eol_info));
}
return buf;
}
// Append the stringified health_info to string str and return the pointer of
// next available address.
char *stringify_health_info(char *buf, const char *end, const HealthInfo *info)
{
switch (info->type) {
case STORAGE_INFO_TYPE_NVME:
if (!IS_ENABLED(CONFIG_NVME))
break;
return stringify_nvme_smart(buf, end, &info->data.nvme_data);
case STORAGE_INFO_TYPE_MMC:
if (!IS_ENABLED(CONFIG_MMC))
break;
return stringify_mmc_health(buf, end, &info->data.mmc_data);
case STORAGE_INFO_TYPE_UNKNOWN:
break;
}
printf("%s: unsupported data type: %d\n", __func__, info->type);
assert(0);
return NULL;
}
char *dump_all_health_info(char *buf, const char *end)
{
int n = blk_count_devices(BLKF_FIXED);
if (!n) {
buf += snprintf(buf, end - buf, "No storage device found\n\n");
return buf;
}
buf += snprintf(buf, end - buf, "Total %d storage device%s\n\n", n,
n > 1 ? "s" : "");
// Fill them from the BlockDev structures.
struct udevice *dev;
int idx = 1;
blk_foreach_probe(BLKF_FIXED, dev) {
if (0 /* bdev->ops.get_health_info */) {
/* TODO(sjg@chromium.org): Implement this */
#if 0
HealthInfo info = {0};
int res = bdev->ops.get_health_info(&bdev->ops, &info);
if (res) {
buf += snprintf(
buf, end - buf,
"%s: Get Health info error: %d\n",
bdev->name, res);
continue;
}
buf += snprintf(buf, end - buf,
"(%d/%d) Block device '%s':\n", idx, n,
bdev->name);
buf = stringify_health_info(buf, end, &info);
if (idx < n)
buf += snprintf(buf, end - buf, "\n");
idx += 1;
#endif
} else {
buf += snprintf(buf, end - buf,
"(%d/%d) Block device '%s' does not "
"provide health info.\n",
idx, n, dev->name);
}
}
*buf = '\0';
return buf;
}