/*
 * This is the DECtalk PC firmware loader for the Linux kernel, version 1.0
 *
 * Original 386BSD source:
 *      Copyright ( c ) 1996 Brian Buhrow <buhrow@lothlorien.nfbcal.org>
 *
 * Adapted for Linux:
 *      Copyright ( c ) 1997 Nicolas Pitre <nico@cam.org>
 *
 * Adapted for speakup:
 *      Copyright ( c ) 2003 David Borowski <david575@golden.net>
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * ( at your option ) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <malloc.h>
#include <sys/errno.h>
#include <asm/io.h>
#include "dtload.h"
#include "dtpc_reg.h"

#define dt_delay(x) usleep(x)
int verbose = 0, intest = 0,infd = -1;
int image_len, total_paras;
int dt_stat, dma_state = 0, has_kernel = 0;
struct dos_reloc fixups[512];
char *read_buff = NULL;
struct dos_exe_header header;
u_short iobase = 0x350;

static inline int dt_getstatus( )
{
  dt_stat =  inb_p( iobase )|(inb_p( iobase+1 )<<8);
  return dt_stat;
}

static void dt_sendcmd( u_int cmd )
{
  outb_p( cmd & 0xFF, iobase );
  outb_p( (cmd>>8) & 0xFF, iobase+1 );
}

static int dt_waitbit( int bit )
{
  int timeout = 100;
  while ( --timeout > 0 ) {
    if( (dt_getstatus( ) & bit ) == bit ) return 1;
    usleep( 1000 );
  }
  return 0;
}

static int dt_sendcmd_wait( u_int cmd, int bit )
{
  int timeout = 1000;
  outb_p( cmd & 0xFF, iobase );
  outb_p( (cmd>>8) & 0xFF, iobase+1 );
  while ( --timeout > 0 ) {
    if( (dt_getstatus( ) & bit ) == bit ) return 1;
    usleep( 1000 );
  }
  return 0;
}

static int dt_waitmode( int pattern )
{
  int timeout = 1000;
  while ( --timeout > 0 ) {
    if( dt_getstatus( ) == pattern ) return 1;
    usleep( 1000 );
  }
  fprintf( stderr, "waitmode p=%x s = %x\n", pattern, dt_stat );
  return 0;
}

static int dt_wait_dma( )
{
  int timeout = 1000, state = dma_state;
  if( !has_kernel ){
    dt_delay( 500 );
    return( dt_waitbit( STAT_dma_ready ) );
  }
  if( ! dt_waitbit( STAT_dma_ready ) ) return 0;
  while ( --timeout > 0 ) {
    if( (dt_getstatus()&STAT_dma_state) == state ) return 1;
    usleep( 1000 );
  }
  dma_state = dt_getstatus( ) & STAT_dma_state;
  return 1;
}

dt_ctrl( u_int cmd )
{
  while ( ! dt_waitbit( STAT_cmd_ready ) ) dt_delay( 100 );
  outb_p( 0, iobase+2 );
  outb_p( 0, iobase+3 );
  dt_getstatus( );
  dt_sendcmd( CMD_control|cmd );
  outb_p( 0, iobase+6 );
  dt_delay( 100 );
  dt_sendcmd( CMD_null );
  while ( ! dt_waitbit( STAT_cmd_ready ) ) dt_delay( 100 );
}

int dt_flush( void )
{
  dt_ctrl( CTRL_flush );
  dt_waitbit( STAT_dma_ready );
  outb_p( DMA_sync, iobase+4 );
  outb_p( 0, iobase+4 );
  dma_state ^= STAT_dma_state;
  while( dt_getstatus( ) & STAT_flushing ) dt_delay( 100 );
  return 0;
}

static int dt_sendbuff( char *src, int len )
{
  while( len-- ){
    if( ! dt_wait_dma( ) ) return -1;
    if( ! (dt_getstatus( ) & STAT_rr_char) ) break;
    outb_p( DMA_single_in, iobase+4 );
    outb_p( *src++, iobase+4 );
    dma_state ^= STAT_dma_state;
  }
  return 0;
}

unsigned long dt_allocmem( unsigned long paras )
{
	unsigned long addr;
	if( ! dt_wait_dma( ) ) return 0;
	outb_p( DMA_control, iobase+4 );
	outb_p( DT_MEM_ALLOC, iobase+4 );
	dma_state ^= STAT_dma_state;
	if( ! dt_wait_dma( ) ) return 0;
	outb_p( paras & 0xFF, iobase+4 );
	outb_p( (paras>>8) & 0xFF, iobase+4 );
	dma_state ^= STAT_dma_state;
	if( ! dt_wait_dma( ) ) return 0;
	addr = inb_p( iobase+4 );
	addr |= (inb_p( iobase+4 )<<8);
	addr += (inb_p( iobase+4 )<<4);
	addr += (inb_p( iobase+4 )<<12);
	dma_state ^= STAT_dma_state;
  return addr;
}

static int testkernel( void )
{
  dt_sendcmd( CMD_sync );
  if( ! dt_waitbit( STAT_cmd_ready ) ) return -10;
  has_kernel = ( dt_stat&0x8000 ) ? 1 : 0;
  if ( verbose ) printf( "testkernel got %x\n", dt_stat );
  if ( has_kernel ) return 0;
	dt_delay( 100 );
  return 1;
}

static int dt_loadmem( int addr, int len, char *src )
{
  char c;
  int l;
  if ( verbose ) printf( "dt_loadmem: addr = %08X size = %d\n", addr, len );
  do {
    l = len;
    if ( l >= 0xc000 ) l = 0xc000;
    len -= l;
    if( ! dt_wait_dma( ) ) return -1;
    outb_p( DMA_control, iobase+4 );
    outb_p( DT_LOAD_MEM, iobase+4 );
    dma_state ^= STAT_dma_state;
    if( ! dt_wait_dma( ) ) return -2;
    outb_p( addr & 0xFF, iobase+4 );
    outb_p( (addr>>8) & 0xFF, iobase+4 );
    outb_p( (addr>>16) & 0xFF, iobase+4 );
    outb_p( (addr>>24) & 0xFF, iobase+4 );
    outb_p( l & 0xFF, iobase+4 );
    outb_p( (l>>8) & 0xFF, iobase+4 );
    dma_state ^= STAT_dma_state;
    if( ! dt_wait_dma( ) ) return -3;
    addr += l;
    while( l-- ){
      c = *src++;
      outb_p( c, iobase+4 );
    }
    dma_state ^= STAT_dma_state;
  } while ( len > 0 );
  return 0;
}

unsigned int loadfile ( char *filename )
{
  int i, header_size;
  unsigned int total_paras;
  long fix;
  infd = open ( filename, O_RDONLY );
  if ( infd == -1 ) {
      perror ( "Opening file: " );
      return 0;
    }
  read ( infd, &header, sizeof ( struct dos_exe_header ) );
  if ( header.id != 0x5a4d ) {
      fprintf ( stderr, "Invalid header file format\n" );
      fprintf ( stderr, "Want 0x5a4d, got 0x%x\n", header.id );
      return 0;
    }
  if ( header.relen > MAX_FIXUPS ) {
      fprintf ( stderr, "Too many fixups\n" );
      return 0;
    }
  lseek ( infd, ( long ) header.reloc, SEEK_SET );
  read ( infd, fixups, sizeof ( struct dos_reloc ) * header.relen );
  header_size = header.hsize * 16;
  lseek ( infd, ( long )header_size, SEEK_SET );
  image_len = ( ( header.pages-1 )*512 ) + ( header.rem- header_size );
  total_paras =  ( image_len >> 4 ) + header.hmin + 16;
  read ( infd, read_buff, image_len );
  close( infd );
  return total_paras;
}

static int loadkernel( char *filename )
{
  int segfix = 0x40, fix, i;
  int ipval, csval;
  if ( has_kernel ) return 0;
  if ( !loadfile( filename ) ) return -1;
  header.csval += segfix;
  header.ssval += segfix;
  if ( verbose ) {
      printf ( "Loading kernel of %ld bytes ( %d relocs )\n",
	      image_len, header.relen );
      printf ( "    cs:ip == %04x:%04x   ss:sp == %04x:%04x\n",
	      header.csval, header.ipval, header.ssval, header.spval );
    }
  for ( i = 0; i < header.relen; i++ ) {
      fix = ( fixups[i].segment << 4 ) + fixups[i].offset;
      ( *( unsigned int * ) &read_buff[fix] ) += segfix;
    }
  csval = header.csval;
  ipval = header.ipval;
  dt_sendcmd_wait( MODULE_reset, MODULE_init );
  dt_sendcmd( CMD_reset );
  if( dt_getstatus( ) == MODULE_self_test ){
    if( ! dt_waitmode( MODULE_init ) ) return -1;
  }
  if ( !dt_sendcmd_wait( CMD_reset, MODE_status ) ) return -2;
  if ( !dt_sendcmd_wait( CMD_sync, MODE_error ) ) return -3;
  if ( !dt_sendcmd_wait( CMD_reset, MODE_status ) ) return -4;
  if ( verbose ) printf( "card is ready\n" );
  dt_sendcmd( CMD_dma );
  if( ! dt_waitbit( STAT_dma_ready ) ) return -5;
  if( ( i = dt_loadmem( 0x00000400, image_len, read_buff ) ) ) {
   fprintf( stderr, "kernel load failed, status %d\n", i );
    return -6;
  }
  dt_delay( 100 );
  /* the kernel is loaded, start it */
  if ( !dt_sendcmd_wait( CMD_reset, MODE_status ) ) return -7;
  dt_sendcmd( CMD_dma+1 );   /**xxx**/
	dt_delay( 100 );
  if( ! dt_waitbit( STAT_dma_ready ) ) return-8;
  outb_p( DMA_control, iobase+4 );
  outb_p( DT_START_TASK, iobase+4 );
	dt_delay( 100 );
  outb_p( ipval & 0xFF, iobase+4 );
  outb_p( (ipval>>8) & 0xFF, iobase+4 );
  outb_p( csval & 0xFF, iobase+4 );
  outb_p( (csval>>8) & 0xFF, iobase+4 );
	if( ! dt_waitmode( 0xc001 ) ) return -9;
  if ( verbose ) {
    printf( "done loading kernel\n" );
  }
  return testkernel( );
}

