From bfa9992c7dc71a9af75458e7dc0c06aad71547d9 Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Mon, 7 May 2018 14:42:02 -0500 Subject: [PATCH] Refactor AsyncOp class Add the OperationList class to simplify managing async operations. Add a callback to the AsyncOp class so completion can be signaled with another mechanism. Finally rework the lock handling so AsyncOp::wait is passed the lock to use rather than taking it in a constructor. --- usb/device/utilities/AsyncOp.cpp | 117 ++++++++++++++++----- usb/device/utilities/AsyncOp.h | 79 +++++++++++--- usb/device/utilities/OperationList.h | 86 +++++++++++++++ usb/device/utilities/OperationListBase.cpp | 87 +++++++++++++++ usb/device/utilities/OperationListBase.h | 96 +++++++++++++++++ 5 files changed, 422 insertions(+), 43 deletions(-) create mode 100644 usb/device/utilities/OperationList.h create mode 100644 usb/device/utilities/OperationListBase.cpp create mode 100644 usb/device/utilities/OperationListBase.h diff --git a/usb/device/utilities/AsyncOp.cpp b/usb/device/utilities/AsyncOp.cpp index 5f6939be2c..aa6557d1d7 100644 --- a/usb/device/utilities/AsyncOp.cpp +++ b/usb/device/utilities/AsyncOp.cpp @@ -16,68 +16,129 @@ #include "AsyncOp.h" #include "mbed_critical.h" +#include "mbed_assert.h" using namespace rtos; -AsyncOp::AsyncOp(Mutex *lock): _list(NULL), _signal(NULL), _signal_lock(lock) +AsyncOp::AsyncOp(): + _list(NULL), _wait(NULL), _aborted(false), _timeout(false) { } -void AsyncOp::start(LinkedListBase *list) +AsyncOp::AsyncOp(mbed::Callback &callback): + _list(NULL), _wait(NULL), _aborted(false), _timeout(false) { - _lock(); - _list = list; - list->enqueue(this); - _unlock(); + _callback = callback; } -void AsyncOp::wait() +AsyncOp::~AsyncOp() { - if (_list == NULL) { - // Event either hasn't start or has already occurred + MBED_ASSERT(_list == NULL); +} + +void AsyncOp::wait(rtos::Mutex *host_mutex, uint32_t milliseconds) +{ + // Optimization so semaphore is only created if necessary + core_util_critical_section_enter(); + bool done = _list == NULL; + core_util_critical_section_exit(); + if (done) { return; } // Construct semaphore to wait on Semaphore sem(0); - // Atomically set the semaphore pointer and - // check for completion - _lock(); - bool done = _list == NULL; - _signal = &sem; - _unlock(); + core_util_critical_section_enter(); + done = _list == NULL; + // Wait is only allowed to be called from one thread + MBED_ASSERT(_wait == NULL); + _wait = &sem; + core_util_critical_section_exit(); - if (!done) { - sem.wait(); + if (done) { + // Operation was signaled before semaphore was set + return; } + + if (sem.wait(milliseconds) == 1) { + // Operation completion signaled semaphore + return; + } + + _host_lock(host_mutex); + _abort(true); + _host_unlock(host_mutex); +} + +void AsyncOp::abort() +{ + // Host lock must be held + + _abort(false); } void AsyncOp::complete() { - _lock(); - _list->remove(this); + core_util_critical_section_enter(); + + mbed::Callback cb = _callback; + _callback = NULL; _list = NULL; - if (_signal != NULL) { - _signal->release(); + if (_wait != NULL) { + _wait->release(); + } + + core_util_critical_section_exit(); + + if (cb) { + cb(); } - _unlock(); } -void AsyncOp::_lock() +bool AsyncOp::timeout() { - if (_signal_lock) { - _signal_lock->lock(); + core_util_critical_section_enter(); + + bool ret = _timeout; + + core_util_critical_section_exit(); + return ret; +} + +void AsyncOp::_abort(bool timeout) +{ + // host lock must be held + + core_util_critical_section_enter(); + OperationListBase *list = _list; + if (list) { + _callback = NULL; + _aborted = true; + _wait = NULL; + _timeout = timeout; + _list = NULL; + } + core_util_critical_section_exit(); + if (list) { + list->remove(this); + } +} + +void AsyncOp::_host_lock(rtos::Mutex *host_mutex) +{ + if (host_mutex) { + host_mutex->lock(); } else { core_util_critical_section_enter(); } } -void AsyncOp::_unlock() +void AsyncOp::_host_unlock(rtos::Mutex *host_mutex) { - if (_signal_lock) { - _signal_lock->unlock(); + if (host_mutex) { + host_mutex->unlock(); } else { core_util_critical_section_exit(); } diff --git a/usb/device/utilities/AsyncOp.h b/usb/device/utilities/AsyncOp.h index be28c28cde..75996b02c3 100644 --- a/usb/device/utilities/AsyncOp.h +++ b/usb/device/utilities/AsyncOp.h @@ -19,44 +19,93 @@ #include "Mutex.h" #include "Semaphore.h" +#include "Callback.h" #include "LinkEntry.h" -#include "LinkedListBase.h" +#include "OperationListBase.h" class AsyncOp: public LinkEntry { public: + + /** + * Construct a new AsyncOp object + */ + AsyncOp(); + /** * Construct a new AsyncOp object * - * @param lock Mutex used to serialize the object or code calling complete - * or NULL if a critical section is used + * @param callback Completion callback */ - AsyncOp(rtos::Mutex *lock); + AsyncOp(mbed::Callback &callback); /** - * Add this operation to the linked list to start it + * Cleanup resources used by this AsyncOp */ - void start(LinkedListBase *list); + virtual ~AsyncOp(); /** * Wait for this asynchronous operation to complete + * + * If the timeout expires then this asynchronous operation is + * aborted and the timeout flag is set. + * + * @note - the host object's lock MUST NOT be held when this call is made */ - void wait(); + void wait(rtos::Mutex *host_mutex, uint32_t milliseconds=osWaitForever); /** - * Mark this asynchronous operation as complete + * Abort this asynchronous operation * - * This wake the thread calling wait() + * This function has no effect if the operation is complete. Otherwise + * the aborted flag is set. + * + * @note - the host object's lock MUST be held when this call is made */ - void complete(); + void abort(); + + /** + * Check if this operation timed out + * + * @return true if this operation timed out, false otherwise + */ + bool timeout(); + + /** + * Check if this operation was aborted + * + * @return true if this operation was aborted, false otherwise + */ + bool aborted(); + +protected: + + /** + * Callback indicating that something changed + * + * @return true if finished false if not + */ + virtual bool process() = 0; + + /** + * Callback indicating that this event finished + */ + virtual void complete(); private: - void _lock(); - void _unlock(); + friend class OperationListBase; - LinkedListBase *_list; - rtos::Semaphore *_signal; - rtos::Mutex *const _signal_lock; + mbed::Callback _callback; + OperationListBase *_list; + rtos::Semaphore *_wait; + bool _aborted; + bool _timeout; + + void _abort(bool timeout); + + static void _host_lock(rtos::Mutex *host_mutex); + + static void _host_unlock(rtos::Mutex *host_mutex); }; #endif diff --git a/usb/device/utilities/OperationList.h b/usb/device/utilities/OperationList.h new file mode 100644 index 0000000000..369ccc9356 --- /dev/null +++ b/usb/device/utilities/OperationList.h @@ -0,0 +1,86 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MBED_OPERATION_LIST_H +#define MBED_OPERATION_LIST_H + +#include "OperationListBase.h" +#include "AsyncOp.h" + +template +class OperationList: public OperationListBase { +public: + + /** + * Create a new empty operation list + */ + OperationList() + { + + } + + /** + * Destroy this object and abort all operations + */ + ~OperationList() + { + + } + /** + * Add an operation to the list + * + * If the list was empty then call process on this + * operation + * + * @param op Operation to add + */ + void add(T *op) + { + OperationListBase::add(op); + } + + /** + * Remove an operation from the list + * + * If this was the head of the list then process the + * next element in the list. + * + * @param op Operation to remove + */ + void remove(T *op) + { + OperationListBase::remove(op); + } + + /** + * Dequeue the head of the list + * + * Remove the head of the operation list without completing it + * or processing the next element. The caller must call the + * AsnycOp::complete() function of the returned object. + * Additionally process() must be called on this object + * if there are still elements in the list. + * + * @return The async op at the head of the list + */ + T *dequeue_raw() + { + return static_cast(OperationListBase::dequeue_raw()); + } + +}; + +#endif diff --git a/usb/device/utilities/OperationListBase.cpp b/usb/device/utilities/OperationListBase.cpp new file mode 100644 index 0000000000..b13128093d --- /dev/null +++ b/usb/device/utilities/OperationListBase.cpp @@ -0,0 +1,87 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "OperationListBase.h" +#include "AsyncOp.h" +#include "mbed_assert.h" + +OperationListBase::OperationListBase() +{ + +} + +OperationListBase::~OperationListBase() +{ + remove_all(); +} + +bool OperationListBase::empty() +{ + return _list.head() == NULL; +} + +void OperationListBase::add(AsyncOp *op) +{ + bool was_empty = _list.head() == NULL; + op->_list = this; + _list.enqueue(op); + if (was_empty) { + process(); + } +} + +void OperationListBase::process() +{ + while (true) { + AsyncOp *op = static_cast(_list.head()); + if (op == NULL) { + // List empty, nothing left to do + break; + } + if (!op->process()) { + // Processing is in progress + break; + } + _list.dequeue(); + op->complete(); + } +} + +void OperationListBase::remove(AsyncOp *op) +{ + bool head = _list.head() == op; + _list.remove(op); + if (head) { + process(); + } +} + +AsyncOp *OperationListBase::dequeue_raw() +{ + return static_cast(_list.dequeue()); +} + +void OperationListBase::remove_all() +{ + while (true) { + AsyncOp *op = static_cast(_list.head()); + if (op == NULL) { + // List empty, nothing left to do + break; + } + op->complete(); + } +} diff --git a/usb/device/utilities/OperationListBase.h b/usb/device/utilities/OperationListBase.h new file mode 100644 index 0000000000..c3f903f794 --- /dev/null +++ b/usb/device/utilities/OperationListBase.h @@ -0,0 +1,96 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018-2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MBED_OPERATION_LIST_BASE_H +#define MBED_OPERATION_LIST_BASE_H + +#include "LinkedListBase.h" +#include "Mutex.h" + +class AsyncOp; + +class OperationListBase { +public: + + /** + * Create a new empty operation list + */ + OperationListBase(); + + /** + * Destroy this object and abort all operations + */ + ~OperationListBase(); + + /** + * Check if the list is empty + * + * @return true if the list is empty false otherwise + */ + bool empty(); + + /** + * Add an operation to the list + * + * If the list was empty then call process on this + * operation + * + * @param op Operation to add + */ + void add(AsyncOp *op); + + /** + * Remove an operation from the list + * + * If this was the head of the list then process the + * next element in the list. + * + * @param op Operation to remove + */ + void remove(AsyncOp *op); + + /** + * Dequeue the head of the list + * + * Remove the head of the operation list without completing it + * or processing the next element. The caller must call the + * AsnycOp::complete() function of the returned object. + * Additionally process() must be called on this object + * if there are still elements in the list. + * + * @return The async op at the head of the list + */ + AsyncOp *dequeue_raw(); + + /** + * Abort all operations + */ + void remove_all(); + + /** + * Process the operation list + * + * This allow the operation at the head of the list to perform processing + */ + void process(); + +private: + friend class AsyncOp; + + LinkedListBase _list; +}; + +#endif