/*
 * Copyright (c) 2001, Adam Dunkels.
 * All rights reserved. 
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution. 
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.  
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
 *
 * This file is part of the uIP TCP/IP stack.
 *
 *
 */


#include "net/ip/uip.h"
#include "net/ip/uip_arch.h"

#define BUF ((uip_tcpip_hdr *)&uip_buf[UIP_LLH_LEN])
#define IP_PROTO_TCP    6
#define IP_PROTO_UDP    17

/*-----------------------------------------------------------------------------------*/
#pragma optimize(push, off)
void
uip_add32(uint8_t *op32, uint16_t op16)
{
  asm("ldy #3");
  asm("jsr ldaxysp");
  asm("sta ptr1");
  asm("stx ptr1+1");
  asm("ldy #0");
  asm("lda (sp),y");
  asm("ldy #3");
  asm("clc");
  asm("adc (ptr1),y");
  asm("sta _uip_acc32+3");
  asm("dey");
  asm("lda (ptr1),y");
  asm("ldy #1");
  asm("adc (sp),y");
  asm("sta _uip_acc32+2");
  asm("ldy #1");
  asm("lda (ptr1),y");
  asm("adc #0");
  asm("sta _uip_acc32+1");
  asm("dey");
  asm("lda (ptr1),y");
  asm("adc #0");
  asm("sta _uip_acc32+0");  
}
#pragma optimize(pop)
/*-----------------------------------------------------------------------------------*/
static uint16_t chksum_ptr, chksum_len, chksum_tmp;
static uint8_t chksum_protocol;
static uint16_t chksum(void);
/*-----------------------------------------------------------------------------------*/
#pragma optimize(push, off)
uint16_t
chksum(void) {

  asm("lda #0");
  asm("sta tmp1");
  asm("sta tmp1+1");
  asm("lda _chksum_ptr");
  asm("sta ptr1");
  asm("lda _chksum_ptr+1");
  asm("sta ptr1+1");

  asm("lda _chksum_len+1");
  asm("beq chksumlast");

  /* If checksum is > 256, do the first runs. */
  asm("ldy #0");
  asm("clc");
  asm("chksumloop_256:");
  asm("lda (ptr1),y");
  asm("adc tmp1");
  asm("sta tmp1");
  asm("iny");
  asm("lda (ptr1),y");
  asm("adc tmp1+1");
  asm("sta tmp1+1");
  asm("iny");
  asm("bne chksumloop_256");
  asm("inc ptr1+1");
  asm("dec _chksum_len+1");
  asm("bne chksumloop_256");

  asm("chksum_endloop_256:");
  asm("lda tmp1");
  asm("adc #0");
  asm("sta tmp1");
  asm("lda tmp1+1");
  asm("adc #0");
  asm("sta tmp1+1");
  asm("bcs chksum_endloop_256");

  asm("chksumlast:");
  asm("lda _chksum_len");
  asm("lsr");
  asm("bcc chksum_noodd");  
  asm("ldy _chksum_len");
  asm("dey");
  asm("lda (ptr1),y");
  asm("clc");
  asm("adc tmp1");
  asm("sta tmp1");
  asm("bcc noinc1");
  asm("inc tmp1+1");
  asm("bne noinc1");
  asm("inc tmp1");
  asm("noinc1:");
  asm("dec _chksum_len");

  asm("chksum_noodd:");
  asm("clc");
  asm("php");
  asm("ldy _chksum_len");
  asm("chksum_loop1:");
  asm("cpy #0");
  asm("beq chksum_loop1_end");
  asm("plp");
  asm("dey");
  asm("dey");
  asm("lda (ptr1),y");
  asm("adc tmp1");
  asm("sta tmp1");
  asm("iny");
  asm("lda (ptr1),y");
  asm("adc tmp1+1");
  asm("sta tmp1+1");
  asm("dey");
  asm("php");
  asm("jmp chksum_loop1");
  asm("chksum_loop1_end:");
  asm("plp");

  asm("chksum_endloop:");
  asm("lda tmp1");
  asm("adc #0");
  asm("sta tmp1");
  asm("lda tmp1+1");
  asm("adc #0");
  asm("sta tmp1+1");
  asm("bcs chksum_endloop");

  asm("lda tmp1");
  asm("ldx tmp1+1");

  return __AX__;
}
#pragma optimize(pop)
/*-----------------------------------------------------------------------------------*/
uint16_t
uip_chksum(uint16_t *buf, uint16_t len)
{
  /*  unsigned long sum;

  sum = 0;

  chksum_ptr = (uint16_t)buf;
  while(len >= 256) {  
    chksum_len = 256;
    sum += chksum();
    len -= 256;
    chksum_ptr += 256;
  }

  if(len < 256) {
    chksum_len = len;
    sum += chksum();
  }

  while((sum >> 16) != 0) {
    sum = (sum >> 16) + (sum & 0xffff);
  }

  return sum;*/

  chksum_len = len;
  chksum_ptr = (uint16_t)buf;
  return chksum();
}
/*-----------------------------------------------------------------------------------*/
uint16_t
uip_ipchksum(void)
{
  chksum_ptr = (uint16_t)uip_buf + UIP_LLH_LEN;
  chksum_len = UIP_IPH_LEN;  
  return chksum();
}
/*-----------------------------------------------------------------------------------*/
#pragma optimize(push, off)
static uint16_t
transport_chksum(uint8_t protocol)
{
  chksum_protocol = protocol;
  chksum_ptr = (uint16_t)&uip_buf[UIP_LLH_LEN + UIP_IPH_LEN];
  chksum_len = UIP_TCPH_LEN;  
  chksum_tmp = chksum();

  chksum_ptr = (uint16_t)uip_appdata;
  asm("lda _uip_aligned_buf+3+%b", UIP_LLH_LEN);
  asm("sec");
  asm("sbc #%b", UIP_IPTCPH_LEN);
  asm("sta _chksum_len");
  asm("lda _uip_aligned_buf+2+%b", UIP_LLH_LEN);
  asm("sbc #0");
  asm("sta _chksum_len+1");

  asm("jsr %v", chksum);

  asm("clc");
  asm("adc _chksum_tmp");
  asm("sta _chksum_tmp");
  asm("txa");
  asm("adc _chksum_tmp+1");
  asm("sta _chksum_tmp+1");

  /* Fold carry */
  /*  asm("bcc noinc");
  asm("inc _chksum_tmp");
  asm("noinc:");*/

  asm("tcpchksum_loop1:");
  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc #0");
  asm("sta _chksum_tmp+1");
  asm("bcs tcpchksum_loop1");

  asm("lda _uip_aligned_buf+3+%b", UIP_LLH_LEN);
  asm("sec");
  asm("sbc #%b", UIP_IPH_LEN);
  asm("sta _chksum_len");
  asm("lda _uip_aligned_buf+2+%b", UIP_LLH_LEN);
  asm("sbc #0");
  asm("sta _chksum_len+1");

  asm("ldy #$0c");
  asm("clc");
  asm("php");
  asm("tcpchksum_loop2:");
  asm("plp");
  asm("lda _uip_aligned_buf+%b,y", UIP_LLH_LEN);
  asm("adc _chksum_tmp");
  asm("sta _chksum_tmp");
  asm("iny");
  asm("lda _uip_aligned_buf+%b,y", UIP_LLH_LEN);
  asm("adc _chksum_tmp+1");
  asm("sta _chksum_tmp+1");
  asm("iny");
  asm("php");
  asm("cpy #$14");
  asm("bne tcpchksum_loop2");

  asm("plp");

  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc %v", chksum_protocol);  
  asm("sta _chksum_tmp+1");

  asm("lda _chksum_tmp");
  asm("adc _chksum_len+1");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc _chksum_len");
  asm("sta _chksum_tmp+1");

  asm("tcpchksum_loop3:");
  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc #0");
  asm("sta _chksum_tmp+1");
  asm("bcs tcpchksum_loop3");

  return chksum_tmp;
}
#pragma optimize(pop)

