/* Copyright (c) 2013, 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 #include "ipa_i.h" #include "ipa_rm_resource.h" #include "ipa_rm_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_BRIDGE_PROD: case IPA_RM_RESOURCE_A2_PROD: case IPA_RM_RESOURCE_USB_PROD: case IPA_RM_RESOURCE_HSIC_PROD: case IPA_RM_RESOURCE_STD_ECM_PROD: case IPA_RM_RESOURCE_WWAN_0_PROD: case IPA_RM_RESOURCE_WWAN_1_PROD: case IPA_RM_RESOURCE_WWAN_2_PROD: case IPA_RM_RESOURCE_WWAN_3_PROD: case IPA_RM_RESOURCE_WWAN_4_PROD: case IPA_RM_RESOURCE_WWAN_5_PROD: case IPA_RM_RESOURCE_WWAN_6_PROD: case IPA_RM_RESOURCE_WWAN_7_PROD: case IPA_RM_RESOURCE_WLAN_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_A2_CONS: case IPA_RM_RESOURCE_USB_CONS: case IPA_RM_RESOURCE_HSIC_CONS: break; default: result = IPA_RM_INDEX_INVALID; break; } return result; } static int ipa_rm_resource_consumer_request( struct ipa_rm_resource_cons *consumer) { int result = 0; int driver_result; unsigned long flags; spin_lock_irqsave(&consumer->resource.state_lock, flags); IPA_RM_DBG("%s state: %d\n", ipa_rm_resource_str(consumer->resource.name), consumer->resource.state); switch (consumer->resource.state) { case IPA_RM_RELEASED: case IPA_RM_RELEASE_IN_PROGRESS: { enum ipa_rm_resource_state prev_state = consumer->resource.state; consumer->resource.state = IPA_RM_REQUEST_IN_PROGRESS; spin_unlock_irqrestore(&consumer->resource.state_lock, flags); IPA_RM_DBG("calling driver CB\n"); driver_result = consumer->request_resource(); IPA_RM_DBG("driver CB returned with %d\n", driver_result); spin_lock_irqsave(&consumer->resource.state_lock, flags); if (driver_result == 0) consumer->resource.state = IPA_RM_GRANTED; else if (driver_result != -EINPROGRESS) { consumer->resource.state = prev_state; result = driver_result; goto bail; } result = driver_result; break; } case IPA_RM_GRANTED: break; case IPA_RM_REQUEST_IN_PROGRESS: result = -EINPROGRESS; break; default: result = -EPERM; goto bail; } consumer->usage_count++; bail: IPA_RM_DBG("%s new state: %d\n", ipa_rm_resource_str(consumer->resource.name), consumer->resource.state); spin_unlock_irqrestore(&consumer->resource.state_lock, flags); IPA_RM_DBG("EXIT with %d\n", result); return result; } static int ipa_rm_resource_consumer_release( struct ipa_rm_resource_cons *consumer) { int result = 0; int driver_result; unsigned long flags; enum ipa_rm_resource_state save_state; spin_lock_irqsave(&consumer->resource.state_lock, flags); IPA_RM_DBG("%s state: %d\n", ipa_rm_resource_str(consumer->resource.name), consumer->resource.state); switch (consumer->resource.state) { case IPA_RM_RELEASED: break; case IPA_RM_GRANTED: case IPA_RM_REQUEST_IN_PROGRESS: if (consumer->usage_count > 0) consumer->usage_count--; if (consumer->usage_count == 0) { save_state = consumer->resource.state; consumer->resource.state = IPA_RM_RELEASE_IN_PROGRESS; spin_unlock_irqrestore(&consumer->resource.state_lock, flags); IPA_RM_DBG("calling driver CB\n"); driver_result = consumer->release_resource(); IPA_RM_DBG("driver CB returned with %d\n", driver_result); spin_lock_irqsave(&consumer->resource.state_lock, flags); if (driver_result == 0) consumer->resource.state = IPA_RM_RELEASED; else if (driver_result != -EINPROGRESS) consumer->resource.state = save_state; result = driver_result; } break; case IPA_RM_RELEASE_IN_PROGRESS: if (consumer->usage_count > 0) consumer->usage_count--; result = -EINPROGRESS; break; default: result = -EPERM; goto bail; } bail: IPA_RM_DBG("%s new state: %d\n", ipa_rm_resource_str(consumer->resource.name), consumer->resource.state); spin_unlock_irqrestore(&consumer->resource.state_lock, flags); IPA_RM_DBG("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, *reg_info_cloned; struct list_head *pos, *q; LIST_HEAD(cloned_list); IPA_RM_DBG("%s event: %d notify_registered_only: %d\n", ipa_rm_resource_str(producer->resource.name), event, notify_registered_only); read_lock(&producer->event_listeners_lock); list_for_each(pos, &(producer->event_listeners)) { reg_info = list_entry(pos, struct ipa_rm_notification_info, link); if (notify_registered_only && !reg_info->explicit) continue; reg_info_cloned = kzalloc(sizeof(*reg_info_cloned), GFP_KERNEL); if (!reg_info_cloned) { IPA_RM_ERR("No memory\n"); goto clone_list_failed; } reg_info_cloned->reg_params.notify_cb = reg_info->reg_params.notify_cb; reg_info_cloned->reg_params.user_data = reg_info->reg_params.user_data; list_add(®_info_cloned->link, &cloned_list); } read_unlock(&producer->event_listeners_lock); list_for_each_safe(pos, q, &cloned_list) { reg_info = list_entry(pos, struct ipa_rm_notification_info, link); IPA_RM_DBG("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("back from client CB\n"); list_del(pos); kfree(reg_info); } return; clone_list_failed: read_unlock(&producer->event_listeners_lock); } 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_KERNEL); if (*producer == NULL) { IPA_RM_ERR("no mem\n"); result = -ENOMEM; goto bail; } rwlock_init(&(*producer)->event_listeners_lock); 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_CONS_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; write_lock(&producer->event_listeners_lock); 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); } write_unlock(&producer->event_listeners_lock); } 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_KERNEL); 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; *max_peers = IPA_RM_RESOURCE_PROD_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)->state = IPA_RM_RELEASED; spin_lock_init(&((*resource)->state_lock)); 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, *producer; int peers_index, result = 0, list_size; IPA_RM_DBG("ipa_rm_resource_delete ENTER with resource %d\n", resource->name); if (!resource) { IPA_RM_ERR("invalid params\n"); return -EINVAL; } 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) ipa_rm_resource_delete_dependency( resource, consumer); } ipa_rm_peers_list_delete(resource->peers_list); } ipa_rm_resource_producer_delete( (struct ipa_rm_resource_prod *) resource); kfree((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) ipa_rm_resource_delete_dependency( producer, resource); } ipa_rm_peers_list_delete(resource->peers_list); } kfree((struct ipa_rm_resource_cons *) 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; } read_lock(&producer->event_listeners_lock); 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; read_unlock(&producer->event_listeners_lock); goto bail; } } read_unlock(&producer->event_listeners_lock); reg_info = kzalloc(sizeof(*reg_info), GFP_KERNEL); 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(®_info->link); write_lock(&producer->event_listeners_lock); list_add(®_info->link, &producer->event_listeners); write_unlock(&producer->event_listeners_lock); 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; } write_lock(&producer->event_listeners_lock); 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: write_unlock(&producer->event_listeners_lock); 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) { int result = 0; unsigned long flags; int consumer_result; 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)) { IPA_RM_ERR("dependency already exists\n"); return -EEXIST; } ipa_rm_peers_list_add_peer(resource->peers_list, depends_on); ipa_rm_peers_list_add_peer(depends_on->peers_list, resource); spin_lock_irqsave(&resource->state_lock, flags); IPA_RM_DBG("%s state: %d\n", ipa_rm_resource_str(resource->name), resource->state); 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++; spin_unlock_irqrestore(&resource->state_lock, flags); consumer_result = ipa_rm_resource_consumer_request( (struct ipa_rm_resource_cons *)depends_on); spin_lock_irqsave(&resource->state_lock, flags); if (consumer_result != -EINPROGRESS) { resource->state = prev_state; ((struct ipa_rm_resource_prod *) resource)->pending_request--; } 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); spin_unlock_irqrestore(&resource->state_lock, flags); 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 * EINPROGRESS is returned in case this is the last dependency * of given resource and IPA RM client should receive the RELEASED cb */ int ipa_rm_resource_delete_dependency(struct ipa_rm_resource *resource, struct ipa_rm_resource *depends_on) { int result = 0; unsigned long flags; unsigned long consumer_flags; bool state_changed = false; bool release_consumer = false; enum ipa_rm_event evt; 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)) { IPA_RM_ERR("dependency does not exist\n"); return -EINVAL; } spin_lock_irqsave(&resource->state_lock, flags); IPA_RM_DBG("%s state: %d\n", ipa_rm_resource_str(resource->name), resource->state); switch (resource->state) { case IPA_RM_RELEASED: break; case IPA_RM_GRANTED: 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--; spin_lock_irqsave(&depends_on->state_lock, consumer_flags); 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; } spin_unlock_irqrestore(&depends_on->state_lock, consumer_flags); 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--; spin_lock_irqsave(&depends_on->state_lock, consumer_flags); 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; } spin_unlock_irqrestore(&depends_on->state_lock, consumer_flags); break; default: result = -EINVAL; spin_unlock_irqrestore(&resource->state_lock, flags); goto bail; } if (state_changed && ipa_rm_peers_list_has_last_peer(resource->peers_list)) { (void) ipa_rm_wq_send_cmd(IPA_RM_WQ_NOTIFY_PROD, resource->name, evt, false); result = -EINPROGRESS; } IPA_RM_DBG("%s new state: %d\n", ipa_rm_resource_str(resource->name), resource->state); spin_unlock_irqrestore(&resource->state_lock, flags); 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); 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; unsigned long flags; struct ipa_rm_resource *consumer; int consumer_result; enum ipa_rm_resource_state state; if (ipa_rm_peers_list_is_empty(producer->resource.peers_list)) { spin_lock_irqsave(&producer->resource.state_lock, flags); state = producer->resource.state; producer->resource.state = IPA_RM_GRANTED; (void) ipa_rm_wq_send_cmd(IPA_RM_WQ_NOTIFY_PROD, producer->resource.name, IPA_RM_RESOURCE_GRANTED, true); result = 0; goto unlock_and_bail; } spin_lock_irqsave(&producer->resource.state_lock, flags); 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; spin_unlock_irqrestore(&producer->resource.state_lock, flags); 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) { spin_lock_irqsave( &producer->resource.state_lock, flags); producer->pending_request++; spin_unlock_irqrestore( &producer->resource.state_lock, flags); consumer_result = ipa_rm_resource_consumer_request( (struct ipa_rm_resource_cons *)consumer); if (consumer_result == -EINPROGRESS) { result = -EINPROGRESS; } else { spin_lock_irqsave( &producer->resource.state_lock, flags); producer->pending_request--; spin_unlock_irqrestore( &producer->resource.state_lock, flags); if (consumer_result != 0) { result = consumer_result; goto bail; } } } } spin_lock_irqsave(&producer->resource.state_lock, flags); if (producer->pending_request == 0) { producer->resource.state = IPA_RM_GRANTED; (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("%s state changed %d->%d\n", ipa_rm_resource_str(producer->resource.name), state, producer->resource.state); spin_unlock_irqrestore(&producer->resource.state_lock, flags); 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; unsigned long flags; struct ipa_rm_resource *consumer; int consumer_result; enum ipa_rm_resource_state state; if (ipa_rm_peers_list_is_empty(producer->resource.peers_list)) { spin_lock_irqsave(&producer->resource.state_lock, flags); state = producer->resource.state; producer->resource.state = IPA_RM_RELEASED; (void) ipa_rm_wq_send_cmd(IPA_RM_WQ_NOTIFY_PROD, producer->resource.name, IPA_RM_RESOURCE_RELEASED, true); goto bail; } spin_lock_irqsave(&producer->resource.state_lock, flags); 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; spin_unlock_irqrestore(&producer->resource.state_lock, flags); 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) { spin_lock_irqsave( &producer->resource.state_lock, flags); producer->pending_release++; spin_unlock_irqrestore( &producer->resource.state_lock, flags); consumer_result = ipa_rm_resource_consumer_release( (struct ipa_rm_resource_cons *)consumer); spin_lock_irqsave( &producer->resource.state_lock, flags); producer->pending_release--; spin_unlock_irqrestore( &producer->resource.state_lock, flags); } } spin_lock_irqsave(&producer->resource.state_lock, flags); if (producer->pending_release == 0) { producer->resource.state = IPA_RM_RELEASED; (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("%s state changed %d->%d\n", ipa_rm_resource_str(producer->resource.name), state, producer->resource.state); spin_unlock_irqrestore(&producer->resource.state_lock, flags); return result; } static void ipa_rm_resource_producer_handle_cb( struct ipa_rm_resource_prod *producer, enum ipa_rm_event event) { unsigned long flags; spin_lock_irqsave(&producer->resource.state_lock, flags); IPA_RM_DBG("%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; spin_unlock_irqrestore( &producer->resource.state_lock, flags); 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; spin_unlock_irqrestore( &producer->resource.state_lock, flags); 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("%s new state: %d\n", ipa_rm_resource_str(producer->resource.name), producer->resource.state); spin_unlock_irqrestore(&producer->resource.state_lock, flags); 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; unsigned long flags; if (!consumer) { IPA_RM_ERR("invalid params\n"); return; } spin_lock_irqsave(&consumer->resource.state_lock, flags); IPA_RM_DBG("%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; 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; } spin_unlock_irqrestore(&consumer->resource.state_lock, flags); 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("%s new state: %d\n", ipa_rm_resource_str(consumer->resource.name), consumer->resource.state); spin_unlock_irqrestore(&consumer->resource.state_lock, flags); return; } /* * 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, nbytes, cnt = 0; unsigned long flags; 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, "["); cnt += nbytes; spin_lock_irqsave(&resource->state_lock, flags); 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: spin_unlock_irqrestore( &resource->state_lock, flags); return -EPERM; } spin_unlock_irqrestore( &resource->state_lock, flags); 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, "["); cnt += nbytes; spin_lock_irqsave(&consumer->state_lock, flags); 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: spin_unlock_irqrestore( &consumer->state_lock, flags); return -EPERM; } spin_unlock_irqrestore( &consumer->state_lock, flags); } } nbytes = scnprintf(buf + cnt, size - cnt, "\n"); cnt += nbytes; return cnt; }