/*---------------------------------------------------------------------------
   FT1000 driver for Flarion Flash OFDM NIC Device

   Copyright (C) 2002 Flarion Technologies, 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., 59 Temple Place -
   Suite 330, Boston, MA 02111-1307, USA.
  --------------------------------------------------------------------------

   Description:  This module will handshake with the DSP bootloader to
                 download the DSP runtime image.

---------------------------------------------------------------------------*/

#define __KERNEL_SYSCALLS__

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/unistd.h>
#include <linux/netdevice.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/vmalloc.h>

#include "ft1000.h"
#include "boot.h"

#ifdef FT_DEBUG
#define DEBUG(n, args...) printk(KERN_DEBUG args);
#else
#define DEBUG(n, args...)
#endif

#define  MAX_DSP_WAIT_LOOPS      100
#define  DSP_WAIT_SLEEP_TIME     1	/* 1 millisecond */

#define  MAX_LENGTH              0x7f0

#define  DWNLD_MAG_HANDSHAKE_LOC 0x00
#define  DWNLD_MAG_TYPE_LOC      0x01
#define  DWNLD_MAG_SIZE_LOC      0x02
#define  DWNLD_MAG_PS_HDR_LOC    0x03

#define  DWNLD_HANDSHAKE_LOC     0x02
#define  DWNLD_TYPE_LOC          0x04
#define  DWNLD_SIZE_MSW_LOC      0x06
#define  DWNLD_SIZE_LSW_LOC      0x08
#define  DWNLD_PS_HDR_LOC        0x0A

#define  HANDSHAKE_TIMEOUT_VALUE 0xF1F1
#define  HANDSHAKE_RESET_VALUE   0xFEFE	/* When DSP requests startover */
#define  HANDSHAKE_DSP_BL_READY  0xFEFE	/* At start DSP writes this when bootloader ready */
#define  HANDSHAKE_DRIVER_READY  0xFFFF	/* Driver writes after receiving 0xFEFE */
#define  HANDSHAKE_SEND_DATA     0x0000	/* DSP writes this when ready for more data */

#define  HANDSHAKE_REQUEST       0x0001	/* Request from DSP */
#define  HANDSHAKE_RESPONSE      0x0000	/* Satisfied DSP request */

#define  REQUEST_CODE_LENGTH     0x0000
#define  REQUEST_RUN_ADDRESS     0x0001
#define  REQUEST_CODE_SEGMENT    0x0002	/* In WORD count */
#define  REQUEST_DONE_BL         0x0003
#define  REQUEST_DONE_CL         0x0004
#define  REQUEST_VERSION_INFO    0x0005
#define  REQUEST_CODE_BY_VERSION 0x0006
#define  REQUEST_MAILBOX_DATA    0x0007
#define  REQUEST_FILE_CHECKSUM   0x0008

#define  STATE_START_DWNLD       0x01
#define  STATE_BOOT_DWNLD        0x02
#define  STATE_CODE_DWNLD        0x03
#define  STATE_DONE_DWNLD        0x04
#define  STATE_SECTION_PROV      0x05
#define  STATE_DONE_PROV         0x06
#define  STATE_DONE_FILE         0x07

u16 get_handshake(struct net_device *dev, u16 expected_value);
void put_handshake(struct net_device *dev, u16 handshake_value);
u16 get_request_type(struct net_device *dev);
long get_request_value(struct net_device *dev);
void put_request_value(struct net_device *dev, long lvalue);
u16 hdr_checksum(struct pseudo_hdr *pHdr);

struct dsp_file_hdr {
	u32  version_id;	// Version ID of this image format.
	u32  package_id;	// Package ID of code release.
	u32  build_date;	// Date/time stamp when file was built.
	u32  commands_offset;	// Offset to attached commands in Pseudo Hdr format.
	u32  loader_offset;	// Offset to bootloader code.
	u32  loader_code_address;	// Start address of bootloader.
	u32  loader_code_end;	// Where bootloader code ends.
	u32  loader_code_size;
	u32  version_data_offset;	// Offset were scrambled version data begins.
	u32  version_data_size;	// Size, in words, of scrambled version data.
	u32  nDspImages;	// Number of DSP images in file.
} __attribute__ ((packed));

