File:  [Power 6/32 Unix Tahoe 4.2BSD] / cci / sys / stand / vdformat.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Sun Jul 28 12:24:19 2019 UTC (7 years ago) by root
Branches: bsd, MAIN
CVS tags: v12b, HEAD
Power 6/32 Unix version 1.2b

/*
** format disk on VDDC / SMD_E controller - (fsd/smd/xfd/xsd) type 
**
** Author:	John R. Franks
**
**	This program is used to maintain drives attached to a VDDC controller.
** The basic functions are 1) format media,  2) verify media, 3) relocate
** bad blocks that have appeared on the media, and 4) Format and initialize the
** maintenance cylinders on the drive.
**
**	For in depth information on the structure of this program please refer
** to the comments above the individual subroutines in addition to the design
** notes in vdutil.doc.
*/

#include	<setjmp.h>
#include	"../machine/mtpr.h"
#include	"../h/param.h"
#include	"../h/inode.h"
#include	"../h/fs.h"
#include	"../vba/vddc.h"
#include	"../stand/saio.h"

#define	MAXCONTROLLER	4
#define	MAXDRIVE	16

#define	NUMMNT		2
#define	NUMREL		3
#define	NUMSYS		(NUMREL+NUMMNT)

#define	TRUE		1
#define	FALSE	0

#define	MAXTRKS		24
#define	MAXSECS_PER_TRK	48
#define	MAXERR		1000
#define SECSIZ		512
#define	TRKSIZ		((SECSIZ/sizeof(long)) * MAXSECS_PER_TRK)

#define	HARD_ERROR	(DRVNRDY | INVDADR | DNEMEM | PARERR | OPABRT | \
			 WPTERR | DSEEKERR | NOTCYLERR)
#define DATA_ERROR	(CTLRERR | UCDATERR | DCOMPERR | DSERLY | DSLATE | \
			 TOPLUS | TOMNUS | CPDCRT | \
			 HRDERR | SFTERR)
#define HEADER_ERROR	(HCRCERR | HCMPERR)
#define	NRM		(short)0
#define	BAD		(short)VDUF
#define WPT		(short)(NRM | VDWPT)
#define RELOC_SECTOR	(short)(VDALT)
#define	ALT_SECTOR	(short)(VDALT)

static jmp_buf	environ;

/* Free bad block allocation bit map */
typedef struct {
	long	error;
	enum	{ALLOCATED, NOTALLOCATED} freestatus;
} fmt_free;



typedef enum {SINGLE_SECTOR, FULL_TRACK} rel_type;

typedef struct {
	dskadr	err_adr;
	long	err_stat;
} fmt_err;

static fmt_free	free[NUMREL*MAXTRKS][MAXSECS_PER_TRK];
static fmt_err	dsk_err[MAXERR];/* Disk error table */
static fmt_mdcb	mdcb;		/* Master device control block */
static fmt_dcb	dcb;		/* Device control blocks */
static int	bad_secs = 0;	/* Sequence # of last error detected */
static cdr	*controller_address;	/* controller physical address */
static int	ctlr_num = 0;	/* ctlr number */
static int	unit_number = 0;	/* unit number */
static int	drive_type;	/* disk type index */
static char	*drive_name;
static int	verify_count = 16;
static char	*stars = "***************";
static char	*sure_question = "This is DESTRUCTIVE!  Are you sure";
static char	*operation_string = 0;
static char	*format_string = "Formatting";
static char	*scan_string = "Scanning";
static char	*relocate_string = "Relocation";
static char	drives_to_do[MAXCONTROLLER][MAXDRIVE];
static char	ctlr_type[MAXCONTROLLER] =
		    {UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN};
static char	*controller_name[MAXCONTROLLER] =
		    {"Unknown","Unknown","Unknown","Unknown"};

static long	pattern_0[TRKSIZ],  pattern_1[TRKSIZ];
static long	pattern_2[TRKSIZ],  pattern_3[TRKSIZ];
static long	pattern_4[TRKSIZ],  pattern_5[TRKSIZ];
static long	pattern_6[TRKSIZ],  pattern_7[TRKSIZ];
static long	pattern_8[TRKSIZ],  pattern_9[TRKSIZ];
static long	pattern_10[TRKSIZ], pattern_11[TRKSIZ];
static long	pattern_12[TRKSIZ], pattern_13[TRKSIZ];
static long	pattern_14[TRKSIZ], pattern_15[TRKSIZ];

static long	*pattern_address[] = {
	pattern_0,  pattern_1,  pattern_2,  pattern_3,
	pattern_4,  pattern_5,  pattern_6,  pattern_7,
	pattern_8,  pattern_9,  pattern_10, pattern_11,
	pattern_12, pattern_13, pattern_14, pattern_15
};

/* Double buffer for scanning existing file systems and general scratch */
static long	scratch[TRKSIZ];
static long	save[TRKSIZ];

static long	max_cyl;
static long	max_trk;
static long	max_sec;
static long	sector_size;
static long	num_slip;
static int	max_drive;

/*
**	Main, of course, is the entry point into vdfmt.  It is responsible
** for calling the routine for the get the disk parameters from the user,
** creating and initializing the bad block patterns for scanning the disk, and
** determining whether or not the user wants to format or relocate bad
** blocks before calling the appropriate routines.
**
**	Formatting implies cerification of media and creation of the maintenance
** cylinders and relocation of bad blocks.
*/

