/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2019 Joyent, Inc.
 * Copyright 2020 Oxide Computer Company
 */

/*
 * VIRTIO BLOCK DRIVER
 */

#ifndef _VIOBLK_H
#define	_VIOBLK_H

#include "virtio.h"

#ifdef __cplusplus
extern "C" {
#endif

/*
 * VIRTIO BLOCK CONFIGURATION REGISTERS
 *
 * These are offsets into the device-specific configuration space available
 * through the virtio_dev_*() family of functions.
 */
#define	VIRTIO_BLK_CONFIG_CAPACITY		0x00	/* 64 R   */
#define	VIRTIO_BLK_CONFIG_SIZE_MAX		0x08	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_SEG_MAX		0x0C	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_GEOMETRY_C		0x10	/* 16 R   */
#define	VIRTIO_BLK_CONFIG_GEOMETRY_H		0x12	/*  8 R   */
#define	VIRTIO_BLK_CONFIG_GEOMETRY_S		0x13	/*  8 R   */
#define	VIRTIO_BLK_CONFIG_BLK_SIZE		0x14	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_TOPO_PBEXP		0x18	/*  8 R   */
#define	VIRTIO_BLK_CONFIG_TOPO_ALIGN		0x19	/*  8 R   */
#define	VIRTIO_BLK_CONFIG_TOPO_MIN_SZ		0x1A	/* 16 R   */
#define	VIRTIO_BLK_CONFIG_TOPO_OPT_SZ		0x1C	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_WRITEBACK		0x20	/*  8 R	  */
				/* unused	0x21 */ /*  8 R   */
#define	VIRTIO_BLK_CONFIG_NUM_QUEUES		0x22	/* 16 R   */
#define	VIRTIO_BLK_CONFIG_MAX_DISCARD_SECT	0x24	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_MAX_DISCARD_SEG	0x28	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_DISCARD_ALIGN		0x2C	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_MAX_WRITE_ZERO_SECT	0x30	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_MAX_WRITE_ZERO_SEG	0x34	/* 32 R   */
#define	VIRTIO_BLK_CONFIG_WRITE_ZERO_UNMAP	0x38	/*  8 R   */

/*
 * VIRTIO BLOCK VIRTQUEUES
 *
 * Virtio block devices have just one queue which is used to make the various
 * supported I/O requests.
 */
#define	VIRTIO_BLK_VIRTQ_IO		0

/*
 * VIRTIO BLOCK FEATURE BITS
 */
#define	VIRTIO_BLK_F_BARRIER		(1ULL << 0)
#define	VIRTIO_BLK_F_SIZE_MAX		(1ULL << 1)
#define	VIRTIO_BLK_F_SEG_MAX		(1ULL << 2)
#define	VIRTIO_BLK_F_GEOMETRY		(1ULL << 4)
#define	VIRTIO_BLK_F_RO			(1ULL << 5)
#define	VIRTIO_BLK_F_BLK_SIZE		(1ULL << 6)
#define	VIRTIO_BLK_F_SCSI		(1ULL << 7)
#define	VIRTIO_BLK_F_FLUSH		(1ULL << 9)
#define	VIRTIO_BLK_F_TOPOLOGY		(1ULL << 10)
#define	VIRTIO_BLK_F_CONFIG_WCE		(1ULL << 11)
#define	VIRTIO_BLK_F_MQ			(1ULL << 12)
#define	VIRTIO_BLK_F_DISCARD		(1ULL << 13)
#define	VIRTIO_BLK_F_WRITE_ZEROES	(1ULL << 14)

/*
 * These features are supported by the driver and we will request them from the
 * device.
 */
#define	VIRTIO_BLK_WANTED_FEATURES	(VIRTIO_BLK_F_RO |		\
					VIRTIO_BLK_F_BLK_SIZE |		\
					VIRTIO_BLK_F_FLUSH |		\
					VIRTIO_BLK_F_TOPOLOGY |		\
					VIRTIO_BLK_F_SEG_MAX |		\
					VIRTIO_BLK_F_SIZE_MAX |		\
					VIRTIO_BLK_F_DISCARD)

/*
 * VIRTIO BLOCK REQUEST HEADER
 *
 * This structure appears at the start of each I/O request buffer.  Note that
 * neither the data payload nor the status byte appear in this structure as
 * both are handled in separate descriptor entries.
 */
struct vioblk_req_hdr {
	uint32_t			vbh_type;
	uint32_t			vbh_ioprio;
	uint64_t			vbh_sector;
} __packed;

/*
 * VIRTIO BLOCK REQUEST HEADER: COMMANDS (vbh_type)
 *
 * Each of these is a command type, except for BARRIER which is logically
 * OR-ed with one of the other types.
 */
#define	VIRTIO_BLK_T_IN			0
#define	VIRTIO_BLK_T_OUT		1
#define	VIRTIO_BLK_T_SCSI_CMD		2
#define	VIRTIO_BLK_T_SCSI_CMD_OUT	3
#define	VIRTIO_BLK_T_FLUSH		4
#define	VIRTIO_BLK_T_FLUSH_OUT		5
#define	VIRTIO_BLK_T_GET_ID		8
#define	VIRTIO_BLK_T_DISCARD		11
#define	VIRTIO_BLK_T_WRITE_ZEROES	13
#define	VIRTIO_BLK_T_BARRIER		0x80000000

