Page MenuHomeFreeBSD

D6147.id16470.diff
No OneTemporary

D6147.id16470.diff

This file is larger than 256 KB, so syntax highlighting was skipped.
Index: sbin/camcontrol/Makefile
===================================================================
--- sbin/camcontrol/Makefile
+++ sbin/camcontrol/Makefile
@@ -4,7 +4,7 @@
PROG= camcontrol
SRCS= camcontrol.c util.c
.if !defined(RELEASE_CRUNCH)
-SRCS+= attrib.c fwdownload.c modeedit.c persist.c progress.c
+SRCS+= attrib.c epc.c fwdownload.c modeedit.c persist.c progress.c zone.c
.else
CFLAGS+= -DMINIMALISTIC
.endif
Index: sbin/camcontrol/camcontrol.h
===================================================================
--- sbin/camcontrol/camcontrol.h
+++ sbin/camcontrol/camcontrol.h
@@ -63,16 +63,24 @@
int timeout, int verbosemode);
int get_device_type(struct cam_device *dev, int retry_count, int timeout,
int verbosemode, camcontrol_devtype *devtype);
-void build_ata_cmd(union ccb *ccb, uint32_t retry_count, uint32_t flags,
- uint8_t tag_action, uint8_t protocol, uint8_t ata_flags,
- uint16_t features, uint16_t sector_count, uint64_t lba,
- uint8_t command, uint8_t *data_ptr, uint16_t dxfer_len,
- uint8_t sense_len, uint32_t timeout, int is48bit,
- camcontrol_devtype devtype);
+int build_ata_cmd(union ccb *ccb, uint32_t retry_count, uint32_t flags,
+ uint8_t tag_action, uint8_t protocol, uint8_t ata_flags,
+ uint16_t features, uint16_t sector_count, uint64_t lba,
+ uint8_t command, uint32_t auxiliary, uint8_t *data_ptr,
+ uint32_t dxfer_len, uint8_t *cdb_storage,
+ size_t cdb_storage_len, uint8_t sense_len, uint32_t timeout,
+ int is48bit, camcontrol_devtype devtype);
+int get_ata_status(struct cam_device *dev, union ccb *ccb, uint8_t *error,
+ uint16_t *count, uint64_t *lba, uint8_t *device,
+ uint8_t *status);
int camxferrate(struct cam_device *device);
int fwdownload(struct cam_device *device, int argc, char **argv,
char *combinedopt, int printerrors, int retry_count,
int timeout);
+int zone(struct cam_device *device, int argc, char **argv, char *combinedopt,
+ int retry_count, int timeout, int verbosemode);
+int epc(struct cam_device *device, int argc, char **argv, char *combinedopt,
+ int retry_count, int timeout, int verbosemode);
void mode_sense(struct cam_device *device, int mode_page, int page_control,
int dbd, int retry_count, int timeout, u_int8_t *data,
int datalen);
Index: sbin/camcontrol/camcontrol.8
===================================================================
--- sbin/camcontrol/camcontrol.8
+++ sbin/camcontrol/camcontrol.8
@@ -27,7 +27,7 @@
.\"
.\" $FreeBSD$
.\"
-.Dd April 26, 2016
+.Dd May 16, 2016
.Dt CAMCONTROL 8
.Os
.Sh NAME
@@ -323,6 +323,26 @@
.Op Fl N
.Op Fl T
.Nm
+.Ic zone
+.Aq Fl c Ar cmd
+.Op Fl a
+.Op Fl l Ar lba
+.Op Fl o Ar rep_opts
+.Op Fl P Ar print_opts
+.Nm
+.Ic epc
+.Aq Fl c Ar cmd
+.Op Fl d
+.Op Fl D
+.Op Fl e
+.Op Fl H
+.Op Fl p Ar power_cond
+.Op Fl P
+.Op Fl r Ar restore_src
+.Op Fl s
+.Op Fl S Ar power_src
+.Op Fl T Ar timer
+.Nm
.Ic help
.Sh DESCRIPTION
The
@@ -2052,11 +2072,11 @@
.Dq portal ,
and
.Dq drive .
-.El
.It Fl V Ar vol_num
Specify the number of the logical volume to operate on.
If the media has multiple logical volumes, this will allow displaying
or writing attributes on the given logical volume.
+.El
.It Ic opcodes
Issue the REPORT SUPPORTED OPCODES service action of the
.Tn SCSI
@@ -2103,6 +2123,300 @@
The timeout values are in seconds.
The timeout descriptor also includes a command-specific
.El
+.It Ic zone
+Manage
+.Tn SCSI
+and
+.Tn ATA
+Zoned Block devices.
+This allows managing devices that conform to the
+.Tn SCSI
+Zoned Block Commands (ZBC) and
+.Tn ATA
+Zoned ATA Command Set (ZAC)
+specifications.
+Devices using these command sets are usually hard drives using Shingled
+Magnetic Recording (SMR).
+There are three types of SMR drives:
+.Bl -tag -width 13n
+.It Drive Managed
+Drive Managed drives look and act just like a standard random access block
+device, but underneath, the drive reads and writes the bulk of its capacity
+using SMR zones.
+Sequential writes will yield better performance, but writing sequentially
+is not required.
+.It Host Aware
+Host Aware drives expose the underlying zone layout via
+.Tn SCSI
+or
+.Tn ATA
+commands and allow the host to manage the zone conditions.
+The host is not required to manage the zones on the drive, though.
+Sequential writes will yield better performance in Sequential Write
+Preferred zones, but the host can write randomly in those zones.
+.It Host Managed
+Host Managed drives expose the underlying zone layout via
+.Tn SCSI
+or
+.Tn ATA
+commands.
+The host is required to access the zones according to the rules described
+by the zone layout.
+Any commands that violate the rules will be returned with an error.
+.El
+.Pp
+SMR drives are divided into zones (typically in the range of 256MB each)
+that fall into three general categories:
+.Bl -tag -width 20n
+.It Conventional
+These are also known as Non Write Pointer zones.
+These zones can be randomly written without an unexpected performance penalty.
+.It Sequential Preferred
+These zones should be written sequentially starting at the write pointer
+for the zone.
+They may be written randomly.
+Writes that do not conform to the zone layout may be significantly slower
+than expected.
+.It Sequential Required
+These zones must be written sequentially.
+If they are not written sequentially, starting at the write pointer, the
+command will fail.
+.El
+.Pp
+.Bl -tag -width 12n
+.It Fl c Ar cmd
+Specify the zone subcommand:
+.Bl -tag -width 6n
+.It rz
+Issue the Report Zones command.
+All zones are returned by default.
+Specify report options with
+.Fl o
+and printing options with
+.Fl P .
+Specify the starting LBA with
+.Fl l .
+Note that
+.Dq reportzones
+is also accepted as a command argument.
+.It open
+Explicitly open the zone specified by the starting LBA.
+.It close
+Close the zone specified by starting LBA.
+.It finish
+Finish the zone specified by the starting LBA.
+.It rwp
+Reset the write pointer for the zone specified by the starting LBA.
+.El
+.It Fl a
+For the Open, Close, Finish, and Reset Write Pointer operations, apply the
+operation to all zones on the drive.
+.It Fl l Ar lba
+Specify the starting LBA.
+For the Report Zones command, this tells the drive to report starting with
+the zone that starts at the given LBA.
+For the other commands, this allows the user to identify the zone requested
+by its starting LBA.
+The LBA may be specified in decimal, hexadecimal or octal notation.
+.It Fl o Ar rep_opt
+For the Report Zones command, specify a subset of zones to report.
+.Bl -tag -width 8n
+.It all
+Report all zones.
+This is the default.
+.It emtpy
+Report only empty zones.
+.It imp_open
+Report zones that are implicitly open.
+This means that the host has sent a write to the zone without explicitly
+opening the zone.
+.It exp_open
+Report zones that are explicitly open.
+.It closed
+Report zones that have been closed by the host.
+.It full
+Report zones that are full.
+.It ro
+Report zones that are in the read only state.
+Note that
+.Dq readonly
+is also accepted as an argument.
+.It offline
+Report zones that are in the offline state.
+.It reset
+Report zones where the device recommends resetting write pointers.
+.It nonseq
+Report zones that have the Non Sequential Resources Active flag set.
+These are zones that are Sequential Write Preferred, but have been written
+non-sequentially.
+.It nonwp
+Report Non Write Pointer zones, also known as Conventional zones.
+.El
+.It Fl P Ar print_opt
+Specify a printing option for Report Zones:
+.Bl -tag -width 7n
+.It normal
+Normal Report Zones output.
+This is the default.
+The summary and column headings are printed, fields are separated by spaces
+and the fields themselves may contain spaces.
+.It summary
+Just print the summary: the number of zones, the maximum LBA (LBA of the
+last logical block on the drive), and the value of the
+.Dq same
+field.
+The
+.Dq same
+field describes whether the zones on the drive are all identical, all
+different, or whether they are the same except for the last zone, etc.
+.It script
+Print the zones in a script friendly format.
+The summary and column headings are omitted, the fields are separated by
+commas, and the fields do not contain spaces.
+The fields contain underscores where spaces would normally be used.
+.El
+.El
+.It Ic epc
+Issue
+.Tn ATA
+Extended Power Conditions (EPC) feature set commands.
+This only works on
+.Tn ATA
+protocol drives, and will not work on
+.Tn SCSI
+protocol drives.
+It will work on
+.Tn SATA
+drives behind a
+.Tn SCSI
+to
+.Tn ATA
+translation layer (SAT).
+It may be helpful to read the ATA Command Set - 4 (ACS-4) description of
+the Extended Power Conditions feature set, available at t13.org, to
+understand the details of this particular
+.Nm
+subcommand.
+.Bl -tag -width 6n
+.It Fl c Ar cmd
+Specify the epc subcommand
+.Bl -tag -width 7n
+.It restore
+Restore drive power condition settings.
+.Bl -tag -width 6n
+.It Fl r Ar src
+Specify the source for the restored power settings, either
+.Dq default
+or
+.Dq saved .
+This argument is required.
+.It Fl s
+Save the settings.
+This only makes sense to specify when restoring from defaults.
+.El
+.It goto
+Go to the specified power condition.
+.Bl -tag -width 7n
+.It Fl p Ar cond
+Specify the power condition: Idle_a, Idle_b, Idle_c, Standby_y, Standby_z.
+This argument is required.
+.It Fl D
+Specify delayed entry to the power condition.
+The drive, if it supports this, can enter the power condition after the
+command completes.
+.It Fl H
+Hold the power condition.
+If the drive supports this option, it will hold the power condition and
+reject all commands that would normally cause it to exit that power
+condition.
+.El
+.It timer
+Set the timer value for a power condition and enable or disable the
+condition.
+See the
+.Dq list
+display described below to see what the current timer settings are for each
+Idle and Standby mode supported by the drive.
+.Bl -tag -width 8n
+.It Fl e
+Enable the power condition.
+One of
+.Fl e
+or
+.Fl d
+is requiretd.
+.It Fl d
+Disable the power condition.
+One of
+.Fl d
+or
+.Fl e
+is required.
+.It Fl T Ar timer
+Specify the timer in seconds.
+The user may specify a timer as a floating point number with a maximum
+supported resolution of tenths of a second.
+Drives may or may not support sub-second timer values.
+.It Fl p Ar cond
+Specify the power condition: Idle_a, Idle_b, Idle_c, Standby_y, Standby_z.
+This argument is required.
+.It Fl s
+Save the timer and power condition enable/disable state.
+By default, if this option is not specified, only the current values for
+this power condition will be affected.
+.El
+.It state
+Enable or disable a particular power condition.
+.Bl -tag -width 7n
+.It Fl p Ar cond
+Specify the power condition: Idle_a, Idle_b, Idle_c, Standby_y, Standby_z.
+This argument is required.
+.It Fl s
+Save the power condition enable/disable state.
+By default, if this option is not specified, only the current values for
+this power condition will be affected.
+.El
+.It enable
+Enable the Extended Power Condition (EPC) feature set.
+.It disable
+Disable the Extended Power Condition (EPC) feature set.
+.It source
+Specify the EPC power source.
+.Bl -tag -width 6n
+.It Fl S Ar src
+Specify the power source, either
+.Dq battery
+or
+.Dq nonbattery .
+.El
+.It status
+Get the current status of several parameters related to the Extended Power
+Condition (EPC) feature set, including whether APM and EPC are supported
+and enabled, whether Low Power Standby is supported, whether setting the
+EPC power source is supported, whether Low Power Standby is supported and
+the current power condition.
+.Bl -tag -width 3n
+.It Fl P
+Only report the current power condition.
+Some drives will exit their current power condition if a command other than
+the
+.Tn ATA
+CHECK POWER MODE command is received.
+If this flag is specified,
+.Nm
+will only issue the
+.Tn ATA
+CHECK POWER MODE command to the drive.
+.El
+.It list
+Display the
+.Tn ATA
+Power Conditions log (Log Address 0x08).
+This shows the list of Idle and Standby power conditions the drive
+supports, and a number of parameters about each condition, including
+whether it is enabled and what the timer value is.
+.El
+.El
.It Ic help
Print out verbose usage information.
.El
@@ -2336,6 +2650,86 @@
in tape drive sa0, and will display any
.Tn SCSI
errors that result.
+.Pp
+.Bd -literal -offset indent
+camcontrol zone da0 -v -c rz -P summary
+.Ed
+.Pp
+This will request the SMR zone list from disk da0, and print out a
+summary of the zone parameters, and display any
+.Tn SCSI
+or
+.Tn ATA
+errors that result.
+.Pp
+.Bd -literal -offset indent
+camcontrol zone da0 -v -c rz -o reset
+.Ed
+.Pp
+This will request the list of SMR zones that should have their write
+pointer reset from the disk da0, and display any
+.Tn SCSI
+or
+.Tn ATA
+errors that result.
+.Pp
+.Bd -literal -offset indent
+camcontrol zone da0 -v -c rwp -l 0x2c80000
+.Ed
+.Pp
+This will issue the Reset Write Pointer command to disk da0 for the zone
+that starts at LBA 0x2c80000 and display any
+.Tn SCSI
+or
+.Tn ATA
+errors that result.
+.Pp
+.Bd -literal -offset indent
+camcontrol epc ada0 -c timer -T 60.1 -p Idle_a -e -s
+.Ed
+.Pp
+Set the timer for the Idle_a power condition on drive
+.Pa ada0
+to 60.1 seconds, enable that particular power condition, and save the timer
+value and the enabled state of the power condition.
+.Pp
+.Bd -literal -offset indent
+camcontrol epc da4 -c goto -p Standby_z -H
+.Ed
+.Pp
+Tell drive
+.Pa da4
+to go to the Standby_z power state (which is
+the drive's lowest power state) and hold in that state until it is
+explicitly released by another
+.Cm goto
+command.
+.Pp
+.Bd -literal -offset indent
+camcontrol epc da2 -c status -P
+.Ed
+.Pp
+Report only the power state of
+drive
+.Pa da2 .
+Some drives will power up in response to the commands sent by the
+.Pa status
+subcommand, and the
+.Fl P
+option causes
+.Nm
+to only send the
+.Tn ATA
+CHECK POWER MODE command, which should not trigger a change in the drive's
+power state.
+.Pp
+.Bd -literal -offset indent
+camcontrol epc ada0 -c list
+.Ed
+.Pp
+Display the ATA Power Conditions log (Log Address 0x08) for
+drive
+.Pa ada0 .
.Sh SEE ALSO
.Xr cam 3 ,
.Xr cam_cdbparse 3 ,
Index: sbin/camcontrol/camcontrol.c
===================================================================
--- sbin/camcontrol/camcontrol.c
+++ sbin/camcontrol/camcontrol.c
@@ -101,7 +101,9 @@
CAM_CMD_AAM = 0x00000022,
CAM_CMD_ATTRIB = 0x00000023,
CAM_CMD_OPCODES = 0x00000024,
- CAM_CMD_REPROBE = 0x00000025
+ CAM_CMD_REPROBE = 0x00000025,
+ CAM_CMD_ZONE = 0x00000026,
+ CAM_CMD_EPC = 0x00000027
} cam_cmdmask;
typedef enum {
@@ -230,6 +232,8 @@
{"persist", CAM_CMD_PERSIST, CAM_ARG_NONE, "ai:I:k:K:o:ps:ST:U"},
{"attrib", CAM_CMD_ATTRIB, CAM_ARG_NONE, "a:ce:F:p:r:s:T:w:V:"},
{"opcodes", CAM_CMD_OPCODES, CAM_ARG_NONE, "No:s:T"},
+ {"zone", CAM_CMD_ZONE, CAM_ARG_NONE, "ac:l:No:P:"},
+ {"epc", CAM_CMD_EPC, CAM_ARG_NONE, "c:dDeHp:Pr:sS:T:"},
#endif /* MINIMALISTIC */
{"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
{"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
@@ -5071,13 +5075,16 @@
return (retval);
}
-void
+int
build_ata_cmd(union ccb *ccb, uint32_t retry_count, uint32_t flags,
uint8_t tag_action, uint8_t protocol, uint8_t ata_flags, uint16_t features,
- uint16_t sector_count, uint64_t lba, uint8_t command, uint8_t *data_ptr,
- uint16_t dxfer_len, uint8_t sense_len, uint32_t timeout,
+ uint16_t sector_count, uint64_t lba, uint8_t command, uint32_t auxiliary,
+ uint8_t *data_ptr, uint32_t dxfer_len, uint8_t *cdb_storage,
+ size_t cdb_storage_len, uint8_t sense_len, uint32_t timeout,
int is48bit, camcontrol_devtype devtype)
{
+ int retval = 0;
+
if (devtype == CC_DT_ATA) {
cam_fill_ataio(&ccb->ataio,
/*retries*/ retry_count,
@@ -5093,11 +5100,24 @@
else
ata_28bit_cmd(&ccb->ataio, command, features, lba,
sector_count);
+
+ if (auxiliary != 0) {
+ ccb->ataio.ata_flags |= ATA_FLAG_AUX;
+ ccb->ataio.aux = auxiliary;
+ }
+
+ if (ata_flags & AP_FLAG_CHK_COND)
+ ccb->ataio.cmd.flags |= CAM_ATAIO_NEEDRESULT;
+
+ if ((protocol & AP_PROTO_MASK) == AP_PROTO_DMA)
+ ccb->ataio.cmd.flags |= CAM_ATAIO_DMA;
+ else if ((protocol & AP_PROTO_MASK) == AP_PROTO_FPDMA)
+ ccb->ataio.cmd.flags |= CAM_ATAIO_FPDMA;
} else {
if (is48bit || lba > ATA_MAX_28BIT_LBA)
protocol |= AP_EXTEND;
- scsi_ata_pass_16(&ccb->csio,
+ retval = scsi_ata_pass(&ccb->csio,
/*retries*/ retry_count,
/*cbfcnp*/ NULL,
/*flags*/ flags,
@@ -5108,15 +5128,159 @@
/*sector_count*/ sector_count,
/*lba*/ lba,
/*command*/ command,
+ /*device*/ 0,
+ /*icc*/ 0,
+ /*auxiliary*/ auxiliary,
/*control*/ 0,
/*data_ptr*/ data_ptr,
/*dxfer_len*/ dxfer_len,
+ /*cdb_storage*/ cdb_storage,
+ /*cdb_storage_len*/ cdb_storage_len,
+ /*minimum_cmd_size*/ 0,
/*sense_len*/ sense_len,
/*timeout*/ timeout);
}
+
+ return (retval);
}
+int
+get_ata_status(struct cam_device *dev, union ccb *ccb, uint8_t *error,
+ uint16_t *count, uint64_t *lba, uint8_t *device, uint8_t *status)
+{
+ int retval = 0;
+ switch (ccb->ccb_h.func_code) {
+ case XPT_SCSI_IO: {
+ uint8_t opcode;
+ int error_code = 0, sense_key = 0, asc = 0, ascq = 0;
+
+ /*
+ * In this case, we have SCSI ATA PASS-THROUGH command, 12
+ * or 16 byte, and need to see what
+ */
+ if (ccb->ccb_h.flags & CAM_CDB_POINTER)
+ opcode = ccb->csio.cdb_io.cdb_ptr[0];
+ else
+ opcode = ccb->csio.cdb_io.cdb_bytes[0];
+ if ((opcode != ATA_PASS_12)
+ && (opcode != ATA_PASS_16)) {
+ retval = 1;
+ warnx("%s: unsupported opcode %02x", __func__, opcode);
+ goto bailout;
+ }
+
+ retval = scsi_extract_sense_ccb(ccb, &error_code, &sense_key,
+ &asc, &ascq);
+ /* Note: the _ccb() variant returns 0 for an error */
+ if (retval == 0) {
+ retval = 1;
+ goto bailout;
+ } else
+ retval = 0;
+
+ switch (error_code) {
+ case SSD_DESC_CURRENT_ERROR:
+ case SSD_DESC_DEFERRED_ERROR: {
+ struct scsi_sense_data_desc *sense;
+ struct scsi_sense_ata_ret_desc *desc;
+ uint8_t *desc_ptr;
+
+ sense = (struct scsi_sense_data_desc *)
+ &ccb->csio.sense_data;
+
+ desc_ptr = scsi_find_desc(sense, ccb->csio.sense_len -
+ ccb->csio.sense_resid, SSD_DESC_ATA);
+ if (desc_ptr == NULL) {
+ cam_error_print(dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto bailout;
+ }
+ desc = (struct scsi_sense_ata_ret_desc *)desc_ptr;
+
+ *error = desc->error;
+ *count = (desc->count_15_8 << 8) |
+ desc->count_7_0;
+ *lba = ((uint64_t)desc->lba_47_40 << 40) |
+ ((uint64_t)desc->lba_39_32 << 32) |
+ (desc->lba_31_24 << 24) |
+ (desc->lba_23_16 << 16) |
+ (desc->lba_15_8 << 8) |
+ desc->lba_7_0;
+ *device = desc->device;
+ *status = desc->status;
+
+ /*
+ * If the extend bit isn't set, the result is for a
+ * 12-byte ATA PASS-THROUGH command or a 16 or 32 byte
+ * command without the extend bit set. This means
+ * that the device is supposed to return 28-bit
+ * status. The count field is only 8 bits, and the
+ * LBA field is only 8 bits.
+ */
+ if ((desc->flags & SSD_DESC_ATA_FLAG_EXTEND) == 0){
+ *count &= 0xff;
+ *lba &= 0x0fffffff;
+ }
+ break;
+ }
+ case SSD_CURRENT_ERROR:
+ case SSD_DEFERRED_ERROR: {
+#if 0
+ struct scsi_sense_data_fixed *sense;
+#endif
+ /*
+ * XXX KDM need to support fixed sense data.
+ */
+ warnx("%s: Fixed sense data not supported yet",
+ __func__);
+ retval = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+ default:
+ retval = 1;
+ goto bailout;
+ break;
+ }
+
+ break;
+ }
+ case XPT_ATA_IO: {
+ struct ata_res *res;
+
+ /*
+ * In this case, we have an ATA command, and we need to
+ * fill in the requested values from the result register
+ * set.
+ */
+ res = &ccb->ataio.res;
+ *error = res->error;
+ *status = res->status;
+ *device = res->device;
+ *count = res->sector_count;
+ *lba = (res->lba_high << 16) |
+ (res->lba_mid << 8) |
+ (res->lba_low);
+ if (res->flags & CAM_ATAIO_48BIT) {
+ *count |= (res->sector_count_exp << 8);
+ *lba |= (res->lba_low_exp << 24) |
+ ((uint64_t)res->lba_mid_exp << 32) |
+ ((uint64_t)res->lba_high_exp << 40);
+ } else {
+ *lba |= (res->device & 0xf) << 24;
+ }
+ break;
+ }
+ default:
+ retval = 1;
+ break;
+ }
+bailout:
+ return (retval);
+}
+
static void
cpi_print(struct ccb_pathinq *cpi)
{
@@ -8774,6 +8938,11 @@
" [-p part][-s start][-T type][-V vol]\n"
" camcontrol opcodes [dev_id][generic args][-o opcode][-s SA]\n"
" [-N][-T]\n"
+" camcontrol zone [dev_id][generic args]<-c cmd> [-a] [-l LBA]\n"
+" [-o rep_opts] [-P print_opts]\n"
+" camcontrol epc [dev_id][generic_args]<-c cmd> [-d] [-D] [-e]\n"
+" [-H] [-p power_cond] [-P] [-r rst_src] [-s]\n"
+" [-S power_src] [-T timer]\n"
#endif /* MINIMALISTIC */
" camcontrol help\n");
if (!printlong)
@@ -8816,6 +8985,8 @@
"persist send the SCSI PERSISTENT RESERVE IN or OUT commands\n"
"attrib send the SCSI READ or WRITE ATTRIBUTE commands\n"
"opcodes send the SCSI REPORT SUPPORTED OPCODES command\n"
+"zone manage Zoned Block (Shingled) devices\n"
+"epc send ATA Extended Power Conditions commands\n"
"help this message\n"
"Device Identifiers:\n"
"bus:target specify the bus and target, lun defaults to 0\n"
@@ -8986,6 +9157,27 @@
"-s service_action specify the service action for the opcode\n"
"-N do not return SCSI error for unsupported SA\n"
"-T request nominal and recommended timeout values\n"
+"zone arguments:\n"
+"-c cmd required: rz, open, close, finish, or rwp\n"
+"-a apply the action to all zones\n"
+"-l LBA specify the zone starting LBA\n"
+"-o rep_opts report zones options: all, empty, imp_open, exp_open,\n"
+" closed, full, ro, offline, reset, nonseq, nonwp\n"
+"-P print_opt report zones printing: normal, summary, script\n"
+"epc arguments:\n"
+"-c cmd required: restore, goto, timer, state, enable, disable,\n"
+" source, status, list\n"
+"-d disable power mode (timer, state)\n"
+"-D delayed entry (goto)\n"
+"-e enable power mode (timer, state)\n"
+"-H hold power mode (goto)\n"
+"-p power_cond Idle_a, Idle_b, Idle_c, Standby_y, Standby_z (timer,\n"
+" state, goto)\n"
+"-P only display power mode (status)\n"
+"-r rst_src restore settings from: default, saved (restore)\n"
+"-s save mode (timer, state, restore)\n"
+"-S power_src set power source: battery, nonbattery (source)\n"
+"-T timer set timer, seconds, .1 sec resolution (timer)\n"
);
#endif /* MINIMALISTIC */
}
@@ -9341,7 +9533,14 @@
case CAM_CMD_REPROBE:
error = scsireprobe(cam_dev);
break;
-
+ case CAM_CMD_ZONE:
+ error = zone(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout, arglist & CAM_ARG_VERBOSE);
+ break;
+ case CAM_CMD_EPC:
+ error = epc(cam_dev, argc, argv, combinedopt,
+ retry_count, timeout, arglist & CAM_ARG_VERBOSE);
+ break;
#endif /* MINIMALISTIC */
case CAM_CMD_USAGE:
usage(1);
Index: sbin/camcontrol/epc.c
===================================================================
--- sbin/camcontrol/epc.c
+++ sbin/camcontrol/epc.c
@@ -0,0 +1,857 @@
+/*-
+ * Copyright (c) 2016 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * 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 MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * Authors: Ken Merry (Spectra Logic Corporation)
+ */
+/*
+ * ATA Extended Power Conditions (EPC) support
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/stdint.h>
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <sys/sbuf.h>
+#include <sys/queue.h>
+#include <sys/ata.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <limits.h>
+#include <err.h>
+#include <locale.h>
+
+#include <cam/cam.h>
+#include <cam/cam_debug.h>
+#include <cam/cam_ccb.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_da.h>
+#include <cam/scsi/scsi_pass.h>
+#include <cam/scsi/scsi_message.h>
+#include <camlib.h>
+#include "camcontrol.h"
+
+typedef enum {
+ EPC_ACTION_NONE = 0x00,
+ EPC_ACTION_LIST = 0x01,
+ EPC_ACTION_TIMER_SET = 0x02,
+ EPC_ACTION_IMMEDIATE = 0x03,
+ EPC_ACTION_GETMODE = 0x04
+} epc_action;
+
+static struct scsi_nv epc_flags[] = {
+ { "Supported", ATA_PCL_COND_SUPPORTED },
+ { "Saveable", ATA_PCL_COND_SUPPORTED },
+ { "Changeable", ATA_PCL_COND_CHANGEABLE },
+ { "Default Timer Enabled", ATA_PCL_DEFAULT_TIMER_EN },
+ { "Saved Timer Enabled", ATA_PCL_SAVED_TIMER_EN },
+ { "Current Timer Enabled", ATA_PCL_CURRENT_TIMER_EN },
+ { "Hold Power Condition Not Supported", ATA_PCL_HOLD_PC_NOT_SUP }
+};
+
+static struct scsi_nv epc_power_cond_map[] = {
+ { "Standby_z", ATA_EPC_STANDBY_Z },
+ { "z", ATA_EPC_STANDBY_Z },
+ { "Standby_y", ATA_EPC_STANDBY_Y },
+ { "y", ATA_EPC_STANDBY_Y },
+ { "Idle_a", ATA_EPC_IDLE_A },
+ { "a", ATA_EPC_IDLE_A },
+ { "Idle_b", ATA_EPC_IDLE_B },
+ { "b", ATA_EPC_IDLE_B },
+ { "Idle_c", ATA_EPC_IDLE_C },
+ { "c", ATA_EPC_IDLE_C }
+};
+
+static struct scsi_nv epc_rst_val[] = {
+ { "default", ATA_SF_EPC_RST_DFLT },
+ { "saved", 0}
+};
+
+static struct scsi_nv epc_ps_map[] = {
+ { "unknown", ATA_SF_EPC_SRC_UNKNOWN },
+ { "battery", ATA_SF_EPC_SRC_BAT },
+ { "notbattery", ATA_SF_EPC_SRC_NOT_BAT }
+};
+
+/*
+ * These aren't subcommands of the EPC SET FEATURES subcommand, but rather
+ * commands that determine the current capabilities and status of the drive.
+ * The EPC subcommands are limited to 4 bits, so we won't collide with any
+ * future values.
+ */
+#define CCTL_EPC_GET_STATUS 0x8001
+#define CCTL_EPC_LIST 0x8002
+
+static struct scsi_nv epc_cmd_map[] = {
+ { "restore", ATA_SF_EPC_RESTORE },
+ { "goto", ATA_SF_EPC_GOTO },
+ { "timer", ATA_SF_EPC_SET_TIMER },
+ { "state", ATA_SF_EPC_SET_STATE },
+ { "enable", ATA_SF_EPC_ENABLE },
+ { "disable", ATA_SF_EPC_DISABLE },
+ { "source", ATA_SF_EPC_SET_SOURCE },
+ { "status", CCTL_EPC_GET_STATUS },
+ { "list", CCTL_EPC_LIST }
+};
+
+static int epc_list(struct cam_device *device, camcontrol_devtype devtype,
+ union ccb *ccb, int retry_count, int timeout);
+static void epc_print_pcl_desc(struct ata_power_cond_log_desc *desc,
+ const char *prefix);
+static int epc_getmode(struct cam_device *device, camcontrol_devtype devtype,
+ union ccb *ccb, int retry_count, int timeout,
+ int power_only);
+static int epc_set_features(struct cam_device *device,
+ camcontrol_devtype devtype, union ccb *ccb,
+ int retry_count, int timeout, int action,
+ int power_cond, int timer, int enable, int save,
+ int delayed_entry, int hold, int power_src,
+ int restore_src);
+
+static void
+epc_print_pcl_desc(struct ata_power_cond_log_desc *desc, const char *prefix)
+{
+ int first;
+ unsigned int i, num_printed, max_chars;
+
+ first = 1;
+ max_chars = 75;
+
+ num_printed = printf("%sFlags: ", prefix);
+ for (i = 0; i < (sizeof(epc_flags) / sizeof(epc_flags[0])); i++) {
+ if ((desc->flags & epc_flags[i].value) == 0)
+ continue;
+ if (first == 0) {
+ num_printed += printf(", ");
+ }
+ if ((num_printed + strlen(epc_flags[i].name)) > max_chars) {
+ printf("\n");
+ num_printed = printf("%s ", prefix);
+ }
+ num_printed += printf("%s", epc_flags[i].name);
+ first = 0;
+ }
+ if (first != 0)
+ printf("None");
+ printf("\n");
+
+ printf("%sDefault timer setting: %.1f sec\n", prefix,
+ (double)(le32dec(desc->default_timer) / 10));
+ printf("%sSaved timer setting: %.1f sec\n", prefix,
+ (double)(le32dec(desc->saved_timer) / 10));
+ printf("%sCurrent timer setting: %.1f sec\n", prefix,
+ (double)(le32dec(desc->current_timer) / 10));
+ printf("%sNominal time to active: %.1f sec\n", prefix,
+ (double)(le32dec(desc->nom_time_to_active) / 10));
+ printf("%sMinimum timer: %.1f sec\n", prefix,
+ (double)(le32dec(desc->min_timer) / 10));
+ printf("%sMaximum timer: %.1f sec\n", prefix,
+ (double)(le32dec(desc->max_timer) / 10));
+ printf("%sNumber of transitions to power condition: %u\n", prefix,
+ le32dec(desc->num_transitions_to_pc));
+ printf("%sHours in power condition: %u\n", prefix,
+ le32dec(desc->hours_in_pc));
+}
+
+static int
+epc_list(struct cam_device *device, camcontrol_devtype devtype, union ccb *ccb,
+ int retry_count, int timeout)
+{
+ struct ata_power_cond_log_idle *idle_log;
+ struct ata_power_cond_log_standby *standby_log;
+ uint8_t log_buf[sizeof(*idle_log) + sizeof(*standby_log)];
+ uint16_t log_addr = ATA_POWER_COND_LOG;
+ uint16_t page_number = ATA_PCL_IDLE;
+ uint64_t lba;
+ int error = 0;
+
+ lba = (((uint64_t)page_number & 0xff00) << 32) |
+ ((page_number & 0x00ff) << 8) |
+ (log_addr & 0xff);
+
+ error = build_ata_cmd(ccb,
+ /*retry_count*/ retry_count,
+ /*flags*/ CAM_DIR_IN | CAM_DEV_QFRZDIS,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*protocol*/ AP_PROTO_DMA | AP_EXTEND,
+ /*ata_flags*/ AP_FLAG_BYT_BLOK_BLOCKS |
+ AP_FLAG_TLEN_SECT_CNT |
+ AP_FLAG_TDIR_FROM_DEV,
+ /*features*/ 0,
+ /*sector_count*/ 2,
+ /*lba*/ lba,
+ /*command*/ ATA_READ_LOG_DMA_EXT,
+ /*auxiliary*/ 0,
+ /*data_ptr*/ log_buf,
+ /*dxfer_len*/ sizeof(log_buf),
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 60000,
+ /*is48bit*/ 1,
+ /*devtype*/ devtype);
+
+ if (error != 0) {
+ warnx("%s: build_ata_cmd() failed, likely programmer error",
+ __func__);
+ goto bailout;
+ }
+
+ if (retry_count > 0)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ error = cam_send_ccb(device, ccb);
+ if (error != 0) {
+ warn("error sending ATA READ LOG EXT CCB");
+ error = 1;
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL,stderr);
+ error = 1;
+ goto bailout;
+ }
+
+ idle_log = (struct ata_power_cond_log_idle *)log_buf;
+ standby_log =
+ (struct ata_power_cond_log_standby *)&log_buf[sizeof(*idle_log)];
+
+ printf("ATA Power Conditions Log:\n");
+ printf(" Idle power conditions page:\n");
+ printf(" Idle A condition:\n");
+ epc_print_pcl_desc(&idle_log->idle_a_desc, " ");
+ printf(" Idle B condition:\n");
+ epc_print_pcl_desc(&idle_log->idle_b_desc, " ");
+ printf(" Idle C condition:\n");
+ epc_print_pcl_desc(&idle_log->idle_c_desc, " ");
+ printf(" Standby power conditions page:\n");
+ printf(" Standby Y condition:\n");
+ epc_print_pcl_desc(&standby_log->standby_y_desc, " ");
+ printf(" Standby Z condition:\n");
+ epc_print_pcl_desc(&standby_log->standby_z_desc, " ");
+bailout:
+ return (error);
+}
+
+static int
+epc_getmode(struct cam_device *device, camcontrol_devtype devtype,
+ union ccb *ccb, int retry_count, int timeout, int power_only)
+{
+ struct ata_params *ident = NULL;
+ struct ata_identify_log_sup_cap sup_cap;
+ const char *mode_name = NULL;
+ uint8_t error = 0, ata_device = 0, status = 0;
+ uint16_t count = 0;
+ uint64_t lba = 0;
+ uint32_t page_number, log_address;
+ uint64_t caps = 0;
+ int avail_bytes = 0;
+ int res_available = 0;
+ int retval;
+
+ retval = 0;
+
+ if (power_only != 0)
+ goto check_power_mode;
+
+ /*
+ * Get standard ATA Identify data.
+ */
+ retval = ata_do_identify(device, retry_count, timeout, ccb, &ident);
+ if (retval != 0) {
+ warnx("Couldn't get identify data");
+ goto bailout;
+ }
+
+ /*
+ * Get the ATA Identify Data Log (0x30),
+ * Supported Capabilities Page (0x03).
+ */
+ log_address = ATA_IDENTIFY_DATA_LOG;
+ page_number = ATA_IDL_SUP_CAP;
+ lba = (((uint64_t)page_number & 0xff00) << 32) |
+ ((page_number & 0x00ff) << 8) |
+ (log_address & 0xff);
+
+ bzero(&sup_cap, sizeof(sup_cap));
+ /*
+ * XXX KDM check the supported protocol.
+ */
+ retval = build_ata_cmd(ccb,
+ /*retry_count*/ retry_count,
+ /*flags*/ CAM_DIR_IN | CAM_DEV_QFRZDIS,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*protocol*/ AP_PROTO_DMA |
+ AP_EXTEND,
+ /*ata_flags*/ AP_FLAG_BYT_BLOK_BLOCKS |
+ AP_FLAG_TLEN_SECT_CNT |
+ AP_FLAG_TDIR_FROM_DEV,
+ /*features*/ 0,
+ /*sector_count*/ 1,
+ /*lba*/ lba,
+ /*command*/ ATA_READ_LOG_DMA_EXT,
+ /*auxiliary*/ 0,
+ /*data_ptr*/ (uint8_t *)&sup_cap,
+ /*dxfer_len*/ sizeof(sup_cap),
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 60000,
+ /*is48bit*/ 1,
+ /*devtype*/ devtype);
+
+ if (retval != 0) {
+ warnx("%s: build_ata_cmd() failed, likely a programmer error",
+ __func__);
+ goto bailout;
+ }
+
+ if (retry_count > 0)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ retval = cam_send_ccb(device, ccb);
+ if (retval != 0) {
+ warn("error sending ATA READ LOG CCB");
+ retval = 1;
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL,stderr);
+ retval = 1;
+ goto bailout;
+ }
+
+ if (ccb->ccb_h.func_code == XPT_SCSI_IO) {
+ avail_bytes = ccb->csio.dxfer_len - ccb->csio.resid;
+ } else {
+ avail_bytes = ccb->ataio.dxfer_len - ccb->ataio.resid;
+ }
+ if (avail_bytes < (int)sizeof(sup_cap)) {
+ warnx("Couldn't get enough of the ATA Supported "
+ "Capabilities log, %d bytes returned", avail_bytes);
+ retval = 1;
+ goto bailout;
+ }
+ caps = le64dec(sup_cap.sup_cap);
+ if ((caps & ATA_SUP_CAP_VALID) == 0) {
+ warnx("Supported capabilities bits are not valid");
+ retval = 1;
+ goto bailout;
+ }
+
+ printf("APM: %sSupported, %sEnabled\n",
+ (ident->support.command2 & ATA_SUPPORT_APM) ? "" : "NOT ",
+ (ident->enabled.command2 & ATA_SUPPORT_APM) ? "" : "NOT ");
+ printf("EPC: %sSupported, %sEnabled\n",
+ (ident->support2 & ATA_SUPPORT_EPC) ? "" : "NOT ",
+ (ident->enabled2 & ATA_ENABLED_EPC) ? "" : "NOT ");
+ printf("Low Power Standby %sSupported\n",
+ (caps & ATA_SC_LP_STANDBY_SUP) ? "" : "NOT ");
+ printf("Set EPC Power Source %sSupported\n",
+ (caps & ATA_SC_SET_EPC_PS_SUP) ? "" : "NOT ");
+
+
+check_power_mode:
+
+ retval = build_ata_cmd(ccb,
+ /*retry_count*/ retry_count,
+ /*flags*/ CAM_DIR_NONE | CAM_DEV_QFRZDIS,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*protocol*/ AP_PROTO_NON_DATA |
+ AP_EXTEND,
+ /*ata_flags*/ AP_FLAG_BYT_BLOK_BLOCKS |
+ AP_FLAG_TLEN_NO_DATA |
+ AP_FLAG_CHK_COND,
+ /*features*/ ATA_SF_EPC,
+ /*sector_count*/ 0,
+ /*lba*/ 0,
+ /*command*/ ATA_CHECK_POWER_MODE,
+ /*auxiliary*/ 0,
+ /*data_ptr*/ NULL,
+ /*dxfer_len*/ 0,
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 60000,
+ /*is48bit*/ 0,
+ /*devtype*/ devtype);
+
+ if (retval != 0) {
+ warnx("%s: build_ata_cmd() failed, likely a programmer error",
+ __func__);
+ goto bailout;
+ }
+
+ if (retry_count > 0)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ retval = cam_send_ccb(device, ccb);
+ if (retval != 0) {
+ warn("error sending ATA CHECK POWER MODE CCB");
+ retval = 1;
+ goto bailout;
+ }
+
+ /*
+ * Check to see whether we got the requested ATA result if this
+ * is an SCSI ATA PASS-THROUGH command.
+ */
+ if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR)
+ && (ccb->csio.scsi_status == SCSI_STATUS_CHECK_COND)) {
+ int error_code, sense_key, asc, ascq;
+
+ retval = scsi_extract_sense_ccb(ccb, &error_code,
+ &sense_key, &asc, &ascq);
+ if (retval == 0) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL,
+ stderr);
+ retval = 1;
+ goto bailout;
+ }
+ if ((sense_key == SSD_KEY_RECOVERED_ERROR)
+ && (asc == 0x00)
+ && (ascq == 0x1d)) {
+ res_available = 1;
+ }
+
+ }
+ if (((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)
+ && (res_available == 0)) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL,stderr);
+ retval = 1;
+ goto bailout;
+ }
+
+ retval = get_ata_status(device, ccb, &error, &count, &lba, &ata_device,
+ &status);
+ if (retval != 0) {
+ warnx("Unable to get ATA CHECK POWER MODE result");
+ retval = 1;
+ goto bailout;
+ }
+
+ mode_name = scsi_nv_to_str(epc_power_cond_map,
+ sizeof(epc_power_cond_map) / sizeof(epc_power_cond_map[0]), count);
+ printf("Current power state: ");
+ /* Note: ident can be null in power_only mode */
+ if ((ident == NULL)
+ || (ident->enabled2 & ATA_ENABLED_EPC)) {
+ if (mode_name != NULL)
+ printf("%s", mode_name);
+ else if (count == 0xff) {
+ printf("PM0:Active or PM1:Idle");
+ }
+ } else {
+ switch (count) {
+ case 0x00:
+ printf("PM2:Standby");
+ break;
+ case 0x80:
+ printf("PM1:Idle");
+ break;
+ case 0xff:
+ printf("PM0:Active or PM1:Idle");
+ break;
+ }
+ }
+ printf("(0x%02x)\n", count);
+
+ if (power_only != 0)
+ goto bailout;
+
+ if (caps & ATA_SC_LP_STANDBY_SUP) {
+ uint32_t wait_mode;
+
+ wait_mode = (lba >> 20) & 0xff;
+ if (wait_mode == 0xff) {
+ printf("Device not waiting to enter lower power "
+ "condition");
+ } else {
+ mode_name = scsi_nv_to_str(epc_power_cond_map,
+ sizeof(epc_power_cond_map) /
+ sizeof(epc_power_cond_map[0]), wait_mode);
+ printf("Device waiting to enter mode %s (0x%02x)\n",
+ (mode_name != NULL) ? mode_name : "Unknown",
+ wait_mode);
+ }
+ printf("Device is %sheld in the current power condition\n",
+ (lba & 0x80000) ? "" : "NOT ");
+ }
+bailout:
+ return (retval);
+
+}
+
+static int
+epc_set_features(struct cam_device *device, camcontrol_devtype devtype,
+ union ccb *ccb, int retry_count, int timeout, int action,
+ int power_cond, int timer, int enable, int save,
+ int delayed_entry, int hold, int power_src, int restore_src)
+{
+ uint64_t lba;
+ uint16_t count = 0;
+ int error;
+
+ error = 0;
+
+ lba = action;
+
+ switch (action) {
+ case ATA_SF_EPC_SET_TIMER:
+ lba |= ((timer << ATA_SF_EPC_TIMER_SHIFT) &
+ ATA_SF_EPC_TIMER_MASK);
+ /* FALLTHROUGH */
+ case ATA_SF_EPC_SET_STATE:
+ lba |= (enable ? ATA_SF_EPC_TIMER_EN : 0) |
+ (save ? ATA_SF_EPC_TIMER_SAVE : 0);
+ count = power_cond;
+ break;
+ case ATA_SF_EPC_GOTO:
+ count = power_cond;
+ lba |= (delayed_entry ? ATA_SF_EPC_GOTO_DELAY : 0) |
+ (hold ? ATA_SF_EPC_GOTO_HOLD : 0);
+ break;
+ case ATA_SF_EPC_RESTORE:
+ lba |= restore_src |
+ (save ? ATA_SF_EPC_RST_SAVE : 0);
+ break;
+ case ATA_SF_EPC_ENABLE:
+ case ATA_SF_EPC_DISABLE:
+ break;
+ case ATA_SF_EPC_SET_SOURCE:
+ count = power_src;
+ break;
+ }
+
+ error = build_ata_cmd(ccb,
+ /*retry_count*/ retry_count,
+ /*flags*/ CAM_DIR_NONE | CAM_DEV_QFRZDIS,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*protocol*/ AP_PROTO_NON_DATA | AP_EXTEND,
+ /*ata_flags*/ AP_FLAG_BYT_BLOK_BLOCKS |
+ AP_FLAG_TLEN_NO_DATA |
+ AP_FLAG_TDIR_FROM_DEV,
+ /*features*/ ATA_SF_EPC,
+ /*sector_count*/ count,
+ /*lba*/ lba,
+ /*command*/ ATA_SETFEATURES,
+ /*auxiliary*/ 0,
+ /*data_ptr*/ NULL,
+ /*dxfer_len*/ 0,
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 60000,
+ /*is48bit*/ 1,
+ /*devtype*/ devtype);
+
+ if (error != 0) {
+ warnx("%s: build_ata_cmd() failed, likely a programmer error",
+ __func__);
+ goto bailout;
+ }
+
+ if (retry_count > 0)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ error = cam_send_ccb(device, ccb);
+ if (error != 0) {
+ warn("error sending ATA SET FEATURES CCB");
+ error = 1;
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL,stderr);
+ error = 1;
+ goto bailout;
+ }
+
+bailout:
+ return (error);
+}
+
+int
+epc(struct cam_device *device, int argc, char **argv, char *combinedopt,
+ int retry_count, int timeout, int verbosemode __unused)
+{
+ union ccb *ccb = NULL;
+ int error = 0;
+ int c;
+ int action = -1;
+ camcontrol_devtype devtype;
+ double timer_val = -1;
+ int timer_tenths = 0, power_cond = -1;
+ int delayed_entry = 0, hold = 0;
+ int enable = -1, save = 0;
+ int restore_src = -1;
+ int power_src = -1;
+ int power_only = 0;
+
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ error = 1;
+ goto bailout;
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'c': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(epc_cmd_map,
+ (sizeof(epc_cmd_map) / sizeof(epc_cmd_map[0])),
+ optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ action = epc_cmd_map[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "epc command",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'd':
+ enable = 0;
+ break;
+ case 'D':
+ delayed_entry = 1;
+ break;
+ case 'e':
+ enable = 1;
+ break;
+ case 'H':
+ hold = 1;
+ break;
+ case 'p': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(epc_power_cond_map,
+ (sizeof(epc_power_cond_map) /
+ sizeof(epc_power_cond_map[0])), optarg,
+ &entry_num, SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ power_cond =epc_power_cond_map[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "power condition",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'P':
+ power_only = 1;
+ break;
+ case 'r': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(epc_rst_val,
+ (sizeof(epc_rst_val) /
+ sizeof(epc_rst_val[0])), optarg,
+ &entry_num, SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ restore_src = epc_rst_val[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid",
+ "restore value source", optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 's':
+ save = 1;
+ break;
+ case 'S': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(epc_ps_map,
+ (sizeof(epc_ps_map) / sizeof(epc_ps_map[0])),
+ optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ power_src = epc_ps_map[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "power source",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'T': {
+ char *endptr;
+
+ timer_val = strtod(optarg, &endptr);
+ if (timer_val < 0) {
+ warnx("Invalid timer value %f", timer_val);
+ error = 1;
+ goto bailout;
+ } else if (*endptr != '\0') {
+ warnx("Invalid timer value %s", optarg);
+ error = 1;
+ goto bailout;
+ }
+ timer_tenths = timer_val * 10;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (action == -1) {
+ warnx("Must specify an action");
+ error = 1;
+ goto bailout;
+ }
+
+ error = get_device_type(device, retry_count, timeout,
+ /*printerrors*/ 1, &devtype);
+ if (error != 0)
+ errx(1, "Unable to determine device type");
+
+ switch (devtype) {
+ case CC_DT_ATA:
+ case CC_DT_ATA_BEHIND_SCSI:
+ break;
+ default:
+ warnx("The epc subcommand only works with ATA protocol "
+ "devices");
+ error = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+
+ switch (action) {
+ case ATA_SF_EPC_SET_TIMER:
+ if (timer_val == -1) {
+ warnx("Must specify a timer value (-T time)");
+ error = 1;
+ }
+ case ATA_SF_EPC_SET_STATE:
+ if (enable == -1) {
+ warnx("Must specify enable (-e) or disable (-d)");
+ error = 1;
+ }
+ /* FALLTHROUGH */
+ case ATA_SF_EPC_GOTO:
+ if (power_cond == -1) {
+ warnx("Must specify a power condition with -p");
+ error = 1;
+ }
+ if (error != 0)
+ goto bailout;
+ break;
+ case ATA_SF_EPC_SET_SOURCE:
+ if (power_src == -1) {
+ warnx("Must specify a power source (-S battery or "
+ "-S notbattery) value");
+ error = 1;
+ goto bailout;
+ }
+ break;
+ case ATA_SF_EPC_RESTORE:
+ if (restore_src == -1) {
+ warnx("Must specify a source for restored value, "
+ "-r default or -r saved");
+ error = 1;
+ goto bailout;
+ }
+ break;
+ case ATA_SF_EPC_ENABLE:
+ case ATA_SF_EPC_DISABLE:
+ case CCTL_EPC_GET_STATUS:
+ case CCTL_EPC_LIST:
+ default:
+ break;
+ }
+
+ switch (action) {
+ case CCTL_EPC_GET_STATUS:
+ error = epc_getmode(device, devtype, ccb, retry_count, timeout,
+ power_only);
+ break;
+ case CCTL_EPC_LIST:
+ error = epc_list(device, devtype, ccb, retry_count, timeout);
+ break;
+ case ATA_SF_EPC_RESTORE:
+ case ATA_SF_EPC_GOTO:
+ case ATA_SF_EPC_SET_TIMER:
+ case ATA_SF_EPC_SET_STATE:
+ case ATA_SF_EPC_ENABLE:
+ case ATA_SF_EPC_DISABLE:
+ case ATA_SF_EPC_SET_SOURCE:
+ error = epc_set_features(device, devtype, ccb, retry_count,
+ timeout, action, power_cond, timer_tenths, enable, save,
+ delayed_entry, hold, power_src, restore_src);
+ break;
+ default:
+ warnx("Not implemented yet");
+ error = 1;
+ goto bailout;
+ break;
+ }
+
+
+bailout:
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ return (error);
+}
Index: sbin/camcontrol/fwdownload.c
===================================================================
--- sbin/camcontrol/fwdownload.c
+++ sbin/camcontrol/fwdownload.c
@@ -692,7 +692,7 @@
break;
case CC_DT_ATA_BEHIND_SCSI:
case CC_DT_ATA: {
- build_ata_cmd(ccb,
+ retval = build_ata_cmd(ccb,
/*retries*/ 1,
/*flags*/ CAM_DIR_IN,
/*tag_action*/ MSG_SIMPLE_Q_TAG,
@@ -704,12 +704,21 @@
/*sector_count*/ (uint8_t) dxfer_len,
/*lba*/ 0,
/*command*/ ATA_ATA_IDENTIFY,
+ /*auxiliary*/ 0,
/*data_ptr*/ (uint8_t *)ptr,
/*dxfer_len*/ dxfer_len,
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
/*sense_len*/ SSD_FULL_SIZE,
/*timeout*/ timeout ? timeout : 30 * 1000,
/*is48bit*/ 0,
/*devtype*/ devtype);
+ if (retval != 0) {
+ retval = -1;
+ warnx("%s: build_ata_cmd() failed, likely "
+ "programmer error", __func__);
+ goto bailout;
+ }
break;
}
default:
@@ -847,7 +856,7 @@
off = (uint32_t)(pkt_ptr - buf);
- build_ata_cmd(ccb,
+ retval = build_ata_cmd(ccb,
/*retry_count*/ retry_count,
/*flags*/ CAM_DIR_OUT | CAM_DEV_QFRZDIS,
/*tag_action*/ CAM_TAG_ACTION_NONE,
@@ -859,12 +868,21 @@
/*sector_count*/ ATA_MAKE_SECTORS(pkt_size),
/*lba*/ ATA_MAKE_LBA(off, pkt_size),
/*command*/ ATA_DOWNLOAD_MICROCODE,
+ /*auxiliary*/ 0,
/*data_ptr*/ (uint8_t *)pkt_ptr,
/*dxfer_len*/ pkt_size,
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
/*sense_len*/ SSD_FULL_SIZE,
/*timeout*/ timeout ? timeout : WB_TIMEOUT,
/*is48bit*/ 0,
/*devtype*/ devtype);
+
+ if (retval != 0) {
+ warnx("%s: build_ata_cmd() failed, likely "
+ "programmer error", __func__);
+ goto bailout;
+ }
break;
}
default:
Index: sbin/camcontrol/zone.c
===================================================================
--- sbin/camcontrol/zone.c
+++ sbin/camcontrol/zone.c
@@ -0,0 +1,676 @@
+/*-
+ * Copyright (c) 2015, 2016 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * 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 MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * Authors: Ken Merry (Spectra Logic Corporation)
+ */
+/*
+ * SCSI and ATA Shingled Media Recording (SMR) support for camcontrol(8).
+ * This is an implementation of the SCSI ZBC and ATA ZAC specs.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/stdint.h>
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <sys/sbuf.h>
+#include <sys/queue.h>
+#include <sys/chio.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <limits.h>
+#include <err.h>
+#include <locale.h>
+
+#include <cam/cam.h>
+#include <cam/cam_debug.h>
+#include <cam/cam_ccb.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_da.h>
+#include <cam/scsi/scsi_pass.h>
+#include <cam/scsi/scsi_ch.h>
+#include <cam/scsi/scsi_message.h>
+#include <camlib.h>
+#include "camcontrol.h"
+
+static struct scsi_nv zone_cmd_map[] = {
+ { "rz", ZBC_IN_SA_REPORT_ZONES },
+ { "reportzones", ZBC_IN_SA_REPORT_ZONES },
+ { "close", ZBC_OUT_SA_CLOSE },
+ { "finish", ZBC_OUT_SA_FINISH },
+ { "open", ZBC_OUT_SA_OPEN },
+ { "rwp", ZBC_OUT_SA_RWP }
+};
+
+static struct scsi_nv zone_rep_opts[] = {
+ { "all", ZBC_IN_REP_ALL_ZONES },
+ { "empty", ZBC_IN_REP_EMPTY },
+ { "imp_open", ZBC_IN_REP_IMP_OPEN },
+ { "exp_open", ZBC_IN_REP_EXP_OPEN },
+ { "closed", ZBC_IN_REP_CLOSED },
+ { "full", ZBC_IN_REP_FULL },
+ { "readonly", ZBC_IN_REP_READONLY },
+ { "ro", ZBC_IN_REP_READONLY },
+ { "offline", ZBC_IN_REP_OFFLINE },
+ { "rwp", ZBC_IN_REP_RESET },
+ { "reset", ZBC_IN_REP_RESET },
+ { "nonseq", ZBC_IN_REP_NON_SEQ },
+ { "nonwp", ZBC_IN_REP_NON_WP }
+};
+
+typedef enum {
+ ZONE_OF_NORMAL = 0x00,
+ ZONE_OF_SUMMARY = 0x01,
+ ZONE_OF_SCRIPT = 0x02
+} zone_output_flags;
+
+static struct scsi_nv zone_print_opts[] = {
+ { "normal", ZONE_OF_NORMAL },
+ { "summary", ZONE_OF_SUMMARY },
+ { "script", ZONE_OF_SCRIPT }
+};
+
+#define ZAC_ATA_SECTOR_COUNT(bcount) (((bcount) / 512) & 0xffff)
+
+typedef enum {
+ ZONE_PRINT_OK,
+ ZONE_PRINT_MORE_DATA,
+ ZONE_PRINT_ERROR
+} zone_print_status;
+
+typedef enum {
+ ZONE_FW_START,
+ ZONE_FW_LEN,
+ ZONE_FW_WP,
+ ZONE_FW_TYPE,
+ ZONE_FW_COND,
+ ZONE_FW_SEQ,
+ ZONE_FW_RESET,
+ ZONE_NUM_FIELDS
+} zone_field_widths;
+
+zone_print_status zone_rz_print(uint8_t *data_ptr, uint32_t valid_len,
+ int ata_format, zone_output_flags out_flags,
+ int first_pass, uint64_t *next_start_lba);
+
+
+zone_print_status
+zone_rz_print(uint8_t *data_ptr, uint32_t valid_len, int ata_format,
+ zone_output_flags out_flags, int first_pass,
+ uint64_t *next_start_lba)
+{
+ struct scsi_report_zones_hdr *hdr = NULL;
+ struct scsi_report_zones_desc *desc = NULL;
+ uint32_t hdr_len, len;
+ uint64_t max_lba, next_lba = 0;
+ int more_data = 0;
+ zone_print_status status = ZONE_PRINT_OK;
+ char tmpstr[80];
+ int field_widths[ZONE_NUM_FIELDS];
+ char word_sep;
+
+ if (valid_len < sizeof(*hdr)) {
+ status = ZONE_PRINT_ERROR;
+ goto bailout;
+ }
+
+ hdr = (struct scsi_report_zones_hdr *)data_ptr;
+
+ field_widths[ZONE_FW_START] = 11;
+ field_widths[ZONE_FW_LEN] = 6;
+ field_widths[ZONE_FW_WP] = 11;
+ field_widths[ZONE_FW_TYPE] = 13;
+ field_widths[ZONE_FW_COND] = 13;
+ field_widths[ZONE_FW_SEQ] = 14;
+ field_widths[ZONE_FW_RESET] = 16;
+
+ if (ata_format == 0) {
+ hdr_len = scsi_4btoul(hdr->length);
+ max_lba = scsi_8btou64(hdr->maximum_lba);
+ } else {
+ hdr_len = le32dec(hdr->length);
+ max_lba = le64dec(hdr->maximum_lba);
+ }
+
+ if (hdr_len > (valid_len + sizeof(*hdr))) {
+ more_data = 1;
+ status = ZONE_PRINT_MORE_DATA;
+ }
+
+ len = MIN(valid_len - sizeof(*hdr), hdr_len);
+
+ if (out_flags == ZONE_OF_SCRIPT)
+ word_sep = '_';
+ else
+ word_sep = ' ';
+
+ if ((out_flags != ZONE_OF_SCRIPT)
+ && (first_pass != 0)) {
+ printf("%zu zones, Maximum LBA %#jx (%ju)\n",
+ hdr_len / sizeof(*desc), (uintmax_t)max_lba,
+ (uintmax_t)max_lba);
+
+ switch (hdr->byte4 & SRZ_SAME_MASK) {
+ case SRZ_SAME_ALL_DIFFERENT:
+ printf("Zone lengths and types may vary\n");
+ break;
+ case SRZ_SAME_ALL_SAME:
+ printf("Zone lengths and types are all the same\n");
+ break;
+ case SRZ_SAME_LAST_DIFFERENT:
+ printf("Zone types are the same, last zone length "
+ "differs\n");
+ break;
+ case SRZ_SAME_TYPES_DIFFERENT:
+ printf("Zone lengths are the same, types vary\n");
+ break;
+ default:
+ printf("Unknown SAME field value %#x\n",
+ hdr->byte4 & SRZ_SAME_MASK);
+ break;
+ }
+ }
+ if (out_flags == ZONE_OF_SUMMARY) {
+ status = ZONE_PRINT_OK;
+ goto bailout;
+ }
+
+ if ((out_flags == ZONE_OF_NORMAL)
+ && (first_pass != 0)) {
+ printf("%*s %*s %*s %*s %*s %*s %*s\n",
+ field_widths[ZONE_FW_START], "Start LBA",
+ field_widths[ZONE_FW_LEN], "Length",
+ field_widths[ZONE_FW_WP], "WP LBA",
+ field_widths[ZONE_FW_TYPE], "Zone Type",
+ field_widths[ZONE_FW_COND], "Condition",
+ field_widths[ZONE_FW_SEQ], "Sequential",
+ field_widths[ZONE_FW_RESET], "Reset");
+ }
+
+ for (desc = &hdr->desc_list[0]; len >= sizeof(*desc);
+ len -= sizeof(*desc), desc++) {
+ uint64_t length, start_lba, wp_lba;
+
+ if (ata_format == 0) {
+ length = scsi_8btou64(desc->zone_length);
+ start_lba = scsi_8btou64(desc->zone_start_lba);
+ wp_lba = scsi_8btou64(desc->write_pointer_lba);
+ } else {
+ length = le64dec(desc->zone_length);
+ start_lba = le64dec(desc->zone_start_lba);
+ wp_lba = le64dec(desc->write_pointer_lba);
+ }
+
+ printf("%#*jx, %*ju, %#*jx, ", field_widths[ZONE_FW_START],
+ (uintmax_t)start_lba, field_widths[ZONE_FW_LEN],
+ (uintmax_t)length, field_widths[ZONE_FW_WP],
+ (uintmax_t)wp_lba);
+
+ switch (desc->zone_type & SRZ_TYPE_MASK) {
+ case SRZ_TYPE_CONVENTIONAL:
+ snprintf(tmpstr, sizeof(tmpstr), "Conventional");
+ break;
+ case SRZ_TYPE_SEQ_PREFERRED:
+ case SRZ_TYPE_SEQ_REQUIRED:
+ snprintf(tmpstr, sizeof(tmpstr), "Seq%c%s",
+ word_sep, ((desc->zone_type & SRZ_TYPE_MASK) ==
+ SRZ_TYPE_SEQ_PREFERRED) ? "Preferred" :
+ "Required");
+ break;
+ default:
+ snprintf(tmpstr, sizeof(tmpstr), "Zone%ctype%c%#x",
+ word_sep, word_sep,desc->zone_type &
+ SRZ_TYPE_MASK);
+ break;
+ }
+ printf("%*s, ", field_widths[ZONE_FW_TYPE], tmpstr);
+
+ switch (desc->zone_flags & SRZ_ZONE_COND_MASK) {
+ case SRZ_ZONE_COND_NWP:
+ snprintf(tmpstr, sizeof(tmpstr), "NWP");
+ break;
+ case SRZ_ZONE_COND_EMPTY:
+ snprintf(tmpstr, sizeof(tmpstr), "Empty");
+ break;
+ case SRZ_ZONE_COND_IMP_OPEN:
+ snprintf(tmpstr, sizeof(tmpstr), "Implicit%cOpen",
+ word_sep);
+ break;
+ case SRZ_ZONE_COND_EXP_OPEN:
+ snprintf(tmpstr, sizeof(tmpstr), "Explicit%cOpen",
+ word_sep);
+ break;
+ case SRZ_ZONE_COND_CLOSED:
+ snprintf(tmpstr, sizeof(tmpstr), "Closed");
+ break;
+ case SRZ_ZONE_COND_READONLY:
+ snprintf(tmpstr, sizeof(tmpstr), "Readonly");
+ break;
+ case SRZ_ZONE_COND_FULL:
+ snprintf(tmpstr, sizeof(tmpstr), "Full");
+ break;
+ case SRZ_ZONE_COND_OFFLINE:
+ snprintf(tmpstr, sizeof(tmpstr), "Offline");
+ break;
+ default:
+ snprintf(tmpstr, sizeof(tmpstr), "%#x",
+ desc->zone_flags & SRZ_ZONE_COND_MASK);
+ break;
+ }
+
+ printf("%*s, ", field_widths[ZONE_FW_COND], tmpstr);
+
+ if (desc->zone_flags & SRZ_ZONE_NON_SEQ)
+ snprintf(tmpstr, sizeof(tmpstr), "Non%cSequential",
+ word_sep);
+ else
+ snprintf(tmpstr, sizeof(tmpstr), "Sequential");
+
+ printf("%*s, ", field_widths[ZONE_FW_SEQ], tmpstr);
+
+ if (desc->zone_flags & SRZ_ZONE_RESET)
+ snprintf(tmpstr, sizeof(tmpstr), "Reset%cNeeded",
+ word_sep);
+ else
+ snprintf(tmpstr, sizeof(tmpstr), "No%cReset%cNeeded",
+ word_sep, word_sep);
+
+ printf("%*s\n", field_widths[ZONE_FW_RESET], tmpstr);
+
+ next_lba = start_lba + length;
+ }
+bailout:
+ *next_start_lba = next_lba;
+
+ return (status);
+}
+
+int
+zone(struct cam_device *device, int argc, char **argv, char *combinedopt,
+ int retry_count, int timeout, int verbosemode __unused)
+{
+ union ccb *ccb = NULL;
+ int action = -1, rep_option = -1;
+ int all_zones = 0;
+ uint64_t lba = 0;
+ int error = 0;
+ uint8_t *data_ptr = NULL;
+ uint32_t alloc_len = 65536, valid_len = 0;
+ camcontrol_devtype devtype;
+ int ata_format = 0, use_ncq = 0;
+ int first_pass = 1;
+ zone_print_status zp_status;
+ zone_output_flags out_flags = ZONE_OF_NORMAL;
+ uint8_t *cdb_storage = NULL;
+ int cdb_storage_len = 32;
+ int c;
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("%s: error allocating CCB", __func__);
+ error = 1;
+ goto bailout;
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ while ((c = getopt(argc, argv, combinedopt)) != -1) {
+ switch (c) {
+ case 'a':
+ all_zones = 1;
+ break;
+ case 'c': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(zone_cmd_map,
+ (sizeof(zone_cmd_map) / sizeof(zone_cmd_map[0])),
+ optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ action = zone_cmd_map[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "zone command",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'l': {
+ char *endptr;
+
+ lba = strtoull(optarg, &endptr, 0);
+ if (*endptr != '\0') {
+ warnx("%s: invalid lba argument %s", __func__,
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'N':
+ use_ncq = 1;
+ break;
+ case 'o': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(zone_rep_opts,
+ (sizeof(zone_rep_opts) /sizeof(zone_rep_opts[0])),
+ optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ rep_option = zone_rep_opts[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "report zones",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'P': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(zone_print_opts,
+ (sizeof(zone_print_opts) /
+ sizeof(zone_print_opts[0])), optarg, &entry_num,
+ SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ out_flags = zone_print_opts[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "print",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if (action == -1) {
+ warnx("%s: must specify -c <zone_cmd>", __func__);
+ error = 1;
+ goto bailout;
+ }
+ error = get_device_type(device, retry_count, timeout,
+ /*printerrors*/ 1, &devtype);
+ if (error != 0)
+ errx(1, "Unable to determine device type");
+
+ if (action == ZBC_IN_SA_REPORT_ZONES) {
+
+ data_ptr = malloc(alloc_len);
+ if (data_ptr == NULL)
+ err(1, "unable to allocate %u bytes", alloc_len);
+
+restart_report:
+ bzero(data_ptr, alloc_len);
+
+ switch (devtype) {
+ case CC_DT_SCSI:
+ scsi_zbc_in(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*service_action*/ action,
+ /*zone_start_lba*/ lba,
+ /*zone_options*/ (rep_option != -1) ?
+ rep_option : 0,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ alloc_len,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 60000);
+ break;
+ case CC_DT_ATA:
+ case CC_DT_ATA_BEHIND_SCSI: {
+ uint8_t command = 0;
+ uint8_t protocol = 0;
+ uint16_t features = 0, sector_count = 0;
+ uint32_t auxiliary = 0;
+
+ /*
+ * XXX KDM support the partial bit?
+ */
+ if (use_ncq == 0) {
+ command = ATA_ZAC_MANAGEMENT_IN;
+ features = action;
+ if (rep_option != -1)
+ features |= (rep_option << 8);
+ sector_count = ZAC_ATA_SECTOR_COUNT(alloc_len);
+ protocol = AP_PROTO_DMA;
+ } else {
+ cdb_storage = calloc(cdb_storage_len, 1);
+ if (cdb_storage == NULL)
+ err(1, "couldn't allocate memory");
+
+ command = ATA_RECV_FPDMA_QUEUED;
+ features = ZAC_ATA_SECTOR_COUNT(alloc_len);
+ sector_count = ATA_RFPDMA_ZAC_MGMT_IN << 8;
+ auxiliary = action & 0xf;
+ if (rep_option != -1)
+ auxiliary |= rep_option << 8;
+ protocol = AP_PROTO_FPDMA;
+ }
+
+ error = build_ata_cmd(ccb,
+ /*retry_count*/ retry_count,
+ /*flags*/ CAM_DIR_IN | CAM_DEV_QFRZDIS,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*protocol*/ protocol,
+ /*ata_flags*/ AP_FLAG_BYT_BLOK_BLOCKS |
+ AP_FLAG_TLEN_SECT_CNT |
+ AP_FLAG_TDIR_FROM_DEV,
+ /*features*/ features,
+ /*sector_count*/ sector_count,
+ /*lba*/ lba,
+ /*command*/ command,
+ /*auxiliary*/ auxiliary,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ ZAC_ATA_SECTOR_COUNT(alloc_len)*512,
+ /*cdb_storage*/ cdb_storage,
+ /*cdb_storage_len*/ cdb_storage_len,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 60000,
+ /*is48bit*/ 1,
+ /*devtype*/ devtype);
+
+ if (error != 0) {
+ warnx("%s: build_ata_cmd() failed, likely "
+ "programmer error", __func__);
+ goto bailout;
+ }
+
+ ata_format = 1;
+
+ break;
+ }
+ default:
+ warnx("%s: Unknown device type %d", __func__,devtype);
+ error = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+ } else {
+ /*
+ * XXX KDM the current methodology is to always send ATA
+ * commands to ATA devices. Need to figure out how to
+ * detect whether a SCSI to ATA translation layer will
+ * translate ZBC IN/OUT commands to the appropriate ZAC
+ * command.
+ */
+ switch (devtype) {
+ case CC_DT_SCSI:
+ scsi_zbc_out(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*service_action*/ action,
+ /*zone_id*/ lba,
+ /*zone_flags*/ (all_zones != 0) ? ZBC_OUT_ALL : 0,
+ /*data_ptr*/ NULL,
+ /*dxfer_len*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 60000);
+ break;
+ case CC_DT_ATA:
+ case CC_DT_ATA_BEHIND_SCSI: {
+ uint8_t command = 0;
+ uint8_t protocol = 0;
+ uint16_t features = 0, sector_count = 0;
+ uint32_t auxiliary = 0;
+
+ /*
+ * Note that we're taking advantage of the fact
+ * that the action numbers are the same between the
+ * ZBC and ZAC specs.
+ */
+
+ if (use_ncq == 0) {
+ protocol = AP_PROTO_NON_DATA;
+ command = ATA_ZAC_MANAGEMENT_OUT;
+ features = action & 0xf;
+ if (all_zones != 0)
+ features |= (ZBC_OUT_ALL << 8);
+ } else {
+ cdb_storage = calloc(cdb_storage_len, 1);
+ if (cdb_storage == NULL)
+ err(1, "couldn't allocate memory");
+
+ protocol = AP_PROTO_FPDMA;
+ command = ATA_NCQ_NON_DATA;
+ features = ATA_NCQ_ZAC_MGMT_OUT;
+ auxiliary = action & 0xf;
+ if (all_zones != 0)
+ auxiliary |= (ZBC_OUT_ALL << 8);
+ }
+
+
+ error = build_ata_cmd(ccb,
+ /*retry_count*/ retry_count,
+ /*flags*/ CAM_DIR_NONE | CAM_DEV_QFRZDIS,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*protocol*/ AP_PROTO_NON_DATA,
+ /*ata_flags*/ AP_FLAG_BYT_BLOK_BYTES |
+ AP_FLAG_TLEN_NO_DATA,
+ /*features*/ features,
+ /*sector_count*/ sector_count,
+ /*lba*/ lba,
+ /*command*/ command,
+ /*auxiliary*/ auxiliary,
+ /*data_ptr*/ NULL,
+ /*dxfer_len*/ 0,
+ /*cdb_storage*/ cdb_storage,
+ /*cdb_storage_len*/ cdb_storage_len,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 60000,
+ /*is48bit*/ 1,
+ /*devtype*/ devtype);
+ if (error != 0) {
+ warnx("%s: build_ata_cmd() failed, likely "
+ "programmer error", __func__);
+ goto bailout;
+ }
+ ata_format = 1;
+ break;
+ }
+ default:
+ warnx("%s: Unknown device type %d", __func__,devtype);
+ error = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+ }
+
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+ if (retry_count > 0)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ error = cam_send_ccb(device, ccb);
+ if (error != 0) {
+ warn("error sending %s %s CCB", (devtype == CC_DT_SCSI) ?
+ "ZBC" : "ZAC Management",
+ (action == ZBC_IN_SA_REPORT_ZONES) ? "In" : "Out");
+ error = -1;
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL,stderr);
+ error = 1;
+ goto bailout;
+ }
+
+ /*
+ * If we aren't reading the list of zones, we're done.
+ */
+ if (action != ZBC_IN_SA_REPORT_ZONES)
+ goto bailout;
+
+ if (ccb->ccb_h.func_code == XPT_SCSI_IO)
+ valid_len = ccb->csio.dxfer_len - ccb->csio.resid;
+ else
+ valid_len = ccb->ataio.dxfer_len - ccb->ataio.resid;
+
+ zp_status = zone_rz_print(data_ptr, valid_len, ata_format, out_flags,
+ first_pass, &lba);
+
+ if (zp_status == ZONE_PRINT_MORE_DATA) {
+ bzero(ccb, sizeof(*ccb));
+ first_pass = 0;
+ goto restart_report;
+ } else if (zp_status == ZONE_PRINT_ERROR)
+ error = 1;
+bailout:
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ free(data_ptr);
+ free(cdb_storage);
+
+ return (error);
+}
Index: sys/cam/ata/ata_all.h
===================================================================
--- sys/cam/ata/ata_all.h
+++ sys/cam/ata/ata_all.h
@@ -125,6 +125,11 @@
void ata_reset_cmd(struct ccb_ataio *ataio);
void ata_pm_read_cmd(struct ccb_ataio *ataio, int reg, int port);
void ata_pm_write_cmd(struct ccb_ataio *ataio, int reg, int port, uint32_t val);
+void ata_read_log(struct ccb_ataio *ataio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint32_t log_address, uint32_t page_number,
+ uint16_t block_count, uint32_t protocol,
+ uint8_t *data_ptr, uint32_t dxfer_len, uint32_t timeout);
void ata_bswap(int8_t *buf, int len);
void ata_btrim(int8_t *buf, int len);
@@ -167,4 +172,16 @@
uint8_t tag_action, uint8_t *data_ptr, uint16_t param_list_length,
uint32_t timeout);
+void ata_zac_mgmt_out(struct ccb_ataio *ataio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ int use_ncq __unused, uint8_t zm_action, uint64_t zone_id,
+ uint8_t zone_flags, uint16_t sector_count, uint8_t *data_ptr,
+ uint32_t dxfer_len, uint32_t timeout);
+
+void ata_zac_mgmt_in(struct ccb_ataio *ataio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ int use_ncq __unused, uint8_t zm_action, uint64_t zone_id,
+ uint8_t zone_flags, uint8_t *data_ptr, uint32_t dxfer_len,
+ uint32_t timeout);
+
#endif
Index: sys/cam/ata/ata_all.c
===================================================================
--- sys/cam/ata/ata_all.c
+++ sys/cam/ata/ata_all.c
@@ -110,6 +110,7 @@
case 0x3f: return ("WRITE_LOG_EXT");
case 0x40: return ("READ_VERIFY");
case 0x42: return ("READ_VERIFY48");
+ case 0x44: return ("ZERO_EXT");
case 0x45:
switch (cmd->features) {
case 0x55: return ("WRITE_UNCORRECTABLE48 PSEUDO");
@@ -116,12 +117,38 @@
case 0xaa: return ("WRITE_UNCORRECTABLE48 FLAGGED");
}
return "WRITE_UNCORRECTABLE48";
+ case 0x47: return ("READ_LOG_DMA_EXT");
+ case 0x4a: return ("ZAC_MANAGEMENT_IN");
case 0x51: return ("CONFIGURE_STREAM");
case 0x60: return ("READ_FPDMA_QUEUED");
case 0x61: return ("WRITE_FPDMA_QUEUED");
- case 0x63: return ("NCQ_NON_DATA");
- case 0x64: return ("SEND_FPDMA_QUEUED");
- case 0x65: return ("RECEIVE_FPDMA_QUEUED");
+ case 0x63:
+ switch (cmd->features & 0xf) {
+ case 0x00: return ("NCQ_NON_DATA ABORT NCQ QUEUE");
+ case 0x01: return ("NCQ_NON_DATA DEADLINE HANDLING");
+ case 0x05: return ("NCQ_NON_DATA SET FEATURES");
+ /*
+ * XXX KDM need common decoding between NCQ and non-NCQ
+ * versions of SET FEATURES.
+ */
+ case 0x06: return ("NCQ_NON_DATA ZERO EXT");
+ case 0x07: return ("NCQ_NON_DATA ZAC MANAGEMENT OUT");
+ }
+ return ("NCQ_NON_DATA");
+ case 0x64:
+ switch (cmd->sector_count_exp & 0xf) {
+ case 0x00: return ("SEND_FPDMA_QUEUED DATA SET MANAGEMENT");
+ case 0x02: return ("SEND_FPDMA_QUEUED WRITE LOG DMA EXT");
+ case 0x03: return ("SEND_FPDMA_QUEUED ZAC MANAGEMENT OUT");
+ case 0x04: return ("SEND_FPDMA_QUEUED DATA SET MANAGEMENT XL");
+ }
+ return ("SEND_FPDMA_QUEUED");
+ case 0x65:
+ switch (cmd->sector_count_exp & 0xf) {
+ case 0x01: return ("RECEIVE_FPDMA_QUEUED READ LOG DMA EXT");
+ case 0x02: return ("RECEIVE_FPDMA_QUEUED ZAC MANAGEMENT IN");
+ }
+ return ("RECEIVE_FPDMA_QUEUED");
case 0x67:
if (cmd->features == 0xec)
return ("SEP_ATTN IDENTIFY");
@@ -136,6 +163,7 @@
case 0x87: return ("CFA_TRANSLATE_SECTOR");
case 0x90: return ("EXECUTE_DEVICE_DIAGNOSTIC");
case 0x92: return ("DOWNLOAD_MICROCODE");
+ case 0x9a: return ("ZAC_MANAGEMENT_OUT");
case 0xa0: return ("PACKET");
case 0xa1: return ("ATAPI_IDENTIFY");
case 0xa2: return ("SERVICE");
@@ -179,23 +207,44 @@
case 0xec: return ("ATA_IDENTIFY");
case 0xed: return ("MEDIA_EJECT");
case 0xef:
+ /*
+ * XXX KDM need common decoding between NCQ and non-NCQ
+ * versions of SET FEATURES.
+ */
switch (cmd->features) {
- case 0x03: return ("SETFEATURES SET TRANSFER MODE");
- case 0x02: return ("SETFEATURES ENABLE WCACHE");
- case 0x82: return ("SETFEATURES DISABLE WCACHE");
- case 0x06: return ("SETFEATURES ENABLE PUIS");
- case 0x86: return ("SETFEATURES DISABLE PUIS");
- case 0x07: return ("SETFEATURES SPIN-UP");
- case 0x10: return ("SETFEATURES ENABLE SATA FEATURE");
- case 0x90: return ("SETFEATURES DISABLE SATA FEATURE");
- case 0xaa: return ("SETFEATURES ENABLE RCACHE");
- case 0x55: return ("SETFEATURES DISABLE RCACHE");
+ case 0x02: return ("SETFEATURES ENABLE WCACHE");
+ case 0x03: return ("SETFEATURES SET TRANSFER MODE");
+ case 0x04: return ("SETFEATURES ENABLE APM");
+ case 0x06: return ("SETFEATURES ENABLE PUIS");
+ case 0x07: return ("SETFEATURES SPIN-UP");
+ case 0x0b: return ("SETFEATURES ENABLE WRITE READ VERIFY");
+ case 0x0c: return ("SETFEATURES ENABLE DEVICE LIFE CONTROL");
+ case 0x10: return ("SETFEATURES ENABLE SATA FEATURE");
+ case 0x41: return ("SETFEATURES ENABLE FREEFALL CONTROL");
+ case 0x43: return ("SETFEATURES SET MAX HOST INT SECT TIMES");
+ case 0x45: return ("SETFEATURES SET RATE BASIS");
+ case 0x4a: return ("SETFEATURES EXTENDED POWER CONDITIONS");
+ case 0x55: return ("SETFEATURES DISABLE RCACHE");
case 0x5d: return ("SETFEATURES ENABLE RELIRQ");
+ case 0x5e: return ("SETFEATURES ENABLE SRVIRQ");
+ case 0x62: return ("SETFEATURES LONG PHYS SECT ALIGN ERC");
+ case 0x63: return ("SETFEATURES DSN");
+ case 0x66: return ("SETFEATURES DISABLE DEFAULTS");
+ case 0x82: return ("SETFEATURES DISABLE WCACHE");
+ case 0x85: return ("SETFEATURES DISABLE APM");
+ case 0x86: return ("SETFEATURES DISABLE PUIS");
+ case 0x8b: return ("SETFEATURES DISABLE WRITE READ VERIFY");
+ case 0x8c: return ("SETFEATURES DISABLE DEVICE LIFE CONTROL");
+ case 0x90: return ("SETFEATURES DISABLE SATA FEATURE");
+ case 0xaa: return ("SETFEATURES ENABLE RCACHE");
+ case 0xC1: return ("SETFEATURES DISABLE FREEFALL CONTROL");
+ case 0xC3: return ("SETFEATURES SENSE DATA REPORTING");
+ case 0xC4: return ("SETFEATURES NCQ SENSE DATA RETURN");
+ case 0xCC: return ("SETFEATURES ENABLE DEFAULTS");
case 0xdd: return ("SETFEATURES DISABLE RELIRQ");
- case 0x5e: return ("SETFEATURES ENABLE SRVIRQ");
case 0xde: return ("SETFEATURES DISABLE SRVIRQ");
- }
- return "SETFEATURES";
+ }
+ return "SETFEATURES";
case 0xf1: return ("SECURITY_SET_PASSWORD");
case 0xf2: return ("SECURITY_UNLOCK");
case 0xf3: return ("SECURITY_ERASE_PREPARE");
@@ -463,7 +512,8 @@
cmd == ATA_WRITE_DMA_QUEUED48 ||
cmd == ATA_WRITE_DMA_QUEUED_FUA48 ||
cmd == ATA_WRITE_STREAM_DMA48 ||
- cmd == ATA_DATA_SET_MANAGEMENT)
+ cmd == ATA_DATA_SET_MANAGEMENT ||
+ cmd == ATA_READ_LOG_DMA_EXT)
ataio->cmd.flags |= CAM_ATAIO_DMA;
ataio->cmd.command = cmd;
ataio->cmd.features = features;
@@ -534,6 +584,36 @@
}
void
+ata_read_log(struct ccb_ataio *ataio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint32_t log_address, uint32_t page_number, uint16_t block_count,
+ uint32_t protocol, uint8_t *data_ptr, uint32_t dxfer_len,
+ uint32_t timeout)
+{
+ uint64_t lba;
+
+ cam_fill_ataio(ataio,
+ /*retries*/ 1,
+ /*cbfcnp*/ cbfcnp,
+ /*flags*/ CAM_DIR_IN,
+ /*tag_action*/ 0,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ dxfer_len,
+ /*timeout*/ timeout);
+
+ lba = (((uint64_t)page_number & 0xff00) << 32) |
+ ((page_number & 0x00ff) << 8) |
+ (log_address & 0xff);
+
+ ata_48bit_cmd(ataio,
+ /*cmd*/ (protocol & CAM_ATAIO_DMA) ? ATA_READ_LOG_DMA_EXT :
+ ATA_READ_LOG_EXT,
+ /*features*/ 0,
+ /*lba*/ lba,
+ /*sector_count*/ block_count);
+}
+
+void
ata_bswap(int8_t *buf, int len)
{
u_int16_t *ptr = (u_int16_t*)(buf + len);
@@ -893,3 +973,148 @@
length > 0 ? data_ptr[0] : 0, 0x80, length / 4);
}
+
+void
+ata_zac_mgmt_out(struct ccb_ataio *ataio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ int use_ncq, uint8_t zm_action, uint64_t zone_id,
+ uint8_t zone_flags, uint16_t sector_count, uint8_t *data_ptr,
+ uint32_t dxfer_len, uint32_t timeout)
+{
+ uint8_t command_out, ata_flags;
+ uint16_t features_out, sectors_out;
+ uint32_t auxiliary;
+
+ if (use_ncq == 0) {
+ command_out = ATA_ZAC_MANAGEMENT_OUT;
+ features_out = (zm_action & 0xf) | (zone_flags << 8);
+ if (dxfer_len == 0) {
+ ata_flags = 0;
+ sectors_out = 0;
+ } else {
+ ata_flags = CAM_ATAIO_DMA;
+ /* XXX KDM use sector count? */
+ sectors_out = ((dxfer_len >> 9) & 0xffff);
+ }
+ auxiliary = 0;
+ } else {
+ if (dxfer_len == 0) {
+ command_out = ATA_NCQ_NON_DATA;
+ features_out = ATA_NCQ_ZAC_MGMT_OUT;
+ sectors_out = 0;
+ } else {
+ command_out = ATA_SEND_FPDMA_QUEUED;
+
+ /* Note that we're defaulting to normal priority */
+ sectors_out = ATA_SFPDMA_ZAC_MGMT_OUT << 8;
+
+ /*
+ * For SEND FPDMA QUEUED, the transfer length is
+ * encoded in the FEATURE register, and 0 means
+ * that 65536 512 byte blocks are to be tranferred.
+ * In practice, it seems unlikely that we'll see
+ * a transfer that large.
+ */
+ if (dxfer_len == (65536 * 512)) {
+ features_out = 0;
+ } else {
+ /*
+ * Yes, the caller can theoretically send a
+ * transfer larger than we can handle.
+ * Anyone using this function needs enough
+ * knowledge to avoid doing that.
+ */
+ features_out = ((dxfer_len >> 9) & 0xffff);
+ }
+ }
+ auxiliary = (zm_action & 0xf) | (zone_flags << 8);
+
+ ata_flags = CAM_ATAIO_FPDMA;
+ }
+
+ cam_fill_ataio(ataio,
+ /*retries*/ retries,
+ /*cbfcnp*/ cbfcnp,
+ /*flags*/ (dxfer_len > 0) ? CAM_DIR_OUT : CAM_DIR_NONE,
+ /*tag_action*/ 0,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ dxfer_len,
+ /*timeout*/ timeout);
+
+ ata_48bit_cmd(ataio,
+ /*cmd*/ command_out,
+ /*features*/ features_out,
+ /*lba*/ zone_id,
+ /*sector_count*/ sectors_out);
+
+ ataio->cmd.flags |= ata_flags;
+ if (auxiliary != 0) {
+ ataio->ata_flags |= ATA_FLAG_AUX;
+ ataio->aux = auxiliary;
+ }
+}
+
+void
+ata_zac_mgmt_in(struct ccb_ataio *ataio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ int use_ncq, uint8_t zm_action, uint64_t zone_id,
+ uint8_t zone_flags, uint8_t *data_ptr, uint32_t dxfer_len,
+ uint32_t timeout)
+{
+ uint8_t command_out, ata_flags;
+ uint16_t features_out, sectors_out;
+ uint32_t auxiliary;
+
+ if (use_ncq == 0) {
+ command_out = ATA_ZAC_MANAGEMENT_IN;
+ /* XXX KDM put a macro here */
+ features_out = (zm_action & 0xf) | (zone_flags << 8);
+ ata_flags = CAM_ATAIO_DMA;
+ sectors_out = ((dxfer_len >> 9) & 0xffff);
+ auxiliary = 0;
+ } else {
+ command_out = ATA_RECV_FPDMA_QUEUED;
+ sectors_out = ATA_RFPDMA_ZAC_MGMT_IN << 8;
+ auxiliary = (zm_action & 0xf) | (zone_flags << 8),
+ ata_flags = CAM_ATAIO_FPDMA;
+ /*
+ * For RECEIVE FPDMA QUEUED, the transfer length is
+ * encoded in the FEATURE register, and 0 means
+ * that 65536 512 byte blocks are to be tranferred.
+ * In practice, it is unlikely we will see a transfer that
+ * large.
+ */
+ if (dxfer_len == (65536 * 512)) {
+ features_out = 0;
+ } else {
+ /*
+ * Yes, the caller can theoretically request a
+ * transfer larger than we can handle.
+ * Anyone using this function needs enough
+ * knowledge to avoid doing that.
+ */
+ features_out = ((dxfer_len >> 9) & 0xffff);
+ }
+ }
+
+ cam_fill_ataio(ataio,
+ /*retries*/ retries,
+ /*cbfcnp*/ cbfcnp,
+ /*flags*/ CAM_DIR_IN,
+ /*tag_action*/ 0,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ dxfer_len,
+ /*timeout*/ timeout);
+
+ ata_48bit_cmd(ataio,
+ /*cmd*/ command_out,
+ /*features*/ features_out,
+ /*lba*/ zone_id,
+ /*sector_count*/ sectors_out);
+
+ ataio->cmd.flags |= ata_flags;
+ if (auxiliary != 0) {
+ ataio->ata_flags |= ATA_FLAG_AUX;
+ ataio->aux = auxiliary;
+ }
+}
Index: sys/cam/ata/ata_da.c
===================================================================
--- sys/cam/ata/ata_da.c
+++ sys/cam/ata/ata_da.c
@@ -43,9 +43,11 @@
#include <sys/devicestat.h>
#include <sys/eventhandler.h>
#include <sys/malloc.h>
+#include <sys/endian.h>
#include <sys/cons.h>
#include <sys/proc.h>
#include <sys/reboot.h>
+#include <sys/sbuf.h>
#include <geom/geom_disk.h>
#endif /* _KERNEL */
@@ -58,6 +60,8 @@
#include <cam/cam_ccb.h>
#include <cam/cam_periph.h>
#include <cam/cam_xpt_periph.h>
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_da.h>
#include <cam/cam_sim.h>
#include <cam/cam_iosched.h>
@@ -74,25 +78,37 @@
typedef enum {
ADA_STATE_RAHEAD,
ADA_STATE_WCACHE,
+ ADA_STATE_LOGDIR,
+ ADA_STATE_IDDIR,
+ ADA_STATE_SUP_CAP,
+ ADA_STATE_ZONE,
ADA_STATE_NORMAL
} ada_state;
typedef enum {
- ADA_FLAG_CAN_48BIT = 0x0002,
- ADA_FLAG_CAN_FLUSHCACHE = 0x0004,
- ADA_FLAG_CAN_NCQ = 0x0008,
- ADA_FLAG_CAN_DMA = 0x0010,
- ADA_FLAG_NEED_OTAG = 0x0020,
- ADA_FLAG_WAS_OTAG = 0x0040,
- ADA_FLAG_CAN_TRIM = 0x0080,
- ADA_FLAG_OPEN = 0x0100,
- ADA_FLAG_SCTX_INIT = 0x0200,
- ADA_FLAG_CAN_CFA = 0x0400,
- ADA_FLAG_CAN_POWERMGT = 0x0800,
- ADA_FLAG_CAN_DMA48 = 0x1000,
- ADA_FLAG_DIRTY = 0x2000,
- ADA_FLAG_CAN_NCQ_TRIM = 0x4000, /* CAN_TRIM also set */
- ADA_FLAG_PIM_CAN_NCQ_TRIM = 0x8000
+ ADA_FLAG_CAN_48BIT = 0x00000002,
+ ADA_FLAG_CAN_FLUSHCACHE = 0x00000004,
+ ADA_FLAG_CAN_NCQ = 0x00000008,
+ ADA_FLAG_CAN_DMA = 0x00000010,
+ ADA_FLAG_NEED_OTAG = 0x00000020,
+ ADA_FLAG_WAS_OTAG = 0x00000040,
+ ADA_FLAG_CAN_TRIM = 0x00000080,
+ ADA_FLAG_OPEN = 0x00000100,
+ ADA_FLAG_SCTX_INIT = 0x00000200,
+ ADA_FLAG_CAN_CFA = 0x00000400,
+ ADA_FLAG_CAN_POWERMGT = 0x00000800,
+ ADA_FLAG_CAN_DMA48 = 0x00001000,
+ ADA_FLAG_CAN_LOG = 0x00002000,
+ ADA_FLAG_CAN_IDLOG = 0x00004000,
+ ADA_FLAG_CAN_SUPCAP = 0x00008000,
+ ADA_FLAG_CAN_ZONE = 0x00010000,
+ ADA_FLAG_CAN_WCACHE = 0x00020000,
+ ADA_FLAG_CAN_RAHEAD = 0x00040000,
+ ADA_FLAG_PROBED = 0x00080000,
+ ADA_FLAG_ANNOUNCED = 0x00100000,
+ ADA_FLAG_DIRTY = 0x00200000,
+ ADA_FLAG_CAN_NCQ_TRIM = 0x00400000, /* CAN_TRIM also set */
+ ADA_FLAG_PIM_ATA_EXT = 0x00800000
} ada_flags;
typedef enum {
@@ -112,9 +128,52 @@
ADA_CCB_BUFFER_IO = 0x03,
ADA_CCB_DUMP = 0x05,
ADA_CCB_TRIM = 0x06,
+ ADA_CCB_LOGDIR = 0x07,
+ ADA_CCB_IDDIR = 0x08,
+ ADA_CCB_SUP_CAP = 0x09,
+ ADA_CCB_ZONE = 0x0a,
ADA_CCB_TYPE_MASK = 0x0F,
} ada_ccb_state;
+typedef enum {
+ ADA_ZONE_NONE = 0x00,
+ ADA_ZONE_DRIVE_MANAGED = 0x01,
+ ADA_ZONE_HOST_AWARE = 0x02,
+ ADA_ZONE_HOST_MANAGED = 0x03
+} ada_zone_mode;
+
+typedef enum {
+ ADA_ZONE_FLAG_RZ_SUP = 0x0001,
+ ADA_ZONE_FLAG_OPEN_SUP = 0x0002,
+ ADA_ZONE_FLAG_CLOSE_SUP = 0x0004,
+ ADA_ZONE_FLAG_FINISH_SUP = 0x0008,
+ ADA_ZONE_FLAG_RWP_SUP = 0x0010,
+ ADA_ZONE_FLAG_SUP_MASK = (ADA_ZONE_FLAG_RZ_SUP |
+ ADA_ZONE_FLAG_OPEN_SUP |
+ ADA_ZONE_FLAG_CLOSE_SUP |
+ ADA_ZONE_FLAG_FINISH_SUP |
+ ADA_ZONE_FLAG_RWP_SUP),
+ ADA_ZONE_FLAG_URSWRZ = 0x0020,
+ ADA_ZONE_FLAG_OPT_SEQ_SET = 0x0040,
+ ADA_ZONE_FLAG_OPT_NONSEQ_SET = 0x0080,
+ ADA_ZONE_FLAG_MAX_SEQ_SET = 0x0100,
+ ADA_ZONE_FLAG_SET_MASK = (ADA_ZONE_FLAG_OPT_SEQ_SET |
+ ADA_ZONE_FLAG_OPT_NONSEQ_SET |
+ ADA_ZONE_FLAG_MAX_SEQ_SET)
+} ada_zone_flags;
+
+static struct ada_zone_desc {
+ ada_zone_flags value;
+ const char *desc;
+} ada_zone_desc_table[] = {
+ {ADA_ZONE_FLAG_RZ_SUP, "Report Zones" },
+ {ADA_ZONE_FLAG_OPEN_SUP, "Open" },
+ {ADA_ZONE_FLAG_CLOSE_SUP, "Close" },
+ {ADA_ZONE_FLAG_FINISH_SUP, "Finish" },
+ {ADA_ZONE_FLAG_RWP_SUP, "Reset Write Pointer" },
+};
+
+
/* Offsets into our private area for storing information */
#define ccb_state ppriv_field0
#define ccb_bp ppriv_ptr1
@@ -157,6 +216,15 @@
int refcount; /* Active xpt_action() calls */
ada_state state;
ada_flags flags;
+ ada_zone_mode zone_mode;
+ ada_zone_flags zone_flags;
+ struct ata_gp_log_dir ata_logdir;
+ int valid_logdir_len;
+ struct ata_identify_log_pages ata_iddir;
+ int valid_iddir_len;
+ uint64_t optimal_seq_zones;
+ uint64_t optimal_nonseq_zones;
+ uint64_t max_seq_zones;
ada_quirks quirks;
ada_delete_methods delete_method;
int trim_max_ranges;
@@ -624,13 +692,33 @@
static disk_strategy_t adastrategy;
static dumper_t adadump;
static periph_init_t adainit;
+static void adadiskgonecb(struct disk *dp);
+static periph_oninv_t adaoninvalidate;
+static periph_dtor_t adacleanup;
+#if 0
+/* See comment above function, this may be used later */
+static void adareprobe(struct cam_periph *periph,
+ struct ccb_getdev *cgd);
+#endif
static void adaasync(void *callback_arg, u_int32_t code,
struct cam_path *path, void *arg);
+static int adazonemodesysctl(SYSCTL_HANDLER_ARGS);
+static int adazonesupsysctl(SYSCTL_HANDLER_ARGS);
static void adasysctlinit(void *context, int pending);
+static int adagetattr(struct bio *bp);
+static void adasetflags(struct ada_softc *softc,
+ struct ccb_getdev *cgd);
static periph_ctor_t adaregister;
-static periph_dtor_t adacleanup;
+static void ada_dsmtrim(struct ada_softc *softc, struct bio *bp,
+ struct ccb_ataio *ataio);
+static void ada_cfaerase(struct ada_softc *softc, struct bio *bp,
+ struct ccb_ataio *ataio);
+static int ada_zone_bio_to_ata(int disk_zone_cmd);
+static int ada_zone_cmd(struct cam_periph *periph, union ccb *ccb,
+ struct bio *bp, int *queue_ccb);
static periph_start_t adastart;
-static periph_oninv_t adaoninvalidate;
+static void adaprobedone(struct cam_periph *periph, union ccb *ccb);
+static void adazonedone(struct cam_periph *periph, union ccb *ccb);
static void adadone(struct cam_periph *periph,
union ccb *done_ccb);
static int adaerror(union ccb *ccb, u_int32_t cam_flags,
@@ -738,6 +826,8 @@
PERIPHDRIVER_DECLARE(ada, adadriver);
+static MALLOC_DEFINE(M_ATADA, "ata_da", "ata_da buffers");
+
static int
adaopen(struct disk *dp)
{
@@ -860,6 +950,14 @@
biofinish(bp, NULL, ENXIO);
return;
}
+
+ /*
+ * Zone commands must be ordered, because they can depend on the
+ * effects of previously issued commands, and they may affect
+ * commands after them.
+ */
+ if (bp->bio_cmd == BIO_ZONE)
+ bp->bio_flags |= BIO_ORDERED;
/*
* Place it in the queue of disk activities for this disk
@@ -1129,46 +1227,11 @@
cgd.ccb_h.func_code = XPT_GDEV_TYPE;
xpt_action((union ccb *)&cgd);
- if ((cgd.ident_data.capabilities1 & ATA_SUPPORT_DMA) &&
- (cgd.inq_flags & SID_DMA))
- softc->flags |= ADA_FLAG_CAN_DMA;
- else
- softc->flags &= ~ADA_FLAG_CAN_DMA;
- if (cgd.ident_data.support.command2 & ATA_SUPPORT_ADDRESS48) {
- softc->flags |= ADA_FLAG_CAN_48BIT;
- if (cgd.inq_flags & SID_DMA48)
- softc->flags |= ADA_FLAG_CAN_DMA48;
- else
- softc->flags &= ~ADA_FLAG_CAN_DMA48;
- } else
- softc->flags &= ~(ADA_FLAG_CAN_48BIT |
- ADA_FLAG_CAN_DMA48);
- if ((cgd.ident_data.satacapabilities & ATA_SUPPORT_NCQ) &&
- (cgd.inq_flags & SID_DMA) && (cgd.inq_flags & SID_CmdQue))
- softc->flags |= ADA_FLAG_CAN_NCQ;
- else
- softc->flags &= ~ADA_FLAG_CAN_NCQ;
+ /*
+ * Set/clear support flags based on the new Identify data.
+ */
+ adasetflags(softc, &cgd);
- if ((cgd.ident_data.support_dsm & ATA_SUPPORT_DSM_TRIM) &&
- (cgd.inq_flags & SID_DMA)) {
- softc->flags |= ADA_FLAG_CAN_TRIM;
- /*
- * If we can do RCVSND_FPDMA_QUEUED commands, we may be able to do
- * NCQ trims, if we support trims at all. We also need support from
- * the sim do do things properly. Perhaps we should look at log 13
- * dword 0 bit 0 and dword 1 bit 0 are set too...
- */
- if ((softc->quirks & ADA_Q_NCQ_TRIM_BROKEN) == 0 &&
- (softc->flags & ADA_FLAG_PIM_CAN_NCQ_TRIM) != 0 &&
- (cgd.ident_data.satacapabilities2 & ATA_SUPPORT_RCVSND_FPDMA_QUEUED) != 0 &&
- (softc->flags & ADA_FLAG_CAN_TRIM) != 0)
- softc->flags |= ADA_FLAG_CAN_NCQ_TRIM;
- else
- softc->flags &= ~ADA_FLAG_CAN_NCQ_TRIM;
- } else
- softc->flags &= ~(ADA_FLAG_CAN_TRIM | ADA_FLAG_CAN_NCQ_TRIM);
- adasetdeletemethod(softc);
-
cam_periph_async(periph, code, path, arg);
break;
}
@@ -1196,12 +1259,12 @@
xpt_setup_ccb(&cgd.ccb_h, periph->path, CAM_PRIORITY_NORMAL);
cgd.ccb_h.func_code = XPT_GDEV_TYPE;
xpt_action((union ccb *)&cgd);
- if (ADA_RA >= 0 &&
- cgd.ident_data.support.command1 & ATA_SUPPORT_LOOKAHEAD)
+ if (ADA_RA >= 0 && softc->flags & ADA_FLAG_CAN_RAHEAD)
softc->state = ADA_STATE_RAHEAD;
- else if (ADA_WC >= 0 &&
- cgd.ident_data.support.command1 & ATA_SUPPORT_WRITECACHE)
+ else if (ADA_WC >= 0 && softc->flags & ADA_FLAG_CAN_RAHEAD)
softc->state = ADA_STATE_WCACHE;
+ else if (softc->flags & ADA_FLAG_CAN_LOG)
+ softc->state = ADA_STATE_LOGDIR;
else
break;
if (cam_periph_acquire(periph) != CAM_REQ_CMP)
@@ -1215,6 +1278,73 @@
}
}
+static int
+adazonemodesysctl(SYSCTL_HANDLER_ARGS)
+{
+ char tmpbuf[40];
+ struct ada_softc *softc;
+ int error;
+
+ softc = (struct ada_softc *)arg1;
+
+ switch (softc->zone_mode) {
+ case ADA_ZONE_DRIVE_MANAGED:
+ snprintf(tmpbuf, sizeof(tmpbuf), "Drive Managed");
+ break;
+ case ADA_ZONE_HOST_AWARE:
+ snprintf(tmpbuf, sizeof(tmpbuf), "Host Aware");
+ break;
+ case ADA_ZONE_HOST_MANAGED:
+ snprintf(tmpbuf, sizeof(tmpbuf), "Host Managed");
+ break;
+ case ADA_ZONE_NONE:
+ default:
+ snprintf(tmpbuf, sizeof(tmpbuf), "Not Zoned");
+ break;
+ }
+
+ error = sysctl_handle_string(oidp, tmpbuf, sizeof(tmpbuf), req);
+
+ return (error);
+}
+
+static int
+adazonesupsysctl(SYSCTL_HANDLER_ARGS)
+{
+ char tmpbuf[180];
+ struct ada_softc *softc;
+ struct sbuf sb;
+ int error, first;
+ unsigned int i;
+
+ softc = (struct ada_softc *)arg1;
+
+ error = 0;
+ first = 1;
+ sbuf_new(&sb, tmpbuf, sizeof(tmpbuf), 0);
+
+ for (i = 0; i < sizeof(ada_zone_desc_table) /
+ sizeof(ada_zone_desc_table[0]); i++) {
+ if (softc->zone_flags & ada_zone_desc_table[i].value) {
+ if (first == 0)
+ sbuf_printf(&sb, ", ");
+ else
+ first = 0;
+ sbuf_cat(&sb, ada_zone_desc_table[i].desc);
+ }
+ }
+
+ if (first == 1)
+ sbuf_printf(&sb, "None");
+
+ sbuf_finish(&sb);
+
+ error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
+
+ return (error);
+}
+
+
static void
adasysctlinit(void *context, int pending)
{
@@ -1231,7 +1361,7 @@
}
softc = (struct ada_softc *)periph->softc;
- snprintf(tmpstr, sizeof(tmpstr), "CAM ADA unit %d", periph->unit_number);
+ snprintf(tmpstr, sizeof(tmpstr), "CAM ADA unit %d",periph->unit_number);
snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number);
sysctl_ctx_init(&softc->sysctl_ctx);
@@ -1261,6 +1391,29 @@
SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree),
OID_AUTO, "rotating", CTLFLAG_RD | CTLFLAG_MPSAFE,
&softc->rotating, 0, "Rotating media");
+ SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree),
+ OID_AUTO, "zone_mode", CTLTYPE_STRING | CTLFLAG_RD,
+ softc, 0, adazonemodesysctl, "A",
+ "Zone Mode");
+ SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree),
+ OID_AUTO, "zone_support", CTLTYPE_STRING | CTLFLAG_RD,
+ softc, 0, adazonesupsysctl, "A",
+ "Zone Support");
+ SYSCTL_ADD_UQUAD(&softc->sysctl_ctx,
+ SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO,
+ "optimal_seq_zones", CTLFLAG_RD, &softc->optimal_seq_zones,
+ "Optimal Number of Open Sequential Write Preferred Zones");
+ SYSCTL_ADD_UQUAD(&softc->sysctl_ctx,
+ SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO,
+ "optimal_nonseq_zones", CTLFLAG_RD,
+ &softc->optimal_nonseq_zones,
+ "Optimal Number of Non-Sequentially Written Sequential Write "
+ "Preferred Zones");
+ SYSCTL_ADD_UQUAD(&softc->sysctl_ctx,
+ SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO,
+ "max_seq_zones", CTLFLAG_RD, &softc->max_seq_zones,
+ "Maximum Number of Open Sequential Write Required Zones");
+
#ifdef ADA_TEST_FAILURE
/*
* Add a 'door bell' sysctl which allows one to set it from userland
@@ -1361,6 +1514,103 @@
return (EINVAL);
}
+static void
+adasetflags(struct ada_softc *softc, struct ccb_getdev *cgd)
+{
+ if ((cgd->ident_data.capabilities1 & ATA_SUPPORT_DMA) &&
+ (cgd->inq_flags & SID_DMA))
+ softc->flags |= ADA_FLAG_CAN_DMA;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_DMA;
+
+ if (cgd->ident_data.support.command2 & ATA_SUPPORT_ADDRESS48) {
+ softc->flags |= ADA_FLAG_CAN_48BIT;
+ if (cgd->inq_flags & SID_DMA48)
+ softc->flags |= ADA_FLAG_CAN_DMA48;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_DMA48;
+ } else
+ softc->flags &= ~(ADA_FLAG_CAN_48BIT | ADA_FLAG_CAN_DMA48);
+
+ if (cgd->ident_data.support.command2 & ATA_SUPPORT_FLUSHCACHE)
+ softc->flags |= ADA_FLAG_CAN_FLUSHCACHE;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_FLUSHCACHE;
+
+ if (cgd->ident_data.support.command1 & ATA_SUPPORT_POWERMGT)
+ softc->flags |= ADA_FLAG_CAN_POWERMGT;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_POWERMGT;
+
+ if ((cgd->ident_data.satacapabilities & ATA_SUPPORT_NCQ) &&
+ (cgd->inq_flags & SID_DMA) && (cgd->inq_flags & SID_CmdQue))
+ softc->flags |= ADA_FLAG_CAN_NCQ;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_NCQ;
+
+ if ((cgd->ident_data.support_dsm & ATA_SUPPORT_DSM_TRIM) &&
+ (cgd->inq_flags & SID_DMA)) {
+ softc->flags |= ADA_FLAG_CAN_TRIM;
+ softc->trim_max_ranges = TRIM_MAX_RANGES;
+ if (cgd->ident_data.max_dsm_blocks != 0) {
+ softc->trim_max_ranges =
+ min(cgd->ident_data.max_dsm_blocks *
+ ATA_DSM_BLK_RANGES, softc->trim_max_ranges);
+ }
+ /*
+ * If we can do RCVSND_FPDMA_QUEUED commands, we may be able
+ * to do NCQ trims, if we support trims at all. We also need
+ * support from the sim do do things properly. Perhaps we
+ * should look at log 13 dword 0 bit 0 and dword 1 bit 0 are
+ * set too...
+ */
+ if ((softc->quirks & ADA_Q_NCQ_TRIM_BROKEN) == 0 &&
+ (softc->flags & ADA_FLAG_PIM_ATA_EXT) != 0 &&
+ (cgd->ident_data.satacapabilities2 &
+ ATA_SUPPORT_RCVSND_FPDMA_QUEUED) != 0 &&
+ (softc->flags & ADA_FLAG_CAN_TRIM) != 0)
+ softc->flags |= ADA_FLAG_CAN_NCQ_TRIM;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_NCQ_TRIM;
+ } else
+ softc->flags &= ~(ADA_FLAG_CAN_TRIM | ADA_FLAG_CAN_NCQ_TRIM);
+
+ if (cgd->ident_data.support.command2 & ATA_SUPPORT_CFA)
+ softc->flags |= ADA_FLAG_CAN_CFA;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_CFA;
+
+ /*
+ * Now that we've set the appropriate flags, setup the delete
+ * method.
+ */
+ adasetdeletemethod(softc);
+
+ if (cgd->ident_data.support.extension & ATA_SUPPORT_GENLOG)
+ softc->flags |= ADA_FLAG_CAN_LOG;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_LOG;
+
+ if ((cgd->ident_data.support3 & ATA_SUPPORT_ZONE_MASK) ==
+ ATA_SUPPORT_ZONE_HOST_AWARE)
+ softc->zone_mode = ADA_ZONE_HOST_AWARE;
+ else if ((cgd->ident_data.support3 & ATA_SUPPORT_ZONE_MASK) ==
+ ATA_SUPPORT_ZONE_DEV_MANAGED)
+ softc->zone_mode = ADA_ZONE_DRIVE_MANAGED;
+ else
+ softc->zone_mode = ADA_ZONE_NONE;
+
+ if (cgd->ident_data.support.command1 & ATA_SUPPORT_LOOKAHEAD)
+ softc->flags |= ADA_FLAG_CAN_RAHEAD;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_RAHEAD;
+
+ if (cgd->ident_data.support.command1 & ATA_SUPPORT_WRITECACHE)
+ softc->flags |= ADA_FLAG_CAN_WCACHE;
+ else
+ softc->flags &= ~ADA_FLAG_CAN_WCACHE;
+}
+
static cam_status
adaregister(struct cam_periph *periph, void *arg)
{
@@ -1394,36 +1644,11 @@
return(CAM_REQ_CMP_ERR);
}
- if ((cgd->ident_data.capabilities1 & ATA_SUPPORT_DMA) &&
- (cgd->inq_flags & SID_DMA))
- softc->flags |= ADA_FLAG_CAN_DMA;
- if (cgd->ident_data.support.command2 & ATA_SUPPORT_ADDRESS48) {
- softc->flags |= ADA_FLAG_CAN_48BIT;
- if (cgd->inq_flags & SID_DMA48)
- softc->flags |= ADA_FLAG_CAN_DMA48;
- }
- if (cgd->ident_data.support.command2 & ATA_SUPPORT_FLUSHCACHE)
- softc->flags |= ADA_FLAG_CAN_FLUSHCACHE;
- if (cgd->ident_data.support.command1 & ATA_SUPPORT_POWERMGT)
- softc->flags |= ADA_FLAG_CAN_POWERMGT;
- if ((cgd->ident_data.satacapabilities & ATA_SUPPORT_NCQ) &&
- (cgd->inq_flags & SID_DMA) && (cgd->inq_flags & SID_CmdQue))
- softc->flags |= ADA_FLAG_CAN_NCQ;
- if ((cgd->ident_data.support_dsm & ATA_SUPPORT_DSM_TRIM) &&
- (cgd->inq_flags & SID_DMA)) {
- softc->flags |= ADA_FLAG_CAN_TRIM;
- softc->trim_max_ranges = TRIM_MAX_RANGES;
- if (cgd->ident_data.max_dsm_blocks != 0) {
- softc->trim_max_ranges =
- min(cgd->ident_data.max_dsm_blocks *
- ATA_DSM_BLK_RANGES, softc->trim_max_ranges);
- }
- }
- if (cgd->ident_data.support.command2 & ATA_SUPPORT_CFA)
- softc->flags |= ADA_FLAG_CAN_CFA;
+ /*
+ * Set support flags based on the Identify data.
+ */
+ adasetflags(softc, cgd);
- adasetdeletemethod(softc);
-
periph->softc = softc;
/*
@@ -1498,7 +1723,7 @@
maxio = min(maxio, 256 * softc->params.secsize);
softc->disk->d_maxsize = maxio;
softc->disk->d_unit = periph->unit_number;
- softc->disk->d_flags = DISKFLAG_DIRECT_COMPLETION;
+ softc->disk->d_flags = DISKFLAG_DIRECT_COMPLETION | DISKFLAG_CANZONE;
if (softc->flags & ADA_FLAG_CAN_FLUSHCACHE)
softc->disk->d_flags |= DISKFLAG_CANFLUSHCACHE;
if (softc->flags & ADA_FLAG_CAN_TRIM) {
@@ -1516,19 +1741,6 @@
softc->disk->d_flags |= DISKFLAG_UNMAPPED_BIO;
softc->unmappedio = 1;
}
- /*
- * If we can do RCVSND_FPDMA_QUEUED commands, we may be able to do
- * NCQ trims, if we support trims at all. We also need support from
- * the sim do do things properly. Perhaps we should look at log 13
- * dword 0 bit 0 and dword 1 bit 0 are set too...
- */
- if (cpi.hba_misc & PIM_ATA_EXT)
- softc->flags |= ADA_FLAG_PIM_CAN_NCQ_TRIM;
- if ((softc->quirks & ADA_Q_NCQ_TRIM_BROKEN) == 0 &&
- (softc->flags & ADA_FLAG_PIM_CAN_NCQ_TRIM) != 0 &&
- (cgd->ident_data.satacapabilities2 & ATA_SUPPORT_RCVSND_FPDMA_QUEUED) != 0 &&
- (softc->flags & ADA_FLAG_CAN_TRIM) != 0)
- softc->flags |= ADA_FLAG_CAN_NCQ_TRIM;
strlcpy(softc->disk->d_descr, cgd->ident_data.model,
MIN(sizeof(softc->disk->d_descr), sizeof(cgd->ident_data.model)));
strlcpy(softc->disk->d_ident, cgd->ident_data.serial,
@@ -1555,7 +1767,6 @@
softc->disk->d_fwsectors = softc->params.secs_per_track;
softc->disk->d_fwheads = softc->params.heads;
ata_disk_firmware_geom_adjust(softc->disk);
- adasetdeletemethod(softc);
/*
* Acquire a reference to the periph before we register with GEOM.
@@ -1570,7 +1781,6 @@
}
disk_create(softc->disk, DISK_VERSION);
cam_periph_lock(periph);
- cam_periph_unhold(periph);
dp = &softc->params;
snprintf(announce_buf, sizeof(announce_buf),
@@ -1608,20 +1818,23 @@
(ada_default_timeout * hz) / ADA_ORDEREDTAG_INTERVAL,
adasendorderedtag, softc);
- if (ADA_RA >= 0 &&
- cgd->ident_data.support.command1 & ATA_SUPPORT_LOOKAHEAD) {
+ if (ADA_RA >= 0 && softc->flags & ADA_FLAG_CAN_RAHEAD) {
softc->state = ADA_STATE_RAHEAD;
- } else if (ADA_WC >= 0 &&
- cgd->ident_data.support.command1 & ATA_SUPPORT_WRITECACHE) {
+ } else if (ADA_WC >= 0 && softc->flags & ADA_FLAG_CAN_WCACHE) {
softc->state = ADA_STATE_WCACHE;
+ } else if (softc->flags & ADA_FLAG_CAN_LOG) {
+ softc->state = ADA_STATE_LOGDIR;
} else {
- softc->state = ADA_STATE_NORMAL;
+ /*
+ * Nothing to probe, so we can just transition to the
+ * normal state.
+ */
+ adaprobedone(periph, NULL);
return(CAM_REQ_CMP);
}
- if (cam_periph_acquire(periph) != CAM_REQ_CMP)
- softc->state = ADA_STATE_NORMAL;
- else
- xpt_schedule(periph, CAM_PRIORITY_DEV);
+
+ xpt_schedule(periph, CAM_PRIORITY_DEV);
+
return(CAM_REQ_CMP);
}
@@ -1754,6 +1967,209 @@
ata_28bit_cmd(ataio, ATA_CFA_ERASE, 0, lba, count);
}
+static int
+ada_zone_bio_to_ata(int disk_zone_cmd)
+{
+ switch (disk_zone_cmd) {
+ case DISK_ZONE_OPEN:
+ return ATA_ZM_OPEN_ZONE;
+ case DISK_ZONE_CLOSE:
+ return ATA_ZM_CLOSE_ZONE;
+ case DISK_ZONE_FINISH:
+ return ATA_ZM_FINISH_ZONE;
+ case DISK_ZONE_RWP:
+ return ATA_ZM_RWP;
+ }
+
+ return -1;
+}
+
+static int
+ada_zone_cmd(struct cam_periph *periph, union ccb *ccb, struct bio *bp,
+ int *queue_ccb)
+{
+ struct ada_softc *softc;
+ int error;
+
+ error = 0;
+
+ if (bp->bio_cmd != BIO_ZONE) {
+ error = EINVAL;
+ goto bailout;
+ }
+
+ softc = periph->softc;
+
+ switch (bp->bio_zone.zone_cmd) {
+ case DISK_ZONE_OPEN:
+ case DISK_ZONE_CLOSE:
+ case DISK_ZONE_FINISH:
+ case DISK_ZONE_RWP: {
+ int zone_flags;
+ int zone_sa;
+ uint64_t lba;
+
+ zone_sa = ada_zone_bio_to_ata(bp->bio_zone.zone_cmd);
+ if (zone_sa == -1) {
+ xpt_print(periph->path, "Cannot translate zone "
+ "cmd %#x to ATA\n", bp->bio_zone.zone_cmd);
+ error = EINVAL;
+ goto bailout;
+ }
+
+ zone_flags = 0;
+ lba = bp->bio_zone.zone_params.rwp.id;
+
+ if (bp->bio_zone.zone_params.rwp.flags &
+ DISK_ZONE_RWP_FLAG_ALL)
+ zone_flags |= ZBC_OUT_ALL;
+
+ ata_zac_mgmt_out(&ccb->ataio,
+ /*retries*/ ada_retry_count,
+ /*cbfcnp*/ adadone,
+ /*use_ncq*/ (softc->flags &
+ ADA_FLAG_PIM_ATA_EXT) ? 1 : 0,
+ /*zm_action*/ zone_sa,
+ /*zone_id*/ lba,
+ /*zone_flags*/ zone_flags,
+ /*sector_count*/ 0,
+ /*data_ptr*/ NULL,
+ /*dxfer_len*/ 0,
+ /*timeout*/ ada_default_timeout * 1000);
+ *queue_ccb = 1;
+
+ break;
+ }
+ case DISK_ZONE_REPORT_ZONES: {
+ uint8_t *rz_ptr;
+ uint32_t num_entries, alloc_size;
+ struct disk_zone_report *rep;
+
+ rep = &bp->bio_zone.zone_params.report;
+
+ num_entries = rep->entries_allocated;
+ if (num_entries == 0) {
+ xpt_print(periph->path, "No entries allocated for "
+ "Report Zones request\n");
+ error = EINVAL;
+ goto bailout;
+ }
+ alloc_size = sizeof(struct scsi_report_zones_hdr) +
+ (sizeof(struct scsi_report_zones_desc) * num_entries);
+ alloc_size = min(alloc_size, softc->disk->d_maxsize);
+ rz_ptr = malloc(alloc_size, M_ATADA, M_NOWAIT | M_ZERO);
+ if (rz_ptr == NULL) {
+ xpt_print(periph->path, "Unable to allocate memory "
+ "for Report Zones request\n");
+ error = ENOMEM;
+ goto bailout;
+ }
+
+ ata_zac_mgmt_in(&ccb->ataio,
+ /*retries*/ ada_retry_count,
+ /*cbcfnp*/ adadone,
+ /*use_ncq*/ (softc->flags &
+ ADA_FLAG_PIM_ATA_EXT) ? 1 : 0,
+ /*zm_action*/ ATA_ZM_REPORT_ZONES,
+ /*zone_id*/ rep->starting_id,
+ /*zone_flags*/ rep->rep_options,
+ /*data_ptr*/ rz_ptr,
+ /*dxfer_len*/ alloc_size,
+ /*timeout*/ ada_default_timeout * 1000);
+
+ /*
+ * For BIO_ZONE, this isn't normally needed. However, it
+ * is used by devstat_end_transaction_bio() to determine
+ * how much data was transferred.
+ */
+ /*
+ * XXX KDM we have a problem. But I'm not sure how to fix
+ * it. devstat uses bio_bcount - bio_resid to calculate
+ * the amount of data transferred. The GEOM disk code
+ * uses bio_length - bio_resid to calculate the amount of
+ * data in bio_completed. We have different structure
+ * sizes above and below the ada(4) driver. So, if we
+ * use the sizes above, the amount transferred won't be
+ * quite accurate for devstat. If we use different sizes
+ * for bio_bcount and bio_length (above and below
+ * respectively), then the residual needs to match one or
+ * the other. Everything is calculated after the bio
+ * leaves the driver, so changing the values around isn't
+ * really an option. For now, just set the count to the
+ * passed in length. This means that the calculations
+ * above (e.g. bio_completed) will be correct, but the
+ * amount of data reported to devstat will be slightly
+ * under or overstated.
+ */
+ bp->bio_bcount = bp->bio_length;
+
+ *queue_ccb = 1;
+
+ break;
+ }
+ case DISK_ZONE_GET_PARAMS: {
+ struct disk_zone_disk_params *params;
+
+ params = &bp->bio_zone.zone_params.disk_params;
+ bzero(params, sizeof(*params));
+
+ switch (softc->zone_mode) {
+ case ADA_ZONE_DRIVE_MANAGED:
+ params->zone_mode = DISK_ZONE_MODE_DRIVE_MANAGED;
+ break;
+ case ADA_ZONE_HOST_AWARE:
+ params->zone_mode = DISK_ZONE_MODE_HOST_AWARE;
+ break;
+ case ADA_ZONE_HOST_MANAGED:
+ params->zone_mode = DISK_ZONE_MODE_HOST_MANAGED;
+ break;
+ default:
+ case ADA_ZONE_NONE:
+ params->zone_mode = DISK_ZONE_MODE_NONE;
+ break;
+ }
+
+ if (softc->zone_flags & ADA_ZONE_FLAG_URSWRZ)
+ params->flags |= DISK_ZONE_DISK_URSWRZ;
+
+ if (softc->zone_flags & ADA_ZONE_FLAG_OPT_SEQ_SET) {
+ params->optimal_seq_zones = softc->optimal_seq_zones;
+ params->flags |= DISK_ZONE_OPT_SEQ_SET;
+ }
+
+ if (softc->zone_flags & ADA_ZONE_FLAG_OPT_NONSEQ_SET) {
+ params->optimal_nonseq_zones =
+ softc->optimal_nonseq_zones;
+ params->flags |= DISK_ZONE_OPT_NONSEQ_SET;
+ }
+
+ if (softc->zone_flags & ADA_ZONE_FLAG_MAX_SEQ_SET) {
+ params->max_seq_zones = softc->max_seq_zones;
+ params->flags |= DISK_ZONE_MAX_SEQ_SET;
+ }
+ if (softc->zone_flags & ADA_ZONE_FLAG_RZ_SUP)
+ params->flags |= DISK_ZONE_RZ_SUP;
+
+ if (softc->zone_flags & ADA_ZONE_FLAG_OPEN_SUP)
+ params->flags |= DISK_ZONE_OPEN_SUP;
+
+ if (softc->zone_flags & ADA_ZONE_FLAG_CLOSE_SUP)
+ params->flags |= DISK_ZONE_CLOSE_SUP;
+
+ if (softc->zone_flags & ADA_ZONE_FLAG_FINISH_SUP)
+ params->flags |= DISK_ZONE_FINISH_SUP;
+
+ if (softc->zone_flags & ADA_ZONE_FLAG_RWP_SUP)
+ params->flags |= DISK_ZONE_RWP_SUP;
+ break;
+ }
+ default:
+ break;
+ }
+bailout:
+ return (error);
+}
+
static void
adastart(struct cam_periph *periph, union ccb *start_ccb)
{
@@ -1941,7 +2357,21 @@
else
ata_28bit_cmd(ataio, ATA_FLUSHCACHE, 0, 0, 0);
break;
+ case BIO_ZONE: {
+ int error, queue_ccb;
+
+ queue_ccb = 0;
+
+ error = ada_zone_cmd(periph, start_ccb, bp, &queue_ccb);
+ if ((error != 0)
+ || (queue_ccb == 0)) {
+ biofinish(bp, NULL, error);
+ xpt_release_ccb(start_ccb);
+ return;
+ }
+ break;
}
+ }
start_ccb->ccb_h.ccb_state = ADA_CCB_BUFFER_IO;
start_ccb->ccb_h.flags |= CAM_UNLOCKED;
out:
@@ -1982,21 +2412,306 @@
xpt_action(start_ccb);
break;
}
+ case ADA_STATE_LOGDIR:
+ {
+ struct ata_gp_log_dir *log_dir;
+
+ if ((softc->flags & ADA_FLAG_CAN_LOG) == 0) {
+ adaprobedone(periph, start_ccb);
+ break;
+ }
+
+ log_dir = malloc(sizeof(*log_dir), M_ATADA, M_NOWAIT|M_ZERO);
+ if (log_dir == NULL) {
+ xpt_print(periph->path, "Couldn't malloc log_dir "
+ "data\n");
+ softc->state = ADA_STATE_NORMAL;
+ xpt_release_ccb(start_ccb);
+ break;
+ }
+
+
+ ata_read_log(ataio,
+ /*retries*/1,
+ /*cbfcnp*/adadone,
+ /*log_address*/ ATA_LOG_DIRECTORY,
+ /*page_number*/ 0,
+ /*block_count*/ 1,
+ /*protocol*/ softc->flags & ADA_FLAG_CAN_DMA ?
+ CAM_ATAIO_DMA : 0,
+ /*data_ptr*/ (uint8_t *)log_dir,
+ /*dxfer_len*/sizeof(*log_dir),
+ /*timeout*/ada_default_timeout*1000);
+
+ start_ccb->ccb_h.ccb_state = ADA_CCB_LOGDIR;
+ xpt_action(start_ccb);
+ break;
}
+ case ADA_STATE_IDDIR:
+ {
+ struct ata_identify_log_pages *id_dir;
+
+ id_dir = malloc(sizeof(*id_dir), M_ATADA, M_NOWAIT | M_ZERO);
+ if (id_dir == NULL) {
+ xpt_print(periph->path, "Couldn't malloc id_dir "
+ "data\n");
+ adaprobedone(periph, start_ccb);
+ break;
+ }
+
+ ata_read_log(ataio,
+ /*retries*/1,
+ /*cbfcnp*/adadone,
+ /*log_address*/ ATA_IDENTIFY_DATA_LOG,
+ /*page_number*/ ATA_IDL_PAGE_LIST,
+ /*block_count*/ 1,
+ /*protocol*/ softc->flags & ADA_FLAG_CAN_DMA ?
+ CAM_ATAIO_DMA : 0,
+ /*data_ptr*/ (uint8_t *)id_dir,
+ /*dxfer_len*/ sizeof(*id_dir),
+ /*timeout*/ada_default_timeout*1000);
+
+ start_ccb->ccb_h.ccb_state = ADA_CCB_IDDIR;
+ xpt_action(start_ccb);
+ break;
+ }
+ case ADA_STATE_SUP_CAP:
+ {
+ struct ata_identify_log_sup_cap *sup_cap;
+
+ sup_cap = malloc(sizeof(*sup_cap), M_ATADA, M_NOWAIT|M_ZERO);
+ if (sup_cap == NULL) {
+ xpt_print(periph->path, "Couldn't malloc sup_cap "
+ "data\n");
+ adaprobedone(periph, start_ccb);
+ break;
+ }
+
+ ata_read_log(ataio,
+ /*retries*/1,
+ /*cbfcnp*/adadone,
+ /*log_address*/ ATA_IDENTIFY_DATA_LOG,
+ /*page_number*/ ATA_IDL_SUP_CAP,
+ /*block_count*/ 1,
+ /*protocol*/ softc->flags & ADA_FLAG_CAN_DMA ?
+ CAM_ATAIO_DMA : 0,
+ /*data_ptr*/ (uint8_t *)sup_cap,
+ /*dxfer_len*/ sizeof(*sup_cap),
+ /*timeout*/ada_default_timeout*1000);
+
+ start_ccb->ccb_h.ccb_state = ADA_CCB_SUP_CAP;
+ xpt_action(start_ccb);
+ break;
+ }
+ case ADA_STATE_ZONE:
+ {
+ struct ata_zoned_info_log *ata_zone;
+
+ ata_zone = malloc(sizeof(*ata_zone), M_ATADA, M_NOWAIT|M_ZERO);
+ if (ata_zone == NULL) {
+ xpt_print(periph->path, "Couldn't malloc ata_zone "
+ "data\n");
+ adaprobedone(periph, start_ccb);
+ break;
+ }
+
+ ata_read_log(ataio,
+ /*retries*/1,
+ /*cbfcnp*/adadone,
+ /*log_address*/ ATA_IDENTIFY_DATA_LOG,
+ /*page_number*/ ATA_IDL_ZDI,
+ /*block_count*/ 1,
+ /*protocol*/ softc->flags & ADA_FLAG_CAN_DMA ?
+ CAM_ATAIO_DMA : 0,
+ /*data_ptr*/ (uint8_t *)ata_zone,
+ /*dxfer_len*/ sizeof(*ata_zone),
+ /*timeout*/ada_default_timeout*1000);
+
+ start_ccb->ccb_h.ccb_state = ADA_CCB_ZONE;
+ xpt_action(start_ccb);
+ break;
+ }
+ }
}
static void
+adaprobedone(struct cam_periph *periph, union ccb *ccb)
+{
+ struct ada_softc *softc;
+
+ softc = (struct ada_softc *)periph->softc;
+
+ if (ccb != NULL)
+ xpt_release_ccb(ccb);
+
+ softc->state = ADA_STATE_NORMAL;
+ softc->flags |= ADA_FLAG_PROBED;
+ adaschedule(periph);
+ if ((softc->flags & ADA_FLAG_ANNOUNCED) == 0) {
+ softc->flags |= ADA_FLAG_ANNOUNCED;
+ cam_periph_unhold(periph);
+ } else {
+ cam_periph_release_locked(periph);
+ }
+}
+
+static void
+adazonedone(struct cam_periph *periph, union ccb *ccb)
+{
+ struct ada_softc *softc;
+ struct bio *bp;
+
+ softc = periph->softc;
+ bp = (struct bio *)ccb->ccb_h.ccb_bp;
+
+ switch (bp->bio_zone.zone_cmd) {
+ case DISK_ZONE_OPEN:
+ case DISK_ZONE_CLOSE:
+ case DISK_ZONE_FINISH:
+ case DISK_ZONE_RWP:
+ break;
+ case DISK_ZONE_REPORT_ZONES: {
+ uint32_t avail_len;
+ struct disk_zone_report *rep;
+ struct scsi_report_zones_hdr *hdr;
+ struct scsi_report_zones_desc *desc;
+ struct disk_zone_rep_entry *entry;
+ uint32_t num_alloced, hdr_len, num_avail;
+ uint32_t num_to_fill, i;
+
+ rep = &bp->bio_zone.zone_params.report;
+ avail_len = ccb->ataio.dxfer_len - ccb->ataio.resid;
+ /*
+ * Note that bio_resid isn't normally used for zone
+ * commands, but it is used by devstat_end_transaction_bio()
+ * to determine how much data was transferred. Because
+ * the size of the SCSI/ATA data structures is different
+ * than the size of the BIO interface structures, the
+ * amount of data actually transferred from the drive will
+ * be different than the amount of data transferred to
+ * the user.
+ */
+ num_alloced = rep->entries_allocated;
+ hdr = (struct scsi_report_zones_hdr *)ccb->ataio.data_ptr;
+ if (avail_len < sizeof(*hdr)) {
+ /*
+ * Is there a better error than EIO here? We asked
+ * for at least the header, and we got less than
+ * that.
+ */
+ bp->bio_error = EIO;
+ bp->bio_flags |= BIO_ERROR;
+ bp->bio_resid = bp->bio_bcount;
+ break;
+ }
+
+ hdr_len = le32dec(hdr->length);
+ if (hdr_len > 0)
+ rep->entries_available = hdr_len / sizeof(*desc);
+ else
+ rep->entries_available = 0;
+ /*
+ * NOTE: using the same values for the BIO version of the
+ * same field as the SCSI/ATA values. This means we could
+ * get some additional values that aren't defined in bio.h
+ * if more values of the same field are defined later.
+ */
+ rep->header.same = hdr->byte4 & SRZ_SAME_MASK;
+ rep->header.maximum_lba = le64dec(hdr->maximum_lba);
+ /*
+ * If the drive reports no entries that match the query,
+ * we're done.
+ */
+ if (hdr_len == 0) {
+ rep->entries_filled = 0;
+ bp->bio_resid = bp->bio_bcount;
+ break;
+ }
+
+ num_avail = min((avail_len - sizeof(*hdr)) / sizeof(*desc),
+ hdr_len / sizeof(*desc));
+ /*
+ * If the drive didn't return any data, then we're done.
+ */
+ if (num_avail == 0) {
+ rep->entries_filled = 0;
+ bp->bio_resid = bp->bio_bcount;
+ break;
+ }
+
+ num_to_fill = min(num_avail, rep->entries_allocated);
+ /*
+ * If the user didn't allocate any entries for us to fill,
+ * we're done.
+ */
+ if (num_to_fill == 0) {
+ rep->entries_filled = 0;
+ bp->bio_resid = bp->bio_bcount;
+ break;
+ }
+
+ for (i = 0, desc = &hdr->desc_list[0], entry=&rep->entries[0];
+ i < num_to_fill; i++, desc++, entry++) {
+ /*
+ * NOTE: we're mapping the values here directly
+ * from the SCSI/ATA bit definitions to the bio.h
+ * definitons. There is also a warning in
+ * disk_zone.h, but the impact is that if
+ * additional values are added in the SCSI/ATA
+ * specs these will be visible to consumers of
+ * this interface.
+ */
+ entry->zone_type = desc->zone_type & SRZ_TYPE_MASK;
+ entry->zone_condition =
+ (desc->zone_flags & SRZ_ZONE_COND_MASK) >>
+ SRZ_ZONE_COND_SHIFT;
+ entry->zone_flags |= desc->zone_flags &
+ (SRZ_ZONE_NON_SEQ|SRZ_ZONE_RESET);
+ entry->zone_length = le64dec(desc->zone_length);
+ entry->zone_start_lba = le64dec(desc->zone_start_lba);
+ entry->write_pointer_lba =
+ le64dec(desc->write_pointer_lba);
+ }
+ rep->entries_filled = num_to_fill;
+ /*
+ * Note that this residual is accurate from the user's
+ * standpoint, but the amount transferred isn't accurate
+ * from the standpoint of what actually came back from the
+ * drive.
+ */
+ bp->bio_resid = bp->bio_bcount - (num_to_fill * sizeof(*entry));
+ break;
+ }
+ case DISK_ZONE_GET_PARAMS:
+ default:
+ /*
+ * In theory we should not get a GET_PARAMS bio, since it
+ * should be handled without queueing the command to the
+ * drive.
+ */
+ panic("%s: Invalid zone command %d", __func__,
+ bp->bio_zone.zone_cmd);
+ break;
+ }
+
+ if (bp->bio_zone.zone_cmd == DISK_ZONE_REPORT_ZONES)
+ free(ccb->ataio.data_ptr, M_ATADA);
+}
+
+
+static void
adadone(struct cam_periph *periph, union ccb *done_ccb)
{
struct ada_softc *softc;
struct ccb_ataio *ataio;
- struct ccb_getdev *cgd;
struct cam_path *path;
+ uint32_t priority;
int state;
softc = (struct ada_softc *)periph->softc;
ataio = &done_ccb->ataio;
path = done_ccb->ccb_h.path;
+ priority = done_ccb->ccb_h.pinfo.priority;
CAM_DEBUG(path, CAM_DEBUG_TRACE, ("adadone\n"));
@@ -2040,6 +2755,7 @@
} else {
if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0)
panic("REQ_CMP with QFRZN");
+
error = 0;
}
bp->bio_error = error;
@@ -2047,11 +2763,15 @@
bp->bio_resid = bp->bio_bcount;
bp->bio_flags |= BIO_ERROR;
} else {
- if (state == ADA_CCB_TRIM)
+ if (bp->bio_cmd == BIO_ZONE)
+ adazonedone(periph, done_ccb);
+ else if (state == ADA_CCB_TRIM)
bp->bio_resid = 0;
else
bp->bio_resid = ataio->resid;
- if (bp->bio_resid > 0)
+
+ if ((bp->bio_resid > 0)
+ && (bp->bio_cmd != BIO_ZONE))
bp->bio_flags |= BIO_ERROR;
}
softc->outstanding_cmds--;
@@ -2100,7 +2820,6 @@
{
if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
if (adaerror(done_ccb, 0, 0) == ERESTART) {
-out:
/* Drop freeze taken due to CAM_DEV_QFREEZE */
cam_release_devq(path, 0, 0, 0, FALSE);
return;
@@ -2121,23 +2840,12 @@
* is removed, and we need it around for the CCB release
* operation.
*/
- cgd = (struct ccb_getdev *)done_ccb;
- xpt_setup_ccb(&cgd->ccb_h, path, CAM_PRIORITY_NORMAL);
- cgd->ccb_h.func_code = XPT_GDEV_TYPE;
- xpt_action((union ccb *)cgd);
- if (ADA_WC >= 0 &&
- cgd->ident_data.support.command1 & ATA_SUPPORT_WRITECACHE) {
- softc->state = ADA_STATE_WCACHE;
- xpt_release_ccb(done_ccb);
- xpt_schedule(periph, CAM_PRIORITY_DEV);
- goto out;
- }
- softc->state = ADA_STATE_NORMAL;
+
xpt_release_ccb(done_ccb);
+ softc->state = ADA_STATE_WCACHE;
+ xpt_schedule(periph, priority);
/* Drop freeze taken due to CAM_DEV_QFREEZE */
cam_release_devq(path, 0, 0, 0, FALSE);
- adaschedule(periph);
- cam_periph_release_locked(periph);
return;
}
case ADA_CCB_WCACHE:
@@ -2144,7 +2852,9 @@
{
if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
if (adaerror(done_ccb, 0, 0) == ERESTART) {
- goto out;
+ /* Drop freeze taken due to CAM_DEV_QFREEZE */
+ cam_release_devq(path, 0, 0, 0, FALSE);
+ return;
} else if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) {
cam_release_devq(path,
/*relsim_flags*/0,
@@ -2154,22 +2864,367 @@
}
}
- softc->state = ADA_STATE_NORMAL;
- /*
- * Since our peripheral may be invalidated by an error
- * above or an external event, we must release our CCB
- * before releasing the reference on the peripheral.
- * The peripheral will only go away once the last reference
- * is removed, and we need it around for the CCB release
- * operation.
- */
- xpt_release_ccb(done_ccb);
/* Drop freeze taken due to CAM_DEV_QFREEZE */
cam_release_devq(path, 0, 0, 0, FALSE);
- adaschedule(periph);
- cam_periph_release_locked(periph);
+
+ if (softc->flags & ADA_FLAG_CAN_LOG) {
+ xpt_release_ccb(done_ccb);
+ softc->state = ADA_STATE_LOGDIR;
+ xpt_schedule(periph, priority);
+ } else {
+ adaprobedone(periph, done_ccb);
+ }
return;
}
+ case ADA_CCB_LOGDIR:
+ {
+ int error;
+
+ if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ error = 0;
+ softc->valid_logdir_len = 0;
+ bzero(&softc->ata_logdir, sizeof(softc->ata_logdir));
+ softc->valid_logdir_len =
+ ataio->dxfer_len - ataio->resid;
+ if (softc->valid_logdir_len > 0)
+ bcopy(ataio->data_ptr, &softc->ata_logdir,
+ min(softc->valid_logdir_len,
+ sizeof(softc->ata_logdir)));
+ /*
+ * Figure out whether the Identify Device log is
+ * supported. The General Purpose log directory
+ * has a header, and lists the number of pages
+ * available for each GP log identified by the
+ * offset into the list.
+ */
+ if ((softc->valid_logdir_len >=
+ ((ATA_IDENTIFY_DATA_LOG + 1) * sizeof(uint16_t)))
+ && (le16dec(softc->ata_logdir.header) ==
+ ATA_GP_LOG_DIR_VERSION)
+ && (le16dec(&softc->ata_logdir.num_pages[
+ (ATA_IDENTIFY_DATA_LOG *
+ sizeof(uint16_t)) - sizeof(uint16_t)]) > 0)){
+ softc->flags |= ADA_FLAG_CAN_IDLOG;
+ } else {
+ softc->flags &= ~ADA_FLAG_CAN_IDLOG;
+ }
+ } else {
+ error = adaerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ /*
+ * If we can't get the ATA log directory,
+ * then ATA logs are effectively not
+ * supported even if the bit is set in the
+ * identify data.
+ */
+ softc->flags &= ~(ADA_FLAG_CAN_LOG |
+ ADA_FLAG_CAN_IDLOG);
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+
+
+ }
+
+ free(ataio->data_ptr, M_ATADA);
+
+ if ((error == 0)
+ && (softc->flags & ADA_FLAG_CAN_IDLOG)) {
+ softc->state = ADA_STATE_IDDIR;
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ } else
+ adaprobedone(periph, done_ccb);
+
+ return;
+ }
+ case ADA_CCB_IDDIR: {
+ int error;
+
+ if ((ataio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ off_t entries_offset, max_entries;
+ error = 0;
+
+ softc->valid_iddir_len = 0;
+ bzero(&softc->ata_iddir, sizeof(softc->ata_iddir));
+ softc->flags &= ~(ADA_FLAG_CAN_SUPCAP |
+ ADA_FLAG_CAN_ZONE);
+ softc->valid_iddir_len =
+ ataio->dxfer_len - ataio->resid;
+ if (softc->valid_iddir_len > 0)
+ bcopy(ataio->data_ptr, &softc->ata_iddir,
+ min(softc->valid_iddir_len,
+ sizeof(softc->ata_iddir)));
+
+ entries_offset =
+ __offsetof(struct ata_identify_log_pages,entries);
+ max_entries = softc->valid_iddir_len - entries_offset;
+ if ((softc->valid_iddir_len > (entries_offset + 1))
+ && (le64dec(softc->ata_iddir.header) ==
+ ATA_IDLOG_REVISION)
+ && (softc->ata_iddir.entry_count > 0)) {
+ int num_entries, i;
+
+ num_entries = softc->ata_iddir.entry_count;
+ num_entries = min(num_entries,
+ softc->valid_iddir_len - entries_offset);
+ for (i = 0; i < num_entries &&
+ i < max_entries; i++) {
+ if (softc->ata_iddir.entries[i] ==
+ ATA_IDL_SUP_CAP)
+ softc->flags |=
+ ADA_FLAG_CAN_SUPCAP;
+ else if (softc->ata_iddir.entries[i]==
+ ATA_IDL_ZDI)
+ softc->flags |=
+ ADA_FLAG_CAN_ZONE;
+
+ if ((softc->flags &
+ ADA_FLAG_CAN_SUPCAP)
+ && (softc->flags &
+ ADA_FLAG_CAN_ZONE))
+ break;
+ }
+ }
+ } else {
+ error = adaerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ /*
+ * If we can't get the ATA Identify Data log
+ * directory, then it effectively isn't
+ * supported even if the ATA Log directory
+ * a non-zero number of pages present for
+ * this log.
+ */
+ softc->flags &= ~ADA_FLAG_CAN_IDLOG;
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+ }
+
+ free(ataio->data_ptr, M_ATADA);
+
+ if ((error == 0)
+ && (softc->flags & ADA_FLAG_CAN_SUPCAP)) {
+ softc->state = ADA_STATE_SUP_CAP;
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ } else
+ adaprobedone(periph, done_ccb);
+ return;
+ }
+ case ADA_CCB_SUP_CAP: {
+ int error;
+
+ if ((ataio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ uint32_t valid_len;
+ size_t needed_size;
+ struct ata_identify_log_sup_cap *sup_cap;
+ error = 0;
+
+ sup_cap = (struct ata_identify_log_sup_cap *)
+ ataio->data_ptr;
+ valid_len = ataio->dxfer_len - ataio->resid;
+ needed_size =
+ __offsetof(struct ata_identify_log_sup_cap,
+ sup_zac_cap) + 1 + sizeof(sup_cap->sup_zac_cap);
+ if (valid_len >= needed_size) {
+ uint64_t zoned, zac_cap;
+
+ zoned = le64dec(sup_cap->zoned_cap);
+ if (zoned & ATA_ZONED_VALID) {
+ /*
+ * This should have already been
+ * set, because this is also in the
+ * ATA identify data.
+ */
+ if ((zoned & ATA_ZONED_MASK) ==
+ ATA_SUPPORT_ZONE_HOST_AWARE)
+ softc->zone_mode =
+ ADA_ZONE_HOST_AWARE;
+ else if ((zoned & ATA_ZONED_MASK) ==
+ ATA_SUPPORT_ZONE_DEV_MANAGED)
+ softc->zone_mode =
+ ADA_ZONE_DRIVE_MANAGED;
+ }
+
+ zac_cap = le64dec(sup_cap->sup_zac_cap);
+ if (zac_cap & ATA_SUP_ZAC_CAP_VALID) {
+ if (zac_cap & ATA_REPORT_ZONES_SUP)
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_RZ_SUP;
+ if (zac_cap & ATA_ND_OPEN_ZONE_SUP)
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_OPEN_SUP;
+ if (zac_cap & ATA_ND_CLOSE_ZONE_SUP)
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_CLOSE_SUP;
+ if (zac_cap & ATA_ND_FINISH_ZONE_SUP)
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_FINISH_SUP;
+ if (zac_cap & ATA_ND_RWP_SUP)
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_RWP_SUP;
+ } else {
+ /*
+ * This field was introduced in
+ * ACS-4, r08 on April 28th, 2015.
+ * If the drive firmware was written
+ * to an earlier spec, it won't have
+ * the field. So, assume all
+ * commands are supported.
+ */
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_SUP_MASK;
+ }
+
+ }
+ } else {
+ error = adaerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ /*
+ * If we can't get the ATA Identify Data
+ * Supported Capabilities page, clear the
+ * flag...
+ */
+ softc->flags &= ~ADA_FLAG_CAN_SUPCAP;
+ /*
+ * And clear zone capabilities.
+ */
+ softc->zone_flags &= ~ADA_ZONE_FLAG_SUP_MASK;
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+ }
+
+ free(ataio->data_ptr, M_ATADA);
+
+ if ((error == 0)
+ && (softc->flags & ADA_FLAG_CAN_ZONE)) {
+ softc->state = ADA_STATE_ZONE;
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ } else
+ adaprobedone(periph, done_ccb);
+ return;
+ }
+ case ADA_CCB_ZONE: {
+ int error;
+
+ if ((ataio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ struct ata_zoned_info_log *zi_log;
+ uint32_t valid_len;
+ size_t needed_size;
+
+ zi_log = (struct ata_zoned_info_log *)ataio->data_ptr;
+
+ valid_len = ataio->dxfer_len - ataio->resid;
+ needed_size = __offsetof(struct ata_zoned_info_log,
+ version_info) + 1 + sizeof(zi_log->version_info);
+ if (valid_len >= needed_size) {
+ uint64_t tmpvar;
+
+ tmpvar = le64dec(zi_log->zoned_cap);
+ if (tmpvar & ATA_ZDI_CAP_VALID) {
+ if (tmpvar & ATA_ZDI_CAP_URSWRZ)
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_URSWRZ;
+ else
+ softc->zone_flags &=
+ ~ADA_ZONE_FLAG_URSWRZ;
+ }
+ tmpvar = le64dec(zi_log->optimal_seq_zones);
+ if (tmpvar & ATA_ZDI_OPT_SEQ_VALID) {
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_OPT_SEQ_SET;
+ softc->optimal_seq_zones = (tmpvar &
+ ATA_ZDI_OPT_SEQ_MASK);
+ } else {
+ softc->zone_flags &=
+ ~ADA_ZONE_FLAG_OPT_SEQ_SET;
+ softc->optimal_seq_zones = 0;
+ }
+
+ tmpvar =le64dec(zi_log->optimal_nonseq_zones);
+ if (tmpvar & ATA_ZDI_OPT_NS_VALID) {
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_OPT_NONSEQ_SET;
+ softc->optimal_nonseq_zones =
+ (tmpvar & ATA_ZDI_OPT_NS_MASK);
+ } else {
+ softc->zone_flags &=
+ ~ADA_ZONE_FLAG_OPT_NONSEQ_SET;
+ softc->optimal_nonseq_zones = 0;
+ }
+
+ tmpvar = le64dec(zi_log->max_seq_req_zones);
+ if (tmpvar & ATA_ZDI_MAX_SEQ_VALID) {
+ softc->zone_flags |=
+ ADA_ZONE_FLAG_MAX_SEQ_SET;
+ softc->max_seq_zones =
+ (tmpvar & ATA_ZDI_MAX_SEQ_MASK);
+ } else {
+ softc->zone_flags &=
+ ~ADA_ZONE_FLAG_MAX_SEQ_SET;
+ softc->max_seq_zones = 0;
+ }
+ }
+ } else {
+ error = adaerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ softc->flags &= ~ADA_FLAG_CAN_ZONE;
+ softc->flags &= ~ADA_ZONE_FLAG_SET_MASK;
+
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+
+ }
+ free(ataio->data_ptr, M_ATADA);
+
+ adaprobedone(periph, done_ccb);
+ return;
+ }
case ADA_CCB_DUMP:
/* No-op. We're polling */
return;
Index: sys/cam/scsi/scsi_all.h
===================================================================
--- sys/cam/scsi/scsi_all.h
+++ sys/cam/scsi/scsi_all.h
@@ -1414,6 +1414,7 @@
#define AP_PROTO_UDMA_OUT (0x0b << 1)
#define AP_PROTO_FPDMA (0x0c << 1)
#define AP_PROTO_RESP_INFO (0x0f << 1)
+#define AP_PROTO_MASK 0x1e
#define AP_MULTI 0xe0
u_int8_t flags;
#define AP_T_LEN 0x03
@@ -1955,6 +1956,27 @@
u_int8_t control;
};
+struct ata_pass_32 {
+ uint8_t opcode;
+ uint8_t control;
+ uint8_t reserved1[5];
+ uint8_t length;
+ uint8_t service_action[2];
+#define ATA_PASS_32_SA 0x1ff0
+ uint8_t protocol;
+ uint8_t flags;
+ uint8_t reserved2[2];
+ uint8_t lba[6];
+ uint8_t features[2];
+ uint8_t count[2];
+ uint8_t device;
+ uint8_t command;
+ uint8_t reserved3;
+ uint8_t icc;
+ uint8_t auxiliary[4];
+};
+
+
#define SC_SCSI_1 0x01
#define SC_SCSI_2 0x03
@@ -1997,6 +2019,8 @@
#define MODE_SENSE_10 0x5A
#define PERSISTENT_RES_IN 0x5E
#define PERSISTENT_RES_OUT 0x5F
+#define EXTENDED_CDB 0x7E
+#define VARIABLE_LEN_CDB 0x7F
#define EXTENDED_COPY 0x83
#define RECEIVE_COPY_STATUS 0x84
#define ATA_PASS_16 0x85
@@ -2064,6 +2088,7 @@
#define T_OCRW 0x0f
#define T_OSD 0x11
#define T_ADC 0x12
+#define T_ZBC_HM 0x14
#define T_NODEVICE 0x1f
#define T_ANY 0xff /* Used in Quirk table matches */
@@ -2712,10 +2737,17 @@
uint8_t flags;
#define SVPD_VBULS 0x01
#define SVPD_FUAB 0x02
-#define SVPD_HAW_ZBC 0x10
+#define SVPD_ZBC_NR 0x00 /* Not Reported */
+#define SVPD_HAW_ZBC 0x10 /* Host Aware */
+#define SVPD_DM_ZBC 0x20 /* Drive Managed */
+#define SVPD_ZBC_MASK 0x30 /* Zoned mask */
uint8_t reserved[55];
};
+#define SBDC_IS_PRESENT(bdc, length, field) \
+ ((length >= offsetof(struct scsi_vpd_block_device_characteristics, \
+ field) + sizeof(bdc->field)) ? 1 : 0)
+
/*
* Logical Block Provisioning VPD Page based on
* T10/1799-D Revision 31
@@ -2774,6 +2806,28 @@
u_int8_t max_atomic_boundary_size[4];
};
+/*
+ * Zoned Block Device Characacteristics VPD page.
+ * From ZBC-r04, dated August 12, 2015.
+ */
+struct scsi_vpd_zoned_bdc {
+ uint8_t device;
+ uint8_t page_code;
+#define SVPD_ZONED_BDC 0xB6
+ uint8_t page_length[2];
+#define SVPD_ZBDC_PL 0x3C
+ uint8_t flags;
+#define SVPD_ZBDC_URSWRZ 0x01
+ uint8_t reserved1[3];
+ uint8_t optimal_seq_zones[4];
+#define SVPD_ZBDC_OPT_SEQ_NR 0xffffffff
+ uint8_t optimal_nonseq_zones[4];
+#define SVPD_ZBDC_OPT_NONSEQ_NR 0xffffffff
+ uint8_t max_seq_req_zones[4];
+#define SVPD_ZBDC_MAX_SEQ_UNLIMITED 0xffffffff
+ uint8_t reserved2[44];
+};
+
struct scsi_read_capacity
{
u_int8_t opcode;
@@ -3345,6 +3399,29 @@
};
/*
+ * ATA Return descriptor, used for the SCSI ATA PASS-THROUGH(12), (16) and
+ * (32) commands. Described in SAT-4r05.
+ */
+struct scsi_sense_ata_ret_desc
+{
+ uint8_t desc_type;
+#define SSD_DESC_ATA 0x09
+ uint8_t length;
+ uint8_t flags;
+#define SSD_DESC_ATA_FLAG_EXTEND 0x01
+ uint8_t error;
+ uint8_t count_15_8;
+ uint8_t count_7_0;
+ uint8_t lba_31_24;
+ uint8_t lba_7_0;
+ uint8_t lba_39_32;
+ uint8_t lba_15_8;
+ uint8_t lba_47_40;
+ uint8_t lba_23_16;
+ uint8_t device;
+ uint8_t status;
+};
+/*
* Used with Sense keys No Sense (0x00) and Not Ready (0x02).
*
* Maximum descriptors allowed: 32 (as of SPC-4)
@@ -3960,6 +4037,23 @@
u_int8_t *data_ptr, u_int16_t dxfer_len,
u_int8_t sense_len, u_int32_t timeout);
+int scsi_ata_read_log(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, uint32_t log_address,
+ uint32_t page_number, uint16_t block_count,
+ uint8_t protocol, uint8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t sense_len, uint32_t timeout);
+
+int scsi_ata_pass(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint32_t flags, uint8_t tag_action,
+ uint8_t protocol, uint8_t ata_flags, uint16_t features,
+ uint16_t sector_count, uint64_t lba, uint8_t command,
+ uint8_t device, uint8_t icc, uint32_t auxiliary,
+ uint8_t control, u_int8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t *cdb_storage, size_t cdb_storage_len,
+ int minimum_cmd_size, u_int8_t sense_len, u_int32_t timeout);
+
void scsi_ata_pass_16(struct ccb_scsiio *csio, u_int32_t retries,
void (*cbfcnp)(struct cam_periph *, union ccb *),
u_int32_t flags, u_int8_t tag_action,
Index: sys/cam/scsi/scsi_all.c
===================================================================
--- sys/cam/scsi/scsi_all.c
+++ sys/cam/scsi/scsi_all.c
@@ -111,6 +111,7 @@
struct scsi_inquiry_data *,
const struct sense_key_table_entry **,
const struct asc_table_entry **);
+
#ifdef _KERNEL
static void init_scsi_delay(void);
static int sysctl_scsi_delay(SYSCTL_HANDLER_ARGS);
@@ -502,9 +503,9 @@
/* 93 M ERASE(16) */
{ 0x93, T, "ERASE(16)" },
/* 94 O ZBC OUT */
- { 0x94, D, "ZBC OUT" },
+ { 0x94, ALL, "ZBC OUT" },
/* 95 O ZBC OUT */
- { 0x95, D, "ZBC OUT" },
+ { 0x95, ALL, "ZBC OUT" },
/* 96 */
/* 97 */
/* 98 */
@@ -520,7 +521,6 @@
/* XXX KDM ALL for this? op-num.txt defines it for none.. */
/* 9E SERVICE ACTION IN(16) */
{ 0x9E, ALL, "SERVICE ACTION IN(16)" },
- /* XXX KDM ALL for this? op-num.txt defines it for ADC.. */
/* 9F M SERVICE ACTION OUT(16) */
{ 0x9F, ALL, "SERVICE ACTION OUT(16)" },
/* A0 MMOOO OMMM OMO REPORT LUNS */
@@ -673,6 +673,12 @@
if (pd_type == T_RBC)
pd_type = T_DIRECT;
+ /*
+ * Host managed drives are direct access for the most part.
+ */
+ if (pd_type == T_ZBC_HM)
+ pd_type = T_DIRECT;
+
/* Map NODEVICE to Direct Access Device to handle REPORT LUNS, etc. */
if (pd_type == T_NODEVICE)
pd_type = T_DIRECT;
@@ -4259,6 +4265,7 @@
switch (SID_TYPE(inq_data)) {
case T_DIRECT:
case T_RBC:
+ case T_ZBC_HM:
break;
default:
goto bailout;
@@ -5408,6 +5415,9 @@
case T_ADC:
dtype = "Automation/Drive Interface";
break;
+ case T_ZBC_HM:
+ dtype = "Host Managed Zoned Block";
+ break;
case T_NODEVICE:
dtype = "Uninstalled";
break;
@@ -8135,23 +8145,30 @@
u_int16_t dxfer_len, u_int8_t sense_len,
u_int32_t timeout)
{
- scsi_ata_pass_16(csio,
- retries,
- cbfcnp,
- /*flags*/CAM_DIR_IN,
- tag_action,
- /*protocol*/AP_PROTO_PIO_IN,
- /*ata_flags*/AP_FLAG_TDIR_FROM_DEV|
- AP_FLAG_BYT_BLOK_BYTES|AP_FLAG_TLEN_SECT_CNT,
- /*features*/0,
- /*sector_count*/dxfer_len,
- /*lba*/0,
- /*command*/ATA_ATA_IDENTIFY,
- /*control*/0,
- data_ptr,
- dxfer_len,
- sense_len,
- timeout);
+ scsi_ata_pass(csio,
+ retries,
+ cbfcnp,
+ /*flags*/CAM_DIR_IN,
+ tag_action,
+ /*protocol*/AP_PROTO_PIO_IN,
+ /*ata_flags*/AP_FLAG_TDIR_FROM_DEV |
+ AP_FLAG_BYT_BLOK_BYTES |
+ AP_FLAG_TLEN_SECT_CNT,
+ /*features*/0,
+ /*sector_count*/dxfer_len,
+ /*lba*/0,
+ /*command*/ATA_ATA_IDENTIFY,
+ /*device*/ 0,
+ /*icc*/ 0,
+ /*auxiliary*/ 0,
+ /*control*/0,
+ data_ptr,
+ dxfer_len,
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
+ /*minimum_cmd_size*/ 0,
+ sense_len,
+ timeout);
}
void
@@ -8179,6 +8196,248 @@
timeout);
}
+int
+scsi_ata_read_log(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, uint32_t log_address,
+ uint32_t page_number, uint16_t block_count,
+ uint8_t protocol, uint8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t sense_len, uint32_t timeout)
+{
+ uint8_t command, protocol_out;
+ uint16_t count_out;
+ uint64_t lba;
+ int retval;
+
+ retval = 0;
+
+ switch (protocol) {
+ case AP_PROTO_DMA:
+ count_out = block_count;
+ command = ATA_READ_LOG_DMA_EXT;
+ protocol_out = AP_PROTO_DMA;
+ break;
+ case AP_PROTO_PIO_IN:
+ default:
+ count_out = block_count;
+ command = ATA_READ_LOG_EXT;
+ protocol_out = AP_PROTO_PIO_IN;
+ break;
+ }
+
+ lba = (((uint64_t)page_number & 0xff00) << 32) |
+ ((page_number & 0x00ff) << 8) |
+ (log_address & 0xff);
+
+ protocol_out |= AP_EXTEND;
+
+ retval = scsi_ata_pass(csio,
+ retries,
+ cbfcnp,
+ /*flags*/CAM_DIR_IN,
+ tag_action,
+ /*protocol*/ protocol_out,
+ /*ata_flags*/AP_FLAG_TLEN_SECT_CNT |
+ AP_FLAG_BYT_BLOK_BLOCKS |
+ AP_FLAG_TDIR_FROM_DEV,
+ /*feature*/ 0,
+ /*sector_count*/ count_out,
+ /*lba*/ lba,
+ /*command*/ command,
+ /*device*/ 0,
+ /*icc*/ 0,
+ /*auxiliary*/ 0,
+ /*control*/0,
+ data_ptr,
+ dxfer_len,
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
+ /*minimum_cmd_size*/ 0,
+ sense_len,
+ timeout);
+
+ return (retval);
+}
+
+/*
+ * Note! This is an unusual CDB building function because it can return
+ * an error in the event that the command in question requires a variable
+ * length CDB, but the caller has not given storage space for one or has not
+ * given enough storage space. If there is enough space available in the
+ * standard SCSI CCB CDB bytes, we'll prefer that over passed in storage.
+ */
+int
+scsi_ata_pass(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint32_t flags, uint8_t tag_action,
+ uint8_t protocol, uint8_t ata_flags, uint16_t features,
+ uint16_t sector_count, uint64_t lba, uint8_t command,
+ uint8_t device, uint8_t icc, uint32_t auxiliary,
+ uint8_t control, u_int8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t *cdb_storage, size_t cdb_storage_len,
+ int minimum_cmd_size, u_int8_t sense_len, u_int32_t timeout)
+{
+ uint32_t cam_flags;
+ uint8_t *cdb_ptr;
+ int cmd_size;
+ int retval;
+ uint8_t cdb_len;
+
+ retval = 0;
+ cam_flags = flags;
+
+ /*
+ * Round the user's request to the nearest command size that is at
+ * least as big as what he requested.
+ */
+ if (minimum_cmd_size <= 12)
+ cmd_size = 12;
+ else if (minimum_cmd_size > 16)
+ cmd_size = 32;
+ else
+ cmd_size = 16;
+
+ /*
+ * If we have parameters that require a 48-bit ATA command, we have to
+ * use the 16 byte ATA PASS-THROUGH command at least.
+ */
+ if (((lba > ATA_MAX_28BIT_LBA)
+ || (sector_count > 255)
+ || (features > 255)
+ || (protocol & AP_EXTEND))
+ && ((cmd_size < 16)
+ || ((protocol & AP_EXTEND) == 0))) {
+ if (cmd_size < 16)
+ cmd_size = 16;
+ protocol |= AP_EXTEND;
+ }
+
+ /*
+ * The icc and auxiliary ATA registers are only supported in the
+ * 32-byte version of the ATA PASS-THROUGH command.
+ */
+ if ((icc != 0)
+ || (auxiliary != 0)) {
+ cmd_size = 32;
+ protocol |= AP_EXTEND;
+ }
+
+
+ if ((cmd_size > sizeof(csio->cdb_io.cdb_bytes))
+ && ((cdb_storage == NULL)
+ || (cdb_storage_len < cmd_size))) {
+ retval = 1;
+ goto bailout;
+ }
+
+ /*
+ * At this point we know we have enough space to store the command
+ * in one place or another. We prefer the built-in array, but used
+ * the passed in storage if necessary.
+ */
+ if (cmd_size <= sizeof(csio->cdb_io.cdb_bytes))
+ cdb_ptr = csio->cdb_io.cdb_bytes;
+ else {
+ cdb_ptr = cdb_storage;
+ cam_flags |= CAM_CDB_POINTER;
+ }
+
+ if (cmd_size <= 12) {
+ struct ata_pass_12 *cdb;
+
+ cdb = (struct ata_pass_12 *)cdb_ptr;
+ cdb_len = sizeof(*cdb);
+ bzero(cdb, cdb_len);
+
+ cdb->opcode = ATA_PASS_12;
+ cdb->protocol = protocol;
+ cdb->flags = ata_flags;
+ cdb->features = features;
+ cdb->sector_count = sector_count;
+ cdb->lba_low = lba & 0xff;
+ cdb->lba_mid = (lba >> 8) & 0xff;
+ cdb->lba_high = (lba >> 16) & 0xff;
+ cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA;
+ cdb->command = command;
+ cdb->control = control;
+ } else if (cmd_size <= 16) {
+ struct ata_pass_16 *cdb;
+
+ cdb = (struct ata_pass_16 *)cdb_ptr;
+ cdb_len = sizeof(*cdb);
+ bzero(cdb, cdb_len);
+
+ cdb->opcode = ATA_PASS_16;
+ cdb->protocol = protocol;
+ cdb->flags = ata_flags;
+ cdb->features = features & 0xff;
+ cdb->sector_count = sector_count & 0xff;
+ cdb->lba_low = lba & 0xff;
+ cdb->lba_mid = (lba >> 8) & 0xff;
+ cdb->lba_high = (lba >> 16) & 0xff;
+ /*
+ * If AP_EXTEND is set, we're sending a 48-bit command.
+ * Otherwise it's a 28-bit command.
+ */
+ if (protocol & AP_EXTEND) {
+ cdb->lba_low_ext = (lba >> 24) & 0xff;
+ cdb->lba_mid_ext = (lba >> 32) & 0xff;
+ cdb->lba_high_ext = (lba >> 40) & 0xff;
+ cdb->features_ext = (features >> 8) & 0xff;
+ cdb->sector_count_ext = (sector_count >> 8) & 0xff;
+ cdb->device = device | ATA_DEV_LBA;
+ } else {
+ cdb->lba_low_ext = (lba >> 24) & 0xf;
+ cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA;
+ }
+ cdb->command = command;
+ cdb->control = control;
+ } else {
+ struct ata_pass_32 *cdb;
+ uint8_t tmp_lba[8];
+
+ cdb = (struct ata_pass_32 *)cdb_ptr;
+ cdb_len = sizeof(*cdb);
+ bzero(cdb, cdb_len);
+ cdb->opcode = VARIABLE_LEN_CDB;
+ cdb->control = control;
+ cdb->length = sizeof(*cdb) - __offsetof(struct ata_pass_32,
+ service_action);
+ scsi_ulto2b(ATA_PASS_32_SA, cdb->service_action);
+ cdb->protocol = protocol;
+ cdb->flags = ata_flags;
+
+ if ((protocol & AP_EXTEND) == 0) {
+ lba &= 0x0fffffff;
+ cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA;
+ features &= 0xff;
+ sector_count &= 0xff;
+ } else {
+ cdb->device = device | ATA_DEV_LBA;
+ }
+ scsi_u64to8b(lba, tmp_lba);
+ bcopy(&tmp_lba[2], cdb->lba, sizeof(cdb->lba));
+ scsi_ulto2b(features, cdb->features);
+ scsi_ulto2b(sector_count, cdb->count);
+ cdb->command = command;
+ cdb->icc = icc;
+ scsi_ulto4b(auxiliary, cdb->auxiliary);
+ }
+
+ cam_fill_csio(csio,
+ retries,
+ cbfcnp,
+ cam_flags,
+ tag_action,
+ data_ptr,
+ dxfer_len,
+ sense_len,
+ cmd_size,
+ timeout);
+bailout:
+ return (retval);
+}
+
void
scsi_ata_pass_16(struct ccb_scsiio *csio, u_int32_t retries,
void (*cbfcnp)(struct cam_periph *, union ccb *),
Index: sys/cam/scsi/scsi_da.h
===================================================================
--- sys/cam/scsi/scsi_da.h
+++ sys/cam/scsi/scsi_da.h
@@ -153,7 +153,85 @@
uint8_t control;
};
+struct scsi_zbc_out
+{
+ uint8_t opcode;
+ uint8_t service_action;
+#define ZBC_OUT_SA_CLOSE 0x01
+#define ZBC_OUT_SA_FINISH 0x02
+#define ZBC_OUT_SA_OPEN 0x03
+#define ZBC_OUT_SA_RWP 0x04
+ uint8_t zone_id[8];
+ uint8_t reserved[4];
+ uint8_t zone_flags;
+#define ZBC_OUT_ALL 0x01
+ uint8_t control;
+};
+struct scsi_zbc_in
+{
+ uint8_t opcode;
+ uint8_t service_action;
+#define ZBC_IN_SA_REPORT_ZONES 0x00
+ uint8_t zone_start_lba[8];
+ uint8_t length[4];
+ uint8_t zone_options;
+#define ZBC_IN_PARTIAL 0x80
+#define ZBC_IN_REP_ALL_ZONES 0x00
+#define ZBC_IN_REP_EMPTY 0x01
+#define ZBC_IN_REP_IMP_OPEN 0x02
+#define ZBC_IN_REP_EXP_OPEN 0x03
+#define ZBC_IN_REP_CLOSED 0x04
+#define ZBC_IN_REP_FULL 0x05
+#define ZBC_IN_REP_READONLY 0x06
+#define ZBC_IN_REP_OFFLINE 0x07
+#define ZBC_IN_REP_RESET 0x10
+#define ZBC_IN_REP_NON_SEQ 0x11
+#define ZBC_IN_REP_NON_WP 0x3f
+#define ZBC_IN_REP_MASK 0x3f
+ uint8_t control;
+};
+
+struct scsi_report_zones_desc {
+ uint8_t zone_type;
+#define SRZ_TYPE_CONVENTIONAL 0x01
+#define SRZ_TYPE_SEQ_REQUIRED 0x02
+#define SRZ_TYPE_SEQ_PREFERRED 0x03
+#define SRZ_TYPE_MASK 0x0f
+ uint8_t zone_flags;
+#define SRZ_ZONE_COND_SHIFT 4
+#define SRZ_ZONE_COND_MASK 0xf0
+#define SRZ_ZONE_COND_NWP 0x00
+#define SRZ_ZONE_COND_EMPTY 0x10
+#define SRZ_ZONE_COND_IMP_OPEN 0x20
+#define SRZ_ZONE_COND_EXP_OPEN 0x30
+#define SRZ_ZONE_COND_CLOSED 0x40
+#define SRZ_ZONE_COND_READONLY 0xd0
+#define SRZ_ZONE_COND_FULL 0xe0
+#define SRZ_ZONE_COND_OFFLINE 0xf0
+#define SRZ_ZONE_NON_SEQ 0x02
+#define SRZ_ZONE_RESET 0x01
+ uint8_t reserved[6];
+ uint8_t zone_length[8];
+ uint8_t zone_start_lba[8];
+ uint8_t write_pointer_lba[8];
+ uint8_t reserved2[32];
+};
+
+struct scsi_report_zones_hdr {
+ uint8_t length[4];
+ uint8_t byte4;
+#define SRZ_SAME_ALL_DIFFERENT 0x00 /* Lengths and types vary */
+#define SRZ_SAME_ALL_SAME 0x01 /* Lengths and types the same */
+#define SRZ_SAME_LAST_DIFFERENT 0x02 /* Types same, last length varies */
+#define SRZ_SAME_TYPES_DIFFERENT 0x03 /* Types vary, length the same */
+#define SRZ_SAME_MASK 0x0f
+ uint8_t reserved[3];
+ uint8_t maximum_lba[8];
+ uint8_t reserved2[48];
+ struct scsi_report_zones_desc desc_list[];
+};
+
/*
* Opcodes
*/
@@ -167,6 +245,8 @@
#define VERIFY 0x2f
#define READ_DEFECT_DATA_10 0x37
#define SANITIZE 0x48
+#define ZBC_OUT 0x94
+#define ZBC_IN 0x95
#define READ_DEFECT_DATA_12 0xb7
struct format_defect_list_header
@@ -581,6 +661,38 @@
u_int32_t timeout);
#endif /* !_KERNEL */
+
+void scsi_zbc_out(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, uint8_t service_action, uint64_t zone_id,
+ uint8_t zone_flags, uint8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t sense_len, uint32_t timeout);
+
+void scsi_zbc_in(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, uint8_t service_action,
+ uint64_t zone_start_lba, uint8_t zone_options,
+ uint8_t *data_ptr, uint32_t dxfer_len, uint8_t sense_len,
+ uint32_t timeout);
+
+int scsi_ata_zac_mgmt_out(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, int use_ncq,
+ uint8_t zm_action, uint64_t zone_id,
+ uint8_t zone_flags, uint8_t *data_ptr,
+ uint32_t dxfer_len, uint8_t *cdb_storage,
+ size_t cdb_storage_len, uint8_t sense_len,
+ uint32_t timeout);
+
+int scsi_ata_zac_mgmt_in(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, int use_ncq,
+ uint8_t zm_action, uint64_t zone_id,
+ uint8_t zone_flags, uint8_t *data_ptr,
+ uint32_t dxfer_len, uint8_t *cdb_storage,
+ size_t cdb_storage_len, uint8_t sense_len,
+ uint32_t timeout);
+
__END_DECLS
#endif /* _SCSI_SCSI_DA_H */
Index: sys/cam/scsi/scsi_da.c
===================================================================
--- sys/cam/scsi/scsi_da.c
+++ sys/cam/scsi/scsi_da.c
@@ -46,6 +46,7 @@
#include <sys/cons.h>
#include <sys/endian.h>
#include <sys/proc.h>
+#include <sys/sbuf.h>
#include <geom/geom.h>
#include <geom/geom_disk.h>
#endif /* _KERNEL */
@@ -63,12 +64,20 @@
#include <cam/cam_iosched.h>
#include <cam/scsi/scsi_message.h>
-
-#ifndef _KERNEL
#include <cam/scsi/scsi_da.h>
-#endif /* !_KERNEL */
#ifdef _KERNEL
+/*
+ * Note that there are probe ordering dependencies here. The order isn't
+ * controlled by this enumeration, but by explicit state transitions in
+ * dastart() and dadone(). Here are some of the dependencies:
+ *
+ * 1. RC should come first, before RC16, unless there is evidence that RC16
+ * is supported.
+ * 2. BDC needs to come before any of the ATA probes, or the ZONE probe.
+ * 3. The ATA probes should go in this order:
+ * ATA -> LOGDIR -> IDDIR -> SUP -> ATA_ZONE
+ */
typedef enum {
DA_STATE_PROBE_RC,
DA_STATE_PROBE_RC16,
@@ -76,23 +85,33 @@
DA_STATE_PROBE_BLK_LIMITS,
DA_STATE_PROBE_BDC,
DA_STATE_PROBE_ATA,
+ DA_STATE_PROBE_ATA_LOGDIR,
+ DA_STATE_PROBE_ATA_IDDIR,
+ DA_STATE_PROBE_ATA_SUP,
+ DA_STATE_PROBE_ATA_ZONE,
+ DA_STATE_PROBE_ZONE,
DA_STATE_NORMAL
} da_state;
typedef enum {
- DA_FLAG_PACK_INVALID = 0x001,
- DA_FLAG_NEW_PACK = 0x002,
- DA_FLAG_PACK_LOCKED = 0x004,
- DA_FLAG_PACK_REMOVABLE = 0x008,
- DA_FLAG_NEED_OTAG = 0x020,
- DA_FLAG_WAS_OTAG = 0x040,
- DA_FLAG_RETRY_UA = 0x080,
- DA_FLAG_OPEN = 0x100,
- DA_FLAG_SCTX_INIT = 0x200,
- DA_FLAG_CAN_RC16 = 0x400,
- DA_FLAG_PROBED = 0x800,
- DA_FLAG_DIRTY = 0x1000,
- DA_FLAG_ANNOUNCED = 0x2000
+ DA_FLAG_PACK_INVALID = 0x000001,
+ DA_FLAG_NEW_PACK = 0x000002,
+ DA_FLAG_PACK_LOCKED = 0x000004,
+ DA_FLAG_PACK_REMOVABLE = 0x000008,
+ DA_FLAG_NEED_OTAG = 0x000020,
+ DA_FLAG_WAS_OTAG = 0x000040,
+ DA_FLAG_RETRY_UA = 0x000080,
+ DA_FLAG_OPEN = 0x000100,
+ DA_FLAG_SCTX_INIT = 0x000200,
+ DA_FLAG_CAN_RC16 = 0x000400,
+ DA_FLAG_PROBED = 0x000800,
+ DA_FLAG_DIRTY = 0x001000,
+ DA_FLAG_ANNOUNCED = 0x002000,
+ DA_FLAG_CAN_ATA_DMA = 0x004000,
+ DA_FLAG_CAN_ATA_LOG = 0x008000,
+ DA_FLAG_CAN_ATA_IDLOG = 0x010000,
+ DA_FLAG_CAN_ATA_SUPCAP = 0x020000,
+ DA_FLAG_CAN_ATA_ZONE = 0x040000
} da_flags;
typedef enum {
@@ -103,7 +122,8 @@
DA_Q_4K = 0x08,
DA_Q_NO_RC16 = 0x10,
DA_Q_NO_UNMAP = 0x20,
- DA_Q_RETRY_BUSY = 0x40
+ DA_Q_RETRY_BUSY = 0x40,
+ DA_Q_SMR_DM = 0x80
} da_quirks;
#define DA_Q_BIT_STRING \
@@ -114,7 +134,8 @@
"\0044K" \
"\005NO_RC16" \
"\006NO_UNMAP" \
- "\007RETRY_BUSY"
+ "\007RETRY_BUSY" \
+ "\008SMR_DM"
typedef enum {
DA_CCB_PROBE_RC = 0x01,
@@ -127,8 +148,13 @@
DA_CCB_DUMP = 0x0A,
DA_CCB_DELETE = 0x0B,
DA_CCB_TUR = 0x0C,
- DA_CCB_TYPE_MASK = 0x0F,
- DA_CCB_RETRY_UA = 0x10
+ DA_CCB_PROBE_ZONE = 0x0D,
+ DA_CCB_PROBE_ATA_LOGDIR = 0x0E,
+ DA_CCB_PROBE_ATA_IDDIR = 0x0F,
+ DA_CCB_PROBE_ATA_SUP = 0x10,
+ DA_CCB_PROBE_ATA_ZONE = 0x11,
+ DA_CCB_TYPE_MASK = 0x1F,
+ DA_CCB_RETRY_UA = 0x20
} da_ccb_state;
/*
@@ -152,6 +178,63 @@
DA_DELETE_MAX = DA_DELETE_ZERO
} da_delete_methods;
+/*
+ * For SCSI, host managed drives show up as a separate device type. For
+ * ATA, host managed drives also have a different device signature.
+ * XXX KDM figure out the ATA host managed signature.
+ */
+typedef enum {
+ DA_ZONE_NONE = 0x00,
+ DA_ZONE_DRIVE_MANAGED = 0x01,
+ DA_ZONE_HOST_AWARE = 0x02,
+ DA_ZONE_HOST_MANAGED = 0x03
+} da_zone_mode;
+
+/*
+ * We distinguish between these interface cases in addition to the drive type:
+ * o ATA drive behind a SCSI translation layer that knows about ZBC/ZAC
+ * o ATA drive behind a SCSI translation layer that does not know about
+ * ZBC/ZAC, and so needs to be managed via ATA passthrough. In this
+ * case, we would need to share the ATA code with the ada(4) driver.
+ * o SCSI drive.
+ */
+typedef enum {
+ DA_ZONE_IF_SCSI,
+ DA_ZONE_IF_ATA_PASS,
+ DA_ZONE_IF_ATA_SAT,
+} da_zone_interface;
+
+typedef enum {
+ DA_ZONE_FLAG_RZ_SUP = 0x0001,
+ DA_ZONE_FLAG_OPEN_SUP = 0x0002,
+ DA_ZONE_FLAG_CLOSE_SUP = 0x0004,
+ DA_ZONE_FLAG_FINISH_SUP = 0x0008,
+ DA_ZONE_FLAG_RWP_SUP = 0x0010,
+ DA_ZONE_FLAG_SUP_MASK = (DA_ZONE_FLAG_RZ_SUP |
+ DA_ZONE_FLAG_OPEN_SUP |
+ DA_ZONE_FLAG_CLOSE_SUP |
+ DA_ZONE_FLAG_FINISH_SUP |
+ DA_ZONE_FLAG_RWP_SUP),
+ DA_ZONE_FLAG_URSWRZ = 0x0020,
+ DA_ZONE_FLAG_OPT_SEQ_SET = 0x0040,
+ DA_ZONE_FLAG_OPT_NONSEQ_SET = 0x0080,
+ DA_ZONE_FLAG_MAX_SEQ_SET = 0x0100,
+ DA_ZONE_FLAG_SET_MASK = (DA_ZONE_FLAG_OPT_SEQ_SET |
+ DA_ZONE_FLAG_OPT_NONSEQ_SET |
+ DA_ZONE_FLAG_MAX_SEQ_SET)
+} da_zone_flags;
+
+static struct da_zone_desc {
+ da_zone_flags value;
+ const char *desc;
+} da_zone_desc_table[] = {
+ {DA_ZONE_FLAG_RZ_SUP, "Report Zones" },
+ {DA_ZONE_FLAG_OPEN_SUP, "Open" },
+ {DA_ZONE_FLAG_CLOSE_SUP, "Close" },
+ {DA_ZONE_FLAG_FINISH_SUP, "Finish" },
+ {DA_ZONE_FLAG_RWP_SUP, "Reset Write Pointer" },
+};
+
typedef void da_delete_func_t (struct cam_periph *periph, union ccb *ccb,
struct bio *bp);
static da_delete_func_t da_delete_trim;
@@ -214,7 +297,17 @@
int error_inject;
int trim_max_ranges;
int delete_available; /* Delete methods possibly available */
- u_int maxio;
+ da_zone_mode zone_mode;
+ da_zone_interface zone_interface;
+ da_zone_flags zone_flags;
+ struct ata_gp_log_dir ata_logdir;
+ int valid_logdir_len;
+ struct ata_identify_log_pages ata_iddir;
+ int valid_iddir_len;
+ uint64_t optimal_seq_zones;
+ uint64_t optimal_nonseq_zones;
+ uint64_t max_seq_zones;
+ u_int maxio;
uint32_t unmap_max_ranges;
uint32_t unmap_max_lba; /* Max LBAs in UNMAP req */
uint64_t ws_max_blks;
@@ -1188,6 +1281,15 @@
},
{
/*
+ * Seagate Lamarr 8TB Shingled Magnetic Recording (SMR)
+ * Drive Managed SATA hard drive. This drive doesn't report
+ * in firmware that it is a drive managed SMR drive.
+ */
+ { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST8000AS0002*", "*" },
+ /*quirks*/DA_Q_SMR_DM
+ },
+ {
+ /*
* MX-ES USB Drive by Mach Xtreme
*/
{ T_DIRECT, SIP_MEDIA_REMOVABLE, "MX", "MXUB3*", "*"},
@@ -1204,6 +1306,8 @@
static int dasysctlsofttimeout(SYSCTL_HANDLER_ARGS);
static int dacmdsizesysctl(SYSCTL_HANDLER_ARGS);
static int dadeletemethodsysctl(SYSCTL_HANDLER_ARGS);
+static int dazonemodesysctl(SYSCTL_HANDLER_ARGS);
+static int dazonesupsysctl(SYSCTL_HANDLER_ARGS);
static int dadeletemaxsysctl(SYSCTL_HANDLER_ARGS);
static void dadeletemethodset(struct da_softc *softc,
da_delete_methods delete_method);
@@ -1217,6 +1321,7 @@
static periph_dtor_t dacleanup;
static periph_start_t dastart;
static periph_oninv_t daoninvalidate;
+static void dazonedone(struct cam_periph *periph, union ccb *ccb);
static void dadone(struct cam_periph *periph,
union ccb *done_ccb);
static int daerror(union ccb *ccb, u_int32_t cam_flags,
@@ -1447,6 +1552,14 @@
CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dastrategy(%p)\n", bp));
/*
+ * Zone commands must be ordered, because they can depend on the
+ * effects of previously issued commands, and they may affect
+ * commands after them.
+ */
+ if (bp->bio_cmd == BIO_ZONE)
+ bp->bio_flags |= BIO_ORDERED;
+
+ /*
* Place it in the queue of disk activities for this disk
*/
cam_iosched_queue_work(softc->cam_iosched, bp);
@@ -1678,7 +1791,8 @@
break;
if (SID_TYPE(&cgd->inq_data) != T_DIRECT
&& SID_TYPE(&cgd->inq_data) != T_RBC
- && SID_TYPE(&cgd->inq_data) != T_OPTICAL)
+ && SID_TYPE(&cgd->inq_data) != T_OPTICAL
+ && SID_TYPE(&cgd->inq_data) != T_ZBC_HM)
break;
/*
@@ -1829,6 +1943,29 @@
&softc->minimum_cmd_size, 0, dacmdsizesysctl, "I",
"Minimum CDB size");
+ SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree),
+ OID_AUTO, "zone_mode", CTLTYPE_STRING | CTLFLAG_RD,
+ softc, 0, dazonemodesysctl, "A",
+ "Zone Mode");
+ SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree),
+ OID_AUTO, "zone_support", CTLTYPE_STRING | CTLFLAG_RD,
+ softc, 0, dazonesupsysctl, "A",
+ "Zone Support");
+ SYSCTL_ADD_UQUAD(&softc->sysctl_ctx,
+ SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO,
+ "optimal_seq_zones", CTLFLAG_RD, &softc->optimal_seq_zones,
+ "Optimal Number of Open Sequential Write Preferred Zones");
+ SYSCTL_ADD_UQUAD(&softc->sysctl_ctx,
+ SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO,
+ "optimal_nonseq_zones", CTLFLAG_RD,
+ &softc->optimal_nonseq_zones,
+ "Optimal Number of Non-Sequentially Written Sequential Write "
+ "Preferred Zones");
+ SYSCTL_ADD_UQUAD(&softc->sysctl_ctx,
+ SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO,
+ "max_seq_zones", CTLFLAG_RD, &softc->max_seq_zones,
+ "Maximum Number of Open Sequential Write Required Zones");
+
SYSCTL_ADD_INT(&softc->sysctl_ctx,
SYSCTL_CHILDREN(softc->sysctl_tree),
OID_AUTO,
@@ -2147,6 +2284,72 @@
return (0);
}
+static int
+dazonemodesysctl(SYSCTL_HANDLER_ARGS)
+{
+ char tmpbuf[40];
+ struct da_softc *softc;
+ int error;
+
+ softc = (struct da_softc *)arg1;
+
+ switch (softc->zone_mode) {
+ case DA_ZONE_DRIVE_MANAGED:
+ snprintf(tmpbuf, sizeof(tmpbuf), "Drive Managed");
+ break;
+ case DA_ZONE_HOST_AWARE:
+ snprintf(tmpbuf, sizeof(tmpbuf), "Host Aware");
+ break;
+ case DA_ZONE_HOST_MANAGED:
+ snprintf(tmpbuf, sizeof(tmpbuf), "Host Managed");
+ break;
+ case DA_ZONE_NONE:
+ default:
+ snprintf(tmpbuf, sizeof(tmpbuf), "Not Zoned");
+ break;
+ }
+
+ error = sysctl_handle_string(oidp, tmpbuf, sizeof(tmpbuf), req);
+
+ return (error);
+}
+
+static int
+dazonesupsysctl(SYSCTL_HANDLER_ARGS)
+{
+ char tmpbuf[180];
+ struct da_softc *softc;
+ struct sbuf sb;
+ int error, first;
+ unsigned int i;
+
+ softc = (struct da_softc *)arg1;
+
+ error = 0;
+ first = 1;
+ sbuf_new(&sb, tmpbuf, sizeof(tmpbuf), 0);
+
+ for (i = 0; i < sizeof(da_zone_desc_table) /
+ sizeof(da_zone_desc_table[0]); i++) {
+ if (softc->zone_flags & da_zone_desc_table[i].value) {
+ if (first == 0)
+ sbuf_printf(&sb, ", ");
+ else
+ first = 0;
+ sbuf_cat(&sb, da_zone_desc_table[i].desc);
+ }
+ }
+
+ if (first == 1)
+ sbuf_printf(&sb, "None");
+
+ sbuf_finish(&sb);
+
+ error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
+
+ return (error);
+}
+
static cam_status
daregister(struct cam_periph *periph, void *arg)
{
@@ -2211,6 +2414,23 @@
if (cpi.ccb_h.status == CAM_REQ_CMP && (cpi.hba_misc & PIM_NO_6_BYTE))
softc->quirks |= DA_Q_NO_6_BYTE;
+ if (SID_TYPE(&cgd->inq_data) == T_ZBC_HM)
+ softc->zone_mode = DA_ZONE_HOST_MANAGED;
+ else if (softc->quirks & DA_Q_SMR_DM)
+ softc->zone_mode = DA_ZONE_DRIVE_MANAGED;
+ else
+ softc->zone_mode = DA_ZONE_NONE;
+
+ if (softc->zone_mode != DA_ZONE_NONE) {
+ if (scsi_vpd_supported_page(periph, SVPD_ATA_INFORMATION)) {
+ if (scsi_vpd_supported_page(periph, SVPD_ZONED_BDC))
+ softc->zone_interface = DA_ZONE_IF_ATA_SAT;
+ else
+ softc->zone_interface = DA_ZONE_IF_ATA_PASS;
+ } else
+ softc->zone_interface = DA_ZONE_IF_SCSI;
+ }
+
TASK_INIT(&softc->sysctl_task, 0, dasysctlinit, periph);
/*
@@ -2292,7 +2512,7 @@
softc->maxio = cpi.maxio;
softc->disk->d_maxsize = softc->maxio;
softc->disk->d_unit = periph->unit_number;
- softc->disk->d_flags = DISKFLAG_DIRECT_COMPLETION;
+ softc->disk->d_flags = DISKFLAG_DIRECT_COMPLETION | DISKFLAG_CANZONE;
if ((softc->quirks & DA_Q_NO_SYNC_CACHE) == 0)
softc->disk->d_flags |= DISKFLAG_CANFLUSHCACHE;
if ((cpi.hba_misc & PIM_UNMAPPED) != 0) {
@@ -2360,6 +2580,300 @@
return(CAM_REQ_CMP);
}
+static int
+da_zone_bio_to_scsi(int disk_zone_cmd)
+{
+ switch (disk_zone_cmd) {
+ case DISK_ZONE_OPEN:
+ return ZBC_OUT_SA_OPEN;
+ case DISK_ZONE_CLOSE:
+ return ZBC_OUT_SA_CLOSE;
+ case DISK_ZONE_FINISH:
+ return ZBC_OUT_SA_FINISH;
+ case DISK_ZONE_RWP:
+ return ZBC_OUT_SA_RWP;
+ }
+
+ return -1;
+}
+
+static int
+da_zone_cmd(struct cam_periph *periph, union ccb *ccb, struct bio *bp,
+ int *queue_ccb)
+{
+ struct da_softc *softc;
+ int error;
+
+ error = 0;
+
+ if (bp->bio_cmd != BIO_ZONE) {
+ error = EINVAL;
+ goto bailout;
+ }
+
+ softc = periph->softc;
+
+ switch (bp->bio_zone.zone_cmd) {
+ case DISK_ZONE_OPEN:
+ case DISK_ZONE_CLOSE:
+ case DISK_ZONE_FINISH:
+ case DISK_ZONE_RWP: {
+ int zone_flags;
+ int zone_sa;
+ uint64_t lba;
+
+ zone_sa = da_zone_bio_to_scsi(bp->bio_zone.zone_cmd);
+ if (zone_sa == -1) {
+ xpt_print(periph->path, "Cannot translate zone "
+ "cmd %#x to SCSI\n", bp->bio_zone.zone_cmd);
+ error = EINVAL;
+ goto bailout;
+ }
+
+ zone_flags = 0;
+ lba = bp->bio_zone.zone_params.rwp.id;
+
+ if (bp->bio_zone.zone_params.rwp.flags &
+ DISK_ZONE_RWP_FLAG_ALL)
+ zone_flags |= ZBC_OUT_ALL;
+
+ if (softc->zone_interface != DA_ZONE_IF_ATA_PASS) {
+ scsi_zbc_out(&ccb->csio,
+ /*retries*/ da_retry_count,
+ /*cbfcnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*service_action*/ zone_sa,
+ /*zone_id*/ lba,
+ /*zone_flags*/ zone_flags,
+ /*data_ptr*/ NULL,
+ /*dxfer_len*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ da_default_timeout * 1000);
+ } else {
+ /*
+ * Note that in this case, even though we can
+ * technically use NCQ, we don't bother for several
+ * reasons:
+ * 1. It hasn't been tested on a SAT layer that
+ * supports it. This is new as of SAT-4.
+ * 2. Even when there is a SAT layer that supports
+ * it, that SAT layer will also probably support
+ * ZBC -> ZAC translation, since they are both
+ * in the SAT-4 spec.
+ * 3. Translation will likely be preferable to ATA
+ * passthrough. LSI / Avago at least single
+ * steps ATA passthrough commands in the HBA,
+ * regardless of protocol, so unless that
+ * changes, there is a performance penalty for
+ * doing ATA passthrough no matter whether
+ * you're using NCQ/FPDMA, DMA or PIO.
+ * 4. It requires a 32-byte CDB, which at least at
+ * this point in CAM requires a CDB pointer, which
+ * would require us to allocate an additional bit
+ * of storage separate from the CCB.
+ */
+ error = scsi_ata_zac_mgmt_out(&ccb->csio,
+ /*retries*/ da_retry_count,
+ /*cbfcnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*use_ncq*/ 0,
+ /*zm_action*/ zone_sa,
+ /*zone_id*/ lba,
+ /*zone_flags*/ zone_flags,
+ /*data_ptr*/ NULL,
+ /*dxfer_len*/ 0,
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ da_default_timeout * 1000);
+ if (error != 0) {
+ error = EINVAL;
+ xpt_print(periph->path,
+ "scsi_ata_zac_mgmt_out() returned an "
+ "error!");
+ goto bailout;
+ }
+ }
+ *queue_ccb = 1;
+
+ break;
+ }
+ case DISK_ZONE_REPORT_ZONES: {
+ uint8_t *rz_ptr;
+ uint32_t num_entries, alloc_size;
+ struct disk_zone_report *rep;
+
+ rep = &bp->bio_zone.zone_params.report;
+
+ num_entries = rep->entries_allocated;
+ if (num_entries == 0) {
+ xpt_print(periph->path, "No entries allocated for "
+ "Report Zones request\n");
+ error = EINVAL;
+ goto bailout;
+ }
+ alloc_size = sizeof(struct scsi_report_zones_hdr) +
+ (sizeof(struct scsi_report_zones_desc) * num_entries);
+ alloc_size = min(alloc_size, softc->disk->d_maxsize);
+ rz_ptr = malloc(alloc_size, M_SCSIDA, M_NOWAIT | M_ZERO);
+ if (rz_ptr == NULL) {
+ xpt_print(periph->path, "Unable to allocate memory "
+ "for Report Zones request\n");
+ error = ENOMEM;
+ goto bailout;
+ }
+
+ if (softc->zone_interface != DA_ZONE_IF_ATA_PASS) {
+ scsi_zbc_in(&ccb->csio,
+ /*retries*/ da_retry_count,
+ /*cbcfnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*service_action*/ ZBC_IN_SA_REPORT_ZONES,
+ /*zone_start_lba*/ rep->starting_id,
+ /*zone_options*/ rep->rep_options,
+ /*data_ptr*/ rz_ptr,
+ /*dxfer_len*/ alloc_size,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ da_default_timeout * 1000);
+ } else {
+ /*
+ * Note that in this case, even though we can
+ * technically use NCQ, we don't bother for several
+ * reasons:
+ * 1. It hasn't been tested on a SAT layer that
+ * supports it. This is new as of SAT-4.
+ * 2. Even when there is a SAT layer that supports
+ * it, that SAT layer will also probably support
+ * ZBC -> ZAC translation, since they are both
+ * in the SAT-4 spec.
+ * 3. Translation will likely be preferable to ATA
+ * passthrough. LSI / Avago at least single
+ * steps ATA passthrough commands in the HBA,
+ * regardless of protocol, so unless that
+ * changes, there is a performance penalty for
+ * doing ATA passthrough no matter whether
+ * you're using NCQ/FPDMA, DMA or PIO.
+ * 4. It requires a 32-byte CDB, which at least at
+ * this point in CAM requires a CDB pointer, which
+ * would require us to allocate an additional bit
+ * of storage separate from the CCB.
+ */
+ error = scsi_ata_zac_mgmt_in(&ccb->csio,
+ /*retries*/ da_retry_count,
+ /*cbcfnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*use_ncq*/ 0,
+ /*zm_action*/ ATA_ZM_REPORT_ZONES,
+ /*zone_id*/ rep->starting_id,
+ /*zone_flags*/ rep->rep_options,
+ /*data_ptr*/ rz_ptr,
+ /*dxfer_len*/ alloc_size,
+ /*cdb_storage*/ NULL,
+ /*cdb_storage_len*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ da_default_timeout * 1000);
+ if (error != 0) {
+ error = EINVAL;
+ xpt_print(periph->path,
+ "scsi_ata_zac_mgmt_in() returned an "
+ "error!");
+ goto bailout;
+ }
+ }
+
+ /*
+ * For BIO_ZONE, this isn't normally needed. However, it
+ * is used by devstat_end_transaction_bio() to determine
+ * how much data was transferred.
+ */
+ /*
+ * XXX KDM we have a problem. But I'm not sure how to fix
+ * it. devstat uses bio_bcount - bio_resid to calculate
+ * the amount of data transferred. The GEOM disk code
+ * uses bio_length - bio_resid to calculate the amount of
+ * data in bio_completed. We have different structure
+ * sizes above and below the ada(4) driver. So, if we
+ * use the sizes above, the amount transferred won't be
+ * quite accurate for devstat. If we use different sizes
+ * for bio_bcount and bio_length (above and below
+ * respectively), then the residual needs to match one or
+ * the other. Everything is calculated after the bio
+ * leaves the driver, so changing the values around isn't
+ * really an option. For now, just set the count to the
+ * passed in length. This means that the calculations
+ * above (e.g. bio_completed) will be correct, but the
+ * amount of data reported to devstat will be slightly
+ * under or overstated.
+ */
+ bp->bio_bcount = bp->bio_length;
+
+ *queue_ccb = 1;
+
+ break;
+ }
+ case DISK_ZONE_GET_PARAMS: {
+ struct disk_zone_disk_params *params;
+
+ params = &bp->bio_zone.zone_params.disk_params;
+ bzero(params, sizeof(*params));
+
+ switch (softc->zone_mode) {
+ case DA_ZONE_DRIVE_MANAGED:
+ params->zone_mode = DISK_ZONE_MODE_DRIVE_MANAGED;
+ break;
+ case DA_ZONE_HOST_AWARE:
+ params->zone_mode = DISK_ZONE_MODE_HOST_AWARE;
+ break;
+ case DA_ZONE_HOST_MANAGED:
+ params->zone_mode = DISK_ZONE_MODE_HOST_MANAGED;
+ break;
+ default:
+ case DA_ZONE_NONE:
+ params->zone_mode = DISK_ZONE_MODE_NONE;
+ break;
+ }
+
+ if (softc->zone_flags & DA_ZONE_FLAG_URSWRZ)
+ params->flags |= DISK_ZONE_DISK_URSWRZ;
+
+ if (softc->zone_flags & DA_ZONE_FLAG_OPT_SEQ_SET) {
+ params->optimal_seq_zones = softc->optimal_seq_zones;
+ params->flags |= DISK_ZONE_OPT_SEQ_SET;
+ }
+
+ if (softc->zone_flags & DA_ZONE_FLAG_OPT_NONSEQ_SET) {
+ params->optimal_nonseq_zones =
+ softc->optimal_nonseq_zones;
+ params->flags |= DISK_ZONE_OPT_NONSEQ_SET;
+ }
+
+ if (softc->zone_flags & DA_ZONE_FLAG_MAX_SEQ_SET) {
+ params->max_seq_zones = softc->max_seq_zones;
+ params->flags |= DISK_ZONE_MAX_SEQ_SET;
+ }
+ if (softc->zone_flags & DA_ZONE_FLAG_RZ_SUP)
+ params->flags |= DISK_ZONE_RZ_SUP;
+
+ if (softc->zone_flags & DA_ZONE_FLAG_OPEN_SUP)
+ params->flags |= DISK_ZONE_OPEN_SUP;
+
+ if (softc->zone_flags & DA_ZONE_FLAG_CLOSE_SUP)
+ params->flags |= DISK_ZONE_CLOSE_SUP;
+
+ if (softc->zone_flags & DA_ZONE_FLAG_FINISH_SUP)
+ params->flags |= DISK_ZONE_FINISH_SUP;
+
+ if (softc->zone_flags & DA_ZONE_FLAG_RWP_SUP)
+ params->flags |= DISK_ZONE_RWP_SUP;
+ break;
+ }
+ default:
+ break;
+ }
+bailout:
+ return (error);
+}
+
static void
dastart(struct cam_periph *periph, union ccb *start_ccb)
{
@@ -2473,7 +2987,21 @@
SSD_FULL_SIZE,
da_default_timeout*1000);
break;
+ case BIO_ZONE: {
+ int error, queue_ccb;
+
+ queue_ccb = 0;
+
+ error = da_zone_cmd(periph, start_ccb, bp,&queue_ccb);
+ if ((error != 0)
+ || (queue_ccb == 0)) {
+ biofinish(bp, NULL, error);
+ xpt_release_ccb(start_ccb);
+ return;
+ }
+ break;
}
+ }
start_ccb->ccb_h.ccb_state = DA_CCB_BUFFER_IO;
start_ccb->ccb_h.flags |= CAM_UNLOCKED;
start_ccb->ccb_h.softtimeout = sbttotv(da_default_softtimeout);
@@ -2663,15 +3191,28 @@
struct ata_params *ata_params;
if (!scsi_vpd_supported_page(periph, SVPD_ATA_INFORMATION)) {
+ if ((softc->zone_mode == DA_ZONE_HOST_AWARE)
+ || (softc->zone_mode == DA_ZONE_HOST_MANAGED)) {
+ /*
+ * Note that if the ATA VPD page isn't
+ * supported, we aren't talking to an ATA
+ * device anyway. Support for that VPD
+ * page is mandatory for SCSI to ATA (SAT)
+ * translation layers.
+ */
+ softc->state = DA_STATE_PROBE_ZONE;
+ goto skipstate;
+ }
daprobedone(periph, start_ccb);
break;
}
ata_params = (struct ata_params*)
- malloc(sizeof(*ata_params), M_SCSIDA, M_NOWAIT|M_ZERO);
+ malloc(sizeof(*ata_params), M_SCSIDA,M_NOWAIT|M_ZERO);
if (ata_params == NULL) {
- printf("dastart: Couldn't malloc ata_params data\n");
+ xpt_print(periph->path, "Couldn't malloc ata_params "
+ "data\n");
/* da_free_periph??? */
break;
}
@@ -2689,7 +3230,253 @@
xpt_action(start_ccb);
break;
}
+ case DA_STATE_PROBE_ATA_LOGDIR:
+ {
+ struct ata_gp_log_dir *log_dir;
+ int retval;
+
+ retval = 0;
+
+ if ((softc->flags & DA_FLAG_CAN_ATA_LOG) == 0) {
+ /*
+ * If we don't have log support, not much point in
+ * trying to probe zone support.
+ */
+ daprobedone(periph, start_ccb);
+ break;
+ }
+
+ /*
+ * If we have an ATA device (the SCSI ATA Information VPD
+ * page should be present and the ATA identify should have
+ * succeeded) and it supports logs, ask for the log directory.
+ */
+
+ log_dir = malloc(sizeof(*log_dir), M_SCSIDA, M_NOWAIT|M_ZERO);
+ if (log_dir == NULL) {
+ xpt_print(periph->path, "Couldn't malloc log_dir "
+ "data\n");
+ daprobedone(periph, start_ccb);
+ break;
+ }
+
+ retval = scsi_ata_read_log(&start_ccb->csio,
+ /*retries*/ da_retry_count,
+ /*cbfcnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*log_address*/ ATA_LOG_DIRECTORY,
+ /*page_number*/ 0,
+ /*block_count*/ 1,
+ /*protocol*/ softc->flags & DA_FLAG_CAN_ATA_DMA ?
+ AP_PROTO_DMA : AP_PROTO_PIO_IN,
+ /*data_ptr*/ (uint8_t *)log_dir,
+ /*dxfer_len*/ sizeof(*log_dir),
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ da_default_timeout * 1000);
+
+ if (retval != 0) {
+ xpt_print(periph->path, "scsi_ata_read_log() failed!");
+ free(log_dir, M_SCSIDA);
+ daprobedone(periph, start_ccb);
+ break;
+ }
+ start_ccb->ccb_h.ccb_bp = NULL;
+ start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_ATA_LOGDIR;
+ xpt_action(start_ccb);
+ break;
}
+ case DA_STATE_PROBE_ATA_IDDIR:
+ {
+ struct ata_identify_log_pages *id_dir;
+ int retval;
+
+ retval = 0;
+
+ /*
+ * Check here to see whether the Identify Device log is
+ * supported in the directory of logs. If so, continue
+ * with requesting the log of identify device pages.
+ */
+ if ((softc->flags & DA_FLAG_CAN_ATA_IDLOG) == 0) {
+ daprobedone(periph, start_ccb);
+ break;
+ }
+
+ id_dir = malloc(sizeof(*id_dir), M_SCSIDA, M_NOWAIT | M_ZERO);
+ if (id_dir == NULL) {
+ xpt_print(periph->path, "Couldn't malloc id_dir "
+ "data\n");
+ daprobedone(periph, start_ccb);
+ break;
+ }
+
+ retval = scsi_ata_read_log(&start_ccb->csio,
+ /*retries*/ da_retry_count,
+ /*cbfcnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*log_address*/ ATA_IDENTIFY_DATA_LOG,
+ /*page_number*/ ATA_IDL_PAGE_LIST,
+ /*block_count*/ 1,
+ /*protocol*/ softc->flags & DA_FLAG_CAN_ATA_DMA ?
+ AP_PROTO_DMA : AP_PROTO_PIO_IN,
+ /*data_ptr*/ (uint8_t *)id_dir,
+ /*dxfer_len*/ sizeof(*id_dir),
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ da_default_timeout * 1000);
+
+ if (retval != 0) {
+ xpt_print(periph->path, "scsi_ata_read_log() failed!");
+ free(id_dir, M_SCSIDA);
+ daprobedone(periph, start_ccb);
+ break;
+ }
+ start_ccb->ccb_h.ccb_bp = NULL;
+ start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_ATA_IDDIR;
+ xpt_action(start_ccb);
+ break;
+ }
+ case DA_STATE_PROBE_ATA_SUP:
+ {
+ struct ata_identify_log_sup_cap *sup_cap;
+ int retval;
+
+ retval = 0;
+
+ /*
+ * Check here to see whether the Supported Capabilities log
+ * is in the list of Identify Device logs.
+ */
+ if ((softc->flags & DA_FLAG_CAN_ATA_SUPCAP) == 0) {
+ daprobedone(periph, start_ccb);
+ break;
+ }
+
+ sup_cap = malloc(sizeof(*sup_cap), M_SCSIDA, M_NOWAIT|M_ZERO);
+ if (sup_cap == NULL) {
+ xpt_print(periph->path, "Couldn't malloc sup_cap "
+ "data\n");
+ daprobedone(periph, start_ccb);
+ break;
+ }
+
+ retval = scsi_ata_read_log(&start_ccb->csio,
+ /*retries*/ da_retry_count,
+ /*cbfcnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*log_address*/ ATA_IDENTIFY_DATA_LOG,
+ /*page_number*/ ATA_IDL_SUP_CAP,
+ /*block_count*/ 1,
+ /*protocol*/ softc->flags & DA_FLAG_CAN_ATA_DMA ?
+ AP_PROTO_DMA : AP_PROTO_PIO_IN,
+ /*data_ptr*/ (uint8_t *)sup_cap,
+ /*dxfer_len*/ sizeof(*sup_cap),
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ da_default_timeout * 1000);
+
+ if (retval != 0) {
+ xpt_print(periph->path, "scsi_ata_read_log() failed!");
+ free(sup_cap, M_SCSIDA);
+ daprobedone(periph, start_ccb);
+ break;
+
+ }
+
+ start_ccb->ccb_h.ccb_bp = NULL;
+ start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_ATA_SUP;
+ xpt_action(start_ccb);
+ break;
+ }
+ case DA_STATE_PROBE_ATA_ZONE:
+ {
+ struct ata_zoned_info_log *ata_zone;
+ int retval;
+
+ retval = 0;
+
+ /*
+ * Check here to see whether the zoned device information
+ * page is supported. If so, continue on to request it.
+ * If not, skip to DA_STATE_PROBE_LOG or done.
+ */
+ if ((softc->flags & DA_FLAG_CAN_ATA_ZONE) == 0) {
+ daprobedone(periph, start_ccb);
+ break;
+ }
+ ata_zone = malloc(sizeof(*ata_zone), M_SCSIDA,
+ M_NOWAIT|M_ZERO);
+ if (ata_zone == NULL) {
+ xpt_print(periph->path, "Couldn't malloc ata_zone "
+ "data\n");
+ daprobedone(periph, start_ccb);
+ break;
+ }
+
+ retval = scsi_ata_read_log(&start_ccb->csio,
+ /*retries*/ da_retry_count,
+ /*cbfcnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*log_address*/ ATA_IDENTIFY_DATA_LOG,
+ /*page_number*/ ATA_IDL_ZDI,
+ /*block_count*/ 1,
+ /*protocol*/ softc->flags & DA_FLAG_CAN_ATA_DMA ?
+ AP_PROTO_DMA : AP_PROTO_PIO_IN,
+ /*data_ptr*/ (uint8_t *)ata_zone,
+ /*dxfer_len*/ sizeof(*ata_zone),
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ da_default_timeout * 1000);
+
+ if (retval != 0) {
+ xpt_print(periph->path, "scsi_ata_read_log() failed!");
+ free(ata_zone, M_SCSIDA);
+ daprobedone(periph, start_ccb);
+ break;
+ }
+ start_ccb->ccb_h.ccb_bp = NULL;
+ start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_ATA_ZONE;
+ xpt_action(start_ccb);
+
+ break;
+ }
+ case DA_STATE_PROBE_ZONE:
+ {
+ struct scsi_vpd_zoned_bdc *bdc;
+
+ /*
+ * Note that this page will be supported for SCSI protocol
+ * devices that support ZBC (SMR devices), as well as ATA
+ * protocol devices that are behind a SAT (SCSI to ATA
+ * Translation) layer that supports converting ZBC commands
+ * to their ZAC equivalents.
+ */
+ if (!scsi_vpd_supported_page(periph, SVPD_ZONED_BDC)) {
+ daprobedone(periph, start_ccb);
+ break;
+ }
+ bdc = (struct scsi_vpd_zoned_bdc *)
+ malloc(sizeof(*bdc), M_SCSIDA, M_NOWAIT|M_ZERO);
+
+ if (bdc == NULL) {
+ xpt_release_ccb(start_ccb);
+ xpt_print(periph->path, "Couldn't malloc zone VPD "
+ "data\n");
+ break;
+ }
+ scsi_inquiry(&start_ccb->csio,
+ /*retries*/da_retry_count,
+ /*cbfcnp*/dadone,
+ /*tag_action*/MSG_SIMPLE_Q_TAG,
+ /*inq_buf*/(u_int8_t *)bdc,
+ /*inq_len*/sizeof(*bdc),
+ /*evpd*/TRUE,
+ /*page_code*/SVPD_ZONED_BDC,
+ /*sense_len*/SSD_FULL_SIZE,
+ /*timeout*/da_default_timeout * 1000);
+ start_ccb->ccb_h.ccb_bp = NULL;
+ start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_ZONE;
+ xpt_action(start_ccb);
+ break;
+ }
+ }
}
/*
@@ -3053,6 +3840,153 @@
}
static void
+dazonedone(struct cam_periph *periph, union ccb *ccb)
+{
+ struct da_softc *softc;
+ struct bio *bp;
+
+ softc = periph->softc;
+ bp = (struct bio *)ccb->ccb_h.ccb_bp;
+
+ switch (bp->bio_zone.zone_cmd) {
+ case DISK_ZONE_OPEN:
+ case DISK_ZONE_CLOSE:
+ case DISK_ZONE_FINISH:
+ case DISK_ZONE_RWP:
+ break;
+ case DISK_ZONE_REPORT_ZONES: {
+ uint32_t avail_len;
+ struct disk_zone_report *rep;
+ struct scsi_report_zones_hdr *hdr;
+ struct scsi_report_zones_desc *desc;
+ struct disk_zone_rep_entry *entry;
+ uint32_t num_alloced, hdr_len, num_avail;
+ uint32_t num_to_fill, i;
+ int ata;
+
+ rep = &bp->bio_zone.zone_params.report;
+ avail_len = ccb->csio.dxfer_len - ccb->csio.resid;
+ /*
+ * Note that bio_resid isn't normally used for zone
+ * commands, but it is used by devstat_end_transaction_bio()
+ * to determine how much data was transferred. Because
+ * the size of the SCSI/ATA data structures is different
+ * than the size of the BIO interface structures, the
+ * amount of data actually transferred from the drive will
+ * be different than the amount of data transferred to
+ * the user.
+ */
+ bp->bio_resid = ccb->csio.resid;
+ num_alloced = rep->entries_allocated;
+ hdr = (struct scsi_report_zones_hdr *)ccb->csio.data_ptr;
+ if (avail_len < sizeof(*hdr)) {
+ /*
+ * Is there a better error than EIO here? We asked
+ * for at least the header, and we got less than
+ * that.
+ */
+ bp->bio_error = EIO;
+ bp->bio_flags |= BIO_ERROR;
+ bp->bio_resid = bp->bio_bcount;
+ break;
+ }
+
+ if (softc->zone_interface == DA_ZONE_IF_ATA_PASS)
+ ata = 1;
+ else
+ ata = 0;
+
+ hdr_len = ata ? le32dec(hdr->length) :
+ scsi_4btoul(hdr->length);
+ if (hdr_len > 0)
+ rep->entries_available = hdr_len / sizeof(*desc);
+ else
+ rep->entries_available = 0;
+ /*
+ * NOTE: using the same values for the BIO version of the
+ * same field as the SCSI/ATA values. This means we could
+ * get some additional values that aren't defined in bio.h
+ * if more values of the same field are defined later.
+ */
+ rep->header.same = hdr->byte4 & SRZ_SAME_MASK;
+ rep->header.maximum_lba = ata ? le64dec(hdr->maximum_lba) :
+ scsi_8btou64(hdr->maximum_lba);
+ /*
+ * If the drive reports no entries that match the query,
+ * we're done.
+ */
+ if (hdr_len == 0) {
+ rep->entries_filled = 0;
+ break;
+ }
+
+ num_avail = min((avail_len - sizeof(*hdr)) / sizeof(*desc),
+ hdr_len / sizeof(*desc));
+ /*
+ * If the drive didn't return any data, then we're done.
+ */
+ if (num_avail == 0) {
+ rep->entries_filled = 0;
+ break;
+ }
+
+ num_to_fill = min(num_avail, rep->entries_allocated);
+ /*
+ * If the user didn't allocate any entries for us to fill,
+ * we're done.
+ */
+ if (num_to_fill == 0) {
+ rep->entries_filled = 0;
+ break;
+ }
+
+ for (i = 0, desc = &hdr->desc_list[0], entry=&rep->entries[0];
+ i < num_to_fill; i++, desc++, entry++) {
+ /*
+ * NOTE: we're mapping the values here directly
+ * from the SCSI/ATA bit definitions to the bio.h
+ * definitons. There is also a warning in
+ * disk_zone.h, but the impact is that if
+ * additional values are added in the SCSI/ATA
+ * specs these will be visible to consumers of
+ * this interface.
+ */
+ entry->zone_type = desc->zone_type & SRZ_TYPE_MASK;
+ entry->zone_condition =
+ (desc->zone_flags & SRZ_ZONE_COND_MASK) >>
+ SRZ_ZONE_COND_SHIFT;
+ entry->zone_flags |= desc->zone_flags &
+ (SRZ_ZONE_NON_SEQ|SRZ_ZONE_RESET);
+ entry->zone_length =
+ ata ? le64dec(desc->zone_length) :
+ scsi_8btou64(desc->zone_length);
+ entry->zone_start_lba =
+ ata ? le64dec(desc->zone_start_lba) :
+ scsi_8btou64(desc->zone_start_lba);
+ entry->write_pointer_lba =
+ ata ? le64dec(desc->write_pointer_lba) :
+ scsi_8btou64(desc->write_pointer_lba);
+ }
+ rep->entries_filled = num_to_fill;
+ break;
+ }
+ case DISK_ZONE_GET_PARAMS:
+ default:
+ /*
+ * In theory we should not get a GET_PARAMS bio, since it
+ * should be handled without queueing the command to the
+ * drive.
+ */
+ panic("%s: Invalid zone command %d", __func__,
+ bp->bio_zone.zone_cmd);
+ break;
+ }
+
+ if (bp->bio_zone.zone_cmd == DISK_ZONE_REPORT_ZONES)
+ free(ccb->csio.data_ptr, M_SCSIDA);
+}
+
+static void
dadone(struct cam_periph *periph, union ccb *done_ccb)
{
struct da_softc *softc;
@@ -3147,11 +4081,14 @@
} else if (bp != NULL) {
if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0)
panic("REQ_CMP with QFRZN");
- if (state == DA_CCB_DELETE)
+ if (bp->bio_cmd == BIO_ZONE)
+ dazonedone(periph, done_ccb);
+ else if (state == DA_CCB_DELETE)
bp->bio_resid = 0;
else
bp->bio_resid = csio->resid;
- if (csio->resid > 0)
+ if ((csio->resid > 0)
+ && (bp->bio_cmd != BIO_ZONE))
bp->bio_flags |= BIO_ERROR;
if (softc->error_inject != 0) {
bp->bio_error = softc->error_inject;
@@ -3569,11 +4506,14 @@
}
case DA_CCB_PROBE_BDC:
{
- struct scsi_vpd_block_characteristics *bdc;
+ struct scsi_vpd_block_device_characteristics *bdc;
- bdc = (struct scsi_vpd_block_characteristics *)csio->data_ptr;
+ bdc = (struct scsi_vpd_block_device_characteristics *)
+ csio->data_ptr;
if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ uint32_t valid_len;
+
/*
* Disable queue sorting for non-rotational media
* by default.
@@ -3580,16 +4520,55 @@
*/
u_int16_t old_rate = softc->disk->d_rotation_rate;
- softc->disk->d_rotation_rate =
- scsi_2btoul(bdc->medium_rotation_rate);
- if (softc->disk->d_rotation_rate ==
- SVPD_BDC_RATE_NON_ROTATING) {
- cam_iosched_set_sort_queue(softc->cam_iosched, 0);
- softc->rotating = 0;
+ valid_len = csio->dxfer_len - csio->resid;
+ if (SBDC_IS_PRESENT(bdc, valid_len,
+ medium_rotation_rate)) {
+ softc->disk->d_rotation_rate =
+ scsi_2btoul(bdc->medium_rotation_rate);
+ if (softc->disk->d_rotation_rate ==
+ SVPD_BDC_RATE_NON_ROTATING) {
+ cam_iosched_set_sort_queue(
+ softc->cam_iosched, 0);
+ softc->rotating = 0;
+ }
+ if (softc->disk->d_rotation_rate != old_rate) {
+ disk_attr_changed(softc->disk,
+ "GEOM::rotation_rate", M_NOWAIT);
+ }
}
- if (softc->disk->d_rotation_rate != old_rate) {
- disk_attr_changed(softc->disk,
- "GEOM::rotation_rate", M_NOWAIT);
+ if ((SBDC_IS_PRESENT(bdc, valid_len, flags))
+ && (softc->zone_mode == DA_ZONE_NONE)) {
+ int ata_proto;
+
+ if (scsi_vpd_supported_page(periph,
+ SVPD_ATA_INFORMATION))
+ ata_proto = 1;
+ else
+ ata_proto = 0;
+
+ /*
+ * The Zoned field will only be set for
+ * Drive Managed and Host Aware drives. If
+ * they are Host Managed, the device type
+ * in the standard INQUIRY data should be
+ * set to T_ZBC_HM (0x14).
+ */
+ if ((bdc->flags & SVPD_ZBC_MASK) ==
+ SVPD_HAW_ZBC) {
+ softc->zone_mode = DA_ZONE_HOST_AWARE;
+ softc->zone_interface = (ata_proto) ?
+ DA_ZONE_IF_ATA_SAT : DA_ZONE_IF_SCSI;
+ } else if ((bdc->flags & SVPD_ZBC_MASK) ==
+ SVPD_DM_ZBC) {
+ softc->zone_mode =DA_ZONE_DRIVE_MANAGED;
+ softc->zone_interface = (ata_proto) ?
+ DA_ZONE_IF_ATA_SAT : DA_ZONE_IF_SCSI;
+ } else if ((bdc->flags & SVPD_ZBC_MASK) !=
+ SVPD_ZBC_NR) {
+ xpt_print(periph->path, "Unknown zoned "
+ "type %#x",
+ bdc->flags & SVPD_ZBC_MASK);
+ }
}
} else {
int error;
@@ -3619,10 +4598,14 @@
{
int i;
struct ata_params *ata_params;
+ int continue_probe;
+ int error;
int16_t *ptr;
ata_params = (struct ata_params *)csio->data_ptr;
ptr = (uint16_t *)ata_params;
+ continue_probe = 0;
+ error = 0;
if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
uint16_t old_rate;
@@ -3654,14 +4637,59 @@
disk_attr_changed(softc->disk,
"GEOM::rotation_rate", M_NOWAIT);
}
+
+ if (ata_params->capabilities1 & ATA_SUPPORT_DMA)
+ softc->flags |= DA_FLAG_CAN_ATA_DMA;
+
+ if (ata_params->support.extension &
+ ATA_SUPPORT_GENLOG)
+ softc->flags |= DA_FLAG_CAN_ATA_LOG;
+
+ /*
+ * At this point, if we have a SATA host aware drive,
+ * we communicate via ATA passthrough unless the
+ * SAT layer supports ZBC -> ZAC translation. In
+ * that case,
+ */
+ /*
+ * XXX KDM figure out how to detect a host managed
+ * SATA drive.
+ */
+ if (softc->zone_mode == DA_ZONE_NONE) {
+ /*
+ * Note that we don't override the zone
+ * mode or interface if it has already been
+ * set. This is because it has either been
+ * set as a quirk, or when we probed the
+ * SCSI Block Device Characteristics page,
+ * the zoned field was set. The latter
+ * means that the SAT layer supports ZBC to
+ * ZAC translation, and we would prefer to
+ * use that if it is available.
+ */
+ if ((ata_params->support3 &
+ ATA_SUPPORT_ZONE_MASK) ==
+ ATA_SUPPORT_ZONE_HOST_AWARE) {
+ softc->zone_mode = DA_ZONE_HOST_AWARE;
+ softc->zone_interface =
+ DA_ZONE_IF_ATA_PASS;
+ } else if ((ata_params->support3 &
+ ATA_SUPPORT_ZONE_MASK) ==
+ ATA_SUPPORT_ZONE_DEV_MANAGED) {
+ softc->zone_mode =DA_ZONE_DRIVE_MANAGED;
+ softc->zone_interface =
+ DA_ZONE_IF_ATA_PASS;
+ }
+ }
+
} else {
- int error;
error = daerror(done_ccb, CAM_RETRY_SELTO,
SF_RETRY_UA|SF_NO_PRINT);
if (error == ERESTART)
return;
else if (error != 0) {
- if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) {
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
/* Don't wedge this device's queue */
cam_release_devq(done_ccb->ccb_h.path,
/*relsim_flags*/0,
@@ -3673,9 +4701,457 @@
}
free(ata_params, M_SCSIDA);
+ if ((softc->zone_mode == DA_ZONE_HOST_AWARE)
+ || (softc->zone_mode == DA_ZONE_HOST_MANAGED)) {
+ /*
+ * If the ATA IDENTIFY failed, we could be talking
+ * to a SCSI drive, although that seems unlikely,
+ * since the drive did report that it supported the
+ * ATA Information VPD page. If the ATA IDENTIFY
+ * succeeded, and the SAT layer doesn't support
+ * ZBC -> ZAC translation, continue on to get the
+ * directory of ATA logs, and complete the rest of
+ * the ZAC probe. If the SAT layer does support
+ * ZBC -> ZAC translation, we want to use that,
+ * and we'll probe the SCSI Zoned Block Device
+ * Characteristics VPD page next.
+ */
+ if ((error == 0)
+ && (softc->flags & DA_FLAG_CAN_ATA_LOG)
+ && (softc->zone_interface == DA_ZONE_IF_ATA_PASS))
+ softc->state = DA_STATE_PROBE_ATA_LOGDIR;
+ else
+ softc->state = DA_STATE_PROBE_ZONE;
+ continue_probe = 1;
+ }
+ if (continue_probe != 0) {
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ return;
+ } else
+ daprobedone(periph, done_ccb);
+ return;
+ }
+ case DA_CCB_PROBE_ATA_LOGDIR:
+ {
+ int error;
+
+ if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ error = 0;
+ softc->valid_logdir_len = 0;
+ bzero(&softc->ata_logdir, sizeof(softc->ata_logdir));
+ softc->valid_logdir_len =
+ csio->dxfer_len - csio->resid;
+ if (softc->valid_logdir_len > 0)
+ bcopy(csio->data_ptr, &softc->ata_logdir,
+ min(softc->valid_logdir_len,
+ sizeof(softc->ata_logdir)));
+ /*
+ * Figure out whether the Identify Device log is
+ * supported. The General Purpose log directory
+ * has a header, and lists the number of pages
+ * available for each GP log identified by the
+ * offset into the list.
+ */
+ if ((softc->valid_logdir_len >=
+ ((ATA_IDENTIFY_DATA_LOG + 1) * sizeof(uint16_t)))
+ && (le16dec(softc->ata_logdir.header) ==
+ ATA_GP_LOG_DIR_VERSION)
+ && (le16dec(&softc->ata_logdir.num_pages[
+ (ATA_IDENTIFY_DATA_LOG *
+ sizeof(uint16_t)) - sizeof(uint16_t)]) > 0)){
+ softc->flags |= DA_FLAG_CAN_ATA_IDLOG;
+ } else {
+ softc->flags &= ~DA_FLAG_CAN_ATA_IDLOG;
+ }
+ } else {
+ error = daerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ /*
+ * If we can't get the ATA log directory,
+ * then ATA logs are effectively not
+ * supported even if the bit is set in the
+ * identify data.
+ */
+ softc->flags &= ~(DA_FLAG_CAN_ATA_LOG |
+ DA_FLAG_CAN_ATA_IDLOG);
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+ }
+
+ free(csio->data_ptr, M_SCSIDA);
+
+ if ((error == 0)
+ && (softc->flags & DA_FLAG_CAN_ATA_IDLOG)) {
+ softc->state = DA_STATE_PROBE_ATA_IDDIR;
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ return;
+ }
daprobedone(periph, done_ccb);
return;
}
+ case DA_CCB_PROBE_ATA_IDDIR:
+ {
+ int error;
+
+ if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ off_t entries_offset, max_entries;
+ error = 0;
+
+ softc->valid_iddir_len = 0;
+ bzero(&softc->ata_iddir, sizeof(softc->ata_iddir));
+ softc->flags &= ~(DA_FLAG_CAN_ATA_SUPCAP |
+ DA_FLAG_CAN_ATA_ZONE);
+ softc->valid_iddir_len =
+ csio->dxfer_len - csio->resid;
+ if (softc->valid_iddir_len > 0)
+ bcopy(csio->data_ptr, &softc->ata_iddir,
+ min(softc->valid_iddir_len,
+ sizeof(softc->ata_iddir)));
+
+ entries_offset =
+ __offsetof(struct ata_identify_log_pages,entries);
+ max_entries = softc->valid_iddir_len - entries_offset;
+ if ((softc->valid_iddir_len > (entries_offset + 1))
+ && (le64dec(softc->ata_iddir.header) ==
+ ATA_IDLOG_REVISION)
+ && (softc->ata_iddir.entry_count > 0)) {
+ int num_entries, i;
+
+ num_entries = softc->ata_iddir.entry_count;
+ num_entries = min(num_entries,
+ softc->valid_iddir_len - entries_offset);
+ for (i = 0; i < num_entries &&
+ i < max_entries; i++) {
+ if (softc->ata_iddir.entries[i] ==
+ ATA_IDL_SUP_CAP)
+ softc->flags |=
+ DA_FLAG_CAN_ATA_SUPCAP;
+ else if (softc->ata_iddir.entries[i]==
+ ATA_IDL_ZDI)
+ softc->flags |=
+ DA_FLAG_CAN_ATA_ZONE;
+
+ if ((softc->flags &
+ DA_FLAG_CAN_ATA_SUPCAP)
+ && (softc->flags &
+ DA_FLAG_CAN_ATA_ZONE))
+ break;
+ }
+ }
+ } else {
+ error = daerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ /*
+ * If we can't get the ATA Identify Data log
+ * directory, then it effectively isn't
+ * supported even if the ATA Log directory
+ * a non-zero number of pages present for
+ * this log.
+ */
+ softc->flags &= ~DA_FLAG_CAN_ATA_IDLOG;
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+ }
+
+ free(csio->data_ptr, M_SCSIDA);
+
+ if ((error == 0)
+ && (softc->flags & DA_FLAG_CAN_ATA_SUPCAP)) {
+ softc->state = DA_STATE_PROBE_ATA_SUP;
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ return;
+ }
+ daprobedone(periph, done_ccb);
+ return;
+ }
+ case DA_CCB_PROBE_ATA_SUP:
+ {
+ int error;
+
+ if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ uint32_t valid_len;
+ size_t needed_size;
+ struct ata_identify_log_sup_cap *sup_cap;
+ error = 0;
+
+ sup_cap = (struct ata_identify_log_sup_cap *)
+ csio->data_ptr;
+ valid_len = csio->dxfer_len - csio->resid;
+ needed_size =
+ __offsetof(struct ata_identify_log_sup_cap,
+ sup_zac_cap) + 1 + sizeof(sup_cap->sup_zac_cap);
+ if (valid_len >= needed_size) {
+ uint64_t zoned, zac_cap;
+
+ zoned = le64dec(sup_cap->zoned_cap);
+ if (zoned & ATA_ZONED_VALID) {
+ /*
+ * This should have already been
+ * set, because this is also in the
+ * ATA identify data.
+ */
+ if ((zoned & ATA_ZONED_MASK) ==
+ ATA_SUPPORT_ZONE_HOST_AWARE)
+ softc->zone_mode =
+ DA_ZONE_HOST_AWARE;
+ else if ((zoned & ATA_ZONED_MASK) ==
+ ATA_SUPPORT_ZONE_DEV_MANAGED)
+ softc->zone_mode =
+ DA_ZONE_DRIVE_MANAGED;
+ }
+
+ zac_cap = le64dec(sup_cap->sup_zac_cap);
+ if (zac_cap & ATA_SUP_ZAC_CAP_VALID) {
+ if (zac_cap & ATA_REPORT_ZONES_SUP)
+ softc->zone_flags |=
+ DA_ZONE_FLAG_RZ_SUP;
+ if (zac_cap & ATA_ND_OPEN_ZONE_SUP)
+ softc->zone_flags |=
+ DA_ZONE_FLAG_OPEN_SUP;
+ if (zac_cap & ATA_ND_CLOSE_ZONE_SUP)
+ softc->zone_flags |=
+ DA_ZONE_FLAG_CLOSE_SUP;
+ if (zac_cap & ATA_ND_FINISH_ZONE_SUP)
+ softc->zone_flags |=
+ DA_ZONE_FLAG_FINISH_SUP;
+ if (zac_cap & ATA_ND_RWP_SUP)
+ softc->zone_flags |=
+ DA_ZONE_FLAG_RWP_SUP;
+ } else {
+ /*
+ * This field was introduced in
+ * ACS-4, r08 on April 28th, 2015.
+ * If the drive firmware was written
+ * to an earlier spec, it won't have
+ * the field. So, assume all
+ * commands are supported.
+ */
+ softc->zone_flags |=
+ DA_ZONE_FLAG_SUP_MASK;
+ }
+
+ }
+ } else {
+ error = daerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ /*
+ * If we can't get the ATA Identify Data
+ * Supported Capabilities page, clear the
+ * flag...
+ */
+ softc->flags &= ~DA_FLAG_CAN_ATA_SUPCAP;
+ /*
+ * And clear zone capabilities.
+ */
+ softc->zone_flags &= ~DA_ZONE_FLAG_SUP_MASK;
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+ }
+
+ free(csio->data_ptr, M_SCSIDA);
+
+ if ((error == 0)
+ && (softc->flags & DA_FLAG_CAN_ATA_ZONE)) {
+ softc->state = DA_STATE_PROBE_ATA_ZONE;
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ return;
+ }
+ daprobedone(periph, done_ccb);
+ return;
+ }
+ case DA_CCB_PROBE_ATA_ZONE:
+ {
+ int error;
+
+ if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ struct ata_zoned_info_log *zi_log;
+ uint32_t valid_len;
+ size_t needed_size;
+
+ zi_log = (struct ata_zoned_info_log *)csio->data_ptr;
+
+ valid_len = csio->dxfer_len - csio->resid;
+ needed_size = __offsetof(struct ata_zoned_info_log,
+ version_info) + 1 + sizeof(zi_log->version_info);
+ if (valid_len >= needed_size) {
+ uint64_t tmpvar;
+
+ tmpvar = le64dec(zi_log->zoned_cap);
+ if (tmpvar & ATA_ZDI_CAP_VALID) {
+ if (tmpvar & ATA_ZDI_CAP_URSWRZ)
+ softc->zone_flags |=
+ DA_ZONE_FLAG_URSWRZ;
+ else
+ softc->zone_flags &=
+ ~DA_ZONE_FLAG_URSWRZ;
+ }
+ tmpvar = le64dec(zi_log->optimal_seq_zones);
+ if (tmpvar & ATA_ZDI_OPT_SEQ_VALID) {
+ softc->zone_flags |=
+ DA_ZONE_FLAG_OPT_SEQ_SET;
+ softc->optimal_seq_zones = (tmpvar &
+ ATA_ZDI_OPT_SEQ_MASK);
+ } else {
+ softc->zone_flags &=
+ ~DA_ZONE_FLAG_OPT_SEQ_SET;
+ softc->optimal_seq_zones = 0;
+ }
+
+ tmpvar =le64dec(zi_log->optimal_nonseq_zones);
+ if (tmpvar & ATA_ZDI_OPT_NS_VALID) {
+ softc->zone_flags |=
+ DA_ZONE_FLAG_OPT_NONSEQ_SET;
+ softc->optimal_nonseq_zones =
+ (tmpvar & ATA_ZDI_OPT_NS_MASK);
+ } else {
+ softc->zone_flags &=
+ ~DA_ZONE_FLAG_OPT_NONSEQ_SET;
+ softc->optimal_nonseq_zones = 0;
+ }
+
+ tmpvar = le64dec(zi_log->max_seq_req_zones);
+ if (tmpvar & ATA_ZDI_MAX_SEQ_VALID) {
+ softc->zone_flags |=
+ DA_ZONE_FLAG_MAX_SEQ_SET;
+ softc->max_seq_zones =
+ (tmpvar & ATA_ZDI_MAX_SEQ_MASK);
+ } else {
+ softc->zone_flags &=
+ ~DA_ZONE_FLAG_MAX_SEQ_SET;
+ softc->max_seq_zones = 0;
+ }
+ }
+ } else {
+ error = daerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ softc->flags &= ~DA_FLAG_CAN_ATA_ZONE;
+ softc->flags &= ~DA_ZONE_FLAG_SET_MASK;
+
+ if ((done_ccb->ccb_h.status &
+ CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+
+ }
+ free(csio->data_ptr, M_SCSIDA);
+
+ daprobedone(periph, done_ccb);
+ return;
+ }
+ case DA_CCB_PROBE_ZONE:
+ {
+ int error;
+
+ if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+ uint32_t valid_len;
+ size_t needed_len;
+ struct scsi_vpd_zoned_bdc *zoned_bdc;
+
+ error = 0;
+ zoned_bdc = (struct scsi_vpd_zoned_bdc *)
+ csio->data_ptr;
+ valid_len = csio->dxfer_len - csio->resid;
+ needed_len = __offsetof(struct scsi_vpd_zoned_bdc,
+ max_seq_req_zones) + 1 +
+ sizeof(zoned_bdc->max_seq_req_zones);
+ if ((valid_len >= needed_len)
+ && (scsi_2btoul(zoned_bdc->page_length) >=
+ SVPD_ZBDC_PL)) {
+ if (zoned_bdc->flags & SVPD_ZBDC_URSWRZ)
+ softc->zone_flags |=
+ DA_ZONE_FLAG_URSWRZ;
+ else
+ softc->zone_flags &=
+ ~DA_ZONE_FLAG_URSWRZ;
+ softc->optimal_seq_zones =
+ scsi_4btoul(zoned_bdc->optimal_seq_zones);
+ softc->zone_flags |= DA_ZONE_FLAG_OPT_SEQ_SET;
+ softc->optimal_nonseq_zones = scsi_4btoul(
+ zoned_bdc->optimal_nonseq_zones);
+ softc->zone_flags |=
+ DA_ZONE_FLAG_OPT_NONSEQ_SET;
+ softc->max_seq_zones =
+ scsi_4btoul(zoned_bdc->max_seq_req_zones);
+ softc->zone_flags |= DA_ZONE_FLAG_MAX_SEQ_SET;
+ }
+ /*
+ * All of the zone commands are mandatory for SCSI
+ * devices.
+ *
+ * XXX KDM this is valid as of September 2015.
+ * Re-check this assumption once the SAT spec is
+ * updated to support SCSI ZBC to ATA ZAC mapping.
+ * Since ATA allows zone commands to be reported
+ * as supported or not, this may not necessarily
+ * be true for an ATA device behind a SAT (SCSI to
+ * ATA Translation) layer.
+ */
+ softc->zone_flags |= DA_ZONE_FLAG_SUP_MASK;
+ } else {
+ error = daerror(done_ccb, CAM_RETRY_SELTO,
+ SF_RETRY_UA|SF_NO_PRINT);
+ if (error == ERESTART)
+ return;
+ else if (error != 0) {
+ if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) {
+ /* Don't wedge this device's queue */
+ cam_release_devq(done_ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+ }
+ }
+ }
+ daprobedone(periph, done_ccb);
+ return;
+ }
case DA_CCB_DUMP:
/* No-op. We're polling */
return;
@@ -4167,3 +5643,253 @@
}
#endif /* _KERNEL */
+
+void
+scsi_zbc_out(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, uint8_t service_action, uint64_t zone_id,
+ uint8_t zone_flags, uint8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t sense_len, uint32_t timeout)
+{
+ struct scsi_zbc_out *scsi_cmd;
+
+ scsi_cmd = (struct scsi_zbc_out *)&csio->cdb_io.cdb_bytes;
+ scsi_cmd->opcode = ZBC_OUT;
+ scsi_cmd->service_action = service_action;
+ scsi_u64to8b(zone_id, scsi_cmd->zone_id);
+ scsi_cmd->zone_flags = zone_flags;
+
+ cam_fill_csio(csio,
+ retries,
+ cbfcnp,
+ /*flags*/ (dxfer_len > 0) ? CAM_DIR_OUT : CAM_DIR_NONE,
+ tag_action,
+ data_ptr,
+ dxfer_len,
+ sense_len,
+ sizeof(*scsi_cmd),
+ timeout);
+}
+
+void
+scsi_zbc_in(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, uint8_t service_action, uint64_t zone_start_lba,
+ uint8_t zone_options, uint8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t sense_len, uint32_t timeout)
+{
+ struct scsi_zbc_in *scsi_cmd;
+
+ scsi_cmd = (struct scsi_zbc_in *)&csio->cdb_io.cdb_bytes;
+ scsi_cmd->opcode = ZBC_IN;
+ scsi_cmd->service_action = service_action;
+ scsi_u64to8b(zone_start_lba, scsi_cmd->zone_start_lba);
+ scsi_cmd->zone_options = zone_options;
+
+ cam_fill_csio(csio,
+ retries,
+ cbfcnp,
+ /*flags*/ (dxfer_len > 0) ? CAM_DIR_IN : CAM_DIR_NONE,
+ tag_action,
+ data_ptr,
+ dxfer_len,
+ sense_len,
+ sizeof(*scsi_cmd),
+ timeout);
+
+}
+
+int
+scsi_ata_zac_mgmt_out(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, int use_ncq,
+ uint8_t zm_action, uint64_t zone_id, uint8_t zone_flags,
+ uint8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t *cdb_storage, size_t cdb_storage_len,
+ uint8_t sense_len, uint32_t timeout)
+{
+ uint8_t command_out, protocol, ata_flags;
+ uint16_t features_out;
+ uint32_t sectors_out, auxiliary;
+ int retval;
+
+ retval = 0;
+
+ if (use_ncq == 0) {
+ command_out = ATA_ZAC_MANAGEMENT_OUT;
+ features_out = (zm_action & 0xf) | (zone_flags << 8),
+ ata_flags = AP_FLAG_BYT_BLOK_BLOCKS;
+ if (dxfer_len == 0) {
+ protocol = AP_PROTO_NON_DATA;
+ ata_flags |= AP_FLAG_TLEN_NO_DATA;
+ sectors_out = 0;
+ } else {
+ protocol = AP_PROTO_DMA;
+ ata_flags |= AP_FLAG_TLEN_SECT_CNT |
+ AP_FLAG_TDIR_TO_DEV;
+ sectors_out = ((dxfer_len >> 9) & 0xffff);
+ }
+ auxiliary = 0;
+ } else {
+ ata_flags = AP_FLAG_BYT_BLOK_BLOCKS;
+ if (dxfer_len == 0) {
+ command_out = ATA_NCQ_NON_DATA;
+ features_out = ATA_NCQ_ZAC_MGMT_OUT;
+ /*
+ * We're assuming the SCSI to ATA translation layer
+ * will set the NCQ tag number in the tag field.
+ * That isn't clear from the SAT-4 spec (as of rev 05).
+ */
+ sectors_out = 0;
+ ata_flags |= AP_FLAG_TLEN_NO_DATA;
+ } else {
+ command_out = ATA_SEND_FPDMA_QUEUED;
+ /*
+ * Note that we're defaulting to normal priority,
+ * and assuming that the SCSI to ATA translation
+ * layer will insert the NCQ tag number in the tag
+ * field. That isn't clear in the SAT-4 spec (as
+ * of rev 05).
+ */
+ sectors_out = ATA_SFPDMA_ZAC_MGMT_OUT << 8;
+
+ ata_flags |= AP_FLAG_TLEN_FEAT |
+ AP_FLAG_TDIR_TO_DEV;
+
+ /*
+ * For SEND FPDMA QUEUED, the transfer length is
+ * encoded in the FEATURE register, and 0 means
+ * that 65536 512 byte blocks are to be tranferred.
+ * In practice, it seems unlikely that we'll see
+ * a transfer that large, and it may confuse the
+ * the SAT layer, because generally that means that
+ * 0 bytes should be transferred.
+ */
+ if (dxfer_len == (65536 * 512)) {
+ features_out = 0;
+ } else if (dxfer_len <= (65535 * 512)) {
+ features_out = ((dxfer_len >> 9) & 0xffff);
+ } else {
+ /* The transfer is too big. */
+ retval = 1;
+ goto bailout;
+ }
+
+ }
+
+ auxiliary = (zm_action & 0xf) | (zone_flags << 8);
+ protocol = AP_PROTO_FPDMA;
+ }
+
+ protocol |= AP_EXTEND;
+
+ retval = scsi_ata_pass(csio,
+ retries,
+ cbfcnp,
+ /*flags*/ (dxfer_len > 0) ? CAM_DIR_OUT : CAM_DIR_NONE,
+ tag_action,
+ /*protocol*/ protocol,
+ /*ata_flags*/ ata_flags,
+ /*features*/ features_out,
+ /*sector_count*/ sectors_out,
+ /*lba*/ zone_id,
+ /*command*/ command_out,
+ /*device*/ 0,
+ /*icc*/ 0,
+ /*auxiliary*/ auxiliary,
+ /*control*/ 0,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ dxfer_len,
+ /*cdb_storage*/ cdb_storage,
+ /*cdb_storage_len*/ cdb_storage_len,
+ /*minimum_cmd_size*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout);
+
+bailout:
+
+ return (retval);
+}
+
+int
+scsi_ata_zac_mgmt_in(struct ccb_scsiio *csio, uint32_t retries,
+ void (*cbfcnp)(struct cam_periph *, union ccb *),
+ uint8_t tag_action, int use_ncq,
+ uint8_t zm_action, uint64_t zone_id, uint8_t zone_flags,
+ uint8_t *data_ptr, uint32_t dxfer_len,
+ uint8_t *cdb_storage, size_t cdb_storage_len,
+ uint8_t sense_len, uint32_t timeout)
+{
+ uint8_t command_out, protocol;
+ uint16_t features_out, sectors_out;
+ uint32_t auxiliary;
+ int ata_flags;
+ int retval;
+
+ retval = 0;
+ ata_flags = AP_FLAG_TDIR_FROM_DEV | AP_FLAG_BYT_BLOK_BLOCKS;
+
+ if (use_ncq == 0) {
+ command_out = ATA_ZAC_MANAGEMENT_IN;
+ /* XXX KDM put a macro here */
+ features_out = (zm_action & 0xf) | (zone_flags << 8),
+ sectors_out = dxfer_len >> 9, /* XXX KDM macro*/
+ protocol = AP_PROTO_DMA;
+ ata_flags |= AP_FLAG_TLEN_SECT_CNT;
+ auxiliary = 0;
+ } else {
+ ata_flags |= AP_FLAG_TLEN_FEAT;
+
+ command_out = ATA_RECV_FPDMA_QUEUED;
+ sectors_out = ATA_RFPDMA_ZAC_MGMT_IN << 8;
+
+ /*
+ * For RECEIVE FPDMA QUEUED, the transfer length is
+ * encoded in the FEATURE register, and 0 means
+ * that 65536 512 byte blocks are to be tranferred.
+ * In practice, it seems unlikely that we'll see
+ * a transfer that large, and it may confuse the
+ * the SAT layer, because generally that means that
+ * 0 bytes should be transferred.
+ */
+ if (dxfer_len == (65536 * 512)) {
+ features_out = 0;
+ } else if (dxfer_len <= (65535 * 512)) {
+ features_out = ((dxfer_len >> 9) & 0xffff);
+ } else {
+ /* The transfer is too big. */
+ retval = 1;
+ goto bailout;
+ }
+ auxiliary = (zm_action & 0xf) | (zone_flags << 8),
+ protocol = AP_PROTO_FPDMA;
+ }
+
+ protocol |= AP_EXTEND;
+
+ retval = scsi_ata_pass(csio,
+ retries,
+ cbfcnp,
+ /*flags*/ CAM_DIR_IN,
+ tag_action,
+ /*protocol*/ protocol,
+ /*ata_flags*/ ata_flags,
+ /*features*/ features_out,
+ /*sector_count*/ sectors_out,
+ /*lba*/ zone_id,
+ /*command*/ command_out,
+ /*device*/ 0,
+ /*icc*/ 0,
+ /*auxiliary*/ auxiliary,
+ /*control*/ 0,
+ /*data_ptr*/ data_ptr,
+ /*dxfer_len*/ (dxfer_len >> 9) * 512, /* XXX KDM */
+ /*cdb_storage*/ cdb_storage,
+ /*cdb_storage_len*/ cdb_storage_len,
+ /*minimum_cmd_size*/ 0,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout);
+
+bailout:
+ return (retval);
+}
Index: sys/dev/ahci/ahci.c
===================================================================
--- sys/dev/ahci/ahci.c
+++ sys/dev/ahci/ahci.c
@@ -2411,11 +2411,10 @@
fis[11] = ccb->ataio.cmd.features_exp;
if (ccb->ataio.cmd.flags & CAM_ATAIO_FPDMA) {
fis[12] = tag << 3;
- fis[13] = 0;
} else {
fis[12] = ccb->ataio.cmd.sector_count;
- fis[13] = ccb->ataio.cmd.sector_count_exp;
}
+ fis[13] = ccb->ataio.cmd.sector_count_exp;
fis[15] = ATA_A_4BIT;
} else {
fis[15] = ccb->ataio.cmd.control;
Index: sys/geom/eli/g_eli.c
===================================================================
--- sys/geom/eli/g_eli.c
+++ sys/geom/eli/g_eli.c
@@ -309,6 +309,7 @@
case BIO_WRITE:
case BIO_GETATTR:
case BIO_FLUSH:
+ case BIO_ZONE:
break;
case BIO_DELETE:
/*
@@ -348,6 +349,7 @@
case BIO_GETATTR:
case BIO_FLUSH:
case BIO_DELETE:
+ case BIO_ZONE:
cbp->bio_done = g_std_done;
cp = LIST_FIRST(&sc->sc_geom->consumer);
cbp->bio_to = cp->provider;
Index: sys/geom/geom.h
===================================================================
--- sys/geom/geom.h
+++ sys/geom/geom.h
@@ -56,6 +56,7 @@
struct sbuf;
struct gctl_req;
struct g_configargs;
+struct disk_zone_args;
typedef int g_config_t (struct g_configargs *ca);
typedef void g_ctl_req_t (struct gctl_req *, struct g_class *cp, char const *verb);
@@ -318,6 +319,7 @@
void g_destroy_bio(struct bio *);
void g_io_deliver(struct bio *bp, int error);
int g_io_getattr(const char *attr, struct g_consumer *cp, int *len, void *ptr);
+int g_io_zonecmd(struct disk_zone_args *zone_args, struct g_consumer *cp);
int g_io_flush(struct g_consumer *cp);
int g_register_classifier(struct g_classifier_hook *hook);
void g_unregister_classifier(struct g_classifier_hook *hook);
Index: sys/geom/geom_dev.c
===================================================================
--- sys/geom/geom_dev.c
+++ sys/geom/geom_dev.c
@@ -549,6 +549,42 @@
error = g_io_getattr(arg->name, cp, &arg->len, &arg->value);
break;
}
+ case DIOCZONECMD: {
+ struct disk_zone_args *zone_args =(struct disk_zone_args *)data;
+ struct disk_zone_rep_entry *new_entries, *old_entries;
+ struct disk_zone_report *rep;
+ size_t alloc_size;
+
+ old_entries = NULL;
+ new_entries = NULL;
+ rep = NULL;
+ alloc_size = 0;
+
+ if (zone_args->zone_cmd == DISK_ZONE_REPORT_ZONES) {
+
+ rep = &zone_args->zone_params.report;
+ alloc_size = rep->entries_allocated *
+ sizeof(struct disk_zone_rep_entry);
+ if (alloc_size != 0)
+ new_entries = g_malloc(alloc_size,
+ M_WAITOK| M_ZERO);
+ old_entries = rep->entries;
+ rep->entries = new_entries;
+ }
+ error = g_io_zonecmd(zone_args, cp);
+ if ((zone_args->zone_cmd == DISK_ZONE_REPORT_ZONES)
+ && (alloc_size != 0)
+ && (error == 0)) {
+ error = copyout(new_entries, old_entries, alloc_size);
+ }
+ if ((old_entries != NULL)
+ && (rep != NULL))
+ rep->entries = old_entries;
+
+ if (new_entries != NULL)
+ g_free(new_entries);
+ break;
+ }
default:
if (cp->provider->geom->ioctl != NULL) {
error = cp->provider->geom->ioctl(cp->provider, cmd, data, fflag, td);
@@ -574,6 +610,9 @@
bp->bio_error = bp2->bio_error;
bp->bio_completed = bp2->bio_completed;
bp->bio_resid = bp->bio_length - bp2->bio_completed;
+ if (bp2->bio_cmd == BIO_ZONE)
+ bcopy(&bp2->bio_zone, &bp->bio_zone, sizeof(bp->bio_zone));
+
if (bp2->bio_error != 0) {
g_trace(G_T_BIO, "g_dev_done(%p) had error %d",
bp2, bp2->bio_error);
@@ -608,7 +647,8 @@
KASSERT(bp->bio_cmd == BIO_READ ||
bp->bio_cmd == BIO_WRITE ||
bp->bio_cmd == BIO_DELETE ||
- bp->bio_cmd == BIO_FLUSH,
+ bp->bio_cmd == BIO_FLUSH ||
+ bp->bio_cmd == BIO_ZONE,
("Wrong bio_cmd bio=%p cmd=%d", bp, bp->bio_cmd));
dev = bp->bio_dev;
cp = dev->si_drv2;
Index: sys/geom/geom_disk.h
===================================================================
--- sys/geom/geom_disk.h
+++ sys/geom/geom_disk.h
@@ -109,6 +109,7 @@
#define DISKFLAG_CANFLUSHCACHE 0x8
#define DISKFLAG_UNMAPPED_BIO 0x10
#define DISKFLAG_DIRECT_COMPLETION 0x20
+#define DISKFLAG_CANZONE 0x80
struct disk *disk_alloc(void);
void disk_create(struct disk *disk, int version);
Index: sys/geom/geom_disk.c
===================================================================
--- sys/geom/geom_disk.c
+++ sys/geom/geom_disk.c
@@ -226,7 +226,11 @@
if (bp2->bio_error == 0)
bp2->bio_error = bp->bio_error;
bp2->bio_completed += bp->bio_completed;
+
switch (bp->bio_cmd) {
+ case BIO_ZONE:
+ bcopy(&bp->bio_zone, &bp2->bio_zone, sizeof(bp->bio_zone));
+ /*FALLTHROUGH*/
case BIO_READ:
case BIO_WRITE:
case BIO_DELETE:
@@ -515,6 +519,16 @@
error = EOPNOTSUPP;
break;
}
+ /*FALLTHROUGH*/
+ case BIO_ZONE:
+ if (bp->bio_cmd == BIO_ZONE) {
+ if (!(dp->d_flags & DISKFLAG_CANZONE)) {
+ error = EOPNOTSUPP;
+ break;
+ }
+ g_trace(G_T_BIO, "g_disk_zone(%s)",
+ bp->bio_to->name);
+ }
bp2 = g_clone_bio(bp);
if (bp2 == NULL) {
g_io_deliver(bp, ENOMEM);
Index: sys/geom/geom_io.c
===================================================================
--- sys/geom/geom_io.c
+++ sys/geom/geom_io.c
@@ -218,6 +218,9 @@
bp2->bio_ma_n = bp->bio_ma_n;
bp2->bio_ma_offset = bp->bio_ma_offset;
bp2->bio_attribute = bp->bio_attribute;
+ if (bp->bio_cmd == BIO_ZONE)
+ bcopy(&bp->bio_zone, &bp2->bio_zone,
+ sizeof(bp->bio_zone));
/* Inherit classification info from the parent */
bp2->bio_classifier1 = bp->bio_classifier1;
bp2->bio_classifier2 = bp->bio_classifier2;
@@ -305,6 +308,34 @@
}
int
+g_io_zonecmd(struct disk_zone_args *zone_args, struct g_consumer *cp)
+{
+ struct bio *bp;
+ int error;
+
+ g_trace(G_T_BIO, "bio_zone(%d)", zone_args->zone_cmd);
+ bp = g_alloc_bio();
+ bp->bio_cmd = BIO_ZONE;
+ bp->bio_done = NULL;
+ /*
+ * XXX KDM need to handle report zone data.
+ */
+ bcopy(zone_args, &bp->bio_zone, sizeof(*zone_args));
+ if (zone_args->zone_cmd == DISK_ZONE_REPORT_ZONES)
+ bp->bio_length =
+ zone_args->zone_params.report.entries_allocated *
+ sizeof(struct disk_zone_rep_entry);
+ else
+ bp->bio_length = 0;
+
+ g_io_request(bp, cp);
+ error = biowait(bp, "gzone");
+ bcopy(&bp->bio_zone, zone_args, sizeof(*zone_args));
+ g_destroy_bio(bp);
+ return (error);
+}
+
+int
g_io_flush(struct g_consumer *cp)
{
struct bio *bp;
@@ -349,6 +380,14 @@
if (cp->acw == 0)
return (EPERM);
break;
+ case BIO_ZONE:
+ if ((bp->bio_zone.zone_cmd == DISK_ZONE_REPORT_ZONES) ||
+ (bp->bio_zone.zone_cmd == DISK_ZONE_GET_PARAMS)) {
+ if (cp->acr == 0)
+ return (EPERM);
+ } else if (cp->acw == 0)
+ return (EPERM);
+ break;
default:
return (EPERM);
}
@@ -988,6 +1027,35 @@
cmd = "FLUSH";
printf("%s[%s]", pname, cmd);
return;
+ case BIO_ZONE: {
+ char *subcmd = NULL;
+ cmd = "ZONE";
+ switch (bp->bio_zone.zone_cmd) {
+ case DISK_ZONE_OPEN:
+ subcmd = "OPEN";
+ break;
+ case DISK_ZONE_CLOSE:
+ subcmd = "CLOSE";
+ break;
+ case DISK_ZONE_FINISH:
+ subcmd = "FINISH";
+ break;
+ case DISK_ZONE_RWP:
+ subcmd = "RWP";
+ break;
+ case DISK_ZONE_REPORT_ZONES:
+ subcmd = "REPORT ZONES";
+ break;
+ case DISK_ZONE_GET_PARAMS:
+ subcmd = "GET PARAMS";
+ break;
+ default:
+ subcmd = "UNKNOWN";
+ break;
+ }
+ printf("%s[%s,%s]", pname, cmd, subcmd);
+ return;
+ }
case BIO_READ:
cmd = "READ";
break;
Index: sys/geom/geom_subr.c
===================================================================
--- sys/geom/geom_subr.c
+++ sys/geom/geom_subr.c
@@ -1471,6 +1471,7 @@
case BIO_CMD0: db_printf("BIO_CMD0"); break;
case BIO_CMD1: db_printf("BIO_CMD1"); break;
case BIO_CMD2: db_printf("BIO_CMD2"); break;
+ case BIO_ZONE: db_printf("BIO_ZONE"); break;
default: db_printf("UNKNOWN"); break;
}
db_printf("\n");
Index: sys/kern/subr_devstat.c
===================================================================
--- sys/kern/subr_devstat.c
+++ sys/kern/subr_devstat.c
@@ -354,7 +354,9 @@
if (bp->bio_cmd == BIO_DELETE)
flg = DEVSTAT_FREE;
- else if (bp->bio_cmd == BIO_READ)
+ else if ((bp->bio_cmd == BIO_READ)
+ || ((bp->bio_cmd == BIO_ZONE)
+ && (bp->bio_zone.zone_cmd == DISK_ZONE_REPORT_ZONES)))
flg = DEVSTAT_READ;
else if (bp->bio_cmd == BIO_WRITE)
flg = DEVSTAT_WRITE;
Index: sys/sys/ata.h
===================================================================
--- sys/sys/ata.h
+++ sys/sys/ata.h
@@ -105,6 +105,10 @@
/*069*/ u_int16_t support3;
#define ATA_SUPPORT_RZAT 0x0020
#define ATA_SUPPORT_DRAT 0x4000
+#define ATA_SUPPORT_ZONE_MASK 0x0003
+#define ATA_SUPPORT_ZONE_NR 0x0000
+#define ATA_SUPPORT_ZONE_HOST_AWARE 0x0001
+#define ATA_SUPPORT_ZONE_DEV_MANAGED 0x0002
u_int16_t reserved70;
/*071*/ u_int16_t rlsovlap; /* rel time (us) for overlap */
/*072*/ u_int16_t rlsservice; /* rel time (us) for service */
@@ -228,7 +232,14 @@
#define ATA_SUPPORT_RWLOGDMAEXT 0x0008
#define ATA_SUPPORT_MICROCODE3 0x0010
#define ATA_SUPPORT_FREEFALL 0x0020
+#define ATA_SUPPORT_SENSE_REPORT 0x0040
+#define ATA_SUPPORT_EPC 0x0080
/*120*/ u_int16_t enabled2;
+#define ATA_ENABLED_WRITEREADVERIFY 0x0002
+#define ATA_ENABLED_WRITEUNCORREXT 0x0004
+#define ATA_ENABLED_FREEFALL 0x0020
+#define ATA_ENABLED_SENSE_REPORT 0x0040
+#define ATA_ENABLED_EPC 0x0080
u_int16_t reserved121[6];
/*127*/ u_int16_t removable_status;
/*128*/ u_int16_t security_status;
@@ -298,8 +309,14 @@
#define ATA_MAX_28BIT_LBA 268435455UL
/* ATA Status Register */
-#define ATA_STATUS_ERROR 0x01
-#define ATA_STATUS_DEVICE_FAULT 0x20
+#define ATA_STATUS_ERROR 0x01
+#define ATA_STATUS_SENSE_AVAIL 0x02
+#define ATA_STATUS_ALIGN_ERR 0x04
+#define ATA_STATUS_DATA_REQ 0x08
+#define ATA_STATUS_DEF_WRITE_ERR 0x10
+#define ATA_STATUS_DEVICE_FAULT 0x20
+#define ATA_STATUS_DEVICE_READY 0x40
+#define ATA_STATUS_BUSY 0x80
/* ATA Error Register */
#define ATA_ERROR_ABORT 0x04
@@ -372,17 +389,32 @@
#define ATA_WU_PSEUDO 0x55 /* pseudo-uncorrectable error */
#define ATA_WU_FLAGGED 0xaa /* flagged-uncorrectable error */
#define ATA_READ_LOG_DMA_EXT 0x47 /* read log DMA ext - PIO Data-In */
+#define ATA_ZAC_MANAGEMENT_IN 0x4a /* ZAC management in */
+#define ATA_ZM_REPORT_ZONES 0x00 /* report zones */
#define ATA_READ_FPDMA_QUEUED 0x60 /* read DMA NCQ */
#define ATA_WRITE_FPDMA_QUEUED 0x61 /* write DMA NCQ */
#define ATA_NCQ_NON_DATA 0x63 /* NCQ non-data command */
+#define ATA_ABORT_NCQ_QUEUE 0x00 /* abort NCQ queue */
+#define ATA_DEADLINE_HANDLING 0x01 /* deadline handling */
+#define ATA_SET_FEATURES 0x05 /* set features */
+#define ATA_ZERO_EXT 0x06 /* zero ext */
+#define ATA_NCQ_ZAC_MGMT_OUT 0x07 /* NCQ ZAC mgmt out no data */
#define ATA_SEND_FPDMA_QUEUED 0x64 /* send DMA NCQ */
#define ATA_SFPDMA_DSM 0x00 /* Data set management */
#define ATA_SFPDMA_DSM_TRIM 0x01 /* Set trim bit in auxiliary */
#define ATA_SFPDMA_HYBRID_EVICT 0x01 /* Hybrid Evict */
#define ATA_SFPDMA_WLDMA 0x02 /* Write Log DMA EXT */
-#define ATA_RECV_FPDMA_QUEUED 0x65 /* receive DMA NCQ */
+#define ATA_SFPDMA_ZAC_MGMT_OUT 0x03 /* NCQ ZAC mgmt out w/data */
+#define ATA_RECV_FPDMA_QUEUED 0x65 /* recieve DMA NCQ */
+#define ATA_RFPDMA_RL_DMA_EXT 0x00 /* Read Log DMA EXT */
+#define ATA_RFPDMA_ZAC_MGMT_IN 0x02 /* NCQ ZAC mgmt in w/data */
#define ATA_SEP_ATTN 0x67 /* SEP request */
#define ATA_SEEK 0x70 /* seek */
+#define ATA_ZAC_MANAGEMENT_OUT 0x9f /* ZAC management out */
+#define ATA_ZM_CLOSE_ZONE 0x01 /* close zone */
+#define ATA_ZM_FINISH_ZONE 0x02 /* finish zone */
+#define ATA_ZM_OPEN_ZONE 0x03 /* open zone */
+#define ATA_ZM_RWP 0x04 /* reset write pointer */
#define ATA_PACKET_CMD 0xa0 /* packet command */
#define ATA_ATAPI_IDENTIFY 0xa1 /* get ATAPI params*/
#define ATA_SERVICE 0xa2 /* service command */
@@ -409,12 +441,20 @@
#define ATA_FLUSHCACHE48 0xea /* flush cache to disk */
#define ATA_ATA_IDENTIFY 0xec /* get ATA params */
#define ATA_SETFEATURES 0xef /* features command */
-#define ATA_SF_SETXFER 0x03 /* set transfer mode */
#define ATA_SF_ENAB_WCACHE 0x02 /* enable write cache */
#define ATA_SF_DIS_WCACHE 0x82 /* disable write cache */
+#define ATA_SF_SETXFER 0x03 /* set transfer mode */
+#define ATA_SF_APM 0x05 /* Enable APM feature set */
#define ATA_SF_ENAB_PUIS 0x06 /* enable PUIS */
#define ATA_SF_DIS_PUIS 0x86 /* disable PUIS */
#define ATA_SF_PUIS_SPINUP 0x07 /* PUIS spin-up */
+#define ATA_SF_WRV 0x0b /* Enable Write-Read-Verify */
+#define ATA_SF_DLC 0x0c /* Enable device life control */
+#define ATA_SF_SATA 0x10 /* Enable use of SATA feature */
+#define ATA_SF_FFC 0x41 /* Free-fall Control */
+#define ATA_SF_MHIST 0x43 /* Set Max Host Sect. Times */
+#define ATA_SF_RATE 0x45 /* Set Rate Basis */
+#define ATA_SF_EPC 0x4A /* Extended Power Conditions */
#define ATA_SF_ENAB_RCACHE 0xaa /* enable readahead cache */
#define ATA_SF_DIS_RCACHE 0x55 /* disable readahead cache */
#define ATA_SF_ENAB_RELIRQ 0x5d /* enable release interrupt */
@@ -421,6 +461,9 @@
#define ATA_SF_DIS_RELIRQ 0xdd /* disable release interrupt */
#define ATA_SF_ENAB_SRVIRQ 0x5e /* enable service interrupt */
#define ATA_SF_DIS_SRVIRQ 0xde /* disable service interrupt */
+#define ATA_SF_LPSAERC 0x62 /* Long Phys Sect Align ErrRep*/
+#define ATA_SF_DSN 0x63 /* Device Stats Notification */
+#define ATA_CHECK_POWER_MODE 0xe5 /* Check Power Mode */
#define ATA_SECURITY_SET_PASSWORD 0xf1 /* set drive password */
#define ATA_SECURITY_UNLOCK 0xf2 /* unlock drive using passwd */
#define ATA_SECURITY_ERASE_PREPARE 0xf3 /* prepare to erase drive */
@@ -547,6 +590,333 @@
u_int8_t specific2; /* sense key specific */
} __packed;
+/*
+ * SET FEATURES subcommands
+ */
+
+/*
+ * SET FEATURES command
+ * Extended Power Conditions subcommand -- ATA_SF_EPC (0x4A)
+ * These values go in the LBA 3:0.
+ */
+#define ATA_SF_EPC_RESTORE 0x00 /* Restore Power Condition Settings */
+#define ATA_SF_EPC_GOTO 0x01 /* Go To Power Condition */
+#define ATA_SF_EPC_SET_TIMER 0x02 /* Set Power Condition Timer */
+#define ATA_SF_EPC_SET_STATE 0x03 /* Set Power Condition State */
+#define ATA_SF_EPC_ENABLE 0x04 /* Enable the EPC feature set */
+#define ATA_SF_EPC_DISABLE 0x05 /* Disable the EPC feature set */
+#define ATA_SF_EPC_SET_SOURCE 0x06 /* Set EPC Power Source */
+
+/*
+ * SET FEATURES command
+ * Extended Power Conditions subcommand -- ATA_SF_EPC (0x4A)
+ * Power Condition ID field
+ * These values go in the count register.
+ */
+#define ATA_EPC_STANDBY_Z 0x00 /* Substate of PM2:Standby */
+#define ATA_EPC_STANDBY_Y 0x01 /* Substate of PM2:Standby */
+#define ATA_EPC_IDLE_A 0x81 /* Substate of PM1:Idle */
+#define ATA_EPC_IDLE_B 0x82 /* Substate of PM1:Idle */
+#define ATA_EPC_IDLE_C 0x83 /* Substate of PM1:Idle */
+#define ATA_EPC_ALL 0xff /* All supported power conditions */
+
+/*
+ * SET FEATURES command
+ * Extended Power Conditions subcommand -- ATA_SF_EPC (0x4A)
+ * Restore Power Conditions Settings subcommand
+ * These values go in the LBA register.
+ */
+#define ATA_SF_EPC_RST_DFLT 0x40 /* 1=Rst from Default, 0= from Saved */
+#define ATA_SF_EPC_RST_SAVE 0x10 /* 1=Save on completion */
+
+/*
+ * SET FEATURES command
+ * Extended Power Conditions subcommand -- ATA_SF_EPC (0x4A)
+ * Got To Power Condition subcommand
+ * These values go in the LBA register.
+ */
+#define ATA_SF_EPC_GOTO_DELAY 0x02000000 /* Delayed entry bit */
+#define ATA_SF_EPC_GOTO_HOLD 0x01000000 /* Hold Power Cond bit */
+
+/*
+ * SET FEATURES command
+ * Extended Power Conditions subcommand -- ATA_SF_EPC (0x4A)
+ * Set Power Condition Timer subcommand
+ * These values go in the LBA register.
+ */
+#define ATA_SF_EPC_TIMER_MASK 0x00ffff00 /* Timer field */
+#define ATA_SF_EPC_TIMER_SHIFT 8
+#define ATA_SF_EPC_TIMER_SEC 0x00000080 /* Timer units, 1=sec, 0=.1s */
+#define ATA_SF_EPC_TIMER_EN 0x00000020 /* Enable/disable cond. */
+#define ATA_SF_EPC_TIMER_SAVE 0x00000010 /* Save settings on comp. */
+
+/*
+ * SET FEATURES command
+ * Extended Power Conditions subcommand -- ATA_SF_EPC (0x4A)
+ * Set Power Condition State subcommand
+ * These values go in the LBA register.
+ */
+#define ATA_SF_EPC_SETCON_EN 0x00000020 /* Enable power cond. */
+#define ATA_SF_EPC_SETCON_SAVE 0x00000010 /* Save settings on comp */
+
+/*
+ * SET FEATURES command
+ * Extended Power Conditions subcommand -- ATA_SF_EPC (0x4A)
+ * Set EPC Power Source subcommand
+ * These values go in the count register.
+ */
+#define ATA_SF_EPC_SRC_UNKNOWN 0x0000 /* Unknown source */
+#define ATA_SF_EPC_SRC_BAT 0x0001 /* battery source */
+#define ATA_SF_EPC_SRC_NOT_BAT 0x0002 /* not battery source */
+
+#define ATA_LOG_DIRECTORY 0x00 /* Directory of all logs */
+#define ATA_POWER_COND_LOG 0x08 /* Power Conditions Log */
+#define ATA_PCL_IDLE 0x00 /* Idle Power Conditions Page */
+#define ATA_PCL_STANDBY 0x01 /* Standby Power Conditions Page */
+#define ATA_IDENTIFY_DATA_LOG 0x30 /* Identify Device Data Log */
+#define ATA_IDL_PAGE_LIST 0x00 /* List of supported pages */
+#define ATA_IDL_IDENTIFY_DATA 0x01 /* Copy of Identify Device data */
+#define ATA_IDL_CAPACITY 0x02 /* Capacity */
+#define ATA_IDL_SUP_CAP 0x03 /* Supported Capabilities */
+#define ATA_IDL_CUR_SETTINGS 0x04 /* Current Settings */
+#define ATA_IDL_ATA_STRINGS 0x05 /* ATA Strings */
+#define ATA_IDL_SECURITY 0x06 /* Security */
+#define ATA_IDL_PARALLEL_ATA 0x07 /* Parallel ATA */
+#define ATA_IDL_SERIAL_ATA 0x08 /* Seiral ATA */
+#define ATA_IDL_ZDI 0x09 /* Zoned Device Information */
+
+struct ata_gp_log_dir {
+ uint8_t header[2];
+#define ATA_GP_LOG_DIR_VERSION 0x0001
+ uint8_t num_pages[255*2]; /* Number of log pages at address */
+};
+
+/*
+ * ATA Power Conditions log descriptor
+ */
+struct ata_power_cond_log_desc {
+ uint8_t reserved1;
+ uint8_t flags;
+#define ATA_PCL_COND_SUPPORTED 0x80
+#define ATA_PCL_COND_SAVEABLE 0x40
+#define ATA_PCL_COND_CHANGEABLE 0x20
+#define ATA_PCL_DEFAULT_TIMER_EN 0x10
+#define ATA_PCL_SAVED_TIMER_EN 0x08
+#define ATA_PCL_CURRENT_TIMER_EN 0x04
+#define ATA_PCL_HOLD_PC_NOT_SUP 0x02
+ uint8_t reserved2[2];
+ uint8_t default_timer[4];
+ uint8_t saved_timer[4];
+ uint8_t current_timer[4];
+ uint8_t nom_time_to_active[4];
+ uint8_t min_timer[4];
+ uint8_t max_timer[4];
+ uint8_t num_transitions_to_pc[4];
+ uint8_t hours_in_pc[4];
+ uint8_t reserved3[28];
+};
+
+/*
+ * ATA Power Conditions Log (0x08), Idle power conditions page (0x00)
+ */
+struct ata_power_cond_log_idle {
+ struct ata_power_cond_log_desc idle_a_desc;
+ struct ata_power_cond_log_desc idle_b_desc;
+ struct ata_power_cond_log_desc idle_c_desc;
+ uint8_t reserved[320];
+};
+
+/*
+ * ATA Power Conditions Log (0x08), Standby power conditions page (0x01)
+ */
+struct ata_power_cond_log_standby {
+ uint8_t reserved[384];
+ struct ata_power_cond_log_desc standby_y_desc;
+ struct ata_power_cond_log_desc standby_z_desc;
+};
+
+/*
+ * ATA IDENTIFY DEVICE data log (0x30) page 0x00
+ * List of Supported IDENTIFY DEVICE data pages.
+ */
+struct ata_identify_log_pages {
+ uint8_t header[8];
+#define ATA_IDLOG_REVISION 0x0000000000000001
+ uint8_t entry_count;
+ uint8_t entries[503];
+};
+
+/*
+ * ATA IDENTIFY DEVICE data log (0x30)
+ * Capacity (Page 0x02).
+ */
+struct ata_identify_log_capacity {
+ uint8_t header[8];
+#define ATA_CAP_HEADER_VALID 0x8000000000000000
+#define ATA_CAP_PAGE_NUM_MASK 0x0000000000ff0000
+#define ATA_CAP_PAGE_NUM_SHIFT 16
+#define ATA_CAP_REV_MASK 0x00000000000000ff
+ uint8_t capacity[8];
+#define ATA_CAP_CAPACITY_VALID 0x8000000000000000
+#define ATA_CAP_ACCESSIBLE_CAP 0x0000ffffffffffff
+ uint8_t phys_logical_sect_size[8];
+#define ATA_CAP_PL_VALID 0x8000000000000000
+#define ATA_CAP_LTOP_REL_SUP 0x4000000000000000
+#define ATA_CAP_LOG_SECT_SUP 0x2000000000000000
+#define ATA_CAP_ALIGN_ERR_MASK 0x0000000000300000
+#define ATA_CAP_LTOP_MASK 0x00000000000f0000
+#define ATA_CAP_LOG_SECT_OFF 0x000000000000ffff
+ uint8_t logical_sect_size[8];
+#define ATA_CAP_LOG_SECT_VALID 0x8000000000000000
+#define ATA_CAP_LOG_SECT_SIZE 0x00000000ffffffff
+ uint8_t nominal_buffer_size[8];
+#define ATA_CAP_NOM_BUF_VALID 0x8000000000000000
+#define ATA_CAP_NOM_BUF_SIZE 0x7fffffffffffffff
+ uint8_t reserved[472];
+};
+
+/*
+ * ATA IDENTIFY DEVICE data log (0x30)
+ * Supported Capabilities (Page 0x03).
+ */
+
+struct ata_identify_log_sup_cap {
+ uint8_t header[8];
+#define ATA_SUP_CAP_HEADER_VALID 0x8000000000000000
+#define ATA_SUP_CAP_PAGE_NUM_MASK 0x0000000000ff0000
+#define ATA_SUP_CAP_PAGE_NUM_SHIFT 16
+#define ATA_SUP_CAP_REV_MASK 0x00000000000000ff
+ uint8_t sup_cap[8];
+#define ATA_SUP_CAP_VALID 0x8000000000000000
+#define ATA_SC_SET_SECT_CONFIG_SUP 0x0002000000000000 /* Set Sect Conf*/
+#define ATA_SC_ZERO_EXT_SUP 0x0001000000000000 /* Zero EXT */
+#define ATA_SC_SUCC_NCQ_SENSE_SUP 0x0000800000000000 /* Succ. NCQ Sns */
+#define ATA_SC_DLC_SUP 0x0000400000000000 /* DLC */
+#define ATA_SC_RQSN_DEV_FAULT_SUP 0x0000200000000000 /* Req Sns Dev Flt*/
+#define ATA_SC_DSN_SUP 0x0000100000000000 /* DSN */
+#define ATA_SC_LP_STANDBY_SUP 0x0000080000000000 /* LP Standby */
+#define ATA_SC_SET_EPC_PS_SUP 0x0000040000000000 /* Set EPC PS */
+#define ATA_SC_AMAX_ADDR_SUP 0x0000020000000000 /* AMAX Addr */
+#define ATA_SC_DRAT_SUP 0x0000008000000000 /* DRAT */
+#define ATA_SC_LPS_MISALGN_SUP 0x0000004000000000 /* LPS Misalign */
+#define ATA_SC_RB_DMA_SUP 0x0000001000000000 /* Read Buf DMA */
+#define ATA_SC_WB_DMA_SUP 0x0000000800000000 /* Write Buf DMA */
+#define ATA_SC_DNLD_MC_DMA_SUP 0x0000000200000000 /* DL MCode DMA */
+#define ATA_SC_28BIT_SUP 0x0000000100000000 /* 28-bit */
+#define ATA_SC_RZAT_SUP 0x0000000080000000 /* RZAT */
+#define ATA_SC_NOP_SUP 0x0000000020000000 /* NOP */
+#define ATA_SC_READ_BUFFER_SUP 0x0000000010000000 /* Read Buffer */
+#define ATA_SC_WRITE_BUFFER_SUP 0x0000000008000000 /* Write Buffer */
+#define ATA_SC_READ_LOOK_AHEAD_SUP 0x0000000002000000 /* Read Look-Ahead*/
+#define ATA_SC_VOLATILE_WC_SUP 0x0000000001000000 /* Volatile WC */
+#define ATA_SC_SMART_SUP 0x0000000000800000 /* SMART */
+#define ATA_SC_FLUSH_CACHE_EXT_SUP 0x0000000000400000 /* Flush Cache Ext */
+#define ATA_SC_48BIT_SUP 0x0000000000100000 /* 48-Bit */
+#define ATA_SC_SPINUP_SUP 0x0000000000040000 /* Spin-Up */
+#define ATA_SC_PUIS_SUP 0x0000000000020000 /* PUIS */
+#define ATA_SC_APM_SUP 0x0000000000010000 /* APM */
+#define ATA_SC_DL_MICROCODE_SUP 0x0000000000004000 /* DL Microcode */
+#define ATA_SC_UNLOAD_SUP 0x0000000000002000 /* Unload */
+#define ATA_SC_WRITE_FUA_EXT_SUP 0x0000000000001000 /* Write FUA EXT */
+#define ATA_SC_GPL_SUP 0x0000000000000800 /* GPL */
+#define ATA_SC_STREAMING_SUP 0x0000000000000400 /* Streaming */
+#define ATA_SC_SMART_SELFTEST_SUP 0x0000000000000100 /* SMART self-test */
+#define ATA_SC_SMART_ERR_LOG_SUP 0x0000000000000080 /* SMART Err Log */
+#define ATA_SC_EPC_SUP 0x0000000000000040 /* EPC */
+#define ATA_SC_SENSE_SUP 0x0000000000000020 /* Sense data */
+#define ATA_SC_FREEFALL_SUP 0x0000000000000010 /* Free-Fall */
+#define ATA_SC_DM_MODE3_SUP 0x0000000000000008 /* DM Mode 3 */
+#define ATA_SC_GPL_DMA_SUP 0x0000000000000004 /* GPL DMA */
+#define ATA_SC_WRITE_UNCOR_SUP 0x0000000000000002 /* Write uncorr. */
+#define ATA_SC_WRV_SUP 0x0000000000000001 /* WRV */
+ uint8_t download_code_cap[8];
+#define ATA_DL_CODE_VALID 0x8000000000000000
+#define ATA_DLC_DM_OFFSETS_DEFER_SUP 0x0000000400000000
+#define ATA_DLC_DM_IMMED_SUP 0x0000000200000000
+#define ATA_DLC_DM_OFF_IMMED_SUP 0x0000000100000000
+#define ATA_DLC_DM_MAX_XFER_SIZE_MASK 0x00000000ffff0000
+#define ATA_DLC_DM_MAX_XFER_SIZE_SHIFT 16
+#define ATA_DLC_DM_MIN_XFER_SIZE_MASK 0x000000000000ffff
+ uint8_t nom_media_rotation_rate[8];
+#define ATA_NOM_MEDIA_ROTATION_VALID 0x8000000000000000
+#define ATA_ROTATION_MASK 0x000000000000ffff
+ uint8_t form_factor[8];
+#define ATA_FORM_FACTOR_VALID 0x8000000000000000
+#define ATA_FF_MASK 0x000000000000000f
+#define ATA_FF_NOT_REPORTED 0x0000000000000000 /* Not reported */
+#define ATA_FF_525_IN 0x0000000000000001 /* 5.25 inch */
+#define ATA_FF_35_IN 0x0000000000000002 /* 3.5 inch */
+#define ATA_FF_25_IN 0x0000000000000003 /* 2.5 inch */
+#define ATA_FF_18_IN 0x0000000000000004 /* 1.8 inch */
+#define ATA_FF_LT_18_IN 0x0000000000000005 /* < 1.8 inch */
+#define ATA_FF_MSATA 0x0000000000000006 /* mSATA */
+#define ATA_FF_M2 0x0000000000000007 /* M.2 */
+#define ATA_FF_MICROSSD 0x0000000000000008 /* MicroSSD */
+#define ATA_FF_CFAST 0x0000000000000009 /* CFast */
+ uint8_t wrv_sec_cnt_mode3[8];
+#define ATA_WRV_MODE3_VALID 0x8000000000000000
+#define ATA_WRV_MODE3_COUNT 0x00000000ffffffff
+ uint8_t wrv_sec_cnt_mode2[8];
+#define ATA_WRV_MODE2_VALID 0x8000000000000000
+#define ATA_WRV_MODE2_COUNT 0x00000000ffffffff
+ uint8_t wwn[16];
+ /* XXX KDM need to figure out how to handle 128-bit fields */
+ uint8_t dsm[8];
+#define ATA_DSM_VALID 0x8000000000000000
+#define ATA_LB_MARKUP_SUP 0x000000000000ff00
+#define ATA_TRIM_SUP 0x0000000000000001
+ uint8_t util_per_unit_time[16];
+ /* XXX KDM need to figure out how to handle 128-bit fields */
+ uint8_t util_usage_rate_sup[8];
+#define ATA_UTIL_USAGE_RATE_VALID 0x8000000000000000
+#define ATA_SETTING_RATE_SUP 0x0000000000800000
+#define ATA_SINCE_POWERON_SUP 0x0000000000000100
+#define ATA_POH_RATE_SUP 0x0000000000000010
+#define ATA_DATE_TIME_RATE_SUP 0x0000000000000001
+ uint8_t zoned_cap[8];
+#define ATA_ZONED_VALID 0x8000000000000000
+#define ATA_ZONED_MASK 0x0000000000000003
+ uint8_t sup_zac_cap[8];
+#define ATA_SUP_ZAC_CAP_VALID 0x8000000000000000
+#define ATA_ND_RWP_SUP 0x0000000000000010 /* Reset Write Ptr*/
+#define ATA_ND_FINISH_ZONE_SUP 0x0000000000000008 /* Finish Zone */
+#define ATA_ND_CLOSE_ZONE_SUP 0x0000000000000004 /* Close Zone */
+#define ATA_ND_OPEN_ZONE_SUP 0x0000000000000002 /* Open Zone */
+#define ATA_REPORT_ZONES_SUP 0x0000000000000001 /* Report Zones */
+ uint8_t reserved[392];
+};
+
+/*
+ * ATA Identify Device Data Log Zoned Device Information Page (0x09).
+ * Current as of ZAC r04a, August 25, 2015.
+ */
+struct ata_zoned_info_log {
+ uint8_t header[8];
+#define ATA_ZDI_HEADER_VALID 0x8000000000000000
+#define ATA_ZDI_PAGE_NUM_MASK 0x0000000000ff0000
+#define ATA_ZDI_PAGE_NUM_SHIFT 16
+#define ATA_ZDI_REV_MASK 0x00000000000000ff
+ uint8_t zoned_cap[8];
+#define ATA_ZDI_CAP_VALID 0x8000000000000000
+#define ATA_ZDI_CAP_URSWRZ 0x0000000000000001
+ uint8_t zoned_settings[8];
+#define ATA_ZDI_SETTINGS_VALID 0x8000000000000000
+ uint8_t optimal_seq_zones[8];
+#define ATA_ZDI_OPT_SEQ_VALID 0x8000000000000000
+#define ATA_ZDI_OPT_SEQ_MASK 0x00000000ffffffff
+ uint8_t optimal_nonseq_zones[8];
+#define ATA_ZDI_OPT_NS_VALID 0x8000000000000000
+#define ATA_ZDI_OPT_NS_MASK 0x00000000ffffffff
+ uint8_t max_seq_req_zones[8];
+#define ATA_ZDI_MAX_SEQ_VALID 0x8000000000000000
+#define ATA_ZDI_MAX_SEQ_MASK 0x00000000ffffffff
+ uint8_t version_info[8];
+#define ATA_ZDI_VER_VALID 0x8000000000000000
+#define ATA_ZDI_VER_ZAC_SUP 0x0100000000000000
+#define ATA_ZDI_VER_ZAC_MASK 0x00000000000000ff
+ uint8_t reserved[456];
+};
+
struct ata_ioc_request {
union {
struct {
Index: sys/sys/bio.h
===================================================================
--- sys/sys/bio.h
+++ sys/sys/bio.h
@@ -39,16 +39,18 @@
#define _SYS_BIO_H_
#include <sys/queue.h>
+#include <sys/disk_zone.h>
/* bio_cmd */
#define BIO_READ 0x01 /* Read I/O data */
#define BIO_WRITE 0x02 /* Write I/O data */
-#define BIO_DELETE 0x04 /* TRIM or free blocks, i.e. mark as unused */
-#define BIO_GETATTR 0x08 /* Get GEOM attributes of object */
-#define BIO_FLUSH 0x10 /* Commit outstanding I/O now */
-#define BIO_CMD0 0x20 /* Available for local hacks */
-#define BIO_CMD1 0x40 /* Available for local hacks */
-#define BIO_CMD2 0x80 /* Available for local hacks */
+#define BIO_DELETE 0x03 /* TRIM or free blocks, i.e. mark as unused */
+#define BIO_GETATTR 0x04 /* Get GEOM attributes of object */
+#define BIO_FLUSH 0x05 /* Commit outstanding I/O now */
+#define BIO_CMD0 0x06 /* Available for local hacks */
+#define BIO_CMD1 0x07 /* Available for local hacks */
+#define BIO_CMD2 0x08 /* Available for local hacks */
+#define BIO_ZONE 0x09 /* Zone command */
/* bio_flags */
#define BIO_ERROR 0x01 /* An error occurred processing this bio. */
@@ -98,6 +100,7 @@
void *bio_caller2; /* Private use by the consumer. */
TAILQ_ENTRY(bio) bio_queue; /* Disksort queue. */
const char *bio_attribute; /* Attribute for BIO_[GS]ETATTR */
+ struct disk_zone_args bio_zone;/* Used for BIO_ZONE */
struct g_consumer *bio_from; /* GEOM linkage */
struct g_provider *bio_to; /* GEOM linkage */
off_t bio_length; /* Like bio_bcount */
Index: sys/sys/disk.h
===================================================================
--- sys/sys/disk.h
+++ sys/sys/disk.h
@@ -15,6 +15,7 @@
#include <sys/ioccom.h>
#include <sys/types.h>
+#include <sys/disk_zone.h>
#ifdef _KERNEL
@@ -136,4 +137,6 @@
};
#define DIOCGATTR _IOWR('d', 142, struct diocgattr_arg)
+#define DIOCZONECMD _IOWR('d', 143, struct disk_zone_args)
+
#endif /* _SYS_DISK_H_ */
Index: sys/sys/disk_zone.h
===================================================================
--- sys/sys/disk_zone.h
+++ sys/sys/disk_zone.h
@@ -0,0 +1,182 @@
+/*-
+ * Copyright (c) 2015 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * 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 MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * Authors: Ken Merry (Spectra Logic Corporation)
+ */
+
+#ifndef _SYS_DISK_ZONE_H_
+#define _SYS_DISK_ZONE_H_
+
+/*
+ * Interface for Zone-based disks. This allows managing devices that
+ * conform to the SCSI Zoned Block Commands (ZBC) and ATA Zoned ATA Command
+ * Set (ZAC) specifications. Devices using these command sets are
+ * currently (October 2015) hard drives using Shingled Magnetic Recording
+ * (SMR).
+ */
+
+/*
+ * There are currently three types of zoned devices:
+ *
+ * Drive Managed:
+ * Drive Managed drives look and act just like a standard random access
+ * block device, but underneath, the drive reads and writes the bulk of
+ * its capacity using SMR zones. Sequential writes will yield better
+ * performance, but writing sequentially is not required.
+ *
+ * Host Aware:
+ * Host Aware drives expose the underlying zone layout via SCSI or ATA
+ * commands and allow the host to manage the zone conditions. The host
+ * is not required to manage the zones on the drive, though. Sequential
+ * writes will yield better performance in Sequential Write Preferred
+ * zones, but the host can write randomly in those zones.
+ *
+ * Host Managed:
+ * Host Managed drives expose the underlying zone layout via SCSI or ATA
+ * commands. The host is required to access the zones according to the
+ * rules described by the zone layout. Any commands that violate the
+ * rules will be returned with an error.
+ */
+struct disk_zone_disk_params {
+ uint32_t zone_mode;
+#define DISK_ZONE_MODE_NONE 0x00
+#define DISK_ZONE_MODE_HOST_AWARE 0x01
+#define DISK_ZONE_MODE_DRIVE_MANAGED 0x02
+#define DISK_ZONE_MODE_HOST_MANAGED 0x04
+ uint64_t flags;
+#define DISK_ZONE_DISK_URSWRZ 0x001
+#define DISK_ZONE_OPT_SEQ_SET 0x002
+#define DISK_ZONE_OPT_NONSEQ_SET 0x004
+#define DISK_ZONE_MAX_SEQ_SET 0x008
+#define DISK_ZONE_RZ_SUP 0x010
+#define DISK_ZONE_OPEN_SUP 0x020
+#define DISK_ZONE_CLOSE_SUP 0x040
+#define DISK_ZONE_FINISH_SUP 0x080
+#define DISK_ZONE_RWP_SUP 0x100
+#define DISK_ZONE_CMD_SUP_MASK 0x1f0
+ uint64_t optimal_seq_zones;
+ uint64_t optimal_nonseq_zones;
+ uint64_t max_seq_zones;
+};
+
+/*
+ * Used for reset write pointer, open, close and finish.
+ */
+struct disk_zone_rwp {
+ uint64_t id;
+ uint8_t flags;
+#define DISK_ZONE_RWP_FLAG_NONE 0x00
+#define DISK_ZONE_RWP_FLAG_ALL 0x01
+};
+
+/*
+ * Report Zones header. All of these values are passed out.
+ */
+struct disk_zone_rep_header {
+ uint8_t same;
+#define DISK_ZONE_SAME_ALL_DIFFERENT 0x0 /* Lengths and types vary */
+#define DISK_ZONE_SAME_ALL_SAME 0x1 /* Lengths and types the same */
+#define DISK_ZONE_SAME_LAST_DIFFERENT 0x2 /* Types same, last len varies */
+#define DISK_ZONE_SAME_TYPES_DIFFERENT 0x3 /* Types vary, length the same */
+ uint64_t maximum_lba;
+ /*
+ * XXX KDM padding space may not be a good idea inside the bio.
+ */
+ uint8_t reserved[64];
+};
+
+/*
+ * Report Zones entry. Note that the zone types, conditions, and flags
+ * are mapped directly from the SCSI/ATA flag values. Any additional
+ * SCSI/ATA zone types or conditions or flags that are defined in the
+ * future could result in additional values that are not yet defined here.
+ */
+struct disk_zone_rep_entry {
+ uint8_t zone_type;
+#define DISK_ZONE_TYPE_CONVENTIONAL 0x01
+#define DISK_ZONE_TYPE_SEQ_REQUIRED 0x02 /* Host Managed */
+#define DISK_ZONE_TYPE_SEQ_PREFERRED 0x03 /* Host Aware */
+ uint8_t zone_condition;
+#define DISK_ZONE_COND_NOT_WP 0x00
+#define DISK_ZONE_COND_EMPTY 0x01
+#define DISK_ZONE_COND_IMPLICIT_OPEN 0x02
+#define DISK_ZONE_COND_EXPLICIT_OPEN 0x03
+#define DISK_ZONE_COND_CLOSED 0x04
+#define DISK_ZONE_COND_READONLY 0x0D
+#define DISK_ZONE_COND_FULL 0x0E
+#define DISK_ZONE_COND_OFFLINE 0x0F
+ uint8_t zone_flags;
+#define DISK_ZONE_FLAG_RESET 0x01 /* Zone needs RWP */
+#define DISK_ZONE_FLAG_NON_SEQ 0x02 /* Zone accssessed nonseq */
+ uint64_t zone_length;
+ uint64_t zone_start_lba;
+ uint64_t write_pointer_lba;
+ /* XXX KDM padding space may not be a good idea inside the bio */
+ uint8_t reserved[32];
+};
+
+struct disk_zone_report {
+ uint64_t starting_id; /* Passed In */
+ uint8_t rep_options; /* Passed In */
+#define DISK_ZONE_REP_ALL 0x00
+#define DISK_ZONE_REP_EMPTY 0x01
+#define DISK_ZONE_REP_IMP_OPEN 0x02
+#define DISK_ZONE_REP_EXP_OPEN 0x03
+#define DISK_ZONE_REP_CLOSED 0x04
+#define DISK_ZONE_REP_FULL 0x05
+#define DISK_ZONE_REP_READONLY 0x06
+#define DISK_ZONE_REP_OFFLINE 0x07
+#define DISK_ZONE_REP_RWP 0x10
+#define DISK_ZONE_REP_NON_SEQ 0x11
+#define DISK_ZONE_REP_NON_WP 0x3F
+ struct disk_zone_rep_header header;
+ uint32_t entries_allocated; /* Passed In */
+ uint32_t entries_filled; /* Passed Out */
+ uint32_t entries_available; /* Passed Out */
+ struct disk_zone_rep_entry *entries;
+};
+
+union disk_zone_params {
+ struct disk_zone_disk_params disk_params;
+ struct disk_zone_rwp rwp;
+ struct disk_zone_report report;
+};
+
+struct disk_zone_args {
+ uint8_t zone_cmd;
+#define DISK_ZONE_OPEN 0x00
+#define DISK_ZONE_CLOSE 0x01
+#define DISK_ZONE_FINISH 0x02
+#define DISK_ZONE_REPORT_ZONES 0x03
+#define DISK_ZONE_RWP 0x04
+#define DISK_ZONE_GET_PARAMS 0x05
+ union disk_zone_params zone_params;
+};
+
+#endif /* _SYS_DISK_ZONE_H_ */
Index: usr.bin/systat/devs.c
===================================================================
--- usr.bin/systat/devs.c
+++ usr.bin/systat/devs.c
@@ -167,6 +167,8 @@
return(0);
} else if (retval == 1)
return(2);
+ else
+ return(1);
}
if (prefix(cmd, "drives")) {
int i;
Index: usr.sbin/Makefile
===================================================================
--- usr.sbin/Makefile
+++ usr.sbin/Makefile
@@ -96,7 +96,8 @@
wake \
watch \
watchdogd \
- zic
+ zic \
+ zonectl
# NB: keep these sorted by MK_* knobs
Index: usr.sbin/diskinfo/diskinfo.c
===================================================================
--- usr.sbin/diskinfo/diskinfo.c
+++ usr.sbin/diskinfo/diskinfo.c
@@ -1,5 +1,6 @@
/*-
* Copyright (c) 2003 Poul-Henning Kamp
+ * Copyright (c) 2015 Spectra Logic Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -54,6 +55,8 @@
static void speeddisk(int fd, off_t mediasize, u_int sectorsize);
static void commandtime(int fd, off_t mediasize, u_int sectorsize);
+static int zonecheck(int fd, uint32_t *zone_mode, char *zone_str,
+ size_t zone_str_len);
int
main(int argc, char **argv)
@@ -60,8 +63,10 @@
{
int i, ch, fd, error, exitval = 0;
char buf[BUFSIZ], ident[DISK_IDENT_SIZE], physpath[MAXPATHLEN];
+ char zone_desc[64];
off_t mediasize, stripesize, stripeoffset;
- u_int sectorsize, fwsectors, fwheads;
+ u_int sectorsize, fwsectors, fwheads, zoned = 0;
+ uint32_t zone_mode;
while ((ch = getopt(argc, argv, "ctv")) != -1) {
switch (ch) {
@@ -121,6 +126,9 @@
error = ioctl(fd, DIOCGSTRIPEOFFSET, &stripeoffset);
if (error)
stripeoffset = 0;
+ error = zonecheck(fd, &zone_mode, zone_desc, sizeof(zone_desc));
+ if (error == 0)
+ zoned = 1;
if (!opt_v) {
printf("%s", argv[i]);
printf("\t%u", sectorsize);
@@ -155,6 +163,8 @@
printf("\t%-12s\t# Disk ident.\n", ident);
if (ioctl(fd, DIOCGPHYSPATH, physpath) == 0)
printf("\t%-12s\t# Physical path\n", physpath);
+ if (zoned != 0)
+ printf("\t%-12s\t# Zone Mode\n", zone_desc);
}
printf("\n");
if (opt_c)
@@ -386,3 +396,39 @@
printf("\n");
return;
}
+
+static int
+zonecheck(int fd, uint32_t *zone_mode, char *zone_str, size_t zone_str_len)
+{
+ struct disk_zone_args zone_args;
+ int error;
+
+ bzero(&zone_args, sizeof(zone_args));
+
+ zone_args.zone_cmd = DISK_ZONE_GET_PARAMS;
+ error = ioctl(fd, DIOCZONECMD, &zone_args);
+
+ if (error == 0) {
+ *zone_mode = zone_args.zone_params.disk_params.zone_mode;
+
+ switch (*zone_mode) {
+ case DISK_ZONE_MODE_NONE:
+ snprintf(zone_str, zone_str_len, "Not_Zoned");
+ break;
+ case DISK_ZONE_MODE_HOST_AWARE:
+ snprintf(zone_str, zone_str_len, "Host_Aware");
+ break;
+ case DISK_ZONE_MODE_DRIVE_MANAGED:
+ snprintf(zone_str, zone_str_len, "Drive_Managed");
+ break;
+ case DISK_ZONE_MODE_HOST_MANAGED:
+ snprintf(zone_str, zone_str_len, "Host_Managed");
+ break;
+ default:
+ snprintf(zone_str, zone_str_len, "Unknown_zone_mode_%u",
+ *zone_mode);
+ break;
+ }
+ }
+ return (error);
+}
Index: usr.sbin/zonectl/Makefile
===================================================================
--- usr.sbin/zonectl/Makefile
+++ usr.sbin/zonectl/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= zonectl
+SRCS= zonectl.c
+SDIR= ${.CURDIR}/../../sys
+LIBADD= cam sbuf util
+MAN= zonectl.8
+CFLAGS+=-g -O0
+
+.include <bsd.prog.mk>
Index: usr.sbin/zonectl/zonectl.8
===================================================================
--- usr.sbin/zonectl/zonectl.8
+++ usr.sbin/zonectl/zonectl.8
@@ -0,0 +1,236 @@
+.\"
+.\" Copyright (c) 2015 Spectra Logic Corporation
+.\" All rights reserved.
+.\"
+.\" 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,
+.\" without modification.
+.\" 2. Redistributions in binary form must reproduce at minimum a disclaimer
+.\" substantially similar to the "NO WARRANTY" disclaimer below
+.\" ("Disclaimer") and any redistribution must be conditioned upon
+.\" including a substantially similar Disclaimer requirement for further
+.\" binary redistribution.
+.\"
+.\" NO WARRANTY
+.\" 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 MERCHANTIBILITY AND FITNESS FOR
+.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+.\" HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+.\"
+.\" Authors: Ken Merry (Spectra Logic Corporation)
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 16, 2015
+.Dt ZONECTL 8
+.Os
+.Sh NAME
+.Nm zonectl
+.Nd Shingled Magnetic Recording Zone Control utility
+.Sh SYNOPSIS
+.Nm
+.Aq Fl d Ar dev
+.Aq Fl c Ar cmd
+.Op Fl a
+.Op Fl l Ar LBA
+.Op Fl o Ar rep_opts
+.Op Fl P Ar print_opts
+.Sh DESCRIPTION
+Manage
+.Tn SCSI
+and
+.Tn ATA
+Zoned Block devices.
+This allows managing devices that conform to the
+.Tn SCSI
+Zoned Block Commands (ZBC) and
+.Tn ATA
+Zoned ATA Command Set (ZAC)
+specifications.
+Devices using these command sets are usually hard drives using Shingled
+Magnetic Recording (SMR).
+There are three types of SMR drives:
+.Bl -tag -width 13n
+.It Drive Managed
+Drive Managed drives look and act just like a standard random access block
+device, but underneath, the drive reads and writes the bulk of its capacity
+using SMR zones.
+Sequential writes will yield better performance, but writing sequentially
+is not required.
+.It Host Aware
+Host Aware drives expose the underlying zone layout via
+.Tn SCSI
+or
+.Tn ATA
+commands and allow the host to manage the zone conditions.
+The host is not required to manage the zones on the drive, though.
+Sequential writes will yield better performance in Sequential Write
+Preferred zones, but the host can write randomly in those zones.
+.It Host Managed
+Host Managed drives expose the underlying zone layout via
+.Tn SCSI
+or
+.Tn ATA
+commands.
+The host is required to access the zones according to the rules described
+by the zone layout.
+Any commands that violate the rules will be returned with an error.
+.El
+.Pp
+SMR drives are divided into zones (typically in the range of 256MB each)
+that fall into three general categories:
+.Bl -tag -width 20n
+.It Conventional
+These are also known as Non Write Pointer zones.
+These zones can be randomly written without an unexpected performance penalty.
+.It Sequential Preferred
+These zones should be written sequentially starting at the write pointer
+for the zone.
+They may be written randomly.
+Writes that do not conform to the zone layout may be significantly slower
+than expected.
+.It Sequential Required
+These zones must be written sequentially.
+If they are not written sequentially, starting at the write pointer, the
+command will fail.
+.El
+.Pp
+.Bl -tag -width 12n
+.It Fl c Ar cmd
+Specify the zone subcommand:
+.Bl -tag -width 6n
+.It params
+Display device parameters, including the type of device (Drive Managed,
+Host Aware, Host Managed, Not Zoned), the zone commands supported, and
+how many open zones it supports.
+.It rz
+Issue the Report Zones command.
+All zones are returned by default.
+Specify report options with
+.Fl o
+and printing options with
+.Fl P .
+Specify the starting LBA with
+.Fl l .
+Note that
+.Dq reportzones
+is also accepted as a command argument.
+.It open
+Explicitly open the zone specified by the starting LBA.
+.It close
+Close the zone specified by starting LBA.
+.It finish
+Finish the zone specified by the starting LBA.
+.It rwp
+Reset the write pointer for the zone specified by the starting LBA.
+.El
+.It Fl a
+For the Open, Close, Finish and Reset Write Pointer operations, apply the
+operation to all zones on the drive.
+.It Fl l Ar lba
+Specify the starting LBA.
+For the Report Zones command, this tells the drive to report starting with
+the zone that starts at the given LBA.
+For the other commands, this allows the user to identify the zone requested
+by its starting LBA.
+The LBA may be specified in decimal, hexadecimal or octal notation.
+.It Fl o Ar rep_opt
+For the Report Zones command, specify a subset of zones to report.
+.Bl -tag -width 8n
+.It all
+Report all zones.
+This is the default.
+.It emtpy
+Report only empty zones.
+.It imp_open
+Report zones that are implicitly open.
+This means that the host has sent a write to the zone without explicitly
+opening the zone.
+.It exp_open
+Report zones that are explicitly open.
+.It closed
+Report zones that have been closed by the host.
+.It full
+Report zones that are full.
+.It ro
+Report zones that are in the read only state.
+Note that
+.Dq readonly
+is also accepted as an argument.
+.It offline
+Report zones that are in the offline state.
+.It reset
+Report zones that the device recommends should have their write pointers reset.
+.It nonseq
+Report zones that have the Non Sequential Resources Active flag set.
+These are zones that are Sequential Write Preferred, but have been written
+non-sequentially.
+.It nonwp
+Report Non Write Pointer zones, also known as Conventional zones.
+.El
+.It Fl P Ar print_opt
+Specify a printing option for Report Zones:
+.Bl -tag -width 7n
+.It normal
+Normal Report Zones output.
+This is the default.
+The summary and column headings are printed, fields are separated by spaces
+and the fields themselves may contain spaces.
+.It summary
+Just print the summary: the number of zones, the maximum LBA (LBA of the
+last logical block on the drive), and the value of the
+.Dq same
+field.
+The
+.Dq same
+field describes whether the zones on the drive are all identical, all
+different, or whether they are the same except for the last zone, etc.
+.It script
+Print the zones in a script friendly format.
+The summary and column headings are omitted, the fields are separated by
+commas, and the fields do not contain spaces.
+The fields contain underscores where spaces would normally be used.
+.El
+.El
+.Sh EXAMPLES
+.Bd -literal -offset indent
+zonectl -d /dev/da5 -c params
+.Ed
+.Pp
+This will display basic zoning information for disk da5.
+.Pp
+.Bd -literal -offset indent
+zonectl -d /dev/da5 -c rz
+.Ed
+.Pp
+This will issue the Report Zones command to disk da5, and print out all
+zones on the drive in the default format.
+.Pp
+.Bd -literal -offset indent
+zonectl -d /dev/da5 -c rz -o reset -P script
+.Ed
+.Pp
+This will issue the Report Zones command to disk da5, and print out all
+of the zones that have the Reset Write Pointer Recommended bit set to true.
+It will print the zones in a script friendly form.
+.Pp
+.Bd -literal -offset indent
+zonectl -d /dev/da5 -c rwp -l 0x2c80000
+.Ed
+.Pp
+This will issue the Reset Write Pointer command to disk da5 for the zone
+that starts at LBA 0x2c80000.
+.Pp
+.Bd -literal -offset indent
+.Sh AUTHORS
+.An Kenneth Merry Aq ken@FreeBSD.org
Index: usr.sbin/zonectl/zonectl.c
===================================================================
--- usr.sbin/zonectl/zonectl.c
+++ usr.sbin/zonectl/zonectl.c
@@ -0,0 +1,591 @@
+/*-
+ * Copyright (c) 2015, 2016 Spectra Logic Corporation
+ * All rights reserved.
+ *
+ * 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,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ *
+ * NO WARRANTY
+ * 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 MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ *
+ * Authors: Ken Merry (Spectra Logic Corporation)
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/stdint.h>
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <sys/sbuf.h>
+#include <sys/queue.h>
+#include <sys/disk.h>
+#include <sys/disk_zone.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <limits.h>
+#include <err.h>
+#include <locale.h>
+
+#include <cam/cam.h>
+#include <cam/cam_debug.h>
+#include <cam/cam_ccb.h>
+#include <cam/scsi/scsi_all.h>
+
+static struct scsi_nv zone_cmd_map[] = {
+ { "rz", DISK_ZONE_REPORT_ZONES },
+ { "reportzones", DISK_ZONE_REPORT_ZONES },
+ { "close", DISK_ZONE_CLOSE },
+ { "finish", DISK_ZONE_FINISH },
+ { "open", DISK_ZONE_OPEN },
+ { "rwp", DISK_ZONE_RWP },
+ { "params", DISK_ZONE_GET_PARAMS }
+};
+
+static struct scsi_nv zone_rep_opts[] = {
+ { "all", DISK_ZONE_REP_ALL },
+ { "empty", DISK_ZONE_REP_EMPTY },
+ { "imp_open", DISK_ZONE_REP_IMP_OPEN },
+ { "exp_open", DISK_ZONE_REP_EXP_OPEN },
+ { "closed", DISK_ZONE_REP_CLOSED },
+ { "full", DISK_ZONE_REP_FULL },
+ { "readonly", DISK_ZONE_REP_READONLY },
+ { "ro", DISK_ZONE_REP_READONLY },
+ { "offline", DISK_ZONE_REP_OFFLINE },
+ { "reset", DISK_ZONE_REP_RWP },
+ { "rwp", DISK_ZONE_REP_RWP },
+ { "nonseq", DISK_ZONE_REP_NON_SEQ },
+ { "nonwp", DISK_ZONE_REP_NON_WP }
+};
+
+
+typedef enum {
+ ZONE_OF_NORMAL = 0x00,
+ ZONE_OF_SUMMARY = 0x01,
+ ZONE_OF_SCRIPT = 0x02
+} zone_output_flags;
+
+static struct scsi_nv zone_print_opts[] = {
+ { "normal", ZONE_OF_NORMAL },
+ { "summary", ZONE_OF_SUMMARY },
+ { "script", ZONE_OF_SCRIPT }
+};
+
+static struct scsi_nv zone_cmd_desc_table[] = {
+ {"Report Zones", DISK_ZONE_RZ_SUP },
+ {"Open", DISK_ZONE_OPEN_SUP },
+ {"Close", DISK_ZONE_CLOSE_SUP },
+ {"Finish", DISK_ZONE_FINISH_SUP },
+ {"Reset Write Pointer", DISK_ZONE_RWP_SUP }
+};
+
+typedef enum {
+ ZONE_PRINT_OK,
+ ZONE_PRINT_MORE_DATA,
+ ZONE_PRINT_ERROR
+} zone_print_status;
+
+typedef enum {
+ ZONE_FW_START,
+ ZONE_FW_LEN,
+ ZONE_FW_WP,
+ ZONE_FW_TYPE,
+ ZONE_FW_COND,
+ ZONE_FW_SEQ,
+ ZONE_FW_RESET,
+ ZONE_NUM_FIELDS
+} zone_field_widths;
+
+
+static void usage(int error);
+static void zonectl_print_params(struct disk_zone_disk_params *params);
+zone_print_status zonectl_print_rz(struct disk_zone_report *report,
+ zone_output_flags out_flags, int first_pass);
+
+static void
+usage(int error)
+{
+ fprintf(error ? stderr : stdout,
+"usage: zonectl <-d dev> <-c cmd> [-a][-o rep_opts] [-l lba][-P print_opts]\n"
+ );
+}
+
+static void
+zonectl_print_params(struct disk_zone_disk_params *params)
+{
+ unsigned int i;
+ int first;
+
+ printf("Zone Mode: ");
+ switch (params->zone_mode) {
+ case DISK_ZONE_MODE_NONE:
+ printf("None");
+ break;
+ case DISK_ZONE_MODE_HOST_AWARE:
+ printf("Host Aware");
+ break;
+ case DISK_ZONE_MODE_DRIVE_MANAGED:
+ printf("Drive Managed");
+ break;
+ case DISK_ZONE_MODE_HOST_MANAGED:
+ printf("Host Managed");
+ break;
+ default:
+ printf("Unknown mode %#x", params->zone_mode);
+ break;
+ }
+ printf("\n");
+
+ first = 1;
+ printf("Command support: ");
+ for (i = 0; i < sizeof(zone_cmd_desc_table) /
+ sizeof(zone_cmd_desc_table[0]); i++) {
+ if (params->flags & zone_cmd_desc_table[i].value) {
+ if (first == 0)
+ printf(", ");
+ else
+ first = 0;
+ printf("%s", zone_cmd_desc_table[i].name);
+ }
+ }
+ if (first == 1)
+ printf("None");
+ printf("\n");
+
+ printf("Unrestricted Read in Sequential Write Required Zone "
+ "(URSWRZ): %s\n", (params->flags & DISK_ZONE_DISK_URSWRZ) ?
+ "Yes" : "No");
+
+ printf("Optimal Number of Open Sequential Write Preferred Zones: ");
+ if (params->flags & DISK_ZONE_OPT_SEQ_SET)
+ if (params->optimal_seq_zones == SVPD_ZBDC_OPT_SEQ_NR)
+ printf("Not Reported");
+ else
+ printf("%ju", (uintmax_t)params->optimal_seq_zones);
+ else
+ printf("Not Set");
+ printf("\n");
+
+
+ printf("Optimal Number of Non-Sequentially Written Sequential Write "
+ "Preferred Zones: ");
+ if (params->flags & DISK_ZONE_OPT_NONSEQ_SET)
+ if (params->optimal_nonseq_zones == SVPD_ZBDC_OPT_NONSEQ_NR)
+ printf("Not Reported");
+ else
+ printf("%ju",(uintmax_t)params->optimal_nonseq_zones);
+ else
+ printf("Not Set");
+ printf("\n");
+
+ printf("Maximum Number of Open Sequential Write Required Zones: ");
+ if (params->flags & DISK_ZONE_MAX_SEQ_SET)
+ if (params->max_seq_zones == SVPD_ZBDC_MAX_SEQ_UNLIMITED)
+ printf("Unlimited");
+ else
+ printf("%ju", (uintmax_t)params->max_seq_zones);
+ else
+ printf("Not Set");
+ printf("\n");
+}
+
+zone_print_status
+zonectl_print_rz(struct disk_zone_report *report, zone_output_flags out_flags,
+ int first_pass)
+{
+ zone_print_status status = ZONE_PRINT_OK;
+ struct disk_zone_rep_header *header = &report->header;
+ int field_widths[ZONE_NUM_FIELDS];
+ struct disk_zone_rep_entry *entry;
+ uint64_t next_lba = 0;
+ char tmpstr[80];
+ char word_sep;
+ int more_data = 0;
+ uint32_t i;
+
+ field_widths[ZONE_FW_START] = 11;
+ field_widths[ZONE_FW_LEN] = 6;
+ field_widths[ZONE_FW_WP] = 11;
+ field_widths[ZONE_FW_TYPE] = 13;
+ field_widths[ZONE_FW_COND] = 13;
+ field_widths[ZONE_FW_SEQ] = 14;
+ field_widths[ZONE_FW_RESET] = 16;
+
+ if ((report->entries_available - report->entries_filled) > 0) {
+ more_data = 1;
+ status = ZONE_PRINT_MORE_DATA;
+ }
+
+ if (out_flags == ZONE_OF_SCRIPT)
+ word_sep = '_';
+ else
+ word_sep = ' ';
+
+ if ((out_flags != ZONE_OF_SCRIPT)
+ && (first_pass != 0)) {
+ printf("%u zones, Maximum LBA %#jx (%ju)\n",
+ report->entries_available,
+ (uintmax_t)header->maximum_lba,
+ (uintmax_t)header->maximum_lba);
+
+ switch (header->same) {
+ case DISK_ZONE_SAME_ALL_DIFFERENT:
+ printf("Zone lengths and types may vary\n");
+ break;
+ case DISK_ZONE_SAME_ALL_SAME:
+ printf("Zone lengths and types are all the same\n");
+ break;
+ case DISK_ZONE_SAME_LAST_DIFFERENT:
+ printf("Zone types are the same, last zone length "
+ "differs\n");
+ break;
+ case DISK_ZONE_SAME_TYPES_DIFFERENT:
+ printf("Zone lengths are the same, types vary\n");
+ break;
+ default:
+ printf("Unknown SAME field value %#x\n",header->same);
+ break;
+ }
+ }
+ if (out_flags == ZONE_OF_SUMMARY) {
+ status = ZONE_PRINT_OK;
+ goto bailout;
+ }
+
+ if ((out_flags == ZONE_OF_NORMAL)
+ && (first_pass != 0)) {
+ printf("%*s %*s %*s %*s %*s %*s %*s\n",
+ field_widths[ZONE_FW_START], "Start LBA",
+ field_widths[ZONE_FW_LEN], "Length",
+ field_widths[ZONE_FW_WP], "WP LBA",
+ field_widths[ZONE_FW_TYPE], "Zone Type",
+ field_widths[ZONE_FW_COND], "Condition",
+ field_widths[ZONE_FW_SEQ], "Sequential",
+ field_widths[ZONE_FW_RESET], "Reset");
+ }
+
+ for (i = 0; i < report->entries_filled; i++) {
+ entry = &report->entries[i];
+
+ printf("%#*jx, %*ju, %#*jx, ", field_widths[ZONE_FW_START],
+ (uintmax_t)entry->zone_start_lba,
+ field_widths[ZONE_FW_LEN],
+ (uintmax_t)entry->zone_length, field_widths[ZONE_FW_WP],
+ (uintmax_t)entry->write_pointer_lba);
+
+ switch (entry->zone_type) {
+ case DISK_ZONE_TYPE_CONVENTIONAL:
+ snprintf(tmpstr, sizeof(tmpstr), "Conventional");
+ break;
+ case DISK_ZONE_TYPE_SEQ_PREFERRED:
+ case DISK_ZONE_TYPE_SEQ_REQUIRED:
+ snprintf(tmpstr, sizeof(tmpstr), "Seq%c%s",
+ word_sep, (entry->zone_type ==
+ DISK_ZONE_TYPE_SEQ_PREFERRED) ? "Preferred" :
+ "Required");
+ break;
+ default:
+ snprintf(tmpstr, sizeof(tmpstr), "Zone%ctype%c%#x",
+ word_sep, word_sep, entry->zone_type);
+ break;
+ }
+ printf("%*s, ", field_widths[ZONE_FW_TYPE], tmpstr);
+
+ switch (entry->zone_condition) {
+ case DISK_ZONE_COND_NOT_WP:
+ snprintf(tmpstr, sizeof(tmpstr), "NWP");
+ break;
+ case DISK_ZONE_COND_EMPTY:
+ snprintf(tmpstr, sizeof(tmpstr), "Empty");
+ break;
+ case DISK_ZONE_COND_IMPLICIT_OPEN:
+ snprintf(tmpstr, sizeof(tmpstr), "Implicit%cOpen",
+ word_sep);
+ break;
+ case DISK_ZONE_COND_EXPLICIT_OPEN:
+ snprintf(tmpstr, sizeof(tmpstr), "Explicit%cOpen",
+ word_sep);
+ break;
+ case DISK_ZONE_COND_CLOSED:
+ snprintf(tmpstr, sizeof(tmpstr), "Closed");
+ break;
+ case DISK_ZONE_COND_READONLY:
+ snprintf(tmpstr, sizeof(tmpstr), "Readonly");
+ break;
+ case DISK_ZONE_COND_FULL:
+ snprintf(tmpstr, sizeof(tmpstr), "Full");
+ break;
+ case DISK_ZONE_COND_OFFLINE:
+ snprintf(tmpstr, sizeof(tmpstr), "Offline");
+ break;
+ default:
+ snprintf(tmpstr, sizeof(tmpstr), "%#x",
+ entry->zone_condition);
+ break;
+ }
+
+ printf("%*s, ", field_widths[ZONE_FW_COND], tmpstr);
+
+ if (entry->zone_flags & DISK_ZONE_FLAG_NON_SEQ)
+ snprintf(tmpstr, sizeof(tmpstr), "Non%cSequential",
+ word_sep);
+ else
+ snprintf(tmpstr, sizeof(tmpstr), "Sequential");
+
+ printf("%*s, ", field_widths[ZONE_FW_SEQ], tmpstr);
+
+ if (entry->zone_flags & DISK_ZONE_FLAG_RESET)
+ snprintf(tmpstr, sizeof(tmpstr), "Reset%cNeeded",
+ word_sep);
+ else
+ snprintf(tmpstr, sizeof(tmpstr), "No%cReset%cNeeded",
+ word_sep, word_sep);
+
+ printf("%*s\n", field_widths[ZONE_FW_RESET], tmpstr);
+
+ next_lba = entry->zone_start_lba + entry->zone_length;
+ }
+bailout:
+ report->starting_id = next_lba;
+
+ return (status);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int all_zones = 0;
+ int error = 0;
+ int action = -1, rep_option = -1;
+ int fd = -1;
+ uint64_t lba = 0;
+ zone_output_flags out_flags = ZONE_OF_NORMAL;
+ char *filename = NULL;
+ struct disk_zone_args zone_args;
+ struct disk_zone_rep_entry *entries = NULL;
+ uint32_t num_entries = 16384;
+ zone_print_status zp_status;
+ int first_pass = 1;
+ size_t entry_alloc_size;
+ int open_flags = O_RDONLY;
+
+ while ((c = getopt(argc, argv, "ac:d:hl:o:P:?")) != -1) {
+ switch (c) {
+ case 'a':
+ all_zones = 1;
+ break;
+ case 'c': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(zone_cmd_map,
+ (sizeof(zone_cmd_map) / sizeof(zone_cmd_map[0])),
+ optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ action = zone_cmd_map[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "zone command",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'd':
+ filename = strdup(optarg);
+ if (filename == NULL)
+ err(1, "Unable to allocate memory for "
+ "filename");
+ break;
+ case 'l': {
+ char *endptr;
+
+ lba = strtoull(optarg, &endptr, 0);
+ if (*endptr != '\0') {
+ warnx("%s: invalid lba argument %s", __func__,
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'o': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(zone_rep_opts,
+ (sizeof(zone_rep_opts) /
+ sizeof(zone_rep_opts[0])),
+ optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ rep_option = zone_rep_opts[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "report zones",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ case 'P': {
+ scsi_nv_status status;
+ int entry_num;
+
+ status = scsi_get_nv(zone_print_opts,
+ (sizeof(zone_print_opts) /
+ sizeof(zone_print_opts[0])), optarg, &entry_num,
+ SCSI_NV_FLAG_IG_CASE);
+ if (status == SCSI_NV_FOUND)
+ out_flags = zone_print_opts[entry_num].value;
+ else {
+ warnx("%s: %s: %s option %s", __func__,
+ (status == SCSI_NV_AMBIGUOUS) ?
+ "ambiguous" : "invalid", "print",
+ optarg);
+ error = 1;
+ goto bailout;
+ }
+ break;
+ }
+ default:
+ error = 1;
+ case 'h': /*FALLTHROUGH*/
+ usage(error);
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+ }
+
+ if (filename == NULL) {
+ warnx("You must specify a device with -d");
+ error = 1;
+ }
+ if (action == -1) {
+ warnx("You must specify an action with -c");
+ error = 1;
+ }
+
+ if (error != 0) {
+ usage(error);
+ goto bailout;
+ }
+
+ bzero(&zone_args, sizeof(zone_args));
+
+ zone_args.zone_cmd = action;
+
+ switch (action) {
+ case DISK_ZONE_OPEN:
+ case DISK_ZONE_CLOSE:
+ case DISK_ZONE_FINISH:
+ case DISK_ZONE_RWP:
+ open_flags = O_RDWR;
+ zone_args.zone_params.rwp.id = lba;
+ if (all_zones != 0)
+ zone_args.zone_params.rwp.flags |=
+ DISK_ZONE_RWP_FLAG_ALL;
+ break;
+ case DISK_ZONE_REPORT_ZONES: {
+ entry_alloc_size = num_entries *
+ sizeof(struct disk_zone_rep_entry);
+ entries = malloc(entry_alloc_size);
+ if (entries == NULL) {
+ warn("Could not allocate %zu bytes",
+ entry_alloc_size);
+ error = 1;
+ goto bailout;
+ }
+ zone_args.zone_params.report.entries_allocated = num_entries;
+ zone_args.zone_params.report.entries = entries;
+ zone_args.zone_params.report.starting_id = lba;
+ if (rep_option != -1)
+ zone_args.zone_params.report.rep_options = rep_option;
+ break;
+ }
+ case DISK_ZONE_GET_PARAMS:
+ break;
+ default:
+ warnx("Unknown action %d", action);
+ error = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+
+ fd = open(filename, open_flags);
+ if (fd == -1) {
+ warn("Unable to open device %s", filename);
+ error = 1;
+ goto bailout;
+ }
+next_chunk:
+ error = ioctl(fd, DIOCZONECMD, &zone_args);
+ if (error == -1) {
+ warn("DIOCZONECMD ioctl failed");
+ error = 1;
+ goto bailout;
+ }
+
+ switch (action) {
+ case DISK_ZONE_OPEN:
+ case DISK_ZONE_CLOSE:
+ case DISK_ZONE_FINISH:
+ case DISK_ZONE_RWP:
+ break;
+ case DISK_ZONE_REPORT_ZONES:
+ zp_status = zonectl_print_rz(&zone_args.zone_params.report,
+ out_flags, first_pass);
+ if (zp_status == ZONE_PRINT_MORE_DATA) {
+ first_pass = 0;
+ bzero(entries, entry_alloc_size);
+ zone_args.zone_params.report.entries_filled = 0;
+ goto next_chunk;
+ } else if (zp_status == ZONE_PRINT_ERROR)
+ error = 1;
+ break;
+ case DISK_ZONE_GET_PARAMS:
+ zonectl_print_params(&zone_args.zone_params.disk_params);
+ break;
+ default:
+ warnx("Unknown action %d", action);
+ error = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+bailout:
+ free(entries);
+
+ if (fd != -1)
+ close(fd);
+ exit (error);
+}

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 2, 7:07 AM (2 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29147365
Default Alt Text
D6147.id16470.diff (264 KB)

Event Timeline