/****************************************uart _ymodem.c**************************************************/

#include "uart_ymodem.h"
#include "osdep_service.h"
#include "PinNames.h"

/*****************************************************************************************
*                                uart basic functions                                    *
******************************************************************************************/
 void uarty_irq(uint32_t id, SerialIrq event)
{
	uart_ymodem_t *ptr = (uart_ymodem_t *)id;
	//u8 ch = 0;
	if(event == RxIrq) {
		if(ptr->uart_recv_index == 0){
			RtlUpSemaFromISR(&ptr->uart_rx_sema);//up uart rx semaphore
		}
		if(ptr->uart_recv_index == RCV_BUF_SIZE)
			ptr->uart_recv_index = 0;
		//ch = serial_getc(&ptr->sobj);
	//	printf("[%d] 0x%x\r\n", ptr->uart_recv_index, ch);
		ptr->uart_irq_buf[ptr->uart_recv_index++] = serial_getc(&ptr->sobj);
		ptr->tick_last_update =  xTaskGetTickCountFromISR();	// update tick everytime recved data 
	}
	
	if(event == TxIrq){
//		uart_send_string(sobj, "\r\n8195a$");
//		rcv_ch = 0;
	}
}

void uart_init(uart_ymodem_t *ptr)
{
//	serial_t sobj;

	//uart init
	serial_init(&ptr->sobj,UART_TX,UART_RX);
	serial_baud(&ptr->sobj,UART_BAUDRATE);		//set baudrate 38400
	serial_format(&ptr->sobj, 8, ParityNone, 0);

	serial_irq_handler(&ptr->sobj, uarty_irq, (int)ptr);
	serial_irq_set(&ptr->sobj, RxIrq, 1);
	serial_irq_set(&ptr->sobj, TxIrq, 1);
	
	RtlInitSema(&ptr->uart_rx_sema, 0);
}
void uart_sendbyte(uart_ymodem_t *ptr,u8 sendc )
{

	serial_putc(&ptr->sobj, sendc);
//	printf(" uart send 0x%x\r\n",sendc);
}

int uart_recvbytetimeout(uart_ymodem_t *uart_ymodem,u8 *ptr)
{
	int ret = 0;
//	static int uart_recv_buf_index = 0;

//	printf(" [%d] = %x\r\n",uart_ymodem->uart_recv_buf_index,uart_ymodem->uart_irq_buf[uart_ymodem->uart_recv_buf_index]);
	*ptr = uart_ymodem->uart_irq_buf[uart_ymodem->uart_recv_buf_index];
	uart_ymodem->uart_recv_buf_index++;
	if(uart_ymodem->uart_recv_buf_index == RCV_BUF_SIZE)
		uart_ymodem->uart_recv_buf_index = 0;
	return ret;
}

void uart_rxempty(uart_ymodem_t *ptr)
{
	/*clean uart recv buf*/
//	printf("Uart_RxEmpty\r\n");
	memset(ptr->uart_irq_buf, 0, RCV_BUF_SIZE);
	memset(ptr->uart_rcv_buf, 0, RCV_BUF_SIZE);
	ptr->uart_recv_buf_index = 0;
	ptr->uart_recv_index = 0;
}
/*****************************************************************************************
*                                       flash function                                                   *
******************************************************************************************/
int ymodem_flashwrite(int flashadd, u8 *pbuf, int len)
{
	
	int ret = 0;
	flash_t flash;
	
//	if(!FLASH_ADDRESS_CHECK_WRITE_ERASE(flashadd)){
//		ret = -1;
//		return ret;
//		}
	if( len == 0){
		printf("input error,data length should not be null!\r\n");
		ret = -1;
		return ret;
		}
	else	//as 8711am only canbe r/w in words.so make len is 4-bytes aligmented.
		len += 4 - ((len%4)==0 ? 4 : (len%4));
	
	while(len){
		if(flash_write_word(&flash, flashadd, *(unsigned int *)pbuf) !=1 ){
			printf("write flash error!\r\n");
			ret = -1;
			return ret;
		}
		len -= 4;
		pbuf += 4;
		flashadd += 4;
	}

	return ret;
}
/****************************uart_ymodem_init**********************************/
void uart_ymodem_init(uart_ymodem_t *uart_ymodem_ptr)
{
//	u32 ret = 0;
#if CONFIG_CALC_FILE_SIZE
	u8 filename[33] = {0}; //file name: max 32 bytes+ '\0'=33
#endif
	//init uart struct 
	uart_ymodem_ptr->cur_num = 0;
	uart_ymodem_ptr->filelen = 0 ;
#if CONFIG_CALC_FILE_SIZE
	uart_ymodem_ptr->filename = &filename[0];
#endif
	uart_ymodem_ptr->len = 0;
	uart_ymodem_ptr->nxt_num = 0;
	uart_ymodem_ptr->modemtype = 2;	 //ymodem protocol 
	uart_ymodem_ptr->rec_err = 0;
	uart_ymodem_ptr->crc_mode = 1;	//crc check
	uart_ymodem_ptr->uart_recv_buf_index = 0;
	uart_ymodem_ptr->uart_recv_index = 0;
	uart_ymodem_ptr->image_address = IMAGE_TWO;

//	return uart_ymodem_ptr;
}

