From 4b87fc1baebe2d97afbedbef7a2b8c363b781f68 Mon Sep 17 00:00:00 2001
From: "Wesley W. Terpstra" <w.terpstra@gsi.de>
Date: Thu, 9 Feb 2012 09:44:26 +0000
Subject: [PATCH] Skeleton test bench in C++

---
 api/v2/Makefile           |   5 +-
 api/v2/etherbone.h        |  64 ++++++++++----
 api/v2/glue/cplusplus.cpp |  14 ++-
 api/v2/test/loopback.cpp  | 173 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 237 insertions(+), 19 deletions(-)
 create mode 100644 api/v2/test/loopback.cpp

diff --git a/api/v2/Makefile b/api/v2/Makefile
index a5ed088..40c4b64 100644
--- a/api/v2/Makefile
+++ b/api/v2/Makefile
@@ -9,7 +9,7 @@ CXXFLAGS= $(FLAGS)
 CC	= gcc
 CXX	= g++
 
-TARGETS	= demo/sizes demo/eb-read demo/eb-write demo/eb-snoop
+TARGETS	= demo/sizes demo/eb-read demo/eb-write demo/eb-snoop test/loopback
 TARGET	= etherbone.a
 OBJECTS	= $(patsubst %.cpp,%.o,$(patsubst %.c,%.o,$(SOURCES)))
 SOURCES	= memory/static.c		\
@@ -43,6 +43,9 @@ $(TARGET):	$(OBJECTS)
 demo/%:	demo/%.c $(TARGET)
 	$(CC) $(CFLAGS) -o $@ $^
 
+test/%:	test/%.cpp $(TARGET)
+	$(CXX) $(CXXFLAGS) -o $@ $^
+
 clean:
 	rm -f $(TARGET) $(OBJECTS) $(TARGETS)
 
diff --git a/api/v2/etherbone.h b/api/v2/etherbone.h
index 67af32b..6819220 100644
--- a/api/v2/etherbone.h
+++ b/api/v2/etherbone.h
@@ -409,7 +409,12 @@ typedef eb_data_t data_t;
 typedef eb_status_t status_t;
 typedef eb_width_t width_t;
 typedef eb_descriptor_t descriptor_t;
