/*
 * Copyright (c) 2016-2018 The DragonFly Project.  All rights reserved.
 *
 * This code is derived from software contributed to The DragonFly Project
 * by Matthew Dillon <dillon@backplane.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of The DragonFly Project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific, prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*
 * Primary header file
 * NVME softc and structural definitions
 */

#if defined(__DragonFly__)
#include "nvme_dragonfly.h"
#else
#error "build for OS unknown"
#endif
#include "nvme_fw.h"
#include "nvme_log.h"
#include "nvme_ident.h"
#include "nvme_ns.h"
#include "nvme_chipset.h"
#include "nvme_ioctl.h"

MALLOC_DECLARE(M_NVME);

/*
 * Choose some reasonable limit, even if the hardware supports more.
 */
#define NVME_MAX_QUEUES		1024
#define NVME_MAX_NAMESPACES	1024

struct nvme_queue;
struct nvme_softc;
struct nvme_softns;

/*
 * Device matching array for special attach/detach codings.
 */
typedef struct {
	pci_vendor_id_t	 vendor;
	pci_product_id_t product;
	int		(*attach)(device_t dev);
	int		(*detach)(device_t dev);
	char		*name;
} nvme_device_t;

/*
 * Kernel-level request structure.  This structure allows the driver to
 * construct, issue, wait for, and process responses to commands.  Each
 * queue manages its own request bank.
 *
 * In order to disconnect the request structure from the hardware queue
 * mechanism itself, to reduce SMP conflicts and interactions, and allow
 * command/response processing to block without interfering with queue
 * operations, this structure embeds a copy of the HW command and response
 * structures instead of referencing the ones in the actual hardware queues.
 * These will be copied to/from the actual queue entries by lower-level
 * chipset code.
 *
 * Requests are associated with particular queues, completions can occur on
 * any queue.  Requests associated with the admin queue conveniently include
 * an additional 4K 'info' block suitable for DMA.
 */
typedef struct nvme_request {
	struct nvme_request *next_avail;
	struct nvme_subqueue *subq;	/* which queue is submission on */
	struct nvme_comqueue *comq;	/* which queue is completion on */
	uint32_t	state;
	uint32_t	cmd_id;		/* reqary[] index */
	int		waiting;
	nvme_allcmd_t	cmd;		/* hw submit structure for entry */
	nvme_allres_t	res;		/* hw completion structure for entry */
	nvme_admin_data_t *info;	/* DMA data (admin request only) */
	bus_addr_t	pinfo;		/* phys address for PRP */

	/*
	 * Aux fields to keep track of bio and other data, depending on
	 * the callback.  If the callback is NULL the caller will poll for
	 * completion.
	 */
	void		(*callback)(struct nvme_request *req, struct lock *lk);
	struct nvme_softns *nsc;
	struct bio	*bio;
} nvme_request_t;

#define NVME_REQ_AVAIL		0
#define NVME_REQ_ALLOCATED	1
#define NVME_REQ_SUBMITTED	2
#define NVME_REQ_COMPLETED	3

typedef struct nvme_subqueue {
	/*
	 * Driver stuff
	 */
	struct lock	lk;		/* queue lock controls access */
	struct nvme_softc *sc;
	nvme_request_t	*dump_req;	/* one request is set aside */
	nvme_request_t	*first_avail;
	nvme_request_t	*reqary;
	uint32_t	nqe;		/* #of queue entries */
	uint16_t	qid;		/* which queue# are we on? */
	uint16_t	comqid;		/* we are tied to this completion qu */
	uint32_t	subq_doorbell_reg;
	uint32_t	subq_head;	/* start of active requests */
	uint32_t	subq_tail;	/* new requests */
	uint32_t	unsubmitted;	/* #unsubmitted requests */
	int		unused01;
	int		signal_requeue;

	/*
	 * DMA resources
	 */
	bus_dmamap_t	sque_map;
	bus_dmamap_t	prps_map;
	nvme_allcmd_t	*ksubq;		/* kernel-mapped addresses */
	uint64_t	*kprps;		/* enough PRPs per request for */
					/* MAXPHYS bytes worth of mappings */
	bus_addr_t	psubq;		/* physical addresses */
	bus_addr_t	pprps;

	/*
	 * Additional DMA resources for admin queue (A NVME_MAX_ADMIN_BUFFER
	 * sized buffer for each queue entry).
	 */
	bus_dmamap_t	adm_map;
	bus_addr_t	pdatapgs;
	nvme_admin_data_t *kdatapgs;
} nvme_subqueue_t;

