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 * Copyright (c) 2018, Joyent, Inc. 25 */ 26 27 /* 28 * Functions in this file are shared between the disk and ses enumerators. 29 * 30 * A topo_list_t of all disks is returned by a successful disk_list_gather() 31 * call, and the list is freed by a disk_list_free(). To create a 'disk' topo 32 * node below a specific 'bay' parent node either disk_declare_path() or 33 * disk_declare_addr() are called. The caller determines which 'disk' is 34 * in which 'bay'. A disk's 'label' and 'authority' information come from 35 * its parent 'bay' node. 36 */ 37 38 #include <ctype.h> 39 #include <strings.h> 40 #include <libdevinfo.h> 41 #include <libdiskmgt.h> 42 #include <devid.h> 43 #include <sys/libdevid.h> 44 #include <pthread.h> 45 #include <inttypes.h> 46 #include <sys/dkio.h> 47 #include <sys/scsi/scsi_types.h> 48 #include <fm/topo_mod.h> 49 #include <fm/topo_list.h> 50 #include <fm/libdiskstatus.h> 51 #include <sys/fm/protocol.h> 52 #include <sys/scsi/generic/inquiry.h> 53 #include "disk.h" 54 55 /* common callback information for di_walk_node() and di_devlink_walk */ 56 typedef struct disk_cbdata { 57 topo_mod_t *dcb_mod; 58 topo_list_t *dcb_list; 59 60 di_devlink_handle_t dcb_devhdl; 61 dev_di_node_t *dcb_dnode; /* for di_devlink_walk only */ 62 } disk_cbdata_t; 63 64 /* 65 * Given a /devices path for a whole disk, appending this extension gives the 66 * path to a raw device that can be opened. 67 */ 68 #if defined(__i386) || defined(__amd64) 69 #define PHYS_EXTN ":q,raw" 70 #elif defined(__sparc) || defined(__sparcv9) 71 #define PHYS_EXTN ":c,raw" 72 #else 73 #error Unknown architecture 74 #endif 75 76 /* 77 * Methods for disks. This is used by the disk-transport module to 78 * generate ereports based off SCSI disk status. 79 */ 80 static int disk_status(topo_mod_t *, tnode_t *, topo_version_t, 81 nvlist_t *, nvlist_t **); 82 83 static const topo_method_t disk_methods[] = { 84 { TOPO_METH_DISK_STATUS, TOPO_METH_DISK_STATUS_DESC, 85 TOPO_METH_DISK_STATUS_VERSION, TOPO_STABILITY_INTERNAL, 86 disk_status }, 87 { NULL } 88 }; 89 90 static int disk_temp_reading(topo_mod_t *, tnode_t *, topo_version_t, 91 nvlist_t *, nvlist_t **); 92 93 #define TOPO_METH_DISK_TEMP "disk_temp_reading" 94 #define TOPO_METH_DISK_TEMP_DESC "Disk Temperature Reading" 95 #define TOPO_METH_DISK_TEMP_VERSION 0 96 97 static const topo_method_t disk_fac_methods[] = { 98 { TOPO_METH_DISK_TEMP, TOPO_METH_DISK_TEMP_DESC, 99 TOPO_METH_DISK_TEMP_VERSION, TOPO_STABILITY_INTERNAL, 100 disk_temp_reading }, 101 { NULL } 102 }; 103 104 static const topo_pgroup_info_t io_pgroup = { 105 TOPO_PGROUP_IO, 106 TOPO_STABILITY_PRIVATE, 107 TOPO_STABILITY_PRIVATE, 108 1 109 }; 110 111 static const topo_pgroup_info_t disk_auth_pgroup = { 112 FM_FMRI_AUTHORITY, 113 TOPO_STABILITY_PRIVATE, 114 TOPO_STABILITY_PRIVATE, 115 1 116 }; 117 118 static const topo_pgroup_info_t storage_pgroup = { 119 TOPO_PGROUP_STORAGE, 120 TOPO_STABILITY_PRIVATE, 121 TOPO_STABILITY_PRIVATE, 122 1 123 }; 124 125 /* 126 * Set the properties of the disk node, from dev_di_node_t data. 127 * Properties include: 128 * group: protocol properties: resource, asru, label, fru 129 * group: authority properties: product-id, chasis-id, server-id 130 * group: io properties: devfs-path, devid 131 * group: storage properties: 132 * - logical-disk, disk-model, disk-manufacturer, serial-number 133 * - firmware-revision, capacity-in-bytes 134 * 135 * NOTE: the io and storage groups won't be present if the dnode passed in is 136 * NULL. This happens when a disk is found through ses, but is not enumerated 137 * in the devinfo tree. 138 */ 139 static int 140 disk_set_props(topo_mod_t *mod, tnode_t *parent, 141 tnode_t *dtn, dev_di_node_t *dnode) 142 { 143 nvlist_t *asru = NULL, *drive_attrs; 144 char *label = NULL; 145 nvlist_t *fmri = NULL; 146 dm_descriptor_t drive_descr = NULL; 147 uint32_t rpm; 148 int err; 149 150 /* pull the label property down from our parent 'bay' node */ 151 if (topo_node_label(parent, &label, &err) != 0) { 152 if (err != ETOPO_PROP_NOENT) { 153 topo_mod_dprintf(mod, "disk_set_props: " 154 "label error %s\n", topo_strerror(err)); 155 goto error; 156 } 157 } else if (topo_node_label_set(dtn, label, &err) != 0) { 158 topo_mod_dprintf(mod, "disk_set_props: " 159 "label_set error %s\n", topo_strerror(err)); 160 goto error; 161 } 162 163 /* get the resource fmri, and use it as the fru */ 164 if (topo_node_resource(dtn, &fmri, &err) != 0) { 165 topo_mod_dprintf(mod, "disk_set_props: " 166 "resource error: %s\n", topo_strerror(err)); 167 goto error; 168 } 169 if (topo_node_fru_set(dtn, fmri, 0, &err) != 0) { 170 topo_mod_dprintf(mod, "disk_set_props: " 171 "fru_set error: %s\n", topo_strerror(err)); 172 goto error; 173 } 174 175 /* create/set the authority group */ 176 if ((topo_pgroup_create(dtn, &disk_auth_pgroup, &err) != 0) && 177 (err != ETOPO_PROP_DEFD)) { 178 topo_mod_dprintf(mod, "disk_set_props: " 179 "create disk_auth error %s\n", topo_strerror(err)); 180 goto error; 181 } 182 183 /* create the storage group */ 184 if (topo_pgroup_create(dtn, &storage_pgroup, &err) != 0) { 185 topo_mod_dprintf(mod, "disk_set_props: " 186 "create storage error %s\n", topo_strerror(err)); 187 goto error; 188 } 189 190 /* no dnode was found for this disk - skip the io and storage groups */ 191 if (dnode == NULL) { 192 err = 0; 193 goto out; 194 } 195 196 /* form and set the asru */ 197 if ((asru = topo_mod_devfmri(mod, FM_DEV_SCHEME_VERSION, 198 dnode->ddn_dpath, dnode->ddn_devid)) == NULL) { 199 err = ETOPO_FMRI_UNKNOWN; 200 topo_mod_dprintf(mod, "disk_set_props: " 201 "asru error %s\n", topo_strerror(err)); 202 goto error; 203 } 204 if (topo_node_asru_set(dtn, asru, 0, &err) != 0) { 205 topo_mod_dprintf(mod, "disk_set_props: " 206 "asru_set error %s\n", topo_strerror(err)); 207 goto error; 208 } 209 210 /* create/set the devfs-path and devid in the io group */ 211 if (topo_pgroup_create(dtn, &io_pgroup, &err) != 0) { 212 topo_mod_dprintf(mod, "disk_set_props: " 213 "create io error %s\n", topo_strerror(err)); 214 goto error; 215 } 216 217 if (topo_prop_set_string(dtn, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH, 218 TOPO_PROP_IMMUTABLE, dnode->ddn_dpath, &err) != 0) { 219 topo_mod_dprintf(mod, "disk_set_props: " 220 "set dev error %s\n", topo_strerror(err)); 221 goto error; 222 } 223 224 if (dnode->ddn_devid && topo_prop_set_string(dtn, TOPO_PGROUP_IO, 225 TOPO_IO_DEVID, TOPO_PROP_IMMUTABLE, dnode->ddn_devid, &err) != 0) { 226 topo_mod_dprintf(mod, "disk_set_props: " 227 "set devid error %s\n", topo_strerror(err)); 228 goto error; 229 } 230 231 if (dnode->ddn_ppath_count != 0 && 232 topo_prop_set_string_array(dtn, TOPO_PGROUP_IO, TOPO_IO_PHYS_PATH, 233 TOPO_PROP_IMMUTABLE, (const char **)dnode->ddn_ppath, 234 dnode->ddn_ppath_count, &err) != 0) { 235 topo_mod_dprintf(mod, "disk_set_props: " 236 "set phys-path error %s\n", topo_strerror(err)); 237 goto error; 238 } 239 240 /* set the storage group public /dev name */ 241 if (dnode->ddn_lpath != NULL && 242 topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE, 243 TOPO_STORAGE_LOGICAL_DISK_NAME, TOPO_PROP_IMMUTABLE, 244 dnode->ddn_lpath, &err) != 0) { 245 topo_mod_dprintf(mod, "disk_set_props: " 246 "set disk_name error %s\n", topo_strerror(err)); 247 goto error; 248 } 249 250 /* populate other misc storage group properties */ 251 if (dnode->ddn_mfg && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE, 252 TOPO_STORAGE_MANUFACTURER, TOPO_PROP_IMMUTABLE, 253 dnode->ddn_mfg, &err) != 0)) { 254 topo_mod_dprintf(mod, "disk_set_props: " 255 "set mfg error %s\n", topo_strerror(err)); 256 goto error; 257 } 258 if (dnode->ddn_model && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE, 259 TOPO_STORAGE_MODEL, TOPO_PROP_IMMUTABLE, 260 dnode->ddn_model, &err) != 0)) { 261 topo_mod_dprintf(mod, "disk_set_props: " 262 "set model error %s\n", topo_strerror(err)); 263 goto error; 264 } 265 if (dnode->ddn_serial && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE, 266 TOPO_STORAGE_SERIAL_NUM, TOPO_PROP_IMMUTABLE, 267 dnode->ddn_serial, &err) != 0)) { 268 topo_mod_dprintf(mod, "disk_set_props: " 269 "set serial error %s\n", topo_strerror(err)); 270 goto error; 271 } 272 if (dnode->ddn_firm && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE, 273 TOPO_STORAGE_FIRMWARE_REV, TOPO_PROP_IMMUTABLE, 274 dnode->ddn_firm, &err) != 0)) { 275 topo_mod_dprintf(mod, "disk_set_props: " 276 "set firm error %s\n", topo_strerror(err)); 277 goto error; 278 } 279 if (dnode->ddn_cap && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE, 280 TOPO_STORAGE_CAPACITY, TOPO_PROP_IMMUTABLE, 281 dnode->ddn_cap, &err) != 0)) { 282 topo_mod_dprintf(mod, "disk_set_props: " 283 "set cap error %s\n", topo_strerror(err)); 284 goto error; 285 } 286 287 if (dnode->ddn_devid == NULL || 288 (drive_descr = dm_get_descriptor_by_name(DM_DRIVE, 289 dnode->ddn_devid, &err)) == NULL || 290 (drive_attrs = dm_get_attributes(drive_descr, &err)) == NULL) 291 goto out; 292 293 if (nvlist_lookup_boolean(drive_attrs, DM_SOLIDSTATE) == 0 || 294 nvlist_lookup_uint32(drive_attrs, DM_RPM, &rpm) != 0) 295 goto out; 296 297 if (topo_prop_set_uint32(dtn, TOPO_PGROUP_STORAGE, TOPO_STORAGE_RPM, 298 TOPO_PROP_IMMUTABLE, rpm, &err) != 0) { 299 topo_mod_dprintf(mod, "disk_set_props: " 300 "set rpm error %s\n", topo_strerror(err)); 301 dm_free_descriptor(drive_descr); 302 goto error; 303 } 304 err = 0; 305 306 out: 307 if (drive_descr != NULL) 308 dm_free_descriptor(drive_descr); 309 nvlist_free(fmri); 310 if (label) 311 topo_mod_strfree(mod, label); 312 nvlist_free(asru); 313 return (err); 314 315 error: err = topo_mod_seterrno(mod, err); 316 goto out; 317 } 318 319 /* 320 * Trim leading and trailing whitespace from the string. 321 */ 322 static char * 323 disk_trim_whitespace(topo_mod_t *mod, const char *begin) 324 { 325 const char *end; 326 char *buf; 327 size_t count; 328 329 if (begin == NULL) 330 return (NULL); 331 332 end = begin + strlen(begin); 333 334 while (begin < end && isspace(*begin)) 335 begin++; 336 while (begin < end && isspace(*(end - 1))) 337 end--; 338 339 count = end - begin; 340 if ((buf = topo_mod_alloc(mod, count + 1)) == NULL) 341 return (NULL); 342 343 (void) strlcpy(buf, begin, count + 1); 344 345 return (buf); 346 } 347 348 /*ARGSUSED*/ 349 static int 350 disk_temp_reading(topo_mod_t *mod, tnode_t *node, topo_version_t vers, 351 nvlist_t *in, nvlist_t **out) 352 { 353 char *devid; 354 uint32_t temp; 355 dm_descriptor_t drive_descr = NULL; 356 nvlist_t *drive_stats, *pargs, *nvl; 357 int err; 358 359 if (vers > TOPO_METH_DISK_TEMP_VERSION) 360 return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW)); 361 362 if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &pargs) != 0 || 363 nvlist_lookup_string(pargs, TOPO_IO_DEVID, &devid) != 0) { 364 topo_mod_dprintf(mod, "Failed to lookup %s arg", 365 TOPO_IO_DEVID); 366 return (topo_mod_seterrno(mod, EMOD_NVL_INVAL)); 367 } 368 369 if ((drive_descr = dm_get_descriptor_by_name(DM_DRIVE, devid, 370 &err)) == NULL) { 371 topo_mod_dprintf(mod, "failed to get drive decriptor for %s", 372 devid); 373 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 374 } 375 376 if ((drive_stats = dm_get_stats(drive_descr, DM_DRV_STAT_TEMPERATURE, 377 &err)) == NULL || 378 nvlist_lookup_uint32(drive_stats, DM_TEMPERATURE, &temp) != 0) { 379 topo_mod_dprintf(mod, "failed to read disk temp for %s", 380 devid); 381 dm_free_descriptor(drive_descr); 382 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 383 } 384 dm_free_descriptor(drive_descr); 385 386 if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 || 387 nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, 388 TOPO_SENSOR_READING) != 0 || 389 nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_DOUBLE) != 390 0 || nvlist_add_double(nvl, TOPO_PROP_VAL_VAL, (double)temp) != 0) { 391 topo_mod_dprintf(mod, "Failed to allocate 'out' nvlist\n"); 392 nvlist_free(nvl); 393 return (topo_mod_seterrno(mod, EMOD_NOMEM)); 394 } 395 *out = nvl; 396 397 return (0); 398 } 399 400 static int 401 disk_add_temp_sensor(topo_mod_t *mod, tnode_t *pnode, const char *devid) 402 { 403 tnode_t *fnode; 404 topo_pgroup_info_t pgi; 405 nvlist_t *arg_nvl = NULL; 406 int err; 407 408 if ((fnode = topo_node_facbind(mod, pnode, "temp", 409 TOPO_FAC_TYPE_SENSOR)) == NULL) { 410 topo_mod_dprintf(mod, "failed to bind facility node"); 411 /* errno set */ 412 return (-1); 413 } 414 415 /* 416 * Set props: 417 * - facility/sensor-class 418 * - facility/sensor-type 419 * - facility/units 420 */ 421 pgi.tpi_name = TOPO_PGROUP_FACILITY; 422 pgi.tpi_namestab = TOPO_STABILITY_PRIVATE; 423 pgi.tpi_datastab = TOPO_STABILITY_PRIVATE; 424 pgi.tpi_version = 1; 425 if (topo_pgroup_create(fnode, &pgi, &err) != 0) { 426 if (err != ETOPO_PROP_DEFD) { 427 topo_mod_dprintf(mod, "pgroups create failure (%s)\n", 428 topo_strerror(err)); 429 /* errno set */ 430 goto err; 431 } 432 } 433 if (topo_prop_set_string(fnode, TOPO_PGROUP_FACILITY, 434 TOPO_SENSOR_CLASS, TOPO_PROP_IMMUTABLE, 435 TOPO_SENSOR_CLASS_THRESHOLD, &err) != 0 || 436 topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY, 437 TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE, TOPO_SENSOR_TYPE_TEMP, 438 &err) != 0 || 439 topo_prop_set_uint32(fnode, TOPO_PGROUP_FACILITY, 440 TOPO_SENSOR_UNITS, TOPO_PROP_IMMUTABLE, 441 TOPO_SENSOR_UNITS_DEGREES_C, &err) != 0) { 442 topo_mod_dprintf(mod, "Failed to set props on facnode (%s)", 443 topo_strerror(err)); 444 /* errno set */ 445 goto err; 446 } 447 448 /* 449 * Register a property method for facility/reading 450 */ 451 if (topo_method_register(mod, fnode, disk_fac_methods) < 0) { 452 topo_mod_dprintf(mod, "failed to register facility methods"); 453 goto err; 454 } 455 if (topo_mod_nvalloc(mod, &arg_nvl, NV_UNIQUE_NAME) < 0 || 456 nvlist_add_string(arg_nvl, TOPO_IO_DEVID, devid) != 0) { 457 topo_mod_dprintf(mod, "Failed build arg nvlist\n"); 458 (void) topo_mod_seterrno(mod, EMOD_NOMEM); 459 goto err; 460 } 461 if (topo_prop_method_register(fnode, TOPO_PGROUP_FACILITY, 462 TOPO_SENSOR_READING, TOPO_TYPE_DOUBLE, "disk_temp_reading", 463 arg_nvl, &err) != 0) { 464 topo_mod_dprintf(mod, "Failed to register %s propmeth " 465 "on fac node %s (%s)\n", TOPO_SENSOR_READING, 466 topo_node_name(fnode), topo_strerror(err)); 467 /* errno set */ 468 goto err; 469 } 470 nvlist_free(arg_nvl); 471 return (0); 472 err: 473 topo_node_unbind(fnode); 474 nvlist_free(arg_nvl); 475 return (-1); 476 } 477 478 /* create the disk topo node */ 479 static int 480 disk_tnode_create(topo_mod_t *mod, tnode_t *parent, 481 dev_di_node_t *dnode, const char *name, topo_instance_t i, tnode_t **rval) 482 { 483 int len; 484 nvlist_t *fmri; 485 tnode_t *dtn; 486 char *part = NULL; 487 nvlist_t *auth; 488 char *mfg, *model, *firm, *serial; 489 490 *rval = NULL; 491 if (dnode != NULL) { 492 mfg = topo_mod_clean_str(mod, dnode->ddn_mfg); 493 model = topo_mod_clean_str(mod, dnode->ddn_model); 494 firm = topo_mod_clean_str(mod, dnode->ddn_firm); 495 serial = topo_mod_clean_str(mod, dnode->ddn_serial); 496 } else { 497 mfg = model = firm = serial = NULL; 498 } 499 500 /* form 'part=' of fmri as "<mfg>-<model>" */ 501 if (mfg != NULL && model != NULL) { 502 len = strlen(mfg) + 1 + strlen(model) + 1; 503 if ((part = topo_mod_alloc(mod, len)) != NULL) 504 (void) snprintf(part, len, "%s-%s", 505 mfg, model); 506 } 507 508 auth = topo_mod_auth(mod, parent); 509 fmri = topo_mod_hcfmri(mod, parent, FM_HC_SCHEME_VERSION, name, i, NULL, 510 auth, part ? part : model, firm, serial); 511 nvlist_free(auth); 512 513 topo_mod_strfree(mod, part); 514 topo_mod_strfree(mod, mfg); 515 topo_mod_strfree(mod, model); 516 topo_mod_strfree(mod, firm); 517 topo_mod_strfree(mod, serial); 518 519 if (fmri == NULL) { 520 topo_mod_dprintf(mod, "disk_tnode_create: " 521 "hcfmri (%s%d/%s%d) error %s\n", 522 topo_node_name(parent), topo_node_instance(parent), 523 name, i, topo_strerror(topo_mod_errno(mod))); 524 return (-1); 525 } 526 527 if ((dtn = topo_node_bind(mod, parent, name, i, fmri)) == NULL) { 528 if (topo_mod_errno(mod) == EMOD_NODE_BOUND) { 529 /* 530 * if disk 0 is already there then we're done 531 */ 532 nvlist_free(fmri); 533 return (0); 534 } 535 topo_mod_dprintf(mod, "disk_tnode_create: " 536 "bind (%s%d/%s%d) error %s\n", 537 topo_node_name(parent), topo_node_instance(parent), 538 name, i, topo_strerror(topo_mod_errno(mod))); 539 nvlist_free(fmri); 540 return (-1); 541 } 542 nvlist_free(fmri); 543 544 /* add the properties of the disk */ 545 if (disk_set_props(mod, parent, dtn, dnode) != 0) { 546 topo_mod_dprintf(mod, "disk_tnode_create: " 547 "disk_set_props (%s%d/%s%d) error %s\n", 548 topo_node_name(parent), topo_node_instance(parent), 549 name, i, topo_strerror(topo_mod_errno(mod))); 550 topo_node_unbind(dtn); 551 return (-1); 552 } 553 554 if (dnode != NULL && dnode->ddn_devid != NULL && 555 disk_add_temp_sensor(mod, dtn, dnode->ddn_devid) != 0) { 556 topo_mod_dprintf(mod, "disk_tnode_create: failed to create " 557 "temperature sensor node on bay=%d/disk=0", 558 topo_node_instance(parent)); 559 } 560 *rval = dtn; 561 return (0); 562 } 563 564 static int 565 disk_declare(topo_mod_t *mod, tnode_t *parent, dev_di_node_t *dnode, 566 tnode_t **childp) 567 { 568 tnode_t *dtn = NULL; 569 int rval; 570 571 rval = disk_tnode_create(mod, parent, dnode, DISK, 0, &dtn); 572 if (dtn == NULL) { 573 if (rval == 0) 574 return (0); 575 topo_mod_dprintf(mod, "disk_declare: " 576 "disk_tnode_create error %s\n", 577 topo_strerror(topo_mod_errno(mod))); 578 return (-1); 579 } 580 581 /* register disk_methods against the disk topo node */ 582 if (topo_method_register(mod, dtn, disk_methods) != 0) { 583 topo_mod_dprintf(mod, "disk_declare: " 584 "topo_method_register error %s\n", 585 topo_strerror(topo_mod_errno(mod))); 586 topo_node_unbind(dtn); 587 return (-1); 588 } 589 if (childp != NULL) 590 *childp = dtn; 591 return (0); 592 } 593 594 int 595 disk_declare_path(topo_mod_t *mod, tnode_t *parent, topo_list_t *listp, 596 const char *path) 597 { 598 dev_di_node_t *dnode; 599 int i; 600 601 /* 602 * Check for match using physical phci (ddn_ppath). Use 603 * di_devfs_path_match so generic.vs.non-generic names match. 604 */ 605 for (dnode = topo_list_next(listp); dnode != NULL; 606 dnode = topo_list_next(dnode)) { 607 if (dnode->ddn_ppath == NULL) 608 continue; 609 610 for (i = 0; i < dnode->ddn_ppath_count; i++) { 611 if (di_devfs_path_match(dnode->ddn_ppath[0], path)) 612 return (disk_declare(mod, parent, dnode, NULL)); 613 } 614 } 615 616 topo_mod_dprintf(mod, "disk_declare_path: " 617 "failed to find disk matching path %s", path); 618 return (0); 619 } 620 621 int 622 disk_declare_addr(topo_mod_t *mod, tnode_t *parent, topo_list_t *listp, 623 const char *addr, tnode_t **childp) 624 { 625 dev_di_node_t *dnode; 626 int i; 627 628 /* Check for match using addr. */ 629 for (dnode = topo_list_next(listp); dnode != NULL; 630 dnode = topo_list_next(dnode)) { 631 if (dnode->ddn_target_port == NULL) 632 continue; 633 634 for (i = 0; i < dnode->ddn_ppath_count; i++) { 635 if ((dnode->ddn_target_port[i] != NULL) && 636 (strncmp(dnode->ddn_target_port[i], addr, 637 strcspn(dnode->ddn_target_port[i], ":"))) == 0) { 638 topo_mod_dprintf(mod, "disk_declare_addr: " 639 "found disk matching addr %s", addr); 640 return (disk_declare(mod, parent, dnode, 641 childp)); 642 } 643 } 644 } 645 646 topo_mod_dprintf(mod, "disk_declare_addr: " 647 "failed to find disk matching addr %s", addr); 648 649 return (1); 650 } 651 652 /* 653 * Try to find a disk based on the bridge-port property. This is most often used 654 * for SATA devices which are attached to a SAS controller and are therefore 655 * behind a SATL bridge port. SES only knows of devices based on this SAS WWN, 656 * not based on any SATA GUIDs. 657 */ 658 int 659 disk_declare_bridge(topo_mod_t *mod, tnode_t *parent, topo_list_t *listp, 660 const char *addr, tnode_t **childp) 661 { 662 dev_di_node_t *dnode; 663 int i; 664 665 /* Check for match using addr. */ 666 for (dnode = topo_list_next(listp); dnode != NULL; 667 dnode = topo_list_next(dnode)) { 668 if (dnode->ddn_bridge_port == NULL) 669 continue; 670 671 for (i = 0; i < dnode->ddn_ppath_count; i++) { 672 if ((dnode->ddn_bridge_port[i] != NULL) && 673 (strncmp(dnode->ddn_bridge_port[i], addr, 674 strcspn(dnode->ddn_bridge_port[i], ":"))) == 0) { 675 topo_mod_dprintf(mod, "disk_declare_bridge: " 676 "found disk matching bridge %s", addr); 677 return (disk_declare(mod, parent, dnode, 678 childp)); 679 } 680 } 681 } 682 683 topo_mod_dprintf(mod, "disk_declare_bridge: " 684 "failed to find disk matching bridge %s", addr); 685 686 return (1); 687 } 688 689 /* 690 * Used to declare a disk that has been discovered through other means (usually 691 * ses), that is not enumerated in the devinfo tree. 692 */ 693 int 694 disk_declare_non_enumerated(topo_mod_t *mod, tnode_t *parent, tnode_t **childp) 695 { 696 return (disk_declare(mod, parent, NULL, childp)); 697 } 698 699 /* di_devlink callback for dev_di_node_add */ 700 static int 701 disk_devlink_callback(di_devlink_t dl, void *arg) 702 { 703 disk_cbdata_t *cbp = (disk_cbdata_t *)arg; 704 topo_mod_t *mod = cbp->dcb_mod; 705 dev_di_node_t *dnode = cbp->dcb_dnode; 706 const char *devpath; 707 char *ctds, *slice; 708 709 devpath = di_devlink_path(dl); 710 if ((dnode == NULL) || (devpath == NULL)) 711 return (DI_WALK_TERMINATE); 712 713 /* trim the slice off the public name */ 714 if (((ctds = strrchr(devpath, '/')) != NULL) && 715 ((slice = strchr(ctds, 's')) != NULL)) 716 *slice = '\0'; 717 718 /* Establish the public /dev name (no slice) */ 719 dnode->ddn_lpath = topo_mod_strdup(mod, ctds ? ctds + 1 : devpath); 720 721 if (ctds && slice) 722 *slice = 's'; 723 return (DI_WALK_TERMINATE); 724 } 725 726 static void 727 dev_di_node_free(topo_mod_t *mod, dev_di_node_t *dnode) 728 { 729 int i; 730 731 /* free the stuff we point to */ 732 if (dnode->ddn_devid) 733 topo_mod_strfree(mod, dnode->ddn_devid); 734 for (i = 0; i < dnode->ddn_ppath_count; i++) { 735 /* topo_mod_strfree does NULL checking. */ 736 topo_mod_strfree(mod, dnode->ddn_ppath[i]); 737 topo_mod_strfree(mod, dnode->ddn_target_port[i]); 738 topo_mod_strfree(mod, dnode->ddn_attached_port[i]); 739 topo_mod_strfree(mod, dnode->ddn_bridge_port[i]); 740 } 741 topo_mod_free(mod, dnode->ddn_ppath, 742 dnode->ddn_ppath_count * sizeof (char *)); 743 topo_mod_free(mod, dnode->ddn_target_port, 744 dnode->ddn_ppath_count * sizeof (char *)); 745 topo_mod_free(mod, dnode->ddn_attached_port, 746 dnode->ddn_ppath_count * sizeof (char *)); 747 topo_mod_free(mod, dnode->ddn_bridge_port, 748 dnode->ddn_ppath_count * sizeof (char *)); 749 topo_mod_strfree(mod, dnode->ddn_dpath); 750 topo_mod_strfree(mod, dnode->ddn_lpath); 751 752 topo_mod_strfree(mod, dnode->ddn_mfg); 753 topo_mod_strfree(mod, dnode->ddn_model); 754 topo_mod_strfree(mod, dnode->ddn_serial); 755 topo_mod_strfree(mod, dnode->ddn_firm); 756 topo_mod_strfree(mod, dnode->ddn_cap); 757 758 /* free self */ 759 topo_mod_free(mod, dnode, sizeof (dev_di_node_t)); 760 } 761 762 static int 763 dev_di_node_add(di_node_t node, char *devid, disk_cbdata_t *cbp) 764 { 765 topo_mod_t *mod = cbp->dcb_mod; 766 dev_di_node_t *dnode; 767 di_path_t pnode; 768 char *path; 769 int mlen; 770 char *minorpath; 771 char *extn = ":a"; 772 char *s; 773 int64_t *nblocksp; 774 uint64_t nblocks; 775 int *dblksizep; 776 uint_t dblksize; 777 char lentry[MAXPATHLEN]; 778 int pathcount; 779 int *inq_dtype, itype; 780 int i; 781 782 if (devid) { 783 /* 784 * Check for list duplicate using devid search. 785 * Note if there is no devid, then we can end up with duplicates 786 * in the list, but this doesn't do any harm. 787 */ 788 for (dnode = topo_list_next(cbp->dcb_list); 789 dnode != NULL; dnode = topo_list_next(dnode)) { 790 if (dnode->ddn_devid && 791 devid_str_compare(dnode->ddn_devid, devid) == 0) { 792 topo_mod_dprintf(mod, "dev_di_node_add: " 793 "already there %s\n", devid); 794 return (0); 795 } 796 } 797 } 798 799 if ((dnode = topo_mod_zalloc(mod, sizeof (dev_di_node_t))) == NULL) 800 return (-1); 801 802 if (devid) { 803 /* Establish the devid. */ 804 dnode->ddn_devid = topo_mod_strdup(mod, devid); 805 if (dnode->ddn_devid == NULL) 806 goto error; 807 } 808 809 /* Establish the devinfo dpath */ 810 if ((path = di_devfs_path(node)) == NULL) { 811 (void) topo_mod_seterrno(mod, errno); 812 goto error; 813 } 814 815 dnode->ddn_dpath = topo_mod_strdup(mod, path); 816 di_devfs_path_free(path); 817 if (dnode->ddn_dpath == NULL) 818 goto error; 819 820 /* 821 * Establish the physical ppath and target ports. If the device is 822 * non-mpxio then dpath and ppath are the same, and the target port is a 823 * property of the device node. 824 * 825 * If dpath is a client node under scsi_vhci, then iterate over all 826 * paths and get their physical paths and target port properrties. 827 * di_path_client_next_path call below will 828 * return non-NULL, and ppath is set to the physical path to the first 829 * pathinfo node. 830 * 831 * NOTE: It is possible to get a generic.vs.non-generic path 832 * for di_devfs_path.vs.di_path_devfs_path like: 833 * xml: /pci@7b,0/pci1022,7458@11/pci1000,3060@2/sd@2,0 834 * pnode: /pci@7b,0/pci1022,7458@11/pci1000,3060@2/disk@2,0 835 * To resolve this issue disk_declare_path() needs to use the 836 * special di_devfs_path_match() interface. 837 */ 838 pathcount = 0; 839 pnode = NULL; 840 while ((pnode = di_path_client_next_path(node, pnode)) != NULL) { 841 pathcount++; 842 } 843 844 if (pathcount == 0) { 845 if ((dnode->ddn_ppath = 846 topo_mod_zalloc(mod, sizeof (char *))) == NULL) 847 goto error; 848 849 dnode->ddn_ppath_count = 1; 850 if ((dnode->ddn_ppath[0] = topo_mod_strdup(mod, 851 dnode->ddn_dpath)) == NULL) 852 goto error; 853 854 if ((dnode->ddn_target_port = topo_mod_zalloc(mod, 855 sizeof (char *))) == NULL) 856 goto error; 857 858 if ((dnode->ddn_attached_port = topo_mod_zalloc(mod, 859 sizeof (char *))) == NULL) 860 goto error; 861 862 if ((dnode->ddn_bridge_port = topo_mod_zalloc(mod, 863 sizeof (char *))) == NULL) 864 goto error; 865 866 /* There should be only one target port for a devinfo node. */ 867 if ((di_prop_lookup_strings(DDI_DEV_T_ANY, node, 868 SCSI_ADDR_PROP_TARGET_PORT, &s)) == 1) { 869 if ((dnode->ddn_target_port[0] = 870 topo_mod_strdup(mod, 871 scsi_wwnstr_skip_ua_prefix(s))) == 872 NULL) 873 goto error; 874 } 875 876 if ((di_prop_lookup_strings(DDI_DEV_T_ANY, node, 877 SCSI_ADDR_PROP_ATTACHED_PORT, &s)) == 1) { 878 /* There should be one attached port if any. */ 879 if ((dnode->ddn_attached_port[0] = 880 topo_mod_strdup(mod, 881 scsi_wwnstr_skip_ua_prefix(s))) == 882 NULL) 883 goto error; 884 } 885 886 if ((di_prop_lookup_strings(DDI_DEV_T_ANY, node, 887 SCSI_ADDR_PROP_BRIDGE_PORT, &s)) == 1) { 888 /* There should be one bridge port if any. */ 889 if ((dnode->ddn_bridge_port[0] = 890 topo_mod_strdup(mod, 891 scsi_wwnstr_skip_ua_prefix(s))) == 892 NULL) 893 goto error; 894 } 895 896 } else { 897 /* processing a scsi_vhci device. */ 898 if ((dnode->ddn_ppath = topo_mod_zalloc(mod, 899 pathcount * sizeof (char *))) == NULL) 900 goto error; 901 902 dnode->ddn_ppath_count = pathcount; 903 904 if ((dnode->ddn_target_port = topo_mod_zalloc(mod, 905 pathcount * sizeof (char *))) == NULL) 906 goto error; 907 908 if ((dnode->ddn_attached_port = topo_mod_zalloc(mod, 909 pathcount * sizeof (char *))) == NULL) 910 goto error; 911 912 if ((dnode->ddn_bridge_port = topo_mod_zalloc(mod, 913 pathcount * sizeof (char *))) == NULL) 914 goto error; 915 916 pnode = NULL; 917 pathcount = 0; 918 while ((pnode = di_path_client_next_path(node, 919 pnode)) != NULL) { 920 if ((path = di_path_devfs_path(pnode)) == NULL) { 921 (void) topo_mod_seterrno(mod, errno); 922 goto error; 923 } 924 925 dnode->ddn_ppath[pathcount] = 926 topo_mod_strdup(mod, path); 927 di_devfs_path_free(path); 928 if (dnode->ddn_ppath[pathcount] == NULL) 929 goto error; 930 931 if ((di_path_prop_lookup_strings(pnode, 932 SCSI_ADDR_PROP_TARGET_PORT, &s)) == 1) { 933 if ((dnode->ddn_target_port[pathcount] = 934 topo_mod_strdup(mod, 935 scsi_wwnstr_skip_ua_prefix(s))) == 936 NULL) 937 goto error; 938 } 939 940 if ((di_path_prop_lookup_strings(pnode, 941 SCSI_ADDR_PROP_ATTACHED_PORT, &s)) == 1) { 942 if ((dnode->ddn_attached_port[pathcount] = 943 topo_mod_strdup(mod, 944 scsi_wwnstr_skip_ua_prefix(s))) == 945 NULL) 946 goto error; 947 } 948 949 if ((di_path_prop_lookup_strings(pnode, 950 SCSI_ADDR_PROP_BRIDGE_PORT, &s)) == 1) { 951 if ((dnode->ddn_bridge_port[pathcount] = 952 topo_mod_strdup(mod, 953 scsi_wwnstr_skip_ua_prefix(s))) == 954 NULL) 955 goto error; 956 } 957 958 pathcount++; 959 } 960 } 961 962 /* 963 * Find the public /dev name for a disk by adding a minor name and using 964 * di_devlink interface for reverse translation (use devinfo path). 965 */ 966 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "inquiry-device-type", 967 &inq_dtype) > 0) { 968 dnode->ddn_dtype = *inq_dtype; 969 itype = (*inq_dtype) & DTYPE_MASK; 970 if (itype == DTYPE_DIRECT) { 971 mlen = strlen(dnode->ddn_dpath) + strlen(extn) + 1; 972 if ((minorpath = topo_mod_alloc(mod, mlen)) == NULL) 973 goto error; 974 (void) snprintf(minorpath, mlen, "%s%s", 975 dnode->ddn_dpath, extn); 976 cbp->dcb_dnode = dnode; 977 (void) di_devlink_walk(cbp->dcb_devhdl, "^dsk/", 978 minorpath, DI_PRIMARY_LINK, cbp, 979 disk_devlink_callback); 980 topo_mod_free(mod, minorpath, mlen); 981 if (dnode->ddn_lpath == NULL) { 982 topo_mod_dprintf(mod, "dev_di_node_add: " 983 "failed to determine logical path"); 984 } 985 } 986 } else { 987 dnode->ddn_dtype = DTYPE_UNKNOWN; 988 } 989 990 /* cache various bits of optional information about the device. */ 991 if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, 992 INQUIRY_VENDOR_ID, &s) > 0) { 993 if ((dnode->ddn_mfg = disk_trim_whitespace(mod, s)) == NULL) 994 goto error; 995 } 996 if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, 997 INQUIRY_PRODUCT_ID, &s) > 0) { 998 if ((dnode->ddn_model = disk_trim_whitespace(mod, s)) == NULL) 999 goto error; 1000 } 1001 if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, 1002 INQUIRY_REVISION_ID, &s) > 0) { 1003 if ((dnode->ddn_firm = disk_trim_whitespace(mod, s)) == NULL) 1004 goto error; 1005 } 1006 if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, 1007 INQUIRY_SERIAL_NO, &s) > 0) { 1008 if ((dnode->ddn_serial = disk_trim_whitespace(mod, s)) == NULL) 1009 goto error; 1010 } else { 1011 /* 1012 * Many USB disk devices don't emulate serial inquiry number 1013 * because their serial number can be longer than the standard 1014 * SCSI length. If we didn't get an inquiry serial number, fill 1015 * one in this way. 1016 */ 1017 di_node_t parent; 1018 1019 if ((parent = di_parent_node(node)) != DI_NODE_NIL && 1020 di_prop_lookup_strings(DDI_DEV_T_ANY, parent, 1021 "usb-serialno", &s) > 0) { 1022 if ((dnode->ddn_serial = disk_trim_whitespace(mod, 1023 s)) == NULL) { 1024 goto error; 1025 } 1026 } 1027 } 1028 1029 if (di_prop_lookup_int64(DDI_DEV_T_ANY, node, 1030 "device-nblocks", &nblocksp) > 0) { 1031 nblocks = (uint64_t)*nblocksp; 1032 /* 1033 * To save kernel memory, the driver may not define 1034 * "device-dblksize" when its value is default DEV_BSIZE. 1035 */ 1036 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, 1037 "device-dblksize", &dblksizep) > 0) 1038 dblksize = (uint_t)*dblksizep; 1039 else 1040 dblksize = DEV_BSIZE; /* default value */ 1041 (void) snprintf(lentry, sizeof (lentry), 1042 "%" PRIu64, nblocks * dblksize); 1043 if ((dnode->ddn_cap = topo_mod_strdup(mod, lentry)) == NULL) 1044 goto error; 1045 } 1046 1047 topo_mod_dprintf(mod, "dev_di_node_add: " 1048 "adding %s\n", devid ? dnode->ddn_devid : "NULL devid"); 1049 topo_mod_dprintf(mod, " " 1050 " %s\n", dnode->ddn_dpath); 1051 for (i = 0; i < dnode->ddn_ppath_count; i++) { 1052 topo_mod_dprintf(mod, " " 1053 " %s\n", dnode->ddn_ppath[i]); 1054 } 1055 topo_list_append(cbp->dcb_list, dnode); 1056 return (0); 1057 1058 error: 1059 dev_di_node_free(mod, dnode); 1060 return (-1); 1061 } 1062 1063 /* di_walk_node callback for disk_list_gather */ 1064 static int 1065 dev_walk_di_nodes(di_node_t node, void *arg) 1066 { 1067 char *devidstr = NULL; 1068 char *s; 1069 int *val; 1070 1071 /* 1072 * If it's not a scsi_vhci client and doesn't have a target_port 1073 * property and doesn't have a target property then it's not a storage 1074 * device and we're not interested. 1075 */ 1076 if (di_path_client_next_path(node, NULL) == NULL && 1077 di_prop_lookup_strings(DDI_DEV_T_ANY, node, 1078 SCSI_ADDR_PROP_TARGET_PORT, &s) <= 0 && 1079 di_prop_lookup_ints(DDI_DEV_T_ANY, node, 1080 SCSI_ADDR_PROP_TARGET, &val) <= 0) { 1081 return (DI_WALK_CONTINUE); 1082 } 1083 (void) di_prop_lookup_strings(DDI_DEV_T_ANY, node, 1084 DEVID_PROP_NAME, &devidstr); 1085 1086 /* create/find the devid scsi topology node */ 1087 (void) dev_di_node_add(node, devidstr, arg); 1088 1089 return (DI_WALK_CONTINUE); 1090 } 1091 1092 int 1093 dev_list_gather(topo_mod_t *mod, topo_list_t *listp) 1094 { 1095 di_node_t devtree; 1096 di_devlink_handle_t devhdl; 1097 disk_cbdata_t dcb; 1098 1099 if ((devtree = topo_mod_devinfo(mod)) == DI_NODE_NIL) { 1100 topo_mod_dprintf(mod, "disk_list_gather: " 1101 "topo_mod_devinfo() failed"); 1102 return (-1); 1103 } 1104 1105 if ((devhdl = di_devlink_init(NULL, 0)) == DI_NODE_NIL) { 1106 topo_mod_dprintf(mod, "disk_list_gather: " 1107 "di_devlink_init() failed"); 1108 return (-1); 1109 } 1110 1111 dcb.dcb_mod = mod; 1112 dcb.dcb_list = listp; 1113 dcb.dcb_devhdl = devhdl; 1114 1115 /* walk the devinfo snapshot looking for disk nodes */ 1116 (void) di_walk_node(devtree, DI_WALK_CLDFIRST, &dcb, 1117 dev_walk_di_nodes); 1118 1119 (void) di_devlink_fini(&devhdl); 1120 1121 return (0); 1122 } 1123 1124 void 1125 dev_list_free(topo_mod_t *mod, topo_list_t *listp) 1126 { 1127 dev_di_node_t *dnode; 1128 1129 while ((dnode = topo_list_next(listp)) != NULL) { 1130 /* order of delete/free is important */ 1131 topo_list_delete(listp, dnode); 1132 dev_di_node_free(mod, dnode); 1133 } 1134 } 1135 1136 /* 1137 * Query the current disk status. If successful, the disk status is returned 1138 * as an nvlist consisting of at least the following members: 1139 * 1140 * protocol string Supported protocol (currently "scsi") 1141 * 1142 * status nvlist Arbitrary protocol-specific information 1143 * about the current state of the disk. 1144 * 1145 * faults nvlist A list of supported faults. Each 1146 * element of this list is a boolean value. 1147 * An element's existence indicates that 1148 * the drive supports detecting this fault, 1149 * and the value indicates the current 1150 * state of the fault. 1151 * 1152 * <fault-name> nvlist For each fault named in 'faults', a 1153 * nvlist describing protocol-specific 1154 * attributes of the fault. 1155 * 1156 * This method relies on the libdiskstatus library to query this information. 1157 */ 1158 static int 1159 disk_status(topo_mod_t *mod, tnode_t *nodep, topo_version_t vers, 1160 nvlist_t *in_nvl, nvlist_t **out_nvl) 1161 { 1162 disk_status_t *dsp; 1163 char *devpath, *fullpath; 1164 size_t pathlen; 1165 nvlist_t *status; 1166 int err; 1167 1168 *out_nvl = NULL; 1169 1170 if (vers != TOPO_METH_DISK_STATUS_VERSION) 1171 return (topo_mod_seterrno(mod, EMOD_VER_NEW)); 1172 1173 /* 1174 * If the caller specifies the "path" parameter, then this indicates 1175 * that we should use this instead of deriving it from the topo node 1176 * itself. 1177 */ 1178 if (nvlist_lookup_string(in_nvl, "path", &fullpath) == 0) { 1179 devpath = NULL; 1180 } else { 1181 /* 1182 * Get the /devices path and attempt to open the disk status 1183 * handle. 1184 */ 1185 if (topo_prop_get_string(nodep, TOPO_PGROUP_IO, 1186 TOPO_IO_DEV_PATH, &devpath, &err) != 0) 1187 return (topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP)); 1188 1189 /* 1190 * Note that sizeof(string) includes the terminating NULL byte 1191 */ 1192 pathlen = strlen(devpath) + sizeof ("/devices") + 1193 sizeof (PHYS_EXTN) - 1; 1194 1195 if ((fullpath = topo_mod_alloc(mod, pathlen)) == NULL) 1196 return (topo_mod_seterrno(mod, EMOD_NOMEM)); 1197 1198 (void) snprintf(fullpath, pathlen, "/devices%s%s", devpath, 1199 PHYS_EXTN); 1200 1201 topo_mod_strfree(mod, devpath); 1202 } 1203 1204 if ((dsp = disk_status_open(fullpath, &err)) == NULL) { 1205 if (devpath) 1206 topo_mod_free(mod, fullpath, pathlen); 1207 return (topo_mod_seterrno(mod, err == EDS_NOMEM ? 1208 EMOD_NOMEM : EMOD_METHOD_NOTSUP)); 1209 } 1210 1211 if (devpath) 1212 topo_mod_free(mod, fullpath, pathlen); 1213 1214 if ((status = disk_status_get(dsp)) == NULL) { 1215 err = (disk_status_errno(dsp) == EDS_NOMEM ? 1216 EMOD_NOMEM : EMOD_METHOD_NOTSUP); 1217 disk_status_close(dsp); 1218 return (topo_mod_seterrno(mod, err)); 1219 } 1220 1221 *out_nvl = status; 1222 disk_status_close(dsp); 1223 return (0); 1224 } 1225