/*
 * Copyright (C) 2016 CNEX Labs.  All rights reserved.
 *
 * Author: Matias Bjoerling <matias@cnexlabs.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
 * USA.
 */

#ifndef NVME_LIGHTNVM_H_
#define NVME_LIGHTNVM_H_

#include "linux/lightnvm.h"

enum nvme_nvm_admin_opcode {
	nvme_nvm_admin_identity		= 0xe2,
	nvme_nvm_admin_get_bb_tbl	= 0xf2,
	nvme_nvm_admin_set_bb_tbl	= 0xf1,
};

struct nvme_nvm_identity {
	__u8	opcode;
	__u8	flags;
	__le16	command_id;
	__le32	nsid;
	__le64	rsvd[2];
	__le64	prp1;
	__le64	prp2;
	__le32	chnl_off;
	__le32	rsvd11[5];
};

struct nvme_nvm_setbbtbl {
	__u8	opcode;
	__u8	flags;
	__le16	rsvd1;
	__le32	nsid;
	__le32	cdw2;
	__le32	cdw3;
	__le64	metadata;
	__u64	addr;
	__le32	metadata_len;
	__le32	data_len;
	__le64	ppa;
	__le16	nlb;
	__u8	value;
	__u8	rsvd2;
	__le32	cdw14;
	__le32	cdw15;
	__le32	timeout_ms;
	__le32	result;
};

struct nvme_nvm_getbbtbl {
	__u8	opcode;
	__u8	flags;
	__le16	rsvd1;
	__le32	nsid;
	__le32	cdw2;
	__le32	cdw3;
	__u64	metadata;
	__u64	addr;
	__u32	metadata_len;
	__u32	data_len;
	__le64	ppa;
	__le32	cdw12;
	__le32	cdw13;
	__le32	cdw14;
	__le32	cdw15;
	__le32	timeout_ms;
	__le32	result;
};

struct nvme_nvm_command {
	union {
		struct nvme_nvm_identity identity;
		struct nvme_nvm_getbbtbl get_bb;
	};
};

struct nvme_nvm_completion {
	__le64	result;		/* Used by LightNVM to return ppa completions */
	__le16	sq_head;	/* how much of this queue may be reclaimed */
	__le16	sq_id;		/* submission queue that generated this entry */
	__le16	command_id;	/* of the command which completed */
	__le16	status;		/* did the command fail, and if so, why? */
};

#define NVME_NVM_LP_MLC_PAIRS 886
struct nvme_nvm_lp_mlc {
	__le16			num_pairs;
	__u8			pairs[NVME_NVM_LP_MLC_PAIRS];
};

struct nvme_nvm_lp_tbl {
	__u8			id[8];
	struct nvme_nvm_lp_mlc	mlc;
};

struct nvme_nvm_id12_group {
	__u8			mtype;
	__u8			fmtype;
	__le16			res16;
	__u8			num_ch;
	__u8			num_lun;
	__u8			num_pln;
	__u8			rsvd1;
	__le16			num_blk;
	__le16			num_pg;
	__le16			fpg_sz;
	__le16			csecs;
	__le16			sos;
	__le16			rsvd2;
	__le32			trdt;
	__le32			trdm;
	__le32			tprt;
	__le32			tprm;
	__le32			tbet;
	__le32			tbem;
	__le32			mpos;
	__le32			mccap;
	__le16			cpar;
	__u8			reserved[10];
	struct nvme_nvm_lp_tbl lptbl;
} __attribute__((packed));

struct nvme_nvm_addr_format {
	__u8			ch_offset;
	__u8			ch_len;
	__u8			lun_offset;
	__u8			lun_len;
	__u8			pln_offset;
	__u8			pln_len;
	__u8			blk_offset;
	__u8			blk_len;
	__u8			pg_offset;
	__u8			pg_len;
	__u8			sect_offset;
	__u8			sect_len;
	__u8			res[4];
} __attribute__((packed));

enum {
	LNVM_IDFY_CAP_BAD_BLK_TBL_MGMT	= 0,
	LNVM_IDFY_CAP_HYBRID_CMD_SUPP	= 1,
	LNVM_IDFY_CAP_VCOPY		= 0,
	LNVM_IDFY_CAP_MRESETS		= 1,
	LNVM_IDFY_DOM_HYBRID_MODE	= 0,
	LNVM_IDFY_DOM_ECC_MODE		= 1,
	LNVM_IDFY_GRP_MTYPE_NAND	= 0,
	LNVM_IDFY_GRP_FMTYPE_SLC	= 0,
	LNVM_IDFY_GRP_FMTYPE_MLC	= 1,
	LNVM_IDFY_GRP_FMTYPE_TLC	= 2,
	LNVM_IDFY_GRP_MPOS_SNGL_PLN_RD	= 0,
	LNVM_IDFY_GRP_MPOS_DUAL_PLN_RD	= 1,
	LNVM_IDFY_GRP_MPOS_QUAD_PLN_RD	= 2,
	LNVM_IDFY_GRP_MPOS_SNGL_PLN_PRG	= 8,
	LNVM_IDFY_GRP_MPOS_DUAL_PLN_PRG	= 9,
	LNVM_IDFY_GRP_MPOS_QUAD_PLN_PRG	= 10,
	LNVM_IDFY_GRP_MPOS_SNGL_PLN_ERS	= 16,
	LNVM_IDFY_GRP_MPOS_DUAL_PLN_ERS	= 17,
	LNVM_IDFY_GRP_MPOS_QUAD_PLN_ERS	= 18,
	LNVM_IDFY_GRP_MCCAP_SLC		= 0,
	LNVM_IDFY_GRP_MCCAP_CMD_SUSP	= 1,
	LNVM_IDFY_GRP_MCCAP_SCRAMBLE	= 2,
	LNVM_IDFY_GRP_MCCAP_ENCRYPT	= 3,
};