void uart_ymodem_deinit(uart_ymodem_t *ptr)
{

	/* Free uart_rx-sema */
	RtlFreeSema(&ptr->uart_rx_sema);
	
	/* Free serial */
	serial_free(&ptr->sobj);

	/* Free uart_ymodem_t */
	RtlMfree((u8 *)ptr,sizeof(uart_ymodem_t));
}

#if CONFIG_CALC_FILE_SIZE
unsigned int buf_filelen(u8 *ptr)
{
	int datatype=10, result=0;

	if (ptr[0]=='0' && (ptr[1]=='x' && ptr[1]=='X'))
	{
		datatype = 16;
		ptr += 2;
	}

	for ( ; *ptr!='\0'; ptr++)
	{
		if (*ptr>= '0' && *ptr<='9')
		{
			result =result*datatype+*ptr-'0';
		}
		else
		{
			if (datatype == 10)
			{
				return result;
			}
			else
			{
				if (*ptr>='A' && *ptr<='F')
				{
					result = result*16 + *ptr-55;             //55 = 'A'-10
				}
				else if (*ptr>='a' && *ptr<='f')
				{
					result = result*16 + *ptr-87;             //87 = 'a'-10
				}
				else
				{
					return result;
				}
			}
		}
	}
	return result;
}
#endif
void modem_cancle(uart_ymodem_t *ptr)
{
	uart_sendbyte(ptr,0x18);
	uart_sendbyte(ptr,0x18);
	uart_sendbyte(ptr,0x18);
	uart_sendbyte(ptr,0x18);
	uart_sendbyte(ptr,0x18);
}

int start_next_round(uart_ymodem_t *ptr)
{
	int ret = 0;
	
//	printf(" uart ymodedm transfer %d block\r\n",ptr->nxt_num);
	//clean recv buf
	if(!ptr->rec_err){
		uart_rxempty(ptr);
	} 
	else{
		ret = -1;
		printf("\r\n recv data error!");
	}
	
	if (ptr->nxt_num == 0)
	{
		if (ptr->crc_mode)
		{
			uart_sendbyte(ptr,MODEM_C);	//first receiver send c
		}
		else
		{
			uart_sendbyte(ptr,MODEM_NAK);
		}
	}
	else
	{
		if (ptr->rec_err)
		{
			uart_sendbyte(ptr,MODEM_NAK);
		}
		else
		{
			if (ptr->nxt_num == 1)
			{
				if (ptr->crc_mode)
				{
					uart_sendbyte(ptr,MODEM_ACK);
					uart_sendbyte(ptr,MODEM_C);
				}
				else
				{
					uart_sendbyte(ptr,MODEM_NAK);
				}
			}
			else
			{
				uart_sendbyte(ptr,MODEM_ACK);
			}
		}
	}
	return ret;
}
int data_write_to_flash(uart_ymodem_t *ptr)
{
	int ret = 0;
//	uint32_t update_image_address = IMAGE_TWO;
	static int offset = 0x0;
	u32 data;
	static int flags = 1;	//write update image header only once
//  int file_blk_size = 0
	
	flash_read_word(&ptr->flash, OFFSET_DATA, &data);
//	file_blk_size = ((ptr->filelen - 1)/4096) + 1;
	if(data == ~0x0){
		flash_write_word(&ptr->flash, OFFSET_DATA, ptr->image_address);
	}
//	printf("image_address get from flash =  0x%x\n\r",ptr->image_address);
	//erase flash where to be written,since ymodem blk size can be 128 or 1024,so, erase once when gather 4096
	if(offset ==0 || (offset % 4096)==0){
		flash_erase_sector(&ptr->flash, ptr->image_address + offset);
	}
	//write to flash
	//write back image size and address
	if(!flags){
		flash_write_word(&ptr->flash, ptr->image_address, ptr->filelen);
		flash_write_word(&ptr->flash, ptr->image_address+4,0x10004000);
		flags = 1;
	}
//	ymodem_flashwrite(update_image_address + offset, ptr->uart_rcv_buf, ptr->len);
	device_mutex_lock(RT_DEV_LOCK_FLASH);
	flash_stream_write(&ptr->flash, ptr->image_address+offset, ptr->len, ptr->uart_rcv_buf);
	device_mutex_unlock(RT_DEV_LOCK_FLASH);
	offset += ptr->len;
	
	return ret;
}
int block_num_check(uart_ymodem_t *ptr)
{

	u8 blk,cblk;
	int stat, ret = 0;
	/**************** check blk and blk complement *********************/
	stat = uart_recvbytetimeout(ptr,&blk);	//blk num,bytes 2
	if (stat != 0)
	{
		ret = -1;
	}
	printf(" blk num = %x\r\n", blk);

	stat = uart_recvbytetimeout(ptr,&cblk);	//block num complement,bytes 3
	if (stat != 0)
	{
		ret = -1;
	}
//	printf(" block num cmpl = %x\r\n",cblk);

	if (blk+cblk != 0xff)
	{
		ret = -1;
	}
	return ret;

}