main()
{
	register int	index;
	register int	(*operation)();
	register int	controller, drive;
	cdr		*controller_address;
	int		(*get_operation_type())();

	printf("VDFORMAT  Version 2.\n\n");
	/* Identify which controllers are present and what type they are. */
	for(controller = 0; controller < MAXCONTROLLER; controller++) {
		controller_address = (cdr *)(vddcaddr[controller]|IOBASE);
		if(!badaddr(controller_address, 2)) {
			controller_address->cdr_reset = 0xffffffff;
			DELAY(1000000);
			if(controller_address->cdr_reset != 0xffffffff) {
				ctlr_type[controller] = SMDCTLR;
				controller_name[controller] = "SMD";
				DELAY(1000000);
			}
			else {
				ctlr_type[controller] = SMD_ECTLR;
				controller_name[controller] = "SMD/E";
				controller_address->cdr_reserved = 0x0;
				DELAY(3000000);
			}
			printf("Controller #%d is an %s disk controller.\n",
			    controller, controller_name[controller]);
		}
		else
			ctlr_type[controller] = UNKNOWN;
	}
	/* Get operation type */
	operation = get_operation_type();
	/* Read user input about disk parameters */
	get_disk_parameters();
	for(controller=0; controller<MAXCONTROLLER; controller++) {
		max_drive = (ctlr_type[controller] == SMDCTLR) ? 4 : 16;
		for(drive=0; drive<max_drive; drive++)
			if(drives_to_do[controller][drive] != -1)
				do_operation(controller, drive, operation);
	}
}

/*
**	Get_operation_type allows the user to choose which operation he / she
** wants to perform on all the drives selected.  It returns the address of that
** function.
*/

int (*get_operation_type())()
{
	extern int	format_operation();
	extern int	scan_operation();
	extern int	relocate_operation();

	for(;;) {
		if(get_yes_no("Do you want to format media"))
			if(get_yes_no(sure_question)) {
				get_number_of_patterns();
				operation_string = format_string;
				return	format_operation;
			}
		if(get_yes_no("Do you want to scan an existing file system for bad sectors")) {
			get_number_of_patterns();
			operation_string = scan_string;
			return scan_operation;
		}
		if(get_yes_no("Do you want to add bad sectors from manufacturer's list")) {
			operation_string = relocate_string;
			return relocate_operation;
		}
		printf("\nAn operation must be specified!\n\n");
	}
}

/*
**	Get_number_of_patterns reads and validates and sets the number of
** patterns to use during a verification operation.  If no string was typed
** then the number of patterns defaults to four.
*/

get_number_of_patterns()
{
	char	count_string[10];

	for(;;) {
		count_string[0] = (char)0;
		printf("Number of patterns to use when verifying? [1-16]: (16) ");
		gets(count_string);
		if(count_string[0]) {
			sscanf(count_string, "%d", &verify_count);
			if ((verify_count >= 1) && (verify_count <= 16))
				break;
			printf("\nPattern count must be between 1 and 16.\n\n");
		}
		else {
			printf("\nDefaulting to 16 patterns.\n\n");
			verify_count = 16;
			break;
		}
	}
}

/*
**	This routine ask the user the controller number, unit number,
** and the drive type of the drive to be formatted.  When all information is
** gathered and correct, then, the open() system routine is called to initialize
** the controller and spin up drives if necessary.  If the open fails the whole
** process  is started over again.
*/

get_disk_parameters()
{
	register int	at_least_one_drive_specified = 0;
	register int	controller, drive, type;

	for(controller = 0; controller < MAXCONTROLLER; controller++) {
		max_drive = (ctlr_type[controller] == SMDCTLR) ? 4 : 16;
		for(drive = 0; drive < max_drive; drive++)
			drives_to_do[controller][drive] = -1;
	}
	printf("\nEnter controller number and drive number for each disk.\n");
	printf("Key <RETURN> to controller number prompt when through.\n");
	for(;;) {
		if((controller = get_controller_number()) != -1) {
			drive = get_drive_number(controller);
			type = get_drive_type(controller);
			printf("Is an '%s' drive on controller %d, unit %d ok",
			    vdst[type].type_name, controller, drive);
			if(get_yes_no("")) {
				drives_to_do[controller][drive] = type;
				at_least_one_drive_specified = 1;
			}
			else
				printf("\nEntry ignored.\n");
			continue;
		}
		if(at_least_one_drive_specified)
			break;
		printf("You must specify at least one controller number!\n");
	}
}

/*
**	Get_controller_number reads, validates and returns a controller number.
*/

get_controller_number()
{
	char	controller[20];
	int	controller_number;
	int	i, controller_printed = FALSE;

	for(;;) {
		printf("\nController number? [");
		for(i=0; i<MAXCONTROLLER; i++)
			if(ctlr_type[i] != UNKNOWN) {
				if(controller_printed)
					printf(", ");
				controller_printed = TRUE;
				printf("%d", i);
			}
		printf("]: ");
		gets(controller);
		if(!controller[0])
			return -1;
		if(!sscanf(controller, "%d", &controller_number)) {
			printf("\nResponse must start with a digit!\n");
			continue;
		}
		if ((controller_number >= 0) && (controller_number <= 3))
			if(ctlr_type[controller_number] != UNKNOWN)
				break;
		printf("\nController number %d is illegal!\n",
		    controller_number);
	}
	return controller_number;
}

/*
**	Get_drive_number reads, validates and returns a drive number.
*/

get_drive_number(controller)
int	controller;
{
	char	drive_string[20];
	int	drive, max_drive;

	for(;;) {
		max_drive = (ctlr_type[controller] == SMDCTLR) ? 3 : 15;
		printf("Unit number? [0-%d]: ", max_drive);
		gets(drive_string);
		if(!drive_string[0]) {
			printf("\nA drive number must be specified!\n\n");
			continue;
		}
		if(!sscanf(drive_string, "%d", &drive)) {
			printf("\nResponse must start with a digit!\n\n");
			continue;
		}
		if((drive >= 0) && (drive <= max_drive))
			break;
		printf("\nDrive number %d is illegal!\n\n", drive);
	}
	return drive;
}

