diff --git a/block/Kconfig b/block/Kconfig
index 0363cd7..c7324ee 100644
--- a/block/Kconfig
+++ b/block/Kconfig
@@ -113,6 +113,18 @@ config BLK_DEV_THROTTLING
 
 	See Documentation/cgroups/blkio-controller.txt for more information.
 
+config BLK_ZONED_CTRL
+	bool "DEPRECATED. Support sending zac/zbc commands..."
+	default y
+	---help---
+	Support sending close/finish/open/reset wp, and report zones
+	to zoned based devices. Support sending ZAC/ZBC commands to
+	block devices.
+
+	Say yes here if you have a storage device that is has T10 ZBC
+	or T13 ZAC command support for zoned based storage.
+	If in doubt, say N.
+
 config BLK_CMDLINE_PARSER
 	bool "Block device command line partition parser"
 	default n
diff --git a/block/Makefile b/block/Makefile
index 9eda232..f921ecc 100644
--- a/block/Makefile
+++ b/block/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_BOUNCE)	+= bounce.o
 obj-$(CONFIG_BLK_DEV_BSG)	+= bsg.o
 obj-$(CONFIG_BLK_DEV_BSGLIB)	+= bsg-lib.o
 obj-$(CONFIG_BLK_CGROUP)	+= blk-cgroup.o
+obj-$(CONFIG_BLK_ZONED_CTRL)	+= blk-zoned-ctrl.o
 obj-$(CONFIG_BLK_DEV_THROTTLING)	+= blk-throttle.o
 obj-$(CONFIG_IOSCHED_NOOP)	+= noop-iosched.o
 obj-$(CONFIG_IOSCHED_DEADLINE)	+= deadline-iosched.o
