/*
 * dbdcd.c
 *
 * DSP-BIOS Bridge driver support functions for TI OMAP processors.
 *
 * This file contains the implementation of the DSP/BIOS Bridge
 * Configuration Database (DCD).
 *
 * Notes:
 *   The fxn dcd_get_objects can apply a callback fxn to each DCD object
 *   that is located in a specified COFF file.  At the moment,
 *   dcd_auto_register, dcd_auto_unregister, and NLDR module all use
 *   dcd_get_objects.
 *
 * Copyright (C) 2005-2006 Texas Instruments, Inc.
 *
 * This package is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
#include <linux/types.h>

/*  ----------------------------------- Host OS */
#include <dspbridge/host_os.h>

/*  ----------------------------------- DSP/BIOS Bridge */
#include <dspbridge/dbdefs.h>

/*  ----------------------------------- Platform Manager */
#include <dspbridge/cod.h>

/*  ----------------------------------- Others */
#include <dspbridge/uuidutil.h>

/*  ----------------------------------- This */
#include <dspbridge/dbdcd.h>

/*  ----------------------------------- Global defines. */
#define MAX_INT2CHAR_LENGTH     16	/* Max int2char len of 32 bit int */

/* Name of section containing dependent libraries */
#define DEPLIBSECT		".dspbridge_deplibs"

/* DCD specific structures. */
struct dcd_manager {
	struct cod_manager *cod_mgr;	/* Handle to COD manager object. */
};

/*  Pointer to the registry support key */
static struct list_head reg_key_list;
static DEFINE_SPINLOCK(dbdcd_lock);

/* Global reference variables. */
static u32 refs;
static u32 enum_refs;

/* Helper function prototypes. */
static s32 atoi(char *psz_buf);
static int get_attrs_from_buf(char *psz_buf, u32 ul_buf_size,
				     enum dsp_dcdobjtype obj_type,
				     struct dcd_genericobj *gen_obj);
static void compress_buf(char *psz_buf, u32 ul_buf_size, s32 char_size);
static char dsp_char2_gpp_char(char *word, s32 dsp_char_size);
static int get_dep_lib_info(struct dcd_manager *hdcd_mgr,
				   struct dsp_uuid *uuid_obj,
				   u16 *num_libs,
				   u16 *num_pers_libs,
				   struct dsp_uuid *dep_lib_uuids,
				   bool *prstnt_dep_libs,
				   enum nldr_phase phase);

/*
 *  ======== dcd_auto_register ========
 *  Purpose:
 *      Parses the supplied image and resigsters with DCD.
 */
int dcd_auto_register(struct dcd_manager *hdcd_mgr,
			     char *sz_coff_path)
{
	int status = 0;

	if (hdcd_mgr)
		status = dcd_get_objects(hdcd_mgr, sz_coff_path,
					 (dcd_registerfxn) dcd_register_object,
					 (void *)sz_coff_path);
	else
		status = -EFAULT;

	return status;
}

/*
 *  ======== dcd_auto_unregister ========
 *  Purpose:
 *      Parses the supplied DSP image and unresiters from DCD.
 */
int dcd_auto_unregister(struct dcd_manager *hdcd_mgr,
			       char *sz_coff_path)
{
	int status = 0;

	if (hdcd_mgr)
		status = dcd_get_objects(hdcd_mgr, sz_coff_path,
					 (dcd_registerfxn) dcd_register_object,
					 NULL);
	else
		status = -EFAULT;

	return status;
}

/*
 *  ======== dcd_create_manager ========
 *  Purpose:
 *      Creates DCD manager.
 */
int dcd_create_manager(char *sz_zl_dll_name,
			      struct dcd_manager **dcd_mgr)
{
	struct cod_manager *cod_mgr;	/* COD manager handle */
	struct dcd_manager *dcd_mgr_obj = NULL;	/* DCD Manager pointer */
	int status = 0;

	status = cod_create(&cod_mgr, sz_zl_dll_name);
	if (status)
		goto func_end;

	/* Create a DCD object. */
	dcd_mgr_obj = kzalloc(sizeof(struct dcd_manager), GFP_KERNEL);
	if (dcd_mgr_obj != NULL) {
		/* Fill out the object. */
		dcd_mgr_obj->cod_mgr = cod_mgr;

		/* Return handle to this DCD interface. */
		*dcd_mgr = dcd_mgr_obj;
	} else {
		status = -ENOMEM;

		/*
		 * If allocation of DcdManager object failed, delete the
		 * COD manager.
		 */
		cod_delete(cod_mgr);
	}

func_end:
	return status;
}

/*
 *  ======== dcd_destroy_manager ========
 *  Purpose:
 *      Frees DCD Manager object.
 */
int dcd_destroy_manager(struct dcd_manager *hdcd_mgr)
{
	struct dcd_manager *dcd_mgr_obj = hdcd_mgr;
	int status = -EFAULT;

	if (hdcd_mgr) {
		/* Delete the COD manager. */
		cod_delete(dcd_mgr_obj->cod_mgr);

		/* Deallocate a DCD manager object. */
		kfree(dcd_mgr_obj);

		status = 0;
	}

	return status;
}

/*
 *  ======== dcd_enumerate_object ========
 *  Purpose:
 *      Enumerates objects in the DCD.
 */
int dcd_enumerate_object(s32 index, enum dsp_dcdobjtype obj_type,
				struct dsp_uuid *uuid_obj)
{
	int status = 0;
	char sz_reg_key[DCD_MAXPATHLENGTH];
	char sz_value[DCD_MAXPATHLENGTH];
	struct dsp_uuid dsp_uuid_obj;
	char sz_obj_type[MAX_INT2CHAR_LENGTH];	/* str. rep. of obj_type. */
	u32 dw_key_len = 0;
	struct dcd_key_elem *dcd_key;
	int len;