struct dsp_image_info {
	u32  coff_date;		// Date/time when DSP Coff image was built.
	u32  begin_offset;	// Offset in file where image begins.
	u32  end_offset;	// Offset in file where image begins.
	u32  run_address;	// On chip Start address of DSP code.
	u32  image_size;	// Size of image.
	u32  version;		// Embedded version # of DSP code.
	unsigned short checksum;	// Dsp File checksum
	unsigned short pad1;
} __attribute__ ((packed));

void card_bootload(struct net_device *dev)
{
	struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev);
	unsigned long flags;
	u32 *pdata;
	u32 size;
	u32 i;
	u32 templong;

	DEBUG(0, "card_bootload is called\n");

	pdata = (u32 *) bootimage;
	size = sizeof(bootimage);

	// check for odd word
	if (size & 0x0003) {
		size += 4;
	}
	// Provide mutual exclusive access while reading ASIC registers.
	spin_lock_irqsave(&info->dpram_lock, flags);

	// need to set i/o base address initially and hardware will autoincrement
	ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, FT1000_DPRAM_BASE);
	// write bytes
	for (i = 0; i < (size >> 2); i++) {
		templong = *pdata++;
		outl(templong, dev->base_addr + FT1000_REG_MAG_DPDATA);
	}

	spin_unlock_irqrestore(&info->dpram_lock, flags);
}

u16 get_handshake(struct net_device *dev, u16 expected_value)
{
	struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev);
	u16 handshake;
	u32 tempx;
	int loopcnt;

	loopcnt = 0;
	while (loopcnt < MAX_DSP_WAIT_LOOPS) {
		if (info->AsicID == ELECTRABUZZ_ID) {
			ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR,
					 DWNLD_HANDSHAKE_LOC);

			handshake = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA);
		} else {
			tempx =
				ntohl(ft1000_read_dpram_mag_32
				  (dev, DWNLD_MAG_HANDSHAKE_LOC));
			handshake = (u16) tempx;
		}

		if ((handshake == expected_value)
			|| (handshake == HANDSHAKE_RESET_VALUE)) {
			return handshake;
		} else {
			loopcnt++;
			mdelay(DSP_WAIT_SLEEP_TIME);
		}

	}

	return HANDSHAKE_TIMEOUT_VALUE;

}

void put_handshake(struct net_device *dev, u16 handshake_value)
{
	struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev);
	u32 tempx;

	if (info->AsicID == ELECTRABUZZ_ID) {
		ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR,
				 DWNLD_HANDSHAKE_LOC);
		ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, handshake_value);	/* Handshake */
	} else {
		tempx = (u32) handshake_value;
		tempx = ntohl(tempx);
		ft1000_write_dpram_mag_32(dev, DWNLD_MAG_HANDSHAKE_LOC, tempx);	/* Handshake */
	}
}

u16 get_request_type(struct net_device *dev)
{
	struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev);
	u16 request_type;
	u32 tempx;

	if (info->AsicID == ELECTRABUZZ_ID) {
		ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_TYPE_LOC);
		request_type = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA);
	} else {
		tempx = ft1000_read_dpram_mag_32(dev, DWNLD_MAG_TYPE_LOC);
		tempx = ntohl(tempx);
		request_type = (u16) tempx;
	}

	return request_type;

}