int calc_file_name_size(uart_ymodem_t *ptr,u8* bufptr)
{
	int ret = 0;
	u8* nameptr = ptr->filename;
	
	while (*bufptr != '\0'){
		*nameptr++ = *bufptr++;
	}
	*nameptr = '\0';
	bufptr++;
	while (*bufptr == ' ')
	{
		bufptr++;
	}
	//file length
	ptr->filelen = buf_filelen(bufptr);
	
	return ret;
}

int crc_check(uart_ymodem_t *ptr)
{
	u8 crch, crcl;
	u8 *in_ptr;
	int stat,i,ret = 0;
	u32 cksum = 0;

	stat = uart_recvbytetimeout(ptr,&crch);			//CRC byte 1
	if (stat != 0){
		ret = 1;
	}
//	printf(" char recved CRC byte 1 = %x\r\n", crch);
	if (ptr->crc_mode){
		stat = uart_recvbytetimeout(ptr,&crcl); 	//CRC byte 2
		if (stat != 0){
			ret = 1;
		}
	}
//	printf(" char recved CRC byte 2 = %x\r\n", crcl);
#if CRC_CHECK
	for (i=0; i<ptr->len; i++)			//sum check for last block
	{
		cksum += ptr->uart_rcv_buf[i];
	}
	if(cksum == 0)
	{ 
	 	ret = 2;
		return ret;
	}
	
	if (ptr->crc_mode)
	{
		in_ptr = ptr->uart_rcv_buf;
		cksum = 0;
		
		for (stat=ptr->len ; stat>0; stat--)
		{
			cksum = cksum^(int)(*in_ptr++) << 8;
			for (i=8; i !=0; i--)
			{
				if (cksum & 0x8000)
					cksum = cksum << 1 ^ 0x1021;
				else
					cksum = cksum << 1;
			}

		}
		cksum &= 0xffff;
		
		if (cksum != (crch<<8 | crcl))
		{
			ptr->rec_err = 1;
			ret = 1;
		}
	}
	else
	{
		for (i=0; i<ptr->len; i++)				//sum check
		{
			cksum += ptr->uart_rcv_buf[i];
		}
		if ((cksum&0xff)!=crch)
		{
			ptr->rec_err = 1;
			ret = 1;
		}
	}
#endif
	return ret;

}
#if DUMP_DATA
void flash_dump_data(uart_ymodem_t *ptr)
{
	int i,offset = 0;
	u32 data;
	printf("flash dump data");
	for(i = 0;i< ptr->filelen;i+=4){
		flash_read_word(&ptr->flash, ptr->image_address + 0x10 + offset, &data);
		offset += 4;
		printf("%x ",data);
	}
}
#endif
#if AUTO_REBOOT
void auto_reboot(void)
{
	// Set processor clock to default before system reset
	HAL_WRITE32(SYSTEM_CTRL_BASE, 0x14, 0x00000021);
	osDelay(100);

	// Cortex-M3 SCB->AIRCR
	HAL_WRITE32(0xE000ED00, 0x0C, (0x5FA << 16) |                             // VECTKEY
	                              (HAL_READ32(0xE000ED00, 0x0C) & (7 << 8)) | // PRIGROUP
	                              (1 << 2));                                  // SYSRESETREQ
	while(1) osDelay(1000);
}
#endif