	if ((index != 0) && (enum_refs == 0)) {
		/*
		 * If an enumeration is being performed on an index greater
		 * than zero, then the current enum_refs must have been
		 * incremented to greater than zero.
		 */
		status = -EIDRM;
	} else {
		/*
		 * Pre-determine final key length. It's length of DCD_REGKEY +
		 *  "_\0" + length of sz_obj_type string + terminating NULL.
		 */
		dw_key_len = strlen(DCD_REGKEY) + 1 + sizeof(sz_obj_type) + 1;

		/* Create proper REG key; concatenate DCD_REGKEY with
		 * obj_type. */
		strncpy(sz_reg_key, DCD_REGKEY, strlen(DCD_REGKEY) + 1);
		if ((strlen(sz_reg_key) + strlen("_\0")) <
		    DCD_MAXPATHLENGTH) {
			strncat(sz_reg_key, "_\0", 2);
		} else {
			status = -EPERM;
		}

		/* This snprintf is guaranteed not to exceed max size of an
		 * integer. */
		status = snprintf(sz_obj_type, MAX_INT2CHAR_LENGTH, "%d",
				  obj_type);

		if (status == -1) {
			status = -EPERM;
		} else {
			status = 0;
			if ((strlen(sz_reg_key) + strlen(sz_obj_type)) <
			    DCD_MAXPATHLENGTH) {
				strncat(sz_reg_key, sz_obj_type,
					strlen(sz_obj_type) + 1);
			} else {
				status = -EPERM;
			}
		}

		if (!status) {
			len = strlen(sz_reg_key);
			spin_lock(&dbdcd_lock);
			list_for_each_entry(dcd_key, &reg_key_list, link) {
				if (!strncmp(dcd_key->name, sz_reg_key, len)
						&& !index--) {
					strncpy(sz_value, &dcd_key->name[len],
					       strlen(&dcd_key->name[len]) + 1);
						break;
				}
			}
			spin_unlock(&dbdcd_lock);

			if (&dcd_key->link == &reg_key_list)
				status = -ENODATA;
		}

		if (!status) {
			/* Create UUID value using string retrieved from
			 * registry. */
			uuid_uuid_from_string(sz_value, &dsp_uuid_obj);

			*uuid_obj = dsp_uuid_obj;

			/* Increment enum_refs to update reference count. */
			enum_refs++;

			status = 0;
		} else if (status == -ENODATA) {
			/* At the end of enumeration. Reset enum_refs. */
			enum_refs = 0;

			/*
			 * TODO: Revisit, this is not an error case but code
			 * expects non-zero value.
			 */
			status = ENODATA;
		} else {
			status = -EPERM;
		}
	}

	return status;
}

/*
 *  ======== dcd_exit ========
 *  Purpose:
 *      Discontinue usage of the DCD module.
 */
void dcd_exit(void)
{
	struct dcd_key_elem *rv, *rv_tmp;

	refs--;
	if (refs == 0) {
		list_for_each_entry_safe(rv, rv_tmp, &reg_key_list, link) {
			list_del(&rv->link);
			kfree(rv->path);
			kfree(rv);
		}
	}

}

/*
 *  ======== dcd_get_dep_libs ========
 */
int dcd_get_dep_libs(struct dcd_manager *hdcd_mgr,
			    struct dsp_uuid *uuid_obj,
			    u16 num_libs, struct dsp_uuid *dep_lib_uuids,
			    bool *prstnt_dep_libs,
			    enum nldr_phase phase)
{
	int status = 0;

	status =
	    get_dep_lib_info(hdcd_mgr, uuid_obj, &num_libs, NULL, dep_lib_uuids,
			     prstnt_dep_libs, phase);

	return status;
}

/*
 *  ======== dcd_get_num_dep_libs ========
 */
int dcd_get_num_dep_libs(struct dcd_manager *hdcd_mgr,
				struct dsp_uuid *uuid_obj,
				u16 *num_libs, u16 *num_pers_libs,
				enum nldr_phase phase)
{
	int status = 0;

	status = get_dep_lib_info(hdcd_mgr, uuid_obj, num_libs, num_pers_libs,
				  NULL, NULL, phase);

	return status;
}

/*
 *  ======== dcd_get_object_def ========
 *  Purpose:
 *      Retrieves the properties of a node or processor based on the UUID and
 *      object type.
 */
int dcd_get_object_def(struct dcd_manager *hdcd_mgr,
			      struct dsp_uuid *obj_uuid,
			      enum dsp_dcdobjtype obj_type,
			      struct dcd_genericobj *obj_def)
{
	struct dcd_manager *dcd_mgr_obj = hdcd_mgr;	/* ptr to DCD mgr */
	struct cod_libraryobj *lib = NULL;
	int status = 0;
	int len;
	u32 ul_addr = 0;	/* Used by cod_get_section */
	u32 ul_len = 0;		/* Used by cod_get_section */
	u32 dw_buf_size;	/* Used by REG functions */
	char sz_reg_key[DCD_MAXPATHLENGTH];
	char *sz_uuid;		/*[MAXUUIDLEN]; */
	char *tmp;
	struct dcd_key_elem *dcd_key = NULL;
	char sz_sect_name[MAXUUIDLEN + 2];	/* ".[UUID]\0" */
	char *psz_coff_buf;
	u32 dw_key_len;		/* Len of REG key. */
	char sz_obj_type[MAX_INT2CHAR_LENGTH];	/* str. rep. of obj_type. */

	sz_uuid = kzalloc(MAXUUIDLEN, GFP_KERNEL);
	if (!sz_uuid) {
		status = -ENOMEM;
		goto func_end;
	}

	if (!hdcd_mgr) {
		status = -EFAULT;
		goto func_end;
	}

	/* Pre-determine final key length. It's length of DCD_REGKEY +
	 *  "_\0" + length of sz_obj_type string + terminating NULL */
	dw_key_len = strlen(DCD_REGKEY) + 1 + sizeof(sz_obj_type) + 1;

