From 8ed98b8459cd88a8b949605d2f3cd6ca399157af Mon Sep 17 00:00:00 2001 From: Johannes Schlatow Date: Fri, 9 Nov 2018 09:48:05 +0100 Subject: [PATCH] remote_rom: use UDP and implement go-back-N ARQ --- include/remote_rom/backend_base.h | 6 +- include/remote_rom/rom_forwarder.h | 11 +- include/remote_rom/rom_receiver.h | 6 +- include/remote_rom/util.h | 49 +++ lib/mk/remote_rom_backend_nic_ip.mk | 2 +- run/remote_rom_backend_nic_ip.run | 36 +- run/remote_rom_backend_nic_ip_client.run | 9 +- run/remote_rom_backend_nic_ip_server.run | 17 +- src/lib/remote_rom/backend/nic_ip/base.cc | 103 +++++ src/lib/remote_rom/backend/nic_ip/base.h | 173 +++++--- src/lib/remote_rom/backend/nic_ip/client.cc | 403 ++++++++++++++---- src/lib/remote_rom/backend/nic_ip/packet.h | 104 +++-- src/lib/remote_rom/backend/nic_ip/server.cc | 434 ++++++++++++++++---- src/proxy/remote_rom/README | 13 +- src/proxy/remote_rom/client/main.cc | 188 ++++----- src/proxy/remote_rom/server/main.cc | 86 ++-- 16 files changed, 1221 insertions(+), 419 deletions(-) create mode 100644 include/remote_rom/util.h create mode 100644 src/lib/remote_rom/backend/nic_ip/base.cc diff --git a/include/remote_rom/backend_base.h b/include/remote_rom/backend_base.h index 8d85996..f7084bb 100644 --- a/include/remote_rom/backend_base.h +++ b/include/remote_rom/backend_base.h @@ -25,9 +25,11 @@ namespace Remote_rom { class Exception : public ::Genode::Exception { }; Backend_server_base &backend_init_server(Genode::Env &env, - Genode::Allocator &alloc); + Genode::Allocator &alloc, + Genode::Xml_node config); Backend_client_base &backend_init_client(Genode::Env &env, - Genode::Allocator &alloc); + Genode::Allocator &alloc, + Genode::Xml_node config); }; struct Remote_rom::Backend_server_base : Genode::Interface diff --git a/include/remote_rom/rom_forwarder.h b/include/remote_rom/rom_forwarder.h index 5a32b26..bc1639c 100644 --- a/include/remote_rom/rom_forwarder.h +++ b/include/remote_rom/rom_forwarder.h @@ -24,10 +24,13 @@ namespace Remote_rom { struct Remote_rom::Rom_forwarder_base : Genode::Interface { - virtual const char *module_name() const = 0; - virtual size_t content_size() const = 0; - virtual size_t transfer_content(char *dst, size_t dst_len, - size_t offset=0) const = 0; + virtual void start_transmission() = 0; + virtual void finish_transmission() = 0; + virtual const char *module_name() const = 0; + virtual size_t content_size() const = 0; + virtual unsigned content_hash() const = 0; + virtual size_t transfer_content(char *dst, size_t dst_len, + size_t offset=0) const = 0; }; #endif diff --git a/include/remote_rom/rom_receiver.h b/include/remote_rom/rom_receiver.h index 82fe489..91d6f6e 100644 --- a/include/remote_rom/rom_receiver.h +++ b/include/remote_rom/rom_receiver.h @@ -24,8 +24,10 @@ namespace Remote_rom { struct Remote_rom::Rom_receiver_base : Genode::Interface { - virtual const char *module_name() const = 0; - virtual char* start_new_content(size_t len) = 0; + virtual const char *module_name() const = 0; + virtual unsigned content_hash() const = 0; + virtual char* start_new_content(unsigned hash, + size_t len) = 0; virtual void commit_new_content(bool abort=false) = 0; }; diff --git a/include/remote_rom/util.h b/include/remote_rom/util.h new file mode 100644 index 0000000..92cccb9 --- /dev/null +++ b/include/remote_rom/util.h @@ -0,0 +1,49 @@ +/* + * \brief Utility functions used by ROM forwarder and receiver. + * \author Johannes Schlatow + * \date 2018-11-14 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef __INCLUDE__REMOTE_ROM__UTIL_H_ +#define __INCLUDE__REMOTE_ROM__UTIL_H_ + +#include + +namespace Remote_rom { + using Genode::uint32_t; + using Genode::int32_t; + using Genode::uint8_t; + using Genode::size_t; + + uint32_t cksum(void const * const buf, size_t size); +} +/** + * Calculating checksum compatible to POSIX cksum + * + * \param buf pointer to buffer containing data + * \param size length of buffer in bytes + * + * \return CRC32 checksum of data + */ +Genode::uint32_t Remote_rom::cksum(void const * const buf, size_t size) +{ + uint8_t const *p = static_cast(buf); + uint32_t crc = ~0U; + + while (size--) { + crc ^= *p++; + for (uint32_t j = 0; j < 8; j++) + crc = (-int32_t(crc & 1) & 0xedb88320) ^ (crc >> 1); + } + + return crc ^ ~0U; +} + +#endif diff --git a/lib/mk/remote_rom_backend_nic_ip.mk b/lib/mk/remote_rom_backend_nic_ip.mk index 43c5553..6ea304d 100644 --- a/lib/mk/remote_rom_backend_nic_ip.mk +++ b/lib/mk/remote_rom_backend_nic_ip.mk @@ -1,7 +1,7 @@ # \author Johannes Schlatow # \date 2016-03-19 -SRC_CC += backend/nic_ip/client.cc backend/nic_ip/server.cc +SRC_CC += backend/nic_ip/base.cc backend/nic_ip/client.cc backend/nic_ip/server.cc INC_DIR += $(REP_DIR)/src/lib/remote_rom/backend/nic_ip LIBS += base net diff --git a/run/remote_rom_backend_nic_ip.run b/run/remote_rom_backend_nic_ip.run index 1ee37ec..18bbe7d 100644 --- a/run/remote_rom_backend_nic_ip.run +++ b/run/remote_rom_backend_nic_ip.run @@ -1,3 +1,10 @@ +proc nic_drv_opt {} { + if {[have_spec linux]} { + return "ld=\"no\"" + } + return "" +} + # # Build # @@ -36,6 +43,7 @@ install_config { + @@ -64,36 +72,22 @@ install_config { - - + + - + - - - - - + - - - - - - - - - - - + @@ -105,7 +99,7 @@ install_config { - + diff --git a/run/remote_rom_backend_nic_ip_client.run b/run/remote_rom_backend_nic_ip_client.run index a1cf944..3bbd2f7 100644 --- a/run/remote_rom_backend_nic_ip_client.run +++ b/run/remote_rom_backend_nic_ip_client.run @@ -1,3 +1,10 @@ +proc nic_drv_opt {} { + if {[have_spec linux]} { + return "ld=\"no\"" + } + return "" +} + # # Build # @@ -58,7 +65,7 @@ install_config { - + diff --git a/run/remote_rom_backend_nic_ip_server.run b/run/remote_rom_backend_nic_ip_server.run index 47b0946..76ba4e8 100644 --- a/run/remote_rom_backend_nic_ip_server.run +++ b/run/remote_rom_backend_nic_ip_server.run @@ -1,3 +1,10 @@ +proc nic_drv_opt {} { + if {[have_spec linux]} { + return "ld=\"no\"" + } + return "" +} + # # Build # @@ -62,18 +69,18 @@ install_config { - - + + - + - - + diff --git a/src/lib/remote_rom/backend/nic_ip/base.cc b/src/lib/remote_rom/backend/nic_ip/base.cc new file mode 100644 index 0000000..b3e446b --- /dev/null +++ b/src/lib/remote_rom/backend/nic_ip/base.cc @@ -0,0 +1,103 @@ +/* + * \brief Client implementation + * \author Johannes Schlatow + * \author Edgard Schmidt + * \date 2018-11-06 + */ + +#include + +void Remote_rom::Backend_base::_handle_rx_packet() +{ + /* we assume that the SUBMIT and ACK queue are equally dimensioned + * i.e. ready_to_ack should always return true */ + while (_nic.rx()->packet_avail() && _nic.rx()->ready_to_ack()) { + Packet_descriptor pd = _nic.rx()->get_packet(); + if (pd.size()) + handle_ethernet(_nic.rx()->packet_content(pd), pd.size()); + + _nic.rx()->acknowledge_packet(pd); + } +} + +void Remote_rom::Backend_base::handle_ethernet(void* src, Genode::size_t size) +{ + try { + /* parse ethernet frame header */ + Size_guard edguard(size); + Ethernet_frame ð = Ethernet_frame::cast_from(src, edguard); + switch (eth.type()) { + case Ethernet_frame::Type::ARP: + handle_arp(eth, edguard); + break; + case Ethernet_frame::Type::IPV4: + handle_ip(eth, edguard); + break; + default: + ; + } + } catch(Size_guard::Exceeded) { + Genode::warning("Packet size guard exceeded!"); + } +} + +void Remote_rom::Backend_base::handle_arp(Ethernet_frame ð, + Size_guard &edguard) +{ + /* ignore broken packets */ + Arp_packet &arp = eth.data(edguard); + if (!arp.ethernet_ipv4()) + return; + + /* look whether the IP address is our's */ + if (arp.dst_ip() == _accept_ip) { + if (arp.opcode() == Arp_packet::REQUEST) { + /* + * The ARP packet gets re-written, we interchange source + * and destination MAC and IP addresses, and set the opcode + * to reply, and then push the packet back to the NIC driver. + */ + Ipv4_address old_src_ip = arp.src_ip(); + arp.opcode(Arp_packet::REPLY); + arp.dst_mac(arp.src_mac()); + arp.src_mac(_mac_address); + arp.src_ip(arp.dst_ip()); + arp.dst_ip(old_src_ip); + eth.dst(arp.dst_mac()); + + /* set our MAC as sender */ + eth.src(_mac_address); + send(ð, edguard.total_size()); + } + } +} + +void Remote_rom::Backend_base::handle_ip(Ethernet_frame ð, + Size_guard &edguard) +{ + Ipv4_packet &ip = eth.data(edguard); + if (_accept_ip == Ipv4_packet::broadcast() + || _accept_ip == ip.dst()) { + + if (ip.protocol() == Ipv4_packet::Protocol::UDP) { + /* remote_rom protocol is wrapped in UDP */ + Udp_packet &udp = ip.data(edguard); + if (udp.dst_port() == _udp_port) + receive(udp.data(edguard), edguard); + } + } +} + +void Remote_rom::Backend_base::send(Ethernet_frame *eth, Genode::size_t size) +{ + try { + /* copy and submit packet */ + Packet_descriptor packet = _nic.tx()->alloc_packet(size); + char *content = _nic.tx()->packet_content(packet); + Genode::memcpy((void*)content, (void*)eth, size); + submit_tx_packet(packet); + + } catch(Nic::Session::Tx::Source::Packet_alloc_failed) { + Genode::warning("Packet dropped"); + } +} diff --git a/src/lib/remote_rom/backend/nic_ip/base.h b/src/lib/remote_rom/backend/nic_ip/base.h index 7d59344..f364ccb 100644 --- a/src/lib/remote_rom/backend/nic_ip/base.h +++ b/src/lib/remote_rom/backend/nic_ip/base.h @@ -1,9 +1,16 @@ /* * \brief Common base for client and server * \author Johannes Schlatow + * \author Edgard Schmidt * \date 2016-02-18 */ +#include +#include +#include +#include +#include + #include #include #include @@ -11,8 +18,7 @@ #include #include -#include -#include +#include #include @@ -27,6 +33,9 @@ namespace Remote_rom { using Net::Mac_address; using Net::Ipv4_address; using Net::Size_guard; + using Net::Arp_packet; + using Net::Udp_packet; + using Net::Port; class Backend_base; }; @@ -39,24 +48,11 @@ class Remote_rom::Backend_base : public Genode::Interface Genode::Signal_handler _link_state_handler; Genode::Signal_handler _rx_packet_handler; - void _handle_rx_packet() - { - while (_nic.rx()->packet_avail() && _nic.rx()->ready_to_ack()) { - Packet_descriptor _rx_packet = _nic.rx()->get_packet(); + Port _udp_port; + Port const _src_port { 51234 }; - char *content = _nic.rx()->packet_content(_rx_packet); - Size_guard edguard(_rx_packet.size()); - Ethernet_frame ð = Ethernet_frame::cast_from(content, edguard); - - /* check IP */ - Ipv4_packet &ip_packet = eth.data(edguard); - if (_accept_ip == Ipv4_packet::broadcast() - || _accept_ip == ip_packet.dst()) - receive(ip_packet.data(edguard), edguard); - - _nic.rx()->acknowledge_packet(_rx_packet); - } - } + /* rx packet handler */ + void _handle_rx_packet(); void _handle_link_state() { @@ -76,99 +72,122 @@ class Remote_rom::Backend_base : public Genode::Interface protected: enum { - PACKET_SIZE = 1024, + PACKET_SIZE = 1600, BUF_SIZE = Nic::Session::QUEUE_SIZE * PACKET_SIZE }; + Timer::Connection _timer; + const bool _verbose = false; Nic::Packet_allocator _tx_block_alloc; Nic::Connection _nic; Mac_address _mac_address; + Mac_address _dst_mac; Ipv4_address _src_ip; Ipv4_address _accept_ip; Ipv4_address _dst_ip; + bool _chksum_offload { false }; /** * Handle accepted network packet from the other side */ virtual void receive(Packet &packet, Size_guard &) = 0; - Ipv4_packet &_prepare_upper_layers(void *base, Size_guard &size_guard) + Ethernet_frame &prepare_eth(void *base, Size_guard &size_guard) { Ethernet_frame ð = Ethernet_frame::construct_at(base, size_guard); eth.src(_mac_address); - eth.dst(Ethernet_frame::broadcast()); + eth.dst(_dst_mac); eth.type(Ethernet_frame::Type::IPV4); + return eth; + } + + Ipv4_packet &prepare_ipv4(Ethernet_frame ð, Size_guard &size_guard) + { Ipv4_packet &ip = eth.construct_at_data(size_guard); ip.version(4); - ip.header_length(5); + ip.header_length(sizeof(Ipv4_packet) / 4); ip.time_to_live(10); + ip.protocol(Ipv4_packet::Protocol::UDP); ip.src(_src_ip); ip.dst(_dst_ip); return ip; } - size_t _upper_layer_size(size_t size) + Udp_packet &prepare_udp(Ipv4_packet &ip, Size_guard &size_guard) { - return sizeof(Ethernet_frame) + sizeof(Ipv4_packet) + size; - } + Udp_packet &udp = ip.construct_at_data(size_guard); + udp.src_port(_src_port); + udp.dst_port(_udp_port); - void _finish_ipv4(Ipv4_packet &ip, size_t payload) - { - ip.total_length(sizeof(ip) + payload); - ip.update_checksum(); + return udp; } template - void _transmit_notification_packet(Packet::Type type, T *frontend) + void transmit_notification(Packet::Type type, + T const &frontend) { - size_t frame_size = _upper_layer_size(sizeof(Packet)); + size_t const frame_size = sizeof(Ethernet_frame) + + sizeof(Ipv4_packet) + + sizeof(Udp_packet) + + sizeof(Packet) + + sizeof(NotificationPacket); Nic::Packet_descriptor pd = alloc_tx_packet(frame_size); Size_guard size_guard(pd.size()); char *content = _nic.tx()->packet_content(pd); - Ipv4_packet &ip = _prepare_upper_layers(content, size_guard); - Packet &pak = ip.construct_at_data(size_guard); + Ethernet_frame ð = prepare_eth(content, size_guard); + + size_t const ip_off = size_guard.head_size(); + Ipv4_packet &ip = prepare_ipv4(eth, size_guard); + + size_t const udp_off = size_guard.head_size(); + Udp_packet &udp = prepare_udp(ip, size_guard); + + Packet &pak = udp.construct_at_data(size_guard); pak.type(type); - pak.module_name(frontend->module_name()); - _finish_ipv4(ip, sizeof(Packet)); + pak.module_name(frontend.module_name()); + pak.content_hash(frontend.content_hash()); + + NotificationPacket &npak = + pak.construct_at_data(size_guard); + npak.content_size(frontend.content_size()); + + /* fill in header values that need the packet to be complete already */ + udp.length(size_guard.head_size() - udp_off); + if (!_chksum_offload) + udp.update_checksum(ip.src(), ip.dst()); + + ip.total_length(size_guard.head_size() - ip_off); + ip.update_checksum(); submit_tx_packet(pd); } - explicit Backend_base(Genode::Env &env, Genode::Allocator &alloc) + explicit Backend_base(Genode::Env &env, + Genode::Allocator &alloc, + Genode::Xml_node config, + Genode::Xml_node policy) : _link_state_handler(env.ep(), *this, &Backend_base::_handle_link_state), _rx_packet_handler(env.ep(), *this, &Backend_base::_handle_rx_packet), + _udp_port(policy.attribute_value("udp_port", Port(9009))), + _timer(env), _tx_block_alloc(&alloc), _nic(env, &_tx_block_alloc, BUF_SIZE, BUF_SIZE), _mac_address(_nic.mac_address()), - _src_ip(Ipv4_packet::current()), - _accept_ip(Ipv4_packet::broadcast()), - _dst_ip(Ipv4_packet::broadcast()) + _dst_mac (policy.attribute_value("dst_mac", Ethernet_frame::broadcast())), + _src_ip (policy.attribute_value("src", Ipv4_packet::current())), + _accept_ip(policy.attribute_value("src", Ipv4_packet::broadcast())), + _dst_ip (policy.attribute_value("dst", Ipv4_packet::broadcast())), + _chksum_offload(config.attribute_value("chksum_offload", _chksum_offload)) { _nic.link_state_sigh(_link_state_handler); _nic.rx_channel()->sigh_packet_avail(_rx_packet_handler); - _nic.rx_channel()->sigh_ready_to_ack(_rx_packet_handler); - - Genode::Attached_rom_dataspace config = {env, "config"}; - try { - char ip_string[15]; - Genode::Xml_node remoterom = config.xml().sub_node("remote_rom"); - remoterom.attribute("src").value(ip_string, sizeof(ip_string)); - _src_ip = Ipv4_packet::ip_from_string(ip_string); - - remoterom.attribute("dst").value(ip_string, sizeof(ip_string)); - _dst_ip = Ipv4_packet::ip_from_string(ip_string); - - _accept_ip = _src_ip; - } catch (...) { - Genode::warning("No IP configured, falling back to broadcast mode!"); - } } Nic::Packet_descriptor alloc_tx_packet(Genode::size_t size) @@ -186,10 +205,50 @@ class Remote_rom::Backend_base : public Genode::Interface void submit_tx_packet(Nic::Packet_descriptor packet) { - _nic.tx()->submit_packet(packet); /* check for acknowledgements */ _tx_ack(); + + if (!_nic.tx()->ready_to_submit()) { + Genode::error("not ready to submit"); + return; + } + + _nic.tx()->submit_packet(packet); } + + /** + * Handle an Ethernet packet + * + * \param src Ethernet frame's address + * \param size Ethernet frame's size. + */ + void handle_ethernet(void* src, Genode::size_t size); + + /* + * Handle an ARP packet + * + * \param eth Ethernet frame containing the ARP packet. + * \param size size guard + */ + void handle_arp(Ethernet_frame ð, + Size_guard &edguard); + + /* + * Handle an IP packet + * + * \param eth Ethernet frame containing the IP packet. + * \param size size guard + */ + void handle_ip(Ethernet_frame ð, + Size_guard &edguard); + + /** + * Send ethernet frame + * + * \param eth ethernet frame to send. + * \param size ethernet frame's size. + */ + void send(Ethernet_frame *eth, Genode::size_t size); }; #endif diff --git a/src/lib/remote_rom/backend/nic_ip/client.cc b/src/lib/remote_rom/backend/nic_ip/client.cc index 3bfa31c..0d2c6da 100644 --- a/src/lib/remote_rom/backend/nic_ip/client.cc +++ b/src/lib/remote_rom/backend/nic_ip/client.cc @@ -1,16 +1,142 @@ /* * \brief Client implementation * \author Johannes Schlatow + * \author Edgard Schmidt * \date 2018-11-06 */ -#include #include +#include namespace Remote_rom { using Genode::Cstring; + using Genode::Microseconds; - struct Backend_client; + class Content_receiver; + class Backend_client; +}; + +class Remote_rom::Content_receiver +{ + private: + enum { + MAX_PAYLOAD_SIZE = DataPacket::MAX_PAYLOAD_SIZE, + TIMEOUT_DATA_US = 50000 /* 50ms */ + }; + + char *_write_ptr { nullptr }; + size_t _buf_size { 0 }; + size_t _offset { 0 }; + + /* window state */ + size_t _window_id { 0 }; + size_t _window_length { 0 }; + size_t _next_packet_id { 0 }; + bool _omit_nack { false }; + + /* timeouts and general object management*/ + Timer::One_shot_timeout _timeout; + Backend_client &_backend; + Rom_receiver_base *_frontend { nullptr }; + + size_t _write_offset() + { return _offset + _next_packet_id * MAX_PAYLOAD_SIZE; } + + void timeout_handler(Genode::Duration); + + /* Noncopyable */ + Content_receiver(Content_receiver const &); + Content_receiver &operator=(Content_receiver const &); + + bool _start_window(size_t window_length) + { + /* calculate offset to beginning of new window */ + _offset += _window_length * MAX_PAYLOAD_SIZE; + + /* zero packet_id marks first window */ + if (_next_packet_id > 0) + _window_id++; + + _window_length = window_length; + _next_packet_id = 0; + + if (_offset >= _buf_size) + return false; + + + return true; + } + + void _write(const void *data, size_t packet_id, size_t size) + { + if (!_write_ptr) return; + if (packet_id != _next_packet_id) return; + + size_t const offset = _write_offset(); + if (offset >= _buf_size) + return; + + size_t const len = Genode::min(size, _buf_size-offset); + Genode::memcpy(_write_ptr+offset, data, len); + } + + public: + Content_receiver(Timer::Connection &timer, + Backend_client &backend) + : _timeout(timer, *this, &Content_receiver::timeout_handler), + _backend(backend) + { } + + void register_receiver(Rom_receiver_base *frontend) + { + _frontend = frontend; + } + + void start_new_content(unsigned hash, + size_t size) + { + if (!_frontend) return; + + _write_ptr = _frontend->start_new_content(hash, size); + _buf_size = _write_ptr ? size : 0; + _offset = 0; + _window_id = 0; + _window_length = 0; + _next_packet_id = 0; + _omit_nack = false; + + if (_timeout.scheduled()) + _timeout.discard(); + } + + /********************** + * frontend accessors * + **********************/ + + unsigned content_hash() const + { return _frontend ? _frontend->content_hash() : 0; } + + char const *module_name() const + { return _frontend ? _frontend->module_name() : ""; } + + size_t content_size() const + { return _buf_size; } + + bool window_complete() + { + /* remark: also returns true if we havent started any window yet */ + return _next_packet_id == _window_length; + } + + bool complete() + { + return _write_offset() >= _buf_size; + } + + bool accept_packet(const DataPacket &p); + + size_t window_id() { return _window_id; } + size_t ack_until() { return _next_packet_id; } }; class Remote_rom::Backend_client : @@ -18,97 +144,43 @@ class Remote_rom::Backend_client : public Backend_base { private: + friend class Content_receiver; - Rom_receiver_base *_receiver; - char *_write_ptr; - size_t _buf_size; + Content_receiver _content_receiver { _timer, *this }; Backend_client(Backend_client &); Backend_client &operator= (Backend_client &); - void write(const void *data, size_t offset, size_t size) - { - if (!_write_ptr) return; - - - size_t const len = Genode::min(size, _buf_size-offset); - Genode::memcpy(_write_ptr+offset, data, len); - - if (offset + len >= _buf_size) - _receiver->commit_new_content(); - } - - void update(const char* module_name) { - if (!_receiver) return; - /* check module name */ - if (Genode::strcmp(module_name, _receiver->module_name())) + if (Genode::strcmp(module_name, _content_receiver.module_name())) return; - _transmit_notification_packet(Packet::UPDATE, _receiver); + if (_verbose) + Genode::log("sending UPDATE(", _content_receiver.module_name(), ")"); + + transmit_notification(Packet::UPDATE, _content_receiver); } + void send_ack(Content_receiver const &recv); - void receive(Packet &packet, Size_guard &size_guard) - { - switch (packet.type()) - { - case Packet::SIGNAL: - if (_verbose) - Genode::log("receiving SIGNAL(", - Cstring(packet.module_name()), - ") packet"); - - /* send update request */ - update(packet.module_name()); - - break; - case Packet::DATA: - { - if (_verbose) - Genode::log("receiving DATA(", - Cstring(packet.module_name()), - ") packet"); - - /* write into buffer */ - if (!_receiver) return; - - /* check module name */ - if (Genode::strcmp(packet.module_name(), _receiver->module_name())) - return; - - const DataPacket &data = packet.data(size_guard); - size_guard.consume_head(data.payload_size()); - - if (!data.offset()) { - _write_ptr = _receiver->start_new_content(data.content_size()); - _buf_size = (_write_ptr) ? data.content_size() : 0; - } - - write(data.addr(), data.offset(), data.payload_size()); - - break; - } - default: - break; - } - } + void receive(Packet &packet, Size_guard &size_guard); public: - Backend_client(Genode::Env &env, Genode::Allocator &alloc) : - Backend_base(env, alloc), - _receiver(nullptr), _write_ptr(nullptr), - _buf_size(0) + Backend_client(Genode::Env &env, + Genode::Allocator &alloc, + Genode::Xml_node config, + Genode::Xml_node policy) + : Backend_base(env, alloc, config, policy) { } void register_receiver(Rom_receiver_base *receiver) { /* TODO support multiple receivers (ROM names) */ - _receiver = receiver; + _content_receiver.register_receiver(receiver); /* * FIXME request update on startup @@ -122,10 +194,195 @@ class Remote_rom::Backend_client : namespace Remote_rom { using Genode::Env; using Genode::Allocator; + using Genode::Xml_node; - Backend_client_base &backend_init_client(Env &env, Allocator &alloc) + Backend_client_base &backend_init_client(Env &env, + Allocator &alloc, + Xml_node config) { - static Backend_client backend(env, alloc); + static Backend_client backend(env, + alloc, + config, + config.sub_node("remote_rom")); return backend; } }; + +void Remote_rom::Backend_client::send_ack(Content_receiver const &recv) +{ + size_t const frame_size = sizeof(Ethernet_frame) + + sizeof(Ipv4_packet) + + sizeof(Udp_packet) + + sizeof(Packet) + + sizeof(AckPacket); + Nic::Packet_descriptor pd = alloc_tx_packet(frame_size); + Size_guard size_guard(pd.size()); + + char *content = _nic.tx()->packet_content(pd); + Ethernet_frame ð = prepare_eth(content, size_guard); + + size_t const ip_off = size_guard.head_size(); + Ipv4_packet &ip = prepare_ipv4(eth, size_guard); + + size_t const udp_off = size_guard.head_size(); + Udp_packet &udp = prepare_udp(ip, size_guard); + + Packet &pak = udp.construct_at_data(size_guard); + pak.type(Packet::ACK); + pak.module_name(recv.module_name()); + pak.content_hash(recv.content_hash()); + + AckPacket &ack = + pak.construct_at_data(size_guard); + ack.window_id(_content_receiver.window_id()); + ack.ack_until(_content_receiver.ack_until()); + + /* fill in header values that need the packet to be complete already */ + udp.length(size_guard.head_size() - udp_off); + if (!_chksum_offload) + udp.update_checksum(ip.src(), ip.dst()); + + ip.total_length(size_guard.head_size() - ip_off); + ip.update_checksum(); + + submit_tx_packet(pd); + + if (_verbose) + Genode::log("Sent ACK for window ", _content_receiver.window_id()); +} + +void Remote_rom::Backend_client::receive(Packet &packet, + Size_guard &size_guard) +{ + switch (packet.type()) + { + case Packet::SIGNAL: + { + const NotificationPacket &signal + = packet.data(size_guard); + + if (_verbose) + Genode::log("receiving SIGNAL(", + Cstring(packet.module_name()), + ") packet, size ", + signal.content_size()); + + /* start new content with given size and hash */ + _content_receiver.start_new_content( + packet.content_hash(), + signal.content_size()); + + /* send update request */ + update(packet.module_name()); + + break; + } + case Packet::DATA: + { + /* check module name */ + if (Genode::strcmp(packet.module_name(), _content_receiver.module_name())) + return; + + /* check hash */ + if (packet.content_hash() != _content_receiver.content_hash()) { + Genode::warning("ignoring hash mismatch ", + Genode::Hex(packet.content_hash()), + " != ", + Genode::Hex(_content_receiver.content_hash())); + return; + } + + const DataPacket &data = packet.data(size_guard); + size_guard.consume_head(data.payload_size()); + + _content_receiver.accept_packet(data); + + break; + } + case Packet::UPDATE: + /* drop UPDATE packets received from other clients */ + if (_verbose) + Genode::log("ignoring UPDATE"); + break; + default: + Genode::error("unknown packet type (", Genode::Hex(packet.type()), ")."); + break; + } +} + +void Remote_rom::Content_receiver::timeout_handler(Genode::Duration) +{ + Genode::warning("timeout occurred waiting for packet ", _next_packet_id, + " in window ", _window_id, " of length ", _window_length); + _backend.send_ack(*this); +} + +bool Remote_rom::Content_receiver::accept_packet(const DataPacket &p) +{ + /** + * TODO replace return value with exceptions + */ + if (complete() || !_frontend) return false; + + if (_timeout.scheduled()) + _timeout.discard(); + + if (window_complete()) { + if (!_start_window(p.window_length())) { + Genode::warning("unexpected error starting window of size ", + p.window_length()); + return false; + } + } + + /* drop packets with wrong window id */ + if (p.window_id() != _window_id) { + if (p.window_id() == _window_id-1 + && p.packet_id() == p.window_length()-1) { + + /* rollback the window */ + _offset -= _window_length * MAX_PAYLOAD_SIZE; + _window_id = p.window_id(); + _window_length = p.window_length(); + _next_packet_id = _window_length; + + /* send acknowledge if we receive the last packet, as + * the sender missed our previous ACK */ + Genode::log("re-sending ACK"); + _backend.send_ack(*this); + } + return false; + } + + /* NACK if we missed a packet */ + if (p.packet_id() != _next_packet_id) { + + if (!_omit_nack) { + + Genode::log("lost packet, sending NACK"); + _backend.send_ack(*this); + + /* omit any further NACKs until we received a correct + * package or a timeout occurred */ + _omit_nack = true; + } + + return false; + } + else { + _omit_nack = false; + } + + _write(p.addr(), p.packet_id(), p.payload_size()); + _next_packet_id++; + + if (window_complete()) + _backend.send_ack(*this); + + if (complete()) + _frontend->commit_new_content(); + else + _timeout.schedule(Microseconds(TIMEOUT_DATA_US)); + + return true; +} diff --git a/src/lib/remote_rom/backend/nic_ip/packet.h b/src/lib/remote_rom/backend/nic_ip/packet.h index c94c41e..5f377a6 100644 --- a/src/lib/remote_rom/backend/nic_ip/packet.h +++ b/src/lib/remote_rom/backend/nic_ip/packet.h @@ -1,6 +1,7 @@ /* * \brief Packet format we use for inter-system communication * \author Johannes Schlatow + * \author Edgard Schmidt * \date 2018-11-05 */ @@ -13,13 +14,19 @@ namespace Remote_rom { using Genode::size_t; + using Genode::uint8_t; using Genode::uint16_t; using Genode::uint32_t; - struct Packet; - struct DataPacket; + class Window_state; + + class Packet; + class NotificationPacket; + class AckPacket; + class DataPacket; } + class Remote_rom::Packet { public: @@ -31,17 +38,15 @@ class Remote_rom::Packet SIGNAL = 1, /* signal that ROM content has changed */ UPDATE = 2, /* request transmission of updated content */ DATA = 3, /* data packet */ + ACK = 4, /* acknowledge data packets */ } Type; private: char _module_name[MAX_NAME_LEN]; /* the ROM module name */ + uint32_t _content_hash; /* serves as version */ Type _type; /* packet type */ - /***************************************************** - ** 'payload' must be the last member of this class ** - *****************************************************/ - - char payload[0]; + char _data[0]; public: /** @@ -59,6 +64,9 @@ class Remote_rom::Packet _type = type; } + void content_hash(uint32_t hash) { _content_hash = hash; } + uint32_t content_hash() const { return _content_hash; } + void module_name(const char *module) { Genode::strncpy(_module_name, module, MAX_NAME_LEN); @@ -68,30 +76,66 @@ class Remote_rom::Packet T const &data(Net::Size_guard &size_guard) const { size_guard.consume_head(sizeof(T)); - return *(T const *)(payload); + return *(T const *)(_data); } template T &construct_at_data(Net::Size_guard &size_guard) { size_guard.consume_head(sizeof(T)); - return *Genode::construct_at(payload); + return *Genode::construct_at(_data); } } __attribute__((packed)); +class Remote_rom::NotificationPacket +{ + private: + uint32_t _content_size; /* ROM content size in bytes */ + + public: + + void content_size(size_t size) { _content_size = size; } + size_t content_size() const { return _content_size; } + +} __attribute__((packed)); + +class Remote_rom::AckPacket +{ + private: + uint16_t _window_id; /* refers to this window id */ + uint16_t _ack_until; /* acknowledge until this packet id - 1 */ + + /** + * TODO Implement selective ARQ by using a Bit_array to save + * the ACK state in the window. + * We should, however, not use the Bit_array directly as a + * Packet member. + */ + + public: + + void window_id(size_t window_id) { _window_id = window_id; } + void ack_until(size_t packet_id) { _ack_until = packet_id; } + + size_t window_id() const { return _window_id; } + size_t ack_until() const { return _ack_until; } + +} __attribute__((packed)); + class Remote_rom::DataPacket { public: - static const size_t MAX_PAYLOAD_SIZE = 1024; + static const size_t MAX_PAYLOAD_SIZE = 1350; private: - uint32_t _content_size; /* ROM content size in bytes */ - uint32_t _offset; /* offset in bytes */ - uint16_t _payload_size; /* payload size in bytes */ + uint16_t _payload_size; /* payload size in bytes */ + uint16_t _window_id; /* window id */ + uint16_t _packet_id; /* packet number within window */ + uint16_t _window_length; /* 0: no ARQ, >0: ARQ window length */ - char payload[0]; + char _data[0]; public: /** @@ -99,18 +143,13 @@ class Remote_rom::DataPacket */ size_t size() const { return _payload_size + sizeof(*this); } - /** - * Return content_size of the packet - */ - size_t content_size() const { return _content_size; } + void window_length(size_t len) { _window_length = len; } + void window_id(size_t id) { _window_id = id; } + void packet_id(size_t id) { _packet_id = id; } - /** - * Return offset of the packet - */ - size_t offset() const { return _offset; } - - void content_size(size_t size) { _content_size = size; } - void offset(size_t offset) { _offset = offset; } + size_t window_length() const { return _window_length; } + size_t window_id() const { return _window_id; } + size_t packet_id() const { return _packet_id; } /** * Set payload size of the packet @@ -126,17 +165,10 @@ class Remote_rom::DataPacket size_t payload_size() const { return _payload_size; } /** - * Return address of the payload - */ - void *addr() { return payload; } - const void *addr() const { return payload; } - - /** - * Return packet size for given payload - */ - static size_t packet_size(size_t payload) { - return sizeof(DataPacket) + Genode::min(payload, MAX_PAYLOAD_SIZE); - } + * Return address of the payload + */ + void *addr() { return _data; } + const void *addr() const { return _data; } } __attribute__((packed)); diff --git a/src/lib/remote_rom/backend/nic_ip/server.cc b/src/lib/remote_rom/backend/nic_ip/server.cc index 86c4984..d302d18 100644 --- a/src/lib/remote_rom/backend/nic_ip/server.cc +++ b/src/lib/remote_rom/backend/nic_ip/server.cc @@ -1,16 +1,182 @@ /* * \brief Server implementation * \author Johannes Schlatow + * \author Edgard Schmidt * \date 2018-11-06 */ -#include #include +#include namespace Remote_rom { using Genode::Cstring; + using Genode::Microseconds; - struct Backend_server; + class Content_sender; + class Backend_server; +}; + +class Remote_rom::Content_sender +{ + private: + enum { + MAX_PAYLOAD_SIZE = DataPacket::MAX_PAYLOAD_SIZE, + MAX_WINDOW_SIZE = 900, + TIMEOUT_ACK_US = 1000000 /* 1000ms */ + }; + + /* total data size */ + size_t _data_size { 0 }; + + /* current window length */ + size_t _window_length { MAX_WINDOW_SIZE }; + + /* current window id */ + size_t _window_id { 0 }; + + /* data offset of current window */ + size_t _offset { 0 }; + + /* current packed id */ + size_t _packet_id { 0 }; + + size_t _errors { 0 }; + + /* timeouts and general object management*/ + Timer::One_shot_timeout _timeout; + Backend_server &_backend; + Rom_forwarder_base *_frontend { nullptr }; + + void timeout_handler(Genode::Duration) + { + Genode::warning("no ACK received for window ", _window_id); + + /* only handle */ + if (_errors == 0) { + /* go back to beginning of window */ + go_back(0); + transmit(false); + } + else { + reset(); + _frontend->finish_transmission(); + Genode::warning("transmission cancelled"); + } + _errors++; + } + + /* Noncopyable */ + Content_sender(Content_sender const &); + Content_sender &operator=(Content_sender const &); + + static size_t _calculate_window_size(size_t size) + { + size_t const mod = size % MAX_PAYLOAD_SIZE; + size_t const packets = size / MAX_PAYLOAD_SIZE + (mod ? 1 : 0); + + return Genode::min((size_t)MAX_WINDOW_SIZE, packets); + } + + /** + * Go to next packet. Returns false if window is complete. + */ + inline bool _next_packet() + { return ++_packet_id < _window_length; } + + inline bool _window_complete() const + { return _packet_id == _window_length; } + + inline bool _transmission_complete() const + { return _offset >= _data_size; } + /** + * Return absolute data offset of current packet. + */ + inline size_t _data_offset() const + { return _offset + _packet_id * MAX_PAYLOAD_SIZE; } + + + /** + * Go to next window. Returns false if end of data was reached. + * TODO we may adapt the window size if retransmission occurred + */ + bool _next_window() { + /* advance offset by data transmitted in the last window */ + _offset += _window_length * MAX_PAYLOAD_SIZE; + if (_transmission_complete()) + return false; + + _window_id++; + _window_length = _calculate_window_size(_data_size-_offset); + _packet_id = 0; + + return true; + } + + public: + Content_sender(Timer::Connection &timer, Backend_server &backend) + : _timeout(timer, *this, &Content_sender::timeout_handler), + _backend(backend) + { } + + void register_forwarder(Rom_forwarder_base *forwarder) + { + _frontend = forwarder; + } + + void reset() + { + _offset = 0; + _packet_id = 0; + _window_id = 0; + _data_size = 0; + _window_length = 0; + _errors = 0; + } + + bool transmitting() { return _packet_id > 0; } + + /********************** + * frontend accessors * + **********************/ + + unsigned content_hash() const + { return _frontend ? _frontend->content_hash() : 0; } + + size_t content_size() const + { return _frontend ? _frontend->content_size() : 0; } + + char const *module_name() const + { return _frontend ? _frontend->module_name() : ""; } + + size_t transfer_content(char* dst, size_t max_size) const + { + if (!_frontend) return 0; + + return _frontend->transfer_content(dst, max_size, _data_offset()); + } + + /************************ + * transmission control * + ************************/ + + bool transmit(bool restart); + + void go_back(size_t packet_id) { _packet_id = packet_id; } + + /************************************* + * accessors for packet construction * + *************************************/ + + /** + * Return payload size of current packet. + */ + size_t payload_size() const + { return Genode::min(_data_size-_data_offset(), + (size_t)MAX_PAYLOAD_SIZE); } + + size_t window_id() const { return _window_id; } + size_t window_length() const { return _window_length; } + size_t packet_id() const { return _packet_id; } }; class Remote_rom::Backend_server : @@ -19,101 +185,221 @@ class Remote_rom::Backend_server : { private: - Rom_forwarder_base *_forwarder; + friend class Content_sender; + + Content_sender _content_sender { _timer, *this }; Backend_server(Backend_server &); Backend_server &operator= (Backend_server &); - void send_data() - { - if (!_forwarder) return; + void send_packet(Content_sender const &sender); - size_t offset = 0; - size_t size = _forwarder->content_size(); - while (offset < size) - { - /* create and transmit packet via NIC session */ - size_t max_size = _upper_layer_size(sizeof(Packet) - + DataPacket::packet_size(size)); - Nic::Packet_descriptor pd = alloc_tx_packet(max_size); - Size_guard size_guard(pd.size()); - - char *content = _nic.tx()->packet_content(pd); - Ipv4_packet &ip = _prepare_upper_layers(content, size_guard); - Packet &pak = ip.construct_at_data(size_guard); - pak.type(Packet::DATA); - pak.module_name(_forwarder->module_name()); - - DataPacket &data = pak.construct_at_data(size_guard); - data.offset(offset); - data.content_size(size); - - const size_t max = DataPacket::MAX_PAYLOAD_SIZE; - data.payload_size(_forwarder->transfer_content((char*)data.addr(), - max, - offset)); - _finish_ipv4(ip, sizeof(Packet) + data.size()); - - submit_tx_packet(pd); - - offset += data.payload_size(); - } - } - - - void receive(Packet &packet, Size_guard &) - { - switch (packet.type()) - { - case Packet::UPDATE: - if (_verbose) - Genode::log("receiving UPDATE (", - Cstring(packet.module_name()), - ") packet"); - - if (!_forwarder) - return; - - /* check module name */ - if (Genode::strcmp(packet.module_name(), _forwarder->module_name())) - return; - - send_data(); - - break; - default: - break; - } - } + void receive(Packet &packet, Size_guard &); public: - Backend_server(Genode::Env &env, Genode::Allocator &alloc) : - Backend_base(env, alloc), - _forwarder(nullptr) - { } + Backend_server(Genode::Env &env, + Genode::Allocator &alloc, + Genode::Xml_node config, + Genode::Xml_node policy) + : Backend_base(env, alloc, config, policy) + { } void register_forwarder(Rom_forwarder_base *forwarder) - { - _forwarder = forwarder; - } + { _content_sender.register_forwarder(forwarder); } void send_update() { - if (!_forwarder) return; - _transmit_notification_packet(Packet::SIGNAL, _forwarder); + if (!_content_sender.content_size()) return; + + if (_verbose) + Genode::log("sending SIGNAL(", _content_sender.module_name(), ")"); + + /* TODO re-send SIGNAL packet after a timeout */ + transmit_notification(Packet::SIGNAL, _content_sender); } }; namespace Remote_rom { using Genode::Env; using Genode::Allocator; + using Genode::Xml_node; - Backend_server_base &backend_init_server(Env &env, Allocator &alloc) + Backend_server_base &backend_init_server(Env &env, + Allocator &alloc, + Xml_node config) { - static Backend_server backend(env, alloc); + static Backend_server backend(env, + alloc, + config, + config.sub_node("remote_rom")); return backend; } }; + +void Remote_rom::Backend_server::send_packet(Content_sender const &sender) +{ + /* create and transmit packet via NIC session */ + size_t const max_payload = sender.payload_size(); + size_t const max_size = sizeof(Ethernet_frame) + + sizeof(Ipv4_packet) + + sizeof(Udp_packet) + + sizeof(Packet) + + sizeof(DataPacket) + + max_payload; + Nic::Packet_descriptor pd = alloc_tx_packet(max_size); + Size_guard size_guard(pd.size()); + + char* const content = _nic.tx()->packet_content(pd); + Ethernet_frame ð = prepare_eth(content, size_guard); + + size_t const ip_off = size_guard.head_size(); + Ipv4_packet &ip = prepare_ipv4(eth, size_guard); + + size_t const udp_off = size_guard.head_size(); + Udp_packet &udp = prepare_udp(ip, size_guard); + + Packet &pak = udp.construct_at_data(size_guard); + pak.type(Packet::DATA); + pak.module_name(sender.module_name()); + pak.content_hash(sender.content_hash()); + + DataPacket &data = pak.construct_at_data(size_guard); + data.window_id(sender.window_id()); + data.window_length(sender.window_length()); + data.packet_id(sender.packet_id()); + + size_guard.consume_head(max_payload); + data.payload_size(sender.transfer_content((char*)data.addr(), + max_payload)); + + /* fill in header values that need the packet to be complete already */ + udp.length(size_guard.head_size() - udp_off); + if (!_chksum_offload) + udp.update_checksum(ip.src(), ip.dst()); + + ip.total_length(size_guard.head_size() - ip_off); + ip.update_checksum(); + + submit_tx_packet(pd); +} + +void Remote_rom::Backend_server::receive(Packet &packet, + Size_guard &size_guard) +{ + switch (packet.type()) + { + case Packet::UPDATE: + if (_verbose) + Genode::log("receiving UPDATE (", + Cstring(packet.module_name()), + ") packet"); + + /* check module name */ + if (Genode::strcmp(packet.module_name(), _content_sender.module_name())) + return; + + /* compare content hash */ + if (packet.content_hash() != _content_sender.content_hash()) { + if (_verbose) + Genode::log("ignoring UPDATE with invalid hash"); + return; + } + + if (_verbose) { + Genode::log("Sending data of size ", _content_sender.content_size()); + } + + _content_sender.transmit(true); + + break; + case Packet::SIGNAL: + if (_verbose) + Genode::log("ignoring SIGNAL"); + break; + case Packet::DATA: + if (_verbose) + Genode::log("ignoring DATA"); + break; + case Packet::ACK: + { + if (!_content_sender.transmitting()) + return; + + if (Genode::strcmp(packet.module_name(), _content_sender.module_name())) + return; + + if (packet.content_hash() != _content_sender.content_hash()) { + if (_verbose) + Genode::warning("ignoring ACK with wrong hash"); + return; + } + + AckPacket const &ack = packet.data(size_guard); + + if (ack.window_id() != _content_sender.window_id()) { + if (_verbose) + Genode::warning("ignoring ACK with wrong window id"); + return; + } + + if (ack.ack_until() < _content_sender.packet_id()) { + if (_verbose) + Genode::warning("Go back to packet id ", ack.ack_until()); + + _content_sender.go_back(ack.ack_until()); + } + + _content_sender.transmit(false); + + break; + } + default: + break; + } +} + +bool Remote_rom::Content_sender::transmit(bool restart) +{ + if (!_frontend) return false; + + if (_timeout.scheduled()) + _timeout.discard(); + + _errors = 0; + + if (restart) { + /* do not start if we are still transmitting */ + if (!_transmission_complete()) + return false; + + _frontend->start_transmission(); + reset(); + + _data_size = _frontend->content_size(); + _window_length = _calculate_window_size(_data_size); + } + else if (_window_complete()) { + if (!_next_window()) { + _frontend->finish_transmission(); + + if (transmitting()) { + reset(); + return true; + } + else + return false; + } + } + + do { + _backend.send_packet(*this); + } while (_next_packet()); + + /* set ACK timeout */ + _timeout.schedule(Microseconds(TIMEOUT_ACK_US)); + + return false; +} diff --git a/src/proxy/remote_rom/README b/src/proxy/remote_rom/README index 8dc387c..1df6c95 100644 --- a/src/proxy/remote_rom/README +++ b/src/proxy/remote_rom/README @@ -18,15 +18,20 @@ policy for this. :'nic_ip': This back end uses a Nic_session to transmit network packets with IPv4 - headers. + and UDP headers. Configuration ------------- Both the client and the server evaluate the '' node of their -config. The _name_ attribute specifies the ROMs module name. The -'' node may further contain a '' node that can be used to -populate the ROM with a default content. +config. The _name_ attribute specifies the ROMs module name. The source IP +address is specified by the _src_ attribute and the destination IP address +by the _dst_ attribute. The _dst_mac_ attribute may specify the destination +MAC address (default: broadcast). Attribute _udp_port_ specifies the +destination port (default: 9009). +A boolean _binary_ attribute can be used to switch between transmission of +the entire ROM dataspace (binary="true") or transmission of string content +using strlen. Example ~~~~~~~ diff --git a/src/proxy/remote_rom/client/main.cc b/src/proxy/remote_rom/client/main.cc index f39ceac..d623887 100644 --- a/src/proxy/remote_rom/client/main.cc +++ b/src/proxy/remote_rom/client/main.cc @@ -33,16 +33,18 @@ #include #include +#include namespace Remote_rom { using Genode::size_t; - using Genode::Constructible; + using Genode::Attached_ram_dataspace; + using Genode::Rom_dataspace_capability; using Genode::Signal_context_capability; class Session_component; class Root; struct Main; - struct Read_buffer; + struct Rom_module; typedef Genode::List_element Session_element; typedef Genode::List Session_list; @@ -53,10 +55,68 @@ namespace Remote_rom { * Interface used by the sessions to obtain the ROM data received from the * remote server */ -struct Remote_rom::Read_buffer : Genode::Interface +class Remote_rom::Rom_module { - virtual size_t content_size() const = 0; - virtual size_t export_content(char *dst, size_t dst_len) const = 0; + private: + Genode::Ram_allocator &_ram; + Attached_ram_dataspace _fg; /* dataspace delivered to clients */ + Attached_ram_dataspace _bg; /* dataspace for receiving data */ + + unsigned _bg_hash { 0 }; + size_t _bg_size { 0 }; + + public: + Rom_module(Genode::Ram_allocator &ram, Genode::Env &env) + : _ram(ram), + _fg(_ram, env.rm(), 0), + _bg(_ram, env.rm(), 4096) + { } + + Rom_dataspace_capability fg_dataspace() const + { + using namespace Genode; + + if (!_fg.size()) + return Rom_dataspace_capability(); + + Dataspace_capability ds_cap = _fg.cap(); + return static_cap_cast(ds_cap); + } + + /** + * Return pointer to buffer that is ready to be filled with data. + * + * Data is written into the background dataspace. + * Once it is ready, the 'commit_bg()' function is called. + */ + char* base(size_t size) + { + /* let background buffer grow if needed */ + if (_bg.size() < size) + _bg.realloc(&_ram, size); + + _bg_size = size; + return _bg.local_addr(); + } + + /** + * Commit data contained in background dataspace + * (swap foreground and background dataspace) + */ + bool commit_bg() + { + if (_bg_hash != cksum(_bg.local_addr(), _bg_size)) { + Genode::error("checksum error"); + return false; + } + + _fg.swap(_bg); + return true; + } + + unsigned hash() const { return _bg_hash; } + void hash(unsigned v) { _bg_hash = v; } + }; class Remote_rom::Session_component : @@ -67,22 +127,19 @@ class Remote_rom::Session_component : Signal_context_capability _sigh; - Read_buffer const &_buffer; + Rom_module const &_rom_module; Session_list &_sessions; Session_element _element; - Constructible _ram_ds; - public: static int version() { return 1; } Session_component(Genode::Env &env, Session_list &sessions, - Read_buffer const &buffer) + Rom_module const &rom_module) : - _env(env), _sigh(), _buffer(buffer), _sessions(sessions), _element(this), - _ram_ds() + _env(env), _sigh(), _rom_module(rom_module), _sessions(sessions), _element(this) { _sessions.insert(&_element); } @@ -101,27 +158,7 @@ class Remote_rom::Session_component : Genode::Rom_dataspace_capability dataspace() override { - using namespace Genode; - - /* replace dataspace by new one as needed */ - if (!_ram_ds.is_constructed() - || _buffer.content_size() > _ram_ds->size()) { - - _ram_ds.construct(_env.ram(), _env.rm(), _buffer.content_size()); - } - - char *dst = _ram_ds->local_addr(); - size_t const dst_size = _ram_ds->size(); - - /* fill with content of current evaluation result */ - size_t const copied_len = _buffer.export_content(dst, dst_size); - - /* clear remainder of dataspace */ - Genode::memset(dst + copied_len, 0, dst_size - copied_len); - - /* cast RAM into ROM dataspace capability */ - Dataspace_capability ds_cap = static_cap_cast(_ram_ds->cap()); - return static_cap_cast(ds_cap); + return _rom_module.fg_dataspace(); } void sigh(Genode::Signal_context_capability sigh) override @@ -130,13 +167,12 @@ class Remote_rom::Session_component : } }; - class Remote_rom::Root : public Genode::Root_component { private: Genode::Env &_env; - Read_buffer &_buffer; + Rom_module &_rom_module; Session_list _sessions; protected: @@ -150,16 +186,16 @@ class Remote_rom::Root : public Genode::Root_component * */ return new (Root::md_alloc()) - Session_component(_env, _sessions, _buffer); + Session_component(_env, _sessions, _rom_module); } public: - Root(Genode::Env &env, Genode::Allocator &md_alloc, Read_buffer &buffer) + Root(Genode::Env &env, Genode::Allocator &md_alloc, Rom_module &rom_module) : Genode::Root_component(&env.ep().rpc_ep(), &md_alloc), _env(env), - _buffer(buffer), + _rom_module(rom_module), _sessions() { } @@ -170,14 +206,12 @@ class Remote_rom::Root : public Genode::Root_component } }; -struct Remote_rom::Main : public Read_buffer, public Rom_receiver_base +struct Remote_rom::Main : public Rom_receiver_base { Genode::Env &env; - Genode::Heap heap = { &env.ram(), &env.rm() }; - Root remote_rom_root = { env, heap, *this }; - - Genode::Constructible _ds; - size_t _ds_content_size; + Genode::Heap heap { &env.ram(), &env.rm() }; + Rom_module rom_module { env.ram(), env }; + Root remote_rom_root { env, heap, rom_module }; Genode::Attached_rom_dataspace _config = { env, "config" }; @@ -186,8 +220,8 @@ struct Remote_rom::Main : public Read_buffer, public Rom_receiver_base char remotename[255]; Main(Genode::Env &env) : - env(env), _ds(), _ds_content_size(1024), - _backend(backend_init_client(env, heap)) + env(env), + _backend(backend_init_client(env, heap, _config.xml())) { try { Genode::Xml_node remote_rom = _config.xml().sub_node("remote_rom"); @@ -202,71 +236,27 @@ struct Remote_rom::Main : public Read_buffer, public Rom_receiver_base _backend.register_receiver(this); } - const char* module_name() const { return remotename; } + const char* module_name() const { return remotename; } + unsigned content_hash() const { return rom_module.hash(); } - char* start_new_content(size_t len) + char* start_new_content(unsigned hash, size_t len) { - /* Create buffer for new data */ - _ds_content_size = len; + /* save expected hash */ + /* TODO (optional) skip if we already have the same data */ + rom_module.hash(hash); - // TODO (optional) implement double buffering - if (!_ds.is_constructed() || _ds_content_size > _ds->size()) - _ds.construct(env.ram(), env.rm(), _ds_content_size); - - // TODO set write lock - - return _ds->local_addr(); + return rom_module.base(len); } void commit_new_content(bool abort=false) { if (abort) - Genode::error("abort not supported"); + return; - // TODO release write lock - - remote_rom_root.notify_clients(); + if (rom_module.commit_bg()) + remote_rom_root.notify_clients(); } - size_t content_size() const - { - if (_ds.is_constructed()) { - return _ds_content_size; - } - else { - /* transfer default content if set */ - try { - Genode::Xml_node default_content = _config.xml(). - sub_node("remote_rom"). - sub_node("default"); - return default_content.content_size(); - } catch (...) { } - } - - return 0; - } - - size_t export_content(char *dst, size_t dst_len) const - { - if (_ds.is_constructed()) { - size_t const len = Genode::min(dst_len, _ds_content_size); - Genode::memcpy(dst, _ds->local_addr(), len); - return len; - } - else { - /* transfer default content if set */ - try { - Genode::Xml_node default_content = _config.xml(). - sub_node("remote_rom"). - sub_node("default"); - size_t const len = Genode::min(dst_len, - default_content.content_size()); - Genode::memcpy(dst, default_content.content_base(), len); - return len; - } catch (...) { } - } - return 0; - } }; namespace Component { diff --git a/src/proxy/remote_rom/server/main.cc b/src/proxy/remote_rom/server/main.cc index e8cdd9c..1807563 100644 --- a/src/proxy/remote_rom/server/main.cc +++ b/src/proxy/remote_rom/server/main.cc @@ -21,11 +21,12 @@ #include #include -#include - #include #include +#include +#include + namespace Remote_rom { using Genode::size_t; using Genode::Attached_rom_dataspace; @@ -34,7 +35,6 @@ namespace Remote_rom { struct Main; static char modulename[255]; - static char remotename[255]; static bool binary = false; }; @@ -44,6 +44,10 @@ struct Remote_rom::Rom_forwarder : Rom_forwarder_base Attached_rom_dataspace &_rom; Backend_server_base &_backend; + unsigned _current_hash { 0 }; + bool _transmitting { false }; + bool _update_received { false }; + Attached_rom_dataspace &_config; Rom_forwarder(Attached_rom_dataspace &rom, Backend_server_base &backend, @@ -56,17 +60,42 @@ struct Remote_rom::Rom_forwarder : Rom_forwarder_base update(); } - const char *module_name() const { return remotename; } + void start_transmission() { _transmitting = true; } + void finish_transmission() + { + _transmitting = false; + + /* if we received and update during transmission, + * we are now able to process it */ + if (_update_received) + update(); + } + + const char *module_name() const { return modulename; } void update() { - /* TODO don't update ROM if a transfer is still in progress */ + if (_transmitting) { + /* if transmission is ongoing, do not update ROM */ + _update_received = true; + return; + } + _update_received = false; - /* refresh dataspace if valid*/ + /* refresh dataspace if valid */ _rom.update(); - /* trigger backend_server */ - _backend.send_update(); + if (_rom.is_valid()) { + _current_hash = cksum(_rom.local_addr(), content_size()); + + /* trigger backend_server */ + _backend.send_update(); + } + } + + unsigned content_hash() const + { + return _current_hash; } size_t content_size() const @@ -75,17 +104,9 @@ struct Remote_rom::Rom_forwarder : Rom_forwarder_base if (binary) return _rom.size(); else - return Genode::min(1+Genode::strlen(_rom.local_addr()), + return Genode::min(Genode::strlen(_rom.local_addr()), _rom.size()); } - else { - try { - Genode::Xml_node default_content = _config.xml(). - sub_node("remote_rom"). - sub_node("default"); - return default_content.content_size(); - } catch (...) { } - } return 0; } @@ -94,21 +115,12 @@ struct Remote_rom::Rom_forwarder : Rom_forwarder_base if (_rom.is_valid()) { size_t const len = Genode::min(dst_len, content_size()-offset); Genode::memcpy(dst, _rom.local_addr() + offset, len); - return len; + /* clear remaining buffer to prevent data leakage */ + if (dst_len > len) { + Genode::memset(dst + len, 0, dst_len-len); + } + return dst_len; } - else { - /* transfer default content if set */ - try { - Genode::Xml_node default_content = _config.xml(). - sub_node("remote_rom"). - sub_node("default"); - size_t const remainder = default_content.content_size()-offset; - size_t const len = Genode::min(dst_len, remainder); - Genode::memcpy(dst, default_content.content_base() + offset, len); - return len; - } catch (...) { } - } - return 0; } }; @@ -128,7 +140,8 @@ struct Remote_rom::Main Main(Genode::Env &env) : _env(env), _rom(env, modulename), - _forwarder(_rom, backend_init_server(env, _heap), _config) + _forwarder(_rom, backend_init_server(env, _heap, _config.xml()), + _config) { /* register update dispatcher */ _rom.sigh(_dispatcher); @@ -142,18 +155,11 @@ namespace Component { void construct(Genode::Env &env) { using Remote_rom::modulename; - using Remote_rom::remotename; Genode::Attached_rom_dataspace config = { env, "config" }; try { Genode::Xml_node remote_rom = config.xml().sub_node("remote_rom"); - if (remote_rom.has_attribute("localname")) - remote_rom.attribute("localname").value(modulename, - sizeof(modulename)); - else - remote_rom.attribute("name").value(modulename, sizeof(modulename)); - - remote_rom.attribute("name").value(remotename, sizeof(remotename)); + remote_rom.attribute("name").value(modulename, sizeof(modulename)); try { remote_rom.attribute("binary").value(&Remote_rom::binary); } catch (...) { }