int set_signature(uart_ymodem_t *ptr)
{
	int ret = 0;
	uint32_t sig_readback0,sig_readback1;
	uint32_t oldimg2addr;

	//old image address
	flash_read_word(&ptr->flash, 0x18, &oldimg2addr);
	oldimg2addr = (oldimg2addr&0xFFFF)*1024;
	printf(" lod image  address 0x%x\n\r",oldimg2addr);
	//Set signature in New Image 2 addr + 8 and + 12
//	flash_write_word(&ptr->flash,ptr->image_address + 8, 0x35393138);//0x35393138
//	flash_write_word(&ptr->flash,ptr->image_address + 12, 0x31313738);
//	flash_read_word(&ptr->flash, ptr->image_address + 8, &sig_readback0);
//	flash_read_word(&ptr->flash, ptr->image_address + 12, &sig_readback1);
//	printf(" new signature %x,%x,\n\r",sig_readback0, sig_readback1);
#if 1
	flash_write_word(&ptr->flash,oldimg2addr + 8, 0x35393130);
	flash_write_word(&ptr->flash,oldimg2addr + 12, 0x31313738);
	flash_read_word(&ptr->flash, oldimg2addr + 8, &sig_readback0);
	flash_read_word(&ptr->flash, oldimg2addr + 12, &sig_readback1);
	printf(" old signature %x,%x\n\r",sig_readback0, sig_readback1);
#endif
	printf(" set signature success!\n\r");

	return ret;
}

