// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
 */

#include <linux/slab.h>
#include "ipa_rm_resource.h"
#include "ipa_rm_i.h"
#include "ipa_common_i.h"
/**
 * ipa_rm_dep_prod_index() - producer name to producer index mapping
 * @resource_name: [in] resource name (should be of producer)
 *
 * Returns: resource index mapping, IPA_RM_INDEX_INVALID
 *	in case provided resource name isn't contained
 *	in enum ipa_rm_resource_name or is not of producers.
 *
 */
int ipa_rm_prod_index(enum ipa_rm_resource_name resource_name)
{
	int result = resource_name;

	switch (resource_name) {
	case IPA_RM_RESOURCE_Q6_PROD:
	case IPA_RM_RESOURCE_USB_PROD:
	case IPA_RM_RESOURCE_USB_DPL_DUMMY_PROD:
	case IPA_RM_RESOURCE_HSIC_PROD:
	case IPA_RM_RESOURCE_STD_ECM_PROD:
	case IPA_RM_RESOURCE_RNDIS_PROD:
	case IPA_RM_RESOURCE_WWAN_0_PROD:
	case IPA_RM_RESOURCE_WLAN_PROD:
	case IPA_RM_RESOURCE_ODU_ADAPT_PROD:
	case IPA_RM_RESOURCE_MHI_PROD:
	case IPA_RM_RESOURCE_ETHERNET_PROD:
		break;
	default:
		result = IPA_RM_INDEX_INVALID;
		break;
	}

	return result;
}

/**
 * ipa_rm_cons_index() - consumer name to consumer index mapping
 * @resource_name: [in] resource name (should be of consumer)
 *
 * Returns: resource index mapping, IPA_RM_INDEX_INVALID
 *	in case provided resource name isn't contained
 *	in enum ipa_rm_resource_name or is not of consumers.
 *
 */
int ipa_rm_cons_index(enum ipa_rm_resource_name resource_name)
{
	int result = resource_name;

	switch (resource_name) {
	case IPA_RM_RESOURCE_Q6_CONS:
	case IPA_RM_RESOURCE_USB_CONS:
	case IPA_RM_RESOURCE_HSIC_CONS:
	case IPA_RM_RESOURCE_WLAN_CONS:
	case IPA_RM_RESOURCE_APPS_CONS:
	case IPA_RM_RESOURCE_ODU_ADAPT_CONS:
	case IPA_RM_RESOURCE_MHI_CONS:
	case IPA_RM_RESOURCE_USB_DPL_CONS:
	case IPA_RM_RESOURCE_ETHERNET_CONS:
		break;
	default:
		result = IPA_RM_INDEX_INVALID;
		break;
	}

	return result;
}

int ipa_rm_resource_consumer_release_work(
		struct ipa_rm_resource_cons *consumer,
		enum ipa_rm_resource_state prev_state,
		bool notify_completion)
{
	int driver_result;

	IPA_RM_DBG_LOW("calling driver CB\n");
	driver_result = consumer->release_resource();
	IPA_RM_DBG_LOW("driver CB returned with %d\n", driver_result);
	/*
	 * Treat IPA_RM_RELEASE_IN_PROGRESS as IPA_RM_RELEASED
	 * for CONS which remains in RELEASE_IN_PROGRESS.
	 */
	if (driver_result == -EINPROGRESS)
		driver_result = 0;
	if (driver_result != 0 && driver_result != -EINPROGRESS) {
		IPA_RM_ERR("driver CB returned error %d\n", driver_result);
		consumer->resource.state = prev_state;
		goto bail;
	}
	if (driver_result == 0) {
		if (notify_completion)
			ipa_rm_resource_consumer_handle_cb(consumer,
					IPA_RM_RESOURCE_RELEASED);
		else
			consumer->resource.state = IPA_RM_RELEASED;
	}
	complete_all(&consumer->request_consumer_in_progress);

	ipa_rm_perf_profile_change(consumer->resource.name);
bail:
	return driver_result;
}

int ipa_rm_resource_consumer_request_work(struct ipa_rm_resource_cons *consumer,
		enum ipa_rm_resource_state prev_state,
		u32 prod_needed_bw,
		bool notify_completion,
		bool dec_client_on_err)
{
	int driver_result;

	IPA_RM_DBG_LOW("calling driver CB\n");
	driver_result = consumer->request_resource();
	IPA_RM_DBG_LOW("driver CB returned with %d\n", driver_result);
	if (driver_result == 0) {
		if (notify_completion) {
			ipa_rm_resource_consumer_handle_cb(consumer,
					IPA_RM_RESOURCE_GRANTED);
		} else {
			consumer->resource.state = IPA_RM_GRANTED;
			ipa_rm_perf_profile_change(consumer->resource.name);
			ipa_resume_resource(consumer->resource.name);
		}
	} else if (driver_result != -EINPROGRESS) {
		consumer->resource.state = prev_state;
		consumer->resource.needed_bw -= prod_needed_bw;
		if (dec_client_on_err)
			consumer->usage_count--;
	}

	return driver_result;
}

