#if 0
#
# The code is in public domain, May-September 2002.
#
# RICOH Program Loader 8 (primarily DVD+RW units) for Linux 2.4 and
# upwards. So far has been tested with:
#	- HP dvd100i
#	- Ricoh MP5120A
#	- Philips DVDRW208
#	- HP dvd200i
#
# This is second version adding support for Program Loader 9 and
# firmware consistency check.
#
# The program never updates Program Loader code so that you should
# always be able to downgrade your firmware in case something goes
# wrong.
#
# - make sure you have no media in drive, optionally go to single user
#   mode;
# - run 'rpl8 /dev/cdrom firmware.image.bin';
# - observe the program backing up your current firmware and uploading
#   new one;
# - take system down and cycle the power;
#
# There is -dump-only option which only backs up firmware. Note that
# you might have to cycle the power even after -dump-only.
#
# "ide-scsi: Strange, packet command initiated yet DRQ isn't asserted"
# surrounded by other nasty messages logged on console is "normal."
#
/bin/sh << EOS
MODNAME=\`expr "/$0" : '\(.*[^/]\)/*$' : '.*/\(..*\)' : '\(.*\)\..*$'\`
case "`uname -s`" in
SunOS)	(set -x; g++ -fno-exceptions -O -o \$MODNAME "$0" -lvolmgt) ;;
*)	(set -x; g++ -fno-exceptions -O -o \$MODNAME "$0") ;;
esac
EOS
exit
#endif

#if defined(__linux)
#define _XOPEN_SOURCE 500
#endif

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#include <poll.h>
#include <signal.h>

#include "transport.hxx"

#define BUFFER_BLOCK	(16*1024)
#define FIRMWARE_SIZE	(1024*1024)
#define MAGIC_OFFSET	(32*1024)
#if FIRMWARE_SIZE%BUFFER_BLOCK!=0
#error "Invalid BUFFER_BLOCK"
#endif
#if MAGIC_OFFSET%BUFFER_BLOCK!=0
#error "Invalid BUFFER_BLOCK"
#endif

int main (int argc,char *argv[])
{ int			fd,in,out,off,j;
  struct stat		sb;
  char			dump[64],*meaning;
  unsigned char		*buffer,*firmware,sigfile[6],sigunit[6],ascq='\xFF';
  struct	{
	unsigned char	type,skip[7];
	char		vendor[8],ident[16],revision[4];
		}	inq;
  Scsi_Command		cmd;
  unsigned char		*sense=cmd.sense();

    if (argc<3)
    {	fprintf (stderr,"usage: %s /dev/cdrom "
			"[-[dump-only]|firmware.bin]\n",argv[0]);
	return 1;
    }

    if ((fd=open ("/dev/zero",O_RDWR)) < 0)
    {	fprintf (stderr,"unable to open(\"/dev/zero\"): "), perror(NULL);
	return 1;
    }

    buffer = (unsigned char *)mmap(NULL,BUFFER_BLOCK,PROT_READ|PROT_WRITE,
					MAP_PRIVATE,fd,0);
    if (buffer==MAP_FAILED)
    {	fprintf (stderr,"unable to anonymously mmap %d bytes: ",BUFFER_BLOCK),
	perror (NULL);
	return 1;
    }
    close (fd);

    if (!cmd.associate(argv[1]))
    {	fprintf (stderr,"%s: unable to open: ",argv[1]), perror (NULL);
	return 1;
    }

    cmd[0]=0x12;		// INQUIRY
    cmd[4]=sizeof(inq);
    cmd[5]=0;
    if (cmd.transport(READ,(unsigned char *)&inq,sizeof(inq)))
    {	perror("unable to INQUIRY, examine /var/log/message");
	return 1;
    }

    if ((inq.type&0x1F) != 5)
    {	fprintf (stderr,"\"%s\" is not a CD-ROM device\n",argv[1]);
	return 1;
    }

    cmd[0]=0x00;		// TEST UNIT READY
    cmd[5]=0x80;
    if (!cmd.transport())
    {	fprintf (stderr,"\"%s\" is ready, is media present?\n",argv[1]);
	return 1;
    }

    if (sense[12] != 0x3A)	// should reply with "NO MEDIA"
    {	fprintf (stderr,"\"%s\": unexpected ASC=%02xh/ASCQ=%02xh\n",
			argv[1],sense[12],sense[13]);
	return 1;
    }

    sprintf (dump,"%.8s%.16s%.4s.bin",inq.vendor,inq.ident,inq.revision);
    for(j=0;dump[j];j++) if (dump[j]==' ') dump[j]='_';

    in=-1, firmware=NULL;
    if (argv[2][0] != '-')
    { unsigned short cksum;
      int i;

	if ((in=open(argv[2],O_RDONLY)) < 0)
        {   fprintf (stderr,"unable to open(\"%s\"): ",argv[2]), perror (NULL);
	    return 1;
	}
	if (fstat(in,&sb) < 0)
        {   fprintf (stderr,"unable to stat(\"%s\"): ",argv[2]), perror (NULL);
	    return 1;
	}

	if (sb.st_size != FIRMWARE_SIZE)
        {   fprintf (stderr,"\"%s\": size is insane\n",argv[2]);
	    return 1;
	}

	firmware=(unsigned char *)mmap(NULL,FIRMWARE_SIZE,PROT_READ,
					    MAP_PRIVATE,in,0);
	if (firmware==MAP_FAILED)
	{   fprintf (stderr,"unable to mmap(\"%s\"): ",argv[2]), perror (NULL);
	    return 1;
	}

	close (in);

	memcpy(sigfile,firmware+MAGIC_OFFSET+4,6);
	if (memcmp(sigfile,"MMP5",4))
	{   fprintf (stderr,"\"%s\": signature is insane\n",argv[2]);
	    return 1;
	}

	for (cksum=0,i=MAGIC_OFFSET;i<FIRMWARE_SIZE-2;i++)
	    cksum += firmware[i];
	if ((cksum&0xFF) != firmware[FIRMWARE_SIZE-1] ||
	    (cksum>>8)   != firmware[FIRMWARE_SIZE-2]
	   )
	{   fprintf (stderr,"\"%s\" is corrupted or encrypted\n",argv[2]);
	    return 1;
	}
    }

    if ((out=open(dump,O_WRONLY|O_CREAT|O_EXCL,0666)) < 0)
    {   fprintf (stderr,"unable to creat(\"%s\"): ",dump), perror (NULL);
	return 1;
    }

    cmd[0]=0xE4;		// INVOKE PROGRAM LOADER
    cmd[9]=(1<<6)&0x40;
    if (cmd.transport())
    {	fprintf (stderr,"\"%s\": unable to invoke RICOH Program Loader "
			"[ASC=%02xh/ASCQ=%02xh]\n",
			argv[1],sense[12],sense[13]);
	return 1;
    }

    do {	// not really a loop, just a way to get rid of goto's...
	poll(NULL,0,250);

	cmd[0]=0xE4;		// INVOKE PROGRAM LOADER
	cmd[9]=(1<<6)&0x40;
	if (cmd.transport())
	{   fprintf (stderr,"\"%s\"[E4]: unexpected ASC=%02xh/ASCQ=%02xh\n",
			    argv[1],sense[12],sense[13]);
	    break;
	}

	poll(NULL,0,250);

	cmd[0]=0x12;		// INQUIRY
	cmd[4]=sizeof(inq);
	cmd[5]=0;
	if (cmd.transport(READ,(unsigned char *)&inq,sizeof(inq)))
	{   perror("unable to INQUIRY, examine /var/log/message");
	    break;
	}

	if (memcmp(&inq.vendor,"RICOH   Program Loader 8",24) &&
	    memcmp(&inq.vendor,"RICOH   Program Loader 9",24) )
	{   fprintf (stderr,"\"%s\": unknown Program Loader "
			    "%.8s|%.16s|%.4s\n",
			    argv[1],inq.vendor,inq.ident,inq.revision);
	    break;
	}

	fprintf (stderr,"\"%s\": dumping firmware image to \"%s\" ",
			argv[1],dump);
	for (off=0;off<FIRMWARE_SIZE;off+=BUFFER_BLOCK)
	{   cmd[0]=0xE3;	// DOWNLOAD FIRMWARE PAGE
	    cmd[2]=(off>>24)&0xFF;
	    cmd[3]=(off>>16)&0xFF;
	    cmd[4]=(off>>8)&0xFF;
	    cmd[5]=(off)&0xFF;
	    cmd[7]=(BUFFER_BLOCK>>8)&0xFF;
	    cmd[8]=(BUFFER_BLOCK)&0xFF;
	    cmd[9]=0;
	    if (cmd.transport(READ,buffer,BUFFER_BLOCK))
            {	fprintf (stderr,"\"%s\"[E3]: unexpected "
				"ASC=%02xh/ASCQ=%02xh\n",
				argv[1],sense[12],sense[13]);
		in=-1;
		break;
	    }
	    if (write(out,buffer,BUFFER_BLOCK) != BUFFER_BLOCK)
	    {	fprintf (stderr,"unable to write(\"%s\"): ",dump),
		perror(NULL);
		in=-1;
		break;
	    }
	    fprintf (stderr,".");
	    if (off==MAGIC_OFFSET) { memcpy(sigunit,buffer+4,6); }
	}
	fprintf (stderr,"\n");

	if (in==-1) break; /* dump only or we failed to backup */

#if 1
	if (memcmp(sigunit,sigfile,6))
	{   fprintf (stderr,"\"%s\": firmware signature mismatch "
			    "[%.6s|%.6s]\n",
			    argv[1],sigunit,sigfile);
	    break;
	}
#endif

	fprintf (stderr,"\"%s\": uploading firmware image",argv[1]);
	for (off=0;off<FIRMWARE_SIZE;off+=BUFFER_BLOCK)
	{   cmd[0]=0xE2;	// UPLOAD FIRMWARE PAGE
	    cmd[5]=(BUFFER_BLOCK>>16)&0xFF;
	    cmd[6]=(BUFFER_BLOCK>>8)&0xFF;
	    cmd[7]=(BUFFER_BLOCK)&0xFF;
	    cmd[11]=0;
	    if (cmd.transport(WRITE,firmware+off,BUFFER_BLOCK))
            {	fprintf (stderr,"\"%s\"[E2]: unexpected "
				"ASC=%02xh/ASCQ=%02xh\n",
				argv[1],sense[12],sense[13]);
		in=-1;
		break;
            }
	    fprintf (stderr,".");
	}
	fprintf (stderr,"\n");

	if (in==-1) break; /* failed to upload */

	fprintf (stderr,"\"%s\": unlocking firmware\n",argv[1]);
	cmd[0]=0xE0;		// UNLOCK FIRMWARE
	cmd[3]=0;
	cmd[9]=0;
	if (cmd.transport())
	{   fprintf (stderr,"\"%s\"[E0]: unexpected "
			    "ASC=%02xh/ASCQ=%02xh\n",
			    argv[1],sense[12],sense[13]);
	    break;
	}

	fprintf (stderr,"\"%s\": ĄPOINT OF NO RETURN!\n",argv[1]);
	poll(NULL,0,3000);
	signal (SIGINT,SIG_IGN);
	signal (SIGTERM,SIG_IGN);

	cmd[0]=0xE1;		// FIRE IT UP
	// -----v----- don't update the Program Loader code
	cmd[9]=(0<<6)&0x40;
	if (cmd.transport())
	{   fprintf (stderr,"\"%s\"[E1]: unexpected "
			    "ASC=%02xh/ASCQ=%02xh\n",
			    argv[1],sense[12],sense[13]);
	    break;
	}

	while(1)
	{   poll(NULL,0,250);
	    cmd[0]=0x00;		// TEST UNIT READY
	    cmd[5]=0x80;
	    if (!cmd.transport()) continue; // never happens?
	    if (sense[12] == 0xF7)
	    {	if (ascq != sense[13])
		{   switch (ascq=sense[13])
		    {	case 0: meaning="preparing";		break;
			case 1: meaning="erasing contents";	break;
			case 2: meaning="writing contents";	break;
			case 3: meaning="verifying contents";	break;
			default:meaning="unknown meaning";	break;
		    }
		    fprintf (stderr,"\"%s\": %s\n",argv[1],meaning);
		}
	    }
	    else if (sense[12] == 0x3A) /* no media */
	    {	fprintf (stderr,"\"%s\": firmware upgrade succeeded:-)\n",
				argv[1]),
		fprintf (stderr,"\"%s\": "
				"ĄCYCLE THE POWER BEFORE PROCEEDING!\n",
				argv[1]);
		break;
	    }
	    else
            {	fprintf (stderr,"\"%s\"[0]: unexpected "
				"ASC=%02xh/ASCQ=%02xh\n",
				argv[1],sense[12],sense[13]);
		in=-1;
		break;
	    }
	}
    } while (0);

    cmd[0]=0xE4;	// TERMINATE PROGRAM LOADER
    cmd[9]=(0<<6)&0x40;
    if (cmd.transport())
    {	fprintf (stderr,"\"%s\": unexpected ASC=%02xh/ASCQ=%02xh\n",
			argv[1],sense[12],sense[13]);
	return 1;
    }

  return in<0;
}
