/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* ses (SCSI Generic Device) specific functions.
*/
#include <libnvpair.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/queue.h>
#include <fcntl.h>
#include <string.h>
#include <scsi/libscsi.h>
#include <scsi/libses.h>
#include <libintl.h> /* for gettext(3c) */
#include <fwflash/fwflash.h>
#define VIDLEN 0x08
#define PIDLEN 0x10
#define REVLEN 0x04
#define SASADDRLEN 0x10
#define PCBUFLEN 0x40
#define RQBUFLEN 0xfe
#define STATBUFLEN 0xfe
#define INQBUFLEN 0x80
/* useful defines */
#define UCODE_CHECK_STATUS 0
#define UCODE_CHECK_SUPPORTED 1
typedef struct ucode_statdesc {
uint64_t us_value;
const char *us_desc;
boolean_t us_pending;
boolean_t us_iserr;
} ucode_statdesc_t;
static ucode_statdesc_t ucode_statdesc_table[] = {
{ SES2_DLUCODE_S_NOP, "none", B_FALSE, B_FALSE },
{ SES2_DLUCODE_S_INPROGRESS, "in progress", B_TRUE, B_FALSE },
{ SES2_DLUCODE_S_SAVING, "saved", B_TRUE, B_FALSE },
{ SES2_DLUCODE_S_COMPLETE_NOW, "completed (available)", B_FALSE,
B_FALSE },
{ SES2_DLUCODE_S_COMPLETE_AT_RESET,
"completed (need reset or power on)", B_FALSE, B_FALSE },
{ SES2_DLUCODE_S_COMPLETE_AT_POWERON, "completed (need power on)",
B_FALSE, B_FALSE },
{ SES2_DLUCODE_S_PAGE_ERR, "page error (offset %d)",
B_FALSE, B_TRUE },
{ SES2_DLUCODE_S_IMAGE_ERR, "invalid image",
B_FALSE, B_TRUE },
{ SES2_DLUCODE_S_TIMEOUT, "download timeout",
B_FALSE, B_TRUE },
{ SES2_DLUCODE_S_INTERNAL_NEEDIMAGE,
"internal error (NEED NEW IMAGE BEFORE RESET)",
B_FALSE, B_TRUE },
{ SES2_DLUCODE_S_INTERNAL_SAFE,
"internal error (reset to revert to backup)",
B_FALSE, B_TRUE },
};
#define NUCODE_STATUS \
(sizeof (ucode_statdesc_table) / sizeof (ucode_statdesc_table[0]))
typedef struct ucode_status {
uint64_t us_status;
boolean_t us_iserr;
boolean_t us_pending;
char us_desc[128];
} ucode_status_t;
typedef struct ucode_wait {
uint64_t uw_prevstatus;
boolean_t uw_pending;
ses_node_t *uw_oldnp;
} ucode_wait_t;
typedef struct sam4_statdesc {
int status;
char *message;
} sam4_statdesc_t;
static sam4_statdesc_t sam4_status[] = {
{ SAM4_STATUS_GOOD, "Status: GOOD (success)" },
{ SAM4_STATUS_CHECK_CONDITION, "Status: CHECK CONDITION" },
{ SAM4_STATUS_CONDITION_MET, "Status: CONDITION MET" },
{ SAM4_STATUS_BUSY, "Status: Device is BUSY" },
{ SAM4_STATUS_RESERVATION_CONFLICT, "Status: Device is RESERVED" },
{ SAM4_STATUS_TASK_SET_FULL,
"Status: TASK SET FULL (insufficient resources in command queue" },
{ SAM4_STATUS_TASK_ABORTED, "Status: TASK ABORTED" },
{ NULL, NULL }
};
#define NSAM4_STATUS \
(sizeof (sam4_status) / sizeof (sam4_status[0]))
char drivername[] = "ses\0";
static char *devprefix = "/devices";
static char *sessuffix = ":0";
static char *sgensuffix = ":ses";
static ses_target_t *ses_target;
extern di_node_t rootnode;
extern int errno;
extern struct fw_plugin *self;
extern struct vrfyplugin *verifier;
extern int fwflash_debug;
/* required functions for this plugin */
int fw_readfw(struct devicelist *device, char *filename);
int fw_writefw(struct devicelist *device);
int fw_identify(int start);
int fw_devinfo(struct devicelist *thisdev);
/* helper functions */
static int print_updated_status(ses_node_t *np, void *arg);
static int get_status(nvlist_t *props, ucode_status_t *sp);
static int sendimg(ses_node_t *np, void *data);
static int scsi_writebuf();
/*
* We don't currently support reading firmware from a SAS
* expander. If we do eventually support it, we would use
* the scsi READ BUFFER command to do so.
*/
int
fw_readfw(struct devicelist *flashdev, char *filename)
{
logmsg(MSG_INFO,
"%s: not writing firmware for device %s to file %s\n",
flashdev->drvname, flashdev->access_devname, filename);
logmsg(MSG_ERROR,
gettext("\n\nReading of firmware images from %s-attached "
"devices is not supported\n\n"),
flashdev->drvname);
return (FWFLASH_SUCCESS);
}
/*
* If we're invoking fw_writefw, then flashdev is a valid,
* flashable device supporting the SES2 Download Microcode Diagnostic
* Control page (0x0e).
*
* If verifier is null, then we haven't been called following a firmware
* image verification load operation.
*
* *THIS* function uses scsi SEND DIAGNOSTIC/download microcode to
* achieve the task... if you chase down to the bottom of libses you
* can see that too.
*/
int
fw_writefw(struct devicelist *flashdev)
{
int rv = FWFLASH_FAILURE;
nvlist_t *nvl;
ses_snap_t *snapshot;
ses_node_t *targetnode;
if ((verifier == NULL) || (verifier->imgsize == 0) ||
(verifier->fwimage == NULL)) {
/* should _not_ happen */
logmsg(MSG_ERROR,
gettext("%s: Firmware image has not "
"been verified.\n"),
flashdev->drvname);
return (FWFLASH_FAILURE);
}
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
nvlist_add_uint64(nvl, SES_CTL_PROP_UCODE_MODE,
SES_DLUCODE_M_WITH_OFFS) != 0) {
logmsg(MSG_ERROR,
gettext("%s: Unable to allocate "
"space for device prop list\n"),
flashdev->drvname);
return (FWFLASH_FAILURE);
}
fprintf(stdout, "\n"); /* get a fresh line for progress updates */
if (nvlist_add_uint64(nvl, SES_CTL_PROP_UCODE_BUFID,
verifier->flashbuf) != 0) {
logmsg(MSG_ERROR,
gettext("%s: Unable to add buffer id "
"property, hence unable to flash device\n"),
flashdev->drvname);
goto cancel;
}
if (nvlist_add_byte_array(nvl, SES_CTL_PROP_UCODE_DATA,
(uint8_t *)verifier->fwimage, verifier->imgsize) != 0) {
logmsg(MSG_ERROR,
"%s: Out of memory for property addition\n",
flashdev->drvname);
goto cancel;
}
if ((ses_target =
ses_open(LIBSES_VERSION, flashdev->access_devname)) == NULL) {
logmsg(MSG_ERROR,
gettext("%s: Unable to open flashable device %s\n"),
flashdev->drvname, flashdev->access_devname);
goto cancel;
}
snapshot = ses_snap_hold(ses_target);
if ((targetnode = ses_snap_primary_enclosure(snapshot)) == NULL) {
logmsg(MSG_ERROR,
gettext("%s: Unable to locate primary enclosure for "
"device %s\n"),
flashdev->access_devname);
} else {
rv = sendimg(targetnode, nvl);
if (rv == FWFLASH_SUCCESS) {
logmsg(MSG_ERROR,
gettext("%s: Done. New image will be active "
"after the system is rebooted.\n\n"),
flashdev->drvname);
} else {
logmsg(MSG_INFO,
"%s: unable to flash image %s to device %s\n\n",
flashdev->drvname, verifier->imgfile,
flashdev->access_devname);
}
}
ses_snap_rele(snapshot);
ses_close(ses_target);
cancel:
nvlist_free(nvl);
return (rv);
}
/*
* The fw_identify() function walks the device
* tree trying to find devices which this plugin
* can work with.
*
* The parameter "start" gives us the starting index number
* to give the device when we add it to the fw_devices list.
*
* firstdev is allocated by us and we add space as needed
*/
int
fw_identify(int start)
{
int rv = FWFLASH_FAILURE;
di_node_t thisnode;
struct devicelist *newdev;
char *devpath;
char *devsuffix;
char *driver;
int idx = start;
size_t devlength = 0;
nvlist_t *props;
ses_snap_t *snapshot;
ses_node_t *rootnodep, *nodep;
if (strcmp(self->drvname, "sgen") == 0) {
devsuffix = sgensuffix;
driver = self->drvname;
} else {
devsuffix = sessuffix;
driver = drivername;
}
thisnode = di_drv_first_node(driver, rootnode);
if (thisnode == DI_NODE_NIL) {
logmsg(MSG_INFO, gettext("No %s nodes in this system\n"),
driver);
return (FWFLASH_FAILURE);
}
if ((devpath = calloc(1, MAXPATHLEN + 1)) == NULL) {
logmsg(MSG_ERROR,
gettext("%s: Unable to allocate space "
"for a device node\n"),
driver);
return (FWFLASH_FAILURE);
}
/* we've found one, at least */
for (; thisnode != DI_NODE_NIL; thisnode = di_drv_next_node(thisnode)) {
devpath = di_devfs_path(thisnode);
if ((newdev = calloc(1, sizeof (struct devicelist)))
== NULL) {
logmsg(MSG_ERROR,
gettext("%s: identification function unable "
"to allocate space for device entry\n"),
driver);
free(devpath);
return (FWFLASH_FAILURE);
}
/* calloc enough for /devices + devpath + devsuffix + '\0' */
devlength = strlen(devpath) + strlen(devprefix) +
strlen(devsuffix) + 2;
if ((newdev->access_devname = calloc(1, devlength)) == NULL) {
logmsg(MSG_ERROR,
gettext("%s: Unable to allocate "
"space for a devfs name\n"),
driver);
free(devpath);
free(newdev);
return (FWFLASH_FAILURE);
}
snprintf(newdev->access_devname, devlength,
"%s%s%s", devprefix, devpath, devsuffix);
if ((newdev->drvname = calloc(1, strlen(driver) + 1))
== NULL) {
logmsg(MSG_ERROR,
gettext("%s: Unable to allocate "
"space to store a driver name\n"),
driver);
free(newdev->access_devname);
free(newdev);
free(devpath);
return (FWFLASH_FAILURE);
}
(void) strlcpy(newdev->drvname, driver,
strlen(driver) + 1);
if ((newdev->classname = calloc(1, strlen(driver) + 1))
== NULL) {
logmsg(MSG_ERROR,
gettext("%s: Unable to malloc "
"space for a class name\n"),
drivername);
free(newdev->access_devname);
free(newdev->drvname);
free(newdev);
free(devpath);
return (FWFLASH_FAILURE);
}
(void) strlcpy(newdev->classname, driver,
strlen(driver) + 1);
/*
* Only alloc as much as we truly need, and DON'T forget
* that libnvpair manages the memory for property lookups!
* The same goes for libdevinfo properties.
*
* Also note that we're allocating here before we try to
* ses_open() the target, because if we can't allocate
* sufficient space then we might as well go home.
*/
newdev->ident = calloc(1, VIDLEN + PIDLEN + REVLEN + 3);
if (newdev->ident == NULL) {
logmsg(MSG_ERROR,
gettext("%s: Unable to malloc space for"
"SCSI INQUIRY data\n"), driver);
free(newdev->classname);
free(newdev->drvname);
free(newdev->access_devname);
free(newdev);
free(devpath);
return (FWFLASH_FAILURE);
}
if ((ses_target =
ses_open(LIBSES_VERSION, newdev->access_devname))
== NULL) {
logmsg(MSG_INFO,
gettext("%s: Unable to open device %s\n"),
driver, newdev->access_devname);
free(newdev->ident);
free(newdev->classname);
free(newdev->access_devname);
free(newdev->drvname);
free(newdev);
free(devpath);
continue;
}
snapshot = ses_snap_hold(ses_target);
rootnodep = ses_root_node(snapshot);
/*
* If the node has no properties, or the INQUIRY properties
* don't exist, this device does not comply with SES2 so we
* won't touch it.
*/
if ((props = ses_node_props(rootnodep)) == NULL) {
free(newdev->ident);
ses_snap_rele(snapshot);
ses_close(ses_target);
free(newdev->classname);
free(newdev->access_devname);
free(newdev->drvname);
free(newdev);
free(devpath);
continue;
}
if ((nvlist_lookup_string(props, SCSI_PROP_VENDOR,
&newdev->ident->vid) != 0) ||
(nvlist_lookup_string(props, SCSI_PROP_PRODUCT,
&newdev->ident->pid) != 0) ||
(nvlist_lookup_string(props, SCSI_PROP_REVISION,
&newdev->ident->revid) != 0)) {
free(newdev->ident);
ses_snap_rele(snapshot);
ses_close(ses_target);
free(newdev->classname);
free(newdev->access_devname);
free(newdev->drvname);
free(newdev);
free(devpath);
continue;
}
nodep = ses_snap_primary_enclosure(snapshot);
if ((props = ses_node_props(nodep)) == NULL) {
free(newdev->ident);
ses_snap_rele(snapshot);
ses_close(ses_target);
free(newdev->classname);
free(newdev->access_devname);
free(newdev->drvname);
free(newdev);
free(devpath);
continue;
}
logmsg(MSG_INFO,
"\nvid: %s\npid: %s\nrevid: %s\n",
newdev->ident->vid,
newdev->ident->pid,
newdev->ident->revid);
if (nvlist_lookup_string(props, LIBSES_EN_PROP_CSN,
&newdev->addresses[0]) == 0) {
logmsg(MSG_INFO,
"Chassis Serial Number: %s\n",
newdev->addresses[0]);
} else
logmsg(MSG_INFO,
"%s: no chassis-serial-number property "
"for device %s\n",
driver, newdev->access_devname);
rv = di_prop_lookup_strings(DDI_DEV_T_ANY,
thisnode, "target-port", &newdev->addresses[1]);
if (rv < 0) {
logmsg(MSG_INFO,
"%s: no target-port property "
"for device %s\n",
driver, newdev->access_devname);
} else
logmsg(MSG_INFO,
"target-port property: %s\n",
newdev->addresses[1]);
newdev->index = idx;
++idx;
newdev->plugin = self;
ses_snap_rele(snapshot);
TAILQ_INSERT_TAIL(fw_devices, newdev, nextdev);
}
if (fwflash_debug != 0) {
struct devicelist *tempdev;
TAILQ_FOREACH(tempdev, fw_devices, nextdev) {
logmsg(MSG_INFO, "%s:fw_identify:\n",
driver);
logmsg(MSG_INFO,
"\ttempdev @ 0x%lx\n"
"\t\taccess_devname: %s\n"
"\t\tdrvname: %s\tclassname: %s\n"
"\t\tident->vid: %s\n"
"\t\tident->pid: %s\n"
"\t\tident->revid: %s\n"
"\t\tindex: %d\n"
"\t\taddress[0]: %s\n"
"\t\taddress[1]: %s\n"
"\t\tplugin @ 0x%lx\n\n",
&tempdev,
tempdev->access_devname,
tempdev->drvname, newdev->classname,
tempdev->ident->vid,
tempdev->ident->pid,
tempdev->ident->revid,
tempdev->index,
(tempdev->addresses[0] ? tempdev->addresses[0] :
"(not supported)"),
(tempdev->addresses[1] ? tempdev->addresses[1] :
"(not supported)"),
&tempdev->plugin);
}
}
return (FWFLASH_SUCCESS);
}
int
fw_devinfo(struct devicelist *thisdev)
{
fprintf(stdout, gettext("Device[%d] %s\n Class [%s]\n"),
thisdev->index, thisdev->access_devname, thisdev->classname);
fprintf(stdout,
gettext("\tVendor : %s\n"
"\tProduct : %s\n"
"\tFirmware revision : %s\n"
"\tChassis Serial Number : %s\n"
"\tTarget-port identifier : %s\n"),
thisdev->ident->vid,
thisdev->ident->pid,
thisdev->ident->revid,
(thisdev->addresses[0] ? thisdev->addresses[0] :
"(not supported)"),
(thisdev->addresses[1] ? thisdev->addresses[1] :
"(not supported)"));
fprintf(stdout, "\n\n");
return (FWFLASH_SUCCESS);
}
/*ARGSUSED*/
static int
get_status(nvlist_t *props, ucode_status_t *sp)
{
int i;
uint64_t status, astatus;
if (nvlist_lookup_uint64(props, SES_EN_PROP_UCODE, &status) != 0) {
sp->us_status = -1ULL;
(void) snprintf(sp->us_desc, sizeof (sp->us_desc),
"not supported");
return (FWFLASH_FAILURE);
}
if (nvlist_lookup_uint64(props, SES_EN_PROP_UCODE_A,
&astatus) != 0) {
logmsg(MSG_ERROR,
gettext("\nError: Unable to retrieve current status\n"));
return (FWFLASH_FAILURE);
}
for (i = 0; i < NUCODE_STATUS; i++) {
if (ucode_statdesc_table[i].us_value == status)
break;
}
sp->us_status = status;
if (i == NUCODE_STATUS) {
(void) snprintf(sp->us_desc, sizeof (sp->us_desc),
"unknown (0x%02x)", (int)status);
sp->us_iserr = sp->us_pending = B_TRUE;
return (FWFLASH_FAILURE);
} else {
/* LINTED */
(void) snprintf(sp->us_desc, sizeof (sp->us_desc),
ucode_statdesc_table[i].us_desc, (int)astatus);
sp->us_iserr = ucode_statdesc_table[i].us_iserr;
sp->us_pending = ucode_statdesc_table[i].us_pending;
}
return (FWFLASH_SUCCESS);
}
static int
print_updated_status(ses_node_t *np, void *arg)
{
ucode_wait_t *uwp = arg;
nvlist_t *props;
ucode_status_t status;
if ((props = ses_node_props(np)) == NULL) {
return (FWFLASH_FAILURE);
}
if (get_status(props, &status) != FWFLASH_SUCCESS)
return (FWFLASH_FAILURE);
if (status.us_status != uwp->uw_prevstatus)
(void) printf("%30s: %s\n", "status", status.us_desc);
uwp->uw_prevstatus = status.us_status;
uwp->uw_pending = status.us_pending;
if (status.us_iserr) {
logmsg(MSG_INFO,
"libses: status.us_iserr: 0x%0x\n",
status.us_iserr);
return (FWFLASH_FAILURE);
}
return (FWFLASH_SUCCESS);
}
/*ARGSUSED*/
static int
sendimg(ses_node_t *np, void *data)
{
nvlist_t *props;
nvlist_t *arg = data;
char *vendor, *product, *revision, *csn;
char buf[128];
ses_snap_t *newsnap;
int ret;
ucode_status_t statdesc;
ucode_wait_t wait;
uint8_t *imagedata;
uint_t len;
/* If we've been called without data, eject */
if (nvlist_lookup_byte_array(arg, SES_CTL_PROP_UCODE_DATA,
&imagedata, &len) != 0) {
return (FWFLASH_FAILURE);
}
props = ses_node_props(np);
if ((props == NULL) ||
(nvlist_lookup_string(props, SES_EN_PROP_VID, &vendor) != 0) ||
(nvlist_lookup_string(props, SES_EN_PROP_PID, &product) != 0) ||
(nvlist_lookup_string(props, SES_EN_PROP_REV, &revision) != 0) ||
(nvlist_lookup_string(props, LIBSES_EN_PROP_CSN, &csn) != 0)) {
return (FWFLASH_FAILURE);
}
(void) printf("%30s: %s\n", "vendor", vendor);
(void) printf("%30s: %s\n", "product", product);
(void) printf("%30s: %s\n", "revision", revision);
(void) printf("%30s: %s\n", "serial", csn);
ret = get_status(props, &statdesc);
(void) printf("%30s: %s\n", "current status", statdesc.us_desc);
if (ret != FWFLASH_SUCCESS) {
return (FWFLASH_FAILURE);
}
(void) snprintf(buf, sizeof (buf), "downloading %u bytes", len);
(void) printf("\n%30s: ", buf);
/*
* If the bufferid isn't 2, then the verifier has already
* OK'd the image that the user has provided.
*
* At present the non-"standard" images need to be flashed
* using the scsi WRITE BUFFER command
*/
if (verifier->flashbuf != 2)
return (scsi_writebuf());
if (ses_node_ctl(np, SES_CTL_OP_DL_UCODE, arg) != FWFLASH_SUCCESS) {
(void) printf("failed!\n");
(void) printf("%s\n", ses_errmsg());
return (FWFLASH_FAILURE);
} else {
(void) printf("ok\n");
}
wait.uw_prevstatus = -1ULL;
wait.uw_oldnp = np;
if ((newsnap = ses_snap_new(ses_target)) == NULL) {
logmsg(MSG_ERROR,
"failed to update SES snapshot: %s",
ses_errmsg());
return (FWFLASH_FAILURE);
}
print_updated_status(ses_snap_primary_enclosure(newsnap),
&wait);
ses_snap_rele(newsnap);
return (FWFLASH_SUCCESS);
}
static int
scsi_writebuf()
{
int ret;
int i = 0;
libscsi_action_t *action;
spc3_write_buffer_cdb_t *wb_cdb;
libscsi_hdl_t *handle;
libscsi_target_t *target;
sam4_status_t samstatus;
target = ses_scsi_target(ses_target);
handle = libscsi_get_handle(target);
action = libscsi_action_alloc(handle, SPC3_CMD_WRITE_BUFFER,
LIBSCSI_AF_WRITE|LIBSCSI_AF_RQSENSE,
(void *)verifier->fwimage, (size_t)verifier->imgsize);
wb_cdb = (spc3_write_buffer_cdb_t *)libscsi_action_get_cdb(action);
wb_cdb->wbc_mode = SPC3_WB_MODE_DATA;
wb_cdb->wbc_bufferid = verifier->flashbuf;
wb_cdb->wbc_buffer_offset[0] = 0;
wb_cdb->wbc_buffer_offset[1] = 0;
wb_cdb->wbc_buffer_offset[2] = 0;
wb_cdb->wbc_parameter_list_len[0] =
(verifier->imgsize & 0xff0000) >> 16;
wb_cdb->wbc_parameter_list_len[1] = (verifier->imgsize & 0xff00) >> 8;
wb_cdb->wbc_parameter_list_len[2] = (verifier->imgsize & 0xff);
ret = libscsi_exec(action, target);
samstatus = libscsi_action_get_status(action);
logmsg(MSG_INFO,
"\nscsi_writebuffer: ret 0x%0x, samstatus 0x%0x\n",
ret, samstatus);
if ((ret != FWFLASH_SUCCESS) || samstatus != SAM4_STATUS_GOOD) {
libscsi_action_free(action);
return (FWFLASH_FAILURE);
} else {
(void) printf("ok\n");
}
for (i = 0; i < NSAM4_STATUS; i++) {
if (sam4_status[i].status == samstatus) {
(void) printf("%s\n", (sam4_status[i].message));
break;
}
}
if (i == NSAM4_STATUS)
(void) printf("Status: UNKNOWN\n");
if (samstatus == SAM4_STATUS_GOOD) {
return (FWFLASH_SUCCESS);
}
return (FWFLASH_FAILURE);
}