Start working on remote control server

This commit is contained in:
Joris van Rantwijk 2024-09-27 19:30:52 +02:00
parent 66050aca5b
commit ea5d3c3a1d
7 changed files with 1146 additions and 87 deletions

1
sw/.gitignore vendored
View File

@ -1,5 +1,6 @@
devicetree/devicetree.dtb devicetree/devicetree.dtb
buildroot_overlay/opt/puzzlefw/bin/puzzlecmd buildroot_overlay/opt/puzzlefw/bin/puzzlecmd
buildroot_overlay/opt/puzzlefw/bin/remotectl
buildroot_overlay/opt/puzzlefw/driver/ buildroot_overlay/opt/puzzlefw/driver/
downloads/ downloads/
buildroot-2023.02.8/ buildroot-2023.02.8/

View File

@ -8,17 +8,19 @@ CFLAGS = -Wall -O2
CXXFLAGS = -std=c++17 -Wall -Wno-psabi -O2 CXXFLAGS = -std=c++17 -Wall -Wno-psabi -O2
.PHONY: all .PHONY: all
all: puzzlecmd all: puzzlecmd remotectl
puzzlecmd: puzzlecmd.o puzzlefw.o puzzlecmd: puzzlecmd.o puzzlefw.o
$(CXX) -o $@ $(LDFLAGS) $^ $(CXX) -o $@ $(LDFLAGS) $^
puzzlecmd.o: puzzlecmd.cpp logging.hpp puzzlefw.hpp interrupt_manager.hpp data_server.hpp remotectl: remotectl.o puzzlefw.o
puzzlefw.o: puzzlefw.cpp puzzlefw.hpp $(CXX) -o $@ $(LDFLAGS) $^
testje: testje.c puzzlecmd.o: puzzlecmd.cpp logging.hpp puzzlefw.hpp interrupt_manager.hpp data_server.hpp
remotectl.o: remotectl.cpp logging.hpp puzzlefw.hpp interrupt_manager.hpp data_server.hpp version.hpp
puzzlefw.o: puzzlefw.cpp puzzlefw.hpp
.PHONY: clean .PHONY: clean
clean: clean:
$(RM) -f -- puzzlecmd testje *.o $(RM) -f -- puzzlecmd remotectl *.o

View File