int ipa_rm_resource_consumer_request(
		struct ipa_rm_resource_cons *consumer,
		u32 prod_needed_bw,
		bool inc_usage_count,
		bool wake_client)
{
	int result = 0;
	enum ipa_rm_resource_state prev_state;
	struct ipa_active_client_logging_info log_info;

	IPA_RM_DBG_LOW("%s state: %d\n",
			ipa_rm_resource_str(consumer->resource.name),
			consumer->resource.state);

	prev_state = consumer->resource.state;
	consumer->resource.needed_bw += prod_needed_bw;
	switch (consumer->resource.state) {
	case IPA_RM_RELEASED:
	case IPA_RM_RELEASE_IN_PROGRESS:
		reinit_completion(&consumer->request_consumer_in_progress);
		consumer->resource.state = IPA_RM_REQUEST_IN_PROGRESS;
		IPA_ACTIVE_CLIENTS_PREP_RESOURCE(log_info,
				ipa_rm_resource_str(consumer->resource.name));
		if (prev_state == IPA_RM_RELEASE_IN_PROGRESS ||
			ipa_inc_client_enable_clks_no_block(&log_info) != 0) {
			IPA_RM_DBG_LOW("async resume work for %s\n",
				ipa_rm_resource_str(consumer->resource.name));
			ipa_rm_wq_send_resume_cmd(consumer->resource.name,
						prev_state,
						prod_needed_bw,
						inc_usage_count);
			result = -EINPROGRESS;
			break;
		}
		result = ipa_rm_resource_consumer_request_work(consumer,
						prev_state,
						prod_needed_bw,
						false,
						inc_usage_count);
		break;
	case IPA_RM_GRANTED:
		if (wake_client) {
			result = ipa_rm_resource_consumer_request_work(
				consumer, prev_state, prod_needed_bw, false,
				inc_usage_count);
			break;
		}
		ipa_rm_perf_profile_change(consumer->resource.name);
		break;
	case IPA_RM_REQUEST_IN_PROGRESS:
		result = -EINPROGRESS;
		break;
	default:
		consumer->resource.needed_bw -= prod_needed_bw;
		result = -EPERM;
		goto bail;
	}
	if (inc_usage_count)
		consumer->usage_count++;
bail:
	IPA_RM_DBG_LOW("%s new state: %d\n",
		ipa_rm_resource_str(consumer->resource.name),
		consumer->resource.state);
	IPA_RM_DBG_LOW("EXIT with %d\n", result);

	return result;
}

int ipa_rm_resource_consumer_release(
		struct ipa_rm_resource_cons *consumer,
		u32 prod_needed_bw,
		bool dec_usage_count)
{
	int result = 0;
	enum ipa_rm_resource_state save_state;

	IPA_RM_DBG_LOW("%s state: %d\n",
		ipa_rm_resource_str(consumer->resource.name),
		consumer->resource.state);
	save_state = consumer->resource.state;
	consumer->resource.needed_bw -= prod_needed_bw;
	switch (consumer->resource.state) {
	case IPA_RM_RELEASED:
		break;
	case IPA_RM_GRANTED:
	case IPA_RM_REQUEST_IN_PROGRESS:
		if (dec_usage_count && consumer->usage_count > 0)
			consumer->usage_count--;
		if (consumer->usage_count == 0) {
			consumer->resource.state = IPA_RM_RELEASE_IN_PROGRESS;
			if (save_state == IPA_RM_REQUEST_IN_PROGRESS ||
			    ipa_suspend_resource_no_block(
						consumer->resource.name) != 0) {
				ipa_rm_wq_send_suspend_cmd(
						consumer->resource.name,
						save_state,
						prod_needed_bw);
				result = -EINPROGRESS;
				goto bail;
			}
			result = ipa_rm_resource_consumer_release_work(consumer,
					save_state, false);
			goto bail;
		} else if (consumer->resource.state == IPA_RM_GRANTED) {
			ipa_rm_perf_profile_change(consumer->resource.name);
		}
		break;
	case IPA_RM_RELEASE_IN_PROGRESS:
		if (dec_usage_count && consumer->usage_count > 0)
			consumer->usage_count--;
		result = -EINPROGRESS;
		break;
	default:
		result = -EPERM;
		goto bail;
	}
bail:
	IPA_RM_DBG_LOW("%s new state: %d\n",
		ipa_rm_resource_str(consumer->resource.name),
		consumer->resource.state);
	IPA_RM_DBG_LOW("EXIT with %d\n", result);

	return result;
}

/**
 * ipa_rm_resource_producer_notify_clients() - notify
 *	all registered clients of given producer
 * @producer: producer
 * @event: event to notify
 * @notify_registered_only: notify only clients registered by
 *	ipa_rm_register()
 */