struct nvme_nvm_id12 {
	__u8			ver_id;
	__u8			vmnt;
	__u8			cgrps;
	__u8			res;
	__le32			cap;
	__le32			dom;
	struct nvme_nvm_addr_format ppaf;
	__u8			resv[228];
	struct nvme_nvm_id12_group groups[4];
} __attribute__((packed));

struct nvme_nvm_id20_addrf {
	__u8			grp_len;
	__u8			pu_len;
	__u8			chk_len;
	__u8			lba_len;
	__u8			resv[4];
} __attribute__((packed));

struct nvme_nvm_id20 {
	__u8			mjr;
	__u8			mnr;
	__u8			resv[6];

	struct nvme_nvm_id20_addrf lbaf;

	__le32			mccap;
	__u8			resv2[12];

	__u8			wit;
	__u8			resv3[31];

	/* Geometry */
	__le16			num_grp;
	__le16			num_pu;
	__le32			num_chk;
	__le32			clba;
	__u8			resv4[52];

	/* Write data requirements */
	__le32			ws_min;
	__le32			ws_opt;
	__le32			mw_cunits;
	__le32			maxoc;
	__le32			maxocpu;
	__u8			resv5[44];

	/* Performance related metrics */
	__le32			trdt;
	__le32			trdm;
	__le32			twrt;
	__le32			twrm;
	__le32			tcrst;
	__le32			tcrsm;
	__u8			resv6[40];

	/* Reserved area */
	__u8			resv7[2816];

	/* Vendor specific */
	__u8			vs[1024];
} __attribute__((packed));

struct nvme_nvm_id {
	__u8			ver_id;
	__u8			resv[4095];
} __attribute__((packed));

enum {
	NVM_LID_CHUNK_INFO = 0xCA,
};

struct nvme_nvm_chunk_desc {
	__u8	cs;
	__u8	ct;
	__u8	wli;
	__u8	rsvd_7_3[5];
	__u64	slba;
	__u64	cnlb;
	__u64	wp;
};

struct nvme_nvm_bb_tbl {
	__u8	tblid[4];
	__le16	verid;
	__le16	revid;
	__le32	rvsd1;
	__le32	tblks;
	__le32	tfact;
	__le32	tgrown;
	__le32	tdresv;
	__le32	thresv;
	__le32	rsvd2[8];
	__u8	blk[0];
};

#define NVM_BLK_BITS (16)
#define NVM_PG_BITS  (16)
#define NVM_SEC_BITS (8)
#define NVM_PL_BITS  (8)
#define NVM_LUN_BITS (8)
#define NVM_CH_BITS  (7)

struct ppa_addr {
	/* Generic structure for all addresses */
	union {
		struct {
			__u64 blk	: NVM_BLK_BITS;
			__u64 pg	: NVM_PG_BITS;
			__u64 sec	: NVM_SEC_BITS;
			__u64 pl	: NVM_PL_BITS;
			__u64 lun	: NVM_LUN_BITS;
			__u64 ch	: NVM_CH_BITS;
			__u64 reserved	: 1;
		} g;

		__u64 ppa;
	};
};

static inline struct ppa_addr generic_to_dev_addr(
			struct nvme_nvm_addr_format *ppaf, struct ppa_addr r)
{
	struct ppa_addr l;

	l.ppa = ((__u64)r.g.blk) << ppaf->blk_offset;
	l.ppa |= ((__u64)r.g.pg) << ppaf->pg_offset;
	l.ppa |= ((__u64)r.g.sec) << ppaf->sect_offset;
	l.ppa |= ((__u64)r.g.pl) << ppaf->pln_offset;
	l.ppa |= ((__u64)r.g.lun) << ppaf->lun_offset;
	l.ppa |= ((__u64)r.g.ch) << ppaf->ch_offset;

	return l;
}

int lnvm_get_identity(int fd, int nsid, struct nvme_nvm_id *nvm_id);

int lnvm_do_init(char *, char *);
int lnvm_do_list_devices(void);
int lnvm_do_info(void);
int lnvm_do_create_tgt(char *, char *, char *, int, int, int, int);
int lnvm_do_remove_tgt(char *);
int lnvm_do_factory_init(char *, int, int, int);
int lnvm_do_id_ns(int, int, unsigned int);
int lnvm_do_chunk_log(int, __u32, __u32, void *, unsigned int);
int lnvm_do_get_bbtbl(int, int, int, int, unsigned int);
int lnvm_do_set_bbtbl(int, int, int, int, int, int, __u8);

#endif
