Files
android_kernel_samsung_sm86…/components/dsc/src/wlan_dsc_psoc.c
Sourav Mohapatra b273c0fa3a qcacld-3.0: Reject down the tree transitions instead of waiting
The driver currently waits for driver transitions (loading/unloading)
to complete before allowing a psoc or vdev level transition/operation.
This can lead to race conditions if the driver acquires rtnl lock as a part
of the operation and simultaneously a driver transition is invoked.

One such scenario occurs if delete virtual interface is invoked in parallel
to rmmod. The timing of the calls can be such that the delete interface is
waiting on DSC queue to complete after rmmod while the rmmod is waiting on
the rtnl lock to be freed by the delete interface resulting in a scenario
where the threads are waiting on the other to complete.

* Thread B - Invokes rmmod and context switch happens before rtnl lock is
             taken

* Thread A - Takes rtnl lock and invokes iw dev wlan0 del
	   - Context switch after entering wlan_hdd_del_virtual_intf
	     before osif_vdev_sync_trans_start_wait

* Thread B - Waits for rtnl lock to be released by Thread A

* Thread A - Waits for driver transition to be completed by Thread B

To avoid this possible scenario, modify the infrastructure such that any
down the tree transitions/operations are rejected if a driver transition
is taking place instead of waiting. Also, modify the corresponding tests
in the DSC unit test framework to correctly verify the changes made.

Change-Id: I61715c8fc2df33fd2deb46389da0375e4df5080c
CRs-Fixed: 2475386
2019-08-08 08:40:17 -07:00

398 lines
8.5 KiB
C