void ipa_rm_resource_producer_notify_clients(
				struct ipa_rm_resource_prod *producer,
				enum ipa_rm_event event,
				bool notify_registered_only)
{
	struct ipa_rm_notification_info *reg_info;

	IPA_RM_DBG_LOW("%s event: %d notify_registered_only: %d\n",
		ipa_rm_resource_str(producer->resource.name),
		event,
		notify_registered_only);

	list_for_each_entry(reg_info, &(producer->event_listeners), link) {
		if (notify_registered_only && !reg_info->explicit)
			continue;

		IPA_RM_DBG_LOW("Notifying %s event: %d\n",
			   ipa_rm_resource_str(producer->resource.name), event);
		reg_info->reg_params.notify_cb(reg_info->reg_params.user_data,
					       event,
					       0);
		IPA_RM_DBG_LOW("back from client CB\n");
	}
}

static int ipa_rm_resource_producer_create(struct ipa_rm_resource **resource,
		struct ipa_rm_resource_prod **producer,
		struct ipa_rm_create_params *create_params,
		int *max_peers)
{
	int result = 0;

	*producer = kzalloc(sizeof(**producer), GFP_ATOMIC);
	if (*producer == NULL) {
		result = -ENOMEM;
		goto bail;
	}

	INIT_LIST_HEAD(&((*producer)->event_listeners));
	result = ipa_rm_resource_producer_register(*producer,
			&(create_params->reg_params),
			false);
	if (result) {
		IPA_RM_ERR("ipa_rm_resource_producer_register() failed\n");
		goto register_fail;
	}

	(*resource) = (struct ipa_rm_resource *) (*producer);
	(*resource)->type = IPA_RM_PRODUCER;
	*max_peers = IPA_RM_RESOURCE_MAX;
	goto bail;
register_fail:
	kfree(*producer);
bail:
	return result;
}

static void ipa_rm_resource_producer_delete(
				struct ipa_rm_resource_prod *producer)
{
	struct ipa_rm_notification_info *reg_info;
	struct list_head *pos, *q;

	ipa_rm_resource_producer_release(producer);
	list_for_each_safe(pos, q, &(producer->event_listeners)) {
		reg_info = list_entry(pos,
				struct ipa_rm_notification_info,
				link);
		list_del(pos);
		kfree(reg_info);
	}
}

static int ipa_rm_resource_consumer_create(struct ipa_rm_resource **resource,
		struct ipa_rm_resource_cons **consumer,
		struct ipa_rm_create_params *create_params,
		int *max_peers)
{
	int result = 0;

	*consumer = kzalloc(sizeof(**consumer), GFP_ATOMIC);
	if (*consumer == NULL) {
		result = -ENOMEM;
		goto bail;
	}

	(*consumer)->request_resource = create_params->request_resource;
	(*consumer)->release_resource = create_params->release_resource;
	(*resource) = (struct ipa_rm_resource *) (*consumer);
	(*resource)->type = IPA_RM_CONSUMER;
	init_completion(&((*consumer)->request_consumer_in_progress));
	*max_peers = IPA_RM_RESOURCE_MAX;
bail:
	return result;
}

/**
 * ipa_rm_resource_create() - creates resource
 * @create_params: [in] parameters needed
 *			for resource initialization with IPA RM
 * @resource: [out] created resource
 *
 * Returns: 0 on success, negative on failure
 */
int ipa_rm_resource_create(
		struct ipa_rm_create_params *create_params,
		struct ipa_rm_resource **resource)
{
	struct ipa_rm_resource_cons *consumer;
	struct ipa_rm_resource_prod *producer;
	int max_peers;
	int result = 0;

	if (!create_params) {
		result = -EINVAL;
		goto bail;
	}

	if (IPA_RM_RESORCE_IS_PROD(create_params->name)) {
		result = ipa_rm_resource_producer_create(resource,
				&producer,
				create_params,
				&max_peers);
		if (result) {
			IPA_RM_ERR("ipa_rm_resource_producer_create failed\n");
			goto bail;
		}
	} else if (IPA_RM_RESORCE_IS_CONS(create_params->name)) {
		result = ipa_rm_resource_consumer_create(resource,
				&consumer,
				create_params,
				&max_peers);
		if (result) {
			IPA_RM_ERR("ipa_rm_resource_producer_create failed\n");
			goto bail;
		}
	} else {
		IPA_RM_ERR("invalid resource\n");
		result = -EPERM;
		goto bail;
	}

	result = ipa_rm_peers_list_create(max_peers,
			&((*resource)->peers_list));
	if (result) {
		IPA_RM_ERR("ipa_rm_peers_list_create failed\n");
		goto peers_alloc_fail;
	}
	(*resource)->name = create_params->name;
	(*resource)->floor_voltage = create_params->floor_voltage;
	(*resource)->state = IPA_RM_RELEASED;
	goto bail;

peers_alloc_fail:
	ipa_rm_resource_delete(*resource);
bail:
	return result;
}