-typedef eb_handler_t handler_t;
+
+class Handler {
+  public:
+    virtual status_t read (address_t address, width_t width, data_t* data) = 0;
+    virtual status_t write(address_t address, width_t width, data_t  data) = 0;
+};
 
 class Socket {
   public:
@@ -419,7 +424,7 @@ class Socket {
     status_t close();
     
     /* attach/detach a virtual device */
-    status_t attach(handler_t handler);
+    status_t attach(address_t base, address_t mask, Handler* handler);
     status_t detach(address_t address);
     
     status_t poll();
@@ -445,7 +450,8 @@ class Device {
     status_t close();
     void flush();
     
-    Socket socket() const;
+    const Socket socket() const;
+    Socket socket();
     
   protected:
     Device(eb_device_t device);
@@ -471,7 +477,8 @@ class Cycle {
     Cycle& read_config (address_t address, data_t* data = 0);
     Cycle& write_config(address_t address, data_t  data);
     
-    Device device() const;
+    const Device device() const;
+    Device device();
     
   protected:
     eb_cycle_t cycle;
@@ -493,19 +500,20 @@ class Operation {
     address_t address() const;
     data_t    data   () const;
     
-    Operation next() const;
+    const Operation next() const;
+    Operation next();
     
   protected:
     Operation(eb_operation_t op);
     
     eb_operation_t operation;
-};
 
-/* Convenience templates to convert member functions into callback type */
-template <typename T, void (T::*cb)(Operation, status_t)>
-void proxy(T* object, eb_operation_t op, eb_status_t status) {
-  return (object->*cb)(Operation(op), status);
-}
+  /* Convenience templates to convert member functions into callback type */
+  template <typename T, void (T::*cb)(Operation, status_t)>
+  friend void proxy(T* object, eb_operation_t op, eb_status_t status) {
+    return (object->*cb)(Operation(op), status);
+  }
+};
 
 /****************************************************************************/
 /*                            C++ Implementation                            */
@@ -529,8 +537,18 @@ inline status_t Socket::close() {
   return out;
 }
 
-inline status_t Socket::attach(handler_t handler) {
-  return eb_socket_attach(socket, handler);
+/* Proxy */
+eb_status_t eb_proxy_read_handler(eb_user_data_t data, eb_address_t address, eb_width_t width, eb_data_t* ptr);
+eb_status_t eb_proxy_write_handler(eb_user_data_t data, eb_address_t address, eb_width_t width, eb_data_t value);
+
+inline status_t Socket::attach(address_t base, address_t mask, Handler* handler) {
+  struct eb_handler h;
+  h.base = base;
+  h.mask = mask;
+  h.data = handler;
+  h.read  = &eb_proxy_read_handler;
+  h.write = &eb_proxy_write_handler;
+  return eb_socket_attach(socket, &h);
 }
 
 inline status_t Socket::detach(address_t address) {
@@ -571,10 +589,14 @@ inline status_t Device::close() {
   return out;
 }
 
-inline Socket Device::socket() const {
+inline const Socket Device::socket() const {
   return Socket(eb_device_socket(device));
 }
-    
+
+inline Socket Device::socket() {
+  return Socket(eb_device_socket(device));
+}
+
 inline void Device::flush() {
   return eb_device_flush(device);
 }
@@ -625,7 +647,11 @@ inline Cycle& Cycle::write_config(address_t address, data_t data) {
   return *this;
 }
 
-inline Device Cycle::device() const {
+inline const Device Cycle::device() const {
+  return Device(eb_cycle_device(cycle));
+}
+
+inline Device Cycle::device() {
   return Device(eb_cycle_device(cycle));
 }
 
@@ -657,7 +683,11 @@ inline data_t Operation::data() const {
   return eb_operation_data(operation);
 }
 
-inline Operation Operation::next() const {
+inline Operation Operation::next() {
+  return Operation(eb_operation_next(operation));
+}
+
+inline const Operation Operation::next() const {
   return Operation(eb_operation_next(operation));
 }
 
diff --git a/api/v2/glue/cplusplus.cpp b/api/v2/glue/cplusplus.cpp
index cfc2424..373617f 100644
--- a/api/v2/glue/cplusplus.cpp
+++ b/api/v2/glue/cplusplus.cpp
@@ -27,7 +27,7 @@
 
 #include "../etherbone.h"
 
-using namespace etherbone;
+namespace etherbone {
 
 static void eb_descriptor_push(eb_user_data_t data, eb_descriptor_t des) {
   std::vector<descriptor_t>* out = (std::vector<descriptor_t>*)data;
@@ -39,3 +39,15 @@ std::vector<descriptor_t> Socket::descriptor() const {
   eb_socket_descriptor(socket, &out, &eb_descriptor_push);
   return out;
 }
+
+eb_status_t eb_proxy_read_handler(eb_user_data_t data, eb_address_t address, eb_width_t width, eb_data_t* ptr) {
+  Handler* handler = reinterpret_cast<Handler*>(data);
+  return handler->read(address, width, ptr);
+}
+
+eb_status_t eb_proxy_write_handler(eb_user_data_t data, eb_address_t address, eb_width_t width, eb_data_t value) {
+  Handler* handler = reinterpret_cast<Handler*>(data);
+  return handler->write(address, width, value);
+}
+
+}
diff --git a/api/v2/test/loopback.cpp b/api/v2/test/loopback.cpp
new file mode 100644
index 0000000..be0a3db
--- /dev/null
+++ b/api/v2/test/loopback.cpp
@@ -0,0 +1,173 @@
+/** @file loopback.cpp
+ *  @brief A test program which executes many many EB queries.
+ *
+ *  Copyright (C) 2011-2012 GSI Helmholtz Centre for Heavy Ion Research GmbH 
+ *
+ *  All Etherbone object types are opaque in this interface.
+ *  Only those methods listed in this header comprise the public interface.
+ *
+ *  @author Wesley W. Terpstra <w.terpstra@gsi.de>
+ *
+ *  @bug None!
+ *
+ *******************************************************************************
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 3 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *  
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *******************************************************************************
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <vector>
+#include <algorithm>
+
+#include "../etherbone.h"
+
+using namespace etherbone;
+using namespace std;
+
+void die(const char* why, status_t error);
+void test_query(Device device, int len, int requests);
+void test_width(Socket socket, width_t width);
+
+void die(const char* why, status_t error) {
+  fprintf(stderr, "%s: %s\n", why, eb_status(error));
+  exit(1);
+}
+
+enum RecordType { READ_BUS, READ_CFG, WRITE_BUS, WRITE_CFG };
+struct Record {
+  address_t address;
+  data_t data;
+  bool error;
+  RecordType type;
+};
+
+class Echo : public Handler {
+public:
+  status_t read (address_t address, width_t width, data_t* data);
+  status_t write(address_t address, width_t width, data_t  data);
+};
+
+status_t Echo::read (address_t address, width_t width, data_t* data) {
+  return EB_OK;
+}
+
+status_t Echo::write(address_t address, width_t width, data_t  data) {
+  return EB_OK;
+}
+
+class TestCycle {
+public:
+  std::vector<Record> records;
+  int* success;
+
+  TestCycle(Device device, int length, int* success);
+  void complete(Operation op, status_t status);
+};
+
+void TestCycle::complete(Operation op, status_t status) {
+  for (unsigned i = 0; i < records.size(); ++i) {
+    Record& r = records[i];
+    
+    if (op.is_null()) die("unexpected null op", EB_FAIL);
+    
+    switch (r.type) {
+    case READ_BUS:  if (!op.is_read() ||  op.is_config()) die("wrong op", EB_FAIL); break;
+    case READ_CFG:  if (!op.is_read() || !op.is_config()) die("wrong op", EB_FAIL); break;
+    case WRITE_BUS: if ( op.is_read() ||  op.is_config()) die("wrong op", EB_FAIL); break;
+    case WRITE_CFG: if ( op.is_read() || !op.is_config()) die("wrong op", EB_FAIL); break;
+    }
+    
+    if (op.address  () != r.address) die("wrong addr", EB_FAIL);
+    if (op.data     () != r.data)    die("wrong data", EB_FAIL);
+    if (op.had_error() != r.error)   die("wrong flag", EB_FAIL);
+  }
+  if (!op.is_null()) die("too many ops", EB_FAIL);
+}
+
+TestCycle::TestCycle(Device device, int length, int* success_)
+ : success(success_) {
+  Cycle cycle(device, this, &proxy<TestCycle, &TestCycle::complete>);
+  
+  for (int op = 0; op < length; ++op) {
+    Record r;
+    switch (r.type) {
+    case READ_BUS:  cycle.read        (r.address, 0);      break;
+    case READ_CFG:  cycle.read_config (r.address, 0);      break;
+    case WRITE_BUS: cycle.write       (r.address, r.data); break;
+    case WRITE_CFG: cycle.write_config(r.address, r.data); break;
+    }
+    records.push_back(r);
+  }
+}
+
+void test_query(Device device, int len, int requests) {
+  std::vector<int> cuts;
+  std::vector<int>::iterator i;
+  int success, timeout;
+  
+  cuts.push_back(0);
+  cuts.push_back(len);
+  for (int cut = 1; cut < requests; ++cut)
+    cuts.push_back(random() % len);
+  sort(cuts.begin(), cuts.end());
+  
+  /* Prepare each cycle */
+  for (i = cuts.begin(); i+1 != cuts.end(); ++i) {
+    int amount = *(i+1) - *i;
+    TestCycle(device, amount, &success);
+  }
+  
+  /* Flush the queries */
+  device.flush();
+  
+  /* Wait until all complete successfully */
+  timeout = 1000000; /* 1 second */
+  Socket socket = device.socket();
+  while (success < requests && timeout > 0) {
+    timeout -= socket.block(timeout);
+    socket.poll();
+  }
+  
+  if (timeout < 0) die("waiting for loopback success", EB_TIMEOUT);
+}
+
+void test_width(Socket socket, width_t width) {
+  Device device;
+  status_t err;
+  
+  if ((err = device.open(socket, "udp/localhost/8183", width)) != EB_OK) die("device.open", err);
+  
+  for (int len = 0; len < 4000; ++len)
+    for (int requests = 0; requests <= 9; ++requests)
+      for (int repetitions = 0; repetitions < 100; ++repetitions)
+        test_query(device, len, requests);
+    
+  if ((err = device.close()) != EB_OK) die("device.close", err);
+}  
+
+int main() {
+  status_t err;
+  
+  Socket socket;
+  if ((err = socket.open(8183)) != EB_OK) die("socket.open", err);
+  
+  Echo echo;
+  if ((err = socket.attach(0, ~0, &echo)) != EB_OK) die("socket.attach", err);
+  
+  /* for widths */
+  test_width(socket, EB_DATAX | EB_ADDRX);
+  return 0;
+}
-- 
GitLab