/* SPDX-License-Identifier: MIT */
/*
 * Description: Helpers for NVMe uring passthrough commands
 */
#ifndef LIBURING_NVME_H
#define LIBURING_NVME_H

#ifdef __cplusplus
extern "C" {
#endif

#include <sys/ioctl.h>
#include <linux/nvme_ioctl.h>

/*
 * If the uapi headers installed on the system lacks nvme uring command
 * support, use the local version to prevent compilation issues.
 */
#ifndef CONFIG_HAVE_NVME_URING
struct nvme_uring_cmd {
	__u8	opcode;
	__u8	flags;
	__u16	rsvd1;
	__u32	nsid;
	__u32	cdw2;
	__u32	cdw3;
	__u64	metadata;
	__u64	addr;
	__u32	metadata_len;
	__u32	data_len;
	__u32	cdw10;
	__u32	cdw11;
	__u32	cdw12;
	__u32	cdw13;
	__u32	cdw14;
	__u32	cdw15;
	__u32	timeout_ms;
	__u32   rsvd2;
};

#define NVME_URING_CMD_IO	_IOWR('N', 0x80, struct nvme_uring_cmd)
#define NVME_URING_CMD_IO_VEC	_IOWR('N', 0x81, struct nvme_uring_cmd)
#endif /* CONFIG_HAVE_NVME_URING */

#define NVME_DEFAULT_IOCTL_TIMEOUT 0
#define NVME_IDENTIFY_DATA_SIZE 4096
#define NVME_IDENTIFY_CSI_SHIFT 24
#define NVME_IDENTIFY_CNS_NS 0
#define NVME_CSI_NVM 0

enum nvme_admin_opcode {
	nvme_admin_identify		= 0x06,
};

enum nvme_io_opcode {
	nvme_cmd_write			= 0x01,
	nvme_cmd_read			= 0x02,
};

static int nsid;
static __u32 lba_shift;
static __u32 meta_size;

struct nvme_lbaf {
	__le16			ms;
	__u8			ds;
	__u8			rp;
};

struct nvme_id_ns {
	__le64			nsze;
	__le64			ncap;
	__le64			nuse;
	__u8			nsfeat;
	__u8			nlbaf;
	__u8			flbas;
	__u8			mc;
	__u8			dpc;
	__u8			dps;
	__u8			nmic;
	__u8			rescap;
	__u8			fpi;
	__u8			dlfeat;
	__le16			nawun;
	__le16			nawupf;
	__le16			nacwu;
	__le16			nabsn;
	__le16			nabo;
	__le16			nabspf;
	__le16			noiob;
	__u8			nvmcap[16];
	__le16			npwg;
	__le16			npwa;
	__le16			npdg;
	__le16			npda;
	__le16			nows;
	__le16			mssrl;
	__le32			mcl;
	__u8			msrc;
	__u8			rsvd81[11];
	__le32			anagrpid;
	__u8			rsvd96[3];
	__u8			nsattr;
	__le16			nvmsetid;
	__le16			endgid;
	__u8			nguid[16];
	__u8			eui64[8];
	struct nvme_lbaf	lbaf[16];
	__u8			rsvd192[192];
	__u8			vs[3712];
};

static inline int ilog2(uint32_t i)
{
	int log = -1;

	while (i) {
		i >>= 1;
		log++;
	}
	return log;
}

__attribute__((__unused__))
static int nvme_get_info(const char *file)
{
	struct nvme_id_ns ns;
	int fd, err;
	__u32 lba_size;

	fd = open(file, O_RDONLY);
	if (fd < 0) {
		perror("file open");
		return -errno;
	}

	nsid = ioctl(fd, NVME_IOCTL_ID);
	if (nsid < 0) {
		close(fd);
		return -errno;
	}

	struct nvme_passthru_cmd cmd = {
		.opcode         = nvme_admin_identify,
		.nsid           = nsid,
		.addr           = (__u64)(uintptr_t)&ns,
		.data_len       = NVME_IDENTIFY_DATA_SIZE,
		.cdw10          = NVME_IDENTIFY_CNS_NS,
		.cdw11          = NVME_CSI_NVM << NVME_IDENTIFY_CSI_SHIFT,
		.timeout_ms     = NVME_DEFAULT_IOCTL_TIMEOUT,
	};

	err = ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd);
	if (err) {
		close(fd);
		return err;
	}

	lba_size = 1 << ns.lbaf[(ns.flbas & 0x0f)].ds;
	lba_shift = ilog2(lba_size);
	meta_size = ns.lbaf[(ns.flbas & 0x0f)].ms;

	close(fd);
	return 0;
}

#ifdef __cplusplus
}
#endif

#endif
