1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * ses (SCSI Generic Device) specific functions.
30  */
31 
32 
33 #include <assert.h>
34 #include <libnvpair.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <sys/sysmacros.h>
41 #include <sys/queue.h>
42 #include <fcntl.h>
43 #include <string.h>
44 #include <strings.h>
45 #include <scsi/libscsi.h>
46 #include <scsi/libses.h>
47 #include <sys/scsi/generic/commands.h>
48 #include <sys/scsi/impl/uscsi.h>
49 #include <libintl.h> /* for gettext(3c) */
50 #include <fwflash/fwflash.h>
51 
52 
53 
54 #ifdef NDEBUG
55 #define	verify(EX)	((void)(EX))
56 #else
57 #define	verify(EX)	assert(EX)
58 #endif
59 
60 
61 #define	VIDLEN		0x08
62 #define	PIDLEN		0x10
63 #define	REVLEN		0x04
64 #define	SASADDRLEN	0x10
65 #define	PCBUFLEN	0x40
66 #define	RQBUFLEN	0xfe
67 #define	STATBUFLEN	0xfe
68 #define	INQBUFLEN	0x80
69 
70 /* useful defines */
71 #define	UCODE_CHECK_STATUS	0
72 #define	UCODE_CHECK_SUPPORTED	1
73 
74 typedef struct ucode_statdesc {
75 	uint64_t	us_value;
76 	const char	*us_desc;
77 	boolean_t	us_pending;
78 	boolean_t	us_iserr;
79 } ucode_statdesc_t;
80 
81 static ucode_statdesc_t ucode_statdesc_table[] = {
82 	{ SES2_DLUCODE_S_NOP,		"none",	B_FALSE, B_FALSE },
83 	{ SES2_DLUCODE_S_INPROGRESS,	"in progress", B_TRUE, B_FALSE },
84 	{ SES2_DLUCODE_S_SAVING,	"saved", B_TRUE, B_FALSE },
85 	{ SES2_DLUCODE_S_COMPLETE_NOW,	"completed (available)", B_FALSE,
86 	    B_FALSE },
87 	{ SES2_DLUCODE_S_COMPLETE_AT_RESET,
88 	    "completed (need reset or power on)", B_FALSE, B_FALSE },
89 	{ SES2_DLUCODE_S_COMPLETE_AT_POWERON,	"completed (need power on)",
90 	    B_FALSE, B_FALSE },
91 	{ SES2_DLUCODE_S_PAGE_ERR,	"page error (offset %d)",
92 	    B_FALSE, B_TRUE },
93 	{ SES2_DLUCODE_S_IMAGE_ERR,	"invalid image",
94 	    B_FALSE, B_TRUE },
95 	{ SES2_DLUCODE_S_TIMEOUT,	"download timeout",
96 	    B_FALSE, B_TRUE },
97 	{ SES2_DLUCODE_S_INTERNAL_NEEDIMAGE,
98 	    "internal error (NEED NEW IMAGE BEFORE RESET)",
99 	    B_FALSE, B_TRUE },
100 	{ SES2_DLUCODE_S_INTERNAL_SAFE,
101 	    "internal error (reset to revert to backup)",
102 	    B_FALSE, B_TRUE },
103 };
104 
105 #define	NUCODE_STATUS	\
106 	(sizeof (ucode_statdesc_table) / sizeof (ucode_statdesc_table[0]))
107 
108 typedef struct ucode_status {
109 	uint64_t	us_status;
110 	boolean_t	us_iserr;
111 	boolean_t	us_pending;
112 	char		us_desc[128];
113 } ucode_status_t;
114 
115 typedef struct ucode_wait {
116 	uint64_t	uw_prevstatus;
117 	boolean_t	uw_pending;
118 	ses_node_t	*uw_oldnp;
119 } ucode_wait_t;
120 
121 
122 char drivername[] = "ses\0";
123 static char *devprefix = "/devices";
124 static char *sessuffix = ":0";
125 static char *sgensuffix = ":ses";
126 
127 
128 static ses_target_t *ses_target;
129 static int internalstatus;
130 
131 extern di_node_t rootnode;
132 extern int errno;
133 extern struct fw_plugin *self;
134 extern struct vrfyplugin *verifier;
135 extern int fwflash_debug;
136 
137 
138 /* required functions for this plugin */
139 int fw_readfw(struct devicelist *device, char *filename);
140 int fw_writefw(struct devicelist *device);
141 int fw_identify(int start);
142 int fw_devinfo(struct devicelist *thisdev);
143 
144 
145 /* helper functions */
146 static ses_walk_action_t  print_updated_status(ses_node_t *np, void *arg);
147 static int get_status(nvlist_t *props, ucode_status_t *sp);
148 static ses_walk_action_t sendimg(ses_node_t *np, void *data);
149 static void tidyup(struct devicelist *thisdev);
150 
151 
152 /*
153  * SES2 does not actually allow us to read a firmware
154  * image from an SES device, so we just return success
155  * if this is requested, after printing a message.
156  */
157 int
158 fw_readfw(struct devicelist *flashdev, char *filename)
159 {
160 
161 	logmsg(MSG_INFO,
162 	    "%s: not writing firmware for device %s to file %s\n",
163 	    flashdev->drvname, flashdev->access_devname, filename);
164 	logmsg(MSG_ERROR, gettext("\n\nReading of firmware images from "
165 	    "your device is not currently supported\n\n"));
166 
167 	return (FWFLASH_SUCCESS);
168 }
169 
170 
171 /*
172  * If we're invoking fw_writefw, then flashdev is a valid,
173  * flashable device supporting the SES2 Download Microcode Diagnostic
174  * Control page (0x0e).
175  *
176  * If verifier is null, then we haven't been called following a firmware
177  * image verification load operation.
178  *
179  * *THIS* function uses scsi SEND DIAGNOSTIC/download microcode to
180  * achieve the task... if you chase down to the bottom of libses you
181  * can see that too.
182  */
183 int
184 fw_writefw(struct devicelist *flashdev)
185 {
186 
187 	nvlist_t *nvl;
188 	ses_snap_t *snapshot;
189 
190 
191 
192 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0 ||
193 	    nvlist_add_uint64(nvl, SES_CTL_PROP_UCODE_MODE,
194 	    SES_DLUCODE_M_WITH_OFFS) != 0) {
195 		logmsg(MSG_ERROR,
196 		    gettext("%s: Unable to allocate "
197 		    "space for device prop list\n"),
198 		    flashdev->drvname);
199 		tidyup(flashdev);
200 		return (FWFLASH_FAILURE);
201 	}
202 
203 
204 	if ((verifier == NULL) || (verifier->imgsize == 0) ||
205 	    (verifier->fwimage == NULL)) {
206 		/* should _not_ happen */
207 		logmsg(MSG_ERROR,
208 		    gettext("%s: Firmware image has not "
209 		    "been verified.\n"),
210 		    flashdev->drvname);
211 		tidyup(flashdev);
212 		return (FWFLASH_FAILURE);
213 	}
214 
215 	fprintf(stdout, "\n"); /* get a fresh line for progress updates */
216 
217 	if (nvlist_add_uint64(nvl, SES_CTL_PROP_UCODE_BUFID,
218 	    verifier->flashbuf) != 0) {
219 		logmsg(MSG_ERROR,
220 		    gettext("%s: Unable to add buffer id "
221 		    "property, hence unable to flash device\n"),
222 		    flashdev->drvname);
223 		goto cancel;
224 	}
225 
226 	if (nvlist_add_byte_array(nvl, SES_CTL_PROP_UCODE_DATA,
227 	    (uint8_t *)verifier->fwimage, verifier->imgsize) != 0) {
228 		logmsg(MSG_ERROR,
229 		    "%s: Out of memory for property addition\n",
230 		    flashdev->drvname);
231 		goto cancel;
232 	}
233 
234 	if ((ses_target =
235 	    ses_open(LIBSES_VERSION, flashdev->access_devname)) == NULL) {
236 		logmsg(MSG_ERROR,
237 		    gettext("%s: Unable to open flashable device\n%s\n"),
238 		    flashdev->drvname, flashdev->access_devname);
239 		goto cancel;
240 	}
241 	snapshot = ses_snap_hold(ses_target);
242 
243 	/*
244 	 * We flash via a walker callback function, because it's easier
245 	 * to do it this way when using libses.
246 	 */
247 
248 	internalstatus = FWFLASH_SUCCESS;
249 	(void) ses_walk(snapshot, sendimg, nvl);
250 
251 	if (internalstatus == FWFLASH_SUCCESS) {
252 		logmsg(MSG_ERROR,
253 		    gettext("%s: Done. New image will be active "
254 		    "after the system is rebooted.\n"),
255 		    flashdev->drvname);
256 		fprintf(stdout, "\n");
257 	}
258 
259 	ses_snap_rele(snapshot);
260 	ses_close(ses_target);
261 
262 cancel:
263 	nvlist_free(nvl);
264 	tidyup(flashdev);
265 
266 	return (internalstatus);
267 }
268 
269 
270 /*
271  * The fw_identify() function walks the device
272  * tree trying to find devices which this plugin
273  * can work with.
274  *
275  * The parameter "start" gives us the starting index number
276  * to give the device when we add it to the fw_devices list.
277  *
278  * firstdev is allocated by us and we add space as needed
279  */
280 int
281 fw_identify(int start)
282 {
283 
284 	int rv = FWFLASH_FAILURE;
285 	di_node_t thisnode;
286 	struct devicelist *newdev;
287 	char *devpath;
288 	char *devsuffix;
289 	char *driver;
290 	int idx = start;
291 	int devlength = 0;
292 	nvlist_t *props;
293 	ses_snap_t *snapshot;
294 	ses_node_t *rootnodep, *nodep, *tnodep;
295 
296 
297 	if (strcmp(self->drvname, "sgen") == 0) {
298 		devsuffix = sgensuffix;
299 		driver = self->drvname;
300 
301 	} else {
302 		devsuffix = sessuffix;
303 		driver = drivername;
304 	}
305 
306 	thisnode = di_drv_first_node(driver, rootnode);
307 
308 	if (thisnode == DI_NODE_NIL) {
309 		logmsg(MSG_INFO, gettext("No %s nodes in this system\n"),
310 		    driver);
311 
312 		return (rv);
313 	}
314 
315 	if ((devpath = calloc(1, MAXPATHLEN + 1)) == NULL) {
316 		logmsg(MSG_ERROR,
317 		    gettext("%s: Unable to allocate space "
318 		    "for a device node\n"),
319 		    driver);
320 		return (rv);
321 	}
322 
323 	/* we've found one, at least */
324 
325 	for (; thisnode != DI_NODE_NIL; thisnode = di_drv_next_node(thisnode)) {
326 
327 		devpath = di_devfs_path(thisnode);
328 
329 		if ((newdev = calloc(1, sizeof (struct devicelist)))
330 		    == NULL) {
331 			logmsg(MSG_ERROR,
332 			    gettext("%s: identification function unable "
333 			    "to allocate space for device entry\n"),
334 			    driver);
335 			tidyup(newdev);
336 			free(devpath);
337 			return (rv);
338 		}
339 
340 		/* calloc enough for /devices + devpath + devsuffix + '\0' */
341 		devlength = strlen(devpath) + strlen(devprefix) +
342 		    strlen(devsuffix) + 2;
343 
344 		if ((newdev->access_devname = calloc(1, devlength)) == NULL) {
345 			logmsg(MSG_ERROR,
346 			    gettext("%s: Unable to allocate "
347 			    "space for a devfs name\n"),
348 			    driver);
349 			tidyup(newdev);
350 			free(devpath);
351 			free(newdev);
352 			return (FWFLASH_FAILURE);
353 		}
354 		snprintf(newdev->access_devname, devlength,
355 		    "%s%s%s", devprefix, devpath, devsuffix);
356 
357 		if ((newdev->drvname = calloc(1, strlen(driver) + 1))
358 		    == NULL) {
359 			logmsg(MSG_ERROR,
360 			    gettext("%s: Unable to allocate "
361 			    "space to store a driver name\n"),
362 			    driver);
363 			tidyup(newdev);
364 			free(newdev->access_devname);
365 			free(newdev);
366 			free(devpath);
367 			return (FWFLASH_FAILURE);
368 		}
369 		(void) strlcpy(newdev->drvname, driver,
370 		    strlen(driver) + 1);
371 
372 		if ((newdev->classname = calloc(1, strlen(driver) + 1))
373 		    == NULL) {
374 			logmsg(MSG_ERROR,
375 			    gettext("%s: Unable to malloc "
376 			    "space for a class name\n"),
377 			    drivername);
378 			tidyup(newdev);
379 			free(newdev->access_devname);
380 			free(newdev->drvname);
381 			free(newdev);
382 			free(devpath);
383 			return (FWFLASH_FAILURE);
384 		}
385 		(void) strlcpy(newdev->classname, driver,
386 		    strlen(driver) + 1);
387 
388 		/*
389 		 * Only alloc as much as we truly need, and DON'T forget
390 		 * that libnvpair manages the memory for property lookups!
391 		 * That means we need to call tidyup() after fw_writefw()
392 		 * and fw_devinfo().
393 		 */
394 		newdev->ident = calloc(1, VIDLEN + PIDLEN + REVLEN + 3);
395 		if (newdev->ident == NULL) {
396 			logmsg(MSG_ERROR,
397 			    gettext("%s: Unable to malloc %d bytes "
398 			    "for SCSI INQUIRY data\n"),
399 			    driver, sizeof (struct vpr));
400 			tidyup(newdev);
401 			free(newdev->classname);
402 			free(newdev->drvname);
403 			free(newdev->access_devname);
404 			free(newdev);
405 			free(devpath);
406 			return (FWFLASH_FAILURE);
407 		}
408 
409 
410 		if ((ses_target =
411 		    ses_open(LIBSES_VERSION, newdev->access_devname))
412 		    == NULL) {
413 			logmsg(MSG_INFO,
414 			    gettext("%s: Unable to open device\n%s\n"),
415 			    driver,
416 			    newdev->access_devname);
417 			tidyup(newdev);
418 			free(newdev->ident);
419 			free(newdev->classname);
420 			free(newdev->access_devname);
421 			free(newdev->drvname);
422 			free(newdev);
423 			free(devpath);
424 			continue;
425 		}
426 		snapshot = ses_snap_hold(ses_target);
427 		rootnodep = ses_root_node(snapshot);
428 
429 		/*
430 		 * The root node of the snapshot is likely to be of
431 		 * type SES_NODE_TARGET, so use Shank's Pony to get
432 		 * where we need to go
433 		 */
434 		nodep = ses_node_child(rootnodep);
435 		tnodep = nodep;
436 
437 		if ((props = ses_node_props(rootnodep)) == NULL) {
438 			tidyup(newdev);
439 			free(newdev->ident);
440 			ses_snap_rele(snapshot);
441 			ses_close(ses_target);
442 			free(newdev->classname);
443 			free(newdev->access_devname);
444 			free(newdev->drvname);
445 			free(newdev);
446 			free(devpath);
447 			continue;
448 		}
449 
450 		/*
451 		 * If these properties don't exist, this device does
452 		 * not comply with SES2 so we won't touch it.
453 		 */
454 		if ((nvlist_lookup_string(props, SCSI_PROP_VENDOR,
455 		    &newdev->ident->vid) != 0) ||
456 		    (nvlist_lookup_string(props, SCSI_PROP_PRODUCT,
457 		    &newdev->ident->pid) != 0) ||
458 		    (nvlist_lookup_string(props, SCSI_PROP_REVISION,
459 		    &newdev->ident->revid) != 0)) {
460 			tidyup(newdev);
461 			free(newdev->ident);
462 			ses_snap_rele(snapshot);
463 			ses_close(ses_target);
464 			free(newdev->classname);
465 			free(newdev->access_devname);
466 			free(newdev->drvname);
467 			free(newdev);
468 			free(devpath);
469 			continue;
470 		}
471 
472 
473 		while (ses_node_type(tnodep) != SES_NODE_ENCLOSURE) {
474 			tnodep = ses_node_child(nodep);
475 			nodep = tnodep;
476 		}
477 
478 		if ((nodep == NULL) ||
479 		    (props = ses_node_props(nodep)) == NULL) {
480 			tidyup(newdev);
481 			free(newdev->ident);
482 			ses_snap_rele(snapshot);
483 			ses_close(ses_target);
484 			free(newdev->classname);
485 			free(newdev->access_devname);
486 			free(newdev->drvname);
487 			free(newdev);
488 			free(devpath);
489 			continue;
490 		}
491 
492 		logmsg(MSG_INFO,
493 		    "\nvid: %s\npid: %s\nrevid: %s\n",
494 		    newdev->ident->vid,
495 		    newdev->ident->pid,
496 		    newdev->ident->revid);
497 
498 		if (nvlist_lookup_string(props, LIBSES_EN_PROP_CSN,
499 		    &newdev->addresses[0]) == 0) {
500 			logmsg(MSG_INFO,
501 			    "Chassis Serial Number: %s\n",
502 			    newdev->addresses[0]);
503 		} else {
504 			(void) strlcpy(newdev->addresses[0],
505 			    "(not supported)", 17);
506 		}
507 
508 
509 		if (di_prop_lookup_strings(DDI_DEV_T_ANY,
510 		    thisnode, "target-port",
511 		    &newdev->addresses[1]) < 0) {
512 			logmsg(MSG_INFO,
513 			    "%s: no target-port property "
514 			    "for device %s\n",
515 			    driver, newdev->access_devname);
516 			(void) strlcpy(newdev->addresses[1],
517 			    "(not supported)", 17);
518 		} else
519 			logmsg(MSG_INFO,
520 			    "target-port property: %s\n",
521 			    newdev->addresses[1]);
522 
523 
524 		newdev->index = idx;
525 		++idx;
526 		newdev->plugin = self;
527 
528 		ses_snap_rele(snapshot);
529 		TAILQ_INSERT_TAIL(fw_devices, newdev, nextdev);
530 	}
531 
532 
533 	if (fwflash_debug != 0) {
534 		struct devicelist *tempdev;
535 
536 		TAILQ_FOREACH(tempdev, fw_devices, nextdev) {
537 			logmsg(MSG_INFO, "%s:fw_identify:\n",
538 			    driver);
539 			logmsg(MSG_INFO,
540 			    "\ttempdev @ 0x%lx\n"
541 			    "\t\taccess_devname: %s\n"
542 			    "\t\tdrvname: %s\tclassname: %s\n"
543 			    "\t\tident->vid:   %s\n"
544 			    "\t\tident->pid:   %s\n"
545 			    "\t\tident->revid: %s\n"
546 			    "\t\tindex:        %d\n"
547 			    "\t\taddress[0]:   %s\n"
548 			    "\t\taddress[1]:   %s\n"
549 			    "\t\tplugin @ 0x%lx\n\n",
550 			    &tempdev,
551 			    tempdev->access_devname,
552 			    tempdev->drvname, newdev->classname,
553 			    tempdev->ident->vid,
554 			    tempdev->ident->pid,
555 			    tempdev->ident->revid,
556 			    tempdev->index,
557 			    tempdev->addresses[0],
558 			    tempdev->addresses[1],
559 			    &tempdev->plugin);
560 		}
561 	}
562 
563 	return (FWFLASH_SUCCESS);
564 }
565 
566 
567 
568 int
569 fw_devinfo(struct devicelist *thisdev)
570 {
571 
572 
573 	fprintf(stdout, gettext("Device[%d] %s\n  Class [%s]\n"),
574 	    thisdev->index, thisdev->access_devname, thisdev->classname);
575 
576 	fprintf(stdout,
577 	    gettext("\tVendor                 : %s\n"
578 	    "\tProduct                : %s\n"
579 	    "\tFirmware revision      : %s\n"
580 	    "\tChassis Serial Number  : %s\n"
581 	    "\tTarget-port identifier : %s\n"),
582 	    thisdev->ident->vid,
583 	    thisdev->ident->pid,
584 	    thisdev->ident->revid,
585 	    thisdev->addresses[0],
586 	    thisdev->addresses[1]);
587 
588 	fprintf(stdout, "\n\n");
589 
590 	/* Don't leave any bits behind... */
591 	tidyup(thisdev);
592 
593 	return (FWFLASH_SUCCESS);
594 }
595 
596 
597 
598 
599 
600 /*ARGSUSED*/
601 static int
602 get_status(nvlist_t *props, ucode_status_t *sp)
603 {
604 	int i;
605 	uint64_t status, astatus;
606 
607 	if (nvlist_lookup_uint64(props, SES_EN_PROP_UCODE, &status) != 0) {
608 		sp->us_status = -1ULL;
609 		(void) snprintf(sp->us_desc, sizeof (sp->us_desc),
610 		    "not supported");
611 		internalstatus = FWFLASH_FAILURE;
612 		return (-1);
613 	}
614 
615 	if (nvlist_lookup_uint64(props, SES_EN_PROP_UCODE_A,
616 	    &astatus) != 0) {
617 		logmsg(MSG_ERROR,
618 		    gettext("\nError: Unable to retrieve current status\n"));
619 		internalstatus = FWFLASH_FAILURE;
620 		return (-1);
621 	}
622 
623 
624 	for (i = 0; i < NUCODE_STATUS; i++) {
625 		if (ucode_statdesc_table[i].us_value == status)
626 			break;
627 	}
628 
629 	sp->us_status = status;
630 
631 	if (i == NUCODE_STATUS) {
632 		(void) snprintf(sp->us_desc, sizeof (sp->us_desc),
633 		    "unknown (0x%02x)", (int)status);
634 		sp->us_iserr = sp->us_pending = B_FALSE;
635 		internalstatus = FWFLASH_FAILURE;
636 	} else {
637 		/* LINTED */
638 		(void) snprintf(sp->us_desc, sizeof (sp->us_desc),
639 		    ucode_statdesc_table[i].us_desc, (int)astatus);
640 		sp->us_iserr = ucode_statdesc_table[i].us_iserr;
641 		sp->us_pending = ucode_statdesc_table[i].us_pending;
642 	}
643 
644 	return (0);
645 }
646 
647 
648 static ses_walk_action_t
649 print_updated_status(ses_node_t *np, void *arg)
650 {
651 	ucode_wait_t *uwp = arg;
652 	ses_node_t *oldnp = uwp->uw_oldnp;
653 	nvlist_t *props, *oldprops;
654 	uint64_t id, oldid;
655 	ucode_status_t status;
656 
657 	if (ses_node_type(np) != SES_NODE_ENCLOSURE)
658 		return (SES_WALK_ACTION_CONTINUE);
659 
660 	verify((props = ses_node_props(np)) != NULL);
661 	verify((oldprops = ses_node_props(oldnp)) != NULL);
662 	verify(nvlist_lookup_uint64(props, SES_EN_PROP_EID, &id) == 0);
663 	verify(nvlist_lookup_uint64(oldprops, SES_EN_PROP_EID, &oldid) == 0);
664 
665 	if (oldid != id)
666 		return (SES_WALK_ACTION_CONTINUE);
667 
668 	(void) get_status(props, &status);
669 	if (status.us_status != uwp->uw_prevstatus)
670 		(void) printf("%30s: %s\n", "status", status.us_desc);
671 	uwp->uw_prevstatus = status.us_status;
672 	uwp->uw_pending = status.us_pending;
673 
674 	if (status.us_iserr) {
675 		logmsg(MSG_INFO,
676 		    "libses: status.us_iserr: 0x%0x\n",
677 		    status.us_iserr);
678 		internalstatus = FWFLASH_FAILURE;
679 	}
680 
681 	return (SES_WALK_ACTION_CONTINUE);
682 }
683 
684 /*ARGSUSED*/
685 static ses_walk_action_t
686 sendimg(ses_node_t *np, void *data)
687 {
688 	nvlist_t *props;
689 	nvlist_t *arg = data;
690 	char *vendor, *product, *revision, *csn;
691 	char buf[128];
692 	ses_snap_t *newsnap;
693 	int ret;
694 	ucode_status_t statdesc;
695 	ucode_wait_t wait;
696 	uint8_t *imagedata;
697 	uint_t len;
698 
699 	if (ses_node_type(np) != SES_NODE_ENCLOSURE)
700 		return (SES_WALK_ACTION_CONTINUE);
701 
702 	verify((props = ses_node_props(np)) != NULL);
703 
704 	verify(nvlist_lookup_string(props, SES_EN_PROP_VID, &vendor) == 0);
705 	verify(nvlist_lookup_string(props, SES_EN_PROP_PID, &product) == 0);
706 	verify(nvlist_lookup_string(props, SES_EN_PROP_REV, &revision) == 0);
707 	verify(nvlist_lookup_string(props, LIBSES_EN_PROP_CSN, &csn) == 0);
708 
709 	(void) printf("%30s: %s\n", "vendor", vendor);
710 	(void) printf("%30s: %s\n", "product", product);
711 	(void) printf("%30s: %s\n", "revision", revision);
712 	(void) printf("%30s: %s\n", "serial", csn);
713 
714 	ret = get_status(props, &statdesc);
715 	(void) printf("%30s: %s\n", "current status", statdesc.us_desc);
716 	if (ret != 0) {
717 		/* internalstatus is already set */
718 		if (arg != NULL)
719 			return (SES_WALK_ACTION_TERMINATE);
720 		else
721 			return (SES_WALK_ACTION_CONTINUE);
722 	}
723 
724 	verify(nvlist_lookup_byte_array(arg, SES_CTL_PROP_UCODE_DATA,
725 	    &imagedata, &len) == 0);
726 	(void) snprintf(buf, sizeof (buf), "downloading %u bytes", len);
727 	(void) printf("\n%30s: ", buf);
728 	if (ses_node_ctl(np, SES_CTL_OP_DL_UCODE, arg) != 0) {
729 		(void) printf("failed!\n");
730 		(void) printf("%s\n", ses_errmsg());
731 		internalstatus = FWFLASH_FAILURE;
732 	} else {
733 		(void) printf("ok\n");
734 	}
735 
736 	wait.uw_prevstatus = -1ULL;
737 	wait.uw_oldnp = np;
738 	do {
739 		if ((newsnap = ses_snap_new(ses_target)) == NULL)
740 			logmsg(MSG_ERROR,
741 			    "failed to update SES snapshot: %s",
742 			    ses_errmsg());
743 
744 		(void) ses_walk(newsnap, print_updated_status,
745 		    &wait);
746 		ses_snap_rele(newsnap);
747 	} while (wait.uw_pending);
748 
749 	return (SES_WALK_ACTION_CONTINUE);
750 }
751 
752 static void
753 tidyup(struct devicelist *thisdev)
754 {
755 	/*
756 	 * Since we didn't allocate the space for the ident->* and
757 	 * addresses[*], set them to NULL and let the libraries
758 	 * (libnvpair and libdevinfo) handle them.
759 	 */
760 	thisdev->ident->vid = NULL;
761 	thisdev->ident->pid = NULL;
762 	thisdev->ident->revid = NULL;
763 	thisdev->addresses[0] = NULL;
764 	thisdev->addresses[1] = NULL;
765 }
766