/******************************************************************************
Extended Message Function With Response Caching

    Copyright (C) 1997-2017  AudioScience Inc. <support@audioscience.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of version 2 of the GNU General Public License as
    published by the Free Software Foundation;

    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.
*****************************************************************************/
#define SOURCEFILE_NAME "hpimsgx.c"
#include "hpi_internal.h"
#include "hpi_version.h"
#include "hpimsginit.h"
#include "hpicmn.h"
#include "hpimsgx.h"
#include "hpidebug.h"


#ifdef HPI_LOCKING
static HpiOs_Mutex msgxLock;
#endif

static int logging_enabled = 1;


static inline void HwEntryPoint(
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	struct hpi_adapter_obj *pao = HpiGetAdapter(phm->wAdapterIndex);

	if (pao)
		pao->ep.transact(pao, phm, phr);
	else
		HPI_InitResponse(phr, phm->wObject, phm->wFunction,
			HPI_ERROR_PROCESSING_MESSAGE);
}

static void OutStreamOpen(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
);
static void OutStreamClose(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
);
static void InStreamOpen(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
);
static void InStreamClose(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
);

static void AdapterReset(uint16_t wAdapterIndex);
static void HPIMSGX_Reset(void);
static void AdapterCleanup(uint16_t wAdapter, void *hOwner);
static void HPIMSGX_Cleanup(void *hOwner);

