1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
  24  */
  25 
  26 #include <sys/types.h>
  27 #include <sys/isa_defs.h>
  28 #include <sys/systeminfo.h>
  29 #include <sys/scsi/generic/commands.h>
  30 #include <sys/scsi/impl/commands.h>
  31 #include <sys/scsi/impl/uscsi.h>
  32 
  33 #include <stdio.h>
  34 #include <stdlib.h>
  35 #include <stddef.h>
  36 #include <string.h>
  37 #include <dlfcn.h>
  38 #include <limits.h>
  39 
  40 #include <scsi/libscsi.h>
  41 #include "libscsi_impl.h"
  42 
  43 static const libscsi_engine_t *
  44 get_engine(libscsi_hdl_t *hp, const char *name)
  45 {
  46         libscsi_engine_impl_t *eip;
  47         const libscsi_engine_t *ep;
  48         const char *engine_path, *p, *q;
  49         char engine_dir[MAXPATHLEN];
  50         char engine_lib[MAXPATHLEN];
  51         char init_name[MAXPATHLEN];
  52         void *dl_hdl;
  53         libscsi_engine_init_f init;
  54         boolean_t found_lib = B_FALSE, found_init = B_FALSE;
  55         int dirs_tried = 0;
  56         char isa[257];
  57 
  58         for (eip = hp->lsh_engines; eip != NULL; eip = eip->lsei_next) {
  59                 if (strcmp(eip->lsei_engine->lse_name, name) == 0)
  60                         return (eip->lsei_engine);
  61         }
  62 
  63         if ((engine_path = getenv("LIBSCSI_ENGINE_PATH")) == NULL)
  64                 engine_path = LIBSCSI_DEFAULT_ENGINE_PATH;
  65 
  66 #if defined(_LP64)
  67         if (sysinfo(SI_ARCHITECTURE_64, isa, sizeof (isa)) < 0)
  68                 isa[0] = '\0';
  69 #else
  70         isa[0] = '\0';
  71 #endif
  72 
  73         for (p = engine_path; p != NULL; p = q) {
  74                 if ((q = strchr(p, ':')) != NULL) {
  75                         ptrdiff_t len = q - p;
  76                         (void) strncpy(engine_dir, p, len);
  77                         engine_dir[len] = '\0';
  78                         while (*q == ':')
  79                                 ++q;
  80                         if (*q == '\0')
  81                                 q = NULL;
  82                         if (len == 0)
  83                                 continue;
  84                 } else {
  85                         (void) strcpy(engine_dir, p);
  86                 }
  87                 if (engine_dir[0] != '/')
  88                         continue;
  89 
  90                 ++dirs_tried;
  91 
  92                 (void) snprintf(engine_lib, MAXPATHLEN, "%s/%s/%s%s",
  93                     engine_dir, isa, name, LIBSCSI_ENGINE_EXT);
  94 
  95                 dl_hdl = dlopen(engine_lib,
  96                     RTLD_LOCAL | RTLD_LAZY | RTLD_PARENT);
  97                 if (dl_hdl == NULL) {
  98                         if (!found_lib)
  99                                 (void) libscsi_error(hp, ESCSI_NOENGINE,
 100                                     "unable to dlopen %s: %s", engine_lib,
 101                                     dlerror());
 102                         continue;
 103                 }
 104                 found_lib = B_TRUE;
 105                 (void) snprintf(init_name, MAXPATHLEN, "libscsi_%s_init", name);
 106                 init = (libscsi_engine_init_f)dlsym(dl_hdl, init_name);
 107                 if (init == NULL) {
 108                         if (!found_init)
 109                                 (void) libscsi_error(hp, ESCSI_NOENGINE,
 110                                     "failed to find %s in %s: %s", init_name,
 111                                     engine_lib, dlerror());
 112                         (void) dlclose(dl_hdl);
 113                         continue;
 114                 }
 115                 if ((ep = init(hp)) == NULL) {
 116                         (void) dlclose(dl_hdl);
 117                         /*
 118                          * libscsi errno set by init.
 119                          */
 120                         return (NULL);
 121                 }
 122                 if (ep->lse_libversion != hp->lsh_version) {
 123                         (void) dlclose(dl_hdl);
 124                         (void) libscsi_error(hp, ESCSI_ENGINE_VER, "engine "
 125                             "%s version %u does not match library version %u",
 126                             engine_lib, ep->lse_libversion, hp->lsh_version);
 127                         return (NULL);
 128                 }
 129 
 130                 eip = libscsi_zalloc(hp, sizeof (libscsi_engine_impl_t));
 131                 if (eip == NULL) {
 132                         (void) dlclose(dl_hdl);
 133                         return (NULL);
 134                 }
 135                 eip->lsei_engine = ep;
 136                 eip->lsei_dl_hdl = dl_hdl;
 137                 eip->lsei_next = hp->lsh_engines;
 138                 hp->lsh_engines = eip;
 139 
 140                 return (ep);
 141         }
 142 
 143         if (dirs_tried == 0)
 144                 (void) libscsi_error(hp, ESCSI_ENGINE_BADPATH, "no valid "
 145                     "directories found in engine path %s", engine_path);
 146 
 147         return (NULL);
 148 }
 149 
 150 static void
 151 scsi_parse_mtbf(const char *envvar, uint_t *intp)
 152 {
 153         const char *strval;
 154         int intval;
 155 
 156         if ((strval = getenv(envvar)) != NULL &&
 157             (intval = atoi(strval)) > 0) {
 158                 srand48(gethrtime());
 159                 *intp = intval;
 160         }
 161 }
 162 
 163 libscsi_target_t *
 164 libscsi_open(libscsi_hdl_t *hp, const char *engine, const void *target)
 165 {
 166         const libscsi_engine_t *ep;
 167         libscsi_target_t *tp;
 168         void *private;
 169 
 170         if (engine == NULL) {
 171                 if ((engine = getenv("LIBSCSI_DEFAULT_ENGINE")) == NULL)
 172                         engine = LIBSCSI_DEFAULT_ENGINE;
 173         }
 174 
 175         if ((ep = get_engine(hp, engine)) == NULL)
 176                 return (NULL);
 177 
 178         if ((tp = libscsi_zalloc(hp, sizeof (libscsi_target_t))) == NULL)
 179                 return (NULL);
 180 
 181         if ((private = ep->lse_ops->lseo_open(hp, target)) == NULL) {
 182                 libscsi_free(hp, tp);
 183                 return (NULL);
 184         }
 185 
 186         scsi_parse_mtbf("LIBSCSI_MTBF_CDB", &tp->lst_mtbf_cdb);
 187         scsi_parse_mtbf("LIBSCSI_MTBF_READ", &tp->lst_mtbf_read);
 188         scsi_parse_mtbf("LIBSCSI_MTBF_WRITE", &tp->lst_mtbf_write);
 189 
 190         tp->lst_hdl = hp;
 191         tp->lst_engine = ep;
 192         tp->lst_priv = private;
 193 
 194         ++hp->lsh_targets;
 195 
 196         if (libscsi_get_inquiry(hp, tp) != 0) {
 197                 libscsi_close(hp, tp);
 198                 return (NULL);
 199         }
 200 
 201         return (tp);
 202 }
 203 
 204 libscsi_hdl_t *
 205 libscsi_get_handle(libscsi_target_t *tp)
 206 {
 207         return (tp->lst_hdl);
 208 }
 209 
 210 void
 211 libscsi_close(libscsi_hdl_t *hp, libscsi_target_t *tp)
 212 {
 213         tp->lst_engine->lse_ops->lseo_close(hp, tp->lst_priv);
 214         libscsi_free(hp, tp->lst_vendor);
 215         libscsi_free(hp, tp->lst_product);
 216         libscsi_free(hp, tp->lst_revision);
 217         libscsi_free(hp, tp);
 218         --hp->lsh_targets;
 219 }
 220 
 221 sam4_status_t
 222 libscsi_action_get_status(const libscsi_action_t *ap)
 223 {
 224         const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;
 225 
 226         return (aip->lsai_status);
 227 }
 228 
 229 /*
 230  * Set the timeout in seconds for this action.  If no timeout is specified
 231  * or if the timeout is set to 0, an implementation-specific timeout will be
 232  * used (which may vary based on the target, command or other variables).
 233  * Not all engines support all timeout values.  Setting the timeout to a value
 234  * not supported by the engine will cause engine-defined behavior when the
 235  * action is executed.
 236  */
 237 void
 238 libscsi_action_set_timeout(libscsi_action_t *ap, uint32_t timeout)
 239 {
 240         libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;
 241 
 242         aip->lsai_timeout = timeout;
 243 }
 244 
 245 /*
 246  * Obtain the timeout setting for this action.
 247  */
 248 uint32_t
 249 libscsi_action_get_timeout(const libscsi_action_t *ap)
 250 {
 251         const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;
 252 
 253         return (aip->lsai_timeout);
 254 }
 255 
 256 /*
 257  * Returns the flags associated with this action.  Never fails.
 258  */
 259 uint_t
 260 libscsi_action_get_flags(const libscsi_action_t *ap)
 261 {
 262         const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;
 263 
 264         return (aip->lsai_flags);
 265 }
 266 
 267 /*
 268  * Returns the address of the action's CDB.  The CDB buffer is guaranteed to
 269  * be large enough to hold the complete CDB for the command specified when the
 270  * action was allocated.  Therefore, changing the command/opcode portion of
 271  * the CDB has undefined effects.  The remainder of the CDB may be modified.
 272  */
 273 uint8_t *
 274 libscsi_action_get_cdb(const libscsi_action_t *ap)
 275 {
 276         const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;
 277 
 278         return (aip->lsai_cdb);
 279 }
 280 
 281 /*
 282  * Places the address of the action buffer in the location pointed to by bp,
 283  * if bp is not NULL.  If ap is not NULL, it will contain the allocated size
 284  * of the buffer itself.  If vp is not NULL, it will contain the number of
 285  * bytes of valid data currently stored in the buffer.
 286  *
 287  * If the action has LIBSCSI_AF_WRITE set and it has not yet been executed
 288  * successfully, the entire buffer is assumed to contain valid data.
 289  *
 290  * If the action has LIBSCSI_AF_READ set and it has not yet been executed
 291  * successfully, the amount of valid data is 0.
 292  *
 293  * If both LIBSCSI_AF_READ and LIBSCSI_AF_WRITE are clear, this function
 294  * fails with ESCSI_BADFLAGS to indicate that the action flags are
 295  * incompatible with the action data buffer.
 296  */
 297 int
 298 libscsi_action_get_buffer(const libscsi_action_t *ap, uint8_t **bp,
 299     size_t *sp, size_t *vp)
 300 {
 301         const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;
 302 
 303         if ((aip->lsai_flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE)) == 0)
 304                 return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS,
 305                     "data buffer not supported for actions with both "
 306                     "LIBSCSI_AF_READ and LIBSCSI_AF_WRITE clear"));
 307 
 308         if ((aip->lsai_flags & LIBSCSI_AF_WRITE) &&
 309             aip->lsai_status == LIBSCSI_STATUS_INVALID) {
 310                 if (bp != NULL)
 311                         *bp = aip->lsai_data;
 312                 if (sp != NULL)
 313                         *sp = aip->lsai_data_alloc;
 314                 if (vp != NULL)
 315                         *vp = aip->lsai_data_alloc;
 316 
 317                 return (0);
 318         }
 319 
 320         if ((aip->lsai_flags & LIBSCSI_AF_READ) &&
 321             aip->lsai_status != LIBSCSI_STATUS_INVALID) {
 322                 if (bp != NULL)
 323                         *bp = aip->lsai_data;
 324                 if (sp != NULL)
 325                         *sp = aip->lsai_data_alloc;
 326                 if (vp != NULL)
 327                         *vp = aip->lsai_data_len;
 328 
 329                 return (0);
 330         }
 331 
 332         if (aip->lsai_flags & LIBSCSI_AF_WRITE) {
 333                 if (bp != NULL)
 334                         *bp = NULL;
 335                 if (sp != NULL)
 336                         *sp = NULL;
 337                 if (vp != NULL)
 338                         *vp = 0;
 339         } else {
 340                 if (bp != NULL)
 341                         *bp = aip->lsai_data;
 342                 if (sp != NULL)
 343                         *sp = aip->lsai_data_alloc;
 344                 if (vp != NULL)
 345                         *vp = 0;
 346         }
 347 
 348         return (0);
 349 }
 350 
 351 /*
 352  * Obtain a pointer to the sense buffer for this action, if any, along with
 353  * the size of the sense buffer and the amount of valid data it contains.
 354  */
 355 int
 356 libscsi_action_get_sense(const libscsi_action_t *ap, uint8_t **bp,
 357     size_t *sp, size_t *vp)
 358 {
 359         const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;
 360 
 361         if (!(aip->lsai_flags & LIBSCSI_AF_RQSENSE))
 362                 return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS,
 363                     "sense data unavailable: LIBSCSI_AF_RQSENSE is clear"));
 364 
 365         if (vp != NULL) {
 366                 if (aip->lsai_status == LIBSCSI_STATUS_INVALID)
 367                         *vp = 0;
 368                 else
 369                         *vp = aip->lsai_sense_len;
 370         }
 371 
 372         if (bp != NULL) {
 373                 ASSERT(aip->lsai_sense_data != NULL);
 374                 *bp = aip->lsai_sense_data;
 375         }
 376 
 377         if (sp != NULL)
 378                 *sp = UINT8_MAX;
 379 
 380         return (0);
 381 }
 382 
 383 /*
 384  * Set the SCSI status of the action.
 385  *
 386  * Engines only.
 387  */
 388 void
 389 libscsi_action_set_status(libscsi_action_t *ap, sam4_status_t status)
 390 {
 391         libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;
 392 
 393         ASSERT(aip->lsai_status == LIBSCSI_STATUS_INVALID);
 394 
 395         aip->lsai_status = status;
 396 }
 397 
 398 /*
 399  * Set the length of valid data returned by a READ action.  If the action is
 400  * not a READ action, or the length exceeds the size of the buffer, an error
 401  * results.
 402  *
 403  * Engines only.
 404  */
 405 int
 406 libscsi_action_set_datalen(libscsi_action_t *ap, size_t len)
 407 {
 408         libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;
 409 
 410         if ((aip->lsai_flags & LIBSCSI_AF_READ) == 0)
 411                 return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS,
 412                     "data cannot be returned for actions with LIBSCSI_AF_READ "
 413                     "clear"));
 414         if (len > aip->lsai_data_alloc)
 415                 return (libscsi_error(aip->lsai_hdl, ESCSI_BADLENGTH,
 416                     "data length %lu exceeds allocated buffer capacity %lu",
 417                     (ulong_t)len, (ulong_t)aip->lsai_data_alloc));
 418 
 419         ASSERT(aip->lsai_data_len == 0);
 420         aip->lsai_data_len = len;
 421 
 422         return (0);
 423 }
 424 
 425 /*
 426  * Set the length of the valid sense data returned following the command, if
 427  * LIBSCSI_AF_RQSENSE is set for this action.  Otherwise, fail.
 428  *
 429  * Engines only.
 430  */
 431 int
 432 libscsi_action_set_senselen(libscsi_action_t *ap, size_t len)
 433 {
 434         libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;
 435 
 436         if (!(aip->lsai_flags & LIBSCSI_AF_RQSENSE))
 437                 return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS,
 438                     "sense data not supported: LIBSCSI_AF_RQSENSE is clear"));
 439 
 440         if (len > UINT8_MAX)
 441                 return (libscsi_error(aip->lsai_hdl, ESCSI_BADLENGTH,
 442                     "sense length %lu exceeds allocated buffer capacity %lu",
 443                     (ulong_t)len, (ulong_t)UINT8_MAX));
 444 
 445         ASSERT(aip->lsai_sense_len == 0);
 446         aip->lsai_sense_len = len;
 447 
 448         return (0);
 449 }
 450 
 451 /*
 452  * Allocate an action object.  The object will contain a CDB area sufficiently
 453  * large to hold a CDB for the given command, and the CDB's opcode will be
 454  * filled in.  A pointer to this CDB, the contents of which may be modified by
 455  * the caller, may be obtained by a subsequent call to libscsi_action_cdb().
 456  *
 457  * If flags includes LIBSCSI_AF_READ or LIBSCSI_AF_WRITE, buflen must be
 458  * greater than zero.  Otherwise, buflen must be 0 and buf must be NULL.
 459  * If buflen is nonzero but buf is NULL, a suitably-sized buffer will be
 460  * allocated; otherwise, the specified buffer will be used.  In either case,
 461  * a pointer to the buffer may be obtained via a subsequent call to
 462  * libscsi_action_buffer().
 463  *
 464  * If flags includes LIBSCSI_AF_RQSENSE, a REQUEST SENSE command will be
 465  * issued immediately following the termination of the specified command.
 466  * A buffer will be allocated to receive this sense data.  Following successful
 467  * execution of the action, a pointer to this buffer and the length of
 468  * valid sense data may be obtained by a call to libscsi_action_sense().
 469  * If cmd is SPC3_CMD_REQUEST_SENSE, this flag must be clear.
 470  */
 471 libscsi_action_t *
 472 libscsi_action_alloc(libscsi_hdl_t *hp, spc3_cmd_t cmd, uint_t flags,
 473     void *buf, size_t buflen)
 474 {
 475         libscsi_action_impl_t *aip;
 476         size_t cdbsz, sz;
 477         ptrdiff_t off;
 478 
 479         /*
 480          * If there's no buffer, it makes no sense to try to read or write
 481          * data.  Likewise, if we're neither reading nor writing data, we
 482          * should not have a buffer.  Both of these are programmer error.
 483          */
 484         if (buflen == 0 && (flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE))) {
 485                 (void) libscsi_error(hp, ESCSI_NEEDBUF, "a buffer is "
 486                     "required when reading or writing");
 487                 return (NULL);
 488         }
 489         if (buflen > 0 && !(flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE))) {
 490                 (void) libscsi_error(hp, ESCSI_BADFLAGS, "one of "
 491                     "LIBSCSI_AF_READ and LIBSCSI_AF_WRITE must be specified "
 492                     "in order to use a buffer");
 493                 return (NULL);
 494         }
 495         if (cmd == SPC3_CMD_REQUEST_SENSE && (flags & LIBSCSI_AF_RQSENSE)) {
 496                 (void) libscsi_error(hp, ESCSI_BADFLAGS, "request sense "
 497                     "flag not allowed for request sense command");
 498                 return (NULL);
 499         }
 500 
 501         if ((sz = cdbsz = libscsi_cmd_cdblen(hp, cmd)) == 0)
 502                 return (NULL);
 503 
 504         /*
 505          * If the caller has asked for a buffer but has not provided one, we
 506          * will allocate it in our internal buffer along with the CDB and
 507          * request sense space (if requested).
 508          */
 509         if (buf == NULL)
 510                 sz += buflen;
 511 
 512         if (flags & LIBSCSI_AF_RQSENSE)
 513                 sz += UINT8_MAX;
 514 
 515         sz += offsetof(libscsi_action_impl_t, lsai_buf[0]);
 516 
 517         if ((aip = libscsi_zalloc(hp, sz)) == NULL)
 518                 return (NULL);
 519 
 520         aip->lsai_hdl = hp;
 521         aip->lsai_flags = flags;
 522 
 523         off = 0;
 524 
 525         aip->lsai_cdb = aip->lsai_buf + off;
 526         aip->lsai_cdb_len = cdbsz;
 527         off += cdbsz;
 528         aip->lsai_cdb[0] = (uint8_t)cmd;
 529 
 530         if (buflen > 0) {
 531                 if (buf != NULL) {
 532                         aip->lsai_data = buf;
 533                 } else {
 534                         aip->lsai_data = aip->lsai_buf + off;
 535                         off += buflen;
 536                 }
 537                 aip->lsai_data_alloc = buflen;
 538                 if (flags & LIBSCSI_AF_WRITE)
 539                         aip->lsai_data_len = buflen;
 540         }
 541 
 542         if (flags & LIBSCSI_AF_RQSENSE) {
 543                 aip->lsai_sense_data = aip->lsai_buf + off;
 544                 off += UINT8_MAX;
 545         }
 546 
 547         aip->lsai_status = LIBSCSI_STATUS_INVALID;
 548 
 549         return ((libscsi_action_t *)aip);
 550 }
 551 
 552 void
 553 libscsi_action_free(libscsi_action_t *ap)
 554 {
 555         libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;
 556 
 557         libscsi_free(aip->lsai_hdl, aip);
 558 }
 559 
 560 /*
 561  * For testing purposes, we allow data to be corrupted via an environment
 562  * variable setting.  This helps ensure that higher level software can cope with
 563  * arbitrarily broken targets.  The mtbf value represents the number of bytes we
 564  * will see, on average, in between each failure.  Therefore, for each N bytes,
 565  * we would expect to see (N / mtbf) bytes of corruption.
 566  */
 567 static void
 568 scsi_inject_errors(void *data, size_t len, uint_t mtbf)
 569 {
 570         char *buf = data;
 571         double prob;
 572         size_t index;
 573 
 574         if (len == 0)
 575                 return;
 576 
 577         prob = (double)len / mtbf;
 578 
 579         while (prob > 1) {
 580                 index = lrand48() % len;
 581                 buf[index] = (lrand48() % 256);
 582                 prob -= 1;
 583         }
 584 
 585         if (drand48() <= prob) {
 586                 index = lrand48() % len;
 587                 buf[index] = (lrand48() % 256);
 588         }
 589 }
 590 
 591 int
 592 libscsi_exec(libscsi_action_t *ap, libscsi_target_t *tp)
 593 {
 594         libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;
 595         libscsi_hdl_t *hp = aip->lsai_hdl;
 596         int ret;
 597 
 598         if (tp->lst_mtbf_write != 0 &&
 599             (aip->lsai_flags & LIBSCSI_AF_WRITE)) {
 600                 scsi_inject_errors(aip->lsai_data, aip->lsai_data_len,
 601                     tp->lst_mtbf_write);
 602         }
 603 
 604         if (tp->lst_mtbf_cdb != 0) {
 605                 scsi_inject_errors(aip->lsai_cdb, aip->lsai_cdb_len,
 606                     tp->lst_mtbf_cdb);
 607         }
 608 
 609         ret = tp->lst_engine->lse_ops->lseo_exec(hp, tp->lst_priv, ap);
 610 
 611         if (ret == 0 && tp->lst_mtbf_read != 0 &&
 612             (aip->lsai_flags & LIBSCSI_AF_READ)) {
 613                 scsi_inject_errors(aip->lsai_data, aip->lsai_data_len,
 614                     tp->lst_mtbf_read);
 615         }
 616 
 617         return (ret);
 618 }