// SPDX-License-Identifier: GPL-2.0-or-later

#include <errno.h>
#include <inttypes.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <sys/time.h>

#include <libnvme-mi.h>
#include <libnvme.h>

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

#include "logging.h"
#include "nvme-print.h"

struct submit_data {
	struct timeval start;
	struct timeval end;
};

int log_level;
static struct submit_data sb;

bool is_printable_at_level(int level)
{
	return ((log_level >= level) &&
		(strcmp(nvme_args.output_format, "normal") == 0));
}

int map_log_level(int verbose, bool quiet)
{
	if (quiet)
		return LIBNVME_LOG_ERR;

	switch (verbose) {
	case 0:
		return LIBNVME_LOG_ERR;
	case 1:
		return LIBNVME_LOG_INFO;
	case 2:
		return LIBNVME_LOG_DEBUG;
	default:
		break;
	}

	return LIBNVME_LOG_DEBUG_VERBOSE;
}

static void nvme_show_common(struct libnvme_passthru_cmd *cmd)
{
	nvme_show_key_value("opcode       ", "%02x", cmd->opcode);
	nvme_show_key_value("flags        ", "%02x", cmd->flags);
	nvme_show_key_value("rsvd1        ", "%04x", cmd->rsvd1);
	nvme_show_key_value("nsid         ", "%08x", cmd->nsid);
	nvme_show_key_value("cdw2         ", "%08x", cmd->cdw2);
	nvme_show_key_value("cdw3         ", "%08x", cmd->cdw3);
	nvme_show_key_value("data_len     ", "%08x", cmd->data_len);
	nvme_show_key_value("metadata_len ", "%08x", cmd->metadata_len);
	nvme_show_key_value("addr         ", "%"PRIx64"", (uint64_t)(uintptr_t)cmd->addr);
	nvme_show_key_value("metadata     ", "%"PRIx64"", (uint64_t)(uintptr_t)cmd->metadata);
	nvme_show_key_value("cdw10        ", "%08x", cmd->cdw10);
	nvme_show_key_value("cdw11        ", "%08x", cmd->cdw11);
	nvme_show_key_value("cdw12        ", "%08x", cmd->cdw12);
	nvme_show_key_value("cdw13        ", "%08x", cmd->cdw13);
	nvme_show_key_value("cdw14        ", "%08x", cmd->cdw14);
	nvme_show_key_value("cdw15        ", "%08x", cmd->cdw15);
	nvme_show_key_value("timeout_ms   ", "%08x", cmd->timeout_ms);
}

static void nvme_show_command(struct libnvme_passthru_cmd *cmd, int err)
{
	nvme_show_common(cmd);
	nvme_show_key_value("result       ", "%"PRIx64"", (uint64_t)(uintptr_t)cmd->result);
	nvme_show_key_value("err          ", "%d", err);
}

static void nvme_show_latency(struct timeval start, struct timeval end)
{
	nvme_show_key_value("latency      ", "%llu us",
			    (unsigned long long)((end.tv_sec - start.tv_sec) * 1000000 +
						 (end.tv_usec - start.tv_usec)));
}

static void nvme_show_data(const char *data, uintptr_t addr, int len)
{
	if (len) {
		nvme_show_key_value(data, "%lu bytes", len);
		d((unsigned char *)addr, len, 16, 1);
	}
}

static void nvme_show_datum(struct libnvme_passthru_cmd *cmd)
{
	nvme_show_data("data", (uintptr_t)cmd->addr, cmd->data_len);
	nvme_show_data("metadata", (uintptr_t)cmd->metadata, cmd->metadata_len);
}

static void nvme_log_retry(int errnum)
{
	if (log_level < LIBNVME_LOG_DEBUG)
		return;

	print_debug("passthru command returned '%s'\n", libnvme_strerror(errnum));
}

void *nvme_submit_entry(struct libnvme_transport_handle *hdl,
		struct libnvme_passthru_cmd *cmd)
{
	memset(&sb, 0, sizeof(sb));

	if (log_level >= LIBNVME_LOG_DEBUG)
		gettimeofday(&sb.start, NULL);

	return &sb;
}

void nvme_submit_exit(struct libnvme_transport_handle *hdl,
		struct libnvme_passthru_cmd *cmd, int err, void *user_data)
{
	struct submit_data *sb = user_data;

	if (log_level >= LIBNVME_LOG_DEBUG) {
		gettimeofday(&sb->end, NULL);
		nvme_show_command(cmd, err);
		nvme_show_latency(sb->start, sb->end);
		nvme_show_datum(cmd);
	}
}