static void SubSysMessage(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{
	if (phm->wAdapterIndex != HPI_ADAPTER_INDEX_INVALID)
		HPI_DEBUG_LOG2(WARNING,
			"Suspicious adapter index %d in subsys message 0x%x.\n",
			phm->wAdapterIndex, phm->wFunction);

	switch (phm->wFunction) {
	case HPI_SUBSYS_GET_VERSION:
		HPI_InitResponse(phr, HPI_OBJ_SUBSYSTEM,
			HPI_SUBSYS_GET_VERSION, 0);
		phr->u.s.dwVersion = HPI_VER >> 8;	// return major.minor
		phr->u.s.dwData = HPI_VER;	// return major.minor.release
		break;
	case HPI_SUBSYS_OPEN:
		//do not propagate the message down the chain
		HPI_InitResponse(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_OPEN, 0);
		break;
	case HPI_SUBSYS_CLOSE:
		//do not propagate the message down the chain
		HPI_InitResponse(phr, HPI_OBJ_SUBSYSTEM, HPI_SUBSYS_CLOSE, 0);
		HPIMSGX_Cleanup(hOwner);
		break;
	case HPI_SUBSYS_DRIVER_LOAD:
		// Initialize this module's internal state
		HpiOs_Mutex_Init(&msgxLock);
#ifndef HPI_OS_LINUX_KERNEL
		HpiOs_LockedMem_Init();
#endif
		// Init subsys_findadapters response to no-adapters
		HPIMSGX_Reset();
		HPI_InitResponse(phr, HPI_OBJ_SUBSYSTEM,
			HPI_SUBSYS_DRIVER_LOAD, 0);
		/* individual HPIs dont implement driver load */
		HPI_COMMON(phm, phr);
		break;
	case HPI_SUBSYS_DRIVER_UNLOAD:
		HPI_COMMON(phm, phr);
		HPIMSGX_Cleanup(hOwner);
#ifndef HPI_OS_LINUX_KERNEL
		HpiOs_LockedMem_FreeAll();
#endif
		HPI_InitResponse(phr, HPI_OBJ_SUBSYSTEM,
			HPI_SUBSYS_DRIVER_UNLOAD, 0);
		return;

	case HPI_SUBSYS_GET_NUM_ADAPTERS:
	case HPI_SUBSYS_GET_ADAPTER:
		HPI_COMMON(phm, phr);
		break;

//? Uses of HPI_SUBSYS_WAKE_ADAPTER need replaced by
// call to adapter's init function followed by HPIMSGX_Wake()
//
//	case HPI_SUBSYS_CREATE_ADAPTER:
//	case HPI_SUBSYS_WAKE_ADAPTER:
//		HPIMSGX_Init(phm, phr);
//		break;
	default:
		// Must explicitly handle every subsys message in this switch
		HPI_InitResponse(phr, HPI_OBJ_SUBSYSTEM, phm->wFunction,
						HPI_ERROR_INVALID_FUNC);
		break;
	}
}

static void AdapterMessage(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{
	switch (phm->wFunction) {
	case HPI_ADAPTER_OPEN:
	case HPI_ADAPTER_CLOSE:
		HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
		break;
	case HPI_ADAPTER_DELETE:
		AdapterCleanup(phm->wAdapterIndex, hOwner);
		{
			struct hpi_message hm;
			struct hpi_response hr;
			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ADAPTER,
				HPI_ADAPTER_CLOSE);
			hm.wAdapterIndex = phm->wAdapterIndex;
			HwEntryPoint(&hm, &hr);
		}
		HwEntryPoint(phm, phr);
		break;

	default:
		HwEntryPoint(phm, phr);
		break;
	}
}

static void MixerMessage(
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	switch (phm->wFunction) {
	case HPI_MIXER_OPEN:
	case HPI_MIXER_CLOSE:
		HPI_InitResponse(phr, phm->wObject, phm->wFunction, 0);
		break;
	default:
		HwEntryPoint(phm, phr);
		break;
	}
}

static void OStreamMessage(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{
	struct hpi_adapter_obj *pao = HpiGetAdapter(phm->wAdapterIndex);

	if (!pao) {
		HPI_InitResponse(phr, HPI_OBJ_OSTREAM, phm->wFunction,
			HPI_ERROR_BAD_ADAPTER_NUMBER);
		return;
	}
	if (phm->wObjIndex >= pao->outstreams) {
		HPI_InitResponse(phr, HPI_OBJ_OSTREAM, phm->wFunction,
			HPI_ERROR_INVALID_OBJ_INDEX);
		return;
	}

	switch (phm->wFunction) {
	case HPI_OSTREAM_OPEN:
		OutStreamOpen(phm, phr, hOwner);
		break;
	case HPI_OSTREAM_CLOSE:
		OutStreamClose(phm, phr, hOwner);
		break;
	default:
		HwEntryPoint(phm, phr);
		break;
	}
}

static void IStreamMessage(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{
	struct hpi_adapter_obj *pao = HpiGetAdapter(phm->wAdapterIndex);

	if (!pao) {
		HPI_InitResponse(phr, HPI_OBJ_OSTREAM, phm->wFunction,
			HPI_ERROR_BAD_ADAPTER_NUMBER);
		return;
	}
	if (phm->wObjIndex >= pao->instreams) {
		HPI_InitResponse(phr, HPI_OBJ_ISTREAM, phm->wFunction,
			HPI_ERROR_INVALID_OBJ_INDEX);
		return;
	}

	switch (phm->wFunction) {
	case HPI_ISTREAM_OPEN:
		InStreamOpen(phm, phr, hOwner);
		break;
	case HPI_ISTREAM_CLOSE:
		InStreamClose(phm, phr, hOwner);
		break;
	default:
		HwEntryPoint(phm, phr);
		break;
	}
}

/* NOTE: HPI_Message() must be defined in the driver as a wrapper for
 * HPI_MessageEx so that functions in hpifunc.c compile.
 */
void HPI_MessageEx(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{
	if (logging_enabled)
		HPI_DEBUG_MESSAGE(DEBUG, phm);

	if (phm->wType != HPI_TYPE_REQUEST) {
		HPI_InitResponse(phr, phm->wObject, phm->wFunction,
			HPI_ERROR_INVALID_TYPE);
		return;
	}

	if (phm->wAdapterIndex >= HPI_MAX_ADAPTERS &&
		phm->wAdapterIndex != HPIMSGX_ALLADAPTERS) {
		HPI_InitResponse(phr, phm->wObject, phm->wFunction,
			HPI_ERROR_BAD_ADAPTER_NUMBER);
		return;
	}

	switch (phm->wObject) {
	case HPI_OBJ_SUBSYSTEM:
		SubSysMessage(phm, phr, hOwner);
		break;

	case HPI_OBJ_ADAPTER:
		AdapterMessage(phm, phr, hOwner);
		break;

	case HPI_OBJ_MIXER:
		MixerMessage(phm, phr);
		break;

	case HPI_OBJ_OSTREAM:
		OStreamMessage(phm, phr, hOwner);
		break;

	case HPI_OBJ_ISTREAM:
		IStreamMessage(phm, phr, hOwner);
		break;

	default:
		HwEntryPoint(phm, phr);
		break;
	}

	if (logging_enabled)
		HPI_DEBUG_RESPONSE(phr);

#ifndef HPI_OS_LINUX_KERNEL
	if ((phr->wError == HPI_ERROR_DSP_COMMUNICATION) ||
		(phr->wError == HPI_ERROR_DSP_BOOTLOAD))
	{
		HPI_DEBUG_MESSAGE(ERROR, phm);

		if (hpiDebugLevel >= HPI_DEBUG_LEVEL_VERBOSE)
			hpi_debug_data((uint16_t *)phm,
				sizeof(*phm)/sizeof(uint16_t));
	}
#endif
	if (phr->wError >= HPI_ERROR_DSP_COMMUNICATION) {
		HPI_DebugLevelSet(HPI_DEBUG_LEVEL_ERROR);
		logging_enabled = 0;
	}
}

static void InStreamOpen(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{
	struct hpi_message hm;
	struct hpi_response hr;
	struct hpi_adapter_obj *pao = HpiGetAdapter(phm->wAdapterIndex);
	uint16_t idx = phm->wObjIndex;

	if (!pao) {
		phr->wError = HPI_ERROR_BAD_ADAPTER_NUMBER;
		return;
	}

	HPI_InitResponse(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_OPEN, 0);

	HpiOs_Mutex_Lock(&msgxLock);

	if (pao->instream_user_open[idx].is_open)
		phr->wError = HPI_ERROR_OBJ_ALREADY_OPEN;
	else if (pao->instream_user_open[idx].error)
		phr->wError = pao->instream_user_open[idx].error;
	else {
		pao->instream_user_open[idx].is_open = 1;
		HpiOs_Mutex_Unlock(&msgxLock);

		// issue a reset
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = idx;
		HwEntryPoint(&hm, &hr);

		HpiOs_Mutex_Lock(&msgxLock);
		phr->wError = hr.wError;
		pao->instream_user_open[idx].error = hr.wError;
		if (hr.wError) {
			pao->instream_user_open[idx].is_open = 0;
		} else {
			pao->instream_user_open[idx].is_open = 1;
			pao->instream_user_open[idx].hOwner = hOwner;
		}
	}
	HpiOs_Mutex_Unlock(&msgxLock);
}

static void InStreamClose(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{
	struct hpi_message hm;
	struct hpi_response hr;
	struct hpi_adapter_obj *pao = HpiGetAdapter(phm->wAdapterIndex);
	uint16_t idx = phm->wObjIndex;

	if (!pao) {
		phr->wError = HPI_ERROR_BAD_ADAPTER_NUMBER;
		return;
	}

	HPI_InitResponse(phr, HPI_OBJ_ISTREAM, HPI_ISTREAM_CLOSE, 0);

	HpiOs_Mutex_Lock(&msgxLock);
	if (hOwner == pao->instream_user_open[idx].hOwner) {
		/* HPI_DEBUG_LOG3(INFO,"closing adapter %d "
		   "instream %d owned by %p\n",
		   phm->wAdapterIndex, idx, hOwner); */
		pao->instream_user_open[idx].hOwner = NULL;
		HpiOs_Mutex_Unlock(&msgxLock);
		// issue a reset
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = idx;
		HwEntryPoint(&hm, &hr);
		HpiOs_Mutex_Lock(&msgxLock);
		if (hr.wError) {
			pao->instream_user_open[idx].hOwner = hOwner;
			phr->wError = hr.wError;
		} else {
			pao->instream_user_open[idx].is_open = 0;
			pao->instream_user_open[idx].hOwner = NULL;
		}
	} else {
		HPI_DEBUG_LOG4(WARNING,
			"%p trying to close %d instream %d owned by %p\n",
			hOwner, phm->wAdapterIndex, idx,
			pao->instream_user_open[idx].hOwner);
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
	}
	HpiOs_Mutex_Unlock(&msgxLock);
}

static void OutStreamOpen(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{

	struct hpi_message hm;
	struct hpi_response hr;
	struct hpi_adapter_obj *pao = HpiGetAdapter(phm->wAdapterIndex);
	uint16_t idx = phm->wObjIndex;

	if (!pao) {
		phr->wError = HPI_ERROR_BAD_ADAPTER_NUMBER;
		return;
	}

	HPI_InitResponse(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_OPEN, 0);

	HpiOs_Mutex_Lock(&msgxLock);

	if (pao->outstream_user_open[idx].is_open)
		phr->wError = HPI_ERROR_OBJ_ALREADY_OPEN;
	else if (pao->outstream_user_open[idx].error)
		phr->wError = pao->outstream_user_open[idx].error;
	else {
		pao->outstream_user_open[idx].is_open = 1;
		HpiOs_Mutex_Unlock(&msgxLock);

		// issue a reset
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = idx;
		HwEntryPoint(&hm, &hr);

		HpiOs_Mutex_Lock(&msgxLock);
		phr->wError = hr.wError;
		pao->outstream_user_open[idx].error = hr.wError;
		if (hr.wError) {
			pao->outstream_user_open[idx].is_open = 0;
		} else {
			pao->outstream_user_open[idx].is_open = 1;
			pao->outstream_user_open[idx].hOwner = hOwner;
		}
	}
	HpiOs_Mutex_Unlock(&msgxLock);
}

static void OutStreamClose(
	struct hpi_message *phm,
	struct hpi_response *phr,
	void *hOwner
)
{
	struct hpi_message hm;
	struct hpi_response hr;
	struct hpi_adapter_obj *pao = HpiGetAdapter(phm->wAdapterIndex);
	uint16_t idx = phm->wObjIndex;

	if (!pao) {
		phr->wError = HPI_ERROR_BAD_ADAPTER_NUMBER;
		return;
	}

	HPI_InitResponse(phr, HPI_OBJ_OSTREAM, HPI_OSTREAM_CLOSE, 0);

	HpiOs_Mutex_Lock(&msgxLock);

	if (hOwner == pao->outstream_user_open[idx].hOwner) {
		/* HPI_DEBUG_LOG3(INFO,"closing adapter %d "
		   "outstream %d owned by %p\n",
		   phm->wAdapterIndex, idx, hOwner); */
		pao->outstream_user_open[idx].hOwner = NULL;
		HpiOs_Mutex_Unlock(&msgxLock);
		// issue a reset
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_RESET);
		hm.wAdapterIndex = phm->wAdapterIndex;
		hm.wObjIndex = idx;
		HwEntryPoint(&hm, &hr);
		HpiOs_Mutex_Lock(&msgxLock);
		if (hr.wError) {
			pao->outstream_user_open[idx].hOwner = hOwner;
			phr->wError = hr.wError;
		} else {
			pao->outstream_user_open[idx].is_open = 0;
			pao->outstream_user_open[idx].hOwner = NULL;
		}
	} else {
		HPI_DEBUG_LOG4(WARNING,
			"%p trying to close %d outstream %d owned by %p\n",
			hOwner, phm->wAdapterIndex, idx,
			pao->outstream_user_open[idx].hOwner);
		phr->wError = HPI_ERROR_OBJ_NOT_OPEN;
	}
	HpiOs_Mutex_Unlock(&msgxLock);
}

uint16_t HPIMSGX_AdapterWake(struct hpi_adapter_obj *pao)
{
	struct hpi_message hm;
	struct hpi_response hr;
	uint16_t i;

	HPI_DEBUG_LOG0(DEBUG, "HPIMSGX_AdapterWake()\n");

/* Adapter and Mixer open must succeed. They perform necessary
 * initialization steps on the DSP
 * Should these calls be moved in to driver backend startup sequence?
 */
	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ADAPTER, HPI_ADAPTER_OPEN);
	pao->ep.transact(pao, &hm, &hr);
	if (hr.wError && hr.wError != HPI_ERROR_OBJ_ALREADY_OPEN)
		return hr.wError;

	HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_MIXER, HPI_MIXER_OPEN);
	pao->ep.transact(pao, &hm, &hr);
	if (hr.wError && hr.wError != HPI_ERROR_OBJ_ALREADY_OPEN)
		return hr.wError;

	for (i = 0; i < pao->outstreams; i++) {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM, HPI_OSTREAM_OPEN);
		hm.wAdapterIndex = pao->index;
		hm.wObjIndex = i;
		pao->ep.transact(pao, &hm, &hr);
		pao->outstream_user_open[i].error = hr.wError;
	}

	for (i = 0; i < pao->instreams; i++) {
		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM, HPI_ISTREAM_OPEN);
		hm.wAdapterIndex = pao->index;
		hm.wObjIndex = i;
		pao->ep.transact(pao, &hm, &hr);
		pao->instream_user_open[i].error = hr.wError;
	}

	return 0;
}

uint16_t HPIMSGX_AdapterPrepare(struct hpi_adapter_obj *pao)
{
	uint16_t err;
	uint16_t i;

	HPI_DEBUG_LOG0(DEBUG, "HPIMSGX_AdapterPrepare()\n");

	if (!pao)
		return HPI_ERROR_BAD_ADAPTER_NUMBER;

	err = HPIMSGX_AdapterWake(pao);
	if (err)
		return err;

	for (i = 0; i < pao->outstreams; i++) {
		pao->outstream_user_open[i].is_open = 0;
		pao->outstream_user_open[i].hOwner = NULL;
	}

	for (i = 0; i < pao->instreams; i++) {
		pao->instream_user_open[i].is_open = 0;
		pao->instream_user_open[i].hOwner = NULL;
	}

	return 0;
}

static void AdapterReset(uint16_t wAdapter)
{
	int i;
	struct hpi_adapter_obj *pao = HpiGetAdapter(wAdapter);

	if (!pao)
		return;

	for (i = 0; i < HPI_MAX_STREAMS; i++) {
		pao->outstream_user_open[i].error = HPI_ERROR_INVALID_OBJ;
		pao->instream_user_open[i].error = HPI_ERROR_INVALID_OBJ;
	}
}

static void HPIMSGX_Reset(void)
{
	uint16_t i;
	for (i = 0; i < HPI_MAX_ADAPTERS; i++)
		AdapterReset(i);
}

/** Reset and release ownership of any streams left open when app closes
 * linux: via file_operations.release method
 */
static void AdapterCleanup(uint16_t wAdapter, void *hOwner)
{
	int i;
	struct hpi_adapter_obj *pao = HpiGetAdapter(wAdapter);

	if (!hOwner || !pao)
		return;

	for (i = 0; i < HPI_MAX_STREAMS; i++) {
		if (hOwner == pao->outstream_user_open[i].hOwner) {
			struct hpi_message hm;
			struct hpi_response hr;

			HPI_DEBUG_LOG2(DEBUG,
				"Close adapter %d ostream %d\n",
				wAdapter, i);

			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_OSTREAM,
				HPI_OSTREAM_RESET);
			hm.wAdapterIndex = (uint16_t)wAdapter;
			hm.wObjIndex = (uint16_t)i;
			HwEntryPoint(&hm, &hr);

			hm.wFunction = HPI_OSTREAM_HOSTBUFFER_FREE;
			HwEntryPoint(&hm, &hr);

			hm.wFunction = HPI_OSTREAM_GROUP_RESET;
			HwEntryPoint(&hm, &hr);

			pao->outstream_user_open[i].is_open = 0;
			pao->outstream_user_open[i].hOwner = NULL;
			pao->outstream_user_open[i].error = 0;
		}
		if (hOwner == pao->instream_user_open[i].hOwner) {
			struct hpi_message hm;
			struct hpi_response hr;

			HPI_DEBUG_LOG2(DEBUG,
				"Close adapter %d istream %d\n",
				wAdapter, i);

			HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ISTREAM,
				HPI_ISTREAM_RESET);
			hm.wAdapterIndex = (uint16_t)wAdapter;
			hm.wObjIndex = (uint16_t)i;
			HwEntryPoint(&hm, &hr);

			hm.wFunction = HPI_ISTREAM_HOSTBUFFER_FREE;
			HwEntryPoint(&hm, &hr);

			hm.wFunction = HPI_ISTREAM_GROUP_RESET;
			HwEntryPoint(&hm, &hr);

			pao->instream_user_open[i].is_open = 0;
			pao->instream_user_open[i].hOwner = NULL;
			pao->instream_user_open[i].error = 0;
		}
	}
}

static void HPIMSGX_Cleanup(void *hOwner)
{
	uint16_t i;

	for (i = 0; i < HPI_MAX_ADAPTERS; i++)
		AdapterCleanup(i, hOwner);
}

