/*
 * Copyright (c) 2003 Linuxant inc.
 * Copyright (c) 2001-2003 Conexant Systems, Inc.
 *
 * NOTE: The use and distribution of this software is governed by the terms in
 * the file LICENSE, which is included in the package. You must read this and
 * agree to these terms before using or distributing this software.
 *
 */
#include "oscompat.h"
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>

#include "framewrk.h"
#include "ossysenv.h"

#include "configtypes.h"
#include "configcodes.h"

#include "osnvm.h"
#include "osstring_ex.h"
#include "ostime_ex.h"
#include "osstdio.h"

typedef struct {
	struct list_head node;
	CFGMGR_CODE cfgcode;
	UINT32 size;
} NVMItem;

/* TODO: implement locking if simultaneous read/write access is required */
typedef struct list_head NVMCodeList;


typedef struct OS_NVM_TAG
{
	NVMCodeList		CodeList;
	BOOL			bModified;
	HANDLE			hWriteTimeOut;
}OS_NVM_T, *POS_NVM_T;

#ifdef DEBUG
#define caseretstr(x) case x: return #x

static inline char* eCodeStr(CFGMGR_CODE eCode)
{
	switch(eCode) {
		caseretstr(CFGMGR_MS_STR);
		caseretstr(CFGMGR_MS_PARAMS);
		caseretstr(CFGMGR_EC_PARAMS);
		caseretstr(CFGMGR_COMPRESSION_PARAMS);
		caseretstr(CFGMGR_COMPRESSION_ENABLE);
		caseretstr(CFGMGR_RETRAIN_ENABLED);
		caseretstr(CFGMGR_K56_USE_ULAW);
		caseretstr(CFGMGR_DCD_ON);
		caseretstr(CFGMGR_CADENCE_PARAMS);
		caseretstr(CFGMGR_ITF_PARAMS);
		caseretstr(CFGMGR_V44_PARAMS);
		caseretstr(CFGMGR_FORCE_RENEG_UP);
		caseretstr(CFGMGR_FORCE_RENEG_DOWN);
		caseretstr(CFGMGR_IN_V34FAX);
		//caseretstr(CFGMGR_SREG);
		caseretstr(CFGMGR_NRINGS_TO_ANSWER);
		caseretstr(CFGMGR_RING_COUNTER);
		caseretstr(CFGMGR_ESCAPE_CHAR);
		caseretstr(CFGMGR_CR_CHAR);
		caseretstr(CFGMGR_LF_CHAR);
		caseretstr(CFGMGR_BS_CHAR);
		caseretstr(CFGMGR_BLIND_DIAL_WAIT_TIME);
		caseretstr(CFGMGR_CARRIER_WAIT_TIME);
		caseretstr(CFGMGR_PAUSE_DELAY_TIME);
		caseretstr(CFGMGR_CARDET_RESPONSE_TIME);
		caseretstr(CFGMGR_HANGUP_DELAY_TIME);
		caseretstr(CFGMGR_DTMF_DURATION);
		caseretstr(CFGMGR_ESCAPE_PROMPT_DELAY);
		caseretstr(CFGMGR_TEST_TIMER);
		caseretstr(CFGMGR_FLASH_DELAY_TIME);
		caseretstr(CFGMGR_INACTIVITY_TIME);
		caseretstr(CFGMGR_DATA_COMPRESSION);
		caseretstr(CFGMGR_EXTENDED_RESULT_CODES);
		caseretstr(CFGMGR_SREG_LAST);
		caseretstr(CFGMGR_DIALSTRING);
		caseretstr(CFGMGR_PULSE_DIAL_CONFIG);
		caseretstr(CFGMGR_TONE_DIAL_CONFIG);
		caseretstr(CFGMGR_DIALTONE_WAIT_TIME);
		caseretstr(CFGMGR_DIAL_MODE);
		caseretstr(CFGMGR_AMPERP);
		caseretstr(CFGMGR_USE_S7_WHEN_W);
		caseretstr(CFGMGR_INVERT_CALLING_TONE);
		caseretstr(CFGMGR_CALL_PROGRESS_TONE_DEBOUNCE);
		caseretstr(CFGMGR_CALL_MODE);
		caseretstr(CFGMGR_CALL_PROGRESS_TIMING);
		caseretstr(CFGMGR_CALL_PROGRESS_FLAGS);
		caseretstr(CFGMGR_V8BIS_CONTROL);
		caseretstr(CFGMGR_V8BIS_RESULT);
		caseretstr(CFGMGR_FIRST_CONNECTION_AFTER_TRAINING);
		caseretstr(CFGMGR_PREFERRED_FLEX_VERSION);
		caseretstr(CFGMGR_V90_ENABLED);
		caseretstr(CFGMGR_V70_ALLOWED);
		caseretstr(CFGMGR_ATRESULT_OK);
		caseretstr(CFGMGR_ATRESULT_CONNECT);
		caseretstr(CFGMGR_ATRESULT_RING);
		caseretstr(CFGMGR_ATRESULT_NOCARRIER);
		caseretstr(CFGMGR_ATRESULT_ERROR);
		caseretstr(CFGMGR_ATRESULT_NODIALTONE);
		caseretstr(CFGMGR_ATRESULT_BUSY);
		caseretstr(CFGMGR_ATRESULT_NOANSWER);
		caseretstr(CFGMGR_ATRESULT_DELAYED);
		caseretstr(CFGMGR_ATRESULT_BLACKLISTED);
		caseretstr(CFGMGR_ATRESULT_FAX);
		caseretstr(CFGMGR_ATRESULT_DATA);
		caseretstr(CFGMGR_ATRESULT_FCERROR);
		caseretstr(CFGMGR_MCR_B103);
		caseretstr(CFGMGR_MCR_B212);
		caseretstr(CFGMGR_MCR_V21);
		caseretstr(CFGMGR_MCR_V22);
		caseretstr(CFGMGR_MCR_V22B);
		caseretstr(CFGMGR_MCR_V23);
		caseretstr(CFGMGR_MCR_V32);
		caseretstr(CFGMGR_MCR_V32B);
		caseretstr(CFGMGR_MCR_V34);
		caseretstr(CFGMGR_MCR_K56);
		caseretstr(CFGMGR_MCR_V90);
		caseretstr(CFGMGR_ER_NONE);
		caseretstr(CFGMGR_ER_LAPM);
		caseretstr(CFGMGR_ER_ALT);
		caseretstr(CFGMGR_DR_ALT);
		caseretstr(CFGMGR_DR_V42B);
		caseretstr(CFGMGR_DR_NONE);
		caseretstr(CFGMGR_ATMESSAGE_I0);
		caseretstr(CFGMGR_ATMESSAGE_I1);
		caseretstr(CFGMGR_ATMESSAGE_I2);
		caseretstr(CFGMGR_ATMESSAGE_I3);
		caseretstr(CFGMGR_ATMESSAGE_I4);
		caseretstr(CFGMGR_ATMESSAGE_I5);
		caseretstr(CFGMGR_ATMESSAGE_I6);
		caseretstr(CFGMGR_ATMESSAGE_I7);
		caseretstr(CFGMGR_ATMESSAGE_I8);
		caseretstr(CFGMGR_ATMESSAGE_I9);
		caseretstr(CFGMGR_ATMESSAGE_I10);
		caseretstr(CFGMGR_ATMESSAGE_I11);
		caseretstr(CFGMGR_ATMESSAGE_I12);
		caseretstr(CFGMGR_ATMESSAGE_MANUFACTURER);
		caseretstr(CFGMGR_ATMESSAGE_MODEL);
		caseretstr(CFGMGR_ATMESSAGE_REVISION);
		caseretstr(CFGMGR_ATMESSAGE_SERIAL_NUM);
		caseretstr(CFGMGR_ATMESSAGE_GOI);
		caseretstr(CFGMGR_ATMESSAGE_CAPABILITIES);
		caseretstr(CFGMGR_DTE_ECHO);
		caseretstr(CFGMGR_DTE_CONFIG);
		caseretstr(CFGMGR_SYNC_MODE);
		caseretstr(CFGMGR_RLSD_BEHAVIOR);
		caseretstr(CFGMGR_DTR_BEHAVIOR);
		caseretstr(CFGMGR_SPEAKER_VOLUME);
		caseretstr(CFGMGR_SPEAKER_CONTROL);
		caseretstr(CFGMGR_PULSE_MAKE_BREAK);
		caseretstr(CFGMGR_RING_BURST);
		caseretstr(CFGMGR_ANSWER_TONE_DETECTOR);
		caseretstr(CFGMGR_BELL_TONE_DETECTOR);
		caseretstr(CFGMGR_DTMF_GENERATOR);
		caseretstr(CFGMGR_MODULATION_REPORT);
		//caseretstr(CFGMGR_CADENCE_BASE);
		caseretstr(CFGMGR_BUSY_TONE_CADENCE);
		caseretstr(CFGMGR_RING_CADENCE);
		caseretstr(CFGMGR_REORDER_TONE_CADENCE);
		caseretstr(CFGMGR_SDIAL_TONE_CADENCE);
		caseretstr(CFGMGR_FAX_CALL_TONE_CADENCE);
		caseretstr(CFGMGR_FAX_AUTO_ANSWER);
		caseretstr(CFGMGR_OEM_FLAGS);
		caseretstr(CFGMGR_OEM_FLAGS2);
		caseretstr(CFGMGR_OEM_FILTERS);
		caseretstr(CFGMGR_OEM_SPKR_MUTE_DELAY);
		caseretstr(CFGMGR_OEM_READ_MASK);
		caseretstr(CFGMGR_OEM_THRESHOLD);
		caseretstr(CFGMGR_COUNTRY_CODE);
		caseretstr(CFGMGR_PREVIOUS_COUNTRY_CODE);
		caseretstr(CFGMGR_COUNTRY_NAME);
		caseretstr(CFGMGR_COUNTRY_CODE_LIST);
		//caseretstr(CFGMGR_COUNTRY_STRUCT);
		caseretstr(CFGMGR_COUNTRY_BLIST);
		caseretstr(CFGMGR_FILTERS);
		caseretstr(CFGMGR_DTMF);
		caseretstr(CFGMGR_SMART_DTMF);
		caseretstr(CFGMGR_RING_PARAMS);
		caseretstr(CFGMGR_RLSDOFFSET);
		caseretstr(CFGMGR_THRESHOLD);
		caseretstr(CFGMGR_TXLEVELS);
		caseretstr(CFGMGR_SMART_TXLEVELS);
		caseretstr(CFGMGR_RELAYS);
		caseretstr(CFGMGR_SMART_RELAYS);
		caseretstr(CFGMGR_SPEED_ADJUST);
		caseretstr(CFGMGR_SREG_LIMITS);
		caseretstr(CFGMGR_PR_IGNORE_TIME);
#ifdef CW_CID_SUPPORTED
		caseretstr(CFGMGR_COUNTRY_CALLERID1);
		caseretstr(CFGMGR_COUNTRY_CALLERID2);
		caseretstr(CFGMGR_COUNTRY_CALL_WAITING);
		caseretstr(CFGMGR_VCID_FORMAT);
		caseretstr(CFGMGR_VRID_FORMAT);
		caseretstr(CFGMGR_CALL_WAITING);
		caseretstr(CFGMGR_CALLER_ID_INFO);
		caseretstr(CFGMGR_VRID_CRC);
		caseretstr(CFGMGR_VRID_TYPE);
		caseretstr(CFGMGR_VRID_LENGTH);
		caseretstr(CFGMGR_VRID_DATA);
#endif
#ifdef V92_SUPPORTED
		caseretstr(CFGMGR_MOH);
		caseretstr(CFGMGR_PCMUPSTREAM);
		caseretstr(CFGMGR_QC);
#endif // V92_SUPPORTED
		caseretstr(CFGMGR_PROFILE_STORED);
		caseretstr(CFGMGR_PROFILE_FACTORY);
		caseretstr(CFGMGR_POUND_UD);
		caseretstr(CFGMGR_ATPARSER_ONLINE);
		caseretstr(CFGMGR_BLACK_LIST);
		caseretstr(CFGMGR_SMARTDAA);
		caseretstr(CFGMGR_TONE_PRIMARY_PARAMS);
		caseretstr(CFGMGR_TONE_ALTERNATE_PARAMS);
		caseretstr(CFGMGR_TONE_ATBEL_PARAMS);
		caseretstr(CFGMGR_TONE_ATV25_PARAMS);
		caseretstr(CFGMGR_TONE_1100_PARAMS);
		caseretstr(CFGMGR_TONE_2130_PARAMS);
		caseretstr(CFGMGR_TONE_2312_PARAMS);
		caseretstr(CFGMGR_TONE_2750_PARAMS);
		caseretstr(CFGMGR_TONE_2912_PARAMS);
		caseretstr(CFGMGR_VOICETONEACALLPROG_PARAMS);
		caseretstr(CFGMGR_VOICETONEB_1100_PARAMS);
		caseretstr(CFGMGR_VOICETONEC_1300_PARAMS);
		caseretstr(CFGMGR_VOICETONEC_2100_PARAMS);
		caseretstr(CFGMGR_VOICETONEB_2225_PARAMS);
		caseretstr(CFGMGR_V8BTONE_2100_PARAMS);
		caseretstr(CFGMGR_V8BTONE_2225_PARAMS);
		caseretstr(CFGMGR_CI_CENTER_TONEB_PARAMS);
		caseretstr(CFGMGR_CI_SIDE_TONEC_PARAMS);
		caseretstr(CFGMGR_VOICEVIEW_TONEC_2912_PARAMS);
		caseretstr(CFGMGR_VOICEVIEW_TONEB_2312_PARAMS);
		caseretstr(CFGMGR_VOICEVIEW_TONEB_2750_PARAMS);
		caseretstr(CFGMGR_VOICEVIEW_TONEA_2130_PARAMS);
		caseretstr(CFGMGR_VOICEVIEW_TONEA_NOT_ST_PARAMS);
		caseretstr(CFGMGR_TONEC_1650_PARAMS);
		caseretstr(CFGMGR_CReSEG1_2002PARAMS);
		caseretstr(CFGMGR_TONE_GEN_V25CALL);
		caseretstr(CFGMGR_TONE_GEN_CNG);
		caseretstr(CFGMGR_TONE_GEN_CED);
		caseretstr(CFGMGR_TONE_GEN_CI);
		caseretstr(CFGMGR_BUFFERS_DELAY);
		caseretstr(CFGMGR_PCI_DEVICE_ID);
		caseretstr(CFGMGR_PCI_VENDOR_ID);
		caseretstr(CFGMGR_OEM_HWDATA);
		caseretstr(CFGMGR_OEM_DAATYPE);
		caseretstr(CFGMGR_OEM_SMARTDAA_PARAMS);
		caseretstr(CFGMGR_OEM_SMARTDAA_PARAMSK3);
		caseretstr(CFGMGR_HW_ADAPTER_TYPE);
		caseretstr(CFGMGR_CTRY_TONE);
		caseretstr(CFGMGR_CTRY_CADENCE);
		caseretstr(CFGMGR_CTRY_CALLERID);
		caseretstr(CFGMGR_LAST);
		default: {
					 static char buf[80];

					 if((eCode & 0xffff) == CFGMGR_COUNTRY_STRUCT) {
					 	sprintf(buf, "CFGMGR_COUNTRY_STRUCT_%02x\n", eCode >> 16);
					 } else {
					 	sprintf(buf, "code_%d\n", eCode);
					 }
					 return buf;
				 }
	}
}
#endif // DEBUG

