/* * 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 2019, Joyent, Inc. * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #define dprint(args) devfsadm_errprint args /* * for use in print routine arg list as a shorthand way to locate node via * "prtconf -D" to avoid messy and cluttered debugging code * don't forget the corresponding "%s%d" format */ #define DRVINST(node) di_driver_name(node), di_instance(node) #else #define dprint(args) #endif static int scsi_cfg_creat_cb(di_minor_t minor, di_node_t node); static int sbd_cfg_creat_cb(di_minor_t minor, di_node_t node); static int usb_cfg_creat_cb(di_minor_t minor, di_node_t node); static char *get_roothub(const char *path, void *cb_arg); static int pci_cfg_creat_cb(di_minor_t minor, di_node_t node); static int ib_cfg_creat_cb(di_minor_t minor, di_node_t node); static int sata_cfg_creat_cb(di_minor_t minor, di_node_t node); static int sdcard_cfg_creat_cb(di_minor_t minor, di_node_t node); static int ccid_cfg_creat_cb(di_minor_t minor, di_node_t node); static di_node_t pci_cfg_chassis_node(di_node_t, di_prom_handle_t); static char *pci_cfg_slotname(di_node_t, di_prom_handle_t, minor_t); static int pci_cfg_ap_node(minor_t, di_node_t, di_prom_handle_t, char *, int, int); static int pci_cfg_iob_name(di_minor_t, di_node_t, di_prom_handle_t, char *, int); static minor_t pci_cfg_pcidev(di_node_t, di_prom_handle_t); static int pci_cfg_ap_path(di_minor_t, di_node_t, di_prom_handle_t, char *, int, char **); static char *pci_cfg_info_data(char *); static int pci_cfg_is_ap_path(di_node_t, di_prom_handle_t); static int pci_cfg_ap_legacy(di_minor_t, di_node_t, di_prom_handle_t, char *, int); static void pci_cfg_rm_invalid_links(char *, char *); static void pci_cfg_rm_link(char *); static void pci_cfg_rm_all(char *); static char *pci_cfg_devpath(di_node_t, di_minor_t); static di_node_t pci_cfg_snapshot(di_node_t, di_minor_t, di_node_t *, di_minor_t *); /* flag definitions for di_propall_*(); value "0" is always the default flag */ #define DIPROP_PRI_NODE 0x0 #define DIPROP_PRI_PROM 0x1 static int di_propall_lookup_ints(di_prom_handle_t, int, dev_t, di_node_t, const char *, int **); static int di_propall_lookup_strings(di_prom_handle_t, int, dev_t, di_node_t, const char *, char **); static int serid_printable(uint64_t *seridp); static int di_propall_lookup_slot_names(di_prom_handle_t, int, dev_t, di_node_t, di_slot_name_t **); /* * NOTE: The CREATE_DEFER flag is private to this module. * NOT to be used by other modules */ static devfsadm_create_t cfg_create_cbt[] = { { "attachment-point", DDI_NT_SCSI_ATTACHMENT_POINT, NULL, TYPE_EXACT | CREATE_DEFER, ILEVEL_0, scsi_cfg_creat_cb }, { "attachment-point", DDI_NT_SBD_ATTACHMENT_POINT, NULL, TYPE_EXACT, ILEVEL_0, sbd_cfg_creat_cb }, { "fc-attachment-point", DDI_NT_FC_ATTACHMENT_POINT, NULL, TYPE_EXACT | CREATE_DEFER, ILEVEL_0, scsi_cfg_creat_cb }, { "attachment-point", DDI_NT_USB_ATTACHMENT_POINT, NULL, TYPE_EXACT, ILEVEL_0, usb_cfg_creat_cb }, { "attachment-point", DDI_NT_PCI_ATTACHMENT_POINT, NULL, TYPE_EXACT, ILEVEL_0, pci_cfg_creat_cb }, { "attachment-point", DDI_NT_IB_ATTACHMENT_POINT, NULL, TYPE_EXACT, ILEVEL_0, ib_cfg_creat_cb }, { "attachment-point", DDI_NT_SATA_ATTACHMENT_POINT, NULL, TYPE_EXACT, ILEVEL_0, sata_cfg_creat_cb }, { "attachment-point", DDI_NT_SDCARD_ATTACHMENT_POINT, NULL, TYPE_EXACT, ILEVEL_0, sdcard_cfg_creat_cb }, { "attachment-point", DDI_NT_CCID_ATTACHMENT_POINT, NULL, TYPE_EXACT, ILEVEL_0, ccid_cfg_creat_cb } }; DEVFSADM_CREATE_INIT_V0(cfg_create_cbt); static devfsadm_remove_t cfg_remove_cbt[] = { { "attachment-point", SCSI_CFG_LINK_RE, RM_POST, ILEVEL_0, devfsadm_rm_all }, { "attachment-point", SBD_CFG_LINK_RE, RM_POST, ILEVEL_0, devfsadm_rm_all }, { "fc-attachment-point", SCSI_CFG_LINK_RE, RM_POST, ILEVEL_0, devfsadm_rm_all }, { "attachment-point", USB_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS, ILEVEL_0, devfsadm_rm_all }, { "attachment-point", PCI_CFG_LINK_RE, RM_POST, ILEVEL_0, devfsadm_rm_all }, { "attachment-point", PCI_CFG_PATH_LINK_RE, RM_POST|RM_HOT, ILEVEL_0, pci_cfg_rm_all }, { "attachment-point", IB_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS, ILEVEL_0, devfsadm_rm_all }, { "attachment-point", SATA_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS, ILEVEL_0, devfsadm_rm_all }, { "attachment-point", SDCARD_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS, ILEVEL_0, devfsadm_rm_all }, { "attachment-point", CCID_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS, ILEVEL_0, devfsadm_rm_all } }; DEVFSADM_REMOVE_INIT_V0(cfg_remove_cbt); static int scsi_cfg_creat_cb(di_minor_t minor, di_node_t node) { char path[PATH_MAX + 1]; char *c_num = NULL, *devfs_path, *mn; devfsadm_enumerate_t rules[3] = { {"^r?dsk$/^c([0-9]+)", 1, MATCH_PARENT}, {"^cfg$/^c([0-9]+)$", 1, MATCH_ADDR}, {"^scsi$/^.+$/^c([0-9]+)", 1, MATCH_PARENT} }; mn = di_minor_name(minor); if ((devfs_path = di_devfs_path(node)) == NULL) { return (DEVFSADM_CONTINUE); } (void) strcpy(path, devfs_path); (void) strcat(path, ":"); (void) strcat(path, mn); di_devfs_path_free(devfs_path); if (ctrl_enumerate_int(path, 1, &c_num, rules, 3, 0, B_FALSE) == DEVFSADM_FAILURE) { /* * Unlike the disks module we don't retry on failure. * If we have multiple "c" numbers for a single physical * controller due to bug 4045879, we will not assign a * c-number/symlink for the controller. */ return (DEVFSADM_CONTINUE); } (void) strcpy(path, CFG_DIRNAME); (void) strcat(path, "/c"); (void) strcat(path, c_num); free(c_num); (void) devfsadm_mklink(path, node, minor, 0); return (DEVFSADM_CONTINUE); } static int sbd_cfg_creat_cb(di_minor_t minor, di_node_t node) { char path[PATH_MAX + 1]; (void) strcpy(path, CFG_DIRNAME); (void) strcat(path, "/"); (void) strcat(path, di_minor_name(minor)); (void) devfsadm_mklink(path, node, minor, 0); return (DEVFSADM_CONTINUE); } static int usb_cfg_creat_cb(di_minor_t minor, di_node_t node) { char *cp, path[PATH_MAX + 1]; devfsadm_enumerate_t rules[1] = {"^cfg$/^usb([0-9]+)$", 1, MATCH_CALLBACK, NULL, get_roothub}; if ((cp = di_devfs_path(node)) == NULL) { return (DEVFSADM_CONTINUE); } (void) snprintf(path, sizeof (path), "%s:%s", cp, di_minor_name(minor)); di_devfs_path_free(cp); if (ctrl_enumerate_int(path, 0, &cp, rules, 1, 0, B_FALSE)) { return (DEVFSADM_CONTINUE); } /* create usbN and the symlink */ (void) snprintf(path, sizeof (path), "%s/usb%s/%s", CFG_DIRNAME, cp, di_minor_name(minor)); free(cp); (void) devfsadm_mklink(path, node, minor, 0); return (DEVFSADM_CONTINUE); } static int sata_cfg_creat_cb(di_minor_t minor, di_node_t node) { char path[PATH_MAX + 1], l_path[PATH_MAX], *buf, *devfspath; char *minor_nm; devfsadm_enumerate_t rules[1] = {"^cfg$/^sata([0-9]+)$", 1, MATCH_ADDR}; minor_nm = di_minor_name(minor); if (minor_nm == NULL) return (DEVFSADM_CONTINUE); devfspath = di_devfs_path(node); if (devfspath == NULL) return (DEVFSADM_CONTINUE); (void) strlcpy(path, devfspath, sizeof (path)); (void) strlcat(path, ":", sizeof (path)); (void) strlcat(path, minor_nm, sizeof (path)); di_devfs_path_free(devfspath); /* build the physical path from the components */ if (ctrl_enumerate_int(path, 0, &buf, rules, 1, 0, B_FALSE) == DEVFSADM_FAILURE) { return (DEVFSADM_CONTINUE); } (void) snprintf(l_path, sizeof (l_path), "%s/sata%s/%s", CFG_DIRNAME, buf, minor_nm); free(buf); (void) devfsadm_mklink(l_path, node, minor, 0); return (DEVFSADM_CONTINUE); } static int sdcard_cfg_creat_cb(di_minor_t minor, di_node_t node) { char path[PATH_MAX +1], l_path[PATH_MAX], *buf, *devfspath; char *minor_nm; devfsadm_enumerate_t rules[1] = {"^cfg$/^sdcard([0-9]+)$", 1, MATCH_ADDR}; minor_nm = di_minor_name(minor); if (minor_nm == NULL) return (DEVFSADM_CONTINUE); devfspath = di_devfs_path(node); if (devfspath == NULL) return (DEVFSADM_CONTINUE); (void) snprintf(path, sizeof (path), "%s:%s", devfspath, minor_nm); di_devfs_path_free(devfspath); /* build the physical path from the components */ if (ctrl_enumerate_int(path, 0, &buf, rules, 1, 0, B_FALSE) == DEVFSADM_FAILURE) { return (DEVFSADM_CONTINUE); } (void) snprintf(l_path, sizeof (l_path), "%s/sdcard%s/%s", CFG_DIRNAME, buf, minor_nm); free(buf); (void) devfsadm_mklink(l_path, node, minor, 0); return (DEVFSADM_CONTINUE); } /* * get_roothub: * figure out the root hub path to calculate /dev/cfg/usbN */ /* ARGSUSED */ static char * get_roothub(const char *path, void *cb_arg) { int i, count = 0; char *physpath, *cp; /* make a copy */ if ((physpath = strdup(path)) == NULL) { return (NULL); } /* * physpath must always have a minor name component */ if ((cp = strrchr(physpath, ':')) == NULL) { free(physpath); return (NULL); } *cp++ = '\0'; /* * No '.' in the minor name indicates a roothub port. */ if (strchr(cp, '.') == NULL) { /* roothub device */ return (physpath); } while (*cp) { if (*cp == '.') count++; cp++; } /* Remove as many trailing path components as there are '.'s */ for (i = 0; i < count; i++) { if ((cp = strrchr(physpath, '/')) == NULL || (cp == physpath)) { free(physpath); return (NULL); } /* * Check if there is any usb_mid node in the middle * and remove the node as if there is an extra '.' */ if (strstr(cp, "miscellaneous") != NULL) { count++; } *cp = '\0'; } /* Remove the usb_mid node immediately before the trailing path */ if ((cp = strrchr(physpath, '/')) != NULL && (cp != physpath)) { if (strstr(cp, "miscellaneous") != NULL) { *cp = '\0'; } } return (physpath); } /* * returns an allocted string containing the device path for and * */ static char * pci_cfg_devpath(di_node_t node, di_minor_t minor) { char *path; char *bufp; char *minor_nm; int buflen; path = di_devfs_path(node); minor_nm = di_minor_name(minor); buflen = snprintf(NULL, 0, "%s:%s", path, minor_nm) + 1; bufp = malloc(sizeof (char) * buflen); if (bufp != NULL) (void) snprintf(bufp, buflen, "%s:%s", path, minor_nm); di_devfs_path_free(path); return (bufp); } static int di_propall_lookup_ints(di_prom_handle_t ph, int flags, dev_t dev, di_node_t node, const char *prop_name, int **prop_data) { int rv; if (flags & DIPROP_PRI_PROM) { rv = di_prom_prop_lookup_ints(ph, node, prop_name, prop_data); if (rv < 0) rv = di_prop_lookup_ints(dev, node, prop_name, prop_data); } else { rv = di_prop_lookup_ints(dev, node, prop_name, prop_data); if (rv < 0) rv = di_prom_prop_lookup_ints(ph, node, prop_name, prop_data); } return (rv); } static int di_propall_lookup_strings(di_prom_handle_t ph, int flags, dev_t dev, di_node_t node, const char *prop_name, char **prop_data) { int rv; if (flags & DIPROP_PRI_PROM) { rv = di_prom_prop_lookup_strings(ph, node, prop_name, prop_data); if (rv < 0) rv = di_prop_lookup_strings(dev, node, prop_name, prop_data); } else { rv = di_prop_lookup_strings(dev, node, prop_name, prop_data); if (rv < 0) rv = di_prom_prop_lookup_strings(ph, node, prop_name, prop_data); } return (rv); } static di_node_t pci_cfg_chassis_node(di_node_t node, di_prom_handle_t ph) { di_node_t curnode = node; int *firstchas; do { if (di_propall_lookup_ints(ph, 0, DDI_DEV_T_ANY, curnode, DI_PROP_FIRST_CHAS, &firstchas) >= 0) return (curnode); } while ((curnode = di_parent_node(curnode)) != DI_NODE_NIL); return (DI_NODE_NIL); } static int di_propall_lookup_slot_names(di_prom_handle_t ph, int flags, dev_t dev, di_node_t node, di_slot_name_t **prop_data) { int rv; if (flags & DIPROP_PRI_PROM) { rv = di_prom_prop_lookup_slot_names(ph, node, prop_data); if (rv < 0) rv = di_prop_lookup_slot_names(dev, node, prop_data); } else { rv = di_prop_lookup_slot_names(dev, node, prop_data); if (rv < 0) rv = di_prom_prop_lookup_slot_names(ph, node, prop_data); } return (rv); } /* * returns an allocated string containing the slot name for the slot with * device number on bus */ static char * pci_cfg_slotname(di_node_t node, di_prom_handle_t ph, minor_t pci_dev) { #ifdef DEBUG char *fnm = "pci_cfg_slotname"; #endif int i, count; char *name = NULL; di_slot_name_t *slot_names = NULL; count = di_propall_lookup_slot_names(ph, 0, DDI_DEV_T_ANY, node, &slot_names); if (count < 0) return (NULL); for (i = 0; i < count; i++) { if (slot_names[i].num == (int)pci_dev) { name = strdup(slot_names[i].name); break; } } #ifdef DEBUG if (name == NULL) dprint(("%s: slot w/ pci_dev %d not found in %s for %s%d\n", fnm, (int)pci_dev, DI_PROP_SLOT_NAMES, DRVINST(node))); #endif if (count > 0) di_slot_names_free(count, slot_names); return (name); } /* * returns non-zero if we can return a valid attachment point name for , * for its slot identified by child pci device number , through * * prioritized naming scheme: * 1) (see pci_cfg_slotname()) * 2) * 3) . * * where is derived from the DI_PROP_DEV_TYPE property: * if its value is "pciex" then is "pcie" * else the raw value is used * * if contains APNODE_DEFNAME, then scheme (3) is used */ static int pci_cfg_ap_node(minor_t pci_dev, di_node_t node, di_prom_handle_t ph, char *buf, int bufsz, int flags) { int *nump; int rv; char *str, *devtype; rv = di_propall_lookup_strings(ph, 0, DDI_DEV_T_ANY, node, DI_PROP_DEV_TYPE, &devtype); if (rv < 1) return (0); if (strcmp(devtype, PROPVAL_PCIEX) == 0) devtype = DEVTYPE_PCIE; if (flags & APNODE_DEFNAME) goto DEF; str = pci_cfg_slotname(node, ph, pci_dev); if (str != NULL) { (void) strlcpy(buf, str, bufsz); free(str); return (1); } if (di_propall_lookup_ints(ph, 0, DDI_DEV_T_ANY, node, DI_PROP_PHYS_SLOT, &nump) > 0) { if (*nump > 0) { (void) snprintf(buf, bufsz, "%s%d", devtype, *nump); return (1); } } DEF: (void) snprintf(buf, bufsz, "%s%d.%s%d", di_driver_name(node), di_instance(node), devtype, pci_dev); return (1); } /* * returns non-zero if we can return a valid expansion chassis name for * through * * prioritized naming scheme: * 1) * 2) * 3) * * DI_PROP_SERID encoding <64-bit int: msb ... lsb>: * <24 bits: IEEE company id><40 bits: serial number> * * sun encoding of 40 bit serial number: * first byte = device type indicator * next 4 bytes = 4 ascii characters * * In the unlikely event that serial id contains non-printable characters * the full 64 bit raw hex string will be used for the attachment point. */ /*ARGSUSED*/ static int pci_cfg_iob_name(di_minor_t minor, di_node_t node, di_prom_handle_t ph, char *buf, int bufsz) { int64_t *seridp; uint64_t serid; char *idstr; if (di_prop_lookup_int64(DDI_DEV_T_ANY, node, DI_PROP_SERID, &seridp) < 1) { (void) strlcpy(buf, IOB_PRE, bufsz); return (1); } serid = (uint64_t)*seridp; if ((serid >> 40) != (uint64_t)IEEE_SUN_ID || !serid_printable(&serid)) { (void) snprintf(buf, bufsz, "%s%llx", IOB_PRE, serid); return (1); } /* * the serial id is constructed from lower 40 bits of the serialid * property and is represented by 5 ascii characters. The first * character indicates if the IO Box is PCIe or PCI-X. */ serid <<= 24; idstr = (char *)&serid; idstr[sizeof (serid) -1] = '\0'; (void) snprintf(buf, bufsz, "%s%s", IOB_PRE, idstr); return (1); } /* * returns the pci device number for if found, else returns PCIDEV_NIL */ static minor_t pci_cfg_pcidev(di_node_t node, di_prom_handle_t ph) { int rv; int *regp; rv = di_propall_lookup_ints(ph, 0, DDI_DEV_T_ANY, node, DI_PROP_REG, ®p); if (rv < 1) { dprint(("pci_cfg_pcidev: property %s not found " "for %s%d\n", DI_PROP_REG, DRVINST(node))); return (PCIDEV_NIL); } return (REG_PCIDEV(regp)); } /* * returns non-zero when it can successfully return an attachment point * through whose length is less than ; returns the full * path of the AP through which may be larger than . * Callers need to free . If it cannot return the full path through * it will be set to NULL * * The ap path reflects a subset of the device path from an onboard host slot * up to . We traverse up the device tree starting from , naming * each component using pci_cfg_ap_node(). If we detect that a certain * segment is contained within an expansion chassis, then we skip any bus * nodes in between our current node and the topmost node of the chassis, * which is identified by the DI_PROP_FIRST_CHAS property, and prepend the name * of the expansion chassis as given by pci_cfg_iob_name() * * This scheme is always used for . If however, the size of * is greater than then only the default name as given * by pci_cfg_ap_node() for will be used */ static int pci_cfg_ap_path(di_minor_t minor, di_node_t node, di_prom_handle_t ph, char *ap_path, int ap_pathsz, char **pathret) { #ifdef DEBUG char *fnm = "pci_cfg_ap_path"; #endif #define seplen (sizeof (AP_PATH_SEP) - 1) #define iob_pre_len (sizeof (IOB_PRE) - 1) #define ap_path_iob_sep_len (sizeof (AP_PATH_IOB_SEP) - 1) char *bufptr; char buf[MAXPATHLEN]; char pathbuf[MAXPATHLEN]; int bufsz; char *pathptr; char *pathend = NULL; int len; int rv = 0; int chasflag = 0; di_node_t curnode = node; di_node_t chasnode = DI_NODE_NIL; minor_t pci_dev; buf[0] = '\0'; pathbuf[0] = '\0'; pathptr = &pathbuf[sizeof (pathbuf) - 1]; *pathptr = '\0'; /* * as we traverse up the device tree, we prepend components of our * path inside pathbuf, using pathptr and decrementing */ pci_dev = PCIHP_AP_MINOR_NUM_TO_PCI_DEVNUM(di_minor_devt(minor)); do { bufptr = buf; bufsz = sizeof (buf); chasnode = pci_cfg_chassis_node(curnode, ph); if (chasnode != DI_NODE_NIL) { rv = pci_cfg_iob_name(minor, chasnode, ph, bufptr, bufsz); if (rv == 0) { dprint(("%s: cannot create iob name " "for %s%d\n", fnm, DRVINST(node))); *pathptr = '\0'; goto OUT; } (void) strncat(bufptr, AP_PATH_IOB_SEP, bufsz); len = strlen(bufptr); bufptr += len; bufsz -= len - 1; /* set chasflag when the leaf node is within an iob */ if (curnode == node) chasflag = 1; } rv = pci_cfg_ap_node(pci_dev, curnode, ph, bufptr, bufsz, 0); if (rv == 0) { dprint(("%s: cannot create ap node name " "for %s%d\n", fnm, DRVINST(node))); *pathptr = '\0'; goto OUT; } /* * if we can't fit the entire path in our pathbuf, then use * the default short name and nullify pathptr; also, since * we prepend in the buffer, we must avoid adding a null char */ if (curnode != node) { pathptr -= seplen; if (pathptr < pathbuf) { pathptr = pathbuf; *pathptr = '\0'; goto DEF; } (void) memcpy(pathptr, AP_PATH_SEP, seplen); } len = strlen(buf); pathptr -= len; if (pathptr < pathbuf) { pathptr = pathbuf; *pathptr = '\0'; goto DEF; } (void) memcpy(pathptr, buf, len); /* remember the leaf component */ if (curnode == node) pathend = pathptr; /* * go no further than the hosts' onboard slots */ if (chasnode == DI_NODE_NIL) break; curnode = chasnode; /* * the pci device number of the current node is used to * identify which slot of the parent's bus (next iteration) * the current node is on */ pci_dev = pci_cfg_pcidev(curnode, ph); if (pci_dev == PCIDEV_NIL) { dprint(("%s: cannot obtain pci device number " "for %s%d\n", fnm, DRVINST(node))); *pathptr = '\0'; goto OUT; } } while ((curnode = di_parent_node(curnode)) != DI_NODE_NIL); pathbuf[sizeof (pathbuf) - 1] = '\0'; if (strlen(pathptr) < ap_pathsz) { (void) strlcpy(ap_path, pathptr, ap_pathsz); rv = 1; goto OUT; } DEF: /* * when our name won't fit we use the endpoint/leaf * 's name ONLY IF it has a serialid# which will make the apid * globally unique */ if (chasflag && pathend != NULL) { if ((strncmp(pathend + iob_pre_len, AP_PATH_IOB_SEP, ap_path_iob_sep_len) != 0) && (strlen(pathend) < ap_pathsz)) { (void) strlcpy(ap_path, pathend, ap_pathsz); rv = 1; goto OUT; } } /* * if our name still won't fit , then use the leaf 's * default name */ pci_dev = PCIHP_AP_MINOR_NUM_TO_PCI_DEVNUM(di_minor_devt(minor)); rv = pci_cfg_ap_node(pci_dev, node, ph, buf, bufsz, APNODE_DEFNAME); if (rv == 0) { dprint(("%s: cannot create default ap node name for %s%d\n", fnm, DRVINST(node))); *pathptr = '\0'; goto OUT; } if (strlen(buf) < ap_pathsz) { (void) strlcpy(ap_path, buf, ap_pathsz); rv = 1; goto OUT; } /* * in this case, cfgadm goes through an expensive process to generate * a purely dynamic logical apid: the framework will look through * the device tree for attachment point minor nodes and will invoke * each plugin responsible for that attachment point class, and if * the plugin returns a logical apid that matches the queried apid * or matches the default apid generated by the cfgadm framework for * that driver/class (occurs when plugin returns an empty logical apid) * then that is what it will use * * it is doubly expensive because the cfgadm pci plugin itself will * also search the entire device tree in the absence of a link */ rv = 0; dprint(("%s: cannot create apid for %s%d within length of %d\n", fnm, DRVINST(node), ap_pathsz)); OUT: ap_path[ap_pathsz - 1] = '\0'; *pathret = (*pathptr == '\0') ? NULL : strdup(pathptr); return (rv); #undef seplen #undef iob_pre_len #undef ap_path_iob_sep_len } /* * the DI_PROP_AP_NAMES property contains the first integer section of the * ieee1275 "slot-names" property and functions as a bitmask; see comment for * pci_cfg_slotname() * * we use the name of the attachment point minor node if its pci device * number (encoded in the minor number) is allowed by DI_PROP_AP_NAMES * * returns non-zero if we return a valid attachment point through */ static int pci_cfg_ap_legacy(di_minor_t minor, di_node_t node, di_prom_handle_t ph, char *ap_path, int ap_pathsz) { minor_t pci_dev; int *anp; if (di_propall_lookup_ints(ph, 0, DDI_DEV_T_ANY, node, DI_PROP_AP_NAMES, &anp) < 1) return (0); pci_dev = PCIHP_AP_MINOR_NUM_TO_PCI_DEVNUM(di_minor_devt(minor)); if ((*anp & (1 << pci_dev)) == 0) return (0); (void) strlcpy(ap_path, di_minor_name(minor), ap_pathsz); return (1); } /* * determine if qualifies for a path style apid */ static int pci_cfg_is_ap_path(di_node_t node, di_prom_handle_t ph) { char *devtype; di_node_t curnode = node; do { if (di_propall_lookup_strings(ph, 0, DDI_DEV_T_ANY, curnode, DI_PROP_DEV_TYPE, &devtype) > 0) if (strcmp(devtype, PROPVAL_PCIEX) == 0) return (1); } while ((curnode = di_parent_node(curnode)) != DI_NODE_NIL); return (0); } /* * takes a full path as returned by from pci_cfg_ap_path() and * returns an allocated string intendend to be stored in a devlink info (dli) * file * * data format: "Location: " * where is with occurrances of AP_PATH_SEP * replaced by "/" */ static char * pci_cfg_info_data(char *path) { #define head "Location: " #define headlen (sizeof (head) - 1) #define seplen (sizeof (AP_PATH_SEP) - 1) char *sep, *prev, *np; char *newpath; int pathlen = strlen(path); int len; newpath = malloc(sizeof (char) * (headlen + pathlen + 1)); np = newpath; (void) strcpy(np, head); np += headlen; prev = path; while ((sep = strstr(prev, AP_PATH_SEP)) != NULL) { len = sep - prev; (void) memcpy(np, prev, len); np += len; *np++ = '/'; prev = sep + seplen; } (void) strcpy(np, prev); return (newpath); #undef head #undef headlen #undef seplen } static void pci_cfg_rm_link(char *file) { char *dlipath; dlipath = di_dli_name(file); (void) unlink(dlipath); devfsadm_rm_all(file); free(dlipath); } /* * removes all registered devlinks to physical path except for * the devlink if not NULL; * must include the minor node */ static void pci_cfg_rm_invalid_links(char *physpath, char *valid) { char **dnp; char *cp, *vcp; int i, dnlen; dnp = devfsadm_lookup_dev_names(physpath, NULL, &dnlen); if (dnp == NULL) return; if (valid != NULL) { if (strncmp(valid, DEV "/", DEV_LEN + 1) == 0) vcp = valid + DEV_LEN + 1; else vcp = valid; } for (i = 0; i < dnlen; i++) { if (strncmp(dnp[i], DEV "/", DEV_LEN + 1) == 0) cp = dnp[i] + DEV_LEN + 1; else cp = dnp[i]; if (valid != NULL) { if (strcmp(vcp, cp) == 0) continue; } pci_cfg_rm_link(cp); } devfsadm_free_dev_names(dnp, dnlen); } /* * takes a complete devinfo snapshot and returns the root node; * callers must do a di_fini() on the returned node; * if the snapshot failed, DI_NODE_NIL is returned instead * * if is not DI_NODE_NIL, it will search for the same devinfo node * in the new snapshot and return it through if it is found, * else DI_NODE_NIL is returned instead * * in addition, if is not DI_MINOR_NIL, it will also return * the matching minor in the new snapshot through if it is found, * else DI_MINOR_NIL is returned instead */ static di_node_t pci_cfg_snapshot(di_node_t pci_node, di_minor_t pci_minor, di_node_t *ret_node, di_minor_t *ret_minor) { di_node_t root_node; di_node_t node; di_minor_t minor; int pci_inst; dev_t pci_devt; *ret_node = DI_NODE_NIL; *ret_minor = DI_MINOR_NIL; root_node = di_init("/", DINFOCPYALL); if (root_node == DI_NODE_NIL) return (DI_NODE_NIL); /* * narrow down search by driver, then instance, then minor */ if (pci_node == DI_NODE_NIL) return (root_node); pci_inst = di_instance(pci_node); node = di_drv_first_node(di_driver_name(pci_node), root_node); do { if (pci_inst == di_instance(node)) { *ret_node = node; break; } } while ((node = di_drv_next_node(node)) != DI_NODE_NIL); if (node == DI_NODE_NIL) return (root_node); /* * found node, now search minors */ if (pci_minor == DI_MINOR_NIL) return (root_node); pci_devt = di_minor_devt(pci_minor); minor = DI_MINOR_NIL; while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) { if (pci_devt == di_minor_devt(minor)) { *ret_minor = minor; break; } } return (root_node); } static int pci_cfg_creat_cb(di_minor_t pci_minor, di_node_t pci_node) { #ifdef DEBUG char *fnm = "pci_cfg_creat_cb"; #endif #define ap_pathsz (sizeof (ap_path)) char ap_path[CFGA_LOG_EXT_LEN]; char linkbuf[MAXPATHLEN]; char *fullpath = NULL; char *pathinfo = NULL; char *devpath = NULL; int rv, fd = -1; size_t sz; di_prom_handle_t ph; di_node_t node; di_node_t root_node = DI_NODE_NIL; di_minor_t minor; ph = di_prom_init(); if (ph == DI_PROM_HANDLE_NIL) { dprint(("%s: di_prom_init() failed for %s%d\n", fnm, DRVINST(pci_node))); goto OUT; } /* * Since incoming nodes from hotplug events are from snapshots that * do NOT contain parent/ancestor data, we must retake our own * snapshot and search for the target node */ root_node = pci_cfg_snapshot(pci_node, pci_minor, &node, &minor); if (root_node == DI_NODE_NIL || node == DI_NODE_NIL || minor == DI_MINOR_NIL) { dprint(("%s: devinfo snapshot or search failed for %s%d\n", fnm, DRVINST(pci_node))); goto OUT; } if (pci_cfg_is_ap_path(node, ph)) { rv = pci_cfg_ap_path(minor, node, ph, ap_path, ap_pathsz, &fullpath); if (rv == 0) goto OUT; (void) snprintf(linkbuf, sizeof (linkbuf), "%s/%s", CFG_DIRNAME, ap_path); /* * We must remove existing links because we may have invalid * apids that are valid links. Since these are not dangling, * devfsadm will not invoke the remove callback on them. * * What are "invalid apids with valid links"? Consider swapping * an attachment point bus with another while the system is * down, on the same device path bound to the same drivers * but with the new AP bus having different properties * (e.g. serialid#). If the previous apid is not removed, * there will now be two different links pointing to the same * attachment point, but only one reflects the correct * logical apid */ devpath = pci_cfg_devpath(node, minor); if (devpath == NULL) goto OUT; pci_cfg_rm_invalid_links(devpath, linkbuf); free(devpath); (void) devfsadm_mklink(linkbuf, node, minor, 0); /* * we store the full logical path of the attachment point for * cfgadm to display in its info field which is useful when * the full logical path exceeds the size limit for logical * apids (CFGA_LOG_EXT_LEN) * * for the cfgadm pci plugin to do the same would be expensive * (i.e. devinfo snapshot + top down exhaustive minor search + * equivalent of pci_cfg_ap_path() on every invocation) * * note that if we do not create a link (pci_cfg_ap_path() is * not successful), that is what cfgadm will do anyways to * create a purely dynamic apid */ pathinfo = pci_cfg_info_data(fullpath); fd = di_dli_openw(linkbuf); if (fd < 0) goto OUT; sz = strlen(pathinfo) + 1; rv = write(fd, pathinfo, sz); if (rv < sz) { dprint(("%s: could not write full pathinfo to dli " "file for %s%d\n", fnm, DRVINST(node))); goto OUT; } di_dli_close(fd); } else { rv = pci_cfg_ap_legacy(minor, node, ph, ap_path, ap_pathsz); if (rv == 0) goto OUT; (void) snprintf(linkbuf, sizeof (linkbuf), "%s/%s", CFG_DIRNAME, ap_path); (void) devfsadm_mklink(linkbuf, node, minor, 0); } OUT: if (fd >= 0) di_dli_close(fd); if (fullpath != NULL) free(fullpath); if (pathinfo != NULL) free(pathinfo); if (ph != DI_PROM_HANDLE_NIL) di_prom_fini(ph); if (root_node != DI_NODE_NIL) di_fini(root_node); return (DEVFSADM_CONTINUE); #undef ap_pathsz } static void pci_cfg_rm_all(char *file) { pci_cfg_rm_link(file); } /* * ib_cfg_creat_cb() creates two types of links * One for the fabric as /dev/cfg/ib * Another for each HCA seen in the fabric as /dev/cfg/hca: */ static int ib_cfg_creat_cb(di_minor_t minor, di_node_t node) { char *cp; char path[PATH_MAX + 1]; if ((cp = di_devfs_path(node)) == NULL) { return (DEVFSADM_CONTINUE); } (void) snprintf(path, sizeof (path), "%s:%s", cp, di_minor_name(minor)); di_devfs_path_free(cp); /* create fabric or hca:GUID and the symlink */ if (strstr(path, "ib:fabric") != NULL) { (void) snprintf(path, sizeof (path), "%s/ib", CFG_DIRNAME); } else { (void) snprintf(path, sizeof (path), "%s/hca:%s", CFG_DIRNAME, di_minor_name(minor)); } (void) devfsadm_mklink(path, node, minor, 0); return (DEVFSADM_CONTINUE); } /* * This function verifies if the serial id is printable. */ static int serid_printable(uint64_t *seridp) { char *ptr; int i = 0; for (ptr = (char *)seridp+3; i < 5; ptr++, i++) if (*ptr < 0x21 || *ptr >= 0x7f) return (0); return (1); } /* * Create a link for cfgadm that points back to the normal ccid links in * /dev/ccid. */ static int ccid_cfg_creat_cb(di_minor_t minor, di_node_t node) { const char *minor_nm; char cfg_path[MAXPATHLEN]; if ((minor_nm = di_minor_name(minor)) == NULL) { return (DEVFSADM_CONTINUE); } (void) snprintf(cfg_path, sizeof (cfg_path), "%s/ccid%d/%s", CFG_DIRNAME, di_instance(node), minor_nm); (void) devfsadm_mklink(cfg_path, node, minor, 0); return (DEVFSADM_CONTINUE); }