long get_request_value(struct net_device *dev)
{
	struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev);
	long value;
	u16 w_val;

	if (info->AsicID == ELECTRABUZZ_ID) {
		ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR,
				 DWNLD_SIZE_MSW_LOC);

		w_val = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA);

		value = (long)(w_val << 16);

		ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR,
				 DWNLD_SIZE_LSW_LOC);

		w_val = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA);

		value = (long)(value | w_val);
	} else {
		value = ft1000_read_dpram_mag_32(dev, DWNLD_MAG_SIZE_LOC);
		value = ntohl(value);
	}

	return value;

}

void put_request_value(struct net_device *dev, long lvalue)
{
	struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev);
	u16 size;
	u32 tempx;

	if (info->AsicID == ELECTRABUZZ_ID) {
		size = (u16) (lvalue >> 16);

		ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR,
				 DWNLD_SIZE_MSW_LOC);

		ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, size);

		size = (u16) (lvalue);

		ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR,
				 DWNLD_SIZE_LSW_LOC);

		ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, size);
	} else {
		tempx = ntohl(lvalue);
		ft1000_write_dpram_mag_32(dev, DWNLD_MAG_SIZE_LOC, tempx);	/* Handshake */
	}

}

u16 hdr_checksum(struct pseudo_hdr *pHdr)
{
	u16 *usPtr = (u16 *) pHdr;
	u16 chksum;

	chksum = ((((((usPtr[0] ^ usPtr[1]) ^ usPtr[2]) ^ usPtr[3]) ^
			usPtr[4]) ^ usPtr[5]) ^ usPtr[6]);

	return chksum;
}

