diff --git a/core/hdd/inc/wlan_hdd_dsc.h b/core/hdd/inc/wlan_hdd_dsc.h index 7b496db90bc4..3a4f96a932ee 100644 --- a/core/hdd/inc/wlan_hdd_dsc.h +++ b/core/hdd/inc/wlan_hdd_dsc.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 The Linux Foundation. All rights reserved. + * 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 @@ -19,6 +19,7 @@ #ifndef __WLAN_HDD_DSC_H__ #define __WLAN_HDD_DSC_H__ +#include "qdf_types.h" #include "wlan_dsc.h" #include "wlan_hdd_main.h" @@ -45,56 +46,147 @@ void hdd_dsc_deinit(void); struct dsc_psoc *hdd_dsc_psoc_from_wiphy(struct wiphy *wiphy); /** - * hdd_vdev_ops_register() - register a dsc_vdev to accept new operations - * @net_dev: the net_device which will be used for lookup during op start - * @dsc_vdev: the dsc_vdev to use to protect operations on @net_dev + * struct hdd_vdev_sync - opaque synchronization handle for a vdev + */ +struct hdd_vdev_sync; + +/** + * hdd_vdev_sync_create() - create a vdev synchronization context + * @wiphy: parent wiphy to the vdev + * @out_vdev_sync: out parameter for the new synchronization context + * + * Return: Errno + */ +qdf_must_check int +hdd_vdev_sync_create(struct wiphy *wiphy, struct hdd_vdev_sync **out_vdev_sync); + +/** + * hdd_vdev_sync_create_with_trans() - create a vdev synchronization context + * @wiphy: parent wiphy to the vdev + * @out_vdev_sync: out parameter for the new synchronization context + * + * For protecting the net_device creation process. + * + * Return: Errno + */ +#define hdd_vdev_sync_create_with_trans(wiphy, out_vdev_sync) \ + __hdd_vdev_sync_create_with_trans(wiphy, out_vdev_sync, __func__) + +qdf_must_check int +__hdd_vdev_sync_create_with_trans(struct wiphy *wiphy, + struct hdd_vdev_sync **out_vdev_sync, + const char *desc); + +/** + * hdd_vdev_sync_destroy() - destroy a vdev synchronization context + * @vdev_sync: the context to destroy + * + * Return: none + */ +void hdd_vdev_sync_destroy(struct hdd_vdev_sync *vdev_sync); + +/** + * hdd_vdev_sync_register() - register a vdev for operations/transitions + * @net_dev: the net_device to use as the operation/transition lookup key + * @vdev_sync: the vdev synchronization context to register + * + * Return: none + */ +void hdd_vdev_sync_register(struct net_device *net_dev, + struct hdd_vdev_sync *vdev_sync); + +/** + * hdd_vdev_sync_unregister() - unregister a vdev for operations/transitions + * @net_dev: the net_device originally used to register the vdev_sync context + * + * Return: the vdev synchronization context that was registered for @net_dev + */ +struct hdd_vdev_sync *hdd_vdev_sync_unregister(struct net_device *net_dev); + +/** + * hdd_vdev_sync_trans_start() - attempt to start a transition on @net_dev + * @net_dev: the net_device to transition + * @out_vdev_sync: out parameter for the synchronization context registered with + * @net_dev, populated on success + * + * Return: Errno + */ +#define hdd_vdev_sync_trans_start(net_dev, out_vdev_sync) \ + __hdd_vdev_sync_trans_start(net_dev, out_vdev_sync, __func__) + +qdf_must_check int +__hdd_vdev_sync_trans_start(struct net_device *net_dev, + struct hdd_vdev_sync **out_vdev_sync, + const char *desc); + +/** + * hdd_vdev_sync_trans_start_wait() - attempt to start a transition on @net_dev, + * blocking if a conflicting transition is in flight + * @net_dev: the net_device to transition + * @out_vdev_sync: out parameter for the synchronization context registered with + * @net_dev, populated on success + * + * Return: Errno + */ +#define hdd_vdev_sync_trans_start_wait(net_dev, out_vdev_sync) \ + __hdd_vdev_sync_trans_start_wait(net_dev, out_vdev_sync, __func__) + +qdf_must_check int +__hdd_vdev_sync_trans_start_wait(struct net_device *net_dev, + struct hdd_vdev_sync **out_vdev_sync, + const char *desc); + +/** + * hdd_vdev_sync_trans_stop() - stop a transition associated with @vdev_sync + * @vdev_sync: the synchonization context tracking the transition + * + * Return: none + */ +void hdd_vdev_sync_trans_stop(struct hdd_vdev_sync *vdev_sync); + +/** + * hdd_vdev_sync_assert_trans_protected() - assert that @net_dev is currently + * protected by a transition + * @net_dev: the net_device to check + * + * Return: none + */ +void hdd_vdev_sync_assert_trans_protected(struct net_device *net_dev); + +/** + * hdd_vdev_sync_op_start() - attempt to start an operation on @net_dev + * @net_dev: the net_device to operate against + * @out_vdev_sync: out parameter for the synchronization context registered with + * @net_dev, populated on success + * + * Return: Errno + */ +#define hdd_vdev_sync_op_start(net_dev, out_vdev_sync) \ + __hdd_vdev_sync_op_start(net_dev, out_vdev_sync, __func__) + +qdf_must_check int +__hdd_vdev_sync_op_start(struct net_device *net_dev, + struct hdd_vdev_sync **out_vdev_sync, + const char *func); + +/** + * hdd_vdev_sync_op_stop() - stop an operation associated with @vdev_sync + * @vdev_sync: the synchonization context tracking the operation + * + * Return: none + */ +#define hdd_vdev_sync_op_stop(net_dev) \ + __hdd_vdev_sync_op_stop(net_dev, __func__) + +void __hdd_vdev_sync_op_stop(struct hdd_vdev_sync *vdev_sync, + const char *func); + +/** + * hdd_vdev_sync_wait_for_ops() - wait until all @vdev_sync operations complete + * @vdev_sync: the synchonization context tracking the operations * * Return: None */ -void hdd_vdev_ops_register(struct net_device *net_dev, - struct dsc_vdev *dsc_vdev); - -/** - * hdd_vdev_ops_unregister() - unregister a dsc_vdev to reject future operations - * @net_dev: the net_device to use for lookup - * - * Return: None - */ -void hdd_vdev_ops_unregister(struct net_device *net_dev); - -/** - * struct hdd_vdev_op - opaque handle used to identify a specific vdev operation - */ -struct hdd_vdev_op; - -/** - * hdd_vdev_op_start() - attempt to start a vdev-level driver operation - * @net_dev: the net_device to start the operation on - * - * Return: an operation handle on success, NULL on failure - */ -#define hdd_vdev_op_start(net_dev) __hdd_vdev_op_start(net_dev, __func__) - -/** - * hdd_vdev_op_start_with_wdev() - attempt to start a vdev-level driver - * operation using a wireless_dev instance - * @wdev: the wireless_dev to start the operation on - * - * Return: an operation handle on success, NULL on failure - */ -#define hdd_vdev_op_start_with_wdev(wdev) hdd_vdev_op_start((wdev)->netdev) - -struct hdd_vdev_op *__hdd_vdev_op_start(struct net_device *net_dev, - const char *func); - -/** - * hdd_vdev_op_stop() - stop a given vdev-level operation - * @op: an operation handle identifying the operation to stop - * - * Return: None - */ -#define hdd_vdev_op_stop(op) __hdd_vdev_op_stop(op, __func__) - -void __hdd_vdev_op_stop(struct hdd_vdev_op *op, const char *func); +void hdd_vdev_sync_wait_for_ops(struct hdd_vdev_sync *vdev_sync); #endif diff --git a/core/hdd/src/wlan_hdd_dsc.c b/core/hdd/src/wlan_hdd_dsc.c index 6a523abf00fc..f64f2c32351e 100644 --- a/core/hdd/src/wlan_hdd_dsc.c +++ b/core/hdd/src/wlan_hdd_dsc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 The Linux Foundation. All rights reserved. + * 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 @@ -37,130 +37,283 @@ struct dsc_psoc *hdd_dsc_psoc_from_wiphy(struct wiphy *wiphy) } /** - * struct hdd_vdev_kvp - net_device/dsc_vdev key-value-pair for mapping a - * net_device to a dsc_vdev - * @net_dev: the net_device key - * @dsc_vdev: the dsc_vdev value + * struct hdd_vdev_sync - a vdev synchronization context + * @net_dev: the net_device used as a lookup key + * @dsc_vdev: the dsc_vdev used for synchronization + * @in_use: indicates if the context is being used */ -struct hdd_vdev_kvp { +struct hdd_vdev_sync { struct net_device *net_dev; struct dsc_vdev *dsc_vdev; + bool in_use; }; -static struct hdd_vdev_kvp __hdd_vdev_map[CSR_ROAM_SESSION_MAX]; -static qdf_spinlock_t __hdd_vdev_lock; +static struct hdd_vdev_sync __hdd_vdev_sync_arr[CSR_ROAM_SESSION_MAX]; +static qdf_spinlock_t __hdd_vdev_sync_lock; -#define hdd_vdev_map_lock() qdf_spin_lock_bh(&__hdd_vdev_lock) -#define hdd_vdev_map_unlock() qdf_spin_unlock_bh(&__hdd_vdev_lock) -#define hdd_vdev_map_lock_assert() QDF_BUG(qdf_spin_is_locked(&__hdd_vdev_lock)) +#define hdd_vdev_sync_lock_create() qdf_spinlock_create(&__hdd_vdev_sync_lock) +#define hdd_vdev_sync_lock_destroy() qdf_spinlock_destroy(&__hdd_vdev_sync_lock) +#define hdd_vdev_sync_lock() qdf_spin_lock_bh(&__hdd_vdev_sync_lock) +#define hdd_vdev_sync_unlock() qdf_spin_unlock_bh(&__hdd_vdev_sync_lock) +#define hdd_vdev_sync_lock_assert() \ + QDF_BUG(qdf_spin_is_locked(&__hdd_vdev_sync_lock)) -static struct hdd_vdev_kvp *hdd_vdev_kvp_get(struct net_device *net_dev) +static struct hdd_vdev_sync *hdd_vdev_sync_lookup(struct net_device *net_dev) { int i; - hdd_vdev_map_lock_assert(); + hdd_vdev_sync_lock_assert(); - for (i = 0; i < QDF_ARRAY_SIZE(__hdd_vdev_map); i++) { - struct hdd_vdev_kvp *kvp = __hdd_vdev_map + i; + for (i = 0; i < QDF_ARRAY_SIZE(__hdd_vdev_sync_arr); i++) { + struct hdd_vdev_sync *vdev_sync = __hdd_vdev_sync_arr + i; - if (kvp->net_dev == net_dev) - return kvp; + if (vdev_sync->net_dev == net_dev) + return vdev_sync; } return NULL; } -void hdd_vdev_ops_register(struct net_device *net_dev, - struct dsc_vdev *dsc_vdev) +static struct hdd_vdev_sync *hdd_vdev_sync_get(void) { - struct hdd_vdev_kvp *kvp; + int i; - QDF_BUG(net_dev); - if (!net_dev) - return; + hdd_vdev_sync_lock_assert(); - QDF_BUG(dsc_vdev); - if (!dsc_vdev) - return; + for (i = 0; i < QDF_ARRAY_SIZE(__hdd_vdev_sync_arr); i++) { + struct hdd_vdev_sync *vdev_sync = __hdd_vdev_sync_arr + i; - hdd_vdev_map_lock(); - - kvp = hdd_vdev_kvp_get(NULL); - QDF_BUG(kvp); - if (kvp) { - kvp->net_dev = net_dev; - kvp->dsc_vdev = dsc_vdev; + if (!vdev_sync->in_use) { + vdev_sync->in_use = true; + return vdev_sync; + } } - hdd_vdev_map_unlock(); + return NULL; } -void hdd_vdev_ops_unregister(struct net_device *net_dev) +static void hdd_vdev_sync_put(struct hdd_vdev_sync *vdev_sync) { - struct hdd_vdev_kvp *kvp; + hdd_vdev_sync_lock_assert(); - QDF_BUG(net_dev); - if (!net_dev) - return; - - hdd_vdev_map_lock(); - - kvp = hdd_vdev_kvp_get(net_dev); - QDF_BUG(kvp); - if (kvp) { - kvp->net_dev = NULL; - kvp->dsc_vdev = NULL; - } - - hdd_vdev_map_unlock(); -} - -static struct hdd_vdev_op *hdd_vdev_to_op(struct dsc_vdev *dsc_vdev) -{ - return (struct hdd_vdev_op *)dsc_vdev; -} - -static struct dsc_vdev *hdd_op_to_vdev(struct hdd_vdev_op *op) -{ - return (struct dsc_vdev *)op; -} - -struct hdd_vdev_op *__hdd_vdev_op_start(struct net_device *net_dev, - const char *func) -{ - QDF_STATUS status = QDF_STATUS_E_NULL_VALUE; - struct hdd_vdev_kvp *kvp; - - QDF_BUG(net_dev); - if (!net_dev) - return NULL; - - hdd_vdev_map_lock(); - - kvp = hdd_vdev_kvp_get(net_dev); - if (kvp) - status = _dsc_vdev_op_start(kvp->dsc_vdev, func); - - hdd_vdev_map_unlock(); - - if (QDF_IS_STATUS_ERROR(status)) - return NULL; - - return hdd_vdev_to_op(kvp->dsc_vdev); -} - -void __hdd_vdev_op_stop(struct hdd_vdev_op *op, const char *func) -{ - _dsc_vdev_op_stop(hdd_op_to_vdev(op), func); + qdf_mem_zero(vdev_sync, sizeof(*vdev_sync)); } void hdd_dsc_init(void) { - qdf_spinlock_create(&__hdd_vdev_lock); + hdd_vdev_sync_lock_create(); } void hdd_dsc_deinit(void) { - qdf_spinlock_destroy(&__hdd_vdev_lock); + hdd_vdev_sync_lock_destroy(); +} + +int hdd_vdev_sync_create(struct wiphy *wiphy, + struct hdd_vdev_sync **out_vdev_sync) +{ + QDF_STATUS status; + struct dsc_psoc *dsc_psoc; + struct hdd_vdev_sync *vdev_sync; + + QDF_BUG(wiphy); + if (!wiphy) + return -EINVAL; + + QDF_BUG(out_vdev_sync); + if (!out_vdev_sync) + return -EINVAL; + + dsc_psoc = hdd_dsc_psoc_from_wiphy(wiphy); + if (!dsc_psoc) + return -EINVAL; + + hdd_vdev_sync_lock(); + vdev_sync = hdd_vdev_sync_get(); + hdd_vdev_sync_unlock(); + if (!vdev_sync) + return -ENOMEM; + + status = dsc_vdev_create(dsc_psoc, &vdev_sync->dsc_vdev); + if (QDF_IS_STATUS_ERROR(status)) + goto sync_put; + + *out_vdev_sync = vdev_sync; + + return 0; + +sync_put: + hdd_vdev_sync_lock(); + hdd_vdev_sync_put(vdev_sync); + hdd_vdev_sync_unlock(); + + return qdf_status_to_os_return(status); +} + +int __hdd_vdev_sync_create_with_trans(struct wiphy *wiphy, + struct hdd_vdev_sync **out_vdev_sync, + const char *desc) +{ + struct hdd_vdev_sync *vdev_sync; + QDF_STATUS status; + int errno; + + errno = hdd_vdev_sync_create(wiphy, &vdev_sync); + if (errno) + return errno; + + status = dsc_vdev_trans_start(vdev_sync->dsc_vdev, desc); + if (QDF_IS_STATUS_ERROR(status)) + goto sync_destroy; + + *out_vdev_sync = vdev_sync; + + return 0; + +sync_destroy: + hdd_vdev_sync_destroy(vdev_sync); + + return qdf_status_to_os_return(status); +} + +void hdd_vdev_sync_destroy(struct hdd_vdev_sync *vdev_sync) +{ + QDF_BUG(vdev_sync); + if (!vdev_sync) + return; + + dsc_vdev_destroy(&vdev_sync->dsc_vdev); + + hdd_vdev_sync_lock(); + hdd_vdev_sync_put(vdev_sync); + hdd_vdev_sync_unlock(); +} + +void hdd_vdev_sync_register(struct net_device *net_dev, + struct hdd_vdev_sync *vdev_sync) +{ + QDF_BUG(net_dev); + QDF_BUG(vdev_sync); + if (!vdev_sync) + return; + + hdd_vdev_sync_lock(); + vdev_sync->net_dev = net_dev; + hdd_vdev_sync_unlock(); +} + +struct hdd_vdev_sync *hdd_vdev_sync_unregister(struct net_device *net_dev) +{ + struct hdd_vdev_sync *vdev_sync; + + QDF_BUG(net_dev); + if (!net_dev) + return NULL; + + hdd_vdev_sync_lock(); + vdev_sync = hdd_vdev_sync_lookup(net_dev); + vdev_sync->net_dev = NULL; + hdd_vdev_sync_unlock(); + + return vdev_sync; +} + +typedef QDF_STATUS (*vdev_start_func)(struct dsc_vdev *, const char *); + +static int __hdd_vdev_sync_start_callback(struct net_device *net_dev, + struct hdd_vdev_sync **out_vdev_sync, + const char *desc, + vdev_start_func vdev_start_cb) +{ + QDF_STATUS status; + struct hdd_vdev_sync *vdev_sync; + + hdd_vdev_sync_lock_assert(); + + *out_vdev_sync = NULL; + + vdev_sync = hdd_vdev_sync_lookup(net_dev); + if (!vdev_sync) + return -EAGAIN; + + status = vdev_start_cb(vdev_sync->dsc_vdev, desc); + if (QDF_IS_STATUS_ERROR(status)) + return qdf_status_to_os_return(status); + + *out_vdev_sync = vdev_sync; + + return 0; +} + +int __hdd_vdev_sync_trans_start(struct net_device *net_dev, + struct hdd_vdev_sync **out_vdev_sync, + const char *desc) +{ + int errno; + + hdd_vdev_sync_lock(); + errno = __hdd_vdev_sync_start_callback(net_dev, out_vdev_sync, desc, + dsc_vdev_trans_start); + hdd_vdev_sync_unlock(); + + return errno; +} + +int __hdd_vdev_sync_trans_start_wait(struct net_device *net_dev, + struct hdd_vdev_sync **out_vdev_sync, + const char *desc) +{ + int errno; + + hdd_vdev_sync_lock(); + errno = __hdd_vdev_sync_start_callback(net_dev, out_vdev_sync, desc, + dsc_vdev_trans_start_wait); + hdd_vdev_sync_unlock(); + + return errno; +} + +void hdd_vdev_sync_trans_stop(struct hdd_vdev_sync *vdev_sync) +{ + dsc_vdev_trans_stop(vdev_sync->dsc_vdev); +} + +void hdd_vdev_sync_assert_trans_protected(struct net_device *net_dev) +{ + struct hdd_vdev_sync *vdev_sync; + + hdd_vdev_sync_lock(); + + vdev_sync = hdd_vdev_sync_lookup(net_dev); + QDF_BUG(vdev_sync); + if (vdev_sync) + dsc_vdev_assert_trans_protected(vdev_sync->dsc_vdev); + + hdd_vdev_sync_unlock(); +} + +int __hdd_vdev_sync_op_start(struct net_device *net_dev, + struct hdd_vdev_sync **out_vdev_sync, + const char *func) +{ + int errno; + + hdd_vdev_sync_lock(); + errno = __hdd_vdev_sync_start_callback(net_dev, out_vdev_sync, func, + _dsc_vdev_op_start); + hdd_vdev_sync_unlock(); + + return errno; +} + +void __hdd_vdev_sync_op_stop(struct hdd_vdev_sync *vdev_sync, + const char *func) +{ + _dsc_vdev_op_stop(vdev_sync->dsc_vdev, func); +} + +void hdd_vdev_sync_wait_for_ops(struct hdd_vdev_sync *vdev_sync) +{ + dsc_vdev_wait_for_ops(vdev_sync->dsc_vdev); }