/**************************************************************************** * * Copyright (c) 2015 PX4 Development Team. All rights reserved. * Pavel Kirienko * David Sidrane * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name PX4 nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include #include #include /* * General purpose wrapper around os's mutual exclusion * mechanism. * * It supports the * * Note the naming of thier_mutex_ implies that the underlying * mutex is owned by class using the Lock. * and this wrapper provides service to initialize and de initialize * the mutex. */ class Lock { pthread_mutex_t &thier_mutex_; public: Lock(pthread_mutex_t &m) : thier_mutex_(m) { (void)pthread_mutex_lock(&m); } ~Lock() { (void)pthread_mutex_unlock(&thier_mutex_); } static int init(pthread_mutex_t &thier_mutex_) { return pthread_mutex_init(&thier_mutex_, NULL); } static int deinit(pthread_mutex_t &thier_mutex_) { return pthread_mutex_destroy(&thier_mutex_); } }; /** * Generic queue based on the linked list class defined in libuavcan. * This class does not use heap memory. */ template class Queue { struct Item : public uavcan::LinkedListNode { T payload; template Item(Args... args) : payload(args...) { } }; uavcan::LimitedPoolAllocator allocator_; uavcan::LinkedListRoot list_; public: Queue(uavcan::IPoolAllocator &arg_allocator, std::size_t block_allocation_quota) : allocator_(arg_allocator, block_allocation_quota) { uavcan::IsDynamicallyAllocatable::check(); } ~Queue() { while (!isEmpty()) { pop(); } } bool isEmpty() const { return list_.isEmpty(); } /** * Creates one item in-place at the end of the list. * Returns true if the item was appended successfully, false if there's not enough memory. * Complexity is O(N) where N is queue length. */ template bool tryEmplace(Args... args) { // Allocating memory void *const ptr = allocator_.allocate(sizeof(Item)); if (ptr == nullptr) { return false; } // Constructing the new item Item *const item = new (ptr) Item(args...); assert(item != nullptr); // Inserting the new item at the end of the list Item *p = list_.get(); if (p == nullptr) { list_.insert(item); } else { while (p->getNextListNode() != nullptr) { p = p->getNextListNode(); } assert(p->getNextListNode() == nullptr); p->setNextListNode(item); assert(p->getNextListNode()->getNextListNode() == nullptr); } return true; } /** * Accesses the first element. * Nullptr will be returned if the queue is empty. * Complexity is O(1). */ T *peek() { return isEmpty() ? nullptr : &list_.get()->payload; } const T *peek() const { return isEmpty() ? nullptr : &list_.get()->payload; } /** * Removes the first element. * If the queue is empty, nothing will be done and assertion failure will be triggered. * Complexity is O(1). */ void pop() { Item *const item = list_.get(); assert(item != nullptr); if (item != nullptr) { list_.remove(item); item->~Item(); allocator_.deallocate(item); } } }; /** * Objects of this class are owned by the sub-node thread. * This class does not use heap memory. */ class VirtualCanIface : public uavcan::ICanIface, uavcan::Noncopyable { /** * This class re-defines uavcan::RxCanFrame with flags. * Simple inheritance or composition won't work here, because the 40 byte limit will be exceeded, * rendering this class unusable with Queue<>. */ struct RxItem: public uavcan::CanFrame { const uavcan::MonotonicTime ts_mono; const uavcan::UtcTime ts_utc; const uavcan::CanIOFlags flags; const uint8_t iface_index; RxItem(const uavcan::CanRxFrame &arg_frame, uavcan::CanIOFlags arg_flags) : uavcan::CanFrame(arg_frame), ts_mono(arg_frame.ts_mono), ts_utc(arg_frame.ts_utc), flags(arg_flags), iface_index(arg_frame.iface_index) { // Making sure it will fit into a pool block with a pointer prefix static_assert(sizeof(RxItem) <= (uavcan::MemPoolBlockSize - 8), "Bad coder, no coffee"); } }; pthread_mutex_t &common_driver_mutex_; uavcan::CanTxQueue prioritized_tx_queue_; Queue rx_queue_; int16_t send(const uavcan::CanFrame &frame, uavcan::MonotonicTime tx_deadline, uavcan::CanIOFlags flags) override { Lock lock(common_driver_mutex_); prioritized_tx_queue_.push(frame, tx_deadline, uavcan::CanTxQueue::Volatile, flags); return 1; } int16_t receive(uavcan::CanFrame &out_frame, uavcan::MonotonicTime &out_ts_monotonic, uavcan::UtcTime &out_ts_utc, uavcan::CanIOFlags &out_flags) override { Lock lock(common_driver_mutex_); if (rx_queue_.isEmpty()) { return 0; } const auto item = *rx_queue_.peek(); rx_queue_.pop(); out_frame = item; out_ts_monotonic = item.ts_mono; out_ts_utc = item.ts_utc; out_flags = item.flags; return 1; } int16_t configureFilters(const uavcan::CanFilterConfig *, std::uint16_t) override { return -uavcan::ErrDriver; } uint16_t getNumFilters() const override { return 0; } uint64_t getErrorCount() const override { return 0; } public: VirtualCanIface(uavcan::IPoolAllocator &allocator, uavcan::ISystemClock &clock, pthread_mutex_t &arg_mutex, unsigned quota_per_queue) : common_driver_mutex_(arg_mutex), prioritized_tx_queue_(allocator, clock, quota_per_queue), rx_queue_(allocator, quota_per_queue) { } ~VirtualCanIface() { } /** * Note that RX queue overwrites oldest items when overflowed. * Call this from the main thread only. * No additional locking is required. */ void addRxFrame(const uavcan::CanRxFrame &frame, uavcan::CanIOFlags flags) { Lock lock(common_driver_mutex_); if (!rx_queue_.tryEmplace(frame, flags) && !rx_queue_.isEmpty()) { rx_queue_.pop(); (void)rx_queue_.tryEmplace(frame, flags); } } /** * Call this from the main thread only. * No additional locking is required. */ void flushTxQueueTo(uavcan::INode &main_node, std::uint8_t iface_index) { Lock lock(common_driver_mutex_); const std::uint8_t iface_mask = static_cast(1U << iface_index); while (auto e = prioritized_tx_queue_.peek()) { UAVCAN_TRACE("VirtualCanIface", "TX injection [iface=0x%02x]: %s", unsigned(iface_mask), e->toString().c_str()); const int res = main_node.injectTxFrame(e->frame, e->deadline, iface_mask, uavcan::CanTxQueue::Qos(e->qos), e->flags); prioritized_tx_queue_.remove(e); if (res <= 0) { break; } } } /** * Call this from the sub-node thread only. * No additional locking is required. */ bool hasDataInRxQueue() { Lock lock(common_driver_mutex_); return !rx_queue_.isEmpty(); } }; /** * This interface defines one method that will be called by the main node thread periodically in order to * transfer contents of TX queue of the sub-node into the TX queue of the main node. */ class ITxQueueInjector { public: virtual ~ITxQueueInjector() { } /** * Flush contents of TX queues into the main node. * @param main_node Reference to the main node. */ virtual void injectTxFramesInto(uavcan::INode &main_node) = 0; }; /** * Objects of this class are owned by the sub-node thread. * This class does not use heap memory. */ class VirtualCanDriver : public uavcan::ICanDriver, public uavcan::IRxFrameListener, public ITxQueueInjector, uavcan::Noncopyable { class Event { FAR px4_sem_t sem; public: int init() { int rv = px4_sem_init(&sem, 0, 0); if (rv == 0) { px4_sem_setprotocol(&sem, SEM_PRIO_NONE); } return rv; } int deinit() { return px4_sem_destroy(&sem); } Event() { } ~Event() { } /** */ void waitFor(uavcan::MonotonicDuration duration) { static const int NsPerSec = 1000000000; if (duration.isPositive()) { auto abstime = ::timespec(); if (clock_gettime(CLOCK_REALTIME, &abstime) >= 0) { abstime.tv_nsec += duration.toUSec() * 1000; if (abstime.tv_nsec >= NsPerSec) { abstime.tv_sec++; abstime.tv_nsec -= NsPerSec; } (void)px4_sem_timedwait(&sem, &abstime); } } } void signal() { int count; int rv = px4_sem_getvalue(&sem, &count); if (rv == 0 && count <= 0) { px4_sem_post(&sem); } } }; Event event_; ///< Used to unblock the select() call when IO happens. pthread_mutex_t driver_mutex_; ///< Shared across all ifaces uavcan::LazyConstructor ifaces_[uavcan::MaxCanIfaces]; const unsigned num_ifaces_; uavcan::ISystemClock &clock_; uavcan::ICanIface *getIface(uint8_t iface_index) override { return (iface_index < num_ifaces_) ? ifaces_[iface_index].operator VirtualCanIface * () : nullptr; } uint8_t getNumIfaces() const override { return num_ifaces_; } /** * This and other methods of ICanDriver will be invoked by the sub-node thread. */ int16_t select(uavcan::CanSelectMasks &inout_masks, const uavcan::CanFrame * (&)[uavcan::MaxCanIfaces], uavcan::MonotonicTime blocking_deadline) override { bool need_block = (inout_masks.write == 0); // Write queue is infinite for (unsigned i = 0; need_block && (i < num_ifaces_); i++) { const bool need_read = inout_masks.read & (1U << i); if (need_read && ifaces_[i]->hasDataInRxQueue()) { need_block = false; } } if (need_block) { event_.waitFor(blocking_deadline - clock_.getMonotonic()); } inout_masks = uavcan::CanSelectMasks(); for (unsigned i = 0; i < num_ifaces_; i++) { const std::uint8_t iface_mask = 1U << i; inout_masks.write |= iface_mask; // Always ready to write if (ifaces_[i]->hasDataInRxQueue()) { inout_masks.read |= iface_mask; } } return num_ifaces_; // We're always ready to write, hence > 0. } /** * This handler will be invoked by the main node thread. */ void handleRxFrame(const uavcan::CanRxFrame &frame, uavcan::CanIOFlags flags) override { UAVCAN_TRACE("VirtualCanDriver", "RX [flags=%u]: %s", unsigned(flags), frame.toString().c_str()); if (frame.iface_index < num_ifaces_) { ifaces_[frame.iface_index]->addRxFrame(frame, flags); event_.signal(); } } /** * This method will be invoked by the main node thread. */ void injectTxFramesInto(uavcan::INode &main_node) override { for (unsigned i = 0; i < num_ifaces_; i++) { ifaces_[i]->flushTxQueueTo(main_node, i); } event_.signal(); } public: VirtualCanDriver(unsigned arg_num_ifaces, uavcan::ISystemClock &system_clock, uavcan::IPoolAllocator &allocator, unsigned virtual_iface_block_allocation_quota) : num_ifaces_(arg_num_ifaces), clock_(system_clock) { Lock::init(driver_mutex_); event_.init(); assert(num_ifaces_ > 0 && num_ifaces_ <= uavcan::MaxCanIfaces); const unsigned quota_per_queue = virtual_iface_block_allocation_quota; // 2x overcommit for (unsigned i = 0; i < num_ifaces_; i++) { ifaces_[i].construct(allocator, clock_, driver_mutex_, quota_per_queue); } } ~VirtualCanDriver() { Lock::deinit(driver_mutex_); event_.deinit(); } };