/*
**	Do_operation executes the function wanted on all drive in the to_to
** list.
*/

do_operation(controller, drive, operation)
int	controller, drive;
int	(*operation)();
{
	int	status;

	printf("\n%s %s on controller %d, drive %d, started. %s\n\n",
	    stars, operation_string, controller, drive, stars);
	if(!(status = _setjmp(environ))) {
		drive_name = "unknown";
		init_environment_for_operation(controller, drive);
		(*operation)();
		printf("\n%s %s on controller %d, drive %d completed ok. %s\n",
		    stars, operation_string, controller, drive, stars);
		return;
	}
	printf("\n%s %s on controller %d, drive %d ABORTED!  %s\n",
	    stars, operation_string, controller, drive, stars);
	return;
}

/*
**	Init_environment_for_operation reset all the variables used by the
** system to suit the new drive type.
*/

init_environment_for_operation(controller, drive)
int	controller, drive;
{
	register int	index;

	ctlr_num = controller;
	controller_address = (cdr *)(vddcaddr[controller]+IOBASE);
	unit_number = drive;
	drive_type = drives_to_do[controller][drive];
	drive_name = vdst[drive_type].type_name;
	max_cyl = vdst[drive_type].ncyl;
	max_trk = vdst[drive_type].ntrak;
	max_sec = vdst[drive_type].nsec;
	sector_size = vdst[drive_type].secsize;
	num_slip = vdst[drive_type].nslip;
	bad_secs = 0;
	/* Init bad block pattern array */
	for(index=0; index<TRKSIZ; index++) {
		pattern_0[index] = vdst[drive_type].fmt_pat[0];
		pattern_1[index] = vdst[drive_type].fmt_pat[1];
		pattern_2[index] = vdst[drive_type].fmt_pat[2];
		pattern_3[index] = vdst[drive_type].fmt_pat[3];
		pattern_4[index] = vdst[drive_type].fmt_pat[4];
		pattern_5[index] = vdst[drive_type].fmt_pat[5];
		pattern_6[index] = vdst[drive_type].fmt_pat[6];
		pattern_7[index] = vdst[drive_type].fmt_pat[7];
		pattern_8[index] = vdst[drive_type].fmt_pat[8];
		pattern_9[index] = vdst[drive_type].fmt_pat[9];
		pattern_10[index] = vdst[drive_type].fmt_pat[10];
		pattern_11[index] = vdst[drive_type].fmt_pat[11];
		pattern_12[index] = vdst[drive_type].fmt_pat[12];
		pattern_13[index] = vdst[drive_type].fmt_pat[13];
		pattern_14[index] = vdst[drive_type].fmt_pat[14];
		pattern_15[index] = vdst[drive_type].fmt_pat[15];
	}
	spin_up_drive();
}

/*
**	Spin_up_drive starts the drives on a controller and waits around for
** the drive to spin up if it is not already spinning.
*/

spin_up_drive()
{
	VDDC_RESET(controller_address, ctlr_type[ctlr_num]);
	if(ctlr_type[ctlr_num] == SMD_ECTLR) {
		((cdr *)controller_address)->cdr_csr =  0;
		((cdr *)controller_address)->mdcb_tcf =  AM_ENPDA;
		((cdr *)controller_address)->dcb_tcf =  AM_ENPDA;
		((cdr *)controller_address)->trail_tcf =  AM_ENPDA;
		((cdr *)controller_address)->data_tcf =  AM_ENPDA;
		((cdr *)controller_address)->cdr_ccf =  CCF_STS | XMD_32BIT |
		    BSZ_16WRD | CCF_ERR | CCF_ENP | CCF_EPE | CCF_EDE | CCF_ECE;
	}
	access_with_no_trailer(INIT, 10);
	access_with_no_trailer(DIAG, 20);
	configure_drive(0);
}

/*
**	Access_with_no_trailer is used to perform controller functions which
** require no data movement.
*/

access_with_no_trailer(function, wait_time)
int	function, wait_time;
{
	dcb.opcode = function;		/* command */
	dcb.intflg = NOINT;
	dcb.nxtdcb = (fmt_dcb *)0;	/* end of chain */
	dcb.operrsta  = 0;
	dcb.devselect = (function == VDSTART) ? 0 : (char)unit_number;
	dcb.trailcnt = (char)0;
	mdcb.firstdcb = &dcb;
	mdcb.vddcstat = 0;
	VDDC_ATTENTION(controller_address, &mdcb, ctlr_type[ctlr_num]);	
	POLLTILLDONE(controller_address, &dcb, wait_time, ctlr_type[ctlr_num]);
	if(vdtimeout <= 0) {
		printf(" during startup operation.\n");
		_longjmp(environ, 1);
	}
	return dcb.operrsta;
}

/*
**	Configure_drive tells the controller what kind of drive is attached
** on a particular line.
*/

