text: /*---------------------------------------------------------------------------
     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;
  
  }
  