/*
 * Copyright (c) 2022 Intel Corporation
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(nvme, CONFIG_NVME_LOG_LEVEL);

#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>

#include "nvme.h"

static int nvme_disk_status(struct disk_info *disk)
{
	return 0;
}

static int nvme_disk_read(struct disk_info *disk,
			  uint8_t *data_buf,
			  uint32_t start_sector,
			  uint32_t num_sector)
{
	struct nvme_namespace *ns = CONTAINER_OF(disk->name,
						 struct nvme_namespace, name[0]);
	struct nvme_completion_poll_status status =
		NVME_CPL_STATUS_POLL_INIT(status);
	struct nvme_request *request;
	uint32_t payload_size;
	int ret = 0;

	if (!NVME_IS_BUFFER_DWORD_ALIGNED(data_buf)) {
		LOG_WRN("Data buffer pointer needs to be 4-bytes aligned");
		return -EINVAL;
	}

	nvme_lock(disk->dev);

	payload_size = num_sector * nvme_namespace_get_sector_size(ns);

	request = nvme_allocate_request_vaddr((void *)data_buf, payload_size,
					      nvme_completion_poll_cb, &status);
	if (request == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	nvme_namespace_read_cmd(&request->cmd, ns->id,
				start_sector, num_sector);

	/* We use only the first ioq atm
	 * ToDo: use smp cpu id and use it to select ioq
	 */
	nvme_cmd_qpair_submit_request(ns->ctrlr->ioq, request);

	nvme_completion_poll(&status);
	if (nvme_cpl_status_is_error(&status)) {
		LOG_WRN("Reading at sector %u (count %d) on disk %s failed",
			start_sector, num_sector, ns->name);
		nvme_completion_print(&status.cpl);
		ret = -EIO;
	}
out:
	nvme_unlock(disk->dev);
	return ret;
}

static int nvme_disk_write(struct disk_info *disk,
			   const uint8_t *data_buf,
			   uint32_t start_sector,
			   uint32_t num_sector)
{
	struct nvme_namespace *ns = CONTAINER_OF(disk->name,
						 struct nvme_namespace, name[0]);
	struct nvme_completion_poll_status status =
		NVME_CPL_STATUS_POLL_INIT(status);
	struct nvme_request *request;
	uint32_t payload_size;
	int ret = 0;

	if (!NVME_IS_BUFFER_DWORD_ALIGNED(data_buf)) {
		LOG_WRN("Data buffer pointer needs to be 4-bytes aligned");
		return -EINVAL;
	}

	nvme_lock(disk->dev);

	payload_size = num_sector * nvme_namespace_get_sector_size(ns);

	request = nvme_allocate_request_vaddr((void *)data_buf, payload_size,
					      nvme_completion_poll_cb, &status);
	if (request == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	nvme_namespace_write_cmd(&request->cmd, ns->id,
				 start_sector, num_sector);

	/* We use only the first ioq atm
	 * ToDo: use smp cpu id and use it to select ioq
	 */
	nvme_cmd_qpair_submit_request(ns->ctrlr->ioq, request);

	nvme_completion_poll(&status);
	if (nvme_cpl_status_is_error(&status)) {
		LOG_WRN("Writing at sector %u (count %d) on disk %s failed",
			start_sector, num_sector, ns->name);
		nvme_completion_print(&status.cpl);
		ret = -EIO;
	}
out:
	nvme_unlock(disk->dev);
	return ret;
}

static int nvme_disk_flush(struct nvme_namespace *ns)
{
	struct nvme_completion_poll_status status =
		NVME_CPL_STATUS_POLL_INIT(status);
	struct nvme_request *request;

	request = nvme_allocate_request_null(nvme_completion_poll_cb, &status);
	if (request == NULL) {
		return -ENOMEM;
	}

	nvme_namespace_flush_cmd(&request->cmd, ns->id);

	/* We use only the first ioq
	 * ToDo: use smp cpu id and use it to select ioq
	 */
	nvme_cmd_qpair_submit_request(ns->ctrlr->ioq, request);

	nvme_completion_poll(&status);
	if (nvme_cpl_status_is_error(&status)) {
		LOG_ERR("Flushing disk %s failed", ns->name);
		nvme_completion_print(&status.cpl);
		return -EIO;
	}

	return 0;
}

static int nvme_disk_ioctl(struct disk_info *disk, uint8_t cmd, void *buff)
{
	struct nvme_namespace *ns = CONTAINER_OF(disk->name,
						 struct nvme_namespace, name[0]);
	int ret = 0;

	nvme_lock(disk->dev);

	switch (cmd) {
	case DISK_IOCTL_GET_SECTOR_COUNT:
		if (!buff) {
			ret = -EINVAL;
			break;
		}

		*(uint32_t *)buff = nvme_namespace_get_num_sectors(ns);

		break;
	case DISK_IOCTL_GET_SECTOR_SIZE:
		if (!buff) {
			ret = -EINVAL;
			break;
		}

		*(uint32_t *)buff = nvme_namespace_get_sector_size(ns);

		break;
	case DISK_IOCTL_GET_ERASE_BLOCK_SZ:
		if (!buff) {
			ret = -EINVAL;
			break;
		}

		*(uint32_t *)buff = nvme_namespace_get_sector_size(ns);

		break;
	case DISK_IOCTL_CTRL_DEINIT:
	case DISK_IOCTL_CTRL_SYNC:
		ret = nvme_disk_flush(ns);
		break;
	case DISK_IOCTL_CTRL_INIT:
		ret = 0;
		break;
	default:
		ret = -EINVAL;
	}

	nvme_unlock(disk->dev);
	return ret;
}

static int nvme_disk_init(struct disk_info *disk)
{
	return nvme_disk_ioctl(disk, DISK_IOCTL_CTRL_INIT, NULL);
}

static const struct disk_operations nvme_disk_ops = {
	.init = nvme_disk_init,
	.status = nvme_disk_status,
	.read = nvme_disk_read,
	.write = nvme_disk_write,
	.ioctl = nvme_disk_ioctl,
};

int nvme_namespace_disk_setup(struct nvme_namespace *ns,
			      struct disk_info *disk)
{
	disk->name = ns->name;
	disk->ops = &nvme_disk_ops;
	disk->dev = ns->ctrlr->dev;

	return disk_access_register(disk);
}