	/* Create proper REG key; concatenate DCD_REGKEY with obj_type. */
	strncpy(sz_reg_key, DCD_REGKEY, strlen(DCD_REGKEY) + 1);

	if ((strlen(sz_reg_key) + strlen("_\0")) < DCD_MAXPATHLENGTH)
		strncat(sz_reg_key, "_\0", 2);
	else
		status = -EPERM;

	status = snprintf(sz_obj_type, MAX_INT2CHAR_LENGTH, "%d", obj_type);
	if (status == -1) {
		status = -EPERM;
	} else {
		status = 0;

		if ((strlen(sz_reg_key) + strlen(sz_obj_type)) <
		    DCD_MAXPATHLENGTH) {
			strncat(sz_reg_key, sz_obj_type,
				strlen(sz_obj_type) + 1);
		} else {
			status = -EPERM;
		}

		/* Create UUID value to set in registry. */
		snprintf(sz_uuid, MAXUUIDLEN, "%pUL", obj_uuid);

		if ((strlen(sz_reg_key) + MAXUUIDLEN) < DCD_MAXPATHLENGTH)
			strncat(sz_reg_key, sz_uuid, MAXUUIDLEN);
		else
			status = -EPERM;

		/* Retrieve paths from the registry based on struct dsp_uuid */
		dw_buf_size = DCD_MAXPATHLENGTH;
	}
	if (!status) {
		spin_lock(&dbdcd_lock);
		list_for_each_entry(dcd_key, &reg_key_list, link) {
			if (!strncmp(dcd_key->name, sz_reg_key,
						strlen(sz_reg_key) + 1))
				break;
		}
		spin_unlock(&dbdcd_lock);
		if (&dcd_key->link == &reg_key_list) {
			status = -ENOKEY;
			goto func_end;
		}
	}


	/* Open COFF file. */
	status = cod_open(dcd_mgr_obj->cod_mgr, dcd_key->path,
							COD_NOLOAD, &lib);
	if (status) {
		status = -EACCES;
		goto func_end;
	}

	/* Ensure sz_uuid + 1 is not greater than sizeof sz_sect_name. */
	len = strlen(sz_uuid);
	if (len + 1 > sizeof(sz_sect_name)) {
		status = -EPERM;
		goto func_end;
	}

	/* Create section name based on node UUID. A period is
	 * pre-pended to the UUID string to form the section name.
	 * I.e. ".24BC8D90_BB45_11d4_B756_006008BDB66F" */

	len -= 4;	/* uuid has 4 delimiters '-' */
	tmp = sz_uuid;

	strncpy(sz_sect_name, ".", 2);
	do {
		char *uuid = strsep(&tmp, "-");
		if (!uuid)
			break;
		len -= strlen(uuid);
		strncat(sz_sect_name, uuid, strlen(uuid) + 1);
	} while (len && strncat(sz_sect_name, "_", 2));

	/* Get section information. */
	status = cod_get_section(lib, sz_sect_name, &ul_addr, &ul_len);
	if (status) {
		status = -EACCES;
		goto func_end;
	}

	/* Allocate zeroed buffer. */
	psz_coff_buf = kzalloc(ul_len + 4, GFP_KERNEL);
	if (psz_coff_buf == NULL) {
		status = -ENOMEM;
		goto func_end;
	}
#ifdef _DB_TIOMAP
	if (strstr(dcd_key->path, "iva") == NULL) {
		/* Locate section by objectID and read its content. */
		status =
		    cod_read_section(lib, sz_sect_name, psz_coff_buf, ul_len);
	} else {
		status =
		    cod_read_section(lib, sz_sect_name, psz_coff_buf, ul_len);
		dev_dbg(bridge, "%s: Skipped Byte swap for IVA!!\n", __func__);
	}
#else
	status = cod_read_section(lib, sz_sect_name, psz_coff_buf, ul_len);
#endif
	if (!status) {
		/* Compress DSP buffer to conform to PC format. */
		if (strstr(dcd_key->path, "iva") == NULL) {
			compress_buf(psz_coff_buf, ul_len, DSPWORDSIZE);
		} else {
			compress_buf(psz_coff_buf, ul_len, 1);
			dev_dbg(bridge, "%s: Compressing IVA COFF buffer by 1 "
				"for IVA!!\n", __func__);
		}

		/* Parse the content of the COFF buffer. */
		status =
		    get_attrs_from_buf(psz_coff_buf, ul_len, obj_type, obj_def);
		if (status)
			status = -EACCES;
	} else {
		status = -EACCES;
	}

	/* Free the previously allocated dynamic buffer. */
	kfree(psz_coff_buf);
func_end:
	if (lib)
		cod_close(lib);

	kfree(sz_uuid);

	return status;
}

/*
 *  ======== dcd_get_objects ========
 */
