// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2024
 */
#include <errno.h>
#include <stdio.h>

#include <libnvme.h>

#include <ccan/array_size/array_size.h>
#include <ccan/endian/endian.h>
#include <shared/uint128-util.h>

#include "cleanup.h"
#include "global-ctx.h"
#include "logging.h"
#include "nvme-print.h"
#include "ocp-hardware-component-log.h"
#include "ocp-print.h"
#include "ocp-utils.h"
#include "plugin.h"

//#define HWCOMP_DUMMY

#define print_info_array(...) \
	do { \
		if (log_level >= LIBNVME_LOG_INFO) \
			print_array(__VA_ARGS__); \
	} while (false)

#define print_info_error(...) \
	do { \
		if (log_level >= LIBNVME_LOG_INFO) \
			nvme_show_error( __VA_ARGS__); \
	} while (false)

#ifdef HWCOMP_DUMMY
static __u8 hwcomp_dummy[] = {
	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0xdc, 0x57, 0x0f, 0x9f, 0xb9, 0x31, 0x6b, 0xb7,
	0xd0, 0x4e, 0xcd, 0x30, 0x1f, 0x82, 0xb6, 0xbc,
	0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
	0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
};
#endif /* HWCOMP_DUMMY */

const char *hwcomp_id_to_string(__u32 id)
{
	switch (id) {
	case HWCOMP_ID_ASIC:
		return "Controller ASIC component";
	case HWCOMP_ID_NAND:
		return "NAND Component";
	case HWCOMP_ID_DRAM:
		return "DRAM Component";
	case HWCOMP_ID_PMIC:
		return "PMIC Component";
	case HWCOMP_ID_PCB:
		return "PCB Component";
	case HWCOMP_ID_CAP:
		return "capacitor component";
	case HWCOMP_ID_REG:
		return "registor component";
	case HWCOMP_ID_CASE:
		return "case component";
	case HWCOMP_ID_SN:
		return "Device Serial Number";
	case HWCOMP_ID_COUNTRY:
		return "Country of Origin";
	case HWCOMP_ID_HW_REV:
		return "Global Device Hardware Revision";
	case HWCOMP_ID_BORN_ON_DATE:
		return "Born on Date";
	case HWCOMP_ID_VENDOR ... HWCOMP_ID_MAX:
		return "Vendor Unique Component";
	case HWCOMP_ID_RSVD:
	default:
		break;
	}

	return "Reserved";
}

static int get_hwcomp_log_data(struct libnvme_transport_handle *hdl, struct hwcomp_log *log)
{
	size_t desc_offset = offsetof(struct hwcomp_log, desc);
	struct libnvme_passthru_cmd cmd;
	shr_uint128_t log_size;
	long double log_bytes;
	__u32 len;
	__u8 uidx;
	int ret;

	ret = ocp_get_uuid_index(hdl, &uidx);
	if (ret < 0)
		return ret;

#ifdef HWCOMP_DUMMY
	memcpy(log, hwcomp_dummy, desc_offset);
#else /* HWCOMP_DUMMY */
	nvme_init_get_log(&cmd, NVME_NSID_ALL,
			  (enum nvme_cmd_get_log_lid)OCP_LID_HWCOMP,
			  NVME_CSI_NVM, log, desc_offset);
	cmd.cdw14 |= NVME_FIELD_ENCODE(uidx,
				       NVME_LOG_CDW14_UUID_SHIFT,
				       NVME_LOG_CDW14_UUID_MASK);
	ret = libnvme_get_log(hdl, &cmd, false, NVME_LOG_PAGE_PDU_SIZE);
	if (ret) {
		print_info_error("error: ocp: failed to get hwcomp log size (ret: %d)\n", ret);
		return ret;
	}
#endif /* HWCOMP_DUMMY */

	log_size = le128_to_cpu(log->size);

	print_info("id: %02Xh\n", OCP_LID_HWCOMP);
	print_info("version: %04Xh\n", log->ver);
	print_info_array("guid", log->guid, ARRAY_SIZE(log->guid));
	print_info("size: %s\n", uint128_t_to_string(log_size));

	log_bytes = uint128_t_to_double(log_size);
	if (log->ver == 1)
		log_bytes *= sizeof(__le32);

	if (log_bytes <= desc_offset) {
		print_info_error("error: ocp: invalid hwcomp log size bytes: %.0Lf\n", log_bytes);
		return -EINVAL;
	}

	len = log_bytes - desc_offset;

	print_info("args.len: %u\n", len);

	log->desc = calloc(1, len);
	if (!log->desc) {
		nvme_show_error("error: ocp: calloc: %s", libnvme_strerror(errno));
		return -errno;
	}

#ifdef HWCOMP_DUMMY
	memcpy(log->desc, &hwcomp_dummy[desc_offset], len);
#else /* HWCOMP_DUMMY */
	nvme_init_get_log(&cmd, NVME_NSID_ALL,
			  (enum nvme_cmd_get_log_lid)OCP_LID_HWCOMP,
			  NVME_CSI_NVM, log->desc, len);
	nvme_init_get_log_lpo(&cmd, desc_offset);
	ret = libnvme_get_log(hdl, &cmd, false, NVME_LOG_PAGE_PDU_SIZE);
	if (ret) {
		print_info_error("error: ocp: failed to get log page (hwcomp: %02X, ret: %d)\n",
				 OCP_LID_HWCOMP, ret);
		free(log->desc);
		return ret;
	}
#endif /* HWCOMP_DUMMY */

	return ret;
}