static void uart_ymodem_thread(void* param)
{
	u8 ch;
	u32 stat, error_bit = 0, transfer_over = 0;
	u32 can_counter = 0, eot_counter = 0;
	u32 i, send_count = 0 , ret = 0;
	static int first_time = 1;
	uart_ymodem_t *ymodem_ptr = (uart_ymodem_t *)param;
	printf(" ==>uart ymodem_task\r\n");
	while(1)
	{
		//wait 2min,2*60*1000/100
		if(send_count >= (2*60*10)){
			error_bit = 6;
			printf("no response after 2min\r\n");
			goto exit;
		}
		else{
			if (ymodem_ptr->crc_mode){
				uart_sendbyte(ymodem_ptr,MODEM_C);	//first receiver send c
			}
			else{
				uart_sendbyte(ymodem_ptr,MODEM_NAK);
			}
			send_count++; 
		}
		if(xSemaphoreTake(ymodem_ptr->uart_rx_sema, 0) == pdTRUE){
			RtlUpSema(&ymodem_ptr->uart_rx_sema);
			break;
		}
		else
			// send every 100ms
			vTaskDelay(100);
	}
start:
	while(xSemaphoreTake(ymodem_ptr->uart_rx_sema, portMAX_DELAY) == pdTRUE){
//		ymodem_ptr->tick_current = ymodem_ptr->tick_last_update = xTaskGetTickCount();
		ymodem_ptr->tick_current = xTaskGetTickCount();
		while((int)(ymodem_ptr->tick_current - ymodem_ptr->tick_last_update) < 50 ){
			ymodem_ptr->tick_current = xTaskGetTickCount();
			vTaskDelay(5);
		}
		printf("uart_recv_index = %d current=%d last=%d\r\n",ymodem_ptr->uart_recv_index, ymodem_ptr->tick_current, ymodem_ptr->tick_last_update);
		/*uart data recv done and process what we have recvied*/
		stat = uart_recvbytetimeout(ymodem_ptr,&ch);
		if (stat == 0)
		{
			switch (ch)
			{
				case MODEM_SOH :
					ymodem_ptr->len = 128;
//					printf(" char recved was MODEM_SOH!\r\n");
					break;
				case MODEM_STX :
					ymodem_ptr->len = 1024;
//					printf(" char recved was MODEM_STX!\r\n");
					break;
				case MODEM_CAN :
					if ((++can_counter) >= MODEM_CAN_COUNT)
					{
						error_bit = 1;
						goto exit;
					}
//					printf(" char recved was MODEM_CAN!\r\n");
					break;
				case MODEM_EOT :
//					printf(" char recved was MODEM_EOT!\r\n");
					if ((++eot_counter) >= MODEM_EOT_COUNT)
					{
						uart_sendbyte(ymodem_ptr,MODEM_ACK);
						if (ymodem_ptr->modemtype == 2)	//Ymodem protocol 
						{
							uart_sendbyte(ymodem_ptr,MODEM_C);			//first send a C
							uart_sendbyte(ymodem_ptr,MODEM_ACK);			//then send ack
							uart_sendbyte(ymodem_ptr,MODEM_C);			// and then send c
							modem_cancle(ymodem_ptr);					//cancel the transits
						}
						transfer_over = 1;
						goto exit;
					}
					else
					{
						uart_sendbyte(ymodem_ptr,MODEM_ACK);
						uart_sendbyte(ymodem_ptr,MODEM_C);
						goto start;
					}
					break;
				default:
					error_bit = 1;
					goto exit;
					break;
			}
		}

		//block num check
		if(block_num_check(ymodem_ptr)){
			error_bit = 2;
			goto exit;
		}
#if CONFIG_CALC_FILE_SIZE	
		// calculate file name and file size
		if(ymodem_ptr->nxt_num == 0 && first_time){
			error_bit = calc_file_name_size(ymodem_ptr,&ymodem_ptr->uart_irq_buf[3]);
//			first_time = 0;
		}
#endif	
		//copy data from uart irq buf to uart recv buf without header
		for (i=0; i<ymodem_ptr->len; i++)
		{
			stat = uart_recvbytetimeout(ymodem_ptr,&ymodem_ptr->uart_rcv_buf[i]);
//			printf(" data recv[%d] =%x\r\n",i,ymodem_ptr->uart_rcv_buf[i]);		
		}
		//write data to flash,but do not write first block data
		if(ymodem_ptr->nxt_num != 0 || !first_time){
			if(data_write_to_flash(ymodem_ptr)){
				error_bit = 3;
				goto exit;
			}
			first_time = 0;
		}
		//crc check
		ret = crc_check(ymodem_ptr);
		if(ret == 1){
			error_bit = 4;
			goto exit;
		}
		else if(ret == 2 && ymodem_ptr->nxt_num == 0xff){
			printf(" next num = %x\r\n",ymodem_ptr->nxt_num);
			transfer_over = 1;
			goto exit;
		}
	
#if 0 //avoid skip block
		uart_ymodem->cur_num = blk;
		if (blk != uart_ymodem->nxt_num)
		{
			error_bit = -1;
		}
#endif		
		ymodem_ptr->nxt_num++;
		ymodem_ptr->rec_err=0;
		//start another round
		if(start_next_round(ymodem_ptr)){
			error_bit = 5;
			printf(" start next round failed!\r\n");
			goto exit;		
		}
}
exit:
	//if anything goes wrong or transfer over,we kill ourself.
	if(error_bit || transfer_over){
		if(error_bit)
			printf("error!!! error bit = %d\r\n",error_bit);
		else{
			printf(" [%s, %d Bytes] transfer_over!\r\n",ymodem_ptr->filename,ymodem_ptr->filelen);	
			set_signature(ymodem_ptr);
#if DUMP_DATA
			flash_dump_data(ymodem_ptr);
#endif
#if AUTO_REBOOT
			auto_reboot();
#endif
		}
	}
	first_time = 1;
	uart_ymodem_deinit(ymodem_ptr);
	vTaskDelete(NULL);	
}

int uart_ymodem(void)
{
	int ret = 0;
	uart_ymodem_t *uart_ymodem_ptr;
	
	printf("uart ymodem update start\r\n");
	uart_ymodem_ptr = (uart_ymodem_t *)RtlMalloc(sizeof(uart_ymodem_t));
	if(!uart_ymodem_ptr){
		printf("uart ymodem malloc fail!\r\n");
		ret = -1;
		return ret;
	}
	uart_ymodem_init(uart_ymodem_ptr);
	if(ret == -1){
		ret = -1;
		return ret;
	}
	//uart initial
	uart_init(uart_ymodem_ptr);	
	if(xTaskCreate(uart_ymodem_thread, ((const char*)"uart_ymodem_thread"), UART_YMODEM_TASK_DEPTH, uart_ymodem_ptr, UART_YMODEM_TASK_PRIORITY, NULL) != pdPASS)
		printf("%s xTaskCreate(uart_thread) failed\r\n", __FUNCTION__);
	
	return ret;
}