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

#include <inttypes.h>

#include <libnvme.h>

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

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

static struct lm_print_ops stdout_print_ops;

static void stdout_controller_state_data(struct nvme_lm_controller_state_data *data, size_t len,
					 __u32 offset)
{
	if (offset) {
		nvme_show_error("cannot understand non-zero offset");
		return;
	}

	int human = stdout_print_ops.flags & VERBOSE;

	if (sizeof(struct nvme_lm_controller_state_data_header) <= len) {
		printf("Header:\n");
		printf("%-45s: 0x%x\n", "Version (VER)", data->hdr.ver);
		printf("%-45s: 0x%x\n", "Controller State Attributes (CSATTR)", data->hdr.csattr);
		if (human)
			printf("  [0:0] : 0x%x Controller %sSuspended\n",
				data->hdr.csattr & 1, data->hdr.csattr & 1 ? "" : "NOT ");
		printf("%-45s: %s\n", "NVMe Controller State Size (NVMECSS)",
		       uint128_t_to_string(le128_to_cpu(data->hdr.nvmecss)));
		printf("%-45s: %s\n", "Vendor Specific Size (VSS)",
		       uint128_t_to_string(le128_to_cpu(data->hdr.vss)));

		len -= sizeof(struct nvme_lm_controller_state_data_header);
	} else {
		nvme_show_error("WARNING: Header truncated");
		len = 0;
	}

	if (!len)
		return;

	if (sizeof(struct nvme_lm_nvme_controller_state_data_header) <= len) {
		int niosq = data->data.hdr.niosq;
		int niocq = data->data.hdr.niocq;

		printf("\nNVMe Controller State Data Structure:\n");
		printf("%-45s: 0x%x\n", "Version (VER)",
		       le16_to_cpu(data->data.hdr.ver));
		printf("%-45s: %d\n", "Number of I/O Submission Queues (NIOSQ)",
		       le16_to_cpu(niosq));
		printf("%-45s: %d\n", "Number of I/O Completion Queues (NIOCQ)",
		       le16_to_cpu(niocq));

		len -= sizeof(struct nvme_lm_nvme_controller_state_data_header);

		if (len < niosq * sizeof(struct nvme_lm_io_submission_queue_data)) {
			nvme_show_error("WARNING: I/O Submission Queues truncated");
			niosq = len / sizeof(struct nvme_lm_io_submission_queue_data);
		}

		for (int i = 0; i < niosq; ++i) {
			struct nvme_lm_io_submission_queue_data *sq = &(data->data.sqs[i]);
			__u16 iosqa = le16_to_cpu(sq->iosqa);

			printf("\nNVMe I/O Submission Queue Data [%d]:\n", i);
			printf("%-45s: 0x%"PRIu64"\n", "PRP Entry 1 (IOSQPRP1)",
			       le64_to_cpu(sq->iosqprp1));
			printf("%-45s: 0x%x\n", "Queue Size (IOSQQSIZE)",
			       le16_to_cpu(sq->iosqqsize));
			printf("%-45s: 0x%x\n", "Identifier (IOSQQID)",
			       le16_to_cpu(sq->iosqqid));
			printf("%-45s: 0x%x\n", "Completion Queue Identifier (IOSQCQID)",
			       le16_to_cpu(sq->iosqcqid));
			printf("%-45s: 0x%x\n", "Attributes (IOSQA)", iosqa);
			if (human) {
				printf("  [2:1] : 0x%x Queue Priority (IOSQQPRIO)\n",
				       NVME_GET(iosqa, LM_IOSQPRIO));
				printf("  [0:0] : 0x%x Queue %sPhysically Contiguous (IOSQPC)\n",
				       NVME_GET(iosqa, LM_IOSQPC),
				       NVME_GET(iosqa, LM_IOSQPC) ? "" : "NOT ");
			}
			printf("%-45s: 0x%x\n", "I/O Submission Queue Head Pointer (IOSQHP)",
			       le16_to_cpu(sq->iosqhp));
			printf("%-45s: 0x%x\n", "I/O Submission Queue Tail Pointer (IOSQTP)",
			       le16_to_cpu(sq->iosqtp));
		}

		len -= niosq * sizeof(struct nvme_lm_io_submission_queue_data);

		if (len < niocq * sizeof(struct nvme_lm_io_completion_queue_data)) {
			nvme_show_error("WARNING: I/O Completion Queues truncated");
			niocq = len / sizeof(struct nvme_lm_io_completion_queue_data);
		}

		for (int i = 0; i < niocq; ++i) {
			struct nvme_lm_io_completion_queue_data *cq = &data->data.cqs[niosq + i];
			__u32 iocqa = le32_to_cpu(cq->iocqa);

			printf("\nNVMe I/O Completion Queue Data [%d]:\n", i);
			printf("%-45s: 0x%"PRIu64"\n", "I/O Completion PRP Entry 1 (IOCQPRP1)",
			       le64_to_cpu(cq->iocqprp1));
			printf("%-45s: 0x%x\n", "I/O Completion Queue Size (IOCQQSIZE)",
			       le16_to_cpu(cq->iocqqsize));
			printf("%-45s: 0x%x\n", "I/O Completion Queue Identifier (IOCQQID)",
			       le16_to_cpu(cq->iocqqid));
			printf("%-45s: 0x%x\n", "I/O Completion Queue Head Pointer (IOSQHP)",
			       le16_to_cpu(cq->iocqhp));
			printf("%-45s: 0x%x\n", "I/O Completion Queue Tail Pointer (IOSQTP)",
			       le16_to_cpu(cq->iocqtp));
			printf("%-45s: 0x%x\n", "I/O Completion Queue Attributes (IOCQA)", iocqa);
			if (human) {
				printf("  [31:16] : 0x%x I/O Completion Queue Interrupt Vector "
				       "(IOCQIV)\n",
				       NVME_GET(iocqa, LM_IOCQIEN));
				printf("  [2:2] : 0x%x Slot 0 Phase Tag (S0PT)\n",
				       NVME_GET(iocqa, LM_S0PT));
				printf("  [1:1] : 0x%x Interrupts %sEnabled (IOCQIEN)\n",
				       NVME_GET(iocqa, LM_IOCQIEN),
				       NVME_GET(iocqa, LM_IOCQIEN) ? "" : "NOT ");
				printf("  [0:0] : 0x%x Queue %sPhysically Contiguous (IOCQPC)\n",
				       NVME_GET(iocqa, LM_IOCQPC),
				       NVME_GET(iocqa, LM_IOCQPC) ? "" : "NOT ");
			}
		}
	} else
		nvme_show_error("WARNING: NVMe Controller State Data Structure truncated");
}

static void stdout_show_controller_data_queue(struct nvme_lm_ctrl_data_queue_fid_data *data)
{
	printf("Head Pointer: 0x%x\n", le32_to_cpu(data->hp));
	printf("Tail Pointer Trigger: 0x%x\n", le32_to_cpu(data->tpt));
}

static struct lm_print_ops stdout_print_ops = {
	.controller_state_data = stdout_controller_state_data,
	.controller_data_queue = stdout_show_controller_data_queue
};

struct lm_print_ops *lm_get_stdout_print_ops(nvme_print_flags_t flags)
{
	stdout_print_ops.flags = flags;
	return &stdout_print_ops;
}