typedef struct nvme_comqueue {
	/*
	 * Driver stuff
	 */
	struct lock	lk;		/* queue lock controls access */
	struct nvme_softc *sc;
	uint32_t	nqe;		/* #of queue entries */
	uint16_t	phase;		/* phase to match (res->tail.status) */
	uint16_t	qid;		/* which queue# are we on? */
	uint32_t	comq_doorbell_reg;
	uint32_t	comq_head;	/* consume responses */
	uint32_t	comq_tail;

	/*
	 * DMA resources
	 */
	bus_dmamap_t	cque_map;
	nvme_allres_t	*kcomq;
	bus_addr_t	pcomq;
} nvme_comqueue_t;

typedef struct nvme_softns {
	struct nvme_softc *sc;
	nvme_ident_ns_data_t idns;
	struct bio_queue_head bioq;	/* excess BIOs */
	struct lock	lk;		/* mostly for bioq handling */
	int		state;
	int		unit;
	uint32_t	nsid;
	uint32_t	blksize;
	struct disk	disk;		/* disk attachment */
	struct devstat	stats;
	cdev_t		cdev;		/* disk device (cdev) */
} nvme_softns_t;

#define NVME_NSC_STATE_UNATTACHED	0
#define NVME_NSC_STATE_ATTACHED		1


typedef struct nvme_softc {
	TAILQ_ENTRY(nvme_softc) entry;	/* global list */
	device_t	dev;		/* core device */
	const nvme_device_t *ad;	/* quirk detection etc */
	thread_t	admintd;
	uint32_t	maxqe;
	uint64_t	cap;
	uint32_t	vers;
	uint32_t	dstrd4;		/* doorbell stride */
	uint32_t	entimo;		/* enable timeout in ticks */
	uint16_t	niosubqs;	/* #of I/O submission queues */
	uint16_t	niocomqs;	/* #of I/O completion queues */
	uint16_t	dumpqno;
	uint16_t	eventqno;
	uint16_t	qmap[SMP_MAXCPU][2];
	uint32_t	flags;
	nvme_subqueue_t	subqueues[NVME_MAX_QUEUES];
	nvme_comqueue_t	comqueues[NVME_MAX_QUEUES];
	int		cputovect[SMP_MAXCPU];

	/*
	 * bio/disk layer tracking
	 */
	ulong		opencnt;

	/*
	 * admin queue irq resources
	 * register map resources
	 */
	struct resource	*regs;
	struct resource	*bar4;
	bus_space_tag_t	iot;
	bus_space_handle_t ioh;
	int		rid_regs;
	int		rid_bar4;

	int		nirqs;
	int		irq_type;
	struct resource	*irq[NVME_MAX_QUEUES];
	int		rid_irq[NVME_MAX_QUEUES];
	void		*irq_handle[NVME_MAX_QUEUES];

	/*
	 * dma tags
	 */
	bus_dma_tag_t	prps_tag;	/* worst-case PRP table(s) per queue */
	bus_dma_tag_t	sque_tag;	/* submission queue */
	bus_dma_tag_t	cque_tag;	/* completion queue */
	bus_dma_tag_t	adm_tag;	/* DMA data buffers for admin cmds */
	size_t		prp_bytes;
	size_t		cmd_bytes;
	size_t		res_bytes;
	size_t		adm_bytes;

	/*
	 * Admin thread and controller identify data
	 */
	uint32_t	admin_signal;
	struct lock	admin_lk;
	struct lock	ioctl_lk;
	int		(*admin_func)(struct nvme_softc *);
	nvme_ident_ctlr_data_t idctlr;
	nvme_softns_t	*nscary[NVME_MAX_NAMESPACES];
	int		nscmax;
} nvme_softc_t;

