/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include "disk.h" static int disk_status(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *, nvlist_t **); /* * Given a /devices path for a whole disk, appending this extension gives the * path to a raw device that can be opened. */ #if defined(__i386) || defined(__amd64) #define PHYS_EXTN ":q,raw" #elif defined(__sparc) || defined(__sparcv9) #define PHYS_EXTN ":c,raw" #else #error Unknown architecture #endif static int disk_enum(topo_mod_t *, tnode_t *, const char *, topo_instance_t, topo_instance_t, void *, void *); static const topo_modops_t disk_ops = { disk_enum, NULL }; const topo_modinfo_t disk_info = {DISK, FM_FMRI_SCHEME_HC, DISK_VERSION, &disk_ops}; static const topo_pgroup_info_t io_pgroup = { TOPO_PGROUP_IO, TOPO_STABILITY_PRIVATE, TOPO_STABILITY_PRIVATE, 1 }; static const topo_pgroup_info_t disk_auth_pgroup = { FM_FMRI_AUTHORITY, TOPO_STABILITY_PRIVATE, TOPO_STABILITY_PRIVATE, }; static const topo_pgroup_info_t storage_pgroup = { TOPO_STORAGE_PGROUP, TOPO_STABILITY_PRIVATE, TOPO_STABILITY_PRIVATE, 1 }; /* * Methods for disks. This is used by the disk-transport module to * generate ereports based off SCSI disk status. */ static const topo_method_t disk_methods[] = { { TOPO_METH_DISK_STATUS, TOPO_METH_DISK_STATUS_DESC, TOPO_METH_DISK_STATUS_VERSION, TOPO_STABILITY_INTERNAL, disk_status }, { NULL } }; static di_devlink_handle_t devlink_hdl = NULL; /* disk node information */ typedef struct disk_di_node { topo_list_t ddn_list; int ddn_instance; char *ddn_devid; di_node_t ddn_node; char *ddn_lpath; /* logical path */ char *ddn_dpath; /* device path */ }disk_di_node_t; typedef struct disk_di_nodes { pthread_mutex_t disk_di_nodes_lock; topo_list_t disk_di_nodes_list; }disk_di_nodes_t; /* list of devices */ static disk_di_nodes_t disk_di_nodes; /* given a device find it in the global device list */ static disk_di_node_t * disk_di_node_match_device(char *device) { disk_di_node_t *dnode; (void) pthread_mutex_lock(&disk_di_nodes.disk_di_nodes_lock); for (dnode = topo_list_next(&(disk_di_nodes.disk_di_nodes_list)); dnode != NULL; dnode = topo_list_next(dnode)) { if (dnode->ddn_devid != NULL && strcmp(device, dnode->ddn_dpath) == 0) { (void) pthread_mutex_unlock( &disk_di_nodes.disk_di_nodes_lock); return (dnode); } } (void) pthread_mutex_unlock(&disk_di_nodes.disk_di_nodes_lock); return (NULL); } /* get the disk storage group information */ static void disk_storage_info(topo_mod_t *mod, disk_di_node_t *dnode, char **model, char **manuf, char **serial, char **firm, char **cap) { char *entry; di_node_t node = dnode->ddn_node; int64_t *nblocksp; uint64_t nblocks; int *dblksizep; uint_t dblksize; char lentry[MAXPATHLEN]; if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, INQUIRY_VENDOR_ID, &entry) > 0) { *manuf = topo_mod_strdup(mod, entry); } if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, INQUIRY_PRODUCT_ID, &entry) > 0) { *model = topo_mod_strdup(mod, entry); } if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, INQUIRY_REVISION_ID, &entry) > 0) { *firm = topo_mod_strdup(mod, entry); } if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, INQUIRY_SERIAL_NO, &entry) > 0) { *serial = topo_mod_strdup(mod, entry); } if (di_prop_lookup_int64(DDI_DEV_T_ANY, node, "device-nblocks", &nblocksp) > 0) { nblocks = (uint64_t)*nblocksp; /* * To save kernel memory, the driver may not * define "device-dblksize" when its value is * the default DEV_BSIZE value. */ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-dblksize", &dblksizep) > 0) dblksize = (uint_t)*dblksizep; else dblksize = DEV_BSIZE; /* default value */ (void) snprintf(lentry, sizeof (lentry), "%" PRIu64, nblocks * dblksize); *cap = topo_mod_strdup(mod, lentry); } } /* populate the protocol group properties */ static void disk_set_proto_props(topo_mod_t *mod, tnode_t *dtn, int pinst) { int err; nvlist_t *asru = NULL; char label[32]; char *func = "disk_set_proto_props"; nvlist_t *fmri; disk_di_node_t *dnode; /* set the asru */ dnode = topo_node_getspecific(dtn); asru = topo_mod_devfmri(mod, FM_DEV_SCHEME_VERSION, dnode->ddn_dpath, dnode->ddn_devid); if (topo_node_asru_set(dtn, asru, 0, &err) != 0) { topo_mod_dprintf(mod, "%s: topo_node_asru_set error %d\n", func, err); nvlist_free(asru); (void) topo_mod_seterrno(mod, err); return; } nvlist_free(asru); snprintf(label, sizeof (label), "HD_ID_%d", pinst); if (topo_node_label_set(dtn, label, &err) != 0) { topo_mod_dprintf(mod, "%s: label error %s\n", func, topo_strerror(err)); (void) topo_mod_seterrno(mod, err); return; } /* get the resource property */ if (topo_node_resource(dtn, &fmri, &err) != 0) { topo_mod_dprintf(mod, "%s: topo_node_resource error: %s\n", func, topo_strerror(err)); (void) topo_mod_seterrno(mod, err); return; } /* set the child fru to the same as the resource */ if (topo_node_fru_set(dtn, fmri, 0, &err) != 0) { topo_mod_dprintf(mod, "%s: topo_node_fru_set error: %s\n", func, topo_strerror(err)); (void) topo_mod_seterrno(mod, err); nvlist_free(fmri); return; } nvlist_free(fmri); } /* * Set the properties of the disk node which include: * group: protocol properties: resource, asru, label, fru * group: authority properties: product-id, chasis-id, server-id * group: io properties: devfs-path * group: storage properties: * - logical-disk, disk-model, disk-manufacturer, serial-number * - firmware-revision, capacity-in-bytes */ static void disk_set_props(tnode_t *dtn, tnode_t *parent, char *model, char *manuf, char *serial, char *firm, char *cap, int *err, topo_mod_t *mod) { char *device; char *ptr, *ptr1; int inst = topo_node_instance(parent); disk_di_node_t *dnode; topo_prop_get_string(parent, TOPO_BINDING_PGROUP, TOPO_BINDING_OCCUPANT, &device, err); dnode = topo_node_getspecific(dtn); /* set the protocol group properties */ disk_set_proto_props(mod, dtn, inst); /* create/set the authority group */ if (topo_pgroup_create(dtn, &disk_auth_pgroup, err) == 0) { (void) topo_prop_inherit(dtn, FM_FMRI_AUTHORITY, FM_FMRI_AUTH_PRODUCT, err); (void) topo_prop_inherit(dtn, FM_FMRI_AUTHORITY, FM_FMRI_AUTH_CHASSIS, err); (void) topo_prop_inherit(dtn, FM_FMRI_AUTHORITY, FM_FMRI_AUTH_SERVER, err); } /* create/set the devfs-path in the io group */ (void) topo_pgroup_create(dtn, &io_pgroup, err); (void) topo_prop_set_string(dtn, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH, TOPO_PROP_IMMUTABLE, device, err); topo_mod_strfree(mod, device); /* create the storage group */ (void) topo_pgroup_create(dtn, &storage_pgroup, err); /* set the storage group properties */ ptr = strrchr(dnode->ddn_lpath, '/'); ptr1 = strchr(ptr, 's'); if (ptr1) *ptr1 = '\0'; (void) topo_prop_set_string(dtn, TOPO_STORAGE_PGROUP, TOPO_STORAGE_LOGICAL_DISK_NAME, TOPO_PROP_IMMUTABLE, ptr+1, err); if (ptr1) *ptr1 = 's'; /* populate the storage group properties */ if (model) { (void) topo_prop_set_string(dtn, TOPO_STORAGE_PGROUP, TOPO_STORAGE_MODEL, TOPO_PROP_IMMUTABLE, model, err); } if (manuf) { (void) topo_prop_set_string(dtn, TOPO_STORAGE_PGROUP, TOPO_STORAGE_MANUFACTURER, TOPO_PROP_IMMUTABLE, manuf, err); } if (serial) { (void) topo_prop_set_string(dtn, TOPO_STORAGE_PGROUP, TOPO_STORAGE_SERIAL_NUM, TOPO_PROP_IMMUTABLE, serial, err); } if (firm) { (void) topo_prop_set_string(dtn, TOPO_STORAGE_PGROUP, TOPO_STORAGE_FIRMWARE_REV, TOPO_PROP_IMMUTABLE, firm, err); } if (cap) { (void) topo_prop_set_string(dtn, TOPO_STORAGE_PGROUP, TOPO_STORAGE_CAPACITY, TOPO_PROP_IMMUTABLE, cap, err); } } /* create the disk topo node */ /*ARGSUSED*/ static tnode_t * disk_tnode_create(topo_mod_t *mod, tnode_t *parent, const char *name, topo_instance_t i, char *model, char *manuf, char *serial, char *firm, char *cap, void *priv) { int err, len = 0; nvlist_t *fmri; tnode_t *dtn; char *mm = NULL; char *s; nvlist_t *auth = topo_mod_auth(mod, parent); if ((s = strchr(model, ' ')) != NULL) { *s = '-'; } len = strlen(manuf) + strlen(model) + 2; if ((mm = topo_mod_alloc(mod, len)) != NULL) (void) snprintf(mm, len, "%s-%s", manuf, model); else mm = model; fmri = topo_mod_hcfmri(mod, parent, FM_HC_SCHEME_VERSION, name, i, NULL, auth, mm, firm, serial); nvlist_free(auth); if (mm != model) topo_mod_free(mod, mm, len); else if (*s != NULL) *s = ' '; if (fmri == NULL) { topo_mod_dprintf(mod, "Unable to make nvlist for %s bind: %s.\n", name, topo_mod_errmsg(mod)); return (NULL); } if ((dtn = topo_node_bind(mod, parent, name, i, fmri)) == NULL) { topo_mod_dprintf(mod, "topo_node_bind (%s%d/%s%d) failed: %s\n", topo_node_name(parent), topo_node_instance(parent), name, i, topo_strerror(topo_mod_errno(mod))); nvlist_free(fmri); return (NULL); } nvlist_free(fmri); topo_node_setspecific(dtn, priv); /* add the properties of the disk */ disk_set_props(dtn, parent, model, manuf, serial, firm, cap, &err, mod); return (dtn); } /*ARGSUSED*/ static tnode_t * disk_declare(tnode_t *parent, const char *name, topo_instance_t i, void *priv, topo_mod_t *mod) { tnode_t *dtn; int err; char *func = "disk_declare"; char *model = NULL, *manuf = NULL, *serial = NULL; char *cap = NULL, *firm = NULL; disk_di_node_t *dnode = (disk_di_node_t *)priv; nvlist_t *fmri; disk_storage_info(mod, dnode, &model, &manuf, &serial, &firm, &cap); /* create the node */ dtn = disk_tnode_create(mod, parent, name, i, model, manuf, serial, firm, cap, priv); topo_mod_strfree(mod, model); topo_mod_strfree(mod, manuf); topo_mod_strfree(mod, serial); topo_mod_strfree(mod, firm); topo_mod_strfree(mod, cap); if (dtn == NULL) { return (NULL); } /* set the parent fru */ if (topo_node_resource(parent, &fmri, &err) != 0) { topo_mod_dprintf(mod, "%s: topo_node_resource error: %s\n", func, topo_strerror(err)); topo_node_unbind(dtn); return (NULL); } if (topo_node_fru_set(parent, fmri, 0, &err) != 0) { topo_mod_dprintf(mod, "%s topo_node_fru error: %s\n", func, topo_strerror(err)); nvlist_free(fmri); topo_node_unbind(dtn); return (NULL); } if (topo_method_register(mod, dtn, disk_methods) != 0) { topo_mod_dprintf(mod, "topo_method_register failed: %s\n", topo_strerror(topo_mod_errno(mod))); nvlist_free(fmri); topo_node_unbind(dtn); return (NULL); } nvlist_free(fmri); return (dtn); } /*ARGSUSED*/ static int disk_enum(topo_mod_t *mod, tnode_t *rnode, const char *name, topo_instance_t min, topo_instance_t max, void *arg, void *notused) { tnode_t *diskn; char *device; int err; disk_di_node_t *dnode; if (strcmp(name, DISK) != 0) { topo_mod_dprintf(mod, "Currently only know how to enumerate %s components.\n", DISK); return (-1); } topo_prop_get_string(rnode, TOPO_BINDING_PGROUP, TOPO_BINDING_OCCUPANT, &device, &err); if ((dnode = disk_di_node_match_device(device)) == NULL) { topo_mod_dprintf(mod, "No occupant found for bay=%d.\n", topo_node_instance(rnode)); topo_mod_strfree(mod, device); return (-1); } diskn = disk_declare(rnode, name, 0, dnode, mod); if (diskn == NULL) { topo_mod_dprintf(mod, "Enumeration of %s failed: %s\n", DISK, topo_strerror(topo_mod_errno(mod))); topo_mod_strfree(mod, device); return (-1); /* mod_errno already set */ } topo_mod_strfree(mod, device); return (0); } /* * Query the current disk status. If successful, the disk status is returned as * an nvlist consisting of at least the following members: * * protocol string Supported protocol (currently "scsi") * * status nvlist Arbitrary protocol-specific information * about the current state of the disk. * * faults nvlist A list of supported faults. Each * element of this list is a boolean value. * An element's existence indicates that * the drive supports detecting this fault, * and the value indicates the current * state of the fault. * * nvlist For each fault named in 'faults', a * nvlist describing protocol-specific * attributes of the fault. * * This method relies on the libdiskstatus library to query this information. */ static int disk_status(topo_mod_t *mod, tnode_t *nodep, topo_version_t vers, nvlist_t *in_nvl, nvlist_t **out_nvl) { disk_status_t *dsp; char *devpath, *fullpath; size_t pathlen; int err; nvlist_t *status; *out_nvl = NULL; if (vers != TOPO_METH_DISK_STATUS_VERSION) return (topo_mod_seterrno(mod, EMOD_VER_NEW)); /* * If the caller specifies the "path" parameter, then this indicates * that we should use this instead of deriving it from the topo node * itself. */ if (nvlist_lookup_string(in_nvl, "path", &fullpath) == 0) { devpath = NULL; } else { /* * Get the /devices path and attempt to open the disk status * handle. */ if (topo_prop_get_string(nodep, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH, &devpath, &err) != 0) return (topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP)); /* * Note that sizeof(string) includes the terminating NULL byte */ pathlen = strlen(devpath) + sizeof ("/devices") + sizeof (PHYS_EXTN) - 1; if ((fullpath = topo_mod_alloc(mod, pathlen)) == NULL) return (topo_mod_seterrno(mod, EMOD_NOMEM)); (void) snprintf(fullpath, pathlen, "/devices%s%s", devpath, PHYS_EXTN); topo_mod_strfree(mod, devpath); } if ((dsp = disk_status_open(fullpath, &err)) == NULL) { if (devpath) topo_mod_free(mod, fullpath, pathlen); return (topo_mod_seterrno(mod, err == EDS_NOMEM ? EMOD_NOMEM : EMOD_METHOD_NOTSUP)); } if (devpath) topo_mod_free(mod, fullpath, pathlen); if ((status = disk_status_get(dsp)) == NULL) { err = (disk_status_errno(dsp) == EDS_NOMEM ? EMOD_NOMEM : EMOD_METHOD_NOTSUP); disk_status_close(dsp); return (topo_mod_seterrno(mod, err)); } *out_nvl = status; disk_status_close(dsp); return (0); } /* di_devlink callback for disk_drvinst2devpath */ static int disk_drvinst2devpath_devlink_callback(di_devlink_t dl, void *arg) { char **devpathp = (char **)arg; char *devpath = (char *)di_devlink_path(dl); *devpathp = strdup(devpath); return (DI_WALK_TERMINATE); } static disk_di_node_t * disk_di_node_add(int *instancep, char *devid, di_node_t node, topo_mod_t *mod) { int mlen; char *devpath, *minorpath; char *extn = ":a"; disk_di_node_t *dnode; (void) pthread_mutex_lock(&(disk_di_nodes.disk_di_nodes_lock)); for (dnode = topo_list_next(&(disk_di_nodes.disk_di_nodes_list)); dnode != NULL; dnode = topo_list_next(dnode)) { if (strcmp(dnode->ddn_devid, devid) == 0) { topo_mod_dprintf(mod, "disk_node_add - already there %s\n", devid); (void) pthread_mutex_unlock( &disk_di_nodes.disk_di_nodes_lock); return (dnode); /* return existing node */ } } if ((dnode = topo_mod_alloc(mod, sizeof (disk_di_node_t))) == NULL) { topo_mod_dprintf(mod, "disk_node_add - topo_mod_alloc failed\n"); (void) pthread_mutex_unlock(&disk_di_nodes.disk_di_nodes_lock); return (NULL); /* return existing node */ } dnode->ddn_devid = strdup(devid); dnode->ddn_instance = *instancep; dnode->ddn_node = node; dnode->ddn_dpath = di_devfs_path(node); mlen = strlen(dnode->ddn_dpath) + strlen(extn) + 1; minorpath = topo_mod_alloc(mod, mlen); (void) snprintf(minorpath, mlen, "%s%s", dnode->ddn_dpath, extn); /* walk devlink looking for node that maps to /device path */ devpath = NULL; (void) di_devlink_walk(devlink_hdl, "^dsk/", minorpath, DI_PRIMARY_LINK, (void *)&devpath, disk_drvinst2devpath_devlink_callback); topo_mod_free(mod, minorpath, mlen); dnode->ddn_lpath = devpath; topo_list_append(&disk_di_nodes.disk_di_nodes_list, (void *)dnode); (void) pthread_mutex_unlock(&disk_di_nodes.disk_di_nodes_lock); topo_mod_dprintf(mod, "disk_node_add - adding %s inst: %d\n", dnode->ddn_devid, *instancep); *instancep = (*instancep) + 1; return (dnode); } /*ARGSUSED*/ static int disk_walk_di_nodes(di_node_t node, void *arg) { ddi_devid_t devid = NULL; char *devidstr; static int instance_devid = 0; topo_mod_t *mod = (topo_mod_t *)arg; /* only interested in nodes that have devids */ devid = (ddi_devid_t)di_devid(node); if (devid == NULL) return (DI_WALK_CONTINUE); /* ... with a string representation of the devid */ devidstr = devid_str_encode(devid, NULL); if (devidstr == NULL) return (DI_WALK_CONTINUE); /* create/find the devid scsi topology node */ (void) disk_di_node_add(&instance_devid, devidstr, node, mod); devid_str_free(devidstr); return (DI_WALK_CONTINUE); } /*ARGSUSED*/ int _topo_init(topo_mod_t *mod, topo_version_t version) { di_node_t devtree; /* * Turn on module debugging output */ if (getenv("TOPODISKDEBUG") != NULL) topo_mod_setdebug(mod); topo_mod_dprintf(mod, "initializing %s enumerator\n", DISK); if (topo_mod_register(mod, &disk_info, TOPO_VERSION) != 0) { topo_mod_dprintf(mod, "%s registration failed: %s\n", DISK, topo_mod_errmsg(mod)); return (-1); /* mod errno already set */ } (void) pthread_mutex_init(&disk_di_nodes.disk_di_nodes_lock, NULL); disk_di_nodes.disk_di_nodes_list.l_next = NULL; disk_di_nodes.disk_di_nodes_list.l_prev = NULL; devtree = di_init("/", DINFOCACHE); /* we don't get all the nodes with topo_mod_devinfo */ if (devtree == NULL) { topo_mod_unregister(mod); (void) pthread_mutex_destroy(&disk_di_nodes.disk_di_nodes_lock); topo_mod_dprintf(mod, "topo_mod_devinfo init failed."); return (-1); } /* walk the tree to get the devids */ devlink_hdl = di_devlink_init(NULL, 0); if (devlink_hdl == DI_NODE_NIL) { topo_mod_unregister(mod); (void) pthread_mutex_destroy(&disk_di_nodes.disk_di_nodes_lock); topo_mod_dprintf(mod, "di_devlink init failed."); return (-1); } (void) di_walk_node(devtree, DI_WALK_CLDFIRST, mod, disk_walk_di_nodes); if (devlink_hdl != NULL) (void) di_devlink_fini(&devlink_hdl); topo_mod_dprintf(mod, "%s enumerator initialized\n", DISK); return (0); } void _topo_fini(topo_mod_t *mod) { disk_di_node_t *dnode; (void) pthread_mutex_lock(&disk_di_nodes.disk_di_nodes_lock); while ((dnode = topo_list_next(&(disk_di_nodes.disk_di_nodes_list))) != NULL) { free(dnode->ddn_lpath); free(dnode->ddn_dpath); free(dnode->ddn_devid); topo_list_delete(&(disk_di_nodes.disk_di_nodes_list), (void *)dnode); topo_mod_free(mod, dnode, sizeof (disk_di_node_t)); } (void) pthread_mutex_unlock(&disk_di_nodes.disk_di_nodes_lock); (void) pthread_mutex_destroy(&disk_di_nodes.disk_di_nodes_lock); topo_mod_unregister(mod); }