Index: head/sys/cam/cam_iosched.c =================================================================== --- head/sys/cam/cam_iosched.c (revision 328069) +++ head/sys/cam/cam_iosched.c (revision 328070) @@ -1,1744 +1,1746 @@ /*- * CAM IO Scheduler Interface * + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2015 Netflix, Inc. * 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. * * $FreeBSD$ */ #include "opt_cam.h" #include "opt_ddb.h" #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_CAMSCHED, "CAM I/O Scheduler", "CAM I/O Scheduler buffers"); /* * Default I/O scheduler for FreeBSD. This implementation is just a thin-vineer * over the bioq_* interface, with notions of separate calls for normal I/O and * for trims. * * When CAM_IOSCHED_DYNAMIC is defined, the scheduler is enhanced to dynamically * steer the rate of one type of traffic to help other types of traffic (eg * limit writes when read latency deteriorates on SSDs). */ #ifdef CAM_IOSCHED_DYNAMIC static int do_dynamic_iosched = 1; TUNABLE_INT("kern.cam.do_dynamic_iosched", &do_dynamic_iosched); SYSCTL_INT(_kern_cam, OID_AUTO, do_dynamic_iosched, CTLFLAG_RD, &do_dynamic_iosched, 1, "Enable Dynamic I/O scheduler optimizations."); /* * For an EMA, with an alpha of alpha, we know * alpha = 2 / (N + 1) * or * N = 1 + (2 / alpha) * where N is the number of samples that 86% of the current * EMA is derived from. * * So we invent[*] alpha_bits: * alpha_bits = -log_2(alpha) * alpha = 2^-alpha_bits * So * N = 1 + 2^(alpha_bits + 1) * * The default 9 gives a 1025 lookback for 86% of the data. * For a brief intro: https://en.wikipedia.org/wiki/Moving_average * * [*] Steal from the load average code and many other places. * Note: See computation of EMA and EMVAR for acceptable ranges of alpha. */ static int alpha_bits = 9; TUNABLE_INT("kern.cam.iosched_alpha_bits", &alpha_bits); SYSCTL_INT(_kern_cam, OID_AUTO, iosched_alpha_bits, CTLFLAG_RW, &alpha_bits, 1, "Bits in EMA's alpha."); struct iop_stats; struct cam_iosched_softc; int iosched_debug = 0; typedef enum { none = 0, /* No limits */ queue_depth, /* Limit how many ops we queue to SIM */ iops, /* Limit # of IOPS to the drive */ bandwidth, /* Limit bandwidth to the drive */ limiter_max } io_limiter; static const char *cam_iosched_limiter_names[] = { "none", "queue_depth", "iops", "bandwidth" }; /* * Called to initialize the bits of the iop_stats structure relevant to the * limiter. Called just after the limiter is set. */ typedef int l_init_t(struct iop_stats *); /* * Called every tick. */ typedef int l_tick_t(struct iop_stats *); /* * Called to see if the limiter thinks this IOP can be allowed to * proceed. If so, the limiter assumes that the IOP proceeded * and makes any accounting of it that's needed. */ typedef int l_iop_t(struct iop_stats *, struct bio *); /* * Called when an I/O completes so the limiter can update its * accounting. Pending I/Os may complete in any order (even when * sent to the hardware at the same time), so the limiter may not * make any assumptions other than this I/O has completed. If it * returns 1, then xpt_schedule() needs to be called again. */ typedef int l_iodone_t(struct iop_stats *, struct bio *); static l_iop_t cam_iosched_qd_iop; static l_iop_t cam_iosched_qd_caniop; static l_iodone_t cam_iosched_qd_iodone; static l_init_t cam_iosched_iops_init; static l_tick_t cam_iosched_iops_tick; static l_iop_t cam_iosched_iops_caniop; static l_iop_t cam_iosched_iops_iop; static l_init_t cam_iosched_bw_init; static l_tick_t cam_iosched_bw_tick; static l_iop_t cam_iosched_bw_caniop; static l_iop_t cam_iosched_bw_iop; struct limswitch { l_init_t *l_init; l_tick_t *l_tick; l_iop_t *l_iop; l_iop_t *l_caniop; l_iodone_t *l_iodone; } limsw[] = { { /* none */ .l_init = NULL, .l_tick = NULL, .l_iop = NULL, .l_iodone= NULL, }, { /* queue_depth */ .l_init = NULL, .l_tick = NULL, .l_caniop = cam_iosched_qd_caniop, .l_iop = cam_iosched_qd_iop, .l_iodone= cam_iosched_qd_iodone, }, { /* iops */ .l_init = cam_iosched_iops_init, .l_tick = cam_iosched_iops_tick, .l_caniop = cam_iosched_iops_caniop, .l_iop = cam_iosched_iops_iop, .l_iodone= NULL, }, { /* bandwidth */ .l_init = cam_iosched_bw_init, .l_tick = cam_iosched_bw_tick, .l_caniop = cam_iosched_bw_caniop, .l_iop = cam_iosched_bw_iop, .l_iodone= NULL, }, }; struct iop_stats { /* * sysctl state for this subnode. */ struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; /* * Information about the current rate limiters, if any */ io_limiter limiter; /* How are I/Os being limited */ int min; /* Low range of limit */ int max; /* High range of limit */ int current; /* Current rate limiter */ int l_value1; /* per-limiter scratch value 1. */ int l_value2; /* per-limiter scratch value 2. */ /* * Debug information about counts of I/Os that have gone through the * scheduler. */ int pending; /* I/Os pending in the hardware */ int queued; /* number currently in the queue */ int total; /* Total for all time -- wraps */ int in; /* number queued all time -- wraps */ int out; /* number completed all time -- wraps */ /* * Statistics on different bits of the process. */ /* Exp Moving Average, see alpha_bits for more details */ sbintime_t ema; sbintime_t emvar; sbintime_t sd; /* Last computed sd */ uint32_t state_flags; #define IOP_RATE_LIMITED 1u #define LAT_BUCKETS 15 /* < 1ms < 2ms ... < 2^(n-1)ms >= 2^(n-1)ms*/ uint64_t latencies[LAT_BUCKETS]; struct cam_iosched_softc *softc; }; typedef enum { set_max = 0, /* current = max */ read_latency, /* Steer read latency by throttling writes */ cl_max /* Keep last */ } control_type; static const char *cam_iosched_control_type_names[] = { "set_max", "read_latency" }; struct control_loop { /* * sysctl state for this subnode. */ struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; sbintime_t next_steer; /* Time of next steer */ sbintime_t steer_interval; /* How often do we steer? */ sbintime_t lolat; sbintime_t hilat; int alpha; control_type type; /* What type of control? */ int last_count; /* Last I/O count */ struct cam_iosched_softc *softc; }; #endif struct cam_iosched_softc { struct bio_queue_head bio_queue; struct bio_queue_head trim_queue; /* scheduler flags < 16, user flags >= 16 */ uint32_t flags; int sort_io_queue; #ifdef CAM_IOSCHED_DYNAMIC int read_bias; /* Read bias setting */ int current_read_bias; /* Current read bias state */ int total_ticks; int load; /* EMA of 'load average' of disk / 2^16 */ struct bio_queue_head write_queue; struct iop_stats read_stats, write_stats, trim_stats; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; int quanta; /* Number of quanta per second */ struct callout ticker; /* Callout for our quota system */ struct cam_periph *periph; /* cam periph associated with this device */ uint32_t this_frac; /* Fraction of a second (1024ths) for this tick */ sbintime_t last_time; /* Last time we ticked */ struct control_loop cl; #endif }; #ifdef CAM_IOSCHED_DYNAMIC /* * helper functions to call the limsw functions. */ static int cam_iosched_limiter_init(struct iop_stats *ios) { int lim = ios->limiter; /* maybe this should be a kassert */ if (lim < none || lim >= limiter_max) return EINVAL; if (limsw[lim].l_init) return limsw[lim].l_init(ios); return 0; } static int cam_iosched_limiter_tick(struct iop_stats *ios) { int lim = ios->limiter; /* maybe this should be a kassert */ if (lim < none || lim >= limiter_max) return EINVAL; if (limsw[lim].l_tick) return limsw[lim].l_tick(ios); return 0; } static int cam_iosched_limiter_iop(struct iop_stats *ios, struct bio *bp) { int lim = ios->limiter; /* maybe this should be a kassert */ if (lim < none || lim >= limiter_max) return EINVAL; if (limsw[lim].l_iop) return limsw[lim].l_iop(ios, bp); return 0; } static int cam_iosched_limiter_caniop(struct iop_stats *ios, struct bio *bp) { int lim = ios->limiter; /* maybe this should be a kassert */ if (lim < none || lim >= limiter_max) return EINVAL; if (limsw[lim].l_caniop) return limsw[lim].l_caniop(ios, bp); return 0; } static int cam_iosched_limiter_iodone(struct iop_stats *ios, struct bio *bp) { int lim = ios->limiter; /* maybe this should be a kassert */ if (lim < none || lim >= limiter_max) return 0; if (limsw[lim].l_iodone) return limsw[lim].l_iodone(ios, bp); return 0; } /* * Functions to implement the different kinds of limiters */ static int cam_iosched_qd_iop(struct iop_stats *ios, struct bio *bp) { if (ios->current <= 0 || ios->pending < ios->current) return 0; return EAGAIN; } static int cam_iosched_qd_caniop(struct iop_stats *ios, struct bio *bp) { if (ios->current <= 0 || ios->pending < ios->current) return 0; return EAGAIN; } static int cam_iosched_qd_iodone(struct iop_stats *ios, struct bio *bp) { if (ios->current <= 0 || ios->pending != ios->current) return 0; return 1; } static int cam_iosched_iops_init(struct iop_stats *ios) { ios->l_value1 = ios->current / ios->softc->quanta; if (ios->l_value1 <= 0) ios->l_value1 = 1; ios->l_value2 = 0; return 0; } static int cam_iosched_iops_tick(struct iop_stats *ios) { int new_ios; /* * Allow at least one IO per tick until all * the IOs for this interval have been spent. */ new_ios = (int)((ios->current * (uint64_t)ios->softc->this_frac) >> 16); if (new_ios < 1 && ios->l_value2 < ios->current) { new_ios = 1; ios->l_value2++; } /* * If this a new accounting interval, discard any "unspent" ios * granted in the previous interval. Otherwise add the new ios to * the previously granted ones that haven't been spent yet. */ if ((ios->softc->total_ticks % ios->softc->quanta) == 0) { ios->l_value1 = new_ios; ios->l_value2 = 1; } else { ios->l_value1 += new_ios; } return 0; } static int cam_iosched_iops_caniop(struct iop_stats *ios, struct bio *bp) { /* * So if we have any more IOPs left, allow it, * otherwise wait. If current iops is 0, treat that * as unlimited as a failsafe. */ if (ios->current > 0 && ios->l_value1 <= 0) return EAGAIN; return 0; } static int cam_iosched_iops_iop(struct iop_stats *ios, struct bio *bp) { int rv; rv = cam_iosched_limiter_caniop(ios, bp); if (rv == 0) ios->l_value1--; return rv; } static int cam_iosched_bw_init(struct iop_stats *ios) { /* ios->current is in kB/s, so scale to bytes */ ios->l_value1 = ios->current * 1000 / ios->softc->quanta; return 0; } static int cam_iosched_bw_tick(struct iop_stats *ios) { int bw; /* * If we're in the hole for available quota from * the last time, then add the quantum for this. * If we have any left over from last quantum, * then too bad, that's lost. Also, ios->current * is in kB/s, so scale. * * We also allow up to 4 quanta of credits to * accumulate to deal with burstiness. 4 is extremely * arbitrary. */ bw = (int)((ios->current * 1000ull * (uint64_t)ios->softc->this_frac) >> 16); if (ios->l_value1 < bw * 4) ios->l_value1 += bw; return 0; } static int cam_iosched_bw_caniop(struct iop_stats *ios, struct bio *bp) { /* * So if we have any more bw quota left, allow it, * otherwise wait. Note, we'll go negative and that's * OK. We'll just get a little less next quota. * * Note on going negative: that allows us to process * requests in order better, since we won't allow * shorter reads to get around the long one that we * don't have the quota to do just yet. It also prevents * starvation by being a little more permissive about * what we let through this quantum (to prevent the * starvation), at the cost of getting a little less * next quantum. * * Also note that if the current limit is <= 0, * we treat it as unlimited as a failsafe. */ if (ios->current > 0 && ios->l_value1 <= 0) return EAGAIN; return 0; } static int cam_iosched_bw_iop(struct iop_stats *ios, struct bio *bp) { int rv; rv = cam_iosched_limiter_caniop(ios, bp); if (rv == 0) ios->l_value1 -= bp->bio_length; return rv; } static void cam_iosched_cl_maybe_steer(struct control_loop *clp); static void cam_iosched_ticker(void *arg) { struct cam_iosched_softc *isc = arg; sbintime_t now, delta; int pending; callout_reset(&isc->ticker, hz / isc->quanta, cam_iosched_ticker, isc); now = sbinuptime(); delta = now - isc->last_time; isc->this_frac = (uint32_t)delta >> 16; /* Note: discards seconds -- should be 0 harmless if not */ isc->last_time = now; cam_iosched_cl_maybe_steer(&isc->cl); cam_iosched_limiter_tick(&isc->read_stats); cam_iosched_limiter_tick(&isc->write_stats); cam_iosched_limiter_tick(&isc->trim_stats); cam_iosched_schedule(isc, isc->periph); /* * isc->load is an EMA of the pending I/Os at each tick. The number of * pending I/Os is the sum of the I/Os queued to the hardware, and those * in the software queue that could be queued to the hardware if there * were slots. * * ios_stats.pending is a count of requests in the SIM right now for * each of these types of I/O. So the total pending count is the sum of * these I/Os and the sum of the queued I/Os still in the software queue * for those operations that aren't being rate limited at the moment. * * The reason for the rate limiting bit is because those I/Os * aren't part of the software queued load (since we could * give them to hardware, but choose not to). * * Note: due to a bug in counting pending TRIM in the device, we * don't include them in this count. We count each BIO_DELETE in * the pending count, but the periph drivers collapse them down * into one TRIM command. That one trim command gets the completion * so the counts get off. */ pending = isc->read_stats.pending + isc->write_stats.pending /* + isc->trim_stats.pending */; pending += !!(isc->read_stats.state_flags & IOP_RATE_LIMITED) * isc->read_stats.queued + !!(isc->write_stats.state_flags & IOP_RATE_LIMITED) * isc->write_stats.queued /* + !!(isc->trim_stats.state_flags & IOP_RATE_LIMITED) * isc->trim_stats.queued */ ; pending <<= 16; pending /= isc->periph->path->device->ccbq.total_openings; isc->load = (pending + (isc->load << 13) - isc->load) >> 13; /* see above: 13 -> 16139 / 200/s = ~81s ~1 minute */ isc->total_ticks++; } static void cam_iosched_cl_init(struct control_loop *clp, struct cam_iosched_softc *isc) { clp->next_steer = sbinuptime(); clp->softc = isc; clp->steer_interval = SBT_1S * 5; /* Let's start out steering every 5s */ clp->lolat = 5 * SBT_1MS; clp->hilat = 15 * SBT_1MS; clp->alpha = 20; /* Alpha == gain. 20 = .2 */ clp->type = set_max; } static void cam_iosched_cl_maybe_steer(struct control_loop *clp) { struct cam_iosched_softc *isc; sbintime_t now, lat; int old; isc = clp->softc; now = isc->last_time; if (now < clp->next_steer) return; clp->next_steer = now + clp->steer_interval; switch (clp->type) { case set_max: if (isc->write_stats.current != isc->write_stats.max) printf("Steering write from %d kBps to %d kBps\n", isc->write_stats.current, isc->write_stats.max); isc->read_stats.current = isc->read_stats.max; isc->write_stats.current = isc->write_stats.max; isc->trim_stats.current = isc->trim_stats.max; break; case read_latency: old = isc->write_stats.current; lat = isc->read_stats.ema; /* * Simple PLL-like engine. Since we're steering to a range for * the SP (set point) that makes things a little more * complicated. In addition, we're not directly controlling our * PV (process variable), the read latency, but instead are * manipulating the write bandwidth limit for our MV * (manipulation variable), analysis of this code gets a bit * messy. Also, the MV is a very noisy control surface for read * latency since it is affected by many hidden processes inside * the device which change how responsive read latency will be * in reaction to changes in write bandwidth. Unlike the classic * boiler control PLL. this may result in over-steering while * the SSD takes its time to react to the new, lower load. This * is why we use a relatively low alpha of between .1 and .25 to * compensate for this effect. At .1, it takes ~22 steering * intervals to back off by a factor of 10. At .2 it only takes * ~10. At .25 it only takes ~8. However some preliminary data * from the SSD drives suggests a reasponse time in 10's of * seconds before latency drops regardless of the new write * rate. Careful observation will be required to tune this * effectively. * * Also, when there's no read traffic, we jack up the write * limit too regardless of the last read latency. 10 is * somewhat arbitrary. */ if (lat < clp->lolat || isc->read_stats.total - clp->last_count < 10) isc->write_stats.current = isc->write_stats.current * (100 + clp->alpha) / 100; /* Scale up */ else if (lat > clp->hilat) isc->write_stats.current = isc->write_stats.current * (100 - clp->alpha) / 100; /* Scale down */ clp->last_count = isc->read_stats.total; /* * Even if we don't steer, per se, enforce the min/max limits as * those may have changed. */ if (isc->write_stats.current < isc->write_stats.min) isc->write_stats.current = isc->write_stats.min; if (isc->write_stats.current > isc->write_stats.max) isc->write_stats.current = isc->write_stats.max; if (old != isc->write_stats.current && iosched_debug) printf("Steering write from %d kBps to %d kBps due to latency of %jdus\n", old, isc->write_stats.current, (uintmax_t)((uint64_t)1000000 * (uint32_t)lat) >> 32); break; case cl_max: break; } } #endif /* * Trim or similar currently pending completion. Should only be set for * those drivers wishing only one Trim active at a time. */ #define CAM_IOSCHED_FLAG_TRIM_ACTIVE (1ul << 0) /* Callout active, and needs to be torn down */ #define CAM_IOSCHED_FLAG_CALLOUT_ACTIVE (1ul << 1) /* Periph drivers set these flags to indicate work */ #define CAM_IOSCHED_FLAG_WORK_FLAGS ((0xffffu) << 16) #ifdef CAM_IOSCHED_DYNAMIC static void cam_iosched_io_metric_update(struct cam_iosched_softc *isc, sbintime_t sim_latency, int cmd, size_t size); #endif static inline int cam_iosched_has_flagged_work(struct cam_iosched_softc *isc) { return !!(isc->flags & CAM_IOSCHED_FLAG_WORK_FLAGS); } static inline int cam_iosched_has_io(struct cam_iosched_softc *isc) { #ifdef CAM_IOSCHED_DYNAMIC if (do_dynamic_iosched) { struct bio *rbp = bioq_first(&isc->bio_queue); struct bio *wbp = bioq_first(&isc->write_queue); int can_write = wbp != NULL && cam_iosched_limiter_caniop(&isc->write_stats, wbp) == 0; int can_read = rbp != NULL && cam_iosched_limiter_caniop(&isc->read_stats, rbp) == 0; if (iosched_debug > 2) { printf("can write %d: pending_writes %d max_writes %d\n", can_write, isc->write_stats.pending, isc->write_stats.max); printf("can read %d: read_stats.pending %d max_reads %d\n", can_read, isc->read_stats.pending, isc->read_stats.max); printf("Queued reads %d writes %d\n", isc->read_stats.queued, isc->write_stats.queued); } return can_read || can_write; } #endif return bioq_first(&isc->bio_queue) != NULL; } static inline int cam_iosched_has_more_trim(struct cam_iosched_softc *isc) { return !(isc->flags & CAM_IOSCHED_FLAG_TRIM_ACTIVE) && bioq_first(&isc->trim_queue); } #define cam_iosched_sort_queue(isc) ((isc)->sort_io_queue >= 0 ? \ (isc)->sort_io_queue : cam_sort_io_queues) static inline int cam_iosched_has_work(struct cam_iosched_softc *isc) { #ifdef CAM_IOSCHED_DYNAMIC if (iosched_debug > 2) printf("has work: %d %d %d\n", cam_iosched_has_io(isc), cam_iosched_has_more_trim(isc), cam_iosched_has_flagged_work(isc)); #endif return cam_iosched_has_io(isc) || cam_iosched_has_more_trim(isc) || cam_iosched_has_flagged_work(isc); } #ifdef CAM_IOSCHED_DYNAMIC static void cam_iosched_iop_stats_init(struct cam_iosched_softc *isc, struct iop_stats *ios) { ios->limiter = none; ios->in = 0; ios->max = ios->current = 300000; ios->min = 1; ios->out = 0; ios->pending = 0; ios->queued = 0; ios->total = 0; ios->ema = 0; ios->emvar = 0; ios->softc = isc; cam_iosched_limiter_init(ios); } static int cam_iosched_limiter_sysctl(SYSCTL_HANDLER_ARGS) { char buf[16]; struct iop_stats *ios; struct cam_iosched_softc *isc; int value, i, error; const char *p; ios = arg1; isc = ios->softc; value = ios->limiter; if (value < none || value >= limiter_max) p = "UNKNOWN"; else p = cam_iosched_limiter_names[value]; strlcpy(buf, p, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return error; cam_periph_lock(isc->periph); for (i = none; i < limiter_max; i++) { if (strcmp(buf, cam_iosched_limiter_names[i]) != 0) continue; ios->limiter = i; error = cam_iosched_limiter_init(ios); if (error != 0) { ios->limiter = value; cam_periph_unlock(isc->periph); return error; } /* Note: disk load averate requires ticker to be always running */ callout_reset(&isc->ticker, hz / isc->quanta, cam_iosched_ticker, isc); isc->flags |= CAM_IOSCHED_FLAG_CALLOUT_ACTIVE; cam_periph_unlock(isc->periph); return 0; } cam_periph_unlock(isc->periph); return EINVAL; } static int cam_iosched_control_type_sysctl(SYSCTL_HANDLER_ARGS) { char buf[16]; struct control_loop *clp; struct cam_iosched_softc *isc; int value, i, error; const char *p; clp = arg1; isc = clp->softc; value = clp->type; if (value < none || value >= cl_max) p = "UNKNOWN"; else p = cam_iosched_control_type_names[value]; strlcpy(buf, p, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return error; for (i = set_max; i < cl_max; i++) { if (strcmp(buf, cam_iosched_control_type_names[i]) != 0) continue; cam_periph_lock(isc->periph); clp->type = i; cam_periph_unlock(isc->periph); return 0; } return EINVAL; } static int cam_iosched_sbintime_sysctl(SYSCTL_HANDLER_ARGS) { char buf[16]; sbintime_t value; int error; uint64_t us; value = *(sbintime_t *)arg1; us = (uint64_t)value / SBT_1US; snprintf(buf, sizeof(buf), "%ju", (intmax_t)us); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return error; us = strtoul(buf, NULL, 10); if (us == 0) return EINVAL; *(sbintime_t *)arg1 = us * SBT_1US; return 0; } static int cam_iosched_sysctl_latencies(SYSCTL_HANDLER_ARGS) { int i, error; struct sbuf sb; uint64_t *latencies; latencies = arg1; sbuf_new_for_sysctl(&sb, NULL, LAT_BUCKETS * 16, req); for (i = 0; i < LAT_BUCKETS - 1; i++) sbuf_printf(&sb, "%jd,", (intmax_t)latencies[i]); sbuf_printf(&sb, "%jd", (intmax_t)latencies[LAT_BUCKETS - 1]); error = sbuf_finish(&sb); sbuf_delete(&sb); return (error); } static int cam_iosched_quanta_sysctl(SYSCTL_HANDLER_ARGS) { int *quanta; int error, value; quanta = (unsigned *)arg1; value = *quanta; error = sysctl_handle_int(oidp, (int *)&value, 0, req); if ((error != 0) || (req->newptr == NULL)) return (error); if (value < 1 || value > hz) return (EINVAL); *quanta = value; return (0); } static void cam_iosched_iop_stats_sysctl_init(struct cam_iosched_softc *isc, struct iop_stats *ios, char *name) { struct sysctl_oid_list *n; struct sysctl_ctx_list *ctx; ios->sysctl_tree = SYSCTL_ADD_NODE(&isc->sysctl_ctx, SYSCTL_CHILDREN(isc->sysctl_tree), OID_AUTO, name, CTLFLAG_RD, 0, name); n = SYSCTL_CHILDREN(ios->sysctl_tree); ctx = &ios->sysctl_ctx; SYSCTL_ADD_UQUAD(ctx, n, OID_AUTO, "ema", CTLFLAG_RD, &ios->ema, "Fast Exponentially Weighted Moving Average"); SYSCTL_ADD_UQUAD(ctx, n, OID_AUTO, "emvar", CTLFLAG_RD, &ios->emvar, "Fast Exponentially Weighted Moving Variance"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "pending", CTLFLAG_RD, &ios->pending, 0, "Instantaneous # of pending transactions"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "count", CTLFLAG_RD, &ios->total, 0, "# of transactions submitted to hardware"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "queued", CTLFLAG_RD, &ios->queued, 0, "# of transactions in the queue"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "in", CTLFLAG_RD, &ios->in, 0, "# of transactions queued to driver"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "out", CTLFLAG_RD, &ios->out, 0, "# of transactions completed"); SYSCTL_ADD_PROC(ctx, n, OID_AUTO, "limiter", CTLTYPE_STRING | CTLFLAG_RW, ios, 0, cam_iosched_limiter_sysctl, "A", "Current limiting type."); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "min", CTLFLAG_RW, &ios->min, 0, "min resource"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "max", CTLFLAG_RW, &ios->max, 0, "max resource"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "current", CTLFLAG_RW, &ios->current, 0, "current resource"); SYSCTL_ADD_PROC(ctx, n, OID_AUTO, "latencies", CTLTYPE_STRING | CTLFLAG_RD, &ios->latencies, 0, cam_iosched_sysctl_latencies, "A", "Array of power of 2 latency from 1ms to 1.024s"); } static void cam_iosched_iop_stats_fini(struct iop_stats *ios) { if (ios->sysctl_tree) if (sysctl_ctx_free(&ios->sysctl_ctx) != 0) printf("can't remove iosched sysctl stats context\n"); } static void cam_iosched_cl_sysctl_init(struct cam_iosched_softc *isc) { struct sysctl_oid_list *n; struct sysctl_ctx_list *ctx; struct control_loop *clp; clp = &isc->cl; clp->sysctl_tree = SYSCTL_ADD_NODE(&isc->sysctl_ctx, SYSCTL_CHILDREN(isc->sysctl_tree), OID_AUTO, "control", CTLFLAG_RD, 0, "Control loop info"); n = SYSCTL_CHILDREN(clp->sysctl_tree); ctx = &clp->sysctl_ctx; SYSCTL_ADD_PROC(ctx, n, OID_AUTO, "type", CTLTYPE_STRING | CTLFLAG_RW, clp, 0, cam_iosched_control_type_sysctl, "A", "Control loop algorithm"); SYSCTL_ADD_PROC(ctx, n, OID_AUTO, "steer_interval", CTLTYPE_STRING | CTLFLAG_RW, &clp->steer_interval, 0, cam_iosched_sbintime_sysctl, "A", "How often to steer (in us)"); SYSCTL_ADD_PROC(ctx, n, OID_AUTO, "lolat", CTLTYPE_STRING | CTLFLAG_RW, &clp->lolat, 0, cam_iosched_sbintime_sysctl, "A", "Low water mark for Latency (in us)"); SYSCTL_ADD_PROC(ctx, n, OID_AUTO, "hilat", CTLTYPE_STRING | CTLFLAG_RW, &clp->hilat, 0, cam_iosched_sbintime_sysctl, "A", "Hi water mark for Latency (in us)"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "alpha", CTLFLAG_RW, &clp->alpha, 0, "Alpha for PLL (x100) aka gain"); } static void cam_iosched_cl_sysctl_fini(struct control_loop *clp) { if (clp->sysctl_tree) if (sysctl_ctx_free(&clp->sysctl_ctx) != 0) printf("can't remove iosched sysctl control loop context\n"); } #endif /* * Allocate the iosched structure. This also insulates callers from knowing * sizeof struct cam_iosched_softc. */ int cam_iosched_init(struct cam_iosched_softc **iscp, struct cam_periph *periph) { *iscp = malloc(sizeof(**iscp), M_CAMSCHED, M_NOWAIT | M_ZERO); if (*iscp == NULL) return ENOMEM; #ifdef CAM_IOSCHED_DYNAMIC if (iosched_debug) printf("CAM IOSCHEDULER Allocating entry at %p\n", *iscp); #endif (*iscp)->sort_io_queue = -1; bioq_init(&(*iscp)->bio_queue); bioq_init(&(*iscp)->trim_queue); #ifdef CAM_IOSCHED_DYNAMIC if (do_dynamic_iosched) { bioq_init(&(*iscp)->write_queue); (*iscp)->read_bias = 100; (*iscp)->current_read_bias = 100; (*iscp)->quanta = min(hz, 200); cam_iosched_iop_stats_init(*iscp, &(*iscp)->read_stats); cam_iosched_iop_stats_init(*iscp, &(*iscp)->write_stats); cam_iosched_iop_stats_init(*iscp, &(*iscp)->trim_stats); (*iscp)->trim_stats.max = 1; /* Trims are special: one at a time for now */ (*iscp)->last_time = sbinuptime(); callout_init_mtx(&(*iscp)->ticker, cam_periph_mtx(periph), 0); (*iscp)->periph = periph; cam_iosched_cl_init(&(*iscp)->cl, *iscp); callout_reset(&(*iscp)->ticker, hz / (*iscp)->quanta, cam_iosched_ticker, *iscp); (*iscp)->flags |= CAM_IOSCHED_FLAG_CALLOUT_ACTIVE; } #endif return 0; } /* * Reclaim all used resources. This assumes that other folks have * drained the requests in the hardware. Maybe an unwise assumption. */ void cam_iosched_fini(struct cam_iosched_softc *isc) { if (isc) { cam_iosched_flush(isc, NULL, ENXIO); #ifdef CAM_IOSCHED_DYNAMIC cam_iosched_iop_stats_fini(&isc->read_stats); cam_iosched_iop_stats_fini(&isc->write_stats); cam_iosched_iop_stats_fini(&isc->trim_stats); cam_iosched_cl_sysctl_fini(&isc->cl); if (isc->sysctl_tree) if (sysctl_ctx_free(&isc->sysctl_ctx) != 0) printf("can't remove iosched sysctl stats context\n"); if (isc->flags & CAM_IOSCHED_FLAG_CALLOUT_ACTIVE) { callout_drain(&isc->ticker); isc->flags &= ~ CAM_IOSCHED_FLAG_CALLOUT_ACTIVE; } #endif free(isc, M_CAMSCHED); } } /* * After we're sure we're attaching a device, go ahead and add * hooks for any sysctl we may wish to honor. */ void cam_iosched_sysctl_init(struct cam_iosched_softc *isc, struct sysctl_ctx_list *ctx, struct sysctl_oid *node) { #ifdef CAM_IOSCHED_DYNAMIC struct sysctl_oid_list *n; #endif SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "sort_io_queue", CTLFLAG_RW | CTLFLAG_MPSAFE, &isc->sort_io_queue, 0, "Sort IO queue to try and optimise disk access patterns"); #ifdef CAM_IOSCHED_DYNAMIC if (!do_dynamic_iosched) return; isc->sysctl_tree = SYSCTL_ADD_NODE(&isc->sysctl_ctx, SYSCTL_CHILDREN(node), OID_AUTO, "iosched", CTLFLAG_RD, 0, "I/O scheduler statistics"); n = SYSCTL_CHILDREN(isc->sysctl_tree); ctx = &isc->sysctl_ctx; cam_iosched_iop_stats_sysctl_init(isc, &isc->read_stats, "read"); cam_iosched_iop_stats_sysctl_init(isc, &isc->write_stats, "write"); cam_iosched_iop_stats_sysctl_init(isc, &isc->trim_stats, "trim"); cam_iosched_cl_sysctl_init(isc); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "read_bias", CTLFLAG_RW, &isc->read_bias, 100, "How biased towards read should we be independent of limits"); SYSCTL_ADD_PROC(ctx, n, OID_AUTO, "quanta", CTLTYPE_UINT | CTLFLAG_RW, &isc->quanta, 0, cam_iosched_quanta_sysctl, "I", "How many quanta per second do we slice the I/O up into"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "total_ticks", CTLFLAG_RD, &isc->total_ticks, 0, "Total number of ticks we've done"); SYSCTL_ADD_INT(ctx, n, OID_AUTO, "load", CTLFLAG_RD, &isc->load, 0, "scaled load average / 100"); #endif } /* * Flush outstanding I/O. Consumers of this library don't know all the * queues we may keep, so this allows all I/O to be flushed in one * convenient call. */ void cam_iosched_flush(struct cam_iosched_softc *isc, struct devstat *stp, int err) { bioq_flush(&isc->bio_queue, stp, err); bioq_flush(&isc->trim_queue, stp, err); #ifdef CAM_IOSCHED_DYNAMIC if (do_dynamic_iosched) bioq_flush(&isc->write_queue, stp, err); #endif } #ifdef CAM_IOSCHED_DYNAMIC static struct bio * cam_iosched_get_write(struct cam_iosched_softc *isc) { struct bio *bp; /* * We control the write rate by controlling how many requests we send * down to the drive at any one time. Fewer requests limits the * effects of both starvation when the requests take a while and write * amplification when each request is causing more than one write to * the NAND media. Limiting the queue depth like this will also limit * the write throughput and give and reads that want to compete to * compete unfairly. */ bp = bioq_first(&isc->write_queue); if (bp == NULL) { if (iosched_debug > 3) printf("No writes present in write_queue\n"); return NULL; } /* * If pending read, prefer that based on current read bias * setting. */ if (bioq_first(&isc->bio_queue) && isc->current_read_bias) { if (iosched_debug) printf("Reads present and current_read_bias is %d queued writes %d queued reads %d\n", isc->current_read_bias, isc->write_stats.queued, isc->read_stats.queued); isc->current_read_bias--; /* We're not limiting writes, per se, just doing reads first */ return NULL; } /* * See if our current limiter allows this I/O. */ if (cam_iosched_limiter_iop(&isc->write_stats, bp) != 0) { if (iosched_debug) printf("Can't write because limiter says no.\n"); isc->write_stats.state_flags |= IOP_RATE_LIMITED; return NULL; } /* * Let's do this: We've passed all the gates and we're a go * to schedule the I/O in the SIM. */ isc->current_read_bias = isc->read_bias; bioq_remove(&isc->write_queue, bp); if (bp->bio_cmd == BIO_WRITE) { isc->write_stats.queued--; isc->write_stats.total++; isc->write_stats.pending++; } if (iosched_debug > 9) printf("HWQ : %p %#x\n", bp, bp->bio_cmd); isc->write_stats.state_flags &= ~IOP_RATE_LIMITED; return bp; } #endif /* * Put back a trim that you weren't able to actually schedule this time. */ void cam_iosched_put_back_trim(struct cam_iosched_softc *isc, struct bio *bp) { bioq_insert_head(&isc->trim_queue, bp); #ifdef CAM_IOSCHED_DYNAMIC isc->trim_stats.queued++; isc->trim_stats.total--; /* since we put it back, don't double count */ isc->trim_stats.pending--; #endif } /* * gets the next trim from the trim queue. * * Assumes we're called with the periph lock held. It removes this * trim from the queue and the device must explicitly reinsert it * should the need arise. */ struct bio * cam_iosched_next_trim(struct cam_iosched_softc *isc) { struct bio *bp; bp = bioq_first(&isc->trim_queue); if (bp == NULL) return NULL; bioq_remove(&isc->trim_queue, bp); #ifdef CAM_IOSCHED_DYNAMIC isc->trim_stats.queued--; isc->trim_stats.total++; isc->trim_stats.pending++; #endif return bp; } /* * gets an available trim from the trim queue, if there's no trim * already pending. It removes this trim from the queue and the device * must explicitly reinsert it should the need arise. * * Assumes we're called with the periph lock held. */ struct bio * cam_iosched_get_trim(struct cam_iosched_softc *isc) { if (!cam_iosched_has_more_trim(isc)) return NULL; return cam_iosched_next_trim(isc); } /* * Determine what the next bit of work to do is for the periph. The * default implementation looks to see if we have trims to do, but no * trims outstanding. If so, we do that. Otherwise we see if we have * other work. If we do, then we do that. Otherwise why were we called? */ struct bio * cam_iosched_next_bio(struct cam_iosched_softc *isc) { struct bio *bp; /* * See if we have a trim that can be scheduled. We can only send one * at a time down, so this takes that into account. * * XXX newer TRIM commands are queueable. Revisit this when we * implement them. */ if ((bp = cam_iosched_get_trim(isc)) != NULL) return bp; #ifdef CAM_IOSCHED_DYNAMIC /* * See if we have any pending writes, and room in the queue for them, * and if so, those are next. */ if (do_dynamic_iosched) { if ((bp = cam_iosched_get_write(isc)) != NULL) return bp; } #endif /* * next, see if there's other, normal I/O waiting. If so return that. */ if ((bp = bioq_first(&isc->bio_queue)) == NULL) return NULL; #ifdef CAM_IOSCHED_DYNAMIC /* * For the dynamic scheduler, bio_queue is only for reads, so enforce * the limits here. Enforce only for reads. */ if (do_dynamic_iosched) { if (bp->bio_cmd == BIO_READ && cam_iosched_limiter_iop(&isc->read_stats, bp) != 0) { isc->read_stats.state_flags |= IOP_RATE_LIMITED; return NULL; } } isc->read_stats.state_flags &= ~IOP_RATE_LIMITED; #endif bioq_remove(&isc->bio_queue, bp); #ifdef CAM_IOSCHED_DYNAMIC if (do_dynamic_iosched) { if (bp->bio_cmd == BIO_READ) { isc->read_stats.queued--; isc->read_stats.total++; isc->read_stats.pending++; } else printf("Found bio_cmd = %#x\n", bp->bio_cmd); } if (iosched_debug > 9) printf("HWQ : %p %#x\n", bp, bp->bio_cmd); #endif return bp; } /* * Driver has been given some work to do by the block layer. Tell the * scheduler about it and have it queue the work up. The scheduler module * will then return the currently most useful bit of work later, possibly * deferring work for various reasons. */ void cam_iosched_queue_work(struct cam_iosched_softc *isc, struct bio *bp) { /* * Put all trims on the trim queue sorted, since we know * that the collapsing code requires this. Otherwise put * the work on the bio queue. */ if (bp->bio_cmd == BIO_DELETE) { bioq_disksort(&isc->trim_queue, bp); #ifdef CAM_IOSCHED_DYNAMIC isc->trim_stats.in++; isc->trim_stats.queued++; #endif } #ifdef CAM_IOSCHED_DYNAMIC else if (do_dynamic_iosched && (bp->bio_cmd != BIO_READ)) { if (cam_iosched_sort_queue(isc)) bioq_disksort(&isc->write_queue, bp); else bioq_insert_tail(&isc->write_queue, bp); if (iosched_debug > 9) printf("Qw : %p %#x\n", bp, bp->bio_cmd); if (bp->bio_cmd == BIO_WRITE) { isc->write_stats.in++; isc->write_stats.queued++; } } #endif else { if (cam_iosched_sort_queue(isc)) bioq_disksort(&isc->bio_queue, bp); else bioq_insert_tail(&isc->bio_queue, bp); #ifdef CAM_IOSCHED_DYNAMIC if (iosched_debug > 9) printf("Qr : %p %#x\n", bp, bp->bio_cmd); if (bp->bio_cmd == BIO_READ) { isc->read_stats.in++; isc->read_stats.queued++; } else if (bp->bio_cmd == BIO_WRITE) { isc->write_stats.in++; isc->write_stats.queued++; } #endif } } /* * If we have work, get it scheduled. Called with the periph lock held. */ void cam_iosched_schedule(struct cam_iosched_softc *isc, struct cam_periph *periph) { if (cam_iosched_has_work(isc)) xpt_schedule(periph, CAM_PRIORITY_NORMAL); } /* * Complete a trim request. Mark that we no longer have one in flight. */ void cam_iosched_trim_done(struct cam_iosched_softc *isc) { isc->flags &= ~CAM_IOSCHED_FLAG_TRIM_ACTIVE; } /* * Complete a bio. Called before we release the ccb with xpt_release_ccb so we * might use notes in the ccb for statistics. */ int cam_iosched_bio_complete(struct cam_iosched_softc *isc, struct bio *bp, union ccb *done_ccb) { int retval = 0; #ifdef CAM_IOSCHED_DYNAMIC if (!do_dynamic_iosched) return retval; if (iosched_debug > 10) printf("done: %p %#x\n", bp, bp->bio_cmd); if (bp->bio_cmd == BIO_WRITE) { retval = cam_iosched_limiter_iodone(&isc->write_stats, bp); isc->write_stats.out++; isc->write_stats.pending--; } else if (bp->bio_cmd == BIO_READ) { retval = cam_iosched_limiter_iodone(&isc->read_stats, bp); isc->read_stats.out++; isc->read_stats.pending--; } else if (bp->bio_cmd == BIO_DELETE) { isc->trim_stats.out++; isc->trim_stats.pending--; } else if (bp->bio_cmd != BIO_FLUSH) { if (iosched_debug) printf("Completing command with bio_cmd == %#x\n", bp->bio_cmd); } if (!(bp->bio_flags & BIO_ERROR)) cam_iosched_io_metric_update(isc, cam_iosched_sbintime_t(done_ccb->ccb_h.qos.periph_data), bp->bio_cmd, bp->bio_bcount); #endif return retval; } /* * Tell the io scheduler that you've pushed a trim down into the sim. * This also tells the I/O scheduler not to push any more trims down, so * some periphs do not call it if they can cope with multiple trims in flight. */ void cam_iosched_submit_trim(struct cam_iosched_softc *isc) { isc->flags |= CAM_IOSCHED_FLAG_TRIM_ACTIVE; } /* * Change the sorting policy hint for I/O transactions for this device. */ void cam_iosched_set_sort_queue(struct cam_iosched_softc *isc, int val) { isc->sort_io_queue = val; } int cam_iosched_has_work_flags(struct cam_iosched_softc *isc, uint32_t flags) { return isc->flags & flags; } void cam_iosched_set_work_flags(struct cam_iosched_softc *isc, uint32_t flags) { isc->flags |= flags; } void cam_iosched_clr_work_flags(struct cam_iosched_softc *isc, uint32_t flags) { isc->flags &= ~flags; } #ifdef CAM_IOSCHED_DYNAMIC /* * After the method presented in Jack Crenshaw's 1998 article "Integer * Square Roots," reprinted at * http://www.embedded.com/electronics-blogs/programmer-s-toolbox/4219659/Integer-Square-Roots * and well worth the read. Briefly, we find the power of 4 that's the * largest smaller than val. We then check each smaller power of 4 to * see if val is still bigger. The right shifts at each step divide * the result by 2 which after successive application winds up * accumulating the right answer. It could also have been accumulated * using a separate root counter, but this code is smaller and faster * than that method. This method is also integer size invariant. * It returns floor(sqrt((float)val)), or the largest integer less than * or equal to the square root. */ static uint64_t isqrt64(uint64_t val) { uint64_t res = 0; uint64_t bit = 1ULL << (sizeof(uint64_t) * NBBY - 2); /* * Find the largest power of 4 smaller than val. */ while (bit > val) bit >>= 2; /* * Accumulate the answer, one bit at a time (we keep moving * them over since 2 is the square root of 4 and we test * powers of 4). We accumulate where we find the bit, but * the successive shifts land the bit in the right place * by the end. */ while (bit != 0) { if (val >= res + bit) { val -= res + bit; res = (res >> 1) + bit; } else res >>= 1; bit >>= 2; } return res; } static sbintime_t latencies[LAT_BUCKETS - 1] = { SBT_1MS << 0, SBT_1MS << 1, SBT_1MS << 2, SBT_1MS << 3, SBT_1MS << 4, SBT_1MS << 5, SBT_1MS << 6, SBT_1MS << 7, SBT_1MS << 8, SBT_1MS << 9, SBT_1MS << 10, SBT_1MS << 11, SBT_1MS << 12, SBT_1MS << 13 /* 8.192s */ }; static void cam_iosched_update(struct iop_stats *iop, sbintime_t sim_latency) { sbintime_t y, deltasq, delta; int i; /* * Keep counts for latency. We do it by power of two buckets. * This helps us spot outlier behavior obscured by averages. */ for (i = 0; i < LAT_BUCKETS - 1; i++) { if (sim_latency < latencies[i]) { iop->latencies[i]++; break; } } if (i == LAT_BUCKETS - 1) iop->latencies[i]++; /* Put all > 1024ms values into the last bucket. */ /* * Classic exponentially decaying average with a tiny alpha * (2 ^ -alpha_bits). For more info see the NIST statistical * handbook. * * ema_t = y_t * alpha + ema_t-1 * (1 - alpha) [nist] * ema_t = y_t * alpha + ema_t-1 - alpha * ema_t-1 * ema_t = alpha * y_t - alpha * ema_t-1 + ema_t-1 * alpha = 1 / (1 << alpha_bits) * sub e == ema_t-1, b == 1/alpha (== 1 << alpha_bits), d == y_t - ema_t-1 * = y_t/b - e/b + be/b * = (y_t - e + be) / b * = (e + d) / b * * Since alpha is a power of two, we can compute this w/o any mult or * division. * * Variance can also be computed. Usually, it would be expressed as follows: * diff_t = y_t - ema_t-1 * emvar_t = (1 - alpha) * (emavar_t-1 + diff_t^2 * alpha) * = emavar_t-1 - alpha * emavar_t-1 + delta_t^2 * alpha - (delta_t * alpha)^2 * sub b == 1/alpha (== 1 << alpha_bits), e == emavar_t-1, d = delta_t^2 * = e - e/b + dd/b + dd/bb * = (bbe - be + bdd + dd) / bb * = (bbe + b(dd-e) + dd) / bb (which is expanded below bb = 1<<(2*alpha_bits)) */ /* * XXX possible numeric issues * o We assume right shifted integers do the right thing, since that's * implementation defined. You can change the right shifts to / (1LL << alpha). * o alpha_bits = 9 gives ema ceiling of 23 bits of seconds for ema and 14 bits * for emvar. This puts a ceiling of 13 bits on alpha since we need a * few tens of seconds of representation. * o We mitigate alpha issues by never setting it too high. */ y = sim_latency; delta = (y - iop->ema); /* d */ iop->ema = ((iop->ema << alpha_bits) + delta) >> alpha_bits; /* * Were we to naively plow ahead at this point, we wind up with many numerical * issues making any SD > ~3ms unreliable. So, we shift right by 12. This leaves * us with microsecond level precision in the input, so the same in the * output. It means we can't overflow deltasq unless delta > 4k seconds. It * also means that emvar can be up 46 bits 40 of which are fraction, which * gives us a way to measure up to ~8s in the SD before the computation goes * unstable. Even the worst hard disk rarely has > 1s service time in the * drive. It does mean we have to shift left 12 bits after taking the * square root to compute the actual standard deviation estimate. This loss of * precision is preferable to needing int128 types to work. The above numbers * assume alpha=9. 10 or 11 are ok, but we start to run into issues at 12, * so 12 or 13 is OK for EMA, EMVAR and SD will be wrong in those cases. */ delta >>= 12; deltasq = delta * delta; /* dd */ iop->emvar = ((iop->emvar << (2 * alpha_bits)) + /* bbe */ ((deltasq - iop->emvar) << alpha_bits) + /* b(dd-e) */ deltasq) /* dd */ >> (2 * alpha_bits); /* div bb */ iop->sd = (sbintime_t)isqrt64((uint64_t)iop->emvar) << 12; } static void cam_iosched_io_metric_update(struct cam_iosched_softc *isc, sbintime_t sim_latency, int cmd, size_t size) { /* xxx Do we need to scale based on the size of the I/O ? */ switch (cmd) { case BIO_READ: cam_iosched_update(&isc->read_stats, sim_latency); break; case BIO_WRITE: cam_iosched_update(&isc->write_stats, sim_latency); break; case BIO_DELETE: cam_iosched_update(&isc->trim_stats, sim_latency); break; default: break; } } #ifdef DDB static int biolen(struct bio_queue_head *bq) { int i = 0; struct bio *bp; TAILQ_FOREACH(bp, &bq->queue, bio_queue) { i++; } return i; } /* * Show the internal state of the I/O scheduler. */ DB_SHOW_COMMAND(iosched, cam_iosched_db_show) { struct cam_iosched_softc *isc; if (!have_addr) { db_printf("Need addr\n"); return; } isc = (struct cam_iosched_softc *)addr; db_printf("pending_reads: %d\n", isc->read_stats.pending); db_printf("min_reads: %d\n", isc->read_stats.min); db_printf("max_reads: %d\n", isc->read_stats.max); db_printf("reads: %d\n", isc->read_stats.total); db_printf("in_reads: %d\n", isc->read_stats.in); db_printf("out_reads: %d\n", isc->read_stats.out); db_printf("queued_reads: %d\n", isc->read_stats.queued); db_printf("Current Q len %d\n", biolen(&isc->bio_queue)); db_printf("pending_writes: %d\n", isc->write_stats.pending); db_printf("min_writes: %d\n", isc->write_stats.min); db_printf("max_writes: %d\n", isc->write_stats.max); db_printf("writes: %d\n", isc->write_stats.total); db_printf("in_writes: %d\n", isc->write_stats.in); db_printf("out_writes: %d\n", isc->write_stats.out); db_printf("queued_writes: %d\n", isc->write_stats.queued); db_printf("Current Q len %d\n", biolen(&isc->write_queue)); db_printf("pending_trims: %d\n", isc->trim_stats.pending); db_printf("min_trims: %d\n", isc->trim_stats.min); db_printf("max_trims: %d\n", isc->trim_stats.max); db_printf("trims: %d\n", isc->trim_stats.total); db_printf("in_trims: %d\n", isc->trim_stats.in); db_printf("out_trims: %d\n", isc->trim_stats.out); db_printf("queued_trims: %d\n", isc->trim_stats.queued); db_printf("Current Q len %d\n", biolen(&isc->trim_queue)); db_printf("read_bias: %d\n", isc->read_bias); db_printf("current_read_bias: %d\n", isc->current_read_bias); db_printf("Trim active? %s\n", (isc->flags & CAM_IOSCHED_FLAG_TRIM_ACTIVE) ? "yes" : "no"); } #endif #endif Index: head/sys/cam/cam_iosched.h =================================================================== --- head/sys/cam/cam_iosched.h (revision 328069) +++ head/sys/cam/cam_iosched.h (revision 328070) @@ -1,102 +1,104 @@ /*- * CAM IO Scheduler Interface * + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2015 Netflix, Inc. * 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. * * $FreeBSD$ */ #ifndef _CAM_CAM_IOSCHED_H #define _CAM_CAM_IOSCHED_H /* No user-serviceable parts in here. */ #ifdef _KERNEL /* Forward declare all structs to keep interface thin */ struct cam_iosched_softc; struct sysctl_ctx_list; struct sysctl_oid; union ccb; struct bio; /* * For 64-bit platforms, we know that uintptr_t is the same size as sbintime_t * so we can store values in it. For 32-bit systems, however, uintptr_t is only * 32-bits, so it won't fit. For those systems, store 24 bits of fraction and 8 * bits of seconds. This allows us to measure an interval of up to ~256s, which * is ~200x what our current uses require. Provide some convenience functions to * get the time, subtract two times and convert back to sbintime_t in a safe way * that can be centralized. */ #ifdef __LP64__ #define CAM_IOSCHED_TIME_SHIFT 0 #else #define CAM_IOSCHED_TIME_SHIFT 8 #endif static inline uintptr_t cam_iosched_now(void) { /* Cast here is to avoid right shifting a signed value */ return (uintptr_t)((uint64_t)sbinuptime() >> CAM_IOSCHED_TIME_SHIFT); } static inline uintptr_t cam_iosched_delta_t(uintptr_t then) { /* Since the types are identical, wrapping works correctly */ return (cam_iosched_now() - then); } static inline sbintime_t cam_iosched_sbintime_t(uintptr_t delta) { /* Cast here is to widen the type so the left shift doesn't lose precision */ return (sbintime_t)((uint64_t)delta << CAM_IOSCHED_TIME_SHIFT); } int cam_iosched_init(struct cam_iosched_softc **, struct cam_periph *periph); void cam_iosched_fini(struct cam_iosched_softc *); void cam_iosched_sysctl_init(struct cam_iosched_softc *, struct sysctl_ctx_list *, struct sysctl_oid *); struct bio *cam_iosched_next_trim(struct cam_iosched_softc *isc); struct bio *cam_iosched_get_trim(struct cam_iosched_softc *isc); struct bio *cam_iosched_next_bio(struct cam_iosched_softc *isc); void cam_iosched_queue_work(struct cam_iosched_softc *isc, struct bio *bp); void cam_iosched_flush(struct cam_iosched_softc *isc, struct devstat *stp, int err); void cam_iosched_schedule(struct cam_iosched_softc *isc, struct cam_periph *periph); void cam_iosched_finish_trim(struct cam_iosched_softc *isc); void cam_iosched_submit_trim(struct cam_iosched_softc *isc); void cam_iosched_put_back_trim(struct cam_iosched_softc *isc, struct bio *bp); void cam_iosched_set_sort_queue(struct cam_iosched_softc *isc, int val); int cam_iosched_has_work_flags(struct cam_iosched_softc *isc, uint32_t flags); void cam_iosched_set_work_flags(struct cam_iosched_softc *isc, uint32_t flags); void cam_iosched_clr_work_flags(struct cam_iosched_softc *isc, uint32_t flags); void cam_iosched_trim_done(struct cam_iosched_softc *isc); int cam_iosched_bio_complete(struct cam_iosched_softc *isc, struct bio *bp, union ccb *done_ccb); #endif #endif Index: head/sys/cam/ctl/ctl_ha.c =================================================================== --- head/sys/cam/ctl/ctl_ha.c (revision 328069) +++ head/sys/cam/ctl/ctl_ha.c (revision 328070) @@ -1,1006 +1,1008 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2015 Alexander Motin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if (__FreeBSD_version < 1100000) struct mbufq { struct mbuf *head; struct mbuf *tail; }; static void mbufq_init(struct mbufq *q, int limit) { q->head = q->tail = NULL; } static void mbufq_drain(struct mbufq *q) { struct mbuf *m; while ((m = q->head) != NULL) { q->head = m->m_nextpkt; m_freem(m); } q->tail = NULL; } static struct mbuf * mbufq_dequeue(struct mbufq *q) { struct mbuf *m; m = q->head; if (m) { if (q->tail == m) q->tail = NULL; q->head = m->m_nextpkt; m->m_nextpkt = NULL; } return (m); } static void mbufq_enqueue(struct mbufq *q, struct mbuf *m) { m->m_nextpkt = NULL; if (q->tail) q->tail->m_nextpkt = m; else q->head = m; q->tail = m; } static u_int sbavail(struct sockbuf *sb) { return (sb->sb_cc); } #if (__FreeBSD_version < 1000000) #define mtodo(m, o) ((void *)(((m)->m_data) + (o))) #endif #endif struct ha_msg_wire { uint32_t channel; uint32_t length; }; struct ha_dt_msg_wire { ctl_ha_dt_cmd command; uint32_t size; uint8_t *local; uint8_t *remote; }; struct ha_softc { struct ctl_softc *ha_ctl_softc; ctl_evt_handler ha_handler[CTL_HA_CHAN_MAX]; char ha_peer[128]; struct sockaddr_in ha_peer_in; struct socket *ha_lso; struct socket *ha_so; struct mbufq ha_sendq; struct mbuf *ha_sending; struct mtx ha_lock; int ha_connect; int ha_listen; int ha_connected; int ha_receiving; int ha_wakeup; int ha_disconnect; int ha_shutdown; eventhandler_tag ha_shutdown_eh; TAILQ_HEAD(, ctl_ha_dt_req) ha_dts; } ha_softc; static void ctl_ha_conn_wake(struct ha_softc *softc) { mtx_lock(&softc->ha_lock); softc->ha_wakeup = 1; mtx_unlock(&softc->ha_lock); wakeup(&softc->ha_wakeup); } static int ctl_ha_lupcall(struct socket *so, void *arg, int waitflag) { struct ha_softc *softc = arg; ctl_ha_conn_wake(softc); return (SU_OK); } static int ctl_ha_rupcall(struct socket *so, void *arg, int waitflag) { struct ha_softc *softc = arg; wakeup(&softc->ha_receiving); return (SU_OK); } static int ctl_ha_supcall(struct socket *so, void *arg, int waitflag) { struct ha_softc *softc = arg; ctl_ha_conn_wake(softc); return (SU_OK); } static void ctl_ha_evt(struct ha_softc *softc, ctl_ha_channel ch, ctl_ha_event evt, int param) { int i; if (ch < CTL_HA_CHAN_MAX) { if (softc->ha_handler[ch]) softc->ha_handler[ch](ch, evt, param); return; } for (i = 0; i < CTL_HA_CHAN_MAX; i++) { if (softc->ha_handler[i]) softc->ha_handler[i](i, evt, param); } } static void ctl_ha_close(struct ha_softc *softc) { struct socket *so = softc->ha_so; int report = 0; if (softc->ha_connected || softc->ha_disconnect) { softc->ha_connected = 0; mbufq_drain(&softc->ha_sendq); m_freem(softc->ha_sending); softc->ha_sending = NULL; report = 1; } if (so) { SOCKBUF_LOCK(&so->so_rcv); soupcall_clear(so, SO_RCV); while (softc->ha_receiving) { wakeup(&softc->ha_receiving); msleep(&softc->ha_receiving, SOCKBUF_MTX(&so->so_rcv), 0, "ha_rx exit", 0); } SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_LOCK(&so->so_snd); soupcall_clear(so, SO_SND); SOCKBUF_UNLOCK(&so->so_snd); softc->ha_so = NULL; if (softc->ha_connect) pause("reconnect", hz / 2); soclose(so); } if (report) { ctl_ha_evt(softc, CTL_HA_CHAN_MAX, CTL_HA_EVT_LINK_CHANGE, (softc->ha_connect || softc->ha_listen) ? CTL_HA_LINK_UNKNOWN : CTL_HA_LINK_OFFLINE); } } static void ctl_ha_lclose(struct ha_softc *softc) { if (softc->ha_lso) { SOCKBUF_LOCK(&softc->ha_lso->so_rcv); soupcall_clear(softc->ha_lso, SO_RCV); SOCKBUF_UNLOCK(&softc->ha_lso->so_rcv); soclose(softc->ha_lso); softc->ha_lso = NULL; } } static void ctl_ha_rx_thread(void *arg) { struct ha_softc *softc = arg; struct socket *so = softc->ha_so; struct ha_msg_wire wire_hdr; struct uio uio; struct iovec iov; int error, flags, next; bzero(&wire_hdr, sizeof(wire_hdr)); while (1) { if (wire_hdr.length > 0) next = wire_hdr.length; else next = sizeof(wire_hdr); SOCKBUF_LOCK(&so->so_rcv); while (sbavail(&so->so_rcv) < next || softc->ha_disconnect) { if (softc->ha_connected == 0 || softc->ha_disconnect || so->so_error || (so->so_rcv.sb_state & SBS_CANTRCVMORE)) { goto errout; } so->so_rcv.sb_lowat = next; msleep(&softc->ha_receiving, SOCKBUF_MTX(&so->so_rcv), 0, "-", 0); } SOCKBUF_UNLOCK(&so->so_rcv); if (wire_hdr.length == 0) { iov.iov_base = &wire_hdr; iov.iov_len = sizeof(wire_hdr); uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_rw = UIO_READ; uio.uio_segflg = UIO_SYSSPACE; uio.uio_td = curthread; uio.uio_resid = sizeof(wire_hdr); flags = MSG_DONTWAIT; error = soreceive(softc->ha_so, NULL, &uio, NULL, NULL, &flags); if (error != 0) { printf("%s: header receive error %d\n", __func__, error); SOCKBUF_LOCK(&so->so_rcv); goto errout; } } else { ctl_ha_evt(softc, wire_hdr.channel, CTL_HA_EVT_MSG_RECV, wire_hdr.length); wire_hdr.length = 0; } } errout: softc->ha_receiving = 0; wakeup(&softc->ha_receiving); SOCKBUF_UNLOCK(&so->so_rcv); ctl_ha_conn_wake(softc); kthread_exit(); } static void ctl_ha_send(struct ha_softc *softc) { struct socket *so = softc->ha_so; int error; while (1) { if (softc->ha_sending == NULL) { mtx_lock(&softc->ha_lock); softc->ha_sending = mbufq_dequeue(&softc->ha_sendq); mtx_unlock(&softc->ha_lock); if (softc->ha_sending == NULL) { so->so_snd.sb_lowat = so->so_snd.sb_hiwat + 1; break; } } SOCKBUF_LOCK(&so->so_snd); if (sbspace(&so->so_snd) < softc->ha_sending->m_pkthdr.len) { so->so_snd.sb_lowat = softc->ha_sending->m_pkthdr.len; SOCKBUF_UNLOCK(&so->so_snd); break; } SOCKBUF_UNLOCK(&so->so_snd); error = sosend(softc->ha_so, NULL, NULL, softc->ha_sending, NULL, MSG_DONTWAIT, curthread); softc->ha_sending = NULL; if (error != 0) { printf("%s: sosend() error %d\n", __func__, error); return; } } } static void ctl_ha_sock_setup(struct ha_softc *softc) { struct sockopt opt; struct socket *so = softc->ha_so; int error, val; val = 1024 * 1024; error = soreserve(so, val, val); if (error) printf("%s: soreserve failed %d\n", __func__, error); SOCKBUF_LOCK(&so->so_rcv); so->so_rcv.sb_lowat = sizeof(struct ha_msg_wire); soupcall_set(so, SO_RCV, ctl_ha_rupcall, softc); SOCKBUF_UNLOCK(&so->so_rcv); SOCKBUF_LOCK(&so->so_snd); so->so_snd.sb_lowat = sizeof(struct ha_msg_wire); soupcall_set(so, SO_SND, ctl_ha_supcall, softc); SOCKBUF_UNLOCK(&so->so_snd); bzero(&opt, sizeof(struct sockopt)); opt.sopt_dir = SOPT_SET; opt.sopt_level = SOL_SOCKET; opt.sopt_name = SO_KEEPALIVE; opt.sopt_val = &val; opt.sopt_valsize = sizeof(val); val = 1; error = sosetopt(so, &opt); if (error) printf("%s: KEEPALIVE setting failed %d\n", __func__, error); opt.sopt_level = IPPROTO_TCP; opt.sopt_name = TCP_NODELAY; val = 1; error = sosetopt(so, &opt); if (error) printf("%s: NODELAY setting failed %d\n", __func__, error); opt.sopt_name = TCP_KEEPINIT; val = 3; error = sosetopt(so, &opt); if (error) printf("%s: KEEPINIT setting failed %d\n", __func__, error); opt.sopt_name = TCP_KEEPIDLE; val = 1; error = sosetopt(so, &opt); if (error) printf("%s: KEEPIDLE setting failed %d\n", __func__, error); opt.sopt_name = TCP_KEEPINTVL; val = 1; error = sosetopt(so, &opt); if (error) printf("%s: KEEPINTVL setting failed %d\n", __func__, error); opt.sopt_name = TCP_KEEPCNT; val = 5; error = sosetopt(so, &opt); if (error) printf("%s: KEEPCNT setting failed %d\n", __func__, error); } static int ctl_ha_connect(struct ha_softc *softc) { struct thread *td = curthread; struct sockaddr_in sa; struct socket *so; int error; /* Create the socket */ error = socreate(PF_INET, &so, SOCK_STREAM, IPPROTO_TCP, td->td_ucred, td); if (error != 0) { printf("%s: socreate() error %d\n", __func__, error); return (error); } softc->ha_so = so; ctl_ha_sock_setup(softc); memcpy(&sa, &softc->ha_peer_in, sizeof(sa)); error = soconnect(so, (struct sockaddr *)&sa, td); if (error != 0) { if (bootverbose) printf("%s: soconnect() error %d\n", __func__, error); goto out; } return (0); out: ctl_ha_close(softc); return (error); } static int ctl_ha_accept(struct ha_softc *softc) { struct socket *lso, *so; struct sockaddr *sap; int error; lso = softc->ha_lso; SOLISTEN_LOCK(lso); error = solisten_dequeue(lso, &so, 0); if (error == EWOULDBLOCK) return (error); if (error) { printf("%s: socket error %d\n", __func__, error); goto out; } sap = NULL; error = soaccept(so, &sap); if (error != 0) { printf("%s: soaccept() error %d\n", __func__, error); if (sap != NULL) free(sap, M_SONAME); goto out; } if (sap != NULL) free(sap, M_SONAME); softc->ha_so = so; ctl_ha_sock_setup(softc); return (0); out: ctl_ha_lclose(softc); return (error); } static int ctl_ha_listen(struct ha_softc *softc) { struct thread *td = curthread; struct sockaddr_in sa; struct sockopt opt; int error, val; /* Create the socket */ if (softc->ha_lso == NULL) { error = socreate(PF_INET, &softc->ha_lso, SOCK_STREAM, IPPROTO_TCP, td->td_ucred, td); if (error != 0) { printf("%s: socreate() error %d\n", __func__, error); return (error); } bzero(&opt, sizeof(struct sockopt)); opt.sopt_dir = SOPT_SET; opt.sopt_level = SOL_SOCKET; opt.sopt_name = SO_REUSEADDR; opt.sopt_val = &val; opt.sopt_valsize = sizeof(val); val = 1; error = sosetopt(softc->ha_lso, &opt); if (error) { printf("%s: REUSEADDR setting failed %d\n", __func__, error); } bzero(&opt, sizeof(struct sockopt)); opt.sopt_dir = SOPT_SET; opt.sopt_level = SOL_SOCKET; opt.sopt_name = SO_REUSEPORT; opt.sopt_val = &val; opt.sopt_valsize = sizeof(val); val = 1; error = sosetopt(softc->ha_lso, &opt); if (error) { printf("%s: REUSEPORT setting failed %d\n", __func__, error); } } memcpy(&sa, &softc->ha_peer_in, sizeof(sa)); error = sobind(softc->ha_lso, (struct sockaddr *)&sa, td); if (error != 0) { printf("%s: sobind() error %d\n", __func__, error); goto out; } error = solisten(softc->ha_lso, 1, td); if (error != 0) { printf("%s: solisten() error %d\n", __func__, error); goto out; } SOLISTEN_LOCK(softc->ha_lso); softc->ha_lso->so_state |= SS_NBIO; solisten_upcall_set(softc->ha_lso, ctl_ha_lupcall, softc); SOLISTEN_UNLOCK(softc->ha_lso); return (0); out: ctl_ha_lclose(softc); return (error); } static void ctl_ha_conn_thread(void *arg) { struct ha_softc *softc = arg; int error; while (1) { if (softc->ha_disconnect || softc->ha_shutdown) { ctl_ha_close(softc); if (softc->ha_disconnect == 2 || softc->ha_shutdown) ctl_ha_lclose(softc); softc->ha_disconnect = 0; if (softc->ha_shutdown) break; } else if (softc->ha_so != NULL && (softc->ha_so->so_error || softc->ha_so->so_rcv.sb_state & SBS_CANTRCVMORE)) ctl_ha_close(softc); if (softc->ha_so == NULL) { if (softc->ha_lso != NULL) ctl_ha_accept(softc); else if (softc->ha_listen) ctl_ha_listen(softc); else if (softc->ha_connect) ctl_ha_connect(softc); } if (softc->ha_so != NULL) { if (softc->ha_connected == 0 && softc->ha_so->so_error == 0 && (softc->ha_so->so_state & SS_ISCONNECTING) == 0) { softc->ha_connected = 1; ctl_ha_evt(softc, CTL_HA_CHAN_MAX, CTL_HA_EVT_LINK_CHANGE, CTL_HA_LINK_ONLINE); softc->ha_receiving = 1; error = kproc_kthread_add(ctl_ha_rx_thread, softc, &softc->ha_ctl_softc->ctl_proc, NULL, 0, 0, "ctl", "ha_rx"); if (error != 0) { printf("Error creating CTL HA rx thread!\n"); softc->ha_receiving = 0; softc->ha_disconnect = 1; } } ctl_ha_send(softc); } mtx_lock(&softc->ha_lock); if (softc->ha_so != NULL && (softc->ha_so->so_error || softc->ha_so->so_rcv.sb_state & SBS_CANTRCVMORE)) ; else if (!softc->ha_wakeup) msleep(&softc->ha_wakeup, &softc->ha_lock, 0, "-", hz); softc->ha_wakeup = 0; mtx_unlock(&softc->ha_lock); } mtx_lock(&softc->ha_lock); softc->ha_shutdown = 2; wakeup(&softc->ha_wakeup); mtx_unlock(&softc->ha_lock); kthread_exit(); } static int ctl_ha_peer_sysctl(SYSCTL_HANDLER_ARGS) { struct ha_softc *softc = (struct ha_softc *)arg1; struct sockaddr_in *sa; int error, b1, b2, b3, b4, p, num; char buf[128]; strlcpy(buf, softc->ha_peer, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if ((error != 0) || (req->newptr == NULL) || strncmp(buf, softc->ha_peer, sizeof(buf)) == 0) return (error); sa = &softc->ha_peer_in; mtx_lock(&softc->ha_lock); if ((num = sscanf(buf, "connect %d.%d.%d.%d:%d", &b1, &b2, &b3, &b4, &p)) >= 4) { softc->ha_connect = 1; softc->ha_listen = 0; } else if ((num = sscanf(buf, "listen %d.%d.%d.%d:%d", &b1, &b2, &b3, &b4, &p)) >= 4) { softc->ha_connect = 0; softc->ha_listen = 1; } else { softc->ha_connect = 0; softc->ha_listen = 0; if (buf[0] != 0) { buf[0] = 0; error = EINVAL; } } strlcpy(softc->ha_peer, buf, sizeof(softc->ha_peer)); if (softc->ha_connect || softc->ha_listen) { memset(sa, 0, sizeof(*sa)); sa->sin_len = sizeof(struct sockaddr_in); sa->sin_family = AF_INET; sa->sin_port = htons((num >= 5) ? p : 999); sa->sin_addr.s_addr = htonl((b1 << 24) + (b2 << 16) + (b3 << 8) + b4); } softc->ha_disconnect = 2; softc->ha_wakeup = 1; mtx_unlock(&softc->ha_lock); wakeup(&softc->ha_wakeup); return (error); } ctl_ha_status ctl_ha_msg_register(ctl_ha_channel channel, ctl_evt_handler handler) { struct ha_softc *softc = &ha_softc; KASSERT(channel < CTL_HA_CHAN_MAX, ("Wrong CTL HA channel %d", channel)); softc->ha_handler[channel] = handler; return (CTL_HA_STATUS_SUCCESS); } ctl_ha_status ctl_ha_msg_deregister(ctl_ha_channel channel) { struct ha_softc *softc = &ha_softc; KASSERT(channel < CTL_HA_CHAN_MAX, ("Wrong CTL HA channel %d", channel)); softc->ha_handler[channel] = NULL; return (CTL_HA_STATUS_SUCCESS); } /* * Receive a message of the specified size. */ ctl_ha_status ctl_ha_msg_recv(ctl_ha_channel channel, void *addr, size_t len, int wait) { struct ha_softc *softc = &ha_softc; struct uio uio; struct iovec iov; int error, flags; if (!softc->ha_connected) return (CTL_HA_STATUS_DISCONNECT); iov.iov_base = addr; iov.iov_len = len; uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_rw = UIO_READ; uio.uio_segflg = UIO_SYSSPACE; uio.uio_td = curthread; uio.uio_resid = len; flags = wait ? 0 : MSG_DONTWAIT; error = soreceive(softc->ha_so, NULL, &uio, NULL, NULL, &flags); if (error == 0) return (CTL_HA_STATUS_SUCCESS); /* Consider all errors fatal for HA sanity. */ mtx_lock(&softc->ha_lock); if (softc->ha_connected) { softc->ha_disconnect = 1; softc->ha_wakeup = 1; wakeup(&softc->ha_wakeup); } mtx_unlock(&softc->ha_lock); return (CTL_HA_STATUS_ERROR); } /* * Send a message of the specified size. */ ctl_ha_status ctl_ha_msg_send2(ctl_ha_channel channel, const void *addr, size_t len, const void *addr2, size_t len2, int wait) { struct ha_softc *softc = &ha_softc; struct mbuf *mb, *newmb; struct ha_msg_wire hdr; size_t copylen, off; if (!softc->ha_connected) return (CTL_HA_STATUS_DISCONNECT); newmb = m_getm2(NULL, sizeof(hdr) + len + len2, wait, MT_DATA, M_PKTHDR); if (newmb == NULL) { /* Consider all errors fatal for HA sanity. */ mtx_lock(&softc->ha_lock); if (softc->ha_connected) { softc->ha_disconnect = 1; softc->ha_wakeup = 1; wakeup(&softc->ha_wakeup); } mtx_unlock(&softc->ha_lock); printf("%s: Can't allocate mbuf chain\n", __func__); return (CTL_HA_STATUS_ERROR); } hdr.channel = channel; hdr.length = len + len2; mb = newmb; memcpy(mtodo(mb, 0), &hdr, sizeof(hdr)); mb->m_len += sizeof(hdr); off = 0; for (; mb != NULL && off < len; mb = mb->m_next) { copylen = min(M_TRAILINGSPACE(mb), len - off); memcpy(mtodo(mb, mb->m_len), (const char *)addr + off, copylen); mb->m_len += copylen; off += copylen; if (off == len) break; } KASSERT(off == len, ("%s: off (%zu) != len (%zu)", __func__, off, len)); off = 0; for (; mb != NULL && off < len2; mb = mb->m_next) { copylen = min(M_TRAILINGSPACE(mb), len2 - off); memcpy(mtodo(mb, mb->m_len), (const char *)addr2 + off, copylen); mb->m_len += copylen; off += copylen; } KASSERT(off == len2, ("%s: off (%zu) != len2 (%zu)", __func__, off, len2)); newmb->m_pkthdr.len = sizeof(hdr) + len + len2; mtx_lock(&softc->ha_lock); if (!softc->ha_connected) { mtx_unlock(&softc->ha_lock); m_freem(newmb); return (CTL_HA_STATUS_DISCONNECT); } mbufq_enqueue(&softc->ha_sendq, newmb); softc->ha_wakeup = 1; mtx_unlock(&softc->ha_lock); wakeup(&softc->ha_wakeup); return (CTL_HA_STATUS_SUCCESS); } ctl_ha_status ctl_ha_msg_send(ctl_ha_channel channel, const void *addr, size_t len, int wait) { return (ctl_ha_msg_send2(channel, addr, len, NULL, 0, wait)); } ctl_ha_status ctl_ha_msg_abort(ctl_ha_channel channel) { struct ha_softc *softc = &ha_softc; mtx_lock(&softc->ha_lock); softc->ha_disconnect = 1; softc->ha_wakeup = 1; mtx_unlock(&softc->ha_lock); wakeup(&softc->ha_wakeup); return (CTL_HA_STATUS_SUCCESS); } /* * Allocate a data transfer request structure. */ struct ctl_ha_dt_req * ctl_dt_req_alloc(void) { return (malloc(sizeof(struct ctl_ha_dt_req), M_CTL, M_WAITOK | M_ZERO)); } /* * Free a data transfer request structure. */ void ctl_dt_req_free(struct ctl_ha_dt_req *req) { free(req, M_CTL); } /* * Issue a DMA request for a single buffer. */ ctl_ha_status ctl_dt_single(struct ctl_ha_dt_req *req) { struct ha_softc *softc = &ha_softc; struct ha_dt_msg_wire wire_dt; ctl_ha_status status; wire_dt.command = req->command; wire_dt.size = req->size; wire_dt.local = req->local; wire_dt.remote = req->remote; if (req->command == CTL_HA_DT_CMD_READ && req->callback != NULL) { mtx_lock(&softc->ha_lock); TAILQ_INSERT_TAIL(&softc->ha_dts, req, links); mtx_unlock(&softc->ha_lock); ctl_ha_msg_send(CTL_HA_CHAN_DATA, &wire_dt, sizeof(wire_dt), M_WAITOK); return (CTL_HA_STATUS_WAIT); } if (req->command == CTL_HA_DT_CMD_READ) { status = ctl_ha_msg_send(CTL_HA_CHAN_DATA, &wire_dt, sizeof(wire_dt), M_WAITOK); } else { status = ctl_ha_msg_send2(CTL_HA_CHAN_DATA, &wire_dt, sizeof(wire_dt), req->local, req->size, M_WAITOK); } return (status); } static void ctl_dt_event_handler(ctl_ha_channel channel, ctl_ha_event event, int param) { struct ha_softc *softc = &ha_softc; struct ctl_ha_dt_req *req; ctl_ha_status isc_status; if (event == CTL_HA_EVT_MSG_RECV) { struct ha_dt_msg_wire wire_dt; uint8_t *tmp; int size; size = min(sizeof(wire_dt), param); isc_status = ctl_ha_msg_recv(CTL_HA_CHAN_DATA, &wire_dt, size, M_WAITOK); if (isc_status != CTL_HA_STATUS_SUCCESS) { printf("%s: Error receiving message: %d\n", __func__, isc_status); return; } if (wire_dt.command == CTL_HA_DT_CMD_READ) { wire_dt.command = CTL_HA_DT_CMD_WRITE; tmp = wire_dt.local; wire_dt.local = wire_dt.remote; wire_dt.remote = tmp; ctl_ha_msg_send2(CTL_HA_CHAN_DATA, &wire_dt, sizeof(wire_dt), wire_dt.local, wire_dt.size, M_WAITOK); } else if (wire_dt.command == CTL_HA_DT_CMD_WRITE) { isc_status = ctl_ha_msg_recv(CTL_HA_CHAN_DATA, wire_dt.remote, wire_dt.size, M_WAITOK); mtx_lock(&softc->ha_lock); TAILQ_FOREACH(req, &softc->ha_dts, links) { if (req->local == wire_dt.remote) { TAILQ_REMOVE(&softc->ha_dts, req, links); break; } } mtx_unlock(&softc->ha_lock); if (req) { req->ret = isc_status; req->callback(req); } } } else if (event == CTL_HA_EVT_LINK_CHANGE) { CTL_DEBUG_PRINT(("%s: Link state change to %d\n", __func__, param)); if (param != CTL_HA_LINK_ONLINE) { mtx_lock(&softc->ha_lock); while ((req = TAILQ_FIRST(&softc->ha_dts)) != NULL) { TAILQ_REMOVE(&softc->ha_dts, req, links); mtx_unlock(&softc->ha_lock); req->ret = CTL_HA_STATUS_DISCONNECT; req->callback(req); mtx_lock(&softc->ha_lock); } mtx_unlock(&softc->ha_lock); } } else { printf("%s: Unknown event %d\n", __func__, event); } } ctl_ha_status ctl_ha_msg_init(struct ctl_softc *ctl_softc) { struct ha_softc *softc = &ha_softc; int error; softc->ha_ctl_softc = ctl_softc; mtx_init(&softc->ha_lock, "CTL HA mutex", NULL, MTX_DEF); mbufq_init(&softc->ha_sendq, INT_MAX); TAILQ_INIT(&softc->ha_dts); error = kproc_kthread_add(ctl_ha_conn_thread, softc, &ctl_softc->ctl_proc, NULL, 0, 0, "ctl", "ha_tx"); if (error != 0) { printf("error creating CTL HA connection thread!\n"); mtx_destroy(&softc->ha_lock); return (CTL_HA_STATUS_ERROR); } softc->ha_shutdown_eh = EVENTHANDLER_REGISTER(shutdown_pre_sync, ctl_ha_msg_shutdown, ctl_softc, SHUTDOWN_PRI_FIRST); SYSCTL_ADD_PROC(&ctl_softc->sysctl_ctx, SYSCTL_CHILDREN(ctl_softc->sysctl_tree), OID_AUTO, "ha_peer", CTLTYPE_STRING | CTLFLAG_RWTUN, softc, 0, ctl_ha_peer_sysctl, "A", "HA peer connection method"); if (ctl_ha_msg_register(CTL_HA_CHAN_DATA, ctl_dt_event_handler) != CTL_HA_STATUS_SUCCESS) { printf("%s: ctl_ha_msg_register failed.\n", __func__); } return (CTL_HA_STATUS_SUCCESS); }; void ctl_ha_msg_shutdown(struct ctl_softc *ctl_softc) { struct ha_softc *softc = &ha_softc; /* Disconnect and shutdown threads. */ mtx_lock(&softc->ha_lock); if (softc->ha_shutdown < 2) { softc->ha_shutdown = 1; softc->ha_wakeup = 1; wakeup(&softc->ha_wakeup); while (softc->ha_shutdown < 2 && !SCHEDULER_STOPPED()) { msleep(&softc->ha_wakeup, &softc->ha_lock, 0, "shutdown", hz); } } mtx_unlock(&softc->ha_lock); }; ctl_ha_status ctl_ha_msg_destroy(struct ctl_softc *ctl_softc) { struct ha_softc *softc = &ha_softc; if (softc->ha_shutdown_eh != NULL) { EVENTHANDLER_DEREGISTER(shutdown_pre_sync, softc->ha_shutdown_eh); softc->ha_shutdown_eh = NULL; } ctl_ha_msg_shutdown(ctl_softc); /* Just in case. */ if (ctl_ha_msg_deregister(CTL_HA_CHAN_DATA) != CTL_HA_STATUS_SUCCESS) printf("%s: ctl_ha_msg_deregister failed.\n", __func__); mtx_destroy(&softc->ha_lock); return (CTL_HA_STATUS_SUCCESS); }; Index: head/sys/cam/ctl/ctl_tpc.c =================================================================== --- head/sys/cam/ctl/ctl_tpc.c (revision 328069) +++ head/sys/cam/ctl/ctl_tpc.c (revision 328070) @@ -1,2471 +1,2473 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2014 Alexander Motin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TPC_MAX_CSCDS 64 #define TPC_MAX_SEGS 64 #define TPC_MAX_SEG 0 #define TPC_MAX_LIST 8192 #define TPC_MAX_INLINE 0 #define TPC_MAX_LISTS 255 #define TPC_MAX_IO_SIZE (1024 * 1024) #define TPC_MAX_IOCHUNK_SIZE (TPC_MAX_IO_SIZE * 16) #define TPC_MIN_TOKEN_TIMEOUT 1 #define TPC_DFL_TOKEN_TIMEOUT 60 #define TPC_MAX_TOKEN_TIMEOUT 600 MALLOC_DEFINE(M_CTL_TPC, "ctltpc", "CTL TPC"); typedef enum { TPC_ERR_RETRY = 0x000, TPC_ERR_FAIL = 0x001, TPC_ERR_MASK = 0x0ff, TPC_ERR_NO_DECREMENT = 0x100 } tpc_error_action; struct tpc_list; TAILQ_HEAD(runl, tpc_io); struct tpc_io { union ctl_io *io; uint8_t target; uint32_t cscd; uint64_t lun; uint8_t *buf; struct tpc_list *list; struct runl run; TAILQ_ENTRY(tpc_io) rlinks; TAILQ_ENTRY(tpc_io) links; }; struct tpc_token { uint8_t token[512]; uint64_t lun; uint32_t blocksize; uint8_t *params; struct scsi_range_desc *range; int nrange; int active; time_t last_active; uint32_t timeout; TAILQ_ENTRY(tpc_token) links; }; struct tpc_list { uint8_t service_action; int init_port; uint32_t init_idx; uint32_t list_id; uint8_t flags; uint8_t *params; struct scsi_ec_cscd *cscd; struct scsi_ec_segment *seg[TPC_MAX_SEGS]; uint8_t *inl; int ncscd; int nseg; int leninl; struct tpc_token *token; struct scsi_range_desc *range; int nrange; off_t offset_into_rod; int curseg; off_t cursectors; off_t curbytes; int curops; int stage; off_t segsectors; off_t segbytes; int tbdio; int error; int abort; int completed; time_t last_active; TAILQ_HEAD(, tpc_io) allio; struct scsi_sense_data fwd_sense_data; uint8_t fwd_sense_len; uint8_t fwd_scsi_status; uint8_t fwd_target; uint16_t fwd_cscd; struct scsi_sense_data sense_data; uint8_t sense_len; uint8_t scsi_status; struct ctl_scsiio *ctsio; struct ctl_lun *lun; int res_token_valid; uint8_t res_token[512]; TAILQ_ENTRY(tpc_list) links; }; static void tpc_timeout(void *arg) { struct ctl_softc *softc = arg; struct ctl_lun *lun; struct tpc_token *token, *ttoken; struct tpc_list *list, *tlist; /* Free completed lists with expired timeout. */ STAILQ_FOREACH(lun, &softc->lun_list, links) { mtx_lock(&lun->lun_lock); TAILQ_FOREACH_SAFE(list, &lun->tpc_lists, links, tlist) { if (!list->completed || time_uptime < list->last_active + TPC_DFL_TOKEN_TIMEOUT) continue; TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } mtx_unlock(&lun->lun_lock); } /* Free inactive ROD tokens with expired timeout. */ mtx_lock(&softc->tpc_lock); TAILQ_FOREACH_SAFE(token, &softc->tpc_tokens, links, ttoken) { if (token->active || time_uptime < token->last_active + token->timeout + 1) continue; TAILQ_REMOVE(&softc->tpc_tokens, token, links); free(token->params, M_CTL); free(token, M_CTL); } mtx_unlock(&softc->tpc_lock); callout_schedule(&softc->tpc_timeout, hz); } void ctl_tpc_init(struct ctl_softc *softc) { mtx_init(&softc->tpc_lock, "CTL TPC mutex", NULL, MTX_DEF); TAILQ_INIT(&softc->tpc_tokens); callout_init_mtx(&softc->tpc_timeout, &softc->ctl_lock, 0); callout_reset(&softc->tpc_timeout, hz, tpc_timeout, softc); } void ctl_tpc_shutdown(struct ctl_softc *softc) { struct tpc_token *token; callout_drain(&softc->tpc_timeout); /* Free ROD tokens. */ mtx_lock(&softc->tpc_lock); while ((token = TAILQ_FIRST(&softc->tpc_tokens)) != NULL) { TAILQ_REMOVE(&softc->tpc_tokens, token, links); free(token->params, M_CTL); free(token, M_CTL); } mtx_unlock(&softc->tpc_lock); mtx_destroy(&softc->tpc_lock); } void ctl_tpc_lun_init(struct ctl_lun *lun) { TAILQ_INIT(&lun->tpc_lists); } void ctl_tpc_lun_clear(struct ctl_lun *lun, uint32_t initidx) { struct tpc_list *list, *tlist; TAILQ_FOREACH_SAFE(list, &lun->tpc_lists, links, tlist) { if (initidx != -1 && list->init_idx != initidx) continue; if (!list->completed) continue; TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } } void ctl_tpc_lun_shutdown(struct ctl_lun *lun) { struct ctl_softc *softc = lun->ctl_softc; struct tpc_list *list; struct tpc_token *token, *ttoken; /* Free lists for this LUN. */ while ((list = TAILQ_FIRST(&lun->tpc_lists)) != NULL) { TAILQ_REMOVE(&lun->tpc_lists, list, links); KASSERT(list->completed, ("Not completed TPC (%p) on shutdown", list)); free(list, M_CTL); } /* Free ROD tokens for this LUN. */ mtx_lock(&softc->tpc_lock); TAILQ_FOREACH_SAFE(token, &softc->tpc_tokens, links, ttoken) { if (token->lun != lun->lun || token->active) continue; TAILQ_REMOVE(&softc->tpc_tokens, token, links); free(token->params, M_CTL); free(token, M_CTL); } mtx_unlock(&softc->tpc_lock); } int ctl_inquiry_evpd_tpc(struct ctl_scsiio *ctsio, int alloc_len) { struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_vpd_tpc *tpc_ptr; struct scsi_vpd_tpc_descriptor *d_ptr; struct scsi_vpd_tpc_descriptor_bdrl *bdrl_ptr; struct scsi_vpd_tpc_descriptor_sc *sc_ptr; struct scsi_vpd_tpc_descriptor_sc_descr *scd_ptr; struct scsi_vpd_tpc_descriptor_pd *pd_ptr; struct scsi_vpd_tpc_descriptor_sd *sd_ptr; struct scsi_vpd_tpc_descriptor_sdid *sdid_ptr; struct scsi_vpd_tpc_descriptor_rtf *rtf_ptr; struct scsi_vpd_tpc_descriptor_rtf_block *rtfb_ptr; struct scsi_vpd_tpc_descriptor_srt *srt_ptr; struct scsi_vpd_tpc_descriptor_srtd *srtd_ptr; struct scsi_vpd_tpc_descriptor_gco *gco_ptr; int data_len; data_len = sizeof(struct scsi_vpd_tpc) + sizeof(struct scsi_vpd_tpc_descriptor_bdrl) + roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sc) + 2 * sizeof(struct scsi_vpd_tpc_descriptor_sc_descr) + 11, 4) + sizeof(struct scsi_vpd_tpc_descriptor_pd) + roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sd) + 4, 4) + roundup2(sizeof(struct scsi_vpd_tpc_descriptor_sdid) + 2, 4) + sizeof(struct scsi_vpd_tpc_descriptor_rtf) + sizeof(struct scsi_vpd_tpc_descriptor_rtf_block) + sizeof(struct scsi_vpd_tpc_descriptor_srt) + 2*sizeof(struct scsi_vpd_tpc_descriptor_srtd) + sizeof(struct scsi_vpd_tpc_descriptor_gco); ctsio->kern_data_ptr = malloc(data_len, M_CTL, M_WAITOK | M_ZERO); tpc_ptr = (struct scsi_vpd_tpc *)ctsio->kern_data_ptr; ctsio->kern_rel_offset = 0; ctsio->kern_sg_entries = 0; ctsio->kern_data_len = min(data_len, alloc_len); ctsio->kern_total_len = ctsio->kern_data_len; /* * The control device is always connected. The disk device, on the * other hand, may not be online all the time. */ if (lun != NULL) tpc_ptr->device = (SID_QUAL_LU_CONNECTED << 5) | lun->be_lun->lun_type; else tpc_ptr->device = (SID_QUAL_LU_OFFLINE << 5) | T_DIRECT; tpc_ptr->page_code = SVPD_SCSI_TPC; scsi_ulto2b(data_len - 4, tpc_ptr->page_length); /* Block Device ROD Limits */ d_ptr = (struct scsi_vpd_tpc_descriptor *)&tpc_ptr->descr[0]; bdrl_ptr = (struct scsi_vpd_tpc_descriptor_bdrl *)d_ptr; scsi_ulto2b(SVPD_TPC_BDRL, bdrl_ptr->desc_type); scsi_ulto2b(sizeof(*bdrl_ptr) - 4, bdrl_ptr->desc_length); scsi_ulto2b(TPC_MAX_SEGS, bdrl_ptr->maximum_ranges); scsi_ulto4b(TPC_MAX_TOKEN_TIMEOUT, bdrl_ptr->maximum_inactivity_timeout); scsi_ulto4b(TPC_DFL_TOKEN_TIMEOUT, bdrl_ptr->default_inactivity_timeout); scsi_u64to8b(0, bdrl_ptr->maximum_token_transfer_size); scsi_u64to8b(0, bdrl_ptr->optimal_transfer_count); /* Supported commands */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); sc_ptr = (struct scsi_vpd_tpc_descriptor_sc *)d_ptr; scsi_ulto2b(SVPD_TPC_SC, sc_ptr->desc_type); sc_ptr->list_length = 2 * sizeof(*scd_ptr) + 11; scsi_ulto2b(roundup2(1 + sc_ptr->list_length, 4), sc_ptr->desc_length); scd_ptr = &sc_ptr->descr[0]; scd_ptr->opcode = EXTENDED_COPY; scd_ptr->sa_length = 5; scd_ptr->supported_service_actions[0] = EC_EC_LID1; scd_ptr->supported_service_actions[1] = EC_EC_LID4; scd_ptr->supported_service_actions[2] = EC_PT; scd_ptr->supported_service_actions[3] = EC_WUT; scd_ptr->supported_service_actions[4] = EC_COA; scd_ptr = (struct scsi_vpd_tpc_descriptor_sc_descr *) &scd_ptr->supported_service_actions[scd_ptr->sa_length]; scd_ptr->opcode = RECEIVE_COPY_STATUS; scd_ptr->sa_length = 6; scd_ptr->supported_service_actions[0] = RCS_RCS_LID1; scd_ptr->supported_service_actions[1] = RCS_RCFD; scd_ptr->supported_service_actions[2] = RCS_RCS_LID4; scd_ptr->supported_service_actions[3] = RCS_RCOP; scd_ptr->supported_service_actions[4] = RCS_RRTI; scd_ptr->supported_service_actions[5] = RCS_RART; /* Parameter data. */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); pd_ptr = (struct scsi_vpd_tpc_descriptor_pd *)d_ptr; scsi_ulto2b(SVPD_TPC_PD, pd_ptr->desc_type); scsi_ulto2b(sizeof(*pd_ptr) - 4, pd_ptr->desc_length); scsi_ulto2b(TPC_MAX_CSCDS, pd_ptr->maximum_cscd_descriptor_count); scsi_ulto2b(TPC_MAX_SEGS, pd_ptr->maximum_segment_descriptor_count); scsi_ulto4b(TPC_MAX_LIST, pd_ptr->maximum_descriptor_list_length); scsi_ulto4b(TPC_MAX_INLINE, pd_ptr->maximum_inline_data_length); /* Supported Descriptors */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); sd_ptr = (struct scsi_vpd_tpc_descriptor_sd *)d_ptr; scsi_ulto2b(SVPD_TPC_SD, sd_ptr->desc_type); scsi_ulto2b(roundup2(sizeof(*sd_ptr) - 4 + 4, 4), sd_ptr->desc_length); sd_ptr->list_length = 4; sd_ptr->supported_descriptor_codes[0] = EC_SEG_B2B; sd_ptr->supported_descriptor_codes[1] = EC_SEG_VERIFY; sd_ptr->supported_descriptor_codes[2] = EC_SEG_REGISTER_KEY; sd_ptr->supported_descriptor_codes[3] = EC_CSCD_ID; /* Supported CSCD Descriptor IDs */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); sdid_ptr = (struct scsi_vpd_tpc_descriptor_sdid *)d_ptr; scsi_ulto2b(SVPD_TPC_SDID, sdid_ptr->desc_type); scsi_ulto2b(roundup2(sizeof(*sdid_ptr) - 4 + 2, 4), sdid_ptr->desc_length); scsi_ulto2b(2, sdid_ptr->list_length); scsi_ulto2b(0xffff, &sdid_ptr->supported_descriptor_ids[0]); /* ROD Token Features */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); rtf_ptr = (struct scsi_vpd_tpc_descriptor_rtf *)d_ptr; scsi_ulto2b(SVPD_TPC_RTF, rtf_ptr->desc_type); scsi_ulto2b(sizeof(*rtf_ptr) - 4 + sizeof(*rtfb_ptr), rtf_ptr->desc_length); rtf_ptr->remote_tokens = 0; scsi_ulto4b(TPC_MIN_TOKEN_TIMEOUT, rtf_ptr->minimum_token_lifetime); scsi_ulto4b(UINT32_MAX, rtf_ptr->maximum_token_lifetime); scsi_ulto4b(TPC_MAX_TOKEN_TIMEOUT, rtf_ptr->maximum_token_inactivity_timeout); scsi_ulto2b(sizeof(*rtfb_ptr), rtf_ptr->type_specific_features_length); rtfb_ptr = (struct scsi_vpd_tpc_descriptor_rtf_block *) &rtf_ptr->type_specific_features; rtfb_ptr->type_format = SVPD_TPC_RTF_BLOCK; scsi_ulto2b(sizeof(*rtfb_ptr) - 4, rtfb_ptr->desc_length); scsi_ulto2b(0, rtfb_ptr->optimal_length_granularity); scsi_u64to8b(0, rtfb_ptr->maximum_bytes); scsi_u64to8b(0, rtfb_ptr->optimal_bytes); scsi_u64to8b(UINT64_MAX, rtfb_ptr->optimal_bytes_to_token_per_segment); scsi_u64to8b(TPC_MAX_IOCHUNK_SIZE, rtfb_ptr->optimal_bytes_from_token_per_segment); /* Supported ROD Tokens */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); srt_ptr = (struct scsi_vpd_tpc_descriptor_srt *)d_ptr; scsi_ulto2b(SVPD_TPC_SRT, srt_ptr->desc_type); scsi_ulto2b(sizeof(*srt_ptr) - 4 + 2*sizeof(*srtd_ptr), srt_ptr->desc_length); scsi_ulto2b(2*sizeof(*srtd_ptr), srt_ptr->rod_type_descriptors_length); srtd_ptr = (struct scsi_vpd_tpc_descriptor_srtd *) &srt_ptr->rod_type_descriptors; scsi_ulto4b(ROD_TYPE_AUR, srtd_ptr->rod_type); srtd_ptr->flags = SVPD_TPC_SRTD_TIN | SVPD_TPC_SRTD_TOUT; scsi_ulto2b(0, srtd_ptr->preference_indicator); srtd_ptr++; scsi_ulto4b(ROD_TYPE_BLOCK_ZERO, srtd_ptr->rod_type); srtd_ptr->flags = SVPD_TPC_SRTD_TIN; scsi_ulto2b(0, srtd_ptr->preference_indicator); /* General Copy Operations */ d_ptr = (struct scsi_vpd_tpc_descriptor *) (&d_ptr->parameters[0] + scsi_2btoul(d_ptr->desc_length)); gco_ptr = (struct scsi_vpd_tpc_descriptor_gco *)d_ptr; scsi_ulto2b(SVPD_TPC_GCO, gco_ptr->desc_type); scsi_ulto2b(sizeof(*gco_ptr) - 4, gco_ptr->desc_length); scsi_ulto4b(TPC_MAX_LISTS, gco_ptr->total_concurrent_copies); scsi_ulto4b(TPC_MAX_LISTS, gco_ptr->maximum_identified_concurrent_copies); scsi_ulto4b(TPC_MAX_SEG, gco_ptr->maximum_segment_length); gco_ptr->data_segment_granularity = 0; gco_ptr->inline_data_granularity = 0; ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } int ctl_receive_copy_operating_parameters(struct ctl_scsiio *ctsio) { struct scsi_receive_copy_operating_parameters *cdb; struct scsi_receive_copy_operating_parameters_data *data; int retval; int alloc_len, total_len; CTL_DEBUG_PRINT(("ctl_report_supported_tmf\n")); cdb = (struct scsi_receive_copy_operating_parameters *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; total_len = sizeof(*data) + 4; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; ctsio->kern_rel_offset = 0; ctsio->kern_data_len = min(total_len, alloc_len); ctsio->kern_total_len = ctsio->kern_data_len; data = (struct scsi_receive_copy_operating_parameters_data *)ctsio->kern_data_ptr; scsi_ulto4b(sizeof(*data) - 4 + 4, data->length); data->snlid = RCOP_SNLID; scsi_ulto2b(TPC_MAX_CSCDS, data->maximum_cscd_descriptor_count); scsi_ulto2b(TPC_MAX_SEGS, data->maximum_segment_descriptor_count); scsi_ulto4b(TPC_MAX_LIST, data->maximum_descriptor_list_length); scsi_ulto4b(TPC_MAX_SEG, data->maximum_segment_length); scsi_ulto4b(TPC_MAX_INLINE, data->maximum_inline_data_length); scsi_ulto4b(0, data->held_data_limit); scsi_ulto4b(0, data->maximum_stream_device_transfer_size); scsi_ulto2b(TPC_MAX_LISTS, data->total_concurrent_copies); data->maximum_concurrent_copies = TPC_MAX_LISTS; data->data_segment_granularity = 0; data->inline_data_granularity = 0; data->held_data_granularity = 0; data->implemented_descriptor_list_length = 4; data->list_of_implemented_descriptor_type_codes[0] = EC_SEG_B2B; data->list_of_implemented_descriptor_type_codes[1] = EC_SEG_VERIFY; data->list_of_implemented_descriptor_type_codes[2] = EC_SEG_REGISTER_KEY; data->list_of_implemented_descriptor_type_codes[3] = EC_CSCD_ID; ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } static struct tpc_list * tpc_find_list(struct ctl_lun *lun, uint32_t list_id, uint32_t init_idx) { struct tpc_list *list; mtx_assert(&lun->lun_lock, MA_OWNED); TAILQ_FOREACH(list, &lun->tpc_lists, links) { if ((list->flags & EC_LIST_ID_USAGE_MASK) != EC_LIST_ID_USAGE_NONE && list->list_id == list_id && list->init_idx == init_idx) break; } return (list); } int ctl_receive_copy_status_lid1(struct ctl_scsiio *ctsio) { struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_receive_copy_status_lid1 *cdb; struct scsi_receive_copy_status_lid1_data *data; struct tpc_list *list; struct tpc_list list_copy; int retval; int alloc_len, total_len; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_receive_copy_status_lid1\n")); cdb = (struct scsi_receive_copy_status_lid1 *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; list_id = cdb->list_identifier; mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_initindex(&ctsio->io_hdr.nexus)); if (list == NULL) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list_copy = *list; if (list->completed) { TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } mtx_unlock(&lun->lun_lock); total_len = sizeof(*data); alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; ctsio->kern_rel_offset = 0; ctsio->kern_data_len = min(total_len, alloc_len); ctsio->kern_total_len = ctsio->kern_data_len; data = (struct scsi_receive_copy_status_lid1_data *)ctsio->kern_data_ptr; scsi_ulto4b(sizeof(*data) - 4, data->available_data); if (list_copy.completed) { if (list_copy.error || list_copy.abort) data->copy_command_status = RCS_CCS_ERROR; else data->copy_command_status = RCS_CCS_COMPLETED; } else data->copy_command_status = RCS_CCS_INPROG; scsi_ulto2b(list_copy.curseg, data->segments_processed); if (list_copy.curbytes <= UINT32_MAX) { data->transfer_count_units = RCS_TC_BYTES; scsi_ulto4b(list_copy.curbytes, data->transfer_count); } else { data->transfer_count_units = RCS_TC_MBYTES; scsi_ulto4b(list_copy.curbytes >> 20, data->transfer_count); } ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } int ctl_receive_copy_failure_details(struct ctl_scsiio *ctsio) { struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_receive_copy_failure_details *cdb; struct scsi_receive_copy_failure_details_data *data; struct tpc_list *list; struct tpc_list list_copy; int retval; int alloc_len, total_len; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_receive_copy_failure_details\n")); cdb = (struct scsi_receive_copy_failure_details *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; list_id = cdb->list_identifier; mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_initindex(&ctsio->io_hdr.nexus)); if (list == NULL || !list->completed) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list_copy = *list; TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); mtx_unlock(&lun->lun_lock); total_len = sizeof(*data) + list_copy.sense_len; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; ctsio->kern_rel_offset = 0; ctsio->kern_data_len = min(total_len, alloc_len); ctsio->kern_total_len = ctsio->kern_data_len; data = (struct scsi_receive_copy_failure_details_data *)ctsio->kern_data_ptr; if (list_copy.completed && (list_copy.error || list_copy.abort)) { scsi_ulto4b(sizeof(*data) - 4 + list_copy.sense_len, data->available_data); data->copy_command_status = RCS_CCS_ERROR; } else scsi_ulto4b(0, data->available_data); scsi_ulto2b(list_copy.sense_len, data->sense_data_length); memcpy(data->sense_data, &list_copy.sense_data, list_copy.sense_len); ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } int ctl_receive_copy_status_lid4(struct ctl_scsiio *ctsio) { struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_receive_copy_status_lid4 *cdb; struct scsi_receive_copy_status_lid4_data *data; struct tpc_list *list; struct tpc_list list_copy; int retval; int alloc_len, total_len; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_receive_copy_status_lid4\n")); cdb = (struct scsi_receive_copy_status_lid4 *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; list_id = scsi_4btoul(cdb->list_identifier); mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_initindex(&ctsio->io_hdr.nexus)); if (list == NULL) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list_copy = *list; if (list->completed) { TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } mtx_unlock(&lun->lun_lock); total_len = sizeof(*data) + list_copy.sense_len; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; ctsio->kern_rel_offset = 0; ctsio->kern_data_len = min(total_len, alloc_len); ctsio->kern_total_len = ctsio->kern_data_len; data = (struct scsi_receive_copy_status_lid4_data *)ctsio->kern_data_ptr; scsi_ulto4b(sizeof(*data) - 4 + list_copy.sense_len, data->available_data); data->response_to_service_action = list_copy.service_action; if (list_copy.completed) { if (list_copy.error) data->copy_command_status = RCS_CCS_ERROR; else if (list_copy.abort) data->copy_command_status = RCS_CCS_ABORTED; else data->copy_command_status = RCS_CCS_COMPLETED; } else data->copy_command_status = RCS_CCS_INPROG_FG; scsi_ulto2b(list_copy.curops, data->operation_counter); scsi_ulto4b(UINT32_MAX, data->estimated_status_update_delay); data->transfer_count_units = RCS_TC_BYTES; scsi_u64to8b(list_copy.curbytes, data->transfer_count); scsi_ulto2b(list_copy.curseg, data->segments_processed); data->length_of_the_sense_data_field = list_copy.sense_len; data->sense_data_length = list_copy.sense_len; memcpy(data->sense_data, &list_copy.sense_data, list_copy.sense_len); ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } int ctl_copy_operation_abort(struct ctl_scsiio *ctsio) { struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_copy_operation_abort *cdb; struct tpc_list *list; int retval; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_copy_operation_abort\n")); cdb = (struct scsi_copy_operation_abort *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; list_id = scsi_4btoul(cdb->list_identifier); mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_initindex(&ctsio->io_hdr.nexus)); if (list == NULL) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list->abort = 1; mtx_unlock(&lun->lun_lock); ctl_set_success(ctsio); ctl_done((union ctl_io *)ctsio); return (retval); } static uint64_t tpc_resolve(struct tpc_list *list, uint16_t idx, uint32_t *ss, uint32_t *pb, uint32_t *pbo) { if (idx == 0xffff) { if (ss && list->lun->be_lun) *ss = list->lun->be_lun->blocksize; if (pb && list->lun->be_lun) *pb = list->lun->be_lun->blocksize << list->lun->be_lun->pblockexp; if (pbo && list->lun->be_lun) *pbo = list->lun->be_lun->blocksize * list->lun->be_lun->pblockoff; return (list->lun->lun); } if (idx >= list->ncscd) return (UINT64_MAX); return (tpcl_resolve(list->lun->ctl_softc, list->init_port, &list->cscd[idx], ss, pb, pbo)); } static void tpc_set_io_error_sense(struct tpc_list *list) { int flen; uint8_t csi[4]; uint8_t sks[3]; uint8_t fbuf[4 + 64]; scsi_ulto4b(list->curseg, csi); if (list->fwd_cscd <= 0x07ff) { sks[0] = SSD_SKS_SEGMENT_VALID; scsi_ulto2b((uint8_t *)&list->cscd[list->fwd_cscd] - list->params, &sks[1]); } else sks[0] = 0; if (list->fwd_scsi_status) { fbuf[0] = 0x0c; fbuf[2] = list->fwd_target; flen = list->fwd_sense_len; if (flen > 64) { flen = 64; fbuf[2] |= SSD_FORWARDED_FSDT; } fbuf[1] = 2 + flen; fbuf[3] = list->fwd_scsi_status; bcopy(&list->fwd_sense_data, &fbuf[4], flen); flen += 4; } else flen = 0; ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x0d, /*ascq*/ 0x01, SSD_ELEM_COMMAND, sizeof(csi), csi, sks[0] ? SSD_ELEM_SKS : SSD_ELEM_SKIP, sizeof(sks), sks, flen ? SSD_ELEM_DESC : SSD_ELEM_SKIP, flen, fbuf, SSD_ELEM_NONE); } static int tpc_process_b2b(struct tpc_list *list) { struct scsi_ec_segment_b2b *seg; struct scsi_ec_cscd_dtsp *sdstp, *ddstp; struct tpc_io *tior, *tiow; struct runl run; uint64_t sl, dl; off_t srclba, dstlba, numbytes, donebytes, roundbytes; int numlba; uint32_t srcblock, dstblock, pb, pbo, adj; uint16_t scscd, dcscd; uint8_t csi[4]; scsi_ulto4b(list->curseg, csi); if (list->stage == 1) { while ((tior = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tior, links); ctl_free_io(tior->io); free(tior->buf, M_CTL); free(tior, M_CTL); } if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { tpc_set_io_error_sense(list); return (CTL_RETVAL_ERROR); } list->cursectors += list->segsectors; list->curbytes += list->segbytes; return (CTL_RETVAL_COMPLETE); } TAILQ_INIT(&list->allio); seg = (struct scsi_ec_segment_b2b *)list->seg[list->curseg]; scscd = scsi_2btoul(seg->src_cscd); dcscd = scsi_2btoul(seg->dst_cscd); sl = tpc_resolve(list, scscd, &srcblock, NULL, NULL); dl = tpc_resolve(list, dcscd, &dstblock, &pb, &pbo); if (sl == UINT64_MAX || dl == UINT64_MAX) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_COMMAND, sizeof(csi), csi, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } if (pbo > 0) pbo = pb - pbo; sdstp = &list->cscd[scscd].dtsp; if (scsi_3btoul(sdstp->block_length) != 0) srcblock = scsi_3btoul(sdstp->block_length); ddstp = &list->cscd[dcscd].dtsp; if (scsi_3btoul(ddstp->block_length) != 0) dstblock = scsi_3btoul(ddstp->block_length); numlba = scsi_2btoul(seg->number_of_blocks); if (seg->flags & EC_SEG_DC) numbytes = (off_t)numlba * dstblock; else numbytes = (off_t)numlba * srcblock; srclba = scsi_8btou64(seg->src_lba); dstlba = scsi_8btou64(seg->dst_lba); // printf("Copy %ju bytes from %ju @ %ju to %ju @ %ju\n", // (uintmax_t)numbytes, sl, scsi_8btou64(seg->src_lba), // dl, scsi_8btou64(seg->dst_lba)); if (numbytes == 0) return (CTL_RETVAL_COMPLETE); if (numbytes % srcblock != 0 || numbytes % dstblock != 0) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x26, /*ascq*/ 0x0A, SSD_ELEM_COMMAND, sizeof(csi), csi, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } list->segbytes = numbytes; list->segsectors = numbytes / dstblock; donebytes = 0; TAILQ_INIT(&run); list->tbdio = 0; while (donebytes < numbytes) { roundbytes = numbytes - donebytes; if (roundbytes > TPC_MAX_IO_SIZE) { roundbytes = TPC_MAX_IO_SIZE; roundbytes -= roundbytes % dstblock; if (pb > dstblock) { adj = (dstlba * dstblock + roundbytes - pbo) % pb; if (roundbytes > adj) roundbytes -= adj; } } tior = malloc(sizeof(*tior), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tior->run); tior->buf = malloc(roundbytes, M_CTL, M_WAITOK); tior->list = list; TAILQ_INSERT_TAIL(&list->allio, tior, links); tior->io = tpcl_alloc_io(); ctl_scsi_read_write(tior->io, /*data_ptr*/ tior->buf, /*data_len*/ roundbytes, /*read_op*/ 1, /*byte2*/ 0, /*minimum_cdb_size*/ 0, /*lba*/ srclba, /*num_blocks*/ roundbytes / srcblock, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tior->io->io_hdr.retries = 3; tior->target = SSD_FORWARDED_SDS_EXSRC; tior->cscd = scscd; tior->lun = sl; tior->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tior; tiow = malloc(sizeof(*tior), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tiow->run); tiow->list = list; TAILQ_INSERT_TAIL(&list->allio, tiow, links); tiow->io = tpcl_alloc_io(); ctl_scsi_read_write(tiow->io, /*data_ptr*/ tior->buf, /*data_len*/ roundbytes, /*read_op*/ 0, /*byte2*/ 0, /*minimum_cdb_size*/ 0, /*lba*/ dstlba, /*num_blocks*/ roundbytes / dstblock, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tiow->io->io_hdr.retries = 3; tiow->target = SSD_FORWARDED_SDS_EXDST; tiow->cscd = dcscd; tiow->lun = dl; tiow->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tiow; TAILQ_INSERT_TAIL(&tior->run, tiow, rlinks); TAILQ_INSERT_TAIL(&run, tior, rlinks); list->tbdio++; donebytes += roundbytes; srclba += roundbytes / srcblock; dstlba += roundbytes / dstblock; } while ((tior = TAILQ_FIRST(&run)) != NULL) { TAILQ_REMOVE(&run, tior, rlinks); if (tpcl_queue(tior->io, tior->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); } list->stage++; return (CTL_RETVAL_QUEUED); } static int tpc_process_verify(struct tpc_list *list) { struct scsi_ec_segment_verify *seg; struct tpc_io *tio; uint64_t sl; uint16_t cscd; uint8_t csi[4]; scsi_ulto4b(list->curseg, csi); if (list->stage == 1) { while ((tio = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tio, links); ctl_free_io(tio->io); free(tio, M_CTL); } if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { tpc_set_io_error_sense(list); return (CTL_RETVAL_ERROR); } else return (CTL_RETVAL_COMPLETE); } TAILQ_INIT(&list->allio); seg = (struct scsi_ec_segment_verify *)list->seg[list->curseg]; cscd = scsi_2btoul(seg->src_cscd); sl = tpc_resolve(list, cscd, NULL, NULL, NULL); if (sl == UINT64_MAX) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_COMMAND, sizeof(csi), csi, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } // printf("Verify %ju\n", sl); if ((seg->tur & 0x01) == 0) return (CTL_RETVAL_COMPLETE); list->tbdio = 1; tio = malloc(sizeof(*tio), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tio->run); tio->list = list; TAILQ_INSERT_TAIL(&list->allio, tio, links); tio->io = tpcl_alloc_io(); ctl_scsi_tur(tio->io, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tio->io->io_hdr.retries = 3; tio->target = SSD_FORWARDED_SDS_EXSRC; tio->cscd = cscd; tio->lun = sl; tio->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tio; list->stage++; if (tpcl_queue(tio->io, tio->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); return (CTL_RETVAL_QUEUED); } static int tpc_process_register_key(struct tpc_list *list) { struct scsi_ec_segment_register_key *seg; struct tpc_io *tio; uint64_t dl; int datalen; uint16_t cscd; uint8_t csi[4]; scsi_ulto4b(list->curseg, csi); if (list->stage == 1) { while ((tio = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tio, links); ctl_free_io(tio->io); free(tio->buf, M_CTL); free(tio, M_CTL); } if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { tpc_set_io_error_sense(list); return (CTL_RETVAL_ERROR); } else return (CTL_RETVAL_COMPLETE); } TAILQ_INIT(&list->allio); seg = (struct scsi_ec_segment_register_key *)list->seg[list->curseg]; cscd = scsi_2btoul(seg->dst_cscd); dl = tpc_resolve(list, cscd, NULL, NULL, NULL); if (dl == UINT64_MAX) { ctl_set_sense(list->ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x08, /*ascq*/ 0x04, SSD_ELEM_COMMAND, sizeof(csi), csi, SSD_ELEM_NONE); return (CTL_RETVAL_ERROR); } // printf("Register Key %ju\n", dl); list->tbdio = 1; tio = malloc(sizeof(*tio), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tio->run); tio->list = list; TAILQ_INSERT_TAIL(&list->allio, tio, links); tio->io = tpcl_alloc_io(); datalen = sizeof(struct scsi_per_res_out_parms); tio->buf = malloc(datalen, M_CTL, M_WAITOK); ctl_scsi_persistent_res_out(tio->io, tio->buf, datalen, SPRO_REGISTER, -1, scsi_8btou64(seg->res_key), scsi_8btou64(seg->sa_res_key), /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tio->io->io_hdr.retries = 3; tio->target = SSD_FORWARDED_SDS_EXDST; tio->cscd = cscd; tio->lun = dl; tio->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tio; list->stage++; if (tpcl_queue(tio->io, tio->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); return (CTL_RETVAL_QUEUED); } static off_t tpc_ranges_length(struct scsi_range_desc *range, int nrange) { off_t length = 0; int r; for (r = 0; r < nrange; r++) length += scsi_4btoul(range[r].length); return (length); } static int tpc_check_ranges_l(struct scsi_range_desc *range, int nrange, uint64_t maxlba, uint64_t *lba) { uint64_t b1; uint32_t l1; int i; for (i = 0; i < nrange; i++) { b1 = scsi_8btou64(range[i].lba); l1 = scsi_4btoul(range[i].length); if (b1 + l1 < b1 || b1 + l1 > maxlba + 1) { *lba = MAX(b1, maxlba + 1); return (-1); } } return (0); } static int tpc_check_ranges_x(struct scsi_range_desc *range, int nrange) { uint64_t b1, b2; uint32_t l1, l2; int i, j; for (i = 0; i < nrange - 1; i++) { b1 = scsi_8btou64(range[i].lba); l1 = scsi_4btoul(range[i].length); for (j = i + 1; j < nrange; j++) { b2 = scsi_8btou64(range[j].lba); l2 = scsi_4btoul(range[j].length); if (b1 + l1 > b2 && b2 + l2 > b1) return (-1); } } return (0); } static int tpc_skip_ranges(struct scsi_range_desc *range, int nrange, off_t skip, int *srange, off_t *soffset) { off_t off; int r; r = 0; off = 0; while (r < nrange) { if (skip - off < scsi_4btoul(range[r].length)) { *srange = r; *soffset = skip - off; return (0); } off += scsi_4btoul(range[r].length); r++; } return (-1); } static int tpc_process_wut(struct tpc_list *list) { struct tpc_io *tio, *tior, *tiow; struct runl run; int drange, srange; off_t doffset, soffset; off_t srclba, dstlba, numbytes, donebytes, roundbytes; uint32_t srcblock, dstblock, pb, pbo, adj; if (list->stage > 0) { /* Cleanup after previous rounds. */ while ((tio = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tio, links); ctl_free_io(tio->io); free(tio->buf, M_CTL); free(tio, M_CTL); } if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { if (list->fwd_scsi_status) { list->ctsio->io_hdr.status = CTL_SCSI_ERROR | CTL_AUTOSENSE; list->ctsio->scsi_status = list->fwd_scsi_status; list->ctsio->sense_data = list->fwd_sense_data; list->ctsio->sense_len = list->fwd_sense_len; } else { ctl_set_invalid_field(list->ctsio, /*sks_valid*/ 0, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); } return (CTL_RETVAL_ERROR); } list->cursectors += list->segsectors; list->curbytes += list->segbytes; } /* Check where we are on destination ranges list. */ if (tpc_skip_ranges(list->range, list->nrange, list->cursectors, &drange, &doffset) != 0) return (CTL_RETVAL_COMPLETE); dstblock = list->lun->be_lun->blocksize; pb = dstblock << list->lun->be_lun->pblockexp; if (list->lun->be_lun->pblockoff > 0) pbo = pb - dstblock * list->lun->be_lun->pblockoff; else pbo = 0; /* Check where we are on source ranges list. */ srcblock = list->token->blocksize; if (tpc_skip_ranges(list->token->range, list->token->nrange, list->offset_into_rod + list->cursectors * dstblock / srcblock, &srange, &soffset) != 0) { ctl_set_invalid_field(list->ctsio, /*sks_valid*/ 0, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); return (CTL_RETVAL_ERROR); } srclba = scsi_8btou64(list->token->range[srange].lba) + soffset; dstlba = scsi_8btou64(list->range[drange].lba) + doffset; numbytes = srcblock * (scsi_4btoul(list->token->range[srange].length) - soffset); numbytes = omin(numbytes, dstblock * (scsi_4btoul(list->range[drange].length) - doffset)); if (numbytes > TPC_MAX_IOCHUNK_SIZE) { numbytes = TPC_MAX_IOCHUNK_SIZE; numbytes -= numbytes % dstblock; if (pb > dstblock) { adj = (dstlba * dstblock + numbytes - pbo) % pb; if (numbytes > adj) numbytes -= adj; } } if (numbytes % srcblock != 0 || numbytes % dstblock != 0) { ctl_set_invalid_field(list->ctsio, /*sks_valid*/ 0, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); return (CTL_RETVAL_ERROR); } list->segbytes = numbytes; list->segsectors = numbytes / dstblock; //printf("Copy chunk of %ju sectors from %ju to %ju\n", list->segsectors, // srclba, dstlba); donebytes = 0; TAILQ_INIT(&run); list->tbdio = 0; TAILQ_INIT(&list->allio); while (donebytes < numbytes) { roundbytes = numbytes - donebytes; if (roundbytes > TPC_MAX_IO_SIZE) { roundbytes = TPC_MAX_IO_SIZE; roundbytes -= roundbytes % dstblock; if (pb > dstblock) { adj = (dstlba * dstblock + roundbytes - pbo) % pb; if (roundbytes > adj) roundbytes -= adj; } } tior = malloc(sizeof(*tior), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tior->run); tior->buf = malloc(roundbytes, M_CTL, M_WAITOK); tior->list = list; TAILQ_INSERT_TAIL(&list->allio, tior, links); tior->io = tpcl_alloc_io(); ctl_scsi_read_write(tior->io, /*data_ptr*/ tior->buf, /*data_len*/ roundbytes, /*read_op*/ 1, /*byte2*/ 0, /*minimum_cdb_size*/ 0, /*lba*/ srclba, /*num_blocks*/ roundbytes / srcblock, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tior->io->io_hdr.retries = 3; tior->lun = list->token->lun; tior->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tior; tiow = malloc(sizeof(*tiow), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tiow->run); tiow->list = list; TAILQ_INSERT_TAIL(&list->allio, tiow, links); tiow->io = tpcl_alloc_io(); ctl_scsi_read_write(tiow->io, /*data_ptr*/ tior->buf, /*data_len*/ roundbytes, /*read_op*/ 0, /*byte2*/ 0, /*minimum_cdb_size*/ 0, /*lba*/ dstlba, /*num_blocks*/ roundbytes / dstblock, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tiow->io->io_hdr.retries = 3; tiow->lun = list->lun->lun; tiow->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tiow; TAILQ_INSERT_TAIL(&tior->run, tiow, rlinks); TAILQ_INSERT_TAIL(&run, tior, rlinks); list->tbdio++; donebytes += roundbytes; srclba += roundbytes / srcblock; dstlba += roundbytes / dstblock; } while ((tior = TAILQ_FIRST(&run)) != NULL) { TAILQ_REMOVE(&run, tior, rlinks); if (tpcl_queue(tior->io, tior->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); } list->stage++; return (CTL_RETVAL_QUEUED); } static int tpc_process_zero_wut(struct tpc_list *list) { struct tpc_io *tio, *tiow; struct runl run, *prun; int r; uint32_t dstblock, len; if (list->stage > 0) { complete: /* Cleanup after previous rounds. */ while ((tio = TAILQ_FIRST(&list->allio)) != NULL) { TAILQ_REMOVE(&list->allio, tio, links); ctl_free_io(tio->io); free(tio, M_CTL); } if (list->abort) { ctl_set_task_aborted(list->ctsio); return (CTL_RETVAL_ERROR); } else if (list->error) { if (list->fwd_scsi_status) { list->ctsio->io_hdr.status = CTL_SCSI_ERROR | CTL_AUTOSENSE; list->ctsio->scsi_status = list->fwd_scsi_status; list->ctsio->sense_data = list->fwd_sense_data; list->ctsio->sense_len = list->fwd_sense_len; } else { ctl_set_invalid_field(list->ctsio, /*sks_valid*/ 0, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); } return (CTL_RETVAL_ERROR); } list->cursectors += list->segsectors; list->curbytes += list->segbytes; return (CTL_RETVAL_COMPLETE); } dstblock = list->lun->be_lun->blocksize; TAILQ_INIT(&run); prun = &run; list->tbdio = 1; TAILQ_INIT(&list->allio); list->segsectors = 0; for (r = 0; r < list->nrange; r++) { len = scsi_4btoul(list->range[r].length); if (len == 0) continue; tiow = malloc(sizeof(*tiow), M_CTL, M_WAITOK | M_ZERO); TAILQ_INIT(&tiow->run); tiow->list = list; TAILQ_INSERT_TAIL(&list->allio, tiow, links); tiow->io = tpcl_alloc_io(); ctl_scsi_write_same(tiow->io, /*data_ptr*/ NULL, /*data_len*/ 0, /*byte2*/ SWS_NDOB, /*lba*/ scsi_8btou64(list->range[r].lba), /*num_blocks*/ len, /*tag_type*/ CTL_TAG_SIMPLE, /*control*/ 0); tiow->io->io_hdr.retries = 3; tiow->lun = list->lun->lun; tiow->io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = tiow; TAILQ_INSERT_TAIL(prun, tiow, rlinks); prun = &tiow->run; list->segsectors += len; } list->segbytes = list->segsectors * dstblock; if (TAILQ_EMPTY(&run)) goto complete; while ((tiow = TAILQ_FIRST(&run)) != NULL) { TAILQ_REMOVE(&run, tiow, rlinks); if (tpcl_queue(tiow->io, tiow->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); } list->stage++; return (CTL_RETVAL_QUEUED); } static void tpc_process(struct tpc_list *list) { struct ctl_lun *lun = list->lun; struct ctl_softc *softc = lun->ctl_softc; struct scsi_ec_segment *seg; struct ctl_scsiio *ctsio = list->ctsio; int retval = CTL_RETVAL_COMPLETE; uint8_t csi[4]; if (list->service_action == EC_WUT) { if (list->token != NULL) retval = tpc_process_wut(list); else retval = tpc_process_zero_wut(list); if (retval == CTL_RETVAL_QUEUED) return; if (retval == CTL_RETVAL_ERROR) { list->error = 1; goto done; } } else { //printf("ZZZ %d cscd, %d segs\n", list->ncscd, list->nseg); while (list->curseg < list->nseg) { seg = list->seg[list->curseg]; switch (seg->type_code) { case EC_SEG_B2B: retval = tpc_process_b2b(list); break; case EC_SEG_VERIFY: retval = tpc_process_verify(list); break; case EC_SEG_REGISTER_KEY: retval = tpc_process_register_key(list); break; default: scsi_ulto4b(list->curseg, csi); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_COPY_ABORTED, /*asc*/ 0x26, /*ascq*/ 0x09, SSD_ELEM_COMMAND, sizeof(csi), csi, SSD_ELEM_NONE); goto done; } if (retval == CTL_RETVAL_QUEUED) return; if (retval == CTL_RETVAL_ERROR) { list->error = 1; goto done; } list->curseg++; list->stage = 0; } } ctl_set_success(ctsio); done: //printf("ZZZ done\n"); free(list->params, M_CTL); list->params = NULL; if (list->token) { mtx_lock(&softc->tpc_lock); if (--list->token->active == 0) list->token->last_active = time_uptime; mtx_unlock(&softc->tpc_lock); list->token = NULL; } mtx_lock(&lun->lun_lock); if ((list->flags & EC_LIST_ID_USAGE_MASK) == EC_LIST_ID_USAGE_NONE) { TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } else { list->completed = 1; list->last_active = time_uptime; list->sense_data = ctsio->sense_data; list->sense_len = ctsio->sense_len; list->scsi_status = ctsio->scsi_status; } mtx_unlock(&lun->lun_lock); ctl_done((union ctl_io *)ctsio); } /* * For any sort of check condition, busy, etc., we just retry. We do not * decrement the retry count for unit attention type errors. These are * normal, and we want to save the retry count for "real" errors. Otherwise, * we could end up with situations where a command will succeed in some * situations and fail in others, depending on whether a unit attention is * pending. Also, some of our error recovery actions, most notably the * LUN reset action, will cause a unit attention. * * We can add more detail here later if necessary. */ static tpc_error_action tpc_checkcond_parse(union ctl_io *io) { tpc_error_action error_action; int error_code, sense_key, asc, ascq; /* * Default to retrying the command. */ error_action = TPC_ERR_RETRY; scsi_extract_sense_len(&io->scsiio.sense_data, io->scsiio.sense_len, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); switch (error_code) { case SSD_DEFERRED_ERROR: case SSD_DESC_DEFERRED_ERROR: error_action |= TPC_ERR_NO_DECREMENT; break; case SSD_CURRENT_ERROR: case SSD_DESC_CURRENT_ERROR: default: switch (sense_key) { case SSD_KEY_UNIT_ATTENTION: error_action |= TPC_ERR_NO_DECREMENT; break; case SSD_KEY_HARDWARE_ERROR: /* * This is our generic "something bad happened" * error code. It often isn't recoverable. */ if ((asc == 0x44) && (ascq == 0x00)) error_action = TPC_ERR_FAIL; break; case SSD_KEY_NOT_READY: /* * If the LUN is powered down, there likely isn't * much point in retrying right now. */ if ((asc == 0x04) && (ascq == 0x02)) error_action = TPC_ERR_FAIL; /* * If the LUN is offline, there probably isn't much * point in retrying, either. */ if ((asc == 0x04) && (ascq == 0x03)) error_action = TPC_ERR_FAIL; break; } } return (error_action); } static tpc_error_action tpc_error_parse(union ctl_io *io) { tpc_error_action error_action = TPC_ERR_RETRY; switch (io->io_hdr.io_type) { case CTL_IO_SCSI: switch (io->io_hdr.status & CTL_STATUS_MASK) { case CTL_SCSI_ERROR: switch (io->scsiio.scsi_status) { case SCSI_STATUS_CHECK_COND: error_action = tpc_checkcond_parse(io); break; default: break; } break; default: break; } break; case CTL_IO_TASK: break; default: panic("%s: invalid ctl_io type %d\n", __func__, io->io_hdr.io_type); break; } return (error_action); } void tpc_done(union ctl_io *io) { struct tpc_io *tio, *tior; /* * Very minimal retry logic. We basically retry if we got an error * back, and the retry count is greater than 0. If we ever want * more sophisticated initiator type behavior, the CAM error * recovery code in ../common might be helpful. */ tio = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; if (((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS) && (io->io_hdr.retries > 0)) { ctl_io_status old_status; tpc_error_action error_action; error_action = tpc_error_parse(io); switch (error_action & TPC_ERR_MASK) { case TPC_ERR_FAIL: break; case TPC_ERR_RETRY: default: if ((error_action & TPC_ERR_NO_DECREMENT) == 0) io->io_hdr.retries--; old_status = io->io_hdr.status; io->io_hdr.status = CTL_STATUS_NONE; io->io_hdr.flags &= ~CTL_FLAG_ABORT; io->io_hdr.flags &= ~CTL_FLAG_SENT_2OTHER_SC; if (tpcl_queue(io, tio->lun) != CTL_RETVAL_COMPLETE) { printf("%s: error returned from ctl_queue()!\n", __func__); io->io_hdr.status = old_status; } else return; } } if ((io->io_hdr.status & CTL_STATUS_MASK) != CTL_SUCCESS) { tio->list->error = 1; if (io->io_hdr.io_type == CTL_IO_SCSI && (io->io_hdr.status & CTL_STATUS_MASK) == CTL_SCSI_ERROR) { tio->list->fwd_scsi_status = io->scsiio.scsi_status; tio->list->fwd_sense_data = io->scsiio.sense_data; tio->list->fwd_sense_len = io->scsiio.sense_len; tio->list->fwd_target = tio->target; tio->list->fwd_cscd = tio->cscd; } } else atomic_add_int(&tio->list->curops, 1); if (!tio->list->error && !tio->list->abort) { while ((tior = TAILQ_FIRST(&tio->run)) != NULL) { TAILQ_REMOVE(&tio->run, tior, rlinks); atomic_add_int(&tio->list->tbdio, 1); if (tpcl_queue(tior->io, tior->lun) != CTL_RETVAL_COMPLETE) panic("tpcl_queue() error"); } } if (atomic_fetchadd_int(&tio->list->tbdio, -1) == 1) tpc_process(tio->list); } int ctl_extended_copy_lid1(struct ctl_scsiio *ctsio) { struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_extended_copy *cdb; struct scsi_extended_copy_lid1_data *data; struct scsi_ec_cscd *cscd; struct scsi_ec_segment *seg; struct tpc_list *list, *tlist; uint8_t *ptr; char *value; int len, off, lencscd, lenseg, leninl, nseg; CTL_DEBUG_PRINT(("ctl_extended_copy_lid1\n")); cdb = (struct scsi_extended_copy *)ctsio->cdb; len = scsi_4btoul(cdb->length); if (len == 0) { ctl_set_success(ctsio); goto done; } if (len < sizeof(struct scsi_extended_copy_lid1_data) || len > sizeof(struct scsi_extended_copy_lid1_data) + TPC_MAX_LIST + TPC_MAX_INLINE) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* * If we've got a kernel request that hasn't been malloced yet, * malloc it and tell the caller the data buffer is here. */ if ((ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) == 0) { ctsio->kern_data_ptr = malloc(len, M_CTL, M_WAITOK); ctsio->kern_data_len = len; ctsio->kern_total_len = len; ctsio->kern_rel_offset = 0; ctsio->kern_sg_entries = 0; ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } data = (struct scsi_extended_copy_lid1_data *)ctsio->kern_data_ptr; lencscd = scsi_2btoul(data->cscd_list_length); lenseg = scsi_4btoul(data->segment_list_length); leninl = scsi_4btoul(data->inline_data_length); if (lencscd > TPC_MAX_CSCDS * sizeof(struct scsi_ec_cscd)) { ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x06, SSD_ELEM_NONE); goto done; } if (lenseg > TPC_MAX_SEGS * sizeof(struct scsi_ec_segment)) { ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x08, SSD_ELEM_NONE); goto done; } if (lencscd + lenseg > TPC_MAX_LIST || leninl > TPC_MAX_INLINE || len < sizeof(struct scsi_extended_copy_lid1_data) + lencscd + lenseg + leninl) { ctl_set_param_len_error(ctsio); goto done; } list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO); list->service_action = cdb->service_action; value = ctl_get_opt(&lun->be_lun->options, "insecure_tpc"); if (value != NULL && strcmp(value, "on") == 0) list->init_port = -1; else list->init_port = ctsio->io_hdr.nexus.targ_port; list->init_idx = ctl_get_initindex(&ctsio->io_hdr.nexus); list->list_id = data->list_identifier; list->flags = data->flags; list->params = ctsio->kern_data_ptr; list->cscd = (struct scsi_ec_cscd *)&data->data[0]; ptr = &data->data[0]; for (off = 0; off < lencscd; off += sizeof(struct scsi_ec_cscd)) { cscd = (struct scsi_ec_cscd *)(ptr + off); if (cscd->type_code != EC_CSCD_ID) { free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x07, SSD_ELEM_NONE); goto done; } } ptr = &data->data[lencscd]; for (nseg = 0, off = 0; off < lenseg; nseg++) { if (nseg >= TPC_MAX_SEGS) { free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x08, SSD_ELEM_NONE); goto done; } seg = (struct scsi_ec_segment *)(ptr + off); if (seg->type_code != EC_SEG_B2B && seg->type_code != EC_SEG_VERIFY && seg->type_code != EC_SEG_REGISTER_KEY) { free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x09, SSD_ELEM_NONE); goto done; } list->seg[nseg] = seg; off += sizeof(struct scsi_ec_segment) + scsi_2btoul(seg->descr_length); } list->inl = &data->data[lencscd + lenseg]; list->ncscd = lencscd / sizeof(struct scsi_ec_cscd); list->nseg = nseg; list->leninl = leninl; list->ctsio = ctsio; list->lun = lun; mtx_lock(&lun->lun_lock); if ((list->flags & EC_LIST_ID_USAGE_MASK) != EC_LIST_ID_USAGE_NONE) { tlist = tpc_find_list(lun, list->list_id, list->init_idx); if (tlist != NULL && !tlist->completed) { mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (tlist != NULL) { TAILQ_REMOVE(&lun->tpc_lists, tlist, links); free(tlist, M_CTL); } } TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); tpc_process(list); return (CTL_RETVAL_COMPLETE); done: if (ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) { free(ctsio->kern_data_ptr, M_CTL); ctsio->io_hdr.flags &= ~CTL_FLAG_ALLOCATED; } ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } int ctl_extended_copy_lid4(struct ctl_scsiio *ctsio) { struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_extended_copy *cdb; struct scsi_extended_copy_lid4_data *data; struct scsi_ec_cscd *cscd; struct scsi_ec_segment *seg; struct tpc_list *list, *tlist; uint8_t *ptr; char *value; int len, off, lencscd, lenseg, leninl, nseg; CTL_DEBUG_PRINT(("ctl_extended_copy_lid4\n")); cdb = (struct scsi_extended_copy *)ctsio->cdb; len = scsi_4btoul(cdb->length); if (len == 0) { ctl_set_success(ctsio); goto done; } if (len < sizeof(struct scsi_extended_copy_lid4_data) || len > sizeof(struct scsi_extended_copy_lid4_data) + TPC_MAX_LIST + TPC_MAX_INLINE) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* * If we've got a kernel request that hasn't been malloced yet, * malloc it and tell the caller the data buffer is here. */ if ((ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) == 0) { ctsio->kern_data_ptr = malloc(len, M_CTL, M_WAITOK); ctsio->kern_data_len = len; ctsio->kern_total_len = len; ctsio->kern_rel_offset = 0; ctsio->kern_sg_entries = 0; ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } data = (struct scsi_extended_copy_lid4_data *)ctsio->kern_data_ptr; lencscd = scsi_2btoul(data->cscd_list_length); lenseg = scsi_2btoul(data->segment_list_length); leninl = scsi_2btoul(data->inline_data_length); if (lencscd > TPC_MAX_CSCDS * sizeof(struct scsi_ec_cscd)) { ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x06, SSD_ELEM_NONE); goto done; } if (lenseg > TPC_MAX_SEGS * sizeof(struct scsi_ec_segment)) { ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x08, SSD_ELEM_NONE); goto done; } if (lencscd + lenseg > TPC_MAX_LIST || leninl > TPC_MAX_INLINE || len < sizeof(struct scsi_extended_copy_lid1_data) + lencscd + lenseg + leninl) { ctl_set_param_len_error(ctsio); goto done; } list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO); list->service_action = cdb->service_action; value = ctl_get_opt(&lun->be_lun->options, "insecure_tpc"); if (value != NULL && strcmp(value, "on") == 0) list->init_port = -1; else list->init_port = ctsio->io_hdr.nexus.targ_port; list->init_idx = ctl_get_initindex(&ctsio->io_hdr.nexus); list->list_id = scsi_4btoul(data->list_identifier); list->flags = data->flags; list->params = ctsio->kern_data_ptr; list->cscd = (struct scsi_ec_cscd *)&data->data[0]; ptr = &data->data[0]; for (off = 0; off < lencscd; off += sizeof(struct scsi_ec_cscd)) { cscd = (struct scsi_ec_cscd *)(ptr + off); if (cscd->type_code != EC_CSCD_ID) { free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x07, SSD_ELEM_NONE); goto done; } } ptr = &data->data[lencscd]; for (nseg = 0, off = 0; off < lenseg; nseg++) { if (nseg >= TPC_MAX_SEGS) { free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x08, SSD_ELEM_NONE); goto done; } seg = (struct scsi_ec_segment *)(ptr + off); if (seg->type_code != EC_SEG_B2B && seg->type_code != EC_SEG_VERIFY && seg->type_code != EC_SEG_REGISTER_KEY) { free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x26, /*ascq*/ 0x09, SSD_ELEM_NONE); goto done; } list->seg[nseg] = seg; off += sizeof(struct scsi_ec_segment) + scsi_2btoul(seg->descr_length); } list->inl = &data->data[lencscd + lenseg]; list->ncscd = lencscd / sizeof(struct scsi_ec_cscd); list->nseg = nseg; list->leninl = leninl; list->ctsio = ctsio; list->lun = lun; mtx_lock(&lun->lun_lock); if ((list->flags & EC_LIST_ID_USAGE_MASK) != EC_LIST_ID_USAGE_NONE) { tlist = tpc_find_list(lun, list->list_id, list->init_idx); if (tlist != NULL && !tlist->completed) { mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (tlist != NULL) { TAILQ_REMOVE(&lun->tpc_lists, tlist, links); free(tlist, M_CTL); } } TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); tpc_process(list); return (CTL_RETVAL_COMPLETE); done: if (ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) { free(ctsio->kern_data_ptr, M_CTL); ctsio->io_hdr.flags &= ~CTL_FLAG_ALLOCATED; } ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } static void tpc_create_token(struct ctl_lun *lun, struct ctl_port *port, off_t len, struct scsi_token *token) { static int id = 0; struct scsi_vpd_id_descriptor *idd = NULL; struct scsi_ec_cscd_id *cscd; struct scsi_read_capacity_data_long *dtsd; int targid_len; scsi_ulto4b(ROD_TYPE_AUR, token->type); scsi_ulto2b(0x01f8, token->length); scsi_u64to8b(atomic_fetchadd_int(&id, 1), &token->body[0]); if (lun->lun_devid) idd = scsi_get_devid_desc((struct scsi_vpd_id_descriptor *) lun->lun_devid->data, lun->lun_devid->len, scsi_devid_is_lun_naa); if (idd == NULL && lun->lun_devid) idd = scsi_get_devid_desc((struct scsi_vpd_id_descriptor *) lun->lun_devid->data, lun->lun_devid->len, scsi_devid_is_lun_eui64); if (idd != NULL) { cscd = (struct scsi_ec_cscd_id *)&token->body[8]; cscd->type_code = EC_CSCD_ID; cscd->luidt_pdt = T_DIRECT; memcpy(&cscd->codeset, idd, 4 + idd->length); scsi_ulto3b(lun->be_lun->blocksize, cscd->dtsp.block_length); } scsi_u64to8b(0, &token->body[40]); /* XXX: Should be 128bit value. */ scsi_u64to8b(len, &token->body[48]); /* ROD token device type specific data (RC16 without first field) */ dtsd = (struct scsi_read_capacity_data_long *)&token->body[88 - 8]; scsi_ulto4b(lun->be_lun->blocksize, dtsd->length); dtsd->prot_lbppbe = lun->be_lun->pblockexp & SRC16_LBPPBE; scsi_ulto2b(lun->be_lun->pblockoff & SRC16_LALBA_A, dtsd->lalba_lbp); if (lun->be_lun->flags & CTL_LUN_FLAG_UNMAP) dtsd->lalba_lbp[0] |= SRC16_LBPME | SRC16_LBPRZ; if (port->target_devid) { targid_len = port->target_devid->len; memcpy(&token->body[120], port->target_devid->data, targid_len); } else targid_len = 32; arc4rand(&token->body[120 + targid_len], 384 - targid_len, 0); }; int ctl_populate_token(struct ctl_scsiio *ctsio) { struct ctl_softc *softc = CTL_SOFTC(ctsio); struct ctl_port *port = CTL_PORT(ctsio); struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_populate_token *cdb; struct scsi_populate_token_data *data; struct tpc_list *list, *tlist; struct tpc_token *token; uint64_t lba; int len, lendata, lendesc; CTL_DEBUG_PRINT(("ctl_populate_token\n")); cdb = (struct scsi_populate_token *)ctsio->cdb; len = scsi_4btoul(cdb->length); if (len < sizeof(struct scsi_populate_token_data) || len > sizeof(struct scsi_populate_token_data) + TPC_MAX_SEGS * sizeof(struct scsi_range_desc)) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* * If we've got a kernel request that hasn't been malloced yet, * malloc it and tell the caller the data buffer is here. */ if ((ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) == 0) { ctsio->kern_data_ptr = malloc(len, M_CTL, M_WAITOK); ctsio->kern_data_len = len; ctsio->kern_total_len = len; ctsio->kern_rel_offset = 0; ctsio->kern_sg_entries = 0; ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } data = (struct scsi_populate_token_data *)ctsio->kern_data_ptr; lendata = scsi_2btoul(data->length); if (lendata < sizeof(struct scsi_populate_token_data) - 2 + sizeof(struct scsi_range_desc)) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } lendesc = scsi_2btoul(data->range_descriptor_length); if (lendesc < sizeof(struct scsi_range_desc) || len < sizeof(struct scsi_populate_token_data) + lendesc || lendata < sizeof(struct scsi_populate_token_data) - 2 + lendesc) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 14, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* printf("PT(list=%u) flags=%x to=%d rt=%x len=%x\n", scsi_4btoul(cdb->list_identifier), data->flags, scsi_4btoul(data->inactivity_timeout), scsi_4btoul(data->rod_type), scsi_2btoul(data->range_descriptor_length)); */ /* Validate INACTIVITY TIMEOUT field */ if (scsi_4btoul(data->inactivity_timeout) > TPC_MAX_TOKEN_TIMEOUT) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 4, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* Validate ROD TYPE field */ if ((data->flags & EC_PT_RTV) && scsi_4btoul(data->rod_type) != ROD_TYPE_AUR) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 8, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* Validate list of ranges */ if (tpc_check_ranges_l(&data->desc[0], scsi_2btoul(data->range_descriptor_length) / sizeof(struct scsi_range_desc), lun->be_lun->maxlba, &lba) != 0) { ctl_set_lba_out_of_range(ctsio, lba); goto done; } if (tpc_check_ranges_x(&data->desc[0], scsi_2btoul(data->range_descriptor_length) / sizeof(struct scsi_range_desc)) != 0) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 0, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO); list->service_action = cdb->service_action; list->init_port = ctsio->io_hdr.nexus.targ_port; list->init_idx = ctl_get_initindex(&ctsio->io_hdr.nexus); list->list_id = scsi_4btoul(cdb->list_identifier); list->flags = data->flags; list->ctsio = ctsio; list->lun = lun; mtx_lock(&lun->lun_lock); tlist = tpc_find_list(lun, list->list_id, list->init_idx); if (tlist != NULL && !tlist->completed) { mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (tlist != NULL) { TAILQ_REMOVE(&lun->tpc_lists, tlist, links); free(tlist, M_CTL); } TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); token = malloc(sizeof(*token), M_CTL, M_WAITOK | M_ZERO); token->lun = lun->lun; token->blocksize = lun->be_lun->blocksize; token->params = ctsio->kern_data_ptr; token->range = &data->desc[0]; token->nrange = scsi_2btoul(data->range_descriptor_length) / sizeof(struct scsi_range_desc); list->cursectors = tpc_ranges_length(token->range, token->nrange); list->curbytes = (off_t)list->cursectors * lun->be_lun->blocksize; tpc_create_token(lun, port, list->curbytes, (struct scsi_token *)token->token); token->active = 0; token->last_active = time_uptime; token->timeout = scsi_4btoul(data->inactivity_timeout); if (token->timeout == 0) token->timeout = TPC_DFL_TOKEN_TIMEOUT; else if (token->timeout < TPC_MIN_TOKEN_TIMEOUT) token->timeout = TPC_MIN_TOKEN_TIMEOUT; memcpy(list->res_token, token->token, sizeof(list->res_token)); list->res_token_valid = 1; list->curseg = 0; list->completed = 1; list->last_active = time_uptime; mtx_lock(&softc->tpc_lock); TAILQ_INSERT_TAIL(&softc->tpc_tokens, token, links); mtx_unlock(&softc->tpc_lock); ctl_set_success(ctsio); ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); done: if (ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) { free(ctsio->kern_data_ptr, M_CTL); ctsio->io_hdr.flags &= ~CTL_FLAG_ALLOCATED; } ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } int ctl_write_using_token(struct ctl_scsiio *ctsio) { struct ctl_softc *softc = CTL_SOFTC(ctsio); struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_write_using_token *cdb; struct scsi_write_using_token_data *data; struct tpc_list *list, *tlist; struct tpc_token *token; uint64_t lba; int len, lendata, lendesc; CTL_DEBUG_PRINT(("ctl_write_using_token\n")); cdb = (struct scsi_write_using_token *)ctsio->cdb; len = scsi_4btoul(cdb->length); if (len < sizeof(struct scsi_write_using_token_data) || len > sizeof(struct scsi_write_using_token_data) + TPC_MAX_SEGS * sizeof(struct scsi_range_desc)) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 9, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* * If we've got a kernel request that hasn't been malloced yet, * malloc it and tell the caller the data buffer is here. */ if ((ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) == 0) { ctsio->kern_data_ptr = malloc(len, M_CTL, M_WAITOK); ctsio->kern_data_len = len; ctsio->kern_total_len = len; ctsio->kern_rel_offset = 0; ctsio->kern_sg_entries = 0; ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } data = (struct scsi_write_using_token_data *)ctsio->kern_data_ptr; lendata = scsi_2btoul(data->length); if (lendata < sizeof(struct scsi_write_using_token_data) - 2 + sizeof(struct scsi_range_desc)) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } lendesc = scsi_2btoul(data->range_descriptor_length); if (lendesc < sizeof(struct scsi_range_desc) || len < sizeof(struct scsi_write_using_token_data) + lendesc || lendata < sizeof(struct scsi_write_using_token_data) - 2 + lendesc) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 534, /*bit_valid*/ 0, /*bit*/ 0); goto done; } /* printf("WUT(list=%u) flags=%x off=%ju len=%x\n", scsi_4btoul(cdb->list_identifier), data->flags, scsi_8btou64(data->offset_into_rod), scsi_2btoul(data->range_descriptor_length)); */ /* Validate list of ranges */ if (tpc_check_ranges_l(&data->desc[0], scsi_2btoul(data->range_descriptor_length) / sizeof(struct scsi_range_desc), lun->be_lun->maxlba, &lba) != 0) { ctl_set_lba_out_of_range(ctsio, lba); goto done; } if (tpc_check_ranges_x(&data->desc[0], scsi_2btoul(data->range_descriptor_length) / sizeof(struct scsi_range_desc)) != 0) { ctl_set_invalid_field(ctsio, /*sks_valid*/ 0, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } list = malloc(sizeof(struct tpc_list), M_CTL, M_WAITOK | M_ZERO); list->service_action = cdb->service_action; list->init_port = ctsio->io_hdr.nexus.targ_port; list->init_idx = ctl_get_initindex(&ctsio->io_hdr.nexus); list->list_id = scsi_4btoul(cdb->list_identifier); list->flags = data->flags; list->params = ctsio->kern_data_ptr; list->range = &data->desc[0]; list->nrange = scsi_2btoul(data->range_descriptor_length) / sizeof(struct scsi_range_desc); list->offset_into_rod = scsi_8btou64(data->offset_into_rod); list->ctsio = ctsio; list->lun = lun; mtx_lock(&lun->lun_lock); tlist = tpc_find_list(lun, list->list_id, list->init_idx); if (tlist != NULL && !tlist->completed) { mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 0, /*field*/ 0, /*bit_valid*/ 0, /*bit*/ 0); goto done; } if (tlist != NULL) { TAILQ_REMOVE(&lun->tpc_lists, tlist, links); free(tlist, M_CTL); } TAILQ_INSERT_TAIL(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); /* Block device zero ROD token -> no token. */ if (scsi_4btoul(data->rod_token) == ROD_TYPE_BLOCK_ZERO) { tpc_process(list); return (CTL_RETVAL_COMPLETE); } mtx_lock(&softc->tpc_lock); TAILQ_FOREACH(token, &softc->tpc_tokens, links) { if (memcmp(token->token, data->rod_token, sizeof(data->rod_token)) == 0) break; } if (token != NULL) { token->active++; list->token = token; if (data->flags & EC_WUT_DEL_TKN) token->timeout = 0; } mtx_unlock(&softc->tpc_lock); if (token == NULL) { mtx_lock(&lun->lun_lock); TAILQ_REMOVE(&lun->tpc_lists, list, links); mtx_unlock(&lun->lun_lock); free(list, M_CTL); ctl_set_sense(ctsio, /*current_error*/ 1, /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST, /*asc*/ 0x23, /*ascq*/ 0x04, SSD_ELEM_NONE); goto done; } tpc_process(list); return (CTL_RETVAL_COMPLETE); done: if (ctsio->io_hdr.flags & CTL_FLAG_ALLOCATED) { free(ctsio->kern_data_ptr, M_CTL); ctsio->io_hdr.flags &= ~CTL_FLAG_ALLOCATED; } ctl_done((union ctl_io *)ctsio); return (CTL_RETVAL_COMPLETE); } int ctl_receive_rod_token_information(struct ctl_scsiio *ctsio) { struct ctl_lun *lun = CTL_LUN(ctsio); struct scsi_receive_rod_token_information *cdb; struct scsi_receive_copy_status_lid4_data *data; struct tpc_list *list; struct tpc_list list_copy; uint8_t *ptr; int retval; int alloc_len, total_len, token_len; uint32_t list_id; CTL_DEBUG_PRINT(("ctl_receive_rod_token_information\n")); cdb = (struct scsi_receive_rod_token_information *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; list_id = scsi_4btoul(cdb->list_identifier); mtx_lock(&lun->lun_lock); list = tpc_find_list(lun, list_id, ctl_get_initindex(&ctsio->io_hdr.nexus)); if (list == NULL) { mtx_unlock(&lun->lun_lock); ctl_set_invalid_field(ctsio, /*sks_valid*/ 1, /*command*/ 1, /*field*/ 2, /*bit_valid*/ 0, /*bit*/ 0); ctl_done((union ctl_io *)ctsio); return (retval); } list_copy = *list; if (list->completed) { TAILQ_REMOVE(&lun->tpc_lists, list, links); free(list, M_CTL); } mtx_unlock(&lun->lun_lock); token_len = list_copy.res_token_valid ? 2 + sizeof(list_copy.res_token) : 0; total_len = sizeof(*data) + list_copy.sense_len + 4 + token_len; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; ctsio->kern_rel_offset = 0; ctsio->kern_data_len = min(total_len, alloc_len); ctsio->kern_total_len = ctsio->kern_data_len; data = (struct scsi_receive_copy_status_lid4_data *)ctsio->kern_data_ptr; scsi_ulto4b(sizeof(*data) - 4 + list_copy.sense_len + 4 + token_len, data->available_data); data->response_to_service_action = list_copy.service_action; if (list_copy.completed) { if (list_copy.error) data->copy_command_status = RCS_CCS_ERROR; else if (list_copy.abort) data->copy_command_status = RCS_CCS_ABORTED; else data->copy_command_status = RCS_CCS_COMPLETED; } else data->copy_command_status = RCS_CCS_INPROG_FG; scsi_ulto2b(list_copy.curops, data->operation_counter); scsi_ulto4b(UINT32_MAX, data->estimated_status_update_delay); data->transfer_count_units = RCS_TC_LBAS; scsi_u64to8b(list_copy.cursectors, data->transfer_count); scsi_ulto2b(list_copy.curseg, data->segments_processed); data->length_of_the_sense_data_field = list_copy.sense_len; data->sense_data_length = list_copy.sense_len; memcpy(data->sense_data, &list_copy.sense_data, list_copy.sense_len); ptr = &data->sense_data[data->length_of_the_sense_data_field]; scsi_ulto4b(token_len, &ptr[0]); if (list_copy.res_token_valid) { scsi_ulto2b(0, &ptr[4]); memcpy(&ptr[6], list_copy.res_token, sizeof(list_copy.res_token)); } /* printf("RRTI(list=%u) valid=%d\n", scsi_4btoul(cdb->list_identifier), list_copy.res_token_valid); */ ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } int ctl_report_all_rod_tokens(struct ctl_scsiio *ctsio) { struct ctl_softc *softc = CTL_SOFTC(ctsio); struct scsi_report_all_rod_tokens *cdb; struct scsi_report_all_rod_tokens_data *data; struct tpc_token *token; int retval; int alloc_len, total_len, tokens, i; CTL_DEBUG_PRINT(("ctl_receive_rod_token_information\n")); cdb = (struct scsi_report_all_rod_tokens *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; tokens = 0; mtx_lock(&softc->tpc_lock); TAILQ_FOREACH(token, &softc->tpc_tokens, links) tokens++; mtx_unlock(&softc->tpc_lock); if (tokens > 512) tokens = 512; total_len = sizeof(*data) + tokens * 96; alloc_len = scsi_4btoul(cdb->length); ctsio->kern_data_ptr = malloc(total_len, M_CTL, M_WAITOK | M_ZERO); ctsio->kern_sg_entries = 0; ctsio->kern_rel_offset = 0; ctsio->kern_data_len = min(total_len, alloc_len); ctsio->kern_total_len = ctsio->kern_data_len; data = (struct scsi_report_all_rod_tokens_data *)ctsio->kern_data_ptr; i = 0; mtx_lock(&softc->tpc_lock); TAILQ_FOREACH(token, &softc->tpc_tokens, links) { if (i >= tokens) break; memcpy(&data->rod_management_token_list[i * 96], token->token, 96); i++; } mtx_unlock(&softc->tpc_lock); scsi_ulto4b(sizeof(*data) - 4 + i * 96, data->available_data); /* printf("RART tokens=%d\n", i); */ ctl_set_success(ctsio); ctsio->io_hdr.flags |= CTL_FLAG_ALLOCATED; ctsio->be_move_done = ctl_config_move_done; ctl_datamove((union ctl_io *)ctsio); return (retval); } Index: head/sys/cam/ctl/ctl_tpc.h =================================================================== --- head/sys/cam/ctl/ctl_tpc.h (revision 328069) +++ head/sys/cam/ctl/ctl_tpc.h (revision 328070) @@ -1,39 +1,41 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2014 Alexander Motin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * $FreeBSD$ */ #ifndef _CTL_TPC_H #define _CTL_TPC_H 1 void tpc_done(union ctl_io *io); uint64_t tpcl_resolve(struct ctl_softc *softc, int init_port, struct scsi_ec_cscd *cscd, uint32_t *ss, uint32_t *ps, uint32_t *pso); union ctl_io * tpcl_alloc_io(void); int tpcl_queue(union ctl_io *io, uint64_t lun); #endif /* _CTL_TPC_H */ Index: head/sys/cam/ctl/ctl_tpc_local.c =================================================================== --- head/sys/cam/ctl/ctl_tpc_local.c (revision 328069) +++ head/sys/cam/ctl/ctl_tpc_local.c (revision 328070) @@ -1,330 +1,332 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2014 Alexander Motin * Copyright (c) 2004, 2005 Silicon Graphics International Corp. * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct tpcl_softc { struct ctl_port port; int cur_tag_num; }; static struct tpcl_softc tpcl_softc; static int tpcl_init(void); static int tpcl_shutdown(void); static void tpcl_datamove(union ctl_io *io); static void tpcl_done(union ctl_io *io); static struct ctl_frontend tpcl_frontend = { .name = "tpc", .init = tpcl_init, .shutdown = tpcl_shutdown, }; CTL_FRONTEND_DECLARE(ctltpc, tpcl_frontend); static int tpcl_init(void) { struct tpcl_softc *tsoftc = &tpcl_softc; struct ctl_port *port; struct scsi_transportid_spi *tid; int error, len; memset(tsoftc, 0, sizeof(*tsoftc)); port = &tsoftc->port; port->frontend = &tpcl_frontend; port->port_type = CTL_PORT_INTERNAL; port->num_requested_ctl_io = 100; port->port_name = "tpc"; port->fe_datamove = tpcl_datamove; port->fe_done = tpcl_done; port->targ_port = -1; port->max_initiators = 1; if ((error = ctl_port_register(port)) != 0) { printf("%s: tpc port registration failed\n", __func__); return (error); } len = sizeof(struct scsi_transportid_spi); port->init_devid = malloc(sizeof(struct ctl_devid) + len, M_CTL, M_WAITOK | M_ZERO); port->init_devid->len = len; tid = (struct scsi_transportid_spi *)port->init_devid->data; tid->format_protocol = SCSI_TRN_SPI_FORMAT_DEFAULT | SCSI_PROTO_SPI; scsi_ulto2b(0, tid->scsi_addr); scsi_ulto2b(port->targ_port, tid->rel_trgt_port_id); ctl_port_online(port); return (0); } static int tpcl_shutdown(void) { struct tpcl_softc *tsoftc = &tpcl_softc; struct ctl_port *port = &tsoftc->port; int error; ctl_port_offline(port); if ((error = ctl_port_deregister(port)) != 0) printf("%s: tpc port deregistration failed\n", __func__); return (error); } static void tpcl_datamove(union ctl_io *io) { struct ctl_sg_entry *ext_sglist, *kern_sglist; struct ctl_sg_entry ext_entry, kern_entry; int ext_sg_entries, kern_sg_entries; int ext_sg_start, ext_offset; int len_to_copy; int kern_watermark, ext_watermark; struct ctl_scsiio *ctsio; int i, j; CTL_DEBUG_PRINT(("%s\n", __func__)); ctsio = &io->scsiio; /* * If this is the case, we're probably doing a BBR read and don't * actually need to transfer the data. This will effectively * bit-bucket the data. */ if (ctsio->ext_data_ptr == NULL) goto bailout; /* * To simplify things here, if we have a single buffer, stick it in * a S/G entry and just make it a single entry S/G list. */ if (ctsio->ext_sg_entries > 0) { int len_seen; ext_sglist = (struct ctl_sg_entry *)ctsio->ext_data_ptr; ext_sg_entries = ctsio->ext_sg_entries; ext_sg_start = 0; ext_offset = 0; len_seen = 0; for (i = 0; i < ext_sg_entries; i++) { if ((len_seen + ext_sglist[i].len) >= ctsio->ext_data_filled) { ext_sg_start = i; ext_offset = ctsio->ext_data_filled - len_seen; break; } len_seen += ext_sglist[i].len; } } else { ext_sglist = &ext_entry; ext_sglist->addr = ctsio->ext_data_ptr; ext_sglist->len = ctsio->ext_data_len; ext_sg_entries = 1; ext_sg_start = 0; ext_offset = ctsio->ext_data_filled; } if (ctsio->kern_sg_entries > 0) { kern_sglist = (struct ctl_sg_entry *)ctsio->kern_data_ptr; kern_sg_entries = ctsio->kern_sg_entries; } else { kern_sglist = &kern_entry; kern_sglist->addr = ctsio->kern_data_ptr; kern_sglist->len = ctsio->kern_data_len; kern_sg_entries = 1; } kern_watermark = 0; ext_watermark = ext_offset; for (i = ext_sg_start, j = 0; i < ext_sg_entries && j < kern_sg_entries;) { uint8_t *ext_ptr, *kern_ptr; len_to_copy = min(ext_sglist[i].len - ext_watermark, kern_sglist[j].len - kern_watermark); ext_ptr = (uint8_t *)ext_sglist[i].addr; ext_ptr = ext_ptr + ext_watermark; if (io->io_hdr.flags & CTL_FLAG_BUS_ADDR) { /* * XXX KDM fix this! */ panic("need to implement bus address support"); #if 0 kern_ptr = bus_to_virt(kern_sglist[j].addr); #endif } else kern_ptr = (uint8_t *)kern_sglist[j].addr; kern_ptr = kern_ptr + kern_watermark; if ((ctsio->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) { CTL_DEBUG_PRINT(("%s: copying %d bytes to user\n", __func__, len_to_copy)); CTL_DEBUG_PRINT(("%s: from %p to %p\n", __func__, kern_ptr, ext_ptr)); memcpy(ext_ptr, kern_ptr, len_to_copy); } else { CTL_DEBUG_PRINT(("%s: copying %d bytes from user\n", __func__, len_to_copy)); CTL_DEBUG_PRINT(("%s: from %p to %p\n", __func__, ext_ptr, kern_ptr)); memcpy(kern_ptr, ext_ptr, len_to_copy); } ctsio->ext_data_filled += len_to_copy; ctsio->kern_data_resid -= len_to_copy; ext_watermark += len_to_copy; if (ext_sglist[i].len == ext_watermark) { i++; ext_watermark = 0; } kern_watermark += len_to_copy; if (kern_sglist[j].len == kern_watermark) { j++; kern_watermark = 0; } } CTL_DEBUG_PRINT(("%s: ext_sg_entries: %d, kern_sg_entries: %d\n", __func__, ext_sg_entries, kern_sg_entries)); CTL_DEBUG_PRINT(("%s: ext_data_len = %d, kern_data_len = %d\n", __func__, ctsio->ext_data_len, ctsio->kern_data_len)); bailout: io->scsiio.be_move_done(io); } static void tpcl_done(union ctl_io *io) { tpc_done(io); } uint64_t tpcl_resolve(struct ctl_softc *softc, int init_port, struct scsi_ec_cscd *cscd, uint32_t *ss, uint32_t *ps, uint32_t *pso) { struct scsi_ec_cscd_id *cscdid; struct ctl_port *port; struct ctl_lun *lun; uint64_t lunid = UINT64_MAX; if (cscd->type_code != EC_CSCD_ID || (cscd->luidt_pdt & EC_LUIDT_MASK) != EC_LUIDT_LUN || (cscd->luidt_pdt & EC_NUL) != 0) return (lunid); cscdid = (struct scsi_ec_cscd_id *)cscd; mtx_lock(&softc->ctl_lock); if (init_port >= 0) port = softc->ctl_ports[init_port]; else port = NULL; STAILQ_FOREACH(lun, &softc->lun_list, links) { if (port != NULL && ctl_lun_map_to_port(port, lun->lun) == UINT32_MAX) continue; if (lun->lun_devid == NULL) continue; if (scsi_devid_match(lun->lun_devid->data, lun->lun_devid->len, &cscdid->codeset, cscdid->length + 4) == 0) { lunid = lun->lun; if (ss && lun->be_lun) *ss = lun->be_lun->blocksize; if (ps && lun->be_lun) *ps = lun->be_lun->blocksize << lun->be_lun->pblockexp; if (pso && lun->be_lun) *pso = lun->be_lun->blocksize * lun->be_lun->pblockoff; break; } } mtx_unlock(&softc->ctl_lock); return (lunid); }; union ctl_io * tpcl_alloc_io(void) { struct tpcl_softc *tsoftc = &tpcl_softc; return (ctl_alloc_io(tsoftc->port.ctl_pool_ref)); }; int tpcl_queue(union ctl_io *io, uint64_t lun) { struct tpcl_softc *tsoftc = &tpcl_softc; io->io_hdr.nexus.initid = 0; io->io_hdr.nexus.targ_port = tsoftc->port.targ_port; io->io_hdr.nexus.targ_lun = lun; io->scsiio.tag_num = atomic_fetchadd_int(&tsoftc->cur_tag_num, 1); io->scsiio.ext_data_filled = 0; return (ctl_queue(io)); } Index: head/sys/cam/mmc/mmc.h =================================================================== --- head/sys/cam/mmc/mmc.h (revision 328069) +++ head/sys/cam/mmc/mmc.h (revision 328070) @@ -1,95 +1,97 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2014-2016 Ilya Bakulin. 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. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * Portions of this software may have been developed with reference to * the SD Simplified Specification. The following disclaimer may apply: * * The following conditions apply to the release of the simplified * specification ("Simplified Specification") by the SD Card Association and * the SD Group. The Simplified Specification is a subset of the complete SD * Specification which is owned by the SD Card Association and the SD * Group. This Simplified Specification is provided on a non-confidential * basis subject to the disclaimers below. Any implementation of the * Simplified Specification may require a license from the SD Card * Association, SD Group, SD-3C LLC or other third parties. * * Disclaimers: * * The information contained in the Simplified Specification is presented only * as a standard specification for SD Cards and SD Host/Ancillary products and * is provided "AS-IS" without any representations or warranties of any * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD * Card Association for any damages, any infringements of patents or other * right of the SD Group, SD-3C LLC, the SD Card Association or any third * parties, which may result from its use. No license is granted by * implication, estoppel or otherwise under any patent or other rights of the * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing * herein shall be construed as an obligation by the SD Group, the SD-3C LLC * or the SD Card Association to disclose or distribute any technical * information, know-how or other confidential information to any third party. * * Inspired coded in sys/dev/mmc. Thanks to Warner Losh , * Bernd Walter , and other authors. * * $FreeBSD$ */ #ifndef CAM_MMC_H #define CAM_MMC_H #include /* * This structure describes an MMC/SD card */ struct mmc_params { u_int8_t model[40]; /* Card model */ /* Card OCR */ uint32_t card_ocr; /* OCR of the IO portion of the card */ uint32_t io_ocr; /* Card CID -- raw and parsed */ uint32_t card_cid[4]; struct mmc_cid cid; /* Card CSD -- raw */ uint32_t card_csd[4]; /* Card RCA */ uint16_t card_rca; /* What kind of card is it */ uint32_t card_features; #define CARD_FEATURE_MEMORY 0x1 #define CARD_FEATURE_SDHC 0x1 << 1 #define CARD_FEATURE_SDIO 0x1 << 2 #define CARD_FEATURE_SD20 0x1 << 3 #define CARD_FEATURE_MMC 0x1 << 4 #define CARD_FEATURE_18V 0x1 << 5 uint8_t sdio_func_count; } __packed; #endif Index: head/sys/cam/mmc/mmc_all.h =================================================================== --- head/sys/cam/mmc/mmc_all.h (revision 328069) +++ head/sys/cam/mmc/mmc_all.h (revision 328070) @@ -1,70 +1,72 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2014-2016 Ilya Bakulin. 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. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * Portions of this software may have been developed with reference to * the SD Simplified Specification. The following disclaimer may apply: * * The following conditions apply to the release of the simplified * specification ("Simplified Specification") by the SD Card Association and * the SD Group. The Simplified Specification is a subset of the complete SD * Specification which is owned by the SD Card Association and the SD * Group. This Simplified Specification is provided on a non-confidential * basis subject to the disclaimers below. Any implementation of the * Simplified Specification may require a license from the SD Card * Association, SD Group, SD-3C LLC or other third parties. * * Disclaimers: * * The information contained in the Simplified Specification is presented only * as a standard specification for SD Cards and SD Host/Ancillary products and * is provided "AS-IS" without any representations or warranties of any * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD * Card Association for any damages, any infringements of patents or other * right of the SD Group, SD-3C LLC, the SD Card Association or any third * parties, which may result from its use. No license is granted by * implication, estoppel or otherwise under any patent or other rights of the * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing * herein shall be construed as an obligation by the SD Group, the SD-3C LLC * or the SD Card Association to disclose or distribute any technical * information, know-how or other confidential information to any third party. * * $FreeBSD$ */ /* * MMC function that should be visible to the CAM subsystem * and are somehow useful should be declared here * * Like in other *_all.h, it's also a nice place to include * some other transport-specific headers. */ #ifndef CAM_MMC_ALL_H #define CAM_MMC_ALL_H #include #include void mmc_print_ident(struct mmc_params *ident_data); #endif Index: head/sys/cam/mmc/mmc_da.c =================================================================== --- head/sys/cam/mmc/mmc_da.c (revision 328069) +++ head/sys/cam/mmc/mmc_da.c (revision 328070) @@ -1,1427 +1,1429 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2006 Bernd Walter * Copyright (c) 2006 M. Warner Losh * Copyright (c) 2009 Alexander Motin * Copyright (c) 2015-2017 Ilya Bakulin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * Some code derived from the sys/dev/mmc and sys/cam/ata * Thanks to Warner Losh , Alexander Motin * Bernd Walter , and other authors. */ #include __FBSDID("$FreeBSD$"); //#include "opt_sdda.h" #include #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for PRIu64 */ #endif /* _KERNEL */ #ifndef _KERNEL #include #include #endif /* _KERNEL */ #include #include #include #include #include #include #include #include #include #include #include #include /* geometry translation */ #ifdef _KERNEL typedef enum { SDDA_FLAG_OPEN = 0x0002, SDDA_FLAG_DIRTY = 0x0004 } sdda_flags; typedef enum { SDDA_STATE_INIT, SDDA_STATE_INVALID, SDDA_STATE_NORMAL } sdda_state; struct sdda_softc { struct bio_queue_head bio_queue; int outstanding_cmds; /* Number of active commands */ int refcount; /* Active xpt_action() calls */ sdda_state state; sdda_flags flags; struct mmc_data *mmcdata; // sdda_quirks quirks; struct task start_init_task; struct disk *disk; uint32_t raw_csd[4]; uint8_t raw_ext_csd[512]; /* MMC only? */ struct mmc_csd csd; struct mmc_cid cid; struct mmc_scr scr; /* Calculated from CSD */ uint64_t sector_count; uint64_t mediasize; /* Calculated from CID */ char card_id_string[64];/* Formatted CID info (serial, MFG, etc) */ char card_sn_string[16];/* Formatted serial # for disk->d_ident */ /* Determined from CSD + is highspeed card*/ uint32_t card_f_max; }; #define ccb_bp ppriv_ptr1 static disk_strategy_t sddastrategy; static periph_init_t sddainit; static void sddaasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static periph_ctor_t sddaregister; static periph_dtor_t sddacleanup; static periph_start_t sddastart; static periph_oninv_t sddaoninvalidate; static void sddadone(struct cam_periph *periph, union ccb *done_ccb); static int sddaerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); static uint16_t get_rca(struct cam_periph *periph); static cam_status sdda_hook_into_geom(struct cam_periph *periph); static void sdda_start_init(void *context, union ccb *start_ccb); static void sdda_start_init_task(void *context, int pending); static struct periph_driver sddadriver = { sddainit, "sdda", TAILQ_HEAD_INITIALIZER(sddadriver.units), /* generation */ 0 }; PERIPHDRIVER_DECLARE(sdda, sddadriver); static MALLOC_DEFINE(M_SDDA, "sd_da", "sd_da buffers"); static const int exp[8] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 }; static const int mant[16] = { 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 }; static const int cur_min[8] = { 500, 1000, 5000, 10000, 25000, 35000, 60000, 100000 }; static const int cur_max[8] = { 1000, 5000, 10000, 25000, 35000, 45000, 800000, 200000 }; static uint16_t get_rca(struct cam_periph *periph) { return periph->path->device->mmc_ident_data.card_rca; } static uint32_t mmc_get_bits(uint32_t *bits, int bit_len, int start, int size) { const int i = (bit_len / 32) - (start / 32) - 1; const int shift = start & 31; uint32_t retval = bits[i] >> shift; if (size + shift > 32) retval |= bits[i - 1] << (32 - shift); return (retval & ((1llu << size) - 1)); } static void mmc_decode_csd_sd(uint32_t *raw_csd, struct mmc_csd *csd) { int v; int m; int e; memset(csd, 0, sizeof(*csd)); csd->csd_structure = v = mmc_get_bits(raw_csd, 128, 126, 2); if (v == 0) { m = mmc_get_bits(raw_csd, 128, 115, 4); e = mmc_get_bits(raw_csd, 128, 112, 3); csd->tacc = (exp[e] * mant[m] + 9) / 10; csd->nsac = mmc_get_bits(raw_csd, 128, 104, 8) * 100; m = mmc_get_bits(raw_csd, 128, 99, 4); e = mmc_get_bits(raw_csd, 128, 96, 3); csd->tran_speed = exp[e] * 10000 * mant[m]; csd->ccc = mmc_get_bits(raw_csd, 128, 84, 12); csd->read_bl_len = 1 << mmc_get_bits(raw_csd, 128, 80, 4); csd->read_bl_partial = mmc_get_bits(raw_csd, 128, 79, 1); csd->write_blk_misalign = mmc_get_bits(raw_csd, 128, 78, 1); csd->read_blk_misalign = mmc_get_bits(raw_csd, 128, 77, 1); csd->dsr_imp = mmc_get_bits(raw_csd, 128, 76, 1); csd->vdd_r_curr_min = cur_min[mmc_get_bits(raw_csd, 128, 59, 3)]; csd->vdd_r_curr_max = cur_max[mmc_get_bits(raw_csd, 128, 56, 3)]; csd->vdd_w_curr_min = cur_min[mmc_get_bits(raw_csd, 128, 53, 3)]; csd->vdd_w_curr_max = cur_max[mmc_get_bits(raw_csd, 128, 50, 3)]; m = mmc_get_bits(raw_csd, 128, 62, 12); e = mmc_get_bits(raw_csd, 128, 47, 3); csd->capacity = ((1 + m) << (e + 2)) * csd->read_bl_len; csd->erase_blk_en = mmc_get_bits(raw_csd, 128, 46, 1); csd->erase_sector = mmc_get_bits(raw_csd, 128, 39, 7) + 1; csd->wp_grp_size = mmc_get_bits(raw_csd, 128, 32, 7); csd->wp_grp_enable = mmc_get_bits(raw_csd, 128, 31, 1); csd->r2w_factor = 1 << mmc_get_bits(raw_csd, 128, 26, 3); csd->write_bl_len = 1 << mmc_get_bits(raw_csd, 128, 22, 4); csd->write_bl_partial = mmc_get_bits(raw_csd, 128, 21, 1); } else if (v == 1) { m = mmc_get_bits(raw_csd, 128, 115, 4); e = mmc_get_bits(raw_csd, 128, 112, 3); csd->tacc = (exp[e] * mant[m] + 9) / 10; csd->nsac = mmc_get_bits(raw_csd, 128, 104, 8) * 100; m = mmc_get_bits(raw_csd, 128, 99, 4); e = mmc_get_bits(raw_csd, 128, 96, 3); csd->tran_speed = exp[e] * 10000 * mant[m]; csd->ccc = mmc_get_bits(raw_csd, 128, 84, 12); csd->read_bl_len = 1 << mmc_get_bits(raw_csd, 128, 80, 4); csd->read_bl_partial = mmc_get_bits(raw_csd, 128, 79, 1); csd->write_blk_misalign = mmc_get_bits(raw_csd, 128, 78, 1); csd->read_blk_misalign = mmc_get_bits(raw_csd, 128, 77, 1); csd->dsr_imp = mmc_get_bits(raw_csd, 128, 76, 1); csd->capacity = ((uint64_t)mmc_get_bits(raw_csd, 128, 48, 22) + 1) * 512 * 1024; csd->erase_blk_en = mmc_get_bits(raw_csd, 128, 46, 1); csd->erase_sector = mmc_get_bits(raw_csd, 128, 39, 7) + 1; csd->wp_grp_size = mmc_get_bits(raw_csd, 128, 32, 7); csd->wp_grp_enable = mmc_get_bits(raw_csd, 128, 31, 1); csd->r2w_factor = 1 << mmc_get_bits(raw_csd, 128, 26, 3); csd->write_bl_len = 1 << mmc_get_bits(raw_csd, 128, 22, 4); csd->write_bl_partial = mmc_get_bits(raw_csd, 128, 21, 1); } else panic("unknown SD CSD version"); } static void mmc_decode_csd_mmc(uint32_t *raw_csd, struct mmc_csd *csd) { int m; int e; memset(csd, 0, sizeof(*csd)); csd->csd_structure = mmc_get_bits(raw_csd, 128, 126, 2); csd->spec_vers = mmc_get_bits(raw_csd, 128, 122, 4); m = mmc_get_bits(raw_csd, 128, 115, 4); e = mmc_get_bits(raw_csd, 128, 112, 3); csd->tacc = exp[e] * mant[m] + 9 / 10; csd->nsac = mmc_get_bits(raw_csd, 128, 104, 8) * 100; m = mmc_get_bits(raw_csd, 128, 99, 4); e = mmc_get_bits(raw_csd, 128, 96, 3); csd->tran_speed = exp[e] * 10000 * mant[m]; csd->ccc = mmc_get_bits(raw_csd, 128, 84, 12); csd->read_bl_len = 1 << mmc_get_bits(raw_csd, 128, 80, 4); csd->read_bl_partial = mmc_get_bits(raw_csd, 128, 79, 1); csd->write_blk_misalign = mmc_get_bits(raw_csd, 128, 78, 1); csd->read_blk_misalign = mmc_get_bits(raw_csd, 128, 77, 1); csd->dsr_imp = mmc_get_bits(raw_csd, 128, 76, 1); csd->vdd_r_curr_min = cur_min[mmc_get_bits(raw_csd, 128, 59, 3)]; csd->vdd_r_curr_max = cur_max[mmc_get_bits(raw_csd, 128, 56, 3)]; csd->vdd_w_curr_min = cur_min[mmc_get_bits(raw_csd, 128, 53, 3)]; csd->vdd_w_curr_max = cur_max[mmc_get_bits(raw_csd, 128, 50, 3)]; m = mmc_get_bits(raw_csd, 128, 62, 12); e = mmc_get_bits(raw_csd, 128, 47, 3); csd->capacity = ((1 + m) << (e + 2)) * csd->read_bl_len; csd->erase_blk_en = 0; csd->erase_sector = (mmc_get_bits(raw_csd, 128, 42, 5) + 1) * (mmc_get_bits(raw_csd, 128, 37, 5) + 1); csd->wp_grp_size = mmc_get_bits(raw_csd, 128, 32, 5); csd->wp_grp_enable = mmc_get_bits(raw_csd, 128, 31, 1); csd->r2w_factor = 1 << mmc_get_bits(raw_csd, 128, 26, 3); csd->write_bl_len = 1 << mmc_get_bits(raw_csd, 128, 22, 4); csd->write_bl_partial = mmc_get_bits(raw_csd, 128, 21, 1); } static void mmc_decode_cid_sd(uint32_t *raw_cid, struct mmc_cid *cid) { int i; /* There's no version info, so we take it on faith */ memset(cid, 0, sizeof(*cid)); cid->mid = mmc_get_bits(raw_cid, 128, 120, 8); cid->oid = mmc_get_bits(raw_cid, 128, 104, 16); for (i = 0; i < 5; i++) cid->pnm[i] = mmc_get_bits(raw_cid, 128, 96 - i * 8, 8); cid->pnm[5] = 0; cid->prv = mmc_get_bits(raw_cid, 128, 56, 8); cid->psn = mmc_get_bits(raw_cid, 128, 24, 32); cid->mdt_year = mmc_get_bits(raw_cid, 128, 12, 8) + 2000; cid->mdt_month = mmc_get_bits(raw_cid, 128, 8, 4); } static void mmc_decode_cid_mmc(uint32_t *raw_cid, struct mmc_cid *cid) { int i; /* There's no version info, so we take it on faith */ memset(cid, 0, sizeof(*cid)); cid->mid = mmc_get_bits(raw_cid, 128, 120, 8); cid->oid = mmc_get_bits(raw_cid, 128, 104, 8); for (i = 0; i < 6; i++) cid->pnm[i] = mmc_get_bits(raw_cid, 128, 96 - i * 8, 8); cid->pnm[6] = 0; cid->prv = mmc_get_bits(raw_cid, 128, 48, 8); cid->psn = mmc_get_bits(raw_cid, 128, 16, 32); cid->mdt_month = mmc_get_bits(raw_cid, 128, 12, 4); cid->mdt_year = mmc_get_bits(raw_cid, 128, 8, 4) + 1997; } static void mmc_format_card_id_string(struct sdda_softc *sc, struct mmc_params *mmcp) { char oidstr[8]; uint8_t c1; uint8_t c2; /* * Format a card ID string for use by the mmcsd driver, it's what * appears between the <> in the following: * mmcsd0: 968MB at mmc0 * 22.5MHz/4bit/128-block * * Also format just the card serial number, which the mmcsd driver will * use as the disk->d_ident string. * * The card_id_string in mmc_ivars is currently allocated as 64 bytes, * and our max formatted length is currently 55 bytes if every field * contains the largest value. * * Sometimes the oid is two printable ascii chars; when it's not, * format it as 0xnnnn instead. */ c1 = (sc->cid.oid >> 8) & 0x0ff; c2 = sc->cid.oid & 0x0ff; if (c1 > 0x1f && c1 < 0x7f && c2 > 0x1f && c2 < 0x7f) snprintf(oidstr, sizeof(oidstr), "%c%c", c1, c2); else snprintf(oidstr, sizeof(oidstr), "0x%04x", sc->cid.oid); snprintf(sc->card_sn_string, sizeof(sc->card_sn_string), "%08X", sc->cid.psn); snprintf(sc->card_id_string, sizeof(sc->card_id_string), "%s%s %s %d.%d SN %08X MFG %02d/%04d by %d %s", mmcp->card_features & CARD_FEATURE_MMC ? "MMC" : "SD", mmcp->card_features & CARD_FEATURE_SDHC ? "HC" : "", sc->cid.pnm, sc->cid.prv >> 4, sc->cid.prv & 0x0f, sc->cid.psn, sc->cid.mdt_month, sc->cid.mdt_year, sc->cid.mid, oidstr); } static int sddaopen(struct disk *dp) { struct cam_periph *periph; struct sdda_softc *softc; int error; periph = (struct cam_periph *)dp->d_drv1; if (cam_periph_acquire(periph) != CAM_REQ_CMP) { return(ENXIO); } cam_periph_lock(periph); if ((error = cam_periph_hold(periph, PRIBIO|PCATCH)) != 0) { cam_periph_unlock(periph); cam_periph_release(periph); return (error); } CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sddaopen\n")); softc = (struct sdda_softc *)periph->softc; softc->flags |= SDDA_FLAG_OPEN; cam_periph_unhold(periph); cam_periph_unlock(periph); return (0); } static int sddaclose(struct disk *dp) { struct cam_periph *periph; struct sdda_softc *softc; // union ccb *ccb; // int error; periph = (struct cam_periph *)dp->d_drv1; softc = (struct sdda_softc *)periph->softc; softc->flags &= ~SDDA_FLAG_OPEN; cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sddaclose\n")); while (softc->refcount != 0) cam_periph_sleep(periph, &softc->refcount, PRIBIO, "sddaclose", 1); cam_periph_unlock(periph); cam_periph_release(periph); return (0); } static void sddaschedule(struct cam_periph *periph) { struct sdda_softc *softc = (struct sdda_softc *)periph->softc; /* Check if we have more work to do. */ if (bioq_first(&softc->bio_queue)) { xpt_schedule(periph, CAM_PRIORITY_NORMAL); } } /* * 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 sddastrategy(struct bio *bp) { struct cam_periph *periph; struct sdda_softc *softc; periph = (struct cam_periph *)bp->bio_disk->d_drv1; softc = (struct sdda_softc *)periph->softc; cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sddastrategy(%p)\n", bp)); /* * If the device has been made invalid, error out */ if ((periph->flags & CAM_PERIPH_INVALID) != 0) { cam_periph_unlock(periph); biofinish(bp, NULL, ENXIO); return; } /* * Place it in the queue of disk activities for this disk */ bioq_disksort(&softc->bio_queue, bp); /* * Schedule ourselves for performing the work. */ sddaschedule(periph); cam_periph_unlock(periph); return; } static void sddainit(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, sddaasync, NULL, NULL); if (status != CAM_REQ_CMP) { printf("sdda: Failed to attach master async callback " "due to status 0x%x!\n", status); } } /* * Callback from GEOM, called when it has finished cleaning up its * resources. */ static void sddadiskgonecb(struct disk *dp) { struct cam_periph *periph; periph = (struct cam_periph *)dp->d_drv1; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sddadiskgonecb\n")); cam_periph_release(periph); } static void sddaoninvalidate(struct cam_periph *periph) { struct sdda_softc *softc; softc = (struct sdda_softc *)periph->softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sddaoninvalidate\n")); /* * De-register any async callbacks. */ xpt_register_async(0, sddaasync, periph, periph->path); /* * Return all queued I/O with ENXIO. * XXX Handle any transactions queued to the card * with XPT_ABORT_CCB. */ CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("bioq_flush start\n")); bioq_flush(&softc->bio_queue, NULL, ENXIO); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("bioq_flush end\n")); disk_gone(softc->disk); } static void sddacleanup(struct cam_periph *periph) { struct sdda_softc *softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sddacleanup\n")); softc = (struct sdda_softc *)periph->softc; cam_periph_unlock(periph); disk_destroy(softc->disk); free(softc, M_DEVBUF); cam_periph_lock(periph); } static void sddaasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct ccb_getdev cgd; struct cam_periph *periph; struct sdda_softc *softc; periph = (struct cam_periph *)callback_arg; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("sddaasync(code=%d)\n", code)); switch (code) { case AC_FOUND_DEVICE: { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("=> AC_FOUND_DEVICE\n")); struct ccb_getdev *cgd; cam_status status; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) break; if (cgd->protocol != PROTO_MMCSD) break; if (!(path->device->mmc_ident_data.card_features & CARD_FEATURE_MEMORY)) { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("No memory on the card!\n")); break; } /* * Allocate a peripheral instance for * this device and start the probe * process. */ status = cam_periph_alloc(sddaregister, sddaoninvalidate, sddacleanup, sddastart, "sdda", CAM_PERIPH_BIO, path, sddaasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) printf("sddaasync: Unable to attach to new device " "due to status 0x%x\n", status); break; } case AC_GETDEV_CHANGED: { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("=> AC_GETDEV_CHANGED\n")); softc = (struct sdda_softc *)periph->softc; xpt_setup_ccb(&cgd.ccb_h, periph->path, CAM_PRIORITY_NORMAL); cgd.ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)&cgd); cam_periph_async(periph, code, path, arg); break; } case AC_ADVINFO_CHANGED: { uintptr_t buftype; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("=> AC_ADVINFO_CHANGED\n")); buftype = (uintptr_t)arg; if (buftype == CDAI_TYPE_PHYS_PATH) { struct sdda_softc *softc; softc = periph->softc; disk_attr_changed(softc->disk, "GEOM::physpath", M_NOWAIT); } break; } case AC_SENT_BDR: case AC_BUS_RESET: { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("AC_BUS_RESET")); } default: CAM_DEBUG(path, CAM_DEBUG_TRACE, ("=> default?!\n")); cam_periph_async(periph, code, path, arg); break; } } static int sddagetattr(struct bio *bp) { int ret; struct cam_periph *periph; 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 cam_status sddaregister(struct cam_periph *periph, void *arg) { struct sdda_softc *softc; // struct ccb_pathinq cpi; struct ccb_getdev *cgd; // char announce_buf[80], buf1[32]; // caddr_t match; union ccb *request_ccb; /* CCB representing the probe request */ CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sddaregister\n")); cgd = (struct ccb_getdev *)arg; if (cgd == NULL) { printf("sddaregister: no getdev CCB, can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (struct sdda_softc *)malloc(sizeof(*softc), M_DEVBUF, M_NOWAIT|M_ZERO); if (softc == NULL) { printf("sddaregister: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } bioq_init(&softc->bio_queue); softc->state = SDDA_STATE_INIT; softc->mmcdata = (struct mmc_data *) malloc(sizeof(struct mmc_data), M_DEVBUF, M_NOWAIT|M_ZERO); periph->softc = softc; request_ccb = (union ccb*) arg; xpt_schedule(periph, CAM_PRIORITY_XPT); TASK_INIT(&softc->start_init_task, 0, sdda_start_init_task, periph); taskqueue_enqueue(taskqueue_thread, &softc->start_init_task); return (CAM_REQ_CMP); } static cam_status sdda_hook_into_geom(struct cam_periph *periph) { struct sdda_softc *softc; struct ccb_pathinq cpi; struct ccb_getdev cgd; u_int maxio; softc = (struct sdda_softc*) periph->softc; xpt_path_inq(&cpi, periph->path); bzero(&cgd, sizeof(cgd)); xpt_setup_ccb(&cgd.ccb_h, periph->path, CAM_PRIORITY_NONE); cpi.ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)&cgd); /* * Register this media as a disk */ (void)cam_periph_hold(periph, PRIBIO); cam_periph_unlock(periph); softc->disk = disk_alloc(); softc->disk->d_rotation_rate = 0; softc->disk->d_devstat = devstat_new_entry(periph->periph_name, periph->unit_number, 512, DEVSTAT_ALL_SUPPORTED, DEVSTAT_TYPE_DIRECT | XPORT_DEVSTAT_TYPE(cpi.transport), DEVSTAT_PRIORITY_DISK); softc->disk->d_open = sddaopen; softc->disk->d_close = sddaclose; softc->disk->d_strategy = sddastrategy; softc->disk->d_getattr = sddagetattr; // softc->disk->d_dump = sddadump; softc->disk->d_gone = sddadiskgonecb; softc->disk->d_name = "sdda"; softc->disk->d_drv1 = periph; maxio = cpi.maxio; /* Honor max I/O size of SIM */ if (maxio == 0) maxio = DFLTPHYS; /* traditional default */ else if (maxio > MAXPHYS) maxio = MAXPHYS; /* for safety */ softc->disk->d_maxsize = maxio; softc->disk->d_unit = periph->unit_number; softc->disk->d_flags = DISKFLAG_CANDELETE; strlcpy(softc->disk->d_descr, softc->card_id_string, MIN(sizeof(softc->disk->d_descr), sizeof(softc->card_id_string))); strlcpy(softc->disk->d_ident, softc->card_sn_string, MIN(sizeof(softc->disk->d_ident), sizeof(softc->card_sn_string))); 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; softc->disk->d_sectorsize = 512; softc->disk->d_mediasize = softc->mediasize; softc->disk->d_stripesize = 0; softc->disk->d_fwsectors = 0; softc->disk->d_fwheads = 0; /* * Acquire a reference to the periph before we register with GEOM. * We'll release this reference once GEOM calls us back (via * sddadiskgonecb()) telling us that our provider has been freed. */ if (cam_periph_acquire(periph) != CAM_REQ_CMP) { 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); cam_periph_unhold(periph); xpt_announce_periph(periph, softc->card_id_string); /* * Add async callbacks for bus reset and * bus device reset calls. 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_GETDEV_CHANGED | AC_ADVINFO_CHANGED, sddaasync, periph, periph->path); return(CAM_REQ_CMP); } static int mmc_exec_app_cmd(struct cam_periph *periph, union ccb *ccb, struct mmc_command *cmd) { int err; /* Send APP_CMD first */ memset(&ccb->mmcio.cmd, 0, sizeof(struct mmc_command)); memset(&ccb->mmcio.stop, 0, sizeof(struct mmc_command)); cam_fill_mmcio(&ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ CAM_DIR_NONE, /*mmc_opcode*/ MMC_APP_CMD, /*mmc_arg*/ get_rca(periph) << 16, /*mmc_flags*/ MMC_RSP_R1 | MMC_CMD_AC, /*mmc_data*/ NULL, /*timeout*/ 0); err = cam_periph_runccb(ccb, sddaerror, CAM_FLAG_NONE, /*sense_flags*/0, NULL); if (err != 0) return err; if (!(ccb->mmcio.cmd.resp[0] & R1_APP_CMD)) return MMC_ERR_FAILED; /* Now exec actual command */ int flags = 0; if (cmd->data != NULL) { ccb->mmcio.cmd.data = cmd->data; if (cmd->data->flags & MMC_DATA_READ) flags |= CAM_DIR_IN; if (cmd->data->flags & MMC_DATA_WRITE) flags |= CAM_DIR_OUT; } else flags = CAM_DIR_NONE; cam_fill_mmcio(&ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ flags, /*mmc_opcode*/ cmd->opcode, /*mmc_arg*/ cmd->arg, /*mmc_flags*/ cmd->flags, /*mmc_data*/ cmd->data, /*timeout*/ 0); err = cam_periph_runccb(ccb, sddaerror, CAM_FLAG_NONE, /*sense_flags*/0, NULL); memcpy(cmd->resp, ccb->mmcio.cmd.resp, sizeof(cmd->resp)); cmd->error = ccb->mmcio.cmd.error; if (err != 0) return err; return 0; } static int mmc_app_get_scr(struct cam_periph *periph, union ccb *ccb, uint32_t *rawscr) { int err; struct mmc_command cmd; struct mmc_data d; memset(&cmd, 0, sizeof(cmd)); memset(rawscr, 0, 8); cmd.opcode = ACMD_SEND_SCR; cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; cmd.arg = 0; d.data = rawscr; d.len = 8; d.flags = MMC_DATA_READ; cmd.data = &d; err = mmc_exec_app_cmd(periph, ccb, &cmd); rawscr[0] = be32toh(rawscr[0]); rawscr[1] = be32toh(rawscr[1]); return (err); } static int mmc_send_ext_csd(struct cam_periph *periph, union ccb *ccb, uint8_t *rawextcsd, size_t buf_len) { int err; struct mmc_data d; KASSERT(buf_len == 512, ("Buffer for ext csd must be 512 bytes")); d.data = rawextcsd; d.len = buf_len; d.flags = MMC_DATA_READ; memset(d.data, 0, d.len); cam_fill_mmcio(&ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ CAM_DIR_IN, /*mmc_opcode*/ MMC_SEND_EXT_CSD, /*mmc_arg*/ 0, /*mmc_flags*/ MMC_RSP_R1 | MMC_CMD_ADTC, /*mmc_data*/ &d, /*timeout*/ 0); err = cam_periph_runccb(ccb, sddaerror, CAM_FLAG_NONE, /*sense_flags*/0, NULL); if (err != 0) return err; if (!(ccb->mmcio.cmd.resp[0] & R1_APP_CMD)) return MMC_ERR_FAILED; return MMC_ERR_NONE; } static void mmc_app_decode_scr(uint32_t *raw_scr, struct mmc_scr *scr) { unsigned int scr_struct; memset(scr, 0, sizeof(*scr)); scr_struct = mmc_get_bits(raw_scr, 64, 60, 4); if (scr_struct != 0) { printf("Unrecognised SCR structure version %d\n", scr_struct); return; } scr->sda_vsn = mmc_get_bits(raw_scr, 64, 56, 4); scr->bus_widths = mmc_get_bits(raw_scr, 64, 48, 4); } static int mmc_switch(struct cam_periph *periph, union ccb *ccb, uint8_t set, uint8_t index, uint8_t value) { int arg = (MMC_SWITCH_FUNC_WR << 24) | (index << 16) | (value << 8) | set; cam_fill_mmcio(&ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ CAM_DIR_NONE, /*mmc_opcode*/ MMC_SWITCH_FUNC, /*mmc_arg*/ arg, /*mmc_flags*/ MMC_RSP_R1B | MMC_CMD_AC, /*mmc_data*/ NULL, /*timeout*/ 0); cam_periph_runccb(ccb, sddaerror, CAM_FLAG_NONE, /*sense_flags*/0, NULL); if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)) { if (ccb->mmcio.cmd.error != 0) { CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: MMC command failed", __func__)); return EIO; } return 0; /* Normal return */ } else { CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: CAM request failed\n", __func__)); return EIO; } } static int mmc_sd_switch(struct cam_periph *periph, union ccb *ccb, uint8_t mode, uint8_t grp, uint8_t value, uint8_t *res) { struct mmc_data mmc_d; memset(res, 0, 64); mmc_d.len = 64; mmc_d.data = res; mmc_d.flags = MMC_DATA_READ; cam_fill_mmcio(&ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ CAM_DIR_IN, /*mmc_opcode*/ SD_SWITCH_FUNC, /*mmc_arg*/ mode << 31, /*mmc_flags*/ MMC_RSP_R1 | MMC_CMD_ADTC, /*mmc_data*/ &mmc_d, /*timeout*/ 0); cam_periph_runccb(ccb, sddaerror, CAM_FLAG_NONE, /*sense_flags*/0, NULL); if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)) { if (ccb->mmcio.cmd.error != 0) { CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: MMC command failed", __func__)); return EIO; } return 0; /* Normal return */ } else { CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: CAM request failed\n", __func__)); return EIO; } } static int mmc_set_timing(struct cam_periph *periph, union ccb *ccb, enum mmc_bus_timing timing) { u_char switch_res[64]; int err; uint8_t value; struct mmc_params *mmcp = &periph->path->device->mmc_ident_data; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("mmc_set_timing(timing=%d)", timing)); switch (timing) { case bus_timing_normal: value = 0; break; case bus_timing_hs: value = 1; break; default: return (MMC_ERR_INVALID); } if (mmcp->card_features & CARD_FEATURE_MMC) { err = mmc_switch(periph, ccb, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, value); } else { err = mmc_sd_switch(periph, ccb, SD_SWITCH_MODE_SET, SD_SWITCH_GROUP1, value, switch_res); } /* Set high-speed timing on the host */ struct ccb_trans_settings_mmc *cts; cts = &ccb->cts.proto_specific.mmc; ccb->ccb_h.func_code = XPT_SET_TRAN_SETTINGS; ccb->ccb_h.flags = CAM_DIR_NONE; ccb->ccb_h.retry_count = 0; ccb->ccb_h.timeout = 100; ccb->ccb_h.cbfcnp = NULL; cts->ios.timing = timing; cts->ios_valid = MMC_BT; xpt_action(ccb); return (err); } static void sdda_start_init_task(void *context, int pending) { union ccb *new_ccb; struct cam_periph *periph; periph = (struct cam_periph *)context; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sdda_start_init_task\n")); new_ccb = xpt_alloc_ccb(); xpt_setup_ccb(&new_ccb->ccb_h, periph->path, CAM_PRIORITY_NONE); cam_periph_lock(periph); sdda_start_init(context, new_ccb); cam_periph_unlock(periph); xpt_free_ccb(new_ccb); } static void sdda_set_bus_width(struct cam_periph *periph, union ccb *ccb, int width) { struct mmc_params *mmcp = &periph->path->device->mmc_ident_data; int err; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sdda_set_bus_width\n")); /* First set for the card, then for the host */ if (mmcp->card_features & CARD_FEATURE_MMC) { uint8_t value; switch (width) { case bus_width_1: value = EXT_CSD_BUS_WIDTH_1; break; case bus_width_4: value = EXT_CSD_BUS_WIDTH_4; break; case bus_width_8: value = EXT_CSD_BUS_WIDTH_8; break; default: panic("Invalid bus width %d", width); } err = mmc_switch(periph, ccb, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, value); } else { /* For SD cards we send ACMD6 with the required bus width in arg */ struct mmc_command cmd; memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = ACMD_SET_BUS_WIDTH; cmd.arg = width; cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_exec_app_cmd(periph, ccb, &cmd); } if (err != MMC_ERR_NONE) { CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, ("Error %d when setting bus width on the card\n", err)); return; } /* Now card is done, set the host to the same width */ struct ccb_trans_settings_mmc *cts; cts = &ccb->cts.proto_specific.mmc; ccb->ccb_h.func_code = XPT_SET_TRAN_SETTINGS; ccb->ccb_h.flags = CAM_DIR_NONE; ccb->ccb_h.retry_count = 0; ccb->ccb_h.timeout = 100; ccb->ccb_h.cbfcnp = NULL; cts->ios.bus_width = width; cts->ios_valid = MMC_BW; xpt_action(ccb); } static inline const char *bus_width_str(enum mmc_bus_width w) { switch (w) { case bus_width_1: return "1-bit"; case bus_width_4: return "4-bit"; case bus_width_8: return "8-bit"; } } static void sdda_start_init(void *context, union ccb *start_ccb) { struct cam_periph *periph; periph = (struct cam_periph *)context; int err; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sdda_start_init\n")); /* periph was held for us when this task was enqueued */ if ((periph->flags & CAM_PERIPH_INVALID) != 0) { cam_periph_release(periph); return; } struct sdda_softc *softc = (struct sdda_softc *)periph->softc; //struct ccb_mmcio *mmcio = &start_ccb->mmcio; struct mmc_params *mmcp = &periph->path->device->mmc_ident_data; struct cam_ed *device = periph->path->device; if (mmcp->card_features & CARD_FEATURE_MMC) { mmc_decode_csd_mmc(mmcp->card_csd, &softc->csd); mmc_decode_cid_mmc(mmcp->card_cid, &softc->cid); if (softc->csd.spec_vers >= 4) err = mmc_send_ext_csd(periph, start_ccb, (uint8_t *)&softc->raw_ext_csd, sizeof(softc->raw_ext_csd)); } else { mmc_decode_csd_sd(mmcp->card_csd, &softc->csd); mmc_decode_cid_sd(mmcp->card_cid, &softc->cid); } softc->sector_count = softc->csd.capacity / 512; softc->mediasize = softc->csd.capacity; /* MMC >= 4.x have EXT_CSD that has its own opinion about capacity */ if (softc->csd.spec_vers >= 4) { uint32_t sec_count = softc->raw_ext_csd[EXT_CSD_SEC_CNT] + (softc->raw_ext_csd[EXT_CSD_SEC_CNT + 1] << 8) + (softc->raw_ext_csd[EXT_CSD_SEC_CNT + 2] << 16) + (softc->raw_ext_csd[EXT_CSD_SEC_CNT + 3] << 24); if (sec_count != 0) { softc->sector_count = sec_count; softc->mediasize = softc->sector_count * 512; /* FIXME: there should be a better name for this option...*/ mmcp->card_features |= CARD_FEATURE_SDHC; } } CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, ("Capacity: %"PRIu64", sectors: %"PRIu64"\n", softc->mediasize, softc->sector_count)); mmc_format_card_id_string(softc, mmcp); /* Update info for CAM */ device->serial_num_len = strlen(softc->card_sn_string); device->serial_num = (u_int8_t *)malloc((device->serial_num_len + 1), M_CAMXPT, M_NOWAIT); strlcpy(device->serial_num, softc->card_sn_string, device->serial_num_len); device->device_id_len = strlen(softc->card_id_string); device->device_id = (u_int8_t *)malloc((device->device_id_len + 1), M_CAMXPT, M_NOWAIT); strlcpy(device->device_id, softc->card_id_string, device->device_id_len); strlcpy(mmcp->model, softc->card_id_string, sizeof(mmcp->model)); /* Set the clock frequency that the card can handle */ struct ccb_trans_settings_mmc *cts; cts = &start_ccb->cts.proto_specific.mmc; /* First, get the host's max freq */ start_ccb->ccb_h.func_code = XPT_GET_TRAN_SETTINGS; start_ccb->ccb_h.flags = CAM_DIR_NONE; start_ccb->ccb_h.retry_count = 0; start_ccb->ccb_h.timeout = 100; start_ccb->ccb_h.cbfcnp = NULL; xpt_action(start_ccb); if (start_ccb->ccb_h.status != CAM_REQ_CMP) panic("Cannot get max host freq"); int host_f_max = cts->host_f_max; uint32_t host_caps = cts->host_caps; if (cts->ios.bus_width != bus_width_1) panic("Bus width in ios is not 1-bit"); /* Now check if the card supports High-speed */ softc->card_f_max = softc->csd.tran_speed; if (host_caps & MMC_CAP_HSPEED) { /* Find out if the card supports High speed timing */ if (mmcp->card_features & CARD_FEATURE_SD20) { /* Get and decode SCR */ uint32_t rawscr; uint8_t res[64]; if (mmc_app_get_scr(periph, start_ccb, &rawscr)) { CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, ("Cannot get SCR\n")); goto finish_hs_tests; } mmc_app_decode_scr(&rawscr, &softc->scr); if ((softc->scr.sda_vsn >= 1) && (softc->csd.ccc & (1<<10))) { mmc_sd_switch(periph, start_ccb, SD_SWITCH_MODE_CHECK, SD_SWITCH_GROUP1, SD_SWITCH_NOCHANGE, res); if (res[13] & 2) { CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, ("Card supports HS\n")); softc->card_f_max = SD_HS_MAX; } } else { CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, ("Not trying the switch\n")); goto finish_hs_tests; } } if (mmcp->card_features & CARD_FEATURE_MMC && softc->csd.spec_vers >= 4) { if (softc->raw_ext_csd[EXT_CSD_CARD_TYPE] & EXT_CSD_CARD_TYPE_HS_52) softc->card_f_max = MMC_TYPE_HS_52_MAX; else if (softc->raw_ext_csd[EXT_CSD_CARD_TYPE] & EXT_CSD_CARD_TYPE_HS_26) softc->card_f_max = MMC_TYPE_HS_26_MAX; } } int f_max; finish_hs_tests: f_max = min(host_f_max, softc->card_f_max); CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, ("Set SD freq to %d MHz (min out of host f=%d MHz and card f=%d MHz)\n", f_max / 1000000, host_f_max / 1000000, softc->card_f_max / 1000000)); start_ccb->ccb_h.func_code = XPT_SET_TRAN_SETTINGS; start_ccb->ccb_h.flags = CAM_DIR_NONE; start_ccb->ccb_h.retry_count = 0; start_ccb->ccb_h.timeout = 100; start_ccb->ccb_h.cbfcnp = NULL; cts->ios.clock = f_max; cts->ios_valid = MMC_CLK; xpt_action(start_ccb); /* Set bus width */ enum mmc_bus_width desired_bus_width = bus_width_1; enum mmc_bus_width max_host_bus_width = (host_caps & MMC_CAP_8_BIT_DATA ? bus_width_8 : host_caps & MMC_CAP_4_BIT_DATA ? bus_width_4 : bus_width_1); enum mmc_bus_width max_card_bus_width = bus_width_1; if (mmcp->card_features & CARD_FEATURE_SD20 && softc->scr.bus_widths & SD_SCR_BUS_WIDTH_4) max_card_bus_width = bus_width_4; /* * Unlike SD, MMC cards don't have any information about supported bus width... * So we need to perform read/write test to find out the width. */ /* TODO: figure out bus width for MMC; use 8-bit for now (to test on BBB) */ if (mmcp->card_features & CARD_FEATURE_MMC) max_card_bus_width = bus_width_8; desired_bus_width = min(max_host_bus_width, max_card_bus_width); CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, ("Set bus width to %s (min of host %s and card %s)\n", bus_width_str(desired_bus_width), bus_width_str(max_host_bus_width), bus_width_str(max_card_bus_width))); sdda_set_bus_width(periph, start_ccb, desired_bus_width); if (f_max > 25000000) { err = mmc_set_timing(periph, start_ccb, bus_timing_hs); if (err != MMC_ERR_NONE) CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("Cannot switch card to high-speed mode")); } softc->state = SDDA_STATE_NORMAL; sdda_hook_into_geom(periph); } /* Called with periph lock held! */ static void sddastart(struct cam_periph *periph, union ccb *start_ccb) { struct sdda_softc *softc = (struct sdda_softc *)periph->softc; struct mmc_params *mmcp = &periph->path->device->mmc_ident_data; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("sddastart\n")); if (softc->state != SDDA_STATE_NORMAL) { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("device is not in SDDA_STATE_NORMAL yet")); xpt_release_ccb(start_ccb); return; } struct bio *bp; /* Run regular command. */ bp = bioq_first(&softc->bio_queue); if (bp == NULL) { xpt_release_ccb(start_ccb); return; } bioq_remove(&softc->bio_queue, bp); switch (bp->bio_cmd) { case BIO_WRITE: CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("BIO_WRITE\n")); softc->flags |= SDDA_FLAG_DIRTY; /* FALLTHROUGH */ case BIO_READ: { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("BIO_READ\n")); uint64_t blockno = bp->bio_pblkno; uint16_t count = bp->bio_bcount / 512; uint16_t opcode; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("Block %"PRIu64" cnt %u\n", blockno, count)); /* Construct new MMC command */ if (bp->bio_cmd == BIO_READ) { if (count > 1) opcode = MMC_READ_MULTIPLE_BLOCK; else opcode = MMC_READ_SINGLE_BLOCK; } else { if (count > 1) opcode = MMC_WRITE_MULTIPLE_BLOCK; else opcode = MMC_WRITE_BLOCK; } start_ccb->ccb_h.func_code = XPT_MMC_IO; start_ccb->ccb_h.flags = (bp->bio_cmd == BIO_READ ? CAM_DIR_IN : CAM_DIR_OUT); start_ccb->ccb_h.retry_count = 0; start_ccb->ccb_h.timeout = 15 * 1000; start_ccb->ccb_h.cbfcnp = sddadone; struct ccb_mmcio *mmcio; mmcio = &start_ccb->mmcio; mmcio->cmd.opcode = opcode; mmcio->cmd.arg = blockno; if (!(mmcp->card_features & CARD_FEATURE_SDHC)) mmcio->cmd.arg <<= 9; mmcio->cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; mmcio->cmd.data = softc->mmcdata; mmcio->cmd.data->data = bp->bio_data; mmcio->cmd.data->len = 512 * count; mmcio->cmd.data->flags = (bp->bio_cmd == BIO_READ ? MMC_DATA_READ : MMC_DATA_WRITE); /* Direct h/w to issue CMD12 upon completion */ if (count > 1) { mmcio->stop.opcode = MMC_STOP_TRANSMISSION; mmcio->stop.flags = MMC_RSP_R1B | MMC_CMD_AC; mmcio->stop.arg = 0; } break; } case BIO_FLUSH: CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("BIO_FLUSH\n")); sddaschedule(periph); break; case BIO_DELETE: CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("BIO_DELETE\n")); sddaschedule(periph); break; } start_ccb->ccb_h.ccb_bp = bp; softc->outstanding_cmds++; softc->refcount++; cam_periph_unlock(periph); xpt_action(start_ccb); cam_periph_lock(periph); softc->refcount--; /* May have more work to do, so ensure we stay scheduled */ sddaschedule(periph); } static void sddadone(struct cam_periph *periph, union ccb *done_ccb) { struct sdda_softc *softc; struct ccb_mmcio *mmcio; // struct ccb_getdev *cgd; struct cam_path *path; // int state; softc = (struct sdda_softc *)periph->softc; mmcio = &done_ccb->mmcio; path = done_ccb->ccb_h.path; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("sddadone\n")); struct bio *bp; int error = 0; // cam_periph_lock(periph); if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("Error!!!\n")); if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); error = 5; /* EIO */ } else { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) panic("REQ_CMP with QFRZN"); error = 0; } bp = (struct bio *)done_ccb->ccb_h.ccb_bp; bp->bio_error = error; if (error != 0) { bp->bio_resid = bp->bio_bcount; bp->bio_flags |= BIO_ERROR; } else { /* XXX: How many bytes remaining? */ bp->bio_resid = 0; if (bp->bio_resid > 0) bp->bio_flags |= BIO_ERROR; } uint32_t card_status = mmcio->cmd.resp[0]; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("Card status: %08x\n", R1_STATUS(card_status))); CAM_DEBUG(path, CAM_DEBUG_TRACE, ("Current state: %d\n", R1_CURRENT_STATE(card_status))); softc->outstanding_cmds--; xpt_release_ccb(done_ccb); biodone(bp); } static int sddaerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { return(cam_periph_error(ccb, cam_flags, sense_flags)); } #endif /* _KERNEL */ Index: head/sys/cam/mmc/mmc_xpt.c =================================================================== --- head/sys/cam/mmc/mmc_xpt.c (revision 328069) +++ head/sys/cam/mmc/mmc_xpt.c (revision 328070) @@ -1,1073 +1,1075 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2013,2014 Ilya Bakulin * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for xpt_print below */ #include /* for PRIu64 */ #include "opt_cam.h" FEATURE(mmccam, "CAM-based MMC/SD/SDIO stack"); static struct cam_ed * mmc_alloc_device(struct cam_eb *bus, struct cam_et *target, lun_id_t lun_id); static void mmc_dev_async(u_int32_t async_code, struct cam_eb *bus, struct cam_et *target, struct cam_ed *device, void *async_arg); static void mmc_action(union ccb *start_ccb); static void mmc_dev_advinfo(union ccb *start_ccb); static void mmc_announce_periph(struct cam_periph *periph); static void mmc_scan_lun(struct cam_periph *periph, struct cam_path *path, cam_flags flags, union ccb *ccb); /* mmcprobe methods */ static cam_status mmcprobe_register(struct cam_periph *periph, void *arg); static void mmcprobe_start(struct cam_periph *periph, union ccb *start_ccb); static void mmcprobe_cleanup(struct cam_periph *periph); static void mmcprobe_done(struct cam_periph *periph, union ccb *done_ccb); static void mmc_proto_announce(struct cam_ed *device); static void mmc_proto_denounce(struct cam_ed *device); static void mmc_proto_debug_out(union ccb *ccb); typedef enum { PROBE_RESET, PROBE_IDENTIFY, PROBE_SDIO_RESET, PROBE_SEND_IF_COND, PROBE_SDIO_INIT, PROBE_MMC_INIT, PROBE_SEND_APP_OP_COND, PROBE_GET_CID, PROBE_GET_CSD, PROBE_SEND_RELATIVE_ADDR, PROBE_SELECT_CARD, PROBE_DONE, PROBE_INVALID } probe_action; static char *probe_action_text[] = { "PROBE_RESET", "PROBE_IDENTIFY", "PROBE_SDIO_RESET", "PROBE_SEND_IF_COND", "PROBE_SDIO_INIT", "PROBE_MMC_INIT", "PROBE_SEND_APP_OP_COND", "PROBE_GET_CID", "PROBE_GET_CSD", "PROBE_SEND_RELATIVE_ADDR", "PROBE_SELECT_CARD", "PROBE_DONE", "PROBE_INVALID" }; #define PROBE_SET_ACTION(softc, newaction) \ do { \ char **text; \ text = probe_action_text; \ CAM_DEBUG((softc)->periph->path, CAM_DEBUG_PROBE, \ ("Probe %s to %s\n", text[(softc)->action], \ text[(newaction)])); \ (softc)->action = (newaction); \ } while(0) static struct xpt_xport_ops mmc_xport_ops = { .alloc_device = mmc_alloc_device, .action = mmc_action, .async = mmc_dev_async, .announce = mmc_announce_periph, }; #define MMC_XPT_XPORT(x, X) \ static struct xpt_xport mmc_xport_ ## x = { \ .xport = XPORT_ ## X, \ .name = #x, \ .ops = &mmc_xport_ops, \ }; \ CAM_XPT_XPORT(mmc_xport_ ## x); MMC_XPT_XPORT(mmc, MMCSD); static struct xpt_proto_ops mmc_proto_ops = { .announce = mmc_proto_announce, .denounce = mmc_proto_denounce, .debug_out = mmc_proto_debug_out, }; static struct xpt_proto mmc_proto = { .proto = PROTO_MMCSD, .name = "mmcsd", .ops = &mmc_proto_ops, }; CAM_XPT_PROTO(mmc_proto); typedef struct { probe_action action; int restart; union ccb saved_ccb; uint32_t flags; #define PROBE_FLAG_ACMD_SENT 0x1 /* CMD55 is sent, card expects ACMD */ uint8_t acmd41_count; /* how many times ACMD41 has been issued */ struct cam_periph *periph; } mmcprobe_softc; /* XPort functions -- an interface to CAM at periph side */ static struct cam_ed * mmc_alloc_device(struct cam_eb *bus, struct cam_et *target, lun_id_t lun_id) { struct cam_ed *device; printf("mmc_alloc_device()\n"); device = xpt_alloc_device(bus, target, lun_id); if (device == NULL) return (NULL); device->quirk = NULL; device->mintags = 0; device->maxtags = 0; bzero(&device->inq_data, sizeof(device->inq_data)); device->inq_flags = 0; device->queue_flags = 0; device->serial_num = NULL; device->serial_num_len = 0; return (device); } static void mmc_dev_async(u_int32_t async_code, struct cam_eb *bus, struct cam_et *target, struct cam_ed *device, void *async_arg) { printf("mmc_dev_async(async_code=0x%x, path_id=%d, target_id=%x, lun_id=%" SCNx64 "\n", async_code, bus->path_id, target->target_id, device->lun_id); /* * We only need to handle events for real devices. */ if (target->target_id == CAM_TARGET_WILDCARD || device->lun_id == CAM_LUN_WILDCARD) return; if (async_code == AC_LOST_DEVICE) { if ((device->flags & CAM_DEV_UNCONFIGURED) == 0) { printf("AC_LOST_DEVICE -> set to unconfigured\n"); device->flags |= CAM_DEV_UNCONFIGURED; xpt_release_device(device); } else { printf("AC_LOST_DEVICE on unconfigured device\n"); } } else if (async_code == AC_FOUND_DEVICE) { printf("Got AC_FOUND_DEVICE -- whatever...\n"); } else if (async_code == AC_PATH_REGISTERED) { printf("Got AC_PATH_REGISTERED -- whatever...\n"); } else if (async_code == AC_PATH_DEREGISTERED ) { printf("Got AC_PATH_DEREGISTERED -- whatever...\n"); } else if (async_code == AC_UNIT_ATTENTION) { printf("Got interrupt generated by the card and ignored it\n"); } else panic("Unknown async code\n"); } /* Taken from nvme_scan_lun, thanks to bsdimp@ */ static void mmc_scan_lun(struct cam_periph *periph, struct cam_path *path, cam_flags flags, union ccb *request_ccb) { struct ccb_pathinq cpi; cam_status status; struct cam_periph *old_periph; int lock; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("mmc_scan_lun\n")); xpt_path_inq(&cpi, periph->path); if (cpi.ccb_h.status != CAM_REQ_CMP) { if (request_ccb != NULL) { request_ccb->ccb_h.status = cpi.ccb_h.status; xpt_done(request_ccb); } return; } if (xpt_path_lun_id(path) == CAM_LUN_WILDCARD) { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("mmd_scan_lun ignoring bus\n")); request_ccb->ccb_h.status = CAM_REQ_CMP; /* XXX signal error ? */ xpt_done(request_ccb); return; } lock = (xpt_path_owned(path) == 0); if (lock) xpt_path_lock(path); if ((old_periph = cam_periph_find(path, "mmcprobe")) != NULL) { if ((old_periph->flags & CAM_PERIPH_INVALID) == 0) { // mmcprobe_softc *softc; // softc = (mmcprobe_softc *)old_periph->softc; // Not sure if we need request ccb queue for mmc // TAILQ_INSERT_TAIL(&softc->request_ccbs, // &request_ccb->ccb_h, periph_links.tqe); // softc->restart = 1; CAM_DEBUG(path, CAM_DEBUG_INFO, ("Got scan request, but mmcprobe already exists\n")); request_ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(request_ccb); } else { request_ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(request_ccb); } } else { xpt_print(path, " Set up the mmcprobe device...\n"); status = cam_periph_alloc(mmcprobe_register, NULL, mmcprobe_cleanup, mmcprobe_start, "mmcprobe", CAM_PERIPH_BIO, path, NULL, 0, request_ccb); if (status != CAM_REQ_CMP) { xpt_print(path, "xpt_scan_lun: cam_alloc_periph " "returned an error, can't continue probe\n"); } request_ccb->ccb_h.status = status; xpt_done(request_ccb); } if (lock) xpt_path_unlock(path); } static void mmc_action(union ccb *start_ccb) { CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("mmc_action! func_code=%x, action %s\n", start_ccb->ccb_h.func_code, xpt_action_name(start_ccb->ccb_h.func_code))); switch (start_ccb->ccb_h.func_code) { case XPT_SCAN_BUS: /* FALLTHROUGH */ case XPT_SCAN_TGT: /* FALLTHROUGH */ case XPT_SCAN_LUN: CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_INFO, ("XPT_SCAN_{BUS,TGT,LUN}\n")); mmc_scan_lun(start_ccb->ccb_h.path->periph, start_ccb->ccb_h.path, start_ccb->crcn.flags, start_ccb); break; case XPT_DEV_ADVINFO: { mmc_dev_advinfo(start_ccb); break; } default: xpt_action_default(start_ccb); break; } } static void mmc_dev_advinfo(union ccb *start_ccb) { struct cam_ed *device; struct ccb_dev_advinfo *cdai; off_t amt; start_ccb->ccb_h.status = CAM_REQ_INVALID; device = start_ccb->ccb_h.path->device; cdai = &start_ccb->cdai; CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("%s: request %x\n", __func__, cdai->buftype)); /* We don't support writing any data */ if (cdai->flags & CDAI_FLAG_STORE) panic("Attempt to store data?!"); switch(cdai->buftype) { case CDAI_TYPE_SCSI_DEVID: cdai->provsiz = device->device_id_len; if (device->device_id_len == 0) break; amt = MIN(cdai->provsiz, cdai->bufsiz); memcpy(cdai->buf, device->device_id, amt); break; case CDAI_TYPE_SERIAL_NUM: cdai->provsiz = device->serial_num_len; if (device->serial_num_len == 0) break; amt = MIN(cdai->provsiz, cdai->bufsiz); memcpy(cdai->buf, device->serial_num, amt); break; case CDAI_TYPE_PHYS_PATH: /* pass(4) wants this */ cdai->provsiz = 0; break; default: panic("Unknown buftype"); return; } start_ccb->ccb_h.status = CAM_REQ_CMP; } static void mmc_announce_periph(struct cam_periph *periph) { struct ccb_pathinq cpi; struct ccb_trans_settings cts; struct cam_path *path = periph->path; cam_periph_assert(periph, MA_OWNED); CAM_DEBUG(periph->path, CAM_DEBUG_INFO, ("mmc_announce_periph: called\n")); xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NORMAL); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb*)&cts); if ((cts.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) return; xpt_path_inq(&cpi, periph->path); printf("XPT info: CLK %04X, ...\n", cts.proto_specific.mmc.ios.clock); } /* This func is called per attached device :-( */ void mmc_print_ident(struct mmc_params *ident_data) { printf("Relative addr: %08x\n", ident_data->card_rca); printf("Card features: <"); if (ident_data->card_features & CARD_FEATURE_MMC) printf("MMC "); if (ident_data->card_features & CARD_FEATURE_MEMORY) printf("Memory "); if (ident_data->card_features & CARD_FEATURE_SDHC) printf("High-Capacity "); if (ident_data->card_features & CARD_FEATURE_SD20) printf("SD2.0-Conditions "); if (ident_data->card_features & CARD_FEATURE_SDIO) printf("SDIO "); printf(">\n"); if (ident_data->card_features & CARD_FEATURE_MEMORY) printf("Card memory OCR: %08x\n", ident_data->card_ocr); if (ident_data->card_features & CARD_FEATURE_SDIO) { printf("Card IO OCR: %08x\n", ident_data->io_ocr); printf("Number of funcitions: %u\n", ident_data->sdio_func_count); } } static void mmc_proto_announce(struct cam_ed *device) { mmc_print_ident(&device->mmc_ident_data); } static void mmc_proto_denounce(struct cam_ed *device) { mmc_print_ident(&device->mmc_ident_data); } static void mmc_proto_debug_out(union ccb *ccb) { if (ccb->ccb_h.func_code != XPT_MMC_IO) return; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_CDB,("mmc_proto_debug_out\n")); } static periph_init_t probe_periph_init; static struct periph_driver probe_driver = { probe_periph_init, "mmcprobe", TAILQ_HEAD_INITIALIZER(probe_driver.units), /* generation */ 0, CAM_PERIPH_DRV_EARLY }; PERIPHDRIVER_DECLARE(mmcprobe, probe_driver); #define CARD_ID_FREQUENCY 400000 /* Spec requires 400kHz max during ID phase. */ static void probe_periph_init() { } static cam_status mmcprobe_register(struct cam_periph *periph, void *arg) { union ccb *request_ccb; /* CCB representing the probe request */ cam_status status; mmcprobe_softc *softc; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("mmcprobe_register\n")); request_ccb = (union ccb *)arg; if (request_ccb == NULL) { printf("mmcprobe_register: no probe CCB, " "can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (mmcprobe_softc *)malloc(sizeof(*softc), M_CAMXPT, M_NOWAIT); if (softc == NULL) { printf("proberegister: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } softc->flags = 0; softc->acmd41_count = 0; periph->softc = softc; softc->periph = periph; softc->action = PROBE_INVALID; softc->restart = 0; status = cam_periph_acquire(periph); memset(&periph->path->device->mmc_ident_data, 0, sizeof(struct mmc_params)); if (status != CAM_REQ_CMP) { printf("proberegister: cam_periph_acquire failed (status=%d)\n", status); return (status); } CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Probe started\n")); if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) PROBE_SET_ACTION(softc, PROBE_RESET); else PROBE_SET_ACTION(softc, PROBE_IDENTIFY); /* This will kick the ball */ xpt_schedule(periph, CAM_PRIORITY_XPT); return(CAM_REQ_CMP); } static int mmc_highest_voltage(uint32_t ocr) { int i; for (i = MMC_OCR_MAX_VOLTAGE_SHIFT; i >= MMC_OCR_MIN_VOLTAGE_SHIFT; i--) if (ocr & (1 << i)) return (i); return (-1); } static inline void init_standard_ccb(union ccb *ccb, uint32_t cmd) { ccb->ccb_h.func_code = cmd; ccb->ccb_h.flags = CAM_DIR_OUT; ccb->ccb_h.retry_count = 0; ccb->ccb_h.timeout = 15 * 1000; ccb->ccb_h.cbfcnp = mmcprobe_done; } static void mmcprobe_start(struct cam_periph *periph, union ccb *start_ccb) { mmcprobe_softc *softc; struct cam_path *path; struct ccb_mmcio *mmcio; struct mtx *p_mtx = cam_periph_mtx(periph); struct ccb_trans_settings_mmc *cts; CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("mmcprobe_start\n")); softc = (mmcprobe_softc *)periph->softc; path = start_ccb->ccb_h.path; mmcio = &start_ccb->mmcio; cts = &start_ccb->cts.proto_specific.mmc; struct mmc_params *mmcp = &path->device->mmc_ident_data; memset(&mmcio->cmd, 0, sizeof(struct mmc_command)); if (softc->restart) { softc->restart = 0; if (path->device->flags & CAM_DEV_UNCONFIGURED) softc->action = PROBE_RESET; else softc->action = PROBE_IDENTIFY; } /* Here is the place where the identify fun begins */ switch (softc->action) { case PROBE_RESET: /* FALLTHROUGH */ case PROBE_IDENTIFY: xpt_path_inq(&start_ccb->cpi, periph->path); CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Start with PROBE_RESET\n")); init_standard_ccb(start_ccb, XPT_SET_TRAN_SETTINGS); cts->ios.power_mode = power_off; cts->ios_valid = MMC_PM; xpt_action(start_ccb); mtx_sleep(periph, p_mtx, 0, "mmcios", 100); /* mmc_power_up */ /* Get the host OCR */ init_standard_ccb(start_ccb, XPT_GET_TRAN_SETTINGS); xpt_action(start_ccb); uint32_t hv = mmc_highest_voltage(cts->host_ocr); init_standard_ccb(start_ccb, XPT_SET_TRAN_SETTINGS); cts->ios.vdd = hv; cts->ios.bus_mode = opendrain; cts->ios.chip_select = cs_dontcare; cts->ios.power_mode = power_up; cts->ios.bus_width = bus_width_1; cts->ios.clock = 0; cts->ios_valid = MMC_VDD | MMC_PM | MMC_BM | MMC_CS | MMC_BW | MMC_CLK; xpt_action(start_ccb); mtx_sleep(periph, p_mtx, 0, "mmcios", 100); init_standard_ccb(start_ccb, XPT_SET_TRAN_SETTINGS); cts->ios.power_mode = power_on; cts->ios.clock = CARD_ID_FREQUENCY; cts->ios.timing = bus_timing_normal; cts->ios_valid = MMC_PM | MMC_CLK | MMC_BT; xpt_action(start_ccb); mtx_sleep(periph, p_mtx, 0, "mmcios", 100); /* End for mmc_power_on */ /* Begin mmc_idle_cards() */ init_standard_ccb(start_ccb, XPT_SET_TRAN_SETTINGS); cts->ios.chip_select = cs_high; cts->ios_valid = MMC_CS; xpt_action(start_ccb); mtx_sleep(periph, p_mtx, 0, "mmcios", 1); CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Send first XPT_MMC_IO\n")); init_standard_ccb(start_ccb, XPT_MMC_IO); mmcio->cmd.opcode = MMC_GO_IDLE_STATE; /* CMD 0 */ mmcio->cmd.arg = 0; mmcio->cmd.flags = MMC_RSP_NONE | MMC_CMD_BC; mmcio->cmd.data = NULL; mmcio->stop.opcode = 0; /* XXX Reset I/O portion as well */ break; case PROBE_SDIO_RESET: CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Start with PROBE_SDIO_RESET\n")); uint32_t mmc_arg = SD_IO_RW_ADR(SD_IO_CCCR_CTL) | SD_IO_RW_DAT(CCCR_CTL_RES) | SD_IO_RW_WR | SD_IO_RW_RAW; cam_fill_mmcio(&start_ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ mmcprobe_done, /*flags*/ CAM_DIR_NONE, /*mmc_opcode*/ SD_IO_RW_DIRECT, /*mmc_arg*/ mmc_arg, /*mmc_flags*/ MMC_RSP_R5 | MMC_CMD_AC, /*mmc_data*/ NULL, /*timeout*/ 1000); break; case PROBE_SEND_IF_COND: CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Start with PROBE_SEND_IF_COND\n")); init_standard_ccb(start_ccb, XPT_MMC_IO); mmcio->cmd.opcode = SD_SEND_IF_COND; /* CMD 8 */ mmcio->cmd.arg = (1 << 8) + 0xAA; mmcio->cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR; mmcio->stop.opcode = 0; break; case PROBE_SDIO_INIT: CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Start with PROBE_SDIO_INIT\n")); init_standard_ccb(start_ccb, XPT_MMC_IO); mmcio->cmd.opcode = IO_SEND_OP_COND; /* CMD 5 */ mmcio->cmd.arg = mmcp->io_ocr; mmcio->cmd.flags = MMC_RSP_R4; mmcio->stop.opcode = 0; break; case PROBE_MMC_INIT: CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Start with PROBE_MMC_INIT\n")); init_standard_ccb(start_ccb, XPT_MMC_IO); mmcio->cmd.opcode = MMC_SEND_OP_COND; /* CMD 1 */ mmcio->cmd.arg = MMC_OCR_CCS | mmcp->card_ocr; /* CCS + ocr */; mmcio->cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR; mmcio->stop.opcode = 0; break; case PROBE_SEND_APP_OP_COND: init_standard_ccb(start_ccb, XPT_MMC_IO); if (softc->flags & PROBE_FLAG_ACMD_SENT) { mmcio->cmd.opcode = ACMD_SD_SEND_OP_COND; /* CMD 41 */ /* * We set CCS bit because we do support SDHC cards. * XXX: Don't set CCS if no response to CMD8. */ uint32_t cmd_arg = MMC_OCR_CCS | mmcp->card_ocr; /* CCS + ocr */ if (softc->acmd41_count < 10 && mmcp->card_ocr != 0 ) cmd_arg |= MMC_OCR_S18R; mmcio->cmd.arg = cmd_arg; mmcio->cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR; softc->acmd41_count++; } else { mmcio->cmd.opcode = MMC_APP_CMD; /* CMD 55 */ mmcio->cmd.arg = 0; /* rca << 16 */ mmcio->cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; } mmcio->stop.opcode = 0; break; case PROBE_GET_CID: /* XXX move to mmc_da */ init_standard_ccb(start_ccb, XPT_MMC_IO); mmcio->cmd.opcode = MMC_ALL_SEND_CID; mmcio->cmd.arg = 0; mmcio->cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR; mmcio->stop.opcode = 0; break; case PROBE_SEND_RELATIVE_ADDR: init_standard_ccb(start_ccb, XPT_MMC_IO); mmcio->cmd.opcode = SD_SEND_RELATIVE_ADDR; mmcio->cmd.arg = 0; mmcio->cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR; mmcio->stop.opcode = 0; break; case PROBE_SELECT_CARD: init_standard_ccb(start_ccb, XPT_MMC_IO); mmcio->cmd.opcode = MMC_SELECT_CARD; mmcio->cmd.arg = (uint32_t)path->device->mmc_ident_data.card_rca << 16; mmcio->cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; mmcio->stop.opcode = 0; break; case PROBE_GET_CSD: /* XXX move to mmc_da */ init_standard_ccb(start_ccb, XPT_MMC_IO); mmcio->cmd.opcode = MMC_SEND_CSD; mmcio->cmd.arg = (uint32_t)path->device->mmc_ident_data.card_rca << 16; mmcio->cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR; mmcio->stop.opcode = 0; break; case PROBE_DONE: CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Start with PROBE_DONE\n")); init_standard_ccb(start_ccb, XPT_SET_TRAN_SETTINGS); cts->ios.bus_mode = pushpull; cts->ios_valid = MMC_BM; xpt_action(start_ccb); return; /* NOTREACHED */ break; case PROBE_INVALID: break; default: CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("probestart: invalid action state 0x%x\n", softc->action)); panic("default: case in mmc_probe_start()"); } start_ccb->ccb_h.flags |= CAM_DEV_QFREEZE; xpt_action(start_ccb); } static void mmcprobe_cleanup(struct cam_periph *periph) { free(periph->softc, M_CAMXPT); } static void mmcprobe_done(struct cam_periph *periph, union ccb *done_ccb) { mmcprobe_softc *softc; struct cam_path *path; int err; struct ccb_mmcio *mmcio; u_int32_t priority; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("mmcprobe_done\n")); softc = (mmcprobe_softc *)periph->softc; path = done_ccb->ccb_h.path; priority = done_ccb->ccb_h.pinfo.priority; switch (softc->action) { case PROBE_RESET: /* FALLTHROUGH */ case PROBE_IDENTIFY: { printf("Starting completion of PROBE_RESET\n"); CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("done with PROBE_RESET\n")); mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; if (err != MMC_ERR_NONE) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("GO_IDLE_STATE failed with error %d\n", err)); /* There was a device there, but now it's gone... */ if ((path->device->flags & CAM_DEV_UNCONFIGURED) == 0) { xpt_async(AC_LOST_DEVICE, path, NULL); } PROBE_SET_ACTION(softc, PROBE_INVALID); break; } path->device->protocol = PROTO_MMCSD; PROBE_SET_ACTION(softc, PROBE_SEND_IF_COND); break; } case PROBE_SEND_IF_COND: { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; struct mmc_params *mmcp = &path->device->mmc_ident_data; if (err != MMC_ERR_NONE || mmcio->cmd.resp[0] != 0x1AA) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("IF_COND: error %d, pattern %08x\n", err, mmcio->cmd.resp[0])); } else { mmcp->card_features |= CARD_FEATURE_SD20; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("SD 2.0 interface conditions: OK\n")); } PROBE_SET_ACTION(softc, PROBE_SDIO_RESET); break; } case PROBE_SDIO_RESET: { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("SDIO_RESET: error %d, CCCR CTL register: %08x\n", err, mmcio->cmd.resp[0])); PROBE_SET_ACTION(softc, PROBE_SDIO_INIT); break; } case PROBE_SDIO_INIT: { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; struct mmc_params *mmcp = &path->device->mmc_ident_data; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("SDIO_INIT: error %d, %08x %08x %08x %08x\n", err, mmcio->cmd.resp[0], mmcio->cmd.resp[1], mmcio->cmd.resp[2], mmcio->cmd.resp[3])); /* * Error here means that this card is not SDIO, * so proceed with memory init as if nothing has happened */ if (err != MMC_ERR_NONE) { PROBE_SET_ACTION(softc, PROBE_SEND_APP_OP_COND); break; } mmcp->card_features |= CARD_FEATURE_SDIO; uint32_t ioifcond = mmcio->cmd.resp[0]; uint32_t io_ocr = ioifcond & R4_IO_OCR_MASK; mmcp->sdio_func_count = R4_IO_NUM_FUNCTIONS(ioifcond); CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("SDIO card: %d functions\n", mmcp->sdio_func_count)); if (io_ocr == 0) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("SDIO OCR invalid?!\n")); break; /* Retry */ } if (io_ocr != 0 && mmcp->io_ocr == 0) { mmcp->io_ocr = io_ocr; break; /* Retry, this time with non-0 OCR */ } CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("SDIO OCR: %08x\n", mmcp->io_ocr)); if (ioifcond & R4_IO_MEM_PRESENT) { /* Combo card -- proceed to memory initialization */ PROBE_SET_ACTION(softc, PROBE_SEND_APP_OP_COND); } else { /* No memory portion -- get RCA and select card */ PROBE_SET_ACTION(softc, PROBE_SEND_RELATIVE_ADDR); } break; } case PROBE_MMC_INIT: { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; struct mmc_params *mmcp = &path->device->mmc_ident_data; if (err != MMC_ERR_NONE) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("MMC_INIT: error %d, resp %08x\n", err, mmcio->cmd.resp[0])); PROBE_SET_ACTION(softc, PROBE_INVALID); break; } CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("MMC card, OCR %08x\n", mmcio->cmd.resp[0])); if (mmcp->card_ocr == 0) { /* We haven't sent the OCR to the card yet -- do it */ mmcp->card_ocr = mmcio->cmd.resp[0]; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("-> sending OCR to card\n")); break; } if (!(mmcio->cmd.resp[0] & MMC_OCR_CARD_BUSY)) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Card is still powering up\n")); break; } mmcp->card_features |= CARD_FEATURE_MMC | CARD_FEATURE_MEMORY; PROBE_SET_ACTION(softc, PROBE_GET_CID); break; } case PROBE_SEND_APP_OP_COND: { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; if (err != MMC_ERR_NONE) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("APP_OP_COND: error %d, resp %08x\n", err, mmcio->cmd.resp[0])); PROBE_SET_ACTION(softc, PROBE_MMC_INIT); break; } if (!(softc->flags & PROBE_FLAG_ACMD_SENT)) { /* Don't change the state */ softc->flags |= PROBE_FLAG_ACMD_SENT; break; } softc->flags &= ~PROBE_FLAG_ACMD_SENT; if ((mmcio->cmd.resp[0] & MMC_OCR_CARD_BUSY) || (mmcio->cmd.arg & MMC_OCR_VOLTAGE) == 0) { struct mmc_params *mmcp = &path->device->mmc_ident_data; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Card OCR: %08x\n", mmcio->cmd.resp[0])); if (mmcp->card_ocr == 0) { mmcp->card_ocr = mmcio->cmd.resp[0]; /* Now when we know OCR that we want -- send it to card */ CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("-> sending OCR to card\n")); } else { /* We already know the OCR and despite of that we * are processing the answer to ACMD41 -> move on */ PROBE_SET_ACTION(softc, PROBE_GET_CID); } /* Getting an answer to ACMD41 means the card has memory */ mmcp->card_features |= CARD_FEATURE_MEMORY; /* Standard capacity vs High Capacity memory card */ if (mmcio->cmd.resp[0] & MMC_OCR_CCS) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Card is SDHC\n")); mmcp->card_features |= CARD_FEATURE_SDHC; } /* Whether the card supports 1.8V signaling */ if (mmcio->cmd.resp[0] & MMC_OCR_S18A) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Card supports 1.8V signaling\n")); mmcp->card_features |= CARD_FEATURE_18V; } } else { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Card not ready: %08x\n", mmcio->cmd.resp[0])); /* Send CMD55+ACMD41 once again */ PROBE_SET_ACTION(softc, PROBE_SEND_APP_OP_COND); } break; } case PROBE_GET_CID: /* XXX move to mmc_da */ { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; if (err != MMC_ERR_NONE) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("PROBE_GET_CID: error %d\n", err)); PROBE_SET_ACTION(softc, PROBE_INVALID); break; } struct mmc_params *mmcp = &path->device->mmc_ident_data; memcpy(mmcp->card_cid, mmcio->cmd.resp, 4 * sizeof(uint32_t)); CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("CID %08x%08x%08x%08x\n", mmcp->card_cid[0], mmcp->card_cid[1], mmcp->card_cid[2], mmcp->card_cid[3])); PROBE_SET_ACTION(softc, PROBE_SEND_RELATIVE_ADDR); break; } case PROBE_SEND_RELATIVE_ADDR: { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; struct mmc_params *mmcp = &path->device->mmc_ident_data; uint16_t rca = mmcio->cmd.resp[0] >> 16; CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("Card published RCA: %u\n", rca)); path->device->mmc_ident_data.card_rca = rca; if (err != MMC_ERR_NONE) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("PROBE_SEND_RELATIVE_ADDR: error %d\n", err)); PROBE_SET_ACTION(softc, PROBE_INVALID); break; } /* If memory is present, get CSD, otherwise select card */ if (mmcp->card_features & CARD_FEATURE_MEMORY) PROBE_SET_ACTION(softc, PROBE_GET_CSD); else PROBE_SET_ACTION(softc, PROBE_SELECT_CARD); break; } case PROBE_GET_CSD: { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; if (err != MMC_ERR_NONE) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("PROBE_GET_CSD: error %d\n", err)); PROBE_SET_ACTION(softc, PROBE_INVALID); break; } struct mmc_params *mmcp = &path->device->mmc_ident_data; memcpy(mmcp->card_csd, mmcio->cmd.resp, 4 * sizeof(uint32_t)); CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("CSD %08x%08x%08x%08x\n", mmcp->card_csd[0], mmcp->card_csd[1], mmcp->card_csd[2], mmcp->card_csd[3])); PROBE_SET_ACTION(softc, PROBE_SELECT_CARD); break; } case PROBE_SELECT_CARD: { mmcio = &done_ccb->mmcio; err = mmcio->cmd.error; if (err != MMC_ERR_NONE) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("PROBE_SEND_RELATIVE_ADDR: error %d\n", err)); PROBE_SET_ACTION(softc, PROBE_INVALID); break; } PROBE_SET_ACTION(softc, PROBE_DONE); break; } default: CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("mmc_probedone: invalid action state 0x%x\n", softc->action)); panic("default: case in mmc_probe_done()"); } if (softc->action == PROBE_INVALID && (path->device->flags & CAM_DEV_UNCONFIGURED) == 0) { CAM_DEBUG(done_ccb->ccb_h.path, CAM_DEBUG_PROBE, ("mmc_probedone: Should send AC_LOST_DEVICE but won't for now\n")); //xpt_async(AC_LOST_DEVICE, path, NULL); } xpt_release_ccb(done_ccb); if (softc->action != PROBE_INVALID) xpt_schedule(periph, priority); /* Drop freeze taken due to CAM_DEV_QFREEZE flag set. */ int frozen = cam_release_devq(path, 0, 0, 0, FALSE); printf("mmc_probedone: remaining freezecnt %d\n", frozen); if (softc->action == PROBE_DONE) { /* Notify the system that the device is found! */ if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); done_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(done_ccb); xpt_async(AC_FOUND_DEVICE, path, done_ccb); } } if (softc->action == PROBE_DONE || softc->action == PROBE_INVALID) { cam_periph_invalidate(periph); cam_periph_release_locked(periph); } } Index: head/sys/cam/nvme/nvme_all.c =================================================================== --- head/sys/cam/nvme/nvme_all.c (revision 328069) +++ head/sys/cam/nvme/nvme_all.c (revision 328070) @@ -1,159 +1,161 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2015 Netflix, Inc * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 __FBSDID("$FreeBSD$"); #include #ifdef _KERNEL #include "opt_scsi.h" #include #include #include #include #include #else #include #include #include #include #ifndef min #define min(a,b) (((a)<(b))?(a):(b)) #endif #endif #include #include #include #include #include #include #include #ifdef _KERNEL #include #include #include #include #endif void nvme_ns_cmd(struct ccb_nvmeio *nvmeio, uint8_t cmd, uint32_t nsid, uint32_t cdw10, uint32_t cdw11, uint32_t cdw12, uint32_t cdw13, uint32_t cdw14, uint32_t cdw15) { bzero(&nvmeio->cmd, sizeof(struct nvme_command)); nvmeio->cmd.opc = cmd; nvmeio->cmd.nsid = nsid; nvmeio->cmd.cdw10 = cdw10; nvmeio->cmd.cdw11 = cdw11; nvmeio->cmd.cdw12 = cdw12; nvmeio->cmd.cdw13 = cdw13; nvmeio->cmd.cdw14 = cdw14; nvmeio->cmd.cdw15 = cdw15; } int nvme_identify_match(caddr_t identbuffer, caddr_t table_entry) { return 0; } void nvme_print_ident(const struct nvme_controller_data *cdata, const struct nvme_namespace_data *data, struct sbuf *sb) { sbuf_printf(sb, "<"); cam_strvis_sbuf(sb, cdata->mn, sizeof(cdata->mn), 0); sbuf_printf(sb, " "); cam_strvis_sbuf(sb, cdata->fr, sizeof(cdata->fr), 0); sbuf_printf(sb, " "); cam_strvis_sbuf(sb, cdata->sn, sizeof(cdata->sn), 0); sbuf_printf(sb, ">\n"); } /* XXX need to do nvme admin opcodes too, but those aren't used yet by nda */ static const char * nvme_opc2str[] = { "FLUSH", "WRITE", "READ", "RSVD-3", "WRITE_UNCORRECTABLE", "COMPARE", "RSVD-6", "RSVD-7", "DATASET_MANAGEMENT" }; const char * nvme_op_string(const struct nvme_command *cmd) { if (cmd->opc > nitems(nvme_opc2str)) return "UNKNOWN"; return nvme_opc2str[cmd->opc]; } const char * nvme_cmd_string(const struct nvme_command *cmd, char *cmd_string, size_t len) { /* * cid, rsvd areas and mptr not printed, since they are used * only internally by the SIM. */ snprintf(cmd_string, len, "opc=%x fuse=%x nsid=%x prp1=%llx prp2=%llx cdw=%x %x %x %x %x %x", cmd->opc, cmd->fuse, cmd->nsid, (unsigned long long)cmd->prp1, (unsigned long long)cmd->prp2, cmd->cdw10, cmd->cdw11, cmd->cdw12, cmd->cdw13, cmd->cdw14, cmd->cdw15); return cmd_string; } const void * nvme_get_identify_cntrl(struct cam_periph *periph) { struct cam_ed *device; device = periph->path->device; return device->nvme_cdata; } const void * nvme_get_identify_ns(struct cam_periph *periph) { struct cam_ed *device; device = periph->path->device; return device->nvme_data; } Index: head/sys/cam/nvme/nvme_all.h =================================================================== --- head/sys/cam/nvme/nvme_all.h (revision 328069) +++ head/sys/cam/nvme/nvme_all.h (revision 328070) @@ -1,49 +1,51 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2015 Netflix, Inc * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * $FreeBSD$ */ #ifndef CAM_NVME_NVME_ALL_H #define CAM_NVME_NVME_ALL_H 1 #include struct ccb_nvmeio; void nvme_ns_cmd(struct ccb_nvmeio *nvmeio, uint8_t cmd, uint32_t nsid, uint32_t cdw10, uint32_t cdw11, uint32_t cdw12, uint32_t cdw13, uint32_t cdw14, uint32_t cdw15); int nvme_identify_match(caddr_t identbuffer, caddr_t table_entry); struct sbuf; void nvme_print_ident(const struct nvme_controller_data *, const struct nvme_namespace_data *, struct sbuf *); const char *nvme_op_string(const struct nvme_command *); const char *nvme_cmd_string(const struct nvme_command *, char *, size_t); const void *nvme_get_identify_cntrl(struct cam_periph *); const void *nvme_get_identify_ns(struct cam_periph *); #endif /* CAM_NVME_NVME_ALL_H */ Index: head/sys/cam/nvme/nvme_da.c =================================================================== --- head/sys/cam/nvme/nvme_da.c (revision 328069) +++ head/sys/cam/nvme/nvme_da.c (revision 328070) @@ -1,1131 +1,1133 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2015 Netflix, Inc * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * Derived from ata_da.c: * Copyright (c) 2009 Alexander Motin */ #include __FBSDID("$FreeBSD$"); #include #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* _KERNEL */ #ifndef _KERNEL #include #include #endif /* _KERNEL */ #include #include #include #include #include #include #include typedef enum { NDA_STATE_NORMAL } nda_state; typedef enum { NDA_FLAG_OPEN = 0x0001, NDA_FLAG_DIRTY = 0x0002, NDA_FLAG_SCTX_INIT = 0x0004, } nda_flags; typedef enum { NDA_Q_4K = 0x01, NDA_Q_NONE = 0x00, } nda_quirks; #define NDA_Q_BIT_STRING \ "\020" \ "\001Bit 0" typedef enum { NDA_CCB_BUFFER_IO = 0x01, NDA_CCB_DUMP = 0x02, NDA_CCB_TRIM = 0x03, NDA_CCB_TYPE_MASK = 0x0F, } nda_ccb_state; /* Offsets into our private area for storing information */ #define ccb_state ppriv_field0 #define ccb_bp ppriv_ptr1 struct trim_request { TAILQ_HEAD(, bio) bps; }; struct nda_softc { struct cam_iosched_softc *cam_iosched; int outstanding_cmds; /* Number of active commands */ int refcount; /* Active xpt_action() calls */ nda_state state; nda_flags flags; nda_quirks quirks; int unmappedio; uint32_t nsid; /* Namespace ID for this nda device */ struct disk *disk; struct task sysctl_task; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; struct trim_request trim_req; #ifdef CAM_IO_STATS struct sysctl_ctx_list sysctl_stats_ctx; struct sysctl_oid *sysctl_stats_tree; u_int timeouts; u_int errors; u_int invalidations; #endif }; /* Need quirk table */ static disk_strategy_t ndastrategy; static dumper_t ndadump; static periph_init_t ndainit; static void ndaasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg); static void ndasysctlinit(void *context, int pending); static periph_ctor_t ndaregister; static periph_dtor_t ndacleanup; static periph_start_t ndastart; static periph_oninv_t ndaoninvalidate; static void ndadone(struct cam_periph *periph, union ccb *done_ccb); static int ndaerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags); static void ndashutdown(void *arg, int howto); static void ndasuspend(void *arg); #ifndef NDA_DEFAULT_SEND_ORDERED #define NDA_DEFAULT_SEND_ORDERED 1 #endif #ifndef NDA_DEFAULT_TIMEOUT #define NDA_DEFAULT_TIMEOUT 30 /* Timeout in seconds */ #endif #ifndef NDA_DEFAULT_RETRY #define NDA_DEFAULT_RETRY 4 #endif //static int nda_retry_count = NDA_DEFAULT_RETRY; static int nda_send_ordered = NDA_DEFAULT_SEND_ORDERED; static int nda_default_timeout = NDA_DEFAULT_TIMEOUT; /* * All NVMe media is non-rotational, so all nvme device instances * share this to implement the sysctl. */ static int nda_rotating_media = 0; static SYSCTL_NODE(_kern_cam, OID_AUTO, nda, CTLFLAG_RD, 0, "CAM Direct Access Disk driver"); static struct periph_driver ndadriver = { ndainit, "nda", TAILQ_HEAD_INITIALIZER(ndadriver.units), /* generation */ 0 }; PERIPHDRIVER_DECLARE(nda, ndadriver); static MALLOC_DEFINE(M_NVMEDA, "nvme_da", "nvme_da buffers"); /* * nice wrappers. Maybe these belong in nvme_all.c instead of * here, but this is the only place that uses these. Should * we ever grow another NVME periph, we should move them * all there wholesale. */ static void nda_nvme_flush(struct nda_softc *softc, struct ccb_nvmeio *nvmeio) { cam_fill_nvmeio(nvmeio, 0, /* retries */ ndadone, /* cbfcnp */ CAM_DIR_NONE, /* flags */ NULL, /* data_ptr */ 0, /* dxfer_len */ nda_default_timeout * 1000); /* timeout 30s */ nvme_ns_flush_cmd(&nvmeio->cmd, softc->nsid); } static void nda_nvme_trim(struct nda_softc *softc, struct ccb_nvmeio *nvmeio, void *payload, uint32_t num_ranges) { cam_fill_nvmeio(nvmeio, 0, /* retries */ ndadone, /* cbfcnp */ CAM_DIR_OUT, /* flags */ payload, /* data_ptr */ num_ranges * sizeof(struct nvme_dsm_range), /* dxfer_len */ nda_default_timeout * 1000); /* timeout 30s */ nvme_ns_trim_cmd(&nvmeio->cmd, softc->nsid, num_ranges); } static void nda_nvme_write(struct nda_softc *softc, struct ccb_nvmeio *nvmeio, void *payload, uint64_t lba, uint32_t len, uint32_t count) { cam_fill_nvmeio(nvmeio, 0, /* retries */ ndadone, /* cbfcnp */ CAM_DIR_OUT, /* flags */ payload, /* data_ptr */ len, /* dxfer_len */ nda_default_timeout * 1000); /* timeout 30s */ nvme_ns_write_cmd(&nvmeio->cmd, softc->nsid, lba, count); } static void nda_nvme_rw_bio(struct nda_softc *softc, struct ccb_nvmeio *nvmeio, struct bio *bp, uint32_t rwcmd) { int flags = rwcmd == NVME_OPC_READ ? CAM_DIR_IN : CAM_DIR_OUT; void *payload; uint64_t lba; uint32_t count; if (bp->bio_flags & BIO_UNMAPPED) { flags |= CAM_DATA_BIO; payload = bp; } else { payload = bp->bio_data; } lba = bp->bio_pblkno; count = bp->bio_bcount / softc->disk->d_sectorsize; cam_fill_nvmeio(nvmeio, 0, /* retries */ ndadone, /* cbfcnp */ flags, /* flags */ payload, /* data_ptr */ bp->bio_bcount, /* dxfer_len */ nda_default_timeout * 1000); /* timeout 30s */ nvme_ns_rw_cmd(&nvmeio->cmd, rwcmd, softc->nsid, lba, count); } static int ndaopen(struct disk *dp) { struct cam_periph *periph; struct nda_softc *softc; int error; periph = (struct cam_periph *)dp->d_drv1; if (cam_periph_acquire(periph) != CAM_REQ_CMP) { return(ENXIO); } cam_periph_lock(periph); if ((error = cam_periph_hold(periph, PRIBIO|PCATCH)) != 0) { cam_periph_unlock(periph); cam_periph_release(periph); return (error); } CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, ("ndaopen\n")); softc = (struct nda_softc *)periph->softc; softc->flags |= NDA_FLAG_OPEN; cam_periph_unhold(periph); cam_periph_unlock(periph); return (0); } static int ndaclose(struct disk *dp) { struct cam_periph *periph; struct nda_softc *softc; union ccb *ccb; int error; periph = (struct cam_periph *)dp->d_drv1; softc = (struct nda_softc *)periph->softc; cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, ("ndaclose\n")); if ((softc->flags & NDA_FLAG_DIRTY) != 0 && (periph->flags & CAM_PERIPH_INVALID) == 0 && cam_periph_hold(periph, PRIBIO) == 0) { ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); nda_nvme_flush(softc, &ccb->nvmeio); error = cam_periph_runccb(ccb, ndaerror, /*cam_flags*/0, /*sense_flags*/0, softc->disk->d_devstat); if (error != 0) xpt_print(periph->path, "Synchronize cache failed\n"); else softc->flags &= ~NDA_FLAG_DIRTY; xpt_release_ccb(ccb); cam_periph_unhold(periph); } softc->flags &= ~NDA_FLAG_OPEN; while (softc->refcount != 0) cam_periph_sleep(periph, &softc->refcount, PRIBIO, "ndaclose", 1); cam_periph_unlock(periph); cam_periph_release(periph); return (0); } static void ndaschedule(struct cam_periph *periph) { struct nda_softc *softc = (struct nda_softc *)periph->softc; if (softc->state != NDA_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 ndastrategy(struct bio *bp) { struct cam_periph *periph; struct nda_softc *softc; periph = (struct cam_periph *)bp->bio_disk->d_drv1; softc = (struct nda_softc *)periph->softc; cam_periph_lock(periph); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ndastrategy(%p)\n", bp)); /* * If the device has been made invalid, error out */ if ((periph->flags & CAM_PERIPH_INVALID) != 0) { cam_periph_unlock(periph); biofinish(bp, NULL, ENXIO); return; } /* * 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. */ ndaschedule(periph); cam_periph_unlock(periph); return; } static int ndadump(void *arg, void *virtual, vm_offset_t physical, off_t offset, size_t length) { struct cam_periph *periph; struct nda_softc *softc; u_int secsize; struct ccb_nvmeio nvmeio; struct disk *dp; uint64_t lba; uint32_t count; int error = 0; dp = arg; periph = dp->d_drv1; softc = (struct nda_softc *)periph->softc; secsize = softc->disk->d_sectorsize; lba = offset / secsize; count = length / secsize; if ((periph->flags & CAM_PERIPH_INVALID) != 0) return (ENXIO); /* xpt_get_ccb returns a zero'd allocation for the ccb, mimic that here */ memset(&nvmeio, 0, sizeof(nvmeio)); if (length > 0) { xpt_setup_ccb(&nvmeio.ccb_h, periph->path, CAM_PRIORITY_NORMAL); nvmeio.ccb_h.ccb_state = NDA_CCB_DUMP; nda_nvme_write(softc, &nvmeio, virtual, lba, length, count); error = cam_periph_runccb((union ccb *)&nvmeio, cam_periph_error, 0, SF_NO_RECOVERY | SF_NO_RETRY, NULL); if (error != 0) printf("Aborting dump due to I/O error %d.\n", error); return (error); } /* Flush */ xpt_setup_ccb(&nvmeio.ccb_h, periph->path, CAM_PRIORITY_NORMAL); nvmeio.ccb_h.ccb_state = NDA_CCB_DUMP; nda_nvme_flush(softc, &nvmeio); error = cam_periph_runccb((union ccb *)&nvmeio, cam_periph_error, 0, SF_NO_RECOVERY | SF_NO_RETRY, NULL); if (error != 0) xpt_print(periph->path, "flush cmd failed\n"); return (error); } static void ndainit(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, ndaasync, NULL, NULL); if (status != CAM_REQ_CMP) { printf("nda: Failed to attach master async callback " "due to status 0x%x!\n", status); } else if (nda_send_ordered) { /* Register our event handlers */ if ((EVENTHANDLER_REGISTER(power_suspend, ndasuspend, NULL, EVENTHANDLER_PRI_LAST)) == NULL) printf("ndainit: power event registration failed!\n"); if ((EVENTHANDLER_REGISTER(shutdown_post_sync, ndashutdown, NULL, SHUTDOWN_PRI_DEFAULT)) == NULL) printf("ndainit: shutdown event registration failed!\n"); } } /* * Callback from GEOM, called when it has finished cleaning up its * resources. */ static void ndadiskgonecb(struct disk *dp) { struct cam_periph *periph; periph = (struct cam_periph *)dp->d_drv1; cam_periph_release(periph); } static void ndaoninvalidate(struct cam_periph *periph) { struct nda_softc *softc; softc = (struct nda_softc *)periph->softc; /* * De-register any async callbacks. */ xpt_register_async(0, ndaasync, periph, periph->path); #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); disk_gone(softc->disk); } static void ndacleanup(struct cam_periph *periph) { struct nda_softc *softc; softc = (struct nda_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 & NDA_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"); } disk_destroy(softc->disk); free(softc, M_DEVBUF); cam_periph_lock(periph); } static void ndaasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; periph = (struct cam_periph *)callback_arg; switch (code) { case AC_FOUND_DEVICE: { struct ccb_getdev *cgd; cam_status status; cgd = (struct ccb_getdev *)arg; if (cgd == NULL) break; if (cgd->protocol != PROTO_NVME) break; /* * Allocate a peripheral instance for * this device and start the probe * process. */ status = cam_periph_alloc(ndaregister, ndaoninvalidate, ndacleanup, ndastart, "nda", CAM_PERIPH_BIO, path, ndaasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) printf("ndaasync: Unable to attach to new device " "due to status 0x%x\n", status); break; } case AC_ADVINFO_CHANGED: { uintptr_t buftype; buftype = (uintptr_t)arg; if (buftype == CDAI_TYPE_PHYS_PATH) { struct nda_softc *softc; softc = periph->softc; disk_attr_changed(softc->disk, "GEOM::physpath", M_NOWAIT); } break; } case AC_LOST_DEVICE: default: cam_periph_async(periph, code, path, arg); break; } } static void ndasysctlinit(void *context, int pending) { struct cam_periph *periph; struct nda_softc *softc; char tmpstr[32], tmpstr2[16]; periph = (struct cam_periph *)context; /* periph was held for us when this task was enqueued */ if ((periph->flags & CAM_PERIPH_INVALID) != 0) { cam_periph_release(periph); return; } softc = (struct nda_softc *)periph->softc; snprintf(tmpstr, sizeof(tmpstr), "CAM NDA unit %d", periph->unit_number); snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number); sysctl_ctx_init(&softc->sysctl_ctx); softc->flags |= NDA_FLAG_SCTX_INIT; softc->sysctl_tree = SYSCTL_ADD_NODE_WITH_LABEL(&softc->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_kern_cam_nda), OID_AUTO, tmpstr2, CTLFLAG_RD, 0, tmpstr, "device_index"); if (softc->sysctl_tree == NULL) { printf("ndasysctlinit: unable to allocate sysctl tree\n"); cam_periph_release(periph); return; } SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "unmapped_io", CTLFLAG_RD | CTLFLAG_MPSAFE, &softc->unmappedio, 0, "Unmapped I/O leaf"); SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "rotating", CTLFLAG_RD | CTLFLAG_MPSAFE, &nda_rotating_media, 0, "Rotating media"); #ifdef CAM_IO_STATS softc->sysctl_stats_tree = SYSCTL_ADD_NODE(&softc->sysctl_stats_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "stats", CTLFLAG_RD, 0, "Statistics"); if (softc->sysctl_stats_tree == NULL) { printf("ndasysctlinit: unable to allocate sysctl tree for stats\n"); cam_periph_release(periph); return; } SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, SYSCTL_CHILDREN(softc->sysctl_stats_tree), OID_AUTO, "timeouts", CTLFLAG_RD | CTLFLAG_MPSAFE, &softc->timeouts, 0, "Device timeouts reported by the SIM"); SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, SYSCTL_CHILDREN(softc->sysctl_stats_tree), OID_AUTO, "errors", CTLFLAG_RD | CTLFLAG_MPSAFE, &softc->errors, 0, "Transport errors reported by the SIM."); SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, SYSCTL_CHILDREN(softc->sysctl_stats_tree), OID_AUTO, "pack_invalidations", CTLFLAG_RD | CTLFLAG_MPSAFE, &softc->invalidations, 0, "Device pack invalidations."); #endif cam_iosched_sysctl_init(softc->cam_iosched, &softc->sysctl_ctx, softc->sysctl_tree); cam_periph_release(periph); } static int ndagetattr(struct bio *bp) { int ret; struct cam_periph *periph; 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 cam_status ndaregister(struct cam_periph *periph, void *arg) { struct nda_softc *softc; struct disk *disk; struct ccb_pathinq cpi; const struct nvme_namespace_data *nsd; const struct nvme_controller_data *cd; char announce_buf[80]; u_int maxio; int quirks; nsd = nvme_get_identify_ns(periph); cd = nvme_get_identify_cntrl(periph); softc = (struct nda_softc *)malloc(sizeof(*softc), M_DEVBUF, M_NOWAIT | M_ZERO); if (softc == NULL) { printf("ndaregister: 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("ndaregister: Unable to probe new device. " "Unable to allocate iosched memory\n"); return(CAM_REQ_CMP_ERR); } /* ident_data parsing */ periph->softc = softc; softc->quirks = NDA_Q_NONE; xpt_path_inq(&cpi, periph->path); TASK_INIT(&softc->sysctl_task, 0, ndasysctlinit, periph); /* * The name space ID is the lun, save it for later I/O */ softc->nsid = (uint32_t)xpt_path_lun_id(periph->path); /* * Register this media as a disk */ (void)cam_periph_hold(periph, PRIBIO); cam_periph_unlock(periph); snprintf(announce_buf, sizeof(announce_buf), "kern.cam.nda.%d.quirks", periph->unit_number); quirks = softc->quirks; TUNABLE_INT_FETCH(announce_buf, &quirks); softc->quirks = quirks; cam_iosched_set_sort_queue(softc->cam_iosched, 0); softc->disk = disk = disk_alloc(); strlcpy(softc->disk->d_descr, cd->mn, MIN(sizeof(softc->disk->d_descr), sizeof(cd->mn))); strlcpy(softc->disk->d_ident, cd->sn, MIN(sizeof(softc->disk->d_ident), sizeof(cd->sn))); disk->d_rotation_rate = DISK_RR_NON_ROTATING; disk->d_open = ndaopen; disk->d_close = ndaclose; disk->d_strategy = ndastrategy; disk->d_getattr = ndagetattr; disk->d_dump = ndadump; disk->d_gone = ndadiskgonecb; disk->d_name = "nda"; disk->d_drv1 = periph; disk->d_unit = periph->unit_number; maxio = cpi.maxio; /* Honor max I/O size of SIM */ if (maxio == 0) maxio = DFLTPHYS; /* traditional default */ else if (maxio > MAXPHYS) maxio = MAXPHYS; /* for safety */ disk->d_maxsize = maxio; disk->d_sectorsize = 1 << nsd->lbaf[nsd->flbas.format].lbads; disk->d_mediasize = (off_t)(disk->d_sectorsize * nsd->nsze); disk->d_delmaxsize = disk->d_mediasize; disk->d_flags = DISKFLAG_DIRECT_COMPLETION; // if (cd->oncs.dsm) // XXX broken? disk->d_flags |= DISKFLAG_CANDELETE; if (cd->vwc.present) disk->d_flags |= DISKFLAG_CANFLUSHCACHE; if ((cpi.hba_misc & PIM_UNMAPPED) != 0) { disk->d_flags |= DISKFLAG_UNMAPPED_BIO; softc->unmappedio = 1; } /* * d_ident and d_descr are both far bigger than the length of either * the serial or model number strings. */ nvme_strvis(disk->d_descr, cd->mn, sizeof(disk->d_descr), NVME_MODEL_NUMBER_LENGTH); nvme_strvis(disk->d_ident, cd->sn, sizeof(disk->d_ident), NVME_SERIAL_NUMBER_LENGTH); disk->d_hba_vendor = cpi.hba_vendor; disk->d_hba_device = cpi.hba_device; disk->d_hba_subvendor = cpi.hba_subvendor; disk->d_hba_subdevice = cpi.hba_subdevice; disk->d_stripesize = disk->d_sectorsize; disk->d_stripeoffset = 0; disk->d_devstat = devstat_new_entry(periph->periph_name, periph->unit_number, disk->d_sectorsize, DEVSTAT_ALL_SUPPORTED, DEVSTAT_TYPE_DIRECT | XPORT_DEVSTAT_TYPE(cpi.transport), DEVSTAT_PRIORITY_DISK); /* * Add alias for older nvd drives to ease transition. */ /* disk_add_alias(disk, "nvd"); Have reports of this causing problems */ /* * Acquire a reference to the periph before we register with GEOM. * We'll release this reference once GEOM calls us back (via * ndadiskgonecb()) telling us that our provider has been freed. */ if (cam_periph_acquire(periph) != CAM_REQ_CMP) { 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); cam_periph_unhold(periph); snprintf(announce_buf, sizeof(announce_buf), "%juMB (%ju %u byte sectors)", (uintmax_t)((uintmax_t)disk->d_mediasize / (1024*1024)), (uintmax_t)disk->d_mediasize / disk->d_sectorsize, disk->d_sectorsize); xpt_announce_periph(periph, announce_buf); xpt_announce_quirks(periph, softc->quirks, NDA_Q_BIT_STRING); /* * Create our sysctl variables, now that we know * we have successfully attached. */ if (cam_periph_acquire(periph) == CAM_REQ_CMP) taskqueue_enqueue(taskqueue_thread, &softc->sysctl_task); /* * Register for device going away and info about the drive * changing (though with NVMe, it can't) */ xpt_register_async(AC_LOST_DEVICE | AC_ADVINFO_CHANGED, ndaasync, periph, periph->path); softc->state = NDA_STATE_NORMAL; return(CAM_REQ_CMP); } static void ndastart(struct cam_periph *periph, union ccb *start_ccb) { struct nda_softc *softc = (struct nda_softc *)periph->softc; struct ccb_nvmeio *nvmeio = &start_ccb->nvmeio; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ndastart\n")); switch (softc->state) { case NDA_STATE_NORMAL: { struct bio *bp; bp = cam_iosched_next_bio(softc->cam_iosched); CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ndastart: bio %p\n", bp)); if (bp == NULL) { xpt_release_ccb(start_ccb); break; } switch (bp->bio_cmd) { case BIO_WRITE: softc->flags |= NDA_FLAG_DIRTY; /* FALLTHROUGH */ case BIO_READ: { #ifdef NDA_TEST_FAILURE int fail = 0; /* * Support the failure ioctls. If the command is a * read, and there are pending forced read errors, or * if a write and pending write errors, then fail this * operation with EIO. This is useful for testing * purposes. Also, support having every Nth read fail. * * This is a rather blunt tool. */ if (bp->bio_cmd == BIO_READ) { if (softc->force_read_error) { softc->force_read_error--; fail = 1; } if (softc->periodic_read_error > 0) { if (++softc->periodic_read_count >= softc->periodic_read_error) { softc->periodic_read_count = 0; fail = 1; } } } else { if (softc->force_write_error) { softc->force_write_error--; fail = 1; } } if (fail) { biofinish(bp, NULL, EIO); xpt_release_ccb(start_ccb); ndaschedule(periph); return; } #endif KASSERT((bp->bio_flags & BIO_UNMAPPED) == 0 || round_page(bp->bio_bcount + bp->bio_ma_offset) / PAGE_SIZE == bp->bio_ma_n, ("Short bio %p", bp)); nda_nvme_rw_bio(softc, &start_ccb->nvmeio, bp, bp->bio_cmd == BIO_READ ? NVME_OPC_READ : NVME_OPC_WRITE); break; } case BIO_DELETE: { struct nvme_dsm_range *dsm_range; dsm_range = malloc(sizeof(*dsm_range), M_NVMEDA, M_ZERO | M_WAITOK); dsm_range->length = bp->bio_bcount / softc->disk->d_sectorsize; dsm_range->starting_lba = bp->bio_offset / softc->disk->d_sectorsize; bp->bio_driver2 = dsm_range; nda_nvme_trim(softc, &start_ccb->nvmeio, dsm_range, 1); start_ccb->ccb_h.ccb_state = NDA_CCB_TRIM; start_ccb->ccb_h.flags |= CAM_UNLOCKED; /* * Note: We can have multiple TRIMs in flight, so we don't call * cam_iosched_submit_trim(softc->cam_iosched); * since that forces the I/O scheduler to only schedule one at a time. * On NVMe drives, this is a performance disaster. */ goto out; } case BIO_FLUSH: nda_nvme_flush(softc, nvmeio); break; } start_ccb->ccb_h.ccb_state = NDA_CCB_BUFFER_IO; start_ccb->ccb_h.flags |= CAM_UNLOCKED; out: start_ccb->ccb_h.ccb_bp = bp; softc->outstanding_cmds++; softc->refcount++; cam_periph_unlock(periph); xpt_action(start_ccb); cam_periph_lock(periph); softc->refcount--; /* May have more work to do, so ensure we stay scheduled */ ndaschedule(periph); break; } } } static void ndadone(struct cam_periph *periph, union ccb *done_ccb) { struct nda_softc *softc; struct ccb_nvmeio *nvmeio = &done_ccb->nvmeio; struct cam_path *path; int state; softc = (struct nda_softc *)periph->softc; path = done_ccb->ccb_h.path; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("ndadone\n")); state = nvmeio->ccb_h.ccb_state & NDA_CCB_TYPE_MASK; switch (state) { case NDA_CCB_BUFFER_IO: case NDA_CCB_TRIM: { struct bio *bp; int error; 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) { error = ndaerror(done_ccb, 0, 0); if (error == ERESTART) { /* A retry was scheduled, so just return. */ cam_periph_unlock(periph); return; } if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) cam_release_devq(path, /*relsim_flags*/0, /*reduction*/0, /*timeout*/0, /*getcount_only*/0); } else { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) panic("REQ_CMP with QFRZN"); error = 0; } bp->bio_error = error; if (error != 0) { bp->bio_resid = bp->bio_bcount; bp->bio_flags |= BIO_ERROR; } else { bp->bio_resid = 0; } if (state == NDA_CCB_TRIM) free(bp->bio_driver2, M_NVMEDA); softc->outstanding_cmds--; /* * 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. */ cam_iosched_bio_complete(softc->cam_iosched, bp, done_ccb); xpt_release_ccb(done_ccb); if (state == NDA_CCB_TRIM) { #ifdef notyet TAILQ_HEAD(, bio) queue; struct bio *bp1; TAILQ_INIT(&queue); TAILQ_CONCAT(&queue, &softc->trim_req.bps, bio_queue); #endif /* * Since we can have multiple trims in flight, we don't * need to call this here. * cam_iosched_trim_done(softc->cam_iosched); */ ndaschedule(periph); cam_periph_unlock(periph); #ifdef notyet /* Not yet collapsing several BIO_DELETE requests into one TRIM */ while ((bp1 = TAILQ_FIRST(&queue)) != NULL) { TAILQ_REMOVE(&queue, bp1, bio_queue); bp1->bio_error = error; if (error != 0) { bp1->bio_flags |= BIO_ERROR; bp1->bio_resid = bp1->bio_bcount; } else bp1->bio_resid = 0; biodone(bp1); } #else biodone(bp); #endif } else { ndaschedule(periph); cam_periph_unlock(periph); biodone(bp); } return; } case NDA_CCB_DUMP: /* No-op. We're polling */ return; default: break; } xpt_release_ccb(done_ccb); } static int ndaerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { struct nda_softc *softc; struct cam_periph *periph; periph = xpt_path_periph(ccb->ccb_h.path); softc = (struct nda_softc *)periph->softc; switch (ccb->ccb_h.status & CAM_STATUS_MASK) { case CAM_CMD_TIMEOUT: #ifdef CAM_IO_STATS softc->timeouts++; #endif break; case CAM_REQ_ABORTED: case CAM_REQ_CMP_ERR: case CAM_REQ_TERMIO: case CAM_UNREC_HBA_ERROR: case CAM_DATA_RUN_ERR: case CAM_ATA_STATUS_ERROR: #ifdef CAM_IO_STATS softc->errors++; #endif break; default: break; } return(cam_periph_error(ccb, cam_flags, sense_flags)); } /* * Step through all NDA peripheral drivers, and if the device is still open, * sync the disk cache to physical media. */ static void ndaflush(void) { struct cam_periph *periph; struct nda_softc *softc; union ccb *ccb; int error; CAM_PERIPH_FOREACH(periph, &ndadriver) { softc = (struct nda_softc *)periph->softc; if (SCHEDULER_STOPPED()) { /* If we paniced with the lock held, do not recurse. */ if (!cam_periph_owned(periph) && (softc->flags & NDA_FLAG_OPEN)) { ndadump(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 & NDA_FLAG_OPEN) == 0) { cam_periph_unlock(periph); continue; } ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); nda_nvme_flush(softc, &ccb->nvmeio); error = cam_periph_runccb(ccb, ndaerror, /*cam_flags*/0, /*sense_flags*/ SF_NO_RECOVERY | SF_NO_RETRY, softc->disk->d_devstat); if (error != 0) xpt_print(periph->path, "Synchronize cache failed\n"); xpt_release_ccb(ccb); cam_periph_unlock(periph); } } static void ndashutdown(void *arg, int howto) { ndaflush(); } static void ndasuspend(void *arg) { ndaflush(); } Index: head/sys/cam/nvme/nvme_xpt.c =================================================================== --- head/sys/cam/nvme/nvme_xpt.c (revision 328069) +++ head/sys/cam/nvme/nvme_xpt.c (revision 328070) @@ -1,672 +1,674 @@ /*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * * Copyright (c) 2015 Netflix, Inc. * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. * * derived from ata_xpt.c: Copyright (c) 2009 Alexander Motin */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for xpt_print below */ #include "opt_cam.h" struct nvme_quirk_entry { u_int quirks; #define CAM_QUIRK_MAXTAGS 1 u_int mintags; u_int maxtags; }; /* Not even sure why we need this */ static periph_init_t nvme_probe_periph_init; static struct periph_driver nvme_probe_driver = { nvme_probe_periph_init, "nvme_probe", TAILQ_HEAD_INITIALIZER(nvme_probe_driver.units), /* generation */ 0, CAM_PERIPH_DRV_EARLY }; PERIPHDRIVER_DECLARE(nvme_probe, nvme_probe_driver); typedef enum { NVME_PROBE_IDENTIFY, NVME_PROBE_DONE, NVME_PROBE_INVALID, NVME_PROBE_RESET } nvme_probe_action; static char *nvme_probe_action_text[] = { "NVME_PROBE_IDENTIFY", "NVME_PROBE_DONE", "NVME_PROBE_INVALID", "NVME_PROBE_RESET", }; #define NVME_PROBE_SET_ACTION(softc, newaction) \ do { \ char **text; \ text = nvme_probe_action_text; \ CAM_DEBUG((softc)->periph->path, CAM_DEBUG_PROBE, \ ("Probe %s to %s\n", text[(softc)->action], \ text[(newaction)])); \ (softc)->action = (newaction); \ } while(0) typedef enum { NVME_PROBE_NO_ANNOUNCE = 0x04 } nvme_probe_flags; typedef struct { TAILQ_HEAD(, ccb_hdr) request_ccbs; nvme_probe_action action; nvme_probe_flags flags; int restart; struct cam_periph *periph; } nvme_probe_softc; static struct nvme_quirk_entry nvme_quirk_table[] = { { // { // T_ANY, SIP_MEDIA_REMOVABLE|SIP_MEDIA_FIXED, // /*vendor*/"*", /*product*/"*", /*revision*/"*" // }, .quirks = 0, .mintags = 0, .maxtags = 0 }, }; static const int nvme_quirk_table_size = sizeof(nvme_quirk_table) / sizeof(*nvme_quirk_table); static cam_status nvme_probe_register(struct cam_periph *periph, void *arg); static void nvme_probe_schedule(struct cam_periph *nvme_probe_periph); static void nvme_probe_start(struct cam_periph *periph, union ccb *start_ccb); static void nvme_probe_cleanup(struct cam_periph *periph); //static void nvme_find_quirk(struct cam_ed *device); static void nvme_scan_lun(struct cam_periph *periph, struct cam_path *path, cam_flags flags, union ccb *ccb); static struct cam_ed * nvme_alloc_device(struct cam_eb *bus, struct cam_et *target, lun_id_t lun_id); static void nvme_device_transport(struct cam_path *path); static void nvme_dev_async(u_int32_t async_code, struct cam_eb *bus, struct cam_et *target, struct cam_ed *device, void *async_arg); static void nvme_action(union ccb *start_ccb); static void nvme_announce_periph(struct cam_periph *periph); static void nvme_proto_announce(struct cam_ed *device); static void nvme_proto_denounce(struct cam_ed *device); static void nvme_proto_debug_out(union ccb *ccb); static struct xpt_xport_ops nvme_xport_ops = { .alloc_device = nvme_alloc_device, .action = nvme_action, .async = nvme_dev_async, .announce = nvme_announce_periph, }; #define NVME_XPT_XPORT(x, X) \ static struct xpt_xport nvme_xport_ ## x = { \ .xport = XPORT_ ## X, \ .name = #x, \ .ops = &nvme_xport_ops, \ }; \ CAM_XPT_XPORT(nvme_xport_ ## x); NVME_XPT_XPORT(nvme, NVME); #undef NVME_XPT_XPORT static struct xpt_proto_ops nvme_proto_ops = { .announce = nvme_proto_announce, .denounce = nvme_proto_denounce, .debug_out = nvme_proto_debug_out, }; static struct xpt_proto nvme_proto = { .proto = PROTO_NVME, .name = "nvme", .ops = &nvme_proto_ops, }; CAM_XPT_PROTO(nvme_proto); static void nvme_probe_periph_init() { } static cam_status nvme_probe_register(struct cam_periph *periph, void *arg) { union ccb *request_ccb; /* CCB representing the probe request */ cam_status status; nvme_probe_softc *softc; request_ccb = (union ccb *)arg; if (request_ccb == NULL) { printf("nvme_probe_register: no probe CCB, " "can't register device\n"); return(CAM_REQ_CMP_ERR); } softc = (nvme_probe_softc *)malloc(sizeof(*softc), M_CAMXPT, M_ZERO | M_NOWAIT); if (softc == NULL) { printf("nvme_probe_register: Unable to probe new device. " "Unable to allocate softc\n"); return(CAM_REQ_CMP_ERR); } TAILQ_INIT(&softc->request_ccbs); TAILQ_INSERT_TAIL(&softc->request_ccbs, &request_ccb->ccb_h, periph_links.tqe); softc->flags = 0; periph->softc = softc; softc->periph = periph; softc->action = NVME_PROBE_INVALID; status = cam_periph_acquire(periph); if (status != CAM_REQ_CMP) { return (status); } CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Probe started\n")); // nvme_device_transport(periph->path); nvme_probe_schedule(periph); return(CAM_REQ_CMP); } static void nvme_probe_schedule(struct cam_periph *periph) { union ccb *ccb; nvme_probe_softc *softc; softc = (nvme_probe_softc *)periph->softc; ccb = (union ccb *)TAILQ_FIRST(&softc->request_ccbs); NVME_PROBE_SET_ACTION(softc, NVME_PROBE_IDENTIFY); if (ccb->crcn.flags & CAM_EXPECT_INQ_CHANGE) softc->flags |= NVME_PROBE_NO_ANNOUNCE; else softc->flags &= ~NVME_PROBE_NO_ANNOUNCE; xpt_schedule(periph, CAM_PRIORITY_XPT); } static void nvme_probe_start(struct cam_periph *periph, union ccb *start_ccb) { struct ccb_nvmeio *nvmeio; struct ccb_scsiio *csio; nvme_probe_softc *softc; struct cam_path *path; const struct nvme_namespace_data *nvme_data; lun_id_t lun; CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("nvme_probe_start\n")); softc = (nvme_probe_softc *)periph->softc; path = start_ccb->ccb_h.path; nvmeio = &start_ccb->nvmeio; csio = &start_ccb->csio; nvme_data = periph->path->device->nvme_data; if (softc->restart) { softc->restart = 0; if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) NVME_PROBE_SET_ACTION(softc, NVME_PROBE_RESET); else NVME_PROBE_SET_ACTION(softc, NVME_PROBE_IDENTIFY); } /* * Other transports have to ask their SIM to do a lot of action. * NVMe doesn't, so don't do the dance. Just do things * directly. */ switch (softc->action) { case NVME_PROBE_RESET: /* FALLTHROUGH */ case NVME_PROBE_IDENTIFY: nvme_device_transport(path); /* * Test for lun == CAM_LUN_WILDCARD is lame, but * appears to be necessary here. XXX */ lun = xpt_path_lun_id(periph->path); if (lun == CAM_LUN_WILDCARD || periph->path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); start_ccb->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action(start_ccb); xpt_async(AC_FOUND_DEVICE, path, start_ccb); } NVME_PROBE_SET_ACTION(softc, NVME_PROBE_DONE); break; default: panic("nvme_probe_start: invalid action state 0x%x\n", softc->action); } /* * Probing is now done. We need to complete any lingering items * in the queue, though there shouldn't be any. */ xpt_release_ccb(start_ccb); CAM_DEBUG(periph->path, CAM_DEBUG_PROBE, ("Probe completed\n")); while ((start_ccb = (union ccb *)TAILQ_FIRST(&softc->request_ccbs))) { TAILQ_REMOVE(&softc->request_ccbs, &start_ccb->ccb_h, periph_links.tqe); start_ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(start_ccb); } cam_periph_invalidate(periph); cam_periph_release_locked(periph); } static void nvme_probe_cleanup(struct cam_periph *periph) { free(periph->softc, M_CAMXPT); } #if 0 /* XXX should be used, don't delete */ static void nvme_find_quirk(struct cam_ed *device) { struct nvme_quirk_entry *quirk; caddr_t match; match = cam_quirkmatch((caddr_t)&device->nvme_data, (caddr_t)nvme_quirk_table, nvme_quirk_table_size, sizeof(*nvme_quirk_table), nvme_identify_match); if (match == NULL) panic("xpt_find_quirk: device didn't match wildcard entry!!"); quirk = (struct nvme_quirk_entry *)match; device->quirk = quirk; if (quirk->quirks & CAM_QUIRK_MAXTAGS) { device->mintags = quirk->mintags; device->maxtags = quirk->maxtags; } } #endif static void nvme_scan_lun(struct cam_periph *periph, struct cam_path *path, cam_flags flags, union ccb *request_ccb) { struct ccb_pathinq cpi; cam_status status; struct cam_periph *old_periph; int lock; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("nvme_scan_lun\n")); xpt_path_inq(&cpi, path); if (cpi.ccb_h.status != CAM_REQ_CMP) { if (request_ccb != NULL) { request_ccb->ccb_h.status = cpi.ccb_h.status; xpt_done(request_ccb); } return; } if (xpt_path_lun_id(path) == CAM_LUN_WILDCARD) { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("nvme_scan_lun ignoring bus\n")); request_ccb->ccb_h.status = CAM_REQ_CMP; /* XXX signal error ? */ xpt_done(request_ccb); return; } lock = (xpt_path_owned(path) == 0); if (lock) xpt_path_lock(path); if ((old_periph = cam_periph_find(path, "nvme_probe")) != NULL) { if ((old_periph->flags & CAM_PERIPH_INVALID) == 0) { nvme_probe_softc *softc; softc = (nvme_probe_softc *)old_periph->softc; TAILQ_INSERT_TAIL(&softc->request_ccbs, &request_ccb->ccb_h, periph_links.tqe); softc->restart = 1; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("restarting nvme_probe device\n")); } else { request_ccb->ccb_h.status = CAM_REQ_CMP_ERR; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("Failing to restart nvme_probe device\n")); xpt_done(request_ccb); } } else { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("Adding nvme_probe device\n")); status = cam_periph_alloc(nvme_probe_register, NULL, nvme_probe_cleanup, nvme_probe_start, "nvme_probe", CAM_PERIPH_BIO, request_ccb->ccb_h.path, NULL, 0, request_ccb); if (status != CAM_REQ_CMP) { xpt_print(path, "xpt_scan_lun: cam_alloc_periph " "returned an error, can't continue probe\n"); request_ccb->ccb_h.status = status; xpt_done(request_ccb); } } if (lock) xpt_path_unlock(path); } static struct cam_ed * nvme_alloc_device(struct cam_eb *bus, struct cam_et *target, lun_id_t lun_id) { struct nvme_quirk_entry *quirk; struct cam_ed *device; device = xpt_alloc_device(bus, target, lun_id); if (device == NULL) return (NULL); /* * Take the default quirk entry until we have inquiry * data from nvme and can determine a better quirk to use. */ quirk = &nvme_quirk_table[nvme_quirk_table_size - 1]; device->quirk = (void *)quirk; device->mintags = 0; device->maxtags = 0; device->inq_flags = 0; device->queue_flags = 0; device->device_id = NULL; /* XXX Need to set this somewhere */ device->device_id_len = 0; device->serial_num = NULL; /* XXX Need to set this somewhere */ device->serial_num_len = 0; return (device); } static void nvme_device_transport(struct cam_path *path) { struct ccb_pathinq cpi; struct ccb_trans_settings cts; /* XXX get data from nvme namespace and other info ??? */ /* Get transport information from the SIM */ xpt_path_inq(&cpi, path); path->device->transport = cpi.transport; path->device->transport_version = cpi.transport_version; path->device->protocol = cpi.protocol; path->device->protocol_version = cpi.protocol_version; /* Tell the controller what we think */ xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NONE); cts.ccb_h.func_code = XPT_SET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; cts.transport = path->device->transport; cts.transport_version = path->device->transport_version; cts.protocol = path->device->protocol; cts.protocol_version = path->device->protocol_version; cts.proto_specific.valid = 0; cts.xport_specific.valid = 0; xpt_action((union ccb *)&cts); } static void nvme_dev_advinfo(union ccb *start_ccb) { struct cam_ed *device; struct ccb_dev_advinfo *cdai; off_t amt; start_ccb->ccb_h.status = CAM_REQ_INVALID; device = start_ccb->ccb_h.path->device; cdai = &start_ccb->cdai; switch(cdai->buftype) { case CDAI_TYPE_SCSI_DEVID: if (cdai->flags & CDAI_FLAG_STORE) return; cdai->provsiz = device->device_id_len; if (device->device_id_len == 0) break; amt = device->device_id_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->device_id, amt); break; case CDAI_TYPE_SERIAL_NUM: if (cdai->flags & CDAI_FLAG_STORE) return; cdai->provsiz = device->serial_num_len; if (device->serial_num_len == 0) break; amt = device->serial_num_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->serial_num, amt); break; case CDAI_TYPE_PHYS_PATH: if (cdai->flags & CDAI_FLAG_STORE) { if (device->physpath != NULL) free(device->physpath, M_CAMXPT); device->physpath_len = cdai->bufsiz; /* Clear existing buffer if zero length */ if (cdai->bufsiz == 0) break; device->physpath = malloc(cdai->bufsiz, M_CAMXPT, M_NOWAIT); if (device->physpath == NULL) { start_ccb->ccb_h.status = CAM_REQ_ABORTED; return; } memcpy(device->physpath, cdai->buf, cdai->bufsiz); } else { cdai->provsiz = device->physpath_len; if (device->physpath_len == 0) break; amt = device->physpath_len; if (cdai->provsiz > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->physpath, amt); } break; case CDAI_TYPE_NVME_CNTRL: if (cdai->flags & CDAI_FLAG_STORE) return; amt = sizeof(struct nvme_controller_data); cdai->provsiz = amt; if (amt > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->nvme_cdata, amt); break; case CDAI_TYPE_NVME_NS: if (cdai->flags & CDAI_FLAG_STORE) return; amt = sizeof(struct nvme_namespace_data); cdai->provsiz = amt; if (amt > cdai->bufsiz) amt = cdai->bufsiz; memcpy(cdai->buf, device->nvme_data, amt); break; default: return; } start_ccb->ccb_h.status = CAM_REQ_CMP; if (cdai->flags & CDAI_FLAG_STORE) { xpt_async(AC_ADVINFO_CHANGED, start_ccb->ccb_h.path, (void *)(uintptr_t)cdai->buftype); } } static void nvme_action(union ccb *start_ccb) { CAM_DEBUG(start_ccb->ccb_h.path, CAM_DEBUG_TRACE, ("nvme_action: func= %#x\n", start_ccb->ccb_h.func_code)); switch (start_ccb->ccb_h.func_code) { case XPT_SCAN_BUS: case XPT_SCAN_TGT: case XPT_SCAN_LUN: nvme_scan_lun(start_ccb->ccb_h.path->periph, start_ccb->ccb_h.path, start_ccb->crcn.flags, start_ccb); break; case XPT_DEV_ADVINFO: nvme_dev_advinfo(start_ccb); break; default: xpt_action_default(start_ccb); break; } } /* * Handle any per-device event notifications that require action by the XPT. */ static void nvme_dev_async(u_int32_t async_code, struct cam_eb *bus, struct cam_et *target, struct cam_ed *device, void *async_arg) { /* * We only need to handle events for real devices. */ if (target->target_id == CAM_TARGET_WILDCARD || device->lun_id == CAM_LUN_WILDCARD) return; if (async_code == AC_LOST_DEVICE && (device->flags & CAM_DEV_UNCONFIGURED) == 0) { device->flags |= CAM_DEV_UNCONFIGURED; xpt_release_device(device); } } static void nvme_announce_periph(struct cam_periph *periph) { struct ccb_pathinq cpi; struct ccb_trans_settings cts; struct cam_path *path = periph->path; struct ccb_trans_settings_nvme *nvmex; cam_periph_assert(periph, MA_OWNED); /* Ask the SIM for connection details */ xpt_setup_ccb(&cts.ccb_h, path, CAM_PRIORITY_NORMAL); cts.ccb_h.func_code = XPT_GET_TRAN_SETTINGS; cts.type = CTS_TYPE_CURRENT_SETTINGS; xpt_action((union ccb*)&cts); if ((cts.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) return; nvmex = &cts.xport_specific.nvme; /* Ask the SIM for its base transfer speed */ xpt_path_inq(&cpi, periph->path); printf("%s%d: nvme version %d.%d x%d (max x%d) lanes PCIe Gen%d (max Gen%d) link", periph->periph_name, periph->unit_number, NVME_MAJOR(nvmex->spec), NVME_MINOR(nvmex->spec), nvmex->lanes, nvmex->max_lanes, nvmex->speed, nvmex->max_speed); printf("\n"); } static void nvme_proto_announce(struct cam_ed *device) { struct sbuf sb; char buffer[120]; sbuf_new(&sb, buffer, sizeof(buffer), SBUF_FIXEDLEN); nvme_print_ident(device->nvme_cdata, device->nvme_data, &sb); sbuf_finish(&sb); sbuf_putbuf(&sb); } static void nvme_proto_denounce(struct cam_ed *device) { nvme_proto_announce(device); } static void nvme_proto_debug_out(union ccb *ccb) { char cdb_str[(sizeof(struct nvme_command) * 3) + 1]; if (ccb->ccb_h.func_code != XPT_NVME_IO) return; CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_CDB,("%s. NCB: %s\n", nvme_op_string(&ccb->nvmeio.cmd), nvme_cmd_string(&ccb->nvmeio.cmd, cdb_str, sizeof(cdb_str)))); } Index: head/sys/cam/scsi/scsi_all.c =================================================================== --- head/sys/cam/scsi/scsi_all.c (revision 328069) +++ head/sys/cam/scsi/scsi_all.c (revision 328070) @@ -1,9233 +1,9233 @@ /*- - * SPDX-License-Identifier: BSD-2-Clause-FreeBSD - * * Implementation of Utility functions for all SCSI device types. + * + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1997, 1998, 1999 Justin T. Gibbs. * Copyright (c) 1997, 1998, 2003 Kenneth D. Merry. * 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 __FBSDID("$FreeBSD$"); #include #include #include #ifdef _KERNEL #include "opt_scsi.h" #include #include #include #include #include #include #include #include #else #include #include #include #include #include #endif #include #include #include #include #include #include #include #ifdef _KERNEL #include #include #include #include #else #include #include #ifndef FALSE #define FALSE 0 #endif /* FALSE */ #ifndef TRUE #define TRUE 1 #endif /* TRUE */ #define ERESTART -1 /* restart syscall */ #define EJUSTRETURN -2 /* don't modify regs, just return */ #endif /* !_KERNEL */ /* * This is the default number of milliseconds we wait for devices to settle * after a SCSI bus reset. */ #ifndef SCSI_DELAY #define SCSI_DELAY 2000 #endif /* * All devices need _some_ sort of bus settle delay, so we'll set it to * a minimum value of 100ms. Note that this is pertinent only for SPI- * not transport like Fibre Channel or iSCSI where 'delay' is completely * meaningless. */ #ifndef SCSI_MIN_DELAY #define SCSI_MIN_DELAY 100 #endif /* * Make sure the user isn't using seconds instead of milliseconds. */ #if (SCSI_DELAY < SCSI_MIN_DELAY && SCSI_DELAY != 0) #error "SCSI_DELAY is in milliseconds, not seconds! Please use a larger value" #endif int scsi_delay; static int ascentrycomp(const void *key, const void *member); static int senseentrycomp(const void *key, const void *member); static void fetchtableentries(int sense_key, int asc, int ascq, struct scsi_inquiry_data *, const struct sense_key_table_entry **, const struct asc_table_entry **); #ifdef _KERNEL static void init_scsi_delay(void); static int sysctl_scsi_delay(SYSCTL_HANDLER_ARGS); static int set_scsi_delay(int delay); #endif #if !defined(SCSI_NO_OP_STRINGS) #define D (1 << T_DIRECT) #define T (1 << T_SEQUENTIAL) #define L (1 << T_PRINTER) #define P (1 << T_PROCESSOR) #define W (1 << T_WORM) #define R (1 << T_CDROM) #define O (1 << T_OPTICAL) #define M (1 << T_CHANGER) #define A (1 << T_STORARRAY) #define E (1 << T_ENCLOSURE) #define B (1 << T_RBC) #define K (1 << T_OCRW) #define V (1 << T_ADC) #define F (1 << T_OSD) #define S (1 << T_SCANNER) #define C (1 << T_COMM) #define ALL (D | T | L | P | W | R | O | M | A | E | B | K | V | F | S | C) static struct op_table_entry plextor_cd_ops[] = { { 0xD8, R, "CD-DA READ" } }; static struct scsi_op_quirk_entry scsi_op_quirk_table[] = { { /* * I believe that 0xD8 is the Plextor proprietary command * to read CD-DA data. I'm not sure which Plextor CDROM * models support the command, though. I know for sure * that the 4X, 8X, and 12X models do, and presumably the * 12-20X does. I don't know about any earlier models, * though. If anyone has any more complete information, * feel free to change this quirk entry. */ {T_CDROM, SIP_MEDIA_REMOVABLE, "PLEXTOR", "CD-ROM PX*", "*"}, nitems(plextor_cd_ops), plextor_cd_ops } }; static struct op_table_entry scsi_op_codes[] = { /* * From: http://www.t10.org/lists/op-num.txt * Modifications by Kenneth Merry (ken@FreeBSD.ORG) * and Jung-uk Kim (jkim@FreeBSD.org) * * Note: order is important in this table, scsi_op_desc() currently * depends on the opcodes in the table being in order to save * search time. * Note: scanner and comm. devices are carried over from the previous * version because they were removed in the latest spec. */ /* File: OP-NUM.TXT * * SCSI Operation Codes * Numeric Sorted Listing * as of 5/26/15 * * D - DIRECT ACCESS DEVICE (SBC-2) device column key * .T - SEQUENTIAL ACCESS DEVICE (SSC-2) ----------------- * . L - PRINTER DEVICE (SSC) M = Mandatory * . P - PROCESSOR DEVICE (SPC) O = Optional * . .W - WRITE ONCE READ MULTIPLE DEVICE (SBC-2) V = Vendor spec. * . . R - CD/DVE DEVICE (MMC-3) Z = Obsolete * . . O - OPTICAL MEMORY DEVICE (SBC-2) * . . .M - MEDIA CHANGER DEVICE (SMC-2) * . . . A - STORAGE ARRAY DEVICE (SCC-2) * . . . .E - ENCLOSURE SERVICES DEVICE (SES) * . . . .B - SIMPLIFIED DIRECT-ACCESS DEVICE (RBC) * . . . . K - OPTICAL CARD READER/WRITER DEVICE (OCRW) * . . . . V - AUTOMATION/DRIVE INTERFACE (ADC) * . . . . .F - OBJECT-BASED STORAGE (OSD) * OP DTLPWROMAEBKVF Description * -- -------------- ---------------------------------------------- */ /* 00 MMMMMMMMMMMMMM TEST UNIT READY */ { 0x00, ALL, "TEST UNIT READY" }, /* 01 M REWIND */ { 0x01, T, "REWIND" }, /* 01 Z V ZZZZ REZERO UNIT */ { 0x01, D | W | R | O | M, "REZERO UNIT" }, /* 02 VVVVVV V */ /* 03 MMMMMMMMMMOMMM REQUEST SENSE */ { 0x03, ALL, "REQUEST SENSE" }, /* 04 M OO FORMAT UNIT */ { 0x04, D | R | O, "FORMAT UNIT" }, /* 04 O FORMAT MEDIUM */ { 0x04, T, "FORMAT MEDIUM" }, /* 04 O FORMAT */ { 0x04, L, "FORMAT" }, /* 05 VMVVVV V READ BLOCK LIMITS */ { 0x05, T, "READ BLOCK LIMITS" }, /* 06 VVVVVV V */ /* 07 OVV O OV REASSIGN BLOCKS */ { 0x07, D | W | O, "REASSIGN BLOCKS" }, /* 07 O INITIALIZE ELEMENT STATUS */ { 0x07, M, "INITIALIZE ELEMENT STATUS" }, /* 08 MOV O OV READ(6) */ { 0x08, D | T | W | O, "READ(6)" }, /* 08 O RECEIVE */ { 0x08, P, "RECEIVE" }, /* 08 GET MESSAGE(6) */ { 0x08, C, "GET MESSAGE(6)" }, /* 09 VVVVVV V */ /* 0A OO O OV WRITE(6) */ { 0x0A, D | T | W | O, "WRITE(6)" }, /* 0A M SEND(6) */ { 0x0A, P, "SEND(6)" }, /* 0A SEND MESSAGE(6) */ { 0x0A, C, "SEND MESSAGE(6)" }, /* 0A M PRINT */ { 0x0A, L, "PRINT" }, /* 0B Z ZOZV SEEK(6) */ { 0x0B, D | W | R | O, "SEEK(6)" }, /* 0B O SET CAPACITY */ { 0x0B, T, "SET CAPACITY" }, /* 0B O SLEW AND PRINT */ { 0x0B, L, "SLEW AND PRINT" }, /* 0C VVVVVV V */ /* 0D VVVVVV V */ /* 0E VVVVVV V */ /* 0F VOVVVV V READ REVERSE(6) */ { 0x0F, T, "READ REVERSE(6)" }, /* 10 VM VVV WRITE FILEMARKS(6) */ { 0x10, T, "WRITE FILEMARKS(6)" }, /* 10 O SYNCHRONIZE BUFFER */ { 0x10, L, "SYNCHRONIZE BUFFER" }, /* 11 VMVVVV SPACE(6) */ { 0x11, T, "SPACE(6)" }, /* 12 MMMMMMMMMMMMMM INQUIRY */ { 0x12, ALL, "INQUIRY" }, /* 13 V VVVV */ /* 13 O VERIFY(6) */ { 0x13, T, "VERIFY(6)" }, /* 14 VOOVVV RECOVER BUFFERED DATA */ { 0x14, T | L, "RECOVER BUFFERED DATA" }, /* 15 OMO O OOOO OO MODE SELECT(6) */ { 0x15, ALL & ~(P | R | B | F), "MODE SELECT(6)" }, /* 16 ZZMZO OOOZ O RESERVE(6) */ { 0x16, ALL & ~(R | B | V | F | C), "RESERVE(6)" }, /* 16 Z RESERVE ELEMENT(6) */ { 0x16, M, "RESERVE ELEMENT(6)" }, /* 17 ZZMZO OOOZ O RELEASE(6) */ { 0x17, ALL & ~(R | B | V | F | C), "RELEASE(6)" }, /* 17 Z RELEASE ELEMENT(6) */ { 0x17, M, "RELEASE ELEMENT(6)" }, /* 18 ZZZZOZO Z COPY */ { 0x18, D | T | L | P | W | R | O | K | S, "COPY" }, /* 19 VMVVVV ERASE(6) */ { 0x19, T, "ERASE(6)" }, /* 1A OMO O OOOO OO MODE SENSE(6) */ { 0x1A, ALL & ~(P | R | B | F), "MODE SENSE(6)" }, /* 1B O OOO O MO O START STOP UNIT */ { 0x1B, D | W | R | O | A | B | K | F, "START STOP UNIT" }, /* 1B O M LOAD UNLOAD */ { 0x1B, T | V, "LOAD UNLOAD" }, /* 1B SCAN */ { 0x1B, S, "SCAN" }, /* 1B O STOP PRINT */ { 0x1B, L, "STOP PRINT" }, /* 1B O OPEN/CLOSE IMPORT/EXPORT ELEMENT */ { 0x1B, M, "OPEN/CLOSE IMPORT/EXPORT ELEMENT" }, /* 1C OOOOO OOOM OOO RECEIVE DIAGNOSTIC RESULTS */ { 0x1C, ALL & ~(R | B), "RECEIVE DIAGNOSTIC RESULTS" }, /* 1D MMMMM MMOM MMM SEND DIAGNOSTIC */ { 0x1D, ALL & ~(R | B), "SEND DIAGNOSTIC" }, /* 1E OO OOOO O O PREVENT ALLOW MEDIUM REMOVAL */ { 0x1E, D | T | W | R | O | M | K | F, "PREVENT ALLOW MEDIUM REMOVAL" }, /* 1F */ /* 20 V VVV V */ /* 21 V VVV V */ /* 22 V VVV V */ /* 23 V V V V */ /* 23 O READ FORMAT CAPACITIES */ { 0x23, R, "READ FORMAT CAPACITIES" }, /* 24 V VV SET WINDOW */ { 0x24, S, "SET WINDOW" }, /* 25 M M M M READ CAPACITY(10) */ { 0x25, D | W | O | B, "READ CAPACITY(10)" }, /* 25 O READ CAPACITY */ { 0x25, R, "READ CAPACITY" }, /* 25 M READ CARD CAPACITY */ { 0x25, K, "READ CARD CAPACITY" }, /* 25 GET WINDOW */ { 0x25, S, "GET WINDOW" }, /* 26 V VV */ /* 27 V VV */ /* 28 M MOM MM READ(10) */ { 0x28, D | W | R | O | B | K | S, "READ(10)" }, /* 28 GET MESSAGE(10) */ { 0x28, C, "GET MESSAGE(10)" }, /* 29 V VVO READ GENERATION */ { 0x29, O, "READ GENERATION" }, /* 2A O MOM MO WRITE(10) */ { 0x2A, D | W | R | O | B | K, "WRITE(10)" }, /* 2A SEND(10) */ { 0x2A, S, "SEND(10)" }, /* 2A SEND MESSAGE(10) */ { 0x2A, C, "SEND MESSAGE(10)" }, /* 2B Z OOO O SEEK(10) */ { 0x2B, D | W | R | O | K, "SEEK(10)" }, /* 2B O LOCATE(10) */ { 0x2B, T, "LOCATE(10)" }, /* 2B O POSITION TO ELEMENT */ { 0x2B, M, "POSITION TO ELEMENT" }, /* 2C V OO ERASE(10) */ { 0x2C, R | O, "ERASE(10)" }, /* 2D O READ UPDATED BLOCK */ { 0x2D, O, "READ UPDATED BLOCK" }, /* 2D V */ /* 2E O OOO MO WRITE AND VERIFY(10) */ { 0x2E, D | W | R | O | B | K, "WRITE AND VERIFY(10)" }, /* 2F O OOO VERIFY(10) */ { 0x2F, D | W | R | O, "VERIFY(10)" }, /* 30 Z ZZZ SEARCH DATA HIGH(10) */ { 0x30, D | W | R | O, "SEARCH DATA HIGH(10)" }, /* 31 Z ZZZ SEARCH DATA EQUAL(10) */ { 0x31, D | W | R | O, "SEARCH DATA EQUAL(10)" }, /* 31 OBJECT POSITION */ { 0x31, S, "OBJECT POSITION" }, /* 32 Z ZZZ SEARCH DATA LOW(10) */ { 0x32, D | W | R | O, "SEARCH DATA LOW(10)" }, /* 33 Z OZO SET LIMITS(10) */ { 0x33, D | W | R | O, "SET LIMITS(10)" }, /* 34 O O O O PRE-FETCH(10) */ { 0x34, D | W | O | K, "PRE-FETCH(10)" }, /* 34 M READ POSITION */ { 0x34, T, "READ POSITION" }, /* 34 GET DATA BUFFER STATUS */ { 0x34, S, "GET DATA BUFFER STATUS" }, /* 35 O OOO MO SYNCHRONIZE CACHE(10) */ { 0x35, D | W | R | O | B | K, "SYNCHRONIZE CACHE(10)" }, /* 36 Z O O O LOCK UNLOCK CACHE(10) */ { 0x36, D | W | O | K, "LOCK UNLOCK CACHE(10)" }, /* 37 O O READ DEFECT DATA(10) */ { 0x37, D | O, "READ DEFECT DATA(10)" }, /* 37 O INITIALIZE ELEMENT STATUS WITH RANGE */ { 0x37, M, "INITIALIZE ELEMENT STATUS WITH RANGE" }, /* 38 O O O MEDIUM SCAN */ { 0x38, W | O | K, "MEDIUM SCAN" }, /* 39 ZZZZOZO Z COMPARE */ { 0x39, D | T | L | P | W | R | O | K | S, "COMPARE" }, /* 3A ZZZZOZO Z COPY AND VERIFY */ { 0x3A, D | T | L | P | W | R | O | K | S, "COPY AND VERIFY" }, /* 3B OOOOOOOOOOMOOO WRITE BUFFER */ { 0x3B, ALL, "WRITE BUFFER" }, /* 3C OOOOOOOOOO OOO READ BUFFER */ { 0x3C, ALL & ~(B), "READ BUFFER" }, /* 3D O UPDATE BLOCK */ { 0x3D, O, "UPDATE BLOCK" }, /* 3E O O O READ LONG(10) */ { 0x3E, D | W | O, "READ LONG(10)" }, /* 3F O O O WRITE LONG(10) */ { 0x3F, D | W | O, "WRITE LONG(10)" }, /* 40 ZZZZOZOZ CHANGE DEFINITION */ { 0x40, D | T | L | P | W | R | O | M | S | C, "CHANGE DEFINITION" }, /* 41 O WRITE SAME(10) */ { 0x41, D, "WRITE SAME(10)" }, /* 42 O UNMAP */ { 0x42, D, "UNMAP" }, /* 42 O READ SUB-CHANNEL */ { 0x42, R, "READ SUB-CHANNEL" }, /* 43 O READ TOC/PMA/ATIP */ { 0x43, R, "READ TOC/PMA/ATIP" }, /* 44 M M REPORT DENSITY SUPPORT */ { 0x44, T | V, "REPORT DENSITY SUPPORT" }, /* 44 READ HEADER */ /* 45 O PLAY AUDIO(10) */ { 0x45, R, "PLAY AUDIO(10)" }, /* 46 M GET CONFIGURATION */ { 0x46, R, "GET CONFIGURATION" }, /* 47 O PLAY AUDIO MSF */ { 0x47, R, "PLAY AUDIO MSF" }, /* 48 */ /* 49 */ /* 4A M GET EVENT STATUS NOTIFICATION */ { 0x4A, R, "GET EVENT STATUS NOTIFICATION" }, /* 4B O PAUSE/RESUME */ { 0x4B, R, "PAUSE/RESUME" }, /* 4C OOOOO OOOO OOO LOG SELECT */ { 0x4C, ALL & ~(R | B), "LOG SELECT" }, /* 4D OOOOO OOOO OMO LOG SENSE */ { 0x4D, ALL & ~(R | B), "LOG SENSE" }, /* 4E O STOP PLAY/SCAN */ { 0x4E, R, "STOP PLAY/SCAN" }, /* 4F */ /* 50 O XDWRITE(10) */ { 0x50, D, "XDWRITE(10)" }, /* 51 O XPWRITE(10) */ { 0x51, D, "XPWRITE(10)" }, /* 51 O READ DISC INFORMATION */ { 0x51, R, "READ DISC INFORMATION" }, /* 52 O XDREAD(10) */ { 0x52, D, "XDREAD(10)" }, /* 52 O READ TRACK INFORMATION */ { 0x52, R, "READ TRACK INFORMATION" }, /* 53 O RESERVE TRACK */ { 0x53, R, "RESERVE TRACK" }, /* 54 O SEND OPC INFORMATION */ { 0x54, R, "SEND OPC INFORMATION" }, /* 55 OOO OMOOOOMOMO MODE SELECT(10) */ { 0x55, ALL & ~(P), "MODE SELECT(10)" }, /* 56 ZZMZO OOOZ RESERVE(10) */ { 0x56, ALL & ~(R | B | K | V | F | C), "RESERVE(10)" }, /* 56 Z RESERVE ELEMENT(10) */ { 0x56, M, "RESERVE ELEMENT(10)" }, /* 57 ZZMZO OOOZ RELEASE(10) */ { 0x57, ALL & ~(R | B | K | V | F | C), "RELEASE(10)" }, /* 57 Z RELEASE ELEMENT(10) */ { 0x57, M, "RELEASE ELEMENT(10)" }, /* 58 O REPAIR TRACK */ { 0x58, R, "REPAIR TRACK" }, /* 59 */ /* 5A OOO OMOOOOMOMO MODE SENSE(10) */ { 0x5A, ALL & ~(P), "MODE SENSE(10)" }, /* 5B O CLOSE TRACK/SESSION */ { 0x5B, R, "CLOSE TRACK/SESSION" }, /* 5C O READ BUFFER CAPACITY */ { 0x5C, R, "READ BUFFER CAPACITY" }, /* 5D O SEND CUE SHEET */ { 0x5D, R, "SEND CUE SHEET" }, /* 5E OOOOO OOOO M PERSISTENT RESERVE IN */ { 0x5E, ALL & ~(R | B | K | V | C), "PERSISTENT RESERVE IN" }, /* 5F OOOOO OOOO M PERSISTENT RESERVE OUT */ { 0x5F, ALL & ~(R | B | K | V | C), "PERSISTENT RESERVE OUT" }, /* 7E OO O OOOO O extended CDB */ { 0x7E, D | T | R | M | A | E | B | V, "extended CDB" }, /* 7F O M variable length CDB (more than 16 bytes) */ { 0x7F, D | F, "variable length CDB (more than 16 bytes)" }, /* 80 Z XDWRITE EXTENDED(16) */ { 0x80, D, "XDWRITE EXTENDED(16)" }, /* 80 M WRITE FILEMARKS(16) */ { 0x80, T, "WRITE FILEMARKS(16)" }, /* 81 Z REBUILD(16) */ { 0x81, D, "REBUILD(16)" }, /* 81 O READ REVERSE(16) */ { 0x81, T, "READ REVERSE(16)" }, /* 82 Z REGENERATE(16) */ { 0x82, D, "REGENERATE(16)" }, /* 83 OOOOO O OO EXTENDED COPY */ { 0x83, D | T | L | P | W | O | K | V, "EXTENDED COPY" }, /* 84 OOOOO O OO RECEIVE COPY RESULTS */ { 0x84, D | T | L | P | W | O | K | V, "RECEIVE COPY RESULTS" }, /* 85 O O O ATA COMMAND PASS THROUGH(16) */ { 0x85, D | R | B, "ATA COMMAND PASS THROUGH(16)" }, /* 86 OO OO OOOOOOO ACCESS CONTROL IN */ { 0x86, ALL & ~(L | R | F), "ACCESS CONTROL IN" }, /* 87 OO OO OOOOOOO ACCESS CONTROL OUT */ { 0x87, ALL & ~(L | R | F), "ACCESS CONTROL OUT" }, /* 88 MM O O O READ(16) */ { 0x88, D | T | W | O | B, "READ(16)" }, /* 89 O COMPARE AND WRITE*/ { 0x89, D, "COMPARE AND WRITE" }, /* 8A OM O O O WRITE(16) */ { 0x8A, D | T | W | O | B, "WRITE(16)" }, /* 8B O ORWRITE */ { 0x8B, D, "ORWRITE" }, /* 8C OO O OO O M READ ATTRIBUTE */ { 0x8C, D | T | W | O | M | B | V, "READ ATTRIBUTE" }, /* 8D OO O OO O O WRITE ATTRIBUTE */ { 0x8D, D | T | W | O | M | B | V, "WRITE ATTRIBUTE" }, /* 8E O O O O WRITE AND VERIFY(16) */ { 0x8E, D | W | O | B, "WRITE AND VERIFY(16)" }, /* 8F OO O O O VERIFY(16) */ { 0x8F, D | T | W | O | B, "VERIFY(16)" }, /* 90 O O O O PRE-FETCH(16) */ { 0x90, D | W | O | B, "PRE-FETCH(16)" }, /* 91 O O O O SYNCHRONIZE CACHE(16) */ { 0x91, D | W | O | B, "SYNCHRONIZE CACHE(16)" }, /* 91 O SPACE(16) */ { 0x91, T, "SPACE(16)" }, /* 92 Z O O LOCK UNLOCK CACHE(16) */ { 0x92, D | W | O, "LOCK UNLOCK CACHE(16)" }, /* 92 O LOCATE(16) */ { 0x92, T, "LOCATE(16)" }, /* 93 O WRITE SAME(16) */ { 0x93, D, "WRITE SAME(16)" }, /* 93 M ERASE(16) */ { 0x93, T, "ERASE(16)" }, /* 94 O ZBC OUT */ { 0x94, ALL, "ZBC OUT" }, /* 95 O ZBC IN */ { 0x95, ALL, "ZBC IN" }, /* 96 */ /* 97 */ /* 98 */ /* 99 */ /* 9A O WRITE STREAM(16) */ { 0x9A, D, "WRITE STREAM(16)" }, /* 9B OOOOOOOOOO OOO READ BUFFER(16) */ { 0x9B, ALL & ~(B) , "READ BUFFER(16)" }, /* 9C O WRITE ATOMIC(16) */ { 0x9C, D, "WRITE ATOMIC(16)" }, /* 9D SERVICE ACTION BIDIRECTIONAL */ { 0x9D, ALL, "SERVICE ACTION BIDIRECTIONAL" }, /* XXX KDM ALL for this? op-num.txt defines it for none.. */ /* 9E SERVICE ACTION IN(16) */ { 0x9E, ALL, "SERVICE ACTION IN(16)" }, /* 9F M SERVICE ACTION OUT(16) */ { 0x9F, ALL, "SERVICE ACTION OUT(16)" }, /* A0 MMOOO OMMM OMO REPORT LUNS */ { 0xA0, ALL & ~(R | B), "REPORT LUNS" }, /* A1 O BLANK */ { 0xA1, R, "BLANK" }, /* A1 O O ATA COMMAND PASS THROUGH(12) */ { 0xA1, D | B, "ATA COMMAND PASS THROUGH(12)" }, /* A2 OO O O SECURITY PROTOCOL IN */ { 0xA2, D | T | R | V, "SECURITY PROTOCOL IN" }, /* A3 OOO O OOMOOOM MAINTENANCE (IN) */ { 0xA3, ALL & ~(P | R | F), "MAINTENANCE (IN)" }, /* A3 O SEND KEY */ { 0xA3, R, "SEND KEY" }, /* A4 OOO O OOOOOOO MAINTENANCE (OUT) */ { 0xA4, ALL & ~(P | R | F), "MAINTENANCE (OUT)" }, /* A4 O REPORT KEY */ { 0xA4, R, "REPORT KEY" }, /* A5 O O OM MOVE MEDIUM */ { 0xA5, T | W | O | M, "MOVE MEDIUM" }, /* A5 O PLAY AUDIO(12) */ { 0xA5, R, "PLAY AUDIO(12)" }, /* A6 O EXCHANGE MEDIUM */ { 0xA6, M, "EXCHANGE MEDIUM" }, /* A6 O LOAD/UNLOAD C/DVD */ { 0xA6, R, "LOAD/UNLOAD C/DVD" }, /* A7 ZZ O O MOVE MEDIUM ATTACHED */ { 0xA7, D | T | W | O, "MOVE MEDIUM ATTACHED" }, /* A7 O SET READ AHEAD */ { 0xA7, R, "SET READ AHEAD" }, /* A8 O OOO READ(12) */ { 0xA8, D | W | R | O, "READ(12)" }, /* A8 GET MESSAGE(12) */ { 0xA8, C, "GET MESSAGE(12)" }, /* A9 O SERVICE ACTION OUT(12) */ { 0xA9, V, "SERVICE ACTION OUT(12)" }, /* AA O OOO WRITE(12) */ { 0xAA, D | W | R | O, "WRITE(12)" }, /* AA SEND MESSAGE(12) */ { 0xAA, C, "SEND MESSAGE(12)" }, /* AB O O SERVICE ACTION IN(12) */ { 0xAB, R | V, "SERVICE ACTION IN(12)" }, /* AC O ERASE(12) */ { 0xAC, O, "ERASE(12)" }, /* AC O GET PERFORMANCE */ { 0xAC, R, "GET PERFORMANCE" }, /* AD O READ DVD STRUCTURE */ { 0xAD, R, "READ DVD STRUCTURE" }, /* AE O O O WRITE AND VERIFY(12) */ { 0xAE, D | W | O, "WRITE AND VERIFY(12)" }, /* AF O OZO VERIFY(12) */ { 0xAF, D | W | R | O, "VERIFY(12)" }, /* B0 ZZZ SEARCH DATA HIGH(12) */ { 0xB0, W | R | O, "SEARCH DATA HIGH(12)" }, /* B1 ZZZ SEARCH DATA EQUAL(12) */ { 0xB1, W | R | O, "SEARCH DATA EQUAL(12)" }, /* B2 ZZZ SEARCH DATA LOW(12) */ { 0xB2, W | R | O, "SEARCH DATA LOW(12)" }, /* B3 Z OZO SET LIMITS(12) */ { 0xB3, D | W | R | O, "SET LIMITS(12)" }, /* B4 ZZ OZO READ ELEMENT STATUS ATTACHED */ { 0xB4, D | T | W | R | O, "READ ELEMENT STATUS ATTACHED" }, /* B5 OO O O SECURITY PROTOCOL OUT */ { 0xB5, D | T | R | V, "SECURITY PROTOCOL OUT" }, /* B5 O REQUEST VOLUME ELEMENT ADDRESS */ { 0xB5, M, "REQUEST VOLUME ELEMENT ADDRESS" }, /* B6 O SEND VOLUME TAG */ { 0xB6, M, "SEND VOLUME TAG" }, /* B6 O SET STREAMING */ { 0xB6, R, "SET STREAMING" }, /* B7 O O READ DEFECT DATA(12) */ { 0xB7, D | O, "READ DEFECT DATA(12)" }, /* B8 O OZOM READ ELEMENT STATUS */ { 0xB8, T | W | R | O | M, "READ ELEMENT STATUS" }, /* B9 O READ CD MSF */ { 0xB9, R, "READ CD MSF" }, /* BA O O OOMO REDUNDANCY GROUP (IN) */ { 0xBA, D | W | O | M | A | E, "REDUNDANCY GROUP (IN)" }, /* BA O SCAN */ { 0xBA, R, "SCAN" }, /* BB O O OOOO REDUNDANCY GROUP (OUT) */ { 0xBB, D | W | O | M | A | E, "REDUNDANCY GROUP (OUT)" }, /* BB O SET CD SPEED */ { 0xBB, R, "SET CD SPEED" }, /* BC O O OOMO SPARE (IN) */ { 0xBC, D | W | O | M | A | E, "SPARE (IN)" }, /* BD O O OOOO SPARE (OUT) */ { 0xBD, D | W | O | M | A | E, "SPARE (OUT)" }, /* BD O MECHANISM STATUS */ { 0xBD, R, "MECHANISM STATUS" }, /* BE O O OOMO VOLUME SET (IN) */ { 0xBE, D | W | O | M | A | E, "VOLUME SET (IN)" }, /* BE O READ CD */ { 0xBE, R, "READ CD" }, /* BF O O OOOO VOLUME SET (OUT) */ { 0xBF, D | W | O | M | A | E, "VOLUME SET (OUT)" }, /* BF O SEND DVD STRUCTURE */ { 0xBF, R, "SEND DVD STRUCTURE" } }; const char * scsi_op_desc(u_int16_t opcode, struct scsi_inquiry_data *inq_data) { caddr_t match; int i, j; u_int32_t opmask; u_int16_t pd_type; int num_ops[2]; struct op_table_entry *table[2]; int num_tables; /* * If we've got inquiry data, use it to determine what type of * device we're dealing with here. Otherwise, assume direct * access. */ if (inq_data == NULL) { pd_type = T_DIRECT; match = NULL; } else { pd_type = SID_TYPE(inq_data); match = cam_quirkmatch((caddr_t)inq_data, (caddr_t)scsi_op_quirk_table, nitems(scsi_op_quirk_table), sizeof(*scsi_op_quirk_table), scsi_inquiry_match); } if (match != NULL) { table[0] = ((struct scsi_op_quirk_entry *)match)->op_table; num_ops[0] = ((struct scsi_op_quirk_entry *)match)->num_ops; table[1] = scsi_op_codes; num_ops[1] = nitems(scsi_op_codes); num_tables = 2; } else { /* * If this is true, we have a vendor specific opcode that * wasn't covered in the quirk table. */ if ((opcode > 0xBF) || ((opcode > 0x5F) && (opcode < 0x80))) return("Vendor Specific Command"); table[0] = scsi_op_codes; num_ops[0] = nitems(scsi_op_codes); num_tables = 1; } /* RBC is 'Simplified' Direct Access Device */ if (pd_type == T_RBC) pd_type = T_DIRECT; /* * Host managed drives are direct access for the most part. */ if (pd_type == T_ZBC_HM) pd_type = T_DIRECT; /* Map NODEVICE to Direct Access Device to handle REPORT LUNS, etc. */ if (pd_type == T_NODEVICE) pd_type = T_DIRECT; opmask = 1 << pd_type; for (j = 0; j < num_tables; j++) { for (i = 0;i < num_ops[j] && table[j][i].opcode <= opcode; i++){ if ((table[j][i].opcode == opcode) && ((table[j][i].opmask & opmask) != 0)) return(table[j][i].desc); } } /* * If we can't find a match for the command in the table, we just * assume it's a vendor specifc command. */ return("Vendor Specific Command"); } #else /* SCSI_NO_OP_STRINGS */ const char * scsi_op_desc(u_int16_t opcode, struct scsi_inquiry_data *inq_data) { return(""); } #endif #if !defined(SCSI_NO_SENSE_STRINGS) #define SST(asc, ascq, action, desc) \ asc, ascq, action, desc #else const char empty_string[] = ""; #define SST(asc, ascq, action, desc) \ asc, ascq, action, empty_string #endif const struct sense_key_table_entry sense_key_table[] = { { SSD_KEY_NO_SENSE, SS_NOP, "NO SENSE" }, { SSD_KEY_RECOVERED_ERROR, SS_NOP|SSQ_PRINT_SENSE, "RECOVERED ERROR" }, { SSD_KEY_NOT_READY, SS_RDEF, "NOT READY" }, { SSD_KEY_MEDIUM_ERROR, SS_RDEF, "MEDIUM ERROR" }, { SSD_KEY_HARDWARE_ERROR, SS_RDEF, "HARDWARE FAILURE" }, { SSD_KEY_ILLEGAL_REQUEST, SS_FATAL|EINVAL, "ILLEGAL REQUEST" }, { SSD_KEY_UNIT_ATTENTION, SS_FATAL|ENXIO, "UNIT ATTENTION" }, { SSD_KEY_DATA_PROTECT, SS_FATAL|EACCES, "DATA PROTECT" }, { SSD_KEY_BLANK_CHECK, SS_FATAL|ENOSPC, "BLANK CHECK" }, { SSD_KEY_Vendor_Specific, SS_FATAL|EIO, "Vendor Specific" }, { SSD_KEY_COPY_ABORTED, SS_FATAL|EIO, "COPY ABORTED" }, { SSD_KEY_ABORTED_COMMAND, SS_RDEF, "ABORTED COMMAND" }, { SSD_KEY_EQUAL, SS_NOP, "EQUAL" }, { SSD_KEY_VOLUME_OVERFLOW, SS_FATAL|EIO, "VOLUME OVERFLOW" }, { SSD_KEY_MISCOMPARE, SS_NOP, "MISCOMPARE" }, { SSD_KEY_COMPLETED, SS_NOP, "COMPLETED" } }; static struct asc_table_entry quantum_fireball_entries[] = { { SST(0x04, 0x0b, SS_START | SSQ_DECREMENT_COUNT | ENXIO, "Logical unit not ready, initializing cmd. required") } }; static struct asc_table_entry sony_mo_entries[] = { { SST(0x04, 0x00, SS_START | SSQ_DECREMENT_COUNT | ENXIO, "Logical unit not ready, cause not reportable") } }; static struct asc_table_entry hgst_entries[] = { { SST(0x04, 0xF0, SS_RDEF, "Vendor Unique - Logical Unit Not Ready") }, { SST(0x0A, 0x01, SS_RDEF, "Unrecovered Super Certification Log Write Error") }, { SST(0x0A, 0x02, SS_RDEF, "Unrecovered Super Certification Log Read Error") }, { SST(0x15, 0x03, SS_RDEF, "Unrecovered Sector Error") }, { SST(0x3E, 0x04, SS_RDEF, "Unrecovered Self-Test Hard-Cache Test Fail") }, { SST(0x3E, 0x05, SS_RDEF, "Unrecovered Self-Test OTF-Cache Fail") }, { SST(0x40, 0x00, SS_RDEF, "Unrecovered SAT No Buffer Overflow Error") }, { SST(0x40, 0x01, SS_RDEF, "Unrecovered SAT Buffer Overflow Error") }, { SST(0x40, 0x02, SS_RDEF, "Unrecovered SAT No Buffer Overflow With ECS Fault") }, { SST(0x40, 0x03, SS_RDEF, "Unrecovered SAT Buffer Overflow With ECS Fault") }, { SST(0x40, 0x81, SS_RDEF, "DRAM Failure") }, { SST(0x44, 0x0B, SS_RDEF, "Vendor Unique - Internal Target Failure") }, { SST(0x44, 0xF2, SS_RDEF, "Vendor Unique - Internal Target Failure") }, { SST(0x44, 0xF6, SS_RDEF, "Vendor Unique - Internal Target Failure") }, { SST(0x44, 0xF9, SS_RDEF, "Vendor Unique - Internal Target Failure") }, { SST(0x44, 0xFA, SS_RDEF, "Vendor Unique - Internal Target Failure") }, { SST(0x5D, 0x22, SS_RDEF, "Extreme Over-Temperature Warning") }, { SST(0x5D, 0x50, SS_RDEF, "Load/Unload cycle Count Warning") }, { SST(0x81, 0x00, SS_RDEF, "Vendor Unique - Internal Logic Error") }, { SST(0x85, 0x00, SS_RDEF, "Vendor Unique - Internal Key Seed Error") }, }; static struct asc_table_entry seagate_entries[] = { { SST(0x04, 0xF0, SS_RDEF, "Logical Unit Not Ready, super certify in Progress") }, { SST(0x08, 0x86, SS_RDEF, "Write Fault Data Corruption") }, { SST(0x09, 0x0D, SS_RDEF, "Tracking Failure") }, { SST(0x09, 0x0E, SS_RDEF, "ETF Failure") }, { SST(0x0B, 0x5D, SS_RDEF, "Pre-SMART Warning") }, { SST(0x0B, 0x85, SS_RDEF, "5V Voltage Warning") }, { SST(0x0B, 0x8C, SS_RDEF, "12V Voltage Warning") }, { SST(0x0C, 0xFF, SS_RDEF, "Write Error - Too many error recovery revs") }, { SST(0x11, 0xFF, SS_RDEF, "Unrecovered Read Error - Too many error recovery revs") }, { SST(0x19, 0x0E, SS_RDEF, "Fewer than 1/2 defect list copies") }, { SST(0x20, 0xF3, SS_RDEF, "Illegal CDB linked to skip mask cmd") }, { SST(0x24, 0xF0, SS_RDEF, "Illegal byte in CDB, LBA not matching") }, { SST(0x24, 0xF1, SS_RDEF, "Illegal byte in CDB, LEN not matching") }, { SST(0x24, 0xF2, SS_RDEF, "Mask not matching transfer length") }, { SST(0x24, 0xF3, SS_RDEF, "Drive formatted without plist") }, { SST(0x26, 0x95, SS_RDEF, "Invalid Field Parameter - CAP File") }, { SST(0x26, 0x96, SS_RDEF, "Invalid Field Parameter - RAP File") }, { SST(0x26, 0x97, SS_RDEF, "Invalid Field Parameter - TMS Firmware Tag") }, { SST(0x26, 0x98, SS_RDEF, "Invalid Field Parameter - Check Sum") }, { SST(0x26, 0x99, SS_RDEF, "Invalid Field Parameter - Firmware Tag") }, { SST(0x29, 0x08, SS_RDEF, "Write Log Dump data") }, { SST(0x29, 0x09, SS_RDEF, "Write Log Dump data") }, { SST(0x29, 0x0A, SS_RDEF, "Reserved disk space") }, { SST(0x29, 0x0B, SS_RDEF, "SDBP") }, { SST(0x29, 0x0C, SS_RDEF, "SDBP") }, { SST(0x31, 0x91, SS_RDEF, "Format Corrupted World Wide Name (WWN) is Invalid") }, { SST(0x32, 0x03, SS_RDEF, "Defect List - Length exceeds Command Allocated Length") }, { SST(0x33, 0x00, SS_RDEF, "Flash not ready for access") }, { SST(0x3F, 0x70, SS_RDEF, "Invalid RAP block") }, { SST(0x3F, 0x71, SS_RDEF, "RAP/ETF mismatch") }, { SST(0x3F, 0x90, SS_RDEF, "Invalid CAP block") }, { SST(0x3F, 0x91, SS_RDEF, "World Wide Name (WWN) Mismatch") }, { SST(0x40, 0x01, SS_RDEF, "DRAM Parity Error") }, { SST(0x40, 0x02, SS_RDEF, "DRAM Parity Error") }, { SST(0x42, 0x0A, SS_RDEF, "Loopback Test") }, { SST(0x42, 0x0B, SS_RDEF, "Loopback Test") }, { SST(0x44, 0xF2, SS_RDEF, "Compare error during data integrity check") }, { SST(0x44, 0xF6, SS_RDEF, "Unrecoverable error during data integrity check") }, { SST(0x47, 0x80, SS_RDEF, "Fibre Channel Sequence Error") }, { SST(0x4E, 0x01, SS_RDEF, "Information Unit Too Short") }, { SST(0x80, 0x00, SS_RDEF, "General Firmware Error / Command Timeout") }, { SST(0x80, 0x01, SS_RDEF, "Command Timeout") }, { SST(0x80, 0x02, SS_RDEF, "Command Timeout") }, { SST(0x80, 0x80, SS_RDEF, "FC FIFO Error During Read Transfer") }, { SST(0x80, 0x81, SS_RDEF, "FC FIFO Error During Write Transfer") }, { SST(0x80, 0x82, SS_RDEF, "DISC FIFO Error During Read Transfer") }, { SST(0x80, 0x83, SS_RDEF, "DISC FIFO Error During Write Transfer") }, { SST(0x80, 0x84, SS_RDEF, "LBA Seeded LRC Error on Read") }, { SST(0x80, 0x85, SS_RDEF, "LBA Seeded LRC Error on Write") }, { SST(0x80, 0x86, SS_RDEF, "IOEDC Error on Read") }, { SST(0x80, 0x87, SS_RDEF, "IOEDC Error on Write") }, { SST(0x80, 0x88, SS_RDEF, "Host Parity Check Failed") }, { SST(0x80, 0x89, SS_RDEF, "IOEDC error on read detected by formatter") }, { SST(0x80, 0x8A, SS_RDEF, "Host Parity Errors / Host FIFO Initialization Failed") }, { SST(0x80, 0x8B, SS_RDEF, "Host Parity Errors") }, { SST(0x80, 0x8C, SS_RDEF, "Host Parity Errors") }, { SST(0x80, 0x8D, SS_RDEF, "Host Parity Errors") }, { SST(0x81, 0x00, SS_RDEF, "LA Check Failed") }, { SST(0x82, 0x00, SS_RDEF, "Internal client detected insufficient buffer") }, { SST(0x84, 0x00, SS_RDEF, "Scheduled Diagnostic And Repair") }, }; static struct scsi_sense_quirk_entry sense_quirk_table[] = { { /* * XXX The Quantum Fireball ST and SE like to return 0x04 0x0b * when they really should return 0x04 0x02. */ {T_DIRECT, SIP_MEDIA_FIXED, "QUANTUM", "FIREBALL S*", "*"}, /*num_sense_keys*/0, nitems(quantum_fireball_entries), /*sense key entries*/NULL, quantum_fireball_entries }, { /* * This Sony MO drive likes to return 0x04, 0x00 when it * isn't spun up. */ {T_DIRECT, SIP_MEDIA_REMOVABLE, "SONY", "SMO-*", "*"}, /*num_sense_keys*/0, nitems(sony_mo_entries), /*sense key entries*/NULL, sony_mo_entries }, { /* * HGST vendor-specific error codes */ {T_DIRECT, SIP_MEDIA_FIXED, "HGST", "*", "*"}, /*num_sense_keys*/0, nitems(hgst_entries), /*sense key entries*/NULL, hgst_entries }, { /* * SEAGATE vendor-specific error codes */ {T_DIRECT, SIP_MEDIA_FIXED, "SEAGATE", "*", "*"}, /*num_sense_keys*/0, nitems(seagate_entries), /*sense key entries*/NULL, seagate_entries } }; const u_int sense_quirk_table_size = nitems(sense_quirk_table); static struct asc_table_entry asc_table[] = { /* * From: http://www.t10.org/lists/asc-num.txt * Modifications by Jung-uk Kim (jkim@FreeBSD.org) */ /* * File: ASC-NUM.TXT * * SCSI ASC/ASCQ Assignments * Numeric Sorted Listing * as of 8/12/15 * * D - DIRECT ACCESS DEVICE (SBC-2) device column key * .T - SEQUENTIAL ACCESS DEVICE (SSC) ------------------- * . L - PRINTER DEVICE (SSC) blank = reserved * . P - PROCESSOR DEVICE (SPC) not blank = allowed * . .W - WRITE ONCE READ MULTIPLE DEVICE (SBC-2) * . . R - CD DEVICE (MMC) * . . O - OPTICAL MEMORY DEVICE (SBC-2) * . . .M - MEDIA CHANGER DEVICE (SMC) * . . . A - STORAGE ARRAY DEVICE (SCC) * . . . E - ENCLOSURE SERVICES DEVICE (SES) * . . . .B - SIMPLIFIED DIRECT-ACCESS DEVICE (RBC) * . . . . K - OPTICAL CARD READER/WRITER DEVICE (OCRW) * . . . . V - AUTOMATION/DRIVE INTERFACE (ADC) * . . . . .F - OBJECT-BASED STORAGE (OSD) * DTLPWROMAEBKVF * ASC ASCQ Action * Description */ /* DTLPWROMAEBKVF */ { SST(0x00, 0x00, SS_NOP, "No additional sense information") }, /* T */ { SST(0x00, 0x01, SS_RDEF, "Filemark detected") }, /* T */ { SST(0x00, 0x02, SS_RDEF, "End-of-partition/medium detected") }, /* T */ { SST(0x00, 0x03, SS_RDEF, "Setmark detected") }, /* T */ { SST(0x00, 0x04, SS_RDEF, "Beginning-of-partition/medium detected") }, /* TL */ { SST(0x00, 0x05, SS_RDEF, "End-of-data detected") }, /* DTLPWROMAEBKVF */ { SST(0x00, 0x06, SS_RDEF, "I/O process terminated") }, /* T */ { SST(0x00, 0x07, SS_RDEF, /* XXX TBD */ "Programmable early warning detected") }, /* R */ { SST(0x00, 0x11, SS_FATAL | EBUSY, "Audio play operation in progress") }, /* R */ { SST(0x00, 0x12, SS_NOP, "Audio play operation paused") }, /* R */ { SST(0x00, 0x13, SS_NOP, "Audio play operation successfully completed") }, /* R */ { SST(0x00, 0x14, SS_RDEF, "Audio play operation stopped due to error") }, /* R */ { SST(0x00, 0x15, SS_NOP, "No current audio status to return") }, /* DTLPWROMAEBKVF */ { SST(0x00, 0x16, SS_FATAL | EBUSY, "Operation in progress") }, /* DTL WROMAEBKVF */ { SST(0x00, 0x17, SS_RDEF, "Cleaning requested") }, /* T */ { SST(0x00, 0x18, SS_RDEF, /* XXX TBD */ "Erase operation in progress") }, /* T */ { SST(0x00, 0x19, SS_RDEF, /* XXX TBD */ "Locate operation in progress") }, /* T */ { SST(0x00, 0x1A, SS_RDEF, /* XXX TBD */ "Rewind operation in progress") }, /* T */ { SST(0x00, 0x1B, SS_RDEF, /* XXX TBD */ "Set capacity operation in progress") }, /* T */ { SST(0x00, 0x1C, SS_RDEF, /* XXX TBD */ "Verify operation in progress") }, /* DT B */ { SST(0x00, 0x1D, SS_NOP, "ATA pass through information available") }, /* DT R MAEBKV */ { SST(0x00, 0x1E, SS_RDEF, /* XXX TBD */ "Conflicting SA creation request") }, /* DT B */ { SST(0x00, 0x1F, SS_RDEF, /* XXX TBD */ "Logical unit transitioning to another power condition") }, /* DT P B */ { SST(0x00, 0x20, SS_NOP, "Extended copy information available") }, /* D */ { SST(0x00, 0x21, SS_RDEF, /* XXX TBD */ "Atomic command aborted due to ACA") }, /* D W O BK */ { SST(0x01, 0x00, SS_RDEF, "No index/sector signal") }, /* D WRO BK */ { SST(0x02, 0x00, SS_RDEF, "No seek complete") }, /* DTL W O BK */ { SST(0x03, 0x00, SS_RDEF, "Peripheral device write fault") }, /* T */ { SST(0x03, 0x01, SS_RDEF, "No write current") }, /* T */ { SST(0x03, 0x02, SS_RDEF, "Excessive write errors") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x00, SS_RDEF, "Logical unit not ready, cause not reportable") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x01, SS_WAIT | EBUSY, "Logical unit is in process of becoming ready") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x02, SS_START | SSQ_DECREMENT_COUNT | ENXIO, "Logical unit not ready, initializing command required") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x03, SS_FATAL | ENXIO, "Logical unit not ready, manual intervention required") }, /* DTL RO B */ { SST(0x04, 0x04, SS_FATAL | EBUSY, "Logical unit not ready, format in progress") }, /* DT W O A BK F */ { SST(0x04, 0x05, SS_FATAL | EBUSY, "Logical unit not ready, rebuild in progress") }, /* DT W O A BK */ { SST(0x04, 0x06, SS_FATAL | EBUSY, "Logical unit not ready, recalculation in progress") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x07, SS_FATAL | EBUSY, "Logical unit not ready, operation in progress") }, /* R */ { SST(0x04, 0x08, SS_FATAL | EBUSY, "Logical unit not ready, long write in progress") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x09, SS_RDEF, /* XXX TBD */ "Logical unit not ready, self-test in progress") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x0A, SS_WAIT | ENXIO, "Logical unit not accessible, asymmetric access state transition")}, /* DTLPWROMAEBKVF */ { SST(0x04, 0x0B, SS_FATAL | ENXIO, "Logical unit not accessible, target port in standby state") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x0C, SS_FATAL | ENXIO, "Logical unit not accessible, target port in unavailable state") }, /* F */ { SST(0x04, 0x0D, SS_RDEF, /* XXX TBD */ "Logical unit not ready, structure check required") }, /* DTL WR MAEBKVF */ { SST(0x04, 0x0E, SS_RDEF, /* XXX TBD */ "Logical unit not ready, security session in progress") }, /* DT WROM B */ { SST(0x04, 0x10, SS_RDEF, /* XXX TBD */ "Logical unit not ready, auxiliary memory not accessible") }, /* DT WRO AEB VF */ { SST(0x04, 0x11, SS_WAIT | EBUSY, "Logical unit not ready, notify (enable spinup) required") }, /* M V */ { SST(0x04, 0x12, SS_RDEF, /* XXX TBD */ "Logical unit not ready, offline") }, /* DT R MAEBKV */ { SST(0x04, 0x13, SS_RDEF, /* XXX TBD */ "Logical unit not ready, SA creation in progress") }, /* D B */ { SST(0x04, 0x14, SS_RDEF, /* XXX TBD */ "Logical unit not ready, space allocation in progress") }, /* M */ { SST(0x04, 0x15, SS_RDEF, /* XXX TBD */ "Logical unit not ready, robotics disabled") }, /* M */ { SST(0x04, 0x16, SS_RDEF, /* XXX TBD */ "Logical unit not ready, configuration required") }, /* M */ { SST(0x04, 0x17, SS_RDEF, /* XXX TBD */ "Logical unit not ready, calibration required") }, /* M */ { SST(0x04, 0x18, SS_RDEF, /* XXX TBD */ "Logical unit not ready, a door is open") }, /* M */ { SST(0x04, 0x19, SS_RDEF, /* XXX TBD */ "Logical unit not ready, operating in sequential mode") }, /* DT B */ { SST(0x04, 0x1A, SS_RDEF, /* XXX TBD */ "Logical unit not ready, START/STOP UNIT command in progress") }, /* D B */ { SST(0x04, 0x1B, SS_RDEF, /* XXX TBD */ "Logical unit not ready, sanitize in progress") }, /* DT MAEB */ { SST(0x04, 0x1C, SS_RDEF, /* XXX TBD */ "Logical unit not ready, additional power use not yet granted") }, /* D */ { SST(0x04, 0x1D, SS_RDEF, /* XXX TBD */ "Logical unit not ready, configuration in progress") }, /* D */ { SST(0x04, 0x1E, SS_FATAL | ENXIO, "Logical unit not ready, microcode activation required") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x1F, SS_FATAL | ENXIO, "Logical unit not ready, microcode download required") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x20, SS_RDEF, /* XXX TBD */ "Logical unit not ready, logical unit reset required") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x21, SS_RDEF, /* XXX TBD */ "Logical unit not ready, hard reset required") }, /* DTLPWROMAEBKVF */ { SST(0x04, 0x22, SS_RDEF, /* XXX TBD */ "Logical unit not ready, power cycle required") }, /* DTL WROMAEBKVF */ { SST(0x05, 0x00, SS_RDEF, "Logical unit does not respond to selection") }, /* D WROM BK */ { SST(0x06, 0x00, SS_RDEF, "No reference position found") }, /* DTL WROM BK */ { SST(0x07, 0x00, SS_RDEF, "Multiple peripheral devices selected") }, /* DTL WROMAEBKVF */ { SST(0x08, 0x00, SS_RDEF, "Logical unit communication failure") }, /* DTL WROMAEBKVF */ { SST(0x08, 0x01, SS_RDEF, "Logical unit communication time-out") }, /* DTL WROMAEBKVF */ { SST(0x08, 0x02, SS_RDEF, "Logical unit communication parity error") }, /* DT ROM BK */ { SST(0x08, 0x03, SS_RDEF, "Logical unit communication CRC error (Ultra-DMA/32)") }, /* DTLPWRO K */ { SST(0x08, 0x04, SS_RDEF, /* XXX TBD */ "Unreachable copy target") }, /* DT WRO B */ { SST(0x09, 0x00, SS_RDEF, "Track following error") }, /* WRO K */ { SST(0x09, 0x01, SS_RDEF, "Tracking servo failure") }, /* WRO K */ { SST(0x09, 0x02, SS_RDEF, "Focus servo failure") }, /* WRO */ { SST(0x09, 0x03, SS_RDEF, "Spindle servo failure") }, /* DT WRO B */ { SST(0x09, 0x04, SS_RDEF, "Head select fault") }, /* DT RO B */ { SST(0x09, 0x05, SS_RDEF, "Vibration induced tracking error") }, /* DTLPWROMAEBKVF */ { SST(0x0A, 0x00, SS_FATAL | ENOSPC, "Error log overflow") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x00, SS_NOP | SSQ_PRINT_SENSE, "Warning") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x01, SS_NOP | SSQ_PRINT_SENSE, "Warning - specified temperature exceeded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x02, SS_NOP | SSQ_PRINT_SENSE, "Warning - enclosure degraded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x03, SS_NOP | SSQ_PRINT_SENSE, "Warning - background self-test failed") }, /* DTLPWRO AEBKVF */ { SST(0x0B, 0x04, SS_NOP | SSQ_PRINT_SENSE, "Warning - background pre-scan detected medium error") }, /* DTLPWRO AEBKVF */ { SST(0x0B, 0x05, SS_NOP | SSQ_PRINT_SENSE, "Warning - background medium scan detected medium error") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x06, SS_NOP | SSQ_PRINT_SENSE, "Warning - non-volatile cache now volatile") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x07, SS_NOP | SSQ_PRINT_SENSE, "Warning - degraded power to non-volatile cache") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x08, SS_NOP | SSQ_PRINT_SENSE, "Warning - power loss expected") }, /* D */ { SST(0x0B, 0x09, SS_NOP | SSQ_PRINT_SENSE, "Warning - device statistics notification available") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x0A, SS_NOP | SSQ_PRINT_SENSE, "Warning - High critical temperature limit exceeded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x0B, SS_NOP | SSQ_PRINT_SENSE, "Warning - Low critical temperature limit exceeded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x0C, SS_NOP | SSQ_PRINT_SENSE, "Warning - High operating temperature limit exceeded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x0D, SS_NOP | SSQ_PRINT_SENSE, "Warning - Low operating temperature limit exceeded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x0E, SS_NOP | SSQ_PRINT_SENSE, "Warning - High citical humidity limit exceeded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x0F, SS_NOP | SSQ_PRINT_SENSE, "Warning - Low citical humidity limit exceeded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x10, SS_NOP | SSQ_PRINT_SENSE, "Warning - High operating humidity limit exceeded") }, /* DTLPWROMAEBKVF */ { SST(0x0B, 0x11, SS_NOP | SSQ_PRINT_SENSE, "Warning - Low operating humidity limit exceeded") }, /* T R */ { SST(0x0C, 0x00, SS_RDEF, "Write error") }, /* K */ { SST(0x0C, 0x01, SS_NOP | SSQ_PRINT_SENSE, "Write error - recovered with auto reallocation") }, /* D W O BK */ { SST(0x0C, 0x02, SS_RDEF, "Write error - auto reallocation failed") }, /* D W O BK */ { SST(0x0C, 0x03, SS_RDEF, "Write error - recommend reassignment") }, /* DT W O B */ { SST(0x0C, 0x04, SS_RDEF, "Compression check miscompare error") }, /* DT W O B */ { SST(0x0C, 0x05, SS_RDEF, "Data expansion occurred during compression") }, /* DT W O B */ { SST(0x0C, 0x06, SS_RDEF, "Block not compressible") }, /* R */ { SST(0x0C, 0x07, SS_RDEF, "Write error - recovery needed") }, /* R */ { SST(0x0C, 0x08, SS_RDEF, "Write error - recovery failed") }, /* R */ { SST(0x0C, 0x09, SS_RDEF, "Write error - loss of streaming") }, /* R */ { SST(0x0C, 0x0A, SS_RDEF, "Write error - padding blocks added") }, /* DT WROM B */ { SST(0x0C, 0x0B, SS_RDEF, /* XXX TBD */ "Auxiliary memory write error") }, /* DTLPWRO AEBKVF */ { SST(0x0C, 0x0C, SS_RDEF, /* XXX TBD */ "Write error - unexpected unsolicited data") }, /* DTLPWRO AEBKVF */ { SST(0x0C, 0x0D, SS_RDEF, /* XXX TBD */ "Write error - not enough unsolicited data") }, /* DT W O BK */ { SST(0x0C, 0x0E, SS_RDEF, /* XXX TBD */ "Multiple write errors") }, /* R */ { SST(0x0C, 0x0F, SS_RDEF, /* XXX TBD */ "Defects in error window") }, /* D */ { SST(0x0C, 0x10, SS_RDEF, /* XXX TBD */ "Incomplete multiple atomic write operations") }, /* D */ { SST(0x0C, 0x11, SS_RDEF, /* XXX TBD */ "Write error - recovery scan needed") }, /* D */ { SST(0x0C, 0x12, SS_RDEF, /* XXX TBD */ "Write error - insufficient zone resources") }, /* DTLPWRO A K */ { SST(0x0D, 0x00, SS_RDEF, /* XXX TBD */ "Error detected by third party temporary initiator") }, /* DTLPWRO A K */ { SST(0x0D, 0x01, SS_RDEF, /* XXX TBD */ "Third party device failure") }, /* DTLPWRO A K */ { SST(0x0D, 0x02, SS_RDEF, /* XXX TBD */ "Copy target device not reachable") }, /* DTLPWRO A K */ { SST(0x0D, 0x03, SS_RDEF, /* XXX TBD */ "Incorrect copy target device type") }, /* DTLPWRO A K */ { SST(0x0D, 0x04, SS_RDEF, /* XXX TBD */ "Copy target device data underrun") }, /* DTLPWRO A K */ { SST(0x0D, 0x05, SS_RDEF, /* XXX TBD */ "Copy target device data overrun") }, /* DT PWROMAEBK F */ { SST(0x0E, 0x00, SS_RDEF, /* XXX TBD */ "Invalid information unit") }, /* DT PWROMAEBK F */ { SST(0x0E, 0x01, SS_RDEF, /* XXX TBD */ "Information unit too short") }, /* DT PWROMAEBK F */ { SST(0x0E, 0x02, SS_RDEF, /* XXX TBD */ "Information unit too long") }, /* DT P R MAEBK F */ { SST(0x0E, 0x03, SS_FATAL | EINVAL, "Invalid field in command information unit") }, /* D W O BK */ { SST(0x10, 0x00, SS_RDEF, "ID CRC or ECC error") }, /* DT W O */ { SST(0x10, 0x01, SS_RDEF, /* XXX TBD */ "Logical block guard check failed") }, /* DT W O */ { SST(0x10, 0x02, SS_RDEF, /* XXX TBD */ "Logical block application tag check failed") }, /* DT W O */ { SST(0x10, 0x03, SS_RDEF, /* XXX TBD */ "Logical block reference tag check failed") }, /* T */ { SST(0x10, 0x04, SS_RDEF, /* XXX TBD */ "Logical block protection error on recovered buffer data") }, /* T */ { SST(0x10, 0x05, SS_RDEF, /* XXX TBD */ "Logical block protection method error") }, /* DT WRO BK */ { SST(0x11, 0x00, SS_FATAL|EIO, "Unrecovered read error") }, /* DT WRO BK */ { SST(0x11, 0x01, SS_FATAL|EIO, "Read retries exhausted") }, /* DT WRO BK */ { SST(0x11, 0x02, SS_FATAL|EIO, "Error too long to correct") }, /* DT W O BK */ { SST(0x11, 0x03, SS_FATAL|EIO, "Multiple read errors") }, /* D W O BK */ { SST(0x11, 0x04, SS_FATAL|EIO, "Unrecovered read error - auto reallocate failed") }, /* WRO B */ { SST(0x11, 0x05, SS_FATAL|EIO, "L-EC uncorrectable error") }, /* WRO B */ { SST(0x11, 0x06, SS_FATAL|EIO, "CIRC unrecovered error") }, /* W O B */ { SST(0x11, 0x07, SS_RDEF, "Data re-synchronization error") }, /* T */ { SST(0x11, 0x08, SS_RDEF, "Incomplete block read") }, /* T */ { SST(0x11, 0x09, SS_RDEF, "No gap found") }, /* DT O BK */ { SST(0x11, 0x0A, SS_RDEF, "Miscorrected error") }, /* D W O BK */ { SST(0x11, 0x0B, SS_FATAL|EIO, "Unrecovered read error - recommend reassignment") }, /* D W O BK */ { SST(0x11, 0x0C, SS_FATAL|EIO, "Unrecovered read error - recommend rewrite the data") }, /* DT WRO B */ { SST(0x11, 0x0D, SS_RDEF, "De-compression CRC error") }, /* DT WRO B */ { SST(0x11, 0x0E, SS_RDEF, "Cannot decompress using declared algorithm") }, /* R */ { SST(0x11, 0x0F, SS_RDEF, "Error reading UPC/EAN number") }, /* R */ { SST(0x11, 0x10, SS_RDEF, "Error reading ISRC number") }, /* R */ { SST(0x11, 0x11, SS_RDEF, "Read error - loss of streaming") }, /* DT WROM B */ { SST(0x11, 0x12, SS_RDEF, /* XXX TBD */ "Auxiliary memory read error") }, /* DTLPWRO AEBKVF */ { SST(0x11, 0x13, SS_RDEF, /* XXX TBD */ "Read error - failed retransmission request") }, /* D */ { SST(0x11, 0x14, SS_RDEF, /* XXX TBD */ "Read error - LBA marked bad by application client") }, /* D */ { SST(0x11, 0x15, SS_RDEF, /* XXX TBD */ "Write after sanitize required") }, /* D W O BK */ { SST(0x12, 0x00, SS_RDEF, "Address mark not found for ID field") }, /* D W O BK */ { SST(0x13, 0x00, SS_RDEF, "Address mark not found for data field") }, /* DTL WRO BK */ { SST(0x14, 0x00, SS_RDEF, "Recorded entity not found") }, /* DT WRO BK */ { SST(0x14, 0x01, SS_RDEF, "Record not found") }, /* T */ { SST(0x14, 0x02, SS_RDEF, "Filemark or setmark not found") }, /* T */ { SST(0x14, 0x03, SS_RDEF, "End-of-data not found") }, /* T */ { SST(0x14, 0x04, SS_RDEF, "Block sequence error") }, /* DT W O BK */ { SST(0x14, 0x05, SS_RDEF, "Record not found - recommend reassignment") }, /* DT W O BK */ { SST(0x14, 0x06, SS_RDEF, "Record not found - data auto-reallocated") }, /* T */ { SST(0x14, 0x07, SS_RDEF, /* XXX TBD */ "Locate operation failure") }, /* DTL WROM BK */ { SST(0x15, 0x00, SS_RDEF, "Random positioning error") }, /* DTL WROM BK */ { SST(0x15, 0x01, SS_RDEF, "Mechanical positioning error") }, /* DT WRO BK */ { SST(0x15, 0x02, SS_RDEF, "Positioning error detected by read of medium") }, /* D W O BK */ { SST(0x16, 0x00, SS_RDEF, "Data synchronization mark error") }, /* D W O BK */ { SST(0x16, 0x01, SS_RDEF, "Data sync error - data rewritten") }, /* D W O BK */ { SST(0x16, 0x02, SS_RDEF, "Data sync error - recommend rewrite") }, /* D W O BK */ { SST(0x16, 0x03, SS_NOP | SSQ_PRINT_SENSE, "Data sync error - data auto-reallocated") }, /* D W O BK */ { SST(0x16, 0x04, SS_RDEF, "Data sync error - recommend reassignment") }, /* DT WRO BK */ { SST(0x17, 0x00, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with no error correction applied") }, /* DT WRO BK */ { SST(0x17, 0x01, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with retries") }, /* DT WRO BK */ { SST(0x17, 0x02, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with positive head offset") }, /* DT WRO BK */ { SST(0x17, 0x03, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with negative head offset") }, /* WRO B */ { SST(0x17, 0x04, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with retries and/or CIRC applied") }, /* D WRO BK */ { SST(0x17, 0x05, SS_NOP | SSQ_PRINT_SENSE, "Recovered data using previous sector ID") }, /* D W O BK */ { SST(0x17, 0x06, SS_NOP | SSQ_PRINT_SENSE, "Recovered data without ECC - data auto-reallocated") }, /* D WRO BK */ { SST(0x17, 0x07, SS_NOP | SSQ_PRINT_SENSE, "Recovered data without ECC - recommend reassignment") }, /* D WRO BK */ { SST(0x17, 0x08, SS_NOP | SSQ_PRINT_SENSE, "Recovered data without ECC - recommend rewrite") }, /* D WRO BK */ { SST(0x17, 0x09, SS_NOP | SSQ_PRINT_SENSE, "Recovered data without ECC - data rewritten") }, /* DT WRO BK */ { SST(0x18, 0x00, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with error correction applied") }, /* D WRO BK */ { SST(0x18, 0x01, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with error corr. & retries applied") }, /* D WRO BK */ { SST(0x18, 0x02, SS_NOP | SSQ_PRINT_SENSE, "Recovered data - data auto-reallocated") }, /* R */ { SST(0x18, 0x03, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with CIRC") }, /* R */ { SST(0x18, 0x04, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with L-EC") }, /* D WRO BK */ { SST(0x18, 0x05, SS_NOP | SSQ_PRINT_SENSE, "Recovered data - recommend reassignment") }, /* D WRO BK */ { SST(0x18, 0x06, SS_NOP | SSQ_PRINT_SENSE, "Recovered data - recommend rewrite") }, /* D W O BK */ { SST(0x18, 0x07, SS_NOP | SSQ_PRINT_SENSE, "Recovered data with ECC - data rewritten") }, /* R */ { SST(0x18, 0x08, SS_RDEF, /* XXX TBD */ "Recovered data with linking") }, /* D O K */ { SST(0x19, 0x00, SS_RDEF, "Defect list error") }, /* D O K */ { SST(0x19, 0x01, SS_RDEF, "Defect list not available") }, /* D O K */ { SST(0x19, 0x02, SS_RDEF, "Defect list error in primary list") }, /* D O K */ { SST(0x19, 0x03, SS_RDEF, "Defect list error in grown list") }, /* DTLPWROMAEBKVF */ { SST(0x1A, 0x00, SS_RDEF, "Parameter list length error") }, /* DTLPWROMAEBKVF */ { SST(0x1B, 0x00, SS_RDEF, "Synchronous data transfer error") }, /* D O BK */ { SST(0x1C, 0x00, SS_RDEF, "Defect list not found") }, /* D O BK */ { SST(0x1C, 0x01, SS_RDEF, "Primary defect list not found") }, /* D O BK */ { SST(0x1C, 0x02, SS_RDEF, "Grown defect list not found") }, /* DT WRO BK */ { SST(0x1D, 0x00, SS_FATAL, "Miscompare during verify operation") }, /* D B */ { SST(0x1D, 0x01, SS_RDEF, /* XXX TBD */ "Miscomparable verify of unmapped LBA") }, /* D W O BK */ { SST(0x1E, 0x00, SS_NOP | SSQ_PRINT_SENSE, "Recovered ID with ECC correction") }, /* D O K */ { SST(0x1F, 0x00, SS_RDEF, "Partial defect list transfer") }, /* DTLPWROMAEBKVF */ { SST(0x20, 0x00, SS_FATAL | EINVAL, "Invalid command operation code") }, /* DT PWROMAEBK */ { SST(0x20, 0x01, SS_RDEF, /* XXX TBD */ "Access denied - initiator pending-enrolled") }, /* DT PWROMAEBK */ { SST(0x20, 0x02, SS_FATAL | EPERM, "Access denied - no access rights") }, /* DT PWROMAEBK */ { SST(0x20, 0x03, SS_RDEF, /* XXX TBD */ "Access denied - invalid mgmt ID key") }, /* T */ { SST(0x20, 0x04, SS_RDEF, /* XXX TBD */ "Illegal command while in write capable state") }, /* T */ { SST(0x20, 0x05, SS_RDEF, /* XXX TBD */ "Obsolete") }, /* T */ { SST(0x20, 0x06, SS_RDEF, /* XXX TBD */ "Illegal command while in explicit address mode") }, /* T */ { SST(0x20, 0x07, SS_RDEF, /* XXX TBD */ "Illegal command while in implicit address mode") }, /* DT PWROMAEBK */ { SST(0x20, 0x08, SS_RDEF, /* XXX TBD */ "Access denied - enrollment conflict") }, /* DT PWROMAEBK */ { SST(0x20, 0x09, SS_RDEF, /* XXX TBD */ "Access denied - invalid LU identifier") }, /* DT PWROMAEBK */ { SST(0x20, 0x0A, SS_RDEF, /* XXX TBD */ "Access denied - invalid proxy token") }, /* DT PWROMAEBK */ { SST(0x20, 0x0B, SS_RDEF, /* XXX TBD */ "Access denied - ACL LUN conflict") }, /* T */ { SST(0x20, 0x0C, SS_FATAL | EINVAL, "Illegal command when not in append-only mode") }, /* DT WRO BK */ { SST(0x21, 0x00, SS_FATAL | EINVAL, "Logical block address out of range") }, /* DT WROM BK */ { SST(0x21, 0x01, SS_FATAL | EINVAL, "Invalid element address") }, /* R */ { SST(0x21, 0x02, SS_RDEF, /* XXX TBD */ "Invalid address for write") }, /* R */ { SST(0x21, 0x03, SS_RDEF, /* XXX TBD */ "Invalid write crossing layer jump") }, /* D */ { SST(0x21, 0x04, SS_RDEF, /* XXX TBD */ "Unaligned write command") }, /* D */ { SST(0x21, 0x05, SS_RDEF, /* XXX TBD */ "Write boundary violation") }, /* D */ { SST(0x21, 0x06, SS_RDEF, /* XXX TBD */ "Attempt to read invalid data") }, /* D */ { SST(0x21, 0x07, SS_RDEF, /* XXX TBD */ "Read boundary violation") }, /* D */ { SST(0x22, 0x00, SS_FATAL | EINVAL, "Illegal function (use 20 00, 24 00, or 26 00)") }, /* DT P B */ { SST(0x23, 0x00, SS_FATAL | EINVAL, "Invalid token operation, cause not reportable") }, /* DT P B */ { SST(0x23, 0x01, SS_FATAL | EINVAL, "Invalid token operation, unsupported token type") }, /* DT P B */ { SST(0x23, 0x02, SS_FATAL | EINVAL, "Invalid token operation, remote token usage not supported") }, /* DT P B */ { SST(0x23, 0x03, SS_FATAL | EINVAL, "Invalid token operation, remote ROD token creation not supported") }, /* DT P B */ { SST(0x23, 0x04, SS_FATAL | EINVAL, "Invalid token operation, token unknown") }, /* DT P B */ { SST(0x23, 0x05, SS_FATAL | EINVAL, "Invalid token operation, token corrupt") }, /* DT P B */ { SST(0x23, 0x06, SS_FATAL | EINVAL, "Invalid token operation, token revoked") }, /* DT P B */ { SST(0x23, 0x07, SS_FATAL | EINVAL, "Invalid token operation, token expired") }, /* DT P B */ { SST(0x23, 0x08, SS_FATAL | EINVAL, "Invalid token operation, token cancelled") }, /* DT P B */ { SST(0x23, 0x09, SS_FATAL | EINVAL, "Invalid token operation, token deleted") }, /* DT P B */ { SST(0x23, 0x0A, SS_FATAL | EINVAL, "Invalid token operation, invalid token length") }, /* DTLPWROMAEBKVF */ { SST(0x24, 0x00, SS_FATAL | EINVAL, "Invalid field in CDB") }, /* DTLPWRO AEBKVF */ { SST(0x24, 0x01, SS_RDEF, /* XXX TBD */ "CDB decryption error") }, /* T */ { SST(0x24, 0x02, SS_RDEF, /* XXX TBD */ "Obsolete") }, /* T */ { SST(0x24, 0x03, SS_RDEF, /* XXX TBD */ "Obsolete") }, /* F */ { SST(0x24, 0x04, SS_RDEF, /* XXX TBD */ "Security audit value frozen") }, /* F */ { SST(0x24, 0x05, SS_RDEF, /* XXX TBD */ "Security working key frozen") }, /* F */ { SST(0x24, 0x06, SS_RDEF, /* XXX TBD */ "NONCE not unique") }, /* F */ { SST(0x24, 0x07, SS_RDEF, /* XXX TBD */ "NONCE timestamp out of range") }, /* DT R MAEBKV */ { SST(0x24, 0x08, SS_RDEF, /* XXX TBD */ "Invalid XCDB") }, /* DTLPWROMAEBKVF */ { SST(0x25, 0x00, SS_FATAL | ENXIO | SSQ_LOST, "Logical unit not supported") }, /* DTLPWROMAEBKVF */ { SST(0x26, 0x00, SS_FATAL | EINVAL, "Invalid field in parameter list") }, /* DTLPWROMAEBKVF */ { SST(0x26, 0x01, SS_FATAL | EINVAL, "Parameter not supported") }, /* DTLPWROMAEBKVF */ { SST(0x26, 0x02, SS_FATAL | EINVAL, "Parameter value invalid") }, /* DTLPWROMAE K */ { SST(0x26, 0x03, SS_FATAL | EINVAL, "Threshold parameters not supported") }, /* DTLPWROMAEBKVF */ { SST(0x26, 0x04, SS_FATAL | EINVAL, "Invalid release of persistent reservation") }, /* DTLPWRO A BK */ { SST(0x26, 0x05, SS_RDEF, /* XXX TBD */ "Data decryption error") }, /* DTLPWRO K */ { SST(0x26, 0x06, SS_FATAL | EINVAL, "Too many target descriptors") }, /* DTLPWRO K */ { SST(0x26, 0x07, SS_FATAL | EINVAL, "Unsupported target descriptor type code") }, /* DTLPWRO K */ { SST(0x26, 0x08, SS_FATAL | EINVAL, "Too many segment descriptors") }, /* DTLPWRO K */ { SST(0x26, 0x09, SS_FATAL | EINVAL, "Unsupported segment descriptor type code") }, /* DTLPWRO K */ { SST(0x26, 0x0A, SS_FATAL | EINVAL, "Unexpected inexact segment") }, /* DTLPWRO K */ { SST(0x26, 0x0B, SS_FATAL | EINVAL, "Inline data length exceeded") }, /* DTLPWRO K */ { SST(0x26, 0x0C, SS_FATAL | EINVAL, "Invalid operation for copy source or destination") }, /* DTLPWRO K */ { SST(0x26, 0x0D, SS_FATAL | EINVAL, "Copy segment granularity violation") }, /* DT PWROMAEBK */ { SST(0x26, 0x0E, SS_RDEF, /* XXX TBD */ "Invalid parameter while port is enabled") }, /* F */ { SST(0x26, 0x0F, SS_RDEF, /* XXX TBD */ "Invalid data-out buffer integrity check value") }, /* T */ { SST(0x26, 0x10, SS_RDEF, /* XXX TBD */ "Data decryption key fail limit reached") }, /* T */ { SST(0x26, 0x11, SS_RDEF, /* XXX TBD */ "Incomplete key-associated data set") }, /* T */ { SST(0x26, 0x12, SS_RDEF, /* XXX TBD */ "Vendor specific key reference not found") }, /* D */ { SST(0x26, 0x13, SS_RDEF, /* XXX TBD */ "Application tag mode page is invalid") }, /* DT WRO BK */ { SST(0x27, 0x00, SS_FATAL | EACCES, "Write protected") }, /* DT WRO BK */ { SST(0x27, 0x01, SS_FATAL | EACCES, "Hardware write protected") }, /* DT WRO BK */ { SST(0x27, 0x02, SS_FATAL | EACCES, "Logical unit software write protected") }, /* T R */ { SST(0x27, 0x03, SS_FATAL | EACCES, "Associated write protect") }, /* T R */ { SST(0x27, 0x04, SS_FATAL | EACCES, "Persistent write protect") }, /* T R */ { SST(0x27, 0x05, SS_FATAL | EACCES, "Permanent write protect") }, /* R F */ { SST(0x27, 0x06, SS_RDEF, /* XXX TBD */ "Conditional write protect") }, /* D B */ { SST(0x27, 0x07, SS_FATAL | ENOSPC, "Space allocation failed write protect") }, /* D */ { SST(0x27, 0x08, SS_FATAL | EACCES, "Zone is read only") }, /* DTLPWROMAEBKVF */ { SST(0x28, 0x00, SS_FATAL | ENXIO, "Not ready to ready change, medium may have changed") }, /* DT WROM B */ { SST(0x28, 0x01, SS_FATAL | ENXIO, "Import or export element accessed") }, /* R */ { SST(0x28, 0x02, SS_RDEF, /* XXX TBD */ "Format-layer may have changed") }, /* M */ { SST(0x28, 0x03, SS_RDEF, /* XXX TBD */ "Import/export element accessed, medium changed") }, /* * XXX JGibbs - All of these should use the same errno, but I don't * think ENXIO is the correct choice. Should we borrow from * the networking errnos? ECONNRESET anyone? */ /* DTLPWROMAEBKVF */ { SST(0x29, 0x00, SS_FATAL | ENXIO, "Power on, reset, or bus device reset occurred") }, /* DTLPWROMAEBKVF */ { SST(0x29, 0x01, SS_RDEF, "Power on occurred") }, /* DTLPWROMAEBKVF */ { SST(0x29, 0x02, SS_RDEF, "SCSI bus reset occurred") }, /* DTLPWROMAEBKVF */ { SST(0x29, 0x03, SS_RDEF, "Bus device reset function occurred") }, /* DTLPWROMAEBKVF */ { SST(0x29, 0x04, SS_RDEF, "Device internal reset") }, /* DTLPWROMAEBKVF */ { SST(0x29, 0x05, SS_RDEF, "Transceiver mode changed to single-ended") }, /* DTLPWROMAEBKVF */ { SST(0x29, 0x06, SS_RDEF, "Transceiver mode changed to LVD") }, /* DTLPWROMAEBKVF */ { SST(0x29, 0x07, SS_RDEF, /* XXX TBD */ "I_T nexus loss occurred") }, /* DTL WROMAEBKVF */ { SST(0x2A, 0x00, SS_RDEF, "Parameters changed") }, /* DTL WROMAEBKVF */ { SST(0x2A, 0x01, SS_RDEF, "Mode parameters changed") }, /* DTL WROMAE K */ { SST(0x2A, 0x02, SS_RDEF, "Log parameters changed") }, /* DTLPWROMAE K */ { SST(0x2A, 0x03, SS_RDEF, "Reservations preempted") }, /* DTLPWROMAE */ { SST(0x2A, 0x04, SS_RDEF, /* XXX TBD */ "Reservations released") }, /* DTLPWROMAE */ { SST(0x2A, 0x05, SS_RDEF, /* XXX TBD */ "Registrations preempted") }, /* DTLPWROMAEBKVF */ { SST(0x2A, 0x06, SS_RDEF, /* XXX TBD */ "Asymmetric access state changed") }, /* DTLPWROMAEBKVF */ { SST(0x2A, 0x07, SS_RDEF, /* XXX TBD */ "Implicit asymmetric access state transition failed") }, /* DT WROMAEBKVF */ { SST(0x2A, 0x08, SS_RDEF, /* XXX TBD */ "Priority changed") }, /* D */ { SST(0x2A, 0x09, SS_RDEF, /* XXX TBD */ "Capacity data has changed") }, /* DT */ { SST(0x2A, 0x0A, SS_RDEF, /* XXX TBD */ "Error history I_T nexus cleared") }, /* DT */ { SST(0x2A, 0x0B, SS_RDEF, /* XXX TBD */ "Error history snapshot released") }, /* F */ { SST(0x2A, 0x0C, SS_RDEF, /* XXX TBD */ "Error recovery attributes have changed") }, /* T */ { SST(0x2A, 0x0D, SS_RDEF, /* XXX TBD */ "Data encryption capabilities changed") }, /* DT M E V */ { SST(0x2A, 0x10, SS_RDEF, /* XXX TBD */ "Timestamp changed") }, /* T */ { SST(0x2A, 0x11, SS_RDEF, /* XXX TBD */ "Data encryption parameters changed by another I_T nexus") }, /* T */ { SST(0x2A, 0x12, SS_RDEF, /* XXX TBD */ "Data encryption parameters changed by vendor specific event") }, /* T */ { SST(0x2A, 0x13, SS_RDEF, /* XXX TBD */ "Data encryption key instance counter has changed") }, /* DT R MAEBKV */ { SST(0x2A, 0x14, SS_RDEF, /* XXX TBD */ "SA creation capabilities data has changed") }, /* T M V */ { SST(0x2A, 0x15, SS_RDEF, /* XXX TBD */ "Medium removal prevention preempted") }, /* DTLPWRO K */ { SST(0x2B, 0x00, SS_RDEF, "Copy cannot execute since host cannot disconnect") }, /* DTLPWROMAEBKVF */ { SST(0x2C, 0x00, SS_RDEF, "Command sequence error") }, /* */ { SST(0x2C, 0x01, SS_RDEF, "Too many windows specified") }, /* */ { SST(0x2C, 0x02, SS_RDEF, "Invalid combination of windows specified") }, /* R */ { SST(0x2C, 0x03, SS_RDEF, "Current program area is not empty") }, /* R */ { SST(0x2C, 0x04, SS_RDEF, "Current program area is empty") }, /* B */ { SST(0x2C, 0x05, SS_RDEF, /* XXX TBD */ "Illegal power condition request") }, /* R */ { SST(0x2C, 0x06, SS_RDEF, /* XXX TBD */ "Persistent prevent conflict") }, /* DTLPWROMAEBKVF */ { SST(0x2C, 0x07, SS_RDEF, /* XXX TBD */ "Previous busy status") }, /* DTLPWROMAEBKVF */ { SST(0x2C, 0x08, SS_RDEF, /* XXX TBD */ "Previous task set full status") }, /* DTLPWROM EBKVF */ { SST(0x2C, 0x09, SS_RDEF, /* XXX TBD */ "Previous reservation conflict status") }, /* F */ { SST(0x2C, 0x0A, SS_RDEF, /* XXX TBD */ "Partition or collection contains user objects") }, /* T */ { SST(0x2C, 0x0B, SS_RDEF, /* XXX TBD */ "Not reserved") }, /* D */ { SST(0x2C, 0x0C, SS_RDEF, /* XXX TBD */ "ORWRITE generation does not match") }, /* D */ { SST(0x2C, 0x0D, SS_RDEF, /* XXX TBD */ "Reset write pointer not allowed") }, /* D */ { SST(0x2C, 0x0E, SS_RDEF, /* XXX TBD */ "Zone is offline") }, /* D */ { SST(0x2C, 0x0F, SS_RDEF, /* XXX TBD */ "Stream not open") }, /* D */ { SST(0x2C, 0x10, SS_RDEF, /* XXX TBD */ "Unwritten data in zone") }, /* T */ { SST(0x2D, 0x00, SS_RDEF, "Overwrite error on update in place") }, /* R */ { SST(0x2E, 0x00, SS_RDEF, /* XXX TBD */ "Insufficient time for operation") }, /* D */ { SST(0x2E, 0x01, SS_RDEF, /* XXX TBD */ "Command timeout before processing") }, /* D */ { SST(0x2E, 0x02, SS_RDEF, /* XXX TBD */ "Command timeout during processing") }, /* D */ { SST(0x2E, 0x03, SS_RDEF, /* XXX TBD */ "Command timeout during processing due to error recovery") }, /* DTLPWROMAEBKVF */ { SST(0x2F, 0x00, SS_RDEF, "Commands cleared by another initiator") }, /* D */ { SST(0x2F, 0x01, SS_RDEF, /* XXX TBD */ "Commands cleared by power loss notification") }, /* DTLPWROMAEBKVF */ { SST(0x2F, 0x02, SS_RDEF, /* XXX TBD */ "Commands cleared by device server") }, /* DTLPWROMAEBKVF */ { SST(0x2F, 0x03, SS_RDEF, /* XXX TBD */ "Some commands cleared by queuing layer event") }, /* DT WROM BK */ { SST(0x30, 0x00, SS_RDEF, "Incompatible medium installed") }, /* DT WRO BK */ { SST(0x30, 0x01, SS_RDEF, "Cannot read medium - unknown format") }, /* DT WRO BK */ { SST(0x30, 0x02, SS_RDEF, "Cannot read medium - incompatible format") }, /* DT R K */ { SST(0x30, 0x03, SS_RDEF, "Cleaning cartridge installed") }, /* DT WRO BK */ { SST(0x30, 0x04, SS_RDEF, "Cannot write medium - unknown format") }, /* DT WRO BK */ { SST(0x30, 0x05, SS_RDEF, "Cannot write medium - incompatible format") }, /* DT WRO B */ { SST(0x30, 0x06, SS_RDEF, "Cannot format medium - incompatible medium") }, /* DTL WROMAEBKVF */ { SST(0x30, 0x07, SS_RDEF, "Cleaning failure") }, /* R */ { SST(0x30, 0x08, SS_RDEF, "Cannot write - application code mismatch") }, /* R */ { SST(0x30, 0x09, SS_RDEF, "Current session not fixated for append") }, /* DT WRO AEBK */ { SST(0x30, 0x0A, SS_RDEF, /* XXX TBD */ "Cleaning request rejected") }, /* T */ { SST(0x30, 0x0C, SS_RDEF, /* XXX TBD */ "WORM medium - overwrite attempted") }, /* T */ { SST(0x30, 0x0D, SS_RDEF, /* XXX TBD */ "WORM medium - integrity check") }, /* R */ { SST(0x30, 0x10, SS_RDEF, /* XXX TBD */ "Medium not formatted") }, /* M */ { SST(0x30, 0x11, SS_RDEF, /* XXX TBD */ "Incompatible volume type") }, /* M */ { SST(0x30, 0x12, SS_RDEF, /* XXX TBD */ "Incompatible volume qualifier") }, /* M */ { SST(0x30, 0x13, SS_RDEF, /* XXX TBD */ "Cleaning volume expired") }, /* DT WRO BK */ { SST(0x31, 0x00, SS_RDEF, "Medium format corrupted") }, /* D L RO B */ { SST(0x31, 0x01, SS_RDEF, "Format command failed") }, /* R */ { SST(0x31, 0x02, SS_RDEF, /* XXX TBD */ "Zoned formatting failed due to spare linking") }, /* D B */ { SST(0x31, 0x03, SS_RDEF, /* XXX TBD */ "SANITIZE command failed") }, /* D W O BK */ { SST(0x32, 0x00, SS_RDEF, "No defect spare location available") }, /* D W O BK */ { SST(0x32, 0x01, SS_RDEF, "Defect list update failure") }, /* T */ { SST(0x33, 0x00, SS_RDEF, "Tape length error") }, /* DTLPWROMAEBKVF */ { SST(0x34, 0x00, SS_RDEF, "Enclosure failure") }, /* DTLPWROMAEBKVF */ { SST(0x35, 0x00, SS_RDEF, "Enclosure services failure") }, /* DTLPWROMAEBKVF */ { SST(0x35, 0x01, SS_RDEF, "Unsupported enclosure function") }, /* DTLPWROMAEBKVF */ { SST(0x35, 0x02, SS_RDEF, "Enclosure services unavailable") }, /* DTLPWROMAEBKVF */ { SST(0x35, 0x03, SS_RDEF, "Enclosure services transfer failure") }, /* DTLPWROMAEBKVF */ { SST(0x35, 0x04, SS_RDEF, "Enclosure services transfer refused") }, /* DTL WROMAEBKVF */ { SST(0x35, 0x05, SS_RDEF, /* XXX TBD */ "Enclosure services checksum error") }, /* L */ { SST(0x36, 0x00, SS_RDEF, "Ribbon, ink, or toner failure") }, /* DTL WROMAEBKVF */ { SST(0x37, 0x00, SS_RDEF, "Rounded parameter") }, /* B */ { SST(0x38, 0x00, SS_RDEF, /* XXX TBD */ "Event status notification") }, /* B */ { SST(0x38, 0x02, SS_RDEF, /* XXX TBD */ "ESN - power management class event") }, /* B */ { SST(0x38, 0x04, SS_RDEF, /* XXX TBD */ "ESN - media class event") }, /* B */ { SST(0x38, 0x06, SS_RDEF, /* XXX TBD */ "ESN - device busy class event") }, /* D */ { SST(0x38, 0x07, SS_RDEF, /* XXX TBD */ "Thin provisioning soft threshold reached") }, /* DTL WROMAE K */ { SST(0x39, 0x00, SS_RDEF, "Saving parameters not supported") }, /* DTL WROM BK */ { SST(0x3A, 0x00, SS_FATAL | ENXIO, "Medium not present") }, /* DT WROM BK */ { SST(0x3A, 0x01, SS_FATAL | ENXIO, "Medium not present - tray closed") }, /* DT WROM BK */ { SST(0x3A, 0x02, SS_FATAL | ENXIO, "Medium not present - tray open") }, /* DT WROM B */ { SST(0x3A, 0x03, SS_RDEF, /* XXX TBD */ "Medium not present - loadable") }, /* DT WRO B */ { SST(0x3A, 0x04, SS_RDEF, /* XXX TBD */ "Medium not present - medium auxiliary memory accessible") }, /* TL */ { SST(0x3B, 0x00, SS_RDEF, "Sequential positioning error") }, /* T */ { SST(0x3B, 0x01, SS_RDEF, "Tape position error at beginning-of-medium") }, /* T */ { SST(0x3B, 0x02, SS_RDEF, "Tape position error at end-of-medium") }, /* L */ { SST(0x3B, 0x03, SS_RDEF, "Tape or electronic vertical forms unit not ready") }, /* L */ { SST(0x3B, 0x04, SS_RDEF, "Slew failure") }, /* L */ { SST(0x3B, 0x05, SS_RDEF, "Paper jam") }, /* L */ { SST(0x3B, 0x06, SS_RDEF, "Failed to sense top-of-form") }, /* L */ { SST(0x3B, 0x07, SS_RDEF, "Failed to sense bottom-of-form") }, /* T */ { SST(0x3B, 0x08, SS_RDEF, "Reposition error") }, /* */ { SST(0x3B, 0x09, SS_RDEF, "Read past end of medium") }, /* */ { SST(0x3B, 0x0A, SS_RDEF, "Read past beginning of medium") }, /* */ { SST(0x3B, 0x0B, SS_RDEF, "Position past end of medium") }, /* T */ { SST(0x3B, 0x0C, SS_RDEF, "Position past beginning of medium") }, /* DT WROM BK */ { SST(0x3B, 0x0D, SS_FATAL | ENOSPC, "Medium destination element full") }, /* DT WROM BK */ { SST(0x3B, 0x0E, SS_RDEF, "Medium source element empty") }, /* R */ { SST(0x3B, 0x0F, SS_RDEF, "End of medium reached") }, /* DT WROM BK */ { SST(0x3B, 0x11, SS_RDEF, "Medium magazine not accessible") }, /* DT WROM BK */ { SST(0x3B, 0x12, SS_RDEF, "Medium magazine removed") }, /* DT WROM BK */ { SST(0x3B, 0x13, SS_RDEF, "Medium magazine inserted") }, /* DT WROM BK */ { SST(0x3B, 0x14, SS_RDEF, "Medium magazine locked") }, /* DT WROM BK */ { SST(0x3B, 0x15, SS_RDEF, "Medium magazine unlocked") }, /* R */ { SST(0x3B, 0x16, SS_RDEF, /* XXX TBD */ "Mechanical positioning or changer error") }, /* F */ { SST(0x3B, 0x17, SS_RDEF, /* XXX TBD */ "Read past end of user object") }, /* M */ { SST(0x3B, 0x18, SS_RDEF, /* XXX TBD */ "Element disabled") }, /* M */ { SST(0x3B, 0x19, SS_RDEF, /* XXX TBD */ "Element enabled") }, /* M */ { SST(0x3B, 0x1A, SS_RDEF, /* XXX TBD */ "Data transfer device removed") }, /* M */ { SST(0x3B, 0x1B, SS_RDEF, /* XXX TBD */ "Data transfer device inserted") }, /* T */ { SST(0x3B, 0x1C, SS_RDEF, /* XXX TBD */ "Too many logical objects on partition to support operation") }, /* DTLPWROMAE K */ { SST(0x3D, 0x00, SS_RDEF, "Invalid bits in IDENTIFY message") }, /* DTLPWROMAEBKVF */ { SST(0x3E, 0x00, SS_RDEF, "Logical unit has not self-configured yet") }, /* DTLPWROMAEBKVF */ { SST(0x3E, 0x01, SS_RDEF, "Logical unit failure") }, /* DTLPWROMAEBKVF */ { SST(0x3E, 0x02, SS_RDEF, "Timeout on logical unit") }, /* DTLPWROMAEBKVF */ { SST(0x3E, 0x03, SS_RDEF, /* XXX TBD */ "Logical unit failed self-test") }, /* DTLPWROMAEBKVF */ { SST(0x3E, 0x04, SS_RDEF, /* XXX TBD */ "Logical unit unable to update self-test log") }, /* DTLPWROMAEBKVF */ { SST(0x3F, 0x00, SS_RDEF, "Target operating conditions have changed") }, /* DTLPWROMAEBKVF */ { SST(0x3F, 0x01, SS_RDEF, "Microcode has been changed") }, /* DTLPWROM BK */ { SST(0x3F, 0x02, SS_RDEF, "Changed operating definition") }, /* DTLPWROMAEBKVF */ { SST(0x3F, 0x03, SS_RDEF, "INQUIRY data has changed") }, /* DT WROMAEBK */ { SST(0x3F, 0x04, SS_RDEF, "Component device attached") }, /* DT WROMAEBK */ { SST(0x3F, 0x05, SS_RDEF, "Device identifier changed") }, /* DT WROMAEB */ { SST(0x3F, 0x06, SS_RDEF, "Redundancy group created or modified") }, /* DT WROMAEB */ { SST(0x3F, 0x07, SS_RDEF, "Redundancy group deleted") }, /* DT WROMAEB */ { SST(0x3F, 0x08, SS_RDEF, "Spare created or modified") }, /* DT WROMAEB */ { SST(0x3F, 0x09, SS_RDEF, "Spare deleted") }, /* DT WROMAEBK */ { SST(0x3F, 0x0A, SS_RDEF, "Volume set created or modified") }, /* DT WROMAEBK */ { SST(0x3F, 0x0B, SS_RDEF, "Volume set deleted") }, /* DT WROMAEBK */ { SST(0x3F, 0x0C, SS_RDEF, "Volume set deassigned") }, /* DT WROMAEBK */ { SST(0x3F, 0x0D, SS_RDEF, "Volume set reassigned") }, /* DTLPWROMAE */ { SST(0x3F, 0x0E, SS_RDEF | SSQ_RESCAN , "Reported LUNs data has changed") }, /* DTLPWROMAEBKVF */ { SST(0x3F, 0x0F, SS_RDEF, /* XXX TBD */ "Echo buffer overwritten") }, /* DT WROM B */ { SST(0x3F, 0x10, SS_RDEF, /* XXX TBD */ "Medium loadable") }, /* DT WROM B */ { SST(0x3F, 0x11, SS_RDEF, /* XXX TBD */ "Medium auxiliary memory accessible") }, /* DTLPWR MAEBK F */ { SST(0x3F, 0x12, SS_RDEF, /* XXX TBD */ "iSCSI IP address added") }, /* DTLPWR MAEBK F */ { SST(0x3F, 0x13, SS_RDEF, /* XXX TBD */ "iSCSI IP address removed") }, /* DTLPWR MAEBK F */ { SST(0x3F, 0x14, SS_RDEF, /* XXX TBD */ "iSCSI IP address changed") }, /* DTLPWR MAEBK */ { SST(0x3F, 0x15, SS_RDEF, /* XXX TBD */ "Inspect referrals sense descriptors") }, /* DTLPWROMAEBKVF */ { SST(0x3F, 0x16, SS_RDEF, /* XXX TBD */ "Microcode has been changed without reset") }, /* D */ { SST(0x3F, 0x17, SS_RDEF, /* XXX TBD */ "Zone transition to full") }, /* D */ { SST(0x40, 0x00, SS_RDEF, "RAM failure") }, /* deprecated - use 40 NN instead */ /* DTLPWROMAEBKVF */ { SST(0x40, 0x80, SS_RDEF, "Diagnostic failure: ASCQ = Component ID") }, /* DTLPWROMAEBKVF */ { SST(0x40, 0xFF, SS_RDEF | SSQ_RANGE, NULL) }, /* Range 0x80->0xFF */ /* D */ { SST(0x41, 0x00, SS_RDEF, "Data path failure") }, /* deprecated - use 40 NN instead */ /* D */ { SST(0x42, 0x00, SS_RDEF, "Power-on or self-test failure") }, /* deprecated - use 40 NN instead */ /* DTLPWROMAEBKVF */ { SST(0x43, 0x00, SS_RDEF, "Message error") }, /* DTLPWROMAEBKVF */ { SST(0x44, 0x00, SS_FATAL | EIO, "Internal target failure") }, /* DT P MAEBKVF */ { SST(0x44, 0x01, SS_RDEF, /* XXX TBD */ "Persistent reservation information lost") }, /* DT B */ { SST(0x44, 0x71, SS_RDEF, /* XXX TBD */ "ATA device failed set features") }, /* DTLPWROMAEBKVF */ { SST(0x45, 0x00, SS_RDEF, "Select or reselect failure") }, /* DTLPWROM BK */ { SST(0x46, 0x00, SS_RDEF, "Unsuccessful soft reset") }, /* DTLPWROMAEBKVF */ { SST(0x47, 0x00, SS_RDEF, "SCSI parity error") }, /* DTLPWROMAEBKVF */ { SST(0x47, 0x01, SS_RDEF, /* XXX TBD */ "Data phase CRC error detected") }, /* DTLPWROMAEBKVF */ { SST(0x47, 0x02, SS_RDEF, /* XXX TBD */ "SCSI parity error detected during ST data phase") }, /* DTLPWROMAEBKVF */ { SST(0x47, 0x03, SS_RDEF, /* XXX TBD */ "Information unit iuCRC error detected") }, /* DTLPWROMAEBKVF */ { SST(0x47, 0x04, SS_RDEF, /* XXX TBD */ "Asynchronous information protection error detected") }, /* DTLPWROMAEBKVF */ { SST(0x47, 0x05, SS_RDEF, /* XXX TBD */ "Protocol service CRC error") }, /* DT MAEBKVF */ { SST(0x47, 0x06, SS_RDEF, /* XXX TBD */ "PHY test function in progress") }, /* DT PWROMAEBK */ { SST(0x47, 0x7F, SS_RDEF, /* XXX TBD */ "Some commands cleared by iSCSI protocol event") }, /* DTLPWROMAEBKVF */ { SST(0x48, 0x00, SS_RDEF, "Initiator detected error message received") }, /* DTLPWROMAEBKVF */ { SST(0x49, 0x00, SS_RDEF, "Invalid message error") }, /* DTLPWROMAEBKVF */ { SST(0x4A, 0x00, SS_RDEF, "Command phase error") }, /* DTLPWROMAEBKVF */ { SST(0x4B, 0x00, SS_RDEF, "Data phase error") }, /* DT PWROMAEBK */ { SST(0x4B, 0x01, SS_RDEF, /* XXX TBD */ "Invalid target port transfer tag received") }, /* DT PWROMAEBK */ { SST(0x4B, 0x02, SS_RDEF, /* XXX TBD */ "Too much write data") }, /* DT PWROMAEBK */ { SST(0x4B, 0x03, SS_RDEF, /* XXX TBD */ "ACK/NAK timeout") }, /* DT PWROMAEBK */ { SST(0x4B, 0x04, SS_RDEF, /* XXX TBD */ "NAK received") }, /* DT PWROMAEBK */ { SST(0x4B, 0x05, SS_RDEF, /* XXX TBD */ "Data offset error") }, /* DT PWROMAEBK */ { SST(0x4B, 0x06, SS_RDEF, /* XXX TBD */ "Initiator response timeout") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x07, SS_RDEF, /* XXX TBD */ "Connection lost") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x08, SS_RDEF, /* XXX TBD */ "Data-in buffer overflow - data buffer size") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x09, SS_RDEF, /* XXX TBD */ "Data-in buffer overflow - data buffer descriptor area") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x0A, SS_RDEF, /* XXX TBD */ "Data-in buffer error") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x0B, SS_RDEF, /* XXX TBD */ "Data-out buffer overflow - data buffer size") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x0C, SS_RDEF, /* XXX TBD */ "Data-out buffer overflow - data buffer descriptor area") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x0D, SS_RDEF, /* XXX TBD */ "Data-out buffer error") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x0E, SS_RDEF, /* XXX TBD */ "PCIe fabric error") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x0F, SS_RDEF, /* XXX TBD */ "PCIe completion timeout") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x10, SS_RDEF, /* XXX TBD */ "PCIe completer abort") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x11, SS_RDEF, /* XXX TBD */ "PCIe poisoned TLP received") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x12, SS_RDEF, /* XXX TBD */ "PCIe ECRC check failed") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x13, SS_RDEF, /* XXX TBD */ "PCIe unsupported request") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x14, SS_RDEF, /* XXX TBD */ "PCIe ACS violation") }, /* DT PWROMAEBK F */ { SST(0x4B, 0x15, SS_RDEF, /* XXX TBD */ "PCIe TLP prefix blocket") }, /* DTLPWROMAEBKVF */ { SST(0x4C, 0x00, SS_RDEF, "Logical unit failed self-configuration") }, /* DTLPWROMAEBKVF */ { SST(0x4D, 0x00, SS_RDEF, "Tagged overlapped commands: ASCQ = Queue tag ID") }, /* DTLPWROMAEBKVF */ { SST(0x4D, 0xFF, SS_RDEF | SSQ_RANGE, NULL) }, /* Range 0x00->0xFF */ /* DTLPWROMAEBKVF */ { SST(0x4E, 0x00, SS_RDEF, "Overlapped commands attempted") }, /* T */ { SST(0x50, 0x00, SS_RDEF, "Write append error") }, /* T */ { SST(0x50, 0x01, SS_RDEF, "Write append position error") }, /* T */ { SST(0x50, 0x02, SS_RDEF, "Position error related to timing") }, /* T RO */ { SST(0x51, 0x00, SS_RDEF, "Erase failure") }, /* R */ { SST(0x51, 0x01, SS_RDEF, /* XXX TBD */ "Erase failure - incomplete erase operation detected") }, /* T */ { SST(0x52, 0x00, SS_RDEF, "Cartridge fault") }, /* DTL WROM BK */ { SST(0x53, 0x00, SS_RDEF, "Media load or eject failed") }, /* T */ { SST(0x53, 0x01, SS_RDEF, "Unload tape failure") }, /* DT WROM BK */ { SST(0x53, 0x02, SS_RDEF, "Medium removal prevented") }, /* M */ { SST(0x53, 0x03, SS_RDEF, /* XXX TBD */ "Medium removal prevented by data transfer element") }, /* T */ { SST(0x53, 0x04, SS_RDEF, /* XXX TBD */ "Medium thread or unthread failure") }, /* M */ { SST(0x53, 0x05, SS_RDEF, /* XXX TBD */ "Volume identifier invalid") }, /* T */ { SST(0x53, 0x06, SS_RDEF, /* XXX TBD */ "Volume identifier missing") }, /* M */ { SST(0x53, 0x07, SS_RDEF, /* XXX TBD */ "Duplicate volume identifier") }, /* M */ { SST(0x53, 0x08, SS_RDEF, /* XXX TBD */ "Element status unknown") }, /* M */ { SST(0x53, 0x09, SS_RDEF, /* XXX TBD */ "Data transfer device error - load failed") }, /* M */ { SST(0x53, 0x0A, SS_RDEF, /* XXX TBD */ "Data transfer device error - unload failed") }, /* M */ { SST(0x53, 0x0B, SS_RDEF, /* XXX TBD */ "Data transfer device error - unload missing") }, /* M */ { SST(0x53, 0x0C, SS_RDEF, /* XXX TBD */ "Data transfer device error - eject failed") }, /* M */ { SST(0x53, 0x0D, SS_RDEF, /* XXX TBD */ "Data transfer device error - library communication failed") }, /* P */ { SST(0x54, 0x00, SS_RDEF, "SCSI to host system interface failure") }, /* P */ { SST(0x55, 0x00, SS_RDEF, "System resource failure") }, /* D O BK */ { SST(0x55, 0x01, SS_FATAL | ENOSPC, "System buffer full") }, /* DTLPWROMAE K */ { SST(0x55, 0x02, SS_RDEF, /* XXX TBD */ "Insufficient reservation resources") }, /* DTLPWROMAE K */ { SST(0x55, 0x03, SS_RDEF, /* XXX TBD */ "Insufficient resources") }, /* DTLPWROMAE K */ { SST(0x55, 0x04, SS_RDEF, /* XXX TBD */ "Insufficient registration resources") }, /* DT PWROMAEBK */ { SST(0x55, 0x05, SS_RDEF, /* XXX TBD */ "Insufficient access control resources") }, /* DT WROM B */ { SST(0x55, 0x06, SS_RDEF, /* XXX TBD */ "Auxiliary memory out of space") }, /* F */ { SST(0x55, 0x07, SS_RDEF, /* XXX TBD */ "Quota error") }, /* T */ { SST(0x55, 0x08, SS_RDEF, /* XXX TBD */ "Maximum number of supplemental decryption keys exceeded") }, /* M */ { SST(0x55, 0x09, SS_RDEF, /* XXX TBD */ "Medium auxiliary memory not accessible") }, /* M */ { SST(0x55, 0x0A, SS_RDEF, /* XXX TBD */ "Data currently unavailable") }, /* DTLPWROMAEBKVF */ { SST(0x55, 0x0B, SS_RDEF, /* XXX TBD */ "Insufficient power for operation") }, /* DT P B */ { SST(0x55, 0x0C, SS_RDEF, /* XXX TBD */ "Insufficient resources to create ROD") }, /* DT P B */ { SST(0x55, 0x0D, SS_RDEF, /* XXX TBD */ "Insufficient resources to create ROD token") }, /* D */ { SST(0x55, 0x0E, SS_RDEF, /* XXX TBD */ "Insufficient zone resources") }, /* D */ { SST(0x55, 0x0F, SS_RDEF, /* XXX TBD */ "Insufficient zone resources to complete write") }, /* D */ { SST(0x55, 0x10, SS_RDEF, /* XXX TBD */ "Maximum number of streams open") }, /* R */ { SST(0x57, 0x00, SS_RDEF, "Unable to recover table-of-contents") }, /* O */ { SST(0x58, 0x00, SS_RDEF, "Generation does not exist") }, /* O */ { SST(0x59, 0x00, SS_RDEF, "Updated block read") }, /* DTLPWRO BK */ { SST(0x5A, 0x00, SS_RDEF, "Operator request or state change input") }, /* DT WROM BK */ { SST(0x5A, 0x01, SS_RDEF, "Operator medium removal request") }, /* DT WRO A BK */ { SST(0x5A, 0x02, SS_RDEF, "Operator selected write protect") }, /* DT WRO A BK */ { SST(0x5A, 0x03, SS_RDEF, "Operator selected write permit") }, /* DTLPWROM K */ { SST(0x5B, 0x00, SS_RDEF, "Log exception") }, /* DTLPWROM K */ { SST(0x5B, 0x01, SS_RDEF, "Threshold condition met") }, /* DTLPWROM K */ { SST(0x5B, 0x02, SS_RDEF, "Log counter at maximum") }, /* DTLPWROM K */ { SST(0x5B, 0x03, SS_RDEF, "Log list codes exhausted") }, /* D O */ { SST(0x5C, 0x00, SS_RDEF, "RPL status change") }, /* D O */ { SST(0x5C, 0x01, SS_NOP | SSQ_PRINT_SENSE, "Spindles synchronized") }, /* D O */ { SST(0x5C, 0x02, SS_RDEF, "Spindles not synchronized") }, /* DTLPWROMAEBKVF */ { SST(0x5D, 0x00, SS_NOP | SSQ_PRINT_SENSE, "Failure prediction threshold exceeded") }, /* R B */ { SST(0x5D, 0x01, SS_NOP | SSQ_PRINT_SENSE, "Media failure prediction threshold exceeded") }, /* R */ { SST(0x5D, 0x02, SS_NOP | SSQ_PRINT_SENSE, "Logical unit failure prediction threshold exceeded") }, /* R */ { SST(0x5D, 0x03, SS_NOP | SSQ_PRINT_SENSE, "Spare area exhaustion prediction threshold exceeded") }, /* D B */ { SST(0x5D, 0x10, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure general hard drive failure") }, /* D B */ { SST(0x5D, 0x11, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure drive error rate too high") }, /* D B */ { SST(0x5D, 0x12, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure data error rate too high") }, /* D B */ { SST(0x5D, 0x13, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure seek error rate too high") }, /* D B */ { SST(0x5D, 0x14, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure too many block reassigns") }, /* D B */ { SST(0x5D, 0x15, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure access times too high") }, /* D B */ { SST(0x5D, 0x16, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure start unit times too high") }, /* D B */ { SST(0x5D, 0x17, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure channel parametrics") }, /* D B */ { SST(0x5D, 0x18, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure controller detected") }, /* D B */ { SST(0x5D, 0x19, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure throughput performance") }, /* D B */ { SST(0x5D, 0x1A, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure seek time performance") }, /* D B */ { SST(0x5D, 0x1B, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure spin-up retry count") }, /* D B */ { SST(0x5D, 0x1C, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure drive calibration retry count") }, /* D B */ { SST(0x5D, 0x1D, SS_NOP | SSQ_PRINT_SENSE, "Hardware impending failure power loss protection circuit") }, /* D B */ { SST(0x5D, 0x20, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure general hard drive failure") }, /* D B */ { SST(0x5D, 0x21, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure drive error rate too high") }, /* D B */ { SST(0x5D, 0x22, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure data error rate too high") }, /* D B */ { SST(0x5D, 0x23, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure seek error rate too high") }, /* D B */ { SST(0x5D, 0x24, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure too many block reassigns") }, /* D B */ { SST(0x5D, 0x25, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure access times too high") }, /* D B */ { SST(0x5D, 0x26, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure start unit times too high") }, /* D B */ { SST(0x5D, 0x27, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure channel parametrics") }, /* D B */ { SST(0x5D, 0x28, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure controller detected") }, /* D B */ { SST(0x5D, 0x29, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure throughput performance") }, /* D B */ { SST(0x5D, 0x2A, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure seek time performance") }, /* D B */ { SST(0x5D, 0x2B, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure spin-up retry count") }, /* D B */ { SST(0x5D, 0x2C, SS_NOP | SSQ_PRINT_SENSE, "Controller impending failure drive calibration retry count") }, /* D B */ { SST(0x5D, 0x30, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure general hard drive failure") }, /* D B */ { SST(0x5D, 0x31, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure drive error rate too high") }, /* D B */ { SST(0x5D, 0x32, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure data error rate too high") }, /* D B */ { SST(0x5D, 0x33, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure seek error rate too high") }, /* D B */ { SST(0x5D, 0x34, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure too many block reassigns") }, /* D B */ { SST(0x5D, 0x35, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure access times too high") }, /* D B */ { SST(0x5D, 0x36, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure start unit times too high") }, /* D B */ { SST(0x5D, 0x37, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure channel parametrics") }, /* D B */ { SST(0x5D, 0x38, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure controller detected") }, /* D B */ { SST(0x5D, 0x39, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure throughput performance") }, /* D B */ { SST(0x5D, 0x3A, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure seek time performance") }, /* D B */ { SST(0x5D, 0x3B, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure spin-up retry count") }, /* D B */ { SST(0x5D, 0x3C, SS_NOP | SSQ_PRINT_SENSE, "Data channel impending failure drive calibration retry count") }, /* D B */ { SST(0x5D, 0x40, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure general hard drive failure") }, /* D B */ { SST(0x5D, 0x41, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure drive error rate too high") }, /* D B */ { SST(0x5D, 0x42, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure data error rate too high") }, /* D B */ { SST(0x5D, 0x43, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure seek error rate too high") }, /* D B */ { SST(0x5D, 0x44, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure too many block reassigns") }, /* D B */ { SST(0x5D, 0x45, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure access times too high") }, /* D B */ { SST(0x5D, 0x46, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure start unit times too high") }, /* D B */ { SST(0x5D, 0x47, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure channel parametrics") }, /* D B */ { SST(0x5D, 0x48, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure controller detected") }, /* D B */ { SST(0x5D, 0x49, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure throughput performance") }, /* D B */ { SST(0x5D, 0x4A, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure seek time performance") }, /* D B */ { SST(0x5D, 0x4B, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure spin-up retry count") }, /* D B */ { SST(0x5D, 0x4C, SS_NOP | SSQ_PRINT_SENSE, "Servo impending failure drive calibration retry count") }, /* D B */ { SST(0x5D, 0x50, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure general hard drive failure") }, /* D B */ { SST(0x5D, 0x51, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure drive error rate too high") }, /* D B */ { SST(0x5D, 0x52, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure data error rate too high") }, /* D B */ { SST(0x5D, 0x53, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure seek error rate too high") }, /* D B */ { SST(0x5D, 0x54, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure too many block reassigns") }, /* D B */ { SST(0x5D, 0x55, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure access times too high") }, /* D B */ { SST(0x5D, 0x56, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure start unit times too high") }, /* D B */ { SST(0x5D, 0x57, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure channel parametrics") }, /* D B */ { SST(0x5D, 0x58, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure controller detected") }, /* D B */ { SST(0x5D, 0x59, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure throughput performance") }, /* D B */ { SST(0x5D, 0x5A, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure seek time performance") }, /* D B */ { SST(0x5D, 0x5B, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure spin-up retry count") }, /* D B */ { SST(0x5D, 0x5C, SS_NOP | SSQ_PRINT_SENSE, "Spindle impending failure drive calibration retry count") }, /* D B */ { SST(0x5D, 0x60, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure general hard drive failure") }, /* D B */ { SST(0x5D, 0x61, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure drive error rate too high") }, /* D B */ { SST(0x5D, 0x62, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure data error rate too high") }, /* D B */ { SST(0x5D, 0x63, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure seek error rate too high") }, /* D B */ { SST(0x5D, 0x64, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure too many block reassigns") }, /* D B */ { SST(0x5D, 0x65, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure access times too high") }, /* D B */ { SST(0x5D, 0x66, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure start unit times too high") }, /* D B */ { SST(0x5D, 0x67, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure channel parametrics") }, /* D B */ { SST(0x5D, 0x68, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure controller detected") }, /* D B */ { SST(0x5D, 0x69, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure throughput performance") }, /* D B */ { SST(0x5D, 0x6A, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure seek time performance") }, /* D B */ { SST(0x5D, 0x6B, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure spin-up retry count") }, /* D B */ { SST(0x5D, 0x6C, SS_NOP | SSQ_PRINT_SENSE, "Firmware impending failure drive calibration retry count") }, /* D B */ { SST(0x5D, 0x73, SS_NOP | SSQ_PRINT_SENSE, "Media impending failure endurance limit met") }, /* DTLPWROMAEBKVF */ { SST(0x5D, 0xFF, SS_NOP | SSQ_PRINT_SENSE, "Failure prediction threshold exceeded (false)") }, /* DTLPWRO A K */ { SST(0x5E, 0x00, SS_RDEF, "Low power condition on") }, /* DTLPWRO A K */ { SST(0x5E, 0x01, SS_RDEF, "Idle condition activated by timer") }, /* DTLPWRO A K */ { SST(0x5E, 0x02, SS_RDEF, "Standby condition activated by timer") }, /* DTLPWRO A K */ { SST(0x5E, 0x03, SS_RDEF, "Idle condition activated by command") }, /* DTLPWRO A K */ { SST(0x5E, 0x04, SS_RDEF, "Standby condition activated by command") }, /* DTLPWRO A K */ { SST(0x5E, 0x05, SS_RDEF, "Idle-B condition activated by timer") }, /* DTLPWRO A K */ { SST(0x5E, 0x06, SS_RDEF, "Idle-B condition activated by command") }, /* DTLPWRO A K */ { SST(0x5E, 0x07, SS_RDEF, "Idle-C condition activated by timer") }, /* DTLPWRO A K */ { SST(0x5E, 0x08, SS_RDEF, "Idle-C condition activated by command") }, /* DTLPWRO A K */ { SST(0x5E, 0x09, SS_RDEF, "Standby-Y condition activated by timer") }, /* DTLPWRO A K */ { SST(0x5E, 0x0A, SS_RDEF, "Standby-Y condition activated by command") }, /* B */ { SST(0x5E, 0x41, SS_RDEF, /* XXX TBD */ "Power state change to active") }, /* B */ { SST(0x5E, 0x42, SS_RDEF, /* XXX TBD */ "Power state change to idle") }, /* B */ { SST(0x5E, 0x43, SS_RDEF, /* XXX TBD */ "Power state change to standby") }, /* B */ { SST(0x5E, 0x45, SS_RDEF, /* XXX TBD */ "Power state change to sleep") }, /* BK */ { SST(0x5E, 0x47, SS_RDEF, /* XXX TBD */ "Power state change to device control") }, /* */ { SST(0x60, 0x00, SS_RDEF, "Lamp failure") }, /* */ { SST(0x61, 0x00, SS_RDEF, "Video acquisition error") }, /* */ { SST(0x61, 0x01, SS_RDEF, "Unable to acquire video") }, /* */ { SST(0x61, 0x02, SS_RDEF, "Out of focus") }, /* */ { SST(0x62, 0x00, SS_RDEF, "Scan head positioning error") }, /* R */ { SST(0x63, 0x00, SS_RDEF, "End of user area encountered on this track") }, /* R */ { SST(0x63, 0x01, SS_FATAL | ENOSPC, "Packet does not fit in available space") }, /* R */ { SST(0x64, 0x00, SS_FATAL | ENXIO, "Illegal mode for this track") }, /* R */ { SST(0x64, 0x01, SS_RDEF, "Invalid packet size") }, /* DTLPWROMAEBKVF */ { SST(0x65, 0x00, SS_RDEF, "Voltage fault") }, /* */ { SST(0x66, 0x00, SS_RDEF, "Automatic document feeder cover up") }, /* */ { SST(0x66, 0x01, SS_RDEF, "Automatic document feeder lift up") }, /* */ { SST(0x66, 0x02, SS_RDEF, "Document jam in automatic document feeder") }, /* */ { SST(0x66, 0x03, SS_RDEF, "Document miss feed automatic in document feeder") }, /* A */ { SST(0x67, 0x00, SS_RDEF, "Configuration failure") }, /* A */ { SST(0x67, 0x01, SS_RDEF, "Configuration of incapable logical units failed") }, /* A */ { SST(0x67, 0x02, SS_RDEF, "Add logical unit failed") }, /* A */ { SST(0x67, 0x03, SS_RDEF, "Modification of logical unit failed") }, /* A */ { SST(0x67, 0x04, SS_RDEF, "Exchange of logical unit failed") }, /* A */ { SST(0x67, 0x05, SS_RDEF, "Remove of logical unit failed") }, /* A */ { SST(0x67, 0x06, SS_RDEF, "Attachment of logical unit failed") }, /* A */ { SST(0x67, 0x07, SS_RDEF, "Creation of logical unit failed") }, /* A */ { SST(0x67, 0x08, SS_RDEF, /* XXX TBD */ "Assign failure occurred") }, /* A */ { SST(0x67, 0x09, SS_RDEF, /* XXX TBD */ "Multiply assigned logical unit") }, /* DTLPWROMAEBKVF */ { SST(0x67, 0x0A, SS_RDEF, /* XXX TBD */ "Set target port groups command failed") }, /* DT B */ { SST(0x67, 0x0B, SS_RDEF, /* XXX TBD */ "ATA device feature not enabled") }, /* A */ { SST(0x68, 0x00, SS_RDEF, "Logical unit not configured") }, /* D */ { SST(0x68, 0x01, SS_RDEF, "Subsidiary logical unit not configured") }, /* A */ { SST(0x69, 0x00, SS_RDEF, "Data loss on logical unit") }, /* A */ { SST(0x69, 0x01, SS_RDEF, "Multiple logical unit failures") }, /* A */ { SST(0x69, 0x02, SS_RDEF, "Parity/data mismatch") }, /* A */ { SST(0x6A, 0x00, SS_RDEF, "Informational, refer to log") }, /* A */ { SST(0x6B, 0x00, SS_RDEF, "State change has occurred") }, /* A */ { SST(0x6B, 0x01, SS_RDEF, "Redundancy level got better") }, /* A */ { SST(0x6B, 0x02, SS_RDEF, "Redundancy level got worse") }, /* A */ { SST(0x6C, 0x00, SS_RDEF, "Rebuild failure occurred") }, /* A */ { SST(0x6D, 0x00, SS_RDEF, "Recalculate failure occurred") }, /* A */ { SST(0x6E, 0x00, SS_RDEF, "Command to logical unit failed") }, /* R */ { SST(0x6F, 0x00, SS_RDEF, /* XXX TBD */ "Copy protection key exchange failure - authentication failure") }, /* R */ { SST(0x6F, 0x01, SS_RDEF, /* XXX TBD */ "Copy protection key exchange failure - key not present") }, /* R */ { SST(0x6F, 0x02, SS_RDEF, /* XXX TBD */ "Copy protection key exchange failure - key not established") }, /* R */ { SST(0x6F, 0x03, SS_RDEF, /* XXX TBD */ "Read of scrambled sector without authentication") }, /* R */ { SST(0x6F, 0x04, SS_RDEF, /* XXX TBD */ "Media region code is mismatched to logical unit region") }, /* R */ { SST(0x6F, 0x05, SS_RDEF, /* XXX TBD */ "Drive region must be permanent/region reset count error") }, /* R */ { SST(0x6F, 0x06, SS_RDEF, /* XXX TBD */ "Insufficient block count for binding NONCE recording") }, /* R */ { SST(0x6F, 0x07, SS_RDEF, /* XXX TBD */ "Conflict in binding NONCE recording") }, /* T */ { SST(0x70, 0x00, SS_RDEF, "Decompression exception short: ASCQ = Algorithm ID") }, /* T */ { SST(0x70, 0xFF, SS_RDEF | SSQ_RANGE, NULL) }, /* Range 0x00 -> 0xFF */ /* T */ { SST(0x71, 0x00, SS_RDEF, "Decompression exception long: ASCQ = Algorithm ID") }, /* T */ { SST(0x71, 0xFF, SS_RDEF | SSQ_RANGE, NULL) }, /* Range 0x00 -> 0xFF */ /* R */ { SST(0x72, 0x00, SS_RDEF, "Session fixation error") }, /* R */ { SST(0x72, 0x01, SS_RDEF, "Session fixation error writing lead-in") }, /* R */ { SST(0x72, 0x02, SS_RDEF, "Session fixation error writing lead-out") }, /* R */ { SST(0x72, 0x03, SS_RDEF, "Session fixation error - incomplete track in session") }, /* R */ { SST(0x72, 0x04, SS_RDEF, "Empty or partially written reserved track") }, /* R */ { SST(0x72, 0x05, SS_RDEF, /* XXX TBD */ "No more track reservations allowed") }, /* R */ { SST(0x72, 0x06, SS_RDEF, /* XXX TBD */ "RMZ extension is not allowed") }, /* R */ { SST(0x72, 0x07, SS_RDEF, /* XXX TBD */ "No more test zone extensions are allowed") }, /* R */ { SST(0x73, 0x00, SS_RDEF, "CD control error") }, /* R */ { SST(0x73, 0x01, SS_RDEF, "Power calibration area almost full") }, /* R */ { SST(0x73, 0x02, SS_FATAL | ENOSPC, "Power calibration area is full") }, /* R */ { SST(0x73, 0x03, SS_RDEF, "Power calibration area error") }, /* R */ { SST(0x73, 0x04, SS_RDEF, "Program memory area update failure") }, /* R */ { SST(0x73, 0x05, SS_RDEF, "Program memory area is full") }, /* R */ { SST(0x73, 0x06, SS_RDEF, /* XXX TBD */ "RMA/PMA is almost full") }, /* R */ { SST(0x73, 0x10, SS_RDEF, /* XXX TBD */ "Current power calibration area almost full") }, /* R */ { SST(0x73, 0x11, SS_RDEF, /* XXX TBD */ "Current power calibration area is full") }, /* R */ { SST(0x73, 0x17, SS_RDEF, /* XXX TBD */ "RDZ is full") }, /* T */ { SST(0x74, 0x00, SS_RDEF, /* XXX TBD */ "Security error") }, /* T */ { SST(0x74, 0x01, SS_RDEF, /* XXX TBD */ "Unable to decrypt data") }, /* T */ { SST(0x74, 0x02, SS_RDEF, /* XXX TBD */ "Unencrypted data encountered while decrypting") }, /* T */ { SST(0x74, 0x03, SS_RDEF, /* XXX TBD */ "Incorrect data encryption key") }, /* T */ { SST(0x74, 0x04, SS_RDEF, /* XXX TBD */ "Cryptographic integrity validation failed") }, /* T */ { SST(0x74, 0x05, SS_RDEF, /* XXX TBD */ "Error decrypting data") }, /* T */ { SST(0x74, 0x06, SS_RDEF, /* XXX TBD */ "Unknown signature verification key") }, /* T */ { SST(0x74, 0x07, SS_RDEF, /* XXX TBD */ "Encryption parameters not useable") }, /* DT R M E VF */ { SST(0x74, 0x08, SS_RDEF, /* XXX TBD */ "Digital signature validation failure") }, /* T */ { SST(0x74, 0x09, SS_RDEF, /* XXX TBD */ "Encryption mode mismatch on read") }, /* T */ { SST(0x74, 0x0A, SS_RDEF, /* XXX TBD */ "Encrypted block not raw read enabled") }, /* T */ { SST(0x74, 0x0B, SS_RDEF, /* XXX TBD */ "Incorrect encryption parameters") }, /* DT R MAEBKV */ { SST(0x74, 0x0C, SS_RDEF, /* XXX TBD */ "Unable to decrypt parameter list") }, /* T */ { SST(0x74, 0x0D, SS_RDEF, /* XXX TBD */ "Encryption algorithm disabled") }, /* DT R MAEBKV */ { SST(0x74, 0x10, SS_RDEF, /* XXX TBD */ "SA creation parameter value invalid") }, /* DT R MAEBKV */ { SST(0x74, 0x11, SS_RDEF, /* XXX TBD */ "SA creation parameter value rejected") }, /* DT R MAEBKV */ { SST(0x74, 0x12, SS_RDEF, /* XXX TBD */ "Invalid SA usage") }, /* T */ { SST(0x74, 0x21, SS_RDEF, /* XXX TBD */ "Data encryption configuration prevented") }, /* DT R MAEBKV */ { SST(0x74, 0x30, SS_RDEF, /* XXX TBD */ "SA creation parameter not supported") }, /* DT R MAEBKV */ { SST(0x74, 0x40, SS_RDEF, /* XXX TBD */ "Authentication failed") }, /* V */ { SST(0x74, 0x61, SS_RDEF, /* XXX TBD */ "External data encryption key manager access error") }, /* V */ { SST(0x74, 0x62, SS_RDEF, /* XXX TBD */ "External data encryption key manager error") }, /* V */ { SST(0x74, 0x63, SS_RDEF, /* XXX TBD */ "External data encryption key not found") }, /* V */ { SST(0x74, 0x64, SS_RDEF, /* XXX TBD */ "External data encryption request not authorized") }, /* T */ { SST(0x74, 0x6E, SS_RDEF, /* XXX TBD */ "External data encryption control timeout") }, /* T */ { SST(0x74, 0x6F, SS_RDEF, /* XXX TBD */ "External data encryption control error") }, /* DT R M E V */ { SST(0x74, 0x71, SS_FATAL | EACCES, "Logical unit access not authorized") }, /* D */ { SST(0x74, 0x79, SS_FATAL | EACCES, "Security conflict in translated device") } }; const u_int asc_table_size = nitems(asc_table); struct asc_key { int asc; int ascq; }; static int ascentrycomp(const void *key, const void *member) { int asc; int ascq; const struct asc_table_entry *table_entry; asc = ((const struct asc_key *)key)->asc; ascq = ((const struct asc_key *)key)->ascq; table_entry = (const struct asc_table_entry *)member; if (asc >= table_entry->asc) { if (asc > table_entry->asc) return (1); if (ascq <= table_entry->ascq) { /* Check for ranges */ if (ascq == table_entry->ascq || ((table_entry->action & SSQ_RANGE) != 0 && ascq >= (table_entry - 1)->ascq)) return (0); return (-1); } return (1); } return (-1); } static int senseentrycomp(const void *key, const void *member) { int sense_key; const struct sense_key_table_entry *table_entry; sense_key = *((const int *)key); table_entry = (const struct sense_key_table_entry *)member; if (sense_key >= table_entry->sense_key) { if (sense_key == table_entry->sense_key) return (0); return (1); } return (-1); } static void fetchtableentries(int sense_key, int asc, int ascq, struct scsi_inquiry_data *inq_data, const struct sense_key_table_entry **sense_entry, const struct asc_table_entry **asc_entry) { caddr_t match; const struct asc_table_entry *asc_tables[2]; const struct sense_key_table_entry *sense_tables[2]; struct asc_key asc_ascq; size_t asc_tables_size[2]; size_t sense_tables_size[2]; int num_asc_tables; int num_sense_tables; int i; /* Default to failure */ *sense_entry = NULL; *asc_entry = NULL; match = NULL; if (inq_data != NULL) match = cam_quirkmatch((caddr_t)inq_data, (caddr_t)sense_quirk_table, sense_quirk_table_size, sizeof(*sense_quirk_table), scsi_inquiry_match); if (match != NULL) { struct scsi_sense_quirk_entry *quirk; quirk = (struct scsi_sense_quirk_entry *)match; asc_tables[0] = quirk->asc_info; asc_tables_size[0] = quirk->num_ascs; asc_tables[1] = asc_table; asc_tables_size[1] = asc_table_size; num_asc_tables = 2; sense_tables[0] = quirk->sense_key_info; sense_tables_size[0] = quirk->num_sense_keys; sense_tables[1] = sense_key_table; sense_tables_size[1] = nitems(sense_key_table); num_sense_tables = 2; } else { asc_tables[0] = asc_table; asc_tables_size[0] = asc_table_size; num_asc_tables = 1; sense_tables[0] = sense_key_table; sense_tables_size[0] = nitems(sense_key_table); num_sense_tables = 1; } asc_ascq.asc = asc; asc_ascq.ascq = ascq; for (i = 0; i < num_asc_tables; i++) { void *found_entry; found_entry = bsearch(&asc_ascq, asc_tables[i], asc_tables_size[i], sizeof(**asc_tables), ascentrycomp); if (found_entry) { *asc_entry = (struct asc_table_entry *)found_entry; break; } } for (i = 0; i < num_sense_tables; i++) { void *found_entry; found_entry = bsearch(&sense_key, sense_tables[i], sense_tables_size[i], sizeof(**sense_tables), senseentrycomp); if (found_entry) { *sense_entry = (struct sense_key_table_entry *)found_entry; break; } } } void scsi_sense_desc(int sense_key, int asc, int ascq, struct scsi_inquiry_data *inq_data, const char **sense_key_desc, const char **asc_desc) { const struct asc_table_entry *asc_entry; const struct sense_key_table_entry *sense_entry; fetchtableentries(sense_key, asc, ascq, inq_data, &sense_entry, &asc_entry); if (sense_entry != NULL) *sense_key_desc = sense_entry->desc; else *sense_key_desc = "Invalid Sense Key"; if (asc_entry != NULL) *asc_desc = asc_entry->desc; else if (asc >= 0x80 && asc <= 0xff) *asc_desc = "Vendor Specific ASC"; else if (ascq >= 0x80 && ascq <= 0xff) *asc_desc = "Vendor Specific ASCQ"; else *asc_desc = "Reserved ASC/ASCQ pair"; } /* * Given sense and device type information, return the appropriate action. * If we do not understand the specific error as identified by the ASC/ASCQ * pair, fall back on the more generic actions derived from the sense key. */ scsi_sense_action scsi_error_action(struct ccb_scsiio *csio, struct scsi_inquiry_data *inq_data, u_int32_t sense_flags) { const struct asc_table_entry *asc_entry; const struct sense_key_table_entry *sense_entry; int error_code, sense_key, asc, ascq; scsi_sense_action action; if (!scsi_extract_sense_ccb((union ccb *)csio, &error_code, &sense_key, &asc, &ascq)) { action = SS_RETRY | SSQ_DECREMENT_COUNT | SSQ_PRINT_SENSE | EIO; } else if ((error_code == SSD_DEFERRED_ERROR) || (error_code == SSD_DESC_DEFERRED_ERROR)) { /* * XXX dufault@FreeBSD.org * This error doesn't relate to the command associated * with this request sense. A deferred error is an error * for a command that has already returned GOOD status * (see SCSI2 8.2.14.2). * * By my reading of that section, it looks like the current * command has been cancelled, we should now clean things up * (hopefully recovering any lost data) and then retry the * current command. There are two easy choices, both wrong: * * 1. Drop through (like we had been doing), thus treating * this as if the error were for the current command and * return and stop the current command. * * 2. Issue a retry (like I made it do) thus hopefully * recovering the current transfer, and ignoring the * fact that we've dropped a command. * * These should probably be handled in a device specific * sense handler or punted back up to a user mode daemon */ action = SS_RETRY|SSQ_DECREMENT_COUNT|SSQ_PRINT_SENSE; } else { fetchtableentries(sense_key, asc, ascq, inq_data, &sense_entry, &asc_entry); /* * Override the 'No additional Sense' entry (0,0) * with the error action of the sense key. */ if (asc_entry != NULL && (asc != 0 || ascq != 0)) action = asc_entry->action; else if (sense_entry != NULL) action = sense_entry->action; else action = SS_RETRY|SSQ_DECREMENT_COUNT|SSQ_PRINT_SENSE; if (sense_key == SSD_KEY_RECOVERED_ERROR) { /* * The action succeeded but the device wants * the user to know that some recovery action * was required. */ action &= ~(SS_MASK|SSQ_MASK|SS_ERRMASK); action |= SS_NOP|SSQ_PRINT_SENSE; } else if (sense_key == SSD_KEY_ILLEGAL_REQUEST) { if ((sense_flags & SF_QUIET_IR) != 0) action &= ~SSQ_PRINT_SENSE; } else if (sense_key == SSD_KEY_UNIT_ATTENTION) { if ((sense_flags & SF_RETRY_UA) != 0 && (action & SS_MASK) == SS_FAIL) { action &= ~(SS_MASK|SSQ_MASK); action |= SS_RETRY|SSQ_DECREMENT_COUNT| SSQ_PRINT_SENSE; } action |= SSQ_UA; } } if ((action & SS_MASK) >= SS_START && (sense_flags & SF_NO_RECOVERY)) { action &= ~SS_MASK; action |= SS_FAIL; } else if ((action & SS_MASK) == SS_RETRY && (sense_flags & SF_NO_RETRY)) { action &= ~SS_MASK; action |= SS_FAIL; } if ((sense_flags & SF_PRINT_ALWAYS) != 0) action |= SSQ_PRINT_SENSE; else if ((sense_flags & SF_NO_PRINT) != 0) action &= ~SSQ_PRINT_SENSE; return (action); } char * scsi_cdb_string(u_int8_t *cdb_ptr, char *cdb_string, size_t len) { struct sbuf sb; int error; if (len == 0) return (""); sbuf_new(&sb, cdb_string, len, SBUF_FIXEDLEN); scsi_cdb_sbuf(cdb_ptr, &sb); /* ENOMEM just means that the fixed buffer is full, OK to ignore */ error = sbuf_finish(&sb); if (error != 0 && error != ENOMEM) return (""); return(sbuf_data(&sb)); } void scsi_cdb_sbuf(u_int8_t *cdb_ptr, struct sbuf *sb) { u_int8_t cdb_len; int i; if (cdb_ptr == NULL) return; /* * This is taken from the SCSI-3 draft spec. * (T10/1157D revision 0.3) * The top 3 bits of an opcode are the group code. The next 5 bits * are the command code. * Group 0: six byte commands * Group 1: ten byte commands * Group 2: ten byte commands * Group 3: reserved * Group 4: sixteen byte commands * Group 5: twelve byte commands * Group 6: vendor specific * Group 7: vendor specific */ switch((*cdb_ptr >> 5) & 0x7) { case 0: cdb_len = 6; break; case 1: case 2: cdb_len = 10; break; case 3: case 6: case 7: /* in this case, just print out the opcode */ cdb_len = 1; break; case 4: cdb_len = 16; break; case 5: cdb_len = 12; break; } for (i = 0; i < cdb_len; i++) sbuf_printf(sb, "%02hhx ", cdb_ptr[i]); return; } const char * scsi_status_string(struct ccb_scsiio *csio) { switch(csio->scsi_status) { case SCSI_STATUS_OK: return("OK"); case SCSI_STATUS_CHECK_COND: return("Check Condition"); case SCSI_STATUS_BUSY: return("Busy"); case SCSI_STATUS_INTERMED: return("Intermediate"); case SCSI_STATUS_INTERMED_COND_MET: return("Intermediate-Condition Met"); case SCSI_STATUS_RESERV_CONFLICT: return("Reservation Conflict"); case SCSI_STATUS_CMD_TERMINATED: return("Command Terminated"); case SCSI_STATUS_QUEUE_FULL: return("Queue Full"); case SCSI_STATUS_ACA_ACTIVE: return("ACA Active"); case SCSI_STATUS_TASK_ABORTED: return("Task Aborted"); default: { static char unkstr[64]; snprintf(unkstr, sizeof(unkstr), "Unknown %#x", csio->scsi_status); return(unkstr); } } } /* * scsi_command_string() returns 0 for success and -1 for failure. */ #ifdef _KERNEL int scsi_command_string(struct ccb_scsiio *csio, struct sbuf *sb) #else /* !_KERNEL */ int scsi_command_string(struct cam_device *device, struct ccb_scsiio *csio, struct sbuf *sb) #endif /* _KERNEL/!_KERNEL */ { struct scsi_inquiry_data *inq_data; #ifdef _KERNEL struct ccb_getdev *cgd; #endif /* _KERNEL */ #ifdef _KERNEL if ((cgd = (struct ccb_getdev*)xpt_alloc_ccb_nowait()) == NULL) return(-1); /* * Get the device information. */ xpt_setup_ccb(&cgd->ccb_h, csio->ccb_h.path, CAM_PRIORITY_NORMAL); cgd->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)cgd); /* * If the device is unconfigured, just pretend that it is a hard * drive. scsi_op_desc() needs this. */ if (cgd->ccb_h.status == CAM_DEV_NOT_THERE) cgd->inq_data.device = T_DIRECT; inq_data = &cgd->inq_data; #else /* !_KERNEL */ inq_data = &device->inq_data; #endif /* _KERNEL/!_KERNEL */ sbuf_printf(sb, "%s. CDB: ", scsi_op_desc(scsiio_cdb_ptr(csio)[0], inq_data)); scsi_cdb_sbuf(scsiio_cdb_ptr(csio), sb); #ifdef _KERNEL xpt_free_ccb((union ccb *)cgd); #endif return(0); } /* * Iterate over sense descriptors. Each descriptor is passed into iter_func(). * If iter_func() returns 0, list traversal continues. If iter_func() * returns non-zero, list traversal is stopped. */ void scsi_desc_iterate(struct scsi_sense_data_desc *sense, u_int sense_len, int (*iter_func)(struct scsi_sense_data_desc *sense, u_int, struct scsi_sense_desc_header *, void *), void *arg) { int cur_pos; int desc_len; /* * First make sure the extra length field is present. */ if (SSD_DESC_IS_PRESENT(sense, sense_len, extra_len) == 0) return; /* * The length of data actually returned may be different than the * extra_len recorded in the structure. */ desc_len = sense_len -offsetof(struct scsi_sense_data_desc, sense_desc); /* * Limit this further by the extra length reported, and the maximum * allowed extra length. */ desc_len = MIN(desc_len, MIN(sense->extra_len, SSD_EXTRA_MAX)); /* * Subtract the size of the header from the descriptor length. * This is to ensure that we have at least the header left, so we * don't have to check that inside the loop. This can wind up * being a negative value. */ desc_len -= sizeof(struct scsi_sense_desc_header); for (cur_pos = 0; cur_pos < desc_len;) { struct scsi_sense_desc_header *header; header = (struct scsi_sense_desc_header *) &sense->sense_desc[cur_pos]; /* * Check to make sure we have the entire descriptor. We * don't call iter_func() unless we do. * * Note that although cur_pos is at the beginning of the * descriptor, desc_len already has the header length * subtracted. So the comparison of the length in the * header (which does not include the header itself) to * desc_len - cur_pos is correct. */ if (header->length > (desc_len - cur_pos)) break; if (iter_func(sense, sense_len, header, arg) != 0) break; cur_pos += sizeof(*header) + header->length; } } struct scsi_find_desc_info { uint8_t desc_type; struct scsi_sense_desc_header *header; }; static int scsi_find_desc_func(struct scsi_sense_data_desc *sense, u_int sense_len, struct scsi_sense_desc_header *header, void *arg) { struct scsi_find_desc_info *desc_info; desc_info = (struct scsi_find_desc_info *)arg; if (header->desc_type == desc_info->desc_type) { desc_info->header = header; /* We found the descriptor, tell the iterator to stop. */ return (1); } else return (0); } /* * Given a descriptor type, return a pointer to it if it is in the sense * data and not truncated. Avoiding truncating sense data will simplify * things significantly for the caller. */ uint8_t * scsi_find_desc(struct scsi_sense_data_desc *sense, u_int sense_len, uint8_t desc_type) { struct scsi_find_desc_info desc_info; desc_info.desc_type = desc_type; desc_info.header = NULL; scsi_desc_iterate(sense, sense_len, scsi_find_desc_func, &desc_info); return ((uint8_t *)desc_info.header); } /* * Fill in SCSI descriptor sense data with the specified parameters. */ static void scsi_set_sense_data_desc_va(struct scsi_sense_data *sense_data, u_int *sense_len, scsi_sense_data_type sense_format, int current_error, int sense_key, int asc, int ascq, va_list ap) { struct scsi_sense_data_desc *sense; scsi_sense_elem_type elem_type; int space, len; uint8_t *desc, *data; memset(sense_data, 0, sizeof(*sense_data)); sense = (struct scsi_sense_data_desc *)sense_data; if (current_error != 0) sense->error_code = SSD_DESC_CURRENT_ERROR; else sense->error_code = SSD_DESC_DEFERRED_ERROR; sense->sense_key = sense_key; sense->add_sense_code = asc; sense->add_sense_code_qual = ascq; sense->flags = 0; desc = &sense->sense_desc[0]; space = *sense_len - offsetof(struct scsi_sense_data_desc, sense_desc); while ((elem_type = va_arg(ap, scsi_sense_elem_type)) != SSD_ELEM_NONE) { if (elem_type >= SSD_ELEM_MAX) { printf("%s: invalid sense type %d\n", __func__, elem_type); break; } len = va_arg(ap, int); data = va_arg(ap, uint8_t *); switch (elem_type) { case SSD_ELEM_SKIP: break; case SSD_ELEM_DESC: if (space < len) { sense->flags |= SSDD_SDAT_OVFL; break; } bcopy(data, desc, len); desc += len; space -= len; break; case SSD_ELEM_SKS: { struct scsi_sense_sks *sks = (void *)desc; if (len > sizeof(sks->sense_key_spec)) break; if (space < sizeof(*sks)) { sense->flags |= SSDD_SDAT_OVFL; break; } sks->desc_type = SSD_DESC_SKS; sks->length = sizeof(*sks) - (offsetof(struct scsi_sense_sks, length) + 1); bcopy(data, &sks->sense_key_spec, len); desc += sizeof(*sks); space -= sizeof(*sks); break; } case SSD_ELEM_COMMAND: { struct scsi_sense_command *cmd = (void *)desc; if (len > sizeof(cmd->command_info)) break; if (space < sizeof(*cmd)) { sense->flags |= SSDD_SDAT_OVFL; break; } cmd->desc_type = SSD_DESC_COMMAND; cmd->length = sizeof(*cmd) - (offsetof(struct scsi_sense_command, length) + 1); bcopy(data, &cmd->command_info[ sizeof(cmd->command_info) - len], len); desc += sizeof(*cmd); space -= sizeof(*cmd); break; } case SSD_ELEM_INFO: { struct scsi_sense_info *info = (void *)desc; if (len > sizeof(info->info)) break; if (space < sizeof(*info)) { sense->flags |= SSDD_SDAT_OVFL; break; } info->desc_type = SSD_DESC_INFO; info->length = sizeof(*info) - (offsetof(struct scsi_sense_info, length) + 1); info->byte2 = SSD_INFO_VALID; bcopy(data, &info->info[sizeof(info->info) - len], len); desc += sizeof(*info); space -= sizeof(*info); break; } case SSD_ELEM_FRU: { struct scsi_sense_fru *fru = (void *)desc; if (len > sizeof(fru->fru)) break; if (space < sizeof(*fru)) { sense->flags |= SSDD_SDAT_OVFL; break; } fru->desc_type = SSD_DESC_FRU; fru->length = sizeof(*fru) - (offsetof(struct scsi_sense_fru, length) + 1); fru->fru = *data; desc += sizeof(*fru); space -= sizeof(*fru); break; } case SSD_ELEM_STREAM: { struct scsi_sense_stream *stream = (void *)desc; if (len > sizeof(stream->byte3)) break; if (space < sizeof(*stream)) { sense->flags |= SSDD_SDAT_OVFL; break; } stream->desc_type = SSD_DESC_STREAM; stream->length = sizeof(*stream) - (offsetof(struct scsi_sense_stream, length) + 1); stream->byte3 = *data; desc += sizeof(*stream); space -= sizeof(*stream); break; } default: /* * We shouldn't get here, but if we do, do nothing. * We've already consumed the arguments above. */ break; } } sense->extra_len = desc - &sense->sense_desc[0]; *sense_len = offsetof(struct scsi_sense_data_desc, extra_len) + 1 + sense->extra_len; } /* * Fill in SCSI fixed sense data with the specified parameters. */ static void scsi_set_sense_data_fixed_va(struct scsi_sense_data *sense_data, u_int *sense_len, scsi_sense_data_type sense_format, int current_error, int sense_key, int asc, int ascq, va_list ap) { struct scsi_sense_data_fixed *sense; scsi_sense_elem_type elem_type; uint8_t *data; int len; memset(sense_data, 0, sizeof(*sense_data)); sense = (struct scsi_sense_data_fixed *)sense_data; if (current_error != 0) sense->error_code = SSD_CURRENT_ERROR; else sense->error_code = SSD_DEFERRED_ERROR; sense->flags = sense_key & SSD_KEY; sense->extra_len = 0; if (*sense_len >= 13) { sense->add_sense_code = asc; sense->extra_len = MAX(sense->extra_len, 5); } else sense->flags |= SSD_SDAT_OVFL; if (*sense_len >= 14) { sense->add_sense_code_qual = ascq; sense->extra_len = MAX(sense->extra_len, 6); } else sense->flags |= SSD_SDAT_OVFL; while ((elem_type = va_arg(ap, scsi_sense_elem_type)) != SSD_ELEM_NONE) { if (elem_type >= SSD_ELEM_MAX) { printf("%s: invalid sense type %d\n", __func__, elem_type); break; } len = va_arg(ap, int); data = va_arg(ap, uint8_t *); switch (elem_type) { case SSD_ELEM_SKIP: break; case SSD_ELEM_SKS: if (len > sizeof(sense->sense_key_spec)) break; if (*sense_len < 18) { sense->flags |= SSD_SDAT_OVFL; break; } bcopy(data, &sense->sense_key_spec[0], len); sense->extra_len = MAX(sense->extra_len, 10); break; case SSD_ELEM_COMMAND: if (*sense_len < 12) { sense->flags |= SSD_SDAT_OVFL; break; } if (len > sizeof(sense->cmd_spec_info)) { data += len - sizeof(sense->cmd_spec_info); len -= len - sizeof(sense->cmd_spec_info); } bcopy(data, &sense->cmd_spec_info[ sizeof(sense->cmd_spec_info) - len], len); sense->extra_len = MAX(sense->extra_len, 4); break; case SSD_ELEM_INFO: /* Set VALID bit only if no overflow. */ sense->error_code |= SSD_ERRCODE_VALID; while (len > sizeof(sense->info)) { if (data[0] != 0) sense->error_code &= ~SSD_ERRCODE_VALID; data ++; len --; } bcopy(data, &sense->info[sizeof(sense->info) - len], len); break; case SSD_ELEM_FRU: if (*sense_len < 15) { sense->flags |= SSD_SDAT_OVFL; break; } sense->fru = *data; sense->extra_len = MAX(sense->extra_len, 7); break; case SSD_ELEM_STREAM: sense->flags |= *data & (SSD_ILI | SSD_EOM | SSD_FILEMARK); break; default: /* * We can't handle that in fixed format. Skip it. */ break; } } *sense_len = offsetof(struct scsi_sense_data_fixed, extra_len) + 1 + sense->extra_len; } /* * Fill in SCSI sense data with the specified parameters. This routine can * fill in either fixed or descriptor type sense data. */ void scsi_set_sense_data_va(struct scsi_sense_data *sense_data, u_int *sense_len, scsi_sense_data_type sense_format, int current_error, int sense_key, int asc, int ascq, va_list ap) { if (*sense_len > SSD_FULL_SIZE) *sense_len = SSD_FULL_SIZE; if (sense_format == SSD_TYPE_DESC) scsi_set_sense_data_desc_va(sense_data, sense_len, sense_format, current_error, sense_key, asc, ascq, ap); else scsi_set_sense_data_fixed_va(sense_data, sense_len, sense_format, current_error, sense_key, asc, ascq, ap); } void scsi_set_sense_data(struct scsi_sense_data *sense_data, scsi_sense_data_type sense_format, int current_error, int sense_key, int asc, int ascq, ...) { va_list ap; u_int sense_len = SSD_FULL_SIZE; va_start(ap, ascq); scsi_set_sense_data_va(sense_data, &sense_len, sense_format, current_error, sense_key, asc, ascq, ap); va_end(ap); } void scsi_set_sense_data_len(struct scsi_sense_data *sense_data, u_int *sense_len, scsi_sense_data_type sense_format, int current_error, int sense_key, int asc, int ascq, ...) { va_list ap; va_start(ap, ascq); scsi_set_sense_data_va(sense_data, sense_len, sense_format, current_error, sense_key, asc, ascq, ap); va_end(ap); } /* * Get sense information for three similar sense data types. */ int scsi_get_sense_info(struct scsi_sense_data *sense_data, u_int sense_len, uint8_t info_type, uint64_t *info, int64_t *signed_info) { scsi_sense_data_type sense_type; if (sense_len == 0) goto bailout; sense_type = scsi_sense_type(sense_data); switch (sense_type) { case SSD_TYPE_DESC: { struct scsi_sense_data_desc *sense; uint8_t *desc; sense = (struct scsi_sense_data_desc *)sense_data; desc = scsi_find_desc(sense, sense_len, info_type); if (desc == NULL) goto bailout; switch (info_type) { case SSD_DESC_INFO: { struct scsi_sense_info *info_desc; info_desc = (struct scsi_sense_info *)desc; *info = scsi_8btou64(info_desc->info); if (signed_info != NULL) *signed_info = *info; break; } case SSD_DESC_COMMAND: { struct scsi_sense_command *cmd_desc; cmd_desc = (struct scsi_sense_command *)desc; *info = scsi_8btou64(cmd_desc->command_info); if (signed_info != NULL) *signed_info = *info; break; } case SSD_DESC_FRU: { struct scsi_sense_fru *fru_desc; fru_desc = (struct scsi_sense_fru *)desc; *info = fru_desc->fru; if (signed_info != NULL) *signed_info = (int8_t)fru_desc->fru; break; } default: goto bailout; break; } break; } case SSD_TYPE_FIXED: { struct scsi_sense_data_fixed *sense; sense = (struct scsi_sense_data_fixed *)sense_data; switch (info_type) { case SSD_DESC_INFO: { uint32_t info_val; if ((sense->error_code & SSD_ERRCODE_VALID) == 0) goto bailout; if (SSD_FIXED_IS_PRESENT(sense, sense_len, info) == 0) goto bailout; info_val = scsi_4btoul(sense->info); *info = info_val; if (signed_info != NULL) *signed_info = (int32_t)info_val; break; } case SSD_DESC_COMMAND: { uint32_t cmd_val; if ((SSD_FIXED_IS_PRESENT(sense, sense_len, cmd_spec_info) == 0) || (SSD_FIXED_IS_FILLED(sense, cmd_spec_info) == 0)) goto bailout; cmd_val = scsi_4btoul(sense->cmd_spec_info); if (cmd_val == 0) goto bailout; *info = cmd_val; if (signed_info != NULL) *signed_info = (int32_t)cmd_val; break; } case SSD_DESC_FRU: if ((SSD_FIXED_IS_PRESENT(sense, sense_len, fru) == 0) || (SSD_FIXED_IS_FILLED(sense, fru) == 0)) goto bailout; if (sense->fru == 0) goto bailout; *info = sense->fru; if (signed_info != NULL) *signed_info = (int8_t)sense->fru; break; default: goto bailout; break; } break; } default: goto bailout; break; } return (0); bailout: return (1); } int scsi_get_sks(struct scsi_sense_data *sense_data, u_int sense_len, uint8_t *sks) { scsi_sense_data_type sense_type; if (sense_len == 0) goto bailout; sense_type = scsi_sense_type(sense_data); switch (sense_type) { case SSD_TYPE_DESC: { struct scsi_sense_data_desc *sense; struct scsi_sense_sks *desc; sense = (struct scsi_sense_data_desc *)sense_data; desc = (struct scsi_sense_sks *)scsi_find_desc(sense, sense_len, SSD_DESC_SKS); if (desc == NULL) goto bailout; /* * No need to check the SKS valid bit for descriptor sense. * If the descriptor is present, it is valid. */ bcopy(desc->sense_key_spec, sks, sizeof(desc->sense_key_spec)); break; } case SSD_TYPE_FIXED: { struct scsi_sense_data_fixed *sense; sense = (struct scsi_sense_data_fixed *)sense_data; if ((SSD_FIXED_IS_PRESENT(sense, sense_len, sense_key_spec)== 0) || (SSD_FIXED_IS_FILLED(sense, sense_key_spec) == 0)) goto bailout; if ((sense->sense_key_spec[0] & SSD_SCS_VALID) == 0) goto bailout; bcopy(sense->sense_key_spec, sks,sizeof(sense->sense_key_spec)); break; } default: goto bailout; break; } return (0); bailout: return (1); } /* * Provide a common interface for fixed and descriptor sense to detect * whether we have block-specific sense information. It is clear by the * presence of the block descriptor in descriptor mode, but we have to * infer from the inquiry data and ILI bit in fixed mode. */ int scsi_get_block_info(struct scsi_sense_data *sense_data, u_int sense_len, struct scsi_inquiry_data *inq_data, uint8_t *block_bits) { scsi_sense_data_type sense_type; if (inq_data != NULL) { switch (SID_TYPE(inq_data)) { case T_DIRECT: case T_RBC: case T_ZBC_HM: break; default: goto bailout; break; } } sense_type = scsi_sense_type(sense_data); switch (sense_type) { case SSD_TYPE_DESC: { struct scsi_sense_data_desc *sense; struct scsi_sense_block *block; sense = (struct scsi_sense_data_desc *)sense_data; block = (struct scsi_sense_block *)scsi_find_desc(sense, sense_len, SSD_DESC_BLOCK); if (block == NULL) goto bailout; *block_bits = block->byte3; break; } case SSD_TYPE_FIXED: { struct scsi_sense_data_fixed *sense; sense = (struct scsi_sense_data_fixed *)sense_data; if (SSD_FIXED_IS_PRESENT(sense, sense_len, flags) == 0) goto bailout; if ((sense->flags & SSD_ILI) == 0) goto bailout; *block_bits = sense->flags & SSD_ILI; break; } default: goto bailout; break; } return (0); bailout: return (1); } int scsi_get_stream_info(struct scsi_sense_data *sense_data, u_int sense_len, struct scsi_inquiry_data *inq_data, uint8_t *stream_bits) { scsi_sense_data_type sense_type; if (inq_data != NULL) { switch (SID_TYPE(inq_data)) { case T_SEQUENTIAL: break; default: goto bailout; break; } } sense_type = scsi_sense_type(sense_data); switch (sense_type) { case SSD_TYPE_DESC: { struct scsi_sense_data_desc *sense; struct scsi_sense_stream *stream; sense = (struct scsi_sense_data_desc *)sense_data; stream = (struct scsi_sense_stream *)scsi_find_desc(sense, sense_len, SSD_DESC_STREAM); if (stream == NULL) goto bailout; *stream_bits = stream->byte3; break; } case SSD_TYPE_FIXED: { struct scsi_sense_data_fixed *sense; sense = (struct scsi_sense_data_fixed *)sense_data; if (SSD_FIXED_IS_PRESENT(sense, sense_len, flags) == 0) goto bailout; if ((sense->flags & (SSD_ILI|SSD_EOM|SSD_FILEMARK)) == 0) goto bailout; *stream_bits = sense->flags & (SSD_ILI|SSD_EOM|SSD_FILEMARK); break; } default: goto bailout; break; } return (0); bailout: return (1); } void scsi_info_sbuf(struct sbuf *sb, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, uint64_t info) { sbuf_printf(sb, "Info: %#jx", info); } void scsi_command_sbuf(struct sbuf *sb, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, uint64_t csi) { sbuf_printf(sb, "Command Specific Info: %#jx", csi); } void scsi_progress_sbuf(struct sbuf *sb, uint16_t progress) { sbuf_printf(sb, "Progress: %d%% (%d/%d) complete", (progress * 100) / SSD_SKS_PROGRESS_DENOM, progress, SSD_SKS_PROGRESS_DENOM); } /* * Returns 1 for failure (i.e. SKS isn't valid) and 0 for success. */ int scsi_sks_sbuf(struct sbuf *sb, int sense_key, uint8_t *sks) { if ((sks[0] & SSD_SKS_VALID) == 0) return (1); switch (sense_key) { case SSD_KEY_ILLEGAL_REQUEST: { struct scsi_sense_sks_field *field; int bad_command; char tmpstr[40]; /*Field Pointer*/ field = (struct scsi_sense_sks_field *)sks; if (field->byte0 & SSD_SKS_FIELD_CMD) bad_command = 1; else bad_command = 0; tmpstr[0] = '\0'; /* Bit pointer is valid */ if (field->byte0 & SSD_SKS_BPV) snprintf(tmpstr, sizeof(tmpstr), "bit %d ", field->byte0 & SSD_SKS_BIT_VALUE); sbuf_printf(sb, "%s byte %d %sis invalid", bad_command ? "Command" : "Data", scsi_2btoul(field->field), tmpstr); break; } case SSD_KEY_UNIT_ATTENTION: { struct scsi_sense_sks_overflow *overflow; overflow = (struct scsi_sense_sks_overflow *)sks; /*UA Condition Queue Overflow*/ sbuf_printf(sb, "Unit Attention Condition Queue %s", (overflow->byte0 & SSD_SKS_OVERFLOW_SET) ? "Overflowed" : "Did Not Overflow??"); break; } case SSD_KEY_RECOVERED_ERROR: case SSD_KEY_HARDWARE_ERROR: case SSD_KEY_MEDIUM_ERROR: { struct scsi_sense_sks_retry *retry; /*Actual Retry Count*/ retry = (struct scsi_sense_sks_retry *)sks; sbuf_printf(sb, "Actual Retry Count: %d", scsi_2btoul(retry->actual_retry_count)); break; } case SSD_KEY_NO_SENSE: case SSD_KEY_NOT_READY: { struct scsi_sense_sks_progress *progress; int progress_val; /*Progress Indication*/ progress = (struct scsi_sense_sks_progress *)sks; progress_val = scsi_2btoul(progress->progress); scsi_progress_sbuf(sb, progress_val); break; } case SSD_KEY_COPY_ABORTED: { struct scsi_sense_sks_segment *segment; char tmpstr[40]; /*Segment Pointer*/ segment = (struct scsi_sense_sks_segment *)sks; tmpstr[0] = '\0'; if (segment->byte0 & SSD_SKS_SEGMENT_BPV) snprintf(tmpstr, sizeof(tmpstr), "bit %d ", segment->byte0 & SSD_SKS_SEGMENT_BITPTR); sbuf_printf(sb, "%s byte %d %sis invalid", (segment->byte0 & SSD_SKS_SEGMENT_SD) ? "Segment" : "Data", scsi_2btoul(segment->field), tmpstr); break; } default: sbuf_printf(sb, "Sense Key Specific: %#x,%#x", sks[0], scsi_2btoul(&sks[1])); break; } return (0); } void scsi_fru_sbuf(struct sbuf *sb, uint64_t fru) { sbuf_printf(sb, "Field Replaceable Unit: %d", (int)fru); } void scsi_stream_sbuf(struct sbuf *sb, uint8_t stream_bits, uint64_t info) { int need_comma; need_comma = 0; /* * XXX KDM this needs more descriptive decoding. */ if (stream_bits & SSD_DESC_STREAM_FM) { sbuf_printf(sb, "Filemark"); need_comma = 1; } if (stream_bits & SSD_DESC_STREAM_EOM) { sbuf_printf(sb, "%sEOM", (need_comma) ? "," : ""); need_comma = 1; } if (stream_bits & SSD_DESC_STREAM_ILI) sbuf_printf(sb, "%sILI", (need_comma) ? "," : ""); sbuf_printf(sb, ": Info: %#jx", (uintmax_t) info); } void scsi_block_sbuf(struct sbuf *sb, uint8_t block_bits, uint64_t info) { if (block_bits & SSD_DESC_BLOCK_ILI) sbuf_printf(sb, "ILI: residue %#jx", (uintmax_t) info); } void scsi_sense_info_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_info *info; info = (struct scsi_sense_info *)header; scsi_info_sbuf(sb, cdb, cdb_len, inq_data, scsi_8btou64(info->info)); } void scsi_sense_command_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_command *command; command = (struct scsi_sense_command *)header; scsi_command_sbuf(sb, cdb, cdb_len, inq_data, scsi_8btou64(command->command_info)); } void scsi_sense_sks_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_sks *sks; int error_code, sense_key, asc, ascq; sks = (struct scsi_sense_sks *)header; scsi_extract_sense_len(sense, sense_len, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); scsi_sks_sbuf(sb, sense_key, sks->sense_key_spec); } void scsi_sense_fru_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_fru *fru; fru = (struct scsi_sense_fru *)header; scsi_fru_sbuf(sb, (uint64_t)fru->fru); } void scsi_sense_stream_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_stream *stream; uint64_t info; stream = (struct scsi_sense_stream *)header; info = 0; scsi_get_sense_info(sense, sense_len, SSD_DESC_INFO, &info, NULL); scsi_stream_sbuf(sb, stream->byte3, info); } void scsi_sense_block_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_block *block; uint64_t info; block = (struct scsi_sense_block *)header; info = 0; scsi_get_sense_info(sense, sense_len, SSD_DESC_INFO, &info, NULL); scsi_block_sbuf(sb, block->byte3, info); } void scsi_sense_progress_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_progress *progress; const char *sense_key_desc; const char *asc_desc; int progress_val; progress = (struct scsi_sense_progress *)header; /* * Get descriptions for the sense key, ASC, and ASCQ in the * progress descriptor. These could be different than the values * in the overall sense data. */ scsi_sense_desc(progress->sense_key, progress->add_sense_code, progress->add_sense_code_qual, inq_data, &sense_key_desc, &asc_desc); progress_val = scsi_2btoul(progress->progress); /* * The progress indicator is for the operation described by the * sense key, ASC, and ASCQ in the descriptor. */ sbuf_cat(sb, sense_key_desc); sbuf_printf(sb, " asc:%x,%x (%s): ", progress->add_sense_code, progress->add_sense_code_qual, asc_desc); scsi_progress_sbuf(sb, progress_val); } void scsi_sense_ata_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_ata_ret_desc *res; res = (struct scsi_sense_ata_ret_desc *)header; sbuf_printf(sb, "ATA status: %02x (%s%s%s%s%s%s%s%s), ", res->status, (res->status & 0x80) ? "BSY " : "", (res->status & 0x40) ? "DRDY " : "", (res->status & 0x20) ? "DF " : "", (res->status & 0x10) ? "SERV " : "", (res->status & 0x08) ? "DRQ " : "", (res->status & 0x04) ? "CORR " : "", (res->status & 0x02) ? "IDX " : "", (res->status & 0x01) ? "ERR" : ""); if (res->status & 1) { sbuf_printf(sb, "error: %02x (%s%s%s%s%s%s%s%s), ", res->error, (res->error & 0x80) ? "ICRC " : "", (res->error & 0x40) ? "UNC " : "", (res->error & 0x20) ? "MC " : "", (res->error & 0x10) ? "IDNF " : "", (res->error & 0x08) ? "MCR " : "", (res->error & 0x04) ? "ABRT " : "", (res->error & 0x02) ? "NM " : "", (res->error & 0x01) ? "ILI" : ""); } if (res->flags & SSD_DESC_ATA_FLAG_EXTEND) { sbuf_printf(sb, "count: %02x%02x, ", res->count_15_8, res->count_7_0); sbuf_printf(sb, "LBA: %02x%02x%02x%02x%02x%02x, ", res->lba_47_40, res->lba_39_32, res->lba_31_24, res->lba_23_16, res->lba_15_8, res->lba_7_0); } else { sbuf_printf(sb, "count: %02x, ", res->count_7_0); sbuf_printf(sb, "LBA: %02x%02x%02x, ", res->lba_23_16, res->lba_15_8, res->lba_7_0); } sbuf_printf(sb, "device: %02x, ", res->device); } void scsi_sense_forwarded_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { struct scsi_sense_forwarded *forwarded; const char *sense_key_desc; const char *asc_desc; int error_code, sense_key, asc, ascq; forwarded = (struct scsi_sense_forwarded *)header; scsi_extract_sense_len((struct scsi_sense_data *)forwarded->sense_data, forwarded->length - 2, &error_code, &sense_key, &asc, &ascq, 1); scsi_sense_desc(sense_key, asc, ascq, NULL, &sense_key_desc, &asc_desc); sbuf_printf(sb, "Forwarded sense: %s asc:%x,%x (%s): ", sense_key_desc, asc, ascq, asc_desc); } /* * Generic sense descriptor printing routine. This is used when we have * not yet implemented a specific printing routine for this descriptor. */ void scsi_sense_generic_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { int i; uint8_t *buf_ptr; sbuf_printf(sb, "Descriptor %#x:", header->desc_type); buf_ptr = (uint8_t *)&header[1]; for (i = 0; i < header->length; i++, buf_ptr++) sbuf_printf(sb, " %02x", *buf_ptr); } /* * Keep this list in numeric order. This speeds the array traversal. */ struct scsi_sense_desc_printer { uint8_t desc_type; /* * The function arguments here are the superset of what is needed * to print out various different descriptors. Command and * information descriptors need inquiry data and command type. * Sense key specific descriptors need the sense key. * * The sense, cdb, and inquiry data arguments may be NULL, but the * information printed may not be fully decoded as a result. */ void (*print_func)(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header); } scsi_sense_printers[] = { {SSD_DESC_INFO, scsi_sense_info_sbuf}, {SSD_DESC_COMMAND, scsi_sense_command_sbuf}, {SSD_DESC_SKS, scsi_sense_sks_sbuf}, {SSD_DESC_FRU, scsi_sense_fru_sbuf}, {SSD_DESC_STREAM, scsi_sense_stream_sbuf}, {SSD_DESC_BLOCK, scsi_sense_block_sbuf}, {SSD_DESC_ATA, scsi_sense_ata_sbuf}, {SSD_DESC_PROGRESS, scsi_sense_progress_sbuf}, {SSD_DESC_FORWARDED, scsi_sense_forwarded_sbuf} }; void scsi_sense_desc_sbuf(struct sbuf *sb, struct scsi_sense_data *sense, u_int sense_len, uint8_t *cdb, int cdb_len, struct scsi_inquiry_data *inq_data, struct scsi_sense_desc_header *header) { u_int i; for (i = 0; i < nitems(scsi_sense_printers); i++) { struct scsi_sense_desc_printer *printer; printer = &scsi_sense_printers[i]; /* * The list is sorted, so quit if we've passed our * descriptor number. */ if (printer->desc_type > header->desc_type) break; if (printer->desc_type != header->desc_type) continue; printer->print_func(sb, sense, sense_len, cdb, cdb_len, inq_data, header); return; } /* * No specific printing routine, so use the generic routine. */ scsi_sense_generic_sbuf(sb, sense, sense_len, cdb, cdb_len, inq_data, header); } scsi_sense_data_type scsi_sense_type(struct scsi_sense_data *sense_data) { switch (sense_data->error_code & SSD_ERRCODE) { case SSD_DESC_CURRENT_ERROR: case SSD_DESC_DEFERRED_ERROR: return (SSD_TYPE_DESC); break; case SSD_CURRENT_ERROR: case SSD_DEFERRED_ERROR: return (SSD_TYPE_FIXED); break; default: break; } return (SSD_TYPE_NONE); } struct scsi_print_sense_info { struct sbuf *sb; char *path_str; uint8_t *cdb; int cdb_len; struct scsi_inquiry_data *inq_data; }; static int scsi_print_desc_func(struct scsi_sense_data_desc *sense, u_int sense_len, struct scsi_sense_desc_header *header, void *arg) { struct scsi_print_sense_info *print_info; print_info = (struct scsi_print_sense_info *)arg; switch (header->desc_type) { case SSD_DESC_INFO: case SSD_DESC_FRU: case SSD_DESC_COMMAND: case SSD_DESC_SKS: case SSD_DESC_BLOCK: case SSD_DESC_STREAM: /* * We have already printed these descriptors, if they are * present. */ break; default: { sbuf_printf(print_info->sb, "%s", print_info->path_str); scsi_sense_desc_sbuf(print_info->sb, (struct scsi_sense_data *)sense, sense_len, print_info->cdb, print_info->cdb_len, print_info->inq_data, header); sbuf_printf(print_info->sb, "\n"); break; } } /* * Tell the iterator that we want to see more descriptors if they * are present. */ return (0); } void scsi_sense_only_sbuf(struct scsi_sense_data *sense, u_int sense_len, struct sbuf *sb, char *path_str, struct scsi_inquiry_data *inq_data, uint8_t *cdb, int cdb_len) { int error_code, sense_key, asc, ascq; sbuf_cat(sb, path_str); scsi_extract_sense_len(sense, sense_len, &error_code, &sense_key, &asc, &ascq, /*show_errors*/ 1); sbuf_printf(sb, "SCSI sense: "); switch (error_code) { case SSD_DEFERRED_ERROR: case SSD_DESC_DEFERRED_ERROR: sbuf_printf(sb, "Deferred error: "); /* FALLTHROUGH */ case SSD_CURRENT_ERROR: case SSD_DESC_CURRENT_ERROR: { struct scsi_sense_data_desc *desc_sense; struct scsi_print_sense_info print_info; const char *sense_key_desc; const char *asc_desc; uint8_t sks[3]; uint64_t val; int info_valid; /* * Get descriptions for the sense key, ASC, and ASCQ. If * these aren't present in the sense data (i.e. the sense * data isn't long enough), the -1 values that * scsi_extract_sense_len() returns will yield default * or error descriptions. */ scsi_sense_desc(sense_key, asc, ascq, inq_data, &sense_key_desc, &asc_desc); /* * We first print the sense key and ASC/ASCQ. */ sbuf_cat(sb, sense_key_desc); sbuf_printf(sb, " asc:%x,%x (%s)\n", asc, ascq, asc_desc); /* * Get the info field if it is valid. */ if (scsi_get_sense_info(sense, sense_len, SSD_DESC_INFO, &val, NULL) == 0) info_valid = 1; else info_valid = 0; if (info_valid != 0) { uint8_t bits; /* * Determine whether we have any block or stream * device-specific information. */ if (scsi_get_block_info(sense, sense_len, inq_data, &bits) == 0) { sbuf_cat(sb, path_str); scsi_block_sbuf(sb, bits, val); sbuf_printf(sb, "\n"); } else if (scsi_get_stream_info(sense, sense_len, inq_data, &bits) == 0) { sbuf_cat(sb, path_str); scsi_stream_sbuf(sb, bits, val); sbuf_printf(sb, "\n"); } else if (val != 0) { /* * The information field can be valid but 0. * If the block or stream bits aren't set, * and this is 0, it isn't terribly useful * to print it out. */ sbuf_cat(sb, path_str); scsi_info_sbuf(sb, cdb, cdb_len, inq_data, val); sbuf_printf(sb, "\n"); } } /* * Print the FRU. */ if (scsi_get_sense_info(sense, sense_len, SSD_DESC_FRU, &val, NULL) == 0) { sbuf_cat(sb, path_str); scsi_fru_sbuf(sb, val); sbuf_printf(sb, "\n"); } /* * Print any command-specific information. */ if (scsi_get_sense_info(sense, sense_len, SSD_DESC_COMMAND, &val, NULL) == 0) { sbuf_cat(sb, path_str); scsi_command_sbuf(sb, cdb, cdb_len, inq_data, val); sbuf_printf(sb, "\n"); } /* * Print out any sense-key-specific information. */ if (scsi_get_sks(sense, sense_len, sks) == 0) { sbuf_cat(sb, path_str); scsi_sks_sbuf(sb, sense_key, sks); sbuf_printf(sb, "\n"); } /* * If this is fixed sense, we're done. If we have * descriptor sense, we might have more information * available. */ if (scsi_sense_type(sense) != SSD_TYPE_DESC) break; desc_sense = (struct scsi_sense_data_desc *)sense; print_info.sb = sb; print_info.path_str = path_str; print_info.cdb = cdb; print_info.cdb_len = cdb_len; print_info.inq_data = inq_data; /* * Print any sense descriptors that we have not already printed. */ scsi_desc_iterate(desc_sense, sense_len, scsi_print_desc_func, &print_info); break; } case -1: /* * scsi_extract_sense_len() sets values to -1 if the * show_errors flag is set and they aren't present in the * sense data. This means that sense_len is 0. */ sbuf_printf(sb, "No sense data present\n"); break; default: { sbuf_printf(sb, "Error code 0x%x", error_code); if (sense->error_code & SSD_ERRCODE_VALID) { struct scsi_sense_data_fixed *fixed_sense; fixed_sense = (struct scsi_sense_data_fixed *)sense; if (SSD_FIXED_IS_PRESENT(fixed_sense, sense_len, info)){ uint32_t info; info = scsi_4btoul(fixed_sense->info); sbuf_printf(sb, " at block no. %d (decimal)", info); } } sbuf_printf(sb, "\n"); break; } } } /* * scsi_sense_sbuf() returns 0 for success and -1 for failure. */ #ifdef _KERNEL int scsi_sense_sbuf(struct ccb_scsiio *csio, struct sbuf *sb, scsi_sense_string_flags flags) #else /* !_KERNEL */ int scsi_sense_sbuf(struct cam_device *device, struct ccb_scsiio *csio, struct sbuf *sb, scsi_sense_string_flags flags) #endif /* _KERNEL/!_KERNEL */ { struct scsi_sense_data *sense; struct scsi_inquiry_data *inq_data; #ifdef _KERNEL struct ccb_getdev *cgd; #endif /* _KERNEL */ char path_str[64]; #ifndef _KERNEL if (device == NULL) return(-1); #endif /* !_KERNEL */ if ((csio == NULL) || (sb == NULL)) return(-1); /* * If the CDB is a physical address, we can't deal with it.. */ if ((csio->ccb_h.flags & CAM_CDB_PHYS) != 0) flags &= ~SSS_FLAG_PRINT_COMMAND; #ifdef _KERNEL xpt_path_string(csio->ccb_h.path, path_str, sizeof(path_str)); #else /* !_KERNEL */ cam_path_string(device, path_str, sizeof(path_str)); #endif /* _KERNEL/!_KERNEL */ #ifdef _KERNEL if ((cgd = (struct ccb_getdev*)xpt_alloc_ccb_nowait()) == NULL) return(-1); /* * Get the device information. */ xpt_setup_ccb(&cgd->ccb_h, csio->ccb_h.path, CAM_PRIORITY_NORMAL); cgd->ccb_h.func_code = XPT_GDEV_TYPE; xpt_action((union ccb *)cgd); /* * If the device is unconfigured, just pretend that it is a hard * drive. scsi_op_desc() needs this. */ if (cgd->ccb_h.status == CAM_DEV_NOT_THERE) cgd->inq_data.device = T_DIRECT; inq_data = &cgd->inq_data; #else /* !_KERNEL */ inq_data = &device->inq_data; #endif /* _KERNEL/!_KERNEL */ sense = NULL; if (flags & SSS_FLAG_PRINT_COMMAND) { sbuf_cat(sb, path_str); #ifdef _KERNEL scsi_command_string(csio, sb); #else /* !_KERNEL */ scsi_command_string(device, csio, sb); #endif /* _KERNEL/!_KERNEL */ sbuf_printf(sb, "\n"); } /* * If the sense data is a physical pointer, forget it. */ if (csio->ccb_h.flags & CAM_SENSE_PTR) { if (csio->ccb_h.flags & CAM_SENSE_PHYS) { #ifdef _KERNEL xpt_free_ccb((union ccb*)cgd); #endif /* _KERNEL/!_KERNEL */ return(-1); } else { /* * bcopy the pointer to avoid unaligned access * errors on finicky architectures. We don't * ensure that the sense data is pointer aligned. */ bcopy((struct scsi_sense_data **)&csio->sense_data, &sense, sizeof(struct scsi_sense_data *)); } } else { /* * If the physical sense flag is set, but the sense pointer * is not also set, we assume that the user is an idiot and * return. (Well, okay, it could be that somehow, the * entire csio is physical, but we would have probably core * dumped on one of the bogus pointer deferences above * already.) */ if (csio->ccb_h.flags & CAM_SENSE_PHYS) { #ifdef _KERNEL xpt_free_ccb((union ccb*)cgd); #endif /* _KERNEL/!_KERNEL */ return(-1); } else sense = &csio->sense_data; } scsi_sense_only_sbuf(sense, csio->sense_len - csio->sense_resid, sb, path_str, inq_data, scsiio_cdb_ptr(csio), csio->cdb_len); #ifdef _KERNEL xpt_free_ccb((union ccb*)cgd); #endif /* _KERNEL/!_KERNEL */ return(0); } #ifdef _KERNEL char * scsi_sense_string(struct ccb_scsiio *csio, char *str, int str_len) #else /* !_KERNEL */ char * scsi_sense_string(struct cam_device *device, struct ccb_scsiio *csio, char *str, int str_len) #endif /* _KERNEL/!_KERNEL */ { struct sbuf sb; sbuf_new(&sb, str, str_len, 0); #ifdef _KERNEL scsi_sense_sbuf(csio, &sb, SSS_FLAG_PRINT_COMMAND); #else /* !_KERNEL */ scsi_sense_sbuf(device, csio, &sb, SSS_FLAG_PRINT_COMMAND); #endif /* _KERNEL/!_KERNEL */ sbuf_finish(&sb); return(sbuf_data(&sb)); } #ifdef _KERNEL void scsi_sense_print(struct ccb_scsiio *csio) { struct sbuf sb; char str[512]; sbuf_new(&sb, str, sizeof(str), 0); scsi_sense_sbuf(csio, &sb, SSS_FLAG_PRINT_COMMAND); sbuf_finish(&sb); sbuf_putbuf(&sb); } #else /* !_KERNEL */ void scsi_sense_print(struct cam_device *device, struct ccb_scsiio *csio, FILE *ofile) { struct sbuf sb; char str[512]; if ((device == NULL) || (csio == NULL) || (ofile == NULL)) return; sbuf_new(&sb, str, sizeof(str), 0); scsi_sense_sbuf(device, csio, &sb, SSS_FLAG_PRINT_COMMAND); sbuf_finish(&sb); fprintf(ofile, "%s", sbuf_data(&sb)); } #endif /* _KERNEL/!_KERNEL */ /* * Extract basic sense information. This is backward-compatible with the * previous implementation. For new implementations, * scsi_extract_sense_len() is recommended. */ void scsi_extract_sense(struct scsi_sense_data *sense_data, int *error_code, int *sense_key, int *asc, int *ascq) { scsi_extract_sense_len(sense_data, sizeof(*sense_data), error_code, sense_key, asc, ascq, /*show_errors*/ 0); } /* * Extract basic sense information from SCSI I/O CCB structure. */ int scsi_extract_sense_ccb(union ccb *ccb, int *error_code, int *sense_key, int *asc, int *ascq) { struct scsi_sense_data *sense_data; /* Make sure there are some sense data we can access. */ if (ccb->ccb_h.func_code != XPT_SCSI_IO || (ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_SCSI_STATUS_ERROR || (ccb->csio.scsi_status != SCSI_STATUS_CHECK_COND) || (ccb->ccb_h.status & CAM_AUTOSNS_VALID) == 0 || (ccb->ccb_h.flags & CAM_SENSE_PHYS)) return (0); if (ccb->ccb_h.flags & CAM_SENSE_PTR) bcopy((struct scsi_sense_data **)&ccb->csio.sense_data, &sense_data, sizeof(struct scsi_sense_data *)); else sense_data = &ccb->csio.sense_data; scsi_extract_sense_len(sense_data, ccb->csio.sense_len - ccb->csio.sense_resid, error_code, sense_key, asc, ascq, 1); if (*error_code == -1) return (0); return (1); } /* * Extract basic sense information. If show_errors is set, sense values * will be set to -1 if they are not present. */ void scsi_extract_sense_len(struct scsi_sense_data *sense_data, u_int sense_len, int *error_code, int *sense_key, int *asc, int *ascq, int show_errors) { /* * If we have no length, we have no sense. */ if (sense_len == 0) { if (show_errors == 0) { *error_code = 0; *sense_key = 0; *asc = 0; *ascq = 0; } else { *error_code = -1; *sense_key = -1; *asc = -1; *ascq = -1; } return; } *error_code = sense_data->error_code & SSD_ERRCODE; switch (*error_code) { case SSD_DESC_CURRENT_ERROR: case SSD_DESC_DEFERRED_ERROR: { struct scsi_sense_data_desc *sense; sense = (struct scsi_sense_data_desc *)sense_data; if (SSD_DESC_IS_PRESENT(sense, sense_len, sense_key)) *sense_key = sense->sense_key & SSD_KEY; else *sense_key = (show_errors) ? -1 : 0; if (SSD_DESC_IS_PRESENT(sense, sense_len, add_sense_code)) *asc = sense->add_sense_code; else *asc = (show_errors) ? -1 : 0; if (SSD_DESC_IS_PRESENT(sense, sense_len, add_sense_code_qual)) *ascq = sense->add_sense_code_qual; else *ascq = (show_errors) ? -1 : 0; break; } case SSD_CURRENT_ERROR: case SSD_DEFERRED_ERROR: default: { struct scsi_sense_data_fixed *sense; sense = (struct scsi_sense_data_fixed *)sense_data; if (SSD_FIXED_IS_PRESENT(sense, sense_len, flags)) *sense_key = sense->flags & SSD_KEY; else *sense_key = (show_errors) ? -1 : 0; if ((SSD_FIXED_IS_PRESENT(sense, sense_len, add_sense_code)) && (SSD_FIXED_IS_FILLED(sense, add_sense_code))) *asc = sense->add_sense_code; else *asc = (show_errors) ? -1 : 0; if ((SSD_FIXED_IS_PRESENT(sense, sense_len,add_sense_code_qual)) && (SSD_FIXED_IS_FILLED(sense, add_sense_code_qual))) *ascq = sense->add_sense_code_qual; else *ascq = (show_errors) ? -1 : 0; break; } } } int scsi_get_sense_key(struct scsi_sense_data *sense_data, u_int sense_len, int show_errors) { int error_code, sense_key, asc, ascq; scsi_extract_sense_len(sense_data, sense_len, &error_code, &sense_key, &asc, &ascq, show_errors); return (sense_key); } int scsi_get_asc(struct scsi_sense_data *sense_data, u_int sense_len, int show_errors) { int error_code, sense_key, asc, ascq; scsi_extract_sense_len(sense_data, sense_len, &error_code, &sense_key, &asc, &ascq, show_errors); return (asc); } int scsi_get_ascq(struct scsi_sense_data *sense_data, u_int sense_len, int show_errors) { int error_code, sense_key, asc, ascq; scsi_extract_sense_len(sense_data, sense_len, &error_code, &sense_key, &asc, &ascq, show_errors); return (ascq); } /* * This function currently requires at least 36 bytes, or * SHORT_INQUIRY_LENGTH, worth of data to function properly. If this * function needs more or less data in the future, another length should be * defined in scsi_all.h to indicate the minimum amount of data necessary * for this routine to function properly. */ void scsi_print_inquiry_sbuf(struct sbuf *sb, struct scsi_inquiry_data *inq_data) { u_int8_t type; char *dtype, *qtype; type = SID_TYPE(inq_data); /* * Figure out basic device type and qualifier. */ if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data)) { qtype = " (vendor-unique qualifier)"; } else { switch (SID_QUAL(inq_data)) { case SID_QUAL_LU_CONNECTED: qtype = ""; break; case SID_QUAL_LU_OFFLINE: qtype = " (offline)"; break; case SID_QUAL_RSVD: qtype = " (reserved qualifier)"; break; default: case SID_QUAL_BAD_LU: qtype = " (LUN not supported)"; break; } } switch (type) { case T_DIRECT: dtype = "Direct Access"; break; case T_SEQUENTIAL: dtype = "Sequential Access"; break; case T_PRINTER: dtype = "Printer"; break; case T_PROCESSOR: dtype = "Processor"; break; case T_WORM: dtype = "WORM"; break; case T_CDROM: dtype = "CD-ROM"; break; case T_SCANNER: dtype = "Scanner"; break; case T_OPTICAL: dtype = "Optical"; break; case T_CHANGER: dtype = "Changer"; break; case T_COMM: dtype = "Communication"; break; case T_STORARRAY: dtype = "Storage Array"; break; case T_ENCLOSURE: dtype = "Enclosure Services"; break; case T_RBC: dtype = "Simplified Direct Access"; break; case T_OCRW: dtype = "Optical Card Read/Write"; break; case T_OSD: dtype = "Object-Based Storage"; break; case T_ADC: dtype = "Automation/Drive Interface"; break; case T_ZBC_HM: dtype = "Host Managed Zoned Block"; break; case T_NODEVICE: dtype = "Uninstalled"; break; default: dtype = "unknown"; break; } scsi_print_inquiry_short_sbuf(sb, inq_data); sbuf_printf(sb, "%s %s ", SID_IS_REMOVABLE(inq_data) ? "Removable" : "Fixed", dtype); if (SID_ANSI_REV(inq_data) == SCSI_REV_0) sbuf_printf(sb, "SCSI "); else if (SID_ANSI_REV(inq_data) <= SCSI_REV_SPC) { sbuf_printf(sb, "SCSI-%d ", SID_ANSI_REV(inq_data)); } else { sbuf_printf(sb, "SPC-%d SCSI ", SID_ANSI_REV(inq_data) - 2); } sbuf_printf(sb, "device%s\n", qtype); } void scsi_print_inquiry(struct scsi_inquiry_data *inq_data) { struct sbuf sb; char buffer[120]; sbuf_new(&sb, buffer, 120, SBUF_FIXEDLEN); scsi_print_inquiry_sbuf(&sb, inq_data); sbuf_finish(&sb); sbuf_putbuf(&sb); } void scsi_print_inquiry_short_sbuf(struct sbuf *sb, struct scsi_inquiry_data *inq_data) { sbuf_printf(sb, "<"); cam_strvis_sbuf(sb, inq_data->vendor, sizeof(inq_data->vendor), 0); sbuf_printf(sb, " "); cam_strvis_sbuf(sb, inq_data->product, sizeof(inq_data->product), 0); sbuf_printf(sb, " "); cam_strvis_sbuf(sb, inq_data->revision, sizeof(inq_data->revision), 0); sbuf_printf(sb, "> "); } void scsi_print_inquiry_short(struct scsi_inquiry_data *inq_data) { struct sbuf sb; char buffer[84]; sbuf_new(&sb, buffer, 84, SBUF_FIXEDLEN); scsi_print_inquiry_short_sbuf(&sb, inq_data); sbuf_finish(&sb); sbuf_putbuf(&sb); } /* * Table of syncrates that don't follow the "divisible by 4" * rule. This table will be expanded in future SCSI specs. */ static struct { u_int period_factor; u_int period; /* in 100ths of ns */ } scsi_syncrates[] = { { 0x08, 625 }, /* FAST-160 */ { 0x09, 1250 }, /* FAST-80 */ { 0x0a, 2500 }, /* FAST-40 40MHz */ { 0x0b, 3030 }, /* FAST-40 33MHz */ { 0x0c, 5000 } /* FAST-20 */ }; /* * Return the frequency in kHz corresponding to the given * sync period factor. */ u_int scsi_calc_syncsrate(u_int period_factor) { u_int i; u_int num_syncrates; /* * It's a bug if period is zero, but if it is anyway, don't * die with a divide fault- instead return something which * 'approximates' async */ if (period_factor == 0) { return (3300); } num_syncrates = nitems(scsi_syncrates); /* See if the period is in the "exception" table */ for (i = 0; i < num_syncrates; i++) { if (period_factor == scsi_syncrates[i].period_factor) { /* Period in kHz */ return (100000000 / scsi_syncrates[i].period); } } /* * Wasn't in the table, so use the standard * 4 times conversion. */ return (10000000 / (period_factor * 4 * 10)); } /* * Return the SCSI sync parameter that corresponds to * the passed in period in 10ths of ns. */ u_int scsi_calc_syncparam(u_int period) { u_int i; u_int num_syncrates; if (period == 0) return (~0); /* Async */ /* Adjust for exception table being in 100ths. */ period *= 10; num_syncrates = nitems(scsi_syncrates); /* See if the period is in the "exception" table */ for (i = 0; i < num_syncrates; i++) { if (period <= scsi_syncrates[i].period) { /* Period in 100ths of ns */ return (scsi_syncrates[i].period_factor); } } /* * Wasn't in the table, so use the standard * 1/4 period in ns conversion. */ return (period/400); } int scsi_devid_is_naa_ieee_reg(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; struct scsi_vpd_id_naa_basic *naa; descr = (struct scsi_vpd_id_descriptor *)bufp; naa = (struct scsi_vpd_id_naa_basic *)descr->identifier; if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_NAA) return 0; if (descr->length < sizeof(struct scsi_vpd_id_naa_ieee_reg)) return 0; if ((naa->naa >> SVPD_ID_NAA_NAA_SHIFT) != SVPD_ID_NAA_IEEE_REG) return 0; return 1; } int scsi_devid_is_sas_target(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; descr = (struct scsi_vpd_id_descriptor *)bufp; if (!scsi_devid_is_naa_ieee_reg(bufp)) return 0; if ((descr->id_type & SVPD_ID_PIV) == 0) /* proto field reserved */ return 0; if ((descr->proto_codeset >> SVPD_ID_PROTO_SHIFT) != SCSI_PROTO_SAS) return 0; return 1; } int scsi_devid_is_lun_eui64(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; descr = (struct scsi_vpd_id_descriptor *)bufp; if ((descr->id_type & SVPD_ID_ASSOC_MASK) != SVPD_ID_ASSOC_LUN) return 0; if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_EUI64) return 0; return 1; } int scsi_devid_is_lun_naa(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; descr = (struct scsi_vpd_id_descriptor *)bufp; if ((descr->id_type & SVPD_ID_ASSOC_MASK) != SVPD_ID_ASSOC_LUN) return 0; if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_NAA) return 0; return 1; } int scsi_devid_is_lun_t10(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; descr = (struct scsi_vpd_id_descriptor *)bufp; if ((descr->id_type & SVPD_ID_ASSOC_MASK) != SVPD_ID_ASSOC_LUN) return 0; if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_T10) return 0; return 1; } int scsi_devid_is_lun_name(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; descr = (struct scsi_vpd_id_descriptor *)bufp; if ((descr->id_type & SVPD_ID_ASSOC_MASK) != SVPD_ID_ASSOC_LUN) return 0; if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_SCSI_NAME) return 0; return 1; } int scsi_devid_is_lun_md5(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; descr = (struct scsi_vpd_id_descriptor *)bufp; if ((descr->id_type & SVPD_ID_ASSOC_MASK) != SVPD_ID_ASSOC_LUN) return 0; if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_MD5_LUN_ID) return 0; return 1; } int scsi_devid_is_lun_uuid(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; descr = (struct scsi_vpd_id_descriptor *)bufp; if ((descr->id_type & SVPD_ID_ASSOC_MASK) != SVPD_ID_ASSOC_LUN) return 0; if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_UUID) return 0; return 1; } int scsi_devid_is_port_naa(uint8_t *bufp) { struct scsi_vpd_id_descriptor *descr; descr = (struct scsi_vpd_id_descriptor *)bufp; if ((descr->id_type & SVPD_ID_ASSOC_MASK) != SVPD_ID_ASSOC_PORT) return 0; if ((descr->id_type & SVPD_ID_TYPE_MASK) != SVPD_ID_TYPE_NAA) return 0; return 1; } struct scsi_vpd_id_descriptor * scsi_get_devid_desc(struct scsi_vpd_id_descriptor *desc, uint32_t len, scsi_devid_checkfn_t ck_fn) { uint8_t *desc_buf_end; desc_buf_end = (uint8_t *)desc + len; for (; desc->identifier <= desc_buf_end && desc->identifier + desc->length <= desc_buf_end; desc = (struct scsi_vpd_id_descriptor *)(desc->identifier + desc->length)) { if (ck_fn == NULL || ck_fn((uint8_t *)desc) != 0) return (desc); } return (NULL); } struct scsi_vpd_id_descriptor * scsi_get_devid(struct scsi_vpd_device_id *id, uint32_t page_len, scsi_devid_checkfn_t ck_fn) { uint32_t len; if (page_len < sizeof(*id)) return (NULL); len = MIN(scsi_2btoul(id->length), page_len - sizeof(*id)); return (scsi_get_devid_desc((struct scsi_vpd_id_descriptor *) id->desc_list, len, ck_fn)); } int scsi_transportid_sbuf(struct sbuf *sb, struct scsi_transportid_header *hdr, uint32_t valid_len) { switch (hdr->format_protocol & SCSI_TRN_PROTO_MASK) { case SCSI_PROTO_FC: { struct scsi_transportid_fcp *fcp; uint64_t n_port_name; fcp = (struct scsi_transportid_fcp *)hdr; n_port_name = scsi_8btou64(fcp->n_port_name); sbuf_printf(sb, "FCP address: 0x%.16jx",(uintmax_t)n_port_name); break; } case SCSI_PROTO_SPI: { struct scsi_transportid_spi *spi; spi = (struct scsi_transportid_spi *)hdr; sbuf_printf(sb, "SPI address: %u,%u", scsi_2btoul(spi->scsi_addr), scsi_2btoul(spi->rel_trgt_port_id)); break; } case SCSI_PROTO_SSA: /* * XXX KDM there is no transport ID defined in SPC-4 for * SSA. */ break; case SCSI_PROTO_1394: { struct scsi_transportid_1394 *sbp; uint64_t eui64; sbp = (struct scsi_transportid_1394 *)hdr; eui64 = scsi_8btou64(sbp->eui64); sbuf_printf(sb, "SBP address: 0x%.16jx", (uintmax_t)eui64); break; } case SCSI_PROTO_RDMA: { struct scsi_transportid_rdma *rdma; unsigned int i; rdma = (struct scsi_transportid_rdma *)hdr; sbuf_printf(sb, "RDMA address: 0x"); for (i = 0; i < sizeof(rdma->initiator_port_id); i++) sbuf_printf(sb, "%02x", rdma->initiator_port_id[i]); break; } case SCSI_PROTO_ISCSI: { uint32_t add_len, i; uint8_t *iscsi_name = NULL; int nul_found = 0; sbuf_printf(sb, "iSCSI address: "); if ((hdr->format_protocol & SCSI_TRN_FORMAT_MASK) == SCSI_TRN_ISCSI_FORMAT_DEVICE) { struct scsi_transportid_iscsi_device *dev; dev = (struct scsi_transportid_iscsi_device *)hdr; /* * Verify how much additional data we really have. */ add_len = scsi_2btoul(dev->additional_length); add_len = MIN(add_len, valid_len - __offsetof(struct scsi_transportid_iscsi_device, iscsi_name)); iscsi_name = &dev->iscsi_name[0]; } else if ((hdr->format_protocol & SCSI_TRN_FORMAT_MASK) == SCSI_TRN_ISCSI_FORMAT_PORT) { struct scsi_transportid_iscsi_port *port; port = (struct scsi_transportid_iscsi_port *)hdr; add_len = scsi_2btoul(port->additional_length); add_len = MIN(add_len, valid_len - __offsetof(struct scsi_transportid_iscsi_port, iscsi_name)); iscsi_name = &port->iscsi_name[0]; } else { sbuf_printf(sb, "unknown format %x", (hdr->format_protocol & SCSI_TRN_FORMAT_MASK) >> SCSI_TRN_FORMAT_SHIFT); break; } if (add_len == 0) { sbuf_printf(sb, "not enough data"); break; } /* * This is supposed to be a NUL-terminated ASCII * string, but you never know. So we're going to * check. We need to do this because there is no * sbuf equivalent of strncat(). */ for (i = 0; i < add_len; i++) { if (iscsi_name[i] == '\0') { nul_found = 1; break; } } /* * If there is a NUL in the name, we can just use * sbuf_cat(). Otherwise we need to use sbuf_bcat(). */ if (nul_found != 0) sbuf_cat(sb, iscsi_name); else sbuf_bcat(sb, iscsi_name, add_len); break; } case SCSI_PROTO_SAS: { struct scsi_transportid_sas *sas; uint64_t sas_addr; sas = (struct scsi_transportid_sas *)hdr; sas_addr = scsi_8btou64(sas->sas_address); sbuf_printf(sb, "SAS address: 0x%.16jx", (uintmax_t)sas_addr); break; } case SCSI_PROTO_ADITP: case SCSI_PROTO_ATA: case SCSI_PROTO_UAS: /* * No Transport ID format for ADI, ATA or USB is defined in * SPC-4. */ sbuf_printf(sb, "No known Transport ID format for protocol " "%#x", hdr->format_protocol & SCSI_TRN_PROTO_MASK); break; case SCSI_PROTO_SOP: { struct scsi_transportid_sop *sop; struct scsi_sop_routing_id_norm *rid; sop = (struct scsi_transportid_sop *)hdr; rid = (struct scsi_sop_routing_id_norm *)sop->routing_id; /* * Note that there is no alternate format specified in SPC-4 * for the PCIe routing ID, so we don't really have a way * to know whether the second byte of the routing ID is * a device and function or just a function. So we just * assume bus,device,function. */ sbuf_printf(sb, "SOP Routing ID: %u,%u,%u", rid->bus, rid->devfunc >> SCSI_TRN_SOP_DEV_SHIFT, rid->devfunc & SCSI_TRN_SOP_FUNC_NORM_MAX); break; } case SCSI_PROTO_NONE: default: sbuf_printf(sb, "Unknown protocol %#x", hdr->format_protocol & SCSI_TRN_PROTO_MASK); break; } return (0); } struct scsi_nv scsi_proto_map[] = { { "fcp", SCSI_PROTO_FC }, { "spi", SCSI_PROTO_SPI }, { "ssa", SCSI_PROTO_SSA }, { "sbp", SCSI_PROTO_1394 }, { "1394", SCSI_PROTO_1394 }, { "srp", SCSI_PROTO_RDMA }, { "rdma", SCSI_PROTO_RDMA }, { "iscsi", SCSI_PROTO_ISCSI }, { "iqn", SCSI_PROTO_ISCSI }, { "sas", SCSI_PROTO_SAS }, { "aditp", SCSI_PROTO_ADITP }, { "ata", SCSI_PROTO_ATA }, { "uas", SCSI_PROTO_UAS }, { "usb", SCSI_PROTO_UAS }, { "sop", SCSI_PROTO_SOP } }; const char * scsi_nv_to_str(struct scsi_nv *table, int num_table_entries, uint64_t value) { int i; for (i = 0; i < num_table_entries; i++) { if (table[i].value == value) return (table[i].name); } return (NULL); } /* * Given a name/value table, find a value matching the given name. * Return values: * SCSI_NV_FOUND - match found * SCSI_NV_AMBIGUOUS - more than one match, none of them exact * SCSI_NV_NOT_FOUND - no match found */ scsi_nv_status scsi_get_nv(struct scsi_nv *table, int num_table_entries, char *name, int *table_entry, scsi_nv_flags flags) { int i, num_matches = 0; for (i = 0; i < num_table_entries; i++) { size_t table_len, name_len; table_len = strlen(table[i].name); name_len = strlen(name); if ((((flags & SCSI_NV_FLAG_IG_CASE) != 0) && (strncasecmp(table[i].name, name, name_len) == 0)) || (((flags & SCSI_NV_FLAG_IG_CASE) == 0) && (strncmp(table[i].name, name, name_len) == 0))) { *table_entry = i; /* * Check for an exact match. If we have the same * number of characters in the table as the argument, * and we already know they're the same, we have * an exact match. */ if (table_len == name_len) return (SCSI_NV_FOUND); /* * Otherwise, bump up the number of matches. We'll * see later how many we have. */ num_matches++; } } if (num_matches > 1) return (SCSI_NV_AMBIGUOUS); else if (num_matches == 1) return (SCSI_NV_FOUND); else return (SCSI_NV_NOT_FOUND); } /* * Parse transport IDs for Fibre Channel, 1394 and SAS. Since these are * all 64-bit numbers, the code is similar. */ int scsi_parse_transportid_64bit(int proto_id, char *id_str, struct scsi_transportid_header **hdr, unsigned int *alloc_len, #ifdef _KERNEL struct malloc_type *type, int flags, #endif char *error_str, int error_str_len) { uint64_t value; char *endptr; int retval; size_t alloc_size; retval = 0; value = strtouq(id_str, &endptr, 0); if (*endptr != '\0') { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: error " "parsing ID %s, 64-bit number required", __func__, id_str); } retval = 1; goto bailout; } switch (proto_id) { case SCSI_PROTO_FC: alloc_size = sizeof(struct scsi_transportid_fcp); break; case SCSI_PROTO_1394: alloc_size = sizeof(struct scsi_transportid_1394); break; case SCSI_PROTO_SAS: alloc_size = sizeof(struct scsi_transportid_sas); break; default: if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: unsupported " "protocol %d", __func__, proto_id); } retval = 1; goto bailout; break; /* NOTREACHED */ } #ifdef _KERNEL *hdr = malloc(alloc_size, type, flags); #else /* _KERNEL */ *hdr = malloc(alloc_size); #endif /*_KERNEL */ if (*hdr == NULL) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: unable to " "allocate %zu bytes", __func__, alloc_size); } retval = 1; goto bailout; } *alloc_len = alloc_size; bzero(*hdr, alloc_size); switch (proto_id) { case SCSI_PROTO_FC: { struct scsi_transportid_fcp *fcp; fcp = (struct scsi_transportid_fcp *)(*hdr); fcp->format_protocol = SCSI_PROTO_FC | SCSI_TRN_FCP_FORMAT_DEFAULT; scsi_u64to8b(value, fcp->n_port_name); break; } case SCSI_PROTO_1394: { struct scsi_transportid_1394 *sbp; sbp = (struct scsi_transportid_1394 *)(*hdr); sbp->format_protocol = SCSI_PROTO_1394 | SCSI_TRN_1394_FORMAT_DEFAULT; scsi_u64to8b(value, sbp->eui64); break; } case SCSI_PROTO_SAS: { struct scsi_transportid_sas *sas; sas = (struct scsi_transportid_sas *)(*hdr); sas->format_protocol = SCSI_PROTO_SAS | SCSI_TRN_SAS_FORMAT_DEFAULT; scsi_u64to8b(value, sas->sas_address); break; } default: break; } bailout: return (retval); } /* * Parse a SPI (Parallel SCSI) address of the form: id,rel_tgt_port */ int scsi_parse_transportid_spi(char *id_str, struct scsi_transportid_header **hdr, unsigned int *alloc_len, #ifdef _KERNEL struct malloc_type *type, int flags, #endif char *error_str, int error_str_len) { unsigned long scsi_addr, target_port; struct scsi_transportid_spi *spi; char *tmpstr, *endptr; int retval; retval = 0; tmpstr = strsep(&id_str, ","); if (tmpstr == NULL) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: no ID found", __func__); } retval = 1; goto bailout; } scsi_addr = strtoul(tmpstr, &endptr, 0); if (*endptr != '\0') { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: error " "parsing SCSI ID %s, number required", __func__, tmpstr); } retval = 1; goto bailout; } if (id_str == NULL) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: no relative " "target port found", __func__); } retval = 1; goto bailout; } target_port = strtoul(id_str, &endptr, 0); if (*endptr != '\0') { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: error " "parsing relative target port %s, number " "required", __func__, id_str); } retval = 1; goto bailout; } #ifdef _KERNEL spi = malloc(sizeof(*spi), type, flags); #else spi = malloc(sizeof(*spi)); #endif if (spi == NULL) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: unable to " "allocate %zu bytes", __func__, sizeof(*spi)); } retval = 1; goto bailout; } *alloc_len = sizeof(*spi); bzero(spi, sizeof(*spi)); spi->format_protocol = SCSI_PROTO_SPI | SCSI_TRN_SPI_FORMAT_DEFAULT; scsi_ulto2b(scsi_addr, spi->scsi_addr); scsi_ulto2b(target_port, spi->rel_trgt_port_id); *hdr = (struct scsi_transportid_header *)spi; bailout: return (retval); } /* * Parse an RDMA/SRP Initiator Port ID string. This is 32 hexadecimal digits, * optionally prefixed by "0x" or "0X". */ int scsi_parse_transportid_rdma(char *id_str, struct scsi_transportid_header **hdr, unsigned int *alloc_len, #ifdef _KERNEL struct malloc_type *type, int flags, #endif char *error_str, int error_str_len) { struct scsi_transportid_rdma *rdma; int retval; size_t id_len, rdma_id_size; uint8_t rdma_id[SCSI_TRN_RDMA_PORT_LEN]; char *tmpstr; unsigned int i, j; retval = 0; id_len = strlen(id_str); rdma_id_size = SCSI_TRN_RDMA_PORT_LEN; /* * Check the size. It needs to be either 32 or 34 characters long. */ if ((id_len != (rdma_id_size * 2)) && (id_len != ((rdma_id_size * 2) + 2))) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: RDMA ID " "must be 32 hex digits (0x prefix " "optional), only %zu seen", __func__, id_len); } retval = 1; goto bailout; } tmpstr = id_str; /* * If the user gave us 34 characters, the string needs to start * with '0x'. */ if (id_len == ((rdma_id_size * 2) + 2)) { if ((tmpstr[0] == '0') && ((tmpstr[1] == 'x') || (tmpstr[1] == 'X'))) { tmpstr += 2; } else { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: RDMA " "ID prefix, if used, must be \"0x\", " "got %s", __func__, tmpstr); } retval = 1; goto bailout; } } bzero(rdma_id, sizeof(rdma_id)); /* * Convert ASCII hex into binary bytes. There is no standard * 128-bit integer type, and so no strtou128t() routine to convert * from hex into a large integer. In the end, we're not going to * an integer, but rather to a byte array, so that and the fact * that we require the user to give us 32 hex digits simplifies the * logic. */ for (i = 0; i < (rdma_id_size * 2); i++) { int cur_shift; unsigned char c; /* Increment the byte array one for every 2 hex digits */ j = i >> 1; /* * The first digit in every pair is the most significant * 4 bits. The second is the least significant 4 bits. */ if ((i % 2) == 0) cur_shift = 4; else cur_shift = 0; c = tmpstr[i]; /* Convert the ASCII hex character into a number */ if (isdigit(c)) c -= '0'; else if (isalpha(c)) c -= isupper(c) ? 'A' - 10 : 'a' - 10; else { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: " "RDMA ID must be hex digits, got " "invalid character %c", __func__, tmpstr[i]); } retval = 1; goto bailout; } /* * The converted number can't be less than 0; the type is * unsigned, and the subtraction logic will not give us * a negative number. So we only need to make sure that * the value is not greater than 0xf. (i.e. make sure the * user didn't give us a value like "0x12jklmno"). */ if (c > 0xf) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: " "RDMA ID must be hex digits, got " "invalid character %c", __func__, tmpstr[i]); } retval = 1; goto bailout; } rdma_id[j] |= c << cur_shift; } #ifdef _KERNEL rdma = malloc(sizeof(*rdma), type, flags); #else rdma = malloc(sizeof(*rdma)); #endif if (rdma == NULL) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: unable to " "allocate %zu bytes", __func__, sizeof(*rdma)); } retval = 1; goto bailout; } *alloc_len = sizeof(*rdma); bzero(rdma, *alloc_len); rdma->format_protocol = SCSI_PROTO_RDMA | SCSI_TRN_RDMA_FORMAT_DEFAULT; bcopy(rdma_id, rdma->initiator_port_id, SCSI_TRN_RDMA_PORT_LEN); *hdr = (struct scsi_transportid_header *)rdma; bailout: return (retval); } /* * Parse an iSCSI name. The format is either just the name: * * iqn.2012-06.com.example:target0 * or the name, separator and initiator session ID: * * iqn.2012-06.com.example:target0,i,0x123 * * The separator format is exact. */ int scsi_parse_transportid_iscsi(char *id_str, struct scsi_transportid_header **hdr, unsigned int *alloc_len, #ifdef _KERNEL struct malloc_type *type, int flags, #endif char *error_str, int error_str_len) { size_t id_len, sep_len, id_size, name_len; int retval; unsigned int i, sep_pos, sep_found; const char *sep_template = ",i,0x"; const char *iqn_prefix = "iqn."; struct scsi_transportid_iscsi_device *iscsi; retval = 0; sep_found = 0; id_len = strlen(id_str); sep_len = strlen(sep_template); /* * The separator is defined as exactly ',i,0x'. Any other commas, * or any other form, is an error. So look for a comma, and once * we find that, the next few characters must match the separator * exactly. Once we get through the separator, there should be at * least one character. */ for (i = 0, sep_pos = 0; i < id_len; i++) { if (sep_pos == 0) { if (id_str[i] == sep_template[sep_pos]) sep_pos++; continue; } if (sep_pos < sep_len) { if (id_str[i] == sep_template[sep_pos]) { sep_pos++; continue; } if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: " "invalid separator in iSCSI name " "\"%s\"", __func__, id_str); } retval = 1; goto bailout; } else { sep_found = 1; break; } } /* * Check to see whether we have a separator but no digits after it. */ if ((sep_pos != 0) && (sep_found == 0)) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: no digits " "found after separator in iSCSI name \"%s\"", __func__, id_str); } retval = 1; goto bailout; } /* * The incoming ID string has the "iqn." prefix stripped off. We * need enough space for the base structure (the structures are the * same for the two iSCSI forms), the prefix, the ID string and a * terminating NUL. */ id_size = sizeof(*iscsi) + strlen(iqn_prefix) + id_len + 1; #ifdef _KERNEL iscsi = malloc(id_size, type, flags); #else iscsi = malloc(id_size); #endif if (iscsi == NULL) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: unable to " "allocate %zu bytes", __func__, id_size); } retval = 1; goto bailout; } *alloc_len = id_size; bzero(iscsi, id_size); iscsi->format_protocol = SCSI_PROTO_ISCSI; if (sep_found == 0) iscsi->format_protocol |= SCSI_TRN_ISCSI_FORMAT_DEVICE; else iscsi->format_protocol |= SCSI_TRN_ISCSI_FORMAT_PORT; name_len = id_size - sizeof(*iscsi); scsi_ulto2b(name_len, iscsi->additional_length); snprintf(iscsi->iscsi_name, name_len, "%s%s", iqn_prefix, id_str); *hdr = (struct scsi_transportid_header *)iscsi; bailout: return (retval); } /* * Parse a SCSI over PCIe (SOP) identifier. The Routing ID can either be * of the form 'bus,device,function' or 'bus,function'. */ int scsi_parse_transportid_sop(char *id_str, struct scsi_transportid_header **hdr, unsigned int *alloc_len, #ifdef _KERNEL struct malloc_type *type, int flags, #endif char *error_str, int error_str_len) { struct scsi_transportid_sop *sop; unsigned long bus, device, function; char *tmpstr, *endptr; int retval, device_spec; retval = 0; device_spec = 0; device = 0; tmpstr = strsep(&id_str, ","); if ((tmpstr == NULL) || (*tmpstr == '\0')) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: no ID found", __func__); } retval = 1; goto bailout; } bus = strtoul(tmpstr, &endptr, 0); if (*endptr != '\0') { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: error " "parsing PCIe bus %s, number required", __func__, tmpstr); } retval = 1; goto bailout; } if ((id_str == NULL) || (*id_str == '\0')) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: no PCIe " "device or function found", __func__); } retval = 1; goto bailout; } tmpstr = strsep(&id_str, ","); function = strtoul(tmpstr, &endptr, 0); if (*endptr != '\0') { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: error " "parsing PCIe device/function %s, number " "required", __func__, tmpstr); } retval = 1; goto bailout; } /* * Check to see whether the user specified a third value. If so, * the second is the device. */ if (id_str != NULL) { if (*id_str == '\0') { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: " "no PCIe function found", __func__); } retval = 1; goto bailout; } device = function; device_spec = 1; function = strtoul(id_str, &endptr, 0); if (*endptr != '\0') { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: " "error parsing PCIe function %s, " "number required", __func__, id_str); } retval = 1; goto bailout; } } if (bus > SCSI_TRN_SOP_BUS_MAX) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: bus value " "%lu greater than maximum %u", __func__, bus, SCSI_TRN_SOP_BUS_MAX); } retval = 1; goto bailout; } if ((device_spec != 0) && (device > SCSI_TRN_SOP_DEV_MASK)) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: device value " "%lu greater than maximum %u", __func__, device, SCSI_TRN_SOP_DEV_MAX); } retval = 1; goto bailout; } if (((device_spec != 0) && (function > SCSI_TRN_SOP_FUNC_NORM_MAX)) || ((device_spec == 0) && (function > SCSI_TRN_SOP_FUNC_ALT_MAX))) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: function value " "%lu greater than maximum %u", __func__, function, (device_spec == 0) ? SCSI_TRN_SOP_FUNC_ALT_MAX : SCSI_TRN_SOP_FUNC_NORM_MAX); } retval = 1; goto bailout; } #ifdef _KERNEL sop = malloc(sizeof(*sop), type, flags); #else sop = malloc(sizeof(*sop)); #endif if (sop == NULL) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: unable to " "allocate %zu bytes", __func__, sizeof(*sop)); } retval = 1; goto bailout; } *alloc_len = sizeof(*sop); bzero(sop, sizeof(*sop)); sop->format_protocol = SCSI_PROTO_SOP | SCSI_TRN_SOP_FORMAT_DEFAULT; if (device_spec != 0) { struct scsi_sop_routing_id_norm rid; rid.bus = bus; rid.devfunc = (device << SCSI_TRN_SOP_DEV_SHIFT) | function; bcopy(&rid, sop->routing_id, MIN(sizeof(rid), sizeof(sop->routing_id))); } else { struct scsi_sop_routing_id_alt rid; rid.bus = bus; rid.function = function; bcopy(&rid, sop->routing_id, MIN(sizeof(rid), sizeof(sop->routing_id))); } *hdr = (struct scsi_transportid_header *)sop; bailout: return (retval); } /* * transportid_str: NUL-terminated string with format: protcol,id * The ID is protocol specific. * hdr: Storage will be allocated for the transport ID. * alloc_len: The amount of memory allocated is returned here. * type: Malloc bucket (kernel only). * flags: Malloc flags (kernel only). * error_str: If non-NULL, it will contain error information (without * a terminating newline) if an error is returned. * error_str_len: Allocated length of the error string. * * Returns 0 for success, non-zero for failure. */ int scsi_parse_transportid(char *transportid_str, struct scsi_transportid_header **hdr, unsigned int *alloc_len, #ifdef _KERNEL struct malloc_type *type, int flags, #endif char *error_str, int error_str_len) { char *tmpstr; scsi_nv_status status; u_int num_proto_entries; int retval, table_entry; retval = 0; table_entry = 0; /* * We do allow a period as well as a comma to separate the protocol * from the ID string. This is to accommodate iSCSI names, which * start with "iqn.". */ tmpstr = strsep(&transportid_str, ",."); if (tmpstr == NULL) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: transportid_str is NULL", __func__); } retval = 1; goto bailout; } num_proto_entries = nitems(scsi_proto_map); status = scsi_get_nv(scsi_proto_map, num_proto_entries, tmpstr, &table_entry, SCSI_NV_FLAG_IG_CASE); if (status != SCSI_NV_FOUND) { if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: %s protocol " "name %s", __func__, (status == SCSI_NV_AMBIGUOUS) ? "ambiguous" : "invalid", tmpstr); } retval = 1; goto bailout; } switch (scsi_proto_map[table_entry].value) { case SCSI_PROTO_FC: case SCSI_PROTO_1394: case SCSI_PROTO_SAS: retval = scsi_parse_transportid_64bit( scsi_proto_map[table_entry].value, transportid_str, hdr, alloc_len, #ifdef _KERNEL type, flags, #endif error_str, error_str_len); break; case SCSI_PROTO_SPI: retval = scsi_parse_transportid_spi(transportid_str, hdr, alloc_len, #ifdef _KERNEL type, flags, #endif error_str, error_str_len); break; case SCSI_PROTO_RDMA: retval = scsi_parse_transportid_rdma(transportid_str, hdr, alloc_len, #ifdef _KERNEL type, flags, #endif error_str, error_str_len); break; case SCSI_PROTO_ISCSI: retval = scsi_parse_transportid_iscsi(transportid_str, hdr, alloc_len, #ifdef _KERNEL type, flags, #endif error_str, error_str_len); break; case SCSI_PROTO_SOP: retval = scsi_parse_transportid_sop(transportid_str, hdr, alloc_len, #ifdef _KERNEL type, flags, #endif error_str, error_str_len); break; case SCSI_PROTO_SSA: case SCSI_PROTO_ADITP: case SCSI_PROTO_ATA: case SCSI_PROTO_UAS: case SCSI_PROTO_NONE: default: /* * There is no format defined for a Transport ID for these * protocols. So even if the user gives us something, we * have no way to turn it into a standard SCSI Transport ID. */ retval = 1; if (error_str != NULL) { snprintf(error_str, error_str_len, "%s: no Transport " "ID format exists for protocol %s", __func__, tmpstr); } goto bailout; break; /* NOTREACHED */ } bailout: return (retval); } struct scsi_attrib_table_entry scsi_mam_attr_table[] = { { SMA_ATTR_REM_CAP_PARTITION, SCSI_ATTR_FLAG_NONE, "Remaining Capacity in Partition", /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf,/*parse_str*/ NULL }, { SMA_ATTR_MAX_CAP_PARTITION, SCSI_ATTR_FLAG_NONE, "Maximum Capacity in Partition", /*suffix*/"MB", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_TAPEALERT_FLAGS, SCSI_ATTR_FLAG_HEX, "TapeAlert Flags", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_LOAD_COUNT, SCSI_ATTR_FLAG_NONE, "Load Count", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MAM_SPACE_REMAINING, SCSI_ATTR_FLAG_NONE, "MAM Space Remaining", /*suffix*/"bytes", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_DEV_ASSIGNING_ORG, SCSI_ATTR_FLAG_NONE, "Assigning Organization", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_FORMAT_DENSITY_CODE, SCSI_ATTR_FLAG_HEX, "Format Density Code", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_INITIALIZATION_COUNT, SCSI_ATTR_FLAG_NONE, "Initialization Count", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_VOLUME_ID, SCSI_ATTR_FLAG_NONE, "Volume Identifier", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_VOLUME_CHANGE_REF, SCSI_ATTR_FLAG_HEX, "Volume Change Reference", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_DEV_SERIAL_LAST_LOAD, SCSI_ATTR_FLAG_NONE, "Device Vendor/Serial at Last Load", /*suffix*/NULL, /*to_str*/ scsi_attrib_vendser_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_DEV_SERIAL_LAST_LOAD_1, SCSI_ATTR_FLAG_NONE, "Device Vendor/Serial at Last Load - 1", /*suffix*/NULL, /*to_str*/ scsi_attrib_vendser_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_DEV_SERIAL_LAST_LOAD_2, SCSI_ATTR_FLAG_NONE, "Device Vendor/Serial at Last Load - 2", /*suffix*/NULL, /*to_str*/ scsi_attrib_vendser_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_DEV_SERIAL_LAST_LOAD_3, SCSI_ATTR_FLAG_NONE, "Device Vendor/Serial at Last Load - 3", /*suffix*/NULL, /*to_str*/ scsi_attrib_vendser_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_TOTAL_MB_WRITTEN_LT, SCSI_ATTR_FLAG_NONE, "Total MB Written in Medium Life", /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_TOTAL_MB_READ_LT, SCSI_ATTR_FLAG_NONE, "Total MB Read in Medium Life", /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_TOTAL_MB_WRITTEN_CUR, SCSI_ATTR_FLAG_NONE, "Total MB Written in Current/Last Load", /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_TOTAL_MB_READ_CUR, SCSI_ATTR_FLAG_NONE, "Total MB Read in Current/Last Load", /*suffix*/ "MB", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_FIRST_ENC_BLOCK, SCSI_ATTR_FLAG_NONE, "Logical Position of First Encrypted Block", /*suffix*/ NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_NEXT_UNENC_BLOCK, SCSI_ATTR_FLAG_NONE, "Logical Position of First Unencrypted Block after First " "Encrypted Block", /*suffix*/ NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MEDIUM_USAGE_HIST, SCSI_ATTR_FLAG_NONE, "Medium Usage History", /*suffix*/ NULL, /*to_str*/ NULL, /*parse_str*/ NULL }, { SMA_ATTR_PART_USAGE_HIST, SCSI_ATTR_FLAG_NONE, "Partition Usage History", /*suffix*/ NULL, /*to_str*/ NULL, /*parse_str*/ NULL }, { SMA_ATTR_MED_MANUF, SCSI_ATTR_FLAG_NONE, "Medium Manufacturer", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_SERIAL, SCSI_ATTR_FLAG_NONE, "Medium Serial Number", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_LENGTH, SCSI_ATTR_FLAG_NONE, "Medium Length", /*suffix*/"m", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_WIDTH, SCSI_ATTR_FLAG_FP | SCSI_ATTR_FLAG_DIV_10 | SCSI_ATTR_FLAG_FP_1DIGIT, "Medium Width", /*suffix*/"mm", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_ASSIGNING_ORG, SCSI_ATTR_FLAG_NONE, "Assigning Organization", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_DENSITY_CODE, SCSI_ATTR_FLAG_HEX, "Medium Density Code", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_MANUF_DATE, SCSI_ATTR_FLAG_NONE, "Medium Manufacture Date", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MAM_CAPACITY, SCSI_ATTR_FLAG_NONE, "MAM Capacity", /*suffix*/"bytes", /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_TYPE, SCSI_ATTR_FLAG_HEX, "Medium Type", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_TYPE_INFO, SCSI_ATTR_FLAG_HEX, "Medium Type Information", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MED_SERIAL_NUM, SCSI_ATTR_FLAG_NONE, "Medium Serial Number", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_APP_VENDOR, SCSI_ATTR_FLAG_NONE, "Application Vendor", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_APP_NAME, SCSI_ATTR_FLAG_NONE, "Application Name", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_APP_VERSION, SCSI_ATTR_FLAG_NONE, "Application Version", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_USER_MED_TEXT_LABEL, SCSI_ATTR_FLAG_NONE, "User Medium Text Label", /*suffix*/NULL, /*to_str*/ scsi_attrib_text_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_LAST_WRITTEN_TIME, SCSI_ATTR_FLAG_NONE, "Date and Time Last Written", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_TEXT_LOCAL_ID, SCSI_ATTR_FLAG_HEX, "Text Localization Identifier", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_BARCODE, SCSI_ATTR_FLAG_NONE, "Barcode", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_HOST_OWNER_NAME, SCSI_ATTR_FLAG_NONE, "Owning Host Textual Name", /*suffix*/NULL, /*to_str*/ scsi_attrib_text_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_MEDIA_POOL, SCSI_ATTR_FLAG_NONE, "Media Pool", /*suffix*/NULL, /*to_str*/ scsi_attrib_text_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_PART_USER_LABEL, SCSI_ATTR_FLAG_NONE, "Partition User Text Label", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_LOAD_UNLOAD_AT_PART, SCSI_ATTR_FLAG_NONE, "Load/Unload at Partition", /*suffix*/NULL, /*to_str*/ scsi_attrib_int_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_APP_FORMAT_VERSION, SCSI_ATTR_FLAG_NONE, "Application Format Version", /*suffix*/NULL, /*to_str*/ scsi_attrib_ascii_sbuf, /*parse_str*/ NULL }, { SMA_ATTR_VOL_COHERENCY_INFO, SCSI_ATTR_FLAG_NONE, "Volume Coherency Information", /*suffix*/NULL, /*to_str*/ scsi_attrib_volcoh_sbuf, /*parse_str*/ NULL }, { 0x0ff1, SCSI_ATTR_FLAG_NONE, "Spectra MLM Creation", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x0ff2, SCSI_ATTR_FLAG_NONE, "Spectra MLM C3", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x0ff3, SCSI_ATTR_FLAG_NONE, "Spectra MLM RW", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x0ff4, SCSI_ATTR_FLAG_NONE, "Spectra MLM SDC List", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x0ff7, SCSI_ATTR_FLAG_NONE, "Spectra MLM Post Scan", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x0ffe, SCSI_ATTR_FLAG_NONE, "Spectra MLM Checksum", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x17f1, SCSI_ATTR_FLAG_NONE, "Spectra MLM Creation", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x17f2, SCSI_ATTR_FLAG_NONE, "Spectra MLM C3", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x17f3, SCSI_ATTR_FLAG_NONE, "Spectra MLM RW", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x17f4, SCSI_ATTR_FLAG_NONE, "Spectra MLM SDC List", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x17f7, SCSI_ATTR_FLAG_NONE, "Spectra MLM Post Scan", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, { 0x17ff, SCSI_ATTR_FLAG_NONE, "Spectra MLM Checksum", /*suffix*/NULL, /*to_str*/ scsi_attrib_hexdump_sbuf, /*parse_str*/ NULL }, }; /* * Print out Volume Coherency Information (Attribute 0x080c). * This field has two variable length members, including one at the * beginning, so it isn't practical to have a fixed structure definition. * This is current as of SSC4r03 (see section 4.2.21.3), dated March 25, * 2013. */ int scsi_attrib_volcoh_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, uint32_t valid_len, uint32_t flags, uint32_t output_flags, char *error_str, int error_str_len) { size_t avail_len; uint32_t field_size; uint64_t tmp_val; uint8_t *cur_ptr; int retval; int vcr_len, as_len; retval = 0; tmp_val = 0; field_size = scsi_2btoul(hdr->length); avail_len = valid_len - sizeof(*hdr); if (field_size > avail_len) { if (error_str != NULL) { snprintf(error_str, error_str_len, "Available " "length of attribute ID 0x%.4x %zu < field " "length %u", scsi_2btoul(hdr->id), avail_len, field_size); } retval = 1; goto bailout; } else if (field_size == 0) { /* * It isn't clear from the spec whether a field length of * 0 is invalid here. It probably is, but be lenient here * to avoid inconveniencing the user. */ goto bailout; } cur_ptr = hdr->attribute; vcr_len = *cur_ptr; cur_ptr++; sbuf_printf(sb, "\n\tVolume Change Reference Value:"); switch (vcr_len) { case 0: if (error_str != NULL) { snprintf(error_str, error_str_len, "Volume Change " "Reference value has length of 0"); } retval = 1; goto bailout; break; /*NOTREACHED*/ case 1: tmp_val = *cur_ptr; break; case 2: tmp_val = scsi_2btoul(cur_ptr); break; case 3: tmp_val = scsi_3btoul(cur_ptr); break; case 4: tmp_val = scsi_4btoul(cur_ptr); break; case 8: tmp_val = scsi_8btou64(cur_ptr); break; default: sbuf_printf(sb, "\n"); sbuf_hexdump(sb, cur_ptr, vcr_len, NULL, 0); break; } if (vcr_len <= 8) sbuf_printf(sb, " 0x%jx\n", (uintmax_t)tmp_val); cur_ptr += vcr_len; tmp_val = scsi_8btou64(cur_ptr); sbuf_printf(sb, "\tVolume Coherency Count: %ju\n", (uintmax_t)tmp_val); cur_ptr += sizeof(tmp_val); tmp_val = scsi_8btou64(cur_ptr); sbuf_printf(sb, "\tVolume Coherency Set Identifier: 0x%jx\n", (uintmax_t)tmp_val); /* * Figure out how long the Application Client Specific Information * is and produce a hexdump. */ cur_ptr += sizeof(tmp_val); as_len = scsi_2btoul(cur_ptr); cur_ptr += sizeof(uint16_t); sbuf_printf(sb, "\tApplication Client Specific Information: "); if (((as_len == SCSI_LTFS_VER0_LEN) || (as_len == SCSI_LTFS_VER1_LEN)) && (strncmp(cur_ptr, SCSI_LTFS_STR_NAME, SCSI_LTFS_STR_LEN) == 0)) { sbuf_printf(sb, "LTFS\n"); cur_ptr += SCSI_LTFS_STR_LEN + 1; if (cur_ptr[SCSI_LTFS_UUID_LEN] != '\0') cur_ptr[SCSI_LTFS_UUID_LEN] = '\0'; sbuf_printf(sb, "\tLTFS UUID: %s\n", cur_ptr); cur_ptr += SCSI_LTFS_UUID_LEN + 1; /* XXX KDM check the length */ sbuf_printf(sb, "\tLTFS Version: %d\n", *cur_ptr); } else { sbuf_printf(sb, "Unknown\n"); sbuf_hexdump(sb, cur_ptr, as_len, NULL, 0); } bailout: return (retval); } int scsi_attrib_vendser_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, uint32_t valid_len, uint32_t flags, uint32_t output_flags, char *error_str, int error_str_len) { size_t avail_len; uint32_t field_size; struct scsi_attrib_vendser *vendser; cam_strvis_flags strvis_flags; int retval = 0; field_size = scsi_2btoul(hdr->length); avail_len = valid_len - sizeof(*hdr); if (field_size > avail_len) { if (error_str != NULL) { snprintf(error_str, error_str_len, "Available " "length of attribute ID 0x%.4x %zu < field " "length %u", scsi_2btoul(hdr->id), avail_len, field_size); } retval = 1; goto bailout; } else if (field_size == 0) { /* * A field size of 0 doesn't make sense here. The device * can at least give you the vendor ID, even if it can't * give you the serial number. */ if (error_str != NULL) { snprintf(error_str, error_str_len, "The length of " "attribute ID 0x%.4x is 0", scsi_2btoul(hdr->id)); } retval = 1; goto bailout; } vendser = (struct scsi_attrib_vendser *)hdr->attribute; switch (output_flags & SCSI_ATTR_OUTPUT_NONASCII_MASK) { case SCSI_ATTR_OUTPUT_NONASCII_TRIM: strvis_flags = CAM_STRVIS_FLAG_NONASCII_TRIM; break; case SCSI_ATTR_OUTPUT_NONASCII_RAW: strvis_flags = CAM_STRVIS_FLAG_NONASCII_RAW; break; case SCSI_ATTR_OUTPUT_NONASCII_ESC: default: strvis_flags = CAM_STRVIS_FLAG_NONASCII_ESC; break;; } cam_strvis_sbuf(sb, vendser->vendor, sizeof(vendser->vendor), strvis_flags); sbuf_putc(sb, ' '); cam_strvis_sbuf(sb, vendser->serial_num, sizeof(vendser->serial_num), strvis_flags); bailout: return (retval); } int scsi_attrib_hexdump_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, uint32_t valid_len, uint32_t flags, uint32_t output_flags, char *error_str, int error_str_len) { uint32_t field_size; ssize_t avail_len; uint32_t print_len; uint8_t *num_ptr; int retval = 0; field_size = scsi_2btoul(hdr->length); avail_len = valid_len - sizeof(*hdr); print_len = MIN(avail_len, field_size); num_ptr = hdr->attribute; if (print_len > 0) { sbuf_printf(sb, "\n"); sbuf_hexdump(sb, num_ptr, print_len, NULL, 0); } return (retval); } int scsi_attrib_int_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, uint32_t valid_len, uint32_t flags, uint32_t output_flags, char *error_str, int error_str_len) { uint64_t print_number; size_t avail_len; uint32_t number_size; int retval = 0; number_size = scsi_2btoul(hdr->length); avail_len = valid_len - sizeof(*hdr); if (avail_len < number_size) { if (error_str != NULL) { snprintf(error_str, error_str_len, "Available " "length of attribute ID 0x%.4x %zu < field " "length %u", scsi_2btoul(hdr->id), avail_len, number_size); } retval = 1; goto bailout; } switch (number_size) { case 0: /* * We don't treat this as an error, since there may be * scenarios where a device reports a field but then gives * a length of 0. See the note in scsi_attrib_ascii_sbuf(). */ goto bailout; break; /*NOTREACHED*/ case 1: print_number = hdr->attribute[0]; break; case 2: print_number = scsi_2btoul(hdr->attribute); break; case 3: print_number = scsi_3btoul(hdr->attribute); break; case 4: print_number = scsi_4btoul(hdr->attribute); break; case 8: print_number = scsi_8btou64(hdr->attribute); break; default: /* * If we wind up here, the number is too big to print * normally, so just do a hexdump. */ retval = scsi_attrib_hexdump_sbuf(sb, hdr, valid_len, flags, output_flags, error_str, error_str_len); goto bailout; break; } if (flags & SCSI_ATTR_FLAG_FP) { #ifndef _KERNEL long double num_float; num_float = (long double)print_number; if (flags & SCSI_ATTR_FLAG_DIV_10) num_float /= 10; sbuf_printf(sb, "%.*Lf", (flags & SCSI_ATTR_FLAG_FP_1DIGIT) ? 1 : 0, num_float); #else /* _KERNEL */ sbuf_printf(sb, "%ju", (flags & SCSI_ATTR_FLAG_DIV_10) ? (print_number / 10) : print_number); #endif /* _KERNEL */ } else if (flags & SCSI_ATTR_FLAG_HEX) { sbuf_printf(sb, "0x%jx", (uintmax_t)print_number); } else sbuf_printf(sb, "%ju", (uintmax_t)print_number); bailout: return (retval); } int scsi_attrib_ascii_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, uint32_t valid_len, uint32_t flags, uint32_t output_flags, char *error_str, int error_str_len) { size_t avail_len; uint32_t field_size, print_size; int retval = 0; avail_len = valid_len - sizeof(*hdr); field_size = scsi_2btoul(hdr->length); print_size = MIN(avail_len, field_size); if (print_size > 0) { cam_strvis_flags strvis_flags; switch (output_flags & SCSI_ATTR_OUTPUT_NONASCII_MASK) { case SCSI_ATTR_OUTPUT_NONASCII_TRIM: strvis_flags = CAM_STRVIS_FLAG_NONASCII_TRIM; break; case SCSI_ATTR_OUTPUT_NONASCII_RAW: strvis_flags = CAM_STRVIS_FLAG_NONASCII_RAW; break; case SCSI_ATTR_OUTPUT_NONASCII_ESC: default: strvis_flags = CAM_STRVIS_FLAG_NONASCII_ESC; break; } cam_strvis_sbuf(sb, hdr->attribute, print_size, strvis_flags); } else if (avail_len < field_size) { /* * We only report an error if the user didn't allocate * enough space to hold the full value of this field. If * the field length is 0, that is allowed by the spec. * e.g. in SPC-4r37, section 7.4.2.2.5, VOLUME IDENTIFIER * "This attribute indicates the current volume identifier * (see SMC-3) of the medium. If the device server supports * this attribute but does not have access to the volume * identifier, the device server shall report this attribute * with an attribute length value of zero." */ if (error_str != NULL) { snprintf(error_str, error_str_len, "Available " "length of attribute ID 0x%.4x %zu < field " "length %u", scsi_2btoul(hdr->id), avail_len, field_size); } retval = 1; } return (retval); } int scsi_attrib_text_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, uint32_t valid_len, uint32_t flags, uint32_t output_flags, char *error_str, int error_str_len) { size_t avail_len; uint32_t field_size, print_size; int retval = 0; int esc_text = 1; avail_len = valid_len - sizeof(*hdr); field_size = scsi_2btoul(hdr->length); print_size = MIN(avail_len, field_size); if ((output_flags & SCSI_ATTR_OUTPUT_TEXT_MASK) == SCSI_ATTR_OUTPUT_TEXT_RAW) esc_text = 0; if (print_size > 0) { uint32_t i; for (i = 0; i < print_size; i++) { if (hdr->attribute[i] == '\0') continue; else if (((unsigned char)hdr->attribute[i] < 0x80) || (esc_text == 0)) sbuf_putc(sb, hdr->attribute[i]); else sbuf_printf(sb, "%%%02x", (unsigned char)hdr->attribute[i]); } } else if (avail_len < field_size) { /* * We only report an error if the user didn't allocate * enough space to hold the full value of this field. */ if (error_str != NULL) { snprintf(error_str, error_str_len, "Available " "length of attribute ID 0x%.4x %zu < field " "length %u", scsi_2btoul(hdr->id), avail_len, field_size); } retval = 1; } return (retval); } struct scsi_attrib_table_entry * scsi_find_attrib_entry(struct scsi_attrib_table_entry *table, size_t num_table_entries, uint32_t id) { uint32_t i; for (i = 0; i < num_table_entries; i++) { if (table[i].id == id) return (&table[i]); } return (NULL); } struct scsi_attrib_table_entry * scsi_get_attrib_entry(uint32_t id) { return (scsi_find_attrib_entry(scsi_mam_attr_table, nitems(scsi_mam_attr_table), id)); } int scsi_attrib_value_sbuf(struct sbuf *sb, uint32_t valid_len, struct scsi_mam_attribute_header *hdr, uint32_t output_flags, char *error_str, size_t error_str_len) { int retval; switch (hdr->byte2 & SMA_FORMAT_MASK) { case SMA_FORMAT_ASCII: retval = scsi_attrib_ascii_sbuf(sb, hdr, valid_len, SCSI_ATTR_FLAG_NONE, output_flags, error_str,error_str_len); break; case SMA_FORMAT_BINARY: if (scsi_2btoul(hdr->length) <= 8) retval = scsi_attrib_int_sbuf(sb, hdr, valid_len, SCSI_ATTR_FLAG_NONE, output_flags, error_str, error_str_len); else retval = scsi_attrib_hexdump_sbuf(sb, hdr, valid_len, SCSI_ATTR_FLAG_NONE, output_flags, error_str, error_str_len); break; case SMA_FORMAT_TEXT: retval = scsi_attrib_text_sbuf(sb, hdr, valid_len, SCSI_ATTR_FLAG_NONE, output_flags, error_str, error_str_len); break; default: if (error_str != NULL) { snprintf(error_str, error_str_len, "Unknown attribute " "format 0x%x", hdr->byte2 & SMA_FORMAT_MASK); } retval = 1; goto bailout; break; /*NOTREACHED*/ } sbuf_trim(sb); bailout: return (retval); } void scsi_attrib_prefix_sbuf(struct sbuf *sb, uint32_t output_flags, struct scsi_mam_attribute_header *hdr, uint32_t valid_len, const char *desc) { int need_space = 0; uint32_t len; uint32_t id; /* * We can't do anything if we don't have enough valid data for the * header. */ if (valid_len < sizeof(*hdr)) return; id = scsi_2btoul(hdr->id); /* * Note that we print out the value of the attribute listed in the * header, regardless of whether we actually got that many bytes * back from the device through the controller. A truncated result * could be the result of a failure to ask for enough data; the * header indicates how many bytes are allocated for this attribute * in the MAM. */ len = scsi_2btoul(hdr->length); if ((output_flags & SCSI_ATTR_OUTPUT_FIELD_MASK) == SCSI_ATTR_OUTPUT_FIELD_NONE) return; if ((output_flags & SCSI_ATTR_OUTPUT_FIELD_DESC) && (desc != NULL)) { sbuf_printf(sb, "%s", desc); need_space = 1; } if (output_flags & SCSI_ATTR_OUTPUT_FIELD_NUM) { sbuf_printf(sb, "%s(0x%.4x)", (need_space) ? " " : "", id); need_space = 0; } if (output_flags & SCSI_ATTR_OUTPUT_FIELD_SIZE) { sbuf_printf(sb, "%s[%d]", (need_space) ? " " : "", len); need_space = 0; } if (output_flags & SCSI_ATTR_OUTPUT_FIELD_RW) { sbuf_printf(sb, "%s(%s)", (need_space) ? " " : "", (hdr->byte2 & SMA_READ_ONLY) ? "RO" : "RW"); } sbuf_printf(sb, ": "); } int scsi_attrib_sbuf(struct sbuf *sb, struct scsi_mam_attribute_header *hdr, uint32_t valid_len, struct scsi_attrib_table_entry *user_table, size_t num_user_entries, int prefer_user_table, uint32_t output_flags, char *error_str, int error_str_len) { int retval; struct scsi_attrib_table_entry *table1 = NULL, *table2 = NULL; struct scsi_attrib_table_entry *entry = NULL; size_t table1_size = 0, table2_size = 0; uint32_t id; retval = 0; if (valid_len < sizeof(*hdr)) { retval = 1; goto bailout; } id = scsi_2btoul(hdr->id); if (user_table != NULL) { if (prefer_user_table != 0) { table1 = user_table; table1_size = num_user_entries; table2 = scsi_mam_attr_table; table2_size = nitems(scsi_mam_attr_table); } else { table1 = scsi_mam_attr_table; table1_size = nitems(scsi_mam_attr_table); table2 = user_table; table2_size = num_user_entries; } } else { table1 = scsi_mam_attr_table; table1_size = nitems(scsi_mam_attr_table); } entry = scsi_find_attrib_entry(table1, table1_size, id); if (entry != NULL) { scsi_attrib_prefix_sbuf(sb, output_flags, hdr, valid_len, entry->desc); if (entry->to_str == NULL) goto print_default; retval = entry->to_str(sb, hdr, valid_len, entry->flags, output_flags, error_str, error_str_len); goto bailout; } if (table2 != NULL) { entry = scsi_find_attrib_entry(table2, table2_size, id); if (entry != NULL) { if (entry->to_str == NULL) goto print_default; scsi_attrib_prefix_sbuf(sb, output_flags, hdr, valid_len, entry->desc); retval = entry->to_str(sb, hdr, valid_len, entry->flags, output_flags, error_str, error_str_len); goto bailout; } } scsi_attrib_prefix_sbuf(sb, output_flags, hdr, valid_len, NULL); print_default: retval = scsi_attrib_value_sbuf(sb, valid_len, hdr, output_flags, error_str, error_str_len); bailout: if (retval == 0) { if ((entry != NULL) && (entry->suffix != NULL)) sbuf_printf(sb, " %s", entry->suffix); sbuf_trim(sb); sbuf_printf(sb, "\n"); } return (retval); } void scsi_test_unit_ready(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t sense_len, u_int32_t timeout) { struct scsi_test_unit_ready *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_NONE, tag_action, /*data_ptr*/NULL, /*dxfer_len*/0, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_test_unit_ready *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = TEST_UNIT_READY; } void scsi_request_sense(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), void *data_ptr, u_int8_t dxfer_len, u_int8_t tag_action, u_int8_t sense_len, u_int32_t timeout) { struct scsi_request_sense *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_IN, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_request_sense *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = REQUEST_SENSE; scsi_cmd->length = dxfer_len; } void scsi_inquiry(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t *inq_buf, u_int32_t inq_len, int evpd, u_int8_t page_code, u_int8_t sense_len, u_int32_t timeout) { struct scsi_inquiry *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/inq_buf, /*dxfer_len*/inq_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_inquiry *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = INQUIRY; if (evpd) { scsi_cmd->byte2 |= SI_EVPD; scsi_cmd->page_code = page_code; } scsi_ulto2b(inq_len, scsi_cmd->length); } void scsi_mode_sense(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, int dbd, uint8_t pc, uint8_t page, uint8_t *param_buf, uint32_t param_len, uint8_t sense_len, uint32_t timeout) { scsi_mode_sense_subpage(csio, retries, cbfcnp, tag_action, dbd, pc, page, 0, param_buf, param_len, 0, sense_len, timeout); } void scsi_mode_sense_len(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, int dbd, uint8_t pc, uint8_t page, uint8_t *param_buf, uint32_t param_len, int minimum_cmd_size, uint8_t sense_len, uint32_t timeout) { scsi_mode_sense_subpage(csio, retries, cbfcnp, tag_action, dbd, pc, page, 0, param_buf, param_len, minimum_cmd_size, sense_len, timeout); } void scsi_mode_sense_subpage(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, int dbd, uint8_t pc, uint8_t page, uint8_t subpage, uint8_t *param_buf, uint32_t param_len, int minimum_cmd_size, uint8_t sense_len, uint32_t timeout) { u_int8_t cdb_len; /* * Use the smallest possible command to perform the operation. */ if ((param_len < 256) && (minimum_cmd_size < 10)) { /* * We can fit in a 6 byte cdb. */ struct scsi_mode_sense_6 *scsi_cmd; scsi_cmd = (struct scsi_mode_sense_6 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MODE_SENSE_6; if (dbd != 0) scsi_cmd->byte2 |= SMS_DBD; scsi_cmd->page = pc | page; scsi_cmd->subpage = subpage; scsi_cmd->length = param_len; cdb_len = sizeof(*scsi_cmd); } else { /* * Need a 10 byte cdb. */ struct scsi_mode_sense_10 *scsi_cmd; scsi_cmd = (struct scsi_mode_sense_10 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MODE_SENSE_10; if (dbd != 0) scsi_cmd->byte2 |= SMS_DBD; scsi_cmd->page = pc | page; scsi_cmd->subpage = subpage; scsi_ulto2b(param_len, scsi_cmd->length); cdb_len = sizeof(*scsi_cmd); } cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_IN, tag_action, param_buf, param_len, sense_len, cdb_len, timeout); } void scsi_mode_select(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int scsi_page_fmt, int save_pages, u_int8_t *param_buf, u_int32_t param_len, u_int8_t sense_len, u_int32_t timeout) { scsi_mode_select_len(csio, retries, cbfcnp, tag_action, scsi_page_fmt, save_pages, param_buf, param_len, 0, sense_len, timeout); } void scsi_mode_select_len(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int scsi_page_fmt, int save_pages, u_int8_t *param_buf, u_int32_t param_len, int minimum_cmd_size, u_int8_t sense_len, u_int32_t timeout) { u_int8_t cdb_len; /* * Use the smallest possible command to perform the operation. */ if ((param_len < 256) && (minimum_cmd_size < 10)) { /* * We can fit in a 6 byte cdb. */ struct scsi_mode_select_6 *scsi_cmd; scsi_cmd = (struct scsi_mode_select_6 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MODE_SELECT_6; if (scsi_page_fmt != 0) scsi_cmd->byte2 |= SMS_PF; if (save_pages != 0) scsi_cmd->byte2 |= SMS_SP; scsi_cmd->length = param_len; cdb_len = sizeof(*scsi_cmd); } else { /* * Need a 10 byte cdb. */ struct scsi_mode_select_10 *scsi_cmd; scsi_cmd = (struct scsi_mode_select_10 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MODE_SELECT_10; if (scsi_page_fmt != 0) scsi_cmd->byte2 |= SMS_PF; if (save_pages != 0) scsi_cmd->byte2 |= SMS_SP; scsi_ulto2b(param_len, scsi_cmd->length); cdb_len = sizeof(*scsi_cmd); } cam_fill_csio(csio, retries, cbfcnp, CAM_DIR_OUT, tag_action, param_buf, param_len, sense_len, cdb_len, timeout); } void scsi_log_sense(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t page_code, u_int8_t page, int save_pages, int ppc, u_int32_t paramptr, u_int8_t *param_buf, u_int32_t param_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_log_sense *scsi_cmd; u_int8_t cdb_len; scsi_cmd = (struct scsi_log_sense *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = LOG_SENSE; scsi_cmd->page = page_code | page; if (save_pages != 0) scsi_cmd->byte2 |= SLS_SP; if (ppc != 0) scsi_cmd->byte2 |= SLS_PPC; scsi_ulto2b(paramptr, scsi_cmd->paramptr); scsi_ulto2b(param_len, scsi_cmd->length); cdb_len = sizeof(*scsi_cmd); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/param_buf, /*dxfer_len*/param_len, sense_len, cdb_len, timeout); } void scsi_log_select(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t page_code, int save_pages, int pc_reset, u_int8_t *param_buf, u_int32_t param_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_log_select *scsi_cmd; u_int8_t cdb_len; scsi_cmd = (struct scsi_log_select *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = LOG_SELECT; scsi_cmd->page = page_code & SLS_PAGE_CODE; if (save_pages != 0) scsi_cmd->byte2 |= SLS_SP; if (pc_reset != 0) scsi_cmd->byte2 |= SLS_PCR; scsi_ulto2b(param_len, scsi_cmd->length); cdb_len = sizeof(*scsi_cmd); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, /*data_ptr*/param_buf, /*dxfer_len*/param_len, sense_len, cdb_len, timeout); } /* * Prevent or allow the user to remove the media */ void scsi_prevent(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t action, u_int8_t sense_len, u_int32_t timeout) { struct scsi_prevent *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_NONE, tag_action, /*data_ptr*/NULL, /*dxfer_len*/0, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_prevent *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = PREVENT_ALLOW; scsi_cmd->how = action; } /* XXX allow specification of address and PMI bit and LBA */ void scsi_read_capacity(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, struct scsi_read_capacity_data *rcap_buf, u_int8_t sense_len, u_int32_t timeout) { struct scsi_read_capacity *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/(u_int8_t *)rcap_buf, /*dxfer_len*/sizeof(*rcap_buf), sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_read_capacity *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = READ_CAPACITY; } void scsi_read_capacity_16(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, uint64_t lba, int reladr, int pmi, uint8_t *rcap_buf, int rcap_buf_len, uint8_t sense_len, uint32_t timeout) { struct scsi_read_capacity_16 *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/(u_int8_t *)rcap_buf, /*dxfer_len*/rcap_buf_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_read_capacity_16 *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = SERVICE_ACTION_IN; scsi_cmd->service_action = SRC16_SERVICE_ACTION; scsi_u64to8b(lba, scsi_cmd->addr); scsi_ulto4b(rcap_buf_len, scsi_cmd->alloc_len); if (pmi) reladr |= SRC16_PMI; if (reladr) reladr |= SRC16_RELADR; } void scsi_report_luns(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t select_report, struct scsi_report_luns_data *rpl_buf, u_int32_t alloc_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_report_luns *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/(u_int8_t *)rpl_buf, /*dxfer_len*/alloc_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_report_luns *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = REPORT_LUNS; scsi_cmd->select_report = select_report; scsi_ulto4b(alloc_len, scsi_cmd->length); } void scsi_report_target_group(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t pdf, void *buf, u_int32_t alloc_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_target_group *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/(u_int8_t *)buf, /*dxfer_len*/alloc_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_target_group *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MAINTENANCE_IN; scsi_cmd->service_action = REPORT_TARGET_PORT_GROUPS | pdf; scsi_ulto4b(alloc_len, scsi_cmd->length); } void scsi_report_timestamp(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t pdf, void *buf, u_int32_t alloc_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_timestamp *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/(u_int8_t *)buf, /*dxfer_len*/alloc_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_timestamp *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MAINTENANCE_IN; scsi_cmd->service_action = REPORT_TIMESTAMP | pdf; scsi_ulto4b(alloc_len, scsi_cmd->length); } void scsi_set_target_group(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, void *buf, u_int32_t alloc_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_target_group *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, /*data_ptr*/(u_int8_t *)buf, /*dxfer_len*/alloc_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_target_group *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MAINTENANCE_OUT; scsi_cmd->service_action = SET_TARGET_PORT_GROUPS; scsi_ulto4b(alloc_len, scsi_cmd->length); } void scsi_create_timestamp(uint8_t *timestamp_6b_buf, uint64_t timestamp) { uint8_t buf[8]; scsi_u64to8b(timestamp, buf); /* * Using memcopy starting at buf[2] because the set timestamp parameters * only has six bytes for the timestamp to fit into, and we don't have a * scsi_u64to6b function. */ memcpy(timestamp_6b_buf, &buf[2], 6); } void scsi_set_timestamp(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, void *buf, u_int32_t alloc_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_timestamp *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, /*data_ptr*/(u_int8_t *) buf, /*dxfer_len*/alloc_len, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_timestamp *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MAINTENANCE_OUT; scsi_cmd->service_action = SET_TIMESTAMP; scsi_ulto4b(alloc_len, scsi_cmd->length); } /* * Syncronize the media to the contents of the cache for * the given lba/count pair. Specifying 0/0 means sync * the whole cache. */ void scsi_synchronize_cache(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int32_t begin_lba, u_int16_t lb_count, u_int8_t sense_len, u_int32_t timeout) { struct scsi_sync_cache *scsi_cmd; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_NONE, tag_action, /*data_ptr*/NULL, /*dxfer_len*/0, sense_len, sizeof(*scsi_cmd), timeout); scsi_cmd = (struct scsi_sync_cache *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = SYNCHRONIZE_CACHE; scsi_ulto4b(begin_lba, scsi_cmd->begin_lba); scsi_ulto2b(lb_count, scsi_cmd->lb_count); } void scsi_read_write(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int readop, u_int8_t byte2, int minimum_cmd_size, u_int64_t lba, u_int32_t block_count, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { int read; u_int8_t cdb_len; read = (readop & SCSI_RW_DIRMASK) == SCSI_RW_READ; /* * Use the smallest possible command to perform the operation * as some legacy hardware does not support the 10 byte commands. * If any of the bits in byte2 is set, we have to go with a larger * command. */ if ((minimum_cmd_size < 10) && ((lba & 0x1fffff) == lba) && ((block_count & 0xff) == block_count) && (byte2 == 0)) { /* * We can fit in a 6 byte cdb. */ struct scsi_rw_6 *scsi_cmd; scsi_cmd = (struct scsi_rw_6 *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = read ? READ_6 : WRITE_6; scsi_ulto3b(lba, scsi_cmd->addr); scsi_cmd->length = block_count & 0xff; scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); CAM_DEBUG(csio->ccb_h.path, CAM_DEBUG_SUBTRACE, ("6byte: %x%x%x:%d:%d\n", scsi_cmd->addr[0], scsi_cmd->addr[1], scsi_cmd->addr[2], scsi_cmd->length, dxfer_len)); } else if ((minimum_cmd_size < 12) && ((block_count & 0xffff) == block_count) && ((lba & 0xffffffff) == lba)) { /* * Need a 10 byte cdb. */ struct scsi_rw_10 *scsi_cmd; scsi_cmd = (struct scsi_rw_10 *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = read ? READ_10 : WRITE_10; scsi_cmd->byte2 = byte2; scsi_ulto4b(lba, scsi_cmd->addr); scsi_cmd->reserved = 0; scsi_ulto2b(block_count, scsi_cmd->length); scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); CAM_DEBUG(csio->ccb_h.path, CAM_DEBUG_SUBTRACE, ("10byte: %x%x%x%x:%x%x: %d\n", scsi_cmd->addr[0], scsi_cmd->addr[1], scsi_cmd->addr[2], scsi_cmd->addr[3], scsi_cmd->length[0], scsi_cmd->length[1], dxfer_len)); } else if ((minimum_cmd_size < 16) && ((block_count & 0xffffffff) == block_count) && ((lba & 0xffffffff) == lba)) { /* * The block count is too big for a 10 byte CDB, use a 12 * byte CDB. */ struct scsi_rw_12 *scsi_cmd; scsi_cmd = (struct scsi_rw_12 *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = read ? READ_12 : WRITE_12; scsi_cmd->byte2 = byte2; scsi_ulto4b(lba, scsi_cmd->addr); scsi_cmd->reserved = 0; scsi_ulto4b(block_count, scsi_cmd->length); scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); CAM_DEBUG(csio->ccb_h.path, CAM_DEBUG_SUBTRACE, ("12byte: %x%x%x%x:%x%x%x%x: %d\n", scsi_cmd->addr[0], scsi_cmd->addr[1], scsi_cmd->addr[2], scsi_cmd->addr[3], scsi_cmd->length[0], scsi_cmd->length[1], scsi_cmd->length[2], scsi_cmd->length[3], dxfer_len)); } else { /* * 16 byte CDB. We'll only get here if the LBA is larger * than 2^32, or if the user asks for a 16 byte command. */ struct scsi_rw_16 *scsi_cmd; scsi_cmd = (struct scsi_rw_16 *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = read ? READ_16 : WRITE_16; scsi_cmd->byte2 = byte2; scsi_u64to8b(lba, scsi_cmd->addr); scsi_cmd->reserved = 0; scsi_ulto4b(block_count, scsi_cmd->length); scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); } cam_fill_csio(csio, retries, cbfcnp, (read ? CAM_DIR_IN : CAM_DIR_OUT) | ((readop & SCSI_RW_BIO) != 0 ? CAM_DATA_BIO : 0), tag_action, data_ptr, dxfer_len, sense_len, cdb_len, timeout); } void scsi_write_same(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t byte2, int minimum_cmd_size, u_int64_t lba, u_int32_t block_count, u_int8_t *data_ptr, u_int32_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { u_int8_t cdb_len; if ((minimum_cmd_size < 16) && ((block_count & 0xffff) == block_count) && ((lba & 0xffffffff) == lba)) { /* * Need a 10 byte cdb. */ struct scsi_write_same_10 *scsi_cmd; scsi_cmd = (struct scsi_write_same_10 *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = WRITE_SAME_10; scsi_cmd->byte2 = byte2; scsi_ulto4b(lba, scsi_cmd->addr); scsi_cmd->group = 0; scsi_ulto2b(block_count, scsi_cmd->length); scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); CAM_DEBUG(csio->ccb_h.path, CAM_DEBUG_SUBTRACE, ("10byte: %x%x%x%x:%x%x: %d\n", scsi_cmd->addr[0], scsi_cmd->addr[1], scsi_cmd->addr[2], scsi_cmd->addr[3], scsi_cmd->length[0], scsi_cmd->length[1], dxfer_len)); } else { /* * 16 byte CDB. We'll only get here if the LBA is larger * than 2^32, or if the user asks for a 16 byte command. */ struct scsi_write_same_16 *scsi_cmd; scsi_cmd = (struct scsi_write_same_16 *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = WRITE_SAME_16; scsi_cmd->byte2 = byte2; scsi_u64to8b(lba, scsi_cmd->addr); scsi_ulto4b(block_count, scsi_cmd->length); scsi_cmd->group = 0; scsi_cmd->control = 0; cdb_len = sizeof(*scsi_cmd); CAM_DEBUG(csio->ccb_h.path, CAM_DEBUG_SUBTRACE, ("16byte: %x%x%x%x%x%x%x%x:%x%x%x%x: %d\n", scsi_cmd->addr[0], scsi_cmd->addr[1], scsi_cmd->addr[2], scsi_cmd->addr[3], scsi_cmd->addr[4], scsi_cmd->addr[5], scsi_cmd->addr[6], scsi_cmd->addr[7], scsi_cmd->length[0], scsi_cmd->length[1], scsi_cmd->length[2], scsi_cmd->length[3], dxfer_len)); } cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, data_ptr, dxfer_len, sense_len, cdb_len, timeout); } void scsi_ata_identify(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t *data_ptr, u_int16_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { scsi_ata_pass(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*protocol*/AP_PROTO_PIO_IN, /*ata_flags*/AP_FLAG_TDIR_FROM_DEV | AP_FLAG_BYT_BLOK_BYTES | AP_FLAG_TLEN_SECT_CNT, /*features*/0, /*sector_count*/dxfer_len, /*lba*/0, /*command*/ATA_ATA_IDENTIFY, /*device*/ 0, /*icc*/ 0, /*auxiliary*/ 0, /*control*/0, data_ptr, dxfer_len, /*cdb_storage*/ NULL, /*cdb_storage_len*/ 0, /*minimum_cmd_size*/ 0, sense_len, timeout); } void scsi_ata_trim(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int16_t block_count, u_int8_t *data_ptr, u_int16_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { scsi_ata_pass_16(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, /*protocol*/AP_EXTEND|AP_PROTO_DMA, /*ata_flags*/AP_FLAG_TLEN_SECT_CNT|AP_FLAG_BYT_BLOK_BLOCKS, /*features*/ATA_DSM_TRIM, /*sector_count*/block_count, /*lba*/0, /*command*/ATA_DATA_SET_MANAGEMENT, /*control*/0, data_ptr, dxfer_len, sense_len, timeout); } int scsi_ata_read_log(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, uint32_t log_address, uint32_t page_number, uint16_t block_count, uint8_t protocol, uint8_t *data_ptr, uint32_t dxfer_len, uint8_t sense_len, uint32_t timeout) { uint8_t command, protocol_out; uint16_t count_out; uint64_t lba; int retval; retval = 0; switch (protocol) { case AP_PROTO_DMA: count_out = block_count; command = ATA_READ_LOG_DMA_EXT; protocol_out = AP_PROTO_DMA; break; case AP_PROTO_PIO_IN: default: count_out = block_count; command = ATA_READ_LOG_EXT; protocol_out = AP_PROTO_PIO_IN; break; } lba = (((uint64_t)page_number & 0xff00) << 32) | ((page_number & 0x00ff) << 8) | (log_address & 0xff); protocol_out |= AP_EXTEND; retval = scsi_ata_pass(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*protocol*/ protocol_out, /*ata_flags*/AP_FLAG_TLEN_SECT_CNT | AP_FLAG_BYT_BLOK_BLOCKS | AP_FLAG_TDIR_FROM_DEV, /*feature*/ 0, /*sector_count*/ count_out, /*lba*/ lba, /*command*/ command, /*device*/ 0, /*icc*/ 0, /*auxiliary*/ 0, /*control*/0, data_ptr, dxfer_len, /*cdb_storage*/ NULL, /*cdb_storage_len*/ 0, /*minimum_cmd_size*/ 0, sense_len, timeout); return (retval); } /* * Note! This is an unusual CDB building function because it can return * an error in the event that the command in question requires a variable * length CDB, but the caller has not given storage space for one or has not * given enough storage space. If there is enough space available in the * standard SCSI CCB CDB bytes, we'll prefer that over passed in storage. */ int scsi_ata_pass(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint32_t flags, uint8_t tag_action, uint8_t protocol, uint8_t ata_flags, uint16_t features, uint16_t sector_count, uint64_t lba, uint8_t command, uint8_t device, uint8_t icc, uint32_t auxiliary, uint8_t control, u_int8_t *data_ptr, uint32_t dxfer_len, uint8_t *cdb_storage, size_t cdb_storage_len, int minimum_cmd_size, u_int8_t sense_len, u_int32_t timeout) { uint32_t cam_flags; uint8_t *cdb_ptr; int cmd_size; int retval; uint8_t cdb_len; retval = 0; cam_flags = flags; /* * Round the user's request to the nearest command size that is at * least as big as what he requested. */ if (minimum_cmd_size <= 12) cmd_size = 12; else if (minimum_cmd_size > 16) cmd_size = 32; else cmd_size = 16; /* * If we have parameters that require a 48-bit ATA command, we have to * use the 16 byte ATA PASS-THROUGH command at least. */ if (((lba > ATA_MAX_28BIT_LBA) || (sector_count > 255) || (features > 255) || (protocol & AP_EXTEND)) && ((cmd_size < 16) || ((protocol & AP_EXTEND) == 0))) { if (cmd_size < 16) cmd_size = 16; protocol |= AP_EXTEND; } /* * The icc and auxiliary ATA registers are only supported in the * 32-byte version of the ATA PASS-THROUGH command. */ if ((icc != 0) || (auxiliary != 0)) { cmd_size = 32; protocol |= AP_EXTEND; } if ((cmd_size > sizeof(csio->cdb_io.cdb_bytes)) && ((cdb_storage == NULL) || (cdb_storage_len < cmd_size))) { retval = 1; goto bailout; } /* * At this point we know we have enough space to store the command * in one place or another. We prefer the built-in array, but used * the passed in storage if necessary. */ if (cmd_size <= sizeof(csio->cdb_io.cdb_bytes)) cdb_ptr = csio->cdb_io.cdb_bytes; else { cdb_ptr = cdb_storage; cam_flags |= CAM_CDB_POINTER; } if (cmd_size <= 12) { struct ata_pass_12 *cdb; cdb = (struct ata_pass_12 *)cdb_ptr; cdb_len = sizeof(*cdb); bzero(cdb, cdb_len); cdb->opcode = ATA_PASS_12; cdb->protocol = protocol; cdb->flags = ata_flags; cdb->features = features; cdb->sector_count = sector_count; cdb->lba_low = lba & 0xff; cdb->lba_mid = (lba >> 8) & 0xff; cdb->lba_high = (lba >> 16) & 0xff; cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA; cdb->command = command; cdb->control = control; } else if (cmd_size <= 16) { struct ata_pass_16 *cdb; cdb = (struct ata_pass_16 *)cdb_ptr; cdb_len = sizeof(*cdb); bzero(cdb, cdb_len); cdb->opcode = ATA_PASS_16; cdb->protocol = protocol; cdb->flags = ata_flags; cdb->features = features & 0xff; cdb->sector_count = sector_count & 0xff; cdb->lba_low = lba & 0xff; cdb->lba_mid = (lba >> 8) & 0xff; cdb->lba_high = (lba >> 16) & 0xff; /* * If AP_EXTEND is set, we're sending a 48-bit command. * Otherwise it's a 28-bit command. */ if (protocol & AP_EXTEND) { cdb->lba_low_ext = (lba >> 24) & 0xff; cdb->lba_mid_ext = (lba >> 32) & 0xff; cdb->lba_high_ext = (lba >> 40) & 0xff; cdb->features_ext = (features >> 8) & 0xff; cdb->sector_count_ext = (sector_count >> 8) & 0xff; cdb->device = device | ATA_DEV_LBA; } else { cdb->lba_low_ext = (lba >> 24) & 0xf; cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA; } cdb->command = command; cdb->control = control; } else { struct ata_pass_32 *cdb; uint8_t tmp_lba[8]; cdb = (struct ata_pass_32 *)cdb_ptr; cdb_len = sizeof(*cdb); bzero(cdb, cdb_len); cdb->opcode = VARIABLE_LEN_CDB; cdb->control = control; cdb->length = sizeof(*cdb) - __offsetof(struct ata_pass_32, service_action); scsi_ulto2b(ATA_PASS_32_SA, cdb->service_action); cdb->protocol = protocol; cdb->flags = ata_flags; if ((protocol & AP_EXTEND) == 0) { lba &= 0x0fffffff; cdb->device = ((lba >> 24) & 0xf) | ATA_DEV_LBA; features &= 0xff; sector_count &= 0xff; } else { cdb->device = device | ATA_DEV_LBA; } scsi_u64to8b(lba, tmp_lba); bcopy(&tmp_lba[2], cdb->lba, sizeof(cdb->lba)); scsi_ulto2b(features, cdb->features); scsi_ulto2b(sector_count, cdb->count); cdb->command = command; cdb->icc = icc; scsi_ulto4b(auxiliary, cdb->auxiliary); } cam_fill_csio(csio, retries, cbfcnp, cam_flags, tag_action, data_ptr, dxfer_len, sense_len, cmd_size, timeout); bailout: return (retval); } void scsi_ata_pass_16(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int32_t flags, u_int8_t tag_action, u_int8_t protocol, u_int8_t ata_flags, u_int16_t features, u_int16_t sector_count, uint64_t lba, u_int8_t command, u_int8_t control, u_int8_t *data_ptr, u_int16_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct ata_pass_16 *ata_cmd; ata_cmd = (struct ata_pass_16 *)&csio->cdb_io.cdb_bytes; ata_cmd->opcode = ATA_PASS_16; ata_cmd->protocol = protocol; ata_cmd->flags = ata_flags; ata_cmd->features_ext = features >> 8; ata_cmd->features = features; ata_cmd->sector_count_ext = sector_count >> 8; ata_cmd->sector_count = sector_count; ata_cmd->lba_low = lba; ata_cmd->lba_mid = lba >> 8; ata_cmd->lba_high = lba >> 16; ata_cmd->device = ATA_DEV_LBA; if (protocol & AP_EXTEND) { ata_cmd->lba_low_ext = lba >> 24; ata_cmd->lba_mid_ext = lba >> 32; ata_cmd->lba_high_ext = lba >> 40; } else ata_cmd->device |= (lba >> 24) & 0x0f; ata_cmd->command = command; ata_cmd->control = control; cam_fill_csio(csio, retries, cbfcnp, flags, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*ata_cmd), timeout); } void scsi_unmap(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_int8_t *data_ptr, u_int16_t dxfer_len, u_int8_t sense_len, u_int32_t timeout) { struct scsi_unmap *scsi_cmd; scsi_cmd = (struct scsi_unmap *)&csio->cdb_io.cdb_bytes; scsi_cmd->opcode = UNMAP; scsi_cmd->byte2 = byte2; scsi_ulto4b(0, scsi_cmd->reserved); scsi_cmd->group = 0; scsi_ulto2b(dxfer_len, scsi_cmd->length); scsi_cmd->control = 0; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_receive_diagnostic_results(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb*), uint8_t tag_action, int pcv, uint8_t page_code, uint8_t *data_ptr, uint16_t allocation_length, uint8_t sense_len, uint32_t timeout) { struct scsi_receive_diag *scsi_cmd; scsi_cmd = (struct scsi_receive_diag *)&csio->cdb_io.cdb_bytes; memset(scsi_cmd, 0, sizeof(*scsi_cmd)); scsi_cmd->opcode = RECEIVE_DIAGNOSTIC; if (pcv) { scsi_cmd->byte2 |= SRD_PCV; scsi_cmd->page_code = page_code; } scsi_ulto2b(allocation_length, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, data_ptr, allocation_length, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_send_diagnostic(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, int unit_offline, int device_offline, int self_test, int page_format, int self_test_code, uint8_t *data_ptr, uint16_t param_list_length, uint8_t sense_len, uint32_t timeout) { struct scsi_send_diag *scsi_cmd; scsi_cmd = (struct scsi_send_diag *)&csio->cdb_io.cdb_bytes; memset(scsi_cmd, 0, sizeof(*scsi_cmd)); scsi_cmd->opcode = SEND_DIAGNOSTIC; /* * The default self-test mode control and specific test * control are mutually exclusive. */ if (self_test) self_test_code = SSD_SELF_TEST_CODE_NONE; scsi_cmd->byte2 = ((self_test_code << SSD_SELF_TEST_CODE_SHIFT) & SSD_SELF_TEST_CODE_MASK) | (unit_offline ? SSD_UNITOFFL : 0) | (device_offline ? SSD_DEVOFFL : 0) | (self_test ? SSD_SELFTEST : 0) | (page_format ? SSD_PF : 0); scsi_ulto2b(param_list_length, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/param_list_length ? CAM_DIR_OUT : CAM_DIR_NONE, tag_action, data_ptr, param_list_length, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_read_buffer(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb*), uint8_t tag_action, int mode, uint8_t buffer_id, u_int32_t offset, uint8_t *data_ptr, uint32_t allocation_length, uint8_t sense_len, uint32_t timeout) { struct scsi_read_buffer *scsi_cmd; scsi_cmd = (struct scsi_read_buffer *)&csio->cdb_io.cdb_bytes; memset(scsi_cmd, 0, sizeof(*scsi_cmd)); scsi_cmd->opcode = READ_BUFFER; scsi_cmd->byte2 = mode; scsi_cmd->buffer_id = buffer_id; scsi_ulto3b(offset, scsi_cmd->offset); scsi_ulto3b(allocation_length, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, data_ptr, allocation_length, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_write_buffer(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, int mode, uint8_t buffer_id, u_int32_t offset, uint8_t *data_ptr, uint32_t param_list_length, uint8_t sense_len, uint32_t timeout) { struct scsi_write_buffer *scsi_cmd; scsi_cmd = (struct scsi_write_buffer *)&csio->cdb_io.cdb_bytes; memset(scsi_cmd, 0, sizeof(*scsi_cmd)); scsi_cmd->opcode = WRITE_BUFFER; scsi_cmd->byte2 = mode; scsi_cmd->buffer_id = buffer_id; scsi_ulto3b(offset, scsi_cmd->offset); scsi_ulto3b(param_list_length, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/param_list_length ? CAM_DIR_OUT : CAM_DIR_NONE, tag_action, data_ptr, param_list_length, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_start_stop(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, int start, int load_eject, int immediate, u_int8_t sense_len, u_int32_t timeout) { struct scsi_start_stop_unit *scsi_cmd; int extra_flags = 0; scsi_cmd = (struct scsi_start_stop_unit *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = START_STOP_UNIT; if (start != 0) { scsi_cmd->how |= SSS_START; /* it takes a lot of power to start a drive */ extra_flags |= CAM_HIGH_POWER; } if (load_eject != 0) scsi_cmd->how |= SSS_LOEJ; if (immediate != 0) scsi_cmd->byte2 |= SSS_IMMED; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_NONE | extra_flags, tag_action, /*data_ptr*/NULL, /*dxfer_len*/0, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_read_attribute(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, u_int8_t service_action, uint32_t element, u_int8_t elem_type, int logical_volume, int partition, u_int32_t first_attribute, int cache, u_int8_t *data_ptr, u_int32_t length, int sense_len, u_int32_t timeout) { struct scsi_read_attribute *scsi_cmd; scsi_cmd = (struct scsi_read_attribute *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = READ_ATTRIBUTE; scsi_cmd->service_action = service_action; scsi_ulto2b(element, scsi_cmd->element); scsi_cmd->elem_type = elem_type; scsi_cmd->logical_volume = logical_volume; scsi_cmd->partition = partition; scsi_ulto2b(first_attribute, scsi_cmd->first_attribute); scsi_ulto4b(length, scsi_cmd->length); if (cache != 0) scsi_cmd->cache |= SRA_CACHE; cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, /*data_ptr*/data_ptr, /*dxfer_len*/length, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_write_attribute(struct ccb_scsiio *csio, u_int32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), u_int8_t tag_action, uint32_t element, int logical_volume, int partition, int wtc, u_int8_t *data_ptr, u_int32_t length, int sense_len, u_int32_t timeout) { struct scsi_write_attribute *scsi_cmd; scsi_cmd = (struct scsi_write_attribute *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = WRITE_ATTRIBUTE; if (wtc != 0) scsi_cmd->byte2 = SWA_WTC; scsi_ulto3b(element, scsi_cmd->element); scsi_cmd->logical_volume = logical_volume; scsi_cmd->partition = partition; scsi_ulto4b(length, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, /*data_ptr*/data_ptr, /*dxfer_len*/length, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_persistent_reserve_in(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, int service_action, uint8_t *data_ptr, uint32_t dxfer_len, int sense_len, int timeout) { struct scsi_per_res_in *scsi_cmd; scsi_cmd = (struct scsi_per_res_in *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = PERSISTENT_RES_IN; scsi_cmd->action = service_action; scsi_ulto2b(dxfer_len, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_persistent_reserve_out(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, int service_action, int scope, int res_type, uint8_t *data_ptr, uint32_t dxfer_len, int sense_len, int timeout) { struct scsi_per_res_out *scsi_cmd; scsi_cmd = (struct scsi_per_res_out *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = PERSISTENT_RES_OUT; scsi_cmd->action = service_action; scsi_cmd->scope_type = scope | res_type; scsi_ulto4b(dxfer_len, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, /*data_ptr*/data_ptr, /*dxfer_len*/dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_security_protocol_in(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, uint32_t security_protocol, uint32_t security_protocol_specific, int byte4, uint8_t *data_ptr, uint32_t dxfer_len, int sense_len, int timeout) { struct scsi_security_protocol_in *scsi_cmd; scsi_cmd = (struct scsi_security_protocol_in *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = SECURITY_PROTOCOL_IN; scsi_cmd->security_protocol = security_protocol; scsi_ulto2b(security_protocol_specific, scsi_cmd->security_protocol_specific); scsi_cmd->byte4 = byte4; scsi_ulto4b(dxfer_len, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_security_protocol_out(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, uint32_t security_protocol, uint32_t security_protocol_specific, int byte4, uint8_t *data_ptr, uint32_t dxfer_len, int sense_len, int timeout) { struct scsi_security_protocol_out *scsi_cmd; scsi_cmd = (struct scsi_security_protocol_out *)&csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = SECURITY_PROTOCOL_OUT; scsi_cmd->security_protocol = security_protocol; scsi_ulto2b(security_protocol_specific, scsi_cmd->security_protocol_specific); scsi_cmd->byte4 = byte4; scsi_ulto4b(dxfer_len, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_OUT, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } void scsi_report_supported_opcodes(struct ccb_scsiio *csio, uint32_t retries, void (*cbfcnp)(struct cam_periph *, union ccb *), uint8_t tag_action, int options, int req_opcode, int req_service_action, uint8_t *data_ptr, uint32_t dxfer_len, int sense_len, int timeout) { struct scsi_report_supported_opcodes *scsi_cmd; scsi_cmd = (struct scsi_report_supported_opcodes *) &csio->cdb_io.cdb_bytes; bzero(scsi_cmd, sizeof(*scsi_cmd)); scsi_cmd->opcode = MAINTENANCE_IN; scsi_cmd->service_action = REPORT_SUPPORTED_OPERATION_CODES; scsi_cmd->options = options; scsi_cmd->requested_opcode = req_opcode; scsi_ulto2b(req_service_action, scsi_cmd->requested_service_action); scsi_ulto4b(dxfer_len, scsi_cmd->length); cam_fill_csio(csio, retries, cbfcnp, /*flags*/CAM_DIR_IN, tag_action, data_ptr, dxfer_len, sense_len, sizeof(*scsi_cmd), timeout); } /* * Try make as good a match as possible with * available sub drivers */ int scsi_inquiry_match(caddr_t inqbuffer, caddr_t table_entry) { struct scsi_inquiry_pattern *entry; struct scsi_inquiry_data *inq; entry = (struct scsi_inquiry_pattern *)table_entry; inq = (struct scsi_inquiry_data *)inqbuffer; if (((SID_TYPE(inq) == entry->type) || (entry->type == T_ANY)) && (SID_IS_REMOVABLE(inq) ? entry->media_type & SIP_MEDIA_REMOVABLE : entry->media_type & SIP_MEDIA_FIXED) && (cam_strmatch(inq->vendor, entry->vendor, sizeof(inq->vendor)) == 0) && (cam_strmatch(inq->product, entry->product, sizeof(inq->product)) == 0) && (cam_strmatch(inq->revision, entry->revision, sizeof(inq->revision)) == 0)) { return (0); } return (-1); } /* * Try make as good a match as possible with * available sub drivers */ int scsi_static_inquiry_match(caddr_t inqbuffer, caddr_t table_entry) { struct scsi_static_inquiry_pattern *entry; struct scsi_inquiry_data *inq; entry = (struct scsi_static_inquiry_pattern *)table_entry; inq = (struct scsi_inquiry_data *)inqbuffer; if (((SID_TYPE(inq) == entry->type) || (entry->type == T_ANY)) && (SID_IS_REMOVABLE(inq) ? entry->media_type & SIP_MEDIA_REMOVABLE : entry->media_type & SIP_MEDIA_FIXED) && (cam_strmatch(inq->vendor, entry->vendor, sizeof(inq->vendor)) == 0) && (cam_strmatch(inq->product, entry->product, sizeof(inq->product)) == 0) && (cam_strmatch(inq->revision, entry->revision, sizeof(inq->revision)) == 0)) { return (0); } return (-1); } /** * Compare two buffers of vpd device descriptors for a match. * * \param lhs Pointer to first buffer of descriptors to compare. * \param lhs_len The length of the first buffer. * \param rhs Pointer to second buffer of descriptors to compare. * \param rhs_len The length of the second buffer. * * \return 0 on a match, -1 otherwise. * * Treat rhs and lhs as arrays of vpd device id descriptors. Walk lhs matching * against each element in rhs until all data are exhausted or we have found * a match. */ int scsi_devid_match(uint8_t *lhs, size_t lhs_len, uint8_t *rhs, size_t rhs_len) { struct scsi_vpd_id_descriptor *lhs_id; struct scsi_vpd_id_descriptor *lhs_last; struct scsi_vpd_id_descriptor *rhs_last; uint8_t *lhs_end; uint8_t *rhs_end; lhs_end = lhs + lhs_len; rhs_end = rhs + rhs_len; /* * rhs_last and lhs_last are the last posible position of a valid * descriptor assuming it had a zero length identifier. We use * these variables to insure we can safely dereference the length * field in our loop termination tests. */ lhs_last = (struct scsi_vpd_id_descriptor *) (lhs_end - __offsetof(struct scsi_vpd_id_descriptor, identifier)); rhs_last = (struct scsi_vpd_id_descriptor *) (rhs_end - __offsetof(struct scsi_vpd_id_descriptor, identifier)); lhs_id = (struct scsi_vpd_id_descriptor *)lhs; while (lhs_id <= lhs_last && (lhs_id->identifier + lhs_id->length) <= lhs_end) { struct scsi_vpd_id_descriptor *rhs_id; rhs_id = (struct scsi_vpd_id_descriptor *)rhs; while (rhs_id <= rhs_last && (rhs_id->identifier + rhs_id->length) <= rhs_end) { if ((rhs_id->id_type & (SVPD_ID_ASSOC_MASK | SVPD_ID_TYPE_MASK)) == (lhs_id->id_type & (SVPD_ID_ASSOC_MASK | SVPD_ID_TYPE_MASK)) && rhs_id->length == lhs_id->length && memcmp(rhs_id->identifier, lhs_id->identifier, rhs_id->length) == 0) return (0); rhs_id = (struct scsi_vpd_id_descriptor *) (rhs_id->identifier + rhs_id->length); } lhs_id = (struct scsi_vpd_id_descriptor *) (lhs_id->identifier + lhs_id->length); } return (-1); } #ifdef _KERNEL int scsi_vpd_supported_page(struct cam_periph *periph, uint8_t page_id) { struct cam_ed *device; struct scsi_vpd_supported_pages *vpds; int i, num_pages; device = periph->path->device; vpds = (struct scsi_vpd_supported_pages *)device->supported_vpds; if (vpds != NULL) { num_pages = device->supported_vpds_len - SVPD_SUPPORTED_PAGES_HDR_LEN; for (i = 0; i < num_pages; i++) { if (vpds->page_list[i] == page_id) return (1); } } return (0); } static void init_scsi_delay(void) { int delay; delay = SCSI_DELAY; TUNABLE_INT_FETCH("kern.cam.scsi_delay", &delay); if (set_scsi_delay(delay) != 0) { printf("cam: invalid value for tunable kern.cam.scsi_delay\n"); set_scsi_delay(SCSI_DELAY); } } SYSINIT(scsi_delay, SI_SUB_TUNABLES, SI_ORDER_ANY, init_scsi_delay, NULL); static int sysctl_scsi_delay(SYSCTL_HANDLER_ARGS) { int error, delay; delay = scsi_delay; error = sysctl_handle_int(oidp, &delay, 0, req); if (error != 0 || req->newptr == NULL) return (error); return (set_scsi_delay(delay)); } SYSCTL_PROC(_kern_cam, OID_AUTO, scsi_delay, CTLTYPE_INT|CTLFLAG_RW, 0, 0, sysctl_scsi_delay, "I", "Delay to allow devices to settle after a SCSI bus reset (ms)"); static int set_scsi_delay(int delay) { /* * If someone sets this to 0, we assume that they want the * minimum allowable bus settle delay. */ if (delay == 0) { printf("cam: using minimum scsi_delay (%dms)\n", SCSI_MIN_DELAY); delay = SCSI_MIN_DELAY; } if (delay < SCSI_MIN_DELAY) return (EINVAL); scsi_delay = delay; return (0); } #endif /* _KERNEL */ Index: head/sys/cam/scsi/scsi_enc.h =================================================================== --- head/sys/cam/scsi/scsi_enc.h (revision 328069) +++ head/sys/cam/scsi/scsi_enc.h (revision 328070) @@ -1,221 +1,223 @@ /* $FreeBSD$ */ /*- + * SPDX-License-Identifier: (BSD-2-Clause-FreeBSD OR GPL-2.0) + * * Copyright (c) 2000 by Matthew Jacob * 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. * * Alternatively, this software may be distributed under the terms of the * the GNU Public License ("GPL"). * * 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. * */ #ifndef _SCSI_ENC_H_ #define _SCSI_ENC_H_ #include #define ENCIOC ('s' - 040) #define ENCIOC_GETNELM _IO(ENCIOC, 1) #define ENCIOC_GETELMMAP _IO(ENCIOC, 2) #define ENCIOC_GETENCSTAT _IO(ENCIOC, 3) #define ENCIOC_SETENCSTAT _IO(ENCIOC, 4) #define ENCIOC_GETELMSTAT _IO(ENCIOC, 5) #define ENCIOC_SETELMSTAT _IO(ENCIOC, 6) #define ENCIOC_GETTEXT _IO(ENCIOC, 7) #define ENCIOC_INIT _IO(ENCIOC, 8) #define ENCIOC_GETELMDESC _IO(ENCIOC, 9) #define ENCIOC_GETELMDEVNAMES _IO(ENCIOC, 10) #define ENCIOC_GETSTRING _IO(ENCIOC, 11) #define ENCIOC_SETSTRING _IO(ENCIOC, 12) #define ENCIOC_GETENCNAME _IO(ENCIOC, 13) #define ENCIOC_GETENCID _IO(ENCIOC, 14) /* * Platform Independent Definitions for enclosure devices. */ /* * SCSI Based Environmental Services Application Defines * * Based almost entirely on SCSI-3 ENC Revision 8A specification, * but slightly abstracted as the underlying device may in fact * be a SAF-TE or vendor unique device. */ /* * ENC Driver Operations: * (The defines themselves are platform and access method specific) * * ENCIOC_GETNELM * ENCIOC_GETELMMAP * ENCIOC_GETENCSTAT * ENCIOC_SETENCSTAT * ENCIOC_GETELMSTAT * ENCIOC_SETELMSTAT * ENCIOC_INIT * * * An application finds out how many elements an enclosure instance * is managing by performing a ENCIOC_GETNELM operation. It then * performs a ENCIOC_GETELMMAP to get the map that contains the * elment identifiers for all elements (see encioc_element_t below). * This information is static. * * The application may perform ENCIOC_GETELMSTAT operations to retrieve * status on an element (see the enc_elm_status_t structure below), * ENCIOC_SETELMSTAT operations to set status for an element. * * Similarly, overall enclosure status me be fetched or set via * ENCIOC_GETENCSTAT or ENCIOC_SETENCSTAT operations (see encioc_enc_status_t * below). * * Readers should note that there is nothing that requires either a set * or a clear operation to actually latch and do anything in the target. * * A ENCIOC_INIT operation causes the enclosure to be initialized. */ /* Element Types */ typedef enum { ELMTYP_UNSPECIFIED = 0x00, ELMTYP_DEVICE = 0x01, ELMTYP_POWER = 0x02, ELMTYP_FAN = 0x03, ELMTYP_THERM = 0x04, ELMTYP_DOORLOCK = 0x05, ELMTYP_ALARM = 0x06, ELMTYP_ESCC = 0x07, /* Enclosure SCC */ ELMTYP_SCC = 0x08, /* SCC */ ELMTYP_NVRAM = 0x09, ELMTYP_INV_OP_REASON = 0x0a, ELMTYP_UPS = 0x0b, ELMTYP_DISPLAY = 0x0c, ELMTYP_KEYPAD = 0x0d, ELMTYP_ENCLOSURE = 0x0e, ELMTYP_SCSIXVR = 0x0f, ELMTYP_LANGUAGE = 0x10, ELMTYP_COMPORT = 0x11, ELMTYP_VOM = 0x12, ELMTYP_AMMETER = 0x13, ELMTYP_SCSI_TGT = 0x14, ELMTYP_SCSI_INI = 0x15, ELMTYP_SUBENC = 0x16, ELMTYP_ARRAY_DEV = 0x17, ELMTYP_SAS_EXP = 0x18, /* SAS expander */ ELMTYP_SAS_CONN = 0x19 /* SAS connector */ } elm_type_t; typedef struct encioc_element { /* Element Index */ unsigned int elm_idx; /* ID of SubEnclosure containing Element*/ unsigned int elm_subenc_id; /* Element Type */ elm_type_t elm_type; } encioc_element_t; /* * Overall Enclosure Status */ typedef unsigned char encioc_enc_status_t; /* * Element Status */ typedef struct encioc_elm_status { unsigned int elm_idx; unsigned char cstat[4]; } encioc_elm_status_t; /* * ENC String structure, for StringIn and StringOut commands; use this with * the ENCIOC_GETSTRING and ENCIOC_SETSTRING ioctls. */ typedef struct encioc_string { size_t bufsiz; /* IN/OUT: length of string provided/returned */ #define ENC_STRING_MAX 0xffff uint8_t *buf; /* IN/OUT: string */ } encioc_string_t; /*============================================================================*/ /* * SES v2 r20 6.1.10 (pg 39) - Element Descriptor diagnostic page * Tables 21, 22, and 23 */ typedef struct encioc_elm_desc { unsigned int elm_idx; /* IN: elment requested */ uint16_t elm_desc_len; /* IN: buffer size; OUT: bytes written */ char *elm_desc_str; /* IN/OUT: buffer for descriptor data */ } encioc_elm_desc_t; /* * ENCIOC_GETELMDEVNAMES: * ioctl structure to get an element's device names, if available */ typedef struct encioc_elm_devnames { unsigned int elm_idx; /* IN: element index */ size_t elm_names_size;/* IN: size of elm_devnames */ size_t elm_names_len; /* OUT: actual size returned */ /* * IN/OUT: comma separated list of peripheral driver * instances servicing this element. */ char *elm_devnames; } encioc_elm_devnames_t; /* ioctl structure for requesting FC info for a port */ typedef struct encioc_elm_fc_port { unsigned int elm_idx; unsigned int port_idx; struct ses_elm_fc_port port_data; } encioc_elm_fc_port_t; /* ioctl structure for requesting SAS info for element phys */ typedef struct encioc_elm_sas_device_phy { unsigned int elm_idx; unsigned int phy_idx; struct ses_elm_sas_device_phy phy_data; } enioc_elm_sas_phy_t; /* ioctl structure for requesting SAS info for an expander phy */ typedef struct encioc_elm_sas_expander_phy { unsigned int elm_idx; unsigned int phy_idx; struct ses_elm_sas_expander_phy phy_data; } encioc_elm_sas_expander_phy_t; /* ioctl structure for requesting SAS info for a port phy */ typedef struct encioc_elm_sas_port_phy { unsigned int elm_idx; unsigned int phy_idx; struct ses_elm_sas_port_phy phy_data; } enioc_elm_sas_port_phy_t; /* ioctl structure for requesting additional status for an element */ typedef struct encioc_addl_status { unsigned int elm_idx; union ses_elm_addlstatus_descr_hdr addl_hdr; union ses_elm_addlstatus_proto_hdr proto_hdr; } enioc_addl_status_t; #endif /* _SCSI_ENC_H_ */ Index: head/sys/cam/scsi/scsi_ses.h =================================================================== --- head/sys/cam/scsi/scsi_ses.h (revision 328069) +++ head/sys/cam/scsi/scsi_ses.h (revision 328070) @@ -1,2442 +1,2444 @@ /* $FreeBSD$ */ /*- + * SPDX-License-Identifier: (BSD-2-Clause-FreeBSD OR GPL-2.0) + * * Copyright (c) 2000 by Matthew Jacob * 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. * * Alternatively, this software may be distributed under the terms of the * the GNU Public License ("GPL"). * * 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. * */ #ifndef _SCSI_SES_H_ #define _SCSI_SES_H_ #include /*========================== Field Extraction Macros =========================*/ #define MK_ENUM(S, F, SUFFIX) S ## _ ## F ## SUFFIX #define GEN_GETTER(LS, US, LF, UF) \ static inline int \ LS ## _get_ ## LF(struct LS *elem) { \ return ((elem->bytes[MK_ENUM(US,UF,_BYTE)] & MK_ENUM(US,UF,_MASK)) \ >> MK_ENUM(US,UF,_SHIFT)); \ } #define GEN_SETTER(LS, US, LF, UF) \ static inline void \ LS ## _set_ ## LF(struct LS *elem, int val) { \ elem->bytes[MK_ENUM(US,UF,_BYTE)] &= ~MK_ENUM(US,UF,_MASK); \ elem->bytes[MK_ENUM(US,UF,_BYTE)] |= \ (val << MK_ENUM(US,UF,_SHIFT)) & MK_ENUM(US,UF,_MASK); \ } #define GEN_HDR_GETTER(LS, US, LF, UF) \ static inline int \ LS ## _get_ ## LF(struct LS *page) { \ return ((page->hdr.page_specific_flags & MK_ENUM(US,UF,_MASK)) \ >> MK_ENUM(US,UF,_SHIFT)); \ } #define GEN_HDR_SETTER(LS, US, LF, UF) \ static inline void \ LS ## _set_ ## LF(struct LS *page, int val) { \ page->hdr.page_specific_flags &= ~MK_ENUM(US,UF,_MASK); \ page->hdr.page_specific_flags |= \ (val << MK_ENUM(US,UF,_SHIFT)) & MK_ENUM(US,UF,_MASK); \ } #define GEN_ACCESSORS(LS, US, LF, UF) \ GEN_GETTER(LS, US, LF, UF) \ GEN_SETTER(LS, US, LF, UF) #define GEN_HDR_ACCESSORS(LS, US, LF, UF) \ GEN_HDR_GETTER(LS, US, LF, UF) \ GEN_HDR_SETTER(LS, US, LF, UF) /*=============== Common SCSI ENC Diagnostic Page Structures ===============*/ struct ses_page_hdr { uint8_t page_code; uint8_t page_specific_flags; uint8_t length[2]; uint8_t gen_code[4]; }; static inline size_t ses_page_length(const struct ses_page_hdr *hdr) { /* * The page length as received only accounts for bytes that * follow the length field, namely starting with the generation * code field. */ return (scsi_2btoul(hdr->length) + offsetof(struct ses_page_hdr, gen_code)); } /*============= SCSI ENC Configuration Diagnostic Page Structures ============*/ struct ses_enc_desc { uint8_t byte0; /* * reserved0 : 1, * rel_id : 3, relative enclosure process id * reserved1 : 1, * num_procs : 3; number of enclosure procesenc */ uint8_t subenc_id; /* Sub-enclosure Identifier */ uint8_t num_types; /* # of supported types */ uint8_t length; /* Enclosure Descriptor Length */ uint8_t logical_id[8]; /* formerly wwn */ uint8_t vendor_id[8]; uint8_t product_id[16]; uint8_t product_rev[4]; uint8_t vendor_bytes[]; }; static inline uint8_t * ses_enc_desc_last_byte(struct ses_enc_desc *encdesc) { return (&encdesc->length + encdesc->length); } static inline struct ses_enc_desc * ses_enc_desc_next(struct ses_enc_desc *encdesc) { return ((struct ses_enc_desc *)(ses_enc_desc_last_byte(encdesc) + 1)); } static inline int ses_enc_desc_is_complete(struct ses_enc_desc *encdesc, uint8_t *last_buf_byte) { return (&encdesc->length <= last_buf_byte && ses_enc_desc_last_byte(encdesc) <= last_buf_byte); } struct ses_elm_type_desc { uint8_t etype_elm_type; /* type of element */ uint8_t etype_maxelt; /* maximum supported */ uint8_t etype_subenc; /* in sub-enclosure #n */ uint8_t etype_txt_len; /* Type Descriptor Text Length */ }; struct ses_cfg_page { struct ses_page_hdr hdr; struct ses_enc_desc subencs[]; /* type descriptors */ /* type text */ }; static inline int ses_cfg_page_get_num_subenc(struct ses_cfg_page *page) { return (page->hdr.page_specific_flags + 1); } /*================ SCSI SES Control Diagnostic Page Structures ==============*/ struct ses_ctrl_common { uint8_t bytes[1]; }; enum ses_ctrl_common_field_data { SES_CTRL_COMMON_SELECT_BYTE = 0, SES_CTRL_COMMON_SELECT_MASK = 0x80, SES_CTRL_COMMON_SELECT_SHIFT = 7, SES_CTRL_COMMON_PRDFAIL_BYTE = 0, SES_CTRL_COMMON_PRDFAIL_MASK = 0x40, SES_CTRL_COMMON_PRDFAIL_SHIFT = 6, SES_CTRL_COMMON_DISABLE_BYTE = 0, SES_CTRL_COMMON_DISABLE_MASK = 0x20, SES_CTRL_COMMON_DISABLE_SHIFT = 5, SES_CTRL_COMMON_RST_SWAP_BYTE = 0, SES_CTRL_COMMON_RST_SWAP_MASK = 0x10, SES_CTRL_COMMON_RST_SWAP_SHIFT = 4 }; #define GEN_SES_CTRL_COMMON_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_common, SES_CTRL_COMMON, LCASE, UCASE) GEN_SES_CTRL_COMMON_ACCESSORS(select, SELECT) GEN_SES_CTRL_COMMON_ACCESSORS(prdfail, PRDFAIL) GEN_SES_CTRL_COMMON_ACCESSORS(disable, DISABLE) GEN_SES_CTRL_COMMON_ACCESSORS(rst_swap, RST_SWAP) #undef GEN_SES_CTRL_COMMON_ACCESSORS /*------------------------ Device Slot Control Element ----------------------*/ struct ses_ctrl_dev_slot { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_dev_slot_field_data { SES_CTRL_DEV_SLOT_RQST_ACTIVE_BYTE = 1, SES_CTRL_DEV_SLOT_RQST_ACTIVE_MASK = 0x80, SES_CTRL_DEV_SLOT_RQST_ACTIVE_SHIFT = 7, SES_CTRL_DEV_SLOT_DO_NOT_REMOVE_BYTE = 1, SES_CTRL_DEV_SLOT_DO_NOT_REMOVE_MASK = 0x40, SES_CTRL_DEV_SLOT_DO_NOT_REMOVE_SHIFT = 6, SES_CTRL_DEV_SLOT_RQST_MISSING_BYTE = 1, SES_CTRL_DEV_SLOT_RQST_MISSING_MASK = 0x10, SES_CTRL_DEV_SLOT_RQST_MISSING_SHIFT = 4, SES_CTRL_DEV_SLOT_RQST_INSERT_BYTE = 1, SES_CTRL_DEV_SLOT_RQST_INSERT_MASK = 0x08, SES_CTRL_DEV_SLOT_RQST_INSERT_SHIFT = 3, SES_CTRL_DEV_SLOT_RQST_REMOVE_BYTE = 1, SES_CTRL_DEV_SLOT_RQST_REMOVE_MASK = 0x04, SES_CTRL_DEV_SLOT_RQST_REMOVE_SHIFT = 2, SES_CTRL_DEV_SLOT_RQST_IDENT_BYTE = 1, SES_CTRL_DEV_SLOT_RQST_IDENT_MASK = 0x02, SES_CTRL_DEV_SLOT_RQST_IDENT_SHIFT = 1, SES_CTRL_DEV_SLOT_RQST_FAULT_BYTE = 2, SES_CTRL_DEV_SLOT_RQST_FAULT_MASK = 0x20, SES_CTRL_DEV_SLOT_RQST_FAULT_SHIFT = 5, SES_CTRL_DEV_SLOT_DEVICE_OFF_BYTE = 2, SES_CTRL_DEV_SLOT_DEVICE_OFF_MASK = 0x10, SES_CTRL_DEV_SLOT_DEVICE_OFF_SHIFT = 4, SES_CTRL_DEV_SLOT_ENABLE_BYP_A_BYTE = 2, SES_CTRL_DEV_SLOT_ENABLE_BYP_A_MASK = 0x08, SES_CTRL_DEV_SLOT_ENABLE_BYP_A_SHIFT = 3, SES_CTRL_DEV_SLOT_ENABLE_BYP_B_BYTE = 2, SES_CTRL_DEV_SLOT_ENABLE_BYP_B_MASK = 0x04, SES_CTRL_DEV_SLOT_ENABLE_BYP_B_SHIFT = 2 }; #define GEN_SES_CTRL_DEV_SLOT_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_dev_slot, SES_CTRL_DEV_SLOT, LCASE, UCASE) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(rqst_active, RQST_ACTIVE) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(do_not_remove, DO_NOT_REMOVE) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(rqst_missing, RQST_MISSING) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(rqst_insert, RQST_INSERT) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(rqst_remove, RQST_REMOVE) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(rqst_fault, RQST_FAULT) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(device_off, DEVICE_OFF) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(enable_byp_a, ENABLE_BYP_A) GEN_SES_CTRL_DEV_SLOT_ACCESSORS(enable_byp_b, ENABLE_BYP_B) #undef GEN_SES_CTRL_DEV_SLOT_ACCESSORS /*--------------------- Array Device Slot Control Element --------------------*/ struct ses_ctrl_array_dev_slot { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_array_dev_slot_field_data { SES_CTRL_ARRAY_DEV_SLOT_RQST_OK_BYTE = 0, SES_CTRL_ARRAY_DEV_SLOT_RQST_OK_MASK = 0x80, SES_CTRL_ARRAY_DEV_SLOT_RQST_OK_SHIFT = 7, SES_CTRL_ARRAY_DEV_SLOT_RQST_RSVD_DEVICE_BYTE = 0, SES_CTRL_ARRAY_DEV_SLOT_RQST_RSVD_DEVICE_MASK = 0x40, SES_CTRL_ARRAY_DEV_SLOT_RQST_RSVD_DEVICE_SHIFT = 6, SES_CTRL_ARRAY_DEV_SLOT_RQST_HOT_SPARE_BYTE = 0, SES_CTRL_ARRAY_DEV_SLOT_RQST_HOT_SPARE_MASK = 0x20, SES_CTRL_ARRAY_DEV_SLOT_RQST_HOT_SPARE_SHIFT = 5, SES_CTRL_ARRAY_DEV_SLOT_RQST_CONS_CHECK_BYTE = 0, SES_CTRL_ARRAY_DEV_SLOT_RQST_CONS_CHECK_MASK = 0x10, SES_CTRL_ARRAY_DEV_SLOT_RQST_CONS_CHECK_SHIFT = 4, SES_CTRL_ARRAY_DEV_SLOT_RQST_IN_CRIT_ARRAY_BYTE = 0, SES_CTRL_ARRAY_DEV_SLOT_RQST_IN_CRIT_ARRAY_MASK = 0x08, SES_CTRL_ARRAY_DEV_SLOT_RQST_IN_CRIT_ARRAY_SHIFT = 3, SES_CTRL_ARRAY_DEV_SLOT_RQST_IN_FAILED_ARRAY_BYTE = 0, SES_CTRL_ARRAY_DEV_SLOT_RQST_IN_FAILED_ARRAY_MASK = 0x04, SES_CTRL_ARRAY_DEV_SLOT_RQST_IN_FAILED_ARRAY_SHIFT = 2, SES_CTRL_ARRAY_DEV_SLOT_RQST_REBUILD_REMAP_BYTE = 0, SES_CTRL_ARRAY_DEV_SLOT_RQST_REBUILD_REMAP_MASK = 0x02, SES_CTRL_ARRAY_DEV_SLOT_RQST_REBUILD_REMAP_SHIFT = 1, SES_CTRL_ARRAY_DEV_SLOT_RQST_REBUILD_REMAP_ABORT_BYTE = 0, SES_CTRL_ARRAY_DEV_SLOT_RQST_REBUILD_REMAP_ABORT_MASK = 0x01, SES_CTRL_ARRAY_DEV_SLOT_RQST_REBUILD_REMAP_ABORT_SHIFT = 0 /* * The remaining fields are identical to the device * slot element type. Access them through the device slot * element type and its accessors. */ }; #define GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_array_dev_slot, SES_CTRL_ARRAY_DEV_SLOT, \ LCASE, UCASE) GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(rqst_ok, RQST_OK) GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(rqst_rsvd_device, RQST_RSVD_DEVICE) GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(rqst_hot_spare, RQST_HOT_SPARE) GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(rqst_cons_check, RQST_CONS_CHECK) GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(rqst_in_crit_array, RQST_IN_CRIT_ARRAY) GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(rqst_in_failed_array, RQST_IN_FAILED_ARRAY) GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(rqst_rebuild_remap, RQST_REBUILD_REMAP) GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS(rqst_rebuild_remap_abort, RQST_REBUILD_REMAP_ABORT) #undef GEN_SES_CTRL_ARRAY_DEV_SLOT_ACCESSORS /*----------------------- Power Supply Control Element -----------------------*/ struct ses_ctrl_power_supply { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_power_supply_field_data { SES_CTRL_POWER_SUPPLY_RQST_IDENT_BYTE = 0, SES_CTRL_POWER_SUPPLY_RQST_IDENT_MASK = 0x80, SES_CTRL_POWER_SUPPLY_RQST_IDENT_SHIFT = 7, SES_CTRL_POWER_SUPPLY_RQST_FAIL_BYTE = 2, SES_CTRL_POWER_SUPPLY_RQST_FAIL_MASK = 0x40, SES_CTRL_POWER_SUPPLY_RQST_FAIL_SHIFT = 6, SES_CTRL_POWER_SUPPLY_RQST_ON_BYTE = 2, SES_CTRL_POWER_SUPPLY_RQST_ON_MASK = 0x20, SES_CTRL_POWER_SUPPLY_RQST_ON_SHIFT = 5 }; #define GEN_SES_CTRL_POWER_SUPPLY_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_power_supply, SES_CTRL_POWER_SUPPLY, LCASE, UCASE) GEN_SES_CTRL_POWER_SUPPLY_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_POWER_SUPPLY_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_POWER_SUPPLY_ACCESSORS(rqst_on, RQST_ON) #undef GEN_SES_CTRL_POWER_SUPPLY_ACCESSORS /*-------------------------- Cooling Control Element -------------------------*/ struct ses_ctrl_cooling { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_cooling_field_data { SES_CTRL_COOLING_RQST_IDENT_BYTE = 0, SES_CTRL_COOLING_RQST_IDENT_MASK = 0x80, SES_CTRL_COOLING_RQST_IDENT_SHIFT = 7, SES_CTRL_COOLING_RQST_FAIL_BYTE = 2, SES_CTRL_COOLING_RQST_FAIL_MASK = 0x40, SES_CTRL_COOLING_RQST_FAIL_SHIFT = 6, SES_CTRL_COOLING_RQST_ON_BYTE = 2, SES_CTRL_COOLING_RQST_ON_MASK = 0x20, SES_CTRL_COOLING_RQST_ON_SHIFT = 5, SES_CTRL_COOLING_RQSTED_SPEED_CODE_BYTE = 2, SES_CTRL_COOLING_RQSTED_SPEED_CODE_MASK = 0x07, SES_CTRL_COOLING_RQSTED_SPEED_CODE_SHIFT = 2, SES_CTRL_COOLING_RQSTED_SPEED_CODE_UNCHANGED = 0x00, SES_CTRL_COOLING_RQSTED_SPEED_CODE_LOWEST = 0x01, SES_CTRL_COOLING_RQSTED_SPEED_CODE_HIGHEST = 0x07 }; #define GEN_SES_CTRL_COOLING_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_cooling, SES_CTRL_COOLING, LCASE, UCASE) GEN_SES_CTRL_COOLING_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_COOLING_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_COOLING_ACCESSORS(rqst_on, RQST_ON) GEN_SES_CTRL_COOLING_ACCESSORS(rqsted_speed_code, RQSTED_SPEED_CODE) #undef GEN_SES_CTRL_COOLING_ACCESSORS /*-------------------- Temperature Sensor Control Element --------------------*/ struct ses_ctrl_temp_sensor { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_temp_sensor_field_data { SES_CTRL_TEMP_SENSOR_RQST_IDENT_BYTE = 0, SES_CTRL_TEMP_SENSOR_RQST_IDENT_MASK = 0x80, SES_CTRL_TEMP_SENSOR_RQST_IDENT_SHIFT = 7, SES_CTRL_TEMP_SENSOR_RQST_FAIL_BYTE = 0, SES_CTRL_TEMP_SENSOR_RQST_FAIL_MASK = 0x40, SES_CTRL_TEMP_SENSOR_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_TEMP_SENSOR_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_temp_sensor, SES_CTRL_TEMP_SENSOR, LCASE, UCASE) GEN_SES_CTRL_TEMP_SENSOR_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_TEMP_SENSOR_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_TEMP_SENSOR_ACCESSORS /*------------------------- Door Lock Control Element ------------------------*/ struct ses_ctrl_door_lock { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_door_lock_field_data { SES_CTRL_DOOR_LOCK_RQST_IDENT_BYTE = 0, SES_CTRL_DOOR_LOCK_RQST_IDENT_MASK = 0x80, SES_CTRL_DOOR_LOCK_RQST_IDENT_SHIFT = 7, SES_CTRL_DOOR_LOCK_RQST_FAIL_BYTE = 0, SES_CTRL_DOOR_LOCK_RQST_FAIL_MASK = 0x40, SES_CTRL_DOOR_LOCK_RQST_FAIL_SHIFT = 6, SES_CTRL_DOOR_LOCK_UNLOCK_BYTE = 2, SES_CTRL_DOOR_LOCK_UNLOCK_MASK = 0x01, SES_CTRL_DOOR_LOCK_UNLOCK_SHIFT = 0 }; #define GEN_SES_CTRL_DOOR_LOCK_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_door_lock, SES_CTRL_DOOR_LOCK, LCASE, UCASE) GEN_SES_CTRL_DOOR_LOCK_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_DOOR_LOCK_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_DOOR_LOCK_ACCESSORS(unlock, UNLOCK) #undef GEN_SES_CTRL_DOOR_LOCK_ACCESSORS /*----------------------- Audible Alarm Control Element ----------------------*/ struct ses_ctrl_audible_alarm { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_audible_alarm_field_data { SES_CTRL_AUDIBLE_ALARM_RQST_IDENT_BYTE = 0, SES_CTRL_AUDIBLE_ALARM_RQST_IDENT_MASK = 0x80, SES_CTRL_AUDIBLE_ALARM_RQST_IDENT_SHIFT = 7, SES_CTRL_AUDIBLE_ALARM_RQST_FAIL_BYTE = 0, SES_CTRL_AUDIBLE_ALARM_RQST_FAIL_MASK = 0x40, SES_CTRL_AUDIBLE_ALARM_RQST_FAIL_SHIFT = 6, SES_CTRL_AUDIBLE_ALARM_SET_MUTE_BYTE = 2, SES_CTRL_AUDIBLE_ALARM_SET_MUTE_MASK = 0x40, SES_CTRL_AUDIBLE_ALARM_SET_MUTE_SHIFT = 6, SES_CTRL_AUDIBLE_ALARM_SET_REMIND_BYTE = 2, SES_CTRL_AUDIBLE_ALARM_SET_REMIND_MASK = 0x10, SES_CTRL_AUDIBLE_ALARM_SET_REMIND_SHIFT = 4, SES_CTRL_AUDIBLE_ALARM_TONE_CONTROL_BYTE = 2, SES_CTRL_AUDIBLE_ALARM_TONE_CONTROL_MASK = 0x0F, SES_CTRL_AUDIBLE_ALARM_TONE_CONTROL_SHIFT = 0, SES_CTRL_AUDIBLE_ALARM_TONE_CONTROL_INFO = 0x08, SES_CTRL_AUDIBLE_ALARM_TONE_CONTROL_NON_CRIT = 0x04, SES_CTRL_AUDIBLE_ALARM_TONE_CONTROL_CRIT = 0x02, SES_CTRL_AUDIBLE_ALARM_TONE_CONTROL_UNRECOV = 0x01 }; #define GEN_SES_CTRL_AUDIBLE_ALARM_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_audible_alarm, SES_CTRL_AUDIBLE_ALARM, LCASE, UCASE) GEN_SES_CTRL_AUDIBLE_ALARM_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_AUDIBLE_ALARM_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_AUDIBLE_ALARM_ACCESSORS(set_mute, SET_MUTE) GEN_SES_CTRL_AUDIBLE_ALARM_ACCESSORS(set_remind, SET_REMIND) GEN_SES_CTRL_AUDIBLE_ALARM_ACCESSORS(tone_control, TONE_CONTROL) #undef GEN_SES_CTRL_AUDIBLE_ALARM_ACCESSORS /*--------- Enclosure Services Controller Electronics Control Element --------*/ struct ses_ctrl_ecc_electronics { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_ecc_electronics_field_data { SES_CTRL_ECC_ELECTRONICS_RQST_IDENT_BYTE = 0, SES_CTRL_ECC_ELECTRONICS_RQST_IDENT_MASK = 0x80, SES_CTRL_ECC_ELECTRONICS_RQST_IDENT_SHIFT = 7, SES_CTRL_ECC_ELECTRONICS_RQST_FAIL_BYTE = 0, SES_CTRL_ECC_ELECTRONICS_RQST_FAIL_MASK = 0x40, SES_CTRL_ECC_ELECTRONICS_RQST_FAIL_SHIFT = 6, SES_CTRL_ECC_ELECTRONICS_SELECT_ELEMENT_BYTE = 1, SES_CTRL_ECC_ELECTRONICS_SELECT_ELEMENT_MASK = 0x01, SES_CTRL_ECC_ELECTRONICS_SELECT_ELEMENT_SHIFT = 0 }; #define GEN_SES_CTRL_ECC_ELECTRONICS_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_ecc_electronics, SES_CTRL_ECC_ELECTRONICS, \ LCASE, UCASE) GEN_SES_CTRL_ECC_ELECTRONICS_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_ECC_ELECTRONICS_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_ECC_ELECTRONICS_ACCESSORS(select_element, SELECT_ELEMENT) #undef GEN_SES_CTRL_ECC_ELECTRONICS_ACCESSORS /*----------- SCSI Services Controller Electronics Control Element -----------*/ struct ses_ctrl_scc_electronics { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_scc_electronics_field_data { SES_CTRL_SCC_ELECTRONICS_RQST_IDENT_BYTE = 0, SES_CTRL_SCC_ELECTRONICS_RQST_IDENT_MASK = 0x80, SES_CTRL_SCC_ELECTRONICS_RQST_IDENT_SHIFT = 7, SES_CTRL_SCC_ELECTRONICS_RQST_FAIL_BYTE = 0, SES_CTRL_SCC_ELECTRONICS_RQST_FAIL_MASK = 0x40, SES_CTRL_SCC_ELECTRONICS_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_SCC_ELECTRONICS_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_scc_electronics, SES_CTRL_SCC_ELECTRONICS, \ LCASE, UCASE) GEN_SES_CTRL_SCC_ELECTRONICS_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_SCC_ELECTRONICS_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_SCC_ELECTRONICS_ACCESSORS /*--------------------- Nonvolatile Cache Control Element --------------------*/ struct ses_ctrl_nv_cache { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_nv_cache_field_data { SES_CTRL_NV_CACHE_RQST_IDENT_BYTE = 0, SES_CTRL_NV_CACHE_RQST_IDENT_MASK = 0x80, SES_CTRL_NV_CACHE_RQST_IDENT_SHIFT = 7, SES_CTRL_NV_CACHE_RQST_FAIL_BYTE = 0, SES_CTRL_NV_CACHE_RQST_FAIL_MASK = 0x40, SES_CTRL_NV_CACHE_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_NV_CACHE_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_nv_cache, SES_CTRL_NV_CACHE, LCASE, UCASE) GEN_SES_CTRL_NV_CACHE_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_NV_CACHE_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_NV_CACHE_ACCESSORS /*----------------- Invalid Operation Reason Control Element -----------------*/ struct ses_ctrl_invalid_op_reason { struct ses_ctrl_common common; uint8_t bytes[3]; }; /* There are no element specific fields currently defined in the spec. */ /*--------------- Uninterruptible Power Supply Control Element ---------------*/ struct ses_ctrl_ups { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_ups_field_data { SES_CTRL_UPS_RQST_IDENT_BYTE = 2, SES_CTRL_UPS_RQST_IDENT_MASK = 0x80, SES_CTRL_UPS_RQST_IDENT_SHIFT = 7, SES_CTRL_UPS_RQST_FAIL_BYTE = 2, SES_CTRL_UPS_RQST_FAIL_MASK = 0x40, SES_CTRL_UPS_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_UPS_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_ups, SES_CTRL_UPS, LCASE, UCASE) GEN_SES_CTRL_UPS_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_UPS_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_UPS_ACCESSORS /*-------------------------- Display Control Element -------------------------*/ struct ses_ctrl_display { struct ses_ctrl_common common; uint8_t bytes[1]; uint8_t display_character[2]; }; enum ses_ctrl_display_field_data { SES_CTRL_DISPLAY_RQST_IDENT_BYTE = 0, SES_CTRL_DISPLAY_RQST_IDENT_MASK = 0x80, SES_CTRL_DISPLAY_RQST_IDENT_SHIFT = 7, SES_CTRL_DISPLAY_RQST_FAIL_BYTE = 0, SES_CTRL_DISPLAY_RQST_FAIL_MASK = 0x40, SES_CTRL_DISPLAY_RQST_FAIL_SHIFT = 6, SES_CTRL_DISPLAY_DISPLAY_MODE_BYTE = 0, SES_CTRL_DISPLAY_DISPLAY_MODE_MASK = 0x03, SES_CTRL_DISPLAY_DISPLAY_MODE_SHIFT = 6, SES_CTRL_DISPLAY_DISPLAY_MODE_UNCHANGED = 0x0, SES_CTRL_DISPLAY_DISPLAY_MODE_ESP = 0x1, SES_CTRL_DISPLAY_DISPLAY_MODE_DC_FIELD = 0x2 }; #define GEN_SES_CTRL_DISPLAY_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_display, SES_CTRL_DISPLAY, LCASE, UCASE) GEN_SES_CTRL_DISPLAY_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_DISPLAY_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_DISPLAY_ACCESSORS(display_mode, DISPLAY_MODE) #undef GEN_SES_CTRL_DISPLAY_ACCESSORS /*----------------------- Key Pad Entry Control Element ----------------------*/ struct ses_ctrl_key_pad_entry { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_key_pad_entry_field_data { SES_CTRL_KEY_PAD_ENTRY_RQST_IDENT_BYTE = 0, SES_CTRL_KEY_PAD_ENTRY_RQST_IDENT_MASK = 0x80, SES_CTRL_KEY_PAD_ENTRY_RQST_IDENT_SHIFT = 7, SES_CTRL_KEY_PAD_ENTRY_RQST_FAIL_BYTE = 0, SES_CTRL_KEY_PAD_ENTRY_RQST_FAIL_MASK = 0x40, SES_CTRL_KEY_PAD_ENTRY_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_KEY_PAD_ENTRY_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_key_pad_entry, SES_CTRL_KEY_PAD_ENTRY, LCASE, UCASE) GEN_SES_CTRL_KEY_PAD_ENTRY_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_KEY_PAD_ENTRY_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_KEY_PAD_ENTRY_ACCESSORS /*------------------------- Enclosure Control Element ------------------------*/ struct ses_ctrl_enclosure { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_enclosure_field_data { SES_CTRL_ENCLOSURE_RQST_IDENT_BYTE = 0, SES_CTRL_ENCLOSURE_RQST_IDENT_MASK = 0x80, SES_CTRL_ENCLOSURE_RQST_IDENT_SHIFT = 7, SES_CTRL_ENCLOSURE_POWER_CYCLE_RQST_BYTE = 1, SES_CTRL_ENCLOSURE_POWER_CYCLE_RQST_MASK = 0xC0, SES_CTRL_ENCLOSURE_POWER_CYCLE_RQST_SHIFT = 6, SES_CTRL_ENCLOSURE_POWER_CYCLE_RQST_NONE = 0x0, SES_CTRL_ENCLOSURE_POWER_CYCLE_RQST_AFTER_DELAY = 0x1, SES_CTRL_ENCLOSURE_POWER_CYCLE_RQST_CANCEL = 0x2, SES_CTRL_ENCLOSURE_POWER_CYCLE_DELAY_BYTE = 1, SES_CTRL_ENCLOSURE_POWER_CYCLE_DELAY_MASK = 0x3F, SES_CTRL_ENCLOSURE_POWER_CYCLE_DELAY_SHIFT = 0, SES_CTRL_ENCLOSURE_POWER_CYCLE_DELAY_MAX = 60,/*minutes*/ SES_CTRL_ENCLOSURE_POWER_OFF_DURATION_BYTE = 2, SES_CTRL_ENCLOSURE_POWER_OFF_DURATION_MASK = 0xFC, SES_CTRL_ENCLOSURE_POWER_OFF_DURATION_SHIFT = 2, SES_CTRL_ENCLOSURE_POWER_OFF_DURATION_MAX_AUTO = 60, SES_CTRL_ENCLOSURE_POWER_OFF_DURATION_MANUAL = 63, SES_CTRL_ENCLOSURE_RQST_FAIL_BYTE = 2, SES_CTRL_ENCLOSURE_RQST_FAIL_MASK = 0x02, SES_CTRL_ENCLOSURE_RQST_FAIL_SHIFT = 1, SES_CTRL_ENCLOSURE_RQST_WARN_BYTE = 2, SES_CTRL_ENCLOSURE_RQST_WARN_MASK = 0x01, SES_CTRL_ENCLOSURE_RQST_WARN_SHIFT = 0 }; #define GEN_SES_CTRL_ENCLOSURE_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_enclosure, SES_CTRL_ENCLOSURE, LCASE, UCASE) GEN_SES_CTRL_ENCLOSURE_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_ENCLOSURE_ACCESSORS(power_cycle_rqst, POWER_CYCLE_RQST) GEN_SES_CTRL_ENCLOSURE_ACCESSORS(power_cycle_delay, POWER_CYCLE_DELAY) GEN_SES_CTRL_ENCLOSURE_ACCESSORS(power_off_duration, POWER_OFF_DURATION) GEN_SES_CTRL_ENCLOSURE_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_ENCLOSURE_ACCESSORS(rqst_warn, RQST_WARN) #undef GEN_SES_CTRL_ENCLOSURE_ACCESSORS /*------------------- SCSI Port/Transceiver Control Element ------------------*/ struct ses_ctrl_scsi_port_or_xcvr { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_scsi_port_or_xcvr_field_data { SES_CTRL_SCSI_PORT_OR_XCVR_RQST_IDENT_BYTE = 0, SES_CTRL_SCSI_PORT_OR_XCVR_RQST_IDENT_MASK = 0x80, SES_CTRL_SCSI_PORT_OR_XCVR_RQST_IDENT_SHIFT = 7, SES_CTRL_SCSI_PORT_OR_XCVR_RQST_FAIL_BYTE = 0, SES_CTRL_SCSI_PORT_OR_XCVR_RQST_FAIL_MASK = 0x40, SES_CTRL_SCSI_PORT_OR_XCVR_RQST_FAIL_SHIFT = 6, SES_CTRL_SCSI_PORT_OR_XCVR_DISABLE_BYTE = 2, SES_CTRL_SCSI_PORT_OR_XCVR_DISABLE_MASK = 0x10, SES_CTRL_SCSI_PORT_OR_XCVR_DISABLE_SHIFT = 4 }; #define GEN_SES_CTRL_SCSI_PORT_OR_XCVR_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_scsi_port_or_xcvr, SES_CTRL_SCSI_PORT_OR_XCVR,\ LCASE, UCASE) GEN_SES_CTRL_SCSI_PORT_OR_XCVR_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_SCSI_PORT_OR_XCVR_ACCESSORS(disable, DISABLE) GEN_SES_CTRL_SCSI_PORT_OR_XCVR_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_SCSI_PORT_OR_XCVR_ACCESSORS /*------------------------- Language Control Element -------------------------*/ struct ses_ctrl_language { struct ses_ctrl_common common; uint8_t bytes[1]; uint8_t language_code[2]; }; enum ses_ctrl_language_field_data { SES_CTRL_LANGUAGE_RQST_IDENT_BYTE = 0, SES_CTRL_LANGUAGE_RQST_IDENT_MASK = 0x80, SES_CTRL_LANGUAGE_RQST_IDENT_SHIFT = 7 }; #define GEN_SES_CTRL_LANGUAGE_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_language, SES_CTRL_LANGUAGE, LCASE, UCASE) GEN_SES_CTRL_LANGUAGE_ACCESSORS(rqst_ident, RQST_IDENT) #undef GEN_SES_CTRL_LANGUAGE_ACCESSORS /*-------------------- Communication Port Control Element --------------------*/ struct ses_ctrl_comm_port { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_comm_port_field_data { SES_CTRL_COMM_PORT_RQST_IDENT_BYTE = 0, SES_CTRL_COMM_PORT_RQST_IDENT_MASK = 0x80, SES_CTRL_COMM_PORT_RQST_IDENT_SHIFT = 7, SES_CTRL_COMM_PORT_RQST_FAIL_BYTE = 0, SES_CTRL_COMM_PORT_RQST_FAIL_MASK = 0x40, SES_CTRL_COMM_PORT_RQST_FAIL_SHIFT = 6, SES_CTRL_COMM_PORT_DISABLE_BYTE = 2, SES_CTRL_COMM_PORT_DISABLE_MASK = 0x01, SES_CTRL_COMM_PORT_DISABLE_SHIFT = 0 }; #define GEN_SES_CTRL_COMM_PORT_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_comm_port, SES_CTRL_COMM_PORT, LCASE, UCASE) GEN_SES_CTRL_COMM_PORT_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_COMM_PORT_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_COMM_PORT_ACCESSORS(disable, DISABLE) #undef GEN_SES_CTRL_COMM_PORT_ACCESSORS /*---------------------- Voltage Sensor Control Element ----------------------*/ struct ses_ctrl_voltage_sensor { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_voltage_sensor_field_data { SES_CTRL_VOLTAGE_SENSOR_RQST_IDENT_BYTE = 0, SES_CTRL_VOLTAGE_SENSOR_RQST_IDENT_MASK = 0x80, SES_CTRL_VOLTAGE_SENSOR_RQST_IDENT_SHIFT = 7, SES_CTRL_VOLTAGE_SENSOR_RQST_FAIL_BYTE = 0, SES_CTRL_VOLTAGE_SENSOR_RQST_FAIL_MASK = 0x40, SES_CTRL_VOLTAGE_SENSOR_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_VOLTAGE_SENSOR_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_voltage_sensor, SES_CTRL_VOLTAGE_SENSOR, \ LCASE, UCASE) GEN_SES_CTRL_VOLTAGE_SENSOR_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_VOLTAGE_SENSOR_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_VOLTAGE_SENSOR_ACCESSORS /*---------------------- Current Sensor Control Element ----------------------*/ struct ses_ctrl_current_sensor { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_current_sensor_field_data { SES_CTRL_CURRENT_SENSOR_RQST_IDENT_BYTE = 0, SES_CTRL_CURRENT_SENSOR_RQST_IDENT_MASK = 0x80, SES_CTRL_CURRENT_SENSOR_RQST_IDENT_SHIFT = 7, SES_CTRL_CURRENT_SENSOR_RQST_FAIL_BYTE = 0, SES_CTRL_CURRENT_SENSOR_RQST_FAIL_MASK = 0x40, SES_CTRL_CURRENT_SENSOR_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_CURRENT_SENSOR_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_current_sensor, SES_CTRL_CURRENT_SENSOR, \ LCASE, UCASE) GEN_SES_CTRL_CURRENT_SENSOR_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_CURRENT_SENSOR_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_CURRENT_SENSOR_ACCESSORS /*--------------------- SCSI Target Port Control Element ---------------------*/ struct ses_ctrl_target_port { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_scsi_target_port_field_data { SES_CTRL_TARGET_PORT_RQST_IDENT_BYTE = 0, SES_CTRL_TARGET_PORT_RQST_IDENT_MASK = 0x80, SES_CTRL_TARGET_PORT_RQST_IDENT_SHIFT = 7, SES_CTRL_TARGET_PORT_RQST_FAIL_BYTE = 0, SES_CTRL_TARGET_PORT_RQST_FAIL_MASK = 0x40, SES_CTRL_TARGET_PORT_RQST_FAIL_SHIFT = 6, SES_CTRL_TARGET_PORT_ENABLE_BYTE = 2, SES_CTRL_TARGET_PORT_ENABLE_MASK = 0x01, SES_CTRL_TARGET_PORT_ENABLE_SHIFT = 0 }; #define GEN_SES_CTRL_TARGET_PORT_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_target_port, SES_CTRL_TARGET_PORT, LCASE, UCASE) GEN_SES_CTRL_TARGET_PORT_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_TARGET_PORT_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_TARGET_PORT_ACCESSORS(enable, ENABLE) #undef GEN_SES_CTRL_TARGET_PORT_ACCESSORS /*-------------------- SCSI Initiator Port Control Element -------------------*/ struct ses_ctrl_initiator_port { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_initiator_port_field_data { SES_CTRL_INITIATOR_PORT_RQST_IDENT_BYTE = 0, SES_CTRL_INITIATOR_PORT_RQST_IDENT_MASK = 0x80, SES_CTRL_INITIATOR_PORT_RQST_IDENT_SHIFT = 7, SES_CTRL_INITIATOR_PORT_RQST_FAIL_BYTE = 0, SES_CTRL_INITIATOR_PORT_RQST_FAIL_MASK = 0x40, SES_CTRL_INITIATOR_PORT_RQST_FAIL_SHIFT = 6, SES_CTRL_INITIATOR_PORT_ENABLE_BYTE = 2, SES_CTRL_INITIATOR_PORT_ENABLE_MASK = 0x01, SES_CTRL_INITIATOR_PORT_ENABLE_SHIFT = 0 }; #define GEN_SES_CTRL_INITIATOR_PORT_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_initiator_port, SES_CTRL_INITIATOR_PORT, \ LCASE, UCASE) GEN_SES_CTRL_INITIATOR_PORT_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_INITIATOR_PORT_ACCESSORS(rqst_fail, RQST_FAIL) GEN_SES_CTRL_INITIATOR_PORT_ACCESSORS(enable, ENABLE) #undef GEN_SES_CTRL_INITIATOR_PORT_ACCESSORS /*-------------------- Simple Subenclosure Control Element -------------------*/ struct ses_ctrl_simple_subenc { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_simple_subenc_field_data { SES_CTRL_SIMPlE_SUBSES_RQST_IDENT_BYTE = 0, SES_CTRL_SIMPlE_SUBSES_RQST_IDENT_MASK = 0x80, SES_CTRL_SIMPlE_SUBSES_RQST_IDENT_SHIFT = 7, SES_CTRL_SIMPlE_SUBSES_RQST_FAIL_BYTE = 0, SES_CTRL_SIMPlE_SUBSES_RQST_FAIL_MASK = 0x40, SES_CTRL_SIMPlE_SUBSES_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_SIMPlE_SUBSES_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_simple_subenc, SES_CTRL_SIMPlE_SUBSES, \ LCASE, UCASE) GEN_SES_CTRL_SIMPlE_SUBSES_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_SIMPlE_SUBSES_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_SIMPlE_SUBSES_ACCESSORS /*----------------------- SAS Expander Control Element -----------------------*/ struct ses_ctrl_sas_expander { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_sas_expander_field_data { SES_CTRL_SAS_EXPANDER_RQST_IDENT_BYTE = 0, SES_CTRL_SAS_EXPANDER_RQST_IDENT_MASK = 0x80, SES_CTRL_SAS_EXPANDER_RQST_IDENT_SHIFT = 7, SES_CTRL_SAS_EXPANDER_RQST_FAIL_BYTE = 0, SES_CTRL_SAS_EXPANDER_RQST_FAIL_MASK = 0x40, SES_CTRL_SAS_EXPANDER_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_SAS_EXPANDER_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_sas_expander, SES_CTRL_SAS_EXPANDER, LCASE, UCASE) GEN_SES_CTRL_SAS_EXPANDER_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_SAS_EXPANDER_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_SAS_EXPANDER_ACCESSORS /*----------------------- SAS Connector Control Element ----------------------*/ struct ses_ctrl_sas_connector { struct ses_ctrl_common common; uint8_t bytes[3]; }; enum ses_ctrl_sas_connector_field_data { SES_CTRL_SAS_CONNECTOR_RQST_IDENT_BYTE = 0, SES_CTRL_SAS_CONNECTOR_RQST_IDENT_MASK = 0x80, SES_CTRL_SAS_CONNECTOR_RQST_IDENT_SHIFT = 7, SES_CTRL_SAS_CONNECTOR_RQST_FAIL_BYTE = 2, SES_CTRL_SAS_CONNECTOR_RQST_FAIL_MASK = 0x40, SES_CTRL_SAS_CONNECTOR_RQST_FAIL_SHIFT = 6 }; #define GEN_SES_CTRL_SAS_CONNECTOR_ACCESSORS(LCASE, UCASE) \ GEN_ACCESSORS(ses_ctrl_sas_connector, SES_CTRL_SAS_CONNECTOR, \ LCASE, UCASE) GEN_SES_CTRL_SAS_CONNECTOR_ACCESSORS(rqst_ident, RQST_IDENT) GEN_SES_CTRL_SAS_CONNECTOR_ACCESSORS(rqst_fail, RQST_FAIL) #undef GEN_SES_CTRL_SAS_CONNECTOR_ACCESSORS /*------------------------- Universal Control Element ------------------------*/ union ses_ctrl_element { struct ses_ctrl_common common; struct ses_ctrl_dev_slot dev_slot; struct ses_ctrl_array_dev_slot array_dev_slot; struct ses_ctrl_power_supply power_supply; struct ses_ctrl_cooling cooling; struct ses_ctrl_temp_sensor temp_sensor; struct ses_ctrl_door_lock door_lock; struct ses_ctrl_audible_alarm audible_alarm; struct ses_ctrl_ecc_electronics ecc_electronics; struct ses_ctrl_scc_electronics scc_electronics; struct ses_ctrl_nv_cache nv_cache; struct ses_ctrl_invalid_op_reason invalid_op_reason; struct ses_ctrl_ups ups; struct ses_ctrl_display display; struct ses_ctrl_key_pad_entry key_pad_entry; struct ses_ctrl_scsi_port_or_xcvr scsi_port_or_xcvr; struct ses_ctrl_language language; struct ses_ctrl_comm_port comm_port; struct ses_ctrl_voltage_sensor voltage_sensor; struct ses_ctrl_current_sensor current_sensor; struct ses_ctrl_target_port target_port; struct ses_ctrl_initiator_port initiator_port; struct ses_ctrl_simple_subenc simple_subenc; struct ses_ctrl_sas_expander sas_expander; struct ses_ctrl_sas_connector sas_connector; }; /*--------------------- SCSI SES Control Diagnostic Page ---------------------*/ struct ses_ctrl_page { struct ses_page_hdr hdr; union ses_ctrl_element elements[]; }; enum ses_ctrl_page_field_data { SES_CTRL_PAGE_INFO_MASK = 0x08, SES_CTRL_PAGE_INFO_SHIFT = 3, SES_CTRL_PAGE_NON_CRIT_MASK = 0x04, SES_CTRL_PAGE_NON_CRIT_SHIFT = 2, SES_CTRL_PAGE_CRIT_MASK = 0x02, SES_CTRL_PAGE_CRIT_SHIFT = 1, SES_CTRL_PAGE_UNRECOV_MASK = 0x01, SES_CTRL_PAGE_UNRECOV_SHIFT = 0 }; #define GEN_SES_CTRL_PAGE_ACCESSORS(LCASE, UCASE) \ GEN_HDR_ACCESSORS(ses_ctrl_page, SES_CTRL_PAGE, LCASE, UCASE) GEN_SES_CTRL_PAGE_ACCESSORS(info, INFO) GEN_SES_CTRL_PAGE_ACCESSORS(non_crit, NON_CRIT) GEN_SES_CTRL_PAGE_ACCESSORS(crit, CRIT) GEN_SES_CTRL_PAGE_ACCESSORS(unrecov, UNRECOV) #undef GEN_SES_CTRL_PAGE_ACCESSORS /*================= SCSI SES Status Diagnostic Page Structures ===============*/ struct ses_status_common { uint8_t bytes[1]; }; enum ses_status_common_field_data { SES_STATUS_COMMON_PRDFAIL_BYTE = 0, SES_STATUS_COMMON_PRDFAIL_MASK = 0x40, SES_STATUS_COMMON_PRDFAIL_SHIFT = 6, SES_STATUS_COMMON_DISABLED_BYTE = 0, SES_STATUS_COMMON_DISABLED_MASK = 0x20, SES_STATUS_COMMON_DISABLED_SHIFT = 5, SES_STATUS_COMMON_SWAP_BYTE = 0, SES_STATUS_COMMON_SWAP_MASK = 0x10, SES_STATUS_COMMON_SWAP_SHIFT = 4, SES_STATUS_COMMON_ELEMENT_STATUS_CODE_BYTE = 0, SES_STATUS_COMMON_ELEMENT_STATUS_CODE_MASK = 0x0F, SES_STATUS_COMMON_ELEMENT_STATUS_CODE_SHIFT = 0 }; #define GEN_SES_STATUS_COMMON_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_common, SES_STATUS_COMMON, LCASE, UCASE) GEN_SES_STATUS_COMMON_ACCESSORS(prdfail, PRDFAIL) GEN_SES_STATUS_COMMON_ACCESSORS(disabled, DISABLED) GEN_SES_STATUS_COMMON_ACCESSORS(swap, SWAP) GEN_SES_STATUS_COMMON_ACCESSORS(element_status_code, ELEMENT_STATUS_CODE) #undef GEN_SES_STATUS_COMMON_ACCESSORS /*------------------------- Device Slot Status Element -----------------------*/ struct ses_status_dev_slot { struct ses_status_common common; uint8_t slot_address; uint8_t bytes[2]; }; enum ses_status_dev_slot_field_data { SES_STATUS_DEV_SLOT_APP_CLIENT_BYPED_A_BYTE = 0, SES_STATUS_DEV_SLOT_APP_CLIENT_BYPED_A_MASK = 0x80, SES_STATUS_DEV_SLOT_APP_CLIENT_BYPED_A_SHIFT = 7, SES_STATUS_DEV_SLOT_DO_NOT_REMOVE_BYTE = 0, SES_STATUS_DEV_SLOT_DO_NOT_REMOVE_MASK = 0x40, SES_STATUS_DEV_SLOT_DO_NOT_REMOVE_SHIFT = 6, SES_STATUS_DEV_SLOT_ENCLOSURE_BYPED_A_BYTE = 0, SES_STATUS_DEV_SLOT_ENCLOSURE_BYPED_A_MASK = 0x20, SES_STATUS_DEV_SLOT_ENCLOSURE_BYPED_A_SHIFT = 5, SES_STATUS_DEV_SLOT_ENCLOSURE_BYPED_B_BYTE = 0, SES_STATUS_DEV_SLOT_ENCLOSURE_BYPED_B_MASK = 0x10, SES_STATUS_DEV_SLOT_ENCLOSURE_BYPED_B_SHIFT = 4, SES_STATUS_DEV_SLOT_INSERT_READY_BYTE = 0, SES_STATUS_DEV_SLOT_INSERT_READY_MASK = 0x08, SES_STATUS_DEV_SLOT_INSERT_READY_SHIFT = 3, SES_STATUS_DEV_SLOT_REMOVE_BYTE = 0, SES_STATUS_DEV_SLOT_REMOVE_MASK = 0x04, SES_STATUS_DEV_SLOT_REMOVE_SHIFT = 2, SES_STATUS_DEV_SLOT_IDENT_BYTE = 0, SES_STATUS_DEV_SLOT_IDENT_MASK = 0x02, SES_STATUS_DEV_SLOT_IDENT_SHIFT = 1, SES_STATUS_DEV_SLOT_REPORT_BYTE = 0, SES_STATUS_DEV_SLOT_REPORT_MASK = 0x01, SES_STATUS_DEV_SLOT_REPORT_SHIFT = 0, SES_STATUS_DEV_SLOT_APP_CLIENT_BYPED_B_BYTE = 1, SES_STATUS_DEV_SLOT_APP_CLIENT_BYPED_B_MASK = 0x80, SES_STATUS_DEV_SLOT_APP_CLIENT_BYPED_B_SHIFT = 7, SES_STATUS_DEV_SLOT_FAULT_SENSED_BYTE = 1, SES_STATUS_DEV_SLOT_FAULT_SENSED_MASK = 0x40, SES_STATUS_DEV_SLOT_FAULT_SENSED_SHIFT = 6, SES_STATUS_DEV_SLOT_FAULT_REQUESTED_BYTE = 1, SES_STATUS_DEV_SLOT_FAULT_REQUESTED_MASK = 0x20, SES_STATUS_DEV_SLOT_FAULT_REQUESTED_SHIFT = 5, SES_STATUS_DEV_SLOT_DEVICE_OFF_BYTE = 1, SES_STATUS_DEV_SLOT_DEVICE_OFF_MASK = 0x10, SES_STATUS_DEV_SLOT_DEVICE_OFF_SHIFT = 4, SES_STATUS_DEV_SLOT_BYPED_A_BYTE = 1, SES_STATUS_DEV_SLOT_BYPED_A_MASK = 0x08, SES_STATUS_DEV_SLOT_BYPED_A_SHIFT = 3, SES_STATUS_DEV_SLOT_BYPED_B_BYTE = 1, SES_STATUS_DEV_SLOT_BYPED_B_MASK = 0x04, SES_STATUS_DEV_SLOT_BYPED_B_SHIFT = 2, SES_STATUS_DEV_SLOT_DEVICE_BYPED_A_BYTE = 1, SES_STATUS_DEV_SLOT_DEVICE_BYPED_A_MASK = 0x02, SES_STATUS_DEV_SLOT_DEVICE_BYPED_A_SHIFT = 1, SES_STATUS_DEV_SLOT_DEVICE_BYPED_B_BYTE = 1, SES_STATUS_DEV_SLOT_DEVICE_BYPED_B_MASK = 0x01, SES_STATUS_DEV_SLOT_DEVICE_BYPED_B_SHIFT = 0 }; #define GEN_SES_STATUS_DEV_SLOT_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_dev_slot, SES_STATUS_DEV_SLOT, LCASE, UCASE) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(app_client_byped_a, APP_CLIENT_BYPED_A) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(do_not_remove, DO_NOT_REMOVE) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(enclosure_byped_a, ENCLOSURE_BYPED_A) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(enclosure_byped_b, ENCLOSURE_BYPED_B) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(insert_ready, INSERT_READY) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(remove, REMOVE) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(ident, IDENT) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(report, REPORT) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(app_client_byped_b, APP_CLIENT_BYPED_B) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(fault_sensed, FAULT_SENSED) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(fault_requested, FAULT_REQUESTED) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(device_off, DEVICE_OFF) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(byped_a, BYPED_A) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(byped_b, BYPED_B) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(device_byped_a, DEVICE_BYPED_A) GEN_SES_STATUS_DEV_SLOT_ACCESSORS(device_byped_b, DEVICE_BYPED_B) #undef GEN_SES_STATUS_DEV_SLOT_ACCESSORS /*---------------------- Array Device Slot Status Element --------------------*/ struct ses_status_array_dev_slot { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_array_dev_slot_field_data { SES_STATUS_ARRAY_DEV_SLOT_OK_BYTE = 0, SES_STATUS_ARRAY_DEV_SLOT_OK_MASK = 0x80, SES_STATUS_ARRAY_DEV_SLOT_OK_SHIFT = 7, SES_STATUS_ARRAY_DEV_SLOT_RSVD_DEVICE_BYTE = 0, SES_STATUS_ARRAY_DEV_SLOT_RSVD_DEVICE_MASK = 0x40, SES_STATUS_ARRAY_DEV_SLOT_RSVD_DEVICE_SHIFT = 6, SES_STATUS_ARRAY_DEV_SLOT_HOT_SPARE_BYTE = 0, SES_STATUS_ARRAY_DEV_SLOT_HOT_SPARE_MASK = 0x20, SES_STATUS_ARRAY_DEV_SLOT_HOT_SPARE_SHIFT = 5, SES_STATUS_ARRAY_DEV_SLOT_CONS_CHECK_BYTE = 0, SES_STATUS_ARRAY_DEV_SLOT_CONS_CHECK_MASK = 0x10, SES_STATUS_ARRAY_DEV_SLOT_CONS_CHECK_SHIFT = 4, SES_STATUS_ARRAY_DEV_SLOT_IN_CRIT_ARRAY_BYTE = 0, SES_STATUS_ARRAY_DEV_SLOT_IN_CRIT_ARRAY_MASK = 0x08, SES_STATUS_ARRAY_DEV_SLOT_IN_CRIT_ARRAY_SHIFT = 3, SES_STATUS_ARRAY_DEV_SLOT_IN_FAILED_ARRAY_BYTE = 0, SES_STATUS_ARRAY_DEV_SLOT_IN_FAILED_ARRAY_MASK = 0x04, SES_STATUS_ARRAY_DEV_SLOT_IN_FAILED_ARRAY_SHIFT = 2, SES_STATUS_ARRAY_DEV_SLOT_REBUILD_REMAP_BYTE = 0, SES_STATUS_ARRAY_DEV_SLOT_REBUILD_REMAP_MASK = 0x02, SES_STATUS_ARRAY_DEV_SLOT_REBUILD_REMAP_SHIFT = 1, SES_STATUS_ARRAY_DEV_SLOT_REBUILD_REMAP_ABORT_BYTE = 0, SES_STATUS_ARRAY_DEV_SLOT_REBUILD_REMAP_ABORT_MASK = 0x01, SES_STATUS_ARRAY_DEV_SLOT_REBUILD_REMAP_ABORT_SHIFT = 0 /* * The remaining fields are identical to the device * slot element type. Access them through the device slot * element type and its accessors. */ }; #define GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_array_dev_slot, SES_STATUS_ARRAY_DEV_SLOT, \ LCASE, UCASE) GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(ok, OK) GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(rsvd_device, RSVD_DEVICE) GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(hot_spare, HOT_SPARE) GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(cons_check, CONS_CHECK) GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(in_crit_array, IN_CRIT_ARRAY) GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(in_failed_array, IN_FAILED_ARRAY) GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(rebuild_remap, REBUILD_REMAP) GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS(rebuild_remap_abort, REBUILD_REMAP_ABORT) #undef GEN_SES_STATUS_ARRAY_DEV_SLOT_ACCESSORS /*----------------------- Power Supply Status Element ------------------------*/ struct ses_status_power_supply { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_power_supply_field_data { SES_STATUS_POWER_SUPPLY_IDENT_BYTE = 0, SES_STATUS_POWER_SUPPLY_IDENT_MASK = 0x80, SES_STATUS_POWER_SUPPLY_IDENT_SHIFT = 7, SES_STATUS_POWER_SUPPLY_DC_OVER_VOLTAGE_BYTE = 1, SES_STATUS_POWER_SUPPLY_DC_OVER_VOLTAGE_MASK = 0x08, SES_STATUS_POWER_SUPPLY_DC_OVER_VOLTAGE_SHIFT = 3, SES_STATUS_POWER_SUPPLY_DC_UNDER_VOLTAGE_BYTE = 1, SES_STATUS_POWER_SUPPLY_DC_UNDER_VOLTAGE_MASK = 0x04, SES_STATUS_POWER_SUPPLY_DC_UNDER_VOLTAGE_SHIFT = 2, SES_STATUS_POWER_SUPPLY_DC_OVER_CURRENT_BYTE = 1, SES_STATUS_POWER_SUPPLY_DC_OVER_CURRENT_MASK = 0x02, SES_STATUS_POWER_SUPPLY_DC_OVER_CURRENT_SHIFT = 1, SES_STATUS_POWER_SUPPLY_HOT_SWAP_BYTE = 2, SES_STATUS_POWER_SUPPLY_HOT_SWAP_MASK = 0x80, SES_STATUS_POWER_SUPPLY_HOT_SWAP_SHIFT = 7, SES_STATUS_POWER_SUPPLY_FAIL_BYTE = 2, SES_STATUS_POWER_SUPPLY_FAIL_MASK = 0x40, SES_STATUS_POWER_SUPPLY_FAIL_SHIFT = 6, SES_STATUS_POWER_SUPPLY_REQUESTED_ON_BYTE = 2, SES_STATUS_POWER_SUPPLY_REQUESTED_ON_MASK = 0x20, SES_STATUS_POWER_SUPPLY_REQUESTED_ON_SHIFT = 5, SES_STATUS_POWER_SUPPLY_OFF_BYTE = 2, SES_STATUS_POWER_SUPPLY_OFF_MASK = 0x10, SES_STATUS_POWER_SUPPLY_OFF_SHIFT = 4, SES_STATUS_POWER_SUPPLY_OVERTMP_FAIL_BYTE = 2, SES_STATUS_POWER_SUPPLY_OVERTMP_FAIL_MASK = 0x08, SES_STATUS_POWER_SUPPLY_OVERTMP_FAIL_SHIFT = 3, SES_STATUS_POWER_SUPPLY_TEMP_WARN_BYTE = 2, SES_STATUS_POWER_SUPPLY_TEMP_WARN_MASK = 0x04, SES_STATUS_POWER_SUPPLY_TEMP_WARN_SHIFT = 2, SES_STATUS_POWER_SUPPLY_AC_FAIL_BYTE = 2, SES_STATUS_POWER_SUPPLY_AC_FAIL_MASK = 0x02, SES_STATUS_POWER_SUPPLY_AC_FAIL_SHIFT = 1, SES_STATUS_POWER_SUPPLY_DC_FAIL_BYTE = 2, SES_STATUS_POWER_SUPPLY_DC_FAIL_MASK = 0x01, SES_STATUS_POWER_SUPPLY_DC_FAIL_SHIFT = 0 }; #define GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_power_supply, SES_STATUS_POWER_SUPPLY, LCASE, UCASE) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(ident, IDENT) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(dc_over_voltage, DC_OVER_VOLTAGE) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(dc_under_voltage, DC_UNDER_VOLTAGE) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(dc_over_current, DC_OVER_CURRENT) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(hot_swap, HOT_SWAP) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(fail, FAIL) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(requested_on, REQUESTED_ON) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(off, OFF) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(overtmp_fail, OVERTMP_FAIL) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(temp_warn, TEMP_WARN) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(ac_fail, AC_FAIL) GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS(dc_fail, DC_FAIL) #undef GEN_SES_STATUS_POWER_SUPPLY_ACCESSORS /*-------------------------- Cooling Status Element --------------------------*/ struct ses_status_cooling { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_cooling_field_data { SES_STATUS_COOLING_IDENT_BYTE = 0, SES_STATUS_COOLING_IDENT_MASK = 0x80, SES_STATUS_COOLING_IDENT_SHIFT = 7, SES_STATUS_COOLING_ACTUAL_FAN_SPEED_MSB_BYTE = 0, SES_STATUS_COOLING_ACTUAL_FAN_SPEED_MSB_MASK = 0x07, SES_STATUS_COOLING_ACTUAL_FAN_SPEED_MSB_SHIFT = 0, SES_STATUS_COOLING_ACTUAL_FAN_SPEED_LSB_BYTE = 1, SES_STATUS_COOLING_ACTUAL_FAN_SPEED_LSB_MASK = 0xFF, SES_STATUS_COOLING_ACTUAL_FAN_SPEED_LSB_SHIFT = 0, SES_STATUS_COOLING_HOT_SWAP_BYTE = 2, SES_STATUS_COOLING_HOT_SWAP_MASK = 0x40, SES_STATUS_COOLING_HOT_SWAP_SHIFT = 6, SES_STATUS_COOLING_FAIL_BYTE = 2, SES_STATUS_COOLING_FAIL_MASK = 0x40, SES_STATUS_COOLING_FAIL_SHIFT = 6, SES_STATUS_COOLING_REQUESTED_ON_BYTE = 2, SES_STATUS_COOLING_REQUESTED_ON_MASK = 0x20, SES_STATUS_COOLING_REQUESTED_ON_SHIFT = 5, SES_STATUS_COOLING_OFF_BYTE = 2, SES_STATUS_COOLING_OFF_MASK = 0x20, SES_STATUS_COOLING_OFF_SHIFT = 5, SES_STATUS_COOLING_ACTUAL_SPEED_CODE_BYTE = 2, SES_STATUS_COOLING_ACTUAL_SPEED_CODE_MASK = 0x07, SES_STATUS_COOLING_ACTUAL_SPEED_CODE_SHIFT = 2, SES_STATUS_COOLING_ACTUAL_SPEED_CODE_STOPPED = 0x00, SES_STATUS_COOLING_ACTUAL_SPEED_CODE_LOWEST = 0x01, SES_STATUS_COOLING_ACTUAL_SPEED_CODE_HIGHEST = 0x07 }; #define GEN_SES_STATUS_COOLING_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_cooling, SES_STATUS_COOLING, LCASE, UCASE) GEN_SES_STATUS_COOLING_ACCESSORS(ident, IDENT) GEN_SES_STATUS_COOLING_ACCESSORS(actual_fan_speed_msb, ACTUAL_FAN_SPEED_MSB) GEN_SES_STATUS_COOLING_ACCESSORS(actual_fan_speed_lsb, ACTUAL_FAN_SPEED_LSB) GEN_SES_STATUS_COOLING_ACCESSORS(hot_swap, HOT_SWAP) GEN_SES_STATUS_COOLING_ACCESSORS(fail, FAIL) GEN_SES_STATUS_COOLING_ACCESSORS(requested_on, REQUESTED_ON) GEN_SES_STATUS_COOLING_ACCESSORS(off, OFF) GEN_SES_STATUS_COOLING_ACCESSORS(actual_speed_code, ACTUAL_SPEED_CODE) #undef GEN_SES_STATUS_COOLING_ACCESSORS static inline int ses_status_cooling_get_actual_fan_speed(struct ses_status_cooling *elem) { return (ses_status_cooling_get_actual_fan_speed_msb(elem) << 8 | ses_status_cooling_get_actual_fan_speed_lsb(elem)); } /*-------------------- Temperature Sensor Status Element ---------------------*/ struct ses_status_temp_sensor { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_temp_sensor_field_data { SES_STATUS_TEMP_SENSOR_IDENT_BYTE = 0, SES_STATUS_TEMP_SENSOR_IDENT_MASK = 0x80, SES_STATUS_TEMP_SENSOR_IDENT_SHIFT = 7, SES_STATUS_TEMP_SENSOR_FAIL_BYTE = 0, SES_STATUS_TEMP_SENSOR_FAIL_MASK = 0x40, SES_STATUS_TEMP_SENSOR_FAIL_SHIFT = 6, SES_STATUS_TEMP_SENSOR_TEMPERATURE_BYTE = 1, SES_STATUS_TEMP_SENSOR_TEMPERATURE_MASK = 0xFF, SES_STATUS_TEMP_SENSOR_TEMPERATURE_SHIFT = 0, SES_STATUS_TEMP_SENSOR_OT_FAILURE_BYTE = 2, SES_STATUS_TEMP_SENSOR_OT_FAILURE_MASK = 0x08, SES_STATUS_TEMP_SENSOR_OT_FAILURE_SHIFT = 3, SES_STATUS_TEMP_SENSOR_OT_WARNING_BYTE = 2, SES_STATUS_TEMP_SENSOR_OT_WARNING_MASK = 0x04, SES_STATUS_TEMP_SENSOR_OT_WARNING_SHIFT = 2, SES_STATUS_TEMP_SENSOR_UT_FAILURE_BYTE = 2, SES_STATUS_TEMP_SENSOR_UT_FAILURE_MASK = 0x02, SES_STATUS_TEMP_SENSOR_UT_FAILURE_SHIFT = 1, SES_STATUS_TEMP_SENSOR_UT_WARNING_BYTE = 2, SES_STATUS_TEMP_SENSOR_UT_WARNING_MASK = 0x01, SES_STATUS_TEMP_SENSOR_UT_WARNING_SHIFT = 0 }; #define GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_temp_sensor, SES_STATUS_TEMP_SENSOR, LCASE, UCASE) GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS(ident, IDENT) GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS(fail, FAIL) GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS(temperature, TEMPERATURE) GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS(ot_failure, OT_FAILURE) GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS(ot_warning, OT_WARNING) GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS(ut_failure, UT_FAILURE) GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS(ut_warning, UT_WARNING) #undef GEN_SES_STATUS_TEMP_SENSOR_ACCESSORS /*------------------------- Door Lock Status Element -------------------------*/ struct ses_status_door_lock { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_door_lock_field_data { SES_STATUS_DOOR_LOCK_IDENT_BYTE = 0, SES_STATUS_DOOR_LOCK_IDENT_MASK = 0x80, SES_STATUS_DOOR_LOCK_IDENT_SHIFT = 7, SES_STATUS_DOOR_LOCK_FAIL_BYTE = 0, SES_STATUS_DOOR_LOCK_FAIL_MASK = 0x40, SES_STATUS_DOOR_LOCK_FAIL_SHIFT = 6, SES_STATUS_DOOR_LOCK_UNLOCKED_BYTE = 2, SES_STATUS_DOOR_LOCK_UNLOCKED_MASK = 0x01, SES_STATUS_DOOR_LOCK_UNLOCKED_SHIFT = 0 }; #define GEN_SES_STATUS_DOOR_LOCK_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_door_lock, SES_STATUS_DOOR_LOCK, LCASE, UCASE) GEN_SES_STATUS_DOOR_LOCK_ACCESSORS(ident, IDENT) GEN_SES_STATUS_DOOR_LOCK_ACCESSORS(fail, FAIL) GEN_SES_STATUS_DOOR_LOCK_ACCESSORS(unlocked, UNLOCKED) #undef GEN_SES_STATUS_DOOR_LOCK_ACCESSORS /*----------------------- Audible Alarm Status Element -----------------------*/ struct ses_status_audible_alarm { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_audible_alarm_field_data { SES_STATUS_AUDIBLE_ALARM_IDENT_BYTE = 0, SES_STATUS_AUDIBLE_ALARM_IDENT_MASK = 0x80, SES_STATUS_AUDIBLE_ALARM_IDENT_SHIFT = 7, SES_STATUS_AUDIBLE_ALARM_FAIL_BYTE = 0, SES_STATUS_AUDIBLE_ALARM_FAIL_MASK = 0x40, SES_STATUS_AUDIBLE_ALARM_FAIL_SHIFT = 6, SES_STATUS_AUDIBLE_ALARM_RQST_MUTE_BYTE = 2, SES_STATUS_AUDIBLE_ALARM_RQST_MUTE_MASK = 0x80, SES_STATUS_AUDIBLE_ALARM_RQST_MUTE_SHIFT = 7, SES_STATUS_AUDIBLE_ALARM_MUTED_BYTE = 2, SES_STATUS_AUDIBLE_ALARM_MUTED_MASK = 0x40, SES_STATUS_AUDIBLE_ALARM_MUTED_SHIFT = 6, SES_STATUS_AUDIBLE_ALARM_REMIND_BYTE = 2, SES_STATUS_AUDIBLE_ALARM_REMIND_MASK = 0x10, SES_STATUS_AUDIBLE_ALARM_REMIND_SHIFT = 4, SES_STATUS_AUDIBLE_ALARM_TONE_INDICATOR_BYTE = 2, SES_STATUS_AUDIBLE_ALARM_TONE_INDICATOR_MASK = 0x0F, SES_STATUS_AUDIBLE_ALARM_TONE_INDICATOR_SHIFT = 0, SES_STATUS_AUDIBLE_ALARM_TONE_INDICATOR_INFO = 0x08, SES_STATUS_AUDIBLE_ALARM_TONE_INDICATOR_NON_CRIT = 0x04, SES_STATUS_AUDIBLE_ALARM_TONE_INDICATOR_CRIT = 0x02, SES_STATUS_AUDIBLE_ALARM_TONE_INDICATOR_UNRECOV = 0x01 }; #define GEN_SES_STATUS_AUDIBLE_ALARM_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_audible_alarm, SES_STATUS_AUDIBLE_ALARM, LCASE, UCASE) GEN_SES_STATUS_AUDIBLE_ALARM_ACCESSORS(ident, IDENT) GEN_SES_STATUS_AUDIBLE_ALARM_ACCESSORS(fail, FAIL) GEN_SES_STATUS_AUDIBLE_ALARM_ACCESSORS(rqst_mute, RQST_MUTE) GEN_SES_STATUS_AUDIBLE_ALARM_ACCESSORS(muted, MUTED) GEN_SES_STATUS_AUDIBLE_ALARM_ACCESSORS(remind, REMIND) GEN_SES_STATUS_AUDIBLE_ALARM_ACCESSORS(tone_indicator, TONE_INDICATOR) #undef GEN_SES_STATUS_AUDIBLE_ALARM_ACCESSORS /*---------- Enclosure Services Statusler Electronics Status Element ---------*/ struct ses_status_ecc_electronics { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_ecc_electronics_field_data { SES_STATUS_ECC_ELECTRONICS_IDENT_BYTE = 0, SES_STATUS_ECC_ELECTRONICS_IDENT_MASK = 0x80, SES_STATUS_ECC_ELECTRONICS_IDENT_SHIFT = 7, SES_STATUS_ECC_ELECTRONICS_FAIL_BYTE = 0, SES_STATUS_ECC_ELECTRONICS_FAIL_MASK = 0x40, SES_STATUS_ECC_ELECTRONICS_FAIL_SHIFT = 6, SES_STATUS_ECC_ELECTRONICS_REPORT_BYTE = 1, SES_STATUS_ECC_ELECTRONICS_REPORT_MASK = 0x01, SES_STATUS_ECC_ELECTRONICS_REPORT_SHIFT = 0, SES_STATUS_ECC_ELECTRONICS_HOT_SWAP_BYTE = 2, SES_STATUS_ECC_ELECTRONICS_HOT_SWAP_MASK = 0x80, SES_STATUS_ECC_ELECTRONICS_HOT_SWAP_SHIFT = 7 }; #define GEN_SES_STATUS_ECC_ELECTRONICS_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_ecc_electronics, SES_STATUS_ECC_ELECTRONICS, \ LCASE, UCASE) GEN_SES_STATUS_ECC_ELECTRONICS_ACCESSORS(ident, IDENT) GEN_SES_STATUS_ECC_ELECTRONICS_ACCESSORS(fail, FAIL) GEN_SES_STATUS_ECC_ELECTRONICS_ACCESSORS(report, REPORT) GEN_SES_STATUS_ECC_ELECTRONICS_ACCESSORS(hot_swap, HOT_SWAP) #undef GEN_SES_STATUS_ECC_ELECTRONICS_ACCESSORS /*------------ SCSI Services Statusler Electronics Status Element ------------*/ struct ses_status_scc_electronics { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_scc_electronics_field_data { SES_STATUS_SCC_ELECTRONICS_IDENT_BYTE = 0, SES_STATUS_SCC_ELECTRONICS_IDENT_MASK = 0x80, SES_STATUS_SCC_ELECTRONICS_IDENT_SHIFT = 7, SES_STATUS_SCC_ELECTRONICS_FAIL_BYTE = 0, SES_STATUS_SCC_ELECTRONICS_FAIL_MASK = 0x40, SES_STATUS_SCC_ELECTRONICS_FAIL_SHIFT = 6, SES_STATUS_SCC_ELECTRONICS_REPORT_BYTE = 1, SES_STATUS_SCC_ELECTRONICS_REPORT_MASK = 0x01, SES_STATUS_SCC_ELECTRONICS_REPORT_SHIFT = 0 }; #define GEN_SES_STATUS_SCC_ELECTRONICS_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_scc_electronics, SES_STATUS_SCC_ELECTRONICS, \ LCASE, UCASE) GEN_SES_STATUS_SCC_ELECTRONICS_ACCESSORS(ident, IDENT) GEN_SES_STATUS_SCC_ELECTRONICS_ACCESSORS(fail, FAIL) GEN_SES_STATUS_SCC_ELECTRONICS_ACCESSORS(report, REPORT) #undef GEN_SES_STATUS_SCC_ELECTRONICS_ACCESSORS /*--------------------- Nonvolatile Cache Status Element ---------------------*/ struct ses_status_nv_cache { struct ses_status_common common; uint8_t bytes[1]; uint8_t cache_size[2]; }; enum ses_status_nv_cache_field_data { SES_STATUS_NV_CACHE_IDENT_BYTE = 0, SES_STATUS_NV_CACHE_IDENT_MASK = 0x80, SES_STATUS_NV_CACHE_IDENT_SHIFT = 7, SES_STATUS_NV_CACHE_FAIL_BYTE = 0, SES_STATUS_NV_CACHE_FAIL_MASK = 0x40, SES_STATUS_NV_CACHE_FAIL_SHIFT = 6, SES_STATUS_NV_CACHE_SIZE_MULTIPLIER_BYTE = 0, SES_STATUS_NV_CACHE_SIZE_MULTIPLIER_MASK = 0x03, SES_STATUS_NV_CACHE_SIZE_MULTIPLIER_SHIFT = 0, SES_STATUS_NV_CACHE_SIZE_MULTIPLIER_BYTES = 0x0, SES_STATUS_NV_CACHE_SIZE_MULTIPLIER_KBYTES = 0x1, SES_STATUS_NV_CACHE_SIZE_MULTIPLIER_MBYTES = 0x2, SES_STATUS_NV_CACHE_SIZE_MULTIPLIER_GBYTES = 0x3 }; #define GEN_SES_STATUS_NV_CACHE_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_nv_cache, SES_STATUS_NV_CACHE, LCASE, UCASE) GEN_SES_STATUS_NV_CACHE_ACCESSORS(ident, IDENT) GEN_SES_STATUS_NV_CACHE_ACCESSORS(fail, FAIL) GEN_SES_STATUS_NV_CACHE_ACCESSORS(size_multiplier, SIZE_MULTIPLIER) #undef GEN_SES_STATUS_NV_CACHE_ACCESSORS static inline uintmax_t ses_status_nv_cache_get_cache_size(struct ses_status_nv_cache *elem) { uintmax_t cache_size; int multiplier; /* Multiplier is in units of 2^10 */ cache_size = scsi_2btoul(elem->cache_size); multiplier = 10 * ses_status_nv_cache_get_size_multiplier(elem); return (cache_size << multiplier); } /*----------------- Invalid Operation Reason Status Element ------------------*/ struct ses_status_invalid_op_reason { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_invalid_op_field_data { SES_STATUS_INVALID_OP_REASON_TYPE_BYTE = 0, SES_STATUS_INVALID_OP_REASON_TYPE_MASK = 0xC0, SES_STATUS_INVALID_OP_REASON_TYPE_SHIFT = 6, SES_STATUS_INVALID_OP_REASON_TYPE_PC_ERROR = 0x00, SES_STATUS_INVALID_OP_REASON_TYPE_PF_ERROR = 0x01, SES_STATUS_INVALID_OP_REASON_TYPE_VS_ERROR = 0x03, SES_STATUS_INVALID_OP_REASON_PC_ERROR_PC_NOT_SUPPORTED_BYTE = 0, SES_STATUS_INVALID_OP_REASON_PC_ERROR_PC_NOT_SUPPORTED_MASK = 0x01, SES_STATUS_INVALID_OP_REASON_PC_ERROR_PC_NOT_SUPPORTED_SHIFT = 0, SES_STATUS_INVALID_OP_REASON_PF_ERROR_BIT_NUMBER_BYTE = 0, SES_STATUS_INVALID_OP_REASON_PF_ERROR_BIT_NUMBER_MASK = 0x03, SES_STATUS_INVALID_OP_REASON_PF_ERROR_BIT_NUMBER_SHIFT = 0 }; #define GEN_SES_STATUS_INVALID_OP_REASON_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_invalid_op_reason, SES_STATUS_INVALID_OP_REASON, \ LCASE, UCASE) GEN_SES_STATUS_INVALID_OP_REASON_ACCESSORS(type, TYPE) GEN_SES_STATUS_INVALID_OP_REASON_ACCESSORS(pc_error_pc_not_supported, PC_ERROR_PC_NOT_SUPPORTED) GEN_SES_STATUS_INVALID_OP_REASON_ACCESSORS(pf_error_bit_number, PF_ERROR_BIT_NUMBER) #undef GEN_SES_STATUS_INVALID_OP_ACCESSORS /*--------------- Uninterruptible Power Supply Status Element ----------------*/ struct ses_status_ups { struct ses_status_common common; /* Minutes of remaining capacity. */ uint8_t battery_status; uint8_t bytes[2]; }; enum ses_status_ups_field_data { SES_STATUS_UPS_AC_LO_BYTE = 0, SES_STATUS_UPS_AC_LO_MASK = 0x80, SES_STATUS_UPS_AC_LO_SHIFT = 7, SES_STATUS_UPS_AC_HI_BYTE = 0, SES_STATUS_UPS_AC_HI_MASK = 0x40, SES_STATUS_UPS_AC_HI_SHIFT = 6, SES_STATUS_UPS_AC_QUAL_BYTE = 0, SES_STATUS_UPS_AC_QUAL_MASK = 0x20, SES_STATUS_UPS_AC_QUAL_SHIFT = 5, SES_STATUS_UPS_AC_FAIL_BYTE = 0, SES_STATUS_UPS_AC_FAIL_MASK = 0x10, SES_STATUS_UPS_AC_FAIL_SHIFT = 4, SES_STATUS_UPS_DC_FAIL_BYTE = 0, SES_STATUS_UPS_DC_FAIL_MASK = 0x08, SES_STATUS_UPS_DC_FAIL_SHIFT = 3, SES_STATUS_UPS_UPS_FAIL_BYTE = 0, SES_STATUS_UPS_UPS_FAIL_MASK = 0x04, SES_STATUS_UPS_UPS_FAIL_SHIFT = 2, SES_STATUS_UPS_WARN_BYTE = 0, SES_STATUS_UPS_WARN_MASK = 0x02, SES_STATUS_UPS_WARN_SHIFT = 1, SES_STATUS_UPS_INTF_FAIL_BYTE = 0, SES_STATUS_UPS_INTF_FAIL_MASK = 0x01, SES_STATUS_UPS_INTF_FAIL_SHIFT = 0, SES_STATUS_UPS_IDENT_BYTE = 0, SES_STATUS_UPS_IDENT_MASK = 0x80, SES_STATUS_UPS_IDENT_SHIFT = 7, SES_STATUS_UPS_FAIL_BYTE = 1, SES_STATUS_UPS_FAIL_MASK = 0x40, SES_STATUS_UPS_FAIL_SHIFT = 6, SES_STATUS_UPS_BATT_FAIL_BYTE = 1, SES_STATUS_UPS_BATT_FAIL_MASK = 0x02, SES_STATUS_UPS_BATT_FAIL_SHIFT = 1, SES_STATUS_UPS_BPF_BYTE = 1, SES_STATUS_UPS_BPF_MASK = 0x01, SES_STATUS_UPS_BPF_SHIFT = 0 }; #define GEN_SES_STATUS_UPS_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_ups, SES_STATUS_UPS, LCASE, UCASE) GEN_SES_STATUS_UPS_ACCESSORS(ac_lo, AC_LO) GEN_SES_STATUS_UPS_ACCESSORS(ac_hi, AC_HI) GEN_SES_STATUS_UPS_ACCESSORS(ac_qual, AC_QUAL) GEN_SES_STATUS_UPS_ACCESSORS(ac_fail, AC_FAIL) GEN_SES_STATUS_UPS_ACCESSORS(dc_fail, DC_FAIL) GEN_SES_STATUS_UPS_ACCESSORS(ups_fail, UPS_FAIL) GEN_SES_STATUS_UPS_ACCESSORS(warn, WARN) GEN_SES_STATUS_UPS_ACCESSORS(intf_fail, INTF_FAIL) GEN_SES_STATUS_UPS_ACCESSORS(ident, IDENT) GEN_SES_STATUS_UPS_ACCESSORS(fail, FAIL) GEN_SES_STATUS_UPS_ACCESSORS(batt_fail, BATT_FAIL) GEN_SES_STATUS_UPS_ACCESSORS(bpf, BPF) #undef GEN_SES_STATUS_UPS_ACCESSORS /*-------------------------- Display Status Element --------------------------*/ struct ses_status_display { struct ses_status_common common; uint8_t bytes[1]; uint8_t display_character[2]; }; enum ses_status_display_field_data { SES_STATUS_DISPLAY_IDENT_BYTE = 0, SES_STATUS_DISPLAY_IDENT_MASK = 0x80, SES_STATUS_DISPLAY_IDENT_SHIFT = 7, SES_STATUS_DISPLAY_FAIL_BYTE = 0, SES_STATUS_DISPLAY_FAIL_MASK = 0x40, SES_STATUS_DISPLAY_FAIL_SHIFT = 6, SES_STATUS_DISPLAY_DISPLAY_MODE_BYTE = 0, SES_STATUS_DISPLAY_DISPLAY_MODE_MASK = 0x03, SES_STATUS_DISPLAY_DISPLAY_MODE_SHIFT = 6, SES_STATUS_DISPLAY_DISPLAY_MODE_DC_FIELD_UNSUPP = 0x0, SES_STATUS_DISPLAY_DISPLAY_MODE_DC_FIELD_SUPP = 0x1, SES_STATUS_DISPLAY_DISPLAY_MODE_DC_FIELD = 0x2 }; #define GEN_SES_STATUS_DISPLAY_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_display, SES_STATUS_DISPLAY, LCASE, UCASE) GEN_SES_STATUS_DISPLAY_ACCESSORS(ident, IDENT) GEN_SES_STATUS_DISPLAY_ACCESSORS(fail, FAIL) GEN_SES_STATUS_DISPLAY_ACCESSORS(display_mode, DISPLAY_MODE) #undef GEN_SES_STATUS_DISPLAY_ACCESSORS /*----------------------- Key Pad Entry Status Element -----------------------*/ struct ses_status_key_pad_entry { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_key_pad_entry_field_data { SES_STATUS_KEY_PAD_ENTRY_IDENT_BYTE = 0, SES_STATUS_KEY_PAD_ENTRY_IDENT_MASK = 0x80, SES_STATUS_KEY_PAD_ENTRY_IDENT_SHIFT = 7, SES_STATUS_KEY_PAD_ENTRY_FAIL_BYTE = 0, SES_STATUS_KEY_PAD_ENTRY_FAIL_MASK = 0x40, SES_STATUS_KEY_PAD_ENTRY_FAIL_SHIFT = 6 }; #define GEN_SES_STATUS_KEY_PAD_ENTRY_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_key_pad_entry, SES_STATUS_KEY_PAD_ENTRY, LCASE, UCASE) GEN_SES_STATUS_KEY_PAD_ENTRY_ACCESSORS(ident, IDENT) GEN_SES_STATUS_KEY_PAD_ENTRY_ACCESSORS(fail, FAIL) #undef GEN_SES_STATUS_KEY_PAD_ENTRY_ACCESSORS /*------------------------- Enclosure Status Element -------------------------*/ struct ses_status_enclosure { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_enclosure_field_data { SES_STATUS_ENCLOSURE_IDENT_BYTE = 0, SES_STATUS_ENCLOSURE_IDENT_MASK = 0x80, SES_STATUS_ENCLOSURE_IDENT_SHIFT = 7, SES_STATUS_ENCLOSURE_TIME_UNTIL_POWER_CYCLE_BYTE = 1, SES_STATUS_ENCLOSURE_TIME_UNTIL_POWER_CYCLE_MASK = 0xFC, SES_STATUS_ENCLOSURE_TIME_UNTIL_POWER_CYCLE_SHIFT = 2, SES_STATUS_ENCLOSURE_FAIL_BYTE = 1, SES_STATUS_ENCLOSURE_FAIL_MASK = 0x02, SES_STATUS_ENCLOSURE_FAIL_SHIFT = 1, SES_STATUS_ENCLOSURE_WARN_BYTE = 1, SES_STATUS_ENCLOSURE_WARN_MASK = 0x01, SES_STATUS_ENCLOSURE_WARN_SHIFT = 0, SES_STATUS_ENCLOSURE_REQUESTED_POWER_OFF_DURATION_BYTE = 2, SES_STATUS_ENCLOSURE_REQUESTED_POWER_OFF_DURATION_MASK = 0xFC, SES_STATUS_ENCLOSURE_REQUESTED_POWER_OFF_DURATION_SHIFT = 2, SES_STATUS_ENCLOSURE_REQUESTED_POWER_OFF_DURATION_MAX_AUTO = 60, SES_STATUS_ENCLOSURE_REQUESTED_POWER_OFF_DURATION_MANUAL = 63, SES_STATUS_ENCLOSURE_REQUESTED_FAIL_BYTE = 2, SES_STATUS_ENCLOSURE_REQUESTED_FAIL_MASK = 0x02, SES_STATUS_ENCLOSURE_REQUESTED_FAIL_SHIFT = 1, SES_STATUS_ENCLOSURE_REQUESTED_WARN_BYTE = 2, SES_STATUS_ENCLOSURE_REQUESTED_WARN_MASK = 0x01, SES_STATUS_ENCLOSURE_REQUESTED_WARN_SHIFT = 0 }; #define GEN_SES_STATUS_ENCLOSURE_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_enclosure, SES_STATUS_ENCLOSURE, LCASE, UCASE) GEN_SES_STATUS_ENCLOSURE_ACCESSORS(ident, IDENT) GEN_SES_STATUS_ENCLOSURE_ACCESSORS(time_until_power_cycle, TIME_UNTIL_POWER_CYCLE) GEN_SES_STATUS_ENCLOSURE_ACCESSORS(fail, FAIL) GEN_SES_STATUS_ENCLOSURE_ACCESSORS(warn, WARN) GEN_SES_STATUS_ENCLOSURE_ACCESSORS(requested_power_off_duration, REQUESTED_POWER_OFF_DURATION) GEN_SES_STATUS_ENCLOSURE_ACCESSORS(requested_fail, REQUESTED_FAIL) GEN_SES_STATUS_ENCLOSURE_ACCESSORS(requested_warn, REQUESTED_WARN) #undef GEN_SES_STATUS_ENCLOSURE_ACCESSORS /*------------------- SCSI Port/Transceiver Status Element -------------------*/ struct ses_status_scsi_port_or_xcvr { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_scsi_port_or_xcvr_field_data { SES_STATUS_SCSI_PORT_OR_XCVR_IDENT_BYTE = 0, SES_STATUS_SCSI_PORT_OR_XCVR_IDENT_MASK = 0x80, SES_STATUS_SCSI_PORT_OR_XCVR_IDENT_SHIFT = 7, SES_STATUS_SCSI_PORT_OR_XCVR_FAIL_BYTE = 0, SES_STATUS_SCSI_PORT_OR_XCVR_FAIL_MASK = 0x40, SES_STATUS_SCSI_PORT_OR_XCVR_FAIL_SHIFT = 6, SES_STATUS_SCSI_PORT_OR_XCVR_REPORT_BYTE = 1, SES_STATUS_SCSI_PORT_OR_XCVR_REPORT_MASK = 0x01, SES_STATUS_SCSI_PORT_OR_XCVR_REPORT_SHIFT = 0, SES_STATUS_SCSI_PORT_OR_XCVR_DISABLED_BYTE = 2, SES_STATUS_SCSI_PORT_OR_XCVR_DISABLED_MASK = 0x10, SES_STATUS_SCSI_PORT_OR_XCVR_DISABLED_SHIFT = 4, SES_STATUS_SCSI_PORT_OR_XCVR_LOL_BYTE = 2, SES_STATUS_SCSI_PORT_OR_XCVR_LOL_MASK = 0x02, SES_STATUS_SCSI_PORT_OR_XCVR_LOL_SHIFT = 1, SES_STATUS_SCSI_PORT_OR_XCVR_XMIT_FAIL_BYTE = 2, SES_STATUS_SCSI_PORT_OR_XCVR_XMIT_FAIL_MASK = 0x01, SES_STATUS_SCSI_PORT_OR_XCVR_XMIT_FAIL_SHIFT = 0 }; #define GEN_SES_STATUS_SCSI_PORT_OR_XCVR_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_scsi_port_or_xcvr, SES_STATUS_SCSI_PORT_OR_XCVR,\ LCASE, UCASE) GEN_SES_STATUS_SCSI_PORT_OR_XCVR_ACCESSORS(ident, IDENT) GEN_SES_STATUS_SCSI_PORT_OR_XCVR_ACCESSORS(fail, FAIL) GEN_SES_STATUS_SCSI_PORT_OR_XCVR_ACCESSORS(report, REPORT) GEN_SES_STATUS_SCSI_PORT_OR_XCVR_ACCESSORS(disable, DISABLED) GEN_SES_STATUS_SCSI_PORT_OR_XCVR_ACCESSORS(lol, LOL) GEN_SES_STATUS_SCSI_PORT_OR_XCVR_ACCESSORS(xmit_fail, XMIT_FAIL) #undef GEN_SES_STATUS_SCSI_PORT_OR_XCVR_ACCESSORS /*------------------------- Language Status Element --------------------------*/ struct ses_status_language { struct ses_status_common common; uint8_t bytes[1]; uint8_t language_code[2]; }; enum ses_status_language_field_data { SES_STATUS_LANGUAGE_IDENT_BYTE = 0, SES_STATUS_LANGUAGE_IDENT_MASK = 0x80, SES_STATUS_LANGUAGE_IDENT_SHIFT = 7 }; #define GEN_SES_STATUS_LANGUAGE_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_language, SES_STATUS_LANGUAGE, LCASE, UCASE) GEN_SES_STATUS_LANGUAGE_ACCESSORS(ident, IDENT) #undef GEN_SES_STATUS_LANGUAGE_ACCESSORS /*-------------------- Communication Port Status Element ---------------------*/ struct ses_status_comm_port { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_comm_port_field_data { SES_STATUS_COMM_PORT_IDENT_BYTE = 0, SES_STATUS_COMM_PORT_IDENT_MASK = 0x80, SES_STATUS_COMM_PORT_IDENT_SHIFT = 7, SES_STATUS_COMM_PORT_FAIL_BYTE = 0, SES_STATUS_COMM_PORT_FAIL_MASK = 0x40, SES_STATUS_COMM_PORT_FAIL_SHIFT = 6, SES_STATUS_COMM_PORT_DISABLED_BYTE = 2, SES_STATUS_COMM_PORT_DISABLED_MASK = 0x01, SES_STATUS_COMM_PORT_DISABLED_SHIFT = 0 }; #define GEN_SES_STATUS_COMM_PORT_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_comm_port, SES_STATUS_COMM_PORT, LCASE, UCASE) GEN_SES_STATUS_COMM_PORT_ACCESSORS(ident, IDENT) GEN_SES_STATUS_COMM_PORT_ACCESSORS(fail, FAIL) GEN_SES_STATUS_COMM_PORT_ACCESSORS(disabled, DISABLED) #undef GEN_SES_STATUS_COMM_PORT_ACCESSORS /*---------------------- Voltage Sensor Status Element -----------------------*/ struct ses_status_voltage_sensor { struct ses_status_common common; uint8_t bytes[1]; uint8_t voltage[2]; }; enum ses_status_voltage_sensor_field_data { SES_STATUS_VOLTAGE_SENSOR_IDENT_BYTE = 0, SES_STATUS_VOLTAGE_SENSOR_IDENT_MASK = 0x80, SES_STATUS_VOLTAGE_SENSOR_IDENT_SHIFT = 7, SES_STATUS_VOLTAGE_SENSOR_FAIL_BYTE = 0, SES_STATUS_VOLTAGE_SENSOR_FAIL_MASK = 0x40, SES_STATUS_VOLTAGE_SENSOR_FAIL_SHIFT = 6, SES_STATUS_VOLTAGE_SENSOR_WARN_OVER_BYTE = 0, SES_STATUS_VOLTAGE_SENSOR_WARN_OVER_MASK = 0x08, SES_STATUS_VOLTAGE_SENSOR_WARN_OVER_SHIFT = 3, SES_STATUS_VOLTAGE_SENSOR_WARN_UNDER_BYTE = 0, SES_STATUS_VOLTAGE_SENSOR_WARN_UNDER_MASK = 0x04, SES_STATUS_VOLTAGE_SENSOR_WARN_UNDER_SHIFT = 2, SES_STATUS_VOLTAGE_SENSOR_CRIT_OVER_BYTE = 0, SES_STATUS_VOLTAGE_SENSOR_CRIT_OVER_MASK = 0x02, SES_STATUS_VOLTAGE_SENSOR_CRIT_OVER_SHIFT = 1, SES_STATUS_VOLTAGE_SENSOR_CRIT_UNDER_BYTE = 0, SES_STATUS_VOLTAGE_SENSOR_CRIT_UNDER_MASK = 0x01, SES_STATUS_VOLTAGE_SENSOR_CRIT_UNDER_SHIFT = 0 }; #define GEN_SES_STATUS_VOLTAGE_SENSOR_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_voltage_sensor, SES_STATUS_VOLTAGE_SENSOR, \ LCASE, UCASE) GEN_SES_STATUS_VOLTAGE_SENSOR_ACCESSORS(ident, IDENT) GEN_SES_STATUS_VOLTAGE_SENSOR_ACCESSORS(fail, FAIL) GEN_SES_STATUS_VOLTAGE_SENSOR_ACCESSORS(warn_over, WARN_OVER) GEN_SES_STATUS_VOLTAGE_SENSOR_ACCESSORS(warn_under, WARN_UNDER) GEN_SES_STATUS_VOLTAGE_SENSOR_ACCESSORS(crit_over, CRIT_OVER) GEN_SES_STATUS_VOLTAGE_SENSOR_ACCESSORS(crit_under, CRIT_UNDER) #undef GEN_SES_STATUS_VOLTAGE_SENSOR_ACCESSORS /*---------------------- Current Sensor Status Element -----------------------*/ struct ses_status_current_sensor { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_current_sensor_field_data { SES_STATUS_CURRENT_SENSOR_IDENT_BYTE = 0, SES_STATUS_CURRENT_SENSOR_IDENT_MASK = 0x80, SES_STATUS_CURRENT_SENSOR_IDENT_SHIFT = 7, SES_STATUS_CURRENT_SENSOR_FAIL_BYTE = 0, SES_STATUS_CURRENT_SENSOR_FAIL_MASK = 0x40, SES_STATUS_CURRENT_SENSOR_FAIL_SHIFT = 6, SES_STATUS_CURRENT_SENSOR_WARN_OVER_BYTE = 0, SES_STATUS_CURRENT_SENSOR_WARN_OVER_MASK = 0x08, SES_STATUS_CURRENT_SENSOR_WARN_OVER_SHIFT = 3, SES_STATUS_CURRENT_SENSOR_CRIT_OVER_BYTE = 0, SES_STATUS_CURRENT_SENSOR_CRIT_OVER_MASK = 0x02, SES_STATUS_CURRENT_SENSOR_CRIT_OVER_SHIFT = 1 }; #define GEN_SES_STATUS_CURRENT_SENSOR_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_current_sensor, SES_STATUS_CURRENT_SENSOR, \ LCASE, UCASE) GEN_SES_STATUS_CURRENT_SENSOR_ACCESSORS(ident, IDENT) GEN_SES_STATUS_CURRENT_SENSOR_ACCESSORS(fail, FAIL) GEN_SES_STATUS_CURRENT_SENSOR_ACCESSORS(warn_over, WARN_OVER) GEN_SES_STATUS_CURRENT_SENSOR_ACCESSORS(crit_over, CRIT_OVER) #undef GEN_SES_STATUS_CURRENT_SENSOR_ACCESSORS /*--------------------- SCSI Target Port Status Element ----------------------*/ struct ses_status_target_port { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_scsi_target_port_field_data { SES_STATUS_TARGET_PORT_IDENT_BYTE = 0, SES_STATUS_TARGET_PORT_IDENT_MASK = 0x80, SES_STATUS_TARGET_PORT_IDENT_SHIFT = 7, SES_STATUS_TARGET_PORT_FAIL_BYTE = 0, SES_STATUS_TARGET_PORT_FAIL_MASK = 0x40, SES_STATUS_TARGET_PORT_FAIL_SHIFT = 6, SES_STATUS_TARGET_PORT_REPORT_BYTE = 1, SES_STATUS_TARGET_PORT_REPORT_MASK = 0x01, SES_STATUS_TARGET_PORT_REPORT_SHIFT = 0, SES_STATUS_TARGET_PORT_ENABLED_BYTE = 2, SES_STATUS_TARGET_PORT_ENABLED_MASK = 0x01, SES_STATUS_TARGET_PORT_ENABLED_SHIFT = 0 }; #define GEN_SES_STATUS_TARGET_PORT_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_target_port, SES_STATUS_TARGET_PORT, LCASE, UCASE) GEN_SES_STATUS_TARGET_PORT_ACCESSORS(ident, IDENT) GEN_SES_STATUS_TARGET_PORT_ACCESSORS(fail, FAIL) GEN_SES_STATUS_TARGET_PORT_ACCESSORS(report, REPORT) GEN_SES_STATUS_TARGET_PORT_ACCESSORS(enabled, ENABLED) #undef GEN_SES_STATUS_TARGET_PORT_ACCESSORS /*-------------------- SCSI Initiator Port Status Element --------------------*/ struct ses_status_initiator_port { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_scsi_initiator_port_field_data { SES_STATUS_INITIATOR_PORT_IDENT_BYTE = 0, SES_STATUS_INITIATOR_PORT_IDENT_MASK = 0x80, SES_STATUS_INITIATOR_PORT_IDENT_SHIFT = 7, SES_STATUS_INITIATOR_PORT_FAIL_BYTE = 0, SES_STATUS_INITIATOR_PORT_FAIL_MASK = 0x40, SES_STATUS_INITIATOR_PORT_FAIL_SHIFT = 6, SES_STATUS_INITIATOR_PORT_REPORT_BYTE = 1, SES_STATUS_INITIATOR_PORT_REPORT_MASK = 0x01, SES_STATUS_INITIATOR_PORT_REPORT_SHIFT = 0, SES_STATUS_INITIATOR_PORT_ENABLED_BYTE = 2, SES_STATUS_INITIATOR_PORT_ENABLED_MASK = 0x01, SES_STATUS_INITIATOR_PORT_ENABLED_SHIFT = 0 }; #define GEN_SES_STATUS_INITIATOR_PORT_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_initiator_port, SES_STATUS_INITIATOR_PORT, \ LCASE, UCASE) GEN_SES_STATUS_INITIATOR_PORT_ACCESSORS(ident, IDENT) GEN_SES_STATUS_INITIATOR_PORT_ACCESSORS(fail, FAIL) GEN_SES_STATUS_INITIATOR_PORT_ACCESSORS(report, REPORT) GEN_SES_STATUS_INITIATOR_PORT_ACCESSORS(enabled, ENABLED) #undef GEN_SES_STATUS_INITIATOR_PORT_ACCESSORS /*-------------------- Simple Subenclosure Status Element --------------------*/ struct ses_status_simple_subses { struct ses_status_common common; uint8_t bytes[2]; uint8_t short_enclosure_status; }; enum ses_status_simple_subses_field_data { SES_STATUS_SIMPlE_SUBSES_IDENT_BYTE = 0, SES_STATUS_SIMPlE_SUBSES_IDENT_MASK = 0x80, SES_STATUS_SIMPlE_SUBSES_IDENT_SHIFT = 7, SES_STATUS_SIMPlE_SUBSES_FAIL_BYTE = 0, SES_STATUS_SIMPlE_SUBSES_FAIL_MASK = 0x40, SES_STATUS_SIMPlE_SUBSES_FAIL_SHIFT = 6 }; #define GEN_SES_STATUS_SIMPlE_SUBSES_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_simple_subses, SES_STATUS_SIMPlE_SUBSES, \ LCASE, UCASE) GEN_SES_STATUS_SIMPlE_SUBSES_ACCESSORS(ident, IDENT) GEN_SES_STATUS_SIMPlE_SUBSES_ACCESSORS(fail, FAIL) #undef GEN_SES_STATUS_SIMPlE_SUBSES_ACCESSORS /*----------------------- SAS Expander Status Element ------------------------*/ struct ses_status_sas_expander { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_sas_expander_field_data { SES_STATUS_SAS_EXPANDER_IDENT_BYTE = 0, SES_STATUS_SAS_EXPANDER_IDENT_MASK = 0x80, SES_STATUS_SAS_EXPANDER_IDENT_SHIFT = 7, SES_STATUS_SAS_EXPANDER_FAIL_BYTE = 0, SES_STATUS_SAS_EXPANDER_FAIL_MASK = 0x40, SES_STATUS_SAS_EXPANDER_FAIL_SHIFT = 6 }; #define GEN_SES_STATUS_SAS_EXPANDER_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_sas_expander, SES_STATUS_SAS_EXPANDER, LCASE, UCASE) GEN_SES_STATUS_SAS_EXPANDER_ACCESSORS(ident, IDENT) GEN_SES_STATUS_SAS_EXPANDER_ACCESSORS(fail, FAIL) #undef GEN_SES_STATUS_SAS_EXPANDER_ACCESSORS /*----------------------- SAS Connector Status Element -----------------------*/ struct ses_status_sas_connector { struct ses_status_common common; uint8_t bytes[3]; }; enum ses_status_sas_connector_field_data { SES_STATUS_SAS_CONNECTOR_IDENT_BYTE = 0, SES_STATUS_SAS_CONNECTOR_IDENT_MASK = 0x80, SES_STATUS_SAS_CONNECTOR_IDENT_SHIFT = 7, SES_STATUS_SAS_CONNECTOR_TYPE_BYTE = 0, SES_STATUS_SAS_CONNECTOR_TYPE_MASK = 0x7F, SES_STATUS_SAS_CONNECTOR_TYPE_SHIFT = 0, SES_STATUS_SAS_CONNECTOR_PHYS_LINK_BYTE = 1, SES_STATUS_SAS_CONNECTOR_PHYS_LINK_MASK = 0xFF, SES_STATUS_SAS_CONNECTOR_PHYS_LINK_SHIFT = 0, SES_STATUS_SAS_CONNECTOR_PHYS_LINK_ALL = 0xFF, SES_STATUS_SAS_CONNECTOR_FAIL_BYTE = 2, SES_STATUS_SAS_CONNECTOR_FAIL_MASK = 0x40, SES_STATUS_SAS_CONNECTOR_FAIL_SHIFT = 6, }; #define GEN_SES_STATUS_SAS_CONNECTOR_ACCESSORS(LCASE, UCASE) \ GEN_GETTER(ses_status_sas_connector, SES_STATUS_SAS_CONNECTOR, \ LCASE, UCASE) GEN_SES_STATUS_SAS_CONNECTOR_ACCESSORS(ident, IDENT) GEN_SES_STATUS_SAS_CONNECTOR_ACCESSORS(type, TYPE) GEN_SES_STATUS_SAS_CONNECTOR_ACCESSORS(phys_link, PHYS_LINK) GEN_SES_STATUS_SAS_CONNECTOR_ACCESSORS(fail, FAIL) #undef GEN_SES_STATUS_SAS_CONNECTOR_ACCESSORS /*------------------------- Universal Status Element -------------------------*/ union ses_status_element { struct ses_status_common common; struct ses_status_dev_slot dev_slot; struct ses_status_array_dev_slot array_dev_slot; struct ses_status_power_supply power_supply; struct ses_status_cooling cooling; struct ses_status_temp_sensor temp_sensor; struct ses_status_door_lock door_lock; struct ses_status_audible_alarm audible_alarm; struct ses_status_ecc_electronics ecc_electronics; struct ses_status_scc_electronics scc_electronics; struct ses_status_nv_cache nv_cache; struct ses_status_invalid_op_reason invalid_op_reason; struct ses_status_ups ups; struct ses_status_display display; struct ses_status_key_pad_entry key_pad_entry; struct ses_status_scsi_port_or_xcvr scsi_port_or_xcvr; struct ses_status_language language; struct ses_status_comm_port comm_port; struct ses_status_voltage_sensor voltage_sensor; struct ses_status_current_sensor current_sensor; struct ses_status_target_port target_port; struct ses_status_initiator_port initiator_port; struct ses_status_simple_subses simple_subses; struct ses_status_sas_expander sas_expander; struct ses_status_sas_connector sas_connector; uint8_t bytes[4]; }; /*===================== SCSI SES Status Diagnostic Page =====================*/ struct ses_status_page { struct ses_page_hdr hdr; union ses_status_element elements[]; }; enum ses_status_page_field_data { SES_STATUS_PAGE_INVOP_MASK = 0x10, SES_STATUS_PAGE_INVOP_SHIFT = 4, SES_STATUS_PAGE_INFO_MASK = 0x08, SES_STATUS_PAGE_INFO_SHIFT = 3, SES_STATUS_PAGE_NON_CRIT_MASK = 0x04, SES_STATUS_PAGE_NON_CRIT_SHIFT = 2, SES_STATUS_PAGE_CRIT_MASK = 0x02, SES_STATUS_PAGE_CRIT_SHIFT = 1, SES_STATUS_PAGE_UNRECOV_MASK = 0x01, SES_STATUS_PAGE_UNRECOV_SHIFT = 0, SES_STATUS_PAGE_CHANGED_MASK = SES_STATUS_PAGE_INVOP_MASK | SES_STATUS_PAGE_INFO_MASK | SES_STATUS_PAGE_NON_CRIT_MASK | SES_STATUS_PAGE_CRIT_MASK | SES_STATUS_PAGE_UNRECOV_MASK, SES_STATUS_PAGE_CHANGED_SHIFT = 0, }; #define GEN_SES_STATUS_PAGE_ACCESSORS(LCASE, UCASE) \ GEN_HDR_ACCESSORS(ses_status_page, SES_STATUS_PAGE, LCASE, UCASE) GEN_SES_STATUS_PAGE_ACCESSORS(invop, INVOP) GEN_SES_STATUS_PAGE_ACCESSORS(info, INFO) GEN_SES_STATUS_PAGE_ACCESSORS(non_crit, NON_CRIT) GEN_SES_STATUS_PAGE_ACCESSORS(crit, CRIT) GEN_SES_STATUS_PAGE_ACCESSORS(unrecov, UNRECOV) GEN_SES_STATUS_PAGE_ACCESSORS(changed, CHANGED) #undef GEN_SES_STATUS_PAGE_ACCESSORS /*================ SCSI SES Element Descriptor Diagnostic Page ===============*/ struct ses_elem_descr { uint8_t reserved[2]; uint8_t length[2]; char description[]; }; struct ses_elem_descr_page { struct ses_page_hdr hdr; struct ses_elem_descr descrs[]; }; /*============ SCSI SES Additional Element Status Diagnostic Page ============*/ struct ses_addl_elem_status_page { struct ses_page_hdr hdr; }; /*====================== Legacy (Deprecated) Structures ======================*/ struct ses_control_page_hdr { uint8_t page_code; uint8_t control_flags; uint8_t length[2]; uint8_t gen_code[4]; /* Followed by variable length array of descriptors. */ }; struct ses_status_page_hdr { uint8_t page_code; uint8_t status_flags; uint8_t length[2]; uint8_t gen_code[4]; /* Followed by variable length array of descriptors. */ }; /* ses_page_hdr.reserved values */ /* * Enclosure Status Diagnostic Page: * uint8_t reserved : 3, * invop : 1, * info : 1, * noncritical : 1, * critical : 1, * unrecov : 1; */ #define SES_ENCSTAT_UNRECOV 0x01 #define SES_ENCSTAT_CRITICAL 0x02 #define SES_ENCSTAT_NONCRITICAL 0x04 #define SES_ENCSTAT_INFO 0x08 #define SES_ENCSTAT_INVOP 0x10 /* Status mask: All of the above OR'd together */ #define SES_STATUS_MASK 0x1f #define SES_SET_STATUS_MASK 0xf /* Element Descriptor Diagnostic Page: unused */ /* Additional Element Status Diagnostic Page: unused */ /* Summary SES Status Defines, Common Status Codes */ #define SES_OBJSTAT_UNSUPPORTED 0 #define SES_OBJSTAT_OK 1 #define SES_OBJSTAT_CRIT 2 #define SES_OBJSTAT_NONCRIT 3 #define SES_OBJSTAT_UNRECOV 4 #define SES_OBJSTAT_NOTINSTALLED 5 #define SES_OBJSTAT_UNKNOWN 6 #define SES_OBJSTAT_NOTAVAIL 7 #define SES_OBJSTAT_NOACCESS 8 /* * For control pages, cstat[0] is the same for the * enclosure and is common across all device types. * * If SESCTL_CSEL is set, then PRDFAIL, DISABLE and RSTSWAP * are checked, otherwise bits that are specific to the device * type in the other 3 bytes of cstat or checked. */ #define SESCTL_CSEL 0x80 #define SESCTL_PRDFAIL 0x40 #define SESCTL_DISABLE 0x20 #define SESCTL_RSTSWAP 0x10 /* Control bits, Device Elements, byte 2 */ #define SESCTL_DRVLCK 0x40 /* "DO NOT REMOVE" */ #define SESCTL_RQSINS 0x08 /* RQST INSERT */ #define SESCTL_RQSRMV 0x04 /* RQST REMOVE */ #define SESCTL_RQSID 0x02 /* RQST IDENT */ /* Control bits, Device Elements, byte 3 */ #define SESCTL_RQSFLT 0x20 /* RQST FAULT */ #define SESCTL_DEVOFF 0x10 /* DEVICE OFF */ /* Control bits, Generic, byte 3 */ #define SESCTL_RQSTFAIL 0x40 #define SESCTL_RQSTON 0x20 /* * Getting text for an object type is a little * trickier because it's string data that can * go up to 64 KBytes. Build this union and * fill the obj_id with the id of the object who's * help text you want, and if text is available, * obj_text will be filled in, null terminated. */ typedef union { unsigned int obj_id; char obj_text[1]; } ses_hlptxt; /*============================================================================*/ struct ses_elm_desc_hdr { uint8_t reserved[2]; uint8_t length[2]; }; /* * SES v2 r20 6.1.13 - Element Additional Status diagnostic page * Tables 26-28 (general), 29-32 (FC), 33-41 (SAS) * * Protocol identifier uses definitions in scsi_all.h; * SPSP_PROTO_FC, SPSP_PROTO_SAS are the only ones used here. */ struct ses_elm_fc_eip_hdr { uint8_t num_phys; uint8_t reserved[2]; uint8_t dev_slot_num; uint8_t node_name[8]; }; struct ses_elm_fc_noneip_hdr { uint8_t num_phys; uint8_t reserved; uint8_t node_name[8]; }; struct ses_elm_fc_base_hdr { uint8_t num_phys; }; union ses_elm_fc_hdr { struct ses_elm_fc_base_hdr base_hdr; struct ses_elm_fc_eip_hdr eip_hdr; struct ses_elm_fc_noneip_hdr noneip_hdr; }; struct ses_elm_fc_port { uint8_t port_loop_position; uint8_t bypass_reason; #define SES_FC_PORT_BYPASS_UNBYPASSED 0x00 #define SES_FC_PORT_BYPASS_LINKFAIL_RATE_TOO_HIGH 0x10 #define SES_FC_PORT_BYPASS_SYNC_LOSS_RATE_TOO_HIGH 0x11 #define SES_FC_PORT_BYPASS_SIGNAL_LOSS_RATE_TOO_HIGH 0x12 #define SES_FC_PORT_BYPASS_SEQPROTO_ERR_RATE_TOO_HIGH 0x13 #define SES_FC_PORT_BYPASS_INVAL_XMIT_RATE_TOO_HIGH 0x14 #define SES_FC_PORT_BYPASS_CRC_ERR_RATE_TOO_HIGH 0x15 #define SES_FC_PORT_BYPASS_ERR_RATE_RESERVED_BEGIN 0x16 #define SES_FC_PORT_BYPASS_ERR_RATE_RESERVED_END 0x1F #define SES_FC_PORT_BYPASS_LINKFAIL_COUNT_TOO_HIGH 0x20 #define SES_FC_PORT_BYPASS_SYNC_LOSS_COUNT_TOO_HIGH 0x21 #define SES_FC_PORT_BYPASS_SIGNAL_LOSS_COUNT_TOO_HIGH 0x22 #define SES_FC_PORT_BYPASS_SEQPROTO_ERR_COUNT_TOO_HIGH 0x23 #define SES_FC_PORT_BYPASS_INVAL_XMIT_COUNT_TOO_HIGH 0x24 #define SES_FC_PORT_BYPASS_CRC_ERR_COUNT_TOO_HIGH 0x25 #define SES_FC_PORT_BYPASS_ERR_COUNT_RESERVED_BEGIN 0x26 #define SES_FC_PORT_BYPASS_ERR_COUNT_RESERVED_END 0x2F #define SES_FC_PORT_BYPASS_RESERVED_BEGIN 0x30 #define SES_FC_PORT_BYPASS_RESERVED_END 0xBF #define SES_FC_PORT_BYPASS_VENDOR_SPECIFIC_BEGIN 0xC0 #define SES_FC_PORT_BYPASS_VENDOR_SPECIFIC_END 0xFF uint8_t port_req_hard_addr; uint8_t n_port_id[3]; uint8_t n_port_name[8]; }; struct ses_elm_sas_device_phy { uint8_t byte0; /* * uint8_t reserved0 : 1, * uint8_t device_type : 3, * uint8_t reserved1 : 4; */ uint8_t reserved0; /* Bit positions for initiator and target port protocols */ #define SES_SASOBJ_DEV_PHY_SMP 0x2 #define SES_SASOBJ_DEV_PHY_STP 0x4 #define SES_SASOBJ_DEV_PHY_SSP 0x8 /* Select all of the above protocols */ #define SES_SASOBJ_DEV_PHY_PROTOMASK 0xe uint8_t initiator_ports; /* * uint8_t reserved0 : 4, * uint8_t ssp : 1, * uint8_t stp : 1, * uint8_t smp : 1, * uint8_t reserved1 : 3; */ uint8_t target_ports; /* * uint8_t sata_port_selector : 1, * uint8_t reserved : 3, * uint8_t ssp : 1, * uint8_t stp : 1, * uint8_t smp : 1, * uint8_t sata_device : 1; */ uint8_t parent_addr[8]; /* SAS address of parent */ uint8_t phy_addr[8]; /* SAS address of this phy */ uint8_t phy_id; uint8_t reserved1[7]; }; #ifdef _KERNEL int ses_elm_sas_dev_phy_sata_dev(struct ses_elm_sas_device_phy *); int ses_elm_sas_dev_phy_sata_port(struct ses_elm_sas_device_phy *); int ses_elm_sas_dev_phy_dev_type(struct ses_elm_sas_device_phy *); #endif /* _KERNEL */ struct ses_elm_sas_expander_phy { uint8_t connector_index; uint8_t other_index; }; struct ses_elm_sas_port_phy { uint8_t phy_id; uint8_t reserved; uint8_t connector_index; uint8_t other_index; uint8_t phy_addr[8]; }; struct ses_elm_sas_type0_base_hdr { uint8_t num_phys; uint8_t byte1; /* * uint8_t descriptor_type : 2, * uint8_t reserved : 5, * uint8_t not_all_phys : 1; */ #define SES_SASOBJ_TYPE0_NOT_ALL_PHYS(obj) \ ((obj)->byte1 & 0x1) }; struct ses_elm_sas_type0_eip_hdr { struct ses_elm_sas_type0_base_hdr base; uint8_t reserved; uint8_t dev_slot_num; }; struct ses_elm_sas_type1_expander_hdr { uint8_t num_phys; uint8_t byte1; /* * uint8_t descriptor_type : 2, * uint8_t reserved : 6; */ uint8_t reserved[2]; uint8_t sas_addr[8]; }; struct ses_elm_sas_type1_nonexpander_hdr { uint8_t num_phys; uint8_t byte1; /* * uint8_t descriptor_type : 2, * uint8_t reserved : 6; */ uint8_t reserved[2]; }; /* NB: This is only usable for as long as the headers happen to match */ struct ses_elm_sas_base_hdr { uint8_t num_phys; uint8_t byte1; /* * uint8_t descriptor_type : 2, * uint8_t descr_specific : 6; */ #define SES_SASOBJ_TYPE_SLOT 0 #define SES_SASOBJ_TYPE_OTHER 1 }; union ses_elm_sas_hdr { struct ses_elm_sas_base_hdr base_hdr; struct ses_elm_sas_type0_base_hdr type0_noneip; struct ses_elm_sas_type0_eip_hdr type0_eip; struct ses_elm_sas_type1_expander_hdr type1_exp; struct ses_elm_sas_type1_nonexpander_hdr type1_nonexp; }; int ses_elm_sas_type0_not_all_phys(union ses_elm_sas_hdr *); int ses_elm_sas_descr_type(union ses_elm_sas_hdr *); struct ses_elm_addlstatus_base_hdr { uint8_t byte0; /* * uint8_t invalid : 1, * uint8_t reserved : 2, * uint8_t eip : 1, * uint8_t proto_id : 4; */ uint8_t length; }; int ses_elm_addlstatus_proto(struct ses_elm_addlstatus_base_hdr *); int ses_elm_addlstatus_eip(struct ses_elm_addlstatus_base_hdr *); int ses_elm_addlstatus_invalid(struct ses_elm_addlstatus_base_hdr *); struct ses_elm_addlstatus_eip_hdr { struct ses_elm_addlstatus_base_hdr base; uint8_t byte2; #define SES_ADDL_EIP_EIIOE 1 uint8_t element_index; /* NB: This define (currently) applies to all eip=1 headers */ #define SES_EIP_HDR_EXTRA_LEN 2 }; union ses_elm_addlstatus_descr_hdr { struct ses_elm_addlstatus_base_hdr base; struct ses_elm_addlstatus_eip_hdr eip; }; union ses_elm_addlstatus_proto_hdr { union ses_elm_fc_hdr fc; union ses_elm_sas_hdr sas; }; /*============================= Namespace Cleanup ============================*/ #undef GEN_HDR_ACCESSORS #undef GEN_ACCESSORS #undef GEN_HDR_SETTER #undef GEN_HDR_GETTER #undef GEN_SETTER #undef GEN_GETTER #undef MK_ENUM #endif /* _SCSI_SES_H_ */