configure_drive(pass)
int	pass;
{
	dcb.opcode = RSTCFG;		/* command */
	dcb.intflg = NOINT;
	dcb.nxtdcb = (fmt_dcb *)0;	/* end of chain */
	dcb.operrsta  = 0;
	dcb.devselect = (char)unit_number;
	dcb.trail.rstrail.ncyl = max_cyl;
	dcb.trail.rstrail.nsurfaces = max_trk;
	if(ctlr_type[ctlr_num] == SMDCTLR)
		dcb.trailcnt = (char)2;
	else {
		dcb.trailcnt = (char)4;
		dcb.trail.rstrail.nsectors = max_sec;
		dcb.trail.rstrail.slip_sec = num_slip;
	}
	mdcb.firstdcb = &dcb;
	mdcb.vddcstat = 0;
	VDDC_ATTENTION(controller_address, &mdcb, ctlr_type[ctlr_num]);
	POLLTILLDONE(controller_address, &dcb, 5, ctlr_type[ctlr_num]);
	if(vdtimeout <= 0) {
		printf(" during drive configuration.\n");
		_longjmp(environ, 1);
	}
	if(dcb.operrsta & (NOTCYLERR | DRVNRDY)) {
		if(pass) {
			printf("\nDrive failed to start!\n\n");
			_longjmp(environ, -1);
		}
		access_with_no_trailer(VDSTART, (unit_number*6)+62);
		DELAY((unit_number * 5500000) + 62000000);
		configure_drive(1);
	}
}

/*
**	Get_drive_type reads a drive type, validates it and returns the
** validated drive type number to the calling routine.
*/

get_drive_type(controller)
register int	controller;
{
	char	disk[10];

	for(;;) {
		printf("Drive type? [fsd/smd/xfd");
		if(ctlr_type[controller] == SMD_ECTLR)
			printf("/fuj/xsd");
		printf("]: ");
		gets(disk);
		if (!strcmp(disk,"fsd"))
			return FSD;
		if (!strcmp(disk,"smd"))
			return SMD;
		if (!strcmp(disk,"xfd"))
			return XFD;
		if(ctlr_type[controller] == SMD_ECTLR) {
			if (!strcmp(disk,"fuj"))
				return FUJ;
			if (!strcmp(disk,"xsd"))
				return XSD;
		}
		printf("Illegal drive type '%s'\n", disk); 
	}
}

/*
**	Format_operation is the main routine for formatting a disk.
** Take note the the first operation must be format_data_area due to a micro-
** code bug in the controller.  While formatting sectors one sector count is
** lost every 64k sectors, so, on an fsd drive the last four sectors will
** not be formatted.  This is overcome by formatting the entire disk first, then
** formatting the maintenance area, which happens to cover the last n sectors
** that were lost.
*/

format_operation()
{
	/* Format relocation area and data area */
	format_data_area();
	/* Verify the reloaction area and data area */
	verify_data_area();
	if(bad_secs)
		relocate_bad_blocks();
	/* Format maintenance cylinders for field service */
	lay_maint_cylinders();
}

/*
**	Relocate_operation() prompts the user for bad block numbers and adds
** to the current bad block table.  This process continues until a null response
** is typed in when prompted for a bad block.  The user is asked to verify that
** the block number typed in is really the block that they wanted to relocate
** before the tables are updated.
*/

relocate_operation()
{
	long	old_blk;
	char	nxt_blk[20];
	dskadr	daddr;
	long	last_block = max_cyl * max_trk * max_sec;

	bad_secs = 0;
	printf("Just key <RETURN>  when through entering sector numbers.\n");
	while(bad_secs < MAXERR) {
		printf("Sector number? ");
		gets(nxt_blk);
		if(!nxt_blk[0])
			break;
		sscanf(nxt_blk, "%ld", &old_blk);
		if(old_blk >= last_block) {
			printf("\nBlock number must be between 0 and %d!\n\n",
			    last_block - 1);
			continue;
		}
		printf("Relocate sector #%ld", old_blk);
		if(get_yes_no("")) {
			daddr.sector = old_blk % max_sec;
			daddr.track = (old_blk / max_sec) % max_trk;
			daddr.cylinder = (old_blk / max_sec) / max_trk;
			flag_sector(&daddr, 0);
		}
		else
			printf("\nEntry ignored.\n");
		printf("\n");
	}
	relocate_bad_blocks();
}

/*
**
*/

scan_operation()
{
	register int	status, j=0;
	dskadr		daddr;
	register int	old_verify_count = verify_count;

	bad_secs = 0;
	daddr.sector = 0;
	for(daddr.cylinder=0;daddr.cylinder<(max_cyl-NUMSYS);daddr.cylinder++) {
		if(j++ % 50) printf(".");
		else printf("\n    Scanning cylinder %d.", (int)daddr.cylinder);
		for(daddr.track = 0; daddr.track < max_trk; daddr.track++) {
			status = access(save, &daddr , FTR, 1, 1);
			if(status & (HRDERR | SFTERR))
				verify_count = 16;
			verify_track(&daddr);
			status = access(save, &daddr , FTW, 1, 1);
			verify_count = old_verify_count;
		}
	}
	printf("\n");
	print_bad_sector_list();
	print_number_of_bad_sectors("file system");
	if(bad_secs)
		relocate_bad_blocks();
}

/*
**	This routine formats the user data area and bad block area of the disk.
*/

format_data_area()
{
	register long		sector_count;
	dskadr			zero;
	register int		index;

	printf("Formatting media...\n");
	zero.cylinder = (short)0;
	zero.track = (char)0;
	zero.sector = (char)0;
	sector_count = (long)(max_cyl * max_trk * max_sec);
	format_sectors(&zero, &zero, NRM, sector_count);
	printf("Formatting complete.\n");
}

/*
**	This routine creates a bad block relocation area on disk and then
** certifies the user data area of the disk.
*/

verify_data_area()
{
	printf("Scanning for bad sectors...\n");
	init_relocation_area();
	verify_user_data_area();
	printf("\nBad block scan complete.\n");
}