@ -13,6 +13,7 @@
#include <stdint.h> #include <stdint.h>
#include <chrono> #include <chrono>
#include <iomanip> #include <iomanip>
#include <sstream>
#include <stdexcept> #include <stdexcept>
#include <system_error> #include <system_error>
#include <thread> #include <thread>
@ -82,11 +83,17 @@ public:
m_dma_stream.disable_interrupt(); m_dma_stream.disable_interrupt();
} }
/** Return the Asio strand that runs all handlers for this object. */
asio::strand<asio::io_context::executor_type> get_executor()
{
return m_strand;
}
/** /**
* Start the server. * Start the server.
* *
* This method must not be called directly while a multi-threaded Asio * If a multi-threaded Asio event loop is running, this method may only
* event loop is running. In that case, use "async_start_server()". * be called through the strand returned by "get_executor()".
*/ */
void start_server() void start_server()
{ {
@ -113,7 +120,7 @@ public:
// Asynchronously accept connections. // Asynchronously accept connections.
m_acceptor.async_accept( m_acceptor.async_accept(
[this](auto& ec, auto s){ handle_accept(ec, std::move(s)); }); [this](auto ec, auto s){ handle_accept(ec, std::move(s)); });
log(LOG_INFO, "Ready for TCP connections to port %d", m_tcp_port); log(LOG_INFO, "Ready for TCP connections to port %d", m_tcp_port);
} }
@ -121,8 +128,8 @@ public:
/** /**
* Stop the server and close current connections. * Stop the server and close current connections.
* *
* This method must not be called directly while a multi-threaded Asio * If a multi-threaded Asio event loop is running, this method may only
* event loop is running. In that case, use "async_stop_server()". * be called through the strand returned by "get_executor()".
*/ */
void stop_server() void stop_server()
{ {
@ -144,46 +151,6 @@ public:
} }
} }
/**
* Submit a call to "start_server()" to be executed in the strand
* of this server instance.
*
* After starting the server, the specified completion handler will
* be submitted for execution.
*
* This method may safely be called from any thread.
*/
template <typename Handler>
void async_start_server(Handler&& handler)
{
asio::post(
m_strand,
[this, handler] () mutable {
start_server();
asio::post(m_strand, handler);
});
}
/**
* Submit a call to "stop_server()" to be executed in the strand
* of this server instance.
*
* After stopping the server, the specified completion handler will
* be submitted for execution.
*
* This method may safely be called from any thread.
*/
template <typename Handler>
void async_stop_server(Handler&& handler)
{
asio::post(
m_strand,
[this, handler] () mutable {
stop_server();
asio::post(m_strand, handler);
});
}
/** /**
* Called when an FPGA interrupt occurs. * Called when an FPGA interrupt occurs.
* *
@ -206,7 +173,7 @@ private:
/** /**
* Re-initialize DMA stream and discard stale data from the DMA buffer. * Re-initialize DMA stream and discard stale data from the DMA buffer.
* *
* Do not call this while an async_send() operation is in progress. * Do not call this while an async_write() operation is in progress.
*/ */
void discard_stale_data() void discard_stale_data()
{ {
@ -228,7 +195,7 @@ private:
} }
/** Accept completion handler. */ /** Accept completion handler. */
void handle_accept(const boost::system::error_code& error, void handle_accept(boost::system::error_code error,
asio::ip::tcp::socket conn) asio::ip::tcp::socket conn)
{ {
if (error) { if (error) {
@ -256,7 +223,7 @@ private:
// Retry accept call. // Retry accept call.
if (m_acceptor.is_open()) { if (m_acceptor.is_open()) {
m_acceptor.async_accept( m_acceptor.async_accept(
[this](auto& ec, auto s) { [this](auto ec, auto s) {
handle_accept(ec, std::move(s)); handle_accept(ec, std::move(s));
}); });
} }
@ -281,7 +248,7 @@ private:
if (m_acceptor.is_open()) { if (m_acceptor.is_open()) {
// Continue accepting connections. // Continue accepting connections.
m_acceptor.async_accept( m_acceptor.async_accept(
[this](auto& ec, auto s){ handle_accept(ec, std::move(s)); }); [this](auto ec, auto s){ handle_accept(ec, std::move(s)); });
} }
if (m_connection.is_open()) { if (m_connection.is_open()) {
@ -302,7 +269,7 @@ private:
// closed remotely (or client writes unexpected data). // closed remotely (or client writes unexpected data).
m_connection.async_receive( m_connection.async_receive(
asio::buffer(m_receive_buf, sizeof(m_receive_buf)), asio::buffer(m_receive_buf, sizeof(m_receive_buf)),
[this](auto& ec, size_t n){ handle_receive(ec, n); }); [this](auto ec, size_t n){ handle_receive(ec, n); });
} }
// Clear buffer, then enable DMA stream. // Clear buffer, then enable DMA stream.
@ -314,7 +281,7 @@ private:
} }
/** Receive completion handler. */ /** Receive completion handler. */
void handle_receive(const boost::system::error_code& error, size_t len) void handle_receive(boost::system::error_code error, size_t len)
{ {
if (m_stale_receive) { if (m_stale_receive) {
// This completion refers to an old, already closed connection. // This completion refers to an old, already closed connection.
@ -324,7 +291,7 @@ private:
// Initiate async receive for the new connection. // Initiate async receive for the new connection.
m_connection.async_receive( m_connection.async_receive(
asio::buffer(m_receive_buf, sizeof(m_receive_buf)), asio::buffer(m_receive_buf, sizeof(m_receive_buf)),
[this](auto& ec, size_t n){ handle_receive(ec, n); }); [this](auto ec, size_t n){ handle_receive(ec, n); });
} }
return; return;
@ -359,10 +326,9 @@ private:
} }
/** Send completion handler. */ /** Send completion handler. */
void handle_send(const boost::system::error_code& error, size_t len) void handle_send(boost::system::error_code error, size_t len)
{ {
assert(m_send_in_progress); assert(m_send_in_progress);
assert(m_send_size > 0);
m_send_in_progress = false; m_send_in_progress = false;
@ -394,22 +360,14 @@ private:
return; return;
} }
if (len < m_send_buffer.size()) { // Send operation completed.
// Partially completed. Send the rest.
m_send_buffer += len;
m_send_in_progress = true;
m_connection.async_send(
m_send_buffer,
[this](auto& ec, size_t n){ handle_send(ec, n); });
} else {
// Fully completed.
// Release the completed part of the DMA buffer. // Release the completed part of the DMA buffer.
m_dma_stream.consume_data(m_send_size); assert(len == m_send_buffer.size());
m_dma_stream.consume_data(m_send_buffer.size());
// Try to send more data. // Try to send more data.
transmit_data(false); transmit_data(false);
} }
}
/** /**
* Try to send data from the DMA buffer to the TCP connection. * Try to send data from the DMA buffer to the TCP connection.
@ -447,7 +405,7 @@ private:
// If timeout occurs, we will send whatever data is available. // If timeout occurs, we will send whatever data is available.
m_timer.expires_after(WAIT_TIMEOUT); m_timer.expires_after(WAIT_TIMEOUT);
m_timer.async_wait( m_timer.async_wait(
[this](auto& ec){ handle_timer(ec); }); [this](auto ec){ handle_timer(ec); });
// Done. We will be notified for interrupt or timeout. // Done. We will be notified for interrupt or timeout.
return; return;
@ -467,16 +425,15 @@ private:
// Initiate async send. // Initiate async send.
// Limit the block size so we can release that part of the buffer // Limit the block size so we can release that part of the buffer
// as soon as it completes. // as soon as it completes.
m_send_size = std::min(data_available, SEND_MAX_BLOCK); size_t block_size = std::min(data_available, SEND_MAX_BLOCK);
m_send_buffer = asio::buffer(data, m_send_size); m_send_buffer = asio::buffer(data, block_size);
m_send_in_progress = true; m_send_in_progress = true;
m_connection.async_send( asio::async_write(m_connection, m_send_buffer,
m_send_buffer, [this](auto ec, size_t n){ handle_send(ec, n); });
[this](auto& ec, size_t n){ handle_send(ec, n); });
} }
/** Timeout handler. */ /** Timeout handler. */
void handle_timer(const boost::system::error_code& error) void handle_timer(boost::system::error_code error)
{ {
if (error) { if (error) {
// Ignore error due to cancellation. // Ignore error due to cancellation.
@ -520,7 +477,6 @@ private:
bool m_stale_receive; bool m_stale_receive;
bool m_stale_send; bool m_stale_send;
bool m_send_in_progress; bool m_send_in_progress;
size_t m_send_size;
asio::const_buffer m_send_buffer; asio::const_buffer m_send_buffer;
}; };
@ -541,7 +497,7 @@ public:
, m_interval(interval) , m_interval(interval)
{ {
m_timer.expires_after(m_interval); m_timer.expires_after(m_interval);
m_timer.async_wait([this](auto& ec){ handle_timer(ec); }); m_timer.async_wait([this](auto ec){ handle_timer(ec); });
} }
// Delete copy constructor and assignment operator. // Delete copy constructor and assignment operator.
@ -567,7 +523,7 @@ public:
private: private:
/** Timeout handler. */ /** Timeout handler. */
void handle_timer(const boost::system::error_code& error) void handle_timer(boost::system::error_code error)
{ {
if (error) { if (error) {
// Ignore error due to cancellation. // Ignore error due to cancellation.
@ -580,7 +536,7 @@ private:
} }
m_timer.expires_after(m_interval); m_timer.expires_after(m_interval);
m_timer.async_wait([this](auto& ec){ handle_timer(ec); }); m_timer.async_wait([this](auto ec){ handle_timer(ec); });
check_dma_error(); check_dma_error();
} }

View File

@ -74,7 +74,7 @@ public:
private: private:
/** Called when an FPGA interrupt occurs. */ /** Called when an FPGA interrupt occurs. */
void handle_wait(const boost::system::error_code& error) void handle_wait(boost::system::error_code error)
{ {
if (error) { if (error) {
// Ignore error due to cancellation. // Ignore error due to cancellation.
@ -100,7 +100,7 @@ private:
// Wait for next interrupt. // Wait for next interrupt.
m_uio_fd.async_wait(asio::posix::descriptor_base::wait_read, m_uio_fd.async_wait(asio::posix::descriptor_base::wait_read,
[this](auto& ec){ handle_wait(ec); }); [this](auto ec){ handle_wait(ec); });
} }
PuzzleFwDevice& m_device; PuzzleFwDevice& m_device;

View File

@ -2,6 +2,8 @@
* puzzlecmd.cpp * puzzlecmd.cpp
* *
* Command-line program to test PuzzleFW firmware. * Command-line program to test PuzzleFW firmware.
*
* Joris van Rantwijk 2024
*/ */
#include <limits.h> #include <limits.h>
@ -120,7 +122,7 @@ void run_data_server(puzzlefw::PuzzleFwDevice& device)
// Catch Ctrl-C for controlled shut down. // Catch Ctrl-C for controlled shut down.
asio::signal_set signals(io, SIGINT); asio::signal_set signals(io, SIGINT);
signals.async_wait( signals.async_wait(
[&io](auto& ec, int sig) { [&io](auto ec, int sig) {
log(LOG_INFO, "Got SIGINT, stopping server"); log(LOG_INFO, "Got SIGINT, stopping server");
io.stop(); io.stop();
}); });

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
#define PUZZLEFW_SW_MAJOR 0
#define PUZZLEFW_SW_MINOR 1