/**
 * ipa_rm_resource_delete() - deletes resource
 * @resource: [in] resource
 *			for resource initialization with IPA RM
 *
 * Returns: 0 on success, negative on failure
 */
int ipa_rm_resource_delete(struct ipa_rm_resource *resource)
{
	struct ipa_rm_resource *consumer;
	struct ipa_rm_resource *producer;
	int peers_index;
	int result = 0;
	int list_size;
	bool userspace_dep;

	if (!resource) {
		IPA_RM_ERR("invalid params\n");
		return -EINVAL;
	}

	IPA_RM_DBG("ENTER with resource %d\n", resource->name);
	if (resource->type == IPA_RM_PRODUCER) {
		if (resource->peers_list) {
			list_size = ipa_rm_peers_list_get_size(
				resource->peers_list);
			for (peers_index = 0;
				peers_index < list_size;
				peers_index++) {
				consumer = ipa_rm_peers_list_get_resource(
						peers_index,
						resource->peers_list);
				if (consumer) {
					userspace_dep =
					ipa_rm_peers_list_get_userspace_dep(
							peers_index,
							resource->peers_list);
					ipa_rm_resource_delete_dependency(
						resource,
						consumer,
						userspace_dep);
				}
			}
		}

		ipa_rm_resource_producer_delete(
				(struct ipa_rm_resource_prod *) resource);
	} else if (resource->type == IPA_RM_CONSUMER) {
		if (resource->peers_list) {
			list_size = ipa_rm_peers_list_get_size(
				resource->peers_list);
			for (peers_index = 0;
					peers_index < list_size;
					peers_index++){
				producer = ipa_rm_peers_list_get_resource(
							peers_index,
							resource->peers_list);
				if (producer) {
					userspace_dep =
					ipa_rm_peers_list_get_userspace_dep(
						peers_index,
						resource->peers_list);
					ipa_rm_resource_delete_dependency(
							producer,
							resource,
							userspace_dep);
				}
			}
		}
	}
	ipa_rm_peers_list_delete(resource->peers_list);
	kfree(resource);
	return result;
}

/**
 * ipa_rm_resource_register() - register resource
 * @resource: [in] resource
 * @reg_params: [in] registration parameters
 * @explicit: [in] registered explicitly by ipa_rm_register()
 *
 * Returns: 0 on success, negative on failure
 *
 * Producer resource is expected for this call.
 *
 */
int ipa_rm_resource_producer_register(struct ipa_rm_resource_prod *producer,
		struct ipa_rm_register_params *reg_params,
		bool explicit)
{
	int result = 0;
	struct ipa_rm_notification_info *reg_info;
	struct list_head *pos;

	if (!producer || !reg_params) {
		IPA_RM_ERR("invalid params\n");
		result = -EPERM;
		goto bail;
	}

	list_for_each(pos, &(producer->event_listeners)) {
		reg_info = list_entry(pos,
					struct ipa_rm_notification_info,
					link);
		if (reg_info->reg_params.notify_cb ==
						reg_params->notify_cb) {
			IPA_RM_ERR("already registered\n");
			result = -EPERM;
			goto bail;
		}

	}

	reg_info = kzalloc(sizeof(*reg_info), GFP_ATOMIC);
	if (reg_info == NULL) {
		result = -ENOMEM;
		goto bail;
	}

	reg_info->reg_params.user_data = reg_params->user_data;
	reg_info->reg_params.notify_cb = reg_params->notify_cb;
	reg_info->explicit = explicit;
	INIT_LIST_HEAD(&reg_info->link);
	list_add(&reg_info->link, &producer->event_listeners);
bail:
	return result;
}

/**
 * ipa_rm_resource_deregister() - register resource
 * @resource: [in] resource
 * @reg_params: [in] registration parameters
 *
 * Returns: 0 on success, negative on failure
 *
 * Producer resource is expected for this call.
 * This function deleted only single instance of
 * registration info.
 *
 */
int ipa_rm_resource_producer_deregister(struct ipa_rm_resource_prod *producer,
		struct ipa_rm_register_params *reg_params)
{
	int result = -EINVAL;
	struct ipa_rm_notification_info *reg_info;
	struct list_head *pos, *q;

	if (!producer || !reg_params) {
		IPA_RM_ERR("invalid params\n");
		return -EINVAL;
	}

	list_for_each_safe(pos, q, &(producer->event_listeners)) {
		reg_info = list_entry(pos,
				struct ipa_rm_notification_info,
				link);
		if (reg_info->reg_params.notify_cb ==
						reg_params->notify_cb) {
			list_del(pos);
			kfree(reg_info);
			result = 0;
			goto bail;
		}
	}
bail:
	return result;
}