int dcd_get_objects(struct dcd_manager *hdcd_mgr,
			   char *sz_coff_path, dcd_registerfxn register_fxn,
			   void *handle)
{
	struct dcd_manager *dcd_mgr_obj = hdcd_mgr;
	int status = 0;
	char *psz_coff_buf;
	char *psz_cur;
	struct cod_libraryobj *lib = NULL;
	u32 ul_addr = 0;	/* Used by cod_get_section */
	u32 ul_len = 0;		/* Used by cod_get_section */
	char seps[] = ":, ";
	char *token = NULL;
	struct dsp_uuid dsp_uuid_obj;
	s32 object_type;

	if (!hdcd_mgr) {
		status = -EFAULT;
		goto func_end;
	}

	/* Open DSP coff file, don't load symbols. */
	status = cod_open(dcd_mgr_obj->cod_mgr, sz_coff_path, COD_NOLOAD, &lib);
	if (status) {
		status = -EACCES;
		goto func_cont;
	}

	/* Get DCD_RESIGER_SECTION section information. */
	status = cod_get_section(lib, DCD_REGISTER_SECTION, &ul_addr, &ul_len);
	if (status || !(ul_len > 0)) {
		status = -EACCES;
		goto func_cont;
	}

	/* Allocate zeroed buffer. */
	psz_coff_buf = kzalloc(ul_len + 4, GFP_KERNEL);
	if (psz_coff_buf == NULL) {
		status = -ENOMEM;
		goto func_cont;
	}
#ifdef _DB_TIOMAP
	if (strstr(sz_coff_path, "iva") == NULL) {
		/* Locate section by objectID and read its content. */
		status = cod_read_section(lib, DCD_REGISTER_SECTION,
					  psz_coff_buf, ul_len);
	} else {
		dev_dbg(bridge, "%s: Skipped Byte swap for IVA!!\n", __func__);
		status = cod_read_section(lib, DCD_REGISTER_SECTION,
					  psz_coff_buf, ul_len);
	}
#else
	status =
	    cod_read_section(lib, DCD_REGISTER_SECTION, psz_coff_buf, ul_len);
#endif
	if (!status) {
		/* Compress DSP buffer to conform to PC format. */
		if (strstr(sz_coff_path, "iva") == NULL) {
			compress_buf(psz_coff_buf, ul_len, DSPWORDSIZE);
		} else {
			compress_buf(psz_coff_buf, ul_len, 1);
			dev_dbg(bridge, "%s: Compress COFF buffer with 1 word "
				"for IVA!!\n", __func__);
		}

		/* Read from buffer and register object in buffer. */
		psz_cur = psz_coff_buf;
		while ((token = strsep(&psz_cur, seps)) && *token != '\0') {
			/*  Retrieve UUID string. */
			uuid_uuid_from_string(token, &dsp_uuid_obj);

			/*  Retrieve object type */
			token = strsep(&psz_cur, seps);

			/*  Retrieve object type */
			object_type = atoi(token);

			/*
			 *  Apply register_fxn to the found DCD object.
			 *  Possible actions include:
			 *
			 *  1) Register found DCD object.
			 *  2) Unregister found DCD object (when handle == NULL)
			 *  3) Add overlay node.
			 */
			status =
			    register_fxn(&dsp_uuid_obj, object_type, handle);
			if (status) {
				/* if error occurs, break from while loop. */
				break;
			}
		}
	} else {
		status = -EACCES;
	}

	/* Free the previously allocated dynamic buffer. */
	kfree(psz_coff_buf);
func_cont:
	if (lib)
		cod_close(lib);

func_end:
	return status;
}

/*
 *  ======== dcd_get_library_name ========
 *  Purpose:
 *      Retrieves the library name for the given UUID.
 *
 */
int dcd_get_library_name(struct dcd_manager *hdcd_mgr,
				struct dsp_uuid *uuid_obj,
				char *str_lib_name,
				u32 *buff_size,
				enum nldr_phase phase, bool *phase_split)
{
	char sz_reg_key[DCD_MAXPATHLENGTH];
	char sz_uuid[MAXUUIDLEN];
	u32 dw_key_len;		/* Len of REG key. */
	char sz_obj_type[MAX_INT2CHAR_LENGTH];	/* str. rep. of obj_type. */
	int status = 0;
	struct dcd_key_elem *dcd_key = NULL;

	dev_dbg(bridge, "%s: hdcd_mgr %p, uuid_obj %p, str_lib_name %p,"
		" buff_size %p\n", __func__, hdcd_mgr, uuid_obj, str_lib_name,
		buff_size);

	/*
	 *  Pre-determine final key length. It's length of DCD_REGKEY +
	 *  "_\0" + length of sz_obj_type string + terminating NULL.
	 */
	dw_key_len = strlen(DCD_REGKEY) + 1 + sizeof(sz_obj_type) + 1;

	/* Create proper REG key; concatenate DCD_REGKEY with obj_type. */
	strncpy(sz_reg_key, DCD_REGKEY, strlen(DCD_REGKEY) + 1);
	if ((strlen(sz_reg_key) + strlen("_\0")) < DCD_MAXPATHLENGTH)
		strncat(sz_reg_key, "_\0", 2);
	else
		status = -EPERM;

	switch (phase) {
	case NLDR_CREATE:
		/* create phase type */
		sprintf(sz_obj_type, "%d", DSP_DCDCREATELIBTYPE);
		break;
	case NLDR_EXECUTE:
		/* execute phase type */
		sprintf(sz_obj_type, "%d", DSP_DCDEXECUTELIBTYPE);
		break;
	case NLDR_DELETE:
		/* delete phase type */
		sprintf(sz_obj_type, "%d", DSP_DCDDELETELIBTYPE);
		break;
	case NLDR_NOPHASE:
		/* known to be a dependent library */
		sprintf(sz_obj_type, "%d", DSP_DCDLIBRARYTYPE);
		break;
	default:
		status = -EINVAL;
	}
	if (!status) {
		if ((strlen(sz_reg_key) + strlen(sz_obj_type)) <
		    DCD_MAXPATHLENGTH) {
			strncat(sz_reg_key, sz_obj_type,
				strlen(sz_obj_type) + 1);
		} else {
			status = -EPERM;
		}
		/* Create UUID value to find match in registry. */
		snprintf(sz_uuid, MAXUUIDLEN, "%pUL", uuid_obj);
		if ((strlen(sz_reg_key) + MAXUUIDLEN) < DCD_MAXPATHLENGTH)
			strncat(sz_reg_key, sz_uuid, MAXUUIDLEN);
		else
			status = -EPERM;
	}
	if (!status) {
		spin_lock(&dbdcd_lock);
		list_for_each_entry(dcd_key, &reg_key_list, link) {
			/*  See if the name matches. */
			if (!strncmp(dcd_key->name, sz_reg_key,
						strlen(sz_reg_key) + 1))
				break;
		}
		spin_unlock(&dbdcd_lock);
	}