diff --git a/block/blk-zoned-ctrl.c b/block/blk-zoned-ctrl.c
new file mode 100644
index 0000000..078b0cd
--- /dev/null
+++ b/block/blk-zoned-ctrl.c
@@ -0,0 +1,642 @@
+/*
+ * Functions for zone based SMR devices.
+ *
+ * Copyright (C) 2015 Seagate Technology PLC
+ *
+ * Written by:
+ * Shaun Tancheff <shaun.tancheff@seagate.com>
+ * XiaoDong Han <xiaodong.h.han@seagate.com>
+ *
+ * This file is licensed under  the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/bio.h>
+#include <linux/blkdev.h>
+#include <linux/blk-mq.h>
+#include <linux/sched/sysctl.h>
+
+#include "blk.h"
+#include <linux/blk-zoned-ctrl.h>
+#include <linux/ata.h>
+
+/*
+ * for max sense size
+ */
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_dbg.h>
+#include <linux/ata.h>
+
+#define ZBC_TIMEOUT		   (30 * HZ)
+#define ZBC_MAX_RETRIES		     5
+#define CMD_LEN			    16
+#define INQUIRY_CMDLEN		     6
+
+/**
+ * ATA pass through 16.
+ */
+#define ZAC_PASS_THROUGH16_OPCODE  ATA_16
+#define ZAC_PASS_THROUGH16_CDB_LEN 16
+
+/**
+ * ATA commands.
+ */
+#define ZAC_ATA_OPCODE_IDENTIFY    ATA_CMD_ID_ATA
+
+static inline void _len_to_cmd_zbc(u8 *cmd, u32 _len)
+{
+	__be32 len = cpu_to_be32(_len);
+
+	memcpy(cmd, &len, sizeof(len));
+}
+
+static inline void _lba_to_cmd_zbc(u8 *cmd, u64 _lba)
+{
+	__be64 lba = cpu_to_be64(_lba);
+
+	memcpy(cmd, &lba, sizeof(lba));
+}
+
+static inline u16 zc_get_word(u8 *buf)
+{
+	u16 w = buf[1];
+
+	w <<= 8;
+	w |= buf[0];
+	return w;
+}
+
+static void pr_debug_scsi_cmd(const char *func_name, u8 *cmd)
+{
+#define FMT_LBA "lba:%02x%02x%02x%02x%02x%02x%02x%02x "
+#define FMT_LEN "len:%02x%02x%02x%02x"
+
+	pr_debug("%s: %02x:%02x " FMT_LBA FMT_LEN " %02x %02x\n",
+		func_name, cmd[0],  cmd[1],
+		cmd[2],  cmd[3],  cmd[4],  cmd[5],
+		cmd[6],  cmd[7],  cmd[8],  cmd[9],
+		cmd[10], cmd[11], cmd[12], cmd[13],
+		cmd[14], cmd[15]);
+
+}
+
+/* NOTE: this is basically scsi_execute */
+int blk_cmd_execute(struct request_queue *queue,
+			   const unsigned char *cmd,
+			   int data_direction,
+			   void *buffer,
+			   unsigned bufflen,
+			   unsigned char *sense,
+			   int timeout,
+			   int retries,
+			   u64 flags,
+			   int *resid)
+{
+	struct request *req;
+	int write = (data_direction == DMA_TO_DEVICE);
+	int ret = DRIVER_ERROR << 24;
+
+	req = blk_get_request(queue, write, GFP_KERNEL);
+	if (IS_ERR(req))
+		return ret;
+	blk_rq_set_block_pc(req);
+
+	if (bufflen &&  blk_rq_map_kern(queue, req,
+					buffer, bufflen, GFP_KERNEL))
+		goto out;
+
+	req->cmd_len = COMMAND_SIZE(cmd[0]);
+	memcpy(req->cmd, cmd, req->cmd_len);
+	req->sense = sense;
+	req->sense_len = 0;
+	req->retries = retries;
+	req->timeout = timeout;
+	req->cmd_flags |= flags | REQ_QUIET | REQ_PREEMPT;
+
+	/*
+	 * head injection *required* here otherwise quiesce won't work
+	 */
+	blk_execute_rq(req->q, NULL, req, 1);
+
+	/*
+	 * Some devices (USB mass-storage in particular) may transfer
+	 * garbage data together with a residue indicating that the data
+	 * is invalid.  Prevent the garbage from being misinterpreted
+	 * and prevent security leaks by zeroing out the excess data.
+	 */
+	if (unlikely(req->resid_len > 0 && req->resid_len <= bufflen))
+		memset(buffer + (bufflen - req->resid_len), 0, req->resid_len);
+
+	if (resid)
+		*resid = req->resid_len;
+	ret = req->errors;
+ out:
+	blk_put_request(req);
+
+	return ret;
+}
+EXPORT_SYMBOL(blk_cmd_execute);
+
+int blk_cmd_with_sense(struct gendisk *disk,
+	u8 *cmd, int data_direction,
+	void *buf, u32 buf_len, u8 *sense_buffer)
+{
+	struct request_queue *queue = disk->queue;
+	int rc;
+	struct scsi_sense_hdr sshdr = { 0 };
+
+	if (!sense_buffer) {
+		pr_err("scsi cmd exec: sense buffer is NULL\n");
+		return -1;
+	}
+
+	rc = blk_cmd_execute(queue, cmd, data_direction, buf, buf_len,
+		sense_buffer, ZBC_TIMEOUT, ZBC_MAX_RETRIES, 0, NULL);
+
+	pr_debug("%s: %s -> 0x%08x [h:%02x d:%02x m:%02x s:%02x]\n", __func__,
+		 disk->disk_name, rc, host_byte(rc), driver_byte(rc),
+		 msg_byte(rc), status_byte(rc));
+
+	scsi_normalize_sense(sense_buffer, SCSI_SENSE_BUFFERSIZE, &sshdr);
+	if (host_byte(rc)
+	    || (driver_byte(rc) && (driver_byte(rc) != DRIVER_SENSE))
+	    || (status_byte(rc)	&& (status_byte(rc) != CHECK_CONDITION))) {
+		pr_err("exec scsi cmd failed,opcode:%d\n", cmd[0]);
+		if (driver_byte(rc) & DRIVER_SENSE)
+			pr_err("%s: %s", __func__, disk->disk_name);
+
+		return -1;
+	} else if ((driver_byte(rc) == DRIVER_SENSE)
+		    && ((cmd[0] == ATA_16) || (cmd[0] == ATA_12))) {
+		if (sense_buffer[21] != 0x50) {
+			pr_err("%s: ATA pass through command failed\n",
+				__func__);
+			return -1;
+		}
+	} else if (rc) {
+		if ((driver_byte(rc) == DRIVER_SENSE)
+		    && (status_byte(rc) == CHECK_CONDITION)
+		    && (sense_buffer[0] != 0)) {
+			pr_err("%s: Something else failed\n", __func__);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(blk_cmd_with_sense);
+
+int blk_zoned_report(struct gendisk *disk,
+			u64 start_lba,
+			u8 opt,
+			struct bdev_zone_report *buf,
+			size_t bufsz)
+{
+	int ret = 0;
+	u8 cmd[CMD_LEN] = {0};
+	u8 sense_buf[SCSI_SENSE_BUFFERSIZE] = {0};
+
+	cmd[0] = ZBC_REPORT_ZONES;
+	cmd[1] = ZBC_REPORT_OPT;
+
+	_lba_to_cmd_zbc(&cmd[2],  start_lba);
+	_len_to_cmd_zbc(&cmd[10], (u32)bufsz);
+
+	cmd[14] = opt;
+
+	pr_debug_scsi_cmd(__func__, cmd);
+	ret = blk_cmd_with_sense(disk, cmd, DMA_FROM_DEVICE, buf, bufsz,
+					&sense_buf[0]);
+	return ret;
+}
+EXPORT_SYMBOL(blk_zoned_report);
+
+int blk_zoned_inquiry(struct gendisk *disk, u8 evpd, u8 pg_op,
+	u16 mx_resp_len, u8 *buf)
+{
+	int ret = 0;
+	u8 cmd[INQUIRY_CMDLEN] = {0};
+	u8 sense_buf[SCSI_SENSE_BUFFERSIZE] = {0};
+	__be16 slen = cpu_to_be16(mx_resp_len);
+	u16 len = (__force u16)slen;
+
+	if (pg_op != 0xb1) {
+		pr_err("Unsupported Page Code %02x. Expected 0xb1\n", pg_op);
+		return -1;
+	}
+
+	cmd[0] = INQUIRY;
+	if (evpd)
+		cmd[1] |= 1;
+	cmd[2] = pg_op;
+	cmd[3] = len & 0xff;
+	cmd[4] = (len >> 8) & 0xff;
+
+	pr_debug("%s: cmd: %02x:%02x:%02x:%02x:%02x:%02x\n",
+		__func__, cmd[0],  cmd[1], cmd[2],  cmd[3],  cmd[4],  cmd[5]);
+
+	ret = blk_cmd_with_sense(disk, cmd, DMA_FROM_DEVICE,
+					buf, mx_resp_len, &sense_buf[0]);
+	if (ret != 0) {
+		pr_err("%s: inquiry failed\n", disk->disk_name);
+		goto out;
+	}
+
+out:
+	return ret;
+}
+EXPORT_SYMBOL(blk_zoned_inquiry);
+
+static int blk_zoned_cmd(struct gendisk *disk, u64 start_lba, u8 command)
+{
+	int ret = 0;
+	u8 cmd[CMD_LEN] = {0};
+	u8 sense_buf[SCSI_SENSE_BUFFERSIZE] = {0};
+	u8 all_bit = 0;
+
+	pr_debug("zoned cmd (%x): on %s, start_lba %lld\n",
+		command, disk->disk_name, start_lba);
+
+	if (start_lba == ~0ul) {
+		all_bit = 1;
+		start_lba = 0;
+	}
+
+	cmd[0] = ZBC_ACTION;
+	cmd[1] = command;
+
+	_lba_to_cmd_zbc(&cmd[2], start_lba);
+
+	cmd[14] = all_bit;
+	pr_debug_scsi_cmd(__func__, cmd);
+
+	ret = blk_cmd_with_sense(disk, cmd, DMA_FROM_DEVICE, NULL, 0,
+		&sense_buf[0]);
+	if (ret != 0) {
+		pr_err("%s: zone command %d failed\n",
+			disk->disk_name, command);
+		return -1;
+	}
+	return ret;
+}
+
+int blk_zoned_close(struct gendisk *disk, u64 start_lba)
+{
+	return blk_zoned_cmd(disk, start_lba, ZBC_SA_ZONE_CLOSE);
+}
+EXPORT_SYMBOL(blk_zoned_close);
+
+int blk_zoned_finish(struct gendisk *disk, u64 start_lba)
+{
+	return blk_zoned_cmd(disk, start_lba, ZBC_SA_ZONE_FINISH);
+}
+EXPORT_SYMBOL(blk_zoned_finish);
+
+int blk_zoned_open(struct gendisk *disk, u64 start_lba)
+{
+	return blk_zoned_cmd(disk, start_lba, ZBC_SA_ZONE_OPEN);
+}
+EXPORT_SYMBOL(blk_zoned_open);
+
+int blk_zoned_reset_wp(struct gendisk *disk, u64 start_lba)
+{
+	return blk_zoned_cmd(disk, start_lba, ZBC_SA_RESET_WP);
+}
+EXPORT_SYMBOL(blk_zoned_reset_wp);
+
+static inline void _lba_to_cmd_ata(u8 *cmd, u64 _lba)
+{
+	cmd[1] =  _lba	      & 0xff;
+	cmd[3] = (_lba >>  8) & 0xff;
+	cmd[5] = (_lba >> 16) & 0xff;
+	cmd[0] = (_lba >> 24) & 0xff;
+	cmd[2] = (_lba >> 32) & 0xff;
+	cmd[4] = (_lba >> 40) & 0xff;
+}
+
+/*
+ * ata-16 passthrough byte 1:
+ *   multiple [bits 7:5]
+ *   protocol [bits 4:1]
+ *   ext      [bit    0]
+ */
+static inline u8 ata16byte1(u8 multiple, u8 protocol, u8 ext)
+{
+	return ((multiple & 0x7) << 5) | ((protocol & 0xF) << 1) | (ext & 0x01);
+}
+
+int blk_zoned_report_ata(struct gendisk *disk, u64 start_lba, u8 opt,
+			 struct bdev_zone_report *buf, size_t bufsz)
+{
+	int ret = 0;
+	u8 cmd[ZAC_PASS_THROUGH16_CDB_LEN] = { 0 };
+	u8 sense_buf[SCSI_SENSE_BUFFERSIZE] = { 0 };
+
+	cmd[0] = ZAC_PASS_THROUGH16_OPCODE;
+	cmd[1] = ata16byte1(0, ATAPI_PROT_PIO, 1);
+	cmd[2] = 0x0e;
+
+	cmd[4] = ATA_SUBCMD_REP_ZONES;
+	cmd[3] = opt;
+
+	cmd[5] = (bufsz / 512) >> 8;
+	cmd[6] = (bufsz / 512) & 0xff;
+
+	_lba_to_cmd_ata(&cmd[7], start_lba);
+
+	cmd[13] = 1 << 6;
+	cmd[14] = ATA_CMD_ZONE_MAN_IN;
+
+	pr_debug_scsi_cmd(__func__, cmd);
+
+	ret = blk_cmd_with_sense(disk, cmd, DMA_FROM_DEVICE, buf, bufsz,
+					&sense_buf[0]);
+	return ret;
+}
+EXPORT_SYMBOL(blk_zoned_report_ata);
+
+int blk_zoned_identify_ata(struct gendisk *disk, struct zoned_identity *ident)
+{
+	int ret = 0;
+	u8 identify_cmd[ZAC_PASS_THROUGH16_CDB_LEN] = { 0 };
+	u8 sense_buf[SCSI_SENSE_BUFFERSIZE] = { 0 };
+	u8 buffer[512] = { 0 };
+	int flag = 0;
+
+	ident->type_id = NOT_ZONED;
+
+	if (!disk)
+		return -1;
+
+	identify_cmd[0] = ZAC_PASS_THROUGH16_OPCODE;
+	identify_cmd[1] = ata16byte1(0, ATA_PROT_NCQ, 1);
+	identify_cmd[2] = 0xe;
+	identify_cmd[6] = 0x1;
+	identify_cmd[8] = 0x1;
+	identify_cmd[14] = ZAC_ATA_OPCODE_IDENTIFY;
+
+	ret = blk_cmd_with_sense(disk, identify_cmd, DMA_FROM_DEVICE,
+				 buffer, sizeof(buffer), &sense_buf[0]);
+	if (ret != 0) {
+		pr_err("%s: identify failed.\n", disk->disk_name);
+		goto out;
+	}
+
+	flag = zc_get_word(&buffer[138]);
+	if ((flag & 0x3) == 0x1) {
+		ident->type_id = HOST_AWARE;
+		pr_debug("%s: is SMR-HostAware\n", disk->disk_name);
+	} else {
+		ret = -1;
+		pr_debug("%s: not HostAware\n", disk->disk_name);
+	}
+
+out:
+	return ret;
+}
+EXPORT_SYMBOL(blk_zoned_identify_ata);
+
+static int _blk_zoned_command_ata(struct gendisk *disk, u64 start_lba,
+				  u8 sub_command)
+{
+	int ret = 0;
+	u8 cmd[ZAC_PASS_THROUGH16_CDB_LEN] = { 0 };
+	u8 sense_buf[SCSI_SENSE_BUFFERSIZE] = { 0 };
+
+	pr_debug("zoned command (%x): %s, start_lba %llx\n",
+		 sub_command, disk->disk_name, start_lba);
+
+	cmd[0] = ZAC_PASS_THROUGH16_OPCODE;
+	cmd[1] = ata16byte1(0, 3, 1);
+	cmd[4] = sub_command; /* [feature] */
+
+	if (start_lba == ~0ul || start_lba == ~1ul)
+		cmd[3] = 0x1; /* [hob_feature] apply command to all zones */
+	else
+		_lba_to_cmd_ata(&cmd[7], start_lba);
+
+	cmd[13] = 1 << 6;
+	cmd[14] = ATA_CMD_ZONE_MAN_OUT;
+
+	pr_debug_scsi_cmd(__func__, cmd);
+
+	ret = blk_cmd_with_sense(disk, cmd, DMA_NONE, NULL, 0, &sense_buf[0]);
+	if (ret != 0) {
+		pr_err("%s: command %d failed\n", disk->disk_name, sub_command);
+		return -1;
+	}
+	return ret;
+}
+
+int blk_zoned_close_ata(struct gendisk *disk, u64 start_lba)
+{
+	return _blk_zoned_command_ata(disk, start_lba, ATA_SUBCMD_CLOSE_ZONES);
+}
+EXPORT_SYMBOL(blk_zoned_close_ata);
+
+int blk_zoned_finish_ata(struct gendisk *disk, u64 start_lba)
+{
+	return _blk_zoned_command_ata(disk, start_lba, ATA_SUBCMD_FINISH_ZONES);
+}
+EXPORT_SYMBOL(blk_zoned_finish_ata);
+
+int blk_zoned_open_ata(struct gendisk *disk, u64 start_lba)
+{
+	return _blk_zoned_command_ata(disk, start_lba, ATA_SUBCMD_OPEN_ZONES);
+}
+EXPORT_SYMBOL(blk_zoned_open_ata);
+
+int blk_zoned_reset_wp_ata(struct gendisk *disk, u64 start_lba)
+{
+	return _blk_zoned_command_ata(disk, start_lba, ATA_SUBCMD_RESET_WP);
+}
+EXPORT_SYMBOL(blk_zoned_reset_wp_ata);
+
+int _inquiry_ioctl(struct gendisk *disk, void __user *parg)
+{
+	int error = 0;
+	size_t result_size = 0;
+	size_t alloc_size = PAGE_SIZE;
+	struct zoned_inquiry *inq = kmalloc(alloc_size, GFP_KERNEL);
+	u8 extended;
+
+	if (!inq) {
+		error = -ENOMEM;
+		goto out;
+	}
+	if (copy_from_user(inq, parg, sizeof(*inq))) {
+		error = -EFAULT;
+		goto out;
+	}
+	result_size = inq->mx_resp_len + offsetof(struct zoned_inquiry, result);
+	if (result_size > alloc_size) {
+		void *tmp;
+
+		alloc_size = result_size;
+		tmp = krealloc(inq, alloc_size, GFP_KERNEL);
+		if (!tmp) {
+			error = -ENOMEM;
+			goto out;
+		}
+		inq = tmp;
+	}
+
+	extended = inq->evpd & 0x7f;
+	if (inq->evpd & ZOPT_USE_ATA_PASS) {
+		struct zoned_identity ident;
+
+		pr_debug("%s: using ata passthrough.\n", __func__);
+		error = blk_zoned_identify_ata(disk, &ident);
+		inq->result[8] = ident.type_id << 4;
+	} else {
+		error = blk_zoned_inquiry(disk, extended,   inq->pg_op,
+					  inq->mx_resp_len, inq->result);
+	}
+	if (error) {
+		error = -EFAULT;
+		goto out;
+	}
+	if (copy_to_user(parg, inq, result_size)) {
+		error = -EFAULT;
+		goto out;
+	}
+
+out:
+	kfree(inq);
+
+	return error;
+}
+EXPORT_SYMBOL(_inquiry_ioctl);
+
+int _zone_close_ioctl(struct gendisk *disk, unsigned long arg)
+{
+	int error = -EFAULT;
+
+	if (arg & 1) {
+		if (arg != ~0ul)
+			arg &= ~1ul; /* ~1 :: 0xFF...FE */
+		error = blk_zoned_close_ata(disk, arg);
+	} else {
+		if (arg == ~1ul)
+			arg = ~0ul;
+		error = blk_zoned_close(disk, arg);
+	}
+
+	return error;
+}
+EXPORT_SYMBOL(_zone_close_ioctl);
+
+int _zone_finish_ioctl(struct gendisk *disk, unsigned long arg)
+{
+	int error = -EFAULT;
+
+	if (arg & 1) {
+		if (arg != ~0ul)
+			arg &= ~1ul; /* ~1 :: 0xFF...FE */
+		error = blk_zoned_finish_ata(disk, arg);
+	} else {
+		if (arg == ~1ul)
+			arg = ~0ul;
+		error = blk_zoned_finish(disk, arg);
+	}
+
+	return error;
+}
+EXPORT_SYMBOL(_zone_finish_ioctl);
+
+int _zone_open_ioctl(struct gendisk *disk, unsigned long arg)
+{
+	int error = -EFAULT;
+
+	if (arg & 1) {
+		if (arg != ~0ul)
+			arg &= ~1ul; /* ~1 :: 0xFF...FE */
+		error = blk_zoned_open_ata(disk, arg);
+	} else {
+		if (arg == ~1ul)
+			arg = ~0ul;
+		error = blk_zoned_open(disk, arg);
+	}
+
+	return error;
+}
+EXPORT_SYMBOL(_zone_open_ioctl);
+
+int _reset_wp_ioctl(struct gendisk *disk, unsigned long arg)
+{
+	int error = -EFAULT;
+
+	if (arg & 1) {
+		if (arg != ~0ul)
+			arg &= ~1ul; /* ~1 :: 0xFF...FE */
+		error = blk_zoned_reset_wp_ata(disk, arg);
+	} else {
+		if (arg == ~1ul)
+			arg = ~0ul;
+		error = blk_zoned_reset_wp(disk, arg);
+	}
+
+	return error;
+}
+EXPORT_SYMBOL(_reset_wp_ioctl);
+
+int _report_zones_ioctl(struct gendisk *disk, void __user *parg)
+{
+	int error = -EFAULT;
+	struct bdev_zone_report_io *zone_iodata = NULL;
+	u32 alloc_size = max(PAGE_SIZE, sizeof(*zone_iodata));
+	u8 opt = 0;
+
+	zone_iodata = kmalloc(alloc_size, GFP_KERNEL);
+	if (!zone_iodata) {
+		error = -ENOMEM;
+		goto report_zones_out;
+	}
+	if (copy_from_user(zone_iodata, parg, sizeof(*zone_iodata))) {
+		error = -EFAULT;
+		goto report_zones_out;
+	}
+	if (zone_iodata->data.in.return_page_count > alloc_size) {
+		void *tmp;
+
+		alloc_size = zone_iodata->data.in.return_page_count;
+		if (alloc_size < KMALLOC_MAX_SIZE) {
+			tmp = krealloc(zone_iodata, alloc_size, GFP_KERNEL);
+			if (!tmp) {
+				error = -ENOMEM;
+				goto report_zones_out;
+			}
+			zone_iodata = tmp;
+		} else {
+			/* Result requires DMA capable memory */
+			pr_err("Not enough memory available for request.\n");
+			error = -ENOMEM;
+			goto report_zones_out;
+		}
+	}
+	opt = zone_iodata->data.in.report_option & 0x7F;
+	if (zone_iodata->data.in.report_option & ZOPT_USE_ATA_PASS)
+		error = blk_zoned_report_ata(disk,
+				zone_iodata->data.in.zone_locator_lba,
+				opt, &zone_iodata->data.out, alloc_size);
+	else
+		error = blk_zoned_report(disk,
+				zone_iodata->data.in.zone_locator_lba,
+				opt, &zone_iodata->data.out, alloc_size);
+
+	if (error)
+		goto report_zones_out;
+
+	if (copy_to_user(parg, zone_iodata, alloc_size))
+		error = -EFAULT;
+
+report_zones_out:
+	kfree(zone_iodata);
+	return error;
+}
+EXPORT_SYMBOL(_report_zones_ioctl);
diff --git a/block/scsi_ioctl.c b/block/scsi_ioctl.c
index 0774799..5790f59 100644
--- a/block/scsi_ioctl.c
+++ b/block/scsi_ioctl.c
@@ -21,6 +21,7 @@
 #include <linux/string.h>
 #include <linux/module.h>
 #include <linux/blkdev.h>
+#include <linux/blk-zoned-ctrl.h>
 #include <linux/capability.h>
 #include <linux/completion.h>
 #include <linux/cdrom.h>
@@ -705,6 +706,11 @@ int scsi_verify_blk_ioctl(struct block_device *bd, unsigned int cmd)
 	case SG_GET_RESERVED_SIZE:
 	case SG_SET_RESERVED_SIZE:
 	case SG_EMULATED_HOST:
+	case SCSI_IOCTL_INQUIRY:
+	case SCSI_IOCTL_REPORT_ZONES:
+	case SCSI_IOCTL_RESET_WP:
+	case SCSI_IOCTL_OPEN_ZONE:
+	case SCSI_IOCTL_CLOSE_ZONE:
 		return 0;
 	case CDROM_GET_CAPABILITY:
 		/* Keep this until we remove the printk below.  udev sends it
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 3cb0200..726544c 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -45,6 +45,7 @@
 #include <linux/init.h>
 #include <linux/blkdev.h>
 #include <linux/blkpg.h>
+#include <linux/blk-zoned-ctrl.h>
 #include <linux/delay.h>
 #include <linux/mutex.h>
 #include <linux/string_helpers.h>
@@ -1479,6 +1480,24 @@ static int sd_ioctl(struct block_device *bdev, fmode_t mode,
 		case SCSI_IOCTL_GET_BUS_NUMBER:
 			error = scsi_ioctl(sdp, cmd, p);
 			break;
+		case SCSI_IOCTL_INQUIRY:
+			error = _inquiry_ioctl(disk, p);
+			break;
+		case SCSI_IOCTL_CLOSE_ZONE:
+			error = _zone_close_ioctl(disk, arg);
+			break;
+		case SCSI_IOCTL_FINISH_ZONE:
+			error = _zone_finish_ioctl(disk, arg);
+			break;
+		case SCSI_IOCTL_OPEN_ZONE:
+			error = _zone_open_ioctl(disk, arg);
+			break;
+		case SCSI_IOCTL_RESET_WP:
+			error = _reset_wp_ioctl(disk, arg);
+			break;
+		case SCSI_IOCTL_REPORT_ZONES:
+			error = _report_zones_ioctl(disk, p);
+			break;
 		default:
 			error = scsi_cmd_blk_ioctl(bdev, mode, cmd, p);
 			if (error != -ENOTTY)
diff --git a/include/linux/blk-zoned-ctrl.h b/include/linux/blk-zoned-ctrl.h
new file mode 100644
index 0000000..fb1d494
--- /dev/null
+++ b/include/linux/blk-zoned-ctrl.h
@@ -0,0 +1,51 @@
+/*
+ * Functions for zone based SMR devices.
+ *
+ * Copyright (C) 2015 Seagate Technology PLC
+ *
+ * Written by:
+ * Shaun Tancheff <shaun.tancheff@seagate.com>
+ *
+ * This file is licensed under  the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef BLK_ZONED_CTRL_H
+#define BLK_ZONED_CTRL_H
+
+#include <linux/blkzoned_api.h>
+#include <uapi/linux/blk-zoned-ctrl.h>
+
+/* this is basically scsi_execute */
+int blk_cmd_execute(struct request_queue *, const unsigned char *, int, void *,
+		    unsigned, unsigned char *, int, int, u64, int *);
+int blk_cmd_with_sense(struct gendisk *, u8 *, int, void *, u32 len, u8 *);
+
+/* scsi zbc commands */
+int blk_zoned_inquiry(struct gendisk *, u8, u8, u16, u8 *);
+int blk_zoned_close(struct gendisk *disk, u64 start_lba);
+int blk_zoned_finish(struct gendisk *disk, u64 start_lba);
+int blk_zoned_open(struct gendisk *disk, u64 start_lba);
+int blk_zoned_reset_wp(struct gendisk *disk, u64 start_lba);
+int blk_zoned_report(struct gendisk *, u64, u8,
+		     struct bdev_zone_report *, size_t);
+
+/* ata zac variants */
+int blk_zoned_identify_ata(struct gendisk *disk, struct zoned_identity *);
+int blk_zoned_close_ata(struct gendisk *disk, u64 start_lba);
+int blk_zoned_finish_ata(struct gendisk *disk, u64 start_lba);
+int blk_zoned_open_ata(struct gendisk *disk, u64 start_lba);
+int blk_zoned_reset_wp_ata(struct gendisk *disk, u64 start_lba);
+int blk_zoned_report_ata(struct gendisk *disk, u64, u8,
+			 struct bdev_zone_report *, size_t);
+
+/* for testing from userspace via ioctl */
+int _inquiry_ioctl(struct gendisk *disk, void __user *parg);
+int _zone_close_ioctl(struct gendisk *disk, unsigned long arg);
+int _zone_finish_ioctl(struct gendisk *disk, unsigned long arg);
+int _zone_open_ioctl(struct gendisk *disk, unsigned long arg);
+int _reset_wp_ioctl(struct gendisk *disk, unsigned long arg);
+int _report_zones_ioctl(struct gendisk *disk, void __user *parg);
+
+#endif /* BLK_ZONED_CTRL_H */
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index 52e2d1a..ad0084a 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -71,6 +71,7 @@ header-y += binfmts.h
 header-y += blkpg.h
 header-y += blktrace_api.h
 header-y += blkzoned_api.h
+header-y += blk-zoned-ctrl.h
 header-y += bpf_common.h
 header-y += bpf.h
 header-y += bpqether.h
diff --git a/include/uapi/linux/blk-zoned-ctrl.h b/include/uapi/linux/blk-zoned-ctrl.h
new file mode 100644
index 0000000..559c10d
--- /dev/null
+++ b/include/uapi/linux/blk-zoned-ctrl.h
@@ -0,0 +1,70 @@
+/*
+ * Functions for zone based SMR devices.
+ *
+ * Copyright (C) 2015 Seagate Technology PLC
+ *
+ * Written by:
+ * Shaun Tancheff <shaun.tancheff@seagate.com>
+ *
+ * This file is licensed under  the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef _UAPI_BLK_ZONED_CTRL_H
+#define _UAPI_BLK_ZONED_CTRL_H
+
+#include <linux/types.h>
+
+/* Used for Zone based SMR devices */
+#define SCSI_IOCTL_INQUIRY		0x10000
+#define SCSI_IOCTL_CLOSE_ZONE		0x10001
+#define SCSI_IOCTL_FINISH_ZONE		0x10002
+#define SCSI_IOCTL_OPEN_ZONE		0x10003
+#define SCSI_IOCTL_RESET_WP		0x10004
+#define SCSI_IOCTL_REPORT_ZONES		0x10005
+
+/**
+ * struct zoned_inquiry - deterine if device is block zoned.
+ *
+ * @evpd:
+ * @pg_op:
+ * @mx_resp_len:
+ * @result:
+ *
+ */
+struct zoned_inquiry {
+	__u8  evpd;
+	__u8  pg_op;
+	__u16 mx_resp_len;
+	__u8  result[0];
+} __packed;
+
+/**
+ * enum zoned_identity_type_id - Zoned, HA or HM.
+ *
+ * @NOT_ZONED: Not a zoned device.
+ * @HOST_AWARE: ZAC/ZBC device reports as Host Aware.
+ * @HOST_MANAGE: ZAC/ZBC device reports as Host Managed.
+ *
+ */
+enum zoned_identity_type_id {
+	NOT_ZONED    = 0x00,
+	HOST_AWARE   = 0x01,
+	HOST_MANAGE  = 0x02,
+};
+
+/**
+ * struct zoned_identity
+ *
+ * @type_id: See zoned_identity_type_id
+ * @reserved: reserved.
+ *
+ * Used for ata pass through to detected devices which support ZAC.
+ */
+struct zoned_identity {
+	__u8 type_id;
+	__u8 reserved[3];
+} __packed;
+
+#endif /* _UAPI_BLK_ZONED_CTRL_H */