int loaddict ( char *filename, char *name, int type )
{
  int i, read_index, read_size, act_size;
  unsigned short *index_fix, seg_fix;
  unsigned long entries, index, dic_bytes, dic_addr;
  unsigned int total_paras;
  unsigned long param, l;
  infd = open ( filename, O_RDONLY );
  if ( infd == -1 ) {
      perror ( filename );
      return -1;
    }
/* read in the entry count and the actual entry size excluding the
 * index table ( which is entries * 4 ) ...  */
  read ( infd, &entries, 4 );
  read ( infd, &dic_bytes, 4 );
  if ( verbose )
    printf ( "Loading %s dictionary of %lu entries, %lu bytes.\n",
	    name, entries, dic_bytes );
  total_paras = ( ( ( entries * 4 ) + dic_bytes ) >> 4 ) + 2;
  if ( verbose )
    printf ( "Allocating %d paragraphs of free ram ...\n", total_paras );
  l = dt_allocmem( total_paras );
  if ( l == 0 ) {
      perror ( "Error requesting memory from speech device" );
      return -1;
    }
  seg_fix = ( l >> 4 ) & 0xffff;
  dic_addr = l;
  index = entries;
  index_fix = ( unsigned short * ) &read_buff[0];
  if ( verbose )
    printf ( "Index table starts at %lx\n", l );
  read_index = index*4;
  act_size = read ( infd, read_buff, read_index );
  if ( act_size != read_index ) {
    fprintf ( stderr, "\nError reading indexes\n" );
    fprintf ( stderr, "    exp : %d  act : %d\n", read_index * 4, act_size );
    return -1;
  }
  for ( i = 1; i < index * 2; i += 2 )
    index_fix[i] += seg_fix;
  if( ( i = dt_loadmem( l, read_index, read_buff ) ) ) {
    fprintf ( stderr, "\nError loading indexes at 0x%lX: i %d\n",
      l, i );
    return -1;
  }
  l += read_index;
/* now, load up the dictionary bytes ...  */
  if ( verbose )
    printf ( "Dictionary text starts at %lx\n", l );
  read_size = dic_bytes;
  if ( ( act_size = read ( infd, read_buff, read_size ) ) != read_size ) {
    fprintf ( stderr, "\nError reading dictionary text!\n" );
    fprintf ( stderr, "asked : %d  actual : %d\n", act_size, read_size );
    return -1;
  }
  if( ( i = dt_loadmem( l, read_size, read_buff ) ) ) {
    fprintf ( stderr, "\nError loading dictionary at 0x%lX: status %d\n",
      l, i );
    return -1;
  }
  if( ! dt_wait_dma( ) ) return -1;
  outb_p( DMA_control, iobase+4 );
  outb_p( DT_SET_DIC, iobase+4 );
	dma_state ^= STAT_dma_state;
  if( ! dt_wait_dma( ) ) return -1;
  l  = dic_addr;
	l = ((l << 12) & 0xFFFF0000) + (l & 0x0000000F);
  outb_p( l & 0xFF, iobase+4 );
  outb_p( (l>>8) & 0xFF, iobase+4 );
  outb_p( (l>>16) & 0xFF, iobase+4 );
  outb_p( (l>>24) & 0xFF, iobase+4 );
  l = entries;
  outb_p( l & 0xFF, iobase+4 );
  outb_p( (l>>8) & 0xFF, iobase+4 );
  outb_p( (l>>16) & 0xFF, iobase+4 );
  outb_p( (l>>24) & 0xFF, iobase+4 );
	l = type;
  outb_p( l & 0xFF, iobase+4 );
  outb_p( (l>>8) & 0xFF, iobase+4 );
	dma_state ^= STAT_dma_state;
  close ( infd );
  if ( verbose ) printf( "dictionary load complete\n" );
  return 0;
}

