/* * 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. * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "elxu_common.h" #include "elx_pt_ioctl.h" #include #define MAXDEVS 1024 #define ELX_PT_SERIAL_NUM_SIZE 32 /* * This file contains functions specific to Linux. * Any non-static functions here should: * - be declared in elxu_common.h * - have an equivalent in elxu_bsd.c, even if it's a stub */ static char* format_ip_address(uint8_t *address, uint8_t address_type); static void pt_device_init(elxu_device_t *device); static int os_create_device_files(char *driver_path) { int major; int count; int total; int rc; FILE *proc_file; int idx; char path[FILENAME_MAX]; FTS *ftsp; FTSENT *ftsent; FTSENT *chp; char *dirs[2]; char *drv; char *p; if (driver_path) dirs[0] = driver_path; else dirs[0] = "/proc"; dirs[1] = NULL; ftsp = fts_open(dirs, FTS_COMFOLLOW | FTS_NOCHDIR, NULL); if (ftsp == NULL) { // can't initialize fts return -1; } chp = fts_children(ftsp, 0); if (chp == NULL) { // child initialization failed return -1; } /* * Bug 181400 - Remove any device special files before creating new ones. * Since drivers can be unloaded, then we must walk the /dev/ocs_XX and remove * all that exist and then recreate them. */ total = 0; do { snprintf(path, sizeof(path), "/dev/%s_%d", "ocs", total); rc = remove(path); if (rc != 0 && errno != ENOENT) { perror("Error removing /dev/ocs*\n"); } total++; } while (rc == 0); total = 0; while((ftsent = fts_read(ftsp)) != NULL) { if (ftsent->fts_info != FTS_F) { continue; } if (strncmp(ftsent->fts_path, "/proc/ocs", strlen("/proc/ocs"))) { continue; } /* * Don't process /proc/ocsu. That's for the user space shim, and * we don't want to access those directly. */ if (strncmp(ftsent->fts_path, "/proc/ocsu", strlen("/proc/ocsu")) == 0) { continue; } proc_file = fopen(ftsent->fts_path, "r"); if (proc_file == NULL) { // Can't open /proc/ocs to get driver info return -1; } rc = fscanf(proc_file, "%d,%d", &major, &count); fclose(proc_file); if (rc != 2) { // Didn't get major and count from /proc/ocs return -1; } drv = strdup(ftsent->fts_path + strlen("/proc/")); if (drv == NULL) { // Allocation failed return -1; } if ((p = strchr(drv, '_'))) { *p = 0; } for (idx = 0; idx < count; idx ++) { snprintf(path, sizeof(path), "/dev/%s_%d", drv, total); // Try to make the device file. If it fails, ignore the error and continue. rc = mknod(path, S_IFCHR | 0644, makedev(major, idx)); if (rc) { printf("Failed creating %s\n", path); perror("Error =\n"); } total++; } free(drv); } return 0; } /** * @ingroup dev_mgmt * @brief Build a list of devices * * @par Description * This function locates all devices on the system that the utility can manage * and builds the device list. It is only concerned with devices accessed directly * through the PCI bus, not with user space devices accessed through sockets. * * For each Emulex device found on the bus it will determine the driver controlling * the device, and will only add the device to the list if the driver is either one * of the ocs_* drivers or is elx_pt. * * @return Returns a pointer to the first device in the list. May be NULL if no * devices are found. */ elxu_device_t *os_build_device_list(char *path) { DIR *pci_dir; struct dirent *entry; elxu_device_t *new_device; elxu_device_t *list; char config_file_name[1024]; int config_file; char driver_link_name[1024]; char driver_path[1024]; char *driver_name; uint16_t vendor_id; uint16_t device_id; int n; int next_index; int ocs_index; pci_dir = opendir("/sys/bus/pci/devices"); if (pci_dir == NULL) { return NULL; } /* Create device files for OCS drivers, if needed */ os_create_device_files(path); list = NULL; next_index=0; /* * Find OCS devices first, so that instance numbers agree with * /dev/ocs_* filenames (for backwards compatibility). */ ocs_index = 0; while (1) { char device_file_name[256]; int fd; int rc; ocs_ioctl_driver_info_t info; sprintf(device_file_name, "/dev/ocs_%d", ocs_index); fd = open(device_file_name, O_RDONLY); if (fd < 0) { break; } rc = ioctl(fd, OCS_IOCTL_CMD_DRIVER_INFO, &info); if (rc == 0) { new_device = malloc(sizeof(elxu_device_t)); /* Should check for malloc failure here */ bzero(new_device, sizeof(*new_device)); /* Add the device to the global list */ new_device->next = list; list = new_device; new_device->driver = DEVICE_DRIVER_OCS; strcpy(new_device->device_file_name, device_file_name); new_device->vendor = info.pci_vendor; new_device->device = info.pci_device; strcpy(new_device->bus_addr, info.businfo); new_device->type = DEVICE_TYPE_UNKNOWN; set_protocol(new_device); new_device->deviceIndex = next_index++; } ++ocs_index; close(fd); } /* * Now scan the PCI devices for any other devices we want to manage. */ while ((entry = readdir(pci_dir))) { snprintf(config_file_name, sizeof(config_file_name), "/sys/bus/pci/devices/%s/config", entry->d_name); config_file = open(config_file_name, O_RDWR); if (config_file < 0) { continue; } if ((read(config_file, &vendor_id, sizeof(vendor_id)) < sizeof(vendor_id)) || (read(config_file, &device_id, sizeof(device_id)) < sizeof(device_id))) { fprintf(stderr , "couldn't read vendor ID or device ID"); } if (vendor_id == PCI_VENDOR_ID_EMULEX) { new_device = malloc(sizeof(elxu_device_t)); /* Should check for malloc failure here */ bzero(new_device, sizeof(*new_device)); /* Add the device to the global list */ new_device->next = list; list = new_device; /* Fill in information which applies to all drivers */ new_device->vendor = vendor_id; new_device->device = device_id; strcpy(new_device->bus_addr, entry->d_name); set_protocol(new_device); new_device->deviceIndex = next_index++; /* Figure out the driver */ snprintf(driver_link_name, sizeof(driver_link_name), "/sys/bus/pci/devices/%s/driver", entry->d_name); n = readlink(driver_link_name, driver_path, sizeof(driver_path) - 1); /* * Take driver specific actions, including possibly removing the * device from the list if it's not a driver we handle. */ if (n < 0) { /* * If we support CSII (mailbox access with no driver) in the * future then we'll keep this device and mark it with * DEVICE_DRIVER_NONE. For now we'll just take it off the list. */ list = new_device->next; --next_index; free(new_device); } else { driver_path[n] = '\0'; driver_name = strrchr(driver_path, '/') + 1; if (strncmp(driver_name, "ocs_", 4) == 0) { /* OCS drivers were handled already */ list = new_device->next; --next_index; free(new_device); } else if (strcmp(driver_name, "elx_pt") == 0) { pt_device_init(new_device); } else if (strcmp(driver_name, "be2net") == 0) { /* * If we support be2net passthrough in the future * we'll keep this device and mark it with DEVICE_DRIVER_BE2NET. * For now we'll just take it off the list. */ list = new_device->next; --next_index; free(new_device); } else { /* * If it has a driver we don't recognize (lpfc?) we just * ignore it. */ list = new_device->next; --next_index; free(new_device); } } } } closedir(pci_dir); return list; } elxu_device_t *os_build_device_list_uspace(char *hostname) { int devidx = 0; elxu_device_t *device_p; elxu_device_t *list; void *desc; list = NULL; while(1) { ocs_ioctl_driver_info_t info; int rc; desc = ocsu_open(hostname, devidx); if (desc == NULL) { break; } device_p = malloc(sizeof(elxu_device_t)); /* Should check for malloc failure here */ memset(device_p, '\0', sizeof(*device_p)); snprintf(device_p->device_file_name, sizeof(device_p->device_file_name), "%s[%d]", hostname, devidx); device_p->desc = desc; device_p->driver = DEVICE_DRIVER_OCS; rc = ocsu_ioctl(desc, OCS_IOCTL_CMD_DRIVER_INFO, &info); if (rc == 0) { device_p->vendor = info.pci_vendor; device_p->device = info.pci_device; strncpy(device_p->bus_addr, info.businfo, sizeof(device_p->bus_addr)); } device_p->type = DEVICE_TYPE_UNKNOWN; set_protocol(device_p); fill_device_details(device_p); device_p->next = list; list = device_p; device_p->deviceIndex = devidx; ++devidx; ocsu_close(device_p->desc); } return list; } static void pt_device_init(elxu_device_t *device) { int major; int minor; char driver_name[80]; char line[80]; FILE *proc_file; char dev_file_name[80]; int i; int fd; int rc; elx_pt_ioctl_info_t info; char *serial_num = NULL; /* Get major number from /proc/devices */ proc_file = fopen("/proc/devices", "r"); if (proc_file == NULL) { return; } major = -1; while (fgets(line, sizeof(line), proc_file)) { if (strstr(line, "elx_pt")) { sscanf(line, "%d %s", &i, driver_name); if (strcmp(driver_name, "elx_pt") == 0) { major = i; break; } } } fclose(proc_file); if (major == -1) { return; } /* Try each minor number */ for (minor=0; minorfd = -1; return; } rc = mknod(dev_file_name, S_IFCHR, makedev(major, minor)); if (rc != 0) { return; } fd = open(dev_file_name, O_RDWR); if (fd < 0) { return; } } device->fd = fd; /* Get driver info from the device file */ bzero(&info, sizeof(info)); rc = ioctl(fd, IOCTL_CMD_ELX_PT_GET_INFO, &info); if (rc != 0) { close(fd); device->fd = -1; continue; } /* See if this device is the correct one */ if (strcmp(info.bus_addr, device->bus_addr) == 0) { sli4_res_common_get_cntl_attributes_t attr; elx_pt_ioctl_mbox_t mbox; strcpy(device->device_file_name, dev_file_name); device->vendor = info.vendor; device->device = info.device; device->asic_id = info.asic_id; device->sli_intf = info.sli_intf; device->driver = DEVICE_DRIVER_PT; bzero(&attr, sizeof(attr)); attr.hdr.req.subsystem = MBOX_SUBSYSTEM_COMMON; attr.hdr.req.opcode = OPCODE_COMMON_GET_CNTL_ATTRIBUTES; attr.hdr.req.request_length = sizeof(attr); mbox.words = (uint32_t *)&attr; mbox.num_words = (sizeof(attr) + 3) / 4; rc = ioctl(fd, IOCTL_CMD_ELX_PT_MBOX, &mbox); if (rc == 0) { device->modeldesc = strdup((char *)attr.controller_description); strcpy(device->fwrev, (char *)attr.firmware_version_string); if (is_iscsi(device) || is_nic(device)) { device->serialnum = strdup((char *)attr.controller_serial_number); } if (is_fc(device)) { serial_num = zmalloc(ELX_PT_SERIAL_NUM_SIZE); if (serial_num) { mbox_get_serialnum_pt(device, serial_num, ELX_PT_SERIAL_NUM_SIZE); device->serialnum = strdup((char *)serial_num); } else { device->serialnum = NULL; } } } if (is_iscsi(device) || is_nic(device)) { mbox_get_mac_pt(device, device->mac_addr); } if (is_fc(device)) { mbox_get_wwn_pt(fd, device->wwnn, device->wwpn); } device->fd = -1; if (serial_num) free(serial_num); close(fd); return; } device->fd = -1; close(fd); if (serial_num) free(serial_num); } } elxu_device_t *os_open_device(elxu_device_name_t *devname) { elxu_device_t *device = NULL; char path[FILENAME_MAX]; device = elxu_device_by_index(devname->devidx); if (device == NULL) { /* Device index not found in list */ return NULL; } /* if devname->hostname is non-null, then open device special file, otherwise * open a socket. */ if (devname->hostname == NULL) { /* Open device */ device->fd = open(device->device_file_name, O_RDWR); if (device->fd < 0) { return NULL; } } else { device->desc = ocsu_open(devname->hostname, devname->devidx); if (device->desc == NULL) { return NULL; } snprintf(path, sizeof(path), "%s[%d]", devname->hostname, devname->devidx); strncpy(device->device_file_name, path , sizeof(device->device_file_name) - 1); } return device; } int os_ioctl_device(elxu_device_t *device, int req, void *arg) { int rc; if (device->desc != NULL) { rc = ocsu_ioctl(device->desc, req, arg); } else { rc = ioctl(device->fd, req, arg); } return rc; } void os_close_device(elxu_device_t *device) { if (device->desc != NULL) { ocsu_close(device->desc); } else { close(device->fd); } } int os_remove_device(elxu_device_t *device) { return remove(device->device_file_name); } void elxu_get_connection_info(elxu_device_t *device) { #define MAX_CONNECTIONS 256 ocs_ioctl_connection_info_t *info = calloc(MAX_CONNECTIONS, sizeof(*info)); ocs_ioctl_connections_t req; int rval; int i; req.connections = info; req.max_connections = MAX_CONNECTIONS; rval = elxu_ioctl_device(device, OCS_IOCTL_CMD_CONNECTION_INFO, &req); if (rval < 0) { perror("ioctl"); return; } if (req.num_connections == 0) { printf("No connections found\n"); return; } for (i=0; i