/*
* 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
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include "cfga_scsi.h"
struct larg {
int ndevs;
int nelem;
char *dev;
char **dev_list;
};
/* Function prototypes */
static int critical_ctrlr(const char *hba_phys);
static char *get_node_path(char *minor_path);
static void free_dev_list_elements(char **dev_list);
static void free_dev_list(char **dev_list);
/*
* Single thread all implicit quiesce operations
*/
/*ARGSUSED*/
struct cfga_confirm *confp,
char **errstring)
{
/*
* No dynamic components allowed
*/
return (SCFGA_ERR);
}
/* Get bus state */
return (SCFGA_ERR);
}
switch (state_change_cmd) {
case CFGA_CMD_DISCONNECT: /* quiesce bus */
/*
* If force flag not specified, check if controller is
* critical.
*/
if (!force) {
/*
* This check is not foolproof, get user confirmation
* if test passes.
*/
break;
} else if (!disconnect(confp)) {
ret = SCFGA_NACK;
break;
}
}
goto common;
case CFGA_CMD_CONNECT: /* unquiesce bus */
goto common;
case CFGA_CMD_CONFIGURE:
goto common;
case CFGA_CMD_UNCONFIGURE:
/* FALLTHROUGH */
: ERR_BUS_CONNECTED, 0);
break;
}
/*
* When quiescing or unconfiguring a bus, first suspend or
* offline it through RCM.
* For unquiescing, we simple build the dev_list for
* resume notification.
*/
((cmd == SCFGA_BUS_QUIESCE) ||
(cmd == SCFGA_BUS_UNQUIESCE) ||
(cmd == SCFGA_BUS_UNCONFIGURE))) {
break;
}
if (cmd == SCFGA_BUS_QUIESCE) {
break;
}
} else if (cmd == SCFGA_BUS_UNCONFIGURE) {
break;
}
}
}
/*
* EIO when child devices are busy may confuse user.
* So explain it.
*/
/*
* If the bus was suspended in RCM, then cancel the RCM
* operation. Discard RCM failures here because the
* devctl's failure is what is most relevant.
*/
if (cmd == SCFGA_BUS_QUIESCE)
(void) scsi_rcm_resume(dev_list,
else if (cmd == SCFGA_BUS_UNCONFIGURE) {
&l_errno);
(void) scsi_rcm_online(dev_list,
(flags & (~CFGA_FLAG_FORCE)));
}
}
break;
}
/*
* When unquiescing or configuring a bus, resume or online it
* in RCM when the devctl command is complete.
* When unconfiguring a bus, notify removal of devices.
*/
if (cmd == SCFGA_BUS_UNQUIESCE) {
} else if (cmd == SCFGA_BUS_UNCONFIGURE) {
(flags & (~CFGA_FLAG_FORCE)));
}
}
break;
case CFGA_CMD_LOAD:
case CFGA_CMD_UNLOAD:
break;
default:
break;
}
return (ret);
}
char **errstring)
{
int l_errno = 0;
/*
* For a device, dynamic component must be present
*/
return (SCFGA_ERR);
}
/* Get bus state */
return (SCFGA_ERR);
}
switch (state_change_cmd) {
case CFGA_CMD_CONFIGURE: /* online device */
goto common;
case CFGA_CMD_UNCONFIGURE: /* offline device */
/* FALLTHROUGH */
if (bus_state != CFGA_STAT_CONNECTED) {
break;
}
} else {
/*
* When unconfiguring a device, first offline it
* through RCM.
*/
if (cmd == SCFGA_DEV_UNCONFIGURE) {
dev_list[0] =
break;
}
break;
}
}
}
/*
* If an unconfigure fails, cancel the RCM offline.
* Discard any RCM failures so that the devctl
* failure will still be reported.
*/
if (cmd == SCFGA_DEV_UNCONFIGURE)
(void) scsi_rcm_online(dev_list,
}
break;
}
/*
* Unconfigure succeeded, call the RCM notify_remove.
*/
if (cmd == SCFGA_DEV_UNCONFIGURE)
(void) scsi_rcm_remove(dev_list,
}
}
break;
/*
* Cannot disconnect/connect individual devices without affecting
* other devices on the bus. So we don't support these ops.
*/
case CFGA_CMD_DISCONNECT:
case CFGA_CMD_CONNECT:
break;
case CFGA_CMD_LOAD:
case CFGA_CMD_UNLOAD:
break;
default:
break;
}
return (ret);
}
/*ARGSUSED*/
const char *func,
char **errstring)
{
int do_quiesce;
/* device operation only */
return (SCFGA_ERR);
}
proceed = 1;
&l_errno);
return (ret);
}
if (!proceed) {
return (SCFGA_NACK);
}
/*
* Offline the device in RCM
*/
return (SCFGA_ERR);
!= SCFGA_OK) {
return (ret);
}
}
/*
* Offline the device
*/
/*
* Cancel the RCM offline. Discard the RCM failures so that
* the above devctl failure is still reported.
*/
return (ret);
}
/* Do the physical removal */
/*
* Complete the remove.
* Since the device is already offlined, remove shouldn't
* fail. Even if remove fails, there is no side effect.
*/
} else {
/*
* Reconfigure the device and restore the device's RCM state.
* If reconfigure succeeds, restore the state to online.
* If reconfigure fails (e.g. a typo from user), we treat
* the device as removed.
*/
== SCFGA_OK) {
flags);
} else {
if (cp)
*cp = '\0';
if (cp)
*cp = ':';
flags);
}
}
return (ret);
}
/*ARGSUSED*/
const char *func,
char **errstring)
{
int do_quiesce;
/* Currently, insert operation only allowed for bus */
return (SCFGA_ERR);
}
proceed = 1;
&l_errno);
return (ret);
}
if (!proceed) {
return (SCFGA_NACK);
}
/* Do the physical addition */
return (ret);
}
/*
* Configure bus to online new device(s).
* Previously offlined devices will not be onlined.
*/
return (SCFGA_ERR);
}
return (SCFGA_OK);
}
/*ARGSUSED*/
const char *func,
char **errstring)
{
int do_quiesce;
/* device operation only */
return (SCFGA_ERR);
}
proceed = 1;
&l_errno);
return (ret);
}
if (!proceed) {
return (SCFGA_NACK);
}
/* Offline the device in RCM */
return (SCFGA_ERR);
!= SCFGA_OK) {
return (ret);
}
}
/*
* Cancel the RCM offline. Discard any RCM failures so that
* the devctl failure can still be reported.
*/
return (ret);
}
/* do the physical replace */
/* Online the replacement, or restore state on error */
}
/*
* Remove the replaced device in RCM, or online the device in RCM
* to recover.
*/
}
}
/*ARGSUSED*/
const char *func,
char **errstring)
{
/*
* The implementation of the led command is platform-specific, so
* the default behavior is to say that the functionality is not
* available for this device.
*/
if (plat_dev_led) {
}
return (SCFGA_ERR);
}
/*ARGSUSED*/
const char *func,
char **errstring)
{
int l_errno = 0;
switch (cmd) {
case SCFGA_RESET_DEV:
return (SCFGA_ERR);
}
break;
case SCFGA_RESET_BUS:
case SCFGA_RESET_ALL:
return (SCFGA_ERR);
}
break;
default:
return (SCFGA_ERR);
}
}
return (ret);
}
static int
{
char *cq;
append_newline = 0;
return (ans == 1);
}
/*
* Check for "scsi-no-quiesce" property
* Return code: -1 error, 0 quiesce not required, 1 quiesce required
*/
static int
{
int *propval;
/* take libdevinfo snapshot of subtree at hba */
if (bus_end)
*bus_end = '\0';
if (bus_end)
*bus_end = ':';
if (bus_node == DI_NODE_NIL) {
return (-1); /* error */
}
/* check bus node for property */
&propval) == 1) {
return (0); /* quiesce not required */
}
/* if this ap is HBA, return with quiesce required */
return (1);
}
/* check device node for property */
if (dev_end)
*dev_end = '\0';
while (dev_node != DI_NODE_NIL) {
char *child_path;
break;
}
}
if (dev_end)
*dev_end = ':';
if (dev_node == DI_NODE_NIL) {
return (1); /* dev not found (insert case) */
}
/* check child node for property */
&propval) == 1) {
return (0); /* quiesce not required */
}
return (1); /* quiesce required */
}
static scfga_ret_t
int *okp,
int *quiesce,
int *l_errnop)
{
char *cp;
int i = 0, append_newline;
if (*quiesce == -1)
return (SCFGA_ERR);
else if (*quiesce == 0)
return (SCFGA_OK);
/*
* Try to create HBA logical ap_id.
* If that fails use physical path
*/
return (SCFGA_LIB_ERR);
}
}
append_newline = 0;
ret = SCFGA_LIB_ERR;
goto out;
}
/* Remove minor name (if any) from phys path */
*cp = '\0';
}
/* describe operation being attempted */
/* Restore minor name */
*cp = ':';
}
/* request permission to quiesce */
/*FALLTHRU*/
out:
return (ret);
}
static scfga_ret_t
char ***suspend_list_ptr,
char **errstring,
{
*suspend_list_ptr = NULL;
/* Suspend the bus through RCM */
return (SCFGA_OK);
/* The bus_path is the HBA path without its minor */
return (SCFGA_ERR);
/*
* The dev_path is already initialized to NULL. If the AP Id
* path differs from the HBA path, then the dev_path should
* instead be set to the AP Id path without its minor.
*/
goto out;
}
}
!= SCFGA_OK) {
goto out;
}
} else {
}
/*FALLTHROUGH*/
out:
return (ret);
}
/*
* Resume the bus through RCM if it successfully
* unquiesced.
*/
static void
char **suspend_list,
char **errstring,
{
return;
}
static scfga_ret_t
{
int append_newline = 0;
ret = SCFGA_NACK;
} else {
}
return (ret);
}
static scfga_ret_t
{
int l_errno;
return (ret);
}
/*
* If the quiesce fails, then cancel the RCM suspend.
* Discard any RCM failures so that the devctl failure
* can still be reported.
*/
l_errno = 0;
return (ret);
}
/*
* Prompt user to proceed with physical hotplug
* and wait until they are done.
*/
/*
* The unquiesce may fail with EALREADY (which is ok)
* or some other error (which is not ok).
*/
l_errno = 0;
return (SCFGA_ERR);
}
return (hpret);
}
static void
{
int count = 0;
return;
}
if (++count < MAX_UNLINK_TRIES) {
(void) sleep(1);
goto retry;
}
} else {
}
}
static scfga_ret_t
{
int count;
int mnted;
*fdp = -1;
/*
* that the lock file is left behind, we want it
* cleared on the next reboot.
*/
errno = 0;
return (SCFGA_LIB_ERR);
}
mnted = 0;
mnted = 1;
break;
}
}
if (!mnted) {
return (SCFGA_LIB_ERR);
}
/*
* Wait for a short period of time if we cannot O_EXCL create
* lock file. If some other cfgadm process is finishing up, we
* can get in. If the wait required is long however, just
* return SYSTEM_BUSY to the user - a hotplug operation is
* probably in progress.
*/
count = 0;
if (++count < MAX_LOCK_TRIES) {
if (count == 1)
(void) sleep(1);
goto retry;
}
}
return (SCFGA_SYSTEM_BUSY);
} else if (*fdp == -1) {
return (SCFGA_LIB_ERR);
}
s_getpid(), SCFGA_LOCK));
return (SCFGA_OK);
}
static scfga_ret_t
{
int count;
int rval;
s_getpid(), SCFGA_LOCK));
count = 0;
if (++count >= MAX_LOCK_TRIES) {
s_getpid(), SCFGA_LOCK));
goto badlock;
}
(void) sleep(1);
}
if (rval != -1) {
s_getpid(), SCFGA_LOCK));
return (SCFGA_OK);
}
/*FALLTHROUGH*/
/* trace message to display pid */
s_getpid(), SCFGA_LOCK));
return (SCFGA_LIB_ERR);
}
static void
{
int status;
for (;;) {
return;
}
}
return;
}
if (WIFSIGNALED(status)) {
return;
}
/*
* The child has not terminated. We received status
* because the child was either stopped or continued.
* Wait for child termination by calling waitpid() again.
*/
}
}
static void
{
int l_errno;
/* This is the child */
" - _exit(1)\n", s_getpid()));
/*
* As a last resort, unlink the lock file. This is relatively
* safe as the child doesn't unquiesce the bus in this case.
*/
_exit(1);
}
l_errno = 0;
else
} else {
}
_exit(0);
}
static void
{
(void) sigemptyset(&set);
(void) sigemptyset(osp);
}
static void
{
}
/*
* Here is the algorithm used to ensure that a SCSI bus is not
* left in the quiesced state:
*
* lock quiesce mutex // single threads this code
* open(O_CREAT|O_EXCL) lock file // only 1 process at a time
* exclusive record lock on lock file
* fork1()
* quiesce bus
* do the physical hotplug operation
* unquiesce bus
* unlock record lock
* -> *child*
* -> wait for record lock
* -> unconditionally unquiesce bus
* -> unlink lock file
* -> exit
* wait for child to exit
* unlock quiesce mutex
*
* NOTE1: since record locks are per-process and a close() can
* release a lock, to keep things MT-safe we need a quiesce mutex.
*
* NOTE2: To ensure that the child does not unquiesce a bus quiesced
* by an unrelated cfgadm_scsi operation, exactly 1 process in the
* system can be doing an implicit quiesce operation The exclusive
* creation of the lock file guarantees this.
*
* abnormally terminated. If the parent dies before the child is
* forked, the bus is not quiesced. If the parent dies after the
* bus is quiesced, the child process will ensure that the bus is
* unquiesced.
*/
static scfga_ret_t
int do_quiesce,
char **errstring)
{
int fd;
/* If no quiesce required, prompt the user to do the operation */
if (!do_quiesce)
(void) mutex_lock(&quiesce_mutex);
(void) mutex_unlock(&quiesce_mutex);
return (ret);
}
goto bad;
}
/*
* block signals in the child. Parent may
* exit, causing signal to be sent to child.
*/
case 0:
/* child */
_exit(0); /* paranoia */
/*NOTREACHED*/
case -1:
ret = SCFGA_LIB_ERR;
goto bad;
default:
/* parent */
break;
}
/* We have forked successfully - this is the parent */
(void) mutex_unlock(&quiesce_mutex);
return (ret);
bad:
(void) mutex_unlock(&quiesce_mutex);
return (ret);
}
/*
* Checks if HBA controls a critical file-system (/, /usr or swap)
* If an error occurs, assumes that controller is NOT critical.
*/
static int
{
char *bufp;
return (0);
}
rv = 0;
goto out;
}
/* Ignore non-critical entries */
continue;
}
/* get physical path */
continue;
}
/* Check if critical partition is on the HBA */
break;
}
}
/*FALLTHRU*/
out:
}
return (rv);
}
/*
* Convert bus state to receptacle state
*/
static cfga_stat_t
{
switch (bus_dc_state) {
case BUS_ACTIVE:
break;
case BUS_QUIESCED:
case BUS_SHUTDOWN:
break;
default:
rs = CFGA_STAT_NONE;
break;
}
return (rs);
}
static int
{
char *path, *p;
/* ignore hba itself and all detached nodes */
return (DI_WALK_CONTINUE);
return (DI_WALK_TERMINATE);
}
/* sizeof (DEVICES_DIR) includes the null terminator */
return (DI_WALK_TERMINATE);
}
/* ignore device to be excluded */
free(p);
return (DI_WALK_CONTINUE);
}
/* grow dev_list to allow room for one more device */
if (alloc_dev_list(largp) != 0) {
free(p);
return (DI_WALK_TERMINATE);
}
return (DI_WALK_CONTINUE);
}
/*
* Get list of children excluding dev_excl (if not null).
*/
static int
{
walkarg_t u;
&err);
return (SCFGA_ERR);
}
return (ret);
}
static char *
{
return (NULL);
*cp = '\0';
return (path);
}
/*
* Ensure largp->dev_list has room for one more device.
* Returns 0 on success, -1 on failure.
*/
static int
{
int nelem;
char **p;
return (0);
if (p == NULL)
return (-1);
return (0);
}
static void
{
while (*dev_list) {
dev_list++;
}
}
static void
{
return;
}