	if (&dcd_key->link == &reg_key_list)
		status = -ENOKEY;

	/* If can't find, phases might be registered as generic LIBRARYTYPE */
	if (status && phase != NLDR_NOPHASE) {
		if (phase_split)
			*phase_split = false;

		strncpy(sz_reg_key, DCD_REGKEY, strlen(DCD_REGKEY) + 1);
		if ((strlen(sz_reg_key) + strlen("_\0")) <
		    DCD_MAXPATHLENGTH) {
			strncat(sz_reg_key, "_\0", 2);
		} else {
			status = -EPERM;
		}
		sprintf(sz_obj_type, "%d", DSP_DCDLIBRARYTYPE);
		if ((strlen(sz_reg_key) + strlen(sz_obj_type))
		    < DCD_MAXPATHLENGTH) {
			strncat(sz_reg_key, sz_obj_type,
				strlen(sz_obj_type) + 1);
		} else {
			status = -EPERM;
		}
		snprintf(sz_uuid, MAXUUIDLEN, "%pUL", uuid_obj);
		if ((strlen(sz_reg_key) + MAXUUIDLEN) < DCD_MAXPATHLENGTH)
			strncat(sz_reg_key, sz_uuid, MAXUUIDLEN);
		else
			status = -EPERM;

		spin_lock(&dbdcd_lock);
		list_for_each_entry(dcd_key, &reg_key_list, link) {
			/*  See if the name matches. */
			if (!strncmp(dcd_key->name, sz_reg_key,
						strlen(sz_reg_key) + 1))
				break;
		}
		spin_unlock(&dbdcd_lock);

		status = (&dcd_key->link != &reg_key_list) ?
						0 : -ENOKEY;
	}

	if (!status)
		memcpy(str_lib_name, dcd_key->path, strlen(dcd_key->path) + 1);
	return status;
}

/*
 *  ======== dcd_init ========
 *  Purpose:
 *      Initialize the DCD module.
 */
bool dcd_init(void)
{
	bool ret = true;

	if (refs == 0)
		INIT_LIST_HEAD(&reg_key_list);

	if (ret)
		refs++;

	return ret;
}

/*
 *  ======== dcd_register_object ========
 *  Purpose:
 *      Registers a node or a processor with the DCD.
 *      If psz_path_name == NULL, unregister the specified DCD object.
 */
int dcd_register_object(struct dsp_uuid *uuid_obj,
			       enum dsp_dcdobjtype obj_type,
			       char *psz_path_name)
{
	int status = 0;
	char sz_reg_key[DCD_MAXPATHLENGTH];
	char sz_uuid[MAXUUIDLEN + 1];
	u32 dw_path_size = 0;
	u32 dw_key_len;		/* Len of REG key. */
	char sz_obj_type[MAX_INT2CHAR_LENGTH];	/* str. rep. of obj_type. */
	struct dcd_key_elem *dcd_key = NULL;

	dev_dbg(bridge, "%s: object UUID %p, obj_type %d, szPathName %s\n",
		__func__, uuid_obj, obj_type, psz_path_name);

	/*
	 * Pre-determine final key length. It's length of DCD_REGKEY +
	 *  "_\0" + length of sz_obj_type string + terminating NULL.
	 */
	dw_key_len = strlen(DCD_REGKEY) + 1 + sizeof(sz_obj_type) + 1;

	/* Create proper REG key; concatenate DCD_REGKEY with obj_type. */
	strncpy(sz_reg_key, DCD_REGKEY, strlen(DCD_REGKEY) + 1);
	if ((strlen(sz_reg_key) + strlen("_\0")) < DCD_MAXPATHLENGTH)
		strncat(sz_reg_key, "_\0", 2);
	else {
		status = -EPERM;
		goto func_end;
	}

	status = snprintf(sz_obj_type, MAX_INT2CHAR_LENGTH, "%d", obj_type);
	if (status == -1) {
		status = -EPERM;
	} else {
		status = 0;
		if ((strlen(sz_reg_key) + strlen(sz_obj_type)) <
		    DCD_MAXPATHLENGTH) {
			strncat(sz_reg_key, sz_obj_type,
				strlen(sz_obj_type) + 1);
		} else
			status = -EPERM;

		/* Create UUID value to set in registry. */
		snprintf(sz_uuid, MAXUUIDLEN, "%pUL", uuid_obj);
		if ((strlen(sz_reg_key) + MAXUUIDLEN) < DCD_MAXPATHLENGTH)
			strncat(sz_reg_key, sz_uuid, MAXUUIDLEN);
		else
			status = -EPERM;
	}

	if (status)
		goto func_end;

	/*
	 * If psz_path_name != NULL, perform registration, otherwise,
	 * perform unregistration.
	 */

	if (psz_path_name) {
		dw_path_size = strlen(psz_path_name) + 1;
		spin_lock(&dbdcd_lock);
		list_for_each_entry(dcd_key, &reg_key_list, link) {
			/*  See if the name matches. */
			if (!strncmp(dcd_key->name, sz_reg_key,
						strlen(sz_reg_key) + 1))
				break;
		}
		spin_unlock(&dbdcd_lock);
		if (&dcd_key->link == &reg_key_list) {
			/*
			 * Add new reg value (UUID+obj_type)
			 * with COFF path info
			 */

			dcd_key = kmalloc(sizeof(struct dcd_key_elem),
								GFP_KERNEL);
			if (!dcd_key) {
				status = -ENOMEM;
				goto func_end;
			}

			dcd_key->path = kmalloc(dw_path_size, GFP_KERNEL);

			if (!dcd_key->path) {
				kfree(dcd_key);
				status = -ENOMEM;
				goto func_end;
			}

			strncpy(dcd_key->name, sz_reg_key,
						strlen(sz_reg_key) + 1);
			strncpy(dcd_key->path, psz_path_name ,
						dw_path_size);
			spin_lock(&dbdcd_lock);
			list_add_tail(&dcd_key->link, &reg_key_list);
			spin_unlock(&dbdcd_lock);
		} else {
			/*  Make sure the new data is the same. */
			if (strncmp(dcd_key->path, psz_path_name,
							dw_path_size)) {
				/*  The caller needs a different data size! */
				kfree(dcd_key->path);
				dcd_key->path = kmalloc(dw_path_size,
								GFP_KERNEL);
				if (dcd_key->path == NULL) {
					status = -ENOMEM;
					goto func_end;
				}
			}

			/*  We have a match!  Copy out the data. */
			memcpy(dcd_key->path, psz_path_name, dw_path_size);
		}
		dev_dbg(bridge, "%s: psz_path_name=%s, dw_path_size=%d\n",
			__func__, psz_path_name, dw_path_size);
	} else {
		/* Deregister an existing object */
		spin_lock(&dbdcd_lock);
		list_for_each_entry(dcd_key, &reg_key_list, link) {
			if (!strncmp(dcd_key->name, sz_reg_key,
						strlen(sz_reg_key) + 1)) {
				list_del(&dcd_key->link);
				kfree(dcd_key->path);
				kfree(dcd_key);
				break;
			}
		}
		spin_unlock(&dbdcd_lock);
		if (&dcd_key->link == &reg_key_list)
			status = -EPERM;
	}

	if (!status) {
		/*
		 *  Because the node database has been updated through a
		 *  successful object registration/de-registration operation,
		 *  we need to reset the object enumeration counter to allow
		 *  current enumerations to reflect this update in the node
		 *  database.
		 */
		enum_refs = 0;
	}
func_end:
	return status;
}