/*
* Copyright (c) 2018-2019 The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "qdf_list.h"
#include "qdf_status.h"
#include "qdf_talloc.h"
#include "qdf_types.h"
#include "__wlan_dsc.h"
#include "wlan_dsc.h"
#define __dsc_driver_lock(psoc) __dsc_lock((psoc)->driver)
#define __dsc_driver_unlock(psoc) __dsc_unlock((psoc)->driver)
static QDF_STATUS
__dsc_psoc_create(struct dsc_driver *driver, struct dsc_psoc **out_psoc)
{
struct dsc_psoc *psoc;
if (!dsc_assert(driver))
return QDF_STATUS_E_INVAL;
if (!dsc_assert(out_psoc))
return QDF_STATUS_E_INVAL;
*out_psoc = NULL;
psoc = qdf_talloc_type(driver, psoc);
if (!psoc)
return QDF_STATUS_E_NOMEM;
/* init */
psoc->driver = driver;
qdf_list_create(&psoc->vdevs, 0);
__dsc_trans_init(&psoc->trans);
__dsc_ops_init(&psoc->ops);
/* attach */
__dsc_driver_lock(psoc);
qdf_list_insert_back(&driver->psocs, &psoc->node);
__dsc_driver_unlock(psoc);
*out_psoc = psoc;
return QDF_STATUS_SUCCESS;
}
QDF_STATUS
dsc_psoc_create(struct dsc_driver *driver, struct dsc_psoc **out_psoc)
{
QDF_STATUS status;
dsc_enter();
status = __dsc_psoc_create(driver, out_psoc);
dsc_exit();
return status;
}
static void __dsc_psoc_destroy(struct dsc_psoc **out_psoc)
{
struct dsc_psoc *psoc;
if (!dsc_assert(out_psoc))
return;
psoc = *out_psoc;
if (!dsc_assert(psoc))
return;
*out_psoc = NULL;
/* assert no children */
dsc_assert(qdf_list_empty(&psoc->vdevs));
/* flush pending transitions */
while (__dsc_trans_abort(&psoc->trans))
;
/* detach */
__dsc_driver_lock(psoc);
qdf_list_remove_node(&psoc->driver->psocs, &psoc->node);
__dsc_driver_unlock(psoc);
/* de-init */
__dsc_ops_deinit(&psoc->ops);
__dsc_trans_deinit(&psoc->trans);
qdf_list_destroy(&psoc->vdevs);
psoc->driver = NULL;
qdf_tfree(psoc);
}
void dsc_psoc_destroy(struct dsc_psoc **out_psoc)
{
dsc_enter();
__dsc_psoc_destroy(out_psoc);
dsc_exit();
}
static bool __dsc_psoc_trans_active_down_tree(struct dsc_psoc *psoc)
{
struct dsc_vdev *vdev;
dsc_for_each_psoc_vdev(psoc, vdev) {
if (__dsc_trans_active(&vdev->trans))
return true;
}
return false;
}
#define __dsc_psoc_can_op(psoc) __dsc_psoc_can_trans(psoc)
/*
* __dsc_psoc_can_trans() - Returns if the psoc transition can occur or not
* @psoc: The DSC psoc
*
* This function checks if the psoc transition can occur or not by checking if
* any other down the tree/up the tree transition/operation is taking place.
*
* If there are any driver transition taking place, then the psoc trans/ops
* should be rejected and not queued in the DSC queue. Return QDF_STATUS_E_INVAL
* in this case.
*
* If there any psoc or vdev trans/ops is taking place, then the psoc trans/ops
* should be rejected and queued in the DSC queue so that it may be resumed
* after the current trans/ops is completed. Return QDF_STATUS_E_AGAIN in this
* case.
*
* Return: QDF_STATUS_SUCCESS if transition is allowed, error code if not.
*/
static QDF_STATUS __dsc_psoc_can_trans(struct dsc_psoc *psoc)
{
if (__dsc_trans_active_or_queued(&psoc->driver->trans))
return QDF_STATUS_E_INVAL;
if (__dsc_trans_active_or_queued(&psoc->trans) ||
__dsc_psoc_trans_active_down_tree(psoc))
return QDF_STATUS_E_AGAIN;
return QDF_STATUS_SUCCESS;
}
static bool __dsc_psoc_can_trigger(struct dsc_psoc *psoc)
{
return !__dsc_trans_active_or_queued(&psoc->driver->trans) &&
!__dsc_trans_active(&psoc->trans) &&
!__dsc_psoc_trans_active_down_tree(psoc);
}
static QDF_STATUS
__dsc_psoc_trans_start_nolock(struct dsc_psoc *psoc, const char *desc)
{
QDF_STATUS status;
status = __dsc_psoc_can_trans(psoc);
if (QDF_IS_STATUS_ERROR(status))
return status;
return __dsc_trans_start(&psoc->trans, desc);
}
static QDF_STATUS
__dsc_psoc_trans_start(struct dsc_psoc *psoc, const char *desc)
{
QDF_STATUS status;
if (!dsc_assert(psoc))
return QDF_STATUS_E_INVAL;
if (!dsc_assert(desc))
return QDF_STATUS_E_INVAL;
__dsc_driver_lock(psoc);
status = __dsc_psoc_trans_start_nolock(psoc, desc);
__dsc_driver_unlock(psoc);
return status;
}
QDF_STATUS dsc_psoc_trans_start(struct dsc_psoc *psoc, const char *desc)
{
QDF_STATUS status;
dsc_enter_str(desc);
status = __dsc_psoc_trans_start(psoc, desc);
dsc_exit_status(status);
return status;
}
static QDF_STATUS
__dsc_psoc_trans_start_wait(struct dsc_psoc *psoc, const char *desc)
{
QDF_STATUS status;
struct dsc_tran tran = { 0 };
if (!dsc_assert(psoc))
return QDF_STATUS_E_INVAL;
if (!dsc_assert(desc))
return QDF_STATUS_E_INVAL;
__dsc_driver_lock(psoc);
/* try to start without waiting */
status = __dsc_psoc_trans_start_nolock(psoc, desc);
if (QDF_IS_STATUS_SUCCESS(status) || status == QDF_STATUS_E_INVAL)
goto unlock;
status = __dsc_trans_queue(&psoc->trans, &tran, desc);
if (QDF_IS_STATUS_ERROR(status))
goto unlock;
__dsc_driver_unlock(psoc);
return __dsc_tran_wait(&tran);
unlock:
__dsc_driver_unlock(psoc);
return status;
}
QDF_STATUS dsc_psoc_trans_start_wait(struct dsc_psoc *psoc, const char *desc)
{
QDF_STATUS status;
dsc_enter_str(desc);
status = __dsc_psoc_trans_start_wait(psoc, desc);
dsc_exit_status(status);
return status;
}
static void __dsc_psoc_trigger_trans(struct dsc_psoc *psoc)
{
struct dsc_vdev *vdev;
if (__dsc_driver_trans_trigger_checked(psoc->driver))
return;
if (__dsc_trans_trigger(&psoc->trans))
return;
dsc_for_each_psoc_vdev(psoc, vdev)
__dsc_trans_trigger(&vdev->trans);
}
static void __dsc_psoc_trans_stop(struct dsc_psoc *psoc)
{
if (!dsc_assert(psoc))
return;
__dsc_driver_lock(psoc);
__dsc_trans_stop(&psoc->trans);
__dsc_psoc_trigger_trans(psoc);
__dsc_driver_unlock(psoc);
}
void dsc_psoc_trans_stop(struct dsc_psoc *psoc)
{
dsc_enter();
__dsc_psoc_trans_stop(psoc);
dsc_exit();
}
static void __dsc_psoc_assert_trans_protected(struct dsc_psoc *psoc)
{
if (!dsc_assert(psoc))
return;
__dsc_driver_lock(psoc);
dsc_assert(__dsc_trans_active(&psoc->trans) ||
__dsc_trans_active(&psoc->driver->trans));
__dsc_driver_unlock(psoc);
}
void dsc_psoc_assert_trans_protected(struct dsc_psoc *psoc)
{
dsc_enter();
__dsc_psoc_assert_trans_protected(psoc);
dsc_exit();
}
bool __dsc_psoc_trans_trigger_checked(struct dsc_psoc *psoc)
{
if (qdf_list_empty(&psoc->trans.queue))
return false;
/* handled, but don't trigger; we need to wait for more children */
if (!__dsc_psoc_can_trigger(psoc))
return true;
return __dsc_trans_trigger(&psoc->trans);
}
static QDF_STATUS __dsc_psoc_op_start(struct dsc_psoc *psoc, const char *func)
{
QDF_STATUS status;
if (!dsc_assert(psoc))
return QDF_STATUS_E_INVAL;
if (!dsc_assert(func))
return QDF_STATUS_E_INVAL;
__dsc_driver_lock(psoc);
status = __dsc_psoc_can_op(psoc);
if (QDF_IS_STATUS_ERROR(status))
goto unlock;
status = __dsc_ops_insert(&psoc->ops, func);
unlock:
__dsc_driver_unlock(psoc);
return status;
}
QDF_STATUS _dsc_psoc_op_start(struct dsc_psoc *psoc, const char *func)
{
QDF_STATUS status;
status = __dsc_psoc_op_start(psoc, func);
return status;
}
static void __dsc_psoc_op_stop(struct dsc_psoc *psoc, const char *func)
{
if (!dsc_assert(psoc))
return;
if (!dsc_assert(func))
return;
__dsc_driver_lock(psoc);
if (__dsc_ops_remove(&psoc->ops, func))
qdf_event_set(&psoc->ops.event);
__dsc_driver_unlock(psoc);
}
void _dsc_psoc_op_stop(struct dsc_psoc *psoc, const char *func)
{
__dsc_psoc_op_stop(psoc, func);
}
static void __dsc_psoc_wait_for_ops(struct dsc_psoc *psoc)
{
struct dsc_vdev *vdev;
bool wait;
if (!dsc_assert(psoc))
return;
__dsc_driver_lock(psoc);
wait = psoc->ops.count > 0;
if (wait)
qdf_event_reset(&psoc->ops.event);
__dsc_driver_unlock(psoc);
if (wait)
qdf_wait_single_event(&psoc->ops.event, 0);
/* wait for down-tree ops to complete as well */
dsc_for_each_psoc_vdev(psoc, vdev)
dsc_vdev_wait_for_ops(vdev);
}
void dsc_psoc_wait_for_ops(struct dsc_psoc *psoc)
{
dsc_enter();
__dsc_psoc_wait_for_ops(psoc);
dsc_exit();
}