// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013-2018, The Linux Foundation. All rights reserved. */ #include #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(®_info->link); list_add(®_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; }