/*
**	Vdinit_relocation_area certifies the bad block relocation area and flags
** each bad block in the area as bad for later use by load_free_table().  No
** relocation takes place.
*/

init_relocation_area()
{
	printf("  Verifying bad sector relocation area...\n");
	verify_cylinders((int)max_cyl - NUMSYS, NUMREL);
	print_number_of_bad_sectors("relocation");
	if(bad_secs)
		mark_sectors_as_bad();
	printf("  Relocation area verified.\n");
}

/*
**	This routine certifies the area to be used for user data.
*/

verify_user_data_area()
{
	int	temp;
	
	printf("  Verifying file system area...\n");
	verify_cylinders(0, (int)(max_cyl-NUMSYS));
	print_number_of_bad_sectors("file system");
	printf("  File system area verified.\n");
}

/*
**	Vdlay_maint_cylinders creates the maintenance cylinders for field
** service techs to run diagnostics on.
*/

lay_maint_cylinders()
{
	dskadr		daddr;
	register long	*pattern_ptr = (long *)&daddr;
	register int	index;
	
	printf("Creating maintenance cylinders...\n");
	/* format read only cylinder */
	daddr.cylinder = (short)(max_cyl - NUMMNT);
	daddr.track = daddr.sector = (char)0;
	format_sectors(&daddr, &daddr, NRM, (long)(NUMMNT * max_trk * max_sec));
	printf("  Verifying maintenance area...\n");
	verify_cylinders(daddr.cylinder, NUMMNT);
	print_number_of_bad_sectors("maintenance");
	printf("  Maintenance area verified.\n");
	printf("  Writing Read / Write patterns.\n");
	daddr.cylinder = (short)(max_cyl - NUMMNT);
	for(daddr.track=0; daddr.track<max_trk; daddr.track++)
		for(daddr.sector=0; daddr.sector<max_sec; daddr.sector++)
			if(is_bad(&daddr)) {
				extern rel_type	new_location();
				rel_type	type;
				dskadr		naddr;

				type = new_location(&daddr, &naddr,
				    dsk_err[index].err_stat);
				relocate(&daddr, &naddr, type);
			}
	printf("  Writing Read only patterns.\n");
	for(daddr.cylinder++; daddr.cylinder<max_cyl; daddr.cylinder++) {
		for(daddr.track=0; daddr.track<max_trk; daddr.track++)
			for(daddr.sector = 0; daddr.sector < max_sec;
			    daddr.sector++) {
				for(index=0; index<(sector_size/sizeof(long)); index++)
					scratch[index] = *pattern_ptr;
				if(is_bad(&daddr))
					mark_bad_sector(&daddr, (short)VDWPT);
				else
					format_sectors(&daddr,&daddr,WPT,(long)1);
			}
	}
	printf("Maintenance cylinders complete.\n");
}

/*
**	Is_bad checks to see if a block is known to be bad already.
*/

is_bad(daddr)
dskadr	*daddr;
{
	register int	index;

	for(index=0; index<bad_secs; index++)
		if(daddr->cylinder == dsk_err[index].err_adr.cylinder)
			if(daddr->track == dsk_err[index].err_adr.track)
				if(daddr->sector==dsk_err[index].err_adr.sector)
					return 1;
	return 0;
}

/*
**	verify_cylinders does full track certification for every track
** on the cylinder.  This is done for speed and minimal head movement.  If
** an error occurs on any single track the track is flagged for later
** verification by verify sectors.
*/

verify_cylinders(base_cyl, cyl_count)
int	base_cyl, cyl_count;
{
	register int	i, j = 0;
	dskadr		daddr;

	bad_secs = 0;
	/* verify each track of each cylinder */
	for (daddr.cylinder=base_cyl; daddr.cylinder<(base_cyl+cyl_count);
	    daddr.cylinder++) {
		if(j++ % 50) printf(".");
		else printf("\n    Scanning cylinder %d.", (int)daddr.cylinder);
		for (daddr.track = 0; daddr.track < max_trk; daddr.track++) {
			verify_track(&daddr);
		}
	}
	printf("\n");
	print_bad_sector_list();
}

/*
**	verify_track verifies a single track. If the full track write and
** compare operation fails then each sector is read individually to determin
** which sectors are really bad.  If a sector is bad it is flagged as bad by
** the verify sector routine.
*/

verify_track(daddr)
dskadr	*daddr;
{
	register int	index, i;
	register int	count;
	register long	before;
	register long	*after;
	register int	status;
	register long	offset = sector_size / sizeof(long);
	int		pattern_count = verify_count;

	daddr->sector = (char)0;
	access(pattern_address[0], daddr, FTW, 1, 1);
	for(index = 0; index < pattern_count; index++) {
		data_ok(dcb.operrsta);
		if(dcb.operrsta & HEADER_ERROR)  {
			if(ctlr_type[ctlr_num]==SMDCTLR)
				goto hard;
			flag_sector(daddr, dcb.operrsta);
			break;
		}
		if(status & DATA_ERROR)
			pattern_count = 16;
		status = access(scratch, daddr, FTR, 1, 1);
		if(status & HEADER_ERROR) {
			if(ctlr_type[ctlr_num]==SMDCTLR)
				goto hard;
			flag_sector(daddr, status);
			break;
		}
		if(!data_ok(status)) {
			pattern_count = 16;
			for(i = 0; i < max_sec; i++) {
				register long	*next;

				daddr->sector = i;
				next = &scratch[i * offset];
				status = access(next, daddr, RD, 1, 1);
				if(!data_ok(status))
					flag_sector(daddr, status);
			}
			daddr->sector = (char)0;
		}
		if(index+1 < pattern_count)
			access(pattern_address[index+1], daddr, FTW, 1, 0);
		count = max_sec * offset;
		before = *pattern_address[index];
		after = scratch;
		mtpr(0, PADC);
		for(i=0; i<count; i++) {
			if(before != *(after++)) {
				daddr->sector = i / offset;
				flag_sector(daddr, 0);
			}
		}
		POLLTILLDONE(controller_address, &dcb, 60, ctlr_type[ctlr_num]);
		if(vdtimeout <= 0) {
			printf(" in verify_track.\n");
			_longjmp(environ, 1);
		}
	}
	return;

hard:	printf("\nSMD Controllers can't recover from header errors!");
	printf("  Status = 0x%x\n", dcb.operrsta);
	_longjmp(environ, dcb.operrsta);
}

