/*

	This is a quick hack to format MFM/RLL drives that are being
	used with Adaptec's acb-40XX bridge boards. These are bridge
	boards that convert SCSI --> RLL/MFM. The acb-4070 is the RLL
	version, and the acb-4000 is the MFM version. Note that the 
	RLL version uses 26 sectors/track and the MFM version uses
	18 (not 17!) sectors/track.

	Note that the acb-40XX holds the SCSI bus for the entire
	duration of the format command (> 15 minutes for big drive!)
	and Linux will not like this. Linux will try and rest the
	device, and then the SCSI bus, which will kill the format.
	We have to disable this in the kernel for the format to work.
	See the README for more details.

	This code is a hack derived from Eric Youngdale's code
	in "scsiinfo.c" -- all credit goes to him, and all flames
	to me  :-(

	You will *have* to edit "geometry.h" to tell the program
	what your drive parameters are. It is set up to fail the
	compile stage if you don't.

	Good luck with it.
	Paul.
*/

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "geometry.h"

int put_mode_page(void);
int do_rezero(void);
int do_format(void);
void dump( void *buffer, unsigned int length );

FILE *infile;
unsigned char buffer[10*1024];
unsigned char buffer1[10*1024];

#define MODE_SELECT	0x15
#define FORMAT		0x04
#define REZERO		0x01

void dump( void *buffer, unsigned int length )
{
  unsigned int i;

  for (i=0; i<length; i++)
  {
      printf( "%02x ", (unsigned int) ((unsigned char *) buffer)[i] );
    if (i%16 == 15) { printf( "\n" ); }
  }
  printf( "\n" );

}

int put_mode_page()
{
  int status, i;
  int pagelen, pagelen1;
  unsigned char *cmd;

  for (i=0; i<10*1024; i++)
    {
      buffer1[i] = 0;
    }
  
  pagelen = 0x16;
  pagelen1 = 1024;

  *( (int *)  buffer1 )		= pagelen;	/* length of input data */
  *( ((int *) buffer1) + 1 )	= pagelen1;	/* length of output buffer */
  
  cmd = (char *) ( ((int *) buffer1) + 2 );
  
  cmd[0] = MODE_SELECT;
  cmd[1] = 0x00;			/* upper 3 bits = lun = 0 */
  cmd[2] = 0x00;
  cmd[3] = 0x00;			/* (reserved) */
  cmd[4] = 0x16;			/* data block size */
  cmd[5] = 0x00;			/* (reserved) */

/* Now the drive parameters */

  cmd[6+0] = 0x0;	/* Reserved */
  cmd[6+1] = 0x0;	/* Reserved */
  cmd[6+2] = 0x0;	/* Reserved */
  cmd[6+3] = 0x8;	/* Length of Extent Descriptor List */
  cmd[6+4] = 0x0;	/* Reserved */
  cmd[6+5] = 0x0;	/* Reserved */
  cmd[6+6] = 0x0;	/* Reserved */
  cmd[6+7] = 0x0;	/* Reserved */
  cmd[6+8] = 0x0;	/* Reserved */

  cmd[6+9] =  BLK_HI;
  cmd[6+10] = BLK_MID;
  cmd[6+11] = BLK_LO;

  cmd[6+12] = DISK_TYPE;

  cmd[6+13] = CYLS_HI;
  cmd[6+14] = CYLS_LO;

  cmd[6+15] = HEADS;

/*
 RWC and precomp not really needed for RLL acb-4070
 RWC and precomp ARE really needed for MFM acb-4000
*/

  cmd[6+16] = RWC_HI;
  cmd[6+17] = RWC_LO;

  cmd[6+18] = WPC_HI;
  cmd[6+19] = WPC_LO;

  cmd[6+20] = LZ_OFFSET;

  cmd[6+21] = STEP_CODE;
  
  status = ioctl( fileno(infile), 1 /* SCSI_IOCTL_SEND_COMMAND */, buffer1 );
  if(status) fprintf(stderr,"Unable to store mode page with drive parameters.");
  if(status) dump(buffer1, 48);

  return status;

}


int do_rezero()
{
  int status, i;
  unsigned char *cmd;

  for (i=0; i<10*1024; i++) buffer[i] = 0;
  
  *( (int *)  buffer )		= 0;	/* length of input data */
  *( ((int *) buffer) + 1 )	= 1024;	/* length of output buffer */

  cmd = (char *) ( ((int *) buffer) + 2 );
  
  cmd[0] = REZERO;
  cmd[1] = 0x00;			/* upper 3 bits = lun = 0 */
  cmd[2] = 0x00;			/* (reserved) */
  cmd[3] = 0x00;			/* (reserved) */
  cmd[4] = 0x00;			/* (reserved) */
  cmd[5] = 0x00;			/* (reserved) */

  status = ioctl( fileno(infile), 1 /* SCSI_IOCTL_SEND_COMMAND */, buffer );
  if(status) printf( "ioctl(SCSI_REZERO) status\t= %d\n", status );

  return status;

}

int do_format()
{
  int status, i;
  unsigned char *cmd;

  for (i=0; i<10*1024; i++)
  {
    buffer[i] = 0;
  }
  
  *( (int *)  buffer )          = 0;    /* length of input data */
  *( ((int *) buffer) + 1 )     = 1024; /* length of output buffer */

  cmd = (char *) ( ((int *) buffer) + 2 );

  cmd[0] = FORMAT;       
  cmd[1] = 0x00;                        /* lun=0,  no defects */
  cmd[2] = 0x00;                        /* (reserved) */
  cmd[3] = 0x00;                        /* high byte interleave */
  cmd[4] = 0x01;                        /* low byte */
  cmd[5] = 0x00;                        /* (reserved) */

/* 
Note that the interleave should always be 1, because the acb-40XX
has a 1k buffer onboard. 
*/

  status = ioctl( fileno(infile), 1 /* SCSI_IOCTL_SEND_COMMAND */, buffer );
  if(status) printf("ioctl(SCSI_FORMAT) status\t= %d\n", status );

  return status;

}

int main( int argc, char *argv[])
{
  int i;

  if (argc!=2) {
	fprintf(stderr,"Usage: \"format /dev/sdn\" where n={a,b,c,d...}\n");
	exit(1);
  }

  infile = fopen( argv[1], "r" );
  if(!infile) {
  	perror(argv[1]);
  	exit(1);
  }

  printf("Re-zeroing unit.\n");
  i=do_rezero();
  if (i) {
	fprintf(stderr,"Error trying to re-zero unit.\n");
	exit(i);
  }

  sleep(1);

  printf("Storing drive geometry on track zero.\n");
  i=put_mode_page();
  if (i) {
	fprintf(stderr,"Error trying to write drive geometry to disk.\n");
	exit(i);
  }

  sleep(1);

  fprintf(stderr,"Formatting with 0x6C data pattern. (This may take a while...)\n");
  i=do_format();
  if (i) {
	fprintf(stderr,"Error while formatting disk.");
	exit(i);
  } 

  return 0;
}