#define NVME_SC_ATTACHED	0x00000001
#define NVME_SC_UNLOADING	0x00000002

#define ADMIN_SIG_STOP		0x00000001
#define ADMIN_SIG_RUNNING	0x00000002
#define ADMIN_SIG_PROBED	0x00000004
#define ADMIN_SIG_REQUEUE	0x00000008
#define ADMIN_SIG_RUN_MASK	(ADMIN_SIG_STOP | ADMIN_SIG_REQUEUE)

#define NVME_QMAP_RD		0
#define NVME_QMAP_WR		1

typedef struct nvme_status_buf {
	char buf[64];
} nvme_status_buf_t;

/*
 * Prototypes
 */
const nvme_device_t *nvme_lookup_device(device_t dev);
void nvme_os_sleep(int ms);
int nvme_os_softsleep(void);
void nvme_os_hardsleep(int us);
u_int32_t nvme_read(nvme_softc_t *sc, bus_size_t r);
u_int64_t nvme_read8(nvme_softc_t *sc, bus_size_t r);
void nvme_write(nvme_softc_t *sc, bus_size_t r, u_int32_t v);
void nvme_write8(nvme_softc_t *sc, bus_size_t r, u_int64_t v);
int nvme_enable(nvme_softc_t *sc, int enable);
int nvme_alloc_subqueue(nvme_softc_t *sc, uint16_t qid);
int nvme_alloc_comqueue(nvme_softc_t *sc, uint16_t qid);
void nvme_free_subqueue(nvme_softc_t *sc, uint16_t qid);
void nvme_free_comqueue(nvme_softc_t *sc, uint16_t qid);

nvme_request_t *nvme_get_admin_request(nvme_softc_t *sc, uint8_t opcode);
nvme_request_t *nvme_get_request(nvme_subqueue_t *queue, uint8_t opcode,
			char *kva, size_t bytes);
nvme_request_t *nvme_get_dump_request(nvme_subqueue_t *queue, uint8_t opcode,
			char *kva, size_t bytes);
void nvme_submit_request(nvme_request_t *req);
int nvme_wait_request(nvme_request_t *req);
int nvme_poll_request(nvme_request_t *req);
void nvme_put_request(nvme_request_t *req);
void nvme_put_dump_request(nvme_request_t *req);
void nvme_poll_completions(nvme_comqueue_t *queue, struct lock *lk);

int nvme_start_admin_thread(nvme_softc_t *sc);
void nvme_stop_admin_thread(nvme_softc_t *sc);

int nvme_create_subqueue(nvme_softc_t *sc, uint16_t qid);
int nvme_create_comqueue(nvme_softc_t *sc, uint16_t qid);
int nvme_delete_subqueue(nvme_softc_t *sc, uint16_t qid);
int nvme_delete_comqueue(nvme_softc_t *sc, uint16_t qid);
int nvme_issue_shutdown(nvme_softc_t *sc, int dopoll);

void nvme_disk_attach(nvme_softns_t *nsc);
void nvme_disk_detach(nvme_softns_t *nsc);
void nvme_disk_requeues(nvme_softc_t *sc);
int nvme_alloc_disk_unit(void);

int nvme_getlog_ioctl(nvme_softc_t *sc, nvme_getlog_ioctl_t *ioc);

void nvme_intr(void *arg);
size_t string_cleanup(char *str, int domiddle);
