/* YMODEM support for bootldr
 * ^^^^^^^^^^^^^^^^^^^^^^^^^^
 * Copyright (C) 2001  John G Dorsey
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The author may be contacted via electronic mail at <john+@cs.cmu.edu>,
 * or at the following address:
 *
 *   John Dorsey
 *   Carnegie Mellon University
 *   HbH2201 - ICES
 *   5000 Forbes Avenue
 *   Pittsburgh, PA  15213
 *
 */

#include <linux/bootldr/bootldr.h>
#include <linux/init.h>
#include "ymodem.h"
#include "crc.h"
#include <linux/types.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>

static int receive_byte(char *c, int timeout){
  int error = 0;

  *c = awaitkey_seconds(timeout, &error);

  return error ? -1 : 0;
}

/* Returns 0 on success, 1 on corrupt packet, -1 on error (timeout): */
static int receive_packet(char *data, int *length, int use_crc, int timeout){
  int i;
  unsigned int packet_size, sum;
  char c;

  *length = 0;

  if(receive_byte(&c, timeout) < 0)
    return -1;

  switch(c){
  case SOH:
    packet_size = PACKET_SIZE;
    break;

  case STX:
    packet_size = PACKET_1K_SIZE;
    break;

  case EOT:
    return 0;

  case CAN:
    if(receive_byte(&c, timeout) == 0 && c == CAN){
      *length = -1;
      return 0;
    }

  default:

    /* This case could be the result of corruption on the first octet
     * of the packet, but it's more likely that it's the user banging
     * on the terminal trying to abort a transfer. Technically, the
     * former case deserves a NAK, but for now we'll just treat this
     * as an abort case.
     */

    *length = -1;
    return 0;
  }

  *data = c;

  for(i = 1; i < (packet_size + 
		  (use_crc ? PACKET_OVERHEAD_CRC : PACKET_OVERHEAD)); ++i)
    if(receive_byte(data + i, timeout) < 0)
      return -1;
  
  /* Just a sanity check on the sequence number/complement value. 
   * Caller should check for in-order arrival.
   */
  if(data[PACKET_SEQNO_INDEX] != 
     ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff))
    return 1;

  if(use_crc){

    /* It seems odd that the CRC doesn't cover the three preamble bytes. */
    if(crc16_buf(data + PACKET_HEADER, packet_size + PACKET_TRAILER_CRC) != 0)
      return 1;

  } else {

    for(i = PACKET_HEADER, sum = 0; i < packet_size + PACKET_HEADER; ++i)
      sum += data[i];

    if((sum & 0xff) != (data[i] & 0xff))
      return 1;

  }

  *length = packet_size;

  return 0;
}

EXPORT_SYMBOL(lab_ymodem_receive);

int labymodem_init()
{
	crc16_init();
};

/* Returns the length of the file received, or 0 on error: */
char* lab_ymodem_receive(unsigned int* length)
{
  unsigned char packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
  int packet_length, i, file_done, session_done, crc_tries, crc_nak, use_crc;
  unsigned int packets_received, errors, timeout, first_try = 1;
  char file_name[FILE_NAME_LENGTH], file_size[FILE_SIZE_LENGTH], *file_ptr;
  char *buf_ptr;
  char* buf;
  unsigned long size = 0;
  int crcchar = CRC;

  putstr("ready for YMODEM transfer...\r\n");
  
  /* Give the user time to frantically type in the file name: */
  timeout = INITIAL_TIMEOUT;
  packet_length=0;
for(session_done = 0, errors = 0; ; ){
	crc_tries = crc_nak = use_crc = 1;

	delay_seconds(3);
	putc(crcchar);

	first_try = 0;

	for(packets_received = 0, file_done = 0, buf_ptr = buf; ; ){
		switch(receive_packet(packet_data, &packet_length, use_crc, timeout)){
		case 0:
			errors = 0;
			switch(packet_length){
			case -1:  /* abort */
				putc(ACK);
				return 0;
			case 0:   /* end of transmission */
				putc(ACK);
				file_done = 1;
				break;
			default:  /* normal packet */
				if((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))
					putc(NAK);
				else {
					if(packets_received == 0)
					{
						for(i = PACKET_HEADER; i < PACKET_HEADER + 4; ++i)
							if(packet_data[i] != 0)
								break;
						if(i < PACKET_HEADER + 4)
						{						
							for(file_ptr = packet_data + PACKET_HEADER, i = 0;
								*file_ptr && i < FILE_NAME_LENGTH;)
								file_name[i++] = *file_ptr++;
							
							file_name[i++] = '\0';
							
							for(++file_ptr, i = 0; 
								*file_ptr != ' ' && i < FILE_SIZE_LENGTH;)
								file_size[i++] = *file_ptr++;
								
							file_size[i++] = '\0';
							
							size = strtoul(file_size, NULL, 0);
							
							buf_ptr = buf=vmalloc(size+packet_length*6);
							
							*length=size+packet_length*3;
							
							if(!buf){
								putc(CAN);
								putc(CAN);
								
								delay_seconds(3);
								
								putstr("Failed to allocate ");
								putHexInt32(size);
								putstr(" bytes...\r\n");
								return 0;
							}
							
							putc(ACK);
							putc(crc_nak ? crcchar : NAK);
							crc_nak = 0;
						} else {  /* filename packet is empty; end session */
							putc(ACK);
							file_done = 1;
							session_done = 1;
							break;
						}
					} else {
						if((buf_ptr + packet_length) - buf > *length){
							putc(CAN);
							putc(CAN);
							
							delay_seconds(3);
							putLabeledWord("Current size:   ", (buf_ptr + packet_length) - buf);
							putLabeledWord("Receive buffer: ",
							       *length);
							delay_seconds(1);
							return 0;
						}
						
						memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);
						buf_ptr += packet_length;
						putc(ACK);
					}
					
					++packets_received;
				}  /* sequence number ok */
			}
			break;
		default:
			if(++errors >= ((packets_received == 0 ? MAX_CRC_TRIES : 0) + MAX_ERRORS)){
				putc(CAN);
				putc(CAN);
				delay_seconds(1);
				
				putstr("Too many errors during receive; giving up.\r\n");

				return 0;
			}
			
			if(packets_received == 0){
				if(crc_tries < MAX_CRC_TRIES) {
					++crc_tries;
					timeout = CRC_TIMEOUT;
				} else {
					crc_nak = use_crc = 0;
					timeout = NAK_TIMEOUT;
				}
			}
			
			putc(crc_nak ? crcchar : NAK);
		}
      
		if(file_done)
			break;

	}  /* receive packets */

	if(session_done)
		break;

}  /* receive files */

  *length = size;
  delay_seconds(1);
  return buf;

}

module_init(labymodem_init);