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 August 6, 2015 +.Dd April 8, 2016 .Dt CAMCONTROL 8 .Os .Sh NAME @@ -320,6 +320,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 @@ -2037,11 +2057,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 @@ -2088,6 +2108,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 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 +.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 @@ -2321,6 +2635,69 @@ 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 +This will set the timer for the Idle_a power condition on drive 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 +This will tell drive da4 to go to the Standby_z power state (which should +be the drive's lowest power state) and hold in that state until it is +explicitly released by another goto command. +.Pp +.Bd -literal -offset indent +camcontrol epc da2 -c status -P +.Ed +.Pp +This will report the current power state, and only the power state, of +drive da2. +.Pp +.Bd -literal -offset indent +camcontrol epc ada0 -c list +.Ed +.Pp +This will display the ATA Power Conditions log (Log Address 0x08) for +drive ada0. .Sh SEE ALSO .Xr cam 3 , .Xr cam_cdbparse 3 , Index: sbin/camcontrol/camcontrol.c =================================================================== --- sbin/camcontrol/camcontrol.c +++ sbin/camcontrol/camcontrol.c @@ -100,7 +100,9 @@ CAM_CMD_APM = 0x00000021, CAM_CMD_AAM = 0x00000022, CAM_CMD_ATTRIB = 0x00000023, - CAM_CMD_OPCODES = 0x00000024 + CAM_CMD_OPCODES = 0x00000024, + CAM_CMD_ZONE = 0x00000025, + CAM_CMD_EPC = 0x00000026 } cam_cmdmask; typedef enum { @@ -228,6 +230,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:o: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}, @@ -5070,13 +5074,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, @@ -5092,11 +5099,19 @@ 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; } 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, @@ -5107,15 +5122,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) { @@ -8736,6 +8895,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) @@ -8777,6 +8941,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" @@ -8947,6 +9113,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 */ } @@ -9299,6 +9486,14 @@ error = scsiopcodes(cam_dev, argc, argv, combinedopt, retry_count, timeout, arglist & CAM_ARG_VERBOSE); 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#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 @@ -690,7 +690,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, @@ -702,12 +702,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: @@ -845,7 +854,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, @@ -857,12 +866,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,632 @@ +/*- + * 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) + */ +/* + * SCSI and ATA Shingled Media Recording (SMR) support for camcontrol(8). + * This is an implementation of the SCSI ZBC and ATA ZAC specs. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 = 0, valid_len = 0; + camcontrol_devtype devtype; + int ata_format = 0; + int first_pass = 1; + zone_print_status zp_status; + zone_output_flags out_flags = ZONE_OF_NORMAL; + 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 '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 ", __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) { + + alloc_len = sizeof(struct scsi_report_zones_hdr) + + (sizeof(struct scsi_report_zones_desc) * 4096); + + 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: { + uint16_t features; + + /* + * XXX KDM support the partial bit? + */ + features = action; + if (rep_option != -1) + features |= (rep_option << 8); + + 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, + /*ata_flags*/ AP_FLAG_BYT_BLOK_BLOCKS | + AP_FLAG_TLEN_SECT_CNT | + AP_FLAG_TDIR_FROM_DEV, + /*features*/ features, + /*sector_count*/ ZAC_ATA_SECTOR_COUNT(alloc_len), + /*lba*/ lba, + /*command*/ ATA_ZAC_MANAGEMENT_IN, + /*auxiliary*/ 0, + /*data_ptr*/ data_ptr, + /*dxfer_len*/ ZAC_ATA_SECTOR_COUNT(alloc_len)*512, + /*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; + } + + 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: { + uint16_t features; + + /* + * Note that we're taking advantage of the fact + * that the action numbers are the same between the + * ZBC and ZAC specs. + */ + features = action & 0xf; + if (all_zones != 0) + features |= (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*/ 0, + /*lba*/ lba, + /*command*/ ATA_ZAC_MANAGEMENT_OUT, + /*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 " + "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); + + 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 @@ -116,6 +116,7 @@ case 0xaa: return ("WRITE_UNCORRECTABLE48 FLAGGED"); } return "WRITE_UNCORRECTABLE48"; + case 0x4a: return ("ZAC_MANAGEMENT_IN"); case 0x51: return ("CONFIGURE_STREAM"); case 0x60: return ("READ_FPDMA_QUEUED"); case 0x61: return ("WRITE_FPDMA_QUEUED"); @@ -136,6 +137,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"); @@ -463,7 +465,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 +537,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 +926,136 @@ 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 __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) +{ + uint8_t command_out, ata_flags; + uint16_t features_out, sectors_out; + +#if 0 + if ((protocol & CAM_ATAIO_FPDMA) == 0) { +#endif + 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); + } +#if 0 + } else { + /* + * XXX KDM how do we specify the action here? + */ + if (dxfer_len == 0) { + command_out = ATA_NCQ_NON_DATA; + features_out = ATA_NCQ_ZAC_MGMT_OUT; + features_out |= ((sectors_out & 0xff) << 8); + sectors_out = 0; + } else { + command_out = ATA_SEND_FPDMA_QUEUED; + sectors_out = (ATA_SFPDMA_ZAC_MGMT_OUT << 8); + features_out = ((dxfer_len >> 9) & 0xffff); + } + + ata_flags = CAM_ATAIO_FPDMA; + } +#endif + + cam_fill_ataio(ataio, + /*retries*/ 1, + /*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); + +} + +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) +{ + uint8_t command_out, ata_flags; + uint16_t features_out, sectors_out; + + +#if 0 + if ((protocol & CAM_ATAIO_FPDMA) == 0) { +#endif + command_out = ATA_ZAC_MANAGEMENT_IN; + /* XXX KDM put a macro here */ + features_out = (zm_action & 0xf) | (zone_flags << 8); + if (dxfer_len == 0) { + /* + * XXX KDM there is currently not a defined action + * of ZAC Management In that specifies no data. So + * callers shouldn't be doing this currently. But, + * just in case one is defined later, set things + * properly here. + */ + ata_flags = 0; + sectors_out = 0; + } else { + ata_flags = CAM_ATAIO_DMA; + sectors_out = ((dxfer_len >> 9) & 0xffff); + } +#if 0 + } else { + /* + * XXX KDM NCQ encapsulation cannot work currently. It + * requires using the Auxiliary register to hold + * Features(15:0), but there is no way to represent that + * in struct ata_cmd. Need to add the auxiliary register, + * backward compatibility glue, and update at least some + * of the ATA drivers to support it. It will also require + * drive support. + */ + if (dxfer_len == 0) { + command_out = ATA_NCQ_NON_DATA; + features_out = ATA_NCQ_ZAC_MGMT_IN; + features_out |= ((sectors_out & 0xff) << 8); + sectors_out = 0; + } else { + command_out = ATA_RECV_FPDMA_QUEUED; + auxiliary = ATA_RFPDMA_ZAC_MGMT_IN | (zone_flags << 8); + features_out = ((dxfer_len >> 9) & 0xffff); + sectors_out = + } + ata_flags = CAM_ATAIO_FPDMA; + } +#endif + + cam_fill_ataio(ataio, + /*retries*/ 1, + /*cbfcnp*/ cbfcnp, + /*flags*/ (dxfer_len > 0) ? CAM_DIR_IN : 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); +} Index: sys/cam/ata/ata_da.c =================================================================== --- sys/cam/ata/ata_da.c +++ sys/cam/ata/ata_da.c @@ -43,9 +43,11 @@ #include #include #include +#include #include #include #include +#include #include #endif /* _KERNEL */ @@ -58,6 +60,8 @@ #include #include #include +#include +#include #include #include @@ -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 = 0x000002, + ADA_FLAG_CAN_FLUSHCACHE = 0x000004, + ADA_FLAG_CAN_NCQ = 0x000008, + ADA_FLAG_CAN_DMA = 0x000010, + ADA_FLAG_NEED_OTAG = 0x000020, + ADA_FLAG_WAS_OTAG = 0x000040, + ADA_FLAG_CAN_TRIM = 0x000080, + ADA_FLAG_OPEN = 0x000100, + ADA_FLAG_SCTX_INIT = 0x000200, + ADA_FLAG_CAN_CFA = 0x000400, + ADA_FLAG_CAN_POWERMGT = 0x000800, + ADA_FLAG_CAN_DMA48 = 0x001000, + ADA_FLAG_CAN_LOG = 0x002000, + ADA_FLAG_CAN_IDLOG = 0x004000, + ADA_FLAG_CAN_SUPCAP = 0x008000, + ADA_FLAG_CAN_ZONE = 0x010000, + ADA_FLAG_CAN_WCACHE = 0x020000, + ADA_FLAG_CAN_RAHEAD = 0x040000, + ADA_FLAG_PROBED = 0x080000, + ADA_FLAG_ANNOUNCED = 0x100000, + ADA_FLAG_DIRTY = 0x200000, + ADA_FLAG_CAN_NCQ_TRIM = 0x400000, /* CAN_TRIM also set */ + ADA_FLAG_PIM_CAN_NCQ_TRIM = 0x800000 } 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_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); + + 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) { @@ -1555,7 +1780,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 +1794,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 +1831,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 +1980,206 @@ 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*/ 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); + 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*/ 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 +2367,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 +2422,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 +2765,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 +2773,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 +2830,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 +2850,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 +2862,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 +2874,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; @@ -8134,23 +8144,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 @@ -8178,6 +8195,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 #include #include +#include #include #include #endif /* _KERNEL */ @@ -63,12 +64,20 @@ #include #include - -#ifndef _KERNEL #include -#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; /* @@ -1824,6 +1938,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, @@ -2142,6 +2279,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) { @@ -2206,6 +2409,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); /* @@ -2287,7 +2507,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) { @@ -2355,6 +2575,299 @@ 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); + 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) { @@ -2468,7 +2981,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); @@ -2658,15 +3185,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; } @@ -2684,7 +3224,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; + } + } } /* @@ -3048,6 +3834,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; @@ -3142,11 +4075,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; @@ -3564,11 +4500,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. @@ -3575,16 +4514,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; @@ -3614,10 +4592,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; @@ -3649,14 +4631,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, @@ -3668,9 +4695,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; @@ -4162,3 +5637,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/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 = "UKNOWN"; + 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; @@ -372,17 +383,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 auxilary */ #define ATA_SFPDMA_HYBRID_EVICT 0x01 /* Hybrid Evict */ #define ATA_SFPDMA_WLDMA 0x02 /* Write Log DMA EXT */ +#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 +435,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 +455,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 +584,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 +#include /* 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 #include +#include #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 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 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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 = 2048; + 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); +}