/*-----------------------------------------------------------------------------------*/
uint16_t
uip_tcpchksum(void)
{
  return transport_chksum(IP_PROTO_TCP);
#if 0
  chksum_ptr = (uint16_t)&uip_buf[UIP_LLH_LEN + UIP_IPH_LEN];
  chksum_len = UIP_TCPH_LEN;  
  chksum_tmp = chksum();

  chksum_ptr = (uint16_t)uip_appdata;
  asm("lda _uip_buf+3+%b", UIP_LLH_LEN);
  asm("sec");
  asm("sbc #%b", UIP_IPTCPH_LEN);
  asm("sta _chksum_len");
  asm("lda _uip_buf+2+%b", UIP_LLH_LEN);
  asm("sbc #0");
  asm("sta _chksum_len+1");

  asm("jsr %v", chksum);

  asm("clc");
  asm("adc _chksum_tmp");
  asm("sta _chksum_tmp");
  asm("txa");
  asm("adc _chksum_tmp+1");
  asm("sta _chksum_tmp+1");

  /* Fold carry */
  /*  asm("bcc noinc");
  asm("inc _chksum_tmp");
  asm("noinc:");*/

  asm("tcpchksum_loop1:");
  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc #0");
  asm("sta _chksum_tmp+1");
  asm("bcs tcpchksum_loop1");

  asm("lda _uip_buf+3+%b", UIP_LLH_LEN);
  asm("sec");
  asm("sbc #%b", UIP_IPH_LEN);
  asm("sta _chksum_len");
  asm("lda _uip_buf+2+%b", UIP_LLH_LEN);
  asm("sbc #0");
  asm("sta _chksum_len+1");

  asm("ldy #$0c");
  asm("clc");
  asm("php");
  asm("tcpchksum_loop2:");
  asm("plp");
  asm("lda _uip_buf+%b,y", UIP_LLH_LEN);
  asm("adc _chksum_tmp");
  asm("sta _chksum_tmp");
  asm("iny");
  asm("lda _uip_buf+%b,y", UIP_LLH_LEN);
  asm("adc _chksum_tmp+1");
  asm("sta _chksum_tmp+1");
  asm("iny");
  asm("php");
  asm("cpy #$14");
  asm("bne tcpchksum_loop2");

  asm("plp");

  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc #6");  /* IP_PROTO_TCP */
  asm("sta _chksum_tmp+1");

  asm("lda _chksum_tmp");
  asm("adc _chksum_len+1");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc _chksum_len");
  asm("sta _chksum_tmp+1");

  asm("tcpchksum_loop3:");
  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc #0");
  asm("sta _chksum_tmp+1");
  asm("bcs tcpchksum_loop3");

  return chksum_tmp;
#endif 
}