/**
 * ipa_rm_resource_add_dependency() - add dependency between two
 *				given resources
 * @resource: [in] resource resource
 * @depends_on: [in] depends_on resource
 *
 * Returns: 0 on success, negative on failure
 */
int ipa_rm_resource_add_dependency(struct ipa_rm_resource *resource,
				   struct ipa_rm_resource *depends_on,
				   bool userspace_dep)
{
	int result = 0;
	int consumer_result;
	bool add_dep_by_userspace;

	if (!resource || !depends_on) {
		IPA_RM_ERR("invalid params\n");
		return -EINVAL;
	}

	if (ipa_rm_peers_list_check_dependency(resource->peers_list,
			resource->name,
			depends_on->peers_list,
			depends_on->name,
			&add_dep_by_userspace)) {
		IPA_RM_ERR("dependency already exists, added by %s\n",
			add_dep_by_userspace ? "userspace" : "kernel");
		return -EEXIST;
	}

	ipa_rm_peers_list_add_peer(resource->peers_list, depends_on,
		userspace_dep);
	ipa_rm_peers_list_add_peer(depends_on->peers_list, resource,
		userspace_dep);
	IPA_RM_DBG("%s state: %d\n", ipa_rm_resource_str(resource->name),
				resource->state);

	resource->needed_bw += depends_on->max_bw;
	switch (resource->state) {
	case IPA_RM_RELEASED:
	case IPA_RM_RELEASE_IN_PROGRESS:
		break;
	case IPA_RM_GRANTED:
	case IPA_RM_REQUEST_IN_PROGRESS:
	{
		enum ipa_rm_resource_state prev_state = resource->state;

		resource->state = IPA_RM_REQUEST_IN_PROGRESS;
		((struct ipa_rm_resource_prod *)
					resource)->pending_request++;
		consumer_result = ipa_rm_resource_consumer_request(
				(struct ipa_rm_resource_cons *)depends_on,
				resource->max_bw,
				true, false);
		if (consumer_result != -EINPROGRESS) {
			resource->state = prev_state;
			((struct ipa_rm_resource_prod *)
					resource)->pending_request--;
			ipa_rm_perf_profile_change(resource->name);
		}
		result = consumer_result;
		break;
	}
	default:
		IPA_RM_ERR("invalid state\n");
		result = -EPERM;
		goto bail;
	}
bail:
	IPA_RM_DBG("%s new state: %d\n", ipa_rm_resource_str(resource->name),
					resource->state);
	IPA_RM_DBG("EXIT with %d\n", result);

	return result;
}

/**
 * ipa_rm_resource_delete_dependency() - add dependency between two
 *				given resources
 * @resource: [in] resource resource
 * @depends_on: [in] depends_on resource
 *
 * Returns: 0 on success, negative on failure
 * In case the resource state was changed, a notification
 * will be sent to the RM client
 */
int ipa_rm_resource_delete_dependency(struct ipa_rm_resource *resource,
				   struct ipa_rm_resource *depends_on,
				   bool userspace_dep)
{
	int result = 0;
	bool state_changed = false;
	bool release_consumer = false;
	enum ipa_rm_event evt;
	bool add_dep_by_userspace;

	if (!resource || !depends_on) {
		IPA_RM_ERR("invalid params\n");
		return -EINVAL;
	}

	if (!ipa_rm_peers_list_check_dependency(resource->peers_list,
			resource->name,
			depends_on->peers_list,
			depends_on->name,
			&add_dep_by_userspace)) {
		IPA_RM_ERR("dependency does not exist\n");
		return -EINVAL;
	}

	/*
	 * to avoid race conditions between kernel and userspace
	 * need to check that the dependency was added by same entity
	 */
	if (add_dep_by_userspace != userspace_dep) {
		IPA_RM_DBG("dependency was added by %s\n",
			add_dep_by_userspace ? "userspace" : "kernel");
		IPA_RM_DBG("ignore request to delete dependency by %s\n",
			userspace_dep ? "userspace" : "kernel");
		return 0;
	}

	IPA_RM_DBG("%s state: %d\n", ipa_rm_resource_str(resource->name),
				resource->state);

	resource->needed_bw -= depends_on->max_bw;
	switch (resource->state) {
	case IPA_RM_RELEASED:
		break;
	case IPA_RM_GRANTED:
		ipa_rm_perf_profile_change(resource->name);
		release_consumer = true;
		break;
	case IPA_RM_RELEASE_IN_PROGRESS:
		if (((struct ipa_rm_resource_prod *)
			resource)->pending_release > 0)
			((struct ipa_rm_resource_prod *)
				resource)->pending_release--;
		if (depends_on->state == IPA_RM_RELEASE_IN_PROGRESS &&
			((struct ipa_rm_resource_prod *)
			resource)->pending_release == 0) {
			resource->state = IPA_RM_RELEASED;
			state_changed = true;
			evt = IPA_RM_RESOURCE_RELEASED;
			ipa_rm_perf_profile_change(resource->name);
		}
		break;
	case IPA_RM_REQUEST_IN_PROGRESS:
		release_consumer = true;
		if (((struct ipa_rm_resource_prod *)
			resource)->pending_request > 0)
			((struct ipa_rm_resource_prod *)
				resource)->pending_request--;
		if (depends_on->state == IPA_RM_REQUEST_IN_PROGRESS &&
			((struct ipa_rm_resource_prod *)
				resource)->pending_request == 0) {
			resource->state = IPA_RM_GRANTED;
			state_changed = true;
			evt = IPA_RM_RESOURCE_GRANTED;
			ipa_rm_perf_profile_change(resource->name);
		}
		break;
	default:
		result = -EINVAL;
		goto bail;
	}
	if (state_changed) {
		(void) ipa_rm_wq_send_cmd(IPA_RM_WQ_NOTIFY_PROD,
				resource->name,
				evt,
				false);
	}
	IPA_RM_DBG("%s new state: %d\n", ipa_rm_resource_str(resource->name),
					resource->state);
	ipa_rm_peers_list_remove_peer(resource->peers_list,
			depends_on->name);
	ipa_rm_peers_list_remove_peer(depends_on->peers_list,
			resource->name);
	if (release_consumer)
		(void) ipa_rm_resource_consumer_release(
				(struct ipa_rm_resource_cons *)depends_on,
				resource->max_bw,
				true);
bail:
	IPA_RM_DBG("EXIT with %d\n", result);

	return result;
}

