diff --git a/src/common/util/logging.h b/src/common/util/logging.h index 72096f4fb..b1b0175f0 100644 --- a/src/common/util/logging.h +++ b/src/common/util/logging.h @@ -349,7 +349,6 @@ template std::ostream& operator<<(std::ostream& stream, const std:: } // namespace logging /** @} */ #undef LEVELS -#undef CAT #ifndef NDEBUG //! check only in debug mode diff --git a/src/interfaces/CMakeLists.txt b/src/interfaces/CMakeLists.txt index 4bbd4f587..ab2878a3e 100644 --- a/src/interfaces/CMakeLists.txt +++ b/src/interfaces/CMakeLists.txt @@ -12,6 +12,7 @@ set(LIB_SOURCES ocp/ocp_tlm.cpp tilelink/tl_tlm.cpp cxs/cxs_tlm.cpp + eth/eth_tlm.cpp ) add_library(${PROJECT_NAME} ${LIB_SOURCES}) diff --git a/src/interfaces/eth/eth_tlm.cpp b/src/interfaces/eth/eth_tlm.cpp new file mode 100644 index 000000000..77680352f --- /dev/null +++ b/src/interfaces/eth/eth_tlm.cpp @@ -0,0 +1,57 @@ +#include "eth_tlm.h" +namespace eth { +ethernet_frame ethernet_frame::parse_ethernet(nonstd::span frame) { + ethernet_frame eth_frame{}; + + if(frame.size() < sizeof(ethernet_header)) + throw std::runtime_error("frame too small for Ethernet header"); + + // Copy header safely (no unaligned access) + std::memcpy(ð_frame.l2, frame.data(), sizeof(ethernet_header)); + std::uint16_t t = bswap16(eth_frame.l2.type_or_len); + + std::size_t offset = sizeof(ethernet_header); + + // VLAN TPIDs commonly: 0x8100 (802.1Q), 0x88A8 (802.1ad/QinQ) + auto is_vlan_tpid = [](std::uint16_t x) { return x == 0x8100 || x == 0x88A8; }; + + if(is_vlan_tpid(t)) { + if(frame.size() < offset + sizeof(vlan_tag_8021Q) + 2) + throw std::runtime_error("frame too small for VLAN tag"); + + vlan_tag_8021Q tag{}; + std::memcpy(&tag, frame.data() + offset, sizeof(tag)); + eth_frame.has_vlan = true; + eth_frame.vlan_tpid = t; + + std::uint16_t tci = bswap16(tag.tci); + eth_frame.vlan_pcp = (tci >> 13) & 0x7; + eth_frame.vlan_dei = ((tci >> 12) & 0x1) != 0; + eth_frame.vlan_id = tci & 0x0FFF; + + offset += sizeof(vlan_tag_8021Q); + + // Next 2 bytes after VLAN tag is the *real* EtherType/Len + std::uint16_t inner{}; + std::memcpy(&inner, frame.data() + offset, sizeof(inner)); + t = bswap16(inner); + offset += sizeof(inner); + } + + // Distinguish Ethernet II EtherType vs 802.3 length: + // <=1500 => length, >=1536 (0x0600) => EtherType + if(t <= 1500) { + eth_frame.kind = ether_kind::LENGTH_8023; + eth_frame.etherType_or_len = t; + } else { + eth_frame.kind = ether_kind::ETHER_TYPE; + eth_frame.etherType_or_len = t; + } + + if(frame.size() < offset) + throw std::runtime_error("internal parse error (offset beyond frame)"); + + eth_frame.payload = frame.subspan(offset); + return eth_frame; +} +} // namespace eth \ No newline at end of file diff --git a/src/interfaces/eth/eth_tlm.h b/src/interfaces/eth/eth_tlm.h new file mode 100644 index 000000000..1fb47ab89 --- /dev/null +++ b/src/interfaces/eth/eth_tlm.h @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright 2024 MINRES Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef _ETH_ETH_TLM_H_ +#define _ETH_ETH_TLM_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//! @brief ETH TLM utilities +namespace eth { + +inline std::uint16_t bswap16(std::uint16_t x) { return static_cast((x >> 8) | (x << 8)); } + +#pragma pack(push, 1) +struct ethernet_header { + std::array dst; + std::array src; + std::uint16_t type_or_len; // big-endian on the wire (e.g., 0x0800 IPv4, 0x86DD IPv6) +}; + +struct vlan_tag_8021Q { + std::uint16_t tpid; // 0x8100 (big-endian) + std::uint16_t tci; // PCP(3) | DEI(1) | VID(12) (big-endian) +}; +#pragma pack(pop) + +static_assert(sizeof(ethernet_header) == 14); +static_assert(sizeof(vlan_tag_8021Q) == 4); + +enum class ether_kind { ETHER_TYPE, LENGTH_8023 }; + +struct ethernet_frame { + ethernet_header l2; // original 14-byte header + bool has_vlan = false; + std::uint16_t vlan_tpid = 0; // host order (e.g., 0x8100) + std::uint16_t vlan_id = 0; // 0..4095 + std::uint16_t vlan_pcp = 0; // 0..7 + bool vlan_dei = false; + + ether_kind kind = ether_kind::ETHER_TYPE; + std::uint16_t etherType_or_len = 0; // host order: EtherType or 802.3 length + nonstd::span payload; + static ethernet_frame parse_ethernet(nonstd::span frame); +}; + +////////////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////////////// +enum class ETH { FRAME }; +struct eth_packet_payload : public tlm::nw::tlm_network_payload { + eth_packet_payload() { m_data.resize(1536); }; + + explicit eth_packet_payload(tlm::nw::tlm_base_mm_interface* mm) + : tlm::nw::tlm_network_payload(mm) { + m_data.resize(1536); + } + + const sc_core::sc_time& get_sender_clk_period() const { return sender_clk_period; } + + void set_sender_clk_period(sc_core::sc_time period) { this->sender_clk_period = period; } + +private: + sc_core::sc_time sender_clk_period{sc_core::SC_ZERO_TIME}; +}; + +struct eth_packet_types { + using tlm_payload_type = eth_packet_payload; + using tlm_phase_type = ::tlm::tlm_phase; +}; + +} // namespace eth + +namespace tlm { +namespace scc { +// provide needed info for SCC memory manager +template <> struct tlm_mm_traits { + using mm_if_type = tlm::nw::tlm_base_mm_interface; + using payload_base = tlm::nw::tlm_network_payload_base; +}; +} // namespace scc +} // namespace tlm + +namespace eth { +template using eth_pkt_initiator_socket = tlm::nw::tlm_network_initiator_socket<1, ETH, eth_packet_types, N>; +template using eth_pkt_target_socket = tlm::nw::tlm_network_target_socket<1, ETH, eth_packet_types, N>; +using eth_pkt_shared_ptr = tlm::scc::tlm_payload_shared_ptr; +using eth_pkt_mm = tlm::scc::tlm_mm; + +struct eth_channel : public sc_core::sc_module, + public tlm::nw::tlm_network_fw_transport_if, + public tlm::nw::tlm_network_bw_transport_if { + + using transaction_type = eth_packet_types::tlm_payload_type; + using phase_type = eth_packet_types::tlm_phase_type; + + eth_pkt_target_socket<> tsck{"tsck"}; + + eth_pkt_initiator_socket<> isck{"isck"}; + + cci::cci_param channel_delay{"channel_delay", sc_core::SC_ZERO_TIME, "delay of the SPI channel"}; + + eth_channel(sc_core::sc_module_name const& nm) + : sc_core::sc_module(nm) + , isck{"isckt"} { + isck(*this); + tsck(*this); + } + + void b_transport(transaction_type& trans, sc_core::sc_time& t) override { + t += channel_delay; + isck->b_transport(trans, t); + } + + tlm::tlm_sync_enum nb_transport_fw(transaction_type& trans, phase_type& phase, sc_core::sc_time& t) override { + SCCTRACE(SCMOD) << "Received non-blocking transaction in fw path with phase " << phase.get_name(); + if(phase == tlm::nw::REQUEST) { + phase_type ph = tlm::nw::INDICATION; + auto ret = isck->nb_transport_fw(trans, ph, t); + if(ph == tlm::nw::RESPONSE) + phase = tlm::nw::CONFIRM; + return ret; + } + return tlm::TLM_ACCEPTED; + } + + tlm::tlm_sync_enum nb_transport_bw(transaction_type& trans, phase_type& phase, sc_core::sc_time& t) override { + SCCTRACE(SCMOD) << "Received non-blocking transaction in bw path with phase " << phase.get_name(); + if(phase == tlm::nw::RESPONSE) { + phase_type ph = tlm::nw::CONFIRM; + return tsck->nb_transport_bw(trans, ph, t); + } + return tlm::TLM_ACCEPTED; + } + + unsigned int transport_dbg(transaction_type& trans) override { return isck->transport_dbg(trans); } +}; +} // namespace eth +#endif // _ETH_ETH_TLM_H_