/* * Copyright (c) 2011-2015, Emulex * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "elxu_common.h" #include "elxu_mgmt.h" static void hba_list_info_ocs(elxu_device_t *device_p); static void hba_list_info_pt(elxu_device_t *device_p); /** * @brief Allocates and zeroes memory to be used. * * @param len The amount of memory to be zeroed and allocated. * @return Returns a pointer to the allocated memory or NULL on failure. * */ void *zmalloc (uint32_t len) { void *ret = malloc (len); if (ret != NULL) memset (ret, 0, len); return ret; } /** * @brief Frees memory that has been allocated. * * @par Description * Free memory that was allocated with malloc or zmalloc. * If the pointer is NULL no action is taken. * * @param p The pointer to the memory to be freed. */ void zfree (void *p) { if (p != NULL) { free (p); } } /** * @brief Print a buffer in hexadecimal and ASCII * * @par Description * This function prints out a buffer of data in both * hexadecimal and ASCII. * * @param label Label to be printed above the output. * @param buffer Buffer of binary data to be printed. * @param bufferLength Length of buffer. * * @return Returns nothing. */ void dump8 (const char *label, void *buffer, uint32_t bufferLength) { uint32_t i; uint8_t *pbuf = (uint8_t*) buffer; const uint32_t columns = 16; uint32_t nremaining; uint32_t n; if (label != 0) { printf ("%s\n", label); } nremaining = bufferLength; while (nremaining > 0) { n = min(columns, nremaining); printf (" "); for (i = 0; i < n; i ++) { printf ("%02X ", pbuf[i]); } for (; i < columns; i ++) { printf (" "); } for (i = 0; i < n; i ++) { printf ("%c", isprint (pbuf[i]) ? pbuf[i] : '.'); } for (; i < columns; i ++) { printf (" "); } printf ("\n"); pbuf += n; nremaining -= n; } } /** * @brief Display the list of available devices. * * @par Description * This function displays the list of available devices. It iterates * over devices, starting with index 0. For each index it attempts * to open the device. If the device is opened successfully then * the information about that device is displayed. The loop terminates * when any open fails. * * @param hostname hostname in the case of the user space driver. * * @return Returns nothing. */ void elxu_display_device_list(char *hostname) { elxu_device_t *device_p; elxu_device_name_t devname; int index; index = 0; device_p = elxu_device_by_index(index); while (device_p) { devname.devidx = device_p->deviceIndex; devname.hostname = hostname; if (elxu_open_device(&devname) != NULL) { printf("[%d] ", devname.devidx); elxu_hba_list_info(device_p); elxu_close_device(device_p); } device_p = elxu_device_by_index(++index); } } /** * @brief Displays HBA List information. * * @par Description * Given an open device pointer, this function retrieves and displays * information about the device. The actual handler is different * depending on the driver. * * @param device_p A pointer to an elxu_device_t. The device should * already have been opened with elxu_open_device. * * @return Returns nothing. */ void elxu_hba_list_info (elxu_device_t *device_p) { if (device_p->driver == DEVICE_DRIVER_OCS) { return hba_list_info_ocs(device_p); } else if (device_p->driver == DEVICE_DRIVER_PT) { return hba_list_info_pt(device_p); } else { return; } } static void hba_list_info_ocs(elxu_device_t *device_p) { char* modeldesc; char* fwrev; char* ipl; char* serialnum; char* numamap; char* physnuma; char* fmtwwn; int i; char buf[80]; int num_sports; uint32_t fabemu_enable, phy_port; printf("%s/%s\n", elxu_get_vendor_name(device_p), elxu_get_device_name(device_p)); modeldesc = elxu_get_modeldesc(device_p); printf(" modeldesc: %s\n", modeldesc); printf(" subsystem_vendor: %s\n", elxu_get_subsystem_vendor(device_p)); printf(" subsystem_device: %s\n", elxu_get_subsystem_device(device_p)); printf(" PCI bus address: %s\n", elxu_device_get_value(device_p, "/ocs/businfo")); if(is_lancer(device_p)) { printf(" ASIC ID: %s\n", elxu_device_get_value(device_p, "/ocs/asic_id_reg")); } printf(" Chip Type: %s\n", elxu_device_get_value(device_p, "/ocs/chip_type")); phy_port = elxu_get_phy_port_num(device_p); printf(" Physical Port number: %d\n", phy_port); fwrev = elxu_get_fwrev(device_p); printf(" fwrev: %s\n", fwrev); ipl = elxu_get_ipl(device_p); printf(" IPL: %s\n ", ipl); serialnum = elxu_get_serialnumber(device_p); printf(" s/n: %s\n", serialnum); physnuma = get_physical_numa_node( elxu_device_get_value(device_p, "/ocs/businfo")); printf(" Physical NUMA Node: %s\n", physnuma); numamap = get_numa_node_map(device_p->deviceIndex); printf(" NUMA Map: %s", numamap); fabemu_enable = strtol( elxu_device_get_value(device_p, "/ocs/driver/ctrlmask"), NULL, 0); printf(" Fabric Emul:%s Enabled\n", (fabemu_enable & 0x100) ? "" : " Not"); if (is_iscsi(device_p)) { printf(" mac addr: %s\n", hex_dump(device_p->mac_addr, 6)); } else { printf(" wwnn: %s\n", hex_dump(device_p->wwnn, 8)); printf(" wwpn: %s\n", hex_dump(device_p->wwpn, 8)); } num_sports = strtol( elxu_device_get_value(device_p, "/ocs/domain[0]/num_sports"), NULL, 0); if (num_sports > 1) { printf(" Additional vports:\n"); for (i = 1; i <= num_sports; i++) { char *nn; uint64_t wwn; sprintf(buf, "/ocs/domain[0]/sport[%d]/wwnn", i); nn = elxu_device_get_value(device_p, buf); if (strlen(nn) > 0) { wwn = strtoull(nn, NULL, 0); fmtwwn = format_wwn(wwn); printf(" wwnn: %s\n", fmtwwn); free(fmtwwn); } sprintf(buf, "/ocs/domain[0]/sport[%d]/wwpn", i); nn = elxu_device_get_value(device_p, buf); if (strlen(nn) > 0) { wwn = strtoull(nn, NULL, 0); fmtwwn = format_wwn(wwn); printf(" wwpn: %s\n", fmtwwn); free(fmtwwn); } } } free(serialnum); free(modeldesc); free(numamap); free(physnuma); } static void hba_list_info_pt(elxu_device_t *device_p) { static char* family_names[] = { "BE2", "Unknown", "Unknown", "BE3-R", "Skyhawk-R", "Corsair", "Talon", "Unknown", "Unknown", "Unknown", "Unknown", "Lancer", "Lancer G6", "Unknown", "Unknown", "See ASIC_ID", }; int sli_family; printf("%s/%s (via elx_pt driver)\n", elxu_get_vendor_name(device_p), elxu_get_device_name(device_p)); printf(" PCI bus address: %s\n", device_p->bus_addr); printf(" ASIC ID: 0x%04x\n", device_p->asic_id); printf(" SLI_INTF: 0x%08x\n", device_p->sli_intf); sli_family = ((device_p->sli_intf & 0xf00) >> 8); printf(" SLI_FAMILY: 0x%02x (%s)\n", sli_family, family_names[sli_family]); printf(" fwrev: %s\n", device_p->fwrev); printf(" s/n: %s\n", device_p->serialnum); if (is_iscsi(device_p) || is_nic(device_p)) { printf(" MAC address: %s\n", hex_dump(device_p->mac_addr, 6)); } if (is_fc(device_p)) { printf(" WWNN: %s\n", hex_dump(device_p->wwnn, 8)); printf(" WWPN: %s\n", hex_dump(device_p->wwpn, 8)); } } /** * @brief Parse a WWN from a string into a 64-bit value. * * @par Description * Given a pointer to a string, parse the string into a 64-bit * WWN value. The format of the string must be xx:xx:xx:xx:xx:xx:xx:xx * * @param wwn_in Pointer to the string to be parsed. * @param wwn_out Pointer to uint64_t in which to put the parsed result. * * @return Returns 0 if successful; non-zero if the WWN is malformed and couldn't be parsed */ int parse_wwn(char *wwn_in, uint64_t *wwn_out) { uint8_t byte0; uint8_t byte1; uint8_t byte2; uint8_t byte3; uint8_t byte4; uint8_t byte5; uint8_t byte6; uint8_t byte7; int rc; rc = sscanf(wwn_in, "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", &byte0, &byte1, &byte2, &byte3, &byte4, &byte5, &byte6, &byte7); if (rc == 8) { *wwn_out = ((uint64_t)byte0 << 56) | ((uint64_t)byte1 << 48) | ((uint64_t)byte2 << 40) | ((uint64_t)byte3 << 32) | ((uint64_t)byte4 << 24) | ((uint64_t)byte5 << 16) | ((uint64_t)byte6 << 8) | ((uint64_t)byte7); return 0; } else { return -1; } } /** * @brief Format a 64-bit WWN for printing. * * @par Description * Given a 64-bit WWN value, return a string in the format xx:xx:xx:xx:xx:xx:xx:xx * * @param wwn_in uint64_t containing the WWN * * @return Returns a pointer to the formatted string */ char * format_wwn(uint64_t wwn_in) { char result[24]; sprintf(result, "%02llx:%02llx:%02llx:%02llx:%02llx:%02llx:%02llx:%02llx", ((wwn_in & 0xff00000000000000ll) >> 56), ((wwn_in & 0x00ff000000000000ll) >> 48), ((wwn_in & 0x0000ff0000000000ll) >> 40), ((wwn_in & 0x000000ff00000000ll) >> 32), ((wwn_in & 0x00000000ff000000ll) >> 24), ((wwn_in & 0x0000000000ff0000ll) >> 16), ((wwn_in & 0x000000000000ff00ll) >> 8), ((wwn_in & 0x00000000000000ffll))); return strdup(result); } /** * @brief Display a buffer in hexadecimal. * * @par Description * The buffer pointed to by value is displayed in hexadecimal. * The bytes are displayed as two digit hexadecimal values separated * by spaces. The result is termintated with a NULL. * * @param value A pointer to an array of bytes to be printed. * @param size The size of the array. The maximum size that can be * handled is 25 bytes. Values larger than 25 will result * in only the first 25 bytes being printed. * * @return Returns a pointer to a static character string with the * hexadecimal values. */ char *hex_dump(uint8_t *value, int size) { static char result[80]; char buf[4]; int i; if (size > 25) { size = 25; } result[0] = '\0'; for (i = 0; i < size; i++) { sprintf(buf, "%02x ", value[i]); strcat(result, buf); } return result; } /** * @brief Trim trailing non-printable characters from a string. * * @par Description * Given a pointer to a string, this function returns a copy of * the string up to the first non-printable character found. * The largest string that can be returned is 2047 characters * plus the terminating NULL. * * * @param string A pointer to the string to be trimmed. * * @return Returns a pointer to a static copy of the input string * up to the first non-printable character, or the first * 2047 characters if no non-printable character is found * in that range. */ char *trimmed_string(uint8_t *string) { static char result[2048]; uint8_t *s; char *d; s = string; d = result; while (isprint(*s)) { *d = *s; ++s; ++d; if ((d-result) >= 2048) { break; } } *d = '\0'; return result; } /** * @brief Return the name of a UFI entry type. * * @par Description * Given an integer that represents an entry type in a UFI directory, * this function returns a pointer to a constant string containing * the name of that entry type. * * @param entry_type The type of the entry to lookup. * * @return Returns a pointer to a constant string which is the name * of the given entry type. If the entry type is not recognized * the string "Unknown" will be returned. */ char *decode_entry_type(uint32_t entry_type) { switch(entry_type) { case 0x20: return "PXE BIOS"; case 0x21: return "FCoE BIOS"; case 0x22: return "iSCSI BIOS"; case 0xA0: return "iSCSI/NIC ARM Firmware"; case 0xA2: return "FCoE/NIC ARM Firmware"; case 0xA1: return "Compressed iSCSI/NIC Firmware"; case 0xA3: return "Compressed FCoE/NIC Firmware"; case 0xB0: return "Backup iSCSI/NIC Firmware"; case 0xB2: return "Backup FCoE/NIC Firmware"; case 0xB1: return "Backup Compressed iSCSI/NIC Firmware"; case 0xB3: return "Backup Compressed FCoE/NIC Firmware"; case 0xE0: return "Boot Code"; case 0x10: return "Binary NCSI Firmware"; case 0x30: return "ISM and Jump Vectors"; case 0x31: return "ISM code"; case 0x32: return "Jump Vectors"; case 0xC0: return "PHY Firmware"; case 0xD0: return "Redboot Directory"; case 0xD1: return "Redboot Configuration"; case 0xD2: return "UFI Directory"; default: return "Unknown"; } } /** * @brief Return a string of cpu to numa node mapping . * * @par Description * Given the port index for the ocs port, * this function returns a pointer to a string containing * the cpu to numa node mapping for that port * * @param device_port The type of the port to list the cpu to numa node map. * * @return Returns a pointer to a string which lists the cpu to numa node map * for the given port. If the port is not recognized * no value is returned. * */ char *get_numa_node_map(uint32_t device_port) { #define NUMNODE 256 #define NODERESULT 40 #define NUMCPU 36 #define CPULIST 16 #define NNODELIST 16 #define NODELEN 12 static const char *cmd = "cat /sys/devices/system/node"; static const char *port_cmd = "for i in $(pgrep ocs:); do ps -mo psr -p $i --no-heading | grep -v '-'; done "; static const char *nnode_cmd = "ls /sys/devices/system/node/ 2> /dev/null | grep node"; char buf[BUFSIZ]; char nnode_list[NUMNODE]; char result[NODERESULT]; FILE *ptr; int i; int k = 0; int ret_val = 0; int cpu_cnt = 0; int cpu_list[NUMCPU]; int cpu_val = 0; int nnode_cnt = 0; int port_cpu_list[CPULIST]; char numa_node_list[NNODELIST][NODELEN + 1]; int port_cpu_cnt = 0; int loop_node = 0; if ((ptr = popen(nnode_cmd, "r")) != NULL) { while (fgets(buf, BUFSIZ, ptr) != NULL) { strncpy(numa_node_list[nnode_cnt], buf, NODELEN); nnode_cnt++; } (void) pclose(ptr); } if (nnode_cnt == 0 ) { snprintf(result, 6, "n/a"); return strdup(result); } for (loop_node = 0; loop_node < nnode_cnt; loop_node++) { ret_val = snprintf(nnode_list, sizeof nnode_list, "%s/%s%d/%s",cmd, "node", loop_node, "cpulist"); if (ret_val < 0) { return NULL; } if ((ptr = popen(nnode_list, "r")) != NULL) { while (fgets(buf, BUFSIZ, ptr) != NULL){ } (void) pclose(ptr); } cpu_cnt = 0; cpu_val = 0; for (i = 0; buf[i] != '\0'; i++) { if (buf[i] == '-') { cpu_cnt++; while (cpu_val < atoi(&buf[i+1])) { cpu_list[cpu_cnt] = ++cpu_val; cpu_cnt++; } i++; } else if (buf[i] == ',') { continue; } else { if (isalnum(buf[i])) { cpu_list[cpu_cnt] = atoi(&buf[i]); cpu_val = cpu_list[cpu_cnt]; if (cpu_val > 10) { i++; } } } } if ((ptr = popen(port_cmd, "r")) != NULL) { port_cpu_cnt = 0; while (fgets(buf, BUFSIZ, ptr) != NULL) { port_cpu_list[port_cpu_cnt] = atoi(buf); port_cpu_cnt++; } (void) pclose(ptr); } i = 0; port_cpu_cnt = device_port; for (k = 0; k < cpu_cnt; k++) { if (cpu_list[k] == port_cpu_list[port_cpu_cnt]) { snprintf(result, 20, "cpu%d:%s", port_cpu_list[port_cpu_cnt], numa_node_list[loop_node]); } } i++; } /* end for */ return strdup(result); } /** * @brief Return the physical Numa Node location for an OCS port . * * @par Description * Given the port index for the ocs port, * this function returns a pointer to a string containing * the value of the physical numa node the OCS port is associated with * * @param bus_addr The bus address for the current OCS port * * @return Returns a pointer to a string listing the Physical Numa Node port * for the given port. If the bus address is not found * a value of 'n/a' is listed. * */ char *get_physical_numa_node(char *bus_addr) { #define NPATHLEN 65 #define NUMDEVID 10 #define DEVBUFF 8 FILE *fp; int device_inst = 0; int path_id = 0; static char device_buff[DEVBUFF]; char node_path[NPATHLEN]; char device_id[NUMDEVID]; int res; for (device_inst = 0; bus_addr[device_inst] != '\0'; device_inst++) { if (path_id != 1 || (bus_addr[device_inst] != ':')) { device_id[device_inst] = bus_addr[device_inst]; if(bus_addr[device_inst] == ':') { path_id++; } } } snprintf(node_path, sizeof(node_path)-1, "/sys/class/pci_bus/%s/device/%s/numa_node", device_id, bus_addr); fp = fopen(node_path, "r"); if(fp != NULL) { res = fscanf(fp, "%7s", device_buff); if (res != 1) { snprintf(device_buff, DEVBUFF, "n/a"); } fclose(fp); } else { snprintf(device_buff, DEVBUFF, "n/a"); } return strdup(device_buff); } /** * @page dev_enum Device Enumeration (Using "list") * * Device enumeration uses the List Adapters CLI command (@ref cmd_list "list") to discover the * Emulex adapters installed in the host system. It is executed by the * elxu_display_device_list() function in the elxu_common.c module. * * The function tries to open each device in order, starting with device 0. If a * device is successfully opened, its information is displayed, and then * the device is closed. The process continues until an attempt to open a device fails. */