/**
 * ipa_rm_resource_producer_request() - producer resource request
 * @producer: [in] producer
 *
 * Returns: 0 on success, negative on failure
 */
int ipa_rm_resource_producer_request(struct ipa_rm_resource_prod *producer)
{
	int peers_index;
	int result = 0;
	struct ipa_rm_resource *consumer;
	int consumer_result;
	enum ipa_rm_resource_state state;

	state = producer->resource.state;
	switch (producer->resource.state) {
	case IPA_RM_RELEASED:
	case IPA_RM_RELEASE_IN_PROGRESS:
		producer->resource.state = IPA_RM_REQUEST_IN_PROGRESS;
		break;
	case IPA_RM_GRANTED:
		goto unlock_and_bail;
	case IPA_RM_REQUEST_IN_PROGRESS:
		result = -EINPROGRESS;
		goto unlock_and_bail;
	default:
		result = -EINVAL;
		goto unlock_and_bail;
	}

	producer->pending_request = 0;
	for (peers_index = 0;
		peers_index < ipa_rm_peers_list_get_size(
				producer->resource.peers_list);
		peers_index++) {
		consumer = ipa_rm_peers_list_get_resource(peers_index,
				producer->resource.peers_list);
		if (consumer) {
			producer->pending_request++;
			consumer_result = ipa_rm_resource_consumer_request(
				(struct ipa_rm_resource_cons *)consumer,
				producer->resource.max_bw,
				true, false);
			if (consumer_result == -EINPROGRESS) {
				result = -EINPROGRESS;
			} else {
				producer->pending_request--;
				if (consumer_result != 0) {
					result = consumer_result;
					goto bail;
				}
			}
		}
	}

	if (producer->pending_request == 0) {
		producer->resource.state = IPA_RM_GRANTED;
		ipa_rm_perf_profile_change(producer->resource.name);
		(void) ipa_rm_wq_send_cmd(IPA_RM_WQ_NOTIFY_PROD,
			producer->resource.name,
			IPA_RM_RESOURCE_GRANTED,
			true);
		result = 0;
	}
unlock_and_bail:
	if (state != producer->resource.state)
		IPA_RM_DBG_LOW("%s state changed %d->%d\n",
			ipa_rm_resource_str(producer->resource.name),
			state,
			producer->resource.state);
bail:
	return result;
}

/**
 * ipa_rm_resource_producer_release() - producer resource release
 * producer: [in] producer resource
 *
 * Returns: 0 on success, negative on failure
 *
 */