/*
 *  ======== dcd_unregister_object ========
 *  Call DCD_Register object with psz_path_name set to NULL to
 *  perform actual object de-registration.
 */
int dcd_unregister_object(struct dsp_uuid *uuid_obj,
				 enum dsp_dcdobjtype obj_type)
{
	int status = 0;

	/*
	 *  When dcd_register_object is called with NULL as pathname,
	 *  it indicates an unregister object operation.
	 */
	status = dcd_register_object(uuid_obj, obj_type, NULL);

	return status;
}

/*
 **********************************************************************
 * DCD Helper Functions
 **********************************************************************
 */

/*
 *  ======== atoi ========
 *  Purpose:
 *      This function converts strings in decimal or hex format to integers.
 */
static s32 atoi(char *psz_buf)
{
	char *pch = psz_buf;
	s32 base = 0;

	while (isspace(*pch))
		pch++;

	if (*pch == '-' || *pch == '+') {
		base = 10;
		pch++;
	} else if (*pch && tolower(pch[strlen(pch) - 1]) == 'h') {
		base = 16;
	}

	return simple_strtoul(pch, NULL, base);
}

/*
 *  ======== get_attrs_from_buf ========
 *  Purpose:
 *      Parse the content of a buffer filled with DSP-side data and
 *      retrieve an object's attributes from it. IMPORTANT: Assume the
 *      buffer has been converted from DSP format to GPP format.
 */
static int get_attrs_from_buf(char *psz_buf, u32 ul_buf_size,
				     enum dsp_dcdobjtype obj_type,
				     struct dcd_genericobj *gen_obj)
{
	int status = 0;
	char seps[] = ", ";
	char *psz_cur;
	char *token;
	s32 token_len = 0;
	u32 i = 0;
#ifdef _DB_TIOMAP
	s32 entry_id;
#endif

