xref: /illumos-gate/usr/src/cmd/format/menu_scsi.c (revision cb1bb6c32d034ea24e8549ef763c9c2b79413eb8)
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 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * This file contains functions implementing the scsi menu commands.
29  *
30  * These functions are intended for expert use only, and provide
31  * a raw access to a scsi device's mode pages.  The ability to
32  * issue a raw format command is also provided, should a page be
33  * changed that requires a format.
34  */
35 #include "global.h"
36 #include <stdlib.h>
37 #include <ctype.h>
38 
39 #include "io.h"
40 #include "menu.h"
41 #include "misc.h"
42 #include "menu_scsi.h"
43 #include "ctlr_scsi.h"
44 #include "startup.h"
45 #include "checkdev.h"
46 
47 static int	do_mode_sense(int);
48 static int	do_mode_sense_all(void);
49 static int	do_mode_select(struct chg_list *);
50 static int	do_format(void);
51 static void	do_list(void);
52 static int	do_inquiry(void);
53 static void	do_apply(void);
54 static void	do_cancel(void);
55 static void	do_display(void);
56 static int	parse_change_spec(char *, char *, int, struct chg_list *);
57 static void	add_new_change_list_item(struct chg_list *);
58 static void	free_change_list(void);
59 static void	do_default(char *);
60 static void	default_all_pages(void);
61 static int	default_page(int);
62 
63 /*
64  * Menu data for the SCSI menu display
65  */
66 static char	*scsi_menu_strings[] = {
67 	"p<n>                   - display a mode sense page",
68 	"p<n> b<n> <op> [~]<n>  - change a byte and issue mode select",
69 	"b<n> <op> [~]<n>       - add an operation to the mode select list",
70 	"                             for the current page",
71 	"",
72 	"        where:  p<n> specifies the page with page code <n>",
73 	"                b<n> specifies byte <n> of the page",
74 	"                <op> can be one of the following operators:",
75 	"                     =    (set specified value)",
76 	"                     |=   (bitwise OR with current value)",
77 	"                     &=   (bitwise AND with current value)",
78 	"                <n> can be a decimal value in the range 0-255,",
79 	"                or two hexadecimal digits, in the form 0x<xx>.",
80 	"                [~] complements the specified value",
81 	"",
82 	"apply                  - apply mode select list",
83 	"cancel                 - cancel mode select list",
84 	"display                - display mode select list",
85 	"all                    - display all supported mode sense pages",
86 	"default p<n>           - mode select page <n> to default values",
87 	"default all            - mode select all pages to default values",
88 	"format                 - format without standard mode selects",
89 	"inquiry                - display device's inquiry response",
90 	"list                   - list common SCSI-2 mode pages",
91 	"!<cmd>                 - execute <cmd> , then return"
92 };
93 
94 #define	N_SCSI_STRINGS	(sizeof (scsi_menu_strings) / sizeof (char *))
95 
96 /*
97  * Command types
98  */
99 #define	CMD_ALL			0
100 #define	CMD_FORMAT		1
101 #define	CMD_QUIT		2
102 #define	CMD_HELP		3
103 #define	CMD_LIST		4
104 #define	CMD_INQUIRY		5
105 #define	CMD_APPLY		6
106 #define	CMD_CANCEL		7
107 #define	CMD_DISPLAY		8
108 
109 /*
110  * SCSI menu commands for minimum recognition
111  */
112 static struct slist	cmds_list[] = {
113 	{ "all",	NULL,	CMD_ALL },
114 	{ "format",	NULL,	CMD_FORMAT },
115 	{ "quit",	NULL,	CMD_QUIT },
116 	{ "help",	NULL,	CMD_HELP },
117 	{ "?",		NULL,	CMD_HELP },
118 	{ "list",	NULL,	CMD_LIST },
119 	{ "inquiry",	NULL,	CMD_INQUIRY },
120 	{ "apply",	NULL,	CMD_APPLY },
121 	{ "cancel",	NULL,	CMD_CANCEL },
122 	{ "display",	NULL,	CMD_DISPLAY },
123 	{ NULL	}
124 };
125 
126 /*
127  * Implied page for mode select change lists
128  */
129 static	int		current_page;
130 static	struct chg_list	*change_list;
131 
132 /*
133  * Manage the SCSI menu.
134  * Parse input and dispatch to the appropriate functions.
135  * The commands we accept are not simple one-word commands,
136  * so we cannot use the standard format menu-handling functions.
137  */
138 int
139 c_scsi(void)
140 {
141 	int			i;
142 	struct env		env;
143 	char			**menu;
144 	struct menu_item	scsi_menu[N_SCSI_STRINGS+1];
145 	struct chg_list		change_item;
146 	struct chg_list		*chg_item;
147 	char			s[MAXPATHLEN], nclean[MAXPATHLEN];
148 	char			*p;
149 	char			*p2;
150 	int			cmd;
151 	int			pageno;
152 	int			help = 1;
153 
154 	/*
155 	 * Warn casual users that maybe they should not be
156 	 * using this menu.
157 	 */
158 	fmt_print("\n"
159 "Warning:  these functions are intended for expert use only, for\n"
160 "debugging disk devices and for unusual configuration settings.\n"
161 "It is recommended that you do not use this menu for normal disk\n"
162 "configuration and formatting, unless you have explicit instructions,\n"
163 "or know exactly what you are doing.\n");
164 
165 	/*
166 	 * Initialize change list and current page to empty
167 	 */
168 	current_page = -1;
169 	change_list = NULL;
170 
171 	/*
172 	 * Build and display the menu.
173 	 * we only use this for display purposes.
174 	 */
175 	for (i = 0; i < N_SCSI_STRINGS; i++) {
176 		scsi_menu[i].menu_cmd = scsi_menu_strings[i];
177 		scsi_menu[i].menu_func = NULL;
178 		scsi_menu[i].menu_state = true;
179 	}
180 	scsi_menu[i].menu_cmd = NULL;
181 	menu = create_menu_list(scsi_menu);
182 	/*
183 	 * Save the environment so a ctrl-C out of a command lands here.
184 	 */
185 	saveenv(env);
186 	for (;;) {
187 		if (help) {
188 			help = 0;
189 			fmt_print("\n\nSCSI MENU:\n");
190 			display_menu_list(menu);
191 		}
192 		/*
193 		 * Prompt and get next input line.  We don't use the
194 		 * standard input routine, since we need a little
195 		 * more flexibility in parsing the input.
196 		 */
197 		fmt_print("scsi> ");
198 		get_inputline(nclean, sizeof (nclean));
199 
200 		clean_token(s, nclean);
201 
202 		/*
203 		 * Mark the saved environment active so the user can now
204 		 * do a ctrl-C to get out of the command.
205 		 */
206 		useenv();
207 
208 		/*
209 		 * Figure out what the user chose
210 		 */
211 		i = find_value(cmds_list, s, &cmd);
212 		if (i == 1) {
213 			switch (cmd) {
214 			case CMD_ALL:
215 				(void) do_mode_sense_all();
216 				break;
217 			case CMD_FORMAT:
218 				(void) do_format();
219 				break;
220 			case CMD_QUIT:
221 				goto exit;
222 				/*NOTREACHED*/
223 			case CMD_HELP:
224 				fmt_print("\n\nSCSI MENU:\n");
225 				display_menu_list(menu);
226 				break;
227 			case CMD_LIST:
228 				do_list();
229 				break;
230 			case CMD_INQUIRY:
231 				(void) do_inquiry();
232 				break;
233 			case CMD_APPLY:
234 				do_apply();
235 				break;
236 			case CMD_CANCEL:
237 				do_cancel();
238 				break;
239 			case CMD_DISPLAY:
240 				do_display();
241 				break;
242 			}
243 		} else if (s[0] == 'd') {
244 			do_default(s);
245 		} else if (s[0] == 'p') {
246 			p = s + 1;
247 			pageno = (int)strtol(p, &p2, 0);
248 			if (p2 == p) {
249 				err_print("Syntax error: %s\n", s);
250 				goto error;
251 			}
252 			current_page = pageno;
253 			for (p = p2; *p == ' '; p++)
254 				;
255 			if (*p == 0) {
256 				(void) do_mode_sense(pageno);
257 			} else if (*p == 'b') {
258 				if (parse_change_spec(s, p, pageno,
259 				    &change_item)) {
260 					(void) do_mode_select(&change_item);
261 				}
262 			}
263 		} else if (s[0] == 'b') {
264 				if (current_page == -1) {
265 					err_print("\
266 Please display the page on which you'd like to do a mode select\n");
267 					goto error;
268 				}
269 				chg_item = (struct chg_list *)
270 				    zalloc(sizeof (struct chg_list));
271 				if (parse_change_spec(s, s, current_page,
272 				    chg_item)) {
273 					add_new_change_list_item(chg_item);
274 				} else {
275 					destroy_data((char *)chg_item);
276 				}
277 		} else if (s[0] == '!') {
278 			(void) execute_shell(&s[1], sizeof (s) - 1);
279 			help = 1;
280 		} else if (s[0] != 0) {
281 			err_print("Syntax error: %s\n", s);
282 		}
283 error:
284 		/*
285 		 * Mark the saved environment inactive so ctrl-C doesn't
286 		 * work at the menu itself.
287 		 */
288 		unuseenv();
289 	}
290 exit:
291 	/*
292 	 * Clean up the environment stack and free the menu
293 	 */
294 	clearenv();
295 	destroy_data((char *)menu);
296 
297 	/*
298 	 * Clean up the change list, if anything left over
299 	 */
300 	free_change_list();
301 
302 	/*
303 	 * Make sure user is prompted with previous menu
304 	 */
305 	last_menu++;
306 
307 	return (0);
308 }
309 
310 
311 /*
312  * Do a mode sense on a particular page, and dump the data.
313  * Get all the various flavors:  default, current, saved, changeable.
314  */
315 static int
316 do_mode_sense(int pageno)
317 {
318 	struct scsi_ms_header	header;
319 	struct mode_page	*pg;
320 	char			msbuf[MAX_MODE_SENSE_SIZE];
321 	int			result = 0;
322 
323 	char	*default_msg	= "default:     ";
324 	char	*saved_msg	= "saved:       ";
325 	char	*current_msg	= "current:     ";
326 	char	*changeable_msg	= "changeable:  ";
327 
328 
329 	pg = (struct mode_page *)msbuf;
330 
331 	fmt_print("\nPage 0x%x:\n", pageno);
332 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_DEFAULT,
333 	    msbuf, MAX_MODE_SENSE_SIZE, &header)) {
334 		err_print("%sfailed\n", default_msg);
335 		result = 1;
336 	} else {
337 		dump(default_msg, msbuf, MODESENSE_PAGE_LEN(pg), HEX_ONLY);
338 	}
339 
340 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_CURRENT,
341 	    msbuf, MAX_MODE_SENSE_SIZE, &header)) {
342 		err_print("%sfailed\n", current_msg);
343 		result = 1;
344 	} else {
345 		dump(current_msg, msbuf, MODESENSE_PAGE_LEN(pg), HEX_ONLY);
346 	}
347 
348 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_SAVED,
349 	    msbuf, MAX_MODE_SENSE_SIZE, &header)) {
350 		err_print("%sfailed\n", saved_msg);
351 		result = 1;
352 	} else {
353 		dump(saved_msg, msbuf, MODESENSE_PAGE_LEN(pg), HEX_ONLY);
354 	}
355 
356 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_CHANGEABLE,
357 	    msbuf, MAX_MODE_SENSE_SIZE, &header)) {
358 		err_print("%sfailed\n", changeable_msg);
359 		result = 1;
360 	} else {
361 		dump(changeable_msg, msbuf, MODESENSE_PAGE_LEN(pg), HEX_ONLY);
362 	}
363 
364 	fmt_print("\n");
365 	return (result);
366 }
367 
368 
369 /*
370  * Dump all the pages a device supports
371  */
372 static int
373 do_mode_sense_all(void)
374 {
375 	int	result = 0;
376 
377 	if (scsi_dump_mode_sense_pages(MODE_SENSE_PC_DEFAULT)) {
378 		result = 1;
379 	}
380 	if (scsi_dump_mode_sense_pages(MODE_SENSE_PC_CURRENT)) {
381 		result = 1;
382 	}
383 	if (scsi_dump_mode_sense_pages(MODE_SENSE_PC_SAVED)) {
384 		result = 1;
385 	}
386 	if (scsi_dump_mode_sense_pages(MODE_SENSE_PC_CHANGEABLE)) {
387 		result = 1;
388 	}
389 	fmt_print("\n");
390 	return (result);
391 }
392 
393 
394 /*
395  * Get the current mode sense for a particular page, change
396  * a byte, and issue a mode select.  Note that we can only
397  * change a value if the device indicates that those bits
398  * are changeable.
399  */
400 static int
401 do_mode_select(struct chg_list *change_item)
402 {
403 	struct scsi_ms_header	header;
404 	char			saved[MAX_MODE_SENSE_SIZE];
405 	char			changeable[MAX_MODE_SENSE_SIZE];
406 	struct mode_page	*pg;
407 	struct mode_page	*pg2;
408 	int			length;
409 	int			pageno;
410 	int			flags;
411 	int			result = 0;
412 
413 	pageno = change_item->pageno;
414 
415 	/*
416 	 * Get changeable mode sense
417 	 */
418 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_CHANGEABLE,
419 	    changeable, MAX_MODE_SENSE_SIZE, &header)) {
420 		err_print("Mode sense on page %x (changeable) failed\n",
421 		    pageno);
422 		return (1);
423 	}
424 
425 	/*
426 	 * Get saved mode sense.  If saved fails, use current values.
427 	 */
428 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_SAVED,
429 	    saved, MAX_MODE_SENSE_SIZE, &header)) {
430 		err_print("Mode sense on page %x (saved) failed\n", pageno);
431 		if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_CURRENT,
432 		    saved, MAX_MODE_SENSE_SIZE, &header)) {
433 			err_print("Mode sense on page %x (current) failed\n",
434 			    pageno);
435 			return (1);
436 		} else {
437 			err_print("Using current values instead\n");
438 		}
439 	}
440 
441 	/*
442 	 * Use the intersection of the saved and changeable
443 	 */
444 	pg = (struct mode_page *)saved;
445 	pg2 = (struct mode_page *)changeable;
446 	length = min(MODESENSE_PAGE_LEN(pg), MODESENSE_PAGE_LEN(pg2));
447 
448 	/*
449 	 * Try making this change to this page
450 	 */
451 	if (apply_chg_list(pageno, length, (uchar_t *)saved,
452 	    (uchar_t *)changeable, change_item)) {
453 		/*
454 		 * A change was made.  Do a mode select
455 		 * We always want to set the Page Format bit.
456 		 * Set the Save Page bit if the drive indicates
457 		 * that it can save this page.
458 		 */
459 		flags = MODE_SELECT_PF;
460 		if (pg->ps) {
461 			flags |= MODE_SELECT_SP;
462 		}
463 		pg->ps = 0;
464 		header.mode_header.length = 0;
465 		header.mode_header.device_specific = 0;
466 		if (uscsi_mode_select(cur_file, pageno, flags,
467 		    saved, length, &header)) {
468 			/*
469 			 * Failed - try not saving parameters,
470 			 * if possible.
471 			 */
472 			if (flags & MODE_SELECT_SP) {
473 				flags &= ~MODE_SELECT_SP;
474 				if (uscsi_mode_select(cur_file, pageno,
475 				    flags, saved, length, &header)) {
476 					result = 1;
477 				}
478 			} else {
479 				result = 1;
480 			}
481 		}
482 
483 		if (result) {
484 			fmt_print("\n\
485 Mode select on page %x failed.\n", pageno);
486 		} else if ((flags & MODE_SELECT_SP) == 0) {
487 			fmt_print("\n\
488 Mode select on page %x ok, but unable to save change permanently.\n", pageno);
489 		} else {
490 			fmt_print("\n\
491 Mode select on page %x ok.\n", pageno);
492 		}
493 	} else {
494 		err_print("\nDevice cannot support this change\n");
495 	}
496 
497 	fmt_print("\n");
498 	return (result);
499 }
500 
501 
502 /*
503  * Format a device, without any of the standard mode selects.
504  * Ask if we should format with the P or the P&G lists.
505  */
506 static int
507 do_format(void)
508 {
509 	struct uscsi_cmd	ucmd;
510 	union scsi_cdb		cdb;
511 	struct scsi_defect_hdr	defect_hdr;
512 	int			status;
513 	u_ioparam_t		ioparam;
514 	int			deflt;
515 	int			grown_list;
516 
517 	fmt_print("\n");
518 	/*
519 	 * Are there mounted partitions?
520 	 */
521 	if (checkmount((diskaddr_t)-1, (diskaddr_t)-1)) {
522 		err_print("Cannot format disk with mounted partitions\n\n");
523 		return (-1);
524 	}
525 
526 	/*
527 	 * Is any of the partitions being used for swapping.
528 	 */
529 	if (checkswap((diskaddr_t)-1, (diskaddr_t)-1)) {
530 		err_print("Cannot format disk while its partitions are \
531 currently being used for swapping.\n\n");
532 		return (-1);
533 	}
534 	/*
535 	 * Are any being used for SVM, VxVM or live upgrade.
536 	 */
537 	if (checkdevinuse(cur_disk->disk_name, (diskaddr_t)-1,
538 	    (diskaddr_t)-1, 0, 0)) {
539 		err_print("Cannot format disk while its partitions are "
540 		    "currently being used as described.\n");
541 		return (-1);
542 	}
543 
544 	/*
545 	 * Let the user choose between formatting with either
546 	 * the P, or the P&G lists.  Note that yes is 0, no is 1.
547 	 */
548 	deflt = 0;
549 	ioparam.io_charlist = confirm_list;
550 	grown_list = !input(FIO_MSTR, "Format with the Grown Defects list",
551 	    '?', &ioparam, &deflt, DATA_INPUT);
552 
553 	/*
554 	 * Construct the uscsi format ioctl.
555 	 * To format with the P and G list, we set the fmtData
556 	 * and cmpLst bits to zero.  To format with just the
557 	 * P list, we set the fmtData bit (meaning that we will
558 	 * send down a defect list in the data phase) and the
559 	 * cmpLst bit (meaning that the list we send is the
560 	 * complete G list), and a defect list header with
561 	 * a defect list length of zero.
562 	 */
563 	(void) memset((char *)&ucmd, 0, sizeof (ucmd));
564 	(void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
565 	cdb.scc_cmd = SCMD_FORMAT;
566 	ucmd.uscsi_cdb = (caddr_t)&cdb;
567 	ucmd.uscsi_cdblen = CDB_GROUP0;
568 	if (!grown_list) {
569 		/*
570 		 * No G list.   Send empty defect list to replace it.
571 		 */
572 		cdb.cdb_opaque[1] = FPB_DATA | FPB_CMPLT | FPB_BFI;
573 		(void) memset((char *)&defect_hdr, 0, sizeof (defect_hdr));
574 		ucmd.uscsi_bufaddr = (caddr_t)&defect_hdr;
575 		ucmd.uscsi_buflen = sizeof (defect_hdr);
576 	}
577 
578 	/*
579 	 * Issue the format ioctl
580 	 */
581 	fmt_print("Formatting...\n");
582 	(void) fflush(stdout);
583 	status = uscsi_cmd(cur_file, &ucmd, F_NORMAL);
584 	fmt_print(status ? "Format failed\n\n" : "Format ok\n\n");
585 	return (status);
586 }
587 
588 
589 /*
590  * List common SCSI-2 mode pages
591  */
592 static void
593 do_list(void)
594 {
595 	fmt_print("\n\
596 Common SCSI-2 pages applicable to direct-access devices:\n\n");
597 	fmt_print("Page 0x1   - Read-Write Error Recovery Page\n");
598 	fmt_print("Page 0x2   - Disconnect-Reconnect Page\n");
599 	fmt_print("Page 0x3   - Format Device Page\n");
600 	fmt_print("Page 0x4   - Rigid Disk Geometry Page\n");
601 	fmt_print("Page 0x7   - Verify Error Recovery Page\n");
602 	fmt_print("Page 0x8   - Caching Page\n");
603 	fmt_print("Page 0xA   - Control Mode Page\n");
604 	fmt_print("\n");
605 }
606 
607 
608 /*
609  * Labels for the various fields of the scsi_inquiry structure
610  */
611 static char *scsi_inquiry_labels[] = {
612 	"Vendor:                     ",
613 	"Product:                    ",
614 	"Revision:                   ",
615 	"Removable media:            ",
616 	"Device type:                ",
617 	"ISO version:                ",
618 	"ECMA version:               ",
619 	"ANSI version:               ",
620 	"Async event notification:   ",
621 	"Terminate i/o process msg:  ",
622 	"Response data format:       ",
623 	"Additional length:          ",
624 	"Relative addressing:        ",
625 	"32 bit transfers:           ",
626 	"16 bit transfers:           ",
627 	"Synchronous transfers:      ",
628 	"Linked commands:            ",
629 	"Command queueing:           ",
630 	"Soft reset option:          "
631 };
632 
633 
634 /*
635  * Dump the full inquiry as returned by the device
636  */
637 static int
638 do_inquiry(void)
639 {
640 	char			inqbuf[255];
641 	struct scsi_inquiry	*inq;
642 	char			**p;
643 
644 	inq = (struct scsi_inquiry *)inqbuf;
645 
646 	if (uscsi_inquiry(cur_file, inqbuf, sizeof (inqbuf))) {
647 		err_print("\nInquiry failed\n");
648 		return (1);
649 	}
650 
651 	fmt_print("\nInquiry:\n");
652 	/*
653 	 * The SCSI-2 spec defines "Additional length" as (n-4) bytes,
654 	 * where n is the last byte of the INQUIRY data.  Thus
655 	 * there are n+1 bytes of INQUIRY data.  We need to add 5 to
656 	 * inq_len in order to get all the INQUIRY data.
657 	 */
658 	dump("    ", inqbuf, inq->inq_len + 5, HEX_ASCII);
659 	fmt_print("\n");
660 
661 	p = scsi_inquiry_labels;
662 
663 	fmt_print("%s", *p++);
664 	print_buf(inq->inq_vid, sizeof (inq->inq_vid));
665 	fmt_print("\n%s", *p++);
666 	print_buf(inq->inq_pid, sizeof (inq->inq_pid));
667 	fmt_print("\n%s", *p++);
668 	print_buf(inq->inq_revision, sizeof (inq->inq_revision));
669 
670 	fmt_print("\n%s%s\n", *p++, inq->inq_rmb ? "yes" : "no");
671 	fmt_print("%s%d\n", *p++, inq->inq_qual);
672 	fmt_print("%s%d\n", *p++, inq->inq_iso);
673 	fmt_print("%s%d\n", *p++, inq->inq_ecma);
674 	fmt_print("%s%d\n", *p++, inq->inq_ansi);
675 	fmt_print("%s%s\n", *p++, inq->inq_aenc ? "yes" : "no");
676 	fmt_print("%s%s\n", *p++, inq->inq_trmiop ? "yes" : "no");
677 	fmt_print("%s%d\n", *p++, inq->inq_rdf);
678 	fmt_print("%s%d\n", *p++, inq->inq_len);
679 	fmt_print("%s%s\n", *p++, inq->inq_reladdr ? "yes" : "no");
680 	fmt_print("%s%s\n", *p++, inq->inq_wbus32 ? "yes" : "no");
681 	fmt_print("%s%s\n", *p++, inq->inq_wbus16 ? "yes" : "no");
682 	fmt_print("%s%s\n", *p++, inq->inq_sync ? "yes" : "no");
683 	fmt_print("%s%s\n", *p++, inq->inq_linked ? "yes" : "no");
684 	fmt_print("%s%s\n", *p++, inq->inq_cmdque ? "yes" : "no");
685 	fmt_print("%s%s\n", *p++, inq->inq_sftre ? "yes" : "no");
686 
687 	fmt_print("\n");
688 	return (0);
689 }
690 
691 
692 static void
693 do_apply(void)
694 {
695 	if (change_list == NULL) {
696 		fmt_print("\nlist empty.\n");
697 	} else {
698 		(void) do_mode_select(change_list);
699 		free_change_list();
700 	}
701 }
702 
703 
704 static void
705 do_cancel(void)
706 {
707 	if (change_list == NULL) {
708 		fmt_print("\nlist empty.\n");
709 	} else {
710 		free_change_list();
711 	}
712 }
713 
714 
715 static void
716 do_display(void)
717 {
718 	struct chg_list	*cp;
719 
720 	if (change_list == NULL) {
721 		fmt_print("\nlist empty.\n");
722 	} else {
723 		fmt_print("\nPage 0x%x\n", current_page);
724 		for (cp = change_list; cp != NULL; cp = cp->next) {
725 			fmt_print("   b0x%x ", cp->byteno);
726 			switch (cp->mode) {
727 			case CHG_MODE_ABS:
728 				fmt_print("= 0x%x\n", cp->value);
729 				break;
730 			case CHG_MODE_SET:
731 				fmt_print("|= 0x%x\n", cp->value);
732 				break;
733 			case CHG_MODE_CLR:
734 				fmt_print("&= ~0x%x\n",
735 				    (~(cp->value)) & 0xff);
736 				break;
737 			default:
738 				impossible("do_display");
739 				/*NOTREACHED*/
740 			}
741 		}
742 		fmt_print("\n");
743 	}
744 }
745 
746 
747 static int
748 parse_change_spec(char *full_input, char *input, int pageno,
749     struct chg_list *chg_item)
750 {
751 	char		*p;
752 	int		tilde;
753 
754 	assert(*input == 'b');
755 
756 	chg_item->pageno = pageno;
757 	chg_item->next = NULL;
758 
759 	input++;
760 	chg_item->byteno = (int)strtol(input, &p, 0);
761 	if (p == input) {
762 		err_print("Syntax error: %s\n", full_input);
763 		return (0);
764 	}
765 	if (chg_item->byteno < 2) {
766 		err_print(" Unsupported byte offset: %d\n", chg_item->byteno);
767 		return (0);
768 	}
769 	for (input = p; *input == ' '; input++)
770 		;
771 	chg_item->mode = CHG_MODE_UNDEFINED;
772 	switch (*input++) {
773 	case '=':
774 		chg_item->mode = CHG_MODE_ABS;
775 		break;
776 	case '|':
777 		if (*input++ == '=') {
778 			chg_item->mode = CHG_MODE_SET;
779 		}
780 		break;
781 	case '&':
782 		if (*input++ == '=') {
783 			chg_item->mode = CHG_MODE_CLR;
784 		}
785 		break;
786 	}
787 	if (chg_item->mode == CHG_MODE_UNDEFINED) {
788 		err_print("Syntax error: %s\n", full_input);
789 		return (0);
790 	}
791 	for (; *input == ' '; input++)
792 		;
793 	if (*input == '~') {
794 		tilde = 1;
795 		for (input++; *input == ' '; input++)
796 			;
797 	} else {
798 		tilde = 0;
799 	}
800 	chg_item->value = (int)strtol(input, &p, 0);
801 	if (p == input || *p != 0) {
802 		err_print("Syntax error: %s\n", full_input);
803 		return (0);
804 	}
805 	/*
806 	 * Apply complement if selected.
807 	 * Constrain to a byte value.
808 	 */
809 	if (tilde) {
810 		chg_item->value = ~chg_item->value;
811 	}
812 	chg_item->value &= 0xff;
813 
814 	return (1);
815 }
816 
817 
818 static void
819 add_new_change_list_item(struct chg_list *chg_item)
820 {
821 	struct chg_list	*cp;
822 
823 	if (change_list == NULL) {
824 		change_list = chg_item;
825 	} else {
826 		for (cp = change_list; cp->next != NULL; cp = cp->next)
827 			;
828 		cp->next = chg_item;
829 	}
830 	chg_item->next = NULL;
831 }
832 
833 
834 static void
835 free_change_list(void)
836 {
837 	struct chg_list	*cp;
838 	struct chg_list	*cp2;
839 
840 	cp = change_list;
841 	while (cp != NULL) {
842 		cp2 = cp->next;
843 		destroy_data((char *)cp);
844 		cp = cp2;
845 	}
846 	change_list = NULL;
847 }
848 
849 
850 static void
851 do_default(char *input)
852 {
853 	char		*s = input;
854 	char		*p;
855 	int		n;
856 
857 	/*
858 	 * Reset current page indicator
859 	 */
860 	current_page = -1;
861 
862 	/*
863 	 * Skip the leading "default" command, which we
864 	 * must have, or we wouldn't have come here,
865 	 * and any white space.
866 	 */
867 	while (isspace(*s)) {
868 		s++;
869 	}
870 
871 	while (*s && isascii(*s) && isalpha(*s)) {
872 		s++;
873 	}
874 
875 	while (isspace(*s)) {
876 		s++;
877 	}
878 
879 	/*
880 	 * Subsequent modifier must be either "p<n>", or "all".
881 	 */
882 	if (*s == 'p') {
883 		s++;
884 		n = (int)strtol(s, &p, 0);
885 		if (p == s || *p != 0) {
886 			err_print("Syntax error: %s\n", input);
887 		} else {
888 			fmt_print("\n");
889 			(void) default_page(n);
890 			fmt_print("\n");
891 		}
892 	} else if (*s == 'a') {
893 		default_all_pages();
894 	} else {
895 		err_print("Syntax error: %s\n", input);
896 	}
897 }
898 
899 
900 static void
901 default_all_pages(void)
902 {
903 	char			*p;
904 	struct mode_header	*mh;
905 	struct mode_page	*mp;
906 	int			n;
907 	struct uscsi_cmd	ucmd;
908 	union scsi_cdb		cdb;
909 	char			msbuf[MAX_MODE_SENSE_SIZE];
910 	int			nbytes = sizeof (msbuf);
911 	int			status;
912 
913 	/*
914 	 * Build and execute the uscsi ioctl.  Note that
915 	 * we cannot simply call uscsi_mode_sense() here,
916 	 * since that function attempts to valididate the
917 	 * returned data, and the page 0x3f has a unique
918 	 * format.
919 	 */
920 	nbytes = MAX_MODE_SENSE_SIZE;
921 	(void) memset(msbuf, 0, nbytes);
922 	(void) memset((char *)&ucmd, 0, sizeof (ucmd));
923 	(void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
924 	cdb.scc_cmd = SCMD_MODE_SENSE;
925 	FORMG0COUNT(&cdb, (uchar_t)nbytes);
926 	cdb.cdb_opaque[2] = MODE_SENSE_PC_DEFAULT | 0x3f;
927 	ucmd.uscsi_cdb = (caddr_t)&cdb;
928 	ucmd.uscsi_cdblen = CDB_GROUP0;
929 	ucmd.uscsi_bufaddr = msbuf;
930 	ucmd.uscsi_buflen = nbytes;
931 	status = uscsi_cmd(cur_file, &ucmd, (option_msg) ? F_NORMAL : F_SILENT);
932 	if (status) {
933 		if (!option_msg) {
934 			err_print("\nMode sense page 0x3f failed\n");
935 		}
936 		return;
937 	}
938 
939 	fmt_print("\n");
940 
941 	/*
942 	 * Now parse the page 0x3f
943 	 */
944 	mh = (struct mode_header *)msbuf;
945 	nbytes = mh->length - sizeof (struct mode_header) -
946 	    mh->bdesc_length + 1;
947 	p = msbuf + sizeof (struct mode_header) + mh->bdesc_length;
948 
949 	while (nbytes > 0) {
950 		mp = (struct mode_page *)p;
951 		n = mp->length + sizeof (struct mode_page);
952 		nbytes -= n;
953 		if (nbytes < 0)
954 			break;
955 		if (default_page(mp->code) == 0) {
956 			goto error;
957 		}
958 		p += n;
959 	}
960 
961 	if (nbytes < 0) {
962 		err_print("Mode sense page 0x3f formatted incorrectly:\n");
963 	}
964 error:
965 	fmt_print("\n");
966 }
967 
968 
969 static int
970 default_page(int pageno)
971 {
972 	struct scsi_ms_header	header;
973 	char			saved[MAX_MODE_SENSE_SIZE];
974 	char			current[MAX_MODE_SENSE_SIZE];
975 	char			dfault[MAX_MODE_SENSE_SIZE];
976 	struct mode_page	*sp;
977 	struct mode_page	*cp;
978 	struct mode_page	*dp;
979 	int			length;
980 	int			flags;
981 	int			i;
982 	int			need_mode_select;
983 
984 	/*
985 	 * Get default mode sense
986 	 */
987 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_DEFAULT,
988 	    dfault, MAX_MODE_SENSE_SIZE, &header)) {
989 		err_print("Mode sense on page %x (dfault) failed\n", pageno);
990 		return (0);
991 	}
992 
993 	/*
994 	 * Get the current mode sense.
995 	 */
996 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_CURRENT,
997 	    current, MAX_MODE_SENSE_SIZE, &header)) {
998 		err_print("Mode sense on page %x (current) failed\n", pageno);
999 		return (0);
1000 	}
1001 
1002 	/*
1003 	 * Get saved mode sense.  If this fails, assume it is
1004 	 * the same as the current.
1005 	 */
1006 	if (uscsi_mode_sense(cur_file, pageno, MODE_SENSE_PC_SAVED,
1007 	    saved, MAX_MODE_SENSE_SIZE, &header)) {
1008 		(void) memcpy(saved, current, MAX_MODE_SENSE_SIZE);
1009 	}
1010 
1011 	/*
1012 	 * Determine if we need a mode select on this page.
1013 	 * Just deal with the intersection of the three pages.
1014 	 */
1015 	sp = (struct mode_page *)saved;
1016 	cp = (struct mode_page *)current;
1017 	dp = (struct mode_page *)dfault;
1018 	length = min(MODESENSE_PAGE_LEN(sp), MODESENSE_PAGE_LEN(cp));
1019 	length = min(length, MODESENSE_PAGE_LEN(dp));
1020 
1021 	need_mode_select = 0;
1022 	for (i = 2; i < length; i++) {
1023 		if (current[i] != dfault[i] || saved[i] != dfault[i]) {
1024 			current[i] = dfault[i];
1025 			need_mode_select = 1;
1026 		}
1027 	}
1028 
1029 	if (need_mode_select == 0) {
1030 		fmt_print("Defaulting page 0x%x: ok\n", pageno);
1031 		return (1);
1032 	}
1033 
1034 	/*
1035 	 * A change was made.  Do a mode select
1036 	 * We always want to set the Page Format bit.
1037 	 * Set the Save Page bit if the drive indicates
1038 	 * that it can save this page.
1039 	 */
1040 	length = MODESENSE_PAGE_LEN(cp);
1041 	flags = MODE_SELECT_PF;
1042 	if (cp->ps) {
1043 		flags |= MODE_SELECT_SP;
1044 	}
1045 	cp->ps = 0;
1046 	header.mode_header.length = 0;
1047 	header.mode_header.device_specific = 0;
1048 	if (uscsi_mode_select(cur_file, pageno, flags,
1049 	    current, length, &header)) {
1050 		/*
1051 		 * Failed - try not saving parameters,
1052 		 * if possible.
1053 		 */
1054 		if (flags & MODE_SELECT_SP) {
1055 			flags &= ~MODE_SELECT_SP;
1056 			if (uscsi_mode_select(cur_file, pageno, flags,
1057 			    saved, length, &header)) {
1058 				fmt_print("Defaulting page 0x%x: failed\n",
1059 				    pageno);
1060 			} else {
1061 				fmt_print("Defaulting page 0x%x: ", pageno);
1062 				fmt_print("cannot save page permanently\n");
1063 			}
1064 		} else {
1065 			fmt_print("Defaulting page 0x%x: ", pageno);
1066 			fmt_print("cannot save page permanently\n");
1067 		}
1068 	} else {
1069 		fmt_print("Defaulting page 0x%x: mode select ok\n", pageno);
1070 	}
1071 
1072 	return (1);
1073 }
1074