int ipa_rm_resource_producer_release(struct ipa_rm_resource_prod *producer)
{
	int peers_index;
	int result = 0;
	struct ipa_rm_resource *consumer;
	int consumer_result;
	enum ipa_rm_resource_state state;

	state = producer->resource.state;
	switch (producer->resource.state) {
	case IPA_RM_RELEASED:
		goto bail;
	case IPA_RM_GRANTED:
	case IPA_RM_REQUEST_IN_PROGRESS:
		producer->resource.state = IPA_RM_RELEASE_IN_PROGRESS;
		break;
	case IPA_RM_RELEASE_IN_PROGRESS:
		result = -EINPROGRESS;
		goto bail;
	default:
		result = -EPERM;
		goto bail;
	}

	producer->pending_release = 0;
	for (peers_index = 0;
		peers_index < ipa_rm_peers_list_get_size(
				producer->resource.peers_list);
		peers_index++) {
		consumer = ipa_rm_peers_list_get_resource(peers_index,
				producer->resource.peers_list);
		if (consumer) {
			producer->pending_release++;
			consumer_result = ipa_rm_resource_consumer_release(
				(struct ipa_rm_resource_cons *)consumer,
				producer->resource.max_bw,
				true);
			producer->pending_release--;
		}
	}

	if (producer->pending_release == 0) {
		producer->resource.state = IPA_RM_RELEASED;
		ipa_rm_perf_profile_change(producer->resource.name);
		(void) ipa_rm_wq_send_cmd(IPA_RM_WQ_NOTIFY_PROD,
			producer->resource.name,
			IPA_RM_RESOURCE_RELEASED,
			true);
	}
bail:
	if (state != producer->resource.state)
		IPA_RM_DBG_LOW("%s state changed %d->%d\n",
		ipa_rm_resource_str(producer->resource.name),
		state,
		producer->resource.state);

	return result;
}

static void ipa_rm_resource_producer_handle_cb(
		struct ipa_rm_resource_prod *producer,
		enum ipa_rm_event event)
{
	IPA_RM_DBG_LOW("%s state: %d event: %d pending_request: %d\n",
		ipa_rm_resource_str(producer->resource.name),
		producer->resource.state,
		event,
		producer->pending_request);

	switch (producer->resource.state) {
	case IPA_RM_REQUEST_IN_PROGRESS:
		if (event != IPA_RM_RESOURCE_GRANTED)
			goto unlock_and_bail;
		if (producer->pending_request > 0) {
			producer->pending_request--;
			if (producer->pending_request == 0) {
				producer->resource.state =
						IPA_RM_GRANTED;
				ipa_rm_perf_profile_change(
					producer->resource.name);
				ipa_rm_resource_producer_notify_clients(
						producer,
						IPA_RM_RESOURCE_GRANTED,
						false);
				goto bail;
			}
		}
		break;
	case IPA_RM_RELEASE_IN_PROGRESS:
		if (event != IPA_RM_RESOURCE_RELEASED)
			goto unlock_and_bail;
		if (producer->pending_release > 0) {
			producer->pending_release--;
			if (producer->pending_release == 0) {
				producer->resource.state =
						IPA_RM_RELEASED;
				ipa_rm_perf_profile_change(
					producer->resource.name);
				ipa_rm_resource_producer_notify_clients(
						producer,
						IPA_RM_RESOURCE_RELEASED,
						false);
				goto bail;
			}
		}
		break;
	case IPA_RM_GRANTED:
	case IPA_RM_RELEASED:
	default:
		goto unlock_and_bail;
	}
unlock_and_bail:
	IPA_RM_DBG_LOW("%s new state: %d\n",
		ipa_rm_resource_str(producer->resource.name),
		producer->resource.state);
bail:
	return;
}

/**
 * ipa_rm_resource_consumer_handle_cb() - propagates resource
 *	notification to all dependent producers
 * @consumer: [in] notifying resource
 *
 */
void ipa_rm_resource_consumer_handle_cb(struct ipa_rm_resource_cons *consumer,
				enum ipa_rm_event event)
{
	int peers_index;
	struct ipa_rm_resource *producer;

	if (!consumer) {
		IPA_RM_ERR("invalid params\n");
		return;
	}
	IPA_RM_DBG_LOW("%s state: %d event: %d\n",
		ipa_rm_resource_str(consumer->resource.name),
		consumer->resource.state,
		event);

	switch (consumer->resource.state) {
	case IPA_RM_REQUEST_IN_PROGRESS:
		if (event == IPA_RM_RESOURCE_RELEASED)
			goto bail;
		consumer->resource.state = IPA_RM_GRANTED;
		ipa_rm_perf_profile_change(consumer->resource.name);
		ipa_resume_resource(consumer->resource.name);
		complete_all(&consumer->request_consumer_in_progress);
		break;
	case IPA_RM_RELEASE_IN_PROGRESS:
		if (event == IPA_RM_RESOURCE_GRANTED)
			goto bail;
		consumer->resource.state = IPA_RM_RELEASED;
		break;
	case IPA_RM_GRANTED:
	case IPA_RM_RELEASED:
	default:
		goto bail;
	}

	for (peers_index = 0;
		peers_index < ipa_rm_peers_list_get_size(
				consumer->resource.peers_list);
		peers_index++) {
		producer = ipa_rm_peers_list_get_resource(peers_index,
				consumer->resource.peers_list);
		if (producer)
			ipa_rm_resource_producer_handle_cb(
					(struct ipa_rm_resource_prod *)
						producer,
						event);
	}

	return;
bail:
	IPA_RM_DBG_LOW("%s new state: %d\n",
		ipa_rm_resource_str(consumer->resource.name),
		consumer->resource.state);
}