static COM_STATUS      NVM_SaveAll (NVMCodeList *pCodeList)
{
	FILE *myfile;
	int count;
	COM_STATUS ret = COM_STATUS_FAIL;
	struct list_head *p;
	unsigned long numItems;

	myfile = OsFOpen(HSFBININF_FILE, "w");
	if (myfile == NULL)
	{
		printk(KERN_ERR "%s: Could not open \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	if(OsFWrite((void*)NVM_MAGIC_HSF_VERSION, NVM_MAGIC_LENGTH, 1, myfile) != 1) {
		printk(KERN_ERR "%s: Error writing 'NVM_MAGIC_HSF_VERSION' to \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	numItems = sizeof(HSFLINUXVERSION);
	if(OsFWrite((void*)&numItems, sizeof(numItems), 1, myfile) != 1) {
		printk(KERN_ERR "%s: Error writing 'sizeof(HSFLINUXVERSION)' to \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	if(OsFWrite((void*)HSFLINUXVERSION, sizeof(HSFLINUXVERSION), 1, myfile) != 1) {
		printk(KERN_ERR "%s: Error writing HSFLINUXVERSION' to \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	for(numItems = 0, p = pCodeList->next; p != pCodeList; p = p->next) {
		numItems++;
	}

	if(OsFWrite((void*)&numItems, sizeof(numItems), 1, myfile) != 1) {
		printk(KERN_ERR "%s: Error writing 'numItems' to \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	for(count = 1, p = pCodeList->next; p != pCodeList; p = p->next, count++) {
		NVMItem *item = (NVMItem *)p;

		if(OsFWrite((void*)&item->cfgcode, sizeof(item->cfgcode), 1, myfile) != 1) {
			printk(KERN_ERR "%s: Error writing 'CfgCode' to \"%s\" (count=%d)\n", __FUNCTION__, HSFBININF_FILE, count );
			goto out;
		}

		if(OsFWrite((void*)&item->size, sizeof(item->size), 1, myfile) != 1) {
			printk(KERN_ERR "%s: Error writing 'ItemSize' to \"%s\" (count=%d)\n", __FUNCTION__, HSFBININF_FILE, count );
			goto out;
		}

		if(item->size && (OsFWrite((void*)(item+1), item->size, 1, myfile) != 1)) {
			printk(KERN_ERR "%s: Error writing item CfgCode=%d ItemSize=%ld to \"%s\" (count=%d)\n", __FUNCTION__, item->cfgcode, item->size, HSFBININF_FILE, count );
			goto out;
		}

#if 0
		printk(KERN_INFO "%s: wrote %d of %ld to \"%s\": CfgCode=%s Size=%ld\n", __FUNCTION__, count, numItems, HSFBININF_FILE,
				eCodeStr(item->cfgcode),
				item->size
				);
#endif
	}

	ret = COM_STATUS_SUCCESS;

out:

	if (myfile)
		OsFClose(myfile);

	return ret;
}

STATIC VOID
NvmWriteCallBack(PVOID arg)
{
	POS_NVM_T pNvm = (POS_NVM_T)arg;
	static unsigned long entered;

	if(test_and_set_bit(0, &entered))
		return;

	if(pNvm->hWriteTimeOut) /* disable periodic timeout */
		OSSetPeriodicTimeOut(pNvm->hWriteTimeOut, 0);

	if (NVM_SaveAll(&pNvm->CodeList) == COM_STATUS_SUCCESS)
		pNvm->bModified = FALSE;

	clear_bit(0, &entered);
}


#define NVM_MAX_FIRMWARE_SIZE 80*1024

static NVM_FIRMWARW_MEM_TAG YukonFirmware;

static COM_STATUS
NvmLoadFirmware(POS_NVM_T pNvm)
{
	FILE *YkFile;
	HW_TYPE HwType = HW_TYPE_NONE;
	UINT32 Size;

	Size = 1;
	/* handle the yukon firmware file */
	NVM_Read((HANDLE)pNvm, CFGMGR_HW_ADAPTER_TYPE, &HwType, &Size);

	if ( HW_TYPE_YUKON != HwType )
		return COM_STATUS_SUCCESS;

	if(YukonFirmware.HexData) {
		vfree(YukonFirmware.HexData);
		YukonFirmware.HexData = NULL;
		YukonFirmware.DataSize = 0;
	}

	YkFile = OsFOpen(HSFFIRMWR_FILE, "r");

	if ( YkFile == NULL ) {
		printk( KERN_ERR "%s: Could not open %s\n", __FUNCTION__, HSFFIRMWR_FILE );
		return COM_STATUS_FAIL;
	}
		
		/* the size of the data */
	if(OsFRead((void*)&YukonFirmware.DataSize, sizeof(YukonFirmware.DataSize), 1, YkFile) != 1) {
		YukonFirmware.DataSize = 0;
		OsFClose(YkFile);
		printk(KERN_ERR "%s: Error reading 'YukonFirmware.DataSize' from \"%s\"\n", __FUNCTION__, HSFFIRMWR_FILE );
		return COM_STATUS_FAIL;
	}

	YukonFirmware.HexData = vmalloc(YukonFirmware.DataSize);
	if ( NULL == YukonFirmware.HexData )
	{
		YukonFirmware.DataSize = 0;
		OsFClose(YkFile);
		printk(KERN_ERR "%s: Could not allocate memory for Yukon firmware data \n", __FUNCTION__ );
		return COM_STATUS_FAIL;
	}

	/* read the hex-file */
	if(OsFRead((void*)YukonFirmware.HexData, YukonFirmware.DataSize, 1, YkFile) != 1) {
		vfree(YukonFirmware.HexData);
		YukonFirmware.HexData = NULL;
		YukonFirmware.DataSize = 0;
		OsFClose(YkFile);
		printk(KERN_ERR "%s: Error reading %ld bytes from \"%s\"\n", __FUNCTION__, YukonFirmware.DataSize, HSFFIRMWR_FILE );
		return COM_STATUS_FAIL;
	}

	OsFClose(YkFile);

	return COM_STATUS_SUCCESS;
}

static void
NvmFreeFirmware(void)
{
	if(YukonFirmware.HexData) {
		vfree(YukonFirmware.HexData);
		YukonFirmware.HexData = NULL;
		YukonFirmware.DataSize = 0;
	}
}

HANDLE      NVM_Open    (UINT32 dwDevNode)
{
	FILE *myfile;
	int count;
	POS_NVM_T pNvm = NULL;
	HANDLE ret = NULL;
	unsigned long numItems;
	unsigned char versionMagic[NVM_MAGIC_LENGTH + sizeof(HSFLINUXVERSION)];

	ASSERT(OSContextAllowsSleeping());

	myfile = OsFOpen(HSFBININF_FILE, "r");
	if ( myfile == NULL )
	{
		printk(KERN_ERR "%s: Could not open \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	if(OsFRead((void*)versionMagic, NVM_MAGIC_LENGTH, 1, myfile) != 1) {
		printk(KERN_ERR "%s: Error reading 'versionMagic' from \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	if(OsMemCmp(versionMagic, NVM_MAGIC_HSF_VERSION, NVM_MAGIC_LENGTH)) {
		printk(KERN_ERR "%s: Unsupported NVM file format (%02X%02X%02X%02X) \"%s\"\n", __FUNCTION__, versionMagic[0], (int)versionMagic[1], (int)versionMagic[2], (int)versionMagic[3], HSFBININF_FILE );
		goto out;
	}

	if(OsFRead((void*)&numItems, sizeof(numItems), 1, myfile) != 1) {
		printk(KERN_ERR "%s: Error reading 'numItems' from \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	if(numItems != sizeof(HSFLINUXVERSION)) {
		printk(KERN_ERR "%s: Inconsistent file version (\"%s\"); rebuild with \"hsfconfig --nvram\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	if(OsFRead((void*)versionMagic, numItems, 1, myfile) != 1) {
		printk(KERN_ERR "%s: Error reading 'versionMagic' from \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	if(OsMemCmp(versionMagic, HSFLINUXVERSION, sizeof(HSFLINUXVERSION))) {
		printk(KERN_ERR "%s: Inconsistent file version (\"%s\"); rebuild with \"hsfconfig --nvram\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	if(OsFRead((void*)&numItems, sizeof(numItems), 1, myfile) != 1) {
		printk(KERN_ERR "%s: Error reading 'numItems' from \"%s\"\n", __FUNCTION__, HSFBININF_FILE );
		goto out;
	}

	pNvm = kmalloc(sizeof(*pNvm), GFP_KERNEL);
	if(!pNvm) {
		printk(KERN_ERR "%s: Cannot allocate memory for pNvm\n", __FUNCTION__);
		goto out;
	}

	INIT_LIST_HEAD(&pNvm->CodeList);

	pNvm->bModified = FALSE;

	pNvm->hWriteTimeOut = OSCreatePeriodicTimeOut(0, 0, NvmWriteCallBack, NULL, NULL, pNvm);
	if(!pNvm->hWriteTimeOut) {
		goto out;
	}

	for(count = 1; count <= numItems; count++) {
		CFGMGR_CODE CfgCode;
		UINT32 ItemSize;
		NVMItem *newItem;

		if(OsFRead((void*)&CfgCode, sizeof(CfgCode), 1, myfile) != 1) {
			printk(KERN_ERR "%s: Error reading 'CfgCode' from \"%s\" (count=%d)\n", __FUNCTION__, HSFBININF_FILE, count );
			goto out;
		}

		if(OsFRead((void*)&ItemSize, sizeof(ItemSize), 1, myfile) != 1) {
			printk(KERN_ERR "%s: Error reading 'ItemSize' from \"%s\" (count=%d)\n", __FUNCTION__, HSFBININF_FILE, count );
			goto out;
		}

		newItem = kmalloc(sizeof(*newItem) + ItemSize, GFP_KERNEL);
		if(!newItem) {
			goto out;
		}

		newItem->cfgcode = CfgCode;
		newItem->size = ItemSize;

		if(ItemSize && (OsFRead((void*)(newItem + 1), ItemSize, 1, myfile) != 1)) {
			printk(KERN_ERR "%s: Error reading item CfgCode=%d ItemSize=%ld from \"%s\" (count=%d)\n", __FUNCTION__, CfgCode, ItemSize, HSFBININF_FILE, count );
			kfree(newItem);
			goto out;
		}

		list_add(&newItem->node, pNvm->CodeList.prev);

#if 0
		printk(KERN_INFO "%s: read %d of %ld from \"%s\": CfgCode=%s Size=%ld\n", __FUNCTION__, count, numItems, HSFBININF_FILE,
				eCodeStr(CfgCode),
				ItemSize
				);
#endif
	}

	if (NvmLoadFirmware(pNvm) != COM_STATUS_SUCCESS) {
		goto out;
	}

	ret = (HANDLE)pNvm;

out:
	if(!ret && pNvm) {
		/* an error occured; free pNvm and any items partially read */
		NVM_Close((HANDLE)pNvm);
	}

	if (myfile)
		OsFClose(myfile);

	//printk(KERN_INFO "%s: returning hNVM=%p\n", __FUNCTION__, ret);
	return ret;
}

COM_STATUS  NVM_Close   (HANDLE hNVM)
{
	POS_NVM_T pNvm = (POS_NVM_T)hNVM;
	struct list_head *p;

	//printk(KERN_INFO "%s: hNVM=%p\n", __FUNCTION__, hNVM);

	if(!pNvm)
		return COM_STATUS_NULL_POINTER;

	NvmFreeFirmware();

	if(pNvm->bModified)
		NvmWriteCallBack(pNvm);

	p = pNvm->CodeList.next;
	while(p != &pNvm->CodeList) {
		struct list_head *tmp;

		tmp = p->next;
		kfree(p);
		p = tmp;
	}

	if(pNvm->hWriteTimeOut)
		OSDestroyPeriodicTimeOut(pNvm->hWriteTimeOut);

	kfree(pNvm);
	return COM_STATUS_SUCCESS;
}

COM_STATUS  NVM_Read (HANDLE hNVM, CFGMGR_CODE eCode, PVOID pBuf, PUINT32 pdwSize)
{
	POS_NVM_T pNvm = (POS_NVM_T)hNVM;
	struct list_head *p;
	UINT32 Size, AltSize;

	if  (hNVM == NULL) {
		ASSERT(hNVM != NULL);
		return (COM_STATUS_NULL_POINTER);
	}

	if(eCode == CFGMGR_COUNTRY_STRUCT) {
		eCode |= *((UINT16*)pdwSize) << 16;
		AltSize = sizeof(CtryPrmsStruct);
		pdwSize = &AltSize;
	}

	/* for reading the firmware of yukon */
	if ( CFGMGR_YUKON_FIRMWARE_DATA == eCode )
	{
/*  		printk("NvmRead ,CFGMGR_YUKON_FIRMWARE_DATA\n"); */
		if ( NULL != pBuf )
		{
			OsMemCpy(pBuf, &YukonFirmware, sizeof(YukonFirmware));
			return COM_STATUS_SUCCESS;
		}
//		(PBYTE *)buff =  &YukonFirmware.HexFile;
	}

	if ( CFGMGR_YUKON_FIRMWARE_DELETE == eCode )
	{
/*  		printk("NvmRead ,CFGMGR_YUKON_FIRMWARE_DELETE\n"); */
		NvmFreeFirmware();
		return COM_STATUS_SUCCESS;
	}

	for(p = pNvm->CodeList.next; p != &pNvm->CodeList; p = p->next) {
		NVMItem *item = (NVMItem *)p;

		if(item->cfgcode == eCode) {
			Size = item->size;
			if(pdwSize && *pdwSize < Size)
				Size = *pdwSize;
			if(pBuf != NULL)
				memcpy(pBuf, (unsigned char *)(item + 1), Size);
			if(pdwSize)
				*pdwSize = Size;

			return COM_STATUS_SUCCESS;
		}
	}

	//printk(KERN_ERR "%s: eCode=%s not found!\n", __FUNCTION__, eCodeStr(eCode));

	return COM_STATUS_VALUE_NOT_FOUND;
}

COM_STATUS  NVM_Write   (HANDLE hNVM, CFGMGR_CODE eCode, PVOID pBuf, PUINT32 pdwSize)
{
	POS_NVM_T pNvm = (POS_NVM_T)hNVM;
	struct list_head *p;
	NVMItem *newItem;

	ASSERT(OSContextAllowsSleeping());

	if ( ( CFGMGR_YUKON_FIRMWARE_DATA == eCode ) ||
			( CFGMGR_YUKON_FIRMWARE_DELETE == eCode ) )
		return COM_STATUS_SUCCESS;

	for(p = pNvm->CodeList.next; p != &pNvm->CodeList; p = p->next) {
		NVMItem *item = (NVMItem *)p;

		if(item->cfgcode == eCode) {
			if(item->size == *pdwSize && !OsMemCmp((unsigned char *)(item + 1),pBuf,item->size))
				goto outsame;
			if(item->size >= *pdwSize) { /* does it fit in existing space? */
				item->size = *pdwSize;
				memcpy((unsigned char *)(item + 1), pBuf, item->size);
				goto outmodified;
			} else
				break;
		}
	}

	newItem = kmalloc(sizeof(*newItem) + *pdwSize, GFP_KERNEL);
	if(!newItem) {
		return COM_STATUS_FAIL;
	}

	newItem->cfgcode = eCode;
	newItem->size = *pdwSize;
	memcpy((unsigned char *)(newItem + 1), pBuf, newItem->size);

	// try to preserve location in list if replacing old one, else add to tail
	list_add(&newItem->node, p->prev);

	if(p != &pNvm->CodeList) {
		list_del(p); // remove old one
	}

outmodified:
	pNvm->bModified = TRUE;
	/* schedule write to happen asynchronously 1/2 sec after last NVM_Write() */
	OSSetPeriodicTimeOut(pNvm->hWriteTimeOut, 500);

	//printk(KERN_INFO "%s: modified CfgCode=%s Size=%ld\n", __FUNCTION__, eCodeStr(eCode), *pdwSize);

outsame:
	return COM_STATUS_SUCCESS;
}

