diff --git a/src/openlcb/VirtualMemorySpace.cxxtest b/src/openlcb/VirtualMemorySpace.cxxtest index ba84e044c..6d92ef448 100644 --- a/src/openlcb/VirtualMemorySpace.cxxtest +++ b/src/openlcb/VirtualMemorySpace.cxxtest @@ -49,6 +49,7 @@ CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); CDI_GROUP_ENTRY(first, StringConfigEntry<13>); CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); CDI_GROUP_ENTRY(second, StringConfigEntry<20>); +CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); CDI_GROUP_END(); ExampleMemorySpace cfg(44); @@ -367,4 +368,207 @@ TEST_F(TestSpaceAsyncTest, write_payload_async) EXPECT_EQ(5u, arg2.size()); } +CDI_GROUP(RepeatMemoryDef); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(before, StringConfigEntry<13>); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +using GroupRept = RepeatedGroup; +CDI_GROUP_ENTRY(grp, GroupRept); +CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); +CDI_GROUP_ENTRY(after, StringConfigEntry<20>); +CDI_GROUP_END(); + +RepeatMemoryDef spacerept(22); + +class SpaceWithRepeat : public VirtualMemorySpace +{ +public: + SpaceWithRepeat() + { + arg1.clear(); + arg2.clear(); + register_string(spacerept.grp().entry<0>().first(), + string_reader(&arg1), string_writer(&arg1)); + register_string(spacerept.grp().entry<0>().second(), + string_reader(&arg2), string_writer(&arg2)); + register_string(spacerept.before(), string_reader(&before_), + string_writer(&before_)); + register_string( + spacerept.after(), string_reader(&after_), string_writer(&after_)); + register_repeat(spacerept.grp()); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return [this, ptr]( + unsigned repeat, string *contents, BarrierNotifiable *done) { + lastRepeat_ = repeat; + *contents = *ptr; + done->notify(); + return true; + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_writer(string *ptr) + { + return [this, ptr]( + unsigned repeat, string contents, BarrierNotifiable *done) { + lastRepeat_ = repeat; + *ptr = std::move(contents); + done->notify(); + }; + } + + /// Saves the last repeat variable into this value. + unsigned lastRepeat_; + /// Storage variable for a field. + string before_; + /// Storage variable for a field. + string after_; +}; + +class ReptSpaceTest : public VirtualMemorySpaceTest +{ +protected: + ReptSpaceTest() + { + memCfg_.registry()->insert(node_, SPACE, &s_); + } + + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; + SpaceWithRepeat s_; +}; + +TEST_F(ReptSpaceTest, create) +{ +} + +// Looks for a field that is before the repeated group. +TEST_F(ReptSpaceTest, before) +{ + s_.before_ = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, spacerept.before().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, spacerept.before().offset() - 2, + 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0hel", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); +} + +// Looks for a field in the first repetition of the group. +TEST_F(ReptSpaceTest, first_repeat) +{ + arg1 = "world"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("world", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Start offset within the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0wor", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Start offset _before_ the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset() - 7, 10); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0\0\0\0\0\0wor", 10), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); + + arg2 = "ahoi"; + // Second field, exact match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().second().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("ahoi", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Second field, before match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().second().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0aho", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); +} + +// Looks for a field in the first repetition of the group. +TEST_F(ReptSpaceTest, mid_repeat) +{ + arg1 = "world"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("world", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Start offset within the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0wor", 5), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Start offset in the previous group repeat. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset() - 7, 10); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0\0\0\0\0\0wor", 10), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); + + arg2 = "ahoi"; + // Second field, exact match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().second().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("ahoi", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Second field, before match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().second().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0aho", 5), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); +} + } // namespace openlcb diff --git a/src/openlcb/VirtualMemorySpace.hxx b/src/openlcb/VirtualMemorySpace.hxx index fc6e48f30..5393b3d6a 100644 --- a/src/openlcb/VirtualMemorySpace.hxx +++ b/src/openlcb/VirtualMemorySpace.hxx @@ -37,6 +37,7 @@ #define _OPENLCB_VIRTUALMEMORYSPACE_HXX #include "openlcb/ConfigEntry.hxx" +#include "openlcb/ConfigRepresentation.hxx" #include "openlcb/MemoryConfig.hxx" #include "utils/SortedListMap.hxx" @@ -226,6 +227,25 @@ protected: register_element(entry.offset(), SIZE, read_f, write_f); } + /// Registers a repeated group. Calling this function means that the + /// virtual memory space of the group will be looped onto the first + /// repetition. The correct usage is to register the elements of the first + /// repetition, then register the repetition itself using this call. Nested + /// repetitions are not supported (either the outer or the inner repetition + /// needs to be unrolled and registered for each repeat there). + /// @param group is the repeated group instance. Will take the start + /// offset, repeat count and repeat size from it. + template + void register_repeat(const RepeatedGroup &group) + { + RepeatElement re; + re.start_ = group.offset(); + re.end_ = group.end_offset(); + re.repeatSize_ = Group::size(); + HASSERT(re.repeatSize_ * N == re.end_ - re.start_); + repeats_.insert(std::move(re)); + } + /// Bounds for valid addresses. address_t minAddress_ = 0xFFFFFFFFu; /// Bounds for valid addresses. A read of length 1 from this address @@ -257,7 +277,8 @@ private: ReadFunction readImpl_; }; - struct Comparator + /// STL-compatible comparator function for sorting DataElements. + struct DataComparator { /// Sorting operator by address. bool operator()(const DataElement &a, const DataElement &b) const @@ -269,6 +290,38 @@ private: { return a < b.address_; } + /// Sorting operator by address. + bool operator()(const DataElement &a, unsigned b) const + { + return a.address_ < b; + } + }; + + /// Represents a repeated group. + struct RepeatElement + { + /// Offset of the repeated group (first repeat). + uint32_t start_; + /// Address bytes per repeat. + uint32_t repeatSize_; + /// Address byte after the last repeat. + uint32_t end_; + }; + + /// STL-compatible comparator function for sorting RepeatElements. Sorts + /// repeats by the end_ as the key. + struct RepeatComparator + { + /// Sorting operator by end address. + bool operator()(const RepeatElement &a, const RepeatElement &b) const + { + return a.end_ < b.end_; + } + /// Sorting operator by end address against a lookup key. + bool operator()(uint32_t a, const RepeatElement &b) const + { + return a < b.end_; + } }; /// Look up the first matching data element given an address in the virtual @@ -291,35 +344,86 @@ private: { *repeat = 0; *ptr = nullptr; - auto it = elements_.upper_bound(address); - if (it != elements_.begin()) + bool in_repeat = false; + address_t original_address = address; + ElementsType::iterator b = elements_.begin(); + ElementsType::iterator e = elements_.end(); + // Align in the known repetitions first. + auto rit = repeats_.upper_bound(address); + if (rit == repeats_.end()) + { + // not a repeat. + } + else { - auto pit = it - 1; - // now: pit->address_ <= address - if (pit->address_ + pit->size_ > address) + if (rit->start_ <= address && rit->end_ > address) { - // found overlap - *ptr = &*pit; - return (ssize_t)pit->address_ - - (ssize_t)address; // may be negative! + // we are in the repeat. + unsigned cnt = (address - rit->start_) / rit->repeatSize_; + *repeat = cnt; + // re-aligns address to the first repetition. + address -= cnt * rit->repeatSize_; + in_repeat = true; + b = elements_.lower_bound(rit->start_); + e = elements_.lower_bound(rit->start_ + rit->repeatSize_); } - // else: no overlap, look at the next item } - // now: it->address_ > address - if (address + len > it->address_) + + for (int is_repeat = 0; is_repeat <= 1; ++is_repeat) { - // found overlap, but some data needs to be discarded. - *ptr = &*it; - return it->address_ - address; + auto it = std::upper_bound(b, e, address, DataComparator()); + if (it != elements_.begin()) + { + auto pit = it - 1; + // now: pit->address_ <= address + if (pit->address_ + pit->size_ > address) + { + // found overlap + *ptr = &*pit; + return (ssize_t)pit->address_ - + (ssize_t)address; // may be negative! + } + // else: no overlap, look at the next item + } + // now: it->address_ > address + if (address + len > it->address_) + { + // found overlap, but some data needs to be discarded. + *ptr = &*it; + return it->address_ - address; + } + + if (in_repeat) + { + // We might be too close to the end of a repetition, we will + // try with the next repeat instead. + address -= rit->repeatSize_; + *repeat += 1; + if (original_address + rit->repeatSize_ >= rit->end_) + { + // We ran out of repeats. Look at the range beyond the + // group instead. + b = elements_.lower_bound(rit->end_); + e = elements_.end(); + *repeat = 0; + } + } + else + { + break; + } } - /// @todo try repeated fields here first. // now: no overlap either before or after. return len; } + /// Container type for storing the data elements. + typedef SortedListSet ElementsType; + /// Stores all the registered variables. + ElementsType elements_; /// Stores all the registered variables. - SortedListSet elements_; + SortedListSet repeats_; /// Helper object in the function calls. BarrierNotifiable bn_; }; // class VirtualMemorySpace