/* * Copyright (c) 2008, Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "utils.h" #include "api_lib.h" #include "adapt_impl.h" #include "bind_impl.h" /* * Binding capabilities we understand. */ #define BINDING_CAPABILITIES (HBA_CAN_BIND_TO_D_ID | \ HBA_CAN_BIND_TO_WWPN | \ HBA_CAN_BIND_TO_WWNN) #define SYSFS_BIND "tgtid_bind_type" /* * Name-value strings for kernel bindings. * The first word of the strings must exactly match those in * Linux's drivers/scsi/scsi_transport_fc */ static struct sa_nameval binding_types_table[] = { { "none", 0 }, { "wwpn (World Wide Port Name)", HBA_CAN_BIND_TO_WWPN }, { "wwnn (World Wide Node Name)", HBA_CAN_BIND_TO_WWNN }, { "port_id (FC Address)", HBA_CAN_BIND_TO_D_ID }, { NULL, 0 } }; /* * Context for LUN binding reader. */ struct binding_context { HBA_HANDLE oc_handle; int oc_kern_hba; /* kernel HBA number */ int oc_port; int oc_target; int oc_lun; u_int32_t oc_count; u_int32_t oc_limit; u_int32_t oc_ver; void *oc_entries; HBA_STATUS oc_status; char oc_sg[32]; /* SCSI-generic dev name */ HBA_SCSIID *oc_scp; /* place for OS device name */ struct port_info *oc_rport; /* target remote port, if known */ char oc_path[256]; /* parent dir save area */ }; /* * Get binding capability. * We currently don't have a way to get this from the driver. * Instead, we hardcode what we know about Linux's capabilities. * We don't care which HBA is specified, except to return the correct error. */ HBA_STATUS get_binding_capability(HBA_HANDLE handle, HBA_WWN wwn, HBA_BIND_CAPABILITY *cp) { struct port_info *pp; int count = 0; pp = adapter_get_port_by_wwn(handle, wwn, &count); if (count > 1) return HBA_STATUS_ERROR_AMBIGUOUS_WWN; else if (pp == NULL) return HBA_STATUS_ERROR_ILLEGAL_WWN; *cp = BINDING_CAPABILITIES; return HBA_STATUS_OK; } /* * Get binding support. */ HBA_STATUS get_binding_support(HBA_HANDLE handle, HBA_WWN wwn, HBA_BIND_CAPABILITY *cp) { struct port_info *pp; char dir[50]; char bind[50]; int count = 0; pp = adapter_get_port_by_wwn(handle, wwn, &count); if (count > 1) return HBA_STATUS_ERROR_AMBIGUOUS_WWN; else if (pp == NULL) return HBA_STATUS_ERROR_ILLEGAL_WWN; snprintf(dir, sizeof(dir), SYSFS_HOST_DIR "/host%u", pp->ap_kern_hba); if (sa_sys_read_line(dir, SYSFS_BIND, bind, sizeof(bind)) == 0) sa_enum_encode(binding_types_table, bind, cp); return HBA_STATUS_OK; } /* * Set binding support. */ HBA_STATUS set_binding_support(HBA_HANDLE handle, HBA_WWN wwn, HBA_BIND_CAPABILITY flags) { struct port_info *pp; int count = 0; char dir[50]; char buf[50]; const char *bind; pp = adapter_get_port_by_wwn(handle, wwn, &count); if (count > 1) return HBA_STATUS_ERROR_AMBIGUOUS_WWN; if (pp == NULL) return HBA_STATUS_ERROR_ILLEGAL_WWN; if ((flags & BINDING_CAPABILITIES) != flags) return HBA_STATUS_ERROR_NOT_SUPPORTED; bind = sa_enum_decode(buf, sizeof(buf), binding_types_table, flags); snprintf(dir, sizeof(dir), SYSFS_HOST_DIR "/host%u", pp->ap_kern_hba); if (strstr(bind, "Unknown") != NULL) return HBA_STATUS_ERROR_NOT_SUPPORTED; if (sa_sys_write_line(dir, SYSFS_BIND, bind) == 0) return HBA_STATUS_ERROR_INCAPABLE; return HBA_STATUS_OK; } static int get_deprecated_device_name(struct dirent *dp, void *arg) { struct binding_context *cp = arg; if (strstr(cp->oc_path, "block")) snprintf(cp->oc_scp->OSDeviceName, sizeof(cp->oc_scp->OSDeviceName), "/dev/%s", dp->d_name); if (strstr(cp->oc_path, "scsi_generic")) snprintf(cp->oc_sg, sizeof(cp->oc_sg), "/dev/%s", dp->d_name); return 0; } static int get_binding_os_names(struct dirent *dp, void *arg) { struct binding_context *cp = arg; char *name = dp->d_name; char *sep; char buf[sizeof(cp->oc_scp->OSDeviceName)]; sep = strchr(name, ':'); if (dp->d_type == DT_LNK && sep != NULL) { *sep = '\0'; /* replace colon */ if (strcmp(name, "block") == 0) { snprintf(cp->oc_scp->OSDeviceName, sizeof(cp->oc_scp->OSDeviceName), "/dev/%s", sep + 1); } else if (strcmp(name, "scsi_generic") == 0) { snprintf(cp->oc_sg, sizeof(cp->oc_sg), "/dev/%s", sep + 1); } *sep = ':'; /* not really needed */ } else if (dp->d_type == DT_DIR && sep == NULL) { if ((!strcmp(name, "block")) || (!strcmp(name, "scsi_generic"))) { /* save the original path */ sa_strncpy_safe(buf, sizeof(buf), cp->oc_path, sizeof(cp->oc_path)); snprintf(cp->oc_path, sizeof(cp->oc_path), "%s/%s", buf, name); sa_dir_read(cp->oc_path, get_deprecated_device_name, cp); /* restore the original path */ sa_strncpy_safe(cp->oc_path, sizeof(cp->oc_path), buf, sizeof(buf)); } } return 0; } static int get_binding_target_mapping(struct dirent *dp, void *ctxt_arg) { struct binding_context *cp = ctxt_arg; struct port_info *pp; HBA_FCPSCSIENTRY *sp; HBA_FCPSCSIENTRYV2 *s2p; HBA_SCSIID *scp = NULL; HBA_FCPID *fcp = NULL; HBA_LUID *luid = NULL; char name[50]; u_int32_t hba = -1; u_int32_t port = -1; u_int32_t tgt = -1; u_int32_t lun = -1; /* * Parse directory entry name to see if it matches * :::. */ if (sscanf(dp->d_name, "%u:%u:%u:%u", &hba, &port, &tgt, &lun) != 4) return 0; if (hba != cp->oc_kern_hba || (port != cp->oc_port && cp->oc_port != -1) || (tgt != cp->oc_target && cp->oc_target != -1) || (lun != cp->oc_lun && cp->oc_lun != -1)) { return 0; } /* * Name matches. Add to count and to mapping list if there's room. */ if (cp->oc_count < cp->oc_limit) { switch (cp->oc_ver) { case 1: sp = &((HBA_FCPSCSIENTRY *) cp->oc_entries)[cp->oc_count]; scp = &sp->ScsiId; fcp = &sp->FcpId; luid = NULL; break; case 2: s2p = &((HBA_FCPSCSIENTRYV2 *) cp->oc_entries)[cp->oc_count]; scp = &s2p->ScsiId; fcp = &s2p->FcpId; luid = &s2p->LUID; break; default: fprintf(stderr, "*** Fatal! ***\n"); break; } pp = cp->oc_rport; if (pp == NULL) pp = adapter_get_rport_target(cp->oc_handle, port, tgt); if (pp != NULL) { fcp->FcId = pp->ap_attr.PortFcId; fcp->NodeWWN = pp->ap_attr.NodeWWN; fcp->PortWWN = pp->ap_attr.PortWWN; fcp->FcpLun = (HBA_UINT64) lun; } /* * Find OS device name by searching for symlink block: * and SG name by searching for scsi_generic: * in device subdirectory. */ snprintf(name, sizeof(name), SYSFS_LUN_DIR "/%s/device", dp->d_name); cp->oc_sg[0] = '\0'; cp->oc_scp = scp; scp->OSDeviceName[0] = '\0'; /* save a copy of the dir name */ sa_strncpy_safe(cp->oc_path, sizeof(cp->oc_path), name, sizeof(name)); sa_dir_read(name, get_binding_os_names, cp); scp->ScsiBusNumber = hba; scp->ScsiTargetNumber = tgt; scp->ScsiOSLun = lun; /* * find the LUN ID information by using scsi_generic I/O. */ if (luid != NULL && cp->oc_sg[0] != '\0') sg_get_dev_id(cp->oc_sg, luid->buffer, sizeof(luid->buffer)); } cp->oc_count++; return 0; } /* * Get FCP target mapping. */ HBA_STATUS get_binding_target_mapping_v1(HBA_HANDLE handle, HBA_FCPTARGETMAPPING *map) { struct binding_context ctxt; struct adapter_info *ap; ap = adapter_open_handle(handle); if (ap == NULL) return HBA_STATUS_ERROR_INVALID_HANDLE; memset(&ctxt, 0, sizeof(ctxt)); ctxt.oc_handle = handle; ctxt.oc_kern_hba = ap->ad_kern_index; ctxt.oc_port = -1; ctxt.oc_target = -1; ctxt.oc_lun = -1; ctxt.oc_limit = map->NumberOfEntries; ctxt.oc_ver = 1; ctxt.oc_entries = map->entry; ctxt.oc_status = HBA_STATUS_OK; memset(map->entry, 0, sizeof(map->entry[0]) * ctxt.oc_limit); sa_dir_read(SYSFS_LUN_DIR, get_binding_target_mapping, &ctxt); map->NumberOfEntries = ctxt.oc_count; if (ctxt.oc_status == HBA_STATUS_OK && ctxt.oc_count > ctxt.oc_limit) ctxt.oc_status = HBA_STATUS_ERROR_MORE_DATA; return ctxt.oc_status; } /* * Get FCP target mapping. */ HBA_STATUS get_binding_target_mapping_v2(HBA_HANDLE handle, HBA_WWN wwn, HBA_FCPTARGETMAPPINGV2 *map) { struct binding_context ctxt; struct adapter_info *ap; struct port_info *pp; pp = adapter_get_port_by_wwn(handle, wwn, NULL); if (pp == NULL) return HBA_STATUS_ERROR_INVALID_HANDLE; ap = pp->ap_adapt; if (ap == NULL) return HBA_STATUS_ERROR_INVALID_HANDLE; memset(&ctxt, 0, sizeof(ctxt)); ctxt.oc_handle = handle; ctxt.oc_kern_hba = ap->ad_kern_index; ctxt.oc_port = pp->ap_index; ctxt.oc_target = -1; ctxt.oc_lun = -1; ctxt.oc_limit = map->NumberOfEntries; ctxt.oc_ver = 2; ctxt.oc_entries = map->entry; ctxt.oc_status = HBA_STATUS_OK; memset(map->entry, 0, sizeof(map->entry[0]) * ctxt.oc_limit); sa_dir_read(SYSFS_LUN_DIR, get_binding_target_mapping, &ctxt); map->NumberOfEntries = ctxt.oc_count; if (ctxt.oc_status == HBA_STATUS_OK && ctxt.oc_count > ctxt.oc_limit) ctxt.oc_status = HBA_STATUS_ERROR_MORE_DATA; return ctxt.oc_status; } /* * Get LUN scsi-generic device name. */ int get_binding_sg_name(struct port_info *lp, HBA_WWN disc_wwpn, HBA_UINT64 fc_lun, char *buf, size_t len) { struct binding_context ctxt; struct port_info *rp; HBA_FCPSCSIENTRYV2 entry; /* * find discovered (remote) port. */ rp = adapter_get_rport_by_wwn(lp, disc_wwpn); if (rp == NULL) return HBA_STATUS_ERROR_ILLEGAL_WWN; memset(&ctxt, 0, sizeof(ctxt)); memset(&entry, 0, sizeof(entry)); ctxt.oc_rport = rp; ctxt.oc_kern_hba = rp->ap_kern_hba; ctxt.oc_port = rp->ap_index; ctxt.oc_target = rp->ap_scsi_target; if (ctxt.oc_target == -1) return ENOENT; ctxt.oc_lun = (int) fc_lun; ctxt.oc_limit = 1; ctxt.oc_ver = 1; ctxt.oc_entries = &entry; sa_dir_read(SYSFS_LUN_DIR, get_binding_target_mapping, &ctxt); if (ctxt.oc_count != 1) return ENOENT; sa_strncpy_safe(buf, len, ctxt.oc_sg, sizeof(ctxt.oc_sg)); return 0; }