/*
**	Vdflag_sector makes an entry into the bad block table for the current
** bad sector.
*/

flag_sector(daddr, status)
dskadr	*daddr;
long	status;
{
	register int	index;

	if(bad_secs < MAXERR) {
		for(index=0; index<bad_secs; index++)
			if((dsk_err[index].err_adr.cylinder==daddr->cylinder) &&
			    (dsk_err[index].err_adr.track == daddr->track) &&
			    (dsk_err[index].err_adr.sector==daddr->sector))
						return;
		dsk_err[bad_secs].err_adr.cylinder = daddr->cylinder;
		dsk_err[bad_secs].err_adr.track = daddr->track;
		dsk_err[bad_secs].err_adr.sector = daddr->sector;
		dsk_err[bad_secs++].err_stat = status;
		return;
	}
	printf("Maximum number of %d bad tracks exceeded!\n", MAXERR);
	_longjmp(environ, MAXERR);
}

/*
**	Print_bad_sector list tells the user which sectors are bad.
*/

print_bad_sector_list()
{
	register int	index;
	register int	sec;
	dskadr	daddr;

	if(bad_secs) {
		printf("    The following sector");
		if(bad_secs == 1)
			printf(" is");
		else
			printf("s are");
		printf(" bad:\n");
		for(index=0; index<bad_secs; index++) {
			sec = dsk_err[index].err_adr.cylinder * max_trk;
			sec += dsk_err[index].err_adr.track;
			sec *= max_sec;
			sec += dsk_err[index].err_adr.sector;
			printf("        %ld\n", sec);
		}
	}
}

/*
**	Print_number_of_bad_sectors is used to announce to the user the
** total number of bad sectors in a particular segment of the disk.  Rules
** of english for making a word plural are used to make a readable sentence.
*/

print_number_of_bad_sectors(str)
char	*str;
{
	if(bad_secs)
		printf("\n    %d", bad_secs);
	else
		printf("\n    No");
	printf(" bad sector");
	if(bad_secs != 1)
		printf("s");
	printf(" found in %s area.\n", str);
}

/*
**	Vdmark_sectors_as_bad marks every block in the current bad block
** table as bad on the disk.
*/

mark_sectors_as_bad()
{
	register int	index;
	dskadr	daddr;

	for(index=0; index<bad_secs; index++) {
		daddr.cylinder = dsk_err[index].err_adr.cylinder;
		daddr.track = dsk_err[index].err_adr.track;
		daddr.sector = dsk_err[index].err_adr.sector;
		mark_bad_sector(&daddr, (short)0);
	}
}

/*
**	mark_bad_sector marks a single sector, on disk, as bad using the bad
** block flags in the sector header.
*/

mark_bad_sector(daddr, flags)
dskadr	*daddr;
short	flags;
{
	format_sectors(daddr, daddr, BAD | flags, (long)1);
}


/*
**	relocate_bad_blocks scans the current disk error table and relocates
** every block that is flagged as bad in the table.  At the beginning of the
** routine the operator is given the chance to add blocks of his/her own to
** the table.
*/

relocate_bad_blocks()
{
	extern rel_type	new_location();
	register int	index;
	dskadr		daddr, naddr;
	rel_type	type;

	printf("Relocating bad sectors...\n");
	load_free_table();
	for(index=0; index<bad_secs; index++) {
		daddr.cylinder = dsk_err[index].err_adr.cylinder;
		daddr.track = dsk_err[index].err_adr.track;
		daddr.sector = dsk_err[index].err_adr.sector;
		type = new_location(&daddr, &naddr, dsk_err[index].err_stat);
		relocate(&daddr, &naddr, type);
	}
	printf("Relocation complete.\n");
}

/*
**	Vdrelocate commands the controller to relocate a block from daddr
** to naddr.  Once this has been done the controller will automatically perform
** relocation whenever data is transfered to or from daddr.
*/

relocate(daddr, naddr, type)
dskadr		*daddr, *naddr;
rel_type	type;
{
	if(type == FULL_TRACK)
		relocate_track(daddr, naddr);
	else
		relocate_sector(daddr, naddr);
}


/*
**
*/

relocate_sector(daddr, naddr)
dskadr		*daddr, *naddr;
{
	dskadr		phys, reloc;
	register long	blk;
	register long	status;

	access(save, daddr, RD, 1, 1);
	phys.cylinder = daddr->cylinder;
	phys.track = daddr->track;
	phys.sector = daddr-> sector;
	reloc.cylinder = naddr->cylinder;
	reloc.track = naddr->track;
	reloc.sector = naddr->sector;
	format_sectors(&phys, &reloc, RELOC_SECTOR, (long)1);
	
	phys.cylinder = naddr->cylinder;
	phys.track = naddr->track;
	phys.sector = naddr->sector;
	reloc.cylinder = daddr->cylinder;
	reloc.track = daddr->track;
	reloc.sector = daddr->sector;
	format_sectors(&phys, &reloc, ALT_SECTOR, (long)1);
	blk = (daddr->cylinder * max_trk) + daddr->track;
	blk = (blk * max_sec) + daddr->sector;
	printf("  Sector #%ld ", blk);
	status = access(save, daddr, WD, 1, 1);
	if((status & ALTACC) && !(status & HRDERR)) {
		blk = (naddr->cylinder * max_trk) + naddr->track;
		blk = (blk * max_sec) + naddr->sector;
		printf("relocated to sector #%d.\n", blk);
		return;
	}
	printf("was not relocated successfully!  Status = %lx\n", status);
}