/*-----------------------------------------------------------------------------------*/
#if UIP_UDP_CHECKSUMS
uint16_t
uip_udpchksum(void)
{
  return transport_chksum(IP_PROTO_UDP);
#if 0
  chksum_ptr = (uint16_t)&uip_buf[20 + UIP_LLH_LEN];
  chksum_len = 20;  
  chksum_tmp = chksum();

  chksum_ptr = (uint16_t)uip_appdata;
  asm("lda _uip_buf+3+%b", UIP_LLH_LEN);
  asm("sec");
  asm("sbc #40");
  asm("sta _chksum_len");
  asm("lda _uip_buf+2+%b", UIP_LLH_LEN);
  asm("sbc #0");
  asm("sta _chksum_len+1");

  asm("jsr %v", chksum);

  asm("clc");
  asm("adc _chksum_tmp");
  asm("sta _chksum_tmp");
  asm("txa");
  asm("adc _chksum_tmp+1");
  asm("sta _chksum_tmp+1");

  /* Fold carry */
  /*  asm("bcc noinc");
  asm("inc _chksum_tmp");
  asm("noinc:");*/
  
  asm("tcpchksum_loop1:");
  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc #0");
  asm("sta _chksum_tmp+1");
  asm("bcs tcpchksum_loop1");

  asm("lda _uip_buf+3+%b", UIP_LLH_LEN);
  asm("sec");
  asm("sbc #20");
  asm("sta _chksum_len");
  asm("lda _uip_buf+2+%b", UIP_LLH_LEN);
  asm("sbc #0");
  asm("sta _chksum_len+1");

  asm("ldy #$0c");
  asm("clc");
  asm("php");
  asm("tcpchksum_loop2:");
  asm("plp");
  asm("lda _uip_buf+%b,y", UIP_LLH_LEN);
  asm("adc _chksum_tmp");
  asm("sta _chksum_tmp");
  asm("iny");
  asm("lda _uip_buf+%b,y", UIP_LLH_LEN);
  asm("adc _chksum_tmp+1");
  asm("sta _chksum_tmp+1");
  asm("iny");
  asm("php");
  asm("cpy #$14");
  asm("bne tcpchksum_loop2");

  asm("plp");

  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc #17");  /* IP_PROTO_UDP */
  asm("sta _chksum_tmp+1");

  asm("lda _chksum_tmp");
  asm("adc _chksum_len+1");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc _chksum_len");
  asm("sta _chksum_tmp+1");

  asm("tcpchksum_loop3:");
  asm("lda _chksum_tmp");
  asm("adc #0");
  asm("sta _chksum_tmp");
  asm("lda _chksum_tmp+1");
  asm("adc #0");
  asm("sta _chksum_tmp+1");
  asm("bcs tcpchksum_loop3");

  return chksum_tmp;
#endif
}
#endif /* UIP_UDP_CHECKSUMS */
/*-----------------------------------------------------------------------------------*/