diff options
Diffstat (limited to 'sys/cam/scsi/scsi_da.c')
-rw-r--r-- | sys/cam/scsi/scsi_da.c | 6639 |
1 files changed, 6639 insertions, 0 deletions
diff --git a/sys/cam/scsi/scsi_da.c b/sys/cam/scsi/scsi_da.c new file mode 100644 index 000000000000..490f75336efd --- /dev/null +++ b/sys/cam/scsi/scsi_da.c @@ -0,0 +1,6639 @@ +/*- + * Implementation of SCSI Direct Access Peripheral driver for CAM. + * + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 1997 Justin T. Gibbs. + * 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, immediately at the beginning of the file. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> + +#ifdef _KERNEL +#include "opt_da.h" +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bio.h> +#include <sys/sysctl.h> +#include <sys/taskqueue.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/conf.h> +#include <sys/devicestat.h> +#include <sys/eventhandler.h> +#include <sys/malloc.h> +#include <sys/cons.h> +#include <sys/endian.h> +#include <sys/proc.h> +#include <sys/sbuf.h> +#include <geom/geom.h> +#include <geom/geom_disk.h> +#include <machine/atomic.h> +#endif /* _KERNEL */ + +#ifndef _KERNEL +#include <stdio.h> +#include <string.h> +#endif /* _KERNEL */ + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_periph.h> +#include <cam/cam_xpt_periph.h> +#ifdef _KERNEL +#include <cam/cam_xpt_internal.h> +#endif /* _KERNEL */ +#include <cam/cam_sim.h> +#include <cam/cam_iosched.h> + +#include <cam/scsi/scsi_message.h> +#include <cam/scsi/scsi_da.h> + +#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_WP, + DA_STATE_PROBE_RC, + DA_STATE_PROBE_RC16, + DA_STATE_PROBE_LBP, + 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 = 0x000001, + DA_FLAG_NEW_PACK = 0x000002, + DA_FLAG_PACK_LOCKED = 0x000004, + DA_FLAG_PACK_REMOVABLE = 0x000008, + DA_FLAG_ROTATING = 0x000010, + 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_FLAG_TUR_PENDING = 0x080000, + DA_FLAG_UNMAPPEDIO = 0x100000 +} da_flags; +#define DA_FLAG_STRING \ + "\020" \ + "\001PACK_INVALID" \ + "\002NEW_PACK" \ + "\003PACK_LOCKED" \ + "\004PACK_REMOVABLE" \ + "\005ROTATING" \ + "\006NEED_OTAG" \ + "\007WAS_OTAG" \ + "\010RETRY_UA" \ + "\011OPEN" \ + "\012SCTX_INIT" \ + "\013CAN_RC16" \ + "\014PROBED" \ + "\015DIRTY" \ + "\016ANNOUCNED" \ + "\017CAN_ATA_DMA" \ + "\020CAN_ATA_LOG" \ + "\021CAN_ATA_IDLOG" \ + "\022CAN_ATA_SUPACP" \ + "\023CAN_ATA_ZONE" \ + "\024TUR_PENDING" \ + "\025UNMAPPEDIO" + +typedef enum { + DA_Q_NONE = 0x00, + DA_Q_NO_SYNC_CACHE = 0x01, + DA_Q_NO_6_BYTE = 0x02, + DA_Q_NO_PREVENT = 0x04, + DA_Q_4K = 0x08, + DA_Q_NO_RC16 = 0x10, + DA_Q_NO_UNMAP = 0x20, + DA_Q_RETRY_BUSY = 0x40, + DA_Q_SMR_DM = 0x80, + DA_Q_STRICT_UNMAP = 0x100, + DA_Q_128KB = 0x200 +} da_quirks; + +#define DA_Q_BIT_STRING \ + "\020" \ + "\001NO_SYNC_CACHE" \ + "\002NO_6_BYTE" \ + "\003NO_PREVENT" \ + "\0044K" \ + "\005NO_RC16" \ + "\006NO_UNMAP" \ + "\007RETRY_BUSY" \ + "\010SMR_DM" \ + "\011STRICT_UNMAP" \ + "\012128KB" + +typedef enum { + DA_CCB_PROBE_RC = 0x01, + DA_CCB_PROBE_RC16 = 0x02, + DA_CCB_PROBE_LBP = 0x03, + DA_CCB_PROBE_BLK_LIMITS = 0x04, + DA_CCB_PROBE_BDC = 0x05, + DA_CCB_PROBE_ATA = 0x06, + DA_CCB_BUFFER_IO = 0x07, + DA_CCB_DUMP = 0x0A, + DA_CCB_DELETE = 0x0B, + DA_CCB_TUR = 0x0C, + 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_PROBE_WP = 0x12, + DA_CCB_TYPE_MASK = 0x1F, + DA_CCB_RETRY_UA = 0x20 +} da_ccb_state; + +/* + * Order here is important for method choice + * + * We prefer ATA_TRIM as tests run against a Sandforce 2281 SSD attached to + * LSI 2008 (mps) controller (FW: v12, Drv: v14) resulted 20% quicker deletes + * using ATA_TRIM than the corresponding UNMAP results for a real world mysql + * import taking 5mins. + * + */ +typedef enum { + DA_DELETE_NONE, + DA_DELETE_DISABLE, + DA_DELETE_ATA_TRIM, + DA_DELETE_UNMAP, + DA_DELETE_WS16, + DA_DELETE_WS10, + DA_DELETE_ZERO, + DA_DELETE_MIN = DA_DELETE_ATA_TRIM, + 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; +static da_delete_func_t da_delete_unmap; +static da_delete_func_t da_delete_ws; + +static const void * da_delete_functions[] = { + NULL, + NULL, + da_delete_trim, + da_delete_unmap, + da_delete_ws, + da_delete_ws, + da_delete_ws +}; + +static const char *da_delete_method_names[] = + { "NONE", "DISABLE", "ATA_TRIM", "UNMAP", "WS16", "WS10", "ZERO" }; +static const char *da_delete_method_desc[] = + { "NONE", "DISABLED", "ATA TRIM", "UNMAP", "WRITE SAME(16) with UNMAP", + "WRITE SAME(10) with UNMAP", "ZERO" }; + +/* Offsets into our private area for storing information */ +#define ccb_state ppriv_field0 +#define ccb_bp ppriv_ptr1 + +struct disk_params { + u_int8_t heads; + u_int32_t cylinders; + u_int8_t secs_per_track; + u_int32_t secsize; /* Number of bytes/sector */ + u_int64_t sectors; /* total number sectors */ + u_int stripesize; + u_int stripeoffset; +}; + +#define UNMAP_RANGE_MAX 0xffffffff +#define UNMAP_HEAD_SIZE 8 +#define UNMAP_RANGE_SIZE 16 +#define UNMAP_MAX_RANGES 2048 /* Protocol Max is 4095 */ +#define UNMAP_BUF_SIZE ((UNMAP_MAX_RANGES * UNMAP_RANGE_SIZE) + \ + UNMAP_HEAD_SIZE) + +#define WS10_MAX_BLKS 0xffff +#define WS16_MAX_BLKS 0xffffffff +#define ATA_TRIM_MAX_RANGES ((UNMAP_BUF_SIZE / \ + (ATA_DSM_RANGE_SIZE * ATA_DSM_BLK_SIZE)) * ATA_DSM_BLK_SIZE) + +#define DA_WORK_TUR (1 << 16) + +typedef enum { + DA_REF_OPEN = 1, + DA_REF_OPEN_HOLD, + DA_REF_CLOSE_HOLD, + DA_REF_PROBE_HOLD, + DA_REF_TUR, + DA_REF_GEOM, + DA_REF_SYSCTL, + DA_REF_REPROBE, + DA_REF_MAX /* KEEP LAST */ +} da_ref_token; + +struct da_softc { + struct cam_iosched_softc *cam_iosched; + struct bio_queue_head delete_run_queue; + LIST_HEAD(, ccb_hdr) pending_ccbs; + int refcount; /* Active xpt_action() calls */ + da_state state; + da_flags flags; + da_quirks quirks; + int minimum_cmd_size; + int error_inject; + int trim_max_ranges; + int delete_available; /* Delete methods possibly available */ + 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 */ + uint32_t unmap_gran; + uint32_t unmap_gran_align; + uint64_t ws_max_blks; + uint64_t trim_count; + uint64_t trim_ranges; + uint64_t trim_lbas; + da_delete_methods delete_method_pref; + da_delete_methods delete_method; + da_delete_func_t *delete_func; + int p_type; + struct disk_params params; + struct disk *disk; + union ccb saved_ccb; + struct task sysctl_task; + struct sysctl_ctx_list sysctl_ctx; + struct sysctl_oid *sysctl_tree; + struct callout sendordered_c; + uint64_t wwpn; + uint8_t unmap_buf[UNMAP_BUF_SIZE]; + struct scsi_read_capacity_data_long rcaplong; + struct callout mediapoll_c; + int ref_flags[DA_REF_MAX]; +#ifdef CAM_IO_STATS + struct sysctl_ctx_list sysctl_stats_ctx; + struct sysctl_oid *sysctl_stats_tree; + u_int errors; + u_int timeouts; + u_int invalidations; +#endif +#define DA_ANNOUNCETMP_SZ 160 + char announce_temp[DA_ANNOUNCETMP_SZ]; +#define DA_ANNOUNCE_SZ 400 + char announcebuf[DA_ANNOUNCE_SZ]; +}; + +#define dadeleteflag(softc, delete_method, enable) \ + if (enable) { \ + softc->delete_available |= (1 << delete_method); \ + } else { \ + softc->delete_available &= ~(1 << delete_method); \ + } + +struct da_quirk_entry { + struct scsi_inquiry_pattern inq_pat; + da_quirks quirks; +}; + +static const char quantum[] = "QUANTUM"; +static const char microp[] = "MICROP"; + +static struct da_quirk_entry da_quirk_table[] = +{ + /* SPI, FC devices */ + { + /* + * Fujitsu M2513A MO drives. + * Tested devices: M2513A2 firmware versions 1200 & 1300. + * (dip switch selects whether T_DIRECT or T_OPTICAL device) + * Reported by: W.Scholten <whs@xs4all.nl> + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "FUJITSU", "M2513A", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* See above. */ + {T_OPTICAL, SIP_MEDIA_REMOVABLE, "FUJITSU", "M2513A", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * This particular Fujitsu drive doesn't like the + * synchronize cache command. + * Reported by: Tom Jackson <toj@gorilla.net> + */ + {T_DIRECT, SIP_MEDIA_FIXED, "FUJITSU", "M2954*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * This drive doesn't like the synchronize cache command + * either. Reported by: Matthew Jacob <mjacob@feral.com> + * in NetBSD PR kern/6027, August 24, 1998. + */ + {T_DIRECT, SIP_MEDIA_FIXED, microp, "2217*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * This drive doesn't like the synchronize cache command + * either. Reported by: Hellmuth Michaelis (hm@kts.org) + * (PR 8882). + */ + {T_DIRECT, SIP_MEDIA_FIXED, microp, "2112*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Doesn't like the synchronize cache command. + * Reported by: Blaz Zupan <blaz@gold.amis.net> + */ + {T_DIRECT, SIP_MEDIA_FIXED, "NEC", "D3847*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Doesn't like the synchronize cache command. + * Reported by: Blaz Zupan <blaz@gold.amis.net> + */ + {T_DIRECT, SIP_MEDIA_FIXED, quantum, "MAVERICK 540S", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Doesn't like the synchronize cache command. + */ + {T_DIRECT, SIP_MEDIA_FIXED, quantum, "LPS525S", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Doesn't like the synchronize cache command. + * Reported by: walter@pelissero.de + */ + {T_DIRECT, SIP_MEDIA_FIXED, quantum, "LPS540S", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Doesn't work correctly with 6 byte reads/writes. + * Returns illegal request, and points to byte 9 of the + * 6-byte CDB. + * Reported by: Adam McDougall <bsdx@spawnet.com> + */ + {T_DIRECT, SIP_MEDIA_FIXED, quantum, "VIKING 4*", "*"}, + /*quirks*/ DA_Q_NO_6_BYTE + }, + { + /* See above. */ + {T_DIRECT, SIP_MEDIA_FIXED, quantum, "VIKING 2*", "*"}, + /*quirks*/ DA_Q_NO_6_BYTE + }, + { + /* + * Doesn't like the synchronize cache command. + * Reported by: walter@pelissero.de + */ + {T_DIRECT, SIP_MEDIA_FIXED, "CONNER", "CP3500*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * The CISS RAID controllers do not support SYNC_CACHE + */ + {T_DIRECT, SIP_MEDIA_FIXED, "COMPAQ", "RAID*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * The STEC SSDs sometimes hang on UNMAP. + */ + {T_DIRECT, SIP_MEDIA_FIXED, "STEC", "*", "*"}, + /*quirks*/ DA_Q_NO_UNMAP + }, + { + /* + * VMware returns BUSY status when storage has transient + * connectivity problems, so better wait. + * Also VMware returns odd errors on misaligned UNMAPs. + */ + {T_DIRECT, SIP_MEDIA_FIXED, "VMware*", "*", "*"}, + /*quirks*/ DA_Q_RETRY_BUSY | DA_Q_STRICT_UNMAP + }, + /* USB mass storage devices supported by umass(4) */ + { + /* + * EXATELECOM (Sigmatel) i-Bead 100/105 USB Flash MP3 Player + * PR: kern/51675 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "EXATEL", "i-BEAD10*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Power Quotient Int. (PQI) USB flash key + * PR: kern/53067 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Generic*", "USB Flash Disk*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Creative Nomad MUVO mp3 player (USB) + * PR: kern/53094 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "CREATIVE", "NOMAD_MUVO", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT + }, + { + /* + * Jungsoft NEXDISK USB flash key + * PR: kern/54737 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "JUNGSOFT", "NEXDISK*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * FreeDik USB Mini Data Drive + * PR: kern/54786 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "FreeDik*", "Mini Data Drive", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Sigmatel USB Flash MP3 Player + * PR: kern/57046 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "SigmaTel", "MSCN", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT + }, + { + /* + * Neuros USB Digital Audio Computer + * PR: kern/63645 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "NEUROS", "dig. audio comp.", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * SEAGRAND NP-900 MP3 Player + * PR: kern/64563 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "SEAGRAND", "NP-900*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT + }, + { + /* + * iRiver iFP MP3 player (with UMS Firmware) + * PR: kern/54881, i386/63941, kern/66124 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "iRiver", "iFP*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Frontier Labs NEX IA+ Digital Audio Player, rev 1.10/0.01 + * PR: kern/70158 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "FL" , "Nex*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * ZICPlay USB MP3 Player with FM + * PR: kern/75057 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "ACTIONS*" , "USB DISK*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * TEAC USB floppy mechanisms + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "TEAC" , "FD-05*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Kingston DataTraveler II+ USB Pen-Drive. + * Reported by: Pawel Jakub Dawidek <pjd@FreeBSD.org> + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Kingston" , "DataTraveler II+", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * USB DISK Pro PMAP + * Reported by: jhs + * PR: usb/96381 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, " ", "USB DISK Pro", "PMAP"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Motorola E398 Mobile Phone (TransFlash memory card). + * Reported by: Wojciech A. Koszek <dunstan@FreeBSD.czest.pl> + * PR: usb/89889 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Motorola" , "Motorola Phone", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Qware BeatZkey! Pro + * PR: usb/79164 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "GENERIC", "USB DISK DEVICE", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Time DPA20B 1GB MP3 Player + * PR: usb/81846 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "USB2.0*", "(FS) FLASH DISK*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Samsung USB key 128Mb + * PR: usb/90081 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "USB-DISK", "FreeDik-FlashUsb", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Kingston DataTraveler 2.0 USB Flash memory. + * PR: usb/89196 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Kingston", "DataTraveler 2.0", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Creative MUVO Slim mp3 player (USB) + * PR: usb/86131 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "CREATIVE", "MuVo Slim", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT + }, + { + /* + * United MP5512 Portable MP3 Player (2-in-1 USB DISK/MP3) + * PR: usb/80487 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Generic*", "MUSIC DISK", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * SanDisk Micro Cruzer 128MB + * PR: usb/75970 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "SanDisk" , "Micro Cruzer", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * TOSHIBA TransMemory USB sticks + * PR: kern/94660 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "TOSHIBA", "TransMemory", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * PNY USB 3.0 Flash Drives + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "PNY", "USB 3.0 FD*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE | DA_Q_NO_RC16 + }, + { + /* + * PNY USB Flash keys + * PR: usb/75578, usb/72344, usb/65436 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "*" , "USB DISK*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Genesys GL3224 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Generic*", "STORAGE DEVICE*", + "120?"}, /*quirks*/ DA_Q_NO_SYNC_CACHE | DA_Q_4K | DA_Q_NO_RC16 + }, + { + /* + * Genesys 6-in-1 Card Reader + * PR: usb/94647 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Generic*", "STORAGE DEVICE*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Rekam Digital CAMERA + * PR: usb/98713 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "CAMERA*", "4MP-9J6*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * iRiver H10 MP3 player + * PR: usb/102547 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "iriver", "H10*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * iRiver U10 MP3 player + * PR: usb/92306 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "iriver", "U10*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * X-Micro Flash Disk + * PR: usb/96901 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "X-Micro", "Flash Disk", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * EasyMP3 EM732X USB 2.0 Flash MP3 Player + * PR: usb/96546 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "EM732X", "MP3 Player*", + "1.00"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Denver MP3 player + * PR: usb/107101 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "DENVER", "MP3 PLAYER", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Philips USB Key Audio KEY013 + * PR: usb/68412 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "PHILIPS", "Key*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE | DA_Q_NO_PREVENT + }, + { + /* + * JNC MP3 Player + * PR: usb/94439 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "JNC*" , "MP3 Player*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * SAMSUNG MP0402H + * PR: usb/108427 + */ + {T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "MP0402H", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * I/O Magic USB flash - Giga Bank + * PR: usb/108810 + */ + {T_DIRECT, SIP_MEDIA_FIXED, "GS-Magic", "stor*", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * JoyFly 128mb USB Flash Drive + * PR: 96133 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "USB 2.0", "Flash Disk*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * ChipsBnk usb stick + * PR: 103702 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "ChipsBnk", "USB*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Storcase (Kingston) InfoStation IFS FC2/SATA-R 201A + * PR: 129858 + */ + {T_DIRECT, SIP_MEDIA_FIXED, "IFS", "FC2/SATA-R*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Samsung YP-U3 mp3-player + * PR: 125398 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Samsung", "YP-U3", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Netac", "OnlyDisk*", + "2000"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Sony Cyber-Shot DSC cameras + * PR: usb/137035 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Sony", "Sony DSC", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE | DA_Q_NO_PREVENT + }, + { + {T_DIRECT, SIP_MEDIA_REMOVABLE, "Kingston", "DataTraveler G3", + "1.00"}, /*quirks*/ DA_Q_NO_PREVENT + }, + { + /* At least several Transcent USB sticks lie on RC16. */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "JetFlash", "Transcend*", + "*"}, /*quirks*/ DA_Q_NO_RC16 + }, + { + /* + * I-O Data USB Flash Disk + * PR: usb/211716 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "I-O DATA", "USB Flash Disk*", + "*"}, /*quirks*/ DA_Q_NO_RC16 + }, + { + /* + * SLC CHIPFANCIER USB drives + * PR: usb/234503 (RC10 right, RC16 wrong) + * 16GB, 32GB and 128GB confirmed to have same issue + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "*SLC", "CHIPFANCIER", + "*"}, /*quirks*/ DA_Q_NO_RC16 + }, + /* ATA/SATA devices over SAS/USB/... */ + { + /* Sandisk X400 */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SanDisk SD8SB8U1*", "*" }, + /*quirks*/DA_Q_128KB + }, + { + /* Hitachi Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "Hitachi", "H??????????E3*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Micron Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Micron 5100 MTFDDAK*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Samsung Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG HD155UI*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Samsung Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "HD155UI*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Samsung Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG HD204UI*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Samsung Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "HD204UI*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Barracuda Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST????DL*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Barracuda Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST????DL", "*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Barracuda Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST???DM*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Barracuda Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST???DM*", "*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Barracuda Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST????DM*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Barracuda Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST????DM", "*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9500423AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST950042", "3AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9500424AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST950042", "4AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9640423AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST964042", "3AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9640424AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST964042", "4AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9750420AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST975042", "0AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9750422AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST975042", "2AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST9750423AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST975042", "3AS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Thin Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "ST???LT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* Seagate Momentus Thin Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ST???LT*", "*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Caviar Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD????RS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Caviar Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "??RS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Caviar Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD????RX*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Caviar Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "??RX*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Caviar Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD??????RS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Caviar Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "????RS*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Caviar Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD??????RX*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Caviar Green Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "????RX*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Scorpio Black Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD???PKT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Scorpio Black Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "?PKT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Scorpio Black Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD?????PKT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Scorpio Black Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "???PKT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Scorpio Blue Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD???PVT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Scorpio Blue Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "?PVT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Scorpio Blue Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "WDC WD?????PVT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* WDC Scorpio Blue Advanced Format (4k) drives */ + { T_DIRECT, SIP_MEDIA_FIXED, "WDC WD??", "???PVT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Olympus digital cameras (C-3040ZOOM, C-2040ZOOM, C-1) + * PR: usb/97472 + */ + { T_DIRECT, SIP_MEDIA_REMOVABLE, "OLYMPUS", "C*", "*"}, + /*quirks*/ DA_Q_NO_6_BYTE | DA_Q_NO_SYNC_CACHE + }, + { + /* + * Olympus digital cameras (D-370) + * PR: usb/97472 + */ + { T_DIRECT, SIP_MEDIA_REMOVABLE, "OLYMPUS", "D*", "*"}, + /*quirks*/ DA_Q_NO_6_BYTE + }, + { + /* + * Olympus digital cameras (E-100RS, E-10). + * PR: usb/97472 + */ + { T_DIRECT, SIP_MEDIA_REMOVABLE, "OLYMPUS", "E*", "*"}, + /*quirks*/ DA_Q_NO_6_BYTE | DA_Q_NO_SYNC_CACHE + }, + { + /* + * Olympus FE-210 camera + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "OLYMPUS", "FE210*", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Pentax Digital Camera + * PR: usb/93389 + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "PENTAX", "DIGITAL CAMERA", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * LG UP3S MP3 player + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "LG", "UP3S", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * Laser MP3-2GA13 MP3 player + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "USB 2.0", "(HS) Flash Disk", + "*"}, /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + { + /* + * LaCie external 250GB Hard drive des by Porsche + * Submitted by: Ben Stuyts <ben@altesco.nl> + * PR: 121474 + */ + {T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "HM250JI", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE + }, + /* SATA SSDs */ + { + /* + * Corsair Force 2 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Corsair CSSD-F*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Corsair Force 3 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Corsair Force 3*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Corsair Neutron GTX SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "Corsair Neutron GTX*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Corsair Force GT & GS SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Corsair Force G*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Crucial M4 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "M4-CT???M4SSD2*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Crucial RealSSD C300 SSDs + * 4k optimised + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "C300-CTFDDAC???MAG*", + "*" }, /*quirks*/DA_Q_4K + }, + { + /* + * Intel 320 Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSA2CW*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Intel 330 Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSC2CT*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Intel 510 Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSC2MH*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Intel 520 Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSC2BW*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Intel S3610 Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSC2BX*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Intel X25-M Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "INTEL SSDSA2M*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Kingston E100 Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "KINGSTON SE100S3*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Kingston HyperX 3k SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "KINGSTON SH103S3*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Marvell SSDs (entry taken from OpenSolaris) + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "MARVELL SD88SA02*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * OCZ Agility 2 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "OCZ-AGILITY2*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * OCZ Agility 3 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "OCZ-AGILITY3*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * OCZ Deneva R Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "DENRSTE251M45*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * OCZ Vertex 2 SSDs (inc pro series) + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "OCZ?VERTEX2*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * OCZ Vertex 3 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "OCZ-VERTEX3*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * OCZ Vertex 4 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "OCZ-VERTEX4*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Samsung 750 Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Samsung SSD 750*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Samsung 830 Series SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG SSD 830 Series*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Samsung 840 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Samsung SSD 840*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Samsung 845 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Samsung SSD 845*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Samsung 850 SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "Samsung SSD 850*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Samsung 843T Series SSDs (MZ7WD*) + * Samsung PM851 Series SSDs (MZ7TE*) + * Samsung PM853T Series SSDs (MZ7GE*) + * Samsung SM863 Series SSDs (MZ7KM*) + * 4k optimised + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SAMSUNG MZ7*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Same as for SAMSUNG MZ7* but enable the quirks for SSD + * starting with MZ7* too + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "MZ7*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Same as above but enable the quirks for SSD SAMSUNG MZ7* + * connected via SATA-to-SAS interposer and because of this + * starting without "ATA" + */ + { T_DIRECT, SIP_MEDIA_FIXED, "SAMSUNG", "MZ7*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * SuperTalent TeraDrive CT SSDs + * 4k optimised & trim only works in 4k requests + 4k aligned + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "FTM??CT25H*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * XceedIOPS SATA SSDs + * 4k optimised + */ + { T_DIRECT, SIP_MEDIA_FIXED, "ATA", "SG9XCS2D*", "*" }, + /*quirks*/DA_Q_4K + }, + { + /* + * Hama Innostor USB-Stick + */ + { T_DIRECT, SIP_MEDIA_REMOVABLE, "Innostor", "Innostor*", "*" }, + /*quirks*/DA_Q_NO_RC16 + }, + { + /* + * 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", "ST8000AS000[23]*", "*" }, + /*quirks*/DA_Q_SMR_DM + }, + { + /* + * MX-ES USB Drive by Mach Xtreme + */ + { T_DIRECT, SIP_MEDIA_REMOVABLE, "MX", "MXUB3*", "*"}, + /*quirks*/DA_Q_NO_RC16 + }, +}; + +static disk_strategy_t dastrategy; +static dumper_t dadump; +static periph_init_t dainit; +static void daasync(void *callback_arg, u_int32_t code, + struct cam_path *path, void *arg); +static void dasysctlinit(void *context, int pending); +static int dasysctlsofttimeout(SYSCTL_HANDLER_ARGS); +static int dacmdsizesysctl(SYSCTL_HANDLER_ARGS); +static int dadeletemethodsysctl(SYSCTL_HANDLER_ARGS); +static int dabitsysctl(SYSCTL_HANDLER_ARGS); +static int daflagssysctl(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); +static off_t dadeletemaxsize(struct da_softc *softc, + da_delete_methods delete_method); +static void dadeletemethodchoose(struct da_softc *softc, + da_delete_methods default_method); +static void daprobedone(struct cam_periph *periph, union ccb *ccb); + +static periph_ctor_t daregister; +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 void dadone_probewp(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_proberc(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probelbp(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probeblklimits(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probebdc(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probeata(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probeatalogdir(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probeataiddir(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probeatasup(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probeatazone(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_probezone(struct cam_periph *periph, + union ccb *done_ccb); +static void dadone_tur(struct cam_periph *periph, + union ccb *done_ccb); +static int daerror(union ccb *ccb, u_int32_t cam_flags, + u_int32_t sense_flags); +static void daprevent(struct cam_periph *periph, int action); +static void dareprobe(struct cam_periph *periph); +static void dasetgeom(struct cam_periph *periph, uint32_t block_len, + uint64_t maxsector, + struct scsi_read_capacity_data_long *rcaplong, + size_t rcap_size); +static callout_func_t dasendorderedtag; +static void dashutdown(void *arg, int howto); +static callout_func_t damediapoll; + +#ifndef DA_DEFAULT_POLL_PERIOD +#define DA_DEFAULT_POLL_PERIOD 3 +#endif + +#ifndef DA_DEFAULT_TIMEOUT +#define DA_DEFAULT_TIMEOUT 60 /* Timeout in seconds */ +#endif + +#ifndef DA_DEFAULT_SOFTTIMEOUT +#define DA_DEFAULT_SOFTTIMEOUT 0 +#endif + +#ifndef DA_DEFAULT_RETRY +#define DA_DEFAULT_RETRY 4 +#endif + +#ifndef DA_DEFAULT_SEND_ORDERED +#define DA_DEFAULT_SEND_ORDERED 1 +#endif + +static int da_poll_period = DA_DEFAULT_POLL_PERIOD; +static int da_retry_count = DA_DEFAULT_RETRY; +static int da_default_timeout = DA_DEFAULT_TIMEOUT; +static sbintime_t da_default_softtimeout = DA_DEFAULT_SOFTTIMEOUT; +static int da_send_ordered = DA_DEFAULT_SEND_ORDERED; +static int da_disable_wp_detection = 0; +static int da_enable_biospeedup = 1; + +static SYSCTL_NODE(_kern_cam, OID_AUTO, da, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + "CAM Direct Access Disk driver"); +SYSCTL_INT(_kern_cam_da, OID_AUTO, poll_period, CTLFLAG_RWTUN, + &da_poll_period, 0, "Media polling period in seconds"); +SYSCTL_INT(_kern_cam_da, OID_AUTO, retry_count, CTLFLAG_RWTUN, + &da_retry_count, 0, "Normal I/O retry count"); +SYSCTL_INT(_kern_cam_da, OID_AUTO, default_timeout, CTLFLAG_RWTUN, + &da_default_timeout, 0, "Normal I/O timeout (in seconds)"); +SYSCTL_INT(_kern_cam_da, OID_AUTO, send_ordered, CTLFLAG_RWTUN, + &da_send_ordered, 0, "Send Ordered Tags"); +SYSCTL_INT(_kern_cam_da, OID_AUTO, disable_wp_detection, CTLFLAG_RWTUN, + &da_disable_wp_detection, 0, + "Disable detection of write-protected disks"); +SYSCTL_INT(_kern_cam_da, OID_AUTO, enable_biospeedup, CTLFLAG_RDTUN, + &da_enable_biospeedup, 0, "Enable BIO_SPEEDUP processing"); + +SYSCTL_PROC(_kern_cam_da, OID_AUTO, default_softtimeout, + CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, NULL, 0, + dasysctlsofttimeout, "I", + "Soft I/O timeout (ms)"); +TUNABLE_INT64("kern.cam.da.default_softtimeout", &da_default_softtimeout); + +/* + * DA_ORDEREDTAG_INTERVAL determines how often, relative + * to the default timeout, we check to see whether an ordered + * tagged transaction is appropriate to prevent simple tag + * starvation. Since we'd like to ensure that there is at least + * 1/2 of the timeout length left for a starved transaction to + * complete after we've sent an ordered tag, we must poll at least + * four times in every timeout period. This takes care of the worst + * case where a starved transaction starts during an interval that + * meets the requirement "don't send an ordered tag" test so it takes + * us two intervals to determine that a tag must be sent. + */ +#ifndef DA_ORDEREDTAG_INTERVAL +#define DA_ORDEREDTAG_INTERVAL 4 +#endif + +static struct periph_driver dadriver = +{ + dainit, "da", + TAILQ_HEAD_INITIALIZER(dadriver.units), /* generation */ 0 +}; + +PERIPHDRIVER_DECLARE(da, dadriver); + +static MALLOC_DEFINE(M_SCSIDA, "scsi_da", "scsi_da buffers"); + +/* + * This driver takes out references / holds in well defined pairs, never + * recursively. These macros / inline functions enforce those rules. They + * are only enabled with DA_TRACK_REFS or INVARIANTS. If DA_TRACK_REFS is + * defined to be 2 or larger, the tracking also includes debug printfs. + */ +#if defined(DA_TRACK_REFS) || defined(INVARIANTS) + +#ifndef DA_TRACK_REFS +#define DA_TRACK_REFS 1 +#endif + +#if DA_TRACK_REFS > 1 +static const char *da_ref_text[] = { + "bogus", + "open", + "open hold", + "close hold", + "reprobe hold", + "Test Unit Ready", + "Geom", + "sysctl", + "reprobe", + "max -- also bogus" +}; + +#define DA_PERIPH_PRINT(periph, msg, args...) \ + CAM_PERIPH_PRINT(periph, msg, ##args) +#else +#define DA_PERIPH_PRINT(periph, msg, args...) +#endif + +static inline void +token_sanity(da_ref_token token) +{ + if ((unsigned)token >= DA_REF_MAX) + panic("Bad token value passed in %d\n", token); +} + +static inline int +da_periph_hold(struct cam_periph *periph, int priority, da_ref_token token) +{ + int err = cam_periph_hold(periph, priority); + + token_sanity(token); + DA_PERIPH_PRINT(periph, "Holding device %s (%d): %d\n", + da_ref_text[token], token, err); + if (err == 0) { + int cnt; + struct da_softc *softc = periph->softc; + + cnt = atomic_fetchadd_int(&softc->ref_flags[token], 1); + if (cnt != 0) + panic("Re-holding for reason %d, cnt = %d", token, cnt); + } + return (err); +} + +static inline void +da_periph_unhold(struct cam_periph *periph, da_ref_token token) +{ + int cnt; + struct da_softc *softc = periph->softc; + + token_sanity(token); + DA_PERIPH_PRINT(periph, "Unholding device %s (%d)\n", + da_ref_text[token], token); + cnt = atomic_fetchadd_int(&softc->ref_flags[token], -1); + if (cnt != 1) + panic("Unholding %d with cnt = %d", token, cnt); + cam_periph_unhold(periph); +} + +static inline int +da_periph_acquire(struct cam_periph *periph, da_ref_token token) +{ + int err = cam_periph_acquire(periph); + + token_sanity(token); + DA_PERIPH_PRINT(periph, "acquiring device %s (%d): %d\n", + da_ref_text[token], token, err); + if (err == 0) { + int cnt; + struct da_softc *softc = periph->softc; + + cnt = atomic_fetchadd_int(&softc->ref_flags[token], 1); + if (cnt != 0) + panic("Re-refing for reason %d, cnt = %d", token, cnt); + } + return (err); +} + +static inline void +da_periph_release(struct cam_periph *periph, da_ref_token token) +{ + int cnt; + struct da_softc *softc = periph->softc; + + token_sanity(token); + DA_PERIPH_PRINT(periph, "releasing device %s (%d)\n", + da_ref_text[token], token); + cnt = atomic_fetchadd_int(&softc->ref_flags[token], -1); + if (cnt != 1) + panic("Releasing %d with cnt = %d", token, cnt); + cam_periph_release(periph); +} + +static inline void +da_periph_release_locked(struct cam_periph *periph, da_ref_token token) +{ + int cnt; + struct da_softc *softc = periph->softc; + + token_sanity(token); + DA_PERIPH_PRINT(periph, "releasing device (locked) %s (%d)\n", + da_ref_text[token], token); + cnt = atomic_fetchadd_int(&softc->ref_flags[token], -1); + if (cnt != 1) + panic("releasing (locked) %d with cnt = %d", token, cnt); + cam_periph_release_locked(periph); +} + +#define cam_periph_hold POISON +#define cam_periph_unhold POISON +#define cam_periph_acquire POISON +#define cam_periph_release POISON +#define cam_periph_release_locked POISON + +#else +#define da_periph_hold(periph, prio, token) cam_periph_hold((periph), (prio)) +#define da_periph_unhold(periph, token) cam_periph_unhold((periph)) +#define da_periph_acquire(periph, token) cam_periph_acquire((periph)) +#define da_periph_release(periph, token) cam_periph_release((periph)) +#define da_periph_release_locked(periph, token) cam_periph_release_locked((periph)) +#endif + +static int +daopen(struct disk *dp) +{ + struct cam_periph *periph; + struct da_softc *softc; + int error; + + periph = (struct cam_periph *)dp->d_drv1; + if (da_periph_acquire(periph, DA_REF_OPEN) != 0) { + return (ENXIO); + } + + cam_periph_lock(periph); + if ((error = da_periph_hold(periph, PRIBIO|PCATCH, DA_REF_OPEN_HOLD)) != 0) { + cam_periph_unlock(periph); + da_periph_release(periph, DA_REF_OPEN); + return (error); + } + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, + ("daopen\n")); + + softc = (struct da_softc *)periph->softc; + dareprobe(periph); + + /* Wait for the disk size update. */ + error = cam_periph_sleep(periph, &softc->disk->d_mediasize, PRIBIO, + "dareprobe", 0); + if (error != 0) + xpt_print(periph->path, "unable to retrieve capacity data\n"); + + if (periph->flags & CAM_PERIPH_INVALID) + error = ENXIO; + + if (error == 0 && (softc->flags & DA_FLAG_PACK_REMOVABLE) != 0 && + (softc->quirks & DA_Q_NO_PREVENT) == 0) + daprevent(periph, PR_PREVENT); + + if (error == 0) { + softc->flags &= ~DA_FLAG_PACK_INVALID; + softc->flags |= DA_FLAG_OPEN; + } + + da_periph_unhold(periph, DA_REF_OPEN_HOLD); + cam_periph_unlock(periph); + + if (error != 0) + da_periph_release(periph, DA_REF_OPEN); + + return (error); +} + +static int +daclose(struct disk *dp) +{ + struct cam_periph *periph; + struct da_softc *softc; + union ccb *ccb; + + periph = (struct cam_periph *)dp->d_drv1; + softc = (struct da_softc *)periph->softc; + cam_periph_lock(periph); + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, + ("daclose\n")); + + if (da_periph_hold(periph, PRIBIO, DA_REF_CLOSE_HOLD) == 0) { + /* Flush disk cache. */ + if ((softc->flags & DA_FLAG_DIRTY) != 0 && + (softc->quirks & DA_Q_NO_SYNC_CACHE) == 0 && + (softc->flags & DA_FLAG_PACK_INVALID) == 0) { + ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); + scsi_synchronize_cache(&ccb->csio, /*retries*/1, + /*cbfcnp*/NULL, MSG_SIMPLE_Q_TAG, + /*begin_lba*/0, /*lb_count*/0, SSD_FULL_SIZE, + 5 * 60 * 1000); + cam_periph_runccb(ccb, daerror, /*cam_flags*/0, + /*sense_flags*/SF_RETRY_UA | SF_QUIET_IR, + softc->disk->d_devstat); + softc->flags &= ~DA_FLAG_DIRTY; + xpt_release_ccb(ccb); + } + + /* Allow medium removal. */ + if ((softc->flags & DA_FLAG_PACK_REMOVABLE) != 0 && + (softc->quirks & DA_Q_NO_PREVENT) == 0) + daprevent(periph, PR_ALLOW); + + da_periph_unhold(periph, DA_REF_CLOSE_HOLD); + } + + /* + * If we've got removable media, mark the blocksize as + * unavailable, since it could change when new media is + * inserted. + */ + if ((softc->flags & DA_FLAG_PACK_REMOVABLE) != 0) + softc->disk->d_devstat->flags |= DEVSTAT_BS_UNAVAILABLE; + + softc->flags &= ~DA_FLAG_OPEN; + while (softc->refcount != 0) + cam_periph_sleep(periph, &softc->refcount, PRIBIO, "daclose", 1); + cam_periph_unlock(periph); + da_periph_release(periph, DA_REF_OPEN); + return (0); +} + +static void +daschedule(struct cam_periph *periph) +{ + struct da_softc *softc = (struct da_softc *)periph->softc; + + if (softc->state != DA_STATE_NORMAL) + return; + + cam_iosched_schedule(softc->cam_iosched, periph); +} + +/* + * Actually translate the requested transfer into one the physical driver + * can understand. The transfer is described by a buf and will include + * only one physical transfer. + */ +static void +dastrategy(struct bio *bp) +{ + struct cam_periph *periph; + struct da_softc *softc; + + periph = (struct cam_periph *)bp->bio_disk->d_drv1; + softc = (struct da_softc *)periph->softc; + + cam_periph_lock(periph); + + /* + * If the device has been made invalid, error out + */ + if ((softc->flags & DA_FLAG_PACK_INVALID)) { + cam_periph_unlock(periph); + biofinish(bp, NULL, ENXIO); + return; + } + + 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); + + /* + * Schedule ourselves for performing the work. + */ + daschedule(periph); + cam_periph_unlock(periph); + + return; +} + +static int +dadump(void *arg, void *virtual, vm_offset_t physical, off_t offset, size_t length) +{ + struct cam_periph *periph; + struct da_softc *softc; + u_int secsize; + struct ccb_scsiio csio; + struct disk *dp; + int error = 0; + + dp = arg; + periph = dp->d_drv1; + softc = (struct da_softc *)periph->softc; + secsize = softc->params.secsize; + + if ((softc->flags & DA_FLAG_PACK_INVALID) != 0) + return (ENXIO); + + memset(&csio, 0, sizeof(csio)); + if (length > 0) { + xpt_setup_ccb(&csio.ccb_h, periph->path, CAM_PRIORITY_NORMAL); + csio.ccb_h.ccb_state = DA_CCB_DUMP; + scsi_read_write(&csio, + /*retries*/0, + /*cbfcnp*/NULL, + MSG_ORDERED_Q_TAG, + /*read*/SCSI_RW_WRITE, + /*byte2*/0, + /*minimum_cmd_size*/ softc->minimum_cmd_size, + offset / secsize, + length / secsize, + /*data_ptr*/(u_int8_t *) virtual, + /*dxfer_len*/length, + /*sense_len*/SSD_FULL_SIZE, + da_default_timeout * 1000); + error = cam_periph_runccb((union ccb *)&csio, cam_periph_error, + 0, SF_NO_RECOVERY | SF_NO_RETRY, NULL); + if (error != 0) + printf("Aborting dump due to I/O error.\n"); + return (error); + } + + /* + * Sync the disk cache contents to the physical media. + */ + if ((softc->quirks & DA_Q_NO_SYNC_CACHE) == 0) { + xpt_setup_ccb(&csio.ccb_h, periph->path, CAM_PRIORITY_NORMAL); + csio.ccb_h.ccb_state = DA_CCB_DUMP; + scsi_synchronize_cache(&csio, + /*retries*/0, + /*cbfcnp*/NULL, + MSG_SIMPLE_Q_TAG, + /*begin_lba*/0,/* Cover the whole disk */ + /*lb_count*/0, + SSD_FULL_SIZE, + 5 * 1000); + error = cam_periph_runccb((union ccb *)&csio, cam_periph_error, + 0, SF_NO_RECOVERY | SF_NO_RETRY, NULL); + if (error != 0) + xpt_print(periph->path, "Synchronize cache failed\n"); + } + return (error); +} + +static int +dagetattr(struct bio *bp) +{ + int ret; + struct cam_periph *periph; + + if (g_handleattr_int(bp, "GEOM::canspeedup", da_enable_biospeedup)) + return (EJUSTRETURN); + + periph = (struct cam_periph *)bp->bio_disk->d_drv1; + cam_periph_lock(periph); + ret = xpt_getattr(bp->bio_data, bp->bio_length, bp->bio_attribute, + periph->path); + cam_periph_unlock(periph); + if (ret == 0) + bp->bio_completed = bp->bio_length; + return ret; +} + +static void +dainit(void) +{ + cam_status status; + + /* + * Install a global async callback. This callback will + * receive async callbacks like "new device found". + */ + status = xpt_register_async(AC_FOUND_DEVICE, daasync, NULL, NULL); + + if (status != CAM_REQ_CMP) { + printf("da: Failed to attach master async callback " + "due to status 0x%x!\n", status); + } else if (da_send_ordered) { + /* Register our shutdown event handler */ + if ((EVENTHANDLER_REGISTER(shutdown_post_sync, dashutdown, + NULL, SHUTDOWN_PRI_DEFAULT)) == NULL) + printf("dainit: shutdown event registration failed!\n"); + } +} + +/* + * Callback from GEOM, called when it has finished cleaning up its + * resources. + */ +static void +dadiskgonecb(struct disk *dp) +{ + struct cam_periph *periph; + + periph = (struct cam_periph *)dp->d_drv1; + da_periph_release(periph, DA_REF_GEOM); +} + +static void +daoninvalidate(struct cam_periph *periph) +{ + struct da_softc *softc; + + cam_periph_assert(periph, MA_OWNED); + softc = (struct da_softc *)periph->softc; + + /* + * De-register any async callbacks. + */ + xpt_register_async(0, daasync, periph, periph->path); + + softc->flags |= DA_FLAG_PACK_INVALID; +#ifdef CAM_IO_STATS + softc->invalidations++; +#endif + + /* + * Return all queued I/O with ENXIO. + * XXX Handle any transactions queued to the card + * with XPT_ABORT_CCB. + */ + cam_iosched_flush(softc->cam_iosched, NULL, ENXIO); + + /* + * Tell GEOM that we've gone away, we'll get a callback when it is + * done cleaning up its resources. + */ + disk_gone(softc->disk); +} + +static void +dacleanup(struct cam_periph *periph) +{ + struct da_softc *softc; + + softc = (struct da_softc *)periph->softc; + + cam_periph_unlock(periph); + + cam_iosched_fini(softc->cam_iosched); + + /* + * If we can't free the sysctl tree, oh well... + */ + if ((softc->flags & DA_FLAG_SCTX_INIT) != 0) { +#ifdef CAM_IO_STATS + if (sysctl_ctx_free(&softc->sysctl_stats_ctx) != 0) + xpt_print(periph->path, + "can't remove sysctl stats context\n"); +#endif + if (sysctl_ctx_free(&softc->sysctl_ctx) != 0) + xpt_print(periph->path, + "can't remove sysctl context\n"); + } + + callout_drain(&softc->mediapoll_c); + disk_destroy(softc->disk); + callout_drain(&softc->sendordered_c); + free(softc, M_DEVBUF); + cam_periph_lock(periph); +} + +static void +daasync(void *callback_arg, u_int32_t code, + struct cam_path *path, void *arg) +{ + struct cam_periph *periph; + struct da_softc *softc; + + periph = (struct cam_periph *)callback_arg; + switch (code) { + case AC_FOUND_DEVICE: /* callback to create periph, no locking yet */ + { + struct ccb_getdev *cgd; + cam_status status; + + cgd = (struct ccb_getdev *)arg; + if (cgd == NULL) + break; + + if (cgd->protocol != PROTO_SCSI) + break; + if (SID_QUAL(&cgd->inq_data) != SID_QUAL_LU_CONNECTED) + 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_ZBC_HM) + break; + + /* + * Allocate a peripheral instance for + * this device and start the probe + * process. + */ + status = cam_periph_alloc(daregister, daoninvalidate, + dacleanup, dastart, + "da", CAM_PERIPH_BIO, + path, daasync, + AC_FOUND_DEVICE, cgd); + + if (status != CAM_REQ_CMP + && status != CAM_REQ_INPROG) + printf("daasync: Unable to attach to new device " + "due to status 0x%x\n", status); + return; + } + case AC_ADVINFO_CHANGED: /* Doesn't touch periph */ + { + uintptr_t buftype; + + buftype = (uintptr_t)arg; + if (buftype == CDAI_TYPE_PHYS_PATH) { + struct da_softc *softc; + + softc = periph->softc; + disk_attr_changed(softc->disk, "GEOM::physpath", + M_NOWAIT); + } + break; + } + case AC_UNIT_ATTENTION: + { + union ccb *ccb; + int error_code, sense_key, asc, ascq; + + softc = (struct da_softc *)periph->softc; + ccb = (union ccb *)arg; + + /* + * Handle all UNIT ATTENTIONs except our own, as they will be + * handled by daerror(). Since this comes from a different periph, + * that periph's lock is held, not ours, so we have to take it ours + * out to touch softc flags. + */ + if (xpt_path_periph(ccb->ccb_h.path) != periph && + scsi_extract_sense_ccb(ccb, + &error_code, &sense_key, &asc, &ascq)) { + if (asc == 0x2A && ascq == 0x09) { + xpt_print(ccb->ccb_h.path, + "Capacity data has changed\n"); + cam_periph_lock(periph); + softc->flags &= ~DA_FLAG_PROBED; + dareprobe(periph); + cam_periph_unlock(periph); + } else if (asc == 0x28 && ascq == 0x00) { + cam_periph_lock(periph); + softc->flags &= ~DA_FLAG_PROBED; + cam_periph_unlock(periph); + disk_media_changed(softc->disk, M_NOWAIT); + } else if (asc == 0x3F && ascq == 0x03) { + xpt_print(ccb->ccb_h.path, + "INQUIRY data has changed\n"); + cam_periph_lock(periph); + softc->flags &= ~DA_FLAG_PROBED; + dareprobe(periph); + cam_periph_unlock(periph); + } + } + break; + } + case AC_SCSI_AEN: /* Called for this path: periph locked */ + /* + * Appears to be currently unused for SCSI devices, only ata SIMs + * generate this. + */ + cam_periph_assert(periph, MA_OWNED); + softc = (struct da_softc *)periph->softc; + if (!cam_iosched_has_work_flags(softc->cam_iosched, DA_WORK_TUR) && + (softc->flags & DA_FLAG_TUR_PENDING) == 0) { + if (da_periph_acquire(periph, DA_REF_TUR) == 0) { + cam_iosched_set_work_flags(softc->cam_iosched, DA_WORK_TUR); + daschedule(periph); + } + } + /* FALLTHROUGH */ + case AC_SENT_BDR: /* Called for this path: periph locked */ + case AC_BUS_RESET: /* Called for this path: periph locked */ + { + struct ccb_hdr *ccbh; + + cam_periph_assert(periph, MA_OWNED); + softc = (struct da_softc *)periph->softc; + /* + * Don't fail on the expected unit attention + * that will occur. + */ + softc->flags |= DA_FLAG_RETRY_UA; + LIST_FOREACH(ccbh, &softc->pending_ccbs, periph_links.le) + ccbh->ccb_state |= DA_CCB_RETRY_UA; + break; + } + case AC_INQ_CHANGED: /* Called for this path: periph locked */ + cam_periph_assert(periph, MA_OWNED); + softc = (struct da_softc *)periph->softc; + softc->flags &= ~DA_FLAG_PROBED; + dareprobe(periph); + break; + default: + break; + } + cam_periph_async(periph, code, path, arg); +} + +static void +dasysctlinit(void *context, int pending) +{ + struct cam_periph *periph; + struct da_softc *softc; + char tmpstr[32], tmpstr2[16]; + struct ccb_trans_settings cts; + + periph = (struct cam_periph *)context; + /* + * periph was held for us when this task was enqueued + */ + if (periph->flags & CAM_PERIPH_INVALID) { + da_periph_release(periph, DA_REF_SYSCTL); + return; + } + + softc = (struct da_softc *)periph->softc; + snprintf(tmpstr, sizeof(tmpstr), "CAM DA unit %d", periph->unit_number); + snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number); + + sysctl_ctx_init(&softc->sysctl_ctx); + cam_periph_lock(periph); + softc->flags |= DA_FLAG_SCTX_INIT; + cam_periph_unlock(periph); + softc->sysctl_tree = SYSCTL_ADD_NODE_WITH_LABEL(&softc->sysctl_ctx, + SYSCTL_STATIC_CHILDREN(_kern_cam_da), OID_AUTO, tmpstr2, + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, tmpstr, "device_index"); + if (softc->sysctl_tree == NULL) { + printf("dasysctlinit: unable to allocate sysctl tree\n"); + da_periph_release(periph, DA_REF_SYSCTL); + return; + } + + /* + * Now register the sysctl handler, so the user can change the value on + * the fly. + */ + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "delete_method", + CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT, + softc, 0, dadeletemethodsysctl, "A", + "BIO_DELETE execution method"); + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "delete_max", + CTLTYPE_U64 | CTLFLAG_RW | CTLFLAG_NEEDGIANT, + softc, 0, dadeletemaxsysctl, "Q", + "Maximum BIO_DELETE size"); + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "minimum_cmd_size", + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, + &softc->minimum_cmd_size, 0, dacmdsizesysctl, "I", + "Minimum CDB size"); + SYSCTL_ADD_UQUAD(&softc->sysctl_ctx, + SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, + "trim_count", CTLFLAG_RD, &softc->trim_count, + "Total number of unmap/dsm commands sent"); + SYSCTL_ADD_UQUAD(&softc->sysctl_ctx, + SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, + "trim_ranges", CTLFLAG_RD, &softc->trim_ranges, + "Total number of ranges in unmap/dsm commands"); + SYSCTL_ADD_UQUAD(&softc->sysctl_ctx, + SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, + "trim_lbas", CTLFLAG_RD, &softc->trim_lbas, + "Total lbas in the unmap/dsm commands sent"); + + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "zone_mode", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT, + 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 | CTLFLAG_NEEDGIANT, + 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, + "error_inject", + CTLFLAG_RW, + &softc->error_inject, + 0, + "error_inject leaf"); + + SYSCTL_ADD_INT(&softc->sysctl_ctx, + SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, + "p_type", + CTLFLAG_RD, + &softc->p_type, + 0, + "DIF protection type"); + + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "flags", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + softc, 0, daflagssysctl, "A", + "Flags for drive"); + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "rotating", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, + &softc->flags, (u_int)DA_FLAG_ROTATING, dabitsysctl, "I", + "Rotating media *DEPRECATED* gone in FreeBSD 14"); + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "unmapped_io", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, + &softc->flags, (u_int)DA_FLAG_UNMAPPEDIO, dabitsysctl, "I", + "Unmapped I/O support *DEPRECATED* gone in FreeBSD 14"); + +#ifdef CAM_TEST_FAILURE + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "invalidate", CTLTYPE_U64 | CTLFLAG_RW | CTLFLAG_MPSAFE, + periph, 0, cam_periph_invalidate_sysctl, "I", + "Write 1 to invalidate the drive immediately"); +#endif + + /* + * Add some addressing info. + */ + memset(&cts, 0, sizeof (cts)); + xpt_setup_ccb(&cts.ccb_h, periph->path, CAM_PRIORITY_NONE); + cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; + cts.type = CTS_TYPE_CURRENT_SETTINGS; + cam_periph_lock(periph); + xpt_action((union ccb *)&cts); + cam_periph_unlock(periph); + if (cts.ccb_h.status != CAM_REQ_CMP) { + da_periph_release(periph, DA_REF_SYSCTL); + return; + } + if (cts.protocol == PROTO_SCSI && cts.transport == XPORT_FC) { + struct ccb_trans_settings_fc *fc = &cts.xport_specific.fc; + if (fc->valid & CTS_FC_VALID_WWPN) { + softc->wwpn = fc->wwpn; + SYSCTL_ADD_UQUAD(&softc->sysctl_ctx, + SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "wwpn", CTLFLAG_RD, + &softc->wwpn, "World Wide Port Name"); + } + } + +#ifdef CAM_IO_STATS + /* + * Now add some useful stats. + * XXX These should live in cam_periph and be common to all periphs + */ + softc->sysctl_stats_tree = SYSCTL_ADD_NODE(&softc->sysctl_stats_ctx, + SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "stats", + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Statistics"); + SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, + SYSCTL_CHILDREN(softc->sysctl_stats_tree), + OID_AUTO, + "errors", + CTLFLAG_RD, + &softc->errors, + 0, + "Transport errors reported by the SIM"); + SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, + SYSCTL_CHILDREN(softc->sysctl_stats_tree), + OID_AUTO, + "timeouts", + CTLFLAG_RD, + &softc->timeouts, + 0, + "Device timeouts reported by the SIM"); + SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, + SYSCTL_CHILDREN(softc->sysctl_stats_tree), + OID_AUTO, + "pack_invalidations", + CTLFLAG_RD, + &softc->invalidations, + 0, + "Device pack invalidations"); +#endif + + cam_iosched_sysctl_init(softc->cam_iosched, &softc->sysctl_ctx, + softc->sysctl_tree); + + da_periph_release(periph, DA_REF_SYSCTL); +} + +static int +dadeletemaxsysctl(SYSCTL_HANDLER_ARGS) +{ + int error; + uint64_t value; + struct da_softc *softc; + + softc = (struct da_softc *)arg1; + + value = softc->disk->d_delmaxsize; + error = sysctl_handle_64(oidp, &value, 0, req); + if ((error != 0) || (req->newptr == NULL)) + return (error); + + /* only accept values smaller than the calculated value */ + if (value > dadeletemaxsize(softc, softc->delete_method)) { + return (EINVAL); + } + softc->disk->d_delmaxsize = value; + + return (0); +} + +static int +dacmdsizesysctl(SYSCTL_HANDLER_ARGS) +{ + int error, value; + + value = *(int *)arg1; + + error = sysctl_handle_int(oidp, &value, 0, req); + + if ((error != 0) + || (req->newptr == NULL)) + return (error); + + /* + * Acceptable values here are 6, 10, 12 or 16. + */ + if (value < 6) + value = 6; + else if ((value > 6) + && (value <= 10)) + value = 10; + else if ((value > 10) + && (value <= 12)) + value = 12; + else if (value > 12) + value = 16; + + *(int *)arg1 = value; + + return (0); +} + +static int +dasysctlsofttimeout(SYSCTL_HANDLER_ARGS) +{ + sbintime_t value; + int error; + + value = da_default_softtimeout / SBT_1MS; + + error = sysctl_handle_int(oidp, (int *)&value, 0, req); + if ((error != 0) || (req->newptr == NULL)) + return (error); + + /* XXX Should clip this to a reasonable level */ + if (value > da_default_timeout * 1000) + return (EINVAL); + + da_default_softtimeout = value * SBT_1MS; + return (0); +} + +static void +dadeletemethodset(struct da_softc *softc, da_delete_methods delete_method) +{ + + softc->delete_method = delete_method; + softc->disk->d_delmaxsize = dadeletemaxsize(softc, delete_method); + softc->delete_func = da_delete_functions[delete_method]; + + if (softc->delete_method > DA_DELETE_DISABLE) + softc->disk->d_flags |= DISKFLAG_CANDELETE; + else + softc->disk->d_flags &= ~DISKFLAG_CANDELETE; +} + +static off_t +dadeletemaxsize(struct da_softc *softc, da_delete_methods delete_method) +{ + off_t sectors; + + switch(delete_method) { + case DA_DELETE_UNMAP: + sectors = (off_t)softc->unmap_max_lba; + break; + case DA_DELETE_ATA_TRIM: + sectors = (off_t)ATA_DSM_RANGE_MAX * softc->trim_max_ranges; + break; + case DA_DELETE_WS16: + sectors = omin(softc->ws_max_blks, WS16_MAX_BLKS); + break; + case DA_DELETE_ZERO: + case DA_DELETE_WS10: + sectors = omin(softc->ws_max_blks, WS10_MAX_BLKS); + break; + default: + return 0; + } + + return (off_t)softc->params.secsize * + omin(sectors, softc->params.sectors); +} + +static void +daprobedone(struct cam_periph *periph, union ccb *ccb) +{ + struct da_softc *softc; + + softc = (struct da_softc *)periph->softc; + + cam_periph_assert(periph, MA_OWNED); + + dadeletemethodchoose(softc, DA_DELETE_NONE); + + if (bootverbose && (softc->flags & DA_FLAG_ANNOUNCED) == 0) { + char buf[80]; + int i, sep; + + snprintf(buf, sizeof(buf), "Delete methods: <"); + sep = 0; + for (i = 0; i <= DA_DELETE_MAX; i++) { + if ((softc->delete_available & (1 << i)) == 0 && + i != softc->delete_method) + continue; + if (sep) + strlcat(buf, ",", sizeof(buf)); + strlcat(buf, da_delete_method_names[i], + sizeof(buf)); + if (i == softc->delete_method) + strlcat(buf, "(*)", sizeof(buf)); + sep = 1; + } + strlcat(buf, ">", sizeof(buf)); + printf("%s%d: %s\n", periph->periph_name, + periph->unit_number, buf); + } + if ((softc->disk->d_flags & DISKFLAG_WRITE_PROTECT) != 0 && + (softc->flags & DA_FLAG_ANNOUNCED) == 0) { + printf("%s%d: Write Protected\n", periph->periph_name, + periph->unit_number); + } + + /* + * Since our peripheral may be invalidated by an error + * above or an external event, we must release our CCB + * before releasing the probe lock on the peripheral. + * The peripheral will only go away once the last lock + * is removed, and we need it around for the CCB release + * operation. + */ + xpt_release_ccb(ccb); + softc->state = DA_STATE_NORMAL; + softc->flags |= DA_FLAG_PROBED; + daschedule(periph); + wakeup(&softc->disk->d_mediasize); + if ((softc->flags & DA_FLAG_ANNOUNCED) == 0) { + softc->flags |= DA_FLAG_ANNOUNCED; + da_periph_unhold(periph, DA_REF_PROBE_HOLD); + } else + da_periph_release_locked(periph, DA_REF_REPROBE); +} + +static void +dadeletemethodchoose(struct da_softc *softc, da_delete_methods default_method) +{ + int i, methods; + + /* If available, prefer the method requested by user. */ + i = softc->delete_method_pref; + methods = softc->delete_available | (1 << DA_DELETE_DISABLE); + if (methods & (1 << i)) { + dadeletemethodset(softc, i); + return; + } + + /* Use the pre-defined order to choose the best performing delete. */ + for (i = DA_DELETE_MIN; i <= DA_DELETE_MAX; i++) { + if (i == DA_DELETE_ZERO) + continue; + if (softc->delete_available & (1 << i)) { + dadeletemethodset(softc, i); + return; + } + } + + /* Fallback to default. */ + dadeletemethodset(softc, default_method); +} + +static int +dabitsysctl(SYSCTL_HANDLER_ARGS) +{ + u_int *flags = arg1; + u_int test = arg2; + int tmpout, error; + + tmpout = !!(*flags & test); + error = SYSCTL_OUT(req, &tmpout, sizeof(tmpout)); + if (error || !req->newptr) + return (error); + + return (EPERM); +} + +static int +daflagssysctl(SYSCTL_HANDLER_ARGS) +{ + struct sbuf sbuf; + struct da_softc *softc = arg1; + int error; + + sbuf_new_for_sysctl(&sbuf, NULL, 0, req); + if (softc->flags != 0) + sbuf_printf(&sbuf, "0x%b", (unsigned)softc->flags, DA_FLAG_STRING); + else + sbuf_printf(&sbuf, "0"); + error = sbuf_finish(&sbuf); + sbuf_delete(&sbuf); + + return (error); +} + +static int +dadeletemethodsysctl(SYSCTL_HANDLER_ARGS) +{ + char buf[16]; + const char *p; + struct da_softc *softc; + int i, error, value; + + softc = (struct da_softc *)arg1; + + value = softc->delete_method; + if (value < 0 || value > DA_DELETE_MAX) + p = "UNKNOWN"; + else + p = da_delete_method_names[value]; + strncpy(buf, p, sizeof(buf)); + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + if (error != 0 || req->newptr == NULL) + return (error); + for (i = 0; i <= DA_DELETE_MAX; i++) { + if (strcmp(buf, da_delete_method_names[i]) == 0) + break; + } + if (i > DA_DELETE_MAX) + return (EINVAL); + softc->delete_method_pref = i; + dadeletemethodchoose(softc, DA_DELETE_NONE); + 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) +{ + struct da_softc *softc; + struct ccb_pathinq cpi; + struct ccb_getdev *cgd; + char tmpstr[80]; + caddr_t match; + int quirks; + + cgd = (struct ccb_getdev *)arg; + if (cgd == NULL) { + printf("daregister: no getdev CCB, can't register device\n"); + return(CAM_REQ_CMP_ERR); + } + + softc = (struct da_softc *)malloc(sizeof(*softc), M_DEVBUF, + M_NOWAIT|M_ZERO); + + if (softc == NULL) { + printf("daregister: Unable to probe new device. " + "Unable to allocate softc\n"); + return(CAM_REQ_CMP_ERR); + } + + if (cam_iosched_init(&softc->cam_iosched, periph) != 0) { + printf("daregister: Unable to probe new device. " + "Unable to allocate iosched memory\n"); + free(softc, M_DEVBUF); + return(CAM_REQ_CMP_ERR); + } + + LIST_INIT(&softc->pending_ccbs); + softc->state = DA_STATE_PROBE_WP; + bioq_init(&softc->delete_run_queue); + if (SID_IS_REMOVABLE(&cgd->inq_data)) + softc->flags |= DA_FLAG_PACK_REMOVABLE; + softc->unmap_max_ranges = UNMAP_MAX_RANGES; + softc->unmap_max_lba = UNMAP_RANGE_MAX; + softc->unmap_gran = 0; + softc->unmap_gran_align = 0; + softc->ws_max_blks = WS16_MAX_BLKS; + softc->trim_max_ranges = ATA_TRIM_MAX_RANGES; + softc->flags |= DA_FLAG_ROTATING; + + periph->softc = softc; + + /* + * See if this device has any quirks. + */ + match = cam_quirkmatch((caddr_t)&cgd->inq_data, + (caddr_t)da_quirk_table, + nitems(da_quirk_table), + sizeof(*da_quirk_table), scsi_inquiry_match); + + if (match != NULL) + softc->quirks = ((struct da_quirk_entry *)match)->quirks; + else + softc->quirks = DA_Q_NONE; + + /* Check if the SIM does not want 6 byte commands */ + xpt_path_inq(&cpi, periph->path); + if (cpi.ccb_h.status == CAM_REQ_CMP && (cpi.hba_misc & PIM_NO_6_BYTE)) + softc->quirks |= DA_Q_NO_6_BYTE; + + /* Override quirks if tunable is set */ + snprintf(tmpstr, sizeof(tmpstr), "kern.cam.da.%d.quirks", + periph->unit_number); + quirks = softc->quirks; + TUNABLE_INT_FETCH(tmpstr, &quirks); + softc->quirks = quirks; + + 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); + + /* + * Take an exclusive section lock qon the periph while dastart is called + * to finish the probe. The lock will be dropped in dadone at the end + * of probe. This locks out daopen and daclose from racing with the + * probe. + * + * XXX if cam_periph_hold returns an error, we don't hold a refcount. + */ + (void)da_periph_hold(periph, PRIBIO, DA_REF_PROBE_HOLD); + + /* + * Schedule a periodic event to occasionally send an + * ordered tag to a device. + */ + callout_init_mtx(&softc->sendordered_c, cam_periph_mtx(periph), 0); + callout_reset(&softc->sendordered_c, + (da_default_timeout * hz) / DA_ORDEREDTAG_INTERVAL, + dasendorderedtag, periph); + + cam_periph_unlock(periph); + /* + * RBC devices don't have to support READ(6), only READ(10). + */ + if (softc->quirks & DA_Q_NO_6_BYTE || SID_TYPE(&cgd->inq_data) == T_RBC) + softc->minimum_cmd_size = 10; + else + softc->minimum_cmd_size = 6; + + /* + * Load the user's default, if any. + */ + snprintf(tmpstr, sizeof(tmpstr), "kern.cam.da.%d.minimum_cmd_size", + periph->unit_number); + TUNABLE_INT_FETCH(tmpstr, &softc->minimum_cmd_size); + + /* + * 6, 10, 12 and 16 are the currently permissible values. + */ + if (softc->minimum_cmd_size > 12) + softc->minimum_cmd_size = 16; + else if (softc->minimum_cmd_size > 10) + softc->minimum_cmd_size = 12; + else if (softc->minimum_cmd_size > 6) + softc->minimum_cmd_size = 10; + else + softc->minimum_cmd_size = 6; + + /* Predict whether device may support READ CAPACITY(16). */ + if (SID_ANSI_REV(&cgd->inq_data) >= SCSI_REV_SPC3 && + (softc->quirks & DA_Q_NO_RC16) == 0) { + softc->flags |= DA_FLAG_CAN_RC16; + } + + /* + * Register this media as a disk. + */ + softc->disk = disk_alloc(); + softc->disk->d_devstat = devstat_new_entry(periph->periph_name, + periph->unit_number, 0, + DEVSTAT_BS_UNAVAILABLE, + SID_TYPE(&cgd->inq_data) | + XPORT_DEVSTAT_TYPE(cpi.transport), + DEVSTAT_PRIORITY_DISK); + softc->disk->d_open = daopen; + softc->disk->d_close = daclose; + softc->disk->d_strategy = dastrategy; + softc->disk->d_dump = dadump; + softc->disk->d_getattr = dagetattr; + softc->disk->d_gone = dadiskgonecb; + softc->disk->d_name = "da"; + softc->disk->d_drv1 = periph; + if (cpi.maxio == 0) + softc->maxio = DFLTPHYS; /* traditional default */ + else if (cpi.maxio > maxphys) + softc->maxio = maxphys; /* for safety */ + else + softc->maxio = cpi.maxio; + if (softc->quirks & DA_Q_128KB) + softc->maxio = min(softc->maxio, 128 * 1024); + softc->disk->d_maxsize = softc->maxio; + softc->disk->d_unit = periph->unit_number; + 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) { + softc->flags |= DA_FLAG_UNMAPPEDIO; + softc->disk->d_flags |= DISKFLAG_UNMAPPED_BIO; + } + cam_strvis(softc->disk->d_descr, cgd->inq_data.vendor, + sizeof(cgd->inq_data.vendor), sizeof(softc->disk->d_descr)); + strlcat(softc->disk->d_descr, " ", sizeof(softc->disk->d_descr)); + cam_strvis(&softc->disk->d_descr[strlen(softc->disk->d_descr)], + cgd->inq_data.product, sizeof(cgd->inq_data.product), + sizeof(softc->disk->d_descr) - strlen(softc->disk->d_descr)); + softc->disk->d_hba_vendor = cpi.hba_vendor; + softc->disk->d_hba_device = cpi.hba_device; + softc->disk->d_hba_subvendor = cpi.hba_subvendor; + softc->disk->d_hba_subdevice = cpi.hba_subdevice; + snprintf(softc->disk->d_attachment, sizeof(softc->disk->d_attachment), + "%s%d", cpi.dev_name, cpi.unit_number); + + /* + * Acquire a reference to the periph before we register with GEOM. + * We'll release this reference once GEOM calls us back (via + * dadiskgonecb()) telling us that our provider has been freed. + */ + if (da_periph_acquire(periph, DA_REF_GEOM) != 0) { + xpt_print(periph->path, "%s: lost periph during " + "registration!\n", __func__); + cam_periph_lock(periph); + return (CAM_REQ_CMP_ERR); + } + + disk_create(softc->disk, DISK_VERSION); + cam_periph_lock(periph); + + /* + * Add async callbacks for events of interest. + * I don't bother checking if this fails as, + * in most cases, the system will function just + * fine without them and the only alternative + * would be to not attach the device on failure. + */ + xpt_register_async(AC_SENT_BDR | AC_BUS_RESET | AC_LOST_DEVICE | + AC_ADVINFO_CHANGED | AC_SCSI_AEN | AC_UNIT_ATTENTION | + AC_INQ_CHANGED, daasync, periph, periph->path); + + /* + * Emit an attribute changed notification just in case + * physical path information arrived before our async + * event handler was registered, but after anyone attaching + * to our disk device polled it. + */ + disk_attr_changed(softc->disk, "GEOM::physpath", M_NOWAIT); + + /* + * Schedule a periodic media polling events. + */ + callout_init_mtx(&softc->mediapoll_c, cam_periph_mtx(periph), 0); + if ((softc->flags & DA_FLAG_PACK_REMOVABLE) && + (cgd->inq_flags & SID_AEN) == 0 && + da_poll_period != 0) + callout_reset(&softc->mediapoll_c, da_poll_period * hz, + damediapoll, periph); + + xpt_schedule(periph, CAM_PRIORITY_DEV); + + return(CAM_REQ_CMP); +} + +static int +da_zone_bio_to_scsi(int disk_zone_cmd) +{ + switch (disk_zone_cmd) { + case DISK_ZONE_OPEN: + return ZBC_OUT_SA_OPEN; + case DISK_ZONE_CLOSE: + return ZBC_OUT_SA_CLOSE; + case DISK_ZONE_FINISH: + return ZBC_OUT_SA_FINISH; + case DISK_ZONE_RWP: + return ZBC_OUT_SA_RWP; + } + + return -1; +} + +static int +da_zone_cmd(struct cam_periph *periph, union ccb *ccb, struct bio *bp, + int *queue_ccb) +{ + struct da_softc *softc; + int error; + + error = 0; + + if (bp->bio_cmd != BIO_ZONE) { + error = EINVAL; + goto bailout; + } + + softc = periph->softc; + + switch (bp->bio_zone.zone_cmd) { + case DISK_ZONE_OPEN: + case DISK_ZONE_CLOSE: + case DISK_ZONE_FINISH: + case DISK_ZONE_RWP: { + int zone_flags; + int zone_sa; + uint64_t lba; + + zone_sa = da_zone_bio_to_scsi(bp->bio_zone.zone_cmd); + if (zone_sa == -1) { + xpt_print(periph->path, "Cannot translate zone " + "cmd %#x to SCSI\n", bp->bio_zone.zone_cmd); + error = EINVAL; + goto bailout; + } + + zone_flags = 0; + lba = bp->bio_zone.zone_params.rwp.id; + + if (bp->bio_zone.zone_params.rwp.flags & + DISK_ZONE_RWP_FLAG_ALL) + zone_flags |= ZBC_OUT_ALL; + + if (softc->zone_interface != DA_ZONE_IF_ATA_PASS) { + scsi_zbc_out(&ccb->csio, + /*retries*/ da_retry_count, + /*cbfcnp*/ dadone, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*service_action*/ zone_sa, + /*zone_id*/ lba, + /*zone_flags*/ zone_flags, + /*data_ptr*/ NULL, + /*dxfer_len*/ 0, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ da_default_timeout * 1000); + } else { + /* + * Note that in this case, even though we can + * technically use NCQ, we don't bother for several + * reasons: + * 1. It hasn't been tested on a SAT layer that + * supports it. This is new as of SAT-4. + * 2. Even when there is a SAT layer that supports + * it, that SAT layer will also probably support + * ZBC -> ZAC translation, since they are both + * in the SAT-4 spec. + * 3. Translation will likely be preferable to ATA + * passthrough. LSI / Avago at least single + * steps ATA passthrough commands in the HBA, + * regardless of protocol, so unless that + * changes, there is a performance penalty for + * doing ATA passthrough no matter whether + * you're using NCQ/FPDMA, DMA or PIO. + * 4. It requires a 32-byte CDB, which at least at + * this point in CAM requires a CDB pointer, which + * would require us to allocate an additional bit + * of storage separate from the CCB. + */ + error = scsi_ata_zac_mgmt_out(&ccb->csio, + /*retries*/ da_retry_count, + /*cbfcnp*/ dadone, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*use_ncq*/ 0, + /*zm_action*/ zone_sa, + /*zone_id*/ lba, + /*zone_flags*/ zone_flags, + /*data_ptr*/ NULL, + /*dxfer_len*/ 0, + /*cdb_storage*/ NULL, + /*cdb_storage_len*/ 0, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ da_default_timeout * 1000); + if (error != 0) { + error = EINVAL; + xpt_print(periph->path, + "scsi_ata_zac_mgmt_out() returned an " + "error!"); + goto bailout; + } + } + *queue_ccb = 1; + + break; + } + case DISK_ZONE_REPORT_ZONES: { + uint8_t *rz_ptr; + uint32_t num_entries, alloc_size; + struct disk_zone_report *rep; + + rep = &bp->bio_zone.zone_params.report; + + num_entries = rep->entries_allocated; + if (num_entries == 0) { + xpt_print(periph->path, "No entries allocated for " + "Report Zones request\n"); + error = EINVAL; + goto bailout; + } + alloc_size = sizeof(struct scsi_report_zones_hdr) + + (sizeof(struct scsi_report_zones_desc) * num_entries); + alloc_size = min(alloc_size, softc->disk->d_maxsize); + rz_ptr = malloc(alloc_size, M_SCSIDA, M_NOWAIT | M_ZERO); + if (rz_ptr == NULL) { + xpt_print(periph->path, "Unable to allocate memory " + "for Report Zones request\n"); + error = ENOMEM; + goto bailout; + } + + if (softc->zone_interface != DA_ZONE_IF_ATA_PASS) { + scsi_zbc_in(&ccb->csio, + /*retries*/ da_retry_count, + /*cbcfnp*/ dadone, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*service_action*/ ZBC_IN_SA_REPORT_ZONES, + /*zone_start_lba*/ rep->starting_id, + /*zone_options*/ rep->rep_options, + /*data_ptr*/ rz_ptr, + /*dxfer_len*/ alloc_size, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ da_default_timeout * 1000); + } else { + /* + * Note that in this case, even though we can + * technically use NCQ, we don't bother for several + * reasons: + * 1. It hasn't been tested on a SAT layer that + * supports it. This is new as of SAT-4. + * 2. Even when there is a SAT layer that supports + * it, that SAT layer will also probably support + * ZBC -> ZAC translation, since they are both + * in the SAT-4 spec. + * 3. Translation will likely be preferable to ATA + * passthrough. LSI / Avago at least single + * steps ATA passthrough commands in the HBA, + * regardless of protocol, so unless that + * changes, there is a performance penalty for + * doing ATA passthrough no matter whether + * you're using NCQ/FPDMA, DMA or PIO. + * 4. It requires a 32-byte CDB, which at least at + * this point in CAM requires a CDB pointer, which + * would require us to allocate an additional bit + * of storage separate from the CCB. + */ + error = scsi_ata_zac_mgmt_in(&ccb->csio, + /*retries*/ da_retry_count, + /*cbcfnp*/ dadone, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*use_ncq*/ 0, + /*zm_action*/ ATA_ZM_REPORT_ZONES, + /*zone_id*/ rep->starting_id, + /*zone_flags*/ rep->rep_options, + /*data_ptr*/ rz_ptr, + /*dxfer_len*/ alloc_size, + /*cdb_storage*/ NULL, + /*cdb_storage_len*/ 0, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ da_default_timeout * 1000); + if (error != 0) { + error = EINVAL; + xpt_print(periph->path, + "scsi_ata_zac_mgmt_in() returned an " + "error!"); + goto bailout; + } + } + + /* + * For BIO_ZONE, this isn't normally needed. However, it + * is used by devstat_end_transaction_bio() to determine + * how much data was transferred. + */ + /* + * XXX KDM we have a problem. But I'm not sure how to fix + * it. devstat uses bio_bcount - bio_resid to calculate + * the amount of data transferred. The GEOM disk code + * uses bio_length - bio_resid to calculate the amount of + * data in bio_completed. We have different structure + * sizes above and below the ada(4) driver. So, if we + * use the sizes above, the amount transferred won't be + * quite accurate for devstat. If we use different sizes + * for bio_bcount and bio_length (above and below + * respectively), then the residual needs to match one or + * the other. Everything is calculated after the bio + * leaves the driver, so changing the values around isn't + * really an option. For now, just set the count to the + * passed in length. This means that the calculations + * above (e.g. bio_completed) will be correct, but the + * amount of data reported to devstat will be slightly + * under or overstated. + */ + bp->bio_bcount = bp->bio_length; + + *queue_ccb = 1; + + break; + } + case DISK_ZONE_GET_PARAMS: { + struct disk_zone_disk_params *params; + + params = &bp->bio_zone.zone_params.disk_params; + bzero(params, sizeof(*params)); + + switch (softc->zone_mode) { + case DA_ZONE_DRIVE_MANAGED: + params->zone_mode = DISK_ZONE_MODE_DRIVE_MANAGED; + break; + case DA_ZONE_HOST_AWARE: + params->zone_mode = DISK_ZONE_MODE_HOST_AWARE; + break; + case DA_ZONE_HOST_MANAGED: + params->zone_mode = DISK_ZONE_MODE_HOST_MANAGED; + break; + default: + case DA_ZONE_NONE: + params->zone_mode = DISK_ZONE_MODE_NONE; + break; + } + + if (softc->zone_flags & DA_ZONE_FLAG_URSWRZ) + params->flags |= DISK_ZONE_DISK_URSWRZ; + + if (softc->zone_flags & DA_ZONE_FLAG_OPT_SEQ_SET) { + params->optimal_seq_zones = softc->optimal_seq_zones; + params->flags |= DISK_ZONE_OPT_SEQ_SET; + } + + if (softc->zone_flags & DA_ZONE_FLAG_OPT_NONSEQ_SET) { + params->optimal_nonseq_zones = + softc->optimal_nonseq_zones; + params->flags |= DISK_ZONE_OPT_NONSEQ_SET; + } + + if (softc->zone_flags & DA_ZONE_FLAG_MAX_SEQ_SET) { + params->max_seq_zones = softc->max_seq_zones; + params->flags |= DISK_ZONE_MAX_SEQ_SET; + } + if (softc->zone_flags & DA_ZONE_FLAG_RZ_SUP) + params->flags |= DISK_ZONE_RZ_SUP; + + if (softc->zone_flags & DA_ZONE_FLAG_OPEN_SUP) + params->flags |= DISK_ZONE_OPEN_SUP; + + if (softc->zone_flags & DA_ZONE_FLAG_CLOSE_SUP) + params->flags |= DISK_ZONE_CLOSE_SUP; + + if (softc->zone_flags & DA_ZONE_FLAG_FINISH_SUP) + params->flags |= DISK_ZONE_FINISH_SUP; + + if (softc->zone_flags & DA_ZONE_FLAG_RWP_SUP) + params->flags |= DISK_ZONE_RWP_SUP; + break; + } + default: + break; + } +bailout: + return (error); +} + +static void +dastart(struct cam_periph *periph, union ccb *start_ccb) +{ + struct da_softc *softc; + + cam_periph_assert(periph, MA_OWNED); + softc = (struct da_softc *)periph->softc; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dastart\n")); + +skipstate: + switch (softc->state) { + case DA_STATE_NORMAL: + { + struct bio *bp; + uint8_t tag_code; + +more: + bp = cam_iosched_next_bio(softc->cam_iosched); + if (bp == NULL) { + if (cam_iosched_has_work_flags(softc->cam_iosched, + DA_WORK_TUR)) { + softc->flags |= DA_FLAG_TUR_PENDING; + cam_iosched_clr_work_flags(softc->cam_iosched, + DA_WORK_TUR); + scsi_test_unit_ready(&start_ccb->csio, + /*retries*/ da_retry_count, + dadone_tur, + MSG_SIMPLE_Q_TAG, + SSD_FULL_SIZE, + da_default_timeout * 1000); + start_ccb->ccb_h.ccb_bp = NULL; + start_ccb->ccb_h.ccb_state = DA_CCB_TUR; + xpt_action(start_ccb); + } else + xpt_release_ccb(start_ccb); + break; + } + + if (bp->bio_cmd == BIO_DELETE) { + if (softc->delete_func != NULL) { + softc->delete_func(periph, start_ccb, bp); + goto out; + } else { + /* + * Not sure this is possible, but failsafe by + * lying and saying "sure, done." + */ + biofinish(bp, NULL, 0); + goto more; + } + } + + if (cam_iosched_has_work_flags(softc->cam_iosched, + DA_WORK_TUR)) { + cam_iosched_clr_work_flags(softc->cam_iosched, + DA_WORK_TUR); + da_periph_release_locked(periph, DA_REF_TUR); + } + + if ((bp->bio_flags & BIO_ORDERED) != 0 || + (softc->flags & DA_FLAG_NEED_OTAG) != 0) { + softc->flags &= ~DA_FLAG_NEED_OTAG; + softc->flags |= DA_FLAG_WAS_OTAG; + tag_code = MSG_ORDERED_Q_TAG; + } else { + tag_code = MSG_SIMPLE_Q_TAG; + } + + switch (bp->bio_cmd) { + case BIO_WRITE: + case BIO_READ: + { + void *data_ptr; + int rw_op; + + biotrack(bp, __func__); + + if (bp->bio_cmd == BIO_WRITE) { + softc->flags |= DA_FLAG_DIRTY; + rw_op = SCSI_RW_WRITE; + } else { + rw_op = SCSI_RW_READ; + } + + data_ptr = bp->bio_data; + if ((bp->bio_flags & (BIO_UNMAPPED|BIO_VLIST)) != 0) { + rw_op |= SCSI_RW_BIO; + data_ptr = bp; + } + + scsi_read_write(&start_ccb->csio, + /*retries*/da_retry_count, + /*cbfcnp*/dadone, + /*tag_action*/tag_code, + rw_op, + /*byte2*/0, + softc->minimum_cmd_size, + /*lba*/bp->bio_pblkno, + /*block_count*/bp->bio_bcount / + softc->params.secsize, + data_ptr, + /*dxfer_len*/ bp->bio_bcount, + /*sense_len*/SSD_FULL_SIZE, + da_default_timeout * 1000); +#if defined(BUF_TRACKING) || defined(FULL_BUF_TRACKING) + start_ccb->csio.bio = bp; +#endif + break; + } + case BIO_FLUSH: + /* + * If we don't support sync cache, or the disk + * isn't dirty, FLUSH is a no-op. Use the + * allocated CCB for the next bio if one is + * available. + */ + if ((softc->quirks & DA_Q_NO_SYNC_CACHE) != 0 || + (softc->flags & DA_FLAG_DIRTY) == 0) { + biodone(bp); + goto skipstate; + } + + /* + * BIO_FLUSH doesn't currently communicate + * range data, so we synchronize the cache + * over the whole disk. + */ + scsi_synchronize_cache(&start_ccb->csio, + /*retries*/1, + /*cbfcnp*/dadone, + /*tag_action*/tag_code, + /*begin_lba*/0, + /*lb_count*/0, + SSD_FULL_SIZE, + da_default_timeout*1000); + /* + * Clear the dirty flag before sending the command. + * Either this sync cache will be successful, or it + * will fail after a retry. If it fails, it is + * unlikely to be successful if retried later, so + * we'll save ourselves time by just marking the + * device clean. + */ + softc->flags &= ~DA_FLAG_DIRTY; + 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; + } + default: + biofinish(bp, NULL, EOPNOTSUPP); + xpt_release_ccb(start_ccb); + return; + } + 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); + +out: + LIST_INSERT_HEAD(&softc->pending_ccbs, + &start_ccb->ccb_h, periph_links.le); + + /* We expect a unit attention from this device */ + if ((softc->flags & DA_FLAG_RETRY_UA) != 0) { + start_ccb->ccb_h.ccb_state |= DA_CCB_RETRY_UA; + softc->flags &= ~DA_FLAG_RETRY_UA; + } + + start_ccb->ccb_h.ccb_bp = bp; + softc->refcount++; + cam_periph_unlock(periph); + xpt_action(start_ccb); + cam_periph_lock(periph); + + /* May have more work to do, so ensure we stay scheduled */ + daschedule(periph); + break; + } + case DA_STATE_PROBE_WP: + { + void *mode_buf; + int mode_buf_len; + + if (da_disable_wp_detection) { + if ((softc->flags & DA_FLAG_CAN_RC16) != 0) + softc->state = DA_STATE_PROBE_RC16; + else + softc->state = DA_STATE_PROBE_RC; + goto skipstate; + } + mode_buf_len = 192; + mode_buf = malloc(mode_buf_len, M_SCSIDA, M_NOWAIT); + if (mode_buf == NULL) { + xpt_print(periph->path, "Unable to send mode sense - " + "malloc failure\n"); + if ((softc->flags & DA_FLAG_CAN_RC16) != 0) + softc->state = DA_STATE_PROBE_RC16; + else + softc->state = DA_STATE_PROBE_RC; + goto skipstate; + } + scsi_mode_sense_len(&start_ccb->csio, + /*retries*/ da_retry_count, + /*cbfcnp*/ dadone_probewp, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*dbd*/ FALSE, + /*pc*/ SMS_PAGE_CTRL_CURRENT, + /*page*/ SMS_ALL_PAGES_PAGE, + /*param_buf*/ mode_buf, + /*param_len*/ mode_buf_len, + /*minimum_cmd_size*/ softc->minimum_cmd_size, + /*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_WP; + xpt_action(start_ccb); + break; + } + case DA_STATE_PROBE_RC: + { + struct scsi_read_capacity_data *rcap; + + rcap = (struct scsi_read_capacity_data *) + malloc(sizeof(*rcap), M_SCSIDA, M_NOWAIT|M_ZERO); + if (rcap == NULL) { + printf("dastart: Couldn't malloc read_capacity data\n"); + /* da_free_periph??? */ + break; + } + scsi_read_capacity(&start_ccb->csio, + /*retries*/da_retry_count, + dadone_proberc, + MSG_SIMPLE_Q_TAG, + rcap, + SSD_FULL_SIZE, + /*timeout*/5000); + start_ccb->ccb_h.ccb_bp = NULL; + start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_RC; + xpt_action(start_ccb); + break; + } + case DA_STATE_PROBE_RC16: + { + struct scsi_read_capacity_data_long *rcaplong; + + rcaplong = (struct scsi_read_capacity_data_long *) + malloc(sizeof(*rcaplong), M_SCSIDA, M_NOWAIT|M_ZERO); + if (rcaplong == NULL) { + printf("dastart: Couldn't malloc read_capacity data\n"); + /* da_free_periph??? */ + break; + } + scsi_read_capacity_16(&start_ccb->csio, + /*retries*/ da_retry_count, + /*cbfcnp*/ dadone_proberc, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*lba*/ 0, + /*reladr*/ 0, + /*pmi*/ 0, + /*rcap_buf*/ (uint8_t *)rcaplong, + /*rcap_buf_len*/ sizeof(*rcaplong), + /*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_RC16; + xpt_action(start_ccb); + break; + } + case DA_STATE_PROBE_LBP: + { + struct scsi_vpd_logical_block_prov *lbp; + + if (!scsi_vpd_supported_page(periph, SVPD_LBP)) { + /* + * If we get here we don't support any SBC-3 delete + * methods with UNMAP as the Logical Block Provisioning + * VPD page support is required for devices which + * support it according to T10/1799-D Revision 31 + * however older revisions of the spec don't mandate + * this so we currently don't remove these methods + * from the available set. + */ + softc->state = DA_STATE_PROBE_BLK_LIMITS; + goto skipstate; + } + + lbp = (struct scsi_vpd_logical_block_prov *) + malloc(sizeof(*lbp), M_SCSIDA, M_NOWAIT|M_ZERO); + + if (lbp == NULL) { + printf("dastart: Couldn't malloc lbp data\n"); + /* da_free_periph??? */ + break; + } + + scsi_inquiry(&start_ccb->csio, + /*retries*/da_retry_count, + /*cbfcnp*/dadone_probelbp, + /*tag_action*/MSG_SIMPLE_Q_TAG, + /*inq_buf*/(u_int8_t *)lbp, + /*inq_len*/sizeof(*lbp), + /*evpd*/TRUE, + /*page_code*/SVPD_LBP, + /*sense_len*/SSD_MIN_SIZE, + /*timeout*/da_default_timeout * 1000); + start_ccb->ccb_h.ccb_bp = NULL; + start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_LBP; + xpt_action(start_ccb); + break; + } + case DA_STATE_PROBE_BLK_LIMITS: + { + struct scsi_vpd_block_limits *block_limits; + + if (!scsi_vpd_supported_page(periph, SVPD_BLOCK_LIMITS)) { + /* Not supported skip to next probe */ + softc->state = DA_STATE_PROBE_BDC; + goto skipstate; + } + + block_limits = (struct scsi_vpd_block_limits *) + malloc(sizeof(*block_limits), M_SCSIDA, M_NOWAIT|M_ZERO); + + if (block_limits == NULL) { + printf("dastart: Couldn't malloc block_limits data\n"); + /* da_free_periph??? */ + break; + } + + scsi_inquiry(&start_ccb->csio, + /*retries*/da_retry_count, + /*cbfcnp*/dadone_probeblklimits, + /*tag_action*/MSG_SIMPLE_Q_TAG, + /*inq_buf*/(u_int8_t *)block_limits, + /*inq_len*/sizeof(*block_limits), + /*evpd*/TRUE, + /*page_code*/SVPD_BLOCK_LIMITS, + /*sense_len*/SSD_MIN_SIZE, + /*timeout*/da_default_timeout * 1000); + start_ccb->ccb_h.ccb_bp = NULL; + start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_BLK_LIMITS; + xpt_action(start_ccb); + break; + } + case DA_STATE_PROBE_BDC: + { + struct scsi_vpd_block_characteristics *bdc; + + if (!scsi_vpd_supported_page(periph, SVPD_BDC)) { + softc->state = DA_STATE_PROBE_ATA; + goto skipstate; + } + + bdc = (struct scsi_vpd_block_characteristics *) + malloc(sizeof(*bdc), M_SCSIDA, M_NOWAIT|M_ZERO); + + if (bdc == NULL) { + printf("dastart: Couldn't malloc bdc data\n"); + /* da_free_periph??? */ + break; + } + + scsi_inquiry(&start_ccb->csio, + /*retries*/da_retry_count, + /*cbfcnp*/dadone_probebdc, + /*tag_action*/MSG_SIMPLE_Q_TAG, + /*inq_buf*/(u_int8_t *)bdc, + /*inq_len*/sizeof(*bdc), + /*evpd*/TRUE, + /*page_code*/SVPD_BDC, + /*sense_len*/SSD_MIN_SIZE, + /*timeout*/da_default_timeout * 1000); + start_ccb->ccb_h.ccb_bp = NULL; + start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_BDC; + xpt_action(start_ccb); + break; + } + case DA_STATE_PROBE_ATA: + { + 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 = &periph->path->device->ident_data; + + scsi_ata_identify(&start_ccb->csio, + /*retries*/da_retry_count, + /*cbfcnp*/dadone_probeata, + /*tag_action*/MSG_SIMPLE_Q_TAG, + /*data_ptr*/(u_int8_t *)ata_params, + /*dxfer_len*/sizeof(*ata_params), + /*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_ATA; + 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_probeatalogdir, + /*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_probeataiddir, + /*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_probeatasup, + /*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_probeatazone, + /*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_probezone, + /*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; + } + } +} + +/* + * In each of the methods below, while its the caller's + * responsibility to ensure the request will fit into a + * single device request, we might have changed the delete + * method due to the device incorrectly advertising either + * its supported methods or limits. + * + * To prevent this causing further issues we validate the + * against the methods limits, and warn which would + * otherwise be unnecessary. + */ +static void +da_delete_unmap(struct cam_periph *periph, union ccb *ccb, struct bio *bp) +{ + struct da_softc *softc = (struct da_softc *)periph->softc; + struct bio *bp1; + uint8_t *buf = softc->unmap_buf; + struct scsi_unmap_desc *d = (void *)&buf[UNMAP_HEAD_SIZE]; + uint64_t lba, lastlba = (uint64_t)-1; + uint64_t totalcount = 0; + uint64_t count; + uint32_t c, lastcount = 0, ranges = 0; + + /* + * Currently this doesn't take the UNMAP + * Granularity and Granularity Alignment + * fields into account. + * + * This could result in both unoptimal unmap + * requests as as well as UNMAP calls unmapping + * fewer LBA's than requested. + */ + + bzero(softc->unmap_buf, sizeof(softc->unmap_buf)); + bp1 = bp; + do { + /* + * Note: ada and da are different in how they store the + * pending bp's in a trim. ada stores all of them in the + * trim_req.bps. da stores all but the first one in the + * delete_run_queue. ada then completes all the bps in + * its adadone() loop. da completes all the bps in the + * delete_run_queue in dadone, and relies on the biodone + * after to complete. This should be reconciled since there's + * no real reason to do it differently. XXX + */ + if (bp1 != bp) + bioq_insert_tail(&softc->delete_run_queue, bp1); + lba = bp1->bio_pblkno; + count = bp1->bio_bcount / softc->params.secsize; + + /* Try to extend the previous range. */ + if (lba == lastlba) { + c = omin(count, UNMAP_RANGE_MAX - lastcount); + lastlba += c; + lastcount += c; + scsi_ulto4b(lastcount, d[ranges - 1].length); + count -= c; + lba += c; + totalcount += c; + } else if ((softc->quirks & DA_Q_STRICT_UNMAP) && + softc->unmap_gran != 0) { + /* Align length of the previous range. */ + if ((c = lastcount % softc->unmap_gran) != 0) { + if (lastcount <= c) { + totalcount -= lastcount; + lastlba = (uint64_t)-1; + lastcount = 0; + ranges--; + } else { + totalcount -= c; + lastlba -= c; + lastcount -= c; + scsi_ulto4b(lastcount, + d[ranges - 1].length); + } + } + /* Align beginning of the new range. */ + c = (lba - softc->unmap_gran_align) % softc->unmap_gran; + if (c != 0) { + c = softc->unmap_gran - c; + if (count <= c) { + count = 0; + } else { + lba += c; + count -= c; + } + } + } + + while (count > 0) { + c = omin(count, UNMAP_RANGE_MAX); + if (totalcount + c > softc->unmap_max_lba || + ranges >= softc->unmap_max_ranges) { + xpt_print(periph->path, + "%s issuing short delete %ld > %ld" + "|| %d >= %d", + da_delete_method_desc[softc->delete_method], + totalcount + c, softc->unmap_max_lba, + ranges, softc->unmap_max_ranges); + break; + } + scsi_u64to8b(lba, d[ranges].lba); + scsi_ulto4b(c, d[ranges].length); + lba += c; + totalcount += c; + ranges++; + count -= c; + lastlba = lba; + lastcount = c; + } + bp1 = cam_iosched_next_trim(softc->cam_iosched); + if (bp1 == NULL) + break; + if (ranges >= softc->unmap_max_ranges || + totalcount + bp1->bio_bcount / + softc->params.secsize > softc->unmap_max_lba) { + cam_iosched_put_back_trim(softc->cam_iosched, bp1); + break; + } + } while (1); + + /* Align length of the last range. */ + if ((softc->quirks & DA_Q_STRICT_UNMAP) && softc->unmap_gran != 0 && + (c = lastcount % softc->unmap_gran) != 0) { + if (lastcount <= c) + ranges--; + else + scsi_ulto4b(lastcount - c, d[ranges - 1].length); + } + + scsi_ulto2b(ranges * 16 + 6, &buf[0]); + scsi_ulto2b(ranges * 16, &buf[2]); + + scsi_unmap(&ccb->csio, + /*retries*/da_retry_count, + /*cbfcnp*/dadone, + /*tag_action*/MSG_SIMPLE_Q_TAG, + /*byte2*/0, + /*data_ptr*/ buf, + /*dxfer_len*/ ranges * 16 + 8, + /*sense_len*/SSD_FULL_SIZE, + da_default_timeout * 1000); + ccb->ccb_h.ccb_state = DA_CCB_DELETE; + ccb->ccb_h.flags |= CAM_UNLOCKED; + softc->trim_count++; + softc->trim_ranges += ranges; + softc->trim_lbas += totalcount; + cam_iosched_submit_trim(softc->cam_iosched); +} + +static void +da_delete_trim(struct cam_periph *periph, union ccb *ccb, struct bio *bp) +{ + struct da_softc *softc = (struct da_softc *)periph->softc; + struct bio *bp1; + uint8_t *buf = softc->unmap_buf; + uint64_t lastlba = (uint64_t)-1; + uint64_t count; + uint64_t lba; + uint32_t lastcount = 0, c, requestcount; + int ranges = 0, off, block_count; + + bzero(softc->unmap_buf, sizeof(softc->unmap_buf)); + bp1 = bp; + do { + if (bp1 != bp)//XXX imp XXX + bioq_insert_tail(&softc->delete_run_queue, bp1); + lba = bp1->bio_pblkno; + count = bp1->bio_bcount / softc->params.secsize; + requestcount = count; + + /* Try to extend the previous range. */ + if (lba == lastlba) { + c = omin(count, ATA_DSM_RANGE_MAX - lastcount); + lastcount += c; + off = (ranges - 1) * 8; + buf[off + 6] = lastcount & 0xff; + buf[off + 7] = (lastcount >> 8) & 0xff; + count -= c; + lba += c; + } + + while (count > 0) { + c = omin(count, ATA_DSM_RANGE_MAX); + off = ranges * 8; + + buf[off + 0] = lba & 0xff; + buf[off + 1] = (lba >> 8) & 0xff; + buf[off + 2] = (lba >> 16) & 0xff; + buf[off + 3] = (lba >> 24) & 0xff; + buf[off + 4] = (lba >> 32) & 0xff; + buf[off + 5] = (lba >> 40) & 0xff; + buf[off + 6] = c & 0xff; + buf[off + 7] = (c >> 8) & 0xff; + lba += c; + ranges++; + count -= c; + lastcount = c; + if (count != 0 && ranges == softc->trim_max_ranges) { + xpt_print(periph->path, + "%s issuing short delete %ld > %ld\n", + da_delete_method_desc[softc->delete_method], + requestcount, + (softc->trim_max_ranges - ranges) * + ATA_DSM_RANGE_MAX); + break; + } + } + lastlba = lba; + bp1 = cam_iosched_next_trim(softc->cam_iosched); + if (bp1 == NULL) + break; + if (bp1->bio_bcount / softc->params.secsize > + (softc->trim_max_ranges - ranges) * ATA_DSM_RANGE_MAX) { + cam_iosched_put_back_trim(softc->cam_iosched, bp1); + break; + } + } while (1); + + block_count = howmany(ranges, ATA_DSM_BLK_RANGES); + scsi_ata_trim(&ccb->csio, + /*retries*/da_retry_count, + /*cbfcnp*/dadone, + /*tag_action*/MSG_SIMPLE_Q_TAG, + block_count, + /*data_ptr*/buf, + /*dxfer_len*/block_count * ATA_DSM_BLK_SIZE, + /*sense_len*/SSD_FULL_SIZE, + da_default_timeout * 1000); + ccb->ccb_h.ccb_state = DA_CCB_DELETE; + ccb->ccb_h.flags |= CAM_UNLOCKED; + cam_iosched_submit_trim(softc->cam_iosched); +} + +/* + * We calculate ws_max_blks here based off d_delmaxsize instead + * of using softc->ws_max_blks as it is absolute max for the + * device not the protocol max which may well be lower. + */ +static void +da_delete_ws(struct cam_periph *periph, union ccb *ccb, struct bio *bp) +{ + struct da_softc *softc; + struct bio *bp1; + uint64_t ws_max_blks; + uint64_t lba; + uint64_t count; /* forward compat with WS32 */ + + softc = (struct da_softc *)periph->softc; + ws_max_blks = softc->disk->d_delmaxsize / softc->params.secsize; + lba = bp->bio_pblkno; + count = 0; + bp1 = bp; + do { + if (bp1 != bp)//XXX imp XXX + bioq_insert_tail(&softc->delete_run_queue, bp1); + count += bp1->bio_bcount / softc->params.secsize; + if (count > ws_max_blks) { + xpt_print(periph->path, + "%s issuing short delete %ld > %ld\n", + da_delete_method_desc[softc->delete_method], + count, ws_max_blks); + count = omin(count, ws_max_blks); + break; + } + bp1 = cam_iosched_next_trim(softc->cam_iosched); + if (bp1 == NULL) + break; + if (lba + count != bp1->bio_pblkno || + count + bp1->bio_bcount / + softc->params.secsize > ws_max_blks) { + cam_iosched_put_back_trim(softc->cam_iosched, bp1); + break; + } + } while (1); + + scsi_write_same(&ccb->csio, + /*retries*/da_retry_count, + /*cbfcnp*/dadone, + /*tag_action*/MSG_SIMPLE_Q_TAG, + /*byte2*/softc->delete_method == + DA_DELETE_ZERO ? 0 : SWS_UNMAP, + softc->delete_method == DA_DELETE_WS16 ? 16 : 10, + /*lba*/lba, + /*block_count*/count, + /*data_ptr*/ __DECONST(void *, zero_region), + /*dxfer_len*/ softc->params.secsize, + /*sense_len*/SSD_FULL_SIZE, + da_default_timeout * 1000); + ccb->ccb_h.ccb_state = DA_CCB_DELETE; + ccb->ccb_h.flags |= CAM_UNLOCKED; + cam_iosched_submit_trim(softc->cam_iosched); +} + +static int +cmd6workaround(union ccb *ccb) +{ + struct scsi_rw_6 cmd6; + struct scsi_rw_10 *cmd10; + struct da_softc *softc; + u_int8_t *cdb; + struct bio *bp; + int frozen; + + cdb = ccb->csio.cdb_io.cdb_bytes; + softc = (struct da_softc *)xpt_path_periph(ccb->ccb_h.path)->softc; + + if (ccb->ccb_h.ccb_state == DA_CCB_DELETE) { + da_delete_methods old_method = softc->delete_method; + + /* + * Typically there are two reasons for failure here + * 1. Delete method was detected as supported but isn't + * 2. Delete failed due to invalid params e.g. too big + * + * While we will attempt to choose an alternative delete method + * this may result in short deletes if the existing delete + * requests from geom are big for the new method chosen. + * + * This method assumes that the error which triggered this + * will not retry the io otherwise a panic will occur + */ + dadeleteflag(softc, old_method, 0); + dadeletemethodchoose(softc, DA_DELETE_DISABLE); + if (softc->delete_method == DA_DELETE_DISABLE) + xpt_print(ccb->ccb_h.path, + "%s failed, disabling BIO_DELETE\n", + da_delete_method_desc[old_method]); + else + xpt_print(ccb->ccb_h.path, + "%s failed, switching to %s BIO_DELETE\n", + da_delete_method_desc[old_method], + da_delete_method_desc[softc->delete_method]); + + while ((bp = bioq_takefirst(&softc->delete_run_queue)) != NULL) + cam_iosched_queue_work(softc->cam_iosched, bp); + cam_iosched_queue_work(softc->cam_iosched, + (struct bio *)ccb->ccb_h.ccb_bp); + ccb->ccb_h.ccb_bp = NULL; + return (0); + } + + /* Detect unsupported PREVENT ALLOW MEDIUM REMOVAL. */ + if ((ccb->ccb_h.flags & CAM_CDB_POINTER) == 0 && + (*cdb == PREVENT_ALLOW) && + (softc->quirks & DA_Q_NO_PREVENT) == 0) { + if (bootverbose) + xpt_print(ccb->ccb_h.path, + "PREVENT ALLOW MEDIUM REMOVAL not supported.\n"); + softc->quirks |= DA_Q_NO_PREVENT; + return (0); + } + + /* Detect unsupported SYNCHRONIZE CACHE(10). */ + if ((ccb->ccb_h.flags & CAM_CDB_POINTER) == 0 && + (*cdb == SYNCHRONIZE_CACHE) && + (softc->quirks & DA_Q_NO_SYNC_CACHE) == 0) { + if (bootverbose) + xpt_print(ccb->ccb_h.path, + "SYNCHRONIZE CACHE(10) not supported.\n"); + softc->quirks |= DA_Q_NO_SYNC_CACHE; + softc->disk->d_flags &= ~DISKFLAG_CANFLUSHCACHE; + return (0); + } + + /* Translation only possible if CDB is an array and cmd is R/W6 */ + if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0 || + (*cdb != READ_6 && *cdb != WRITE_6)) + return 0; + + xpt_print(ccb->ccb_h.path, "READ(6)/WRITE(6) not supported, " + "increasing minimum_cmd_size to 10.\n"); + softc->minimum_cmd_size = 10; + + bcopy(cdb, &cmd6, sizeof(struct scsi_rw_6)); + cmd10 = (struct scsi_rw_10 *)cdb; + cmd10->opcode = (cmd6.opcode == READ_6) ? READ_10 : WRITE_10; + cmd10->byte2 = 0; + scsi_ulto4b(scsi_3btoul(cmd6.addr), cmd10->addr); + cmd10->reserved = 0; + scsi_ulto2b(cmd6.length, cmd10->length); + cmd10->control = cmd6.control; + ccb->csio.cdb_len = sizeof(*cmd10); + + /* Requeue request, unfreezing queue if necessary */ + frozen = (ccb->ccb_h.status & CAM_DEV_QFRZN) != 0; + ccb->ccb_h.status = CAM_REQUEUE_REQ; + xpt_action(ccb); + if (frozen) { + cam_release_devq(ccb->ccb_h.path, + /*relsim_flags*/0, + /*reduction*/0, + /*timeout*/0, + /*getcount_only*/0); + } + return (ERESTART); +} + +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 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; + 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 bio *bp, *bp1; + struct da_softc *softc; + struct ccb_scsiio *csio; + u_int32_t priority; + da_ccb_state state; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + +#if defined(BUF_TRACKING) || defined(FULL_BUF_TRACKING) + if (csio->bio != NULL) + biotrack(csio->bio, __func__); +#endif + state = csio->ccb_h.ccb_state & DA_CCB_TYPE_MASK; + + cam_periph_lock(periph); + bp = (struct bio *)done_ccb->ccb_h.ccb_bp; + if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + int error; + int sf; + + if ((csio->ccb_h.ccb_state & DA_CCB_RETRY_UA) != 0) + sf = SF_RETRY_UA; + else + sf = 0; + + error = daerror(done_ccb, CAM_RETRY_SELTO, sf); + if (error == ERESTART) { + /* A retry was scheduled, so just return. */ + cam_periph_unlock(periph); + return; + } + bp = (struct bio *)done_ccb->ccb_h.ccb_bp; + if (error != 0) { + int queued_error; + + /* + * return all queued I/O with EIO, so that + * the client can retry these I/Os in the + * proper order should it attempt to recover. + */ + queued_error = EIO; + + if (error == ENXIO + && (softc->flags & DA_FLAG_PACK_INVALID)== 0) { + /* + * Catastrophic error. Mark our pack as + * invalid. + * + * XXX See if this is really a media + * XXX change first? + */ + xpt_print(periph->path, "Invalidating pack\n"); + softc->flags |= DA_FLAG_PACK_INVALID; +#ifdef CAM_IO_STATS + softc->invalidations++; +#endif + queued_error = ENXIO; + } + cam_iosched_flush(softc->cam_iosched, NULL, + queued_error); + if (bp != NULL) { + bp->bio_error = error; + bp->bio_resid = bp->bio_bcount; + bp->bio_flags |= BIO_ERROR; + } + } else if (bp != NULL) { + if (state == DA_CCB_DELETE) + bp->bio_resid = 0; + else + bp->bio_resid = csio->resid; + bp->bio_error = 0; + if (bp->bio_resid != 0) + bp->bio_flags |= BIO_ERROR; + } + if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) + cam_release_devq(done_ccb->ccb_h.path, + /*relsim_flags*/0, + /*reduction*/0, + /*timeout*/0, + /*getcount_only*/0); + } else if (bp != NULL) { + if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) + panic("REQ_CMP with QFRZN"); + 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) && (bp->bio_cmd != BIO_ZONE)) + bp->bio_flags |= BIO_ERROR; + if (softc->error_inject != 0) { + bp->bio_error = softc->error_inject; + bp->bio_resid = bp->bio_bcount; + bp->bio_flags |= BIO_ERROR; + softc->error_inject = 0; + } + } + + if (bp != NULL) + biotrack(bp, __func__); + LIST_REMOVE(&done_ccb->ccb_h, periph_links.le); + if (LIST_EMPTY(&softc->pending_ccbs)) + softc->flags |= DA_FLAG_WAS_OTAG; + + /* + * We need to call cam_iosched before we call biodone so that we don't + * measure any activity that happens in the completion routine, which in + * the case of sendfile can be quite extensive. Release the periph + * refcount taken in dastart() for each CCB. + */ + cam_iosched_bio_complete(softc->cam_iosched, bp, done_ccb); + xpt_release_ccb(done_ccb); + KASSERT(softc->refcount >= 1, ("dadone softc %p refcount %d", softc, softc->refcount)); + softc->refcount--; + if (state == DA_CCB_DELETE) { + TAILQ_HEAD(, bio) queue; + + TAILQ_INIT(&queue); + TAILQ_CONCAT(&queue, &softc->delete_run_queue.queue, bio_queue); + softc->delete_run_queue.insert_point = NULL; + /* + * Normally, the xpt_release_ccb() above would make sure + * that when we have more work to do, that work would + * get kicked off. However, we specifically keep + * delete_running set to 0 before the call above to + * allow other I/O to progress when many BIO_DELETE + * requests are pushed down. We set delete_running to 0 + * and call daschedule again so that we don't stall if + * there are no other I/Os pending apart from BIO_DELETEs. + */ + cam_iosched_trim_done(softc->cam_iosched); + daschedule(periph); + cam_periph_unlock(periph); + while ((bp1 = TAILQ_FIRST(&queue)) != NULL) { + TAILQ_REMOVE(&queue, bp1, bio_queue); + bp1->bio_error = bp->bio_error; + if (bp->bio_flags & BIO_ERROR) { + bp1->bio_flags |= BIO_ERROR; + bp1->bio_resid = bp1->bio_bcount; + } else + bp1->bio_resid = 0; + biodone(bp1); + } + } else { + daschedule(periph); + cam_periph_unlock(periph); + } + if (bp != NULL) + biodone(bp); + return; +} + +static void +dadone_probewp(struct cam_periph *periph, union ccb *done_ccb) +{ + struct scsi_mode_header_6 *mode_hdr6; + struct scsi_mode_header_10 *mode_hdr10; + struct da_softc *softc; + struct ccb_scsiio *csio; + u_int32_t priority; + uint8_t dev_spec; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probewp\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + + cam_periph_assert(periph, MA_OWNED); + + KASSERT(softc->state == DA_STATE_PROBE_WP, + ("State (%d) not PROBE_WP in dadone_probewp, periph %p ccb %p", + softc->state, periph, done_ccb)); + KASSERT((csio->ccb_h.ccb_state & DA_CCB_TYPE_MASK) == DA_CCB_PROBE_WP, + ("CCB State (%lu) not PROBE_WP in dadone_probewp, periph %p ccb %p", + (unsigned long)csio->ccb_h.ccb_state & DA_CCB_TYPE_MASK, periph, + done_ccb)); + + if (softc->minimum_cmd_size > 6) { + mode_hdr10 = (struct scsi_mode_header_10 *)csio->data_ptr; + dev_spec = mode_hdr10->dev_spec; + } else { + mode_hdr6 = (struct scsi_mode_header_6 *)csio->data_ptr; + dev_spec = mode_hdr6->dev_spec; + } + if (cam_ccb_status(done_ccb) == CAM_REQ_CMP) { + if ((dev_spec & 0x80) != 0) + softc->disk->d_flags |= DISKFLAG_WRITE_PROTECT; + else + softc->disk->d_flags &= ~DISKFLAG_WRITE_PROTECT; + } 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) { + /* 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 ((softc->flags & DA_FLAG_CAN_RC16) != 0) + softc->state = DA_STATE_PROBE_RC16; + else + softc->state = DA_STATE_PROBE_RC; + xpt_release_ccb(done_ccb); + xpt_schedule(periph, priority); + return; +} + +static void +dadone_proberc(struct cam_periph *periph, union ccb *done_ccb) +{ + struct scsi_read_capacity_data *rdcap; + struct scsi_read_capacity_data_long *rcaplong; + struct da_softc *softc; + struct ccb_scsiio *csio; + da_ccb_state state; + char *announce_buf; + u_int32_t priority; + int lbp, n; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_proberc\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + state = csio->ccb_h.ccb_state & DA_CCB_TYPE_MASK; + + KASSERT(softc->state == DA_STATE_PROBE_RC || softc->state == DA_STATE_PROBE_RC16, + ("State (%d) not PROBE_RC* in dadone_proberc, periph %p ccb %p", + softc->state, periph, done_ccb)); + KASSERT(state == DA_CCB_PROBE_RC || state == DA_CCB_PROBE_RC16, + ("CCB State (%lu) not PROBE_RC* in dadone_probewp, periph %p ccb %p", + (unsigned long)state, periph, done_ccb)); + + lbp = 0; + rdcap = NULL; + rcaplong = NULL; + /* XXX TODO: can this be a malloc? */ + announce_buf = softc->announce_temp; + bzero(announce_buf, DA_ANNOUNCETMP_SZ); + + if (state == DA_CCB_PROBE_RC) + rdcap =(struct scsi_read_capacity_data *)csio->data_ptr; + else + rcaplong = (struct scsi_read_capacity_data_long *) + csio->data_ptr; + + cam_periph_assert(periph, MA_OWNED); + + if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + struct disk_params *dp; + uint32_t block_size; + uint64_t maxsector; + u_int lalba; /* Lowest aligned LBA. */ + + if (state == DA_CCB_PROBE_RC) { + block_size = scsi_4btoul(rdcap->length); + maxsector = scsi_4btoul(rdcap->addr); + lalba = 0; + + /* + * According to SBC-2, if the standard 10 + * byte READ CAPACITY command returns 2^32, + * we should issue the 16 byte version of + * the command, since the device in question + * has more sectors than can be represented + * with the short version of the command. + */ + if (maxsector == 0xffffffff) { + free(rdcap, M_SCSIDA); + softc->state = DA_STATE_PROBE_RC16; + xpt_release_ccb(done_ccb); + xpt_schedule(periph, priority); + return; + } + } else { + block_size = scsi_4btoul(rcaplong->length); + maxsector = scsi_8btou64(rcaplong->addr); + lalba = scsi_2btoul(rcaplong->lalba_lbp); + } + + /* + * Because GEOM code just will panic us if we + * give them an 'illegal' value we'll avoid that + * here. + */ + if (block_size == 0) { + block_size = 512; + if (maxsector == 0) + maxsector = -1; + } + if (block_size >= maxphys) { + xpt_print(periph->path, + "unsupportable block size %ju\n", + (uintmax_t) block_size); + announce_buf = NULL; + cam_periph_invalidate(periph); + } else { + /* + * We pass rcaplong into dasetgeom(), + * because it will only use it if it is + * non-NULL. + */ + dasetgeom(periph, block_size, maxsector, + rcaplong, sizeof(*rcaplong)); + lbp = (lalba & SRC16_LBPME_A); + dp = &softc->params; + n = snprintf(announce_buf, DA_ANNOUNCETMP_SZ, + "%juMB (%ju %u byte sectors", + ((uintmax_t)dp->secsize * dp->sectors) / + (1024 * 1024), + (uintmax_t)dp->sectors, dp->secsize); + if (softc->p_type != 0) { + n += snprintf(announce_buf + n, + DA_ANNOUNCETMP_SZ - n, + ", DIF type %d", softc->p_type); + } + snprintf(announce_buf + n, DA_ANNOUNCETMP_SZ - n, ")"); + } + } else { + int error; + + /* + * Retry any UNIT ATTENTION type errors. They + * are expected at boot. + */ + error = daerror(done_ccb, CAM_RETRY_SELTO, + SF_RETRY_UA|SF_NO_PRINT); + if (error == ERESTART) { + /* + * A retry was scheuled, so + * just return. + */ + return; + } else if (error != 0) { + int asc, ascq; + int sense_key, error_code; + int have_sense; + cam_status status; + struct ccb_getdev cgd; + + /* Don't wedge this device's queue */ + status = done_ccb->ccb_h.status; + if ((status & CAM_DEV_QFRZN) != 0) + cam_release_devq(done_ccb->ccb_h.path, + /*relsim_flags*/0, + /*reduction*/0, + /*timeout*/0, + /*getcount_only*/0); + + xpt_setup_ccb(&cgd.ccb_h, done_ccb->ccb_h.path, + CAM_PRIORITY_NORMAL); + cgd.ccb_h.func_code = XPT_GDEV_TYPE; + xpt_action((union ccb *)&cgd); + + if (scsi_extract_sense_ccb(done_ccb, + &error_code, &sense_key, &asc, &ascq)) + have_sense = TRUE; + else + have_sense = FALSE; + + /* + * If we tried READ CAPACITY(16) and failed, + * fallback to READ CAPACITY(10). + */ + if ((state == DA_CCB_PROBE_RC16) && + (softc->flags & DA_FLAG_CAN_RC16) && + (((csio->ccb_h.status & CAM_STATUS_MASK) == + CAM_REQ_INVALID) || + ((have_sense) && + (error_code == SSD_CURRENT_ERROR || + error_code == SSD_DESC_CURRENT_ERROR) && + (sense_key == SSD_KEY_ILLEGAL_REQUEST)))) { + cam_periph_assert(periph, MA_OWNED); + softc->flags &= ~DA_FLAG_CAN_RC16; + free(rdcap, M_SCSIDA); + softc->state = DA_STATE_PROBE_RC; + xpt_release_ccb(done_ccb); + xpt_schedule(periph, priority); + return; + } + + /* + * Attach to anything that claims to be a + * direct access or optical disk device, + * as long as it doesn't return a "Logical + * unit not supported" (0x25) error. + * "Internal Target Failure" (0x44) is also + * special and typically means that the + * device is a SATA drive behind a SATL + * translation that's fallen into a + * terminally fatal state. + */ + if ((have_sense) + && (asc != 0x25) && (asc != 0x44) + && (error_code == SSD_CURRENT_ERROR + || error_code == SSD_DESC_CURRENT_ERROR)) { + const char *sense_key_desc; + const char *asc_desc; + + dasetgeom(periph, 512, -1, NULL, 0); + scsi_sense_desc(sense_key, asc, ascq, + &cgd.inq_data, &sense_key_desc, + &asc_desc); + snprintf(announce_buf, DA_ANNOUNCETMP_SZ, + "Attempt to query device " + "size failed: %s, %s", + sense_key_desc, asc_desc); + } else { + if (have_sense) + scsi_sense_print(&done_ccb->csio); + else { + xpt_print(periph->path, + "got CAM status %#x\n", + done_ccb->ccb_h.status); + } + + xpt_print(periph->path, "fatal error, " + "failed to attach to device\n"); + + announce_buf = NULL; + + /* + * Free up resources. + */ + cam_periph_invalidate(periph); + } + } + } + free(csio->data_ptr, M_SCSIDA); + if (announce_buf != NULL && + ((softc->flags & DA_FLAG_ANNOUNCED) == 0)) { + struct sbuf sb; + + sbuf_new(&sb, softc->announcebuf, DA_ANNOUNCE_SZ, + SBUF_FIXEDLEN); + xpt_announce_periph_sbuf(periph, &sb, announce_buf); + xpt_announce_quirks_sbuf(periph, &sb, softc->quirks, + DA_Q_BIT_STRING); + sbuf_finish(&sb); + sbuf_putbuf(&sb); + + /* + * Create our sysctl variables, now that we know + * we have successfully attached. + */ + /* increase the refcount */ + if (da_periph_acquire(periph, DA_REF_SYSCTL) == 0) { + taskqueue_enqueue(taskqueue_thread, + &softc->sysctl_task); + } else { + /* XXX This message is useless! */ + xpt_print(periph->path, "fatal error, " + "could not acquire reference count\n"); + } + } + + /* We already probed the device. */ + if (softc->flags & DA_FLAG_PROBED) { + daprobedone(periph, done_ccb); + return; + } + + /* Ensure re-probe doesn't see old delete. */ + softc->delete_available = 0; + dadeleteflag(softc, DA_DELETE_ZERO, 1); + if (lbp && (softc->quirks & DA_Q_NO_UNMAP) == 0) { + /* + * Based on older SBC-3 spec revisions + * any of the UNMAP methods "may" be + * available via LBP given this flag so + * we flag all of them as available and + * then remove those which further + * probes confirm aren't available + * later. + * + * We could also check readcap(16) p_type + * flag to exclude one or more invalid + * write same (X) types here + */ + dadeleteflag(softc, DA_DELETE_WS16, 1); + dadeleteflag(softc, DA_DELETE_WS10, 1); + dadeleteflag(softc, DA_DELETE_UNMAP, 1); + + softc->state = DA_STATE_PROBE_LBP; + xpt_release_ccb(done_ccb); + xpt_schedule(periph, priority); + return; + } + + softc->state = DA_STATE_PROBE_BDC; + xpt_release_ccb(done_ccb); + xpt_schedule(periph, priority); + return; +} + +static void +dadone_probelbp(struct cam_periph *periph, union ccb *done_ccb) +{ + struct scsi_vpd_logical_block_prov *lbp; + struct da_softc *softc; + struct ccb_scsiio *csio; + u_int32_t priority; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probelbp\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + lbp = (struct scsi_vpd_logical_block_prov *)csio->data_ptr; + + cam_periph_assert(periph, MA_OWNED); + + if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + /* + * T10/1799-D Revision 31 states at least one of these + * must be supported but we don't currently enforce this. + */ + dadeleteflag(softc, DA_DELETE_WS16, + (lbp->flags & SVPD_LBP_WS16)); + dadeleteflag(softc, DA_DELETE_WS10, + (lbp->flags & SVPD_LBP_WS10)); + dadeleteflag(softc, DA_DELETE_UNMAP, + (lbp->flags & SVPD_LBP_UNMAP)); + } 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) { + /* 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); + } + + /* + * Failure indicates we don't support any SBC-3 + * delete methods with UNMAP + */ + } + } + + free(lbp, M_SCSIDA); + softc->state = DA_STATE_PROBE_BLK_LIMITS; + xpt_release_ccb(done_ccb); + xpt_schedule(periph, priority); + return; +} + +static void +dadone_probeblklimits(struct cam_periph *periph, union ccb *done_ccb) +{ + struct scsi_vpd_block_limits *block_limits; + struct da_softc *softc; + struct ccb_scsiio *csio; + u_int32_t priority; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probeblklimits\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + block_limits = (struct scsi_vpd_block_limits *)csio->data_ptr; + + cam_periph_assert(periph, MA_OWNED); + + if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + uint32_t max_txfer_len = scsi_4btoul( + block_limits->max_txfer_len); + uint32_t max_unmap_lba_cnt = scsi_4btoul( + block_limits->max_unmap_lba_cnt); + uint32_t max_unmap_blk_cnt = scsi_4btoul( + block_limits->max_unmap_blk_cnt); + uint32_t unmap_gran = scsi_4btoul( + block_limits->opt_unmap_grain); + uint32_t unmap_gran_align = scsi_4btoul( + block_limits->unmap_grain_align); + uint64_t ws_max_blks = scsi_8btou64( + block_limits->max_write_same_length); + + if (max_txfer_len != 0) { + softc->disk->d_maxsize = MIN(softc->maxio, + (off_t)max_txfer_len * softc->params.secsize); + } + + /* + * We should already support UNMAP but we check lba + * and block count to be sure + */ + if (max_unmap_lba_cnt != 0x00L && + max_unmap_blk_cnt != 0x00L) { + softc->unmap_max_lba = max_unmap_lba_cnt; + softc->unmap_max_ranges = min(max_unmap_blk_cnt, + UNMAP_MAX_RANGES); + if (unmap_gran > 1) { + softc->unmap_gran = unmap_gran; + if (unmap_gran_align & 0x80000000) { + softc->unmap_gran_align = + unmap_gran_align & 0x7fffffff; + } + } + } else { + /* + * Unexpected UNMAP limits which means the + * device doesn't actually support UNMAP + */ + dadeleteflag(softc, DA_DELETE_UNMAP, 0); + } + + if (ws_max_blks != 0x00L) + softc->ws_max_blks = ws_max_blks; + } 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) { + /* 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); + } + + /* + * Failure here doesn't mean UNMAP is not + * supported as this is an optional page. + */ + softc->unmap_max_lba = 1; + softc->unmap_max_ranges = 1; + } + } + + free(block_limits, M_SCSIDA); + softc->state = DA_STATE_PROBE_BDC; + xpt_release_ccb(done_ccb); + xpt_schedule(periph, priority); + return; +} + +static void +dadone_probebdc(struct cam_periph *periph, union ccb *done_ccb) +{ + struct scsi_vpd_block_device_characteristics *bdc; + struct da_softc *softc; + struct ccb_scsiio *csio; + u_int32_t priority; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probebdc\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + bdc = (struct scsi_vpd_block_device_characteristics *)csio->data_ptr; + + cam_periph_assert(periph, MA_OWNED); + + if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + uint32_t valid_len; + + /* + * Disable queue sorting for non-rotational media + * by default. + */ + u_int16_t old_rate = softc->disk->d_rotation_rate; + + 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->flags &= ~DA_FLAG_ROTATING; + } + 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; + 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); + } + } + } + + free(bdc, M_SCSIDA); + softc->state = DA_STATE_PROBE_ATA; + xpt_release_ccb(done_ccb); + xpt_schedule(periph, priority); + return; +} + +static void +dadone_probeata(struct cam_periph *periph, union ccb *done_ccb) +{ + struct ata_params *ata_params; + struct ccb_scsiio *csio; + struct da_softc *softc; + u_int32_t priority; + int continue_probe; + int error; + int16_t *ptr; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probeata\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + ata_params = (struct ata_params *)csio->data_ptr; + ptr = (uint16_t *)ata_params; + continue_probe = 0; + error = 0; + + cam_periph_assert(periph, MA_OWNED); + + if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + uint16_t old_rate; + + ata_param_fixup(ata_params); + if (ata_params->support_dsm & ATA_SUPPORT_DSM_TRIM && + (softc->quirks & DA_Q_NO_UNMAP) == 0) { + dadeleteflag(softc, DA_DELETE_ATA_TRIM, 1); + if (ata_params->max_dsm_blocks != 0) + softc->trim_max_ranges = min( + softc->trim_max_ranges, + ata_params->max_dsm_blocks * + ATA_DSM_BLK_RANGES); + } + /* + * Disable queue sorting for non-rotational media + * by default. + */ + old_rate = softc->disk->d_rotation_rate; + softc->disk->d_rotation_rate = ata_params->media_rotation_rate; + if (softc->disk->d_rotation_rate == ATA_RATE_NON_ROTATING) { + cam_iosched_set_sort_queue(softc->cam_iosched, 0); + softc->flags &= ~DA_FLAG_ROTATING; + } + if (softc->disk->d_rotation_rate != old_rate) { + disk_attr_changed(softc->disk, + "GEOM::rotation_rate", M_NOWAIT); + } + + cam_periph_assert(periph, MA_OWNED); + 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 { + 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); + } + } + } + + 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_schedule(periph, priority); + xpt_release_ccb(done_ccb); + return; + } else + daprobedone(periph, done_ccb); + return; +} + +static void +dadone_probeatalogdir(struct cam_periph *periph, union ccb *done_ccb) +{ + struct da_softc *softc; + struct ccb_scsiio *csio; + u_int32_t priority; + int error; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probeatalogdir\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + + cam_periph_assert(periph, MA_OWNED); + 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; +} + +static void +dadone_probeataiddir(struct cam_periph *periph, union ccb *done_ccb) +{ + struct da_softc *softc; + struct ccb_scsiio *csio; + u_int32_t priority; + int error; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probeataiddir\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + + cam_periph_assert(periph, MA_OWNED); + + 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; +} + +static void +dadone_probeatasup(struct cam_periph *periph, union ccb *done_ccb) +{ + struct da_softc *softc; + struct ccb_scsiio *csio; + u_int32_t priority; + int error; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probeatasup\n")); + + softc = (struct da_softc *)periph->softc; + priority = done_ccb->ccb_h.pinfo.priority; + csio = &done_ccb->csio; + + cam_periph_assert(periph, MA_OWNED); + + 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; +} + +static void +dadone_probeatazone(struct cam_periph *periph, union ccb *done_ccb) +{ + struct da_softc *softc; + struct ccb_scsiio *csio; + int error; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probeatazone\n")); + + softc = (struct da_softc *)periph->softc; + csio = &done_ccb->csio; + + cam_periph_assert(periph, MA_OWNED); + + 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; +} + +static void +dadone_probezone(struct cam_periph *periph, union ccb *done_ccb) +{ + struct da_softc *softc; + struct ccb_scsiio *csio; + int error; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_probezone\n")); + + softc = (struct da_softc *)periph->softc; + csio = &done_ccb->csio; + + cam_periph_assert(periph, MA_OWNED); + + 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); + } + } + } + + free(csio->data_ptr, M_SCSIDA); + + daprobedone(periph, done_ccb); + return; +} + +static void +dadone_tur(struct cam_periph *periph, union ccb *done_ccb) +{ + struct da_softc *softc; + struct ccb_scsiio *csio; + + CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dadone_tur\n")); + + softc = (struct da_softc *)periph->softc; + csio = &done_ccb->csio; + + cam_periph_assert(periph, MA_OWNED); + + if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + if (daerror(done_ccb, CAM_RETRY_SELTO, + SF_RETRY_UA | SF_NO_RECOVERY | SF_NO_PRINT) == ERESTART) + return; /* Will complete again, keep reference */ + if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) + cam_release_devq(done_ccb->ccb_h.path, + /*relsim_flags*/0, + /*reduction*/0, + /*timeout*/0, + /*getcount_only*/0); + } + softc->flags &= ~DA_FLAG_TUR_PENDING; + xpt_release_ccb(done_ccb); + da_periph_release_locked(periph, DA_REF_TUR); + return; +} + +static void +dareprobe(struct cam_periph *periph) +{ + struct da_softc *softc; + int status; + + softc = (struct da_softc *)periph->softc; + + cam_periph_assert(periph, MA_OWNED); + + /* Probe in progress; don't interfere. */ + if (softc->state != DA_STATE_NORMAL) + return; + + status = da_periph_acquire(periph, DA_REF_REPROBE); + KASSERT(status == 0, ("dareprobe: cam_periph_acquire failed")); + + softc->state = DA_STATE_PROBE_WP; + xpt_schedule(periph, CAM_PRIORITY_DEV); +} + +static int +daerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) +{ + struct da_softc *softc; + struct cam_periph *periph; + int error, error_code, sense_key, asc, ascq; + +#if defined(BUF_TRACKING) || defined(FULL_BUF_TRACKING) + if (ccb->csio.bio != NULL) + biotrack(ccb->csio.bio, __func__); +#endif + + periph = xpt_path_periph(ccb->ccb_h.path); + softc = (struct da_softc *)periph->softc; + + cam_periph_assert(periph, MA_OWNED); + + /* + * Automatically detect devices that do not support + * READ(6)/WRITE(6) and upgrade to using 10 byte cdbs. + */ + error = 0; + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_INVALID) { + error = cmd6workaround(ccb); + } else if (scsi_extract_sense_ccb(ccb, + &error_code, &sense_key, &asc, &ascq)) { + if (sense_key == SSD_KEY_ILLEGAL_REQUEST) + error = cmd6workaround(ccb); + /* + * If the target replied with CAPACITY DATA HAS CHANGED UA, + * query the capacity and notify upper layers. + */ + else if (sense_key == SSD_KEY_UNIT_ATTENTION && + asc == 0x2A && ascq == 0x09) { + xpt_print(periph->path, "Capacity data has changed\n"); + softc->flags &= ~DA_FLAG_PROBED; + dareprobe(periph); + sense_flags |= SF_NO_PRINT; + } else if (sense_key == SSD_KEY_UNIT_ATTENTION && + asc == 0x28 && ascq == 0x00) { + softc->flags &= ~DA_FLAG_PROBED; + disk_media_changed(softc->disk, M_NOWAIT); + } else if (sense_key == SSD_KEY_UNIT_ATTENTION && + asc == 0x3F && ascq == 0x03) { + xpt_print(periph->path, "INQUIRY data has changed\n"); + softc->flags &= ~DA_FLAG_PROBED; + dareprobe(periph); + sense_flags |= SF_NO_PRINT; + } else if (sense_key == SSD_KEY_NOT_READY && + asc == 0x3a && (softc->flags & DA_FLAG_PACK_INVALID) == 0) { + softc->flags |= DA_FLAG_PACK_INVALID; + disk_media_gone(softc->disk, M_NOWAIT); + } + } + if (error == ERESTART) + return (ERESTART); + +#ifdef CAM_IO_STATS + switch (ccb->ccb_h.status & CAM_STATUS_MASK) { + case CAM_CMD_TIMEOUT: + softc->timeouts++; + break; + case CAM_REQ_ABORTED: + case CAM_REQ_CMP_ERR: + case CAM_REQ_TERMIO: + case CAM_UNREC_HBA_ERROR: + case CAM_DATA_RUN_ERR: + softc->errors++; + break; + default: + break; + } +#endif + + /* + * XXX + * Until we have a better way of doing pack validation, + * don't treat UAs as errors. + */ + sense_flags |= SF_RETRY_UA; + + if (softc->quirks & DA_Q_RETRY_BUSY) + sense_flags |= SF_RETRY_BUSY; + return(cam_periph_error(ccb, cam_flags, sense_flags)); +} + +static void +damediapoll(void *arg) +{ + struct cam_periph *periph = arg; + struct da_softc *softc = periph->softc; + + if (!cam_iosched_has_work_flags(softc->cam_iosched, DA_WORK_TUR) && + (softc->flags & DA_FLAG_TUR_PENDING) == 0 && + softc->state == DA_STATE_NORMAL && + LIST_EMPTY(&softc->pending_ccbs)) { + if (da_periph_acquire(periph, DA_REF_TUR) == 0) { + cam_iosched_set_work_flags(softc->cam_iosched, DA_WORK_TUR); + daschedule(periph); + } + } + /* Queue us up again */ + if (da_poll_period != 0) + callout_schedule(&softc->mediapoll_c, da_poll_period * hz); +} + +static void +daprevent(struct cam_periph *periph, int action) +{ + struct da_softc *softc; + union ccb *ccb; + int error; + + cam_periph_assert(periph, MA_OWNED); + softc = (struct da_softc *)periph->softc; + + if (((action == PR_ALLOW) + && (softc->flags & DA_FLAG_PACK_LOCKED) == 0) + || ((action == PR_PREVENT) + && (softc->flags & DA_FLAG_PACK_LOCKED) != 0)) { + return; + } + + ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); + + scsi_prevent(&ccb->csio, + /*retries*/1, + /*cbcfp*/NULL, + MSG_SIMPLE_Q_TAG, + action, + SSD_FULL_SIZE, + 5000); + + error = cam_periph_runccb(ccb, daerror, CAM_RETRY_SELTO, + SF_RETRY_UA | SF_NO_PRINT, softc->disk->d_devstat); + + if (error == 0) { + if (action == PR_ALLOW) + softc->flags &= ~DA_FLAG_PACK_LOCKED; + else + softc->flags |= DA_FLAG_PACK_LOCKED; + } + + xpt_release_ccb(ccb); +} + +static void +dasetgeom(struct cam_periph *periph, uint32_t block_len, uint64_t maxsector, + struct scsi_read_capacity_data_long *rcaplong, size_t rcap_len) +{ + struct ccb_calc_geometry ccg; + struct da_softc *softc; + struct disk_params *dp; + u_int lbppbe, lalba; + int error; + + softc = (struct da_softc *)periph->softc; + + dp = &softc->params; + dp->secsize = block_len; + dp->sectors = maxsector + 1; + if (rcaplong != NULL) { + lbppbe = rcaplong->prot_lbppbe & SRC16_LBPPBE; + lalba = scsi_2btoul(rcaplong->lalba_lbp); + lalba &= SRC16_LALBA_A; + if (rcaplong->prot & SRC16_PROT_EN) + softc->p_type = ((rcaplong->prot & SRC16_P_TYPE) >> + SRC16_P_TYPE_SHIFT) + 1; + else + softc->p_type = 0; + } else { + lbppbe = 0; + lalba = 0; + softc->p_type = 0; + } + + if (lbppbe > 0) { + dp->stripesize = block_len << lbppbe; + dp->stripeoffset = (dp->stripesize - block_len * lalba) % + dp->stripesize; + } else if (softc->quirks & DA_Q_4K) { + dp->stripesize = 4096; + dp->stripeoffset = 0; + } else if (softc->unmap_gran != 0) { + dp->stripesize = block_len * softc->unmap_gran; + dp->stripeoffset = (dp->stripesize - block_len * + softc->unmap_gran_align) % dp->stripesize; + } else { + dp->stripesize = 0; + dp->stripeoffset = 0; + } + /* + * Have the controller provide us with a geometry + * for this disk. The only time the geometry + * matters is when we boot and the controller + * is the only one knowledgeable enough to come + * up with something that will make this a bootable + * device. + */ + xpt_setup_ccb(&ccg.ccb_h, periph->path, CAM_PRIORITY_NORMAL); + ccg.ccb_h.func_code = XPT_CALC_GEOMETRY; + ccg.block_size = dp->secsize; + ccg.volume_size = dp->sectors; + ccg.heads = 0; + ccg.secs_per_track = 0; + ccg.cylinders = 0; + xpt_action((union ccb*)&ccg); + if ((ccg.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + /* + * We don't know what went wrong here- but just pick + * a geometry so we don't have nasty things like divide + * by zero. + */ + dp->heads = 255; + dp->secs_per_track = 255; + dp->cylinders = dp->sectors / (255 * 255); + if (dp->cylinders == 0) { + dp->cylinders = 1; + } + } else { + dp->heads = ccg.heads; + dp->secs_per_track = ccg.secs_per_track; + dp->cylinders = ccg.cylinders; + } + + /* + * If the user supplied a read capacity buffer, and if it is + * different than the previous buffer, update the data in the EDT. + * If it's the same, we don't bother. This avoids sending an + * update every time someone opens this device. + */ + if ((rcaplong != NULL) + && (bcmp(rcaplong, &softc->rcaplong, + min(sizeof(softc->rcaplong), rcap_len)) != 0)) { + struct ccb_dev_advinfo cdai; + + xpt_setup_ccb(&cdai.ccb_h, periph->path, CAM_PRIORITY_NORMAL); + cdai.ccb_h.func_code = XPT_DEV_ADVINFO; + cdai.buftype = CDAI_TYPE_RCAPLONG; + cdai.flags = CDAI_FLAG_STORE; + cdai.bufsiz = rcap_len; + cdai.buf = (uint8_t *)rcaplong; + xpt_action((union ccb *)&cdai); + if ((cdai.ccb_h.status & CAM_DEV_QFRZN) != 0) + cam_release_devq(cdai.ccb_h.path, 0, 0, 0, FALSE); + if (cdai.ccb_h.status != CAM_REQ_CMP) { + xpt_print(periph->path, "%s: failed to set read " + "capacity advinfo\n", __func__); + /* Use cam_error_print() to decode the status */ + cam_error_print((union ccb *)&cdai, CAM_ESF_CAM_STATUS, + CAM_EPF_ALL); + } else { + bcopy(rcaplong, &softc->rcaplong, + min(sizeof(softc->rcaplong), rcap_len)); + } + } + + softc->disk->d_sectorsize = softc->params.secsize; + softc->disk->d_mediasize = softc->params.secsize * (off_t)softc->params.sectors; + softc->disk->d_stripesize = softc->params.stripesize; + softc->disk->d_stripeoffset = softc->params.stripeoffset; + /* XXX: these are not actually "firmware" values, so they may be wrong */ + softc->disk->d_fwsectors = softc->params.secs_per_track; + softc->disk->d_fwheads = softc->params.heads; + softc->disk->d_devstat->block_size = softc->params.secsize; + softc->disk->d_devstat->flags &= ~DEVSTAT_BS_UNAVAILABLE; + + error = disk_resize(softc->disk, M_NOWAIT); + if (error != 0) + xpt_print(periph->path, "disk_resize(9) failed, error = %d\n", error); +} + +static void +dasendorderedtag(void *arg) +{ + struct cam_periph *periph = arg; + struct da_softc *softc = periph->softc; + + cam_periph_assert(periph, MA_OWNED); + if (da_send_ordered) { + if (!LIST_EMPTY(&softc->pending_ccbs)) { + if ((softc->flags & DA_FLAG_WAS_OTAG) == 0) + softc->flags |= DA_FLAG_NEED_OTAG; + softc->flags &= ~DA_FLAG_WAS_OTAG; + } + } + + /* Queue us up again */ + callout_reset(&softc->sendordered_c, + (da_default_timeout * hz) / DA_ORDEREDTAG_INTERVAL, + dasendorderedtag, periph); +} + +/* + * Step through all DA peripheral drivers, and if the device is still open, + * sync the disk cache to physical media. + */ +static void +dashutdown(void * arg, int howto) +{ + struct cam_periph *periph; + struct da_softc *softc; + union ccb *ccb; + int error; + + CAM_PERIPH_FOREACH(periph, &dadriver) { + softc = (struct da_softc *)periph->softc; + if (SCHEDULER_STOPPED()) { + /* If we paniced with the lock held, do not recurse. */ + if (!cam_periph_owned(periph) && + (softc->flags & DA_FLAG_OPEN)) { + dadump(softc->disk, NULL, 0, 0, 0); + } + continue; + } + cam_periph_lock(periph); + + /* + * We only sync the cache if the drive is still open, and + * if the drive is capable of it.. + */ + if (((softc->flags & DA_FLAG_OPEN) == 0) + || (softc->quirks & DA_Q_NO_SYNC_CACHE)) { + cam_periph_unlock(periph); + continue; + } + + ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); + scsi_synchronize_cache(&ccb->csio, + /*retries*/0, + /*cbfcnp*/NULL, + MSG_SIMPLE_Q_TAG, + /*begin_lba*/0, /* whole disk */ + /*lb_count*/0, + SSD_FULL_SIZE, + 60 * 60 * 1000); + + error = cam_periph_runccb(ccb, daerror, /*cam_flags*/0, + /*sense_flags*/ SF_NO_RECOVERY | SF_NO_RETRY | SF_QUIET_IR, + softc->disk->d_devstat); + if (error != 0) + xpt_print(periph->path, "Synchronize cache failed\n"); + xpt_release_ccb(ccb); + cam_periph_unlock(periph); + } +} + +#else /* !_KERNEL */ + +/* + * XXX These are only left out of the kernel build to silence warnings. If, + * for some reason these functions are used in the kernel, the ifdefs should + * be moved so they are included both in the kernel and userland. + */ +void +scsi_format_unit(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + u_int8_t tag_action, u_int8_t byte2, u_int16_t ileave, + u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, + u_int32_t timeout) +{ + struct scsi_format_unit *scsi_cmd; + + scsi_cmd = (struct scsi_format_unit *)&csio->cdb_io.cdb_bytes; + scsi_cmd->opcode = FORMAT_UNIT; + scsi_cmd->byte2 = byte2; + scsi_ulto2b(ileave, scsi_cmd->interleave); + + 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_read_defects(struct ccb_scsiio *csio, uint32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + uint8_t tag_action, uint8_t list_format, + uint32_t addr_desc_index, uint8_t *data_ptr, + uint32_t dxfer_len, int minimum_cmd_size, + uint8_t sense_len, uint32_t timeout) +{ + uint8_t cdb_len; + + /* + * These conditions allow using the 10 byte command. Otherwise we + * need to use the 12 byte command. + */ + if ((minimum_cmd_size <= 10) + && (addr_desc_index == 0) + && (dxfer_len <= SRDD10_MAX_LENGTH)) { + struct scsi_read_defect_data_10 *cdb10; + + cdb10 = (struct scsi_read_defect_data_10 *) + &csio->cdb_io.cdb_bytes; + + cdb_len = sizeof(*cdb10); + bzero(cdb10, cdb_len); + cdb10->opcode = READ_DEFECT_DATA_10; + cdb10->format = list_format; + scsi_ulto2b(dxfer_len, cdb10->alloc_length); + } else { + struct scsi_read_defect_data_12 *cdb12; + + cdb12 = (struct scsi_read_defect_data_12 *) + &csio->cdb_io.cdb_bytes; + + cdb_len = sizeof(*cdb12); + bzero(cdb12, cdb_len); + cdb12->opcode = READ_DEFECT_DATA_12; + cdb12->format = list_format; + scsi_ulto4b(dxfer_len, cdb12->alloc_length); + scsi_ulto4b(addr_desc_index, cdb12->address_descriptor_index); + } + + cam_fill_csio(csio, + retries, + cbfcnp, + /*flags*/ CAM_DIR_IN, + tag_action, + data_ptr, + dxfer_len, + sense_len, + cdb_len, + timeout); +} + +void +scsi_sanitize(struct ccb_scsiio *csio, u_int32_t retries, + void (*cbfcnp)(struct cam_periph *, union ccb *), + u_int8_t tag_action, u_int8_t byte2, u_int16_t control, + u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, + u_int32_t timeout) +{ + struct scsi_sanitize *scsi_cmd; + + scsi_cmd = (struct scsi_sanitize *)&csio->cdb_io.cdb_bytes; + scsi_cmd->opcode = SANITIZE; + scsi_cmd->byte2 = byte2; + scsi_cmd->control = control; + scsi_ulto2b(dxfer_len, scsi_cmd->length); + + 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); +} + +#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_ulto4b(dxfer_len, scsi_cmd->length); + 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); +} |