bool nvme_decide_retry(struct libnvme_transport_handle *hdl,
		struct libnvme_passthru_cmd *cmd, int err)
{
	if (!nvme_args.no_retries)
		return false;

	if (err != -EAGAIN ||
	    !(err == -EINTR && !shr_sigint_received))
		return false;

	nvme_log_retry(errno);
	return true;
}

static void nvme_show_req_admin(const struct nvme_mi_admin_req_hdr *hdr, size_t hdr_len,
				const void *data, size_t data_len)
{
	struct libnvme_passthru_cmd cmd = {
		.opcode = hdr->opcode,
		.flags = hdr->flags,
		.nsid = le32_to_cpu(hdr->cdw1),
		.cdw2 = le32_to_cpu(hdr->cdw2),
		.cdw3 = le32_to_cpu(hdr->cdw3),
		.addr = (uint64_t)(uintptr_t)data,
		.data_len = data_len,
		.cdw10 = le32_to_cpu(hdr->cdw10),
		.cdw11 = le32_to_cpu(hdr->cdw11),
		.cdw12 = le32_to_cpu(hdr->cdw12),
		.cdw13 = le32_to_cpu(hdr->cdw13),
		.cdw14 = le32_to_cpu(hdr->cdw14),
		.cdw15 = le32_to_cpu(hdr->cdw15),
	};

	nvme_show_common(&cmd);
	nvme_show_key_value("doff         ", "%08x", le32_to_cpu(hdr->doff));
	nvme_show_key_value("dlen         ", "%08x", le32_to_cpu(hdr->dlen));
}

static void nvme_show_req(__u8 type, const struct nvme_mi_msg_hdr *hdr, size_t hdr_len,
			  const void *data, size_t data_len)
{
	if (type != NVME_MI_MSGTYPE_NVME)
		return;

	switch (hdr->nmp >> 3 & 0xf) {
	case NVME_MI_MT_CONTROL:
		break;
	case NVME_MI_MT_MI:
		break;
	case NVME_MI_MT_ADMIN:
		nvme_show_req_admin((struct nvme_mi_admin_req_hdr *)hdr, hdr_len, data, data_len);
		break;
	case NVME_MI_MT_PCIE:
		break;
	case NVME_MI_MT_AE:
		break;
	default:
		break;
	}
}

void *nvme_mi_submit_entry(struct libnvme_mi_ep *ep, __u8 type,
			   const struct nvme_mi_msg_hdr *hdr, size_t hdr_len,
			   const void *data, size_t data_len)
{
	memset(&sb, 0, sizeof(sb));

	if (log_level >= LIBNVME_LOG_DEBUG) {
		nvme_show_req(type, hdr, hdr_len, data, data_len);
		gettimeofday(&sb.start, NULL);
	}

	return &sb;
}

static void nvme_show_resp_admin(const struct nvme_mi_admin_resp_hdr *hdr, size_t hdr_len,
				 const void *data, size_t data_len)
{
	nvme_show_key_value("result       ", "%08x", le32_to_cpu(hdr->cdw0));
	nvme_show_key_value("err          ", "%d", hdr->status);
}

static void nvme_show_resp(__u8 type, const struct nvme_mi_msg_hdr *hdr, size_t hdr_len,
			   const void *data, size_t data_len)
{
	if (type != NVME_MI_MSGTYPE_NVME)
		return;

	switch (hdr->nmp >> 3 & 0xf) {
	case NVME_MI_MT_CONTROL:
		break;
	case NVME_MI_MT_MI:
		break;
	case NVME_MI_MT_ADMIN:
		nvme_show_resp_admin((struct nvme_mi_admin_resp_hdr *)hdr, hdr_len, data, data_len);
		break;
	case NVME_MI_MT_PCIE:
		break;
	case NVME_MI_MT_AE:
		break;
	default:
		break;
	}
}

void nvme_mi_submit_exit(struct libnvme_mi_ep *ep, __u8 type,
			 const struct nvme_mi_msg_hdr *hdr, size_t hdr_len,
			 const void *data, size_t data_len, void *user_data)
{
	struct submit_data *sb = user_data;

	if (log_level >= LIBNVME_LOG_DEBUG) {
		gettimeofday(&sb->end, NULL);
		nvme_show_resp(type, hdr, hdr_len, data, data_len);
		nvme_show_latency(sb->start, sb->end);
	}
}