/*
**
*/

relocate_track(daddr, naddr)
dskadr		*daddr, *naddr;
{
	dskadr		phys, reloc;
	register long	blk;
	register long	status;

	access(save, daddr, FTR, 1, 1);
	phys.cylinder = daddr->cylinder;
	phys.track = daddr->track;
	phys.sector = 0;
	reloc.cylinder = naddr->cylinder;
	reloc.track = naddr->track;
	reloc.sector = 0xff;
	format_sectors(&phys, &reloc, RELOC_SECTOR, max_sec);
	
	phys.cylinder = naddr->cylinder;
	phys.track = naddr->track;
	phys.sector = 0;
	reloc.cylinder = daddr->cylinder;
	reloc.track = daddr->track;
	reloc.sector = 0;
	format_sectors(&phys, &reloc, ALT_SECTOR, max_sec);
	blk = (daddr->cylinder * max_trk) + daddr->track;
	printf("  Track #%ld ", blk);
	status = access(save, daddr, FTW, 1, 1);
	if((status & ALTACC) && !(status & HRDERR)) {
		blk = (naddr->cylinder * max_trk) + naddr->track;
		printf("relocated to track #%d.\n", blk);
		return;
	}
	printf("was not relocated successfully!  Status = %lx\n", status);
}

/*
**	access is used by other routines to do reads and writes to the disk.
** The status of the read / write is returned to the caller for processing.
*/

access(buf, daddr, func, count, wait)
char	*buf;
dskadr	*daddr;
int	func, count, wait;
{
	dcb.opcode = func;		/* format sector command */
	dcb.intflg = NOINT;
	dcb.nxtdcb = (fmt_dcb *)0;	/* end of chain */
	dcb.operrsta  = 0;
	dcb.devselect = (char)unit_number;
	dcb.trailcnt = (char)(sizeof(trrw) / 4);
	dcb.trail.rwtrail.memadr = buf; 
	dcb.trail.rwtrail.wcount = count * (sector_size / sizeof(short));
	dcb.trail.rwtrail.disk.cylinder = daddr->cylinder;
	dcb.trail.rwtrail.disk.track = daddr->track;
	dcb.trail.rwtrail.disk.sector = daddr->sector;
	mdcb.firstdcb = &dcb;
	mdcb.vddcstat = 0;
	VDDC_ATTENTION(controller_address, &mdcb, ctlr_type[ctlr_num]);
	if(wait) {
		POLLTILLDONE(controller_address,&dcb,2*60,ctlr_type[ctlr_num]);
		if(vdtimeout <= 0) {
			printf(" in access.\n");
			_longjmp(environ, 1);
		}
	}
	return dcb.operrsta;
}

/*
**	Vdformat_sectors is used to do the actual formatting of a block.
*/

format_sectors(daddr, haddr, flags, count)
dskadr	*daddr, *haddr;
short	flags;
long	count;
{
	dcb.opcode = FSECT;		/* format sector command */
	dcb.intflg = NOINT;
	dcb.nxtdcb = (fmt_dcb *)0;	/* end of chain */
	dcb.operrsta  = 0;
	dcb.devselect = (char)unit_number;
	dcb.trailcnt = (char)(sizeof(trfmt) / 4);
	dcb.trail.fmtrail.addr = (char *)scratch; 
	dcb.trail.fmtrail.nsectors = count;
	dcb.trail.fmtrail.disk.cylinder = daddr->cylinder | flags;
	dcb.trail.fmtrail.disk.track = daddr->track;
	dcb.trail.fmtrail.disk.sector = daddr->sector;
	dcb.trail.fmtrail.hdr.cylinder = haddr->cylinder | flags;
	dcb.trail.fmtrail.hdr.track = haddr->track;
	dcb.trail.fmtrail.hdr.sector = haddr->sector;
	mdcb.firstdcb = &dcb;
	mdcb.vddcstat = 0;
	VDDC_ATTENTION(controller_address, &mdcb, ctlr_type[ctlr_num]);
	POLLTILLDONE(controller_address, &dcb,
	    ((count+849)/850)+120, ctlr_type[ctlr_num]);
	if(vdtimeout <= 0) {
		printf(" in format_sectors.\n");
		_longjmp(environ, 1);
	}
	if (!data_ok(dcb.operrsta)) {
		printf("Data error during format operation!\n");
		_longjmp(environ, dcb.operrsta);
	}
}

/*
**	Print_dcb() dumps the MDCB and DCB for diagnostic purposes.  This
** routine is called whenever a fatal error is encountered.
*/

printdcb(ptr)
register long	*ptr;
{
	register long	i;
	register long	trailer_count;

	printf("Dump of MDCB: ");
	for(i=0; i<4; i++)
		printf("  %lx", *(ptr+i));
	if(ptr = (long *)*ptr) {
		printf(" and DCB:");
		trailer_count = *(ptr+3) & 0xff;
		for(i=0; i<7+trailer_count; i++) {
			uncache(ptr+i);
			printf("  %lx", *(ptr+i));
		}
	}
	printf("\n");
	for(i=0; i<5000000; i++) ;
}