/*
 * ipa_rm_resource_set_perf_profile() - sets the performance profile to
 *					resource.
 *
 * @resource: [in] resource
 * @profile: [in] profile to be set
 *
 * sets the profile to the given resource, In case the resource is
 * granted, update bandwidth vote of the resource
 */
int ipa_rm_resource_set_perf_profile(struct ipa_rm_resource *resource,
				     struct ipa_rm_perf_profile *profile)
{
	int peers_index;
	struct ipa_rm_resource *peer;

	if (!resource || !profile) {
		IPA_RM_ERR("invalid params\n");
		return -EINVAL;
	}

	if (profile->max_supported_bandwidth_mbps == resource->max_bw) {
		IPA_RM_DBG_LOW("same profile\n");
		return 0;
	}

	if ((resource->type == IPA_RM_PRODUCER &&
	    (resource->state == IPA_RM_GRANTED ||
	    resource->state == IPA_RM_REQUEST_IN_PROGRESS)) ||
	    resource->type == IPA_RM_CONSUMER) {
		for (peers_index = 0;
		     peers_index < ipa_rm_peers_list_get_size(
		     resource->peers_list);
		     peers_index++) {
			peer = ipa_rm_peers_list_get_resource(peers_index,
				resource->peers_list);
			if (!peer)
				continue;
			peer->needed_bw -= resource->max_bw;
			peer->needed_bw +=
				profile->max_supported_bandwidth_mbps;
			if (peer->state == IPA_RM_GRANTED)
				ipa_rm_perf_profile_change(peer->name);
		}
	}

	resource->max_bw = profile->max_supported_bandwidth_mbps;
	if (resource->state == IPA_RM_GRANTED)
		ipa_rm_perf_profile_change(resource->name);

	return 0;
}


/*
 * ipa_rm_resource_producer_print_stat() - print the
 * resource status and all his dependencies
 *
 * @resource: [in] Resource resource
 * @buff: [in] The buf used to print
 * @size: [in] Buf size
 *
 * Returns: number of bytes used on success, negative on failure
 */
int ipa_rm_resource_producer_print_stat(
				struct ipa_rm_resource *resource,
				char *buf,
				int size)
{

	int i;
	int nbytes;
	int cnt = 0;
	struct ipa_rm_resource *consumer;

	if (!buf || size < 0)
		return -EINVAL;

	nbytes = scnprintf(buf + cnt, size - cnt,
		ipa_rm_resource_str(resource->name));
	cnt += nbytes;
	nbytes = scnprintf(buf + cnt, size - cnt, "[%d, ", resource->max_bw);
	cnt += nbytes;

	switch (resource->state) {
	case IPA_RM_RELEASED:
		nbytes = scnprintf(buf + cnt, size - cnt,
			"Released] -> ");
		cnt += nbytes;
		break;
	case IPA_RM_REQUEST_IN_PROGRESS:
		nbytes = scnprintf(buf + cnt, size - cnt,
			"Request In Progress] -> ");
		cnt += nbytes;
		break;
	case IPA_RM_GRANTED:
		nbytes = scnprintf(buf + cnt, size - cnt,
			"Granted] -> ");
		cnt += nbytes;
		break;
	case IPA_RM_RELEASE_IN_PROGRESS:
		nbytes = scnprintf(buf + cnt, size - cnt,
			"Release In Progress] -> ");
		cnt += nbytes;
		break;
	default:
		return -EPERM;
	}

	for (i = 0; i < resource->peers_list->max_peers; ++i) {
		consumer =
			ipa_rm_peers_list_get_resource(
			i,
			resource->peers_list);
		if (consumer) {
			nbytes = scnprintf(buf + cnt, size - cnt,
				ipa_rm_resource_str(consumer->name));
			cnt += nbytes;
			nbytes = scnprintf(buf + cnt, size - cnt, "[%d, ",
				consumer->max_bw);
			cnt += nbytes;

			switch (consumer->state) {
			case IPA_RM_RELEASED:
				nbytes = scnprintf(buf + cnt, size - cnt,
					"Released], ");
				cnt += nbytes;
				break;
			case IPA_RM_REQUEST_IN_PROGRESS:
				nbytes = scnprintf(buf + cnt, size - cnt,
						"Request In Progress], ");
				cnt += nbytes;
					break;
			case IPA_RM_GRANTED:
				nbytes = scnprintf(buf + cnt, size - cnt,
						"Granted], ");
				cnt += nbytes;
				break;
			case IPA_RM_RELEASE_IN_PROGRESS:
				nbytes = scnprintf(buf + cnt, size - cnt,
						"Release In Progress], ");
				cnt += nbytes;
				break;
			default:
				return -EPERM;
			}
		}
	}
	nbytes = scnprintf(buf + cnt, size - cnt, "\n");
	cnt += nbytes;

	return cnt;
}