/*
 * VIRTIO BLOCK DISCARD/WRITE ZEROS DATA
 *
 * For hosts that support the DISCARD or WRITE ZEROES features, instead of
 * data, the vioblk_discard_write_zeros struct is used as the 'data' for
 * the request.
 */
struct vioblk_discard_write_zeroes {
	uint64_t	vdwz_sector;
	uint32_t	vdwz_num_sectors;
	uint32_t	vdwz_flags;
} __packed;

/*
 * vdwz_flags values
 */

/* For a WRITE ZEROES request, also unmap the block */
#define	VIRTIO_BLK_WRITE_ZEROS_UNMAP	(1U << 0)

/*
 * The GET_ID command type does not appear in the specification, but
 * implementations in the wild use a 20 byte buffer into which the device will
 * write an ASCII string.  The string should not be assumed to be
 * NUL-terminated.
 */
#define	VIRTIO_BLK_ID_BYTES		20

/*
 * VIRTIO BLOCK REQUEST HEADER: STATUS CODES
 *
 * These are returned in the writeable status byte descriptor included at the
 * end of each request passed to the device.
 */
#define	VIRTIO_BLK_S_OK			0
#define	VIRTIO_BLK_S_IOERR		1
#define	VIRTIO_BLK_S_UNSUPP		2

/*
 * DRIVER PARAMETERS
 */

/*
 * In the event that the device does not negotiate DMA parameters, we have to
 * make a best guess.
 */
#define	VIRTIO_BLK_DEFAULT_MAX_SEG	128
#define	VIRTIO_BLK_DEFAULT_MAX_SIZE	4096

/*
 * We allocate a fixed number of request buffers in advance and place them in a
 * per-instance free list.
 */
#define	VIRTIO_BLK_REQ_BUFS		256

/*
 * TYPE DEFINITIONS
 */

typedef enum vioblk_req_status {
	VIOBLK_REQSTAT_ALLOCATED =	(0x1 << 0),
	VIOBLK_REQSTAT_INFLIGHT =	(0x1 << 1),
	VIOBLK_REQSTAT_COMPLETE =	(0x1 << 2),
	VIOBLK_REQSTAT_POLLED =		(0x1 << 3),
	VIOBLK_REQSTAT_POLL_COMPLETE =	(0x1 << 4),
} vioblk_req_status_t;

typedef struct vioblk_req {
	vioblk_req_status_t		vbr_status;
	uint64_t			vbr_seqno;
	int				vbr_type;
	int				vbr_error;
	virtio_dma_t			*vbr_dma;
	virtio_chain_t			*vbr_chain;
	bd_xfer_t			*vbr_xfer;
	list_node_t			vbr_link;
} vioblk_req_t;

typedef struct vioblk_stats {
	struct kstat_named		vbs_rw_outofmemory;
	struct kstat_named		vbs_rw_badoffset;
	struct kstat_named		vbs_rw_queuemax;
	struct kstat_named		vbs_rw_cookiesmax;
	struct kstat_named		vbs_rw_cacheflush;
	struct kstat_named		vbs_intr_queuemax;
	struct kstat_named		vbs_intr_total;
	struct kstat_named		vbs_io_errors;
	struct kstat_named		vbs_unsupp_errors;
	struct kstat_named		vbs_nxio_errors;
} vioblk_stats_t;

typedef struct vioblk {
	dev_info_t			*vib_dip;
	virtio_t			*vib_virtio;
	virtio_queue_t			*vib_vq;

	kmutex_t			vib_mutex;
	kcondvar_t			vib_cv;

	bd_handle_t			vib_bd_h;
	ddi_dma_attr_t			vib_bd_dma_attr;

	list_t				vib_reqs;
	uint_t				vib_nreqs_alloc;
	uint_t				vib_reqs_capacity;
	vioblk_req_t			*vib_reqs_mem;

	kstat_t				*vib_kstat;
	vioblk_stats_t			*vib_stats;

	uint64_t			vib_nblks;
	boolean_t			vib_readonly;
	uint_t				vib_blk_size;
	uint_t				vib_pblk_size;
	uint_t				vib_seg_max;
	uint_t				vib_seg_size_max;

	boolean_t			vib_can_discard;	/* Write Once */
	uint_t				vib_max_discard_sectors;	/* WO */
	uint_t				vib_max_discard_seg;		/* WO */
	uint_t				vib_discard_sector_align;	/* WO */

	boolean_t			vib_devid_fetched;
	char				vib_devid[VIRTIO_BLK_ID_BYTES + 1];
	uint8_t				vib_rawid[VIRTIO_BLK_ID_BYTES];
} vioblk_t;

#ifdef __cplusplus
}
#endif

#endif /* _VIOBLK_H */