int loadexe ( char *filename )
{
  unsigned int load_addr = 0, seg_fix;
  int i, read_size;
  int ipval, csval;
  long fix;
  unsigned long total_paras;
  total_paras = loadfile ( filename );
  if ( total_paras == 0 ) return -1;
  load_addr = dt_allocmem( total_paras );
  if ( load_addr == 0 ) {
    fprintf ( stderr, "Error allocating memory on card: " );
    return -1;
  }
  seg_fix = ( load_addr >> 4 ) & 0xffff;
  if ( verbose ) {
      printf ( "Loading %s %ld bytes ( %d relocs )\n",
	      filename, image_len, header.relen );
      printf ( "Allocating %ld bytes of free ram at %05x\n",
	      ( long ) header.hmin * 16, load_addr );
      printf ( "Total memory taken is %ld bytes\n", ( long ) total_paras * 16 );
      printf ( "    cs:ip == %04x:%04x   ss:sp == %04x:%04x\n",
	      header.csval + seg_fix, header.ipval, header.ssval + seg_fix, header.spval );
    }
    for ( i = 0; i < header.relen; i++ ) {
	fix = ( ( long ) fixups[i].segment << 4 ) + ( long ) fixups[i].offset;
	( *( unsigned int * ) &read_buff[fix] ) += seg_fix;
    	 }
  if( ( i = dt_loadmem( load_addr, image_len, read_buff ) ) ) {
    fprintf ( stderr, "Error loading speech device at 0x%lX: status %d\n",
      load_addr, i );
    return -1;
  }
  csval = header.csval + seg_fix;
  ipval = header.ipval;
  if( ! dt_wait_dma( ) ) return -1;
  outb_p( DMA_control, iobase+4 );
  outb_p( DT_START_TASK, iobase+4 );
	dma_state ^= STAT_dma_state;
  if( ! dt_wait_dma( ) ) return -1;
  outb_p( ipval & 0xFF, iobase+4 );
  outb_p( (ipval>>8) & 0xFF, iobase+4 );
  outb_p( csval & 0xFF, iobase+4 );
  outb_p( (csval>>8) & 0xFF, iobase+4 );
	dma_state ^= STAT_dma_state;
  return 0;
}