static int get_hwcomp_log(struct libnvme_transport_handle *hdl, __u32 id, bool list)
{
	int ret;
	nvme_print_flags_t fmt;
	struct hwcomp_log log = {
		.desc = NULL,
	};

	ret = validate_output_format(nvme_args.output_format, &fmt);
	if (ret < 0) {
		nvme_show_error("error: ocp: invalid output format");
		return ret;
	}

	ret = get_hwcomp_log_data(hdl, &log);
	if (ret) {
		print_info_error("error: ocp: failed get hwcomp log: %02X data, ret: %d\n",
				 OCP_LID_HWCOMP, ret);
		return ret;
	}

	ocp_show_hwcomp_log(&log, id, list, fmt);

	free(log.desc);

	return 0;
}

int ocp_hwcomp_log(int argc, char **argv, struct command *acmd, struct plugin *plugin)
{
	__cleanup_nvme_global_ctx struct libnvme_global_ctx *ctx = NULL;
	__cleanup_nvme_transport_handle struct libnvme_transport_handle *hdl = NULL;
	int ret = 0;
	const char *desc = "retrieve hardware component log";
	struct config {
		__u64 id;
		bool list;
	} cfg = { 0 };
	const char *id_desc = "component identifier";
	const char *list_desc = "list component descriptions";

	OPT_VALS(id) = {
		VAL_LONG("asic", HWCOMP_ID_ASIC),
		VAL_LONG("nand", HWCOMP_ID_NAND),
		VAL_LONG("dram", HWCOMP_ID_DRAM),
		VAL_LONG("pmic", HWCOMP_ID_PMIC),
		VAL_LONG("pcb", HWCOMP_ID_PCB),
		VAL_LONG("cap", HWCOMP_ID_CAP),
		VAL_LONG("reg", HWCOMP_ID_REG),
		VAL_LONG("case", HWCOMP_ID_CASE),
		VAL_LONG("sn", HWCOMP_ID_SN),
		VAL_LONG("country", HWCOMP_ID_COUNTRY),
		VAL_LONG("hw-rev", HWCOMP_ID_HW_REV),
		VAL_LONG("born-on-date", HWCOMP_ID_BORN_ON_DATE),
		VAL_LONG("vendor", HWCOMP_ID_VENDOR),
		VAL_END()
	};

	NVME_ARGS(opts, OPT_LONG("comp-id", 'i', &cfg.id, id_desc, id),
		  OPT_FLAG("list", 'l', &cfg.list, list_desc));

	ret = parse_and_open(&ctx, &hdl, argc, argv, desc, opts);
	if (ret)
		return ret;

	ret = get_hwcomp_log(hdl, cfg.id, cfg.list);
	if (ret)
		nvme_show_error("error: ocp: failed to get hwcomp log: %02X, ret: %d",
			OCP_LID_HWCOMP, ret);

	return ret;
}
