blob: 991208f95e6d4fc2f839db04e007047e9b88be5f [file] [log] [blame]
/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 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.
*/
#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) {
IPA_RM_ERR("no mem\n");
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) {
IPA_RM_ERR("no mem\n");
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("invalied 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("ipa_rm_resource_delete 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) {
IPA_RM_ERR("no mem\n");
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;
}