/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include "cfga_fp.h"
/* Function prototypes */
static fpcfga_ret_t get_xport_devlink(const char *hba_phys,
char **hba_logpp, int *l_errnop);
static char ctoi(char c);
static fpcfga_ret_t is_apid_configured(const char *xport_phys,
const char *dyncomp, struct luninfo_list **lunlistpp, int *l_errnop);
static fpcfga_ret_t insert_lun_to_lunlist(struct luninfo_list **lunlistpp,
const char *dyncomp, di_node_t devnode, int *l_errnop);
static fpcfga_ret_t update_lunlist(struct luninfo_list **lunlistpp, int lun,
uint_t state, char *pathp, int *l_errnop);
/* Globals */
/* Various conversions routines */
void
cvt_lawwn_to_dyncomp(const la_wwn_t *pwwn, char **dyncomp, int *l_errnop)
{
*dyncomp = calloc(1, WWN_SIZE*2 + 1);
if (*dyncomp == NULL) {
*l_errnop = errno;
}
(void) sprintf(*dyncomp, "%016llx",
(wwnConversion((uchar_t *)pwwn->raw_wwn)));
}
int
cvt_dyncomp_to_lawwn(const char *dyncomp, la_wwn_t *port_wwn)
{
int i;
char c, c1;
uchar_t *wwnp;
wwnp = port_wwn->raw_wwn;
for (i = 0; i < WWN_SIZE; i++, wwnp++) {
c = ctoi(*dyncomp++);
c1 = ctoi(*dyncomp++);
if (c == -1 || c1 == -1)
return (-1);
*wwnp = ((c << 4) + c1);
}
return (0);
}
static char
ctoi(char c)
{
if ((c >= '0') && (c <= '9'))
c -= '0';
else if ((c >= 'A') && (c <= 'F'))
c = c - 'A' + 10;
else if ((c >= 'a') && (c <= 'f'))
c = c - 'a' + 10;
else
c = -1;
return (c);
}
/*
* Generates the HBA logical ap_id from physical ap_id.
*/
fpcfga_ret_t
make_xport_logid(const char *xport_phys, char **xport_logpp, int *l_errnop)
{
if (*xport_logpp != NULL) {
return (FPCFGA_ERR);
}
/*
* A devlink for the XPORT should exist. Without the /dev/cfg link
* driver name and instance number based based link needs to be
* constructed for the minor node type of DDI_NT_FC_ATTACHMENT_POINT.
* sunddi.h defines DDI_NT_FC_ATTACHMENT_POINT for
* ddi_ctl:attachment_point:fc
*/
if (get_xport_devlink(xport_phys, xport_logpp, l_errnop) == FPCFGA_OK) {
assert(*xport_logpp != NULL);
return (FPCFGA_OK);
} else {
return (FPCFGA_ERR);
}
}
static fpcfga_ret_t
get_xport_devlink(const char *xport_phys, char **xport_logpp, int *l_errnop)
{
int match_minor;
size_t len;
fpcfga_ret_t ret;
match_minor = 1;
ret = physpath_to_devlink(CFGA_DEV_DIR, (char *)xport_phys,
xport_logpp, l_errnop, match_minor);
if (ret != FPCFGA_OK) {
return (ret);
}
assert(*xport_logpp != NULL);
/* Remove the "/dev/cfg/" prefix */
len = strlen(CFGA_DEV_DIR SLASH);
(void) memmove(*xport_logpp, *xport_logpp + len,
strlen(*xport_logpp + len) + 1);
return (FPCFGA_OK);
}
/*
* Given a xport path and dynamic ap_id, returns the physical
* path in pathpp. If the dynamic ap is not configured pathpp set to NULL
* and returns FPCFGA_APID_NOCONFIGURE.
*/
fpcfga_ret_t
dyn_apid_to_path(
const char *xport_phys,
const char *dyncomp,
struct luninfo_list **lunlistpp,
int *l_errnop)
{
fpcfga_ret_t ret;
/* A device MUST have a dynamic component */
if (dyncomp == NULL) {
return (FPCFGA_LIB_ERR);
}
ret = is_apid_configured(xport_phys, dyncomp, lunlistpp, l_errnop);
assert(ret != FPCFGA_OK);
return (ret);
}
/*
* When both the transport and dynamic comp are given this function
* checks to see if the dynamic ap is configured on the dev tree.
* If it is configured the devfs path will be stored in pathpp.
* When the dynamic comp is null this function check to see if the transport
* node has any child.
*
* Retrun value: FPCFGA_OK if the apid is configured.
* FPCFGA_APID_NOCONFIGURE if the apid is not configured.
* FPCFGA_LIB_ERR for other errors.
*/
static fpcfga_ret_t
is_apid_configured(
const char *xport_phys,
const char *dyncomp,
struct luninfo_list **lunlistpp,
int *l_errnop)
{
char *devfs_phys, *devfs_fp_path, *client_path, *cp,
*pathp = NULL;
char path_name[MAXPATHLEN];
di_node_t tree_root, root, fpnode, dev_node, client_node;
di_path_t path = DI_PATH_NIL;
di_prop_t prop = DI_PROP_NIL;
uchar_t *port_wwn_data = NULL;
char *lun_guid = NULL;
char port_wwn[WWN_SIZE*2+1];
int count, *lunnump, devlen,
found_fp = 0;
uint_t state;
uint_t statep;
fpcfga_ret_t ret;
if (*lunlistpp != NULL) {
return (FPCFGA_LIB_ERR);
}
ret = FPCFGA_APID_NOCONFIGURE;
if ((devfs_phys = strdup(xport_phys)) == NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
}
if (strncmp(devfs_phys, DEVICES_DIR SLASH, strlen(DEVICES_DIR) +
strlen(SLASH)) == 0) {
cp = devfs_phys + strlen(DEVICES_DIR);
(void) memmove(devfs_phys, cp, strlen(cp) + 1);
}
if ((cp = strstr(devfs_phys, MINOR_SEP)) != NULL) {
*cp = '\0'; /* Terminate string. */
}
if ((tree_root = di_init("/", DINFOCPYALL | DINFOPATH))
== DI_NODE_NIL) {
*l_errnop = errno;
S_FREE(devfs_phys);
return (FPCFGA_LIB_ERR);
}
fpnode = di_drv_first_node("fp", tree_root);
while (fpnode) {
devfs_fp_path = di_devfs_path(fpnode);
if ((devfs_fp_path) && !(strncmp(devfs_fp_path,
devfs_phys, strlen(devfs_phys)))) {
found_fp = 1;
di_devfs_path_free(devfs_fp_path);
break;
}
di_devfs_path_free(devfs_fp_path);
fpnode = di_drv_next_node(fpnode);
}
if (!(found_fp)) {
ret = FPCFGA_LIB_ERR;
goto out;
} else {
root = fpnode;
}
/*
* when there is no child and path info node the
* FPCFGA_APID_NOCONFIGURE is returned
* regardless of the dynamic comp.
*/
dev_node = di_child_node(root);
path = di_path_next_client(root, path);
if ((dev_node == DI_NODE_NIL) && (path == DI_PATH_NIL)) {
*l_errnop = errno;
ret = FPCFGA_APID_NOCONFIGURE;
goto out;
}
/*
* when dyn comp is null the function just checks if there is any
* child under fp transport attachment point.
*/
if (dyncomp == NULL) {
ret = FPCFGA_OK;
goto out;
}
/*
* now checks the children node to find
* if dynamic ap is configured. if there are multiple luns
* store into lunlist.
*/
if (dev_node != DI_NODE_NIL) {
do {
while ((prop = di_prop_next(dev_node, prop)) !=
DI_PROP_NIL) {
/* is property name port-wwn */
if ((!(strcmp(PORT_WWN_PROP,
di_prop_name(prop)))) &&
(di_prop_type(prop) ==
DI_PROP_TYPE_BYTE)) {
break;
}
}
if (prop != DI_PROP_NIL) {
count = di_prop_bytes(prop, &port_wwn_data);
if (count != WWN_SIZE) {
ret = FPCFGA_LIB_ERR;
goto out;
} else {
(void) sprintf(port_wwn,
"%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x",
port_wwn_data[0], port_wwn_data[1],
port_wwn_data[2], port_wwn_data[3],
port_wwn_data[4], port_wwn_data[5],
port_wwn_data[6], port_wwn_data[7]);
if (!(strncmp(port_wwn, dyncomp,
WWN_SIZE*2))) {
ret = insert_lun_to_lunlist(
lunlistpp, dyncomp,
dev_node, l_errnop);
if (ret != FPCFGA_OK) {
goto out;
}
}
}
}
dev_node = di_sibling_node(dev_node);
prop = DI_PROP_NIL;
} while (dev_node != DI_NODE_NIL);
}
/*
* now checks the path info node to find
* if dynamic ap is configured. if there are multiple luns
* store into lunlist.
*/
if (path != DI_PATH_NIL) {
/*
* now parse the path info node.
*/
do {
count = di_path_prop_lookup_bytes(path, PORT_WWN_PROP,
&port_wwn_data);
if (count != WWN_SIZE) {
ret = FPCFGA_LIB_ERR;
goto out;
}
(void) sprintf(port_wwn,
"%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x%1.2x",
port_wwn_data[0], port_wwn_data[1],
port_wwn_data[2], port_wwn_data[3],
port_wwn_data[4], port_wwn_data[5],
port_wwn_data[6], port_wwn_data[7]);
/* if matches get the path of scsi_vhci child node. */
if (!(strncmp(port_wwn, dyncomp, WWN_SIZE*2))) {
client_node = di_path_client_node(path);
if (client_node == DI_NODE_NIL) {
ret = FPCFGA_LIB_ERR;
*l_errnop = errno;
goto out;
}
count = di_path_prop_lookup_ints(path,
LUN_PROP, &lunnump);
client_path = di_devfs_path(client_node);
strcpy(path_name, client_path);
di_devfs_path_free(client_path);
state = di_state(client_node);
statep = di_path_state(path);
/*
* If the node is
* state then check the devfs_path to
* see if it has a complete path.
* For non scsi_vhci node the path
* doesn't contain @w(portwwn) part
* consistently. For scsi_vhci
* this behavior may not be there.
* To be safe @g(guid) is attempted
* to be added here.
*/
if ((state & DI_DRIVER_DETACHED) &&
(strstr(path_name, "@g") == NULL)) {
prop = DI_PROP_NIL;
while ((prop = di_prop_next(client_node,
prop)) != DI_PROP_NIL) {
/* is property name lun-wwn */
if ((!(strcmp(LUN_GUID_PROP,
di_prop_name(prop)))) &&
(di_prop_type(prop) ==
DI_PROP_TYPE_STRING)) {
break;
}
}
if (prop != DI_PROP_NIL) {
count = di_prop_strings(
prop, &lun_guid);
sprintf(&path_name[
strlen(path_name)],
"@g%s", lun_guid);
} else {
ret = FPCFGA_LIB_ERR;
goto out;
}
}
devlen = strlen(DEVICES_DIR) +
strlen(path_name) + 1;
if ((pathp = calloc(1, devlen))
== NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
} else {
(void) snprintf(pathp, devlen,
"%s%s", DEVICES_DIR, path_name);
}
if ((ret = (update_lunlist(lunlistpp, *lunnump,
statep, pathp, l_errnop))) !=
FPCFGA_OK) {
S_FREE(pathp);
goto out;
}
}
path = di_path_next_client(root, path);
} while (path != DI_PATH_NIL);
}
out:
di_fini(tree_root);
S_FREE(devfs_phys);
return (ret);
}
static fpcfga_ret_t
insert_lun_to_lunlist(
struct luninfo_list **lunlistpp,
const char *dyncomp,
di_node_t dev_node,
int *l_errnop)
{
char path_name[MAXPATHLEN];
char *pathp, *dev_phys;
di_prop_t prop_lun = DI_PROP_NIL;
uint_t state;
int count, devlen;
int *lunp;
while ((prop_lun = di_prop_next(dev_node, prop_lun)) != DI_PROP_NIL) {
if (!(strcmp(LUN_PROP, di_prop_name(prop_lun))) &&
(di_prop_type(prop_lun) == DI_PROP_TYPE_INT)) {
count = di_prop_ints(prop_lun, &lunp);
if (count <= 0) {
return (FPCFGA_LIB_ERR);
}
break;
}
}
if (prop_lun == DI_PROP_NIL) {
return (FPCFGA_LIB_ERR);
}
/*
* stores state info in state.
* This information is used to get the
* validity of path.
* if driver_detached don't try to get
* the devfs_path since it is not
* complete. ex, /pci@1f,2000/pci@1/
* SUNW,qlc@5/fp@0,0/ssd
* which doesn't contain the port wwn
* part. The attached node looks like
* /pci@1f,2000/pci@1/SUNW,qlc@5/fp@0,0/
* ssd@w2100002037006b14,0
*/
state = di_state(dev_node);
dev_phys = di_devfs_path(dev_node);
if (dev_phys == NULL) {
return (FPCFGA_LIB_ERR);
}
strcpy(path_name, dev_phys);
di_devfs_path_free(dev_phys);
if ((state & DI_DRIVER_DETACHED) &&
(strstr(path_name, "@w") == NULL)) {
sprintf(&path_name[strlen(path_name)], "@w%s,%x", dyncomp,
*lunp);
}
devlen = strlen(DEVICES_DIR) + strlen(path_name) + 1;
if ((pathp = calloc(1, devlen))
== NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
} else {
(void) snprintf(pathp, devlen, "%s%s", DEVICES_DIR, path_name);
}
return (update_lunlist(lunlistpp, *lunp, state, pathp, l_errnop));
}
static fpcfga_ret_t
update_lunlist(
struct luninfo_list **lunlistpp,
int lun,
uint_t state,
char *pathp,
int *l_errnop)
{
struct luninfo_list *newlun, *curlun, *prevlun;
newlun = curlun = prevlun = (struct luninfo_list *)NULL;
newlun = calloc(1, sizeof (struct luninfo_list));
if (newlun == NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
}
newlun->lunnum = lun;
newlun->node_state = state;
newlun->path = pathp;
newlun->next = (struct luninfo_list *)NULL;
/* if lunlist is empty add the new lun info and return. */
if (*lunlistpp == NULL) {
*lunlistpp = newlun;
return (FPCFGA_OK);
}
/* if the first lun in the list is the same as the new lun return. */
if ((*lunlistpp)->lunnum == lun) {
S_FREE(newlun);
return (FPCFGA_OK);
}
/*
* if the first lun in the list is less than the new lun add the
* new lun as the first lun and return.
*/
if ((*lunlistpp)->lunnum < lun) {
newlun->next = *lunlistpp;
*lunlistpp = newlun;
return (FPCFGA_OK);
}
/*
* if the first lun in the list is greater than the new lun and
* there is a single lun add new lun after the first lun and return.
*/
if ((*lunlistpp)->next == NULL) {
(*lunlistpp)->next = newlun;
return (FPCFGA_OK);
}
/*
* now there is more than two luns in the list and the first lun
* is greter than the input lun.
*/
curlun = (*lunlistpp)->next;
prevlun = *lunlistpp;
while (curlun != NULL) {
if (curlun->lunnum == lun) {
S_FREE(newlun);
return (FPCFGA_OK);
} else if (curlun->lunnum < lun) {
newlun->next = curlun;
prevlun->next = newlun;
return (FPCFGA_OK);
} else {
prevlun = curlun;
curlun = curlun->next;
}
}
/* add the new lun at the end of list. */
prevlun->next = newlun;
return (FPCFGA_OK);
}
fpcfga_ret_t
make_dyncomp_from_dinode(
const di_node_t node,
char **dyncompp,
int *l_errnop)
{
di_prop_t prop = DI_PROP_NIL;
uchar_t *port_wwn_data;
int count;
*l_errnop = 0;
*dyncompp = calloc(1, WWN_SIZE*2 + 1);
if (*dyncompp == NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
}
/* now get port-wwn for the input node. */
while ((prop = di_prop_next(node, prop)) != DI_PROP_NIL) {
if (!(strcmp(PORT_WWN_PROP, di_prop_name(prop))) &&
(di_prop_type(prop) == DI_PROP_TYPE_BYTE)) {
break;
}
}
if (prop != DI_PROP_NIL) {
count = di_prop_bytes(prop, &port_wwn_data);
if (count != WWN_SIZE) {
S_FREE(*dyncompp);
return (FPCFGA_LIB_ERR);
}
(void) sprintf(*dyncompp, "%016llx",
(wwnConversion(port_wwn_data)));
} else {
*l_errnop = errno;
S_FREE(*dyncompp);
return (FPCFGA_LIB_ERR);
}
return (FPCFGA_OK);
}
fpcfga_ret_t
make_portwwn_luncomp_from_dinode(
const di_node_t node,
char **dyncompp,
int **luncompp,
int *l_errnop)
{
uchar_t *port_wwn_data;
int pwwn_ret, lun_ret;
*l_errnop = 0;
if ((dyncompp != NULL) &&
((pwwn_ret = di_prop_lookup_bytes(DDI_DEV_T_ANY,
node, PORT_WWN_PROP, &port_wwn_data)) <= 0)) {
*l_errnop = errno;
}
if ((luncompp != NULL) &&
((lun_ret = di_prop_lookup_ints(DDI_DEV_T_ANY,
node, LUN_PROP, luncompp)) <= 0)) {
*l_errnop = errno;
}
/*
* di_prop* returns the number of entries found or 0 if not found
* or -1 for othere failure.
*/
if ((pwwn_ret <= 0) || (lun_ret <= 0)) {
return (FPCFGA_LIB_ERR);
}
*dyncompp = calloc(1, WWN_SIZE*2+1);
if (*dyncompp == NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
}
(void) sprintf(*dyncompp, "%016llx", (wwnConversion(port_wwn_data)));
return (FPCFGA_OK);
}
fpcfga_ret_t
make_portwwn_luncomp_from_pinode(
const di_path_t pinode,
char **dyncompp,
int **luncompp,
int *l_errnop)
{
uchar_t *port_wwn_data;
int pwwn_ret, lun_ret;
*l_errnop = 0;
if ((dyncompp != NULL) &&
((pwwn_ret = di_path_prop_lookup_bytes(pinode,
PORT_WWN_PROP, &port_wwn_data)) <= 0)) {
*l_errnop = errno;
}
if ((luncompp != NULL) &&
((lun_ret = di_path_prop_lookup_ints(pinode,
LUN_PROP, luncompp)) <= 0)) {
*l_errnop = errno;
}
/*
* di_prop* returns the number of entries found or 0 if not found
* or -1 for othere failure.
*/
if ((pwwn_ret <= 0) || (lun_ret <= 0)) {
return (FPCFGA_LIB_ERR);
}
*dyncompp = calloc(1, WWN_SIZE*2+1);
if (*dyncompp == NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
}
(void) sprintf(*dyncompp, "%016llx", (wwnConversion(port_wwn_data)));
return (FPCFGA_OK);
}
fpcfga_ret_t
construct_nodepath_from_dinode(
const di_node_t node,
char **node_pathp,
int *l_errnop)
{
char *devfs_path, path_name[MAXPATHLEN], *lun_guid, *port_wwn;
uchar_t *port_wwn_data;
int is_scsi_vhci_dev, di_ret, devlen;
uint_t state;
devfs_path = di_devfs_path(node);
strcpy(path_name, devfs_path);
di_devfs_path_free(devfs_path);
state = di_state(node);
is_scsi_vhci_dev = (strstr(path_name, SCSI_VHCI_DRVR) != NULL) ? 1 : 0;
/*
* If the node is
* state then check the devfs_path to
* see if it has a complete path.
* For non scsi_vhci node the path
* doesn't contain @w(portwwn) part
* consistently. For scsi_vhci
* this behavior may not be there.
* To be safe @g(guid) is attempted
* to be added here.
*/
if (state & DI_DRIVER_DETACHED) {
if (is_scsi_vhci_dev &&
(strstr(path_name, "@g") == NULL)) {
di_ret = di_prop_lookup_strings(DDI_DEV_T_ANY, node,
LUN_GUID_PROP, &lun_guid);
if (di_ret == -1) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
} else {
sprintf(&path_name[strlen(path_name)],
"@g%s", lun_guid);
}
} else if (!is_scsi_vhci_dev &&
(strstr(path_name, "@w") == NULL)) {
di_ret = di_prop_lookup_bytes(DDI_DEV_T_ANY, node,
PORT_WWN_PROP, &port_wwn_data);
if (di_ret == -1) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
} else {
if ((port_wwn = calloc(1, WWN_SIZE*2 + 1))
== NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
}
(void) sprintf(port_wwn, "%016llx",
(wwnConversion(port_wwn_data)));
(void) sprintf(&path_name[strlen(path_name)],
"@w%s", port_wwn);
S_FREE(port_wwn);
}
}
}
devlen = strlen(DEVICES_DIR) + strlen(path_name) + 1;
if ((*node_pathp = calloc(1, devlen)) == NULL) {
*l_errnop = errno;
return (FPCFGA_LIB_ERR);
} else {
(void) snprintf(*node_pathp, devlen,
"%s%s", DEVICES_DIR, path_name);
}
return (FPCFGA_OK);
}
u_longlong_t
wwnConversion(uchar_t *wwn)
{
u_longlong_t tmp;
memcpy(&tmp, wwn, sizeof (u_longlong_t));
return (ntohll(tmp));
}