/* * 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. */ /* * This file contains routines to analyze the surface of a disk. */ #include "global.h" #include "analyze.h" #include #include #include "misc.h" #include "defect.h" #include "label.h" #include "param.h" #include "checkdev.h" /* * These global variables control the surface analysis process. They * are set from a command in the defect menu. */ int scan_entire = 1; /* scan whole disk flag */ diskaddr_t scan_lower = 0; /* lower bound */ diskaddr_t scan_upper = 0; /* upper bound */ int scan_correct = 1; /* correct errors flag */ int scan_stop = 0; /* stop after error flag */ int scan_loop = 0; /* loop forever flag */ int scan_passes = 2; /* number of passes */ int scan_random = 0; /* random patterns flag */ uint_t scan_size = 0; /* sectors/scan operation */ int scan_auto = 1; /* scan after format flag */ int scan_restore_defects = 1; /* restore defect list after writing */ int scan_restore_label = 1; /* restore label after writing */ /* * These are summary variables to print out info after analysis. * Values less than 0 imply they are invalid. */ offset_t scan_cur_block = -1; /* current block */ int64_t scan_blocks_fixed = -1; /* # blocks repaired */ /* * This variable is used to tell whether the most recent surface * analysis error was caused by a media defect or some other problem. */ int media_error; /* error was caused by defect */ int disk_error; /* disk errors during analysis */ /* * These are the data patterns used if random patterns are not chosen. * They are designed to show pattern dependent errors. */ static unsigned int scan_patterns[] = { 0xc6dec6de, 0x6db6db6d, 0x00000000, 0xffffffff, 0xaaaaaaaa, }; #define NPATTERNS 5 /* number of predefined patterns */ /* * These are the data patterns from the SunFed requirements document. */ static unsigned int purge_patterns[] = { /* patterns to be written */ 0xaaaaaaaa, /* 10101010... */ 0x55555555, /* 01010101... == UUUU... */ 0xaaaaaaaa, /* 10101010... */ 0xaaaaaaaa, /* 10101010... */ }; static unsigned int alpha_pattern = 0x40404040; /* 10000000... == @@@@... */ /* Function prototypes */ #ifdef __STDC__ static int scan_repair(diskaddr_t bn, int mode); static int analyze_blocks(int flags, diskaddr_t blkno, uint_t blkcnt, unsigned data, int init, int driver_flags, int *xfercntp); static int handle_error_conditions(void); static int verify_blocks(int flags, diskaddr_t blkno, uint_t blkcnt, unsigned data, int driver_flags, int *xfercntp); #else /* __STDC__ */ static int scan_repair(); static int analyze_blocks(); static int handle_error_conditions(); static int verify_blocks(); #endif /* __STDC__ */ /* * This routine performs a surface analysis based upon the global * parameters. It is called from several commands in the defect menu, * and from the format command in the command menu (if post-format * analysis is enable). */ int do_scan(flags, mode) int flags, mode; { diskaddr_t start, end, curnt; int pass, needinit, data; uint_t size; int status, founderr, i, j; int error = 0; int pattern = 0; int xfercnt; /* * Check to be sure we aren't correcting without a defect list * if the controller can correct the defect. */ if (scan_correct && !EMBEDDED_SCSI && (cur_ops->op_repair != NULL) && (cur_list.list == NULL)) { err_print("Current Defect List must be initialized "); err_print("to do automatic repair.\n"); return (-1); } /* * Define the bounds of the scan. */ if (scan_entire) { start = 0; if (cur_label == L_TYPE_SOLARIS) { if (cur_ctype->ctype_flags & CF_SCSI) end = datasects() - 1; else end = physsects() - 1; } else if (cur_label == L_TYPE_EFI) { end = cur_parts->etoc->efi_last_lba; } } else { start = scan_lower; end = scan_upper; } /* * Make sure the user knows if we are scanning over a mounted * partition. */ if ((flags & (SCAN_PATTERN | SCAN_WRITE)) && (checkmount(start, end))) { err_print("Cannot do analysis on a mounted partition.\n"); return (-1); } /* * Make sure the user knows if we are scanning over a * partition being used for swapping. */ if ((flags & (SCAN_PATTERN | SCAN_WRITE)) && (checkswap(start, end))) { err_print("Cannot do analysis on a partition \ which is currently being used for swapping.\n"); return (-1); } /* * Check to see if any partitions used for svm, vxvm, ZFS zpool * or live upgrade are on the disk. */ if ((flags & (SCAN_PATTERN | SCAN_WRITE)) && (checkdevinuse(cur_disk->disk_name, (diskaddr_t)-1, (diskaddr_t)-1, 0, 0))) { err_print("Cannot do analysis on a partition " "while it in use as described above.\n"); return (-1); } /* * If we are scanning destructively over certain sectors, * we mark the defect list and/or label dirty so it will get rewritten. */ if (flags & (SCAN_PATTERN | SCAN_WRITE)) { if (cur_label == L_TYPE_SOLARIS) { if (start < (diskaddr_t)totalsects() && end >= (diskaddr_t)datasects()) { if (!EMBEDDED_SCSI) { cur_list.flags |= LIST_DIRTY; } if (cur_disk->disk_flags & DSK_LABEL) cur_flags |= LABEL_DIRTY; } } if (start == 0) { if (cur_disk->disk_flags & DSK_LABEL) cur_flags |= LABEL_DIRTY; } } /* * Initialize the summary info on sectors repaired. */ scan_blocks_fixed = 0; /* * Loop through the passes of the scan. If required, loop forever. */ for (pass = 0; pass < scan_passes || scan_loop; pass++) { /* * Determine the data pattern to use if pattern testing * is to be done. */ if (flags & SCAN_PATTERN) { if (scan_random) data = (int)mrand48(); else data = scan_patterns[pass % NPPATTERNS]; if (flags & SCAN_PURGE) { flags &= ~(SCAN_PURGE_READ_PASS | SCAN_PURGE_ALPHA_PASS); switch (pattern % (NPPATTERNS + 1)) { case NPPATTERNS: pattern = 0; if (!error) { fmt_print( "\nThe last %d passes were successful, running alpha pattern pass", NPPATTERNS); flags |= SCAN_PURGE_ALPHA_PASS; data = alpha_pattern; } else { data = purge_patterns[pattern]; pattern++; }; break; case READPATTERN: flags |= SCAN_PURGE_READ_PASS; default: data = purge_patterns[pattern]; pattern++; break; } } fmt_print("\n pass %d", pass); fmt_print(" - pattern = 0x%x", data); } else fmt_print("\n pass %d", pass); fmt_print("\n"); /* * Mark the pattern buffer as corrupt, since it * hasn't been initialized. */ needinit = 1; /* * Print the first block number to the log file if * logging is on so there is some record of what * analysis was performed. */ if (log_file) { pr_dblock(log_print, start); log_print("\n"); } /* * Loop through this pass, each time analyzing an amount * specified by the global parameters. */ xfercnt = 0; for (curnt = start; curnt <= end; curnt += size) { if ((end - curnt) < scan_size) size = end - curnt + 1; else size = scan_size; /* * Print out where we are, so we don't look dead. * Also store it in summary info for logging. */ scan_cur_block = curnt; nolog_print(" "); pr_dblock(nolog_print, curnt); nolog_print(" \015"); (void) fflush(stdout); disk_error = 0; /* * Do the actual analysis. */ status = analyze_blocks(flags, curnt, size, (unsigned)data, needinit, (F_ALLERRS | F_SILENT), &xfercnt); /* * If there were no errors, the pattern buffer is * still initialized, and we just loop to next chunk. */ needinit = 0; if (!status) continue; /* * There was an error. Check if surface analysis * can be continued. */ if (handle_error_conditions()) { scan_blocks_fixed = scan_cur_block = -1; return (-1); } /* * There was an error. Mark the pattern buffer * corrupt so it will get reinitialized. */ needinit = 1; /* * If it was not a media error, ignore it. */ if (!media_error) continue; /* * Loop 5 times through each sector of the chunk, * analyzing them individually. */ nolog_print(" "); pr_dblock(nolog_print, curnt); nolog_print(" \015"); (void) fflush(stdout); founderr = 0; for (j = 0; j < size * 5; j++) { i = j % size; disk_error = 0; status = analyze_blocks(flags, (curnt + i), 1, (unsigned)data, needinit, F_ALLERRS, NULL); needinit = 0; if (!status) continue; /* * There was an error. Check if surface analysis * can be continued. */ if (handle_error_conditions()) { scan_blocks_fixed = scan_cur_block = -1; return (-1); } /* * An error occurred. Mark the buffer * corrupt and see if it was media * related. */ needinit = 1; if (!media_error) continue; /* * We found a bad sector. Print out a message * and fix it if required. */ founderr = 1; if (scan_correct && (flags != SCAN_VALID)) { if (scan_repair(curnt+i, mode)) { error = -1; } } else err_print("\n"); /* * Stop after the error if required. */ if (scan_stop) goto out; } /* * Mark the pattern buffer corrupt to be safe. */ needinit = 1; /* * We didn't find an individual sector that was bad. * Print out a warning. */ if (!founderr) { err_print("Warning: unable to pinpoint "); err_print("defective block.\n"); } } /* * Print the end of each pass to the log file. */ enter_critical(); if (log_file) { pr_dblock(log_print, scan_cur_block); log_print("\n"); } scan_cur_block = -1; exit_critical(); fmt_print("\n"); /* * alternate the read and write for SCAN_VERIFY test */ if (flags & SCAN_VERIFY) { flags ^= SCAN_VERIFY_READ_PASS; } } out: /* * We got here either by giving up after an error or falling * through after all passes were completed. */ fmt_print("\n"); enter_critical(); /* * If the defect list is dirty, write it to disk, * if scan_restore_defects (the default) is true. */ if (!EMBEDDED_SCSI && (cur_list.flags & LIST_DIRTY) && (scan_restore_defects)) { cur_list.flags = 0; write_deflist(&cur_list); } /* * If the label is dirty, write it to disk. * if scan_restore_label (the default) is true. */ if ((cur_flags & LABEL_DIRTY) && (scan_restore_label)) { cur_flags &= ~LABEL_DIRTY; (void) write_label(); } /* * If we dropped down to here after an error, we need to write * the final block number to the log file for record keeping. */ if (log_file && scan_cur_block >= 0) { pr_dblock(log_print, scan_cur_block); log_print("\n"); } fmt_print("Total of %lld defective blocks repaired.\n", scan_blocks_fixed); /* * Reinitialize the logging variables so they don't get used * when they are not really valid. */ scan_blocks_fixed = scan_cur_block = -1; exit_critical(); return (error); } /* * This routine is called to repair a bad block discovered * during a scan operation. Return 0 for success, 1 for failure. * (This has been extracted out of do_scan(), to simplify it.) */ static int scan_repair(bn, mode) diskaddr_t bn; int mode; { int status; int result = 1; char *buf; int buf_is_good; int i; if (cur_ops->op_repair == NULL) { err_print("Warning: Controller does "); err_print("not support repairing.\n\n"); return (result); } buf = malloc(cur_blksz); if (buf == NULL) { err_print("Warning: no memory.\n\n"); return (result); } enter_critical(); /* * Determine if the error appears to be hard or soft. We * already assume there's an error. If we can get any * good data out of the sector, write that data back * after the repair. */ buf_is_good = 0; for (i = 0; i < 5; i++) { status = (*cur_ops->op_rdwr)(DIR_READ, cur_file, bn, 1, buf, F_SILENT, NULL); if (status == 0) { buf_is_good = 1; break; } } fmt_print("Repairing %s error on %llu (", buf_is_good ? "soft" : "hard", bn); pr_dblock(fmt_print, bn); fmt_print(")..."); status = (*cur_ops->op_repair)(bn, mode); if (status) { /* * If the repair failed, we note it and will return the * failure. However, the analysis goes on. */ fmt_print("failed.\n\n"); } else { /* * The repair worked. Write the good data we could * recover from the failed block, if possible. * If not, zero the block. In doing so, try to * determine if the new block appears ok. */ if (!buf_is_good) { bzero(buf, cur_blksz); fmt_print("Warning: Block %llu zero-filled.\n", bn); } else { fmt_print("ok.\n"); } status = (*cur_ops->op_rdwr)(DIR_WRITE, cur_file, bn, 1, buf, (F_SILENT | F_ALLERRS), NULL); if (status == 0) { status = (*cur_ops->op_rdwr)(DIR_READ, cur_file, bn, 1, buf, (F_SILENT | F_ALLERRS), NULL); } if (status) { fmt_print("The new block also appears defective.\n"); } fmt_print("\n"); /* * add the defect to the list and write the list out. * Also, kill the working list so it will get resynced * with the current list. * * For embedded scsi, we don't require a defect list. * However, if we have one, add the defect if the * list includes the grown list. If not, kill it * to force a resync if we need the list later. */ if (EMBEDDED_SCSI) { if (cur_list.list != NULL) { if (cur_list.flags & LIST_PGLIST) { add_ldef(bn, &cur_list); } else { kill_deflist(&cur_list); } } /* * The next "if" statement reflects the fix for * bug id 1026096 where format keeps adding the * same defect to the defect list. */ } else if (cur_ctype->ctype_flags & CF_WLIST) { kill_deflist(&cur_list); (*cur_ops->op_ex_cur)(&cur_list); fmt_print("Current list updated\n"); } else { add_ldef(bn, &cur_list); write_deflist(&cur_list); } kill_deflist(&work_list); /* Log the repair. */ scan_blocks_fixed++; /* return ok */ result = 0; } exit_critical(); free(buf); return (result); } /* * This routine analyzes a set of sectors on the disk. It simply returns * an error if a defect is found. It is called by do_scan(). */ static int analyze_blocks(flags, blkno, blkcnt, data, init, driver_flags, xfercntp) int flags, driver_flags, init; uint_t blkcnt; register unsigned data; diskaddr_t blkno; int *xfercntp; { int corrupt = 0; int status; register diskaddr_t i, nints; register unsigned *ptr = (uint_t *)pattern_buf; media_error = 0; if (flags & SCAN_VERIFY) { return (verify_blocks(flags, blkno, blkcnt, data, driver_flags, xfercntp)); } /* * Initialize the pattern buffer if necessary. */ nints = (diskaddr_t)blkcnt * cur_blksz / sizeof (int); if ((flags & SCAN_PATTERN) && init) { for (i = 0; i < nints; i++) *((int *)((int *)pattern_buf + i)) = data; } /* * Lock out interrupts so we can insure valid data will get * restored. This is necessary because there are modes * of scanning that corrupt the disk data then restore it at * the end of the analysis. */ enter_critical(); /* * If the disk data is valid, read it into the data buffer. */ if (flags & SCAN_VALID) { status = (*cur_ops->op_rdwr)(DIR_READ, cur_file, blkno, blkcnt, (caddr_t)cur_buf, driver_flags, xfercntp); if (status) goto bad; } /* * If we are doing pattern testing, write and read the pattern * from the pattern buffer. */ if (flags & SCAN_PATTERN) { /* * If the disk data was valid, mark it corrupt so we know * to restore it later. */ if (flags & SCAN_VALID) corrupt++; /* * Only write if we're not on the read pass of SCAN_PURGE. */ if (!(flags & SCAN_PURGE_READ_PASS)) { status = (*cur_ops->op_rdwr)(DIR_WRITE, cur_file, blkno, blkcnt, (caddr_t)pattern_buf, driver_flags, xfercntp); if (status) goto bad; } /* * Only read if we are on the read pass of SCAN_PURGE, if we * are purging. */ if ((!(flags & SCAN_PURGE)) || (flags & SCAN_PURGE_READ_PASS)) { status = (*cur_ops->op_rdwr)(DIR_READ, cur_file, blkno, blkcnt, (caddr_t)pattern_buf, driver_flags, xfercntp); if (status) goto bad; } } /* * If we are doing a data compare, make sure the pattern * came back intact. * Only compare if we are on the read pass of SCAN_PURGE, or * we wrote random data instead of the expected data pattern. */ if ((flags & SCAN_COMPARE) || (flags & SCAN_PURGE_READ_PASS)) { for (i = nints, ptr = (uint_t *)pattern_buf; i; i--) if (*ptr++ != data) { err_print("Data miscompare error (expecting "); err_print("0x%x, got 0x%x) at ", data, *((int *)((int *)pattern_buf + (nints - i)))); pr_dblock(err_print, blkno); err_print(", offset = 0x%llx.\n", (nints - i) * sizeof (int)); goto bad; } } /* * If we are supposed to write data out, do so. */ if (flags & SCAN_WRITE) { status = (*cur_ops->op_rdwr)(DIR_WRITE, cur_file, blkno, blkcnt, (caddr_t)cur_buf, driver_flags, xfercntp); if (status) goto bad; } exit_critical(); /* * No errors occurred, return ok. */ return (0); bad: /* * There was an error. If the data was corrupted, we write it * out from the data buffer to restore it. */ if (corrupt) { if ((*cur_ops->op_rdwr)(DIR_WRITE, cur_file, blkno, blkcnt, (caddr_t)cur_buf, F_NORMAL, xfercntp)) err_print("Warning: unable to restore original data.\n"); } exit_critical(); /* * Return the error. */ return (-1); } /* * This routine analyzes a set of sectors on the disk. It simply returns * an error if a defect is found. It is called by analyze_blocks(). * For simplicity, this is done as a separate function instead of * making the analyze_block routine complex. * * This routine implements the 'verify' command. It writes the disk * by writing unique data for each block; after the write pass, it * reads the data and verifies for correctness. Note that the entire * disk (or the range of disk) is fully written first and then read. * This should eliminate any caching effect on the drives. */ static int verify_blocks(int flags, diskaddr_t blkno, uint_t blkcnt, unsigned data, int driver_flags, int *xfercntp) { int status, i, nints; unsigned *ptr = (uint_t *)pattern_buf; nints = cur_blksz / sizeof (int); /* * Initialize the pattern buffer if we are in write pass. * Use the block number itself as data, each block has unique * buffer data that way. */ if (!(flags & SCAN_VERIFY_READ_PASS)) { for (data = blkno; data < blkno + blkcnt; data++) { for (i = 0; i < nints; i++) { *ptr++ = data; } } ptr = (uint_t *)pattern_buf; } /* * Only write if we're not on the read pass of SCAN_VERIFY. */ if (!(flags & SCAN_VERIFY_READ_PASS)) { status = (*cur_ops->op_rdwr)(DIR_WRITE, cur_file, blkno, blkcnt, (caddr_t)pattern_buf, driver_flags, xfercntp); if (status) goto bad; } else { /* * Only read if we are on the read pass of SCAN_VERIFY */ status = (*cur_ops->op_rdwr)(DIR_READ, cur_file, blkno, blkcnt, (caddr_t)pattern_buf, driver_flags, xfercntp); if (status) goto bad; /* * compare and make sure the pattern came back intact. */ for (data = blkno; data < blkno + blkcnt; data++) { for (i = 0; i < nints; i++) { if (*ptr++ != data) { ptr--; err_print("Data miscompare error " "(expecting 0x%x, got 0x%x) at ", data, *ptr); pr_dblock(err_print, blkno); err_print(", offset = 0x%x.\n", (ptr - (uint_t *)pattern_buf) * sizeof (int)); goto bad; } } } } /* * No errors occurred, return ok. */ return (0); bad: return (-1); } static int handle_error_conditions() { /* * Check if the errno is ENXIO. */ if (errno == ENXIO) { fmt_print("\n\nWarning:Cannot access drive, "); fmt_print("aborting surface analysis.\n"); return (-1); } /* * check for disk errors */ switch (disk_error) { case DISK_STAT_RESERVED: case DISK_STAT_UNAVAILABLE: fmt_print("\n\nWarning:Drive may be reserved "); fmt_print("or has been removed, "); fmt_print("aborting surface analysis.\n"); return (-1); case DISK_STAT_NOTREADY: fmt_print("\n\nWarning: Drive not ready, "); fmt_print("aborting surface analysis.\n"); return (-1); case DISK_STAT_DATA_PROTECT: fmt_print("\n\nWarning: Drive is write protected, "); fmt_print("aborting surface analysis.\n"); return (-1); default: break; } return (0); }