	switch (obj_type) {
	case DSP_DCDNODETYPE:
		/*
		 * Parse COFF sect buffer to retrieve individual tokens used
		 * to fill in object attrs.
		 */
		psz_cur = psz_buf;
		token = strsep(&psz_cur, seps);

		/* u32 cb_struct */
		gen_obj->obj_data.node_obj.ndb_props.cb_struct =
		    (u32) atoi(token);
		token = strsep(&psz_cur, seps);

		/* dsp_uuid ui_node_id */
		uuid_uuid_from_string(token,
				      &gen_obj->obj_data.node_obj.ndb_props.
				      ui_node_id);
		token = strsep(&psz_cur, seps);

		/* ac_name */
		token_len = strlen(token);
		if (token_len > DSP_MAXNAMELEN - 1)
			token_len = DSP_MAXNAMELEN - 1;

		strncpy(gen_obj->obj_data.node_obj.ndb_props.ac_name,
			token, token_len);
		gen_obj->obj_data.node_obj.ndb_props.ac_name[token_len] = '\0';
		token = strsep(&psz_cur, seps);
		/* u32 ntype */
		gen_obj->obj_data.node_obj.ndb_props.ntype = atoi(token);
		token = strsep(&psz_cur, seps);
		/* u32 cache_on_gpp */
		gen_obj->obj_data.node_obj.ndb_props.cache_on_gpp = atoi(token);
		token = strsep(&psz_cur, seps);
		/* dsp_resourcereqmts dsp_resource_reqmts */
		gen_obj->obj_data.node_obj.ndb_props.dsp_resource_reqmts.
		    cb_struct = (u32) atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.node_obj.ndb_props.
		    dsp_resource_reqmts.static_data_size = atoi(token);
		token = strsep(&psz_cur, seps);
		gen_obj->obj_data.node_obj.ndb_props.
		    dsp_resource_reqmts.global_data_size = atoi(token);
		token = strsep(&psz_cur, seps);
		gen_obj->obj_data.node_obj.ndb_props.
		    dsp_resource_reqmts.program_mem_size = atoi(token);
		token = strsep(&psz_cur, seps);
		gen_obj->obj_data.node_obj.ndb_props.
		    dsp_resource_reqmts.wc_execution_time = atoi(token);
		token = strsep(&psz_cur, seps);
		gen_obj->obj_data.node_obj.ndb_props.
		    dsp_resource_reqmts.wc_period = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.node_obj.ndb_props.
		    dsp_resource_reqmts.wc_deadline = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.node_obj.ndb_props.
		    dsp_resource_reqmts.avg_exection_time = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.node_obj.ndb_props.
		    dsp_resource_reqmts.minimum_period = atoi(token);
		token = strsep(&psz_cur, seps);

		/* s32 prio */
		gen_obj->obj_data.node_obj.ndb_props.prio = atoi(token);
		token = strsep(&psz_cur, seps);

		/* u32 stack_size */
		gen_obj->obj_data.node_obj.ndb_props.stack_size = atoi(token);
		token = strsep(&psz_cur, seps);

		/* u32 sys_stack_size */
		gen_obj->obj_data.node_obj.ndb_props.sys_stack_size =
		    atoi(token);
		token = strsep(&psz_cur, seps);

		/* u32 stack_seg */
		gen_obj->obj_data.node_obj.ndb_props.stack_seg = atoi(token);
		token = strsep(&psz_cur, seps);

		/* u32 message_depth */
		gen_obj->obj_data.node_obj.ndb_props.message_depth =
		    atoi(token);
		token = strsep(&psz_cur, seps);

		/* u32 num_input_streams */
		gen_obj->obj_data.node_obj.ndb_props.num_input_streams =
		    atoi(token);
		token = strsep(&psz_cur, seps);

		/* u32 num_output_streams */
		gen_obj->obj_data.node_obj.ndb_props.num_output_streams =
		    atoi(token);
		token = strsep(&psz_cur, seps);

		/* u32 timeout */
		gen_obj->obj_data.node_obj.ndb_props.timeout = atoi(token);
		token = strsep(&psz_cur, seps);

		/* char *str_create_phase_fxn */
		token_len = strlen(token);
		gen_obj->obj_data.node_obj.str_create_phase_fxn =
					kzalloc(token_len + 1, GFP_KERNEL);
		strncpy(gen_obj->obj_data.node_obj.str_create_phase_fxn,
			token, token_len);
		gen_obj->obj_data.node_obj.str_create_phase_fxn[token_len] =
		    '\0';
		token = strsep(&psz_cur, seps);

		/* char *str_execute_phase_fxn */
		token_len = strlen(token);
		gen_obj->obj_data.node_obj.str_execute_phase_fxn =
					kzalloc(token_len + 1, GFP_KERNEL);
		strncpy(gen_obj->obj_data.node_obj.str_execute_phase_fxn,
			token, token_len);
		gen_obj->obj_data.node_obj.str_execute_phase_fxn[token_len] =
		    '\0';
		token = strsep(&psz_cur, seps);

		/* char *str_delete_phase_fxn */
		token_len = strlen(token);
		gen_obj->obj_data.node_obj.str_delete_phase_fxn =
					kzalloc(token_len + 1, GFP_KERNEL);
		strncpy(gen_obj->obj_data.node_obj.str_delete_phase_fxn,
			token, token_len);
		gen_obj->obj_data.node_obj.str_delete_phase_fxn[token_len] =
		    '\0';
		token = strsep(&psz_cur, seps);

		/* Segment id for message buffers */
		gen_obj->obj_data.node_obj.msg_segid = atoi(token);
		token = strsep(&psz_cur, seps);

		/* Message notification type */
		gen_obj->obj_data.node_obj.msg_notify_type = atoi(token);
		token = strsep(&psz_cur, seps);

		/* char *str_i_alg_name */
		if (token) {
			token_len = strlen(token);
			gen_obj->obj_data.node_obj.str_i_alg_name =
					kzalloc(token_len + 1, GFP_KERNEL);
			strncpy(gen_obj->obj_data.node_obj.str_i_alg_name,
				token, token_len);
			gen_obj->obj_data.node_obj.str_i_alg_name[token_len] =
			    '\0';
			token = strsep(&psz_cur, seps);
		}

		/* Load type (static, dynamic, or overlay) */
		if (token) {
			gen_obj->obj_data.node_obj.load_type = atoi(token);
			token = strsep(&psz_cur, seps);
		}

		/* Dynamic load data requirements */
		if (token) {
			gen_obj->obj_data.node_obj.data_mem_seg_mask =
			    atoi(token);
			token = strsep(&psz_cur, seps);
		}

		/* Dynamic load code requirements */
		if (token) {
			gen_obj->obj_data.node_obj.code_mem_seg_mask =
			    atoi(token);
			token = strsep(&psz_cur, seps);
		}

		/* Extract node profiles into node properties */
		if (token) {

			gen_obj->obj_data.node_obj.ndb_props.count_profiles =
			    atoi(token);
			for (i = 0;
			     i <
			     gen_obj->obj_data.node_obj.
			     ndb_props.count_profiles; i++) {
				token = strsep(&psz_cur, seps);
				if (token) {
					/* Heap Size for the node */
					gen_obj->obj_data.node_obj.
					    ndb_props.node_profiles[i].
					    heap_size = atoi(token);
				}
			}
		}
		token = strsep(&psz_cur, seps);
		if (token) {
			gen_obj->obj_data.node_obj.ndb_props.stack_seg_name =
			    (u32) (token);
		}

		break;

	case DSP_DCDPROCESSORTYPE:
		/*
		 * Parse COFF sect buffer to retrieve individual tokens used
		 * to fill in object attrs.
		 */
		psz_cur = psz_buf;
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.cb_struct = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.processor_family = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.processor_type = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.clock_rate = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.internal_mem_size = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.external_mem_size = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.processor_id = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.ty_running_rtos = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.node_min_priority = atoi(token);
		token = strsep(&psz_cur, seps);

		gen_obj->obj_data.proc_info.node_max_priority = atoi(token);

#ifdef _DB_TIOMAP
		/* Proc object may contain additional(extended) attributes. */
		/* attr must match proc.hxx */
		for (entry_id = 0; entry_id < 7; entry_id++) {
			token = strsep(&psz_cur, seps);
			gen_obj->obj_data.ext_proc_obj.ty_tlb[entry_id].
			    gpp_phys = atoi(token);

			token = strsep(&psz_cur, seps);
			gen_obj->obj_data.ext_proc_obj.ty_tlb[entry_id].
			    dsp_virt = atoi(token);
		}
#endif

		break;

	default:
		status = -EPERM;
		break;
	}