void release_io( void )
{
  ioperm( (long)iobase, 8, 0 );
  ioperm( (long)0x0080, 1, 0 );
  if ( read_buff ) free( read_buff );
}

parseparm( char *parm, char *value )
{
  char *cp = parm+strlen( parm );
  while ( --cp > parm ) if ( *cp > ' ' ) break;
  cp[1] = '\0';
  if ( !strcmp( parm, "io" ) ) {
    long io = strtol( value, 0, 0 );
    if ( io >= 0x100 && io <= 0x350 ) {
      iobase = (u_short)io;
      return;
    }
    fprintf( stderr, "invalid io value %s\n", value );
    exit( 1 );
  } else if ( !strcmp( parm,"verbose" ) ) {
    verbose = atoi( value );
  }
}

do_test( void )
{
  char buffer[512];
  int len;
  dma_state = dt_getstatus( ) & STAT_dma_state;
  while ( fgets( buffer, 510, stdin ) ) {
    len = strlen( buffer );
    if ( len == 1 ) dt_flush( );
    else {
      if ( buffer[len-1] == '\n' ) buffer[len-1] = '\013';
      dt_sendbuff( buffer, len );
    }
  }
  *buffer = '\013';
  dt_sendbuff( buffer, 1 );
}

int main ( int argc, char **argv )
{
  char name[80], *cp;
  char *dirname = 0, *confname = "dec_pc.conf";
  char *init_msg = "[:ra 360] dec pc initialized\011";
  FILE *confile;
  struct stat statbuf;
  int maxsize = 0, status = 0;
  while ( --argc > 0 ) {
    argv++;
    if ( !strcmp( *argv, "-v" ) ) verbose = 1;
    else if ( !strcmp( *argv, "-t" ) ) intest = 1;
    else dirname = *argv;
  }
  if ( !dirname ) dirname = "/usr/local/lib/dec_pc";
  if ( chdir( dirname ) != 0 ) {
    fprintf( stderr, "cannot chdir to %s\n", dirname );
    exit( 1 );
  }
  if ( !( confile = fopen( confname, "r" ) ) ) {
    fprintf( stderr, "could not open %s", confname );
    exit( 1 );
  }
  while ( fgets( name, 80, confile ) ) {
    cp = strchr( name, '\n' );
    if ( cp ) *cp = '\0';
    if ( ( cp = strchr( name, '=' ) ) ) {
      *cp++ = '\0';
      parseparm( name, cp );
      continue;
    }
    if ( stat( name, &statbuf ) != 0 ) {
      fprintf( stderr, "cannot stat %s\n", name );
      exit( 1 );
    }
    if ( statbuf.st_size > maxsize ) maxsize = statbuf.st_size;
  }
  rewind( confile );
  if ( ioperm( (long)0x0080, 1, 1 ) || ioperm( (long)iobase, 8, 1 ) ) {
    fprintf( stderr, "could not get ioperm\n" );
    exit( 1 );
  }
  atexit( release_io );
  if ( testkernel( ) == 0 ) {
    if ( intest ) do_test( );
    else fprintf( stderr, "kernel already loaded\n" );
    exit( 0 );
  }
  read_buff = malloc(  maxsize );
  if ( !read_buff ) {
    fprintf( stderr, "cannot malloc %d bytes\n", maxsize );
    exit( 1 );
  }
  while ( fgets( name, 80, confile ) && !status ) {
    cp = strchr( name, '\n' );
    if ( cp ) *cp = '\0';
    if ( strchr( name, '=' ) ) continue; /* a parameter */
    if ( !( cp = strchr( name, '.' ) ) ) continue;
    cp++;
    if ( !strcmp ( cp, "dic" ) ) {
      status = loaddict ( name, "primary", PRIMARY_DIC );
    } else if ( !strcmp ( cp, "dtu" ) ) {
      status = loaddict ( name, "user", USER_DIC );
    } else if ( !strcmp ( cp, "dta" ) ) {
      status = loaddict ( name, "abbreviation file", ABBREV_DIC );
    } else if ( !strcmp ( cp, "exe" ) ) {
      status = loadexe ( name );
    } else if ( !strcmp ( cp, "sys" ) ) {
      status = loadkernel ( name );
    }
  }
  if ( status ) fprintf( stderr, "status %d\n", status );
  fclose( confile );
  if ( status ) exit( status );
  dt_sendbuff( init_msg, strlen( init_msg ) );
  sleep( 1 );
  if ( intest ) do_test( );
  exit( 0 );
}
