From 5e919958d21fdc281c3e381b6dab0c4dcd628de7 Mon Sep 17 00:00:00 2001 From: Dustin Brown Date: Tue, 5 Mar 2019 11:12:13 -0800 Subject: [PATCH] qcacmn: Add qdf_periodic_work Add a new deferred work type, qdf_periodic_work, which executes a callback periodically until stopped. Change-Id: Iedf5486474fccadb35e146e6d289c27a7c1002d3 CRs-Fixed: 2410005 --- qdf/inc/qdf_periodic_work.h | 105 ++++++++++++++++++++++++++++ qdf/linux/src/i_qdf_periodic_work.h | 27 +++++++ qdf/linux/src/qdf_periodic_work.c | 95 +++++++++++++++++++++++++ qdf/test/qdf_periodic_work_test.c | 102 +++++++++++++++++++++++++++ qdf/test/qdf_periodic_work_test.h | 37 ++++++++++ 5 files changed, 366 insertions(+) create mode 100644 qdf/inc/qdf_periodic_work.h create mode 100644 qdf/linux/src/i_qdf_periodic_work.h create mode 100644 qdf/linux/src/qdf_periodic_work.c create mode 100644 qdf/test/qdf_periodic_work_test.c create mode 100644 qdf/test/qdf_periodic_work_test.h diff --git a/qdf/inc/qdf_periodic_work.h b/qdf/inc/qdf_periodic_work.h new file mode 100644 index 000000000000..905e13d03e15 --- /dev/null +++ b/qdf/inc/qdf_periodic_work.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 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. + */ + +/** + * DOC: qdf_periodic_work.h + * A simple, periodic work type for repeatedly executing a callback with a + * certain frequency. + */ + +#ifndef __QDF_PERIODIC_WORK_H +#define __QDF_PERIODIC_WORK_H + +#include "i_qdf_periodic_work.h" +#include "qdf_status.h" +#include "qdf_types.h" + +typedef void (*qdf_periodic_work_cb)(void *context); + +/** + * struct qdf_periodic_work - a defered work type which executes a callback + * periodically until stopped + * @dwork: OS-specific delayed work + * @callback: the callback to be executed periodically + * @context: the context to pass to the callback + * @msec: the delay between executions in milliseconds + */ +struct qdf_periodic_work { + struct __qdf_opaque_delayed_work dwork; + qdf_periodic_work_cb callback; + void *context; + uint32_t msec; +}; + +/** + * qdf_periodic_work_create() - initialized a periodic work @pwork + * @pwork: the periodic work to initialize + * @callback: the callback to be executed periodically + * @context: the context to pass to the callback + * + * Return: QDF_STATUS + */ +qdf_must_check QDF_STATUS +qdf_periodic_work_create(struct qdf_periodic_work *pwork, + qdf_periodic_work_cb callback, void *context); + +/** + * qdf_periodic_work_destroy() - deinitialize a periodic work @pwork + * @pwork: the periodic work to initialize + * + * Return: None + */ +void qdf_periodic_work_destroy(struct qdf_periodic_work *pwork); + +/** + * qdf_periodic_work_start() - begin periodic execution of @pwork callback + * @pwork: the periodic work to start + * @msec: the delay between executions in milliseconds + * + * Return: true if started successfully + */ +bool qdf_periodic_work_start(struct qdf_periodic_work *pwork, uint32_t msec); + +/** + * qdf_periodic_work_stop_async() - Asynchronously stop execution of @pwork + * @pwork: the periodic work to stop + * + * When this returns, @pwork is guaranteed to not be queued, *but* its callback + * may still be executing. + * + * This is safe to call from the @pwork callback. + * + * Return: true if @pwork was previously started + */ +bool qdf_periodic_work_stop_async(struct qdf_periodic_work *pwork); + +/** + * qdf_periodic_work_stop_sync() - Synchronously stop execution of @pwork + * @pwork: the periodic work to stop + * + * When this returns, @pwork is guaranteed to not be queued, and its callback + * not executing. + * + * This will deadlock if called from the @pwork callback. + * + * Return: true if @pwork was previously started + */ +bool qdf_periodic_work_stop_sync(struct qdf_periodic_work *pwork); + +#endif /* __QDF_PERIODIC_WORK_H */ + diff --git a/qdf/linux/src/i_qdf_periodic_work.h b/qdf/linux/src/i_qdf_periodic_work.h new file mode 100644 index 000000000000..40c9347ebf8c --- /dev/null +++ b/qdf/linux/src/i_qdf_periodic_work.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + */ + +#ifndef __I_QDF_PERIODIC_WORK_H +#define __I_QDF_PERIODIC_WORK_H + +#include "linux/workqueue.h" + +#define __qdf_opaque_delayed_work delayed_work + +#endif /* __I_QDF_PERIODIC_WORK_H */ + diff --git a/qdf/linux/src/qdf_periodic_work.c b/qdf/linux/src/qdf_periodic_work.c new file mode 100644 index 000000000000..61e1c59f2730 --- /dev/null +++ b/qdf/linux/src/qdf_periodic_work.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 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_periodic_work.h" +#include "qdf_status.h" +#include "qdf_trace.h" +#include "qdf_types.h" + +static void __qdf_periodic_work_handler(struct work_struct *work) +{ + struct qdf_periodic_work *pwork = + container_of(work, struct qdf_periodic_work, dwork.work); + uint32_t msec; + + pwork->callback(pwork->context); + + /* this is intentionally racy; see qdf_periodic_work_stop_sync() */ + msec = pwork->msec; + if (msec) + schedule_delayed_work(&pwork->dwork, msecs_to_jiffies(msec)); +} + +QDF_STATUS qdf_periodic_work_create(struct qdf_periodic_work *pwork, + qdf_periodic_work_cb callback, + void *context) +{ + QDF_BUG(pwork); + QDF_BUG(callback); + if (!pwork || !callback) + return QDF_STATUS_E_INVAL; + + INIT_DELAYED_WORK(&pwork->dwork, __qdf_periodic_work_handler); + pwork->callback = callback; + pwork->context = context; + pwork->msec = 0; + + return QDF_STATUS_SUCCESS; +} + +void qdf_periodic_work_destroy(struct qdf_periodic_work *pwork) +{ + qdf_periodic_work_stop_sync(pwork); +} + +bool qdf_periodic_work_start(struct qdf_periodic_work *pwork, uint32_t msec) +{ + QDF_BUG(msec); + if (!msec) + return false; + + pwork->msec = msec; + + return schedule_delayed_work(&pwork->dwork, msecs_to_jiffies(msec)); +} + +bool qdf_periodic_work_stop_async(struct qdf_periodic_work *pwork) +{ + bool pending = pwork->msec != 0; + + pwork->msec = 0; + cancel_delayed_work(&pwork->dwork); + + return pending; +} + +bool qdf_periodic_work_stop_sync(struct qdf_periodic_work *pwork) +{ + bool pending = pwork->msec != 0; + + /* To avoid using a lock, signal that the work shouldn't be restarted, + * and cancel_sync in a loop. There is a very small race window, and + * thus the work may ocassionally need to be cancelled more than once. + */ + pwork->msec = 0; + while (cancel_delayed_work_sync(&pwork->dwork)) + ; /* no-op*/ + + return pending; +} + diff --git a/qdf/test/qdf_periodic_work_test.c b/qdf/test/qdf_periodic_work_test.c new file mode 100644 index 000000000000..d3f14554fb2e --- /dev/null +++ b/qdf/test/qdf_periodic_work_test.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 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_periodic_work.h" +#include "qdf_periodic_work_test.h" +#include "qdf_trace.h" + +#define pwork_iterations 2 +#define pwork_delay_ms 1 + +struct qdf_pwork_ut_ctx { + struct qdf_periodic_work pwork; + uint32_t count; +}; + +static void __qdf_pwork_inside_cb(void *context) +{ + struct qdf_pwork_ut_ctx *ut_ctx = context; + + /* stop before incrementing; the main thread is looking at @count */ + if (ut_ctx->count + 1 == pwork_iterations) + qdf_periodic_work_stop_async(&ut_ctx->pwork); + + ut_ctx->count++; +} + +static uint32_t qdf_pwork_stop_inside_cb(void) +{ + struct qdf_pwork_ut_ctx ut_ctx = { .count = 0 }; + QDF_STATUS status; + + status = qdf_periodic_work_create(&ut_ctx.pwork, + __qdf_pwork_inside_cb, &ut_ctx); + QDF_BUG(QDF_IS_STATUS_SUCCESS(status)); + + QDF_BUG(qdf_periodic_work_start(&ut_ctx.pwork, pwork_delay_ms)); + + while (ut_ctx.count < pwork_iterations) + schedule(); + + QDF_BUG(!qdf_periodic_work_stop_sync(&ut_ctx.pwork)); + QDF_BUG(ut_ctx.count == pwork_iterations); + + qdf_periodic_work_destroy(&ut_ctx.pwork); + + return 0; +} + +static void __qdf_pwork_outside_cb(void *context) +{ + struct qdf_pwork_ut_ctx *ut_ctx = context; + + ut_ctx->count++; +} + +static uint32_t qdf_pwork_stop_outside_cb(void) +{ + struct qdf_pwork_ut_ctx ut_ctx = { .count = 0 }; + QDF_STATUS status; + + status = qdf_periodic_work_create(&ut_ctx.pwork, + __qdf_pwork_outside_cb, &ut_ctx); + QDF_BUG(QDF_IS_STATUS_SUCCESS(status)); + + QDF_BUG(qdf_periodic_work_start(&ut_ctx.pwork, pwork_delay_ms)); + + while (ut_ctx.count < pwork_iterations) + schedule(); + + QDF_BUG(qdf_periodic_work_stop_sync(&ut_ctx.pwork)); + QDF_BUG(ut_ctx.count >= pwork_iterations); + + qdf_periodic_work_destroy(&ut_ctx.pwork); + + return 0; +} + +uint32_t qdf_periodic_work_unit_test(void) +{ + uint32_t errors = 0; + + errors += qdf_pwork_stop_inside_cb(); + errors += qdf_pwork_stop_outside_cb(); + + return errors; +} + diff --git a/qdf/test/qdf_periodic_work_test.h b/qdf/test/qdf_periodic_work_test.h new file mode 100644 index 000000000000..8a768614080a --- /dev/null +++ b/qdf/test/qdf_periodic_work_test.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 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. + */ + +#ifndef __QDF_PERIODIC_WORK_TEST +#define __QDF_PERIODIC_WORK_TEST + +#ifdef WLAN_PERIODIC_WORK_TEST +/** + * qdf_periodic_work_unit_test() - run the qdf periodic work unit test suite + * + * Return: number of failed test cases + */ +uint32_t qdf_periodic_work_unit_test(void); +#else +static inline uint32_t qdf_periodic_work_unit_test(void) +{ + return 0; +} +#endif /* WLAN_PERIODIC_WORK_TEST */ + +#endif /* __QDF_PERIODIC_WORK_TEST */ +