	return status;
}

/*
 *  ======== CompressBuffer ========
 *  Purpose:
 *      Compress the DSP buffer, if necessary, to conform to PC format.
 */
static void compress_buf(char *psz_buf, u32 ul_buf_size, s32 char_size)
{
	char *p;
	char ch;
	char *q;

	p = psz_buf;
	if (p == NULL)
		return;

	for (q = psz_buf; q < (psz_buf + ul_buf_size);) {
		ch = dsp_char2_gpp_char(q, char_size);
		if (ch == '\\') {
			q += char_size;
			ch = dsp_char2_gpp_char(q, char_size);
			switch (ch) {
			case 't':
				*p = '\t';
				break;

			case 'n':
				*p = '\n';
				break;

			case 'r':
				*p = '\r';
				break;

			case '0':
				*p = '\0';
				break;

			default:
				*p = ch;
				break;
			}
		} else {
			*p = ch;
		}
		p++;
		q += char_size;
	}

	/* NULL out remainder of buffer. */
	while (p < q)
		*p++ = '\0';
}

/*
 *  ======== dsp_char2_gpp_char ========
 *  Purpose:
 *      Convert DSP char to host GPP char in a portable manner
 */
static char dsp_char2_gpp_char(char *word, s32 dsp_char_size)
{
	char ch = '\0';
	char *ch_src;
	s32 i;

	for (ch_src = word, i = dsp_char_size; i > 0; i--)
		ch |= *ch_src++;

	return ch;
}

/*
 *  ======== get_dep_lib_info ========
 */
static int get_dep_lib_info(struct dcd_manager *hdcd_mgr,
				   struct dsp_uuid *uuid_obj,
				   u16 *num_libs,
				   u16 *num_pers_libs,
				   struct dsp_uuid *dep_lib_uuids,
				   bool *prstnt_dep_libs,
				   enum nldr_phase phase)
{
	struct dcd_manager *dcd_mgr_obj = hdcd_mgr;
	char *psz_coff_buf = NULL;
	char *psz_cur;
	char *psz_file_name = NULL;
	struct cod_libraryobj *lib = NULL;
	u32 ul_addr = 0;	/* Used by cod_get_section */
	u32 ul_len = 0;		/* Used by cod_get_section */
	u32 dw_data_size = COD_MAXPATHLENGTH;
	char seps[] = ", ";
	char *token = NULL;
	bool get_uuids = (dep_lib_uuids != NULL);
	u16 dep_libs = 0;
	int status = 0;

	/*  Initialize to 0 dependent libraries, if only counting number of
	 *  dependent libraries */
	if (!get_uuids) {
		*num_libs = 0;
		*num_pers_libs = 0;
	}

	/* Allocate a buffer for file name */
	psz_file_name = kzalloc(dw_data_size, GFP_KERNEL);
	if (psz_file_name == NULL) {
		status = -ENOMEM;
	} else {
		/* Get the name of the library */
		status = dcd_get_library_name(hdcd_mgr, uuid_obj, psz_file_name,
					      &dw_data_size, phase, NULL);
	}

	/* Open the library */
	if (!status) {
		status = cod_open(dcd_mgr_obj->cod_mgr, psz_file_name,
				  COD_NOLOAD, &lib);
	}
	if (!status) {
		/* Get dependent library section information. */
		status = cod_get_section(lib, DEPLIBSECT, &ul_addr, &ul_len);

		if (status) {
			/* Ok, no dependent libraries */
			ul_len = 0;
			status = 0;
		}
	}

	if (status || !(ul_len > 0))
		goto func_cont;

	/* Allocate zeroed buffer. */
	psz_coff_buf = kzalloc(ul_len + 4, GFP_KERNEL);
	if (psz_coff_buf == NULL)
		status = -ENOMEM;

	/* Read section contents. */
	status = cod_read_section(lib, DEPLIBSECT, psz_coff_buf, ul_len);
	if (status)
		goto func_cont;

	/* Compress and format DSP buffer to conform to PC format. */
	compress_buf(psz_coff_buf, ul_len, DSPWORDSIZE);

	/* Read from buffer */
	psz_cur = psz_coff_buf;
	while ((token = strsep(&psz_cur, seps)) && *token != '\0') {
		if (get_uuids) {
			if (dep_libs >= *num_libs) {
				/* Gone beyond the limit */
				break;
			} else {
				/* Retrieve UUID string. */
				uuid_uuid_from_string(token,
						      &(dep_lib_uuids
							[dep_libs]));
				/* Is this library persistent? */
				token = strsep(&psz_cur, seps);
				prstnt_dep_libs[dep_libs] = atoi(token);
				dep_libs++;
			}
		} else {
			/* Advanc to next token */
			token = strsep(&psz_cur, seps);
			if (atoi(token))
				(*num_pers_libs)++;

			/* Just counting number of dependent libraries */
			(*num_libs)++;
		}
	}
func_cont:
	if (lib)
		cod_close(lib);

	/* Free previously allocated dynamic buffers. */
	kfree(psz_file_name);

	kfree(psz_coff_buf);

	return status;
}