/*
**	Vdload_free_table checks each block in the bad block relocation area
** to see if it is used. If it is, the free relocation block table is updated.
*/

load_free_table()
{
	dskadr	daddr;
	register int	i, j;

	/* Clear free table before starting */
	for(i = 0; i < (max_trk * NUMREL); i++)
		for(j = 0; j < max_sec; j++) {
			free[i][j].freestatus = NOTALLOCATED;
			free[i][j].error = (long)0;
		}
	/* For each relocation cylinder */
	for(daddr.cylinder = max_cyl-NUMSYS;
	    daddr.cylinder < (max_cyl-NUMMNT); daddr.cylinder++)
		/* For each track on a cylinder */
		for(daddr.track = 0; daddr.track < max_trk; daddr.track++)
			/* For each sector on a track */
			for(daddr.sector=0;daddr.sector<max_sec;daddr.sector++)
				if(block_should_be_allocated(&daddr))
					allocate(&daddr, (long)0);
}

/*
**	block_should_be_allocated does a read header and data command into
** a local buffer.  The header address is then checked to see if the block
** has the relocation bit set or if the block was marked as bad. (either VDMF
** or VDUF or both bits set). In addition, if data data error is picked up
** during the read then the block should be allocated. If the block meets
** the above criteria then
*/

block_should_be_allocated(daddr)
dskadr	*daddr;
{
	/* return false if already replaced or either VDMF or VDUF is set */ 
	return (access(scratch, daddr, RD, 1, 1) & (HRDERR|SFTERR|ALTACC));
}

/*
**	allocate marks a replacement sector as used.
*/

allocate(daddr, status)
dskadr	*daddr;
long	status;
{
	register long	trk;

	trk = daddr->cylinder - (max_cyl - NUMSYS);
	trk *= max_trk;
	trk += daddr->track;
	free[trk][daddr->sector].freestatus = ALLOCATED;
	free[trk][daddr->sector].error = status;
}

/*
** 	Vdnew_location allocates a replacement block given a bad block address.
**  The algorithm is fairly simple; it simply searches for the first
**  free sector that has the same sector number of the bad sector.  If no sector
**  is found then the drive should be considered bad because of a microcode bug
**  in the controller that forces us to use the same sector number as the bad
**  sector for relocation purposes.  Using different tracks and cylinders is ok
**  of course.
*/

rel_type new_location(daddr, naddr, status)
dskadr	*daddr, *naddr;
long	status;
{
	register int	index, sec;

	if(status & (HEADER_ERROR)) {
		for(index = 0; index < (max_trk * NUMREL); index++) {
			for(sec=0; sec < max_sec; sec++) {
				if(free[index][sec].freestatus == ALLOCATED)
					break;
			}
			if(sec == max_sec) {
				for(sec = 0; sec < max_sec; sec++) {
					free[index][sec].freestatus = ALLOCATED;
					free[index][sec].error = status;
				}
				naddr->cylinder=index/max_trk+(max_cyl-NUMSYS);
				naddr->track = index % max_trk;
				naddr->sector = 0;
				return FULL_TRACK;
			}
		}
	}
	for(index = 0; index < (max_trk * NUMREL); index++)
		if(free[index][daddr->sector].freestatus != ALLOCATED) {
			free[index][daddr->sector].freestatus = ALLOCATED;
			free[index][daddr->sector].error = status;
			naddr->cylinder = index / max_trk + (max_cyl - NUMSYS);
			naddr->track = index % max_trk;
			naddr->sector = daddr->sector;
			return SINGLE_SECTOR;
		}	
	printf("Bad sector relocation area is full!");
	_longjmp(environ, daddr->sector);
}

/*
** 	data_ok checks an error status word for bit patterns
**  associated with error conditions from the VDDC controller.  If a hardware
**  error is present then the problem is reported on the console and the program
**  is halted.  If a data error is present the a zero is returned.
**  If everything is OK then a 1 is returned.
*/

data_ok(status)
long	status;
{
	if(status & HARD_ERROR){
		if(status & DRVNRDY)
			printf("\nDrive is not ready!");
		else if(status & INVDADR)
			printf("\nInvalid disk address issued!");
		else if(status & DNEMEM)
			printf("\nNon-existent memory error!");
		else if(status & PARERR)
			printf("\nMain memory parity error!");
		else if(status & OPABRT) 
			printf("\nCPU aborted operation!");
		else if(status & WPTERR)
			printf("\nDrive is write protected!");
		else if(status & DSEEKERR)
			printf("\nDisk seek error!");
		else
			printf("\nNot on cylinder error!");
		printf("    Status = %lx\n", status);
		_longjmp(environ, -1);
	}
	return (int)(!(status & DATA_ERROR));
}

/*
** 	Vdget_yes_no is used to ask simple yes or no questions.  The question
** prompt is supplied by the caller,  The question mark, possible responses,
** and the default response is printed at the end of the prompt.  The routine
** then reads the answer and returns a 1 if a 'y' is typed or no response was
** given, otherwise, a zero is returned.
*/

get_yes_no(str)
register char	*str;
{
	char	answer[80];

	for(;;) {
		printf("%s? [y/n] (n): ", str);
		gets(answer);
		if((answer[0] == 'Y') || (answer[0] == 'y'))
			return(TRUE);
		if((answer[0] == 'N') || (answer[0] == 'n'))
			return(FALSE);
		printf("\nA 'Y' (yes) or 'N' (no) must be typed!\n\n");
	}
}


unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.