int card_download(struct net_device *dev, const u8 *pFileStart,
		  size_t FileLength)
{
	struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev);
	int Status = SUCCESS;
	u32 uiState;
	u16 handshake;
	struct pseudo_hdr *pHdr;
	u16 usHdrLength;
	long word_length;
	u16 request;
	u16 temp;
	struct prov_record *pprov_record;
	u8 *pbuffer;
	struct dsp_file_hdr *pFileHdr5;
	struct dsp_image_info *pDspImageInfoV6 = NULL;
	long requested_version;
	bool bGoodVersion = 0;
	struct drv_msg *pMailBoxData;
	u16 *pUsData = NULL;
	u16 *pUsFile = NULL;
	u8 *pUcFile = NULL;
	u8 *pBootEnd = NULL;
	u8 *pCodeEnd = NULL;
	int imageN;
	long file_version;
	long loader_code_address = 0;
	long loader_code_size = 0;
	long run_address = 0;
	long run_size = 0;
	unsigned long flags;
	unsigned long templong;
	unsigned long image_chksum = 0;

	file_version = *(long *)pFileStart;
	if (file_version != 6) {
		printk(KERN_ERR "ft1000: unsupported firmware version %ld\n", file_version);
		Status = FAILURE;
	}

	uiState = STATE_START_DWNLD;

	pFileHdr5 = (struct dsp_file_hdr *) pFileStart;

	pUsFile = (u16 *) ((long)pFileStart + pFileHdr5->loader_offset);
	pUcFile = (u8 *) ((long)pFileStart + pFileHdr5->loader_offset);
	pBootEnd = (u8 *) ((long)pFileStart + pFileHdr5->loader_code_end);
	loader_code_address = pFileHdr5->loader_code_address;
	loader_code_size = pFileHdr5->loader_code_size;
	bGoodVersion = false;

	while ((Status == SUCCESS) && (uiState != STATE_DONE_FILE)) {

		switch (uiState) {
		case STATE_START_DWNLD:

			handshake = get_handshake(dev, HANDSHAKE_DSP_BL_READY);

			if (handshake == HANDSHAKE_DSP_BL_READY) {
				put_handshake(dev, HANDSHAKE_DRIVER_READY);
			} else {
				Status = FAILURE;
			}

			uiState = STATE_BOOT_DWNLD;

			break;

		case STATE_BOOT_DWNLD:
			handshake = get_handshake(dev, HANDSHAKE_REQUEST);
			if (handshake == HANDSHAKE_REQUEST) {
				/*
				 * Get type associated with the request.
				 */
				request = get_request_type(dev);
				switch (request) {
				case REQUEST_RUN_ADDRESS:
					put_request_value(dev,
							  loader_code_address);
					break;
				case REQUEST_CODE_LENGTH:
					put_request_value(dev,
							  loader_code_size);
					break;
				case REQUEST_DONE_BL:
					/* Reposition ptrs to beginning of code section */
					pUsFile = (u16 *) ((long)pBootEnd);
					pUcFile = (u8 *) ((long)pBootEnd);
					uiState = STATE_CODE_DWNLD;
					break;
				case REQUEST_CODE_SEGMENT:
					word_length = get_request_value(dev);
					if (word_length > MAX_LENGTH) {
						Status = FAILURE;
						break;
					}
					if ((word_length * 2 + (long)pUcFile) >
						(long)pBootEnd) {
						/*
						 * Error, beyond boot code range.
						 */
						Status = FAILURE;
						break;
					}
					// Provide mutual exclusive access while reading ASIC registers.
					spin_lock_irqsave(&info->dpram_lock,
							  flags);
					/*
					 * Position ASIC DPRAM auto-increment pointer.
					 */
					outw(DWNLD_MAG_PS_HDR_LOC,
						 dev->base_addr +
						 FT1000_REG_DPRAM_ADDR);
					if (word_length & 0x01)
						word_length++;
					word_length = word_length / 2;

					for (; word_length > 0; word_length--) {	/* In words */
						templong = *pUsFile++;
						templong |=
							(*pUsFile++ << 16);
						pUcFile += 4;
						outl(templong,
							 dev->base_addr +
							 FT1000_REG_MAG_DPDATAL);
					}
					spin_unlock_irqrestore(&info->
								   dpram_lock,
								   flags);
					break;
				default:
					Status = FAILURE;
					break;
				}
				put_handshake(dev, HANDSHAKE_RESPONSE);
			} else {
				Status = FAILURE;
			}

			break;

		case STATE_CODE_DWNLD:
			handshake = get_handshake(dev, HANDSHAKE_REQUEST);
			if (handshake == HANDSHAKE_REQUEST) {
				/*
				 * Get type associated with the request.
				 */
				request = get_request_type(dev);
				switch (request) {
				case REQUEST_FILE_CHECKSUM:
					DEBUG(0,
						  "ft1000_dnld: REQUEST_FOR_CHECKSUM\n");
					put_request_value(dev, image_chksum);
					break;
				case REQUEST_RUN_ADDRESS:
					if (bGoodVersion) {
						put_request_value(dev,
								  run_address);
					} else {
						Status = FAILURE;
						break;
					}
					break;
				case REQUEST_CODE_LENGTH:
					if (bGoodVersion) {
						put_request_value(dev,
								  run_size);
					} else {
						Status = FAILURE;
						break;
					}
					break;
				case REQUEST_DONE_CL:
					/* Reposition ptrs to beginning of provisioning section */
					pUsFile = (u16 *) ((long)pFileStart + pFileHdr5->commands_offset);
					pUcFile = (u8 *) ((long)pFileStart + pFileHdr5->commands_offset);
					uiState = STATE_DONE_DWNLD;
					break;
				case REQUEST_CODE_SEGMENT:
					if (!bGoodVersion) {
						Status = FAILURE;
						break;
					}
					word_length = get_request_value(dev);
					if (word_length > MAX_LENGTH) {
						Status = FAILURE;
						break;
					}
					if ((word_length * 2 + (long)pUcFile) >
						(long)pCodeEnd) {
						/*
						 * Error, beyond boot code range.
						 */
						Status = FAILURE;
						break;
					}
					/*
					 * Position ASIC DPRAM auto-increment pointer.
					 */
					outw(DWNLD_MAG_PS_HDR_LOC,
						 dev->base_addr +
						 FT1000_REG_DPRAM_ADDR);
					if (word_length & 0x01)
						word_length++;
					word_length = word_length / 2;

					for (; word_length > 0; word_length--) {	/* In words */
						templong = *pUsFile++;
						templong |=
							(*pUsFile++ << 16);
						pUcFile += 4;
						outl(templong,
							 dev->base_addr +
							 FT1000_REG_MAG_DPDATAL);
					}
					break;

				case REQUEST_MAILBOX_DATA:
					// Convert length from byte count to word count. Make sure we round up.
					word_length =
						(long)(info->DSPInfoBlklen + 1) / 2;
					put_request_value(dev, word_length);
					pMailBoxData =
						(struct drv_msg *) & info->DSPInfoBlk[0];
					pUsData =
						(u16 *) & pMailBoxData->data[0];
					// Provide mutual exclusive access while reading ASIC registers.
					spin_lock_irqsave(&info->dpram_lock,
							  flags);
					if (file_version == 5) {
						/*
						 * Position ASIC DPRAM auto-increment pointer.
						 */
						ft1000_write_reg(dev,
								 FT1000_REG_DPRAM_ADDR,
								 DWNLD_PS_HDR_LOC);

						for (; word_length > 0; word_length--) {	/* In words */
							temp = ntohs(*pUsData);
							ft1000_write_reg(dev,
									 FT1000_REG_DPRAM_DATA,
									 temp);
							pUsData++;
						}
					} else {
						/*
						 * Position ASIC DPRAM auto-increment pointer.
						 */
						outw(DWNLD_MAG_PS_HDR_LOC,
							 dev->base_addr +
							 FT1000_REG_DPRAM_ADDR);
						if (word_length & 0x01) {
							word_length++;
						}
						word_length = word_length / 2;

						for (; word_length > 0; word_length--) {	/* In words */
							templong = *pUsData++;
							templong |=
								(*pUsData++ << 16);
							outl(templong,
								 dev->base_addr +
								 FT1000_REG_MAG_DPDATAL);
						}
					}
					spin_unlock_irqrestore(&info->
								   dpram_lock,
								   flags);
					break;

				case REQUEST_VERSION_INFO:
					word_length =
						pFileHdr5->version_data_size;
					put_request_value(dev, word_length);
					pUsFile =
						(u16 *) ((long)pFileStart +
							pFileHdr5->
							version_data_offset);
					// Provide mutual exclusive access while reading ASIC registers.
					spin_lock_irqsave(&info->dpram_lock,
							  flags);
					/*
					 * Position ASIC DPRAM auto-increment pointer.
					 */
					outw(DWNLD_MAG_PS_HDR_LOC,
						 dev->base_addr +
						 FT1000_REG_DPRAM_ADDR);
					if (word_length & 0x01)
						word_length++;
					word_length = word_length / 2;

					for (; word_length > 0; word_length--) {	/* In words */
						templong =
							ntohs(*pUsFile++);
						temp =
							ntohs(*pUsFile++);
						templong |=
							(temp << 16);
						outl(templong,
							 dev->base_addr +
							 FT1000_REG_MAG_DPDATAL);
					}
					spin_unlock_irqrestore(&info->
								   dpram_lock,
								   flags);
					break;

				case REQUEST_CODE_BY_VERSION:
					bGoodVersion = false;
					requested_version =
						get_request_value(dev);
					pDspImageInfoV6 =
						(struct dsp_image_info *) ((long)
								  pFileStart
								  +
								  sizeof
								  (struct dsp_file_hdr));
					for (imageN = 0;
						 imageN <
						 pFileHdr5->nDspImages;
						 imageN++) {
						temp = (u16)
							(pDspImageInfoV6->
							 version);
						templong = temp;
						temp = (u16)
							(pDspImageInfoV6->
							 version >> 16);
						templong |=
							(temp << 16);
						if (templong ==
							requested_version) {
							bGoodVersion =
								true;
							pUsFile =
								(u16
								 *) ((long)
								 pFileStart
								 +
								 pDspImageInfoV6->
								 begin_offset);
							pUcFile =
								(u8
								 *) ((long)
								 pFileStart
								 +
								 pDspImageInfoV6->
								 begin_offset);
							pCodeEnd =
								(u8
								 *) ((long)
								 pFileStart
								 +
								 pDspImageInfoV6->
								 end_offset);
							run_address =
								pDspImageInfoV6->
								run_address;
							run_size =
								pDspImageInfoV6->
								image_size;
							image_chksum =
								(u32)
								pDspImageInfoV6->
								checksum;
							DEBUG(0,
								  "ft1000_dnld: image_chksum = 0x%8x\n",
								  (unsigned
								   int)
								  image_chksum);
							break;
						}
						pDspImageInfoV6++;
					}
					if (!bGoodVersion) {
						/*
						 * Error, beyond boot code range.
						 */
						Status = FAILURE;
						break;
					}
					break;

				default:
					Status = FAILURE;
					break;
				}
				put_handshake(dev, HANDSHAKE_RESPONSE);
			} else {
				Status = FAILURE;
			}

			break;

		case STATE_DONE_DWNLD:
			if (((unsigned long) (pUcFile) - (unsigned long) pFileStart) >=
				(unsigned long) FileLength) {
				uiState = STATE_DONE_FILE;
				break;
			}

			pHdr = (struct pseudo_hdr *) pUsFile;

			if (pHdr->portdest == 0x80	/* DspOAM */
				&& (pHdr->portsrc == 0x00	/* Driver */
				|| pHdr->portsrc == 0x10 /* FMM */ )) {
				uiState = STATE_SECTION_PROV;
			} else {
				DEBUG(1,
					  "FT1000:download:Download error: Bad Port IDs in Pseudo Record\n");
				DEBUG(1, "\t Port Source = 0x%2.2x\n",
					  pHdr->portsrc);
				DEBUG(1, "\t Port Destination = 0x%2.2x\n",
					  pHdr->portdest);
				Status = FAILURE;
			}

			break;

		case STATE_SECTION_PROV:

			pHdr = (struct pseudo_hdr *) pUcFile;

			if (pHdr->checksum == hdr_checksum(pHdr)) {
				if (pHdr->portdest != 0x80 /* Dsp OAM */ ) {
					uiState = STATE_DONE_PROV;
					break;
				}
				usHdrLength = ntohs(pHdr->length);	/* Byte length for PROV records */

				// Get buffer for provisioning data
				pbuffer =
					kmalloc((usHdrLength + sizeof(struct pseudo_hdr)),
						GFP_ATOMIC);
				if (pbuffer) {
					memcpy(pbuffer, (void *)pUcFile,
						   (u32) (usHdrLength +
							   sizeof(struct pseudo_hdr)));
					// link provisioning data
					pprov_record =
						kmalloc(sizeof(struct prov_record),
							GFP_ATOMIC);
					if (pprov_record) {
						pprov_record->pprov_data =
							pbuffer;
						list_add_tail(&pprov_record->
								  list,
								  &info->prov_list);
						// Move to next entry if available
						pUcFile =
							(u8 *) ((unsigned long) pUcFile +
								   (unsigned long) ((usHdrLength + 1) & 0xFFFFFFFE) + sizeof(struct pseudo_hdr));
						if ((unsigned long) (pUcFile) -
							(unsigned long) (pFileStart) >=
							(unsigned long) FileLength) {
							uiState =
								STATE_DONE_FILE;
						}
					} else {
						kfree(pbuffer);
						Status = FAILURE;
					}
				} else {
					Status = FAILURE;
				}
			} else {
				/* Checksum did not compute */
				Status = FAILURE;
			}

			break;

		case STATE_DONE_PROV:
			uiState = STATE_DONE_FILE;
			break;

		default:
			Status = FAILURE;
			break;
		}		/* End Switch */

	}			/* End while */

	return Status;

}
